diff --git a/.asf.yaml b/.asf.yaml index 2f645d8c99ed..b78168cab4b7 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -17,8 +17,26 @@ # under the License. # +# Please refer https://cwiki.apache.org/confluence/display/INFRA/Git+-+.asf.yaml+features + github: description: "Apache Pinot - A realtime distributed OLAP datastore" homepage: https://pinot.apache.org/ labels: - java + del_branch_on_merge: true + enabled_merge_buttons: + squash: true + merge: false + rebase: false + collaborators: + - sullis + - shenyu0127 + - tibrewalpratik17 + - abhioncbr + - zhtaoxiang + - shounakmk219 + - itschrispeck + - soumitra-st + - swaminathanmanish + - yashmayya diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d135c26b02cc..9719e1a53392 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -24,6 +24,7 @@ updates: directory: "/" schedule: interval: "daily" + open-pull-requests-limit: 20 - package-ecosystem: "npm" directory: "/pinot-controller/src/main/resources" diff --git a/.github/workflows/build-multi-arch-pinot-docker-image.yml b/.github/workflows/build-multi-arch-pinot-docker-image.yml new file mode 100644 index 000000000000..e3314bec9c12 --- /dev/null +++ b/.github/workflows/build-multi-arch-pinot-docker-image.yml @@ -0,0 +1,108 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +name: Pinot Multi-Arch Platform Docker Image Build and Publish + +on: + workflow_dispatch: + inputs: + gitUrl: + description: "The Pinot git repo to check out to build, use https." + default: "https://github.com/apache/pinot.git" + required: true + commit: + description: "The branch/commit to check out to build Pinot image." + default: "master" + required: true + dockerImageName: + description: "The docker image name, default to 'apachepinot/pinot'." + default: "apachepinot/pinot" + required: true + tags: + description: "Tags to push of the image, comma separated, e.g. tag1,tag2,tag3" + default: "" + +jobs: + generate-build-info: + name: Generate Build Info + runs-on: ubuntu-latest + outputs: + commit-id: ${{ steps.generate-build-info.outputs.commit-id }} + tags: ${{ steps.generate-build-info.outputs.tags }} + steps: + - uses: actions/checkout@v4 + - name: Generate Build Info + id: generate-build-info + env: + PINOT_GIT_URL: ${{ github.event.inputs.gitUrl }} + PINOT_BRANCH: ${{ github.event.inputs.commit }} + TAGS: ${{ github.event.inputs.tags }} + run: | + .github/workflows/scripts/docker/.pinot_build_info_gen.sh + build-pinot-docker-image: + runs-on: ubuntu-latest + strategy: + matrix: + arch: [ "amd64", "arm64" ] + base-image-tag: [ "11-amazoncorretto", "11-ms-openjdk" ] + name: Build Pinot Docker Image on ${{ matrix.arch }} with base image ${{ matrix.base-image-tag }} + needs: [ generate-build-info ] + steps: + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - uses: docker/setup-qemu-action@v3 + name: Set up QEMU + with: + platforms: linux/${{ matrix.arch }} + - uses: docker/setup-buildx-action@v3 + name: Set up Docker Buildx + - uses: actions/checkout@v4 + - name: Build and push the Docker image + env: + DOCKER_FILE_BASE_DIR: "docker/images/pinot" + DOCKER_IMAGE_NAME: ${{ github.event.inputs.dockerImageName }} + BUILD_PLATFORM: "linux/${{ matrix.arch }}" + BASE_IMAGE_TAG: ${{ matrix.base-image-tag }} + PINOT_GIT_URL: ${{ github.event.inputs.gitUrl }} + PINOT_BRANCH: "${{needs.generate-build-info.outputs.commit-id}}" + TAGS: "${{needs.generate-build-info.outputs.tags}}" + run: .github/workflows/scripts/docker/.pinot_single_platform_docker_image_build.sh + create-multi-arch-manifest: + name: Create Multi-Arch Manifest + runs-on: ubuntu-latest + needs: [ generate-build-info, build-pinot-docker-image ] + steps: + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - uses: docker/setup-qemu-action@v3 + name: Set up QEMU + - uses: docker/setup-buildx-action@v3 + name: Set up Docker Buildx + - uses: actions/checkout@v4 + - name: Create Multi-Arch Manifest + env: + TAGS: "${{needs.generate-build-info.outputs.tags}}" + BUILD_PLATFORM: "linux/arm64,linux/amd64" + BASE_IMAGE_TAGS: "11-amazoncorretto,11-ms-openjdk" + run: .github/workflows/scripts/docker/.pinot_multi_arch_docker_image_manifest_package.sh diff --git a/.github/workflows/build-pinot-base-docker-image.yml b/.github/workflows/build-pinot-base-docker-image.yml new file mode 100644 index 000000000000..e079fde1c304 --- /dev/null +++ b/.github/workflows/build-pinot-base-docker-image.yml @@ -0,0 +1,50 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +name: Pinot Base Docker Image Build and Publish + +on: + workflow_dispatch: + inputs: { } + +jobs: + build-pinot-build-docker-image: + name: Build Pinot Base Docker Image + runs-on: ubuntu-latest + strategy: + matrix: + baseImageType: [ "build", "runtime" ] + openJdkDist: [ "amazoncorretto", "ms-openjdk" ] + steps: + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - uses: docker/setup-qemu-action@v3 + name: Set up QEMU + - uses: docker/setup-buildx-action@v3 + name: Set up Docker Buildx + - uses: actions/checkout@v4 + - name: Build and push the Docker image + env: + OPEN_JDK_DIST: ${{ matrix.openJdkDist }} + BASE_IMAGE_TYPE: ${{ matrix.baseImageType }} + BUILD_PLATFORM: "linux/amd64,linux/arm64" + TAG: "11-${{ matrix.openJdkDist }}" + run: .github/workflows/scripts/docker/.pinot_base_docker_image_build_and_push.sh diff --git a/.github/workflows/build-pinot-docker-image.yml b/.github/workflows/build-pinot-docker-image.yml index 345440750e97..c21e8bbadd6e 100644 --- a/.github/workflows/build-pinot-docker-image.yml +++ b/.github/workflows/build-pinot-docker-image.yml @@ -47,15 +47,15 @@ jobs: runs-on: ubuntu-latest steps: - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - uses: docker/setup-qemu-action@v1 + - uses: docker/setup-qemu-action@v3 name: Set up QEMU - - uses: docker/setup-buildx-action@v1 + - uses: docker/setup-buildx-action@v3 name: Set up Docker Buildx - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build and push the Docker image env: DOCKER_FILE_BASE_DIR: "docker/images/pinot" diff --git a/.github/workflows/build-presto-docker-image.yml b/.github/workflows/build-presto-docker-image.yml index 7ae35253615d..d054074f1551 100644 --- a/.github/workflows/build-presto-docker-image.yml +++ b/.github/workflows/build-presto-docker-image.yml @@ -47,15 +47,15 @@ jobs: runs-on: ubuntu-latest steps: - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - uses: docker/setup-qemu-action@v1 + - uses: docker/setup-qemu-action@v3 name: Set up QEMU - - uses: docker/setup-buildx-action@v1 + - uses: docker/setup-buildx-action@v3 name: Set up Docker Buildx - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build and push the Docker image env: DOCKER_FILE_BASE_DIR: "docker/images/pinot-presto" diff --git a/.github/workflows/build-superset-docker-image.yml b/.github/workflows/build-superset-docker-image.yml index c0e871991691..9cc362d7c502 100644 --- a/.github/workflows/build-superset-docker-image.yml +++ b/.github/workflows/build-superset-docker-image.yml @@ -43,15 +43,15 @@ jobs: runs-on: ubuntu-latest steps: - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - uses: docker/setup-qemu-action@v1 + - uses: docker/setup-qemu-action@v3 name: Set up QEMU - - uses: docker/setup-buildx-action@v1 + - uses: docker/setup-buildx-action@v3 name: Set up Docker Buildx - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build and push the Docker image env: DOCKER_FILE_BASE_DIR: "docker/images/pinot-superset" diff --git a/.github/workflows/pinot_compatibility_tests.yml b/.github/workflows/pinot_compatibility_tests.yml index acb0b1974caa..ecbc2300e01e 100644 --- a/.github/workflows/pinot_compatibility_tests.yml +++ b/.github/workflows/pinot_compatibility_tests.yml @@ -36,11 +36,23 @@ jobs: test_suite: [ "compatibility-verifier/sample-test-suite" ] name: Pinot Compatibility Regression Testing against ${{ github.event.inputs.oldCommit }} and ${{ github.event.inputs.newCommit }} on ${{ matrix.test_suite }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up JDK 11 - uses: actions/setup-java@v1 + uses: actions/setup-java@v4 with: java-version: 11 + distribution: 'temurin' + cache: 'maven' + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: v16.15.0 + cache: 'npm' + cache-dependency-path: pinot-controller/src/main/resources/package-lock.json + - name: Install npm + run: | + npm install -g npm@8.5.5 + npm --version - name: Pinot Compatibility Regression Testing if : ${{github.event_name == 'workflow_dispatch'}} env: @@ -50,7 +62,7 @@ jobs: TEST_SUITE: ${{ matrix.test_suite }} MAVEN_OPTS: > -Xmx2G -DskipShade -DfailIfNoTests=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=25 - -Dmaven.wagon.http.retryHandler.count=3 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false + -Dmaven.wagon.http.retryHandler.count=30 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false -XX:+IgnoreUnrecognizedVMOptions --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED @@ -62,7 +74,7 @@ jobs: if: always() run: | zip -1 -r artifacts.zip /tmp/compatibility-verifier/* - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 name: Store compatibility verifier work directory if: always() with: diff --git a/.github/workflows/pinot_multi_stage_query_engine_compatibility_tests.yml b/.github/workflows/pinot_multi_stage_query_engine_compatibility_tests.yml new file mode 100644 index 000000000000..52b7773033de --- /dev/null +++ b/.github/workflows/pinot_multi_stage_query_engine_compatibility_tests.yml @@ -0,0 +1,84 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +name: Pinot Multi-Stage Query Engine Compatibility Regression Test + +on: + workflow_dispatch: + inputs: + oldCommit: + description: "Git hash (or tag) for old commit. (required)" + required: true + newCommit: + description: "Git hash (or tag) for new commit. (required)" + required: true + +jobs: + compatibility-verifier: + runs-on: ubuntu-latest + strategy: + matrix: + test_suite: [ "compatibility-verifier/multi-stage-query-engine-test-suite" ] + name: Pinot Multi-Stage Query Engine Compatibility Regression Testing against ${{ github.event.inputs.oldCommit }} and ${{ github.event.inputs.newCommit }} on ${{ matrix.test_suite }} + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 11 + uses: actions/setup-java@v4 + with: + java-version: 11 + distribution: 'temurin' + cache: 'maven' + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: v16.15.0 + cache: 'npm' + cache-dependency-path: pinot-controller/src/main/resources/package-lock.json + - name: Install npm + run: | + npm install -g npm@8.5.5 + npm --version + - name: Pinot Multi-Stage Query Engine Compatibility Regression Testing + if : ${{github.event_name == 'workflow_dispatch'}} + env: + OLD_COMMIT: ${{ github.event.inputs.oldCommit }} + NEW_COMMIT: ${{ github.event.inputs.newCommit }} + WORKING_DIR: /tmp/multi-stage-compatibility-verifier + TEST_SUITE: ${{ matrix.test_suite }} + MAVEN_OPTS: > + -Xmx2G -DskipShade -DfailIfNoTests=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=25 + -Dmaven.wagon.http.retryHandler.count=30 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false + -XX:+IgnoreUnrecognizedVMOptions + --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + run: .github/workflows/scripts/.pinot_compatibility_verifier.sh + - name: Archive artifacts into zip + if: always() + run: | + zip -1 -r artifacts.zip /tmp/multi-stage-compatibility-verifier/* + - uses: actions/upload-artifact@v4 + name: Store multi-stage compatibility verifier work directory + if: always() + with: + ## TODO: currently matrix.test_suite cannot be used as part of name due to invalid path character. + name: multi_stage_compatibility_verifier_work_dir + retention-days: 3 + path: artifacts.zip diff --git a/.github/workflows/pinot_tests.yml b/.github/workflows/pinot_tests.yml index b3331fc7d446..ef2828d549b9 100644 --- a/.github/workflows/pinot_tests.yml +++ b/.github/workflows/pinot_tests.yml @@ -49,16 +49,20 @@ jobs: runs-on: ubuntu-latest name: Pinot Linter Test Set steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up JDK 11 - uses: actions/setup-java@v1 + uses: actions/setup-java@v4 with: java-version: 11 + distribution: 'temurin' + cache: 'maven' - name: Linter Test env: + GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} MAVEN_OPTS: > -Xmx2G -DskipShade -DfailIfNoTests=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=25 - -Dmaven.wagon.http.retryHandler.count=3 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false + -Dmaven.wagon.http.retryHandler.count=30 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false + -B -ntp -XX:+IgnoreUnrecognizedVMOptions --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED @@ -70,17 +74,33 @@ jobs: if: github.repository == 'apache/pinot' runs-on: ubuntu-latest strategy: + # Changed to false in order to improve coverage using unsafe buffers + fail-fast: false matrix: testset: [ 1, 2 ] - name: Pinot Unit Test Set ${{ matrix.testset }} + java: [ 11, 21 ] + distribution: [ "temurin" ] + skip_bytebuffer: [false] + include: + - java: 21 + testset: 1 + distribution: "temurin" + skip_bytebuffer: true + - java: 21 + testset: 2 + distribution: "temurin" + skip_bytebuffer: true + name: Pinot Unit Test Set ${{ matrix.testset }} (${{matrix.distribution}}-${{matrix.java}}) with skip bytebuffers:${{matrix.skip_bytebuffer}} steps: - - uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v1 + - uses: actions/checkout@v4 + - name: Set up JDK ${{ matrix.java }}-${{ matrix.distribution }} + uses: actions/setup-java@v4 with: - java-version: 11 + java-version: ${{ matrix.java }} + distribution: ${{ matrix.distribution }} + cache: 'maven' # Step that does that actual cache save and restore - - uses: actions/cache@v3 + - uses: actions/cache@v4 env: SEGMENT_DOWNLOAD_TIMEOUT_MINS: 10 with: @@ -88,24 +108,45 @@ jobs: key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- + - name: Build Project + env: + RUN_INTEGRATION_TESTS: false + RUN_TEST_SET: ${{ matrix.testset }} + GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} + MAVEN_OPTS: > + -Xmx2G -DskipShade -DfailIfNoTests=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=25 + -Dmaven.wagon.http.retryHandler.count=30 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false + -B -ntp + -XX:+IgnoreUnrecognizedVMOptions + --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + run: .github/workflows/scripts/pr-tests/.pinot_tests_build.sh - name: Unit Test env: RUN_INTEGRATION_TESTS: false RUN_TEST_SET: ${{ matrix.testset }} + PINOT_OFFHEAP_SKIP_BYTEBUFFER: ${{ matrix.skip_bytebuffer }} + GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} MAVEN_OPTS: > -Xmx2G -DskipShade -DfailIfNoTests=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=25 - -Dmaven.wagon.http.retryHandler.count=3 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false + -Dmaven.wagon.http.retryHandler.count=30 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false + -B -ntp -XX:+IgnoreUnrecognizedVMOptions --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED - run: .github/workflows/scripts/.pinot_test.sh + run: .github/workflows/scripts/pr-tests/.pinot_tests_unit.sh - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 + continue-on-error: true + timeout-minutes: 5 with: - flags: unittests${{ matrix.testset }} + flags: unittests,unittests${{ matrix.testset }},${{matrix.distribution}},java-${{matrix.java}},skip-bytebuffers-${{matrix.skip_bytebuffer}} name: codecov-unit-tests fail_ci_if_error: false verbose: true @@ -114,17 +155,33 @@ jobs: if: github.repository == 'apache/pinot' runs-on: ubuntu-latest strategy: + # Changed to false in order to improve coverage using unsafe buffers + fail-fast: false matrix: testset: [ 1, 2 ] - name: Pinot Integration Test Set ${{ matrix.testset }} + java: [ 11, 21 ] + distribution: [ "temurin" ] + skip_bytebuffer: [false] + include: + - java: 21 + testset: 1 + distribution: "temurin" + skip_bytebuffer: true + - java: 21 + testset: 2 + distribution: "temurin" + skip_bytebuffer: true + name: Pinot Integration Test Set ${{ matrix.testset }} (${{matrix.distribution}}-${{matrix.java}}) with skip bytebuffers:${{matrix.skip_bytebuffer}} steps: - - uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v1 + - uses: actions/checkout@v4 + - name: Set up JDK ${{ matrix.java }}-${{ matrix.distribution }} + uses: actions/setup-java@v4 with: - java-version: 11 + java-version: ${{ matrix.java }} + distribution: ${{ matrix.distribution }} + cache: 'maven' # Step that does that actual cache save and restore - - uses: actions/cache@v3 + - uses: actions/cache@v4 env: SEGMENT_DOWNLOAD_TIMEOUT_MINS: 10 with: @@ -132,44 +189,107 @@ jobs: key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- + - name: Build Project + env: + RUN_INTEGRATION_TESTS: true + GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} + MAVEN_OPTS: > + -Xmx2G -DskipShade -DfailIfNoTests=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=25 + -Dmaven.wagon.http.retryHandler.count=30 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false + -B -ntp + -XX:+IgnoreUnrecognizedVMOptions + --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + run: .github/workflows/scripts/pr-tests/.pinot_tests_build.sh - name: Integration Test env: RUN_INTEGRATION_TESTS: true RUN_TEST_SET: ${{ matrix.testset }} + PINOT_OFFHEAP_SKIP_BYTEBUFFER: ${{ matrix.skip_bytebuffer }} + GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} MAVEN_OPTS: > -Xmx2G -DskipShade -DfailIfNoTests=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=25 - -Dmaven.wagon.http.retryHandler.count=3 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false + -Dmaven.wagon.http.retryHandler.count=30 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false + -B -ntp -XX:+IgnoreUnrecognizedVMOptions --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED - run: .github/workflows/scripts/.pinot_test.sh + run: .github/workflows/scripts/pr-tests/.pinot_tests_integration.sh - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 + continue-on-error: true + timeout-minutes: 5 with: - flags: integration${{ matrix.testset }} + flags: integration,integration${{ matrix.testset }},${{matrix.distribution}},java-${{matrix.java}},skip-bytebuffers-${{matrix.skip_bytebuffer}} name: codecov-integration-tests fail_ci_if_error: false verbose: true + - name: Custom Integration Test + if : ${{ matrix.testset == 1 }} + env: + RUN_INTEGRATION_TESTS: true + RUN_TEST_SET: ${{ matrix.testset }} + GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} + MAVEN_OPTS: > + -Xmx2G -DskipShade -DfailIfNoTests=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=25 + -Dmaven.wagon.http.retryHandler.count=30 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false + -B -ntp + -XX:+IgnoreUnrecognizedVMOptions + --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED + --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED + run: .github/workflows/scripts/pr-tests/.pinot_tests_custom_integration.sh + - name: Upload coverage to Codecov + if : ${{ matrix.testset == 1 }} + uses: codecov/codecov-action@v4 + continue-on-error: true + timeout-minutes: 5 + with: + flags: integration,custom-integration${{ matrix.testset }},${{matrix.distribution}},java-${{matrix.java}} + name: codecov-custom-integration-tests + fail_ci_if_error: false + verbose: true compatibility-verifier: if: github.repository == 'apache/pinot' runs-on: ubuntu-latest strategy: + # Changed to false in order to improve coverage using unsafe buffers + fail-fast: false matrix: test_suite: [ "compatibility-verifier/sample-test-suite" ] - old_commit: [ "release-0.9.0", "master" ] + old_commit: [ + "release-1.0.0", "release-1.1.0", "master" + ] name: Pinot Compatibility Regression Testing against ${{ matrix.old_commit }} on ${{ matrix.test_suite }} steps: - - uses: actions/checkout@v2 - - name: Set up JDK 1.8 - uses: actions/setup-java@v1 + - uses: actions/checkout@v4 + - name: Set up JDK 11 + uses: actions/setup-java@v4 with: - java-version: 1.8 + java-version: 11 + distribution: 'temurin' + cache: 'maven' + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: v16.15.0 + cache: 'npm' + cache-dependency-path: pinot-controller/src/main/resources/package-lock.json + - name: Install npm + run: | + npm install -g npm@8.5.5 + npm --version # Step that does that actual cache save and restore - - uses: actions/cache@v3 + - uses: actions/cache@v4 env: SEGMENT_DOWNLOAD_TIMEOUT_MINS: 10 with: @@ -184,7 +304,8 @@ jobs: TEST_SUITE: ${{ matrix.test_suite }} MAVEN_OPTS: > -Xmx2G -DskipShade -DfailIfNoTests=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=25 - -Dmaven.wagon.http.retryHandler.count=3 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false + -Dmaven.wagon.http.retryHandler.count=30 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false + -B -ntp -XX:+IgnoreUnrecognizedVMOptions --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED @@ -193,23 +314,38 @@ jobs: --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED run: .github/workflows/scripts/.pinot_compatibility_verifier.sh - quickstarts: + multi-stage-compatibility-verifier: if: github.repository == 'apache/pinot' runs-on: ubuntu-latest strategy: + # Changed to false in order to improve coverage using unsafe buffers + fail-fast: false matrix: - # We only test LTS Java versions - # TODO: add JDK 17 once ready. see: https://github.com/apache/pinot/issues/8529 - java: [ 1.8, 11, 15 ] - name: Pinot Quickstart on JDK ${{ matrix.java }} + test_suite: [ "compatibility-verifier/multi-stage-query-engine-test-suite" ] + old_commit: [ + "master" + ] + name: Pinot Multi-Stage Query Engine Compatibility Regression Testing against ${{ matrix.old_commit }} on ${{ matrix.test_suite }} steps: - - uses: actions/checkout@v2 - - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@v1 + - uses: actions/checkout@v4 + - name: Set up JDK 11 + uses: actions/setup-java@v4 with: - java-version: ${{ matrix.java }} + java-version: 11 + distribution: 'temurin' + cache: 'maven' + - name: Setup node + uses: actions/setup-node@v4 + with: + node-version: v16.15.0 + cache: 'npm' + cache-dependency-path: pinot-controller/src/main/resources/package-lock.json + - name: Install npm + run: | + npm install -g npm@8.5.5 + npm --version # Step that does that actual cache save and restore - - uses: actions/cache@v3 + - uses: actions/cache@v4 env: SEGMENT_DOWNLOAD_TIMEOUT_MINS: 10 with: @@ -217,38 +353,57 @@ jobs: key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- - - name: Quickstart on JDK ${{ matrix.java }} - run: .github/workflows/scripts/.pinot_quickstart.sh - - presto-driver-build: - if: github.repository == 'apache/pinot' - runs-on: ubuntu-latest - name: Build Presto Pinot Driver - steps: - - uses: actions/checkout@v2 - - name: Set up JDK 11 - uses: actions/setup-java@v1 - with: - java-version: 11 - - name: Build presto pinot driver with JDK 11 + - name: Pinot Multi-Stage Query Engine Compatibility Regression Testing env: + OLD_COMMIT: ${{ matrix.old_commit }} + WORKING_DIR: /tmp/multi-stage-compatibility-verifier + TEST_SUITE: ${{ matrix.test_suite }} MAVEN_OPTS: > - -Xmx2G -DfailIfNoTests=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=25 - -Dmaven.wagon.http.retryHandler.count=3 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false + -Xmx2G -DskipShade -DfailIfNoTests=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=25 + -Dmaven.wagon.http.retryHandler.count=30 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false + -B -ntp -XX:+IgnoreUnrecognizedVMOptions --add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED - run: | - mvn clean install -DskipTests -Ppresto-driver -am -B -pl ':presto-pinot-driver' -T 16 || exit 1 - - name: Set up JDK 8 - uses: actions/setup-java@v1 + run: .github/workflows/scripts/.pinot_compatibility_verifier.sh + + quickstarts: + if: github.repository == 'apache/pinot' + runs-on: ubuntu-latest + strategy: + # Changed to false in order to improve coverage using unsafe buffers + fail-fast: false + matrix: + java: [ 11, 21 ] + distribution: [ "temurin" ] + skip_bytebuffer: [false] + include: + - java: 21 + distribution: "temurin" + skip_bytebuffer: true + name: Pinot Quickstart on JDK ${{ matrix.java }}-${{ matrix.distribution }} with skip bytebuffers:${{matrix.skip_bytebuffer}} + steps: + - uses: actions/checkout@v4 + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v4 with: - java-version: 8 - - name: Build presto pinot driver with JDK 8 + java-version: ${{ matrix.java }} + distribution: ${{ matrix.distribution }} + cache: 'maven' + # Step that does that actual cache save and restore + - uses: actions/cache@v4 env: - MAVEN_OPTS: -Xmx2G -DfailIfNoTests=false -Dmaven.wagon.httpconnectionManager.ttlSeconds=25 -Dmaven.wagon.http.retryHandler.count=3 -Dhttp.keepAlive=false -Dmaven.wagon.http.pool=false - run: | - mvn clean install -DskipTests -Ppresto-driver -am -B -pl ':presto-pinot-driver' -Djdk.version=8 -T 16 || exit 1 + SEGMENT_DOWNLOAD_TIMEOUT_MINS: 10 + GRADLE_ENTERPRISE_ACCESS_KEY: ${{ secrets.GE_ACCESS_TOKEN }} + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: | + ${{ runner.os }}-maven- + - name: Quickstart on JDK ${{ matrix.java }} + env: + PINOT_OFFHEAP_SKIP_BYTEBUFFER: ${{ matrix.skip_bytebuffer }} + run: .github/workflows/scripts/.pinot_quickstart.sh diff --git a/.github/workflows/pinot_vuln_check.yml b/.github/workflows/pinot_vuln_check.yml index f3c8c3e08400..b4924d3bc848 100644 --- a/.github/workflows/pinot_vuln_check.yml +++ b/.github/workflows/pinot_vuln_check.yml @@ -37,20 +37,17 @@ jobs: name: Verify Docker Image runs-on: ubuntu-latest steps: - - uses: docker/setup-qemu-action@v1 + - uses: docker/setup-qemu-action@v3 name: Set up QEMU - - uses: docker/setup-buildx-action@v1 - name: Set up Docker Buildx - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build the Docker image env: DOCKER_FILE_BASE_DIR: "docker/images/pinot" DOCKER_IMAGE_NAME: "apachepinot/pinot" - BUILD_PLATFORM: "linux/amd64" PINOT_GIT_URL: ${{ github.event.inputs.gitUrl }} PINOT_BRANCH: ${{ env.GITHUB_REF }} - TAGS: ${{ github.sha }} - run: .github/workflows/scripts/docker/.pinot_docker_image_build.sh + PINOT_SHA: ${{ github.sha }} + run: .github/workflows/scripts/.pinot_vuln_check.sh - name: Run Trivy vulnerability scanner (sarif) uses: aquasecurity/trivy-action@master @@ -61,7 +58,8 @@ jobs: output: 'trivy-results.sarif' vuln-type: 'os,library' severity: 'CRITICAL,HIGH' + timeout: '10m' - name: Upload Trivy scan results to GitHub Security tab - uses: github/codeql-action/upload-sarif@v2 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: 'trivy-results.sarif' diff --git a/.github/workflows/scripts/.pinot_compatibility_verifier.sh b/.github/workflows/scripts/.pinot_compatibility_verifier.sh index 0897eacdd809..93789ce67ca8 100755 --- a/.github/workflows/scripts/.pinot_compatibility_verifier.sh +++ b/.github/workflows/scripts/.pinot_compatibility_verifier.sh @@ -41,17 +41,49 @@ echo " https://packages.confluent.io/maven/">> ${SETTINGS_FILE} echo " false">> ${SETTINGS_FILE} echo " ">> ${SETTINGS_FILE} echo " ">> ${SETTINGS_FILE} + +echo " ">> ${SETTINGS_FILE} +echo " ">> ${SETTINGS_FILE} +echo " central">> ${SETTINGS_FILE} +echo " ">> ${SETTINGS_FILE} +echo " ">> ${SETTINGS_FILE} +echo " ">> ${SETTINGS_FILE} +echo " 120000">> ${SETTINGS_FILE} +echo " 120000">> ${SETTINGS_FILE} +echo " 3">> ${SETTINGS_FILE} +echo " ">> ${SETTINGS_FILE} +echo " ">> ${SETTINGS_FILE} +echo " ">> ${SETTINGS_FILE} +echo " ">> ${SETTINGS_FILE} +echo " ">> ${SETTINGS_FILE} + echo "">> ${SETTINGS_FILE} # PINOT_MAVEN_OPTS is used to provide additional maven options to the checkoutAndBuild.sh command export PINOT_MAVEN_OPTS="-s $(pwd)/${SETTINGS_FILE}" -if [ -z "$newerCommit" ]; then - echo "Running compatibility regression test against \"${olderCommit}\"" +# Compare commit hash for compatibility verification +git fetch --all +NEW_COMMIT_HASH=`git log -1 --pretty=format:'%h' HEAD` +if [ ! -z "${NEW_COMMIT}" ]; then + NEW_COMMIT_HASH=`git log -1 --pretty=format:'%h' ${NEW_COMMIT}` +fi +OLD_COMMIT_HASH=`git log -1 --pretty=format:'%h' ${OLD_COMMIT}` +if [ $? -ne 0 ]; then + echo "Failed to get commit hash for commit: \"${OLD_COMMIT}\"" + OLD_COMMIT_HASH=`git log -1 --pretty=format:'%h' origin/${OLD_COMMIT}` +fi +if [ "${NEW_COMMIT_HASH}" == "${OLD_COMMIT_HASH}" ]; then + echo "No changes between old commit: \"${OLD_COMMIT}\" and new commit: \"${NEW_COMMIT}\"" + exit 0 +fi + +if [ -z "${NEW_COMMIT}" ]; then + echo "Running compatibility regression test against \"${OLD_COMMIT}\"" compatibility-verifier/checkoutAndBuild.sh -w $WORKING_DIR -o $OLD_COMMIT else - echo "Running compatibility regression test against \"${olderCommit}\" and \"${newerCommit}\"" - compatibility-verifier/checkoutAndBuild.sh -w $WORKING_DIR -o $OLD_COMMIT -n $NEW_OLD_COMMIT + echo "Running compatibility regression test against \"${OLD_COMMIT}\" and \"${NEW_COMMIT}\"" + compatibility-verifier/checkoutAndBuild.sh -w $WORKING_DIR -o $OLD_COMMIT -n $NEW_COMMIT fi compatibility-verifier/compCheck.sh -w $WORKING_DIR -t $TEST_SUITE diff --git a/.github/workflows/scripts/.pinot_linter.sh b/.github/workflows/scripts/.pinot_linter.sh index 90dd8a7d6926..ef4ba7a5f3d2 100755 --- a/.github/workflows/scripts/.pinot_linter.sh +++ b/.github/workflows/scripts/.pinot_linter.sh @@ -26,8 +26,8 @@ ifconfig netstat -i -mvn license:check || exit 1 -mvn checkstyle:check || exit 1 -mvn spotless:check || exit 1 -mvn enforcer:enforce || exit 1 +mvn -B -ntp -T1C license:check || exit 1 +mvn -B -ntp -T1C checkstyle:check || exit 1 +mvn -B -ntp -T1C spotless:check || exit 1 +mvn -B -ntp -T1C enforcer:enforce || exit 1 diff --git a/.github/workflows/scripts/.pinot_quickstart.sh b/.github/workflows/scripts/.pinot_quickstart.sh index f27ecc8dcfd3..fb2ebae6322a 100755 --- a/.github/workflows/scripts/.pinot_quickstart.sh +++ b/.github/workflows/scripts/.pinot_quickstart.sh @@ -64,14 +64,11 @@ jdk_version() { JAVA_VER="$(jdk_version)" # Build +echo "Building Pinot to JAVA 11 source code Using JDK ${JAVA_VER}" PASS=0 for i in $(seq 1 2) do - if [ "$JAVA_VER" -gt 11 ] ; then - mvn clean install -B -DskipTests=true -Pbin-dist -Dmaven.javadoc.skip=true -Djdk.version=11 - else - mvn clean install -B -DskipTests=true -Pbin-dist -Dmaven.javadoc.skip=true -Djdk.version=${JAVA_VER} - fi + mvn clean install -B -ntp -T1C -DskipTests -Pbin-dist -Dmaven.javadoc.skip=true -Djdk.version=11 if [ $? -eq 0 ]; then PASS=1 break; @@ -87,6 +84,7 @@ cd "${DIST_BIN_DIR}" # Test standalone pinot. Configure JAVA_OPTS for smaller memory, and don't use System.exit export JAVA_OPTS="-Xms1G -Dlog4j2.configurationFile=conf/log4j2.xml" + bin/pinot-admin.sh StartZookeeper & ZK_PID=$! sleep 10 @@ -119,6 +117,12 @@ if [ $? -ne 0 ]; then exit 1 fi +bin/pinot-admin.sh AddTable -tableConfigFile examples/batch/dimBaseballTeams/dimBaseballTeams_offline_table_config.json -schemaFile examples/batch/dimBaseballTeams/dimBaseballTeams_schema.json -exec +if [ $? -ne 0 ]; then + echo 'Failed to create table dimBaseballTeams.' + exit 1 +fi + # Ingest Data d=`pwd` INSERT_INTO_RES=`curl -X POST --header 'Content-Type: application/json' -d "{\"sql\":\"INSERT INTO baseballStats FROM FILE '${d}/examples/batch/baseballStats/rawdata'\",\"trace\":false}" http://localhost:8099/query/sql` @@ -128,8 +132,17 @@ if [ $? -ne 0 ]; then fi PASS=0 + +INSERT_INTO_RES=`curl -X POST --header 'Content-Type: application/json' -d "{\"sql\":\"INSERT INTO dimBaseballTeams FROM FILE '${d}/examples/batch/dimBaseballTeams/rawdata'\",\"trace\":false}" http://localhost:8099/query/sql` +if [ $? -ne 0 ]; then + echo 'Failed to ingest data for table baseballStats.' + exit 1 +fi +PASS=0 + # Wait for 10 Seconds for table to be set up, then query the total count. sleep 10 +# Validate V1 query count(*) result for i in $(seq 1 150) do QUERY_RES=`curl -X POST --header 'Accept: application/json' -d '{"sql":"select count(*) from baseballStats limit 1","trace":false}' http://localhost:8099/query/sql` @@ -143,6 +156,38 @@ do sleep 2 done +PASS=0 + +# Validate V2 query count(*) result +for i in $(seq 1 150) +do + QUERY_RES=`curl -X POST --header 'Accept: application/json' -d '{"sql":"SET useMultistageEngine=true; select count(*) from baseballStats limit 1","trace":false}' http://localhost:8099/query/sql` + if [ $? -eq 0 ]; then + COUNT_STAR_RES=`echo "${QUERY_RES}" | jq '.resultTable.rows[0][0]'` + if [[ "${COUNT_STAR_RES}" =~ ^[0-9]+$ ]] && [ "${COUNT_STAR_RES}" -eq 97889 ]; then + PASS=1 + break + fi + fi + sleep 2 +done + +PASS=0 + +# Validate V2 join query results +for i in $(seq 1 150) +do + QUERY_RES=`curl -X POST --header 'Accept: application/json' -d '{"sql":"SET useMultistageEngine=true;SELECT a.playerName, a.teamID, b.teamName FROM baseballStats_OFFLINE AS a JOIN dimBaseballTeams_OFFLINE AS b ON a.teamID = b.teamID LIMIT 10","trace":false}' http://localhost:8099/query/sql` + if [ $? -eq 0 ]; then + RES_0=`echo "${QUERY_RES}" | jq '.resultTable.rows[0][0]'` + if [[ "${RES_0}" = "\"David Allan\"" ]]; then + PASS=1 + break + fi + fi + sleep 2 +done + cleanup "${PINOT_PID}" cleanup "${ZK_PID}" if [ "${PASS}" -eq 0 ]; then @@ -150,8 +195,8 @@ if [ "${PASS}" -eq 0 ]; then exit 1 fi -# Test quick-start-batch -bin/quick-start-batch.sh & +# Test quick-start-multi-stage +bin/pinot-admin.sh QuickStart -type MULTI_STAGE & PID=$! # Print the JVM settings @@ -174,6 +219,38 @@ do sleep 2 done +PASS=0 + +# Validate V2 query count(*) result +for i in $(seq 1 150) +do + QUERY_RES=`curl -X POST --header 'Accept: application/json' -d '{"sql":"SET useMultistageEngine=true; select count(*) from baseballStats limit 1","trace":false}' http://localhost:8000/query/sql` + if [ $? -eq 0 ]; then + COUNT_STAR_RES=`echo "${QUERY_RES}" | jq '.resultTable.rows[0][0]'` + if [[ "${COUNT_STAR_RES}" =~ ^[0-9]+$ ]] && [ "${COUNT_STAR_RES}" -eq 97889 ]; then + PASS=1 + break + fi + fi + sleep 2 +done + +PASS=0 + +# Validate V2 join query results +for i in $(seq 1 150) +do + QUERY_RES=`curl -X POST --header 'Accept: application/json' -d '{"sql":"SET useMultistageEngine=true;SELECT a.playerName, a.teamID, b.teamName FROM baseballStats_OFFLINE AS a JOIN dimBaseballTeams_OFFLINE AS b ON a.teamID = b.teamID LIMIT 10","trace":false}' http://localhost:8000/query/sql` + if [ $? -eq 0 ]; then + RES_0=`echo "${QUERY_RES}" | jq '.resultTable.rows[0][0]'` + if [[ ${RES_0} = "\"David Allan\"" ]]; then + PASS=1 + break + fi + fi + sleep 2 +done + cleanup "${PID}" if [ "${PASS}" -eq 0 ]; then echo 'Batch Quickstart failed: Cannot get correct result for count star query.' @@ -181,42 +258,41 @@ if [ "${PASS}" -eq 0 ]; then fi # Test quick-start-streaming -# TODO: Streaming test is disabled because Meetup RSVP stream is retired. Find a replacement and re-enable this test. -#bin/quick-start-streaming.sh & -#PID=$! -# -#PASS=0 -#RES_1=0 -# -## Wait for 1 minute for table to be set up, then at most 5 minutes to reach the desired state -#sleep 60 -#for i in $(seq 1 150) -#do -# QUERY_RES=`curl -X POST --header 'Accept: application/json' -d '{"sql":"select count(*) from meetupRsvp limit 1","trace":false}' http://localhost:8000/query/sql` -# if [ $? -eq 0 ]; then -# COUNT_STAR_RES=`echo "${QUERY_RES}" | jq '.resultTable.rows[0][0]'` -# if [[ "${COUNT_STAR_RES}" =~ ^[0-9]+$ ]] && [ "${COUNT_STAR_RES}" -gt 0 ]; then -# if [ "${RES_1}" -eq 0 ]; then -# RES_1="${COUNT_STAR_RES}" -# continue -# elif [ "${COUNT_STAR_RES}" -gt "${RES_1}" ]; then -# PASS=1 -# break -# fi -# fi -# fi -# sleep 2 -#done -# -#cleanup "${PID}" -#if [ "${PASS}" -eq 0 ]; then -# if [ "${RES_1}" -eq 0 ]; then -# echo 'Streaming Quickstart test failed: Cannot get correct result for count star query.' -# exit 1 -# fi -# echo 'Streaming Quickstart test failed: Cannot get incremental counts for count star query.' -# exit 1 -#fi +bin/quick-start-streaming.sh & +PID=$! + +PASS=0 +RES_1=0 + +# Wait for 1 minute for table to be set up, then at most 5 minutes to reach the desired state +sleep 60 +for i in $(seq 1 150) +do + QUERY_RES=`curl -X POST --header 'Accept: application/json' -d '{"sql":"select count(*) from meetupRsvp limit 1","trace":false}' http://localhost:8000/query/sql` + if [ $? -eq 0 ]; then + COUNT_STAR_RES=`echo "${QUERY_RES}" | jq '.resultTable.rows[0][0]'` + if [[ "${COUNT_STAR_RES}" =~ ^[0-9]+$ ]] && [ "${COUNT_STAR_RES}" -gt 0 ]; then + if [ "${RES_1}" -eq 0 ]; then + RES_1="${COUNT_STAR_RES}" + continue + elif [ "${COUNT_STAR_RES}" -gt "${RES_1}" ]; then + PASS=1 + break + fi + fi + fi + sleep 2 +done + +cleanup "${PID}" +if [ "${PASS}" -eq 0 ]; then + if [ "${RES_1}" -eq 0 ]; then + echo 'Streaming Quickstart test failed: Cannot get correct result for count star query.' + exit 1 + fi + echo 'Streaming Quickstart test failed: Cannot get incremental counts for count star query.' + exit 1 +fi # Test quick-start-hybrid bin/quick-start-hybrid.sh & diff --git a/.github/workflows/scripts/.pinot_test.sh b/.github/workflows/scripts/.pinot_test.sh deleted file mode 100755 index d88414583c60..000000000000 --- a/.github/workflows/scripts/.pinot_test.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/bin/bash -x -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -# Java version -java -version - -# Check network -ifconfig -netstat -i - -if [ "$RUN_INTEGRATION_TESTS" != false ]; then - # Integration Tests - mvn clean install -DskipTests -am -B -pl 'pinot-integration-tests' -T 16 || exit 1 - if [ "$RUN_TEST_SET" == "1" ]; then - mvn test -am -B \ - -pl 'pinot-integration-tests' \ - -Dtest='C*Test,L*Test,M*Test,R*Test,S*Test' \ - -P github-actions,integration-tests-only && exit 0 || exit 1 - fi - if [ "$RUN_TEST_SET" == "2" ]; then - mvn test -am -B \ - -pl 'pinot-integration-tests' \ - -Dtest='!C*Test,!L*Test,!M*Test,!R*Test,!S*Test' \ - -P github-actions,integration-tests-only && exit 0 || exit 1 - fi -else - # Unit Tests - # - TEST_SET#1 runs install and test together so the module list must ensure no additional modules were tested - # due to the -am flag (include dependency modules) - if [ "$RUN_TEST_SET" == "1" ]; then - mvn clean install -am -B \ - -pl 'pinot-spi' \ - -pl 'pinot-segment-spi' \ - -pl 'pinot-common' \ - -pl 'pinot-segment-local' \ - -pl 'pinot-core' \ - -pl 'pinot-query-planner' \ - -pl 'pinot-query-runtime' \ - -pl 'pinot-server' \ - -pl ':pinot-yammer' \ - -pl ':pinot-avro-base' \ - -pl ':pinot-avro' \ - -pl ':pinot-csv' \ - -pl ':pinot-json' \ - -pl ':pinot-segment-uploader-default' \ - -P github-actions,no-integration-tests && exit 0 || exit 1 - fi - if [ "$RUN_TEST_SET" == "2" ]; then - mvn clean install -DskipTests -T 16 || exit 1 - mvn test -am -B \ - -pl '!pinot-spi' \ - -pl '!pinot-segment-spi' \ - -pl '!pinot-common' \ - -pl '!pinot-segment-local' \ - -pl '!pinot-core' \ - -pl '!pinot-query-planner' \ - -pl '!pinot-query-runtime' \ - -pl '!pinot-server' \ - -pl '!:pinot-yammer' \ - -pl '!:pinot-avro-base' \ - -pl '!:pinot-avro' \ - -pl '!:pinot-csv' \ - -pl '!:pinot-json' \ - -pl '!:pinot-segment-uploader-default' \ - -P github-actions,no-integration-tests && exit 0 || exit 1 - fi -fi - -mvn clean > /dev/null diff --git a/.github/workflows/scripts/.pinot_vuln_check.sh b/.github/workflows/scripts/.pinot_vuln_check.sh new file mode 100755 index 000000000000..928de0b25fae --- /dev/null +++ b/.github/workflows/scripts/.pinot_vuln_check.sh @@ -0,0 +1,43 @@ +#!/bin/bash -x +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +if [ -z "${DOCKER_IMAGE_NAME}" ]; then + DOCKER_IMAGE_NAME="apachepinot/pinot" +fi +if [ -z "${PINOT_GIT_URL}" ]; then + PINOT_GIT_URL="https://github.com/apache/pinot.git" +fi +if [ -z "${PINOT_BRANCH}" ]; then + PINOT_BRANCH="master" +fi + +cd ${DOCKER_FILE_BASE_DIR} + +docker image prune --all --filter "until=1h" -f + +docker build \ + --no-cache \ + --file Dockerfile \ + --build-arg PINOT_GIT_URL=${PINOT_GIT_URL} \ + --build-arg PINOT_BRANCH=${PINOT_BRANCH} \ + --tag ${DOCKER_IMAGE_NAME}:${PINOT_SHA} \ + . + +docker image ls diff --git a/.github/workflows/scripts/docker/.pinot_base_docker_image_build_and_push.sh b/.github/workflows/scripts/docker/.pinot_base_docker_image_build_and_push.sh new file mode 100755 index 000000000000..14a8d9f3cdb4 --- /dev/null +++ b/.github/workflows/scripts/docker/.pinot_base_docker_image_build_and_push.sh @@ -0,0 +1,40 @@ +#!/bin/bash -x +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +set -e + +if [ -z "${BUILD_PLATFORM}" ]; then + exit 1 +fi + +if [ -z "${BASE_IMAGE_TYPE}" ]; then + exit 1 +fi + +cd docker/images/pinot-base/pinot-base-${BASE_IMAGE_TYPE} + +docker buildx build \ + --no-cache \ + --platform=${BUILD_PLATFORM} \ + --file ${OPEN_JDK_DIST}.dockerfile \ + --tag apachepinot/pinot-base-${BASE_IMAGE_TYPE}:${TAG} \ + --build-arg JAVA_VERSION=${JDK_VERSION:-11} \ + --push \ + . diff --git a/.github/workflows/scripts/docker/.pinot_build_info_gen.sh b/.github/workflows/scripts/docker/.pinot_build_info_gen.sh new file mode 100755 index 000000000000..bca2a0ef8e37 --- /dev/null +++ b/.github/workflows/scripts/docker/.pinot_build_info_gen.sh @@ -0,0 +1,41 @@ +#!/bin/bash -x +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +if [ -z "${PINOT_GIT_URL}" ]; then + PINOT_GIT_URL="https://github.com/apache/pinot.git" +fi +if [ -z "${PINOT_BRANCH}" ]; then + PINOT_BRANCH="master" +fi + +# Get pinot commit id +ROOT_DIR=$(pwd) +rm -rf /tmp/pinot +git clone -b ${PINOT_BRANCH} --single-branch ${PINOT_GIT_URL} /tmp/pinot +cd /tmp/pinot +COMMIT_ID=$(git rev-parse --short HEAD) +VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) +rm -rf /tmp/pinot +DATE=$(date +%Y%m%d) + +if [ -z "${TAGS}" ]; then + TAGS="${VERSION}-${COMMIT_ID}-${DATE},latest" +fi +echo "commit-id=${COMMIT_ID}" >>"$GITHUB_OUTPUT" +echo "tags=${TAGS}" >>"$GITHUB_OUTPUT" diff --git a/.github/workflows/scripts/docker/.pinot_docker_image_build.sh b/.github/workflows/scripts/docker/.pinot_docker_image_build.sh index 62bed0fdf508..7f9109f1d466 100755 --- a/.github/workflows/scripts/docker/.pinot_docker_image_build.sh +++ b/.github/workflows/scripts/docker/.pinot_docker_image_build.sh @@ -31,9 +31,17 @@ if [ -z "${BUILD_PLATFORM}" ]; then BUILD_PLATFORM="linux/arm64,linux/amd64" fi +# Get pinot commit id +ROOT_DIR=`pwd` +rm -rf /tmp/pinot +git clone -b ${PINOT_BRANCH} --single-branch ${PINOT_GIT_URL} /tmp/pinot +cd /tmp/pinot COMMIT_ID=`git rev-parse --short HEAD` -DATE=`date +%Y%m%d` VERSION=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout` +rm -rf /tmp/pinot +DATE=`date +%Y%m%d` +cd ${ROOT_DIR} + tags=() if [ -z "${TAGS}" ]; then tags=("${VERSION}-${COMMIT_ID}-${DATE}") diff --git a/.github/workflows/scripts/docker/.pinot_docker_image_build_and_push.sh b/.github/workflows/scripts/docker/.pinot_docker_image_build_and_push.sh index f7f46e8a6087..4b6e2adea2f2 100755 --- a/.github/workflows/scripts/docker/.pinot_docker_image_build_and_push.sh +++ b/.github/workflows/scripts/docker/.pinot_docker_image_build_and_push.sh @@ -31,9 +31,17 @@ if [ -z "${BUILD_PLATFORM}" ]; then BUILD_PLATFORM="linux/arm64,linux/amd64" fi +# Get pinot commit id +ROOT_DIR=`pwd` +rm -rf /tmp/pinot +git clone -b ${PINOT_BRANCH} --single-branch ${PINOT_GIT_URL} /tmp/pinot +cd /tmp/pinot COMMIT_ID=`git rev-parse --short HEAD` -DATE=`date +%Y%m%d` VERSION=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout` +rm -rf /tmp/pinot +DATE=`date +%Y%m%d` +cd ${ROOT_DIR} + tags=() if [ -z "${TAGS}" ]; then tags=("${VERSION}-${COMMIT_ID}-${DATE}") diff --git a/.github/workflows/scripts/docker/.pinot_multi_arch_docker_image_manifest_package.sh b/.github/workflows/scripts/docker/.pinot_multi_arch_docker_image_manifest_package.sh new file mode 100755 index 000000000000..f94df2e9e8f7 --- /dev/null +++ b/.github/workflows/scripts/docker/.pinot_multi_arch_docker_image_manifest_package.sh @@ -0,0 +1,64 @@ +#!/bin/bash -x +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +set -x + +if [ -z "${DOCKER_IMAGE_NAME}" ]; then + DOCKER_IMAGE_NAME="apachepinot/pinot" +fi + +tags=() +declare -a tags=($(echo ${TAGS} | tr "," " ")) + +platforms=() +declare -a platforms=($(echo ${BUILD_PLATFORM} | tr "," " ")) + +baseImageTags=() +declare -a baseImageTags=($(echo ${BASE_IMAGE_TAGS} | tr "," " ")) + +for tag in "${tags[@]}"; do + for baseImageTag in "${baseImageTags[@]}"; do + DOCKER_AMEND_TAGS_CMD="" + for platform in "${platforms[@]}"; do + platformTag=${platform/\//-} + DOCKER_AMEND_TAGS_CMD+=" --amend ${DOCKER_IMAGE_NAME}:${tag}-${baseImageTag}-${platformTag} " + done + + echo "Creating manifest for tag: ${tag}-${baseImageTag}" + docker manifest create \ + ${DOCKER_IMAGE_NAME}:${tag}-${baseImageTag} \ + ${DOCKER_AMEND_TAGS_CMD} + + docker manifest push \ + ${DOCKER_IMAGE_NAME}:${tag}-${baseImageTag} + + if [ "${baseImageTag}" == "11-amazoncorretto" ]; then + if [ "${tag}" == "latest" ]; then + echo "Creating manifest for tag: latest" + docker manifest create \ + ${DOCKER_IMAGE_NAME}:latest \ + ${DOCKER_AMEND_TAGS_CMD} + + docker manifest push \ + ${DOCKER_IMAGE_NAME}:latest + fi + fi + done +done diff --git a/.github/workflows/scripts/docker/.pinot_single_platform_docker_image_build.sh b/.github/workflows/scripts/docker/.pinot_single_platform_docker_image_build.sh new file mode 100755 index 000000000000..80296b473db1 --- /dev/null +++ b/.github/workflows/scripts/docker/.pinot_single_platform_docker_image_build.sh @@ -0,0 +1,89 @@ +#!/bin/bash -x +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +set -e +set -x + +if [ -z "${DOCKER_IMAGE_NAME}" ]; then + DOCKER_IMAGE_NAME="apachepinot/pinot" +fi +if [ -z "${PINOT_GIT_URL}" ]; then + PINOT_GIT_URL="https://github.com/apache/pinot.git" +fi +if [ -z "${JDK_VERSION}" ]; then + JDK_VERSION="11" +fi + + +cd ${DOCKER_FILE_BASE_DIR} +platformTag=${BUILD_PLATFORM/\//-} + +PINOT_BUILD_IMAGE_TAG=${BASE_IMAGE_TAG}-${platformTag} +echo "Building docker image for platform: ${BUILD_PLATFORM} with tag: pinot-build:${PINOT_BUILD_IMAGE_TAG}" +docker build \ + --no-cache \ + --platform ${BUILD_PLATFORM} \ + --file Dockerfile.build \ + --build-arg PINOT_GIT_URL=${PINOT_GIT_URL} \ + --build-arg PINOT_BRANCH=${PINOT_BRANCH} \ + --build-arg JDK_VERSION=${JDK_VERSION} \ + --build-arg PINOT_BASE_IMAGE_TAG=${BASE_IMAGE_TAG} \ + --tag pinot-build:${PINOT_BUILD_IMAGE_TAG} \ + . + +tags=() +declare -a tags=($(echo ${TAGS} | tr "," " ")) + +runtimeImages=() +declare -a runtimeImages=($(echo ${RUNTIME_IMAGE_TAGS} | tr "," " ")) + + +for runtimeImage in "${runtimeImages[@]}"; do + DOCKER_BUILD_TAGS="" + for tag in "${tags[@]}"; do + DOCKER_BUILD_TAGS+=" --tag ${DOCKER_IMAGE_NAME}:${tag}-${runtimeImage}-${platformTag} " + + if [ "${runtimeImage}" == "11-amazoncorretto" ]; then + if [ "${tag}" == "latest" ]; then + DOCKER_BUILD_TAGS+=" --tag ${DOCKER_IMAGE_NAME}:latest-${platformTag} " + fi + fi + done + + echo "Building docker image for platform: ${BUILD_PLATFORM} with tags: ${DOCKER_BUILD_TAGS}" + docker build \ + --no-cache \ + --platform ${BUILD_PLATFORM} \ + --file Dockerfile.package \ + --build-arg PINOT_BUILD_IMAGE_TAG=${PINOT_BUILD_IMAGE_TAG} \ + --build-arg PINOT_RUNTIME_IMAGE_TAG=${runtimeImage} \ + ${DOCKER_BUILD_TAGS} \ + . + + for tag in "${tags[@]}"; do + docker push ${DOCKER_IMAGE_NAME}:${tag}-${runtimeImage}-${platformTag} + + if [ "${runtimeImage}" == "11-amazoncorretto" ]; then + if [ "${tag}" == "latest" ]; then + docker push ${DOCKER_IMAGE_NAME}:${tag}-${platformTag} + fi + fi + done +done diff --git a/.github/workflows/scripts/docker/.presto_build_info_gen.sh b/.github/workflows/scripts/docker/.presto_build_info_gen.sh new file mode 100755 index 000000000000..87c5da3f055a --- /dev/null +++ b/.github/workflows/scripts/docker/.presto_build_info_gen.sh @@ -0,0 +1,42 @@ +#!/bin/bash -x +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +if [ -z "${PRESTO_GIT_URL}" ]; then + PRESTO_GIT_URL="https://github.com/prestodb/presto.git" +fi +if [ -z "${PRESTO_BRANCH}" ]; then + PRESTO_BRANCH="master" +fi + +# Get presto commit id +ROOT_DIR=`pwd` +rm -rf /tmp/presto +git clone -b ${PRESTO_BRANCH} --single-branch ${PRESTO_GIT_URL} /tmp/presto +cd /tmp/presto +COMMIT_ID=`git rev-parse --short HEAD` +./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout +VERSION=`./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout` +rm -rf /tmp/presto +DATE=`date +%Y%m%d` + +if [ -z "${TAGS}" ]; then + TAGS="${VERSION}-${COMMIT_ID}-${DATE},latest" +fi +echo "commit-id=${COMMIT_ID}" >> "$GITHUB_OUTPUT" +echo "tags=${TAGS}" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/scripts/docker/.presto_docker_image_build_and_push.sh b/.github/workflows/scripts/docker/.presto_docker_image_build_and_push.sh index 9db8baf612d6..a95fba69b286 100755 --- a/.github/workflows/scripts/docker/.presto_docker_image_build_and_push.sh +++ b/.github/workflows/scripts/docker/.presto_docker_image_build_and_push.sh @@ -31,40 +31,31 @@ if [ -z "${BUILD_PLATFORM}" ]; then BUILD_PLATFORM="linux/amd64" fi -# Get presto commit id -ROOT_DIR=`pwd` -rm -rf /tmp/presto -git clone -b ${PRESTO_BRANCH} --single-branch ${PRESTO_GIT_URL} /tmp/presto -cd /tmp/presto -COMMIT_ID=`git rev-parse --short HEAD` -VERSION=`mvn help:evaluate -Dexpression=project.version -q -DforceStdout` -rm -rf /tmp/presto -DATE=`date +%Y%m%d` -cd ${ROOT_DIR} - tags=() -if [ -z "${TAGS}" ]; then - tags=("${VERSION}-${COMMIT_ID}-${DATE}") - tags+=("latest") -else - declare -a tags=($(echo ${TAGS} | tr "," " ")) -fi +declare -a tags=($(echo ${TAGS} | tr "," " ")) DOCKER_BUILD_TAGS="" for tag in "${tags[@]}" do - echo "Plan to build and push docker images for: ${DOCKER_IMAGE_NAME}:${tag}" + echo "Plan to build docker images for: ${DOCKER_IMAGE_NAME}:${tag}" DOCKER_BUILD_TAGS+=" --tag ${DOCKER_IMAGE_NAME}:${tag} " done cd ${DOCKER_FILE_BASE_DIR} -docker buildx build \ +docker image prune --all --filter "until=1h" -f + +docker build \ --no-cache \ --platform=${BUILD_PLATFORM} \ --file Dockerfile \ --build-arg PRESTO_GIT_URL=${PRESTO_GIT_URL} --build-arg PRESTO_BRANCH=${PRESTO_BRANCH} \ ${DOCKER_BUILD_TAGS} \ - --push \ . + +for tag in "${tags[@]}" +do + echo "Push docker image: ${DOCKER_IMAGE_NAME}:${tag}" + docker push ${DOCKER_IMAGE_NAME}:${tag} +done diff --git a/.github/workflows/scripts/docker/.superset_docker_image_build_and_push.sh b/.github/workflows/scripts/docker/.superset_docker_image_build_and_push.sh index 700b8e01a1be..a82997e2d842 100755 --- a/.github/workflows/scripts/docker/.superset_docker_image_build_and_push.sh +++ b/.github/workflows/scripts/docker/.superset_docker_image_build_and_push.sh @@ -49,7 +49,7 @@ done cd ${DOCKER_FILE_BASE_DIR} -docker buildx build \ +docker build \ --no-cache \ --platform=${BUILD_PLATFORM} \ --file Dockerfile \ diff --git a/.github/workflows/scripts/docker/.superset_multi_arch_docker_image_build_and_push.sh b/.github/workflows/scripts/docker/.superset_multi_arch_docker_image_build_and_push.sh new file mode 100755 index 000000000000..a82997e2d842 --- /dev/null +++ b/.github/workflows/scripts/docker/.superset_multi_arch_docker_image_build_and_push.sh @@ -0,0 +1,59 @@ +#!/bin/bash -x +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +if [ -z "${DOCKER_IMAGE_NAME}" ]; then + DOCKER_IMAGE_NAME="apachepinot/pinot-superset" +fi +if [ -z "${SUPERSET_IMAGE_TAG}" ]; then + SUPERSET_IMAGE_TAG="latest" +fi +if [ -z "${BUILD_PLATFORM}" ]; then + BUILD_PLATFORM="linux/amd64" +fi + +DATE=`date +%Y%m%d` +docker pull apache/superset:${SUPERSET_IMAGE_TAG} +COMMIT_ID=`docker images apache/superset:${SUPERSET_IMAGE_TAG} --format "{{.ID}}"` + +tags=() +if [ -z "${TAGS}" ]; then + tags=("${COMMIT_ID}-${DATE}") + tags+=("latest") +else + declare -a tags=($(echo ${TAGS} | tr "," " ")) +fi + +DOCKER_BUILD_TAGS="" +for tag in "${tags[@]}" +do + echo "Plan to build and push docker images for: ${DOCKER_IMAGE_NAME}:${tag}" + DOCKER_BUILD_TAGS+=" --tag ${DOCKER_IMAGE_NAME}:${tag} " +done + +cd ${DOCKER_FILE_BASE_DIR} + +docker build \ + --no-cache \ + --platform=${BUILD_PLATFORM} \ + --file Dockerfile \ + --build-arg SUPERSET_IMAGE_TAG=${SUPERSET_IMAGE_TAG} \ + ${DOCKER_BUILD_TAGS} \ + --push \ + . diff --git a/.github/workflows/scripts/pr-tests/.pinot_tests_build.sh b/.github/workflows/scripts/pr-tests/.pinot_tests_build.sh new file mode 100755 index 000000000000..e94d7447b8c9 --- /dev/null +++ b/.github/workflows/scripts/pr-tests/.pinot_tests_build.sh @@ -0,0 +1,61 @@ +#!/bin/bash -x +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Java version +java -version + +# Check network +ifconfig +netstat -i + +if [ "$RUN_INTEGRATION_TESTS" != false ]; then + # Integration Tests + mvn clean install \ + -DskipTests -Dcheckstyle.skip -Dspotless.skip -Denforcer.skip -Dlicense.skip -Dmaven.plugin.appassembler.skip=true \ + -am -B -T 16 -ntp \ + -P github-actions,integration-tests \ + -pl 'pinot-integration-tests' || exit 1 +else + # Unit Tests + # - TEST_SET#1 runs install and test together so the module list must ensure no additional modules were tested + # due to the -am flag (include dependency modules) + if [ "$RUN_TEST_SET" == "1" ]; then + mvn clean install \ + -DskipTests -Dcheckstyle.skip -Dspotless.skip -Denforcer.skip -Dlicense.skip -Dmaven.plugin.appassembler.skip=true \ + -am -B -T 16 -ntp \ + -P github-actions \ + -pl 'pinot-spi' \ + -pl 'pinot-segment-spi' \ + -pl 'pinot-common' \ + -pl 'pinot-segment-local' \ + -pl 'pinot-core' \ + -pl 'pinot-query-planner' \ + -pl 'pinot-query-runtime' || exit 1 + fi + if [ "$RUN_TEST_SET" == "2" ]; then + mvn clean install \ + -DskipTests -Dcheckstyle.skip -Dspotless.skip -Denforcer.skip -Dlicense.skip -Dmaven.plugin.appassembler.skip=true \ + -am -B -T 16 -ntp \ + -P github-actions \ + -pl '!pinot-integration-tests' \ + -pl '!pinot-perf' \ + -pl '!pinot-distribution' || exit 1 + fi +fi diff --git a/.github/workflows/scripts/pr-tests/.pinot_tests_custom_integration.sh b/.github/workflows/scripts/pr-tests/.pinot_tests_custom_integration.sh new file mode 100755 index 000000000000..cd99615ce4d6 --- /dev/null +++ b/.github/workflows/scripts/pr-tests/.pinot_tests_custom_integration.sh @@ -0,0 +1,33 @@ +#!/bin/bash -x +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Java version +java -version + +# Check network +ifconfig +netstat -i + +# Custom Integration Tests +cd pinot-integration-tests || exit 1 +if [ "$RUN_TEST_SET" == "1" ]; then + mvn test \ + -P github-actions,custom-cluster-integration-test-suite || exit 1 +fi diff --git a/.github/workflows/scripts/pr-tests/.pinot_tests_integration.sh b/.github/workflows/scripts/pr-tests/.pinot_tests_integration.sh new file mode 100755 index 000000000000..a6f671c2181d --- /dev/null +++ b/.github/workflows/scripts/pr-tests/.pinot_tests_integration.sh @@ -0,0 +1,37 @@ +#!/bin/bash -x +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Java version +java -version + +# Check network +ifconfig +netstat -i + +# Integration Tests +cd pinot-integration-tests || exit 1 +if [ "$RUN_TEST_SET" == "1" ]; then + mvn test \ + -P github-actions,integration-tests-set-1 && exit 0 || exit 1 +fi +if [ "$RUN_TEST_SET" == "2" ]; then + mvn test \ + -P github-actions,integration-tests-set-2 && exit 0 || exit 1 +fi diff --git a/.github/workflows/scripts/pr-tests/.pinot_tests_unit.sh b/.github/workflows/scripts/pr-tests/.pinot_tests_unit.sh new file mode 100755 index 000000000000..e50cdc93b633 --- /dev/null +++ b/.github/workflows/scripts/pr-tests/.pinot_tests_unit.sh @@ -0,0 +1,52 @@ +#!/bin/bash -x +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Java version +java -version + +# Check network +ifconfig +netstat -i + +# Unit Tests +# - TEST_SET#1 runs install and test together so the module list must ensure no additional modules were tested +# due to the -am flag (include dependency modules) +if [ "$RUN_TEST_SET" == "1" ]; then + mvn test -T 16 \ + -pl 'pinot-spi' \ + -pl 'pinot-segment-spi' \ + -pl 'pinot-common' \ + -pl ':pinot-yammer' \ + -pl 'pinot-core' \ + -pl 'pinot-query-planner' \ + -pl 'pinot-query-runtime' \ + -P github-actions,no-integration-tests || exit 1 +fi +if [ "$RUN_TEST_SET" == "2" ]; then + mvn test -T 16 \ + -pl '!pinot-spi' \ + -pl '!pinot-segment-spi' \ + -pl '!pinot-common' \ + -pl '!pinot-core' \ + -pl '!pinot-query-planner' \ + -pl '!pinot-query-runtime' \ + -pl '!:pinot-yammer' \ + -P github-actions,no-integration-tests || exit 1 +fi diff --git a/.gitignore b/.gitignore index e7ca720ff04e..ca69e90d0e43 100644 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,11 @@ cscope.* .classpath .project .svn +.java-version .externalToolBuilders/ maven-eclipse.xml target/ +examples/ bin/ */bin/ .idea @@ -25,7 +27,6 @@ docs/_build **/.docker/ **/.env **/.factorypath -.mvn/ # Misc .envrc @@ -51,3 +52,5 @@ kubernetes/helm/**/charts/ kubernetes/helm/**/requirements.lock kubernetes/helm/**/Chart.lock +#Develocity +.mvn/.gradle-enterprise/ diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml new file mode 100644 index 000000000000..177fc5d6f1e8 --- /dev/null +++ b/.mvn/extensions.xml @@ -0,0 +1,33 @@ + + + + + com.gradle + gradle-enterprise-maven-extension + 1.20.1 + + + com.gradle + common-custom-user-data-maven-extension + 2 + + diff --git a/.mvn/gradle-enterprise.xml b/.mvn/gradle-enterprise.xml new file mode 100644 index 000000000000..cf1a9a0a9e88 --- /dev/null +++ b/.mvn/gradle-enterprise.xml @@ -0,0 +1,48 @@ + + + + + https://ge.apache.org + false + + + + true + true + true + + #{isFalse(env['GITHUB_ACTIONS'])} + ALWAYS + true + + #{{'0.0.0.0'}} + + + + + #{isFalse(env['GITHUB_ACTIONS'])} + + + false + + + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e1c92e5d57c0..d87c29fcebae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,6 +22,8 @@ Before you begin to contribute, make sure you have reviewed [Dev Environment Setup](https://docs.pinot.apache.org/developers/developers-and-contributors/code-setup) and [Code Modules and Organization](https://docs.pinot.apache.org/developers/developers-and-contributors/code-modules-and-organization) sections and that you have created your own fork of the pinot source code. +> If you wish to contribute to the UI codebase, follow [this guide](/pinot-controller/src/main/resources/Readme.md) to setup UI locally for development. + ### Create a design document If your change is relatively minor, you can skip this step. If you are adding new major feature, we suggest that you add a design document and solicit comments from the community before submitting any code. diff --git a/LICENSE-binary b/LICENSE-binary index caad3acf82cf..dcae96f5c399 100644 --- a/LICENSE-binary +++ b/LICENSE-binary @@ -204,15 +204,15 @@ This project bundles some components that are also licensed under the Apache License Version 2.0: -cloud.localstack:localstack-utils:0.2.19 -com.101tec:zkclient:0.7 -com.chuusai:shapeless_2.12:2.3.3 -com.clearspring.analytics:stream:2.7.0 -com.esri.geometry:esri-geometry-api:2.2.0 +ch.qos.reload4j:reload4j:1.2.25 +cloud.localstack:localstack-utils:0.2.23 +com.101tec:zkclient:0.11 +com.chuusai:shapeless_2.12:2.3.11 +com.clearspring.analytics:stream:2.9.8 +com.dynatrace.hash4j:hash4j:0.17.0 com.fasterxml.jackson.core:jackson-annotations:2.12.7 com.fasterxml.jackson.core:jackson-core:2.12.7 -com.fasterxml.jackson.core:jackson-databind:2.12.7 -com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.12.7 +com.fasterxml.jackson.core:jackson-databind:2.12.7.1 com.fasterxml.jackson.dataformat:jackson-dataformat-csv:2.12.7 com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.12.7 com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.12.7 @@ -221,271 +221,341 @@ com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.12.7 com.fasterxml.jackson.jaxrs:jackson-jaxrs-base:2.12.7 com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.12.7 com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.12.7 -com.fasterxml.jackson.module:jackson-module-jsonSchema:2.12.7 com.fasterxml.jackson.module:jackson-module-scala_2.12:2.12.7 -com.fasterxml.woodstox:woodstox-core:5.0.3 -com.github.ben-manes.caffeine:caffeine:2.6.2 +com.fasterxml.woodstox:woodstox-core:7.0.0 +com.github.jnr:jffi:1.3.13 +com.github.jnr:jnr-a64asm:1.0.0 +com.github.jnr:jnr-constants:0.10.4 +com.github.jnr:jnr-ffi:2.2.16 com.github.os72:protobuf-dynamic:1.0.1 -com.github.seancfoley:ipaddress:5.3.4 +com.github.seancfoley:ipaddress:5.5.0 +com.github.stephenc.jcip:jcip-annotations:1.0-1 com.google.android:annotations:4.1.1.4 -com.google.api-client:google-api-client:1.30.10 -com.google.api.grpc:proto-google-common-protos:1.18.1 -com.google.api.grpc:proto-google-iam-v1:1.0.0 -com.google.apis:google-api-services-storage:v1-rev20200814-1.30.10 -com.google.auto.value:auto-value-annotations:1.7.2 -com.google.cloud:google-cloud-core-http:1.93.8 -com.google.cloud:google-cloud-core:1.93.8 -com.google.cloud:google-cloud-nio:0.120.0-alpha -com.google.cloud:google-cloud-storage:1.113.1 -com.google.code.findbugs:jsr305:3.0.0 -com.google.code.gson:gson:2.2.4 -com.google.errorprone:error_prone_annotations:2.3.4 -com.google.guava:failureaccess:1.0.1 -com.google.guava:guava:20.0 +com.google.api-client:google-api-client:2.6.0 +com.google.api.grpc:gapic-google-cloud-storage-v2:2.40.0-alpha +com.google.api.grpc:grpc-google-cloud-storage-v2:2.40.0-alpha +com.google.api.grpc:proto-google-cloud-storage-v2:2.40.0-alpha +com.google.api.grpc:proto-google-common-protos:2.40.0 +com.google.api.grpc:proto-google-iam-v1:1.35.0 +com.google.apis:google-api-services-storage:v1-rev20240319-2.0.0 +com.google.auto.service:auto-service-annotations:1.1.1 +com.google.auto.service:auto-service:1.1.1 +com.google.auto.value:auto-value-annotations:1.10.4 +com.google.auto:auto-common:1.2.1 +com.google.cloud:google-cloud-core-grpc:2.39.0 +com.google.cloud:google-cloud-core-http:2.39.0 +com.google.cloud:google-cloud-core:2.39.0 +com.google.cloud:google-cloud-nio:0.127.19 +com.google.cloud:google-cloud-storage:2.40.0 +com.google.code.findbugs:jsr305:3.0.2 +com.google.code.gson:gson:2.11.0 +com.google.errorprone:error_prone_annotations:2.28.0 +com.google.guava:failureaccess:1.0.2 +com.google.guava:guava:33.1.0-jre com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava -com.google.http-client:google-http-client-appengine:1.36.0 -com.google.http-client:google-http-client-jackson2:1.36.0 -com.google.http-client:google-http-client:1.36.0 -com.google.j2objc:j2objc-annotations:1.3 -com.google.oauth-client:google-oauth-client:1.31.0 +com.google.http-client:google-http-client-apache-v2:1.44.2 +com.google.http-client:google-http-client-appengine:1.44.2 +com.google.http-client:google-http-client-gson:1.44.2 +com.google.http-client:google-http-client-jackson2:1.44.2 +com.google.http-client:google-http-client:1.44.2 +com.google.j2objc:j2objc-annotations:3.0.0 +com.google.oauth-client:google-oauth-client:1.36.0 com.google.uzaygezen:uzaygezen-core:0.2 -com.jayway.jsonpath:json-path:2.7.0 -com.lmax:disruptor:3.3.4 -com.squareup.okio:okio:1.6.0 -com.squareup.wire:wire-runtime:3.2.2 -com.squareup.wire:wire-schema:3.2.2 +com.googlecode.json-simple:json-simple:1.1.1 +com.jayway.jsonpath:json-path:2.9.0 +com.lmax:disruptor:4.0.0 +com.nimbusds:content-type:2.3 +com.nimbusds:lang-tag:1.7 +com.nimbusds:nimbus-jose-jwt:9.40 +com.nimbusds:oauth2-oidc-sdk:11.9.1 +com.squareup.okio:okio-jvm:3.9.0 +com.squareup.wire:wire-runtime-jvm:4.9.7 +com.squareup.wire:wire-schema-jvm:4.9.7 +com.squareup:javapoet:1.13.0 +com.squareup:kotlinpoet-jvm:1.16.0 com.tdunning:t-digest:3.2 com.twitter:chill-java:0.7.6 -com.twitter:chill_2.12:0.7.6 -com.typesafe.akka:akka-actor_2.12:2.5.21 -com.typesafe.akka:akka-protobuf_2.12:2.5.21 -com.typesafe.akka:akka-slf4j_2.12:2.5.21 -com.typesafe.akka:akka-stream_2.12:2.5.21 -com.typesafe.netty:netty-reactive-streams-http:2.0.4 com.typesafe.netty:netty-reactive-streams:2.0.4 com.typesafe.scala-logging:scala-logging_2.12:3.9.2 -com.typesafe:config:1.3.3 -com.typesafe:ssl-config-core_2.12:0.3.7 -com.uber:h3:4.0.0 -com.yahoo.datasketches:memory:0.8.3 -com.yahoo.datasketches:sketches-core:0.8.3 +com.uber:h3:4.1.1 com.yammer.metrics:metrics-core:2.2.0 +com.yscope.clp:clp-ffi:0.4.4 com.zaxxer:HikariCP-java7:2.4.13 -commons-cli:commons-cli:1.2 -commons-codec:commons-codec:1.15 +commons-cli:commons-cli:1.8.0 +commons-codec:commons-codec:1.17.0 commons-collections:commons-collections:3.2.2 -commons-configuration:commons-configuration:1.10 -commons-httpclient:commons-httpclient:3.1 -commons-io:commons-io:2.11.0 -commons-lang:commons-lang:2.6 -commons-logging:commons-logging:1.2 +commons-io:commons-io:2.16.1 commons-pool:commons-pool:1.6 -info.picocli:picocli:4.6.1 -io.airlift:aircompressor:0.10 -io.circe:circe-core_2.12:0.13.0 -io.circe:circe-generic_2.12:0.13.0 -io.circe:circe-jawn_2.12:0.13.0 -io.circe:circe-numbers_2.12:0.13.0 -io.circe:circe-parser_2.12:0.13.0 -io.confluent:common-config:5.5.3 -io.confluent:common-utils:5.5.3 -io.confluent:kafka-avro-serializer:5.5.3 -io.confluent:kafka-protobuf-provider:5.5.3 -io.confluent:kafka-protobuf-serializer:5.5.3 -io.confluent:kafka-schema-registry-client:5.5.3 -io.confluent:kafka-schema-serializer:5.5.3 -io.dropwizard.metrics:metrics-core:4.2.2 -io.dropwizard.metrics:metrics-jmx:4.2.2 -io.grpc:grpc-api:1.41.0 -io.grpc:grpc-context:1.14.0 -io.grpc:grpc-core:1.41.0 -io.grpc:grpc-netty-shaded:1.41.0 -io.grpc:grpc-protobuf-lite:1.19.0 -io.grpc:grpc-protobuf:1.41.0 -io.grpc:grpc-stub:1.41.0 -io.netty:netty-all:4.1.79.Final -io.netty:netty-buffer:4.1.79.Final -io.netty:netty-codec-dns:4.1.79.Final -io.netty:netty-codec-haproxy:4.1.79.Final -io.netty:netty-codec-http2:4.1.79.Final -io.netty:netty-codec-http:4.1.79.Final -io.netty:netty-codec-memcache:4.1.79.Final -io.netty:netty-codec-mqtt:4.1.79.Final -io.netty:netty-codec-redis:4.1.79.Final -io.netty:netty-codec-smtp:4.1.79.Final -io.netty:netty-codec-socks:4.1.79.Final -io.netty:netty-codec-stomp:4.1.79.Final -io.netty:netty-codec-xml:4.1.79.Final -io.netty:netty-codec:4.1.79.Final -io.netty:netty-common:4.1.79.Final -io.netty:netty-handler-proxy:4.1.79.Final -io.netty:netty-handler:4.1.79.Final -io.netty:netty-resolver-dns-classes-macos:4.1.79.Final -io.netty:netty-resolver-dns-native-macos:4.1.79.Final -io.netty:netty-resolver-dns:4.1.79.Final -io.netty:netty-resolver:4.1.79.Final -io.netty:netty-tcnative-boringssl-static:2.0.53.Final -io.netty:netty-tcnative-classes:2.0.53.Final -io.netty:netty-transport-classes-epoll:4.1.79.Final -io.netty:netty-transport-classes-kqueue:4.1.79.Final -io.netty:netty-transport-native-epoll:4.1.79.Final -io.netty:netty-transport-native-kqueue:4.1.79.Final -io.netty:netty-transport-native-unix-common:4.1.79.Final -io.netty:netty-transport-rxtx:4.1.79.Final -io.netty:netty-transport-sctp:4.1.79.Final -io.netty:netty-transport-udt:4.1.79.Final -io.netty:netty-transport:4.1.79.Final -io.opencensus:opencensus-api:0.24.0 -io.opencensus:opencensus-contrib-http-util:0.24.0 -io.perfmark:perfmark-api:0.23.0 -io.projectreactor.netty:reactor-netty-core:1.0.15 -io.projectreactor.netty:reactor-netty-http:1.0.15 -io.projectreactor:reactor-core:3.4.14 -io.prometheus:simpleclient:0.8.1 -io.prometheus:simpleclient_common:0.8.1 -io.prometheus:simpleclient_hotspot:0.8.1 -io.swagger:swagger-annotations:1.6.2 -io.swagger:swagger-core:1.6.6 -io.swagger:swagger-jaxrs:1.6.6 -io.swagger:swagger-jersey2-jaxrs:1.6.6 -io.swagger:swagger-models:1.6.6 -it.unimi.dsi:fastutil:8.2.3 +info.picocli:picocli:4.7.6 +io.airlift:aircompressor:0.27 +io.circe:circe-core_2.12:0.14.8 +io.circe:circe-generic_2.12:0.14.8 +io.circe:circe-jawn_2.12:0.14.8 +io.circe:circe-numbers_2.12:0.14.8 +io.circe:circe-parser_2.12:0.14.8 +io.confluent:common-utils:7.6.1 +io.confluent:kafka-avro-serializer:7.6.1 +io.confluent:kafka-protobuf-provider:7.6.1 +io.confluent:kafka-protobuf-serializer:7.6.1 +io.confluent:kafka-protobuf-types:7.6.1 +io.confluent:kafka-schema-registry-client:7.6.1 +io.confluent:kafka-schema-serializer:7.6.1 +io.confluent:logredactor-metrics:1.0.12 +io.confluent:logredactor:1.0.12 +io.dropwizard.metrics:metrics-core:4.2.26 +io.dropwizard.metrics:metrics-jmx:4.2.26 +io.github.hakky54:sslcontext-kickstart-for-netty:8.3.6 +io.github.hakky54:sslcontext-kickstart:8.3.6 +io.grpc:grpc-alts:1.65.0 +io.grpc:grpc-api:1.65.0 +io.grpc:grpc-auth:1.65.0 +io.grpc:grpc-context:1.65.0 +io.grpc:grpc-core:1.65.0 +io.grpc:grpc-googleapis:1.65.0 +io.grpc:grpc-grpclb:1.65.0 +io.grpc:grpc-inprocess:1.65.0 +io.grpc:grpc-netty-shaded:1.65.0 +io.grpc:grpc-protobuf-lite:1.65.0 +io.grpc:grpc-protobuf:1.65.0 +io.grpc:grpc-rls:1.65.0 +io.grpc:grpc-services:1.65.0 +io.grpc:grpc-stub:1.65.0 +io.grpc:grpc-util:1.65.0 +io.grpc:grpc-xds:1.65.0 +io.netty:netty-all:4.1.111.Final +io.netty:netty-buffer:4.1.111.Final +io.netty:netty-codec-dns:4.1.111.Final +io.netty:netty-codec-haproxy:4.1.111.Final +io.netty:netty-codec-http2:4.1.111.Final +io.netty:netty-codec-http:4.1.111.Final +io.netty:netty-codec-memcache:4.1.111.Final +io.netty:netty-codec-mqtt:4.1.111.Final +io.netty:netty-codec-redis:4.1.111.Final +io.netty:netty-codec-smtp:4.1.111.Final +io.netty:netty-codec-socks:4.1.111.Final +io.netty:netty-codec-stomp:4.1.111.Final +io.netty:netty-codec-xml:4.1.111.Final +io.netty:netty-codec:4.1.111.Final +io.netty:netty-common:4.1.111.Final +io.netty:netty-handler-proxy:4.1.111.Final +io.netty:netty-handler-ssl-ocsp:4.1.111.Final +io.netty:netty-handler:4.1.111.Final +io.netty:netty-resolver-dns-classes-macos:4.1.111.Final +io.netty:netty-resolver-dns-native-macos:4.1.111.Final +io.netty:netty-resolver-dns:4.1.111.Final +io.netty:netty-resolver:4.1.111.Final +io.netty:netty-tcnative-boringssl-static:2.0.65.Final +io.netty:netty-tcnative-classes:2.0.65.Final +io.netty:netty-transport-classes-epoll:4.1.111.Final +io.netty:netty-transport-classes-kqueue:4.1.111.Final +io.netty:netty-transport-native-epoll:4.1.111.Final +io.netty:netty-transport-native-kqueue:4.1.111.Final +io.netty:netty-transport-native-unix-common:4.1.111.Final +io.netty:netty-transport-rxtx:4.1.111.Final +io.netty:netty-transport-sctp:4.1.111.Final +io.netty:netty-transport-udt:4.1.111.Final +io.netty:netty-transport:4.1.111.Final +io.opencensus:opencensus-api:0.31.1 +io.opencensus:opencensus-contrib-http-util:0.31.1 +io.opencensus:opencensus-proto:0.2.0 +io.opentelemetry:opentelemetry-api-incubator:1.37.0-alpha +io.opentelemetry:opentelemetry-api:1.37.0 +io.opentelemetry:opentelemetry-context:1.37.0 +io.perfmark:perfmark-api:0.26.0 +io.projectreactor.netty:reactor-netty-core:1.0.45 +io.projectreactor.netty:reactor-netty-http:1.0.45 +io.projectreactor:reactor-core:3.4.38 +io.swagger.core.v3:swagger-annotations:2.1.10 +io.swagger:swagger-annotations:1.6.14 +io.swagger:swagger-core:1.6.14 +io.swagger:swagger-jaxrs:1.6.14 +io.swagger:swagger-jersey2-jaxrs:1.6.14 +io.swagger:swagger-models:1.6.14 +it.unimi.dsi:fastutil:8.5.13 jakarta.validation:jakarta.validation-api:2.0.2 javax.inject:javax.inject:1 javax.validation:validation-api:2.0.1.Final -joda-time:joda-time:2.10.5 -net.java.dev.jna:jna:4.1.0 -net.minidev:accessors-smart:1.2 -net.minidev:json-smart:2.3 -org.apache.avro:avro-protobuf:1.9.1 -org.apache.avro:avro:1.9.2 -org.apache.bookkeeper:bookkeeper-common-allocator:4.12.0 -org.apache.bookkeeper:circe-checksum:4.12.0 -org.apache.calcite.avatica:avatica-core:1.20.0 -org.apache.calcite:calcite-babel:1.29.0 -org.apache.calcite:calcite-core:1.29.0 -org.apache.calcite:calcite-linq4j:1.29.0 -org.apache.commons:commons-collections4:4.1 -org.apache.commons:commons-compress:1.21 -org.apache.commons:commons-csv:1.0 -org.apache.commons:commons-lang3:3.11 -org.apache.commons:commons-math3:3.2 +joda-time:joda-time:2.12.7 +net.java.dev.jna:jna-platform:5.14.0 +net.java.dev.jna:jna:5.14.0 +net.minidev:accessors-smart:2.5.1 +net.minidev:json-smart:2.5.1 +net.openhft:chronicle-analytics:2.26ea1 +net.openhft:chronicle-core:2.26ea1 +net.openhft:posix:2.26ea1 +org.apache.avro:avro:1.11.3 +org.apache.calcite.avatica:avatica-core:1.25.0 +org.apache.calcite:calcite-babel:1.37.0 +org.apache.calcite:calcite-core:1.37.0 +org.apache.calcite:calcite-linq4j:1.37.0 +org.apache.commons:commons-collections4:4.4 +org.apache.commons:commons-compress:1.26.2 +org.apache.commons:commons-configuration2:2.11.0 +org.apache.commons:commons-csv:1.11.0 +org.apache.commons:commons-lang3:3.14.0 +org.apache.commons:commons-math3:3.6.1 org.apache.commons:commons-math:2.1 -org.apache.datasketches:datasketches-java:1.2.0-incubating -org.apache.datasketches:datasketches-memory:1.2.0-incubating -org.apache.flink:flink-annotations:1.12.0 -org.apache.flink:flink-core:1.12.0 -org.apache.flink:flink-file-sink-common:1.12.0 -org.apache.flink:flink-hadoop-fs:1.12.0 -org.apache.flink:flink-java:1.12.0 -org.apache.flink:flink-metrics-core:1.12.0 -org.apache.flink:flink-queryable-state-client-java:1.12.0 -org.apache.flink:flink-runtime_2.12:1.12.0 -org.apache.flink:flink-shaded-asm-7:7.1-12.0 -org.apache.flink:flink-shaded-guava:18.0-12.0 -org.apache.flink:flink-shaded-jackson:2.10.1-12.0 -org.apache.flink:flink-shaded-netty:4.1.49.Final-12.0 -org.apache.flink:flink-shaded-zookeeper-3:3.4.14-12.0 -org.apache.flink:flink-streaming-java_2.12:1.12.0 -org.apache.flink:force-shading:1.12.0 -org.apache.helix:helix-common:1.0.4 -org.apache.helix:helix-core:1.0.4 -org.apache.helix:metadata-store-directory-common:1.0.4 -org.apache.helix:metrics-common:1.0.4 -org.apache.helix:zookeeper-api:1.0.4 -org.apache.hive:hive-storage-api:2.7.1 -org.apache.httpcomponents:httpclient:4.5.13 -org.apache.httpcomponents:httpcore:4.4.13 -org.apache.httpcomponents:httpmime:4.5.13 +org.apache.commons:commons-text:1.12.0 +org.apache.curator:curator-client:5.2.0 +org.apache.curator:curator-framework:5.2.0 +org.apache.datasketches:datasketches-java:6.0.0 +org.apache.datasketches:datasketches-memory:2.2.0 +org.apache.flink:flink-annotations:1.19.1 +org.apache.flink:flink-connector-datagen:1.19.1 +org.apache.flink:flink-core:1.19.1 +org.apache.flink:flink-file-sink-common:1.19.1 +org.apache.flink:flink-hadoop-fs:1.19.1 +org.apache.flink:flink-java:1.19.1 +org.apache.flink:flink-metrics-core:1.19.1 +org.apache.flink:flink-queryable-state-client-java:1.19.1 +org.apache.flink:flink-rpc-akka-loader:1.19.1 +org.apache.flink:flink-rpc-core:1.19.1 +org.apache.flink:flink-runtime:1.19.1 +org.apache.flink:flink-shaded-asm-9:9.5-17.0 +org.apache.flink:flink-shaded-guava:31.1-jre-17.0 +org.apache.flink:flink-shaded-jackson:2.14.2-17.0 +org.apache.flink:flink-shaded-netty:4.1.91.Final-17.0 +org.apache.flink:flink-shaded-zookeeper-3:3.7.1-17.0 +org.apache.flink:flink-streaming-java:1.19.1 +org.apache.hadoop.thirdparty:hadoop-shaded-guava:1.1.1 +org.apache.hadoop.thirdparty:hadoop-shaded-protobuf_3_21:1.2.0 +org.apache.hadoop.thirdparty:hadoop-shaded-protobuf_3_7:1.1.1 +org.apache.hadoop:hadoop-annotations:3.3.6 +org.apache.hadoop:hadoop-auth:3.3.6 +org.apache.hadoop:hadoop-mapreduce-client-core:3.3.6 +org.apache.hadoop:hadoop-yarn-api:3.3.6 +org.apache.hadoop:hadoop-yarn-client:3.3.6 +org.apache.hadoop:hadoop-yarn-common:3.3.6 +org.apache.helix:helix-common:1.3.1 +org.apache.helix:helix-core:1.3.1 +org.apache.helix:metadata-store-directory-common:1.3.1 +org.apache.helix:metrics-common:1.3.1 +org.apache.helix:zookeeper-api:1.3.1 +org.apache.hive:hive-storage-api:2.8.1 +org.apache.httpcomponents.client5:httpclient5:5.3.1 +org.apache.httpcomponents.core5:httpcore5-h2:5.2.4 +org.apache.httpcomponents.core5:httpcore5:5.2.4 +org.apache.httpcomponents:httpclient:4.5.14 +org.apache.httpcomponents:httpcore:4.4.16 +org.apache.httpcomponents:httpmime:4.5.14 org.apache.kafka:kafka-clients:2.8.1 org.apache.kafka:kafka-metadata:2.8.1 org.apache.kafka:kafka-raft:2.8.1 -org.apache.kafka:kafka_2.10:0.9.0.1 org.apache.kafka:kafka_2.12:2.8.1 -org.apache.logging.log4j:log4j-1.2-api:2.17.1 -org.apache.logging.log4j:log4j-api:2.17.1 -org.apache.logging.log4j:log4j-core:2.17.1 -org.apache.logging.log4j:log4j-slf4j-impl:2.17.1 -org.apache.lucene:lucene-analyzers-common:8.2.0 -org.apache.lucene:lucene-core:8.2.0 -org.apache.lucene:lucene-queries:8.2.0 -org.apache.lucene:lucene-queryparser:8.2.0 -org.apache.lucene:lucene-sandbox:8.2.0 -org.apache.orc:orc-core:1.5.9 -org.apache.orc:orc-shims:1.5.9 -org.apache.parquet:parquet-avro:1.11.1 -org.apache.parquet:parquet-column:1.11.1 -org.apache.parquet:parquet-common:1.11.1 -org.apache.parquet:parquet-encoding:1.11.1 -org.apache.parquet:parquet-format-structures:1.11.1 -org.apache.parquet:parquet-hadoop:1.11.1 -org.apache.parquet:parquet-jackson:1.11.1 -org.apache.pulsar:bouncy-castle-bc:2.7.2 -org.apache.pulsar:protobuf-shaded:2.1.0-incubating -org.apache.pulsar:pulsar-client-admin-original:2.7.2 -org.apache.pulsar:pulsar-client-api:2.7.2 -org.apache.pulsar:pulsar-client-original:2.7.2 -org.apache.pulsar:pulsar-common:2.7.2 -org.apache.pulsar:pulsar-transaction-common:2.7.2 -org.apache.spark:spark-launcher_2.12:3.2.1 -org.apache.spark:spark-tags_2.12:3.2.1 -org.apache.thrift:libthrift:0.15.0 -org.apache.yetus:audience-annotations:0.13.0 -org.apache.zookeeper:zookeeper-jute:3.6.3 -org.apache.zookeeper:zookeeper:3.6.3 -org.apiguardian:apiguardian-api:1.1.0 +org.apache.kerby:kerb-admin:2.0.3 +org.apache.kerby:kerb-client:2.0.3 +org.apache.kerby:kerb-common:2.0.3 +org.apache.kerby:kerb-core:2.0.3 +org.apache.kerby:kerb-crypto:2.0.3 +org.apache.kerby:kerb-identity:2.0.3 +org.apache.kerby:kerb-server:2.0.3 +org.apache.kerby:kerb-simplekdc:2.0.3 +org.apache.kerby:kerb-util:2.0.3 +org.apache.kerby:kerby-asn1:2.0.3 +org.apache.kerby:kerby-config:2.0.3 +org.apache.kerby:kerby-pkix:2.0.3 +org.apache.kerby:kerby-util:2.0.3 +org.apache.kerby:kerby-xdr:2.0.3 +org.apache.kerby:token-provider:2.0.3 +org.apache.logging.log4j:log4j-1.2-api:2.23.1 +org.apache.logging.log4j:log4j-api:2.23.1 +org.apache.logging.log4j:log4j-core:2.23.1 +org.apache.logging.log4j:log4j-slf4j2-impl:2.23.1 +org.apache.lucene:lucene-analysis-common:9.11.1 +org.apache.lucene:lucene-backward-codecs:9.11.1 +org.apache.lucene:lucene-core:9.11.1 +org.apache.lucene:lucene-queries:9.11.1 +org.apache.lucene:lucene-queryparser:9.11.1 +org.apache.lucene:lucene-sandbox:9.11.1 +org.apache.orc:orc-core:1.9.3 +org.apache.orc:orc-shims:1.9.3 +org.apache.parquet:parquet-avro:1.14.1 +org.apache.parquet:parquet-column:1.14.1 +org.apache.parquet:parquet-common:1.14.1 +org.apache.parquet:parquet-encoding:1.14.1 +org.apache.parquet:parquet-format-structures:1.14.1 +org.apache.parquet:parquet-hadoop:1.14.1 +org.apache.parquet:parquet-jackson:1.14.1 +org.apache.pulsar:bouncy-castle-bc:3.3.0 +org.apache.pulsar:pulsar-client-admin-api:3.3.0 +org.apache.pulsar:pulsar-client-api:3.3.0 +org.apache.pulsar:pulsar-client:3.3.0 +org.apache.spark:spark-launcher_2.12:3.5.1 +org.apache.spark:spark-tags_2.12:3.5.1 +org.apache.thrift:libthrift:0.18.1 +org.apache.yetus:audience-annotations:0.15.0 +org.apache.zookeeper:zookeeper-jute:3.9.2 +org.apache.zookeeper:zookeeper:3.9.2 +org.apiguardian:apiguardian-api:1.1.2 org.asynchttpclient:async-http-client-netty-utils:2.12.3 org.asynchttpclient:async-http-client:2.12.3 org.codehaus.groovy:groovy-all:2.4.21 -org.eclipse.jetty:jetty-http:9.4.45.v20220203 -org.eclipse.jetty:jetty-io:9.4.45.v20220203 -org.eclipse.jetty:jetty-security:9.4.45.v20220203 -org.eclipse.jetty:jetty-server:9.4.45.v20220203 -org.eclipse.jetty:jetty-servlet:9.4.45.v20220203 -org.eclipse.jetty:jetty-util-ajax:9.4.45.v20220203 -org.eclipse.jetty:jetty-util:9.4.45.v20220203 -org.javassist:javassist:3.19.0-GA -org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.71 -org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.71 -org.jetbrains.kotlin:kotlin-stdlib:1.4.0 -org.jetbrains:annotations:13.0 +org.conscrypt:conscrypt-openjdk-uber:2.5.2 +org.eclipse.jetty.websocket:websocket-api:9.4.54.v20240208 +org.eclipse.jetty.websocket:websocket-client:9.4.54.v20240208 +org.eclipse.jetty.websocket:websocket-common:9.4.54.v20240208 +org.eclipse.jetty:jetty-client:9.4.54.v20240208 +org.eclipse.jetty:jetty-http:9.4.54.v20240208 +org.eclipse.jetty:jetty-io:9.4.54.v20240208 +org.javassist:javassist:3.30.2-GA +org.jetbrains.kotlin:kotlin-reflect:1.9.22 +org.jetbrains.kotlin:kotlin-stdlib-common:1.9.24 +org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.24 +org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.24 +org.jetbrains.kotlin:kotlin-stdlib:1.9.24 +org.jetbrains:annotations:17.0.0 +org.locationtech.proj4j:proj4j:1.2.2 org.lz4:lz4-java:1.8.0 org.objenesis:objenesis:2.1 org.quartz-scheduler:quartz:2.3.2 -org.roaringbitmap:RoaringBitmap:0.9.35 -org.roaringbitmap:shims:0.9.35 +org.roaringbitmap:RoaringBitmap:1.1.0 org.scala-lang.modules:scala-collection-compat_2.12:2.3.0 org.scala-lang.modules:scala-java8-compat_2.12:0.9.1 -org.scala-lang.modules:scala-xml_2.12:1.3.0 -org.typelevel:macro-compat_2.12:1.1.1 -org.webjars:swagger-ui:3.23.11 -org.wildfly.openssl:wildfly-openssl:1.0.7.Final +org.scala-lang.modules:scala-xml_2.12:2.3.0 +org.scala-lang:scala-library:2.12.19 +org.slf4j:jcl-over-slf4j:2.0.13 +org.snakeyaml:snakeyaml-engine:2.6 +org.webjars:swagger-ui:5.17.14 org.xerial.larray:larray-buffer:0.4.1 org.xerial.larray:larray-mmap:0.4.1 -org.xerial.snappy:snappy-java:1.1.8.2 -org.yaml:snakeyaml:1.33 -software.amazon.awssdk:annotations:2.14.28 -software.amazon.awssdk:apache-client:2.14.28 -software.amazon.awssdk:arns:2.14.28 -software.amazon.awssdk:auth:2.14.28 -software.amazon.awssdk:aws-cbor-protocol:2.14.28 -software.amazon.awssdk:aws-core:2.14.28 -software.amazon.awssdk:aws-json-protocol:2.14.28 -software.amazon.awssdk:aws-query-protocol:2.14.28 -software.amazon.awssdk:aws-xml-protocol:2.14.28 -software.amazon.awssdk:http-client-spi:2.14.28 -software.amazon.awssdk:kinesis:2.14.28 -software.amazon.awssdk:metrics-spi:2.14.28 -software.amazon.awssdk:netty-nio-client:2.14.28 -software.amazon.awssdk:profiles:2.14.28 -software.amazon.awssdk:protocol-core:2.14.28 -software.amazon.awssdk:regions:2.14.28 -software.amazon.awssdk:s3:2.14.28 -software.amazon.awssdk:sdk-core:2.14.28 -software.amazon.awssdk:sts:2.14.28 -software.amazon.awssdk:utils:2.14.28 +org.xerial.snappy:snappy-java:1.1.10.5 +org.yaml:snakeyaml:2.2 +software.amazon.awssdk:annotations:2.26.11 +software.amazon.awssdk:apache-client:2.26.11 +software.amazon.awssdk:arns:2.26.11 +software.amazon.awssdk:auth:2.26.11 +software.amazon.awssdk:aws-cbor-protocol:2.26.11 +software.amazon.awssdk:aws-core:2.26.11 +software.amazon.awssdk:aws-json-protocol:2.26.11 +software.amazon.awssdk:aws-query-protocol:2.26.11 +software.amazon.awssdk:aws-xml-protocol:2.26.11 +software.amazon.awssdk:checksums-spi:2.26.11 +software.amazon.awssdk:checksums:2.26.11 +software.amazon.awssdk:crt-core:2.26.11 +software.amazon.awssdk:endpoints-spi:2.26.11 +software.amazon.awssdk:http-auth-aws:2.26.11 +software.amazon.awssdk:http-auth-spi:2.26.11 +software.amazon.awssdk:http-auth:2.26.11 +software.amazon.awssdk:http-client-spi:2.26.11 +software.amazon.awssdk:identity-spi:2.26.11 +software.amazon.awssdk:json-utils:2.26.11 +software.amazon.awssdk:kinesis:2.26.11 +software.amazon.awssdk:metrics-spi:2.26.11 +software.amazon.awssdk:netty-nio-client:2.26.11 +software.amazon.awssdk:profiles:2.26.11 +software.amazon.awssdk:protocol-core:2.26.11 +software.amazon.awssdk:regions:2.26.11 +software.amazon.awssdk:retries-spi:2.26.11 +software.amazon.awssdk:retries:2.26.11 +software.amazon.awssdk:s3:2.26.11 +software.amazon.awssdk:sdk-core:2.26.11 +software.amazon.awssdk:sts:2.26.11 +software.amazon.awssdk:third-party-jackson-core:2.26.11 +software.amazon.awssdk:third-party-jackson-dataformat-cbor:2.26.11 +software.amazon.awssdk:utils:2.26.11 software.amazon.eventstream:eventstream:1.0.1 -xml-apis:xml-apis:1.4.01 +tools.profiler:async-profiler:2.9 +xml-apis:xml-apis:1.0.b2 ------------------------------------------------------------------------------------ @@ -496,81 +566,80 @@ of these licenses. MIT License ----------- -com.azure:azure-core-http-netty:1.11.8 -com.azure:azure-core:1.26.0 -com.azure:azure-storage-blob:12.15.0 -com.azure:azure-storage-common:12.15.0 -com.azure:azure-storage-file-datalake:12.8.0 -com.azure:azure-storage-internal-avro:12.2.0 -com.microsoft.azure:azure-data-lake-store-sdk:2.3.10 -com.github.scopt:scopt_2.12:3.5.0 -net.sf.jopt-simple:jopt-simple:4.6 +com.azure:azure-core-http-netty:1.15.1 +com.azure:azure-core:1.49.1 +com.azure:azure-identity:1.13.0 +com.azure:azure-json::1.1.0 +com.azure:azure-storage-blob:12.26.1 +com.azure:azure-storage-common:12.25.1 +com.azure:azure-storage-file-datalake:12.19.1 +com.azure:azure-storage-internal-avro:12.11.1 +com.azure:azure-xml:1.0.0 +com.eclipsesource.minimal-json:minimal-json:0.9.5 +com.github.jnr:jnr-x86asm:1.0.2 +com.microsoft.azure:msal4j-persistence-extension:1.3.0 +com.microsoft.azure:msal4j:1.15.1 +net.sf.jopt-simple:jopt-simple:5.0.4 net.sourceforge.argparse4j:argparse4j:0.7.0 -org.checkerframework:checker-compat-qual:2.5.5 -org.checkerframework:checker-qual:3.10.0 -org.codehaus.mojo:animal-sniffer-annotations:1.17 -org.reactivestreams:reactive-streams:1.0.3 -org.typelevel:cats-core_2.12:2.1.0 -org.typelevel:cats-kernel_2.12:2.1.0 -org.typelevel:cats-macros_2.12:2.1.0 -org.typelevel:jawn-parser_2.12:1.0.0 -org.slf4j:jul-to-slf4j:1.7.25 -org.slf4j:slf4j-api:1.7.25 +org.checkerframework:checker-qual:3.44.0 +org.codehaus.mojo:animal-sniffer-annotations:1.23 +org.reactivestreams:reactive-streams:1.0.4 +org.slf4j:slf4j-api:2.0.13 +org.slf4j:slf4j-reload4j:1.7.36 +org.typelevel:cats-core_2.12:2.10.0 +org.typelevel:cats-kernel_2.12:2.10.0 +org.typelevel:jawn-parser_2.12:1.5.1 BSD --- com.thoughtworks.paranamer:paranamer:2.8 - BSD 2-Clause ------------ -com.github.luben:zstd-jni:1.5.2-3 -org.clapper:grizzled-slf4j_2.12:1.3.2 -org.codehaus.woodstox:stax2-api:3.1.4 +com.github.luben:zstd-jni:1.5.6-3 +org.codehaus.woodstox:stax2-api:4.2.2 BSD 3-Clause ------------ com.esotericsoftware.kryo:kryo:2.24.0 com.esotericsoftware.minlog:minlog:1.2 -com.google.api:api-common:1.10.0 -com.google.api:gax-httpjson:0.75.2 -com.google.api:gax:1.58.2 -com.google.auth:google-auth-library-credentials:0.21.1 -com.google.auth:google-auth-library-oauth2-http:0.21.1 -com.google.protobuf:protobuf-java-util:3.13.0 -com.google.protobuf:protobuf-java:3.19.2 -com.google.protobuf:protobuf-lite:3.0.1 -com.jcabi:jcabi-aspects:0.22 -com.jcabi:jcabi-log:0.17.1 -org.antlr:antlr4-runtime:4.6 -org.codehaus.janino:commons-compiler:3.1.6 -org.codehaus.janino:janino:3.1.6 -org.ow2.asm:asm:5.0.4 -org.scala-lang:scala-library:2.10.5 -org.scala-lang.modules:scala-parser-combinators_2.12:1.1.1 -org.threeten:threeten-extra:1.5.0 -org.threeten:threetenbp:1.4.4 +com.google.api:api-common:2.32.0 +com.google.api:gax-grpc:2.49.0 +com.google.api:gax-httpjson:2.49.0 +com.google.api:gax:2.49.0 +com.google.auth:google-auth-library-credentials:1.23.0 +com.google.auth:google-auth-library-oauth2-http:1.23.0 +com.google.protobuf:protobuf-java-util:3.25.3 +com.google.protobuf:protobuf-java:3.25.3 +org.codehaus.janino:commons-compiler:3.1.12 +org.codehaus.janino:janino:3.1.12 +org.codehaus.jettison:jettison:1.5.4 +org.jline:jline:3.26.2 +org.ow2.asm:asm:9.7 +org.threeten:threeten-extra:1.7.1 +org.threeten:threetenbp:1.6.9 Common Development and Distribution License (CDDL) 1.0 ------------------------------------------------------ (see licenses/LICENSE-cddl-1.0.txt) -javax.activation:activation:1.1.0 -javax.annotation:javax.annotation-api:1.2 -javax.servlet:javax.servlet-api:3.0.1 -org.glassfish.jersey.containers:jersey-container-servlet-core:2.35 +com.sun.activation:javax.activation:1.2.0 +org.glassfish.jersey.containers:jersey-container-servlet-core:2.42 Common Development and Distribution License (CDDL) 1.1 ------------------------------------------------------ (see licenses/LICENSE-cddl-1.1.txt) -com.sun.xml.bind:jaxb-core:2.3.0 -javax.ws.rs:javax.ws.rs-api:2.0.1 -javax.xml.bind:jaxb-api:2.2.11 +com.github.pjfanning:jersey-json:1.20 +com.sun.xml.bind:jaxb-impl:2.2.3-1 +javax.activation:javax.activation-api:1.2.0 +javax.annotation:javax.annotation-api:1.3.2 +javax.servlet:javax.servlet-api:4.0.1 +javax.xml.bind:jaxb-api:2.3.1 Eclipse Public License (EPL) 1.0 @@ -579,8 +648,9 @@ Eclipse Public License (EPL) 1.0 com.mchange:c3p0:0.9.5.4 com.mchange:mchange-commons-java:0.2.15 -org.aspectj:aspectjrt:1.8.4 -org.locationtech.jts:jts-core:1.16.1 +ch.qos.logback:logback-classic:1.2.13 +ch.qos.logback:logback-core:1.2.13 +javax.ws.rs:javax.ws.rs-api:2.1.1 Eclipse Public License (EPL) 2.0 @@ -588,6 +658,7 @@ Eclipse Public License (EPL) 2.0 (see licenses/LICENSE-epl-2.0.txt) jakarta.annotation:jakarta.annotation-api:1.3.5 +jakarta.servlet:jakarta.servlet-api:4.0.4 jakarta.ws.rs:jakarta.ws.rs-api:2.1.6 org.glassfish.grizzly:grizzly-framework:2.4.4 org.glassfish.grizzly:grizzly-http-server:2.4.4 @@ -596,17 +667,21 @@ org.glassfish.hk2.external:aopalliance-repackaged:2.6.1 org.glassfish.hk2.external:jakarta.inject:2.6.1 org.glassfish.hk2:hk2-api:2.6.1 org.glassfish.hk2:hk2-locator:2.6.1 +org.glassfish.hk2:hk2-metadata-generator:2.6.1 org.glassfish.hk2:hk2-utils:2.6.1 org.glassfish.hk2:osgi-resource-locator:1.0.3 -org.glassfish.jersey.containers:jersey-container-grizzly2-http:2.35 -org.glassfish.jersey.core:jersey-client:2.35 -org.glassfish.jersey.core:jersey-common:2.35 -org.glassfish.jersey.core:jersey-server:2.35 -org.glassfish.jersey.ext:jersey-entity-filtering:2.35 -org.glassfish.jersey.inject:jersey-hk2:2.35 -org.glassfish.jersey.media:jersey-media-json-jackson:2.35 -org.glassfish.jersey.media:jersey-media-multipart:2.35 -org.glassfish.tyrus.bundles:tyrus-standalone-client:1.15 +org.glassfish.jersey.containers:jersey-container-grizzly2-http:2.42 +org.glassfish.jersey.core:jersey-client:2.42 +org.glassfish.jersey.core:jersey-common:2.42 +org.glassfish.jersey.core:jersey-server:2.42 +org.glassfish.jersey.ext:jersey-entity-filtering:2.42 +org.glassfish.jersey.inject:jersey-hk2:2.42 +org.glassfish.jersey.media:jersey-media-json-jackson:2.42 +org.glassfish.jersey.media:jersey-media-multipart:2.42 +org.glassfish.tyrus.bundles:tyrus-standalone-client:2.1.5 +org.locationtech.jts.io:jts-io-common:1.19.0 +org.locationtech.jts:jts-core:1.19.0 + Eclipse Distribution License (EDL) 1.0 @@ -614,14 +689,13 @@ Eclipse Distribution License (EDL) 1.0 (see licenses/LICENSE-edl-1.0.txt) com.sun.activation:jakarta.activation:1.2.2 -jakarta.activation:jakarta.activation-api:1.2.1 -jakarta.xml.bind:jakarta.xml.bind-api:2.3.2 -org.jvnet.mimepull:mimepull:1.9.13 +jakarta.xml.bind:jakarta.xml.bind-api:2.3.3 +org.jvnet.mimepull:mimepull:1.9.15 WTFPL License ------------- -org.reflections:reflections:0.9.11 +org.reflections:reflections:0.10.2 Creative Commons Attribution License (CC BY 2.5) @@ -631,12 +705,22 @@ net.jcip:jcip-annotations:1.0 Bounty Castle License --------------------- -org.bouncycastle:bcpkix-jdk15to18:1.68 -org.bouncycastle:bcprov-ext-jdk15to18:1.68 -org.bouncycastle:bcprov-jdk15to18:1.68 - +org.bouncycastle:bcpkix-jdk18on:1.78.1 +org.bouncycastle:bcprov-jdk18on:1.78.1 +org.bouncycastle:bcutil-jdk18on:1.78.1 ISC License ----------- org.mindrot:jbcrypt:0.4 + +The Go License +-------------- + +com.google.re2j:re2j:1.7 + +JCraft, Inc. +------------ + +com.jcraft:jsch:0.1.55 + diff --git a/NOTICE-binary b/NOTICE-binary index 186fe1a2051a..81e0ef937398 100644 --- a/NOTICE-binary +++ b/NOTICE-binary @@ -1,6 +1,3 @@ -Apache Pinot -Copyright 2018-2022 Apache Software Foundation - This product includes software developed at The Apache Software Foundation (http://www.apache.org/). @@ -8,60 +5,53 @@ The Apache Software Foundation (http://www.apache.org/). // NOTICE file corresponding to the section 4d of The Apache License, // Version 2.0, in this case for // ------------------------------------------------------------------ +// NOTICE file corresponding to the section 4d of The Apache License, +// Version 2.0, in this case for +// ------------------------------------------------------------------ Spark Project Tags -Copyright 2022 The Apache Software Foundation +Copyright 2024 Apache Software Foundation This product includes software developed at The Apache Software Foundation (http://www.apache.org/). -Apache Commons Configuration -Copyright 2001-2013 The Apache Software Foundation -Apache Commons Codec -Copyright 2002-2020 The Apache Software Foundation +Apache Commons Lang +Copyright 2001-2023 The Apache Software Foundation This product includes software developed at The Apache Software Foundation (https://www.apache.org/). -src/test/org/apache/commons/codec/language/DoubleMetaphoneTest.java -contains test data from http://aspell.net/test/orig/batch0.tab. -Copyright (C) 2002 Kevin Atkinson (kevina@gnu.org) - -=============================================================================== - -The content of package org.apache.commons.codec.language.bm has been translated -from the original php source code available at http://stevemorse.org/phoneticinfo.htm -with permission from the original authors. -Original source copyright: -Copyright (c) 2008 Alexander Beider & Stephen P. Morse. +Apache Commons Collections +Copyright 2001-2019 The Apache Software Foundation -Apache Commons IO -Copyright 2002-2021 The Apache Software Foundation +Apache Commons Math +Copyright 2001-2016 The Apache Software Foundation -Apache Commons Lang -Copyright 2001-2011 The Apache Software Foundation +This product includes software developed for Orekit by +CS Systèmes d'Information (http://www.c-s.fr/) +Copyright 2010-2012 CS Systèmes d'Information -This product includes software developed by -The Apache Software Foundation (http://www.apache.org/). +Apache Commons Configuration +Copyright 2001-2024 The Apache Software Foundation -Apache Commons Logging -Copyright 2003-2014 The Apache Software Foundation +Apache Commons Text +Copyright 2014-2024 The Apache Software Foundation -Apache Commons Lang -Copyright 2001-2020 The Apache Software Foundation +Apache Commons IO +Copyright 2002-2024 The Apache Software Foundation -Apache Commons Collections -Copyright 2001-2015 The Apache Software Foundation +Apache Commons Codec +Copyright 2002-2024 The Apache Software Foundation -Apache Log4j SLF4J Binding -Copyright 1999-1969 The Apache Software Foundation +Apache Log4j SLF4J 2.0 Binding +Copyright 1999-2024 The Apache Software Foundation Apache Log4j API -Copyright 1999-1969 The Apache Software Foundation +Copyright 1999-2024 The Apache Software Foundation Apache Log4j 1.x Compatibility API -Copyright 1999-1969 The Apache Software Foundation +Copyright 1999-2024 The Apache Software Foundation ============================================================================= = NOTICE file corresponding to section 4d of the Apache License Version 2.0 = @@ -88,7 +78,7 @@ in some artifacts (usually source distributions); but is always available from the source code management (SCM) system project uses. Apache Avro -Copyright 2009-2020 The Apache Software Foundation +Copyright 2009-2023 The Apache Software Foundation Apache Groovy Copyright 2003-2020 The Apache Software Foundation @@ -102,26 +92,33 @@ Licensed under the Creative Commons Attribution Licence v2.5 http://creativecommons.org/licenses/by/2.5/ Apache HttpClient Mime -Copyright 1999-2020 The Apache Software Foundation +Copyright 1999-2022 The Apache Software Foundation Apache HttpClient -Copyright 1999-2020 The Apache Software Foundation +Copyright 1999-2022 The Apache Software Foundation Apache HttpCore -Copyright 2005-2020 The Apache Software Foundation +Copyright 2005-2022 The Apache Software Foundation Apache Calcite -Copyright 2012-2021 The Apache Software Foundation +Copyright 2012-2024 The Apache Software Foundation This product is based on source code originally developed by DynamoBI Corporation, LucidEra Inc., SQLstream Inc. and others under the auspices of the Eigenbase Foundation and released as the LucidDB project. -The web site includes files generated by Jekyll. - Apache Calcite -- Avatica -Copyright 2012-2021 The Apache Software Foundation +Copyright 2012-2024 The Apache Software Foundation + +Apache HttpClient +Copyright 1999-2021 The Apache Software Foundation + +Apache HttpComponents Core HTTP/2 +Copyright 2005-2021 The Apache Software Foundation + +Apache HttpComponents Core HTTP/1.1 +Copyright 2005-2021 The Apache Software Foundation Jackson is a high-performance, Free/Open Source JSON processing library. It was originally written by Tatu Saloranta (tatu.saloranta@iki.fi), and has @@ -138,83 +135,484 @@ A list of contributors may be found from CREDITS file, which is included in some artifacts (usually source distributions); but is always available from the source code management (SCM) system project uses. -Apache Thrift -Copyright (C) 2006 - 2019, The Apache Software Foundation +# Notice for Jersey +This content is produced and maintained by the Eclipse Jersey project. -Apache Jakarta HttpClient -Copyright 1999-2007 The Apache Software Foundation +* Project home: https://projects.eclipse.org/projects/ee4j.jersey -Apache Commons Compress -Copyright 2002-2021 The Apache Software Foundation +## Trademarks +Eclipse Jersey is a trademark of the Eclipse Foundation. + +## Copyright + +All content is the property of the respective authors or their employers. For +more information regarding authorship of content, please consult the listed +source code repository logs. + +## Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License v. 2.0 which is available at +http://www.eclipse.org/legal/epl-2.0. This Source Code may also be made +available under the following Secondary Licenses when the conditions for such +availability set forth in the Eclipse Public License v. 2.0 are satisfied: GNU +General Public License, version 2 with the GNU Classpath Exception which is +available at https://www.gnu.org/software/classpath/license.html. + +SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + +## Source Code +The project maintains the following source code repositories: + +* https://github.com/eclipse-ee4j/jersey + +## Third-party Content + +Angular JS, v1.6.6 +* License MIT (http://www.opensource.org/licenses/mit-license.php) +* Project: http://angularjs.org +* Coyright: (c) 2010-2017 Google, Inc. + +aopalliance Version 1 +* License: all the source code provided by AOP Alliance is Public Domain. +* Project: http://aopalliance.sourceforge.net +* Copyright: Material in the public domain is not protected by copyright + +Bean Validation API 2.0.2 +* License: Apache License, 2.0 +* Project: http://beanvalidation.org/1.1/ +* Copyright: 2009, Red Hat, Inc. and/or its affiliates, and individual contributors +* by the @authors tag. + +Hibernate Validator CDI, 6.2.5.Final +* License: Apache License, 2.0 +* Project: https://beanvalidation.org/ +* Repackaged in org.glassfish.jersey.server.validation.internal.hibernate + +Bootstrap v3.3.7 +* License: MIT license (https://github.com/twbs/bootstrap/blob/master/LICENSE) +* Project: http://getbootstrap.com +* Copyright: 2011-2016 Twitter, Inc + +Google Guava Version 18.0 +* License: Apache License, 2.0 +* Copyright (C) 2009 The Guava Authors + +javax.inject Version: 1 +* License: Apache License, 2.0 +* Copyright (C) 2009 The JSR-330 Expert Group + +Javassist Version 3.30.2-GA +* License: Apache License, 2.0 +* Project: http://www.javassist.org/ +* Copyright (C) 1999- Shigeru Chiba. All Rights Reserved. + +Jackson JAX-RS Providers Version 2.16.2 +* License: Apache License, 2.0 +* Project: https://github.com/FasterXML/jackson-jaxrs-providers +* Copyright: (c) 2009-2024 FasterXML, LLC. All rights reserved unless otherwise indicated. + +jQuery v1.12.4 +* License: jquery.org/license +* Project: jquery.org +* Copyright: (c) jQuery Foundation + +jQuery Barcode plugin 0.3 +* License: MIT & GPL (http://www.opensource.org/licenses/mit-license.php & http://www.gnu.org/licenses/gpl.html) +* Project: http://www.pasella.it/projects/jQuery/barcode +* Copyright: (c) 2009 Antonello Pasella antonello.pasella@gmail.com + +JSR-166 Extension - JEP 266 +* License: CC0 +* No copyright +* Written by Doug Lea with assistance from members of JCP JSR-166 Expert Group and released to the public domain, as explained at http://creativecommons.org/publicdomain/zero/1.0/ + +KineticJS, v4.7.1 +* License: MIT license (http://www.opensource.org/licenses/mit-license.php) +* Project: http://www.kineticjs.com, https://github.com/ericdrowell/KineticJS +* Copyright: Eric Rowell + +org.objectweb.asm Version 9.6 +* License: Modified BSD (https://asm.ow2.io/license.html) +* Copyright (c) 2000-2011 INRIA, France Telecom. All rights reserved. + +org.osgi.core version 6.0.0 +* License: Apache License, 2.0 +* Copyright (c) OSGi Alliance (2005, 2008). All Rights Reserved. + +org.glassfish.jersey.server.internal.monitoring.core +* License: Apache License, 2.0 +* Copyright (c) 2015-2018 Oracle and/or its affiliates. All rights reserved. +* Copyright 2010-2013 Coda Hale and Yammer, Inc. + +W3.org documents +* License: W3C License +* Copyright: Copyright (c) 1994-2001 World Wide Web Consortium, (Massachusetts Institute of Technology, Institut National de Recherche en Informatique et en Automatique, Keio University). All Rights Reserved. http://www.w3.org/Consortium/Legal/ + +# Notices for the Jakarta RESTful Web Services Project + +This content is produced and maintained by the **Jakarta RESTful Web Services** +project. + +* Project home: https://projects.eclipse.org/projects/ee4j.jaxrs + +## Trademarks + +**Jakarta RESTful Web Services** is a trademark of the Eclipse Foundation. + +## Source Code + +The project maintains the following source code repositories: + +* https://github.com/eclipse-ee4j/jaxrs-api + +This project leverages the following third party content. + +javaee-api (7.0) + +* License: Apache-2.0 AND W3C + +JUnit (4.11) + +* License: Common Public License 1.0 + +Mockito (2.16.0) + +* Project: http://site.mockito.org +* Source: https://github.com/mockito/mockito/releases/tag/v2.16.0 + +## Cryptography + +Content may contain encryption software. The country in which you are currently +may have restrictions on the import, possession, and use, and/or re-export to +another country, of encryption software. BEFORE using any encryption software, +please check the country's laws, regulations and policies concerning the import, +possession, or use, and re-export of encryption software, to see if this is +permitted. + +# Notices for Jakarta Annotations + +This content is produced and maintained by the Jakarta Annotations project. + + * Project home: https://projects.eclipse.org/projects/ee4j.ca + +Jakarta Annotations is a trademark of the Eclipse Foundation. + + * https://github.com/eclipse-ee4j/common-annotations-api + +# Notices for Eclipse GlassFish + +This content is produced and maintained by the Eclipse GlassFish project. + +* Project home: https://projects.eclipse.org/projects/ee4j.glassfish + +Eclipse GlassFish, and GlassFish are trademarks of the Eclipse Foundation. + +* https://github.com/eclipse-ee4j/glassfish-ha-api +* https://github.com/eclipse-ee4j/glassfish-logging-annotation-processor +* https://github.com/eclipse-ee4j/glassfish-shoal +* https://github.com/eclipse-ee4j/glassfish-cdi-porting-tck +* https://github.com/eclipse-ee4j/glassfish-jsftemplating +* https://github.com/eclipse-ee4j/glassfish-hk2-extra +* https://github.com/eclipse-ee4j/glassfish-hk2 +* https://github.com/eclipse-ee4j/glassfish-fighterfish ---- +None -The files in the package org.apache.commons.compress.archivers.sevenz -were derived from the LZMA SDK, version 9.20 (C/ and CPP/7zip/), -which has been placed in the public domain: +[//]: # " Copyright (c) 2018, 2021 Oracle and/or its affiliates. All rights reserved. " +[//]: # " " +[//]: # " This program and the accompanying materials are made available under the " +[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at " +[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. " +[//]: # " " +[//]: # " SPDX-License-Identifier: BSD-3-Clause " -"LZMA SDK is placed in the public domain." (http://www.7-zip.org/sdk.html) +# Notices for Eclipse Metro -The test file lbzip2_32767.bz2 has been copied from libbzip2's source -repository: +This content is produced and maintained by the Eclipse Metro project. -This program, "bzip2", the associated library "libbzip2", and all -documentation, are copyright (C) 1996-2019 Julian R Seward. All -rights reserved. +* Project home: https://projects.eclipse.org/projects/ee4j.metro -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: +Eclipse Metro is a trademark of the Eclipse Foundation. -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. +This program and the accompanying materials are made available under the terms +of the Eclipse Distribution License v. 1.0 which is available at +http://www.eclipse.org/org/documents/edl-v10.php. -2. The origin of this software must not be misrepresented; you must - not claim that you wrote the original software. If you use this - software in a product, an acknowledgment in the product - documentation would be appreciated but is not required. +SPDX-License-Identifier: BSD-3-Clause -3. Altered source versions must be plainly marked as such, and must - not be misrepresented as being the original software. +* https://github.com/eclipse-ee4j/metro-xmlstreambuffer +* https://github.com/eclipse-ee4j/metro-policy +* https://github.com/eclipse-ee4j/metro-wsit +* https://github.com/eclipse-ee4j/metro-mimepull +* https://github.com/eclipse-ee4j/metro-ws-test-harness +* https://github.com/eclipse-ee4j/metro-package-rename-task +* https://github.com/eclipse-ee4j/metro-jax-ws +* https://github.com/eclipse-ee4j/metro-saaj +* https://github.com/eclipse-ee4j/metro-jwsdp-samples +* https://github.com/eclipse-ee4j/jax-rpc-ri -4. The name of the author may not be used to endorse or promote - products derived from this software without specific prior written - permission. +addressing.xml Version: 2004/10 (n/a) -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS -OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* License: W3C +* Project: https://www.w3.org/Submission/ws-addressing/ +* Source: http://schemas.xmlsoap.org/ws/2004/08/addressing/ -Julian Seward, jseward@acm.org +ant-launcher (1.10.2) + +* License: Apache-2.0 AND SAX-PD AND W3C +* Project: https://ant.apache.org/ +* Source: + http://central.maven.org/maven2/org/apache/ant/ant-launcher/1.10.2/ant-launcher-1.10.2-sources.jar + +Apache Ant (1.6) + +* License: Apache-1.1 +* Project: https://ant.apache.org/ +* Source: https://repo1.maven.org/maven2/ant/ant/1.6/ant-1.6-sources.jar + +Apache Ant (1.10.2) + +* License: Apache-2.0 AND W3C AND LicenseRef-Public-Domain + +commons-logging (1.1.2) + +* License: Apache-2.0 +* Project: https://commons.apache.org/proper/commons-logging/ +* Source: + http://central.maven.org/maven2/commons-logging/commons-logging/1.1.2/commons-logging-1.1.2-sources.jar + +JUnit (4.12) + +* License: Eclipse Public License + +maven-core (3.5.2) + +* License: Apache-2.0 + +maven-plugin-annotations (3.5.1) + +* License: Apache-2.0 +* Project: + https://maven.apache.org/plugin-tools/maven-plugin-annotations/project-info.html +* Source: + https://github.com/apache/maven-plugin-tools/tree/maven-plugin-tools-3.5.1/maven-plugin-annotations + +maven-plugin-api (3.5.2) + +* License: Apache-2.0 +* Project: https://maven.apache.org/ +* Source: https://github.com/apache/maven/tree/master/maven-plugin-api + +maven-resolver-api (1.1.1) + +maven-resolver-util (1.1.1) + +maven-settings (3.5.2) + +mex.xsd Version: 2004/09 (n/a) + +* License: Oasis Style +* Project: https://www.w3.org/Submission/WS-MetadataExchange/#appendix-II +* Source: http://schemas.xmlsoap.org/ws/2004/09/mex/MetadataExchange.xsd + +plexus-utils (3.1.0) + +* License: Apache- 2.0 or Apache- 1.1 or BSD or Public Domain or Indiana + University Extreme! Lab Software License V1.1.1 (Apache 1.1 style) + +relaxng-datatype (1.0) + +* License: New BSD license + +stax2-api (4.1) + +* License: Pending +* Project: https://github.com/FasterXML/stax2-api +* Source: + http://central.maven.org/maven2/org/codehaus/woodstox/stax2-api/4.1/stax2-api-4.1-sources.jar + +testng (6.14.2) + +* License: Apache-2.0 AND MIT +* Project: https://testng.org/doc/index.html +* Source: https://github.com/cbeust/testng + +woodstox-core-asl (4.4.1) + +woodstox-core-asl (5.1.0) + +* License: Pending +* Project: https://github.com/FasterXML/woodstox +* Source: https://github.com/FasterXML/woodstox + +ws-addr.wsd (1.0) + +* License: W3C +* Project: https://www.w3.org/2005/08/addressing/ +* Source: https://www.w3.org/2006/03/addressing/ws-addr.xsd + +wsat.xsd Version: 2004/10 (n/a) + +* License: Oasis Style +* Project: http://schemas.xmlsoap.org/ws/2004/10/wsat/ +* Source: http://schemas.xmlsoap.org/ws/2004/10/wsat/wsat.xsd + +wscoor.xsd (1.0) + +* License: OASIS Style + +wscoor.xsd (1.1) + +* License: Oasis (Custom) +* Project: http://docs.oasis-open.org/ws-tx/wscoor/2006/06 +* Source: + http://docs.oasis-open.org/ws-tx/wscoor/2006/06/wstx-wscoor-1.1-schema-200701.xsd + +wsrm Version: 2005/02 (n/a) + +* License: Oasis (Custom) +* Project: http://schemas.xmlsoap.org/ws/2005/02/rm/ +* Source: + http://schemas.xmlsoap.org/ws/2005/02/rm/wsrm.xsd;%20http://schemas.xmlsoap.org/ws/2005/02/rm/wsrm-policy.xsd + +wsrm.xsd (1.2) + +* License: Oasis + +wstx-wsat.xsd (1.1) + +* License: Oasis (Custom) + +xmlsec (1.5.8) + +* License: Apache-2.0 +* Project: http://santuario.apache.org/ +* Source: + https://repo1.maven.org/maven2/org/apache/santuario/xmlsec/1.5.8/xmlsec-1.5.8-sources.jar + +Jackson core and extension components may licensed under different licenses. +To find the details that apply to this artifact see the accompanying LICENSE file. +For more information, including possible other licensing options, contact +FasterXML.com (http://fasterxml.com). + +[//]: # " Copyright (c) 2018, 2019 Oracle and/or its affiliates. All rights reserved. " +[//]: # " " +[//]: # " This program and the accompanying materials are made available under the " +[//]: # " terms of the Eclipse Distribution License v. 1.0, which is available at " +[//]: # " http://www.eclipse.org/org/documents/edl-v10.php. " +[//]: # " " +[//]: # " SPDX-License-Identifier: BSD-3-Clause " + +# Notices for Jakarta XML Binding + +This content is produced and maintained by the Jakarta XML Binding +project. + +* Project home: https://projects.eclipse.org/projects/ee4j.jaxb + +Jakarta XML Binding is a trademark of the Eclipse Foundation. + +* https://github.com/eclipse-ee4j/jaxb-api +* https://github.com/eclipse-ee4j/jaxb-tck + +Apache River (3.0.0) + +* License: Apache-2.0 AND BSD-3-Clause + +ASM 7 (n/a) + +* License: BSD-3-Clause +* Project: https://asm.ow2.io/ +* Source: + https://repository.ow2.org/nexus/#nexus-search;gav~org.ow2.asm~asm-commons~~~~kw,versionexpand + +JTHarness (5.0) + +* License: (GPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0) +* Project: https://wiki.openjdk.java.net/display/CodeTools/JT+Harness +* Source: http://hg.openjdk.java.net/code-tools/jtharness/ + +normalize.css (3.0.2) + +* License: MIT + +SigTest (n/a) + +* License: GPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + +Apache Thrift +Copyright (C) 2006 - 2019, The Apache Software Foundation + +Apache Commons Compress +Copyright 2002-2024 The Apache Software Foundation Apache Helix :: Core -Copyright 2022 The Apache Software Foundation +Copyright 2023 Apache Software Foundation Apache Helix :: Helix Common -Copyright 2022 The Apache Software Foundation +Copyright 2023 Apache Software Foundation Apache Helix :: Metrics Common -Copyright 2022 The Apache Software Foundation +Copyright 2023 Apache Software Foundation Apache Helix :: ZooKeeper API -Copyright 2022 The Apache Software Foundation +Copyright 2023 Apache Software Foundation Apache Helix :: Metadata Store Directory Common -Copyright 2022 The Apache Software Foundation +Copyright 2023 Apache Software Foundation + +Apache Commons CLI +Copyright 2002-2024 The Apache Software Foundation + +Apache Commons Math +Copyright 2001-2010 The Apache Software Foundation + +This product includes software developed by +The Apache Software Foundation (http://www.apache.org/). + +=============================================================================== +The LinearConstraint, LinearObjectiveFunction, LinearOptimizer, +RelationShip, SimplexSolver and SimplexTableau classes in package +org.apache.commons.math.optimization.linear include software developed by +Benjamin McCann (http://www.benmccann.com) and distributed with +the following copyright: Copyright 2009 Google Inc. +=============================================================================== + +This product includes software developed by the +University of Chicago, as Operator of Argonne National +Laboratory. +The LevenbergMarquardtOptimizer class in package +org.apache.commons.math.optimization.general includes software +translated from the lmder, lmpar and qrsolv Fortran routines +from the Minpack package +Minpack Copyright Notice (1999) University of Chicago. All rights reserved +=============================================================================== + +The GraggBulirschStoerIntegrator class in package +org.apache.commons.math.ode.nonstiff includes software translated +from the odex Fortran routine developed by E. Hairer and G. Wanner. +Original source copyright: +Copyright (c) 2004, Ernst Hairer +=============================================================================== -Apache ZooKeeper - Server -Copyright 2008-2020 The Apache Software Foundation +The EigenDecompositionImpl class in package +org.apache.commons.math.linear includes software translated +from some LAPACK Fortran routines. Original source copyright: +Copyright (c) 1992-2008 The University of Tennessee. All rights reserved. +=============================================================================== -Apache ZooKeeper - Jute -Copyright 2008-2020 The Apache Software Foundation +The MersenneTwister class in package org.apache.commons.math.random +includes software translated from the 2002-01-26 version of +the Mersenne-Twister generator written in C by Makoto Matsumoto and Takuji +Nishimura. Original source copyright: +Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, +All rights reserved +=============================================================================== + +The complete text of licenses and disclaimers associated with the the original +sources enumerated above at the time of code translation are in the LICENSE.txt +file. The Netty Project ================= @@ -268,66 +666,38 @@ This product contains code from boringssl. * https://boringssl.googlesource.com/boringssl/ Apache Yetus - Audience Annotations -Copyright 2015-2020 The Apache Software Foundation +Copyright 2015-2023 The Apache Software Foundation -Apache Commons Math -Copyright 2001-2010 The Apache Software Foundation +# Notices for Jakarta Activation -=============================================================================== -The LinearConstraint, LinearObjectiveFunction, LinearOptimizer, -RelationShip, SimplexSolver and SimplexTableau classes in package -org.apache.commons.math.optimization.linear include software developed by -Benjamin McCann (http://www.benmccann.com) and distributed with -the following copyright: Copyright 2009 Google Inc. -=============================================================================== +This content is produced and maintained by Jakarta Activation project. -This product includes software developed by the -University of Chicago, as Operator of Argonne National -Laboratory. -The LevenbergMarquardtOptimizer class in package -org.apache.commons.math.optimization.general includes software -translated from the lmder, lmpar and qrsolv Fortran routines -from the Minpack package -Minpack Copyright Notice (1999) University of Chicago. All rights reserved -=============================================================================== +* Project home: https://projects.eclipse.org/projects/ee4j.jaf -The GraggBulirschStoerIntegrator class in package -org.apache.commons.math.ode.nonstiff includes software translated -from the odex Fortran routine developed by E. Hairer and G. Wanner. -Original source copyright: -Copyright (c) 2004, Ernst Hairer -=============================================================================== +This program and the accompanying materials are made available under the terms +of the Eclipse Distribution License v. 1.0, +which is available at http://www.eclipse.org/org/documents/edl-v10.php. -The EigenDecompositionImpl class in package -org.apache.commons.math.linear includes software translated -from some LAPACK Fortran routines. Original source copyright: -Copyright (c) 1992-2008 The University of Tennessee. All rights reserved. -=============================================================================== +* https://github.com/eclipse-ee4j/jaf -The MersenneTwister class in package org.apache.commons.math.random -includes software translated from the 2002-01-26 version of -the Mersenne-Twister generator written in C by Makoto Matsumoto and Takuji -Nishimura. Original source copyright: -Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, -All rights reserved -=============================================================================== +datasketches-java +Copyright 2015-2024 The Apache Software Foundation -The complete text of licenses and disclaimers associated with the the original -sources enumerated above at the time of code translation are in the LICENSE.txt -file. +Apache DataSketches Memory +Copyright 2022 - The Apache Software Foundation -datasketches-java -Copyright 2015-2020 The Apache Software Foundation +Copyright 2015-2018 Yahoo Inc. +Copyright 2019-2020 Verizon Media +Copyright 2021 Yahoo Inc. -datasketches-memory -Copyright 2015-2019 The Apache Software Foundation +Prior to moving to ASF, the software for this project was developed at +Yahoo Inc. (https://developer.yahoo.com). Apache Lucene -Copyright 2001-2019 The Apache Software Foundation +Copyright 2001-2022 The Apache Software Foundation Includes software from other Apache Software Foundation projects, including, but not limited to: - - Apache Ant - Apache Jakarta Regexp - Apache Commons - Apache Xerces @@ -351,12 +721,14 @@ http://bitbucket.org/jpbarrette/moman/overview/ The class org.apache.lucene.util.WeakIdentityMap was derived from the Apache CXF project and is Apache License 2.0. +The class org.apache.lucene.util.compress.LZ4 is a Java rewrite of the LZ4 +compression library (https://github.com/lz4/lz4/tree/dev/lib) that is licensed +under the 2-clause BSD license. +(https://opensource.org/licenses/bsd-license.php) + The Google Code Prettify is Apache License 2.0. See http://code.google.com/p/google-code-prettify/ -JUnit (junit-4.10) is licensed under the Common Public License v. 1.0 -See http://junit.sourceforge.net/cpl-v10.html - This product includes code (JaspellTernarySearchTrie) from Java Spelling Checkin g Package (jaspell): http://jaspell.sourceforge.net/ License: The BSD License (http://www.opensource.org/licenses/bsd-license.php) @@ -368,7 +740,7 @@ The snowball stopword lists in analysis/common/src/resources/org/apache/lucene/analysis/snowball were developed by Martin Porter and Richard Boulton. The full snowball package is available from - http://snowball.tartarus.org/ + https://snowballstem.org/ The KStem stemmer in analysis/common/src/org/apache/lucene/analysis/en @@ -407,7 +779,7 @@ and Edmond Nolan. The Polish analyzer (stempel) comes with a default stopword list that is BSD-licensed created by the Carrot2 project. The file resides in stempel/src/resources/org/apache/lucene/analysis/pl/stopwords.txt. -See http://project.carrot2.org/license.html. +See https://github.com/carrot2/carrot2. The SmartChineseAnalyzer source code (smartcn) was provided by Xiaoping Gao and copyright 2009 by www.imdict.net. @@ -417,14 +789,9 @@ is derived from Unicode data such as the Unicode Character Database. See http://unicode.org/copyright.html for more details. The Morfologik analyzer (morfologik) includes BSD-licensed software -developed by Dawid Weiss and Marcin Miłkowski (http://morfologik.blogspot.com/). - -Morfologik uses data from Polish ispell/myspell dictionary -(http://www.sjp.pl/slownik/en/) licenced on the terms of (inter alia) -LGPL and Creative Commons ShareAlike. - -Morfologic includes data from BSD-licensed dictionary of Polish (SGJP) -(http://sgjp.pl/morfeusz/) +developed by Dawid Weiss and Marcin Miłkowski +(https://github.com/morfologik/morfologik-stemming) and uses +data from the BSD-licensed dictionary of Polish (SGJP, http://sgjp.pl/morfeusz/). Servlet-api.jar and javax.servlet-*.jar are under the CDDL license, the original source code for this can be found at http://www.eclipse.org/jetty/downloads.php @@ -520,128 +887,117 @@ Nori Korean Morphological Analyzer - Apache Lucene Integration https://bitbucket.org/eunjeon/mecab-ko-dic/downloads/mecab-ko-dic-2.0.3-20170922.tar.gz -Apache Commons CLI -Copyright 2001-2009 The Apache Software Foundation +Apache Commons CSV +Copyright 2005-2024 The Apache Software Foundation -Jackson core and extension components may licensed under different licenses. -To find the details that apply to this artifact see the accompanying LICENSE file. -For more information, including possible other licensing options, contact -FasterXML.com (http://fasterxml.com). +Apache Hadoop Third-party Libs +Copyright 2020 and onwards The Apache Software Foundation. -Apache Commons CSV -Copyright 2005-2014 The Apache Software Foundation +Hive Storage API +Copyright 2020 The Apache Software Foundation -src/main/resources/contract.txt -This file was downloaded from http://www.ferc.gov/docs-filing/eqr/soft-tools/sample-csv/contract.txt and contains neither copyright notice nor license. +ORC Core +Copyright 2013-2024 The Apache Software Foundation -src/main/resources/transaction.txt -This file was downloaded from http://www.ferc.gov/docs-filing/eqr/soft-tools/sample-csv/transaction.txt and contains neither copyright notice nor license. +ORC Shims +Copyright 2013-2024 The Apache Software Foundation -src/test/resources/CSVFileParser/bom.csv -src/test/resources/CSVFileParser/test.csv -src/test/resources/CSVFileParser/test_default.txt -src/test/resources/CSVFileParser/test_default_comment.txt -src/test/resources/CSVFileParser/test_rfc4180.txt -src/test/resources/CSVFileParser/test_rfc4180_trim.txt -src/test/resources/CSVFileParser/testCSV85.csv -src/test/resources/CSVFileParser/testCSV85_default.txt -src/test/resources/CSVFileParser/testCSV85_ignoreEmpty.txt -These files are used as test data and test result specifications. +Apache Parquet MR (Incubating) +Copyright 2014-2015 The Apache Software Foundation -Apache Commons Math -Copyright 2001-2013 The Apache Software Foundation +-------------------------------------------------------------------------------- -The inverse error function implementation in the Erf class is based on CUDA -code developed by Mike Giles, Oxford-Man Institute of Quantitative Finance, -and published in GPU Computing Gems, volume 2, 2010. -=============================================================================== +This product includes code from Apache Avro, which includes the following in +its NOTICE file: -The BracketFinder (package org.apache.commons.math3.optimization.univariate) -and PowellOptimizer (package org.apache.commons.math3.optimization.general) -classes are based on the Python code in module "optimize.py" (version 0.5) -developed by Travis E. Oliphant for the SciPy library (http://www.scipy.org/) -Copyright © 2003-2009 SciPy Developers. -=============================================================================== + Apache Avro + Copyright 2010-2015 The Apache Software Foundation -The LinearConstraint, LinearObjectiveFunction, LinearOptimizer, -RelationShip, SimplexSolver and SimplexTableau classes in package -org.apache.commons.math3.optimization.linear include software developed by -Benjamin McCann (http://www.benmccann.com) and distributed with -the following copyright: Copyright 2009 Google Inc. -=============================================================================== + This product includes software developed at + The Apache Software Foundation (http://www.apache.org/). -This product includes software developed by the -University of Chicago, as Operator of Argonne National -Laboratory. -The LevenbergMarquardtOptimizer class in package -org.apache.commons.math3.optimization.general includes software -translated from the lmder, lmpar and qrsolv Fortran routines -from the Minpack package -Minpack Copyright Notice (1999) University of Chicago. All rights reserved -=============================================================================== +Copyright 2007-, Tatu Saloranta (tatu.saloranta@iki.fi) -The GraggBulirschStoerIntegrator class in package -org.apache.commons.math3.ode.nonstiff includes software translated -from the odex Fortran routine developed by E. Hairer and G. Wanner. -Original source copyright: -Copyright (c) 2004, Ernst Hairer -=============================================================================== +## FastDoubleParser -The EigenDecompositionImpl class in package -org.apache.commons.math3.linear includes software translated -from some LAPACK Fortran routines. Original source copyright: -Copyright (c) 1992-2008 The University of Tennessee. All rights reserved. -=============================================================================== +jackson-core bundles a shaded copy of FastDoubleParser . +That code is available under an MIT license +under the following copyright. -The MersenneTwister class in package org.apache.commons.math3.random -includes software translated from the 2002-01-26 version of -the Mersenne-Twister generator written in C by Makoto Matsumoto and Takuji -Nishimura. Original source copyright: -Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura, -All rights reserved -=============================================================================== +Copyright © 2023 Werner Randelshofer, Switzerland. MIT License. -The LocalizedFormatsTest class in the unit tests is an adapted version of -the OrekitMessagesTest class from the orekit library distributed under the -terms of the Apache 2 licence. Original source copyright: -Copyright 2010 CS Systèmes d'Information -=============================================================================== +See FastDoubleParser-NOTICE for details of other source code included in FastDoubleParser +and the licenses and copyrights that apply to that code. -The HermiteInterpolator class and its corresponding test have been imported from -the orekit library distributed under the terms of the Apache 2 licence. Original -source copyright: -Copyright 2010-2012 CS Systèmes d'Information -=============================================================================== +Apache Commons Pool +Copyright 2001-2012 The Apache Software Foundation -The creation of the package "o.a.c.m.analysis.integration.gauss" was inspired -by an original code donated by Sébastien Brisard. -=============================================================================== +AWS SDK for Java 2.0 +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -ORC Core -Copyright 2013-2020 The Apache Software Foundation +This product includes software developed by +Amazon Technologies, Inc (http://www.amazon.com/). -ORC Shims -Copyright 2013-2020 The Apache Software Foundation +********************** +THIRD PARTY COMPONENTS +********************** +This software includes third party software subject to the following copyrights: +- XML parsing and utility functions from JetS3t - Copyright 2006-2009 James Murty. +- PKCS#1 PEM encoded private key parsing and utility functions from oauth.googlecode.com - Copyright 1998-2010 AOL Inc. +- Apache Commons Lang - https://github.com/apache/commons-lang +- Netty Reactive Streams - https://github.com/playframework/netty-reactive-streams +- Jackson-core - https://github.com/FasterXML/jackson-core +- Jackson-dataformat-cbor - https://github.com/FasterXML/jackson-dataformats-binary -Hive Storage API -Copyright 2019 The Apache Software Foundation +The licenses for these third party components are included in LICENSE.txt -Apache Parquet MR (Incubating) -Copyright 2014-2015 The Apache Software Foundation +- For Apache Commons Lang see also this required NOTICE: + Apache Commons Lang + Copyright 2001-2020 The Apache Software Foundation --------------------------------------------------------------------------------- + This product includes software developed at + The Apache Software Foundation (https://www.apache.org/). -This product includes code from Apache Avro, which includes the following in -its NOTICE file: +Pulsar Client Java +Copyright 2017-2024 Apache Software Foundation - Apache Avro - Copyright 2010-2015 The Apache Software Foundation +Apache Commons Lang +Copyright 2001-2020 The Apache Software Foundation - This product includes software developed at - The Apache Software Foundation (http://www.apache.org/). +Pulsar Client :: API +Copyright 2017-2024 Apache Software Foundation -Apache Commons Pool -Copyright 2001-2012 The Apache Software Foundation +Pulsar Client Admin :: API +Copyright 2017-2024 Apache Software Foundation + +Apache Pulsar :: Bouncy Castle :: BC +Copyright 2017-2024 Apache Software Foundation + +# Notices for Eclipse Tyrus + +This content is produced and maintained by the Eclipse Tyrus project. + +* Project home: https://projects.eclipse.org/projects/ee4j.tyrus + +Eclipse Tyrus is a trademark of the Eclipse Foundation. + +* https://github.com/eclipse-ee4j/tyrus + +## Third-party Content +This project leverages the following third party content: + +jakarta.enterprise.cdi-api Version 4.0.1 +* License: Apache License, 2.0 +* Copyright 2010, Red Hat, Inc., and individual contributors + +jakarta.inject Version: 2.0.1 +* License: Apache License, 2.0 +* Copyright (C) 2009 The JSR-330 Expert Group + +jline Version: 2.14.5 +* License: BSD-3-Clause +* Project: https://github.com/jline/jline2 +* Source: https://github.com/jline/jline2 Apache Log4j Core Copyright 1999-2012 Apache Software Foundation @@ -650,5 +1006,5 @@ ResolverUtil.java Copyright 2005-2006 Tim Fennell Spark Project Launcher -Copyright 2022 The Apache Software Foundation +Copyright 2024 Apache Software Foundation diff --git a/README.md b/README.md index 6b115012aec1..f39ad1a3b18a 100644 --- a/README.md +++ b/README.md @@ -87,8 +87,6 @@ SELECT sum(clicks), sum(impressions) FROM AdAnalyticsTable daysSinceEpoch TOP 100 ``` -Pinot is not a replacement for database i.e it cannot be used as source of truth store, cannot mutate data. While Pinot [supports text search](https://docs.pinot.apache.org/basics/features/text-search-support), it's not a replacement for a search engine. Also, Pinot queries cannot span across multiple tables by default. You can use the [Trino-Pinot Connector](https://trino.io/docs/current/connector/pinot.html) or [Presto-Pinot Connector](https://prestodb.io/docs/current/connector/pinot.html) to achieve table joins and other features. - ## Building Pinot More detailed instructions can be found at [Quick Demo](https://docs.pinot.apache.org/basics/getting-started/quick-start) section in the documentation. ``` @@ -104,6 +102,8 @@ $ cd build/ $ bin/quick-start-batch.sh ``` +For UI development setup refer this [doc](https://github.com/apache/pinot/blob/master/pinot-controller/src/main/resources/Readme.md). + ## Deploying Pinot to Kubernetes Please refer to [Running Pinot on Kubernetes](https://docs.pinot.apache.org/basics/getting-started/kubernetes-quickstart) in our project documentation. Pinot also provides Kubernetes integrations with the interactive query engine, [Trino](https://docs.pinot.apache.org/integrations/trino) [Presto](https://docs.pinot.apache.org/integrations/presto), and the data visualization tool, [Apache Superset](kubernetes/helm/superset.yaml). diff --git a/compatibility-verifier/checkoutAndBuild.sh b/compatibility-verifier/checkoutAndBuild.sh index 702e01b1f4aa..e17a270839c7 100755 --- a/compatibility-verifier/checkoutAndBuild.sh +++ b/compatibility-verifier/checkoutAndBuild.sh @@ -22,6 +22,8 @@ cmdName=`basename $0` cmdDir=`dirname $0` source ${cmdDir}/utils.inc MVN_CACHE_DIR="mvn-compat-cache" +df -h +du -hd2 /home/runner # get usage of the script function usage() { @@ -64,7 +66,7 @@ function checkOut() { # compatibility tester # It uses a different version for each build, since we build trees in parallel. # Building the same version on all trees in parallel causes unstable builds. -# Using indpendent buildIds will cause maven cache to fill up, so we use a +# Using independent buildIds will cause maven cache to fill up, so we use a # dedicated maven cache for these builds, and remove the cache after build is # completed. # If buildId is less than 0, then the mvn version is not changed in pom files. @@ -72,10 +74,13 @@ function build() { local outFile=$1 local buildTests=$2 local buildId=$3 + local buildCompatibilityVerifier=$4 local repoOption="" - local versionOption="-Djdk.version=8" + local versionOption="-Djdk.version=11" + local maxRetry=5 mkdir -p ${MVN_CACHE_DIR} + mkdir -p ${mvnCache} if [ ${buildId} -gt 0 ]; then # Build it in a different env under different version so that maven cache does @@ -85,15 +90,40 @@ function build() { mvn versions:commit -q -B 1>${outFile} 2>&1 repoOption="-Dmaven.repo.local=${mvnCache}/${buildId}" fi - - mvn install package -DskipTests -Pbin-dist ${versionOption} ${repoOption} ${PINOT_MAVEN_OPTS} 1>${outFile} 2>&1 - if [ $? -ne 0 ]; then exit 1; fi - mvn -pl pinot-tools package -DskipTests ${versionOption} ${repoOption} ${PINOT_MAVEN_OPTS} 1>>${outFile} 2>&1 - if [ $? -ne 0 ]; then exit 1; fi + buildComponents=":pinot-tools" + if [ $buildCompatibilityVerifier -gt 0 ]; then + buildComponents=":pinot-tools,:pinot-compatibility-verifier" + fi + for i in $(seq 1 $maxRetry); do + mvn clean package -am -pl ${buildComponents} -DskipTests -T1C ${versionOption} ${repoOption} ${PINOT_MAVEN_OPTS} 1>${outFile} 2>&1 + if [ $? -eq 0 ]; then break; fi + if [ $i -eq $maxRetry ]; then exit 1; fi + echo "" + echo "Build failed, see last 1000 lines of output below." + tail -1000 ${outFile} + echo "Retrying after 30 seconds..." + sleep 30 + done if [ $buildTests -eq 1 ]; then - mvn -pl pinot-integration-tests package -DskipTests ${versionOption} ${repoOption} ${PINOT_MAVEN_OPTS} 1>>${outFile} 2>&1 - if [ $? -ne 0 ]; then exit 1; fi + for i in $(seq 1 $maxRetry); do + mvn package -am -pl :pinot-integration-tests -DskipTests -T1C ${versionOption} ${repoOption} ${PINOT_MAVEN_OPTS} 1>>${outFile} 2>&1 + if [ $? -eq 0 ]; then break; fi + if [ $i -eq $maxRetry ]; then exit 1; fi + echo "" + echo "Build failed, see last 500 lines of output below." + tail -500 ${outFile} + echo "Retrying after 30 seconds..." + sleep 30 + done fi + # DELETE the mvn cache + du -hd1 ${mvnCache} + df -h + rm -rf ${mvnCache} + du -hd2 /home/runner + df -h + rm -rf /home/runner/.m2/repository + df -h } # @@ -190,29 +220,46 @@ fi echo "Checking out old version at commit \"${olderCommit}\"" checkOut "$olderCommit" "$oldTargetDir" -# Start builds in parallel. +exitStatus=0 + +# Start builds in sequential. # First build the current tree. We need it so that we can -# run the compatibiity tester. +# run the compatibility tester. echo Starting build for compat checker at ${cmdDir}, buildId none. -(cd ${cmdDir}/..; build ${curBuildOutFile} 1 -1) & +(cd ${cmdDir}/..; build ${curBuildOutFile} 1 -1 1) & curBuildPid=$! +echo Awaiting build complete for compat checker +while true ; do + printf "." + ps -p ${curBuildPid} 1>/dev/null 2>&1 + if [ $? -ne 0 ]; then + printf "\n" + wait ${curBuildPid} + curBuildStatus=$? + break + fi + sleep 5 +done + +if [ ${curBuildStatus} -eq 0 ]; then + echo Compat checker build completed successfully +else + echo Compat checker build failed. See ${curBuildOutFile} + echo ======== Build output ======== + cat ${curBuildOutFile} + echo ==== End Build output ======== + exitStatus=1 + /bin/rm -r ${mvnCache} + exit ${exitStatus} +fi + # The old commit has been cloned in oldTargetDir, build it. buildId=$(date +%s) echo Starting build for old version at ${oldTargetDir} buildId ${buildId} -(cd ${oldTargetDir}; build ${oldBuildOutFile} 0 ${buildId}) & +(cd ${oldTargetDir}; build ${oldBuildOutFile} 0 ${buildId} 0) & oldBuildPid=$! -# In case the user specified the current tree as newer commit, then -# We don't need to build newer commit tree (we have already built the -# current tree above and linked newTargetDir). Otherwise, build the newTargetDir -if [ ${buildNewTarget} -eq 1 ]; then - buildId=$((buildId+1)) - echo Starting build for new version at ${newTargetDir} buildId ${buildId} - (cd ${newTargetDir}; build ${newBuildOutFile} 0 ${buildId}) & - newBuildPid=$! -fi - # We may have potentially started three builds above (at least two). # Wait for each of them to complete. echo Awaiting build complete for old commit @@ -228,18 +275,27 @@ while true ; do sleep 5 done -echo Awaiting build complete for compat checker -while true ; do - printf "." - ps -p ${curBuildPid} 1>/dev/null 2>&1 - if [ $? -ne 0 ]; then - printf "\n" - wait ${curBuildPid} - curBuildStatus=$? - break - fi - sleep 5 -done +if [ ${oldBuildStatus} -eq 0 ]; then + echo Old version build completed successfully +else + echo Old version build failed. See ${oldBuildOutFile} + echo ======== Build output ======== + cat ${oldBuildOutFile} + echo ==== End Build output ======== + exitStatus=1 + /bin/rm -r ${mvnCache} + exit ${exitStatus} +fi + +# In case the user specified the current tree as newer commit, then +# We don't need to build newer commit tree (we have already built the +# current tree above and linked newTargetDir). Otherwise, build the newTargetDir +if [ ${buildNewTarget} -eq 1 ]; then + buildId=$((buildId+1)) + echo Starting build for new version at ${newTargetDir} buildId ${buildId} + (cd ${newTargetDir}; build ${newBuildOutFile} 0 ${buildId} 0) & + newBuildPid=$! +fi if [ ${buildNewTarget} -eq 1 ]; then echo Awaiting build complete for new commit @@ -256,28 +312,6 @@ if [ ${buildNewTarget} -eq 1 ]; then done fi -exitStatus=0 - -if [ ${oldBuildStatus} -eq 0 ]; then - echo Old version build completed successfully -else - echo Old version build failed. See ${oldBuildOutFile} - echo ======== Build output ======== - cat ${oldBuildOutFile} - echo ==== End Build output ======== - exitStatus=1 -fi - -if [ ${curBuildStatus} -eq 0 ]; then - echo Compat checker build completed successfully -else - echo Compat checker build failed. See ${curBuildOutFile} - echo ======== Build output ======== - cat ${curBuildOutFile} - echo ==== End Build output ======== - exitStatus=1 -fi - if [ ${buildNewTarget} -eq 1 ]; then if [ ${newBuildStatus} -eq 0 ]; then echo New version build completed successfully @@ -287,6 +321,7 @@ if [ ${buildNewTarget} -eq 1 ]; then cat ${newBuildOutFile} echo ==== End Build output ======== exitStatus=1 + exit ${exitStatus} fi fi diff --git a/compatibility-verifier/compCheck.sh b/compatibility-verifier/compCheck.sh index 0803a06b9c77..c90f8d75302f 100755 --- a/compatibility-verifier/compCheck.sh +++ b/compatibility-verifier/compCheck.sh @@ -46,20 +46,32 @@ cmdName=`basename $0` source `dirname $0`/utils.inc function cleanupControllerDirs() { - local dirName=$(grep -F controller.data.dir ${CONTROLLER_CONF} | awk '{print $3}') - if [ ! -z "$dirName" ]; then - ${RM} -rf ${dirName} + local dirName=$(grep -F controller.data.dir "${CONTROLLER_CONF}" | awk '{print $3}') + if [ -n "$dirName" ]; then + ${RM} -rf "${dirName}" fi } function cleanupServerDirs() { - local dirName=$(grep -F pinot.server.instance.dataDir ${SERVER_CONF} | awk '{print $3}') - if [ ! -z "$dirName" ]; then - ${RM} -rf ${dirName} + local dirName=$(grep -F pinot.server.instance.dataDir "${SERVER_CONF}" | awk '{print $3}') + if [ -n "$dirName" ]; then + ${RM} -rf "${dirName}" fi - dirName=$(grep -F pinot.server.instance.segmentTarDir ${SERVER_CONF} | awk '{print $3}') - if [ ! -z "$dirName" ]; then - ${RM} -rf ${dirName} + dirName=$(grep -F pinot.server.instance.segmentTarDir "${SERVER_CONF}" | awk '{print $3}') + if [ -n "$dirName" ]; then + ${RM} -rf "${dirName}" + fi + + # Cleanup directories for server 2 if a second server is configured + if [ -f "${SERVER_CONF_2}" ]; then + local dirName=$(grep -F pinot.server.instance.dataDir "${SERVER_CONF_2}" | awk '{print $3}') + if [ -n "$dirName" ]; then + ${RM} -rf "${dirName}" + fi + dirName=$(grep -F pinot.server.instance.segmentTarDir "${SERVER_CONF_2}" | awk '{print $3}') + if [ -n "$dirName" ]; then + ${RM} -rf "${dirName}" + fi fi } @@ -78,9 +90,9 @@ function waitForZkReady() { status=1 while [ $status -ne 0 ]; do sleep 1 - echo Checking port ${ZK_PORT} for zk ready + echo "Checking port ${ZK_PORT} for zk ready" echo x | nc localhost ${ZK_PORT} 1>/dev/null 2>&1 - status=$(echo $?) + status=$? done } @@ -88,9 +100,9 @@ function waitForControllerReady() { status=1 while [ $status -ne 0 ]; do sleep 1 - echo Checking port ${CONTROLLER_PORT} for controller ready + echo "Checking port ${CONTROLLER_PORT} for controller ready" curl localhost:${CONTROLLER_PORT}/health 1>/dev/null 2>&1 - status=$(echo $?) + status=$? done } @@ -98,9 +110,9 @@ function waitForKafkaReady() { status=1 while [ $status -ne 0 ]; do sleep 1 - echo Checking port 19092 for kafka ready + echo "Checking port 19092 for kafka ready" echo x | nc localhost 19092 1>/dev/null 2>&1 - status=$(echo $?) + status=$? done } @@ -108,9 +120,9 @@ function waitForBrokerReady() { local status=1 while [ $status -ne 0 ]; do sleep 1 - echo Checking port ${BROKER_QUERY_PORT} for broker ready + echo "Checking port ${BROKER_QUERY_PORT} for broker ready" curl localhost:${BROKER_QUERY_PORT}/debug/routingTable 1>/dev/null 2>&1 - status=$(echo $?) + status=$? done } @@ -118,15 +130,31 @@ function waitForServerReady() { local status=1 while [ $status -ne 0 ]; do sleep 1 - echo Checking port ${SERVER_ADMIN_PORT} for server ready + echo "Checking port ${SERVER_ADMIN_PORT} for server ready" curl localhost:${SERVER_ADMIN_PORT}/health 1>/dev/null 2>&1 - status=$(echo $?) + status=$? + done +} + +function waitForServer2Ready() { + local status=1 + while [ $status -ne 0 ]; do + sleep 1 + echo "Checking port ${SERVER_2_ADMIN_PORT} for server ready" + curl localhost:${SERVER_2_ADMIN_PORT}/health 1>/dev/null 2>&1 + status=$? done } function waitForClusterReady() { waitForBrokerReady waitForServerReady + + # Check second server if configured + if [ -f "${SERVER_CONF_2}" ]; then + waitForServer2Ready + fi + waitForKafkaReady } @@ -164,6 +192,9 @@ function startService() { elif [ "$serviceName" = "server" ]; then ./pinot-admin.sh StartServer ${configFileArg} 1>${LOG_DIR}/server.${logCount}.log 2>&1 & echo $! >${PID_DIR}/server.pid + elif [ "$serviceName" = "server2" ]; then + ./pinot-admin.sh StartServer ${configFileArg} 1>${LOG_DIR}/server2.${logCount}.log 2>&1 & + echo $! >${PID_DIR}/server2.pid elif [ "$serviceName" = "kafka" ]; then ./pinot-admin.sh StartKafka -zkAddress localhost:${ZK_PORT}/kafka 1>${LOG_DIR}/kafka.${logCount}.log 2>&1 & echo $! >${PID_DIR}/kafka.pid @@ -207,6 +238,12 @@ function startServices() { waitForControllerReady startService broker "$dirName" "$BROKER_CONF" startService server "$dirName" "$SERVER_CONF" + + # Start second server if configured + if [ -f "${SERVER_CONF_2}" ]; then + startService server2 "$dirName" "$SERVER_CONF_2" + fi + startService kafka "$dirName" "unused" echo "Cluster started." waitForClusterReady @@ -217,8 +254,20 @@ function stopServices() { stopService controller stopService broker stopService server + + # Stop second server if configured + if [ -f "${SERVER_CONF_2}" ]; then + stopService server2 + fi + stopService zookeeper stopService kafka + echo "Controller logs:" + cat ${LOG_DIR}/controller.*.log + echo "Broker logs:" + cat ${LOG_DIR}/broker.*.log + echo "Server logs:" + cat ${LOG_DIR}/server.*.log echo "Cluster stopped." } @@ -248,23 +297,50 @@ function setupControllerVariables() { function setupBrokerVariables() { if [ -f ${BROKER_CONF} ]; then local port=$(grep -F pinot.broker.client.queryPort ${BROKER_CONF} | awk '{print $3}') - if [ ! -z "$port" ]; then + if [ -n "$port" ]; then BROKER_QUERY_PORT=$port fi fi } function setupServerVariables() { - if [ -f ${SERVER_CONF} ]; then + if [ -f "${SERVER_CONF}" ]; then local port - port=$(grep -F pinot.server.adminapi.port ${SERVER_CONF} | awk '{print $3}') - if [ ! -z "$port" ]; then + port=$(grep -F pinot.server.adminapi.port "${SERVER_CONF}" | awk '{print $3}') + if [ -n "$port" ]; then SERVER_ADMIN_PORT=$port fi - port=$(grep -F pinot.server.netty.port ${SERVER_CONF} | awk '{print $3}') - if [ ! -z "$port" ]; then + port=$(grep -F pinot.server.netty.port "${SERVER_CONF}" | awk '{print $3}') + if [ -n "$port" ]; then SERVER_NETTY_PORT=$port fi + port=$(grep -F pinot.server.grpc.port "${SERVER_CONF}" | awk '{print $3}') + if [ -n "$port" ]; then + SERVER_GRPC_PORT=$port + fi + fi + + if [ -f "${SERVER_CONF_2}" ]; then + local port + port=$(grep -F pinot.server.adminapi.port "${SERVER_CONF_2}" | awk '{print $3}') + if [ -n "$port" ]; then + SERVER_2_ADMIN_PORT=$port + fi + port=$(grep -F pinot.server.netty.port "${SERVER_CONF_2}" | awk '{print $3}') + if [ -n "$port" ]; then + SERVER_2_NETTY_PORT=$port + fi + port=$(grep -F pinot.server.grpc.port "${SERVER_CONF_2}" | awk '{print $3}') + if [ -n "$port" ]; then + SERVER_2_GRPC_PORT=$port + fi + fi +} + +function checkPortAvailable() { + if lsof -t -i:"$1" -s TCP:LISTEN; then + echo "Port number $1 not available. Check any existing process that may be using this port." + return 1 fi } @@ -314,6 +390,7 @@ COMPAT_TESTER_PATH="pinot-compatibility-verifier/target/pinot-compatibility-veri BROKER_CONF=${testSuiteDir}/config/BrokerConfig.properties CONTROLLER_CONF=${testSuiteDir}/config/ControllerConfig.properties SERVER_CONF=${testSuiteDir}/config/ServerConfig.properties +SERVER_CONF_2=${testSuiteDir}/config/ServerConfig2.properties cleanupControllerDirs cleanupServerDirs @@ -322,7 +399,11 @@ BROKER_QUERY_PORT=8099 ZK_PORT=2181 CONTROLLER_PORT=9000 SERVER_ADMIN_PORT=8097 +SERVER_2_ADMIN_PORT=9097 SERVER_NETTY_PORT=8098 +SERVER_2_NETTY_PORT=9098 +SERVER_GRPC_PORT=8090 +SERVER_2_GRPC_PORT=9090 PID_DIR=${workingDir}/pids LOG_DIR=${workingDir}/logs @@ -344,9 +425,9 @@ newTargetDir="$workingDir"/newTargetDir setupCompatTester # check that the default ports are open -if [ "$(lsof -t -i:${SERVER_ADMIN_PORT} -s TCP:LISTEN)" ] || [ "$(lsof -t -i:${SERVER_NETTY_PORT} -sTCP:LISTEN)" ] || [ "$(lsof -t -i:${BROKER_QUERY_PORT} -sTCP:LISTEN)" ] || - [ "$(lsof -t -i:${CONTROLLER_PORT} -sTCP:LISTEN)" ] || [ "$(lsof -t -i:${ZK_PORT} -sTCP:LISTEN)" ]; then - echo "Cannot start the components since the default ports are not open. Check any existing process that may be using the default ports." +if ! checkPortAvailable ${SERVER_ADMIN_PORT} || ! checkPortAvailable ${SERVER_NETTY_PORT} || ! checkPortAvailable ${SERVER_GRPC_PORT} || + ! checkPortAvailable ${BROKER_QUERY_PORT} || ! checkPortAvailable ${CONTROLLER_PORT} || ! checkPortAvailable ${ZK_PORT} || + { [ -f "${SERVER_CONF_2}" ] && { ! checkPortAvailable ${SERVER_2_ADMIN_PORT} || ! checkPortAvailable ${SERVER_2_NETTY_PORT} || ! checkPortAvailable ${SERVER_2_GRPC_PORT}; } ; }; then exit 1 fi @@ -363,10 +444,11 @@ if [ -f $testSuiteDir/pre-controller-upgrade.yaml ]; then if [ $keepClusterOnFailure == "false" ]; then stopServices fi - echo Failed before controller upgrade + echo "Failed before controller upgrade" exit 1 fi fi + echo "Upgrading controller" stopService controller startService controller "$newTargetDir" "$CONTROLLER_CONF" @@ -380,14 +462,16 @@ if [ -f $testSuiteDir/pre-broker-upgrade.yaml ]; then if [ $keepClusterOnFailure == "false" ]; then stopServices fi - echo Failed before broker upgrade + echo "Failed before broker upgrade" exit 1 fi fi + echo "Upgrading broker" stopService broker startService broker "$newTargetDir" "$BROKER_CONF" waitForBrokerReady + if [ -f $testSuiteDir/pre-server-upgrade.yaml ]; then echo "Running tests after broker upgrade" genNum=$((genNum+1)) @@ -396,14 +480,16 @@ if [ -f $testSuiteDir/pre-server-upgrade.yaml ]; then if [ $keepClusterOnFailure == "false" ]; then stopServices fi - echo Failed before server upgrade + echo "Failed before server upgrade" exit 1 fi fi + echo "Upgrading server" stopService server startService server "$newTargetDir" "$SERVER_CONF" waitForServerReady + if [ -f $testSuiteDir/post-server-upgrade.yaml ]; then echo "Running tests after server upgrade" genNum=$((genNum+1)) @@ -412,16 +498,56 @@ if [ -f $testSuiteDir/post-server-upgrade.yaml ]; then if [ $keepClusterOnFailure == "false" ]; then stopServices fi - echo Failed after server upgrade + echo "Failed after server upgrade" exit 1 fi fi +if [ -f "${SERVER_CONF_2}" ]; then + echo "Upgrading server 2" + stopService server2 + startService server2 "$newTargetDir" "$SERVER_CONF_2" + waitForServer2Ready + + if [ -f $testSuiteDir/post-server-2-upgrade.yaml ]; then + echo "Running tests after server 2 upgrade" + genNum=$((genNum+1)) + $COMPAT_TESTER $testSuiteDir/post-server-2-upgrade.yaml $genNum + if [ $? -ne 0 ]; then + if [ $keepClusterOnFailure == "false" ]; then + stopServices + fi + echo "Failed after server 2 upgrade" + exit 1 + fi + fi + + echo "Downgrading server 2" + # Upgrade completed, now do a rollback + stopService server2 + startService server2 "$oldTargetDir" "$SERVER_CONF_2" + waitForServer2Ready + + if [ -f $testSuiteDir/post-server-2-rollback.yaml ]; then + echo "Running tests after server 2 downgrade" + genNum=$((genNum+1)) + $COMPAT_TESTER $testSuiteDir/post-server-2-rollback.yaml $genNum + if [ $? -ne 0 ]; then + if [ $keepClusterOnFailure == "false" ]; then + stopServices + fi + echo "Failed after server 2 downgrade" + exit 1 + fi + fi +fi + echo "Downgrading server" # Upgrade completed, now do a rollback stopService server startService server "$oldTargetDir" "$SERVER_CONF" waitForServerReady + if [ -f $testSuiteDir/post-server-rollback.yaml ]; then echo "Running tests after server downgrade" genNum=$((genNum+1)) @@ -430,14 +556,16 @@ if [ -f $testSuiteDir/post-server-rollback.yaml ]; then if [ $keepClusterOnFailure == "false" ]; then stopServices fi - echo Failed after server downgrade + echo "Failed after server downgrade" exit 1 fi fi + echo "Downgrading broker" stopService broker startService broker "$oldTargetDir" "$BROKER_CONF" waitForBrokerReady + if [ -f $testSuiteDir/post-broker-rollback.yaml ]; then echo "Running tests after broker downgrade" genNum=$((genNum+1)) @@ -446,15 +574,17 @@ if [ -f $testSuiteDir/post-broker-rollback.yaml ]; then if [ $keepClusterOnFailure == "false" ]; then stopServices fi - echo Failed after broker downgrade + echo "Failed after broker downgrade" exit 1 fi fi + echo "Downgrading controller" stopService controller startService controller "$oldTargetDir" "$CONTROLLER_CONF" waitForControllerReady waitForControllerReady + if [ -f $testSuiteDir/post-controller-rollback.yaml ]; then echo "Running tests after controller downgrade" genNum=$((genNum+1)) @@ -463,10 +593,11 @@ if [ -f $testSuiteDir/post-controller-rollback.yaml ]; then if [ $keepClusterOnFailure == "false" ]; then stopServices fi - echo Failed after controller downgrade + echo "Failed after controller downgrade" exit 1 fi fi + stopServices echo "All tests passed" diff --git a/compatibility-verifier/multi-stage-query-engine-test-suite/config/BrokerConfig.properties b/compatibility-verifier/multi-stage-query-engine-test-suite/config/BrokerConfig.properties new file mode 100644 index 000000000000..e56f23c287b6 --- /dev/null +++ b/compatibility-verifier/multi-stage-query-engine-test-suite/config/BrokerConfig.properties @@ -0,0 +1,23 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +pinot.broker.client.queryPort = 8099 +pinot.zk.server = localhost:2181 +pinot.cluster.name = PinotCluster +pinot.broker.disable.query.groovy=false diff --git a/compatibility-verifier/multi-stage-query-engine-test-suite/config/ControllerConfig.properties b/compatibility-verifier/multi-stage-query-engine-test-suite/config/ControllerConfig.properties new file mode 100644 index 000000000000..9949b2519ea6 --- /dev/null +++ b/compatibility-verifier/multi-stage-query-engine-test-suite/config/ControllerConfig.properties @@ -0,0 +1,25 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +controller.host = localhost +controller.port = 9000 +controller.zk.str = localhost:2181 +controller.data.dir = /tmp/PinotController +controller.helix.cluster.name = PinotCluster +controller.disable.ingestion.groovy = false diff --git a/compatibility-verifier/multi-stage-query-engine-test-suite/config/FeatureTest1-schema.json b/compatibility-verifier/multi-stage-query-engine-test-suite/config/FeatureTest1-schema.json new file mode 100644 index 000000000000..85378dd3a04d --- /dev/null +++ b/compatibility-verifier/multi-stage-query-engine-test-suite/config/FeatureTest1-schema.json @@ -0,0 +1,94 @@ +{ + "dimensionFieldSpecs": [ + { + "dataType": "INT", + "name": "generationNumber" + }, + { + "dataType": "STRING", + "name": "stringDimSV1" + }, + { + "dataType": "STRING", + "name": "stringDimSV2" + }, + { + "dataType": "LONG", + "name": "longDimSV1" + }, + { + "dataType": "LONG", + "name": "longDimSV2" + }, + { + "dataType": "STRING", + "name": "stringDimMV1", + "singleValueField": false + }, + { + "dataType": "STRING", + "name": "stringDimMV2", + "singleValueField": false + }, + { + "dataType": "INT", + "name": "intDimMV1", + "singleValueField": false + }, + { + "dataType": "INT", + "name": "intDimMV2", + "singleValueField": false + }, + { + "dataType": "STRING", + "maxLength": 1000, + "name": "textDim1" + }, + { + "dataType": "BYTES", + "name": "bytesDimSV1" + }, + { + "dataType": "STRING", + "name": "mapDim1__KEYS", + "singleValueField": false + }, + { + "dataType": "INT", + "name": "mapDim1__VALUES", + "singleValueField": false + }, + { + "dataType": "STRING", + "name": "mapDim2json" + } + ], + "metricFieldSpecs": [ + { + "dataType": "INT", + "name": "intMetric1" + }, + { + "dataType": "LONG", + "name": "longMetric1" + }, + { + "dataType": "FLOAT", + "name": "floatMetric1" + }, + { + "dataType": "DOUBLE", + "name": "doubleMetric1" + } + ], + "dateTimeFieldSpecs" : [ + { + "name" : "HoursSinceEpoch", + "dataType" : "INT", + "format" : "1:HOURS:EPOCH", + "granularity": "1:HOURS" + } + ], + "schemaName": "FeatureTest1" +} diff --git a/compatibility-verifier/multi-stage-query-engine-test-suite/config/FeatureTest2-schema.json b/compatibility-verifier/multi-stage-query-engine-test-suite/config/FeatureTest2-schema.json new file mode 100644 index 000000000000..f53c5a2c863f --- /dev/null +++ b/compatibility-verifier/multi-stage-query-engine-test-suite/config/FeatureTest2-schema.json @@ -0,0 +1,94 @@ +{ + "dimensionFieldSpecs": [ + { + "dataType": "INT", + "name": "generationNumber" + }, + { + "dataType": "STRING", + "name": "stringDimSV1" + }, + { + "dataType": "STRING", + "name": "stringDimSV2" + }, + { + "dataType": "LONG", + "name": "longDimSV1" + }, + { + "dataType": "LONG", + "name": "longDimSV2" + }, + { + "dataType": "STRING", + "name": "stringDimMV1", + "singleValueField": false + }, + { + "dataType": "STRING", + "name": "stringDimMV2", + "singleValueField": false + }, + { + "dataType": "INT", + "name": "intDimMV1", + "singleValueField": false + }, + { + "dataType": "INT", + "name": "intDimMV2", + "singleValueField": false + }, + { + "dataType": "STRING", + "maxLength": 1000, + "name": "textDim1" + }, + { + "dataType": "BYTES", + "name": "bytesDimSV1" + }, + { + "dataType": "STRING", + "name": "mapDim1__KEYS", + "singleValueField": false + }, + { + "dataType": "INT", + "name": "mapDim1__VALUES", + "singleValueField": false + }, + { + "dataType": "STRING", + "name": "mapDim2json" + } + ], + "metricFieldSpecs": [ + { + "dataType": "INT", + "name": "intMetric1" + }, + { + "dataType": "LONG", + "name": "longMetric1" + }, + { + "dataType": "FLOAT", + "name": "floatMetric1" + }, + { + "dataType": "DOUBLE", + "name": "doubleMetric1" + } + ], + "dateTimeFieldSpecs" : [ + { + "name" : "HoursSinceEpoch", + "dataType" : "INT", + "format" : "1:HOURS:EPOCH", + "granularity": "1:HOURS" + } + ], + "schemaName": "FeatureTest2" +} diff --git a/compatibility-verifier/multi-stage-query-engine-test-suite/config/ServerConfig.properties b/compatibility-verifier/multi-stage-query-engine-test-suite/config/ServerConfig.properties new file mode 100644 index 000000000000..0bf3a9b47bb8 --- /dev/null +++ b/compatibility-verifier/multi-stage-query-engine-test-suite/config/ServerConfig.properties @@ -0,0 +1,25 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +pinot.server.adminapi.port = 8097 +pinot.server.netty.port = 8098 +pinot.zk.server = localhost:2181 +pinot.cluster.name = PinotCluster +pinot.server.instance.dataDir = /tmp/PinotServer/data +pinot.server.instance.segmentTarDir = /tmp/PinotServer/segments diff --git a/compatibility-verifier/multi-stage-query-engine-test-suite/config/ServerConfig2.properties b/compatibility-verifier/multi-stage-query-engine-test-suite/config/ServerConfig2.properties new file mode 100644 index 000000000000..b0f4707f70a7 --- /dev/null +++ b/compatibility-verifier/multi-stage-query-engine-test-suite/config/ServerConfig2.properties @@ -0,0 +1,26 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +pinot.server.adminapi.port = 9097 +pinot.server.netty.port = 9098 +pinot.server.grpc.port = 9090 +pinot.zk.server = localhost:2181 +pinot.cluster.name = PinotCluster +pinot.server.instance.dataDir = /tmp/PinotServer2/data +pinot.server.instance.segmentTarDir = /tmp/PinotServer2/segments diff --git a/compatibility-verifier/multi-stage-query-engine-test-suite/config/data/FeatureTest1-data-00.csv b/compatibility-verifier/multi-stage-query-engine-test-suite/config/data/FeatureTest1-data-00.csv new file mode 100644 index 000000000000..bb537d9ba333 --- /dev/null +++ b/compatibility-verifier/multi-stage-query-engine-test-suite/config/data/FeatureTest1-data-00.csv @@ -0,0 +1,12 @@ +# HoursSinceEpoch generationNumber stringDimSV1 stringDimSV2 longDimSV1 longDimSV2 stringDimMV1 stringDimMV2 intDimMV1 intDimMV2 textDim1 mapDim1__KEYS mapDim1__VALUES mapDim2json intMetric1 longMetric1 floatMetric1 doubleMetric1 +# Add some common rows from first segment, and some new rows as well +123456,__GENERATION_NUMBER__,"s1-0",s2-0,1,2,m1-0-0;m1-0-1,m2-0-0;m2-0-1,3;4,6;7,Java C++ Python,01a0bc,k1;k2;k3;k4;k5,1;1;2;2;2,"{""k1"":1,""k2"":1,""k3"":2,""k4"":2,""k5"":2}",10,11,12.1,13.1 +123456,__GENERATION_NUMBER__,"s1-0",s2-0,1,2,m1-0-0;m1-0-1,m2-0-0;m2-0-1,3;4,6;7,Java C++ Python,4877625602,k1;k2;k3;k4;k5,3;3;3;3;3,"{""k1"":3,""k2"":3,""k3"":3,""k4"":3,""k5"":3}",10,11,12.1,13.1, # Dupliate of row 0 1 +123456,__GENERATION_NUMBER__,s1-2,s2-2,11,21,m1-2-0;m1-2-1,m2-2-0;m2-2-1,32;42,62;72,Java C++ golang,13225573e3f5,k1;k2;k3;k4;k5,4;5;6;7;7,"{""k1"":4,""k2"":5,""k3"":6,""k4"":7,""k5"":7}",10,21,22.1,23.10 +123456,__GENERATION_NUMBER__,s1-2,s2-2,11,21,m1-3-0;m1-3-1,m2-3-0;m2-3-1,32;42,62;72,Java C++ golang,deadbeef,k1;k2;k3;k4;k5,7;7;7;7;7,"{""k1"":7,""k2"":7,""k3"":7,""k4"":7,""k5"":7}",10,21,22.1,23.10, # All sv cols same as prev +123456,__GENERATION_NUMBER__,s1-4,s2-4,41,22,m1-2-0;m1-2-1,m2-2-0;m2-2-1,42;52,72;82,Java C++ golang,deed0507,k1;k2;k3;k4;k5,7;7;8;8;8,"{""k1"":7,""k2"":7,""k3"":8,""k4"":8,""k5"":8}",14,24,24.1,24.10, # All mv cols same as row 2 +123456,__GENERATION_NUMBER__,s1-5,,,32,m1-5-0,m2-2-0,,92;22,golang shell bash,,k1;k2;k3;k4;k5,7;7;7;7;7,"{""k1"":7,""k2"":7,""k3"":7,""k4"":7,""k5"":7}",,24,,24.10, # Default values for some columns +123456,__GENERATION_NUMBER__,s1-6,s2-6,7611,7621,m1-5-0;m1-5-1;m1-5-2,m2-2-0,392;462,6662;782,C++ golang python,deed0507,k1;k2;k3;k4;k5,7;8;9;10;20,"{""k1"":7,""k2"":8,""k3"":9,""k4"":10,""k5"":20}",101,251,262.1,263.10, # 3 values in MV +123456,__GENERATION_NUMBER__,s1-6,s2-6,7611,7621,m1-5-0;m1-5-1;m1-5-2,m2-2-0,392;462,6662;782,C++ golang python,deed0507,k1;k2;k3;k4;k5,7;8;9;10;20,"{""k1"":7,""k2"":8,""k3"":9,""k4"":10,""k5"":20}",2147483647,251,262.1,263.10, # MAX_INT in int metric +123456,__GENERATION_NUMBER__,s1-6,s2-6,7611,7621,m1-5-0;m1-5-1;m1-5-2,m2-2-0,392;462,6662;782,C++ golang python,deed0507,k1;k2;k3;k4;k5,7;8;9;10;20,"{""k1"":7,""k2"":8,""k3"":9,""k4"":10,""k5"":20}",2147483647,251,262.1,263.10, # MAX_INT in int metric +123456,__GENERATION_NUMBER__,s1-7,s2-7,6766,6777,m1-6-0;m1-6-1;m1-6-2;m1-6-3,m2-6-0;m2-6-1,392;462,6662;782,golang Java,d54d0507,k1;k2;k3;k4;k5,31;31;32;32;32,"{""k1"":31,""k2"":31,""k3"":32,""k4"":32,""k5"":32}",87,251,262.10,263.10 diff --git a/compatibility-verifier/multi-stage-query-engine-test-suite/config/data/FeatureTest2-data-realtime-00.csv b/compatibility-verifier/multi-stage-query-engine-test-suite/config/data/FeatureTest2-data-realtime-00.csv new file mode 100644 index 000000000000..e4e36e47051f --- /dev/null +++ b/compatibility-verifier/multi-stage-query-engine-test-suite/config/data/FeatureTest2-data-realtime-00.csv @@ -0,0 +1,11 @@ +# HoursSinceEpoch generationNumber stringDimSV1 stringDimSV2 longDimSV1 longDimSV2 stringDimMV1 stringDimMV2 intDimMV1 intDimMV2 textDim1 mapDim1__KEYS mapDim1__VALUES mapDim2json intMetric1 longMetric1 floatMetric1 doubleMetric1 +123456,__GENERATION_NUMBER__,"s1-0",s2-0,1,2,m1-0-0;m1-0-1,m2-0-0;m2-0-1,3;4,6;7,Java C++ Python,01a0bc,k1;k2;k3;k4;k5,1;1;2;2;2,"{""k1"":1,""k2"":1,""k3"":2,""k4"":2,""k5"":2}",10,11,12.1,13.1 +123456,__GENERATION_NUMBER__,"s1-0",s2-0,1,2,m1-0-0;m1-0-1,m2-0-0;m2-0-1,3;4,6;7,Java C++ Python,4877625602,k1;k2;k3;k4;k5,3;3;3;3;3,"{""k1"":3,""k2"":3,""k3"":3,""k4"":3,""k5"":3}",10,11,12.1,13.1, # Dupliate of row 0 1 +123456,__GENERATION_NUMBER__,s1-2,s2-2,11,21,m1-2-0;m1-2-1,m2-2-0;m2-2-1,32;42,62;72,Java C++ golang,13225573e3f5,k1;k2;k3;k4;k5,4;5;6;7;7,"{""k1"":4,""k2"":5,""k3"":6,""k4"":7,""k5"":7}",10,21,22.1,23.10 +123456,__GENERATION_NUMBER__,s1-2,s2-2,11,21,m1-3-0;m1-3-1,m2-3-0;m2-3-1,32;42,62;72,Java C++ golang,deadbeef,k1;k2;k3;k4;k5,7;7;7;7;7,"{""k1"":7,""k2"":7,""k3"":7,""k4"":7,""k5"":7}",10,21,22.1,23.10, # All sv cols same as prev +123456,__GENERATION_NUMBER__,s1-4,s2-4,41,22,m1-2-0;m1-2-1,m2-2-0;m2-2-1,42;52,72;82,Java C++ golang,deed0507,k1;k2;k3;k4;k5,7;7;8;8;8,"{""k1"":7,""k2"":7,""k3"":8,""k4"":8,""k5"":8}",14,24,24.1,24.10, # All mv cols same as row 2 +123456,__GENERATION_NUMBER__,s1-5,,,32,m1-5-0,m2-2-0,,92;22,golang shell bash,,k1;k2;k3;k4;k5,7;7;7;7;7,"{""k1"":7,""k2"":7,""k3"":7,""k4"":7,""k5"":7}",,24,,24.10, # Default values for some columns +123456,__GENERATION_NUMBER__,s1-6,s2-6,7611,7621,m1-5-0;m1-5-1;m1-5-2,m2-2-0,392;462,6662;782,C++ golang python,deed0507,k1;k2;k3;k4;k5,7;8;9;10;20,"{""k1"":7,""k2"":8,""k3"":9,""k4"":10,""k5"":20}",101,251,262.1,263.10, # 3 values in MV +123456,__GENERATION_NUMBER__,s1-6,s2-6,7611,7621,m1-5-0;m1-5-1;m1-5-2,m2-2-0,392;462,6662;782,C++ golang python,deed0507,k1;k2;k3;k4;k5,7;8;9;10;20,"{""k1"":7,""k2"":8,""k3"":9,""k4"":10,""k5"":20}",2147483647,251,262.1,263.10, # MAX_INT in int metric +123456,__GENERATION_NUMBER__,s1-6,s2-6,7611,7621,m1-5-0;m1-5-1;m1-5-2,m2-2-0,392;462,6662;782,C++ golang python,deed0507,k1;k2;k3;k4;k5,7;8;9;10;20,"{""k1"":7,""k2"":8,""k3"":9,""k4"":10,""k5"":20}",2147483647,251,262.1,263.10, # MAX_INT in int metric +123456,__GENERATION_NUMBER__,s1-7,s2-7,6766,6777,m1-6-0;m1-6-1;m1-6-2;m1-6-3,m2-6-0;m2-6-1,392;462,6662;782,golang Java,d54d0507,k1;k2;k3;k4;k5,31;31;32;32;32,"{""k1"":31,""k2"":31,""k3"":32,""k4"":32,""k5"":32}",87,251,262.10,263.10 diff --git a/compatibility-verifier/multi-stage-query-engine-test-suite/config/data/recordReaderConfig.json b/compatibility-verifier/multi-stage-query-engine-test-suite/config/data/recordReaderConfig.json new file mode 100644 index 000000000000..ebaa04e10464 --- /dev/null +++ b/compatibility-verifier/multi-stage-query-engine-test-suite/config/data/recordReaderConfig.json @@ -0,0 +1,5 @@ +{ + "commentMarker" : "#", + "header" : + "HoursSinceEpoch,generationNumber,stringDimSV1,stringDimSV2,longDimSV1,longDimSV2,stringDimMV1,stringDimMV2,intDimMV1,intDimMV2,textDim1,bytesDimSV1,mapDim1__KEYS,mapDim1__VALUES,mapDim2json,intMetric1,longMetric1,floatMetric1,doubleMetric1" +} diff --git a/compatibility-verifier/multi-stage-query-engine-test-suite/config/feature-test-1.json b/compatibility-verifier/multi-stage-query-engine-test-suite/config/feature-test-1.json new file mode 100644 index 000000000000..381e6885d19a --- /dev/null +++ b/compatibility-verifier/multi-stage-query-engine-test-suite/config/feature-test-1.json @@ -0,0 +1,46 @@ +{ + "fieldConfigList": [ + { + "encodingType": "RAW", + "indexType": "TEXT", + "name": "textDim1", + "properties": { + "deriveNumDocsPerChunkForRawIndex": "true", + "rawIndexWriterVersion": "3" + } + } + ], + "metadata": { + "customConfigs": { + "d2Name": "" + } + }, + "segmentsConfig": { + "replication": "1", + "retentionTimeUnit": "", + "retentionTimeValue": "", + "segmentAssignmentStrategy": "BalanceNumSegmentAssignmentStrategy", + "segmentPushFrequency": "daily", + "segmentPushType": "REFRESH", + "timeColumnName": "HoursSinceEpoch", + "timeType": "HOURS" + }, + "tableIndexConfig": { + "aggregateMetrics": false, + "autoGeneratedInvertedIndex": false, + "createInvertedIndexDuringSegmentGeneration": false, + "enableDefaultStarTree": false, + "enableDynamicStarTreeCreation": false, + "loadMode": "MMAP", + "noDictionaryColumns": ["textDim1"], + "nullHandlingEnabled": false, + "sortedColumn": [], + "streamConfigs": {} + }, + "tableName": "FeatureTest1_OFFLINE", + "tableType": "OFFLINE", + "tenants": { + "broker": "DefaultTenant", + "server": "DefaultTenant" + } +} diff --git a/compatibility-verifier/multi-stage-query-engine-test-suite/config/feature-test-2-realtime-stream-config.json b/compatibility-verifier/multi-stage-query-engine-test-suite/config/feature-test-2-realtime-stream-config.json new file mode 100644 index 000000000000..441ce201a903 --- /dev/null +++ b/compatibility-verifier/multi-stage-query-engine-test-suite/config/feature-test-2-realtime-stream-config.json @@ -0,0 +1,8 @@ +{ + "streamType": "kafka", + "stream.kafka.consumer.type": "simple", + "topicName": "PinotRealtimeFeatureTest2Event", + "partitionColumn": "longDimSV1", + "numPartitions": "1", + "stream.kafka.consumer.prop.auto.offset.reset": "smallest" +} diff --git a/compatibility-verifier/multi-stage-query-engine-test-suite/config/feature-test-2-realtime.json b/compatibility-verifier/multi-stage-query-engine-test-suite/config/feature-test-2-realtime.json new file mode 100644 index 000000000000..6df335e48841 --- /dev/null +++ b/compatibility-verifier/multi-stage-query-engine-test-suite/config/feature-test-2-realtime.json @@ -0,0 +1,61 @@ +{ + "fieldConfigList": [ + { + "encodingType": "RAW", + "indexType": "TEXT", + "name": "textDim1", + "properties": { + "deriveNumDocsPerChunkForRawIndex": "true", + "rawIndexWriterVersion": "3" + } + } + ], + "metadata": { + "customConfigs": { + "d2Name": "" + } + }, + "segmentsConfig": { + "replicasPerPartition": "1", + "replication": "1", + "retentionTimeUnit": "", + "retentionTimeValue": "", + "schemaName": "FeatureTest2", + "segmentAssignmentStrategy": "BalanceNumSegmentAssignmentStrategy", + "segmentPushFrequency": "daily", + "segmentPushType": "APPEND", + "timeColumnName": "HoursSinceEpoch", + "timeType": "HOURS" + }, + "tableIndexConfig": { + "aggregateMetrics": false, + "autoGeneratedInvertedIndex": false, + "bloomFilterColumns": [], + "createInvertedIndexDuringSegmentGeneration": false, + "enableDefaultStarTree": false, + "enableDynamicStarTreeCreation": false, + "loadMode": "MMAP", + "noDictionaryColumns": [], + "nullHandlingEnabled": false, + "segmentFormatVersion": "v3", + "sortedColumn": [], + "streamConfigs": { + "realtime.segment.flush.threshold.size": "63", + "realtime.segment.flush.threshold.time": "1h", + "streamType": "kafka", + "stream.kafka.topic.name": "PinotRealtimeFeatureTest2Event", + "stream.kafka.consumer.type": "simple", + "stream.kafka.decoder.class.name": "org.apache.pinot.plugin.stream.kafka.KafkaJSONMessageDecoder", + "stream.kafka.consumer.factory.class.name": "org.apache.pinot.plugin.stream.kafka20.KafkaConsumerFactory", + "stream.kafka.broker.list": "localhost:19092", + "stream.kafka.zk.broker.url": "localhost:2181/kafka", + "stream.kafka.consumer.prop.auto.offset.reset": "largest" + } + }, + "tableName": "FeatureTest2", + "tableType": "REALTIME", + "tenants": { + "broker": "DefaultTenant", + "server": "DefaultTenant" + } +} diff --git a/compatibility-verifier/multi-stage-query-engine-test-suite/config/queries/feature-test-multi-stage.queries b/compatibility-verifier/multi-stage-query-engine-test-suite/config/queries/feature-test-multi-stage.queries new file mode 100644 index 000000000000..00d0c6cb8a74 --- /dev/null +++ b/compatibility-verifier/multi-stage-query-engine-test-suite/config/queries/feature-test-multi-stage.queries @@ -0,0 +1,31 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Joins +SELECT COUNT(*) FROM FeatureTest1 ft1 INNER JOIN FeatureTest2 ft2 ON ft1.stringDimSV1 = ft2.stringDimSV1 WHERE ft1.generationNumber = __GENERATION_NUMBER__ AND ft2.generationNumber = __GENERATION_NUMBER__ +SELECT ft1.stringDimSV1, COUNT(ft1.stringDimSV1) FROM FeatureTest1 ft1 INNER JOIN FeatureTest2 ft2 ON ft1.stringDimSV1 = ft2.stringDimSV1 WHERE ft1.generationNumber = __GENERATION_NUMBER__ AND ft2.generationNumber = __GENERATION_NUMBER__ GROUP BY ft1.stringDimSV1 +SELECT ft1.stringDimSV2, SUM(ft1.floatMetric1) FROM FeatureTest1 ft1 INNER JOIN FeatureTest2 ft2 ON ft1.stringDimSV1 = ft2.stringDimSV1 WHERE ft1.generationNumber = __GENERATION_NUMBER__ AND ft2.generationNumber = __GENERATION_NUMBER__ GROUP BY ft1.stringDimSV2 +SELECT ft1.stringDimSV1 FROM FeatureTest1 ft1 WHERE ft1.generationNumber = __GENERATION_NUMBER__ AND EXISTS (SELECT 1 FROM FeatureTest2 ft2 WHERE ft2.generationNumber = __GENERATION_NUMBER__ AND ft2.stringDimSV2 = ft1.stringDimSV1) + +# Set operations +SELECT * FROM (SELECT stringDimSV1 FROM FeatureTest1 WHERE generationNumber = __GENERATION_NUMBER__) INTERSECT (SELECT stringDimSV1 FROM FeatureTest2 WHERE generationNumber = __GENERATION_NUMBER__) +SELECT * FROM (SELECT stringDimSV1 FROM FeatureTest1 WHERE generationNumber = __GENERATION_NUMBER__) UNION (SELECT stringDimSV1 FROM FeatureTest2 WHERE generationNumber = __GENERATION_NUMBER__) + +# Windows +SELECT stringDimSV1, longMetric1, SUM(longMetric1) OVER (PARTITION BY stringDimSV1) FROM FeatureTest1 WHERE generationNumber = __GENERATION_NUMBER__ diff --git a/compatibility-verifier/multi-stage-query-engine-test-suite/config/query-results/feature-test-multi-stage.results b/compatibility-verifier/multi-stage-query-engine-test-suite/config/query-results/feature-test-multi-stage.results new file mode 100644 index 000000000000..b073dd9e1cbb --- /dev/null +++ b/compatibility-verifier/multi-stage-query-engine-test-suite/config/query-results/feature-test-multi-stage.results @@ -0,0 +1,31 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Joins +{"resultTable":{"dataSchema":{"columnNames":["EXPR$0"],"columnDataTypes":["LONG"]},"rows":[[130]]},"requestId":"11345778000000000","stageStats":{"type":"MAILBOX_RECEIVE","executionTimeMs":36,"emittedRows":1,"fanIn":1,"rawMessages":2,"deserializedBytes":714,"upstreamWaitMs":40,"children":[{"type":"MAILBOX_SEND","executionTimeMs":36,"emittedRows":1,"stage":1,"parallelism":1,"fanOut":1,"rawMessages":2,"serializedBytes":99,"serializationTimeMs":2,"children":[{"type":"AGGREGATE","executionTimeMs":1,"emittedRows":1,"children":[{"type":"MAILBOX_RECEIVE","executionTimeMs":1,"emittedRows":1,"fanIn":1,"inMemoryMessages":2,"upstreamWaitMs":7,"children":[{"type":"MAILBOX_SEND","executionTimeMs":1,"emittedRows":1,"stage":2,"parallelism":1,"fanOut":1,"inMemoryMessages":2,"children":[{"type":"AGGREGATE","executionTimeMs":1,"emittedRows":1,"children":[{"type":"HASH_JOIN","executionTimeMs":1,"emittedRows":130,"timeBuildingHashTableMs":1,"children":[{"type":"MAILBOX_RECEIVE","emittedRows":10,"fanIn":1,"inMemoryMessages":2,"upstreamWaitMs":4,"children":[{"type":"MAILBOX_SEND","executionTimeMs":4,"emittedRows":10,"stage":3,"parallelism":1,"fanOut":1,"inMemoryMessages":2,"children":[{"type":"LEAF","table":"FeatureTest1","executionTimeMs":5,"emittedRows":10,"numDocsScanned":10,"numEntriesScannedPostFilter":10,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"totalDocs":10}]}]},{"type":"MAILBOX_RECEIVE","emittedRows":66,"fanIn":1,"inMemoryMessages":3,"upstreamWaitMs":3,"children":[{"type":"MAILBOX_SEND","executionTimeMs":4,"emittedRows":66,"stage":4,"parallelism":1,"fanOut":1,"inMemoryMessages":3,"children":[{"type":"LEAF","table":"FeatureTest2","executionTimeMs":4,"emittedRows":66,"numDocsScanned":66,"numEntriesScannedPostFilter":66,"numSegmentsQueried":2,"numSegmentsProcessed":2,"numSegmentsMatched":2,"numConsumingSegmentsQueried":1,"minConsumingFreshnessTimeMs":1716274896691,"totalDocs":66}]}]}]}]}]}]}]}]}]},"brokerId":"Broker_192.168.29.25_8099","exceptions":[],"numServersQueried":0,"numServersResponded":0,"numSegmentsQueried":3,"numSegmentsProcessed":3,"numSegmentsMatched":3,"numConsumingSegmentsQueried":1,"numConsumingSegmentsProcessed":0,"numConsumingSegmentsMatched":0,"numDocsScanned":76,"numEntriesScannedInFilter":0,"numEntriesScannedPostFilter":76,"numGroupsLimitReached":false,"maxRowsInJoinReached":false,"totalDocs":76,"timeUsedMs":975,"offlineThreadCpuTimeNs":0,"realtimeThreadCpuTimeNs":0,"offlineSystemActivitiesCpuTimeNs":0,"realtimeSystemActivitiesCpuTimeNs":0,"offlineResponseSerializationCpuTimeNs":0,"realtimeResponseSerializationCpuTimeNs":0,"offlineTotalCpuTimeNs":0,"realtimeTotalCpuTimeNs":0,"brokerReduceTimeMs":56,"partialResult":false,"exceptionsSize":0,"numRowsResultSet":1,"maxRowsInOperator":130,"numSegmentsPrunedByBroker":0,"minConsumingFreshnessTimeMs":1716274896691,"numSegmentsPrunedByServer":0,"numSegmentsPrunedInvalid":0,"numSegmentsPrunedByLimit":0,"numSegmentsPrunedByValue":0,"explainPlanNumEmptyFilterSegments":0,"explainPlanNumMatchAllFilterSegments":0} +{"resultTable":{"dataSchema":{"columnNames":["stringDimSV1","EXPR$1"],"columnDataTypes":["STRING","LONG"]},"rows":[["s1-4",7],["s1-6",54],["s1-2",28],["s1-0",28],["s1-5",7],["s1-7",6]]},"requestId":"11345778000000001","stageStats":{"type":"MAILBOX_RECEIVE","executionTimeMs":3,"emittedRows":6,"fanIn":1,"rawMessages":2,"deserializedBytes":824,"upstreamWaitMs":3,"children":[{"type":"MAILBOX_SEND","executionTimeMs":2,"emittedRows":6,"stage":1,"parallelism":1,"fanOut":1,"rawMessages":2,"serializedBytes":245,"children":[{"type":"AGGREGATE","executionTimeMs":2,"emittedRows":6,"children":[{"type":"MAILBOX_RECEIVE","executionTimeMs":1,"emittedRows":6,"fanIn":1,"inMemoryMessages":2,"upstreamWaitMs":4,"children":[{"type":"MAILBOX_SEND","executionTimeMs":1,"emittedRows":6,"stage":2,"parallelism":1,"fanOut":1,"inMemoryMessages":2,"children":[{"type":"AGGREGATE","executionTimeMs":1,"emittedRows":6,"children":[{"type":"HASH_JOIN","emittedRows":130,"timeBuildingHashTableMs":1,"children":[{"type":"MAILBOX_RECEIVE","emittedRows":10,"fanIn":1,"inMemoryMessages":2,"upstreamWaitMs":3,"children":[{"type":"MAILBOX_SEND","emittedRows":10,"stage":3,"parallelism":1,"fanOut":1,"inMemoryMessages":2,"children":[{"type":"LEAF","table":"FeatureTest1","executionTimeMs":1,"emittedRows":10,"numDocsScanned":10,"numEntriesScannedPostFilter":10,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"totalDocs":10}]}]},{"type":"MAILBOX_RECEIVE","emittedRows":66,"fanIn":1,"inMemoryMessages":3,"upstreamWaitMs":3,"children":[{"type":"MAILBOX_SEND","emittedRows":66,"stage":4,"parallelism":1,"fanOut":1,"inMemoryMessages":3,"children":[{"type":"LEAF","table":"FeatureTest2","executionTimeMs":1,"emittedRows":66,"numDocsScanned":66,"numEntriesScannedPostFilter":66,"numSegmentsQueried":2,"numSegmentsProcessed":2,"numSegmentsMatched":2,"numConsumingSegmentsQueried":1,"minConsumingFreshnessTimeMs":1716276514329,"totalDocs":66}]}]}]}]}]}]}]}]}]},"brokerId":"Broker_192.168.29.25_8099","exceptions":[],"numServersQueried":0,"numServersResponded":0,"numSegmentsQueried":3,"numSegmentsProcessed":3,"numSegmentsMatched":3,"numConsumingSegmentsQueried":1,"numConsumingSegmentsProcessed":0,"numConsumingSegmentsMatched":0,"numDocsScanned":76,"numEntriesScannedInFilter":0,"numEntriesScannedPostFilter":76,"numGroupsLimitReached":false,"maxRowsInJoinReached":false,"totalDocs":76,"timeUsedMs":49,"offlineThreadCpuTimeNs":0,"realtimeThreadCpuTimeNs":0,"offlineSystemActivitiesCpuTimeNs":0,"realtimeSystemActivitiesCpuTimeNs":0,"offlineResponseSerializationCpuTimeNs":0,"realtimeResponseSerializationCpuTimeNs":0,"offlineTotalCpuTimeNs":0,"realtimeTotalCpuTimeNs":0,"brokerReduceTimeMs":3,"partialResult":false,"maxRowsInOperator":130,"numRowsResultSet":6,"exceptionsSize":0,"numSegmentsPrunedByBroker":0,"minConsumingFreshnessTimeMs":1716276514329,"numSegmentsPrunedByServer":0,"numSegmentsPrunedInvalid":0,"numSegmentsPrunedByLimit":0,"numSegmentsPrunedByValue":0,"explainPlanNumEmptyFilterSegments":0,"explainPlanNumMatchAllFilterSegments":0} +{"resultTable":{"dataSchema":{"columnNames":["stringDimSV2","EXPR$1"],"columnDataTypes":["STRING","DOUBLE"]},"rows":[["s2-6",14153.4],["s2-2",618.8],["s2-0",338.8],["s2-4",168.7],["s2-7",1572.6],["null",0.0]]},"requestId":"11345778000000002","stageStats":{"type":"MAILBOX_RECEIVE","executionTimeMs":2,"emittedRows":6,"fanIn":1,"rawMessages":2,"deserializedBytes":826,"upstreamWaitMs":3,"children":[{"type":"MAILBOX_SEND","executionTimeMs":4,"emittedRows":6,"stage":1,"parallelism":1,"fanOut":1,"rawMessages":2,"serializedBytes":247,"children":[{"type":"AGGREGATE","executionTimeMs":3,"emittedRows":6,"children":[{"type":"MAILBOX_RECEIVE","executionTimeMs":3,"emittedRows":6,"fanIn":1,"inMemoryMessages":2,"upstreamWaitMs":4,"children":[{"type":"MAILBOX_SEND","executionTimeMs":2,"emittedRows":6,"stage":2,"parallelism":1,"fanOut":1,"inMemoryMessages":2,"children":[{"type":"AGGREGATE","executionTimeMs":2,"emittedRows":6,"children":[{"type":"HASH_JOIN","executionTimeMs":1,"emittedRows":130,"timeBuildingHashTableMs":1,"children":[{"type":"MAILBOX_RECEIVE","emittedRows":10,"fanIn":1,"inMemoryMessages":2,"upstreamWaitMs":2,"children":[{"type":"MAILBOX_SEND","emittedRows":10,"stage":3,"parallelism":1,"fanOut":1,"inMemoryMessages":2,"children":[{"type":"LEAF","table":"FeatureTest1","emittedRows":10,"numDocsScanned":10,"numEntriesScannedPostFilter":30,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"totalDocs":10}]}]},{"type":"MAILBOX_RECEIVE","executionTimeMs":1,"emittedRows":66,"fanIn":1,"inMemoryMessages":3,"upstreamWaitMs":2,"children":[{"type":"MAILBOX_SEND","emittedRows":66,"stage":4,"parallelism":1,"fanOut":1,"inMemoryMessages":3,"children":[{"type":"LEAF","table":"FeatureTest2","emittedRows":66,"numDocsScanned":66,"numEntriesScannedPostFilter":66,"numSegmentsQueried":2,"numSegmentsProcessed":2,"numSegmentsMatched":2,"numConsumingSegmentsQueried":1,"minConsumingFreshnessTimeMs":1716276645639,"totalDocs":66}]}]}]}]}]}]}]}]}]},"brokerId":"Broker_192.168.29.25_8099","exceptions":[],"numServersQueried":0,"numServersResponded":0,"numSegmentsQueried":3,"numSegmentsProcessed":3,"numSegmentsMatched":3,"numConsumingSegmentsQueried":1,"numConsumingSegmentsProcessed":0,"numConsumingSegmentsMatched":0,"numDocsScanned":76,"numEntriesScannedInFilter":0,"numEntriesScannedPostFilter":96,"numGroupsLimitReached":false,"maxRowsInJoinReached":false,"totalDocs":76,"timeUsedMs":21,"offlineThreadCpuTimeNs":0,"realtimeThreadCpuTimeNs":0,"offlineSystemActivitiesCpuTimeNs":0,"realtimeSystemActivitiesCpuTimeNs":0,"offlineResponseSerializationCpuTimeNs":0,"realtimeResponseSerializationCpuTimeNs":0,"offlineTotalCpuTimeNs":0,"realtimeTotalCpuTimeNs":0,"brokerReduceTimeMs":3,"partialResult":false,"exceptionsSize":0,"numRowsResultSet":6,"maxRowsInOperator":130,"numSegmentsPrunedByBroker":0,"minConsumingFreshnessTimeMs":1716276645639,"numSegmentsPrunedByServer":0,"numSegmentsPrunedInvalid":0,"numSegmentsPrunedByLimit":0,"numSegmentsPrunedByValue":0,"explainPlanNumEmptyFilterSegments":0,"explainPlanNumMatchAllFilterSegments":0} +{"resultTable":{"dataSchema":{"columnNames":["stringDimSV1"],"columnDataTypes":["STRING"]},"rows":[]},"requestId":"11345778000000003","stageStats":{"type":"MAILBOX_RECEIVE","fanIn":1,"rawMessages":1,"deserializedBytes":132,"children":[{"type":"MAILBOX_SEND","stage":1,"parallelism":1,"fanOut":1,"rawMessages":1,"children":[{"type":"LEAF","table":"FeatureTest1","numSegmentsQueried":1,"totalDocs":10,"numSegmentsPrunedByServer":1}]}]},"brokerId":"Broker_192.168.29.25_8099","exceptions":[],"numServersQueried":0,"numServersResponded":0,"numSegmentsQueried":1,"numSegmentsProcessed":0,"numSegmentsMatched":0,"numConsumingSegmentsQueried":0,"numConsumingSegmentsProcessed":0,"numConsumingSegmentsMatched":0,"numDocsScanned":0,"numEntriesScannedInFilter":0,"numEntriesScannedPostFilter":0,"numGroupsLimitReached":false,"maxRowsInJoinReached":false,"totalDocs":10,"timeUsedMs":43,"offlineThreadCpuTimeNs":0,"realtimeThreadCpuTimeNs":0,"offlineSystemActivitiesCpuTimeNs":0,"realtimeSystemActivitiesCpuTimeNs":0,"offlineResponseSerializationCpuTimeNs":0,"realtimeResponseSerializationCpuTimeNs":0,"offlineTotalCpuTimeNs":0,"realtimeTotalCpuTimeNs":0,"brokerReduceTimeMs":0,"partialResult":false,"maxRowsInOperator":0,"exceptionsSize":0,"numRowsResultSet":0,"numSegmentsPrunedByBroker":0,"minConsumingFreshnessTimeMs":0,"numSegmentsPrunedByServer":1,"numSegmentsPrunedInvalid":0,"numSegmentsPrunedByLimit":0,"numSegmentsPrunedByValue":0,"explainPlanNumEmptyFilterSegments":0,"explainPlanNumMatchAllFilterSegments":0} + +# Set operations +{"resultTable":{"dataSchema":{"columnNames":["stringDimSV1"],"columnDataTypes":["STRING"]},"rows":[["s1-0"],["s1-2"],["s1-4"],["s1-5"],["s1-6"],["s1-7"]]},"requestId":"11345778000000004","stageStats":{"type":"MAILBOX_RECEIVE","executionTimeMs":1,"emittedRows":6,"fanIn":1,"rawMessages":2,"deserializedBytes":628,"upstreamWaitMs":2,"children":[{"type":"MAILBOX_SEND","executionTimeMs":1,"emittedRows":6,"stage":1,"parallelism":1,"fanOut":1,"rawMessages":2,"serializedBytes":174,"children":[{"children":[{"type":"MAILBOX_RECEIVE","emittedRows":6,"children":[{"type":"MAILBOX_SEND","emittedRows":10,"stage":2,"parallelism":1,"fanOut":1,"inMemoryMessages":2,"children":[{"type":"LEAF","table":"FeatureTest1","emittedRows":10,"numDocsScanned":10,"numEntriesScannedPostFilter":10,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"totalDocs":10}]}]},{"type":"MAILBOX_RECEIVE","emittedRows":10,"fanIn":1,"inMemoryMessages":2,"upstreamWaitMs":1,"children":[{"type":"MAILBOX_SEND","emittedRows":66,"stage":3,"parallelism":1,"fanOut":1,"inMemoryMessages":3,"children":[{"type":"LEAF","table":"FeatureTest2","emittedRows":66,"numDocsScanned":66,"numEntriesScannedPostFilter":66,"numSegmentsQueried":2,"numSegmentsProcessed":2,"numSegmentsMatched":2,"numConsumingSegmentsQueried":1,"minConsumingFreshnessTimeMs":1716276920346,"totalDocs":66}]}]}]}]}]},"brokerId":"Broker_192.168.29.25_8099","exceptions":[],"numServersQueried":0,"numServersResponded":0,"numSegmentsQueried":3,"numSegmentsProcessed":3,"numSegmentsMatched":3,"numConsumingSegmentsQueried":1,"numConsumingSegmentsProcessed":0,"numConsumingSegmentsMatched":0,"numDocsScanned":76,"numEntriesScannedInFilter":0,"numEntriesScannedPostFilter":76,"numGroupsLimitReached":false,"maxRowsInJoinReached":false,"totalDocs":76,"timeUsedMs":35,"offlineThreadCpuTimeNs":0,"realtimeThreadCpuTimeNs":0,"offlineSystemActivitiesCpuTimeNs":0,"realtimeSystemActivitiesCpuTimeNs":0,"offlineResponseSerializationCpuTimeNs":0,"realtimeResponseSerializationCpuTimeNs":0,"offlineTotalCpuTimeNs":0,"realtimeTotalCpuTimeNs":0,"brokerReduceTimeMs":2,"partialResult":false,"numSegmentsPrunedByBroker":0,"minConsumingFreshnessTimeMs":1716276920346,"numSegmentsPrunedByServer":0,"numSegmentsPrunedInvalid":0,"numSegmentsPrunedByLimit":0,"numSegmentsPrunedByValue":0,"explainPlanNumEmptyFilterSegments":0,"explainPlanNumMatchAllFilterSegments":0,"numRowsResultSet":6,"exceptionsSize":0,"maxRowsInOperator":66} +{"resultTable":{"dataSchema":{"columnNames":["stringDimSV1"],"columnDataTypes":["STRING"]},"rows":[["s1-4"],["s1-6"],["s1-2"],["s1-0"],["s1-5"],["s1-7"]]},"requestId":"11345778000000006","stageStats":{"type":"MAILBOX_RECEIVE","emittedRows":6,"fanIn":1,"rawMessages":2,"deserializedBytes":744,"upstreamWaitMs":1,"children":[{"type":"MAILBOX_SEND","executionTimeMs":2,"emittedRows":6,"stage":1,"parallelism":1,"fanOut":1,"rawMessages":2,"serializedBytes":174,"children":[{"type":"AGGREGATE","executionTimeMs":2,"emittedRows":6,"children":[{"type":"MAILBOX_RECEIVE","executionTimeMs":2,"emittedRows":6,"fanIn":1,"inMemoryMessages":2,"upstreamWaitMs":3,"children":[{"type":"MAILBOX_SEND","executionTimeMs":1,"emittedRows":6,"stage":2,"parallelism":1,"fanOut":1,"inMemoryMessages":2,"children":[{"type":"AGGREGATE","executionTimeMs":1,"emittedRows":6,"children":[{"type":"UNION","executionTimeMs":1,"emittedRows":76,"children":[{"type":"MAILBOX_RECEIVE","executionTimeMs":1,"emittedRows":10,"fanIn":1,"inMemoryMessages":2,"upstreamWaitMs":1,"children":[{"type":"MAILBOX_SEND","emittedRows":10,"stage":3,"parallelism":1,"fanOut":1,"inMemoryMessages":2,"children":[{"type":"LEAF","table":"FeatureTest1","emittedRows":10,"numDocsScanned":10,"numEntriesScannedPostFilter":10,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"totalDocs":10}]}]},{"type":"MAILBOX_RECEIVE","emittedRows":66,"fanIn":1,"inMemoryMessages":3,"upstreamWaitMs":1,"children":[{"type":"MAILBOX_SEND","emittedRows":66,"stage":4,"parallelism":1,"fanOut":1,"inMemoryMessages":3,"children":[{"type":"LEAF","table":"FeatureTest2","emittedRows":66,"numDocsScanned":66,"numEntriesScannedPostFilter":66,"numSegmentsQueried":2,"numSegmentsProcessed":2,"numSegmentsMatched":2,"numConsumingSegmentsQueried":1,"minConsumingFreshnessTimeMs":1716276920346,"totalDocs":66}]}]}]}]}]}]}]}]}]},"brokerId":"Broker_192.168.29.25_8099","exceptions":[],"numServersQueried":0,"numServersResponded":0,"numSegmentsQueried":3,"numSegmentsProcessed":3,"numSegmentsMatched":3,"numConsumingSegmentsQueried":1,"numConsumingSegmentsProcessed":0,"numConsumingSegmentsMatched":0,"numDocsScanned":76,"numEntriesScannedInFilter":0,"numEntriesScannedPostFilter":76,"numGroupsLimitReached":false,"maxRowsInJoinReached":false,"totalDocs":76,"timeUsedMs":13,"offlineThreadCpuTimeNs":0,"realtimeThreadCpuTimeNs":0,"offlineSystemActivitiesCpuTimeNs":0,"realtimeSystemActivitiesCpuTimeNs":0,"offlineResponseSerializationCpuTimeNs":0,"realtimeResponseSerializationCpuTimeNs":0,"offlineTotalCpuTimeNs":0,"realtimeTotalCpuTimeNs":0,"brokerReduceTimeMs":1,"partialResult":false,"numSegmentsPrunedByBroker":0,"minConsumingFreshnessTimeMs":1716276920346,"numSegmentsPrunedByServer":0,"numSegmentsPrunedInvalid":0,"numSegmentsPrunedByLimit":0,"numSegmentsPrunedByValue":0,"explainPlanNumEmptyFilterSegments":0,"explainPlanNumMatchAllFilterSegments":0,"numRowsResultSet":6,"exceptionsSize":0,"maxRowsInOperator":76} + +# Windows +{"resultTable":{"dataSchema":{"columnNames":["stringDimSV1","longMetric1","EXPR$2"],"columnDataTypes":["STRING","LONG","LONG"]},"rows":[["s1-5",24,24],["s1-4",24,24],["s1-7",251,251],["s1-6",251,753],["s1-6",251,753],["s1-6",251,753],["s1-0",11,22],["s1-0",11,22],["s1-2",21,42],["s1-2",21,42]]},"partialResult":false,"exceptions":[],"numGroupsLimitReached":false,"maxRowsInJoinReached":false,"timeUsedMs":34,"stageStats":{"type":"MAILBOX_RECEIVE","executionTimeMs":3,"emittedRows":10,"fanIn":1,"rawMessages":2,"deserializedBytes":717,"upstreamWaitMs":1,"children":[{"type":"MAILBOX_SEND","executionTimeMs":1,"emittedRows":10,"stage":1,"parallelism":1,"fanOut":1,"rawMessages":2,"serializedBytes":407,"children":[{"type":"TRANSFORM","executionTimeMs":1,"emittedRows":10,"children":[{"type":"WINDOW","executionTimeMs":1,"emittedRows":10,"children":[{"type":"MAILBOX_RECEIVE","emittedRows":10,"fanIn":1,"inMemoryMessages":2,"upstreamWaitMs":2,"children":[{"type":"MAILBOX_SEND","emittedRows":10,"stage":2,"parallelism":1,"fanOut":1,"inMemoryMessages":2,"children":[{"type":"LEAF","table":"FeatureTest1","executionTimeMs":1,"emittedRows":10,"numDocsScanned":10,"totalDocs":10,"numEntriesScannedPostFilter":20,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1}]}]}]}]}]}]},"maxRowsInOperator":10,"requestId":"11345778000000006","brokerId":"Broker_192.168.29.25_8099","numDocsScanned":10,"totalDocs":10,"numEntriesScannedInFilter":0,"numEntriesScannedPostFilter":20,"numServersQueried":0,"numServersResponded":0,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numConsumingSegmentsQueried":0,"numConsumingSegmentsProcessed":0,"numConsumingSegmentsMatched":0,"minConsumingFreshnessTimeMs":0,"numSegmentsPrunedByBroker":0,"numSegmentsPrunedByServer":0,"numSegmentsPrunedInvalid":0,"numSegmentsPrunedByLimit":0,"numSegmentsPrunedByValue":0,"brokerReduceTimeMs":3,"offlineThreadCpuTimeNs":0,"realtimeThreadCpuTimeNs":0,"offlineSystemActivitiesCpuTimeNs":0,"realtimeSystemActivitiesCpuTimeNs":0,"offlineResponseSerializationCpuTimeNs":0,"realtimeResponseSerializationCpuTimeNs":0,"offlineTotalCpuTimeNs":0,"realtimeTotalCpuTimeNs":0,"explainPlanNumEmptyFilterSegments":0,"explainPlanNumMatchAllFilterSegments":0,"traceInfo":{}} diff --git a/compatibility-verifier/multi-stage-query-engine-test-suite/post-broker-rollback.yaml b/compatibility-verifier/multi-stage-query-engine-test-suite/post-broker-rollback.yaml new file mode 100644 index 000000000000..c03834c943b5 --- /dev/null +++ b/compatibility-verifier/multi-stage-query-engine-test-suite/post-broker-rollback.yaml @@ -0,0 +1,43 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Operations to be done. +description: Operations to be run after broker rollback +operations: + - type: segmentOp + description: Add segment FeatureTest1_Segment8 to table FeatureTest1 + op: UPLOAD + inputDataFileName: data/FeatureTest1-data-00.csv + schemaFileName: FeatureTest1-schema.json + tableConfigFileName: feature-test-1.json + recordReaderConfigFileName: data/recordReaderConfig.json + segmentName: FeatureTest1_Segment8 + - type: streamOp + description: publish rows to PinotRealtimeFeatureTest2Event + op: PRODUCE + streamConfigFileName: feature-test-2-realtime-stream-config.json + numRows: 66 + inputDataFileName: data/FeatureTest2-data-realtime-00.csv + recordReaderConfigFileName: data/recordReaderConfig.json + tableConfigFileName: feature-test-2-realtime.json + - type: queryOp + description: Run multi-stage queries on FeatureTest1 and FeatureTest2 using SQL + useMultiStageQueryEngine: true + queryFileName: queries/feature-test-multi-stage.queries + expectedResultsFileName: query-results/feature-test-multi-stage.results diff --git a/compatibility-verifier/multi-stage-query-engine-test-suite/post-controller-rollback.yaml b/compatibility-verifier/multi-stage-query-engine-test-suite/post-controller-rollback.yaml new file mode 100644 index 000000000000..572bb9bce388 --- /dev/null +++ b/compatibility-verifier/multi-stage-query-engine-test-suite/post-controller-rollback.yaml @@ -0,0 +1,53 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Operations to be done. +description: Operations to be run after controller rollback +operations: + - type: segmentOp + description: Add segment FeatureTest1_Segment9 to table FeatureTest1 + op: UPLOAD + inputDataFileName: data/FeatureTest1-data-00.csv + schemaFileName: FeatureTest1-schema.json + tableConfigFileName: feature-test-1.json + recordReaderConfigFileName: data/recordReaderConfig.json + segmentName: FeatureTest1_Segment9 + - type: streamOp + description: publish rows to PinotRealtimeFeatureTest2Event + op: PRODUCE + streamConfigFileName: feature-test-2-realtime-stream-config.json + numRows: 66 + inputDataFileName: data/FeatureTest2-data-realtime-00.csv + recordReaderConfigFileName: data/recordReaderConfig.json + tableConfigFileName: feature-test-2-realtime.json + - type: queryOp + description: Run multi-stage queries on FeatureTest1 and FeatureTest2 using SQL + useMultiStageQueryEngine: true + queryFileName: queries/feature-test-multi-stage.queries + expectedResultsFileName: query-results/feature-test-multi-stage.results + - type: segmentOp + description: Delete segment FeatureTest1_Segment + op: DELETE + tableConfigFileName: feature-test-1.json + segmentName: FeatureTest1_Segment + - type: tableOp + description: Delete table feature-test-1.json + op: DELETE + schemaFileName: FeatureTest1-schema.json + tableConfigFileName: feature-test-1.json diff --git a/compatibility-verifier/multi-stage-query-engine-test-suite/post-server-2-rollback.yaml b/compatibility-verifier/multi-stage-query-engine-test-suite/post-server-2-rollback.yaml new file mode 100644 index 000000000000..5df517c9a3ac --- /dev/null +++ b/compatibility-verifier/multi-stage-query-engine-test-suite/post-server-2-rollback.yaml @@ -0,0 +1,43 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Operations to be done. +description: Operations to be run after server rollback +operations: + - type: segmentOp + description: Add segment FeatureTest1_Segment6 to table FeatureTest1 + op: UPLOAD + inputDataFileName: data/FeatureTest1-data-00.csv + schemaFileName: FeatureTest1-schema.json + tableConfigFileName: feature-test-1.json + recordReaderConfigFileName: data/recordReaderConfig.json + segmentName: FeatureTest1_Segment6 + - type: streamOp + description: publish rows to PinotRealtimeFeatureTest2Event + op: PRODUCE + streamConfigFileName: feature-test-2-realtime-stream-config.json + numRows: 66 + inputDataFileName: data/FeatureTest2-data-realtime-00.csv + recordReaderConfigFileName: data/recordReaderConfig.json + tableConfigFileName: feature-test-2-realtime.json + - type: queryOp + description: Run multi-stage queries on FeatureTest1 and FeatureTest2 using SQL + useMultiStageQueryEngine: true + queryFileName: queries/feature-test-multi-stage.queries + expectedResultsFileName: query-results/feature-test-multi-stage.results diff --git a/compatibility-verifier/multi-stage-query-engine-test-suite/post-server-2-upgrade.yaml b/compatibility-verifier/multi-stage-query-engine-test-suite/post-server-2-upgrade.yaml new file mode 100644 index 000000000000..6dbb916845cd --- /dev/null +++ b/compatibility-verifier/multi-stage-query-engine-test-suite/post-server-2-upgrade.yaml @@ -0,0 +1,43 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Operations to be done. +description: Operations to be run after server upgrade +operations: + - type: segmentOp + description: Add segment FeatureTest1_Segment5 to table FeatureTest1 + op: UPLOAD + inputDataFileName: data/FeatureTest1-data-00.csv + schemaFileName: FeatureTest1-schema.json + tableConfigFileName: feature-test-1.json + recordReaderConfigFileName: data/recordReaderConfig.json + segmentName: FeatureTest1_Segment5 + - type: streamOp + description: publish rows to PinotRealtimeFeatureTest2Event + op: PRODUCE + streamConfigFileName: feature-test-2-realtime-stream-config.json + numRows: 66 + inputDataFileName: data/FeatureTest2-data-realtime-00.csv + recordReaderConfigFileName: data/recordReaderConfig.json + tableConfigFileName: feature-test-2-realtime.json + - type: queryOp + description: Run multi-stage queries on FeatureTest1 and FeatureTest2 using SQL + useMultiStageQueryEngine: true + queryFileName: queries/feature-test-multi-stage.queries + expectedResultsFileName: query-results/feature-test-multi-stage.results diff --git a/compatibility-verifier/multi-stage-query-engine-test-suite/post-server-rollback.yaml b/compatibility-verifier/multi-stage-query-engine-test-suite/post-server-rollback.yaml new file mode 100644 index 000000000000..6417eef1cd72 --- /dev/null +++ b/compatibility-verifier/multi-stage-query-engine-test-suite/post-server-rollback.yaml @@ -0,0 +1,43 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Operations to be done. +description: Operations to be run after server rollback +operations: + - type: segmentOp + description: Add segment FeatureTest1_Segment7 to table FeatureTest1 + op: UPLOAD + inputDataFileName: data/FeatureTest1-data-00.csv + schemaFileName: FeatureTest1-schema.json + tableConfigFileName: feature-test-1.json + recordReaderConfigFileName: data/recordReaderConfig.json + segmentName: FeatureTest1_Segment7 + - type: streamOp + description: publish rows to PinotRealtimeFeatureTest2Event + op: PRODUCE + streamConfigFileName: feature-test-2-realtime-stream-config.json + numRows: 66 + inputDataFileName: data/FeatureTest2-data-realtime-00.csv + recordReaderConfigFileName: data/recordReaderConfig.json + tableConfigFileName: feature-test-2-realtime.json + - type: queryOp + description: Run multi-stage queries on FeatureTest1 and FeatureTest2 using SQL + useMultiStageQueryEngine: true + queryFileName: queries/feature-test-multi-stage.queries + expectedResultsFileName: query-results/feature-test-multi-stage.results diff --git a/compatibility-verifier/multi-stage-query-engine-test-suite/post-server-upgrade.yaml b/compatibility-verifier/multi-stage-query-engine-test-suite/post-server-upgrade.yaml new file mode 100644 index 000000000000..786cb3fb3981 --- /dev/null +++ b/compatibility-verifier/multi-stage-query-engine-test-suite/post-server-upgrade.yaml @@ -0,0 +1,43 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Operations to be done. +description: Operations to be run after server upgrade +operations: + - type: segmentOp + description: Add segment FeatureTest1_Segment4 to table FeatureTest1 + op: UPLOAD + inputDataFileName: data/FeatureTest1-data-00.csv + schemaFileName: FeatureTest1-schema.json + tableConfigFileName: feature-test-1.json + recordReaderConfigFileName: data/recordReaderConfig.json + segmentName: FeatureTest1_Segment4 + - type: streamOp + description: publish rows to PinotRealtimeFeatureTest2Event + op: PRODUCE + streamConfigFileName: feature-test-2-realtime-stream-config.json + numRows: 66 + inputDataFileName: data/FeatureTest2-data-realtime-00.csv + recordReaderConfigFileName: data/recordReaderConfig.json + tableConfigFileName: feature-test-2-realtime.json + - type: queryOp + description: Run multi-stage queries on FeatureTest1 and FeatureTest2 using SQL + useMultiStageQueryEngine: true + queryFileName: queries/feature-test-multi-stage.queries + expectedResultsFileName: query-results/feature-test-multi-stage.results diff --git a/compatibility-verifier/multi-stage-query-engine-test-suite/pre-broker-upgrade.yaml b/compatibility-verifier/multi-stage-query-engine-test-suite/pre-broker-upgrade.yaml new file mode 100644 index 000000000000..4e255b8cf8a8 --- /dev/null +++ b/compatibility-verifier/multi-stage-query-engine-test-suite/pre-broker-upgrade.yaml @@ -0,0 +1,43 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Operations to be done. +description: Operations to be run before Broker upgrade +operations: + - type: segmentOp + description: Add segment FeatureTest1_Segment2 to table FeatureTest1 + op: UPLOAD + inputDataFileName: data/FeatureTest1-data-00.csv + schemaFileName: FeatureTest1-schema.json + tableConfigFileName: feature-test-1.json + recordReaderConfigFileName: data/recordReaderConfig.json + segmentName: FeatureTest1_Segment2 + - type: streamOp + description: publish rows to PinotRealtimeFeatureTest2Event + op: PRODUCE + streamConfigFileName: feature-test-2-realtime-stream-config.json + numRows: 66 + inputDataFileName: data/FeatureTest2-data-realtime-00.csv + recordReaderConfigFileName: data/recordReaderConfig.json + tableConfigFileName: feature-test-2-realtime.json + - type: queryOp + description: Run multi-stage queries on FeatureTest1 and FeatureTest2 using SQL + useMultiStageQueryEngine: true + queryFileName: queries/feature-test-multi-stage.queries + expectedResultsFileName: query-results/feature-test-multi-stage.results diff --git a/compatibility-verifier/multi-stage-query-engine-test-suite/pre-controller-upgrade.yaml b/compatibility-verifier/multi-stage-query-engine-test-suite/pre-controller-upgrade.yaml new file mode 100644 index 000000000000..39f8bcacdf09 --- /dev/null +++ b/compatibility-verifier/multi-stage-query-engine-test-suite/pre-controller-upgrade.yaml @@ -0,0 +1,58 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Operations to be done. +description: Operations to be run before Controller upgrade +operations: + - type: streamOp + description: create Kafka topic PinotRealtimeFeatureTest2Event + op: CREATE + streamConfigFileName: feature-test-2-realtime-stream-config.json + - type: tableOp + description: Create realtime table FeatureTest2 + op: CREATE + schemaFileName: FeatureTest2-schema.json + tableConfigFileName: feature-test-2-realtime.json + recordReaderConfigFileName: data/recordReaderConfig.json + - type: tableOp + description: Create offline table FeatureTest1 + op: CREATE + schemaFileName: FeatureTest1-schema.json + tableConfigFileName: feature-test-1.json + - type: segmentOp + description: Add segment FeatureTest1_Segment to table FeatureTest1 + op: UPLOAD + inputDataFileName: data/FeatureTest1-data-00.csv + schemaFileName: FeatureTest1-schema.json + tableConfigFileName: feature-test-1.json + recordReaderConfigFileName: data/recordReaderConfig.json + segmentName: FeatureTest1_Segment + - type: streamOp + description: publish rows to PinotRealtimeFeatureTest2Event + op: PRODUCE + streamConfigFileName: feature-test-2-realtime-stream-config.json + numRows: 66 + inputDataFileName: data/FeatureTest2-data-realtime-00.csv + recordReaderConfigFileName: data/recordReaderConfig.json + tableConfigFileName: feature-test-2-realtime.json + - type: queryOp + description: Run multi-stage queries on FeatureTest1 and FeatureTest2 using SQL + useMultiStageQueryEngine: true + queryFileName: queries/feature-test-multi-stage.queries + expectedResultsFileName: query-results/feature-test-multi-stage.results diff --git a/compatibility-verifier/multi-stage-query-engine-test-suite/pre-server-upgrade.yaml b/compatibility-verifier/multi-stage-query-engine-test-suite/pre-server-upgrade.yaml new file mode 100644 index 000000000000..167f71fadf2f --- /dev/null +++ b/compatibility-verifier/multi-stage-query-engine-test-suite/pre-server-upgrade.yaml @@ -0,0 +1,43 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Operations to be done. +description: Operations to be run before server upgrade +operations: + - type: segmentOp + description: Add segment FeatureTest1_Segment3 to table FeatureTest1 + op: UPLOAD + inputDataFileName: data/FeatureTest1-data-00.csv + schemaFileName: FeatureTest1-schema.json + tableConfigFileName: feature-test-1.json + recordReaderConfigFileName: data/recordReaderConfig.json + segmentName: FeatureTest1_Segment3 + - type: streamOp + description: publish rows to PinotRealtimeFeatureTest2Event + op: PRODUCE + streamConfigFileName: feature-test-2-realtime-stream-config.json + numRows: 66 + inputDataFileName: data/FeatureTest2-data-realtime-00.csv + recordReaderConfigFileName: data/recordReaderConfig.json + tableConfigFileName: feature-test-2-realtime.json + - type: queryOp + description: Run multi-stage queries on FeatureTest1 and FeatureTest2 using SQL + useMultiStageQueryEngine: true + queryFileName: queries/feature-test-multi-stage.queries + expectedResultsFileName: query-results/feature-test-multi-stage.results diff --git a/compatibility-verifier/sample-test-suite/config/queries/feature-test-1-sql.queries b/compatibility-verifier/sample-test-suite/config/queries/feature-test-1-sql.queries index 37a6120a5d3c..38b848424362 100644 --- a/compatibility-verifier/sample-test-suite/config/queries/feature-test-1-sql.queries +++ b/compatibility-verifier/sample-test-suite/config/queries/feature-test-1-sql.queries @@ -22,7 +22,7 @@ SELECT count(*) FROM FeatureTest1 WHERE generationNumber = __GENERATION_NUMBER__ SELECT sum(intMetric1), sumMV(intDimMV1), min(intMetric1), minMV(intDimMV2), max(longDimSV1), maxMV(intDimMV1) FROM FeatureTest1 WHERE generationNumber = __GENERATION_NUMBER__ SELECT count(longDimSV1), countMV(intDimMV1), avg(floatMetric1), avgMV(intDimMV2), minMaxRange(doubleMetric1), minMaxRangeMV(intDimMV2) FROM FeatureTest1 WHERE generationNumber = __GENERATION_NUMBER__ SELECT percentile(longDimSV1, 80), percentileMV(intDimMV1, 90), percentileEst(longDimSV1, 80), percentileEstMV(intDimMV1, 90), percentileTDigest(longDimSV1, 80), percentileTDigestMV(intDimMV1, 90) FROM FeatureTest1 WHERE generationNumber = __GENERATION_NUMBER__ -SELECT distinctCount(longDimSV1), distinctCountMV(intDimMV1), distinctCountHLL(longDimSV1), distinctCountHLLMV(intDimMV1) FROM FeatureTest1 WHERE generationNumber = __GENERATION_NUMBER__ +SELECT distinctCount(longDimSV1), distinctCountMV(intDimMV1), distinctCountHLL(longDimSV1), distinctCountHLLMV(intDimMV1), distinctCountThetaSketch(longDimSV1) FROM FeatureTest1 WHERE generationNumber = __GENERATION_NUMBER__ # Selection SELECT longDimSV2, stringDimSV1, textDim1, bytesDimSV1 FROM FeatureTest1 WHERE generationNumber = __GENERATION_NUMBER__ ORDER BY longDimSV2 LIMIT 9 @@ -46,14 +46,14 @@ SELECT longDimSV1, intDimMV1, count(*) FROM FeatureTest1 WHERE generationNumber SELECT longDimSV1, intDimMV1, sum(intMetric1), sumMV(intDimMV1), min(intMetric1), minMV(intDimMV2), max(longDimSV1), maxMV(intDimMV1) FROM FeatureTest1 WHERE generationNumber = __GENERATION_NUMBER__ GROUP BY longDimSV1, intDimMV1 ORDER BY longDimSV1 LIMIT 5 SELECT longDimSV1, intDimMV1, count(longDimSV1), countMV(intDimMV1), avg(floatMetric1), avgMV(intDimMV2), minMaxRange(doubleMetric1), minMaxRangeMV(intDimMV2) FROM FeatureTest1 WHERE generationNumber = __GENERATION_NUMBER__ GROUP BY longDimSV1, intDimMV1 ORDER BY longDimSV1 LIMIT 5 SELECT longDimSV1, intDimMV1, percentile(longDimSV1, 80), percentileMV(intDimMV1, 90), percentileEst(longDimSV1, 80), percentileEstMV(intDimMV1, 90), percentileTDigest(longDimSV1, 80), percentileTDigestMV(intDimMV1, 90) FROM FeatureTest1 WHERE generationNumber = __GENERATION_NUMBER__ GROUP BY longDimSV1, intDimMV1 ORDER BY longDimSV1 LIMIT 5 -SELECT longDimSV1, intDimMV1, distinctCount(longDimSV1), distinctCountMV(intDimMV1), distinctCountHLL(longDimSV1), distinctCountHLLMV(intDimMV1) FROM FeatureTest1 WHERE generationNumber = __GENERATION_NUMBER__ GROUP BY longDimSV1, intDimMV1 ORDER BY longDimSV1 LIMIT 5 +SELECT longDimSV1, intDimMV1, distinctCount(longDimSV1), distinctCountMV(intDimMV1), distinctCountHLL(longDimSV1), distinctCountHLLMV(intDimMV1), distinctCountThetaSketch(longDimSV1) FROM FeatureTest1 WHERE generationNumber = __GENERATION_NUMBER__ GROUP BY longDimSV1, intDimMV1 ORDER BY longDimSV1 LIMIT 5 # Selection & Filtering & Grouping on Aggregation SELECT longDimSV1, intDimMV1, count(*) FROM FeatureTest1 WHERE generationNumber = __GENERATION_NUMBER__ AND (stringDimSV1 != 's1-6' AND longDimSV1 BETWEEN 10 AND 1000 OR (intDimMV1 < 42 AND stringDimMV2 IN ('m2-0-0', 'm2-2-0') AND intDimMV2 NOT IN (6,72))) GROUP BY longDimSV1, intDimMV1 ORDER BY longDimSV1, intDimMV1 LIMIT 5 SELECT longDimSV1, intDimMV1, sum(intMetric1), sumMV(intDimMV1), min(intMetric1), minMV(intDimMV2), max(longDimSV1), maxMV(intDimMV1) FROM FeatureTest1 WHERE generationNumber = __GENERATION_NUMBER__ AND (stringDimSV1 != 's1-6' AND longDimSV1 BETWEEN 10 AND 1000 OR (intDimMV1 < 42 AND stringDimMV2 IN ('m2-0-0', 'm2-2-0') AND intDimMV2 NOT IN (6,72))) GROUP BY longDimSV1, intDimMV1 ORDER BY longDimSV1, intDimMV1 LIMIT 5 SELECT longDimSV1, intDimMV1, count(longDimSV1), countMV(intDimMV1), avg(floatMetric1), avgMV(intDimMV2), minMaxRange(doubleMetric1), minMaxRangeMV(intDimMV2) FROM FeatureTest1 WHERE generationNumber = __GENERATION_NUMBER__ AND (stringDimSV1 != 's1-6' AND longDimSV1 BETWEEN 10 AND 1000 OR (intDimMV1 < 42 AND stringDimMV2 IN ('m2-0-0', 'm2-2-0') AND intDimMV2 NOT IN (6,72))) GROUP BY longDimSV1, intDimMV1 ORDER BY longDimSV1, intDimMV1 LIMIT 5 SELECT longDimSV1, intDimMV1, percentile(longDimSV1, 80), percentileMV(intDimMV1, 90), percentileEst(longDimSV1, 80), percentileEstMV(intDimMV1, 90), percentileTDigest(longDimSV1, 80), percentileTDigestMV(intDimMV1, 90) FROM FeatureTest1 WHERE generationNumber = __GENERATION_NUMBER__ AND (stringDimSV1 != 's1-6' AND longDimSV1 BETWEEN 10 AND 1000 OR (intDimMV1 < 42 AND stringDimMV2 IN ('m2-0-0', 'm2-2-0') AND intDimMV2 NOT IN (6,72))) GROUP BY longDimSV1, intDimMV1 ORDER BY longDimSV1, intDimMV1 LIMIT 5 -SELECT longDimSV1, intDimMV1, distinctCount(longDimSV1), distinctCountMV(intDimMV1), distinctCountHLL(longDimSV1), distinctCountHLLMV(intDimMV1) FROM FeatureTest1 WHERE generationNumber = __GENERATION_NUMBER__ AND (stringDimSV1 != 's1-6' AND longDimSV1 BETWEEN 10 AND 1000 OR (intDimMV1 < 42 AND stringDimMV2 IN ('m2-0-0', 'm2-2-0') AND intDimMV2 NOT IN (6,72))) GROUP BY longDimSV1, intDimMV1 ORDER BY longDimSV1, intDimMV1 LIMIT 5 +SELECT longDimSV1, intDimMV1, distinctCount(longDimSV1), distinctCountMV(intDimMV1), distinctCountHLL(longDimSV1), distinctCountHLLMV(intDimMV1), distinctCountThetaSketch(longDimSV1) FROM FeatureTest1 WHERE generationNumber = __GENERATION_NUMBER__ AND (stringDimSV1 != 's1-6' AND longDimSV1 BETWEEN 10 AND 1000 OR (intDimMV1 < 42 AND stringDimMV2 IN ('m2-0-0', 'm2-2-0') AND intDimMV2 NOT IN (6,72))) GROUP BY longDimSV1, intDimMV1 ORDER BY longDimSV1, intDimMV1 LIMIT 5 # Transformation Functions SELECT add(longDimSV1, sub(longDimSV2, 3)), mod(intMetric1, 10), div(doubleMetric1, mult(floatMetric1, 5)) FROM FeatureTest1 WHERE generationNumber = __GENERATION_NUMBER__ ORDER BY add(longDimSV1, sub(longDimSV2, 3)) DESC, mod(intMetric1, 10) diff --git a/compatibility-verifier/sample-test-suite/config/queries/feature-test-2-sql-realtime.queries b/compatibility-verifier/sample-test-suite/config/queries/feature-test-2-sql-realtime.queries index da9c43d7adbd..362720553426 100644 --- a/compatibility-verifier/sample-test-suite/config/queries/feature-test-2-sql-realtime.queries +++ b/compatibility-verifier/sample-test-suite/config/queries/feature-test-2-sql-realtime.queries @@ -32,7 +32,7 @@ SELECT sum(intMetric1), sumMV(intDimMV1), min(intMetric1), minMV(intDimMV2), max SELECT count(longDimSV1), countMV(intDimMV1), avg(floatMetric1), avgMV(intDimMV2), minMaxRange(doubleMetric1), minMaxRangeMV(intDimMV2) FROM FeatureTest2 WHERE generationNumber = __GENERATION_NUMBER__ SELECT percentile(longDimSV1, 80), percentileMV(intDimMV1, 90), percentileEst(longDimSV1, 80), percentileEstMV(intDimMV1, 90), percentileTDigest(longDimSV1, 80), percentileTDigestMV(intDimMV1, 90) FROM FeatureTest2 WHERE generationNumber = __GENERATION_NUMBER__ SELECT percentile(longDimSV1, 80.01), percentileMV(intDimMV1, 99.99), percentileEst(longDimSV1, 80.01), percentileEstMV(intDimMV1, 99.99), percentileTDigest(longDimSV1, 80.01), percentileTDigestMV(intDimMV1, 99.99) FROM FeatureTest2 WHERE generationNumber = __GENERATION_NUMBER__ -SELECT distinctCount(longDimSV1), distinctCountMV(intDimMV1), distinctCountHLL(longDimSV1), distinctCountHLLMV(intDimMV1) FROM FeatureTest2 WHERE generationNumber = __GENERATION_NUMBER__ +SELECT distinctCount(longDimSV1), distinctCountMV(intDimMV1), distinctCountHLL(longDimSV1), distinctCountHLLMV(intDimMV1), distinctCountThetaSketch(longDimSV1) FROM FeatureTest2 WHERE generationNumber = __GENERATION_NUMBER__ # Selection & Filtering & Grouping on Aggregation SELECT longDimSV1, intDimMV1, count(*) FROM FeatureTest2 WHERE generationNumber = __GENERATION_NUMBER__ AND (stringDimSV1 != 's1-6' AND longDimSV1 BETWEEN 10 AND 1000 OR (intDimMV1 < 42 AND stringDimMV2 IN ('m2-0-0', 'm2-2-0') AND intDimMV2 NOT IN (6,72))) GROUP BY longDimSV1, intDimMV1 ORDER BY longDimSV1, intDimMV1 LIMIT 20 @@ -40,7 +40,7 @@ SELECT longDimSV1, intDimMV1, sum(intMetric1), sumMV(intDimMV1), min(intMetric1) SELECT longDimSV1, intDimMV1, count(longDimSV1), countMV(intDimMV1), avg(floatMetric1), avgMV(intDimMV2), minMaxRange(doubleMetric1), minMaxRangeMV(intDimMV2) FROM FeatureTest2 WHERE generationNumber = __GENERATION_NUMBER__ AND (stringDimSV1 != 's1-6' AND longDimSV1 BETWEEN 10 AND 1000 OR (intDimMV1 < 42 AND stringDimMV2 IN ('m2-0-0', 'm2-2-0') AND intDimMV2 NOT IN (6,72))) GROUP BY longDimSV1, intDimMV1 ORDER BY longDimSV1, intDimMV1 LIMIT 20 SELECT longDimSV1, intDimMV1, percentile(longDimSV1, 80), percentileMV(intDimMV1, 90), percentileEst(longDimSV1, 80), percentileEstMV(intDimMV1, 90), percentileTDigest(longDimSV1, 80), percentileTDigestMV(intDimMV1, 90) FROM FeatureTest2 WHERE generationNumber = __GENERATION_NUMBER__ AND (stringDimSV1 != 's1-6' AND longDimSV1 BETWEEN 10 AND 1000 OR (intDimMV1 < 42 AND stringDimMV2 IN ('m2-0-0', 'm2-2-0') AND intDimMV2 NOT IN (6,72))) GROUP BY longDimSV1, intDimMV1 ORDER BY longDimSV1, intDimMV1 LIMIT 20 SELECT longDimSV1, intDimMV1, percentile(longDimSV1, 80.01), percentileMV(intDimMV1, 99.99), percentileEst(longDimSV1, 80.01), percentileEstMV(intDimMV1, 99.99), percentileTDigest(longDimSV1, 80.01), percentileTDigestMV(intDimMV1, 99.99) FROM FeatureTest2 WHERE generationNumber = __GENERATION_NUMBER__ AND (stringDimSV1 != 's1-6' AND longDimSV1 BETWEEN 10 AND 1000 OR (intDimMV1 < 42 AND stringDimMV2 IN ('m2-0-0', 'm2-2-0') AND intDimMV2 NOT IN (6,72))) GROUP BY longDimSV1, intDimMV1 ORDER BY longDimSV1, intDimMV1 LIMIT 20 -SELECT longDimSV1, intDimMV1, distinctCount(longDimSV1), distinctCountMV(intDimMV1), distinctCountHLL(longDimSV1), distinctCountHLLMV(intDimMV1) FROM FeatureTest2 WHERE generationNumber = __GENERATION_NUMBER__ AND (stringDimSV1 != 's1-6' AND longDimSV1 BETWEEN 10 AND 1000 OR (intDimMV1 < 42 AND stringDimMV2 IN ('m2-0-0', 'm2-2-0') AND intDimMV2 NOT IN (6,72))) GROUP BY longDimSV1, intDimMV1 ORDER BY longDimSV1, intDimMV1 LIMIT 20 +SELECT longDimSV1, intDimMV1, distinctCount(longDimSV1), distinctCountMV(intDimMV1), distinctCountHLL(longDimSV1), distinctCountHLLMV(intDimMV1), distinctCountThetaSketch(longDimSV1) FROM FeatureTest2 WHERE generationNumber = __GENERATION_NUMBER__ AND (stringDimSV1 != 's1-6' AND longDimSV1 BETWEEN 10 AND 1000 OR (intDimMV1 < 42 AND stringDimMV2 IN ('m2-0-0', 'm2-2-0') AND intDimMV2 NOT IN (6,72))) GROUP BY longDimSV1, intDimMV1 ORDER BY longDimSV1, intDimMV1 LIMIT 20 # Transformation Functions SELECT DISTINCT add(longDimSV1, sub(longDimSV2, 3)), mod(intMetric1, 10), div(doubleMetric1, mult(floatMetric1, 5)) FROM FeatureTest2 WHERE generationNumber = __GENERATION_NUMBER__ ORDER BY add(longDimSV1, sub(longDimSV2, 3)) DESC, mod(intMetric1, 10), div(doubleMetric1, mult(floatMetric1, 5)) diff --git a/compatibility-verifier/sample-test-suite/config/query-results/feature-test-1-rest-sql.results b/compatibility-verifier/sample-test-suite/config/query-results/feature-test-1-rest-sql.results index 83ae2471162f..aad84fc46ecd 100644 --- a/compatibility-verifier/sample-test-suite/config/query-results/feature-test-1-rest-sql.results +++ b/compatibility-verifier/sample-test-suite/config/query-results/feature-test-1-rest-sql.results @@ -22,7 +22,7 @@ {"resultTable":{"dataSchema":{"columnDataTypes":["DOUBLE","DOUBLE","DOUBLE","DOUBLE","DOUBLE","DOUBLE"],"columnNames":["sum(intMetric1)","summv(intDimMV1)","min(intMetric1)","minmv(intDimMV2)","max(longDimSV1)","maxmv(intDimMV1)"]},"rows":[[4.294967536E9,-2.147479976E9,0.0,6.0,7611.0,462.0]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":10,"numEntriesScannedPostFilter":40,"numGroupsLimitReached":false,"totalDocs":10,"timeUsedMs":5,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":0} {"resultTable":{"dataSchema":{"columnDataTypes":["LONG","LONG","DOUBLE","DOUBLE","DOUBLE","DOUBLE"],"columnNames":["count(*)","countmv(intDimMV1)","avg(floatMetric1)","avgmv(intDimMV2)","minmaxrange(doubleMetric1)","minmaxrangemv(intDimMV2)"]},"rows":[[10,19,114.09000263214111,1516.9,250.00000000000003,6656.0]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":10,"numEntriesScannedPostFilter":40,"numGroupsLimitReached":false,"totalDocs":10,"timeUsedMs":7,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":0} {"resultTable":{"dataSchema":{"columnDataTypes":["DOUBLE","DOUBLE","LONG","LONG","DOUBLE","DOUBLE"],"columnNames":["percentile(longDimSV1, 80.0)","percentilemv(intDimMV1, 90.0)","percentileest(longDimSV1, 80.0)","percentileestmv(intDimMV1, 90.0)","percentiletdigest(longDimSV1, 80.0)","percentiletdigestmv(intDimMV1, 90.0)"]},"rows":[[7611.0,462.0,7611,462,7611.0,462.0]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":10,"numEntriesScannedPostFilter":20,"numGroupsLimitReached":false,"totalDocs":10,"timeUsedMs":8,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":0} -{"resultTable":{"dataSchema":{"columnDataTypes":["INT","INT","LONG","LONG"],"columnNames":["distinctcount(longDimSV1)","distinctcountmv(intDimMV1)","distinctcounthll(longDimSV1)","distinctcounthllmv(intDimMV1)"]},"rows":[[6,8,6,8]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":10,"numEntriesScannedPostFilter":20,"numGroupsLimitReached":false,"totalDocs":10,"timeUsedMs":6,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":0} +{"resultTable":{"dataSchema":{"columnDataTypes":["INT","INT","LONG","LONG","LONG"],"columnNames":["distinctcount(longDimSV1)","distinctcountmv(intDimMV1)","distinctcounthll(longDimSV1)","distinctcounthllmv(intDimMV1)","distinctcountthetasketch(longDimSV1)"]},"rows":[[6,8,6,8,6]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":10,"numEntriesScannedPostFilter":20,"numGroupsLimitReached":false,"totalDocs":10,"timeUsedMs":6,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":0} # Selection {"resultTable":{"dataSchema":{"columnDataTypes":["LONG","STRING","STRING","BYTES"],"columnNames":["longDimSV2","stringDimSV1","textDim1","bytesDimSV1"]},"rows":[[2,"s1-0","Java C++ Python","4877625602"],[2,"s1-0","Java C++ Python","01a0bc"],[21,"s1-2","Java C++ golang","13225573e3f5"],[21,"s1-2","Java C++ golang","deadbeef"],[22,"s1-4","Java C++ golang","deed0507"],[32,"s1-5","golang shell bash",""],[6777,"s1-7","golang Java","d54d0507"],[7621,"s1-6","C++ golang python","deed0507"],[7621,"s1-6","C++ golang python","deed0507"]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":10,"numEntriesScannedPostFilter":47,"numGroupsLimitReached":false,"totalDocs":10,"timeUsedMs":31,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":0} @@ -42,14 +42,14 @@ {"resultTable":{"dataSchema":{"columnDataTypes":["LONG","INT","DOUBLE","DOUBLE","DOUBLE","DOUBLE","DOUBLE","DOUBLE"],"columnNames":["longDimSV1","intDimMV1","sum(intMetric1)","summv(intDimMV1)","min(intMetric1)","minmv(intDimMV2)","max(longDimSV1)","maxmv(intDimMV1)"]},"rows":[[-9223372036854775808,-2147483648,0.0,-2.147483648E9,0.0,22.0,-9.223372036854776E18,-2.147483648E9],[1,3,20.0,14.0,10.0,6.0,1.0,4.0],[1,4,20.0,14.0,10.0,6.0,1.0,4.0],[11,42,20.0,148.0,10.0,62.0,11.0,42.0],[11,32,20.0,148.0,10.0,62.0,11.0,42.0]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":10,"numEntriesScannedPostFilter":40,"numGroupsLimitReached":false,"totalDocs":10,"timeUsedMs":8,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":0} {"resultTable":{"dataSchema":{"columnDataTypes":["LONG","INT","LONG","LONG","DOUBLE","DOUBLE","DOUBLE","DOUBLE"],"columnNames":["longDimSV1","intDimMV1","count(*)","countmv(intDimMV1)","avg(floatMetric1)","avgmv(intDimMV2)","minmaxrange(doubleMetric1)","minmaxrangemv(intDimMV2)"]},"rows":[[-9223372036854775808,-2147483648,1,1,0.0,57.0,0.0,70.0],[1,3,2,4,12.100000381469727,6.5,0.0,1.0],[1,4,2,4,12.100000381469727,6.5,0.0,1.0],[11,42,2,4,22.100000381469727,67.0,0.0,10.0],[11,32,2,4,22.100000381469727,67.0,0.0,10.0]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":10,"numEntriesScannedPostFilter":50,"numGroupsLimitReached":false,"totalDocs":10,"timeUsedMs":12,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":0} {"resultTable":{"dataSchema":{"columnDataTypes":["LONG","INT","DOUBLE","DOUBLE","LONG","LONG","DOUBLE","DOUBLE"],"columnNames":["longDimSV1","intDimMV1","percentile(longDimSV1, 80.0)","percentilemv(intDimMV1, 90.0)","percentileest(longDimSV1, 80.0)","percentileestmv(intDimMV1, 90.0)","percentiletdigest(longDimSV1, 80.0)","percentiletdigestmv(intDimMV1, 90.0)"]},"rows":[[-9223372036854775808,-2147483648,-9.223372036854776E18,-2.147483648E9,-9223372036854775808,-2147483648,-9.223372036854776E18,-2.147483648E9],[1,3,1.0,4.0,1,4,1.0,4.0],[1,4,1.0,4.0,1,4,1.0,4.0],[11,42,11.0,42.0,11,42,11.0,42.0],[11,32,11.0,42.0,11,42,11.0,42.0]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":10,"numEntriesScannedPostFilter":20,"numGroupsLimitReached":false,"totalDocs":10,"timeUsedMs":13,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":0} -{"resultTable":{"dataSchema":{"columnDataTypes":["LONG","INT","INT","INT","LONG","LONG"],"columnNames":["longDimSV1","intDimMV1","distinctcount(longDimSV1)","distinctcountmv(intDimMV1)","distinctcounthll(longDimSV1)","distinctcounthllmv(intDimMV1)"]},"rows":[[-9223372036854775808,-2147483648,1,1,1,1],[1,3,1,2,1,2],[1,4,1,2,1,2],[11,42,1,2,1,2],[11,32,1,2,1,2]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":10,"numEntriesScannedPostFilter":20,"numGroupsLimitReached":false,"totalDocs":10,"timeUsedMs":10,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":0} +{"resultTable":{"dataSchema":{"columnDataTypes":["LONG","INT","INT","INT","LONG","LONG","LONG"],"columnNames":["longDimSV1","intDimMV1","distinctcount(longDimSV1)","distinctcountmv(intDimMV1)","distinctcounthll(longDimSV1)","distinctcounthllmv(intDimMV1)","distinctcountthetasketch(longDimSV1)"]},"rows":[[-9223372036854775808,-2147483648,1,1,1,1,1],[1,3,1,2,1,2,1],[1,4,1,2,1,2,1],[11,42,1,2,1,2,1],[11,32,1,2,1,2,1]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":10,"numEntriesScannedPostFilter":20,"numGroupsLimitReached":false,"totalDocs":10,"timeUsedMs":10,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":0} # Selection & Filtering & Grouping on Aggregation {"resultTable":{"dataSchema":{"columnDataTypes":["LONG","INT","LONG"],"columnNames":["longDimSV1","intDimMV1","count(*)"]},"rows":[[-9223372036854775808,-2147483648,1],[11,32,2],[11,42,2],[41,42,1],[41,52,1]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":4,"numEntriesScannedPostFilter":8,"numGroupsLimitReached":false,"totalDocs":10,"timeUsedMs":8,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":0} {"resultTable":{"dataSchema":{"columnDataTypes":["LONG","INT","DOUBLE","DOUBLE","DOUBLE","DOUBLE","DOUBLE","DOUBLE"],"columnNames":["longDimSV1","intDimMV1","sum(intMetric1)","summv(intDimMV1)","min(intMetric1)","minmv(intDimMV2)","max(longDimSV1)","maxmv(intDimMV1)"]},"rows":[[-9223372036854775808,-2147483648,0,-2147483648,0,22,-9223372036854776000,-2147483648],[11,32,20,148,10,62,11,42],[11,42,20,148,10,62,11,42],[41,42,14,94,14,72,41,52],[41,52,14,94,14,72,41,52]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":4,"numEntriesScannedPostFilter":16,"numGroupsLimitReached":false,"totalDocs":10,"timeUsedMs":13,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":0} {"resultTable":{"dataSchema":{"columnDataTypes":["LONG","INT","LONG","LONG","DOUBLE","DOUBLE","DOUBLE","DOUBLE"],"columnNames":["longDimSV1","intDimMV1","count(*)","countmv(intDimMV1)","avg(floatMetric1)","avgmv(intDimMV2)","minmaxrange(doubleMetric1)","minmaxrangemv(intDimMV2)"]},"rows":[[-9223372036854775808,-2147483648,1,1,0,57,0,70],[11,32,2,4,22.100000381469727,67,0,10],[11,42,2,4,22.100000381469727,67,0,10],[41,42,1,2,24.100000381469727,77,0,10],[41,52,1,2,24.100000381469727,77,0,10]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":4,"numEntriesScannedPostFilter":20,"numGroupsLimitReached":false,"totalDocs":10,"timeUsedMs":4,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":0} {"resultTable":{"dataSchema":{"columnDataTypes":["LONG","INT","DOUBLE","DOUBLE","LONG","LONG","DOUBLE","DOUBLE"],"columnNames":["longDimSV1","intDimMV1","percentile(longDimSV1, 80.0)","percentilemv(intDimMV1, 90.0)","percentileest(longDimSV1, 80.0)","percentileestmv(intDimMV1, 90.0)","percentiletdigest(longDimSV1, 80.0)","percentiletdigestmv(intDimMV1, 90.0)"]},"rows":[[-9223372036854775808,-2147483648,-9223372036854775808,-2147483648,-9223372036854775808,-2147483648,-9223372036854776000,-2147483648],[11,32,11,42,11,42,11,42],[11,42,11,42,11,42,11,42],[41,42,41,52,41,52,41,52],[41,52,41,52,41,52,41,52]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":4,"numEntriesScannedPostFilter":8,"numGroupsLimitReached":false,"totalDocs":10,"timeUsedMs":18,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":0} -{"resultTable":{"dataSchema":{"columnDataTypes":["LONG","INT","INT","INT","LONG","LONG"],"columnNames":["longDimSV1","intDimMV1","distinctcount(longDimSV1)","distinctcountmv(intDimMV1)","distinctcounthll(longDimSV1)","distinctcounthllmv(intDimMV1)"]},"rows":[[-9223372036854775808,-2147483648,1,1,1,1],[11,32,1,2,1,2],[11,42,1,2,1,2],[41,42,1,2,1,2],[41,52,1,2,1,2]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":4,"numEntriesScannedPostFilter":8,"numGroupsLimitReached":false,"totalDocs":10,"timeUsedMs":7,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":0} +{"resultTable":{"dataSchema":{"columnDataTypes":["LONG","INT","INT","INT","LONG","LONG","LONG"],"columnNames":["longDimSV1","intDimMV1","distinctcount(longDimSV1)","distinctcountmv(intDimMV1)","distinctcounthll(longDimSV1)","distinctcounthllmv(intDimMV1)","distinctcountthetasketch(longDimSV1)"]},"rows":[[-9223372036854775808,-2147483648,1,1,1,1,1],[11,32,1,2,1,2,1],[11,42,1,2,1,2,1],[41,42,1,2,1,2,1],[41,52,1,2,1,2,1]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":4,"numEntriesScannedPostFilter":8,"numGroupsLimitReached":false,"totalDocs":10,"timeUsedMs":7,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":0} # Transformation Functions {"resultTable":{"dataSchema":{"columnDataTypes":["DOUBLE","DOUBLE","DOUBLE"],"columnNames":["add(longDimSV1,sub(longDimSV2,'3'))","mod(intMetric1,'10')","div(doubleMetric1,mult(floatMetric1,'5'))"]},"rows":[[15229.0,1.0,0.20076306285631254],[15229.0,7.0,0.20076306285631254],[15229.0,7.0,0.20076306285631254],[13540.0,7.0,0.20076306285631254],[60.0,4.0,0.1999999968342762],[29.0,0.0,0.20904977014723267],[29.0,0.0,0.20904977014723267],[0.0,0.0,0.21652891879345226],[0.0,0.0,0.21652891879345226],[-9.223372036854776E18,0.0,"Infinity"]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":10,"numEntriesScannedPostFilter":60,"numGroupsLimitReached":false,"totalDocs":10,"timeUsedMs":8,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":0} diff --git a/compatibility-verifier/sample-test-suite/config/query-results/feature-test-2-sql-realtime.results b/compatibility-verifier/sample-test-suite/config/query-results/feature-test-2-sql-realtime.results index 47f7a2805c84..849020c10403 100644 --- a/compatibility-verifier/sample-test-suite/config/query-results/feature-test-2-sql-realtime.results +++ b/compatibility-verifier/sample-test-suite/config/query-results/feature-test-2-sql-realtime.results @@ -29,7 +29,7 @@ {"resultTable":{"dataSchema":{"columnNames":["count(*)","countmv(intDimMV1)","avg(floatMetric1)","avgmv(intDimMV2)","minmaxrange(doubleMetric1)","minmaxrangemv(intDimMV2)"],"columnDataTypes":["LONG","LONG","DOUBLE","DOUBLE","DOUBLE","DOUBLE"]},"rows":[[66,125,105.11969939145175,1383.2575757575758,250.00000000000003,6656.0]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":66,"numEntriesScannedPostFilter":264,"numGroupsLimitReached":false,"totalDocs":66,"timeUsedMs":5,"offlineThreadCpuTimeNs":0,"realtimeThreadCpuTimeNs":0,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":1620950080428} {"resultTable":{"dataSchema":{"columnNames":["percentile(longDimSV1, 80.0)","percentilemv(intDimMV1, 90.0)","percentileest(longDimSV1, 80.0)","percentileestmv(intDimMV1, 90.0)","percentiletdigest(longDimSV1, 80.0)","percentiletdigestmv(intDimMV1, 90.0)"],"columnDataTypes":["DOUBLE","DOUBLE","LONG","LONG","DOUBLE","DOUBLE"]},"rows":[[7611.0,462.0,7611,462,7611.0,462.0]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":66,"numEntriesScannedPostFilter":132,"numGroupsLimitReached":false,"totalDocs":66,"timeUsedMs":9,"offlineThreadCpuTimeNs":0,"realtimeThreadCpuTimeNs":0,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":1620950080428} {"resultTable":{"dataSchema":{"columnNames":["percentile(longDimSV1, 80.01)","percentilemv(intDimMV1, 99.99)","percentileest(longDimSV1, 80.01)","percentileestmv(intDimMV1, 99.99)","percentiletdigest(longDimSV1, 80.01)","percentiletdigestmv(intDimMV1, 99.99)"],"columnDataTypes":["DOUBLE","DOUBLE","LONG","LONG","DOUBLE","DOUBLE"]},"rows":[[7611.0,462.0,7611,462,7611.0,462.0]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":66,"numEntriesScannedPostFilter":132,"numGroupsLimitReached":false,"totalDocs":66,"timeUsedMs":9,"offlineThreadCpuTimeNs":0,"realtimeThreadCpuTimeNs":0,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":1620950080428} -{"resultTable":{"dataSchema":{"columnNames":["distinctcount(longDimSV1)","distinctcountmv(intDimMV1)","distinctcounthll(longDimSV1)","distinctcounthllmv(intDimMV1)"],"columnDataTypes":["INT","INT","LONG","LONG"]},"rows":[[6,8,6,8]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":66,"numEntriesScannedPostFilter":132,"numGroupsLimitReached":false,"totalDocs":66,"timeUsedMs":5,"offlineThreadCpuTimeNs":0,"realtimeThreadCpuTimeNs":0,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":1620950080428} +{"resultTable":{"dataSchema":{"columnNames":["distinctcount(longDimSV1)","distinctcountmv(intDimMV1)","distinctcounthll(longDimSV1)","distinctcounthllmv(intDimMV1)","distinctcountthetasketch(longDimSV1)"],"columnDataTypes":["INT","INT","LONG","LONG","LONG"]},"rows":[[6,8,6,8,6]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":66,"numEntriesScannedPostFilter":132,"numGroupsLimitReached":false,"totalDocs":66,"timeUsedMs":5,"offlineThreadCpuTimeNs":0,"realtimeThreadCpuTimeNs":0,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":1620950080428} # Selection & Filtering & Grouping on Aggregation {"resultTable":{"dataSchema":{"columnNames":["longDimSV1","intDimMV1","count(*)"],"columnDataTypes":["LONG","INT","LONG"]},"rows":[[-9223372036854775808,-2147483648,7],[11,32,14],[11,42,14],[41,42,7],[41,52,7]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":28,"numEntriesScannedPostFilter":56,"numGroupsLimitReached":false,"totalDocs":66,"timeUsedMs":6,"offlineThreadCpuTimeNs":0,"realtimeThreadCpuTimeNs":0,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":1620950080428} @@ -37,7 +37,7 @@ {"resultTable":{"dataSchema":{"columnNames":["longDimSV1","intDimMV1","count(*)","countmv(intDimMV1)","avg(floatMetric1)","avgmv(intDimMV2)","minmaxrange(doubleMetric1)","minmaxrangemv(intDimMV2)"],"columnDataTypes":["LONG","INT","LONG","LONG","DOUBLE","DOUBLE","DOUBLE","DOUBLE"]},"rows":[[-9223372036854775808,-2147483648,7,7,0.0,57.0,0.0,70.0],[11,32,14,28,22.100000381469727,67.0,0.0,10.0],[11,42,14,28,22.100000381469727,67.0,0.0,10.0],[41,42,7,14,24.100000381469727,77.0,0.0,10.0],[41,52,7,14,24.100000381469727,77.0,0.0,10.0]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":28,"numEntriesScannedPostFilter":140,"numGroupsLimitReached":false,"totalDocs":66,"timeUsedMs":5,"offlineThreadCpuTimeNs":0,"realtimeThreadCpuTimeNs":0,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":1620950080428} {"resultTable":{"dataSchema":{"columnNames":["longDimSV1","intDimMV1","percentile(longDimSV1, 80.0)","percentilemv(intDimMV1, 90.0)","percentileest(longDimSV1, 80.0)","percentileestmv(intDimMV1, 90.0)","percentiletdigest(longDimSV1, 80.0)","percentiletdigestmv(intDimMV1, 90.0)"],"columnDataTypes":["LONG","INT","DOUBLE","DOUBLE","LONG","LONG","DOUBLE","DOUBLE"]},"rows":[[-9223372036854775808,-2147483648,-9.223372036854776E18,-2.147483648E9,-9223372036854775808,-2147483648,-9.223372036854776E18,-2.147483648E9],[11,32,11.0,42.0,11,42,11.0,42.0],[11,42,11.0,42.0,11,42,11.0,42.0],[41,42,41.0,52.0,41,52,41.0,52.0],[41,52,41.0,52.0,41,52,41.0,52.0]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":28,"numEntriesScannedPostFilter":56,"numGroupsLimitReached":false,"totalDocs":66,"timeUsedMs":8,"offlineThreadCpuTimeNs":0,"realtimeThreadCpuTimeNs":0,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":1620950080428} {"resultTable":{"dataSchema":{"columnNames":["longDimSV1","intDimMV1","percentile(longDimSV1, 80.01)","percentilemv(intDimMV1, 99.99)","percentileest(longDimSV1, 80.01)","percentileestmv(intDimMV1, 99.99)","percentiletdigest(longDimSV1, 80.01)","percentiletdigestmv(intDimMV1, 99.99)"],"columnDataTypes":["LONG","INT","DOUBLE","DOUBLE","LONG","LONG","DOUBLE","DOUBLE"]},"rows":[[-9223372036854775808,-2147483648,-9.223372036854776E18,-2.147483648E9,-9223372036854775808,-2147483648,-9.223372036854776E18,-2.147483648E9],[11,32,11.0,42.0,11,42,11.0,42.0],[11,42,11.0,42.0,11,42,11.0,42.0],[41,42,41.0,52.0,41,52,41.0,52.0],[41,52,41.0,52.0,41,52,41.0,52.0]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":28,"numEntriesScannedPostFilter":56,"numGroupsLimitReached":false,"totalDocs":66,"timeUsedMs":12,"offlineThreadCpuTimeNs":0,"realtimeThreadCpuTimeNs":0,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":1620950080428} -{"resultTable":{"dataSchema":{"columnNames":["longDimSV1","intDimMV1","distinctcount(longDimSV1)","distinctcountmv(intDimMV1)","distinctcounthll(longDimSV1)","distinctcounthllmv(intDimMV1)"],"columnDataTypes":["LONG","INT","INT","INT","LONG","LONG"]},"rows":[[-9223372036854775808,-2147483648,1,1,1,1],[11,32,1,2,1,2],[11,42,1,2,1,2],[41,42,1,2,1,2],[41,52,1,2,1,2]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":28,"numEntriesScannedPostFilter":56,"numGroupsLimitReached":false,"totalDocs":66,"timeUsedMs":7,"offlineThreadCpuTimeNs":0,"realtimeThreadCpuTimeNs":0,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":1620950080428} +{"resultTable":{"dataSchema":{"columnNames":["longDimSV1","intDimMV1","distinctcount(longDimSV1)","distinctcountmv(intDimMV1)","distinctcounthll(longDimSV1)","distinctcounthllmv(intDimMV1)","distinctcountthetasketch(longDimSV1)"],"columnDataTypes":["LONG","INT","INT","INT","LONG","LONG","LONG"]},"rows":[[-9223372036854775808,-2147483648,1,1,1,1,1],[11,32,1,2,1,2,1],[11,42,1,2,1,2,1],[41,42,1,2,1,2,1],[41,52,1,2,1,2,1]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":28,"numEntriesScannedPostFilter":56,"numGroupsLimitReached":false,"totalDocs":66,"timeUsedMs":7,"offlineThreadCpuTimeNs":0,"realtimeThreadCpuTimeNs":0,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":1620950080428} # Transformation Functions {"resultTable":{"dataSchema":{"columnNames":["add(longDimSV1,sub(longDimSV2,'3'))","mod(intMetric1,'10')","div(doubleMetric1,mult(floatMetric1,'5'))"],"columnDataTypes":["DOUBLE","DOUBLE","DOUBLE"]},"rows":[[15229.0,1.0,0.20076306285631254],[15229.0,7.0,0.20076306285631254],[13540.0,7.0,0.20076306285631254],[60.0,4.0,0.1999999968342762],[29.0,0.0,0.20904977014723267],[0.0,0.0,0.21652891879345226],[-9.223372036854776E18,0.0,"Infinity"]]},"exceptions":[],"numServersQueried":1,"numServersResponded":1,"numSegmentsQueried":1,"numSegmentsProcessed":1,"numSegmentsMatched":1,"numDocsScanned":66,"numEntriesScannedPostFilter":330,"numGroupsLimitReached":false,"totalDocs":66,"timeUsedMs":31,"offlineThreadCpuTimeNs":0,"realtimeThreadCpuTimeNs":0,"segmentStatistics":[],"traceInfo":{},"minConsumingFreshnessTimeMs":1620950080428} diff --git a/config/checkstyle.xml b/config/checkstyle.xml index 86e163f50ced..c1feca644f1e 100644 --- a/config/checkstyle.xml +++ b/config/checkstyle.xml @@ -26,6 +26,10 @@ + + + + diff --git a/config/suppressions.xml b/config/suppressions.xml index 56b06ed6b7ae..10d796b23338 100644 --- a/config/suppressions.xml +++ b/config/suppressions.xml @@ -45,4 +45,7 @@ + + + diff --git a/contrib/pinot-druid-benchmark/pom.xml b/contrib/pinot-druid-benchmark/pom.xml index 050e312a1b2e..0e4589761e0f 100644 --- a/contrib/pinot-druid-benchmark/pom.xml +++ b/contrib/pinot-druid-benchmark/pom.xml @@ -33,7 +33,7 @@ org.apache.httpcomponents httpclient - 4.5.1 + 4.5.13 diff --git a/contrib/pinot-fmpp-maven-plugin/pom.xml b/contrib/pinot-fmpp-maven-plugin/pom.xml deleted file mode 100644 index b7ccad0d8557..000000000000 --- a/contrib/pinot-fmpp-maven-plugin/pom.xml +++ /dev/null @@ -1,111 +0,0 @@ - - - - 4.0.0 - - pinot - org.apache.pinot - 0.13.0-SNAPSHOT - ../.. - - - pinot-fmpp-maven-plugin - Pinot FMPP plugin - https://pinot.apache.org/ - maven-plugin - - ${basedir}/../.. - 3.3.3 - 0.9.16 - 2.3.28 - - - - - commons-io - commons-io - - - org.apache.maven - maven-core - ${maven.version} - - - org.codehaus.plexus - plexus-utils - - - - - org.apache.maven - maven-plugin-api - ${maven.version} - - - net.sourceforge.fmpp - fmpp - ${fmpp.version} - - - org.freemarker - freemarker - ${freemarker.version} - - - - - - - org.apache.maven.plugins - maven-checkstyle-plugin - - true - - - - org.apache.maven.plugins - maven-plugin-plugin - - pinot-fmpp - - - - default-descriptor - - descriptor - - process-classes - - - help-descriptor - - helpmojo - - process-classes - - - - - - diff --git a/contrib/pinot-fmpp-maven-plugin/src/main/java/org/apache/pinot/fmpp/FMPPMojo.java b/contrib/pinot-fmpp-maven-plugin/src/main/java/org/apache/pinot/fmpp/FMPPMojo.java deleted file mode 100644 index 787ac7606cd3..000000000000 --- a/contrib/pinot-fmpp-maven-plugin/src/main/java/org/apache/pinot/fmpp/FMPPMojo.java +++ /dev/null @@ -1,270 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.fmpp; - -import com.google.common.base.Joiner; -import com.google.common.base.Stopwatch; -import fmpp.Engine; -import fmpp.ProgressListener; -import fmpp.progresslisteners.TerseConsoleProgressListener; -import fmpp.setting.Settings; -import fmpp.util.MiscUtil; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.TimeUnit; -import org.apache.commons.io.FileUtils; -import org.apache.maven.plugin.AbstractMojo; -import org.apache.maven.plugin.MojoExecutionException; -import org.apache.maven.plugin.MojoFailureException; -import org.apache.maven.project.MavenProject; - -import static java.lang.String.format; - - -/** - * a maven plugin to run the freemarker generation incrementally - * (if output has not changed, the files are not touched) - * - * @goal generate - * @phase generate-sources - */ -public class FMPPMojo extends AbstractMojo { - - /** - * Used to add new source directories to the build. - * - * @parameter default-value="${project}" - * @required - * @readonly - **/ - private MavenProject project; - - /** - * Where to find the FreeMarker template files. - * - * @parameter default-value="src/main/resources/fmpp/templates/" - * @required - */ - private File templates; - - /** - * Where to write the generated files of the output files. - * - * @parameter default-value="${project.build.directory}/generated-sources/fmpp/" - * @required - */ - private File output; - - /** - * Location of the FreeMarker config file. - * - * @parameter default-value="src/main/resources/fmpp/config.fmpp" - * @required - */ - private File config; - - /** - * compilation scope to be added to ("compile" or "test") - * - * @parameter default-value="compile" - * @required - */ - private String scope; - - /** - * FMPP data model build parameter. - * - * @see FMPP Data Model Building - * @parameter default-value="" - */ - private String data; - - /** - * if maven properties are added as data - * - * @parameter default-value="true" - * @required - */ - private boolean addMavenDataLoader; - - @Override - public void execute() - throws MojoExecutionException, MojoFailureException { - if (project == null) { - throw new MojoExecutionException("This plugin can only be used inside a project."); - } - String outputPath = output.getAbsolutePath(); - if ((!output.exists() && !output.mkdirs()) || !output.isDirectory()) { - throw new MojoFailureException("can not write to output dir: " + outputPath); - } - String templatesPath = templates.getAbsolutePath(); - if (!templates.exists() || !templates.isDirectory()) { - throw new MojoFailureException("templates not found in dir: " + outputPath); - } - - // add the output directory path to the project source directories - switch (scope) { - case "compile": - project.addCompileSourceRoot(outputPath); - break; - case "test": - project.addTestCompileSourceRoot(outputPath); - break; - default: - throw new MojoFailureException("scope must be compile or test"); - } - - final Stopwatch sw = Stopwatch.createStarted(); - try { - getLog().info( - format("Freemarker generation:\n scope: %s,\n config: %s,\n templates: %s", scope, config.getAbsolutePath(), - templatesPath)); - final File tmp = Files.createTempDirectory("freemarker-tmp").toFile(); - String tmpPath = tmp.getAbsolutePath(); - final String tmpPathNormalized = tmpPath.endsWith(File.separator) ? tmpPath : tmpPath + File.separator; - Settings settings = new Settings(new File(".")); - settings.set(Settings.NAME_SOURCE_ROOT, templatesPath); - settings.set(Settings.NAME_OUTPUT_ROOT, tmp.getAbsolutePath()); - settings.load(config); - settings.addProgressListener(new TerseConsoleProgressListener()); - settings.addProgressListener(new ProgressListener() { - @Override - public void notifyProgressEvent(Engine engine, int event, File src, int pMode, Throwable error, Object param) - throws Exception { - if (event == EVENT_END_PROCESSING_SESSION) { - getLog().info(format("Freemarker generation took %dms", sw.elapsed(TimeUnit.MILLISECONDS))); - sw.reset(); - Report report = moveIfChanged(tmp, tmpPathNormalized); - if (!tmp.delete()) { - throw new MojoFailureException(format("can not delete %s", tmp)); - } - getLog().info(format("Incremental output update took %dms", sw.elapsed(TimeUnit.MILLISECONDS))); - getLog().info(format("new: %d", report.newFiles)); - getLog().info(format("changed: %d", report.changedFiles)); - getLog().info(format("unchanged: %d", report.unchangedFiles)); - } - } - }); - List dataValues = new ArrayList<>(); - if (addMavenDataLoader) { - getLog().info("Adding maven data loader"); - settings.setEngineAttribute(MavenDataLoader.MAVEN_DATA_ATTRIBUTE, new MavenDataLoader.MavenData(project)); - dataValues.add(format("maven: %s()", MavenDataLoader.class.getName())); - } - if (data != null) { - dataValues.add(data); - } - if (!dataValues.isEmpty()) { - String dataString = Joiner.on(",").join(dataValues); - getLog().info("Setting data loader " + dataString); - - settings.add(Settings.NAME_DATA, dataString); - } - settings.execute(); - } catch (Exception e) { - throw new MojoFailureException(MiscUtil.causeMessages(e), e); - } - } - - private static final class Report { - int changedFiles; - int unchangedFiles; - int newFiles; - - Report(int changedFiles, int unchangedFiles, int newFiles) { - super(); - this.changedFiles = changedFiles; - this.unchangedFiles = unchangedFiles; - this.newFiles = newFiles; - } - - public Report() { - this(0, 0, 0); - } - - void add(Report other) { - changedFiles += other.changedFiles; - unchangedFiles += other.unchangedFiles; - newFiles += other.newFiles; - } - - public void addChanged() { - ++changedFiles; - } - - public void addNew() { - ++newFiles; - } - - public void addUnchanged() { - ++unchangedFiles; - } - } - - private Report moveIfChanged(File root, String tmpPath) - throws MojoFailureException, IOException { - Report report = new Report(); - for (File file : root.listFiles()) { - if (file.isDirectory()) { - report.add(moveIfChanged(file, tmpPath)); - if (!file.delete()) { - throw new MojoFailureException(format("can not delete %s", file)); - } - } else { - String absPath = file.getAbsolutePath(); - if (!absPath.startsWith(tmpPath)) { - throw new MojoFailureException(format("%s should start with %s", absPath, tmpPath)); - } - String relPath = absPath.substring(tmpPath.length()); - File outputFile = new File(output, relPath); - if (!outputFile.exists()) { - report.addNew(); - } else if (!FileUtils.contentEquals(file, outputFile)) { - getLog().info(format("%s has changed", relPath)); - if (!outputFile.delete()) { - throw new MojoFailureException(format("can not delete %s", outputFile)); - } - report.addChanged(); - } else { - report.addUnchanged(); - } - if (!outputFile.exists()) { - File parentDir = outputFile.getParentFile(); - if (parentDir.exists() && !parentDir.isDirectory()) { - throw new MojoFailureException( - format("can not move %s to %s as %s is not a dir", file, outputFile, parentDir)); - } - if (!parentDir.exists() && !parentDir.mkdirs()) { - throw new MojoFailureException( - format("can not move %s to %s as dir %s can not be created", file, outputFile, parentDir)); - } - FileUtils.moveFile(file, outputFile); - } else { - if (!file.delete()) { - throw new MojoFailureException(format("can not delete %s", file)); - } - } - } - } - return report; - } -} diff --git a/contrib/pinot-fmpp-maven-plugin/src/main/java/org/apache/pinot/fmpp/MavenDataLoader.java b/contrib/pinot-fmpp-maven-plugin/src/main/java/org/apache/pinot/fmpp/MavenDataLoader.java deleted file mode 100644 index df85ad891b18..000000000000 --- a/contrib/pinot-fmpp-maven-plugin/src/main/java/org/apache/pinot/fmpp/MavenDataLoader.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.fmpp; - -import fmpp.Engine; -import fmpp.tdd.DataLoader; -import java.util.List; -import org.apache.maven.project.MavenProject; - - -/** - * A data loader for Maven - */ -public class MavenDataLoader implements DataLoader { - public static final class MavenData { - private final MavenProject project; - - public MavenData(MavenProject project) { - this.project = project; - } - - public MavenProject getProject() { - return project; - } - } - - public static final String MAVEN_DATA_ATTRIBUTE = "maven.data"; - - @Override - public Object load(Engine e, List args) - throws Exception { - if (!args.isEmpty()) { - throw new IllegalArgumentException("maven model data loader has no parameters"); - } - - MavenData data = (MavenData) e.getAttribute(MAVEN_DATA_ATTRIBUTE); - return data; - } -} diff --git a/doap_Pinot.rdf b/doap_Pinot.rdf new file mode 100644 index 000000000000..f4a57a1f4680 --- /dev/null +++ b/doap_Pinot.rdf @@ -0,0 +1,64 @@ + + + + + + 2023-08-07 + + Apache Pinot + + + Apache Pinot is a real-time distributed OLAP datastore purpose-built for low-latency, high-throughput analytics. + Apache Pinot is a real-time distributed online analytical processing (OLAP) datastore. Use Pinot to ingest and immediately query data from streaming or batch data sources (including, Apache Kafka, Amazon Kinesis, Hadoop HDFS, Amazon S3, Azure ADLS, and Google Cloud Storage). +Apache Pinot includes the following: +Ultra low-latency analytics even at extremely high throughput. +Columnar data store with several smart indexing and pre-aggregation techniques. +Scaling up and out with no upper bound. +Consistent performance based on the size of your cluster and an expected query per second (QPS) threshold. +It's perfect for user-facing real-time analytics and other analytical use cases, including internal dashboards, anomaly detection, and ad hoc data exploration. + + + + Java + + + + 1.1.0 + 2024-03-24 + 1.1.0 + + + + + + + + + + + Apache Pinot Team + + + + + + diff --git a/docker/images/pinot-base/README.md b/docker/images/pinot-base/README.md index d7f5e929174c..92430de06c68 100644 --- a/docker/images/pinot-base/README.md +++ b/docker/images/pinot-base/README.md @@ -20,40 +20,35 @@ --> # docker-pinot-base + This is the base docker image to build [Apache Pinot](https://github.com/apache/pinot). -## How to build a docker image +## Build and publish the docker image -Arguments: +Here is +the [Github Action task](https://github.com/apachepinot/pinot-fork/actions/workflows/build-pinot-docker-base-image.yml) +to build and publish pinot base docker images. -`JAVA_VERSION`: The Java Build and Runtime image version. Default is `11` +This task can be triggered manually to build the cross platform(amd64 and arm64v8) base image. -`OPENJDK_IMAGE`: Base image to use for Pinot build and runtime, e.g. `arm64v8/openjdk`. Default is `openjdk`. +The build shell is: -Usage: -```SHELL -docker build -t apachepinot/pinot-base-build:openjdk11 --no-cache --network=host --build-arg JAVA_VERSION=11 -f pinot-base-build/Dockerfile . -``` +For Amazon Corretto 11: ```SHELL -docker build -t apachepinot/pinot-base-runtime:openjdk11 --no-cache --network=host --build-arg JAVA_VERSION=11 -f pinot-base-runtime/Dockerfile . +docker buildx build --no-cache --platform=linux/arm64,linux/amd64 --file pinot-base-build/amazoncorretto.dockerfile --tag apachepinot/pinot-base-build:11-amazoncorretto --push . ``` -Note that if you are not on arm64 machine, you can still build the image by turning on the experimental feature of docker, and add `--platform linux/arm64` into the `docker build ...` script, e.g. -```SHELL -docker build -t apachepinot/pinot-base-build:openjdk11-arm64v8 --platform linux/arm64 --no-cache --network=host --build-arg JAVA_VERSION=11 --build-arg OPENJDK_IMAGE=arm64v8/openjdk -f pinot-base-build/Dockerfile . -``` ```SHELL -docker build -t apachepinot/pinot-base-runtime:openjdk11-arm64v8 --platform linux/arm64 --no-cache --network=host --build-arg JAVA_VERSION=11 --build-arg OPENJDK_IMAGE=arm64v8/openjdk -f pinot-base-runtime/Dockerfile . +docker buildx build --no-cache --platform=linux/arm64,linux/amd64 --file pinot-base-runtime/amazoncorretto.dockerfile --tag apachepinot/pinot-base-runtime:11-amazoncorretto --push . ``` -## Publish the docker image +For MS OpenJDK, the build shell is: -Here is the [Github Action task](https://github.com/apachepinot/pinot-fork/actions/workflows/build-pinot-docker-base-image.yml) to build and publish pinot base docker images. - -This task can be triggered manually to build the cross platform(amd64 and arm64v8) base image. +```SHELL +docker buildx build --no-cache --platform=linux/arm64,linux/amd64 --file pinot-base-build/amazoncorretto.dockerfile --tag apachepinot/pinot-base-build:11-ms-openjdk --push . +``` -The build shell is: ```SHELL -docker buildx build --no-cache --platform=linux/arm64,linux/amd64 --file Dockerfile --tag apachepinot/pinot-base-build:openjdk11 --push . -``` \ No newline at end of file +docker buildx build --no-cache --platform=linux/arm64,linux/amd64 --file pinot-base-runtime/amazoncorretto.dockerfile --tag apachepinot/pinot-base-runtime:11-ms-openjdk --push . +``` diff --git a/docker/images/pinot-base/pinot-base-build/Dockerfile b/docker/images/pinot-base/pinot-base-build/Dockerfile deleted file mode 100644 index 5b93684c5637..000000000000 --- a/docker/images/pinot-base/pinot-base-build/Dockerfile +++ /dev/null @@ -1,48 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -ARG JAVA_VERSION=11 -ARG OPENJDK_IMAGE=openjdk -FROM ${OPENJDK_IMAGE}:${JAVA_VERSION} AS pinot_build_env - -LABEL MAINTAINER=dev@pinot.apache.org - -# extra dependency for running launcher -RUN apt-get update && \ - apt-get install -y --no-install-recommends vim wget curl git automake bison flex g++ libboost-all-dev libevent-dev \ - libssl-dev libtool make pkg-config && \ - rm -rf /var/lib/apt/lists/* - -# install maven -RUN mkdir -p /usr/share/maven /usr/share/maven/ref \ - && wget https://downloads.apache.org/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz -P /tmp \ - && tar -xzf /tmp/apache-maven-*.tar.gz -C /usr/share/maven --strip-components=1 \ - && rm -f /tmp/apache-maven-*.tar.gz \ - && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn -ENV MAVEN_HOME /usr/share/maven -ENV MAVEN_CONFIG /opt/.m2 - -# install thrift -RUN wget http://archive.apache.org/dist/thrift/0.12.0/thrift-0.12.0.tar.gz -O /tmp/thrift-0.12.0.tar.gz && \ - tar xfz /tmp/thrift-0.12.0.tar.gz --directory /tmp && \ - base_dir=`pwd` && \ - cd /tmp/thrift-0.12.0 && \ - ./configure --with-cpp=no --with-c_glib=no --with-java=yes --with-python=no --with-ruby=no --with-erlang=no --with-go=no --with-nodejs=no --with-php=no && \ - make install - -CMD ["-help"] diff --git a/docker/images/pinot-base/pinot-base-build/amazoncorretto.dockerfile b/docker/images/pinot-base/pinot-base-build/amazoncorretto.dockerfile new file mode 100644 index 000000000000..79126ed713ce --- /dev/null +++ b/docker/images/pinot-base/pinot-base-build/amazoncorretto.dockerfile @@ -0,0 +1,64 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +FROM debian:buster-slim + +ARG version=11.0.18.10-1 +# In addition to installing the Amazon corretto, we also install +# fontconfig. The folks who manage the docker hub's +# official image library have found that font management +# is a common usecase, and painpoint, and have +# recommended that Java images include font support. +# +# See: +# https://github.com/docker-library/official-images/blob/master/test/tests/java-uimanager-font/container.java + +LABEL MAINTAINER=dev@pinot.apache.org + +RUN set -eux \ + && apt-get update \ + && apt-get install -y --no-install-recommends \ + curl ca-certificates gnupg software-properties-common fontconfig java-common vim wget git automake bison flex g++ libboost-all-dev libevent-dev libssl-dev libtool make pkg-config\ + && curl -fL https://apt.corretto.aws/corretto.key | apt-key add - \ + && add-apt-repository 'deb https://apt.corretto.aws stable main' \ + && mkdir -p /usr/share/man/man1 || true \ + && apt-get update \ + && apt-get install -y java-11-amazon-corretto-jdk=1:$version \ + && rm -rf /var/lib/apt/lists/* + +ENV LANG C.UTF-8 +ENV JAVA_HOME=/usr/lib/jvm/java-11-amazon-corretto + +# install maven +RUN mkdir -p /usr/share/maven /usr/share/maven/ref \ + && wget https://dlcdn.apache.org/maven/maven-3/3.9.7/binaries/apache-maven-3.9.7-bin.tar.gz -P /tmp \ + && tar -xzf /tmp/apache-maven-*.tar.gz -C /usr/share/maven --strip-components=1 \ + && rm -f /tmp/apache-maven-*.tar.gz \ + && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn +ENV MAVEN_HOME /usr/share/maven +ENV MAVEN_CONFIG /opt/.m2 + +# install thrift +RUN wget https://archive.apache.org/dist/thrift/0.12.0/thrift-0.12.0.tar.gz -O /tmp/thrift-0.12.0.tar.gz && \ + tar xfz /tmp/thrift-0.12.0.tar.gz --directory /tmp && \ + base_dir=`pwd` && \ + cd /tmp/thrift-0.12.0 && \ + ./configure --with-cpp=no --with-c_glib=no --with-java=yes --with-python=no --with-ruby=no --with-erlang=no --with-go=no --with-nodejs=no --with-php=no && \ + make install + +CMD ["bash"] diff --git a/docker/images/pinot-base/pinot-base-build/ms-openjdk.dockerfile b/docker/images/pinot-base/pinot-base-build/ms-openjdk.dockerfile new file mode 100644 index 000000000000..8255d84a1608 --- /dev/null +++ b/docker/images/pinot-base/pinot-base-build/ms-openjdk.dockerfile @@ -0,0 +1,48 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +ARG JAVA_VERSION=11 +ARG JDK_IMAGE=mcr.microsoft.com/openjdk/jdk +FROM ${JDK_IMAGE}:${JAVA_VERSION}-ubuntu AS pinot_build_env + +LABEL MAINTAINER=dev@pinot.apache.org + +# extra dependency for running launcher +RUN apt-get update && \ + apt-get install -y --no-install-recommends vim wget curl git automake bison flex g++ libboost-all-dev libevent-dev \ + libssl-dev libtool make pkg-config && \ + rm -rf /var/lib/apt/lists/* + +# install maven +RUN mkdir -p /usr/share/maven /usr/share/maven/ref \ + && wget https://dlcdn.apache.org/maven/maven-3/3.9.7/binaries/apache-maven-3.9.7-bin.tar.gz -P /tmp \ + && tar -xzf /tmp/apache-maven-*.tar.gz -C /usr/share/maven --strip-components=1 \ + && rm -f /tmp/apache-maven-*.tar.gz \ + && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn +ENV MAVEN_HOME /usr/share/maven +ENV MAVEN_CONFIG /opt/.m2 + +# install thrift +RUN wget https://archive.apache.org/dist/thrift/0.12.0/thrift-0.12.0.tar.gz -O /tmp/thrift-0.12.0.tar.gz && \ + tar xfz /tmp/thrift-0.12.0.tar.gz --directory /tmp && \ + base_dir=`pwd` && \ + cd /tmp/thrift-0.12.0 && \ + ./configure --with-cpp=no --with-c_glib=no --with-java=yes --with-python=no --with-ruby=no --with-erlang=no --with-go=no --with-nodejs=no --with-php=no && \ + make install + +CMD ["bash"] diff --git a/docker/images/pinot-base/pinot-base-build/openjdk.dockerfile b/docker/images/pinot-base/pinot-base-build/openjdk.dockerfile new file mode 100644 index 000000000000..3e0eb30adc3a --- /dev/null +++ b/docker/images/pinot-base/pinot-base-build/openjdk.dockerfile @@ -0,0 +1,52 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +ARG JAVA_VERSION=21 +ARG JDK_IMAGE=openjdk +# At 2023-06-14, slim is the only openjdk flavour without medium, high or critical vulns +FROM ${JDK_IMAGE}:${JAVA_VERSION}-jdk-slim + +LABEL MAINTAINER=dev@pinot.apache.org + +ENV MAVEN_HOME /usr/share/maven +ENV MAVEN_CONFIG /root/.m2 + +# extra dependency for running launcher +RUN apt-get update && \ + apt-get install -y --no-install-recommends vim wget curl git automake bison flex g++ libboost-all-dev libevent-dev \ + libssl-dev libtool make pkg-config && \ + rm -rf /var/lib/apt/lists/* + +# install maven +RUN mkdir -p /usr/share/maven /usr/share/maven/ref \ + && wget https://dlcdn.apache.org/maven/maven-3/3.9.7/binaries/apache-maven-3.9.7-bin.tar.gz -P /tmp \ + && tar -xzf /tmp/apache-maven-*.tar.gz -C /usr/share/maven --strip-components=1 \ + && rm -f /tmp/apache-maven-*.tar.gz \ + && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn \ + && mvn help:evaluate -Dexpression=settings.localRepository + + +# install thrift +RUN wget https://archive.apache.org/dist/thrift/0.12.0/thrift-0.12.0.tar.gz -O /tmp/thrift-0.12.0.tar.gz && \ + tar xfz /tmp/thrift-0.12.0.tar.gz --directory /tmp && \ + base_dir=`pwd` && \ + cd /tmp/thrift-0.12.0 && \ + ./configure --with-cpp=no --with-c_glib=no --with-java=yes --with-python=no --with-ruby=no --with-erlang=no --with-go=no --with-nodejs=no --with-php=no && \ + make install + +CMD ["bash"] diff --git a/docker/images/pinot-base/pinot-base-runtime/Dockerfile b/docker/images/pinot-base/pinot-base-runtime/Dockerfile deleted file mode 100644 index 7a8ade3016a4..000000000000 --- a/docker/images/pinot-base/pinot-base-runtime/Dockerfile +++ /dev/null @@ -1,39 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# -ARG JAVA_VERSION=11 -ARG OPENJDK_IMAGE=openjdk - -FROM ${OPENJDK_IMAGE}:${JAVA_VERSION}-jdk-slim - -LABEL MAINTAINER=dev@pinot.apache.org - -RUN apt-get update && \ - apt-get install -y --no-install-recommends vim less wget curl git python sysstat procps linux-perf openjdk-11-dbg && \ - rm -rf /var/lib/apt/lists/* - -RUN case `uname -m` in \ - x86_64) arch=x64; ;; \ - aarch64) arch=arm64; ;; \ - *) echo "platform=$(uname -m) un-supported, exit ..."; exit 1; ;; \ - esac \ - && mkdir -p /usr/local/lib/async-profiler \ - && curl -L https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.5.1/async-profiler-2.5.1-linux-${arch}.tar.gz | tar -xz --strip-components 1 -C /usr/local/lib/async-profiler \ - && ln -s /usr/local/lib/async-profiler/profiler.sh /usr/local/bin/async-profiler - -CMD ["bash"] diff --git a/docker/images/pinot-base/pinot-base-runtime/amazoncorretto.dockerfile b/docker/images/pinot-base/pinot-base-runtime/amazoncorretto.dockerfile new file mode 100644 index 000000000000..9568ea0bb98e --- /dev/null +++ b/docker/images/pinot-base/pinot-base-runtime/amazoncorretto.dockerfile @@ -0,0 +1,40 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +ARG JAVA_VERSION=11 +ARG JDK_IMAGE=amazoncorretto + +FROM ${JDK_IMAGE}:${JAVA_VERSION}-al2-jdk + +LABEL MAINTAINER=dev@pinot.apache.org + +RUN yum update -y && \ + yum groupinstall 'Development Tools' -y && \ + yum install -y procps vim less wget curl git python sysstat perf libtasn1 zstd && \ + yum clean all + +RUN case `uname -m` in \ + x86_64) arch=x64; ;; \ + aarch64) arch=arm64; ;; \ + *) echo "platform=$(uname -m) un-supported, exit ..."; exit 1; ;; \ + esac \ + && mkdir -p /usr/local/lib/async-profiler \ + && curl -L https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.9/async-profiler-2.9-linux-${arch}.tar.gz | tar -xz --strip-components 1 -C /usr/local/lib/async-profiler \ + && ln -s /usr/local/lib/async-profiler/profiler.sh /usr/local/bin/async-profiler + +CMD ["bash"] diff --git a/docker/images/pinot-base/pinot-base-runtime/ms-openjdk.dockerfile b/docker/images/pinot-base/pinot-base-runtime/ms-openjdk.dockerfile new file mode 100644 index 000000000000..913f08bff8fe --- /dev/null +++ b/docker/images/pinot-base/pinot-base-runtime/ms-openjdk.dockerfile @@ -0,0 +1,40 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +ARG JAVA_VERSION=11 +ARG JDK_IMAGE=mcr.microsoft.com/openjdk/jdk + +FROM ${JDK_IMAGE}:${JAVA_VERSION}-ubuntu + +LABEL MAINTAINER=dev@pinot.apache.org + +RUN apt-get update && \ + apt-get install -y --no-install-recommends vim less wget curl git python sysstat procps linux-tools-generic libtasn1-6 zstd && \ + rm -rf /var/lib/apt/lists/* + +RUN case `uname -m` in \ + x86_64) arch=x64; ;; \ + aarch64) arch=arm64; ;; \ + *) echo "platform=$(uname -m) un-supported, exit ..."; exit 1; ;; \ + esac \ + && mkdir -p /usr/local/lib/async-profiler \ + && curl -L https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.9/async-profiler-2.9-linux-${arch}.tar.gz | tar -xz --strip-components 1 -C /usr/local/lib/async-profiler \ + && ln -s /usr/local/lib/async-profiler/profiler.sh /usr/local/bin/async-profiler + +CMD ["bash"] diff --git a/docker/images/pinot-base/pinot-base-runtime/openjdk.dockerfile b/docker/images/pinot-base/pinot-base-runtime/openjdk.dockerfile new file mode 100644 index 000000000000..d2c5791c5700 --- /dev/null +++ b/docker/images/pinot-base/pinot-base-runtime/openjdk.dockerfile @@ -0,0 +1,39 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +ARG JAVA_VERSION=21 +ARG JDK_IMAGE=openjdk + +FROM ${JDK_IMAGE}:${JAVA_VERSION}-jdk-slim + +LABEL MAINTAINER=dev@pinot.apache.org + +RUN apt-get update && \ + apt-get install -y --no-install-recommends vim less wget curl git python-is-python3 sysstat procps linux-perf libtasn1-6 zstd && \ + rm -rf /var/lib/apt/lists/* + +RUN case `uname -m` in \ + x86_64) arch=x64; ;; \ + aarch64) arch=arm64; ;; \ + *) echo "platform=$(uname -m) un-supported, exit ..."; exit 1; ;; \ + esac \ + && mkdir -p /usr/local/lib/async-profiler \ + && curl -L https://github.com/jvm-profiling-tools/async-profiler/releases/download/v2.9/async-profiler-2.9-linux-${arch}.tar.gz | tar -xz --strip-components 1 -C /usr/local/lib/async-profiler \ + && ln -s /usr/local/lib/async-profiler/profiler.sh /usr/local/bin/async-profiler + +CMD ["bash"] diff --git a/docker/images/pinot-presto/Dockerfile b/docker/images/pinot-presto/Dockerfile index dc42bf2f817f..16ec15385db8 100644 --- a/docker/images/pinot-presto/Dockerfile +++ b/docker/images/pinot-presto/Dockerfile @@ -43,7 +43,7 @@ USER presto RUN git clone ${PRESTO_GIT_URL} ${PRESTO_BUILD_DIR} && \ cd ${PRESTO_BUILD_DIR} && \ git checkout ${PRESTO_BRANCH} && \ - echo "distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip" > .mvn/wrapper/maven-wrapper.properties && \ + echo "distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.9.7/apache-maven-3.9.7-bin.zip" > .mvn/wrapper/maven-wrapper.properties && \ ./mvnw clean install -DskipTests && \ mkdir -p ${PRESTO_HOME}/data && \ cp -r presto-server/target/presto-server-*/presto-server-*/* ${PRESTO_HOME} && \ diff --git a/docker/images/pinot-superset/requirements-db.txt b/docker/images/pinot-superset/requirements-db.txt index bb250db8c99e..322ab46c63af 100644 --- a/docker/images/pinot-superset/requirements-db.txt +++ b/docker/images/pinot-superset/requirements-db.txt @@ -17,4 +17,4 @@ # under the License. # pinotdb>=0.4.5 -redis==3.4.1 +redis>=4.6.0,<5.0 diff --git a/docker/images/pinot-thirdeye/Dockerfile b/docker/images/pinot-thirdeye/Dockerfile deleted file mode 100644 index db15f03d6dec..000000000000 --- a/docker/images/pinot-thirdeye/Dockerfile +++ /dev/null @@ -1,78 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -FROM openjdk:8 AS thirdeye_build_env - -LABEL MAINTAINER=dev@pinot.apache.org - -ARG PINOT_BRANCH=master -ARG PINOT_GIT_URL="https://github.com/apache/pinot.git" -RUN echo "Trying to build Thirdeye from [ ${PINOT_GIT_URL} ] on branch [ ${PINOT_BRANCH} ]" - -ENV TE_HOME=/opt/thirdeye -ENV TE_BUILD_DIR=/opt/thirdeye-build - -# extra dependency for running launcher -RUN apt-get update \ - && curl -sL https://deb.nodesource.com/setup_10.x | bash - \ - && apt-get install -y --no-install-recommends vim wget curl git automake bison flex g++ libboost-all-dev libevent-dev libssl-dev libtool make pkg-config nodejs \ - && echo '{ "allow_root": true }' > /root/.bowerrc \ - && rm -rf /var/lib/apt/lists/* - -# install maven -RUN mkdir -p /usr/share/maven /usr/share/maven/ref \ - && wget https://www.apache.org/dist/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz -P /tmp \ - && tar -xzf /tmp/apache-maven-*.tar.gz -C /usr/share/maven --strip-components=1 \ - && rm -f /tmp/apache-maven-*.tar.gz \ - && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn - -ENV MAVEN_HOME /usr/share/maven -ENV MAVEN_CONFIG /opt/.m2 - -RUN npm install -g phantomjs --unsafe-perm --ignore-scripts - -RUN git clone ${PINOT_GIT_URL} ${TE_BUILD_DIR} \ - && cd ${TE_BUILD_DIR}/thirdeye \ - && git checkout ${PINOT_BRANCH} \ - && cd thirdeye-frontend \ - && mvn clean install -X -DskipTests || exit 1 \ - && cd .. \ - && mvn clean install -X -DskipTests || exit 1 \ - && mkdir -p ${TE_HOME}/config/default \ - && mkdir -p ${TE_HOME}/bin \ - && cp -rp ${TE_BUILD_DIR}/thirdeye/thirdeye-pinot/config/* ${TE_HOME}/config/default/. \ - && rm ${TE_BUILD_DIR}/thirdeye/thirdeye-pinot/target/thirdeye-pinot-*-sources.jar \ - && cp ${TE_BUILD_DIR}/thirdeye/thirdeye-pinot/target/thirdeye-pinot-*.jar ${TE_HOME}/bin/thirdeye-pinot.jar \ - && rm -rf ${TE_BUILD_DIR} - -FROM openjdk:8-jdk-slim - -LABEL MAINTAINER=dev@pinot.apache.org - -ENV TE_HOME=/opt/thirdeye - -COPY --from=thirdeye_build_env ${TE_HOME} ${TE_HOME} -COPY bin ${TE_HOME}/bin -COPY config ${TE_HOME}/config - -VOLUME ["${TE_HOME}/config"] -EXPOSE 1426 1427 -WORKDIR ${TE_HOME} - -ENTRYPOINT ["./bin/start-thirdeye.sh"] diff --git a/docker/images/pinot-thirdeye/README.md b/docker/images/pinot-thirdeye/README.md deleted file mode 100644 index de2b67a4c68d..000000000000 --- a/docker/images/pinot-thirdeye/README.md +++ /dev/null @@ -1,102 +0,0 @@ - - -# docker-pinot-thirdeye -This is a docker image of [Apache Thirdeye](https://github.com/apache/pinot/tree/master/thirdeye). - -## How to build a docker image - -There is a docker build script which will build a given Git repo/branch and tag the image. - -Usage: - -```SHELL -./docker-build.sh [Docker Tag] [Git Branch] [Pinot Git URL] -``` - -This script will check out Pinot Repo `[Pinot Git URL]` on branch `[Git Branch]` and build the docker image for that. - -The docker image is tagged as `[Docker Tag]`. - -`Docker Tag`: Name and tag your docker image. Default is `thirdeye:latest`. - -`Git Branch`: The Pinot branch to build. Default is `master`. - -`Pinot Git URL`: The Pinot Git Repo to build, users can set it to their own fork. Please note that, the URL is `https://` based, not `git://`. Default is the Apache Repo: `https://github.com/apache/pinot.git`. - -* Example of building and tagging a snapshot on your own fork: -```SHELL -./docker-build.sh thirdeye:latest master https://github.com/apache/pinot.git -``` - -## How to publish a docker image - -Script `docker-push.sh` publishes a given docker image to your docker registry. - -In order to push to your own repo, the image needs to be explicitly tagged with the repo name. - -* Example of publishing a image to [apachepinot/thirdeye](https://cloud.docker.com/u/apachepinot/repository/docker/apachepinot/thirdeye) dockerHub repo. - -```SHELL -docker tag thirdeye:latest apachepinot/thirdeye:latest -./docker-push.sh apachepinot/thirdeye:latest -``` - -Script `docker-build-and-push.sh` builds and publishes this docker image to your docker registry after build. - -* Example of building and publishing a image to [apachepinot/thirdeye](https://cloud.docker.com/u/apachepinot/repository/docker/apachepinot/thirdeye) dockerHub repo. - -```SHELL -./docker-build-and-push.sh apachepinot/thirdeye:latest master https://github.com/apache/pinot.git -``` - -## How to Run it - -The entry point of docker image is `start-thirdeye.sh` script. - -* Create an isolated bridge network in docker. - -```SHELL -docker network create -d bridge pinot-demo -``` - -* Start Pinot Batch Quickstart - -```SHELL -docker run \ - --network=pinot-demo \ - --name pinot-quickstart \ - -p 9000:9000 \ - -d apachepinot/pinot:latest QuickStart \ - -type batch -``` - -* Start ThirdEye companion - -```SHELL -docker run \ - --network=pinot-demo \ - --name thirdeye-backend \ - -p 1426:1426 \ - -p 1427:1427 \ - -d apachepinot/thirdeye:latest pinot-quickstart -``` - diff --git a/docker/images/pinot-thirdeye/bin/start-thirdeye.sh b/docker/images/pinot-thirdeye/bin/start-thirdeye.sh deleted file mode 100755 index 2fbfc72877b4..000000000000 --- a/docker/images/pinot-thirdeye/bin/start-thirdeye.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/bin/bash -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -# Script Usage -# --------------------------------------------- -# ./start-thirdeye.sh ${CONFIG_DIR} ${MODE} -# -# - CONFIG_DIR: path to the thirdeye configuration director -# - MODE: Choices: {frontend, backend, * } -# frontend: Start the frontend server only -# backend: Start the backend server only -# database: Start the H@ db service. Also initializes the db with the required tables -# For any other value, defaults to starting all services with an h2 db. -# - -if [[ "$#" -gt 0 ]] -then - CONFIG_DIR="./config/$1" -else - CONFIG_DIR="./config/default" -fi - - -function start_server { - class_ref=$1 - config_dir=$2 - java -Dlog4j.configurationFile=log4j2.xml -cp "./bin/thirdeye-pinot.jar" ${class_ref} "${config_dir}" -} - -function start_frontend { - echo "Running Thirdeye frontend config: ${CONFIG_DIR}" - start_server org.apache.pinot.thirdeye.dashboard.ThirdEyeDashboardApplication "${CONFIG_DIR}" -} - -function start_backend { - echo "Running Thirdeye backend config: ${CONFIG_DIR}" - start_server org.apache.pinot.thirdeye.anomaly.ThirdEyeAnomalyApplication "${CONFIG_DIR}" -} - - -function start_db { - echo "Starting H2 database server" - java -cp "./bin/thirdeye-pinot.jar" org.h2.tools.Server -tcp -baseDir "${CONFIG_DIR}/.." & - sleep 1 - - echo "Creating ThirdEye database schema" - java -cp "./bin/thirdeye-pinot.jar" org.h2.tools.RunScript -user "sa" -password "sa" -url "jdbc:h2:tcp:localhost/h2db" -script "zip:./bin/thirdeye-pinot.jar!/schema/create-schema.sql" - - if [ -f "${CONFIG_DIR}/bootstrap.sql" ]; then - echo "Running database bootstrap script ${CONFIG_DIR}/bootstrap.sql" - java -cp "./bin/thirdeye-pinot.jar" org.h2.tools.RunScript -user "sa" -password "sa" -url "jdbc:h2:tcp:localhost/h2db" -script "${CONFIG_DIR}/bootstrap.sql" - fi -} - -function start_all { - start_db - - start_backend & - sleep 10 - - start_frontend & - wait -} - -MODE=$2 -case ${MODE} in - "frontend" ) start_frontend ;; - "backend" ) start_backend ;; - "database" ) start_db;; - * ) start_all ;; -esac diff --git a/docker/images/pinot-thirdeye/config/ephemeral/dashboard.yml b/docker/images/pinot-thirdeye/config/ephemeral/dashboard.yml deleted file mode 100644 index c5298b19ce5a..000000000000 --- a/docker/images/pinot-thirdeye/config/ephemeral/dashboard.yml +++ /dev/null @@ -1,40 +0,0 @@ -logging: - type: external -authConfig: - authEnabled: false - authKey: "" - ldapUrl: "" - domainSuffix: [] - cacheTTL: 3600 - cookieTTL: 604800 -resourceConfig: [] -rootCause: - definitionsPath: rca.yml - parallelism: 5 - formatters: - - org.apache.pinot.thirdeye.dashboard.resources.v2.rootcause.AnomalyEventFormatter - - org.apache.pinot.thirdeye.dashboard.resources.v2.rootcause.ThirdEyeEventFormatter - - org.apache.pinot.thirdeye.dashboard.resources.v2.rootcause.MetricEntityFormatter - - org.apache.pinot.thirdeye.dashboard.resources.v2.rootcause.DimensionEntityFormatter - - org.apache.pinot.thirdeye.dashboard.resources.v2.rootcause.ServiceEntityFormatter - - org.apache.pinot.thirdeye.dashboard.resources.v2.rootcause.DefaultEventEntityFormatter -dashboardHost: "http://thirdeye-dashboard:1426" -failureFromAddress: thirdeye@localhost -failureToAddress: user@localhost -alerterConfiguration: - smtpConfiguration: - smtpHost: localhost - smtpPort: 25 -server: - requestLog: - type: external - type: default - applicationConnectors: - - type: http - port: 1426 - adminConnectors: - - type: http - port: 1427 -whitelistDatasets: [] -swagger: - resourcePackage: "org.apache.pinot.thirdeye.dashboard.resources,org.apache.pinot.thirdeye.dashboard.resources.v2,org.apache.pinot.thirdeye.anomaly.onboard,org.apache.pinot.thirdeye.detection,org.apache.pinot.thirdeye.detection.yaml" diff --git a/docker/images/pinot-thirdeye/config/ephemeral/data-sources/cache-config.yml b/docker/images/pinot-thirdeye/config/ephemeral/data-sources/cache-config.yml deleted file mode 100644 index 5672706ba36f..000000000000 --- a/docker/images/pinot-thirdeye/config/ephemeral/data-sources/cache-config.yml +++ /dev/null @@ -1,34 +0,0 @@ -useInMemoryCache: true -useCentralizedCache: false - -centralizedCacheSettings: - # TTL (time-to-live) for documents in seconds - ttl: 3600 - # if inserting data points individually, max number of threads to spawn to parallel insert at a time - maxParallelInserts: 10 - # which store to use - cacheDataStoreName: 'couchbase' - cacheDataSources: - couchbase: - className: org.apache.pinot.thirdeye.detection.cache.CouchbaseCacheDAO - config: - useCertificateBasedAuthentication: false - # at least 1 host needed - hosts: - - 'host1' # ex. http://localhost:8091 - - 'host2' # ex. http://localhost:8092 - - 'host3' # ex. http://localhost:8093 - - 'host4' # and so on... - bucketName: 'your_bucket_name' - # if using certificate-based authentication, authUsername and authPassword values don't matter and won't be used - authUsername: 'your_bucket_user_username' - authPassword: 'your_bucket_user_password' - enableDnsSrv: false - # certificate based authentication is only available in Couchbase enterprise edition. - keyStoreFilePath: 'key/store/path/keystore_file' # e.g. '/var/identity.p12' - # if your keystore has a password, enter it here. by default, Java uses 'work_around_jdk-6879539' to enable empty passwords for certificates. - keyStorePassword: 'work_around_jdk-6879539' - trustStoreFilePath: 'trust/store/path/truststore_file' # e.g. '/etc/riddler/cacerts' - trustStorePassword: '' - # add your store of choice here - diff --git a/docker/images/pinot-thirdeye/config/ephemeral/data-sources/data-sources-config-backend.yml b/docker/images/pinot-thirdeye/config/ephemeral/data-sources/data-sources-config-backend.yml deleted file mode 100644 index 97ee45121a76..000000000000 --- a/docker/images/pinot-thirdeye/config/ephemeral/data-sources/data-sources-config-backend.yml +++ /dev/null @@ -1,189 +0,0 @@ - -# Please put the mock data source as the first in this configuration. -dataSourceConfigs: - - className: org.apache.pinot.thirdeye.datasource.mock.MockThirdEyeDataSource - properties: - populateMetaData: false - lookback: 90 - datasets: - tracking: - granularity: "1hour" - timezone: "America/Los_Angeles" - dimensions: [country, browser, platform] - metrics: - pageViews: - us: - chrome: - desktop: - mean: 100 - std: 20 - mobile: - mean: 200 - std: 50 - safari: - desktop: - mean: 70 - std: 15 - mobile: - mean: 250 - std: 40 - firefox: - desktop: - mean: 30 - std: 5 - mobile: - mean: 20 - std: 3 - edge: - desktop: - mean: 85 - std: 18 - ca: - chrome: - desktop: - mean: 30 - std: 5 - mobile: - mean: 70 - std: 8 - safari: - desktop: - mean: 20 - std: 3 - mobile: - mean: 60 - std: 8 - firefox: - desktop: - mean: 12 - std: 2 - mobile: - mean: 7 - std: 1 - edge: - desktop: - mean: 32 - std: 10 - mx: - chrome: - desktop: - mean: 40 - std: 6 - mobile: - mean: 80 - std: 10 - safari: - desktop: - mean: 20 - std: 3 - mobile: - mean: 65 - std: 10 - firefox: - desktop: - mean: 15 - std: 3 - mobile: - mean: 12 - std: 2 - edge: - desktop: - mean: 32 - std: 9 - - adImpressions: - us: - chrome: - desktop: - mean: 50 - std: 10 - mobile: - mean: 100 - std: 25 - safari: - desktop: - mean: 35 - std: 7 - mobile: - mean: 125 - std: 20 - firefox: - desktop: - mean: 15 - std: 2 - mobile: - mean: 10 - std: 1 - edge: - desktop: - mean: 42 - std: 9 - - business: - granularity: "1day" - timezone: "America/Los_Angeles" - dimensions: [country, browser] - metrics: - purchases: - us: - chrome: - mean: 16 - std: 2 - safari: - mean: 19 - std: 3 - edge: - mean: 4 - std: 1 - ca: - chrome: - mean: 5 - std: 1 - safari: - mean: 5 - std: 2 - edge: - mean: 1 - std: 1 - mx: - chrome: - mean: 3 - std: 1 - safari: - mean: 5 - std: 2 - edge: - mean: 2 - std: 1 - - revenue: - us: - chrome: - mean: 160 - std: 20 - safari: - mean: 190 - std: 30 - edge: - mean: 40 - std: 10 - ca: - chrome: - mean: 50 - std: 10 - safari: - mean: 50 - std: 15 - edge: - mean: 10 - std: 2 - mx: - chrome: - mean: 30 - std: 6 - safari: - mean: 50 - std: 15 - edge: - mean: 20 - std: 3 diff --git a/docker/images/pinot-thirdeye/config/ephemeral/data-sources/data-sources-config-frontend.yml b/docker/images/pinot-thirdeye/config/ephemeral/data-sources/data-sources-config-frontend.yml deleted file mode 100644 index b2b083d149da..000000000000 --- a/docker/images/pinot-thirdeye/config/ephemeral/data-sources/data-sources-config-frontend.yml +++ /dev/null @@ -1,189 +0,0 @@ - -# Please put the mock data source as the first in this configuration. -dataSourceConfigs: - - className: org.apache.pinot.thirdeye.datasource.mock.MockThirdEyeDataSource - properties: - populateMetaData: true - lookback: 90 - datasets: - tracking: - granularity: "1hour" - timezone: "America/Los_Angeles" - dimensions: [country, browser, platform] - metrics: - pageViews: - us: - chrome: - desktop: - mean: 100 - std: 20 - mobile: - mean: 200 - std: 50 - safari: - desktop: - mean: 70 - std: 15 - mobile: - mean: 250 - std: 40 - firefox: - desktop: - mean: 30 - std: 5 - mobile: - mean: 20 - std: 3 - edge: - desktop: - mean: 85 - std: 18 - ca: - chrome: - desktop: - mean: 30 - std: 5 - mobile: - mean: 70 - std: 8 - safari: - desktop: - mean: 20 - std: 3 - mobile: - mean: 60 - std: 8 - firefox: - desktop: - mean: 12 - std: 2 - mobile: - mean: 7 - std: 1 - edge: - desktop: - mean: 32 - std: 10 - mx: - chrome: - desktop: - mean: 40 - std: 6 - mobile: - mean: 80 - std: 10 - safari: - desktop: - mean: 20 - std: 3 - mobile: - mean: 65 - std: 10 - firefox: - desktop: - mean: 15 - std: 3 - mobile: - mean: 12 - std: 2 - edge: - desktop: - mean: 32 - std: 9 - - adImpressions: - us: - chrome: - desktop: - mean: 50 - std: 10 - mobile: - mean: 100 - std: 25 - safari: - desktop: - mean: 35 - std: 7 - mobile: - mean: 125 - std: 20 - firefox: - desktop: - mean: 15 - std: 2 - mobile: - mean: 10 - std: 1 - edge: - desktop: - mean: 42 - std: 9 - - business: - granularity: "1day" - timezone: "America/Los_Angeles" - dimensions: [country, browser] - metrics: - purchases: - us: - chrome: - mean: 16 - std: 2 - safari: - mean: 19 - std: 3 - edge: - mean: 4 - std: 1 - ca: - chrome: - mean: 5 - std: 1 - safari: - mean: 5 - std: 2 - edge: - mean: 1 - std: 1 - mx: - chrome: - mean: 3 - std: 1 - safari: - mean: 5 - std: 2 - edge: - mean: 2 - std: 1 - - revenue: - us: - chrome: - mean: 160 - std: 20 - safari: - mean: 190 - std: 30 - edge: - mean: 40 - std: 10 - ca: - chrome: - mean: 50 - std: 10 - safari: - mean: 50 - std: 15 - edge: - mean: 10 - std: 2 - mx: - chrome: - mean: 30 - std: 6 - safari: - mean: 50 - std: 15 - edge: - mean: 20 - std: 3 diff --git a/docker/images/pinot-thirdeye/config/ephemeral/data/README.md b/docker/images/pinot-thirdeye/config/ephemeral/data/README.md deleted file mode 100644 index 6f12739ddeee..000000000000 --- a/docker/images/pinot-thirdeye/config/ephemeral/data/README.md +++ /dev/null @@ -1,7 +0,0 @@ -`daily.csv`: daily data - -From Kaggle: https://www.kaggle.com/marklvl/bike-sharing-dataset#Bike-Sharing-Dataset.zip - - `hourly.csv`: hourly data - - From Kaggle: https://www.kaggle.com/robikscube/hourly-energy-consumption \ No newline at end of file diff --git a/docker/images/pinot-thirdeye/config/ephemeral/data/daily.csv b/docker/images/pinot-thirdeye/config/ephemeral/data/daily.csv deleted file mode 100644 index 094897484692..000000000000 --- a/docker/images/pinot-thirdeye/config/ephemeral/data/daily.csv +++ /dev/null @@ -1,732 +0,0 @@ -date,value -2011-01-01,985 -2011-01-02,801 -2011-01-03,1349 -2011-01-04,1562 -2011-01-05,1600 -2011-01-06,1606 -2011-01-07,1510 -2011-01-08,959 -2011-01-09,822 -2011-01-10,1321 -2011-01-11,1263 -2011-01-12,1162 -2011-01-13,1406 -2011-01-14,1421 -2011-01-15,1248 -2011-01-16,1204 -2011-01-17,1000 -2011-01-18,683 -2011-01-19,1650 -2011-01-20,1927 -2011-01-21,1543 -2011-01-22,981 -2011-01-23,986 -2011-01-24,1416 -2011-01-25,1985 -2011-01-26,506 -2011-01-27,431 -2011-01-28,1167 -2011-01-29,1098 -2011-01-30,1096 -2011-01-31,1501 -2011-02-01,1360 -2011-02-02,1526 -2011-02-03,1550 -2011-02-04,1708 -2011-02-05,1005 -2011-02-06,1623 -2011-02-07,1712 -2011-02-08,1530 -2011-02-09,1605 -2011-02-10,1538 -2011-02-11,1746 -2011-02-12,1472 -2011-02-13,1589 -2011-02-14,1913 -2011-02-15,1815 -2011-02-16,2115 -2011-02-17,2475 -2011-02-18,2927 -2011-02-19,1635 -2011-02-20,1812 -2011-02-21,1107 -2011-02-22,1450 -2011-02-23,1917 -2011-02-24,1807 -2011-02-25,1461 -2011-02-26,1969 -2011-02-27,2402 -2011-02-28,1446 -2011-03-01,1851 -2011-03-02,2134 -2011-03-03,1685 -2011-03-04,1944 -2011-03-05,2077 -2011-03-06,605 -2011-03-07,1872 -2011-03-08,2133 -2011-03-09,1891 -2011-03-10,623 -2011-03-11,1977 -2011-03-12,2132 -2011-03-13,2417 -2011-03-14,2046 -2011-03-15,2056 -2011-03-16,2192 -2011-03-17,2744 -2011-03-18,3239 -2011-03-19,3117 -2011-03-20,2471 -2011-03-21,2077 -2011-03-22,2703 -2011-03-23,2121 -2011-03-24,1865 -2011-03-25,2210 -2011-03-26,2496 -2011-03-27,1693 -2011-03-28,2028 -2011-03-29,2425 -2011-03-30,1536 -2011-03-31,1685 -2011-04-01,2227 -2011-04-02,2252 -2011-04-03,3249 -2011-04-04,3115 -2011-04-05,1795 -2011-04-06,2808 -2011-04-07,3141 -2011-04-08,1471 -2011-04-09,2455 -2011-04-10,2895 -2011-04-11,3348 -2011-04-12,2034 -2011-04-13,2162 -2011-04-14,3267 -2011-04-15,3126 -2011-04-16,795 -2011-04-17,3744 -2011-04-18,3429 -2011-04-19,3204 -2011-04-20,3944 -2011-04-21,4189 -2011-04-22,1683 -2011-04-23,4036 -2011-04-24,4191 -2011-04-25,4073 -2011-04-26,4400 -2011-04-27,3872 -2011-04-28,4058 -2011-04-29,4595 -2011-04-30,5312 -2011-05-01,3351 -2011-05-02,4401 -2011-05-03,4451 -2011-05-04,2633 -2011-05-05,4433 -2011-05-06,4608 -2011-05-07,4714 -2011-05-08,4333 -2011-05-09,4362 -2011-05-10,4803 -2011-05-11,4182 -2011-05-12,4864 -2011-05-13,4105 -2011-05-14,3409 -2011-05-15,4553 -2011-05-16,3958 -2011-05-17,4123 -2011-05-18,3855 -2011-05-19,4575 -2011-05-20,4917 -2011-05-21,5805 -2011-05-22,4660 -2011-05-23,4274 -2011-05-24,4492 -2011-05-25,4978 -2011-05-26,4677 -2011-05-27,4679 -2011-05-28,4758 -2011-05-29,4788 -2011-05-30,4098 -2011-05-31,3982 -2011-06-01,3974 -2011-06-02,4968 -2011-06-03,5312 -2011-06-04,5342 -2011-06-05,4906 -2011-06-06,4548 -2011-06-07,4833 -2011-06-08,4401 -2011-06-09,3915 -2011-06-10,4586 -2011-06-11,4966 -2011-06-12,4460 -2011-06-13,5020 -2011-06-14,4891 -2011-06-15,5180 -2011-06-16,3767 -2011-06-17,4844 -2011-06-18,5119 -2011-06-19,4744 -2011-06-20,4010 -2011-06-21,4835 -2011-06-22,4507 -2011-06-23,4790 -2011-06-24,4991 -2011-06-25,5202 -2011-06-26,5305 -2011-06-27,4708 -2011-06-28,4648 -2011-06-29,5225 -2011-06-30,5515 -2011-07-01,5362 -2011-07-02,5119 -2011-07-03,4649 -2011-07-04,6043 -2011-07-05,4665 -2011-07-06,4629 -2011-07-07,4592 -2011-07-08,4040 -2011-07-09,5336 -2011-07-10,4881 -2011-07-11,4086 -2011-07-12,4258 -2011-07-13,4342 -2011-07-14,5084 -2011-07-15,5538 -2011-07-16,5923 -2011-07-17,5302 -2011-07-18,4458 -2011-07-19,4541 -2011-07-20,4332 -2011-07-21,3784 -2011-07-22,3387 -2011-07-23,3285 -2011-07-24,3606 -2011-07-25,3840 -2011-07-26,4590 -2011-07-27,4656 -2011-07-28,4390 -2011-07-29,3846 -2011-07-30,4475 -2011-07-31,4302 -2011-08-01,4266 -2011-08-02,4845 -2011-08-03,3574 -2011-08-04,4576 -2011-08-05,4866 -2011-08-06,4294 -2011-08-07,3785 -2011-08-08,4326 -2011-08-09,4602 -2011-08-10,4780 -2011-08-11,4792 -2011-08-12,4905 -2011-08-13,4150 -2011-08-14,3820 -2011-08-15,4338 -2011-08-16,4725 -2011-08-17,4694 -2011-08-18,3805 -2011-08-19,4153 -2011-08-20,5191 -2011-08-21,3873 -2011-08-22,4758 -2011-08-23,5895 -2011-08-24,5130 -2011-08-25,3542 -2011-08-26,4661 -2011-08-27,1115 -2011-08-28,4334 -2011-08-29,4634 -2011-08-30,5204 -2011-08-31,5058 -2011-09-01,5115 -2011-09-02,4727 -2011-09-03,4484 -2011-09-04,4940 -2011-09-05,3351 -2011-09-06,2710 -2011-09-07,1996 -2011-09-08,1842 -2011-09-09,3544 -2011-09-10,5345 -2011-09-11,5046 -2011-09-12,4713 -2011-09-13,4763 -2011-09-14,4785 -2011-09-15,3659 -2011-09-16,4760 -2011-09-17,4511 -2011-09-18,4274 -2011-09-19,4539 -2011-09-20,3641 -2011-09-21,4352 -2011-09-22,4795 -2011-09-23,2395 -2011-09-24,5423 -2011-09-25,5010 -2011-09-26,4630 -2011-09-27,4120 -2011-09-28,3907 -2011-09-29,4839 -2011-09-30,5202 -2011-10-01,2429 -2011-10-02,2918 -2011-10-03,3570 -2011-10-04,4456 -2011-10-05,4826 -2011-10-06,4765 -2011-10-07,4985 -2011-10-08,5409 -2011-10-09,5511 -2011-10-10,5117 -2011-10-11,4563 -2011-10-12,2416 -2011-10-13,2913 -2011-10-14,3644 -2011-10-15,5217 -2011-10-16,5041 -2011-10-17,4570 -2011-10-18,4748 -2011-10-19,2424 -2011-10-20,4195 -2011-10-21,4304 -2011-10-22,4308 -2011-10-23,4381 -2011-10-24,4187 -2011-10-25,4687 -2011-10-26,3894 -2011-10-27,2659 -2011-10-28,3747 -2011-10-29,627 -2011-10-30,3331 -2011-10-31,3669 -2011-11-01,4068 -2011-11-02,4186 -2011-11-03,3974 -2011-11-04,4046 -2011-11-05,3926 -2011-11-06,3649 -2011-11-07,4035 -2011-11-08,4205 -2011-11-09,4109 -2011-11-10,2933 -2011-11-11,3368 -2011-11-12,4067 -2011-11-13,3717 -2011-11-14,4486 -2011-11-15,4195 -2011-11-16,1817 -2011-11-17,3053 -2011-11-18,3392 -2011-11-19,3663 -2011-11-20,3520 -2011-11-21,2765 -2011-11-22,1607 -2011-11-23,2566 -2011-11-24,1495 -2011-11-25,2792 -2011-11-26,3068 -2011-11-27,3071 -2011-11-28,3867 -2011-11-29,2914 -2011-11-30,3613 -2011-12-01,3727 -2011-12-02,3940 -2011-12-03,3614 -2011-12-04,3485 -2011-12-05,3811 -2011-12-06,2594 -2011-12-07,705 -2011-12-08,3322 -2011-12-09,3620 -2011-12-10,3190 -2011-12-11,2743 -2011-12-12,3310 -2011-12-13,3523 -2011-12-14,3740 -2011-12-15,3709 -2011-12-16,3577 -2011-12-17,2739 -2011-12-18,2431 -2011-12-19,3403 -2011-12-20,3750 -2011-12-21,2660 -2011-12-22,3068 -2011-12-23,2209 -2011-12-24,1011 -2011-12-25,754 -2011-12-26,1317 -2011-12-27,1162 -2011-12-28,2302 -2011-12-29,2423 -2011-12-30,2999 -2011-12-31,2485 -2012-01-01,2294 -2012-01-02,1951 -2012-01-03,2236 -2012-01-04,2368 -2012-01-05,3272 -2012-01-06,4098 -2012-01-07,4521 -2012-01-08,3425 -2012-01-09,2376 -2012-01-10,3598 -2012-01-11,2177 -2012-01-12,4097 -2012-01-13,3214 -2012-01-14,2493 -2012-01-15,2311 -2012-01-16,2298 -2012-01-17,2935 -2012-01-18,3376 -2012-01-19,3292 -2012-01-20,3163 -2012-01-21,1301 -2012-01-22,1977 -2012-01-23,2432 -2012-01-24,4339 -2012-01-25,4270 -2012-01-26,4075 -2012-01-27,3456 -2012-01-28,4023 -2012-01-29,3243 -2012-01-30,3624 -2012-01-31,4509 -2012-02-01,4579 -2012-02-02,3761 -2012-02-03,4151 -2012-02-04,2832 -2012-02-05,2947 -2012-02-06,3784 -2012-02-07,4375 -2012-02-08,2802 -2012-02-09,3830 -2012-02-10,3831 -2012-02-11,2169 -2012-02-12,1529 -2012-02-13,3422 -2012-02-14,3922 -2012-02-15,4169 -2012-02-16,3005 -2012-02-17,4154 -2012-02-18,4318 -2012-02-19,2689 -2012-02-20,3129 -2012-02-21,3777 -2012-02-22,4773 -2012-02-23,5062 -2012-02-24,3487 -2012-02-25,2732 -2012-02-26,3389 -2012-02-27,4322 -2012-02-28,4363 -2012-02-29,1834 -2012-03-01,4990 -2012-03-02,3194 -2012-03-03,4066 -2012-03-04,3423 -2012-03-05,3333 -2012-03-06,3956 -2012-03-07,4916 -2012-03-08,5382 -2012-03-09,4569 -2012-03-10,4118 -2012-03-11,4911 -2012-03-12,5298 -2012-03-13,5847 -2012-03-14,6312 -2012-03-15,6192 -2012-03-16,4378 -2012-03-17,7836 -2012-03-18,5892 -2012-03-19,6153 -2012-03-20,6093 -2012-03-21,6230 -2012-03-22,6871 -2012-03-23,8362 -2012-03-24,3372 -2012-03-25,4996 -2012-03-26,5558 -2012-03-27,5102 -2012-03-28,5698 -2012-03-29,6133 -2012-03-30,5459 -2012-03-31,6235 -2012-04-01,6041 -2012-04-02,5936 -2012-04-03,6772 -2012-04-04,6436 -2012-04-05,6457 -2012-04-06,6460 -2012-04-07,6857 -2012-04-08,5169 -2012-04-09,5585 -2012-04-10,5918 -2012-04-11,4862 -2012-04-12,5409 -2012-04-13,6398 -2012-04-14,7460 -2012-04-15,7132 -2012-04-16,6370 -2012-04-17,6691 -2012-04-18,4367 -2012-04-19,6565 -2012-04-20,7290 -2012-04-21,6624 -2012-04-22,1027 -2012-04-23,3214 -2012-04-24,5633 -2012-04-25,6196 -2012-04-26,5026 -2012-04-27,6233 -2012-04-28,4220 -2012-04-29,6304 -2012-04-30,5572 -2012-05-01,5740 -2012-05-02,6169 -2012-05-03,6421 -2012-05-04,6296 -2012-05-05,6883 -2012-05-06,6359 -2012-05-07,6273 -2012-05-08,5728 -2012-05-09,4717 -2012-05-10,6572 -2012-05-11,7030 -2012-05-12,7429 -2012-05-13,6118 -2012-05-14,2843 -2012-05-15,5115 -2012-05-16,7424 -2012-05-17,7384 -2012-05-18,7639 -2012-05-19,8294 -2012-05-20,7129 -2012-05-21,4359 -2012-05-22,6073 -2012-05-23,5260 -2012-05-24,6770 -2012-05-25,6734 -2012-05-26,6536 -2012-05-27,6591 -2012-05-28,6043 -2012-05-29,5743 -2012-05-30,6855 -2012-05-31,7338 -2012-06-01,4127 -2012-06-02,8120 -2012-06-03,7641 -2012-06-04,6998 -2012-06-05,7001 -2012-06-06,7055 -2012-06-07,7494 -2012-06-08,7736 -2012-06-09,7498 -2012-06-10,6598 -2012-06-11,6664 -2012-06-12,4972 -2012-06-13,7421 -2012-06-14,7363 -2012-06-15,7665 -2012-06-16,7702 -2012-06-17,6978 -2012-06-18,5099 -2012-06-19,6825 -2012-06-20,6211 -2012-06-21,5905 -2012-06-22,5823 -2012-06-23,7458 -2012-06-24,6891 -2012-06-25,6779 -2012-06-26,7442 -2012-06-27,7335 -2012-06-28,6879 -2012-06-29,5463 -2012-06-30,5687 -2012-07-01,5531 -2012-07-02,6227 -2012-07-03,6660 -2012-07-04,7403 -2012-07-05,6241 -2012-07-06,6207 -2012-07-07,4840 -2012-07-08,4672 -2012-07-09,6569 -2012-07-10,6290 -2012-07-11,7264 -2012-07-12,7446 -2012-07-13,7499 -2012-07-14,6969 -2012-07-15,6031 -2012-07-16,6830 -2012-07-17,6786 -2012-07-18,5713 -2012-07-19,6591 -2012-07-20,5870 -2012-07-21,4459 -2012-07-22,7410 -2012-07-23,6966 -2012-07-24,7592 -2012-07-25,8173 -2012-07-26,6861 -2012-07-27,6904 -2012-07-28,6685 -2012-07-29,6597 -2012-07-30,7105 -2012-07-31,7216 -2012-08-01,7580 -2012-08-02,7261 -2012-08-03,7175 -2012-08-04,6824 -2012-08-05,5464 -2012-08-06,7013 -2012-08-07,7273 -2012-08-08,7534 -2012-08-09,7286 -2012-08-10,5786 -2012-08-11,6299 -2012-08-12,6544 -2012-08-13,6883 -2012-08-14,6784 -2012-08-15,7347 -2012-08-16,7605 -2012-08-17,7148 -2012-08-18,7865 -2012-08-19,4549 -2012-08-20,6530 -2012-08-21,7006 -2012-08-22,7375 -2012-08-23,7765 -2012-08-24,7582 -2012-08-25,6053 -2012-08-26,5255 -2012-08-27,6917 -2012-08-28,7040 -2012-08-29,7697 -2012-08-30,7713 -2012-08-31,7350 -2012-09-01,6140 -2012-09-02,5810 -2012-09-03,6034 -2012-09-04,6864 -2012-09-05,7112 -2012-09-06,6203 -2012-09-07,7504 -2012-09-08,5976 -2012-09-09,8227 -2012-09-10,7525 -2012-09-11,7767 -2012-09-12,7870 -2012-09-13,7804 -2012-09-14,8009 -2012-09-15,8714 -2012-09-16,7333 -2012-09-17,6869 -2012-09-18,4073 -2012-09-19,7591 -2012-09-20,7720 -2012-09-21,8167 -2012-09-22,8395 -2012-09-23,7907 -2012-09-24,7436 -2012-09-25,7538 -2012-09-26,7733 -2012-09-27,7393 -2012-09-28,7415 -2012-09-29,8555 -2012-09-30,6889 -2012-10-01,6778 -2012-10-02,4639 -2012-10-03,7572 -2012-10-04,7328 -2012-10-05,8156 -2012-10-06,7965 -2012-10-07,3510 -2012-10-08,5478 -2012-10-09,6392 -2012-10-10,7691 -2012-10-11,7570 -2012-10-12,7282 -2012-10-13,7109 -2012-10-14,6639 -2012-10-15,5875 -2012-10-16,7534 -2012-10-17,7461 -2012-10-18,7509 -2012-10-19,5424 -2012-10-20,8090 -2012-10-21,6824 -2012-10-22,7058 -2012-10-23,7466 -2012-10-24,7693 -2012-10-25,7359 -2012-10-26,7444 -2012-10-27,7852 -2012-10-28,4459 -2012-10-29,22 -2012-10-30,1096 -2012-10-31,5566 -2012-11-01,5986 -2012-11-02,5847 -2012-11-03,5138 -2012-11-04,5107 -2012-11-05,5259 -2012-11-06,5686 -2012-11-07,5035 -2012-11-08,5315 -2012-11-09,5992 -2012-11-10,6536 -2012-11-11,6852 -2012-11-12,6269 -2012-11-13,4094 -2012-11-14,5495 -2012-11-15,5445 -2012-11-16,5698 -2012-11-17,5629 -2012-11-18,4669 -2012-11-19,5499 -2012-11-20,5634 -2012-11-21,5146 -2012-11-22,2425 -2012-11-23,3910 -2012-11-24,2277 -2012-11-25,2424 -2012-11-26,5087 -2012-11-27,3959 -2012-11-28,5260 -2012-11-29,5323 -2012-11-30,5668 -2012-12-01,5191 -2012-12-02,4649 -2012-12-03,6234 -2012-12-04,6606 -2012-12-05,5729 -2012-12-06,5375 -2012-12-07,5008 -2012-12-08,5582 -2012-12-09,3228 -2012-12-10,5170 -2012-12-11,5501 -2012-12-12,5319 -2012-12-13,5532 -2012-12-14,5611 -2012-12-15,5047 -2012-12-16,3786 -2012-12-17,4585 -2012-12-18,5557 -2012-12-19,5267 -2012-12-20,4128 -2012-12-21,3623 -2012-12-22,1749 -2012-12-23,1787 -2012-12-24,920 -2012-12-25,1013 -2012-12-26,441 -2012-12-27,2114 -2012-12-28,3095 -2012-12-29,1341 -2012-12-30,1796 -2012-12-31,2729 diff --git a/docker/images/pinot-thirdeye/config/ephemeral/data/hourly.csv b/docker/images/pinot-thirdeye/config/ephemeral/data/hourly.csv deleted file mode 100755 index 7714a9380334..000000000000 --- a/docker/images/pinot-thirdeye/config/ephemeral/data/hourly.csv +++ /dev/null @@ -1,66498 +0,0 @@ -datetime,value -2011-12-31 01:00:00,9970.0 -2011-12-31 02:00:00,9428.0 -2011-12-31 03:00:00,9059.0 -2011-12-31 04:00:00,8817.0 -2011-12-31 05:00:00,8743.0 -2011-12-31 06:00:00,8735.0 -2011-12-31 07:00:00,8993.0 -2011-12-31 08:00:00,9363.0 -2011-12-31 09:00:00,9545.0 -2011-12-31 10:00:00,9676.0 -2011-12-31 11:00:00,9937.0 -2011-12-31 12:00:00,10139.0 -2011-12-31 13:00:00,10326.0 -2011-12-31 14:00:00,10359.0 -2011-12-31 15:00:00,10293.0 -2011-12-31 16:00:00,10240.0 -2011-12-31 17:00:00,10416.0 -2011-12-31 18:00:00,11225.0 -2011-12-31 19:00:00,11907.0 -2011-12-31 20:00:00,11812.0 -2011-12-31 21:00:00,11542.0 -2011-12-31 22:00:00,11149.0 -2011-12-31 23:00:00,10855.0 -2012-01-01 00:00:00,10335.0 -2011-12-30 01:00:00,10126.0 -2011-12-30 02:00:00,9535.0 -2011-12-30 03:00:00,9182.0 -2011-12-30 04:00:00,8945.0 -2011-12-30 05:00:00,8874.0 -2011-12-30 06:00:00,9070.0 -2011-12-30 07:00:00,9583.0 -2011-12-30 08:00:00,10329.0 -2011-12-30 09:00:00,10804.0 -2011-12-30 10:00:00,11100.0 -2011-12-30 11:00:00,11396.0 -2011-12-30 12:00:00,11586.0 -2011-12-30 13:00:00,11598.0 -2011-12-30 14:00:00,11511.0 -2011-12-30 15:00:00,11429.0 -2011-12-30 16:00:00,11317.0 -2011-12-30 17:00:00,11384.0 -2011-12-30 18:00:00,11996.0 -2011-12-30 19:00:00,12440.0 -2011-12-30 20:00:00,12248.0 -2011-12-30 21:00:00,12023.0 -2011-12-30 22:00:00,11717.0 -2011-12-30 23:00:00,11318.0 -2011-12-31 00:00:00,10728.0 -2011-12-29 01:00:00,10743.0 -2011-12-29 02:00:00,10083.0 -2011-12-29 03:00:00,9701.0 -2011-12-29 04:00:00,9448.0 -2011-12-29 05:00:00,9353.0 -2011-12-29 06:00:00,9531.0 -2011-12-29 07:00:00,10111.0 -2011-12-29 08:00:00,10975.0 -2011-12-29 09:00:00,11415.0 -2011-12-29 10:00:00,11541.0 -2011-12-29 11:00:00,11634.0 -2011-12-29 12:00:00,11659.0 -2011-12-29 13:00:00,11564.0 -2011-12-29 14:00:00,11432.0 -2011-12-29 15:00:00,11343.0 -2011-12-29 16:00:00,11216.0 -2011-12-29 17:00:00,11140.0 -2011-12-29 18:00:00,11904.0 -2011-12-29 19:00:00,12754.0 -2011-12-29 20:00:00,12593.0 -2011-12-29 21:00:00,12406.0 -2011-12-29 22:00:00,12140.0 -2011-12-29 23:00:00,11661.0 -2011-12-30 00:00:00,10980.0 -2011-12-28 01:00:00,10615.0 -2011-12-28 02:00:00,10017.0 -2011-12-28 03:00:00,9736.0 -2011-12-28 04:00:00,9552.0 -2011-12-28 05:00:00,9557.0 -2011-12-28 06:00:00,9797.0 -2011-12-28 07:00:00,10448.0 -2011-12-28 08:00:00,11364.0 -2011-12-28 09:00:00,11861.0 -2011-12-28 10:00:00,12095.0 -2011-12-28 11:00:00,12287.0 -2011-12-28 12:00:00,12392.0 -2011-12-28 13:00:00,12385.0 -2011-12-28 14:00:00,12344.0 -2011-12-28 15:00:00,12268.0 -2011-12-28 16:00:00,12222.0 -2011-12-28 17:00:00,12249.0 -2011-12-28 18:00:00,12971.0 -2011-12-28 19:00:00,13628.0 -2011-12-28 20:00:00,13447.0 -2011-12-28 21:00:00,13216.0 -2011-12-28 22:00:00,12936.0 -2011-12-28 23:00:00,12432.0 -2011-12-29 00:00:00,11590.0 -2011-12-27 01:00:00,9950.0 -2011-12-27 02:00:00,9391.0 -2011-12-27 03:00:00,9030.0 -2011-12-27 04:00:00,8868.0 -2011-12-27 05:00:00,8832.0 -2011-12-27 06:00:00,9077.0 -2011-12-27 07:00:00,9737.0 -2011-12-27 08:00:00,10649.0 -2011-12-27 09:00:00,11243.0 -2011-12-27 10:00:00,11551.0 -2011-12-27 11:00:00,11809.0 -2011-12-27 12:00:00,11973.0 -2011-12-27 13:00:00,12054.0 -2011-12-27 14:00:00,12040.0 -2011-12-27 15:00:00,12062.0 -2011-12-27 16:00:00,12062.0 -2011-12-27 17:00:00,12133.0 -2011-12-27 18:00:00,12849.0 -2011-12-27 19:00:00,13311.0 -2011-12-27 20:00:00,13093.0 -2011-12-27 21:00:00,12874.0 -2011-12-27 22:00:00,12629.0 -2011-12-27 23:00:00,12122.0 -2011-12-28 00:00:00,11386.0 -2011-12-26 01:00:00,9620.0 -2011-12-26 02:00:00,9171.0 -2011-12-26 03:00:00,8892.0 -2011-12-26 04:00:00,8728.0 -2011-12-26 05:00:00,8723.0 -2011-12-26 06:00:00,8875.0 -2011-12-26 07:00:00,9212.0 -2011-12-26 08:00:00,9625.0 -2011-12-26 09:00:00,9707.0 -2011-12-26 10:00:00,9786.0 -2011-12-26 11:00:00,9954.0 -2011-12-26 12:00:00,10040.0 -2011-12-26 13:00:00,10062.0 -2011-12-26 14:00:00,9983.0 -2011-12-26 15:00:00,9887.0 -2011-12-26 16:00:00,9898.0 -2011-12-26 17:00:00,10172.0 -2011-12-26 18:00:00,11219.0 -2011-12-26 19:00:00,11963.0 -2011-12-26 20:00:00,11908.0 -2011-12-26 21:00:00,11820.0 -2011-12-26 22:00:00,11667.0 -2011-12-26 23:00:00,11280.0 -2011-12-27 00:00:00,10655.0 -2011-12-25 01:00:00,10042.0 -2011-12-25 02:00:00,9487.0 -2011-12-25 03:00:00,9073.0 -2011-12-25 04:00:00,8800.0 -2011-12-25 05:00:00,8662.0 -2011-12-25 06:00:00,8634.0 -2011-12-25 07:00:00,8760.0 -2011-12-25 08:00:00,9005.0 -2011-12-25 09:00:00,9076.0 -2011-12-25 10:00:00,9133.0 -2011-12-25 11:00:00,9219.0 -2011-12-25 12:00:00,9213.0 -2011-12-25 13:00:00,9209.0 -2011-12-25 14:00:00,9137.0 -2011-12-25 15:00:00,9056.0 -2011-12-25 16:00:00,9031.0 -2011-12-25 17:00:00,9087.0 -2011-12-25 18:00:00,9751.0 -2011-12-25 19:00:00,10526.0 -2011-12-25 20:00:00,10577.0 -2011-12-25 21:00:00,10630.0 -2011-12-25 22:00:00,10627.0 -2011-12-25 23:00:00,10563.0 -2011-12-26 00:00:00,10187.0 -2011-12-24 01:00:00,10698.0 -2011-12-24 02:00:00,10021.0 -2011-12-24 03:00:00,9505.0 -2011-12-24 04:00:00,9355.0 -2011-12-24 05:00:00,9237.0 -2011-12-24 06:00:00,9261.0 -2011-12-24 07:00:00,9498.0 -2011-12-24 08:00:00,9864.0 -2011-12-24 09:00:00,10029.0 -2011-12-24 10:00:00,10187.0 -2011-12-24 11:00:00,10327.0 -2011-12-24 12:00:00,10329.0 -2011-12-24 13:00:00,10292.0 -2011-12-24 14:00:00,10211.0 -2011-12-24 15:00:00,10034.0 -2011-12-24 16:00:00,9904.0 -2011-12-24 17:00:00,9919.0 -2011-12-24 18:00:00,10743.0 -2011-12-24 19:00:00,11519.0 -2011-12-24 20:00:00,11437.0 -2011-12-24 21:00:00,11235.0 -2011-12-24 22:00:00,11126.0 -2011-12-24 23:00:00,10934.0 -2011-12-25 00:00:00,10584.0 -2011-12-23 01:00:00,10853.0 -2011-12-23 02:00:00,10177.0 -2011-12-23 03:00:00,9758.0 -2011-12-23 04:00:00,9560.0 -2011-12-23 05:00:00,9504.0 -2011-12-23 06:00:00,9718.0 -2011-12-23 07:00:00,10265.0 -2011-12-23 08:00:00,11162.0 -2011-12-23 09:00:00,11534.0 -2011-12-23 10:00:00,11686.0 -2011-12-23 11:00:00,11707.0 -2011-12-23 12:00:00,11686.0 -2011-12-23 13:00:00,11509.0 -2011-12-23 14:00:00,11374.0 -2011-12-23 15:00:00,11301.0 -2011-12-23 16:00:00,11395.0 -2011-12-23 17:00:00,11668.0 -2011-12-23 18:00:00,12618.0 -2011-12-23 19:00:00,13158.0 -2011-12-23 20:00:00,12967.0 -2011-12-23 21:00:00,12736.0 -2011-12-23 22:00:00,12512.0 -2011-12-23 23:00:00,12165.0 -2011-12-24 00:00:00,11489.0 -2011-12-22 01:00:00,10668.0 -2011-12-22 02:00:00,10013.0 -2011-12-22 03:00:00,9584.0 -2011-12-22 04:00:00,9325.0 -2011-12-22 05:00:00,9303.0 -2011-12-22 06:00:00,9550.0 -2011-12-22 07:00:00,10219.0 -2011-12-22 08:00:00,11347.0 -2011-12-22 09:00:00,11903.0 -2011-12-22 10:00:00,12167.0 -2011-12-22 11:00:00,12327.0 -2011-12-22 12:00:00,12451.0 -2011-12-22 13:00:00,12476.0 -2011-12-22 14:00:00,12405.0 -2011-12-22 15:00:00,12544.0 -2011-12-22 16:00:00,12593.0 -2011-12-22 17:00:00,12763.0 -2011-12-22 18:00:00,13401.0 -2011-12-22 19:00:00,13766.0 -2011-12-22 20:00:00,13530.0 -2011-12-22 21:00:00,13326.0 -2011-12-22 22:00:00,13032.0 -2011-12-22 23:00:00,12580.0 -2011-12-23 00:00:00,11779.0 -2011-12-21 01:00:00,10682.0 -2011-12-21 02:00:00,9935.0 -2011-12-21 03:00:00,9518.0 -2011-12-21 04:00:00,9292.0 -2011-12-21 05:00:00,9248.0 -2011-12-21 06:00:00,9439.0 -2011-12-21 07:00:00,10028.0 -2011-12-21 08:00:00,11174.0 -2011-12-21 09:00:00,11891.0 -2011-12-21 10:00:00,12165.0 -2011-12-21 11:00:00,12344.0 -2011-12-21 12:00:00,12374.0 -2011-12-21 13:00:00,12319.0 -2011-12-21 14:00:00,12366.0 -2011-12-21 15:00:00,12476.0 -2011-12-21 16:00:00,12467.0 -2011-12-21 17:00:00,12612.0 -2011-12-21 18:00:00,13355.0 -2011-12-21 19:00:00,13655.0 -2011-12-21 20:00:00,13412.0 -2011-12-21 21:00:00,13205.0 -2011-12-21 22:00:00,12939.0 -2011-12-21 23:00:00,12466.0 -2011-12-22 00:00:00,11630.0 -2011-12-20 01:00:00,10576.0 -2011-12-20 02:00:00,9883.0 -2011-12-20 03:00:00,9525.0 -2011-12-20 04:00:00,9305.0 -2011-12-20 05:00:00,9276.0 -2011-12-20 06:00:00,9538.0 -2011-12-20 07:00:00,10244.0 -2011-12-20 08:00:00,11431.0 -2011-12-20 09:00:00,12162.0 -2011-12-20 10:00:00,12330.0 -2011-12-20 11:00:00,12362.0 -2011-12-20 12:00:00,12456.0 -2011-12-20 13:00:00,12486.0 -2011-12-20 14:00:00,12454.0 -2011-12-20 15:00:00,12476.0 -2011-12-20 16:00:00,12426.0 -2011-12-20 17:00:00,12590.0 -2011-12-20 18:00:00,13293.0 -2011-12-20 19:00:00,13796.0 -2011-12-20 20:00:00,13616.0 -2011-12-20 21:00:00,13380.0 -2011-12-20 22:00:00,13078.0 -2011-12-20 23:00:00,12559.0 -2011-12-21 00:00:00,11634.0 -2011-12-19 01:00:00,10102.0 -2011-12-19 02:00:00,9535.0 -2011-12-19 03:00:00,9225.0 -2011-12-19 04:00:00,9070.0 -2011-12-19 05:00:00,9072.0 -2011-12-19 06:00:00,9330.0 -2011-12-19 07:00:00,10038.0 -2011-12-19 08:00:00,11245.0 -2011-12-19 09:00:00,11838.0 -2011-12-19 10:00:00,11903.0 -2011-12-19 11:00:00,11960.0 -2011-12-19 12:00:00,11970.0 -2011-12-19 13:00:00,11896.0 -2011-12-19 14:00:00,11829.0 -2011-12-19 15:00:00,11869.0 -2011-12-19 16:00:00,11856.0 -2011-12-19 17:00:00,12113.0 -2011-12-19 18:00:00,13039.0 -2011-12-19 19:00:00,13522.0 -2011-12-19 20:00:00,13371.0 -2011-12-19 21:00:00,13170.0 -2011-12-19 22:00:00,12904.0 -2011-12-19 23:00:00,12402.0 -2011-12-20 00:00:00,11514.0 -2011-12-18 01:00:00,10664.0 -2011-12-18 02:00:00,10059.0 -2011-12-18 03:00:00,9583.0 -2011-12-18 04:00:00,9400.0 -2011-12-18 05:00:00,9280.0 -2011-12-18 06:00:00,9277.0 -2011-12-18 07:00:00,9472.0 -2011-12-18 08:00:00,9810.0 -2011-12-18 09:00:00,10037.0 -2011-12-18 10:00:00,10272.0 -2011-12-18 11:00:00,10477.0 -2011-12-18 12:00:00,10425.0 -2011-12-18 13:00:00,10351.0 -2011-12-18 14:00:00,10285.0 -2011-12-18 15:00:00,10175.0 -2011-12-18 16:00:00,10142.0 -2011-12-18 17:00:00,10223.0 -2011-12-18 18:00:00,11250.0 -2011-12-18 19:00:00,12159.0 -2011-12-18 20:00:00,12308.0 -2011-12-18 21:00:00,12234.0 -2011-12-18 22:00:00,12058.0 -2011-12-18 23:00:00,11626.0 -2011-12-19 00:00:00,10939.0 -2011-12-17 01:00:00,11120.0 -2011-12-17 02:00:00,10415.0 -2011-12-17 03:00:00,10010.0 -2011-12-17 04:00:00,9770.0 -2011-12-17 05:00:00,9682.0 -2011-12-17 06:00:00,9775.0 -2011-12-17 07:00:00,10113.0 -2011-12-17 08:00:00,10607.0 -2011-12-17 09:00:00,10939.0 -2011-12-17 10:00:00,11264.0 -2011-12-17 11:00:00,11566.0 -2011-12-17 12:00:00,11700.0 -2011-12-17 13:00:00,11730.0 -2011-12-17 14:00:00,11550.0 -2011-12-17 15:00:00,11395.0 -2011-12-17 16:00:00,11277.0 -2011-12-17 17:00:00,11462.0 -2011-12-17 18:00:00,12203.0 -2011-12-17 19:00:00,12734.0 -2011-12-17 20:00:00,12666.0 -2011-12-17 21:00:00,12527.0 -2011-12-17 22:00:00,12334.0 -2011-12-17 23:00:00,11999.0 -2011-12-18 00:00:00,11395.0 -2011-12-16 01:00:00,10981.0 -2011-12-16 02:00:00,10314.0 -2011-12-16 03:00:00,9944.0 -2011-12-16 04:00:00,9797.0 -2011-12-16 05:00:00,9809.0 -2011-12-16 06:00:00,10044.0 -2011-12-16 07:00:00,10831.0 -2011-12-16 08:00:00,12069.0 -2011-12-16 09:00:00,12676.0 -2011-12-16 10:00:00,12735.0 -2011-12-16 11:00:00,12637.0 -2011-12-16 12:00:00,12576.0 -2011-12-16 13:00:00,12352.0 -2011-12-16 14:00:00,12191.0 -2011-12-16 15:00:00,12119.0 -2011-12-16 16:00:00,11987.0 -2011-12-16 17:00:00,12060.0 -2011-12-16 18:00:00,12946.0 -2011-12-16 19:00:00,13744.0 -2011-12-16 20:00:00,13666.0 -2011-12-16 21:00:00,13399.0 -2011-12-16 22:00:00,13124.0 -2011-12-16 23:00:00,12759.0 -2011-12-17 00:00:00,11979.0 -2011-12-15 01:00:00,10211.0 -2011-12-15 02:00:00,9511.0 -2011-12-15 03:00:00,9126.0 -2011-12-15 04:00:00,8896.0 -2011-12-15 05:00:00,8864.0 -2011-12-15 06:00:00,9066.0 -2011-12-15 07:00:00,9808.0 -2011-12-15 08:00:00,11073.0 -2011-12-15 09:00:00,11843.0 -2011-12-15 10:00:00,12094.0 -2011-12-15 11:00:00,12185.0 -2011-12-15 12:00:00,12308.0 -2011-12-15 13:00:00,12368.0 -2011-12-15 14:00:00,12408.0 -2011-12-15 15:00:00,12564.0 -2011-12-15 16:00:00,12607.0 -2011-12-15 17:00:00,12790.0 -2011-12-15 18:00:00,13511.0 -2011-12-15 19:00:00,14016.0 -2011-12-15 20:00:00,13872.0 -2011-12-15 21:00:00,13698.0 -2011-12-15 22:00:00,13340.0 -2011-12-15 23:00:00,12863.0 -2011-12-16 00:00:00,11935.0 -2011-12-14 01:00:00,10517.0 -2011-12-14 02:00:00,9852.0 -2011-12-14 03:00:00,9448.0 -2011-12-14 04:00:00,9284.0 -2011-12-14 05:00:00,9228.0 -2011-12-14 06:00:00,9439.0 -2011-12-14 07:00:00,10186.0 -2011-12-14 08:00:00,11439.0 -2011-12-14 09:00:00,12193.0 -2011-12-14 10:00:00,12480.0 -2011-12-14 11:00:00,12741.0 -2011-12-14 12:00:00,12739.0 -2011-12-14 13:00:00,12584.0 -2011-12-14 14:00:00,12477.0 -2011-12-14 15:00:00,12514.0 -2011-12-14 16:00:00,12521.0 -2011-12-14 17:00:00,12688.0 -2011-12-14 18:00:00,13394.0 -2011-12-14 19:00:00,13628.0 -2011-12-14 20:00:00,13361.0 -2011-12-14 21:00:00,13112.0 -2011-12-14 22:00:00,12789.0 -2011-12-14 23:00:00,12206.0 -2011-12-15 00:00:00,11214.0 -2011-12-13 01:00:00,10490.0 -2011-12-13 02:00:00,9902.0 -2011-12-13 03:00:00,9531.0 -2011-12-13 04:00:00,9334.0 -2011-12-13 05:00:00,9290.0 -2011-12-13 06:00:00,9534.0 -2011-12-13 07:00:00,10297.0 -2011-12-13 08:00:00,11545.0 -2011-12-13 09:00:00,12265.0 -2011-12-13 10:00:00,12345.0 -2011-12-13 11:00:00,12382.0 -2011-12-13 12:00:00,12576.0 -2011-12-13 13:00:00,12405.0 -2011-12-13 14:00:00,12307.0 -2011-12-13 15:00:00,12323.0 -2011-12-13 16:00:00,12219.0 -2011-12-13 17:00:00,12351.0 -2011-12-13 18:00:00,13234.0 -2011-12-13 19:00:00,13637.0 -2011-12-13 20:00:00,13455.0 -2011-12-13 21:00:00,13231.0 -2011-12-13 22:00:00,12939.0 -2011-12-13 23:00:00,12386.0 -2011-12-14 00:00:00,11474.0 -2011-12-12 01:00:00,10335.0 -2011-12-12 02:00:00,9882.0 -2011-12-12 03:00:00,9609.0 -2011-12-12 04:00:00,9536.0 -2011-12-12 05:00:00,9585.0 -2011-12-12 06:00:00,9905.0 -2011-12-12 07:00:00,10698.0 -2011-12-12 08:00:00,12007.0 -2011-12-12 09:00:00,12594.0 -2011-12-12 10:00:00,12641.0 -2011-12-12 11:00:00,12553.0 -2011-12-12 12:00:00,12436.0 -2011-12-12 13:00:00,12358.0 -2011-12-12 14:00:00,12198.0 -2011-12-12 15:00:00,12141.0 -2011-12-12 16:00:00,12122.0 -2011-12-12 17:00:00,12175.0 -2011-12-12 18:00:00,13078.0 -2011-12-12 19:00:00,13766.0 -2011-12-12 20:00:00,13557.0 -2011-12-12 21:00:00,13309.0 -2011-12-12 22:00:00,13019.0 -2011-12-12 23:00:00,12461.0 -2011-12-13 00:00:00,11463.0 -2011-12-11 01:00:00,10985.0 -2011-12-11 02:00:00,10416.0 -2011-12-11 03:00:00,9967.0 -2011-12-11 04:00:00,9730.0 -2011-12-11 05:00:00,9700.0 -2011-12-11 06:00:00,9689.0 -2011-12-11 07:00:00,9850.0 -2011-12-11 08:00:00,10135.0 -2011-12-11 09:00:00,10186.0 -2011-12-11 10:00:00,10342.0 -2011-12-11 11:00:00,10457.0 -2011-12-11 12:00:00,10492.0 -2011-12-11 13:00:00,10428.0 -2011-12-11 14:00:00,10368.0 -2011-12-11 15:00:00,10234.0 -2011-12-11 16:00:00,10183.0 -2011-12-11 17:00:00,10314.0 -2011-12-11 18:00:00,11315.0 -2011-12-11 19:00:00,12238.0 -2011-12-11 20:00:00,12396.0 -2011-12-11 21:00:00,12389.0 -2011-12-11 22:00:00,12186.0 -2011-12-11 23:00:00,11823.0 -2011-12-12 00:00:00,11079.0 -2011-12-10 01:00:00,11502.0 -2011-12-10 02:00:00,10836.0 -2011-12-10 03:00:00,10510.0 -2011-12-10 04:00:00,10311.0 -2011-12-10 05:00:00,10272.0 -2011-12-10 06:00:00,10350.0 -2011-12-10 07:00:00,10703.0 -2011-12-10 08:00:00,11274.0 -2011-12-10 09:00:00,11550.0 -2011-12-10 10:00:00,11748.0 -2011-12-10 11:00:00,11889.0 -2011-12-10 12:00:00,11888.0 -2011-12-10 13:00:00,11789.0 -2011-12-10 14:00:00,11553.0 -2011-12-10 15:00:00,11331.0 -2011-12-10 16:00:00,11224.0 -2011-12-10 17:00:00,11318.0 -2011-12-10 18:00:00,12308.0 -2011-12-10 19:00:00,13128.0 -2011-12-10 20:00:00,13050.0 -2011-12-10 21:00:00,12945.0 -2011-12-10 22:00:00,12677.0 -2011-12-10 23:00:00,12363.0 -2011-12-11 00:00:00,11705.0 -2011-12-09 01:00:00,10825.0 -2011-12-09 02:00:00,10237.0 -2011-12-09 03:00:00,9938.0 -2011-12-09 04:00:00,9762.0 -2011-12-09 05:00:00,9802.0 -2011-12-09 06:00:00,10064.0 -2011-12-09 07:00:00,10730.0 -2011-12-09 08:00:00,11993.0 -2011-12-09 09:00:00,12563.0 -2011-12-09 10:00:00,12736.0 -2011-12-09 11:00:00,12689.0 -2011-12-09 12:00:00,12695.0 -2011-12-09 13:00:00,12610.0 -2011-12-09 14:00:00,12539.0 -2011-12-09 15:00:00,12446.0 -2011-12-09 16:00:00,12375.0 -2011-12-09 17:00:00,12405.0 -2011-12-09 18:00:00,13367.0 -2011-12-09 19:00:00,14066.0 -2011-12-09 20:00:00,13947.0 -2011-12-09 21:00:00,13742.0 -2011-12-09 22:00:00,13478.0 -2011-12-09 23:00:00,13098.0 -2011-12-10 00:00:00,12315.0 -2011-12-08 01:00:00,10947.0 -2011-12-08 02:00:00,10329.0 -2011-12-08 03:00:00,10000.0 -2011-12-08 04:00:00,9807.0 -2011-12-08 05:00:00,9784.0 -2011-12-08 06:00:00,10039.0 -2011-12-08 07:00:00,10798.0 -2011-12-08 08:00:00,12064.0 -2011-12-08 09:00:00,12629.0 -2011-12-08 10:00:00,12718.0 -2011-12-08 11:00:00,12691.0 -2011-12-08 12:00:00,12688.0 -2011-12-08 13:00:00,12585.0 -2011-12-08 14:00:00,12481.0 -2011-12-08 15:00:00,12390.0 -2011-12-08 16:00:00,12307.0 -2011-12-08 17:00:00,12493.0 -2011-12-08 18:00:00,13399.0 -2011-12-08 19:00:00,13910.0 -2011-12-08 20:00:00,13781.0 -2011-12-08 21:00:00,13567.0 -2011-12-08 22:00:00,13230.0 -2011-12-08 23:00:00,12705.0 -2011-12-09 00:00:00,11738.0 -2011-12-07 01:00:00,10740.0 -2011-12-07 02:00:00,10122.0 -2011-12-07 03:00:00,9758.0 -2011-12-07 04:00:00,9520.0 -2011-12-07 05:00:00,9530.0 -2011-12-07 06:00:00,9791.0 -2011-12-07 07:00:00,10570.0 -2011-12-07 08:00:00,11858.0 -2011-12-07 09:00:00,12370.0 -2011-12-07 10:00:00,12497.0 -2011-12-07 11:00:00,12468.0 -2011-12-07 12:00:00,12522.0 -2011-12-07 13:00:00,12519.0 -2011-12-07 14:00:00,12350.0 -2011-12-07 15:00:00,12334.0 -2011-12-07 16:00:00,12249.0 -2011-12-07 17:00:00,12398.0 -2011-12-07 18:00:00,13331.0 -2011-12-07 19:00:00,14021.0 -2011-12-07 20:00:00,13890.0 -2011-12-07 21:00:00,13700.0 -2011-12-07 22:00:00,13392.0 -2011-12-07 23:00:00,12818.0 -2011-12-08 00:00:00,11873.0 -2011-12-06 01:00:00,10443.0 -2011-12-06 02:00:00,9834.0 -2011-12-06 03:00:00,9495.0 -2011-12-06 04:00:00,9320.0 -2011-12-06 05:00:00,9296.0 -2011-12-06 06:00:00,9590.0 -2011-12-06 07:00:00,10343.0 -2011-12-06 08:00:00,11633.0 -2011-12-06 09:00:00,12231.0 -2011-12-06 10:00:00,12414.0 -2011-12-06 11:00:00,12490.0 -2011-12-06 12:00:00,12560.0 -2011-12-06 13:00:00,12571.0 -2011-12-06 14:00:00,12495.0 -2011-12-06 15:00:00,12450.0 -2011-12-06 16:00:00,12339.0 -2011-12-06 17:00:00,12356.0 -2011-12-06 18:00:00,13219.0 -2011-12-06 19:00:00,13910.0 -2011-12-06 20:00:00,13774.0 -2011-12-06 21:00:00,13584.0 -2011-12-06 22:00:00,13301.0 -2011-12-06 23:00:00,12709.0 -2011-12-07 00:00:00,11701.0 -2011-12-05 01:00:00,9716.0 -2011-12-05 02:00:00,9266.0 -2011-12-05 03:00:00,9014.0 -2011-12-05 04:00:00,8926.0 -2011-12-05 05:00:00,8947.0 -2011-12-05 06:00:00,9268.0 -2011-12-05 07:00:00,10057.0 -2011-12-05 08:00:00,11419.0 -2011-12-05 09:00:00,11991.0 -2011-12-05 10:00:00,12165.0 -2011-12-05 11:00:00,12268.0 -2011-12-05 12:00:00,12356.0 -2011-12-05 13:00:00,12357.0 -2011-12-05 14:00:00,12352.0 -2011-12-05 15:00:00,12409.0 -2011-12-05 16:00:00,12411.0 -2011-12-05 17:00:00,12547.0 -2011-12-05 18:00:00,13304.0 -2011-12-05 19:00:00,13694.0 -2011-12-05 20:00:00,13485.0 -2011-12-05 21:00:00,13253.0 -2011-12-05 22:00:00,12955.0 -2011-12-05 23:00:00,12382.0 -2011-12-06 00:00:00,11383.0 -2011-12-04 01:00:00,9800.0 -2011-12-04 02:00:00,9175.0 -2011-12-04 03:00:00,8715.0 -2011-12-04 04:00:00,8542.0 -2011-12-04 05:00:00,8409.0 -2011-12-04 06:00:00,8375.0 -2011-12-04 07:00:00,8449.0 -2011-12-04 08:00:00,8703.0 -2011-12-04 09:00:00,8892.0 -2011-12-04 10:00:00,9218.0 -2011-12-04 11:00:00,9583.0 -2011-12-04 12:00:00,9863.0 -2011-12-04 13:00:00,10022.0 -2011-12-04 14:00:00,10165.0 -2011-12-04 15:00:00,10237.0 -2011-12-04 16:00:00,10322.0 -2011-12-04 17:00:00,10588.0 -2011-12-04 18:00:00,11359.0 -2011-12-04 19:00:00,11862.0 -2011-12-04 20:00:00,11844.0 -2011-12-04 21:00:00,11765.0 -2011-12-04 22:00:00,11516.0 -2011-12-04 23:00:00,11114.0 -2011-12-05 00:00:00,10400.0 -2011-12-03 01:00:00,10615.0 -2011-12-03 02:00:00,9917.0 -2011-12-03 03:00:00,9494.0 -2011-12-03 04:00:00,9195.0 -2011-12-03 05:00:00,9041.0 -2011-12-03 06:00:00,9014.0 -2011-12-03 07:00:00,9327.0 -2011-12-03 08:00:00,9808.0 -2011-12-03 09:00:00,10255.0 -2011-12-03 10:00:00,10549.0 -2011-12-03 11:00:00,10781.0 -2011-12-03 12:00:00,10884.0 -2011-12-03 13:00:00,10882.0 -2011-12-03 14:00:00,10786.0 -2011-12-03 15:00:00,10483.0 -2011-12-03 16:00:00,10304.0 -2011-12-03 17:00:00,10241.0 -2011-12-03 18:00:00,11142.0 -2011-12-03 19:00:00,11710.0 -2011-12-03 20:00:00,11689.0 -2011-12-03 21:00:00,11542.0 -2011-12-03 22:00:00,11358.0 -2011-12-03 23:00:00,11018.0 -2011-12-04 00:00:00,10488.0 -2011-12-02 01:00:00,10471.0 -2011-12-02 02:00:00,9923.0 -2011-12-02 03:00:00,9631.0 -2011-12-02 04:00:00,9434.0 -2011-12-02 05:00:00,9421.0 -2011-12-02 06:00:00,9650.0 -2011-12-02 07:00:00,10371.0 -2011-12-02 08:00:00,11618.0 -2011-12-02 09:00:00,12095.0 -2011-12-02 10:00:00,12174.0 -2011-12-02 11:00:00,12216.0 -2011-12-02 12:00:00,12172.0 -2011-12-02 13:00:00,12100.0 -2011-12-02 14:00:00,11925.0 -2011-12-02 15:00:00,11909.0 -2011-12-02 16:00:00,11811.0 -2011-12-02 17:00:00,11830.0 -2011-12-02 18:00:00,12647.0 -2011-12-02 19:00:00,13239.0 -2011-12-02 20:00:00,13121.0 -2011-12-02 21:00:00,12910.0 -2011-12-02 22:00:00,12627.0 -2011-12-02 23:00:00,12242.0 -2011-12-03 00:00:00,11475.0 -2011-12-01 01:00:00,10543.0 -2011-12-01 02:00:00,9986.0 -2011-12-01 03:00:00,9688.0 -2011-12-01 04:00:00,9476.0 -2011-12-01 05:00:00,9466.0 -2011-12-01 06:00:00,9742.0 -2011-12-01 07:00:00,10479.0 -2011-12-01 08:00:00,11728.0 -2011-12-01 09:00:00,12176.0 -2011-12-01 10:00:00,12288.0 -2011-12-01 11:00:00,12291.0 -2011-12-01 12:00:00,12286.0 -2011-12-01 13:00:00,12130.0 -2011-12-01 14:00:00,12038.0 -2011-12-01 15:00:00,11984.0 -2011-12-01 16:00:00,11982.0 -2011-12-01 17:00:00,12204.0 -2011-12-01 18:00:00,12906.0 -2011-12-01 19:00:00,13361.0 -2011-12-01 20:00:00,13269.0 -2011-12-01 21:00:00,13053.0 -2011-12-01 22:00:00,12740.0 -2011-12-01 23:00:00,12219.0 -2011-12-02 00:00:00,11298.0 -2011-11-30 01:00:00,10646.0 -2011-11-30 02:00:00,10080.0 -2011-11-30 03:00:00,9803.0 -2011-11-30 04:00:00,9658.0 -2011-11-30 05:00:00,9636.0 -2011-11-30 06:00:00,9908.0 -2011-11-30 07:00:00,10679.0 -2011-11-30 08:00:00,11919.0 -2011-11-30 09:00:00,12374.0 -2011-11-30 10:00:00,12448.0 -2011-11-30 11:00:00,12412.0 -2011-11-30 12:00:00,12356.0 -2011-11-30 13:00:00,12286.0 -2011-11-30 14:00:00,12118.0 -2011-11-30 15:00:00,12028.0 -2011-11-30 16:00:00,11901.0 -2011-11-30 17:00:00,11987.0 -2011-11-30 18:00:00,12831.0 -2011-11-30 19:00:00,13499.0 -2011-11-30 20:00:00,13379.0 -2011-11-30 21:00:00,13200.0 -2011-11-30 22:00:00,12912.0 -2011-11-30 23:00:00,12346.0 -2011-12-01 00:00:00,11408.0 -2011-11-29 01:00:00,10470.0 -2011-11-29 02:00:00,9879.0 -2011-11-29 03:00:00,9541.0 -2011-11-29 04:00:00,9353.0 -2011-11-29 05:00:00,9347.0 -2011-11-29 06:00:00,9572.0 -2011-11-29 07:00:00,10335.0 -2011-11-29 08:00:00,11618.0 -2011-11-29 09:00:00,12253.0 -2011-11-29 10:00:00,12387.0 -2011-11-29 11:00:00,12505.0 -2011-11-29 12:00:00,12568.0 -2011-11-29 13:00:00,12555.0 -2011-11-29 14:00:00,12458.0 -2011-11-29 15:00:00,12477.0 -2011-11-29 16:00:00,12430.0 -2011-11-29 17:00:00,12454.0 -2011-11-29 18:00:00,13153.0 -2011-11-29 19:00:00,13678.0 -2011-11-29 20:00:00,13531.0 -2011-11-29 21:00:00,13327.0 -2011-11-29 22:00:00,12984.0 -2011-11-29 23:00:00,12419.0 -2011-11-30 00:00:00,11519.0 -2011-11-28 01:00:00,9555.0 -2011-11-28 02:00:00,9090.0 -2011-11-28 03:00:00,8902.0 -2011-11-28 04:00:00,8741.0 -2011-11-28 05:00:00,8812.0 -2011-11-28 06:00:00,9075.0 -2011-11-28 07:00:00,9963.0 -2011-11-28 08:00:00,11258.0 -2011-11-28 09:00:00,11894.0 -2011-11-28 10:00:00,12029.0 -2011-11-28 11:00:00,12145.0 -2011-11-28 12:00:00,12166.0 -2011-11-28 13:00:00,12232.0 -2011-11-28 14:00:00,12206.0 -2011-11-28 15:00:00,12284.0 -2011-11-28 16:00:00,12265.0 -2011-11-28 17:00:00,12329.0 -2011-11-28 18:00:00,13027.0 -2011-11-28 19:00:00,13486.0 -2011-11-28 20:00:00,13326.0 -2011-11-28 21:00:00,13117.0 -2011-11-28 22:00:00,12824.0 -2011-11-28 23:00:00,12248.0 -2011-11-29 00:00:00,11322.0 -2011-11-27 01:00:00,9110.0 -2011-11-27 02:00:00,8628.0 -2011-11-27 03:00:00,8262.0 -2011-11-27 04:00:00,8068.0 -2011-11-27 05:00:00,7987.0 -2011-11-27 06:00:00,8064.0 -2011-11-27 07:00:00,8182.0 -2011-11-27 08:00:00,8525.0 -2011-11-27 09:00:00,8720.0 -2011-11-27 10:00:00,9128.0 -2011-11-27 11:00:00,9530.0 -2011-11-27 12:00:00,9822.0 -2011-11-27 13:00:00,9991.0 -2011-11-27 14:00:00,10132.0 -2011-11-27 15:00:00,10172.0 -2011-11-27 16:00:00,10262.0 -2011-11-27 17:00:00,10454.0 -2011-11-27 18:00:00,11161.0 -2011-11-27 19:00:00,11606.0 -2011-11-27 20:00:00,11574.0 -2011-11-27 21:00:00,11477.0 -2011-11-27 22:00:00,11219.0 -2011-11-27 23:00:00,10789.0 -2011-11-28 00:00:00,10146.0 -2011-11-26 01:00:00,8970.0 -2011-11-26 02:00:00,8441.0 -2011-11-26 03:00:00,8162.0 -2011-11-26 04:00:00,7919.0 -2011-11-26 05:00:00,7873.0 -2011-11-26 06:00:00,7873.0 -2011-11-26 07:00:00,8106.0 -2011-11-26 08:00:00,8501.0 -2011-11-26 09:00:00,8792.0 -2011-11-26 10:00:00,9253.0 -2011-11-26 11:00:00,9601.0 -2011-11-26 12:00:00,9920.0 -2011-11-26 13:00:00,10030.0 -2011-11-26 14:00:00,10085.0 -2011-11-26 15:00:00,10006.0 -2011-11-26 16:00:00,9962.0 -2011-11-26 17:00:00,10058.0 -2011-11-26 18:00:00,10590.0 -2011-11-26 19:00:00,10903.0 -2011-11-26 20:00:00,10838.0 -2011-11-26 21:00:00,10764.0 -2011-11-26 22:00:00,10505.0 -2011-11-26 23:00:00,10258.0 -2011-11-27 00:00:00,9763.0 -2011-11-25 01:00:00,8804.0 -2011-11-25 02:00:00,8450.0 -2011-11-25 03:00:00,8242.0 -2011-11-25 04:00:00,8110.0 -2011-11-25 05:00:00,8108.0 -2011-11-25 06:00:00,8284.0 -2011-11-25 07:00:00,8704.0 -2011-11-25 08:00:00,9181.0 -2011-11-25 09:00:00,9246.0 -2011-11-25 10:00:00,9427.0 -2011-11-25 11:00:00,9587.0 -2011-11-25 12:00:00,9636.0 -2011-11-25 13:00:00,9650.0 -2011-11-25 14:00:00,9568.0 -2011-11-25 15:00:00,9486.0 -2011-11-25 16:00:00,9548.0 -2011-11-25 17:00:00,9658.0 -2011-11-25 18:00:00,10523.0 -2011-11-25 19:00:00,10991.0 -2011-11-25 20:00:00,10843.0 -2011-11-25 21:00:00,10673.0 -2011-11-25 22:00:00,10409.0 -2011-11-25 23:00:00,10120.0 -2011-11-26 00:00:00,9556.0 -2011-11-24 01:00:00,9766.0 -2011-11-24 02:00:00,9161.0 -2011-11-24 03:00:00,8802.0 -2011-11-24 04:00:00,8542.0 -2011-11-24 05:00:00,8382.0 -2011-11-24 06:00:00,8395.0 -2011-11-24 07:00:00,8547.0 -2011-11-24 08:00:00,8774.0 -2011-11-24 09:00:00,8827.0 -2011-11-24 10:00:00,9096.0 -2011-11-24 11:00:00,9436.0 -2011-11-24 12:00:00,9659.0 -2011-11-24 13:00:00,9752.0 -2011-11-24 14:00:00,9673.0 -2011-11-24 15:00:00,9513.0 -2011-11-24 16:00:00,9299.0 -2011-11-24 17:00:00,9195.0 -2011-11-24 18:00:00,9571.0 -2011-11-24 19:00:00,9883.0 -2011-11-24 20:00:00,9777.0 -2011-11-24 21:00:00,9716.0 -2011-11-24 22:00:00,9640.0 -2011-11-24 23:00:00,9520.0 -2011-11-25 00:00:00,9223.0 -2011-11-23 01:00:00,10065.0 -2011-11-23 02:00:00,9524.0 -2011-11-23 03:00:00,9181.0 -2011-11-23 04:00:00,8995.0 -2011-11-23 05:00:00,8969.0 -2011-11-23 06:00:00,9226.0 -2011-11-23 07:00:00,9936.0 -2011-11-23 08:00:00,10926.0 -2011-11-23 09:00:00,11383.0 -2011-11-23 10:00:00,11672.0 -2011-11-23 11:00:00,11819.0 -2011-11-23 12:00:00,11847.0 -2011-11-23 13:00:00,11754.0 -2011-11-23 14:00:00,11551.0 -2011-11-23 15:00:00,11451.0 -2011-11-23 16:00:00,11291.0 -2011-11-23 17:00:00,11176.0 -2011-11-23 18:00:00,11737.0 -2011-11-23 19:00:00,12366.0 -2011-11-23 20:00:00,12178.0 -2011-11-23 21:00:00,11925.0 -2011-11-23 22:00:00,11677.0 -2011-11-23 23:00:00,11211.0 -2011-11-24 00:00:00,10557.0 -2011-11-22 01:00:00,9879.0 -2011-11-22 02:00:00,9363.0 -2011-11-22 03:00:00,9033.0 -2011-11-22 04:00:00,8832.0 -2011-11-22 05:00:00,8793.0 -2011-11-22 06:00:00,9032.0 -2011-11-22 07:00:00,9747.0 -2011-11-22 08:00:00,10938.0 -2011-11-22 09:00:00,11712.0 -2011-11-22 10:00:00,12020.0 -2011-11-22 11:00:00,12179.0 -2011-11-22 12:00:00,12323.0 -2011-11-22 13:00:00,12268.0 -2011-11-22 14:00:00,12290.0 -2011-11-22 15:00:00,12292.0 -2011-11-22 16:00:00,12252.0 -2011-11-22 17:00:00,12294.0 -2011-11-22 18:00:00,12773.0 -2011-11-22 19:00:00,13016.0 -2011-11-22 20:00:00,12774.0 -2011-11-22 21:00:00,12494.0 -2011-11-22 22:00:00,12144.0 -2011-11-22 23:00:00,11641.0 -2011-11-23 00:00:00,10862.0 -2011-11-21 01:00:00,9393.0 -2011-11-21 02:00:00,8975.0 -2011-11-21 03:00:00,8760.0 -2011-11-21 04:00:00,8660.0 -2011-11-21 05:00:00,8691.0 -2011-11-21 06:00:00,8971.0 -2011-11-21 07:00:00,9775.0 -2011-11-21 08:00:00,10939.0 -2011-11-21 09:00:00,11529.0 -2011-11-21 10:00:00,11796.0 -2011-11-21 11:00:00,11882.0 -2011-11-21 12:00:00,11938.0 -2011-11-21 13:00:00,11850.0 -2011-11-21 14:00:00,11709.0 -2011-11-21 15:00:00,11702.0 -2011-11-21 16:00:00,11618.0 -2011-11-21 17:00:00,11710.0 -2011-11-21 18:00:00,12370.0 -2011-11-21 19:00:00,12812.0 -2011-11-21 20:00:00,12604.0 -2011-11-21 21:00:00,12358.0 -2011-11-21 22:00:00,12054.0 -2011-11-21 23:00:00,11503.0 -2011-11-22 00:00:00,10693.0 -2011-11-20 01:00:00,9203.0 -2011-11-20 02:00:00,8709.0 -2011-11-20 03:00:00,8418.0 -2011-11-20 04:00:00,8272.0 -2011-11-20 05:00:00,8139.0 -2011-11-20 06:00:00,8160.0 -2011-11-20 07:00:00,8347.0 -2011-11-20 08:00:00,8614.0 -2011-11-20 09:00:00,8630.0 -2011-11-20 10:00:00,8980.0 -2011-11-20 11:00:00,9330.0 -2011-11-20 12:00:00,9599.0 -2011-11-20 13:00:00,9643.0 -2011-11-20 14:00:00,9789.0 -2011-11-20 15:00:00,9855.0 -2011-11-20 16:00:00,9919.0 -2011-11-20 17:00:00,10051.0 -2011-11-20 18:00:00,10739.0 -2011-11-20 19:00:00,11243.0 -2011-11-20 20:00:00,11244.0 -2011-11-20 21:00:00,11107.0 -2011-11-20 22:00:00,10891.0 -2011-11-20 23:00:00,10508.0 -2011-11-21 00:00:00,9950.0 -2011-11-19 01:00:00,10117.0 -2011-11-19 02:00:00,9514.0 -2011-11-19 03:00:00,9149.0 -2011-11-19 04:00:00,8903.0 -2011-11-19 05:00:00,8788.0 -2011-11-19 06:00:00,8873.0 -2011-11-19 07:00:00,9133.0 -2011-11-19 08:00:00,9646.0 -2011-11-19 09:00:00,9946.0 -2011-11-19 10:00:00,10383.0 -2011-11-19 11:00:00,10691.0 -2011-11-19 12:00:00,10784.0 -2011-11-19 13:00:00,10571.0 -2011-11-19 14:00:00,10388.0 -2011-11-19 15:00:00,10376.0 -2011-11-19 16:00:00,10290.0 -2011-11-19 17:00:00,10287.0 -2011-11-19 18:00:00,10794.0 -2011-11-19 19:00:00,11209.0 -2011-11-19 20:00:00,11118.0 -2011-11-19 21:00:00,10886.0 -2011-11-19 22:00:00,10693.0 -2011-11-19 23:00:00,10330.0 -2011-11-20 00:00:00,9766.0 -2011-11-18 01:00:00,10613.0 -2011-11-18 02:00:00,10120.0 -2011-11-18 03:00:00,9835.0 -2011-11-18 04:00:00,9655.0 -2011-11-18 05:00:00,9656.0 -2011-11-18 06:00:00,9886.0 -2011-11-18 07:00:00,10629.0 -2011-11-18 08:00:00,11720.0 -2011-11-18 09:00:00,12129.0 -2011-11-18 10:00:00,12220.0 -2011-11-18 11:00:00,12206.0 -2011-11-18 12:00:00,12231.0 -2011-11-18 13:00:00,12130.0 -2011-11-18 14:00:00,11998.0 -2011-11-18 15:00:00,11921.0 -2011-11-18 16:00:00,11706.0 -2011-11-18 17:00:00,11648.0 -2011-11-18 18:00:00,12170.0 -2011-11-18 19:00:00,12700.0 -2011-11-18 20:00:00,12528.0 -2011-11-18 21:00:00,12237.0 -2011-11-18 22:00:00,11929.0 -2011-11-18 23:00:00,11497.0 -2011-11-19 00:00:00,10812.0 -2011-11-17 01:00:00,10279.0 -2011-11-17 02:00:00,9795.0 -2011-11-17 03:00:00,9545.0 -2011-11-17 04:00:00,9367.0 -2011-11-17 05:00:00,9379.0 -2011-11-17 06:00:00,9658.0 -2011-11-17 07:00:00,10459.0 -2011-11-17 08:00:00,11604.0 -2011-11-17 09:00:00,12056.0 -2011-11-17 10:00:00,12195.0 -2011-11-17 11:00:00,12285.0 -2011-11-17 12:00:00,12331.0 -2011-11-17 13:00:00,12280.0 -2011-11-17 14:00:00,12196.0 -2011-11-17 15:00:00,12191.0 -2011-11-17 16:00:00,12073.0 -2011-11-17 17:00:00,12060.0 -2011-11-17 18:00:00,12638.0 -2011-11-17 19:00:00,13235.0 -2011-11-17 20:00:00,13171.0 -2011-11-17 21:00:00,12982.0 -2011-11-17 22:00:00,12704.0 -2011-11-17 23:00:00,12186.0 -2011-11-18 00:00:00,11400.0 -2011-11-16 01:00:00,9626.0 -2011-11-16 02:00:00,9160.0 -2011-11-16 03:00:00,8902.0 -2011-11-16 04:00:00,8745.0 -2011-11-16 05:00:00,8749.0 -2011-11-16 06:00:00,9050.0 -2011-11-16 07:00:00,9850.0 -2011-11-16 08:00:00,11063.0 -2011-11-16 09:00:00,11563.0 -2011-11-16 10:00:00,11783.0 -2011-11-16 11:00:00,11937.0 -2011-11-16 12:00:00,11990.0 -2011-11-16 13:00:00,11966.0 -2011-11-16 14:00:00,11925.0 -2011-11-16 15:00:00,11892.0 -2011-11-16 16:00:00,11790.0 -2011-11-16 17:00:00,11786.0 -2011-11-16 18:00:00,12336.0 -2011-11-16 19:00:00,12978.0 -2011-11-16 20:00:00,12810.0 -2011-11-16 21:00:00,12622.0 -2011-11-16 22:00:00,12329.0 -2011-11-16 23:00:00,11825.0 -2011-11-17 00:00:00,11040.0 -2011-11-15 01:00:00,9605.0 -2011-11-15 02:00:00,9095.0 -2011-11-15 03:00:00,8841.0 -2011-11-15 04:00:00,8689.0 -2011-11-15 05:00:00,8691.0 -2011-11-15 06:00:00,8978.0 -2011-11-15 07:00:00,9740.0 -2011-11-15 08:00:00,10898.0 -2011-11-15 09:00:00,11339.0 -2011-11-15 10:00:00,11455.0 -2011-11-15 11:00:00,11533.0 -2011-11-15 12:00:00,11588.0 -2011-11-15 13:00:00,11598.0 -2011-11-15 14:00:00,11536.0 -2011-11-15 15:00:00,11513.0 -2011-11-15 16:00:00,11427.0 -2011-11-15 17:00:00,11351.0 -2011-11-15 18:00:00,11733.0 -2011-11-15 19:00:00,12318.0 -2011-11-15 20:00:00,12200.0 -2011-11-15 21:00:00,11986.0 -2011-11-15 22:00:00,11677.0 -2011-11-15 23:00:00,11141.0 -2011-11-16 00:00:00,10349.0 -2011-11-14 01:00:00,8792.0 -2011-11-14 02:00:00,8423.0 -2011-11-14 03:00:00,8243.0 -2011-11-14 04:00:00,8129.0 -2011-11-14 05:00:00,8215.0 -2011-11-14 06:00:00,8498.0 -2011-11-14 07:00:00,9383.0 -2011-11-14 08:00:00,10601.0 -2011-11-14 09:00:00,11202.0 -2011-11-14 10:00:00,11485.0 -2011-11-14 11:00:00,11673.0 -2011-11-14 12:00:00,11841.0 -2011-11-14 13:00:00,11829.0 -2011-11-14 14:00:00,11791.0 -2011-11-14 15:00:00,11718.0 -2011-11-14 16:00:00,11528.0 -2011-11-14 17:00:00,11433.0 -2011-11-14 18:00:00,11954.0 -2011-11-14 19:00:00,12513.0 -2011-11-14 20:00:00,12333.0 -2011-11-14 21:00:00,12100.0 -2011-11-14 22:00:00,11754.0 -2011-11-14 23:00:00,11235.0 -2011-11-15 00:00:00,10404.0 -2011-11-13 01:00:00,9151.0 -2011-11-13 02:00:00,8673.0 -2011-11-13 03:00:00,8419.0 -2011-11-13 04:00:00,8140.0 -2011-11-13 05:00:00,8067.0 -2011-11-13 06:00:00,8054.0 -2011-11-13 07:00:00,8162.0 -2011-11-13 08:00:00,8341.0 -2011-11-13 09:00:00,8402.0 -2011-11-13 10:00:00,8767.0 -2011-11-13 11:00:00,9058.0 -2011-11-13 12:00:00,9306.0 -2011-11-13 13:00:00,9455.0 -2011-11-13 14:00:00,9415.0 -2011-11-13 15:00:00,9399.0 -2011-11-13 16:00:00,9329.0 -2011-11-13 17:00:00,9377.0 -2011-11-13 18:00:00,9850.0 -2011-11-13 19:00:00,10672.0 -2011-11-13 20:00:00,10682.0 -2011-11-13 21:00:00,10545.0 -2011-11-13 22:00:00,10337.0 -2011-11-13 23:00:00,9937.0 -2011-11-14 00:00:00,9376.0 -2011-11-12 01:00:00,10047.0 -2011-11-12 02:00:00,9577.0 -2011-11-12 03:00:00,9214.0 -2011-11-12 04:00:00,9021.0 -2011-11-12 05:00:00,8917.0 -2011-11-12 06:00:00,8979.0 -2011-11-12 07:00:00,9287.0 -2011-11-12 08:00:00,9641.0 -2011-11-12 09:00:00,9817.0 -2011-11-12 10:00:00,10042.0 -2011-11-12 11:00:00,10253.0 -2011-11-12 12:00:00,10255.0 -2011-11-12 13:00:00,10202.0 -2011-11-12 14:00:00,10013.0 -2011-11-12 15:00:00,9877.0 -2011-11-12 16:00:00,9732.0 -2011-11-12 17:00:00,9816.0 -2011-11-12 18:00:00,10399.0 -2011-11-12 19:00:00,11033.0 -2011-11-12 20:00:00,10958.0 -2011-11-12 21:00:00,10786.0 -2011-11-12 22:00:00,10523.0 -2011-11-12 23:00:00,10218.0 -2011-11-13 00:00:00,9703.0 -2011-11-11 01:00:00,10312.0 -2011-11-11 02:00:00,9799.0 -2011-11-11 03:00:00,9527.0 -2011-11-11 04:00:00,9325.0 -2011-11-11 05:00:00,9305.0 -2011-11-11 06:00:00,9536.0 -2011-11-11 07:00:00,10192.0 -2011-11-11 08:00:00,11116.0 -2011-11-11 09:00:00,11555.0 -2011-11-11 10:00:00,11908.0 -2011-11-11 11:00:00,12108.0 -2011-11-11 12:00:00,12178.0 -2011-11-11 13:00:00,12109.0 -2011-11-11 14:00:00,11929.0 -2011-11-11 15:00:00,11839.0 -2011-11-11 16:00:00,11623.0 -2011-11-11 17:00:00,11480.0 -2011-11-11 18:00:00,11896.0 -2011-11-11 19:00:00,12590.0 -2011-11-11 20:00:00,12400.0 -2011-11-11 21:00:00,12176.0 -2011-11-11 22:00:00,11854.0 -2011-11-11 23:00:00,11388.0 -2011-11-12 00:00:00,10785.0 -2011-11-10 01:00:00,10075.0 -2011-11-10 02:00:00,9549.0 -2011-11-10 03:00:00,9277.0 -2011-11-10 04:00:00,9117.0 -2011-11-10 05:00:00,9126.0 -2011-11-10 06:00:00,9404.0 -2011-11-10 07:00:00,10155.0 -2011-11-10 08:00:00,11312.0 -2011-11-10 09:00:00,11843.0 -2011-11-10 10:00:00,12105.0 -2011-11-10 11:00:00,12185.0 -2011-11-10 12:00:00,12407.0 -2011-11-10 13:00:00,12406.0 -2011-11-10 14:00:00,12346.0 -2011-11-10 15:00:00,12326.0 -2011-11-10 16:00:00,12215.0 -2011-11-10 17:00:00,12192.0 -2011-11-10 18:00:00,12588.0 -2011-11-10 19:00:00,13088.0 -2011-11-10 20:00:00,12886.0 -2011-11-10 21:00:00,12650.0 -2011-11-10 22:00:00,12298.0 -2011-11-10 23:00:00,11801.0 -2011-11-11 00:00:00,11024.0 -2011-11-09 01:00:00,9667.0 -2011-11-09 02:00:00,9169.0 -2011-11-09 03:00:00,8853.0 -2011-11-09 04:00:00,8678.0 -2011-11-09 05:00:00,8588.0 -2011-11-09 06:00:00,8834.0 -2011-11-09 07:00:00,9578.0 -2011-11-09 08:00:00,10867.0 -2011-11-09 09:00:00,11617.0 -2011-11-09 10:00:00,11770.0 -2011-11-09 11:00:00,11749.0 -2011-11-09 12:00:00,11831.0 -2011-11-09 13:00:00,11916.0 -2011-11-09 14:00:00,11905.0 -2011-11-09 15:00:00,11994.0 -2011-11-09 16:00:00,11969.0 -2011-11-09 17:00:00,12062.0 -2011-11-09 18:00:00,12579.0 -2011-11-09 19:00:00,13026.0 -2011-11-09 20:00:00,12809.0 -2011-11-09 21:00:00,12537.0 -2011-11-09 22:00:00,12177.0 -2011-11-09 23:00:00,11641.0 -2011-11-10 00:00:00,10838.0 -2011-11-08 01:00:00,9508.0 -2011-11-08 02:00:00,8967.0 -2011-11-08 03:00:00,8724.0 -2011-11-08 04:00:00,8544.0 -2011-11-08 05:00:00,8498.0 -2011-11-08 06:00:00,8724.0 -2011-11-08 07:00:00,9462.0 -2011-11-08 08:00:00,10670.0 -2011-11-08 09:00:00,11440.0 -2011-11-08 10:00:00,11905.0 -2011-11-08 11:00:00,12043.0 -2011-11-08 12:00:00,12146.0 -2011-11-08 13:00:00,12078.0 -2011-11-08 14:00:00,12041.0 -2011-11-08 15:00:00,12093.0 -2011-11-08 16:00:00,11967.0 -2011-11-08 17:00:00,12031.0 -2011-11-08 18:00:00,12477.0 -2011-11-08 19:00:00,12731.0 -2011-11-08 20:00:00,12483.0 -2011-11-08 21:00:00,12205.0 -2011-11-08 22:00:00,11848.0 -2011-11-08 23:00:00,11272.0 -2011-11-09 00:00:00,10424.0 -2011-11-07 01:00:00,8737.0 -2011-11-07 02:00:00,8366.0 -2011-11-07 03:00:00,8186.0 -2011-11-07 04:00:00,8058.0 -2011-11-07 05:00:00,8101.0 -2011-11-07 06:00:00,8358.0 -2011-11-07 07:00:00,9158.0 -2011-11-07 08:00:00,10396.0 -2011-11-07 09:00:00,11054.0 -2011-11-07 10:00:00,11349.0 -2011-11-07 11:00:00,11485.0 -2011-11-07 12:00:00,11663.0 -2011-11-07 13:00:00,11633.0 -2011-11-07 14:00:00,11596.0 -2011-11-07 15:00:00,11672.0 -2011-11-07 16:00:00,11550.0 -2011-11-07 17:00:00,11579.0 -2011-11-07 18:00:00,11985.0 -2011-11-07 19:00:00,12391.0 -2011-11-07 20:00:00,12198.0 -2011-11-07 21:00:00,11971.0 -2011-11-07 22:00:00,11591.0 -2011-11-07 23:00:00,11067.0 -2011-11-08 00:00:00,10276.0 -2011-11-06 01:00:00,9164.0 -2011-11-06 03:00:00,8298.0 -2011-11-06 04:00:00,8061.0 -2011-11-06 05:00:00,8045.0 -2011-11-06 06:00:00,8041.0 -2011-11-06 07:00:00,8266.0 -2011-11-06 08:00:00,8366.0 -2011-11-06 09:00:00,8574.0 -2011-11-06 10:00:00,8888.0 -2011-11-06 11:00:00,9126.0 -2011-11-06 12:00:00,9236.0 -2011-11-06 13:00:00,9403.0 -2011-11-06 14:00:00,9453.0 -2011-11-06 15:00:00,9534.0 -2011-11-06 16:00:00,9484.0 -2011-11-06 17:00:00,9588.0 -2011-11-06 18:00:00,10071.0 -2011-11-06 19:00:00,10750.0 -2011-11-06 20:00:00,10707.0 -2011-11-06 21:00:00,10597.0 -2011-11-06 22:00:00,10296.0 -2011-11-06 23:00:00,9861.0 -2011-11-07 00:00:00,9262.0 -2011-11-05 01:00:00,9781.0 -2011-11-05 02:00:00,9206.0 -2011-11-05 03:00:00,8922.0 -2011-11-05 04:00:00,8672.0 -2011-11-05 05:00:00,8600.0 -2011-11-05 06:00:00,8602.0 -2011-11-05 07:00:00,8945.0 -2011-11-05 08:00:00,9444.0 -2011-11-05 09:00:00,9879.0 -2011-11-05 10:00:00,10040.0 -2011-11-05 11:00:00,10199.0 -2011-11-05 12:00:00,10206.0 -2011-11-05 13:00:00,10171.0 -2011-11-05 14:00:00,9988.0 -2011-11-05 15:00:00,9792.0 -2011-11-05 16:00:00,9616.0 -2011-11-05 17:00:00,9522.0 -2011-11-05 18:00:00,9528.0 -2011-11-05 19:00:00,9857.0 -2011-11-05 20:00:00,10621.0 -2011-11-05 21:00:00,10539.0 -2011-11-05 22:00:00,10413.0 -2011-11-05 23:00:00,10124.0 -2011-11-06 00:00:00,9685.0 -2011-11-04 01:00:00,9861.0 -2011-11-04 02:00:00,9316.0 -2011-11-04 03:00:00,9008.0 -2011-11-04 04:00:00,8778.0 -2011-11-04 05:00:00,8752.0 -2011-11-04 06:00:00,8960.0 -2011-11-04 07:00:00,9648.0 -2011-11-04 08:00:00,10917.0 -2011-11-04 09:00:00,11612.0 -2011-11-04 10:00:00,11653.0 -2011-11-04 11:00:00,11658.0 -2011-11-04 12:00:00,11691.0 -2011-11-04 13:00:00,11626.0 -2011-11-04 14:00:00,11491.0 -2011-11-04 15:00:00,11468.0 -2011-11-04 16:00:00,11243.0 -2011-11-04 17:00:00,11088.0 -2011-11-04 18:00:00,11002.0 -2011-11-04 19:00:00,11137.0 -2011-11-04 20:00:00,11824.0 -2011-11-04 21:00:00,11727.0 -2011-11-04 22:00:00,11456.0 -2011-11-04 23:00:00,11107.0 -2011-11-05 00:00:00,10461.0 -2011-11-03 01:00:00,9509.0 -2011-11-03 02:00:00,9001.0 -2011-11-03 03:00:00,8690.0 -2011-11-03 04:00:00,8518.0 -2011-11-03 05:00:00,8452.0 -2011-11-03 06:00:00,8693.0 -2011-11-03 07:00:00,9405.0 -2011-11-03 08:00:00,10637.0 -2011-11-03 09:00:00,11661.0 -2011-11-03 10:00:00,11943.0 -2011-11-03 11:00:00,12106.0 -2011-11-03 12:00:00,12189.0 -2011-11-03 13:00:00,12192.0 -2011-11-03 14:00:00,12188.0 -2011-11-03 15:00:00,12192.0 -2011-11-03 16:00:00,12085.0 -2011-11-03 17:00:00,12021.0 -2011-11-03 18:00:00,12006.0 -2011-11-03 19:00:00,12247.0 -2011-11-03 20:00:00,12465.0 -2011-11-03 21:00:00,12283.0 -2011-11-03 22:00:00,11955.0 -2011-11-03 23:00:00,11418.0 -2011-11-04 00:00:00,10612.0 -2011-11-02 01:00:00,9496.0 -2011-11-02 02:00:00,8940.0 -2011-11-02 03:00:00,8623.0 -2011-11-02 04:00:00,8406.0 -2011-11-02 05:00:00,8362.0 -2011-11-02 06:00:00,8522.0 -2011-11-02 07:00:00,9224.0 -2011-11-02 08:00:00,10556.0 -2011-11-02 09:00:00,11270.0 -2011-11-02 10:00:00,11330.0 -2011-11-02 11:00:00,11454.0 -2011-11-02 12:00:00,11593.0 -2011-11-02 13:00:00,11638.0 -2011-11-02 14:00:00,11564.0 -2011-11-02 15:00:00,11596.0 -2011-11-02 16:00:00,11516.0 -2011-11-02 17:00:00,11411.0 -2011-11-02 18:00:00,11366.0 -2011-11-02 19:00:00,11637.0 -2011-11-02 20:00:00,12152.0 -2011-11-02 21:00:00,11981.0 -2011-11-02 22:00:00,11626.0 -2011-11-02 23:00:00,11101.0 -2011-11-03 00:00:00,10258.0 -2011-11-01 01:00:00,9614.0 -2011-11-01 02:00:00,9123.0 -2011-11-01 03:00:00,8842.0 -2011-11-01 04:00:00,8669.0 -2011-11-01 05:00:00,8655.0 -2011-11-01 06:00:00,8887.0 -2011-11-01 07:00:00,9605.0 -2011-11-01 08:00:00,10940.0 -2011-11-01 09:00:00,11670.0 -2011-11-01 10:00:00,11648.0 -2011-11-01 11:00:00,11627.0 -2011-11-01 12:00:00,11660.0 -2011-11-01 13:00:00,11587.0 -2011-11-01 14:00:00,11549.0 -2011-11-01 15:00:00,11549.0 -2011-11-01 16:00:00,11528.0 -2011-11-01 17:00:00,11459.0 -2011-11-01 18:00:00,11381.0 -2011-11-01 19:00:00,11535.0 -2011-11-01 20:00:00,12155.0 -2011-11-01 21:00:00,12044.0 -2011-11-01 22:00:00,11758.0 -2011-11-01 23:00:00,11178.0 -2011-11-02 00:00:00,10325.0 -2011-10-31 01:00:00,9009.0 -2011-10-31 02:00:00,8598.0 -2011-10-31 03:00:00,8367.0 -2011-10-31 04:00:00,8262.0 -2011-10-31 05:00:00,8281.0 -2011-10-31 06:00:00,8586.0 -2011-10-31 07:00:00,9371.0 -2011-10-31 08:00:00,10776.0 -2011-10-31 09:00:00,11477.0 -2011-10-31 10:00:00,11525.0 -2011-10-31 11:00:00,11482.0 -2011-10-31 12:00:00,11516.0 -2011-10-31 13:00:00,11493.0 -2011-10-31 14:00:00,11408.0 -2011-10-31 15:00:00,11465.0 -2011-10-31 16:00:00,11306.0 -2011-10-31 17:00:00,11233.0 -2011-10-31 18:00:00,11162.0 -2011-10-31 19:00:00,11338.0 -2011-10-31 20:00:00,11826.0 -2011-10-31 21:00:00,11782.0 -2011-10-31 22:00:00,11561.0 -2011-10-31 23:00:00,11116.0 -2011-11-01 00:00:00,10369.0 -2011-10-30 01:00:00,9158.0 -2011-10-30 02:00:00,8790.0 -2011-10-30 03:00:00,8515.0 -2011-10-30 04:00:00,8365.0 -2011-10-30 05:00:00,8218.0 -2011-10-30 06:00:00,8252.0 -2011-10-30 07:00:00,8333.0 -2011-10-30 08:00:00,8647.0 -2011-10-30 09:00:00,8841.0 -2011-10-30 10:00:00,9067.0 -2011-10-30 11:00:00,9288.0 -2011-10-30 12:00:00,9384.0 -2011-10-30 13:00:00,9346.0 -2011-10-30 14:00:00,9385.0 -2011-10-30 15:00:00,9391.0 -2011-10-30 16:00:00,9407.0 -2011-10-30 17:00:00,9627.0 -2011-10-30 18:00:00,9945.0 -2011-10-30 19:00:00,10322.0 -2011-10-30 20:00:00,10761.0 -2011-10-30 21:00:00,10718.0 -2011-10-30 22:00:00,10498.0 -2011-10-30 23:00:00,10160.0 -2011-10-31 00:00:00,9595.0 -2011-10-29 01:00:00,9577.0 -2011-10-29 02:00:00,9015.0 -2011-10-29 03:00:00,8694.0 -2011-10-29 04:00:00,8506.0 -2011-10-29 05:00:00,8426.0 -2011-10-29 06:00:00,8475.0 -2011-10-29 07:00:00,8757.0 -2011-10-29 08:00:00,9315.0 -2011-10-29 09:00:00,9636.0 -2011-10-29 10:00:00,9880.0 -2011-10-29 11:00:00,10033.0 -2011-10-29 12:00:00,10099.0 -2011-10-29 13:00:00,10054.0 -2011-10-29 14:00:00,9907.0 -2011-10-29 15:00:00,9742.0 -2011-10-29 16:00:00,9574.0 -2011-10-29 17:00:00,9544.0 -2011-10-29 18:00:00,9511.0 -2011-10-29 19:00:00,9814.0 -2011-10-29 20:00:00,10580.0 -2011-10-29 21:00:00,10629.0 -2011-10-29 22:00:00,10426.0 -2011-10-29 23:00:00,10206.0 -2011-10-30 00:00:00,9697.0 -2011-10-28 01:00:00,9791.0 -2011-10-28 02:00:00,9301.0 -2011-10-28 03:00:00,8972.0 -2011-10-28 04:00:00,8764.0 -2011-10-28 05:00:00,8719.0 -2011-10-28 06:00:00,8935.0 -2011-10-28 07:00:00,9645.0 -2011-10-28 08:00:00,10871.0 -2011-10-28 09:00:00,11574.0 -2011-10-28 10:00:00,11574.0 -2011-10-28 11:00:00,11530.0 -2011-10-28 12:00:00,11562.0 -2011-10-28 13:00:00,11466.0 -2011-10-28 14:00:00,11397.0 -2011-10-28 15:00:00,11323.0 -2011-10-28 16:00:00,11158.0 -2011-10-28 17:00:00,10988.0 -2011-10-28 18:00:00,10911.0 -2011-10-28 19:00:00,11080.0 -2011-10-28 20:00:00,11674.0 -2011-10-28 21:00:00,11576.0 -2011-10-28 22:00:00,11295.0 -2011-10-28 23:00:00,10939.0 -2011-10-29 00:00:00,10290.0 -2011-10-27 01:00:00,9546.0 -2011-10-27 02:00:00,9012.0 -2011-10-27 03:00:00,8747.0 -2011-10-27 04:00:00,8543.0 -2011-10-27 05:00:00,8452.0 -2011-10-27 06:00:00,8658.0 -2011-10-27 07:00:00,9380.0 -2011-10-27 08:00:00,10654.0 -2011-10-27 09:00:00,11467.0 -2011-10-27 10:00:00,11577.0 -2011-10-27 11:00:00,11609.0 -2011-10-27 12:00:00,11651.0 -2011-10-27 13:00:00,11565.0 -2011-10-27 14:00:00,11485.0 -2011-10-27 15:00:00,11497.0 -2011-10-27 16:00:00,11373.0 -2011-10-27 17:00:00,11255.0 -2011-10-27 18:00:00,11203.0 -2011-10-27 19:00:00,11394.0 -2011-10-27 20:00:00,12074.0 -2011-10-27 21:00:00,12080.0 -2011-10-27 22:00:00,11746.0 -2011-10-27 23:00:00,11327.0 -2011-10-28 00:00:00,10479.0 -2011-10-26 01:00:00,9381.0 -2011-10-26 02:00:00,8830.0 -2011-10-26 03:00:00,8512.0 -2011-10-26 04:00:00,8278.0 -2011-10-26 05:00:00,8242.0 -2011-10-26 06:00:00,8427.0 -2011-10-26 07:00:00,9065.0 -2011-10-26 08:00:00,10265.0 -2011-10-26 09:00:00,11182.0 -2011-10-26 10:00:00,11411.0 -2011-10-26 11:00:00,11545.0 -2011-10-26 12:00:00,11726.0 -2011-10-26 13:00:00,11731.0 -2011-10-26 14:00:00,11715.0 -2011-10-26 15:00:00,11776.0 -2011-10-26 16:00:00,11658.0 -2011-10-26 17:00:00,11648.0 -2011-10-26 18:00:00,11691.0 -2011-10-26 19:00:00,11898.0 -2011-10-26 20:00:00,12149.0 -2011-10-26 21:00:00,11978.0 -2011-10-26 22:00:00,11689.0 -2011-10-26 23:00:00,11168.0 -2011-10-27 00:00:00,10330.0 -2011-10-25 01:00:00,9413.0 -2011-10-25 02:00:00,8882.0 -2011-10-25 03:00:00,8580.0 -2011-10-25 04:00:00,8383.0 -2011-10-25 05:00:00,8326.0 -2011-10-25 06:00:00,8521.0 -2011-10-25 07:00:00,9213.0 -2011-10-25 08:00:00,10489.0 -2011-10-25 09:00:00,11217.0 -2011-10-25 10:00:00,11322.0 -2011-10-25 11:00:00,11479.0 -2011-10-25 12:00:00,11691.0 -2011-10-25 13:00:00,11760.0 -2011-10-25 14:00:00,11773.0 -2011-10-25 15:00:00,11797.0 -2011-10-25 16:00:00,11730.0 -2011-10-25 17:00:00,11627.0 -2011-10-25 18:00:00,11568.0 -2011-10-25 19:00:00,11836.0 -2011-10-25 20:00:00,12266.0 -2011-10-25 21:00:00,12140.0 -2011-10-25 22:00:00,11707.0 -2011-10-25 23:00:00,11106.0 -2011-10-26 00:00:00,10206.0 -2011-10-24 01:00:00,8684.0 -2011-10-24 02:00:00,8338.0 -2011-10-24 03:00:00,8081.0 -2011-10-24 04:00:00,7956.0 -2011-10-24 05:00:00,7922.0 -2011-10-24 06:00:00,8197.0 -2011-10-24 07:00:00,8888.0 -2011-10-24 08:00:00,10289.0 -2011-10-24 09:00:00,10924.0 -2011-10-24 10:00:00,11093.0 -2011-10-24 11:00:00,11250.0 -2011-10-24 12:00:00,11388.0 -2011-10-24 13:00:00,11449.0 -2011-10-24 14:00:00,11514.0 -2011-10-24 15:00:00,11540.0 -2011-10-24 16:00:00,11455.0 -2011-10-24 17:00:00,11335.0 -2011-10-24 18:00:00,11207.0 -2011-10-24 19:00:00,11242.0 -2011-10-24 20:00:00,11891.0 -2011-10-24 21:00:00,11845.0 -2011-10-24 22:00:00,11556.0 -2011-10-24 23:00:00,11035.0 -2011-10-25 00:00:00,10191.0 -2011-10-23 01:00:00,9083.0 -2011-10-23 02:00:00,8543.0 -2011-10-23 03:00:00,8234.0 -2011-10-23 04:00:00,8042.0 -2011-10-23 05:00:00,7979.0 -2011-10-23 06:00:00,7975.0 -2011-10-23 07:00:00,8239.0 -2011-10-23 08:00:00,8509.0 -2011-10-23 09:00:00,8603.0 -2011-10-23 10:00:00,8743.0 -2011-10-23 11:00:00,8934.0 -2011-10-23 12:00:00,9102.0 -2011-10-23 13:00:00,9257.0 -2011-10-23 14:00:00,9199.0 -2011-10-23 15:00:00,9274.0 -2011-10-23 16:00:00,9229.0 -2011-10-23 17:00:00,9246.0 -2011-10-23 18:00:00,9207.0 -2011-10-23 19:00:00,9489.0 -2011-10-23 20:00:00,10248.0 -2011-10-23 21:00:00,10430.0 -2011-10-23 22:00:00,10198.0 -2011-10-23 23:00:00,9817.0 -2011-10-24 00:00:00,9281.0 -2011-10-22 01:00:00,9773.0 -2011-10-22 02:00:00,9198.0 -2011-10-22 03:00:00,8753.0 -2011-10-22 04:00:00,8680.0 -2011-10-22 05:00:00,8576.0 -2011-10-22 06:00:00,8620.0 -2011-10-22 07:00:00,8890.0 -2011-10-22 08:00:00,9385.0 -2011-10-22 09:00:00,9580.0 -2011-10-22 10:00:00,9874.0 -2011-10-22 11:00:00,10075.0 -2011-10-22 12:00:00,10095.0 -2011-10-22 13:00:00,10097.0 -2011-10-22 14:00:00,9947.0 -2011-10-22 15:00:00,9848.0 -2011-10-22 16:00:00,9711.0 -2011-10-22 17:00:00,9638.0 -2011-10-22 18:00:00,9582.0 -2011-10-22 19:00:00,9671.0 -2011-10-22 20:00:00,10324.0 -2011-10-22 21:00:00,10351.0 -2011-10-22 22:00:00,10353.0 -2011-10-22 23:00:00,9995.0 -2011-10-23 00:00:00,9566.0 -2011-10-21 01:00:00,9851.0 -2011-10-21 02:00:00,9414.0 -2011-10-21 03:00:00,9078.0 -2011-10-21 04:00:00,8854.0 -2011-10-21 05:00:00,8803.0 -2011-10-21 06:00:00,9025.0 -2011-10-21 07:00:00,9731.0 -2011-10-21 08:00:00,11011.0 -2011-10-21 09:00:00,11539.0 -2011-10-21 10:00:00,11594.0 -2011-10-21 11:00:00,11594.0 -2011-10-21 12:00:00,11573.0 -2011-10-21 13:00:00,11554.0 -2011-10-21 14:00:00,11472.0 -2011-10-21 15:00:00,11384.0 -2011-10-21 16:00:00,11257.0 -2011-10-21 17:00:00,11036.0 -2011-10-21 18:00:00,10952.0 -2011-10-21 19:00:00,10908.0 -2011-10-21 20:00:00,11539.0 -2011-10-21 21:00:00,11555.0 -2011-10-21 22:00:00,11329.0 -2011-10-21 23:00:00,11054.0 -2011-10-22 00:00:00,10425.0 -2011-10-20 01:00:00,9825.0 -2011-10-20 02:00:00,9273.0 -2011-10-20 03:00:00,8987.0 -2011-10-20 04:00:00,8799.0 -2011-10-20 05:00:00,8725.0 -2011-10-20 06:00:00,8976.0 -2011-10-20 07:00:00,9676.0 -2011-10-20 08:00:00,10955.0 -2011-10-20 09:00:00,11743.0 -2011-10-20 10:00:00,11840.0 -2011-10-20 11:00:00,11983.0 -2011-10-20 12:00:00,12101.0 -2011-10-20 13:00:00,12098.0 -2011-10-20 14:00:00,12051.0 -2011-10-20 15:00:00,12024.0 -2011-10-20 16:00:00,11891.0 -2011-10-20 17:00:00,11766.0 -2011-10-20 18:00:00,11750.0 -2011-10-20 19:00:00,11916.0 -2011-10-20 20:00:00,12354.0 -2011-10-20 21:00:00,12281.0 -2011-10-20 22:00:00,11925.0 -2011-10-20 23:00:00,11443.0 -2011-10-21 00:00:00,10666.0 -2011-10-19 01:00:00,9554.0 -2011-10-19 02:00:00,9007.0 -2011-10-19 03:00:00,8693.0 -2011-10-19 04:00:00,8528.0 -2011-10-19 05:00:00,8460.0 -2011-10-19 06:00:00,8651.0 -2011-10-19 07:00:00,9391.0 -2011-10-19 08:00:00,10680.0 -2011-10-19 09:00:00,11436.0 -2011-10-19 10:00:00,11708.0 -2011-10-19 11:00:00,11763.0 -2011-10-19 12:00:00,11961.0 -2011-10-19 13:00:00,11953.0 -2011-10-19 14:00:00,11862.0 -2011-10-19 15:00:00,11888.0 -2011-10-19 16:00:00,11887.0 -2011-10-19 17:00:00,11919.0 -2011-10-19 18:00:00,12035.0 -2011-10-19 19:00:00,12215.0 -2011-10-19 20:00:00,12469.0 -2011-10-19 21:00:00,12328.0 -2011-10-19 22:00:00,12015.0 -2011-10-19 23:00:00,11490.0 -2011-10-20 00:00:00,10643.0 -2011-10-18 01:00:00,9402.0 -2011-10-18 02:00:00,8897.0 -2011-10-18 03:00:00,8599.0 -2011-10-18 04:00:00,8432.0 -2011-10-18 05:00:00,8390.0 -2011-10-18 06:00:00,8590.0 -2011-10-18 07:00:00,9289.0 -2011-10-18 08:00:00,10648.0 -2011-10-18 09:00:00,11329.0 -2011-10-18 10:00:00,11442.0 -2011-10-18 11:00:00,11566.0 -2011-10-18 12:00:00,11604.0 -2011-10-18 13:00:00,11576.0 -2011-10-18 14:00:00,11526.0 -2011-10-18 15:00:00,11512.0 -2011-10-18 16:00:00,11401.0 -2011-10-18 17:00:00,11536.0 -2011-10-18 18:00:00,11582.0 -2011-10-18 19:00:00,11731.0 -2011-10-18 20:00:00,12090.0 -2011-10-18 21:00:00,12019.0 -2011-10-18 22:00:00,11713.0 -2011-10-18 23:00:00,11165.0 -2011-10-19 00:00:00,10334.0 -2011-10-17 01:00:00,8596.0 -2011-10-17 02:00:00,8191.0 -2011-10-17 03:00:00,8027.0 -2011-10-17 04:00:00,7896.0 -2011-10-17 05:00:00,7934.0 -2011-10-17 06:00:00,8147.0 -2011-10-17 07:00:00,8958.0 -2011-10-17 08:00:00,10252.0 -2011-10-17 09:00:00,10939.0 -2011-10-17 10:00:00,11109.0 -2011-10-17 11:00:00,11202.0 -2011-10-17 12:00:00,11326.0 -2011-10-17 13:00:00,11385.0 -2011-10-17 14:00:00,11384.0 -2011-10-17 15:00:00,11410.0 -2011-10-17 16:00:00,11289.0 -2011-10-17 17:00:00,11153.0 -2011-10-17 18:00:00,11067.0 -2011-10-17 19:00:00,11183.0 -2011-10-17 20:00:00,11795.0 -2011-10-17 21:00:00,11853.0 -2011-10-17 22:00:00,11589.0 -2011-10-17 23:00:00,11037.0 -2011-10-18 00:00:00,10193.0 -2011-10-16 01:00:00,8785.0 -2011-10-16 02:00:00,8247.0 -2011-10-16 03:00:00,7950.0 -2011-10-16 04:00:00,7764.0 -2011-10-16 05:00:00,7645.0 -2011-10-16 06:00:00,7645.0 -2011-10-16 07:00:00,7746.0 -2011-10-16 08:00:00,8022.0 -2011-10-16 09:00:00,8291.0 -2011-10-16 10:00:00,8553.0 -2011-10-16 11:00:00,8847.0 -2011-10-16 12:00:00,9063.0 -2011-10-16 13:00:00,9128.0 -2011-10-16 14:00:00,9190.0 -2011-10-16 15:00:00,9108.0 -2011-10-16 16:00:00,9121.0 -2011-10-16 17:00:00,9050.0 -2011-10-16 18:00:00,9137.0 -2011-10-16 19:00:00,9173.0 -2011-10-16 20:00:00,9946.0 -2011-10-16 21:00:00,10259.0 -2011-10-16 22:00:00,10060.0 -2011-10-16 23:00:00,9753.0 -2011-10-17 00:00:00,9139.0 -2011-10-15 01:00:00,9383.0 -2011-10-15 02:00:00,8827.0 -2011-10-15 03:00:00,8433.0 -2011-10-15 04:00:00,8229.0 -2011-10-15 05:00:00,8101.0 -2011-10-15 06:00:00,8113.0 -2011-10-15 07:00:00,8366.0 -2011-10-15 08:00:00,8807.0 -2011-10-15 09:00:00,8972.0 -2011-10-15 10:00:00,9386.0 -2011-10-15 11:00:00,9619.0 -2011-10-15 12:00:00,9775.0 -2011-10-15 13:00:00,9784.0 -2011-10-15 14:00:00,9735.0 -2011-10-15 15:00:00,9619.0 -2011-10-15 16:00:00,9469.0 -2011-10-15 17:00:00,9430.0 -2011-10-15 18:00:00,9405.0 -2011-10-15 19:00:00,9463.0 -2011-10-15 20:00:00,10075.0 -2011-10-15 21:00:00,10240.0 -2011-10-15 22:00:00,10140.0 -2011-10-15 23:00:00,9827.0 -2011-10-16 00:00:00,9339.0 -2011-10-14 01:00:00,9500.0 -2011-10-14 02:00:00,8866.0 -2011-10-14 03:00:00,8543.0 -2011-10-14 04:00:00,8304.0 -2011-10-14 05:00:00,8270.0 -2011-10-14 06:00:00,8391.0 -2011-10-14 07:00:00,9027.0 -2011-10-14 08:00:00,10222.0 -2011-10-14 09:00:00,10754.0 -2011-10-14 10:00:00,10948.0 -2011-10-14 11:00:00,11095.0 -2011-10-14 12:00:00,11288.0 -2011-10-14 13:00:00,11311.0 -2011-10-14 14:00:00,11320.0 -2011-10-14 15:00:00,11332.0 -2011-10-14 16:00:00,11227.0 -2011-10-14 17:00:00,11058.0 -2011-10-14 18:00:00,10906.0 -2011-10-14 19:00:00,10840.0 -2011-10-14 20:00:00,11334.0 -2011-10-14 21:00:00,11468.0 -2011-10-14 22:00:00,11195.0 -2011-10-14 23:00:00,10773.0 -2011-10-15 00:00:00,10081.0 -2011-10-13 01:00:00,9804.0 -2011-10-13 02:00:00,9208.0 -2011-10-13 03:00:00,8836.0 -2011-10-13 04:00:00,8591.0 -2011-10-13 05:00:00,8560.0 -2011-10-13 06:00:00,8690.0 -2011-10-13 07:00:00,9314.0 -2011-10-13 08:00:00,10564.0 -2011-10-13 09:00:00,11387.0 -2011-10-13 10:00:00,11713.0 -2011-10-13 11:00:00,11912.0 -2011-10-13 12:00:00,12118.0 -2011-10-13 13:00:00,12173.0 -2011-10-13 14:00:00,12159.0 -2011-10-13 15:00:00,12197.0 -2011-10-13 16:00:00,12044.0 -2011-10-13 17:00:00,11905.0 -2011-10-13 18:00:00,11834.0 -2011-10-13 19:00:00,11812.0 -2011-10-13 20:00:00,12038.0 -2011-10-13 21:00:00,12140.0 -2011-10-13 22:00:00,11792.0 -2011-10-13 23:00:00,11250.0 -2011-10-14 00:00:00,10321.0 -2011-10-12 01:00:00,9865.0 -2011-10-12 02:00:00,9248.0 -2011-10-12 03:00:00,8832.0 -2011-10-12 04:00:00,8536.0 -2011-10-12 05:00:00,8428.0 -2011-10-12 06:00:00,8579.0 -2011-10-12 07:00:00,9154.0 -2011-10-12 08:00:00,10412.0 -2011-10-12 09:00:00,11010.0 -2011-10-12 10:00:00,11379.0 -2011-10-12 11:00:00,11800.0 -2011-10-12 12:00:00,12246.0 -2011-10-12 13:00:00,12554.0 -2011-10-12 14:00:00,12748.0 -2011-10-12 15:00:00,12951.0 -2011-10-12 16:00:00,12875.0 -2011-10-12 17:00:00,12758.0 -2011-10-12 18:00:00,12480.0 -2011-10-12 19:00:00,12287.0 -2011-10-12 20:00:00,12581.0 -2011-10-12 21:00:00,12649.0 -2011-10-12 22:00:00,12282.0 -2011-10-12 23:00:00,11671.0 -2011-10-13 00:00:00,10726.0 -2011-10-11 01:00:00,9740.0 -2011-10-11 02:00:00,9103.0 -2011-10-11 03:00:00,8750.0 -2011-10-11 04:00:00,8494.0 -2011-10-11 05:00:00,8387.0 -2011-10-11 06:00:00,8540.0 -2011-10-11 07:00:00,9193.0 -2011-10-11 08:00:00,10424.0 -2011-10-11 09:00:00,10981.0 -2011-10-11 10:00:00,11469.0 -2011-10-11 11:00:00,11949.0 -2011-10-11 12:00:00,12415.0 -2011-10-11 13:00:00,12696.0 -2011-10-11 14:00:00,12859.0 -2011-10-11 15:00:00,13015.0 -2011-10-11 16:00:00,12903.0 -2011-10-11 17:00:00,12807.0 -2011-10-11 18:00:00,12660.0 -2011-10-11 19:00:00,12428.0 -2011-10-11 20:00:00,12639.0 -2011-10-11 21:00:00,12778.0 -2011-10-11 22:00:00,12391.0 -2011-10-11 23:00:00,11770.0 -2011-10-12 00:00:00,10790.0 -2011-10-10 01:00:00,9126.0 -2011-10-10 02:00:00,8596.0 -2011-10-10 03:00:00,8311.0 -2011-10-10 04:00:00,8090.0 -2011-10-10 05:00:00,8074.0 -2011-10-10 06:00:00,8202.0 -2011-10-10 07:00:00,8816.0 -2011-10-10 08:00:00,9819.0 -2011-10-10 09:00:00,10417.0 -2011-10-10 10:00:00,10897.0 -2011-10-10 11:00:00,11432.0 -2011-10-10 12:00:00,11770.0 -2011-10-10 13:00:00,12008.0 -2011-10-10 14:00:00,12182.0 -2011-10-10 15:00:00,12388.0 -2011-10-10 16:00:00,12420.0 -2011-10-10 17:00:00,12400.0 -2011-10-10 18:00:00,12282.0 -2011-10-10 19:00:00,12078.0 -2011-10-10 20:00:00,12342.0 -2011-10-10 21:00:00,12464.0 -2011-10-10 22:00:00,12085.0 -2011-10-10 23:00:00,11498.0 -2011-10-11 00:00:00,10628.0 -2011-10-09 01:00:00,9490.0 -2011-10-09 02:00:00,8919.0 -2011-10-09 03:00:00,8468.0 -2011-10-09 04:00:00,8191.0 -2011-10-09 05:00:00,7945.0 -2011-10-09 06:00:00,7887.0 -2011-10-09 07:00:00,7890.0 -2011-10-09 08:00:00,8082.0 -2011-10-09 09:00:00,8020.0 -2011-10-09 10:00:00,8518.0 -2011-10-09 11:00:00,9031.0 -2011-10-09 12:00:00,9542.0 -2011-10-09 13:00:00,9909.0 -2011-10-09 14:00:00,10182.0 -2011-10-09 15:00:00,10395.0 -2011-10-09 16:00:00,10504.0 -2011-10-09 17:00:00,10595.0 -2011-10-09 18:00:00,10579.0 -2011-10-09 19:00:00,10509.0 -2011-10-09 20:00:00,10756.0 -2011-10-09 21:00:00,11034.0 -2011-10-09 22:00:00,10728.0 -2011-10-09 23:00:00,10355.0 -2011-10-10 00:00:00,9747.0 -2011-10-08 01:00:00,10140.0 -2011-10-08 02:00:00,9476.0 -2011-10-08 03:00:00,8962.0 -2011-10-08 04:00:00,8654.0 -2011-10-08 05:00:00,8447.0 -2011-10-08 06:00:00,8444.0 -2011-10-08 07:00:00,8593.0 -2011-10-08 08:00:00,9002.0 -2011-10-08 09:00:00,9139.0 -2011-10-08 10:00:00,9775.0 -2011-10-08 11:00:00,10304.0 -2011-10-08 12:00:00,10822.0 -2011-10-08 13:00:00,11145.0 -2011-10-08 14:00:00,11306.0 -2011-10-08 15:00:00,11404.0 -2011-10-08 16:00:00,11552.0 -2011-10-08 17:00:00,11623.0 -2011-10-08 18:00:00,11485.0 -2011-10-08 19:00:00,11319.0 -2011-10-08 20:00:00,11424.0 -2011-10-08 21:00:00,11580.0 -2011-10-08 22:00:00,11320.0 -2011-10-08 23:00:00,10862.0 -2011-10-09 00:00:00,10212.0 -2011-10-07 01:00:00,9784.0 -2011-10-07 02:00:00,9169.0 -2011-10-07 03:00:00,8753.0 -2011-10-07 04:00:00,8459.0 -2011-10-07 05:00:00,8356.0 -2011-10-07 06:00:00,8500.0 -2011-10-07 07:00:00,9127.0 -2011-10-07 08:00:00,10205.0 -2011-10-07 09:00:00,10821.0 -2011-10-07 10:00:00,11345.0 -2011-10-07 11:00:00,11798.0 -2011-10-07 12:00:00,12295.0 -2011-10-07 13:00:00,12638.0 -2011-10-07 14:00:00,12882.0 -2011-10-07 15:00:00,13047.0 -2011-10-07 16:00:00,13100.0 -2011-10-07 17:00:00,13091.0 -2011-10-07 18:00:00,12922.0 -2011-10-07 19:00:00,12554.0 -2011-10-07 20:00:00,12604.0 -2011-10-07 21:00:00,12714.0 -2011-10-07 22:00:00,12371.0 -2011-10-07 23:00:00,11823.0 -2011-10-08 00:00:00,11010.0 -2011-10-06 01:00:00,9555.0 -2011-10-06 02:00:00,8940.0 -2011-10-06 03:00:00,8552.0 -2011-10-06 04:00:00,8322.0 -2011-10-06 05:00:00,8230.0 -2011-10-06 06:00:00,8398.0 -2011-10-06 07:00:00,8984.0 -2011-10-06 08:00:00,10198.0 -2011-10-06 09:00:00,10805.0 -2011-10-06 10:00:00,11250.0 -2011-10-06 11:00:00,11641.0 -2011-10-06 12:00:00,12117.0 -2011-10-06 13:00:00,12349.0 -2011-10-06 14:00:00,12574.0 -2011-10-06 15:00:00,12762.0 -2011-10-06 16:00:00,12782.0 -2011-10-06 17:00:00,12786.0 -2011-10-06 18:00:00,12626.0 -2011-10-06 19:00:00,12356.0 -2011-10-06 20:00:00,12407.0 -2011-10-06 21:00:00,12593.0 -2011-10-06 22:00:00,12270.0 -2011-10-06 23:00:00,11624.0 -2011-10-07 00:00:00,10712.0 -2011-10-05 01:00:00,9311.0 -2011-10-05 02:00:00,8763.0 -2011-10-05 03:00:00,8435.0 -2011-10-05 04:00:00,8196.0 -2011-10-05 05:00:00,8112.0 -2011-10-05 06:00:00,8299.0 -2011-10-05 07:00:00,8937.0 -2011-10-05 08:00:00,10075.0 -2011-10-05 09:00:00,10658.0 -2011-10-05 10:00:00,11052.0 -2011-10-05 11:00:00,11400.0 -2011-10-05 12:00:00,11760.0 -2011-10-05 13:00:00,12020.0 -2011-10-05 14:00:00,12139.0 -2011-10-05 15:00:00,12360.0 -2011-10-05 16:00:00,12342.0 -2011-10-05 17:00:00,12292.0 -2011-10-05 18:00:00,12191.0 -2011-10-05 19:00:00,11941.0 -2011-10-05 20:00:00,11976.0 -2011-10-05 21:00:00,12264.0 -2011-10-05 22:00:00,11922.0 -2011-10-05 23:00:00,11302.0 -2011-10-06 00:00:00,10413.0 -2011-10-04 01:00:00,9201.0 -2011-10-04 02:00:00,8660.0 -2011-10-04 03:00:00,8356.0 -2011-10-04 04:00:00,8163.0 -2011-10-04 05:00:00,8105.0 -2011-10-04 06:00:00,8295.0 -2011-10-04 07:00:00,8955.0 -2011-10-04 08:00:00,10157.0 -2011-10-04 09:00:00,10670.0 -2011-10-04 10:00:00,11008.0 -2011-10-04 11:00:00,11279.0 -2011-10-04 12:00:00,11571.0 -2011-10-04 13:00:00,11711.0 -2011-10-04 14:00:00,11812.0 -2011-10-04 15:00:00,11937.0 -2011-10-04 16:00:00,11947.0 -2011-10-04 17:00:00,11863.0 -2011-10-04 18:00:00,11732.0 -2011-10-04 19:00:00,11519.0 -2011-10-04 20:00:00,11626.0 -2011-10-04 21:00:00,11981.0 -2011-10-04 22:00:00,11663.0 -2011-10-04 23:00:00,11068.0 -2011-10-05 00:00:00,10150.0 -2011-10-03 01:00:00,8532.0 -2011-10-03 02:00:00,8093.0 -2011-10-03 03:00:00,7916.0 -2011-10-03 04:00:00,7746.0 -2011-10-03 05:00:00,7797.0 -2011-10-03 06:00:00,8034.0 -2011-10-03 07:00:00,8736.0 -2011-10-03 08:00:00,9985.0 -2011-10-03 09:00:00,10593.0 -2011-10-03 10:00:00,10914.0 -2011-10-03 11:00:00,11130.0 -2011-10-03 12:00:00,11430.0 -2011-10-03 13:00:00,11555.0 -2011-10-03 14:00:00,11618.0 -2011-10-03 15:00:00,11682.0 -2011-10-03 16:00:00,11658.0 -2011-10-03 17:00:00,11500.0 -2011-10-03 18:00:00,11328.0 -2011-10-03 19:00:00,11145.0 -2011-10-03 20:00:00,11277.0 -2011-10-03 21:00:00,11684.0 -2011-10-03 22:00:00,11373.0 -2011-10-03 23:00:00,10863.0 -2011-10-04 00:00:00,10036.0 -2011-10-02 01:00:00,8769.0 -2011-10-02 02:00:00,8287.0 -2011-10-02 03:00:00,7997.0 -2011-10-02 04:00:00,7833.0 -2011-10-02 05:00:00,7730.0 -2011-10-02 06:00:00,7777.0 -2011-10-02 07:00:00,7867.0 -2011-10-02 08:00:00,8113.0 -2011-10-02 09:00:00,8140.0 -2011-10-02 10:00:00,8484.0 -2011-10-02 11:00:00,8700.0 -2011-10-02 12:00:00,8944.0 -2011-10-02 13:00:00,9024.0 -2011-10-02 14:00:00,9084.0 -2011-10-02 15:00:00,9058.0 -2011-10-02 16:00:00,9032.0 -2011-10-02 17:00:00,9025.0 -2011-10-02 18:00:00,9075.0 -2011-10-02 19:00:00,9155.0 -2011-10-02 20:00:00,9523.0 -2011-10-02 21:00:00,10175.0 -2011-10-02 22:00:00,10017.0 -2011-10-02 23:00:00,9672.0 -2011-10-03 00:00:00,9069.0 -2011-10-01 01:00:00,9365.0 -2011-10-01 02:00:00,8821.0 -2011-10-01 03:00:00,8481.0 -2011-10-01 04:00:00,8236.0 -2011-10-01 05:00:00,8143.0 -2011-10-01 06:00:00,8112.0 -2011-10-01 07:00:00,8382.0 -2011-10-01 08:00:00,8797.0 -2011-10-01 09:00:00,9005.0 -2011-10-01 10:00:00,9384.0 -2011-10-01 11:00:00,9654.0 -2011-10-01 12:00:00,9774.0 -2011-10-01 13:00:00,9739.0 -2011-10-01 14:00:00,9661.0 -2011-10-01 15:00:00,9517.0 -2011-10-01 16:00:00,9399.0 -2011-10-01 17:00:00,9307.0 -2011-10-01 18:00:00,9285.0 -2011-10-01 19:00:00,9282.0 -2011-10-01 20:00:00,9585.0 -2011-10-01 21:00:00,10149.0 -2011-10-01 22:00:00,10058.0 -2011-10-01 23:00:00,9785.0 -2011-10-02 00:00:00,9321.0 -2011-09-30 01:00:00,9380.0 -2011-09-30 02:00:00,8846.0 -2011-09-30 03:00:00,8532.0 -2011-09-30 04:00:00,8318.0 -2011-09-30 05:00:00,8279.0 -2011-09-30 06:00:00,8436.0 -2011-09-30 07:00:00,9075.0 -2011-09-30 08:00:00,10224.0 -2011-09-30 09:00:00,10810.0 -2011-09-30 10:00:00,10998.0 -2011-09-30 11:00:00,11104.0 -2011-09-30 12:00:00,11232.0 -2011-09-30 13:00:00,11214.0 -2011-09-30 14:00:00,11247.0 -2011-09-30 15:00:00,11270.0 -2011-09-30 16:00:00,11127.0 -2011-09-30 17:00:00,10950.0 -2011-09-30 18:00:00,10839.0 -2011-09-30 19:00:00,10734.0 -2011-09-30 20:00:00,11025.0 -2011-09-30 21:00:00,11305.0 -2011-09-30 22:00:00,11111.0 -2011-09-30 23:00:00,10746.0 -2011-10-01 00:00:00,10068.0 -2011-09-29 01:00:00,9352.0 -2011-09-29 02:00:00,8807.0 -2011-09-29 03:00:00,8483.0 -2011-09-29 04:00:00,8272.0 -2011-09-29 05:00:00,8241.0 -2011-09-29 06:00:00,8399.0 -2011-09-29 07:00:00,9050.0 -2011-09-29 08:00:00,10168.0 -2011-09-29 09:00:00,10735.0 -2011-09-29 10:00:00,11095.0 -2011-09-29 11:00:00,11369.0 -2011-09-29 12:00:00,11585.0 -2011-09-29 13:00:00,11750.0 -2011-09-29 14:00:00,11814.0 -2011-09-29 15:00:00,11863.0 -2011-09-29 16:00:00,11670.0 -2011-09-29 17:00:00,11510.0 -2011-09-29 18:00:00,11431.0 -2011-09-29 19:00:00,11365.0 -2011-09-29 20:00:00,11465.0 -2011-09-29 21:00:00,11800.0 -2011-09-29 22:00:00,11497.0 -2011-09-29 23:00:00,11023.0 -2011-09-30 00:00:00,10150.0 -2011-09-28 01:00:00,9295.0 -2011-09-28 02:00:00,8822.0 -2011-09-28 03:00:00,8503.0 -2011-09-28 04:00:00,8275.0 -2011-09-28 05:00:00,8226.0 -2011-09-28 06:00:00,8388.0 -2011-09-28 07:00:00,8994.0 -2011-09-28 08:00:00,10169.0 -2011-09-28 09:00:00,10845.0 -2011-09-28 10:00:00,11191.0 -2011-09-28 11:00:00,11393.0 -2011-09-28 12:00:00,11575.0 -2011-09-28 13:00:00,11661.0 -2011-09-28 14:00:00,11625.0 -2011-09-28 15:00:00,11707.0 -2011-09-28 16:00:00,11569.0 -2011-09-28 17:00:00,11505.0 -2011-09-28 18:00:00,11392.0 -2011-09-28 19:00:00,11325.0 -2011-09-28 20:00:00,11522.0 -2011-09-28 21:00:00,11822.0 -2011-09-28 22:00:00,11545.0 -2011-09-28 23:00:00,11018.0 -2011-09-29 00:00:00,10177.0 -2011-09-27 01:00:00,9300.0 -2011-09-27 02:00:00,8776.0 -2011-09-27 03:00:00,8477.0 -2011-09-27 04:00:00,8263.0 -2011-09-27 05:00:00,8199.0 -2011-09-27 06:00:00,8369.0 -2011-09-27 07:00:00,9039.0 -2011-09-27 08:00:00,10255.0 -2011-09-27 09:00:00,10931.0 -2011-09-27 10:00:00,11208.0 -2011-09-27 11:00:00,11459.0 -2011-09-27 12:00:00,11596.0 -2011-09-27 13:00:00,11647.0 -2011-09-27 14:00:00,11615.0 -2011-09-27 15:00:00,11639.0 -2011-09-27 16:00:00,11553.0 -2011-09-27 17:00:00,11413.0 -2011-09-27 18:00:00,11308.0 -2011-09-27 19:00:00,11260.0 -2011-09-27 20:00:00,11449.0 -2011-09-27 21:00:00,11779.0 -2011-09-27 22:00:00,11522.0 -2011-09-27 23:00:00,10968.0 -2011-09-28 00:00:00,10119.0 -2011-09-26 01:00:00,8581.0 -2011-09-26 02:00:00,8192.0 -2011-09-26 03:00:00,8007.0 -2011-09-26 04:00:00,7935.0 -2011-09-26 05:00:00,7931.0 -2011-09-26 06:00:00,8205.0 -2011-09-26 07:00:00,8897.0 -2011-09-26 08:00:00,10070.0 -2011-09-26 09:00:00,10824.0 -2011-09-26 10:00:00,11113.0 -2011-09-26 11:00:00,11314.0 -2011-09-26 12:00:00,11553.0 -2011-09-26 13:00:00,11576.0 -2011-09-26 14:00:00,11590.0 -2011-09-26 15:00:00,11610.0 -2011-09-26 16:00:00,11535.0 -2011-09-26 17:00:00,11362.0 -2011-09-26 18:00:00,11293.0 -2011-09-26 19:00:00,11254.0 -2011-09-26 20:00:00,11404.0 -2011-09-26 21:00:00,11765.0 -2011-09-26 22:00:00,11515.0 -2011-09-26 23:00:00,10971.0 -2011-09-27 00:00:00,10107.0 -2011-09-25 01:00:00,8712.0 -2011-09-25 02:00:00,8183.0 -2011-09-25 03:00:00,7925.0 -2011-09-25 04:00:00,7682.0 -2011-09-25 05:00:00,7575.0 -2011-09-25 06:00:00,7527.0 -2011-09-25 07:00:00,7630.0 -2011-09-25 08:00:00,7797.0 -2011-09-25 09:00:00,7960.0 -2011-09-25 10:00:00,8391.0 -2011-09-25 11:00:00,8786.0 -2011-09-25 12:00:00,9041.0 -2011-09-25 13:00:00,9211.0 -2011-09-25 14:00:00,9222.0 -2011-09-25 15:00:00,9284.0 -2011-09-25 16:00:00,9233.0 -2011-09-25 17:00:00,9221.0 -2011-09-25 18:00:00,9241.0 -2011-09-25 19:00:00,9312.0 -2011-09-25 20:00:00,9594.0 -2011-09-25 21:00:00,10235.0 -2011-09-25 22:00:00,10073.0 -2011-09-25 23:00:00,9716.0 -2011-09-26 00:00:00,9142.0 -2011-09-24 01:00:00,9181.0 -2011-09-24 02:00:00,8609.0 -2011-09-24 03:00:00,8252.0 -2011-09-24 04:00:00,8019.0 -2011-09-24 05:00:00,7871.0 -2011-09-24 06:00:00,7938.0 -2011-09-24 07:00:00,8134.0 -2011-09-24 08:00:00,8546.0 -2011-09-24 09:00:00,8740.0 -2011-09-24 10:00:00,9228.0 -2011-09-24 11:00:00,9580.0 -2011-09-24 12:00:00,9804.0 -2011-09-24 13:00:00,9865.0 -2011-09-24 14:00:00,9740.0 -2011-09-24 15:00:00,9639.0 -2011-09-24 16:00:00,9526.0 -2011-09-24 17:00:00,9461.0 -2011-09-24 18:00:00,9451.0 -2011-09-24 19:00:00,9449.0 -2011-09-24 20:00:00,9613.0 -2011-09-24 21:00:00,10093.0 -2011-09-24 22:00:00,10023.0 -2011-09-24 23:00:00,9755.0 -2011-09-25 00:00:00,9230.0 -2011-09-23 01:00:00,9294.0 -2011-09-23 02:00:00,8760.0 -2011-09-23 03:00:00,8435.0 -2011-09-23 04:00:00,8187.0 -2011-09-23 05:00:00,8131.0 -2011-09-23 06:00:00,8287.0 -2011-09-23 07:00:00,8875.0 -2011-09-23 08:00:00,9935.0 -2011-09-23 09:00:00,10535.0 -2011-09-23 10:00:00,10811.0 -2011-09-23 11:00:00,11014.0 -2011-09-23 12:00:00,11198.0 -2011-09-23 13:00:00,11243.0 -2011-09-23 14:00:00,11295.0 -2011-09-23 15:00:00,11336.0 -2011-09-23 16:00:00,11222.0 -2011-09-23 17:00:00,11017.0 -2011-09-23 18:00:00,10857.0 -2011-09-23 19:00:00,10742.0 -2011-09-23 20:00:00,10894.0 -2011-09-23 21:00:00,11205.0 -2011-09-23 22:00:00,10936.0 -2011-09-23 23:00:00,10571.0 -2011-09-24 00:00:00,9875.0 -2011-09-22 01:00:00,9397.0 -2011-09-22 02:00:00,8865.0 -2011-09-22 03:00:00,8511.0 -2011-09-22 04:00:00,8284.0 -2011-09-22 05:00:00,8245.0 -2011-09-22 06:00:00,8392.0 -2011-09-22 07:00:00,8971.0 -2011-09-22 08:00:00,10037.0 -2011-09-22 09:00:00,10596.0 -2011-09-22 10:00:00,11001.0 -2011-09-22 11:00:00,11290.0 -2011-09-22 12:00:00,11503.0 -2011-09-22 13:00:00,11534.0 -2011-09-22 14:00:00,11510.0 -2011-09-22 15:00:00,11551.0 -2011-09-22 16:00:00,11478.0 -2011-09-22 17:00:00,11368.0 -2011-09-22 18:00:00,11273.0 -2011-09-22 19:00:00,11086.0 -2011-09-22 20:00:00,11123.0 -2011-09-22 21:00:00,11649.0 -2011-09-22 22:00:00,11456.0 -2011-09-22 23:00:00,10936.0 -2011-09-23 00:00:00,10079.0 -2011-09-21 01:00:00,9723.0 -2011-09-21 02:00:00,9154.0 -2011-09-21 03:00:00,8753.0 -2011-09-21 04:00:00,8547.0 -2011-09-21 05:00:00,8433.0 -2011-09-21 06:00:00,8584.0 -2011-09-21 07:00:00,9206.0 -2011-09-21 08:00:00,10358.0 -2011-09-21 09:00:00,10989.0 -2011-09-21 10:00:00,11379.0 -2011-09-21 11:00:00,11683.0 -2011-09-21 12:00:00,11953.0 -2011-09-21 13:00:00,12100.0 -2011-09-21 14:00:00,12202.0 -2011-09-21 15:00:00,12307.0 -2011-09-21 16:00:00,12239.0 -2011-09-21 17:00:00,12130.0 -2011-09-21 18:00:00,11941.0 -2011-09-21 19:00:00,11654.0 -2011-09-21 20:00:00,11523.0 -2011-09-21 21:00:00,11987.0 -2011-09-21 22:00:00,11733.0 -2011-09-21 23:00:00,11153.0 -2011-09-22 00:00:00,10259.0 -2011-09-20 01:00:00,9425.0 -2011-09-20 02:00:00,8855.0 -2011-09-20 03:00:00,8513.0 -2011-09-20 04:00:00,8290.0 -2011-09-20 05:00:00,8209.0 -2011-09-20 06:00:00,8380.0 -2011-09-20 07:00:00,9004.0 -2011-09-20 08:00:00,10084.0 -2011-09-20 09:00:00,10679.0 -2011-09-20 10:00:00,11161.0 -2011-09-20 11:00:00,11515.0 -2011-09-20 12:00:00,11865.0 -2011-09-20 13:00:00,12069.0 -2011-09-20 14:00:00,12178.0 -2011-09-20 15:00:00,12319.0 -2011-09-20 16:00:00,12353.0 -2011-09-20 17:00:00,12313.0 -2011-09-20 18:00:00,12214.0 -2011-09-20 19:00:00,11980.0 -2011-09-20 20:00:00,11771.0 -2011-09-20 21:00:00,12264.0 -2011-09-20 22:00:00,12077.0 -2011-09-20 23:00:00,11464.0 -2011-09-21 00:00:00,10567.0 -2011-09-19 01:00:00,8809.0 -2011-09-19 02:00:00,8468.0 -2011-09-19 03:00:00,8208.0 -2011-09-19 04:00:00,8163.0 -2011-09-19 05:00:00,8122.0 -2011-09-19 06:00:00,8368.0 -2011-09-19 07:00:00,9027.0 -2011-09-19 08:00:00,10316.0 -2011-09-19 09:00:00,11165.0 -2011-09-19 10:00:00,11599.0 -2011-09-19 11:00:00,11793.0 -2011-09-19 12:00:00,12002.0 -2011-09-19 13:00:00,12072.0 -2011-09-19 14:00:00,12055.0 -2011-09-19 15:00:00,12087.0 -2011-09-19 16:00:00,11999.0 -2011-09-19 17:00:00,11859.0 -2011-09-19 18:00:00,11772.0 -2011-09-19 19:00:00,11608.0 -2011-09-19 20:00:00,11447.0 -2011-09-19 21:00:00,11958.0 -2011-09-19 22:00:00,11751.0 -2011-09-19 23:00:00,11164.0 -2011-09-20 00:00:00,10270.0 -2011-09-18 01:00:00,8712.0 -2011-09-18 02:00:00,8263.0 -2011-09-18 03:00:00,7921.0 -2011-09-18 04:00:00,7718.0 -2011-09-18 05:00:00,7582.0 -2011-09-18 06:00:00,7523.0 -2011-09-18 07:00:00,7651.0 -2011-09-18 08:00:00,7834.0 -2011-09-18 09:00:00,7977.0 -2011-09-18 10:00:00,8416.0 -2011-09-18 11:00:00,8769.0 -2011-09-18 12:00:00,9063.0 -2011-09-18 13:00:00,9258.0 -2011-09-18 14:00:00,9355.0 -2011-09-18 15:00:00,9391.0 -2011-09-18 16:00:00,9440.0 -2011-09-18 17:00:00,9490.0 -2011-09-18 18:00:00,9573.0 -2011-09-18 19:00:00,9730.0 -2011-09-18 20:00:00,10022.0 -2011-09-18 21:00:00,10448.0 -2011-09-18 22:00:00,10322.0 -2011-09-18 23:00:00,9963.0 -2011-09-19 00:00:00,9428.0 -2011-09-17 01:00:00,9223.0 -2011-09-17 02:00:00,8622.0 -2011-09-17 03:00:00,8320.0 -2011-09-17 04:00:00,8061.0 -2011-09-17 05:00:00,7976.0 -2011-09-17 06:00:00,7987.0 -2011-09-17 07:00:00,8213.0 -2011-09-17 08:00:00,8537.0 -2011-09-17 09:00:00,8765.0 -2011-09-17 10:00:00,9280.0 -2011-09-17 11:00:00,9620.0 -2011-09-17 12:00:00,9878.0 -2011-09-17 13:00:00,9941.0 -2011-09-17 14:00:00,9863.0 -2011-09-17 15:00:00,9815.0 -2011-09-17 16:00:00,9727.0 -2011-09-17 17:00:00,9738.0 -2011-09-17 18:00:00,9647.0 -2011-09-17 19:00:00,9552.0 -2011-09-17 20:00:00,9580.0 -2011-09-17 21:00:00,10152.0 -2011-09-17 22:00:00,10151.0 -2011-09-17 23:00:00,9817.0 -2011-09-18 00:00:00,9289.0 -2011-09-16 01:00:00,9233.0 -2011-09-16 02:00:00,8760.0 -2011-09-16 03:00:00,8437.0 -2011-09-16 04:00:00,8224.0 -2011-09-16 05:00:00,8139.0 -2011-09-16 06:00:00,8338.0 -2011-09-16 07:00:00,8957.0 -2011-09-16 08:00:00,10023.0 -2011-09-16 09:00:00,10667.0 -2011-09-16 10:00:00,11021.0 -2011-09-16 11:00:00,11241.0 -2011-09-16 12:00:00,11359.0 -2011-09-16 13:00:00,11417.0 -2011-09-16 14:00:00,11359.0 -2011-09-16 15:00:00,11311.0 -2011-09-16 16:00:00,11208.0 -2011-09-16 17:00:00,11058.0 -2011-09-16 18:00:00,10941.0 -2011-09-16 19:00:00,10804.0 -2011-09-16 20:00:00,10828.0 -2011-09-16 21:00:00,11216.0 -2011-09-16 22:00:00,11031.0 -2011-09-16 23:00:00,10650.0 -2011-09-17 00:00:00,9926.0 -2011-09-15 01:00:00,9257.0 -2011-09-15 02:00:00,8704.0 -2011-09-15 03:00:00,8377.0 -2011-09-15 04:00:00,8172.0 -2011-09-15 05:00:00,8116.0 -2011-09-15 06:00:00,8286.0 -2011-09-15 07:00:00,8914.0 -2011-09-15 08:00:00,9965.0 -2011-09-15 09:00:00,10548.0 -2011-09-15 10:00:00,10925.0 -2011-09-15 11:00:00,11084.0 -2011-09-15 12:00:00,11255.0 -2011-09-15 13:00:00,11304.0 -2011-09-15 14:00:00,11321.0 -2011-09-15 15:00:00,11384.0 -2011-09-15 16:00:00,11299.0 -2011-09-15 17:00:00,11191.0 -2011-09-15 18:00:00,11062.0 -2011-09-15 19:00:00,10864.0 -2011-09-15 20:00:00,10816.0 -2011-09-15 21:00:00,11388.0 -2011-09-15 22:00:00,11300.0 -2011-09-15 23:00:00,10864.0 -2011-09-16 00:00:00,9957.0 -2011-09-14 01:00:00,10012.0 -2011-09-14 02:00:00,9363.0 -2011-09-14 03:00:00,8959.0 -2011-09-14 04:00:00,8707.0 -2011-09-14 05:00:00,8590.0 -2011-09-14 06:00:00,8720.0 -2011-09-14 07:00:00,9334.0 -2011-09-14 08:00:00,10379.0 -2011-09-14 09:00:00,11023.0 -2011-09-14 10:00:00,11538.0 -2011-09-14 11:00:00,11731.0 -2011-09-14 12:00:00,11851.0 -2011-09-14 13:00:00,11887.0 -2011-09-14 14:00:00,11802.0 -2011-09-14 15:00:00,11781.0 -2011-09-14 16:00:00,11615.0 -2011-09-14 17:00:00,11396.0 -2011-09-14 18:00:00,11259.0 -2011-09-14 19:00:00,11060.0 -2011-09-14 20:00:00,10877.0 -2011-09-14 21:00:00,11395.0 -2011-09-14 22:00:00,11393.0 -2011-09-14 23:00:00,10876.0 -2011-09-15 00:00:00,10042.0 -2011-09-13 01:00:00,11723.0 -2011-09-13 02:00:00,10848.0 -2011-09-13 03:00:00,10265.0 -2011-09-13 04:00:00,9872.0 -2011-09-13 05:00:00,9667.0 -2011-09-13 06:00:00,9755.0 -2011-09-13 07:00:00,10402.0 -2011-09-13 08:00:00,11474.0 -2011-09-13 09:00:00,12120.0 -2011-09-13 10:00:00,12754.0 -2011-09-13 11:00:00,13169.0 -2011-09-13 12:00:00,13574.0 -2011-09-13 13:00:00,13757.0 -2011-09-13 14:00:00,13872.0 -2011-09-13 15:00:00,14058.0 -2011-09-13 16:00:00,14053.0 -2011-09-13 17:00:00,13987.0 -2011-09-13 18:00:00,13738.0 -2011-09-13 19:00:00,13266.0 -2011-09-13 20:00:00,12715.0 -2011-09-13 21:00:00,12909.0 -2011-09-13 22:00:00,12706.0 -2011-09-13 23:00:00,11989.0 -2011-09-14 00:00:00,10956.0 -2011-09-12 01:00:00,10448.0 -2011-09-12 02:00:00,9781.0 -2011-09-12 03:00:00,9372.0 -2011-09-12 04:00:00,9057.0 -2011-09-12 05:00:00,8948.0 -2011-09-12 06:00:00,9140.0 -2011-09-12 07:00:00,9910.0 -2011-09-12 08:00:00,11025.0 -2011-09-12 09:00:00,11901.0 -2011-09-12 10:00:00,12695.0 -2011-09-12 11:00:00,13490.0 -2011-09-12 12:00:00,14160.0 -2011-09-12 13:00:00,14782.0 -2011-09-12 14:00:00,15382.0 -2011-09-12 15:00:00,15931.0 -2011-09-12 16:00:00,16158.0 -2011-09-12 17:00:00,16325.0 -2011-09-12 18:00:00,16278.0 -2011-09-12 19:00:00,15891.0 -2011-09-12 20:00:00,15171.0 -2011-09-12 21:00:00,15253.0 -2011-09-12 22:00:00,15049.0 -2011-09-12 23:00:00,14173.0 -2011-09-13 00:00:00,12935.0 -2011-09-11 01:00:00,9860.0 -2011-09-11 02:00:00,9274.0 -2011-09-11 03:00:00,8798.0 -2011-09-11 04:00:00,8523.0 -2011-09-11 05:00:00,8308.0 -2011-09-11 06:00:00,8245.0 -2011-09-11 07:00:00,8232.0 -2011-09-11 08:00:00,8261.0 -2011-09-11 09:00:00,8429.0 -2011-09-11 10:00:00,9095.0 -2011-09-11 11:00:00,9756.0 -2011-09-11 12:00:00,10379.0 -2011-09-11 13:00:00,10933.0 -2011-09-11 14:00:00,11462.0 -2011-09-11 15:00:00,11794.0 -2011-09-11 16:00:00,12128.0 -2011-09-11 17:00:00,12333.0 -2011-09-11 18:00:00,12538.0 -2011-09-11 19:00:00,12554.0 -2011-09-11 20:00:00,12381.0 -2011-09-11 21:00:00,12644.0 -2011-09-11 22:00:00,12666.0 -2011-09-11 23:00:00,12146.0 -2011-09-12 00:00:00,11317.0 -2011-09-10 01:00:00,10144.0 -2011-09-10 02:00:00,9458.0 -2011-09-10 03:00:00,9055.0 -2011-09-10 04:00:00,8729.0 -2011-09-10 05:00:00,8587.0 -2011-09-10 06:00:00,8525.0 -2011-09-10 07:00:00,8687.0 -2011-09-10 08:00:00,8963.0 -2011-09-10 09:00:00,9314.0 -2011-09-10 10:00:00,9958.0 -2011-09-10 11:00:00,10509.0 -2011-09-10 12:00:00,10965.0 -2011-09-10 13:00:00,11270.0 -2011-09-10 14:00:00,11487.0 -2011-09-10 15:00:00,11508.0 -2011-09-10 16:00:00,11464.0 -2011-09-10 17:00:00,11448.0 -2011-09-10 18:00:00,11458.0 -2011-09-10 19:00:00,11416.0 -2011-09-10 20:00:00,11233.0 -2011-09-10 21:00:00,11569.0 -2011-09-10 22:00:00,11569.0 -2011-09-10 23:00:00,11169.0 -2011-09-11 00:00:00,10561.0 -2011-09-09 01:00:00,9973.0 -2011-09-09 02:00:00,9393.0 -2011-09-09 03:00:00,9089.0 -2011-09-09 04:00:00,8792.0 -2011-09-09 05:00:00,8713.0 -2011-09-09 06:00:00,8902.0 -2011-09-09 07:00:00,9558.0 -2011-09-09 08:00:00,10690.0 -2011-09-09 09:00:00,11363.0 -2011-09-09 10:00:00,11835.0 -2011-09-09 11:00:00,12199.0 -2011-09-09 12:00:00,12426.0 -2011-09-09 13:00:00,12487.0 -2011-09-09 14:00:00,12484.0 -2011-09-09 15:00:00,12538.0 -2011-09-09 16:00:00,12486.0 -2011-09-09 17:00:00,12453.0 -2011-09-09 18:00:00,12272.0 -2011-09-09 19:00:00,12001.0 -2011-09-09 20:00:00,11856.0 -2011-09-09 21:00:00,12193.0 -2011-09-09 22:00:00,12141.0 -2011-09-09 23:00:00,11680.0 -2011-09-10 00:00:00,10882.0 -2011-09-08 01:00:00,9527.0 -2011-09-08 02:00:00,8986.0 -2011-09-08 03:00:00,8641.0 -2011-09-08 04:00:00,8432.0 -2011-09-08 05:00:00,8361.0 -2011-09-08 06:00:00,8551.0 -2011-09-08 07:00:00,9189.0 -2011-09-08 08:00:00,10245.0 -2011-09-08 09:00:00,10942.0 -2011-09-08 10:00:00,11493.0 -2011-09-08 11:00:00,11895.0 -2011-09-08 12:00:00,12282.0 -2011-09-08 13:00:00,12461.0 -2011-09-08 14:00:00,12523.0 -2011-09-08 15:00:00,12625.0 -2011-09-08 16:00:00,12460.0 -2011-09-08 17:00:00,12304.0 -2011-09-08 18:00:00,12131.0 -2011-09-08 19:00:00,11971.0 -2011-09-08 20:00:00,11896.0 -2011-09-08 21:00:00,12279.0 -2011-09-08 22:00:00,12173.0 -2011-09-08 23:00:00,11659.0 -2011-09-09 00:00:00,10782.0 -2011-09-07 01:00:00,9429.0 -2011-09-07 02:00:00,8880.0 -2011-09-07 03:00:00,8545.0 -2011-09-07 04:00:00,8317.0 -2011-09-07 05:00:00,8244.0 -2011-09-07 06:00:00,8415.0 -2011-09-07 07:00:00,9032.0 -2011-09-07 08:00:00,9971.0 -2011-09-07 09:00:00,10667.0 -2011-09-07 10:00:00,11219.0 -2011-09-07 11:00:00,11604.0 -2011-09-07 12:00:00,11909.0 -2011-09-07 13:00:00,11999.0 -2011-09-07 14:00:00,12045.0 -2011-09-07 15:00:00,12185.0 -2011-09-07 16:00:00,12129.0 -2011-09-07 17:00:00,12085.0 -2011-09-07 18:00:00,11980.0 -2011-09-07 19:00:00,11756.0 -2011-09-07 20:00:00,11385.0 -2011-09-07 21:00:00,11681.0 -2011-09-07 22:00:00,11780.0 -2011-09-07 23:00:00,11225.0 -2011-09-08 00:00:00,10351.0 -2011-09-06 01:00:00,8437.0 -2011-09-06 02:00:00,8015.0 -2011-09-06 03:00:00,7803.0 -2011-09-06 04:00:00,7675.0 -2011-09-06 05:00:00,7686.0 -2011-09-06 06:00:00,7930.0 -2011-09-06 07:00:00,8655.0 -2011-09-06 08:00:00,9669.0 -2011-09-06 09:00:00,10399.0 -2011-09-06 10:00:00,10934.0 -2011-09-06 11:00:00,11272.0 -2011-09-06 12:00:00,11396.0 -2011-09-06 13:00:00,11754.0 -2011-09-06 14:00:00,11781.0 -2011-09-06 15:00:00,11940.0 -2011-09-06 16:00:00,11918.0 -2011-09-06 17:00:00,11838.0 -2011-09-06 18:00:00,11757.0 -2011-09-06 19:00:00,11522.0 -2011-09-06 20:00:00,11217.0 -2011-09-06 21:00:00,11533.0 -2011-09-06 22:00:00,11645.0 -2011-09-06 23:00:00,11078.0 -2011-09-07 00:00:00,10248.0 -2011-09-05 01:00:00,9270.0 -2011-09-05 02:00:00,8683.0 -2011-09-05 03:00:00,8324.0 -2011-09-05 04:00:00,8013.0 -2011-09-05 05:00:00,7891.0 -2011-09-05 06:00:00,7815.0 -2011-09-05 07:00:00,7948.0 -2011-09-05 08:00:00,7896.0 -2011-09-05 09:00:00,8012.0 -2011-09-05 10:00:00,8375.0 -2011-09-05 11:00:00,8834.0 -2011-09-05 12:00:00,9184.0 -2011-09-05 13:00:00,9350.0 -2011-09-05 14:00:00,9378.0 -2011-09-05 15:00:00,9345.0 -2011-09-05 16:00:00,9274.0 -2011-09-05 17:00:00,9255.0 -2011-09-05 18:00:00,9267.0 -2011-09-05 19:00:00,9311.0 -2011-09-05 20:00:00,9304.0 -2011-09-05 21:00:00,9718.0 -2011-09-05 22:00:00,9983.0 -2011-09-05 23:00:00,9650.0 -2011-09-06 00:00:00,9032.0 -2011-09-04 01:00:00,11655.0 -2011-09-04 02:00:00,10938.0 -2011-09-04 03:00:00,10275.0 -2011-09-04 04:00:00,9872.0 -2011-09-04 05:00:00,9593.0 -2011-09-04 06:00:00,9440.0 -2011-09-04 07:00:00,9429.0 -2011-09-04 08:00:00,9305.0 -2011-09-04 09:00:00,9317.0 -2011-09-04 10:00:00,9662.0 -2011-09-04 11:00:00,10095.0 -2011-09-04 12:00:00,10473.0 -2011-09-04 13:00:00,10883.0 -2011-09-04 14:00:00,11000.0 -2011-09-04 15:00:00,11139.0 -2011-09-04 16:00:00,11131.0 -2011-09-04 17:00:00,11073.0 -2011-09-04 18:00:00,10922.0 -2011-09-04 19:00:00,10697.0 -2011-09-04 20:00:00,10369.0 -2011-09-04 21:00:00,10486.0 -2011-09-04 22:00:00,10617.0 -2011-09-04 23:00:00,10363.0 -2011-09-05 00:00:00,9804.0 -2011-09-03 01:00:00,15121.0 -2011-09-03 02:00:00,13948.0 -2011-09-03 03:00:00,13139.0 -2011-09-03 04:00:00,12445.0 -2011-09-03 05:00:00,12002.0 -2011-09-03 06:00:00,11710.0 -2011-09-03 07:00:00,11721.0 -2011-09-03 08:00:00,11782.0 -2011-09-03 09:00:00,12271.0 -2011-09-03 10:00:00,13242.0 -2011-09-03 11:00:00,14440.0 -2011-09-03 12:00:00,15616.0 -2011-09-03 13:00:00,16476.0 -2011-09-03 14:00:00,17088.0 -2011-09-03 15:00:00,17126.0 -2011-09-03 16:00:00,16496.0 -2011-09-03 17:00:00,15421.0 -2011-09-03 18:00:00,14895.0 -2011-09-03 19:00:00,14456.0 -2011-09-03 20:00:00,14137.0 -2011-09-03 21:00:00,14017.0 -2011-09-03 22:00:00,13823.0 -2011-09-03 23:00:00,13233.0 -2011-09-04 00:00:00,12485.0 -2011-09-02 01:00:00,16524.0 -2011-09-02 02:00:00,15381.0 -2011-09-02 03:00:00,14539.0 -2011-09-02 04:00:00,13879.0 -2011-09-02 05:00:00,13445.0 -2011-09-02 06:00:00,13312.0 -2011-09-02 07:00:00,13841.0 -2011-09-02 08:00:00,14830.0 -2011-09-02 09:00:00,15591.0 -2011-09-02 10:00:00,16350.0 -2011-09-02 11:00:00,17170.0 -2011-09-02 12:00:00,18099.0 -2011-09-02 13:00:00,19109.0 -2011-09-02 14:00:00,20033.0 -2011-09-02 15:00:00,20745.0 -2011-09-02 16:00:00,21055.0 -2011-09-02 17:00:00,21169.0 -2011-09-02 18:00:00,21028.0 -2011-09-02 19:00:00,20567.0 -2011-09-02 20:00:00,19704.0 -2011-09-02 21:00:00,19148.0 -2011-09-02 22:00:00,18731.0 -2011-09-02 23:00:00,17808.0 -2011-09-03 00:00:00,16435.0 -2011-09-01 01:00:00,14098.0 -2011-09-01 02:00:00,13024.0 -2011-09-01 03:00:00,12219.0 -2011-09-01 04:00:00,11616.0 -2011-09-01 05:00:00,11343.0 -2011-09-01 06:00:00,11394.0 -2011-09-01 07:00:00,12029.0 -2011-09-01 08:00:00,13119.0 -2011-09-01 09:00:00,14253.0 -2011-09-01 10:00:00,15354.0 -2011-09-01 11:00:00,16552.0 -2011-09-01 12:00:00,17751.0 -2011-09-01 13:00:00,18772.0 -2011-09-01 14:00:00,19786.0 -2011-09-01 15:00:00,20587.0 -2011-09-01 16:00:00,20937.0 -2011-09-01 17:00:00,21525.0 -2011-09-01 18:00:00,21900.0 -2011-09-01 19:00:00,21635.0 -2011-09-01 20:00:00,21014.0 -2011-09-01 21:00:00,20666.0 -2011-09-01 22:00:00,20436.0 -2011-09-01 23:00:00,19448.0 -2011-09-02 00:00:00,17900.0 -2011-08-31 01:00:00,10992.0 -2011-08-31 02:00:00,10284.0 -2011-08-31 03:00:00,9811.0 -2011-08-31 04:00:00,9461.0 -2011-08-31 05:00:00,9322.0 -2011-08-31 06:00:00,9482.0 -2011-08-31 07:00:00,10201.0 -2011-08-31 08:00:00,11255.0 -2011-08-31 09:00:00,12091.0 -2011-08-31 10:00:00,12800.0 -2011-08-31 11:00:00,13490.0 -2011-08-31 12:00:00,14277.0 -2011-08-31 13:00:00,14937.0 -2011-08-31 14:00:00,15593.0 -2011-08-31 15:00:00,16422.0 -2011-08-31 16:00:00,17129.0 -2011-08-31 17:00:00,17823.0 -2011-08-31 18:00:00,18299.0 -2011-08-31 19:00:00,18358.0 -2011-08-31 20:00:00,17944.0 -2011-08-31 21:00:00,17767.0 -2011-08-31 22:00:00,17747.0 -2011-08-31 23:00:00,16902.0 -2011-09-01 00:00:00,15468.0 -2011-08-30 01:00:00,10971.0 -2011-08-30 02:00:00,10228.0 -2011-08-30 03:00:00,9712.0 -2011-08-30 04:00:00,9352.0 -2011-08-30 05:00:00,9181.0 -2011-08-30 06:00:00,9331.0 -2011-08-30 07:00:00,9966.0 -2011-08-30 08:00:00,10917.0 -2011-08-30 09:00:00,11685.0 -2011-08-30 10:00:00,12244.0 -2011-08-30 11:00:00,12775.0 -2011-08-30 12:00:00,13254.0 -2011-08-30 13:00:00,13497.0 -2011-08-30 14:00:00,13772.0 -2011-08-30 15:00:00,13912.0 -2011-08-30 16:00:00,13953.0 -2011-08-30 17:00:00,14001.0 -2011-08-30 18:00:00,13875.0 -2011-08-30 19:00:00,13571.0 -2011-08-30 20:00:00,13320.0 -2011-08-30 21:00:00,13549.0 -2011-08-30 22:00:00,13615.0 -2011-08-30 23:00:00,12992.0 -2011-08-31 00:00:00,11980.0 -2011-08-29 01:00:00,10043.0 -2011-08-29 02:00:00,9501.0 -2011-08-29 03:00:00,9115.0 -2011-08-29 04:00:00,8875.0 -2011-08-29 05:00:00,8799.0 -2011-08-29 06:00:00,8994.0 -2011-08-29 07:00:00,9668.0 -2011-08-29 08:00:00,10588.0 -2011-08-29 09:00:00,11579.0 -2011-08-29 10:00:00,12433.0 -2011-08-29 11:00:00,13052.0 -2011-08-29 12:00:00,13628.0 -2011-08-29 13:00:00,13952.0 -2011-08-29 14:00:00,14223.0 -2011-08-29 15:00:00,14558.0 -2011-08-29 16:00:00,14818.0 -2011-08-29 17:00:00,15033.0 -2011-08-29 18:00:00,15071.0 -2011-08-29 19:00:00,14859.0 -2011-08-29 20:00:00,14306.0 -2011-08-29 21:00:00,14047.0 -2011-08-29 22:00:00,14002.0 -2011-08-29 23:00:00,13222.0 -2011-08-30 00:00:00,12066.0 -2011-08-28 01:00:00,10578.0 -2011-08-28 02:00:00,9878.0 -2011-08-28 03:00:00,9381.0 -2011-08-28 04:00:00,8920.0 -2011-08-28 05:00:00,8770.0 -2011-08-28 06:00:00,8641.0 -2011-08-28 07:00:00,8682.0 -2011-08-28 08:00:00,8539.0 -2011-08-28 09:00:00,8872.0 -2011-08-28 10:00:00,9575.0 -2011-08-28 11:00:00,10260.0 -2011-08-28 12:00:00,10854.0 -2011-08-28 13:00:00,11223.0 -2011-08-28 14:00:00,11558.0 -2011-08-28 15:00:00,11792.0 -2011-08-28 16:00:00,12032.0 -2011-08-28 17:00:00,12129.0 -2011-08-28 18:00:00,12139.0 -2011-08-28 19:00:00,11924.0 -2011-08-28 20:00:00,11679.0 -2011-08-28 21:00:00,11726.0 -2011-08-28 22:00:00,11991.0 -2011-08-28 23:00:00,11585.0 -2011-08-29 00:00:00,10819.0 -2011-08-27 01:00:00,11769.0 -2011-08-27 02:00:00,10975.0 -2011-08-27 03:00:00,10383.0 -2011-08-27 04:00:00,9915.0 -2011-08-27 05:00:00,9733.0 -2011-08-27 06:00:00,9638.0 -2011-08-27 07:00:00,9810.0 -2011-08-27 08:00:00,9955.0 -2011-08-27 09:00:00,10663.0 -2011-08-27 10:00:00,11749.0 -2011-08-27 11:00:00,12858.0 -2011-08-27 12:00:00,13801.0 -2011-08-27 13:00:00,14423.0 -2011-08-27 14:00:00,14898.0 -2011-08-27 15:00:00,15026.0 -2011-08-27 16:00:00,15119.0 -2011-08-27 17:00:00,15062.0 -2011-08-27 18:00:00,14896.0 -2011-08-27 19:00:00,14442.0 -2011-08-27 20:00:00,13608.0 -2011-08-27 21:00:00,12989.0 -2011-08-27 22:00:00,12848.0 -2011-08-27 23:00:00,12273.0 -2011-08-28 00:00:00,11421.0 -2011-08-26 01:00:00,11554.0 -2011-08-26 02:00:00,10694.0 -2011-08-26 03:00:00,10059.0 -2011-08-26 04:00:00,9680.0 -2011-08-26 05:00:00,9460.0 -2011-08-26 06:00:00,9585.0 -2011-08-26 07:00:00,10102.0 -2011-08-26 08:00:00,10914.0 -2011-08-26 09:00:00,11832.0 -2011-08-26 10:00:00,12703.0 -2011-08-26 11:00:00,13393.0 -2011-08-26 12:00:00,14027.0 -2011-08-26 13:00:00,14490.0 -2011-08-26 14:00:00,14949.0 -2011-08-26 15:00:00,15461.0 -2011-08-26 16:00:00,15815.0 -2011-08-26 17:00:00,15863.0 -2011-08-26 18:00:00,15634.0 -2011-08-26 19:00:00,15223.0 -2011-08-26 20:00:00,14569.0 -2011-08-26 21:00:00,14264.0 -2011-08-26 22:00:00,14318.0 -2011-08-26 23:00:00,13745.0 -2011-08-27 00:00:00,12799.0 -2011-08-25 01:00:00,12677.0 -2011-08-25 02:00:00,11541.0 -2011-08-25 03:00:00,10758.0 -2011-08-25 04:00:00,10231.0 -2011-08-25 05:00:00,9968.0 -2011-08-25 06:00:00,10018.0 -2011-08-25 07:00:00,10568.0 -2011-08-25 08:00:00,11412.0 -2011-08-25 09:00:00,12361.0 -2011-08-25 10:00:00,13141.0 -2011-08-25 11:00:00,13843.0 -2011-08-25 12:00:00,14538.0 -2011-08-25 13:00:00,14984.0 -2011-08-25 14:00:00,15411.0 -2011-08-25 15:00:00,15882.0 -2011-08-25 16:00:00,16235.0 -2011-08-25 17:00:00,16534.0 -2011-08-25 18:00:00,16676.0 -2011-08-25 19:00:00,16392.0 -2011-08-25 20:00:00,15578.0 -2011-08-25 21:00:00,14944.0 -2011-08-25 22:00:00,14839.0 -2011-08-25 23:00:00,13987.0 -2011-08-26 00:00:00,12745.0 -2011-08-24 01:00:00,11252.0 -2011-08-24 02:00:00,10598.0 -2011-08-24 03:00:00,10154.0 -2011-08-24 04:00:00,9880.0 -2011-08-24 05:00:00,9774.0 -2011-08-24 06:00:00,9988.0 -2011-08-24 07:00:00,10719.0 -2011-08-24 08:00:00,11838.0 -2011-08-24 09:00:00,13031.0 -2011-08-24 10:00:00,14017.0 -2011-08-24 11:00:00,15213.0 -2011-08-24 12:00:00,16452.0 -2011-08-24 13:00:00,17490.0 -2011-08-24 14:00:00,18425.0 -2011-08-24 15:00:00,19367.0 -2011-08-24 16:00:00,20080.0 -2011-08-24 17:00:00,20495.0 -2011-08-24 18:00:00,20515.0 -2011-08-24 19:00:00,19966.0 -2011-08-24 20:00:00,18971.0 -2011-08-24 21:00:00,18029.0 -2011-08-24 22:00:00,17394.0 -2011-08-24 23:00:00,16033.0 -2011-08-25 00:00:00,14285.0 -2011-08-23 01:00:00,11787.0 -2011-08-23 02:00:00,10838.0 -2011-08-23 03:00:00,10205.0 -2011-08-23 04:00:00,9791.0 -2011-08-23 05:00:00,9568.0 -2011-08-23 06:00:00,9703.0 -2011-08-23 07:00:00,10304.0 -2011-08-23 08:00:00,11185.0 -2011-08-23 09:00:00,12069.0 -2011-08-23 10:00:00,12737.0 -2011-08-23 11:00:00,13219.0 -2011-08-23 12:00:00,13586.0 -2011-08-23 13:00:00,13637.0 -2011-08-23 14:00:00,13494.0 -2011-08-23 15:00:00,13480.0 -2011-08-23 16:00:00,13513.0 -2011-08-23 17:00:00,13500.0 -2011-08-23 18:00:00,13660.0 -2011-08-23 19:00:00,13788.0 -2011-08-23 20:00:00,13463.0 -2011-08-23 21:00:00,13456.0 -2011-08-23 22:00:00,13678.0 -2011-08-23 23:00:00,13191.0 -2011-08-24 00:00:00,12237.0 -2011-08-22 01:00:00,10750.0 -2011-08-22 02:00:00,10022.0 -2011-08-22 03:00:00,9529.0 -2011-08-22 04:00:00,9221.0 -2011-08-22 05:00:00,9128.0 -2011-08-22 06:00:00,9306.0 -2011-08-22 07:00:00,9934.0 -2011-08-22 08:00:00,10913.0 -2011-08-22 09:00:00,11862.0 -2011-08-22 10:00:00,12797.0 -2011-08-22 11:00:00,13518.0 -2011-08-22 12:00:00,14138.0 -2011-08-22 13:00:00,14604.0 -2011-08-22 14:00:00,15122.0 -2011-08-22 15:00:00,15565.0 -2011-08-22 16:00:00,15860.0 -2011-08-22 17:00:00,15991.0 -2011-08-22 18:00:00,16129.0 -2011-08-22 19:00:00,15977.0 -2011-08-22 20:00:00,15422.0 -2011-08-22 21:00:00,14964.0 -2011-08-22 22:00:00,14931.0 -2011-08-22 23:00:00,14136.0 -2011-08-23 00:00:00,12970.0 -2011-08-21 01:00:00,11719.0 -2011-08-21 02:00:00,10975.0 -2011-08-21 03:00:00,10319.0 -2011-08-21 04:00:00,9910.0 -2011-08-21 05:00:00,9650.0 -2011-08-21 06:00:00,9416.0 -2011-08-21 07:00:00,9367.0 -2011-08-21 08:00:00,9214.0 -2011-08-21 09:00:00,9559.0 -2011-08-21 10:00:00,10240.0 -2011-08-21 11:00:00,11087.0 -2011-08-21 12:00:00,11862.0 -2011-08-21 13:00:00,12485.0 -2011-08-21 14:00:00,12916.0 -2011-08-21 15:00:00,13219.0 -2011-08-21 16:00:00,13514.0 -2011-08-21 17:00:00,13745.0 -2011-08-21 18:00:00,13891.0 -2011-08-21 19:00:00,13692.0 -2011-08-21 20:00:00,13302.0 -2011-08-21 21:00:00,12903.0 -2011-08-21 22:00:00,13035.0 -2011-08-21 23:00:00,12573.0 -2011-08-22 00:00:00,11707.0 -2011-08-20 01:00:00,13582.0 -2011-08-20 02:00:00,12579.0 -2011-08-20 03:00:00,11838.0 -2011-08-20 04:00:00,11269.0 -2011-08-20 05:00:00,10911.0 -2011-08-20 06:00:00,10786.0 -2011-08-20 07:00:00,10931.0 -2011-08-20 08:00:00,11088.0 -2011-08-20 09:00:00,11564.0 -2011-08-20 10:00:00,12343.0 -2011-08-20 11:00:00,12902.0 -2011-08-20 12:00:00,13354.0 -2011-08-20 13:00:00,13175.0 -2011-08-20 14:00:00,13021.0 -2011-08-20 15:00:00,12963.0 -2011-08-20 16:00:00,13379.0 -2011-08-20 17:00:00,13832.0 -2011-08-20 18:00:00,14164.0 -2011-08-20 19:00:00,14251.0 -2011-08-20 20:00:00,13906.0 -2011-08-20 21:00:00,13569.0 -2011-08-20 22:00:00,13711.0 -2011-08-20 23:00:00,13251.0 -2011-08-21 00:00:00,12559.0 -2011-08-19 01:00:00,12340.0 -2011-08-19 02:00:00,11442.0 -2011-08-19 03:00:00,10762.0 -2011-08-19 04:00:00,10350.0 -2011-08-19 05:00:00,10130.0 -2011-08-19 06:00:00,10194.0 -2011-08-19 07:00:00,10697.0 -2011-08-19 08:00:00,11498.0 -2011-08-19 09:00:00,12416.0 -2011-08-19 10:00:00,13339.0 -2011-08-19 11:00:00,14287.0 -2011-08-19 12:00:00,15312.0 -2011-08-19 13:00:00,16205.0 -2011-08-19 14:00:00,16963.0 -2011-08-19 15:00:00,17590.0 -2011-08-19 16:00:00,18023.0 -2011-08-19 17:00:00,18344.0 -2011-08-19 18:00:00,18391.0 -2011-08-19 19:00:00,18122.0 -2011-08-19 20:00:00,17444.0 -2011-08-19 21:00:00,16840.0 -2011-08-19 22:00:00,16684.0 -2011-08-19 23:00:00,15947.0 -2011-08-20 00:00:00,14831.0 -2011-08-18 01:00:00,13137.0 -2011-08-18 02:00:00,12169.0 -2011-08-18 03:00:00,11543.0 -2011-08-18 04:00:00,11003.0 -2011-08-18 05:00:00,10733.0 -2011-08-18 06:00:00,10772.0 -2011-08-18 07:00:00,11289.0 -2011-08-18 08:00:00,12002.0 -2011-08-18 09:00:00,13061.0 -2011-08-18 10:00:00,14060.0 -2011-08-18 11:00:00,14924.0 -2011-08-18 12:00:00,15843.0 -2011-08-18 13:00:00,16430.0 -2011-08-18 14:00:00,16961.0 -2011-08-18 15:00:00,17513.0 -2011-08-18 16:00:00,17615.0 -2011-08-18 17:00:00,17609.0 -2011-08-18 18:00:00,17455.0 -2011-08-18 19:00:00,17221.0 -2011-08-18 20:00:00,16544.0 -2011-08-18 21:00:00,15848.0 -2011-08-18 22:00:00,15716.0 -2011-08-18 23:00:00,14857.0 -2011-08-19 00:00:00,13561.0 -2011-08-17 01:00:00,11885.0 -2011-08-17 02:00:00,10983.0 -2011-08-17 03:00:00,10345.0 -2011-08-17 04:00:00,9952.0 -2011-08-17 05:00:00,9734.0 -2011-08-17 06:00:00,9850.0 -2011-08-17 07:00:00,10459.0 -2011-08-17 08:00:00,11233.0 -2011-08-17 09:00:00,12099.0 -2011-08-17 10:00:00,13037.0 -2011-08-17 11:00:00,13936.0 -2011-08-17 12:00:00,14773.0 -2011-08-17 13:00:00,15377.0 -2011-08-17 14:00:00,15696.0 -2011-08-17 15:00:00,16097.0 -2011-08-17 16:00:00,16592.0 -2011-08-17 17:00:00,17113.0 -2011-08-17 18:00:00,17117.0 -2011-08-17 19:00:00,16834.0 -2011-08-17 20:00:00,16373.0 -2011-08-17 21:00:00,16066.0 -2011-08-17 22:00:00,16208.0 -2011-08-17 23:00:00,15609.0 -2011-08-18 00:00:00,14397.0 -2011-08-16 01:00:00,11249.0 -2011-08-16 02:00:00,10451.0 -2011-08-16 03:00:00,9933.0 -2011-08-16 04:00:00,9541.0 -2011-08-16 05:00:00,9388.0 -2011-08-16 06:00:00,9522.0 -2011-08-16 07:00:00,10082.0 -2011-08-16 08:00:00,10834.0 -2011-08-16 09:00:00,11830.0 -2011-08-16 10:00:00,12837.0 -2011-08-16 11:00:00,13659.0 -2011-08-16 12:00:00,14405.0 -2011-08-16 13:00:00,14931.0 -2011-08-16 14:00:00,15439.0 -2011-08-16 15:00:00,15894.0 -2011-08-16 16:00:00,16170.0 -2011-08-16 17:00:00,16376.0 -2011-08-16 18:00:00,16451.0 -2011-08-16 19:00:00,16230.0 -2011-08-16 20:00:00,15631.0 -2011-08-16 21:00:00,15037.0 -2011-08-16 22:00:00,14972.0 -2011-08-16 23:00:00,14315.0 -2011-08-17 00:00:00,13102.0 -2011-08-15 01:00:00,10033.0 -2011-08-15 02:00:00,9460.0 -2011-08-15 03:00:00,9032.0 -2011-08-15 04:00:00,8832.0 -2011-08-15 05:00:00,8768.0 -2011-08-15 06:00:00,8968.0 -2011-08-15 07:00:00,9542.0 -2011-08-15 08:00:00,10352.0 -2011-08-15 09:00:00,11362.0 -2011-08-15 10:00:00,12210.0 -2011-08-15 11:00:00,12969.0 -2011-08-15 12:00:00,13568.0 -2011-08-15 13:00:00,13969.0 -2011-08-15 14:00:00,14322.0 -2011-08-15 15:00:00,14696.0 -2011-08-15 16:00:00,14954.0 -2011-08-15 17:00:00,15134.0 -2011-08-15 18:00:00,15199.0 -2011-08-15 19:00:00,15057.0 -2011-08-15 20:00:00,14560.0 -2011-08-15 21:00:00,14065.0 -2011-08-15 22:00:00,14032.0 -2011-08-15 23:00:00,13380.0 -2011-08-16 00:00:00,12330.0 -2011-08-14 01:00:00,10680.0 -2011-08-14 02:00:00,9976.0 -2011-08-14 03:00:00,9531.0 -2011-08-14 04:00:00,9130.0 -2011-08-14 05:00:00,8955.0 -2011-08-14 06:00:00,8806.0 -2011-08-14 07:00:00,8865.0 -2011-08-14 08:00:00,8735.0 -2011-08-14 09:00:00,9019.0 -2011-08-14 10:00:00,9569.0 -2011-08-14 11:00:00,10123.0 -2011-08-14 12:00:00,10499.0 -2011-08-14 13:00:00,10758.0 -2011-08-14 14:00:00,10860.0 -2011-08-14 15:00:00,11029.0 -2011-08-14 16:00:00,11205.0 -2011-08-14 17:00:00,11447.0 -2011-08-14 18:00:00,11631.0 -2011-08-14 19:00:00,11729.0 -2011-08-14 20:00:00,11552.0 -2011-08-14 21:00:00,11397.0 -2011-08-14 22:00:00,11661.0 -2011-08-14 23:00:00,11436.0 -2011-08-15 00:00:00,10799.0 -2011-08-13 01:00:00,12027.0 -2011-08-13 02:00:00,11194.0 -2011-08-13 03:00:00,10671.0 -2011-08-13 04:00:00,10211.0 -2011-08-13 05:00:00,9993.0 -2011-08-13 06:00:00,9895.0 -2011-08-13 07:00:00,10053.0 -2011-08-13 08:00:00,10150.0 -2011-08-13 09:00:00,10680.0 -2011-08-13 10:00:00,11630.0 -2011-08-13 11:00:00,12761.0 -2011-08-13 12:00:00,13887.0 -2011-08-13 13:00:00,14579.0 -2011-08-13 14:00:00,15010.0 -2011-08-13 15:00:00,14668.0 -2011-08-13 16:00:00,13874.0 -2011-08-13 17:00:00,13447.0 -2011-08-13 18:00:00,13084.0 -2011-08-13 19:00:00,12684.0 -2011-08-13 20:00:00,12298.0 -2011-08-13 21:00:00,12142.0 -2011-08-13 22:00:00,12321.0 -2011-08-13 23:00:00,12052.0 -2011-08-14 00:00:00,11414.0 -2011-08-12 01:00:00,11619.0 -2011-08-12 02:00:00,10761.0 -2011-08-12 03:00:00,10179.0 -2011-08-12 04:00:00,9767.0 -2011-08-12 05:00:00,9544.0 -2011-08-12 06:00:00,9631.0 -2011-08-12 07:00:00,10180.0 -2011-08-12 08:00:00,10834.0 -2011-08-12 09:00:00,11790.0 -2011-08-12 10:00:00,12663.0 -2011-08-12 11:00:00,13378.0 -2011-08-12 12:00:00,13945.0 -2011-08-12 13:00:00,14417.0 -2011-08-12 14:00:00,14745.0 -2011-08-12 15:00:00,15090.0 -2011-08-12 16:00:00,15393.0 -2011-08-12 17:00:00,15579.0 -2011-08-12 18:00:00,15490.0 -2011-08-12 19:00:00,15190.0 -2011-08-12 20:00:00,14623.0 -2011-08-12 21:00:00,14270.0 -2011-08-12 22:00:00,14342.0 -2011-08-12 23:00:00,13897.0 -2011-08-13 00:00:00,12936.0 -2011-08-11 01:00:00,10965.0 -2011-08-11 02:00:00,10251.0 -2011-08-11 03:00:00,9752.0 -2011-08-11 04:00:00,9406.0 -2011-08-11 05:00:00,9287.0 -2011-08-11 06:00:00,9402.0 -2011-08-11 07:00:00,9887.0 -2011-08-11 08:00:00,10533.0 -2011-08-11 09:00:00,11546.0 -2011-08-11 10:00:00,12420.0 -2011-08-11 11:00:00,13038.0 -2011-08-11 12:00:00,13615.0 -2011-08-11 13:00:00,14042.0 -2011-08-11 14:00:00,14411.0 -2011-08-11 15:00:00,14814.0 -2011-08-11 16:00:00,15111.0 -2011-08-11 17:00:00,15325.0 -2011-08-11 18:00:00,15399.0 -2011-08-11 19:00:00,15256.0 -2011-08-11 20:00:00,14734.0 -2011-08-11 21:00:00,14237.0 -2011-08-11 22:00:00,14145.0 -2011-08-11 23:00:00,13697.0 -2011-08-12 00:00:00,12660.0 -2011-08-10 01:00:00,11932.0 -2011-08-10 02:00:00,11050.0 -2011-08-10 03:00:00,10373.0 -2011-08-10 04:00:00,9936.0 -2011-08-10 05:00:00,9684.0 -2011-08-10 06:00:00,9757.0 -2011-08-10 07:00:00,10235.0 -2011-08-10 08:00:00,10911.0 -2011-08-10 09:00:00,11867.0 -2011-08-10 10:00:00,12658.0 -2011-08-10 11:00:00,13238.0 -2011-08-10 12:00:00,13694.0 -2011-08-10 13:00:00,13967.0 -2011-08-10 14:00:00,14284.0 -2011-08-10 15:00:00,14560.0 -2011-08-10 16:00:00,14694.0 -2011-08-10 17:00:00,14697.0 -2011-08-10 18:00:00,14643.0 -2011-08-10 19:00:00,14397.0 -2011-08-10 20:00:00,13834.0 -2011-08-10 21:00:00,13345.0 -2011-08-10 22:00:00,13453.0 -2011-08-10 23:00:00,12977.0 -2011-08-11 00:00:00,12012.0 -2011-08-09 01:00:00,12158.0 -2011-08-09 02:00:00,11335.0 -2011-08-09 03:00:00,10707.0 -2011-08-09 04:00:00,10283.0 -2011-08-09 05:00:00,10082.0 -2011-08-09 06:00:00,10194.0 -2011-08-09 07:00:00,10717.0 -2011-08-09 08:00:00,11480.0 -2011-08-09 09:00:00,12541.0 -2011-08-09 10:00:00,13560.0 -2011-08-09 11:00:00,14480.0 -2011-08-09 12:00:00,15395.0 -2011-08-09 13:00:00,16007.0 -2011-08-09 14:00:00,16354.0 -2011-08-09 15:00:00,16496.0 -2011-08-09 16:00:00,16481.0 -2011-08-09 17:00:00,16507.0 -2011-08-09 18:00:00,16416.0 -2011-08-09 19:00:00,16052.0 -2011-08-09 20:00:00,15402.0 -2011-08-09 21:00:00,14702.0 -2011-08-09 22:00:00,14717.0 -2011-08-09 23:00:00,14196.0 -2011-08-10 00:00:00,13112.0 -2011-08-08 01:00:00,12546.0 -2011-08-08 02:00:00,11742.0 -2011-08-08 03:00:00,11111.0 -2011-08-08 04:00:00,10727.0 -2011-08-08 05:00:00,10515.0 -2011-08-08 06:00:00,10701.0 -2011-08-08 07:00:00,11220.0 -2011-08-08 08:00:00,12092.0 -2011-08-08 09:00:00,13273.0 -2011-08-08 10:00:00,14426.0 -2011-08-08 11:00:00,15204.0 -2011-08-08 12:00:00,16003.0 -2011-08-08 13:00:00,16665.0 -2011-08-08 14:00:00,17144.0 -2011-08-08 15:00:00,17590.0 -2011-08-08 16:00:00,17640.0 -2011-08-08 17:00:00,16955.0 -2011-08-08 18:00:00,16355.0 -2011-08-08 19:00:00,15765.0 -2011-08-08 20:00:00,15161.0 -2011-08-08 21:00:00,14749.0 -2011-08-08 22:00:00,14693.0 -2011-08-08 23:00:00,14194.0 -2011-08-09 00:00:00,13246.0 -2011-08-07 01:00:00,13210.0 -2011-08-07 02:00:00,12314.0 -2011-08-07 03:00:00,11624.0 -2011-08-07 04:00:00,10994.0 -2011-08-07 05:00:00,10726.0 -2011-08-07 06:00:00,10620.0 -2011-08-07 07:00:00,10565.0 -2011-08-07 08:00:00,10522.0 -2011-08-07 09:00:00,10842.0 -2011-08-07 10:00:00,11532.0 -2011-08-07 11:00:00,12254.0 -2011-08-07 12:00:00,12725.0 -2011-08-07 13:00:00,13745.0 -2011-08-07 14:00:00,14798.0 -2011-08-07 15:00:00,15700.0 -2011-08-07 16:00:00,16469.0 -2011-08-07 17:00:00,16957.0 -2011-08-07 18:00:00,16982.0 -2011-08-07 19:00:00,16387.0 -2011-08-07 20:00:00,15574.0 -2011-08-07 21:00:00,14943.0 -2011-08-07 22:00:00,14874.0 -2011-08-07 23:00:00,14436.0 -2011-08-08 00:00:00,13581.0 -2011-08-06 01:00:00,13984.0 -2011-08-06 02:00:00,13085.0 -2011-08-06 03:00:00,12380.0 -2011-08-06 04:00:00,11879.0 -2011-08-06 05:00:00,11474.0 -2011-08-06 06:00:00,11420.0 -2011-08-06 07:00:00,11519.0 -2011-08-06 08:00:00,11765.0 -2011-08-06 09:00:00,12207.0 -2011-08-06 10:00:00,12961.0 -2011-08-06 11:00:00,13674.0 -2011-08-06 12:00:00,14440.0 -2011-08-06 13:00:00,15397.0 -2011-08-06 14:00:00,16165.0 -2011-08-06 15:00:00,16423.0 -2011-08-06 16:00:00,16605.0 -2011-08-06 17:00:00,16718.0 -2011-08-06 18:00:00,16732.0 -2011-08-06 19:00:00,16609.0 -2011-08-06 20:00:00,16250.0 -2011-08-06 21:00:00,15742.0 -2011-08-06 22:00:00,15554.0 -2011-08-06 23:00:00,15122.0 -2011-08-07 00:00:00,14181.0 -2011-08-05 01:00:00,13331.0 -2011-08-05 02:00:00,12357.0 -2011-08-05 03:00:00,11686.0 -2011-08-05 04:00:00,11180.0 -2011-08-05 05:00:00,10936.0 -2011-08-05 06:00:00,11028.0 -2011-08-05 07:00:00,11523.0 -2011-08-05 08:00:00,12297.0 -2011-08-05 09:00:00,13484.0 -2011-08-05 10:00:00,14533.0 -2011-08-05 11:00:00,15370.0 -2011-08-05 12:00:00,16281.0 -2011-08-05 13:00:00,17039.0 -2011-08-05 14:00:00,17645.0 -2011-08-05 15:00:00,18013.0 -2011-08-05 16:00:00,18099.0 -2011-08-05 17:00:00,18216.0 -2011-08-05 18:00:00,18302.0 -2011-08-05 19:00:00,18025.0 -2011-08-05 20:00:00,17173.0 -2011-08-05 21:00:00,16493.0 -2011-08-05 22:00:00,16394.0 -2011-08-05 23:00:00,15959.0 -2011-08-06 00:00:00,14935.0 -2011-08-04 01:00:00,13615.0 -2011-08-04 02:00:00,12555.0 -2011-08-04 03:00:00,11826.0 -2011-08-04 04:00:00,11295.0 -2011-08-04 05:00:00,11018.0 -2011-08-04 06:00:00,11066.0 -2011-08-04 07:00:00,11573.0 -2011-08-04 08:00:00,12329.0 -2011-08-04 09:00:00,13520.0 -2011-08-04 10:00:00,14551.0 -2011-08-04 11:00:00,15317.0 -2011-08-04 12:00:00,16029.0 -2011-08-04 13:00:00,16725.0 -2011-08-04 14:00:00,17427.0 -2011-08-04 15:00:00,18059.0 -2011-08-04 16:00:00,18380.0 -2011-08-04 17:00:00,18588.0 -2011-08-04 18:00:00,18618.0 -2011-08-04 19:00:00,18317.0 -2011-08-04 20:00:00,17581.0 -2011-08-04 21:00:00,16686.0 -2011-08-04 22:00:00,16378.0 -2011-08-04 23:00:00,15770.0 -2011-08-05 00:00:00,14573.0 -2011-08-03 01:00:00,15308.0 -2011-08-03 02:00:00,14163.0 -2011-08-03 03:00:00,13418.0 -2011-08-03 04:00:00,12948.0 -2011-08-03 05:00:00,12688.0 -2011-08-03 06:00:00,12806.0 -2011-08-03 07:00:00,13355.0 -2011-08-03 08:00:00,14117.0 -2011-08-03 09:00:00,15069.0 -2011-08-03 10:00:00,15874.0 -2011-08-03 11:00:00,16489.0 -2011-08-03 12:00:00,17380.0 -2011-08-03 13:00:00,18212.0 -2011-08-03 14:00:00,18821.0 -2011-08-03 15:00:00,19354.0 -2011-08-03 16:00:00,19615.0 -2011-08-03 17:00:00,19670.0 -2011-08-03 18:00:00,19530.0 -2011-08-03 19:00:00,19157.0 -2011-08-03 20:00:00,18430.0 -2011-08-03 21:00:00,17499.0 -2011-08-03 22:00:00,17036.0 -2011-08-03 23:00:00,16343.0 -2011-08-04 00:00:00,14974.0 -2011-08-02 01:00:00,15873.0 -2011-08-02 02:00:00,14789.0 -2011-08-02 03:00:00,14048.0 -2011-08-02 04:00:00,13524.0 -2011-08-02 05:00:00,13224.0 -2011-08-02 06:00:00,13311.0 -2011-08-02 07:00:00,13844.0 -2011-08-02 08:00:00,14674.0 -2011-08-02 09:00:00,15751.0 -2011-08-02 10:00:00,16954.0 -2011-08-02 11:00:00,18132.0 -2011-08-02 12:00:00,19191.0 -2011-08-02 13:00:00,20073.0 -2011-08-02 14:00:00,20807.0 -2011-08-02 15:00:00,21481.0 -2011-08-02 16:00:00,22044.0 -2011-08-02 17:00:00,22409.0 -2011-08-02 18:00:00,22576.0 -2011-08-02 19:00:00,22343.0 -2011-08-02 20:00:00,21567.0 -2011-08-02 21:00:00,20767.0 -2011-08-02 22:00:00,20244.0 -2011-08-02 23:00:00,19164.0 -2011-08-03 00:00:00,16834.0 -2011-08-01 01:00:00,15079.0 -2011-08-01 02:00:00,14026.0 -2011-08-01 03:00:00,13313.0 -2011-08-01 04:00:00,12742.0 -2011-08-01 05:00:00,12432.0 -2011-08-01 06:00:00,12471.0 -2011-08-01 07:00:00,12912.0 -2011-08-01 08:00:00,13791.0 -2011-08-01 09:00:00,15173.0 -2011-08-01 10:00:00,16563.0 -2011-08-01 11:00:00,17876.0 -2011-08-01 12:00:00,19019.0 -2011-08-01 13:00:00,19779.0 -2011-08-01 14:00:00,20515.0 -2011-08-01 15:00:00,21091.0 -2011-08-01 16:00:00,21435.0 -2011-08-01 17:00:00,21472.0 -2011-08-01 18:00:00,21053.0 -2011-08-01 19:00:00,20550.0 -2011-08-01 20:00:00,19977.0 -2011-08-01 21:00:00,19413.0 -2011-08-01 22:00:00,19335.0 -2011-08-01 23:00:00,18632.0 -2011-08-02 00:00:00,17213.0 -2011-07-31 01:00:00,13847.0 -2011-07-31 02:00:00,12792.0 -2011-07-31 03:00:00,12031.0 -2011-07-31 04:00:00,11352.0 -2011-07-31 05:00:00,10954.0 -2011-07-31 06:00:00,10647.0 -2011-07-31 07:00:00,10449.0 -2011-07-31 08:00:00,10363.0 -2011-07-31 09:00:00,11235.0 -2011-07-31 10:00:00,12510.0 -2011-07-31 11:00:00,13948.0 -2011-07-31 12:00:00,15271.0 -2011-07-31 13:00:00,16413.0 -2011-07-31 14:00:00,17189.0 -2011-07-31 15:00:00,17862.0 -2011-07-31 16:00:00,18285.0 -2011-07-31 17:00:00,18606.0 -2011-07-31 18:00:00,18876.0 -2011-07-31 19:00:00,18945.0 -2011-07-31 20:00:00,18653.0 -2011-07-31 21:00:00,18116.0 -2011-07-31 22:00:00,17853.0 -2011-07-31 23:00:00,17460.0 -2011-08-01 00:00:00,16342.0 -2011-07-30 01:00:00,13493.0 -2011-07-30 02:00:00,12434.0 -2011-07-30 03:00:00,11647.0 -2011-07-30 04:00:00,11078.0 -2011-07-30 05:00:00,10665.0 -2011-07-30 06:00:00,10543.0 -2011-07-30 07:00:00,10537.0 -2011-07-30 08:00:00,10771.0 -2011-07-30 09:00:00,11693.0 -2011-07-30 10:00:00,12909.0 -2011-07-30 11:00:00,14077.0 -2011-07-30 12:00:00,15248.0 -2011-07-30 13:00:00,16086.0 -2011-07-30 14:00:00,16769.0 -2011-07-30 15:00:00,17180.0 -2011-07-30 16:00:00,17615.0 -2011-07-30 17:00:00,17960.0 -2011-07-30 18:00:00,18202.0 -2011-07-30 19:00:00,18154.0 -2011-07-30 20:00:00,17710.0 -2011-07-30 21:00:00,17005.0 -2011-07-30 22:00:00,16497.0 -2011-07-30 23:00:00,15994.0 -2011-07-31 00:00:00,14933.0 -2011-07-29 01:00:00,14641.0 -2011-07-29 02:00:00,13644.0 -2011-07-29 03:00:00,12869.0 -2011-07-29 04:00:00,12303.0 -2011-07-29 05:00:00,11991.0 -2011-07-29 06:00:00,12055.0 -2011-07-29 07:00:00,12547.0 -2011-07-29 08:00:00,13314.0 -2011-07-29 09:00:00,14220.0 -2011-07-29 10:00:00,15126.0 -2011-07-29 11:00:00,15752.0 -2011-07-29 12:00:00,16337.0 -2011-07-29 13:00:00,16863.0 -2011-07-29 14:00:00,17323.0 -2011-07-29 15:00:00,17789.0 -2011-07-29 16:00:00,18273.0 -2011-07-29 17:00:00,18811.0 -2011-07-29 18:00:00,19052.0 -2011-07-29 19:00:00,18840.0 -2011-07-29 20:00:00,18187.0 -2011-07-29 21:00:00,17160.0 -2011-07-29 22:00:00,16613.0 -2011-07-29 23:00:00,16004.0 -2011-07-30 00:00:00,14791.0 -2011-07-28 01:00:00,15063.0 -2011-07-28 02:00:00,13652.0 -2011-07-28 03:00:00,12608.0 -2011-07-28 04:00:00,12084.0 -2011-07-28 05:00:00,11778.0 -2011-07-28 06:00:00,11838.0 -2011-07-28 07:00:00,12433.0 -2011-07-28 08:00:00,13355.0 -2011-07-28 09:00:00,14200.0 -2011-07-28 10:00:00,14835.0 -2011-07-28 11:00:00,15349.0 -2011-07-28 12:00:00,15875.0 -2011-07-28 13:00:00,16129.0 -2011-07-28 14:00:00,16297.0 -2011-07-28 15:00:00,16572.0 -2011-07-28 16:00:00,17187.0 -2011-07-28 17:00:00,17833.0 -2011-07-28 18:00:00,18313.0 -2011-07-28 19:00:00,18327.0 -2011-07-28 20:00:00,17969.0 -2011-07-28 21:00:00,17521.0 -2011-07-28 22:00:00,17517.0 -2011-07-28 23:00:00,17052.0 -2011-07-29 00:00:00,15874.0 -2011-07-27 01:00:00,12864.0 -2011-07-27 02:00:00,11973.0 -2011-07-27 03:00:00,11361.0 -2011-07-27 04:00:00,10941.0 -2011-07-27 05:00:00,10740.0 -2011-07-27 06:00:00,10851.0 -2011-07-27 07:00:00,11325.0 -2011-07-27 08:00:00,12156.0 -2011-07-27 09:00:00,12998.0 -2011-07-27 10:00:00,13579.0 -2011-07-27 11:00:00,14088.0 -2011-07-27 12:00:00,14476.0 -2011-07-27 13:00:00,14820.0 -2011-07-27 14:00:00,15703.0 -2011-07-27 15:00:00,16553.0 -2011-07-27 16:00:00,17401.0 -2011-07-27 17:00:00,18288.0 -2011-07-27 18:00:00,18825.0 -2011-07-27 19:00:00,19011.0 -2011-07-27 20:00:00,18789.0 -2011-07-27 21:00:00,18406.0 -2011-07-27 22:00:00,18540.0 -2011-07-27 23:00:00,18036.0 -2011-07-28 00:00:00,16762.0 -2011-07-26 01:00:00,13991.0 -2011-07-26 02:00:00,12857.0 -2011-07-26 03:00:00,12052.0 -2011-07-26 04:00:00,11519.0 -2011-07-26 05:00:00,11266.0 -2011-07-26 06:00:00,11345.0 -2011-07-26 07:00:00,11701.0 -2011-07-26 08:00:00,12558.0 -2011-07-26 09:00:00,13990.0 -2011-07-26 10:00:00,15207.0 -2011-07-26 11:00:00,16277.0 -2011-07-26 12:00:00,17276.0 -2011-07-26 13:00:00,17982.0 -2011-07-26 14:00:00,18422.0 -2011-07-26 15:00:00,18832.0 -2011-07-26 16:00:00,18992.0 -2011-07-26 17:00:00,19041.0 -2011-07-26 18:00:00,18900.0 -2011-07-26 19:00:00,18432.0 -2011-07-26 20:00:00,17491.0 -2011-07-26 21:00:00,16472.0 -2011-07-26 22:00:00,15843.0 -2011-07-26 23:00:00,15308.0 -2011-07-27 00:00:00,14094.0 -2011-07-25 01:00:00,13275.0 -2011-07-25 02:00:00,12481.0 -2011-07-25 03:00:00,11884.0 -2011-07-25 04:00:00,11437.0 -2011-07-25 05:00:00,11293.0 -2011-07-25 06:00:00,11455.0 -2011-07-25 07:00:00,11955.0 -2011-07-25 08:00:00,12888.0 -2011-07-25 09:00:00,14144.0 -2011-07-25 10:00:00,15276.0 -2011-07-25 11:00:00,16117.0 -2011-07-25 12:00:00,16954.0 -2011-07-25 13:00:00,17529.0 -2011-07-25 14:00:00,18051.0 -2011-07-25 15:00:00,18503.0 -2011-07-25 16:00:00,18915.0 -2011-07-25 17:00:00,19249.0 -2011-07-25 18:00:00,19457.0 -2011-07-25 19:00:00,19422.0 -2011-07-25 20:00:00,18928.0 -2011-07-25 21:00:00,18168.0 -2011-07-25 22:00:00,17557.0 -2011-07-25 23:00:00,16918.0 -2011-07-26 00:00:00,15479.0 -2011-07-24 01:00:00,13905.0 -2011-07-24 02:00:00,12967.0 -2011-07-24 03:00:00,12183.0 -2011-07-24 04:00:00,11519.0 -2011-07-24 05:00:00,11197.0 -2011-07-24 06:00:00,11033.0 -2011-07-24 07:00:00,10955.0 -2011-07-24 08:00:00,11030.0 -2011-07-24 09:00:00,11463.0 -2011-07-24 10:00:00,12029.0 -2011-07-24 11:00:00,12297.0 -2011-07-24 12:00:00,12690.0 -2011-07-24 13:00:00,13192.0 -2011-07-24 14:00:00,13858.0 -2011-07-24 15:00:00,14701.0 -2011-07-24 16:00:00,15363.0 -2011-07-24 17:00:00,15539.0 -2011-07-24 18:00:00,15889.0 -2011-07-24 19:00:00,16252.0 -2011-07-24 20:00:00,16103.0 -2011-07-24 21:00:00,15600.0 -2011-07-24 22:00:00,15372.0 -2011-07-24 23:00:00,15155.0 -2011-07-25 00:00:00,14318.0 -2011-07-23 01:00:00,15569.0 -2011-07-23 02:00:00,14490.0 -2011-07-23 03:00:00,13337.0 -2011-07-23 04:00:00,12458.0 -2011-07-23 05:00:00,11959.0 -2011-07-23 06:00:00,11621.0 -2011-07-23 07:00:00,11611.0 -2011-07-23 08:00:00,11683.0 -2011-07-23 09:00:00,12211.0 -2011-07-23 10:00:00,12773.0 -2011-07-23 11:00:00,13400.0 -2011-07-23 12:00:00,13994.0 -2011-07-23 13:00:00,14538.0 -2011-07-23 14:00:00,14855.0 -2011-07-23 15:00:00,15161.0 -2011-07-23 16:00:00,15554.0 -2011-07-23 17:00:00,16039.0 -2011-07-23 18:00:00,16519.0 -2011-07-23 19:00:00,16591.0 -2011-07-23 20:00:00,16483.0 -2011-07-23 21:00:00,16209.0 -2011-07-23 22:00:00,15968.0 -2011-07-23 23:00:00,15720.0 -2011-07-24 00:00:00,14918.0 -2011-07-22 01:00:00,17671.0 -2011-07-22 02:00:00,16259.0 -2011-07-22 03:00:00,15164.0 -2011-07-22 04:00:00,14314.0 -2011-07-22 05:00:00,13798.0 -2011-07-22 06:00:00,13705.0 -2011-07-22 07:00:00,14039.0 -2011-07-22 08:00:00,14700.0 -2011-07-22 09:00:00,15529.0 -2011-07-22 10:00:00,16185.0 -2011-07-22 11:00:00,16511.0 -2011-07-22 12:00:00,16288.0 -2011-07-22 13:00:00,16099.0 -2011-07-22 14:00:00,16446.0 -2011-07-22 15:00:00,17205.0 -2011-07-22 16:00:00,17932.0 -2011-07-22 17:00:00,18677.0 -2011-07-22 18:00:00,19309.0 -2011-07-22 19:00:00,19551.0 -2011-07-22 20:00:00,19317.0 -2011-07-22 21:00:00,18811.0 -2011-07-22 22:00:00,18326.0 -2011-07-22 23:00:00,17829.0 -2011-07-23 00:00:00,16698.0 -2011-07-21 01:00:00,18510.0 -2011-07-21 02:00:00,17311.0 -2011-07-21 03:00:00,16438.0 -2011-07-21 04:00:00,15702.0 -2011-07-21 05:00:00,15201.0 -2011-07-21 06:00:00,15081.0 -2011-07-21 07:00:00,15341.0 -2011-07-21 08:00:00,16237.0 -2011-07-21 09:00:00,17725.0 -2011-07-21 10:00:00,19091.0 -2011-07-21 11:00:00,20346.0 -2011-07-21 12:00:00,21622.0 -2011-07-21 13:00:00,22510.0 -2011-07-21 14:00:00,23119.0 -2011-07-21 15:00:00,23446.0 -2011-07-21 16:00:00,23587.0 -2011-07-21 17:00:00,23670.0 -2011-07-21 18:00:00,23542.0 -2011-07-21 19:00:00,23160.0 -2011-07-21 20:00:00,22596.0 -2011-07-21 21:00:00,21843.0 -2011-07-21 22:00:00,21320.0 -2011-07-21 23:00:00,20673.0 -2011-07-22 00:00:00,19229.0 -2011-07-20 01:00:00,16669.0 -2011-07-20 02:00:00,15488.0 -2011-07-20 03:00:00,14599.0 -2011-07-20 04:00:00,13998.0 -2011-07-20 05:00:00,13630.0 -2011-07-20 06:00:00,13505.0 -2011-07-20 07:00:00,13902.0 -2011-07-20 08:00:00,14668.0 -2011-07-20 09:00:00,16041.0 -2011-07-20 10:00:00,17411.0 -2011-07-20 11:00:00,18980.0 -2011-07-20 12:00:00,20550.0 -2011-07-20 13:00:00,21773.0 -2011-07-20 14:00:00,22543.0 -2011-07-20 15:00:00,23194.0 -2011-07-20 16:00:00,23524.0 -2011-07-20 17:00:00,23713.0 -2011-07-20 18:00:00,23753.0 -2011-07-20 19:00:00,23582.0 -2011-07-20 20:00:00,23089.0 -2011-07-20 21:00:00,22458.0 -2011-07-20 22:00:00,22012.0 -2011-07-20 23:00:00,21407.0 -2011-07-21 00:00:00,20016.0 -2011-07-19 01:00:00,17146.0 -2011-07-19 02:00:00,16064.0 -2011-07-19 03:00:00,15301.0 -2011-07-19 04:00:00,14670.0 -2011-07-19 05:00:00,14287.0 -2011-07-19 06:00:00,14182.0 -2011-07-19 07:00:00,14420.0 -2011-07-19 08:00:00,15250.0 -2011-07-19 09:00:00,16672.0 -2011-07-19 10:00:00,18034.0 -2011-07-19 11:00:00,19274.0 -2011-07-19 12:00:00,20429.0 -2011-07-19 13:00:00,21191.0 -2011-07-19 14:00:00,21768.0 -2011-07-19 15:00:00,22278.0 -2011-07-19 16:00:00,22429.0 -2011-07-19 17:00:00,22463.0 -2011-07-19 18:00:00,22330.0 -2011-07-19 19:00:00,21969.0 -2011-07-19 20:00:00,21281.0 -2011-07-19 21:00:00,20584.0 -2011-07-19 22:00:00,20155.0 -2011-07-19 23:00:00,19599.0 -2011-07-20 00:00:00,18164.0 -2011-07-18 01:00:00,16257.0 -2011-07-18 02:00:00,15301.0 -2011-07-18 03:00:00,14622.0 -2011-07-18 04:00:00,14125.0 -2011-07-18 05:00:00,13933.0 -2011-07-18 06:00:00,13968.0 -2011-07-18 07:00:00,14500.0 -2011-07-18 08:00:00,15382.0 -2011-07-18 09:00:00,16669.0 -2011-07-18 10:00:00,17483.0 -2011-07-18 11:00:00,18443.0 -2011-07-18 12:00:00,19065.0 -2011-07-18 13:00:00,19535.0 -2011-07-18 14:00:00,20122.0 -2011-07-18 15:00:00,20650.0 -2011-07-18 16:00:00,20844.0 -2011-07-18 17:00:00,21037.0 -2011-07-18 18:00:00,21294.0 -2011-07-18 19:00:00,21278.0 -2011-07-18 20:00:00,20951.0 -2011-07-18 21:00:00,20461.0 -2011-07-18 22:00:00,20239.0 -2011-07-18 23:00:00,19866.0 -2011-07-19 00:00:00,18514.0 -2011-07-17 01:00:00,13153.0 -2011-07-17 02:00:00,12262.0 -2011-07-17 03:00:00,11504.0 -2011-07-17 04:00:00,11007.0 -2011-07-17 05:00:00,10582.0 -2011-07-17 06:00:00,10336.0 -2011-07-17 07:00:00,10133.0 -2011-07-17 08:00:00,10262.0 -2011-07-17 09:00:00,11105.0 -2011-07-17 10:00:00,12434.0 -2011-07-17 11:00:00,13928.0 -2011-07-17 12:00:00,15305.0 -2011-07-17 13:00:00,16467.0 -2011-07-17 14:00:00,17435.0 -2011-07-17 15:00:00,18224.0 -2011-07-17 16:00:00,18788.0 -2011-07-17 17:00:00,19225.0 -2011-07-17 18:00:00,19524.0 -2011-07-17 19:00:00,19566.0 -2011-07-17 20:00:00,19353.0 -2011-07-17 21:00:00,18862.0 -2011-07-17 22:00:00,18670.0 -2011-07-17 23:00:00,18448.0 -2011-07-18 00:00:00,17431.0 -2011-07-16 01:00:00,13141.0 -2011-07-16 02:00:00,12168.0 -2011-07-16 03:00:00,11417.0 -2011-07-16 04:00:00,10880.0 -2011-07-16 05:00:00,10677.0 -2011-07-16 06:00:00,10554.0 -2011-07-16 07:00:00,10666.0 -2011-07-16 08:00:00,10900.0 -2011-07-16 09:00:00,11506.0 -2011-07-16 10:00:00,12303.0 -2011-07-16 11:00:00,13012.0 -2011-07-16 12:00:00,13639.0 -2011-07-16 13:00:00,14143.0 -2011-07-16 14:00:00,14825.0 -2011-07-16 15:00:00,15460.0 -2011-07-16 16:00:00,15990.0 -2011-07-16 17:00:00,16365.0 -2011-07-16 18:00:00,16711.0 -2011-07-16 19:00:00,16682.0 -2011-07-16 20:00:00,16484.0 -2011-07-16 21:00:00,15920.0 -2011-07-16 22:00:00,15493.0 -2011-07-16 23:00:00,15153.0 -2011-07-17 00:00:00,14243.0 -2011-07-15 01:00:00,11911.0 -2011-07-15 02:00:00,11065.0 -2011-07-15 03:00:00,10472.0 -2011-07-15 04:00:00,10092.0 -2011-07-15 05:00:00,9949.0 -2011-07-15 06:00:00,10061.0 -2011-07-15 07:00:00,10559.0 -2011-07-15 08:00:00,11405.0 -2011-07-15 09:00:00,12623.0 -2011-07-15 10:00:00,13780.0 -2011-07-15 11:00:00,14837.0 -2011-07-15 12:00:00,15759.0 -2011-07-15 13:00:00,16480.0 -2011-07-15 14:00:00,17031.0 -2011-07-15 15:00:00,17573.0 -2011-07-15 16:00:00,17850.0 -2011-07-15 17:00:00,17810.0 -2011-07-15 18:00:00,17562.0 -2011-07-15 19:00:00,17314.0 -2011-07-15 20:00:00,16681.0 -2011-07-15 21:00:00,15839.0 -2011-07-15 22:00:00,15565.0 -2011-07-15 23:00:00,15249.0 -2011-07-16 00:00:00,14245.0 -2011-07-14 01:00:00,10980.0 -2011-07-14 02:00:00,10206.0 -2011-07-14 03:00:00,9694.0 -2011-07-14 04:00:00,9359.0 -2011-07-14 05:00:00,9225.0 -2011-07-14 06:00:00,9371.0 -2011-07-14 07:00:00,9784.0 -2011-07-14 08:00:00,10655.0 -2011-07-14 09:00:00,11683.0 -2011-07-14 10:00:00,12495.0 -2011-07-14 11:00:00,13226.0 -2011-07-14 12:00:00,13887.0 -2011-07-14 13:00:00,14354.0 -2011-07-14 14:00:00,14777.0 -2011-07-14 15:00:00,15251.0 -2011-07-14 16:00:00,15587.0 -2011-07-14 17:00:00,15821.0 -2011-07-14 18:00:00,15908.0 -2011-07-14 19:00:00,15634.0 -2011-07-14 20:00:00,14883.0 -2011-07-14 21:00:00,14235.0 -2011-07-14 22:00:00,13984.0 -2011-07-14 23:00:00,13855.0 -2011-07-15 00:00:00,12958.0 -2011-07-13 01:00:00,12527.0 -2011-07-13 02:00:00,11535.0 -2011-07-13 03:00:00,10899.0 -2011-07-13 04:00:00,10437.0 -2011-07-13 05:00:00,10194.0 -2011-07-13 06:00:00,10263.0 -2011-07-13 07:00:00,10636.0 -2011-07-13 08:00:00,11510.0 -2011-07-13 09:00:00,12471.0 -2011-07-13 10:00:00,13029.0 -2011-07-13 11:00:00,13446.0 -2011-07-13 12:00:00,13909.0 -2011-07-13 13:00:00,14255.0 -2011-07-13 14:00:00,14498.0 -2011-07-13 15:00:00,14660.0 -2011-07-13 16:00:00,14751.0 -2011-07-13 17:00:00,14780.0 -2011-07-13 18:00:00,14727.0 -2011-07-13 19:00:00,14426.0 -2011-07-13 20:00:00,13825.0 -2011-07-13 21:00:00,13254.0 -2011-07-13 22:00:00,12991.0 -2011-07-13 23:00:00,12817.0 -2011-07-14 00:00:00,11949.0 -2011-07-12 01:00:00,14050.0 -2011-07-12 02:00:00,12935.0 -2011-07-12 03:00:00,12146.0 -2011-07-12 04:00:00,11544.0 -2011-07-12 05:00:00,11188.0 -2011-07-12 06:00:00,11133.0 -2011-07-12 07:00:00,11363.0 -2011-07-12 08:00:00,12318.0 -2011-07-12 09:00:00,13732.0 -2011-07-12 10:00:00,14944.0 -2011-07-12 11:00:00,15977.0 -2011-07-12 12:00:00,16892.0 -2011-07-12 13:00:00,17421.0 -2011-07-12 14:00:00,17784.0 -2011-07-12 15:00:00,18194.0 -2011-07-12 16:00:00,18519.0 -2011-07-12 17:00:00,18644.0 -2011-07-12 18:00:00,18447.0 -2011-07-12 19:00:00,18021.0 -2011-07-12 20:00:00,17047.0 -2011-07-12 21:00:00,16275.0 -2011-07-12 22:00:00,15754.0 -2011-07-12 23:00:00,15015.0 -2011-07-13 00:00:00,13788.0 -2011-07-11 01:00:00,14707.0 -2011-07-11 02:00:00,13777.0 -2011-07-11 03:00:00,13115.0 -2011-07-11 04:00:00,12697.0 -2011-07-11 05:00:00,12536.0 -2011-07-11 06:00:00,12683.0 -2011-07-11 07:00:00,13218.0 -2011-07-11 08:00:00,14257.0 -2011-07-11 09:00:00,15368.0 -2011-07-11 10:00:00,13866.0 -2011-07-11 11:00:00,13831.0 -2011-07-11 12:00:00,14016.0 -2011-07-11 13:00:00,14366.0 -2011-07-11 14:00:00,14808.0 -2011-07-11 15:00:00,15813.0 -2011-07-11 16:00:00,16715.0 -2011-07-11 17:00:00,17321.0 -2011-07-11 18:00:00,17804.0 -2011-07-11 19:00:00,18023.0 -2011-07-11 20:00:00,17748.0 -2011-07-11 21:00:00,17258.0 -2011-07-11 22:00:00,16972.0 -2011-07-11 23:00:00,16630.0 -2011-07-12 00:00:00,15432.0 -2011-07-10 01:00:00,13861.0 -2011-07-10 02:00:00,12946.0 -2011-07-10 03:00:00,12245.0 -2011-07-10 04:00:00,11623.0 -2011-07-10 05:00:00,11198.0 -2011-07-10 06:00:00,10950.0 -2011-07-10 07:00:00,10642.0 -2011-07-10 08:00:00,10783.0 -2011-07-10 09:00:00,11641.0 -2011-07-10 10:00:00,12978.0 -2011-07-10 11:00:00,14349.0 -2011-07-10 12:00:00,15488.0 -2011-07-10 13:00:00,16345.0 -2011-07-10 14:00:00,16989.0 -2011-07-10 15:00:00,17488.0 -2011-07-10 16:00:00,17795.0 -2011-07-10 17:00:00,18001.0 -2011-07-10 18:00:00,18073.0 -2011-07-10 19:00:00,17833.0 -2011-07-10 20:00:00,17350.0 -2011-07-10 21:00:00,16982.0 -2011-07-10 22:00:00,16781.0 -2011-07-10 23:00:00,16700.0 -2011-07-11 00:00:00,15795.0 -2011-07-09 01:00:00,12393.0 -2011-07-09 02:00:00,11387.0 -2011-07-09 03:00:00,10628.0 -2011-07-09 04:00:00,10083.0 -2011-07-09 05:00:00,9782.0 -2011-07-09 06:00:00,9610.0 -2011-07-09 07:00:00,9524.0 -2011-07-09 08:00:00,9838.0 -2011-07-09 09:00:00,10771.0 -2011-07-09 10:00:00,11921.0 -2011-07-09 11:00:00,13075.0 -2011-07-09 12:00:00,14194.0 -2011-07-09 13:00:00,15066.0 -2011-07-09 14:00:00,15737.0 -2011-07-09 15:00:00,16221.0 -2011-07-09 16:00:00,16618.0 -2011-07-09 17:00:00,16978.0 -2011-07-09 18:00:00,17273.0 -2011-07-09 19:00:00,17278.0 -2011-07-09 20:00:00,16947.0 -2011-07-09 21:00:00,16399.0 -2011-07-09 22:00:00,16000.0 -2011-07-09 23:00:00,15770.0 -2011-07-10 00:00:00,14868.0 -2011-07-08 01:00:00,11595.0 -2011-07-08 02:00:00,10758.0 -2011-07-08 03:00:00,10185.0 -2011-07-08 04:00:00,9782.0 -2011-07-08 05:00:00,9574.0 -2011-07-08 06:00:00,9663.0 -2011-07-08 07:00:00,10006.0 -2011-07-08 08:00:00,10875.0 -2011-07-08 09:00:00,12037.0 -2011-07-08 10:00:00,13100.0 -2011-07-08 11:00:00,13991.0 -2011-07-08 12:00:00,14760.0 -2011-07-08 13:00:00,15374.0 -2011-07-08 14:00:00,15889.0 -2011-07-08 15:00:00,16440.0 -2011-07-08 16:00:00,16802.0 -2011-07-08 17:00:00,17098.0 -2011-07-08 18:00:00,17227.0 -2011-07-08 19:00:00,17053.0 -2011-07-08 20:00:00,16487.0 -2011-07-08 21:00:00,15730.0 -2011-07-08 22:00:00,15060.0 -2011-07-08 23:00:00,14711.0 -2011-07-09 00:00:00,13586.0 -2011-07-07 01:00:00,12381.0 -2011-07-07 02:00:00,11440.0 -2011-07-07 03:00:00,10792.0 -2011-07-07 04:00:00,10357.0 -2011-07-07 05:00:00,10158.0 -2011-07-07 06:00:00,10188.0 -2011-07-07 07:00:00,10606.0 -2011-07-07 08:00:00,11423.0 -2011-07-07 09:00:00,12392.0 -2011-07-07 10:00:00,13292.0 -2011-07-07 11:00:00,13884.0 -2011-07-07 12:00:00,14535.0 -2011-07-07 13:00:00,14895.0 -2011-07-07 14:00:00,15163.0 -2011-07-07 15:00:00,15435.0 -2011-07-07 16:00:00,15512.0 -2011-07-07 17:00:00,15476.0 -2011-07-07 18:00:00,15385.0 -2011-07-07 19:00:00,15363.0 -2011-07-07 20:00:00,14899.0 -2011-07-07 21:00:00,14342.0 -2011-07-07 22:00:00,13963.0 -2011-07-07 23:00:00,13749.0 -2011-07-08 00:00:00,12756.0 -2011-07-06 01:00:00,14566.0 -2011-07-06 02:00:00,13436.0 -2011-07-06 03:00:00,12596.0 -2011-07-06 04:00:00,11996.0 -2011-07-06 05:00:00,11660.0 -2011-07-06 06:00:00,11607.0 -2011-07-06 07:00:00,11868.0 -2011-07-06 08:00:00,12827.0 -2011-07-06 09:00:00,14194.0 -2011-07-06 10:00:00,15437.0 -2011-07-06 11:00:00,16547.0 -2011-07-06 12:00:00,17562.0 -2011-07-06 13:00:00,18207.0 -2011-07-06 14:00:00,18636.0 -2011-07-06 15:00:00,18950.0 -2011-07-06 16:00:00,19027.0 -2011-07-06 17:00:00,18936.0 -2011-07-06 18:00:00,18711.0 -2011-07-06 19:00:00,18288.0 -2011-07-06 20:00:00,17362.0 -2011-07-06 21:00:00,16267.0 -2011-07-06 22:00:00,15493.0 -2011-07-06 23:00:00,14943.0 -2011-07-07 00:00:00,13689.0 -2011-07-05 01:00:00,11997.0 -2011-07-05 02:00:00,11042.0 -2011-07-05 03:00:00,10338.0 -2011-07-05 04:00:00,9847.0 -2011-07-05 05:00:00,9640.0 -2011-07-05 06:00:00,9712.0 -2011-07-05 07:00:00,10088.0 -2011-07-05 08:00:00,11178.0 -2011-07-05 09:00:00,12691.0 -2011-07-05 10:00:00,14055.0 -2011-07-05 11:00:00,15300.0 -2011-07-05 12:00:00,16439.0 -2011-07-05 13:00:00,17334.0 -2011-07-05 14:00:00,18031.0 -2011-07-05 15:00:00,18712.0 -2011-07-05 16:00:00,19220.0 -2011-07-05 17:00:00,19570.0 -2011-07-05 18:00:00,19759.0 -2011-07-05 19:00:00,19593.0 -2011-07-05 20:00:00,18940.0 -2011-07-05 21:00:00,18158.0 -2011-07-05 22:00:00,17867.0 -2011-07-05 23:00:00,17371.0 -2011-07-06 00:00:00,16030.0 -2011-07-04 01:00:00,11163.0 -2011-07-04 02:00:00,10477.0 -2011-07-04 03:00:00,9855.0 -2011-07-04 04:00:00,9410.0 -2011-07-04 05:00:00,9161.0 -2011-07-04 06:00:00,9086.0 -2011-07-04 07:00:00,8938.0 -2011-07-04 08:00:00,9088.0 -2011-07-04 09:00:00,9739.0 -2011-07-04 10:00:00,10717.0 -2011-07-04 11:00:00,11779.0 -2011-07-04 12:00:00,12733.0 -2011-07-04 13:00:00,13517.0 -2011-07-04 14:00:00,14088.0 -2011-07-04 15:00:00,14515.0 -2011-07-04 16:00:00,14923.0 -2011-07-04 17:00:00,15290.0 -2011-07-04 18:00:00,15550.0 -2011-07-04 19:00:00,15553.0 -2011-07-04 20:00:00,15226.0 -2011-07-04 21:00:00,14657.0 -2011-07-04 22:00:00,14009.0 -2011-07-04 23:00:00,13531.0 -2011-07-05 00:00:00,12833.0 -2011-07-03 01:00:00,13038.0 -2011-07-03 02:00:00,11994.0 -2011-07-03 03:00:00,11183.0 -2011-07-03 04:00:00,10561.0 -2011-07-03 05:00:00,10183.0 -2011-07-03 06:00:00,9875.0 -2011-07-03 07:00:00,9604.0 -2011-07-03 08:00:00,9688.0 -2011-07-03 09:00:00,10446.0 -2011-07-03 10:00:00,11448.0 -2011-07-03 11:00:00,12395.0 -2011-07-03 12:00:00,13359.0 -2011-07-03 13:00:00,14095.0 -2011-07-03 14:00:00,14476.0 -2011-07-03 15:00:00,14564.0 -2011-07-03 16:00:00,14497.0 -2011-07-03 17:00:00,14513.0 -2011-07-03 18:00:00,14443.0 -2011-07-03 19:00:00,14234.0 -2011-07-03 20:00:00,13461.0 -2011-07-03 21:00:00,12838.0 -2011-07-03 22:00:00,12599.0 -2011-07-03 23:00:00,12463.0 -2011-07-04 00:00:00,11921.0 -2011-07-02 01:00:00,13191.0 -2011-07-02 02:00:00,12256.0 -2011-07-02 03:00:00,11640.0 -2011-07-02 04:00:00,11185.0 -2011-07-02 05:00:00,10931.0 -2011-07-02 06:00:00,10794.0 -2011-07-02 07:00:00,10756.0 -2011-07-02 08:00:00,11194.0 -2011-07-02 09:00:00,12398.0 -2011-07-02 10:00:00,13991.0 -2011-07-02 11:00:00,15398.0 -2011-07-02 12:00:00,16528.0 -2011-07-02 13:00:00,17292.0 -2011-07-02 14:00:00,17865.0 -2011-07-02 15:00:00,18082.0 -2011-07-02 16:00:00,18338.0 -2011-07-02 17:00:00,18387.0 -2011-07-02 18:00:00,18186.0 -2011-07-02 19:00:00,17823.0 -2011-07-02 20:00:00,17239.0 -2011-07-02 21:00:00,16377.0 -2011-07-02 22:00:00,15668.0 -2011-07-02 23:00:00,15182.0 -2011-07-03 00:00:00,14123.0 -2011-07-01 01:00:00,13174.0 -2011-07-01 02:00:00,12170.0 -2011-07-01 03:00:00,11461.0 -2011-07-01 04:00:00,10959.0 -2011-07-01 05:00:00,10726.0 -2011-07-01 06:00:00,10808.0 -2011-07-01 07:00:00,11245.0 -2011-07-01 08:00:00,12044.0 -2011-07-01 09:00:00,13198.0 -2011-07-01 10:00:00,13899.0 -2011-07-01 11:00:00,14275.0 -2011-07-01 12:00:00,14553.0 -2011-07-01 13:00:00,14711.0 -2011-07-01 14:00:00,14858.0 -2011-07-01 15:00:00,14924.0 -2011-07-01 16:00:00,15003.0 -2011-07-01 17:00:00,15316.0 -2011-07-01 18:00:00,15812.0 -2011-07-01 19:00:00,16202.0 -2011-07-01 20:00:00,16132.0 -2011-07-01 21:00:00,15697.0 -2011-07-01 22:00:00,15332.0 -2011-07-01 23:00:00,15177.0 -2011-07-02 00:00:00,14255.0 -2011-06-30 01:00:00,11474.0 -2011-06-30 02:00:00,10564.0 -2011-06-30 03:00:00,9929.0 -2011-06-30 04:00:00,9532.0 -2011-06-30 05:00:00,9320.0 -2011-06-30 06:00:00,9432.0 -2011-06-30 07:00:00,9798.0 -2011-06-30 08:00:00,10921.0 -2011-06-30 09:00:00,12166.0 -2011-06-30 10:00:00,13218.0 -2011-06-30 11:00:00,14210.0 -2011-06-30 12:00:00,15153.0 -2011-06-30 13:00:00,15858.0 -2011-06-30 14:00:00,16575.0 -2011-06-30 15:00:00,17272.0 -2011-06-30 16:00:00,17810.0 -2011-06-30 17:00:00,18276.0 -2011-06-30 18:00:00,18435.0 -2011-06-30 19:00:00,18178.0 -2011-06-30 20:00:00,17797.0 -2011-06-30 21:00:00,17257.0 -2011-06-30 22:00:00,16754.0 -2011-06-30 23:00:00,15669.0 -2011-07-01 00:00:00,14323.0 -2011-06-29 01:00:00,11468.0 -2011-06-29 02:00:00,10509.0 -2011-06-29 03:00:00,9877.0 -2011-06-29 04:00:00,9458.0 -2011-06-29 05:00:00,9265.0 -2011-06-29 06:00:00,9355.0 -2011-06-29 07:00:00,9654.0 -2011-06-29 08:00:00,10658.0 -2011-06-29 09:00:00,11860.0 -2011-06-29 10:00:00,12800.0 -2011-06-29 11:00:00,13522.0 -2011-06-29 12:00:00,14164.0 -2011-06-29 13:00:00,14653.0 -2011-06-29 14:00:00,15071.0 -2011-06-29 15:00:00,15519.0 -2011-06-29 16:00:00,15817.0 -2011-06-29 17:00:00,15991.0 -2011-06-29 18:00:00,16049.0 -2011-06-29 19:00:00,15874.0 -2011-06-29 20:00:00,15259.0 -2011-06-29 21:00:00,14531.0 -2011-06-29 22:00:00,14019.0 -2011-06-29 23:00:00,13716.0 -2011-06-30 00:00:00,12632.0 -2011-06-28 01:00:00,12421.0 -2011-06-28 02:00:00,11256.0 -2011-06-28 03:00:00,10454.0 -2011-06-28 04:00:00,9864.0 -2011-06-28 05:00:00,9562.0 -2011-06-28 06:00:00,9597.0 -2011-06-28 07:00:00,9915.0 -2011-06-28 08:00:00,10847.0 -2011-06-28 09:00:00,11874.0 -2011-06-28 10:00:00,12627.0 -2011-06-28 11:00:00,13207.0 -2011-06-28 12:00:00,13773.0 -2011-06-28 13:00:00,14140.0 -2011-06-28 14:00:00,14449.0 -2011-06-28 15:00:00,14807.0 -2011-06-28 16:00:00,15105.0 -2011-06-28 17:00:00,15349.0 -2011-06-28 18:00:00,15523.0 -2011-06-28 19:00:00,15417.0 -2011-06-28 20:00:00,15044.0 -2011-06-28 21:00:00,14465.0 -2011-06-28 22:00:00,14042.0 -2011-06-28 23:00:00,13781.0 -2011-06-29 00:00:00,12681.0 -2011-06-27 01:00:00,10363.0 -2011-06-27 02:00:00,9730.0 -2011-06-27 03:00:00,9331.0 -2011-06-27 04:00:00,9072.0 -2011-06-27 05:00:00,9030.0 -2011-06-27 06:00:00,9279.0 -2011-06-27 07:00:00,9859.0 -2011-06-27 08:00:00,10884.0 -2011-06-27 09:00:00,11894.0 -2011-06-27 10:00:00,12551.0 -2011-06-27 11:00:00,13048.0 -2011-06-27 12:00:00,13724.0 -2011-06-27 13:00:00,14240.0 -2011-06-27 14:00:00,14620.0 -2011-06-27 15:00:00,15212.0 -2011-06-27 16:00:00,15548.0 -2011-06-27 17:00:00,15612.0 -2011-06-27 18:00:00,15802.0 -2011-06-27 19:00:00,15811.0 -2011-06-27 20:00:00,15546.0 -2011-06-27 21:00:00,15210.0 -2011-06-27 22:00:00,14918.0 -2011-06-27 23:00:00,14836.0 -2011-06-28 00:00:00,13793.0 -2011-06-26 01:00:00,9961.0 -2011-06-26 02:00:00,9297.0 -2011-06-26 03:00:00,8862.0 -2011-06-26 04:00:00,8443.0 -2011-06-26 05:00:00,8302.0 -2011-06-26 06:00:00,8222.0 -2011-06-26 07:00:00,8052.0 -2011-06-26 08:00:00,8114.0 -2011-06-26 09:00:00,8651.0 -2011-06-26 10:00:00,9390.0 -2011-06-26 11:00:00,10114.0 -2011-06-26 12:00:00,10768.0 -2011-06-26 13:00:00,11177.0 -2011-06-26 14:00:00,11513.0 -2011-06-26 15:00:00,11789.0 -2011-06-26 16:00:00,12047.0 -2011-06-26 17:00:00,12287.0 -2011-06-26 18:00:00,12454.0 -2011-06-26 19:00:00,12421.0 -2011-06-26 20:00:00,12240.0 -2011-06-26 21:00:00,11900.0 -2011-06-26 22:00:00,11883.0 -2011-06-26 23:00:00,11881.0 -2011-06-27 00:00:00,11281.0 -2011-06-25 01:00:00,10039.0 -2011-06-25 02:00:00,9349.0 -2011-06-25 03:00:00,8948.0 -2011-06-25 04:00:00,8641.0 -2011-06-25 05:00:00,8510.0 -2011-06-25 06:00:00,8469.0 -2011-06-25 07:00:00,8406.0 -2011-06-25 08:00:00,8714.0 -2011-06-25 09:00:00,9489.0 -2011-06-25 10:00:00,10247.0 -2011-06-25 11:00:00,10942.0 -2011-06-25 12:00:00,11463.0 -2011-06-25 13:00:00,11794.0 -2011-06-25 14:00:00,11997.0 -2011-06-25 15:00:00,12032.0 -2011-06-25 16:00:00,12002.0 -2011-06-25 17:00:00,11834.0 -2011-06-25 18:00:00,11753.0 -2011-06-25 19:00:00,11465.0 -2011-06-25 20:00:00,11232.0 -2011-06-25 21:00:00,11068.0 -2011-06-25 22:00:00,11242.0 -2011-06-25 23:00:00,11236.0 -2011-06-26 00:00:00,10654.0 -2011-06-24 01:00:00,10105.0 -2011-06-24 02:00:00,9470.0 -2011-06-24 03:00:00,9055.0 -2011-06-24 04:00:00,8782.0 -2011-06-24 05:00:00,8663.0 -2011-06-24 06:00:00,8781.0 -2011-06-24 07:00:00,9224.0 -2011-06-24 08:00:00,9973.0 -2011-06-24 09:00:00,10777.0 -2011-06-24 10:00:00,11336.0 -2011-06-24 11:00:00,11716.0 -2011-06-24 12:00:00,11999.0 -2011-06-24 13:00:00,12069.0 -2011-06-24 14:00:00,12088.0 -2011-06-24 15:00:00,12117.0 -2011-06-24 16:00:00,11996.0 -2011-06-24 17:00:00,11811.0 -2011-06-24 18:00:00,11687.0 -2011-06-24 19:00:00,11483.0 -2011-06-24 20:00:00,11213.0 -2011-06-24 21:00:00,11212.0 -2011-06-24 22:00:00,11474.0 -2011-06-24 23:00:00,11336.0 -2011-06-25 00:00:00,10729.0 -2011-06-23 01:00:00,10455.0 -2011-06-23 02:00:00,9775.0 -2011-06-23 03:00:00,9297.0 -2011-06-23 04:00:00,9019.0 -2011-06-23 05:00:00,8896.0 -2011-06-23 06:00:00,9076.0 -2011-06-23 07:00:00,9602.0 -2011-06-23 08:00:00,10475.0 -2011-06-23 09:00:00,11416.0 -2011-06-23 10:00:00,12022.0 -2011-06-23 11:00:00,12374.0 -2011-06-23 12:00:00,12698.0 -2011-06-23 13:00:00,12938.0 -2011-06-23 14:00:00,13052.0 -2011-06-23 15:00:00,13078.0 -2011-06-23 16:00:00,12964.0 -2011-06-23 17:00:00,12790.0 -2011-06-23 18:00:00,12560.0 -2011-06-23 19:00:00,12287.0 -2011-06-23 20:00:00,11900.0 -2011-06-23 21:00:00,11773.0 -2011-06-23 22:00:00,11979.0 -2011-06-23 23:00:00,11798.0 -2011-06-24 00:00:00,10990.0 -2011-06-22 01:00:00,11919.0 -2011-06-22 02:00:00,11031.0 -2011-06-22 03:00:00,10446.0 -2011-06-22 04:00:00,10013.0 -2011-06-22 05:00:00,9797.0 -2011-06-22 06:00:00,9909.0 -2011-06-22 07:00:00,10317.0 -2011-06-22 08:00:00,11186.0 -2011-06-22 09:00:00,12148.0 -2011-06-22 10:00:00,12905.0 -2011-06-22 11:00:00,13361.0 -2011-06-22 12:00:00,13535.0 -2011-06-22 13:00:00,13595.0 -2011-06-22 14:00:00,13570.0 -2011-06-22 15:00:00,13668.0 -2011-06-22 16:00:00,13501.0 -2011-06-22 17:00:00,13241.0 -2011-06-22 18:00:00,13018.0 -2011-06-22 19:00:00,12694.0 -2011-06-22 20:00:00,12290.0 -2011-06-22 21:00:00,12130.0 -2011-06-22 22:00:00,12226.0 -2011-06-22 23:00:00,12103.0 -2011-06-23 00:00:00,11335.0 -2011-06-21 01:00:00,12172.0 -2011-06-21 02:00:00,11331.0 -2011-06-21 03:00:00,10774.0 -2011-06-21 04:00:00,10405.0 -2011-06-21 05:00:00,10277.0 -2011-06-21 06:00:00,10450.0 -2011-06-21 07:00:00,11010.0 -2011-06-21 08:00:00,12141.0 -2011-06-21 09:00:00,13635.0 -2011-06-21 10:00:00,14965.0 -2011-06-21 11:00:00,16119.0 -2011-06-21 12:00:00,17222.0 -2011-06-21 13:00:00,18048.0 -2011-06-21 14:00:00,18653.0 -2011-06-21 15:00:00,19140.0 -2011-06-21 16:00:00,19123.0 -2011-06-21 17:00:00,19225.0 -2011-06-21 18:00:00,19183.0 -2011-06-21 19:00:00,18971.0 -2011-06-21 20:00:00,18208.0 -2011-06-21 21:00:00,17719.0 -2011-06-21 22:00:00,16713.0 -2011-06-21 23:00:00,14233.0 -2011-06-22 00:00:00,13065.0 -2011-06-20 01:00:00,11629.0 -2011-06-20 02:00:00,10873.0 -2011-06-20 03:00:00,10259.0 -2011-06-20 04:00:00,9925.0 -2011-06-20 05:00:00,9732.0 -2011-06-20 06:00:00,9930.0 -2011-06-20 07:00:00,10538.0 -2011-06-20 08:00:00,11747.0 -2011-06-20 09:00:00,12620.0 -2011-06-20 10:00:00,13032.0 -2011-06-20 11:00:00,13438.0 -2011-06-20 12:00:00,13829.0 -2011-06-20 13:00:00,14013.0 -2011-06-20 14:00:00,14142.0 -2011-06-20 15:00:00,14556.0 -2011-06-20 16:00:00,14990.0 -2011-06-20 17:00:00,15331.0 -2011-06-20 18:00:00,15493.0 -2011-06-20 19:00:00,15327.0 -2011-06-20 20:00:00,14878.0 -2011-06-20 21:00:00,14487.0 -2011-06-20 22:00:00,14492.0 -2011-06-20 23:00:00,14311.0 -2011-06-21 00:00:00,13290.0 -2011-06-19 01:00:00,11001.0 -2011-06-19 02:00:00,10179.0 -2011-06-19 03:00:00,9598.0 -2011-06-19 04:00:00,9085.0 -2011-06-19 05:00:00,8881.0 -2011-06-19 06:00:00,8737.0 -2011-06-19 07:00:00,8516.0 -2011-06-19 08:00:00,8629.0 -2011-06-19 09:00:00,9155.0 -2011-06-19 10:00:00,9963.0 -2011-06-19 11:00:00,10719.0 -2011-06-19 12:00:00,11527.0 -2011-06-19 13:00:00,12359.0 -2011-06-19 14:00:00,12896.0 -2011-06-19 15:00:00,13247.0 -2011-06-19 16:00:00,13525.0 -2011-06-19 17:00:00,13674.0 -2011-06-19 18:00:00,13692.0 -2011-06-19 19:00:00,13714.0 -2011-06-19 20:00:00,13555.0 -2011-06-19 21:00:00,13308.0 -2011-06-19 22:00:00,13310.0 -2011-06-19 23:00:00,13417.0 -2011-06-20 00:00:00,12729.0 -2011-06-18 01:00:00,11166.0 -2011-06-18 02:00:00,10311.0 -2011-06-18 03:00:00,9736.0 -2011-06-18 04:00:00,9315.0 -2011-06-18 05:00:00,9076.0 -2011-06-18 06:00:00,8947.0 -2011-06-18 07:00:00,8895.0 -2011-06-18 08:00:00,9093.0 -2011-06-18 09:00:00,9835.0 -2011-06-18 10:00:00,10751.0 -2011-06-18 11:00:00,11774.0 -2011-06-18 12:00:00,12742.0 -2011-06-18 13:00:00,13389.0 -2011-06-18 14:00:00,13782.0 -2011-06-18 15:00:00,13999.0 -2011-06-18 16:00:00,14297.0 -2011-06-18 17:00:00,14473.0 -2011-06-18 18:00:00,14444.0 -2011-06-18 19:00:00,13907.0 -2011-06-18 20:00:00,13321.0 -2011-06-18 21:00:00,12847.0 -2011-06-18 22:00:00,12745.0 -2011-06-18 23:00:00,12583.0 -2011-06-19 00:00:00,11854.0 -2011-06-17 01:00:00,11021.0 -2011-06-17 02:00:00,10158.0 -2011-06-17 03:00:00,9623.0 -2011-06-17 04:00:00,9278.0 -2011-06-17 05:00:00,9115.0 -2011-06-17 06:00:00,9195.0 -2011-06-17 07:00:00,9499.0 -2011-06-17 08:00:00,10491.0 -2011-06-17 09:00:00,11673.0 -2011-06-17 10:00:00,12673.0 -2011-06-17 11:00:00,13493.0 -2011-06-17 12:00:00,14262.0 -2011-06-17 13:00:00,14821.0 -2011-06-17 14:00:00,15232.0 -2011-06-17 15:00:00,15629.0 -2011-06-17 16:00:00,15824.0 -2011-06-17 17:00:00,15868.0 -2011-06-17 18:00:00,15733.0 -2011-06-17 19:00:00,15293.0 -2011-06-17 20:00:00,14443.0 -2011-06-17 21:00:00,13675.0 -2011-06-17 22:00:00,13456.0 -2011-06-17 23:00:00,13146.0 -2011-06-18 00:00:00,12169.0 -2011-06-16 01:00:00,10240.0 -2011-06-16 02:00:00,9560.0 -2011-06-16 03:00:00,9119.0 -2011-06-16 04:00:00,8834.0 -2011-06-16 05:00:00,8724.0 -2011-06-16 06:00:00,8865.0 -2011-06-16 07:00:00,9327.0 -2011-06-16 08:00:00,10223.0 -2011-06-16 09:00:00,11069.0 -2011-06-16 10:00:00,11761.0 -2011-06-16 11:00:00,12366.0 -2011-06-16 12:00:00,12854.0 -2011-06-16 13:00:00,13107.0 -2011-06-16 14:00:00,13341.0 -2011-06-16 15:00:00,13679.0 -2011-06-16 16:00:00,13845.0 -2011-06-16 17:00:00,13975.0 -2011-06-16 18:00:00,14037.0 -2011-06-16 19:00:00,13925.0 -2011-06-16 20:00:00,13575.0 -2011-06-16 21:00:00,13254.0 -2011-06-16 22:00:00,13137.0 -2011-06-16 23:00:00,13014.0 -2011-06-17 00:00:00,12090.0 -2011-06-15 01:00:00,10171.0 -2011-06-15 02:00:00,9527.0 -2011-06-15 03:00:00,9096.0 -2011-06-15 04:00:00,8775.0 -2011-06-15 05:00:00,8669.0 -2011-06-15 06:00:00,8797.0 -2011-06-15 07:00:00,9329.0 -2011-06-15 08:00:00,10161.0 -2011-06-15 09:00:00,10826.0 -2011-06-15 10:00:00,11334.0 -2011-06-15 11:00:00,11800.0 -2011-06-15 12:00:00,12244.0 -2011-06-15 13:00:00,12527.0 -2011-06-15 14:00:00,12619.0 -2011-06-15 15:00:00,12687.0 -2011-06-15 16:00:00,12573.0 -2011-06-15 17:00:00,12438.0 -2011-06-15 18:00:00,12337.0 -2011-06-15 19:00:00,12243.0 -2011-06-15 20:00:00,11974.0 -2011-06-15 21:00:00,11903.0 -2011-06-15 22:00:00,12078.0 -2011-06-15 23:00:00,11932.0 -2011-06-16 00:00:00,11139.0 -2011-06-14 01:00:00,9683.0 -2011-06-14 02:00:00,9045.0 -2011-06-14 03:00:00,8638.0 -2011-06-14 04:00:00,8380.0 -2011-06-14 05:00:00,8289.0 -2011-06-14 06:00:00,8442.0 -2011-06-14 07:00:00,8775.0 -2011-06-14 08:00:00,9655.0 -2011-06-14 09:00:00,10576.0 -2011-06-14 10:00:00,11266.0 -2011-06-14 11:00:00,11662.0 -2011-06-14 12:00:00,12096.0 -2011-06-14 13:00:00,12397.0 -2011-06-14 14:00:00,12595.0 -2011-06-14 15:00:00,12863.0 -2011-06-14 16:00:00,12995.0 -2011-06-14 17:00:00,12981.0 -2011-06-14 18:00:00,12808.0 -2011-06-14 19:00:00,12517.0 -2011-06-14 20:00:00,12068.0 -2011-06-14 21:00:00,11791.0 -2011-06-14 22:00:00,12010.0 -2011-06-14 23:00:00,11891.0 -2011-06-15 00:00:00,11082.0 -2011-06-13 01:00:00,8795.0 -2011-06-13 02:00:00,8357.0 -2011-06-13 03:00:00,8065.0 -2011-06-13 04:00:00,7922.0 -2011-06-13 05:00:00,7892.0 -2011-06-13 06:00:00,8169.0 -2011-06-13 07:00:00,8641.0 -2011-06-13 08:00:00,9452.0 -2011-06-13 09:00:00,10451.0 -2011-06-13 10:00:00,11110.0 -2011-06-13 11:00:00,11630.0 -2011-06-13 12:00:00,12001.0 -2011-06-13 13:00:00,12157.0 -2011-06-13 14:00:00,12275.0 -2011-06-13 15:00:00,12439.0 -2011-06-13 16:00:00,12449.0 -2011-06-13 17:00:00,12425.0 -2011-06-13 18:00:00,12342.0 -2011-06-13 19:00:00,12118.0 -2011-06-13 20:00:00,11644.0 -2011-06-13 21:00:00,11334.0 -2011-06-13 22:00:00,11521.0 -2011-06-13 23:00:00,11425.0 -2011-06-14 00:00:00,10596.0 -2011-06-12 01:00:00,9038.0 -2011-06-12 02:00:00,8501.0 -2011-06-12 03:00:00,8193.0 -2011-06-12 04:00:00,7909.0 -2011-06-12 05:00:00,7774.0 -2011-06-12 06:00:00,7757.0 -2011-06-12 07:00:00,7583.0 -2011-06-12 08:00:00,7584.0 -2011-06-12 09:00:00,7857.0 -2011-06-12 10:00:00,8368.0 -2011-06-12 11:00:00,8811.0 -2011-06-12 12:00:00,9139.0 -2011-06-12 13:00:00,9256.0 -2011-06-12 14:00:00,9327.0 -2011-06-12 15:00:00,9316.0 -2011-06-12 16:00:00,9336.0 -2011-06-12 17:00:00,9325.0 -2011-06-12 18:00:00,9381.0 -2011-06-12 19:00:00,9440.0 -2011-06-12 20:00:00,9394.0 -2011-06-12 21:00:00,9416.0 -2011-06-12 22:00:00,9812.0 -2011-06-12 23:00:00,9868.0 -2011-06-13 00:00:00,9412.0 -2011-06-11 01:00:00,9726.0 -2011-06-11 02:00:00,9065.0 -2011-06-11 03:00:00,8639.0 -2011-06-11 04:00:00,8388.0 -2011-06-11 05:00:00,8243.0 -2011-06-11 06:00:00,8301.0 -2011-06-11 07:00:00,8346.0 -2011-06-11 08:00:00,8591.0 -2011-06-11 09:00:00,9044.0 -2011-06-11 10:00:00,9656.0 -2011-06-11 11:00:00,10043.0 -2011-06-11 12:00:00,10366.0 -2011-06-11 13:00:00,10324.0 -2011-06-11 14:00:00,10265.0 -2011-06-11 15:00:00,10163.0 -2011-06-11 16:00:00,10055.0 -2011-06-11 17:00:00,9996.0 -2011-06-11 18:00:00,9918.0 -2011-06-11 19:00:00,9813.0 -2011-06-11 20:00:00,9761.0 -2011-06-11 21:00:00,9779.0 -2011-06-11 22:00:00,10082.0 -2011-06-11 23:00:00,10079.0 -2011-06-12 00:00:00,9587.0 -2011-06-10 01:00:00,9533.0 -2011-06-10 02:00:00,8943.0 -2011-06-10 03:00:00,8572.0 -2011-06-10 04:00:00,8341.0 -2011-06-10 05:00:00,8215.0 -2011-06-10 06:00:00,8435.0 -2011-06-10 07:00:00,8857.0 -2011-06-10 08:00:00,9580.0 -2011-06-10 09:00:00,10466.0 -2011-06-10 10:00:00,11004.0 -2011-06-10 11:00:00,11408.0 -2011-06-10 12:00:00,11783.0 -2011-06-10 13:00:00,12033.0 -2011-06-10 14:00:00,12108.0 -2011-06-10 15:00:00,12210.0 -2011-06-10 16:00:00,12191.0 -2011-06-10 17:00:00,12112.0 -2011-06-10 18:00:00,11980.0 -2011-06-10 19:00:00,11686.0 -2011-06-10 20:00:00,11331.0 -2011-06-10 21:00:00,11196.0 -2011-06-10 22:00:00,11350.0 -2011-06-10 23:00:00,11270.0 -2011-06-11 00:00:00,10556.0 -2011-06-09 01:00:00,14194.0 -2011-06-09 02:00:00,12627.0 -2011-06-09 03:00:00,11723.0 -2011-06-09 04:00:00,11120.0 -2011-06-09 05:00:00,10661.0 -2011-06-09 06:00:00,10639.0 -2011-06-09 07:00:00,11131.0 -2011-06-09 08:00:00,12069.0 -2011-06-09 09:00:00,12958.0 -2011-06-09 10:00:00,13359.0 -2011-06-09 11:00:00,13269.0 -2011-06-09 12:00:00,13153.0 -2011-06-09 13:00:00,12956.0 -2011-06-09 14:00:00,12715.0 -2011-06-09 15:00:00,12535.0 -2011-06-09 16:00:00,12223.0 -2011-06-09 17:00:00,11909.0 -2011-06-09 18:00:00,11659.0 -2011-06-09 19:00:00,11388.0 -2011-06-09 20:00:00,11091.0 -2011-06-09 21:00:00,10966.0 -2011-06-09 22:00:00,11208.0 -2011-06-09 23:00:00,11075.0 -2011-06-10 00:00:00,10380.0 -2011-06-08 01:00:00,15690.0 -2011-06-08 02:00:00,14493.0 -2011-06-08 03:00:00,13656.0 -2011-06-08 04:00:00,13069.0 -2011-06-08 05:00:00,12727.0 -2011-06-08 06:00:00,12737.0 -2011-06-08 07:00:00,13101.0 -2011-06-08 08:00:00,14162.0 -2011-06-08 09:00:00,15637.0 -2011-06-08 10:00:00,16915.0 -2011-06-08 11:00:00,18064.0 -2011-06-08 12:00:00,19190.0 -2011-06-08 13:00:00,19908.0 -2011-06-08 14:00:00,20328.0 -2011-06-08 15:00:00,20707.0 -2011-06-08 16:00:00,21036.0 -2011-06-08 17:00:00,21236.0 -2011-06-08 18:00:00,21174.0 -2011-06-08 19:00:00,20819.0 -2011-06-08 20:00:00,20033.0 -2011-06-08 21:00:00,19268.0 -2011-06-08 22:00:00,19079.0 -2011-06-08 23:00:00,18361.0 -2011-06-09 00:00:00,16400.0 -2011-06-07 01:00:00,14996.0 -2011-06-07 02:00:00,13851.0 -2011-06-07 03:00:00,13007.0 -2011-06-07 04:00:00,12388.0 -2011-06-07 05:00:00,12046.0 -2011-06-07 06:00:00,12040.0 -2011-06-07 07:00:00,12426.0 -2011-06-07 08:00:00,13607.0 -2011-06-07 09:00:00,15152.0 -2011-06-07 10:00:00,16511.0 -2011-06-07 11:00:00,17573.0 -2011-06-07 12:00:00,18667.0 -2011-06-07 13:00:00,19608.0 -2011-06-07 14:00:00,20330.0 -2011-06-07 15:00:00,20722.0 -2011-06-07 16:00:00,21429.0 -2011-06-07 17:00:00,21423.0 -2011-06-07 18:00:00,21455.0 -2011-06-07 19:00:00,21218.0 -2011-06-07 20:00:00,20660.0 -2011-06-07 21:00:00,19600.0 -2011-06-07 22:00:00,19201.0 -2011-06-07 23:00:00,18713.0 -2011-06-08 00:00:00,17202.0 -2011-06-06 01:00:00,10123.0 -2011-06-06 02:00:00,9468.0 -2011-06-06 03:00:00,9023.0 -2011-06-06 04:00:00,8790.0 -2011-06-06 05:00:00,8718.0 -2011-06-06 06:00:00,8980.0 -2011-06-06 07:00:00,9424.0 -2011-06-06 08:00:00,10631.0 -2011-06-06 09:00:00,11907.0 -2011-06-06 10:00:00,12816.0 -2011-06-06 11:00:00,13441.0 -2011-06-06 12:00:00,14231.0 -2011-06-06 13:00:00,15057.0 -2011-06-06 14:00:00,16200.0 -2011-06-06 15:00:00,17245.0 -2011-06-06 16:00:00,18124.0 -2011-06-06 17:00:00,18810.0 -2011-06-06 18:00:00,19287.0 -2011-06-06 19:00:00,19375.0 -2011-06-06 20:00:00,19074.0 -2011-06-06 21:00:00,18573.0 -2011-06-06 22:00:00,18358.0 -2011-06-06 23:00:00,17972.0 -2011-06-07 00:00:00,16559.0 -2011-06-05 01:00:00,11140.0 -2011-06-05 02:00:00,10245.0 -2011-06-05 03:00:00,9663.0 -2011-06-05 04:00:00,9200.0 -2011-06-05 05:00:00,8943.0 -2011-06-05 06:00:00,8814.0 -2011-06-05 07:00:00,8531.0 -2011-06-05 08:00:00,8710.0 -2011-06-05 09:00:00,9213.0 -2011-06-05 10:00:00,9797.0 -2011-06-05 11:00:00,10318.0 -2011-06-05 12:00:00,10832.0 -2011-06-05 13:00:00,11516.0 -2011-06-05 14:00:00,12212.0 -2011-06-05 15:00:00,12681.0 -2011-06-05 16:00:00,13074.0 -2011-06-05 17:00:00,13423.0 -2011-06-05 18:00:00,13686.0 -2011-06-05 19:00:00,13663.0 -2011-06-05 20:00:00,13299.0 -2011-06-05 21:00:00,12704.0 -2011-06-05 22:00:00,12338.0 -2011-06-05 23:00:00,12028.0 -2011-06-06 00:00:00,11093.0 -2011-06-04 01:00:00,13157.0 -2011-06-04 02:00:00,12136.0 -2011-06-04 03:00:00,11352.0 -2011-06-04 04:00:00,10815.0 -2011-06-04 05:00:00,10475.0 -2011-06-04 06:00:00,10372.0 -2011-06-04 07:00:00,10347.0 -2011-06-04 08:00:00,10839.0 -2011-06-04 09:00:00,11989.0 -2011-06-04 10:00:00,13430.0 -2011-06-04 11:00:00,14804.0 -2011-06-04 12:00:00,15873.0 -2011-06-04 13:00:00,16624.0 -2011-06-04 14:00:00,17062.0 -2011-06-04 15:00:00,17272.0 -2011-06-04 16:00:00,17154.0 -2011-06-04 17:00:00,16392.0 -2011-06-04 18:00:00,15302.0 -2011-06-04 19:00:00,14651.0 -2011-06-04 20:00:00,14253.0 -2011-06-04 21:00:00,13560.0 -2011-06-04 22:00:00,13207.0 -2011-06-04 23:00:00,12968.0 -2011-06-05 00:00:00,12066.0 -2011-06-03 01:00:00,9798.0 -2011-06-03 02:00:00,9125.0 -2011-06-03 03:00:00,8764.0 -2011-06-03 04:00:00,8524.0 -2011-06-03 05:00:00,8492.0 -2011-06-03 06:00:00,8697.0 -2011-06-03 07:00:00,9174.0 -2011-06-03 08:00:00,10195.0 -2011-06-03 09:00:00,11197.0 -2011-06-03 10:00:00,11968.0 -2011-06-03 11:00:00,12579.0 -2011-06-03 12:00:00,13217.0 -2011-06-03 13:00:00,13786.0 -2011-06-03 14:00:00,14361.0 -2011-06-03 15:00:00,15060.0 -2011-06-03 16:00:00,15631.0 -2011-06-03 17:00:00,16134.0 -2011-06-03 18:00:00,16446.0 -2011-06-03 19:00:00,16454.0 -2011-06-03 20:00:00,16104.0 -2011-06-03 21:00:00,15652.0 -2011-06-03 22:00:00,15523.0 -2011-06-03 23:00:00,15343.0 -2011-06-04 00:00:00,14316.0 -2011-06-02 01:00:00,11020.0 -2011-06-02 02:00:00,10146.0 -2011-06-02 03:00:00,9520.0 -2011-06-02 04:00:00,9147.0 -2011-06-02 05:00:00,8883.0 -2011-06-02 06:00:00,8919.0 -2011-06-02 07:00:00,9132.0 -2011-06-02 08:00:00,10003.0 -2011-06-02 09:00:00,10945.0 -2011-06-02 10:00:00,11465.0 -2011-06-02 11:00:00,11790.0 -2011-06-02 12:00:00,12062.0 -2011-06-02 13:00:00,12270.0 -2011-06-02 14:00:00,12426.0 -2011-06-02 15:00:00,12596.0 -2011-06-02 16:00:00,12611.0 -2011-06-02 17:00:00,12599.0 -2011-06-02 18:00:00,12500.0 -2011-06-02 19:00:00,12152.0 -2011-06-02 20:00:00,11673.0 -2011-06-02 21:00:00,11489.0 -2011-06-02 22:00:00,11768.0 -2011-06-02 23:00:00,11565.0 -2011-06-03 00:00:00,10727.0 -2011-06-01 01:00:00,11714.0 -2011-06-01 02:00:00,10886.0 -2011-06-01 03:00:00,10325.0 -2011-06-01 04:00:00,9773.0 -2011-06-01 05:00:00,9330.0 -2011-06-01 06:00:00,9347.0 -2011-06-01 07:00:00,9644.0 -2011-06-01 08:00:00,10627.0 -2011-06-01 09:00:00,11710.0 -2011-06-01 10:00:00,12365.0 -2011-06-01 11:00:00,12871.0 -2011-06-01 12:00:00,13300.0 -2011-06-01 13:00:00,13592.0 -2011-06-01 14:00:00,13820.0 -2011-06-01 15:00:00,14106.0 -2011-06-01 16:00:00,14288.0 -2011-06-01 17:00:00,14521.0 -2011-06-01 18:00:00,14690.0 -2011-06-01 19:00:00,14628.0 -2011-06-01 20:00:00,14250.0 -2011-06-01 21:00:00,13742.0 -2011-06-01 22:00:00,13612.0 -2011-06-01 23:00:00,13347.0 -2011-06-02 00:00:00,12225.0 -2011-05-31 01:00:00,11692.0 -2011-05-31 02:00:00,10897.0 -2011-05-31 03:00:00,10328.0 -2011-05-31 04:00:00,9972.0 -2011-05-31 05:00:00,9796.0 -2011-05-31 06:00:00,9950.0 -2011-05-31 07:00:00,10407.0 -2011-05-31 08:00:00,11664.0 -2011-05-31 09:00:00,13261.0 -2011-05-31 10:00:00,14448.0 -2011-05-31 11:00:00,15194.0 -2011-05-31 12:00:00,15868.0 -2011-05-31 13:00:00,16224.0 -2011-05-31 14:00:00,16593.0 -2011-05-31 15:00:00,16753.0 -2011-05-31 16:00:00,16740.0 -2011-05-31 17:00:00,16324.0 -2011-05-31 18:00:00,16076.0 -2011-05-31 19:00:00,15862.0 -2011-05-31 20:00:00,15614.0 -2011-05-31 21:00:00,15162.0 -2011-05-31 22:00:00,14843.0 -2011-05-31 23:00:00,14397.0 -2011-06-01 00:00:00,13058.0 -2011-05-30 01:00:00,8938.0 -2011-05-30 02:00:00,8419.0 -2011-05-30 03:00:00,8072.0 -2011-05-30 04:00:00,7862.0 -2011-05-30 05:00:00,7758.0 -2011-05-30 06:00:00,7817.0 -2011-05-30 07:00:00,7786.0 -2011-05-30 08:00:00,7970.0 -2011-05-30 09:00:00,8462.0 -2011-05-30 10:00:00,9207.0 -2011-05-30 11:00:00,9992.0 -2011-05-30 12:00:00,10772.0 -2011-05-30 13:00:00,11465.0 -2011-05-30 14:00:00,11989.0 -2011-05-30 15:00:00,12471.0 -2011-05-30 16:00:00,12918.0 -2011-05-30 17:00:00,13315.0 -2011-05-30 18:00:00,13649.0 -2011-05-30 19:00:00,13835.0 -2011-05-30 20:00:00,13740.0 -2011-05-30 21:00:00,13493.0 -2011-05-30 22:00:00,13684.0 -2011-05-30 23:00:00,13608.0 -2011-05-31 00:00:00,12730.0 -2011-05-29 01:00:00,8730.0 -2011-05-29 02:00:00,8232.0 -2011-05-29 03:00:00,7912.0 -2011-05-29 04:00:00,7687.0 -2011-05-29 05:00:00,7544.0 -2011-05-29 06:00:00,7611.0 -2011-05-29 07:00:00,7504.0 -2011-05-29 08:00:00,7533.0 -2011-05-29 09:00:00,7859.0 -2011-05-29 10:00:00,8319.0 -2011-05-29 11:00:00,8734.0 -2011-05-29 12:00:00,9068.0 -2011-05-29 13:00:00,9593.0 -2011-05-29 14:00:00,9809.0 -2011-05-29 15:00:00,9931.0 -2011-05-29 16:00:00,9745.0 -2011-05-29 17:00:00,9491.0 -2011-05-29 18:00:00,9366.0 -2011-05-29 19:00:00,9329.0 -2011-05-29 20:00:00,9293.0 -2011-05-29 21:00:00,9362.0 -2011-05-29 22:00:00,9758.0 -2011-05-29 23:00:00,9897.0 -2011-05-30 00:00:00,9493.0 -2011-05-28 01:00:00,9099.0 -2011-05-28 02:00:00,8549.0 -2011-05-28 03:00:00,8180.0 -2011-05-28 04:00:00,7969.0 -2011-05-28 05:00:00,7849.0 -2011-05-28 06:00:00,7881.0 -2011-05-28 07:00:00,7899.0 -2011-05-28 08:00:00,8047.0 -2011-05-28 09:00:00,8496.0 -2011-05-28 10:00:00,9024.0 -2011-05-28 11:00:00,9398.0 -2011-05-28 12:00:00,9645.0 -2011-05-28 13:00:00,9671.0 -2011-05-28 14:00:00,9694.0 -2011-05-28 15:00:00,9579.0 -2011-05-28 16:00:00,9585.0 -2011-05-28 17:00:00,9542.0 -2011-05-28 18:00:00,9599.0 -2011-05-28 19:00:00,9615.0 -2011-05-28 20:00:00,9509.0 -2011-05-28 21:00:00,9565.0 -2011-05-28 22:00:00,9882.0 -2011-05-28 23:00:00,9756.0 -2011-05-29 00:00:00,9273.0 -2011-05-27 01:00:00,9690.0 -2011-05-27 02:00:00,9109.0 -2011-05-27 03:00:00,8801.0 -2011-05-27 04:00:00,8573.0 -2011-05-27 05:00:00,8519.0 -2011-05-27 06:00:00,8707.0 -2011-05-27 07:00:00,9129.0 -2011-05-27 08:00:00,9919.0 -2011-05-27 09:00:00,10688.0 -2011-05-27 10:00:00,11039.0 -2011-05-27 11:00:00,11126.0 -2011-05-27 12:00:00,11242.0 -2011-05-27 13:00:00,11228.0 -2011-05-27 14:00:00,11225.0 -2011-05-27 15:00:00,11224.0 -2011-05-27 16:00:00,11078.0 -2011-05-27 17:00:00,10891.0 -2011-05-27 18:00:00,10695.0 -2011-05-27 19:00:00,10562.0 -2011-05-27 20:00:00,10341.0 -2011-05-27 21:00:00,10410.0 -2011-05-27 22:00:00,10681.0 -2011-05-27 23:00:00,10504.0 -2011-05-28 00:00:00,9857.0 -2011-05-26 01:00:00,9527.0 -2011-05-26 02:00:00,8961.0 -2011-05-26 03:00:00,8626.0 -2011-05-26 04:00:00,8454.0 -2011-05-26 05:00:00,8426.0 -2011-05-26 06:00:00,8619.0 -2011-05-26 07:00:00,9194.0 -2011-05-26 08:00:00,10115.0 -2011-05-26 09:00:00,10958.0 -2011-05-26 10:00:00,11405.0 -2011-05-26 11:00:00,11597.0 -2011-05-26 12:00:00,11778.0 -2011-05-26 13:00:00,11778.0 -2011-05-26 14:00:00,11737.0 -2011-05-26 15:00:00,11710.0 -2011-05-26 16:00:00,11534.0 -2011-05-26 17:00:00,11410.0 -2011-05-26 18:00:00,11352.0 -2011-05-26 19:00:00,11228.0 -2011-05-26 20:00:00,11064.0 -2011-05-26 21:00:00,11126.0 -2011-05-26 22:00:00,11381.0 -2011-05-26 23:00:00,11276.0 -2011-05-27 00:00:00,10490.0 -2011-05-25 01:00:00,9318.0 -2011-05-25 02:00:00,8752.0 -2011-05-25 03:00:00,8408.0 -2011-05-25 04:00:00,8201.0 -2011-05-25 05:00:00,8152.0 -2011-05-25 06:00:00,8321.0 -2011-05-25 07:00:00,8822.0 -2011-05-25 08:00:00,9794.0 -2011-05-25 09:00:00,10998.0 -2011-05-25 10:00:00,11648.0 -2011-05-25 11:00:00,11931.0 -2011-05-25 12:00:00,12003.0 -2011-05-25 13:00:00,11980.0 -2011-05-25 14:00:00,11997.0 -2011-05-25 15:00:00,12128.0 -2011-05-25 16:00:00,12089.0 -2011-05-25 17:00:00,12041.0 -2011-05-25 18:00:00,11946.0 -2011-05-25 19:00:00,11678.0 -2011-05-25 20:00:00,11288.0 -2011-05-25 21:00:00,11277.0 -2011-05-25 22:00:00,11541.0 -2011-05-25 23:00:00,11182.0 -2011-05-26 00:00:00,10371.0 -2011-05-24 01:00:00,10556.0 -2011-05-24 02:00:00,9790.0 -2011-05-24 03:00:00,9285.0 -2011-05-24 04:00:00,8923.0 -2011-05-24 05:00:00,8774.0 -2011-05-24 06:00:00,8860.0 -2011-05-24 07:00:00,9183.0 -2011-05-24 08:00:00,10068.0 -2011-05-24 09:00:00,11063.0 -2011-05-24 10:00:00,11564.0 -2011-05-24 11:00:00,11867.0 -2011-05-24 12:00:00,12079.0 -2011-05-24 13:00:00,12167.0 -2011-05-24 14:00:00,12143.0 -2011-05-24 15:00:00,12144.0 -2011-05-24 16:00:00,11995.0 -2011-05-24 17:00:00,11849.0 -2011-05-24 18:00:00,11666.0 -2011-05-24 19:00:00,11365.0 -2011-05-24 20:00:00,10955.0 -2011-05-24 21:00:00,10813.0 -2011-05-24 22:00:00,11184.0 -2011-05-24 23:00:00,10927.0 -2011-05-25 00:00:00,10143.0 -2011-05-23 01:00:00,9658.0 -2011-05-23 02:00:00,9067.0 -2011-05-23 03:00:00,8690.0 -2011-05-23 04:00:00,8475.0 -2011-05-23 05:00:00,8521.0 -2011-05-23 06:00:00,8709.0 -2011-05-23 07:00:00,9264.0 -2011-05-23 08:00:00,10289.0 -2011-05-23 09:00:00,11487.0 -2011-05-23 10:00:00,12074.0 -2011-05-23 11:00:00,12460.0 -2011-05-23 12:00:00,12961.0 -2011-05-23 13:00:00,13328.0 -2011-05-23 14:00:00,13516.0 -2011-05-23 15:00:00,13735.0 -2011-05-23 16:00:00,13821.0 -2011-05-23 17:00:00,13836.0 -2011-05-23 18:00:00,13788.0 -2011-05-23 19:00:00,13505.0 -2011-05-23 20:00:00,12970.0 -2011-05-23 21:00:00,12631.0 -2011-05-23 22:00:00,12859.0 -2011-05-23 23:00:00,12626.0 -2011-05-24 00:00:00,11638.0 -2011-05-22 01:00:00,9532.0 -2011-05-22 02:00:00,8906.0 -2011-05-22 03:00:00,8511.0 -2011-05-22 04:00:00,8183.0 -2011-05-22 05:00:00,8065.0 -2011-05-22 06:00:00,7909.0 -2011-05-22 07:00:00,7816.0 -2011-05-22 08:00:00,7859.0 -2011-05-22 09:00:00,8318.0 -2011-05-22 10:00:00,8960.0 -2011-05-22 11:00:00,9548.0 -2011-05-22 12:00:00,10132.0 -2011-05-22 13:00:00,10698.0 -2011-05-22 14:00:00,11131.0 -2011-05-22 15:00:00,11571.0 -2011-05-22 16:00:00,11878.0 -2011-05-22 17:00:00,12039.0 -2011-05-22 18:00:00,11933.0 -2011-05-22 19:00:00,11557.0 -2011-05-22 20:00:00,11102.0 -2011-05-22 21:00:00,10900.0 -2011-05-22 22:00:00,11154.0 -2011-05-22 23:00:00,11016.0 -2011-05-23 00:00:00,10486.0 -2011-05-21 01:00:00,9565.0 -2011-05-21 02:00:00,8924.0 -2011-05-21 03:00:00,8531.0 -2011-05-21 04:00:00,8247.0 -2011-05-21 05:00:00,8085.0 -2011-05-21 06:00:00,8149.0 -2011-05-21 07:00:00,8158.0 -2011-05-21 08:00:00,8388.0 -2011-05-21 09:00:00,8943.0 -2011-05-21 10:00:00,9571.0 -2011-05-21 11:00:00,10043.0 -2011-05-21 12:00:00,10354.0 -2011-05-21 13:00:00,10446.0 -2011-05-21 14:00:00,10499.0 -2011-05-21 15:00:00,10373.0 -2011-05-21 16:00:00,10284.0 -2011-05-21 17:00:00,10252.0 -2011-05-21 18:00:00,10333.0 -2011-05-21 19:00:00,10342.0 -2011-05-21 20:00:00,10333.0 -2011-05-21 21:00:00,10302.0 -2011-05-21 22:00:00,10698.0 -2011-05-21 23:00:00,10630.0 -2011-05-22 00:00:00,10135.0 -2011-05-20 01:00:00,9516.0 -2011-05-20 02:00:00,8972.0 -2011-05-20 03:00:00,8575.0 -2011-05-20 04:00:00,8359.0 -2011-05-20 05:00:00,8307.0 -2011-05-20 06:00:00,8423.0 -2011-05-20 07:00:00,8854.0 -2011-05-20 08:00:00,9682.0 -2011-05-20 09:00:00,10676.0 -2011-05-20 10:00:00,11189.0 -2011-05-20 11:00:00,11609.0 -2011-05-20 12:00:00,11963.0 -2011-05-20 13:00:00,12064.0 -2011-05-20 14:00:00,12140.0 -2011-05-20 15:00:00,12302.0 -2011-05-20 16:00:00,12339.0 -2011-05-20 17:00:00,12289.0 -2011-05-20 18:00:00,12156.0 -2011-05-20 19:00:00,11880.0 -2011-05-20 20:00:00,11429.0 -2011-05-20 21:00:00,11177.0 -2011-05-20 22:00:00,11442.0 -2011-05-20 23:00:00,11232.0 -2011-05-21 00:00:00,10458.0 -2011-05-19 01:00:00,9371.0 -2011-05-19 02:00:00,8819.0 -2011-05-19 03:00:00,8484.0 -2011-05-19 04:00:00,8312.0 -2011-05-19 05:00:00,8252.0 -2011-05-19 06:00:00,8442.0 -2011-05-19 07:00:00,8980.0 -2011-05-19 08:00:00,9885.0 -2011-05-19 09:00:00,10693.0 -2011-05-19 10:00:00,11111.0 -2011-05-19 11:00:00,11381.0 -2011-05-19 12:00:00,11627.0 -2011-05-19 13:00:00,11735.0 -2011-05-19 14:00:00,11784.0 -2011-05-19 15:00:00,11883.0 -2011-05-19 16:00:00,11890.0 -2011-05-19 17:00:00,11828.0 -2011-05-19 18:00:00,11697.0 -2011-05-19 19:00:00,11495.0 -2011-05-19 20:00:00,11227.0 -2011-05-19 21:00:00,11135.0 -2011-05-19 22:00:00,11486.0 -2011-05-19 23:00:00,11267.0 -2011-05-20 00:00:00,10406.0 -2011-05-18 01:00:00,9336.0 -2011-05-18 02:00:00,8792.0 -2011-05-18 03:00:00,8466.0 -2011-05-18 04:00:00,8267.0 -2011-05-18 05:00:00,8230.0 -2011-05-18 06:00:00,8420.0 -2011-05-18 07:00:00,8968.0 -2011-05-18 08:00:00,9843.0 -2011-05-18 09:00:00,10687.0 -2011-05-18 10:00:00,11128.0 -2011-05-18 11:00:00,11333.0 -2011-05-18 12:00:00,11458.0 -2011-05-18 13:00:00,11500.0 -2011-05-18 14:00:00,11464.0 -2011-05-18 15:00:00,11490.0 -2011-05-18 16:00:00,11380.0 -2011-05-18 17:00:00,11268.0 -2011-05-18 18:00:00,11130.0 -2011-05-18 19:00:00,10978.0 -2011-05-18 20:00:00,10786.0 -2011-05-18 21:00:00,10862.0 -2011-05-18 22:00:00,11285.0 -2011-05-18 23:00:00,10994.0 -2011-05-19 00:00:00,10188.0 -2011-05-17 01:00:00,9438.0 -2011-05-17 02:00:00,8960.0 -2011-05-17 03:00:00,8667.0 -2011-05-17 04:00:00,8502.0 -2011-05-17 05:00:00,8486.0 -2011-05-17 06:00:00,8710.0 -2011-05-17 07:00:00,9238.0 -2011-05-17 08:00:00,10092.0 -2011-05-17 09:00:00,10858.0 -2011-05-17 10:00:00,11172.0 -2011-05-17 11:00:00,11297.0 -2011-05-17 12:00:00,11433.0 -2011-05-17 13:00:00,11405.0 -2011-05-17 14:00:00,11393.0 -2011-05-17 15:00:00,11406.0 -2011-05-17 16:00:00,11282.0 -2011-05-17 17:00:00,11144.0 -2011-05-17 18:00:00,11002.0 -2011-05-17 19:00:00,10806.0 -2011-05-17 20:00:00,10577.0 -2011-05-17 21:00:00,10552.0 -2011-05-17 22:00:00,11120.0 -2011-05-17 23:00:00,10965.0 -2011-05-18 00:00:00,10152.0 -2011-05-16 01:00:00,8957.0 -2011-05-16 02:00:00,8560.0 -2011-05-16 03:00:00,8354.0 -2011-05-16 04:00:00,8229.0 -2011-05-16 05:00:00,8269.0 -2011-05-16 06:00:00,8542.0 -2011-05-16 07:00:00,9170.0 -2011-05-16 08:00:00,10020.0 -2011-05-16 09:00:00,10843.0 -2011-05-16 10:00:00,11190.0 -2011-05-16 11:00:00,11330.0 -2011-05-16 12:00:00,11425.0 -2011-05-16 13:00:00,11406.0 -2011-05-16 14:00:00,11371.0 -2011-05-16 15:00:00,11350.0 -2011-05-16 16:00:00,11202.0 -2011-05-16 17:00:00,11058.0 -2011-05-16 18:00:00,10924.0 -2011-05-16 19:00:00,10775.0 -2011-05-16 20:00:00,10561.0 -2011-05-16 21:00:00,10620.0 -2011-05-16 22:00:00,11192.0 -2011-05-16 23:00:00,11040.0 -2011-05-17 00:00:00,10242.0 -2011-05-15 01:00:00,8871.0 -2011-05-15 02:00:00,8452.0 -2011-05-15 03:00:00,8141.0 -2011-05-15 04:00:00,7897.0 -2011-05-15 05:00:00,7852.0 -2011-05-15 06:00:00,7804.0 -2011-05-15 07:00:00,7912.0 -2011-05-15 08:00:00,7921.0 -2011-05-15 09:00:00,8248.0 -2011-05-15 10:00:00,8682.0 -2011-05-15 11:00:00,9048.0 -2011-05-15 12:00:00,9309.0 -2011-05-15 13:00:00,9396.0 -2011-05-15 14:00:00,9489.0 -2011-05-15 15:00:00,9432.0 -2011-05-15 16:00:00,9422.0 -2011-05-15 17:00:00,9389.0 -2011-05-15 18:00:00,9435.0 -2011-05-15 19:00:00,9532.0 -2011-05-15 20:00:00,9588.0 -2011-05-15 21:00:00,9765.0 -2011-05-15 22:00:00,10172.0 -2011-05-15 23:00:00,10008.0 -2011-05-16 00:00:00,9497.0 -2011-05-14 01:00:00,9326.0 -2011-05-14 02:00:00,8692.0 -2011-05-14 03:00:00,8353.0 -2011-05-14 04:00:00,8103.0 -2011-05-14 05:00:00,7992.0 -2011-05-14 06:00:00,8000.0 -2011-05-14 07:00:00,8175.0 -2011-05-14 08:00:00,8371.0 -2011-05-14 09:00:00,8856.0 -2011-05-14 10:00:00,9328.0 -2011-05-14 11:00:00,9744.0 -2011-05-14 12:00:00,9911.0 -2011-05-14 13:00:00,9930.0 -2011-05-14 14:00:00,9852.0 -2011-05-14 15:00:00,9735.0 -2011-05-14 16:00:00,9636.0 -2011-05-14 17:00:00,9606.0 -2011-05-14 18:00:00,9626.0 -2011-05-14 19:00:00,9723.0 -2011-05-14 20:00:00,9748.0 -2011-05-14 21:00:00,9921.0 -2011-05-14 22:00:00,10194.0 -2011-05-14 23:00:00,9962.0 -2011-05-15 00:00:00,9444.0 -2011-05-13 01:00:00,11525.0 -2011-05-13 02:00:00,10616.0 -2011-05-13 03:00:00,9999.0 -2011-05-13 04:00:00,9547.0 -2011-05-13 05:00:00,9394.0 -2011-05-13 06:00:00,9423.0 -2011-05-13 07:00:00,9865.0 -2011-05-13 08:00:00,10732.0 -2011-05-13 09:00:00,11944.0 -2011-05-13 10:00:00,12740.0 -2011-05-13 11:00:00,13357.0 -2011-05-13 12:00:00,13626.0 -2011-05-13 13:00:00,13433.0 -2011-05-13 14:00:00,13242.0 -2011-05-13 15:00:00,12878.0 -2011-05-13 16:00:00,12326.0 -2011-05-13 17:00:00,11911.0 -2011-05-13 18:00:00,11544.0 -2011-05-13 19:00:00,11257.0 -2011-05-13 20:00:00,10989.0 -2011-05-13 21:00:00,10929.0 -2011-05-13 22:00:00,11166.0 -2011-05-13 23:00:00,10839.0 -2011-05-14 00:00:00,10081.0 -2011-05-12 01:00:00,11143.0 -2011-05-12 02:00:00,10282.0 -2011-05-12 03:00:00,9688.0 -2011-05-12 04:00:00,9283.0 -2011-05-12 05:00:00,9089.0 -2011-05-12 06:00:00,9178.0 -2011-05-12 07:00:00,9699.0 -2011-05-12 08:00:00,10622.0 -2011-05-12 09:00:00,11650.0 -2011-05-12 10:00:00,12425.0 -2011-05-12 11:00:00,13079.0 -2011-05-12 12:00:00,13647.0 -2011-05-12 13:00:00,14098.0 -2011-05-12 14:00:00,14679.0 -2011-05-12 15:00:00,15278.0 -2011-05-12 16:00:00,15632.0 -2011-05-12 17:00:00,15951.0 -2011-05-12 18:00:00,16049.0 -2011-05-12 19:00:00,15883.0 -2011-05-12 20:00:00,15309.0 -2011-05-12 21:00:00,14855.0 -2011-05-12 22:00:00,14820.0 -2011-05-12 23:00:00,14145.0 -2011-05-13 00:00:00,12843.0 -2011-05-11 01:00:00,11618.0 -2011-05-11 02:00:00,10767.0 -2011-05-11 03:00:00,10175.0 -2011-05-11 04:00:00,9733.0 -2011-05-11 05:00:00,9486.0 -2011-05-11 06:00:00,9573.0 -2011-05-11 07:00:00,10061.0 -2011-05-11 08:00:00,11036.0 -2011-05-11 09:00:00,12254.0 -2011-05-11 10:00:00,13172.0 -2011-05-11 11:00:00,13921.0 -2011-05-11 12:00:00,14705.0 -2011-05-11 13:00:00,15356.0 -2011-05-11 14:00:00,15851.0 -2011-05-11 15:00:00,16127.0 -2011-05-11 16:00:00,16223.0 -2011-05-11 17:00:00,16169.0 -2011-05-11 18:00:00,15926.0 -2011-05-11 19:00:00,15539.0 -2011-05-11 20:00:00,14840.0 -2011-05-11 21:00:00,14377.0 -2011-05-11 22:00:00,14298.0 -2011-05-11 23:00:00,13619.0 -2011-05-12 00:00:00,12359.0 -2011-05-10 01:00:00,9288.0 -2011-05-10 02:00:00,8723.0 -2011-05-10 03:00:00,8373.0 -2011-05-10 04:00:00,8116.0 -2011-05-10 05:00:00,8065.0 -2011-05-10 06:00:00,8235.0 -2011-05-10 07:00:00,8801.0 -2011-05-10 08:00:00,9720.0 -2011-05-10 09:00:00,10680.0 -2011-05-10 10:00:00,11309.0 -2011-05-10 11:00:00,11796.0 -2011-05-10 12:00:00,12321.0 -2011-05-10 13:00:00,12711.0 -2011-05-10 14:00:00,13114.0 -2011-05-10 15:00:00,13524.0 -2011-05-10 16:00:00,13725.0 -2011-05-10 17:00:00,13954.0 -2011-05-10 18:00:00,14077.0 -2011-05-10 19:00:00,14072.0 -2011-05-10 20:00:00,13835.0 -2011-05-10 21:00:00,13732.0 -2011-05-10 22:00:00,14134.0 -2011-05-10 23:00:00,13773.0 -2011-05-11 00:00:00,12722.0 -2011-05-09 01:00:00,8378.0 -2011-05-09 02:00:00,8015.0 -2011-05-09 03:00:00,7787.0 -2011-05-09 04:00:00,7690.0 -2011-05-09 05:00:00,7696.0 -2011-05-09 06:00:00,7957.0 -2011-05-09 07:00:00,8537.0 -2011-05-09 08:00:00,9456.0 -2011-05-09 09:00:00,10391.0 -2011-05-09 10:00:00,10939.0 -2011-05-09 11:00:00,11123.0 -2011-05-09 12:00:00,11354.0 -2011-05-09 13:00:00,11459.0 -2011-05-09 14:00:00,11475.0 -2011-05-09 15:00:00,11531.0 -2011-05-09 16:00:00,11453.0 -2011-05-09 17:00:00,11371.0 -2011-05-09 18:00:00,11211.0 -2011-05-09 19:00:00,11049.0 -2011-05-09 20:00:00,10816.0 -2011-05-09 21:00:00,10944.0 -2011-05-09 22:00:00,11393.0 -2011-05-09 23:00:00,11026.0 -2011-05-10 00:00:00,10138.0 -2011-05-08 01:00:00,8749.0 -2011-05-08 02:00:00,8272.0 -2011-05-08 03:00:00,7982.0 -2011-05-08 04:00:00,7768.0 -2011-05-08 05:00:00,7626.0 -2011-05-08 06:00:00,7698.0 -2011-05-08 07:00:00,7719.0 -2011-05-08 08:00:00,7692.0 -2011-05-08 09:00:00,7932.0 -2011-05-08 10:00:00,8304.0 -2011-05-08 11:00:00,8582.0 -2011-05-08 12:00:00,8743.0 -2011-05-08 13:00:00,8801.0 -2011-05-08 14:00:00,8808.0 -2011-05-08 15:00:00,8759.0 -2011-05-08 16:00:00,8756.0 -2011-05-08 17:00:00,8679.0 -2011-05-08 18:00:00,8696.0 -2011-05-08 19:00:00,8694.0 -2011-05-08 20:00:00,8819.0 -2011-05-08 21:00:00,9034.0 -2011-05-08 22:00:00,9627.0 -2011-05-08 23:00:00,9510.0 -2011-05-09 00:00:00,8998.0 -2011-05-07 01:00:00,9209.0 -2011-05-07 02:00:00,8646.0 -2011-05-07 03:00:00,8206.0 -2011-05-07 04:00:00,8010.0 -2011-05-07 05:00:00,7931.0 -2011-05-07 06:00:00,7928.0 -2011-05-07 07:00:00,8116.0 -2011-05-07 08:00:00,8313.0 -2011-05-07 09:00:00,8754.0 -2011-05-07 10:00:00,9276.0 -2011-05-07 11:00:00,9584.0 -2011-05-07 12:00:00,9762.0 -2011-05-07 13:00:00,9710.0 -2011-05-07 14:00:00,9668.0 -2011-05-07 15:00:00,9508.0 -2011-05-07 16:00:00,9456.0 -2011-05-07 17:00:00,9370.0 -2011-05-07 18:00:00,9326.0 -2011-05-07 19:00:00,9277.0 -2011-05-07 20:00:00,9219.0 -2011-05-07 21:00:00,9312.0 -2011-05-07 22:00:00,9836.0 -2011-05-07 23:00:00,9741.0 -2011-05-08 00:00:00,9324.0 -2011-05-06 01:00:00,9424.0 -2011-05-06 02:00:00,8889.0 -2011-05-06 03:00:00,8551.0 -2011-05-06 04:00:00,8410.0 -2011-05-06 05:00:00,8369.0 -2011-05-06 06:00:00,8473.0 -2011-05-06 07:00:00,8997.0 -2011-05-06 08:00:00,9815.0 -2011-05-06 09:00:00,10559.0 -2011-05-06 10:00:00,10953.0 -2011-05-06 11:00:00,11115.0 -2011-05-06 12:00:00,11275.0 -2011-05-06 13:00:00,11298.0 -2011-05-06 14:00:00,11263.0 -2011-05-06 15:00:00,11267.0 -2011-05-06 16:00:00,11127.0 -2011-05-06 17:00:00,10935.0 -2011-05-06 18:00:00,10762.0 -2011-05-06 19:00:00,10525.0 -2011-05-06 20:00:00,10342.0 -2011-05-06 21:00:00,10429.0 -2011-05-06 22:00:00,10857.0 -2011-05-06 23:00:00,10628.0 -2011-05-07 00:00:00,9911.0 -2011-05-05 01:00:00,9403.0 -2011-05-05 02:00:00,8888.0 -2011-05-05 03:00:00,8565.0 -2011-05-05 04:00:00,8400.0 -2011-05-05 05:00:00,8406.0 -2011-05-05 06:00:00,8645.0 -2011-05-05 07:00:00,9233.0 -2011-05-05 08:00:00,10069.0 -2011-05-05 09:00:00,10835.0 -2011-05-05 10:00:00,11180.0 -2011-05-05 11:00:00,11317.0 -2011-05-05 12:00:00,11439.0 -2011-05-05 13:00:00,11325.0 -2011-05-05 14:00:00,11320.0 -2011-05-05 15:00:00,11330.0 -2011-05-05 16:00:00,11265.0 -2011-05-05 17:00:00,11235.0 -2011-05-05 18:00:00,11089.0 -2011-05-05 19:00:00,10934.0 -2011-05-05 20:00:00,10828.0 -2011-05-05 21:00:00,11079.0 -2011-05-05 22:00:00,11403.0 -2011-05-05 23:00:00,11010.0 -2011-05-06 00:00:00,10206.0 -2011-05-04 01:00:00,9652.0 -2011-05-04 02:00:00,9167.0 -2011-05-04 03:00:00,8871.0 -2011-05-04 04:00:00,8649.0 -2011-05-04 05:00:00,8603.0 -2011-05-04 06:00:00,8851.0 -2011-05-04 07:00:00,9505.0 -2011-05-04 08:00:00,10459.0 -2011-05-04 09:00:00,11158.0 -2011-05-04 10:00:00,11343.0 -2011-05-04 11:00:00,11383.0 -2011-05-04 12:00:00,11449.0 -2011-05-04 13:00:00,11381.0 -2011-05-04 14:00:00,11314.0 -2011-05-04 15:00:00,11316.0 -2011-05-04 16:00:00,11189.0 -2011-05-04 17:00:00,11071.0 -2011-05-04 18:00:00,10880.0 -2011-05-04 19:00:00,10727.0 -2011-05-04 20:00:00,10490.0 -2011-05-04 21:00:00,10608.0 -2011-05-04 22:00:00,11212.0 -2011-05-04 23:00:00,10938.0 -2011-05-05 00:00:00,10147.0 -2011-05-03 01:00:00,9334.0 -2011-05-03 02:00:00,8877.0 -2011-05-03 03:00:00,8594.0 -2011-05-03 04:00:00,8410.0 -2011-05-03 05:00:00,8395.0 -2011-05-03 06:00:00,8613.0 -2011-05-03 07:00:00,9259.0 -2011-05-03 08:00:00,10107.0 -2011-05-03 09:00:00,10785.0 -2011-05-03 10:00:00,11061.0 -2011-05-03 11:00:00,11173.0 -2011-05-03 12:00:00,11268.0 -2011-05-03 13:00:00,11286.0 -2011-05-03 14:00:00,11310.0 -2011-05-03 15:00:00,11397.0 -2011-05-03 16:00:00,11336.0 -2011-05-03 17:00:00,11282.0 -2011-05-03 18:00:00,11338.0 -2011-05-03 19:00:00,11412.0 -2011-05-03 20:00:00,11394.0 -2011-05-03 21:00:00,11532.0 -2011-05-03 22:00:00,11716.0 -2011-05-03 23:00:00,11274.0 -2011-05-04 00:00:00,10435.0 -2011-05-02 01:00:00,8621.0 -2011-05-02 02:00:00,8209.0 -2011-05-02 03:00:00,8001.0 -2011-05-02 04:00:00,7831.0 -2011-05-02 05:00:00,7848.0 -2011-05-02 06:00:00,8124.0 -2011-05-02 07:00:00,8790.0 -2011-05-02 08:00:00,9725.0 -2011-05-02 09:00:00,10484.0 -2011-05-02 10:00:00,10893.0 -2011-05-02 11:00:00,11045.0 -2011-05-02 12:00:00,11152.0 -2011-05-02 13:00:00,11164.0 -2011-05-02 14:00:00,11166.0 -2011-05-02 15:00:00,11201.0 -2011-05-02 16:00:00,11112.0 -2011-05-02 17:00:00,10954.0 -2011-05-02 18:00:00,10792.0 -2011-05-02 19:00:00,10661.0 -2011-05-02 20:00:00,10515.0 -2011-05-02 21:00:00,10718.0 -2011-05-02 22:00:00,11250.0 -2011-05-02 23:00:00,10859.0 -2011-05-03 00:00:00,10090.0 -2011-05-01 01:00:00,8829.0 -2011-05-01 02:00:00,8373.0 -2011-05-01 03:00:00,7993.0 -2011-05-01 04:00:00,7802.0 -2011-05-01 05:00:00,7637.0 -2011-05-01 06:00:00,7630.0 -2011-05-01 07:00:00,7698.0 -2011-05-01 08:00:00,7556.0 -2011-05-01 09:00:00,7854.0 -2011-05-01 10:00:00,8272.0 -2011-05-01 11:00:00,8597.0 -2011-05-01 12:00:00,8794.0 -2011-05-01 13:00:00,8892.0 -2011-05-01 14:00:00,8941.0 -2011-05-01 15:00:00,8904.0 -2011-05-01 16:00:00,8883.0 -2011-05-01 17:00:00,8835.0 -2011-05-01 18:00:00,8913.0 -2011-05-01 19:00:00,8973.0 -2011-05-01 20:00:00,9107.0 -2011-05-01 21:00:00,9420.0 -2011-05-01 22:00:00,9872.0 -2011-05-01 23:00:00,9643.0 -2011-05-02 00:00:00,9101.0 -2011-04-30 01:00:00,9360.0 -2011-04-30 02:00:00,8767.0 -2011-04-30 03:00:00,8434.0 -2011-04-30 04:00:00,8207.0 -2011-04-30 05:00:00,8137.0 -2011-04-30 06:00:00,8131.0 -2011-04-30 07:00:00,8418.0 -2011-04-30 08:00:00,8580.0 -2011-04-30 09:00:00,9028.0 -2011-04-30 10:00:00,9500.0 -2011-04-30 11:00:00,9704.0 -2011-04-30 12:00:00,9783.0 -2011-04-30 13:00:00,9796.0 -2011-04-30 14:00:00,9711.0 -2011-04-30 15:00:00,9592.0 -2011-04-30 16:00:00,9486.0 -2011-04-30 17:00:00,9466.0 -2011-04-30 18:00:00,9502.0 -2011-04-30 19:00:00,9598.0 -2011-04-30 20:00:00,9668.0 -2011-04-30 21:00:00,9910.0 -2011-04-30 22:00:00,10178.0 -2011-04-30 23:00:00,9939.0 -2011-05-01 00:00:00,9428.0 -2011-04-29 01:00:00,9745.0 -2011-04-29 02:00:00,9225.0 -2011-04-29 03:00:00,8895.0 -2011-04-29 04:00:00,8716.0 -2011-04-29 05:00:00,8648.0 -2011-04-29 06:00:00,8980.0 -2011-04-29 07:00:00,9591.0 -2011-04-29 08:00:00,10386.0 -2011-04-29 09:00:00,10965.0 -2011-04-29 10:00:00,11151.0 -2011-04-29 11:00:00,11207.0 -2011-04-29 12:00:00,11286.0 -2011-04-29 13:00:00,11279.0 -2011-04-29 14:00:00,11213.0 -2011-04-29 15:00:00,11203.0 -2011-04-29 16:00:00,11057.0 -2011-04-29 17:00:00,10846.0 -2011-04-29 18:00:00,10678.0 -2011-04-29 19:00:00,10472.0 -2011-04-29 20:00:00,10299.0 -2011-04-29 21:00:00,10551.0 -2011-04-29 22:00:00,10923.0 -2011-04-29 23:00:00,10676.0 -2011-04-30 00:00:00,10003.0 -2011-04-28 01:00:00,9508.0 -2011-04-28 02:00:00,9003.0 -2011-04-28 03:00:00,8709.0 -2011-04-28 04:00:00,8527.0 -2011-04-28 05:00:00,8506.0 -2011-04-28 06:00:00,8725.0 -2011-04-28 07:00:00,9380.0 -2011-04-28 08:00:00,10320.0 -2011-04-28 09:00:00,11103.0 -2011-04-28 10:00:00,11437.0 -2011-04-28 11:00:00,11573.0 -2011-04-28 12:00:00,11704.0 -2011-04-28 13:00:00,11730.0 -2011-04-28 14:00:00,11693.0 -2011-04-28 15:00:00,11661.0 -2011-04-28 16:00:00,11590.0 -2011-04-28 17:00:00,11486.0 -2011-04-28 18:00:00,11423.0 -2011-04-28 19:00:00,11369.0 -2011-04-28 20:00:00,11298.0 -2011-04-28 21:00:00,11522.0 -2011-04-28 22:00:00,11692.0 -2011-04-28 23:00:00,11342.0 -2011-04-29 00:00:00,10552.0 -2011-04-27 01:00:00,9475.0 -2011-04-27 02:00:00,8896.0 -2011-04-27 03:00:00,8574.0 -2011-04-27 04:00:00,8370.0 -2011-04-27 05:00:00,8287.0 -2011-04-27 06:00:00,8448.0 -2011-04-27 07:00:00,9086.0 -2011-04-27 08:00:00,10112.0 -2011-04-27 09:00:00,10964.0 -2011-04-27 10:00:00,11362.0 -2011-04-27 11:00:00,11514.0 -2011-04-27 12:00:00,11548.0 -2011-04-27 13:00:00,11523.0 -2011-04-27 14:00:00,11498.0 -2011-04-27 15:00:00,11551.0 -2011-04-27 16:00:00,11428.0 -2011-04-27 17:00:00,11261.0 -2011-04-27 18:00:00,11162.0 -2011-04-27 19:00:00,11152.0 -2011-04-27 20:00:00,11097.0 -2011-04-27 21:00:00,11302.0 -2011-04-27 22:00:00,11497.0 -2011-04-27 23:00:00,11075.0 -2011-04-28 00:00:00,10268.0 -2011-04-26 01:00:00,9584.0 -2011-04-26 02:00:00,9050.0 -2011-04-26 03:00:00,8744.0 -2011-04-26 04:00:00,8552.0 -2011-04-26 05:00:00,8511.0 -2011-04-26 06:00:00,8692.0 -2011-04-26 07:00:00,9366.0 -2011-04-26 08:00:00,10327.0 -2011-04-26 09:00:00,11041.0 -2011-04-26 10:00:00,11354.0 -2011-04-26 11:00:00,11528.0 -2011-04-26 12:00:00,11711.0 -2011-04-26 13:00:00,11736.0 -2011-04-26 14:00:00,11719.0 -2011-04-26 15:00:00,11726.0 -2011-04-26 16:00:00,11596.0 -2011-04-26 17:00:00,11450.0 -2011-04-26 18:00:00,11339.0 -2011-04-26 19:00:00,11181.0 -2011-04-26 20:00:00,11024.0 -2011-04-26 21:00:00,11196.0 -2011-04-26 22:00:00,11465.0 -2011-04-26 23:00:00,11004.0 -2011-04-27 00:00:00,10232.0 -2011-04-25 01:00:00,8418.0 -2011-04-25 02:00:00,8089.0 -2011-04-25 03:00:00,7886.0 -2011-04-25 04:00:00,7826.0 -2011-04-25 05:00:00,7827.0 -2011-04-25 06:00:00,8131.0 -2011-04-25 07:00:00,8868.0 -2011-04-25 08:00:00,9854.0 -2011-04-25 09:00:00,10699.0 -2011-04-25 10:00:00,11163.0 -2011-04-25 11:00:00,11353.0 -2011-04-25 12:00:00,11501.0 -2011-04-25 13:00:00,11449.0 -2011-04-25 14:00:00,11397.0 -2011-04-25 15:00:00,11351.0 -2011-04-25 16:00:00,11222.0 -2011-04-25 17:00:00,11104.0 -2011-04-25 18:00:00,11117.0 -2011-04-25 19:00:00,11160.0 -2011-04-25 20:00:00,11172.0 -2011-04-25 21:00:00,11467.0 -2011-04-25 22:00:00,11538.0 -2011-04-25 23:00:00,11068.0 -2011-04-26 00:00:00,10322.0 -2011-04-24 01:00:00,8678.0 -2011-04-24 02:00:00,8162.0 -2011-04-24 03:00:00,7893.0 -2011-04-24 04:00:00,7647.0 -2011-04-24 05:00:00,7570.0 -2011-04-24 06:00:00,7539.0 -2011-04-24 07:00:00,7671.0 -2011-04-24 08:00:00,7605.0 -2011-04-24 09:00:00,7875.0 -2011-04-24 10:00:00,8186.0 -2011-04-24 11:00:00,8407.0 -2011-04-24 12:00:00,8452.0 -2011-04-24 13:00:00,8530.0 -2011-04-24 14:00:00,8490.0 -2011-04-24 15:00:00,8429.0 -2011-04-24 16:00:00,8310.0 -2011-04-24 17:00:00,8246.0 -2011-04-24 18:00:00,8188.0 -2011-04-24 19:00:00,8267.0 -2011-04-24 20:00:00,8486.0 -2011-04-24 21:00:00,8977.0 -2011-04-24 22:00:00,9479.0 -2011-04-24 23:00:00,9341.0 -2011-04-25 00:00:00,8927.0 -2011-04-23 01:00:00,9243.0 -2011-04-23 02:00:00,8708.0 -2011-04-23 03:00:00,8372.0 -2011-04-23 04:00:00,8150.0 -2011-04-23 05:00:00,8044.0 -2011-04-23 06:00:00,8070.0 -2011-04-23 07:00:00,8260.0 -2011-04-23 08:00:00,8485.0 -2011-04-23 09:00:00,8709.0 -2011-04-23 10:00:00,9139.0 -2011-04-23 11:00:00,9466.0 -2011-04-23 12:00:00,9506.0 -2011-04-23 13:00:00,9519.0 -2011-04-23 14:00:00,9412.0 -2011-04-23 15:00:00,9271.0 -2011-04-23 16:00:00,9116.0 -2011-04-23 17:00:00,8991.0 -2011-04-23 18:00:00,8955.0 -2011-04-23 19:00:00,8932.0 -2011-04-23 20:00:00,8955.0 -2011-04-23 21:00:00,9295.0 -2011-04-23 22:00:00,9822.0 -2011-04-23 23:00:00,9671.0 -2011-04-24 00:00:00,9203.0 -2011-04-22 01:00:00,9713.0 -2011-04-22 02:00:00,9118.0 -2011-04-22 03:00:00,8857.0 -2011-04-22 04:00:00,8625.0 -2011-04-22 05:00:00,8591.0 -2011-04-22 06:00:00,8737.0 -2011-04-22 07:00:00,9247.0 -2011-04-22 08:00:00,9912.0 -2011-04-22 09:00:00,10399.0 -2011-04-22 10:00:00,10848.0 -2011-04-22 11:00:00,11102.0 -2011-04-22 12:00:00,11324.0 -2011-04-22 13:00:00,11425.0 -2011-04-22 14:00:00,11458.0 -2011-04-22 15:00:00,11309.0 -2011-04-22 16:00:00,11026.0 -2011-04-22 17:00:00,10754.0 -2011-04-22 18:00:00,10672.0 -2011-04-22 19:00:00,10743.0 -2011-04-22 20:00:00,10831.0 -2011-04-22 21:00:00,10966.0 -2011-04-22 22:00:00,10931.0 -2011-04-22 23:00:00,10597.0 -2011-04-23 00:00:00,9915.0 -2011-04-21 01:00:00,10013.0 -2011-04-21 02:00:00,9519.0 -2011-04-21 03:00:00,9229.0 -2011-04-21 04:00:00,9066.0 -2011-04-21 05:00:00,9067.0 -2011-04-21 06:00:00,9307.0 -2011-04-21 07:00:00,9962.0 -2011-04-21 08:00:00,10778.0 -2011-04-21 09:00:00,11350.0 -2011-04-21 10:00:00,11541.0 -2011-04-21 11:00:00,11525.0 -2011-04-21 12:00:00,11575.0 -2011-04-21 13:00:00,11519.0 -2011-04-21 14:00:00,11427.0 -2011-04-21 15:00:00,11396.0 -2011-04-21 16:00:00,11234.0 -2011-04-21 17:00:00,11052.0 -2011-04-21 18:00:00,10929.0 -2011-04-21 19:00:00,10939.0 -2011-04-21 20:00:00,10956.0 -2011-04-21 21:00:00,11309.0 -2011-04-21 22:00:00,11521.0 -2011-04-21 23:00:00,11200.0 -2011-04-22 00:00:00,10487.0 -2011-04-20 01:00:00,10279.0 -2011-04-20 02:00:00,9772.0 -2011-04-20 03:00:00,9479.0 -2011-04-20 04:00:00,9255.0 -2011-04-20 05:00:00,9238.0 -2011-04-20 06:00:00,9429.0 -2011-04-20 07:00:00,10128.0 -2011-04-20 08:00:00,11173.0 -2011-04-20 09:00:00,11832.0 -2011-04-20 10:00:00,12119.0 -2011-04-20 11:00:00,12226.0 -2011-04-20 12:00:00,12272.0 -2011-04-20 13:00:00,12176.0 -2011-04-20 14:00:00,12065.0 -2011-04-20 15:00:00,12029.0 -2011-04-20 16:00:00,11961.0 -2011-04-20 17:00:00,11917.0 -2011-04-20 18:00:00,11878.0 -2011-04-20 19:00:00,11800.0 -2011-04-20 20:00:00,11634.0 -2011-04-20 21:00:00,11801.0 -2011-04-20 22:00:00,12049.0 -2011-04-20 23:00:00,11611.0 -2011-04-21 00:00:00,10770.0 -2011-04-19 01:00:00,10136.0 -2011-04-19 02:00:00,9621.0 -2011-04-19 03:00:00,9312.0 -2011-04-19 04:00:00,9131.0 -2011-04-19 05:00:00,9123.0 -2011-04-19 06:00:00,9335.0 -2011-04-19 07:00:00,9985.0 -2011-04-19 08:00:00,11082.0 -2011-04-19 09:00:00,11887.0 -2011-04-19 10:00:00,12253.0 -2011-04-19 11:00:00,12468.0 -2011-04-19 12:00:00,12745.0 -2011-04-19 13:00:00,12726.0 -2011-04-19 14:00:00,12673.0 -2011-04-19 15:00:00,12676.0 -2011-04-19 16:00:00,12632.0 -2011-04-19 17:00:00,12562.0 -2011-04-19 18:00:00,12480.0 -2011-04-19 19:00:00,12452.0 -2011-04-19 20:00:00,12386.0 -2011-04-19 21:00:00,12674.0 -2011-04-19 22:00:00,12600.0 -2011-04-19 23:00:00,12074.0 -2011-04-20 00:00:00,11144.0 -2011-04-18 01:00:00,8922.0 -2011-04-18 02:00:00,8594.0 -2011-04-18 03:00:00,8489.0 -2011-04-18 04:00:00,8453.0 -2011-04-18 05:00:00,8587.0 -2011-04-18 06:00:00,8932.0 -2011-04-18 07:00:00,9739.0 -2011-04-18 08:00:00,10876.0 -2011-04-18 09:00:00,11618.0 -2011-04-18 10:00:00,11995.0 -2011-04-18 11:00:00,12082.0 -2011-04-18 12:00:00,12248.0 -2011-04-18 13:00:00,12166.0 -2011-04-18 14:00:00,12051.0 -2011-04-18 15:00:00,11957.0 -2011-04-18 16:00:00,11722.0 -2011-04-18 17:00:00,11450.0 -2011-04-18 18:00:00,11353.0 -2011-04-18 19:00:00,11349.0 -2011-04-18 20:00:00,11378.0 -2011-04-18 21:00:00,11839.0 -2011-04-18 22:00:00,12071.0 -2011-04-18 23:00:00,11636.0 -2011-04-19 00:00:00,10885.0 -2011-04-17 01:00:00,9542.0 -2011-04-17 02:00:00,9081.0 -2011-04-17 03:00:00,8710.0 -2011-04-17 04:00:00,8581.0 -2011-04-17 05:00:00,8510.0 -2011-04-17 06:00:00,8511.0 -2011-04-17 07:00:00,8696.0 -2011-04-17 08:00:00,8674.0 -2011-04-17 09:00:00,8886.0 -2011-04-17 10:00:00,9196.0 -2011-04-17 11:00:00,9375.0 -2011-04-17 12:00:00,9373.0 -2011-04-17 13:00:00,9304.0 -2011-04-17 14:00:00,9282.0 -2011-04-17 15:00:00,9195.0 -2011-04-17 16:00:00,9098.0 -2011-04-17 17:00:00,9022.0 -2011-04-17 18:00:00,9066.0 -2011-04-17 19:00:00,9142.0 -2011-04-17 20:00:00,9296.0 -2011-04-17 21:00:00,9893.0 -2011-04-17 22:00:00,10251.0 -2011-04-17 23:00:00,9964.0 -2011-04-18 00:00:00,9438.0 -2011-04-16 01:00:00,9712.0 -2011-04-16 02:00:00,9084.0 -2011-04-16 03:00:00,8728.0 -2011-04-16 04:00:00,8434.0 -2011-04-16 05:00:00,8338.0 -2011-04-16 06:00:00,8376.0 -2011-04-16 07:00:00,8641.0 -2011-04-16 08:00:00,8965.0 -2011-04-16 09:00:00,9366.0 -2011-04-16 10:00:00,9818.0 -2011-04-16 11:00:00,10166.0 -2011-04-16 12:00:00,10301.0 -2011-04-16 13:00:00,10331.0 -2011-04-16 14:00:00,10269.0 -2011-04-16 15:00:00,10285.0 -2011-04-16 16:00:00,10258.0 -2011-04-16 17:00:00,10288.0 -2011-04-16 18:00:00,10308.0 -2011-04-16 19:00:00,10438.0 -2011-04-16 20:00:00,10499.0 -2011-04-16 21:00:00,10800.0 -2011-04-16 22:00:00,10915.0 -2011-04-16 23:00:00,10607.0 -2011-04-17 00:00:00,10153.0 -2011-04-15 01:00:00,9751.0 -2011-04-15 02:00:00,9197.0 -2011-04-15 03:00:00,8923.0 -2011-04-15 04:00:00,8730.0 -2011-04-15 05:00:00,8732.0 -2011-04-15 06:00:00,8966.0 -2011-04-15 07:00:00,9678.0 -2011-04-15 08:00:00,10626.0 -2011-04-15 09:00:00,11215.0 -2011-04-15 10:00:00,11487.0 -2011-04-15 11:00:00,11562.0 -2011-04-15 12:00:00,11717.0 -2011-04-15 13:00:00,11675.0 -2011-04-15 14:00:00,11626.0 -2011-04-15 15:00:00,11604.0 -2011-04-15 16:00:00,11565.0 -2011-04-15 17:00:00,11532.0 -2011-04-15 18:00:00,11593.0 -2011-04-15 19:00:00,11617.0 -2011-04-15 20:00:00,11588.0 -2011-04-15 21:00:00,11712.0 -2011-04-15 22:00:00,11586.0 -2011-04-15 23:00:00,11215.0 -2011-04-16 00:00:00,10461.0 -2011-04-14 01:00:00,9276.0 -2011-04-14 02:00:00,8742.0 -2011-04-14 03:00:00,8379.0 -2011-04-14 04:00:00,8204.0 -2011-04-14 05:00:00,8166.0 -2011-04-14 06:00:00,8366.0 -2011-04-14 07:00:00,9050.0 -2011-04-14 08:00:00,10085.0 -2011-04-14 09:00:00,10830.0 -2011-04-14 10:00:00,11174.0 -2011-04-14 11:00:00,11310.0 -2011-04-14 12:00:00,11447.0 -2011-04-14 13:00:00,11424.0 -2011-04-14 14:00:00,11353.0 -2011-04-14 15:00:00,11357.0 -2011-04-14 16:00:00,11223.0 -2011-04-14 17:00:00,11085.0 -2011-04-14 18:00:00,10988.0 -2011-04-14 19:00:00,10931.0 -2011-04-14 20:00:00,11006.0 -2011-04-14 21:00:00,11545.0 -2011-04-14 22:00:00,11790.0 -2011-04-14 23:00:00,11353.0 -2011-04-15 00:00:00,10511.0 -2011-04-13 01:00:00,9429.0 -2011-04-13 02:00:00,8918.0 -2011-04-13 03:00:00,8622.0 -2011-04-13 04:00:00,8449.0 -2011-04-13 05:00:00,8414.0 -2011-04-13 06:00:00,8648.0 -2011-04-13 07:00:00,9350.0 -2011-04-13 08:00:00,10212.0 -2011-04-13 09:00:00,10855.0 -2011-04-13 10:00:00,11110.0 -2011-04-13 11:00:00,11169.0 -2011-04-13 12:00:00,11315.0 -2011-04-13 13:00:00,11333.0 -2011-04-13 14:00:00,11333.0 -2011-04-13 15:00:00,11405.0 -2011-04-13 16:00:00,11326.0 -2011-04-13 17:00:00,11240.0 -2011-04-13 18:00:00,11102.0 -2011-04-13 19:00:00,10913.0 -2011-04-13 20:00:00,10749.0 -2011-04-13 21:00:00,11091.0 -2011-04-13 22:00:00,11428.0 -2011-04-13 23:00:00,11008.0 -2011-04-14 00:00:00,10151.0 -2011-04-12 01:00:00,9274.0 -2011-04-12 02:00:00,8705.0 -2011-04-12 03:00:00,8412.0 -2011-04-12 04:00:00,8246.0 -2011-04-12 05:00:00,8188.0 -2011-04-12 06:00:00,8432.0 -2011-04-12 07:00:00,9120.0 -2011-04-12 08:00:00,10117.0 -2011-04-12 09:00:00,10781.0 -2011-04-12 10:00:00,11083.0 -2011-04-12 11:00:00,11185.0 -2011-04-12 12:00:00,11307.0 -2011-04-12 13:00:00,11288.0 -2011-04-12 14:00:00,11283.0 -2011-04-12 15:00:00,11314.0 -2011-04-12 16:00:00,11180.0 -2011-04-12 17:00:00,11028.0 -2011-04-12 18:00:00,10874.0 -2011-04-12 19:00:00,10719.0 -2011-04-12 20:00:00,10590.0 -2011-04-12 21:00:00,11003.0 -2011-04-12 22:00:00,11433.0 -2011-04-12 23:00:00,11007.0 -2011-04-13 00:00:00,10232.0 -2011-04-11 01:00:00,9607.0 -2011-04-11 02:00:00,9043.0 -2011-04-11 03:00:00,8582.0 -2011-04-11 04:00:00,8302.0 -2011-04-11 05:00:00,8105.0 -2011-04-11 06:00:00,8269.0 -2011-04-11 07:00:00,8866.0 -2011-04-11 08:00:00,9783.0 -2011-04-11 09:00:00,10449.0 -2011-04-11 10:00:00,10908.0 -2011-04-11 11:00:00,11099.0 -2011-04-11 12:00:00,11203.0 -2011-04-11 13:00:00,11298.0 -2011-04-11 14:00:00,11319.0 -2011-04-11 15:00:00,11374.0 -2011-04-11 16:00:00,11325.0 -2011-04-11 17:00:00,11241.0 -2011-04-11 18:00:00,11126.0 -2011-04-11 19:00:00,10989.0 -2011-04-11 20:00:00,10955.0 -2011-04-11 21:00:00,11332.0 -2011-04-11 22:00:00,11407.0 -2011-04-11 23:00:00,10907.0 -2011-04-12 00:00:00,10072.0 -2011-04-10 01:00:00,8934.0 -2011-04-10 02:00:00,8495.0 -2011-04-10 03:00:00,8168.0 -2011-04-10 04:00:00,7972.0 -2011-04-10 05:00:00,7793.0 -2011-04-10 06:00:00,7833.0 -2011-04-10 07:00:00,7923.0 -2011-04-10 08:00:00,7981.0 -2011-04-10 09:00:00,8099.0 -2011-04-10 10:00:00,8578.0 -2011-04-10 11:00:00,8959.0 -2011-04-10 12:00:00,9352.0 -2011-04-10 13:00:00,9561.0 -2011-04-10 14:00:00,9787.0 -2011-04-10 15:00:00,9924.0 -2011-04-10 16:00:00,10027.0 -2011-04-10 17:00:00,10128.0 -2011-04-10 18:00:00,10176.0 -2011-04-10 19:00:00,10423.0 -2011-04-10 20:00:00,10520.0 -2011-04-10 21:00:00,11012.0 -2011-04-10 22:00:00,11324.0 -2011-04-10 23:00:00,11001.0 -2011-04-11 00:00:00,10357.0 -2011-04-09 01:00:00,9695.0 -2011-04-09 02:00:00,9132.0 -2011-04-09 03:00:00,8807.0 -2011-04-09 04:00:00,8626.0 -2011-04-09 05:00:00,8478.0 -2011-04-09 06:00:00,8554.0 -2011-04-09 07:00:00,8779.0 -2011-04-09 08:00:00,9203.0 -2011-04-09 09:00:00,9534.0 -2011-04-09 10:00:00,9934.0 -2011-04-09 11:00:00,10110.0 -2011-04-09 12:00:00,10162.0 -2011-04-09 13:00:00,10038.0 -2011-04-09 14:00:00,9900.0 -2011-04-09 15:00:00,9682.0 -2011-04-09 16:00:00,9593.0 -2011-04-09 17:00:00,9445.0 -2011-04-09 18:00:00,9432.0 -2011-04-09 19:00:00,9383.0 -2011-04-09 20:00:00,9420.0 -2011-04-09 21:00:00,9848.0 -2011-04-09 22:00:00,10259.0 -2011-04-09 23:00:00,9983.0 -2011-04-10 00:00:00,9502.0 -2011-04-08 01:00:00,9944.0 -2011-04-08 02:00:00,9377.0 -2011-04-08 03:00:00,9067.0 -2011-04-08 04:00:00,8909.0 -2011-04-08 05:00:00,8947.0 -2011-04-08 06:00:00,9092.0 -2011-04-08 07:00:00,9768.0 -2011-04-08 08:00:00,10886.0 -2011-04-08 09:00:00,11611.0 -2011-04-08 10:00:00,11774.0 -2011-04-08 11:00:00,11882.0 -2011-04-08 12:00:00,11960.0 -2011-04-08 13:00:00,11970.0 -2011-04-08 14:00:00,11898.0 -2011-04-08 15:00:00,11851.0 -2011-04-08 16:00:00,11717.0 -2011-04-08 17:00:00,11558.0 -2011-04-08 18:00:00,11399.0 -2011-04-08 19:00:00,11234.0 -2011-04-08 20:00:00,11207.0 -2011-04-08 21:00:00,11548.0 -2011-04-08 22:00:00,11543.0 -2011-04-08 23:00:00,11184.0 -2011-04-09 00:00:00,10491.0 -2011-04-07 01:00:00,9661.0 -2011-04-07 02:00:00,9185.0 -2011-04-07 03:00:00,8884.0 -2011-04-07 04:00:00,8711.0 -2011-04-07 05:00:00,8687.0 -2011-04-07 06:00:00,8896.0 -2011-04-07 07:00:00,9573.0 -2011-04-07 08:00:00,10637.0 -2011-04-07 09:00:00,11331.0 -2011-04-07 10:00:00,11617.0 -2011-04-07 11:00:00,11712.0 -2011-04-07 12:00:00,11761.0 -2011-04-07 13:00:00,11698.0 -2011-04-07 14:00:00,11594.0 -2011-04-07 15:00:00,11621.0 -2011-04-07 16:00:00,11523.0 -2011-04-07 17:00:00,11401.0 -2011-04-07 18:00:00,11332.0 -2011-04-07 19:00:00,11383.0 -2011-04-07 20:00:00,11543.0 -2011-04-07 21:00:00,12019.0 -2011-04-07 22:00:00,11940.0 -2011-04-07 23:00:00,11513.0 -2011-04-08 00:00:00,10733.0 -2011-04-06 01:00:00,9757.0 -2011-04-06 02:00:00,9231.0 -2011-04-06 03:00:00,8909.0 -2011-04-06 04:00:00,8692.0 -2011-04-06 05:00:00,8616.0 -2011-04-06 06:00:00,8818.0 -2011-04-06 07:00:00,9519.0 -2011-04-06 08:00:00,10609.0 -2011-04-06 09:00:00,11245.0 -2011-04-06 10:00:00,11528.0 -2011-04-06 11:00:00,11674.0 -2011-04-06 12:00:00,11742.0 -2011-04-06 13:00:00,11646.0 -2011-04-06 14:00:00,11543.0 -2011-04-06 15:00:00,11545.0 -2011-04-06 16:00:00,11441.0 -2011-04-06 17:00:00,11305.0 -2011-04-06 18:00:00,11155.0 -2011-04-06 19:00:00,11016.0 -2011-04-06 20:00:00,10913.0 -2011-04-06 21:00:00,11417.0 -2011-04-06 22:00:00,11655.0 -2011-04-06 23:00:00,11225.0 -2011-04-07 00:00:00,10438.0 -2011-04-05 01:00:00,9839.0 -2011-04-05 02:00:00,9343.0 -2011-04-05 03:00:00,9026.0 -2011-04-05 04:00:00,8853.0 -2011-04-05 05:00:00,8866.0 -2011-04-05 06:00:00,9131.0 -2011-04-05 07:00:00,9915.0 -2011-04-05 08:00:00,10957.0 -2011-04-05 09:00:00,11460.0 -2011-04-05 10:00:00,11613.0 -2011-04-05 11:00:00,11635.0 -2011-04-05 12:00:00,11700.0 -2011-04-05 13:00:00,11693.0 -2011-04-05 14:00:00,11641.0 -2011-04-05 15:00:00,11560.0 -2011-04-05 16:00:00,11400.0 -2011-04-05 17:00:00,11202.0 -2011-04-05 18:00:00,11063.0 -2011-04-05 19:00:00,10960.0 -2011-04-05 20:00:00,10957.0 -2011-04-05 21:00:00,11491.0 -2011-04-05 22:00:00,11713.0 -2011-04-05 23:00:00,11321.0 -2011-04-06 00:00:00,10535.0 -2011-04-04 01:00:00,8802.0 -2011-04-04 02:00:00,8402.0 -2011-04-04 03:00:00,8035.0 -2011-04-04 04:00:00,8057.0 -2011-04-04 05:00:00,8059.0 -2011-04-04 06:00:00,8214.0 -2011-04-04 07:00:00,8910.0 -2011-04-04 08:00:00,10096.0 -2011-04-04 09:00:00,10908.0 -2011-04-04 10:00:00,11266.0 -2011-04-04 11:00:00,11593.0 -2011-04-04 12:00:00,11578.0 -2011-04-04 13:00:00,11582.0 -2011-04-04 14:00:00,11625.0 -2011-04-04 15:00:00,11676.0 -2011-04-04 16:00:00,11572.0 -2011-04-04 17:00:00,11413.0 -2011-04-04 18:00:00,11389.0 -2011-04-04 19:00:00,11446.0 -2011-04-04 20:00:00,11456.0 -2011-04-04 21:00:00,11893.0 -2011-04-04 22:00:00,11905.0 -2011-04-04 23:00:00,11442.0 -2011-04-05 00:00:00,10646.0 -2011-04-03 01:00:00,9318.0 -2011-04-03 02:00:00,8851.0 -2011-04-03 03:00:00,8539.0 -2011-04-03 04:00:00,8392.0 -2011-04-03 05:00:00,8263.0 -2011-04-03 06:00:00,8290.0 -2011-04-03 07:00:00,8416.0 -2011-04-03 08:00:00,8526.0 -2011-04-03 09:00:00,8755.0 -2011-04-03 10:00:00,9080.0 -2011-04-03 11:00:00,9345.0 -2011-04-03 12:00:00,9567.0 -2011-04-03 13:00:00,9633.0 -2011-04-03 14:00:00,9448.0 -2011-04-03 15:00:00,9482.0 -2011-04-03 16:00:00,9481.0 -2011-04-03 17:00:00,9453.0 -2011-04-03 18:00:00,9407.0 -2011-04-03 19:00:00,9465.0 -2011-04-03 20:00:00,9578.0 -2011-04-03 21:00:00,10151.0 -2011-04-03 22:00:00,10235.0 -2011-04-03 23:00:00,9908.0 -2011-04-04 00:00:00,9418.0 -2011-04-02 01:00:00,10059.0 -2011-04-02 02:00:00,9498.0 -2011-04-02 03:00:00,9143.0 -2011-04-02 04:00:00,8916.0 -2011-04-02 05:00:00,8833.0 -2011-04-02 06:00:00,8890.0 -2011-04-02 07:00:00,9106.0 -2011-04-02 08:00:00,9566.0 -2011-04-02 09:00:00,9727.0 -2011-04-02 10:00:00,10016.0 -2011-04-02 11:00:00,10122.0 -2011-04-02 12:00:00,10202.0 -2011-04-02 13:00:00,10157.0 -2011-04-02 14:00:00,10076.0 -2011-04-02 15:00:00,9857.0 -2011-04-02 16:00:00,9667.0 -2011-04-02 17:00:00,9514.0 -2011-04-02 18:00:00,9457.0 -2011-04-02 19:00:00,9402.0 -2011-04-02 20:00:00,9501.0 -2011-04-02 21:00:00,10145.0 -2011-04-02 22:00:00,10509.0 -2011-04-02 23:00:00,10345.0 -2011-04-03 00:00:00,9855.0 -2011-04-01 01:00:00,10059.0 -2011-04-01 02:00:00,9592.0 -2011-04-01 03:00:00,9317.0 -2011-04-01 04:00:00,9159.0 -2011-04-01 05:00:00,9134.0 -2011-04-01 06:00:00,9351.0 -2011-04-01 07:00:00,9980.0 -2011-04-01 08:00:00,10915.0 -2011-04-01 09:00:00,11441.0 -2011-04-01 10:00:00,11686.0 -2011-04-01 11:00:00,11796.0 -2011-04-01 12:00:00,11915.0 -2011-04-01 13:00:00,11927.0 -2011-04-01 14:00:00,12000.0 -2011-04-01 15:00:00,12110.0 -2011-04-01 16:00:00,11993.0 -2011-04-01 17:00:00,11790.0 -2011-04-01 18:00:00,11632.0 -2011-04-01 19:00:00,11562.0 -2011-04-01 20:00:00,11463.0 -2011-04-01 21:00:00,11800.0 -2011-04-01 22:00:00,11763.0 -2011-04-01 23:00:00,11403.0 -2011-04-02 00:00:00,10760.0 -2011-03-31 01:00:00,10220.0 -2011-03-31 02:00:00,9737.0 -2011-03-31 03:00:00,9466.0 -2011-03-31 04:00:00,9320.0 -2011-03-31 05:00:00,9347.0 -2011-03-31 06:00:00,9598.0 -2011-03-31 07:00:00,10289.0 -2011-03-31 08:00:00,11293.0 -2011-03-31 09:00:00,11744.0 -2011-03-31 10:00:00,11899.0 -2011-03-31 11:00:00,11899.0 -2011-03-31 12:00:00,11860.0 -2011-03-31 13:00:00,11740.0 -2011-03-31 14:00:00,11621.0 -2011-03-31 15:00:00,11557.0 -2011-03-31 16:00:00,11401.0 -2011-03-31 17:00:00,11211.0 -2011-03-31 18:00:00,11073.0 -2011-03-31 19:00:00,10972.0 -2011-03-31 20:00:00,10933.0 -2011-03-31 21:00:00,11557.0 -2011-03-31 22:00:00,11708.0 -2011-03-31 23:00:00,11398.0 -2011-04-01 00:00:00,10737.0 -2011-03-30 01:00:00,10374.0 -2011-03-30 02:00:00,9845.0 -2011-03-30 03:00:00,9508.0 -2011-03-30 04:00:00,9324.0 -2011-03-30 05:00:00,9309.0 -2011-03-30 06:00:00,9523.0 -2011-03-30 07:00:00,10196.0 -2011-03-30 08:00:00,11222.0 -2011-03-30 09:00:00,11771.0 -2011-03-30 10:00:00,12044.0 -2011-03-30 11:00:00,12060.0 -2011-03-30 12:00:00,12015.0 -2011-03-30 13:00:00,11905.0 -2011-03-30 14:00:00,11743.0 -2011-03-30 15:00:00,11649.0 -2011-03-30 16:00:00,11509.0 -2011-03-30 17:00:00,11307.0 -2011-03-30 18:00:00,11176.0 -2011-03-30 19:00:00,11085.0 -2011-03-30 20:00:00,11062.0 -2011-03-30 21:00:00,11680.0 -2011-03-30 22:00:00,11906.0 -2011-03-30 23:00:00,11547.0 -2011-03-31 00:00:00,10897.0 -2011-03-29 01:00:00,10480.0 -2011-03-29 02:00:00,9978.0 -2011-03-29 03:00:00,9712.0 -2011-03-29 04:00:00,9532.0 -2011-03-29 05:00:00,9530.0 -2011-03-29 06:00:00,9731.0 -2011-03-29 07:00:00,10419.0 -2011-03-29 08:00:00,11452.0 -2011-03-29 09:00:00,11952.0 -2011-03-29 10:00:00,12177.0 -2011-03-29 11:00:00,12153.0 -2011-03-29 12:00:00,12121.0 -2011-03-29 13:00:00,12024.0 -2011-03-29 14:00:00,11917.0 -2011-03-29 15:00:00,11821.0 -2011-03-29 16:00:00,11694.0 -2011-03-29 17:00:00,11524.0 -2011-03-29 18:00:00,11355.0 -2011-03-29 19:00:00,11323.0 -2011-03-29 20:00:00,11346.0 -2011-03-29 21:00:00,12047.0 -2011-03-29 22:00:00,12177.0 -2011-03-29 23:00:00,11769.0 -2011-03-30 00:00:00,11089.0 -2011-03-28 01:00:00,10056.0 -2011-03-28 02:00:00,9706.0 -2011-03-28 03:00:00,9501.0 -2011-03-28 04:00:00,9452.0 -2011-03-28 05:00:00,9499.0 -2011-03-28 06:00:00,9810.0 -2011-03-28 07:00:00,10558.0 -2011-03-28 08:00:00,11609.0 -2011-03-28 09:00:00,12104.0 -2011-03-28 10:00:00,12284.0 -2011-03-28 11:00:00,12263.0 -2011-03-28 12:00:00,12297.0 -2011-03-28 13:00:00,12201.0 -2011-03-28 14:00:00,12095.0 -2011-03-28 15:00:00,12035.0 -2011-03-28 16:00:00,11854.0 -2011-03-28 17:00:00,11612.0 -2011-03-28 18:00:00,11499.0 -2011-03-28 19:00:00,11444.0 -2011-03-28 20:00:00,11460.0 -2011-03-28 21:00:00,12202.0 -2011-03-28 22:00:00,12340.0 -2011-03-28 23:00:00,11940.0 -2011-03-29 00:00:00,11190.0 -2011-03-27 01:00:00,10244.0 -2011-03-27 02:00:00,9751.0 -2011-03-27 03:00:00,9484.0 -2011-03-27 04:00:00,9288.0 -2011-03-27 05:00:00,9195.0 -2011-03-27 06:00:00,9274.0 -2011-03-27 07:00:00,9402.0 -2011-03-27 08:00:00,9632.0 -2011-03-27 09:00:00,9561.0 -2011-03-27 10:00:00,9784.0 -2011-03-27 11:00:00,9948.0 -2011-03-27 12:00:00,10049.0 -2011-03-27 13:00:00,10066.0 -2011-03-27 14:00:00,9967.0 -2011-03-27 15:00:00,9895.0 -2011-03-27 16:00:00,9819.0 -2011-03-27 17:00:00,9786.0 -2011-03-27 18:00:00,9797.0 -2011-03-27 19:00:00,9916.0 -2011-03-27 20:00:00,10128.0 -2011-03-27 21:00:00,11042.0 -2011-03-27 22:00:00,11247.0 -2011-03-27 23:00:00,11032.0 -2011-03-28 00:00:00,10561.0 -2011-03-26 01:00:00,10695.0 -2011-03-26 02:00:00,10131.0 -2011-03-26 03:00:00,9841.0 -2011-03-26 04:00:00,9615.0 -2011-03-26 05:00:00,9572.0 -2011-03-26 06:00:00,9661.0 -2011-03-26 07:00:00,9947.0 -2011-03-26 08:00:00,10422.0 -2011-03-26 09:00:00,10657.0 -2011-03-26 10:00:00,11005.0 -2011-03-26 11:00:00,11285.0 -2011-03-26 12:00:00,11302.0 -2011-03-26 13:00:00,11221.0 -2011-03-26 14:00:00,10976.0 -2011-03-26 15:00:00,10759.0 -2011-03-26 16:00:00,10551.0 -2011-03-26 17:00:00,10405.0 -2011-03-26 18:00:00,10352.0 -2011-03-26 19:00:00,10461.0 -2011-03-26 20:00:00,10660.0 -2011-03-26 21:00:00,11394.0 -2011-03-26 22:00:00,11507.0 -2011-03-26 23:00:00,11247.0 -2011-03-27 00:00:00,10778.0 -2011-03-25 01:00:00,10608.0 -2011-03-25 02:00:00,10068.0 -2011-03-25 03:00:00,9801.0 -2011-03-25 04:00:00,9621.0 -2011-03-25 05:00:00,9607.0 -2011-03-25 06:00:00,9831.0 -2011-03-25 07:00:00,10498.0 -2011-03-25 08:00:00,11586.0 -2011-03-25 09:00:00,12127.0 -2011-03-25 10:00:00,12377.0 -2011-03-25 11:00:00,12453.0 -2011-03-25 12:00:00,12505.0 -2011-03-25 13:00:00,12305.0 -2011-03-25 14:00:00,12126.0 -2011-03-25 15:00:00,12039.0 -2011-03-25 16:00:00,11843.0 -2011-03-25 17:00:00,11639.0 -2011-03-25 18:00:00,11509.0 -2011-03-25 19:00:00,11400.0 -2011-03-25 20:00:00,11486.0 -2011-03-25 21:00:00,12158.0 -2011-03-25 22:00:00,12211.0 -2011-03-25 23:00:00,11899.0 -2011-03-26 00:00:00,11276.0 -2011-03-24 01:00:00,10575.0 -2011-03-24 02:00:00,10076.0 -2011-03-24 03:00:00,9788.0 -2011-03-24 04:00:00,9615.0 -2011-03-24 05:00:00,9613.0 -2011-03-24 06:00:00,9841.0 -2011-03-24 07:00:00,10523.0 -2011-03-24 08:00:00,11721.0 -2011-03-24 09:00:00,12234.0 -2011-03-24 10:00:00,12436.0 -2011-03-24 11:00:00,12451.0 -2011-03-24 12:00:00,12508.0 -2011-03-24 13:00:00,12395.0 -2011-03-24 14:00:00,12301.0 -2011-03-24 15:00:00,12189.0 -2011-03-24 16:00:00,12067.0 -2011-03-24 17:00:00,11898.0 -2011-03-24 18:00:00,11758.0 -2011-03-24 19:00:00,11689.0 -2011-03-24 20:00:00,11686.0 -2011-03-24 21:00:00,12381.0 -2011-03-24 22:00:00,12477.0 -2011-03-24 23:00:00,12067.0 -2011-03-25 00:00:00,11331.0 -2011-03-23 01:00:00,10228.0 -2011-03-23 02:00:00,9690.0 -2011-03-23 03:00:00,9351.0 -2011-03-23 04:00:00,9181.0 -2011-03-23 05:00:00,9133.0 -2011-03-23 06:00:00,9365.0 -2011-03-23 07:00:00,10052.0 -2011-03-23 08:00:00,11208.0 -2011-03-23 09:00:00,11879.0 -2011-03-23 10:00:00,12196.0 -2011-03-23 11:00:00,12285.0 -2011-03-23 12:00:00,12337.0 -2011-03-23 13:00:00,12197.0 -2011-03-23 14:00:00,12102.0 -2011-03-23 15:00:00,12047.0 -2011-03-23 16:00:00,12031.0 -2011-03-23 17:00:00,12025.0 -2011-03-23 18:00:00,12077.0 -2011-03-23 19:00:00,12148.0 -2011-03-23 20:00:00,12205.0 -2011-03-23 21:00:00,12646.0 -2011-03-23 22:00:00,12558.0 -2011-03-23 23:00:00,12125.0 -2011-03-24 00:00:00,11353.0 -2011-03-22 01:00:00,9767.0 -2011-03-22 02:00:00,9284.0 -2011-03-22 03:00:00,8986.0 -2011-03-22 04:00:00,8832.0 -2011-03-22 05:00:00,8797.0 -2011-03-22 06:00:00,9027.0 -2011-03-22 07:00:00,9694.0 -2011-03-22 08:00:00,10924.0 -2011-03-22 09:00:00,11789.0 -2011-03-22 10:00:00,12044.0 -2011-03-22 11:00:00,12202.0 -2011-03-22 12:00:00,12298.0 -2011-03-22 13:00:00,12269.0 -2011-03-22 14:00:00,12226.0 -2011-03-22 15:00:00,12291.0 -2011-03-22 16:00:00,12258.0 -2011-03-22 17:00:00,12282.0 -2011-03-22 18:00:00,12284.0 -2011-03-22 19:00:00,12308.0 -2011-03-22 20:00:00,12314.0 -2011-03-22 21:00:00,12593.0 -2011-03-22 22:00:00,12374.0 -2011-03-22 23:00:00,11829.0 -2011-03-23 00:00:00,11042.0 -2011-03-21 01:00:00,8971.0 -2011-03-21 02:00:00,8569.0 -2011-03-21 03:00:00,8303.0 -2011-03-21 04:00:00,8198.0 -2011-03-21 05:00:00,8215.0 -2011-03-21 06:00:00,8460.0 -2011-03-21 07:00:00,9226.0 -2011-03-21 08:00:00,10467.0 -2011-03-21 09:00:00,11198.0 -2011-03-21 10:00:00,11439.0 -2011-03-21 11:00:00,11567.0 -2011-03-21 12:00:00,11612.0 -2011-03-21 13:00:00,11531.0 -2011-03-21 14:00:00,11413.0 -2011-03-21 15:00:00,11389.0 -2011-03-21 16:00:00,11259.0 -2011-03-21 17:00:00,11104.0 -2011-03-21 18:00:00,10986.0 -2011-03-21 19:00:00,10953.0 -2011-03-21 20:00:00,11092.0 -2011-03-21 21:00:00,11757.0 -2011-03-21 22:00:00,11719.0 -2011-03-21 23:00:00,11266.0 -2011-03-22 00:00:00,10492.0 -2011-03-20 01:00:00,9400.0 -2011-03-20 02:00:00,8919.0 -2011-03-20 03:00:00,8573.0 -2011-03-20 04:00:00,8347.0 -2011-03-20 05:00:00,8267.0 -2011-03-20 06:00:00,8224.0 -2011-03-20 07:00:00,8363.0 -2011-03-20 08:00:00,8645.0 -2011-03-20 09:00:00,8807.0 -2011-03-20 10:00:00,9174.0 -2011-03-20 11:00:00,9563.0 -2011-03-20 12:00:00,9864.0 -2011-03-20 13:00:00,10145.0 -2011-03-20 14:00:00,10192.0 -2011-03-20 15:00:00,10066.0 -2011-03-20 16:00:00,9942.0 -2011-03-20 17:00:00,9863.0 -2011-03-20 18:00:00,9734.0 -2011-03-20 19:00:00,9685.0 -2011-03-20 20:00:00,9902.0 -2011-03-20 21:00:00,10559.0 -2011-03-20 22:00:00,10513.0 -2011-03-20 23:00:00,10149.0 -2011-03-21 00:00:00,9580.0 -2011-03-19 01:00:00,9751.0 -2011-03-19 02:00:00,9199.0 -2011-03-19 03:00:00,8905.0 -2011-03-19 04:00:00,8719.0 -2011-03-19 05:00:00,8640.0 -2011-03-19 06:00:00,8732.0 -2011-03-19 07:00:00,8988.0 -2011-03-19 08:00:00,9510.0 -2011-03-19 09:00:00,9715.0 -2011-03-19 10:00:00,10018.0 -2011-03-19 11:00:00,10164.0 -2011-03-19 12:00:00,10247.0 -2011-03-19 13:00:00,10128.0 -2011-03-19 14:00:00,9961.0 -2011-03-19 15:00:00,9754.0 -2011-03-19 16:00:00,9568.0 -2011-03-19 17:00:00,9445.0 -2011-03-19 18:00:00,9411.0 -2011-03-19 19:00:00,9495.0 -2011-03-19 20:00:00,9751.0 -2011-03-19 21:00:00,10500.0 -2011-03-19 22:00:00,10594.0 -2011-03-19 23:00:00,10385.0 -2011-03-20 00:00:00,9927.0 -2011-03-18 01:00:00,9529.0 -2011-03-18 02:00:00,8931.0 -2011-03-18 03:00:00,8585.0 -2011-03-18 04:00:00,8374.0 -2011-03-18 05:00:00,8343.0 -2011-03-18 06:00:00,8563.0 -2011-03-18 07:00:00,9204.0 -2011-03-18 08:00:00,10453.0 -2011-03-18 09:00:00,11043.0 -2011-03-18 10:00:00,11293.0 -2011-03-18 11:00:00,11361.0 -2011-03-18 12:00:00,11479.0 -2011-03-18 13:00:00,11387.0 -2011-03-18 14:00:00,11329.0 -2011-03-18 15:00:00,11284.0 -2011-03-18 16:00:00,11166.0 -2011-03-18 17:00:00,10967.0 -2011-03-18 18:00:00,10827.0 -2011-03-18 19:00:00,10703.0 -2011-03-18 20:00:00,10740.0 -2011-03-18 21:00:00,11349.0 -2011-03-18 22:00:00,11331.0 -2011-03-18 23:00:00,11034.0 -2011-03-19 00:00:00,10433.0 -2011-03-17 01:00:00,9770.0 -2011-03-17 02:00:00,9235.0 -2011-03-17 03:00:00,8912.0 -2011-03-17 04:00:00,8729.0 -2011-03-17 05:00:00,8670.0 -2011-03-17 06:00:00,8865.0 -2011-03-17 07:00:00,9530.0 -2011-03-17 08:00:00,10778.0 -2011-03-17 09:00:00,11420.0 -2011-03-17 10:00:00,11547.0 -2011-03-17 11:00:00,11572.0 -2011-03-17 12:00:00,11687.0 -2011-03-17 13:00:00,11697.0 -2011-03-17 14:00:00,11701.0 -2011-03-17 15:00:00,11744.0 -2011-03-17 16:00:00,11681.0 -2011-03-17 17:00:00,11577.0 -2011-03-17 18:00:00,11527.0 -2011-03-17 19:00:00,11430.0 -2011-03-17 20:00:00,11511.0 -2011-03-17 21:00:00,11879.0 -2011-03-17 22:00:00,11708.0 -2011-03-17 23:00:00,11203.0 -2011-03-18 00:00:00,10334.0 -2011-03-16 01:00:00,10377.0 -2011-03-16 02:00:00,9853.0 -2011-03-16 03:00:00,9534.0 -2011-03-16 04:00:00,9351.0 -2011-03-16 05:00:00,9327.0 -2011-03-16 06:00:00,9576.0 -2011-03-16 07:00:00,10281.0 -2011-03-16 08:00:00,11516.0 -2011-03-16 09:00:00,12053.0 -2011-03-16 10:00:00,12070.0 -2011-03-16 11:00:00,11986.0 -2011-03-16 12:00:00,11916.0 -2011-03-16 13:00:00,11849.0 -2011-03-16 14:00:00,11719.0 -2011-03-16 15:00:00,11684.0 -2011-03-16 16:00:00,11564.0 -2011-03-16 17:00:00,11380.0 -2011-03-16 18:00:00,11228.0 -2011-03-16 19:00:00,11042.0 -2011-03-16 20:00:00,11050.0 -2011-03-16 21:00:00,11707.0 -2011-03-16 22:00:00,11646.0 -2011-03-16 23:00:00,11259.0 -2011-03-17 00:00:00,10547.0 -2011-03-15 01:00:00,10527.0 -2011-03-15 02:00:00,9944.0 -2011-03-15 03:00:00,9644.0 -2011-03-15 04:00:00,9458.0 -2011-03-15 05:00:00,9448.0 -2011-03-15 06:00:00,9651.0 -2011-03-15 07:00:00,10372.0 -2011-03-15 08:00:00,11624.0 -2011-03-15 09:00:00,12340.0 -2011-03-15 10:00:00,12438.0 -2011-03-15 11:00:00,12432.0 -2011-03-15 12:00:00,12478.0 -2011-03-15 13:00:00,12481.0 -2011-03-15 14:00:00,12430.0 -2011-03-15 15:00:00,12462.0 -2011-03-15 16:00:00,12341.0 -2011-03-15 17:00:00,12183.0 -2011-03-15 18:00:00,12139.0 -2011-03-15 19:00:00,12202.0 -2011-03-15 20:00:00,12365.0 -2011-03-15 21:00:00,12706.0 -2011-03-15 22:00:00,12504.0 -2011-03-15 23:00:00,11978.0 -2011-03-16 00:00:00,11115.0 -2011-03-14 01:00:00,9943.0 -2011-03-14 02:00:00,9580.0 -2011-03-14 03:00:00,9349.0 -2011-03-14 04:00:00,9252.0 -2011-03-14 05:00:00,9298.0 -2011-03-14 06:00:00,9582.0 -2011-03-14 07:00:00,10345.0 -2011-03-14 08:00:00,11524.0 -2011-03-14 09:00:00,12226.0 -2011-03-14 10:00:00,12470.0 -2011-03-14 11:00:00,12480.0 -2011-03-14 12:00:00,12400.0 -2011-03-14 13:00:00,12218.0 -2011-03-14 14:00:00,12084.0 -2011-03-14 15:00:00,12000.0 -2011-03-14 16:00:00,11827.0 -2011-03-14 17:00:00,11638.0 -2011-03-14 18:00:00,11545.0 -2011-03-14 19:00:00,11502.0 -2011-03-14 20:00:00,11593.0 -2011-03-14 21:00:00,12458.0 -2011-03-14 22:00:00,12458.0 -2011-03-14 23:00:00,12039.0 -2011-03-15 00:00:00,11298.0 -2011-03-13 01:00:00,10149.0 -2011-03-13 02:00:00,9719.0 -2011-03-13 04:00:00,9477.0 -2011-03-13 05:00:00,9266.0 -2011-03-13 06:00:00,9290.0 -2011-03-13 07:00:00,9332.0 -2011-03-13 08:00:00,9621.0 -2011-03-13 09:00:00,9626.0 -2011-03-13 10:00:00,9831.0 -2011-03-13 11:00:00,10028.0 -2011-03-13 12:00:00,10079.0 -2011-03-13 13:00:00,10063.0 -2011-03-13 14:00:00,10006.0 -2011-03-13 15:00:00,9881.0 -2011-03-13 16:00:00,9842.0 -2011-03-13 17:00:00,9798.0 -2011-03-13 18:00:00,9929.0 -2011-03-13 19:00:00,10165.0 -2011-03-13 20:00:00,10505.0 -2011-03-13 21:00:00,11250.0 -2011-03-13 22:00:00,11262.0 -2011-03-13 23:00:00,11028.0 -2011-03-14 00:00:00,10529.0 -2011-03-12 01:00:00,10256.0 -2011-03-12 02:00:00,9653.0 -2011-03-12 03:00:00,9274.0 -2011-03-12 04:00:00,9098.0 -2011-03-12 05:00:00,9030.0 -2011-03-12 06:00:00,9091.0 -2011-03-12 07:00:00,9396.0 -2011-03-12 08:00:00,9779.0 -2011-03-12 09:00:00,10169.0 -2011-03-12 10:00:00,10653.0 -2011-03-12 11:00:00,11014.0 -2011-03-12 12:00:00,11200.0 -2011-03-12 13:00:00,11204.0 -2011-03-12 14:00:00,11078.0 -2011-03-12 15:00:00,10914.0 -2011-03-12 16:00:00,10757.0 -2011-03-12 17:00:00,10732.0 -2011-03-12 18:00:00,10795.0 -2011-03-12 19:00:00,11154.0 -2011-03-12 20:00:00,11704.0 -2011-03-12 21:00:00,11664.0 -2011-03-12 22:00:00,11477.0 -2011-03-12 23:00:00,11163.0 -2011-03-13 00:00:00,10650.0 -2011-03-11 01:00:00,10811.0 -2011-03-11 02:00:00,10288.0 -2011-03-11 03:00:00,10017.0 -2011-03-11 04:00:00,9844.0 -2011-03-11 05:00:00,9873.0 -2011-03-11 06:00:00,10151.0 -2011-03-11 07:00:00,10838.0 -2011-03-11 08:00:00,11679.0 -2011-03-11 09:00:00,12125.0 -2011-03-11 10:00:00,12206.0 -2011-03-11 11:00:00,12156.0 -2011-03-11 12:00:00,12154.0 -2011-03-11 13:00:00,12004.0 -2011-03-11 14:00:00,11851.0 -2011-03-11 15:00:00,11724.0 -2011-03-11 16:00:00,11523.0 -2011-03-11 17:00:00,11330.0 -2011-03-11 18:00:00,11323.0 -2011-03-11 19:00:00,11653.0 -2011-03-11 20:00:00,12272.0 -2011-03-11 21:00:00,12240.0 -2011-03-11 22:00:00,11964.0 -2011-03-11 23:00:00,11555.0 -2011-03-12 00:00:00,10931.0 -2011-03-10 01:00:00,10482.0 -2011-03-10 02:00:00,10003.0 -2011-03-10 03:00:00,9716.0 -2011-03-10 04:00:00,9588.0 -2011-03-10 05:00:00,9625.0 -2011-03-10 06:00:00,9913.0 -2011-03-10 07:00:00,10646.0 -2011-03-10 08:00:00,11751.0 -2011-03-10 09:00:00,12400.0 -2011-03-10 10:00:00,12683.0 -2011-03-10 11:00:00,12796.0 -2011-03-10 12:00:00,12897.0 -2011-03-10 13:00:00,12865.0 -2011-03-10 14:00:00,12805.0 -2011-03-10 15:00:00,12774.0 -2011-03-10 16:00:00,12678.0 -2011-03-10 17:00:00,12511.0 -2011-03-10 18:00:00,12383.0 -2011-03-10 19:00:00,12497.0 -2011-03-10 20:00:00,13189.0 -2011-03-10 21:00:00,13156.0 -2011-03-10 22:00:00,12839.0 -2011-03-10 23:00:00,12369.0 -2011-03-11 00:00:00,11533.0 -2011-03-09 01:00:00,10325.0 -2011-03-09 02:00:00,9796.0 -2011-03-09 03:00:00,9537.0 -2011-03-09 04:00:00,9420.0 -2011-03-09 05:00:00,9421.0 -2011-03-09 06:00:00,9723.0 -2011-03-09 07:00:00,10440.0 -2011-03-09 08:00:00,11487.0 -2011-03-09 09:00:00,12138.0 -2011-03-09 10:00:00,12321.0 -2011-03-09 11:00:00,12405.0 -2011-03-09 12:00:00,12455.0 -2011-03-09 13:00:00,12346.0 -2011-03-09 14:00:00,12250.0 -2011-03-09 15:00:00,12254.0 -2011-03-09 16:00:00,12217.0 -2011-03-09 17:00:00,12261.0 -2011-03-09 18:00:00,12409.0 -2011-03-09 19:00:00,12824.0 -2011-03-09 20:00:00,13007.0 -2011-03-09 21:00:00,12838.0 -2011-03-09 22:00:00,12542.0 -2011-03-09 23:00:00,12034.0 -2011-03-10 00:00:00,11250.0 -2011-03-08 01:00:00,10596.0 -2011-03-08 02:00:00,10116.0 -2011-03-08 03:00:00,9859.0 -2011-03-08 04:00:00,9691.0 -2011-03-08 05:00:00,9659.0 -2011-03-08 06:00:00,9923.0 -2011-03-08 07:00:00,10662.0 -2011-03-08 08:00:00,11599.0 -2011-03-08 09:00:00,12137.0 -2011-03-08 10:00:00,12247.0 -2011-03-08 11:00:00,12162.0 -2011-03-08 12:00:00,12161.0 -2011-03-08 13:00:00,12083.0 -2011-03-08 14:00:00,12020.0 -2011-03-08 15:00:00,12012.0 -2011-03-08 16:00:00,11943.0 -2011-03-08 17:00:00,11973.0 -2011-03-08 18:00:00,12292.0 -2011-03-08 19:00:00,12707.0 -2011-03-08 20:00:00,13144.0 -2011-03-08 21:00:00,12850.0 -2011-03-08 22:00:00,12529.0 -2011-03-08 23:00:00,11942.0 -2011-03-09 00:00:00,11073.0 -2011-03-07 01:00:00,10241.0 -2011-03-07 02:00:00,9882.0 -2011-03-07 03:00:00,9726.0 -2011-03-07 04:00:00,9626.0 -2011-03-07 05:00:00,9673.0 -2011-03-07 06:00:00,9989.0 -2011-03-07 07:00:00,10742.0 -2011-03-07 08:00:00,11687.0 -2011-03-07 09:00:00,12234.0 -2011-03-07 10:00:00,12547.0 -2011-03-07 11:00:00,12677.0 -2011-03-07 12:00:00,12770.0 -2011-03-07 13:00:00,12652.0 -2011-03-07 14:00:00,12610.0 -2011-03-07 15:00:00,12613.0 -2011-03-07 16:00:00,12553.0 -2011-03-07 17:00:00,12449.0 -2011-03-07 18:00:00,12511.0 -2011-03-07 19:00:00,12836.0 -2011-03-07 20:00:00,13273.0 -2011-03-07 21:00:00,13099.0 -2011-03-07 22:00:00,12800.0 -2011-03-07 23:00:00,12225.0 -2011-03-08 00:00:00,11337.0 -2011-03-06 01:00:00,10482.0 -2011-03-06 02:00:00,10075.0 -2011-03-06 03:00:00,9711.0 -2011-03-06 04:00:00,9564.0 -2011-03-06 05:00:00,9429.0 -2011-03-06 06:00:00,9519.0 -2011-03-06 07:00:00,9645.0 -2011-03-06 08:00:00,9745.0 -2011-03-06 09:00:00,9889.0 -2011-03-06 10:00:00,10247.0 -2011-03-06 11:00:00,10428.0 -2011-03-06 12:00:00,10549.0 -2011-03-06 13:00:00,10551.0 -2011-03-06 14:00:00,10431.0 -2011-03-06 15:00:00,10329.0 -2011-03-06 16:00:00,10204.0 -2011-03-06 17:00:00,10189.0 -2011-03-06 18:00:00,10379.0 -2011-03-06 19:00:00,10902.0 -2011-03-06 20:00:00,11816.0 -2011-03-06 21:00:00,11826.0 -2011-03-06 22:00:00,11681.0 -2011-03-06 23:00:00,11333.0 -2011-03-07 00:00:00,10783.0 -2011-03-05 01:00:00,10480.0 -2011-03-05 02:00:00,10000.0 -2011-03-05 03:00:00,9696.0 -2011-03-05 04:00:00,9506.0 -2011-03-05 05:00:00,9451.0 -2011-03-05 06:00:00,9505.0 -2011-03-05 07:00:00,9839.0 -2011-03-05 08:00:00,10234.0 -2011-03-05 09:00:00,10671.0 -2011-03-05 10:00:00,11082.0 -2011-03-05 11:00:00,11463.0 -2011-03-05 12:00:00,11585.0 -2011-03-05 13:00:00,11578.0 -2011-03-05 14:00:00,11500.0 -2011-03-05 15:00:00,11439.0 -2011-03-05 16:00:00,11320.0 -2011-03-05 17:00:00,11306.0 -2011-03-05 18:00:00,11377.0 -2011-03-05 19:00:00,11745.0 -2011-03-05 20:00:00,12180.0 -2011-03-05 21:00:00,12086.0 -2011-03-05 22:00:00,11854.0 -2011-03-05 23:00:00,11565.0 -2011-03-06 00:00:00,11025.0 -2011-03-04 01:00:00,10416.0 -2011-03-04 02:00:00,9909.0 -2011-03-04 03:00:00,9622.0 -2011-03-04 04:00:00,9418.0 -2011-03-04 05:00:00,9391.0 -2011-03-04 06:00:00,9596.0 -2011-03-04 07:00:00,10226.0 -2011-03-04 08:00:00,11347.0 -2011-03-04 09:00:00,11976.0 -2011-03-04 10:00:00,12190.0 -2011-03-04 11:00:00,12271.0 -2011-03-04 12:00:00,12166.0 -2011-03-04 13:00:00,11981.0 -2011-03-04 14:00:00,11830.0 -2011-03-04 15:00:00,11793.0 -2011-03-04 16:00:00,11765.0 -2011-03-04 17:00:00,11871.0 -2011-03-04 18:00:00,12206.0 -2011-03-04 19:00:00,12527.0 -2011-03-04 20:00:00,12606.0 -2011-03-04 21:00:00,12478.0 -2011-03-04 22:00:00,12300.0 -2011-03-04 23:00:00,11912.0 -2011-03-05 00:00:00,11180.0 -2011-03-03 01:00:00,11021.0 -2011-03-03 02:00:00,10536.0 -2011-03-03 03:00:00,10256.0 -2011-03-03 04:00:00,10098.0 -2011-03-03 05:00:00,10089.0 -2011-03-03 06:00:00,10333.0 -2011-03-03 07:00:00,11043.0 -2011-03-03 08:00:00,12099.0 -2011-03-03 09:00:00,12609.0 -2011-03-03 10:00:00,12742.0 -2011-03-03 11:00:00,12748.0 -2011-03-03 12:00:00,12705.0 -2011-03-03 13:00:00,12598.0 -2011-03-03 14:00:00,12429.0 -2011-03-03 15:00:00,12308.0 -2011-03-03 16:00:00,12170.0 -2011-03-03 17:00:00,12118.0 -2011-03-03 18:00:00,12231.0 -2011-03-03 19:00:00,12592.0 -2011-03-03 20:00:00,13092.0 -2011-03-03 21:00:00,12919.0 -2011-03-03 22:00:00,12565.0 -2011-03-03 23:00:00,12033.0 -2011-03-04 00:00:00,11166.0 -2011-03-02 01:00:00,10505.0 -2011-03-02 02:00:00,10043.0 -2011-03-02 03:00:00,9790.0 -2011-03-02 04:00:00,9649.0 -2011-03-02 05:00:00,9672.0 -2011-03-02 06:00:00,9965.0 -2011-03-02 07:00:00,10764.0 -2011-03-02 08:00:00,11779.0 -2011-03-02 09:00:00,12269.0 -2011-03-02 10:00:00,12412.0 -2011-03-02 11:00:00,12475.0 -2011-03-02 12:00:00,12527.0 -2011-03-02 13:00:00,12437.0 -2011-03-02 14:00:00,12362.0 -2011-03-02 15:00:00,12306.0 -2011-03-02 16:00:00,12166.0 -2011-03-02 17:00:00,12118.0 -2011-03-02 18:00:00,12224.0 -2011-03-02 19:00:00,12694.0 -2011-03-02 20:00:00,13402.0 -2011-03-02 21:00:00,13350.0 -2011-03-02 22:00:00,13090.0 -2011-03-02 23:00:00,12595.0 -2011-03-03 00:00:00,11750.0 -2011-03-01 01:00:00,10795.0 -2011-03-01 02:00:00,10351.0 -2011-03-01 03:00:00,10131.0 -2011-03-01 04:00:00,10005.0 -2011-03-01 05:00:00,10026.0 -2011-03-01 06:00:00,10314.0 -2011-03-01 07:00:00,11036.0 -2011-03-01 08:00:00,12041.0 -2011-03-01 09:00:00,12508.0 -2011-03-01 10:00:00,12621.0 -2011-03-01 11:00:00,12618.0 -2011-03-01 12:00:00,12581.0 -2011-03-01 13:00:00,12462.0 -2011-03-01 14:00:00,12307.0 -2011-03-01 15:00:00,12229.0 -2011-03-01 16:00:00,12086.0 -2011-03-01 17:00:00,12058.0 -2011-03-01 18:00:00,12128.0 -2011-03-01 19:00:00,12409.0 -2011-03-01 20:00:00,12987.0 -2011-03-01 21:00:00,12871.0 -2011-03-01 22:00:00,12587.0 -2011-03-01 23:00:00,12074.0 -2011-03-02 00:00:00,11278.0 -2011-02-28 01:00:00,10288.0 -2011-02-28 02:00:00,9917.0 -2011-02-28 03:00:00,9707.0 -2011-02-28 04:00:00,9652.0 -2011-02-28 05:00:00,9694.0 -2011-02-28 06:00:00,9998.0 -2011-02-28 07:00:00,10809.0 -2011-02-28 08:00:00,11976.0 -2011-02-28 09:00:00,12620.0 -2011-02-28 10:00:00,12764.0 -2011-02-28 11:00:00,12752.0 -2011-02-28 12:00:00,12769.0 -2011-02-28 13:00:00,12635.0 -2011-02-28 14:00:00,12479.0 -2011-02-28 15:00:00,12373.0 -2011-02-28 16:00:00,12198.0 -2011-02-28 17:00:00,12059.0 -2011-02-28 18:00:00,12126.0 -2011-02-28 19:00:00,12583.0 -2011-02-28 20:00:00,13257.0 -2011-02-28 21:00:00,13169.0 -2011-02-28 22:00:00,12900.0 -2011-02-28 23:00:00,12400.0 -2011-03-01 00:00:00,11527.0 -2011-02-27 01:00:00,10356.0 -2011-02-27 02:00:00,9890.0 -2011-02-27 03:00:00,9518.0 -2011-02-27 04:00:00,9372.0 -2011-02-27 05:00:00,9313.0 -2011-02-27 06:00:00,9371.0 -2011-02-27 07:00:00,9525.0 -2011-02-27 08:00:00,9664.0 -2011-02-27 09:00:00,9681.0 -2011-02-27 10:00:00,9917.0 -2011-02-27 11:00:00,9984.0 -2011-02-27 12:00:00,10072.0 -2011-02-27 13:00:00,10149.0 -2011-02-27 14:00:00,10307.0 -2011-02-27 15:00:00,10367.0 -2011-02-27 16:00:00,10516.0 -2011-02-27 17:00:00,10624.0 -2011-02-27 18:00:00,11018.0 -2011-02-27 19:00:00,11594.0 -2011-02-27 20:00:00,11941.0 -2011-02-27 21:00:00,11836.0 -2011-02-27 22:00:00,11679.0 -2011-02-27 23:00:00,11366.0 -2011-02-28 00:00:00,10878.0 -2011-02-26 01:00:00,10806.0 -2011-02-26 02:00:00,10280.0 -2011-02-26 03:00:00,9972.0 -2011-02-26 04:00:00,9785.0 -2011-02-26 05:00:00,9739.0 -2011-02-26 06:00:00,9779.0 -2011-02-26 07:00:00,10096.0 -2011-02-26 08:00:00,10466.0 -2011-02-26 09:00:00,10751.0 -2011-02-26 10:00:00,11216.0 -2011-02-26 11:00:00,11411.0 -2011-02-26 12:00:00,11588.0 -2011-02-26 13:00:00,11574.0 -2011-02-26 14:00:00,11458.0 -2011-02-26 15:00:00,11428.0 -2011-02-26 16:00:00,11267.0 -2011-02-26 17:00:00,11314.0 -2011-02-26 18:00:00,11456.0 -2011-02-26 19:00:00,11925.0 -2011-02-26 20:00:00,12181.0 -2011-02-26 21:00:00,12039.0 -2011-02-26 22:00:00,11845.0 -2011-02-26 23:00:00,11478.0 -2011-02-27 00:00:00,10967.0 -2011-02-25 01:00:00,10975.0 -2011-02-25 02:00:00,10444.0 -2011-02-25 03:00:00,10195.0 -2011-02-25 04:00:00,10064.0 -2011-02-25 05:00:00,10048.0 -2011-02-25 06:00:00,10286.0 -2011-02-25 07:00:00,10965.0 -2011-02-25 08:00:00,11942.0 -2011-02-25 09:00:00,12381.0 -2011-02-25 10:00:00,12623.0 -2011-02-25 11:00:00,12673.0 -2011-02-25 12:00:00,12689.0 -2011-02-25 13:00:00,12518.0 -2011-02-25 14:00:00,12428.0 -2011-02-25 15:00:00,12336.0 -2011-02-25 16:00:00,12122.0 -2011-02-25 17:00:00,11994.0 -2011-02-25 18:00:00,12124.0 -2011-02-25 19:00:00,12655.0 -2011-02-25 20:00:00,12977.0 -2011-02-25 21:00:00,12770.0 -2011-02-25 22:00:00,12495.0 -2011-02-25 23:00:00,12048.0 -2011-02-26 00:00:00,11438.0 -2011-02-24 01:00:00,10890.0 -2011-02-24 02:00:00,10336.0 -2011-02-24 03:00:00,9989.0 -2011-02-24 04:00:00,9817.0 -2011-02-24 05:00:00,9793.0 -2011-02-24 06:00:00,10042.0 -2011-02-24 07:00:00,10771.0 -2011-02-24 08:00:00,11901.0 -2011-02-24 09:00:00,12468.0 -2011-02-24 10:00:00,12683.0 -2011-02-24 11:00:00,12683.0 -2011-02-24 12:00:00,12651.0 -2011-02-24 13:00:00,12566.0 -2011-02-24 14:00:00,12544.0 -2011-02-24 15:00:00,12576.0 -2011-02-24 16:00:00,12525.0 -2011-02-24 17:00:00,12603.0 -2011-02-24 18:00:00,12851.0 -2011-02-24 19:00:00,13392.0 -2011-02-24 20:00:00,13584.0 -2011-02-24 21:00:00,13386.0 -2011-02-24 22:00:00,13015.0 -2011-02-24 23:00:00,12538.0 -2011-02-25 00:00:00,11757.0 -2011-02-23 01:00:00,11079.0 -2011-02-23 02:00:00,10578.0 -2011-02-23 03:00:00,10264.0 -2011-02-23 04:00:00,10095.0 -2011-02-23 05:00:00,10112.0 -2011-02-23 06:00:00,10394.0 -2011-02-23 07:00:00,11156.0 -2011-02-23 08:00:00,12316.0 -2011-02-23 09:00:00,12882.0 -2011-02-23 10:00:00,13108.0 -2011-02-23 11:00:00,13194.0 -2011-02-23 12:00:00,13226.0 -2011-02-23 13:00:00,13142.0 -2011-02-23 14:00:00,13020.0 -2011-02-23 15:00:00,12917.0 -2011-02-23 16:00:00,12754.0 -2011-02-23 17:00:00,12730.0 -2011-02-23 18:00:00,12925.0 -2011-02-23 19:00:00,13327.0 -2011-02-23 20:00:00,13616.0 -2011-02-23 21:00:00,13432.0 -2011-02-23 22:00:00,13122.0 -2011-02-23 23:00:00,12578.0 -2011-02-24 00:00:00,11699.0 -2011-02-22 01:00:00,11016.0 -2011-02-22 02:00:00,10550.0 -2011-02-22 03:00:00,10234.0 -2011-02-22 04:00:00,10073.0 -2011-02-22 05:00:00,10069.0 -2011-02-22 06:00:00,10349.0 -2011-02-22 07:00:00,11137.0 -2011-02-22 08:00:00,12262.0 -2011-02-22 09:00:00,12768.0 -2011-02-22 10:00:00,12965.0 -2011-02-22 11:00:00,13040.0 -2011-02-22 12:00:00,13126.0 -2011-02-22 13:00:00,13102.0 -2011-02-22 14:00:00,13082.0 -2011-02-22 15:00:00,13066.0 -2011-02-22 16:00:00,12950.0 -2011-02-22 17:00:00,12927.0 -2011-02-22 18:00:00,13020.0 -2011-02-22 19:00:00,13538.0 -2011-02-22 20:00:00,13823.0 -2011-02-22 21:00:00,13625.0 -2011-02-22 22:00:00,13283.0 -2011-02-22 23:00:00,12719.0 -2011-02-23 00:00:00,11855.0 -2011-02-21 01:00:00,10270.0 -2011-02-21 02:00:00,9838.0 -2011-02-21 03:00:00,9598.0 -2011-02-21 04:00:00,9514.0 -2011-02-21 05:00:00,9545.0 -2011-02-21 06:00:00,9809.0 -2011-02-21 07:00:00,10483.0 -2011-02-21 08:00:00,11411.0 -2011-02-21 09:00:00,12031.0 -2011-02-21 10:00:00,12446.0 -2011-02-21 11:00:00,12777.0 -2011-02-21 12:00:00,13026.0 -2011-02-21 13:00:00,13101.0 -2011-02-21 14:00:00,13086.0 -2011-02-21 15:00:00,13083.0 -2011-02-21 16:00:00,12988.0 -2011-02-21 17:00:00,12907.0 -2011-02-21 18:00:00,13085.0 -2011-02-21 19:00:00,13568.0 -2011-02-21 20:00:00,13721.0 -2011-02-21 21:00:00,13444.0 -2011-02-21 22:00:00,13137.0 -2011-02-21 23:00:00,12577.0 -2011-02-22 00:00:00,11764.0 -2011-02-20 01:00:00,10311.0 -2011-02-20 02:00:00,9854.0 -2011-02-20 03:00:00,9622.0 -2011-02-20 04:00:00,9338.0 -2011-02-20 05:00:00,9306.0 -2011-02-20 06:00:00,9277.0 -2011-02-20 07:00:00,9427.0 -2011-02-20 08:00:00,9682.0 -2011-02-20 09:00:00,9872.0 -2011-02-20 10:00:00,10207.0 -2011-02-20 11:00:00,10676.0 -2011-02-20 12:00:00,10794.0 -2011-02-20 13:00:00,10988.0 -2011-02-20 14:00:00,11006.0 -2011-02-20 15:00:00,11008.0 -2011-02-20 16:00:00,10978.0 -2011-02-20 17:00:00,11047.0 -2011-02-20 18:00:00,11370.0 -2011-02-20 19:00:00,11884.0 -2011-02-20 20:00:00,12027.0 -2011-02-20 21:00:00,11886.0 -2011-02-20 22:00:00,11679.0 -2011-02-20 23:00:00,11310.0 -2011-02-21 00:00:00,10815.0 -2011-02-19 01:00:00,10439.0 -2011-02-19 02:00:00,9912.0 -2011-02-19 03:00:00,9646.0 -2011-02-19 04:00:00,9447.0 -2011-02-19 05:00:00,9392.0 -2011-02-19 06:00:00,9506.0 -2011-02-19 07:00:00,9836.0 -2011-02-19 08:00:00,10269.0 -2011-02-19 09:00:00,10461.0 -2011-02-19 10:00:00,10772.0 -2011-02-19 11:00:00,10944.0 -2011-02-19 12:00:00,10990.0 -2011-02-19 13:00:00,10914.0 -2011-02-19 14:00:00,10760.0 -2011-02-19 15:00:00,10533.0 -2011-02-19 16:00:00,10317.0 -2011-02-19 17:00:00,10290.0 -2011-02-19 18:00:00,10442.0 -2011-02-19 19:00:00,11119.0 -2011-02-19 20:00:00,11738.0 -2011-02-19 21:00:00,11743.0 -2011-02-19 22:00:00,11595.0 -2011-02-19 23:00:00,11341.0 -2011-02-20 00:00:00,10810.0 -2011-02-18 01:00:00,9948.0 -2011-02-18 02:00:00,9394.0 -2011-02-18 03:00:00,9186.0 -2011-02-18 04:00:00,9024.0 -2011-02-18 05:00:00,9068.0 -2011-02-18 06:00:00,9361.0 -2011-02-18 07:00:00,10083.0 -2011-02-18 08:00:00,11179.0 -2011-02-18 09:00:00,11715.0 -2011-02-18 10:00:00,11836.0 -2011-02-18 11:00:00,11918.0 -2011-02-18 12:00:00,11963.0 -2011-02-18 13:00:00,11900.0 -2011-02-18 14:00:00,11803.0 -2011-02-18 15:00:00,11729.0 -2011-02-18 16:00:00,11549.0 -2011-02-18 17:00:00,11411.0 -2011-02-18 18:00:00,11480.0 -2011-02-18 19:00:00,11948.0 -2011-02-18 20:00:00,12402.0 -2011-02-18 21:00:00,12224.0 -2011-02-18 22:00:00,11979.0 -2011-02-18 23:00:00,11673.0 -2011-02-19 00:00:00,11026.0 -2011-02-17 01:00:00,9997.0 -2011-02-17 02:00:00,9511.0 -2011-02-17 03:00:00,9195.0 -2011-02-17 04:00:00,9008.0 -2011-02-17 05:00:00,8935.0 -2011-02-17 06:00:00,9178.0 -2011-02-17 07:00:00,9904.0 -2011-02-17 08:00:00,11017.0 -2011-02-17 09:00:00,11690.0 -2011-02-17 10:00:00,11886.0 -2011-02-17 11:00:00,12054.0 -2011-02-17 12:00:00,12139.0 -2011-02-17 13:00:00,12095.0 -2011-02-17 14:00:00,12058.0 -2011-02-17 15:00:00,12068.0 -2011-02-17 16:00:00,11983.0 -2011-02-17 17:00:00,11947.0 -2011-02-17 18:00:00,12042.0 -2011-02-17 19:00:00,12353.0 -2011-02-17 20:00:00,12627.0 -2011-02-17 21:00:00,12427.0 -2011-02-17 22:00:00,12067.0 -2011-02-17 23:00:00,11560.0 -2011-02-18 00:00:00,10722.0 -2011-02-16 01:00:00,10678.0 -2011-02-16 02:00:00,10103.0 -2011-02-16 03:00:00,9777.0 -2011-02-16 04:00:00,9614.0 -2011-02-16 05:00:00,9568.0 -2011-02-16 06:00:00,9811.0 -2011-02-16 07:00:00,10513.0 -2011-02-16 08:00:00,11607.0 -2011-02-16 09:00:00,12080.0 -2011-02-16 10:00:00,12232.0 -2011-02-16 11:00:00,12183.0 -2011-02-16 12:00:00,12120.0 -2011-02-16 13:00:00,12025.0 -2011-02-16 14:00:00,11858.0 -2011-02-16 15:00:00,11810.0 -2011-02-16 16:00:00,11724.0 -2011-02-16 17:00:00,11650.0 -2011-02-16 18:00:00,11629.0 -2011-02-16 19:00:00,12300.0 -2011-02-16 20:00:00,12697.0 -2011-02-16 21:00:00,12535.0 -2011-02-16 22:00:00,12185.0 -2011-02-16 23:00:00,11633.0 -2011-02-17 00:00:00,10814.0 -2011-02-15 01:00:00,10697.0 -2011-02-15 02:00:00,10233.0 -2011-02-15 03:00:00,9959.0 -2011-02-15 04:00:00,9814.0 -2011-02-15 05:00:00,9844.0 -2011-02-15 06:00:00,10132.0 -2011-02-15 07:00:00,10945.0 -2011-02-15 08:00:00,12091.0 -2011-02-15 09:00:00,12596.0 -2011-02-15 10:00:00,12666.0 -2011-02-15 11:00:00,12610.0 -2011-02-15 12:00:00,12697.0 -2011-02-15 13:00:00,12684.0 -2011-02-15 14:00:00,12608.0 -2011-02-15 15:00:00,12579.0 -2011-02-15 16:00:00,12505.0 -2011-02-15 17:00:00,12442.0 -2011-02-15 18:00:00,12617.0 -2011-02-15 19:00:00,13184.0 -2011-02-15 20:00:00,13472.0 -2011-02-15 21:00:00,13251.0 -2011-02-15 22:00:00,12919.0 -2011-02-15 23:00:00,12337.0 -2011-02-16 00:00:00,11480.0 -2011-02-14 01:00:00,9986.0 -2011-02-14 02:00:00,9583.0 -2011-02-14 03:00:00,9442.0 -2011-02-14 04:00:00,9387.0 -2011-02-14 05:00:00,9428.0 -2011-02-14 06:00:00,9704.0 -2011-02-14 07:00:00,10469.0 -2011-02-14 08:00:00,11676.0 -2011-02-14 09:00:00,12253.0 -2011-02-14 10:00:00,12492.0 -2011-02-14 11:00:00,12589.0 -2011-02-14 12:00:00,12503.0 -2011-02-14 13:00:00,12409.0 -2011-02-14 14:00:00,12295.0 -2011-02-14 15:00:00,12149.0 -2011-02-14 16:00:00,11980.0 -2011-02-14 17:00:00,11846.0 -2011-02-14 18:00:00,11941.0 -2011-02-14 19:00:00,12581.0 -2011-02-14 20:00:00,13013.0 -2011-02-14 21:00:00,12874.0 -2011-02-14 22:00:00,12629.0 -2011-02-14 23:00:00,12180.0 -2011-02-15 00:00:00,11431.0 -2011-02-13 01:00:00,10424.0 -2011-02-13 02:00:00,9944.0 -2011-02-13 03:00:00,9685.0 -2011-02-13 04:00:00,9497.0 -2011-02-13 05:00:00,9396.0 -2011-02-13 06:00:00,9490.0 -2011-02-13 07:00:00,9618.0 -2011-02-13 08:00:00,9878.0 -2011-02-13 09:00:00,9852.0 -2011-02-13 10:00:00,10175.0 -2011-02-13 11:00:00,10283.0 -2011-02-13 12:00:00,10300.0 -2011-02-13 13:00:00,10181.0 -2011-02-13 14:00:00,10128.0 -2011-02-13 15:00:00,9988.0 -2011-02-13 16:00:00,9912.0 -2011-02-13 17:00:00,9896.0 -2011-02-13 18:00:00,10164.0 -2011-02-13 19:00:00,11022.0 -2011-02-13 20:00:00,11726.0 -2011-02-13 21:00:00,11670.0 -2011-02-13 22:00:00,11499.0 -2011-02-13 23:00:00,11138.0 -2011-02-14 00:00:00,10560.0 -2011-02-12 01:00:00,11532.0 -2011-02-12 02:00:00,10968.0 -2011-02-12 03:00:00,10562.0 -2011-02-12 04:00:00,10299.0 -2011-02-12 05:00:00,10260.0 -2011-02-12 06:00:00,10302.0 -2011-02-12 07:00:00,10638.0 -2011-02-12 08:00:00,11059.0 -2011-02-12 09:00:00,11229.0 -2011-02-12 10:00:00,11430.0 -2011-02-12 11:00:00,11568.0 -2011-02-12 12:00:00,11533.0 -2011-02-12 13:00:00,11429.0 -2011-02-12 14:00:00,11213.0 -2011-02-12 15:00:00,11074.0 -2011-02-12 16:00:00,10984.0 -2011-02-12 17:00:00,11032.0 -2011-02-12 18:00:00,11221.0 -2011-02-12 19:00:00,11975.0 -2011-02-12 20:00:00,12212.0 -2011-02-12 21:00:00,12119.0 -2011-02-12 22:00:00,11923.0 -2011-02-12 23:00:00,11551.0 -2011-02-13 00:00:00,10995.0 -2011-02-11 01:00:00,12311.0 -2011-02-11 02:00:00,11892.0 -2011-02-11 03:00:00,11588.0 -2011-02-11 04:00:00,11482.0 -2011-02-11 05:00:00,11446.0 -2011-02-11 06:00:00,11669.0 -2011-02-11 07:00:00,12287.0 -2011-02-11 08:00:00,13296.0 -2011-02-11 09:00:00,13682.0 -2011-02-11 10:00:00,13832.0 -2011-02-11 11:00:00,13944.0 -2011-02-11 12:00:00,13971.0 -2011-02-11 13:00:00,13810.0 -2011-02-11 14:00:00,13674.0 -2011-02-11 15:00:00,13688.0 -2011-02-11 16:00:00,13620.0 -2011-02-11 17:00:00,13433.0 -2011-02-11 18:00:00,13524.0 -2011-02-11 19:00:00,14076.0 -2011-02-11 20:00:00,14173.0 -2011-02-11 21:00:00,13887.0 -2011-02-11 22:00:00,13513.0 -2011-02-11 23:00:00,13069.0 -2011-02-12 00:00:00,12302.0 -2011-02-10 01:00:00,12685.0 -2011-02-10 02:00:00,12249.0 -2011-02-10 03:00:00,12031.0 -2011-02-10 04:00:00,11918.0 -2011-02-10 05:00:00,11957.0 -2011-02-10 06:00:00,12206.0 -2011-02-10 07:00:00,12838.0 -2011-02-10 08:00:00,13973.0 -2011-02-10 09:00:00,14371.0 -2011-02-10 10:00:00,14402.0 -2011-02-10 11:00:00,14328.0 -2011-02-10 12:00:00,14259.0 -2011-02-10 13:00:00,14140.0 -2011-02-10 14:00:00,13991.0 -2011-02-10 15:00:00,13899.0 -2011-02-10 16:00:00,13758.0 -2011-02-10 17:00:00,13662.0 -2011-02-10 18:00:00,13821.0 -2011-02-10 19:00:00,14582.0 -2011-02-10 20:00:00,14965.0 -2011-02-10 21:00:00,14831.0 -2011-02-10 22:00:00,14469.0 -2011-02-10 23:00:00,13965.0 -2011-02-11 00:00:00,13120.0 -2011-02-09 01:00:00,12435.0 -2011-02-09 02:00:00,11947.0 -2011-02-09 03:00:00,11720.0 -2011-02-09 04:00:00,11593.0 -2011-02-09 05:00:00,11615.0 -2011-02-09 06:00:00,11848.0 -2011-02-09 07:00:00,12548.0 -2011-02-09 08:00:00,13669.0 -2011-02-09 09:00:00,14098.0 -2011-02-09 10:00:00,14219.0 -2011-02-09 11:00:00,14226.0 -2011-02-09 12:00:00,14162.0 -2011-02-09 13:00:00,14025.0 -2011-02-09 14:00:00,13860.0 -2011-02-09 15:00:00,13807.0 -2011-02-09 16:00:00,13681.0 -2011-02-09 17:00:00,13677.0 -2011-02-09 18:00:00,13873.0 -2011-02-09 19:00:00,14705.0 -2011-02-09 20:00:00,15052.0 -2011-02-09 21:00:00,14978.0 -2011-02-09 22:00:00,14728.0 -2011-02-09 23:00:00,14230.0 -2011-02-10 00:00:00,13420.0 -2011-02-08 01:00:00,11387.0 -2011-02-08 02:00:00,10950.0 -2011-02-08 03:00:00,10715.0 -2011-02-08 04:00:00,10628.0 -2011-02-08 05:00:00,10698.0 -2011-02-08 06:00:00,11005.0 -2011-02-08 07:00:00,11794.0 -2011-02-08 08:00:00,12997.0 -2011-02-08 09:00:00,13470.0 -2011-02-08 10:00:00,13618.0 -2011-02-08 11:00:00,13580.0 -2011-02-08 12:00:00,13547.0 -2011-02-08 13:00:00,13445.0 -2011-02-08 14:00:00,13335.0 -2011-02-08 15:00:00,13294.0 -2011-02-08 16:00:00,13191.0 -2011-02-08 17:00:00,13203.0 -2011-02-08 18:00:00,13503.0 -2011-02-08 19:00:00,14294.0 -2011-02-08 20:00:00,14795.0 -2011-02-08 21:00:00,14726.0 -2011-02-08 22:00:00,14527.0 -2011-02-08 23:00:00,13998.0 -2011-02-09 00:00:00,13195.0 -2011-02-07 01:00:00,10486.0 -2011-02-07 02:00:00,10087.0 -2011-02-07 03:00:00,9919.0 -2011-02-07 04:00:00,9782.0 -2011-02-07 05:00:00,9841.0 -2011-02-07 06:00:00,10185.0 -2011-02-07 07:00:00,11003.0 -2011-02-07 08:00:00,12238.0 -2011-02-07 09:00:00,12714.0 -2011-02-07 10:00:00,12851.0 -2011-02-07 11:00:00,12834.0 -2011-02-07 12:00:00,12803.0 -2011-02-07 13:00:00,12717.0 -2011-02-07 14:00:00,12667.0 -2011-02-07 15:00:00,12788.0 -2011-02-07 16:00:00,12809.0 -2011-02-07 17:00:00,12875.0 -2011-02-07 18:00:00,13095.0 -2011-02-07 19:00:00,13756.0 -2011-02-07 20:00:00,13894.0 -2011-02-07 21:00:00,13701.0 -2011-02-07 22:00:00,13445.0 -2011-02-07 23:00:00,12913.0 -2011-02-08 00:00:00,12116.0 -2011-02-06 01:00:00,10900.0 -2011-02-06 02:00:00,10404.0 -2011-02-06 03:00:00,10150.0 -2011-02-06 04:00:00,9912.0 -2011-02-06 05:00:00,9827.0 -2011-02-06 06:00:00,9818.0 -2011-02-06 07:00:00,9988.0 -2011-02-06 08:00:00,10253.0 -2011-02-06 09:00:00,10348.0 -2011-02-06 10:00:00,10571.0 -2011-02-06 11:00:00,10854.0 -2011-02-06 12:00:00,10988.0 -2011-02-06 13:00:00,11075.0 -2011-02-06 14:00:00,11088.0 -2011-02-06 15:00:00,11099.0 -2011-02-06 16:00:00,11144.0 -2011-02-06 17:00:00,11193.0 -2011-02-06 18:00:00,11487.0 -2011-02-06 19:00:00,12069.0 -2011-02-06 20:00:00,12143.0 -2011-02-06 21:00:00,11957.0 -2011-02-06 22:00:00,11795.0 -2011-02-06 23:00:00,11564.0 -2011-02-07 00:00:00,11071.0 -2011-02-05 01:00:00,11902.0 -2011-02-05 02:00:00,11404.0 -2011-02-05 03:00:00,11067.0 -2011-02-05 04:00:00,10888.0 -2011-02-05 05:00:00,10778.0 -2011-02-05 06:00:00,10879.0 -2011-02-05 07:00:00,11138.0 -2011-02-05 08:00:00,11600.0 -2011-02-05 09:00:00,11731.0 -2011-02-05 10:00:00,12002.0 -2011-02-05 11:00:00,12101.0 -2011-02-05 12:00:00,12126.0 -2011-02-05 13:00:00,11979.0 -2011-02-05 14:00:00,11747.0 -2011-02-05 15:00:00,11517.0 -2011-02-05 16:00:00,11296.0 -2011-02-05 17:00:00,11227.0 -2011-02-05 18:00:00,11512.0 -2011-02-05 19:00:00,12335.0 -2011-02-05 20:00:00,12675.0 -2011-02-05 21:00:00,12555.0 -2011-02-05 22:00:00,12337.0 -2011-02-05 23:00:00,12027.0 -2011-02-06 00:00:00,11457.0 -2011-02-04 01:00:00,12319.0 -2011-02-04 02:00:00,11865.0 -2011-02-04 03:00:00,11604.0 -2011-02-04 04:00:00,11428.0 -2011-02-04 05:00:00,11467.0 -2011-02-04 06:00:00,11642.0 -2011-02-04 07:00:00,12302.0 -2011-02-04 08:00:00,13349.0 -2011-02-04 09:00:00,13767.0 -2011-02-04 10:00:00,13739.0 -2011-02-04 11:00:00,13744.0 -2011-02-04 12:00:00,13671.0 -2011-02-04 13:00:00,13528.0 -2011-02-04 14:00:00,13364.0 -2011-02-04 15:00:00,13216.0 -2011-02-04 16:00:00,13014.0 -2011-02-04 17:00:00,12875.0 -2011-02-04 18:00:00,13047.0 -2011-02-04 19:00:00,13838.0 -2011-02-04 20:00:00,14093.0 -2011-02-04 21:00:00,13924.0 -2011-02-04 22:00:00,13619.0 -2011-02-04 23:00:00,13278.0 -2011-02-05 00:00:00,12631.0 -2011-02-03 01:00:00,11068.0 -2011-02-03 02:00:00,10773.0 -2011-02-03 03:00:00,10667.0 -2011-02-03 04:00:00,10618.0 -2011-02-03 05:00:00,10716.0 -2011-02-03 06:00:00,11028.0 -2011-02-03 07:00:00,11709.0 -2011-02-03 08:00:00,12649.0 -2011-02-03 09:00:00,13098.0 -2011-02-03 10:00:00,13350.0 -2011-02-03 11:00:00,13510.0 -2011-02-03 12:00:00,13592.0 -2011-02-03 13:00:00,13559.0 -2011-02-03 14:00:00,13518.0 -2011-02-03 15:00:00,13458.0 -2011-02-03 16:00:00,13356.0 -2011-02-03 17:00:00,13212.0 -2011-02-03 18:00:00,13445.0 -2011-02-03 19:00:00,14378.0 -2011-02-03 20:00:00,14721.0 -2011-02-03 21:00:00,14654.0 -2011-02-03 22:00:00,14382.0 -2011-02-03 23:00:00,13889.0 -2011-02-04 00:00:00,13072.0 -2011-02-02 01:00:00,11056.0 -2011-02-02 02:00:00,10554.0 -2011-02-02 03:00:00,10295.0 -2011-02-02 04:00:00,10153.0 -2011-02-02 05:00:00,10116.0 -2011-02-02 06:00:00,10207.0 -2011-02-02 07:00:00,10475.0 -2011-02-02 08:00:00,11086.0 -2011-02-02 09:00:00,11147.0 -2011-02-02 10:00:00,11352.0 -2011-02-02 11:00:00,11439.0 -2011-02-02 12:00:00,11394.0 -2011-02-02 13:00:00,11318.0 -2011-02-02 14:00:00,11176.0 -2011-02-02 15:00:00,11045.0 -2011-02-02 16:00:00,10999.0 -2011-02-02 17:00:00,11179.0 -2011-02-02 18:00:00,11605.0 -2011-02-02 19:00:00,12666.0 -2011-02-02 20:00:00,12960.0 -2011-02-02 21:00:00,12810.0 -2011-02-02 22:00:00,12552.0 -2011-02-02 23:00:00,12182.0 -2011-02-03 00:00:00,11580.0 -2011-02-01 01:00:00,11665.0 -2011-02-01 02:00:00,11152.0 -2011-02-01 03:00:00,10883.0 -2011-02-01 04:00:00,10762.0 -2011-02-01 05:00:00,10776.0 -2011-02-01 06:00:00,11058.0 -2011-02-01 07:00:00,11786.0 -2011-02-01 08:00:00,12961.0 -2011-02-01 09:00:00,13502.0 -2011-02-01 10:00:00,13695.0 -2011-02-01 11:00:00,13811.0 -2011-02-01 12:00:00,13937.0 -2011-02-01 13:00:00,13944.0 -2011-02-01 14:00:00,13892.0 -2011-02-01 15:00:00,13847.0 -2011-02-01 16:00:00,13629.0 -2011-02-01 17:00:00,13314.0 -2011-02-01 18:00:00,13517.0 -2011-02-01 19:00:00,14175.0 -2011-02-01 20:00:00,13967.0 -2011-02-01 21:00:00,13467.0 -2011-02-01 22:00:00,12972.0 -2011-02-01 23:00:00,12462.0 -2011-02-02 00:00:00,11754.0 -2011-01-31 01:00:00,10565.0 -2011-01-31 02:00:00,10175.0 -2011-01-31 03:00:00,10056.0 -2011-01-31 04:00:00,9992.0 -2011-01-31 05:00:00,10089.0 -2011-01-31 06:00:00,10470.0 -2011-01-31 07:00:00,11255.0 -2011-01-31 08:00:00,12599.0 -2011-01-31 09:00:00,13226.0 -2011-01-31 10:00:00,13447.0 -2011-01-31 11:00:00,13488.0 -2011-01-31 12:00:00,13521.0 -2011-01-31 13:00:00,13316.0 -2011-01-31 14:00:00,13210.0 -2011-01-31 15:00:00,13273.0 -2011-01-31 16:00:00,13249.0 -2011-01-31 17:00:00,13324.0 -2011-01-31 18:00:00,13631.0 -2011-01-31 19:00:00,14332.0 -2011-01-31 20:00:00,14390.0 -2011-01-31 21:00:00,14165.0 -2011-01-31 22:00:00,13907.0 -2011-01-31 23:00:00,13332.0 -2011-02-01 00:00:00,12453.0 -2011-01-30 01:00:00,10436.0 -2011-01-30 02:00:00,9949.0 -2011-01-30 03:00:00,9611.0 -2011-01-30 04:00:00,9433.0 -2011-01-30 05:00:00,9361.0 -2011-01-30 06:00:00,9339.0 -2011-01-30 07:00:00,9520.0 -2011-01-30 08:00:00,9816.0 -2011-01-30 09:00:00,9959.0 -2011-01-30 10:00:00,10282.0 -2011-01-30 11:00:00,10607.0 -2011-01-30 12:00:00,10829.0 -2011-01-30 13:00:00,10946.0 -2011-01-30 14:00:00,10953.0 -2011-01-30 15:00:00,10947.0 -2011-01-30 16:00:00,10923.0 -2011-01-30 17:00:00,10985.0 -2011-01-30 18:00:00,11351.0 -2011-01-30 19:00:00,12239.0 -2011-01-30 20:00:00,12435.0 -2011-01-30 21:00:00,12346.0 -2011-01-30 22:00:00,12140.0 -2011-01-30 23:00:00,11759.0 -2011-01-31 00:00:00,11165.0 -2011-01-29 01:00:00,11256.0 -2011-01-29 02:00:00,10679.0 -2011-01-29 03:00:00,10336.0 -2011-01-29 04:00:00,10057.0 -2011-01-29 05:00:00,10005.0 -2011-01-29 06:00:00,10013.0 -2011-01-29 07:00:00,10299.0 -2011-01-29 08:00:00,10750.0 -2011-01-29 09:00:00,11031.0 -2011-01-29 10:00:00,11375.0 -2011-01-29 11:00:00,11658.0 -2011-01-29 12:00:00,11704.0 -2011-01-29 13:00:00,11665.0 -2011-01-29 14:00:00,11464.0 -2011-01-29 15:00:00,11254.0 -2011-01-29 16:00:00,11073.0 -2011-01-29 17:00:00,11093.0 -2011-01-29 18:00:00,11382.0 -2011-01-29 19:00:00,12161.0 -2011-01-29 20:00:00,12223.0 -2011-01-29 21:00:00,12045.0 -2011-01-29 22:00:00,11866.0 -2011-01-29 23:00:00,11542.0 -2011-01-30 00:00:00,11001.0 -2011-01-28 01:00:00,11462.0 -2011-01-28 02:00:00,10937.0 -2011-01-28 03:00:00,10668.0 -2011-01-28 04:00:00,10445.0 -2011-01-28 05:00:00,10493.0 -2011-01-28 06:00:00,10737.0 -2011-01-28 07:00:00,11451.0 -2011-01-28 08:00:00,12591.0 -2011-01-28 09:00:00,13023.0 -2011-01-28 10:00:00,13058.0 -2011-01-28 11:00:00,13017.0 -2011-01-28 12:00:00,12893.0 -2011-01-28 13:00:00,12773.0 -2011-01-28 14:00:00,12650.0 -2011-01-28 15:00:00,12693.0 -2011-01-28 16:00:00,12619.0 -2011-01-28 17:00:00,12632.0 -2011-01-28 18:00:00,12966.0 -2011-01-28 19:00:00,13631.0 -2011-01-28 20:00:00,13557.0 -2011-01-28 21:00:00,13348.0 -2011-01-28 22:00:00,13073.0 -2011-01-28 23:00:00,12705.0 -2011-01-29 00:00:00,11965.0 -2011-01-27 01:00:00,11531.0 -2011-01-27 02:00:00,11061.0 -2011-01-27 03:00:00,10814.0 -2011-01-27 04:00:00,10643.0 -2011-01-27 05:00:00,10685.0 -2011-01-27 06:00:00,10967.0 -2011-01-27 07:00:00,11637.0 -2011-01-27 08:00:00,12812.0 -2011-01-27 09:00:00,13477.0 -2011-01-27 10:00:00,13580.0 -2011-01-27 11:00:00,13673.0 -2011-01-27 12:00:00,13743.0 -2011-01-27 13:00:00,13657.0 -2011-01-27 14:00:00,13504.0 -2011-01-27 15:00:00,13466.0 -2011-01-27 16:00:00,13365.0 -2011-01-27 17:00:00,13296.0 -2011-01-27 18:00:00,13541.0 -2011-01-27 19:00:00,14227.0 -2011-01-27 20:00:00,14222.0 -2011-01-27 21:00:00,14007.0 -2011-01-27 22:00:00,13644.0 -2011-01-27 23:00:00,13124.0 -2011-01-28 00:00:00,12248.0 -2011-01-26 01:00:00,11092.0 -2011-01-26 02:00:00,10603.0 -2011-01-26 03:00:00,10318.0 -2011-01-26 04:00:00,10146.0 -2011-01-26 05:00:00,10165.0 -2011-01-26 06:00:00,10425.0 -2011-01-26 07:00:00,11150.0 -2011-01-26 08:00:00,12347.0 -2011-01-26 09:00:00,13025.0 -2011-01-26 10:00:00,13166.0 -2011-01-26 11:00:00,13274.0 -2011-01-26 12:00:00,13423.0 -2011-01-26 13:00:00,13406.0 -2011-01-26 14:00:00,13391.0 -2011-01-26 15:00:00,13431.0 -2011-01-26 16:00:00,13385.0 -2011-01-26 17:00:00,13413.0 -2011-01-26 18:00:00,13796.0 -2011-01-26 19:00:00,14383.0 -2011-01-26 20:00:00,14267.0 -2011-01-26 21:00:00,14032.0 -2011-01-26 22:00:00,13722.0 -2011-01-26 23:00:00,13186.0 -2011-01-27 00:00:00,12318.0 -2011-01-25 01:00:00,11544.0 -2011-01-25 02:00:00,11034.0 -2011-01-25 03:00:00,10705.0 -2011-01-25 04:00:00,10515.0 -2011-01-25 05:00:00,10522.0 -2011-01-25 06:00:00,10784.0 -2011-01-25 07:00:00,11434.0 -2011-01-25 08:00:00,12621.0 -2011-01-25 09:00:00,13226.0 -2011-01-25 10:00:00,13318.0 -2011-01-25 11:00:00,13371.0 -2011-01-25 12:00:00,13459.0 -2011-01-25 13:00:00,13374.0 -2011-01-25 14:00:00,13195.0 -2011-01-25 15:00:00,13083.0 -2011-01-25 16:00:00,12981.0 -2011-01-25 17:00:00,13039.0 -2011-01-25 18:00:00,13386.0 -2011-01-25 19:00:00,14043.0 -2011-01-25 20:00:00,13949.0 -2011-01-25 21:00:00,13713.0 -2011-01-25 22:00:00,13393.0 -2011-01-25 23:00:00,12804.0 -2011-01-26 00:00:00,11914.0 -2011-01-24 01:00:00,11401.0 -2011-01-24 02:00:00,11005.0 -2011-01-24 03:00:00,10828.0 -2011-01-24 04:00:00,10756.0 -2011-01-24 05:00:00,10804.0 -2011-01-24 06:00:00,11059.0 -2011-01-24 07:00:00,11725.0 -2011-01-24 08:00:00,12900.0 -2011-01-24 09:00:00,13516.0 -2011-01-24 10:00:00,13630.0 -2011-01-24 11:00:00,13684.0 -2011-01-24 12:00:00,13740.0 -2011-01-24 13:00:00,13643.0 -2011-01-24 14:00:00,13576.0 -2011-01-24 15:00:00,13562.0 -2011-01-24 16:00:00,13469.0 -2011-01-24 17:00:00,13477.0 -2011-01-24 18:00:00,13763.0 -2011-01-24 19:00:00,14411.0 -2011-01-24 20:00:00,14334.0 -2011-01-24 21:00:00,14121.0 -2011-01-24 22:00:00,13799.0 -2011-01-24 23:00:00,13249.0 -2011-01-25 00:00:00,12371.0 -2011-01-23 01:00:00,11908.0 -2011-01-23 02:00:00,11503.0 -2011-01-23 03:00:00,11280.0 -2011-01-23 04:00:00,11068.0 -2011-01-23 05:00:00,11044.0 -2011-01-23 06:00:00,11026.0 -2011-01-23 07:00:00,11195.0 -2011-01-23 08:00:00,11471.0 -2011-01-23 09:00:00,11505.0 -2011-01-23 10:00:00,11715.0 -2011-01-23 11:00:00,11728.0 -2011-01-23 12:00:00,11728.0 -2011-01-23 13:00:00,11652.0 -2011-01-23 14:00:00,11635.0 -2011-01-23 15:00:00,11498.0 -2011-01-23 16:00:00,11373.0 -2011-01-23 17:00:00,11383.0 -2011-01-23 18:00:00,11847.0 -2011-01-23 19:00:00,12869.0 -2011-01-23 20:00:00,13215.0 -2011-01-23 21:00:00,13188.0 -2011-01-23 22:00:00,12980.0 -2011-01-23 23:00:00,12591.0 -2011-01-24 00:00:00,11924.0 -2011-01-22 01:00:00,12449.0 -2011-01-22 02:00:00,11964.0 -2011-01-22 03:00:00,11630.0 -2011-01-22 04:00:00,11388.0 -2011-01-22 05:00:00,11341.0 -2011-01-22 06:00:00,11402.0 -2011-01-22 07:00:00,11709.0 -2011-01-22 08:00:00,12111.0 -2011-01-22 09:00:00,12392.0 -2011-01-22 10:00:00,12665.0 -2011-01-22 11:00:00,12888.0 -2011-01-22 12:00:00,12900.0 -2011-01-22 13:00:00,12727.0 -2011-01-22 14:00:00,12459.0 -2011-01-22 15:00:00,12224.0 -2011-01-22 16:00:00,12165.0 -2011-01-22 17:00:00,12301.0 -2011-01-22 18:00:00,12693.0 -2011-01-22 19:00:00,13544.0 -2011-01-22 20:00:00,13573.0 -2011-01-22 21:00:00,13491.0 -2011-01-22 22:00:00,13287.0 -2011-01-22 23:00:00,12995.0 -2011-01-23 00:00:00,12447.0 -2011-01-21 01:00:00,12417.0 -2011-01-21 02:00:00,11993.0 -2011-01-21 03:00:00,11757.0 -2011-01-21 04:00:00,11632.0 -2011-01-21 05:00:00,11666.0 -2011-01-21 06:00:00,11953.0 -2011-01-21 07:00:00,12580.0 -2011-01-21 08:00:00,13734.0 -2011-01-21 09:00:00,14255.0 -2011-01-21 10:00:00,14292.0 -2011-01-21 11:00:00,14289.0 -2011-01-21 12:00:00,14262.0 -2011-01-21 13:00:00,14148.0 -2011-01-21 14:00:00,14030.0 -2011-01-21 15:00:00,13941.0 -2011-01-21 16:00:00,13802.0 -2011-01-21 17:00:00,13784.0 -2011-01-21 18:00:00,14153.0 -2011-01-21 19:00:00,14949.0 -2011-01-21 20:00:00,14945.0 -2011-01-21 21:00:00,14717.0 -2011-01-21 22:00:00,14449.0 -2011-01-21 23:00:00,13973.0 -2011-01-22 00:00:00,13245.0 -2011-01-20 01:00:00,11651.0 -2011-01-20 02:00:00,11176.0 -2011-01-20 03:00:00,10892.0 -2011-01-20 04:00:00,10713.0 -2011-01-20 05:00:00,10690.0 -2011-01-20 06:00:00,10952.0 -2011-01-20 07:00:00,11659.0 -2011-01-20 08:00:00,12925.0 -2011-01-20 09:00:00,13577.0 -2011-01-20 10:00:00,13719.0 -2011-01-20 11:00:00,13779.0 -2011-01-20 12:00:00,13836.0 -2011-01-20 13:00:00,13692.0 -2011-01-20 14:00:00,13573.0 -2011-01-20 15:00:00,13550.0 -2011-01-20 16:00:00,13418.0 -2011-01-20 17:00:00,13364.0 -2011-01-20 18:00:00,13766.0 -2011-01-20 19:00:00,14702.0 -2011-01-20 20:00:00,14725.0 -2011-01-20 21:00:00,14598.0 -2011-01-20 22:00:00,14363.0 -2011-01-20 23:00:00,13882.0 -2011-01-21 00:00:00,13108.0 -2011-01-19 01:00:00,11359.0 -2011-01-19 02:00:00,10884.0 -2011-01-19 03:00:00,10628.0 -2011-01-19 04:00:00,10469.0 -2011-01-19 05:00:00,10506.0 -2011-01-19 06:00:00,10780.0 -2011-01-19 07:00:00,11515.0 -2011-01-19 08:00:00,12736.0 -2011-01-19 09:00:00,13406.0 -2011-01-19 10:00:00,13477.0 -2011-01-19 11:00:00,13499.0 -2011-01-19 12:00:00,13511.0 -2011-01-19 13:00:00,13358.0 -2011-01-19 14:00:00,13185.0 -2011-01-19 15:00:00,13056.0 -2011-01-19 16:00:00,12943.0 -2011-01-19 17:00:00,12909.0 -2011-01-19 18:00:00,13370.0 -2011-01-19 19:00:00,14307.0 -2011-01-19 20:00:00,14311.0 -2011-01-19 21:00:00,14111.0 -2011-01-19 22:00:00,13821.0 -2011-01-19 23:00:00,13251.0 -2011-01-20 00:00:00,12420.0 -2011-01-18 01:00:00,10988.0 -2011-01-18 02:00:00,10481.0 -2011-01-18 03:00:00,10163.0 -2011-01-18 04:00:00,9974.0 -2011-01-18 05:00:00,9949.0 -2011-01-18 06:00:00,10203.0 -2011-01-18 07:00:00,10945.0 -2011-01-18 08:00:00,12172.0 -2011-01-18 09:00:00,12909.0 -2011-01-18 10:00:00,13043.0 -2011-01-18 11:00:00,13128.0 -2011-01-18 12:00:00,13213.0 -2011-01-18 13:00:00,13219.0 -2011-01-18 14:00:00,13204.0 -2011-01-18 15:00:00,13273.0 -2011-01-18 16:00:00,13295.0 -2011-01-18 17:00:00,13392.0 -2011-01-18 18:00:00,13830.0 -2011-01-18 19:00:00,14339.0 -2011-01-18 20:00:00,14155.0 -2011-01-18 21:00:00,13937.0 -2011-01-18 22:00:00,13648.0 -2011-01-18 23:00:00,13053.0 -2011-01-19 00:00:00,12164.0 -2011-01-17 01:00:00,11005.0 -2011-01-17 02:00:00,10594.0 -2011-01-17 03:00:00,10438.0 -2011-01-17 04:00:00,10329.0 -2011-01-17 05:00:00,10378.0 -2011-01-17 06:00:00,10642.0 -2011-01-17 07:00:00,11288.0 -2011-01-17 08:00:00,12217.0 -2011-01-17 09:00:00,12790.0 -2011-01-17 10:00:00,13115.0 -2011-01-17 11:00:00,13330.0 -2011-01-17 12:00:00,13451.0 -2011-01-17 13:00:00,13363.0 -2011-01-17 14:00:00,13213.0 -2011-01-17 15:00:00,13205.0 -2011-01-17 16:00:00,13142.0 -2011-01-17 17:00:00,13088.0 -2011-01-17 18:00:00,13531.0 -2011-01-17 19:00:00,14075.0 -2011-01-17 20:00:00,13871.0 -2011-01-17 21:00:00,13596.0 -2011-01-17 22:00:00,13231.0 -2011-01-17 23:00:00,12637.0 -2011-01-18 00:00:00,11764.0 -2011-01-16 01:00:00,11200.0 -2011-01-16 02:00:00,10725.0 -2011-01-16 03:00:00,10485.0 -2011-01-16 04:00:00,10306.0 -2011-01-16 05:00:00,10249.0 -2011-01-16 06:00:00,10362.0 -2011-01-16 07:00:00,10495.0 -2011-01-16 08:00:00,10836.0 -2011-01-16 09:00:00,10878.0 -2011-01-16 10:00:00,11154.0 -2011-01-16 11:00:00,11288.0 -2011-01-16 12:00:00,11423.0 -2011-01-16 13:00:00,11429.0 -2011-01-16 14:00:00,11411.0 -2011-01-16 15:00:00,11336.0 -2011-01-16 16:00:00,11257.0 -2011-01-16 17:00:00,11299.0 -2011-01-16 18:00:00,11847.0 -2011-01-16 19:00:00,12647.0 -2011-01-16 20:00:00,12667.0 -2011-01-16 21:00:00,12569.0 -2011-01-16 22:00:00,12332.0 -2011-01-16 23:00:00,12048.0 -2011-01-17 00:00:00,11518.0 -2011-01-15 01:00:00,11449.0 -2011-01-15 02:00:00,10892.0 -2011-01-15 03:00:00,10573.0 -2011-01-15 04:00:00,10343.0 -2011-01-15 05:00:00,10264.0 -2011-01-15 06:00:00,10317.0 -2011-01-15 07:00:00,10610.0 -2011-01-15 08:00:00,11076.0 -2011-01-15 09:00:00,11458.0 -2011-01-15 10:00:00,11750.0 -2011-01-15 11:00:00,11993.0 -2011-01-15 12:00:00,12001.0 -2011-01-15 13:00:00,12004.0 -2011-01-15 14:00:00,11787.0 -2011-01-15 15:00:00,11730.0 -2011-01-15 16:00:00,11633.0 -2011-01-15 17:00:00,11725.0 -2011-01-15 18:00:00,12095.0 -2011-01-15 19:00:00,12899.0 -2011-01-15 20:00:00,12841.0 -2011-01-15 21:00:00,12730.0 -2011-01-15 22:00:00,12555.0 -2011-01-15 23:00:00,12279.0 -2011-01-16 00:00:00,11734.0 -2011-01-14 01:00:00,11643.0 -2011-01-14 02:00:00,11173.0 -2011-01-14 03:00:00,10881.0 -2011-01-14 04:00:00,10703.0 -2011-01-14 05:00:00,10707.0 -2011-01-14 06:00:00,10916.0 -2011-01-14 07:00:00,11582.0 -2011-01-14 08:00:00,12694.0 -2011-01-14 09:00:00,13254.0 -2011-01-14 10:00:00,13345.0 -2011-01-14 11:00:00,13390.0 -2011-01-14 12:00:00,13386.0 -2011-01-14 13:00:00,13250.0 -2011-01-14 14:00:00,13114.0 -2011-01-14 15:00:00,13068.0 -2011-01-14 16:00:00,13000.0 -2011-01-14 17:00:00,12967.0 -2011-01-14 18:00:00,13336.0 -2011-01-14 19:00:00,13980.0 -2011-01-14 20:00:00,13812.0 -2011-01-14 21:00:00,13553.0 -2011-01-14 22:00:00,13231.0 -2011-01-14 23:00:00,12833.0 -2011-01-15 00:00:00,12120.0 -2011-01-13 01:00:00,11348.0 -2011-01-13 02:00:00,10848.0 -2011-01-13 03:00:00,10574.0 -2011-01-13 04:00:00,10405.0 -2011-01-13 05:00:00,10450.0 -2011-01-13 06:00:00,10711.0 -2011-01-13 07:00:00,11419.0 -2011-01-13 08:00:00,12626.0 -2011-01-13 09:00:00,13262.0 -2011-01-13 10:00:00,13315.0 -2011-01-13 11:00:00,13358.0 -2011-01-13 12:00:00,13324.0 -2011-01-13 13:00:00,13268.0 -2011-01-13 14:00:00,13098.0 -2011-01-13 15:00:00,13302.0 -2011-01-13 16:00:00,13310.0 -2011-01-13 17:00:00,13318.0 -2011-01-13 18:00:00,13767.0 -2011-01-13 19:00:00,14455.0 -2011-01-13 20:00:00,14322.0 -2011-01-13 21:00:00,14137.0 -2011-01-13 22:00:00,13850.0 -2011-01-13 23:00:00,13300.0 -2011-01-14 00:00:00,12455.0 -2011-01-12 01:00:00,11510.0 -2011-01-12 02:00:00,11043.0 -2011-01-12 03:00:00,10736.0 -2011-01-12 04:00:00,10593.0 -2011-01-12 05:00:00,10589.0 -2011-01-12 06:00:00,10853.0 -2011-01-12 07:00:00,11551.0 -2011-01-12 08:00:00,12726.0 -2011-01-12 09:00:00,13311.0 -2011-01-12 10:00:00,13364.0 -2011-01-12 11:00:00,13318.0 -2011-01-12 12:00:00,13293.0 -2011-01-12 13:00:00,13135.0 -2011-01-12 14:00:00,13083.0 -2011-01-12 15:00:00,13132.0 -2011-01-12 16:00:00,13154.0 -2011-01-12 17:00:00,13189.0 -2011-01-12 18:00:00,13615.0 -2011-01-12 19:00:00,14272.0 -2011-01-12 20:00:00,14129.0 -2011-01-12 21:00:00,13921.0 -2011-01-12 22:00:00,13606.0 -2011-01-12 23:00:00,13048.0 -2011-01-13 00:00:00,12149.0 -2011-01-11 01:00:00,11263.0 -2011-01-11 02:00:00,10747.0 -2011-01-11 03:00:00,10457.0 -2011-01-11 04:00:00,10320.0 -2011-01-11 05:00:00,10339.0 -2011-01-11 06:00:00,10630.0 -2011-01-11 07:00:00,11359.0 -2011-01-11 08:00:00,12537.0 -2011-01-11 09:00:00,13201.0 -2011-01-11 10:00:00,13279.0 -2011-01-11 11:00:00,13380.0 -2011-01-11 12:00:00,13468.0 -2011-01-11 13:00:00,13369.0 -2011-01-11 14:00:00,13317.0 -2011-01-11 15:00:00,13295.0 -2011-01-11 16:00:00,13226.0 -2011-01-11 17:00:00,13256.0 -2011-01-11 18:00:00,13699.0 -2011-01-11 19:00:00,14402.0 -2011-01-11 20:00:00,14278.0 -2011-01-11 21:00:00,14043.0 -2011-01-11 22:00:00,13739.0 -2011-01-11 23:00:00,13141.0 -2011-01-12 00:00:00,12275.0 -2011-01-10 01:00:00,10817.0 -2011-01-10 02:00:00,10462.0 -2011-01-10 03:00:00,10274.0 -2011-01-10 04:00:00,10202.0 -2011-01-10 05:00:00,10285.0 -2011-01-10 06:00:00,10606.0 -2011-01-10 07:00:00,11369.0 -2011-01-10 08:00:00,12574.0 -2011-01-10 09:00:00,13321.0 -2011-01-10 10:00:00,13208.0 -2011-01-10 11:00:00,13159.0 -2011-01-10 12:00:00,13089.0 -2011-01-10 13:00:00,13036.0 -2011-01-10 14:00:00,12909.0 -2011-01-10 15:00:00,12901.0 -2011-01-10 16:00:00,12871.0 -2011-01-10 17:00:00,13011.0 -2011-01-10 18:00:00,13620.0 -2011-01-10 19:00:00,14197.0 -2011-01-10 20:00:00,14018.0 -2011-01-10 21:00:00,13818.0 -2011-01-10 22:00:00,13482.0 -2011-01-10 23:00:00,12949.0 -2011-01-11 00:00:00,12073.0 -2011-01-09 01:00:00,11469.0 -2011-01-09 02:00:00,10936.0 -2011-01-09 03:00:00,10634.0 -2011-01-09 04:00:00,10531.0 -2011-01-09 05:00:00,10478.0 -2011-01-09 06:00:00,10499.0 -2011-01-09 07:00:00,10670.0 -2011-01-09 08:00:00,10955.0 -2011-01-09 09:00:00,11053.0 -2011-01-09 10:00:00,11113.0 -2011-01-09 11:00:00,11179.0 -2011-01-09 12:00:00,11154.0 -2011-01-09 13:00:00,11116.0 -2011-01-09 14:00:00,11028.0 -2011-01-09 15:00:00,10959.0 -2011-01-09 16:00:00,10880.0 -2011-01-09 17:00:00,11105.0 -2011-01-09 18:00:00,11772.0 -2011-01-09 19:00:00,12671.0 -2011-01-09 20:00:00,12716.0 -2011-01-09 21:00:00,12619.0 -2011-01-09 22:00:00,12368.0 -2011-01-09 23:00:00,11975.0 -2011-01-10 00:00:00,11382.0 -2011-01-08 01:00:00,12044.0 -2011-01-08 02:00:00,11494.0 -2011-01-08 03:00:00,11218.0 -2011-01-08 04:00:00,11035.0 -2011-01-08 05:00:00,10989.0 -2011-01-08 06:00:00,11025.0 -2011-01-08 07:00:00,11326.0 -2011-01-08 08:00:00,11757.0 -2011-01-08 09:00:00,11991.0 -2011-01-08 10:00:00,12164.0 -2011-01-08 11:00:00,12315.0 -2011-01-08 12:00:00,12376.0 -2011-01-08 13:00:00,12308.0 -2011-01-08 14:00:00,12151.0 -2011-01-08 15:00:00,11919.0 -2011-01-08 16:00:00,11717.0 -2011-01-08 17:00:00,11750.0 -2011-01-08 18:00:00,12316.0 -2011-01-08 19:00:00,13212.0 -2011-01-08 20:00:00,13196.0 -2011-01-08 21:00:00,13104.0 -2011-01-08 22:00:00,12897.0 -2011-01-08 23:00:00,12607.0 -2011-01-09 00:00:00,12011.0 -2011-01-07 01:00:00,11582.0 -2011-01-07 02:00:00,11132.0 -2011-01-07 03:00:00,10833.0 -2011-01-07 04:00:00,10653.0 -2011-01-07 05:00:00,10652.0 -2011-01-07 06:00:00,10913.0 -2011-01-07 07:00:00,11538.0 -2011-01-07 08:00:00,12665.0 -2011-01-07 09:00:00,13414.0 -2011-01-07 10:00:00,13499.0 -2011-01-07 11:00:00,13552.0 -2011-01-07 12:00:00,13553.0 -2011-01-07 13:00:00,13441.0 -2011-01-07 14:00:00,13323.0 -2011-01-07 15:00:00,13415.0 -2011-01-07 16:00:00,13504.0 -2011-01-07 17:00:00,13558.0 -2011-01-07 18:00:00,14028.0 -2011-01-07 19:00:00,14500.0 -2011-01-07 20:00:00,14361.0 -2011-01-07 21:00:00,14143.0 -2011-01-07 22:00:00,13832.0 -2011-01-07 23:00:00,13438.0 -2011-01-08 00:00:00,12724.0 -2011-01-06 01:00:00,11268.0 -2011-01-06 02:00:00,10736.0 -2011-01-06 03:00:00,10418.0 -2011-01-06 04:00:00,10252.0 -2011-01-06 05:00:00,10254.0 -2011-01-06 06:00:00,10522.0 -2011-01-06 07:00:00,11221.0 -2011-01-06 08:00:00,12410.0 -2011-01-06 09:00:00,13236.0 -2011-01-06 10:00:00,13349.0 -2011-01-06 11:00:00,13440.0 -2011-01-06 12:00:00,13426.0 -2011-01-06 13:00:00,13377.0 -2011-01-06 14:00:00,13247.0 -2011-01-06 15:00:00,13112.0 -2011-01-06 16:00:00,12965.0 -2011-01-06 17:00:00,12993.0 -2011-01-06 18:00:00,13607.0 -2011-01-06 19:00:00,14420.0 -2011-01-06 20:00:00,14296.0 -2011-01-06 21:00:00,14099.0 -2011-01-06 22:00:00,13771.0 -2011-01-06 23:00:00,13253.0 -2011-01-07 00:00:00,12388.0 -2011-01-05 01:00:00,11829.0 -2011-01-05 02:00:00,11324.0 -2011-01-05 03:00:00,11062.0 -2011-01-05 04:00:00,10908.0 -2011-01-05 05:00:00,10930.0 -2011-01-05 06:00:00,11194.0 -2011-01-05 07:00:00,11868.0 -2011-01-05 08:00:00,13012.0 -2011-01-05 09:00:00,13580.0 -2011-01-05 10:00:00,13579.0 -2011-01-05 11:00:00,13561.0 -2011-01-05 12:00:00,13584.0 -2011-01-05 13:00:00,13530.0 -2011-01-05 14:00:00,13442.0 -2011-01-05 15:00:00,13374.0 -2011-01-05 16:00:00,13244.0 -2011-01-05 17:00:00,13265.0 -2011-01-05 18:00:00,13826.0 -2011-01-05 19:00:00,14335.0 -2011-01-05 20:00:00,14122.0 -2011-01-05 21:00:00,13861.0 -2011-01-05 22:00:00,13526.0 -2011-01-05 23:00:00,12968.0 -2011-01-06 00:00:00,12062.0 -2011-01-04 01:00:00,10941.0 -2011-01-04 02:00:00,10426.0 -2011-01-04 03:00:00,10171.0 -2011-01-04 04:00:00,10044.0 -2011-01-04 05:00:00,10065.0 -2011-01-04 06:00:00,10370.0 -2011-01-04 07:00:00,11063.0 -2011-01-04 08:00:00,12341.0 -2011-01-04 09:00:00,13040.0 -2011-01-04 10:00:00,13114.0 -2011-01-04 11:00:00,13194.0 -2011-01-04 12:00:00,13257.0 -2011-01-04 13:00:00,13223.0 -2011-01-04 14:00:00,13173.0 -2011-01-04 15:00:00,13144.0 -2011-01-04 16:00:00,13049.0 -2011-01-04 17:00:00,13070.0 -2011-01-04 18:00:00,13705.0 -2011-01-04 19:00:00,14527.0 -2011-01-04 20:00:00,14466.0 -2011-01-04 21:00:00,14312.0 -2011-01-04 22:00:00,14038.0 -2011-01-04 23:00:00,13498.0 -2011-01-05 00:00:00,12600.0 -2011-01-03 01:00:00,10897.0 -2011-01-03 02:00:00,10494.0 -2011-01-03 03:00:00,10279.0 -2011-01-03 04:00:00,10165.0 -2011-01-03 05:00:00,10199.0 -2011-01-03 06:00:00,10440.0 -2011-01-03 07:00:00,11096.0 -2011-01-03 08:00:00,12125.0 -2011-01-03 09:00:00,12687.0 -2011-01-03 10:00:00,12682.0 -2011-01-03 11:00:00,12707.0 -2011-01-03 12:00:00,12662.0 -2011-01-03 13:00:00,12573.0 -2011-01-03 14:00:00,12514.0 -2011-01-03 15:00:00,12463.0 -2011-01-03 16:00:00,12500.0 -2011-01-03 17:00:00,12594.0 -2011-01-03 18:00:00,13206.0 -2011-01-03 19:00:00,13873.0 -2011-01-03 20:00:00,13712.0 -2011-01-03 21:00:00,13505.0 -2011-01-03 22:00:00,13175.0 -2011-01-03 23:00:00,12613.0 -2011-01-04 00:00:00,11763.0 -2011-01-02 01:00:00,11199.0 -2011-01-02 02:00:00,10682.0 -2011-01-02 03:00:00,10376.0 -2011-01-02 04:00:00,10247.0 -2011-01-02 05:00:00,10161.0 -2011-01-02 06:00:00,10220.0 -2011-01-02 07:00:00,10346.0 -2011-01-02 08:00:00,10651.0 -2011-01-02 09:00:00,10710.0 -2011-01-02 10:00:00,10834.0 -2011-01-02 11:00:00,10945.0 -2011-01-02 12:00:00,11078.0 -2011-01-02 13:00:00,11127.0 -2011-01-02 14:00:00,11141.0 -2011-01-02 15:00:00,11057.0 -2011-01-02 16:00:00,10973.0 -2011-01-02 17:00:00,11074.0 -2011-01-02 18:00:00,11929.0 -2011-01-02 19:00:00,12841.0 -2011-01-02 20:00:00,12956.0 -2011-01-02 21:00:00,12849.0 -2011-01-02 22:00:00,12689.0 -2011-01-02 23:00:00,12274.0 -2011-01-03 00:00:00,11662.0 -2011-01-01 01:00:00,9631.0 -2011-01-01 02:00:00,9273.0 -2011-01-01 03:00:00,9011.0 -2011-01-01 04:00:00,8741.0 -2011-01-01 05:00:00,8694.0 -2011-01-01 06:00:00,8711.0 -2011-01-01 07:00:00,8943.0 -2011-01-01 08:00:00,9222.0 -2011-01-01 09:00:00,9430.0 -2011-01-01 10:00:00,9670.0 -2011-01-01 11:00:00,10125.0 -2011-01-01 12:00:00,10538.0 -2011-01-01 13:00:00,10782.0 -2011-01-01 14:00:00,10861.0 -2011-01-01 15:00:00,10784.0 -2011-01-01 16:00:00,10928.0 -2011-01-01 17:00:00,11133.0 -2011-01-01 18:00:00,11924.0 -2011-01-01 19:00:00,12665.0 -2011-01-01 20:00:00,12701.0 -2011-01-01 21:00:00,12630.0 -2011-01-01 22:00:00,12513.0 -2011-01-01 23:00:00,12252.0 -2011-01-02 00:00:00,11778.0 -2012-12-31 01:00:00,10445.0 -2012-12-31 02:00:00,9897.0 -2012-12-31 03:00:00,9538.0 -2012-12-31 04:00:00,9333.0 -2012-12-31 05:00:00,9275.0 -2012-12-31 06:00:00,9463.0 -2012-12-31 07:00:00,9830.0 -2012-12-31 08:00:00,10443.0 -2012-12-31 09:00:00,10713.0 -2012-12-31 10:00:00,10845.0 -2012-12-31 11:00:00,11044.0 -2012-12-31 12:00:00,11269.0 -2012-12-31 13:00:00,11311.0 -2012-12-31 14:00:00,11349.0 -2012-12-31 15:00:00,11372.0 -2012-12-31 16:00:00,11297.0 -2012-12-31 17:00:00,11378.0 -2012-12-31 18:00:00,12062.0 -2012-12-31 19:00:00,12779.0 -2012-12-31 20:00:00,12508.0 -2012-12-31 21:00:00,12154.0 -2012-12-31 22:00:00,11812.0 -2012-12-31 23:00:00,11437.0 -2013-01-01 00:00:00,11009.0 -2012-12-30 01:00:00,10857.0 -2012-12-30 02:00:00,10294.0 -2012-12-30 03:00:00,10002.0 -2012-12-30 04:00:00,9823.0 -2012-12-30 05:00:00,9768.0 -2012-12-30 06:00:00,9810.0 -2012-12-30 07:00:00,9986.0 -2012-12-30 08:00:00,10244.0 -2012-12-30 09:00:00,10289.0 -2012-12-30 10:00:00,10399.0 -2012-12-30 11:00:00,10452.0 -2012-12-30 12:00:00,10528.0 -2012-12-30 13:00:00,10498.0 -2012-12-30 14:00:00,10459.0 -2012-12-30 15:00:00,10346.0 -2012-12-30 16:00:00,10337.0 -2012-12-30 17:00:00,10446.0 -2012-12-30 18:00:00,11412.0 -2012-12-30 19:00:00,12278.0 -2012-12-30 20:00:00,12309.0 -2012-12-30 21:00:00,12257.0 -2012-12-30 22:00:00,12031.0 -2012-12-30 23:00:00,11690.0 -2012-12-31 00:00:00,11071.0 -2012-12-29 01:00:00,10594.0 -2012-12-29 02:00:00,9985.0 -2012-12-29 03:00:00,9602.0 -2012-12-29 04:00:00,9378.0 -2012-12-29 05:00:00,9292.0 -2012-12-29 06:00:00,9357.0 -2012-12-29 07:00:00,9608.0 -2012-12-29 08:00:00,10059.0 -2012-12-29 09:00:00,10394.0 -2012-12-29 10:00:00,10587.0 -2012-12-29 11:00:00,10901.0 -2012-12-29 12:00:00,11107.0 -2012-12-29 13:00:00,11208.0 -2012-12-29 14:00:00,11146.0 -2012-12-29 15:00:00,11078.0 -2012-12-29 16:00:00,11051.0 -2012-12-29 17:00:00,11219.0 -2012-12-29 18:00:00,11992.0 -2012-12-29 19:00:00,12591.0 -2012-12-29 20:00:00,12579.0 -2012-12-29 21:00:00,12462.0 -2012-12-29 22:00:00,12320.0 -2012-12-29 23:00:00,12037.0 -2012-12-30 00:00:00,11455.0 -2012-12-28 01:00:00,10617.0 -2012-12-28 02:00:00,10002.0 -2012-12-28 03:00:00,9629.0 -2012-12-28 04:00:00,9443.0 -2012-12-28 05:00:00,9405.0 -2012-12-28 06:00:00,9593.0 -2012-12-28 07:00:00,10160.0 -2012-12-28 08:00:00,10999.0 -2012-12-28 09:00:00,11566.0 -2012-12-28 10:00:00,11845.0 -2012-12-28 11:00:00,12091.0 -2012-12-28 12:00:00,12193.0 -2012-12-28 13:00:00,12277.0 -2012-12-28 14:00:00,12265.0 -2012-12-28 15:00:00,12279.0 -2012-12-28 16:00:00,12223.0 -2012-12-28 17:00:00,12287.0 -2012-12-28 18:00:00,12946.0 -2012-12-28 19:00:00,13325.0 -2012-12-28 20:00:00,13075.0 -2012-12-28 21:00:00,12833.0 -2012-12-28 22:00:00,12498.0 -2012-12-28 23:00:00,12071.0 -2012-12-29 00:00:00,11339.0 -2012-12-27 01:00:00,10736.0 -2012-12-27 02:00:00,10073.0 -2012-12-27 03:00:00,9741.0 -2012-12-27 04:00:00,9534.0 -2012-12-27 05:00:00,9493.0 -2012-12-27 06:00:00,9726.0 -2012-12-27 07:00:00,10306.0 -2012-12-27 08:00:00,11186.0 -2012-12-27 09:00:00,11802.0 -2012-12-27 10:00:00,12050.0 -2012-12-27 11:00:00,12276.0 -2012-12-27 12:00:00,12469.0 -2012-12-27 13:00:00,12432.0 -2012-12-27 14:00:00,12318.0 -2012-12-27 15:00:00,12343.0 -2012-12-27 16:00:00,12293.0 -2012-12-27 17:00:00,12300.0 -2012-12-27 18:00:00,12986.0 -2012-12-27 19:00:00,13521.0 -2012-12-27 20:00:00,13300.0 -2012-12-27 21:00:00,13057.0 -2012-12-27 22:00:00,12749.0 -2012-12-27 23:00:00,12229.0 -2012-12-28 00:00:00,11425.0 -2012-12-26 01:00:00,9966.0 -2012-12-26 02:00:00,9487.0 -2012-12-26 03:00:00,9209.0 -2012-12-26 04:00:00,9093.0 -2012-12-26 05:00:00,9090.0 -2012-12-26 06:00:00,9342.0 -2012-12-26 07:00:00,10001.0 -2012-12-26 08:00:00,10954.0 -2012-12-26 09:00:00,11603.0 -2012-12-26 10:00:00,11908.0 -2012-12-26 11:00:00,12192.0 -2012-12-26 12:00:00,12418.0 -2012-12-26 13:00:00,12533.0 -2012-12-26 14:00:00,12558.0 -2012-12-26 15:00:00,12562.0 -2012-12-26 16:00:00,12511.0 -2012-12-26 17:00:00,12576.0 -2012-12-26 18:00:00,13239.0 -2012-12-26 19:00:00,13715.0 -2012-12-26 20:00:00,13449.0 -2012-12-26 21:00:00,13181.0 -2012-12-26 22:00:00,12881.0 -2012-12-26 23:00:00,12411.0 -2012-12-27 00:00:00,11588.0 -2012-12-25 01:00:00,10158.0 -2012-12-25 02:00:00,9654.0 -2012-12-25 03:00:00,9313.0 -2012-12-25 04:00:00,9095.0 -2012-12-25 05:00:00,9030.0 -2012-12-25 06:00:00,9073.0 -2012-12-25 07:00:00,9267.0 -2012-12-25 08:00:00,9571.0 -2012-12-25 09:00:00,9689.0 -2012-12-25 10:00:00,9813.0 -2012-12-25 11:00:00,10021.0 -2012-12-25 12:00:00,10109.0 -2012-12-25 13:00:00,10208.0 -2012-12-25 14:00:00,10227.0 -2012-12-25 15:00:00,10203.0 -2012-12-25 16:00:00,10185.0 -2012-12-25 17:00:00,10296.0 -2012-12-25 18:00:00,10957.0 -2012-12-25 19:00:00,11354.0 -2012-12-25 20:00:00,11309.0 -2012-12-25 21:00:00,11247.0 -2012-12-25 22:00:00,11198.0 -2012-12-25 23:00:00,10993.0 -2012-12-26 00:00:00,10547.0 -2012-12-24 01:00:00,10345.0 -2012-12-24 02:00:00,9777.0 -2012-12-24 03:00:00,9356.0 -2012-12-24 04:00:00,9180.0 -2012-12-24 05:00:00,9123.0 -2012-12-24 06:00:00,9291.0 -2012-12-24 07:00:00,9618.0 -2012-12-24 08:00:00,10155.0 -2012-12-24 09:00:00,10424.0 -2012-12-24 10:00:00,10616.0 -2012-12-24 11:00:00,10922.0 -2012-12-24 12:00:00,11133.0 -2012-12-24 13:00:00,11238.0 -2012-12-24 14:00:00,11227.0 -2012-12-24 15:00:00,11210.0 -2012-12-24 16:00:00,11195.0 -2012-12-24 17:00:00,11311.0 -2012-12-24 18:00:00,11934.0 -2012-12-24 19:00:00,12183.0 -2012-12-24 20:00:00,11808.0 -2012-12-24 21:00:00,11537.0 -2012-12-24 22:00:00,11336.0 -2012-12-24 23:00:00,11109.0 -2012-12-25 00:00:00,10695.0 -2012-12-23 01:00:00,10824.0 -2012-12-23 02:00:00,10248.0 -2012-12-23 03:00:00,9802.0 -2012-12-23 04:00:00,9627.0 -2012-12-23 05:00:00,9531.0 -2012-12-23 06:00:00,9618.0 -2012-12-23 07:00:00,9752.0 -2012-12-23 08:00:00,10079.0 -2012-12-23 09:00:00,10133.0 -2012-12-23 10:00:00,10276.0 -2012-12-23 11:00:00,10408.0 -2012-12-23 12:00:00,10470.0 -2012-12-23 13:00:00,10491.0 -2012-12-23 14:00:00,10465.0 -2012-12-23 15:00:00,10406.0 -2012-12-23 16:00:00,10458.0 -2012-12-23 17:00:00,10600.0 -2012-12-23 18:00:00,11498.0 -2012-12-23 19:00:00,12194.0 -2012-12-23 20:00:00,12223.0 -2012-12-23 21:00:00,12146.0 -2012-12-23 22:00:00,12003.0 -2012-12-23 23:00:00,11695.0 -2012-12-24 00:00:00,11114.0 -2012-12-22 01:00:00,11473.0 -2012-12-22 02:00:00,10813.0 -2012-12-22 03:00:00,10434.0 -2012-12-22 04:00:00,10210.0 -2012-12-22 05:00:00,10137.0 -2012-12-22 06:00:00,10178.0 -2012-12-22 07:00:00,10472.0 -2012-12-22 08:00:00,10970.0 -2012-12-22 09:00:00,11139.0 -2012-12-22 10:00:00,11337.0 -2012-12-22 11:00:00,11422.0 -2012-12-22 12:00:00,11360.0 -2012-12-22 13:00:00,11229.0 -2012-12-22 14:00:00,10978.0 -2012-12-22 15:00:00,10773.0 -2012-12-22 16:00:00,10691.0 -2012-12-22 17:00:00,10780.0 -2012-12-22 18:00:00,11662.0 -2012-12-22 19:00:00,12522.0 -2012-12-22 20:00:00,12564.0 -2012-12-22 21:00:00,12448.0 -2012-12-22 22:00:00,12338.0 -2012-12-22 23:00:00,12044.0 -2012-12-23 00:00:00,11528.0 -2012-12-21 01:00:00,11018.0 -2012-12-21 02:00:00,10378.0 -2012-12-21 03:00:00,10002.0 -2012-12-21 04:00:00,9849.0 -2012-12-21 05:00:00,9878.0 -2012-12-21 06:00:00,10136.0 -2012-12-21 07:00:00,10854.0 -2012-12-21 08:00:00,12041.0 -2012-12-21 09:00:00,12679.0 -2012-12-21 10:00:00,12802.0 -2012-12-21 11:00:00,12889.0 -2012-12-21 12:00:00,12826.0 -2012-12-21 13:00:00,12699.0 -2012-12-21 14:00:00,12553.0 -2012-12-21 15:00:00,12528.0 -2012-12-21 16:00:00,12449.0 -2012-12-21 17:00:00,12472.0 -2012-12-21 18:00:00,13387.0 -2012-12-21 19:00:00,14173.0 -2012-12-21 20:00:00,14040.0 -2012-12-21 21:00:00,13796.0 -2012-12-21 22:00:00,13510.0 -2012-12-21 23:00:00,13121.0 -2012-12-22 00:00:00,12345.0 -2012-12-20 01:00:00,10772.0 -2012-12-20 02:00:00,10101.0 -2012-12-20 03:00:00,9678.0 -2012-12-20 04:00:00,9469.0 -2012-12-20 05:00:00,9459.0 -2012-12-20 06:00:00,9717.0 -2012-12-20 07:00:00,10381.0 -2012-12-20 08:00:00,11518.0 -2012-12-20 09:00:00,12305.0 -2012-12-20 10:00:00,12462.0 -2012-12-20 11:00:00,12478.0 -2012-12-20 12:00:00,12520.0 -2012-12-20 13:00:00,12528.0 -2012-12-20 14:00:00,12505.0 -2012-12-20 15:00:00,12567.0 -2012-12-20 16:00:00,12477.0 -2012-12-20 17:00:00,12590.0 -2012-12-20 18:00:00,13306.0 -2012-12-20 19:00:00,13752.0 -2012-12-20 20:00:00,13708.0 -2012-12-20 21:00:00,13509.0 -2012-12-20 22:00:00,13284.0 -2012-12-20 23:00:00,12798.0 -2012-12-21 00:00:00,11915.0 -2012-12-19 01:00:00,10638.0 -2012-12-19 02:00:00,9997.0 -2012-12-19 03:00:00,9611.0 -2012-12-19 04:00:00,9410.0 -2012-12-19 05:00:00,9396.0 -2012-12-19 06:00:00,9627.0 -2012-12-19 07:00:00,10354.0 -2012-12-19 08:00:00,11581.0 -2012-12-19 09:00:00,12244.0 -2012-12-19 10:00:00,12322.0 -2012-12-19 11:00:00,12365.0 -2012-12-19 12:00:00,12395.0 -2012-12-19 13:00:00,12298.0 -2012-12-19 14:00:00,12184.0 -2012-12-19 15:00:00,12270.0 -2012-12-19 16:00:00,12250.0 -2012-12-19 17:00:00,12413.0 -2012-12-19 18:00:00,13260.0 -2012-12-19 19:00:00,13750.0 -2012-12-19 20:00:00,13593.0 -2012-12-19 21:00:00,13420.0 -2012-12-19 22:00:00,13175.0 -2012-12-19 23:00:00,12644.0 -2012-12-20 00:00:00,11696.0 -2012-12-18 01:00:00,10622.0 -2012-12-18 02:00:00,9948.0 -2012-12-18 03:00:00,9609.0 -2012-12-18 04:00:00,9405.0 -2012-12-18 05:00:00,9388.0 -2012-12-18 06:00:00,9600.0 -2012-12-18 07:00:00,10320.0 -2012-12-18 08:00:00,11614.0 -2012-12-18 09:00:00,12281.0 -2012-12-18 10:00:00,12412.0 -2012-12-18 11:00:00,12500.0 -2012-12-18 12:00:00,12590.0 -2012-12-18 13:00:00,12620.0 -2012-12-18 14:00:00,12514.0 -2012-12-18 15:00:00,12551.0 -2012-12-18 16:00:00,12544.0 -2012-12-18 17:00:00,12817.0 -2012-12-18 18:00:00,13556.0 -2012-12-18 19:00:00,13823.0 -2012-12-18 20:00:00,13555.0 -2012-12-18 21:00:00,13321.0 -2012-12-18 22:00:00,13027.0 -2012-12-18 23:00:00,12498.0 -2012-12-19 00:00:00,11558.0 -2012-12-17 01:00:00,9776.0 -2012-12-17 02:00:00,9225.0 -2012-12-17 03:00:00,8918.0 -2012-12-17 04:00:00,8837.0 -2012-12-17 05:00:00,8841.0 -2012-12-17 06:00:00,9163.0 -2012-12-17 07:00:00,9924.0 -2012-12-17 08:00:00,11258.0 -2012-12-17 09:00:00,12018.0 -2012-12-17 10:00:00,12135.0 -2012-12-17 11:00:00,12190.0 -2012-12-17 12:00:00,12236.0 -2012-12-17 13:00:00,12203.0 -2012-12-17 14:00:00,12167.0 -2012-12-17 15:00:00,12181.0 -2012-12-17 16:00:00,12126.0 -2012-12-17 17:00:00,12157.0 -2012-12-17 18:00:00,12978.0 -2012-12-17 19:00:00,13596.0 -2012-12-17 20:00:00,13517.0 -2012-12-17 21:00:00,13331.0 -2012-12-17 22:00:00,13059.0 -2012-12-17 23:00:00,12539.0 -2012-12-18 00:00:00,11566.0 -2012-12-16 01:00:00,9816.0 -2012-12-16 02:00:00,9147.0 -2012-12-16 03:00:00,8679.0 -2012-12-16 04:00:00,8470.0 -2012-12-16 05:00:00,8299.0 -2012-12-16 06:00:00,8356.0 -2012-12-16 07:00:00,8460.0 -2012-12-16 08:00:00,8799.0 -2012-12-16 09:00:00,9040.0 -2012-12-16 10:00:00,9404.0 -2012-12-16 11:00:00,9666.0 -2012-12-16 12:00:00,9879.0 -2012-12-16 13:00:00,10016.0 -2012-12-16 14:00:00,10124.0 -2012-12-16 15:00:00,10174.0 -2012-12-16 16:00:00,10275.0 -2012-12-16 17:00:00,10525.0 -2012-12-16 18:00:00,11410.0 -2012-12-16 19:00:00,11860.0 -2012-12-16 20:00:00,11920.0 -2012-12-16 21:00:00,11785.0 -2012-12-16 22:00:00,11671.0 -2012-12-16 23:00:00,11213.0 -2012-12-17 00:00:00,10556.0 -2012-12-15 01:00:00,10466.0 -2012-12-15 02:00:00,9743.0 -2012-12-15 03:00:00,9298.0 -2012-12-15 04:00:00,9085.0 -2012-12-15 05:00:00,8948.0 -2012-12-15 06:00:00,9020.0 -2012-12-15 07:00:00,9318.0 -2012-12-15 08:00:00,9868.0 -2012-12-15 09:00:00,10325.0 -2012-12-15 10:00:00,10811.0 -2012-12-15 11:00:00,11122.0 -2012-12-15 12:00:00,11337.0 -2012-12-15 13:00:00,11381.0 -2012-12-15 14:00:00,11328.0 -2012-12-15 15:00:00,11118.0 -2012-12-15 16:00:00,11089.0 -2012-12-15 17:00:00,11221.0 -2012-12-15 18:00:00,11779.0 -2012-12-15 19:00:00,12045.0 -2012-12-15 20:00:00,11964.0 -2012-12-15 21:00:00,11729.0 -2012-12-15 22:00:00,11498.0 -2012-12-15 23:00:00,11140.0 -2012-12-16 00:00:00,10546.0 -2012-12-14 01:00:00,10539.0 -2012-12-14 02:00:00,9931.0 -2012-12-14 03:00:00,9588.0 -2012-12-14 04:00:00,9404.0 -2012-12-14 05:00:00,9406.0 -2012-12-14 06:00:00,9687.0 -2012-12-14 07:00:00,10430.0 -2012-12-14 08:00:00,11687.0 -2012-12-14 09:00:00,12143.0 -2012-12-14 10:00:00,12169.0 -2012-12-14 11:00:00,12058.0 -2012-12-14 12:00:00,11963.0 -2012-12-14 13:00:00,11800.0 -2012-12-14 14:00:00,11618.0 -2012-12-14 15:00:00,11621.0 -2012-12-14 16:00:00,11538.0 -2012-12-14 17:00:00,11626.0 -2012-12-14 18:00:00,12626.0 -2012-12-14 19:00:00,13242.0 -2012-12-14 20:00:00,13108.0 -2012-12-14 21:00:00,12838.0 -2012-12-14 22:00:00,12515.0 -2012-12-14 23:00:00,12159.0 -2012-12-15 00:00:00,11313.0 -2012-12-13 01:00:00,10825.0 -2012-12-13 02:00:00,10195.0 -2012-12-13 03:00:00,9822.0 -2012-12-13 04:00:00,9627.0 -2012-12-13 05:00:00,9647.0 -2012-12-13 06:00:00,9895.0 -2012-12-13 07:00:00,10632.0 -2012-12-13 08:00:00,11862.0 -2012-12-13 09:00:00,12351.0 -2012-12-13 10:00:00,12347.0 -2012-12-13 11:00:00,12274.0 -2012-12-13 12:00:00,12163.0 -2012-12-13 13:00:00,12077.0 -2012-12-13 14:00:00,11962.0 -2012-12-13 15:00:00,11892.0 -2012-12-13 16:00:00,11800.0 -2012-12-13 17:00:00,11822.0 -2012-12-13 18:00:00,12784.0 -2012-12-13 19:00:00,13581.0 -2012-12-13 20:00:00,13427.0 -2012-12-13 21:00:00,13252.0 -2012-12-13 22:00:00,12952.0 -2012-12-13 23:00:00,12424.0 -2012-12-14 00:00:00,11468.0 -2012-12-12 01:00:00,11029.0 -2012-12-12 02:00:00,10428.0 -2012-12-12 03:00:00,10047.0 -2012-12-12 04:00:00,9866.0 -2012-12-12 05:00:00,9884.0 -2012-12-12 06:00:00,10166.0 -2012-12-12 07:00:00,10866.0 -2012-12-12 08:00:00,12122.0 -2012-12-12 09:00:00,12599.0 -2012-12-12 10:00:00,12543.0 -2012-12-12 11:00:00,12457.0 -2012-12-12 12:00:00,12413.0 -2012-12-12 13:00:00,12283.0 -2012-12-12 14:00:00,12109.0 -2012-12-12 15:00:00,12047.0 -2012-12-12 16:00:00,11909.0 -2012-12-12 17:00:00,11916.0 -2012-12-12 18:00:00,12838.0 -2012-12-12 19:00:00,13669.0 -2012-12-12 20:00:00,13626.0 -2012-12-12 21:00:00,13482.0 -2012-12-12 22:00:00,13230.0 -2012-12-12 23:00:00,12718.0 -2012-12-13 00:00:00,11742.0 -2012-12-11 01:00:00,10837.0 -2012-12-11 02:00:00,10219.0 -2012-12-11 03:00:00,9889.0 -2012-12-11 04:00:00,9695.0 -2012-12-11 05:00:00,9717.0 -2012-12-11 06:00:00,10007.0 -2012-12-11 07:00:00,10777.0 -2012-12-11 08:00:00,12082.0 -2012-12-11 09:00:00,12597.0 -2012-12-11 10:00:00,12586.0 -2012-12-11 11:00:00,12560.0 -2012-12-11 12:00:00,12564.0 -2012-12-11 13:00:00,12503.0 -2012-12-11 14:00:00,12383.0 -2012-12-11 15:00:00,12360.0 -2012-12-11 16:00:00,12263.0 -2012-12-11 17:00:00,12342.0 -2012-12-11 18:00:00,13265.0 -2012-12-11 19:00:00,14096.0 -2012-12-11 20:00:00,13929.0 -2012-12-11 21:00:00,13763.0 -2012-12-11 22:00:00,13496.0 -2012-12-11 23:00:00,12936.0 -2012-12-12 00:00:00,11990.0 -2012-12-10 01:00:00,9854.0 -2012-12-10 02:00:00,9334.0 -2012-12-10 03:00:00,9129.0 -2012-12-10 04:00:00,9067.0 -2012-12-10 05:00:00,9137.0 -2012-12-10 06:00:00,9471.0 -2012-12-10 07:00:00,10364.0 -2012-12-10 08:00:00,11637.0 -2012-12-10 09:00:00,12443.0 -2012-12-10 10:00:00,12528.0 -2012-12-10 11:00:00,12669.0 -2012-12-10 12:00:00,12782.0 -2012-12-10 13:00:00,12741.0 -2012-12-10 14:00:00,12696.0 -2012-12-10 15:00:00,12624.0 -2012-12-10 16:00:00,12559.0 -2012-12-10 17:00:00,12689.0 -2012-12-10 18:00:00,13495.0 -2012-12-10 19:00:00,14040.0 -2012-12-10 20:00:00,13873.0 -2012-12-10 21:00:00,13731.0 -2012-12-10 22:00:00,13407.0 -2012-12-10 23:00:00,12841.0 -2012-12-11 00:00:00,11797.0 -2012-12-09 01:00:00,10063.0 -2012-12-09 02:00:00,9464.0 -2012-12-09 03:00:00,9110.0 -2012-12-09 04:00:00,8933.0 -2012-12-09 05:00:00,8809.0 -2012-12-09 06:00:00,8814.0 -2012-12-09 07:00:00,8946.0 -2012-12-09 08:00:00,9257.0 -2012-12-09 09:00:00,9424.0 -2012-12-09 10:00:00,9749.0 -2012-12-09 11:00:00,10131.0 -2012-12-09 12:00:00,10410.0 -2012-12-09 13:00:00,10552.0 -2012-12-09 14:00:00,10607.0 -2012-12-09 15:00:00,10582.0 -2012-12-09 16:00:00,10618.0 -2012-12-09 17:00:00,10778.0 -2012-12-09 18:00:00,11508.0 -2012-12-09 19:00:00,12002.0 -2012-12-09 20:00:00,11976.0 -2012-12-09 21:00:00,11930.0 -2012-12-09 22:00:00,11685.0 -2012-12-09 23:00:00,11321.0 -2012-12-10 00:00:00,10576.0 -2012-12-08 01:00:00,10249.0 -2012-12-08 02:00:00,9634.0 -2012-12-08 03:00:00,9272.0 -2012-12-08 04:00:00,9021.0 -2012-12-08 05:00:00,8971.0 -2012-12-08 06:00:00,9006.0 -2012-12-08 07:00:00,9280.0 -2012-12-08 08:00:00,9820.0 -2012-12-08 09:00:00,10200.0 -2012-12-08 10:00:00,10517.0 -2012-12-08 11:00:00,10763.0 -2012-12-08 12:00:00,10898.0 -2012-12-08 13:00:00,10865.0 -2012-12-08 14:00:00,10709.0 -2012-12-08 15:00:00,10527.0 -2012-12-08 16:00:00,10462.0 -2012-12-08 17:00:00,10635.0 -2012-12-08 18:00:00,11526.0 -2012-12-08 19:00:00,12120.0 -2012-12-08 20:00:00,12060.0 -2012-12-08 21:00:00,11936.0 -2012-12-08 22:00:00,11726.0 -2012-12-08 23:00:00,11374.0 -2012-12-09 00:00:00,10765.0 -2012-12-07 01:00:00,10117.0 -2012-12-07 02:00:00,9506.0 -2012-12-07 03:00:00,9157.0 -2012-12-07 04:00:00,8999.0 -2012-12-07 05:00:00,8956.0 -2012-12-07 06:00:00,9185.0 -2012-12-07 07:00:00,9907.0 -2012-12-07 08:00:00,11073.0 -2012-12-07 09:00:00,11695.0 -2012-12-07 10:00:00,11794.0 -2012-12-07 11:00:00,11858.0 -2012-12-07 12:00:00,11903.0 -2012-12-07 13:00:00,11877.0 -2012-12-07 14:00:00,11899.0 -2012-12-07 15:00:00,11934.0 -2012-12-07 16:00:00,11892.0 -2012-12-07 17:00:00,12025.0 -2012-12-07 18:00:00,12807.0 -2012-12-07 19:00:00,13177.0 -2012-12-07 20:00:00,12971.0 -2012-12-07 21:00:00,12674.0 -2012-12-07 22:00:00,12361.0 -2012-12-07 23:00:00,11929.0 -2012-12-08 00:00:00,11106.0 -2012-12-06 01:00:00,10550.0 -2012-12-06 02:00:00,9929.0 -2012-12-06 03:00:00,9606.0 -2012-12-06 04:00:00,9427.0 -2012-12-06 05:00:00,9407.0 -2012-12-06 06:00:00,9655.0 -2012-12-06 07:00:00,10402.0 -2012-12-06 08:00:00,11641.0 -2012-12-06 09:00:00,12179.0 -2012-12-06 10:00:00,12299.0 -2012-12-06 11:00:00,12330.0 -2012-12-06 12:00:00,12320.0 -2012-12-06 13:00:00,12203.0 -2012-12-06 14:00:00,12082.0 -2012-12-06 15:00:00,12091.0 -2012-12-06 16:00:00,12053.0 -2012-12-06 17:00:00,12216.0 -2012-12-06 18:00:00,13058.0 -2012-12-06 19:00:00,13451.0 -2012-12-06 20:00:00,13239.0 -2012-12-06 21:00:00,13022.0 -2012-12-06 22:00:00,12670.0 -2012-12-06 23:00:00,12045.0 -2012-12-07 00:00:00,11080.0 -2012-12-05 01:00:00,9987.0 -2012-12-05 02:00:00,9448.0 -2012-12-05 03:00:00,9158.0 -2012-12-05 04:00:00,9055.0 -2012-12-05 05:00:00,9098.0 -2012-12-05 06:00:00,9413.0 -2012-12-05 07:00:00,10196.0 -2012-12-05 08:00:00,11519.0 -2012-12-05 09:00:00,11971.0 -2012-12-05 10:00:00,12009.0 -2012-12-05 11:00:00,12017.0 -2012-12-05 12:00:00,11999.0 -2012-12-05 13:00:00,11837.0 -2012-12-05 14:00:00,11825.0 -2012-12-05 15:00:00,11800.0 -2012-12-05 16:00:00,11735.0 -2012-12-05 17:00:00,11852.0 -2012-12-05 18:00:00,12826.0 -2012-12-05 19:00:00,13567.0 -2012-12-05 20:00:00,13451.0 -2012-12-05 21:00:00,13257.0 -2012-12-05 22:00:00,13011.0 -2012-12-05 23:00:00,12431.0 -2012-12-06 00:00:00,11446.0 -2012-12-04 01:00:00,9665.0 -2012-12-04 02:00:00,9016.0 -2012-12-04 03:00:00,8616.0 -2012-12-04 04:00:00,8441.0 -2012-12-04 05:00:00,8388.0 -2012-12-04 06:00:00,8581.0 -2012-12-04 07:00:00,9284.0 -2012-12-04 08:00:00,10506.0 -2012-12-04 09:00:00,11111.0 -2012-12-04 10:00:00,11281.0 -2012-12-04 11:00:00,11314.0 -2012-12-04 12:00:00,11357.0 -2012-12-04 13:00:00,11311.0 -2012-12-04 14:00:00,11278.0 -2012-12-04 15:00:00,11348.0 -2012-12-04 16:00:00,11241.0 -2012-12-04 17:00:00,11231.0 -2012-12-04 18:00:00,12030.0 -2012-12-04 19:00:00,12802.0 -2012-12-04 20:00:00,12732.0 -2012-12-04 21:00:00,12560.0 -2012-12-04 22:00:00,12289.0 -2012-12-04 23:00:00,11784.0 -2012-12-05 00:00:00,10851.0 -2012-12-03 01:00:00,9058.0 -2012-12-03 02:00:00,8655.0 -2012-12-03 03:00:00,8389.0 -2012-12-03 04:00:00,8254.0 -2012-12-03 05:00:00,8285.0 -2012-12-03 06:00:00,8512.0 -2012-12-03 07:00:00,9286.0 -2012-12-03 08:00:00,10530.0 -2012-12-03 09:00:00,11280.0 -2012-12-03 10:00:00,11551.0 -2012-12-03 11:00:00,11649.0 -2012-12-03 12:00:00,11799.0 -2012-12-03 13:00:00,11829.0 -2012-12-03 14:00:00,11801.0 -2012-12-03 15:00:00,11814.0 -2012-12-03 16:00:00,11726.0 -2012-12-03 17:00:00,11732.0 -2012-12-03 18:00:00,12544.0 -2012-12-03 19:00:00,13095.0 -2012-12-03 20:00:00,12963.0 -2012-12-03 21:00:00,12686.0 -2012-12-03 22:00:00,12317.0 -2012-12-03 23:00:00,11709.0 -2012-12-04 00:00:00,10689.0 -2012-12-02 01:00:00,9299.0 -2012-12-02 02:00:00,8724.0 -2012-12-02 03:00:00,8389.0 -2012-12-02 04:00:00,8080.0 -2012-12-02 05:00:00,7984.0 -2012-12-02 06:00:00,7932.0 -2012-12-02 07:00:00,8035.0 -2012-12-02 08:00:00,8362.0 -2012-12-02 09:00:00,8443.0 -2012-12-02 10:00:00,8702.0 -2012-12-02 11:00:00,8959.0 -2012-12-02 12:00:00,9171.0 -2012-12-02 13:00:00,9227.0 -2012-12-02 14:00:00,9295.0 -2012-12-02 15:00:00,9257.0 -2012-12-02 16:00:00,9230.0 -2012-12-02 17:00:00,9318.0 -2012-12-02 18:00:00,10329.0 -2012-12-02 19:00:00,11089.0 -2012-12-02 20:00:00,11190.0 -2012-12-02 21:00:00,11055.0 -2012-12-02 22:00:00,10873.0 -2012-12-02 23:00:00,10437.0 -2012-12-03 00:00:00,9762.0 -2012-12-01 01:00:00,10037.0 -2012-12-01 02:00:00,9512.0 -2012-12-01 03:00:00,9103.0 -2012-12-01 04:00:00,8918.0 -2012-12-01 05:00:00,8847.0 -2012-12-01 06:00:00,8936.0 -2012-12-01 07:00:00,9216.0 -2012-12-01 08:00:00,9740.0 -2012-12-01 09:00:00,9943.0 -2012-12-01 10:00:00,10198.0 -2012-12-01 11:00:00,10321.0 -2012-12-01 12:00:00,10316.0 -2012-12-01 13:00:00,10277.0 -2012-12-01 14:00:00,10174.0 -2012-12-01 15:00:00,10163.0 -2012-12-01 16:00:00,10084.0 -2012-12-01 17:00:00,10188.0 -2012-12-01 18:00:00,11054.0 -2012-12-01 19:00:00,11450.0 -2012-12-01 20:00:00,11331.0 -2012-12-01 21:00:00,11170.0 -2012-12-01 22:00:00,10966.0 -2012-12-01 23:00:00,10593.0 -2012-12-02 00:00:00,9988.0 -2012-11-30 01:00:00,10065.0 -2012-11-30 02:00:00,9537.0 -2012-11-30 03:00:00,9169.0 -2012-11-30 04:00:00,8979.0 -2012-11-30 05:00:00,8931.0 -2012-11-30 06:00:00,9171.0 -2012-11-30 07:00:00,9857.0 -2012-11-30 08:00:00,10987.0 -2012-11-30 09:00:00,11479.0 -2012-11-30 10:00:00,11524.0 -2012-11-30 11:00:00,11531.0 -2012-11-30 12:00:00,11582.0 -2012-11-30 13:00:00,11481.0 -2012-11-30 14:00:00,11336.0 -2012-11-30 15:00:00,11324.0 -2012-11-30 16:00:00,11217.0 -2012-11-30 17:00:00,11254.0 -2012-11-30 18:00:00,12091.0 -2012-11-30 19:00:00,12644.0 -2012-11-30 20:00:00,12488.0 -2012-11-30 21:00:00,12262.0 -2012-11-30 22:00:00,12005.0 -2012-11-30 23:00:00,11564.0 -2012-12-01 00:00:00,10861.0 -2012-11-29 01:00:00,10540.0 -2012-11-29 02:00:00,9960.0 -2012-11-29 03:00:00,9625.0 -2012-11-29 04:00:00,9468.0 -2012-11-29 05:00:00,9455.0 -2012-11-29 06:00:00,9747.0 -2012-11-29 07:00:00,10467.0 -2012-11-29 08:00:00,11650.0 -2012-11-29 09:00:00,12059.0 -2012-11-29 10:00:00,12063.0 -2012-11-29 11:00:00,12018.0 -2012-11-29 12:00:00,11976.0 -2012-11-29 13:00:00,11877.0 -2012-11-29 14:00:00,11727.0 -2012-11-29 15:00:00,11660.0 -2012-11-29 16:00:00,11587.0 -2012-11-29 17:00:00,11618.0 -2012-11-29 18:00:00,12463.0 -2012-11-29 19:00:00,13012.0 -2012-11-29 20:00:00,12855.0 -2012-11-29 21:00:00,12681.0 -2012-11-29 22:00:00,12322.0 -2012-11-29 23:00:00,11782.0 -2012-11-30 00:00:00,10900.0 -2012-11-28 01:00:00,10731.0 -2012-11-28 02:00:00,10158.0 -2012-11-28 03:00:00,9844.0 -2012-11-28 04:00:00,9663.0 -2012-11-28 05:00:00,9676.0 -2012-11-28 06:00:00,9936.0 -2012-11-28 07:00:00,10686.0 -2012-11-28 08:00:00,11900.0 -2012-11-28 09:00:00,12311.0 -2012-11-28 10:00:00,12308.0 -2012-11-28 11:00:00,12258.0 -2012-11-28 12:00:00,12226.0 -2012-11-28 13:00:00,12094.0 -2012-11-28 14:00:00,11974.0 -2012-11-28 15:00:00,11886.0 -2012-11-28 16:00:00,11784.0 -2012-11-28 17:00:00,11812.0 -2012-11-28 18:00:00,12588.0 -2012-11-28 19:00:00,13338.0 -2012-11-28 20:00:00,13265.0 -2012-11-28 21:00:00,13119.0 -2012-11-28 22:00:00,12857.0 -2012-11-28 23:00:00,12336.0 -2012-11-29 00:00:00,11423.0 -2012-11-27 01:00:00,10734.0 -2012-11-27 02:00:00,10260.0 -2012-11-27 03:00:00,9995.0 -2012-11-27 04:00:00,9880.0 -2012-11-27 05:00:00,9927.0 -2012-11-27 06:00:00,10238.0 -2012-11-27 07:00:00,11012.0 -2012-11-27 08:00:00,12220.0 -2012-11-27 09:00:00,12594.0 -2012-11-27 10:00:00,12609.0 -2012-11-27 11:00:00,12560.0 -2012-11-27 12:00:00,12535.0 -2012-11-27 13:00:00,12484.0 -2012-11-27 14:00:00,12352.0 -2012-11-27 15:00:00,12293.0 -2012-11-27 16:00:00,12156.0 -2012-11-27 17:00:00,12257.0 -2012-11-27 18:00:00,13103.0 -2012-11-27 19:00:00,13782.0 -2012-11-27 20:00:00,13648.0 -2012-11-27 21:00:00,13447.0 -2012-11-27 22:00:00,13138.0 -2012-11-27 23:00:00,12549.0 -2012-11-28 00:00:00,11625.0 -2012-11-26 01:00:00,9626.0 -2012-11-26 02:00:00,9259.0 -2012-11-26 03:00:00,9086.0 -2012-11-26 04:00:00,9043.0 -2012-11-26 05:00:00,9144.0 -2012-11-26 06:00:00,9530.0 -2012-11-26 07:00:00,10367.0 -2012-11-26 08:00:00,11620.0 -2012-11-26 09:00:00,12122.0 -2012-11-26 10:00:00,12329.0 -2012-11-26 11:00:00,12380.0 -2012-11-26 12:00:00,12421.0 -2012-11-26 13:00:00,12314.0 -2012-11-26 14:00:00,12228.0 -2012-11-26 15:00:00,12305.0 -2012-11-26 16:00:00,12309.0 -2012-11-26 17:00:00,12402.0 -2012-11-26 18:00:00,13150.0 -2012-11-26 19:00:00,13635.0 -2012-11-26 20:00:00,13491.0 -2012-11-26 21:00:00,13298.0 -2012-11-26 22:00:00,13012.0 -2012-11-26 23:00:00,12454.0 -2012-11-27 00:00:00,11547.0 -2012-11-25 01:00:00,9950.0 -2012-11-25 02:00:00,9397.0 -2012-11-25 03:00:00,9075.0 -2012-11-25 04:00:00,8825.0 -2012-11-25 05:00:00,8774.0 -2012-11-25 06:00:00,8781.0 -2012-11-25 07:00:00,8986.0 -2012-11-25 08:00:00,9233.0 -2012-11-25 09:00:00,9277.0 -2012-11-25 10:00:00,9461.0 -2012-11-25 11:00:00,9641.0 -2012-11-25 12:00:00,9716.0 -2012-11-25 13:00:00,9756.0 -2012-11-25 14:00:00,9727.0 -2012-11-25 15:00:00,9695.0 -2012-11-25 16:00:00,9724.0 -2012-11-25 17:00:00,9921.0 -2012-11-25 18:00:00,10917.0 -2012-11-25 19:00:00,11658.0 -2012-11-25 20:00:00,11772.0 -2012-11-25 21:00:00,11671.0 -2012-11-25 22:00:00,11421.0 -2012-11-25 23:00:00,10971.0 -2012-11-26 00:00:00,10302.0 -2012-11-24 01:00:00,9728.0 -2012-11-24 02:00:00,9221.0 -2012-11-24 03:00:00,8957.0 -2012-11-24 04:00:00,8786.0 -2012-11-24 05:00:00,8752.0 -2012-11-24 06:00:00,8864.0 -2012-11-24 07:00:00,9163.0 -2012-11-24 08:00:00,9618.0 -2012-11-24 09:00:00,9804.0 -2012-11-24 10:00:00,10231.0 -2012-11-24 11:00:00,10593.0 -2012-11-24 12:00:00,10713.0 -2012-11-24 13:00:00,10637.0 -2012-11-24 14:00:00,10479.0 -2012-11-24 15:00:00,10253.0 -2012-11-24 16:00:00,10149.0 -2012-11-24 17:00:00,10255.0 -2012-11-24 18:00:00,11162.0 -2012-11-24 19:00:00,11786.0 -2012-11-24 20:00:00,11761.0 -2012-11-24 21:00:00,11688.0 -2012-11-24 22:00:00,11469.0 -2012-11-24 23:00:00,11131.0 -2012-11-25 00:00:00,10603.0 -2012-11-23 01:00:00,8316.0 -2012-11-23 02:00:00,8014.0 -2012-11-23 03:00:00,7841.0 -2012-11-23 04:00:00,7742.0 -2012-11-23 05:00:00,7759.0 -2012-11-23 06:00:00,8010.0 -2012-11-23 07:00:00,8477.0 -2012-11-23 08:00:00,9154.0 -2012-11-23 09:00:00,9494.0 -2012-11-23 10:00:00,9845.0 -2012-11-23 11:00:00,10172.0 -2012-11-23 12:00:00,10367.0 -2012-11-23 13:00:00,10474.0 -2012-11-23 14:00:00,10446.0 -2012-11-23 15:00:00,10376.0 -2012-11-23 16:00:00,10446.0 -2012-11-23 17:00:00,10632.0 -2012-11-23 18:00:00,11328.0 -2012-11-23 19:00:00,11850.0 -2012-11-23 20:00:00,11714.0 -2012-11-23 21:00:00,11527.0 -2012-11-23 22:00:00,11247.0 -2012-11-23 23:00:00,10914.0 -2012-11-24 00:00:00,10365.0 -2012-11-22 01:00:00,9212.0 -2012-11-22 02:00:00,8624.0 -2012-11-22 03:00:00,8217.0 -2012-11-22 04:00:00,7970.0 -2012-11-22 05:00:00,7836.0 -2012-11-22 06:00:00,7869.0 -2012-11-22 07:00:00,8041.0 -2012-11-22 08:00:00,8249.0 -2012-11-22 09:00:00,8177.0 -2012-11-22 10:00:00,8389.0 -2012-11-22 11:00:00,8621.0 -2012-11-22 12:00:00,8792.0 -2012-11-22 13:00:00,8870.0 -2012-11-22 14:00:00,8817.0 -2012-11-22 15:00:00,8708.0 -2012-11-22 16:00:00,8593.0 -2012-11-22 17:00:00,8612.0 -2012-11-22 18:00:00,9226.0 -2012-11-22 19:00:00,9362.0 -2012-11-22 20:00:00,9271.0 -2012-11-22 21:00:00,9170.0 -2012-11-22 22:00:00,9081.0 -2012-11-22 23:00:00,8951.0 -2012-11-23 00:00:00,8666.0 -2012-11-21 01:00:00,9772.0 -2012-11-21 02:00:00,9238.0 -2012-11-21 03:00:00,8900.0 -2012-11-21 04:00:00,8717.0 -2012-11-21 05:00:00,8684.0 -2012-11-21 06:00:00,8938.0 -2012-11-21 07:00:00,9631.0 -2012-11-21 08:00:00,10635.0 -2012-11-21 09:00:00,11174.0 -2012-11-21 10:00:00,11515.0 -2012-11-21 11:00:00,11657.0 -2012-11-21 12:00:00,11697.0 -2012-11-21 13:00:00,11611.0 -2012-11-21 14:00:00,11532.0 -2012-11-21 15:00:00,11417.0 -2012-11-21 16:00:00,11243.0 -2012-11-21 17:00:00,11097.0 -2012-11-21 18:00:00,11562.0 -2012-11-21 19:00:00,12151.0 -2012-11-21 20:00:00,11961.0 -2012-11-21 21:00:00,11654.0 -2012-11-21 22:00:00,11377.0 -2012-11-21 23:00:00,10924.0 -2012-11-22 00:00:00,10071.0 -2012-11-20 01:00:00,9548.0 -2012-11-20 02:00:00,9035.0 -2012-11-20 03:00:00,8741.0 -2012-11-20 04:00:00,8496.0 -2012-11-20 05:00:00,8474.0 -2012-11-20 06:00:00,8737.0 -2012-11-20 07:00:00,9464.0 -2012-11-20 08:00:00,10577.0 -2012-11-20 09:00:00,11221.0 -2012-11-20 10:00:00,11456.0 -2012-11-20 11:00:00,11589.0 -2012-11-20 12:00:00,11637.0 -2012-11-20 13:00:00,11628.0 -2012-11-20 14:00:00,11599.0 -2012-11-20 15:00:00,11546.0 -2012-11-20 16:00:00,11438.0 -2012-11-20 17:00:00,11421.0 -2012-11-20 18:00:00,11911.0 -2012-11-20 19:00:00,12412.0 -2012-11-20 20:00:00,12239.0 -2012-11-20 21:00:00,12050.0 -2012-11-20 22:00:00,11791.0 -2012-11-20 23:00:00,11367.0 -2012-11-21 00:00:00,10596.0 -2012-11-19 01:00:00,9179.0 -2012-11-19 02:00:00,8771.0 -2012-11-19 03:00:00,8586.0 -2012-11-19 04:00:00,8498.0 -2012-11-19 05:00:00,8526.0 -2012-11-19 06:00:00,8808.0 -2012-11-19 07:00:00,9603.0 -2012-11-19 08:00:00,10772.0 -2012-11-19 09:00:00,11320.0 -2012-11-19 10:00:00,11591.0 -2012-11-19 11:00:00,11595.0 -2012-11-19 12:00:00,11582.0 -2012-11-19 13:00:00,11489.0 -2012-11-19 14:00:00,11404.0 -2012-11-19 15:00:00,11397.0 -2012-11-19 16:00:00,11323.0 -2012-11-19 17:00:00,11433.0 -2012-11-19 18:00:00,12143.0 -2012-11-19 19:00:00,12465.0 -2012-11-19 20:00:00,12211.0 -2012-11-19 21:00:00,12004.0 -2012-11-19 22:00:00,11710.0 -2012-11-19 23:00:00,11194.0 -2012-11-20 00:00:00,10372.0 -2012-11-18 01:00:00,9388.0 -2012-11-18 02:00:00,9023.0 -2012-11-18 03:00:00,8711.0 -2012-11-18 04:00:00,8524.0 -2012-11-18 05:00:00,8499.0 -2012-11-18 06:00:00,8560.0 -2012-11-18 07:00:00,8700.0 -2012-11-18 08:00:00,8897.0 -2012-11-18 09:00:00,8968.0 -2012-11-18 10:00:00,9135.0 -2012-11-18 11:00:00,9291.0 -2012-11-18 12:00:00,9324.0 -2012-11-18 13:00:00,9388.0 -2012-11-18 14:00:00,9351.0 -2012-11-18 15:00:00,9360.0 -2012-11-18 16:00:00,9300.0 -2012-11-18 17:00:00,9448.0 -2012-11-18 18:00:00,10151.0 -2012-11-18 19:00:00,10921.0 -2012-11-18 20:00:00,10910.0 -2012-11-18 21:00:00,10838.0 -2012-11-18 22:00:00,10587.0 -2012-11-18 23:00:00,10265.0 -2012-11-19 00:00:00,9701.0 -2012-11-17 01:00:00,9894.0 -2012-11-17 02:00:00,9415.0 -2012-11-17 03:00:00,9122.0 -2012-11-17 04:00:00,8931.0 -2012-11-17 05:00:00,8849.0 -2012-11-17 06:00:00,8954.0 -2012-11-17 07:00:00,9286.0 -2012-11-17 08:00:00,9723.0 -2012-11-17 09:00:00,9931.0 -2012-11-17 10:00:00,10177.0 -2012-11-17 11:00:00,10296.0 -2012-11-17 12:00:00,10307.0 -2012-11-17 13:00:00,10192.0 -2012-11-17 14:00:00,10074.0 -2012-11-17 15:00:00,9824.0 -2012-11-17 16:00:00,9741.0 -2012-11-17 17:00:00,9770.0 -2012-11-17 18:00:00,10395.0 -2012-11-17 19:00:00,11007.0 -2012-11-17 20:00:00,10973.0 -2012-11-17 21:00:00,10807.0 -2012-11-17 22:00:00,10673.0 -2012-11-17 23:00:00,10366.0 -2012-11-18 00:00:00,9939.0 -2012-11-16 01:00:00,10098.0 -2012-11-16 02:00:00,9600.0 -2012-11-16 03:00:00,9333.0 -2012-11-16 04:00:00,9152.0 -2012-11-16 05:00:00,9149.0 -2012-11-16 06:00:00,9429.0 -2012-11-16 07:00:00,10135.0 -2012-11-16 08:00:00,11223.0 -2012-11-16 09:00:00,11605.0 -2012-11-16 10:00:00,11638.0 -2012-11-16 11:00:00,11601.0 -2012-11-16 12:00:00,11584.0 -2012-11-16 13:00:00,11464.0 -2012-11-16 14:00:00,11371.0 -2012-11-16 15:00:00,11332.0 -2012-11-16 16:00:00,11214.0 -2012-11-16 17:00:00,11157.0 -2012-11-16 18:00:00,11675.0 -2012-11-16 19:00:00,12276.0 -2012-11-16 20:00:00,12144.0 -2012-11-16 21:00:00,11912.0 -2012-11-16 22:00:00,11613.0 -2012-11-16 23:00:00,11257.0 -2012-11-17 00:00:00,10543.0 -2012-11-15 01:00:00,10218.0 -2012-11-15 02:00:00,9736.0 -2012-11-15 03:00:00,9474.0 -2012-11-15 04:00:00,9299.0 -2012-11-15 05:00:00,9325.0 -2012-11-15 06:00:00,9562.0 -2012-11-15 07:00:00,10328.0 -2012-11-15 08:00:00,11406.0 -2012-11-15 09:00:00,11883.0 -2012-11-15 10:00:00,12028.0 -2012-11-15 11:00:00,12059.0 -2012-11-15 12:00:00,11994.0 -2012-11-15 13:00:00,11857.0 -2012-11-15 14:00:00,11697.0 -2012-11-15 15:00:00,11646.0 -2012-11-15 16:00:00,11517.0 -2012-11-15 17:00:00,11467.0 -2012-11-15 18:00:00,12015.0 -2012-11-15 19:00:00,12711.0 -2012-11-15 20:00:00,12581.0 -2012-11-15 21:00:00,12371.0 -2012-11-15 22:00:00,12101.0 -2012-11-15 23:00:00,11608.0 -2012-11-16 00:00:00,10803.0 -2012-11-14 01:00:00,10308.0 -2012-11-14 02:00:00,9854.0 -2012-11-14 03:00:00,9589.0 -2012-11-14 04:00:00,9431.0 -2012-11-14 05:00:00,9396.0 -2012-11-14 06:00:00,9654.0 -2012-11-14 07:00:00,10369.0 -2012-11-14 08:00:00,11468.0 -2012-11-14 09:00:00,11932.0 -2012-11-14 10:00:00,12005.0 -2012-11-14 11:00:00,12010.0 -2012-11-14 12:00:00,12027.0 -2012-11-14 13:00:00,11924.0 -2012-11-14 14:00:00,11776.0 -2012-11-14 15:00:00,11712.0 -2012-11-14 16:00:00,11626.0 -2012-11-14 17:00:00,11555.0 -2012-11-14 18:00:00,12108.0 -2012-11-14 19:00:00,12819.0 -2012-11-14 20:00:00,12721.0 -2012-11-14 21:00:00,12540.0 -2012-11-14 22:00:00,12274.0 -2012-11-14 23:00:00,11739.0 -2012-11-15 00:00:00,10955.0 -2012-11-13 01:00:00,10288.0 -2012-11-13 02:00:00,9824.0 -2012-11-13 03:00:00,9565.0 -2012-11-13 04:00:00,9455.0 -2012-11-13 05:00:00,9466.0 -2012-11-13 06:00:00,9749.0 -2012-11-13 07:00:00,10521.0 -2012-11-13 08:00:00,11625.0 -2012-11-13 09:00:00,12031.0 -2012-11-13 10:00:00,12130.0 -2012-11-13 11:00:00,12129.0 -2012-11-13 12:00:00,12115.0 -2012-11-13 13:00:00,12028.0 -2012-11-13 14:00:00,11914.0 -2012-11-13 15:00:00,11888.0 -2012-11-13 16:00:00,11696.0 -2012-11-13 17:00:00,11709.0 -2012-11-13 18:00:00,12245.0 -2012-11-13 19:00:00,12969.0 -2012-11-13 20:00:00,12880.0 -2012-11-13 21:00:00,12732.0 -2012-11-13 22:00:00,12415.0 -2012-11-13 23:00:00,11889.0 -2012-11-14 00:00:00,11064.0 -2012-11-12 01:00:00,8874.0 -2012-11-12 02:00:00,8564.0 -2012-11-12 03:00:00,8381.0 -2012-11-12 04:00:00,8358.0 -2012-11-12 05:00:00,8428.0 -2012-11-12 06:00:00,8788.0 -2012-11-12 07:00:00,9582.0 -2012-11-12 08:00:00,10806.0 -2012-11-12 09:00:00,11338.0 -2012-11-12 10:00:00,11560.0 -2012-11-12 11:00:00,11763.0 -2012-11-12 12:00:00,11884.0 -2012-11-12 13:00:00,11903.0 -2012-11-12 14:00:00,11863.0 -2012-11-12 15:00:00,11896.0 -2012-11-12 16:00:00,11942.0 -2012-11-12 17:00:00,12107.0 -2012-11-12 18:00:00,12731.0 -2012-11-12 19:00:00,13208.0 -2012-11-12 20:00:00,13005.0 -2012-11-12 21:00:00,12746.0 -2012-11-12 22:00:00,12402.0 -2012-11-12 23:00:00,11839.0 -2012-11-13 00:00:00,11014.0 -2012-11-11 01:00:00,8980.0 -2012-11-11 02:00:00,8486.0 -2012-11-11 03:00:00,8165.0 -2012-11-11 04:00:00,7922.0 -2012-11-11 05:00:00,7851.0 -2012-11-11 06:00:00,7787.0 -2012-11-11 07:00:00,7969.0 -2012-11-11 08:00:00,8121.0 -2012-11-11 09:00:00,8274.0 -2012-11-11 10:00:00,8605.0 -2012-11-11 11:00:00,8905.0 -2012-11-11 12:00:00,9126.0 -2012-11-11 13:00:00,9222.0 -2012-11-11 14:00:00,9278.0 -2012-11-11 15:00:00,9253.0 -2012-11-11 16:00:00,9325.0 -2012-11-11 17:00:00,9619.0 -2012-11-11 18:00:00,10343.0 -2012-11-11 19:00:00,10695.0 -2012-11-11 20:00:00,10640.0 -2012-11-11 21:00:00,10449.0 -2012-11-11 22:00:00,10199.0 -2012-11-11 23:00:00,9909.0 -2012-11-12 00:00:00,9432.0 -2012-11-10 01:00:00,9446.0 -2012-11-10 02:00:00,8948.0 -2012-11-10 03:00:00,8585.0 -2012-11-10 04:00:00,8381.0 -2012-11-10 05:00:00,8260.0 -2012-11-10 06:00:00,8362.0 -2012-11-10 07:00:00,8610.0 -2012-11-10 08:00:00,8957.0 -2012-11-10 09:00:00,9242.0 -2012-11-10 10:00:00,9660.0 -2012-11-10 11:00:00,9914.0 -2012-11-10 12:00:00,10031.0 -2012-11-10 13:00:00,10143.0 -2012-11-10 14:00:00,10158.0 -2012-11-10 15:00:00,10077.0 -2012-11-10 16:00:00,9872.0 -2012-11-10 17:00:00,9784.0 -2012-11-10 18:00:00,10265.0 -2012-11-10 19:00:00,10863.0 -2012-11-10 20:00:00,10790.0 -2012-11-10 21:00:00,10536.0 -2012-11-10 22:00:00,10400.0 -2012-11-10 23:00:00,10041.0 -2012-11-11 00:00:00,9521.0 -2012-11-09 01:00:00,9911.0 -2012-11-09 02:00:00,9436.0 -2012-11-09 03:00:00,9140.0 -2012-11-09 04:00:00,8970.0 -2012-11-09 05:00:00,8974.0 -2012-11-09 06:00:00,9197.0 -2012-11-09 07:00:00,9900.0 -2012-11-09 08:00:00,10930.0 -2012-11-09 09:00:00,11320.0 -2012-11-09 10:00:00,11481.0 -2012-11-09 11:00:00,11549.0 -2012-11-09 12:00:00,11514.0 -2012-11-09 13:00:00,11415.0 -2012-11-09 14:00:00,11359.0 -2012-11-09 15:00:00,11350.0 -2012-11-09 16:00:00,11264.0 -2012-11-09 17:00:00,11211.0 -2012-11-09 18:00:00,11608.0 -2012-11-09 19:00:00,12110.0 -2012-11-09 20:00:00,11899.0 -2012-11-09 21:00:00,11610.0 -2012-11-09 22:00:00,11299.0 -2012-11-09 23:00:00,10890.0 -2012-11-10 00:00:00,10196.0 -2012-11-08 01:00:00,9749.0 -2012-11-08 02:00:00,9277.0 -2012-11-08 03:00:00,8978.0 -2012-11-08 04:00:00,8783.0 -2012-11-08 05:00:00,8763.0 -2012-11-08 06:00:00,9007.0 -2012-11-08 07:00:00,9716.0 -2012-11-08 08:00:00,10868.0 -2012-11-08 09:00:00,11467.0 -2012-11-08 10:00:00,11677.0 -2012-11-08 11:00:00,11770.0 -2012-11-08 12:00:00,11909.0 -2012-11-08 13:00:00,11867.0 -2012-11-08 14:00:00,11814.0 -2012-11-08 15:00:00,11755.0 -2012-11-08 16:00:00,11673.0 -2012-11-08 17:00:00,11581.0 -2012-11-08 18:00:00,11909.0 -2012-11-08 19:00:00,12615.0 -2012-11-08 20:00:00,12511.0 -2012-11-08 21:00:00,12315.0 -2012-11-08 22:00:00,12006.0 -2012-11-08 23:00:00,11503.0 -2012-11-09 00:00:00,10662.0 -2012-11-07 01:00:00,10021.0 -2012-11-07 02:00:00,9546.0 -2012-11-07 03:00:00,9204.0 -2012-11-07 04:00:00,8984.0 -2012-11-07 05:00:00,8949.0 -2012-11-07 06:00:00,9194.0 -2012-11-07 07:00:00,9886.0 -2012-11-07 08:00:00,11064.0 -2012-11-07 09:00:00,11627.0 -2012-11-07 10:00:00,11851.0 -2012-11-07 11:00:00,11909.0 -2012-11-07 12:00:00,11892.0 -2012-11-07 13:00:00,11890.0 -2012-11-07 14:00:00,11814.0 -2012-11-07 15:00:00,11815.0 -2012-11-07 16:00:00,11749.0 -2012-11-07 17:00:00,11808.0 -2012-11-07 18:00:00,12282.0 -2012-11-07 19:00:00,12688.0 -2012-11-07 20:00:00,12471.0 -2012-11-07 21:00:00,12231.0 -2012-11-07 22:00:00,11888.0 -2012-11-07 23:00:00,11341.0 -2012-11-08 00:00:00,10530.0 -2012-11-06 01:00:00,9941.0 -2012-11-06 02:00:00,9487.0 -2012-11-06 03:00:00,9247.0 -2012-11-06 04:00:00,9117.0 -2012-11-06 05:00:00,9111.0 -2012-11-06 06:00:00,9422.0 -2012-11-06 07:00:00,10227.0 -2012-11-06 08:00:00,11213.0 -2012-11-06 09:00:00,11590.0 -2012-11-06 10:00:00,11776.0 -2012-11-06 11:00:00,11912.0 -2012-11-06 12:00:00,12104.0 -2012-11-06 13:00:00,12150.0 -2012-11-06 14:00:00,12109.0 -2012-11-06 15:00:00,12148.0 -2012-11-06 16:00:00,12077.0 -2012-11-06 17:00:00,12044.0 -2012-11-06 18:00:00,12537.0 -2012-11-06 19:00:00,12946.0 -2012-11-06 20:00:00,12730.0 -2012-11-06 21:00:00,12520.0 -2012-11-06 22:00:00,12171.0 -2012-11-06 23:00:00,11558.0 -2012-11-07 00:00:00,10753.0 -2012-11-05 01:00:00,9100.0 -2012-11-05 02:00:00,8797.0 -2012-11-05 03:00:00,8665.0 -2012-11-05 04:00:00,8600.0 -2012-11-05 05:00:00,8678.0 -2012-11-05 06:00:00,9003.0 -2012-11-05 07:00:00,9840.0 -2012-11-05 08:00:00,11004.0 -2012-11-05 09:00:00,11603.0 -2012-11-05 10:00:00,11777.0 -2012-11-05 11:00:00,11875.0 -2012-11-05 12:00:00,11907.0 -2012-11-05 13:00:00,11894.0 -2012-11-05 14:00:00,11848.0 -2012-11-05 15:00:00,11851.0 -2012-11-05 16:00:00,11776.0 -2012-11-05 17:00:00,11777.0 -2012-11-05 18:00:00,12182.0 -2012-11-05 19:00:00,12771.0 -2012-11-05 20:00:00,12599.0 -2012-11-05 21:00:00,12364.0 -2012-11-05 22:00:00,12052.0 -2012-11-05 23:00:00,11489.0 -2012-11-06 00:00:00,10660.0 -2012-11-04 01:00:00,9382.0 -2012-11-04 03:00:00,8546.0 -2012-11-04 04:00:00,8495.0 -2012-11-04 05:00:00,8424.0 -2012-11-04 06:00:00,8412.0 -2012-11-04 07:00:00,8621.0 -2012-11-04 08:00:00,8817.0 -2012-11-04 09:00:00,9009.0 -2012-11-04 10:00:00,9186.0 -2012-11-04 11:00:00,9106.0 -2012-11-04 12:00:00,9324.0 -2012-11-04 13:00:00,9331.0 -2012-11-04 14:00:00,9272.0 -2012-11-04 15:00:00,9223.0 -2012-11-04 16:00:00,9211.0 -2012-11-04 17:00:00,9260.0 -2012-11-04 18:00:00,9864.0 -2012-11-04 19:00:00,10779.0 -2012-11-04 20:00:00,10868.0 -2012-11-04 21:00:00,10808.0 -2012-11-04 22:00:00,10566.0 -2012-11-04 23:00:00,10240.0 -2012-11-05 00:00:00,9541.0 -2012-11-03 01:00:00,9726.0 -2012-11-03 02:00:00,9140.0 -2012-11-03 03:00:00,8827.0 -2012-11-03 04:00:00,8623.0 -2012-11-03 05:00:00,8537.0 -2012-11-03 06:00:00,8577.0 -2012-11-03 07:00:00,8830.0 -2012-11-03 08:00:00,9388.0 -2012-11-03 09:00:00,9839.0 -2012-11-03 10:00:00,10104.0 -2012-11-03 11:00:00,10347.0 -2012-11-03 12:00:00,10464.0 -2012-11-03 13:00:00,10415.0 -2012-11-03 14:00:00,10290.0 -2012-11-03 15:00:00,10078.0 -2012-11-03 16:00:00,9995.0 -2012-11-03 17:00:00,9915.0 -2012-11-03 18:00:00,10072.0 -2012-11-03 19:00:00,10411.0 -2012-11-03 20:00:00,10881.0 -2012-11-03 21:00:00,10775.0 -2012-11-03 22:00:00,10629.0 -2012-11-03 23:00:00,10337.0 -2012-11-04 00:00:00,9948.0 -2012-11-02 01:00:00,9729.0 -2012-11-02 02:00:00,9211.0 -2012-11-02 03:00:00,8924.0 -2012-11-02 04:00:00,8767.0 -2012-11-02 05:00:00,8779.0 -2012-11-02 06:00:00,9010.0 -2012-11-02 07:00:00,9729.0 -2012-11-02 08:00:00,10978.0 -2012-11-02 09:00:00,11704.0 -2012-11-02 10:00:00,11676.0 -2012-11-02 11:00:00,11664.0 -2012-11-02 12:00:00,11612.0 -2012-11-02 13:00:00,11493.0 -2012-11-02 14:00:00,11354.0 -2012-11-02 15:00:00,11282.0 -2012-11-02 16:00:00,11120.0 -2012-11-02 17:00:00,10948.0 -2012-11-02 18:00:00,10933.0 -2012-11-02 19:00:00,11280.0 -2012-11-02 20:00:00,11873.0 -2012-11-02 21:00:00,11744.0 -2012-11-02 22:00:00,11491.0 -2012-11-02 23:00:00,11093.0 -2012-11-03 00:00:00,10405.0 -2012-11-01 01:00:00,9753.0 -2012-11-01 02:00:00,9314.0 -2012-11-01 03:00:00,9054.0 -2012-11-01 04:00:00,8913.0 -2012-11-01 05:00:00,8912.0 -2012-11-01 06:00:00,9131.0 -2012-11-01 07:00:00,9891.0 -2012-11-01 08:00:00,11179.0 -2012-11-01 09:00:00,11863.0 -2012-11-01 10:00:00,11841.0 -2012-11-01 11:00:00,11785.0 -2012-11-01 12:00:00,11777.0 -2012-11-01 13:00:00,11687.0 -2012-11-01 14:00:00,11570.0 -2012-11-01 15:00:00,11497.0 -2012-11-01 16:00:00,11400.0 -2012-11-01 17:00:00,11262.0 -2012-11-01 18:00:00,11209.0 -2012-11-01 19:00:00,11494.0 -2012-11-01 20:00:00,12105.0 -2012-11-01 21:00:00,12023.0 -2012-11-01 22:00:00,11772.0 -2012-11-01 23:00:00,11288.0 -2012-11-02 00:00:00,10500.0 -2012-10-31 01:00:00,9878.0 -2012-10-31 02:00:00,9347.0 -2012-10-31 03:00:00,9032.0 -2012-10-31 04:00:00,8878.0 -2012-10-31 05:00:00,8856.0 -2012-10-31 06:00:00,9095.0 -2012-10-31 07:00:00,9852.0 -2012-10-31 08:00:00,11163.0 -2012-10-31 09:00:00,11931.0 -2012-10-31 10:00:00,11956.0 -2012-10-31 11:00:00,11951.0 -2012-10-31 12:00:00,11885.0 -2012-10-31 13:00:00,11764.0 -2012-10-31 14:00:00,11659.0 -2012-10-31 15:00:00,11620.0 -2012-10-31 16:00:00,11397.0 -2012-10-31 17:00:00,11302.0 -2012-10-31 18:00:00,11216.0 -2012-10-31 19:00:00,11463.0 -2012-10-31 20:00:00,11962.0 -2012-10-31 21:00:00,11878.0 -2012-10-31 22:00:00,11655.0 -2012-10-31 23:00:00,11228.0 -2012-11-01 00:00:00,10466.0 -2012-10-30 01:00:00,9739.0 -2012-10-30 02:00:00,9232.0 -2012-10-30 03:00:00,8913.0 -2012-10-30 04:00:00,8786.0 -2012-10-30 05:00:00,8756.0 -2012-10-30 06:00:00,9012.0 -2012-10-30 07:00:00,9745.0 -2012-10-30 08:00:00,11067.0 -2012-10-30 09:00:00,11890.0 -2012-10-30 10:00:00,11909.0 -2012-10-30 11:00:00,11998.0 -2012-10-30 12:00:00,12055.0 -2012-10-30 13:00:00,11915.0 -2012-10-30 14:00:00,11768.0 -2012-10-30 15:00:00,11724.0 -2012-10-30 16:00:00,11672.0 -2012-10-30 17:00:00,11546.0 -2012-10-30 18:00:00,11581.0 -2012-10-30 19:00:00,11873.0 -2012-10-30 20:00:00,12359.0 -2012-10-30 21:00:00,12275.0 -2012-10-30 22:00:00,11994.0 -2012-10-30 23:00:00,11507.0 -2012-10-31 00:00:00,10672.0 -2012-10-29 01:00:00,9084.0 -2012-10-29 02:00:00,8749.0 -2012-10-29 03:00:00,8525.0 -2012-10-29 04:00:00,8458.0 -2012-10-29 05:00:00,8412.0 -2012-10-29 06:00:00,8738.0 -2012-10-29 07:00:00,9544.0 -2012-10-29 08:00:00,10865.0 -2012-10-29 09:00:00,11656.0 -2012-10-29 10:00:00,11595.0 -2012-10-29 11:00:00,11566.0 -2012-10-29 12:00:00,11598.0 -2012-10-29 13:00:00,11545.0 -2012-10-29 14:00:00,11471.0 -2012-10-29 15:00:00,11427.0 -2012-10-29 16:00:00,11279.0 -2012-10-29 17:00:00,11170.0 -2012-10-29 18:00:00,11149.0 -2012-10-29 19:00:00,11396.0 -2012-10-29 20:00:00,12132.0 -2012-10-29 21:00:00,12076.0 -2012-10-29 22:00:00,11807.0 -2012-10-29 23:00:00,11315.0 -2012-10-30 00:00:00,10506.0 -2012-10-28 01:00:00,9182.0 -2012-10-28 02:00:00,8771.0 -2012-10-28 03:00:00,8454.0 -2012-10-28 04:00:00,8325.0 -2012-10-28 05:00:00,8218.0 -2012-10-28 06:00:00,8207.0 -2012-10-28 07:00:00,8329.0 -2012-10-28 08:00:00,8590.0 -2012-10-28 09:00:00,8826.0 -2012-10-28 10:00:00,8986.0 -2012-10-28 11:00:00,9201.0 -2012-10-28 12:00:00,9299.0 -2012-10-28 13:00:00,9366.0 -2012-10-28 14:00:00,9345.0 -2012-10-28 15:00:00,9319.0 -2012-10-28 16:00:00,9239.0 -2012-10-28 17:00:00,9220.0 -2012-10-28 18:00:00,9301.0 -2012-10-28 19:00:00,9715.0 -2012-10-28 20:00:00,10519.0 -2012-10-28 21:00:00,10636.0 -2012-10-28 22:00:00,10526.0 -2012-10-28 23:00:00,10193.0 -2012-10-29 00:00:00,9676.0 -2012-10-27 01:00:00,9576.0 -2012-10-27 02:00:00,9056.0 -2012-10-27 03:00:00,8752.0 -2012-10-27 04:00:00,8554.0 -2012-10-27 05:00:00,8471.0 -2012-10-27 06:00:00,8463.0 -2012-10-27 07:00:00,8796.0 -2012-10-27 08:00:00,9280.0 -2012-10-27 09:00:00,9666.0 -2012-10-27 10:00:00,9846.0 -2012-10-27 11:00:00,10028.0 -2012-10-27 12:00:00,10068.0 -2012-10-27 13:00:00,9997.0 -2012-10-27 14:00:00,9829.0 -2012-10-27 15:00:00,9664.0 -2012-10-27 16:00:00,9479.0 -2012-10-27 17:00:00,9413.0 -2012-10-27 18:00:00,9469.0 -2012-10-27 19:00:00,9814.0 -2012-10-27 20:00:00,10599.0 -2012-10-27 21:00:00,10625.0 -2012-10-27 22:00:00,10484.0 -2012-10-27 23:00:00,10196.0 -2012-10-28 00:00:00,9705.0 -2012-10-26 01:00:00,9283.0 -2012-10-26 02:00:00,8760.0 -2012-10-26 03:00:00,8406.0 -2012-10-26 04:00:00,8247.0 -2012-10-26 05:00:00,8183.0 -2012-10-26 06:00:00,8423.0 -2012-10-26 07:00:00,9150.0 -2012-10-26 08:00:00,10386.0 -2012-10-26 09:00:00,11115.0 -2012-10-26 10:00:00,11251.0 -2012-10-26 11:00:00,11374.0 -2012-10-26 12:00:00,11426.0 -2012-10-26 13:00:00,11373.0 -2012-10-26 14:00:00,11272.0 -2012-10-26 15:00:00,11209.0 -2012-10-26 16:00:00,11034.0 -2012-10-26 17:00:00,10867.0 -2012-10-26 18:00:00,10778.0 -2012-10-26 19:00:00,11022.0 -2012-10-26 20:00:00,11573.0 -2012-10-26 21:00:00,11499.0 -2012-10-26 22:00:00,11252.0 -2012-10-26 23:00:00,10894.0 -2012-10-27 00:00:00,10224.0 -2012-10-25 01:00:00,9989.0 -2012-10-25 02:00:00,9437.0 -2012-10-25 03:00:00,8881.0 -2012-10-25 04:00:00,8645.0 -2012-10-25 05:00:00,8515.0 -2012-10-25 06:00:00,8667.0 -2012-10-25 07:00:00,9334.0 -2012-10-25 08:00:00,10624.0 -2012-10-25 09:00:00,11290.0 -2012-10-25 10:00:00,11567.0 -2012-10-25 11:00:00,11860.0 -2012-10-25 12:00:00,12190.0 -2012-10-25 13:00:00,12315.0 -2012-10-25 14:00:00,12425.0 -2012-10-25 15:00:00,12527.0 -2012-10-25 16:00:00,12468.0 -2012-10-25 17:00:00,12397.0 -2012-10-25 18:00:00,12372.0 -2012-10-25 19:00:00,12365.0 -2012-10-25 20:00:00,12328.0 -2012-10-25 21:00:00,11938.0 -2012-10-25 22:00:00,11503.0 -2012-10-25 23:00:00,10935.0 -2012-10-26 00:00:00,10059.0 -2012-10-24 01:00:00,9549.0 -2012-10-24 02:00:00,8999.0 -2012-10-24 03:00:00,8582.0 -2012-10-24 04:00:00,8367.0 -2012-10-24 05:00:00,8286.0 -2012-10-24 06:00:00,8487.0 -2012-10-24 07:00:00,9120.0 -2012-10-24 08:00:00,10382.0 -2012-10-24 09:00:00,11094.0 -2012-10-24 10:00:00,11311.0 -2012-10-24 11:00:00,11562.0 -2012-10-24 12:00:00,11862.0 -2012-10-24 13:00:00,12032.0 -2012-10-24 14:00:00,12181.0 -2012-10-24 15:00:00,12405.0 -2012-10-24 16:00:00,12421.0 -2012-10-24 17:00:00,12409.0 -2012-10-24 18:00:00,12350.0 -2012-10-24 19:00:00,12377.0 -2012-10-24 20:00:00,12831.0 -2012-10-24 21:00:00,12778.0 -2012-10-24 22:00:00,12412.0 -2012-10-24 23:00:00,11794.0 -2012-10-25 00:00:00,10849.0 -2012-10-23 01:00:00,9507.0 -2012-10-23 02:00:00,8902.0 -2012-10-23 03:00:00,8502.0 -2012-10-23 04:00:00,8287.0 -2012-10-23 05:00:00,8224.0 -2012-10-23 06:00:00,8355.0 -2012-10-23 07:00:00,9028.0 -2012-10-23 08:00:00,10314.0 -2012-10-23 09:00:00,11175.0 -2012-10-23 10:00:00,11474.0 -2012-10-23 11:00:00,11608.0 -2012-10-23 12:00:00,11771.0 -2012-10-23 13:00:00,11809.0 -2012-10-23 14:00:00,11880.0 -2012-10-23 15:00:00,11961.0 -2012-10-23 16:00:00,11894.0 -2012-10-23 17:00:00,11796.0 -2012-10-23 18:00:00,11696.0 -2012-10-23 19:00:00,11643.0 -2012-10-23 20:00:00,12149.0 -2012-10-23 21:00:00,12083.0 -2012-10-23 22:00:00,11765.0 -2012-10-23 23:00:00,11181.0 -2012-10-24 00:00:00,10384.0 -2012-10-22 01:00:00,8576.0 -2012-10-22 02:00:00,8167.0 -2012-10-22 03:00:00,7931.0 -2012-10-22 04:00:00,7786.0 -2012-10-22 05:00:00,7800.0 -2012-10-22 06:00:00,8089.0 -2012-10-22 07:00:00,8797.0 -2012-10-22 08:00:00,10082.0 -2012-10-22 09:00:00,10834.0 -2012-10-22 10:00:00,11143.0 -2012-10-22 11:00:00,11345.0 -2012-10-22 12:00:00,11635.0 -2012-10-22 13:00:00,11845.0 -2012-10-22 14:00:00,11843.0 -2012-10-22 15:00:00,11784.0 -2012-10-22 16:00:00,11643.0 -2012-10-22 17:00:00,11569.0 -2012-10-22 18:00:00,11611.0 -2012-10-22 19:00:00,11842.0 -2012-10-22 20:00:00,12164.0 -2012-10-22 21:00:00,12048.0 -2012-10-22 22:00:00,11682.0 -2012-10-22 23:00:00,11155.0 -2012-10-23 00:00:00,10368.0 -2012-10-21 01:00:00,8914.0 -2012-10-21 02:00:00,8458.0 -2012-10-21 03:00:00,8208.0 -2012-10-21 04:00:00,7976.0 -2012-10-21 05:00:00,7909.0 -2012-10-21 06:00:00,7919.0 -2012-10-21 07:00:00,8102.0 -2012-10-21 08:00:00,8343.0 -2012-10-21 09:00:00,8420.0 -2012-10-21 10:00:00,8607.0 -2012-10-21 11:00:00,8799.0 -2012-10-21 12:00:00,8896.0 -2012-10-21 13:00:00,8988.0 -2012-10-21 14:00:00,8994.0 -2012-10-21 15:00:00,9019.0 -2012-10-21 16:00:00,8990.0 -2012-10-21 17:00:00,8998.0 -2012-10-21 18:00:00,9059.0 -2012-10-21 19:00:00,9280.0 -2012-10-21 20:00:00,10091.0 -2012-10-21 21:00:00,10311.0 -2012-10-21 22:00:00,10108.0 -2012-10-21 23:00:00,9746.0 -2012-10-22 00:00:00,9149.0 -2012-10-20 01:00:00,9426.0 -2012-10-20 02:00:00,8895.0 -2012-10-20 03:00:00,8599.0 -2012-10-20 04:00:00,8384.0 -2012-10-20 05:00:00,8285.0 -2012-10-20 06:00:00,8286.0 -2012-10-20 07:00:00,8628.0 -2012-10-20 08:00:00,9140.0 -2012-10-20 09:00:00,9415.0 -2012-10-20 10:00:00,9694.0 -2012-10-20 11:00:00,9821.0 -2012-10-20 12:00:00,9879.0 -2012-10-20 13:00:00,9806.0 -2012-10-20 14:00:00,9713.0 -2012-10-20 15:00:00,9519.0 -2012-10-20 16:00:00,9401.0 -2012-10-20 17:00:00,9283.0 -2012-10-20 18:00:00,9274.0 -2012-10-20 19:00:00,9400.0 -2012-10-20 20:00:00,10107.0 -2012-10-20 21:00:00,10266.0 -2012-10-20 22:00:00,10122.0 -2012-10-20 23:00:00,9885.0 -2012-10-21 00:00:00,9380.0 -2012-10-19 01:00:00,9530.0 -2012-10-19 02:00:00,8985.0 -2012-10-19 03:00:00,8646.0 -2012-10-19 04:00:00,8465.0 -2012-10-19 05:00:00,8403.0 -2012-10-19 06:00:00,8592.0 -2012-10-19 07:00:00,9252.0 -2012-10-19 08:00:00,10413.0 -2012-10-19 09:00:00,11165.0 -2012-10-19 10:00:00,11316.0 -2012-10-19 11:00:00,11384.0 -2012-10-19 12:00:00,11444.0 -2012-10-19 13:00:00,11401.0 -2012-10-19 14:00:00,11321.0 -2012-10-19 15:00:00,11277.0 -2012-10-19 16:00:00,11118.0 -2012-10-19 17:00:00,10997.0 -2012-10-19 18:00:00,10922.0 -2012-10-19 19:00:00,11058.0 -2012-10-19 20:00:00,11522.0 -2012-10-19 21:00:00,11443.0 -2012-10-19 22:00:00,11114.0 -2012-10-19 23:00:00,10751.0 -2012-10-20 00:00:00,10141.0 -2012-10-18 01:00:00,9271.0 -2012-10-18 02:00:00,8750.0 -2012-10-18 03:00:00,8416.0 -2012-10-18 04:00:00,8227.0 -2012-10-18 05:00:00,8161.0 -2012-10-18 06:00:00,8346.0 -2012-10-18 07:00:00,9050.0 -2012-10-18 08:00:00,10290.0 -2012-10-18 09:00:00,10968.0 -2012-10-18 10:00:00,11110.0 -2012-10-18 11:00:00,11200.0 -2012-10-18 12:00:00,11312.0 -2012-10-18 13:00:00,11397.0 -2012-10-18 14:00:00,11380.0 -2012-10-18 15:00:00,11407.0 -2012-10-18 16:00:00,11338.0 -2012-10-18 17:00:00,11279.0 -2012-10-18 18:00:00,11313.0 -2012-10-18 19:00:00,11509.0 -2012-10-18 20:00:00,11902.0 -2012-10-18 21:00:00,11875.0 -2012-10-18 22:00:00,11608.0 -2012-10-18 23:00:00,11116.0 -2012-10-19 00:00:00,10306.0 -2012-10-17 01:00:00,9445.0 -2012-10-17 02:00:00,8910.0 -2012-10-17 03:00:00,8575.0 -2012-10-17 04:00:00,8349.0 -2012-10-17 05:00:00,8252.0 -2012-10-17 06:00:00,8424.0 -2012-10-17 07:00:00,9063.0 -2012-10-17 08:00:00,10288.0 -2012-10-17 09:00:00,10819.0 -2012-10-17 10:00:00,11072.0 -2012-10-17 11:00:00,11284.0 -2012-10-17 12:00:00,11533.0 -2012-10-17 13:00:00,11592.0 -2012-10-17 14:00:00,11631.0 -2012-10-17 15:00:00,11669.0 -2012-10-17 16:00:00,11605.0 -2012-10-17 17:00:00,11595.0 -2012-10-17 18:00:00,11813.0 -2012-10-17 19:00:00,11887.0 -2012-10-17 20:00:00,12090.0 -2012-10-17 21:00:00,12005.0 -2012-10-17 22:00:00,11648.0 -2012-10-17 23:00:00,11041.0 -2012-10-18 00:00:00,10081.0 -2012-10-16 01:00:00,9420.0 -2012-10-16 02:00:00,8870.0 -2012-10-16 03:00:00,8564.0 -2012-10-16 04:00:00,8429.0 -2012-10-16 05:00:00,8413.0 -2012-10-16 06:00:00,8646.0 -2012-10-16 07:00:00,9344.0 -2012-10-16 08:00:00,10565.0 -2012-10-16 09:00:00,11131.0 -2012-10-16 10:00:00,11242.0 -2012-10-16 11:00:00,11290.0 -2012-10-16 12:00:00,11373.0 -2012-10-16 13:00:00,11422.0 -2012-10-16 14:00:00,11430.0 -2012-10-16 15:00:00,11531.0 -2012-10-16 16:00:00,11481.0 -2012-10-16 17:00:00,11367.0 -2012-10-16 18:00:00,11277.0 -2012-10-16 19:00:00,11277.0 -2012-10-16 20:00:00,11820.0 -2012-10-16 21:00:00,11945.0 -2012-10-16 22:00:00,11599.0 -2012-10-16 23:00:00,11080.0 -2012-10-17 00:00:00,10239.0 -2012-10-15 01:00:00,8624.0 -2012-10-15 02:00:00,8237.0 -2012-10-15 03:00:00,8030.0 -2012-10-15 04:00:00,7953.0 -2012-10-15 05:00:00,7962.0 -2012-10-15 06:00:00,8198.0 -2012-10-15 07:00:00,8975.0 -2012-10-15 08:00:00,10252.0 -2012-10-15 09:00:00,10889.0 -2012-10-15 10:00:00,11145.0 -2012-10-15 11:00:00,11185.0 -2012-10-15 12:00:00,11275.0 -2012-10-15 13:00:00,11244.0 -2012-10-15 14:00:00,11193.0 -2012-10-15 15:00:00,11225.0 -2012-10-15 16:00:00,11133.0 -2012-10-15 17:00:00,10996.0 -2012-10-15 18:00:00,10896.0 -2012-10-15 19:00:00,10927.0 -2012-10-15 20:00:00,11522.0 -2012-10-15 21:00:00,11678.0 -2012-10-15 22:00:00,11448.0 -2012-10-15 23:00:00,10959.0 -2012-10-16 00:00:00,10153.0 -2012-10-14 01:00:00,8972.0 -2012-10-14 02:00:00,8543.0 -2012-10-14 03:00:00,8160.0 -2012-10-14 04:00:00,8017.0 -2012-10-14 05:00:00,7911.0 -2012-10-14 06:00:00,7847.0 -2012-10-14 07:00:00,7969.0 -2012-10-14 08:00:00,8171.0 -2012-10-14 09:00:00,8341.0 -2012-10-14 10:00:00,8604.0 -2012-10-14 11:00:00,9037.0 -2012-10-14 12:00:00,9299.0 -2012-10-14 13:00:00,9549.0 -2012-10-14 14:00:00,9507.0 -2012-10-14 15:00:00,9508.0 -2012-10-14 16:00:00,9544.0 -2012-10-14 17:00:00,9567.0 -2012-10-14 18:00:00,9641.0 -2012-10-14 19:00:00,9882.0 -2012-10-14 20:00:00,10262.0 -2012-10-14 21:00:00,10339.0 -2012-10-14 22:00:00,10119.0 -2012-10-14 23:00:00,9776.0 -2012-10-15 00:00:00,9191.0 -2012-10-13 01:00:00,9508.0 -2012-10-13 02:00:00,8946.0 -2012-10-13 03:00:00,8593.0 -2012-10-13 04:00:00,8369.0 -2012-10-13 05:00:00,8271.0 -2012-10-13 06:00:00,8291.0 -2012-10-13 07:00:00,8538.0 -2012-10-13 08:00:00,8956.0 -2012-10-13 09:00:00,9256.0 -2012-10-13 10:00:00,9564.0 -2012-10-13 11:00:00,9894.0 -2012-10-13 12:00:00,10158.0 -2012-10-13 13:00:00,10321.0 -2012-10-13 14:00:00,10229.0 -2012-10-13 15:00:00,10057.0 -2012-10-13 16:00:00,9918.0 -2012-10-13 17:00:00,9956.0 -2012-10-13 18:00:00,10023.0 -2012-10-13 19:00:00,10221.0 -2012-10-13 20:00:00,10545.0 -2012-10-13 21:00:00,10534.0 -2012-10-13 22:00:00,10375.0 -2012-10-13 23:00:00,10074.0 -2012-10-14 00:00:00,9558.0 -2012-10-12 01:00:00,9413.0 -2012-10-12 02:00:00,8906.0 -2012-10-12 03:00:00,8554.0 -2012-10-12 04:00:00,8405.0 -2012-10-12 05:00:00,8397.0 -2012-10-12 06:00:00,8615.0 -2012-10-12 07:00:00,9360.0 -2012-10-12 08:00:00,10567.0 -2012-10-12 09:00:00,11098.0 -2012-10-12 10:00:00,11249.0 -2012-10-12 11:00:00,11274.0 -2012-10-12 12:00:00,11366.0 -2012-10-12 13:00:00,11283.0 -2012-10-12 14:00:00,11208.0 -2012-10-12 15:00:00,11166.0 -2012-10-12 16:00:00,11012.0 -2012-10-12 17:00:00,10854.0 -2012-10-12 18:00:00,10734.0 -2012-10-12 19:00:00,10694.0 -2012-10-12 20:00:00,11211.0 -2012-10-12 21:00:00,11401.0 -2012-10-12 22:00:00,11180.0 -2012-10-12 23:00:00,10847.0 -2012-10-13 00:00:00,10176.0 -2012-10-11 01:00:00,9656.0 -2012-10-11 02:00:00,9183.0 -2012-10-11 03:00:00,8883.0 -2012-10-11 04:00:00,8746.0 -2012-10-11 05:00:00,8708.0 -2012-10-11 06:00:00,8888.0 -2012-10-11 07:00:00,9626.0 -2012-10-11 08:00:00,10821.0 -2012-10-11 09:00:00,11337.0 -2012-10-11 10:00:00,11435.0 -2012-10-11 11:00:00,11462.0 -2012-10-11 12:00:00,11531.0 -2012-10-11 13:00:00,11524.0 -2012-10-11 14:00:00,11505.0 -2012-10-11 15:00:00,11522.0 -2012-10-11 16:00:00,11438.0 -2012-10-11 17:00:00,11312.0 -2012-10-11 18:00:00,11213.0 -2012-10-11 19:00:00,11207.0 -2012-10-11 20:00:00,11680.0 -2012-10-11 21:00:00,11790.0 -2012-10-11 22:00:00,11521.0 -2012-10-11 23:00:00,11013.0 -2012-10-12 00:00:00,10221.0 -2012-10-10 01:00:00,9445.0 -2012-10-10 02:00:00,8920.0 -2012-10-10 03:00:00,8598.0 -2012-10-10 04:00:00,8479.0 -2012-10-10 05:00:00,8455.0 -2012-10-10 06:00:00,8675.0 -2012-10-10 07:00:00,9413.0 -2012-10-10 08:00:00,10750.0 -2012-10-10 09:00:00,11293.0 -2012-10-10 10:00:00,11426.0 -2012-10-10 11:00:00,11486.0 -2012-10-10 12:00:00,11556.0 -2012-10-10 13:00:00,11493.0 -2012-10-10 14:00:00,11448.0 -2012-10-10 15:00:00,11405.0 -2012-10-10 16:00:00,11323.0 -2012-10-10 17:00:00,11182.0 -2012-10-10 18:00:00,11088.0 -2012-10-10 19:00:00,11084.0 -2012-10-10 20:00:00,11594.0 -2012-10-10 21:00:00,11883.0 -2012-10-10 22:00:00,11683.0 -2012-10-10 23:00:00,11219.0 -2012-10-11 00:00:00,10400.0 -2012-10-09 01:00:00,9368.0 -2012-10-09 02:00:00,8886.0 -2012-10-09 03:00:00,8571.0 -2012-10-09 04:00:00,8394.0 -2012-10-09 05:00:00,8380.0 -2012-10-09 06:00:00,8554.0 -2012-10-09 07:00:00,9283.0 -2012-10-09 08:00:00,10540.0 -2012-10-09 09:00:00,11022.0 -2012-10-09 10:00:00,11219.0 -2012-10-09 11:00:00,11339.0 -2012-10-09 12:00:00,11435.0 -2012-10-09 13:00:00,11436.0 -2012-10-09 14:00:00,11502.0 -2012-10-09 15:00:00,11496.0 -2012-10-09 16:00:00,11441.0 -2012-10-09 17:00:00,11312.0 -2012-10-09 18:00:00,11202.0 -2012-10-09 19:00:00,11234.0 -2012-10-09 20:00:00,11749.0 -2012-10-09 21:00:00,11910.0 -2012-10-09 22:00:00,11571.0 -2012-10-09 23:00:00,11018.0 -2012-10-10 00:00:00,10213.0 -2012-10-08 01:00:00,8961.0 -2012-10-08 02:00:00,8595.0 -2012-10-08 03:00:00,8423.0 -2012-10-08 04:00:00,8341.0 -2012-10-08 05:00:00,8378.0 -2012-10-08 06:00:00,8641.0 -2012-10-08 07:00:00,9401.0 -2012-10-08 08:00:00,10380.0 -2012-10-08 09:00:00,10924.0 -2012-10-08 10:00:00,11174.0 -2012-10-08 11:00:00,11326.0 -2012-10-08 12:00:00,11407.0 -2012-10-08 13:00:00,11367.0 -2012-10-08 14:00:00,11334.0 -2012-10-08 15:00:00,11299.0 -2012-10-08 16:00:00,11184.0 -2012-10-08 17:00:00,11032.0 -2012-10-08 18:00:00,10932.0 -2012-10-08 19:00:00,10956.0 -2012-10-08 20:00:00,11449.0 -2012-10-08 21:00:00,11753.0 -2012-10-08 22:00:00,11489.0 -2012-10-08 23:00:00,11014.0 -2012-10-09 00:00:00,10157.0 -2012-10-07 01:00:00,8960.0 -2012-10-07 02:00:00,8479.0 -2012-10-07 03:00:00,8189.0 -2012-10-07 04:00:00,8010.0 -2012-10-07 05:00:00,7902.0 -2012-10-07 06:00:00,7941.0 -2012-10-07 07:00:00,8061.0 -2012-10-07 08:00:00,8351.0 -2012-10-07 09:00:00,8410.0 -2012-10-07 10:00:00,8716.0 -2012-10-07 11:00:00,8943.0 -2012-10-07 12:00:00,9064.0 -2012-10-07 13:00:00,9090.0 -2012-10-07 14:00:00,9121.0 -2012-10-07 15:00:00,9048.0 -2012-10-07 16:00:00,9010.0 -2012-10-07 17:00:00,8966.0 -2012-10-07 18:00:00,8952.0 -2012-10-07 19:00:00,9127.0 -2012-10-07 20:00:00,9744.0 -2012-10-07 21:00:00,10279.0 -2012-10-07 22:00:00,10151.0 -2012-10-07 23:00:00,9924.0 -2012-10-08 00:00:00,9433.0 -2012-10-06 01:00:00,9488.0 -2012-10-06 02:00:00,8969.0 -2012-10-06 03:00:00,8600.0 -2012-10-06 04:00:00,8435.0 -2012-10-06 05:00:00,8260.0 -2012-10-06 06:00:00,8406.0 -2012-10-06 07:00:00,8627.0 -2012-10-06 08:00:00,9124.0 -2012-10-06 09:00:00,9310.0 -2012-10-06 10:00:00,9634.0 -2012-10-06 11:00:00,9866.0 -2012-10-06 12:00:00,9905.0 -2012-10-06 13:00:00,9880.0 -2012-10-06 14:00:00,9712.0 -2012-10-06 15:00:00,9597.0 -2012-10-06 16:00:00,9492.0 -2012-10-06 17:00:00,9476.0 -2012-10-06 18:00:00,9570.0 -2012-10-06 19:00:00,9736.0 -2012-10-06 20:00:00,10252.0 -2012-10-06 21:00:00,10449.0 -2012-10-06 22:00:00,10267.0 -2012-10-06 23:00:00,9961.0 -2012-10-07 00:00:00,9516.0 -2012-10-05 01:00:00,9277.0 -2012-10-05 02:00:00,8726.0 -2012-10-05 03:00:00,8376.0 -2012-10-05 04:00:00,8165.0 -2012-10-05 05:00:00,8082.0 -2012-10-05 06:00:00,8252.0 -2012-10-05 07:00:00,8877.0 -2012-10-05 08:00:00,9991.0 -2012-10-05 09:00:00,10582.0 -2012-10-05 10:00:00,10829.0 -2012-10-05 11:00:00,11019.0 -2012-10-05 12:00:00,11193.0 -2012-10-05 13:00:00,11235.0 -2012-10-05 14:00:00,11235.0 -2012-10-05 15:00:00,11259.0 -2012-10-05 16:00:00,11177.0 -2012-10-05 17:00:00,11053.0 -2012-10-05 18:00:00,10992.0 -2012-10-05 19:00:00,11025.0 -2012-10-05 20:00:00,11383.0 -2012-10-05 21:00:00,11385.0 -2012-10-05 22:00:00,11195.0 -2012-10-05 23:00:00,10789.0 -2012-10-06 00:00:00,10117.0 -2012-10-04 01:00:00,9520.0 -2012-10-04 02:00:00,8934.0 -2012-10-04 03:00:00,8572.0 -2012-10-04 04:00:00,8346.0 -2012-10-04 05:00:00,8264.0 -2012-10-04 06:00:00,8443.0 -2012-10-04 07:00:00,9034.0 -2012-10-04 08:00:00,10210.0 -2012-10-04 09:00:00,10790.0 -2012-10-04 10:00:00,11193.0 -2012-10-04 11:00:00,11538.0 -2012-10-04 12:00:00,11954.0 -2012-10-04 13:00:00,12191.0 -2012-10-04 14:00:00,12363.0 -2012-10-04 15:00:00,12545.0 -2012-10-04 16:00:00,12432.0 -2012-10-04 17:00:00,12161.0 -2012-10-04 18:00:00,11891.0 -2012-10-04 19:00:00,11794.0 -2012-10-04 20:00:00,11799.0 -2012-10-04 21:00:00,11784.0 -2012-10-04 22:00:00,11420.0 -2012-10-04 23:00:00,10890.0 -2012-10-05 00:00:00,10058.0 -2012-10-03 01:00:00,9366.0 -2012-10-03 02:00:00,8860.0 -2012-10-03 03:00:00,8536.0 -2012-10-03 04:00:00,8355.0 -2012-10-03 05:00:00,8277.0 -2012-10-03 06:00:00,8492.0 -2012-10-03 07:00:00,9151.0 -2012-10-03 08:00:00,10401.0 -2012-10-03 09:00:00,11202.0 -2012-10-03 10:00:00,11543.0 -2012-10-03 11:00:00,11743.0 -2012-10-03 12:00:00,11931.0 -2012-10-03 13:00:00,11971.0 -2012-10-03 14:00:00,11956.0 -2012-10-03 15:00:00,11972.0 -2012-10-03 16:00:00,11886.0 -2012-10-03 17:00:00,11821.0 -2012-10-03 18:00:00,11750.0 -2012-10-03 19:00:00,11688.0 -2012-10-03 20:00:00,11958.0 -2012-10-03 21:00:00,12132.0 -2012-10-03 22:00:00,11824.0 -2012-10-03 23:00:00,11230.0 -2012-10-04 00:00:00,10366.0 -2012-10-02 01:00:00,9190.0 -2012-10-02 02:00:00,8670.0 -2012-10-02 03:00:00,8332.0 -2012-10-02 04:00:00,8165.0 -2012-10-02 05:00:00,8086.0 -2012-10-02 06:00:00,8279.0 -2012-10-02 07:00:00,8940.0 -2012-10-02 08:00:00,10091.0 -2012-10-02 09:00:00,10646.0 -2012-10-02 10:00:00,11047.0 -2012-10-02 11:00:00,11362.0 -2012-10-02 12:00:00,11623.0 -2012-10-02 13:00:00,11769.0 -2012-10-02 14:00:00,11806.0 -2012-10-02 15:00:00,11909.0 -2012-10-02 16:00:00,11833.0 -2012-10-02 17:00:00,11734.0 -2012-10-02 18:00:00,11568.0 -2012-10-02 19:00:00,11345.0 -2012-10-02 20:00:00,11464.0 -2012-10-02 21:00:00,11838.0 -2012-10-02 22:00:00,11617.0 -2012-10-02 23:00:00,11054.0 -2012-10-03 00:00:00,10195.0 -2012-10-01 01:00:00,8452.0 -2012-10-01 02:00:00,8086.0 -2012-10-01 03:00:00,7870.0 -2012-10-01 04:00:00,7774.0 -2012-10-01 05:00:00,7773.0 -2012-10-01 06:00:00,8012.0 -2012-10-01 07:00:00,8722.0 -2012-10-01 08:00:00,9910.0 -2012-10-01 09:00:00,10512.0 -2012-10-01 10:00:00,10858.0 -2012-10-01 11:00:00,11055.0 -2012-10-01 12:00:00,11302.0 -2012-10-01 13:00:00,11426.0 -2012-10-01 14:00:00,11522.0 -2012-10-01 15:00:00,11574.0 -2012-10-01 16:00:00,11489.0 -2012-10-01 17:00:00,11309.0 -2012-10-01 18:00:00,11174.0 -2012-10-01 19:00:00,11082.0 -2012-10-01 20:00:00,11396.0 -2012-10-01 21:00:00,11710.0 -2012-10-01 22:00:00,11407.0 -2012-10-01 23:00:00,10843.0 -2012-10-02 00:00:00,9995.0 -2012-09-30 01:00:00,8746.0 -2012-09-30 02:00:00,8249.0 -2012-09-30 03:00:00,7882.0 -2012-09-30 04:00:00,7689.0 -2012-09-30 05:00:00,7555.0 -2012-09-30 06:00:00,7558.0 -2012-09-30 07:00:00,7636.0 -2012-09-30 08:00:00,7804.0 -2012-09-30 09:00:00,7844.0 -2012-09-30 10:00:00,8261.0 -2012-09-30 11:00:00,8636.0 -2012-09-30 12:00:00,8893.0 -2012-09-30 13:00:00,9064.0 -2012-09-30 14:00:00,9134.0 -2012-09-30 15:00:00,9182.0 -2012-09-30 16:00:00,9196.0 -2012-09-30 17:00:00,9194.0 -2012-09-30 18:00:00,9252.0 -2012-09-30 19:00:00,9273.0 -2012-09-30 20:00:00,9670.0 -2012-09-30 21:00:00,10132.0 -2012-09-30 22:00:00,10003.0 -2012-09-30 23:00:00,9598.0 -2012-10-01 00:00:00,9042.0 -2012-09-29 01:00:00,9115.0 -2012-09-29 02:00:00,8581.0 -2012-09-29 03:00:00,8187.0 -2012-09-29 04:00:00,7989.0 -2012-09-29 05:00:00,7850.0 -2012-09-29 06:00:00,7904.0 -2012-09-29 07:00:00,8121.0 -2012-09-29 08:00:00,8513.0 -2012-09-29 09:00:00,8670.0 -2012-09-29 10:00:00,9166.0 -2012-09-29 11:00:00,9521.0 -2012-09-29 12:00:00,9830.0 -2012-09-29 13:00:00,9941.0 -2012-09-29 14:00:00,10010.0 -2012-09-29 15:00:00,9951.0 -2012-09-29 16:00:00,10020.0 -2012-09-29 17:00:00,9955.0 -2012-09-29 18:00:00,9984.0 -2012-09-29 19:00:00,9844.0 -2012-09-29 20:00:00,9995.0 -2012-09-29 21:00:00,10364.0 -2012-09-29 22:00:00,10252.0 -2012-09-29 23:00:00,9868.0 -2012-09-30 00:00:00,9356.0 -2012-09-28 01:00:00,9272.0 -2012-09-28 02:00:00,8732.0 -2012-09-28 03:00:00,8418.0 -2012-09-28 04:00:00,8172.0 -2012-09-28 05:00:00,8095.0 -2012-09-28 06:00:00,8272.0 -2012-09-28 07:00:00,8923.0 -2012-09-28 08:00:00,9971.0 -2012-09-28 09:00:00,10516.0 -2012-09-28 10:00:00,10867.0 -2012-09-28 11:00:00,11140.0 -2012-09-28 12:00:00,11395.0 -2012-09-28 13:00:00,11473.0 -2012-09-28 14:00:00,11474.0 -2012-09-28 15:00:00,11537.0 -2012-09-28 16:00:00,11453.0 -2012-09-28 17:00:00,11325.0 -2012-09-28 18:00:00,11176.0 -2012-09-28 19:00:00,10993.0 -2012-09-28 20:00:00,11023.0 -2012-09-28 21:00:00,11307.0 -2012-09-28 22:00:00,11075.0 -2012-09-28 23:00:00,10574.0 -2012-09-29 00:00:00,9871.0 -2012-09-27 01:00:00,9302.0 -2012-09-27 02:00:00,8745.0 -2012-09-27 03:00:00,8406.0 -2012-09-27 04:00:00,8215.0 -2012-09-27 05:00:00,8134.0 -2012-09-27 06:00:00,8300.0 -2012-09-27 07:00:00,8893.0 -2012-09-27 08:00:00,10003.0 -2012-09-27 09:00:00,10551.0 -2012-09-27 10:00:00,10856.0 -2012-09-27 11:00:00,11160.0 -2012-09-27 12:00:00,11438.0 -2012-09-27 13:00:00,11535.0 -2012-09-27 14:00:00,11597.0 -2012-09-27 15:00:00,11601.0 -2012-09-27 16:00:00,11551.0 -2012-09-27 17:00:00,11407.0 -2012-09-27 18:00:00,11303.0 -2012-09-27 19:00:00,11090.0 -2012-09-27 20:00:00,11187.0 -2012-09-27 21:00:00,11702.0 -2012-09-27 22:00:00,11459.0 -2012-09-27 23:00:00,10932.0 -2012-09-28 00:00:00,10069.0 -2012-09-26 01:00:00,9728.0 -2012-09-26 02:00:00,9143.0 -2012-09-26 03:00:00,8728.0 -2012-09-26 04:00:00,8505.0 -2012-09-26 05:00:00,8396.0 -2012-09-26 06:00:00,8533.0 -2012-09-26 07:00:00,9120.0 -2012-09-26 08:00:00,10268.0 -2012-09-26 09:00:00,10808.0 -2012-09-26 10:00:00,11251.0 -2012-09-26 11:00:00,11527.0 -2012-09-26 12:00:00,11752.0 -2012-09-26 13:00:00,11868.0 -2012-09-26 14:00:00,11948.0 -2012-09-26 15:00:00,12056.0 -2012-09-26 16:00:00,11997.0 -2012-09-26 17:00:00,11857.0 -2012-09-26 18:00:00,11699.0 -2012-09-26 19:00:00,11459.0 -2012-09-26 20:00:00,11411.0 -2012-09-26 21:00:00,11805.0 -2012-09-26 22:00:00,11541.0 -2012-09-26 23:00:00,10986.0 -2012-09-27 00:00:00,10107.0 -2012-09-25 01:00:00,9253.0 -2012-09-25 02:00:00,8660.0 -2012-09-25 03:00:00,8295.0 -2012-09-25 04:00:00,8140.0 -2012-09-25 05:00:00,8063.0 -2012-09-25 06:00:00,8246.0 -2012-09-25 07:00:00,8914.0 -2012-09-25 08:00:00,10014.0 -2012-09-25 09:00:00,10619.0 -2012-09-25 10:00:00,11038.0 -2012-09-25 11:00:00,11262.0 -2012-09-25 12:00:00,11518.0 -2012-09-25 13:00:00,11716.0 -2012-09-25 14:00:00,11921.0 -2012-09-25 15:00:00,12149.0 -2012-09-25 16:00:00,12159.0 -2012-09-25 17:00:00,12156.0 -2012-09-25 18:00:00,12108.0 -2012-09-25 19:00:00,11969.0 -2012-09-25 20:00:00,11937.0 -2012-09-25 21:00:00,12390.0 -2012-09-25 22:00:00,12108.0 -2012-09-25 23:00:00,11519.0 -2012-09-26 00:00:00,10594.0 -2012-09-24 01:00:00,8435.0 -2012-09-24 02:00:00,8062.0 -2012-09-24 03:00:00,7867.0 -2012-09-24 04:00:00,7775.0 -2012-09-24 05:00:00,7806.0 -2012-09-24 06:00:00,8023.0 -2012-09-24 07:00:00,8804.0 -2012-09-24 08:00:00,10004.0 -2012-09-24 09:00:00,10597.0 -2012-09-24 10:00:00,10918.0 -2012-09-24 11:00:00,11067.0 -2012-09-24 12:00:00,11353.0 -2012-09-24 13:00:00,11378.0 -2012-09-24 14:00:00,11366.0 -2012-09-24 15:00:00,11465.0 -2012-09-24 16:00:00,11446.0 -2012-09-24 17:00:00,11401.0 -2012-09-24 18:00:00,11298.0 -2012-09-24 19:00:00,11205.0 -2012-09-24 20:00:00,11176.0 -2012-09-24 21:00:00,11745.0 -2012-09-24 22:00:00,11505.0 -2012-09-24 23:00:00,10945.0 -2012-09-25 00:00:00,10075.0 -2012-09-23 01:00:00,8680.0 -2012-09-23 02:00:00,8211.0 -2012-09-23 03:00:00,7906.0 -2012-09-23 04:00:00,7724.0 -2012-09-23 05:00:00,7604.0 -2012-09-23 06:00:00,7599.0 -2012-09-23 07:00:00,7669.0 -2012-09-23 08:00:00,7837.0 -2012-09-23 09:00:00,7910.0 -2012-09-23 10:00:00,8307.0 -2012-09-23 11:00:00,8558.0 -2012-09-23 12:00:00,8785.0 -2012-09-23 13:00:00,8918.0 -2012-09-23 14:00:00,8982.0 -2012-09-23 15:00:00,8981.0 -2012-09-23 16:00:00,8921.0 -2012-09-23 17:00:00,8948.0 -2012-09-23 18:00:00,8940.0 -2012-09-23 19:00:00,9080.0 -2012-09-23 20:00:00,9263.0 -2012-09-23 21:00:00,10002.0 -2012-09-23 22:00:00,9910.0 -2012-09-23 23:00:00,9584.0 -2012-09-24 00:00:00,8987.0 -2012-09-22 01:00:00,9164.0 -2012-09-22 02:00:00,8645.0 -2012-09-22 03:00:00,8258.0 -2012-09-22 04:00:00,8023.0 -2012-09-22 05:00:00,7894.0 -2012-09-22 06:00:00,7941.0 -2012-09-22 07:00:00,8149.0 -2012-09-22 08:00:00,8522.0 -2012-09-22 09:00:00,8762.0 -2012-09-22 10:00:00,9258.0 -2012-09-22 11:00:00,9570.0 -2012-09-22 12:00:00,9727.0 -2012-09-22 13:00:00,9775.0 -2012-09-22 14:00:00,9667.0 -2012-09-22 15:00:00,9501.0 -2012-09-22 16:00:00,9388.0 -2012-09-22 17:00:00,9354.0 -2012-09-22 18:00:00,9334.0 -2012-09-22 19:00:00,9346.0 -2012-09-22 20:00:00,9481.0 -2012-09-22 21:00:00,10089.0 -2012-09-22 22:00:00,10033.0 -2012-09-22 23:00:00,9755.0 -2012-09-23 00:00:00,9230.0 -2012-09-21 01:00:00,9239.0 -2012-09-21 02:00:00,8715.0 -2012-09-21 03:00:00,8409.0 -2012-09-21 04:00:00,8166.0 -2012-09-21 05:00:00,8145.0 -2012-09-21 06:00:00,8288.0 -2012-09-21 07:00:00,8874.0 -2012-09-21 08:00:00,9897.0 -2012-09-21 09:00:00,10495.0 -2012-09-21 10:00:00,10867.0 -2012-09-21 11:00:00,11214.0 -2012-09-21 12:00:00,11314.0 -2012-09-21 13:00:00,11375.0 -2012-09-21 14:00:00,11368.0 -2012-09-21 15:00:00,11369.0 -2012-09-21 16:00:00,11215.0 -2012-09-21 17:00:00,11086.0 -2012-09-21 18:00:00,11012.0 -2012-09-21 19:00:00,11025.0 -2012-09-21 20:00:00,11103.0 -2012-09-21 21:00:00,11212.0 -2012-09-21 22:00:00,11000.0 -2012-09-21 23:00:00,10557.0 -2012-09-22 00:00:00,9908.0 -2012-09-20 01:00:00,9368.0 -2012-09-20 02:00:00,8836.0 -2012-09-20 03:00:00,8488.0 -2012-09-20 04:00:00,8261.0 -2012-09-20 05:00:00,8156.0 -2012-09-20 06:00:00,8308.0 -2012-09-20 07:00:00,8953.0 -2012-09-20 08:00:00,10057.0 -2012-09-20 09:00:00,10592.0 -2012-09-20 10:00:00,11007.0 -2012-09-20 11:00:00,11418.0 -2012-09-20 12:00:00,11731.0 -2012-09-20 13:00:00,11910.0 -2012-09-20 14:00:00,11974.0 -2012-09-20 15:00:00,12030.0 -2012-09-20 16:00:00,11940.0 -2012-09-20 17:00:00,11861.0 -2012-09-20 18:00:00,11649.0 -2012-09-20 19:00:00,11405.0 -2012-09-20 20:00:00,11324.0 -2012-09-20 21:00:00,11805.0 -2012-09-20 22:00:00,11584.0 -2012-09-20 23:00:00,10965.0 -2012-09-21 00:00:00,10060.0 -2012-09-19 01:00:00,9164.0 -2012-09-19 02:00:00,8635.0 -2012-09-19 03:00:00,8294.0 -2012-09-19 04:00:00,8117.0 -2012-09-19 05:00:00,8072.0 -2012-09-19 06:00:00,8241.0 -2012-09-19 07:00:00,8880.0 -2012-09-19 08:00:00,9931.0 -2012-09-19 09:00:00,10522.0 -2012-09-19 10:00:00,10859.0 -2012-09-19 11:00:00,11108.0 -2012-09-19 12:00:00,11374.0 -2012-09-19 13:00:00,11480.0 -2012-09-19 14:00:00,11555.0 -2012-09-19 15:00:00,11712.0 -2012-09-19 16:00:00,11710.0 -2012-09-19 17:00:00,11676.0 -2012-09-19 18:00:00,11605.0 -2012-09-19 19:00:00,11449.0 -2012-09-19 20:00:00,11328.0 -2012-09-19 21:00:00,11879.0 -2012-09-19 22:00:00,11678.0 -2012-09-19 23:00:00,11099.0 -2012-09-20 00:00:00,10212.0 -2012-09-18 01:00:00,9718.0 -2012-09-18 02:00:00,9085.0 -2012-09-18 03:00:00,8690.0 -2012-09-18 04:00:00,8411.0 -2012-09-18 05:00:00,8273.0 -2012-09-18 06:00:00,8395.0 -2012-09-18 07:00:00,8986.0 -2012-09-18 08:00:00,10018.0 -2012-09-18 09:00:00,10533.0 -2012-09-18 10:00:00,10932.0 -2012-09-18 11:00:00,11196.0 -2012-09-18 12:00:00,11435.0 -2012-09-18 13:00:00,11513.0 -2012-09-18 14:00:00,11578.0 -2012-09-18 15:00:00,11619.0 -2012-09-18 16:00:00,11546.0 -2012-09-18 17:00:00,11422.0 -2012-09-18 18:00:00,11260.0 -2012-09-18 19:00:00,11058.0 -2012-09-18 20:00:00,10938.0 -2012-09-18 21:00:00,11489.0 -2012-09-18 22:00:00,11349.0 -2012-09-18 23:00:00,10814.0 -2012-09-19 00:00:00,9943.0 -2012-09-17 01:00:00,9092.0 -2012-09-17 02:00:00,8608.0 -2012-09-17 03:00:00,8300.0 -2012-09-17 04:00:00,8177.0 -2012-09-17 05:00:00,8115.0 -2012-09-17 06:00:00,8349.0 -2012-09-17 07:00:00,9034.0 -2012-09-17 08:00:00,10152.0 -2012-09-17 09:00:00,10856.0 -2012-09-17 10:00:00,11520.0 -2012-09-17 11:00:00,12031.0 -2012-09-17 12:00:00,12492.0 -2012-09-17 13:00:00,12745.0 -2012-09-17 14:00:00,12936.0 -2012-09-17 15:00:00,13240.0 -2012-09-17 16:00:00,13319.0 -2012-09-17 17:00:00,13361.0 -2012-09-17 18:00:00,13257.0 -2012-09-17 19:00:00,13000.0 -2012-09-17 20:00:00,12847.0 -2012-09-17 21:00:00,12826.0 -2012-09-17 22:00:00,12446.0 -2012-09-17 23:00:00,11664.0 -2012-09-18 00:00:00,10658.0 -2012-09-16 01:00:00,9252.0 -2012-09-16 02:00:00,8650.0 -2012-09-16 03:00:00,8301.0 -2012-09-16 04:00:00,8003.0 -2012-09-16 05:00:00,7842.0 -2012-09-16 06:00:00,7798.0 -2012-09-16 07:00:00,7838.0 -2012-09-16 08:00:00,7872.0 -2012-09-16 09:00:00,7976.0 -2012-09-16 10:00:00,8506.0 -2012-09-16 11:00:00,9066.0 -2012-09-16 12:00:00,9530.0 -2012-09-16 13:00:00,9911.0 -2012-09-16 14:00:00,10130.0 -2012-09-16 15:00:00,10334.0 -2012-09-16 16:00:00,10462.0 -2012-09-16 17:00:00,10595.0 -2012-09-16 18:00:00,10613.0 -2012-09-16 19:00:00,10591.0 -2012-09-16 20:00:00,10595.0 -2012-09-16 21:00:00,11081.0 -2012-09-16 22:00:00,10958.0 -2012-09-16 23:00:00,10495.0 -2012-09-17 00:00:00,9805.0 -2012-09-15 01:00:00,9633.0 -2012-09-15 02:00:00,9022.0 -2012-09-15 03:00:00,8613.0 -2012-09-15 04:00:00,8306.0 -2012-09-15 05:00:00,8146.0 -2012-09-15 06:00:00,8097.0 -2012-09-15 07:00:00,8302.0 -2012-09-15 08:00:00,8515.0 -2012-09-15 09:00:00,8846.0 -2012-09-15 10:00:00,9441.0 -2012-09-15 11:00:00,10013.0 -2012-09-15 12:00:00,10464.0 -2012-09-15 13:00:00,10716.0 -2012-09-15 14:00:00,10884.0 -2012-09-15 15:00:00,10950.0 -2012-09-15 16:00:00,11120.0 -2012-09-15 17:00:00,11235.0 -2012-09-15 18:00:00,11306.0 -2012-09-15 19:00:00,11152.0 -2012-09-15 20:00:00,10855.0 -2012-09-15 21:00:00,11173.0 -2012-09-15 22:00:00,11019.0 -2012-09-15 23:00:00,10599.0 -2012-09-16 00:00:00,9900.0 -2012-09-14 01:00:00,9634.0 -2012-09-14 02:00:00,9098.0 -2012-09-14 03:00:00,8657.0 -2012-09-14 04:00:00,8501.0 -2012-09-14 05:00:00,8374.0 -2012-09-14 06:00:00,8537.0 -2012-09-14 07:00:00,9089.0 -2012-09-14 08:00:00,10076.0 -2012-09-14 09:00:00,10601.0 -2012-09-14 10:00:00,11114.0 -2012-09-14 11:00:00,11510.0 -2012-09-14 12:00:00,11941.0 -2012-09-14 13:00:00,12090.0 -2012-09-14 14:00:00,12228.0 -2012-09-14 15:00:00,12409.0 -2012-09-14 16:00:00,12486.0 -2012-09-14 17:00:00,12498.0 -2012-09-14 18:00:00,12418.0 -2012-09-14 19:00:00,12201.0 -2012-09-14 20:00:00,11806.0 -2012-09-14 21:00:00,11983.0 -2012-09-14 22:00:00,11857.0 -2012-09-14 23:00:00,11271.0 -2012-09-15 00:00:00,10512.0 -2012-09-13 01:00:00,11515.0 -2012-09-13 02:00:00,10639.0 -2012-09-13 03:00:00,10075.0 -2012-09-13 04:00:00,9656.0 -2012-09-13 05:00:00,9460.0 -2012-09-13 06:00:00,9531.0 -2012-09-13 07:00:00,10160.0 -2012-09-13 08:00:00,11170.0 -2012-09-13 09:00:00,11775.0 -2012-09-13 10:00:00,12182.0 -2012-09-13 11:00:00,12599.0 -2012-09-13 12:00:00,12977.0 -2012-09-13 13:00:00,13112.0 -2012-09-13 14:00:00,13190.0 -2012-09-13 15:00:00,13054.0 -2012-09-13 16:00:00,12732.0 -2012-09-13 17:00:00,12407.0 -2012-09-13 18:00:00,12173.0 -2012-09-13 19:00:00,12000.0 -2012-09-13 20:00:00,12020.0 -2012-09-13 21:00:00,12165.0 -2012-09-13 22:00:00,11883.0 -2012-09-13 23:00:00,11404.0 -2012-09-14 00:00:00,10525.0 -2012-09-12 01:00:00,10907.0 -2012-09-12 02:00:00,10119.0 -2012-09-12 03:00:00,9586.0 -2012-09-12 04:00:00,9228.0 -2012-09-12 05:00:00,9078.0 -2012-09-12 06:00:00,9220.0 -2012-09-12 07:00:00,9849.0 -2012-09-12 08:00:00,10858.0 -2012-09-12 09:00:00,11575.0 -2012-09-12 10:00:00,12332.0 -2012-09-12 11:00:00,12987.0 -2012-09-12 12:00:00,13731.0 -2012-09-12 13:00:00,14405.0 -2012-09-12 14:00:00,14993.0 -2012-09-12 15:00:00,15624.0 -2012-09-12 16:00:00,16000.0 -2012-09-12 17:00:00,16346.0 -2012-09-12 18:00:00,16481.0 -2012-09-12 19:00:00,16190.0 -2012-09-12 20:00:00,15474.0 -2012-09-12 21:00:00,15422.0 -2012-09-12 22:00:00,14999.0 -2012-09-12 23:00:00,14072.0 -2012-09-13 00:00:00,12774.0 -2012-09-11 01:00:00,9991.0 -2012-09-11 02:00:00,9338.0 -2012-09-11 03:00:00,8928.0 -2012-09-11 04:00:00,8624.0 -2012-09-11 05:00:00,8536.0 -2012-09-11 06:00:00,8677.0 -2012-09-11 07:00:00,9364.0 -2012-09-11 08:00:00,10304.0 -2012-09-11 09:00:00,10966.0 -2012-09-11 10:00:00,11650.0 -2012-09-11 11:00:00,12164.0 -2012-09-11 12:00:00,12788.0 -2012-09-11 13:00:00,13147.0 -2012-09-11 14:00:00,13480.0 -2012-09-11 15:00:00,13900.0 -2012-09-11 16:00:00,14189.0 -2012-09-11 17:00:00,14447.0 -2012-09-11 18:00:00,14582.0 -2012-09-11 19:00:00,14393.0 -2012-09-11 20:00:00,13867.0 -2012-09-11 21:00:00,14006.0 -2012-09-11 22:00:00,13831.0 -2012-09-11 23:00:00,13080.0 -2012-09-12 00:00:00,11972.0 -2012-09-10 01:00:00,8963.0 -2012-09-10 02:00:00,8487.0 -2012-09-10 03:00:00,8200.0 -2012-09-10 04:00:00,8085.0 -2012-09-10 05:00:00,8038.0 -2012-09-10 06:00:00,8282.0 -2012-09-10 07:00:00,8928.0 -2012-09-10 08:00:00,9904.0 -2012-09-10 09:00:00,10620.0 -2012-09-10 10:00:00,11353.0 -2012-09-10 11:00:00,11806.0 -2012-09-10 12:00:00,12296.0 -2012-09-10 13:00:00,12470.0 -2012-09-10 14:00:00,12646.0 -2012-09-10 15:00:00,12845.0 -2012-09-10 16:00:00,12985.0 -2012-09-10 17:00:00,13068.0 -2012-09-10 18:00:00,13087.0 -2012-09-10 19:00:00,12899.0 -2012-09-10 20:00:00,12524.0 -2012-09-10 21:00:00,12740.0 -2012-09-10 22:00:00,12638.0 -2012-09-10 23:00:00,11952.0 -2012-09-11 00:00:00,10912.0 -2012-09-09 01:00:00,9341.0 -2012-09-09 02:00:00,8761.0 -2012-09-09 03:00:00,8402.0 -2012-09-09 04:00:00,8147.0 -2012-09-09 05:00:00,8025.0 -2012-09-09 06:00:00,7884.0 -2012-09-09 07:00:00,7989.0 -2012-09-09 08:00:00,7960.0 -2012-09-09 09:00:00,8074.0 -2012-09-09 10:00:00,8631.0 -2012-09-09 11:00:00,9080.0 -2012-09-09 12:00:00,9538.0 -2012-09-09 13:00:00,9807.0 -2012-09-09 14:00:00,9999.0 -2012-09-09 15:00:00,10052.0 -2012-09-09 16:00:00,10169.0 -2012-09-09 17:00:00,10203.0 -2012-09-09 18:00:00,10302.0 -2012-09-09 19:00:00,10287.0 -2012-09-09 20:00:00,10217.0 -2012-09-09 21:00:00,10574.0 -2012-09-09 22:00:00,10696.0 -2012-09-09 23:00:00,10286.0 -2012-09-10 00:00:00,9645.0 -2012-09-08 01:00:00,10243.0 -2012-09-08 02:00:00,9504.0 -2012-09-08 03:00:00,9019.0 -2012-09-08 04:00:00,8587.0 -2012-09-08 05:00:00,8485.0 -2012-09-08 06:00:00,8438.0 -2012-09-08 07:00:00,8633.0 -2012-09-08 08:00:00,8778.0 -2012-09-08 09:00:00,9096.0 -2012-09-08 10:00:00,9657.0 -2012-09-08 11:00:00,10164.0 -2012-09-08 12:00:00,10570.0 -2012-09-08 13:00:00,10734.0 -2012-09-08 14:00:00,10805.0 -2012-09-08 15:00:00,10792.0 -2012-09-08 16:00:00,10829.0 -2012-09-08 17:00:00,10837.0 -2012-09-08 18:00:00,10920.0 -2012-09-08 19:00:00,10754.0 -2012-09-08 20:00:00,10588.0 -2012-09-08 21:00:00,10872.0 -2012-09-08 22:00:00,10925.0 -2012-09-08 23:00:00,10609.0 -2012-09-09 00:00:00,10029.0 -2012-09-07 01:00:00,12152.0 -2012-09-07 02:00:00,11204.0 -2012-09-07 03:00:00,10625.0 -2012-09-07 04:00:00,10228.0 -2012-09-07 05:00:00,10089.0 -2012-09-07 06:00:00,10196.0 -2012-09-07 07:00:00,10827.0 -2012-09-07 08:00:00,12045.0 -2012-09-07 09:00:00,12759.0 -2012-09-07 10:00:00,13168.0 -2012-09-07 11:00:00,13330.0 -2012-09-07 12:00:00,13818.0 -2012-09-07 13:00:00,14247.0 -2012-09-07 14:00:00,14579.0 -2012-09-07 15:00:00,14991.0 -2012-09-07 16:00:00,14909.0 -2012-09-07 17:00:00,14682.0 -2012-09-07 18:00:00,14337.0 -2012-09-07 19:00:00,13848.0 -2012-09-07 20:00:00,13035.0 -2012-09-07 21:00:00,12979.0 -2012-09-07 22:00:00,12708.0 -2012-09-07 23:00:00,12097.0 -2012-09-08 00:00:00,11151.0 -2012-09-06 01:00:00,12918.0 -2012-09-06 02:00:00,11904.0 -2012-09-06 03:00:00,11206.0 -2012-09-06 04:00:00,10715.0 -2012-09-06 05:00:00,10441.0 -2012-09-06 06:00:00,10506.0 -2012-09-06 07:00:00,11121.0 -2012-09-06 08:00:00,12157.0 -2012-09-06 09:00:00,13029.0 -2012-09-06 10:00:00,13911.0 -2012-09-06 11:00:00,14814.0 -2012-09-06 12:00:00,15651.0 -2012-09-06 13:00:00,16202.0 -2012-09-06 14:00:00,16679.0 -2012-09-06 15:00:00,17177.0 -2012-09-06 16:00:00,17538.0 -2012-09-06 17:00:00,17828.0 -2012-09-06 18:00:00,17896.0 -2012-09-06 19:00:00,17514.0 -2012-09-06 20:00:00,16592.0 -2012-09-06 21:00:00,16204.0 -2012-09-06 22:00:00,15845.0 -2012-09-06 23:00:00,14850.0 -2012-09-07 00:00:00,13499.0 -2012-09-05 01:00:00,13087.0 -2012-09-05 02:00:00,11703.0 -2012-09-05 03:00:00,10892.0 -2012-09-05 04:00:00,10447.0 -2012-09-05 05:00:00,10219.0 -2012-09-05 06:00:00,10298.0 -2012-09-05 07:00:00,10962.0 -2012-09-05 08:00:00,12060.0 -2012-09-05 09:00:00,12807.0 -2012-09-05 10:00:00,13108.0 -2012-09-05 11:00:00,13422.0 -2012-09-05 12:00:00,14035.0 -2012-09-05 13:00:00,14731.0 -2012-09-05 14:00:00,15565.0 -2012-09-05 15:00:00,16403.0 -2012-09-05 16:00:00,16943.0 -2012-09-05 17:00:00,17294.0 -2012-09-05 18:00:00,17548.0 -2012-09-05 19:00:00,17512.0 -2012-09-05 20:00:00,16895.0 -2012-09-05 21:00:00,16758.0 -2012-09-05 22:00:00,16564.0 -2012-09-05 23:00:00,15651.0 -2012-09-06 00:00:00,14297.0 -2012-09-04 01:00:00,12910.0 -2012-09-04 02:00:00,12004.0 -2012-09-04 03:00:00,11415.0 -2012-09-04 04:00:00,11022.0 -2012-09-04 05:00:00,10850.0 -2012-09-04 06:00:00,10972.0 -2012-09-04 07:00:00,11728.0 -2012-09-04 08:00:00,12939.0 -2012-09-04 09:00:00,13904.0 -2012-09-04 10:00:00,14956.0 -2012-09-04 11:00:00,16023.0 -2012-09-04 12:00:00,17100.0 -2012-09-04 13:00:00,18011.0 -2012-09-04 14:00:00,18813.0 -2012-09-04 15:00:00,19496.0 -2012-09-04 16:00:00,19948.0 -2012-09-04 17:00:00,20216.0 -2012-09-04 18:00:00,20263.0 -2012-09-04 19:00:00,19827.0 -2012-09-04 20:00:00,18837.0 -2012-09-04 21:00:00,18335.0 -2012-09-04 22:00:00,17891.0 -2012-09-04 23:00:00,16773.0 -2012-09-05 00:00:00,15040.0 -2012-09-03 01:00:00,11668.0 -2012-09-03 02:00:00,10948.0 -2012-09-03 03:00:00,10401.0 -2012-09-03 04:00:00,9978.0 -2012-09-03 05:00:00,9746.0 -2012-09-03 06:00:00,9704.0 -2012-09-03 07:00:00,9825.0 -2012-09-03 08:00:00,9918.0 -2012-09-03 09:00:00,10258.0 -2012-09-03 10:00:00,11223.0 -2012-09-03 11:00:00,12367.0 -2012-09-03 12:00:00,13658.0 -2012-09-03 13:00:00,14737.0 -2012-09-03 14:00:00,15594.0 -2012-09-03 15:00:00,16268.0 -2012-09-03 16:00:00,16743.0 -2012-09-03 17:00:00,17052.0 -2012-09-03 18:00:00,17167.0 -2012-09-03 19:00:00,16970.0 -2012-09-03 20:00:00,16403.0 -2012-09-03 21:00:00,16240.0 -2012-09-03 22:00:00,16062.0 -2012-09-03 23:00:00,15224.0 -2012-09-04 00:00:00,14096.0 -2012-09-02 01:00:00,11262.0 -2012-09-02 02:00:00,10697.0 -2012-09-02 03:00:00,10231.0 -2012-09-02 04:00:00,9956.0 -2012-09-02 05:00:00,9748.0 -2012-09-02 06:00:00,9721.0 -2012-09-02 07:00:00,9777.0 -2012-09-02 08:00:00,9889.0 -2012-09-02 09:00:00,10078.0 -2012-09-02 10:00:00,10704.0 -2012-09-02 11:00:00,11303.0 -2012-09-02 12:00:00,11970.0 -2012-09-02 13:00:00,12526.0 -2012-09-02 14:00:00,13007.0 -2012-09-02 15:00:00,13464.0 -2012-09-02 16:00:00,13904.0 -2012-09-02 17:00:00,14088.0 -2012-09-02 18:00:00,14287.0 -2012-09-02 19:00:00,14157.0 -2012-09-02 20:00:00,13769.0 -2012-09-02 21:00:00,13642.0 -2012-09-02 22:00:00,13647.0 -2012-09-02 23:00:00,13191.0 -2012-09-03 00:00:00,12519.0 -2012-09-01 01:00:00,13371.0 -2012-09-01 02:00:00,12295.0 -2012-09-01 03:00:00,11555.0 -2012-09-01 04:00:00,11053.0 -2012-09-01 05:00:00,10723.0 -2012-09-01 06:00:00,10613.0 -2012-09-01 07:00:00,10744.0 -2012-09-01 08:00:00,10946.0 -2012-09-01 09:00:00,11251.0 -2012-09-01 10:00:00,11860.0 -2012-09-01 11:00:00,12362.0 -2012-09-01 12:00:00,12715.0 -2012-09-01 13:00:00,12904.0 -2012-09-01 14:00:00,12880.0 -2012-09-01 15:00:00,12780.0 -2012-09-01 16:00:00,12717.0 -2012-09-01 17:00:00,12618.0 -2012-09-01 18:00:00,12441.0 -2012-09-01 19:00:00,12253.0 -2012-09-01 20:00:00,12157.0 -2012-09-01 21:00:00,12460.0 -2012-09-01 22:00:00,12621.0 -2012-09-01 23:00:00,12394.0 -2012-09-02 00:00:00,11910.0 -2012-08-31 01:00:00,13461.0 -2012-08-31 02:00:00,12313.0 -2012-08-31 03:00:00,11473.0 -2012-08-31 04:00:00,10924.0 -2012-08-31 05:00:00,10603.0 -2012-08-31 06:00:00,10638.0 -2012-08-31 07:00:00,11237.0 -2012-08-31 08:00:00,12182.0 -2012-08-31 09:00:00,13212.0 -2012-08-31 10:00:00,14428.0 -2012-08-31 11:00:00,15821.0 -2012-08-31 12:00:00,17191.0 -2012-08-31 13:00:00,18392.0 -2012-08-31 14:00:00,19290.0 -2012-08-31 15:00:00,20015.0 -2012-08-31 16:00:00,20360.0 -2012-08-31 17:00:00,20190.0 -2012-08-31 18:00:00,19830.0 -2012-08-31 19:00:00,19181.0 -2012-08-31 20:00:00,18156.0 -2012-08-31 21:00:00,17598.0 -2012-08-31 22:00:00,17143.0 -2012-08-31 23:00:00,16076.0 -2012-09-01 00:00:00,14697.0 -2012-08-30 01:00:00,12639.0 -2012-08-30 02:00:00,11645.0 -2012-08-30 03:00:00,10929.0 -2012-08-30 04:00:00,10481.0 -2012-08-30 05:00:00,10202.0 -2012-08-30 06:00:00,10251.0 -2012-08-30 07:00:00,10877.0 -2012-08-30 08:00:00,11857.0 -2012-08-30 09:00:00,12767.0 -2012-08-30 10:00:00,13658.0 -2012-08-30 11:00:00,14608.0 -2012-08-30 12:00:00,15638.0 -2012-08-30 13:00:00,16488.0 -2012-08-30 14:00:00,17263.0 -2012-08-30 15:00:00,18074.0 -2012-08-30 16:00:00,18725.0 -2012-08-30 17:00:00,19199.0 -2012-08-30 18:00:00,19385.0 -2012-08-30 19:00:00,19110.0 -2012-08-30 20:00:00,18347.0 -2012-08-30 21:00:00,17755.0 -2012-08-30 22:00:00,17483.0 -2012-08-30 23:00:00,16457.0 -2012-08-31 00:00:00,14937.0 -2012-08-29 01:00:00,11909.0 -2012-08-29 02:00:00,11020.0 -2012-08-29 03:00:00,10413.0 -2012-08-29 04:00:00,10031.0 -2012-08-29 05:00:00,9856.0 -2012-08-29 06:00:00,9962.0 -2012-08-29 07:00:00,10631.0 -2012-08-29 08:00:00,11621.0 -2012-08-29 09:00:00,12524.0 -2012-08-29 10:00:00,13334.0 -2012-08-29 11:00:00,14061.0 -2012-08-29 12:00:00,14814.0 -2012-08-29 13:00:00,15498.0 -2012-08-29 14:00:00,16107.0 -2012-08-29 15:00:00,16784.0 -2012-08-29 16:00:00,17291.0 -2012-08-29 17:00:00,17645.0 -2012-08-29 18:00:00,17768.0 -2012-08-29 19:00:00,17480.0 -2012-08-29 20:00:00,16714.0 -2012-08-29 21:00:00,16210.0 -2012-08-29 22:00:00,16041.0 -2012-08-29 23:00:00,15182.0 -2012-08-30 00:00:00,13905.0 -2012-08-28 01:00:00,12077.0 -2012-08-28 02:00:00,11111.0 -2012-08-28 03:00:00,10445.0 -2012-08-28 04:00:00,10010.0 -2012-08-28 05:00:00,9798.0 -2012-08-28 06:00:00,9893.0 -2012-08-28 07:00:00,10533.0 -2012-08-28 08:00:00,11450.0 -2012-08-28 09:00:00,12438.0 -2012-08-28 10:00:00,13388.0 -2012-08-28 11:00:00,14224.0 -2012-08-28 12:00:00,15030.0 -2012-08-28 13:00:00,15735.0 -2012-08-28 14:00:00,16402.0 -2012-08-28 15:00:00,17045.0 -2012-08-28 16:00:00,17499.0 -2012-08-28 17:00:00,17793.0 -2012-08-28 18:00:00,17745.0 -2012-08-28 19:00:00,17197.0 -2012-08-28 20:00:00,16164.0 -2012-08-28 21:00:00,15557.0 -2012-08-28 22:00:00,15348.0 -2012-08-28 23:00:00,14439.0 -2012-08-29 00:00:00,13154.0 -2012-08-27 01:00:00,11064.0 -2012-08-27 02:00:00,10368.0 -2012-08-27 03:00:00,9977.0 -2012-08-27 04:00:00,9691.0 -2012-08-27 05:00:00,9697.0 -2012-08-27 06:00:00,9916.0 -2012-08-27 07:00:00,10704.0 -2012-08-27 08:00:00,11883.0 -2012-08-27 09:00:00,12881.0 -2012-08-27 10:00:00,13710.0 -2012-08-27 11:00:00,14599.0 -2012-08-27 12:00:00,15523.0 -2012-08-27 13:00:00,16277.0 -2012-08-27 14:00:00,16833.0 -2012-08-27 15:00:00,17278.0 -2012-08-27 16:00:00,17665.0 -2012-08-27 17:00:00,17947.0 -2012-08-27 18:00:00,18068.0 -2012-08-27 19:00:00,17746.0 -2012-08-27 20:00:00,16972.0 -2012-08-27 21:00:00,16228.0 -2012-08-27 22:00:00,15849.0 -2012-08-27 23:00:00,14815.0 -2012-08-28 00:00:00,13376.0 -2012-08-26 01:00:00,13151.0 -2012-08-26 02:00:00,12235.0 -2012-08-26 03:00:00,11474.0 -2012-08-26 04:00:00,10927.0 -2012-08-26 05:00:00,10509.0 -2012-08-26 06:00:00,10193.0 -2012-08-26 07:00:00,10157.0 -2012-08-26 08:00:00,10036.0 -2012-08-26 09:00:00,10462.0 -2012-08-26 10:00:00,11545.0 -2012-08-26 11:00:00,12407.0 -2012-08-26 12:00:00,12750.0 -2012-08-26 13:00:00,12792.0 -2012-08-26 14:00:00,12708.0 -2012-08-26 15:00:00,12730.0 -2012-08-26 16:00:00,12687.0 -2012-08-26 17:00:00,12710.0 -2012-08-26 18:00:00,12757.0 -2012-08-26 19:00:00,12867.0 -2012-08-26 20:00:00,13019.0 -2012-08-26 21:00:00,13245.0 -2012-08-26 22:00:00,13185.0 -2012-08-26 23:00:00,12680.0 -2012-08-27 00:00:00,11891.0 -2012-08-25 01:00:00,13708.0 -2012-08-25 02:00:00,12637.0 -2012-08-25 03:00:00,11836.0 -2012-08-25 04:00:00,11143.0 -2012-08-25 05:00:00,10685.0 -2012-08-25 06:00:00,10414.0 -2012-08-25 07:00:00,10483.0 -2012-08-25 08:00:00,10587.0 -2012-08-25 09:00:00,11273.0 -2012-08-25 10:00:00,12518.0 -2012-08-25 11:00:00,13910.0 -2012-08-25 12:00:00,15262.0 -2012-08-25 13:00:00,16400.0 -2012-08-25 14:00:00,17170.0 -2012-08-25 15:00:00,17545.0 -2012-08-25 16:00:00,17824.0 -2012-08-25 17:00:00,17940.0 -2012-08-25 18:00:00,17864.0 -2012-08-25 19:00:00,17553.0 -2012-08-25 20:00:00,16800.0 -2012-08-25 21:00:00,16317.0 -2012-08-25 22:00:00,16053.0 -2012-08-25 23:00:00,15346.0 -2012-08-26 00:00:00,14278.0 -2012-08-24 01:00:00,13198.0 -2012-08-24 02:00:00,12136.0 -2012-08-24 03:00:00,11373.0 -2012-08-24 04:00:00,10851.0 -2012-08-24 05:00:00,10521.0 -2012-08-24 06:00:00,10532.0 -2012-08-24 07:00:00,11088.0 -2012-08-24 08:00:00,11928.0 -2012-08-24 09:00:00,12881.0 -2012-08-24 10:00:00,13824.0 -2012-08-24 11:00:00,14855.0 -2012-08-24 12:00:00,16033.0 -2012-08-24 13:00:00,17075.0 -2012-08-24 14:00:00,17976.0 -2012-08-24 15:00:00,18804.0 -2012-08-24 16:00:00,19365.0 -2012-08-24 17:00:00,19602.0 -2012-08-24 18:00:00,19494.0 -2012-08-24 19:00:00,19045.0 -2012-08-24 20:00:00,18230.0 -2012-08-24 21:00:00,17644.0 -2012-08-24 22:00:00,17352.0 -2012-08-24 23:00:00,16445.0 -2012-08-25 00:00:00,15149.0 -2012-08-23 01:00:00,11522.0 -2012-08-23 02:00:00,10690.0 -2012-08-23 03:00:00,10142.0 -2012-08-23 04:00:00,9761.0 -2012-08-23 05:00:00,9582.0 -2012-08-23 06:00:00,9725.0 -2012-08-23 07:00:00,10369.0 -2012-08-23 08:00:00,11297.0 -2012-08-23 09:00:00,12240.0 -2012-08-23 10:00:00,12894.0 -2012-08-23 11:00:00,13412.0 -2012-08-23 12:00:00,14195.0 -2012-08-23 13:00:00,15089.0 -2012-08-23 14:00:00,16020.0 -2012-08-23 15:00:00,16915.0 -2012-08-23 16:00:00,17477.0 -2012-08-23 17:00:00,17881.0 -2012-08-23 18:00:00,18183.0 -2012-08-23 19:00:00,18082.0 -2012-08-23 20:00:00,17478.0 -2012-08-23 21:00:00,16990.0 -2012-08-23 22:00:00,16892.0 -2012-08-23 23:00:00,16027.0 -2012-08-24 00:00:00,14645.0 -2012-08-22 01:00:00,10440.0 -2012-08-22 02:00:00,9708.0 -2012-08-22 03:00:00,9229.0 -2012-08-22 04:00:00,8911.0 -2012-08-22 05:00:00,8774.0 -2012-08-22 06:00:00,8913.0 -2012-08-22 07:00:00,9517.0 -2012-08-22 08:00:00,10342.0 -2012-08-22 09:00:00,11205.0 -2012-08-22 10:00:00,11967.0 -2012-08-22 11:00:00,12664.0 -2012-08-22 12:00:00,13258.0 -2012-08-22 13:00:00,13721.0 -2012-08-22 14:00:00,14097.0 -2012-08-22 15:00:00,14588.0 -2012-08-22 16:00:00,14927.0 -2012-08-22 17:00:00,15269.0 -2012-08-22 18:00:00,15454.0 -2012-08-22 19:00:00,15373.0 -2012-08-22 20:00:00,14857.0 -2012-08-22 21:00:00,14505.0 -2012-08-22 22:00:00,14529.0 -2012-08-22 23:00:00,13856.0 -2012-08-23 00:00:00,12697.0 -2012-08-21 01:00:00,10283.0 -2012-08-21 02:00:00,9623.0 -2012-08-21 03:00:00,9170.0 -2012-08-21 04:00:00,8837.0 -2012-08-21 05:00:00,8694.0 -2012-08-21 06:00:00,8852.0 -2012-08-21 07:00:00,9437.0 -2012-08-21 08:00:00,10214.0 -2012-08-21 09:00:00,11074.0 -2012-08-21 10:00:00,11848.0 -2012-08-21 11:00:00,12438.0 -2012-08-21 12:00:00,12940.0 -2012-08-21 13:00:00,13264.0 -2012-08-21 14:00:00,13511.0 -2012-08-21 15:00:00,13837.0 -2012-08-21 16:00:00,14016.0 -2012-08-21 17:00:00,14091.0 -2012-08-21 18:00:00,14089.0 -2012-08-21 19:00:00,13906.0 -2012-08-21 20:00:00,13390.0 -2012-08-21 21:00:00,13054.0 -2012-08-21 22:00:00,13177.0 -2012-08-21 23:00:00,12584.0 -2012-08-22 00:00:00,11535.0 -2012-08-20 01:00:00,9900.0 -2012-08-20 02:00:00,9263.0 -2012-08-20 03:00:00,8899.0 -2012-08-20 04:00:00,8685.0 -2012-08-20 05:00:00,8615.0 -2012-08-20 06:00:00,8861.0 -2012-08-20 07:00:00,9474.0 -2012-08-20 08:00:00,10384.0 -2012-08-20 09:00:00,11218.0 -2012-08-20 10:00:00,12026.0 -2012-08-20 11:00:00,12567.0 -2012-08-20 12:00:00,13056.0 -2012-08-20 13:00:00,13245.0 -2012-08-20 14:00:00,13342.0 -2012-08-20 15:00:00,13439.0 -2012-08-20 16:00:00,13359.0 -2012-08-20 17:00:00,13344.0 -2012-08-20 18:00:00,13318.0 -2012-08-20 19:00:00,13120.0 -2012-08-20 20:00:00,12683.0 -2012-08-20 21:00:00,12514.0 -2012-08-20 22:00:00,12725.0 -2012-08-20 23:00:00,12191.0 -2012-08-21 00:00:00,11216.0 -2012-08-19 01:00:00,9753.0 -2012-08-19 02:00:00,9227.0 -2012-08-19 03:00:00,8726.0 -2012-08-19 04:00:00,8450.0 -2012-08-19 05:00:00,8238.0 -2012-08-19 06:00:00,8192.0 -2012-08-19 07:00:00,8228.0 -2012-08-19 08:00:00,8107.0 -2012-08-19 09:00:00,8306.0 -2012-08-19 10:00:00,8879.0 -2012-08-19 11:00:00,9394.0 -2012-08-19 12:00:00,9975.0 -2012-08-19 13:00:00,10522.0 -2012-08-19 14:00:00,10839.0 -2012-08-19 15:00:00,11087.0 -2012-08-19 16:00:00,11248.0 -2012-08-19 17:00:00,11429.0 -2012-08-19 18:00:00,11510.0 -2012-08-19 19:00:00,11543.0 -2012-08-19 20:00:00,11333.0 -2012-08-19 21:00:00,11294.0 -2012-08-19 22:00:00,11565.0 -2012-08-19 23:00:00,11305.0 -2012-08-20 00:00:00,10597.0 -2012-08-18 01:00:00,10202.0 -2012-08-18 02:00:00,9451.0 -2012-08-18 03:00:00,8970.0 -2012-08-18 04:00:00,8639.0 -2012-08-18 05:00:00,8506.0 -2012-08-18 06:00:00,8470.0 -2012-08-18 07:00:00,8611.0 -2012-08-18 08:00:00,8664.0 -2012-08-18 09:00:00,9200.0 -2012-08-18 10:00:00,9813.0 -2012-08-18 11:00:00,10409.0 -2012-08-18 12:00:00,10818.0 -2012-08-18 13:00:00,11061.0 -2012-08-18 14:00:00,11235.0 -2012-08-18 15:00:00,11255.0 -2012-08-18 16:00:00,11347.0 -2012-08-18 17:00:00,11503.0 -2012-08-18 18:00:00,11563.0 -2012-08-18 19:00:00,11508.0 -2012-08-18 20:00:00,11193.0 -2012-08-18 21:00:00,11057.0 -2012-08-18 22:00:00,11359.0 -2012-08-18 23:00:00,11025.0 -2012-08-19 00:00:00,10464.0 -2012-08-17 01:00:00,10575.0 -2012-08-17 02:00:00,9852.0 -2012-08-17 03:00:00,9323.0 -2012-08-17 04:00:00,8975.0 -2012-08-17 05:00:00,8822.0 -2012-08-17 06:00:00,8943.0 -2012-08-17 07:00:00,9461.0 -2012-08-17 08:00:00,10049.0 -2012-08-17 09:00:00,10832.0 -2012-08-17 10:00:00,11535.0 -2012-08-17 11:00:00,12055.0 -2012-08-17 12:00:00,12502.0 -2012-08-17 13:00:00,12723.0 -2012-08-17 14:00:00,12895.0 -2012-08-17 15:00:00,13087.0 -2012-08-17 16:00:00,13224.0 -2012-08-17 17:00:00,13315.0 -2012-08-17 18:00:00,13317.0 -2012-08-17 19:00:00,13077.0 -2012-08-17 20:00:00,12613.0 -2012-08-17 21:00:00,12196.0 -2012-08-17 22:00:00,12296.0 -2012-08-17 23:00:00,11879.0 -2012-08-18 00:00:00,11002.0 -2012-08-16 01:00:00,12420.0 -2012-08-16 02:00:00,11535.0 -2012-08-16 03:00:00,10868.0 -2012-08-16 04:00:00,10420.0 -2012-08-16 05:00:00,10177.0 -2012-08-16 06:00:00,10258.0 -2012-08-16 07:00:00,10867.0 -2012-08-16 08:00:00,11761.0 -2012-08-16 09:00:00,12710.0 -2012-08-16 10:00:00,13295.0 -2012-08-16 11:00:00,13470.0 -2012-08-16 12:00:00,13733.0 -2012-08-16 13:00:00,13843.0 -2012-08-16 14:00:00,14129.0 -2012-08-16 15:00:00,14349.0 -2012-08-16 16:00:00,14226.0 -2012-08-16 17:00:00,14064.0 -2012-08-16 18:00:00,13929.0 -2012-08-16 19:00:00,13714.0 -2012-08-16 20:00:00,13361.0 -2012-08-16 21:00:00,13034.0 -2012-08-16 22:00:00,13127.0 -2012-08-16 23:00:00,12590.0 -2012-08-17 00:00:00,11582.0 -2012-08-15 01:00:00,11240.0 -2012-08-15 02:00:00,10418.0 -2012-08-15 03:00:00,9865.0 -2012-08-15 04:00:00,9555.0 -2012-08-15 05:00:00,9402.0 -2012-08-15 06:00:00,9549.0 -2012-08-15 07:00:00,10167.0 -2012-08-15 08:00:00,11031.0 -2012-08-15 09:00:00,11823.0 -2012-08-15 10:00:00,12430.0 -2012-08-15 11:00:00,12935.0 -2012-08-15 12:00:00,13717.0 -2012-08-15 13:00:00,14524.0 -2012-08-15 14:00:00,15183.0 -2012-08-15 15:00:00,15795.0 -2012-08-15 16:00:00,16167.0 -2012-08-15 17:00:00,16479.0 -2012-08-15 18:00:00,16586.0 -2012-08-15 19:00:00,16425.0 -2012-08-15 20:00:00,15780.0 -2012-08-15 21:00:00,15352.0 -2012-08-15 22:00:00,15403.0 -2012-08-15 23:00:00,14765.0 -2012-08-16 00:00:00,13626.0 -2012-08-14 01:00:00,10175.0 -2012-08-14 02:00:00,9516.0 -2012-08-14 03:00:00,9118.0 -2012-08-14 04:00:00,8840.0 -2012-08-14 05:00:00,8733.0 -2012-08-14 06:00:00,8893.0 -2012-08-14 07:00:00,9435.0 -2012-08-14 08:00:00,10163.0 -2012-08-14 09:00:00,11060.0 -2012-08-14 10:00:00,11838.0 -2012-08-14 11:00:00,12523.0 -2012-08-14 12:00:00,13158.0 -2012-08-14 13:00:00,13659.0 -2012-08-14 14:00:00,14043.0 -2012-08-14 15:00:00,14408.0 -2012-08-14 16:00:00,14574.0 -2012-08-14 17:00:00,14655.0 -2012-08-14 18:00:00,14643.0 -2012-08-14 19:00:00,14419.0 -2012-08-14 20:00:00,13971.0 -2012-08-14 21:00:00,13712.0 -2012-08-14 22:00:00,13831.0 -2012-08-14 23:00:00,13317.0 -2012-08-15 00:00:00,12322.0 -2012-08-13 01:00:00,10021.0 -2012-08-13 02:00:00,9485.0 -2012-08-13 03:00:00,9147.0 -2012-08-13 04:00:00,8949.0 -2012-08-13 05:00:00,8882.0 -2012-08-13 06:00:00,9087.0 -2012-08-13 07:00:00,9784.0 -2012-08-13 08:00:00,10743.0 -2012-08-13 09:00:00,11467.0 -2012-08-13 10:00:00,11990.0 -2012-08-13 11:00:00,12418.0 -2012-08-13 12:00:00,12735.0 -2012-08-13 13:00:00,12793.0 -2012-08-13 14:00:00,12786.0 -2012-08-13 15:00:00,12769.0 -2012-08-13 16:00:00,12643.0 -2012-08-13 17:00:00,12495.0 -2012-08-13 18:00:00,12360.0 -2012-08-13 19:00:00,12200.0 -2012-08-13 20:00:00,11929.0 -2012-08-13 21:00:00,11895.0 -2012-08-13 22:00:00,12194.0 -2012-08-13 23:00:00,11825.0 -2012-08-14 00:00:00,11037.0 -2012-08-12 01:00:00,10015.0 -2012-08-12 02:00:00,9398.0 -2012-08-12 03:00:00,8901.0 -2012-08-12 04:00:00,8640.0 -2012-08-12 05:00:00,8429.0 -2012-08-12 06:00:00,8311.0 -2012-08-12 07:00:00,8283.0 -2012-08-12 08:00:00,8155.0 -2012-08-12 09:00:00,8438.0 -2012-08-12 10:00:00,8946.0 -2012-08-12 11:00:00,9442.0 -2012-08-12 12:00:00,9884.0 -2012-08-12 13:00:00,10249.0 -2012-08-12 14:00:00,10464.0 -2012-08-12 15:00:00,10630.0 -2012-08-12 16:00:00,10700.0 -2012-08-12 17:00:00,10766.0 -2012-08-12 18:00:00,10808.0 -2012-08-12 19:00:00,10879.0 -2012-08-12 20:00:00,10883.0 -2012-08-12 21:00:00,11058.0 -2012-08-12 22:00:00,11395.0 -2012-08-12 23:00:00,11231.0 -2012-08-13 00:00:00,10682.0 -2012-08-11 01:00:00,10244.0 -2012-08-11 02:00:00,9551.0 -2012-08-11 03:00:00,9081.0 -2012-08-11 04:00:00,8720.0 -2012-08-11 05:00:00,8549.0 -2012-08-11 06:00:00,8493.0 -2012-08-11 07:00:00,8618.0 -2012-08-11 08:00:00,8652.0 -2012-08-11 09:00:00,9117.0 -2012-08-11 10:00:00,9694.0 -2012-08-11 11:00:00,10323.0 -2012-08-11 12:00:00,10796.0 -2012-08-11 13:00:00,11112.0 -2012-08-11 14:00:00,11259.0 -2012-08-11 15:00:00,11409.0 -2012-08-11 16:00:00,11568.0 -2012-08-11 17:00:00,11750.0 -2012-08-11 18:00:00,11889.0 -2012-08-11 19:00:00,11916.0 -2012-08-11 20:00:00,11677.0 -2012-08-11 21:00:00,11437.0 -2012-08-11 22:00:00,11600.0 -2012-08-11 23:00:00,11382.0 -2012-08-12 00:00:00,10711.0 -2012-08-10 01:00:00,10817.0 -2012-08-10 02:00:00,10134.0 -2012-08-10 03:00:00,9680.0 -2012-08-10 04:00:00,9407.0 -2012-08-10 05:00:00,9252.0 -2012-08-10 06:00:00,9396.0 -2012-08-10 07:00:00,9925.0 -2012-08-10 08:00:00,10606.0 -2012-08-10 09:00:00,11320.0 -2012-08-10 10:00:00,11956.0 -2012-08-10 11:00:00,12409.0 -2012-08-10 12:00:00,12755.0 -2012-08-10 13:00:00,12990.0 -2012-08-10 14:00:00,13139.0 -2012-08-10 15:00:00,13271.0 -2012-08-10 16:00:00,13284.0 -2012-08-10 17:00:00,13220.0 -2012-08-10 18:00:00,12975.0 -2012-08-10 19:00:00,12654.0 -2012-08-10 20:00:00,12207.0 -2012-08-10 21:00:00,11885.0 -2012-08-10 22:00:00,12101.0 -2012-08-10 23:00:00,11840.0 -2012-08-11 00:00:00,11092.0 -2012-08-09 01:00:00,12192.0 -2012-08-09 02:00:00,11373.0 -2012-08-09 03:00:00,10823.0 -2012-08-09 04:00:00,10409.0 -2012-08-09 05:00:00,10246.0 -2012-08-09 06:00:00,10364.0 -2012-08-09 07:00:00,10982.0 -2012-08-09 08:00:00,11795.0 -2012-08-09 09:00:00,12613.0 -2012-08-09 10:00:00,13272.0 -2012-08-09 11:00:00,13744.0 -2012-08-09 12:00:00,14154.0 -2012-08-09 13:00:00,14386.0 -2012-08-09 14:00:00,14513.0 -2012-08-09 15:00:00,14649.0 -2012-08-09 16:00:00,14554.0 -2012-08-09 17:00:00,14268.0 -2012-08-09 18:00:00,13862.0 -2012-08-09 19:00:00,13454.0 -2012-08-09 20:00:00,13005.0 -2012-08-09 21:00:00,12848.0 -2012-08-09 22:00:00,12981.0 -2012-08-09 23:00:00,12586.0 -2012-08-10 00:00:00,11758.0 -2012-08-08 01:00:00,14792.0 -2012-08-08 02:00:00,13618.0 -2012-08-08 03:00:00,12801.0 -2012-08-08 04:00:00,12171.0 -2012-08-08 05:00:00,11787.0 -2012-08-08 06:00:00,11741.0 -2012-08-08 07:00:00,12197.0 -2012-08-08 08:00:00,12928.0 -2012-08-08 09:00:00,14077.0 -2012-08-08 10:00:00,15151.0 -2012-08-08 11:00:00,16024.0 -2012-08-08 12:00:00,16860.0 -2012-08-08 13:00:00,17359.0 -2012-08-08 14:00:00,17760.0 -2012-08-08 15:00:00,17803.0 -2012-08-08 16:00:00,17336.0 -2012-08-08 17:00:00,16692.0 -2012-08-08 18:00:00,15993.0 -2012-08-08 19:00:00,15387.0 -2012-08-08 20:00:00,14877.0 -2012-08-08 21:00:00,14516.0 -2012-08-08 22:00:00,14628.0 -2012-08-08 23:00:00,14159.0 -2012-08-09 00:00:00,13216.0 -2012-08-07 01:00:00,12471.0 -2012-08-07 02:00:00,11456.0 -2012-08-07 03:00:00,10762.0 -2012-08-07 04:00:00,10279.0 -2012-08-07 05:00:00,10028.0 -2012-08-07 06:00:00,10096.0 -2012-08-07 07:00:00,10606.0 -2012-08-07 08:00:00,11349.0 -2012-08-07 09:00:00,12414.0 -2012-08-07 10:00:00,13461.0 -2012-08-07 11:00:00,14499.0 -2012-08-07 12:00:00,15509.0 -2012-08-07 13:00:00,16429.0 -2012-08-07 14:00:00,17279.0 -2012-08-07 15:00:00,18395.0 -2012-08-07 16:00:00,19184.0 -2012-08-07 17:00:00,19659.0 -2012-08-07 18:00:00,19934.0 -2012-08-07 19:00:00,19989.0 -2012-08-07 20:00:00,19547.0 -2012-08-07 21:00:00,18835.0 -2012-08-07 22:00:00,18546.0 -2012-08-07 23:00:00,17784.0 -2012-08-08 00:00:00,16334.0 -2012-08-06 01:00:00,11568.0 -2012-08-06 02:00:00,10695.0 -2012-08-06 03:00:00,10157.0 -2012-08-06 04:00:00,9756.0 -2012-08-06 05:00:00,9630.0 -2012-08-06 06:00:00,9746.0 -2012-08-06 07:00:00,10297.0 -2012-08-06 08:00:00,11009.0 -2012-08-06 09:00:00,12210.0 -2012-08-06 10:00:00,13137.0 -2012-08-06 11:00:00,13960.0 -2012-08-06 12:00:00,14733.0 -2012-08-06 13:00:00,15364.0 -2012-08-06 14:00:00,15908.0 -2012-08-06 15:00:00,16497.0 -2012-08-06 16:00:00,16953.0 -2012-08-06 17:00:00,17338.0 -2012-08-06 18:00:00,17569.0 -2012-08-06 19:00:00,17571.0 -2012-08-06 20:00:00,17051.0 -2012-08-06 21:00:00,16282.0 -2012-08-06 22:00:00,15850.0 -2012-08-06 23:00:00,15088.0 -2012-08-07 00:00:00,13756.0 -2012-08-05 01:00:00,12357.0 -2012-08-05 02:00:00,11526.0 -2012-08-05 03:00:00,10995.0 -2012-08-05 04:00:00,10607.0 -2012-08-05 05:00:00,10290.0 -2012-08-05 06:00:00,10095.0 -2012-08-05 07:00:00,9889.0 -2012-08-05 08:00:00,9720.0 -2012-08-05 09:00:00,10223.0 -2012-08-05 10:00:00,11135.0 -2012-08-05 11:00:00,11982.0 -2012-08-05 12:00:00,12812.0 -2012-08-05 13:00:00,13373.0 -2012-08-05 14:00:00,13812.0 -2012-08-05 15:00:00,14172.0 -2012-08-05 16:00:00,14426.0 -2012-08-05 17:00:00,14705.0 -2012-08-05 18:00:00,14895.0 -2012-08-05 19:00:00,14916.0 -2012-08-05 20:00:00,14580.0 -2012-08-05 21:00:00,14027.0 -2012-08-05 22:00:00,13751.0 -2012-08-05 23:00:00,13419.0 -2012-08-06 00:00:00,12515.0 -2012-08-04 01:00:00,15392.0 -2012-08-04 02:00:00,14168.0 -2012-08-04 03:00:00,13303.0 -2012-08-04 04:00:00,12532.0 -2012-08-04 05:00:00,12077.0 -2012-08-04 06:00:00,11862.0 -2012-08-04 07:00:00,11874.0 -2012-08-04 08:00:00,11960.0 -2012-08-04 09:00:00,12894.0 -2012-08-04 10:00:00,14311.0 -2012-08-04 11:00:00,15831.0 -2012-08-04 12:00:00,17093.0 -2012-08-04 13:00:00,18070.0 -2012-08-04 14:00:00,18753.0 -2012-08-04 15:00:00,19271.0 -2012-08-04 16:00:00,19513.0 -2012-08-04 17:00:00,18617.0 -2012-08-04 18:00:00,16490.0 -2012-08-04 19:00:00,15274.0 -2012-08-04 20:00:00,14603.0 -2012-08-04 21:00:00,14148.0 -2012-08-04 22:00:00,14165.0 -2012-08-04 23:00:00,13945.0 -2012-08-05 00:00:00,13169.0 -2012-08-03 01:00:00,14335.0 -2012-08-03 02:00:00,13149.0 -2012-08-03 03:00:00,12336.0 -2012-08-03 04:00:00,11779.0 -2012-08-03 05:00:00,11486.0 -2012-08-03 06:00:00,11524.0 -2012-08-03 07:00:00,11985.0 -2012-08-03 08:00:00,12709.0 -2012-08-03 09:00:00,14045.0 -2012-08-03 10:00:00,15397.0 -2012-08-03 11:00:00,16684.0 -2012-08-03 12:00:00,17916.0 -2012-08-03 13:00:00,18951.0 -2012-08-03 14:00:00,19770.0 -2012-08-03 15:00:00,20415.0 -2012-08-03 16:00:00,20834.0 -2012-08-03 17:00:00,21053.0 -2012-08-03 18:00:00,21078.0 -2012-08-03 19:00:00,20709.0 -2012-08-03 20:00:00,20004.0 -2012-08-03 21:00:00,19116.0 -2012-08-03 22:00:00,18635.0 -2012-08-03 23:00:00,17957.0 -2012-08-04 00:00:00,16689.0 -2012-08-02 01:00:00,14139.0 -2012-08-02 02:00:00,12987.0 -2012-08-02 03:00:00,12191.0 -2012-08-02 04:00:00,11570.0 -2012-08-02 05:00:00,11255.0 -2012-08-02 06:00:00,11207.0 -2012-08-02 07:00:00,11622.0 -2012-08-02 08:00:00,12242.0 -2012-08-02 09:00:00,13413.0 -2012-08-02 10:00:00,14499.0 -2012-08-02 11:00:00,15359.0 -2012-08-02 12:00:00,16401.0 -2012-08-02 13:00:00,17559.0 -2012-08-02 14:00:00,18610.0 -2012-08-02 15:00:00,19603.0 -2012-08-02 16:00:00,20192.0 -2012-08-02 17:00:00,20240.0 -2012-08-02 18:00:00,19981.0 -2012-08-02 19:00:00,19800.0 -2012-08-02 20:00:00,19135.0 -2012-08-02 21:00:00,18288.0 -2012-08-02 22:00:00,17805.0 -2012-08-02 23:00:00,17333.0 -2012-08-03 00:00:00,15852.0 -2012-08-01 01:00:00,13303.0 -2012-08-01 02:00:00,12284.0 -2012-08-01 03:00:00,11497.0 -2012-08-01 04:00:00,10980.0 -2012-08-01 05:00:00,10707.0 -2012-08-01 06:00:00,10796.0 -2012-08-01 07:00:00,11314.0 -2012-08-01 08:00:00,12066.0 -2012-08-01 09:00:00,13280.0 -2012-08-01 10:00:00,14433.0 -2012-08-01 11:00:00,15381.0 -2012-08-01 12:00:00,16445.0 -2012-08-01 13:00:00,17277.0 -2012-08-01 14:00:00,17983.0 -2012-08-01 15:00:00,18601.0 -2012-08-01 16:00:00,19114.0 -2012-08-01 17:00:00,19507.0 -2012-08-01 18:00:00,19743.0 -2012-08-01 19:00:00,19632.0 -2012-08-01 20:00:00,19067.0 -2012-08-01 21:00:00,18215.0 -2012-08-01 22:00:00,17785.0 -2012-08-01 23:00:00,17029.0 -2012-08-02 00:00:00,15604.0 -2012-07-31 01:00:00,15400.0 -2012-07-31 02:00:00,14175.0 -2012-07-31 03:00:00,13299.0 -2012-07-31 04:00:00,12707.0 -2012-07-31 05:00:00,12107.0 -2012-07-31 06:00:00,11890.0 -2012-07-31 07:00:00,12251.0 -2012-07-31 08:00:00,12927.0 -2012-07-31 09:00:00,14120.0 -2012-07-31 10:00:00,15234.0 -2012-07-31 11:00:00,16237.0 -2012-07-31 12:00:00,17020.0 -2012-07-31 13:00:00,17713.0 -2012-07-31 14:00:00,18326.0 -2012-07-31 15:00:00,18953.0 -2012-07-31 16:00:00,19144.0 -2012-07-31 17:00:00,19258.0 -2012-07-31 18:00:00,19242.0 -2012-07-31 19:00:00,18940.0 -2012-07-31 20:00:00,18196.0 -2012-07-31 21:00:00,17174.0 -2012-07-31 22:00:00,16634.0 -2012-07-31 23:00:00,15963.0 -2012-08-01 00:00:00,14721.0 -2012-07-30 01:00:00,12343.0 -2012-07-30 02:00:00,11612.0 -2012-07-30 03:00:00,10946.0 -2012-07-30 04:00:00,10610.0 -2012-07-30 05:00:00,10446.0 -2012-07-30 06:00:00,10652.0 -2012-07-30 07:00:00,11182.0 -2012-07-30 08:00:00,11983.0 -2012-07-30 09:00:00,13123.0 -2012-07-30 10:00:00,14373.0 -2012-07-30 11:00:00,15638.0 -2012-07-30 12:00:00,16961.0 -2012-07-30 13:00:00,18111.0 -2012-07-30 14:00:00,18949.0 -2012-07-30 15:00:00,19635.0 -2012-07-30 16:00:00,20072.0 -2012-07-30 17:00:00,20295.0 -2012-07-30 18:00:00,20481.0 -2012-07-30 19:00:00,20473.0 -2012-07-30 20:00:00,20010.0 -2012-07-30 21:00:00,19329.0 -2012-07-30 22:00:00,19028.0 -2012-07-30 23:00:00,18307.0 -2012-07-31 00:00:00,16845.0 -2012-07-29 01:00:00,11669.0 -2012-07-29 02:00:00,10867.0 -2012-07-29 03:00:00,10235.0 -2012-07-29 04:00:00,9825.0 -2012-07-29 05:00:00,9467.0 -2012-07-29 06:00:00,9285.0 -2012-07-29 07:00:00,9176.0 -2012-07-29 08:00:00,9089.0 -2012-07-29 09:00:00,9625.0 -2012-07-29 10:00:00,10490.0 -2012-07-29 11:00:00,11511.0 -2012-07-29 12:00:00,12590.0 -2012-07-29 13:00:00,13546.0 -2012-07-29 14:00:00,14173.0 -2012-07-29 15:00:00,14611.0 -2012-07-29 16:00:00,14674.0 -2012-07-29 17:00:00,14438.0 -2012-07-29 18:00:00,14141.0 -2012-07-29 19:00:00,14022.0 -2012-07-29 20:00:00,13946.0 -2012-07-29 21:00:00,13790.0 -2012-07-29 22:00:00,14040.0 -2012-07-29 23:00:00,13908.0 -2012-07-30 00:00:00,13263.0 -2012-07-28 01:00:00,12442.0 -2012-07-28 02:00:00,11508.0 -2012-07-28 03:00:00,10857.0 -2012-07-28 04:00:00,10322.0 -2012-07-28 05:00:00,10074.0 -2012-07-28 06:00:00,9967.0 -2012-07-28 07:00:00,10029.0 -2012-07-28 08:00:00,10218.0 -2012-07-28 09:00:00,11073.0 -2012-07-28 10:00:00,12091.0 -2012-07-28 11:00:00,13012.0 -2012-07-28 12:00:00,13828.0 -2012-07-28 13:00:00,14341.0 -2012-07-28 14:00:00,14732.0 -2012-07-28 15:00:00,14988.0 -2012-07-28 16:00:00,15227.0 -2012-07-28 17:00:00,15421.0 -2012-07-28 18:00:00,15455.0 -2012-07-28 19:00:00,15206.0 -2012-07-28 20:00:00,14804.0 -2012-07-28 21:00:00,14109.0 -2012-07-28 22:00:00,13825.0 -2012-07-28 23:00:00,13472.0 -2012-07-29 00:00:00,12587.0 -2012-07-27 01:00:00,13604.0 -2012-07-27 02:00:00,12544.0 -2012-07-27 03:00:00,11768.0 -2012-07-27 04:00:00,11206.0 -2012-07-27 05:00:00,10948.0 -2012-07-27 06:00:00,10979.0 -2012-07-27 07:00:00,11429.0 -2012-07-27 08:00:00,12105.0 -2012-07-27 09:00:00,13030.0 -2012-07-27 10:00:00,13697.0 -2012-07-27 11:00:00,14428.0 -2012-07-27 12:00:00,15347.0 -2012-07-27 13:00:00,16216.0 -2012-07-27 14:00:00,16791.0 -2012-07-27 15:00:00,17244.0 -2012-07-27 16:00:00,17586.0 -2012-07-27 17:00:00,17674.0 -2012-07-27 18:00:00,17476.0 -2012-07-27 19:00:00,16929.0 -2012-07-27 20:00:00,16141.0 -2012-07-27 21:00:00,15308.0 -2012-07-27 22:00:00,14923.0 -2012-07-27 23:00:00,14531.0 -2012-07-28 00:00:00,13476.0 -2012-07-26 01:00:00,17368.0 -2012-07-26 02:00:00,16170.0 -2012-07-26 03:00:00,15370.0 -2012-07-26 04:00:00,14648.0 -2012-07-26 05:00:00,13951.0 -2012-07-26 06:00:00,13450.0 -2012-07-26 07:00:00,13652.0 -2012-07-26 08:00:00,14318.0 -2012-07-26 09:00:00,15015.0 -2012-07-26 10:00:00,15534.0 -2012-07-26 11:00:00,15920.0 -2012-07-26 12:00:00,16420.0 -2012-07-26 13:00:00,16860.0 -2012-07-26 14:00:00,17696.0 -2012-07-26 15:00:00,18362.0 -2012-07-26 16:00:00,18769.0 -2012-07-26 17:00:00,19293.0 -2012-07-26 18:00:00,19561.0 -2012-07-26 19:00:00,19046.0 -2012-07-26 20:00:00,18082.0 -2012-07-26 21:00:00,17272.0 -2012-07-26 22:00:00,16716.0 -2012-07-26 23:00:00,16256.0 -2012-07-27 00:00:00,14983.0 -2012-07-25 01:00:00,13388.0 -2012-07-25 02:00:00,12461.0 -2012-07-25 03:00:00,11800.0 -2012-07-25 04:00:00,11307.0 -2012-07-25 05:00:00,11081.0 -2012-07-25 06:00:00,11151.0 -2012-07-25 07:00:00,11642.0 -2012-07-25 08:00:00,12508.0 -2012-07-25 09:00:00,13478.0 -2012-07-25 10:00:00,14556.0 -2012-07-25 11:00:00,15656.0 -2012-07-25 12:00:00,17089.0 -2012-07-25 13:00:00,18377.0 -2012-07-25 14:00:00,19668.0 -2012-07-25 15:00:00,20855.0 -2012-07-25 16:00:00,21714.0 -2012-07-25 17:00:00,22311.0 -2012-07-25 18:00:00,22505.0 -2012-07-25 19:00:00,22431.0 -2012-07-25 20:00:00,21984.0 -2012-07-25 21:00:00,21264.0 -2012-07-25 22:00:00,20840.0 -2012-07-25 23:00:00,20259.0 -2012-07-26 00:00:00,18851.0 -2012-07-24 01:00:00,16872.0 -2012-07-24 02:00:00,15789.0 -2012-07-24 03:00:00,14908.0 -2012-07-24 04:00:00,14313.0 -2012-07-24 05:00:00,13947.0 -2012-07-24 06:00:00,13882.0 -2012-07-24 07:00:00,14229.0 -2012-07-24 08:00:00,14441.0 -2012-07-24 09:00:00,14537.0 -2012-07-24 10:00:00,14748.0 -2012-07-24 11:00:00,15223.0 -2012-07-24 12:00:00,15788.0 -2012-07-24 13:00:00,16325.0 -2012-07-24 14:00:00,16680.0 -2012-07-24 15:00:00,17217.0 -2012-07-24 16:00:00,17642.0 -2012-07-24 17:00:00,17794.0 -2012-07-24 18:00:00,17976.0 -2012-07-24 19:00:00,17872.0 -2012-07-24 20:00:00,17283.0 -2012-07-24 21:00:00,16406.0 -2012-07-24 22:00:00,16040.0 -2012-07-24 23:00:00,15655.0 -2012-07-25 00:00:00,14594.0 -2012-07-23 01:00:00,14096.0 -2012-07-23 02:00:00,13273.0 -2012-07-23 03:00:00,12710.0 -2012-07-23 04:00:00,12365.0 -2012-07-23 05:00:00,12162.0 -2012-07-23 06:00:00,12345.0 -2012-07-23 07:00:00,12977.0 -2012-07-23 08:00:00,13895.0 -2012-07-23 09:00:00,15244.0 -2012-07-23 10:00:00,16585.0 -2012-07-23 11:00:00,18014.0 -2012-07-23 12:00:00,19267.0 -2012-07-23 13:00:00,20194.0 -2012-07-23 14:00:00,20929.0 -2012-07-23 15:00:00,21273.0 -2012-07-23 16:00:00,21651.0 -2012-07-23 17:00:00,21876.0 -2012-07-23 18:00:00,21933.0 -2012-07-23 19:00:00,21611.0 -2012-07-23 20:00:00,20917.0 -2012-07-23 21:00:00,20423.0 -2012-07-23 22:00:00,20263.0 -2012-07-23 23:00:00,19640.0 -2012-07-24 00:00:00,18301.0 -2012-07-22 01:00:00,13711.0 -2012-07-22 02:00:00,12688.0 -2012-07-22 03:00:00,11983.0 -2012-07-22 04:00:00,11368.0 -2012-07-22 05:00:00,10945.0 -2012-07-22 06:00:00,10642.0 -2012-07-22 07:00:00,10341.0 -2012-07-22 08:00:00,10417.0 -2012-07-22 09:00:00,11080.0 -2012-07-22 10:00:00,11989.0 -2012-07-22 11:00:00,12980.0 -2012-07-22 12:00:00,13596.0 -2012-07-22 13:00:00,13976.0 -2012-07-22 14:00:00,14301.0 -2012-07-22 15:00:00,14650.0 -2012-07-22 16:00:00,14746.0 -2012-07-22 17:00:00,14989.0 -2012-07-22 18:00:00,15823.0 -2012-07-22 19:00:00,16407.0 -2012-07-22 20:00:00,16549.0 -2012-07-22 21:00:00,16189.0 -2012-07-22 22:00:00,16060.0 -2012-07-22 23:00:00,15892.0 -2012-07-23 00:00:00,15072.0 -2012-07-21 01:00:00,12120.0 -2012-07-21 02:00:00,11196.0 -2012-07-21 03:00:00,10539.0 -2012-07-21 04:00:00,10102.0 -2012-07-21 05:00:00,9864.0 -2012-07-21 06:00:00,9751.0 -2012-07-21 07:00:00,9772.0 -2012-07-21 08:00:00,10023.0 -2012-07-21 09:00:00,10870.0 -2012-07-21 10:00:00,11926.0 -2012-07-21 11:00:00,12995.0 -2012-07-21 12:00:00,14065.0 -2012-07-21 13:00:00,14963.0 -2012-07-21 14:00:00,15709.0 -2012-07-21 15:00:00,16274.0 -2012-07-21 16:00:00,16575.0 -2012-07-21 17:00:00,16619.0 -2012-07-21 18:00:00,16662.0 -2012-07-21 19:00:00,16425.0 -2012-07-21 20:00:00,16124.0 -2012-07-21 21:00:00,15816.0 -2012-07-21 22:00:00,15725.0 -2012-07-21 23:00:00,15572.0 -2012-07-22 00:00:00,14714.0 -2012-07-20 01:00:00,13141.0 -2012-07-20 02:00:00,12183.0 -2012-07-20 03:00:00,11581.0 -2012-07-20 04:00:00,11116.0 -2012-07-20 05:00:00,10913.0 -2012-07-20 06:00:00,11010.0 -2012-07-20 07:00:00,11534.0 -2012-07-20 08:00:00,12266.0 -2012-07-20 09:00:00,13041.0 -2012-07-20 10:00:00,13732.0 -2012-07-20 11:00:00,14473.0 -2012-07-20 12:00:00,15261.0 -2012-07-20 13:00:00,15785.0 -2012-07-20 14:00:00,16183.0 -2012-07-20 15:00:00,16519.0 -2012-07-20 16:00:00,16659.0 -2012-07-20 17:00:00,16856.0 -2012-07-20 18:00:00,16895.0 -2012-07-20 19:00:00,16601.0 -2012-07-20 20:00:00,15848.0 -2012-07-20 21:00:00,14987.0 -2012-07-20 22:00:00,14423.0 -2012-07-20 23:00:00,14090.0 -2012-07-21 00:00:00,13106.0 -2012-07-19 01:00:00,14568.0 -2012-07-19 02:00:00,13599.0 -2012-07-19 03:00:00,12741.0 -2012-07-19 04:00:00,12287.0 -2012-07-19 05:00:00,12046.0 -2012-07-19 06:00:00,12086.0 -2012-07-19 07:00:00,12651.0 -2012-07-19 08:00:00,13463.0 -2012-07-19 09:00:00,14308.0 -2012-07-19 10:00:00,15084.0 -2012-07-19 11:00:00,15734.0 -2012-07-19 12:00:00,16666.0 -2012-07-19 13:00:00,17319.0 -2012-07-19 14:00:00,17700.0 -2012-07-19 15:00:00,17982.0 -2012-07-19 16:00:00,18027.0 -2012-07-19 17:00:00,18213.0 -2012-07-19 18:00:00,18205.0 -2012-07-19 19:00:00,17725.0 -2012-07-19 20:00:00,16889.0 -2012-07-19 21:00:00,16190.0 -2012-07-19 22:00:00,15878.0 -2012-07-19 23:00:00,15419.0 -2012-07-20 00:00:00,14334.0 -2012-07-18 01:00:00,17414.0 -2012-07-18 02:00:00,16104.0 -2012-07-18 03:00:00,15210.0 -2012-07-18 04:00:00,14448.0 -2012-07-18 05:00:00,13940.0 -2012-07-18 06:00:00,13829.0 -2012-07-18 07:00:00,14131.0 -2012-07-18 08:00:00,14913.0 -2012-07-18 09:00:00,16194.0 -2012-07-18 10:00:00,17182.0 -2012-07-18 11:00:00,18058.0 -2012-07-18 12:00:00,18943.0 -2012-07-18 13:00:00,19767.0 -2012-07-18 14:00:00,20552.0 -2012-07-18 15:00:00,21034.0 -2012-07-18 16:00:00,21242.0 -2012-07-18 17:00:00,21434.0 -2012-07-18 18:00:00,21471.0 -2012-07-18 19:00:00,21149.0 -2012-07-18 20:00:00,20389.0 -2012-07-18 21:00:00,19451.0 -2012-07-18 22:00:00,18877.0 -2012-07-18 23:00:00,17931.0 -2012-07-19 00:00:00,15898.0 -2012-07-17 01:00:00,17035.0 -2012-07-17 02:00:00,15850.0 -2012-07-17 03:00:00,14963.0 -2012-07-17 04:00:00,14308.0 -2012-07-17 05:00:00,13882.0 -2012-07-17 06:00:00,13802.0 -2012-07-17 07:00:00,14121.0 -2012-07-17 08:00:00,15111.0 -2012-07-17 09:00:00,16691.0 -2012-07-17 10:00:00,18215.0 -2012-07-17 11:00:00,19653.0 -2012-07-17 12:00:00,20927.0 -2012-07-17 13:00:00,21883.0 -2012-07-17 14:00:00,22450.0 -2012-07-17 15:00:00,22915.0 -2012-07-17 16:00:00,23138.0 -2012-07-17 17:00:00,23269.0 -2012-07-17 18:00:00,23265.0 -2012-07-17 19:00:00,23064.0 -2012-07-17 20:00:00,22569.0 -2012-07-17 21:00:00,21902.0 -2012-07-17 22:00:00,21351.0 -2012-07-17 23:00:00,20605.0 -2012-07-18 00:00:00,19005.0 -2012-07-16 01:00:00,14692.0 -2012-07-16 02:00:00,13605.0 -2012-07-16 03:00:00,12892.0 -2012-07-16 04:00:00,12330.0 -2012-07-16 05:00:00,12035.0 -2012-07-16 06:00:00,12120.0 -2012-07-16 07:00:00,12552.0 -2012-07-16 08:00:00,13620.0 -2012-07-16 09:00:00,15209.0 -2012-07-16 10:00:00,16855.0 -2012-07-16 11:00:00,18204.0 -2012-07-16 12:00:00,19525.0 -2012-07-16 13:00:00,20504.0 -2012-07-16 14:00:00,21192.0 -2012-07-16 15:00:00,21695.0 -2012-07-16 16:00:00,22014.0 -2012-07-16 17:00:00,22301.0 -2012-07-16 18:00:00,22350.0 -2012-07-16 19:00:00,22233.0 -2012-07-16 20:00:00,21804.0 -2012-07-16 21:00:00,21051.0 -2012-07-16 22:00:00,20461.0 -2012-07-16 23:00:00,19914.0 -2012-07-17 00:00:00,18492.0 -2012-07-15 01:00:00,13066.0 -2012-07-15 02:00:00,12103.0 -2012-07-15 03:00:00,11448.0 -2012-07-15 04:00:00,10857.0 -2012-07-15 05:00:00,10456.0 -2012-07-15 06:00:00,10226.0 -2012-07-15 07:00:00,10002.0 -2012-07-15 08:00:00,10136.0 -2012-07-15 09:00:00,10984.0 -2012-07-15 10:00:00,12312.0 -2012-07-15 11:00:00,13778.0 -2012-07-15 12:00:00,15200.0 -2012-07-15 13:00:00,16275.0 -2012-07-15 14:00:00,16972.0 -2012-07-15 15:00:00,17544.0 -2012-07-15 16:00:00,18061.0 -2012-07-15 17:00:00,18404.0 -2012-07-15 18:00:00,18612.0 -2012-07-15 19:00:00,18659.0 -2012-07-15 20:00:00,18338.0 -2012-07-15 21:00:00,17735.0 -2012-07-15 22:00:00,17407.0 -2012-07-15 23:00:00,17034.0 -2012-07-16 00:00:00,15941.0 -2012-07-14 01:00:00,13413.0 -2012-07-14 02:00:00,12411.0 -2012-07-14 03:00:00,11738.0 -2012-07-14 04:00:00,11206.0 -2012-07-14 05:00:00,10888.0 -2012-07-14 06:00:00,10765.0 -2012-07-14 07:00:00,10782.0 -2012-07-14 08:00:00,11083.0 -2012-07-14 09:00:00,11951.0 -2012-07-14 10:00:00,13080.0 -2012-07-14 11:00:00,14250.0 -2012-07-14 12:00:00,15389.0 -2012-07-14 13:00:00,16279.0 -2012-07-14 14:00:00,16926.0 -2012-07-14 15:00:00,16852.0 -2012-07-14 16:00:00,16524.0 -2012-07-14 17:00:00,16380.0 -2012-07-14 18:00:00,16347.0 -2012-07-14 19:00:00,16379.0 -2012-07-14 20:00:00,16158.0 -2012-07-14 21:00:00,15703.0 -2012-07-14 22:00:00,15241.0 -2012-07-14 23:00:00,14986.0 -2012-07-15 00:00:00,14092.0 -2012-07-13 01:00:00,13981.0 -2012-07-13 02:00:00,12935.0 -2012-07-13 03:00:00,12125.0 -2012-07-13 04:00:00,11560.0 -2012-07-13 05:00:00,11236.0 -2012-07-13 06:00:00,11261.0 -2012-07-13 07:00:00,11594.0 -2012-07-13 08:00:00,12451.0 -2012-07-13 09:00:00,13806.0 -2012-07-13 10:00:00,15175.0 -2012-07-13 11:00:00,16463.0 -2012-07-13 12:00:00,17881.0 -2012-07-13 13:00:00,18898.0 -2012-07-13 14:00:00,19436.0 -2012-07-13 15:00:00,19407.0 -2012-07-13 16:00:00,18955.0 -2012-07-13 17:00:00,18555.0 -2012-07-13 18:00:00,18013.0 -2012-07-13 19:00:00,17160.0 -2012-07-13 20:00:00,16351.0 -2012-07-13 21:00:00,15809.0 -2012-07-13 22:00:00,15644.0 -2012-07-13 23:00:00,15460.0 -2012-07-14 00:00:00,14484.0 -2012-07-12 01:00:00,13168.0 -2012-07-12 02:00:00,12146.0 -2012-07-12 03:00:00,11376.0 -2012-07-12 04:00:00,10871.0 -2012-07-12 05:00:00,10550.0 -2012-07-12 06:00:00,10594.0 -2012-07-12 07:00:00,10960.0 -2012-07-12 08:00:00,11815.0 -2012-07-12 09:00:00,12969.0 -2012-07-12 10:00:00,14065.0 -2012-07-12 11:00:00,14987.0 -2012-07-12 12:00:00,16005.0 -2012-07-12 13:00:00,16667.0 -2012-07-12 14:00:00,17691.0 -2012-07-12 15:00:00,18299.0 -2012-07-12 16:00:00,18750.0 -2012-07-12 17:00:00,19004.0 -2012-07-12 18:00:00,19104.0 -2012-07-12 19:00:00,18909.0 -2012-07-12 20:00:00,18324.0 -2012-07-12 21:00:00,17523.0 -2012-07-12 22:00:00,16982.0 -2012-07-12 23:00:00,16568.0 -2012-07-13 00:00:00,15320.0 -2012-07-11 01:00:00,12119.0 -2012-07-11 02:00:00,11240.0 -2012-07-11 03:00:00,10616.0 -2012-07-11 04:00:00,10169.0 -2012-07-11 05:00:00,9981.0 -2012-07-11 06:00:00,10119.0 -2012-07-11 07:00:00,10476.0 -2012-07-11 08:00:00,11435.0 -2012-07-11 09:00:00,12626.0 -2012-07-11 10:00:00,13650.0 -2012-07-11 11:00:00,14511.0 -2012-07-11 12:00:00,15385.0 -2012-07-11 13:00:00,16109.0 -2012-07-11 14:00:00,16674.0 -2012-07-11 15:00:00,17264.0 -2012-07-11 16:00:00,17624.0 -2012-07-11 17:00:00,17896.0 -2012-07-11 18:00:00,18029.0 -2012-07-11 19:00:00,17874.0 -2012-07-11 20:00:00,17251.0 -2012-07-11 21:00:00,16528.0 -2012-07-11 22:00:00,15975.0 -2012-07-11 23:00:00,15587.0 -2012-07-12 00:00:00,14436.0 -2012-07-10 01:00:00,12978.0 -2012-07-10 02:00:00,12031.0 -2012-07-10 03:00:00,11354.0 -2012-07-10 04:00:00,10912.0 -2012-07-10 05:00:00,10628.0 -2012-07-10 06:00:00,10737.0 -2012-07-10 07:00:00,11150.0 -2012-07-10 08:00:00,12119.0 -2012-07-10 09:00:00,13261.0 -2012-07-10 10:00:00,14105.0 -2012-07-10 11:00:00,14879.0 -2012-07-10 12:00:00,15564.0 -2012-07-10 13:00:00,16107.0 -2012-07-10 14:00:00,16392.0 -2012-07-10 15:00:00,16548.0 -2012-07-10 16:00:00,16538.0 -2012-07-10 17:00:00,16535.0 -2012-07-10 18:00:00,16475.0 -2012-07-10 19:00:00,16289.0 -2012-07-10 20:00:00,15693.0 -2012-07-10 21:00:00,15016.0 -2012-07-10 22:00:00,14599.0 -2012-07-10 23:00:00,14367.0 -2012-07-11 00:00:00,13283.0 -2012-07-09 01:00:00,12154.0 -2012-07-09 02:00:00,11344.0 -2012-07-09 03:00:00,10736.0 -2012-07-09 04:00:00,10385.0 -2012-07-09 05:00:00,10193.0 -2012-07-09 06:00:00,10367.0 -2012-07-09 07:00:00,10701.0 -2012-07-09 08:00:00,11722.0 -2012-07-09 09:00:00,13045.0 -2012-07-09 10:00:00,14170.0 -2012-07-09 11:00:00,15220.0 -2012-07-09 12:00:00,16275.0 -2012-07-09 13:00:00,17194.0 -2012-07-09 14:00:00,17928.0 -2012-07-09 15:00:00,18492.0 -2012-07-09 16:00:00,18567.0 -2012-07-09 17:00:00,18425.0 -2012-07-09 18:00:00,18134.0 -2012-07-09 19:00:00,17533.0 -2012-07-09 20:00:00,16747.0 -2012-07-09 21:00:00,16121.0 -2012-07-09 22:00:00,15665.0 -2012-07-09 23:00:00,15339.0 -2012-07-10 00:00:00,14215.0 -2012-07-08 01:00:00,12912.0 -2012-07-08 02:00:00,12102.0 -2012-07-08 03:00:00,11518.0 -2012-07-08 04:00:00,11126.0 -2012-07-08 05:00:00,10788.0 -2012-07-08 06:00:00,10690.0 -2012-07-08 07:00:00,10527.0 -2012-07-08 08:00:00,10524.0 -2012-07-08 09:00:00,10920.0 -2012-07-08 10:00:00,11676.0 -2012-07-08 11:00:00,12497.0 -2012-07-08 12:00:00,13233.0 -2012-07-08 13:00:00,13738.0 -2012-07-08 14:00:00,14123.0 -2012-07-08 15:00:00,14378.0 -2012-07-08 16:00:00,14685.0 -2012-07-08 17:00:00,14988.0 -2012-07-08 18:00:00,15172.0 -2012-07-08 19:00:00,15180.0 -2012-07-08 20:00:00,14983.0 -2012-07-08 21:00:00,14443.0 -2012-07-08 22:00:00,14054.0 -2012-07-08 23:00:00,13932.0 -2012-07-09 00:00:00,13159.0 -2012-07-07 01:00:00,18165.0 -2012-07-07 02:00:00,16808.0 -2012-07-07 03:00:00,15853.0 -2012-07-07 04:00:00,15051.0 -2012-07-07 05:00:00,14511.0 -2012-07-07 06:00:00,14139.0 -2012-07-07 07:00:00,13916.0 -2012-07-07 08:00:00,14187.0 -2012-07-07 09:00:00,15446.0 -2012-07-07 10:00:00,16988.0 -2012-07-07 11:00:00,18441.0 -2012-07-07 12:00:00,19534.0 -2012-07-07 13:00:00,20109.0 -2012-07-07 14:00:00,20181.0 -2012-07-07 15:00:00,20056.0 -2012-07-07 16:00:00,19873.0 -2012-07-07 17:00:00,19603.0 -2012-07-07 18:00:00,19096.0 -2012-07-07 19:00:00,18075.0 -2012-07-07 20:00:00,16970.0 -2012-07-07 21:00:00,15852.0 -2012-07-07 22:00:00,15220.0 -2012-07-07 23:00:00,14728.0 -2012-07-08 00:00:00,13808.0 -2012-07-06 01:00:00,18076.0 -2012-07-06 02:00:00,16934.0 -2012-07-06 03:00:00,16045.0 -2012-07-06 04:00:00,15354.0 -2012-07-06 05:00:00,14876.0 -2012-07-06 06:00:00,14741.0 -2012-07-06 07:00:00,14903.0 -2012-07-06 08:00:00,15739.0 -2012-07-06 09:00:00,17176.0 -2012-07-06 10:00:00,18657.0 -2012-07-06 11:00:00,20078.0 -2012-07-06 12:00:00,21449.0 -2012-07-06 13:00:00,22430.0 -2012-07-06 14:00:00,23022.0 -2012-07-06 15:00:00,23376.0 -2012-07-06 16:00:00,23533.0 -2012-07-06 17:00:00,23603.0 -2012-07-06 18:00:00,23516.0 -2012-07-06 19:00:00,23230.0 -2012-07-06 20:00:00,22755.0 -2012-07-06 21:00:00,22117.0 -2012-07-06 22:00:00,21547.0 -2012-07-06 23:00:00,21011.0 -2012-07-07 00:00:00,19604.0 -2012-07-05 01:00:00,16796.0 -2012-07-05 02:00:00,15696.0 -2012-07-05 03:00:00,14751.0 -2012-07-05 04:00:00,14033.0 -2012-07-05 05:00:00,13546.0 -2012-07-05 06:00:00,13445.0 -2012-07-05 07:00:00,13637.0 -2012-07-05 08:00:00,14569.0 -2012-07-05 09:00:00,16119.0 -2012-07-05 10:00:00,17657.0 -2012-07-05 11:00:00,19115.0 -2012-07-05 12:00:00,20545.0 -2012-07-05 13:00:00,21708.0 -2012-07-05 14:00:00,22511.0 -2012-07-05 15:00:00,23046.0 -2012-07-05 16:00:00,23134.0 -2012-07-05 17:00:00,22647.0 -2012-07-05 18:00:00,22307.0 -2012-07-05 19:00:00,22213.0 -2012-07-05 20:00:00,22023.0 -2012-07-05 21:00:00,21413.0 -2012-07-05 22:00:00,21077.0 -2012-07-05 23:00:00,20684.0 -2012-07-06 00:00:00,19429.0 -2012-07-04 01:00:00,16751.0 -2012-07-04 02:00:00,15558.0 -2012-07-04 03:00:00,14594.0 -2012-07-04 04:00:00,13835.0 -2012-07-04 05:00:00,13330.0 -2012-07-04 06:00:00,13027.0 -2012-07-04 07:00:00,12770.0 -2012-07-04 08:00:00,12903.0 -2012-07-04 09:00:00,13794.0 -2012-07-04 10:00:00,15120.0 -2012-07-04 11:00:00,16659.0 -2012-07-04 12:00:00,18118.0 -2012-07-04 13:00:00,19219.0 -2012-07-04 14:00:00,19946.0 -2012-07-04 15:00:00,20355.0 -2012-07-04 16:00:00,20644.0 -2012-07-04 17:00:00,20769.0 -2012-07-04 18:00:00,20784.0 -2012-07-04 19:00:00,20646.0 -2012-07-04 20:00:00,20155.0 -2012-07-04 21:00:00,19534.0 -2012-07-04 22:00:00,19072.0 -2012-07-04 23:00:00,18661.0 -2012-07-05 00:00:00,17792.0 -2012-07-03 01:00:00,16861.0 -2012-07-03 02:00:00,15562.0 -2012-07-03 03:00:00,14516.0 -2012-07-03 04:00:00,13655.0 -2012-07-03 05:00:00,13132.0 -2012-07-03 06:00:00,12958.0 -2012-07-03 07:00:00,13123.0 -2012-07-03 08:00:00,13883.0 -2012-07-03 09:00:00,15026.0 -2012-07-03 10:00:00,16302.0 -2012-07-03 11:00:00,17422.0 -2012-07-03 12:00:00,18348.0 -2012-07-03 13:00:00,19266.0 -2012-07-03 14:00:00,20248.0 -2012-07-03 15:00:00,21101.0 -2012-07-03 16:00:00,21579.0 -2012-07-03 17:00:00,21837.0 -2012-07-03 18:00:00,21903.0 -2012-07-03 19:00:00,21721.0 -2012-07-03 20:00:00,21263.0 -2012-07-03 21:00:00,20526.0 -2012-07-03 22:00:00,19874.0 -2012-07-03 23:00:00,19313.0 -2012-07-04 00:00:00,18078.0 -2012-07-02 01:00:00,13413.0 -2012-07-02 02:00:00,12475.0 -2012-07-02 03:00:00,11815.0 -2012-07-02 04:00:00,11329.0 -2012-07-02 05:00:00,11088.0 -2012-07-02 06:00:00,11181.0 -2012-07-02 07:00:00,11558.0 -2012-07-02 08:00:00,12620.0 -2012-07-02 09:00:00,14114.0 -2012-07-02 10:00:00,15629.0 -2012-07-02 11:00:00,16926.0 -2012-07-02 12:00:00,18324.0 -2012-07-02 13:00:00,19402.0 -2012-07-02 14:00:00,20253.0 -2012-07-02 15:00:00,20891.0 -2012-07-02 16:00:00,21280.0 -2012-07-02 17:00:00,21537.0 -2012-07-02 18:00:00,21672.0 -2012-07-02 19:00:00,21567.0 -2012-07-02 20:00:00,21020.0 -2012-07-02 21:00:00,20345.0 -2012-07-02 22:00:00,19957.0 -2012-07-02 23:00:00,19540.0 -2012-07-03 00:00:00,18287.0 -2012-07-01 01:00:00,14048.0 -2012-07-01 02:00:00,12982.0 -2012-07-01 03:00:00,12215.0 -2012-07-01 04:00:00,11528.0 -2012-07-01 05:00:00,11105.0 -2012-07-01 06:00:00,10835.0 -2012-07-01 07:00:00,10509.0 -2012-07-01 08:00:00,10753.0 -2012-07-01 09:00:00,11684.0 -2012-07-01 10:00:00,12928.0 -2012-07-01 11:00:00,14225.0 -2012-07-01 12:00:00,15343.0 -2012-07-01 13:00:00,16127.0 -2012-07-01 14:00:00,15451.0 -2012-07-01 15:00:00,14084.0 -2012-07-01 16:00:00,14424.0 -2012-07-01 17:00:00,15323.0 -2012-07-01 18:00:00,15915.0 -2012-07-01 19:00:00,16247.0 -2012-07-01 20:00:00,16178.0 -2012-07-01 21:00:00,15827.0 -2012-07-01 22:00:00,15507.0 -2012-07-01 23:00:00,15351.0 -2012-07-02 00:00:00,14462.0 -2012-06-30 01:00:00,13421.0 -2012-06-30 02:00:00,12310.0 -2012-06-30 03:00:00,11507.0 -2012-06-30 04:00:00,10951.0 -2012-06-30 05:00:00,10637.0 -2012-06-30 06:00:00,10489.0 -2012-06-30 07:00:00,10395.0 -2012-06-30 08:00:00,10693.0 -2012-06-30 09:00:00,11605.0 -2012-06-30 10:00:00,12863.0 -2012-06-30 11:00:00,13996.0 -2012-06-30 12:00:00,15007.0 -2012-06-30 13:00:00,15619.0 -2012-06-30 14:00:00,16101.0 -2012-06-30 15:00:00,16761.0 -2012-06-30 16:00:00,17332.0 -2012-06-30 17:00:00,17719.0 -2012-06-30 18:00:00,17942.0 -2012-06-30 19:00:00,17970.0 -2012-06-30 20:00:00,17664.0 -2012-06-30 21:00:00,17039.0 -2012-06-30 22:00:00,16548.0 -2012-06-30 23:00:00,16149.0 -2012-07-01 00:00:00,15171.0 -2012-06-29 01:00:00,16987.0 -2012-06-29 02:00:00,15761.0 -2012-06-29 03:00:00,14838.0 -2012-06-29 04:00:00,14029.0 -2012-06-29 05:00:00,13350.0 -2012-06-29 06:00:00,13039.0 -2012-06-29 07:00:00,13055.0 -2012-06-29 08:00:00,13770.0 -2012-06-29 09:00:00,14997.0 -2012-06-29 10:00:00,16081.0 -2012-06-29 11:00:00,16980.0 -2012-06-29 12:00:00,17160.0 -2012-06-29 13:00:00,16526.0 -2012-06-29 14:00:00,15669.0 -2012-06-29 15:00:00,15598.0 -2012-06-29 16:00:00,16249.0 -2012-06-29 17:00:00,16895.0 -2012-06-29 18:00:00,17567.0 -2012-06-29 19:00:00,17901.0 -2012-06-29 20:00:00,17603.0 -2012-06-29 21:00:00,16823.0 -2012-06-29 22:00:00,16334.0 -2012-06-29 23:00:00,15837.0 -2012-06-30 00:00:00,14537.0 -2012-06-28 01:00:00,14449.0 -2012-06-28 02:00:00,13319.0 -2012-06-28 03:00:00,12520.0 -2012-06-28 04:00:00,11952.0 -2012-06-28 05:00:00,11615.0 -2012-06-28 06:00:00,11648.0 -2012-06-28 07:00:00,12052.0 -2012-06-28 08:00:00,13179.0 -2012-06-28 09:00:00,14774.0 -2012-06-28 10:00:00,16520.0 -2012-06-28 11:00:00,18159.0 -2012-06-28 12:00:00,19618.0 -2012-06-28 13:00:00,20637.0 -2012-06-28 14:00:00,21456.0 -2012-06-28 15:00:00,22187.0 -2012-06-28 16:00:00,22508.0 -2012-06-28 17:00:00,22749.0 -2012-06-28 18:00:00,22830.0 -2012-06-28 19:00:00,22363.0 -2012-06-28 20:00:00,21493.0 -2012-06-28 21:00:00,20729.0 -2012-06-28 22:00:00,20358.0 -2012-06-28 23:00:00,19889.0 -2012-06-29 00:00:00,18478.0 -2012-06-27 01:00:00,11590.0 -2012-06-27 02:00:00,10671.0 -2012-06-27 03:00:00,10062.0 -2012-06-27 04:00:00,9614.0 -2012-06-27 05:00:00,9417.0 -2012-06-27 06:00:00,9519.0 -2012-06-27 07:00:00,9849.0 -2012-06-27 08:00:00,10840.0 -2012-06-27 09:00:00,12024.0 -2012-06-27 10:00:00,13044.0 -2012-06-27 11:00:00,13892.0 -2012-06-27 12:00:00,14695.0 -2012-06-27 13:00:00,15299.0 -2012-06-27 14:00:00,16061.0 -2012-06-27 15:00:00,16894.0 -2012-06-27 16:00:00,17555.0 -2012-06-27 17:00:00,18153.0 -2012-06-27 18:00:00,18565.0 -2012-06-27 19:00:00,18790.0 -2012-06-27 20:00:00,18473.0 -2012-06-27 21:00:00,17917.0 -2012-06-27 22:00:00,17442.0 -2012-06-27 23:00:00,17129.0 -2012-06-28 00:00:00,15892.0 -2012-06-26 01:00:00,10452.0 -2012-06-26 02:00:00,9773.0 -2012-06-26 03:00:00,9315.0 -2012-06-26 04:00:00,9017.0 -2012-06-26 05:00:00,8876.0 -2012-06-26 06:00:00,9054.0 -2012-06-26 07:00:00,9348.0 -2012-06-26 08:00:00,10308.0 -2012-06-26 09:00:00,11369.0 -2012-06-26 10:00:00,12209.0 -2012-06-26 11:00:00,12815.0 -2012-06-26 12:00:00,13367.0 -2012-06-26 13:00:00,13775.0 -2012-06-26 14:00:00,14141.0 -2012-06-26 15:00:00,14661.0 -2012-06-26 16:00:00,15018.0 -2012-06-26 17:00:00,15283.0 -2012-06-26 18:00:00,15490.0 -2012-06-26 19:00:00,15401.0 -2012-06-26 20:00:00,14952.0 -2012-06-26 21:00:00,14415.0 -2012-06-26 22:00:00,14067.0 -2012-06-26 23:00:00,13828.0 -2012-06-27 00:00:00,12816.0 -2012-06-25 01:00:00,12228.0 -2012-06-25 02:00:00,11280.0 -2012-06-25 03:00:00,10571.0 -2012-06-25 04:00:00,10139.0 -2012-06-25 05:00:00,9837.0 -2012-06-25 06:00:00,9868.0 -2012-06-25 07:00:00,10072.0 -2012-06-25 08:00:00,11060.0 -2012-06-25 09:00:00,12032.0 -2012-06-25 10:00:00,12795.0 -2012-06-25 11:00:00,13263.0 -2012-06-25 12:00:00,13697.0 -2012-06-25 13:00:00,13893.0 -2012-06-25 14:00:00,14039.0 -2012-06-25 15:00:00,14142.0 -2012-06-25 16:00:00,14164.0 -2012-06-25 17:00:00,14142.0 -2012-06-25 18:00:00,14052.0 -2012-06-25 19:00:00,13778.0 -2012-06-25 20:00:00,13191.0 -2012-06-25 21:00:00,12612.0 -2012-06-25 22:00:00,12333.0 -2012-06-25 23:00:00,12241.0 -2012-06-26 00:00:00,11406.0 -2012-06-24 01:00:00,11654.0 -2012-06-24 02:00:00,10857.0 -2012-06-24 03:00:00,10301.0 -2012-06-24 04:00:00,9773.0 -2012-06-24 05:00:00,9502.0 -2012-06-24 06:00:00,9356.0 -2012-06-24 07:00:00,9154.0 -2012-06-24 08:00:00,9201.0 -2012-06-24 09:00:00,9947.0 -2012-06-24 10:00:00,10712.0 -2012-06-24 11:00:00,11609.0 -2012-06-24 12:00:00,12438.0 -2012-06-24 13:00:00,13371.0 -2012-06-24 14:00:00,14263.0 -2012-06-24 15:00:00,14986.0 -2012-06-24 16:00:00,15716.0 -2012-06-24 17:00:00,16197.0 -2012-06-24 18:00:00,16544.0 -2012-06-24 19:00:00,16395.0 -2012-06-24 20:00:00,15870.0 -2012-06-24 21:00:00,15142.0 -2012-06-24 22:00:00,14783.0 -2012-06-24 23:00:00,14422.0 -2012-06-25 00:00:00,13411.0 -2012-06-23 01:00:00,12037.0 -2012-06-23 02:00:00,11049.0 -2012-06-23 03:00:00,10385.0 -2012-06-23 04:00:00,9886.0 -2012-06-23 05:00:00,9591.0 -2012-06-23 06:00:00,9462.0 -2012-06-23 07:00:00,9347.0 -2012-06-23 08:00:00,9774.0 -2012-06-23 09:00:00,10633.0 -2012-06-23 10:00:00,11594.0 -2012-06-23 11:00:00,12542.0 -2012-06-23 12:00:00,13449.0 -2012-06-23 13:00:00,14168.0 -2012-06-23 14:00:00,14670.0 -2012-06-23 15:00:00,14924.0 -2012-06-23 16:00:00,15044.0 -2012-06-23 17:00:00,14795.0 -2012-06-23 18:00:00,14609.0 -2012-06-23 19:00:00,14271.0 -2012-06-23 20:00:00,13712.0 -2012-06-23 21:00:00,13357.0 -2012-06-23 22:00:00,13318.0 -2012-06-23 23:00:00,13166.0 -2012-06-24 00:00:00,12452.0 -2012-06-22 01:00:00,12617.0 -2012-06-22 02:00:00,11534.0 -2012-06-22 03:00:00,10733.0 -2012-06-22 04:00:00,10219.0 -2012-06-22 05:00:00,9899.0 -2012-06-22 06:00:00,9909.0 -2012-06-22 07:00:00,10175.0 -2012-06-22 08:00:00,11082.0 -2012-06-22 09:00:00,12322.0 -2012-06-22 10:00:00,13305.0 -2012-06-22 11:00:00,14039.0 -2012-06-22 12:00:00,14741.0 -2012-06-22 13:00:00,15190.0 -2012-06-22 14:00:00,15579.0 -2012-06-22 15:00:00,15976.0 -2012-06-22 16:00:00,16221.0 -2012-06-22 17:00:00,16414.0 -2012-06-22 18:00:00,16464.0 -2012-06-22 19:00:00,16233.0 -2012-06-22 20:00:00,15714.0 -2012-06-22 21:00:00,15041.0 -2012-06-22 22:00:00,14506.0 -2012-06-22 23:00:00,14234.0 -2012-06-23 00:00:00,13184.0 -2012-06-21 01:00:00,15398.0 -2012-06-21 02:00:00,14255.0 -2012-06-21 03:00:00,13447.0 -2012-06-21 04:00:00,12854.0 -2012-06-21 05:00:00,12551.0 -2012-06-21 06:00:00,12529.0 -2012-06-21 07:00:00,12769.0 -2012-06-21 08:00:00,13874.0 -2012-06-21 09:00:00,14991.0 -2012-06-21 10:00:00,15771.0 -2012-06-21 11:00:00,16281.0 -2012-06-21 12:00:00,16448.0 -2012-06-21 13:00:00,16284.0 -2012-06-21 14:00:00,16121.0 -2012-06-21 15:00:00,16170.0 -2012-06-21 16:00:00,16046.0 -2012-06-21 17:00:00,16384.0 -2012-06-21 18:00:00,16866.0 -2012-06-21 19:00:00,17030.0 -2012-06-21 20:00:00,16695.0 -2012-06-21 21:00:00,16107.0 -2012-06-21 22:00:00,15535.0 -2012-06-21 23:00:00,15151.0 -2012-06-22 00:00:00,13942.0 -2012-06-20 01:00:00,15974.0 -2012-06-20 02:00:00,14699.0 -2012-06-20 03:00:00,13765.0 -2012-06-20 04:00:00,13078.0 -2012-06-20 05:00:00,12635.0 -2012-06-20 06:00:00,12519.0 -2012-06-20 07:00:00,12778.0 -2012-06-20 08:00:00,13915.0 -2012-06-20 09:00:00,15276.0 -2012-06-20 10:00:00,16460.0 -2012-06-20 11:00:00,17542.0 -2012-06-20 12:00:00,18622.0 -2012-06-20 13:00:00,19469.0 -2012-06-20 14:00:00,20225.0 -2012-06-20 15:00:00,20861.0 -2012-06-20 16:00:00,21170.0 -2012-06-20 17:00:00,21303.0 -2012-06-20 18:00:00,21297.0 -2012-06-20 19:00:00,21027.0 -2012-06-20 20:00:00,20459.0 -2012-06-20 21:00:00,19617.0 -2012-06-20 22:00:00,18889.0 -2012-06-20 23:00:00,18358.0 -2012-06-21 00:00:00,16927.0 -2012-06-19 01:00:00,15494.0 -2012-06-19 02:00:00,14320.0 -2012-06-19 03:00:00,13399.0 -2012-06-19 04:00:00,12693.0 -2012-06-19 05:00:00,12319.0 -2012-06-19 06:00:00,12303.0 -2012-06-19 07:00:00,12591.0 -2012-06-19 08:00:00,13776.0 -2012-06-19 09:00:00,15230.0 -2012-06-19 10:00:00,16599.0 -2012-06-19 11:00:00,17733.0 -2012-06-19 12:00:00,18837.0 -2012-06-19 13:00:00,19704.0 -2012-06-19 14:00:00,20405.0 -2012-06-19 15:00:00,20969.0 -2012-06-19 16:00:00,21273.0 -2012-06-19 17:00:00,21368.0 -2012-06-19 18:00:00,21391.0 -2012-06-19 19:00:00,21165.0 -2012-06-19 20:00:00,20684.0 -2012-06-19 21:00:00,20034.0 -2012-06-19 22:00:00,19424.0 -2012-06-19 23:00:00,18944.0 -2012-06-20 00:00:00,17522.0 -2012-06-18 01:00:00,12279.0 -2012-06-18 02:00:00,11310.0 -2012-06-18 03:00:00,10742.0 -2012-06-18 04:00:00,10355.0 -2012-06-18 05:00:00,10280.0 -2012-06-18 06:00:00,10498.0 -2012-06-18 07:00:00,11101.0 -2012-06-18 08:00:00,12369.0 -2012-06-18 09:00:00,13742.0 -2012-06-18 10:00:00,14933.0 -2012-06-18 11:00:00,16253.0 -2012-06-18 12:00:00,17641.0 -2012-06-18 13:00:00,18686.0 -2012-06-18 14:00:00,19472.0 -2012-06-18 15:00:00,20156.0 -2012-06-18 16:00:00,20495.0 -2012-06-18 17:00:00,20633.0 -2012-06-18 18:00:00,20632.0 -2012-06-18 19:00:00,20458.0 -2012-06-18 20:00:00,19983.0 -2012-06-18 21:00:00,19299.0 -2012-06-18 22:00:00,18830.0 -2012-06-18 23:00:00,18431.0 -2012-06-19 00:00:00,17011.0 -2012-06-17 01:00:00,12564.0 -2012-06-17 02:00:00,11587.0 -2012-06-17 03:00:00,10804.0 -2012-06-17 04:00:00,10293.0 -2012-06-17 05:00:00,9906.0 -2012-06-17 06:00:00,9764.0 -2012-06-17 07:00:00,9563.0 -2012-06-17 08:00:00,9523.0 -2012-06-17 09:00:00,10085.0 -2012-06-17 10:00:00,10999.0 -2012-06-17 11:00:00,12144.0 -2012-06-17 12:00:00,13082.0 -2012-06-17 13:00:00,13809.0 -2012-06-17 14:00:00,14223.0 -2012-06-17 15:00:00,14482.0 -2012-06-17 16:00:00,14791.0 -2012-06-17 17:00:00,15179.0 -2012-06-17 18:00:00,15431.0 -2012-06-17 19:00:00,15521.0 -2012-06-17 20:00:00,15208.0 -2012-06-17 21:00:00,14726.0 -2012-06-17 22:00:00,14407.0 -2012-06-17 23:00:00,14267.0 -2012-06-18 00:00:00,13358.0 -2012-06-16 01:00:00,13163.0 -2012-06-16 02:00:00,12053.0 -2012-06-16 03:00:00,11158.0 -2012-06-16 04:00:00,10600.0 -2012-06-16 05:00:00,10211.0 -2012-06-16 06:00:00,10064.0 -2012-06-16 07:00:00,10059.0 -2012-06-16 08:00:00,10299.0 -2012-06-16 09:00:00,11097.0 -2012-06-16 10:00:00,12158.0 -2012-06-16 11:00:00,13327.0 -2012-06-16 12:00:00,14520.0 -2012-06-16 13:00:00,15395.0 -2012-06-16 14:00:00,16015.0 -2012-06-16 15:00:00,16319.0 -2012-06-16 16:00:00,16725.0 -2012-06-16 17:00:00,17135.0 -2012-06-16 18:00:00,17310.0 -2012-06-16 19:00:00,17229.0 -2012-06-16 20:00:00,16645.0 -2012-06-16 21:00:00,15912.0 -2012-06-16 22:00:00,15488.0 -2012-06-16 23:00:00,14899.0 -2012-06-17 00:00:00,13782.0 -2012-06-15 01:00:00,11436.0 -2012-06-15 02:00:00,10574.0 -2012-06-15 03:00:00,9954.0 -2012-06-15 04:00:00,9489.0 -2012-06-15 05:00:00,9203.0 -2012-06-15 06:00:00,9278.0 -2012-06-15 07:00:00,9563.0 -2012-06-15 08:00:00,10528.0 -2012-06-15 09:00:00,11762.0 -2012-06-15 10:00:00,12782.0 -2012-06-15 11:00:00,13677.0 -2012-06-15 12:00:00,14630.0 -2012-06-15 13:00:00,15389.0 -2012-06-15 14:00:00,16085.0 -2012-06-15 15:00:00,16830.0 -2012-06-15 16:00:00,17442.0 -2012-06-15 17:00:00,17860.0 -2012-06-15 18:00:00,18155.0 -2012-06-15 19:00:00,18054.0 -2012-06-15 20:00:00,17548.0 -2012-06-15 21:00:00,16804.0 -2012-06-15 22:00:00,16295.0 -2012-06-15 23:00:00,15798.0 -2012-06-16 00:00:00,14517.0 -2012-06-14 01:00:00,10049.0 -2012-06-14 02:00:00,9363.0 -2012-06-14 03:00:00,8959.0 -2012-06-14 04:00:00,8676.0 -2012-06-14 05:00:00,8605.0 -2012-06-14 06:00:00,8706.0 -2012-06-14 07:00:00,8989.0 -2012-06-14 08:00:00,9919.0 -2012-06-14 09:00:00,10956.0 -2012-06-14 10:00:00,11712.0 -2012-06-14 11:00:00,12279.0 -2012-06-14 12:00:00,12877.0 -2012-06-14 13:00:00,13355.0 -2012-06-14 14:00:00,13772.0 -2012-06-14 15:00:00,14238.0 -2012-06-14 16:00:00,14601.0 -2012-06-14 17:00:00,14835.0 -2012-06-14 18:00:00,14996.0 -2012-06-14 19:00:00,14899.0 -2012-06-14 20:00:00,14415.0 -2012-06-14 21:00:00,13954.0 -2012-06-14 22:00:00,13809.0 -2012-06-14 23:00:00,13594.0 -2012-06-15 00:00:00,12567.0 -2012-06-13 01:00:00,10395.0 -2012-06-13 02:00:00,9641.0 -2012-06-13 03:00:00,9132.0 -2012-06-13 04:00:00,8804.0 -2012-06-13 05:00:00,8667.0 -2012-06-13 06:00:00,8767.0 -2012-06-13 07:00:00,9062.0 -2012-06-13 08:00:00,10033.0 -2012-06-13 09:00:00,11079.0 -2012-06-13 10:00:00,11715.0 -2012-06-13 11:00:00,12188.0 -2012-06-13 12:00:00,12562.0 -2012-06-13 13:00:00,12775.0 -2012-06-13 14:00:00,12897.0 -2012-06-13 15:00:00,13023.0 -2012-06-13 16:00:00,12861.0 -2012-06-13 17:00:00,12757.0 -2012-06-13 18:00:00,12609.0 -2012-06-13 19:00:00,12291.0 -2012-06-13 20:00:00,11878.0 -2012-06-13 21:00:00,11621.0 -2012-06-13 22:00:00,11717.0 -2012-06-13 23:00:00,11720.0 -2012-06-14 00:00:00,10942.0 -2012-06-12 01:00:00,13461.0 -2012-06-12 02:00:00,12210.0 -2012-06-12 03:00:00,11294.0 -2012-06-12 04:00:00,10586.0 -2012-06-12 05:00:00,10167.0 -2012-06-12 06:00:00,10041.0 -2012-06-12 07:00:00,10171.0 -2012-06-12 08:00:00,11096.0 -2012-06-12 09:00:00,12102.0 -2012-06-12 10:00:00,12810.0 -2012-06-12 11:00:00,13268.0 -2012-06-12 12:00:00,13662.0 -2012-06-12 13:00:00,13829.0 -2012-06-12 14:00:00,14011.0 -2012-06-12 15:00:00,14289.0 -2012-06-12 16:00:00,14432.0 -2012-06-12 17:00:00,14519.0 -2012-06-12 18:00:00,14460.0 -2012-06-12 19:00:00,14149.0 -2012-06-12 20:00:00,13528.0 -2012-06-12 21:00:00,12873.0 -2012-06-12 22:00:00,12576.0 -2012-06-12 23:00:00,12381.0 -2012-06-13 00:00:00,11405.0 -2012-06-11 01:00:00,13223.0 -2012-06-11 02:00:00,12273.0 -2012-06-11 03:00:00,11588.0 -2012-06-11 04:00:00,11034.0 -2012-06-11 05:00:00,10757.0 -2012-06-11 06:00:00,10779.0 -2012-06-11 07:00:00,11136.0 -2012-06-11 08:00:00,12321.0 -2012-06-11 09:00:00,13566.0 -2012-06-11 10:00:00,14535.0 -2012-06-11 11:00:00,15665.0 -2012-06-11 12:00:00,16664.0 -2012-06-11 13:00:00,17239.0 -2012-06-11 14:00:00,17722.0 -2012-06-11 15:00:00,18282.0 -2012-06-11 16:00:00,18565.0 -2012-06-11 17:00:00,18762.0 -2012-06-11 18:00:00,18738.0 -2012-06-11 19:00:00,18313.0 -2012-06-11 20:00:00,17670.0 -2012-06-11 21:00:00,16987.0 -2012-06-11 22:00:00,16613.0 -2012-06-11 23:00:00,16220.0 -2012-06-12 00:00:00,14907.0 -2012-06-10 01:00:00,12206.0 -2012-06-10 02:00:00,11299.0 -2012-06-10 03:00:00,10570.0 -2012-06-10 04:00:00,10041.0 -2012-06-10 05:00:00,9635.0 -2012-06-10 06:00:00,9475.0 -2012-06-10 07:00:00,9107.0 -2012-06-10 08:00:00,9336.0 -2012-06-10 09:00:00,10104.0 -2012-06-10 10:00:00,11298.0 -2012-06-10 11:00:00,12558.0 -2012-06-10 12:00:00,13714.0 -2012-06-10 13:00:00,14616.0 -2012-06-10 14:00:00,15187.0 -2012-06-10 15:00:00,15615.0 -2012-06-10 16:00:00,15980.0 -2012-06-10 17:00:00,16296.0 -2012-06-10 18:00:00,16461.0 -2012-06-10 19:00:00,16494.0 -2012-06-10 20:00:00,16079.0 -2012-06-10 21:00:00,15504.0 -2012-06-10 22:00:00,15304.0 -2012-06-10 23:00:00,15194.0 -2012-06-11 00:00:00,14263.0 -2012-06-09 01:00:00,12061.0 -2012-06-09 02:00:00,11096.0 -2012-06-09 03:00:00,10302.0 -2012-06-09 04:00:00,9808.0 -2012-06-09 05:00:00,9489.0 -2012-06-09 06:00:00,9245.0 -2012-06-09 07:00:00,9107.0 -2012-06-09 08:00:00,9567.0 -2012-06-09 09:00:00,10470.0 -2012-06-09 10:00:00,11558.0 -2012-06-09 11:00:00,12648.0 -2012-06-09 12:00:00,13548.0 -2012-06-09 13:00:00,14125.0 -2012-06-09 14:00:00,14693.0 -2012-06-09 15:00:00,15054.0 -2012-06-09 16:00:00,15484.0 -2012-06-09 17:00:00,15844.0 -2012-06-09 18:00:00,16076.0 -2012-06-09 19:00:00,15993.0 -2012-06-09 20:00:00,15696.0 -2012-06-09 21:00:00,15091.0 -2012-06-09 22:00:00,14606.0 -2012-06-09 23:00:00,14253.0 -2012-06-10 00:00:00,13297.0 -2012-06-08 01:00:00,10424.0 -2012-06-08 02:00:00,9666.0 -2012-06-08 03:00:00,9161.0 -2012-06-08 04:00:00,8810.0 -2012-06-08 05:00:00,8633.0 -2012-06-08 06:00:00,8720.0 -2012-06-08 07:00:00,9035.0 -2012-06-08 08:00:00,9975.0 -2012-06-08 09:00:00,11116.0 -2012-06-08 10:00:00,12064.0 -2012-06-08 11:00:00,12799.0 -2012-06-08 12:00:00,13521.0 -2012-06-08 13:00:00,13992.0 -2012-06-08 14:00:00,14449.0 -2012-06-08 15:00:00,14958.0 -2012-06-08 16:00:00,15309.0 -2012-06-08 17:00:00,15638.0 -2012-06-08 18:00:00,15865.0 -2012-06-08 19:00:00,15815.0 -2012-06-08 20:00:00,15424.0 -2012-06-08 21:00:00,14873.0 -2012-06-08 22:00:00,14532.0 -2012-06-08 23:00:00,14277.0 -2012-06-09 00:00:00,13254.0 -2012-06-07 01:00:00,9898.0 -2012-06-07 02:00:00,9218.0 -2012-06-07 03:00:00,8762.0 -2012-06-07 04:00:00,8487.0 -2012-06-07 05:00:00,8352.0 -2012-06-07 06:00:00,8453.0 -2012-06-07 07:00:00,8762.0 -2012-06-07 08:00:00,9711.0 -2012-06-07 09:00:00,10756.0 -2012-06-07 10:00:00,11525.0 -2012-06-07 11:00:00,12080.0 -2012-06-07 12:00:00,12600.0 -2012-06-07 13:00:00,12883.0 -2012-06-07 14:00:00,13152.0 -2012-06-07 15:00:00,13443.0 -2012-06-07 16:00:00,13552.0 -2012-06-07 17:00:00,13639.0 -2012-06-07 18:00:00,13679.0 -2012-06-07 19:00:00,13563.0 -2012-06-07 20:00:00,13132.0 -2012-06-07 21:00:00,12715.0 -2012-06-07 22:00:00,12631.0 -2012-06-07 23:00:00,12459.0 -2012-06-08 00:00:00,11462.0 -2012-06-06 01:00:00,9682.0 -2012-06-06 02:00:00,9027.0 -2012-06-06 03:00:00,8615.0 -2012-06-06 04:00:00,8369.0 -2012-06-06 05:00:00,8254.0 -2012-06-06 06:00:00,8393.0 -2012-06-06 07:00:00,8735.0 -2012-06-06 08:00:00,9687.0 -2012-06-06 09:00:00,10707.0 -2012-06-06 10:00:00,11443.0 -2012-06-06 11:00:00,11961.0 -2012-06-06 12:00:00,12306.0 -2012-06-06 13:00:00,12539.0 -2012-06-06 14:00:00,12664.0 -2012-06-06 15:00:00,12827.0 -2012-06-06 16:00:00,12882.0 -2012-06-06 17:00:00,12880.0 -2012-06-06 18:00:00,12853.0 -2012-06-06 19:00:00,12666.0 -2012-06-06 20:00:00,12233.0 -2012-06-06 21:00:00,11892.0 -2012-06-06 22:00:00,11857.0 -2012-06-06 23:00:00,11725.0 -2012-06-07 00:00:00,10858.0 -2012-06-05 01:00:00,9863.0 -2012-06-05 02:00:00,9188.0 -2012-06-05 03:00:00,8767.0 -2012-06-05 04:00:00,8477.0 -2012-06-05 05:00:00,8346.0 -2012-06-05 06:00:00,8498.0 -2012-06-05 07:00:00,8847.0 -2012-06-05 08:00:00,9795.0 -2012-06-05 09:00:00,10846.0 -2012-06-05 10:00:00,11482.0 -2012-06-05 11:00:00,11885.0 -2012-06-05 12:00:00,12252.0 -2012-06-05 13:00:00,12388.0 -2012-06-05 14:00:00,12473.0 -2012-06-05 15:00:00,12545.0 -2012-06-05 16:00:00,12517.0 -2012-06-05 17:00:00,12484.0 -2012-06-05 18:00:00,12399.0 -2012-06-05 19:00:00,12182.0 -2012-06-05 20:00:00,11769.0 -2012-06-05 21:00:00,11426.0 -2012-06-05 22:00:00,11478.0 -2012-06-05 23:00:00,11400.0 -2012-06-06 00:00:00,10586.0 -2012-06-04 01:00:00,9667.0 -2012-06-04 02:00:00,9094.0 -2012-06-04 03:00:00,8724.0 -2012-06-04 04:00:00,8469.0 -2012-06-04 05:00:00,8388.0 -2012-06-04 06:00:00,8566.0 -2012-06-04 07:00:00,9083.0 -2012-06-04 08:00:00,9978.0 -2012-06-04 09:00:00,10958.0 -2012-06-04 10:00:00,11538.0 -2012-06-04 11:00:00,12021.0 -2012-06-04 12:00:00,12464.0 -2012-06-04 13:00:00,12744.0 -2012-06-04 14:00:00,12923.0 -2012-06-04 15:00:00,13135.0 -2012-06-04 16:00:00,13186.0 -2012-06-04 17:00:00,13192.0 -2012-06-04 18:00:00,13104.0 -2012-06-04 19:00:00,12816.0 -2012-06-04 20:00:00,12313.0 -2012-06-04 21:00:00,11836.0 -2012-06-04 22:00:00,11836.0 -2012-06-04 23:00:00,11697.0 -2012-06-05 00:00:00,10838.0 -2012-06-03 01:00:00,9089.0 -2012-06-03 02:00:00,8511.0 -2012-06-03 03:00:00,8135.0 -2012-06-03 04:00:00,7930.0 -2012-06-03 05:00:00,7731.0 -2012-06-03 06:00:00,7751.0 -2012-06-03 07:00:00,7521.0 -2012-06-03 08:00:00,7595.0 -2012-06-03 09:00:00,7989.0 -2012-06-03 10:00:00,8602.0 -2012-06-03 11:00:00,9161.0 -2012-06-03 12:00:00,9617.0 -2012-06-03 13:00:00,9966.0 -2012-06-03 14:00:00,10185.0 -2012-06-03 15:00:00,10368.0 -2012-06-03 16:00:00,10535.0 -2012-06-03 17:00:00,10717.0 -2012-06-03 18:00:00,10727.0 -2012-06-03 19:00:00,10686.0 -2012-06-03 20:00:00,10604.0 -2012-06-03 21:00:00,10599.0 -2012-06-03 22:00:00,10964.0 -2012-06-03 23:00:00,11017.0 -2012-06-04 00:00:00,10395.0 -2012-06-02 01:00:00,9239.0 -2012-06-02 02:00:00,8639.0 -2012-06-02 03:00:00,8310.0 -2012-06-02 04:00:00,8035.0 -2012-06-02 05:00:00,7948.0 -2012-06-02 06:00:00,7963.0 -2012-06-02 07:00:00,7930.0 -2012-06-02 08:00:00,8185.0 -2012-06-02 09:00:00,8651.0 -2012-06-02 10:00:00,9236.0 -2012-06-02 11:00:00,9655.0 -2012-06-02 12:00:00,10046.0 -2012-06-02 13:00:00,10157.0 -2012-06-02 14:00:00,10195.0 -2012-06-02 15:00:00,10093.0 -2012-06-02 16:00:00,10061.0 -2012-06-02 17:00:00,10032.0 -2012-06-02 18:00:00,10058.0 -2012-06-02 19:00:00,10011.0 -2012-06-02 20:00:00,9879.0 -2012-06-02 21:00:00,9783.0 -2012-06-02 22:00:00,10113.0 -2012-06-02 23:00:00,10218.0 -2012-06-03 00:00:00,9675.0 -2012-06-01 01:00:00,9341.0 -2012-06-01 02:00:00,8799.0 -2012-06-01 03:00:00,8451.0 -2012-06-01 04:00:00,8240.0 -2012-06-01 05:00:00,8139.0 -2012-06-01 06:00:00,8313.0 -2012-06-01 07:00:00,8788.0 -2012-06-01 08:00:00,9645.0 -2012-06-01 09:00:00,10415.0 -2012-06-01 10:00:00,10842.0 -2012-06-01 11:00:00,11055.0 -2012-06-01 12:00:00,11217.0 -2012-06-01 13:00:00,11267.0 -2012-06-01 14:00:00,11255.0 -2012-06-01 15:00:00,11275.0 -2012-06-01 16:00:00,11187.0 -2012-06-01 17:00:00,11052.0 -2012-06-01 18:00:00,10903.0 -2012-06-01 19:00:00,10723.0 -2012-06-01 20:00:00,10464.0 -2012-06-01 21:00:00,10325.0 -2012-06-01 22:00:00,10583.0 -2012-06-01 23:00:00,10621.0 -2012-06-02 00:00:00,9993.0 -2012-05-31 01:00:00,9385.0 -2012-05-31 02:00:00,8805.0 -2012-05-31 03:00:00,8455.0 -2012-05-31 04:00:00,8236.0 -2012-05-31 05:00:00,8113.0 -2012-05-31 06:00:00,8282.0 -2012-05-31 07:00:00,8750.0 -2012-05-31 08:00:00,9580.0 -2012-05-31 09:00:00,10466.0 -2012-05-31 10:00:00,10987.0 -2012-05-31 11:00:00,11258.0 -2012-05-31 12:00:00,11461.0 -2012-05-31 13:00:00,11547.0 -2012-05-31 14:00:00,11600.0 -2012-05-31 15:00:00,11583.0 -2012-05-31 16:00:00,11492.0 -2012-05-31 17:00:00,11336.0 -2012-05-31 18:00:00,11216.0 -2012-05-31 19:00:00,11086.0 -2012-05-31 20:00:00,10982.0 -2012-05-31 21:00:00,11101.0 -2012-05-31 22:00:00,11248.0 -2012-05-31 23:00:00,10900.0 -2012-06-01 00:00:00,10130.0 -2012-05-30 01:00:00,10738.0 -2012-05-30 02:00:00,9908.0 -2012-05-30 03:00:00,9352.0 -2012-05-30 04:00:00,8991.0 -2012-05-30 05:00:00,8794.0 -2012-05-30 06:00:00,8880.0 -2012-05-30 07:00:00,9188.0 -2012-05-30 08:00:00,10129.0 -2012-05-30 09:00:00,11102.0 -2012-05-30 10:00:00,11761.0 -2012-05-30 11:00:00,12110.0 -2012-05-30 12:00:00,12330.0 -2012-05-30 13:00:00,12369.0 -2012-05-30 14:00:00,12411.0 -2012-05-30 15:00:00,12427.0 -2012-05-30 16:00:00,12361.0 -2012-05-30 17:00:00,12177.0 -2012-05-30 18:00:00,11967.0 -2012-05-30 19:00:00,11718.0 -2012-05-30 20:00:00,11290.0 -2012-05-30 21:00:00,10962.0 -2012-05-30 22:00:00,11188.0 -2012-05-30 23:00:00,11026.0 -2012-05-31 00:00:00,10223.0 -2012-05-29 01:00:00,12803.0 -2012-05-29 02:00:00,11809.0 -2012-05-29 03:00:00,11115.0 -2012-05-29 04:00:00,10567.0 -2012-05-29 05:00:00,10171.0 -2012-05-29 06:00:00,10084.0 -2012-05-29 07:00:00,10296.0 -2012-05-29 08:00:00,11254.0 -2012-05-29 09:00:00,12362.0 -2012-05-29 10:00:00,13204.0 -2012-05-29 11:00:00,13925.0 -2012-05-29 12:00:00,14541.0 -2012-05-29 13:00:00,14951.0 -2012-05-29 14:00:00,15245.0 -2012-05-29 15:00:00,15703.0 -2012-05-29 16:00:00,15949.0 -2012-05-29 17:00:00,16074.0 -2012-05-29 18:00:00,16047.0 -2012-05-29 19:00:00,15677.0 -2012-05-29 20:00:00,14966.0 -2012-05-29 21:00:00,14075.0 -2012-05-29 22:00:00,13639.0 -2012-05-29 23:00:00,13112.0 -2012-05-30 00:00:00,11902.0 -2012-05-28 01:00:00,13087.0 -2012-05-28 02:00:00,12174.0 -2012-05-28 03:00:00,11411.0 -2012-05-28 04:00:00,10871.0 -2012-05-28 05:00:00,10465.0 -2012-05-28 06:00:00,10285.0 -2012-05-28 07:00:00,10133.0 -2012-05-28 08:00:00,10354.0 -2012-05-28 09:00:00,11200.0 -2012-05-28 10:00:00,12264.0 -2012-05-28 11:00:00,13464.0 -2012-05-28 12:00:00,14598.0 -2012-05-28 13:00:00,15291.0 -2012-05-28 14:00:00,15796.0 -2012-05-28 15:00:00,16069.0 -2012-05-28 16:00:00,16200.0 -2012-05-28 17:00:00,16494.0 -2012-05-28 18:00:00,16641.0 -2012-05-28 19:00:00,16518.0 -2012-05-28 20:00:00,16240.0 -2012-05-28 21:00:00,15828.0 -2012-05-28 22:00:00,15829.0 -2012-05-28 23:00:00,15418.0 -2012-05-29 00:00:00,14133.0 -2012-05-27 01:00:00,9942.0 -2012-05-27 02:00:00,9404.0 -2012-05-27 03:00:00,8914.0 -2012-05-27 04:00:00,8654.0 -2012-05-27 05:00:00,8579.0 -2012-05-27 06:00:00,8506.0 -2012-05-27 07:00:00,8419.0 -2012-05-27 08:00:00,8489.0 -2012-05-27 09:00:00,9246.0 -2012-05-27 10:00:00,10397.0 -2012-05-27 11:00:00,11669.0 -2012-05-27 12:00:00,12872.0 -2012-05-27 13:00:00,13944.0 -2012-05-27 14:00:00,14807.0 -2012-05-27 15:00:00,15516.0 -2012-05-27 16:00:00,15995.0 -2012-05-27 17:00:00,16343.0 -2012-05-27 18:00:00,16557.0 -2012-05-27 19:00:00,16547.0 -2012-05-27 20:00:00,16230.0 -2012-05-27 21:00:00,15665.0 -2012-05-27 22:00:00,15339.0 -2012-05-27 23:00:00,15014.0 -2012-05-28 00:00:00,14154.0 -2012-05-26 01:00:00,10261.0 -2012-05-26 02:00:00,9500.0 -2012-05-26 03:00:00,9070.0 -2012-05-26 04:00:00,8761.0 -2012-05-26 05:00:00,8597.0 -2012-05-26 06:00:00,8539.0 -2012-05-26 07:00:00,8599.0 -2012-05-26 08:00:00,8795.0 -2012-05-26 09:00:00,9143.0 -2012-05-26 10:00:00,9606.0 -2012-05-26 11:00:00,10149.0 -2012-05-26 12:00:00,10550.0 -2012-05-26 13:00:00,10869.0 -2012-05-26 14:00:00,11239.0 -2012-05-26 15:00:00,11603.0 -2012-05-26 16:00:00,12004.0 -2012-05-26 17:00:00,12431.0 -2012-05-26 18:00:00,12551.0 -2012-05-26 19:00:00,12176.0 -2012-05-26 20:00:00,11564.0 -2012-05-26 21:00:00,11112.0 -2012-05-26 22:00:00,11188.0 -2012-05-26 23:00:00,11089.0 -2012-05-27 00:00:00,10557.0 -2012-05-25 01:00:00,12937.0 -2012-05-25 02:00:00,12008.0 -2012-05-25 03:00:00,11333.0 -2012-05-25 04:00:00,10793.0 -2012-05-25 05:00:00,10360.0 -2012-05-25 06:00:00,10200.0 -2012-05-25 07:00:00,10381.0 -2012-05-25 08:00:00,11263.0 -2012-05-25 09:00:00,12318.0 -2012-05-25 10:00:00,13046.0 -2012-05-25 11:00:00,13596.0 -2012-05-25 12:00:00,14049.0 -2012-05-25 13:00:00,14321.0 -2012-05-25 14:00:00,14482.0 -2012-05-25 15:00:00,14659.0 -2012-05-25 16:00:00,14585.0 -2012-05-25 17:00:00,14445.0 -2012-05-25 18:00:00,14215.0 -2012-05-25 19:00:00,13697.0 -2012-05-25 20:00:00,12907.0 -2012-05-25 21:00:00,12351.0 -2012-05-25 22:00:00,12384.0 -2012-05-25 23:00:00,12017.0 -2012-05-26 00:00:00,11191.0 -2012-05-24 01:00:00,10316.0 -2012-05-24 02:00:00,9644.0 -2012-05-24 03:00:00,9199.0 -2012-05-24 04:00:00,8877.0 -2012-05-24 05:00:00,8751.0 -2012-05-24 06:00:00,8866.0 -2012-05-24 07:00:00,9278.0 -2012-05-24 08:00:00,10401.0 -2012-05-24 09:00:00,11563.0 -2012-05-24 10:00:00,12479.0 -2012-05-24 11:00:00,13128.0 -2012-05-24 12:00:00,13751.0 -2012-05-24 13:00:00,14211.0 -2012-05-24 14:00:00,14654.0 -2012-05-24 15:00:00,15184.0 -2012-05-24 16:00:00,15584.0 -2012-05-24 17:00:00,15984.0 -2012-05-24 18:00:00,16261.0 -2012-05-24 19:00:00,16186.0 -2012-05-24 20:00:00,15780.0 -2012-05-24 21:00:00,15409.0 -2012-05-24 22:00:00,15517.0 -2012-05-24 23:00:00,15212.0 -2012-05-25 00:00:00,14130.0 -2012-05-23 01:00:00,9457.0 -2012-05-23 02:00:00,8856.0 -2012-05-23 03:00:00,8449.0 -2012-05-23 04:00:00,8235.0 -2012-05-23 05:00:00,8166.0 -2012-05-23 06:00:00,8308.0 -2012-05-23 07:00:00,8678.0 -2012-05-23 08:00:00,9593.0 -2012-05-23 09:00:00,10551.0 -2012-05-23 10:00:00,11164.0 -2012-05-23 11:00:00,11655.0 -2012-05-23 12:00:00,12032.0 -2012-05-23 13:00:00,12302.0 -2012-05-23 14:00:00,12512.0 -2012-05-23 15:00:00,12819.0 -2012-05-23 16:00:00,12949.0 -2012-05-23 17:00:00,13091.0 -2012-05-23 18:00:00,13209.0 -2012-05-23 19:00:00,13055.0 -2012-05-23 20:00:00,12699.0 -2012-05-23 21:00:00,12353.0 -2012-05-23 22:00:00,12477.0 -2012-05-23 23:00:00,12234.0 -2012-05-24 00:00:00,11298.0 -2012-05-22 01:00:00,9363.0 -2012-05-22 02:00:00,8798.0 -2012-05-22 03:00:00,8443.0 -2012-05-22 04:00:00,8211.0 -2012-05-22 05:00:00,8126.0 -2012-05-22 06:00:00,8283.0 -2012-05-22 07:00:00,8674.0 -2012-05-22 08:00:00,9622.0 -2012-05-22 09:00:00,10571.0 -2012-05-22 10:00:00,11170.0 -2012-05-22 11:00:00,11545.0 -2012-05-22 12:00:00,11811.0 -2012-05-22 13:00:00,11958.0 -2012-05-22 14:00:00,12069.0 -2012-05-22 15:00:00,12164.0 -2012-05-22 16:00:00,12167.0 -2012-05-22 17:00:00,12161.0 -2012-05-22 18:00:00,12094.0 -2012-05-22 19:00:00,11944.0 -2012-05-22 20:00:00,11580.0 -2012-05-22 21:00:00,11308.0 -2012-05-22 22:00:00,11556.0 -2012-05-22 23:00:00,11257.0 -2012-05-23 00:00:00,10393.0 -2012-05-21 01:00:00,10758.0 -2012-05-21 02:00:00,9960.0 -2012-05-21 03:00:00,9413.0 -2012-05-21 04:00:00,9051.0 -2012-05-21 05:00:00,8850.0 -2012-05-21 06:00:00,8938.0 -2012-05-21 07:00:00,9312.0 -2012-05-21 08:00:00,10153.0 -2012-05-21 09:00:00,10895.0 -2012-05-21 10:00:00,11377.0 -2012-05-21 11:00:00,11596.0 -2012-05-21 12:00:00,11809.0 -2012-05-21 13:00:00,11846.0 -2012-05-21 14:00:00,11892.0 -2012-05-21 15:00:00,12008.0 -2012-05-21 16:00:00,11948.0 -2012-05-21 17:00:00,11893.0 -2012-05-21 18:00:00,11834.0 -2012-05-21 19:00:00,11650.0 -2012-05-21 20:00:00,11338.0 -2012-05-21 21:00:00,11102.0 -2012-05-21 22:00:00,11366.0 -2012-05-21 23:00:00,11106.0 -2012-05-22 00:00:00,10237.0 -2012-05-20 01:00:00,11200.0 -2012-05-20 02:00:00,10430.0 -2012-05-20 03:00:00,9895.0 -2012-05-20 04:00:00,9409.0 -2012-05-20 05:00:00,9101.0 -2012-05-20 06:00:00,8891.0 -2012-05-20 07:00:00,8683.0 -2012-05-20 08:00:00,8814.0 -2012-05-20 09:00:00,9470.0 -2012-05-20 10:00:00,10534.0 -2012-05-20 11:00:00,11600.0 -2012-05-20 12:00:00,12694.0 -2012-05-20 13:00:00,13484.0 -2012-05-20 14:00:00,14032.0 -2012-05-20 15:00:00,14431.0 -2012-05-20 16:00:00,14778.0 -2012-05-20 17:00:00,14981.0 -2012-05-20 18:00:00,14975.0 -2012-05-20 19:00:00,14384.0 -2012-05-20 20:00:00,13756.0 -2012-05-20 21:00:00,13334.0 -2012-05-20 22:00:00,13418.0 -2012-05-20 23:00:00,13003.0 -2012-05-21 00:00:00,11951.0 -2012-05-19 01:00:00,10155.0 -2012-05-19 02:00:00,9469.0 -2012-05-19 03:00:00,8955.0 -2012-05-19 04:00:00,8601.0 -2012-05-19 05:00:00,8424.0 -2012-05-19 06:00:00,8352.0 -2012-05-19 07:00:00,8376.0 -2012-05-19 08:00:00,8625.0 -2012-05-19 09:00:00,9329.0 -2012-05-19 10:00:00,10206.0 -2012-05-19 11:00:00,11062.0 -2012-05-19 12:00:00,11874.0 -2012-05-19 13:00:00,12478.0 -2012-05-19 14:00:00,12922.0 -2012-05-19 15:00:00,13195.0 -2012-05-19 16:00:00,13469.0 -2012-05-19 17:00:00,13696.0 -2012-05-19 18:00:00,13914.0 -2012-05-19 19:00:00,13917.0 -2012-05-19 20:00:00,13630.0 -2012-05-19 21:00:00,13242.0 -2012-05-19 22:00:00,13185.0 -2012-05-19 23:00:00,12879.0 -2012-05-20 00:00:00,12069.0 -2012-05-18 01:00:00,9444.0 -2012-05-18 02:00:00,8807.0 -2012-05-18 03:00:00,8449.0 -2012-05-18 04:00:00,8206.0 -2012-05-18 05:00:00,8113.0 -2012-05-18 06:00:00,8241.0 -2012-05-18 07:00:00,8612.0 -2012-05-18 08:00:00,9499.0 -2012-05-18 09:00:00,10499.0 -2012-05-18 10:00:00,11127.0 -2012-05-18 11:00:00,11572.0 -2012-05-18 12:00:00,11998.0 -2012-05-18 13:00:00,12222.0 -2012-05-18 14:00:00,12403.0 -2012-05-18 15:00:00,12641.0 -2012-05-18 16:00:00,12774.0 -2012-05-18 17:00:00,12867.0 -2012-05-18 18:00:00,12900.0 -2012-05-18 19:00:00,12780.0 -2012-05-18 20:00:00,12405.0 -2012-05-18 21:00:00,12069.0 -2012-05-18 22:00:00,12168.0 -2012-05-18 23:00:00,11912.0 -2012-05-19 00:00:00,11076.0 -2012-05-17 01:00:00,9211.0 -2012-05-17 02:00:00,8712.0 -2012-05-17 03:00:00,8353.0 -2012-05-17 04:00:00,8136.0 -2012-05-17 05:00:00,8068.0 -2012-05-17 06:00:00,8222.0 -2012-05-17 07:00:00,8655.0 -2012-05-17 08:00:00,9531.0 -2012-05-17 09:00:00,10403.0 -2012-05-17 10:00:00,10962.0 -2012-05-17 11:00:00,11335.0 -2012-05-17 12:00:00,11611.0 -2012-05-17 13:00:00,11758.0 -2012-05-17 14:00:00,11858.0 -2012-05-17 15:00:00,11996.0 -2012-05-17 16:00:00,12020.0 -2012-05-17 17:00:00,11992.0 -2012-05-17 18:00:00,11868.0 -2012-05-17 19:00:00,11606.0 -2012-05-17 20:00:00,11255.0 -2012-05-17 21:00:00,11111.0 -2012-05-17 22:00:00,11474.0 -2012-05-17 23:00:00,11171.0 -2012-05-18 00:00:00,10303.0 -2012-05-16 01:00:00,10315.0 -2012-05-16 02:00:00,9384.0 -2012-05-16 03:00:00,8812.0 -2012-05-16 04:00:00,8421.0 -2012-05-16 05:00:00,8263.0 -2012-05-16 06:00:00,8337.0 -2012-05-16 07:00:00,8753.0 -2012-05-16 08:00:00,9553.0 -2012-05-16 09:00:00,10439.0 -2012-05-16 10:00:00,10898.0 -2012-05-16 11:00:00,11224.0 -2012-05-16 12:00:00,11427.0 -2012-05-16 13:00:00,11525.0 -2012-05-16 14:00:00,11578.0 -2012-05-16 15:00:00,11643.0 -2012-05-16 16:00:00,11592.0 -2012-05-16 17:00:00,11527.0 -2012-05-16 18:00:00,11424.0 -2012-05-16 19:00:00,11230.0 -2012-05-16 20:00:00,10885.0 -2012-05-16 21:00:00,10760.0 -2012-05-16 22:00:00,11136.0 -2012-05-16 23:00:00,10883.0 -2012-05-17 00:00:00,10069.0 -2012-05-15 01:00:00,9568.0 -2012-05-15 02:00:00,8957.0 -2012-05-15 03:00:00,8580.0 -2012-05-15 04:00:00,8297.0 -2012-05-15 05:00:00,8195.0 -2012-05-15 06:00:00,8349.0 -2012-05-15 07:00:00,8775.0 -2012-05-15 08:00:00,9754.0 -2012-05-15 09:00:00,10758.0 -2012-05-15 10:00:00,11418.0 -2012-05-15 11:00:00,11929.0 -2012-05-15 12:00:00,12396.0 -2012-05-15 13:00:00,12667.0 -2012-05-15 14:00:00,12866.0 -2012-05-15 15:00:00,13130.0 -2012-05-15 16:00:00,13288.0 -2012-05-15 17:00:00,13442.0 -2012-05-15 18:00:00,13468.0 -2012-05-15 19:00:00,13378.0 -2012-05-15 20:00:00,12996.0 -2012-05-15 21:00:00,12935.0 -2012-05-15 22:00:00,12958.0 -2012-05-15 23:00:00,12342.0 -2012-05-16 00:00:00,11296.0 -2012-05-14 01:00:00,8333.0 -2012-05-14 02:00:00,7953.0 -2012-05-14 03:00:00,7678.0 -2012-05-14 04:00:00,7586.0 -2012-05-14 05:00:00,7550.0 -2012-05-14 06:00:00,7824.0 -2012-05-14 07:00:00,8367.0 -2012-05-14 08:00:00,9330.0 -2012-05-14 09:00:00,10219.0 -2012-05-14 10:00:00,10904.0 -2012-05-14 11:00:00,11326.0 -2012-05-14 12:00:00,11690.0 -2012-05-14 13:00:00,11917.0 -2012-05-14 14:00:00,12039.0 -2012-05-14 15:00:00,12233.0 -2012-05-14 16:00:00,12256.0 -2012-05-14 17:00:00,12234.0 -2012-05-14 18:00:00,12246.0 -2012-05-14 19:00:00,12103.0 -2012-05-14 20:00:00,11769.0 -2012-05-14 21:00:00,11559.0 -2012-05-14 22:00:00,11878.0 -2012-05-14 23:00:00,11464.0 -2012-05-15 00:00:00,10506.0 -2012-05-13 01:00:00,8556.0 -2012-05-13 02:00:00,8100.0 -2012-05-13 03:00:00,7702.0 -2012-05-13 04:00:00,7542.0 -2012-05-13 05:00:00,7445.0 -2012-05-13 06:00:00,7432.0 -2012-05-13 07:00:00,7411.0 -2012-05-13 08:00:00,7382.0 -2012-05-13 09:00:00,7707.0 -2012-05-13 10:00:00,8147.0 -2012-05-13 11:00:00,8503.0 -2012-05-13 12:00:00,8718.0 -2012-05-13 13:00:00,8851.0 -2012-05-13 14:00:00,8884.0 -2012-05-13 15:00:00,8867.0 -2012-05-13 16:00:00,8870.0 -2012-05-13 17:00:00,8883.0 -2012-05-13 18:00:00,8934.0 -2012-05-13 19:00:00,8954.0 -2012-05-13 20:00:00,8967.0 -2012-05-13 21:00:00,9010.0 -2012-05-13 22:00:00,9607.0 -2012-05-13 23:00:00,9506.0 -2012-05-14 00:00:00,8985.0 -2012-05-12 01:00:00,9654.0 -2012-05-12 02:00:00,8981.0 -2012-05-12 03:00:00,8575.0 -2012-05-12 04:00:00,8271.0 -2012-05-12 05:00:00,8139.0 -2012-05-12 06:00:00,8149.0 -2012-05-12 07:00:00,8237.0 -2012-05-12 08:00:00,8410.0 -2012-05-12 09:00:00,8858.0 -2012-05-12 10:00:00,9331.0 -2012-05-12 11:00:00,9603.0 -2012-05-12 12:00:00,9792.0 -2012-05-12 13:00:00,9783.0 -2012-05-12 14:00:00,9690.0 -2012-05-12 15:00:00,9542.0 -2012-05-12 16:00:00,9391.0 -2012-05-12 17:00:00,9280.0 -2012-05-12 18:00:00,9239.0 -2012-05-12 19:00:00,9190.0 -2012-05-12 20:00:00,9208.0 -2012-05-12 21:00:00,9386.0 -2012-05-12 22:00:00,9772.0 -2012-05-12 23:00:00,9610.0 -2012-05-13 00:00:00,9146.0 -2012-05-11 01:00:00,9255.0 -2012-05-11 02:00:00,8769.0 -2012-05-11 03:00:00,8417.0 -2012-05-11 04:00:00,8227.0 -2012-05-11 05:00:00,8134.0 -2012-05-11 06:00:00,8335.0 -2012-05-11 07:00:00,8763.0 -2012-05-11 08:00:00,9596.0 -2012-05-11 09:00:00,10436.0 -2012-05-11 10:00:00,10914.0 -2012-05-11 11:00:00,11260.0 -2012-05-11 12:00:00,11487.0 -2012-05-11 13:00:00,11648.0 -2012-05-11 14:00:00,11695.0 -2012-05-11 15:00:00,11821.0 -2012-05-11 16:00:00,11834.0 -2012-05-11 17:00:00,11764.0 -2012-05-11 18:00:00,11721.0 -2012-05-11 19:00:00,11506.0 -2012-05-11 20:00:00,11214.0 -2012-05-11 21:00:00,11135.0 -2012-05-11 22:00:00,11510.0 -2012-05-11 23:00:00,11242.0 -2012-05-12 00:00:00,10469.0 -2012-05-10 01:00:00,9208.0 -2012-05-10 02:00:00,8701.0 -2012-05-10 03:00:00,8368.0 -2012-05-10 04:00:00,8137.0 -2012-05-10 05:00:00,8112.0 -2012-05-10 06:00:00,8328.0 -2012-05-10 07:00:00,8860.0 -2012-05-10 08:00:00,9731.0 -2012-05-10 09:00:00,10533.0 -2012-05-10 10:00:00,10930.0 -2012-05-10 11:00:00,11186.0 -2012-05-10 12:00:00,11386.0 -2012-05-10 13:00:00,11422.0 -2012-05-10 14:00:00,11469.0 -2012-05-10 15:00:00,11574.0 -2012-05-10 16:00:00,11511.0 -2012-05-10 17:00:00,11379.0 -2012-05-10 18:00:00,11271.0 -2012-05-10 19:00:00,11085.0 -2012-05-10 20:00:00,10834.0 -2012-05-10 21:00:00,10801.0 -2012-05-10 22:00:00,11217.0 -2012-05-10 23:00:00,10931.0 -2012-05-11 00:00:00,10140.0 -2012-05-09 01:00:00,9321.0 -2012-05-09 02:00:00,8729.0 -2012-05-09 03:00:00,8341.0 -2012-05-09 04:00:00,8134.0 -2012-05-09 05:00:00,8050.0 -2012-05-09 06:00:00,8211.0 -2012-05-09 07:00:00,8642.0 -2012-05-09 08:00:00,9708.0 -2012-05-09 09:00:00,10432.0 -2012-05-09 10:00:00,10858.0 -2012-05-09 11:00:00,11084.0 -2012-05-09 12:00:00,11363.0 -2012-05-09 13:00:00,11352.0 -2012-05-09 14:00:00,11327.0 -2012-05-09 15:00:00,11320.0 -2012-05-09 16:00:00,11225.0 -2012-05-09 17:00:00,11088.0 -2012-05-09 18:00:00,10940.0 -2012-05-09 19:00:00,10740.0 -2012-05-09 20:00:00,10512.0 -2012-05-09 21:00:00,10554.0 -2012-05-09 22:00:00,11095.0 -2012-05-09 23:00:00,10779.0 -2012-05-10 00:00:00,10012.0 -2012-05-08 01:00:00,9248.0 -2012-05-08 02:00:00,8701.0 -2012-05-08 03:00:00,8376.0 -2012-05-08 04:00:00,8158.0 -2012-05-08 05:00:00,8077.0 -2012-05-08 06:00:00,8270.0 -2012-05-08 07:00:00,8799.0 -2012-05-08 08:00:00,9707.0 -2012-05-08 09:00:00,10572.0 -2012-05-08 10:00:00,11061.0 -2012-05-08 11:00:00,11392.0 -2012-05-08 12:00:00,11673.0 -2012-05-08 13:00:00,11771.0 -2012-05-08 14:00:00,11833.0 -2012-05-08 15:00:00,11887.0 -2012-05-08 16:00:00,11788.0 -2012-05-08 17:00:00,11671.0 -2012-05-08 18:00:00,11522.0 -2012-05-08 19:00:00,11314.0 -2012-05-08 20:00:00,11025.0 -2012-05-08 21:00:00,11029.0 -2012-05-08 22:00:00,11425.0 -2012-05-08 23:00:00,10984.0 -2012-05-09 00:00:00,10143.0 -2012-05-07 01:00:00,8609.0 -2012-05-07 02:00:00,8257.0 -2012-05-07 03:00:00,7981.0 -2012-05-07 04:00:00,7908.0 -2012-05-07 05:00:00,7802.0 -2012-05-07 06:00:00,8075.0 -2012-05-07 07:00:00,8708.0 -2012-05-07 08:00:00,9744.0 -2012-05-07 09:00:00,10625.0 -2012-05-07 10:00:00,11058.0 -2012-05-07 11:00:00,11312.0 -2012-05-07 12:00:00,11565.0 -2012-05-07 13:00:00,11525.0 -2012-05-07 14:00:00,11551.0 -2012-05-07 15:00:00,11601.0 -2012-05-07 16:00:00,11498.0 -2012-05-07 17:00:00,11387.0 -2012-05-07 18:00:00,11308.0 -2012-05-07 19:00:00,11194.0 -2012-05-07 20:00:00,10947.0 -2012-05-07 21:00:00,10926.0 -2012-05-07 22:00:00,11312.0 -2012-05-07 23:00:00,10924.0 -2012-05-08 00:00:00,10073.0 -2012-05-06 01:00:00,8636.0 -2012-05-06 02:00:00,8123.0 -2012-05-06 03:00:00,7850.0 -2012-05-06 04:00:00,7633.0 -2012-05-06 05:00:00,7545.0 -2012-05-06 06:00:00,7519.0 -2012-05-06 07:00:00,7478.0 -2012-05-06 08:00:00,7473.0 -2012-05-06 09:00:00,7719.0 -2012-05-06 10:00:00,8289.0 -2012-05-06 11:00:00,8635.0 -2012-05-06 12:00:00,9008.0 -2012-05-06 13:00:00,9351.0 -2012-05-06 14:00:00,9610.0 -2012-05-06 15:00:00,9482.0 -2012-05-06 16:00:00,9404.0 -2012-05-06 17:00:00,9346.0 -2012-05-06 18:00:00,9419.0 -2012-05-06 19:00:00,9555.0 -2012-05-06 20:00:00,9554.0 -2012-05-06 21:00:00,9582.0 -2012-05-06 22:00:00,10052.0 -2012-05-06 23:00:00,9778.0 -2012-05-07 00:00:00,9192.0 -2012-05-05 01:00:00,9329.0 -2012-05-05 02:00:00,8694.0 -2012-05-05 03:00:00,8253.0 -2012-05-05 04:00:00,7992.0 -2012-05-05 05:00:00,7880.0 -2012-05-05 06:00:00,7861.0 -2012-05-05 07:00:00,8068.0 -2012-05-05 08:00:00,8256.0 -2012-05-05 09:00:00,8699.0 -2012-05-05 10:00:00,9181.0 -2012-05-05 11:00:00,9565.0 -2012-05-05 12:00:00,9766.0 -2012-05-05 13:00:00,9754.0 -2012-05-05 14:00:00,9676.0 -2012-05-05 15:00:00,9520.0 -2012-05-05 16:00:00,9440.0 -2012-05-05 17:00:00,9382.0 -2012-05-05 18:00:00,9405.0 -2012-05-05 19:00:00,9362.0 -2012-05-05 20:00:00,9368.0 -2012-05-05 21:00:00,9483.0 -2012-05-05 22:00:00,9926.0 -2012-05-05 23:00:00,9734.0 -2012-05-06 00:00:00,9200.0 -2012-05-04 01:00:00,11447.0 -2012-05-04 02:00:00,10713.0 -2012-05-04 03:00:00,10202.0 -2012-05-04 04:00:00,9713.0 -2012-05-04 05:00:00,9406.0 -2012-05-04 06:00:00,9359.0 -2012-05-04 07:00:00,9881.0 -2012-05-04 08:00:00,10732.0 -2012-05-04 09:00:00,11528.0 -2012-05-04 10:00:00,11824.0 -2012-05-04 11:00:00,12066.0 -2012-05-04 12:00:00,12307.0 -2012-05-04 13:00:00,12448.0 -2012-05-04 14:00:00,12566.0 -2012-05-04 15:00:00,12691.0 -2012-05-04 16:00:00,12649.0 -2012-05-04 17:00:00,12510.0 -2012-05-04 18:00:00,12185.0 -2012-05-04 19:00:00,11763.0 -2012-05-04 20:00:00,11203.0 -2012-05-04 21:00:00,11036.0 -2012-05-04 22:00:00,11269.0 -2012-05-04 23:00:00,10856.0 -2012-05-05 00:00:00,10112.0 -2012-05-03 01:00:00,10067.0 -2012-05-03 02:00:00,9442.0 -2012-05-03 03:00:00,9011.0 -2012-05-03 04:00:00,8768.0 -2012-05-03 05:00:00,8664.0 -2012-05-03 06:00:00,8858.0 -2012-05-03 07:00:00,9498.0 -2012-05-03 08:00:00,10411.0 -2012-05-03 09:00:00,11460.0 -2012-05-03 10:00:00,12181.0 -2012-05-03 11:00:00,12805.0 -2012-05-03 12:00:00,13365.0 -2012-05-03 13:00:00,13676.0 -2012-05-03 14:00:00,13900.0 -2012-05-03 15:00:00,14171.0 -2012-05-03 16:00:00,14368.0 -2012-05-03 17:00:00,14566.0 -2012-05-03 18:00:00,14630.0 -2012-05-03 19:00:00,14496.0 -2012-05-03 20:00:00,14161.0 -2012-05-03 21:00:00,14137.0 -2012-05-03 22:00:00,14304.0 -2012-05-03 23:00:00,13820.0 -2012-05-04 00:00:00,12673.0 -2012-05-02 01:00:00,9269.0 -2012-05-02 02:00:00,8729.0 -2012-05-02 03:00:00,8428.0 -2012-05-02 04:00:00,8213.0 -2012-05-02 05:00:00,8159.0 -2012-05-02 06:00:00,8364.0 -2012-05-02 07:00:00,9014.0 -2012-05-02 08:00:00,9937.0 -2012-05-02 09:00:00,10941.0 -2012-05-02 10:00:00,11445.0 -2012-05-02 11:00:00,11735.0 -2012-05-02 12:00:00,12031.0 -2012-05-02 13:00:00,12103.0 -2012-05-02 14:00:00,12097.0 -2012-05-02 15:00:00,12142.0 -2012-05-02 16:00:00,12111.0 -2012-05-02 17:00:00,12094.0 -2012-05-02 18:00:00,12077.0 -2012-05-02 19:00:00,11981.0 -2012-05-02 20:00:00,11731.0 -2012-05-02 21:00:00,11890.0 -2012-05-02 22:00:00,12272.0 -2012-05-02 23:00:00,11845.0 -2012-05-03 00:00:00,10937.0 -2012-05-01 01:00:00,9214.0 -2012-05-01 02:00:00,8678.0 -2012-05-01 03:00:00,8350.0 -2012-05-01 04:00:00,8153.0 -2012-05-01 05:00:00,8083.0 -2012-05-01 06:00:00,8272.0 -2012-05-01 07:00:00,8866.0 -2012-05-01 08:00:00,9759.0 -2012-05-01 09:00:00,10548.0 -2012-05-01 10:00:00,10892.0 -2012-05-01 11:00:00,11106.0 -2012-05-01 12:00:00,11247.0 -2012-05-01 13:00:00,11305.0 -2012-05-01 14:00:00,11357.0 -2012-05-01 15:00:00,11374.0 -2012-05-01 16:00:00,11261.0 -2012-05-01 17:00:00,11196.0 -2012-05-01 18:00:00,11147.0 -2012-05-01 19:00:00,11103.0 -2012-05-01 20:00:00,10995.0 -2012-05-01 21:00:00,11165.0 -2012-05-01 22:00:00,11396.0 -2012-05-01 23:00:00,10893.0 -2012-05-02 00:00:00,10074.0 -2012-04-30 01:00:00,8563.0 -2012-04-30 02:00:00,8194.0 -2012-04-30 03:00:00,7956.0 -2012-04-30 04:00:00,7851.0 -2012-04-30 05:00:00,7865.0 -2012-04-30 06:00:00,8101.0 -2012-04-30 07:00:00,8850.0 -2012-04-30 08:00:00,9852.0 -2012-04-30 09:00:00,10634.0 -2012-04-30 10:00:00,10976.0 -2012-04-30 11:00:00,11247.0 -2012-04-30 12:00:00,11413.0 -2012-04-30 13:00:00,11558.0 -2012-04-30 14:00:00,11584.0 -2012-04-30 15:00:00,11600.0 -2012-04-30 16:00:00,11405.0 -2012-04-30 17:00:00,11262.0 -2012-04-30 18:00:00,11137.0 -2012-04-30 19:00:00,11071.0 -2012-04-30 20:00:00,11004.0 -2012-04-30 21:00:00,11240.0 -2012-04-30 22:00:00,11296.0 -2012-04-30 23:00:00,10790.0 -2012-05-01 00:00:00,9993.0 -2012-04-29 01:00:00,8937.0 -2012-04-29 02:00:00,8511.0 -2012-04-29 03:00:00,8157.0 -2012-04-29 04:00:00,8040.0 -2012-04-29 05:00:00,7939.0 -2012-04-29 06:00:00,7951.0 -2012-04-29 07:00:00,8034.0 -2012-04-29 08:00:00,7901.0 -2012-04-29 09:00:00,8150.0 -2012-04-29 10:00:00,8490.0 -2012-04-29 11:00:00,8714.0 -2012-04-29 12:00:00,8806.0 -2012-04-29 13:00:00,8917.0 -2012-04-29 14:00:00,8912.0 -2012-04-29 15:00:00,8879.0 -2012-04-29 16:00:00,8849.0 -2012-04-29 17:00:00,8933.0 -2012-04-29 18:00:00,9127.0 -2012-04-29 19:00:00,9326.0 -2012-04-29 20:00:00,9521.0 -2012-04-29 21:00:00,9834.0 -2012-04-29 22:00:00,10041.0 -2012-04-29 23:00:00,9716.0 -2012-04-30 00:00:00,9156.0 -2012-04-28 01:00:00,9435.0 -2012-04-28 02:00:00,8890.0 -2012-04-28 03:00:00,8507.0 -2012-04-28 04:00:00,8324.0 -2012-04-28 05:00:00,8179.0 -2012-04-28 06:00:00,8247.0 -2012-04-28 07:00:00,8481.0 -2012-04-28 08:00:00,8909.0 -2012-04-28 09:00:00,9271.0 -2012-04-28 10:00:00,9773.0 -2012-04-28 11:00:00,10111.0 -2012-04-28 12:00:00,10276.0 -2012-04-28 13:00:00,10235.0 -2012-04-28 14:00:00,10137.0 -2012-04-28 15:00:00,9886.0 -2012-04-28 16:00:00,9709.0 -2012-04-28 17:00:00,9563.0 -2012-04-28 18:00:00,9551.0 -2012-04-28 19:00:00,9424.0 -2012-04-28 20:00:00,9430.0 -2012-04-28 21:00:00,9688.0 -2012-04-28 22:00:00,10189.0 -2012-04-28 23:00:00,9967.0 -2012-04-29 00:00:00,9512.0 -2012-04-27 01:00:00,9470.0 -2012-04-27 02:00:00,8972.0 -2012-04-27 03:00:00,8707.0 -2012-04-27 04:00:00,8525.0 -2012-04-27 05:00:00,8504.0 -2012-04-27 06:00:00,8753.0 -2012-04-27 07:00:00,9389.0 -2012-04-27 08:00:00,10213.0 -2012-04-27 09:00:00,10840.0 -2012-04-27 10:00:00,11121.0 -2012-04-27 11:00:00,11197.0 -2012-04-27 12:00:00,11249.0 -2012-04-27 13:00:00,11201.0 -2012-04-27 14:00:00,11090.0 -2012-04-27 15:00:00,11078.0 -2012-04-27 16:00:00,10929.0 -2012-04-27 17:00:00,10768.0 -2012-04-27 18:00:00,10702.0 -2012-04-27 19:00:00,10667.0 -2012-04-27 20:00:00,10601.0 -2012-04-27 21:00:00,10748.0 -2012-04-27 22:00:00,11117.0 -2012-04-27 23:00:00,10833.0 -2012-04-28 00:00:00,10155.0 -2012-04-26 01:00:00,9239.0 -2012-04-26 02:00:00,8712.0 -2012-04-26 03:00:00,8410.0 -2012-04-26 04:00:00,8213.0 -2012-04-26 05:00:00,8167.0 -2012-04-26 06:00:00,8373.0 -2012-04-26 07:00:00,9028.0 -2012-04-26 08:00:00,9856.0 -2012-04-26 09:00:00,10588.0 -2012-04-26 10:00:00,10942.0 -2012-04-26 11:00:00,11077.0 -2012-04-26 12:00:00,11304.0 -2012-04-26 13:00:00,11302.0 -2012-04-26 14:00:00,11242.0 -2012-04-26 15:00:00,11157.0 -2012-04-26 16:00:00,11004.0 -2012-04-26 17:00:00,10851.0 -2012-04-26 18:00:00,10732.0 -2012-04-26 19:00:00,10566.0 -2012-04-26 20:00:00,10390.0 -2012-04-26 21:00:00,10652.0 -2012-04-26 22:00:00,11228.0 -2012-04-26 23:00:00,10897.0 -2012-04-27 00:00:00,10193.0 -2012-04-25 01:00:00,9186.0 -2012-04-25 02:00:00,8687.0 -2012-04-25 03:00:00,8387.0 -2012-04-25 04:00:00,8215.0 -2012-04-25 05:00:00,8156.0 -2012-04-25 06:00:00,8356.0 -2012-04-25 07:00:00,9026.0 -2012-04-25 08:00:00,9913.0 -2012-04-25 09:00:00,10739.0 -2012-04-25 10:00:00,11169.0 -2012-04-25 11:00:00,11206.0 -2012-04-25 12:00:00,11384.0 -2012-04-25 13:00:00,11449.0 -2012-04-25 14:00:00,11451.0 -2012-04-25 15:00:00,11447.0 -2012-04-25 16:00:00,11319.0 -2012-04-25 17:00:00,11223.0 -2012-04-25 18:00:00,11084.0 -2012-04-25 19:00:00,10927.0 -2012-04-25 20:00:00,10676.0 -2012-04-25 21:00:00,10930.0 -2012-04-25 22:00:00,11256.0 -2012-04-25 23:00:00,10804.0 -2012-04-26 00:00:00,9981.0 -2012-04-24 01:00:00,9296.0 -2012-04-24 02:00:00,8823.0 -2012-04-24 03:00:00,8565.0 -2012-04-24 04:00:00,8380.0 -2012-04-24 05:00:00,8397.0 -2012-04-24 06:00:00,8614.0 -2012-04-24 07:00:00,9301.0 -2012-04-24 08:00:00,10175.0 -2012-04-24 09:00:00,10795.0 -2012-04-24 10:00:00,11038.0 -2012-04-24 11:00:00,11155.0 -2012-04-24 12:00:00,11252.0 -2012-04-24 13:00:00,11286.0 -2012-04-24 14:00:00,11269.0 -2012-04-24 15:00:00,11294.0 -2012-04-24 16:00:00,11179.0 -2012-04-24 17:00:00,11024.0 -2012-04-24 18:00:00,10873.0 -2012-04-24 19:00:00,10730.0 -2012-04-24 20:00:00,10515.0 -2012-04-24 21:00:00,10694.0 -2012-04-24 22:00:00,11153.0 -2012-04-24 23:00:00,10770.0 -2012-04-25 00:00:00,9958.0 -2012-04-23 01:00:00,8800.0 -2012-04-23 02:00:00,8469.0 -2012-04-23 03:00:00,8267.0 -2012-04-23 04:00:00,8198.0 -2012-04-23 05:00:00,8243.0 -2012-04-23 06:00:00,8546.0 -2012-04-23 07:00:00,9307.0 -2012-04-23 08:00:00,10229.0 -2012-04-23 09:00:00,10883.0 -2012-04-23 10:00:00,11157.0 -2012-04-23 11:00:00,11210.0 -2012-04-23 12:00:00,11362.0 -2012-04-23 13:00:00,11329.0 -2012-04-23 14:00:00,11284.0 -2012-04-23 15:00:00,11269.0 -2012-04-23 16:00:00,11173.0 -2012-04-23 17:00:00,11002.0 -2012-04-23 18:00:00,10866.0 -2012-04-23 19:00:00,10728.0 -2012-04-23 20:00:00,10576.0 -2012-04-23 21:00:00,10793.0 -2012-04-23 22:00:00,11224.0 -2012-04-23 23:00:00,10836.0 -2012-04-24 00:00:00,10079.0 -2012-04-22 01:00:00,8930.0 -2012-04-22 02:00:00,8480.0 -2012-04-22 03:00:00,8127.0 -2012-04-22 04:00:00,8011.0 -2012-04-22 05:00:00,7897.0 -2012-04-22 06:00:00,7913.0 -2012-04-22 07:00:00,8050.0 -2012-04-22 08:00:00,8039.0 -2012-04-22 09:00:00,8227.0 -2012-04-22 10:00:00,8576.0 -2012-04-22 11:00:00,8760.0 -2012-04-22 12:00:00,8946.0 -2012-04-22 13:00:00,8990.0 -2012-04-22 14:00:00,9000.0 -2012-04-22 15:00:00,8939.0 -2012-04-22 16:00:00,8888.0 -2012-04-22 17:00:00,8859.0 -2012-04-22 18:00:00,8913.0 -2012-04-22 19:00:00,9015.0 -2012-04-22 20:00:00,9109.0 -2012-04-22 21:00:00,9501.0 -2012-04-22 22:00:00,10072.0 -2012-04-22 23:00:00,9894.0 -2012-04-23 00:00:00,9341.0 -2012-04-21 01:00:00,9543.0 -2012-04-21 02:00:00,8987.0 -2012-04-21 03:00:00,8647.0 -2012-04-21 04:00:00,8406.0 -2012-04-21 05:00:00,8357.0 -2012-04-21 06:00:00,8398.0 -2012-04-21 07:00:00,8668.0 -2012-04-21 08:00:00,8800.0 -2012-04-21 09:00:00,9190.0 -2012-04-21 10:00:00,9534.0 -2012-04-21 11:00:00,9762.0 -2012-04-21 12:00:00,9827.0 -2012-04-21 13:00:00,9780.0 -2012-04-21 14:00:00,9603.0 -2012-04-21 15:00:00,9426.0 -2012-04-21 16:00:00,9248.0 -2012-04-21 17:00:00,9171.0 -2012-04-21 18:00:00,9113.0 -2012-04-21 19:00:00,9142.0 -2012-04-21 20:00:00,9213.0 -2012-04-21 21:00:00,9645.0 -2012-04-21 22:00:00,10078.0 -2012-04-21 23:00:00,9905.0 -2012-04-22 00:00:00,9452.0 -2012-04-20 01:00:00,9268.0 -2012-04-20 02:00:00,8766.0 -2012-04-20 03:00:00,8438.0 -2012-04-20 04:00:00,8235.0 -2012-04-20 05:00:00,8160.0 -2012-04-20 06:00:00,8367.0 -2012-04-20 07:00:00,9050.0 -2012-04-20 08:00:00,10103.0 -2012-04-20 09:00:00,10926.0 -2012-04-20 10:00:00,11297.0 -2012-04-20 11:00:00,11523.0 -2012-04-20 12:00:00,11672.0 -2012-04-20 13:00:00,11626.0 -2012-04-20 14:00:00,11536.0 -2012-04-20 15:00:00,11550.0 -2012-04-20 16:00:00,11407.0 -2012-04-20 17:00:00,11309.0 -2012-04-20 18:00:00,11192.0 -2012-04-20 19:00:00,11065.0 -2012-04-20 20:00:00,10926.0 -2012-04-20 21:00:00,11078.0 -2012-04-20 22:00:00,11312.0 -2012-04-20 23:00:00,11007.0 -2012-04-21 00:00:00,10287.0 -2012-04-19 01:00:00,9332.0 -2012-04-19 02:00:00,8755.0 -2012-04-19 03:00:00,8385.0 -2012-04-19 04:00:00,8159.0 -2012-04-19 05:00:00,8086.0 -2012-04-19 06:00:00,8262.0 -2012-04-19 07:00:00,8898.0 -2012-04-19 08:00:00,9829.0 -2012-04-19 09:00:00,10630.0 -2012-04-19 10:00:00,10986.0 -2012-04-19 11:00:00,11140.0 -2012-04-19 12:00:00,11303.0 -2012-04-19 13:00:00,11324.0 -2012-04-19 14:00:00,11363.0 -2012-04-19 15:00:00,11396.0 -2012-04-19 16:00:00,11279.0 -2012-04-19 17:00:00,11145.0 -2012-04-19 18:00:00,11037.0 -2012-04-19 19:00:00,10879.0 -2012-04-19 20:00:00,10844.0 -2012-04-19 21:00:00,11121.0 -2012-04-19 22:00:00,11316.0 -2012-04-19 23:00:00,10845.0 -2012-04-20 00:00:00,10042.0 -2012-04-18 01:00:00,9340.0 -2012-04-18 02:00:00,8840.0 -2012-04-18 03:00:00,8537.0 -2012-04-18 04:00:00,8350.0 -2012-04-18 05:00:00,8326.0 -2012-04-18 06:00:00,8533.0 -2012-04-18 07:00:00,9197.0 -2012-04-18 08:00:00,10107.0 -2012-04-18 09:00:00,10725.0 -2012-04-18 10:00:00,11030.0 -2012-04-18 11:00:00,11207.0 -2012-04-18 12:00:00,11384.0 -2012-04-18 13:00:00,11468.0 -2012-04-18 14:00:00,11508.0 -2012-04-18 15:00:00,11605.0 -2012-04-18 16:00:00,11540.0 -2012-04-18 17:00:00,11430.0 -2012-04-18 18:00:00,11302.0 -2012-04-18 19:00:00,11184.0 -2012-04-18 20:00:00,11025.0 -2012-04-18 21:00:00,11324.0 -2012-04-18 22:00:00,11543.0 -2012-04-18 23:00:00,11027.0 -2012-04-19 00:00:00,10148.0 -2012-04-17 01:00:00,9234.0 -2012-04-17 02:00:00,8751.0 -2012-04-17 03:00:00,8445.0 -2012-04-17 04:00:00,8259.0 -2012-04-17 05:00:00,8209.0 -2012-04-17 06:00:00,8449.0 -2012-04-17 07:00:00,9209.0 -2012-04-17 08:00:00,10115.0 -2012-04-17 09:00:00,10751.0 -2012-04-17 10:00:00,11037.0 -2012-04-17 11:00:00,11098.0 -2012-04-17 12:00:00,11226.0 -2012-04-17 13:00:00,11211.0 -2012-04-17 14:00:00,11162.0 -2012-04-17 15:00:00,11204.0 -2012-04-17 16:00:00,11111.0 -2012-04-17 17:00:00,10951.0 -2012-04-17 18:00:00,10827.0 -2012-04-17 19:00:00,10650.0 -2012-04-17 20:00:00,10476.0 -2012-04-17 21:00:00,10791.0 -2012-04-17 22:00:00,11238.0 -2012-04-17 23:00:00,10868.0 -2012-04-18 00:00:00,10092.0 -2012-04-16 01:00:00,9038.0 -2012-04-16 02:00:00,8581.0 -2012-04-16 03:00:00,8290.0 -2012-04-16 04:00:00,8035.0 -2012-04-16 05:00:00,7987.0 -2012-04-16 06:00:00,8110.0 -2012-04-16 07:00:00,8781.0 -2012-04-16 08:00:00,9685.0 -2012-04-16 09:00:00,10548.0 -2012-04-16 10:00:00,10946.0 -2012-04-16 11:00:00,11163.0 -2012-04-16 12:00:00,11337.0 -2012-04-16 13:00:00,11331.0 -2012-04-16 14:00:00,11309.0 -2012-04-16 15:00:00,11338.0 -2012-04-16 16:00:00,11219.0 -2012-04-16 17:00:00,11031.0 -2012-04-16 18:00:00,10861.0 -2012-04-16 19:00:00,10727.0 -2012-04-16 20:00:00,10578.0 -2012-04-16 21:00:00,10860.0 -2012-04-16 22:00:00,11262.0 -2012-04-16 23:00:00,10832.0 -2012-04-17 00:00:00,10027.0 -2012-04-15 01:00:00,8905.0 -2012-04-15 02:00:00,8438.0 -2012-04-15 03:00:00,8136.0 -2012-04-15 04:00:00,7836.0 -2012-04-15 05:00:00,7732.0 -2012-04-15 06:00:00,7631.0 -2012-04-15 07:00:00,7756.0 -2012-04-15 08:00:00,7751.0 -2012-04-15 09:00:00,7928.0 -2012-04-15 10:00:00,8361.0 -2012-04-15 11:00:00,8770.0 -2012-04-15 12:00:00,9104.0 -2012-04-15 13:00:00,9360.0 -2012-04-15 14:00:00,9428.0 -2012-04-15 15:00:00,9552.0 -2012-04-15 16:00:00,9526.0 -2012-04-15 17:00:00,9585.0 -2012-04-15 18:00:00,9622.0 -2012-04-15 19:00:00,9810.0 -2012-04-15 20:00:00,10058.0 -2012-04-15 21:00:00,10430.0 -2012-04-15 22:00:00,10549.0 -2012-04-15 23:00:00,10262.0 -2012-04-16 00:00:00,9700.0 -2012-04-14 01:00:00,9249.0 -2012-04-14 02:00:00,8679.0 -2012-04-14 03:00:00,8341.0 -2012-04-14 04:00:00,8102.0 -2012-04-14 05:00:00,8006.0 -2012-04-14 06:00:00,8005.0 -2012-04-14 07:00:00,8267.0 -2012-04-14 08:00:00,8489.0 -2012-04-14 09:00:00,8859.0 -2012-04-14 10:00:00,9313.0 -2012-04-14 11:00:00,9651.0 -2012-04-14 12:00:00,9838.0 -2012-04-14 13:00:00,9831.0 -2012-04-14 14:00:00,9818.0 -2012-04-14 15:00:00,9675.0 -2012-04-14 16:00:00,9613.0 -2012-04-14 17:00:00,9592.0 -2012-04-14 18:00:00,9565.0 -2012-04-14 19:00:00,9600.0 -2012-04-14 20:00:00,9551.0 -2012-04-14 21:00:00,9989.0 -2012-04-14 22:00:00,10207.0 -2012-04-14 23:00:00,9968.0 -2012-04-15 00:00:00,9490.0 -2012-04-13 01:00:00,9423.0 -2012-04-13 02:00:00,8883.0 -2012-04-13 03:00:00,8569.0 -2012-04-13 04:00:00,8381.0 -2012-04-13 05:00:00,8367.0 -2012-04-13 06:00:00,8550.0 -2012-04-13 07:00:00,9239.0 -2012-04-13 08:00:00,10081.0 -2012-04-13 09:00:00,10695.0 -2012-04-13 10:00:00,11021.0 -2012-04-13 11:00:00,11049.0 -2012-04-13 12:00:00,11229.0 -2012-04-13 13:00:00,11234.0 -2012-04-13 14:00:00,11232.0 -2012-04-13 15:00:00,11240.0 -2012-04-13 16:00:00,11097.0 -2012-04-13 17:00:00,10923.0 -2012-04-13 18:00:00,10769.0 -2012-04-13 19:00:00,10655.0 -2012-04-13 20:00:00,10521.0 -2012-04-13 21:00:00,10812.0 -2012-04-13 22:00:00,11036.0 -2012-04-13 23:00:00,10676.0 -2012-04-14 00:00:00,9969.0 -2012-04-12 01:00:00,9556.0 -2012-04-12 02:00:00,9090.0 -2012-04-12 03:00:00,8801.0 -2012-04-12 04:00:00,8647.0 -2012-04-12 05:00:00,8641.0 -2012-04-12 06:00:00,8867.0 -2012-04-12 07:00:00,9612.0 -2012-04-12 08:00:00,10538.0 -2012-04-12 09:00:00,11046.0 -2012-04-12 10:00:00,11228.0 -2012-04-12 11:00:00,11311.0 -2012-04-12 12:00:00,11372.0 -2012-04-12 13:00:00,11334.0 -2012-04-12 14:00:00,11305.0 -2012-04-12 15:00:00,11295.0 -2012-04-12 16:00:00,11204.0 -2012-04-12 17:00:00,11063.0 -2012-04-12 18:00:00,10907.0 -2012-04-12 19:00:00,10732.0 -2012-04-12 20:00:00,10636.0 -2012-04-12 21:00:00,11053.0 -2012-04-12 22:00:00,11312.0 -2012-04-12 23:00:00,10899.0 -2012-04-13 00:00:00,10110.0 -2012-04-11 01:00:00,9702.0 -2012-04-11 02:00:00,9256.0 -2012-04-11 03:00:00,8973.0 -2012-04-11 04:00:00,8795.0 -2012-04-11 05:00:00,8819.0 -2012-04-11 06:00:00,9025.0 -2012-04-11 07:00:00,9769.0 -2012-04-11 08:00:00,10709.0 -2012-04-11 09:00:00,11256.0 -2012-04-11 10:00:00,11446.0 -2012-04-11 11:00:00,11486.0 -2012-04-11 12:00:00,11493.0 -2012-04-11 13:00:00,11428.0 -2012-04-11 14:00:00,11365.0 -2012-04-11 15:00:00,11318.0 -2012-04-11 16:00:00,11171.0 -2012-04-11 17:00:00,11002.0 -2012-04-11 18:00:00,10849.0 -2012-04-11 19:00:00,10729.0 -2012-04-11 20:00:00,10617.0 -2012-04-11 21:00:00,11070.0 -2012-04-11 22:00:00,11462.0 -2012-04-11 23:00:00,11081.0 -2012-04-12 00:00:00,10311.0 -2012-04-10 01:00:00,9179.0 -2012-04-10 02:00:00,8733.0 -2012-04-10 03:00:00,8487.0 -2012-04-10 04:00:00,8323.0 -2012-04-10 05:00:00,8351.0 -2012-04-10 06:00:00,8609.0 -2012-04-10 07:00:00,9358.0 -2012-04-10 08:00:00,10374.0 -2012-04-10 09:00:00,11009.0 -2012-04-10 10:00:00,11317.0 -2012-04-10 11:00:00,11446.0 -2012-04-10 12:00:00,11522.0 -2012-04-10 13:00:00,11482.0 -2012-04-10 14:00:00,11466.0 -2012-04-10 15:00:00,11467.0 -2012-04-10 16:00:00,11325.0 -2012-04-10 17:00:00,11143.0 -2012-04-10 18:00:00,10998.0 -2012-04-10 19:00:00,10950.0 -2012-04-10 20:00:00,10859.0 -2012-04-10 21:00:00,11314.0 -2012-04-10 22:00:00,11638.0 -2012-04-10 23:00:00,11214.0 -2012-04-11 00:00:00,10451.0 -2012-04-09 01:00:00,8222.0 -2012-04-09 02:00:00,7858.0 -2012-04-09 03:00:00,7683.0 -2012-04-09 04:00:00,7564.0 -2012-04-09 05:00:00,7626.0 -2012-04-09 06:00:00,7895.0 -2012-04-09 07:00:00,8609.0 -2012-04-09 08:00:00,9610.0 -2012-04-09 09:00:00,10236.0 -2012-04-09 10:00:00,10624.0 -2012-04-09 11:00:00,10899.0 -2012-04-09 12:00:00,11087.0 -2012-04-09 13:00:00,11154.0 -2012-04-09 14:00:00,11153.0 -2012-04-09 15:00:00,11180.0 -2012-04-09 16:00:00,11096.0 -2012-04-09 17:00:00,10918.0 -2012-04-09 18:00:00,10775.0 -2012-04-09 19:00:00,10624.0 -2012-04-09 20:00:00,10484.0 -2012-04-09 21:00:00,10884.0 -2012-04-09 22:00:00,11170.0 -2012-04-09 23:00:00,10720.0 -2012-04-10 00:00:00,9924.0 -2012-04-08 01:00:00,8536.0 -2012-04-08 02:00:00,8012.0 -2012-04-08 03:00:00,7706.0 -2012-04-08 04:00:00,7557.0 -2012-04-08 05:00:00,7413.0 -2012-04-08 06:00:00,7471.0 -2012-04-08 07:00:00,7585.0 -2012-04-08 08:00:00,7679.0 -2012-04-08 09:00:00,7771.0 -2012-04-08 10:00:00,8092.0 -2012-04-08 11:00:00,8301.0 -2012-04-08 12:00:00,8411.0 -2012-04-08 13:00:00,8482.0 -2012-04-08 14:00:00,8448.0 -2012-04-08 15:00:00,8419.0 -2012-04-08 16:00:00,8291.0 -2012-04-08 17:00:00,8246.0 -2012-04-08 18:00:00,8200.0 -2012-04-08 19:00:00,8258.0 -2012-04-08 20:00:00,8509.0 -2012-04-08 21:00:00,9024.0 -2012-04-08 22:00:00,9366.0 -2012-04-08 23:00:00,9153.0 -2012-04-09 00:00:00,8699.0 -2012-04-07 01:00:00,8967.0 -2012-04-07 02:00:00,8480.0 -2012-04-07 03:00:00,8240.0 -2012-04-07 04:00:00,8039.0 -2012-04-07 05:00:00,7997.0 -2012-04-07 06:00:00,8040.0 -2012-04-07 07:00:00,8305.0 -2012-04-07 08:00:00,8480.0 -2012-04-07 09:00:00,8687.0 -2012-04-07 10:00:00,9011.0 -2012-04-07 11:00:00,9239.0 -2012-04-07 12:00:00,9351.0 -2012-04-07 13:00:00,9369.0 -2012-04-07 14:00:00,9285.0 -2012-04-07 15:00:00,9165.0 -2012-04-07 16:00:00,9065.0 -2012-04-07 17:00:00,9031.0 -2012-04-07 18:00:00,9001.0 -2012-04-07 19:00:00,9057.0 -2012-04-07 20:00:00,9220.0 -2012-04-07 21:00:00,9706.0 -2012-04-07 22:00:00,9773.0 -2012-04-07 23:00:00,9538.0 -2012-04-08 00:00:00,9059.0 -2012-04-06 01:00:00,9564.0 -2012-04-06 02:00:00,9050.0 -2012-04-06 03:00:00,8693.0 -2012-04-06 04:00:00,8531.0 -2012-04-06 05:00:00,8440.0 -2012-04-06 06:00:00,8645.0 -2012-04-06 07:00:00,9140.0 -2012-04-06 08:00:00,9718.0 -2012-04-06 09:00:00,9979.0 -2012-04-06 10:00:00,10215.0 -2012-04-06 11:00:00,10359.0 -2012-04-06 12:00:00,10429.0 -2012-04-06 13:00:00,10362.0 -2012-04-06 14:00:00,10254.0 -2012-04-06 15:00:00,10167.0 -2012-04-06 16:00:00,10023.0 -2012-04-06 17:00:00,9852.0 -2012-04-06 18:00:00,9776.0 -2012-04-06 19:00:00,9701.0 -2012-04-06 20:00:00,9630.0 -2012-04-06 21:00:00,10068.0 -2012-04-06 22:00:00,10375.0 -2012-04-06 23:00:00,10131.0 -2012-04-07 00:00:00,9618.0 -2012-04-05 01:00:00,9356.0 -2012-04-05 02:00:00,8837.0 -2012-04-05 03:00:00,8547.0 -2012-04-05 04:00:00,8349.0 -2012-04-05 05:00:00,8330.0 -2012-04-05 06:00:00,8573.0 -2012-04-05 07:00:00,9286.0 -2012-04-05 08:00:00,10237.0 -2012-04-05 09:00:00,10771.0 -2012-04-05 10:00:00,11066.0 -2012-04-05 11:00:00,11191.0 -2012-04-05 12:00:00,11239.0 -2012-04-05 13:00:00,11187.0 -2012-04-05 14:00:00,11132.0 -2012-04-05 15:00:00,11142.0 -2012-04-05 16:00:00,11010.0 -2012-04-05 17:00:00,10831.0 -2012-04-05 18:00:00,10701.0 -2012-04-05 19:00:00,10579.0 -2012-04-05 20:00:00,10587.0 -2012-04-05 21:00:00,11119.0 -2012-04-05 22:00:00,11355.0 -2012-04-05 23:00:00,11060.0 -2012-04-06 00:00:00,10307.0 -2012-04-04 01:00:00,9223.0 -2012-04-04 02:00:00,8717.0 -2012-04-04 03:00:00,8415.0 -2012-04-04 04:00:00,8185.0 -2012-04-04 05:00:00,8172.0 -2012-04-04 06:00:00,8382.0 -2012-04-04 07:00:00,9075.0 -2012-04-04 08:00:00,10047.0 -2012-04-04 09:00:00,10560.0 -2012-04-04 10:00:00,10823.0 -2012-04-04 11:00:00,10989.0 -2012-04-04 12:00:00,11115.0 -2012-04-04 13:00:00,11152.0 -2012-04-04 14:00:00,11109.0 -2012-04-04 15:00:00,11144.0 -2012-04-04 16:00:00,11039.0 -2012-04-04 17:00:00,10866.0 -2012-04-04 18:00:00,10732.0 -2012-04-04 19:00:00,10593.0 -2012-04-04 20:00:00,10484.0 -2012-04-04 21:00:00,11032.0 -2012-04-04 22:00:00,11274.0 -2012-04-04 23:00:00,10864.0 -2012-04-05 00:00:00,10094.0 -2012-04-03 01:00:00,9151.0 -2012-04-03 02:00:00,8607.0 -2012-04-03 03:00:00,8289.0 -2012-04-03 04:00:00,8093.0 -2012-04-03 05:00:00,8033.0 -2012-04-03 06:00:00,8207.0 -2012-04-03 07:00:00,8836.0 -2012-04-03 08:00:00,9832.0 -2012-04-03 09:00:00,10352.0 -2012-04-03 10:00:00,10753.0 -2012-04-03 11:00:00,10989.0 -2012-04-03 12:00:00,11229.0 -2012-04-03 13:00:00,11370.0 -2012-04-03 14:00:00,11472.0 -2012-04-03 15:00:00,11621.0 -2012-04-03 16:00:00,11504.0 -2012-04-03 17:00:00,11308.0 -2012-04-03 18:00:00,11008.0 -2012-04-03 19:00:00,10757.0 -2012-04-03 20:00:00,10617.0 -2012-04-03 21:00:00,11108.0 -2012-04-03 22:00:00,11250.0 -2012-04-03 23:00:00,10766.0 -2012-04-04 00:00:00,9988.0 -2012-04-02 01:00:00,8685.0 -2012-04-02 02:00:00,8339.0 -2012-04-02 03:00:00,8086.0 -2012-04-02 04:00:00,8011.0 -2012-04-02 05:00:00,8002.0 -2012-04-02 06:00:00,8327.0 -2012-04-02 07:00:00,9018.0 -2012-04-02 08:00:00,10074.0 -2012-04-02 09:00:00,10650.0 -2012-04-02 10:00:00,10841.0 -2012-04-02 11:00:00,10969.0 -2012-04-02 12:00:00,11082.0 -2012-04-02 13:00:00,11091.0 -2012-04-02 14:00:00,11083.0 -2012-04-02 15:00:00,11098.0 -2012-04-02 16:00:00,11010.0 -2012-04-02 17:00:00,10863.0 -2012-04-02 18:00:00,10739.0 -2012-04-02 19:00:00,10603.0 -2012-04-02 20:00:00,10463.0 -2012-04-02 21:00:00,10980.0 -2012-04-02 22:00:00,11183.0 -2012-04-02 23:00:00,10736.0 -2012-04-03 00:00:00,9928.0 -2012-04-01 01:00:00,8982.0 -2012-04-01 02:00:00,8533.0 -2012-04-01 03:00:00,8210.0 -2012-04-01 04:00:00,8022.0 -2012-04-01 05:00:00,7910.0 -2012-04-01 06:00:00,7868.0 -2012-04-01 07:00:00,7984.0 -2012-04-01 08:00:00,8143.0 -2012-04-01 09:00:00,8184.0 -2012-04-01 10:00:00,8415.0 -2012-04-01 11:00:00,8729.0 -2012-04-01 12:00:00,8862.0 -2012-04-01 13:00:00,8939.0 -2012-04-01 14:00:00,8915.0 -2012-04-01 15:00:00,8949.0 -2012-04-01 16:00:00,8871.0 -2012-04-01 17:00:00,8819.0 -2012-04-01 18:00:00,8864.0 -2012-04-01 19:00:00,9003.0 -2012-04-01 20:00:00,9213.0 -2012-04-01 21:00:00,9834.0 -2012-04-01 22:00:00,10031.0 -2012-04-01 23:00:00,9733.0 -2012-04-02 00:00:00,9249.0 -2012-03-31 01:00:00,9464.0 -2012-03-31 02:00:00,8962.0 -2012-03-31 03:00:00,8660.0 -2012-03-31 04:00:00,8407.0 -2012-03-31 05:00:00,8343.0 -2012-03-31 06:00:00,8336.0 -2012-03-31 07:00:00,8635.0 -2012-03-31 08:00:00,9073.0 -2012-03-31 09:00:00,9391.0 -2012-03-31 10:00:00,9785.0 -2012-03-31 11:00:00,10111.0 -2012-03-31 12:00:00,10237.0 -2012-03-31 13:00:00,10194.0 -2012-03-31 14:00:00,10018.0 -2012-03-31 15:00:00,9697.0 -2012-03-31 16:00:00,9502.0 -2012-03-31 17:00:00,9326.0 -2012-03-31 18:00:00,9310.0 -2012-03-31 19:00:00,9320.0 -2012-03-31 20:00:00,9436.0 -2012-03-31 21:00:00,10010.0 -2012-03-31 22:00:00,10220.0 -2012-03-31 23:00:00,9984.0 -2012-04-01 00:00:00,9531.0 -2012-03-30 01:00:00,9493.0 -2012-03-30 02:00:00,8977.0 -2012-03-30 03:00:00,8669.0 -2012-03-30 04:00:00,8446.0 -2012-03-30 05:00:00,8383.0 -2012-03-30 06:00:00,8531.0 -2012-03-30 07:00:00,9126.0 -2012-03-30 08:00:00,10159.0 -2012-03-30 09:00:00,10786.0 -2012-03-30 10:00:00,11079.0 -2012-03-30 11:00:00,11170.0 -2012-03-30 12:00:00,11253.0 -2012-03-30 13:00:00,11265.0 -2012-03-30 14:00:00,11117.0 -2012-03-30 15:00:00,11100.0 -2012-03-30 16:00:00,10912.0 -2012-03-30 17:00:00,10752.0 -2012-03-30 18:00:00,10677.0 -2012-03-30 19:00:00,10636.0 -2012-03-30 20:00:00,10703.0 -2012-03-30 21:00:00,11170.0 -2012-03-30 22:00:00,11074.0 -2012-03-30 23:00:00,10799.0 -2012-03-31 00:00:00,10149.0 -2012-03-29 01:00:00,9180.0 -2012-03-29 02:00:00,8656.0 -2012-03-29 03:00:00,8374.0 -2012-03-29 04:00:00,8182.0 -2012-03-29 05:00:00,8163.0 -2012-03-29 06:00:00,8372.0 -2012-03-29 07:00:00,9031.0 -2012-03-29 08:00:00,10044.0 -2012-03-29 09:00:00,10461.0 -2012-03-29 10:00:00,10837.0 -2012-03-29 11:00:00,10976.0 -2012-03-29 12:00:00,11123.0 -2012-03-29 13:00:00,11072.0 -2012-03-29 14:00:00,11014.0 -2012-03-29 15:00:00,11046.0 -2012-03-29 16:00:00,10903.0 -2012-03-29 17:00:00,10759.0 -2012-03-29 18:00:00,10673.0 -2012-03-29 19:00:00,10633.0 -2012-03-29 20:00:00,10671.0 -2012-03-29 21:00:00,11202.0 -2012-03-29 22:00:00,11304.0 -2012-03-29 23:00:00,10909.0 -2012-03-30 00:00:00,10202.0 -2012-03-28 01:00:00,9383.0 -2012-03-28 02:00:00,8782.0 -2012-03-28 03:00:00,8436.0 -2012-03-28 04:00:00,8221.0 -2012-03-28 05:00:00,8138.0 -2012-03-28 06:00:00,8283.0 -2012-03-28 07:00:00,8877.0 -2012-03-28 08:00:00,9817.0 -2012-03-28 09:00:00,10296.0 -2012-03-28 10:00:00,10717.0 -2012-03-28 11:00:00,10982.0 -2012-03-28 12:00:00,11295.0 -2012-03-28 13:00:00,11320.0 -2012-03-28 14:00:00,11321.0 -2012-03-28 15:00:00,11424.0 -2012-03-28 16:00:00,11324.0 -2012-03-28 17:00:00,11192.0 -2012-03-28 18:00:00,10978.0 -2012-03-28 19:00:00,10762.0 -2012-03-28 20:00:00,10541.0 -2012-03-28 21:00:00,10969.0 -2012-03-28 22:00:00,11052.0 -2012-03-28 23:00:00,10596.0 -2012-03-29 00:00:00,9886.0 -2012-03-27 01:00:00,9546.0 -2012-03-27 02:00:00,9065.0 -2012-03-27 03:00:00,8722.0 -2012-03-27 04:00:00,8536.0 -2012-03-27 05:00:00,8499.0 -2012-03-27 06:00:00,8720.0 -2012-03-27 07:00:00,9406.0 -2012-03-27 08:00:00,10495.0 -2012-03-27 09:00:00,10964.0 -2012-03-27 10:00:00,11183.0 -2012-03-27 11:00:00,11309.0 -2012-03-27 12:00:00,11380.0 -2012-03-27 13:00:00,11355.0 -2012-03-27 14:00:00,11337.0 -2012-03-27 15:00:00,11325.0 -2012-03-27 16:00:00,11265.0 -2012-03-27 17:00:00,11144.0 -2012-03-27 18:00:00,11039.0 -2012-03-27 19:00:00,10958.0 -2012-03-27 20:00:00,10836.0 -2012-03-27 21:00:00,11360.0 -2012-03-27 22:00:00,11415.0 -2012-03-27 23:00:00,10947.0 -2012-03-28 00:00:00,10143.0 -2012-03-26 01:00:00,8522.0 -2012-03-26 02:00:00,8170.0 -2012-03-26 03:00:00,7962.0 -2012-03-26 04:00:00,7886.0 -2012-03-26 05:00:00,7889.0 -2012-03-26 06:00:00,8200.0 -2012-03-26 07:00:00,8889.0 -2012-03-26 08:00:00,10102.0 -2012-03-26 09:00:00,10765.0 -2012-03-26 10:00:00,11197.0 -2012-03-26 11:00:00,11420.0 -2012-03-26 12:00:00,11464.0 -2012-03-26 13:00:00,11360.0 -2012-03-26 14:00:00,11282.0 -2012-03-26 15:00:00,11243.0 -2012-03-26 16:00:00,11104.0 -2012-03-26 17:00:00,10917.0 -2012-03-26 18:00:00,10854.0 -2012-03-26 19:00:00,10873.0 -2012-03-26 20:00:00,10997.0 -2012-03-26 21:00:00,11565.0 -2012-03-26 22:00:00,11544.0 -2012-03-26 23:00:00,11044.0 -2012-03-27 00:00:00,10324.0 -2012-03-25 01:00:00,8681.0 -2012-03-25 02:00:00,8239.0 -2012-03-25 03:00:00,7841.0 -2012-03-25 04:00:00,7722.0 -2012-03-25 05:00:00,7563.0 -2012-03-25 06:00:00,7569.0 -2012-03-25 07:00:00,7623.0 -2012-03-25 08:00:00,7862.0 -2012-03-25 09:00:00,7883.0 -2012-03-25 10:00:00,8243.0 -2012-03-25 11:00:00,8576.0 -2012-03-25 12:00:00,8857.0 -2012-03-25 13:00:00,9046.0 -2012-03-25 14:00:00,9146.0 -2012-03-25 15:00:00,9185.0 -2012-03-25 16:00:00,9176.0 -2012-03-25 17:00:00,9110.0 -2012-03-25 18:00:00,9092.0 -2012-03-25 19:00:00,9066.0 -2012-03-25 20:00:00,9122.0 -2012-03-25 21:00:00,9697.0 -2012-03-25 22:00:00,9825.0 -2012-03-25 23:00:00,9528.0 -2012-03-26 00:00:00,9053.0 -2012-03-24 01:00:00,9288.0 -2012-03-24 02:00:00,8664.0 -2012-03-24 03:00:00,8323.0 -2012-03-24 04:00:00,8043.0 -2012-03-24 05:00:00,7937.0 -2012-03-24 06:00:00,7958.0 -2012-03-24 07:00:00,8163.0 -2012-03-24 08:00:00,8564.0 -2012-03-24 09:00:00,8801.0 -2012-03-24 10:00:00,9255.0 -2012-03-24 11:00:00,9594.0 -2012-03-24 12:00:00,9833.0 -2012-03-24 13:00:00,9857.0 -2012-03-24 14:00:00,9781.0 -2012-03-24 15:00:00,9639.0 -2012-03-24 16:00:00,9508.0 -2012-03-24 17:00:00,9434.0 -2012-03-24 18:00:00,9434.0 -2012-03-24 19:00:00,9382.0 -2012-03-24 20:00:00,9450.0 -2012-03-24 21:00:00,9958.0 -2012-03-24 22:00:00,9954.0 -2012-03-24 23:00:00,9669.0 -2012-03-25 00:00:00,9256.0 -2012-03-23 01:00:00,10170.0 -2012-03-23 02:00:00,9446.0 -2012-03-23 03:00:00,8985.0 -2012-03-23 04:00:00,8611.0 -2012-03-23 05:00:00,8513.0 -2012-03-23 06:00:00,8711.0 -2012-03-23 07:00:00,9273.0 -2012-03-23 08:00:00,10453.0 -2012-03-23 09:00:00,10985.0 -2012-03-23 10:00:00,11400.0 -2012-03-23 11:00:00,11639.0 -2012-03-23 12:00:00,11850.0 -2012-03-23 13:00:00,11937.0 -2012-03-23 14:00:00,11862.0 -2012-03-23 15:00:00,11925.0 -2012-03-23 16:00:00,11775.0 -2012-03-23 17:00:00,11555.0 -2012-03-23 18:00:00,11337.0 -2012-03-23 19:00:00,11128.0 -2012-03-23 20:00:00,11036.0 -2012-03-23 21:00:00,11419.0 -2012-03-23 22:00:00,11164.0 -2012-03-23 23:00:00,10719.0 -2012-03-24 00:00:00,9996.0 -2012-03-22 01:00:00,10360.0 -2012-03-22 02:00:00,9689.0 -2012-03-22 03:00:00,9215.0 -2012-03-22 04:00:00,8975.0 -2012-03-22 05:00:00,8848.0 -2012-03-22 06:00:00,8949.0 -2012-03-22 07:00:00,9567.0 -2012-03-22 08:00:00,10721.0 -2012-03-22 09:00:00,11261.0 -2012-03-22 10:00:00,11842.0 -2012-03-22 11:00:00,12302.0 -2012-03-22 12:00:00,12807.0 -2012-03-22 13:00:00,13112.0 -2012-03-22 14:00:00,13420.0 -2012-03-22 15:00:00,13680.0 -2012-03-22 16:00:00,13583.0 -2012-03-22 17:00:00,13230.0 -2012-03-22 18:00:00,13013.0 -2012-03-22 19:00:00,12750.0 -2012-03-22 20:00:00,12631.0 -2012-03-22 21:00:00,13033.0 -2012-03-22 22:00:00,12716.0 -2012-03-22 23:00:00,12060.0 -2012-03-23 00:00:00,11079.0 -2012-03-21 01:00:00,10341.0 -2012-03-21 02:00:00,9641.0 -2012-03-21 03:00:00,9215.0 -2012-03-21 04:00:00,8910.0 -2012-03-21 05:00:00,8773.0 -2012-03-21 06:00:00,8903.0 -2012-03-21 07:00:00,9559.0 -2012-03-21 08:00:00,10766.0 -2012-03-21 09:00:00,11361.0 -2012-03-21 10:00:00,11941.0 -2012-03-21 11:00:00,12440.0 -2012-03-21 12:00:00,12915.0 -2012-03-21 13:00:00,13317.0 -2012-03-21 14:00:00,13618.0 -2012-03-21 15:00:00,13878.0 -2012-03-21 16:00:00,13958.0 -2012-03-21 17:00:00,13962.0 -2012-03-21 18:00:00,13819.0 -2012-03-21 19:00:00,13519.0 -2012-03-21 20:00:00,13089.0 -2012-03-21 21:00:00,13360.0 -2012-03-21 22:00:00,13149.0 -2012-03-21 23:00:00,12394.0 -2012-03-22 00:00:00,11331.0 -2012-03-20 01:00:00,10035.0 -2012-03-20 02:00:00,9383.0 -2012-03-20 03:00:00,8949.0 -2012-03-20 04:00:00,8683.0 -2012-03-20 05:00:00,8540.0 -2012-03-20 06:00:00,8708.0 -2012-03-20 07:00:00,9329.0 -2012-03-20 08:00:00,10505.0 -2012-03-20 09:00:00,11124.0 -2012-03-20 10:00:00,11537.0 -2012-03-20 11:00:00,11966.0 -2012-03-20 12:00:00,12429.0 -2012-03-20 13:00:00,12718.0 -2012-03-20 14:00:00,12926.0 -2012-03-20 15:00:00,13168.0 -2012-03-20 16:00:00,13287.0 -2012-03-20 17:00:00,13323.0 -2012-03-20 18:00:00,13266.0 -2012-03-20 19:00:00,13084.0 -2012-03-20 20:00:00,12729.0 -2012-03-20 21:00:00,13097.0 -2012-03-20 22:00:00,12996.0 -2012-03-20 23:00:00,12339.0 -2012-03-21 00:00:00,11318.0 -2012-03-19 01:00:00,9303.0 -2012-03-19 02:00:00,8823.0 -2012-03-19 03:00:00,8485.0 -2012-03-19 04:00:00,8264.0 -2012-03-19 05:00:00,8246.0 -2012-03-19 06:00:00,8421.0 -2012-03-19 07:00:00,9171.0 -2012-03-19 08:00:00,10427.0 -2012-03-19 09:00:00,11090.0 -2012-03-19 10:00:00,11553.0 -2012-03-19 11:00:00,11889.0 -2012-03-19 12:00:00,12116.0 -2012-03-19 13:00:00,12170.0 -2012-03-19 14:00:00,12187.0 -2012-03-19 15:00:00,12185.0 -2012-03-19 16:00:00,12151.0 -2012-03-19 17:00:00,12312.0 -2012-03-19 18:00:00,12375.0 -2012-03-19 19:00:00,12203.0 -2012-03-19 20:00:00,11957.0 -2012-03-19 21:00:00,12476.0 -2012-03-19 22:00:00,12383.0 -2012-03-19 23:00:00,11834.0 -2012-03-20 00:00:00,10936.0 -2012-03-18 01:00:00,9258.0 -2012-03-18 02:00:00,8718.0 -2012-03-18 03:00:00,8270.0 -2012-03-18 04:00:00,7947.0 -2012-03-18 05:00:00,7748.0 -2012-03-18 06:00:00,7774.0 -2012-03-18 07:00:00,7824.0 -2012-03-18 08:00:00,8000.0 -2012-03-18 09:00:00,8053.0 -2012-03-18 10:00:00,8412.0 -2012-03-18 11:00:00,8914.0 -2012-03-18 12:00:00,9346.0 -2012-03-18 13:00:00,9683.0 -2012-03-18 14:00:00,9903.0 -2012-03-18 15:00:00,10059.0 -2012-03-18 16:00:00,10197.0 -2012-03-18 17:00:00,10257.0 -2012-03-18 18:00:00,10328.0 -2012-03-18 19:00:00,10328.0 -2012-03-18 20:00:00,10323.0 -2012-03-18 21:00:00,10932.0 -2012-03-18 22:00:00,10981.0 -2012-03-18 23:00:00,10631.0 -2012-03-19 00:00:00,9975.0 -2012-03-17 01:00:00,9701.0 -2012-03-17 02:00:00,9005.0 -2012-03-17 03:00:00,8565.0 -2012-03-17 04:00:00,8294.0 -2012-03-17 05:00:00,8116.0 -2012-03-17 06:00:00,8114.0 -2012-03-17 07:00:00,8253.0 -2012-03-17 08:00:00,8676.0 -2012-03-17 09:00:00,8860.0 -2012-03-17 10:00:00,9429.0 -2012-03-17 11:00:00,9919.0 -2012-03-17 12:00:00,10288.0 -2012-03-17 13:00:00,10499.0 -2012-03-17 14:00:00,10554.0 -2012-03-17 15:00:00,10571.0 -2012-03-17 16:00:00,10542.0 -2012-03-17 17:00:00,10587.0 -2012-03-17 18:00:00,10583.0 -2012-03-17 19:00:00,10522.0 -2012-03-17 20:00:00,10430.0 -2012-03-17 21:00:00,10899.0 -2012-03-17 22:00:00,10849.0 -2012-03-17 23:00:00,10490.0 -2012-03-18 00:00:00,9923.0 -2012-03-16 01:00:00,9545.0 -2012-03-16 02:00:00,8921.0 -2012-03-16 03:00:00,8542.0 -2012-03-16 04:00:00,8303.0 -2012-03-16 05:00:00,8192.0 -2012-03-16 06:00:00,8347.0 -2012-03-16 07:00:00,8911.0 -2012-03-16 08:00:00,10084.0 -2012-03-16 09:00:00,10604.0 -2012-03-16 10:00:00,10911.0 -2012-03-16 11:00:00,11164.0 -2012-03-16 12:00:00,11471.0 -2012-03-16 13:00:00,11700.0 -2012-03-16 14:00:00,11827.0 -2012-03-16 15:00:00,12073.0 -2012-03-16 16:00:00,12094.0 -2012-03-16 17:00:00,12011.0 -2012-03-16 18:00:00,11846.0 -2012-03-16 19:00:00,11606.0 -2012-03-16 20:00:00,11392.0 -2012-03-16 21:00:00,11855.0 -2012-03-16 22:00:00,11721.0 -2012-03-16 23:00:00,11319.0 -2012-03-17 00:00:00,10518.0 -2012-03-15 01:00:00,9757.0 -2012-03-15 02:00:00,9139.0 -2012-03-15 03:00:00,8764.0 -2012-03-15 04:00:00,8504.0 -2012-03-15 05:00:00,8413.0 -2012-03-15 06:00:00,8538.0 -2012-03-15 07:00:00,9137.0 -2012-03-15 08:00:00,10355.0 -2012-03-15 09:00:00,10965.0 -2012-03-15 10:00:00,11337.0 -2012-03-15 11:00:00,11629.0 -2012-03-15 12:00:00,11940.0 -2012-03-15 13:00:00,12111.0 -2012-03-15 14:00:00,12291.0 -2012-03-15 15:00:00,12480.0 -2012-03-15 16:00:00,12550.0 -2012-03-15 17:00:00,12508.0 -2012-03-15 18:00:00,12384.0 -2012-03-15 19:00:00,12126.0 -2012-03-15 20:00:00,11858.0 -2012-03-15 21:00:00,12233.0 -2012-03-15 22:00:00,11960.0 -2012-03-15 23:00:00,11347.0 -2012-03-16 00:00:00,10352.0 -2012-03-14 01:00:00,9327.0 -2012-03-14 02:00:00,8760.0 -2012-03-14 03:00:00,8448.0 -2012-03-14 04:00:00,8240.0 -2012-03-14 05:00:00,8218.0 -2012-03-14 06:00:00,8372.0 -2012-03-14 07:00:00,9040.0 -2012-03-14 08:00:00,10292.0 -2012-03-14 09:00:00,10843.0 -2012-03-14 10:00:00,11104.0 -2012-03-14 11:00:00,11306.0 -2012-03-14 12:00:00,11528.0 -2012-03-14 13:00:00,11634.0 -2012-03-14 14:00:00,11807.0 -2012-03-14 15:00:00,11928.0 -2012-03-14 16:00:00,11955.0 -2012-03-14 17:00:00,11884.0 -2012-03-14 18:00:00,11798.0 -2012-03-14 19:00:00,11612.0 -2012-03-14 20:00:00,11465.0 -2012-03-14 21:00:00,12062.0 -2012-03-14 22:00:00,11966.0 -2012-03-14 23:00:00,11462.0 -2012-03-15 00:00:00,10625.0 -2012-03-13 01:00:00,9401.0 -2012-03-13 02:00:00,8844.0 -2012-03-13 03:00:00,8531.0 -2012-03-13 04:00:00,8305.0 -2012-03-13 05:00:00,8244.0 -2012-03-13 06:00:00,8444.0 -2012-03-13 07:00:00,9086.0 -2012-03-13 08:00:00,10347.0 -2012-03-13 09:00:00,10910.0 -2012-03-13 10:00:00,11055.0 -2012-03-13 11:00:00,11100.0 -2012-03-13 12:00:00,11266.0 -2012-03-13 13:00:00,11328.0 -2012-03-13 14:00:00,11322.0 -2012-03-13 15:00:00,11358.0 -2012-03-13 16:00:00,11257.0 -2012-03-13 17:00:00,11134.0 -2012-03-13 18:00:00,10995.0 -2012-03-13 19:00:00,10854.0 -2012-03-13 20:00:00,10780.0 -2012-03-13 21:00:00,11465.0 -2012-03-13 22:00:00,11368.0 -2012-03-13 23:00:00,10889.0 -2012-03-14 00:00:00,10109.0 -2012-03-12 01:00:00,8814.0 -2012-03-12 02:00:00,8387.0 -2012-03-12 03:00:00,8195.0 -2012-03-12 04:00:00,8070.0 -2012-03-12 05:00:00,8074.0 -2012-03-12 06:00:00,8256.0 -2012-03-12 07:00:00,8934.0 -2012-03-12 08:00:00,10179.0 -2012-03-12 09:00:00,11120.0 -2012-03-12 10:00:00,11246.0 -2012-03-12 11:00:00,11377.0 -2012-03-12 12:00:00,11550.0 -2012-03-12 13:00:00,11581.0 -2012-03-12 14:00:00,11569.0 -2012-03-12 15:00:00,11593.0 -2012-03-12 16:00:00,11454.0 -2012-03-12 17:00:00,11312.0 -2012-03-12 18:00:00,11159.0 -2012-03-12 19:00:00,10972.0 -2012-03-12 20:00:00,10919.0 -2012-03-12 21:00:00,11563.0 -2012-03-12 22:00:00,11475.0 -2012-03-12 23:00:00,10996.0 -2012-03-13 00:00:00,10167.0 -2012-03-11 01:00:00,9158.0 -2012-03-11 02:00:00,8773.0 -2012-03-11 04:00:00,8502.0 -2012-03-11 05:00:00,8387.0 -2012-03-11 06:00:00,8293.0 -2012-03-11 07:00:00,8428.0 -2012-03-11 08:00:00,8674.0 -2012-03-11 09:00:00,8684.0 -2012-03-11 10:00:00,8808.0 -2012-03-11 11:00:00,8953.0 -2012-03-11 12:00:00,9006.0 -2012-03-11 13:00:00,9116.0 -2012-03-11 14:00:00,9068.0 -2012-03-11 15:00:00,9041.0 -2012-03-11 16:00:00,8948.0 -2012-03-11 17:00:00,8944.0 -2012-03-11 18:00:00,8934.0 -2012-03-11 19:00:00,9145.0 -2012-03-11 20:00:00,9452.0 -2012-03-11 21:00:00,10199.0 -2012-03-11 22:00:00,10173.0 -2012-03-11 23:00:00,9925.0 -2012-03-12 00:00:00,9348.0 -2012-03-10 01:00:00,10302.0 -2012-03-10 02:00:00,9838.0 -2012-03-10 03:00:00,9530.0 -2012-03-10 04:00:00,9318.0 -2012-03-10 05:00:00,9257.0 -2012-03-10 06:00:00,9351.0 -2012-03-10 07:00:00,9625.0 -2012-03-10 08:00:00,9819.0 -2012-03-10 09:00:00,10168.0 -2012-03-10 10:00:00,10426.0 -2012-03-10 11:00:00,10597.0 -2012-03-10 12:00:00,10598.0 -2012-03-10 13:00:00,10442.0 -2012-03-10 14:00:00,10209.0 -2012-03-10 15:00:00,9919.0 -2012-03-10 16:00:00,9676.0 -2012-03-10 17:00:00,9523.0 -2012-03-10 18:00:00,9542.0 -2012-03-10 19:00:00,9745.0 -2012-03-10 20:00:00,10601.0 -2012-03-10 21:00:00,10553.0 -2012-03-10 22:00:00,10412.0 -2012-03-10 23:00:00,10106.0 -2012-03-11 00:00:00,9652.0 -2012-03-09 01:00:00,10103.0 -2012-03-09 02:00:00,9609.0 -2012-03-09 03:00:00,9343.0 -2012-03-09 04:00:00,9193.0 -2012-03-09 05:00:00,9213.0 -2012-03-09 06:00:00,9493.0 -2012-03-09 07:00:00,10265.0 -2012-03-09 08:00:00,11179.0 -2012-03-09 09:00:00,11671.0 -2012-03-09 10:00:00,11962.0 -2012-03-09 11:00:00,12024.0 -2012-03-09 12:00:00,12088.0 -2012-03-09 13:00:00,12018.0 -2012-03-09 14:00:00,11902.0 -2012-03-09 15:00:00,11779.0 -2012-03-09 16:00:00,11584.0 -2012-03-09 17:00:00,11394.0 -2012-03-09 18:00:00,11370.0 -2012-03-09 19:00:00,11592.0 -2012-03-09 20:00:00,12328.0 -2012-03-09 21:00:00,12300.0 -2012-03-09 22:00:00,12003.0 -2012-03-09 23:00:00,11671.0 -2012-03-10 00:00:00,11007.0 -2012-03-08 01:00:00,9414.0 -2012-03-08 02:00:00,8935.0 -2012-03-08 03:00:00,8678.0 -2012-03-08 04:00:00,8486.0 -2012-03-08 05:00:00,8454.0 -2012-03-08 06:00:00,8682.0 -2012-03-08 07:00:00,9428.0 -2012-03-08 08:00:00,10664.0 -2012-03-08 09:00:00,11413.0 -2012-03-08 10:00:00,11702.0 -2012-03-08 11:00:00,11756.0 -2012-03-08 12:00:00,11750.0 -2012-03-08 13:00:00,11679.0 -2012-03-08 14:00:00,11584.0 -2012-03-08 15:00:00,11490.0 -2012-03-08 16:00:00,11367.0 -2012-03-08 17:00:00,11330.0 -2012-03-08 18:00:00,11333.0 -2012-03-08 19:00:00,11599.0 -2012-03-08 20:00:00,12314.0 -2012-03-08 21:00:00,12325.0 -2012-03-08 22:00:00,12079.0 -2012-03-08 23:00:00,11588.0 -2012-03-09 00:00:00,10803.0 -2012-03-07 01:00:00,9534.0 -2012-03-07 02:00:00,9040.0 -2012-03-07 03:00:00,8762.0 -2012-03-07 04:00:00,8595.0 -2012-03-07 05:00:00,8574.0 -2012-03-07 06:00:00,8806.0 -2012-03-07 07:00:00,9495.0 -2012-03-07 08:00:00,10507.0 -2012-03-07 09:00:00,11072.0 -2012-03-07 10:00:00,11313.0 -2012-03-07 11:00:00,11408.0 -2012-03-07 12:00:00,11488.0 -2012-03-07 13:00:00,11490.0 -2012-03-07 14:00:00,11456.0 -2012-03-07 15:00:00,11416.0 -2012-03-07 16:00:00,11349.0 -2012-03-07 17:00:00,11248.0 -2012-03-07 18:00:00,11398.0 -2012-03-07 19:00:00,11695.0 -2012-03-07 20:00:00,12044.0 -2012-03-07 21:00:00,11884.0 -2012-03-07 22:00:00,11538.0 -2012-03-07 23:00:00,10952.0 -2012-03-08 00:00:00,10162.0 -2012-03-06 01:00:00,10473.0 -2012-03-06 02:00:00,9989.0 -2012-03-06 03:00:00,9716.0 -2012-03-06 04:00:00,9537.0 -2012-03-06 05:00:00,9524.0 -2012-03-06 06:00:00,9774.0 -2012-03-06 07:00:00,10538.0 -2012-03-06 08:00:00,11516.0 -2012-03-06 09:00:00,11931.0 -2012-03-06 10:00:00,11997.0 -2012-03-06 11:00:00,11915.0 -2012-03-06 12:00:00,11857.0 -2012-03-06 13:00:00,11723.0 -2012-03-06 14:00:00,11644.0 -2012-03-06 15:00:00,11573.0 -2012-03-06 16:00:00,11424.0 -2012-03-06 17:00:00,11210.0 -2012-03-06 18:00:00,11161.0 -2012-03-06 19:00:00,11379.0 -2012-03-06 20:00:00,12001.0 -2012-03-06 21:00:00,11886.0 -2012-03-06 22:00:00,11588.0 -2012-03-06 23:00:00,11007.0 -2012-03-07 00:00:00,10188.0 -2012-03-05 01:00:00,10100.0 -2012-03-05 02:00:00,9792.0 -2012-03-05 03:00:00,9602.0 -2012-03-05 04:00:00,9570.0 -2012-03-05 05:00:00,9625.0 -2012-03-05 06:00:00,9966.0 -2012-03-05 07:00:00,10787.0 -2012-03-05 08:00:00,11750.0 -2012-03-05 09:00:00,12153.0 -2012-03-05 10:00:00,12296.0 -2012-03-05 11:00:00,12336.0 -2012-03-05 12:00:00,12440.0 -2012-03-05 13:00:00,12396.0 -2012-03-05 14:00:00,12252.0 -2012-03-05 15:00:00,12148.0 -2012-03-05 16:00:00,11945.0 -2012-03-05 17:00:00,11839.0 -2012-03-05 18:00:00,11952.0 -2012-03-05 19:00:00,12411.0 -2012-03-05 20:00:00,12930.0 -2012-03-05 21:00:00,12836.0 -2012-03-05 22:00:00,12573.0 -2012-03-05 23:00:00,12058.0 -2012-03-06 00:00:00,11222.0 -2012-03-04 01:00:00,10086.0 -2012-03-04 02:00:00,9643.0 -2012-03-04 03:00:00,9329.0 -2012-03-04 04:00:00,9146.0 -2012-03-04 05:00:00,9065.0 -2012-03-04 06:00:00,8997.0 -2012-03-04 07:00:00,9229.0 -2012-03-04 08:00:00,9364.0 -2012-03-04 09:00:00,9465.0 -2012-03-04 10:00:00,9842.0 -2012-03-04 11:00:00,10071.0 -2012-03-04 12:00:00,10160.0 -2012-03-04 13:00:00,10146.0 -2012-03-04 14:00:00,10153.0 -2012-03-04 15:00:00,10032.0 -2012-03-04 16:00:00,10075.0 -2012-03-04 17:00:00,10158.0 -2012-03-04 18:00:00,10477.0 -2012-03-04 19:00:00,11124.0 -2012-03-04 20:00:00,11721.0 -2012-03-04 21:00:00,11695.0 -2012-03-04 22:00:00,11530.0 -2012-03-04 23:00:00,11161.0 -2012-03-05 00:00:00,10635.0 -2012-03-03 01:00:00,10466.0 -2012-03-03 02:00:00,9897.0 -2012-03-03 03:00:00,9661.0 -2012-03-03 04:00:00,9453.0 -2012-03-03 05:00:00,9374.0 -2012-03-03 06:00:00,9429.0 -2012-03-03 07:00:00,9724.0 -2012-03-03 08:00:00,10122.0 -2012-03-03 09:00:00,10386.0 -2012-03-03 10:00:00,10878.0 -2012-03-03 11:00:00,11174.0 -2012-03-03 12:00:00,11362.0 -2012-03-03 13:00:00,11259.0 -2012-03-03 14:00:00,11159.0 -2012-03-03 15:00:00,11014.0 -2012-03-03 16:00:00,10915.0 -2012-03-03 17:00:00,10828.0 -2012-03-03 18:00:00,10989.0 -2012-03-03 19:00:00,11386.0 -2012-03-03 20:00:00,11831.0 -2012-03-03 21:00:00,11714.0 -2012-03-03 22:00:00,11521.0 -2012-03-03 23:00:00,11197.0 -2012-03-04 00:00:00,10672.0 -2012-03-02 01:00:00,10192.0 -2012-03-02 02:00:00,9686.0 -2012-03-02 03:00:00,9411.0 -2012-03-02 04:00:00,9262.0 -2012-03-02 05:00:00,9192.0 -2012-03-02 06:00:00,9459.0 -2012-03-02 07:00:00,10181.0 -2012-03-02 08:00:00,11203.0 -2012-03-02 09:00:00,11793.0 -2012-03-02 10:00:00,12094.0 -2012-03-02 11:00:00,12294.0 -2012-03-02 12:00:00,12375.0 -2012-03-02 13:00:00,12406.0 -2012-03-02 14:00:00,12331.0 -2012-03-02 15:00:00,12225.0 -2012-03-02 16:00:00,12217.0 -2012-03-02 17:00:00,12322.0 -2012-03-02 18:00:00,12417.0 -2012-03-02 19:00:00,12664.0 -2012-03-02 20:00:00,12878.0 -2012-03-02 21:00:00,12683.0 -2012-03-02 22:00:00,12349.0 -2012-03-02 23:00:00,11825.0 -2012-03-03 00:00:00,11165.0 -2012-03-01 01:00:00,10241.0 -2012-03-01 02:00:00,9707.0 -2012-03-01 03:00:00,9419.0 -2012-03-01 04:00:00,9225.0 -2012-03-01 05:00:00,9199.0 -2012-03-01 06:00:00,9428.0 -2012-03-01 07:00:00,10145.0 -2012-03-01 08:00:00,11342.0 -2012-03-01 09:00:00,11939.0 -2012-03-01 10:00:00,12197.0 -2012-03-01 11:00:00,12315.0 -2012-03-01 12:00:00,12382.0 -2012-03-01 13:00:00,12325.0 -2012-03-01 14:00:00,12295.0 -2012-03-01 15:00:00,12331.0 -2012-03-01 16:00:00,12230.0 -2012-03-01 17:00:00,12112.0 -2012-03-01 18:00:00,12185.0 -2012-03-01 19:00:00,12550.0 -2012-03-01 20:00:00,12771.0 -2012-03-01 21:00:00,12587.0 -2012-03-01 22:00:00,12288.0 -2012-03-01 23:00:00,11757.0 -2012-03-02 00:00:00,10947.0 -2012-02-29 01:00:00,10327.0 -2012-02-29 02:00:00,9826.0 -2012-02-29 03:00:00,9513.0 -2012-02-29 04:00:00,9290.0 -2012-02-29 05:00:00,9243.0 -2012-02-29 06:00:00,9434.0 -2012-02-29 07:00:00,10100.0 -2012-02-29 08:00:00,11176.0 -2012-02-29 09:00:00,11482.0 -2012-02-29 10:00:00,11521.0 -2012-02-29 11:00:00,11541.0 -2012-02-29 12:00:00,11599.0 -2012-02-29 13:00:00,11598.0 -2012-02-29 14:00:00,11504.0 -2012-02-29 15:00:00,11518.0 -2012-02-29 16:00:00,11392.0 -2012-02-29 17:00:00,11425.0 -2012-02-29 18:00:00,11789.0 -2012-02-29 19:00:00,12303.0 -2012-02-29 20:00:00,12665.0 -2012-02-29 21:00:00,12535.0 -2012-02-29 22:00:00,12276.0 -2012-02-29 23:00:00,11809.0 -2012-03-01 00:00:00,10983.0 -2012-02-28 01:00:00,10571.0 -2012-02-28 02:00:00,10115.0 -2012-02-28 03:00:00,9888.0 -2012-02-28 04:00:00,9753.0 -2012-02-28 05:00:00,9739.0 -2012-02-28 06:00:00,9988.0 -2012-02-28 07:00:00,10758.0 -2012-02-28 08:00:00,11837.0 -2012-02-28 09:00:00,12293.0 -2012-02-28 10:00:00,12444.0 -2012-02-28 11:00:00,12519.0 -2012-02-28 12:00:00,12483.0 -2012-02-28 13:00:00,12310.0 -2012-02-28 14:00:00,12288.0 -2012-02-28 15:00:00,12219.0 -2012-02-28 16:00:00,12046.0 -2012-02-28 17:00:00,12009.0 -2012-02-28 18:00:00,12237.0 -2012-02-28 19:00:00,12684.0 -2012-02-28 20:00:00,12947.0 -2012-02-28 21:00:00,12772.0 -2012-02-28 22:00:00,12477.0 -2012-02-28 23:00:00,11921.0 -2012-02-29 00:00:00,11083.0 -2012-02-27 01:00:00,9564.0 -2012-02-27 02:00:00,9196.0 -2012-02-27 03:00:00,9042.0 -2012-02-27 04:00:00,8943.0 -2012-02-27 05:00:00,9075.0 -2012-02-27 06:00:00,9475.0 -2012-02-27 07:00:00,10321.0 -2012-02-27 08:00:00,11495.0 -2012-02-27 09:00:00,12105.0 -2012-02-27 10:00:00,12360.0 -2012-02-27 11:00:00,12532.0 -2012-02-27 12:00:00,12427.0 -2012-02-27 13:00:00,12341.0 -2012-02-27 14:00:00,12224.0 -2012-02-27 15:00:00,12097.0 -2012-02-27 16:00:00,11938.0 -2012-02-27 17:00:00,11839.0 -2012-02-27 18:00:00,11910.0 -2012-02-27 19:00:00,12409.0 -2012-02-27 20:00:00,13003.0 -2012-02-27 21:00:00,12920.0 -2012-02-27 22:00:00,12654.0 -2012-02-27 23:00:00,12133.0 -2012-02-28 00:00:00,11298.0 -2012-02-26 01:00:00,10482.0 -2012-02-26 02:00:00,10102.0 -2012-02-26 03:00:00,9740.0 -2012-02-26 04:00:00,9638.0 -2012-02-26 05:00:00,9570.0 -2012-02-26 06:00:00,9561.0 -2012-02-26 07:00:00,9760.0 -2012-02-26 08:00:00,9868.0 -2012-02-26 09:00:00,9864.0 -2012-02-26 10:00:00,10118.0 -2012-02-26 11:00:00,10184.0 -2012-02-26 12:00:00,10212.0 -2012-02-26 13:00:00,10188.0 -2012-02-26 14:00:00,10059.0 -2012-02-26 15:00:00,9888.0 -2012-02-26 16:00:00,9769.0 -2012-02-26 17:00:00,9679.0 -2012-02-26 18:00:00,9839.0 -2012-02-26 19:00:00,10431.0 -2012-02-26 20:00:00,11252.0 -2012-02-26 21:00:00,11162.0 -2012-02-26 22:00:00,10948.0 -2012-02-26 23:00:00,10605.0 -2012-02-27 00:00:00,10042.0 -2012-02-25 01:00:00,10882.0 -2012-02-25 02:00:00,10356.0 -2012-02-25 03:00:00,10046.0 -2012-02-25 04:00:00,9850.0 -2012-02-25 05:00:00,9859.0 -2012-02-25 06:00:00,9997.0 -2012-02-25 07:00:00,10290.0 -2012-02-25 08:00:00,10678.0 -2012-02-25 09:00:00,10924.0 -2012-02-25 10:00:00,11146.0 -2012-02-25 11:00:00,11287.0 -2012-02-25 12:00:00,11310.0 -2012-02-25 13:00:00,11229.0 -2012-02-25 14:00:00,11003.0 -2012-02-25 15:00:00,10775.0 -2012-02-25 16:00:00,10533.0 -2012-02-25 17:00:00,10485.0 -2012-02-25 18:00:00,10612.0 -2012-02-25 19:00:00,11223.0 -2012-02-25 20:00:00,11985.0 -2012-02-25 21:00:00,11936.0 -2012-02-25 22:00:00,11823.0 -2012-02-25 23:00:00,11496.0 -2012-02-26 00:00:00,11040.0 -2012-02-24 01:00:00,10532.0 -2012-02-24 02:00:00,10029.0 -2012-02-24 03:00:00,9758.0 -2012-02-24 04:00:00,9635.0 -2012-02-24 05:00:00,9635.0 -2012-02-24 06:00:00,9894.0 -2012-02-24 07:00:00,10547.0 -2012-02-24 08:00:00,11608.0 -2012-02-24 09:00:00,12135.0 -2012-02-24 10:00:00,12377.0 -2012-02-24 11:00:00,12463.0 -2012-02-24 12:00:00,12559.0 -2012-02-24 13:00:00,12511.0 -2012-02-24 14:00:00,12454.0 -2012-02-24 15:00:00,12368.0 -2012-02-24 16:00:00,12265.0 -2012-02-24 17:00:00,12256.0 -2012-02-24 18:00:00,12426.0 -2012-02-24 19:00:00,12913.0 -2012-02-24 20:00:00,13154.0 -2012-02-24 21:00:00,12935.0 -2012-02-24 22:00:00,12664.0 -2012-02-24 23:00:00,12215.0 -2012-02-25 00:00:00,11538.0 -2012-02-23 01:00:00,10182.0 -2012-02-23 02:00:00,9700.0 -2012-02-23 03:00:00,9404.0 -2012-02-23 04:00:00,9271.0 -2012-02-23 05:00:00,9259.0 -2012-02-23 06:00:00,9529.0 -2012-02-23 07:00:00,10331.0 -2012-02-23 08:00:00,11490.0 -2012-02-23 09:00:00,11989.0 -2012-02-23 10:00:00,12255.0 -2012-02-23 11:00:00,12330.0 -2012-02-23 12:00:00,12461.0 -2012-02-23 13:00:00,12446.0 -2012-02-23 14:00:00,12495.0 -2012-02-23 15:00:00,12552.0 -2012-02-23 16:00:00,12483.0 -2012-02-23 17:00:00,12496.0 -2012-02-23 18:00:00,12709.0 -2012-02-23 19:00:00,13144.0 -2012-02-23 20:00:00,13159.0 -2012-02-23 21:00:00,12925.0 -2012-02-23 22:00:00,12620.0 -2012-02-23 23:00:00,12085.0 -2012-02-24 00:00:00,11293.0 -2012-02-22 01:00:00,10331.0 -2012-02-22 02:00:00,9828.0 -2012-02-22 03:00:00,9547.0 -2012-02-22 04:00:00,9377.0 -2012-02-22 05:00:00,9357.0 -2012-02-22 06:00:00,9547.0 -2012-02-22 07:00:00,10251.0 -2012-02-22 08:00:00,11530.0 -2012-02-22 09:00:00,12048.0 -2012-02-22 10:00:00,12197.0 -2012-02-22 11:00:00,12266.0 -2012-02-22 12:00:00,12296.0 -2012-02-22 13:00:00,12194.0 -2012-02-22 14:00:00,12021.0 -2012-02-22 15:00:00,11982.0 -2012-02-22 16:00:00,11875.0 -2012-02-22 17:00:00,11825.0 -2012-02-22 18:00:00,11986.0 -2012-02-22 19:00:00,12433.0 -2012-02-22 20:00:00,12674.0 -2012-02-22 21:00:00,12475.0 -2012-02-22 22:00:00,12194.0 -2012-02-22 23:00:00,11714.0 -2012-02-23 00:00:00,10911.0 -2012-02-21 01:00:00,10264.0 -2012-02-21 02:00:00,9768.0 -2012-02-21 03:00:00,9479.0 -2012-02-21 04:00:00,9327.0 -2012-02-21 05:00:00,9330.0 -2012-02-21 06:00:00,9631.0 -2012-02-21 07:00:00,10431.0 -2012-02-21 08:00:00,11632.0 -2012-02-21 09:00:00,12170.0 -2012-02-21 10:00:00,12395.0 -2012-02-21 11:00:00,12467.0 -2012-02-21 12:00:00,12537.0 -2012-02-21 13:00:00,12550.0 -2012-02-21 14:00:00,12514.0 -2012-02-21 15:00:00,12603.0 -2012-02-21 16:00:00,12493.0 -2012-02-21 17:00:00,12301.0 -2012-02-21 18:00:00,12244.0 -2012-02-21 19:00:00,12670.0 -2012-02-21 20:00:00,12967.0 -2012-02-21 21:00:00,12795.0 -2012-02-21 22:00:00,12493.0 -2012-02-21 23:00:00,11978.0 -2012-02-22 00:00:00,11103.0 -2012-02-20 01:00:00,9966.0 -2012-02-20 02:00:00,9663.0 -2012-02-20 03:00:00,9461.0 -2012-02-20 04:00:00,9403.0 -2012-02-20 05:00:00,9443.0 -2012-02-20 06:00:00,9744.0 -2012-02-20 07:00:00,10389.0 -2012-02-20 08:00:00,11305.0 -2012-02-20 09:00:00,11678.0 -2012-02-20 10:00:00,11925.0 -2012-02-20 11:00:00,12015.0 -2012-02-20 12:00:00,12060.0 -2012-02-20 13:00:00,11970.0 -2012-02-20 14:00:00,11835.0 -2012-02-20 15:00:00,11752.0 -2012-02-20 16:00:00,11592.0 -2012-02-20 17:00:00,11536.0 -2012-02-20 18:00:00,11755.0 -2012-02-20 19:00:00,12490.0 -2012-02-20 20:00:00,12783.0 -2012-02-20 21:00:00,12633.0 -2012-02-20 22:00:00,12339.0 -2012-02-20 23:00:00,11836.0 -2012-02-21 00:00:00,11031.0 -2012-02-19 01:00:00,9976.0 -2012-02-19 02:00:00,9549.0 -2012-02-19 03:00:00,9289.0 -2012-02-19 04:00:00,9176.0 -2012-02-19 05:00:00,9084.0 -2012-02-19 06:00:00,9146.0 -2012-02-19 07:00:00,9297.0 -2012-02-19 08:00:00,9559.0 -2012-02-19 09:00:00,9519.0 -2012-02-19 10:00:00,9681.0 -2012-02-19 11:00:00,9809.0 -2012-02-19 12:00:00,9903.0 -2012-02-19 13:00:00,9879.0 -2012-02-19 14:00:00,9872.0 -2012-02-19 15:00:00,9738.0 -2012-02-19 16:00:00,9682.0 -2012-02-19 17:00:00,9618.0 -2012-02-19 18:00:00,9862.0 -2012-02-19 19:00:00,10616.0 -2012-02-19 20:00:00,11381.0 -2012-02-19 21:00:00,11321.0 -2012-02-19 22:00:00,11200.0 -2012-02-19 23:00:00,10899.0 -2012-02-20 00:00:00,10447.0 -2012-02-18 01:00:00,10155.0 -2012-02-18 02:00:00,9654.0 -2012-02-18 03:00:00,9390.0 -2012-02-18 04:00:00,9204.0 -2012-02-18 05:00:00,9178.0 -2012-02-18 06:00:00,9208.0 -2012-02-18 07:00:00,9554.0 -2012-02-18 08:00:00,10047.0 -2012-02-18 09:00:00,10213.0 -2012-02-18 10:00:00,10539.0 -2012-02-18 11:00:00,10687.0 -2012-02-18 12:00:00,10737.0 -2012-02-18 13:00:00,10661.0 -2012-02-18 14:00:00,10496.0 -2012-02-18 15:00:00,10271.0 -2012-02-18 16:00:00,10064.0 -2012-02-18 17:00:00,9985.0 -2012-02-18 18:00:00,10168.0 -2012-02-18 19:00:00,10833.0 -2012-02-18 20:00:00,11439.0 -2012-02-18 21:00:00,11353.0 -2012-02-18 22:00:00,11245.0 -2012-02-18 23:00:00,10955.0 -2012-02-19 00:00:00,10473.0 -2012-02-17 01:00:00,10429.0 -2012-02-17 02:00:00,9962.0 -2012-02-17 03:00:00,9629.0 -2012-02-17 04:00:00,9498.0 -2012-02-17 05:00:00,9506.0 -2012-02-17 06:00:00,9778.0 -2012-02-17 07:00:00,10505.0 -2012-02-17 08:00:00,11702.0 -2012-02-17 09:00:00,12051.0 -2012-02-17 10:00:00,12125.0 -2012-02-17 11:00:00,12073.0 -2012-02-17 12:00:00,12133.0 -2012-02-17 13:00:00,12016.0 -2012-02-17 14:00:00,11907.0 -2012-02-17 15:00:00,11785.0 -2012-02-17 16:00:00,11601.0 -2012-02-17 17:00:00,11455.0 -2012-02-17 18:00:00,11580.0 -2012-02-17 19:00:00,12086.0 -2012-02-17 20:00:00,12332.0 -2012-02-17 21:00:00,12133.0 -2012-02-17 22:00:00,11823.0 -2012-02-17 23:00:00,11488.0 -2012-02-18 00:00:00,10815.0 -2012-02-16 01:00:00,10323.0 -2012-02-16 02:00:00,9800.0 -2012-02-16 03:00:00,9489.0 -2012-02-16 04:00:00,9249.0 -2012-02-16 05:00:00,9243.0 -2012-02-16 06:00:00,9483.0 -2012-02-16 07:00:00,10216.0 -2012-02-16 08:00:00,11442.0 -2012-02-16 09:00:00,11946.0 -2012-02-16 10:00:00,12167.0 -2012-02-16 11:00:00,12138.0 -2012-02-16 12:00:00,12125.0 -2012-02-16 13:00:00,11993.0 -2012-02-16 14:00:00,11901.0 -2012-02-16 15:00:00,11886.0 -2012-02-16 16:00:00,11714.0 -2012-02-16 17:00:00,11573.0 -2012-02-16 18:00:00,11654.0 -2012-02-16 19:00:00,12290.0 -2012-02-16 20:00:00,12769.0 -2012-02-16 21:00:00,12700.0 -2012-02-16 22:00:00,12438.0 -2012-02-16 23:00:00,11987.0 -2012-02-17 00:00:00,11189.0 -2012-02-15 01:00:00,10558.0 -2012-02-15 02:00:00,10022.0 -2012-02-15 03:00:00,9769.0 -2012-02-15 04:00:00,9606.0 -2012-02-15 05:00:00,9580.0 -2012-02-15 06:00:00,9837.0 -2012-02-15 07:00:00,10575.0 -2012-02-15 08:00:00,11766.0 -2012-02-15 09:00:00,12206.0 -2012-02-15 10:00:00,12308.0 -2012-02-15 11:00:00,12189.0 -2012-02-15 12:00:00,12232.0 -2012-02-15 13:00:00,12239.0 -2012-02-15 14:00:00,12211.0 -2012-02-15 15:00:00,12304.0 -2012-02-15 16:00:00,12245.0 -2012-02-15 17:00:00,12295.0 -2012-02-15 18:00:00,12539.0 -2012-02-15 19:00:00,13010.0 -2012-02-15 20:00:00,13010.0 -2012-02-15 21:00:00,12810.0 -2012-02-15 22:00:00,12539.0 -2012-02-15 23:00:00,12047.0 -2012-02-16 00:00:00,11167.0 -2012-02-14 01:00:00,10952.0 -2012-02-14 02:00:00,10435.0 -2012-02-14 03:00:00,10085.0 -2012-02-14 04:00:00,9926.0 -2012-02-14 05:00:00,9896.0 -2012-02-14 06:00:00,10144.0 -2012-02-14 07:00:00,10870.0 -2012-02-14 08:00:00,12085.0 -2012-02-14 09:00:00,12565.0 -2012-02-14 10:00:00,12730.0 -2012-02-14 11:00:00,12794.0 -2012-02-14 12:00:00,12835.0 -2012-02-14 13:00:00,12780.0 -2012-02-14 14:00:00,12658.0 -2012-02-14 15:00:00,12651.0 -2012-02-14 16:00:00,12577.0 -2012-02-14 17:00:00,12570.0 -2012-02-14 18:00:00,12773.0 -2012-02-14 19:00:00,13235.0 -2012-02-14 20:00:00,13211.0 -2012-02-14 21:00:00,13001.0 -2012-02-14 22:00:00,12695.0 -2012-02-14 23:00:00,12128.0 -2012-02-15 00:00:00,11291.0 -2012-02-13 01:00:00,10471.0 -2012-02-13 02:00:00,10098.0 -2012-02-13 03:00:00,9944.0 -2012-02-13 04:00:00,9945.0 -2012-02-13 05:00:00,9940.0 -2012-02-13 06:00:00,10279.0 -2012-02-13 07:00:00,11144.0 -2012-02-13 08:00:00,12344.0 -2012-02-13 09:00:00,12883.0 -2012-02-13 10:00:00,13114.0 -2012-02-13 11:00:00,13186.0 -2012-02-13 12:00:00,13221.0 -2012-02-13 13:00:00,13144.0 -2012-02-13 14:00:00,13049.0 -2012-02-13 15:00:00,12993.0 -2012-02-13 16:00:00,12835.0 -2012-02-13 17:00:00,12720.0 -2012-02-13 18:00:00,12890.0 -2012-02-13 19:00:00,13513.0 -2012-02-13 20:00:00,13595.0 -2012-02-13 21:00:00,13435.0 -2012-02-13 22:00:00,13132.0 -2012-02-13 23:00:00,12596.0 -2012-02-14 00:00:00,11762.0 -2012-02-12 01:00:00,11294.0 -2012-02-12 02:00:00,10869.0 -2012-02-12 03:00:00,10548.0 -2012-02-12 04:00:00,10354.0 -2012-02-12 05:00:00,10323.0 -2012-02-12 06:00:00,10305.0 -2012-02-12 07:00:00,10479.0 -2012-02-12 08:00:00,10705.0 -2012-02-12 09:00:00,10670.0 -2012-02-12 10:00:00,10788.0 -2012-02-12 11:00:00,10852.0 -2012-02-12 12:00:00,10942.0 -2012-02-12 13:00:00,10885.0 -2012-02-12 14:00:00,10792.0 -2012-02-12 15:00:00,10611.0 -2012-02-12 16:00:00,10474.0 -2012-02-12 17:00:00,10419.0 -2012-02-12 18:00:00,10636.0 -2012-02-12 19:00:00,11546.0 -2012-02-12 20:00:00,12080.0 -2012-02-12 21:00:00,12060.0 -2012-02-12 22:00:00,11865.0 -2012-02-12 23:00:00,11548.0 -2012-02-13 00:00:00,11004.0 -2012-02-11 01:00:00,11664.0 -2012-02-11 02:00:00,11258.0 -2012-02-11 03:00:00,10932.0 -2012-02-11 04:00:00,10797.0 -2012-02-11 05:00:00,10684.0 -2012-02-11 06:00:00,10822.0 -2012-02-11 07:00:00,11134.0 -2012-02-11 08:00:00,11629.0 -2012-02-11 09:00:00,11773.0 -2012-02-11 10:00:00,12031.0 -2012-02-11 11:00:00,12144.0 -2012-02-11 12:00:00,12211.0 -2012-02-11 13:00:00,12077.0 -2012-02-11 14:00:00,11970.0 -2012-02-11 15:00:00,11733.0 -2012-02-11 16:00:00,11571.0 -2012-02-11 17:00:00,11510.0 -2012-02-11 18:00:00,11708.0 -2012-02-11 19:00:00,12525.0 -2012-02-11 20:00:00,12957.0 -2012-02-11 21:00:00,12874.0 -2012-02-11 22:00:00,12679.0 -2012-02-11 23:00:00,12373.0 -2012-02-12 00:00:00,11830.0 -2012-02-10 01:00:00,10717.0 -2012-02-10 02:00:00,10223.0 -2012-02-10 03:00:00,9924.0 -2012-02-10 04:00:00,9765.0 -2012-02-10 05:00:00,9721.0 -2012-02-10 06:00:00,9945.0 -2012-02-10 07:00:00,10624.0 -2012-02-10 08:00:00,11864.0 -2012-02-10 09:00:00,12538.0 -2012-02-10 10:00:00,12700.0 -2012-02-10 11:00:00,12756.0 -2012-02-10 12:00:00,12868.0 -2012-02-10 13:00:00,12870.0 -2012-02-10 14:00:00,12832.0 -2012-02-10 15:00:00,12882.0 -2012-02-10 16:00:00,12861.0 -2012-02-10 17:00:00,12866.0 -2012-02-10 18:00:00,13118.0 -2012-02-10 19:00:00,13716.0 -2012-02-10 20:00:00,13890.0 -2012-02-10 21:00:00,13737.0 -2012-02-10 22:00:00,13484.0 -2012-02-10 23:00:00,13089.0 -2012-02-11 00:00:00,12403.0 -2012-02-09 01:00:00,10875.0 -2012-02-09 02:00:00,10395.0 -2012-02-09 03:00:00,10161.0 -2012-02-09 04:00:00,10005.0 -2012-02-09 05:00:00,10045.0 -2012-02-09 06:00:00,10328.0 -2012-02-09 07:00:00,11119.0 -2012-02-09 08:00:00,12382.0 -2012-02-09 09:00:00,12790.0 -2012-02-09 10:00:00,12806.0 -2012-02-09 11:00:00,12740.0 -2012-02-09 12:00:00,12727.0 -2012-02-09 13:00:00,12622.0 -2012-02-09 14:00:00,12532.0 -2012-02-09 15:00:00,12432.0 -2012-02-09 16:00:00,12230.0 -2012-02-09 17:00:00,12157.0 -2012-02-09 18:00:00,12361.0 -2012-02-09 19:00:00,13182.0 -2012-02-09 20:00:00,13415.0 -2012-02-09 21:00:00,13247.0 -2012-02-09 22:00:00,12916.0 -2012-02-09 23:00:00,12365.0 -2012-02-10 00:00:00,11484.0 -2012-02-08 01:00:00,10778.0 -2012-02-08 02:00:00,10279.0 -2012-02-08 03:00:00,9995.0 -2012-02-08 04:00:00,9833.0 -2012-02-08 05:00:00,9814.0 -2012-02-08 06:00:00,10089.0 -2012-02-08 07:00:00,10840.0 -2012-02-08 08:00:00,12095.0 -2012-02-08 09:00:00,12616.0 -2012-02-08 10:00:00,12818.0 -2012-02-08 11:00:00,12800.0 -2012-02-08 12:00:00,12700.0 -2012-02-08 13:00:00,12544.0 -2012-02-08 14:00:00,12365.0 -2012-02-08 15:00:00,12337.0 -2012-02-08 16:00:00,12141.0 -2012-02-08 17:00:00,12068.0 -2012-02-08 18:00:00,12220.0 -2012-02-08 19:00:00,13022.0 -2012-02-08 20:00:00,13297.0 -2012-02-08 21:00:00,13170.0 -2012-02-08 22:00:00,12917.0 -2012-02-08 23:00:00,12432.0 -2012-02-09 00:00:00,11631.0 -2012-02-07 01:00:00,10613.0 -2012-02-07 02:00:00,10090.0 -2012-02-07 03:00:00,9807.0 -2012-02-07 04:00:00,9633.0 -2012-02-07 05:00:00,9618.0 -2012-02-07 06:00:00,9870.0 -2012-02-07 07:00:00,10593.0 -2012-02-07 08:00:00,11825.0 -2012-02-07 09:00:00,12442.0 -2012-02-07 10:00:00,12653.0 -2012-02-07 11:00:00,12830.0 -2012-02-07 12:00:00,12944.0 -2012-02-07 13:00:00,12947.0 -2012-02-07 14:00:00,12875.0 -2012-02-07 15:00:00,12895.0 -2012-02-07 16:00:00,12782.0 -2012-02-07 17:00:00,12832.0 -2012-02-07 18:00:00,13040.0 -2012-02-07 19:00:00,13599.0 -2012-02-07 20:00:00,13536.0 -2012-02-07 21:00:00,13296.0 -2012-02-07 22:00:00,12963.0 -2012-02-07 23:00:00,12421.0 -2012-02-08 00:00:00,11543.0 -2012-02-06 01:00:00,9696.0 -2012-02-06 02:00:00,9333.0 -2012-02-06 03:00:00,9158.0 -2012-02-06 04:00:00,9062.0 -2012-02-06 05:00:00,9209.0 -2012-02-06 06:00:00,9510.0 -2012-02-06 07:00:00,10360.0 -2012-02-06 08:00:00,11600.0 -2012-02-06 09:00:00,12341.0 -2012-02-06 10:00:00,12534.0 -2012-02-06 11:00:00,12660.0 -2012-02-06 12:00:00,12752.0 -2012-02-06 13:00:00,12704.0 -2012-02-06 14:00:00,12606.0 -2012-02-06 15:00:00,12619.0 -2012-02-06 16:00:00,12514.0 -2012-02-06 17:00:00,12515.0 -2012-02-06 18:00:00,12778.0 -2012-02-06 19:00:00,13422.0 -2012-02-06 20:00:00,13453.0 -2012-02-06 21:00:00,13252.0 -2012-02-06 22:00:00,12884.0 -2012-02-06 23:00:00,12294.0 -2012-02-07 00:00:00,11411.0 -2012-02-05 01:00:00,9831.0 -2012-02-05 02:00:00,9460.0 -2012-02-05 03:00:00,9142.0 -2012-02-05 04:00:00,8975.0 -2012-02-05 05:00:00,8962.0 -2012-02-05 06:00:00,8985.0 -2012-02-05 07:00:00,9124.0 -2012-02-05 08:00:00,9338.0 -2012-02-05 09:00:00,9385.0 -2012-02-05 10:00:00,9622.0 -2012-02-05 11:00:00,9825.0 -2012-02-05 12:00:00,9932.0 -2012-02-05 13:00:00,9988.0 -2012-02-05 14:00:00,9963.0 -2012-02-05 15:00:00,9833.0 -2012-02-05 16:00:00,9757.0 -2012-02-05 17:00:00,9653.0 -2012-02-05 18:00:00,9907.0 -2012-02-05 19:00:00,10811.0 -2012-02-05 20:00:00,11068.0 -2012-02-05 21:00:00,11021.0 -2012-02-05 22:00:00,10887.0 -2012-02-05 23:00:00,10699.0 -2012-02-06 00:00:00,10234.0 -2012-02-04 01:00:00,10328.0 -2012-02-04 02:00:00,9812.0 -2012-02-04 03:00:00,9455.0 -2012-02-04 04:00:00,9257.0 -2012-02-04 05:00:00,9139.0 -2012-02-04 06:00:00,9156.0 -2012-02-04 07:00:00,9396.0 -2012-02-04 08:00:00,9913.0 -2012-02-04 09:00:00,10216.0 -2012-02-04 10:00:00,10595.0 -2012-02-04 11:00:00,10858.0 -2012-02-04 12:00:00,10966.0 -2012-02-04 13:00:00,10823.0 -2012-02-04 14:00:00,10634.0 -2012-02-04 15:00:00,10411.0 -2012-02-04 16:00:00,10319.0 -2012-02-04 17:00:00,10287.0 -2012-02-04 18:00:00,10585.0 -2012-02-04 19:00:00,11329.0 -2012-02-04 20:00:00,11520.0 -2012-02-04 21:00:00,11342.0 -2012-02-04 22:00:00,11200.0 -2012-02-04 23:00:00,10880.0 -2012-02-05 00:00:00,10425.0 -2012-02-03 01:00:00,10174.0 -2012-02-03 02:00:00,9658.0 -2012-02-03 03:00:00,9393.0 -2012-02-03 04:00:00,9217.0 -2012-02-03 05:00:00,9189.0 -2012-02-03 06:00:00,9446.0 -2012-02-03 07:00:00,10164.0 -2012-02-03 08:00:00,11370.0 -2012-02-03 09:00:00,11981.0 -2012-02-03 10:00:00,12190.0 -2012-02-03 11:00:00,12285.0 -2012-02-03 12:00:00,12338.0 -2012-02-03 13:00:00,12265.0 -2012-02-03 14:00:00,12168.0 -2012-02-03 15:00:00,12158.0 -2012-02-03 16:00:00,12059.0 -2012-02-03 17:00:00,12052.0 -2012-02-03 18:00:00,12311.0 -2012-02-03 19:00:00,12879.0 -2012-02-03 20:00:00,12758.0 -2012-02-03 21:00:00,12470.0 -2012-02-03 22:00:00,12189.0 -2012-02-03 23:00:00,11716.0 -2012-02-04 00:00:00,11052.0 -2012-02-02 01:00:00,10167.0 -2012-02-02 02:00:00,9675.0 -2012-02-02 03:00:00,9425.0 -2012-02-02 04:00:00,9268.0 -2012-02-02 05:00:00,9244.0 -2012-02-02 06:00:00,9459.0 -2012-02-02 07:00:00,10192.0 -2012-02-02 08:00:00,11407.0 -2012-02-02 09:00:00,12032.0 -2012-02-02 10:00:00,12170.0 -2012-02-02 11:00:00,12234.0 -2012-02-02 12:00:00,12272.0 -2012-02-02 13:00:00,12142.0 -2012-02-02 14:00:00,12033.0 -2012-02-02 15:00:00,11926.0 -2012-02-02 16:00:00,11765.0 -2012-02-02 17:00:00,11710.0 -2012-02-02 18:00:00,11941.0 -2012-02-02 19:00:00,12676.0 -2012-02-02 20:00:00,12721.0 -2012-02-02 21:00:00,12562.0 -2012-02-02 22:00:00,12234.0 -2012-02-02 23:00:00,11770.0 -2012-02-03 00:00:00,10941.0 -2012-02-01 01:00:00,9792.0 -2012-02-01 02:00:00,9338.0 -2012-02-01 03:00:00,9077.0 -2012-02-01 04:00:00,8916.0 -2012-02-01 05:00:00,8944.0 -2012-02-01 06:00:00,9208.0 -2012-02-01 07:00:00,9977.0 -2012-02-01 08:00:00,11279.0 -2012-02-01 09:00:00,11918.0 -2012-02-01 10:00:00,12082.0 -2012-02-01 11:00:00,12114.0 -2012-02-01 12:00:00,12057.0 -2012-02-01 13:00:00,11919.0 -2012-02-01 14:00:00,11741.0 -2012-02-01 15:00:00,11690.0 -2012-02-01 16:00:00,11557.0 -2012-02-01 17:00:00,11524.0 -2012-02-01 18:00:00,11768.0 -2012-02-01 19:00:00,12617.0 -2012-02-01 20:00:00,12684.0 -2012-02-01 21:00:00,12539.0 -2012-02-01 22:00:00,12262.0 -2012-02-01 23:00:00,11755.0 -2012-02-02 00:00:00,10923.0 -2012-01-31 01:00:00,10155.0 -2012-01-31 02:00:00,9649.0 -2012-01-31 03:00:00,9333.0 -2012-01-31 04:00:00,9177.0 -2012-01-31 05:00:00,9148.0 -2012-01-31 06:00:00,9380.0 -2012-01-31 07:00:00,10108.0 -2012-01-31 08:00:00,11375.0 -2012-01-31 09:00:00,11778.0 -2012-01-31 10:00:00,11821.0 -2012-01-31 11:00:00,11793.0 -2012-01-31 12:00:00,11792.0 -2012-01-31 13:00:00,11787.0 -2012-01-31 14:00:00,11727.0 -2012-01-31 15:00:00,11751.0 -2012-01-31 16:00:00,11674.0 -2012-01-31 17:00:00,11686.0 -2012-01-31 18:00:00,11962.0 -2012-01-31 19:00:00,12531.0 -2012-01-31 20:00:00,12407.0 -2012-01-31 21:00:00,12194.0 -2012-01-31 22:00:00,11843.0 -2012-01-31 23:00:00,11323.0 -2012-02-01 00:00:00,10515.0 -2012-01-30 01:00:00,10430.0 -2012-01-30 02:00:00,10059.0 -2012-01-30 03:00:00,9865.0 -2012-01-30 04:00:00,9752.0 -2012-01-30 05:00:00,9770.0 -2012-01-30 06:00:00,10084.0 -2012-01-30 07:00:00,10863.0 -2012-01-30 08:00:00,12167.0 -2012-01-30 09:00:00,12845.0 -2012-01-30 10:00:00,13019.0 -2012-01-30 11:00:00,12996.0 -2012-01-30 12:00:00,12936.0 -2012-01-30 13:00:00,12744.0 -2012-01-30 14:00:00,12516.0 -2012-01-30 15:00:00,12295.0 -2012-01-30 16:00:00,11988.0 -2012-01-30 17:00:00,11862.0 -2012-01-30 18:00:00,12009.0 -2012-01-30 19:00:00,12834.0 -2012-01-30 20:00:00,12920.0 -2012-01-30 21:00:00,12701.0 -2012-01-30 22:00:00,12381.0 -2012-01-30 23:00:00,11834.0 -2012-01-31 00:00:00,10923.0 -2012-01-29 01:00:00,10382.0 -2012-01-29 02:00:00,9925.0 -2012-01-29 03:00:00,9666.0 -2012-01-29 04:00:00,9413.0 -2012-01-29 05:00:00,9391.0 -2012-01-29 06:00:00,9377.0 -2012-01-29 07:00:00,9522.0 -2012-01-29 08:00:00,9828.0 -2012-01-29 09:00:00,9977.0 -2012-01-29 10:00:00,10254.0 -2012-01-29 11:00:00,10534.0 -2012-01-29 12:00:00,10606.0 -2012-01-29 13:00:00,10652.0 -2012-01-29 14:00:00,10561.0 -2012-01-29 15:00:00,10435.0 -2012-01-29 16:00:00,10338.0 -2012-01-29 17:00:00,10386.0 -2012-01-29 18:00:00,10787.0 -2012-01-29 19:00:00,11904.0 -2012-01-29 20:00:00,12157.0 -2012-01-29 21:00:00,12125.0 -2012-01-29 22:00:00,11942.0 -2012-01-29 23:00:00,11594.0 -2012-01-30 00:00:00,11020.0 -2012-01-28 01:00:00,10522.0 -2012-01-28 02:00:00,9983.0 -2012-01-28 03:00:00,9702.0 -2012-01-28 04:00:00,9478.0 -2012-01-28 05:00:00,9413.0 -2012-01-28 06:00:00,9482.0 -2012-01-28 07:00:00,9824.0 -2012-01-28 08:00:00,10406.0 -2012-01-28 09:00:00,10653.0 -2012-01-28 10:00:00,10931.0 -2012-01-28 11:00:00,11085.0 -2012-01-28 12:00:00,11205.0 -2012-01-28 13:00:00,11163.0 -2012-01-28 14:00:00,11081.0 -2012-01-28 15:00:00,10842.0 -2012-01-28 16:00:00,10701.0 -2012-01-28 17:00:00,10657.0 -2012-01-28 18:00:00,11006.0 -2012-01-28 19:00:00,11938.0 -2012-01-28 20:00:00,12103.0 -2012-01-28 21:00:00,11969.0 -2012-01-28 22:00:00,11746.0 -2012-01-28 23:00:00,11432.0 -2012-01-29 00:00:00,10903.0 -2012-01-27 01:00:00,10551.0 -2012-01-27 02:00:00,10086.0 -2012-01-27 03:00:00,9775.0 -2012-01-27 04:00:00,9595.0 -2012-01-27 05:00:00,9560.0 -2012-01-27 06:00:00,9812.0 -2012-01-27 07:00:00,10471.0 -2012-01-27 08:00:00,11731.0 -2012-01-27 09:00:00,12148.0 -2012-01-27 10:00:00,12186.0 -2012-01-27 11:00:00,12172.0 -2012-01-27 12:00:00,12160.0 -2012-01-27 13:00:00,12049.0 -2012-01-27 14:00:00,11907.0 -2012-01-27 15:00:00,11849.0 -2012-01-27 16:00:00,11658.0 -2012-01-27 17:00:00,11621.0 -2012-01-27 18:00:00,12004.0 -2012-01-27 19:00:00,12716.0 -2012-01-27 20:00:00,12666.0 -2012-01-27 21:00:00,12411.0 -2012-01-27 22:00:00,12138.0 -2012-01-27 23:00:00,11798.0 -2012-01-28 00:00:00,11125.0 -2012-01-26 01:00:00,10694.0 -2012-01-26 02:00:00,10187.0 -2012-01-26 03:00:00,9897.0 -2012-01-26 04:00:00,9703.0 -2012-01-26 05:00:00,9698.0 -2012-01-26 06:00:00,9932.0 -2012-01-26 07:00:00,10636.0 -2012-01-26 08:00:00,11906.0 -2012-01-26 09:00:00,12512.0 -2012-01-26 10:00:00,12597.0 -2012-01-26 11:00:00,12692.0 -2012-01-26 12:00:00,12787.0 -2012-01-26 13:00:00,12730.0 -2012-01-26 14:00:00,12638.0 -2012-01-26 15:00:00,12580.0 -2012-01-26 16:00:00,12466.0 -2012-01-26 17:00:00,12475.0 -2012-01-26 18:00:00,12796.0 -2012-01-26 19:00:00,13337.0 -2012-01-26 20:00:00,13299.0 -2012-01-26 21:00:00,13044.0 -2012-01-26 22:00:00,12668.0 -2012-01-26 23:00:00,12160.0 -2012-01-27 00:00:00,11346.0 -2012-01-25 01:00:00,10894.0 -2012-01-25 02:00:00,10410.0 -2012-01-25 03:00:00,10174.0 -2012-01-25 04:00:00,10023.0 -2012-01-25 05:00:00,10019.0 -2012-01-25 06:00:00,10300.0 -2012-01-25 07:00:00,11026.0 -2012-01-25 08:00:00,12274.0 -2012-01-25 09:00:00,12819.0 -2012-01-25 10:00:00,12850.0 -2012-01-25 11:00:00,12831.0 -2012-01-25 12:00:00,12845.0 -2012-01-25 13:00:00,12840.0 -2012-01-25 14:00:00,12781.0 -2012-01-25 15:00:00,12811.0 -2012-01-25 16:00:00,12716.0 -2012-01-25 17:00:00,12720.0 -2012-01-25 18:00:00,13064.0 -2012-01-25 19:00:00,13611.0 -2012-01-25 20:00:00,13486.0 -2012-01-25 21:00:00,13299.0 -2012-01-25 22:00:00,12970.0 -2012-01-25 23:00:00,12397.0 -2012-01-26 00:00:00,11501.0 -2012-01-24 01:00:00,10977.0 -2012-01-24 02:00:00,10479.0 -2012-01-24 03:00:00,10181.0 -2012-01-24 04:00:00,10012.0 -2012-01-24 05:00:00,9990.0 -2012-01-24 06:00:00,10269.0 -2012-01-24 07:00:00,11093.0 -2012-01-24 08:00:00,12370.0 -2012-01-24 09:00:00,12941.0 -2012-01-24 10:00:00,12918.0 -2012-01-24 11:00:00,12860.0 -2012-01-24 12:00:00,12778.0 -2012-01-24 13:00:00,12642.0 -2012-01-24 14:00:00,12510.0 -2012-01-24 15:00:00,12430.0 -2012-01-24 16:00:00,12273.0 -2012-01-24 17:00:00,12225.0 -2012-01-24 18:00:00,12583.0 -2012-01-24 19:00:00,13472.0 -2012-01-24 20:00:00,13451.0 -2012-01-24 21:00:00,13266.0 -2012-01-24 22:00:00,12999.0 -2012-01-24 23:00:00,12489.0 -2012-01-25 00:00:00,11641.0 -2012-01-23 01:00:00,10192.0 -2012-01-23 02:00:00,9742.0 -2012-01-23 03:00:00,9466.0 -2012-01-23 04:00:00,9293.0 -2012-01-23 05:00:00,9313.0 -2012-01-23 06:00:00,9565.0 -2012-01-23 07:00:00,10308.0 -2012-01-23 08:00:00,11570.0 -2012-01-23 09:00:00,12327.0 -2012-01-23 10:00:00,12447.0 -2012-01-23 11:00:00,12642.0 -2012-01-23 12:00:00,12847.0 -2012-01-23 13:00:00,12929.0 -2012-01-23 14:00:00,12989.0 -2012-01-23 15:00:00,13049.0 -2012-01-23 16:00:00,13054.0 -2012-01-23 17:00:00,13172.0 -2012-01-23 18:00:00,13582.0 -2012-01-23 19:00:00,14074.0 -2012-01-23 20:00:00,13851.0 -2012-01-23 21:00:00,13571.0 -2012-01-23 22:00:00,13240.0 -2012-01-23 23:00:00,12666.0 -2012-01-24 00:00:00,11787.0 -2012-01-22 01:00:00,11098.0 -2012-01-22 02:00:00,10670.0 -2012-01-22 03:00:00,10396.0 -2012-01-22 04:00:00,10262.0 -2012-01-22 05:00:00,10107.0 -2012-01-22 06:00:00,10166.0 -2012-01-22 07:00:00,10246.0 -2012-01-22 08:00:00,10522.0 -2012-01-22 09:00:00,10604.0 -2012-01-22 10:00:00,10837.0 -2012-01-22 11:00:00,11095.0 -2012-01-22 12:00:00,11242.0 -2012-01-22 13:00:00,11302.0 -2012-01-22 14:00:00,11318.0 -2012-01-22 15:00:00,11248.0 -2012-01-22 16:00:00,11270.0 -2012-01-22 17:00:00,11343.0 -2012-01-22 18:00:00,11707.0 -2012-01-22 19:00:00,12403.0 -2012-01-22 20:00:00,12429.0 -2012-01-22 21:00:00,12202.0 -2012-01-22 22:00:00,11930.0 -2012-01-22 23:00:00,11479.0 -2012-01-23 00:00:00,10872.0 -2012-01-21 01:00:00,11578.0 -2012-01-21 02:00:00,11009.0 -2012-01-21 03:00:00,10693.0 -2012-01-21 04:00:00,10480.0 -2012-01-21 05:00:00,10418.0 -2012-01-21 06:00:00,10491.0 -2012-01-21 07:00:00,10746.0 -2012-01-21 08:00:00,11191.0 -2012-01-21 09:00:00,11471.0 -2012-01-21 10:00:00,11785.0 -2012-01-21 11:00:00,11993.0 -2012-01-21 12:00:00,12161.0 -2012-01-21 13:00:00,12091.0 -2012-01-21 14:00:00,11964.0 -2012-01-21 15:00:00,11728.0 -2012-01-21 16:00:00,11558.0 -2012-01-21 17:00:00,11524.0 -2012-01-21 18:00:00,11813.0 -2012-01-21 19:00:00,12698.0 -2012-01-21 20:00:00,12764.0 -2012-01-21 21:00:00,12638.0 -2012-01-21 22:00:00,12391.0 -2012-01-21 23:00:00,12132.0 -2012-01-22 00:00:00,11657.0 -2012-01-20 01:00:00,12316.0 -2012-01-20 02:00:00,11848.0 -2012-01-20 03:00:00,11541.0 -2012-01-20 04:00:00,11355.0 -2012-01-20 05:00:00,11321.0 -2012-01-20 06:00:00,11572.0 -2012-01-20 07:00:00,12183.0 -2012-01-20 08:00:00,13348.0 -2012-01-20 09:00:00,13978.0 -2012-01-20 10:00:00,13997.0 -2012-01-20 11:00:00,13981.0 -2012-01-20 12:00:00,14092.0 -2012-01-20 13:00:00,14083.0 -2012-01-20 14:00:00,14064.0 -2012-01-20 15:00:00,13977.0 -2012-01-20 16:00:00,13812.0 -2012-01-20 17:00:00,13660.0 -2012-01-20 18:00:00,13948.0 -2012-01-20 19:00:00,14536.0 -2012-01-20 20:00:00,14429.0 -2012-01-20 21:00:00,14093.0 -2012-01-20 22:00:00,13664.0 -2012-01-20 23:00:00,13160.0 -2012-01-21 00:00:00,12327.0 -2012-01-19 01:00:00,11487.0 -2012-01-19 02:00:00,10963.0 -2012-01-19 03:00:00,10617.0 -2012-01-19 04:00:00,10406.0 -2012-01-19 05:00:00,10382.0 -2012-01-19 06:00:00,10652.0 -2012-01-19 07:00:00,11383.0 -2012-01-19 08:00:00,12631.0 -2012-01-19 09:00:00,13353.0 -2012-01-19 10:00:00,13539.0 -2012-01-19 11:00:00,13698.0 -2012-01-19 12:00:00,13788.0 -2012-01-19 13:00:00,13717.0 -2012-01-19 14:00:00,13589.0 -2012-01-19 15:00:00,13503.0 -2012-01-19 16:00:00,13336.0 -2012-01-19 17:00:00,13298.0 -2012-01-19 18:00:00,13794.0 -2012-01-19 19:00:00,14721.0 -2012-01-19 20:00:00,14813.0 -2012-01-19 21:00:00,14649.0 -2012-01-19 22:00:00,14389.0 -2012-01-19 23:00:00,13864.0 -2012-01-20 00:00:00,13064.0 -2012-01-18 01:00:00,11441.0 -2012-01-18 02:00:00,10975.0 -2012-01-18 03:00:00,10736.0 -2012-01-18 04:00:00,10615.0 -2012-01-18 05:00:00,10619.0 -2012-01-18 06:00:00,10945.0 -2012-01-18 07:00:00,11662.0 -2012-01-18 08:00:00,12902.0 -2012-01-18 09:00:00,13448.0 -2012-01-18 10:00:00,13397.0 -2012-01-18 11:00:00,13377.0 -2012-01-18 12:00:00,13341.0 -2012-01-18 13:00:00,13209.0 -2012-01-18 14:00:00,13104.0 -2012-01-18 15:00:00,13042.0 -2012-01-18 16:00:00,12994.0 -2012-01-18 17:00:00,13061.0 -2012-01-18 18:00:00,13490.0 -2012-01-18 19:00:00,14249.0 -2012-01-18 20:00:00,14178.0 -2012-01-18 21:00:00,13964.0 -2012-01-18 22:00:00,13654.0 -2012-01-18 23:00:00,13136.0 -2012-01-19 00:00:00,12257.0 -2012-01-17 01:00:00,10209.0 -2012-01-17 02:00:00,9747.0 -2012-01-17 03:00:00,9476.0 -2012-01-17 04:00:00,9330.0 -2012-01-17 05:00:00,9355.0 -2012-01-17 06:00:00,9623.0 -2012-01-17 07:00:00,10393.0 -2012-01-17 08:00:00,11626.0 -2012-01-17 09:00:00,12493.0 -2012-01-17 10:00:00,12769.0 -2012-01-17 11:00:00,12914.0 -2012-01-17 12:00:00,13047.0 -2012-01-17 13:00:00,13145.0 -2012-01-17 14:00:00,13142.0 -2012-01-17 15:00:00,13177.0 -2012-01-17 16:00:00,13037.0 -2012-01-17 17:00:00,13045.0 -2012-01-17 18:00:00,13462.0 -2012-01-17 19:00:00,14080.0 -2012-01-17 20:00:00,13951.0 -2012-01-17 21:00:00,13787.0 -2012-01-17 22:00:00,13517.0 -2012-01-17 23:00:00,13009.0 -2012-01-18 00:00:00,12191.0 -2012-01-16 01:00:00,10603.0 -2012-01-16 02:00:00,10162.0 -2012-01-16 03:00:00,9958.0 -2012-01-16 04:00:00,9817.0 -2012-01-16 05:00:00,9805.0 -2012-01-16 06:00:00,10048.0 -2012-01-16 07:00:00,10626.0 -2012-01-16 08:00:00,11593.0 -2012-01-16 09:00:00,11988.0 -2012-01-16 10:00:00,12100.0 -2012-01-16 11:00:00,12106.0 -2012-01-16 12:00:00,12295.0 -2012-01-16 13:00:00,12208.0 -2012-01-16 14:00:00,12251.0 -2012-01-16 15:00:00,12333.0 -2012-01-16 16:00:00,12312.0 -2012-01-16 17:00:00,12348.0 -2012-01-16 18:00:00,12727.0 -2012-01-16 19:00:00,13161.0 -2012-01-16 20:00:00,12971.0 -2012-01-16 21:00:00,12709.0 -2012-01-16 22:00:00,12352.0 -2012-01-16 23:00:00,11823.0 -2012-01-17 00:00:00,10981.0 -2012-01-15 01:00:00,11051.0 -2012-01-15 02:00:00,10573.0 -2012-01-15 03:00:00,10319.0 -2012-01-15 04:00:00,10098.0 -2012-01-15 05:00:00,10079.0 -2012-01-15 06:00:00,10057.0 -2012-01-15 07:00:00,10157.0 -2012-01-15 08:00:00,10447.0 -2012-01-15 09:00:00,10493.0 -2012-01-15 10:00:00,10719.0 -2012-01-15 11:00:00,10788.0 -2012-01-15 12:00:00,10970.0 -2012-01-15 13:00:00,10928.0 -2012-01-15 14:00:00,10821.0 -2012-01-15 15:00:00,10667.0 -2012-01-15 16:00:00,10626.0 -2012-01-15 17:00:00,10718.0 -2012-01-15 18:00:00,11236.0 -2012-01-15 19:00:00,12311.0 -2012-01-15 20:00:00,12448.0 -2012-01-15 21:00:00,12339.0 -2012-01-15 22:00:00,12062.0 -2012-01-15 23:00:00,11726.0 -2012-01-16 00:00:00,11143.0 -2012-01-14 01:00:00,11423.0 -2012-01-14 02:00:00,10865.0 -2012-01-14 03:00:00,10604.0 -2012-01-14 04:00:00,10344.0 -2012-01-14 05:00:00,10299.0 -2012-01-14 06:00:00,10363.0 -2012-01-14 07:00:00,10674.0 -2012-01-14 08:00:00,11150.0 -2012-01-14 09:00:00,11482.0 -2012-01-14 10:00:00,11755.0 -2012-01-14 11:00:00,12014.0 -2012-01-14 12:00:00,11982.0 -2012-01-14 13:00:00,11915.0 -2012-01-14 14:00:00,11683.0 -2012-01-14 15:00:00,11397.0 -2012-01-14 16:00:00,11226.0 -2012-01-14 17:00:00,11292.0 -2012-01-14 18:00:00,11706.0 -2012-01-14 19:00:00,12710.0 -2012-01-14 20:00:00,12797.0 -2012-01-14 21:00:00,12651.0 -2012-01-14 22:00:00,12511.0 -2012-01-14 23:00:00,12148.0 -2012-01-15 00:00:00,11627.0 -2012-01-13 01:00:00,11291.0 -2012-01-13 02:00:00,10843.0 -2012-01-13 03:00:00,10582.0 -2012-01-13 04:00:00,10489.0 -2012-01-13 05:00:00,10496.0 -2012-01-13 06:00:00,10752.0 -2012-01-13 07:00:00,11449.0 -2012-01-13 08:00:00,12606.0 -2012-01-13 09:00:00,13108.0 -2012-01-13 10:00:00,13188.0 -2012-01-13 11:00:00,13249.0 -2012-01-13 12:00:00,13343.0 -2012-01-13 13:00:00,13291.0 -2012-01-13 14:00:00,13158.0 -2012-01-13 15:00:00,13121.0 -2012-01-13 16:00:00,13041.0 -2012-01-13 17:00:00,13000.0 -2012-01-13 18:00:00,13384.0 -2012-01-13 19:00:00,14011.0 -2012-01-13 20:00:00,13883.0 -2012-01-13 21:00:00,13611.0 -2012-01-13 22:00:00,13280.0 -2012-01-13 23:00:00,12837.0 -2012-01-14 00:00:00,12124.0 -2012-01-12 01:00:00,10139.0 -2012-01-12 02:00:00,9669.0 -2012-01-12 03:00:00,9385.0 -2012-01-12 04:00:00,9229.0 -2012-01-12 05:00:00,9227.0 -2012-01-12 06:00:00,9501.0 -2012-01-12 07:00:00,10291.0 -2012-01-12 08:00:00,11551.0 -2012-01-12 09:00:00,12310.0 -2012-01-12 10:00:00,12524.0 -2012-01-12 11:00:00,12649.0 -2012-01-12 12:00:00,12854.0 -2012-01-12 13:00:00,12887.0 -2012-01-12 14:00:00,12838.0 -2012-01-12 15:00:00,12852.0 -2012-01-12 16:00:00,12819.0 -2012-01-12 17:00:00,12812.0 -2012-01-12 18:00:00,13274.0 -2012-01-12 19:00:00,13983.0 -2012-01-12 20:00:00,13927.0 -2012-01-12 21:00:00,13760.0 -2012-01-12 22:00:00,13444.0 -2012-01-12 23:00:00,12919.0 -2012-01-13 00:00:00,12110.0 -2012-01-11 01:00:00,10106.0 -2012-01-11 02:00:00,9593.0 -2012-01-11 03:00:00,9328.0 -2012-01-11 04:00:00,9169.0 -2012-01-11 05:00:00,9188.0 -2012-01-11 06:00:00,9449.0 -2012-01-11 07:00:00,10221.0 -2012-01-11 08:00:00,11476.0 -2012-01-11 09:00:00,12140.0 -2012-01-11 10:00:00,12083.0 -2012-01-11 11:00:00,12021.0 -2012-01-11 12:00:00,11974.0 -2012-01-11 13:00:00,11864.0 -2012-01-11 14:00:00,11709.0 -2012-01-11 15:00:00,11661.0 -2012-01-11 16:00:00,11529.0 -2012-01-11 17:00:00,11485.0 -2012-01-11 18:00:00,11858.0 -2012-01-11 19:00:00,12685.0 -2012-01-11 20:00:00,12639.0 -2012-01-11 21:00:00,12475.0 -2012-01-11 22:00:00,12182.0 -2012-01-11 23:00:00,11703.0 -2012-01-12 00:00:00,10918.0 -2012-01-10 01:00:00,10354.0 -2012-01-10 02:00:00,9801.0 -2012-01-10 03:00:00,9524.0 -2012-01-10 04:00:00,9350.0 -2012-01-10 05:00:00,9321.0 -2012-01-10 06:00:00,9607.0 -2012-01-10 07:00:00,10353.0 -2012-01-10 08:00:00,11621.0 -2012-01-10 09:00:00,12214.0 -2012-01-10 10:00:00,12173.0 -2012-01-10 11:00:00,12137.0 -2012-01-10 12:00:00,12117.0 -2012-01-10 13:00:00,11940.0 -2012-01-10 14:00:00,11833.0 -2012-01-10 15:00:00,11784.0 -2012-01-10 16:00:00,11617.0 -2012-01-10 17:00:00,11530.0 -2012-01-10 18:00:00,11940.0 -2012-01-10 19:00:00,12771.0 -2012-01-10 20:00:00,12660.0 -2012-01-10 21:00:00,12466.0 -2012-01-10 22:00:00,12228.0 -2012-01-10 23:00:00,11689.0 -2012-01-11 00:00:00,10864.0 -2012-01-09 01:00:00,9786.0 -2012-01-09 02:00:00,9494.0 -2012-01-09 03:00:00,9301.0 -2012-01-09 04:00:00,9212.0 -2012-01-09 05:00:00,9268.0 -2012-01-09 06:00:00,9620.0 -2012-01-09 07:00:00,10395.0 -2012-01-09 08:00:00,11740.0 -2012-01-09 09:00:00,12435.0 -2012-01-09 10:00:00,12487.0 -2012-01-09 11:00:00,12611.0 -2012-01-09 12:00:00,12617.0 -2012-01-09 13:00:00,12516.0 -2012-01-09 14:00:00,12307.0 -2012-01-09 15:00:00,12195.0 -2012-01-09 16:00:00,11998.0 -2012-01-09 17:00:00,11965.0 -2012-01-09 18:00:00,12407.0 -2012-01-09 19:00:00,13192.0 -2012-01-09 20:00:00,13091.0 -2012-01-09 21:00:00,12882.0 -2012-01-09 22:00:00,12590.0 -2012-01-09 23:00:00,12022.0 -2012-01-10 00:00:00,11171.0 -2012-01-08 01:00:00,9926.0 -2012-01-08 02:00:00,9441.0 -2012-01-08 03:00:00,9182.0 -2012-01-08 04:00:00,9056.0 -2012-01-08 05:00:00,8965.0 -2012-01-08 06:00:00,9049.0 -2012-01-08 07:00:00,9187.0 -2012-01-08 08:00:00,9475.0 -2012-01-08 09:00:00,9528.0 -2012-01-08 10:00:00,9661.0 -2012-01-08 11:00:00,9790.0 -2012-01-08 12:00:00,9825.0 -2012-01-08 13:00:00,9887.0 -2012-01-08 14:00:00,9913.0 -2012-01-08 15:00:00,9838.0 -2012-01-08 16:00:00,9897.0 -2012-01-08 17:00:00,10162.0 -2012-01-08 18:00:00,10869.0 -2012-01-08 19:00:00,11529.0 -2012-01-08 20:00:00,11573.0 -2012-01-08 21:00:00,11454.0 -2012-01-08 22:00:00,11319.0 -2012-01-08 23:00:00,10952.0 -2012-01-09 00:00:00,10352.0 -2012-01-07 01:00:00,10120.0 -2012-01-07 02:00:00,9538.0 -2012-01-07 03:00:00,9199.0 -2012-01-07 04:00:00,8992.0 -2012-01-07 05:00:00,8937.0 -2012-01-07 06:00:00,9031.0 -2012-01-07 07:00:00,9346.0 -2012-01-07 08:00:00,9869.0 -2012-01-07 09:00:00,10100.0 -2012-01-07 10:00:00,10306.0 -2012-01-07 11:00:00,10463.0 -2012-01-07 12:00:00,10543.0 -2012-01-07 13:00:00,10515.0 -2012-01-07 14:00:00,10377.0 -2012-01-07 15:00:00,10172.0 -2012-01-07 16:00:00,9981.0 -2012-01-07 17:00:00,10039.0 -2012-01-07 18:00:00,10688.0 -2012-01-07 19:00:00,11534.0 -2012-01-07 20:00:00,11511.0 -2012-01-07 21:00:00,11426.0 -2012-01-07 22:00:00,11220.0 -2012-01-07 23:00:00,10990.0 -2012-01-08 00:00:00,10443.0 -2012-01-06 01:00:00,10301.0 -2012-01-06 02:00:00,9755.0 -2012-01-06 03:00:00,9393.0 -2012-01-06 04:00:00,9196.0 -2012-01-06 05:00:00,9170.0 -2012-01-06 06:00:00,9391.0 -2012-01-06 07:00:00,10031.0 -2012-01-06 08:00:00,11147.0 -2012-01-06 09:00:00,11787.0 -2012-01-06 10:00:00,11855.0 -2012-01-06 11:00:00,11821.0 -2012-01-06 12:00:00,11795.0 -2012-01-06 13:00:00,11702.0 -2012-01-06 14:00:00,11603.0 -2012-01-06 15:00:00,11526.0 -2012-01-06 16:00:00,11352.0 -2012-01-06 17:00:00,11248.0 -2012-01-06 18:00:00,11805.0 -2012-01-06 19:00:00,12473.0 -2012-01-06 20:00:00,12359.0 -2012-01-06 21:00:00,12134.0 -2012-01-06 22:00:00,11845.0 -2012-01-06 23:00:00,11500.0 -2012-01-07 00:00:00,10802.0 -2012-01-05 01:00:00,10877.0 -2012-01-05 02:00:00,10326.0 -2012-01-05 03:00:00,10003.0 -2012-01-05 04:00:00,9803.0 -2012-01-05 05:00:00,9804.0 -2012-01-05 06:00:00,10055.0 -2012-01-05 07:00:00,10742.0 -2012-01-05 08:00:00,11893.0 -2012-01-05 09:00:00,12431.0 -2012-01-05 10:00:00,12460.0 -2012-01-05 11:00:00,12373.0 -2012-01-05 12:00:00,12309.0 -2012-01-05 13:00:00,12245.0 -2012-01-05 14:00:00,12111.0 -2012-01-05 15:00:00,12067.0 -2012-01-05 16:00:00,11874.0 -2012-01-05 17:00:00,11756.0 -2012-01-05 18:00:00,12304.0 -2012-01-05 19:00:00,13126.0 -2012-01-05 20:00:00,13087.0 -2012-01-05 21:00:00,12861.0 -2012-01-05 22:00:00,12514.0 -2012-01-05 23:00:00,11971.0 -2012-01-06 00:00:00,11141.0 -2012-01-04 01:00:00,11391.0 -2012-01-04 02:00:00,10810.0 -2012-01-04 03:00:00,10471.0 -2012-01-04 04:00:00,10253.0 -2012-01-04 05:00:00,10166.0 -2012-01-04 06:00:00,10406.0 -2012-01-04 07:00:00,11046.0 -2012-01-04 08:00:00,12155.0 -2012-01-04 09:00:00,12692.0 -2012-01-04 10:00:00,12660.0 -2012-01-04 11:00:00,12598.0 -2012-01-04 12:00:00,12575.0 -2012-01-04 13:00:00,12472.0 -2012-01-04 14:00:00,12356.0 -2012-01-04 15:00:00,12404.0 -2012-01-04 16:00:00,12418.0 -2012-01-04 17:00:00,12450.0 -2012-01-04 18:00:00,13102.0 -2012-01-04 19:00:00,13701.0 -2012-01-04 20:00:00,13553.0 -2012-01-04 21:00:00,13362.0 -2012-01-04 22:00:00,13045.0 -2012-01-04 23:00:00,12501.0 -2012-01-05 00:00:00,11664.0 -2012-01-03 01:00:00,11130.0 -2012-01-03 02:00:00,10698.0 -2012-01-03 03:00:00,10440.0 -2012-01-03 04:00:00,10315.0 -2012-01-03 05:00:00,10348.0 -2012-01-03 06:00:00,10620.0 -2012-01-03 07:00:00,11331.0 -2012-01-03 08:00:00,12475.0 -2012-01-03 09:00:00,13078.0 -2012-01-03 10:00:00,13169.0 -2012-01-03 11:00:00,13180.0 -2012-01-03 12:00:00,13205.0 -2012-01-03 13:00:00,13140.0 -2012-01-03 14:00:00,13083.0 -2012-01-03 15:00:00,13062.0 -2012-01-03 16:00:00,12815.0 -2012-01-03 17:00:00,12811.0 -2012-01-03 18:00:00,13523.0 -2012-01-03 19:00:00,14300.0 -2012-01-03 20:00:00,14226.0 -2012-01-03 21:00:00,14046.0 -2012-01-03 22:00:00,13751.0 -2012-01-03 23:00:00,13152.0 -2012-01-04 00:00:00,12282.0 -2012-01-02 01:00:00,10170.0 -2012-01-02 02:00:00,9731.0 -2012-01-02 03:00:00,9459.0 -2012-01-02 04:00:00,9297.0 -2012-01-02 05:00:00,9323.0 -2012-01-02 06:00:00,9451.0 -2012-01-02 07:00:00,9880.0 -2012-01-02 08:00:00,10399.0 -2012-01-02 09:00:00,10675.0 -2012-01-02 10:00:00,10873.0 -2012-01-02 11:00:00,11237.0 -2012-01-02 12:00:00,11503.0 -2012-01-02 13:00:00,11639.0 -2012-01-02 14:00:00,11675.0 -2012-01-02 15:00:00,11673.0 -2012-01-02 16:00:00,11686.0 -2012-01-02 17:00:00,11855.0 -2012-01-02 18:00:00,12670.0 -2012-01-02 19:00:00,13332.0 -2012-01-02 20:00:00,13308.0 -2012-01-02 21:00:00,13179.0 -2012-01-02 22:00:00,12956.0 -2012-01-02 23:00:00,12536.0 -2012-01-03 00:00:00,11810.0 -2012-01-01 01:00:00,9906.0 -2012-01-01 02:00:00,9407.0 -2012-01-01 03:00:00,9086.0 -2012-01-01 04:00:00,8758.0 -2012-01-01 05:00:00,8483.0 -2012-01-01 06:00:00,8432.0 -2012-01-01 07:00:00,8487.0 -2012-01-01 08:00:00,8686.0 -2012-01-01 09:00:00,8953.0 -2012-01-01 10:00:00,9050.0 -2012-01-01 11:00:00,9366.0 -2012-01-01 12:00:00,9387.0 -2012-01-01 13:00:00,9597.0 -2012-01-01 14:00:00,9924.0 -2012-01-01 15:00:00,10089.0 -2012-01-01 16:00:00,10130.0 -2012-01-01 17:00:00,10363.0 -2012-01-01 18:00:00,11132.0 -2012-01-01 19:00:00,11757.0 -2012-01-01 20:00:00,11799.0 -2012-01-01 21:00:00,11681.0 -2012-01-01 22:00:00,11490.0 -2012-01-01 23:00:00,11158.0 -2012-01-02 00:00:00,10721.0 -2013-12-31 01:00:00,11979.0 -2013-12-31 02:00:00,11438.0 -2013-12-31 03:00:00,11113.0 -2013-12-31 04:00:00,10928.0 -2013-12-31 05:00:00,10926.0 -2013-12-31 06:00:00,11078.0 -2013-12-31 07:00:00,11492.0 -2013-12-31 08:00:00,12172.0 -2013-12-31 09:00:00,12473.0 -2013-12-31 10:00:00,12597.0 -2013-12-31 11:00:00,12732.0 -2013-12-31 12:00:00,12828.0 -2013-12-31 13:00:00,12902.0 -2013-12-31 14:00:00,12832.0 -2013-12-31 15:00:00,12794.0 -2013-12-31 16:00:00,12768.0 -2013-12-31 17:00:00,12735.0 -2013-12-31 18:00:00,13383.0 -2013-12-31 19:00:00,14041.0 -2013-12-31 20:00:00,13768.0 -2013-12-31 21:00:00,13358.0 -2013-12-31 22:00:00,12922.0 -2013-12-31 23:00:00,12503.0 -2014-01-01 00:00:00,12035.0 -2013-12-30 01:00:00,11183.0 -2013-12-30 02:00:00,10850.0 -2013-12-30 03:00:00,10682.0 -2013-12-30 04:00:00,10640.0 -2013-12-30 05:00:00,10702.0 -2013-12-30 06:00:00,10971.0 -2013-12-30 07:00:00,11538.0 -2013-12-30 08:00:00,12475.0 -2013-12-30 09:00:00,12903.0 -2013-12-30 10:00:00,13042.0 -2013-12-30 11:00:00,13193.0 -2013-12-30 12:00:00,13307.0 -2013-12-30 13:00:00,13328.0 -2013-12-30 14:00:00,13221.0 -2013-12-30 15:00:00,13127.0 -2013-12-30 16:00:00,13062.0 -2013-12-30 17:00:00,13069.0 -2013-12-30 18:00:00,13834.0 -2013-12-30 19:00:00,14525.0 -2013-12-30 20:00:00,14427.0 -2013-12-30 21:00:00,14260.0 -2013-12-30 22:00:00,14018.0 -2013-12-30 23:00:00,13530.0 -2013-12-31 00:00:00,12756.0 -2013-12-29 01:00:00,10088.0 -2013-12-29 02:00:00,9621.0 -2013-12-29 03:00:00,9309.0 -2013-12-29 04:00:00,9074.0 -2013-12-29 05:00:00,8998.0 -2013-12-29 06:00:00,8997.0 -2013-12-29 07:00:00,9180.0 -2013-12-29 08:00:00,9457.0 -2013-12-29 09:00:00,9634.0 -2013-12-29 10:00:00,9874.0 -2013-12-29 11:00:00,10336.0 -2013-12-29 12:00:00,10678.0 -2013-12-29 13:00:00,10933.0 -2013-12-29 14:00:00,11032.0 -2013-12-29 15:00:00,11157.0 -2013-12-29 16:00:00,11267.0 -2013-12-29 17:00:00,11444.0 -2013-12-29 18:00:00,12134.0 -2013-12-29 19:00:00,12695.0 -2013-12-29 20:00:00,12741.0 -2013-12-29 21:00:00,12694.0 -2013-12-29 22:00:00,12569.0 -2013-12-29 23:00:00,12273.0 -2013-12-30 00:00:00,11754.0 -2013-12-28 01:00:00,10643.0 -2013-12-28 02:00:00,10069.0 -2013-12-28 03:00:00,9710.0 -2013-12-28 04:00:00,9477.0 -2013-12-28 05:00:00,9404.0 -2013-12-28 06:00:00,9469.0 -2013-12-28 07:00:00,9699.0 -2013-12-28 08:00:00,10157.0 -2013-12-28 09:00:00,10341.0 -2013-12-28 10:00:00,10451.0 -2013-12-28 11:00:00,10541.0 -2013-12-28 12:00:00,10577.0 -2013-12-28 13:00:00,10531.0 -2013-12-28 14:00:00,10354.0 -2013-12-28 15:00:00,10112.0 -2013-12-28 16:00:00,9979.0 -2013-12-28 17:00:00,10070.0 -2013-12-28 18:00:00,10796.0 -2013-12-28 19:00:00,11684.0 -2013-12-28 20:00:00,11678.0 -2013-12-28 21:00:00,11598.0 -2013-12-28 22:00:00,11456.0 -2013-12-28 23:00:00,11176.0 -2013-12-29 00:00:00,10687.0 -2013-12-27 01:00:00,10909.0 -2013-12-27 02:00:00,10319.0 -2013-12-27 03:00:00,9958.0 -2013-12-27 04:00:00,9749.0 -2013-12-27 05:00:00,9732.0 -2013-12-27 06:00:00,9975.0 -2013-12-27 07:00:00,10572.0 -2013-12-27 08:00:00,11436.0 -2013-12-27 09:00:00,11814.0 -2013-12-27 10:00:00,11965.0 -2013-12-27 11:00:00,11983.0 -2013-12-27 12:00:00,12002.0 -2013-12-27 13:00:00,11917.0 -2013-12-27 14:00:00,11794.0 -2013-12-27 15:00:00,11712.0 -2013-12-27 16:00:00,11619.0 -2013-12-27 17:00:00,11583.0 -2013-12-27 18:00:00,12311.0 -2013-12-27 19:00:00,13092.0 -2013-12-27 20:00:00,12973.0 -2013-12-27 21:00:00,12770.0 -2013-12-27 22:00:00,12476.0 -2013-12-27 23:00:00,12116.0 -2013-12-28 00:00:00,11410.0 -2013-12-26 01:00:00,10587.0 -2013-12-26 02:00:00,10159.0 -2013-12-26 03:00:00,9979.0 -2013-12-26 04:00:00,9928.0 -2013-12-26 05:00:00,10002.0 -2013-12-26 06:00:00,10259.0 -2013-12-26 07:00:00,10883.0 -2013-12-26 08:00:00,11772.0 -2013-12-26 09:00:00,12194.0 -2013-12-26 10:00:00,12431.0 -2013-12-26 11:00:00,12706.0 -2013-12-26 12:00:00,12845.0 -2013-12-26 13:00:00,12885.0 -2013-12-26 14:00:00,12799.0 -2013-12-26 15:00:00,12628.0 -2013-12-26 16:00:00,12462.0 -2013-12-26 17:00:00,12487.0 -2013-12-26 18:00:00,13261.0 -2013-12-26 19:00:00,13895.0 -2013-12-26 20:00:00,13646.0 -2013-12-26 21:00:00,13345.0 -2013-12-26 22:00:00,13076.0 -2013-12-26 23:00:00,12530.0 -2013-12-27 00:00:00,11718.0 -2013-12-25 01:00:00,11426.0 -2013-12-25 02:00:00,10915.0 -2013-12-25 03:00:00,10537.0 -2013-12-25 04:00:00,10316.0 -2013-12-25 05:00:00,10204.0 -2013-12-25 06:00:00,10210.0 -2013-12-25 07:00:00,10359.0 -2013-12-25 08:00:00,10586.0 -2013-12-25 09:00:00,10732.0 -2013-12-25 10:00:00,10843.0 -2013-12-25 11:00:00,11057.0 -2013-12-25 12:00:00,11162.0 -2013-12-25 13:00:00,11176.0 -2013-12-25 14:00:00,11173.0 -2013-12-25 15:00:00,11078.0 -2013-12-25 16:00:00,11020.0 -2013-12-25 17:00:00,11045.0 -2013-12-25 18:00:00,11627.0 -2013-12-25 19:00:00,11996.0 -2013-12-25 20:00:00,11953.0 -2013-12-25 21:00:00,11874.0 -2013-12-25 22:00:00,11788.0 -2013-12-25 23:00:00,11603.0 -2013-12-26 00:00:00,11152.0 -2013-12-24 01:00:00,12691.0 -2013-12-24 02:00:00,12058.0 -2013-12-24 03:00:00,11664.0 -2013-12-24 04:00:00,11421.0 -2013-12-24 05:00:00,11370.0 -2013-12-24 06:00:00,11437.0 -2013-12-24 07:00:00,11794.0 -2013-12-24 08:00:00,12318.0 -2013-12-24 09:00:00,12609.0 -2013-12-24 10:00:00,12785.0 -2013-12-24 11:00:00,12924.0 -2013-12-24 12:00:00,12885.0 -2013-12-24 13:00:00,12695.0 -2013-12-24 14:00:00,12413.0 -2013-12-24 15:00:00,12162.0 -2013-12-24 16:00:00,11966.0 -2013-12-24 17:00:00,12003.0 -2013-12-24 18:00:00,12855.0 -2013-12-24 19:00:00,13438.0 -2013-12-24 20:00:00,13158.0 -2013-12-24 21:00:00,12857.0 -2013-12-24 22:00:00,12660.0 -2013-12-24 23:00:00,12396.0 -2013-12-25 00:00:00,11944.0 -2013-12-23 01:00:00,10968.0 -2013-12-23 02:00:00,10424.0 -2013-12-23 03:00:00,10161.0 -2013-12-23 04:00:00,10020.0 -2013-12-23 05:00:00,10013.0 -2013-12-23 06:00:00,10285.0 -2013-12-23 07:00:00,10938.0 -2013-12-23 08:00:00,11847.0 -2013-12-23 09:00:00,12478.0 -2013-12-23 10:00:00,12717.0 -2013-12-23 11:00:00,12896.0 -2013-12-23 12:00:00,12894.0 -2013-12-23 13:00:00,12894.0 -2013-12-23 14:00:00,12831.0 -2013-12-23 15:00:00,12831.0 -2013-12-23 16:00:00,12814.0 -2013-12-23 17:00:00,12931.0 -2013-12-23 18:00:00,13879.0 -2013-12-23 19:00:00,14828.0 -2013-12-23 20:00:00,14817.0 -2013-12-23 21:00:00,14733.0 -2013-12-23 22:00:00,14577.0 -2013-12-23 23:00:00,14239.0 -2013-12-24 00:00:00,13487.0 -2013-12-22 01:00:00,10813.0 -2013-12-22 02:00:00,10257.0 -2013-12-22 03:00:00,9913.0 -2013-12-22 04:00:00,9659.0 -2013-12-22 05:00:00,9572.0 -2013-12-22 06:00:00,9562.0 -2013-12-22 07:00:00,9722.0 -2013-12-22 08:00:00,10012.0 -2013-12-22 09:00:00,10226.0 -2013-12-22 10:00:00,10408.0 -2013-12-22 11:00:00,10597.0 -2013-12-22 12:00:00,10794.0 -2013-12-22 13:00:00,10844.0 -2013-12-22 14:00:00,10867.0 -2013-12-22 15:00:00,10885.0 -2013-12-22 16:00:00,10942.0 -2013-12-22 17:00:00,11150.0 -2013-12-22 18:00:00,12025.0 -2013-12-22 19:00:00,12727.0 -2013-12-22 20:00:00,12748.0 -2013-12-22 21:00:00,12734.0 -2013-12-22 22:00:00,12574.0 -2013-12-22 23:00:00,12263.0 -2013-12-23 00:00:00,11657.0 -2013-12-21 01:00:00,10950.0 -2013-12-21 02:00:00,10359.0 -2013-12-21 03:00:00,9949.0 -2013-12-21 04:00:00,9692.0 -2013-12-21 05:00:00,9646.0 -2013-12-21 06:00:00,9695.0 -2013-12-21 07:00:00,9975.0 -2013-12-21 08:00:00,10500.0 -2013-12-21 09:00:00,10969.0 -2013-12-21 10:00:00,11251.0 -2013-12-21 11:00:00,11551.0 -2013-12-21 12:00:00,11731.0 -2013-12-21 13:00:00,11762.0 -2013-12-21 14:00:00,11686.0 -2013-12-21 15:00:00,11587.0 -2013-12-21 16:00:00,11518.0 -2013-12-21 17:00:00,11724.0 -2013-12-21 18:00:00,12439.0 -2013-12-21 19:00:00,12815.0 -2013-12-21 20:00:00,12751.0 -2013-12-21 21:00:00,12628.0 -2013-12-21 22:00:00,12410.0 -2013-12-21 23:00:00,12083.0 -2013-12-22 00:00:00,11486.0 -2013-12-20 01:00:00,10932.0 -2013-12-20 02:00:00,10322.0 -2013-12-20 03:00:00,9925.0 -2013-12-20 04:00:00,9735.0 -2013-12-20 05:00:00,9712.0 -2013-12-20 06:00:00,9968.0 -2013-12-20 07:00:00,10664.0 -2013-12-20 08:00:00,11853.0 -2013-12-20 09:00:00,12618.0 -2013-12-20 10:00:00,12777.0 -2013-12-20 11:00:00,12880.0 -2013-12-20 12:00:00,12983.0 -2013-12-20 13:00:00,12933.0 -2013-12-20 14:00:00,12839.0 -2013-12-20 15:00:00,12826.0 -2013-12-20 16:00:00,12775.0 -2013-12-20 17:00:00,12808.0 -2013-12-20 18:00:00,13509.0 -2013-12-20 19:00:00,13792.0 -2013-12-20 20:00:00,13586.0 -2013-12-20 21:00:00,13353.0 -2013-12-20 22:00:00,12973.0 -2013-12-20 23:00:00,12526.0 -2013-12-21 00:00:00,11818.0 -2013-12-19 01:00:00,11438.0 -2013-12-19 02:00:00,10791.0 -2013-12-19 03:00:00,10401.0 -2013-12-19 04:00:00,10176.0 -2013-12-19 05:00:00,10146.0 -2013-12-19 06:00:00,10355.0 -2013-12-19 07:00:00,11099.0 -2013-12-19 08:00:00,12245.0 -2013-12-19 09:00:00,12766.0 -2013-12-19 10:00:00,12777.0 -2013-12-19 11:00:00,12676.0 -2013-12-19 12:00:00,12628.0 -2013-12-19 13:00:00,12538.0 -2013-12-19 14:00:00,12498.0 -2013-12-19 15:00:00,12573.0 -2013-12-19 16:00:00,12551.0 -2013-12-19 17:00:00,12677.0 -2013-12-19 18:00:00,13535.0 -2013-12-19 19:00:00,13984.0 -2013-12-19 20:00:00,13779.0 -2013-12-19 21:00:00,13570.0 -2013-12-19 22:00:00,13312.0 -2013-12-19 23:00:00,12818.0 -2013-12-20 00:00:00,11879.0 -2013-12-18 01:00:00,11688.0 -2013-12-18 02:00:00,11110.0 -2013-12-18 03:00:00,10896.0 -2013-12-18 04:00:00,10624.0 -2013-12-18 05:00:00,10731.0 -2013-12-18 06:00:00,11036.0 -2013-12-18 07:00:00,11825.0 -2013-12-18 08:00:00,12895.0 -2013-12-18 09:00:00,13473.0 -2013-12-18 10:00:00,13451.0 -2013-12-18 11:00:00,13476.0 -2013-12-18 12:00:00,13358.0 -2013-12-18 13:00:00,13114.0 -2013-12-18 14:00:00,12928.0 -2013-12-18 15:00:00,12892.0 -2013-12-18 16:00:00,12844.0 -2013-12-18 17:00:00,12923.0 -2013-12-18 18:00:00,13835.0 -2013-12-18 19:00:00,14588.0 -2013-12-18 20:00:00,14429.0 -2013-12-18 21:00:00,14212.0 -2013-12-18 22:00:00,13933.0 -2013-12-18 23:00:00,13339.0 -2013-12-19 00:00:00,12406.0 -2013-12-17 01:00:00,12083.0 -2013-12-17 02:00:00,11488.0 -2013-12-17 03:00:00,11213.0 -2013-12-17 04:00:00,11068.0 -2013-12-17 05:00:00,11037.0 -2013-12-17 06:00:00,11254.0 -2013-12-17 07:00:00,11975.0 -2013-12-17 08:00:00,13162.0 -2013-12-17 09:00:00,13650.0 -2013-12-17 10:00:00,13700.0 -2013-12-17 11:00:00,13637.0 -2013-12-17 12:00:00,13707.0 -2013-12-17 13:00:00,13445.0 -2013-12-17 14:00:00,13183.0 -2013-12-17 15:00:00,13052.0 -2013-12-17 16:00:00,12911.0 -2013-12-17 17:00:00,13027.0 -2013-12-17 18:00:00,13861.0 -2013-12-17 19:00:00,14629.0 -2013-12-17 20:00:00,14538.0 -2013-12-17 21:00:00,14362.0 -2013-12-17 22:00:00,14075.0 -2013-12-17 23:00:00,13519.0 -2013-12-18 00:00:00,12609.0 -2013-12-16 01:00:00,11707.0 -2013-12-16 02:00:00,11261.0 -2013-12-16 03:00:00,11018.0 -2013-12-16 04:00:00,10917.0 -2013-12-16 05:00:00,10932.0 -2013-12-16 06:00:00,11282.0 -2013-12-16 07:00:00,11989.0 -2013-12-16 08:00:00,13276.0 -2013-12-16 09:00:00,13740.0 -2013-12-16 10:00:00,13819.0 -2013-12-16 11:00:00,13841.0 -2013-12-16 12:00:00,13752.0 -2013-12-16 13:00:00,13717.0 -2013-12-16 14:00:00,13633.0 -2013-12-16 15:00:00,13614.0 -2013-12-16 16:00:00,13567.0 -2013-12-16 17:00:00,13728.0 -2013-12-16 18:00:00,14565.0 -2013-12-16 19:00:00,15091.0 -2013-12-16 20:00:00,14944.0 -2013-12-16 21:00:00,14743.0 -2013-12-16 22:00:00,14495.0 -2013-12-16 23:00:00,13932.0 -2013-12-17 00:00:00,13022.0 -2013-12-15 01:00:00,11178.0 -2013-12-15 02:00:00,10710.0 -2013-12-15 03:00:00,10360.0 -2013-12-15 04:00:00,10226.0 -2013-12-15 05:00:00,10186.0 -2013-12-15 06:00:00,10262.0 -2013-12-15 07:00:00,10442.0 -2013-12-15 08:00:00,10854.0 -2013-12-15 09:00:00,10967.0 -2013-12-15 10:00:00,11367.0 -2013-12-15 11:00:00,11580.0 -2013-12-15 12:00:00,11771.0 -2013-12-15 13:00:00,11831.0 -2013-12-15 14:00:00,11874.0 -2013-12-15 15:00:00,11821.0 -2013-12-15 16:00:00,11750.0 -2013-12-15 17:00:00,11886.0 -2013-12-15 18:00:00,12856.0 -2013-12-15 19:00:00,13755.0 -2013-12-15 20:00:00,13821.0 -2013-12-15 21:00:00,13811.0 -2013-12-15 22:00:00,13574.0 -2013-12-15 23:00:00,13188.0 -2013-12-16 00:00:00,12442.0 -2013-12-14 01:00:00,11618.0 -2013-12-14 02:00:00,10961.0 -2013-12-14 03:00:00,10516.0 -2013-12-14 04:00:00,10314.0 -2013-12-14 05:00:00,10212.0 -2013-12-14 06:00:00,10302.0 -2013-12-14 07:00:00,10587.0 -2013-12-14 08:00:00,11158.0 -2013-12-14 09:00:00,11452.0 -2013-12-14 10:00:00,11805.0 -2013-12-14 11:00:00,12055.0 -2013-12-14 12:00:00,12152.0 -2013-12-14 13:00:00,12118.0 -2013-12-14 14:00:00,11972.0 -2013-12-14 15:00:00,11900.0 -2013-12-14 16:00:00,11787.0 -2013-12-14 17:00:00,11929.0 -2013-12-14 18:00:00,12725.0 -2013-12-14 19:00:00,13352.0 -2013-12-14 20:00:00,13217.0 -2013-12-14 21:00:00,13058.0 -2013-12-14 22:00:00,12854.0 -2013-12-14 23:00:00,12468.0 -2013-12-15 00:00:00,11921.0 -2013-12-13 01:00:00,12245.0 -2013-12-13 02:00:00,11606.0 -2013-12-13 03:00:00,11239.0 -2013-12-13 04:00:00,11038.0 -2013-12-13 05:00:00,10978.0 -2013-12-13 06:00:00,11196.0 -2013-12-13 07:00:00,11833.0 -2013-12-13 08:00:00,12998.0 -2013-12-13 09:00:00,13416.0 -2013-12-13 10:00:00,13450.0 -2013-12-13 11:00:00,13370.0 -2013-12-13 12:00:00,13257.0 -2013-12-13 13:00:00,13037.0 -2013-12-13 14:00:00,12840.0 -2013-12-13 15:00:00,12852.0 -2013-12-13 16:00:00,12861.0 -2013-12-13 17:00:00,13039.0 -2013-12-13 18:00:00,13905.0 -2013-12-13 19:00:00,14309.0 -2013-12-13 20:00:00,14112.0 -2013-12-13 21:00:00,13897.0 -2013-12-13 22:00:00,13577.0 -2013-12-13 23:00:00,13203.0 -2013-12-14 00:00:00,12436.0 -2013-12-12 01:00:00,12870.0 -2013-12-12 02:00:00,12306.0 -2013-12-12 03:00:00,12005.0 -2013-12-12 04:00:00,11870.0 -2013-12-12 05:00:00,11901.0 -2013-12-12 06:00:00,12154.0 -2013-12-12 07:00:00,12827.0 -2013-12-12 08:00:00,13972.0 -2013-12-12 09:00:00,14507.0 -2013-12-12 10:00:00,14574.0 -2013-12-12 11:00:00,14634.0 -2013-12-12 12:00:00,14507.0 -2013-12-12 13:00:00,14257.0 -2013-12-12 14:00:00,14049.0 -2013-12-12 15:00:00,13882.0 -2013-12-12 16:00:00,13709.0 -2013-12-12 17:00:00,13782.0 -2013-12-12 18:00:00,14668.0 -2013-12-12 19:00:00,15346.0 -2013-12-12 20:00:00,15165.0 -2013-12-12 21:00:00,14976.0 -2013-12-12 22:00:00,14721.0 -2013-12-12 23:00:00,14108.0 -2013-12-13 00:00:00,13170.0 -2013-12-11 01:00:00,12299.0 -2013-12-11 02:00:00,11680.0 -2013-12-11 03:00:00,11391.0 -2013-12-11 04:00:00,11215.0 -2013-12-11 05:00:00,11111.0 -2013-12-11 06:00:00,11339.0 -2013-12-11 07:00:00,12027.0 -2013-12-11 08:00:00,13152.0 -2013-12-11 09:00:00,13612.0 -2013-12-11 10:00:00,13692.0 -2013-12-11 11:00:00,13410.0 -2013-12-11 12:00:00,13641.0 -2013-12-11 13:00:00,13611.0 -2013-12-11 14:00:00,13488.0 -2013-12-11 15:00:00,13488.0 -2013-12-11 16:00:00,13408.0 -2013-12-11 17:00:00,13549.0 -2013-12-11 18:00:00,14552.0 -2013-12-11 19:00:00,15443.0 -2013-12-11 20:00:00,15428.0 -2013-12-11 21:00:00,15310.0 -2013-12-11 22:00:00,15096.0 -2013-12-11 23:00:00,14608.0 -2013-12-12 00:00:00,13749.0 -2013-12-10 01:00:00,12568.0 -2013-12-10 02:00:00,12013.0 -2013-12-10 03:00:00,11736.0 -2013-12-10 04:00:00,11588.0 -2013-12-10 05:00:00,11608.0 -2013-12-10 06:00:00,11834.0 -2013-12-10 07:00:00,12521.0 -2013-12-10 08:00:00,13720.0 -2013-12-10 09:00:00,14239.0 -2013-12-10 10:00:00,14343.0 -2013-12-10 11:00:00,14299.0 -2013-12-10 12:00:00,14105.0 -2013-12-10 13:00:00,13928.0 -2013-12-10 14:00:00,13811.0 -2013-12-10 15:00:00,13736.0 -2013-12-10 16:00:00,13557.0 -2013-12-10 17:00:00,13627.0 -2013-12-10 18:00:00,14619.0 -2013-12-10 19:00:00,15372.0 -2013-12-10 20:00:00,15304.0 -2013-12-10 21:00:00,15126.0 -2013-12-10 22:00:00,14818.0 -2013-12-10 23:00:00,14180.0 -2013-12-11 00:00:00,13258.0 -2013-12-09 01:00:00,10946.0 -2013-12-09 02:00:00,10487.0 -2013-12-09 03:00:00,10323.0 -2013-12-09 04:00:00,10205.0 -2013-12-09 05:00:00,10333.0 -2013-12-09 06:00:00,10653.0 -2013-12-09 07:00:00,11396.0 -2013-12-09 08:00:00,12662.0 -2013-12-09 09:00:00,13265.0 -2013-12-09 10:00:00,13356.0 -2013-12-09 11:00:00,13440.0 -2013-12-09 12:00:00,13376.0 -2013-12-09 13:00:00,13272.0 -2013-12-09 14:00:00,13224.0 -2013-12-09 15:00:00,13277.0 -2013-12-09 16:00:00,13385.0 -2013-12-09 17:00:00,13608.0 -2013-12-09 18:00:00,14595.0 -2013-12-09 19:00:00,15338.0 -2013-12-09 20:00:00,15288.0 -2013-12-09 21:00:00,15147.0 -2013-12-09 22:00:00,14916.0 -2013-12-09 23:00:00,14399.0 -2013-12-10 00:00:00,13530.0 -2013-12-08 01:00:00,11575.0 -2013-12-08 02:00:00,11001.0 -2013-12-08 03:00:00,10691.0 -2013-12-08 04:00:00,10421.0 -2013-12-08 05:00:00,10366.0 -2013-12-08 06:00:00,10343.0 -2013-12-08 07:00:00,10483.0 -2013-12-08 08:00:00,10721.0 -2013-12-08 09:00:00,10836.0 -2013-12-08 10:00:00,11098.0 -2013-12-08 11:00:00,11362.0 -2013-12-08 12:00:00,11593.0 -2013-12-08 13:00:00,11718.0 -2013-12-08 14:00:00,11753.0 -2013-12-08 15:00:00,11739.0 -2013-12-08 16:00:00,11784.0 -2013-12-08 17:00:00,11960.0 -2013-12-08 18:00:00,12828.0 -2013-12-08 19:00:00,13421.0 -2013-12-08 20:00:00,13437.0 -2013-12-08 21:00:00,13332.0 -2013-12-08 22:00:00,13084.0 -2013-12-08 23:00:00,12550.0 -2013-12-09 00:00:00,11699.0 -2013-12-07 01:00:00,12003.0 -2013-12-07 02:00:00,11365.0 -2013-12-07 03:00:00,11057.0 -2013-12-07 04:00:00,10855.0 -2013-12-07 05:00:00,10798.0 -2013-12-07 06:00:00,10859.0 -2013-12-07 07:00:00,11212.0 -2013-12-07 08:00:00,11770.0 -2013-12-07 09:00:00,11987.0 -2013-12-07 10:00:00,12205.0 -2013-12-07 11:00:00,12345.0 -2013-12-07 12:00:00,12344.0 -2013-12-07 13:00:00,12209.0 -2013-12-07 14:00:00,12013.0 -2013-12-07 15:00:00,11757.0 -2013-12-07 16:00:00,11663.0 -2013-12-07 17:00:00,11763.0 -2013-12-07 18:00:00,12726.0 -2013-12-07 19:00:00,13504.0 -2013-12-07 20:00:00,13456.0 -2013-12-07 21:00:00,13366.0 -2013-12-07 22:00:00,13144.0 -2013-12-07 23:00:00,12923.0 -2013-12-08 00:00:00,12268.0 -2013-12-06 01:00:00,11360.0 -2013-12-06 02:00:00,10732.0 -2013-12-06 03:00:00,10413.0 -2013-12-06 04:00:00,10240.0 -2013-12-06 05:00:00,10257.0 -2013-12-06 06:00:00,10539.0 -2013-12-06 07:00:00,11254.0 -2013-12-06 08:00:00,12427.0 -2013-12-06 09:00:00,13115.0 -2013-12-06 10:00:00,13215.0 -2013-12-06 11:00:00,13358.0 -2013-12-06 12:00:00,13436.0 -2013-12-06 13:00:00,13365.0 -2013-12-06 14:00:00,13277.0 -2013-12-06 15:00:00,13234.0 -2013-12-06 16:00:00,13160.0 -2013-12-06 17:00:00,13078.0 -2013-12-06 18:00:00,13812.0 -2013-12-06 19:00:00,14435.0 -2013-12-06 20:00:00,14287.0 -2013-12-06 21:00:00,14088.0 -2013-12-06 22:00:00,13822.0 -2013-12-06 23:00:00,13517.0 -2013-12-07 00:00:00,12761.0 -2013-12-05 01:00:00,10095.0 -2013-12-05 02:00:00,9549.0 -2013-12-05 03:00:00,9312.0 -2013-12-05 04:00:00,9190.0 -2013-12-05 05:00:00,9256.0 -2013-12-05 06:00:00,9563.0 -2013-12-05 07:00:00,10413.0 -2013-12-05 08:00:00,11699.0 -2013-12-05 09:00:00,12413.0 -2013-12-05 10:00:00,12535.0 -2013-12-05 11:00:00,12558.0 -2013-12-05 12:00:00,12540.0 -2013-12-05 13:00:00,12518.0 -2013-12-05 14:00:00,12537.0 -2013-12-05 15:00:00,12651.0 -2013-12-05 16:00:00,12708.0 -2013-12-05 17:00:00,12854.0 -2013-12-05 18:00:00,13716.0 -2013-12-05 19:00:00,14206.0 -2013-12-05 20:00:00,14067.0 -2013-12-05 21:00:00,13867.0 -2013-12-05 22:00:00,13587.0 -2013-12-05 23:00:00,13064.0 -2013-12-06 00:00:00,12225.0 -2013-12-04 01:00:00,9944.0 -2013-12-04 02:00:00,9365.0 -2013-12-04 03:00:00,9020.0 -2013-12-04 04:00:00,8816.0 -2013-12-04 05:00:00,8779.0 -2013-12-04 06:00:00,8998.0 -2013-12-04 07:00:00,9730.0 -2013-12-04 08:00:00,10944.0 -2013-12-04 09:00:00,11581.0 -2013-12-04 10:00:00,11753.0 -2013-12-04 11:00:00,11894.0 -2013-12-04 12:00:00,11970.0 -2013-12-04 13:00:00,11961.0 -2013-12-04 14:00:00,11927.0 -2013-12-04 15:00:00,11959.0 -2013-12-04 16:00:00,11975.0 -2013-12-04 17:00:00,12122.0 -2013-12-04 18:00:00,12734.0 -2013-12-04 19:00:00,13002.0 -2013-12-04 20:00:00,12782.0 -2013-12-04 21:00:00,12573.0 -2013-12-04 22:00:00,12230.0 -2013-12-04 23:00:00,11659.0 -2013-12-05 00:00:00,10847.0 -2013-12-03 01:00:00,10426.0 -2013-12-03 02:00:00,9856.0 -2013-12-03 03:00:00,9505.0 -2013-12-03 04:00:00,9317.0 -2013-12-03 05:00:00,9312.0 -2013-12-03 06:00:00,9554.0 -2013-12-03 07:00:00,10241.0 -2013-12-03 08:00:00,11425.0 -2013-12-03 09:00:00,12088.0 -2013-12-03 10:00:00,12153.0 -2013-12-03 11:00:00,12221.0 -2013-12-03 12:00:00,12235.0 -2013-12-03 13:00:00,12135.0 -2013-12-03 14:00:00,12109.0 -2013-12-03 15:00:00,12132.0 -2013-12-03 16:00:00,12100.0 -2013-12-03 17:00:00,12188.0 -2013-12-03 18:00:00,12847.0 -2013-12-03 19:00:00,13131.0 -2013-12-03 20:00:00,12945.0 -2013-12-03 21:00:00,12661.0 -2013-12-03 22:00:00,12345.0 -2013-12-03 23:00:00,11738.0 -2013-12-04 00:00:00,10861.0 -2013-12-02 01:00:00,9790.0 -2013-12-02 02:00:00,9414.0 -2013-12-02 03:00:00,9163.0 -2013-12-02 04:00:00,9114.0 -2013-12-02 05:00:00,9130.0 -2013-12-02 06:00:00,9510.0 -2013-12-02 07:00:00,10313.0 -2013-12-02 08:00:00,11479.0 -2013-12-02 09:00:00,12066.0 -2013-12-02 10:00:00,12181.0 -2013-12-02 11:00:00,12145.0 -2013-12-02 12:00:00,12152.0 -2013-12-02 13:00:00,12081.0 -2013-12-02 14:00:00,12021.0 -2013-12-02 15:00:00,12056.0 -2013-12-02 16:00:00,12068.0 -2013-12-02 17:00:00,12142.0 -2013-12-02 18:00:00,12949.0 -2013-12-02 19:00:00,13467.0 -2013-12-02 20:00:00,13307.0 -2013-12-02 21:00:00,13108.0 -2013-12-02 22:00:00,12806.0 -2013-12-02 23:00:00,12243.0 -2013-12-03 00:00:00,11332.0 -2013-12-01 01:00:00,9777.0 -2013-12-01 02:00:00,9258.0 -2013-12-01 03:00:00,8953.0 -2013-12-01 04:00:00,8776.0 -2013-12-01 05:00:00,8788.0 -2013-12-01 06:00:00,8762.0 -2013-12-01 07:00:00,8978.0 -2013-12-01 08:00:00,9207.0 -2013-12-01 09:00:00,9312.0 -2013-12-01 10:00:00,9512.0 -2013-12-01 11:00:00,9634.0 -2013-12-01 12:00:00,9699.0 -2013-12-01 13:00:00,9721.0 -2013-12-01 14:00:00,9680.0 -2013-12-01 15:00:00,9676.0 -2013-12-01 16:00:00,9699.0 -2013-12-01 17:00:00,9868.0 -2013-12-01 18:00:00,10884.0 -2013-12-01 19:00:00,11655.0 -2013-12-01 20:00:00,11735.0 -2013-12-01 21:00:00,11650.0 -2013-12-01 22:00:00,11449.0 -2013-12-01 23:00:00,11035.0 -2013-12-02 00:00:00,10435.0 -2013-11-30 01:00:00,10135.0 -2013-11-30 02:00:00,9652.0 -2013-11-30 03:00:00,9366.0 -2013-11-30 04:00:00,9159.0 -2013-11-30 05:00:00,9105.0 -2013-11-30 06:00:00,9198.0 -2013-11-30 07:00:00,9492.0 -2013-11-30 08:00:00,9894.0 -2013-11-30 09:00:00,10134.0 -2013-11-30 10:00:00,10309.0 -2013-11-30 11:00:00,10405.0 -2013-11-30 12:00:00,10378.0 -2013-11-30 13:00:00,10300.0 -2013-11-30 14:00:00,10135.0 -2013-11-30 15:00:00,9884.0 -2013-11-30 16:00:00,9757.0 -2013-11-30 17:00:00,9835.0 -2013-11-30 18:00:00,10628.0 -2013-11-30 19:00:00,11272.0 -2013-11-30 20:00:00,11277.0 -2013-11-30 21:00:00,11162.0 -2013-11-30 22:00:00,11081.0 -2013-11-30 23:00:00,10774.0 -2013-12-01 00:00:00,10290.0 -2013-11-29 01:00:00,9938.0 -2013-11-29 02:00:00,9643.0 -2013-11-29 03:00:00,9476.0 -2013-11-29 04:00:00,9382.0 -2013-11-29 05:00:00,9429.0 -2013-11-29 06:00:00,9637.0 -2013-11-29 07:00:00,10076.0 -2013-11-29 08:00:00,10602.0 -2013-11-29 09:00:00,10730.0 -2013-11-29 10:00:00,10786.0 -2013-11-29 11:00:00,10847.0 -2013-11-29 12:00:00,10823.0 -2013-11-29 13:00:00,10761.0 -2013-11-29 14:00:00,10658.0 -2013-11-29 15:00:00,10563.0 -2013-11-29 16:00:00,10536.0 -2013-11-29 17:00:00,10691.0 -2013-11-29 18:00:00,11547.0 -2013-11-29 19:00:00,12098.0 -2013-11-29 20:00:00,11950.0 -2013-11-29 21:00:00,11816.0 -2013-11-29 22:00:00,11584.0 -2013-11-29 23:00:00,11271.0 -2013-11-30 00:00:00,10705.0 -2013-11-28 01:00:00,10886.0 -2013-11-28 02:00:00,10326.0 -2013-11-28 03:00:00,9965.0 -2013-11-28 04:00:00,9756.0 -2013-11-28 05:00:00,9649.0 -2013-11-28 06:00:00,9688.0 -2013-11-28 07:00:00,9858.0 -2013-11-28 08:00:00,10142.0 -2013-11-28 09:00:00,10171.0 -2013-11-28 10:00:00,10315.0 -2013-11-28 11:00:00,10490.0 -2013-11-28 12:00:00,10539.0 -2013-11-28 13:00:00,10458.0 -2013-11-28 14:00:00,10268.0 -2013-11-28 15:00:00,10049.0 -2013-11-28 16:00:00,9906.0 -2013-11-28 17:00:00,9911.0 -2013-11-28 18:00:00,10365.0 -2013-11-28 19:00:00,10763.0 -2013-11-28 20:00:00,10717.0 -2013-11-28 21:00:00,10702.0 -2013-11-28 22:00:00,10681.0 -2013-11-28 23:00:00,10572.0 -2013-11-29 00:00:00,10283.0 -2013-11-27 01:00:00,11324.0 -2013-11-27 02:00:00,10837.0 -2013-11-27 03:00:00,10588.0 -2013-11-27 04:00:00,10451.0 -2013-11-27 05:00:00,10454.0 -2013-11-27 06:00:00,10734.0 -2013-11-27 07:00:00,11456.0 -2013-11-27 08:00:00,12408.0 -2013-11-27 09:00:00,12816.0 -2013-11-27 10:00:00,12993.0 -2013-11-27 11:00:00,13071.0 -2013-11-27 12:00:00,13059.0 -2013-11-27 13:00:00,12946.0 -2013-11-27 14:00:00,12789.0 -2013-11-27 15:00:00,12636.0 -2013-11-27 16:00:00,12467.0 -2013-11-27 17:00:00,12433.0 -2013-11-27 18:00:00,13132.0 -2013-11-27 19:00:00,13696.0 -2013-11-27 20:00:00,13435.0 -2013-11-27 21:00:00,13193.0 -2013-11-27 22:00:00,12882.0 -2013-11-27 23:00:00,12410.0 -2013-11-28 00:00:00,11659.0 -2013-11-26 01:00:00,10842.0 -2013-11-26 02:00:00,10344.0 -2013-11-26 03:00:00,10022.0 -2013-11-26 04:00:00,9852.0 -2013-11-26 05:00:00,9846.0 -2013-11-26 06:00:00,10089.0 -2013-11-26 07:00:00,10830.0 -2013-11-26 08:00:00,11940.0 -2013-11-26 09:00:00,12515.0 -2013-11-26 10:00:00,12714.0 -2013-11-26 11:00:00,12833.0 -2013-11-26 12:00:00,12872.0 -2013-11-26 13:00:00,12825.0 -2013-11-26 14:00:00,12785.0 -2013-11-26 15:00:00,12899.0 -2013-11-26 16:00:00,12916.0 -2013-11-26 17:00:00,12963.0 -2013-11-26 18:00:00,13441.0 -2013-11-26 19:00:00,13791.0 -2013-11-26 20:00:00,13575.0 -2013-11-26 21:00:00,13452.0 -2013-11-26 22:00:00,13217.0 -2013-11-26 23:00:00,12780.0 -2013-11-27 00:00:00,12034.0 -2013-11-25 01:00:00,10800.0 -2013-11-25 02:00:00,10420.0 -2013-11-25 03:00:00,10207.0 -2013-11-25 04:00:00,10094.0 -2013-11-25 05:00:00,10087.0 -2013-11-25 06:00:00,10386.0 -2013-11-25 07:00:00,11167.0 -2013-11-25 08:00:00,12323.0 -2013-11-25 09:00:00,12899.0 -2013-11-25 10:00:00,13085.0 -2013-11-25 11:00:00,13193.0 -2013-11-25 12:00:00,13275.0 -2013-11-25 13:00:00,13254.0 -2013-11-25 14:00:00,13196.0 -2013-11-25 15:00:00,13221.0 -2013-11-25 16:00:00,13144.0 -2013-11-25 17:00:00,13116.0 -2013-11-25 18:00:00,13660.0 -2013-11-25 19:00:00,13928.0 -2013-11-25 20:00:00,13710.0 -2013-11-25 21:00:00,13429.0 -2013-11-25 22:00:00,13067.0 -2013-11-25 23:00:00,12455.0 -2013-11-26 00:00:00,11620.0 -2013-11-24 01:00:00,10971.0 -2013-11-24 02:00:00,10542.0 -2013-11-24 03:00:00,10311.0 -2013-11-24 04:00:00,10140.0 -2013-11-24 05:00:00,10118.0 -2013-11-24 06:00:00,10150.0 -2013-11-24 07:00:00,10304.0 -2013-11-24 08:00:00,10551.0 -2013-11-24 09:00:00,10567.0 -2013-11-24 10:00:00,10767.0 -2013-11-24 11:00:00,10883.0 -2013-11-24 12:00:00,10958.0 -2013-11-24 13:00:00,10973.0 -2013-11-24 14:00:00,10873.0 -2013-11-24 15:00:00,10792.0 -2013-11-24 16:00:00,10730.0 -2013-11-24 17:00:00,10881.0 -2013-11-24 18:00:00,11713.0 -2013-11-24 19:00:00,12513.0 -2013-11-24 20:00:00,12531.0 -2013-11-24 21:00:00,12489.0 -2013-11-24 22:00:00,12253.0 -2013-11-24 23:00:00,11939.0 -2013-11-25 00:00:00,11364.0 -2013-11-23 01:00:00,10590.0 -2013-11-23 02:00:00,10120.0 -2013-11-23 03:00:00,9881.0 -2013-11-23 04:00:00,9626.0 -2013-11-23 05:00:00,9603.0 -2013-11-23 06:00:00,9723.0 -2013-11-23 07:00:00,10072.0 -2013-11-23 08:00:00,10576.0 -2013-11-23 09:00:00,10801.0 -2013-11-23 10:00:00,11209.0 -2013-11-23 11:00:00,11453.0 -2013-11-23 12:00:00,11549.0 -2013-11-23 13:00:00,11516.0 -2013-11-23 14:00:00,11379.0 -2013-11-23 15:00:00,11158.0 -2013-11-23 16:00:00,11126.0 -2013-11-23 17:00:00,11215.0 -2013-11-23 18:00:00,11967.0 -2013-11-23 19:00:00,12658.0 -2013-11-23 20:00:00,12636.0 -2013-11-23 21:00:00,12514.0 -2013-11-23 22:00:00,12346.0 -2013-11-23 23:00:00,12067.0 -2013-11-24 00:00:00,11512.0 -2013-11-22 01:00:00,9797.0 -2013-11-22 02:00:00,9292.0 -2013-11-22 03:00:00,9011.0 -2013-11-22 04:00:00,8817.0 -2013-11-22 05:00:00,8842.0 -2013-11-22 06:00:00,9124.0 -2013-11-22 07:00:00,9875.0 -2013-11-22 08:00:00,11050.0 -2013-11-22 09:00:00,11854.0 -2013-11-22 10:00:00,12047.0 -2013-11-22 11:00:00,12195.0 -2013-11-22 12:00:00,12290.0 -2013-11-22 13:00:00,12134.0 -2013-11-22 14:00:00,11937.0 -2013-11-22 15:00:00,11963.0 -2013-11-22 16:00:00,11938.0 -2013-11-22 17:00:00,11932.0 -2013-11-22 18:00:00,12520.0 -2013-11-22 19:00:00,12961.0 -2013-11-22 20:00:00,12776.0 -2013-11-22 21:00:00,12548.0 -2013-11-22 22:00:00,12265.0 -2013-11-22 23:00:00,11903.0 -2013-11-23 00:00:00,11317.0 -2013-11-21 01:00:00,10007.0 -2013-11-21 02:00:00,9462.0 -2013-11-21 03:00:00,9161.0 -2013-11-21 04:00:00,8964.0 -2013-11-21 05:00:00,8919.0 -2013-11-21 06:00:00,9178.0 -2013-11-21 07:00:00,9913.0 -2013-11-21 08:00:00,11069.0 -2013-11-21 09:00:00,11545.0 -2013-11-21 10:00:00,11671.0 -2013-11-21 11:00:00,11710.0 -2013-11-21 12:00:00,11714.0 -2013-11-21 13:00:00,11793.0 -2013-11-21 14:00:00,11772.0 -2013-11-21 15:00:00,11838.0 -2013-11-21 16:00:00,11745.0 -2013-11-21 17:00:00,11857.0 -2013-11-21 18:00:00,12427.0 -2013-11-21 19:00:00,12634.0 -2013-11-21 20:00:00,12384.0 -2013-11-21 21:00:00,12134.0 -2013-11-21 22:00:00,11843.0 -2013-11-21 23:00:00,11283.0 -2013-11-22 00:00:00,10530.0 -2013-11-20 01:00:00,10235.0 -2013-11-20 02:00:00,9728.0 -2013-11-20 03:00:00,9447.0 -2013-11-20 04:00:00,9295.0 -2013-11-20 05:00:00,9329.0 -2013-11-20 06:00:00,9587.0 -2013-11-20 07:00:00,10388.0 -2013-11-20 08:00:00,11547.0 -2013-11-20 09:00:00,12017.0 -2013-11-20 10:00:00,12123.0 -2013-11-20 11:00:00,12103.0 -2013-11-20 12:00:00,12061.0 -2013-11-20 13:00:00,11968.0 -2013-11-20 14:00:00,11822.0 -2013-11-20 15:00:00,11843.0 -2013-11-20 16:00:00,11886.0 -2013-11-20 17:00:00,11983.0 -2013-11-20 18:00:00,12629.0 -2013-11-20 19:00:00,12990.0 -2013-11-20 20:00:00,12760.0 -2013-11-20 21:00:00,12502.0 -2013-11-20 22:00:00,12189.0 -2013-11-20 23:00:00,11615.0 -2013-11-21 00:00:00,10791.0 -2013-11-19 01:00:00,10190.0 -2013-11-19 02:00:00,9740.0 -2013-11-19 03:00:00,9497.0 -2013-11-19 04:00:00,9365.0 -2013-11-19 05:00:00,9385.0 -2013-11-19 06:00:00,9668.0 -2013-11-19 07:00:00,10489.0 -2013-11-19 08:00:00,11625.0 -2013-11-19 09:00:00,12004.0 -2013-11-19 10:00:00,12013.0 -2013-11-19 11:00:00,11987.0 -2013-11-19 12:00:00,11980.0 -2013-11-19 13:00:00,11891.0 -2013-11-19 14:00:00,11767.0 -2013-11-19 15:00:00,11763.0 -2013-11-19 16:00:00,11664.0 -2013-11-19 17:00:00,11696.0 -2013-11-19 18:00:00,12366.0 -2013-11-19 19:00:00,12964.0 -2013-11-19 20:00:00,12827.0 -2013-11-19 21:00:00,12630.0 -2013-11-19 22:00:00,12326.0 -2013-11-19 23:00:00,11795.0 -2013-11-20 00:00:00,11004.0 -2013-11-18 01:00:00,9016.0 -2013-11-18 02:00:00,8731.0 -2013-11-18 03:00:00,8564.0 -2013-11-18 04:00:00,8510.0 -2013-11-18 05:00:00,8587.0 -2013-11-18 06:00:00,8956.0 -2013-11-18 07:00:00,9790.0 -2013-11-18 08:00:00,11064.0 -2013-11-18 09:00:00,11554.0 -2013-11-18 10:00:00,11814.0 -2013-11-18 11:00:00,11890.0 -2013-11-18 12:00:00,11880.0 -2013-11-18 13:00:00,11818.0 -2013-11-18 14:00:00,11726.0 -2013-11-18 15:00:00,11693.0 -2013-11-18 16:00:00,11617.0 -2013-11-18 17:00:00,11599.0 -2013-11-18 18:00:00,12184.0 -2013-11-18 19:00:00,12887.0 -2013-11-18 20:00:00,12719.0 -2013-11-18 21:00:00,12520.0 -2013-11-18 22:00:00,12233.0 -2013-11-18 23:00:00,11716.0 -2013-11-19 00:00:00,10935.0 -2013-11-17 01:00:00,9072.0 -2013-11-17 02:00:00,8622.0 -2013-11-17 03:00:00,8293.0 -2013-11-17 04:00:00,8021.0 -2013-11-17 05:00:00,7942.0 -2013-11-17 06:00:00,7917.0 -2013-11-17 07:00:00,8040.0 -2013-11-17 08:00:00,8338.0 -2013-11-17 09:00:00,8439.0 -2013-11-17 10:00:00,8773.0 -2013-11-17 11:00:00,9122.0 -2013-11-17 12:00:00,9305.0 -2013-11-17 13:00:00,9726.0 -2013-11-17 14:00:00,9935.0 -2013-11-17 15:00:00,9693.0 -2013-11-17 16:00:00,9369.0 -2013-11-17 17:00:00,9622.0 -2013-11-17 18:00:00,10164.0 -2013-11-17 19:00:00,10692.0 -2013-11-17 20:00:00,10707.0 -2013-11-17 21:00:00,10631.0 -2013-11-17 22:00:00,10399.0 -2013-11-17 23:00:00,10034.0 -2013-11-18 00:00:00,9538.0 -2013-11-16 01:00:00,9975.0 -2013-11-16 02:00:00,9479.0 -2013-11-16 03:00:00,9097.0 -2013-11-16 04:00:00,8904.0 -2013-11-16 05:00:00,8768.0 -2013-11-16 06:00:00,8844.0 -2013-11-16 07:00:00,9131.0 -2013-11-16 08:00:00,9592.0 -2013-11-16 09:00:00,9939.0 -2013-11-16 10:00:00,10311.0 -2013-11-16 11:00:00,10612.0 -2013-11-16 12:00:00,10800.0 -2013-11-16 13:00:00,10780.0 -2013-11-16 14:00:00,10640.0 -2013-11-16 15:00:00,10499.0 -2013-11-16 16:00:00,10360.0 -2013-11-16 17:00:00,10398.0 -2013-11-16 18:00:00,10907.0 -2013-11-16 19:00:00,11181.0 -2013-11-16 20:00:00,11046.0 -2013-11-16 21:00:00,10876.0 -2013-11-16 22:00:00,10610.0 -2013-11-16 23:00:00,10204.0 -2013-11-17 00:00:00,9701.0 -2013-11-15 01:00:00,10100.0 -2013-11-15 02:00:00,9639.0 -2013-11-15 03:00:00,9361.0 -2013-11-15 04:00:00,9176.0 -2013-11-15 05:00:00,9184.0 -2013-11-15 06:00:00,9470.0 -2013-11-15 07:00:00,10198.0 -2013-11-15 08:00:00,11252.0 -2013-11-15 09:00:00,11724.0 -2013-11-15 10:00:00,11873.0 -2013-11-15 11:00:00,11873.0 -2013-11-15 12:00:00,11845.0 -2013-11-15 13:00:00,11770.0 -2013-11-15 14:00:00,11633.0 -2013-11-15 15:00:00,11648.0 -2013-11-15 16:00:00,11451.0 -2013-11-15 17:00:00,11404.0 -2013-11-15 18:00:00,11943.0 -2013-11-15 19:00:00,12488.0 -2013-11-15 20:00:00,12315.0 -2013-11-15 21:00:00,12056.0 -2013-11-15 22:00:00,11741.0 -2013-11-15 23:00:00,11344.0 -2013-11-16 00:00:00,10670.0 -2013-11-14 01:00:00,10659.0 -2013-11-14 02:00:00,10196.0 -2013-11-14 03:00:00,9889.0 -2013-11-14 04:00:00,9734.0 -2013-11-14 05:00:00,9744.0 -2013-11-14 06:00:00,10003.0 -2013-11-14 07:00:00,10725.0 -2013-11-14 08:00:00,11768.0 -2013-11-14 09:00:00,12138.0 -2013-11-14 10:00:00,12189.0 -2013-11-14 11:00:00,12170.0 -2013-11-14 12:00:00,12144.0 -2013-11-14 13:00:00,12007.0 -2013-11-14 14:00:00,11882.0 -2013-11-14 15:00:00,11850.0 -2013-11-14 16:00:00,11739.0 -2013-11-14 17:00:00,11774.0 -2013-11-14 18:00:00,12329.0 -2013-11-14 19:00:00,12909.0 -2013-11-14 20:00:00,12724.0 -2013-11-14 21:00:00,12506.0 -2013-11-14 22:00:00,12210.0 -2013-11-14 23:00:00,11643.0 -2013-11-15 00:00:00,10848.0 -2013-11-13 01:00:00,10786.0 -2013-11-13 02:00:00,10365.0 -2013-11-13 03:00:00,10132.0 -2013-11-13 04:00:00,10010.0 -2013-11-13 05:00:00,10020.0 -2013-11-13 06:00:00,10316.0 -2013-11-13 07:00:00,11085.0 -2013-11-13 08:00:00,12128.0 -2013-11-13 09:00:00,12495.0 -2013-11-13 10:00:00,12562.0 -2013-11-13 11:00:00,12564.0 -2013-11-13 12:00:00,12551.0 -2013-11-13 13:00:00,12473.0 -2013-11-13 14:00:00,12364.0 -2013-11-13 15:00:00,12292.0 -2013-11-13 16:00:00,12145.0 -2013-11-13 17:00:00,12174.0 -2013-11-13 18:00:00,12777.0 -2013-11-13 19:00:00,13513.0 -2013-11-13 20:00:00,13343.0 -2013-11-13 21:00:00,13107.0 -2013-11-13 22:00:00,12824.0 -2013-11-13 23:00:00,12281.0 -2013-11-14 00:00:00,11474.0 -2013-11-12 01:00:00,10494.0 -2013-11-12 02:00:00,10099.0 -2013-11-12 03:00:00,9840.0 -2013-11-12 04:00:00,9671.0 -2013-11-12 05:00:00,9731.0 -2013-11-12 06:00:00,10037.0 -2013-11-12 07:00:00,10797.0 -2013-11-12 08:00:00,11913.0 -2013-11-12 09:00:00,12294.0 -2013-11-12 10:00:00,12368.0 -2013-11-12 11:00:00,12381.0 -2013-11-12 12:00:00,12387.0 -2013-11-12 13:00:00,12324.0 -2013-11-12 14:00:00,12238.0 -2013-11-12 15:00:00,12189.0 -2013-11-12 16:00:00,12076.0 -2013-11-12 17:00:00,12088.0 -2013-11-12 18:00:00,12663.0 -2013-11-12 19:00:00,13432.0 -2013-11-12 20:00:00,13319.0 -2013-11-12 21:00:00,13111.0 -2013-11-12 22:00:00,12805.0 -2013-11-12 23:00:00,12275.0 -2013-11-13 00:00:00,11526.0 -2013-11-11 01:00:00,9042.0 -2013-11-11 02:00:00,8706.0 -2013-11-11 03:00:00,8521.0 -2013-11-11 04:00:00,8413.0 -2013-11-11 05:00:00,8432.0 -2013-11-11 06:00:00,8735.0 -2013-11-11 07:00:00,9487.0 -2013-11-11 08:00:00,10523.0 -2013-11-11 09:00:00,11068.0 -2013-11-11 10:00:00,11462.0 -2013-11-11 11:00:00,11682.0 -2013-11-11 12:00:00,11875.0 -2013-11-11 13:00:00,11993.0 -2013-11-11 14:00:00,12129.0 -2013-11-11 15:00:00,12243.0 -2013-11-11 16:00:00,12262.0 -2013-11-11 17:00:00,12441.0 -2013-11-11 18:00:00,12918.0 -2013-11-11 19:00:00,13326.0 -2013-11-11 20:00:00,13094.0 -2013-11-11 21:00:00,12877.0 -2013-11-11 22:00:00,12568.0 -2013-11-11 23:00:00,12027.0 -2013-11-12 00:00:00,11213.0 -2013-11-10 01:00:00,9226.0 -2013-11-10 02:00:00,8833.0 -2013-11-10 03:00:00,8590.0 -2013-11-10 04:00:00,8424.0 -2013-11-10 05:00:00,8376.0 -2013-11-10 06:00:00,8434.0 -2013-11-10 07:00:00,8618.0 -2013-11-10 08:00:00,8778.0 -2013-11-10 09:00:00,8792.0 -2013-11-10 10:00:00,9006.0 -2013-11-10 11:00:00,9173.0 -2013-11-10 12:00:00,9198.0 -2013-11-10 13:00:00,9246.0 -2013-11-10 14:00:00,9205.0 -2013-11-10 15:00:00,9144.0 -2013-11-10 16:00:00,9076.0 -2013-11-10 17:00:00,9172.0 -2013-11-10 18:00:00,9745.0 -2013-11-10 19:00:00,10618.0 -2013-11-10 20:00:00,10669.0 -2013-11-10 21:00:00,10582.0 -2013-11-10 22:00:00,10362.0 -2013-11-10 23:00:00,10029.0 -2013-11-11 00:00:00,9543.0 -2013-11-09 01:00:00,9836.0 -2013-11-09 02:00:00,9286.0 -2013-11-09 03:00:00,8954.0 -2013-11-09 04:00:00,8732.0 -2013-11-09 05:00:00,8644.0 -2013-11-09 06:00:00,8711.0 -2013-11-09 07:00:00,9033.0 -2013-11-09 08:00:00,9389.0 -2013-11-09 09:00:00,9561.0 -2013-11-09 10:00:00,9867.0 -2013-11-09 11:00:00,10023.0 -2013-11-09 12:00:00,10063.0 -2013-11-09 13:00:00,10024.0 -2013-11-09 14:00:00,9888.0 -2013-11-09 15:00:00,9741.0 -2013-11-09 16:00:00,9612.0 -2013-11-09 17:00:00,9635.0 -2013-11-09 18:00:00,10064.0 -2013-11-09 19:00:00,10796.0 -2013-11-09 20:00:00,10775.0 -2013-11-09 21:00:00,10622.0 -2013-11-09 22:00:00,10439.0 -2013-11-09 23:00:00,10158.0 -2013-11-10 00:00:00,9726.0 -2013-11-08 01:00:00,10170.0 -2013-11-08 02:00:00,9687.0 -2013-11-08 03:00:00,9361.0 -2013-11-08 04:00:00,9266.0 -2013-11-08 05:00:00,9252.0 -2013-11-08 06:00:00,9574.0 -2013-11-08 07:00:00,10312.0 -2013-11-08 08:00:00,11285.0 -2013-11-08 09:00:00,11724.0 -2013-11-08 10:00:00,11848.0 -2013-11-08 11:00:00,11835.0 -2013-11-08 12:00:00,11840.0 -2013-11-08 13:00:00,11733.0 -2013-11-08 14:00:00,11584.0 -2013-11-08 15:00:00,11549.0 -2013-11-08 16:00:00,11470.0 -2013-11-08 17:00:00,11530.0 -2013-11-08 18:00:00,11982.0 -2013-11-08 19:00:00,12534.0 -2013-11-08 20:00:00,12288.0 -2013-11-08 21:00:00,12041.0 -2013-11-08 22:00:00,11698.0 -2013-11-08 23:00:00,11238.0 -2013-11-09 00:00:00,10538.0 -2013-11-07 01:00:00,9889.0 -2013-11-07 02:00:00,9456.0 -2013-11-07 03:00:00,9211.0 -2013-11-07 04:00:00,9085.0 -2013-11-07 05:00:00,9095.0 -2013-11-07 06:00:00,9366.0 -2013-11-07 07:00:00,10141.0 -2013-11-07 08:00:00,11212.0 -2013-11-07 09:00:00,11624.0 -2013-11-07 10:00:00,11781.0 -2013-11-07 11:00:00,11789.0 -2013-11-07 12:00:00,11794.0 -2013-11-07 13:00:00,11781.0 -2013-11-07 14:00:00,11844.0 -2013-11-07 15:00:00,12005.0 -2013-11-07 16:00:00,11961.0 -2013-11-07 17:00:00,11908.0 -2013-11-07 18:00:00,12268.0 -2013-11-07 19:00:00,12917.0 -2013-11-07 20:00:00,12719.0 -2013-11-07 21:00:00,12512.0 -2013-11-07 22:00:00,12156.0 -2013-11-07 23:00:00,11617.0 -2013-11-08 00:00:00,10860.0 -2013-11-06 01:00:00,9526.0 -2013-11-06 02:00:00,9093.0 -2013-11-06 03:00:00,8775.0 -2013-11-06 04:00:00,8616.0 -2013-11-06 05:00:00,8550.0 -2013-11-06 06:00:00,8833.0 -2013-11-06 07:00:00,9533.0 -2013-11-06 08:00:00,10666.0 -2013-11-06 09:00:00,11392.0 -2013-11-06 10:00:00,11617.0 -2013-11-06 11:00:00,11712.0 -2013-11-06 12:00:00,11842.0 -2013-11-06 13:00:00,11871.0 -2013-11-06 14:00:00,11782.0 -2013-11-06 15:00:00,11815.0 -2013-11-06 16:00:00,11776.0 -2013-11-06 17:00:00,11791.0 -2013-11-06 18:00:00,12157.0 -2013-11-06 19:00:00,12607.0 -2013-11-06 20:00:00,12414.0 -2013-11-06 21:00:00,12158.0 -2013-11-06 22:00:00,11849.0 -2013-11-06 23:00:00,11342.0 -2013-11-07 00:00:00,10578.0 -2013-11-05 01:00:00,9494.0 -2013-11-05 02:00:00,8998.0 -2013-11-05 03:00:00,8728.0 -2013-11-05 04:00:00,8520.0 -2013-11-05 05:00:00,8500.0 -2013-11-05 06:00:00,8718.0 -2013-11-05 07:00:00,9435.0 -2013-11-05 08:00:00,10512.0 -2013-11-05 09:00:00,11055.0 -2013-11-05 10:00:00,11229.0 -2013-11-05 11:00:00,11360.0 -2013-11-05 12:00:00,11516.0 -2013-11-05 13:00:00,11585.0 -2013-11-05 14:00:00,11615.0 -2013-11-05 15:00:00,11650.0 -2013-11-05 16:00:00,11606.0 -2013-11-05 17:00:00,11622.0 -2013-11-05 18:00:00,12018.0 -2013-11-05 19:00:00,12444.0 -2013-11-05 20:00:00,12245.0 -2013-11-05 21:00:00,12003.0 -2013-11-05 22:00:00,11665.0 -2013-11-05 23:00:00,11049.0 -2013-11-06 00:00:00,10289.0 -2013-11-04 01:00:00,9005.0 -2013-11-04 02:00:00,8692.0 -2013-11-04 03:00:00,8546.0 -2013-11-04 04:00:00,8492.0 -2013-11-04 05:00:00,8536.0 -2013-11-04 06:00:00,8837.0 -2013-11-04 07:00:00,9681.0 -2013-11-04 08:00:00,10753.0 -2013-11-04 09:00:00,11343.0 -2013-11-04 10:00:00,11596.0 -2013-11-04 11:00:00,11612.0 -2013-11-04 12:00:00,11612.0 -2013-11-04 13:00:00,11581.0 -2013-11-04 14:00:00,11546.0 -2013-11-04 15:00:00,11475.0 -2013-11-04 16:00:00,11368.0 -2013-11-04 17:00:00,11295.0 -2013-11-04 18:00:00,11539.0 -2013-11-04 19:00:00,12224.0 -2013-11-04 20:00:00,12061.0 -2013-11-04 21:00:00,12011.0 -2013-11-04 22:00:00,11607.0 -2013-11-04 23:00:00,11047.0 -2013-11-05 00:00:00,10252.0 -2013-11-03 01:00:00,9230.0 -2013-11-03 03:00:00,8459.0 -2013-11-03 04:00:00,8336.0 -2013-11-03 05:00:00,8372.0 -2013-11-03 06:00:00,8386.0 -2013-11-03 07:00:00,8659.0 -2013-11-03 08:00:00,8813.0 -2013-11-03 09:00:00,8945.0 -2013-11-03 10:00:00,9137.0 -2013-11-03 11:00:00,9247.0 -2013-11-03 12:00:00,9287.0 -2013-11-03 13:00:00,9315.0 -2013-11-03 14:00:00,9300.0 -2013-11-03 15:00:00,9252.0 -2013-11-03 16:00:00,9227.0 -2013-11-03 17:00:00,9332.0 -2013-11-03 18:00:00,9861.0 -2013-11-03 19:00:00,10722.0 -2013-11-03 20:00:00,10819.0 -2013-11-03 21:00:00,10759.0 -2013-11-03 22:00:00,10525.0 -2013-11-03 23:00:00,10110.0 -2013-11-04 00:00:00,9555.0 -2013-11-02 01:00:00,9622.0 -2013-11-02 02:00:00,9090.0 -2013-11-02 03:00:00,8797.0 -2013-11-02 04:00:00,8577.0 -2013-11-02 05:00:00,8526.0 -2013-11-02 06:00:00,8559.0 -2013-11-02 07:00:00,8834.0 -2013-11-02 08:00:00,9358.0 -2013-11-02 09:00:00,9777.0 -2013-11-02 10:00:00,10019.0 -2013-11-02 11:00:00,10242.0 -2013-11-02 12:00:00,10343.0 -2013-11-02 13:00:00,10313.0 -2013-11-02 14:00:00,10153.0 -2013-11-02 15:00:00,10020.0 -2013-11-02 16:00:00,9888.0 -2013-11-02 17:00:00,9829.0 -2013-11-02 18:00:00,9936.0 -2013-11-02 19:00:00,10307.0 -2013-11-02 20:00:00,10768.0 -2013-11-02 21:00:00,10644.0 -2013-11-02 22:00:00,10495.0 -2013-11-02 23:00:00,10174.0 -2013-11-03 00:00:00,9726.0 -2013-11-01 01:00:00,9408.0 -2013-11-01 02:00:00,8925.0 -2013-11-01 03:00:00,8665.0 -2013-11-01 04:00:00,8475.0 -2013-11-01 05:00:00,8458.0 -2013-11-01 06:00:00,8680.0 -2013-11-01 07:00:00,9359.0 -2013-11-01 08:00:00,10526.0 -2013-11-01 09:00:00,11355.0 -2013-11-01 10:00:00,11523.0 -2013-11-01 11:00:00,11601.0 -2013-11-01 12:00:00,11703.0 -2013-11-01 13:00:00,11679.0 -2013-11-01 14:00:00,11590.0 -2013-11-01 15:00:00,11581.0 -2013-11-01 16:00:00,11508.0 -2013-11-01 17:00:00,11433.0 -2013-11-01 18:00:00,11541.0 -2013-11-01 19:00:00,11757.0 -2013-11-01 20:00:00,11920.0 -2013-11-01 21:00:00,11714.0 -2013-11-01 22:00:00,11390.0 -2013-11-01 23:00:00,10995.0 -2013-11-02 00:00:00,10317.0 -2013-10-31 01:00:00,9601.0 -2013-10-31 02:00:00,9058.0 -2013-10-31 03:00:00,8733.0 -2013-10-31 04:00:00,8580.0 -2013-10-31 05:00:00,8509.0 -2013-10-31 06:00:00,8715.0 -2013-10-31 07:00:00,9321.0 -2013-10-31 08:00:00,10494.0 -2013-10-31 09:00:00,11442.0 -2013-10-31 10:00:00,11710.0 -2013-10-31 11:00:00,11791.0 -2013-10-31 12:00:00,11870.0 -2013-10-31 13:00:00,11878.0 -2013-10-31 14:00:00,11879.0 -2013-10-31 15:00:00,11907.0 -2013-10-31 16:00:00,11840.0 -2013-10-31 17:00:00,11740.0 -2013-10-31 18:00:00,11715.0 -2013-10-31 19:00:00,11718.0 -2013-10-31 20:00:00,11879.0 -2013-10-31 21:00:00,11603.0 -2013-10-31 22:00:00,11275.0 -2013-10-31 23:00:00,10822.0 -2013-11-01 00:00:00,10119.0 -2013-10-30 01:00:00,9497.0 -2013-10-30 02:00:00,8988.0 -2013-10-30 03:00:00,8635.0 -2013-10-30 04:00:00,8457.0 -2013-10-30 05:00:00,8432.0 -2013-10-30 06:00:00,8637.0 -2013-10-30 07:00:00,9336.0 -2013-10-30 08:00:00,10556.0 -2013-10-30 09:00:00,11310.0 -2013-10-30 10:00:00,11358.0 -2013-10-30 11:00:00,11452.0 -2013-10-30 12:00:00,11550.0 -2013-10-30 13:00:00,11542.0 -2013-10-30 14:00:00,11326.0 -2013-10-30 15:00:00,11545.0 -2013-10-30 16:00:00,11495.0 -2013-10-30 17:00:00,11522.0 -2013-10-30 18:00:00,11636.0 -2013-10-30 19:00:00,12053.0 -2013-10-30 20:00:00,12189.0 -2013-10-30 21:00:00,11984.0 -2013-10-30 22:00:00,11667.0 -2013-10-30 23:00:00,11069.0 -2013-10-31 00:00:00,10300.0 -2013-10-29 01:00:00,9727.0 -2013-10-29 02:00:00,9226.0 -2013-10-29 03:00:00,8950.0 -2013-10-29 04:00:00,8779.0 -2013-10-29 05:00:00,8736.0 -2013-10-29 06:00:00,8973.0 -2013-10-29 07:00:00,9686.0 -2013-10-29 08:00:00,10846.0 -2013-10-29 09:00:00,11556.0 -2013-10-29 10:00:00,11590.0 -2013-10-29 11:00:00,11649.0 -2013-10-29 12:00:00,11701.0 -2013-10-29 13:00:00,11619.0 -2013-10-29 14:00:00,11505.0 -2013-10-29 15:00:00,11417.0 -2013-10-29 16:00:00,11275.0 -2013-10-29 17:00:00,11148.0 -2013-10-29 18:00:00,11129.0 -2013-10-29 19:00:00,11455.0 -2013-10-29 20:00:00,11981.0 -2013-10-29 21:00:00,11870.0 -2013-10-29 22:00:00,11568.0 -2013-10-29 23:00:00,11034.0 -2013-10-30 00:00:00,10263.0 -2013-10-28 01:00:00,9016.0 -2013-10-28 02:00:00,8655.0 -2013-10-28 03:00:00,8463.0 -2013-10-28 04:00:00,8369.0 -2013-10-28 05:00:00,8438.0 -2013-10-28 06:00:00,8764.0 -2013-10-28 07:00:00,9612.0 -2013-10-28 08:00:00,10893.0 -2013-10-28 09:00:00,11600.0 -2013-10-28 10:00:00,11574.0 -2013-10-28 11:00:00,11560.0 -2013-10-28 12:00:00,11625.0 -2013-10-28 13:00:00,11551.0 -2013-10-28 14:00:00,11494.0 -2013-10-28 15:00:00,11445.0 -2013-10-28 16:00:00,11314.0 -2013-10-28 17:00:00,11188.0 -2013-10-28 18:00:00,11239.0 -2013-10-28 19:00:00,11567.0 -2013-10-28 20:00:00,12153.0 -2013-10-28 21:00:00,12053.0 -2013-10-28 22:00:00,11783.0 -2013-10-28 23:00:00,11259.0 -2013-10-29 00:00:00,10491.0 -2013-10-27 01:00:00,9213.0 -2013-10-27 02:00:00,8802.0 -2013-10-27 03:00:00,8527.0 -2013-10-27 04:00:00,8425.0 -2013-10-27 05:00:00,8337.0 -2013-10-27 06:00:00,8406.0 -2013-10-27 07:00:00,8529.0 -2013-10-27 08:00:00,8883.0 -2013-10-27 09:00:00,8954.0 -2013-10-27 10:00:00,9154.0 -2013-10-27 11:00:00,9227.0 -2013-10-27 12:00:00,9297.0 -2013-10-27 13:00:00,9288.0 -2013-10-27 14:00:00,9292.0 -2013-10-27 15:00:00,9186.0 -2013-10-27 16:00:00,9131.0 -2013-10-27 17:00:00,9069.0 -2013-10-27 18:00:00,9201.0 -2013-10-27 19:00:00,9534.0 -2013-10-27 20:00:00,10409.0 -2013-10-27 21:00:00,10547.0 -2013-10-27 22:00:00,10438.0 -2013-10-27 23:00:00,10111.0 -2013-10-28 00:00:00,9607.0 -2013-10-26 01:00:00,9829.0 -2013-10-26 02:00:00,9340.0 -2013-10-26 03:00:00,8986.0 -2013-10-26 04:00:00,8823.0 -2013-10-26 05:00:00,8654.0 -2013-10-26 06:00:00,8753.0 -2013-10-26 07:00:00,8959.0 -2013-10-26 08:00:00,9471.0 -2013-10-26 09:00:00,9861.0 -2013-10-26 10:00:00,10013.0 -2013-10-26 11:00:00,10191.0 -2013-10-26 12:00:00,10206.0 -2013-10-26 13:00:00,10099.0 -2013-10-26 14:00:00,9969.0 -2013-10-26 15:00:00,9855.0 -2013-10-26 16:00:00,9731.0 -2013-10-26 17:00:00,9679.0 -2013-10-26 18:00:00,9761.0 -2013-10-26 19:00:00,9910.0 -2013-10-26 20:00:00,10571.0 -2013-10-26 21:00:00,10566.0 -2013-10-26 22:00:00,10470.0 -2013-10-26 23:00:00,10168.0 -2013-10-27 00:00:00,9711.0 -2013-10-25 01:00:00,10059.0 -2013-10-25 02:00:00,9579.0 -2013-10-25 03:00:00,9293.0 -2013-10-25 04:00:00,9204.0 -2013-10-25 05:00:00,9193.0 -2013-10-25 06:00:00,9395.0 -2013-10-25 07:00:00,10169.0 -2013-10-25 08:00:00,11397.0 -2013-10-25 09:00:00,11951.0 -2013-10-25 10:00:00,11954.0 -2013-10-25 11:00:00,11910.0 -2013-10-25 12:00:00,11841.0 -2013-10-25 13:00:00,11708.0 -2013-10-25 14:00:00,11585.0 -2013-10-25 15:00:00,11532.0 -2013-10-25 16:00:00,11354.0 -2013-10-25 17:00:00,11133.0 -2013-10-25 18:00:00,11039.0 -2013-10-25 19:00:00,11166.0 -2013-10-25 20:00:00,11781.0 -2013-10-25 21:00:00,11788.0 -2013-10-25 22:00:00,11544.0 -2013-10-25 23:00:00,11221.0 -2013-10-26 00:00:00,10559.0 -2013-10-24 01:00:00,9888.0 -2013-10-24 02:00:00,9376.0 -2013-10-24 03:00:00,9077.0 -2013-10-24 04:00:00,8916.0 -2013-10-24 05:00:00,8912.0 -2013-10-24 06:00:00,9164.0 -2013-10-24 07:00:00,9915.0 -2013-10-24 08:00:00,11183.0 -2013-10-24 09:00:00,11841.0 -2013-10-24 10:00:00,11916.0 -2013-10-24 11:00:00,11865.0 -2013-10-24 12:00:00,11849.0 -2013-10-24 13:00:00,11792.0 -2013-10-24 14:00:00,11759.0 -2013-10-24 15:00:00,11744.0 -2013-10-24 16:00:00,11613.0 -2013-10-24 17:00:00,11590.0 -2013-10-24 18:00:00,11693.0 -2013-10-24 19:00:00,11952.0 -2013-10-24 20:00:00,12355.0 -2013-10-24 21:00:00,12264.0 -2013-10-24 22:00:00,12027.0 -2013-10-24 23:00:00,11534.0 -2013-10-25 00:00:00,10789.0 -2013-10-23 01:00:00,9902.0 -2013-10-23 02:00:00,9409.0 -2013-10-23 03:00:00,9156.0 -2013-10-23 04:00:00,9010.0 -2013-10-23 05:00:00,9019.0 -2013-10-23 06:00:00,9267.0 -2013-10-23 07:00:00,10031.0 -2013-10-23 08:00:00,11247.0 -2013-10-23 09:00:00,11876.0 -2013-10-23 10:00:00,11848.0 -2013-10-23 11:00:00,11806.0 -2013-10-23 12:00:00,11791.0 -2013-10-23 13:00:00,11712.0 -2013-10-23 14:00:00,11616.0 -2013-10-23 15:00:00,11600.0 -2013-10-23 16:00:00,11609.0 -2013-10-23 17:00:00,11604.0 -2013-10-23 18:00:00,11651.0 -2013-10-23 19:00:00,11891.0 -2013-10-23 20:00:00,12345.0 -2013-10-23 21:00:00,12225.0 -2013-10-23 22:00:00,11961.0 -2013-10-23 23:00:00,11453.0 -2013-10-24 00:00:00,10634.0 -2013-10-22 01:00:00,9702.0 -2013-10-22 02:00:00,9252.0 -2013-10-22 03:00:00,9003.0 -2013-10-22 04:00:00,8831.0 -2013-10-22 05:00:00,8857.0 -2013-10-22 06:00:00,9126.0 -2013-10-22 07:00:00,9894.0 -2013-10-22 08:00:00,11176.0 -2013-10-22 09:00:00,11751.0 -2013-10-22 10:00:00,11785.0 -2013-10-22 11:00:00,11830.0 -2013-10-22 12:00:00,11821.0 -2013-10-22 13:00:00,11820.0 -2013-10-22 14:00:00,11830.0 -2013-10-22 15:00:00,11903.0 -2013-10-22 16:00:00,11863.0 -2013-10-22 17:00:00,11746.0 -2013-10-22 18:00:00,11744.0 -2013-10-22 19:00:00,11940.0 -2013-10-22 20:00:00,12374.0 -2013-10-22 21:00:00,12255.0 -2013-10-22 22:00:00,11970.0 -2013-10-22 23:00:00,11413.0 -2013-10-23 00:00:00,10639.0 -2013-10-21 01:00:00,8691.0 -2013-10-21 02:00:00,8327.0 -2013-10-21 03:00:00,8058.0 -2013-10-21 04:00:00,7974.0 -2013-10-21 05:00:00,7984.0 -2013-10-21 06:00:00,8255.0 -2013-10-21 07:00:00,9012.0 -2013-10-21 08:00:00,10315.0 -2013-10-21 09:00:00,11072.0 -2013-10-21 10:00:00,11244.0 -2013-10-21 11:00:00,11331.0 -2013-10-21 12:00:00,11434.0 -2013-10-21 13:00:00,11408.0 -2013-10-21 14:00:00,11388.0 -2013-10-21 15:00:00,11377.0 -2013-10-21 16:00:00,11268.0 -2013-10-21 17:00:00,11130.0 -2013-10-21 18:00:00,11086.0 -2013-10-21 19:00:00,11257.0 -2013-10-21 20:00:00,11929.0 -2013-10-21 21:00:00,12028.0 -2013-10-21 22:00:00,11795.0 -2013-10-21 23:00:00,11264.0 -2013-10-22 00:00:00,10479.0 -2013-10-20 01:00:00,9014.0 -2013-10-20 02:00:00,8542.0 -2013-10-20 03:00:00,8275.0 -2013-10-20 04:00:00,8078.0 -2013-10-20 05:00:00,8000.0 -2013-10-20 06:00:00,7991.0 -2013-10-20 07:00:00,8151.0 -2013-10-20 08:00:00,8435.0 -2013-10-20 09:00:00,8512.0 -2013-10-20 10:00:00,8783.0 -2013-10-20 11:00:00,8952.0 -2013-10-20 12:00:00,9090.0 -2013-10-20 13:00:00,9169.0 -2013-10-20 14:00:00,9199.0 -2013-10-20 15:00:00,9160.0 -2013-10-20 16:00:00,9146.0 -2013-10-20 17:00:00,9125.0 -2013-10-20 18:00:00,9233.0 -2013-10-20 19:00:00,9641.0 -2013-10-20 20:00:00,10336.0 -2013-10-20 21:00:00,10387.0 -2013-10-20 22:00:00,10198.0 -2013-10-20 23:00:00,9816.0 -2013-10-21 00:00:00,9250.0 -2013-10-19 01:00:00,9552.0 -2013-10-19 02:00:00,9023.0 -2013-10-19 03:00:00,8679.0 -2013-10-19 04:00:00,8468.0 -2013-10-19 05:00:00,8368.0 -2013-10-19 06:00:00,8367.0 -2013-10-19 07:00:00,8640.0 -2013-10-19 08:00:00,9163.0 -2013-10-19 09:00:00,9440.0 -2013-10-19 10:00:00,9649.0 -2013-10-19 11:00:00,9872.0 -2013-10-19 12:00:00,9881.0 -2013-10-19 13:00:00,9819.0 -2013-10-19 14:00:00,9725.0 -2013-10-19 15:00:00,9610.0 -2013-10-19 16:00:00,9546.0 -2013-10-19 17:00:00,9467.0 -2013-10-19 18:00:00,9603.0 -2013-10-19 19:00:00,9872.0 -2013-10-19 20:00:00,10457.0 -2013-10-19 21:00:00,10461.0 -2013-10-19 22:00:00,10282.0 -2013-10-19 23:00:00,10038.0 -2013-10-20 00:00:00,9513.0 -2013-10-18 01:00:00,9500.0 -2013-10-18 02:00:00,8995.0 -2013-10-18 03:00:00,8676.0 -2013-10-18 04:00:00,8468.0 -2013-10-18 05:00:00,8429.0 -2013-10-18 06:00:00,8669.0 -2013-10-18 07:00:00,9335.0 -2013-10-18 08:00:00,10513.0 -2013-10-18 09:00:00,11078.0 -2013-10-18 10:00:00,11241.0 -2013-10-18 11:00:00,11426.0 -2013-10-18 12:00:00,11573.0 -2013-10-18 13:00:00,11602.0 -2013-10-18 14:00:00,11524.0 -2013-10-18 15:00:00,11497.0 -2013-10-18 16:00:00,11353.0 -2013-10-18 17:00:00,11257.0 -2013-10-18 18:00:00,11248.0 -2013-10-18 19:00:00,11394.0 -2013-10-18 20:00:00,11764.0 -2013-10-18 21:00:00,11603.0 -2013-10-18 22:00:00,11337.0 -2013-10-18 23:00:00,10928.0 -2013-10-19 00:00:00,10273.0 -2013-10-17 01:00:00,9329.0 -2013-10-17 02:00:00,8811.0 -2013-10-17 03:00:00,8515.0 -2013-10-17 04:00:00,8311.0 -2013-10-17 05:00:00,8257.0 -2013-10-17 06:00:00,8468.0 -2013-10-17 07:00:00,9131.0 -2013-10-17 08:00:00,10306.0 -2013-10-17 09:00:00,11093.0 -2013-10-17 10:00:00,11287.0 -2013-10-17 11:00:00,11403.0 -2013-10-17 12:00:00,11519.0 -2013-10-17 13:00:00,11500.0 -2013-10-17 14:00:00,11386.0 -2013-10-17 15:00:00,11350.0 -2013-10-17 16:00:00,11283.0 -2013-10-17 17:00:00,11177.0 -2013-10-17 18:00:00,11115.0 -2013-10-17 19:00:00,11069.0 -2013-10-17 20:00:00,11620.0 -2013-10-17 21:00:00,11743.0 -2013-10-17 22:00:00,11497.0 -2013-10-17 23:00:00,10977.0 -2013-10-18 00:00:00,10268.0 -2013-10-16 01:00:00,9286.0 -2013-10-16 02:00:00,8747.0 -2013-10-16 03:00:00,8421.0 -2013-10-16 04:00:00,8238.0 -2013-10-16 05:00:00,8200.0 -2013-10-16 06:00:00,8414.0 -2013-10-16 07:00:00,9056.0 -2013-10-16 08:00:00,10223.0 -2013-10-16 09:00:00,10935.0 -2013-10-16 10:00:00,11120.0 -2013-10-16 11:00:00,11264.0 -2013-10-16 12:00:00,11386.0 -2013-10-16 13:00:00,11360.0 -2013-10-16 14:00:00,11277.0 -2013-10-16 15:00:00,11331.0 -2013-10-16 16:00:00,11281.0 -2013-10-16 17:00:00,11210.0 -2013-10-16 18:00:00,11198.0 -2013-10-16 19:00:00,11394.0 -2013-10-16 20:00:00,11846.0 -2013-10-16 21:00:00,11777.0 -2013-10-16 22:00:00,11467.0 -2013-10-16 23:00:00,10898.0 -2013-10-17 00:00:00,10086.0 -2013-10-15 01:00:00,9308.0 -2013-10-15 02:00:00,8785.0 -2013-10-15 03:00:00,8461.0 -2013-10-15 04:00:00,8242.0 -2013-10-15 05:00:00,8195.0 -2013-10-15 06:00:00,8372.0 -2013-10-15 07:00:00,9024.0 -2013-10-15 08:00:00,10175.0 -2013-10-15 09:00:00,10905.0 -2013-10-15 10:00:00,11131.0 -2013-10-15 11:00:00,11282.0 -2013-10-15 12:00:00,11529.0 -2013-10-15 13:00:00,11627.0 -2013-10-15 14:00:00,11715.0 -2013-10-15 15:00:00,11848.0 -2013-10-15 16:00:00,11794.0 -2013-10-15 17:00:00,11723.0 -2013-10-15 18:00:00,11673.0 -2013-10-15 19:00:00,11627.0 -2013-10-15 20:00:00,11998.0 -2013-10-15 21:00:00,12055.0 -2013-10-15 22:00:00,11667.0 -2013-10-15 23:00:00,10940.0 -2013-10-16 00:00:00,10066.0 -2013-10-14 01:00:00,8467.0 -2013-10-14 02:00:00,8100.0 -2013-10-14 03:00:00,7931.0 -2013-10-14 04:00:00,7806.0 -2013-10-14 05:00:00,7809.0 -2013-10-14 06:00:00,8016.0 -2013-10-14 07:00:00,8681.0 -2013-10-14 08:00:00,9650.0 -2013-10-14 09:00:00,10225.0 -2013-10-14 10:00:00,10569.0 -2013-10-14 11:00:00,10811.0 -2013-10-14 12:00:00,11108.0 -2013-10-14 13:00:00,11188.0 -2013-10-14 14:00:00,11189.0 -2013-10-14 15:00:00,11273.0 -2013-10-14 16:00:00,11215.0 -2013-10-14 17:00:00,11139.0 -2013-10-14 18:00:00,11063.0 -2013-10-14 19:00:00,11009.0 -2013-10-14 20:00:00,11487.0 -2013-10-14 21:00:00,11648.0 -2013-10-14 22:00:00,11345.0 -2013-10-14 23:00:00,10861.0 -2013-10-15 00:00:00,10081.0 -2013-10-13 01:00:00,8721.0 -2013-10-13 02:00:00,8300.0 -2013-10-13 03:00:00,7916.0 -2013-10-13 04:00:00,7742.0 -2013-10-13 05:00:00,7604.0 -2013-10-13 06:00:00,7618.0 -2013-10-13 07:00:00,7676.0 -2013-10-13 08:00:00,7910.0 -2013-10-13 09:00:00,7874.0 -2013-10-13 10:00:00,8207.0 -2013-10-13 11:00:00,8519.0 -2013-10-13 12:00:00,8779.0 -2013-10-13 13:00:00,8874.0 -2013-10-13 14:00:00,8911.0 -2013-10-13 15:00:00,8919.0 -2013-10-13 16:00:00,8984.0 -2013-10-13 17:00:00,8951.0 -2013-10-13 18:00:00,9002.0 -2013-10-13 19:00:00,9165.0 -2013-10-13 20:00:00,9705.0 -2013-10-13 21:00:00,9998.0 -2013-10-13 22:00:00,9804.0 -2013-10-13 23:00:00,9492.0 -2013-10-14 00:00:00,9013.0 -2013-10-12 01:00:00,9558.0 -2013-10-12 02:00:00,8901.0 -2013-10-12 03:00:00,8558.0 -2013-10-12 04:00:00,8281.0 -2013-10-12 05:00:00,8216.0 -2013-10-12 06:00:00,8180.0 -2013-10-12 07:00:00,8426.0 -2013-10-12 08:00:00,8816.0 -2013-10-12 09:00:00,9046.0 -2013-10-12 10:00:00,9529.0 -2013-10-12 11:00:00,9991.0 -2013-10-12 12:00:00,10378.0 -2013-10-12 13:00:00,10516.0 -2013-10-12 14:00:00,10465.0 -2013-10-12 15:00:00,10406.0 -2013-10-12 16:00:00,10275.0 -2013-10-12 17:00:00,10184.0 -2013-10-12 18:00:00,10202.0 -2013-10-12 19:00:00,10144.0 -2013-10-12 20:00:00,10521.0 -2013-10-12 21:00:00,10641.0 -2013-10-12 22:00:00,10358.0 -2013-10-12 23:00:00,9931.0 -2013-10-13 00:00:00,9366.0 -2013-10-11 01:00:00,9537.0 -2013-10-11 02:00:00,8944.0 -2013-10-11 03:00:00,8575.0 -2013-10-11 04:00:00,8323.0 -2013-10-11 05:00:00,8260.0 -2013-10-11 06:00:00,8398.0 -2013-10-11 07:00:00,8989.0 -2013-10-11 08:00:00,10052.0 -2013-10-11 09:00:00,10619.0 -2013-10-11 10:00:00,11022.0 -2013-10-11 11:00:00,11398.0 -2013-10-11 12:00:00,11771.0 -2013-10-11 13:00:00,11995.0 -2013-10-11 14:00:00,12179.0 -2013-10-11 15:00:00,12348.0 -2013-10-11 16:00:00,12323.0 -2013-10-11 17:00:00,12192.0 -2013-10-11 18:00:00,12016.0 -2013-10-11 19:00:00,11716.0 -2013-10-11 20:00:00,11833.0 -2013-10-11 21:00:00,11900.0 -2013-10-11 22:00:00,11549.0 -2013-10-11 23:00:00,11079.0 -2013-10-12 00:00:00,10288.0 -2013-10-10 01:00:00,9374.0 -2013-10-10 02:00:00,8822.0 -2013-10-10 03:00:00,8477.0 -2013-10-10 04:00:00,8271.0 -2013-10-10 05:00:00,8184.0 -2013-10-10 06:00:00,8349.0 -2013-10-10 07:00:00,9005.0 -2013-10-10 08:00:00,10099.0 -2013-10-10 09:00:00,10621.0 -2013-10-10 10:00:00,10939.0 -2013-10-10 11:00:00,11294.0 -2013-10-10 12:00:00,11693.0 -2013-10-10 13:00:00,11921.0 -2013-10-10 14:00:00,12054.0 -2013-10-10 15:00:00,12211.0 -2013-10-10 16:00:00,12252.0 -2013-10-10 17:00:00,12187.0 -2013-10-10 18:00:00,12061.0 -2013-10-10 19:00:00,11860.0 -2013-10-10 20:00:00,11992.0 -2013-10-10 21:00:00,12173.0 -2013-10-10 22:00:00,11812.0 -2013-10-10 23:00:00,11208.0 -2013-10-11 00:00:00,10354.0 -2013-10-09 01:00:00,9527.0 -2013-10-09 02:00:00,8928.0 -2013-10-09 03:00:00,8552.0 -2013-10-09 04:00:00,8340.0 -2013-10-09 05:00:00,8285.0 -2013-10-09 06:00:00,8430.0 -2013-10-09 07:00:00,9057.0 -2013-10-09 08:00:00,10125.0 -2013-10-09 09:00:00,10634.0 -2013-10-09 10:00:00,10987.0 -2013-10-09 11:00:00,11294.0 -2013-10-09 12:00:00,11620.0 -2013-10-09 13:00:00,11781.0 -2013-10-09 14:00:00,11887.0 -2013-10-09 15:00:00,12058.0 -2013-10-09 16:00:00,12059.0 -2013-10-09 17:00:00,11992.0 -2013-10-09 18:00:00,11897.0 -2013-10-09 19:00:00,11696.0 -2013-10-09 20:00:00,11816.0 -2013-10-09 21:00:00,11999.0 -2013-10-09 22:00:00,11645.0 -2013-10-09 23:00:00,11061.0 -2013-10-10 00:00:00,10217.0 -2013-10-08 01:00:00,9259.0 -2013-10-08 02:00:00,8724.0 -2013-10-08 03:00:00,8429.0 -2013-10-08 04:00:00,8218.0 -2013-10-08 05:00:00,8170.0 -2013-10-08 06:00:00,8367.0 -2013-10-08 07:00:00,9048.0 -2013-10-08 08:00:00,10167.0 -2013-10-08 09:00:00,10693.0 -2013-10-08 10:00:00,10952.0 -2013-10-08 11:00:00,11240.0 -2013-10-08 12:00:00,11572.0 -2013-10-08 13:00:00,11787.0 -2013-10-08 14:00:00,11945.0 -2013-10-08 15:00:00,12149.0 -2013-10-08 16:00:00,12207.0 -2013-10-08 17:00:00,12182.0 -2013-10-08 18:00:00,12112.0 -2013-10-08 19:00:00,11919.0 -2013-10-08 20:00:00,12038.0 -2013-10-08 21:00:00,12213.0 -2013-10-08 22:00:00,11850.0 -2013-10-08 23:00:00,11218.0 -2013-10-09 00:00:00,10339.0 -2013-10-07 01:00:00,8547.0 -2013-10-07 02:00:00,8169.0 -2013-10-07 03:00:00,7957.0 -2013-10-07 04:00:00,7901.0 -2013-10-07 05:00:00,7883.0 -2013-10-07 06:00:00,8135.0 -2013-10-07 07:00:00,8840.0 -2013-10-07 08:00:00,10020.0 -2013-10-07 09:00:00,10698.0 -2013-10-07 10:00:00,10978.0 -2013-10-07 11:00:00,11057.0 -2013-10-07 12:00:00,11143.0 -2013-10-07 13:00:00,11252.0 -2013-10-07 14:00:00,11337.0 -2013-10-07 15:00:00,11451.0 -2013-10-07 16:00:00,11489.0 -2013-10-07 17:00:00,11428.0 -2013-10-07 18:00:00,11372.0 -2013-10-07 19:00:00,11285.0 -2013-10-07 20:00:00,11506.0 -2013-10-07 21:00:00,11774.0 -2013-10-07 22:00:00,11468.0 -2013-10-07 23:00:00,10888.0 -2013-10-08 00:00:00,10074.0 -2013-10-06 01:00:00,10248.0 -2013-10-06 02:00:00,9543.0 -2013-10-06 03:00:00,8908.0 -2013-10-06 04:00:00,8463.0 -2013-10-06 05:00:00,8201.0 -2013-10-06 06:00:00,8046.0 -2013-10-06 07:00:00,8073.0 -2013-10-06 08:00:00,8241.0 -2013-10-06 09:00:00,8245.0 -2013-10-06 10:00:00,8579.0 -2013-10-06 11:00:00,8890.0 -2013-10-06 12:00:00,9148.0 -2013-10-06 13:00:00,9354.0 -2013-10-06 14:00:00,9499.0 -2013-10-06 15:00:00,9492.0 -2013-10-06 16:00:00,9421.0 -2013-10-06 17:00:00,9369.0 -2013-10-06 18:00:00,9483.0 -2013-10-06 19:00:00,9708.0 -2013-10-06 20:00:00,10082.0 -2013-10-06 21:00:00,10209.0 -2013-10-06 22:00:00,10015.0 -2013-10-06 23:00:00,9623.0 -2013-10-07 00:00:00,9118.0 -2013-10-05 01:00:00,10570.0 -2013-10-05 02:00:00,9832.0 -2013-10-05 03:00:00,9392.0 -2013-10-05 04:00:00,9098.0 -2013-10-05 05:00:00,8973.0 -2013-10-05 06:00:00,8978.0 -2013-10-05 07:00:00,9206.0 -2013-10-05 08:00:00,9692.0 -2013-10-05 09:00:00,9989.0 -2013-10-05 10:00:00,10718.0 -2013-10-05 11:00:00,11288.0 -2013-10-05 12:00:00,11799.0 -2013-10-05 13:00:00,12175.0 -2013-10-05 14:00:00,12474.0 -2013-10-05 15:00:00,12662.0 -2013-10-05 16:00:00,12851.0 -2013-10-05 17:00:00,12796.0 -2013-10-05 18:00:00,12621.0 -2013-10-05 19:00:00,12316.0 -2013-10-05 20:00:00,12389.0 -2013-10-05 21:00:00,12385.0 -2013-10-05 22:00:00,12102.0 -2013-10-05 23:00:00,11665.0 -2013-10-06 00:00:00,11037.0 -2013-10-04 01:00:00,10702.0 -2013-10-04 02:00:00,10067.0 -2013-10-04 03:00:00,9486.0 -2013-10-04 04:00:00,9179.0 -2013-10-04 05:00:00,9030.0 -2013-10-04 06:00:00,9211.0 -2013-10-04 07:00:00,9900.0 -2013-10-04 08:00:00,11114.0 -2013-10-04 09:00:00,11797.0 -2013-10-04 10:00:00,12364.0 -2013-10-04 11:00:00,12838.0 -2013-10-04 12:00:00,13181.0 -2013-10-04 13:00:00,13272.0 -2013-10-04 14:00:00,13381.0 -2013-10-04 15:00:00,13563.0 -2013-10-04 16:00:00,13494.0 -2013-10-04 17:00:00,13387.0 -2013-10-04 18:00:00,13254.0 -2013-10-04 19:00:00,13104.0 -2013-10-04 20:00:00,13186.0 -2013-10-04 21:00:00,13275.0 -2013-10-04 22:00:00,12836.0 -2013-10-04 23:00:00,12255.0 -2013-10-05 00:00:00,11422.0 -2013-10-03 01:00:00,10141.0 -2013-10-03 02:00:00,9463.0 -2013-10-03 03:00:00,9083.0 -2013-10-03 04:00:00,8874.0 -2013-10-03 05:00:00,8814.0 -2013-10-03 06:00:00,9025.0 -2013-10-03 07:00:00,9775.0 -2013-10-03 08:00:00,11126.0 -2013-10-03 09:00:00,11912.0 -2013-10-03 10:00:00,12412.0 -2013-10-03 11:00:00,12755.0 -2013-10-03 12:00:00,13019.0 -2013-10-03 13:00:00,12985.0 -2013-10-03 14:00:00,13065.0 -2013-10-03 15:00:00,13293.0 -2013-10-03 16:00:00,13539.0 -2013-10-03 17:00:00,13689.0 -2013-10-03 18:00:00,13595.0 -2013-10-03 19:00:00,13451.0 -2013-10-03 20:00:00,13457.0 -2013-10-03 21:00:00,13650.0 -2013-10-03 22:00:00,13321.0 -2013-10-03 23:00:00,12582.0 -2013-10-04 00:00:00,11610.0 -2013-10-02 01:00:00,10453.0 -2013-10-02 02:00:00,9818.0 -2013-10-02 03:00:00,9349.0 -2013-10-02 04:00:00,9055.0 -2013-10-02 05:00:00,8943.0 -2013-10-02 06:00:00,9128.0 -2013-10-02 07:00:00,9758.0 -2013-10-02 08:00:00,10864.0 -2013-10-02 09:00:00,11438.0 -2013-10-02 10:00:00,11870.0 -2013-10-02 11:00:00,12318.0 -2013-10-02 12:00:00,12668.0 -2013-10-02 13:00:00,12893.0 -2013-10-02 14:00:00,13080.0 -2013-10-02 15:00:00,13347.0 -2013-10-02 16:00:00,13483.0 -2013-10-02 17:00:00,13521.0 -2013-10-02 18:00:00,13357.0 -2013-10-02 19:00:00,12987.0 -2013-10-02 20:00:00,12748.0 -2013-10-02 21:00:00,12956.0 -2013-10-02 22:00:00,12606.0 -2013-10-02 23:00:00,11937.0 -2013-10-03 00:00:00,11001.0 -2013-10-01 01:00:00,9678.0 -2013-10-01 02:00:00,9126.0 -2013-10-01 03:00:00,8750.0 -2013-10-01 04:00:00,8577.0 -2013-10-01 05:00:00,8499.0 -2013-10-01 06:00:00,8701.0 -2013-10-01 07:00:00,9387.0 -2013-10-01 08:00:00,10613.0 -2013-10-01 09:00:00,11353.0 -2013-10-01 10:00:00,11779.0 -2013-10-01 11:00:00,12050.0 -2013-10-01 12:00:00,12296.0 -2013-10-01 13:00:00,12374.0 -2013-10-01 14:00:00,12483.0 -2013-10-01 15:00:00,12800.0 -2013-10-01 16:00:00,13034.0 -2013-10-01 17:00:00,13189.0 -2013-10-01 18:00:00,13243.0 -2013-10-01 19:00:00,13104.0 -2013-10-01 20:00:00,12974.0 -2013-10-01 21:00:00,13295.0 -2013-10-01 22:00:00,12986.0 -2013-10-01 23:00:00,12290.0 -2013-10-02 00:00:00,11387.0 -2013-09-30 01:00:00,8653.0 -2013-09-30 02:00:00,8268.0 -2013-09-30 03:00:00,8039.0 -2013-09-30 04:00:00,7947.0 -2013-09-30 05:00:00,7891.0 -2013-09-30 06:00:00,8137.0 -2013-09-30 07:00:00,8815.0 -2013-09-30 08:00:00,9966.0 -2013-09-30 09:00:00,10560.0 -2013-09-30 10:00:00,10977.0 -2013-09-30 11:00:00,11356.0 -2013-09-30 12:00:00,11750.0 -2013-09-30 13:00:00,11894.0 -2013-09-30 14:00:00,11997.0 -2013-09-30 15:00:00,12201.0 -2013-09-30 16:00:00,12262.0 -2013-09-30 17:00:00,12272.0 -2013-09-30 18:00:00,12205.0 -2013-09-30 19:00:00,12040.0 -2013-09-30 20:00:00,11968.0 -2013-09-30 21:00:00,12275.0 -2013-09-30 22:00:00,11930.0 -2013-09-30 23:00:00,11368.0 -2013-10-01 00:00:00,10513.0 -2013-09-29 01:00:00,10212.0 -2013-09-29 02:00:00,9600.0 -2013-09-29 03:00:00,9045.0 -2013-09-29 04:00:00,8533.0 -2013-09-29 05:00:00,8158.0 -2013-09-29 06:00:00,8041.0 -2013-09-29 07:00:00,8029.0 -2013-09-29 08:00:00,8112.0 -2013-09-29 09:00:00,8095.0 -2013-09-29 10:00:00,8567.0 -2013-09-29 11:00:00,8960.0 -2013-09-29 12:00:00,9360.0 -2013-09-29 13:00:00,9545.0 -2013-09-29 14:00:00,9787.0 -2013-09-29 15:00:00,9802.0 -2013-09-29 16:00:00,9915.0 -2013-09-29 17:00:00,9897.0 -2013-09-29 18:00:00,9948.0 -2013-09-29 19:00:00,9887.0 -2013-09-29 20:00:00,10061.0 -2013-09-29 21:00:00,10481.0 -2013-09-29 22:00:00,10277.0 -2013-09-29 23:00:00,9845.0 -2013-09-30 00:00:00,9298.0 -2013-09-28 01:00:00,9803.0 -2013-09-28 02:00:00,9175.0 -2013-09-28 03:00:00,8708.0 -2013-09-28 04:00:00,8455.0 -2013-09-28 05:00:00,8261.0 -2013-09-28 06:00:00,8283.0 -2013-09-28 07:00:00,8454.0 -2013-09-28 08:00:00,8806.0 -2013-09-28 09:00:00,9053.0 -2013-09-28 10:00:00,9628.0 -2013-09-28 11:00:00,10219.0 -2013-09-28 12:00:00,10671.0 -2013-09-28 13:00:00,11046.0 -2013-09-28 14:00:00,11366.0 -2013-09-28 15:00:00,11628.0 -2013-09-28 16:00:00,11809.0 -2013-09-28 17:00:00,11801.0 -2013-09-28 18:00:00,11743.0 -2013-09-28 19:00:00,11503.0 -2013-09-28 20:00:00,11724.0 -2013-09-28 21:00:00,11995.0 -2013-09-28 22:00:00,11895.0 -2013-09-28 23:00:00,11537.0 -2013-09-29 00:00:00,10948.0 -2013-09-27 01:00:00,9646.0 -2013-09-27 02:00:00,9007.0 -2013-09-27 03:00:00,8630.0 -2013-09-27 04:00:00,8377.0 -2013-09-27 05:00:00,8275.0 -2013-09-27 06:00:00,8415.0 -2013-09-27 07:00:00,9036.0 -2013-09-27 08:00:00,10032.0 -2013-09-27 09:00:00,10580.0 -2013-09-27 10:00:00,11098.0 -2013-09-27 11:00:00,11582.0 -2013-09-27 12:00:00,12008.0 -2013-09-27 13:00:00,12342.0 -2013-09-27 14:00:00,12610.0 -2013-09-27 15:00:00,12908.0 -2013-09-27 16:00:00,13035.0 -2013-09-27 17:00:00,13100.0 -2013-09-27 18:00:00,13021.0 -2013-09-27 19:00:00,12667.0 -2013-09-27 20:00:00,12308.0 -2013-09-27 21:00:00,12475.0 -2013-09-27 22:00:00,12055.0 -2013-09-27 23:00:00,11471.0 -2013-09-28 00:00:00,10689.0 -2013-09-26 01:00:00,9471.0 -2013-09-26 02:00:00,8916.0 -2013-09-26 03:00:00,8588.0 -2013-09-26 04:00:00,8354.0 -2013-09-26 05:00:00,8274.0 -2013-09-26 06:00:00,8430.0 -2013-09-26 07:00:00,9080.0 -2013-09-26 08:00:00,10108.0 -2013-09-26 09:00:00,10632.0 -2013-09-26 10:00:00,11132.0 -2013-09-26 11:00:00,11522.0 -2013-09-26 12:00:00,11882.0 -2013-09-26 13:00:00,12077.0 -2013-09-26 14:00:00,12223.0 -2013-09-26 15:00:00,12411.0 -2013-09-26 16:00:00,12521.0 -2013-09-26 17:00:00,12577.0 -2013-09-26 18:00:00,12509.0 -2013-09-26 19:00:00,12241.0 -2013-09-26 20:00:00,12013.0 -2013-09-26 21:00:00,12330.0 -2013-09-26 22:00:00,11989.0 -2013-09-26 23:00:00,11384.0 -2013-09-27 00:00:00,10459.0 -2013-09-25 01:00:00,9379.0 -2013-09-25 02:00:00,8810.0 -2013-09-25 03:00:00,8456.0 -2013-09-25 04:00:00,8266.0 -2013-09-25 05:00:00,8181.0 -2013-09-25 06:00:00,8385.0 -2013-09-25 07:00:00,9012.0 -2013-09-25 08:00:00,10038.0 -2013-09-25 09:00:00,10574.0 -2013-09-25 10:00:00,11011.0 -2013-09-25 11:00:00,11416.0 -2013-09-25 12:00:00,11730.0 -2013-09-25 13:00:00,11862.0 -2013-09-25 14:00:00,11954.0 -2013-09-25 15:00:00,12098.0 -2013-09-25 16:00:00,12145.0 -2013-09-25 17:00:00,12122.0 -2013-09-25 18:00:00,12027.0 -2013-09-25 19:00:00,11791.0 -2013-09-25 20:00:00,11576.0 -2013-09-25 21:00:00,11992.0 -2013-09-25 22:00:00,11699.0 -2013-09-25 23:00:00,11091.0 -2013-09-26 00:00:00,10260.0 -2013-09-24 01:00:00,9297.0 -2013-09-24 02:00:00,8749.0 -2013-09-24 03:00:00,8399.0 -2013-09-24 04:00:00,8203.0 -2013-09-24 05:00:00,8137.0 -2013-09-24 06:00:00,8281.0 -2013-09-24 07:00:00,8903.0 -2013-09-24 08:00:00,9938.0 -2013-09-24 09:00:00,10454.0 -2013-09-24 10:00:00,10829.0 -2013-09-24 11:00:00,11137.0 -2013-09-24 12:00:00,11489.0 -2013-09-24 13:00:00,11702.0 -2013-09-24 14:00:00,11781.0 -2013-09-24 15:00:00,11949.0 -2013-09-24 16:00:00,12002.0 -2013-09-24 17:00:00,11936.0 -2013-09-24 18:00:00,11851.0 -2013-09-24 19:00:00,11598.0 -2013-09-24 20:00:00,11422.0 -2013-09-24 21:00:00,11844.0 -2013-09-24 22:00:00,11600.0 -2013-09-24 23:00:00,10997.0 -2013-09-25 00:00:00,10156.0 -2013-09-23 01:00:00,8578.0 -2013-09-23 02:00:00,8152.0 -2013-09-23 03:00:00,7940.0 -2013-09-23 04:00:00,7830.0 -2013-09-23 05:00:00,7862.0 -2013-09-23 06:00:00,8062.0 -2013-09-23 07:00:00,8777.0 -2013-09-23 08:00:00,9842.0 -2013-09-23 09:00:00,10385.0 -2013-09-23 10:00:00,10706.0 -2013-09-23 11:00:00,11008.0 -2013-09-23 12:00:00,11273.0 -2013-09-23 13:00:00,11437.0 -2013-09-23 14:00:00,11519.0 -2013-09-23 15:00:00,11657.0 -2013-09-23 16:00:00,11698.0 -2013-09-23 17:00:00,11664.0 -2013-09-23 18:00:00,11589.0 -2013-09-23 19:00:00,11422.0 -2013-09-23 20:00:00,11314.0 -2013-09-23 21:00:00,11757.0 -2013-09-23 22:00:00,11502.0 -2013-09-23 23:00:00,10923.0 -2013-09-24 00:00:00,10113.0 -2013-09-22 01:00:00,8727.0 -2013-09-22 02:00:00,8215.0 -2013-09-22 03:00:00,7925.0 -2013-09-22 04:00:00,7686.0 -2013-09-22 05:00:00,7580.0 -2013-09-22 06:00:00,7549.0 -2013-09-22 07:00:00,7650.0 -2013-09-22 08:00:00,7722.0 -2013-09-22 09:00:00,7837.0 -2013-09-22 10:00:00,8270.0 -2013-09-22 11:00:00,8704.0 -2013-09-22 12:00:00,8998.0 -2013-09-22 13:00:00,9205.0 -2013-09-22 14:00:00,9291.0 -2013-09-22 15:00:00,9378.0 -2013-09-22 16:00:00,9387.0 -2013-09-22 17:00:00,9481.0 -2013-09-22 18:00:00,9490.0 -2013-09-22 19:00:00,9555.0 -2013-09-22 20:00:00,9660.0 -2013-09-22 21:00:00,10262.0 -2013-09-22 22:00:00,10137.0 -2013-09-22 23:00:00,9741.0 -2013-09-23 00:00:00,9186.0 -2013-09-21 01:00:00,9809.0 -2013-09-21 02:00:00,9237.0 -2013-09-21 03:00:00,8773.0 -2013-09-21 04:00:00,8512.0 -2013-09-21 05:00:00,8346.0 -2013-09-21 06:00:00,8336.0 -2013-09-21 07:00:00,8545.0 -2013-09-21 08:00:00,8799.0 -2013-09-21 09:00:00,8976.0 -2013-09-21 10:00:00,9484.0 -2013-09-21 11:00:00,9899.0 -2013-09-21 12:00:00,10151.0 -2013-09-21 13:00:00,10245.0 -2013-09-21 14:00:00,10258.0 -2013-09-21 15:00:00,10140.0 -2013-09-21 16:00:00,10073.0 -2013-09-21 17:00:00,9979.0 -2013-09-21 18:00:00,9947.0 -2013-09-21 19:00:00,9826.0 -2013-09-21 20:00:00,9808.0 -2013-09-21 21:00:00,10269.0 -2013-09-21 22:00:00,10152.0 -2013-09-21 23:00:00,9827.0 -2013-09-22 00:00:00,9287.0 -2013-09-20 01:00:00,12274.0 -2013-09-20 02:00:00,11342.0 -2013-09-20 03:00:00,10579.0 -2013-09-20 04:00:00,10017.0 -2013-09-20 05:00:00,9801.0 -2013-09-20 06:00:00,9883.0 -2013-09-20 07:00:00,10564.0 -2013-09-20 08:00:00,11762.0 -2013-09-20 09:00:00,12435.0 -2013-09-20 10:00:00,12855.0 -2013-09-20 11:00:00,13151.0 -2013-09-20 12:00:00,13455.0 -2013-09-20 13:00:00,13727.0 -2013-09-20 14:00:00,13929.0 -2013-09-20 15:00:00,14082.0 -2013-09-20 16:00:00,13866.0 -2013-09-20 17:00:00,13646.0 -2013-09-20 18:00:00,13223.0 -2013-09-20 19:00:00,12648.0 -2013-09-20 20:00:00,12229.0 -2013-09-20 21:00:00,12370.0 -2013-09-20 22:00:00,11953.0 -2013-09-20 23:00:00,11447.0 -2013-09-21 00:00:00,10624.0 -2013-09-19 01:00:00,10498.0 -2013-09-19 02:00:00,9864.0 -2013-09-19 03:00:00,9518.0 -2013-09-19 04:00:00,9270.0 -2013-09-19 05:00:00,9211.0 -2013-09-19 06:00:00,9350.0 -2013-09-19 07:00:00,10148.0 -2013-09-19 08:00:00,11439.0 -2013-09-19 09:00:00,12376.0 -2013-09-19 10:00:00,12843.0 -2013-09-19 11:00:00,13223.0 -2013-09-19 12:00:00,13596.0 -2013-09-19 13:00:00,13863.0 -2013-09-19 14:00:00,14264.0 -2013-09-19 15:00:00,14976.0 -2013-09-19 16:00:00,15499.0 -2013-09-19 17:00:00,15943.0 -2013-09-19 18:00:00,16253.0 -2013-09-19 19:00:00,16112.0 -2013-09-19 20:00:00,15648.0 -2013-09-19 21:00:00,15824.0 -2013-09-19 22:00:00,15466.0 -2013-09-19 23:00:00,14574.0 -2013-09-20 00:00:00,13429.0 -2013-09-18 01:00:00,9483.0 -2013-09-18 02:00:00,8920.0 -2013-09-18 03:00:00,8561.0 -2013-09-18 04:00:00,8371.0 -2013-09-18 05:00:00,8283.0 -2013-09-18 06:00:00,8465.0 -2013-09-18 07:00:00,9107.0 -2013-09-18 08:00:00,10252.0 -2013-09-18 09:00:00,10975.0 -2013-09-18 10:00:00,11406.0 -2013-09-18 11:00:00,11710.0 -2013-09-18 12:00:00,12216.0 -2013-09-18 13:00:00,12629.0 -2013-09-18 14:00:00,12985.0 -2013-09-18 15:00:00,13367.0 -2013-09-18 16:00:00,13439.0 -2013-09-18 17:00:00,13436.0 -2013-09-18 18:00:00,13380.0 -2013-09-18 19:00:00,13206.0 -2013-09-18 20:00:00,12938.0 -2013-09-18 21:00:00,13180.0 -2013-09-18 22:00:00,12975.0 -2013-09-18 23:00:00,12359.0 -2013-09-19 00:00:00,11469.0 -2013-09-17 01:00:00,9159.0 -2013-09-17 02:00:00,8622.0 -2013-09-17 03:00:00,8338.0 -2013-09-17 04:00:00,8154.0 -2013-09-17 05:00:00,8099.0 -2013-09-17 06:00:00,8290.0 -2013-09-17 07:00:00,8928.0 -2013-09-17 08:00:00,9865.0 -2013-09-17 09:00:00,10444.0 -2013-09-17 10:00:00,10829.0 -2013-09-17 11:00:00,11120.0 -2013-09-17 12:00:00,11440.0 -2013-09-17 13:00:00,11581.0 -2013-09-17 14:00:00,11688.0 -2013-09-17 15:00:00,11795.0 -2013-09-17 16:00:00,11856.0 -2013-09-17 17:00:00,11746.0 -2013-09-17 18:00:00,11593.0 -2013-09-17 19:00:00,11448.0 -2013-09-17 20:00:00,11436.0 -2013-09-17 21:00:00,11930.0 -2013-09-17 22:00:00,11747.0 -2013-09-17 23:00:00,11153.0 -2013-09-18 00:00:00,10334.0 -2013-09-16 01:00:00,8718.0 -2013-09-16 02:00:00,8353.0 -2013-09-16 03:00:00,8139.0 -2013-09-16 04:00:00,8003.0 -2013-09-16 05:00:00,7999.0 -2013-09-16 06:00:00,8250.0 -2013-09-16 07:00:00,8896.0 -2013-09-16 08:00:00,9970.0 -2013-09-16 09:00:00,10548.0 -2013-09-16 10:00:00,10843.0 -2013-09-16 11:00:00,11028.0 -2013-09-16 12:00:00,11275.0 -2013-09-16 13:00:00,11409.0 -2013-09-16 14:00:00,11480.0 -2013-09-16 15:00:00,11577.0 -2013-09-16 16:00:00,11536.0 -2013-09-16 17:00:00,11455.0 -2013-09-16 18:00:00,11344.0 -2013-09-16 19:00:00,11140.0 -2013-09-16 20:00:00,10973.0 -2013-09-16 21:00:00,11466.0 -2013-09-16 22:00:00,11346.0 -2013-09-16 23:00:00,10796.0 -2013-09-17 00:00:00,9966.0 -2013-09-15 01:00:00,8916.0 -2013-09-15 02:00:00,8468.0 -2013-09-15 03:00:00,8105.0 -2013-09-15 04:00:00,7868.0 -2013-09-15 05:00:00,7802.0 -2013-09-15 06:00:00,7734.0 -2013-09-15 07:00:00,7816.0 -2013-09-15 08:00:00,7939.0 -2013-09-15 09:00:00,8131.0 -2013-09-15 10:00:00,8505.0 -2013-09-15 11:00:00,8957.0 -2013-09-15 12:00:00,9215.0 -2013-09-15 13:00:00,9444.0 -2013-09-15 14:00:00,9475.0 -2013-09-15 15:00:00,9497.0 -2013-09-15 16:00:00,9543.0 -2013-09-15 17:00:00,9573.0 -2013-09-15 18:00:00,9683.0 -2013-09-15 19:00:00,9771.0 -2013-09-15 20:00:00,9943.0 -2013-09-15 21:00:00,10297.0 -2013-09-15 22:00:00,10196.0 -2013-09-15 23:00:00,9796.0 -2013-09-16 00:00:00,9290.0 -2013-09-14 01:00:00,9375.0 -2013-09-14 02:00:00,8764.0 -2013-09-14 03:00:00,8398.0 -2013-09-14 04:00:00,8136.0 -2013-09-14 05:00:00,8025.0 -2013-09-14 06:00:00,8020.0 -2013-09-14 07:00:00,8230.0 -2013-09-14 08:00:00,8488.0 -2013-09-14 09:00:00,8738.0 -2013-09-14 10:00:00,9270.0 -2013-09-14 11:00:00,9628.0 -2013-09-14 12:00:00,10007.0 -2013-09-14 13:00:00,10133.0 -2013-09-14 14:00:00,10192.0 -2013-09-14 15:00:00,10187.0 -2013-09-14 16:00:00,10215.0 -2013-09-14 17:00:00,10232.0 -2013-09-14 18:00:00,10216.0 -2013-09-14 19:00:00,10123.0 -2013-09-14 20:00:00,10053.0 -2013-09-14 21:00:00,10452.0 -2013-09-14 22:00:00,10388.0 -2013-09-14 23:00:00,10038.0 -2013-09-15 00:00:00,9477.0 -2013-09-13 01:00:00,10599.0 -2013-09-13 02:00:00,9892.0 -2013-09-13 03:00:00,9404.0 -2013-09-13 04:00:00,9064.0 -2013-09-13 05:00:00,8877.0 -2013-09-13 06:00:00,8910.0 -2013-09-13 07:00:00,9376.0 -2013-09-13 08:00:00,10149.0 -2013-09-13 09:00:00,10636.0 -2013-09-13 10:00:00,11013.0 -2013-09-13 11:00:00,11308.0 -2013-09-13 12:00:00,11644.0 -2013-09-13 13:00:00,11801.0 -2013-09-13 14:00:00,11881.0 -2013-09-13 15:00:00,11941.0 -2013-09-13 16:00:00,11921.0 -2013-09-13 17:00:00,11842.0 -2013-09-13 18:00:00,11687.0 -2013-09-13 19:00:00,11431.0 -2013-09-13 20:00:00,11022.0 -2013-09-13 21:00:00,11353.0 -2013-09-13 22:00:00,11236.0 -2013-09-13 23:00:00,10804.0 -2013-09-14 00:00:00,10103.0 -2013-09-12 01:00:00,13327.0 -2013-09-12 02:00:00,12402.0 -2013-09-12 03:00:00,11653.0 -2013-09-12 04:00:00,11146.0 -2013-09-12 05:00:00,10876.0 -2013-09-12 06:00:00,10887.0 -2013-09-12 07:00:00,11473.0 -2013-09-12 08:00:00,12439.0 -2013-09-12 09:00:00,13086.0 -2013-09-12 10:00:00,13786.0 -2013-09-12 11:00:00,14566.0 -2013-09-12 12:00:00,15326.0 -2013-09-12 13:00:00,15834.0 -2013-09-12 14:00:00,16183.0 -2013-09-12 15:00:00,16376.0 -2013-09-12 16:00:00,16407.0 -2013-09-12 17:00:00,16162.0 -2013-09-12 18:00:00,15621.0 -2013-09-12 19:00:00,14818.0 -2013-09-12 20:00:00,13902.0 -2013-09-12 21:00:00,13835.0 -2013-09-12 22:00:00,13512.0 -2013-09-12 23:00:00,12719.0 -2013-09-13 00:00:00,11663.0 -2013-09-11 01:00:00,15302.0 -2013-09-11 02:00:00,14159.0 -2013-09-11 03:00:00,13380.0 -2013-09-11 04:00:00,12808.0 -2013-09-11 05:00:00,12403.0 -2013-09-11 06:00:00,12347.0 -2013-09-11 07:00:00,12926.0 -2013-09-11 08:00:00,13897.0 -2013-09-11 09:00:00,14750.0 -2013-09-11 10:00:00,15625.0 -2013-09-11 11:00:00,16506.0 -2013-09-11 12:00:00,17509.0 -2013-09-11 13:00:00,18412.0 -2013-09-11 14:00:00,19157.0 -2013-09-11 15:00:00,19649.0 -2013-09-11 16:00:00,19353.0 -2013-09-11 17:00:00,18743.0 -2013-09-11 18:00:00,18227.0 -2013-09-11 19:00:00,17860.0 -2013-09-11 20:00:00,17315.0 -2013-09-11 21:00:00,17363.0 -2013-09-11 22:00:00,16984.0 -2013-09-11 23:00:00,16015.0 -2013-09-12 00:00:00,14666.0 -2013-09-10 01:00:00,15941.0 -2013-09-10 02:00:00,14771.0 -2013-09-10 03:00:00,13945.0 -2013-09-10 04:00:00,13288.0 -2013-09-10 05:00:00,12824.0 -2013-09-10 06:00:00,12736.0 -2013-09-10 07:00:00,13244.0 -2013-09-10 08:00:00,14154.0 -2013-09-10 09:00:00,14941.0 -2013-09-10 10:00:00,15985.0 -2013-09-10 11:00:00,17169.0 -2013-09-10 12:00:00,18369.0 -2013-09-10 13:00:00,19398.0 -2013-09-10 14:00:00,20215.0 -2013-09-10 15:00:00,20888.0 -2013-09-10 16:00:00,21315.0 -2013-09-10 17:00:00,21572.0 -2013-09-10 18:00:00,21550.0 -2013-09-10 19:00:00,21132.0 -2013-09-10 20:00:00,20319.0 -2013-09-10 21:00:00,20046.0 -2013-09-10 22:00:00,19489.0 -2013-09-10 23:00:00,18321.0 -2013-09-11 00:00:00,16762.0 -2013-09-09 01:00:00,11147.0 -2013-09-09 02:00:00,10479.0 -2013-09-09 03:00:00,10090.0 -2013-09-09 04:00:00,9848.0 -2013-09-09 05:00:00,9693.0 -2013-09-09 06:00:00,9984.0 -2013-09-09 07:00:00,10829.0 -2013-09-09 08:00:00,12035.0 -2013-09-09 09:00:00,12975.0 -2013-09-09 10:00:00,13908.0 -2013-09-09 11:00:00,14980.0 -2013-09-09 12:00:00,16187.0 -2013-09-09 13:00:00,17377.0 -2013-09-09 14:00:00,18455.0 -2013-09-09 15:00:00,19483.0 -2013-09-09 16:00:00,20246.0 -2013-09-09 17:00:00,20883.0 -2013-09-09 18:00:00,21256.0 -2013-09-09 19:00:00,21081.0 -2013-09-09 20:00:00,20478.0 -2013-09-09 21:00:00,20381.0 -2013-09-09 22:00:00,19974.0 -2013-09-09 23:00:00,18939.0 -2013-09-10 00:00:00,17422.0 -2013-09-08 01:00:00,12014.0 -2013-09-08 02:00:00,11330.0 -2013-09-08 03:00:00,10759.0 -2013-09-08 04:00:00,10294.0 -2013-09-08 05:00:00,10001.0 -2013-09-08 06:00:00,9808.0 -2013-09-08 07:00:00,9836.0 -2013-09-08 08:00:00,10020.0 -2013-09-08 09:00:00,10173.0 -2013-09-08 10:00:00,10709.0 -2013-09-08 11:00:00,11295.0 -2013-09-08 12:00:00,11870.0 -2013-09-08 13:00:00,12629.0 -2013-09-08 14:00:00,13072.0 -2013-09-08 15:00:00,13235.0 -2013-09-08 16:00:00,13278.0 -2013-09-08 17:00:00,13288.0 -2013-09-08 18:00:00,13360.0 -2013-09-08 19:00:00,13252.0 -2013-09-08 20:00:00,12968.0 -2013-09-08 21:00:00,13243.0 -2013-09-08 22:00:00,13299.0 -2013-09-08 23:00:00,12844.0 -2013-09-09 00:00:00,11988.0 -2013-09-07 01:00:00,12200.0 -2013-09-07 02:00:00,11266.0 -2013-09-07 03:00:00,10587.0 -2013-09-07 04:00:00,10101.0 -2013-09-07 05:00:00,9785.0 -2013-09-07 06:00:00,9688.0 -2013-09-07 07:00:00,9867.0 -2013-09-07 08:00:00,10094.0 -2013-09-07 09:00:00,10518.0 -2013-09-07 10:00:00,11408.0 -2013-09-07 11:00:00,12215.0 -2013-09-07 12:00:00,13189.0 -2013-09-07 13:00:00,14161.0 -2013-09-07 14:00:00,14849.0 -2013-09-07 15:00:00,15116.0 -2013-09-07 16:00:00,15133.0 -2013-09-07 17:00:00,15279.0 -2013-09-07 18:00:00,15206.0 -2013-09-07 19:00:00,15088.0 -2013-09-07 20:00:00,14633.0 -2013-09-07 21:00:00,14592.0 -2013-09-07 22:00:00,14446.0 -2013-09-07 23:00:00,13829.0 -2013-09-08 00:00:00,12960.0 -2013-09-06 01:00:00,10292.0 -2013-09-06 02:00:00,9634.0 -2013-09-06 03:00:00,9222.0 -2013-09-06 04:00:00,8955.0 -2013-09-06 05:00:00,8844.0 -2013-09-06 06:00:00,8997.0 -2013-09-06 07:00:00,9615.0 -2013-09-06 08:00:00,10516.0 -2013-09-06 09:00:00,11230.0 -2013-09-06 10:00:00,11776.0 -2013-09-06 11:00:00,12266.0 -2013-09-06 12:00:00,12862.0 -2013-09-06 13:00:00,13376.0 -2013-09-06 14:00:00,13933.0 -2013-09-06 15:00:00,14651.0 -2013-09-06 16:00:00,15187.0 -2013-09-06 17:00:00,15681.0 -2013-09-06 18:00:00,15973.0 -2013-09-06 19:00:00,15878.0 -2013-09-06 20:00:00,15410.0 -2013-09-06 21:00:00,15263.0 -2013-09-06 22:00:00,14993.0 -2013-09-06 23:00:00,14366.0 -2013-09-07 00:00:00,13311.0 -2013-09-05 01:00:00,11224.0 -2013-09-05 02:00:00,10450.0 -2013-09-05 03:00:00,9976.0 -2013-09-05 04:00:00,9722.0 -2013-09-05 05:00:00,9587.0 -2013-09-05 06:00:00,9796.0 -2013-09-05 07:00:00,10459.0 -2013-09-05 08:00:00,11551.0 -2013-09-05 09:00:00,12256.0 -2013-09-05 10:00:00,12639.0 -2013-09-05 11:00:00,12900.0 -2013-09-05 12:00:00,13208.0 -2013-09-05 13:00:00,13430.0 -2013-09-05 14:00:00,13642.0 -2013-09-05 15:00:00,13943.0 -2013-09-05 16:00:00,14079.0 -2013-09-05 17:00:00,14138.0 -2013-09-05 18:00:00,14046.0 -2013-09-05 19:00:00,13686.0 -2013-09-05 20:00:00,13049.0 -2013-09-05 21:00:00,12927.0 -2013-09-05 22:00:00,12831.0 -2013-09-05 23:00:00,12099.0 -2013-09-06 00:00:00,11184.0 -2013-09-04 01:00:00,10231.0 -2013-09-04 02:00:00,9584.0 -2013-09-04 03:00:00,9119.0 -2013-09-04 04:00:00,8856.0 -2013-09-04 05:00:00,8740.0 -2013-09-04 06:00:00,8914.0 -2013-09-04 07:00:00,9538.0 -2013-09-04 08:00:00,10419.0 -2013-09-04 09:00:00,11198.0 -2013-09-04 10:00:00,11874.0 -2013-09-04 11:00:00,12478.0 -2013-09-04 12:00:00,13020.0 -2013-09-04 13:00:00,13447.0 -2013-09-04 14:00:00,13785.0 -2013-09-04 15:00:00,14313.0 -2013-09-04 16:00:00,14705.0 -2013-09-04 17:00:00,15032.0 -2013-09-04 18:00:00,15259.0 -2013-09-04 19:00:00,15135.0 -2013-09-04 20:00:00,14563.0 -2013-09-04 21:00:00,14487.0 -2013-09-04 22:00:00,14348.0 -2013-09-04 23:00:00,13516.0 -2013-09-05 00:00:00,12373.0 -2013-09-03 01:00:00,9599.0 -2013-09-03 02:00:00,8996.0 -2013-09-03 03:00:00,8637.0 -2013-09-03 04:00:00,8435.0 -2013-09-03 05:00:00,8370.0 -2013-09-03 06:00:00,8565.0 -2013-09-03 07:00:00,9215.0 -2013-09-03 08:00:00,10172.0 -2013-09-03 09:00:00,10965.0 -2013-09-03 10:00:00,11689.0 -2013-09-03 11:00:00,12259.0 -2013-09-03 12:00:00,12653.0 -2013-09-03 13:00:00,12965.0 -2013-09-03 14:00:00,13186.0 -2013-09-03 15:00:00,13527.0 -2013-09-03 16:00:00,13771.0 -2013-09-03 17:00:00,13974.0 -2013-09-03 18:00:00,13843.0 -2013-09-03 19:00:00,13535.0 -2013-09-03 20:00:00,13028.0 -2013-09-03 21:00:00,13063.0 -2013-09-03 22:00:00,13045.0 -2013-09-03 23:00:00,12310.0 -2013-09-04 00:00:00,11242.0 -2013-09-02 01:00:00,11495.0 -2013-09-02 02:00:00,10670.0 -2013-09-02 03:00:00,10011.0 -2013-09-02 04:00:00,9607.0 -2013-09-02 05:00:00,9301.0 -2013-09-02 06:00:00,9192.0 -2013-09-02 07:00:00,9282.0 -2013-09-02 08:00:00,9243.0 -2013-09-02 09:00:00,9504.0 -2013-09-02 10:00:00,10216.0 -2013-09-02 11:00:00,10945.0 -2013-09-02 12:00:00,11604.0 -2013-09-02 13:00:00,12007.0 -2013-09-02 14:00:00,12378.0 -2013-09-02 15:00:00,12620.0 -2013-09-02 16:00:00,12747.0 -2013-09-02 17:00:00,12734.0 -2013-09-02 18:00:00,12620.0 -2013-09-02 19:00:00,12323.0 -2013-09-02 20:00:00,11835.0 -2013-09-02 21:00:00,11783.0 -2013-09-02 22:00:00,11849.0 -2013-09-02 23:00:00,11312.0 -2013-09-03 00:00:00,10439.0 -2013-09-01 01:00:00,11161.0 -2013-09-01 02:00:00,10435.0 -2013-09-01 03:00:00,9946.0 -2013-09-01 04:00:00,9542.0 -2013-09-01 05:00:00,9248.0 -2013-09-01 06:00:00,9181.0 -2013-09-01 07:00:00,9149.0 -2013-09-01 08:00:00,9148.0 -2013-09-01 09:00:00,9433.0 -2013-09-01 10:00:00,10348.0 -2013-09-01 11:00:00,11279.0 -2013-09-01 12:00:00,12227.0 -2013-09-01 13:00:00,12862.0 -2013-09-01 14:00:00,13526.0 -2013-09-01 15:00:00,14169.0 -2013-09-01 16:00:00,14690.0 -2013-09-01 17:00:00,15099.0 -2013-09-01 18:00:00,15172.0 -2013-09-01 19:00:00,15055.0 -2013-09-01 20:00:00,14590.0 -2013-09-01 21:00:00,14444.0 -2013-09-01 22:00:00,14446.0 -2013-09-01 23:00:00,13687.0 -2013-09-02 00:00:00,12527.0 -2013-08-31 01:00:00,12789.0 -2013-08-31 02:00:00,11898.0 -2013-08-31 03:00:00,11190.0 -2013-08-31 04:00:00,10634.0 -2013-08-31 05:00:00,10395.0 -2013-08-31 06:00:00,10251.0 -2013-08-31 07:00:00,10440.0 -2013-08-31 08:00:00,10578.0 -2013-08-31 09:00:00,10974.0 -2013-08-31 10:00:00,11712.0 -2013-08-31 11:00:00,12326.0 -2013-08-31 12:00:00,12665.0 -2013-08-31 13:00:00,12834.0 -2013-08-31 14:00:00,12895.0 -2013-08-31 15:00:00,13057.0 -2013-08-31 16:00:00,13276.0 -2013-08-31 17:00:00,13409.0 -2013-08-31 18:00:00,13262.0 -2013-08-31 19:00:00,13034.0 -2013-08-31 20:00:00,12685.0 -2013-08-31 21:00:00,12801.0 -2013-08-31 22:00:00,12895.0 -2013-08-31 23:00:00,12565.0 -2013-09-01 00:00:00,11921.0 -2013-08-30 01:00:00,13268.0 -2013-08-30 02:00:00,12223.0 -2013-08-30 03:00:00,11518.0 -2013-08-30 04:00:00,10991.0 -2013-08-30 05:00:00,10729.0 -2013-08-30 06:00:00,10823.0 -2013-08-30 07:00:00,11509.0 -2013-08-30 08:00:00,12460.0 -2013-08-30 09:00:00,13579.0 -2013-08-30 10:00:00,14740.0 -2013-08-30 11:00:00,16010.0 -2013-08-30 12:00:00,17298.0 -2013-08-30 13:00:00,18561.0 -2013-08-30 14:00:00,19688.0 -2013-08-30 15:00:00,20637.0 -2013-08-30 16:00:00,21204.0 -2013-08-30 17:00:00,21488.0 -2013-08-30 18:00:00,21519.0 -2013-08-30 19:00:00,20372.0 -2013-08-30 20:00:00,17922.0 -2013-08-30 21:00:00,16861.0 -2013-08-30 22:00:00,16081.0 -2013-08-30 23:00:00,15281.0 -2013-08-31 00:00:00,14093.0 -2013-08-29 01:00:00,12887.0 -2013-08-29 02:00:00,11977.0 -2013-08-29 03:00:00,11350.0 -2013-08-29 04:00:00,10933.0 -2013-08-29 05:00:00,10703.0 -2013-08-29 06:00:00,10797.0 -2013-08-29 07:00:00,11492.0 -2013-08-29 08:00:00,12542.0 -2013-08-29 09:00:00,13425.0 -2013-08-29 10:00:00,14197.0 -2013-08-29 11:00:00,15287.0 -2013-08-29 12:00:00,16446.0 -2013-08-29 13:00:00,17320.0 -2013-08-29 14:00:00,18108.0 -2013-08-29 15:00:00,18813.0 -2013-08-29 16:00:00,19274.0 -2013-08-29 17:00:00,19592.0 -2013-08-29 18:00:00,19702.0 -2013-08-29 19:00:00,19322.0 -2013-08-29 20:00:00,18404.0 -2013-08-29 21:00:00,17694.0 -2013-08-29 22:00:00,17357.0 -2013-08-29 23:00:00,16203.0 -2013-08-30 00:00:00,14684.0 -2013-08-28 01:00:00,15993.0 -2013-08-28 02:00:00,14782.0 -2013-08-28 03:00:00,13974.0 -2013-08-28 04:00:00,13326.0 -2013-08-28 05:00:00,12961.0 -2013-08-28 06:00:00,12883.0 -2013-08-28 07:00:00,13387.0 -2013-08-28 08:00:00,14317.0 -2013-08-28 09:00:00,15183.0 -2013-08-28 10:00:00,15948.0 -2013-08-28 11:00:00,16408.0 -2013-08-28 12:00:00,16820.0 -2013-08-28 13:00:00,17183.0 -2013-08-28 14:00:00,17505.0 -2013-08-28 15:00:00,18019.0 -2013-08-28 16:00:00,18323.0 -2013-08-28 17:00:00,18507.0 -2013-08-28 18:00:00,18388.0 -2013-08-28 19:00:00,17998.0 -2013-08-28 20:00:00,17308.0 -2013-08-28 21:00:00,16733.0 -2013-08-28 22:00:00,16483.0 -2013-08-28 23:00:00,15543.0 -2013-08-29 00:00:00,14178.0 -2013-08-27 01:00:00,14682.0 -2013-08-27 02:00:00,13609.0 -2013-08-27 03:00:00,12934.0 -2013-08-27 04:00:00,12440.0 -2013-08-27 05:00:00,12165.0 -2013-08-27 06:00:00,12292.0 -2013-08-27 07:00:00,12985.0 -2013-08-27 08:00:00,14026.0 -2013-08-27 09:00:00,14942.0 -2013-08-27 10:00:00,15780.0 -2013-08-27 11:00:00,16944.0 -2013-08-27 12:00:00,18057.0 -2013-08-27 13:00:00,19119.0 -2013-08-27 14:00:00,20112.0 -2013-08-27 15:00:00,20953.0 -2013-08-27 16:00:00,21495.0 -2013-08-27 17:00:00,21843.0 -2013-08-27 18:00:00,21960.0 -2013-08-27 19:00:00,21716.0 -2013-08-27 20:00:00,21076.0 -2013-08-27 21:00:00,20613.0 -2013-08-27 22:00:00,20392.0 -2013-08-27 23:00:00,19281.0 -2013-08-28 00:00:00,17580.0 -2013-08-26 01:00:00,13027.0 -2013-08-26 02:00:00,12011.0 -2013-08-26 03:00:00,11373.0 -2013-08-26 04:00:00,10955.0 -2013-08-26 05:00:00,10656.0 -2013-08-26 06:00:00,10718.0 -2013-08-26 07:00:00,11463.0 -2013-08-26 08:00:00,12509.0 -2013-08-26 09:00:00,13462.0 -2013-08-26 10:00:00,14287.0 -2013-08-26 11:00:00,14969.0 -2013-08-26 12:00:00,16171.0 -2013-08-26 13:00:00,17241.0 -2013-08-26 14:00:00,18364.0 -2013-08-26 15:00:00,19327.0 -2013-08-26 16:00:00,19882.0 -2013-08-26 17:00:00,20351.0 -2013-08-26 18:00:00,20460.0 -2013-08-26 19:00:00,20154.0 -2013-08-26 20:00:00,19611.0 -2013-08-26 21:00:00,19145.0 -2013-08-26 22:00:00,18888.0 -2013-08-26 23:00:00,17775.0 -2013-08-27 00:00:00,16212.0 -2013-08-25 01:00:00,11543.0 -2013-08-25 02:00:00,10704.0 -2013-08-25 03:00:00,10032.0 -2013-08-25 04:00:00,9577.0 -2013-08-25 05:00:00,9261.0 -2013-08-25 06:00:00,9134.0 -2013-08-25 07:00:00,9075.0 -2013-08-25 08:00:00,9024.0 -2013-08-25 09:00:00,9419.0 -2013-08-25 10:00:00,10308.0 -2013-08-25 11:00:00,11441.0 -2013-08-25 12:00:00,12708.0 -2013-08-25 13:00:00,13862.0 -2013-08-25 14:00:00,14854.0 -2013-08-25 15:00:00,15705.0 -2013-08-25 16:00:00,16409.0 -2013-08-25 17:00:00,16914.0 -2013-08-25 18:00:00,17236.0 -2013-08-25 19:00:00,17170.0 -2013-08-25 20:00:00,16726.0 -2013-08-25 21:00:00,16243.0 -2013-08-25 22:00:00,16232.0 -2013-08-25 23:00:00,15439.0 -2013-08-26 00:00:00,14307.0 -2013-08-24 01:00:00,11084.0 -2013-08-24 02:00:00,10234.0 -2013-08-24 03:00:00,9686.0 -2013-08-24 04:00:00,9306.0 -2013-08-24 05:00:00,9087.0 -2013-08-24 06:00:00,9002.0 -2013-08-24 07:00:00,9170.0 -2013-08-24 08:00:00,9259.0 -2013-08-24 09:00:00,9881.0 -2013-08-24 10:00:00,10735.0 -2013-08-24 11:00:00,11596.0 -2013-08-24 12:00:00,12432.0 -2013-08-24 13:00:00,13085.0 -2013-08-24 14:00:00,13797.0 -2013-08-24 15:00:00,14321.0 -2013-08-24 16:00:00,14804.0 -2013-08-24 17:00:00,15244.0 -2013-08-24 18:00:00,15387.0 -2013-08-24 19:00:00,15270.0 -2013-08-24 20:00:00,14771.0 -2013-08-24 21:00:00,14232.0 -2013-08-24 22:00:00,14140.0 -2013-08-24 23:00:00,13509.0 -2013-08-25 00:00:00,12546.0 -2013-08-23 01:00:00,11369.0 -2013-08-23 02:00:00,10539.0 -2013-08-23 03:00:00,10019.0 -2013-08-23 04:00:00,9636.0 -2013-08-23 05:00:00,9451.0 -2013-08-23 06:00:00,9595.0 -2013-08-23 07:00:00,10222.0 -2013-08-23 08:00:00,11059.0 -2013-08-23 09:00:00,12033.0 -2013-08-23 10:00:00,12871.0 -2013-08-23 11:00:00,13600.0 -2013-08-23 12:00:00,14192.0 -2013-08-23 13:00:00,14622.0 -2013-08-23 14:00:00,14962.0 -2013-08-23 15:00:00,15342.0 -2013-08-23 16:00:00,15602.0 -2013-08-23 17:00:00,15797.0 -2013-08-23 18:00:00,15772.0 -2013-08-23 19:00:00,15452.0 -2013-08-23 20:00:00,14697.0 -2013-08-23 21:00:00,13947.0 -2013-08-23 22:00:00,13721.0 -2013-08-23 23:00:00,13098.0 -2013-08-24 00:00:00,12075.0 -2013-08-22 01:00:00,14215.0 -2013-08-22 02:00:00,13111.0 -2013-08-22 03:00:00,12215.0 -2013-08-22 04:00:00,11595.0 -2013-08-22 05:00:00,11306.0 -2013-08-22 06:00:00,11379.0 -2013-08-22 07:00:00,12052.0 -2013-08-22 08:00:00,13085.0 -2013-08-22 09:00:00,13973.0 -2013-08-22 10:00:00,14370.0 -2013-08-22 11:00:00,14591.0 -2013-08-22 12:00:00,14841.0 -2013-08-22 13:00:00,14734.0 -2013-08-22 14:00:00,14502.0 -2013-08-22 15:00:00,14415.0 -2013-08-22 16:00:00,14255.0 -2013-08-22 17:00:00,14280.0 -2013-08-22 18:00:00,14221.0 -2013-08-22 19:00:00,14159.0 -2013-08-22 20:00:00,14016.0 -2013-08-22 21:00:00,13869.0 -2013-08-22 22:00:00,14078.0 -2013-08-22 23:00:00,13453.0 -2013-08-23 00:00:00,12413.0 -2013-08-21 01:00:00,12995.0 -2013-08-21 02:00:00,11993.0 -2013-08-21 03:00:00,11247.0 -2013-08-21 04:00:00,10795.0 -2013-08-21 05:00:00,10533.0 -2013-08-21 06:00:00,10607.0 -2013-08-21 07:00:00,11266.0 -2013-08-21 08:00:00,12202.0 -2013-08-21 09:00:00,13210.0 -2013-08-21 10:00:00,14196.0 -2013-08-21 11:00:00,15356.0 -2013-08-21 12:00:00,16523.0 -2013-08-21 13:00:00,17506.0 -2013-08-21 14:00:00,18210.0 -2013-08-21 15:00:00,18839.0 -2013-08-21 16:00:00,19354.0 -2013-08-21 17:00:00,19674.0 -2013-08-21 18:00:00,19741.0 -2013-08-21 19:00:00,19430.0 -2013-08-21 20:00:00,18700.0 -2013-08-21 21:00:00,18201.0 -2013-08-21 22:00:00,18057.0 -2013-08-21 23:00:00,17124.0 -2013-08-22 00:00:00,15627.0 -2013-08-20 01:00:00,12193.0 -2013-08-20 02:00:00,11261.0 -2013-08-20 03:00:00,10600.0 -2013-08-20 04:00:00,10207.0 -2013-08-20 05:00:00,9971.0 -2013-08-20 06:00:00,10089.0 -2013-08-20 07:00:00,10714.0 -2013-08-20 08:00:00,11568.0 -2013-08-20 09:00:00,12633.0 -2013-08-20 10:00:00,13703.0 -2013-08-20 11:00:00,14649.0 -2013-08-20 12:00:00,15593.0 -2013-08-20 13:00:00,16313.0 -2013-08-20 14:00:00,16887.0 -2013-08-20 15:00:00,17535.0 -2013-08-20 16:00:00,17866.0 -2013-08-20 17:00:00,18095.0 -2013-08-20 18:00:00,18187.0 -2013-08-20 19:00:00,18018.0 -2013-08-20 20:00:00,17436.0 -2013-08-20 21:00:00,16831.0 -2013-08-20 22:00:00,16713.0 -2013-08-20 23:00:00,15802.0 -2013-08-21 00:00:00,14428.0 -2013-08-19 01:00:00,10605.0 -2013-08-19 02:00:00,9901.0 -2013-08-19 03:00:00,9418.0 -2013-08-19 04:00:00,9088.0 -2013-08-19 05:00:00,9025.0 -2013-08-19 06:00:00,9166.0 -2013-08-19 07:00:00,9872.0 -2013-08-19 08:00:00,10671.0 -2013-08-19 09:00:00,11742.0 -2013-08-19 10:00:00,12638.0 -2013-08-19 11:00:00,13461.0 -2013-08-19 12:00:00,14338.0 -2013-08-19 13:00:00,14985.0 -2013-08-19 14:00:00,15535.0 -2013-08-19 15:00:00,16086.0 -2013-08-19 16:00:00,16487.0 -2013-08-19 17:00:00,16609.0 -2013-08-19 18:00:00,16441.0 -2013-08-19 19:00:00,16212.0 -2013-08-19 20:00:00,15752.0 -2013-08-19 21:00:00,15398.0 -2013-08-19 22:00:00,15484.0 -2013-08-19 23:00:00,14731.0 -2013-08-20 00:00:00,13440.0 -2013-08-18 01:00:00,10258.0 -2013-08-18 02:00:00,9597.0 -2013-08-18 03:00:00,9120.0 -2013-08-18 04:00:00,8792.0 -2013-08-18 05:00:00,8540.0 -2013-08-18 06:00:00,8453.0 -2013-08-18 07:00:00,8442.0 -2013-08-18 08:00:00,8269.0 -2013-08-18 09:00:00,8645.0 -2013-08-18 10:00:00,9416.0 -2013-08-18 11:00:00,10201.0 -2013-08-18 12:00:00,10967.0 -2013-08-18 13:00:00,11536.0 -2013-08-18 14:00:00,12043.0 -2013-08-18 15:00:00,12416.0 -2013-08-18 16:00:00,12853.0 -2013-08-18 17:00:00,13102.0 -2013-08-18 18:00:00,13324.0 -2013-08-18 19:00:00,13317.0 -2013-08-18 20:00:00,13032.0 -2013-08-18 21:00:00,12686.0 -2013-08-18 22:00:00,12848.0 -2013-08-18 23:00:00,12340.0 -2013-08-19 00:00:00,11567.0 -2013-08-17 01:00:00,10604.0 -2013-08-17 02:00:00,9831.0 -2013-08-17 03:00:00,9283.0 -2013-08-17 04:00:00,8967.0 -2013-08-17 05:00:00,8800.0 -2013-08-17 06:00:00,8774.0 -2013-08-17 07:00:00,8908.0 -2013-08-17 08:00:00,8963.0 -2013-08-17 09:00:00,9556.0 -2013-08-17 10:00:00,10405.0 -2013-08-17 11:00:00,11101.0 -2013-08-17 12:00:00,11712.0 -2013-08-17 13:00:00,12073.0 -2013-08-17 14:00:00,12343.0 -2013-08-17 15:00:00,12520.0 -2013-08-17 16:00:00,12712.0 -2013-08-17 17:00:00,12862.0 -2013-08-17 18:00:00,12993.0 -2013-08-17 19:00:00,12863.0 -2013-08-17 20:00:00,12447.0 -2013-08-17 21:00:00,12035.0 -2013-08-17 22:00:00,12115.0 -2013-08-17 23:00:00,11715.0 -2013-08-18 00:00:00,10998.0 -2013-08-16 01:00:00,10152.0 -2013-08-16 02:00:00,9502.0 -2013-08-16 03:00:00,9059.0 -2013-08-16 04:00:00,8750.0 -2013-08-16 05:00:00,8683.0 -2013-08-16 06:00:00,8766.0 -2013-08-16 07:00:00,9272.0 -2013-08-16 08:00:00,9953.0 -2013-08-16 09:00:00,10807.0 -2013-08-16 10:00:00,11541.0 -2013-08-16 11:00:00,12192.0 -2013-08-16 12:00:00,12782.0 -2013-08-16 13:00:00,13186.0 -2013-08-16 14:00:00,13433.0 -2013-08-16 15:00:00,13770.0 -2013-08-16 16:00:00,13957.0 -2013-08-16 17:00:00,14090.0 -2013-08-16 18:00:00,14092.0 -2013-08-16 19:00:00,13865.0 -2013-08-16 20:00:00,13378.0 -2013-08-16 21:00:00,12867.0 -2013-08-16 22:00:00,12895.0 -2013-08-16 23:00:00,12427.0 -2013-08-17 00:00:00,11551.0 -2013-08-15 01:00:00,9936.0 -2013-08-15 02:00:00,9296.0 -2013-08-15 03:00:00,8893.0 -2013-08-15 04:00:00,8600.0 -2013-08-15 05:00:00,8476.0 -2013-08-15 06:00:00,8651.0 -2013-08-15 07:00:00,9179.0 -2013-08-15 08:00:00,9812.0 -2013-08-15 09:00:00,10672.0 -2013-08-15 10:00:00,11330.0 -2013-08-15 11:00:00,11824.0 -2013-08-15 12:00:00,12286.0 -2013-08-15 13:00:00,12601.0 -2013-08-15 14:00:00,12784.0 -2013-08-15 15:00:00,12894.0 -2013-08-15 16:00:00,12833.0 -2013-08-15 17:00:00,12722.0 -2013-08-15 18:00:00,12599.0 -2013-08-15 19:00:00,12392.0 -2013-08-15 20:00:00,12086.0 -2013-08-15 21:00:00,12010.0 -2013-08-15 22:00:00,12309.0 -2013-08-15 23:00:00,11900.0 -2013-08-16 00:00:00,11064.0 -2013-08-14 01:00:00,10009.0 -2013-08-14 02:00:00,9375.0 -2013-08-14 03:00:00,8953.0 -2013-08-14 04:00:00,8680.0 -2013-08-14 05:00:00,8548.0 -2013-08-14 06:00:00,8716.0 -2013-08-14 07:00:00,9284.0 -2013-08-14 08:00:00,9920.0 -2013-08-14 09:00:00,10757.0 -2013-08-14 10:00:00,11360.0 -2013-08-14 11:00:00,11799.0 -2013-08-14 12:00:00,12172.0 -2013-08-14 13:00:00,12377.0 -2013-08-14 14:00:00,12503.0 -2013-08-14 15:00:00,12651.0 -2013-08-14 16:00:00,12712.0 -2013-08-14 17:00:00,12682.0 -2013-08-14 18:00:00,12653.0 -2013-08-14 19:00:00,12510.0 -2013-08-14 20:00:00,12125.0 -2013-08-14 21:00:00,11892.0 -2013-08-14 22:00:00,12138.0 -2013-08-14 23:00:00,11666.0 -2013-08-15 00:00:00,10836.0 -2013-08-13 01:00:00,11966.0 -2013-08-13 02:00:00,11076.0 -2013-08-13 03:00:00,10374.0 -2013-08-13 04:00:00,9911.0 -2013-08-13 05:00:00,9599.0 -2013-08-13 06:00:00,9622.0 -2013-08-13 07:00:00,10042.0 -2013-08-13 08:00:00,10625.0 -2013-08-13 09:00:00,11365.0 -2013-08-13 10:00:00,11976.0 -2013-08-13 11:00:00,12398.0 -2013-08-13 12:00:00,12766.0 -2013-08-13 13:00:00,12930.0 -2013-08-13 14:00:00,12979.0 -2013-08-13 15:00:00,13120.0 -2013-08-13 16:00:00,13108.0 -2013-08-13 17:00:00,12977.0 -2013-08-13 18:00:00,12806.0 -2013-08-13 19:00:00,12573.0 -2013-08-13 20:00:00,12167.0 -2013-08-13 21:00:00,11870.0 -2013-08-13 22:00:00,12113.0 -2013-08-13 23:00:00,11728.0 -2013-08-14 00:00:00,10907.0 -2013-08-12 01:00:00,11236.0 -2013-08-12 02:00:00,10540.0 -2013-08-12 03:00:00,10050.0 -2013-08-12 04:00:00,9758.0 -2013-08-12 05:00:00,9613.0 -2013-08-12 06:00:00,9831.0 -2013-08-12 07:00:00,10572.0 -2013-08-12 08:00:00,11471.0 -2013-08-12 09:00:00,12381.0 -2013-08-12 10:00:00,13264.0 -2013-08-12 11:00:00,14089.0 -2013-08-12 12:00:00,14961.0 -2013-08-12 13:00:00,15605.0 -2013-08-12 14:00:00,16185.0 -2013-08-12 15:00:00,16687.0 -2013-08-12 16:00:00,16649.0 -2013-08-12 17:00:00,16367.0 -2013-08-12 18:00:00,16221.0 -2013-08-12 19:00:00,16084.0 -2013-08-12 20:00:00,15621.0 -2013-08-12 21:00:00,15071.0 -2013-08-12 22:00:00,15032.0 -2013-08-12 23:00:00,14379.0 -2013-08-13 00:00:00,13191.0 -2013-08-11 01:00:00,10710.0 -2013-08-11 02:00:00,9986.0 -2013-08-11 03:00:00,9443.0 -2013-08-11 04:00:00,9091.0 -2013-08-11 05:00:00,8808.0 -2013-08-11 06:00:00,8677.0 -2013-08-11 07:00:00,8630.0 -2013-08-11 08:00:00,8482.0 -2013-08-11 09:00:00,8887.0 -2013-08-11 10:00:00,9675.0 -2013-08-11 11:00:00,10455.0 -2013-08-11 12:00:00,11093.0 -2013-08-11 13:00:00,11685.0 -2013-08-11 14:00:00,12014.0 -2013-08-11 15:00:00,12299.0 -2013-08-11 16:00:00,12755.0 -2013-08-11 17:00:00,13033.0 -2013-08-11 18:00:00,13145.0 -2013-08-11 19:00:00,12982.0 -2013-08-11 20:00:00,12823.0 -2013-08-11 21:00:00,12723.0 -2013-08-11 22:00:00,13024.0 -2013-08-11 23:00:00,12784.0 -2013-08-12 00:00:00,12093.0 -2013-08-10 01:00:00,12112.0 -2013-08-10 02:00:00,11246.0 -2013-08-10 03:00:00,10566.0 -2013-08-10 04:00:00,10065.0 -2013-08-10 05:00:00,9716.0 -2013-08-10 06:00:00,9590.0 -2013-08-10 07:00:00,9650.0 -2013-08-10 08:00:00,9691.0 -2013-08-10 09:00:00,10422.0 -2013-08-10 10:00:00,11439.0 -2013-08-10 11:00:00,12335.0 -2013-08-10 12:00:00,13019.0 -2013-08-10 13:00:00,13432.0 -2013-08-10 14:00:00,13733.0 -2013-08-10 15:00:00,13825.0 -2013-08-10 16:00:00,13941.0 -2013-08-10 17:00:00,13923.0 -2013-08-10 18:00:00,13852.0 -2013-08-10 19:00:00,13588.0 -2013-08-10 20:00:00,13042.0 -2013-08-10 21:00:00,12538.0 -2013-08-10 22:00:00,12570.0 -2013-08-10 23:00:00,12250.0 -2013-08-11 00:00:00,11523.0 -2013-08-09 01:00:00,11056.0 -2013-08-09 02:00:00,10329.0 -2013-08-09 03:00:00,9803.0 -2013-08-09 04:00:00,9441.0 -2013-08-09 05:00:00,9242.0 -2013-08-09 06:00:00,9334.0 -2013-08-09 07:00:00,9835.0 -2013-08-09 08:00:00,10405.0 -2013-08-09 09:00:00,11442.0 -2013-08-09 10:00:00,12271.0 -2013-08-09 11:00:00,13022.0 -2013-08-09 12:00:00,13844.0 -2013-08-09 13:00:00,14481.0 -2013-08-09 14:00:00,14934.0 -2013-08-09 15:00:00,15425.0 -2013-08-09 16:00:00,15675.0 -2013-08-09 17:00:00,15703.0 -2013-08-09 18:00:00,15635.0 -2013-08-09 19:00:00,15322.0 -2013-08-09 20:00:00,14854.0 -2013-08-09 21:00:00,14491.0 -2013-08-09 22:00:00,14553.0 -2013-08-09 23:00:00,14165.0 -2013-08-10 00:00:00,13184.0 -2013-08-08 01:00:00,12245.0 -2013-08-08 02:00:00,11238.0 -2013-08-08 03:00:00,10568.0 -2013-08-08 04:00:00,10132.0 -2013-08-08 05:00:00,9886.0 -2013-08-08 06:00:00,9964.0 -2013-08-08 07:00:00,10511.0 -2013-08-08 08:00:00,11251.0 -2013-08-08 09:00:00,12211.0 -2013-08-08 10:00:00,12933.0 -2013-08-08 11:00:00,13558.0 -2013-08-08 12:00:00,14118.0 -2013-08-08 13:00:00,14505.0 -2013-08-08 14:00:00,14836.0 -2013-08-08 15:00:00,15102.0 -2013-08-08 16:00:00,15224.0 -2013-08-08 17:00:00,15262.0 -2013-08-08 18:00:00,15180.0 -2013-08-08 19:00:00,14777.0 -2013-08-08 20:00:00,13974.0 -2013-08-08 21:00:00,13434.0 -2013-08-08 22:00:00,13517.0 -2013-08-08 23:00:00,13000.0 -2013-08-09 00:00:00,12062.0 -2013-08-07 01:00:00,12823.0 -2013-08-07 02:00:00,11875.0 -2013-08-07 03:00:00,11234.0 -2013-08-07 04:00:00,10764.0 -2013-08-07 05:00:00,10578.0 -2013-08-07 06:00:00,10718.0 -2013-08-07 07:00:00,11306.0 -2013-08-07 08:00:00,12194.0 -2013-08-07 09:00:00,13326.0 -2013-08-07 10:00:00,14528.0 -2013-08-07 11:00:00,15711.0 -2013-08-07 12:00:00,16791.0 -2013-08-07 13:00:00,17553.0 -2013-08-07 14:00:00,18003.0 -2013-08-07 15:00:00,18483.0 -2013-08-07 16:00:00,18613.0 -2013-08-07 17:00:00,18514.0 -2013-08-07 18:00:00,18497.0 -2013-08-07 19:00:00,18230.0 -2013-08-07 20:00:00,17324.0 -2013-08-07 21:00:00,16211.0 -2013-08-07 22:00:00,15672.0 -2013-08-07 23:00:00,14792.0 -2013-08-08 00:00:00,13537.0 -2013-08-06 01:00:00,10596.0 -2013-08-06 02:00:00,10023.0 -2013-08-06 03:00:00,9589.0 -2013-08-06 04:00:00,9339.0 -2013-08-06 05:00:00,9237.0 -2013-08-06 06:00:00,9441.0 -2013-08-06 07:00:00,10043.0 -2013-08-06 08:00:00,10909.0 -2013-08-06 09:00:00,11722.0 -2013-08-06 10:00:00,12423.0 -2013-08-06 11:00:00,12964.0 -2013-08-06 12:00:00,13521.0 -2013-08-06 13:00:00,14022.0 -2013-08-06 14:00:00,14570.0 -2013-08-06 15:00:00,15170.0 -2013-08-06 16:00:00,15467.0 -2013-08-06 17:00:00,15863.0 -2013-08-06 18:00:00,16307.0 -2013-08-06 19:00:00,16188.0 -2013-08-06 20:00:00,15773.0 -2013-08-06 21:00:00,15492.0 -2013-08-06 22:00:00,15549.0 -2013-08-06 23:00:00,15059.0 -2013-08-07 00:00:00,13993.0 -2013-08-05 01:00:00,9880.0 -2013-08-05 02:00:00,9292.0 -2013-08-05 03:00:00,8953.0 -2013-08-05 04:00:00,8713.0 -2013-08-05 05:00:00,8606.0 -2013-08-05 06:00:00,8796.0 -2013-08-05 07:00:00,9379.0 -2013-08-05 08:00:00,10051.0 -2013-08-05 09:00:00,10968.0 -2013-08-05 10:00:00,11572.0 -2013-08-05 11:00:00,11953.0 -2013-08-05 12:00:00,12337.0 -2013-08-05 13:00:00,12608.0 -2013-08-05 14:00:00,12803.0 -2013-08-05 15:00:00,13053.0 -2013-08-05 16:00:00,13156.0 -2013-08-05 17:00:00,12959.0 -2013-08-05 18:00:00,12688.0 -2013-08-05 19:00:00,12469.0 -2013-08-05 20:00:00,12155.0 -2013-08-05 21:00:00,12122.0 -2013-08-05 22:00:00,12453.0 -2013-08-05 23:00:00,12173.0 -2013-08-06 00:00:00,11468.0 -2013-08-04 01:00:00,10248.0 -2013-08-04 02:00:00,9636.0 -2013-08-04 03:00:00,9098.0 -2013-08-04 04:00:00,8805.0 -2013-08-04 05:00:00,8489.0 -2013-08-04 06:00:00,8398.0 -2013-08-04 07:00:00,8298.0 -2013-08-04 08:00:00,8228.0 -2013-08-04 09:00:00,8661.0 -2013-08-04 10:00:00,9366.0 -2013-08-04 11:00:00,9978.0 -2013-08-04 12:00:00,10499.0 -2013-08-04 13:00:00,10894.0 -2013-08-04 14:00:00,11153.0 -2013-08-04 15:00:00,11385.0 -2013-08-04 16:00:00,11509.0 -2013-08-04 17:00:00,11700.0 -2013-08-04 18:00:00,11708.0 -2013-08-04 19:00:00,11483.0 -2013-08-04 20:00:00,11132.0 -2013-08-04 21:00:00,10989.0 -2013-08-04 22:00:00,11277.0 -2013-08-04 23:00:00,11144.0 -2013-08-05 00:00:00,10555.0 -2013-08-03 01:00:00,11838.0 -2013-08-03 02:00:00,10880.0 -2013-08-03 03:00:00,10275.0 -2013-08-03 04:00:00,9736.0 -2013-08-03 05:00:00,9464.0 -2013-08-03 06:00:00,9244.0 -2013-08-03 07:00:00,9273.0 -2013-08-03 08:00:00,9379.0 -2013-08-03 09:00:00,10130.0 -2013-08-03 10:00:00,11006.0 -2013-08-03 11:00:00,11846.0 -2013-08-03 12:00:00,12433.0 -2013-08-03 13:00:00,12795.0 -2013-08-03 14:00:00,12949.0 -2013-08-03 15:00:00,12991.0 -2013-08-03 16:00:00,13092.0 -2013-08-03 17:00:00,13160.0 -2013-08-03 18:00:00,13136.0 -2013-08-03 19:00:00,12919.0 -2013-08-03 20:00:00,12542.0 -2013-08-03 21:00:00,11997.0 -2013-08-03 22:00:00,12005.0 -2013-08-03 23:00:00,11723.0 -2013-08-04 00:00:00,11055.0 -2013-08-02 01:00:00,10840.0 -2013-08-02 02:00:00,10101.0 -2013-08-02 03:00:00,9632.0 -2013-08-02 04:00:00,9376.0 -2013-08-02 05:00:00,9193.0 -2013-08-02 06:00:00,9321.0 -2013-08-02 07:00:00,9862.0 -2013-08-02 08:00:00,10566.0 -2013-08-02 09:00:00,11318.0 -2013-08-02 10:00:00,11983.0 -2013-08-02 11:00:00,12620.0 -2013-08-02 12:00:00,13363.0 -2013-08-02 13:00:00,14111.0 -2013-08-02 14:00:00,14795.0 -2013-08-02 15:00:00,15456.0 -2013-08-02 16:00:00,15668.0 -2013-08-02 17:00:00,15734.0 -2013-08-02 18:00:00,15515.0 -2013-08-02 19:00:00,15092.0 -2013-08-02 20:00:00,14576.0 -2013-08-02 21:00:00,14225.0 -2013-08-02 22:00:00,14246.0 -2013-08-02 23:00:00,13796.0 -2013-08-03 00:00:00,12826.0 -2013-08-01 01:00:00,10640.0 -2013-08-01 02:00:00,9914.0 -2013-08-01 03:00:00,9405.0 -2013-08-01 04:00:00,9077.0 -2013-08-01 05:00:00,8934.0 -2013-08-01 06:00:00,9055.0 -2013-08-01 07:00:00,9499.0 -2013-08-01 08:00:00,10217.0 -2013-08-01 09:00:00,11221.0 -2013-08-01 10:00:00,12023.0 -2013-08-01 11:00:00,12667.0 -2013-08-01 12:00:00,13264.0 -2013-08-01 13:00:00,13558.0 -2013-08-01 14:00:00,13671.0 -2013-08-01 15:00:00,14041.0 -2013-08-01 16:00:00,14232.0 -2013-08-01 17:00:00,14322.0 -2013-08-01 18:00:00,14320.0 -2013-08-01 19:00:00,14176.0 -2013-08-01 20:00:00,13718.0 -2013-08-01 21:00:00,13219.0 -2013-08-01 22:00:00,13149.0 -2013-08-01 23:00:00,12821.0 -2013-08-02 00:00:00,11841.0 -2013-07-31 01:00:00,10361.0 -2013-07-31 02:00:00,9721.0 -2013-07-31 03:00:00,9348.0 -2013-07-31 04:00:00,9108.0 -2013-07-31 05:00:00,9045.0 -2013-07-31 06:00:00,9208.0 -2013-07-31 07:00:00,9800.0 -2013-07-31 08:00:00,10713.0 -2013-07-31 09:00:00,11432.0 -2013-07-31 10:00:00,12020.0 -2013-07-31 11:00:00,12360.0 -2013-07-31 12:00:00,12705.0 -2013-07-31 13:00:00,12870.0 -2013-07-31 14:00:00,12976.0 -2013-07-31 15:00:00,13085.0 -2013-07-31 16:00:00,13067.0 -2013-07-31 17:00:00,13022.0 -2013-07-31 18:00:00,12984.0 -2013-07-31 19:00:00,12961.0 -2013-07-31 20:00:00,12808.0 -2013-07-31 21:00:00,12620.0 -2013-07-31 22:00:00,12847.0 -2013-07-31 23:00:00,12537.0 -2013-08-01 00:00:00,11619.0 -2013-07-30 01:00:00,10221.0 -2013-07-30 02:00:00,9534.0 -2013-07-30 03:00:00,9126.0 -2013-07-30 04:00:00,8833.0 -2013-07-30 05:00:00,8710.0 -2013-07-30 06:00:00,8832.0 -2013-07-30 07:00:00,9291.0 -2013-07-30 08:00:00,9964.0 -2013-07-30 09:00:00,10909.0 -2013-07-30 10:00:00,11584.0 -2013-07-30 11:00:00,12045.0 -2013-07-30 12:00:00,12465.0 -2013-07-30 13:00:00,12666.0 -2013-07-30 14:00:00,12674.0 -2013-07-30 15:00:00,12716.0 -2013-07-30 16:00:00,12641.0 -2013-07-30 17:00:00,12507.0 -2013-07-30 18:00:00,12396.0 -2013-07-30 19:00:00,12239.0 -2013-07-30 20:00:00,11983.0 -2013-07-30 21:00:00,11947.0 -2013-07-30 22:00:00,12238.0 -2013-07-30 23:00:00,11951.0 -2013-07-31 00:00:00,11160.0 -2013-07-29 01:00:00,8981.0 -2013-07-29 02:00:00,8474.0 -2013-07-29 03:00:00,8218.0 -2013-07-29 04:00:00,8023.0 -2013-07-29 05:00:00,8027.0 -2013-07-29 06:00:00,8222.0 -2013-07-29 07:00:00,8738.0 -2013-07-29 08:00:00,9450.0 -2013-07-29 09:00:00,10454.0 -2013-07-29 10:00:00,11099.0 -2013-07-29 11:00:00,11622.0 -2013-07-29 12:00:00,12032.0 -2013-07-29 13:00:00,12284.0 -2013-07-29 14:00:00,12472.0 -2013-07-29 15:00:00,12754.0 -2013-07-29 16:00:00,12883.0 -2013-07-29 17:00:00,12962.0 -2013-07-29 18:00:00,12958.0 -2013-07-29 19:00:00,12853.0 -2013-07-29 20:00:00,12488.0 -2013-07-29 21:00:00,12133.0 -2013-07-29 22:00:00,12229.0 -2013-07-29 23:00:00,11970.0 -2013-07-30 00:00:00,11148.0 -2013-07-28 01:00:00,8870.0 -2013-07-28 02:00:00,8371.0 -2013-07-28 03:00:00,8043.0 -2013-07-28 04:00:00,7814.0 -2013-07-28 05:00:00,7643.0 -2013-07-28 06:00:00,7664.0 -2013-07-28 07:00:00,7652.0 -2013-07-28 08:00:00,7607.0 -2013-07-28 09:00:00,7779.0 -2013-07-28 10:00:00,8219.0 -2013-07-28 11:00:00,8637.0 -2013-07-28 12:00:00,8930.0 -2013-07-28 13:00:00,9109.0 -2013-07-28 14:00:00,9229.0 -2013-07-28 15:00:00,9269.0 -2013-07-28 16:00:00,9387.0 -2013-07-28 17:00:00,9395.0 -2013-07-28 18:00:00,9500.0 -2013-07-28 19:00:00,9558.0 -2013-07-28 20:00:00,9576.0 -2013-07-28 21:00:00,9575.0 -2013-07-28 22:00:00,9900.0 -2013-07-28 23:00:00,9976.0 -2013-07-29 00:00:00,9489.0 -2013-07-27 01:00:00,10449.0 -2013-07-27 02:00:00,9696.0 -2013-07-27 03:00:00,9218.0 -2013-07-27 04:00:00,8841.0 -2013-07-27 05:00:00,8677.0 -2013-07-27 06:00:00,8601.0 -2013-07-27 07:00:00,8751.0 -2013-07-27 08:00:00,8807.0 -2013-07-27 09:00:00,9243.0 -2013-07-27 10:00:00,9667.0 -2013-07-27 11:00:00,10039.0 -2013-07-27 12:00:00,10280.0 -2013-07-27 13:00:00,10418.0 -2013-07-27 14:00:00,10428.0 -2013-07-27 15:00:00,10218.0 -2013-07-27 16:00:00,10000.0 -2013-07-27 17:00:00,9873.0 -2013-07-27 18:00:00,9787.0 -2013-07-27 19:00:00,9669.0 -2013-07-27 20:00:00,9533.0 -2013-07-27 21:00:00,9518.0 -2013-07-27 22:00:00,9840.0 -2013-07-27 23:00:00,9851.0 -2013-07-28 00:00:00,9393.0 -2013-07-26 01:00:00,11399.0 -2013-07-26 02:00:00,10588.0 -2013-07-26 03:00:00,10024.0 -2013-07-26 04:00:00,9676.0 -2013-07-26 05:00:00,9491.0 -2013-07-26 06:00:00,9581.0 -2013-07-26 07:00:00,10128.0 -2013-07-26 08:00:00,10877.0 -2013-07-26 09:00:00,11556.0 -2013-07-26 10:00:00,12145.0 -2013-07-26 11:00:00,12431.0 -2013-07-26 12:00:00,12687.0 -2013-07-26 13:00:00,12821.0 -2013-07-26 14:00:00,12801.0 -2013-07-26 15:00:00,12837.0 -2013-07-26 16:00:00,12764.0 -2013-07-26 17:00:00,12639.0 -2013-07-26 18:00:00,12522.0 -2013-07-26 19:00:00,12339.0 -2013-07-26 20:00:00,12104.0 -2013-07-26 21:00:00,11927.0 -2013-07-26 22:00:00,12025.0 -2013-07-26 23:00:00,11970.0 -2013-07-27 00:00:00,11285.0 -2013-07-25 01:00:00,10329.0 -2013-07-25 02:00:00,9625.0 -2013-07-25 03:00:00,9170.0 -2013-07-25 04:00:00,8879.0 -2013-07-25 05:00:00,8742.0 -2013-07-25 06:00:00,8913.0 -2013-07-25 07:00:00,9347.0 -2013-07-25 08:00:00,10015.0 -2013-07-25 09:00:00,11030.0 -2013-07-25 10:00:00,11751.0 -2013-07-25 11:00:00,12296.0 -2013-07-25 12:00:00,12819.0 -2013-07-25 13:00:00,13227.0 -2013-07-25 14:00:00,13615.0 -2013-07-25 15:00:00,13997.0 -2013-07-25 16:00:00,14264.0 -2013-07-25 17:00:00,14485.0 -2013-07-25 18:00:00,14549.0 -2013-07-25 19:00:00,14324.0 -2013-07-25 20:00:00,13791.0 -2013-07-25 21:00:00,13410.0 -2013-07-25 22:00:00,13524.0 -2013-07-25 23:00:00,13273.0 -2013-07-26 00:00:00,12403.0 -2013-07-24 01:00:00,10753.0 -2013-07-24 02:00:00,10038.0 -2013-07-24 03:00:00,9525.0 -2013-07-24 04:00:00,9196.0 -2013-07-24 05:00:00,9025.0 -2013-07-24 06:00:00,9140.0 -2013-07-24 07:00:00,9514.0 -2013-07-24 08:00:00,10188.0 -2013-07-24 09:00:00,11190.0 -2013-07-24 10:00:00,11855.0 -2013-07-24 11:00:00,12265.0 -2013-07-24 12:00:00,12644.0 -2013-07-24 13:00:00,12903.0 -2013-07-24 14:00:00,13104.0 -2013-07-24 15:00:00,13331.0 -2013-07-24 16:00:00,13428.0 -2013-07-24 17:00:00,13489.0 -2013-07-24 18:00:00,13458.0 -2013-07-24 19:00:00,13253.0 -2013-07-24 20:00:00,12807.0 -2013-07-24 21:00:00,12355.0 -2013-07-24 22:00:00,12264.0 -2013-07-24 23:00:00,12134.0 -2013-07-25 00:00:00,11259.0 -2013-07-23 01:00:00,14400.0 -2013-07-23 02:00:00,12938.0 -2013-07-23 03:00:00,11878.0 -2013-07-23 04:00:00,11366.0 -2013-07-23 05:00:00,11081.0 -2013-07-23 06:00:00,11036.0 -2013-07-23 07:00:00,11389.0 -2013-07-23 08:00:00,12103.0 -2013-07-23 09:00:00,13212.0 -2013-07-23 10:00:00,14067.0 -2013-07-23 11:00:00,14696.0 -2013-07-23 12:00:00,15133.0 -2013-07-23 13:00:00,15453.0 -2013-07-23 14:00:00,15711.0 -2013-07-23 15:00:00,16017.0 -2013-07-23 16:00:00,16107.0 -2013-07-23 17:00:00,16067.0 -2013-07-23 18:00:00,15818.0 -2013-07-23 19:00:00,15240.0 -2013-07-23 20:00:00,14336.0 -2013-07-23 21:00:00,13410.0 -2013-07-23 22:00:00,13052.0 -2013-07-23 23:00:00,12686.0 -2013-07-24 00:00:00,11778.0 -2013-07-22 01:00:00,12281.0 -2013-07-22 02:00:00,11530.0 -2013-07-22 03:00:00,11058.0 -2013-07-22 04:00:00,10738.0 -2013-07-22 05:00:00,10613.0 -2013-07-22 06:00:00,10804.0 -2013-07-22 07:00:00,11354.0 -2013-07-22 08:00:00,12169.0 -2013-07-22 09:00:00,13309.0 -2013-07-22 10:00:00,14422.0 -2013-07-22 11:00:00,15348.0 -2013-07-22 12:00:00,16404.0 -2013-07-22 13:00:00,17253.0 -2013-07-22 14:00:00,17927.0 -2013-07-22 15:00:00,18568.0 -2013-07-22 16:00:00,19018.0 -2013-07-22 17:00:00,19227.0 -2013-07-22 18:00:00,19427.0 -2013-07-22 19:00:00,19362.0 -2013-07-22 20:00:00,18901.0 -2013-07-22 21:00:00,18163.0 -2013-07-22 22:00:00,17696.0 -2013-07-22 23:00:00,17084.0 -2013-07-23 00:00:00,15773.0 -2013-07-21 01:00:00,12795.0 -2013-07-21 02:00:00,11936.0 -2013-07-21 03:00:00,11221.0 -2013-07-21 04:00:00,10655.0 -2013-07-21 05:00:00,10323.0 -2013-07-21 06:00:00,10097.0 -2013-07-21 07:00:00,9897.0 -2013-07-21 08:00:00,9892.0 -2013-07-21 09:00:00,10620.0 -2013-07-21 10:00:00,11541.0 -2013-07-21 11:00:00,12511.0 -2013-07-21 12:00:00,13470.0 -2013-07-21 13:00:00,14456.0 -2013-07-21 14:00:00,15280.0 -2013-07-21 15:00:00,15865.0 -2013-07-21 16:00:00,16269.0 -2013-07-21 17:00:00,16433.0 -2013-07-21 18:00:00,16311.0 -2013-07-21 19:00:00,16037.0 -2013-07-21 20:00:00,15509.0 -2013-07-21 21:00:00,14914.0 -2013-07-21 22:00:00,14687.0 -2013-07-21 23:00:00,14084.0 -2013-07-22 00:00:00,13190.0 -2013-07-20 01:00:00,15356.0 -2013-07-20 02:00:00,14217.0 -2013-07-20 03:00:00,13335.0 -2013-07-20 04:00:00,12631.0 -2013-07-20 05:00:00,12166.0 -2013-07-20 06:00:00,11758.0 -2013-07-20 07:00:00,11606.0 -2013-07-20 08:00:00,11732.0 -2013-07-20 09:00:00,12680.0 -2013-07-20 10:00:00,13771.0 -2013-07-20 11:00:00,14884.0 -2013-07-20 12:00:00,15833.0 -2013-07-20 13:00:00,16443.0 -2013-07-20 14:00:00,16874.0 -2013-07-20 15:00:00,17116.0 -2013-07-20 16:00:00,17381.0 -2013-07-20 17:00:00,17610.0 -2013-07-20 18:00:00,17702.0 -2013-07-20 19:00:00,17459.0 -2013-07-20 20:00:00,16856.0 -2013-07-20 21:00:00,15966.0 -2013-07-20 22:00:00,15600.0 -2013-07-20 23:00:00,14941.0 -2013-07-21 00:00:00,13944.0 -2013-07-19 01:00:00,17044.0 -2013-07-19 02:00:00,15856.0 -2013-07-19 03:00:00,14971.0 -2013-07-19 04:00:00,14315.0 -2013-07-19 05:00:00,13917.0 -2013-07-19 06:00:00,13858.0 -2013-07-19 07:00:00,14174.0 -2013-07-19 08:00:00,15013.0 -2013-07-19 09:00:00,16496.0 -2013-07-19 10:00:00,17839.0 -2013-07-19 11:00:00,19035.0 -2013-07-19 12:00:00,20165.0 -2013-07-19 13:00:00,20951.0 -2013-07-19 14:00:00,21469.0 -2013-07-19 15:00:00,21875.0 -2013-07-19 16:00:00,22032.0 -2013-07-19 17:00:00,22038.0 -2013-07-19 18:00:00,21955.0 -2013-07-19 19:00:00,21675.0 -2013-07-19 20:00:00,21177.0 -2013-07-19 21:00:00,20387.0 -2013-07-19 22:00:00,19788.0 -2013-07-19 23:00:00,18814.0 -2013-07-20 00:00:00,16805.0 -2013-07-18 01:00:00,16696.0 -2013-07-18 02:00:00,15428.0 -2013-07-18 03:00:00,14533.0 -2013-07-18 04:00:00,13888.0 -2013-07-18 05:00:00,13503.0 -2013-07-18 06:00:00,13438.0 -2013-07-18 07:00:00,13777.0 -2013-07-18 08:00:00,14740.0 -2013-07-18 09:00:00,16283.0 -2013-07-18 10:00:00,17764.0 -2013-07-18 11:00:00,19076.0 -2013-07-18 12:00:00,20267.0 -2013-07-18 13:00:00,20978.0 -2013-07-18 14:00:00,21501.0 -2013-07-18 15:00:00,21949.0 -2013-07-18 16:00:00,22208.0 -2013-07-18 17:00:00,22269.0 -2013-07-18 18:00:00,21989.0 -2013-07-18 19:00:00,21464.0 -2013-07-18 20:00:00,20961.0 -2013-07-18 21:00:00,20458.0 -2013-07-18 22:00:00,20082.0 -2013-07-18 23:00:00,19710.0 -2013-07-19 00:00:00,18424.0 -2013-07-17 01:00:00,16249.0 -2013-07-17 02:00:00,14982.0 -2013-07-17 03:00:00,14054.0 -2013-07-17 04:00:00,13381.0 -2013-07-17 05:00:00,12971.0 -2013-07-17 06:00:00,12919.0 -2013-07-17 07:00:00,13260.0 -2013-07-17 08:00:00,14244.0 -2013-07-17 09:00:00,15780.0 -2013-07-17 10:00:00,17126.0 -2013-07-17 11:00:00,18425.0 -2013-07-17 12:00:00,19601.0 -2013-07-17 13:00:00,20293.0 -2013-07-17 14:00:00,20883.0 -2013-07-17 15:00:00,21383.0 -2013-07-17 16:00:00,21616.0 -2013-07-17 17:00:00,21778.0 -2013-07-17 18:00:00,21801.0 -2013-07-17 19:00:00,21664.0 -2013-07-17 20:00:00,21262.0 -2013-07-17 21:00:00,20720.0 -2013-07-17 22:00:00,20250.0 -2013-07-17 23:00:00,19695.0 -2013-07-18 00:00:00,18244.0 -2013-07-16 01:00:00,14922.0 -2013-07-16 02:00:00,13818.0 -2013-07-16 03:00:00,13068.0 -2013-07-16 04:00:00,12490.0 -2013-07-16 05:00:00,12169.0 -2013-07-16 06:00:00,12219.0 -2013-07-16 07:00:00,12643.0 -2013-07-16 08:00:00,13665.0 -2013-07-16 09:00:00,15173.0 -2013-07-16 10:00:00,16577.0 -2013-07-16 11:00:00,17747.0 -2013-07-16 12:00:00,18778.0 -2013-07-16 13:00:00,19540.0 -2013-07-16 14:00:00,20146.0 -2013-07-16 15:00:00,20733.0 -2013-07-16 16:00:00,21088.0 -2013-07-16 17:00:00,21287.0 -2013-07-16 18:00:00,21381.0 -2013-07-16 19:00:00,21210.0 -2013-07-16 20:00:00,20802.0 -2013-07-16 21:00:00,20188.0 -2013-07-16 22:00:00,19727.0 -2013-07-16 23:00:00,19216.0 -2013-07-17 00:00:00,17810.0 -2013-07-15 01:00:00,13349.0 -2013-07-15 02:00:00,12419.0 -2013-07-15 03:00:00,11737.0 -2013-07-15 04:00:00,11291.0 -2013-07-15 05:00:00,11094.0 -2013-07-15 06:00:00,11209.0 -2013-07-15 07:00:00,11685.0 -2013-07-15 08:00:00,12841.0 -2013-07-15 09:00:00,14478.0 -2013-07-15 10:00:00,15936.0 -2013-07-15 11:00:00,17309.0 -2013-07-15 12:00:00,18522.0 -2013-07-15 13:00:00,19221.0 -2013-07-15 14:00:00,19550.0 -2013-07-15 15:00:00,19759.0 -2013-07-15 16:00:00,19781.0 -2013-07-15 17:00:00,19751.0 -2013-07-15 18:00:00,19536.0 -2013-07-15 19:00:00,19153.0 -2013-07-15 20:00:00,18745.0 -2013-07-15 21:00:00,18261.0 -2013-07-15 22:00:00,17989.0 -2013-07-15 23:00:00,17573.0 -2013-07-16 00:00:00,16347.0 -2013-07-14 01:00:00,11922.0 -2013-07-14 02:00:00,11095.0 -2013-07-14 03:00:00,10489.0 -2013-07-14 04:00:00,10113.0 -2013-07-14 05:00:00,9835.0 -2013-07-14 06:00:00,9674.0 -2013-07-14 07:00:00,9481.0 -2013-07-14 08:00:00,9664.0 -2013-07-14 09:00:00,10443.0 -2013-07-14 10:00:00,11526.0 -2013-07-14 11:00:00,12723.0 -2013-07-14 12:00:00,13924.0 -2013-07-14 13:00:00,14842.0 -2013-07-14 14:00:00,15528.0 -2013-07-14 15:00:00,16047.0 -2013-07-14 16:00:00,16399.0 -2013-07-14 17:00:00,16703.0 -2013-07-14 18:00:00,16933.0 -2013-07-14 19:00:00,16998.0 -2013-07-14 20:00:00,16774.0 -2013-07-14 21:00:00,16242.0 -2013-07-14 22:00:00,15862.0 -2013-07-14 23:00:00,15575.0 -2013-07-15 00:00:00,14535.0 -2013-07-13 01:00:00,11765.0 -2013-07-13 02:00:00,10844.0 -2013-07-13 03:00:00,10181.0 -2013-07-13 04:00:00,9735.0 -2013-07-13 05:00:00,9422.0 -2013-07-13 06:00:00,9301.0 -2013-07-13 07:00:00,9214.0 -2013-07-13 08:00:00,9570.0 -2013-07-13 09:00:00,10384.0 -2013-07-13 10:00:00,11388.0 -2013-07-13 11:00:00,12271.0 -2013-07-13 12:00:00,13084.0 -2013-07-13 13:00:00,13686.0 -2013-07-13 14:00:00,14110.0 -2013-07-13 15:00:00,14442.0 -2013-07-13 16:00:00,14781.0 -2013-07-13 17:00:00,15105.0 -2013-07-13 18:00:00,15321.0 -2013-07-13 19:00:00,15292.0 -2013-07-13 20:00:00,14918.0 -2013-07-13 21:00:00,14347.0 -2013-07-13 22:00:00,13887.0 -2013-07-13 23:00:00,13637.0 -2013-07-14 00:00:00,12815.0 -2013-07-12 01:00:00,11374.0 -2013-07-12 02:00:00,10470.0 -2013-07-12 03:00:00,9802.0 -2013-07-12 04:00:00,9360.0 -2013-07-12 05:00:00,9235.0 -2013-07-12 06:00:00,9245.0 -2013-07-12 07:00:00,9550.0 -2013-07-12 08:00:00,10351.0 -2013-07-12 09:00:00,11521.0 -2013-07-12 10:00:00,12453.0 -2013-07-12 11:00:00,13150.0 -2013-07-12 12:00:00,13806.0 -2013-07-12 13:00:00,14322.0 -2013-07-12 14:00:00,14733.0 -2013-07-12 15:00:00,15254.0 -2013-07-12 16:00:00,15687.0 -2013-07-12 17:00:00,15985.0 -2013-07-12 18:00:00,16185.0 -2013-07-12 19:00:00,16045.0 -2013-07-12 20:00:00,15559.0 -2013-07-12 21:00:00,14784.0 -2013-07-12 22:00:00,14165.0 -2013-07-12 23:00:00,13808.0 -2013-07-13 00:00:00,12806.0 -2013-07-11 01:00:00,11984.0 -2013-07-11 02:00:00,11016.0 -2013-07-11 03:00:00,10383.0 -2013-07-11 04:00:00,9991.0 -2013-07-11 05:00:00,9778.0 -2013-07-11 06:00:00,9840.0 -2013-07-11 07:00:00,10160.0 -2013-07-11 08:00:00,10994.0 -2013-07-11 09:00:00,12128.0 -2013-07-11 10:00:00,13069.0 -2013-07-11 11:00:00,13766.0 -2013-07-11 12:00:00,14375.0 -2013-07-11 13:00:00,14790.0 -2013-07-11 14:00:00,15104.0 -2013-07-11 15:00:00,15468.0 -2013-07-11 16:00:00,15724.0 -2013-07-11 17:00:00,15950.0 -2013-07-11 18:00:00,16049.0 -2013-07-11 19:00:00,15866.0 -2013-07-11 20:00:00,15275.0 -2013-07-11 21:00:00,14555.0 -2013-07-11 22:00:00,13956.0 -2013-07-11 23:00:00,13620.0 -2013-07-12 00:00:00,12555.0 -2013-07-10 01:00:00,14662.0 -2013-07-10 02:00:00,13599.0 -2013-07-10 03:00:00,12915.0 -2013-07-10 04:00:00,12366.0 -2013-07-10 05:00:00,12070.0 -2013-07-10 06:00:00,12142.0 -2013-07-10 07:00:00,12545.0 -2013-07-10 08:00:00,13616.0 -2013-07-10 09:00:00,14929.0 -2013-07-10 10:00:00,16060.0 -2013-07-10 11:00:00,16920.0 -2013-07-10 12:00:00,17676.0 -2013-07-10 13:00:00,18130.0 -2013-07-10 14:00:00,18381.0 -2013-07-10 15:00:00,18538.0 -2013-07-10 16:00:00,18477.0 -2013-07-10 17:00:00,18339.0 -2013-07-10 18:00:00,18109.0 -2013-07-10 19:00:00,17613.0 -2013-07-10 20:00:00,16797.0 -2013-07-10 21:00:00,15751.0 -2013-07-10 22:00:00,15059.0 -2013-07-10 23:00:00,14531.0 -2013-07-11 00:00:00,13326.0 -2013-07-09 01:00:00,14054.0 -2013-07-09 02:00:00,12955.0 -2013-07-09 03:00:00,12177.0 -2013-07-09 04:00:00,11638.0 -2013-07-09 05:00:00,11315.0 -2013-07-09 06:00:00,11353.0 -2013-07-09 07:00:00,11765.0 -2013-07-09 08:00:00,12824.0 -2013-07-09 09:00:00,14203.0 -2013-07-09 10:00:00,15322.0 -2013-07-09 11:00:00,16274.0 -2013-07-09 12:00:00,17352.0 -2013-07-09 13:00:00,17787.0 -2013-07-09 14:00:00,17623.0 -2013-07-09 15:00:00,17470.0 -2013-07-09 16:00:00,17415.0 -2013-07-09 17:00:00,17199.0 -2013-07-09 18:00:00,17536.0 -2013-07-09 19:00:00,17992.0 -2013-07-09 20:00:00,17861.0 -2013-07-09 21:00:00,17533.0 -2013-07-09 22:00:00,17342.0 -2013-07-09 23:00:00,17128.0 -2013-07-10 00:00:00,15957.0 -2013-07-08 01:00:00,13514.0 -2013-07-08 02:00:00,12587.0 -2013-07-08 03:00:00,11939.0 -2013-07-08 04:00:00,11582.0 -2013-07-08 05:00:00,11366.0 -2013-07-08 06:00:00,11485.0 -2013-07-08 07:00:00,12025.0 -2013-07-08 08:00:00,13047.0 -2013-07-08 09:00:00,13964.0 -2013-07-08 10:00:00,14382.0 -2013-07-08 11:00:00,14694.0 -2013-07-08 12:00:00,14899.0 -2013-07-08 13:00:00,15038.0 -2013-07-08 14:00:00,15305.0 -2013-07-08 15:00:00,16172.0 -2013-07-08 16:00:00,17111.0 -2013-07-08 17:00:00,17786.0 -2013-07-08 18:00:00,18074.0 -2013-07-08 19:00:00,18244.0 -2013-07-08 20:00:00,18029.0 -2013-07-08 21:00:00,17569.0 -2013-07-08 22:00:00,17171.0 -2013-07-08 23:00:00,16709.0 -2013-07-09 00:00:00,15462.0 -2013-07-07 01:00:00,11669.0 -2013-07-07 02:00:00,10920.0 -2013-07-07 03:00:00,10309.0 -2013-07-07 04:00:00,9906.0 -2013-07-07 05:00:00,9611.0 -2013-07-07 06:00:00,9486.0 -2013-07-07 07:00:00,9283.0 -2013-07-07 08:00:00,9197.0 -2013-07-07 09:00:00,9761.0 -2013-07-07 10:00:00,10709.0 -2013-07-07 11:00:00,11771.0 -2013-07-07 12:00:00,12745.0 -2013-07-07 13:00:00,13640.0 -2013-07-07 14:00:00,14522.0 -2013-07-07 15:00:00,15234.0 -2013-07-07 16:00:00,15805.0 -2013-07-07 17:00:00,16179.0 -2013-07-07 18:00:00,16194.0 -2013-07-07 19:00:00,16258.0 -2013-07-07 20:00:00,16051.0 -2013-07-07 21:00:00,15753.0 -2013-07-07 22:00:00,15674.0 -2013-07-07 23:00:00,15483.0 -2013-07-08 00:00:00,14605.0 -2013-07-06 01:00:00,11780.0 -2013-07-06 02:00:00,10849.0 -2013-07-06 03:00:00,10232.0 -2013-07-06 04:00:00,9730.0 -2013-07-06 05:00:00,9431.0 -2013-07-06 06:00:00,9273.0 -2013-07-06 07:00:00,9177.0 -2013-07-06 08:00:00,9397.0 -2013-07-06 09:00:00,10264.0 -2013-07-06 10:00:00,11325.0 -2013-07-06 11:00:00,12426.0 -2013-07-06 12:00:00,13324.0 -2013-07-06 13:00:00,13995.0 -2013-07-06 14:00:00,14361.0 -2013-07-06 15:00:00,14437.0 -2013-07-06 16:00:00,14415.0 -2013-07-06 17:00:00,14296.0 -2013-07-06 18:00:00,14174.0 -2013-07-06 19:00:00,13948.0 -2013-07-06 20:00:00,13613.0 -2013-07-06 21:00:00,13224.0 -2013-07-06 22:00:00,13205.0 -2013-07-06 23:00:00,13075.0 -2013-07-07 00:00:00,12449.0 -2013-07-05 01:00:00,10426.0 -2013-07-05 02:00:00,9718.0 -2013-07-05 03:00:00,9157.0 -2013-07-05 04:00:00,8741.0 -2013-07-05 05:00:00,8509.0 -2013-07-05 06:00:00,8538.0 -2013-07-05 07:00:00,8716.0 -2013-07-05 08:00:00,9309.0 -2013-07-05 09:00:00,10277.0 -2013-07-05 10:00:00,11327.0 -2013-07-05 11:00:00,12371.0 -2013-07-05 12:00:00,13285.0 -2013-07-05 13:00:00,14017.0 -2013-07-05 14:00:00,14535.0 -2013-07-05 15:00:00,14946.0 -2013-07-05 16:00:00,15228.0 -2013-07-05 17:00:00,15475.0 -2013-07-05 18:00:00,15638.0 -2013-07-05 19:00:00,15498.0 -2013-07-05 20:00:00,14992.0 -2013-07-05 21:00:00,14362.0 -2013-07-05 22:00:00,13971.0 -2013-07-05 23:00:00,13774.0 -2013-07-06 00:00:00,12795.0 -2013-07-04 01:00:00,9828.0 -2013-07-04 02:00:00,9197.0 -2013-07-04 03:00:00,8751.0 -2013-07-04 04:00:00,8399.0 -2013-07-04 05:00:00,8197.0 -2013-07-04 06:00:00,8139.0 -2013-07-04 07:00:00,7991.0 -2013-07-04 08:00:00,8005.0 -2013-07-04 09:00:00,8357.0 -2013-07-04 10:00:00,8921.0 -2013-07-04 11:00:00,9605.0 -2013-07-04 12:00:00,10273.0 -2013-07-04 13:00:00,10861.0 -2013-07-04 14:00:00,11348.0 -2013-07-04 15:00:00,11729.0 -2013-07-04 16:00:00,11982.0 -2013-07-04 17:00:00,12136.0 -2013-07-04 18:00:00,12282.0 -2013-07-04 19:00:00,12276.0 -2013-07-04 20:00:00,12017.0 -2013-07-04 21:00:00,11574.0 -2013-07-04 22:00:00,11405.0 -2013-07-04 23:00:00,11303.0 -2013-07-05 00:00:00,10952.0 -2013-07-03 01:00:00,9654.0 -2013-07-03 02:00:00,9066.0 -2013-07-03 03:00:00,8695.0 -2013-07-03 04:00:00,8454.0 -2013-07-03 05:00:00,8389.0 -2013-07-03 06:00:00,8522.0 -2013-07-03 07:00:00,9004.0 -2013-07-03 08:00:00,9805.0 -2013-07-03 09:00:00,10605.0 -2013-07-03 10:00:00,11157.0 -2013-07-03 11:00:00,11538.0 -2013-07-03 12:00:00,11845.0 -2013-07-03 13:00:00,11967.0 -2013-07-03 14:00:00,12023.0 -2013-07-03 15:00:00,12170.0 -2013-07-03 16:00:00,12184.0 -2013-07-03 17:00:00,12096.0 -2013-07-03 18:00:00,11941.0 -2013-07-03 19:00:00,11800.0 -2013-07-03 20:00:00,11458.0 -2013-07-03 21:00:00,11225.0 -2013-07-03 22:00:00,11284.0 -2013-07-03 23:00:00,11270.0 -2013-07-04 00:00:00,10596.0 -2013-07-02 01:00:00,10081.0 -2013-07-02 02:00:00,9380.0 -2013-07-02 03:00:00,8934.0 -2013-07-02 04:00:00,8637.0 -2013-07-02 05:00:00,8504.0 -2013-07-02 06:00:00,8637.0 -2013-07-02 07:00:00,9019.0 -2013-07-02 08:00:00,9735.0 -2013-07-02 09:00:00,10573.0 -2013-07-02 10:00:00,11148.0 -2013-07-02 11:00:00,11557.0 -2013-07-02 12:00:00,11850.0 -2013-07-02 13:00:00,11930.0 -2013-07-02 14:00:00,12030.0 -2013-07-02 15:00:00,12192.0 -2013-07-02 16:00:00,12154.0 -2013-07-02 17:00:00,12012.0 -2013-07-02 18:00:00,11793.0 -2013-07-02 19:00:00,11580.0 -2013-07-02 20:00:00,11224.0 -2013-07-02 21:00:00,11050.0 -2013-07-02 22:00:00,11237.0 -2013-07-02 23:00:00,11195.0 -2013-07-03 00:00:00,10445.0 -2013-07-01 01:00:00,9372.0 -2013-07-01 02:00:00,8872.0 -2013-07-01 03:00:00,8576.0 -2013-07-01 04:00:00,8392.0 -2013-07-01 05:00:00,8339.0 -2013-07-01 06:00:00,8515.0 -2013-07-01 07:00:00,8923.0 -2013-07-01 08:00:00,9744.0 -2013-07-01 09:00:00,10765.0 -2013-07-01 10:00:00,11473.0 -2013-07-01 11:00:00,11995.0 -2013-07-01 12:00:00,12430.0 -2013-07-01 13:00:00,12651.0 -2013-07-01 14:00:00,12845.0 -2013-07-01 15:00:00,13037.0 -2013-07-01 16:00:00,13115.0 -2013-07-01 17:00:00,13140.0 -2013-07-01 18:00:00,13050.0 -2013-07-01 19:00:00,12836.0 -2013-07-01 20:00:00,12410.0 -2013-07-01 21:00:00,11971.0 -2013-07-01 22:00:00,11861.0 -2013-07-01 23:00:00,11833.0 -2013-07-02 00:00:00,10997.0 -2013-06-30 01:00:00,9582.0 -2013-06-30 02:00:00,9065.0 -2013-06-30 03:00:00,8641.0 -2013-06-30 04:00:00,8464.0 -2013-06-30 05:00:00,8266.0 -2013-06-30 06:00:00,8215.0 -2013-06-30 07:00:00,8093.0 -2013-06-30 08:00:00,8067.0 -2013-06-30 09:00:00,8447.0 -2013-06-30 10:00:00,9020.0 -2013-06-30 11:00:00,9634.0 -2013-06-30 12:00:00,10174.0 -2013-06-30 13:00:00,10600.0 -2013-06-30 14:00:00,10929.0 -2013-06-30 15:00:00,11139.0 -2013-06-30 16:00:00,11341.0 -2013-06-30 17:00:00,11427.0 -2013-06-30 18:00:00,11457.0 -2013-06-30 19:00:00,11371.0 -2013-06-30 20:00:00,11030.0 -2013-06-30 21:00:00,10613.0 -2013-06-30 22:00:00,10600.0 -2013-06-30 23:00:00,10668.0 -2013-07-01 00:00:00,10116.0 -2013-06-29 01:00:00,11434.0 -2013-06-29 02:00:00,10570.0 -2013-06-29 03:00:00,9979.0 -2013-06-29 04:00:00,9519.0 -2013-06-29 05:00:00,9280.0 -2013-06-29 06:00:00,9173.0 -2013-06-29 07:00:00,9261.0 -2013-06-29 08:00:00,9425.0 -2013-06-29 09:00:00,9928.0 -2013-06-29 10:00:00,10466.0 -2013-06-29 11:00:00,10921.0 -2013-06-29 12:00:00,11187.0 -2013-06-29 13:00:00,11321.0 -2013-06-29 14:00:00,11284.0 -2013-06-29 15:00:00,11166.0 -2013-06-29 16:00:00,11121.0 -2013-06-29 17:00:00,11118.0 -2013-06-29 18:00:00,11126.0 -2013-06-29 19:00:00,11074.0 -2013-06-29 20:00:00,10769.0 -2013-06-29 21:00:00,10544.0 -2013-06-29 22:00:00,10650.0 -2013-06-29 23:00:00,10704.0 -2013-06-30 00:00:00,10228.0 -2013-06-28 01:00:00,13005.0 -2013-06-28 02:00:00,11860.0 -2013-06-28 03:00:00,11075.0 -2013-06-28 04:00:00,10529.0 -2013-06-28 05:00:00,10253.0 -2013-06-28 06:00:00,10220.0 -2013-06-28 07:00:00,10501.0 -2013-06-28 08:00:00,11497.0 -2013-06-28 09:00:00,12682.0 -2013-06-28 10:00:00,13653.0 -2013-06-28 11:00:00,14539.0 -2013-06-28 12:00:00,15290.0 -2013-06-28 13:00:00,15816.0 -2013-06-28 14:00:00,16115.0 -2013-06-28 15:00:00,16406.0 -2013-06-28 16:00:00,16253.0 -2013-06-28 17:00:00,15666.0 -2013-06-28 18:00:00,15296.0 -2013-06-28 19:00:00,15054.0 -2013-06-28 20:00:00,14489.0 -2013-06-28 21:00:00,13862.0 -2013-06-28 22:00:00,13506.0 -2013-06-28 23:00:00,13308.0 -2013-06-29 00:00:00,12472.0 -2013-06-27 01:00:00,11982.0 -2013-06-27 02:00:00,10981.0 -2013-06-27 03:00:00,10324.0 -2013-06-27 04:00:00,9896.0 -2013-06-27 05:00:00,9729.0 -2013-06-27 06:00:00,9840.0 -2013-06-27 07:00:00,10197.0 -2013-06-27 08:00:00,11337.0 -2013-06-27 09:00:00,12637.0 -2013-06-27 10:00:00,13878.0 -2013-06-27 11:00:00,15041.0 -2013-06-27 12:00:00,16195.0 -2013-06-27 13:00:00,17064.0 -2013-06-27 14:00:00,17784.0 -2013-06-27 15:00:00,18418.0 -2013-06-27 16:00:00,18832.0 -2013-06-27 17:00:00,19173.0 -2013-06-27 18:00:00,19232.0 -2013-06-27 19:00:00,18539.0 -2013-06-27 20:00:00,17337.0 -2013-06-27 21:00:00,16644.0 -2013-06-27 22:00:00,15994.0 -2013-06-27 23:00:00,15619.0 -2013-06-28 00:00:00,14434.0 -2013-06-26 01:00:00,12543.0 -2013-06-26 02:00:00,11561.0 -2013-06-26 03:00:00,10848.0 -2013-06-26 04:00:00,10389.0 -2013-06-26 05:00:00,10177.0 -2013-06-26 06:00:00,10313.0 -2013-06-26 07:00:00,10856.0 -2013-06-26 08:00:00,11843.0 -2013-06-26 09:00:00,12641.0 -2013-06-26 10:00:00,13026.0 -2013-06-26 11:00:00,13362.0 -2013-06-26 12:00:00,13690.0 -2013-06-26 13:00:00,14032.0 -2013-06-26 14:00:00,14488.0 -2013-06-26 15:00:00,15092.0 -2013-06-26 16:00:00,15294.0 -2013-06-26 17:00:00,15422.0 -2013-06-26 18:00:00,15554.0 -2013-06-26 19:00:00,15599.0 -2013-06-26 20:00:00,15268.0 -2013-06-26 21:00:00,14877.0 -2013-06-26 22:00:00,14607.0 -2013-06-26 23:00:00,14335.0 -2013-06-27 00:00:00,13272.0 -2013-06-25 01:00:00,11589.0 -2013-06-25 02:00:00,10745.0 -2013-06-25 03:00:00,10202.0 -2013-06-25 04:00:00,9907.0 -2013-06-25 05:00:00,9836.0 -2013-06-25 06:00:00,9904.0 -2013-06-25 07:00:00,10404.0 -2013-06-25 08:00:00,11331.0 -2013-06-25 09:00:00,12348.0 -2013-06-25 10:00:00,13091.0 -2013-06-25 11:00:00,13718.0 -2013-06-25 12:00:00,14214.0 -2013-06-25 13:00:00,14473.0 -2013-06-25 14:00:00,15080.0 -2013-06-25 15:00:00,16056.0 -2013-06-25 16:00:00,16647.0 -2013-06-25 17:00:00,16814.0 -2013-06-25 18:00:00,16985.0 -2013-06-25 19:00:00,16614.0 -2013-06-25 20:00:00,16134.0 -2013-06-25 21:00:00,15735.0 -2013-06-25 22:00:00,15462.0 -2013-06-25 23:00:00,15024.0 -2013-06-26 00:00:00,13858.0 -2013-06-24 01:00:00,12095.0 -2013-06-24 02:00:00,11312.0 -2013-06-24 03:00:00,10788.0 -2013-06-24 04:00:00,10388.0 -2013-06-24 05:00:00,10214.0 -2013-06-24 06:00:00,10370.0 -2013-06-24 07:00:00,10908.0 -2013-06-24 08:00:00,11965.0 -2013-06-24 09:00:00,12906.0 -2013-06-24 10:00:00,13416.0 -2013-06-24 11:00:00,13744.0 -2013-06-24 12:00:00,13989.0 -2013-06-24 13:00:00,14477.0 -2013-06-24 14:00:00,15128.0 -2013-06-24 15:00:00,16017.0 -2013-06-24 16:00:00,16747.0 -2013-06-24 17:00:00,17370.0 -2013-06-24 18:00:00,17702.0 -2013-06-24 19:00:00,17257.0 -2013-06-24 20:00:00,15189.0 -2013-06-24 21:00:00,14263.0 -2013-06-24 22:00:00,13899.0 -2013-06-24 23:00:00,13476.0 -2013-06-25 00:00:00,12618.0 -2013-06-23 01:00:00,11289.0 -2013-06-23 02:00:00,10483.0 -2013-06-23 03:00:00,9831.0 -2013-06-23 04:00:00,9414.0 -2013-06-23 05:00:00,9173.0 -2013-06-23 06:00:00,9074.0 -2013-06-23 07:00:00,8933.0 -2013-06-23 08:00:00,8904.0 -2013-06-23 09:00:00,9545.0 -2013-06-23 10:00:00,10616.0 -2013-06-23 11:00:00,11722.0 -2013-06-23 12:00:00,12687.0 -2013-06-23 13:00:00,13604.0 -2013-06-23 14:00:00,14347.0 -2013-06-23 15:00:00,15067.0 -2013-06-23 16:00:00,15655.0 -2013-06-23 17:00:00,16017.0 -2013-06-23 18:00:00,15918.0 -2013-06-23 19:00:00,15361.0 -2013-06-23 20:00:00,14565.0 -2013-06-23 21:00:00,14065.0 -2013-06-23 22:00:00,13926.0 -2013-06-23 23:00:00,13836.0 -2013-06-24 00:00:00,13044.0 -2013-06-22 01:00:00,11614.0 -2013-06-22 02:00:00,10911.0 -2013-06-22 03:00:00,10370.0 -2013-06-22 04:00:00,10021.0 -2013-06-22 05:00:00,9684.0 -2013-06-22 06:00:00,9666.0 -2013-06-22 07:00:00,9772.0 -2013-06-22 08:00:00,10039.0 -2013-06-22 09:00:00,10559.0 -2013-06-22 10:00:00,11233.0 -2013-06-22 11:00:00,11971.0 -2013-06-22 12:00:00,12548.0 -2013-06-22 13:00:00,13117.0 -2013-06-22 14:00:00,13444.0 -2013-06-22 15:00:00,13393.0 -2013-06-22 16:00:00,13255.0 -2013-06-22 17:00:00,13294.0 -2013-06-22 18:00:00,13368.0 -2013-06-22 19:00:00,13440.0 -2013-06-22 20:00:00,13404.0 -2013-06-22 21:00:00,13129.0 -2013-06-22 22:00:00,13037.0 -2013-06-22 23:00:00,12937.0 -2013-06-23 00:00:00,12140.0 -2013-06-21 01:00:00,12415.0 -2013-06-21 02:00:00,11432.0 -2013-06-21 03:00:00,10729.0 -2013-06-21 04:00:00,10298.0 -2013-06-21 05:00:00,10056.0 -2013-06-21 06:00:00,10102.0 -2013-06-21 07:00:00,10435.0 -2013-06-21 08:00:00,11517.0 -2013-06-21 09:00:00,12801.0 -2013-06-21 10:00:00,13971.0 -2013-06-21 11:00:00,14879.0 -2013-06-21 12:00:00,15551.0 -2013-06-21 13:00:00,15800.0 -2013-06-21 14:00:00,15592.0 -2013-06-21 15:00:00,14999.0 -2013-06-21 16:00:00,14374.0 -2013-06-21 17:00:00,13850.0 -2013-06-21 18:00:00,13641.0 -2013-06-21 19:00:00,13444.0 -2013-06-21 20:00:00,13169.0 -2013-06-21 21:00:00,12950.0 -2013-06-21 22:00:00,13061.0 -2013-06-21 23:00:00,13124.0 -2013-06-22 00:00:00,12464.0 -2013-06-20 01:00:00,10216.0 -2013-06-20 02:00:00,9476.0 -2013-06-20 03:00:00,8996.0 -2013-06-20 04:00:00,8729.0 -2013-06-20 05:00:00,8606.0 -2013-06-20 06:00:00,8760.0 -2013-06-20 07:00:00,9069.0 -2013-06-20 08:00:00,10033.0 -2013-06-20 09:00:00,11153.0 -2013-06-20 10:00:00,11918.0 -2013-06-20 11:00:00,12740.0 -2013-06-20 12:00:00,13574.0 -2013-06-20 13:00:00,14281.0 -2013-06-20 14:00:00,14830.0 -2013-06-20 15:00:00,15399.0 -2013-06-20 16:00:00,15797.0 -2013-06-20 17:00:00,16140.0 -2013-06-20 18:00:00,16336.0 -2013-06-20 19:00:00,16321.0 -2013-06-20 20:00:00,15906.0 -2013-06-20 21:00:00,15367.0 -2013-06-20 22:00:00,15022.0 -2013-06-20 23:00:00,14686.0 -2013-06-21 00:00:00,13638.0 -2013-06-19 01:00:00,9838.0 -2013-06-19 02:00:00,9169.0 -2013-06-19 03:00:00,8739.0 -2013-06-19 04:00:00,8466.0 -2013-06-19 05:00:00,8369.0 -2013-06-19 06:00:00,8493.0 -2013-06-19 07:00:00,8839.0 -2013-06-19 08:00:00,9711.0 -2013-06-19 09:00:00,10658.0 -2013-06-19 10:00:00,11351.0 -2013-06-19 11:00:00,11858.0 -2013-06-19 12:00:00,12308.0 -2013-06-19 13:00:00,12645.0 -2013-06-19 14:00:00,12848.0 -2013-06-19 15:00:00,13084.0 -2013-06-19 16:00:00,13208.0 -2013-06-19 17:00:00,13311.0 -2013-06-19 18:00:00,13316.0 -2013-06-19 19:00:00,13211.0 -2013-06-19 20:00:00,12842.0 -2013-06-19 21:00:00,12428.0 -2013-06-19 22:00:00,12215.0 -2013-06-19 23:00:00,12027.0 -2013-06-20 00:00:00,11209.0 -2013-06-18 01:00:00,11067.0 -2013-06-18 02:00:00,10154.0 -2013-06-18 03:00:00,9532.0 -2013-06-18 04:00:00,9088.0 -2013-06-18 05:00:00,8918.0 -2013-06-18 06:00:00,8949.0 -2013-06-18 07:00:00,9275.0 -2013-06-18 08:00:00,10057.0 -2013-06-18 09:00:00,10858.0 -2013-06-18 10:00:00,11387.0 -2013-06-18 11:00:00,11718.0 -2013-06-18 12:00:00,12007.0 -2013-06-18 13:00:00,12259.0 -2013-06-18 14:00:00,12461.0 -2013-06-18 15:00:00,12673.0 -2013-06-18 16:00:00,12753.0 -2013-06-18 17:00:00,12790.0 -2013-06-18 18:00:00,12753.0 -2013-06-18 19:00:00,12552.0 -2013-06-18 20:00:00,12074.0 -2013-06-18 21:00:00,11641.0 -2013-06-18 22:00:00,11552.0 -2013-06-18 23:00:00,11460.0 -2013-06-19 00:00:00,10689.0 -2013-06-17 01:00:00,11355.0 -2013-06-17 02:00:00,10500.0 -2013-06-17 03:00:00,9863.0 -2013-06-17 04:00:00,9449.0 -2013-06-17 05:00:00,9233.0 -2013-06-17 06:00:00,9352.0 -2013-06-17 07:00:00,9684.0 -2013-06-17 08:00:00,10860.0 -2013-06-17 09:00:00,12215.0 -2013-06-17 10:00:00,13368.0 -2013-06-17 11:00:00,14258.0 -2013-06-17 12:00:00,15087.0 -2013-06-17 13:00:00,15769.0 -2013-06-17 14:00:00,16312.0 -2013-06-17 15:00:00,16846.0 -2013-06-17 16:00:00,17160.0 -2013-06-17 17:00:00,17401.0 -2013-06-17 18:00:00,17430.0 -2013-06-17 19:00:00,17089.0 -2013-06-17 20:00:00,16384.0 -2013-06-17 21:00:00,15511.0 -2013-06-17 22:00:00,14683.0 -2013-06-17 23:00:00,13863.0 -2013-06-18 00:00:00,12375.0 -2013-06-16 01:00:00,10923.0 -2013-06-16 02:00:00,10221.0 -2013-06-16 03:00:00,9463.0 -2013-06-16 04:00:00,9068.0 -2013-06-16 05:00:00,8780.0 -2013-06-16 06:00:00,8584.0 -2013-06-16 07:00:00,8370.0 -2013-06-16 08:00:00,8489.0 -2013-06-16 09:00:00,9114.0 -2013-06-16 10:00:00,10003.0 -2013-06-16 11:00:00,10854.0 -2013-06-16 12:00:00,11682.0 -2013-06-16 13:00:00,12363.0 -2013-06-16 14:00:00,12812.0 -2013-06-16 15:00:00,13192.0 -2013-06-16 16:00:00,13528.0 -2013-06-16 17:00:00,13842.0 -2013-06-16 18:00:00,14011.0 -2013-06-16 19:00:00,14062.0 -2013-06-16 20:00:00,13857.0 -2013-06-16 21:00:00,13480.0 -2013-06-16 22:00:00,13291.0 -2013-06-16 23:00:00,13245.0 -2013-06-17 00:00:00,12424.0 -2013-06-15 01:00:00,9841.0 -2013-06-15 02:00:00,9119.0 -2013-06-15 03:00:00,8653.0 -2013-06-15 04:00:00,8444.0 -2013-06-15 05:00:00,8334.0 -2013-06-15 06:00:00,8353.0 -2013-06-15 07:00:00,8439.0 -2013-06-15 08:00:00,8664.0 -2013-06-15 09:00:00,9190.0 -2013-06-15 10:00:00,9798.0 -2013-06-15 11:00:00,10333.0 -2013-06-15 12:00:00,10641.0 -2013-06-15 13:00:00,10811.0 -2013-06-15 14:00:00,10959.0 -2013-06-15 15:00:00,11027.0 -2013-06-15 16:00:00,11152.0 -2013-06-15 17:00:00,11409.0 -2013-06-15 18:00:00,11785.0 -2013-06-15 19:00:00,12057.0 -2013-06-15 20:00:00,11994.0 -2013-06-15 21:00:00,11907.0 -2013-06-15 22:00:00,12132.0 -2013-06-15 23:00:00,12210.0 -2013-06-16 00:00:00,11701.0 -2013-06-14 01:00:00,9964.0 -2013-06-14 02:00:00,9270.0 -2013-06-14 03:00:00,8784.0 -2013-06-14 04:00:00,8554.0 -2013-06-14 05:00:00,8401.0 -2013-06-14 06:00:00,8535.0 -2013-06-14 07:00:00,8785.0 -2013-06-14 08:00:00,9606.0 -2013-06-14 09:00:00,10586.0 -2013-06-14 10:00:00,11241.0 -2013-06-14 11:00:00,11743.0 -2013-06-14 12:00:00,12162.0 -2013-06-14 13:00:00,12425.0 -2013-06-14 14:00:00,12605.0 -2013-06-14 15:00:00,12891.0 -2013-06-14 16:00:00,13071.0 -2013-06-14 17:00:00,13193.0 -2013-06-14 18:00:00,13207.0 -2013-06-14 19:00:00,13008.0 -2013-06-14 20:00:00,12492.0 -2013-06-14 21:00:00,11812.0 -2013-06-14 22:00:00,11644.0 -2013-06-14 23:00:00,11502.0 -2013-06-15 00:00:00,10722.0 -2013-06-13 01:00:00,10963.0 -2013-06-13 02:00:00,10085.0 -2013-06-13 03:00:00,9504.0 -2013-06-13 04:00:00,9172.0 -2013-06-13 05:00:00,9017.0 -2013-06-13 06:00:00,9121.0 -2013-06-13 07:00:00,9471.0 -2013-06-13 08:00:00,10220.0 -2013-06-13 09:00:00,11240.0 -2013-06-13 10:00:00,11987.0 -2013-06-13 11:00:00,12479.0 -2013-06-13 12:00:00,12875.0 -2013-06-13 13:00:00,13100.0 -2013-06-13 14:00:00,13270.0 -2013-06-13 15:00:00,13495.0 -2013-06-13 16:00:00,13563.0 -2013-06-13 17:00:00,13512.0 -2013-06-13 18:00:00,13447.0 -2013-06-13 19:00:00,13215.0 -2013-06-13 20:00:00,12648.0 -2013-06-13 21:00:00,12097.0 -2013-06-13 22:00:00,11939.0 -2013-06-13 23:00:00,11826.0 -2013-06-14 00:00:00,10966.0 -2013-06-12 01:00:00,12011.0 -2013-06-12 02:00:00,11036.0 -2013-06-12 03:00:00,10383.0 -2013-06-12 04:00:00,9943.0 -2013-06-12 05:00:00,9755.0 -2013-06-12 06:00:00,9800.0 -2013-06-12 07:00:00,10276.0 -2013-06-12 08:00:00,11203.0 -2013-06-12 09:00:00,12172.0 -2013-06-12 10:00:00,12788.0 -2013-06-12 11:00:00,13272.0 -2013-06-12 12:00:00,13761.0 -2013-06-12 13:00:00,14148.0 -2013-06-12 14:00:00,14643.0 -2013-06-12 15:00:00,15405.0 -2013-06-12 16:00:00,15863.0 -2013-06-12 17:00:00,15890.0 -2013-06-12 18:00:00,15698.0 -2013-06-12 19:00:00,15149.0 -2013-06-12 20:00:00,14590.0 -2013-06-12 21:00:00,14176.0 -2013-06-12 22:00:00,13842.0 -2013-06-12 23:00:00,13047.0 -2013-06-13 00:00:00,11989.0 -2013-06-11 01:00:00,10567.0 -2013-06-11 02:00:00,9813.0 -2013-06-11 03:00:00,9321.0 -2013-06-11 04:00:00,8989.0 -2013-06-11 05:00:00,8860.0 -2013-06-11 06:00:00,8978.0 -2013-06-11 07:00:00,9345.0 -2013-06-11 08:00:00,10421.0 -2013-06-11 09:00:00,11617.0 -2013-06-11 10:00:00,12561.0 -2013-06-11 11:00:00,13305.0 -2013-06-11 12:00:00,14022.0 -2013-06-11 13:00:00,14569.0 -2013-06-11 14:00:00,15005.0 -2013-06-11 15:00:00,15483.0 -2013-06-11 16:00:00,15712.0 -2013-06-11 17:00:00,15735.0 -2013-06-11 18:00:00,15290.0 -2013-06-11 19:00:00,15182.0 -2013-06-11 20:00:00,14971.0 -2013-06-11 21:00:00,14588.0 -2013-06-11 22:00:00,14573.0 -2013-06-11 23:00:00,14280.0 -2013-06-12 00:00:00,13244.0 -2013-06-10 01:00:00,9803.0 -2013-06-10 02:00:00,9244.0 -2013-06-10 03:00:00,8870.0 -2013-06-10 04:00:00,8618.0 -2013-06-10 05:00:00,8531.0 -2013-06-10 06:00:00,8715.0 -2013-06-10 07:00:00,9207.0 -2013-06-10 08:00:00,10173.0 -2013-06-10 09:00:00,11274.0 -2013-06-10 10:00:00,12020.0 -2013-06-10 11:00:00,12667.0 -2013-06-10 12:00:00,13173.0 -2013-06-10 13:00:00,13490.0 -2013-06-10 14:00:00,13636.0 -2013-06-10 15:00:00,13804.0 -2013-06-10 16:00:00,13892.0 -2013-06-10 17:00:00,13847.0 -2013-06-10 18:00:00,13712.0 -2013-06-10 19:00:00,13562.0 -2013-06-10 20:00:00,13239.0 -2013-06-10 21:00:00,12861.0 -2013-06-10 22:00:00,12814.0 -2013-06-10 23:00:00,12561.0 -2013-06-11 00:00:00,11586.0 -2013-06-09 01:00:00,8935.0 -2013-06-09 02:00:00,8350.0 -2013-06-09 03:00:00,8036.0 -2013-06-09 04:00:00,7803.0 -2013-06-09 05:00:00,7672.0 -2013-06-09 06:00:00,7632.0 -2013-06-09 07:00:00,7485.0 -2013-06-09 08:00:00,7523.0 -2013-06-09 09:00:00,7959.0 -2013-06-09 10:00:00,8519.0 -2013-06-09 11:00:00,9154.0 -2013-06-09 12:00:00,9694.0 -2013-06-09 13:00:00,10089.0 -2013-06-09 14:00:00,10358.0 -2013-06-09 15:00:00,10529.0 -2013-06-09 16:00:00,10625.0 -2013-06-09 17:00:00,10593.0 -2013-06-09 18:00:00,10705.0 -2013-06-09 19:00:00,10687.0 -2013-06-09 20:00:00,10634.0 -2013-06-09 21:00:00,10642.0 -2013-06-09 22:00:00,11029.0 -2013-06-09 23:00:00,11061.0 -2013-06-10 00:00:00,10546.0 -2013-06-08 01:00:00,9252.0 -2013-06-08 02:00:00,8636.0 -2013-06-08 03:00:00,8264.0 -2013-06-08 04:00:00,8008.0 -2013-06-08 05:00:00,7867.0 -2013-06-08 06:00:00,7908.0 -2013-06-08 07:00:00,7846.0 -2013-06-08 08:00:00,8120.0 -2013-06-08 09:00:00,8662.0 -2013-06-08 10:00:00,9246.0 -2013-06-08 11:00:00,9688.0 -2013-06-08 12:00:00,10017.0 -2013-06-08 13:00:00,10232.0 -2013-06-08 14:00:00,10271.0 -2013-06-08 15:00:00,10260.0 -2013-06-08 16:00:00,10204.0 -2013-06-08 17:00:00,10201.0 -2013-06-08 18:00:00,10160.0 -2013-06-08 19:00:00,10056.0 -2013-06-08 20:00:00,9903.0 -2013-06-08 21:00:00,9747.0 -2013-06-08 22:00:00,10024.0 -2013-06-08 23:00:00,10016.0 -2013-06-09 00:00:00,9542.0 -2013-06-07 01:00:00,9408.0 -2013-06-07 02:00:00,8785.0 -2013-06-07 03:00:00,8440.0 -2013-06-07 04:00:00,8208.0 -2013-06-07 05:00:00,8135.0 -2013-06-07 06:00:00,8283.0 -2013-06-07 07:00:00,8641.0 -2013-06-07 08:00:00,9412.0 -2013-06-07 09:00:00,10254.0 -2013-06-07 10:00:00,10728.0 -2013-06-07 11:00:00,11034.0 -2013-06-07 12:00:00,11310.0 -2013-06-07 13:00:00,11422.0 -2013-06-07 14:00:00,11461.0 -2013-06-07 15:00:00,11515.0 -2013-06-07 16:00:00,11477.0 -2013-06-07 17:00:00,11344.0 -2013-06-07 18:00:00,11252.0 -2013-06-07 19:00:00,11077.0 -2013-06-07 20:00:00,10818.0 -2013-06-07 21:00:00,10556.0 -2013-06-07 22:00:00,10649.0 -2013-06-07 23:00:00,10685.0 -2013-06-08 00:00:00,10000.0 -2013-06-06 01:00:00,9480.0 -2013-06-06 02:00:00,8869.0 -2013-06-06 03:00:00,8500.0 -2013-06-06 04:00:00,8298.0 -2013-06-06 05:00:00,8235.0 -2013-06-06 06:00:00,8377.0 -2013-06-06 07:00:00,8870.0 -2013-06-06 08:00:00,9710.0 -2013-06-06 09:00:00,10573.0 -2013-06-06 10:00:00,11058.0 -2013-06-06 11:00:00,11393.0 -2013-06-06 12:00:00,11589.0 -2013-06-06 13:00:00,11589.0 -2013-06-06 14:00:00,11584.0 -2013-06-06 15:00:00,11631.0 -2013-06-06 16:00:00,11518.0 -2013-06-06 17:00:00,11406.0 -2013-06-06 18:00:00,11289.0 -2013-06-06 19:00:00,11094.0 -2013-06-06 20:00:00,10813.0 -2013-06-06 21:00:00,10633.0 -2013-06-06 22:00:00,10901.0 -2013-06-06 23:00:00,10850.0 -2013-06-07 00:00:00,10245.0 -2013-06-05 01:00:00,9496.0 -2013-06-05 02:00:00,8902.0 -2013-06-05 03:00:00,8507.0 -2013-06-05 04:00:00,8276.0 -2013-06-05 05:00:00,8209.0 -2013-06-05 06:00:00,8353.0 -2013-06-05 07:00:00,8826.0 -2013-06-05 08:00:00,9620.0 -2013-06-05 09:00:00,10574.0 -2013-06-05 10:00:00,11177.0 -2013-06-05 11:00:00,11577.0 -2013-06-05 12:00:00,11842.0 -2013-06-05 13:00:00,12048.0 -2013-06-05 14:00:00,12081.0 -2013-06-05 15:00:00,12196.0 -2013-06-05 16:00:00,12128.0 -2013-06-05 17:00:00,11887.0 -2013-06-05 18:00:00,11682.0 -2013-06-05 19:00:00,11445.0 -2013-06-05 20:00:00,11172.0 -2013-06-05 21:00:00,11153.0 -2013-06-05 22:00:00,11391.0 -2013-06-05 23:00:00,11076.0 -2013-06-06 00:00:00,10286.0 -2013-06-04 01:00:00,9343.0 -2013-06-04 02:00:00,8763.0 -2013-06-04 03:00:00,8399.0 -2013-06-04 04:00:00,8172.0 -2013-06-04 05:00:00,8120.0 -2013-06-04 06:00:00,8314.0 -2013-06-04 07:00:00,8680.0 -2013-06-04 08:00:00,9547.0 -2013-06-04 09:00:00,10444.0 -2013-06-04 10:00:00,10971.0 -2013-06-04 11:00:00,11325.0 -2013-06-04 12:00:00,11617.0 -2013-06-04 13:00:00,11798.0 -2013-06-04 14:00:00,11939.0 -2013-06-04 15:00:00,12052.0 -2013-06-04 16:00:00,12044.0 -2013-06-04 17:00:00,11888.0 -2013-06-04 18:00:00,11733.0 -2013-06-04 19:00:00,11591.0 -2013-06-04 20:00:00,11237.0 -2013-06-04 21:00:00,11008.0 -2013-06-04 22:00:00,11275.0 -2013-06-04 23:00:00,11148.0 -2013-06-05 00:00:00,10371.0 -2013-06-03 01:00:00,8612.0 -2013-06-03 02:00:00,8209.0 -2013-06-03 03:00:00,7983.0 -2013-06-03 04:00:00,7857.0 -2013-06-03 05:00:00,7844.0 -2013-06-03 06:00:00,8093.0 -2013-06-03 07:00:00,8508.0 -2013-06-03 08:00:00,9439.0 -2013-06-03 09:00:00,10325.0 -2013-06-03 10:00:00,10845.0 -2013-06-03 11:00:00,11074.0 -2013-06-03 12:00:00,11362.0 -2013-06-03 13:00:00,11485.0 -2013-06-03 14:00:00,11547.0 -2013-06-03 15:00:00,11680.0 -2013-06-03 16:00:00,11643.0 -2013-06-03 17:00:00,11583.0 -2013-06-03 18:00:00,11476.0 -2013-06-03 19:00:00,11318.0 -2013-06-03 20:00:00,11005.0 -2013-06-03 21:00:00,10871.0 -2013-06-03 22:00:00,11070.0 -2013-06-03 23:00:00,10977.0 -2013-06-04 00:00:00,10207.0 -2013-06-02 01:00:00,9731.0 -2013-06-02 02:00:00,9078.0 -2013-06-02 03:00:00,8639.0 -2013-06-02 04:00:00,8329.0 -2013-06-02 05:00:00,8160.0 -2013-06-02 06:00:00,8103.0 -2013-06-02 07:00:00,7950.0 -2013-06-02 08:00:00,7967.0 -2013-06-02 09:00:00,8220.0 -2013-06-02 10:00:00,8569.0 -2013-06-02 11:00:00,8856.0 -2013-06-02 12:00:00,9075.0 -2013-06-02 13:00:00,9165.0 -2013-06-02 14:00:00,9185.0 -2013-06-02 15:00:00,9147.0 -2013-06-02 16:00:00,9109.0 -2013-06-02 17:00:00,9064.0 -2013-06-02 18:00:00,9102.0 -2013-06-02 19:00:00,9155.0 -2013-06-02 20:00:00,9197.0 -2013-06-02 21:00:00,9326.0 -2013-06-02 22:00:00,9672.0 -2013-06-02 23:00:00,9665.0 -2013-06-03 00:00:00,9173.0 -2013-06-01 01:00:00,11378.0 -2013-06-01 02:00:00,10584.0 -2013-06-01 03:00:00,9973.0 -2013-06-01 04:00:00,9451.0 -2013-06-01 05:00:00,9184.0 -2013-06-01 06:00:00,9043.0 -2013-06-01 07:00:00,9054.0 -2013-06-01 08:00:00,9290.0 -2013-06-01 09:00:00,9980.0 -2013-06-01 10:00:00,10590.0 -2013-06-01 11:00:00,11143.0 -2013-06-01 12:00:00,11598.0 -2013-06-01 13:00:00,11855.0 -2013-06-01 14:00:00,11909.0 -2013-06-01 15:00:00,11805.0 -2013-06-01 16:00:00,11751.0 -2013-06-01 17:00:00,11794.0 -2013-06-01 18:00:00,11760.0 -2013-06-01 19:00:00,11579.0 -2013-06-01 20:00:00,11278.0 -2013-06-01 21:00:00,11156.0 -2013-06-01 22:00:00,11137.0 -2013-06-01 23:00:00,11044.0 -2013-06-02 00:00:00,10395.0 -2013-05-31 01:00:00,11208.0 -2013-05-31 02:00:00,10317.0 -2013-05-31 03:00:00,9797.0 -2013-05-31 04:00:00,9503.0 -2013-05-31 05:00:00,9312.0 -2013-05-31 06:00:00,9446.0 -2013-05-31 07:00:00,9905.0 -2013-05-31 08:00:00,10872.0 -2013-05-31 09:00:00,11928.0 -2013-05-31 10:00:00,12755.0 -2013-05-31 11:00:00,13255.0 -2013-05-31 12:00:00,13845.0 -2013-05-31 13:00:00,14197.0 -2013-05-31 14:00:00,14289.0 -2013-05-31 15:00:00,14462.0 -2013-05-31 16:00:00,14456.0 -2013-05-31 17:00:00,14168.0 -2013-05-31 18:00:00,13916.0 -2013-05-31 19:00:00,13689.0 -2013-05-31 20:00:00,13392.0 -2013-05-31 21:00:00,13088.0 -2013-05-31 22:00:00,13059.0 -2013-05-31 23:00:00,12992.0 -2013-06-01 00:00:00,12220.0 -2013-05-30 01:00:00,11907.0 -2013-05-30 02:00:00,11005.0 -2013-05-30 03:00:00,10374.0 -2013-05-30 04:00:00,9976.0 -2013-05-30 05:00:00,9799.0 -2013-05-30 06:00:00,9897.0 -2013-05-30 07:00:00,10362.0 -2013-05-30 08:00:00,11432.0 -2013-05-30 09:00:00,12770.0 -2013-05-30 10:00:00,13696.0 -2013-05-30 11:00:00,14433.0 -2013-05-30 12:00:00,15085.0 -2013-05-30 13:00:00,15577.0 -2013-05-30 14:00:00,15899.0 -2013-05-30 15:00:00,16093.0 -2013-05-30 16:00:00,16018.0 -2013-05-30 17:00:00,15837.0 -2013-05-30 18:00:00,15532.0 -2013-05-30 19:00:00,15239.0 -2013-05-30 20:00:00,14903.0 -2013-05-30 21:00:00,14529.0 -2013-05-30 22:00:00,14447.0 -2013-05-30 23:00:00,13743.0 -2013-05-31 00:00:00,12451.0 -2013-05-29 01:00:00,10207.0 -2013-05-29 02:00:00,9575.0 -2013-05-29 03:00:00,9150.0 -2013-05-29 04:00:00,8899.0 -2013-05-29 05:00:00,8801.0 -2013-05-29 06:00:00,8948.0 -2013-05-29 07:00:00,9470.0 -2013-05-29 08:00:00,10451.0 -2013-05-29 09:00:00,11319.0 -2013-05-29 10:00:00,11906.0 -2013-05-29 11:00:00,12290.0 -2013-05-29 12:00:00,12668.0 -2013-05-29 13:00:00,13056.0 -2013-05-29 14:00:00,13461.0 -2013-05-29 15:00:00,13991.0 -2013-05-29 16:00:00,14340.0 -2013-05-29 17:00:00,14643.0 -2013-05-29 18:00:00,14840.0 -2013-05-29 19:00:00,14842.0 -2013-05-29 20:00:00,14599.0 -2013-05-29 21:00:00,14345.0 -2013-05-29 22:00:00,14409.0 -2013-05-29 23:00:00,14117.0 -2013-05-30 00:00:00,13111.0 -2013-05-28 01:00:00,8407.0 -2013-05-28 02:00:00,7977.0 -2013-05-28 03:00:00,7779.0 -2013-05-28 04:00:00,7660.0 -2013-05-28 05:00:00,7662.0 -2013-05-28 06:00:00,7936.0 -2013-05-28 07:00:00,8582.0 -2013-05-28 08:00:00,9642.0 -2013-05-28 09:00:00,10745.0 -2013-05-28 10:00:00,11332.0 -2013-05-28 11:00:00,11676.0 -2013-05-28 12:00:00,12049.0 -2013-05-28 13:00:00,12242.0 -2013-05-28 14:00:00,12417.0 -2013-05-28 15:00:00,12604.0 -2013-05-28 16:00:00,12585.0 -2013-05-28 17:00:00,12577.0 -2013-05-28 18:00:00,12557.0 -2013-05-28 19:00:00,12455.0 -2013-05-28 20:00:00,12283.0 -2013-05-28 21:00:00,12618.0 -2013-05-28 22:00:00,12760.0 -2013-05-28 23:00:00,12097.0 -2013-05-29 00:00:00,11156.0 -2013-05-27 01:00:00,8380.0 -2013-05-27 02:00:00,7966.0 -2013-05-27 03:00:00,7664.0 -2013-05-27 04:00:00,7492.0 -2013-05-27 05:00:00,7394.0 -2013-05-27 06:00:00,7480.0 -2013-05-27 07:00:00,7561.0 -2013-05-27 08:00:00,7654.0 -2013-05-27 09:00:00,7904.0 -2013-05-27 10:00:00,8239.0 -2013-05-27 11:00:00,8542.0 -2013-05-27 12:00:00,8835.0 -2013-05-27 13:00:00,8978.0 -2013-05-27 14:00:00,8997.0 -2013-05-27 15:00:00,8983.0 -2013-05-27 16:00:00,8946.0 -2013-05-27 17:00:00,8906.0 -2013-05-27 18:00:00,8962.0 -2013-05-27 19:00:00,9056.0 -2013-05-27 20:00:00,9066.0 -2013-05-27 21:00:00,9133.0 -2013-05-27 22:00:00,9546.0 -2013-05-27 23:00:00,9504.0 -2013-05-28 00:00:00,8988.0 -2013-05-26 01:00:00,8642.0 -2013-05-26 02:00:00,8173.0 -2013-05-26 03:00:00,7915.0 -2013-05-26 04:00:00,7727.0 -2013-05-26 05:00:00,7641.0 -2013-05-26 06:00:00,7643.0 -2013-05-26 07:00:00,7503.0 -2013-05-26 08:00:00,7506.0 -2013-05-26 09:00:00,7782.0 -2013-05-26 10:00:00,8110.0 -2013-05-26 11:00:00,8435.0 -2013-05-26 12:00:00,8661.0 -2013-05-26 13:00:00,8791.0 -2013-05-26 14:00:00,8788.0 -2013-05-26 15:00:00,8827.0 -2013-05-26 16:00:00,8784.0 -2013-05-26 17:00:00,8743.0 -2013-05-26 18:00:00,8718.0 -2013-05-26 19:00:00,8819.0 -2013-05-26 20:00:00,8852.0 -2013-05-26 21:00:00,9015.0 -2013-05-26 22:00:00,9331.0 -2013-05-26 23:00:00,9284.0 -2013-05-27 00:00:00,8889.0 -2013-05-25 01:00:00,9110.0 -2013-05-25 02:00:00,8591.0 -2013-05-25 03:00:00,8242.0 -2013-05-25 04:00:00,8076.0 -2013-05-25 05:00:00,7919.0 -2013-05-25 06:00:00,7986.0 -2013-05-25 07:00:00,7969.0 -2013-05-25 08:00:00,8100.0 -2013-05-25 09:00:00,8541.0 -2013-05-25 10:00:00,8982.0 -2013-05-25 11:00:00,9326.0 -2013-05-25 12:00:00,9474.0 -2013-05-25 13:00:00,9581.0 -2013-05-25 14:00:00,9488.0 -2013-05-25 15:00:00,9324.0 -2013-05-25 16:00:00,9196.0 -2013-05-25 17:00:00,9099.0 -2013-05-25 18:00:00,9096.0 -2013-05-25 19:00:00,9068.0 -2013-05-25 20:00:00,9141.0 -2013-05-25 21:00:00,9209.0 -2013-05-25 22:00:00,9631.0 -2013-05-25 23:00:00,9590.0 -2013-05-26 00:00:00,9140.0 -2013-05-24 01:00:00,9262.0 -2013-05-24 02:00:00,8763.0 -2013-05-24 03:00:00,8455.0 -2013-05-24 04:00:00,8297.0 -2013-05-24 05:00:00,8247.0 -2013-05-24 06:00:00,8431.0 -2013-05-24 07:00:00,8893.0 -2013-05-24 08:00:00,9679.0 -2013-05-24 09:00:00,10426.0 -2013-05-24 10:00:00,10791.0 -2013-05-24 11:00:00,10926.0 -2013-05-24 12:00:00,11058.0 -2013-05-24 13:00:00,11021.0 -2013-05-24 14:00:00,11001.0 -2013-05-24 15:00:00,11031.0 -2013-05-24 16:00:00,10888.0 -2013-05-24 17:00:00,10714.0 -2013-05-24 18:00:00,10542.0 -2013-05-24 19:00:00,10397.0 -2013-05-24 20:00:00,10177.0 -2013-05-24 21:00:00,10082.0 -2013-05-24 22:00:00,10450.0 -2013-05-24 23:00:00,10412.0 -2013-05-25 00:00:00,9801.0 -2013-05-23 01:00:00,9922.0 -2013-05-23 02:00:00,9243.0 -2013-05-23 03:00:00,8813.0 -2013-05-23 04:00:00,8515.0 -2013-05-23 05:00:00,8397.0 -2013-05-23 06:00:00,8527.0 -2013-05-23 07:00:00,9071.0 -2013-05-23 08:00:00,9914.0 -2013-05-23 09:00:00,10717.0 -2013-05-23 10:00:00,11134.0 -2013-05-23 11:00:00,11301.0 -2013-05-23 12:00:00,11410.0 -2013-05-23 13:00:00,11372.0 -2013-05-23 14:00:00,11336.0 -2013-05-23 15:00:00,11285.0 -2013-05-23 16:00:00,11110.0 -2013-05-23 17:00:00,10927.0 -2013-05-23 18:00:00,10782.0 -2013-05-23 19:00:00,10638.0 -2013-05-23 20:00:00,10423.0 -2013-05-23 21:00:00,10379.0 -2013-05-23 22:00:00,10842.0 -2013-05-23 23:00:00,10743.0 -2013-05-24 00:00:00,10015.0 -2013-05-22 01:00:00,11472.0 -2013-05-22 02:00:00,10625.0 -2013-05-22 03:00:00,10018.0 -2013-05-22 04:00:00,9616.0 -2013-05-22 05:00:00,9490.0 -2013-05-22 06:00:00,9620.0 -2013-05-22 07:00:00,10239.0 -2013-05-22 08:00:00,11298.0 -2013-05-22 09:00:00,12157.0 -2013-05-22 10:00:00,12596.0 -2013-05-22 11:00:00,12998.0 -2013-05-22 12:00:00,13497.0 -2013-05-22 13:00:00,13541.0 -2013-05-22 14:00:00,13290.0 -2013-05-22 15:00:00,13122.0 -2013-05-22 16:00:00,13158.0 -2013-05-22 17:00:00,13302.0 -2013-05-22 18:00:00,13286.0 -2013-05-22 19:00:00,12972.0 -2013-05-22 20:00:00,12484.0 -2013-05-22 21:00:00,12199.0 -2013-05-22 22:00:00,12342.0 -2013-05-22 23:00:00,11877.0 -2013-05-23 00:00:00,10883.0 -2013-05-21 01:00:00,12197.0 -2013-05-21 02:00:00,11155.0 -2013-05-21 03:00:00,10461.0 -2013-05-21 04:00:00,9979.0 -2013-05-21 05:00:00,9718.0 -2013-05-21 06:00:00,9758.0 -2013-05-21 07:00:00,10193.0 -2013-05-21 08:00:00,11081.0 -2013-05-21 09:00:00,12189.0 -2013-05-21 10:00:00,12926.0 -2013-05-21 11:00:00,13491.0 -2013-05-21 12:00:00,13906.0 -2013-05-21 13:00:00,14147.0 -2013-05-21 14:00:00,14585.0 -2013-05-21 15:00:00,15100.0 -2013-05-21 16:00:00,15329.0 -2013-05-21 17:00:00,15478.0 -2013-05-21 18:00:00,15539.0 -2013-05-21 19:00:00,15351.0 -2013-05-21 20:00:00,14837.0 -2013-05-21 21:00:00,14297.0 -2013-05-21 22:00:00,14249.0 -2013-05-21 23:00:00,13874.0 -2013-05-22 00:00:00,12666.0 -2013-05-20 01:00:00,11070.0 -2013-05-20 02:00:00,10542.0 -2013-05-20 03:00:00,10172.0 -2013-05-20 04:00:00,9918.0 -2013-05-20 05:00:00,9769.0 -2013-05-20 06:00:00,9936.0 -2013-05-20 07:00:00,10562.0 -2013-05-20 08:00:00,11703.0 -2013-05-20 09:00:00,13021.0 -2013-05-20 10:00:00,13520.0 -2013-05-20 11:00:00,13880.0 -2013-05-20 12:00:00,14379.0 -2013-05-20 13:00:00,14861.0 -2013-05-20 14:00:00,15291.0 -2013-05-20 15:00:00,15842.0 -2013-05-20 16:00:00,16260.0 -2013-05-20 17:00:00,16563.0 -2013-05-20 18:00:00,16629.0 -2013-05-20 19:00:00,16537.0 -2013-05-20 20:00:00,16161.0 -2013-05-20 21:00:00,15574.0 -2013-05-20 22:00:00,15492.0 -2013-05-20 23:00:00,15123.0 -2013-05-21 00:00:00,13871.0 -2013-05-19 01:00:00,9585.0 -2013-05-19 02:00:00,8950.0 -2013-05-19 03:00:00,8529.0 -2013-05-19 04:00:00,8219.0 -2013-05-19 05:00:00,8022.0 -2013-05-19 06:00:00,7977.0 -2013-05-19 07:00:00,7859.0 -2013-05-19 08:00:00,7915.0 -2013-05-19 09:00:00,8445.0 -2013-05-19 10:00:00,9096.0 -2013-05-19 11:00:00,9836.0 -2013-05-19 12:00:00,10553.0 -2013-05-19 13:00:00,11189.0 -2013-05-19 14:00:00,11618.0 -2013-05-19 15:00:00,11981.0 -2013-05-19 16:00:00,12281.0 -2013-05-19 17:00:00,12630.0 -2013-05-19 18:00:00,12897.0 -2013-05-19 19:00:00,13030.0 -2013-05-19 20:00:00,12860.0 -2013-05-19 21:00:00,12668.0 -2013-05-19 22:00:00,12824.0 -2013-05-19 23:00:00,12641.0 -2013-05-20 00:00:00,11935.0 -2013-05-18 01:00:00,9376.0 -2013-05-18 02:00:00,8799.0 -2013-05-18 03:00:00,8451.0 -2013-05-18 04:00:00,8222.0 -2013-05-18 05:00:00,8088.0 -2013-05-18 06:00:00,8044.0 -2013-05-18 07:00:00,8120.0 -2013-05-18 08:00:00,8323.0 -2013-05-18 09:00:00,8865.0 -2013-05-18 10:00:00,9579.0 -2013-05-18 11:00:00,10061.0 -2013-05-18 12:00:00,10556.0 -2013-05-18 13:00:00,10827.0 -2013-05-18 14:00:00,11027.0 -2013-05-18 15:00:00,11045.0 -2013-05-18 16:00:00,11061.0 -2013-05-18 17:00:00,11180.0 -2013-05-18 18:00:00,11320.0 -2013-05-18 19:00:00,11337.0 -2013-05-18 20:00:00,11114.0 -2013-05-18 21:00:00,10858.0 -2013-05-18 22:00:00,11109.0 -2013-05-18 23:00:00,10932.0 -2013-05-19 00:00:00,10286.0 -2013-05-17 01:00:00,9666.0 -2013-05-17 02:00:00,9024.0 -2013-05-17 03:00:00,8664.0 -2013-05-17 04:00:00,8418.0 -2013-05-17 05:00:00,8332.0 -2013-05-17 06:00:00,8440.0 -2013-05-17 07:00:00,8935.0 -2013-05-17 08:00:00,9713.0 -2013-05-17 09:00:00,10645.0 -2013-05-17 10:00:00,11218.0 -2013-05-17 11:00:00,11617.0 -2013-05-17 12:00:00,11938.0 -2013-05-17 13:00:00,12052.0 -2013-05-17 14:00:00,11975.0 -2013-05-17 15:00:00,11968.0 -2013-05-17 16:00:00,11904.0 -2013-05-17 17:00:00,11782.0 -2013-05-17 18:00:00,11600.0 -2013-05-17 19:00:00,11324.0 -2013-05-17 20:00:00,11063.0 -2013-05-17 21:00:00,10965.0 -2013-05-17 22:00:00,11157.0 -2013-05-17 23:00:00,10873.0 -2013-05-18 00:00:00,10137.0 -2013-05-16 01:00:00,10087.0 -2013-05-16 02:00:00,9344.0 -2013-05-16 03:00:00,8838.0 -2013-05-16 04:00:00,8587.0 -2013-05-16 05:00:00,8468.0 -2013-05-16 06:00:00,8567.0 -2013-05-16 07:00:00,9007.0 -2013-05-16 08:00:00,9873.0 -2013-05-16 09:00:00,10971.0 -2013-05-16 10:00:00,11655.0 -2013-05-16 11:00:00,12155.0 -2013-05-16 12:00:00,12603.0 -2013-05-16 13:00:00,12875.0 -2013-05-16 14:00:00,13203.0 -2013-05-16 15:00:00,13478.0 -2013-05-16 16:00:00,13683.0 -2013-05-16 17:00:00,13825.0 -2013-05-16 18:00:00,13808.0 -2013-05-16 19:00:00,13466.0 -2013-05-16 20:00:00,12866.0 -2013-05-16 21:00:00,12192.0 -2013-05-16 22:00:00,12180.0 -2013-05-16 23:00:00,11624.0 -2013-05-17 00:00:00,10603.0 -2013-05-15 01:00:00,10844.0 -2013-05-15 02:00:00,10161.0 -2013-05-15 03:00:00,9723.0 -2013-05-15 04:00:00,9384.0 -2013-05-15 05:00:00,9224.0 -2013-05-15 06:00:00,9292.0 -2013-05-15 07:00:00,9781.0 -2013-05-15 08:00:00,10621.0 -2013-05-15 09:00:00,11657.0 -2013-05-15 10:00:00,12272.0 -2013-05-15 11:00:00,12753.0 -2013-05-15 12:00:00,13164.0 -2013-05-15 13:00:00,13452.0 -2013-05-15 14:00:00,13627.0 -2013-05-15 15:00:00,13861.0 -2013-05-15 16:00:00,13898.0 -2013-05-15 17:00:00,13893.0 -2013-05-15 18:00:00,13774.0 -2013-05-15 19:00:00,13495.0 -2013-05-15 20:00:00,12919.0 -2013-05-15 21:00:00,12538.0 -2013-05-15 22:00:00,12629.0 -2013-05-15 23:00:00,12203.0 -2013-05-16 00:00:00,11128.0 -2013-05-14 01:00:00,9160.0 -2013-05-14 02:00:00,8636.0 -2013-05-14 03:00:00,8329.0 -2013-05-14 04:00:00,8142.0 -2013-05-14 05:00:00,8091.0 -2013-05-14 06:00:00,8281.0 -2013-05-14 07:00:00,8855.0 -2013-05-14 08:00:00,9700.0 -2013-05-14 09:00:00,10542.0 -2013-05-14 10:00:00,11038.0 -2013-05-14 11:00:00,11392.0 -2013-05-14 12:00:00,11697.0 -2013-05-14 13:00:00,11995.0 -2013-05-14 14:00:00,12288.0 -2013-05-14 15:00:00,12623.0 -2013-05-14 16:00:00,12914.0 -2013-05-14 17:00:00,13002.0 -2013-05-14 18:00:00,13085.0 -2013-05-14 19:00:00,13013.0 -2013-05-14 20:00:00,12759.0 -2013-05-14 21:00:00,12668.0 -2013-05-14 22:00:00,13024.0 -2013-05-14 23:00:00,12726.0 -2013-05-15 00:00:00,11783.0 -2013-05-13 01:00:00,8573.0 -2013-05-13 02:00:00,8196.0 -2013-05-13 03:00:00,8027.0 -2013-05-13 04:00:00,7951.0 -2013-05-13 05:00:00,8021.0 -2013-05-13 06:00:00,8313.0 -2013-05-13 07:00:00,9041.0 -2013-05-13 08:00:00,9864.0 -2013-05-13 09:00:00,10593.0 -2013-05-13 10:00:00,10858.0 -2013-05-13 11:00:00,11011.0 -2013-05-13 12:00:00,11159.0 -2013-05-13 13:00:00,11187.0 -2013-05-13 14:00:00,11219.0 -2013-05-13 15:00:00,11270.0 -2013-05-13 16:00:00,11164.0 -2013-05-13 17:00:00,11006.0 -2013-05-13 18:00:00,10879.0 -2013-05-13 19:00:00,10690.0 -2013-05-13 20:00:00,10548.0 -2013-05-13 21:00:00,10719.0 -2013-05-13 22:00:00,11147.0 -2013-05-13 23:00:00,10743.0 -2013-05-14 00:00:00,9922.0 -2013-05-12 01:00:00,8813.0 -2013-05-12 02:00:00,8373.0 -2013-05-12 03:00:00,8084.0 -2013-05-12 04:00:00,7929.0 -2013-05-12 05:00:00,7819.0 -2013-05-12 06:00:00,7865.0 -2013-05-12 07:00:00,7823.0 -2013-05-12 08:00:00,7829.0 -2013-05-12 09:00:00,8086.0 -2013-05-12 10:00:00,8430.0 -2013-05-12 11:00:00,8575.0 -2013-05-12 12:00:00,8743.0 -2013-05-12 13:00:00,8728.0 -2013-05-12 14:00:00,8695.0 -2013-05-12 15:00:00,8612.0 -2013-05-12 16:00:00,8556.0 -2013-05-12 17:00:00,8506.0 -2013-05-12 18:00:00,8529.0 -2013-05-12 19:00:00,8600.0 -2013-05-12 20:00:00,8606.0 -2013-05-12 21:00:00,8850.0 -2013-05-12 22:00:00,9535.0 -2013-05-12 23:00:00,9535.0 -2013-05-13 00:00:00,9038.0 -2013-05-11 01:00:00,9247.0 -2013-05-11 02:00:00,8729.0 -2013-05-11 03:00:00,8382.0 -2013-05-11 04:00:00,8189.0 -2013-05-11 05:00:00,8066.0 -2013-05-11 06:00:00,8127.0 -2013-05-11 07:00:00,8270.0 -2013-05-11 08:00:00,8424.0 -2013-05-11 09:00:00,8874.0 -2013-05-11 10:00:00,9277.0 -2013-05-11 11:00:00,9570.0 -2013-05-11 12:00:00,9788.0 -2013-05-11 13:00:00,9786.0 -2013-05-11 14:00:00,9648.0 -2013-05-11 15:00:00,9420.0 -2013-05-11 16:00:00,9260.0 -2013-05-11 17:00:00,9171.0 -2013-05-11 18:00:00,9146.0 -2013-05-11 19:00:00,9123.0 -2013-05-11 20:00:00,9079.0 -2013-05-11 21:00:00,9237.0 -2013-05-11 22:00:00,9778.0 -2013-05-11 23:00:00,9789.0 -2013-05-12 00:00:00,9321.0 -2013-05-10 01:00:00,9218.0 -2013-05-10 02:00:00,8672.0 -2013-05-10 03:00:00,8293.0 -2013-05-10 04:00:00,8097.0 -2013-05-10 05:00:00,8035.0 -2013-05-10 06:00:00,8219.0 -2013-05-10 07:00:00,8777.0 -2013-05-10 08:00:00,9690.0 -2013-05-10 09:00:00,10504.0 -2013-05-10 10:00:00,10870.0 -2013-05-10 11:00:00,11064.0 -2013-05-10 12:00:00,11190.0 -2013-05-10 13:00:00,11210.0 -2013-05-10 14:00:00,11171.0 -2013-05-10 15:00:00,11155.0 -2013-05-10 16:00:00,11016.0 -2013-05-10 17:00:00,10865.0 -2013-05-10 18:00:00,10748.0 -2013-05-10 19:00:00,10661.0 -2013-05-10 20:00:00,10553.0 -2013-05-10 21:00:00,10697.0 -2013-05-10 22:00:00,10972.0 -2013-05-10 23:00:00,10653.0 -2013-05-11 00:00:00,10007.0 -2013-05-09 01:00:00,9444.0 -2013-05-09 02:00:00,8847.0 -2013-05-09 03:00:00,8492.0 -2013-05-09 04:00:00,8246.0 -2013-05-09 05:00:00,8193.0 -2013-05-09 06:00:00,8316.0 -2013-05-09 07:00:00,8818.0 -2013-05-09 08:00:00,9638.0 -2013-05-09 09:00:00,10576.0 -2013-05-09 10:00:00,11089.0 -2013-05-09 11:00:00,11422.0 -2013-05-09 12:00:00,11605.0 -2013-05-09 13:00:00,11748.0 -2013-05-09 14:00:00,11873.0 -2013-05-09 15:00:00,11973.0 -2013-05-09 16:00:00,11917.0 -2013-05-09 17:00:00,11777.0 -2013-05-09 18:00:00,11648.0 -2013-05-09 19:00:00,11478.0 -2013-05-09 20:00:00,11240.0 -2013-05-09 21:00:00,11263.0 -2013-05-09 22:00:00,11460.0 -2013-05-09 23:00:00,10985.0 -2013-05-10 00:00:00,10113.0 -2013-05-08 01:00:00,9264.0 -2013-05-08 02:00:00,8695.0 -2013-05-08 03:00:00,8318.0 -2013-05-08 04:00:00,8134.0 -2013-05-08 05:00:00,8042.0 -2013-05-08 06:00:00,8227.0 -2013-05-08 07:00:00,8762.0 -2013-05-08 08:00:00,9534.0 -2013-05-08 09:00:00,10480.0 -2013-05-08 10:00:00,11053.0 -2013-05-08 11:00:00,11443.0 -2013-05-08 12:00:00,11816.0 -2013-05-08 13:00:00,11956.0 -2013-05-08 14:00:00,12118.0 -2013-05-08 15:00:00,12289.0 -2013-05-08 16:00:00,12358.0 -2013-05-08 17:00:00,12291.0 -2013-05-08 18:00:00,12193.0 -2013-05-08 19:00:00,11968.0 -2013-05-08 20:00:00,11539.0 -2013-05-08 21:00:00,11417.0 -2013-05-08 22:00:00,11742.0 -2013-05-08 23:00:00,11279.0 -2013-05-09 00:00:00,10349.0 -2013-05-07 01:00:00,9046.0 -2013-05-07 02:00:00,8529.0 -2013-05-07 03:00:00,8186.0 -2013-05-07 04:00:00,8027.0 -2013-05-07 05:00:00,7995.0 -2013-05-07 06:00:00,8177.0 -2013-05-07 07:00:00,8757.0 -2013-05-07 08:00:00,9569.0 -2013-05-07 09:00:00,10392.0 -2013-05-07 10:00:00,10846.0 -2013-05-07 11:00:00,11154.0 -2013-05-07 12:00:00,11436.0 -2013-05-07 13:00:00,11610.0 -2013-05-07 14:00:00,11733.0 -2013-05-07 15:00:00,11873.0 -2013-05-07 16:00:00,11931.0 -2013-05-07 17:00:00,11856.0 -2013-05-07 18:00:00,11767.0 -2013-05-07 19:00:00,11583.0 -2013-05-07 20:00:00,11231.0 -2013-05-07 21:00:00,11124.0 -2013-05-07 22:00:00,11523.0 -2013-05-07 23:00:00,11109.0 -2013-05-08 00:00:00,10174.0 -2013-05-06 01:00:00,8392.0 -2013-05-06 02:00:00,8022.0 -2013-05-06 03:00:00,7775.0 -2013-05-06 04:00:00,7683.0 -2013-05-06 05:00:00,7681.0 -2013-05-06 06:00:00,7909.0 -2013-05-06 07:00:00,8578.0 -2013-05-06 08:00:00,9497.0 -2013-05-06 09:00:00,10439.0 -2013-05-06 10:00:00,10766.0 -2013-05-06 11:00:00,10976.0 -2013-05-06 12:00:00,11209.0 -2013-05-06 13:00:00,11317.0 -2013-05-06 14:00:00,11356.0 -2013-05-06 15:00:00,11469.0 -2013-05-06 16:00:00,11410.0 -2013-05-06 17:00:00,11279.0 -2013-05-06 18:00:00,11202.0 -2013-05-06 19:00:00,11008.0 -2013-05-06 20:00:00,10723.0 -2013-05-06 21:00:00,10743.0 -2013-05-06 22:00:00,11234.0 -2013-05-06 23:00:00,10756.0 -2013-05-07 00:00:00,9887.0 -2013-05-05 01:00:00,8600.0 -2013-05-05 02:00:00,8167.0 -2013-05-05 03:00:00,7846.0 -2013-05-05 04:00:00,7656.0 -2013-05-05 05:00:00,7538.0 -2013-05-05 06:00:00,7578.0 -2013-05-05 07:00:00,7587.0 -2013-05-05 08:00:00,7512.0 -2013-05-05 09:00:00,7791.0 -2013-05-05 10:00:00,8202.0 -2013-05-05 11:00:00,8483.0 -2013-05-05 12:00:00,8739.0 -2013-05-05 13:00:00,8830.0 -2013-05-05 14:00:00,8939.0 -2013-05-05 15:00:00,8958.0 -2013-05-05 16:00:00,8963.0 -2013-05-05 17:00:00,8980.0 -2013-05-05 18:00:00,9021.0 -2013-05-05 19:00:00,9041.0 -2013-05-05 20:00:00,9066.0 -2013-05-05 21:00:00,9207.0 -2013-05-05 22:00:00,9788.0 -2013-05-05 23:00:00,9550.0 -2013-05-06 00:00:00,8973.0 -2013-05-04 01:00:00,9402.0 -2013-05-04 02:00:00,8804.0 -2013-05-04 03:00:00,8461.0 -2013-05-04 04:00:00,8249.0 -2013-05-04 05:00:00,8144.0 -2013-05-04 06:00:00,8165.0 -2013-05-04 07:00:00,8303.0 -2013-05-04 08:00:00,8476.0 -2013-05-04 09:00:00,8865.0 -2013-05-04 10:00:00,9322.0 -2013-05-04 11:00:00,9546.0 -2013-05-04 12:00:00,9672.0 -2013-05-04 13:00:00,9638.0 -2013-05-04 14:00:00,9545.0 -2013-05-04 15:00:00,9439.0 -2013-05-04 16:00:00,9345.0 -2013-05-04 17:00:00,9280.0 -2013-05-04 18:00:00,9257.0 -2013-05-04 19:00:00,9204.0 -2013-05-04 20:00:00,9197.0 -2013-05-04 21:00:00,9369.0 -2013-05-04 22:00:00,9835.0 -2013-05-04 23:00:00,9659.0 -2013-05-05 00:00:00,9155.0 -2013-05-03 01:00:00,9187.0 -2013-05-03 02:00:00,8691.0 -2013-05-03 03:00:00,8427.0 -2013-05-03 04:00:00,8256.0 -2013-05-03 05:00:00,8198.0 -2013-05-03 06:00:00,8416.0 -2013-05-03 07:00:00,9091.0 -2013-05-03 08:00:00,9986.0 -2013-05-03 09:00:00,10815.0 -2013-05-03 10:00:00,11199.0 -2013-05-03 11:00:00,11334.0 -2013-05-03 12:00:00,11425.0 -2013-05-03 13:00:00,11358.0 -2013-05-03 14:00:00,11264.0 -2013-05-03 15:00:00,11234.0 -2013-05-03 16:00:00,11166.0 -2013-05-03 17:00:00,11025.0 -2013-05-03 18:00:00,10860.0 -2013-05-03 19:00:00,10753.0 -2013-05-03 20:00:00,10690.0 -2013-05-03 21:00:00,10903.0 -2013-05-03 22:00:00,11121.0 -2013-05-03 23:00:00,10799.0 -2013-05-04 00:00:00,10119.0 -2013-05-02 01:00:00,10221.0 -2013-05-02 02:00:00,9439.0 -2013-05-02 03:00:00,8848.0 -2013-05-02 04:00:00,8478.0 -2013-05-02 05:00:00,8270.0 -2013-05-02 06:00:00,8344.0 -2013-05-02 07:00:00,8891.0 -2013-05-02 08:00:00,9564.0 -2013-05-02 09:00:00,10380.0 -2013-05-02 10:00:00,10704.0 -2013-05-02 11:00:00,10595.0 -2013-05-02 12:00:00,10958.0 -2013-05-02 13:00:00,11164.0 -2013-05-02 14:00:00,11075.0 -2013-05-02 15:00:00,11136.0 -2013-05-02 16:00:00,11021.0 -2013-05-02 17:00:00,10870.0 -2013-05-02 18:00:00,10752.0 -2013-05-02 19:00:00,10660.0 -2013-05-02 20:00:00,10595.0 -2013-05-02 21:00:00,10893.0 -2013-05-02 22:00:00,11124.0 -2013-05-02 23:00:00,10684.0 -2013-05-03 00:00:00,9974.0 -2013-05-01 01:00:00,10247.0 -2013-05-01 02:00:00,9575.0 -2013-05-01 03:00:00,9122.0 -2013-05-01 04:00:00,8795.0 -2013-05-01 05:00:00,8631.0 -2013-05-01 06:00:00,8729.0 -2013-05-01 07:00:00,9304.0 -2013-05-01 08:00:00,10121.0 -2013-05-01 09:00:00,11108.0 -2013-05-01 10:00:00,11741.0 -2013-05-01 11:00:00,12206.0 -2013-05-01 12:00:00,12699.0 -2013-05-01 13:00:00,12937.0 -2013-05-01 14:00:00,13133.0 -2013-05-01 15:00:00,13322.0 -2013-05-01 16:00:00,13421.0 -2013-05-01 17:00:00,13461.0 -2013-05-01 18:00:00,13409.0 -2013-05-01 19:00:00,13194.0 -2013-05-01 20:00:00,12763.0 -2013-05-01 21:00:00,12566.0 -2013-05-01 22:00:00,12859.0 -2013-05-01 23:00:00,12318.0 -2013-05-02 00:00:00,11269.0 -2013-04-30 01:00:00,9056.0 -2013-04-30 02:00:00,8534.0 -2013-04-30 03:00:00,8221.0 -2013-04-30 04:00:00,8184.0 -2013-04-30 05:00:00,8104.0 -2013-04-30 06:00:00,8282.0 -2013-04-30 07:00:00,8923.0 -2013-04-30 08:00:00,9737.0 -2013-04-30 09:00:00,10571.0 -2013-04-30 10:00:00,11015.0 -2013-04-30 11:00:00,11298.0 -2013-04-30 12:00:00,11622.0 -2013-04-30 13:00:00,11864.0 -2013-04-30 14:00:00,12064.0 -2013-04-30 15:00:00,12266.0 -2013-04-30 16:00:00,12416.0 -2013-04-30 17:00:00,12453.0 -2013-04-30 18:00:00,12445.0 -2013-04-30 19:00:00,12310.0 -2013-04-30 20:00:00,12014.0 -2013-04-30 21:00:00,12034.0 -2013-04-30 22:00:00,12474.0 -2013-04-30 23:00:00,12056.0 -2013-05-01 00:00:00,11139.0 -2013-04-29 01:00:00,8415.0 -2013-04-29 02:00:00,8061.0 -2013-04-29 03:00:00,7772.0 -2013-04-29 04:00:00,7678.0 -2013-04-29 05:00:00,7700.0 -2013-04-29 06:00:00,7931.0 -2013-04-29 07:00:00,8661.0 -2013-04-29 08:00:00,9447.0 -2013-04-29 09:00:00,10282.0 -2013-04-29 10:00:00,10733.0 -2013-04-29 11:00:00,10943.0 -2013-04-29 12:00:00,11193.0 -2013-04-29 13:00:00,11294.0 -2013-04-29 14:00:00,11382.0 -2013-04-29 15:00:00,11482.0 -2013-04-29 16:00:00,11432.0 -2013-04-29 17:00:00,11270.0 -2013-04-29 18:00:00,11141.0 -2013-04-29 19:00:00,11017.0 -2013-04-29 20:00:00,10784.0 -2013-04-29 21:00:00,10881.0 -2013-04-29 22:00:00,11370.0 -2013-04-29 23:00:00,10922.0 -2013-04-30 00:00:00,9868.0 -2013-04-28 01:00:00,8566.0 -2013-04-28 02:00:00,8140.0 -2013-04-28 03:00:00,7813.0 -2013-04-28 04:00:00,7638.0 -2013-04-28 05:00:00,7544.0 -2013-04-28 06:00:00,7535.0 -2013-04-28 07:00:00,7666.0 -2013-04-28 08:00:00,7656.0 -2013-04-28 09:00:00,7881.0 -2013-04-28 10:00:00,8235.0 -2013-04-28 11:00:00,8556.0 -2013-04-28 12:00:00,8789.0 -2013-04-28 13:00:00,8870.0 -2013-04-28 14:00:00,8878.0 -2013-04-28 15:00:00,8838.0 -2013-04-28 16:00:00,8820.0 -2013-04-28 17:00:00,8781.0 -2013-04-28 18:00:00,8836.0 -2013-04-28 19:00:00,8899.0 -2013-04-28 20:00:00,9036.0 -2013-04-28 21:00:00,9272.0 -2013-04-28 22:00:00,9806.0 -2013-04-28 23:00:00,9578.0 -2013-04-29 00:00:00,9001.0 -2013-04-27 01:00:00,9066.0 -2013-04-27 02:00:00,8537.0 -2013-04-27 03:00:00,8219.0 -2013-04-27 04:00:00,8010.0 -2013-04-27 05:00:00,7947.0 -2013-04-27 06:00:00,7995.0 -2013-04-27 07:00:00,8258.0 -2013-04-27 08:00:00,8354.0 -2013-04-27 09:00:00,8739.0 -2013-04-27 10:00:00,9102.0 -2013-04-27 11:00:00,9366.0 -2013-04-27 12:00:00,9505.0 -2013-04-27 13:00:00,9478.0 -2013-04-27 14:00:00,9439.0 -2013-04-27 15:00:00,9273.0 -2013-04-27 16:00:00,9187.0 -2013-04-27 17:00:00,9155.0 -2013-04-27 18:00:00,9093.0 -2013-04-27 19:00:00,9091.0 -2013-04-27 20:00:00,9076.0 -2013-04-27 21:00:00,9430.0 -2013-04-27 22:00:00,9762.0 -2013-04-27 23:00:00,9571.0 -2013-04-28 00:00:00,9075.0 -2013-04-26 01:00:00,9576.0 -2013-04-26 02:00:00,9113.0 -2013-04-26 03:00:00,8805.0 -2013-04-26 04:00:00,8661.0 -2013-04-26 05:00:00,8662.0 -2013-04-26 06:00:00,8898.0 -2013-04-26 07:00:00,9657.0 -2013-04-26 08:00:00,10404.0 -2013-04-26 09:00:00,10967.0 -2013-04-26 10:00:00,11067.0 -2013-04-26 11:00:00,11210.0 -2013-04-26 12:00:00,11275.0 -2013-04-26 13:00:00,11239.0 -2013-04-26 14:00:00,11201.0 -2013-04-26 15:00:00,11177.0 -2013-04-26 16:00:00,11012.0 -2013-04-26 17:00:00,10825.0 -2013-04-26 18:00:00,10649.0 -2013-04-26 19:00:00,10448.0 -2013-04-26 20:00:00,10228.0 -2013-04-26 21:00:00,10331.0 -2013-04-26 22:00:00,10744.0 -2013-04-26 23:00:00,10474.0 -2013-04-27 00:00:00,9861.0 -2013-04-25 01:00:00,9666.0 -2013-04-25 02:00:00,9194.0 -2013-04-25 03:00:00,8924.0 -2013-04-25 04:00:00,8755.0 -2013-04-25 05:00:00,8751.0 -2013-04-25 06:00:00,8994.0 -2013-04-25 07:00:00,9686.0 -2013-04-25 08:00:00,10515.0 -2013-04-25 09:00:00,11152.0 -2013-04-25 10:00:00,11383.0 -2013-04-25 11:00:00,11510.0 -2013-04-25 12:00:00,11585.0 -2013-04-25 13:00:00,11559.0 -2013-04-25 14:00:00,11454.0 -2013-04-25 15:00:00,11378.0 -2013-04-25 16:00:00,11238.0 -2013-04-25 17:00:00,11075.0 -2013-04-25 18:00:00,10904.0 -2013-04-25 19:00:00,10731.0 -2013-04-25 20:00:00,10592.0 -2013-04-25 21:00:00,10825.0 -2013-04-25 22:00:00,11376.0 -2013-04-25 23:00:00,11073.0 -2013-04-26 00:00:00,10328.0 -2013-04-24 01:00:00,9638.0 -2013-04-24 02:00:00,9177.0 -2013-04-24 03:00:00,8894.0 -2013-04-24 04:00:00,8735.0 -2013-04-24 05:00:00,8728.0 -2013-04-24 06:00:00,8948.0 -2013-04-24 07:00:00,9667.0 -2013-04-24 08:00:00,10653.0 -2013-04-24 09:00:00,11403.0 -2013-04-24 10:00:00,11751.0 -2013-04-24 11:00:00,11829.0 -2013-04-24 12:00:00,11747.0 -2013-04-24 13:00:00,11607.0 -2013-04-24 14:00:00,11484.0 -2013-04-24 15:00:00,11424.0 -2013-04-24 16:00:00,11268.0 -2013-04-24 17:00:00,11094.0 -2013-04-24 18:00:00,10955.0 -2013-04-24 19:00:00,10803.0 -2013-04-24 20:00:00,10595.0 -2013-04-24 21:00:00,10886.0 -2013-04-24 22:00:00,11415.0 -2013-04-24 23:00:00,11102.0 -2013-04-25 00:00:00,10372.0 -2013-04-23 01:00:00,9188.0 -2013-04-23 02:00:00,8680.0 -2013-04-23 03:00:00,8360.0 -2013-04-23 04:00:00,8190.0 -2013-04-23 05:00:00,8152.0 -2013-04-23 06:00:00,8356.0 -2013-04-23 07:00:00,9084.0 -2013-04-23 08:00:00,9988.0 -2013-04-23 09:00:00,10652.0 -2013-04-23 10:00:00,11000.0 -2013-04-23 11:00:00,11151.0 -2013-04-23 12:00:00,11313.0 -2013-04-23 13:00:00,11391.0 -2013-04-23 14:00:00,11450.0 -2013-04-23 15:00:00,11587.0 -2013-04-23 16:00:00,11478.0 -2013-04-23 17:00:00,11414.0 -2013-04-23 18:00:00,11488.0 -2013-04-23 19:00:00,11579.0 -2013-04-23 20:00:00,11573.0 -2013-04-23 21:00:00,11724.0 -2013-04-23 22:00:00,11691.0 -2013-04-23 23:00:00,11201.0 -2013-04-24 00:00:00,10356.0 -2013-04-22 01:00:00,8938.0 -2013-04-22 02:00:00,8613.0 -2013-04-22 03:00:00,8407.0 -2013-04-22 04:00:00,8350.0 -2013-04-22 05:00:00,8353.0 -2013-04-22 06:00:00,8677.0 -2013-04-22 07:00:00,9493.0 -2013-04-22 08:00:00,10332.0 -2013-04-22 09:00:00,10942.0 -2013-04-22 10:00:00,11117.0 -2013-04-22 11:00:00,11138.0 -2013-04-22 12:00:00,11200.0 -2013-04-22 13:00:00,11216.0 -2013-04-22 14:00:00,11139.0 -2013-04-22 15:00:00,11180.0 -2013-04-22 16:00:00,11100.0 -2013-04-22 17:00:00,10916.0 -2013-04-22 18:00:00,10769.0 -2013-04-22 19:00:00,10636.0 -2013-04-22 20:00:00,10454.0 -2013-04-22 21:00:00,10760.0 -2013-04-22 22:00:00,11179.0 -2013-04-22 23:00:00,10755.0 -2013-04-23 00:00:00,9955.0 -2013-04-21 01:00:00,9481.0 -2013-04-21 02:00:00,9126.0 -2013-04-21 03:00:00,8851.0 -2013-04-21 04:00:00,8697.0 -2013-04-21 05:00:00,8610.0 -2013-04-21 06:00:00,8683.0 -2013-04-21 07:00:00,8791.0 -2013-04-21 08:00:00,8764.0 -2013-04-21 09:00:00,8934.0 -2013-04-21 10:00:00,9213.0 -2013-04-21 11:00:00,9252.0 -2013-04-21 12:00:00,9366.0 -2013-04-21 13:00:00,9349.0 -2013-04-21 14:00:00,9284.0 -2013-04-21 15:00:00,9235.0 -2013-04-21 16:00:00,9126.0 -2013-04-21 17:00:00,9047.0 -2013-04-21 18:00:00,9058.0 -2013-04-21 19:00:00,9103.0 -2013-04-21 20:00:00,9249.0 -2013-04-21 21:00:00,9662.0 -2013-04-21 22:00:00,10228.0 -2013-04-21 23:00:00,10027.0 -2013-04-22 00:00:00,9502.0 -2013-04-20 01:00:00,10280.0 -2013-04-20 02:00:00,9729.0 -2013-04-20 03:00:00,9393.0 -2013-04-20 04:00:00,9181.0 -2013-04-20 05:00:00,9081.0 -2013-04-20 06:00:00,9146.0 -2013-04-20 07:00:00,9381.0 -2013-04-20 08:00:00,9561.0 -2013-04-20 09:00:00,9823.0 -2013-04-20 10:00:00,10133.0 -2013-04-20 11:00:00,10290.0 -2013-04-20 12:00:00,10349.0 -2013-04-20 13:00:00,10281.0 -2013-04-20 14:00:00,10118.0 -2013-04-20 15:00:00,9916.0 -2013-04-20 16:00:00,9689.0 -2013-04-20 17:00:00,9577.0 -2013-04-20 18:00:00,9514.0 -2013-04-20 19:00:00,9551.0 -2013-04-20 20:00:00,9581.0 -2013-04-20 21:00:00,9991.0 -2013-04-20 22:00:00,10576.0 -2013-04-20 23:00:00,10403.0 -2013-04-21 00:00:00,10006.0 -2013-04-19 01:00:00,9836.0 -2013-04-19 02:00:00,9304.0 -2013-04-19 03:00:00,9073.0 -2013-04-19 04:00:00,8938.0 -2013-04-19 05:00:00,8930.0 -2013-04-19 06:00:00,9161.0 -2013-04-19 07:00:00,9857.0 -2013-04-19 08:00:00,10879.0 -2013-04-19 09:00:00,11623.0 -2013-04-19 10:00:00,11949.0 -2013-04-19 11:00:00,12057.0 -2013-04-19 12:00:00,12261.0 -2013-04-19 13:00:00,12245.0 -2013-04-19 14:00:00,12186.0 -2013-04-19 15:00:00,12166.0 -2013-04-19 16:00:00,12068.0 -2013-04-19 17:00:00,11953.0 -2013-04-19 18:00:00,11915.0 -2013-04-19 19:00:00,11758.0 -2013-04-19 20:00:00,11695.0 -2013-04-19 21:00:00,11928.0 -2013-04-19 22:00:00,12012.0 -2013-04-19 23:00:00,11665.0 -2013-04-20 00:00:00,10939.0 -2013-04-18 01:00:00,9869.0 -2013-04-18 02:00:00,9372.0 -2013-04-18 03:00:00,9091.0 -2013-04-18 04:00:00,8916.0 -2013-04-18 05:00:00,8880.0 -2013-04-18 06:00:00,9118.0 -2013-04-18 07:00:00,9762.0 -2013-04-18 08:00:00,10783.0 -2013-04-18 09:00:00,11556.0 -2013-04-18 10:00:00,11694.0 -2013-04-18 11:00:00,11876.0 -2013-04-18 12:00:00,12003.0 -2013-04-18 13:00:00,11924.0 -2013-04-18 14:00:00,11924.0 -2013-04-18 15:00:00,11917.0 -2013-04-18 16:00:00,11778.0 -2013-04-18 17:00:00,11658.0 -2013-04-18 18:00:00,11612.0 -2013-04-18 19:00:00,11660.0 -2013-04-18 20:00:00,11601.0 -2013-04-18 21:00:00,11844.0 -2013-04-18 22:00:00,11847.0 -2013-04-18 23:00:00,11323.0 -2013-04-19 00:00:00,10542.0 -2013-04-17 01:00:00,9544.0 -2013-04-17 02:00:00,9050.0 -2013-04-17 03:00:00,8768.0 -2013-04-17 04:00:00,8600.0 -2013-04-17 05:00:00,8600.0 -2013-04-17 06:00:00,8826.0 -2013-04-17 07:00:00,9539.0 -2013-04-17 08:00:00,10470.0 -2013-04-17 09:00:00,11186.0 -2013-04-17 10:00:00,11566.0 -2013-04-17 11:00:00,11829.0 -2013-04-17 12:00:00,11950.0 -2013-04-17 13:00:00,12021.0 -2013-04-17 14:00:00,12070.0 -2013-04-17 15:00:00,12124.0 -2013-04-17 16:00:00,12097.0 -2013-04-17 17:00:00,12196.0 -2013-04-17 18:00:00,12255.0 -2013-04-17 19:00:00,12236.0 -2013-04-17 20:00:00,11978.0 -2013-04-17 21:00:00,12073.0 -2013-04-17 22:00:00,11901.0 -2013-04-17 23:00:00,11389.0 -2013-04-18 00:00:00,10584.0 -2013-04-16 01:00:00,9337.0 -2013-04-16 02:00:00,8816.0 -2013-04-16 03:00:00,8545.0 -2013-04-16 04:00:00,8416.0 -2013-04-16 05:00:00,8385.0 -2013-04-16 06:00:00,8593.0 -2013-04-16 07:00:00,9331.0 -2013-04-16 08:00:00,10287.0 -2013-04-16 09:00:00,10939.0 -2013-04-16 10:00:00,11073.0 -2013-04-16 11:00:00,11171.0 -2013-04-16 12:00:00,11241.0 -2013-04-16 13:00:00,11223.0 -2013-04-16 14:00:00,11192.0 -2013-04-16 15:00:00,11176.0 -2013-04-16 16:00:00,11091.0 -2013-04-16 17:00:00,10937.0 -2013-04-16 18:00:00,10848.0 -2013-04-16 19:00:00,10830.0 -2013-04-16 20:00:00,10805.0 -2013-04-16 21:00:00,11260.0 -2013-04-16 22:00:00,11472.0 -2013-04-16 23:00:00,11020.0 -2013-04-17 00:00:00,10287.0 -2013-04-15 01:00:00,8757.0 -2013-04-15 02:00:00,8397.0 -2013-04-15 03:00:00,8136.0 -2013-04-15 04:00:00,7992.0 -2013-04-15 05:00:00,7997.0 -2013-04-15 06:00:00,8148.0 -2013-04-15 07:00:00,8924.0 -2013-04-15 08:00:00,9982.0 -2013-04-15 09:00:00,10687.0 -2013-04-15 10:00:00,11062.0 -2013-04-15 11:00:00,11198.0 -2013-04-15 12:00:00,11311.0 -2013-04-15 13:00:00,11328.0 -2013-04-15 14:00:00,11329.0 -2013-04-15 15:00:00,11370.0 -2013-04-15 16:00:00,11264.0 -2013-04-15 17:00:00,11193.0 -2013-04-15 18:00:00,11252.0 -2013-04-15 19:00:00,11246.0 -2013-04-15 20:00:00,11230.0 -2013-04-15 21:00:00,11497.0 -2013-04-15 22:00:00,11400.0 -2013-04-15 23:00:00,10895.0 -2013-04-16 00:00:00,10063.0 -2013-04-14 01:00:00,9397.0 -2013-04-14 02:00:00,8930.0 -2013-04-14 03:00:00,8686.0 -2013-04-14 04:00:00,8460.0 -2013-04-14 05:00:00,8456.0 -2013-04-14 06:00:00,8466.0 -2013-04-14 07:00:00,8661.0 -2013-04-14 08:00:00,8756.0 -2013-04-14 09:00:00,8863.0 -2013-04-14 10:00:00,9146.0 -2013-04-14 11:00:00,9489.0 -2013-04-14 12:00:00,9537.0 -2013-04-14 13:00:00,9425.0 -2013-04-14 14:00:00,9329.0 -2013-04-14 15:00:00,9231.0 -2013-04-14 16:00:00,9128.0 -2013-04-14 17:00:00,9059.0 -2013-04-14 18:00:00,9074.0 -2013-04-14 19:00:00,9185.0 -2013-04-14 20:00:00,9266.0 -2013-04-14 21:00:00,9827.0 -2013-04-14 22:00:00,10213.0 -2013-04-14 23:00:00,9925.0 -2013-04-15 00:00:00,9331.0 -2013-04-13 01:00:00,10025.0 -2013-04-13 02:00:00,9471.0 -2013-04-13 03:00:00,9139.0 -2013-04-13 04:00:00,8948.0 -2013-04-13 05:00:00,8916.0 -2013-04-13 06:00:00,8927.0 -2013-04-13 07:00:00,9227.0 -2013-04-13 08:00:00,9592.0 -2013-04-13 09:00:00,9905.0 -2013-04-13 10:00:00,10392.0 -2013-04-13 11:00:00,10603.0 -2013-04-13 12:00:00,10699.0 -2013-04-13 13:00:00,10667.0 -2013-04-13 14:00:00,10504.0 -2013-04-13 15:00:00,10208.0 -2013-04-13 16:00:00,10026.0 -2013-04-13 17:00:00,9848.0 -2013-04-13 18:00:00,9720.0 -2013-04-13 19:00:00,9710.0 -2013-04-13 20:00:00,9800.0 -2013-04-13 21:00:00,10227.0 -2013-04-13 22:00:00,10608.0 -2013-04-13 23:00:00,10435.0 -2013-04-14 00:00:00,9905.0 -2013-04-12 01:00:00,9949.0 -2013-04-12 02:00:00,9407.0 -2013-04-12 03:00:00,9103.0 -2013-04-12 04:00:00,8931.0 -2013-04-12 05:00:00,8972.0 -2013-04-12 06:00:00,9241.0 -2013-04-12 07:00:00,9924.0 -2013-04-12 08:00:00,11012.0 -2013-04-12 09:00:00,11620.0 -2013-04-12 10:00:00,11945.0 -2013-04-12 11:00:00,12023.0 -2013-04-12 12:00:00,12057.0 -2013-04-12 13:00:00,11915.0 -2013-04-12 14:00:00,11799.0 -2013-04-12 15:00:00,11823.0 -2013-04-12 16:00:00,11658.0 -2013-04-12 17:00:00,11658.0 -2013-04-12 18:00:00,11675.0 -2013-04-12 19:00:00,11643.0 -2013-04-12 20:00:00,11663.0 -2013-04-12 21:00:00,11852.0 -2013-04-12 22:00:00,11794.0 -2013-04-12 23:00:00,11468.0 -2013-04-13 00:00:00,10734.0 -2013-04-11 01:00:00,10222.0 -2013-04-11 02:00:00,9787.0 -2013-04-11 03:00:00,9466.0 -2013-04-11 04:00:00,9229.0 -2013-04-11 05:00:00,9139.0 -2013-04-11 06:00:00,9315.0 -2013-04-11 07:00:00,10013.0 -2013-04-11 08:00:00,11122.0 -2013-04-11 09:00:00,11767.0 -2013-04-11 10:00:00,12001.0 -2013-04-11 11:00:00,12128.0 -2013-04-11 12:00:00,12204.0 -2013-04-11 13:00:00,12119.0 -2013-04-11 14:00:00,12058.0 -2013-04-11 15:00:00,11997.0 -2013-04-11 16:00:00,11856.0 -2013-04-11 17:00:00,11729.0 -2013-04-11 18:00:00,11724.0 -2013-04-11 19:00:00,11732.0 -2013-04-11 20:00:00,11742.0 -2013-04-11 21:00:00,11928.0 -2013-04-11 22:00:00,11908.0 -2013-04-11 23:00:00,11467.0 -2013-04-12 00:00:00,10687.0 -2013-04-10 01:00:00,9710.0 -2013-04-10 02:00:00,9233.0 -2013-04-10 03:00:00,8933.0 -2013-04-10 04:00:00,8743.0 -2013-04-10 05:00:00,8743.0 -2013-04-10 06:00:00,9011.0 -2013-04-10 07:00:00,9776.0 -2013-04-10 08:00:00,10985.0 -2013-04-10 09:00:00,11812.0 -2013-04-10 10:00:00,12083.0 -2013-04-10 11:00:00,11936.0 -2013-04-10 12:00:00,12113.0 -2013-04-10 13:00:00,12078.0 -2013-04-10 14:00:00,12060.0 -2013-04-10 15:00:00,12133.0 -2013-04-10 16:00:00,12016.0 -2013-04-10 17:00:00,11936.0 -2013-04-10 18:00:00,11923.0 -2013-04-10 19:00:00,11955.0 -2013-04-10 20:00:00,11874.0 -2013-04-10 21:00:00,12184.0 -2013-04-10 22:00:00,12153.0 -2013-04-10 23:00:00,11675.0 -2013-04-11 00:00:00,10911.0 -2013-04-09 01:00:00,9322.0 -2013-04-09 02:00:00,8838.0 -2013-04-09 03:00:00,8595.0 -2013-04-09 04:00:00,8462.0 -2013-04-09 05:00:00,8473.0 -2013-04-09 06:00:00,8701.0 -2013-04-09 07:00:00,9425.0 -2013-04-09 08:00:00,10477.0 -2013-04-09 09:00:00,11227.0 -2013-04-09 10:00:00,11576.0 -2013-04-09 11:00:00,11730.0 -2013-04-09 12:00:00,11808.0 -2013-04-09 13:00:00,11742.0 -2013-04-09 14:00:00,11571.0 -2013-04-09 15:00:00,11531.0 -2013-04-09 16:00:00,11364.0 -2013-04-09 17:00:00,11241.0 -2013-04-09 18:00:00,11178.0 -2013-04-09 19:00:00,11182.0 -2013-04-09 20:00:00,11219.0 -2013-04-09 21:00:00,11719.0 -2013-04-09 22:00:00,11801.0 -2013-04-09 23:00:00,11332.0 -2013-04-10 00:00:00,10504.0 -2013-04-08 01:00:00,8857.0 -2013-04-08 02:00:00,8448.0 -2013-04-08 03:00:00,8275.0 -2013-04-08 04:00:00,8172.0 -2013-04-08 05:00:00,8204.0 -2013-04-08 06:00:00,8457.0 -2013-04-08 07:00:00,9244.0 -2013-04-08 08:00:00,10483.0 -2013-04-08 09:00:00,11205.0 -2013-04-08 10:00:00,11391.0 -2013-04-08 11:00:00,11480.0 -2013-04-08 12:00:00,11482.0 -2013-04-08 13:00:00,11408.0 -2013-04-08 14:00:00,11328.0 -2013-04-08 15:00:00,11247.0 -2013-04-08 16:00:00,11090.0 -2013-04-08 17:00:00,10959.0 -2013-04-08 18:00:00,10829.0 -2013-04-08 19:00:00,10673.0 -2013-04-08 20:00:00,10551.0 -2013-04-08 21:00:00,11038.0 -2013-04-08 22:00:00,11225.0 -2013-04-08 23:00:00,10793.0 -2013-04-09 00:00:00,10038.0 -2013-04-07 01:00:00,8869.0 -2013-04-07 02:00:00,8452.0 -2013-04-07 03:00:00,8119.0 -2013-04-07 04:00:00,7969.0 -2013-04-07 05:00:00,7850.0 -2013-04-07 06:00:00,7891.0 -2013-04-07 07:00:00,7974.0 -2013-04-07 08:00:00,8145.0 -2013-04-07 09:00:00,8227.0 -2013-04-07 10:00:00,8495.0 -2013-04-07 11:00:00,8715.0 -2013-04-07 12:00:00,8830.0 -2013-04-07 13:00:00,8852.0 -2013-04-07 14:00:00,8890.0 -2013-04-07 15:00:00,8814.0 -2013-04-07 16:00:00,8792.0 -2013-04-07 17:00:00,8769.0 -2013-04-07 18:00:00,8819.0 -2013-04-07 19:00:00,8907.0 -2013-04-07 20:00:00,9097.0 -2013-04-07 21:00:00,9701.0 -2013-04-07 22:00:00,10148.0 -2013-04-07 23:00:00,9894.0 -2013-04-08 00:00:00,9369.0 -2013-04-06 01:00:00,9854.0 -2013-04-06 02:00:00,9408.0 -2013-04-06 03:00:00,9102.0 -2013-04-06 04:00:00,8945.0 -2013-04-06 05:00:00,8866.0 -2013-04-06 06:00:00,8880.0 -2013-04-06 07:00:00,9164.0 -2013-04-06 08:00:00,9521.0 -2013-04-06 09:00:00,9763.0 -2013-04-06 10:00:00,10151.0 -2013-04-06 11:00:00,10269.0 -2013-04-06 12:00:00,10354.0 -2013-04-06 13:00:00,10238.0 -2013-04-06 14:00:00,10020.0 -2013-04-06 15:00:00,9809.0 -2013-04-06 16:00:00,9635.0 -2013-04-06 17:00:00,9523.0 -2013-04-06 18:00:00,9597.0 -2013-04-06 19:00:00,9659.0 -2013-04-06 20:00:00,9737.0 -2013-04-06 21:00:00,10210.0 -2013-04-06 22:00:00,10226.0 -2013-04-06 23:00:00,9978.0 -2013-04-07 00:00:00,9454.0 -2013-04-05 01:00:00,9694.0 -2013-04-05 02:00:00,9167.0 -2013-04-05 03:00:00,8923.0 -2013-04-05 04:00:00,8719.0 -2013-04-05 05:00:00,8782.0 -2013-04-05 06:00:00,9034.0 -2013-04-05 07:00:00,9772.0 -2013-04-05 08:00:00,10730.0 -2013-04-05 09:00:00,11229.0 -2013-04-05 10:00:00,11433.0 -2013-04-05 11:00:00,11444.0 -2013-04-05 12:00:00,11463.0 -2013-04-05 13:00:00,11371.0 -2013-04-05 14:00:00,11317.0 -2013-04-05 15:00:00,11241.0 -2013-04-05 16:00:00,11055.0 -2013-04-05 17:00:00,10815.0 -2013-04-05 18:00:00,10685.0 -2013-04-05 19:00:00,10619.0 -2013-04-05 20:00:00,10783.0 -2013-04-05 21:00:00,11374.0 -2013-04-05 22:00:00,11539.0 -2013-04-05 23:00:00,11275.0 -2013-04-06 00:00:00,10613.0 -2013-04-04 01:00:00,10172.0 -2013-04-04 02:00:00,9691.0 -2013-04-04 03:00:00,9415.0 -2013-04-04 04:00:00,9292.0 -2013-04-04 05:00:00,9310.0 -2013-04-04 06:00:00,9569.0 -2013-04-04 07:00:00,10325.0 -2013-04-04 08:00:00,11330.0 -2013-04-04 09:00:00,11742.0 -2013-04-04 10:00:00,11836.0 -2013-04-04 11:00:00,11681.0 -2013-04-04 12:00:00,11732.0 -2013-04-04 13:00:00,11601.0 -2013-04-04 14:00:00,11434.0 -2013-04-04 15:00:00,11395.0 -2013-04-04 16:00:00,11205.0 -2013-04-04 17:00:00,11024.0 -2013-04-04 18:00:00,10847.0 -2013-04-04 19:00:00,10719.0 -2013-04-04 20:00:00,10753.0 -2013-04-04 21:00:00,11273.0 -2013-04-04 22:00:00,11427.0 -2013-04-04 23:00:00,11059.0 -2013-04-05 00:00:00,10389.0 -2013-04-03 01:00:00,10157.0 -2013-04-03 02:00:00,9689.0 -2013-04-03 03:00:00,9431.0 -2013-04-03 04:00:00,9312.0 -2013-04-03 05:00:00,9351.0 -2013-04-03 06:00:00,9608.0 -2013-04-03 07:00:00,10323.0 -2013-04-03 08:00:00,11373.0 -2013-04-03 09:00:00,11810.0 -2013-04-03 10:00:00,11859.0 -2013-04-03 11:00:00,11820.0 -2013-04-03 12:00:00,11805.0 -2013-04-03 13:00:00,11717.0 -2013-04-03 14:00:00,11620.0 -2013-04-03 15:00:00,11552.0 -2013-04-03 16:00:00,11407.0 -2013-04-03 17:00:00,11224.0 -2013-04-03 18:00:00,11073.0 -2013-04-03 19:00:00,11085.0 -2013-04-03 20:00:00,11128.0 -2013-04-03 21:00:00,11755.0 -2013-04-03 22:00:00,11973.0 -2013-04-03 23:00:00,11606.0 -2013-04-04 00:00:00,10884.0 -2013-04-02 01:00:00,10171.0 -2013-04-02 02:00:00,9729.0 -2013-04-02 03:00:00,9469.0 -2013-04-02 04:00:00,9366.0 -2013-04-02 05:00:00,9379.0 -2013-04-02 06:00:00,9648.0 -2013-04-02 07:00:00,10372.0 -2013-04-02 08:00:00,11449.0 -2013-04-02 09:00:00,11854.0 -2013-04-02 10:00:00,11954.0 -2013-04-02 11:00:00,11925.0 -2013-04-02 12:00:00,11901.0 -2013-04-02 13:00:00,11818.0 -2013-04-02 14:00:00,11732.0 -2013-04-02 15:00:00,11656.0 -2013-04-02 16:00:00,11486.0 -2013-04-02 17:00:00,11297.0 -2013-04-02 18:00:00,11217.0 -2013-04-02 19:00:00,11152.0 -2013-04-02 20:00:00,11128.0 -2013-04-02 21:00:00,11741.0 -2013-04-02 22:00:00,11956.0 -2013-04-02 23:00:00,11586.0 -2013-04-03 00:00:00,10869.0 -2013-04-01 01:00:00,8983.0 -2013-04-01 02:00:00,8663.0 -2013-04-01 03:00:00,8503.0 -2013-04-01 04:00:00,8451.0 -2013-04-01 05:00:00,8542.0 -2013-04-01 06:00:00,8861.0 -2013-04-01 07:00:00,9688.0 -2013-04-01 08:00:00,10744.0 -2013-04-01 09:00:00,11325.0 -2013-04-01 10:00:00,11601.0 -2013-04-01 11:00:00,11732.0 -2013-04-01 12:00:00,11854.0 -2013-04-01 13:00:00,11774.0 -2013-04-01 14:00:00,11702.0 -2013-04-01 15:00:00,11648.0 -2013-04-01 16:00:00,11482.0 -2013-04-01 17:00:00,11236.0 -2013-04-01 18:00:00,11153.0 -2013-04-01 19:00:00,11112.0 -2013-04-01 20:00:00,11136.0 -2013-04-01 21:00:00,11761.0 -2013-04-01 22:00:00,11992.0 -2013-04-01 23:00:00,11575.0 -2013-04-02 00:00:00,10865.0 -2013-03-31 01:00:00,8918.0 -2013-03-31 02:00:00,8477.0 -2013-03-31 03:00:00,8140.0 -2013-03-31 04:00:00,7990.0 -2013-03-31 05:00:00,7907.0 -2013-03-31 06:00:00,7923.0 -2013-03-31 07:00:00,8006.0 -2013-03-31 08:00:00,8235.0 -2013-03-31 09:00:00,8298.0 -2013-03-31 10:00:00,8596.0 -2013-03-31 11:00:00,8729.0 -2013-03-31 12:00:00,8787.0 -2013-03-31 13:00:00,8723.0 -2013-03-31 14:00:00,8707.0 -2013-03-31 15:00:00,8585.0 -2013-03-31 16:00:00,8517.0 -2013-03-31 17:00:00,8409.0 -2013-03-31 18:00:00,8437.0 -2013-03-31 19:00:00,8511.0 -2013-03-31 20:00:00,8759.0 -2013-03-31 21:00:00,9561.0 -2013-03-31 22:00:00,9923.0 -2013-03-31 23:00:00,9799.0 -2013-04-01 00:00:00,9403.0 -2013-03-30 01:00:00,9361.0 -2013-03-30 02:00:00,8931.0 -2013-03-30 03:00:00,8749.0 -2013-03-30 04:00:00,8557.0 -2013-03-30 05:00:00,8542.0 -2013-03-30 06:00:00,8591.0 -2013-03-30 07:00:00,8829.0 -2013-03-30 08:00:00,9223.0 -2013-03-30 09:00:00,9318.0 -2013-03-30 10:00:00,9636.0 -2013-03-30 11:00:00,9671.0 -2013-03-30 12:00:00,9700.0 -2013-03-30 13:00:00,9605.0 -2013-03-30 14:00:00,9477.0 -2013-03-30 15:00:00,9335.0 -2013-03-30 16:00:00,9242.0 -2013-03-30 17:00:00,9155.0 -2013-03-30 18:00:00,9170.0 -2013-03-30 19:00:00,9307.0 -2013-03-30 20:00:00,9516.0 -2013-03-30 21:00:00,10072.0 -2013-03-30 22:00:00,10049.0 -2013-03-30 23:00:00,9831.0 -2013-03-31 00:00:00,9447.0 -2013-03-29 01:00:00,9776.0 -2013-03-29 02:00:00,9282.0 -2013-03-29 03:00:00,8986.0 -2013-03-29 04:00:00,8800.0 -2013-03-29 05:00:00,8757.0 -2013-03-29 06:00:00,8956.0 -2013-03-29 07:00:00,9471.0 -2013-03-29 08:00:00,10121.0 -2013-03-29 09:00:00,10359.0 -2013-03-29 10:00:00,10522.0 -2013-03-29 11:00:00,10588.0 -2013-03-29 12:00:00,10569.0 -2013-03-29 13:00:00,10483.0 -2013-03-29 14:00:00,10367.0 -2013-03-29 15:00:00,10254.0 -2013-03-29 16:00:00,10082.0 -2013-03-29 17:00:00,9925.0 -2013-03-29 18:00:00,9840.0 -2013-03-29 19:00:00,9823.0 -2013-03-29 20:00:00,9854.0 -2013-03-29 21:00:00,10438.0 -2013-03-29 22:00:00,10629.0 -2013-03-29 23:00:00,10454.0 -2013-03-30 00:00:00,9949.0 -2013-03-28 01:00:00,10296.0 -2013-03-28 02:00:00,9818.0 -2013-03-28 03:00:00,9577.0 -2013-03-28 04:00:00,9447.0 -2013-03-28 05:00:00,9449.0 -2013-03-28 06:00:00,9702.0 -2013-03-28 07:00:00,10389.0 -2013-03-28 08:00:00,11258.0 -2013-03-28 09:00:00,11612.0 -2013-03-28 10:00:00,11735.0 -2013-03-28 11:00:00,11687.0 -2013-03-28 12:00:00,11613.0 -2013-03-28 13:00:00,11508.0 -2013-03-28 14:00:00,11407.0 -2013-03-28 15:00:00,11342.0 -2013-03-28 16:00:00,11109.0 -2013-03-28 17:00:00,10900.0 -2013-03-28 18:00:00,10753.0 -2013-03-28 19:00:00,10667.0 -2013-03-28 20:00:00,10630.0 -2013-03-28 21:00:00,11266.0 -2013-03-28 22:00:00,11360.0 -2013-03-28 23:00:00,11046.0 -2013-03-29 00:00:00,10412.0 -2013-03-27 01:00:00,10279.0 -2013-03-27 02:00:00,9803.0 -2013-03-27 03:00:00,9497.0 -2013-03-27 04:00:00,9348.0 -2013-03-27 05:00:00,9346.0 -2013-03-27 06:00:00,9583.0 -2013-03-27 07:00:00,10237.0 -2013-03-27 08:00:00,11198.0 -2013-03-27 09:00:00,11641.0 -2013-03-27 10:00:00,11926.0 -2013-03-27 11:00:00,11926.0 -2013-03-27 12:00:00,11944.0 -2013-03-27 13:00:00,11885.0 -2013-03-27 14:00:00,11897.0 -2013-03-27 15:00:00,11864.0 -2013-03-27 16:00:00,11740.0 -2013-03-27 17:00:00,11595.0 -2013-03-27 18:00:00,11527.0 -2013-03-27 19:00:00,11491.0 -2013-03-27 20:00:00,11512.0 -2013-03-27 21:00:00,12006.0 -2013-03-27 22:00:00,11971.0 -2013-03-27 23:00:00,11592.0 -2013-03-28 00:00:00,10944.0 -2013-03-26 01:00:00,10469.0 -2013-03-26 02:00:00,10002.0 -2013-03-26 03:00:00,9686.0 -2013-03-26 04:00:00,9528.0 -2013-03-26 05:00:00,9527.0 -2013-03-26 06:00:00,9751.0 -2013-03-26 07:00:00,10399.0 -2013-03-26 08:00:00,11396.0 -2013-03-26 09:00:00,11801.0 -2013-03-26 10:00:00,12074.0 -2013-03-26 11:00:00,12113.0 -2013-03-26 12:00:00,12034.0 -2013-03-26 13:00:00,11955.0 -2013-03-26 14:00:00,11925.0 -2013-03-26 15:00:00,11871.0 -2013-03-26 16:00:00,11773.0 -2013-03-26 17:00:00,11664.0 -2013-03-26 18:00:00,11637.0 -2013-03-26 19:00:00,11629.0 -2013-03-26 20:00:00,11600.0 -2013-03-26 21:00:00,12059.0 -2013-03-26 22:00:00,12007.0 -2013-03-26 23:00:00,11641.0 -2013-03-27 00:00:00,10934.0 -2013-03-25 01:00:00,10037.0 -2013-03-25 02:00:00,9682.0 -2013-03-25 03:00:00,9474.0 -2013-03-25 04:00:00,9398.0 -2013-03-25 05:00:00,9415.0 -2013-03-25 06:00:00,9713.0 -2013-03-25 07:00:00,10392.0 -2013-03-25 08:00:00,11478.0 -2013-03-25 09:00:00,11990.0 -2013-03-25 10:00:00,12369.0 -2013-03-25 11:00:00,12512.0 -2013-03-25 12:00:00,12554.0 -2013-03-25 13:00:00,12514.0 -2013-03-25 14:00:00,12431.0 -2013-03-25 15:00:00,12402.0 -2013-03-25 16:00:00,12310.0 -2013-03-25 17:00:00,12219.0 -2013-03-25 18:00:00,12176.0 -2013-03-25 19:00:00,12225.0 -2013-03-25 20:00:00,12222.0 -2013-03-25 21:00:00,12598.0 -2013-03-25 22:00:00,12388.0 -2013-03-25 23:00:00,11932.0 -2013-03-26 00:00:00,11190.0 -2013-03-24 01:00:00,9799.0 -2013-03-24 02:00:00,9293.0 -2013-03-24 03:00:00,9102.0 -2013-03-24 04:00:00,8916.0 -2013-03-24 05:00:00,8860.0 -2013-03-24 06:00:00,8874.0 -2013-03-24 07:00:00,9046.0 -2013-03-24 08:00:00,9298.0 -2013-03-24 09:00:00,9443.0 -2013-03-24 10:00:00,9740.0 -2013-03-24 11:00:00,9986.0 -2013-03-24 12:00:00,10033.0 -2013-03-24 13:00:00,10203.0 -2013-03-24 14:00:00,10152.0 -2013-03-24 15:00:00,10200.0 -2013-03-24 16:00:00,10254.0 -2013-03-24 17:00:00,10373.0 -2013-03-24 18:00:00,10476.0 -2013-03-24 19:00:00,10732.0 -2013-03-24 20:00:00,11062.0 -2013-03-24 21:00:00,11448.0 -2013-03-24 22:00:00,11369.0 -2013-03-24 23:00:00,11089.0 -2013-03-25 00:00:00,10584.0 -2013-03-23 01:00:00,10376.0 -2013-03-23 02:00:00,9850.0 -2013-03-23 03:00:00,9554.0 -2013-03-23 04:00:00,9357.0 -2013-03-23 05:00:00,9333.0 -2013-03-23 06:00:00,9385.0 -2013-03-23 07:00:00,9697.0 -2013-03-23 08:00:00,10153.0 -2013-03-23 09:00:00,10330.0 -2013-03-23 10:00:00,10538.0 -2013-03-23 11:00:00,10634.0 -2013-03-23 12:00:00,10599.0 -2013-03-23 13:00:00,10505.0 -2013-03-23 14:00:00,10314.0 -2013-03-23 15:00:00,10087.0 -2013-03-23 16:00:00,9859.0 -2013-03-23 17:00:00,9750.0 -2013-03-23 18:00:00,9681.0 -2013-03-23 19:00:00,9835.0 -2013-03-23 20:00:00,10094.0 -2013-03-23 21:00:00,10906.0 -2013-03-23 22:00:00,10981.0 -2013-03-23 23:00:00,10815.0 -2013-03-24 00:00:00,10324.0 -2013-03-22 01:00:00,10777.0 -2013-03-22 02:00:00,10305.0 -2013-03-22 03:00:00,10036.0 -2013-03-22 04:00:00,9900.0 -2013-03-22 05:00:00,9928.0 -2013-03-22 06:00:00,10191.0 -2013-03-22 07:00:00,10891.0 -2013-03-22 08:00:00,12081.0 -2013-03-22 09:00:00,12457.0 -2013-03-22 10:00:00,12459.0 -2013-03-22 11:00:00,12421.0 -2013-03-22 12:00:00,12344.0 -2013-03-22 13:00:00,12176.0 -2013-03-22 14:00:00,12003.0 -2013-03-22 15:00:00,11884.0 -2013-03-22 16:00:00,11603.0 -2013-03-22 17:00:00,11359.0 -2013-03-22 18:00:00,11189.0 -2013-03-22 19:00:00,11112.0 -2013-03-22 20:00:00,11184.0 -2013-03-22 21:00:00,11869.0 -2013-03-22 22:00:00,11899.0 -2013-03-22 23:00:00,11658.0 -2013-03-23 00:00:00,11042.0 -2013-03-21 01:00:00,11351.0 -2013-03-21 02:00:00,10797.0 -2013-03-21 03:00:00,10542.0 -2013-03-21 04:00:00,10395.0 -2013-03-21 05:00:00,10398.0 -2013-03-21 06:00:00,10649.0 -2013-03-21 07:00:00,11360.0 -2013-03-21 08:00:00,12559.0 -2013-03-21 09:00:00,12973.0 -2013-03-21 10:00:00,13002.0 -2013-03-21 11:00:00,12912.0 -2013-03-21 12:00:00,12866.0 -2013-03-21 13:00:00,12660.0 -2013-03-21 14:00:00,12498.0 -2013-03-21 15:00:00,12394.0 -2013-03-21 16:00:00,12152.0 -2013-03-21 17:00:00,11916.0 -2013-03-21 18:00:00,11786.0 -2013-03-21 19:00:00,11715.0 -2013-03-21 20:00:00,11790.0 -2013-03-21 21:00:00,12526.0 -2013-03-21 22:00:00,12616.0 -2013-03-21 23:00:00,12237.0 -2013-03-22 00:00:00,11501.0 -2013-03-20 01:00:00,11116.0 -2013-03-20 02:00:00,10633.0 -2013-03-20 03:00:00,10383.0 -2013-03-20 04:00:00,10295.0 -2013-03-20 05:00:00,10341.0 -2013-03-20 06:00:00,10631.0 -2013-03-20 07:00:00,11436.0 -2013-03-20 08:00:00,12681.0 -2013-03-20 09:00:00,13146.0 -2013-03-20 10:00:00,13230.0 -2013-03-20 11:00:00,13255.0 -2013-03-20 12:00:00,13249.0 -2013-03-20 13:00:00,13150.0 -2013-03-20 14:00:00,13077.0 -2013-03-20 15:00:00,12998.0 -2013-03-20 16:00:00,12838.0 -2013-03-20 17:00:00,12730.0 -2013-03-20 18:00:00,12708.0 -2013-03-20 19:00:00,12799.0 -2013-03-20 20:00:00,12997.0 -2013-03-20 21:00:00,13514.0 -2013-03-20 22:00:00,13353.0 -2013-03-20 23:00:00,12885.0 -2013-03-21 00:00:00,12085.0 -2013-03-19 01:00:00,11261.0 -2013-03-19 02:00:00,10780.0 -2013-03-19 03:00:00,10545.0 -2013-03-19 04:00:00,10406.0 -2013-03-19 05:00:00,10421.0 -2013-03-19 06:00:00,10699.0 -2013-03-19 07:00:00,11437.0 -2013-03-19 08:00:00,12684.0 -2013-03-19 09:00:00,13214.0 -2013-03-19 10:00:00,13276.0 -2013-03-19 11:00:00,13227.0 -2013-03-19 12:00:00,13209.0 -2013-03-19 13:00:00,13074.0 -2013-03-19 14:00:00,12960.0 -2013-03-19 15:00:00,12863.0 -2013-03-19 16:00:00,12626.0 -2013-03-19 17:00:00,12368.0 -2013-03-19 18:00:00,12239.0 -2013-03-19 19:00:00,12189.0 -2013-03-19 20:00:00,12276.0 -2013-03-19 21:00:00,13007.0 -2013-03-19 22:00:00,13074.0 -2013-03-19 23:00:00,12639.0 -2013-03-20 00:00:00,11841.0 -2013-03-18 01:00:00,10168.0 -2013-03-18 02:00:00,9774.0 -2013-03-18 03:00:00,9602.0 -2013-03-18 04:00:00,9480.0 -2013-03-18 05:00:00,9529.0 -2013-03-18 06:00:00,9811.0 -2013-03-18 07:00:00,10583.0 -2013-03-18 08:00:00,11827.0 -2013-03-18 09:00:00,12555.0 -2013-03-18 10:00:00,12661.0 -2013-03-18 11:00:00,12790.0 -2013-03-18 12:00:00,12908.0 -2013-03-18 13:00:00,12890.0 -2013-03-18 14:00:00,12763.0 -2013-03-18 15:00:00,12723.0 -2013-03-18 16:00:00,12628.0 -2013-03-18 17:00:00,12532.0 -2013-03-18 18:00:00,12518.0 -2013-03-18 19:00:00,12639.0 -2013-03-18 20:00:00,12874.0 -2013-03-18 21:00:00,13244.0 -2013-03-18 22:00:00,13118.0 -2013-03-18 23:00:00,12721.0 -2013-03-19 00:00:00,11997.0 -2013-03-17 01:00:00,10139.0 -2013-03-17 02:00:00,9736.0 -2013-03-17 03:00:00,9452.0 -2013-03-17 04:00:00,9334.0 -2013-03-17 05:00:00,9282.0 -2013-03-17 06:00:00,9328.0 -2013-03-17 07:00:00,9435.0 -2013-03-17 08:00:00,9728.0 -2013-03-17 09:00:00,9786.0 -2013-03-17 10:00:00,10062.0 -2013-03-17 11:00:00,10297.0 -2013-03-17 12:00:00,10409.0 -2013-03-17 13:00:00,10447.0 -2013-03-17 14:00:00,10392.0 -2013-03-17 15:00:00,10282.0 -2013-03-17 16:00:00,10164.0 -2013-03-17 17:00:00,10060.0 -2013-03-17 18:00:00,10128.0 -2013-03-17 19:00:00,10295.0 -2013-03-17 20:00:00,10691.0 -2013-03-17 21:00:00,11466.0 -2013-03-17 22:00:00,11510.0 -2013-03-17 23:00:00,11220.0 -2013-03-18 00:00:00,10724.0 -2013-03-16 01:00:00,10512.0 -2013-03-16 02:00:00,9993.0 -2013-03-16 03:00:00,9618.0 -2013-03-16 04:00:00,9447.0 -2013-03-16 05:00:00,9281.0 -2013-03-16 06:00:00,9372.0 -2013-03-16 07:00:00,9657.0 -2013-03-16 08:00:00,10161.0 -2013-03-16 09:00:00,10627.0 -2013-03-16 10:00:00,10982.0 -2013-03-16 11:00:00,11381.0 -2013-03-16 12:00:00,11545.0 -2013-03-16 13:00:00,11494.0 -2013-03-16 14:00:00,11278.0 -2013-03-16 15:00:00,11089.0 -2013-03-16 16:00:00,10917.0 -2013-03-16 17:00:00,10772.0 -2013-03-16 18:00:00,10722.0 -2013-03-16 19:00:00,10837.0 -2013-03-16 20:00:00,11041.0 -2013-03-16 21:00:00,11538.0 -2013-03-16 22:00:00,11408.0 -2013-03-16 23:00:00,11190.0 -2013-03-17 00:00:00,10715.0 -2013-03-15 01:00:00,10790.0 -2013-03-15 02:00:00,10248.0 -2013-03-15 03:00:00,9967.0 -2013-03-15 04:00:00,9798.0 -2013-03-15 05:00:00,9802.0 -2013-03-15 06:00:00,9976.0 -2013-03-15 07:00:00,10652.0 -2013-03-15 08:00:00,11858.0 -2013-03-15 09:00:00,12293.0 -2013-03-15 10:00:00,12319.0 -2013-03-15 11:00:00,12320.0 -2013-03-15 12:00:00,12210.0 -2013-03-15 13:00:00,12081.0 -2013-03-15 14:00:00,12020.0 -2013-03-15 15:00:00,12040.0 -2013-03-15 16:00:00,11946.0 -2013-03-15 17:00:00,11826.0 -2013-03-15 18:00:00,11860.0 -2013-03-15 19:00:00,11952.0 -2013-03-15 20:00:00,12128.0 -2013-03-15 21:00:00,12382.0 -2013-03-15 22:00:00,12175.0 -2013-03-15 23:00:00,11818.0 -2013-03-16 00:00:00,11223.0 -2013-03-14 01:00:00,10927.0 -2013-03-14 02:00:00,10442.0 -2013-03-14 03:00:00,10160.0 -2013-03-14 04:00:00,9994.0 -2013-03-14 05:00:00,10006.0 -2013-03-14 06:00:00,10266.0 -2013-03-14 07:00:00,10976.0 -2013-03-14 08:00:00,12220.0 -2013-03-14 09:00:00,12715.0 -2013-03-14 10:00:00,12666.0 -2013-03-14 11:00:00,12629.0 -2013-03-14 12:00:00,12619.0 -2013-03-14 13:00:00,12537.0 -2013-03-14 14:00:00,12475.0 -2013-03-14 15:00:00,12468.0 -2013-03-14 16:00:00,12386.0 -2013-03-14 17:00:00,12236.0 -2013-03-14 18:00:00,12014.0 -2013-03-14 19:00:00,11995.0 -2013-03-14 20:00:00,12348.0 -2013-03-14 21:00:00,12833.0 -2013-03-14 22:00:00,12682.0 -2013-03-14 23:00:00,12280.0 -2013-03-15 00:00:00,11525.0 -2013-03-13 01:00:00,11072.0 -2013-03-13 02:00:00,10538.0 -2013-03-13 03:00:00,10256.0 -2013-03-13 04:00:00,10068.0 -2013-03-13 05:00:00,10048.0 -2013-03-13 06:00:00,10302.0 -2013-03-13 07:00:00,11076.0 -2013-03-13 08:00:00,12334.0 -2013-03-13 09:00:00,12810.0 -2013-03-13 10:00:00,12864.0 -2013-03-13 11:00:00,12847.0 -2013-03-13 12:00:00,12908.0 -2013-03-13 13:00:00,12792.0 -2013-03-13 14:00:00,12693.0 -2013-03-13 15:00:00,12569.0 -2013-03-13 16:00:00,12370.0 -2013-03-13 17:00:00,12165.0 -2013-03-13 18:00:00,12018.0 -2013-03-13 19:00:00,12019.0 -2013-03-13 20:00:00,12178.0 -2013-03-13 21:00:00,12897.0 -2013-03-13 22:00:00,12878.0 -2013-03-13 23:00:00,12455.0 -2013-03-14 00:00:00,11704.0 -2013-03-12 01:00:00,10707.0 -2013-03-12 02:00:00,10196.0 -2013-03-12 03:00:00,9889.0 -2013-03-12 04:00:00,9733.0 -2013-03-12 05:00:00,9698.0 -2013-03-12 06:00:00,9963.0 -2013-03-12 07:00:00,10684.0 -2013-03-12 08:00:00,11934.0 -2013-03-12 09:00:00,12578.0 -2013-03-12 10:00:00,12640.0 -2013-03-12 11:00:00,12625.0 -2013-03-12 12:00:00,12523.0 -2013-03-12 13:00:00,12492.0 -2013-03-12 14:00:00,12413.0 -2013-03-12 15:00:00,12497.0 -2013-03-12 16:00:00,12451.0 -2013-03-12 17:00:00,12420.0 -2013-03-12 18:00:00,12498.0 -2013-03-12 19:00:00,12455.0 -2013-03-12 20:00:00,12689.0 -2013-03-12 21:00:00,13197.0 -2013-03-12 22:00:00,13091.0 -2013-03-12 23:00:00,12637.0 -2013-03-13 00:00:00,11819.0 -2013-03-11 01:00:00,9498.0 -2013-03-11 02:00:00,9092.0 -2013-03-11 03:00:00,8810.0 -2013-03-11 04:00:00,8736.0 -2013-03-11 05:00:00,8743.0 -2013-03-11 06:00:00,9013.0 -2013-03-11 07:00:00,9723.0 -2013-03-11 08:00:00,11062.0 -2013-03-11 09:00:00,11895.0 -2013-03-11 10:00:00,12067.0 -2013-03-11 11:00:00,12142.0 -2013-03-11 12:00:00,12278.0 -2013-03-11 13:00:00,12323.0 -2013-03-11 14:00:00,12435.0 -2013-03-11 15:00:00,12467.0 -2013-03-11 16:00:00,12390.0 -2013-03-11 17:00:00,12329.0 -2013-03-11 18:00:00,12372.0 -2013-03-11 19:00:00,12467.0 -2013-03-11 20:00:00,12677.0 -2013-03-11 21:00:00,12917.0 -2013-03-11 22:00:00,12707.0 -2013-03-11 23:00:00,12225.0 -2013-03-12 00:00:00,11446.0 -2013-03-10 01:00:00,9739.0 -2013-03-10 02:00:00,9334.0 -2013-03-10 04:00:00,9060.0 -2013-03-10 05:00:00,8992.0 -2013-03-10 06:00:00,8927.0 -2013-03-10 07:00:00,9042.0 -2013-03-10 08:00:00,9218.0 -2013-03-10 09:00:00,9374.0 -2013-03-10 10:00:00,9468.0 -2013-03-10 11:00:00,9786.0 -2013-03-10 12:00:00,9896.0 -2013-03-10 13:00:00,10004.0 -2013-03-10 14:00:00,10012.0 -2013-03-10 15:00:00,10125.0 -2013-03-10 16:00:00,10140.0 -2013-03-10 17:00:00,10245.0 -2013-03-10 18:00:00,10387.0 -2013-03-10 19:00:00,10601.0 -2013-03-10 20:00:00,10784.0 -2013-03-10 21:00:00,11162.0 -2013-03-10 22:00:00,10998.0 -2013-03-10 23:00:00,10644.0 -2013-03-11 00:00:00,10084.0 -2013-03-09 01:00:00,10589.0 -2013-03-09 02:00:00,10076.0 -2013-03-09 03:00:00,9745.0 -2013-03-09 04:00:00,9619.0 -2013-03-09 05:00:00,9502.0 -2013-03-09 06:00:00,9582.0 -2013-03-09 07:00:00,9867.0 -2013-03-09 08:00:00,10275.0 -2013-03-09 09:00:00,10564.0 -2013-03-09 10:00:00,10990.0 -2013-03-09 11:00:00,11164.0 -2013-03-09 12:00:00,11222.0 -2013-03-09 13:00:00,11137.0 -2013-03-09 14:00:00,10955.0 -2013-03-09 15:00:00,10723.0 -2013-03-09 16:00:00,10532.0 -2013-03-09 17:00:00,10472.0 -2013-03-09 18:00:00,10693.0 -2013-03-09 19:00:00,11022.0 -2013-03-09 20:00:00,11559.0 -2013-03-09 21:00:00,11427.0 -2013-03-09 22:00:00,11253.0 -2013-03-09 23:00:00,10837.0 -2013-03-10 00:00:00,10314.0 -2013-03-08 01:00:00,10722.0 -2013-03-08 02:00:00,10298.0 -2013-03-08 03:00:00,10065.0 -2013-03-08 04:00:00,9887.0 -2013-03-08 05:00:00,9930.0 -2013-03-08 06:00:00,10212.0 -2013-03-08 07:00:00,11013.0 -2013-03-08 08:00:00,11855.0 -2013-03-08 09:00:00,12214.0 -2013-03-08 10:00:00,12227.0 -2013-03-08 11:00:00,12228.0 -2013-03-08 12:00:00,12139.0 -2013-03-08 13:00:00,12063.0 -2013-03-08 14:00:00,11891.0 -2013-03-08 15:00:00,11817.0 -2013-03-08 16:00:00,11572.0 -2013-03-08 17:00:00,11461.0 -2013-03-08 18:00:00,11502.0 -2013-03-08 19:00:00,11806.0 -2013-03-08 20:00:00,12549.0 -2013-03-08 21:00:00,12535.0 -2013-03-08 22:00:00,12249.0 -2013-03-08 23:00:00,11859.0 -2013-03-09 00:00:00,11246.0 -2013-03-07 01:00:00,10729.0 -2013-03-07 02:00:00,10228.0 -2013-03-07 03:00:00,9977.0 -2013-03-07 04:00:00,9825.0 -2013-03-07 05:00:00,9826.0 -2013-03-07 06:00:00,10100.0 -2013-03-07 07:00:00,10817.0 -2013-03-07 08:00:00,11707.0 -2013-03-07 09:00:00,12178.0 -2013-03-07 10:00:00,12228.0 -2013-03-07 11:00:00,12222.0 -2013-03-07 12:00:00,12259.0 -2013-03-07 13:00:00,12174.0 -2013-03-07 14:00:00,12028.0 -2013-03-07 15:00:00,11982.0 -2013-03-07 16:00:00,11841.0 -2013-03-07 17:00:00,11759.0 -2013-03-07 18:00:00,11863.0 -2013-03-07 19:00:00,12194.0 -2013-03-07 20:00:00,13028.0 -2013-03-07 21:00:00,12967.0 -2013-03-07 22:00:00,12695.0 -2013-03-07 23:00:00,12230.0 -2013-03-08 00:00:00,11422.0 -2013-03-06 01:00:00,10737.0 -2013-03-06 02:00:00,10286.0 -2013-03-06 03:00:00,10032.0 -2013-03-06 04:00:00,9890.0 -2013-03-06 05:00:00,9937.0 -2013-03-06 06:00:00,10230.0 -2013-03-06 07:00:00,11005.0 -2013-03-06 08:00:00,11974.0 -2013-03-06 09:00:00,12452.0 -2013-03-06 10:00:00,12712.0 -2013-03-06 11:00:00,12796.0 -2013-03-06 12:00:00,12835.0 -2013-03-06 13:00:00,12638.0 -2013-03-06 14:00:00,12516.0 -2013-03-06 15:00:00,12538.0 -2013-03-06 16:00:00,12479.0 -2013-03-06 17:00:00,12446.0 -2013-03-06 18:00:00,12558.0 -2013-03-06 19:00:00,12869.0 -2013-03-06 20:00:00,13293.0 -2013-03-06 21:00:00,13136.0 -2013-03-06 22:00:00,12833.0 -2013-03-06 23:00:00,12290.0 -2013-03-07 00:00:00,11471.0 -2013-03-05 01:00:00,10762.0 -2013-03-05 02:00:00,10296.0 -2013-03-05 03:00:00,10035.0 -2013-03-05 04:00:00,9836.0 -2013-03-05 05:00:00,9846.0 -2013-03-05 06:00:00,10088.0 -2013-03-05 07:00:00,10786.0 -2013-03-05 08:00:00,11728.0 -2013-03-05 09:00:00,12323.0 -2013-03-05 10:00:00,12641.0 -2013-03-05 11:00:00,12822.0 -2013-03-05 12:00:00,12924.0 -2013-03-05 13:00:00,13052.0 -2013-03-05 14:00:00,13049.0 -2013-03-05 15:00:00,12980.0 -2013-03-05 16:00:00,12805.0 -2013-03-05 17:00:00,12687.0 -2013-03-05 18:00:00,12746.0 -2013-03-05 19:00:00,13124.0 -2013-03-05 20:00:00,13556.0 -2013-03-05 21:00:00,13344.0 -2013-03-05 22:00:00,12979.0 -2013-03-05 23:00:00,12398.0 -2013-03-06 00:00:00,11522.0 -2013-03-04 01:00:00,10425.0 -2013-03-04 02:00:00,10079.0 -2013-03-04 03:00:00,9958.0 -2013-03-04 04:00:00,9919.0 -2013-03-04 05:00:00,10039.0 -2013-03-04 06:00:00,10340.0 -2013-03-04 07:00:00,11133.0 -2013-03-04 08:00:00,12058.0 -2013-03-04 09:00:00,12584.0 -2013-03-04 10:00:00,12742.0 -2013-03-04 11:00:00,12813.0 -2013-03-04 12:00:00,12834.0 -2013-03-04 13:00:00,12763.0 -2013-03-04 14:00:00,12682.0 -2013-03-04 15:00:00,12729.0 -2013-03-04 16:00:00,12669.0 -2013-03-04 17:00:00,12603.0 -2013-03-04 18:00:00,12653.0 -2013-03-04 19:00:00,12984.0 -2013-03-04 20:00:00,13452.0 -2013-03-04 21:00:00,13257.0 -2013-03-04 22:00:00,12870.0 -2013-03-04 23:00:00,12319.0 -2013-03-05 00:00:00,11520.0 -2013-03-03 01:00:00,10702.0 -2013-03-03 02:00:00,10336.0 -2013-03-03 03:00:00,10064.0 -2013-03-03 04:00:00,9970.0 -2013-03-03 05:00:00,9906.0 -2013-03-03 06:00:00,9956.0 -2013-03-03 07:00:00,10125.0 -2013-03-03 08:00:00,10203.0 -2013-03-03 09:00:00,10168.0 -2013-03-03 10:00:00,10244.0 -2013-03-03 11:00:00,10301.0 -2013-03-03 12:00:00,10298.0 -2013-03-03 13:00:00,10284.0 -2013-03-03 14:00:00,10217.0 -2013-03-03 15:00:00,10171.0 -2013-03-03 16:00:00,10062.0 -2013-03-03 17:00:00,10081.0 -2013-03-03 18:00:00,10245.0 -2013-03-03 19:00:00,10888.0 -2013-03-03 20:00:00,11786.0 -2013-03-03 21:00:00,11897.0 -2013-03-03 22:00:00,11728.0 -2013-03-03 23:00:00,11451.0 -2013-03-04 00:00:00,10874.0 -2013-03-02 01:00:00,10992.0 -2013-03-02 02:00:00,10553.0 -2013-03-02 03:00:00,10209.0 -2013-03-02 04:00:00,10026.0 -2013-03-02 05:00:00,9970.0 -2013-03-02 06:00:00,10039.0 -2013-03-02 07:00:00,10285.0 -2013-03-02 08:00:00,10645.0 -2013-03-02 09:00:00,10900.0 -2013-03-02 10:00:00,11260.0 -2013-03-02 11:00:00,11327.0 -2013-03-02 12:00:00,11294.0 -2013-03-02 13:00:00,11196.0 -2013-03-02 14:00:00,11001.0 -2013-03-02 15:00:00,10776.0 -2013-03-02 16:00:00,10664.0 -2013-03-02 17:00:00,10609.0 -2013-03-02 18:00:00,10801.0 -2013-03-02 19:00:00,11228.0 -2013-03-02 20:00:00,12039.0 -2013-03-02 21:00:00,11998.0 -2013-03-02 22:00:00,11902.0 -2013-03-02 23:00:00,11608.0 -2013-03-03 00:00:00,11171.0 -2013-03-01 01:00:00,10818.0 -2013-03-01 02:00:00,10340.0 -2013-03-01 03:00:00,10056.0 -2013-03-01 04:00:00,9948.0 -2013-03-01 05:00:00,9932.0 -2013-03-01 06:00:00,10210.0 -2013-03-01 07:00:00,10930.0 -2013-03-01 08:00:00,11949.0 -2013-03-01 09:00:00,12479.0 -2013-03-01 10:00:00,12728.0 -2013-03-01 11:00:00,12830.0 -2013-03-01 12:00:00,12899.0 -2013-03-01 13:00:00,12884.0 -2013-03-01 14:00:00,12784.0 -2013-03-01 15:00:00,12795.0 -2013-03-01 16:00:00,12773.0 -2013-03-01 17:00:00,12745.0 -2013-03-01 18:00:00,12884.0 -2013-03-01 19:00:00,13215.0 -2013-03-01 20:00:00,13367.0 -2013-03-01 21:00:00,13131.0 -2013-03-01 22:00:00,12880.0 -2013-03-01 23:00:00,12453.0 -2013-03-02 00:00:00,11765.0 -2013-02-28 01:00:00,10794.0 -2013-02-28 02:00:00,10298.0 -2013-02-28 03:00:00,10017.0 -2013-02-28 04:00:00,9885.0 -2013-02-28 05:00:00,9892.0 -2013-02-28 06:00:00,10107.0 -2013-02-28 07:00:00,10837.0 -2013-02-28 08:00:00,11901.0 -2013-02-28 09:00:00,12395.0 -2013-02-28 10:00:00,12510.0 -2013-02-28 11:00:00,12620.0 -2013-02-28 12:00:00,12630.0 -2013-02-28 13:00:00,12607.0 -2013-02-28 14:00:00,12489.0 -2013-02-28 15:00:00,12547.0 -2013-02-28 16:00:00,12459.0 -2013-02-28 17:00:00,12505.0 -2013-02-28 18:00:00,12648.0 -2013-02-28 19:00:00,13021.0 -2013-02-28 20:00:00,13253.0 -2013-02-28 21:00:00,13053.0 -2013-02-28 22:00:00,12831.0 -2013-02-28 23:00:00,12331.0 -2013-03-01 00:00:00,11525.0 -2013-02-27 01:00:00,10811.0 -2013-02-27 02:00:00,10287.0 -2013-02-27 03:00:00,10026.0 -2013-02-27 04:00:00,9855.0 -2013-02-27 05:00:00,9814.0 -2013-02-27 06:00:00,10097.0 -2013-02-27 07:00:00,10854.0 -2013-02-27 08:00:00,11920.0 -2013-02-27 09:00:00,12431.0 -2013-02-27 10:00:00,12577.0 -2013-02-27 11:00:00,12662.0 -2013-02-27 12:00:00,12714.0 -2013-02-27 13:00:00,12662.0 -2013-02-27 14:00:00,12635.0 -2013-02-27 15:00:00,12644.0 -2013-02-27 16:00:00,12563.0 -2013-02-27 17:00:00,12507.0 -2013-02-27 18:00:00,12661.0 -2013-02-27 19:00:00,13114.0 -2013-02-27 20:00:00,13440.0 -2013-02-27 21:00:00,13232.0 -2013-02-27 22:00:00,12952.0 -2013-02-27 23:00:00,12427.0 -2013-02-28 00:00:00,11514.0 -2013-02-26 01:00:00,10539.0 -2013-02-26 02:00:00,10089.0 -2013-02-26 03:00:00,9832.0 -2013-02-26 04:00:00,9733.0 -2013-02-26 05:00:00,9758.0 -2013-02-26 06:00:00,10069.0 -2013-02-26 07:00:00,10857.0 -2013-02-26 08:00:00,11969.0 -2013-02-26 09:00:00,12519.0 -2013-02-26 10:00:00,12766.0 -2013-02-26 11:00:00,12884.0 -2013-02-26 12:00:00,13080.0 -2013-02-26 13:00:00,13205.0 -2013-02-26 14:00:00,13264.0 -2013-02-26 15:00:00,13261.0 -2013-02-26 16:00:00,13104.0 -2013-02-26 17:00:00,12942.0 -2013-02-26 18:00:00,13045.0 -2013-02-26 19:00:00,13532.0 -2013-02-26 20:00:00,13801.0 -2013-02-26 21:00:00,13520.0 -2013-02-26 22:00:00,13102.0 -2013-02-26 23:00:00,12460.0 -2013-02-27 00:00:00,11566.0 -2013-02-25 01:00:00,10100.0 -2013-02-25 02:00:00,9780.0 -2013-02-25 03:00:00,9667.0 -2013-02-25 04:00:00,9590.0 -2013-02-25 05:00:00,9693.0 -2013-02-25 06:00:00,10027.0 -2013-02-25 07:00:00,10864.0 -2013-02-25 08:00:00,11915.0 -2013-02-25 09:00:00,12322.0 -2013-02-25 10:00:00,12298.0 -2013-02-25 11:00:00,12245.0 -2013-02-25 12:00:00,12205.0 -2013-02-25 13:00:00,12065.0 -2013-02-25 14:00:00,11947.0 -2013-02-25 15:00:00,11863.0 -2013-02-25 16:00:00,11723.0 -2013-02-25 17:00:00,11638.0 -2013-02-25 18:00:00,11801.0 -2013-02-25 19:00:00,12492.0 -2013-02-25 20:00:00,12935.0 -2013-02-25 21:00:00,12849.0 -2013-02-25 22:00:00,12576.0 -2013-02-25 23:00:00,12095.0 -2013-02-26 00:00:00,11267.0 -2013-02-24 01:00:00,10603.0 -2013-02-24 02:00:00,10201.0 -2013-02-24 03:00:00,9951.0 -2013-02-24 04:00:00,9849.0 -2013-02-24 05:00:00,9763.0 -2013-02-24 06:00:00,9838.0 -2013-02-24 07:00:00,9985.0 -2013-02-24 08:00:00,10103.0 -2013-02-24 09:00:00,10095.0 -2013-02-24 10:00:00,10192.0 -2013-02-24 11:00:00,10219.0 -2013-02-24 12:00:00,10245.0 -2013-02-24 13:00:00,10175.0 -2013-02-24 14:00:00,10089.0 -2013-02-24 15:00:00,9979.0 -2013-02-24 16:00:00,9875.0 -2013-02-24 17:00:00,9838.0 -2013-02-24 18:00:00,10123.0 -2013-02-24 19:00:00,10812.0 -2013-02-24 20:00:00,11651.0 -2013-02-24 21:00:00,11603.0 -2013-02-24 22:00:00,11490.0 -2013-02-24 23:00:00,11156.0 -2013-02-25 00:00:00,10683.0 -2013-02-23 01:00:00,11017.0 -2013-02-23 02:00:00,10496.0 -2013-02-23 03:00:00,10170.0 -2013-02-23 04:00:00,10013.0 -2013-02-23 05:00:00,9938.0 -2013-02-23 06:00:00,9951.0 -2013-02-23 07:00:00,10288.0 -2013-02-23 08:00:00,10610.0 -2013-02-23 09:00:00,10834.0 -2013-02-23 10:00:00,11163.0 -2013-02-23 11:00:00,11310.0 -2013-02-23 12:00:00,11484.0 -2013-02-23 13:00:00,11411.0 -2013-02-23 14:00:00,11244.0 -2013-02-23 15:00:00,11042.0 -2013-02-23 16:00:00,10991.0 -2013-02-23 17:00:00,10987.0 -2013-02-23 18:00:00,11121.0 -2013-02-23 19:00:00,11664.0 -2013-02-23 20:00:00,12193.0 -2013-02-23 21:00:00,12080.0 -2013-02-23 22:00:00,11886.0 -2013-02-23 23:00:00,11620.0 -2013-02-24 00:00:00,11129.0 -2013-02-22 01:00:00,11515.0 -2013-02-22 02:00:00,10992.0 -2013-02-22 03:00:00,10708.0 -2013-02-22 04:00:00,10531.0 -2013-02-22 05:00:00,10566.0 -2013-02-22 06:00:00,10782.0 -2013-02-22 07:00:00,11423.0 -2013-02-22 08:00:00,12316.0 -2013-02-22 09:00:00,12681.0 -2013-02-22 10:00:00,12889.0 -2013-02-22 11:00:00,13048.0 -2013-02-22 12:00:00,13117.0 -2013-02-22 13:00:00,13065.0 -2013-02-22 14:00:00,12914.0 -2013-02-22 15:00:00,12849.0 -2013-02-22 16:00:00,12788.0 -2013-02-22 17:00:00,12773.0 -2013-02-22 18:00:00,12859.0 -2013-02-22 19:00:00,13259.0 -2013-02-22 20:00:00,13440.0 -2013-02-22 21:00:00,13191.0 -2013-02-22 22:00:00,12787.0 -2013-02-22 23:00:00,12392.0 -2013-02-23 00:00:00,11684.0 -2013-02-21 01:00:00,11566.0 -2013-02-21 02:00:00,11102.0 -2013-02-21 03:00:00,10831.0 -2013-02-21 04:00:00,10661.0 -2013-02-21 05:00:00,10622.0 -2013-02-21 06:00:00,10823.0 -2013-02-21 07:00:00,11523.0 -2013-02-21 08:00:00,12655.0 -2013-02-21 09:00:00,13156.0 -2013-02-21 10:00:00,13229.0 -2013-02-21 11:00:00,13312.0 -2013-02-21 12:00:00,13362.0 -2013-02-21 13:00:00,13326.0 -2013-02-21 14:00:00,13329.0 -2013-02-21 15:00:00,13345.0 -2013-02-21 16:00:00,13317.0 -2013-02-21 17:00:00,13302.0 -2013-02-21 18:00:00,13509.0 -2013-02-21 19:00:00,13956.0 -2013-02-21 20:00:00,14129.0 -2013-02-21 21:00:00,13924.0 -2013-02-21 22:00:00,13621.0 -2013-02-21 23:00:00,13095.0 -2013-02-22 00:00:00,12220.0 -2013-02-20 01:00:00,12086.0 -2013-02-20 02:00:00,11642.0 -2013-02-20 03:00:00,11407.0 -2013-02-20 04:00:00,11283.0 -2013-02-20 05:00:00,11251.0 -2013-02-20 06:00:00,11496.0 -2013-02-20 07:00:00,12265.0 -2013-02-20 08:00:00,13286.0 -2013-02-20 09:00:00,13596.0 -2013-02-20 10:00:00,13567.0 -2013-02-20 11:00:00,13528.0 -2013-02-20 12:00:00,13478.0 -2013-02-20 13:00:00,13343.0 -2013-02-20 14:00:00,13191.0 -2013-02-20 15:00:00,13090.0 -2013-02-20 16:00:00,12866.0 -2013-02-20 17:00:00,12786.0 -2013-02-20 18:00:00,12955.0 -2013-02-20 19:00:00,13614.0 -2013-02-20 20:00:00,14077.0 -2013-02-20 21:00:00,13942.0 -2013-02-20 22:00:00,13651.0 -2013-02-20 23:00:00,13159.0 -2013-02-21 00:00:00,12330.0 -2013-02-19 01:00:00,10578.0 -2013-02-19 02:00:00,10238.0 -2013-02-19 03:00:00,10124.0 -2013-02-19 04:00:00,10109.0 -2013-02-19 05:00:00,10243.0 -2013-02-19 06:00:00,10617.0 -2013-02-19 07:00:00,11447.0 -2013-02-19 08:00:00,12740.0 -2013-02-19 09:00:00,13295.0 -2013-02-19 10:00:00,13519.0 -2013-02-19 11:00:00,13644.0 -2013-02-19 12:00:00,13710.0 -2013-02-19 13:00:00,13782.0 -2013-02-19 14:00:00,13754.0 -2013-02-19 15:00:00,13830.0 -2013-02-19 16:00:00,13806.0 -2013-02-19 17:00:00,13788.0 -2013-02-19 18:00:00,13945.0 -2013-02-19 19:00:00,14472.0 -2013-02-19 20:00:00,14716.0 -2013-02-19 21:00:00,14549.0 -2013-02-19 22:00:00,14260.0 -2013-02-19 23:00:00,13723.0 -2013-02-20 00:00:00,12859.0 -2013-02-18 01:00:00,10620.0 -2013-02-18 02:00:00,10221.0 -2013-02-18 03:00:00,10052.0 -2013-02-18 04:00:00,9903.0 -2013-02-18 05:00:00,9955.0 -2013-02-18 06:00:00,10161.0 -2013-02-18 07:00:00,10867.0 -2013-02-18 08:00:00,11726.0 -2013-02-18 09:00:00,12026.0 -2013-02-18 10:00:00,12129.0 -2013-02-18 11:00:00,12217.0 -2013-02-18 12:00:00,12201.0 -2013-02-18 13:00:00,12054.0 -2013-02-18 14:00:00,11955.0 -2013-02-18 15:00:00,12108.0 -2013-02-18 16:00:00,12103.0 -2013-02-18 17:00:00,12110.0 -2013-02-18 18:00:00,12443.0 -2013-02-18 19:00:00,12920.0 -2013-02-18 20:00:00,12890.0 -2013-02-18 21:00:00,12758.0 -2013-02-18 22:00:00,12383.0 -2013-02-18 23:00:00,11842.0 -2013-02-19 00:00:00,11118.0 -2013-02-17 01:00:00,11221.0 -2013-02-17 02:00:00,10789.0 -2013-02-17 03:00:00,10563.0 -2013-02-17 04:00:00,10392.0 -2013-02-17 05:00:00,10387.0 -2013-02-17 06:00:00,10384.0 -2013-02-17 07:00:00,10559.0 -2013-02-17 08:00:00,10740.0 -2013-02-17 09:00:00,10691.0 -2013-02-17 10:00:00,10768.0 -2013-02-17 11:00:00,10785.0 -2013-02-17 12:00:00,10831.0 -2013-02-17 13:00:00,10798.0 -2013-02-17 14:00:00,10696.0 -2013-02-17 15:00:00,10563.0 -2013-02-17 16:00:00,10420.0 -2013-02-17 17:00:00,10455.0 -2013-02-17 18:00:00,10749.0 -2013-02-17 19:00:00,11600.0 -2013-02-17 20:00:00,12174.0 -2013-02-17 21:00:00,12189.0 -2013-02-17 22:00:00,11997.0 -2013-02-17 23:00:00,11710.0 -2013-02-18 00:00:00,11167.0 -2013-02-16 01:00:00,11105.0 -2013-02-16 02:00:00,10672.0 -2013-02-16 03:00:00,10416.0 -2013-02-16 04:00:00,10267.0 -2013-02-16 05:00:00,10257.0 -2013-02-16 06:00:00,10326.0 -2013-02-16 07:00:00,10653.0 -2013-02-16 08:00:00,11041.0 -2013-02-16 09:00:00,11139.0 -2013-02-16 10:00:00,11367.0 -2013-02-16 11:00:00,11547.0 -2013-02-16 12:00:00,11793.0 -2013-02-16 13:00:00,11862.0 -2013-02-16 14:00:00,11776.0 -2013-02-16 15:00:00,11627.0 -2013-02-16 16:00:00,11559.0 -2013-02-16 17:00:00,11531.0 -2013-02-16 18:00:00,11757.0 -2013-02-16 19:00:00,12310.0 -2013-02-16 20:00:00,12666.0 -2013-02-16 21:00:00,12586.0 -2013-02-16 22:00:00,12477.0 -2013-02-16 23:00:00,12224.0 -2013-02-17 00:00:00,11696.0 -2013-02-15 01:00:00,10677.0 -2013-02-15 02:00:00,10170.0 -2013-02-15 03:00:00,9879.0 -2013-02-15 04:00:00,9750.0 -2013-02-15 05:00:00,9740.0 -2013-02-15 06:00:00,10074.0 -2013-02-15 07:00:00,10822.0 -2013-02-15 08:00:00,11964.0 -2013-02-15 09:00:00,12436.0 -2013-02-15 10:00:00,12520.0 -2013-02-15 11:00:00,12497.0 -2013-02-15 12:00:00,12467.0 -2013-02-15 13:00:00,12360.0 -2013-02-15 14:00:00,12213.0 -2013-02-15 15:00:00,12157.0 -2013-02-15 16:00:00,11985.0 -2013-02-15 17:00:00,11925.0 -2013-02-15 18:00:00,12135.0 -2013-02-15 19:00:00,12802.0 -2013-02-15 20:00:00,13095.0 -2013-02-15 21:00:00,12930.0 -2013-02-15 22:00:00,12666.0 -2013-02-15 23:00:00,12331.0 -2013-02-16 00:00:00,11690.0 -2013-02-14 01:00:00,10457.0 -2013-02-14 02:00:00,9943.0 -2013-02-14 03:00:00,9682.0 -2013-02-14 04:00:00,9514.0 -2013-02-14 05:00:00,9524.0 -2013-02-14 06:00:00,9772.0 -2013-02-14 07:00:00,10529.0 -2013-02-14 08:00:00,11691.0 -2013-02-14 09:00:00,12081.0 -2013-02-14 10:00:00,12232.0 -2013-02-14 11:00:00,12435.0 -2013-02-14 12:00:00,12566.0 -2013-02-14 13:00:00,12579.0 -2013-02-14 14:00:00,12577.0 -2013-02-14 15:00:00,12582.0 -2013-02-14 16:00:00,12453.0 -2013-02-14 17:00:00,12390.0 -2013-02-14 18:00:00,12530.0 -2013-02-14 19:00:00,13074.0 -2013-02-14 20:00:00,13218.0 -2013-02-14 21:00:00,12988.0 -2013-02-14 22:00:00,12609.0 -2013-02-14 23:00:00,12136.0 -2013-02-15 00:00:00,11387.0 -2013-02-13 01:00:00,11046.0 -2013-02-13 02:00:00,10571.0 -2013-02-13 03:00:00,10276.0 -2013-02-13 04:00:00,10129.0 -2013-02-13 05:00:00,10142.0 -2013-02-13 06:00:00,10385.0 -2013-02-13 07:00:00,11143.0 -2013-02-13 08:00:00,12296.0 -2013-02-13 09:00:00,12581.0 -2013-02-13 10:00:00,12539.0 -2013-02-13 11:00:00,12435.0 -2013-02-13 12:00:00,12367.0 -2013-02-13 13:00:00,12208.0 -2013-02-13 14:00:00,12065.0 -2013-02-13 15:00:00,11979.0 -2013-02-13 16:00:00,11824.0 -2013-02-13 17:00:00,11748.0 -2013-02-13 18:00:00,11895.0 -2013-02-13 19:00:00,12523.0 -2013-02-13 20:00:00,12932.0 -2013-02-13 21:00:00,12795.0 -2013-02-13 22:00:00,12542.0 -2013-02-13 23:00:00,12051.0 -2013-02-14 00:00:00,11234.0 -2013-02-12 01:00:00,10864.0 -2013-02-12 02:00:00,10391.0 -2013-02-12 03:00:00,10123.0 -2013-02-12 04:00:00,10009.0 -2013-02-12 05:00:00,10049.0 -2013-02-12 06:00:00,10337.0 -2013-02-12 07:00:00,11083.0 -2013-02-12 08:00:00,12204.0 -2013-02-12 09:00:00,12675.0 -2013-02-12 10:00:00,12855.0 -2013-02-12 11:00:00,12954.0 -2013-02-12 12:00:00,13024.0 -2013-02-12 13:00:00,13015.0 -2013-02-12 14:00:00,12947.0 -2013-02-12 15:00:00,12948.0 -2013-02-12 16:00:00,12840.0 -2013-02-12 17:00:00,12741.0 -2013-02-12 18:00:00,12823.0 -2013-02-12 19:00:00,13393.0 -2013-02-12 20:00:00,13584.0 -2013-02-12 21:00:00,13438.0 -2013-02-12 22:00:00,13148.0 -2013-02-12 23:00:00,12653.0 -2013-02-13 00:00:00,11829.0 -2013-02-11 01:00:00,9747.0 -2013-02-11 02:00:00,9383.0 -2013-02-11 03:00:00,9264.0 -2013-02-11 04:00:00,9268.0 -2013-02-11 05:00:00,9402.0 -2013-02-11 06:00:00,9740.0 -2013-02-11 07:00:00,10671.0 -2013-02-11 08:00:00,11918.0 -2013-02-11 09:00:00,12622.0 -2013-02-11 10:00:00,12845.0 -2013-02-11 11:00:00,12917.0 -2013-02-11 12:00:00,13015.0 -2013-02-11 13:00:00,13051.0 -2013-02-11 14:00:00,13053.0 -2013-02-11 15:00:00,13099.0 -2013-02-11 16:00:00,12976.0 -2013-02-11 17:00:00,12937.0 -2013-02-11 18:00:00,13122.0 -2013-02-11 19:00:00,13648.0 -2013-02-11 20:00:00,13645.0 -2013-02-11 21:00:00,13419.0 -2013-02-11 22:00:00,13067.0 -2013-02-11 23:00:00,12469.0 -2013-02-12 00:00:00,11620.0 -2013-02-10 01:00:00,10410.0 -2013-02-10 02:00:00,9902.0 -2013-02-10 03:00:00,9619.0 -2013-02-10 04:00:00,9401.0 -2013-02-10 05:00:00,9368.0 -2013-02-10 06:00:00,9297.0 -2013-02-10 07:00:00,9434.0 -2013-02-10 08:00:00,9698.0 -2013-02-10 09:00:00,9671.0 -2013-02-10 10:00:00,10115.0 -2013-02-10 11:00:00,10441.0 -2013-02-10 12:00:00,10737.0 -2013-02-10 13:00:00,10844.0 -2013-02-10 14:00:00,10858.0 -2013-02-10 15:00:00,10797.0 -2013-02-10 16:00:00,10692.0 -2013-02-10 17:00:00,10806.0 -2013-02-10 18:00:00,11166.0 -2013-02-10 19:00:00,11671.0 -2013-02-10 20:00:00,11752.0 -2013-02-10 21:00:00,11578.0 -2013-02-10 22:00:00,11284.0 -2013-02-10 23:00:00,10884.0 -2013-02-11 00:00:00,10267.0 -2013-02-09 01:00:00,11084.0 -2013-02-09 02:00:00,10556.0 -2013-02-09 03:00:00,10234.0 -2013-02-09 04:00:00,10044.0 -2013-02-09 05:00:00,9951.0 -2013-02-09 06:00:00,10010.0 -2013-02-09 07:00:00,10263.0 -2013-02-09 08:00:00,10759.0 -2013-02-09 09:00:00,10927.0 -2013-02-09 10:00:00,11308.0 -2013-02-09 11:00:00,11510.0 -2013-02-09 12:00:00,11537.0 -2013-02-09 13:00:00,11416.0 -2013-02-09 14:00:00,11183.0 -2013-02-09 15:00:00,10945.0 -2013-02-09 16:00:00,10775.0 -2013-02-09 17:00:00,10666.0 -2013-02-09 18:00:00,10865.0 -2013-02-09 19:00:00,11696.0 -2013-02-09 20:00:00,12060.0 -2013-02-09 21:00:00,11961.0 -2013-02-09 22:00:00,11743.0 -2013-02-09 23:00:00,11497.0 -2013-02-10 00:00:00,10956.0 -2013-02-08 01:00:00,11015.0 -2013-02-08 02:00:00,10524.0 -2013-02-08 03:00:00,10290.0 -2013-02-08 04:00:00,10211.0 -2013-02-08 05:00:00,10245.0 -2013-02-08 06:00:00,10573.0 -2013-02-08 07:00:00,11246.0 -2013-02-08 08:00:00,12397.0 -2013-02-08 09:00:00,12853.0 -2013-02-08 10:00:00,13043.0 -2013-02-08 11:00:00,13107.0 -2013-02-08 12:00:00,13127.0 -2013-02-08 13:00:00,12989.0 -2013-02-08 14:00:00,12878.0 -2013-02-08 15:00:00,12781.0 -2013-02-08 16:00:00,12685.0 -2013-02-08 17:00:00,12593.0 -2013-02-08 18:00:00,12751.0 -2013-02-08 19:00:00,13390.0 -2013-02-08 20:00:00,13492.0 -2013-02-08 21:00:00,13229.0 -2013-02-08 22:00:00,12905.0 -2013-02-08 23:00:00,12499.0 -2013-02-09 00:00:00,11755.0 -2013-02-07 01:00:00,10846.0 -2013-02-07 02:00:00,10293.0 -2013-02-07 03:00:00,10022.0 -2013-02-07 04:00:00,9814.0 -2013-02-07 05:00:00,9835.0 -2013-02-07 06:00:00,10152.0 -2013-02-07 07:00:00,10880.0 -2013-02-07 08:00:00,12073.0 -2013-02-07 09:00:00,12640.0 -2013-02-07 10:00:00,12687.0 -2013-02-07 11:00:00,12848.0 -2013-02-07 12:00:00,12956.0 -2013-02-07 13:00:00,12994.0 -2013-02-07 14:00:00,12967.0 -2013-02-07 15:00:00,12963.0 -2013-02-07 16:00:00,12938.0 -2013-02-07 17:00:00,12912.0 -2013-02-07 18:00:00,13159.0 -2013-02-07 19:00:00,13614.0 -2013-02-07 20:00:00,13639.0 -2013-02-07 21:00:00,13441.0 -2013-02-07 22:00:00,13082.0 -2013-02-07 23:00:00,12548.0 -2013-02-08 00:00:00,11718.0 -2013-02-06 01:00:00,11113.0 -2013-02-06 02:00:00,10615.0 -2013-02-06 03:00:00,10342.0 -2013-02-06 04:00:00,10243.0 -2013-02-06 05:00:00,10283.0 -2013-02-06 06:00:00,10584.0 -2013-02-06 07:00:00,11375.0 -2013-02-06 08:00:00,12545.0 -2013-02-06 09:00:00,12899.0 -2013-02-06 10:00:00,12902.0 -2013-02-06 11:00:00,12787.0 -2013-02-06 12:00:00,12728.0 -2013-02-06 13:00:00,12592.0 -2013-02-06 14:00:00,12436.0 -2013-02-06 15:00:00,12419.0 -2013-02-06 16:00:00,12325.0 -2013-02-06 17:00:00,12309.0 -2013-02-06 18:00:00,12571.0 -2013-02-06 19:00:00,13273.0 -2013-02-06 20:00:00,13386.0 -2013-02-06 21:00:00,13224.0 -2013-02-06 22:00:00,12935.0 -2013-02-06 23:00:00,12405.0 -2013-02-07 00:00:00,11604.0 -2013-02-05 01:00:00,11494.0 -2013-02-05 02:00:00,11039.0 -2013-02-05 03:00:00,10814.0 -2013-02-05 04:00:00,10681.0 -2013-02-05 05:00:00,10689.0 -2013-02-05 06:00:00,10932.0 -2013-02-05 07:00:00,11623.0 -2013-02-05 08:00:00,12810.0 -2013-02-05 09:00:00,13196.0 -2013-02-05 10:00:00,13353.0 -2013-02-05 11:00:00,13308.0 -2013-02-05 12:00:00,13337.0 -2013-02-05 13:00:00,13287.0 -2013-02-05 14:00:00,13254.0 -2013-02-05 15:00:00,13307.0 -2013-02-05 16:00:00,13157.0 -2013-02-05 17:00:00,13028.0 -2013-02-05 18:00:00,13237.0 -2013-02-05 19:00:00,13868.0 -2013-02-05 20:00:00,13874.0 -2013-02-05 21:00:00,13638.0 -2013-02-05 22:00:00,13272.0 -2013-02-05 23:00:00,12711.0 -2013-02-06 00:00:00,11875.0 -2013-02-04 01:00:00,11078.0 -2013-02-04 02:00:00,10665.0 -2013-02-04 03:00:00,10412.0 -2013-02-04 04:00:00,10318.0 -2013-02-04 05:00:00,10356.0 -2013-02-04 06:00:00,10640.0 -2013-02-04 07:00:00,11294.0 -2013-02-04 08:00:00,12454.0 -2013-02-04 09:00:00,12941.0 -2013-02-04 10:00:00,13096.0 -2013-02-04 11:00:00,13238.0 -2013-02-04 12:00:00,13301.0 -2013-02-04 13:00:00,13243.0 -2013-02-04 14:00:00,13164.0 -2013-02-04 15:00:00,13173.0 -2013-02-04 16:00:00,13123.0 -2013-02-04 17:00:00,13056.0 -2013-02-04 18:00:00,13240.0 -2013-02-04 19:00:00,13919.0 -2013-02-04 20:00:00,14030.0 -2013-02-04 21:00:00,13830.0 -2013-02-04 22:00:00,13552.0 -2013-02-04 23:00:00,13045.0 -2013-02-05 00:00:00,12215.0 -2013-02-03 01:00:00,11609.0 -2013-02-03 02:00:00,11151.0 -2013-02-03 03:00:00,10777.0 -2013-02-03 04:00:00,10664.0 -2013-02-03 05:00:00,10582.0 -2013-02-03 06:00:00,10624.0 -2013-02-03 07:00:00,10716.0 -2013-02-03 08:00:00,11034.0 -2013-02-03 09:00:00,11067.0 -2013-02-03 10:00:00,11314.0 -2013-02-03 11:00:00,11558.0 -2013-02-03 12:00:00,11637.0 -2013-02-03 13:00:00,11518.0 -2013-02-03 14:00:00,11441.0 -2013-02-03 15:00:00,11352.0 -2013-02-03 16:00:00,11329.0 -2013-02-03 17:00:00,11412.0 -2013-02-03 18:00:00,11719.0 -2013-02-03 19:00:00,12535.0 -2013-02-03 20:00:00,12674.0 -2013-02-03 21:00:00,12545.0 -2013-02-03 22:00:00,12334.0 -2013-02-03 23:00:00,12050.0 -2013-02-04 00:00:00,11582.0 -2013-02-02 01:00:00,12204.0 -2013-02-02 02:00:00,11675.0 -2013-02-02 03:00:00,11299.0 -2013-02-02 04:00:00,11013.0 -2013-02-02 05:00:00,10837.0 -2013-02-02 06:00:00,10865.0 -2013-02-02 07:00:00,11061.0 -2013-02-02 08:00:00,11441.0 -2013-02-02 09:00:00,11675.0 -2013-02-02 10:00:00,11928.0 -2013-02-02 11:00:00,12203.0 -2013-02-02 12:00:00,12355.0 -2013-02-02 13:00:00,12419.0 -2013-02-02 14:00:00,12199.0 -2013-02-02 15:00:00,12074.0 -2013-02-02 16:00:00,11936.0 -2013-02-02 17:00:00,11967.0 -2013-02-02 18:00:00,12227.0 -2013-02-02 19:00:00,13065.0 -2013-02-02 20:00:00,13284.0 -2013-02-02 21:00:00,13173.0 -2013-02-02 22:00:00,13007.0 -2013-02-02 23:00:00,12681.0 -2013-02-03 00:00:00,12171.0 -2013-02-01 01:00:00,12514.0 -2013-02-01 02:00:00,12084.0 -2013-02-01 03:00:00,11823.0 -2013-02-01 04:00:00,11695.0 -2013-02-01 05:00:00,11713.0 -2013-02-01 06:00:00,11939.0 -2013-02-01 07:00:00,12596.0 -2013-02-01 08:00:00,13693.0 -2013-02-01 09:00:00,14098.0 -2013-02-01 10:00:00,14109.0 -2013-02-01 11:00:00,14158.0 -2013-02-01 12:00:00,14176.0 -2013-02-01 13:00:00,14053.0 -2013-02-01 14:00:00,13944.0 -2013-02-01 15:00:00,13841.0 -2013-02-01 16:00:00,13659.0 -2013-02-01 17:00:00,13525.0 -2013-02-01 18:00:00,13709.0 -2013-02-01 19:00:00,14555.0 -2013-02-01 20:00:00,14689.0 -2013-02-01 21:00:00,14444.0 -2013-02-01 22:00:00,14110.0 -2013-02-01 23:00:00,13692.0 -2013-02-02 00:00:00,12926.0 -2013-01-31 01:00:00,11346.0 -2013-01-31 02:00:00,10916.0 -2013-01-31 03:00:00,10630.0 -2013-01-31 04:00:00,10533.0 -2013-01-31 05:00:00,10590.0 -2013-01-31 06:00:00,10882.0 -2013-01-31 07:00:00,11608.0 -2013-01-31 08:00:00,12878.0 -2013-01-31 09:00:00,13421.0 -2013-01-31 10:00:00,13562.0 -2013-01-31 11:00:00,13615.0 -2013-01-31 12:00:00,13639.0 -2013-01-31 13:00:00,13641.0 -2013-01-31 14:00:00,13652.0 -2013-01-31 15:00:00,13746.0 -2013-01-31 16:00:00,13698.0 -2013-01-31 17:00:00,13732.0 -2013-01-31 18:00:00,14033.0 -2013-01-31 19:00:00,14753.0 -2013-01-31 20:00:00,14918.0 -2013-01-31 21:00:00,14748.0 -2013-01-31 22:00:00,14537.0 -2013-01-31 23:00:00,14065.0 -2013-02-01 00:00:00,13227.0 -2013-01-30 01:00:00,9826.0 -2013-01-30 02:00:00,9341.0 -2013-01-30 03:00:00,9084.0 -2013-01-30 04:00:00,8946.0 -2013-01-30 05:00:00,8956.0 -2013-01-30 06:00:00,9227.0 -2013-01-30 07:00:00,10001.0 -2013-01-30 08:00:00,11303.0 -2013-01-30 09:00:00,12098.0 -2013-01-30 10:00:00,12238.0 -2013-01-30 11:00:00,12376.0 -2013-01-30 12:00:00,12544.0 -2013-01-30 13:00:00,12534.0 -2013-01-30 14:00:00,12457.0 -2013-01-30 15:00:00,12571.0 -2013-01-30 16:00:00,12600.0 -2013-01-30 17:00:00,12705.0 -2013-01-30 18:00:00,13090.0 -2013-01-30 19:00:00,13652.0 -2013-01-30 20:00:00,13599.0 -2013-01-30 21:00:00,13429.0 -2013-01-30 22:00:00,13246.0 -2013-01-30 23:00:00,12779.0 -2013-01-31 00:00:00,12024.0 -2013-01-29 01:00:00,10329.0 -2013-01-29 02:00:00,9785.0 -2013-01-29 03:00:00,9414.0 -2013-01-29 04:00:00,9179.0 -2013-01-29 05:00:00,9048.0 -2013-01-29 06:00:00,9190.0 -2013-01-29 07:00:00,9816.0 -2013-01-29 08:00:00,11015.0 -2013-01-29 09:00:00,11686.0 -2013-01-29 10:00:00,11749.0 -2013-01-29 11:00:00,11796.0 -2013-01-29 12:00:00,11951.0 -2013-01-29 13:00:00,11931.0 -2013-01-29 14:00:00,11939.0 -2013-01-29 15:00:00,11883.0 -2013-01-29 16:00:00,11929.0 -2013-01-29 17:00:00,11932.0 -2013-01-29 18:00:00,12292.0 -2013-01-29 19:00:00,12691.0 -2013-01-29 20:00:00,12559.0 -2013-01-29 21:00:00,12257.0 -2013-01-29 22:00:00,11911.0 -2013-01-29 23:00:00,11370.0 -2013-01-30 00:00:00,10604.0 -2013-01-28 01:00:00,10369.0 -2013-01-28 02:00:00,9926.0 -2013-01-28 03:00:00,9581.0 -2013-01-28 04:00:00,9490.0 -2013-01-28 05:00:00,9450.0 -2013-01-28 06:00:00,9712.0 -2013-01-28 07:00:00,10351.0 -2013-01-28 08:00:00,11551.0 -2013-01-28 09:00:00,12167.0 -2013-01-28 10:00:00,12211.0 -2013-01-28 11:00:00,12260.0 -2013-01-28 12:00:00,12253.0 -2013-01-28 13:00:00,12178.0 -2013-01-28 14:00:00,12050.0 -2013-01-28 15:00:00,11990.0 -2013-01-28 16:00:00,11785.0 -2013-01-28 17:00:00,11704.0 -2013-01-28 18:00:00,12012.0 -2013-01-28 19:00:00,12830.0 -2013-01-28 20:00:00,12828.0 -2013-01-28 21:00:00,12651.0 -2013-01-28 22:00:00,12356.0 -2013-01-28 23:00:00,11834.0 -2013-01-29 00:00:00,11084.0 -2013-01-27 01:00:00,10753.0 -2013-01-27 02:00:00,10331.0 -2013-01-27 03:00:00,10081.0 -2013-01-27 04:00:00,9880.0 -2013-01-27 05:00:00,9852.0 -2013-01-27 06:00:00,9863.0 -2013-01-27 07:00:00,10028.0 -2013-01-27 08:00:00,10250.0 -2013-01-27 09:00:00,10364.0 -2013-01-27 10:00:00,10611.0 -2013-01-27 11:00:00,10927.0 -2013-01-27 12:00:00,11065.0 -2013-01-27 13:00:00,11203.0 -2013-01-27 14:00:00,11212.0 -2013-01-27 15:00:00,11322.0 -2013-01-27 16:00:00,11352.0 -2013-01-27 17:00:00,11614.0 -2013-01-27 18:00:00,12143.0 -2013-01-27 19:00:00,12706.0 -2013-01-27 20:00:00,12613.0 -2013-01-27 21:00:00,12443.0 -2013-01-27 22:00:00,12079.0 -2013-01-27 23:00:00,11634.0 -2013-01-28 00:00:00,10950.0 -2013-01-26 01:00:00,11497.0 -2013-01-26 02:00:00,11026.0 -2013-01-26 03:00:00,10731.0 -2013-01-26 04:00:00,10568.0 -2013-01-26 05:00:00,10551.0 -2013-01-26 06:00:00,10661.0 -2013-01-26 07:00:00,10978.0 -2013-01-26 08:00:00,11478.0 -2013-01-26 09:00:00,11684.0 -2013-01-26 10:00:00,11887.0 -2013-01-26 11:00:00,11952.0 -2013-01-26 12:00:00,11896.0 -2013-01-26 13:00:00,11742.0 -2013-01-26 14:00:00,11538.0 -2013-01-26 15:00:00,11270.0 -2013-01-26 16:00:00,11016.0 -2013-01-26 17:00:00,10997.0 -2013-01-26 18:00:00,11306.0 -2013-01-26 19:00:00,12327.0 -2013-01-26 20:00:00,12531.0 -2013-01-26 21:00:00,12378.0 -2013-01-26 22:00:00,12183.0 -2013-01-26 23:00:00,11828.0 -2013-01-27 00:00:00,11313.0 -2013-01-25 01:00:00,11850.0 -2013-01-25 02:00:00,11352.0 -2013-01-25 03:00:00,11065.0 -2013-01-25 04:00:00,10877.0 -2013-01-25 05:00:00,10842.0 -2013-01-25 06:00:00,11102.0 -2013-01-25 07:00:00,11767.0 -2013-01-25 08:00:00,12848.0 -2013-01-25 09:00:00,13324.0 -2013-01-25 10:00:00,13411.0 -2013-01-25 11:00:00,13591.0 -2013-01-25 12:00:00,13661.0 -2013-01-25 13:00:00,13577.0 -2013-01-25 14:00:00,13473.0 -2013-01-25 15:00:00,13425.0 -2013-01-25 16:00:00,13313.0 -2013-01-25 17:00:00,13213.0 -2013-01-25 18:00:00,13399.0 -2013-01-25 19:00:00,14005.0 -2013-01-25 20:00:00,13892.0 -2013-01-25 21:00:00,13699.0 -2013-01-25 22:00:00,13333.0 -2013-01-25 23:00:00,12933.0 -2013-01-26 00:00:00,12175.0 -2013-01-24 01:00:00,12231.0 -2013-01-24 02:00:00,11758.0 -2013-01-24 03:00:00,11513.0 -2013-01-24 04:00:00,11423.0 -2013-01-24 05:00:00,11436.0 -2013-01-24 06:00:00,11691.0 -2013-01-24 07:00:00,12382.0 -2013-01-24 08:00:00,13548.0 -2013-01-24 09:00:00,14077.0 -2013-01-24 10:00:00,14092.0 -2013-01-24 11:00:00,14104.0 -2013-01-24 12:00:00,14057.0 -2013-01-24 13:00:00,13934.0 -2013-01-24 14:00:00,13764.0 -2013-01-24 15:00:00,13637.0 -2013-01-24 16:00:00,13509.0 -2013-01-24 17:00:00,13482.0 -2013-01-24 18:00:00,13933.0 -2013-01-24 19:00:00,14623.0 -2013-01-24 20:00:00,14509.0 -2013-01-24 21:00:00,14337.0 -2013-01-24 22:00:00,14021.0 -2013-01-24 23:00:00,13508.0 -2013-01-25 00:00:00,12609.0 -2013-01-23 01:00:00,12327.0 -2013-01-23 02:00:00,11875.0 -2013-01-23 03:00:00,11572.0 -2013-01-23 04:00:00,11409.0 -2013-01-23 05:00:00,11399.0 -2013-01-23 06:00:00,11592.0 -2013-01-23 07:00:00,12200.0 -2013-01-23 08:00:00,13307.0 -2013-01-23 09:00:00,13933.0 -2013-01-23 10:00:00,13999.0 -2013-01-23 11:00:00,14074.0 -2013-01-23 12:00:00,14194.0 -2013-01-23 13:00:00,14138.0 -2013-01-23 14:00:00,14071.0 -2013-01-23 15:00:00,14001.0 -2013-01-23 16:00:00,13884.0 -2013-01-23 17:00:00,13844.0 -2013-01-23 18:00:00,14121.0 -2013-01-23 19:00:00,14777.0 -2013-01-23 20:00:00,14683.0 -2013-01-23 21:00:00,14543.0 -2013-01-23 22:00:00,14239.0 -2013-01-23 23:00:00,13800.0 -2013-01-24 00:00:00,12943.0 -2013-01-22 01:00:00,12472.0 -2013-01-22 02:00:00,11983.0 -2013-01-22 03:00:00,11744.0 -2013-01-22 04:00:00,11615.0 -2013-01-22 05:00:00,11629.0 -2013-01-22 06:00:00,11851.0 -2013-01-22 07:00:00,12557.0 -2013-01-22 08:00:00,13741.0 -2013-01-22 09:00:00,14229.0 -2013-01-22 10:00:00,14206.0 -2013-01-22 11:00:00,14274.0 -2013-01-22 12:00:00,14259.0 -2013-01-22 13:00:00,14169.0 -2013-01-22 14:00:00,14085.0 -2013-01-22 15:00:00,13951.0 -2013-01-22 16:00:00,13879.0 -2013-01-22 17:00:00,13901.0 -2013-01-22 18:00:00,14401.0 -2013-01-22 19:00:00,15139.0 -2013-01-22 20:00:00,15052.0 -2013-01-22 21:00:00,14849.0 -2013-01-22 22:00:00,14534.0 -2013-01-22 23:00:00,13977.0 -2013-01-23 00:00:00,13116.0 -2013-01-21 01:00:00,11200.0 -2013-01-21 02:00:00,10827.0 -2013-01-21 03:00:00,10670.0 -2013-01-21 04:00:00,10608.0 -2013-01-21 05:00:00,10673.0 -2013-01-21 06:00:00,10954.0 -2013-01-21 07:00:00,11581.0 -2013-01-21 08:00:00,12575.0 -2013-01-21 09:00:00,13122.0 -2013-01-21 10:00:00,13352.0 -2013-01-21 11:00:00,13544.0 -2013-01-21 12:00:00,13739.0 -2013-01-21 13:00:00,13776.0 -2013-01-21 14:00:00,13773.0 -2013-01-21 15:00:00,13697.0 -2013-01-21 16:00:00,13582.0 -2013-01-21 17:00:00,13558.0 -2013-01-21 18:00:00,13975.0 -2013-01-21 19:00:00,14914.0 -2013-01-21 20:00:00,14950.0 -2013-01-21 21:00:00,14789.0 -2013-01-21 22:00:00,14518.0 -2013-01-21 23:00:00,14025.0 -2013-01-22 00:00:00,13183.0 -2013-01-20 01:00:00,10358.0 -2013-01-20 02:00:00,10024.0 -2013-01-20 03:00:00,9926.0 -2013-01-20 04:00:00,9820.0 -2013-01-20 05:00:00,9854.0 -2013-01-20 06:00:00,9932.0 -2013-01-20 07:00:00,10148.0 -2013-01-20 08:00:00,10419.0 -2013-01-20 09:00:00,10539.0 -2013-01-20 10:00:00,10727.0 -2013-01-20 11:00:00,10876.0 -2013-01-20 12:00:00,10962.0 -2013-01-20 13:00:00,11023.0 -2013-01-20 14:00:00,11071.0 -2013-01-20 15:00:00,11054.0 -2013-01-20 16:00:00,11117.0 -2013-01-20 17:00:00,11217.0 -2013-01-20 18:00:00,11751.0 -2013-01-20 19:00:00,12641.0 -2013-01-20 20:00:00,12756.0 -2013-01-20 21:00:00,12658.0 -2013-01-20 22:00:00,12510.0 -2013-01-20 23:00:00,12207.0 -2013-01-21 00:00:00,11689.0 -2013-01-19 01:00:00,10622.0 -2013-01-19 02:00:00,10049.0 -2013-01-19 03:00:00,9705.0 -2013-01-19 04:00:00,9475.0 -2013-01-19 05:00:00,9415.0 -2013-01-19 06:00:00,9458.0 -2013-01-19 07:00:00,9743.0 -2013-01-19 08:00:00,10194.0 -2013-01-19 09:00:00,10513.0 -2013-01-19 10:00:00,10782.0 -2013-01-19 11:00:00,11063.0 -2013-01-19 12:00:00,11077.0 -2013-01-19 13:00:00,10896.0 -2013-01-19 14:00:00,10804.0 -2013-01-19 15:00:00,10715.0 -2013-01-19 16:00:00,10512.0 -2013-01-19 17:00:00,10381.0 -2013-01-19 18:00:00,10643.0 -2013-01-19 19:00:00,11509.0 -2013-01-19 20:00:00,11545.0 -2013-01-19 21:00:00,11452.0 -2013-01-19 22:00:00,11317.0 -2013-01-19 23:00:00,11099.0 -2013-01-20 00:00:00,10713.0 -2013-01-18 01:00:00,11199.0 -2013-01-18 02:00:00,10729.0 -2013-01-18 03:00:00,10444.0 -2013-01-18 04:00:00,10323.0 -2013-01-18 05:00:00,10297.0 -2013-01-18 06:00:00,10538.0 -2013-01-18 07:00:00,11254.0 -2013-01-18 08:00:00,12424.0 -2013-01-18 09:00:00,13064.0 -2013-01-18 10:00:00,13140.0 -2013-01-18 11:00:00,13165.0 -2013-01-18 12:00:00,13153.0 -2013-01-18 13:00:00,12952.0 -2013-01-18 14:00:00,12652.0 -2013-01-18 15:00:00,12498.0 -2013-01-18 16:00:00,12260.0 -2013-01-18 17:00:00,12162.0 -2013-01-18 18:00:00,12426.0 -2013-01-18 19:00:00,13240.0 -2013-01-18 20:00:00,13159.0 -2013-01-18 21:00:00,12889.0 -2013-01-18 22:00:00,12536.0 -2013-01-18 23:00:00,12063.0 -2013-01-19 00:00:00,11318.0 -2013-01-17 01:00:00,10738.0 -2013-01-17 02:00:00,10203.0 -2013-01-17 03:00:00,9910.0 -2013-01-17 04:00:00,9783.0 -2013-01-17 05:00:00,9804.0 -2013-01-17 06:00:00,10096.0 -2013-01-17 07:00:00,10910.0 -2013-01-17 08:00:00,12213.0 -2013-01-17 09:00:00,12809.0 -2013-01-17 10:00:00,12756.0 -2013-01-17 11:00:00,12742.0 -2013-01-17 12:00:00,12762.0 -2013-01-17 13:00:00,12709.0 -2013-01-17 14:00:00,12617.0 -2013-01-17 15:00:00,12571.0 -2013-01-17 16:00:00,12419.0 -2013-01-17 17:00:00,12414.0 -2013-01-17 18:00:00,12816.0 -2013-01-17 19:00:00,13724.0 -2013-01-17 20:00:00,13670.0 -2013-01-17 21:00:00,13486.0 -2013-01-17 22:00:00,13228.0 -2013-01-17 23:00:00,12717.0 -2013-01-18 00:00:00,11903.0 -2013-01-16 01:00:00,11160.0 -2013-01-16 02:00:00,10707.0 -2013-01-16 03:00:00,10466.0 -2013-01-16 04:00:00,10351.0 -2013-01-16 05:00:00,10376.0 -2013-01-16 06:00:00,10662.0 -2013-01-16 07:00:00,11414.0 -2013-01-16 08:00:00,12684.0 -2013-01-16 09:00:00,13210.0 -2013-01-16 10:00:00,13047.0 -2013-01-16 11:00:00,13054.0 -2013-01-16 12:00:00,13049.0 -2013-01-16 13:00:00,12916.0 -2013-01-16 14:00:00,12803.0 -2013-01-16 15:00:00,12809.0 -2013-01-16 16:00:00,12862.0 -2013-01-16 17:00:00,12947.0 -2013-01-16 18:00:00,13371.0 -2013-01-16 19:00:00,13873.0 -2013-01-16 20:00:00,13644.0 -2013-01-16 21:00:00,13364.0 -2013-01-16 22:00:00,12990.0 -2013-01-16 23:00:00,12401.0 -2013-01-17 00:00:00,11499.0 -2013-01-15 01:00:00,11382.0 -2013-01-15 02:00:00,10925.0 -2013-01-15 03:00:00,10633.0 -2013-01-15 04:00:00,10530.0 -2013-01-15 05:00:00,10547.0 -2013-01-15 06:00:00,10814.0 -2013-01-15 07:00:00,11546.0 -2013-01-15 08:00:00,12796.0 -2013-01-15 09:00:00,13323.0 -2013-01-15 10:00:00,13202.0 -2013-01-15 11:00:00,13096.0 -2013-01-15 12:00:00,12999.0 -2013-01-15 13:00:00,12908.0 -2013-01-15 14:00:00,12774.0 -2013-01-15 15:00:00,12768.0 -2013-01-15 16:00:00,12638.0 -2013-01-15 17:00:00,12589.0 -2013-01-15 18:00:00,12977.0 -2013-01-15 19:00:00,13851.0 -2013-01-15 20:00:00,13771.0 -2013-01-15 21:00:00,13592.0 -2013-01-15 22:00:00,13321.0 -2013-01-15 23:00:00,12765.0 -2013-01-16 00:00:00,11919.0 -2013-01-14 01:00:00,10871.0 -2013-01-14 02:00:00,10564.0 -2013-01-14 03:00:00,10337.0 -2013-01-14 04:00:00,10301.0 -2013-01-14 05:00:00,10363.0 -2013-01-14 06:00:00,10661.0 -2013-01-14 07:00:00,11432.0 -2013-01-14 08:00:00,12777.0 -2013-01-14 09:00:00,13402.0 -2013-01-14 10:00:00,13373.0 -2013-01-14 11:00:00,13299.0 -2013-01-14 12:00:00,13277.0 -2013-01-14 13:00:00,13252.0 -2013-01-14 14:00:00,13121.0 -2013-01-14 15:00:00,13041.0 -2013-01-14 16:00:00,12792.0 -2013-01-14 17:00:00,12712.0 -2013-01-14 18:00:00,13154.0 -2013-01-14 19:00:00,14041.0 -2013-01-14 20:00:00,14023.0 -2013-01-14 21:00:00,13857.0 -2013-01-14 22:00:00,13522.0 -2013-01-14 23:00:00,12993.0 -2013-01-15 00:00:00,12140.0 -2013-01-13 01:00:00,10076.0 -2013-01-13 02:00:00,9666.0 -2013-01-13 03:00:00,9312.0 -2013-01-13 04:00:00,9151.0 -2013-01-13 05:00:00,8997.0 -2013-01-13 06:00:00,9074.0 -2013-01-13 07:00:00,9197.0 -2013-01-13 08:00:00,9546.0 -2013-01-13 09:00:00,9805.0 -2013-01-13 10:00:00,10087.0 -2013-01-13 11:00:00,10367.0 -2013-01-13 12:00:00,10689.0 -2013-01-13 13:00:00,10826.0 -2013-01-13 14:00:00,10966.0 -2013-01-13 15:00:00,11011.0 -2013-01-13 16:00:00,11022.0 -2013-01-13 17:00:00,11185.0 -2013-01-13 18:00:00,11804.0 -2013-01-13 19:00:00,12507.0 -2013-01-13 20:00:00,12609.0 -2013-01-13 21:00:00,12504.0 -2013-01-13 22:00:00,12345.0 -2013-01-13 23:00:00,12005.0 -2013-01-14 00:00:00,11457.0 -2013-01-12 01:00:00,9962.0 -2013-01-12 02:00:00,9393.0 -2013-01-12 03:00:00,9031.0 -2013-01-12 04:00:00,8760.0 -2013-01-12 05:00:00,8659.0 -2013-01-12 06:00:00,8657.0 -2013-01-12 07:00:00,8897.0 -2013-01-12 08:00:00,9369.0 -2013-01-12 09:00:00,9676.0 -2013-01-12 10:00:00,9939.0 -2013-01-12 11:00:00,10227.0 -2013-01-12 12:00:00,10444.0 -2013-01-12 13:00:00,10480.0 -2013-01-12 14:00:00,10430.0 -2013-01-12 15:00:00,10328.0 -2013-01-12 16:00:00,10369.0 -2013-01-12 17:00:00,10511.0 -2013-01-12 18:00:00,11052.0 -2013-01-12 19:00:00,11628.0 -2013-01-12 20:00:00,11559.0 -2013-01-12 21:00:00,11427.0 -2013-01-12 22:00:00,11330.0 -2013-01-12 23:00:00,11069.0 -2013-01-13 00:00:00,10598.0 -2013-01-11 01:00:00,10377.0 -2013-01-11 02:00:00,9781.0 -2013-01-11 03:00:00,9452.0 -2013-01-11 04:00:00,9244.0 -2013-01-11 05:00:00,9162.0 -2013-01-11 06:00:00,9310.0 -2013-01-11 07:00:00,9931.0 -2013-01-11 08:00:00,11057.0 -2013-01-11 09:00:00,11711.0 -2013-01-11 10:00:00,11778.0 -2013-01-11 11:00:00,11856.0 -2013-01-11 12:00:00,11988.0 -2013-01-11 13:00:00,11952.0 -2013-01-11 14:00:00,11945.0 -2013-01-11 15:00:00,11900.0 -2013-01-11 16:00:00,11773.0 -2013-01-11 17:00:00,11700.0 -2013-01-11 18:00:00,12109.0 -2013-01-11 19:00:00,12559.0 -2013-01-11 20:00:00,12403.0 -2013-01-11 21:00:00,12116.0 -2013-01-11 22:00:00,11797.0 -2013-01-11 23:00:00,11384.0 -2013-01-12 00:00:00,10667.0 -2013-01-10 01:00:00,10495.0 -2013-01-10 02:00:00,10057.0 -2013-01-10 03:00:00,9792.0 -2013-01-10 04:00:00,9680.0 -2013-01-10 05:00:00,9692.0 -2013-01-10 06:00:00,9962.0 -2013-01-10 07:00:00,10707.0 -2013-01-10 08:00:00,11963.0 -2013-01-10 09:00:00,12658.0 -2013-01-10 10:00:00,12559.0 -2013-01-10 11:00:00,12494.0 -2013-01-10 12:00:00,12589.0 -2013-01-10 13:00:00,12510.0 -2013-01-10 14:00:00,12300.0 -2013-01-10 15:00:00,12297.0 -2013-01-10 16:00:00,12302.0 -2013-01-10 17:00:00,12414.0 -2013-01-10 18:00:00,12978.0 -2013-01-10 19:00:00,13430.0 -2013-01-10 20:00:00,13219.0 -2013-01-10 21:00:00,12967.0 -2013-01-10 22:00:00,12641.0 -2013-01-10 23:00:00,12084.0 -2013-01-11 00:00:00,11182.0 -2013-01-09 01:00:00,10617.0 -2013-01-09 02:00:00,10080.0 -2013-01-09 03:00:00,9749.0 -2013-01-09 04:00:00,9564.0 -2013-01-09 05:00:00,9587.0 -2013-01-09 06:00:00,9808.0 -2013-01-09 07:00:00,10562.0 -2013-01-09 08:00:00,11802.0 -2013-01-09 09:00:00,12376.0 -2013-01-09 10:00:00,12230.0 -2013-01-09 11:00:00,12187.0 -2013-01-09 12:00:00,12134.0 -2013-01-09 13:00:00,12078.0 -2013-01-09 14:00:00,12006.0 -2013-01-09 15:00:00,11950.0 -2013-01-09 16:00:00,11851.0 -2013-01-09 17:00:00,11799.0 -2013-01-09 18:00:00,12362.0 -2013-01-09 19:00:00,13144.0 -2013-01-09 20:00:00,13017.0 -2013-01-09 21:00:00,12858.0 -2013-01-09 22:00:00,12583.0 -2013-01-09 23:00:00,12068.0 -2013-01-10 00:00:00,11263.0 -2013-01-08 01:00:00,10806.0 -2013-01-08 02:00:00,10318.0 -2013-01-08 03:00:00,10031.0 -2013-01-08 04:00:00,9887.0 -2013-01-08 05:00:00,9877.0 -2013-01-08 06:00:00,10135.0 -2013-01-08 07:00:00,10855.0 -2013-01-08 08:00:00,12113.0 -2013-01-08 09:00:00,12684.0 -2013-01-08 10:00:00,12613.0 -2013-01-08 11:00:00,12590.0 -2013-01-08 12:00:00,12568.0 -2013-01-08 13:00:00,12374.0 -2013-01-08 14:00:00,12264.0 -2013-01-08 15:00:00,12244.0 -2013-01-08 16:00:00,12117.0 -2013-01-08 17:00:00,12085.0 -2013-01-08 18:00:00,12636.0 -2013-01-08 19:00:00,13467.0 -2013-01-08 20:00:00,13313.0 -2013-01-08 21:00:00,13119.0 -2013-01-08 22:00:00,12837.0 -2013-01-08 23:00:00,12286.0 -2013-01-09 00:00:00,11419.0 -2013-01-07 01:00:00,10612.0 -2013-01-07 02:00:00,10244.0 -2013-01-07 03:00:00,10072.0 -2013-01-07 04:00:00,9992.0 -2013-01-07 05:00:00,10095.0 -2013-01-07 06:00:00,10442.0 -2013-01-07 07:00:00,11264.0 -2013-01-07 08:00:00,12518.0 -2013-01-07 09:00:00,13104.0 -2013-01-07 10:00:00,12949.0 -2013-01-07 11:00:00,12937.0 -2013-01-07 12:00:00,12901.0 -2013-01-07 13:00:00,12751.0 -2013-01-07 14:00:00,12619.0 -2013-01-07 15:00:00,12546.0 -2013-01-07 16:00:00,12365.0 -2013-01-07 17:00:00,12348.0 -2013-01-07 18:00:00,12972.0 -2013-01-07 19:00:00,13747.0 -2013-01-07 20:00:00,13626.0 -2013-01-07 21:00:00,13382.0 -2013-01-07 22:00:00,13046.0 -2013-01-07 23:00:00,12452.0 -2013-01-08 00:00:00,11632.0 -2013-01-06 01:00:00,10533.0 -2013-01-06 02:00:00,9985.0 -2013-01-06 03:00:00,9639.0 -2013-01-06 04:00:00,9348.0 -2013-01-06 05:00:00,9275.0 -2013-01-06 06:00:00,9300.0 -2013-01-06 07:00:00,9547.0 -2013-01-06 08:00:00,9837.0 -2013-01-06 09:00:00,10050.0 -2013-01-06 10:00:00,10192.0 -2013-01-06 11:00:00,10452.0 -2013-01-06 12:00:00,10641.0 -2013-01-06 13:00:00,10677.0 -2013-01-06 14:00:00,10624.0 -2013-01-06 15:00:00,10514.0 -2013-01-06 16:00:00,10450.0 -2013-01-06 17:00:00,10582.0 -2013-01-06 18:00:00,11293.0 -2013-01-06 19:00:00,12373.0 -2013-01-06 20:00:00,12421.0 -2013-01-06 21:00:00,12396.0 -2013-01-06 22:00:00,12109.0 -2013-01-06 23:00:00,11775.0 -2013-01-07 00:00:00,11164.0 -2013-01-05 01:00:00,11261.0 -2013-01-05 02:00:00,10728.0 -2013-01-05 03:00:00,10307.0 -2013-01-05 04:00:00,10126.0 -2013-01-05 05:00:00,9997.0 -2013-01-05 06:00:00,10081.0 -2013-01-05 07:00:00,10348.0 -2013-01-05 08:00:00,10821.0 -2013-01-05 09:00:00,11064.0 -2013-01-05 10:00:00,11161.0 -2013-01-05 11:00:00,11275.0 -2013-01-05 12:00:00,11369.0 -2013-01-05 13:00:00,11346.0 -2013-01-05 14:00:00,11257.0 -2013-01-05 15:00:00,11289.0 -2013-01-05 16:00:00,11446.0 -2013-01-05 17:00:00,11459.0 -2013-01-05 18:00:00,12013.0 -2013-01-05 19:00:00,12557.0 -2013-01-05 20:00:00,12525.0 -2013-01-05 21:00:00,12330.0 -2013-01-05 22:00:00,12175.0 -2013-01-05 23:00:00,11806.0 -2013-01-06 00:00:00,11183.0 -2013-01-04 01:00:00,11528.0 -2013-01-04 02:00:00,11019.0 -2013-01-04 03:00:00,10708.0 -2013-01-04 04:00:00,10598.0 -2013-01-04 05:00:00,10648.0 -2013-01-04 06:00:00,10915.0 -2013-01-04 07:00:00,11604.0 -2013-01-04 08:00:00,12587.0 -2013-01-04 09:00:00,13111.0 -2013-01-04 10:00:00,13173.0 -2013-01-04 11:00:00,13165.0 -2013-01-04 12:00:00,13127.0 -2013-01-04 13:00:00,13000.0 -2013-01-04 14:00:00,12777.0 -2013-01-04 15:00:00,12650.0 -2013-01-04 16:00:00,12490.0 -2013-01-04 17:00:00,12391.0 -2013-01-04 18:00:00,12977.0 -2013-01-04 19:00:00,13784.0 -2013-01-04 20:00:00,13693.0 -2013-01-04 21:00:00,13383.0 -2013-01-04 22:00:00,13151.0 -2013-01-04 23:00:00,12716.0 -2013-01-05 00:00:00,12061.0 -2013-01-03 01:00:00,11443.0 -2013-01-03 02:00:00,10882.0 -2013-01-03 03:00:00,10526.0 -2013-01-03 04:00:00,10267.0 -2013-01-03 05:00:00,10213.0 -2013-01-03 06:00:00,10399.0 -2013-01-03 07:00:00,10982.0 -2013-01-03 08:00:00,11955.0 -2013-01-03 09:00:00,12607.0 -2013-01-03 10:00:00,12789.0 -2013-01-03 11:00:00,12968.0 -2013-01-03 12:00:00,13112.0 -2013-01-03 13:00:00,13178.0 -2013-01-03 14:00:00,13179.0 -2013-01-03 15:00:00,13182.0 -2013-01-03 16:00:00,13069.0 -2013-01-03 17:00:00,13001.0 -2013-01-03 18:00:00,13570.0 -2013-01-03 19:00:00,14201.0 -2013-01-03 20:00:00,14044.0 -2013-01-03 21:00:00,13833.0 -2013-01-03 22:00:00,13521.0 -2013-01-03 23:00:00,13076.0 -2013-01-04 00:00:00,12279.0 -2013-01-02 01:00:00,10780.0 -2013-01-02 02:00:00,10391.0 -2013-01-02 03:00:00,10157.0 -2013-01-02 04:00:00,10051.0 -2013-01-02 05:00:00,10087.0 -2013-01-02 06:00:00,10331.0 -2013-01-02 07:00:00,10980.0 -2013-01-02 08:00:00,12081.0 -2013-01-02 09:00:00,12656.0 -2013-01-02 10:00:00,12811.0 -2013-01-02 11:00:00,12887.0 -2013-01-02 12:00:00,12930.0 -2013-01-02 13:00:00,12900.0 -2013-01-02 14:00:00,12808.0 -2013-01-02 15:00:00,12705.0 -2013-01-02 16:00:00,12583.0 -2013-01-02 17:00:00,12562.0 -2013-01-02 18:00:00,13237.0 -2013-01-02 19:00:00,14138.0 -2013-01-02 20:00:00,14039.0 -2013-01-02 21:00:00,13857.0 -2013-01-02 22:00:00,13574.0 -2013-01-02 23:00:00,13098.0 -2013-01-03 00:00:00,12287.0 -2013-01-01 01:00:00,10581.0 -2013-01-01 02:00:00,10175.0 -2013-01-01 03:00:00,9866.0 -2013-01-01 04:00:00,9631.0 -2013-01-01 05:00:00,9536.0 -2013-01-01 06:00:00,9583.0 -2013-01-01 07:00:00,9778.0 -2013-01-01 08:00:00,10059.0 -2013-01-01 09:00:00,10047.0 -2013-01-01 10:00:00,9984.0 -2013-01-01 11:00:00,10102.0 -2013-01-01 12:00:00,10227.0 -2013-01-01 13:00:00,10262.0 -2013-01-01 14:00:00,10263.0 -2013-01-01 15:00:00,10216.0 -2013-01-01 16:00:00,10173.0 -2013-01-01 17:00:00,10339.0 -2013-01-01 18:00:00,11208.0 -2013-01-01 19:00:00,12355.0 -2013-01-01 20:00:00,12429.0 -2013-01-01 21:00:00,12364.0 -2013-01-01 22:00:00,12229.0 -2013-01-01 23:00:00,11909.0 -2013-01-02 00:00:00,11381.0 -2014-12-31 01:00:00,11633.0 -2014-12-31 02:00:00,11139.0 -2014-12-31 03:00:00,10871.0 -2014-12-31 04:00:00,10735.0 -2014-12-31 05:00:00,10714.0 -2014-12-31 06:00:00,10886.0 -2014-12-31 07:00:00,11404.0 -2014-12-31 08:00:00,12098.0 -2014-12-31 09:00:00,12409.0 -2014-12-31 10:00:00,12526.0 -2014-12-31 11:00:00,12620.0 -2014-12-31 12:00:00,12672.0 -2014-12-31 13:00:00,12608.0 -2014-12-31 14:00:00,12441.0 -2014-12-31 15:00:00,12281.0 -2014-12-31 16:00:00,12123.0 -2014-12-31 17:00:00,12081.0 -2014-12-31 18:00:00,12851.0 -2014-12-31 19:00:00,13663.0 -2014-12-31 20:00:00,13419.0 -2014-12-31 21:00:00,13067.0 -2014-12-31 22:00:00,12672.0 -2014-12-31 23:00:00,12244.0 -2015-01-01 00:00:00,11774.0 -2014-12-30 01:00:00,10988.0 -2014-12-30 02:00:00,10521.0 -2014-12-30 03:00:00,10264.0 -2014-12-30 04:00:00,10114.0 -2014-12-30 05:00:00,10150.0 -2014-12-30 06:00:00,10432.0 -2014-12-30 07:00:00,11105.0 -2014-12-30 08:00:00,12020.0 -2014-12-30 09:00:00,12496.0 -2014-12-30 10:00:00,12593.0 -2014-12-30 11:00:00,12687.0 -2014-12-30 12:00:00,12708.0 -2014-12-30 13:00:00,12651.0 -2014-12-30 14:00:00,12541.0 -2014-12-30 15:00:00,12490.0 -2014-12-30 16:00:00,12405.0 -2014-12-30 17:00:00,12460.0 -2014-12-30 18:00:00,13201.0 -2014-12-30 19:00:00,14012.0 -2014-12-30 20:00:00,13869.0 -2014-12-30 21:00:00,13705.0 -2014-12-30 22:00:00,13503.0 -2014-12-30 23:00:00,13083.0 -2014-12-31 00:00:00,12388.0 -2014-12-29 01:00:00,10290.0 -2014-12-29 02:00:00,9809.0 -2014-12-29 03:00:00,9606.0 -2014-12-29 04:00:00,9442.0 -2014-12-29 05:00:00,9491.0 -2014-12-29 06:00:00,9703.0 -2014-12-29 07:00:00,10364.0 -2014-12-29 08:00:00,11238.0 -2014-12-29 09:00:00,11754.0 -2014-12-29 10:00:00,11909.0 -2014-12-29 11:00:00,12058.0 -2014-12-29 12:00:00,12126.0 -2014-12-29 13:00:00,12088.0 -2014-12-29 14:00:00,12095.0 -2014-12-29 15:00:00,12114.0 -2014-12-29 16:00:00,12107.0 -2014-12-29 17:00:00,12162.0 -2014-12-29 18:00:00,12891.0 -2014-12-29 19:00:00,13514.0 -2014-12-29 20:00:00,13355.0 -2014-12-29 21:00:00,13105.0 -2014-12-29 22:00:00,12884.0 -2014-12-29 23:00:00,12414.0 -2014-12-30 00:00:00,11710.0 -2014-12-28 01:00:00,10030.0 -2014-12-28 02:00:00,9590.0 -2014-12-28 03:00:00,9225.0 -2014-12-28 04:00:00,9082.0 -2014-12-28 05:00:00,8985.0 -2014-12-28 06:00:00,9069.0 -2014-12-28 07:00:00,9246.0 -2014-12-28 08:00:00,9559.0 -2014-12-28 09:00:00,9707.0 -2014-12-28 10:00:00,9868.0 -2014-12-28 11:00:00,10041.0 -2014-12-28 12:00:00,10108.0 -2014-12-28 13:00:00,10165.0 -2014-12-28 14:00:00,10064.0 -2014-12-28 15:00:00,9957.0 -2014-12-28 16:00:00,9864.0 -2014-12-28 17:00:00,9984.0 -2014-12-28 18:00:00,10794.0 -2014-12-28 19:00:00,11746.0 -2014-12-28 20:00:00,11832.0 -2014-12-28 21:00:00,11807.0 -2014-12-28 22:00:00,11634.0 -2014-12-28 23:00:00,11395.0 -2014-12-29 00:00:00,10835.0 -2014-12-27 01:00:00,9464.0 -2014-12-27 02:00:00,8989.0 -2014-12-27 03:00:00,8591.0 -2014-12-27 04:00:00,8412.0 -2014-12-27 05:00:00,8301.0 -2014-12-27 06:00:00,8385.0 -2014-12-27 07:00:00,8587.0 -2014-12-27 08:00:00,9034.0 -2014-12-27 09:00:00,9334.0 -2014-12-27 10:00:00,9602.0 -2014-12-27 11:00:00,9948.0 -2014-12-27 12:00:00,10225.0 -2014-12-27 13:00:00,10397.0 -2014-12-27 14:00:00,10477.0 -2014-12-27 15:00:00,10354.0 -2014-12-27 16:00:00,10348.0 -2014-12-27 17:00:00,10522.0 -2014-12-27 18:00:00,11248.0 -2014-12-27 19:00:00,11691.0 -2014-12-27 20:00:00,11684.0 -2014-12-27 21:00:00,11523.0 -2014-12-27 22:00:00,11403.0 -2014-12-27 23:00:00,11107.0 -2014-12-28 00:00:00,10640.0 -2014-12-26 01:00:00,9388.0 -2014-12-26 02:00:00,8952.0 -2014-12-26 03:00:00,8688.0 -2014-12-26 04:00:00,8555.0 -2014-12-26 05:00:00,8566.0 -2014-12-26 06:00:00,8766.0 -2014-12-26 07:00:00,9258.0 -2014-12-26 08:00:00,9919.0 -2014-12-26 09:00:00,10180.0 -2014-12-26 10:00:00,10275.0 -2014-12-26 11:00:00,10399.0 -2014-12-26 12:00:00,10440.0 -2014-12-26 13:00:00,10394.0 -2014-12-26 14:00:00,10294.0 -2014-12-26 15:00:00,10187.0 -2014-12-26 16:00:00,10087.0 -2014-12-26 17:00:00,10114.0 -2014-12-26 18:00:00,10882.0 -2014-12-26 19:00:00,11661.0 -2014-12-26 20:00:00,11542.0 -2014-12-26 21:00:00,11346.0 -2014-12-26 22:00:00,11049.0 -2014-12-26 23:00:00,10730.0 -2014-12-27 00:00:00,10164.0 -2014-12-25 01:00:00,9681.0 -2014-12-25 02:00:00,9210.0 -2014-12-25 03:00:00,8851.0 -2014-12-25 04:00:00,8632.0 -2014-12-25 05:00:00,8526.0 -2014-12-25 06:00:00,8601.0 -2014-12-25 07:00:00,8798.0 -2014-12-25 08:00:00,9088.0 -2014-12-25 09:00:00,9251.0 -2014-12-25 10:00:00,9449.0 -2014-12-25 11:00:00,9696.0 -2014-12-25 12:00:00,9757.0 -2014-12-25 13:00:00,9640.0 -2014-12-25 14:00:00,9501.0 -2014-12-25 15:00:00,9310.0 -2014-12-25 16:00:00,9150.0 -2014-12-25 17:00:00,9218.0 -2014-12-25 18:00:00,9865.0 -2014-12-25 19:00:00,10499.0 -2014-12-25 20:00:00,10477.0 -2014-12-25 21:00:00,10491.0 -2014-12-25 22:00:00,10451.0 -2014-12-25 23:00:00,10272.0 -2014-12-26 00:00:00,9901.0 -2014-12-24 01:00:00,10159.0 -2014-12-24 02:00:00,9477.0 -2014-12-24 03:00:00,9055.0 -2014-12-24 04:00:00,8789.0 -2014-12-24 05:00:00,8735.0 -2014-12-24 06:00:00,8874.0 -2014-12-24 07:00:00,9265.0 -2014-12-24 08:00:00,9863.0 -2014-12-24 09:00:00,10291.0 -2014-12-24 10:00:00,10530.0 -2014-12-24 11:00:00,10824.0 -2014-12-24 12:00:00,10955.0 -2014-12-24 13:00:00,10948.0 -2014-12-24 14:00:00,10845.0 -2014-12-24 15:00:00,10790.0 -2014-12-24 16:00:00,10718.0 -2014-12-24 17:00:00,10835.0 -2014-12-24 18:00:00,11428.0 -2014-12-24 19:00:00,11655.0 -2014-12-24 20:00:00,11259.0 -2014-12-24 21:00:00,10963.0 -2014-12-24 22:00:00,10770.0 -2014-12-24 23:00:00,10549.0 -2014-12-25 00:00:00,10183.0 -2014-12-23 01:00:00,10667.0 -2014-12-23 02:00:00,10019.0 -2014-12-23 03:00:00,9584.0 -2014-12-23 04:00:00,9315.0 -2014-12-23 05:00:00,9215.0 -2014-12-23 06:00:00,9356.0 -2014-12-23 07:00:00,9926.0 -2014-12-23 08:00:00,10816.0 -2014-12-23 09:00:00,11427.0 -2014-12-23 10:00:00,11511.0 -2014-12-23 11:00:00,11566.0 -2014-12-23 12:00:00,11683.0 -2014-12-23 13:00:00,11589.0 -2014-12-23 14:00:00,11480.0 -2014-12-23 15:00:00,11448.0 -2014-12-23 16:00:00,11344.0 -2014-12-23 17:00:00,11303.0 -2014-12-23 18:00:00,12019.0 -2014-12-23 19:00:00,12710.0 -2014-12-23 20:00:00,12570.0 -2014-12-23 21:00:00,12353.0 -2014-12-23 22:00:00,12128.0 -2014-12-23 23:00:00,11727.0 -2014-12-24 00:00:00,10984.0 -2014-12-22 01:00:00,10467.0 -2014-12-22 02:00:00,10035.0 -2014-12-22 03:00:00,9735.0 -2014-12-22 04:00:00,9598.0 -2014-12-22 05:00:00,9617.0 -2014-12-22 06:00:00,9879.0 -2014-12-22 07:00:00,10577.0 -2014-12-22 08:00:00,11505.0 -2014-12-22 09:00:00,12200.0 -2014-12-22 10:00:00,12404.0 -2014-12-22 11:00:00,12544.0 -2014-12-22 12:00:00,12672.0 -2014-12-22 13:00:00,12631.0 -2014-12-22 14:00:00,12586.0 -2014-12-22 15:00:00,12578.0 -2014-12-22 16:00:00,12598.0 -2014-12-22 17:00:00,12731.0 -2014-12-22 18:00:00,13341.0 -2014-12-22 19:00:00,13590.0 -2014-12-22 20:00:00,13361.0 -2014-12-22 21:00:00,13088.0 -2014-12-22 22:00:00,12789.0 -2014-12-22 23:00:00,12323.0 -2014-12-23 00:00:00,11510.0 -2014-12-21 01:00:00,10568.0 -2014-12-21 02:00:00,9990.0 -2014-12-21 03:00:00,9606.0 -2014-12-21 04:00:00,9388.0 -2014-12-21 05:00:00,9319.0 -2014-12-21 06:00:00,9329.0 -2014-12-21 07:00:00,9521.0 -2014-12-21 08:00:00,9783.0 -2014-12-21 09:00:00,10085.0 -2014-12-21 10:00:00,10320.0 -2014-12-21 11:00:00,10600.0 -2014-12-21 12:00:00,10763.0 -2014-12-21 13:00:00,10869.0 -2014-12-21 14:00:00,10810.0 -2014-12-21 15:00:00,10729.0 -2014-12-21 16:00:00,10690.0 -2014-12-21 17:00:00,10792.0 -2014-12-21 18:00:00,11622.0 -2014-12-21 19:00:00,12251.0 -2014-12-21 20:00:00,12236.0 -2014-12-21 21:00:00,12193.0 -2014-12-21 22:00:00,12020.0 -2014-12-21 23:00:00,11733.0 -2014-12-22 00:00:00,11189.0 -2014-12-20 01:00:00,10977.0 -2014-12-20 02:00:00,10418.0 -2014-12-20 03:00:00,10012.0 -2014-12-20 04:00:00,9823.0 -2014-12-20 05:00:00,9677.0 -2014-12-20 06:00:00,9794.0 -2014-12-20 07:00:00,10039.0 -2014-12-20 08:00:00,10527.0 -2014-12-20 09:00:00,10887.0 -2014-12-20 10:00:00,11158.0 -2014-12-20 11:00:00,11480.0 -2014-12-20 12:00:00,11585.0 -2014-12-20 13:00:00,11567.0 -2014-12-20 14:00:00,11490.0 -2014-12-20 15:00:00,11263.0 -2014-12-20 16:00:00,11202.0 -2014-12-20 17:00:00,11265.0 -2014-12-20 18:00:00,12046.0 -2014-12-20 19:00:00,12439.0 -2014-12-20 20:00:00,12394.0 -2014-12-20 21:00:00,12269.0 -2014-12-20 22:00:00,12112.0 -2014-12-20 23:00:00,11745.0 -2014-12-21 00:00:00,11256.0 -2014-12-19 01:00:00,11185.0 -2014-12-19 02:00:00,10589.0 -2014-12-19 03:00:00,10237.0 -2014-12-19 04:00:00,10016.0 -2014-12-19 05:00:00,9998.0 -2014-12-19 06:00:00,10221.0 -2014-12-19 07:00:00,10933.0 -2014-12-19 08:00:00,12023.0 -2014-12-19 09:00:00,12665.0 -2014-12-19 10:00:00,12752.0 -2014-12-19 11:00:00,12837.0 -2014-12-19 12:00:00,12848.0 -2014-12-19 13:00:00,12802.0 -2014-12-19 14:00:00,12605.0 -2014-12-19 15:00:00,12561.0 -2014-12-19 16:00:00,12534.0 -2014-12-19 17:00:00,12690.0 -2014-12-19 18:00:00,13439.0 -2014-12-19 19:00:00,13775.0 -2014-12-19 20:00:00,13566.0 -2014-12-19 21:00:00,13325.0 -2014-12-19 22:00:00,12947.0 -2014-12-19 23:00:00,12560.0 -2014-12-20 00:00:00,11837.0 -2014-12-18 01:00:00,11283.0 -2014-12-18 02:00:00,10654.0 -2014-12-18 03:00:00,10355.0 -2014-12-18 04:00:00,10198.0 -2014-12-18 05:00:00,10170.0 -2014-12-18 06:00:00,10438.0 -2014-12-18 07:00:00,11228.0 -2014-12-18 08:00:00,12379.0 -2014-12-18 09:00:00,12865.0 -2014-12-18 10:00:00,12931.0 -2014-12-18 11:00:00,13040.0 -2014-12-18 12:00:00,13051.0 -2014-12-18 13:00:00,12966.0 -2014-12-18 14:00:00,12841.0 -2014-12-18 15:00:00,12829.0 -2014-12-18 16:00:00,12759.0 -2014-12-18 17:00:00,12840.0 -2014-12-18 18:00:00,13667.0 -2014-12-18 19:00:00,14095.0 -2014-12-18 20:00:00,13923.0 -2014-12-18 21:00:00,13716.0 -2014-12-18 22:00:00,13429.0 -2014-12-18 23:00:00,12907.0 -2014-12-19 00:00:00,12068.0 -2014-12-17 01:00:00,10849.0 -2014-12-17 02:00:00,10278.0 -2014-12-17 03:00:00,9950.0 -2014-12-17 04:00:00,9804.0 -2014-12-17 05:00:00,9827.0 -2014-12-17 06:00:00,10130.0 -2014-12-17 07:00:00,10919.0 -2014-12-17 08:00:00,12134.0 -2014-12-17 09:00:00,12784.0 -2014-12-17 10:00:00,12897.0 -2014-12-17 11:00:00,12974.0 -2014-12-17 12:00:00,13072.0 -2014-12-17 13:00:00,13040.0 -2014-12-17 14:00:00,12995.0 -2014-12-17 15:00:00,13053.0 -2014-12-17 16:00:00,13005.0 -2014-12-17 17:00:00,13117.0 -2014-12-17 18:00:00,13848.0 -2014-12-17 19:00:00,14310.0 -2014-12-17 20:00:00,14099.0 -2014-12-17 21:00:00,13856.0 -2014-12-17 22:00:00,13600.0 -2014-12-17 23:00:00,13054.0 -2014-12-18 00:00:00,12179.0 -2014-12-16 01:00:00,10292.0 -2014-12-16 02:00:00,9677.0 -2014-12-16 03:00:00,9295.0 -2014-12-16 04:00:00,9104.0 -2014-12-16 05:00:00,9071.0 -2014-12-16 06:00:00,9313.0 -2014-12-16 07:00:00,10013.0 -2014-12-16 08:00:00,11169.0 -2014-12-16 09:00:00,11849.0 -2014-12-16 10:00:00,12004.0 -2014-12-16 11:00:00,12091.0 -2014-12-16 12:00:00,12204.0 -2014-12-16 13:00:00,12230.0 -2014-12-16 14:00:00,12227.0 -2014-12-16 15:00:00,12323.0 -2014-12-16 16:00:00,12350.0 -2014-12-16 17:00:00,12556.0 -2014-12-16 18:00:00,13293.0 -2014-12-16 19:00:00,13705.0 -2014-12-16 20:00:00,13577.0 -2014-12-16 21:00:00,13388.0 -2014-12-16 22:00:00,13095.0 -2014-12-16 23:00:00,12580.0 -2014-12-17 00:00:00,11700.0 -2014-12-15 01:00:00,9637.0 -2014-12-15 02:00:00,9162.0 -2014-12-15 03:00:00,8892.0 -2014-12-15 04:00:00,8784.0 -2014-12-15 05:00:00,8800.0 -2014-12-15 06:00:00,9160.0 -2014-12-15 07:00:00,9926.0 -2014-12-15 08:00:00,11138.0 -2014-12-15 09:00:00,11787.0 -2014-12-15 10:00:00,11933.0 -2014-12-15 11:00:00,11966.0 -2014-12-15 12:00:00,12048.0 -2014-12-15 13:00:00,12060.0 -2014-12-15 14:00:00,12047.0 -2014-12-15 15:00:00,12059.0 -2014-12-15 16:00:00,11997.0 -2014-12-15 17:00:00,12156.0 -2014-12-15 18:00:00,12910.0 -2014-12-15 19:00:00,13317.0 -2014-12-15 20:00:00,13092.0 -2014-12-15 21:00:00,12928.0 -2014-12-15 22:00:00,12598.0 -2014-12-15 23:00:00,12050.0 -2014-12-16 00:00:00,11157.0 -2014-12-14 01:00:00,9871.0 -2014-12-14 02:00:00,9309.0 -2014-12-14 03:00:00,8941.0 -2014-12-14 04:00:00,8679.0 -2014-12-14 05:00:00,8546.0 -2014-12-14 06:00:00,8548.0 -2014-12-14 07:00:00,8662.0 -2014-12-14 08:00:00,8966.0 -2014-12-14 09:00:00,9147.0 -2014-12-14 10:00:00,9361.0 -2014-12-14 11:00:00,9640.0 -2014-12-14 12:00:00,9756.0 -2014-12-14 13:00:00,9824.0 -2014-12-14 14:00:00,9861.0 -2014-12-14 15:00:00,9878.0 -2014-12-14 16:00:00,9917.0 -2014-12-14 17:00:00,10101.0 -2014-12-14 18:00:00,10943.0 -2014-12-14 19:00:00,11478.0 -2014-12-14 20:00:00,11549.0 -2014-12-14 21:00:00,11519.0 -2014-12-14 22:00:00,11298.0 -2014-12-14 23:00:00,10940.0 -2014-12-15 00:00:00,10271.0 -2014-12-13 01:00:00,10850.0 -2014-12-13 02:00:00,10204.0 -2014-12-13 03:00:00,9774.0 -2014-12-13 04:00:00,9561.0 -2014-12-13 05:00:00,9444.0 -2014-12-13 06:00:00,9493.0 -2014-12-13 07:00:00,9785.0 -2014-12-13 08:00:00,10301.0 -2014-12-13 09:00:00,10627.0 -2014-12-13 10:00:00,10950.0 -2014-12-13 11:00:00,11142.0 -2014-12-13 12:00:00,11216.0 -2014-12-13 13:00:00,11126.0 -2014-12-13 14:00:00,11040.0 -2014-12-13 15:00:00,10850.0 -2014-12-13 16:00:00,10699.0 -2014-12-13 17:00:00,10761.0 -2014-12-13 18:00:00,11493.0 -2014-12-13 19:00:00,11930.0 -2014-12-13 20:00:00,11826.0 -2014-12-13 21:00:00,11653.0 -2014-12-13 22:00:00,11456.0 -2014-12-13 23:00:00,11104.0 -2014-12-14 00:00:00,10506.0 -2014-12-12 01:00:00,11201.0 -2014-12-12 02:00:00,10646.0 -2014-12-12 03:00:00,10351.0 -2014-12-12 04:00:00,10183.0 -2014-12-12 05:00:00,10217.0 -2014-12-12 06:00:00,10469.0 -2014-12-12 07:00:00,11195.0 -2014-12-12 08:00:00,12275.0 -2014-12-12 09:00:00,12839.0 -2014-12-12 10:00:00,12971.0 -2014-12-12 11:00:00,13012.0 -2014-12-12 12:00:00,13017.0 -2014-12-12 13:00:00,12947.0 -2014-12-12 14:00:00,12786.0 -2014-12-12 15:00:00,12696.0 -2014-12-12 16:00:00,12604.0 -2014-12-12 17:00:00,12670.0 -2014-12-12 18:00:00,13383.0 -2014-12-12 19:00:00,13693.0 -2014-12-12 20:00:00,13464.0 -2014-12-12 21:00:00,13168.0 -2014-12-12 22:00:00,12840.0 -2014-12-12 23:00:00,12428.0 -2014-12-13 00:00:00,11647.0 -2014-12-11 01:00:00,10971.0 -2014-12-11 02:00:00,10429.0 -2014-12-11 03:00:00,10125.0 -2014-12-11 04:00:00,9967.0 -2014-12-11 05:00:00,9939.0 -2014-12-11 06:00:00,10225.0 -2014-12-11 07:00:00,11011.0 -2014-12-11 08:00:00,12198.0 -2014-12-11 09:00:00,12762.0 -2014-12-11 10:00:00,12853.0 -2014-12-11 11:00:00,12845.0 -2014-12-11 12:00:00,12774.0 -2014-12-11 13:00:00,12665.0 -2014-12-11 14:00:00,12487.0 -2014-12-11 15:00:00,12421.0 -2014-12-11 16:00:00,12307.0 -2014-12-11 17:00:00,12373.0 -2014-12-11 18:00:00,13257.0 -2014-12-11 19:00:00,13900.0 -2014-12-11 20:00:00,13794.0 -2014-12-11 21:00:00,13600.0 -2014-12-11 22:00:00,13344.0 -2014-12-11 23:00:00,12872.0 -2014-12-12 00:00:00,12006.0 -2014-12-10 01:00:00,10838.0 -2014-12-10 02:00:00,10289.0 -2014-12-10 03:00:00,9969.0 -2014-12-10 04:00:00,9789.0 -2014-12-10 05:00:00,9810.0 -2014-12-10 06:00:00,10044.0 -2014-12-10 07:00:00,10811.0 -2014-12-10 08:00:00,11943.0 -2014-12-10 09:00:00,12548.0 -2014-12-10 10:00:00,12645.0 -2014-12-10 11:00:00,12740.0 -2014-12-10 12:00:00,12800.0 -2014-12-10 13:00:00,12747.0 -2014-12-10 14:00:00,12687.0 -2014-12-10 15:00:00,12695.0 -2014-12-10 16:00:00,12691.0 -2014-12-10 17:00:00,12842.0 -2014-12-10 18:00:00,13593.0 -2014-12-10 19:00:00,13963.0 -2014-12-10 20:00:00,13751.0 -2014-12-10 21:00:00,13530.0 -2014-12-10 22:00:00,13225.0 -2014-12-10 23:00:00,12705.0 -2014-12-11 00:00:00,11819.0 -2014-12-09 01:00:00,10740.0 -2014-12-09 02:00:00,10158.0 -2014-12-09 03:00:00,9819.0 -2014-12-09 04:00:00,9683.0 -2014-12-09 05:00:00,9675.0 -2014-12-09 06:00:00,9926.0 -2014-12-09 07:00:00,10651.0 -2014-12-09 08:00:00,11792.0 -2014-12-09 09:00:00,12465.0 -2014-12-09 10:00:00,12576.0 -2014-12-09 11:00:00,12591.0 -2014-12-09 12:00:00,12618.0 -2014-12-09 13:00:00,12567.0 -2014-12-09 14:00:00,12465.0 -2014-12-09 15:00:00,12505.0 -2014-12-09 16:00:00,12503.0 -2014-12-09 17:00:00,12637.0 -2014-12-09 18:00:00,13383.0 -2014-12-09 19:00:00,13747.0 -2014-12-09 20:00:00,13623.0 -2014-12-09 21:00:00,13441.0 -2014-12-09 22:00:00,13140.0 -2014-12-09 23:00:00,12573.0 -2014-12-10 00:00:00,11678.0 -2014-12-08 01:00:00,10345.0 -2014-12-08 02:00:00,9863.0 -2014-12-08 03:00:00,9641.0 -2014-12-08 04:00:00,9557.0 -2014-12-08 05:00:00,9579.0 -2014-12-08 06:00:00,9899.0 -2014-12-08 07:00:00,10734.0 -2014-12-08 08:00:00,11889.0 -2014-12-08 09:00:00,12623.0 -2014-12-08 10:00:00,12742.0 -2014-12-08 11:00:00,12859.0 -2014-12-08 12:00:00,12926.0 -2014-12-08 13:00:00,12891.0 -2014-12-08 14:00:00,12843.0 -2014-12-08 15:00:00,12809.0 -2014-12-08 16:00:00,12793.0 -2014-12-08 17:00:00,12963.0 -2014-12-08 18:00:00,13562.0 -2014-12-08 19:00:00,13812.0 -2014-12-08 20:00:00,13603.0 -2014-12-08 21:00:00,13360.0 -2014-12-08 22:00:00,13087.0 -2014-12-08 23:00:00,12522.0 -2014-12-09 00:00:00,11607.0 -2014-12-07 01:00:00,10463.0 -2014-12-07 02:00:00,9963.0 -2014-12-07 03:00:00,9581.0 -2014-12-07 04:00:00,9453.0 -2014-12-07 05:00:00,9372.0 -2014-12-07 06:00:00,9396.0 -2014-12-07 07:00:00,9604.0 -2014-12-07 08:00:00,9862.0 -2014-12-07 09:00:00,9976.0 -2014-12-07 10:00:00,10181.0 -2014-12-07 11:00:00,10432.0 -2014-12-07 12:00:00,10487.0 -2014-12-07 13:00:00,10473.0 -2014-12-07 14:00:00,10491.0 -2014-12-07 15:00:00,10507.0 -2014-12-07 16:00:00,10607.0 -2014-12-07 17:00:00,10861.0 -2014-12-07 18:00:00,11783.0 -2014-12-07 19:00:00,12348.0 -2014-12-07 20:00:00,12379.0 -2014-12-07 21:00:00,12351.0 -2014-12-07 22:00:00,12144.0 -2014-12-07 23:00:00,11733.0 -2014-12-08 00:00:00,11013.0 -2014-12-06 01:00:00,10541.0 -2014-12-06 02:00:00,9952.0 -2014-12-06 03:00:00,9587.0 -2014-12-06 04:00:00,9438.0 -2014-12-06 05:00:00,9383.0 -2014-12-06 06:00:00,9472.0 -2014-12-06 07:00:00,9783.0 -2014-12-06 08:00:00,10314.0 -2014-12-06 09:00:00,10756.0 -2014-12-06 10:00:00,11031.0 -2014-12-06 11:00:00,11251.0 -2014-12-06 12:00:00,11281.0 -2014-12-06 13:00:00,11125.0 -2014-12-06 14:00:00,10930.0 -2014-12-06 15:00:00,10719.0 -2014-12-06 16:00:00,10697.0 -2014-12-06 17:00:00,10752.0 -2014-12-06 18:00:00,11613.0 -2014-12-06 19:00:00,12233.0 -2014-12-06 20:00:00,12192.0 -2014-12-06 21:00:00,12063.0 -2014-12-06 22:00:00,11913.0 -2014-12-06 23:00:00,11619.0 -2014-12-07 00:00:00,11101.0 -2014-12-05 01:00:00,10859.0 -2014-12-05 02:00:00,10271.0 -2014-12-05 03:00:00,9934.0 -2014-12-05 04:00:00,9711.0 -2014-12-05 05:00:00,9676.0 -2014-12-05 06:00:00,9926.0 -2014-12-05 07:00:00,10619.0 -2014-12-05 08:00:00,11768.0 -2014-12-05 09:00:00,12346.0 -2014-12-05 10:00:00,12445.0 -2014-12-05 11:00:00,12579.0 -2014-12-05 12:00:00,12597.0 -2014-12-05 13:00:00,12525.0 -2014-12-05 14:00:00,12375.0 -2014-12-05 15:00:00,12218.0 -2014-12-05 16:00:00,12178.0 -2014-12-05 17:00:00,12149.0 -2014-12-05 18:00:00,12867.0 -2014-12-05 19:00:00,13207.0 -2014-12-05 20:00:00,13024.0 -2014-12-05 21:00:00,12801.0 -2014-12-05 22:00:00,12444.0 -2014-12-05 23:00:00,12077.0 -2014-12-06 00:00:00,11363.0 -2014-12-04 01:00:00,11043.0 -2014-12-04 02:00:00,10522.0 -2014-12-04 03:00:00,10226.0 -2014-12-04 04:00:00,10111.0 -2014-12-04 05:00:00,10118.0 -2014-12-04 06:00:00,10382.0 -2014-12-04 07:00:00,11142.0 -2014-12-04 08:00:00,12301.0 -2014-12-04 09:00:00,12756.0 -2014-12-04 10:00:00,12747.0 -2014-12-04 11:00:00,12617.0 -2014-12-04 12:00:00,12606.0 -2014-12-04 13:00:00,12539.0 -2014-12-04 14:00:00,12531.0 -2014-12-04 15:00:00,12612.0 -2014-12-04 16:00:00,12545.0 -2014-12-04 17:00:00,12661.0 -2014-12-04 18:00:00,13411.0 -2014-12-04 19:00:00,13770.0 -2014-12-04 20:00:00,13574.0 -2014-12-04 21:00:00,13367.0 -2014-12-04 22:00:00,13061.0 -2014-12-04 23:00:00,12504.0 -2014-12-05 00:00:00,11654.0 -2014-12-03 01:00:00,11000.0 -2014-12-03 02:00:00,10492.0 -2014-12-03 03:00:00,10205.0 -2014-12-03 04:00:00,10090.0 -2014-12-03 05:00:00,10106.0 -2014-12-03 06:00:00,10407.0 -2014-12-03 07:00:00,11168.0 -2014-12-03 08:00:00,12343.0 -2014-12-03 09:00:00,12778.0 -2014-12-03 10:00:00,12716.0 -2014-12-03 11:00:00,12670.0 -2014-12-03 12:00:00,12646.0 -2014-12-03 13:00:00,12504.0 -2014-12-03 14:00:00,12370.0 -2014-12-03 15:00:00,12323.0 -2014-12-03 16:00:00,12192.0 -2014-12-03 17:00:00,12242.0 -2014-12-03 18:00:00,13129.0 -2014-12-03 19:00:00,13763.0 -2014-12-03 20:00:00,13631.0 -2014-12-03 21:00:00,13469.0 -2014-12-03 22:00:00,13227.0 -2014-12-03 23:00:00,12657.0 -2014-12-04 00:00:00,11809.0 -2014-12-02 01:00:00,11384.0 -2014-12-02 02:00:00,10872.0 -2014-12-02 03:00:00,10546.0 -2014-12-02 04:00:00,10433.0 -2014-12-02 05:00:00,10418.0 -2014-12-02 06:00:00,10679.0 -2014-12-02 07:00:00,11449.0 -2014-12-02 08:00:00,12563.0 -2014-12-02 09:00:00,12991.0 -2014-12-02 10:00:00,12958.0 -2014-12-02 11:00:00,12836.0 -2014-12-02 12:00:00,12839.0 -2014-12-02 13:00:00,12730.0 -2014-12-02 14:00:00,12629.0 -2014-12-02 15:00:00,12660.0 -2014-12-02 16:00:00,12607.0 -2014-12-02 17:00:00,12770.0 -2014-12-02 18:00:00,13545.0 -2014-12-02 19:00:00,13958.0 -2014-12-02 20:00:00,13781.0 -2014-12-02 21:00:00,13560.0 -2014-12-02 22:00:00,13226.0 -2014-12-02 23:00:00,12704.0 -2014-12-03 00:00:00,11823.0 -2014-12-01 01:00:00,10074.0 -2014-12-01 02:00:00,9764.0 -2014-12-01 03:00:00,9646.0 -2014-12-01 04:00:00,9593.0 -2014-12-01 05:00:00,9689.0 -2014-12-01 06:00:00,10089.0 -2014-12-01 07:00:00,10905.0 -2014-12-01 08:00:00,12188.0 -2014-12-01 09:00:00,12824.0 -2014-12-01 10:00:00,12999.0 -2014-12-01 11:00:00,13032.0 -2014-12-01 12:00:00,13082.0 -2014-12-01 13:00:00,13097.0 -2014-12-01 14:00:00,13055.0 -2014-12-01 15:00:00,13168.0 -2014-12-01 16:00:00,13180.0 -2014-12-01 17:00:00,13289.0 -2014-12-01 18:00:00,14029.0 -2014-12-01 19:00:00,14494.0 -2014-12-01 20:00:00,14378.0 -2014-12-01 21:00:00,14155.0 -2014-12-01 22:00:00,13828.0 -2014-12-01 23:00:00,13199.0 -2014-12-02 00:00:00,12238.0 -2014-11-30 01:00:00,9460.0 -2014-11-30 02:00:00,8953.0 -2014-11-30 03:00:00,8641.0 -2014-11-30 04:00:00,8407.0 -2014-11-30 05:00:00,8310.0 -2014-11-30 06:00:00,8303.0 -2014-11-30 07:00:00,8508.0 -2014-11-30 08:00:00,8725.0 -2014-11-30 09:00:00,8725.0 -2014-11-30 10:00:00,8943.0 -2014-11-30 11:00:00,9188.0 -2014-11-30 12:00:00,9383.0 -2014-11-30 13:00:00,9544.0 -2014-11-30 14:00:00,9793.0 -2014-11-30 15:00:00,10044.0 -2014-11-30 16:00:00,10280.0 -2014-11-30 17:00:00,10570.0 -2014-11-30 18:00:00,11324.0 -2014-11-30 19:00:00,11788.0 -2014-11-30 20:00:00,11835.0 -2014-11-30 21:00:00,11737.0 -2014-11-30 22:00:00,11523.0 -2014-11-30 23:00:00,11176.0 -2014-12-01 00:00:00,10582.0 -2014-11-29 01:00:00,10104.0 -2014-11-29 02:00:00,9661.0 -2014-11-29 03:00:00,9382.0 -2014-11-29 04:00:00,9205.0 -2014-11-29 05:00:00,9123.0 -2014-11-29 06:00:00,9180.0 -2014-11-29 07:00:00,9425.0 -2014-11-29 08:00:00,9819.0 -2014-11-29 09:00:00,9964.0 -2014-11-29 10:00:00,10137.0 -2014-11-29 11:00:00,10259.0 -2014-11-29 12:00:00,10258.0 -2014-11-29 13:00:00,10210.0 -2014-11-29 14:00:00,10083.0 -2014-11-29 15:00:00,9868.0 -2014-11-29 16:00:00,9781.0 -2014-11-29 17:00:00,9896.0 -2014-11-29 18:00:00,10664.0 -2014-11-29 19:00:00,11211.0 -2014-11-29 20:00:00,11117.0 -2014-11-29 21:00:00,10965.0 -2014-11-29 22:00:00,10768.0 -2014-11-29 23:00:00,10486.0 -2014-11-30 00:00:00,10018.0 -2014-11-28 01:00:00,10309.0 -2014-11-28 02:00:00,10002.0 -2014-11-28 03:00:00,9778.0 -2014-11-28 04:00:00,9653.0 -2014-11-28 05:00:00,9653.0 -2014-11-28 06:00:00,9804.0 -2014-11-28 07:00:00,10215.0 -2014-11-28 08:00:00,10757.0 -2014-11-28 09:00:00,10984.0 -2014-11-28 10:00:00,11167.0 -2014-11-28 11:00:00,11373.0 -2014-11-28 12:00:00,11390.0 -2014-11-28 13:00:00,11290.0 -2014-11-28 14:00:00,11323.0 -2014-11-28 15:00:00,11240.0 -2014-11-28 16:00:00,11199.0 -2014-11-28 17:00:00,11347.0 -2014-11-28 18:00:00,12007.0 -2014-11-28 19:00:00,12366.0 -2014-11-28 20:00:00,12111.0 -2014-11-28 21:00:00,11884.0 -2014-11-28 22:00:00,11595.0 -2014-11-28 23:00:00,11266.0 -2014-11-29 00:00:00,10710.0 -2014-11-27 01:00:00,10691.0 -2014-11-27 02:00:00,10138.0 -2014-11-27 03:00:00,9845.0 -2014-11-27 04:00:00,9680.0 -2014-11-27 05:00:00,9579.0 -2014-11-27 06:00:00,9615.0 -2014-11-27 07:00:00,9789.0 -2014-11-27 08:00:00,10029.0 -2014-11-27 09:00:00,10059.0 -2014-11-27 10:00:00,10271.0 -2014-11-27 11:00:00,10435.0 -2014-11-27 12:00:00,10640.0 -2014-11-27 13:00:00,10747.0 -2014-11-27 14:00:00,10696.0 -2014-11-27 15:00:00,10623.0 -2014-11-27 16:00:00,10510.0 -2014-11-27 17:00:00,10476.0 -2014-11-27 18:00:00,10913.0 -2014-11-27 19:00:00,11178.0 -2014-11-27 20:00:00,11120.0 -2014-11-27 21:00:00,11078.0 -2014-11-27 22:00:00,11057.0 -2014-11-27 23:00:00,10972.0 -2014-11-28 00:00:00,10681.0 -2014-11-26 01:00:00,11240.0 -2014-11-26 02:00:00,10720.0 -2014-11-26 03:00:00,10449.0 -2014-11-26 04:00:00,10285.0 -2014-11-26 05:00:00,10285.0 -2014-11-26 06:00:00,10523.0 -2014-11-26 07:00:00,11164.0 -2014-11-26 08:00:00,12046.0 -2014-11-26 09:00:00,12480.0 -2014-11-26 10:00:00,12782.0 -2014-11-26 11:00:00,12910.0 -2014-11-26 12:00:00,13009.0 -2014-11-26 13:00:00,12937.0 -2014-11-26 14:00:00,12857.0 -2014-11-26 15:00:00,12831.0 -2014-11-26 16:00:00,12749.0 -2014-11-26 17:00:00,12680.0 -2014-11-26 18:00:00,13191.0 -2014-11-26 19:00:00,13455.0 -2014-11-26 20:00:00,13179.0 -2014-11-26 21:00:00,12904.0 -2014-11-26 22:00:00,12567.0 -2014-11-26 23:00:00,12120.0 -2014-11-27 00:00:00,11416.0 -2014-11-25 01:00:00,10967.0 -2014-11-25 02:00:00,10474.0 -2014-11-25 03:00:00,10204.0 -2014-11-25 04:00:00,10071.0 -2014-11-25 05:00:00,10093.0 -2014-11-25 06:00:00,10380.0 -2014-11-25 07:00:00,11092.0 -2014-11-25 08:00:00,12174.0 -2014-11-25 09:00:00,12641.0 -2014-11-25 10:00:00,12841.0 -2014-11-25 11:00:00,12807.0 -2014-11-25 12:00:00,12701.0 -2014-11-25 13:00:00,12590.0 -2014-11-25 14:00:00,12486.0 -2014-11-25 15:00:00,12487.0 -2014-11-25 16:00:00,12467.0 -2014-11-25 17:00:00,12569.0 -2014-11-25 18:00:00,13241.0 -2014-11-25 19:00:00,13790.0 -2014-11-25 20:00:00,13655.0 -2014-11-25 21:00:00,13449.0 -2014-11-25 22:00:00,13179.0 -2014-11-25 23:00:00,12683.0 -2014-11-26 00:00:00,11947.0 -2014-11-24 01:00:00,9230.0 -2014-11-24 02:00:00,8822.0 -2014-11-24 03:00:00,8645.0 -2014-11-24 04:00:00,8512.0 -2014-11-24 05:00:00,8581.0 -2014-11-24 06:00:00,8838.0 -2014-11-24 07:00:00,9618.0 -2014-11-24 08:00:00,10800.0 -2014-11-24 09:00:00,11578.0 -2014-11-24 10:00:00,11837.0 -2014-11-24 11:00:00,12095.0 -2014-11-24 12:00:00,12435.0 -2014-11-24 13:00:00,12633.0 -2014-11-24 14:00:00,12633.0 -2014-11-24 15:00:00,12685.0 -2014-11-24 16:00:00,12767.0 -2014-11-24 17:00:00,12856.0 -2014-11-24 18:00:00,13485.0 -2014-11-24 19:00:00,13818.0 -2014-11-24 20:00:00,13650.0 -2014-11-24 21:00:00,13446.0 -2014-11-24 22:00:00,13124.0 -2014-11-24 23:00:00,12597.0 -2014-11-25 00:00:00,11773.0 -2014-11-23 01:00:00,9513.0 -2014-11-23 02:00:00,9078.0 -2014-11-23 03:00:00,8748.0 -2014-11-23 04:00:00,8560.0 -2014-11-23 05:00:00,8442.0 -2014-11-23 06:00:00,8461.0 -2014-11-23 07:00:00,8591.0 -2014-11-23 08:00:00,8808.0 -2014-11-23 09:00:00,8961.0 -2014-11-23 10:00:00,9306.0 -2014-11-23 11:00:00,9558.0 -2014-11-23 12:00:00,9692.0 -2014-11-23 13:00:00,9753.0 -2014-11-23 14:00:00,9846.0 -2014-11-23 15:00:00,9899.0 -2014-11-23 16:00:00,10011.0 -2014-11-23 17:00:00,10231.0 -2014-11-23 18:00:00,10888.0 -2014-11-23 19:00:00,11174.0 -2014-11-23 20:00:00,11068.0 -2014-11-23 21:00:00,10961.0 -2014-11-23 22:00:00,10690.0 -2014-11-23 23:00:00,10366.0 -2014-11-24 00:00:00,9756.0 -2014-11-22 01:00:00,10973.0 -2014-11-22 02:00:00,10412.0 -2014-11-22 03:00:00,10131.0 -2014-11-22 04:00:00,9911.0 -2014-11-22 05:00:00,9845.0 -2014-11-22 06:00:00,9910.0 -2014-11-22 07:00:00,10161.0 -2014-11-22 08:00:00,10566.0 -2014-11-22 09:00:00,10851.0 -2014-11-22 10:00:00,11163.0 -2014-11-22 11:00:00,11389.0 -2014-11-22 12:00:00,11470.0 -2014-11-22 13:00:00,11408.0 -2014-11-22 14:00:00,11246.0 -2014-11-22 15:00:00,10954.0 -2014-11-22 16:00:00,10831.0 -2014-11-22 17:00:00,10863.0 -2014-11-22 18:00:00,11259.0 -2014-11-22 19:00:00,11554.0 -2014-11-22 20:00:00,11424.0 -2014-11-22 21:00:00,11202.0 -2014-11-22 22:00:00,10967.0 -2014-11-22 23:00:00,10607.0 -2014-11-23 00:00:00,10069.0 -2014-11-21 01:00:00,11469.0 -2014-11-21 02:00:00,11073.0 -2014-11-21 03:00:00,10819.0 -2014-11-21 04:00:00,10711.0 -2014-11-21 05:00:00,10755.0 -2014-11-21 06:00:00,11055.0 -2014-11-21 07:00:00,11818.0 -2014-11-21 08:00:00,12856.0 -2014-11-21 09:00:00,13116.0 -2014-11-21 10:00:00,13135.0 -2014-11-21 11:00:00,13009.0 -2014-11-21 12:00:00,12947.0 -2014-11-21 13:00:00,12790.0 -2014-11-21 14:00:00,12670.0 -2014-11-21 15:00:00,12518.0 -2014-11-21 16:00:00,12359.0 -2014-11-21 17:00:00,12335.0 -2014-11-21 18:00:00,13006.0 -2014-11-21 19:00:00,13525.0 -2014-11-21 20:00:00,13402.0 -2014-11-21 21:00:00,13149.0 -2014-11-21 22:00:00,12770.0 -2014-11-21 23:00:00,12378.0 -2014-11-22 00:00:00,11655.0 -2014-11-20 01:00:00,11345.0 -2014-11-20 02:00:00,10942.0 -2014-11-20 03:00:00,10700.0 -2014-11-20 04:00:00,10614.0 -2014-11-20 05:00:00,10628.0 -2014-11-20 06:00:00,10941.0 -2014-11-20 07:00:00,11664.0 -2014-11-20 08:00:00,12713.0 -2014-11-20 09:00:00,13019.0 -2014-11-20 10:00:00,13025.0 -2014-11-20 11:00:00,12999.0 -2014-11-20 12:00:00,13001.0 -2014-11-20 13:00:00,12933.0 -2014-11-20 14:00:00,12826.0 -2014-11-20 15:00:00,12793.0 -2014-11-20 16:00:00,12698.0 -2014-11-20 17:00:00,12739.0 -2014-11-20 18:00:00,13383.0 -2014-11-20 19:00:00,13997.0 -2014-11-20 20:00:00,13876.0 -2014-11-20 21:00:00,13695.0 -2014-11-20 22:00:00,13425.0 -2014-11-20 23:00:00,12946.0 -2014-11-21 00:00:00,12168.0 -2014-11-19 01:00:00,11668.0 -2014-11-19 02:00:00,11225.0 -2014-11-19 03:00:00,10974.0 -2014-11-19 04:00:00,10818.0 -2014-11-19 05:00:00,10808.0 -2014-11-19 06:00:00,11025.0 -2014-11-19 07:00:00,11706.0 -2014-11-19 08:00:00,12744.0 -2014-11-19 09:00:00,13146.0 -2014-11-19 10:00:00,13196.0 -2014-11-19 11:00:00,13080.0 -2014-11-19 12:00:00,13068.0 -2014-11-19 13:00:00,13111.0 -2014-11-19 14:00:00,13155.0 -2014-11-19 15:00:00,13314.0 -2014-11-19 16:00:00,13346.0 -2014-11-19 17:00:00,13384.0 -2014-11-19 18:00:00,13969.0 -2014-11-19 19:00:00,14310.0 -2014-11-19 20:00:00,14087.0 -2014-11-19 21:00:00,13855.0 -2014-11-19 22:00:00,13503.0 -2014-11-19 23:00:00,12856.0 -2014-11-20 00:00:00,12089.0 -2014-11-18 01:00:00,11859.0 -2014-11-18 02:00:00,11458.0 -2014-11-18 03:00:00,11205.0 -2014-11-18 04:00:00,11102.0 -2014-11-18 05:00:00,11115.0 -2014-11-18 06:00:00,11363.0 -2014-11-18 07:00:00,12098.0 -2014-11-18 08:00:00,13146.0 -2014-11-18 09:00:00,13527.0 -2014-11-18 10:00:00,13590.0 -2014-11-18 11:00:00,13621.0 -2014-11-18 12:00:00,13687.0 -2014-11-18 13:00:00,13654.0 -2014-11-18 14:00:00,13615.0 -2014-11-18 15:00:00,13633.0 -2014-11-18 16:00:00,13537.0 -2014-11-18 17:00:00,13699.0 -2014-11-18 18:00:00,14299.0 -2014-11-18 19:00:00,14653.0 -2014-11-18 20:00:00,14441.0 -2014-11-18 21:00:00,14204.0 -2014-11-18 22:00:00,13848.0 -2014-11-18 23:00:00,13246.0 -2014-11-19 00:00:00,12418.0 -2014-11-17 01:00:00,10263.0 -2014-11-17 02:00:00,10002.0 -2014-11-17 03:00:00,9922.0 -2014-11-17 04:00:00,9880.0 -2014-11-17 05:00:00,10080.0 -2014-11-17 06:00:00,10431.0 -2014-11-17 07:00:00,11302.0 -2014-11-17 08:00:00,12420.0 -2014-11-17 09:00:00,12958.0 -2014-11-17 10:00:00,13041.0 -2014-11-17 11:00:00,13071.0 -2014-11-17 12:00:00,13064.0 -2014-11-17 13:00:00,13125.0 -2014-11-17 14:00:00,13235.0 -2014-11-17 15:00:00,13399.0 -2014-11-17 16:00:00,13419.0 -2014-11-17 17:00:00,13548.0 -2014-11-17 18:00:00,14166.0 -2014-11-17 19:00:00,14623.0 -2014-11-17 20:00:00,14507.0 -2014-11-17 21:00:00,14339.0 -2014-11-17 22:00:00,14006.0 -2014-11-17 23:00:00,13429.0 -2014-11-18 00:00:00,12606.0 -2014-11-16 01:00:00,10153.0 -2014-11-16 02:00:00,9749.0 -2014-11-16 03:00:00,9497.0 -2014-11-16 04:00:00,9322.0 -2014-11-16 05:00:00,9275.0 -2014-11-16 06:00:00,9320.0 -2014-11-16 07:00:00,9467.0 -2014-11-16 08:00:00,9709.0 -2014-11-16 09:00:00,9767.0 -2014-11-16 10:00:00,10139.0 -2014-11-16 11:00:00,10408.0 -2014-11-16 12:00:00,10563.0 -2014-11-16 13:00:00,10645.0 -2014-11-16 14:00:00,10673.0 -2014-11-16 15:00:00,10714.0 -2014-11-16 16:00:00,10801.0 -2014-11-16 17:00:00,10985.0 -2014-11-16 18:00:00,11523.0 -2014-11-16 19:00:00,11938.0 -2014-11-16 20:00:00,11866.0 -2014-11-16 21:00:00,11782.0 -2014-11-16 22:00:00,11539.0 -2014-11-16 23:00:00,11223.0 -2014-11-17 00:00:00,10669.0 -2014-11-15 01:00:00,10650.0 -2014-11-15 02:00:00,10243.0 -2014-11-15 03:00:00,10036.0 -2014-11-15 04:00:00,9834.0 -2014-11-15 05:00:00,9794.0 -2014-11-15 06:00:00,9914.0 -2014-11-15 07:00:00,10255.0 -2014-11-15 08:00:00,10662.0 -2014-11-15 09:00:00,10860.0 -2014-11-15 10:00:00,11029.0 -2014-11-15 11:00:00,11125.0 -2014-11-15 12:00:00,11148.0 -2014-11-15 13:00:00,11128.0 -2014-11-15 14:00:00,10951.0 -2014-11-15 15:00:00,10847.0 -2014-11-15 16:00:00,10792.0 -2014-11-15 17:00:00,10981.0 -2014-11-15 18:00:00,11581.0 -2014-11-15 19:00:00,11975.0 -2014-11-15 20:00:00,11857.0 -2014-11-15 21:00:00,11779.0 -2014-11-15 22:00:00,11541.0 -2014-11-15 23:00:00,11228.0 -2014-11-16 00:00:00,10689.0 -2014-11-14 01:00:00,10712.0 -2014-11-14 02:00:00,10238.0 -2014-11-14 03:00:00,9953.0 -2014-11-14 04:00:00,9845.0 -2014-11-14 05:00:00,9814.0 -2014-11-14 06:00:00,10094.0 -2014-11-14 07:00:00,10838.0 -2014-11-14 08:00:00,11843.0 -2014-11-14 09:00:00,12284.0 -2014-11-14 10:00:00,12457.0 -2014-11-14 11:00:00,12449.0 -2014-11-14 12:00:00,12490.0 -2014-11-14 13:00:00,12551.0 -2014-11-14 14:00:00,12510.0 -2014-11-14 15:00:00,12519.0 -2014-11-14 16:00:00,12454.0 -2014-11-14 17:00:00,12440.0 -2014-11-14 18:00:00,12929.0 -2014-11-14 19:00:00,13257.0 -2014-11-14 20:00:00,13053.0 -2014-11-14 21:00:00,12780.0 -2014-11-14 22:00:00,12479.0 -2014-11-14 23:00:00,12050.0 -2014-11-15 00:00:00,11298.0 -2014-11-13 01:00:00,10551.0 -2014-11-13 02:00:00,10117.0 -2014-11-13 03:00:00,9838.0 -2014-11-13 04:00:00,9706.0 -2014-11-13 05:00:00,9696.0 -2014-11-13 06:00:00,9987.0 -2014-11-13 07:00:00,10775.0 -2014-11-13 08:00:00,11881.0 -2014-11-13 09:00:00,12287.0 -2014-11-13 10:00:00,12423.0 -2014-11-13 11:00:00,12498.0 -2014-11-13 12:00:00,12630.0 -2014-11-13 13:00:00,12694.0 -2014-11-13 14:00:00,12655.0 -2014-11-13 15:00:00,12664.0 -2014-11-13 16:00:00,12656.0 -2014-11-13 17:00:00,12602.0 -2014-11-13 18:00:00,13062.0 -2014-11-13 19:00:00,13464.0 -2014-11-13 20:00:00,13317.0 -2014-11-13 21:00:00,13087.0 -2014-11-13 22:00:00,12736.0 -2014-11-13 23:00:00,12184.0 -2014-11-14 00:00:00,11408.0 -2014-11-12 01:00:00,10207.0 -2014-11-12 02:00:00,9685.0 -2014-11-12 03:00:00,9439.0 -2014-11-12 04:00:00,9309.0 -2014-11-12 05:00:00,9335.0 -2014-11-12 06:00:00,9643.0 -2014-11-12 07:00:00,10396.0 -2014-11-12 08:00:00,11476.0 -2014-11-12 09:00:00,11860.0 -2014-11-12 10:00:00,12006.0 -2014-11-12 11:00:00,12216.0 -2014-11-12 12:00:00,12387.0 -2014-11-12 13:00:00,12316.0 -2014-11-12 14:00:00,12126.0 -2014-11-12 15:00:00,12188.0 -2014-11-12 16:00:00,12190.0 -2014-11-12 17:00:00,12335.0 -2014-11-12 18:00:00,12860.0 -2014-11-12 19:00:00,13380.0 -2014-11-12 20:00:00,13181.0 -2014-11-12 21:00:00,12936.0 -2014-11-12 22:00:00,12626.0 -2014-11-12 23:00:00,12014.0 -2014-11-13 00:00:00,11292.0 -2014-11-11 01:00:00,9376.0 -2014-11-11 02:00:00,8891.0 -2014-11-11 03:00:00,8578.0 -2014-11-11 04:00:00,8353.0 -2014-11-11 05:00:00,8342.0 -2014-11-11 06:00:00,8558.0 -2014-11-11 07:00:00,9213.0 -2014-11-11 08:00:00,10235.0 -2014-11-11 09:00:00,10854.0 -2014-11-11 10:00:00,11327.0 -2014-11-11 11:00:00,11709.0 -2014-11-11 12:00:00,11807.0 -2014-11-11 13:00:00,11808.0 -2014-11-11 14:00:00,11790.0 -2014-11-11 15:00:00,11847.0 -2014-11-11 16:00:00,11831.0 -2014-11-11 17:00:00,11936.0 -2014-11-11 18:00:00,12417.0 -2014-11-11 19:00:00,12810.0 -2014-11-11 20:00:00,12633.0 -2014-11-11 21:00:00,12428.0 -2014-11-11 22:00:00,12109.0 -2014-11-11 23:00:00,11563.0 -2014-11-12 00:00:00,10859.0 -2014-11-10 01:00:00,8898.0 -2014-11-10 02:00:00,8568.0 -2014-11-10 03:00:00,8359.0 -2014-11-10 04:00:00,8297.0 -2014-11-10 05:00:00,8303.0 -2014-11-10 06:00:00,8676.0 -2014-11-10 07:00:00,9482.0 -2014-11-10 08:00:00,10611.0 -2014-11-10 09:00:00,11098.0 -2014-11-10 10:00:00,11366.0 -2014-11-10 11:00:00,11441.0 -2014-11-10 12:00:00,11425.0 -2014-11-10 13:00:00,11358.0 -2014-11-10 14:00:00,11324.0 -2014-11-10 15:00:00,11305.0 -2014-11-10 16:00:00,11208.0 -2014-11-10 17:00:00,11121.0 -2014-11-10 18:00:00,11482.0 -2014-11-10 19:00:00,12159.0 -2014-11-10 20:00:00,11991.0 -2014-11-10 21:00:00,11728.0 -2014-11-10 22:00:00,11392.0 -2014-11-10 23:00:00,10814.0 -2014-11-11 00:00:00,10095.0 -2014-11-09 01:00:00,9279.0 -2014-11-09 02:00:00,8886.0 -2014-11-09 03:00:00,8627.0 -2014-11-09 04:00:00,8478.0 -2014-11-09 05:00:00,8465.0 -2014-11-09 06:00:00,8461.0 -2014-11-09 07:00:00,8662.0 -2014-11-09 08:00:00,8832.0 -2014-11-09 09:00:00,8943.0 -2014-11-09 10:00:00,9248.0 -2014-11-09 11:00:00,9504.0 -2014-11-09 12:00:00,9507.0 -2014-11-09 13:00:00,9538.0 -2014-11-09 14:00:00,9452.0 -2014-11-09 15:00:00,9414.0 -2014-11-09 16:00:00,9347.0 -2014-11-09 17:00:00,9349.0 -2014-11-09 18:00:00,10018.0 -2014-11-09 19:00:00,10717.0 -2014-11-09 20:00:00,10698.0 -2014-11-09 21:00:00,10539.0 -2014-11-09 22:00:00,10310.0 -2014-11-09 23:00:00,9914.0 -2014-11-10 00:00:00,9410.0 -2014-11-08 01:00:00,9790.0 -2014-11-08 02:00:00,9296.0 -2014-11-08 03:00:00,8978.0 -2014-11-08 04:00:00,8764.0 -2014-11-08 05:00:00,8679.0 -2014-11-08 06:00:00,8741.0 -2014-11-08 07:00:00,9044.0 -2014-11-08 08:00:00,9521.0 -2014-11-08 09:00:00,9873.0 -2014-11-08 10:00:00,10270.0 -2014-11-08 11:00:00,10455.0 -2014-11-08 12:00:00,10507.0 -2014-11-08 13:00:00,10462.0 -2014-11-08 14:00:00,10340.0 -2014-11-08 15:00:00,10122.0 -2014-11-08 16:00:00,9971.0 -2014-11-08 17:00:00,9875.0 -2014-11-08 18:00:00,10411.0 -2014-11-08 19:00:00,10997.0 -2014-11-08 20:00:00,10942.0 -2014-11-08 21:00:00,10700.0 -2014-11-08 22:00:00,10563.0 -2014-11-08 23:00:00,10252.0 -2014-11-09 00:00:00,9780.0 -2014-11-07 01:00:00,9717.0 -2014-11-07 02:00:00,9313.0 -2014-11-07 03:00:00,9069.0 -2014-11-07 04:00:00,8919.0 -2014-11-07 05:00:00,8922.0 -2014-11-07 06:00:00,9199.0 -2014-11-07 07:00:00,9956.0 -2014-11-07 08:00:00,10942.0 -2014-11-07 09:00:00,11348.0 -2014-11-07 10:00:00,11475.0 -2014-11-07 11:00:00,11473.0 -2014-11-07 12:00:00,11510.0 -2014-11-07 13:00:00,11417.0 -2014-11-07 14:00:00,11320.0 -2014-11-07 15:00:00,11328.0 -2014-11-07 16:00:00,11314.0 -2014-11-07 17:00:00,11324.0 -2014-11-07 18:00:00,11754.0 -2014-11-07 19:00:00,12263.0 -2014-11-07 20:00:00,12080.0 -2014-11-07 21:00:00,11849.0 -2014-11-07 22:00:00,11513.0 -2014-11-07 23:00:00,11083.0 -2014-11-08 00:00:00,10407.0 -2014-11-06 01:00:00,9376.0 -2014-11-06 02:00:00,8910.0 -2014-11-06 03:00:00,8631.0 -2014-11-06 04:00:00,8510.0 -2014-11-06 05:00:00,8481.0 -2014-11-06 06:00:00,8706.0 -2014-11-06 07:00:00,9464.0 -2014-11-06 08:00:00,10582.0 -2014-11-06 09:00:00,11179.0 -2014-11-06 10:00:00,11513.0 -2014-11-06 11:00:00,11685.0 -2014-11-06 12:00:00,11772.0 -2014-11-06 13:00:00,11752.0 -2014-11-06 14:00:00,11742.0 -2014-11-06 15:00:00,11830.0 -2014-11-06 16:00:00,11806.0 -2014-11-06 17:00:00,11886.0 -2014-11-06 18:00:00,12280.0 -2014-11-06 19:00:00,12639.0 -2014-11-06 20:00:00,12399.0 -2014-11-06 21:00:00,12154.0 -2014-11-06 22:00:00,11776.0 -2014-11-06 23:00:00,11207.0 -2014-11-07 00:00:00,10438.0 -2014-11-05 01:00:00,9517.0 -2014-11-05 02:00:00,9075.0 -2014-11-05 03:00:00,8836.0 -2014-11-05 04:00:00,8662.0 -2014-11-05 05:00:00,8653.0 -2014-11-05 06:00:00,8918.0 -2014-11-05 07:00:00,9707.0 -2014-11-05 08:00:00,10734.0 -2014-11-05 09:00:00,11117.0 -2014-11-05 10:00:00,11217.0 -2014-11-05 11:00:00,11244.0 -2014-11-05 12:00:00,11306.0 -2014-11-05 13:00:00,11287.0 -2014-11-05 14:00:00,11248.0 -2014-11-05 15:00:00,11272.0 -2014-11-05 16:00:00,11203.0 -2014-11-05 17:00:00,11179.0 -2014-11-05 18:00:00,11684.0 -2014-11-05 19:00:00,12177.0 -2014-11-05 20:00:00,11970.0 -2014-11-05 21:00:00,11732.0 -2014-11-05 22:00:00,11359.0 -2014-11-05 23:00:00,10799.0 -2014-11-06 00:00:00,10072.0 -2014-11-04 01:00:00,9324.0 -2014-11-04 02:00:00,8867.0 -2014-11-04 03:00:00,8564.0 -2014-11-04 04:00:00,8370.0 -2014-11-04 05:00:00,8407.0 -2014-11-04 06:00:00,8647.0 -2014-11-04 07:00:00,9362.0 -2014-11-04 08:00:00,10414.0 -2014-11-04 09:00:00,10966.0 -2014-11-04 10:00:00,11295.0 -2014-11-04 11:00:00,11384.0 -2014-11-04 12:00:00,11448.0 -2014-11-04 13:00:00,11383.0 -2014-11-04 14:00:00,11362.0 -2014-11-04 15:00:00,11351.0 -2014-11-04 16:00:00,11272.0 -2014-11-04 17:00:00,11272.0 -2014-11-04 18:00:00,11628.0 -2014-11-04 19:00:00,12209.0 -2014-11-04 20:00:00,12098.0 -2014-11-04 21:00:00,11883.0 -2014-11-04 22:00:00,11520.0 -2014-11-04 23:00:00,10952.0 -2014-11-05 00:00:00,10176.0 -2014-11-03 01:00:00,9009.0 -2014-11-03 02:00:00,8707.0 -2014-11-03 03:00:00,8510.0 -2014-11-03 04:00:00,8448.0 -2014-11-03 05:00:00,8560.0 -2014-11-03 06:00:00,8865.0 -2014-11-03 07:00:00,9760.0 -2014-11-03 08:00:00,10725.0 -2014-11-03 09:00:00,11259.0 -2014-11-03 10:00:00,11415.0 -2014-11-03 11:00:00,11466.0 -2014-11-03 12:00:00,11492.0 -2014-11-03 13:00:00,11478.0 -2014-11-03 14:00:00,11377.0 -2014-11-03 15:00:00,11374.0 -2014-11-03 16:00:00,11298.0 -2014-11-03 17:00:00,11323.0 -2014-11-03 18:00:00,11616.0 -2014-11-03 19:00:00,12221.0 -2014-11-03 20:00:00,12050.0 -2014-11-03 21:00:00,11812.0 -2014-11-03 22:00:00,11443.0 -2014-11-03 23:00:00,10824.0 -2014-11-04 00:00:00,10018.0 -2014-11-02 01:00:00,9573.0 -2014-11-02 02:00:00,8869.0 -2014-11-02 02:00:00,9184.0 -2014-11-02 03:00:00,8788.0 -2014-11-02 04:00:00,8678.0 -2014-11-02 05:00:00,8693.0 -2014-11-02 06:00:00,8725.0 -2014-11-02 07:00:00,8995.0 -2014-11-02 08:00:00,9110.0 -2014-11-02 09:00:00,9237.0 -2014-11-02 10:00:00,9445.0 -2014-11-02 11:00:00,9536.0 -2014-11-02 12:00:00,9501.0 -2014-11-02 13:00:00,9465.0 -2014-11-02 14:00:00,9417.0 -2014-11-02 15:00:00,9392.0 -2014-11-02 16:00:00,9365.0 -2014-11-02 17:00:00,9496.0 -2014-11-02 18:00:00,9927.0 -2014-11-02 19:00:00,10831.0 -2014-11-02 20:00:00,10868.0 -2014-11-02 21:00:00,10725.0 -2014-11-02 22:00:00,10441.0 -2014-11-02 23:00:00,10064.0 -2014-11-03 00:00:00,9472.0 -2014-11-01 01:00:00,9970.0 -2014-11-01 02:00:00,9413.0 -2014-11-01 03:00:00,9114.0 -2014-11-01 04:00:00,8907.0 -2014-11-01 05:00:00,8821.0 -2014-11-01 06:00:00,8852.0 -2014-11-01 07:00:00,9212.0 -2014-11-01 08:00:00,9694.0 -2014-11-01 09:00:00,10105.0 -2014-11-01 10:00:00,10312.0 -2014-11-01 11:00:00,10594.0 -2014-11-01 12:00:00,10614.0 -2014-11-01 13:00:00,10536.0 -2014-11-01 14:00:00,10349.0 -2014-11-01 15:00:00,10069.0 -2014-11-01 16:00:00,9878.0 -2014-11-01 17:00:00,9736.0 -2014-11-01 18:00:00,9767.0 -2014-11-01 19:00:00,10106.0 -2014-11-01 20:00:00,10900.0 -2014-11-01 21:00:00,10900.0 -2014-11-01 22:00:00,10797.0 -2014-11-01 23:00:00,10522.0 -2014-11-02 00:00:00,10105.0 -2014-10-31 01:00:00,9627.0 -2014-10-31 02:00:00,9049.0 -2014-10-31 03:00:00,8739.0 -2014-10-31 04:00:00,8607.0 -2014-10-31 05:00:00,8610.0 -2014-10-31 06:00:00,8831.0 -2014-10-31 07:00:00,9579.0 -2014-10-31 08:00:00,10826.0 -2014-10-31 09:00:00,11629.0 -2014-10-31 10:00:00,11717.0 -2014-10-31 11:00:00,11820.0 -2014-10-31 12:00:00,11928.0 -2014-10-31 13:00:00,11964.0 -2014-10-31 14:00:00,11892.0 -2014-10-31 15:00:00,11779.0 -2014-10-31 16:00:00,11672.0 -2014-10-31 17:00:00,11598.0 -2014-10-31 18:00:00,11603.0 -2014-10-31 19:00:00,11801.0 -2014-10-31 20:00:00,12211.0 -2014-10-31 21:00:00,12055.0 -2014-10-31 22:00:00,11735.0 -2014-10-31 23:00:00,11311.0 -2014-11-01 00:00:00,10676.0 -2014-10-30 01:00:00,9566.0 -2014-10-30 02:00:00,9099.0 -2014-10-30 03:00:00,8783.0 -2014-10-30 04:00:00,8659.0 -2014-10-30 05:00:00,8644.0 -2014-10-30 06:00:00,8859.0 -2014-10-30 07:00:00,9579.0 -2014-10-30 08:00:00,10823.0 -2014-10-30 09:00:00,11430.0 -2014-10-30 10:00:00,11400.0 -2014-10-30 11:00:00,11404.0 -2014-10-30 12:00:00,11392.0 -2014-10-30 13:00:00,11310.0 -2014-10-30 14:00:00,11275.0 -2014-10-30 15:00:00,11335.0 -2014-10-30 16:00:00,11210.0 -2014-10-30 17:00:00,11149.0 -2014-10-30 18:00:00,11201.0 -2014-10-30 19:00:00,11524.0 -2014-10-30 20:00:00,11968.0 -2014-10-30 21:00:00,11857.0 -2014-10-30 22:00:00,11586.0 -2014-10-30 23:00:00,11046.0 -2014-10-31 00:00:00,10297.0 -2014-10-29 01:00:00,9213.0 -2014-10-29 02:00:00,8728.0 -2014-10-29 03:00:00,8436.0 -2014-10-29 04:00:00,8319.0 -2014-10-29 05:00:00,8256.0 -2014-10-29 06:00:00,8489.0 -2014-10-29 07:00:00,9207.0 -2014-10-29 08:00:00,10403.0 -2014-10-29 09:00:00,11172.0 -2014-10-29 10:00:00,11254.0 -2014-10-29 11:00:00,11360.0 -2014-10-29 12:00:00,11462.0 -2014-10-29 13:00:00,11422.0 -2014-10-29 14:00:00,11365.0 -2014-10-29 15:00:00,11401.0 -2014-10-29 16:00:00,11279.0 -2014-10-29 17:00:00,11137.0 -2014-10-29 18:00:00,11090.0 -2014-10-29 19:00:00,11315.0 -2014-10-29 20:00:00,11871.0 -2014-10-29 21:00:00,11815.0 -2014-10-29 22:00:00,11547.0 -2014-10-29 23:00:00,11039.0 -2014-10-30 00:00:00,10304.0 -2014-10-28 01:00:00,9439.0 -2014-10-28 02:00:00,8887.0 -2014-10-28 03:00:00,8522.0 -2014-10-28 04:00:00,8315.0 -2014-10-28 05:00:00,8231.0 -2014-10-28 06:00:00,8349.0 -2014-10-28 07:00:00,8968.0 -2014-10-28 08:00:00,10073.0 -2014-10-28 09:00:00,10858.0 -2014-10-28 10:00:00,10975.0 -2014-10-28 11:00:00,11081.0 -2014-10-28 12:00:00,11194.0 -2014-10-28 13:00:00,11245.0 -2014-10-28 14:00:00,11216.0 -2014-10-28 15:00:00,11228.0 -2014-10-28 16:00:00,11134.0 -2014-10-28 17:00:00,11025.0 -2014-10-28 18:00:00,10947.0 -2014-10-28 19:00:00,11068.0 -2014-10-28 20:00:00,11584.0 -2014-10-28 21:00:00,11483.0 -2014-10-28 22:00:00,11182.0 -2014-10-28 23:00:00,10671.0 -2014-10-29 00:00:00,9952.0 -2014-10-27 01:00:00,8492.0 -2014-10-27 02:00:00,8101.0 -2014-10-27 03:00:00,7885.0 -2014-10-27 04:00:00,7777.0 -2014-10-27 05:00:00,7753.0 -2014-10-27 06:00:00,8030.0 -2014-10-27 07:00:00,8795.0 -2014-10-27 08:00:00,10055.0 -2014-10-27 09:00:00,10816.0 -2014-10-27 10:00:00,10929.0 -2014-10-27 11:00:00,11166.0 -2014-10-27 12:00:00,11502.0 -2014-10-27 13:00:00,11631.0 -2014-10-27 14:00:00,11715.0 -2014-10-27 15:00:00,11847.0 -2014-10-27 16:00:00,11899.0 -2014-10-27 17:00:00,11774.0 -2014-10-27 18:00:00,11631.0 -2014-10-27 19:00:00,11717.0 -2014-10-27 20:00:00,12201.0 -2014-10-27 21:00:00,12105.0 -2014-10-27 22:00:00,11741.0 -2014-10-27 23:00:00,11119.0 -2014-10-28 00:00:00,10237.0 -2014-10-26 01:00:00,8504.0 -2014-10-26 02:00:00,8027.0 -2014-10-26 03:00:00,7835.0 -2014-10-26 04:00:00,7644.0 -2014-10-26 05:00:00,7579.0 -2014-10-26 06:00:00,7578.0 -2014-10-26 07:00:00,7757.0 -2014-10-26 08:00:00,8006.0 -2014-10-26 09:00:00,8171.0 -2014-10-26 10:00:00,8368.0 -2014-10-26 11:00:00,8626.0 -2014-10-26 12:00:00,8754.0 -2014-10-26 13:00:00,8883.0 -2014-10-26 14:00:00,8889.0 -2014-10-26 15:00:00,8953.0 -2014-10-26 16:00:00,8899.0 -2014-10-26 17:00:00,8893.0 -2014-10-26 18:00:00,8925.0 -2014-10-26 19:00:00,9166.0 -2014-10-26 20:00:00,9953.0 -2014-10-26 21:00:00,10144.0 -2014-10-26 22:00:00,9933.0 -2014-10-26 23:00:00,9597.0 -2014-10-27 00:00:00,9074.0 -2014-10-25 01:00:00,9098.0 -2014-10-25 02:00:00,8608.0 -2014-10-25 03:00:00,8229.0 -2014-10-25 04:00:00,8041.0 -2014-10-25 05:00:00,7916.0 -2014-10-25 06:00:00,7980.0 -2014-10-25 07:00:00,8197.0 -2014-10-25 08:00:00,8698.0 -2014-10-25 09:00:00,8987.0 -2014-10-25 10:00:00,9308.0 -2014-10-25 11:00:00,9619.0 -2014-10-25 12:00:00,9818.0 -2014-10-25 13:00:00,9849.0 -2014-10-25 14:00:00,9835.0 -2014-10-25 15:00:00,9710.0 -2014-10-25 16:00:00,9608.0 -2014-10-25 17:00:00,9531.0 -2014-10-25 18:00:00,9483.0 -2014-10-25 19:00:00,9538.0 -2014-10-25 20:00:00,10079.0 -2014-10-25 21:00:00,10112.0 -2014-10-25 22:00:00,9900.0 -2014-10-25 23:00:00,9580.0 -2014-10-26 00:00:00,9019.0 -2014-10-24 01:00:00,9159.0 -2014-10-24 02:00:00,8684.0 -2014-10-24 03:00:00,8402.0 -2014-10-24 04:00:00,8186.0 -2014-10-24 05:00:00,8142.0 -2014-10-24 06:00:00,8337.0 -2014-10-24 07:00:00,9018.0 -2014-10-24 08:00:00,10152.0 -2014-10-24 09:00:00,10818.0 -2014-10-24 10:00:00,10947.0 -2014-10-24 11:00:00,10991.0 -2014-10-24 12:00:00,11180.0 -2014-10-24 13:00:00,11264.0 -2014-10-24 14:00:00,11167.0 -2014-10-24 15:00:00,11188.0 -2014-10-24 16:00:00,11145.0 -2014-10-24 17:00:00,11018.0 -2014-10-24 18:00:00,10844.0 -2014-10-24 19:00:00,10831.0 -2014-10-24 20:00:00,11324.0 -2014-10-24 21:00:00,11203.0 -2014-10-24 22:00:00,10847.0 -2014-10-24 23:00:00,10441.0 -2014-10-25 00:00:00,9822.0 -2014-10-23 01:00:00,9367.0 -2014-10-23 02:00:00,8905.0 -2014-10-23 03:00:00,8627.0 -2014-10-23 04:00:00,8463.0 -2014-10-23 05:00:00,8475.0 -2014-10-23 06:00:00,8698.0 -2014-10-23 07:00:00,9399.0 -2014-10-23 08:00:00,10625.0 -2014-10-23 09:00:00,11092.0 -2014-10-23 10:00:00,11162.0 -2014-10-23 11:00:00,11140.0 -2014-10-23 12:00:00,11202.0 -2014-10-23 13:00:00,10889.0 -2014-10-23 14:00:00,10916.0 -2014-10-23 15:00:00,11160.0 -2014-10-23 16:00:00,11066.0 -2014-10-23 17:00:00,10947.0 -2014-10-23 18:00:00,10917.0 -2014-10-23 19:00:00,11166.0 -2014-10-23 20:00:00,11611.0 -2014-10-23 21:00:00,11549.0 -2014-10-23 22:00:00,11251.0 -2014-10-23 23:00:00,10729.0 -2014-10-24 00:00:00,9944.0 -2014-10-22 01:00:00,9342.0 -2014-10-22 02:00:00,8854.0 -2014-10-22 03:00:00,8604.0 -2014-10-22 04:00:00,8445.0 -2014-10-22 05:00:00,8413.0 -2014-10-22 06:00:00,8650.0 -2014-10-22 07:00:00,9323.0 -2014-10-22 08:00:00,10530.0 -2014-10-22 09:00:00,11163.0 -2014-10-22 10:00:00,11187.0 -2014-10-22 11:00:00,11228.0 -2014-10-22 12:00:00,11217.0 -2014-10-22 13:00:00,11222.0 -2014-10-22 14:00:00,11166.0 -2014-10-22 15:00:00,11166.0 -2014-10-22 16:00:00,11075.0 -2014-10-22 17:00:00,10945.0 -2014-10-22 18:00:00,10859.0 -2014-10-22 19:00:00,10907.0 -2014-10-22 20:00:00,11519.0 -2014-10-22 21:00:00,11590.0 -2014-10-22 22:00:00,11328.0 -2014-10-22 23:00:00,10833.0 -2014-10-23 00:00:00,10093.0 -2014-10-21 01:00:00,9110.0 -2014-10-21 02:00:00,8618.0 -2014-10-21 03:00:00,8364.0 -2014-10-21 04:00:00,8194.0 -2014-10-21 05:00:00,8159.0 -2014-10-21 06:00:00,8374.0 -2014-10-21 07:00:00,9050.0 -2014-10-21 08:00:00,10256.0 -2014-10-21 09:00:00,10858.0 -2014-10-21 10:00:00,10997.0 -2014-10-21 11:00:00,11064.0 -2014-10-21 12:00:00,11174.0 -2014-10-21 13:00:00,11211.0 -2014-10-21 14:00:00,11177.0 -2014-10-21 15:00:00,11207.0 -2014-10-21 16:00:00,11147.0 -2014-10-21 17:00:00,11123.0 -2014-10-21 18:00:00,11148.0 -2014-10-21 19:00:00,11344.0 -2014-10-21 20:00:00,11766.0 -2014-10-21 21:00:00,11682.0 -2014-10-21 22:00:00,11424.0 -2014-10-21 23:00:00,10919.0 -2014-10-22 00:00:00,10114.0 -2014-10-20 01:00:00,8638.0 -2014-10-20 02:00:00,8332.0 -2014-10-20 03:00:00,8073.0 -2014-10-20 04:00:00,7997.0 -2014-10-20 05:00:00,7991.0 -2014-10-20 06:00:00,8272.0 -2014-10-20 07:00:00,9040.0 -2014-10-20 08:00:00,10335.0 -2014-10-20 09:00:00,10910.0 -2014-10-20 10:00:00,11040.0 -2014-10-20 11:00:00,11007.0 -2014-10-20 12:00:00,11110.0 -2014-10-20 13:00:00,11163.0 -2014-10-20 14:00:00,11204.0 -2014-10-20 15:00:00,11248.0 -2014-10-20 16:00:00,11179.0 -2014-10-20 17:00:00,11036.0 -2014-10-20 18:00:00,10955.0 -2014-10-20 19:00:00,11012.0 -2014-10-20 20:00:00,11461.0 -2014-10-20 21:00:00,11468.0 -2014-10-20 22:00:00,11164.0 -2014-10-20 23:00:00,10621.0 -2014-10-21 00:00:00,9847.0 -2014-10-19 01:00:00,8944.0 -2014-10-19 02:00:00,8513.0 -2014-10-19 03:00:00,8282.0 -2014-10-19 04:00:00,8080.0 -2014-10-19 05:00:00,8023.0 -2014-10-19 06:00:00,8033.0 -2014-10-19 07:00:00,8209.0 -2014-10-19 08:00:00,8453.0 -2014-10-19 09:00:00,8567.0 -2014-10-19 10:00:00,8765.0 -2014-10-19 11:00:00,8947.0 -2014-10-19 12:00:00,9007.0 -2014-10-19 13:00:00,9060.0 -2014-10-19 14:00:00,9026.0 -2014-10-19 15:00:00,8984.0 -2014-10-19 16:00:00,8902.0 -2014-10-19 17:00:00,8917.0 -2014-10-19 18:00:00,8972.0 -2014-10-19 19:00:00,9335.0 -2014-10-19 20:00:00,10084.0 -2014-10-19 21:00:00,10241.0 -2014-10-19 22:00:00,10018.0 -2014-10-19 23:00:00,9726.0 -2014-10-20 00:00:00,9184.0 -2014-10-18 01:00:00,9142.0 -2014-10-18 02:00:00,8648.0 -2014-10-18 03:00:00,8269.0 -2014-10-18 04:00:00,8120.0 -2014-10-18 05:00:00,8003.0 -2014-10-18 06:00:00,8054.0 -2014-10-18 07:00:00,8292.0 -2014-10-18 08:00:00,8807.0 -2014-10-18 09:00:00,9142.0 -2014-10-18 10:00:00,9505.0 -2014-10-18 11:00:00,9799.0 -2014-10-18 12:00:00,9934.0 -2014-10-18 13:00:00,9906.0 -2014-10-18 14:00:00,9842.0 -2014-10-18 15:00:00,9604.0 -2014-10-18 16:00:00,9455.0 -2014-10-18 17:00:00,9397.0 -2014-10-18 18:00:00,9479.0 -2014-10-18 19:00:00,9514.0 -2014-10-18 20:00:00,10091.0 -2014-10-18 21:00:00,10241.0 -2014-10-18 22:00:00,10095.0 -2014-10-18 23:00:00,9859.0 -2014-10-19 00:00:00,9409.0 -2014-10-17 01:00:00,9252.0 -2014-10-17 02:00:00,8769.0 -2014-10-17 03:00:00,8472.0 -2014-10-17 04:00:00,8274.0 -2014-10-17 05:00:00,8205.0 -2014-10-17 06:00:00,8386.0 -2014-10-17 07:00:00,9055.0 -2014-10-17 08:00:00,10142.0 -2014-10-17 09:00:00,10613.0 -2014-10-17 10:00:00,10781.0 -2014-10-17 11:00:00,10875.0 -2014-10-17 12:00:00,11038.0 -2014-10-17 13:00:00,11067.0 -2014-10-17 14:00:00,11020.0 -2014-10-17 15:00:00,11072.0 -2014-10-17 16:00:00,11013.0 -2014-10-17 17:00:00,10925.0 -2014-10-17 18:00:00,10978.0 -2014-10-17 19:00:00,11105.0 -2014-10-17 20:00:00,11311.0 -2014-10-17 21:00:00,11143.0 -2014-10-17 22:00:00,10874.0 -2014-10-17 23:00:00,10412.0 -2014-10-18 00:00:00,9821.0 -2014-10-16 01:00:00,9166.0 -2014-10-16 02:00:00,8654.0 -2014-10-16 03:00:00,8353.0 -2014-10-16 04:00:00,8172.0 -2014-10-16 05:00:00,8105.0 -2014-10-16 06:00:00,8303.0 -2014-10-16 07:00:00,8985.0 -2014-10-16 08:00:00,10176.0 -2014-10-16 09:00:00,10775.0 -2014-10-16 10:00:00,10888.0 -2014-10-16 11:00:00,11021.0 -2014-10-16 12:00:00,11141.0 -2014-10-16 13:00:00,11130.0 -2014-10-16 14:00:00,11078.0 -2014-10-16 15:00:00,11085.0 -2014-10-16 16:00:00,11079.0 -2014-10-16 17:00:00,11074.0 -2014-10-16 18:00:00,11199.0 -2014-10-16 19:00:00,11182.0 -2014-10-16 20:00:00,11403.0 -2014-10-16 21:00:00,11434.0 -2014-10-16 22:00:00,11194.0 -2014-10-16 23:00:00,10729.0 -2014-10-17 00:00:00,9982.0 -2014-10-15 01:00:00,9150.0 -2014-10-15 02:00:00,8658.0 -2014-10-15 03:00:00,8331.0 -2014-10-15 04:00:00,8129.0 -2014-10-15 05:00:00,8076.0 -2014-10-15 06:00:00,8312.0 -2014-10-15 07:00:00,8948.0 -2014-10-15 08:00:00,10114.0 -2014-10-15 09:00:00,10804.0 -2014-10-15 10:00:00,11010.0 -2014-10-15 11:00:00,11116.0 -2014-10-15 12:00:00,11260.0 -2014-10-15 13:00:00,11269.0 -2014-10-15 14:00:00,11206.0 -2014-10-15 15:00:00,11230.0 -2014-10-15 16:00:00,11148.0 -2014-10-15 17:00:00,11017.0 -2014-10-15 18:00:00,11006.0 -2014-10-15 19:00:00,11133.0 -2014-10-15 20:00:00,11513.0 -2014-10-15 21:00:00,11465.0 -2014-10-15 22:00:00,11152.0 -2014-10-15 23:00:00,10657.0 -2014-10-16 00:00:00,9902.0 -2014-10-14 01:00:00,9391.0 -2014-10-14 02:00:00,8837.0 -2014-10-14 03:00:00,8498.0 -2014-10-14 04:00:00,8269.0 -2014-10-14 05:00:00,8185.0 -2014-10-14 06:00:00,8337.0 -2014-10-14 07:00:00,9052.0 -2014-10-14 08:00:00,10223.0 -2014-10-14 09:00:00,11067.0 -2014-10-14 10:00:00,11364.0 -2014-10-14 11:00:00,11487.0 -2014-10-14 12:00:00,11572.0 -2014-10-14 13:00:00,11591.0 -2014-10-14 14:00:00,11551.0 -2014-10-14 15:00:00,11564.0 -2014-10-14 16:00:00,11474.0 -2014-10-14 17:00:00,11398.0 -2014-10-14 18:00:00,11425.0 -2014-10-14 19:00:00,11544.0 -2014-10-14 20:00:00,11735.0 -2014-10-14 21:00:00,11659.0 -2014-10-14 22:00:00,11297.0 -2014-10-14 23:00:00,10709.0 -2014-10-15 00:00:00,9889.0 -2014-10-13 01:00:00,8425.0 -2014-10-13 02:00:00,8054.0 -2014-10-13 03:00:00,7831.0 -2014-10-13 04:00:00,7694.0 -2014-10-13 05:00:00,7720.0 -2014-10-13 06:00:00,7927.0 -2014-10-13 07:00:00,8629.0 -2014-10-13 08:00:00,9599.0 -2014-10-13 09:00:00,10248.0 -2014-10-13 10:00:00,10584.0 -2014-10-13 11:00:00,10865.0 -2014-10-13 12:00:00,11129.0 -2014-10-13 13:00:00,11232.0 -2014-10-13 14:00:00,11275.0 -2014-10-13 15:00:00,11363.0 -2014-10-13 16:00:00,11394.0 -2014-10-13 17:00:00,11393.0 -2014-10-13 18:00:00,11504.0 -2014-10-13 19:00:00,11667.0 -2014-10-13 20:00:00,11916.0 -2014-10-13 21:00:00,11851.0 -2014-10-13 22:00:00,11504.0 -2014-10-13 23:00:00,10941.0 -2014-10-14 00:00:00,10170.0 -2014-10-12 01:00:00,8587.0 -2014-10-12 02:00:00,8210.0 -2014-10-12 03:00:00,7919.0 -2014-10-12 04:00:00,7747.0 -2014-10-12 05:00:00,7697.0 -2014-10-12 06:00:00,7718.0 -2014-10-12 07:00:00,7871.0 -2014-10-12 08:00:00,8145.0 -2014-10-12 09:00:00,8169.0 -2014-10-12 10:00:00,8392.0 -2014-10-12 11:00:00,8573.0 -2014-10-12 12:00:00,8706.0 -2014-10-12 13:00:00,8797.0 -2014-10-12 14:00:00,8813.0 -2014-10-12 15:00:00,8829.0 -2014-10-12 16:00:00,8773.0 -2014-10-12 17:00:00,8829.0 -2014-10-12 18:00:00,8898.0 -2014-10-12 19:00:00,9138.0 -2014-10-12 20:00:00,9713.0 -2014-10-12 21:00:00,9896.0 -2014-10-12 22:00:00,9628.0 -2014-10-12 23:00:00,9349.0 -2014-10-13 00:00:00,8880.0 -2014-10-11 01:00:00,9140.0 -2014-10-11 02:00:00,8642.0 -2014-10-11 03:00:00,8346.0 -2014-10-11 04:00:00,8160.0 -2014-10-11 05:00:00,8084.0 -2014-10-11 06:00:00,8158.0 -2014-10-11 07:00:00,8411.0 -2014-10-11 08:00:00,8926.0 -2014-10-11 09:00:00,9120.0 -2014-10-11 10:00:00,9432.0 -2014-10-11 11:00:00,9585.0 -2014-10-11 12:00:00,9654.0 -2014-10-11 13:00:00,9610.0 -2014-10-11 14:00:00,9470.0 -2014-10-11 15:00:00,9341.0 -2014-10-11 16:00:00,9201.0 -2014-10-11 17:00:00,9149.0 -2014-10-11 18:00:00,9096.0 -2014-10-11 19:00:00,9138.0 -2014-10-11 20:00:00,9640.0 -2014-10-11 21:00:00,9922.0 -2014-10-11 22:00:00,9758.0 -2014-10-11 23:00:00,9497.0 -2014-10-12 00:00:00,9116.0 -2014-10-10 01:00:00,9053.0 -2014-10-10 02:00:00,8602.0 -2014-10-10 03:00:00,8315.0 -2014-10-10 04:00:00,8125.0 -2014-10-10 05:00:00,8103.0 -2014-10-10 06:00:00,8327.0 -2014-10-10 07:00:00,8995.0 -2014-10-10 08:00:00,10119.0 -2014-10-10 09:00:00,10560.0 -2014-10-10 10:00:00,10761.0 -2014-10-10 11:00:00,10884.0 -2014-10-10 12:00:00,10991.0 -2014-10-10 13:00:00,10997.0 -2014-10-10 14:00:00,10915.0 -2014-10-10 15:00:00,10904.0 -2014-10-10 16:00:00,10799.0 -2014-10-10 17:00:00,10620.0 -2014-10-10 18:00:00,10504.0 -2014-10-10 19:00:00,10463.0 -2014-10-10 20:00:00,10931.0 -2014-10-10 21:00:00,11047.0 -2014-10-10 22:00:00,10744.0 -2014-10-10 23:00:00,10407.0 -2014-10-11 00:00:00,9738.0 -2014-10-09 01:00:00,9057.0 -2014-10-09 02:00:00,8565.0 -2014-10-09 03:00:00,8265.0 -2014-10-09 04:00:00,8083.0 -2014-10-09 05:00:00,8052.0 -2014-10-09 06:00:00,8233.0 -2014-10-09 07:00:00,8886.0 -2014-10-09 08:00:00,10070.0 -2014-10-09 09:00:00,10605.0 -2014-10-09 10:00:00,10769.0 -2014-10-09 11:00:00,10867.0 -2014-10-09 12:00:00,10996.0 -2014-10-09 13:00:00,11001.0 -2014-10-09 14:00:00,10996.0 -2014-10-09 15:00:00,11078.0 -2014-10-09 16:00:00,11052.0 -2014-10-09 17:00:00,10940.0 -2014-10-09 18:00:00,10806.0 -2014-10-09 19:00:00,10691.0 -2014-10-09 20:00:00,11025.0 -2014-10-09 21:00:00,11282.0 -2014-10-09 22:00:00,11011.0 -2014-10-09 23:00:00,10519.0 -2014-10-10 00:00:00,9826.0 -2014-10-08 01:00:00,9045.0 -2014-10-08 02:00:00,8560.0 -2014-10-08 03:00:00,8260.0 -2014-10-08 04:00:00,8081.0 -2014-10-08 05:00:00,8088.0 -2014-10-08 06:00:00,8282.0 -2014-10-08 07:00:00,8985.0 -2014-10-08 08:00:00,10153.0 -2014-10-08 09:00:00,10653.0 -2014-10-08 10:00:00,10803.0 -2014-10-08 11:00:00,10867.0 -2014-10-08 12:00:00,11133.0 -2014-10-08 13:00:00,11104.0 -2014-10-08 14:00:00,11122.0 -2014-10-08 15:00:00,11171.0 -2014-10-08 16:00:00,11121.0 -2014-10-08 17:00:00,11051.0 -2014-10-08 18:00:00,10937.0 -2014-10-08 19:00:00,10840.0 -2014-10-08 20:00:00,11154.0 -2014-10-08 21:00:00,11381.0 -2014-10-08 22:00:00,11101.0 -2014-10-08 23:00:00,10552.0 -2014-10-09 00:00:00,9780.0 -2014-10-07 01:00:00,9095.0 -2014-10-07 02:00:00,8621.0 -2014-10-07 03:00:00,8340.0 -2014-10-07 04:00:00,8165.0 -2014-10-07 05:00:00,8117.0 -2014-10-07 06:00:00,8337.0 -2014-10-07 07:00:00,9010.0 -2014-10-07 08:00:00,10155.0 -2014-10-07 09:00:00,10620.0 -2014-10-07 10:00:00,10840.0 -2014-10-07 11:00:00,10981.0 -2014-10-07 12:00:00,11120.0 -2014-10-07 13:00:00,11178.0 -2014-10-07 14:00:00,11219.0 -2014-10-07 15:00:00,11268.0 -2014-10-07 16:00:00,11186.0 -2014-10-07 17:00:00,11039.0 -2014-10-07 18:00:00,10959.0 -2014-10-07 19:00:00,10835.0 -2014-10-07 20:00:00,11128.0 -2014-10-07 21:00:00,11417.0 -2014-10-07 22:00:00,11129.0 -2014-10-07 23:00:00,10561.0 -2014-10-08 00:00:00,9807.0 -2014-10-06 01:00:00,8439.0 -2014-10-06 02:00:00,8143.0 -2014-10-06 03:00:00,7943.0 -2014-10-06 04:00:00,7867.0 -2014-10-06 05:00:00,7836.0 -2014-10-06 06:00:00,8187.0 -2014-10-06 07:00:00,8896.0 -2014-10-06 08:00:00,10154.0 -2014-10-06 09:00:00,10800.0 -2014-10-06 10:00:00,10969.0 -2014-10-06 11:00:00,11011.0 -2014-10-06 12:00:00,11119.0 -2014-10-06 13:00:00,11141.0 -2014-10-06 14:00:00,11083.0 -2014-10-06 15:00:00,11136.0 -2014-10-06 16:00:00,10997.0 -2014-10-06 17:00:00,10862.0 -2014-10-06 18:00:00,10778.0 -2014-10-06 19:00:00,10719.0 -2014-10-06 20:00:00,11092.0 -2014-10-06 21:00:00,11469.0 -2014-10-06 22:00:00,11171.0 -2014-10-06 23:00:00,10621.0 -2014-10-07 00:00:00,9864.0 -2014-10-05 01:00:00,8749.0 -2014-10-05 02:00:00,8334.0 -2014-10-05 03:00:00,8047.0 -2014-10-05 04:00:00,7920.0 -2014-10-05 05:00:00,7832.0 -2014-10-05 06:00:00,7834.0 -2014-10-05 07:00:00,7925.0 -2014-10-05 08:00:00,8138.0 -2014-10-05 09:00:00,8202.0 -2014-10-05 10:00:00,8454.0 -2014-10-05 11:00:00,8691.0 -2014-10-05 12:00:00,8790.0 -2014-10-05 13:00:00,8889.0 -2014-10-05 14:00:00,8869.0 -2014-10-05 15:00:00,8870.0 -2014-10-05 16:00:00,8774.0 -2014-10-05 17:00:00,8779.0 -2014-10-05 18:00:00,8873.0 -2014-10-05 19:00:00,9197.0 -2014-10-05 20:00:00,9755.0 -2014-10-05 21:00:00,10003.0 -2014-10-05 22:00:00,9882.0 -2014-10-05 23:00:00,9495.0 -2014-10-06 00:00:00,9007.0 -2014-10-04 01:00:00,9105.0 -2014-10-04 02:00:00,8575.0 -2014-10-04 03:00:00,8270.0 -2014-10-04 04:00:00,8101.0 -2014-10-04 05:00:00,7979.0 -2014-10-04 06:00:00,8052.0 -2014-10-04 07:00:00,8304.0 -2014-10-04 08:00:00,8769.0 -2014-10-04 09:00:00,9049.0 -2014-10-04 10:00:00,9564.0 -2014-10-04 11:00:00,9943.0 -2014-10-04 12:00:00,10141.0 -2014-10-04 13:00:00,10066.0 -2014-10-04 14:00:00,10010.0 -2014-10-04 15:00:00,9818.0 -2014-10-04 16:00:00,9701.0 -2014-10-04 17:00:00,9583.0 -2014-10-04 18:00:00,9583.0 -2014-10-04 19:00:00,9588.0 -2014-10-04 20:00:00,9949.0 -2014-10-04 21:00:00,10240.0 -2014-10-04 22:00:00,10060.0 -2014-10-04 23:00:00,9785.0 -2014-10-05 00:00:00,9341.0 -2014-10-03 01:00:00,9903.0 -2014-10-03 02:00:00,9336.0 -2014-10-03 03:00:00,8979.0 -2014-10-03 04:00:00,8684.0 -2014-10-03 05:00:00,8690.0 -2014-10-03 06:00:00,8869.0 -2014-10-03 07:00:00,9366.0 -2014-10-03 08:00:00,10300.0 -2014-10-03 09:00:00,10966.0 -2014-10-03 10:00:00,11124.0 -2014-10-03 11:00:00,11263.0 -2014-10-03 12:00:00,11316.0 -2014-10-03 13:00:00,11315.0 -2014-10-03 14:00:00,11252.0 -2014-10-03 15:00:00,11180.0 -2014-10-03 16:00:00,10989.0 -2014-10-03 17:00:00,10789.0 -2014-10-03 18:00:00,10697.0 -2014-10-03 19:00:00,10643.0 -2014-10-03 20:00:00,10929.0 -2014-10-03 21:00:00,11031.0 -2014-10-03 22:00:00,10790.0 -2014-10-03 23:00:00,10452.0 -2014-10-04 00:00:00,9783.0 -2014-10-02 01:00:00,9308.0 -2014-10-02 02:00:00,8776.0 -2014-10-02 03:00:00,8479.0 -2014-10-02 04:00:00,8260.0 -2014-10-02 05:00:00,8219.0 -2014-10-02 06:00:00,8424.0 -2014-10-02 07:00:00,9081.0 -2014-10-02 08:00:00,10328.0 -2014-10-02 09:00:00,11008.0 -2014-10-02 10:00:00,11369.0 -2014-10-02 11:00:00,11717.0 -2014-10-02 12:00:00,12023.0 -2014-10-02 13:00:00,12223.0 -2014-10-02 14:00:00,12342.0 -2014-10-02 15:00:00,12366.0 -2014-10-02 16:00:00,12318.0 -2014-10-02 17:00:00,12259.0 -2014-10-02 18:00:00,12195.0 -2014-10-02 19:00:00,12097.0 -2014-10-02 20:00:00,12375.0 -2014-10-02 21:00:00,12538.0 -2014-10-02 22:00:00,12185.0 -2014-10-02 23:00:00,11561.0 -2014-10-03 00:00:00,10701.0 -2014-10-01 01:00:00,9024.0 -2014-10-01 02:00:00,8517.0 -2014-10-01 03:00:00,8223.0 -2014-10-01 04:00:00,8039.0 -2014-10-01 05:00:00,7982.0 -2014-10-01 06:00:00,8172.0 -2014-10-01 07:00:00,8830.0 -2014-10-01 08:00:00,9930.0 -2014-10-01 09:00:00,10403.0 -2014-10-01 10:00:00,10688.0 -2014-10-01 11:00:00,10934.0 -2014-10-01 12:00:00,11106.0 -2014-10-01 13:00:00,11264.0 -2014-10-01 14:00:00,11412.0 -2014-10-01 15:00:00,11567.0 -2014-10-01 16:00:00,11578.0 -2014-10-01 17:00:00,11496.0 -2014-10-01 18:00:00,11452.0 -2014-10-01 19:00:00,11312.0 -2014-10-01 20:00:00,11442.0 -2014-10-01 21:00:00,11783.0 -2014-10-01 22:00:00,11496.0 -2014-10-01 23:00:00,10932.0 -2014-10-02 00:00:00,10117.0 -2014-09-30 01:00:00,9333.0 -2014-09-30 02:00:00,8736.0 -2014-09-30 03:00:00,8387.0 -2014-09-30 04:00:00,8167.0 -2014-09-30 05:00:00,8061.0 -2014-09-30 06:00:00,8221.0 -2014-09-30 07:00:00,8844.0 -2014-09-30 08:00:00,9951.0 -2014-09-30 09:00:00,10501.0 -2014-09-30 10:00:00,10750.0 -2014-09-30 11:00:00,10921.0 -2014-09-30 12:00:00,11040.0 -2014-09-30 13:00:00,11060.0 -2014-09-30 14:00:00,11041.0 -2014-09-30 15:00:00,11094.0 -2014-09-30 16:00:00,11014.0 -2014-09-30 17:00:00,10912.0 -2014-09-30 18:00:00,10802.0 -2014-09-30 19:00:00,10777.0 -2014-09-30 20:00:00,11084.0 -2014-09-30 21:00:00,11387.0 -2014-09-30 22:00:00,11105.0 -2014-09-30 23:00:00,10554.0 -2014-10-01 00:00:00,9762.0 -2014-09-29 01:00:00,8922.0 -2014-09-29 02:00:00,8460.0 -2014-09-29 03:00:00,8214.0 -2014-09-29 04:00:00,8034.0 -2014-09-29 05:00:00,8047.0 -2014-09-29 06:00:00,8227.0 -2014-09-29 07:00:00,8979.0 -2014-09-29 08:00:00,10098.0 -2014-09-29 09:00:00,10735.0 -2014-09-29 10:00:00,11204.0 -2014-09-29 11:00:00,11771.0 -2014-09-29 12:00:00,12261.0 -2014-09-29 13:00:00,12700.0 -2014-09-29 14:00:00,13076.0 -2014-09-29 15:00:00,13447.0 -2014-09-29 16:00:00,13644.0 -2014-09-29 17:00:00,13699.0 -2014-09-29 18:00:00,13600.0 -2014-09-29 19:00:00,13217.0 -2014-09-29 20:00:00,12691.0 -2014-09-29 21:00:00,12603.0 -2014-09-29 22:00:00,11968.0 -2014-09-29 23:00:00,11170.0 -2014-09-30 00:00:00,10210.0 -2014-09-28 01:00:00,9114.0 -2014-09-28 02:00:00,8623.0 -2014-09-28 03:00:00,8241.0 -2014-09-28 04:00:00,8023.0 -2014-09-28 05:00:00,7839.0 -2014-09-28 06:00:00,7839.0 -2014-09-28 07:00:00,7830.0 -2014-09-28 08:00:00,8026.0 -2014-09-28 09:00:00,8039.0 -2014-09-28 10:00:00,8625.0 -2014-09-28 11:00:00,9111.0 -2014-09-28 12:00:00,9603.0 -2014-09-28 13:00:00,10029.0 -2014-09-28 14:00:00,10310.0 -2014-09-28 15:00:00,10514.0 -2014-09-28 16:00:00,10727.0 -2014-09-28 17:00:00,10856.0 -2014-09-28 18:00:00,10826.0 -2014-09-28 19:00:00,10799.0 -2014-09-28 20:00:00,10792.0 -2014-09-28 21:00:00,11183.0 -2014-09-28 22:00:00,10862.0 -2014-09-28 23:00:00,10324.0 -2014-09-29 00:00:00,9595.0 -2014-09-27 01:00:00,9687.0 -2014-09-27 02:00:00,9036.0 -2014-09-27 03:00:00,8622.0 -2014-09-27 04:00:00,8277.0 -2014-09-27 05:00:00,8175.0 -2014-09-27 06:00:00,8172.0 -2014-09-27 07:00:00,8385.0 -2014-09-27 08:00:00,8707.0 -2014-09-27 09:00:00,8978.0 -2014-09-27 10:00:00,9628.0 -2014-09-27 11:00:00,10151.0 -2014-09-27 12:00:00,10580.0 -2014-09-27 13:00:00,10869.0 -2014-09-27 14:00:00,11068.0 -2014-09-27 15:00:00,11066.0 -2014-09-27 16:00:00,11151.0 -2014-09-27 17:00:00,11079.0 -2014-09-27 18:00:00,11012.0 -2014-09-27 19:00:00,10755.0 -2014-09-27 20:00:00,10749.0 -2014-09-27 21:00:00,11029.0 -2014-09-27 22:00:00,10810.0 -2014-09-27 23:00:00,10375.0 -2014-09-28 00:00:00,9793.0 -2014-09-26 01:00:00,9456.0 -2014-09-26 02:00:00,8881.0 -2014-09-26 03:00:00,8521.0 -2014-09-26 04:00:00,8290.0 -2014-09-26 05:00:00,8201.0 -2014-09-26 06:00:00,8361.0 -2014-09-26 07:00:00,8982.0 -2014-09-26 08:00:00,9957.0 -2014-09-26 09:00:00,10458.0 -2014-09-26 10:00:00,11023.0 -2014-09-26 11:00:00,11509.0 -2014-09-26 12:00:00,11959.0 -2014-09-26 13:00:00,12254.0 -2014-09-26 14:00:00,12458.0 -2014-09-26 15:00:00,12659.0 -2014-09-26 16:00:00,12750.0 -2014-09-26 17:00:00,12721.0 -2014-09-26 18:00:00,12580.0 -2014-09-26 19:00:00,12195.0 -2014-09-26 20:00:00,11912.0 -2014-09-26 21:00:00,12126.0 -2014-09-26 22:00:00,11749.0 -2014-09-26 23:00:00,11257.0 -2014-09-27 00:00:00,10460.0 -2014-09-25 01:00:00,9306.0 -2014-09-25 02:00:00,8749.0 -2014-09-25 03:00:00,8402.0 -2014-09-25 04:00:00,8213.0 -2014-09-25 05:00:00,8117.0 -2014-09-25 06:00:00,8279.0 -2014-09-25 07:00:00,8941.0 -2014-09-25 08:00:00,9999.0 -2014-09-25 09:00:00,10589.0 -2014-09-25 10:00:00,10977.0 -2014-09-25 11:00:00,11278.0 -2014-09-25 12:00:00,11547.0 -2014-09-25 13:00:00,11705.0 -2014-09-25 14:00:00,11848.0 -2014-09-25 15:00:00,12066.0 -2014-09-25 16:00:00,12135.0 -2014-09-25 17:00:00,12062.0 -2014-09-25 18:00:00,11962.0 -2014-09-25 19:00:00,11732.0 -2014-09-25 20:00:00,11622.0 -2014-09-25 21:00:00,12042.0 -2014-09-25 22:00:00,11745.0 -2014-09-25 23:00:00,11123.0 -2014-09-26 00:00:00,10294.0 -2014-09-24 01:00:00,9202.0 -2014-09-24 02:00:00,8681.0 -2014-09-24 03:00:00,8341.0 -2014-09-24 04:00:00,8143.0 -2014-09-24 05:00:00,8071.0 -2014-09-24 06:00:00,8247.0 -2014-09-24 07:00:00,8890.0 -2014-09-24 08:00:00,9888.0 -2014-09-24 09:00:00,10419.0 -2014-09-24 10:00:00,10866.0 -2014-09-24 11:00:00,11197.0 -2014-09-24 12:00:00,11523.0 -2014-09-24 13:00:00,11728.0 -2014-09-24 14:00:00,11887.0 -2014-09-24 15:00:00,12005.0 -2014-09-24 16:00:00,12023.0 -2014-09-24 17:00:00,11987.0 -2014-09-24 18:00:00,11924.0 -2014-09-24 19:00:00,11660.0 -2014-09-24 20:00:00,11560.0 -2014-09-24 21:00:00,11908.0 -2014-09-24 22:00:00,11588.0 -2014-09-24 23:00:00,10966.0 -2014-09-25 00:00:00,10124.0 -2014-09-23 01:00:00,8982.0 -2014-09-23 02:00:00,8463.0 -2014-09-23 03:00:00,8155.0 -2014-09-23 04:00:00,7978.0 -2014-09-23 05:00:00,7910.0 -2014-09-23 06:00:00,8110.0 -2014-09-23 07:00:00,8712.0 -2014-09-23 08:00:00,9768.0 -2014-09-23 09:00:00,10319.0 -2014-09-23 10:00:00,10675.0 -2014-09-23 11:00:00,10955.0 -2014-09-23 12:00:00,11302.0 -2014-09-23 13:00:00,11509.0 -2014-09-23 14:00:00,11643.0 -2014-09-23 15:00:00,11845.0 -2014-09-23 16:00:00,11896.0 -2014-09-23 17:00:00,11853.0 -2014-09-23 18:00:00,11820.0 -2014-09-23 19:00:00,11633.0 -2014-09-23 20:00:00,11473.0 -2014-09-23 21:00:00,11805.0 -2014-09-23 22:00:00,11502.0 -2014-09-23 23:00:00,10867.0 -2014-09-24 00:00:00,10020.0 -2014-09-22 01:00:00,8306.0 -2014-09-22 02:00:00,7942.0 -2014-09-22 03:00:00,7720.0 -2014-09-22 04:00:00,7549.0 -2014-09-22 05:00:00,7617.0 -2014-09-22 06:00:00,7861.0 -2014-09-22 07:00:00,8565.0 -2014-09-22 08:00:00,9689.0 -2014-09-22 09:00:00,10248.0 -2014-09-22 10:00:00,10565.0 -2014-09-22 11:00:00,10767.0 -2014-09-22 12:00:00,11014.0 -2014-09-22 13:00:00,11111.0 -2014-09-22 14:00:00,11175.0 -2014-09-22 15:00:00,11318.0 -2014-09-22 16:00:00,11315.0 -2014-09-22 17:00:00,11291.0 -2014-09-22 18:00:00,11225.0 -2014-09-22 19:00:00,11060.0 -2014-09-22 20:00:00,10972.0 -2014-09-22 21:00:00,11429.0 -2014-09-22 22:00:00,11153.0 -2014-09-22 23:00:00,10560.0 -2014-09-23 00:00:00,9816.0 -2014-09-21 01:00:00,9651.0 -2014-09-21 02:00:00,9102.0 -2014-09-21 03:00:00,8618.0 -2014-09-21 04:00:00,8301.0 -2014-09-21 05:00:00,7992.0 -2014-09-21 06:00:00,7858.0 -2014-09-21 07:00:00,7865.0 -2014-09-21 08:00:00,7910.0 -2014-09-21 09:00:00,7946.0 -2014-09-21 10:00:00,8409.0 -2014-09-21 11:00:00,8715.0 -2014-09-21 12:00:00,8971.0 -2014-09-21 13:00:00,9052.0 -2014-09-21 14:00:00,9175.0 -2014-09-21 15:00:00,9215.0 -2014-09-21 16:00:00,9235.0 -2014-09-21 17:00:00,9215.0 -2014-09-21 18:00:00,9288.0 -2014-09-21 19:00:00,9344.0 -2014-09-21 20:00:00,9467.0 -2014-09-21 21:00:00,10010.0 -2014-09-21 22:00:00,9863.0 -2014-09-21 23:00:00,9463.0 -2014-09-22 00:00:00,8914.0 -2014-09-20 01:00:00,9427.0 -2014-09-20 02:00:00,8761.0 -2014-09-20 03:00:00,8423.0 -2014-09-20 04:00:00,8130.0 -2014-09-20 05:00:00,8006.0 -2014-09-20 06:00:00,7986.0 -2014-09-20 07:00:00,8287.0 -2014-09-20 08:00:00,8659.0 -2014-09-20 09:00:00,8907.0 -2014-09-20 10:00:00,9442.0 -2014-09-20 11:00:00,9917.0 -2014-09-20 12:00:00,10358.0 -2014-09-20 13:00:00,10721.0 -2014-09-20 14:00:00,10931.0 -2014-09-20 15:00:00,10920.0 -2014-09-20 16:00:00,10918.0 -2014-09-20 17:00:00,10855.0 -2014-09-20 18:00:00,10863.0 -2014-09-20 19:00:00,10905.0 -2014-09-20 20:00:00,10847.0 -2014-09-20 21:00:00,11259.0 -2014-09-20 22:00:00,11112.0 -2014-09-20 23:00:00,10750.0 -2014-09-21 00:00:00,10231.0 -2014-09-19 01:00:00,9365.0 -2014-09-19 02:00:00,8837.0 -2014-09-19 03:00:00,8505.0 -2014-09-19 04:00:00,8331.0 -2014-09-19 05:00:00,8241.0 -2014-09-19 06:00:00,8396.0 -2014-09-19 07:00:00,8982.0 -2014-09-19 08:00:00,9900.0 -2014-09-19 09:00:00,10471.0 -2014-09-19 10:00:00,10873.0 -2014-09-19 11:00:00,11175.0 -2014-09-19 12:00:00,11499.0 -2014-09-19 13:00:00,11709.0 -2014-09-19 14:00:00,11787.0 -2014-09-19 15:00:00,11927.0 -2014-09-19 16:00:00,11932.0 -2014-09-19 17:00:00,11845.0 -2014-09-19 18:00:00,11721.0 -2014-09-19 19:00:00,11422.0 -2014-09-19 20:00:00,11150.0 -2014-09-19 21:00:00,11508.0 -2014-09-19 22:00:00,11288.0 -2014-09-19 23:00:00,10873.0 -2014-09-20 00:00:00,10112.0 -2014-09-18 01:00:00,9128.0 -2014-09-18 02:00:00,8616.0 -2014-09-18 03:00:00,8296.0 -2014-09-18 04:00:00,8092.0 -2014-09-18 05:00:00,8013.0 -2014-09-18 06:00:00,8208.0 -2014-09-18 07:00:00,8843.0 -2014-09-18 08:00:00,9850.0 -2014-09-18 09:00:00,10422.0 -2014-09-18 10:00:00,10755.0 -2014-09-18 11:00:00,11117.0 -2014-09-18 12:00:00,11437.0 -2014-09-18 13:00:00,11575.0 -2014-09-18 14:00:00,11657.0 -2014-09-18 15:00:00,11781.0 -2014-09-18 16:00:00,11773.0 -2014-09-18 17:00:00,11698.0 -2014-09-18 18:00:00,11588.0 -2014-09-18 19:00:00,11344.0 -2014-09-18 20:00:00,11153.0 -2014-09-18 21:00:00,11648.0 -2014-09-18 22:00:00,11443.0 -2014-09-18 23:00:00,10960.0 -2014-09-19 00:00:00,10153.0 -2014-09-17 01:00:00,9054.0 -2014-09-17 02:00:00,8540.0 -2014-09-17 03:00:00,8208.0 -2014-09-17 04:00:00,8032.0 -2014-09-17 05:00:00,7996.0 -2014-09-17 06:00:00,8184.0 -2014-09-17 07:00:00,8848.0 -2014-09-17 08:00:00,9829.0 -2014-09-17 09:00:00,10359.0 -2014-09-17 10:00:00,10702.0 -2014-09-17 11:00:00,10967.0 -2014-09-17 12:00:00,11220.0 -2014-09-17 13:00:00,11362.0 -2014-09-17 14:00:00,11458.0 -2014-09-17 15:00:00,11559.0 -2014-09-17 16:00:00,11583.0 -2014-09-17 17:00:00,11510.0 -2014-09-17 18:00:00,11421.0 -2014-09-17 19:00:00,11259.0 -2014-09-17 20:00:00,11058.0 -2014-09-17 21:00:00,11496.0 -2014-09-17 22:00:00,11315.0 -2014-09-17 23:00:00,10729.0 -2014-09-18 00:00:00,9941.0 -2014-09-16 01:00:00,9035.0 -2014-09-16 02:00:00,8538.0 -2014-09-16 03:00:00,8253.0 -2014-09-16 04:00:00,8101.0 -2014-09-16 05:00:00,8065.0 -2014-09-16 06:00:00,8261.0 -2014-09-16 07:00:00,8921.0 -2014-09-16 08:00:00,9934.0 -2014-09-16 09:00:00,10445.0 -2014-09-16 10:00:00,10719.0 -2014-09-16 11:00:00,10942.0 -2014-09-16 12:00:00,11160.0 -2014-09-16 13:00:00,11233.0 -2014-09-16 14:00:00,11231.0 -2014-09-16 15:00:00,11354.0 -2014-09-16 16:00:00,11341.0 -2014-09-16 17:00:00,11267.0 -2014-09-16 18:00:00,11186.0 -2014-09-16 19:00:00,11028.0 -2014-09-16 20:00:00,10844.0 -2014-09-16 21:00:00,11326.0 -2014-09-16 22:00:00,11185.0 -2014-09-16 23:00:00,10625.0 -2014-09-17 00:00:00,9809.0 -2014-09-15 01:00:00,8413.0 -2014-09-15 02:00:00,8021.0 -2014-09-15 03:00:00,7827.0 -2014-09-15 04:00:00,7691.0 -2014-09-15 05:00:00,7695.0 -2014-09-15 06:00:00,7948.0 -2014-09-15 07:00:00,8652.0 -2014-09-15 08:00:00,9752.0 -2014-09-15 09:00:00,10439.0 -2014-09-15 10:00:00,10704.0 -2014-09-15 11:00:00,10999.0 -2014-09-15 12:00:00,11253.0 -2014-09-15 13:00:00,11273.0 -2014-09-15 14:00:00,11246.0 -2014-09-15 15:00:00,11282.0 -2014-09-15 16:00:00,11208.0 -2014-09-15 17:00:00,11095.0 -2014-09-15 18:00:00,11031.0 -2014-09-15 19:00:00,10966.0 -2014-09-15 20:00:00,10982.0 -2014-09-15 21:00:00,11315.0 -2014-09-15 22:00:00,11100.0 -2014-09-15 23:00:00,10556.0 -2014-09-16 00:00:00,9781.0 -2014-09-14 01:00:00,8534.0 -2014-09-14 02:00:00,8157.0 -2014-09-14 03:00:00,7850.0 -2014-09-14 04:00:00,7690.0 -2014-09-14 05:00:00,7533.0 -2014-09-14 06:00:00,7583.0 -2014-09-14 07:00:00,7690.0 -2014-09-14 08:00:00,7824.0 -2014-09-14 09:00:00,7888.0 -2014-09-14 10:00:00,8267.0 -2014-09-14 11:00:00,8501.0 -2014-09-14 12:00:00,8762.0 -2014-09-14 13:00:00,8833.0 -2014-09-14 14:00:00,8926.0 -2014-09-14 15:00:00,8935.0 -2014-09-14 16:00:00,8977.0 -2014-09-14 17:00:00,8979.0 -2014-09-14 18:00:00,9080.0 -2014-09-14 19:00:00,9116.0 -2014-09-14 20:00:00,9248.0 -2014-09-14 21:00:00,9848.0 -2014-09-14 22:00:00,9857.0 -2014-09-14 23:00:00,9562.0 -2014-09-15 00:00:00,8991.0 -2014-09-13 01:00:00,9152.0 -2014-09-13 02:00:00,8620.0 -2014-09-13 03:00:00,8332.0 -2014-09-13 04:00:00,8041.0 -2014-09-13 05:00:00,8006.0 -2014-09-13 06:00:00,8037.0 -2014-09-13 07:00:00,8351.0 -2014-09-13 08:00:00,8637.0 -2014-09-13 09:00:00,8947.0 -2014-09-13 10:00:00,9311.0 -2014-09-13 11:00:00,9586.0 -2014-09-13 12:00:00,9740.0 -2014-09-13 13:00:00,9680.0 -2014-09-13 14:00:00,9540.0 -2014-09-13 15:00:00,9437.0 -2014-09-13 16:00:00,9366.0 -2014-09-13 17:00:00,9254.0 -2014-09-13 18:00:00,9228.0 -2014-09-13 19:00:00,9177.0 -2014-09-13 20:00:00,9235.0 -2014-09-13 21:00:00,9734.0 -2014-09-13 22:00:00,9843.0 -2014-09-13 23:00:00,9540.0 -2014-09-14 00:00:00,9102.0 -2014-09-12 01:00:00,9119.0 -2014-09-12 02:00:00,8605.0 -2014-09-12 03:00:00,8318.0 -2014-09-12 04:00:00,8146.0 -2014-09-12 05:00:00,8071.0 -2014-09-12 06:00:00,8279.0 -2014-09-12 07:00:00,8856.0 -2014-09-12 08:00:00,9897.0 -2014-09-12 09:00:00,10468.0 -2014-09-12 10:00:00,10800.0 -2014-09-12 11:00:00,10930.0 -2014-09-12 12:00:00,11058.0 -2014-09-12 13:00:00,11080.0 -2014-09-12 14:00:00,10979.0 -2014-09-12 15:00:00,11028.0 -2014-09-12 16:00:00,10955.0 -2014-09-12 17:00:00,10855.0 -2014-09-12 18:00:00,10802.0 -2014-09-12 19:00:00,10791.0 -2014-09-12 20:00:00,10790.0 -2014-09-12 21:00:00,11058.0 -2014-09-12 22:00:00,10908.0 -2014-09-12 23:00:00,10481.0 -2014-09-13 00:00:00,9808.0 -2014-09-11 01:00:00,9837.0 -2014-09-11 02:00:00,9178.0 -2014-09-11 03:00:00,8775.0 -2014-09-11 04:00:00,8519.0 -2014-09-11 05:00:00,8389.0 -2014-09-11 06:00:00,8474.0 -2014-09-11 07:00:00,9063.0 -2014-09-11 08:00:00,10014.0 -2014-09-11 09:00:00,10602.0 -2014-09-11 10:00:00,10882.0 -2014-09-11 11:00:00,11011.0 -2014-09-11 12:00:00,11100.0 -2014-09-11 13:00:00,11089.0 -2014-09-11 14:00:00,11043.0 -2014-09-11 15:00:00,11045.0 -2014-09-11 16:00:00,10965.0 -2014-09-11 17:00:00,10870.0 -2014-09-11 18:00:00,10824.0 -2014-09-11 19:00:00,10766.0 -2014-09-11 20:00:00,10839.0 -2014-09-11 21:00:00,11237.0 -2014-09-11 22:00:00,11099.0 -2014-09-11 23:00:00,10614.0 -2014-09-12 00:00:00,9882.0 -2014-09-10 01:00:00,11516.0 -2014-09-10 02:00:00,10768.0 -2014-09-10 03:00:00,10190.0 -2014-09-10 04:00:00,9813.0 -2014-09-10 05:00:00,9704.0 -2014-09-10 06:00:00,9869.0 -2014-09-10 07:00:00,10604.0 -2014-09-10 08:00:00,11824.0 -2014-09-10 09:00:00,12692.0 -2014-09-10 10:00:00,13214.0 -2014-09-10 11:00:00,13543.0 -2014-09-10 12:00:00,13829.0 -2014-09-10 13:00:00,14062.0 -2014-09-10 14:00:00,14350.0 -2014-09-10 15:00:00,14795.0 -2014-09-10 16:00:00,15094.0 -2014-09-10 17:00:00,15094.0 -2014-09-10 18:00:00,15013.0 -2014-09-10 19:00:00,14647.0 -2014-09-10 20:00:00,13967.0 -2014-09-10 21:00:00,13662.0 -2014-09-10 22:00:00,12975.0 -2014-09-10 23:00:00,11995.0 -2014-09-11 00:00:00,10870.0 -2014-09-09 01:00:00,10484.0 -2014-09-09 02:00:00,9806.0 -2014-09-09 03:00:00,9348.0 -2014-09-09 04:00:00,9077.0 -2014-09-09 05:00:00,8966.0 -2014-09-09 06:00:00,9151.0 -2014-09-09 07:00:00,9834.0 -2014-09-09 08:00:00,10952.0 -2014-09-09 09:00:00,11645.0 -2014-09-09 10:00:00,12127.0 -2014-09-09 11:00:00,12570.0 -2014-09-09 12:00:00,12947.0 -2014-09-09 13:00:00,13442.0 -2014-09-09 14:00:00,13984.0 -2014-09-09 15:00:00,14538.0 -2014-09-09 16:00:00,14750.0 -2014-09-09 17:00:00,15078.0 -2014-09-09 18:00:00,15181.0 -2014-09-09 19:00:00,14783.0 -2014-09-09 20:00:00,14391.0 -2014-09-09 21:00:00,14708.0 -2014-09-09 22:00:00,14524.0 -2014-09-09 23:00:00,13753.0 -2014-09-10 00:00:00,12658.0 -2014-09-08 01:00:00,9476.0 -2014-09-08 02:00:00,8918.0 -2014-09-08 03:00:00,8601.0 -2014-09-08 04:00:00,8476.0 -2014-09-08 05:00:00,8431.0 -2014-09-08 06:00:00,8586.0 -2014-09-08 07:00:00,9263.0 -2014-09-08 08:00:00,10251.0 -2014-09-08 09:00:00,11013.0 -2014-09-08 10:00:00,11624.0 -2014-09-08 11:00:00,12200.0 -2014-09-08 12:00:00,12710.0 -2014-09-08 13:00:00,13004.0 -2014-09-08 14:00:00,13247.0 -2014-09-08 15:00:00,13606.0 -2014-09-08 16:00:00,13789.0 -2014-09-08 17:00:00,13893.0 -2014-09-08 18:00:00,13930.0 -2014-09-08 19:00:00,13646.0 -2014-09-08 20:00:00,13132.0 -2014-09-08 21:00:00,13273.0 -2014-09-08 22:00:00,13136.0 -2014-09-08 23:00:00,12452.0 -2014-09-09 00:00:00,11442.0 -2014-09-07 01:00:00,9453.0 -2014-09-07 02:00:00,8879.0 -2014-09-07 03:00:00,8463.0 -2014-09-07 04:00:00,8204.0 -2014-09-07 05:00:00,8032.0 -2014-09-07 06:00:00,7963.0 -2014-09-07 07:00:00,8036.0 -2014-09-07 08:00:00,8000.0 -2014-09-07 09:00:00,8196.0 -2014-09-07 10:00:00,8843.0 -2014-09-07 11:00:00,9441.0 -2014-09-07 12:00:00,9976.0 -2014-09-07 13:00:00,10510.0 -2014-09-07 14:00:00,10868.0 -2014-09-07 15:00:00,11174.0 -2014-09-07 16:00:00,11415.0 -2014-09-07 17:00:00,11631.0 -2014-09-07 18:00:00,11758.0 -2014-09-07 19:00:00,11736.0 -2014-09-07 20:00:00,11448.0 -2014-09-07 21:00:00,11554.0 -2014-09-07 22:00:00,11539.0 -2014-09-07 23:00:00,10966.0 -2014-09-08 00:00:00,10220.0 -2014-09-06 01:00:00,10941.0 -2014-09-06 02:00:00,10168.0 -2014-09-06 03:00:00,9586.0 -2014-09-06 04:00:00,9175.0 -2014-09-06 05:00:00,8963.0 -2014-09-06 06:00:00,8912.0 -2014-09-06 07:00:00,9061.0 -2014-09-06 08:00:00,9223.0 -2014-09-06 09:00:00,9424.0 -2014-09-06 10:00:00,10030.0 -2014-09-06 11:00:00,10568.0 -2014-09-06 12:00:00,11041.0 -2014-09-06 13:00:00,11233.0 -2014-09-06 14:00:00,11312.0 -2014-09-06 15:00:00,11382.0 -2014-09-06 16:00:00,11499.0 -2014-09-06 17:00:00,11674.0 -2014-09-06 18:00:00,11741.0 -2014-09-06 19:00:00,11656.0 -2014-09-06 20:00:00,11300.0 -2014-09-06 21:00:00,11305.0 -2014-09-06 22:00:00,11280.0 -2014-09-06 23:00:00,10794.0 -2014-09-07 00:00:00,10157.0 -2014-09-05 01:00:00,13672.0 -2014-09-05 02:00:00,12826.0 -2014-09-05 03:00:00,12239.0 -2014-09-05 04:00:00,11831.0 -2014-09-05 05:00:00,11579.0 -2014-09-05 06:00:00,11706.0 -2014-09-05 07:00:00,12331.0 -2014-09-05 08:00:00,13461.0 -2014-09-05 09:00:00,14530.0 -2014-09-05 10:00:00,15700.0 -2014-09-05 11:00:00,16700.0 -2014-09-05 12:00:00,17624.0 -2014-09-05 13:00:00,18097.0 -2014-09-05 14:00:00,18434.0 -2014-09-05 15:00:00,19016.0 -2014-09-05 16:00:00,18974.0 -2014-09-05 17:00:00,17453.0 -2014-09-05 18:00:00,16558.0 -2014-09-05 19:00:00,15659.0 -2014-09-05 20:00:00,14528.0 -2014-09-05 21:00:00,13901.0 -2014-09-05 22:00:00,13434.0 -2014-09-05 23:00:00,12850.0 -2014-09-06 00:00:00,11915.0 -2014-09-04 01:00:00,12596.0 -2014-09-04 02:00:00,11660.0 -2014-09-04 03:00:00,11090.0 -2014-09-04 04:00:00,10777.0 -2014-09-04 05:00:00,10598.0 -2014-09-04 06:00:00,10738.0 -2014-09-04 07:00:00,11508.0 -2014-09-04 08:00:00,12745.0 -2014-09-04 09:00:00,13558.0 -2014-09-04 10:00:00,13896.0 -2014-09-04 11:00:00,14179.0 -2014-09-04 12:00:00,14315.0 -2014-09-04 13:00:00,14312.0 -2014-09-04 14:00:00,14203.0 -2014-09-04 15:00:00,14336.0 -2014-09-04 16:00:00,14764.0 -2014-09-04 17:00:00,15583.0 -2014-09-04 18:00:00,16440.0 -2014-09-04 19:00:00,16774.0 -2014-09-04 20:00:00,16614.0 -2014-09-04 21:00:00,16616.0 -2014-09-04 22:00:00,16626.0 -2014-09-04 23:00:00,16009.0 -2014-09-05 00:00:00,14885.0 -2014-09-03 01:00:00,11632.0 -2014-09-03 02:00:00,10764.0 -2014-09-03 03:00:00,10193.0 -2014-09-03 04:00:00,9810.0 -2014-09-03 05:00:00,9605.0 -2014-09-03 06:00:00,9711.0 -2014-09-03 07:00:00,10279.0 -2014-09-03 08:00:00,11211.0 -2014-09-03 09:00:00,12099.0 -2014-09-03 10:00:00,12994.0 -2014-09-03 11:00:00,13782.0 -2014-09-03 12:00:00,14555.0 -2014-09-03 13:00:00,15167.0 -2014-09-03 14:00:00,15723.0 -2014-09-03 15:00:00,16446.0 -2014-09-03 16:00:00,17003.0 -2014-09-03 17:00:00,17473.0 -2014-09-03 18:00:00,17697.0 -2014-09-03 19:00:00,17527.0 -2014-09-03 20:00:00,16896.0 -2014-09-03 21:00:00,16472.0 -2014-09-03 22:00:00,16169.0 -2014-09-03 23:00:00,15201.0 -2014-09-04 00:00:00,13827.0 -2014-09-02 01:00:00,11443.0 -2014-09-02 02:00:00,10721.0 -2014-09-02 03:00:00,10192.0 -2014-09-02 04:00:00,9816.0 -2014-09-02 05:00:00,9699.0 -2014-09-02 06:00:00,9892.0 -2014-09-02 07:00:00,10662.0 -2014-09-02 08:00:00,11741.0 -2014-09-02 09:00:00,12579.0 -2014-09-02 10:00:00,13411.0 -2014-09-02 11:00:00,14135.0 -2014-09-02 12:00:00,14899.0 -2014-09-02 13:00:00,15488.0 -2014-09-02 14:00:00,15958.0 -2014-09-02 15:00:00,16454.0 -2014-09-02 16:00:00,16765.0 -2014-09-02 17:00:00,16986.0 -2014-09-02 18:00:00,16962.0 -2014-09-02 19:00:00,16641.0 -2014-09-02 20:00:00,15827.0 -2014-09-02 21:00:00,15412.0 -2014-09-02 22:00:00,15197.0 -2014-09-02 23:00:00,14229.0 -2014-09-03 00:00:00,12900.0 -2014-09-01 01:00:00,12223.0 -2014-09-01 02:00:00,11421.0 -2014-09-01 03:00:00,10883.0 -2014-09-01 04:00:00,10455.0 -2014-09-01 05:00:00,10200.0 -2014-09-01 06:00:00,10100.0 -2014-09-01 07:00:00,10299.0 -2014-09-01 08:00:00,10408.0 -2014-09-01 09:00:00,10689.0 -2014-09-01 10:00:00,11343.0 -2014-09-01 11:00:00,12030.0 -2014-09-01 12:00:00,12492.0 -2014-09-01 13:00:00,12961.0 -2014-09-01 14:00:00,13622.0 -2014-09-01 15:00:00,14330.0 -2014-09-01 16:00:00,14869.0 -2014-09-01 17:00:00,15160.0 -2014-09-01 18:00:00,15270.0 -2014-09-01 19:00:00,15110.0 -2014-09-01 20:00:00,14639.0 -2014-09-01 21:00:00,14326.0 -2014-09-01 22:00:00,14236.0 -2014-09-01 23:00:00,13515.0 -2014-09-02 00:00:00,12511.0 -2014-08-31 01:00:00,11729.0 -2014-08-31 02:00:00,10952.0 -2014-08-31 03:00:00,10328.0 -2014-08-31 04:00:00,9900.0 -2014-08-31 05:00:00,9582.0 -2014-08-31 06:00:00,9486.0 -2014-08-31 07:00:00,9486.0 -2014-08-31 08:00:00,9476.0 -2014-08-31 09:00:00,9809.0 -2014-08-31 10:00:00,10735.0 -2014-08-31 11:00:00,11852.0 -2014-08-31 12:00:00,12905.0 -2014-08-31 13:00:00,13735.0 -2014-08-31 14:00:00,14393.0 -2014-08-31 15:00:00,14895.0 -2014-08-31 16:00:00,15363.0 -2014-08-31 17:00:00,15680.0 -2014-08-31 18:00:00,15774.0 -2014-08-31 19:00:00,15682.0 -2014-08-31 20:00:00,15154.0 -2014-08-31 21:00:00,14702.0 -2014-08-31 22:00:00,14516.0 -2014-08-31 23:00:00,13932.0 -2014-09-01 00:00:00,13051.0 -2014-08-30 01:00:00,13079.0 -2014-08-30 02:00:00,12107.0 -2014-08-30 03:00:00,11419.0 -2014-08-30 04:00:00,10912.0 -2014-08-30 05:00:00,10716.0 -2014-08-30 06:00:00,10548.0 -2014-08-30 07:00:00,10702.0 -2014-08-30 08:00:00,10835.0 -2014-08-30 09:00:00,11320.0 -2014-08-30 10:00:00,12084.0 -2014-08-30 11:00:00,12970.0 -2014-08-30 12:00:00,13522.0 -2014-08-30 13:00:00,13893.0 -2014-08-30 14:00:00,14359.0 -2014-08-30 15:00:00,14673.0 -2014-08-30 16:00:00,15004.0 -2014-08-30 17:00:00,15265.0 -2014-08-30 18:00:00,15323.0 -2014-08-30 19:00:00,15121.0 -2014-08-30 20:00:00,14622.0 -2014-08-30 21:00:00,14208.0 -2014-08-30 22:00:00,14057.0 -2014-08-30 23:00:00,13421.0 -2014-08-31 00:00:00,12616.0 -2014-08-29 01:00:00,11491.0 -2014-08-29 02:00:00,10778.0 -2014-08-29 03:00:00,10285.0 -2014-08-29 04:00:00,9965.0 -2014-08-29 05:00:00,9848.0 -2014-08-29 06:00:00,10007.0 -2014-08-29 07:00:00,10728.0 -2014-08-29 08:00:00,11803.0 -2014-08-29 09:00:00,12617.0 -2014-08-29 10:00:00,13266.0 -2014-08-29 11:00:00,14014.0 -2014-08-29 12:00:00,15063.0 -2014-08-29 13:00:00,15962.0 -2014-08-29 14:00:00,16606.0 -2014-08-29 15:00:00,17264.0 -2014-08-29 16:00:00,17629.0 -2014-08-29 17:00:00,17982.0 -2014-08-29 18:00:00,18139.0 -2014-08-29 19:00:00,17739.0 -2014-08-29 20:00:00,16957.0 -2014-08-29 21:00:00,16547.0 -2014-08-29 22:00:00,16153.0 -2014-08-29 23:00:00,15348.0 -2014-08-30 00:00:00,14213.0 -2014-08-28 01:00:00,11301.0 -2014-08-28 02:00:00,10511.0 -2014-08-28 03:00:00,9991.0 -2014-08-28 04:00:00,9673.0 -2014-08-28 05:00:00,9533.0 -2014-08-28 06:00:00,9646.0 -2014-08-28 07:00:00,10332.0 -2014-08-28 08:00:00,11216.0 -2014-08-28 09:00:00,11932.0 -2014-08-28 10:00:00,12639.0 -2014-08-28 11:00:00,13197.0 -2014-08-28 12:00:00,13750.0 -2014-08-28 13:00:00,14201.0 -2014-08-28 14:00:00,14550.0 -2014-08-28 15:00:00,15005.0 -2014-08-28 16:00:00,15231.0 -2014-08-28 17:00:00,15370.0 -2014-08-28 18:00:00,15247.0 -2014-08-28 19:00:00,14644.0 -2014-08-28 20:00:00,14024.0 -2014-08-28 21:00:00,14099.0 -2014-08-28 22:00:00,14072.0 -2014-08-28 23:00:00,13435.0 -2014-08-29 00:00:00,12478.0 -2014-08-27 01:00:00,12253.0 -2014-08-27 02:00:00,11365.0 -2014-08-27 03:00:00,10711.0 -2014-08-27 04:00:00,10315.0 -2014-08-27 05:00:00,10128.0 -2014-08-27 06:00:00,10249.0 -2014-08-27 07:00:00,10887.0 -2014-08-27 08:00:00,11818.0 -2014-08-27 09:00:00,12725.0 -2014-08-27 10:00:00,13600.0 -2014-08-27 11:00:00,14241.0 -2014-08-27 12:00:00,14906.0 -2014-08-27 13:00:00,15515.0 -2014-08-27 14:00:00,15993.0 -2014-08-27 15:00:00,16536.0 -2014-08-27 16:00:00,16837.0 -2014-08-27 17:00:00,16712.0 -2014-08-27 18:00:00,16279.0 -2014-08-27 19:00:00,15543.0 -2014-08-27 20:00:00,14695.0 -2014-08-27 21:00:00,14371.0 -2014-08-27 22:00:00,14309.0 -2014-08-27 23:00:00,13581.0 -2014-08-28 00:00:00,12427.0 -2014-08-26 01:00:00,12154.0 -2014-08-26 02:00:00,11317.0 -2014-08-26 03:00:00,10785.0 -2014-08-26 04:00:00,10400.0 -2014-08-26 05:00:00,10303.0 -2014-08-26 06:00:00,10455.0 -2014-08-26 07:00:00,11149.0 -2014-08-26 08:00:00,12207.0 -2014-08-26 09:00:00,13149.0 -2014-08-26 10:00:00,14022.0 -2014-08-26 11:00:00,14919.0 -2014-08-26 12:00:00,15347.0 -2014-08-26 13:00:00,15023.0 -2014-08-26 14:00:00,14954.0 -2014-08-26 15:00:00,15658.0 -2014-08-26 16:00:00,16398.0 -2014-08-26 17:00:00,17087.0 -2014-08-26 18:00:00,17515.0 -2014-08-26 19:00:00,17381.0 -2014-08-26 20:00:00,16782.0 -2014-08-26 21:00:00,16215.0 -2014-08-26 22:00:00,15959.0 -2014-08-26 23:00:00,14966.0 -2014-08-27 00:00:00,13604.0 -2014-08-25 01:00:00,13546.0 -2014-08-25 02:00:00,12667.0 -2014-08-25 03:00:00,12051.0 -2014-08-25 04:00:00,11774.0 -2014-08-25 05:00:00,11627.0 -2014-08-25 06:00:00,11845.0 -2014-08-25 07:00:00,12624.0 -2014-08-25 08:00:00,13750.0 -2014-08-25 09:00:00,14893.0 -2014-08-25 10:00:00,15974.0 -2014-08-25 11:00:00,17155.0 -2014-08-25 12:00:00,18276.0 -2014-08-25 13:00:00,18893.0 -2014-08-25 14:00:00,18476.0 -2014-08-25 15:00:00,17458.0 -2014-08-25 16:00:00,16526.0 -2014-08-25 17:00:00,16034.0 -2014-08-25 18:00:00,16040.0 -2014-08-25 19:00:00,15810.0 -2014-08-25 20:00:00,15413.0 -2014-08-25 21:00:00,15284.0 -2014-08-25 22:00:00,15247.0 -2014-08-25 23:00:00,14491.0 -2014-08-26 00:00:00,13360.0 -2014-08-24 01:00:00,11284.0 -2014-08-24 02:00:00,10606.0 -2014-08-24 03:00:00,10122.0 -2014-08-24 04:00:00,9794.0 -2014-08-24 05:00:00,9581.0 -2014-08-24 06:00:00,9545.0 -2014-08-24 07:00:00,9654.0 -2014-08-24 08:00:00,9659.0 -2014-08-24 09:00:00,9937.0 -2014-08-24 10:00:00,10795.0 -2014-08-24 11:00:00,11846.0 -2014-08-24 12:00:00,13042.0 -2014-08-24 13:00:00,14189.0 -2014-08-24 14:00:00,15108.0 -2014-08-24 15:00:00,15839.0 -2014-08-24 16:00:00,16502.0 -2014-08-24 17:00:00,16898.0 -2014-08-24 18:00:00,17129.0 -2014-08-24 19:00:00,17139.0 -2014-08-24 20:00:00,16767.0 -2014-08-24 21:00:00,16392.0 -2014-08-24 22:00:00,16362.0 -2014-08-24 23:00:00,15735.0 -2014-08-25 00:00:00,14626.0 -2014-08-23 01:00:00,13612.0 -2014-08-23 02:00:00,12593.0 -2014-08-23 03:00:00,11825.0 -2014-08-23 04:00:00,11331.0 -2014-08-23 05:00:00,10949.0 -2014-08-23 06:00:00,10824.0 -2014-08-23 07:00:00,10999.0 -2014-08-23 08:00:00,11209.0 -2014-08-23 09:00:00,11939.0 -2014-08-23 10:00:00,13124.0 -2014-08-23 11:00:00,14411.0 -2014-08-23 12:00:00,15389.0 -2014-08-23 13:00:00,16063.0 -2014-08-23 14:00:00,16148.0 -2014-08-23 15:00:00,15666.0 -2014-08-23 16:00:00,14642.0 -2014-08-23 17:00:00,13859.0 -2014-08-23 18:00:00,13389.0 -2014-08-23 19:00:00,13194.0 -2014-08-23 20:00:00,13004.0 -2014-08-23 21:00:00,12974.0 -2014-08-23 22:00:00,13203.0 -2014-08-23 23:00:00,12845.0 -2014-08-24 00:00:00,12099.0 -2014-08-22 01:00:00,12577.0 -2014-08-22 02:00:00,11745.0 -2014-08-22 03:00:00,11223.0 -2014-08-22 04:00:00,10848.0 -2014-08-22 05:00:00,10709.0 -2014-08-22 06:00:00,10840.0 -2014-08-22 07:00:00,11486.0 -2014-08-22 08:00:00,12440.0 -2014-08-22 09:00:00,13523.0 -2014-08-22 10:00:00,14468.0 -2014-08-22 11:00:00,15165.0 -2014-08-22 12:00:00,15747.0 -2014-08-22 13:00:00,15939.0 -2014-08-22 14:00:00,16391.0 -2014-08-22 15:00:00,17250.0 -2014-08-22 16:00:00,17732.0 -2014-08-22 17:00:00,18118.0 -2014-08-22 18:00:00,18253.0 -2014-08-22 19:00:00,17953.0 -2014-08-22 20:00:00,17290.0 -2014-08-22 21:00:00,16803.0 -2014-08-22 22:00:00,16603.0 -2014-08-22 23:00:00,15958.0 -2014-08-23 00:00:00,14843.0 -2014-08-21 01:00:00,13189.0 -2014-08-21 02:00:00,12169.0 -2014-08-21 03:00:00,11465.0 -2014-08-21 04:00:00,10958.0 -2014-08-21 05:00:00,10692.0 -2014-08-21 06:00:00,10852.0 -2014-08-21 07:00:00,11474.0 -2014-08-21 08:00:00,12576.0 -2014-08-21 09:00:00,13531.0 -2014-08-21 10:00:00,13913.0 -2014-08-21 11:00:00,14177.0 -2014-08-21 12:00:00,14619.0 -2014-08-21 13:00:00,15052.0 -2014-08-21 14:00:00,15264.0 -2014-08-21 15:00:00,15295.0 -2014-08-21 16:00:00,15437.0 -2014-08-21 17:00:00,15244.0 -2014-08-21 18:00:00,15437.0 -2014-08-21 19:00:00,15466.0 -2014-08-21 20:00:00,15258.0 -2014-08-21 21:00:00,15147.0 -2014-08-21 22:00:00,15284.0 -2014-08-21 23:00:00,14706.0 -2014-08-22 00:00:00,13736.0 -2014-08-20 01:00:00,12399.0 -2014-08-20 02:00:00,11390.0 -2014-08-20 03:00:00,10680.0 -2014-08-20 04:00:00,10228.0 -2014-08-20 05:00:00,9995.0 -2014-08-20 06:00:00,10067.0 -2014-08-20 07:00:00,10676.0 -2014-08-20 08:00:00,11495.0 -2014-08-20 09:00:00,12495.0 -2014-08-20 10:00:00,13452.0 -2014-08-20 11:00:00,14375.0 -2014-08-20 12:00:00,15378.0 -2014-08-20 13:00:00,16138.0 -2014-08-20 14:00:00,16729.0 -2014-08-20 15:00:00,17354.0 -2014-08-20 16:00:00,17759.0 -2014-08-20 17:00:00,18060.0 -2014-08-20 18:00:00,18150.0 -2014-08-20 19:00:00,17940.0 -2014-08-20 20:00:00,17337.0 -2014-08-20 21:00:00,16785.0 -2014-08-20 22:00:00,16731.0 -2014-08-20 23:00:00,15864.0 -2014-08-21 00:00:00,14496.0 -2014-08-19 01:00:00,12310.0 -2014-08-19 02:00:00,11404.0 -2014-08-19 03:00:00,10752.0 -2014-08-19 04:00:00,10363.0 -2014-08-19 05:00:00,10226.0 -2014-08-19 06:00:00,10374.0 -2014-08-19 07:00:00,11031.0 -2014-08-19 08:00:00,12055.0 -2014-08-19 09:00:00,12897.0 -2014-08-19 10:00:00,13809.0 -2014-08-19 11:00:00,14678.0 -2014-08-19 12:00:00,15387.0 -2014-08-19 13:00:00,15748.0 -2014-08-19 14:00:00,15791.0 -2014-08-19 15:00:00,16021.0 -2014-08-19 16:00:00,16456.0 -2014-08-19 17:00:00,16924.0 -2014-08-19 18:00:00,17227.0 -2014-08-19 19:00:00,17139.0 -2014-08-19 20:00:00,16528.0 -2014-08-19 21:00:00,15960.0 -2014-08-19 22:00:00,15920.0 -2014-08-19 23:00:00,15073.0 -2014-08-20 00:00:00,13754.0 -2014-08-18 01:00:00,9937.0 -2014-08-18 02:00:00,9435.0 -2014-08-18 03:00:00,9084.0 -2014-08-18 04:00:00,8909.0 -2014-08-18 05:00:00,8858.0 -2014-08-18 06:00:00,9109.0 -2014-08-18 07:00:00,9842.0 -2014-08-18 08:00:00,10797.0 -2014-08-18 09:00:00,11605.0 -2014-08-18 10:00:00,12192.0 -2014-08-18 11:00:00,12592.0 -2014-08-18 12:00:00,13027.0 -2014-08-18 13:00:00,13577.0 -2014-08-18 14:00:00,14357.0 -2014-08-18 15:00:00,15132.0 -2014-08-18 16:00:00,15731.0 -2014-08-18 17:00:00,16138.0 -2014-08-18 18:00:00,16344.0 -2014-08-18 19:00:00,16209.0 -2014-08-18 20:00:00,15745.0 -2014-08-18 21:00:00,15348.0 -2014-08-18 22:00:00,15392.0 -2014-08-18 23:00:00,14711.0 -2014-08-19 00:00:00,13542.0 -2014-08-17 01:00:00,10942.0 -2014-08-17 02:00:00,10200.0 -2014-08-17 03:00:00,9615.0 -2014-08-17 04:00:00,9201.0 -2014-08-17 05:00:00,8940.0 -2014-08-17 06:00:00,8814.0 -2014-08-17 07:00:00,8856.0 -2014-08-17 08:00:00,8792.0 -2014-08-17 09:00:00,9104.0 -2014-08-17 10:00:00,9659.0 -2014-08-17 11:00:00,10196.0 -2014-08-17 12:00:00,10627.0 -2014-08-17 13:00:00,10960.0 -2014-08-17 14:00:00,11253.0 -2014-08-17 15:00:00,11436.0 -2014-08-17 16:00:00,11534.0 -2014-08-17 17:00:00,11612.0 -2014-08-17 18:00:00,11697.0 -2014-08-17 19:00:00,11604.0 -2014-08-17 20:00:00,11435.0 -2014-08-17 21:00:00,11349.0 -2014-08-17 22:00:00,11595.0 -2014-08-17 23:00:00,11236.0 -2014-08-18 00:00:00,10616.0 -2014-08-16 01:00:00,10360.0 -2014-08-16 02:00:00,9726.0 -2014-08-16 03:00:00,9191.0 -2014-08-16 04:00:00,8860.0 -2014-08-16 05:00:00,8643.0 -2014-08-16 06:00:00,8626.0 -2014-08-16 07:00:00,8840.0 -2014-08-16 08:00:00,8941.0 -2014-08-16 09:00:00,9540.0 -2014-08-16 10:00:00,10378.0 -2014-08-16 11:00:00,11147.0 -2014-08-16 12:00:00,11827.0 -2014-08-16 13:00:00,12302.0 -2014-08-16 14:00:00,12662.0 -2014-08-16 15:00:00,12871.0 -2014-08-16 16:00:00,13167.0 -2014-08-16 17:00:00,13373.0 -2014-08-16 18:00:00,13511.0 -2014-08-16 19:00:00,13415.0 -2014-08-16 20:00:00,13061.0 -2014-08-16 21:00:00,12781.0 -2014-08-16 22:00:00,12916.0 -2014-08-16 23:00:00,12484.0 -2014-08-17 00:00:00,11738.0 -2014-08-15 01:00:00,9782.0 -2014-08-15 02:00:00,9186.0 -2014-08-15 03:00:00,8750.0 -2014-08-15 04:00:00,8475.0 -2014-08-15 05:00:00,8374.0 -2014-08-15 06:00:00,8500.0 -2014-08-15 07:00:00,9000.0 -2014-08-15 08:00:00,9546.0 -2014-08-15 09:00:00,10385.0 -2014-08-15 10:00:00,11072.0 -2014-08-15 11:00:00,11577.0 -2014-08-15 12:00:00,12001.0 -2014-08-15 13:00:00,12285.0 -2014-08-15 14:00:00,12541.0 -2014-08-15 15:00:00,12873.0 -2014-08-15 16:00:00,13084.0 -2014-08-15 17:00:00,13293.0 -2014-08-15 18:00:00,13404.0 -2014-08-15 19:00:00,13256.0 -2014-08-15 20:00:00,12830.0 -2014-08-15 21:00:00,12421.0 -2014-08-15 22:00:00,12520.0 -2014-08-15 23:00:00,12104.0 -2014-08-16 00:00:00,11267.0 -2014-08-14 01:00:00,10564.0 -2014-08-14 02:00:00,9855.0 -2014-08-14 03:00:00,9378.0 -2014-08-14 04:00:00,9049.0 -2014-08-14 05:00:00,8899.0 -2014-08-14 06:00:00,8988.0 -2014-08-14 07:00:00,9495.0 -2014-08-14 08:00:00,10123.0 -2014-08-14 09:00:00,10916.0 -2014-08-14 10:00:00,11579.0 -2014-08-14 11:00:00,12040.0 -2014-08-14 12:00:00,12456.0 -2014-08-14 13:00:00,12725.0 -2014-08-14 14:00:00,12882.0 -2014-08-14 15:00:00,13131.0 -2014-08-14 16:00:00,13241.0 -2014-08-14 17:00:00,13346.0 -2014-08-14 18:00:00,13268.0 -2014-08-14 19:00:00,12921.0 -2014-08-14 20:00:00,12295.0 -2014-08-14 21:00:00,11823.0 -2014-08-14 22:00:00,11974.0 -2014-08-14 23:00:00,11482.0 -2014-08-15 00:00:00,10679.0 -2014-08-13 01:00:00,9940.0 -2014-08-13 02:00:00,9318.0 -2014-08-13 03:00:00,8928.0 -2014-08-13 04:00:00,8639.0 -2014-08-13 05:00:00,8506.0 -2014-08-13 06:00:00,8640.0 -2014-08-13 07:00:00,9137.0 -2014-08-13 08:00:00,9791.0 -2014-08-13 09:00:00,10672.0 -2014-08-13 10:00:00,11393.0 -2014-08-13 11:00:00,11902.0 -2014-08-13 12:00:00,12414.0 -2014-08-13 13:00:00,12916.0 -2014-08-13 14:00:00,13359.0 -2014-08-13 15:00:00,13852.0 -2014-08-13 16:00:00,14102.0 -2014-08-13 17:00:00,14218.0 -2014-08-13 18:00:00,14194.0 -2014-08-13 19:00:00,13953.0 -2014-08-13 20:00:00,13423.0 -2014-08-13 21:00:00,12984.0 -2014-08-13 22:00:00,13056.0 -2014-08-13 23:00:00,12556.0 -2014-08-14 00:00:00,11559.0 -2014-08-12 01:00:00,11747.0 -2014-08-12 02:00:00,10829.0 -2014-08-12 03:00:00,10246.0 -2014-08-12 04:00:00,9858.0 -2014-08-12 05:00:00,9705.0 -2014-08-12 06:00:00,9756.0 -2014-08-12 07:00:00,10332.0 -2014-08-12 08:00:00,11105.0 -2014-08-12 09:00:00,11622.0 -2014-08-12 10:00:00,11949.0 -2014-08-12 11:00:00,12121.0 -2014-08-12 12:00:00,12271.0 -2014-08-12 13:00:00,12268.0 -2014-08-12 14:00:00,12259.0 -2014-08-12 15:00:00,12270.0 -2014-08-12 16:00:00,12158.0 -2014-08-12 17:00:00,12027.0 -2014-08-12 18:00:00,11948.0 -2014-08-12 19:00:00,11792.0 -2014-08-12 20:00:00,11627.0 -2014-08-12 21:00:00,11564.0 -2014-08-12 22:00:00,11869.0 -2014-08-12 23:00:00,11485.0 -2014-08-13 00:00:00,10753.0 -2014-08-11 01:00:00,11075.0 -2014-08-11 02:00:00,10450.0 -2014-08-11 03:00:00,9964.0 -2014-08-11 04:00:00,9685.0 -2014-08-11 05:00:00,9605.0 -2014-08-11 06:00:00,9864.0 -2014-08-11 07:00:00,10580.0 -2014-08-11 08:00:00,11469.0 -2014-08-11 09:00:00,12261.0 -2014-08-11 10:00:00,12899.0 -2014-08-11 11:00:00,13523.0 -2014-08-11 12:00:00,14158.0 -2014-08-11 13:00:00,14656.0 -2014-08-11 14:00:00,15064.0 -2014-08-11 15:00:00,15404.0 -2014-08-11 16:00:00,15733.0 -2014-08-11 17:00:00,16099.0 -2014-08-11 18:00:00,16294.0 -2014-08-11 19:00:00,16110.0 -2014-08-11 20:00:00,15398.0 -2014-08-11 21:00:00,14855.0 -2014-08-11 22:00:00,14771.0 -2014-08-11 23:00:00,14110.0 -2014-08-12 00:00:00,12966.0 -2014-08-10 01:00:00,10750.0 -2014-08-10 02:00:00,10139.0 -2014-08-10 03:00:00,9669.0 -2014-08-10 04:00:00,9312.0 -2014-08-10 05:00:00,9140.0 -2014-08-10 06:00:00,8975.0 -2014-08-10 07:00:00,8933.0 -2014-08-10 08:00:00,8860.0 -2014-08-10 09:00:00,9180.0 -2014-08-10 10:00:00,9910.0 -2014-08-10 11:00:00,10818.0 -2014-08-10 12:00:00,11605.0 -2014-08-10 13:00:00,12250.0 -2014-08-10 14:00:00,12741.0 -2014-08-10 15:00:00,13129.0 -2014-08-10 16:00:00,13340.0 -2014-08-10 17:00:00,13368.0 -2014-08-10 18:00:00,13287.0 -2014-08-10 19:00:00,13053.0 -2014-08-10 20:00:00,12754.0 -2014-08-10 21:00:00,12650.0 -2014-08-10 22:00:00,12889.0 -2014-08-10 23:00:00,12591.0 -2014-08-11 00:00:00,11903.0 -2014-08-09 01:00:00,11412.0 -2014-08-09 02:00:00,10628.0 -2014-08-09 03:00:00,10073.0 -2014-08-09 04:00:00,9579.0 -2014-08-09 05:00:00,9369.0 -2014-08-09 06:00:00,9209.0 -2014-08-09 07:00:00,9309.0 -2014-08-09 08:00:00,9396.0 -2014-08-09 09:00:00,10061.0 -2014-08-09 10:00:00,10971.0 -2014-08-09 11:00:00,11833.0 -2014-08-09 12:00:00,12612.0 -2014-08-09 13:00:00,13133.0 -2014-08-09 14:00:00,13450.0 -2014-08-09 15:00:00,13556.0 -2014-08-09 16:00:00,13699.0 -2014-08-09 17:00:00,13801.0 -2014-08-09 18:00:00,13852.0 -2014-08-09 19:00:00,13574.0 -2014-08-09 20:00:00,13116.0 -2014-08-09 21:00:00,12494.0 -2014-08-09 22:00:00,12533.0 -2014-08-09 23:00:00,12182.0 -2014-08-10 00:00:00,11475.0 -2014-08-08 01:00:00,11238.0 -2014-08-08 02:00:00,10441.0 -2014-08-08 03:00:00,9877.0 -2014-08-08 04:00:00,9514.0 -2014-08-08 05:00:00,9348.0 -2014-08-08 06:00:00,9456.0 -2014-08-08 07:00:00,10033.0 -2014-08-08 08:00:00,10803.0 -2014-08-08 09:00:00,11680.0 -2014-08-08 10:00:00,12512.0 -2014-08-08 11:00:00,13127.0 -2014-08-08 12:00:00,13728.0 -2014-08-08 13:00:00,14269.0 -2014-08-08 14:00:00,14679.0 -2014-08-08 15:00:00,15125.0 -2014-08-08 16:00:00,15441.0 -2014-08-08 17:00:00,15620.0 -2014-08-08 18:00:00,15541.0 -2014-08-08 19:00:00,15140.0 -2014-08-08 20:00:00,14405.0 -2014-08-08 21:00:00,13730.0 -2014-08-08 22:00:00,13684.0 -2014-08-08 23:00:00,13242.0 -2014-08-09 00:00:00,12376.0 -2014-08-07 01:00:00,10829.0 -2014-08-07 02:00:00,10056.0 -2014-08-07 03:00:00,9556.0 -2014-08-07 04:00:00,9244.0 -2014-08-07 05:00:00,9116.0 -2014-08-07 06:00:00,9273.0 -2014-08-07 07:00:00,9809.0 -2014-08-07 08:00:00,10517.0 -2014-08-07 09:00:00,11405.0 -2014-08-07 10:00:00,12199.0 -2014-08-07 11:00:00,12689.0 -2014-08-07 12:00:00,13367.0 -2014-08-07 13:00:00,13963.0 -2014-08-07 14:00:00,14376.0 -2014-08-07 15:00:00,14771.0 -2014-08-07 16:00:00,15120.0 -2014-08-07 17:00:00,15303.0 -2014-08-07 18:00:00,15256.0 -2014-08-07 19:00:00,15019.0 -2014-08-07 20:00:00,14427.0 -2014-08-07 21:00:00,13782.0 -2014-08-07 22:00:00,13697.0 -2014-08-07 23:00:00,13227.0 -2014-08-08 00:00:00,12237.0 -2014-08-06 01:00:00,11275.0 -2014-08-06 02:00:00,10447.0 -2014-08-06 03:00:00,9855.0 -2014-08-06 04:00:00,9495.0 -2014-08-06 05:00:00,9272.0 -2014-08-06 06:00:00,9402.0 -2014-08-06 07:00:00,9909.0 -2014-08-06 08:00:00,10615.0 -2014-08-06 09:00:00,11492.0 -2014-08-06 10:00:00,12274.0 -2014-08-06 11:00:00,12951.0 -2014-08-06 12:00:00,13687.0 -2014-08-06 13:00:00,14220.0 -2014-08-06 14:00:00,14676.0 -2014-08-06 15:00:00,15083.0 -2014-08-06 16:00:00,15291.0 -2014-08-06 17:00:00,15319.0 -2014-08-06 18:00:00,15059.0 -2014-08-06 19:00:00,14615.0 -2014-08-06 20:00:00,13819.0 -2014-08-06 21:00:00,13313.0 -2014-08-06 22:00:00,13317.0 -2014-08-06 23:00:00,12830.0 -2014-08-07 00:00:00,11849.0 -2014-08-05 01:00:00,11769.0 -2014-08-05 02:00:00,10900.0 -2014-08-05 03:00:00,10367.0 -2014-08-05 04:00:00,9957.0 -2014-08-05 05:00:00,9785.0 -2014-08-05 06:00:00,9904.0 -2014-08-05 07:00:00,10532.0 -2014-08-05 08:00:00,11322.0 -2014-08-05 09:00:00,12188.0 -2014-08-05 10:00:00,12788.0 -2014-08-05 11:00:00,13263.0 -2014-08-05 12:00:00,13744.0 -2014-08-05 13:00:00,14293.0 -2014-08-05 14:00:00,14826.0 -2014-08-05 15:00:00,15327.0 -2014-08-05 16:00:00,15689.0 -2014-08-05 17:00:00,15941.0 -2014-08-05 18:00:00,15913.0 -2014-08-05 19:00:00,15525.0 -2014-08-05 20:00:00,14845.0 -2014-08-05 21:00:00,14169.0 -2014-08-05 22:00:00,14052.0 -2014-08-05 23:00:00,13490.0 -2014-08-06 00:00:00,12423.0 -2014-08-04 01:00:00,11446.0 -2014-08-04 02:00:00,10595.0 -2014-08-04 03:00:00,10063.0 -2014-08-04 04:00:00,9710.0 -2014-08-04 05:00:00,9567.0 -2014-08-04 06:00:00,9703.0 -2014-08-04 07:00:00,10265.0 -2014-08-04 08:00:00,11051.0 -2014-08-04 09:00:00,12122.0 -2014-08-04 10:00:00,13015.0 -2014-08-04 11:00:00,13814.0 -2014-08-04 12:00:00,14774.0 -2014-08-04 13:00:00,15598.0 -2014-08-04 14:00:00,16237.0 -2014-08-04 15:00:00,16736.0 -2014-08-04 16:00:00,16994.0 -2014-08-04 17:00:00,17026.0 -2014-08-04 18:00:00,16792.0 -2014-08-04 19:00:00,16194.0 -2014-08-04 20:00:00,15418.0 -2014-08-04 21:00:00,14893.0 -2014-08-04 22:00:00,14698.0 -2014-08-04 23:00:00,14005.0 -2014-08-05 00:00:00,12876.0 -2014-08-03 01:00:00,10815.0 -2014-08-03 02:00:00,10007.0 -2014-08-03 03:00:00,9494.0 -2014-08-03 04:00:00,9056.0 -2014-08-03 05:00:00,8811.0 -2014-08-03 06:00:00,8640.0 -2014-08-03 07:00:00,8620.0 -2014-08-03 08:00:00,8495.0 -2014-08-03 09:00:00,9023.0 -2014-08-03 10:00:00,9891.0 -2014-08-03 11:00:00,10949.0 -2014-08-03 12:00:00,12014.0 -2014-08-03 13:00:00,12856.0 -2014-08-03 14:00:00,13474.0 -2014-08-03 15:00:00,13880.0 -2014-08-03 16:00:00,14206.0 -2014-08-03 17:00:00,14431.0 -2014-08-03 18:00:00,14589.0 -2014-08-03 19:00:00,14549.0 -2014-08-03 20:00:00,14256.0 -2014-08-03 21:00:00,13793.0 -2014-08-03 22:00:00,13781.0 -2014-08-03 23:00:00,13365.0 -2014-08-04 00:00:00,12437.0 -2014-08-02 01:00:00,10768.0 -2014-08-02 02:00:00,9992.0 -2014-08-02 03:00:00,9445.0 -2014-08-02 04:00:00,9063.0 -2014-08-02 05:00:00,8841.0 -2014-08-02 06:00:00,8768.0 -2014-08-02 07:00:00,8892.0 -2014-08-02 08:00:00,8964.0 -2014-08-02 09:00:00,9590.0 -2014-08-02 10:00:00,10328.0 -2014-08-02 11:00:00,11125.0 -2014-08-02 12:00:00,11786.0 -2014-08-02 13:00:00,12370.0 -2014-08-02 14:00:00,12766.0 -2014-08-02 15:00:00,13204.0 -2014-08-02 16:00:00,13467.0 -2014-08-02 17:00:00,13926.0 -2014-08-02 18:00:00,14174.0 -2014-08-02 19:00:00,14059.0 -2014-08-02 20:00:00,13645.0 -2014-08-02 21:00:00,12994.0 -2014-08-02 22:00:00,12790.0 -2014-08-02 23:00:00,12447.0 -2014-08-03 00:00:00,11624.0 -2014-08-01 01:00:00,11496.0 -2014-08-01 02:00:00,10634.0 -2014-08-01 03:00:00,10005.0 -2014-08-01 04:00:00,9620.0 -2014-08-01 05:00:00,9385.0 -2014-08-01 06:00:00,9439.0 -2014-08-01 07:00:00,9933.0 -2014-08-01 08:00:00,10578.0 -2014-08-01 09:00:00,11538.0 -2014-08-01 10:00:00,12335.0 -2014-08-01 11:00:00,13022.0 -2014-08-01 12:00:00,13875.0 -2014-08-01 13:00:00,14529.0 -2014-08-01 14:00:00,14889.0 -2014-08-01 15:00:00,15152.0 -2014-08-01 16:00:00,15230.0 -2014-08-01 17:00:00,15125.0 -2014-08-01 18:00:00,14836.0 -2014-08-01 19:00:00,14333.0 -2014-08-01 20:00:00,13732.0 -2014-08-01 21:00:00,13188.0 -2014-08-01 22:00:00,13035.0 -2014-08-01 23:00:00,12647.0 -2014-08-02 00:00:00,11696.0 -2014-07-31 01:00:00,10583.0 -2014-07-31 02:00:00,9832.0 -2014-07-31 03:00:00,9344.0 -2014-07-31 04:00:00,9014.0 -2014-07-31 05:00:00,8858.0 -2014-07-31 06:00:00,8988.0 -2014-07-31 07:00:00,9508.0 -2014-07-31 08:00:00,10150.0 -2014-07-31 09:00:00,10983.0 -2014-07-31 10:00:00,11729.0 -2014-07-31 11:00:00,12428.0 -2014-07-31 12:00:00,13149.0 -2014-07-31 13:00:00,13778.0 -2014-07-31 14:00:00,14339.0 -2014-07-31 15:00:00,14828.0 -2014-07-31 16:00:00,15095.0 -2014-07-31 17:00:00,15283.0 -2014-07-31 18:00:00,15322.0 -2014-07-31 19:00:00,15141.0 -2014-07-31 20:00:00,14632.0 -2014-07-31 21:00:00,14087.0 -2014-07-31 22:00:00,14048.0 -2014-07-31 23:00:00,13641.0 -2014-08-01 00:00:00,12625.0 -2014-07-30 01:00:00,10469.0 -2014-07-30 02:00:00,9747.0 -2014-07-30 03:00:00,9251.0 -2014-07-30 04:00:00,8927.0 -2014-07-30 05:00:00,8768.0 -2014-07-30 06:00:00,8890.0 -2014-07-30 07:00:00,9402.0 -2014-07-30 08:00:00,10079.0 -2014-07-30 09:00:00,10996.0 -2014-07-30 10:00:00,11714.0 -2014-07-30 11:00:00,12310.0 -2014-07-30 12:00:00,12905.0 -2014-07-30 13:00:00,13311.0 -2014-07-30 14:00:00,13677.0 -2014-07-30 15:00:00,14027.0 -2014-07-30 16:00:00,14231.0 -2014-07-30 17:00:00,14248.0 -2014-07-30 18:00:00,14143.0 -2014-07-30 19:00:00,13892.0 -2014-07-30 20:00:00,13347.0 -2014-07-30 21:00:00,12870.0 -2014-07-30 22:00:00,12817.0 -2014-07-30 23:00:00,12488.0 -2014-07-31 00:00:00,11575.0 -2014-07-29 01:00:00,9942.0 -2014-07-29 02:00:00,9331.0 -2014-07-29 03:00:00,8965.0 -2014-07-29 04:00:00,8710.0 -2014-07-29 05:00:00,8590.0 -2014-07-29 06:00:00,8759.0 -2014-07-29 07:00:00,9254.0 -2014-07-29 08:00:00,9910.0 -2014-07-29 09:00:00,10734.0 -2014-07-29 10:00:00,11450.0 -2014-07-29 11:00:00,11983.0 -2014-07-29 12:00:00,12585.0 -2014-07-29 13:00:00,12990.0 -2014-07-29 14:00:00,13356.0 -2014-07-29 15:00:00,13663.0 -2014-07-29 16:00:00,13806.0 -2014-07-29 17:00:00,13764.0 -2014-07-29 18:00:00,13644.0 -2014-07-29 19:00:00,13443.0 -2014-07-29 20:00:00,13057.0 -2014-07-29 21:00:00,12684.0 -2014-07-29 22:00:00,12783.0 -2014-07-29 23:00:00,12479.0 -2014-07-30 00:00:00,11525.0 -2014-07-28 01:00:00,10503.0 -2014-07-28 02:00:00,9798.0 -2014-07-28 03:00:00,9320.0 -2014-07-28 04:00:00,8949.0 -2014-07-28 05:00:00,8811.0 -2014-07-28 06:00:00,8945.0 -2014-07-28 07:00:00,9449.0 -2014-07-28 08:00:00,10015.0 -2014-07-28 09:00:00,10915.0 -2014-07-28 10:00:00,11473.0 -2014-07-28 11:00:00,11899.0 -2014-07-28 12:00:00,12245.0 -2014-07-28 13:00:00,12438.0 -2014-07-28 14:00:00,12545.0 -2014-07-28 15:00:00,12657.0 -2014-07-28 16:00:00,12716.0 -2014-07-28 17:00:00,12675.0 -2014-07-28 18:00:00,12567.0 -2014-07-28 19:00:00,12382.0 -2014-07-28 20:00:00,11997.0 -2014-07-28 21:00:00,11674.0 -2014-07-28 22:00:00,11840.0 -2014-07-28 23:00:00,11568.0 -2014-07-29 00:00:00,10830.0 -2014-07-27 01:00:00,12092.0 -2014-07-27 02:00:00,11332.0 -2014-07-27 03:00:00,10611.0 -2014-07-27 04:00:00,10141.0 -2014-07-27 05:00:00,9813.0 -2014-07-27 06:00:00,9680.0 -2014-07-27 07:00:00,9550.0 -2014-07-27 08:00:00,9578.0 -2014-07-27 09:00:00,10202.0 -2014-07-27 10:00:00,11360.0 -2014-07-27 11:00:00,12611.0 -2014-07-27 12:00:00,13779.0 -2014-07-27 13:00:00,14492.0 -2014-07-27 14:00:00,14763.0 -2014-07-27 15:00:00,14878.0 -2014-07-27 16:00:00,14808.0 -2014-07-27 17:00:00,14612.0 -2014-07-27 18:00:00,14283.0 -2014-07-27 19:00:00,13836.0 -2014-07-27 20:00:00,13284.0 -2014-07-27 21:00:00,12600.0 -2014-07-27 22:00:00,12421.0 -2014-07-27 23:00:00,12196.0 -2014-07-28 00:00:00,11434.0 -2014-07-26 01:00:00,10646.0 -2014-07-26 02:00:00,9925.0 -2014-07-26 03:00:00,9513.0 -2014-07-26 04:00:00,9176.0 -2014-07-26 05:00:00,9022.0 -2014-07-26 06:00:00,8978.0 -2014-07-26 07:00:00,9143.0 -2014-07-26 08:00:00,9339.0 -2014-07-26 09:00:00,9876.0 -2014-07-26 10:00:00,10642.0 -2014-07-26 11:00:00,11456.0 -2014-07-26 12:00:00,12311.0 -2014-07-26 13:00:00,12803.0 -2014-07-26 14:00:00,13344.0 -2014-07-26 15:00:00,13839.0 -2014-07-26 16:00:00,14116.0 -2014-07-26 17:00:00,14183.0 -2014-07-26 18:00:00,14274.0 -2014-07-26 19:00:00,14208.0 -2014-07-26 20:00:00,13946.0 -2014-07-26 21:00:00,13672.0 -2014-07-26 22:00:00,13814.0 -2014-07-26 23:00:00,13657.0 -2014-07-27 00:00:00,12997.0 -2014-07-25 01:00:00,10203.0 -2014-07-25 02:00:00,9576.0 -2014-07-25 03:00:00,9114.0 -2014-07-25 04:00:00,8813.0 -2014-07-25 05:00:00,8679.0 -2014-07-25 06:00:00,8802.0 -2014-07-25 07:00:00,9165.0 -2014-07-25 08:00:00,9839.0 -2014-07-25 09:00:00,10634.0 -2014-07-25 10:00:00,11260.0 -2014-07-25 11:00:00,11745.0 -2014-07-25 12:00:00,12050.0 -2014-07-25 13:00:00,12286.0 -2014-07-25 14:00:00,12441.0 -2014-07-25 15:00:00,12560.0 -2014-07-25 16:00:00,12664.0 -2014-07-25 17:00:00,12581.0 -2014-07-25 18:00:00,12491.0 -2014-07-25 19:00:00,12339.0 -2014-07-25 20:00:00,12248.0 -2014-07-25 21:00:00,11967.0 -2014-07-25 22:00:00,12126.0 -2014-07-25 23:00:00,12062.0 -2014-07-26 00:00:00,11404.0 -2014-07-24 01:00:00,10144.0 -2014-07-24 02:00:00,9509.0 -2014-07-24 03:00:00,9093.0 -2014-07-24 04:00:00,8814.0 -2014-07-24 05:00:00,8695.0 -2014-07-24 06:00:00,8807.0 -2014-07-24 07:00:00,9222.0 -2014-07-24 08:00:00,9920.0 -2014-07-24 09:00:00,10882.0 -2014-07-24 10:00:00,11608.0 -2014-07-24 11:00:00,12056.0 -2014-07-24 12:00:00,12468.0 -2014-07-24 13:00:00,12684.0 -2014-07-24 14:00:00,12911.0 -2014-07-24 15:00:00,13160.0 -2014-07-24 16:00:00,13318.0 -2014-07-24 17:00:00,13415.0 -2014-07-24 18:00:00,13463.0 -2014-07-24 19:00:00,13370.0 -2014-07-24 20:00:00,12910.0 -2014-07-24 21:00:00,12303.0 -2014-07-24 22:00:00,12259.0 -2014-07-24 23:00:00,11950.0 -2014-07-25 00:00:00,11116.0 -2014-07-23 01:00:00,15069.0 -2014-07-23 02:00:00,13577.0 -2014-07-23 03:00:00,12397.0 -2014-07-23 04:00:00,11391.0 -2014-07-23 05:00:00,10750.0 -2014-07-23 06:00:00,10575.0 -2014-07-23 07:00:00,10801.0 -2014-07-23 08:00:00,11342.0 -2014-07-23 09:00:00,12199.0 -2014-07-23 10:00:00,12875.0 -2014-07-23 11:00:00,13268.0 -2014-07-23 12:00:00,13629.0 -2014-07-23 13:00:00,13821.0 -2014-07-23 14:00:00,13899.0 -2014-07-23 15:00:00,14022.0 -2014-07-23 16:00:00,13998.0 -2014-07-23 17:00:00,14011.0 -2014-07-23 18:00:00,13915.0 -2014-07-23 19:00:00,13583.0 -2014-07-23 20:00:00,12919.0 -2014-07-23 21:00:00,12291.0 -2014-07-23 22:00:00,12120.0 -2014-07-23 23:00:00,11869.0 -2014-07-24 00:00:00,11051.0 -2014-07-22 01:00:00,13296.0 -2014-07-22 02:00:00,12233.0 -2014-07-22 03:00:00,11414.0 -2014-07-22 04:00:00,10850.0 -2014-07-22 05:00:00,10587.0 -2014-07-22 06:00:00,10628.0 -2014-07-22 07:00:00,11094.0 -2014-07-22 08:00:00,11921.0 -2014-07-22 09:00:00,13166.0 -2014-07-22 10:00:00,14300.0 -2014-07-22 11:00:00,15334.0 -2014-07-22 12:00:00,16404.0 -2014-07-22 13:00:00,17276.0 -2014-07-22 14:00:00,18019.0 -2014-07-22 15:00:00,18783.0 -2014-07-22 16:00:00,19359.0 -2014-07-22 17:00:00,19721.0 -2014-07-22 18:00:00,19673.0 -2014-07-22 19:00:00,19497.0 -2014-07-22 20:00:00,19145.0 -2014-07-22 21:00:00,18658.0 -2014-07-22 22:00:00,18486.0 -2014-07-22 23:00:00,17952.0 -2014-07-23 00:00:00,16665.0 -2014-07-21 01:00:00,11131.0 -2014-07-21 02:00:00,10343.0 -2014-07-21 03:00:00,9801.0 -2014-07-21 04:00:00,9464.0 -2014-07-21 05:00:00,9302.0 -2014-07-21 06:00:00,9508.0 -2014-07-21 07:00:00,10063.0 -2014-07-21 08:00:00,10948.0 -2014-07-21 09:00:00,12055.0 -2014-07-21 10:00:00,13068.0 -2014-07-21 11:00:00,13990.0 -2014-07-21 12:00:00,14857.0 -2014-07-21 13:00:00,15560.0 -2014-07-21 14:00:00,16254.0 -2014-07-21 15:00:00,16937.0 -2014-07-21 16:00:00,17468.0 -2014-07-21 17:00:00,17843.0 -2014-07-21 18:00:00,18066.0 -2014-07-21 19:00:00,18017.0 -2014-07-21 20:00:00,17570.0 -2014-07-21 21:00:00,16862.0 -2014-07-21 22:00:00,16426.0 -2014-07-21 23:00:00,15932.0 -2014-07-22 00:00:00,14677.0 -2014-07-20 01:00:00,10438.0 -2014-07-20 02:00:00,9711.0 -2014-07-20 03:00:00,9229.0 -2014-07-20 04:00:00,8842.0 -2014-07-20 05:00:00,8621.0 -2014-07-20 06:00:00,8516.0 -2014-07-20 07:00:00,8412.0 -2014-07-20 08:00:00,8342.0 -2014-07-20 09:00:00,8781.0 -2014-07-20 10:00:00,9500.0 -2014-07-20 11:00:00,10271.0 -2014-07-20 12:00:00,11134.0 -2014-07-20 13:00:00,11822.0 -2014-07-20 14:00:00,12308.0 -2014-07-20 15:00:00,12764.0 -2014-07-20 16:00:00,13072.0 -2014-07-20 17:00:00,13398.0 -2014-07-20 18:00:00,13611.0 -2014-07-20 19:00:00,13690.0 -2014-07-20 20:00:00,13478.0 -2014-07-20 21:00:00,13060.0 -2014-07-20 22:00:00,12968.0 -2014-07-20 23:00:00,12869.0 -2014-07-21 00:00:00,12082.0 -2014-07-19 01:00:00,10712.0 -2014-07-19 02:00:00,9964.0 -2014-07-19 03:00:00,9405.0 -2014-07-19 04:00:00,9025.0 -2014-07-19 05:00:00,8792.0 -2014-07-19 06:00:00,8715.0 -2014-07-19 07:00:00,8739.0 -2014-07-19 08:00:00,8854.0 -2014-07-19 09:00:00,9500.0 -2014-07-19 10:00:00,10325.0 -2014-07-19 11:00:00,11112.0 -2014-07-19 12:00:00,11732.0 -2014-07-19 13:00:00,12205.0 -2014-07-19 14:00:00,12469.0 -2014-07-19 15:00:00,12619.0 -2014-07-19 16:00:00,12777.0 -2014-07-19 17:00:00,12961.0 -2014-07-19 18:00:00,13023.0 -2014-07-19 19:00:00,12908.0 -2014-07-19 20:00:00,12484.0 -2014-07-19 21:00:00,12080.0 -2014-07-19 22:00:00,12004.0 -2014-07-19 23:00:00,11867.0 -2014-07-20 00:00:00,11195.0 -2014-07-18 01:00:00,10347.0 -2014-07-18 02:00:00,9684.0 -2014-07-18 03:00:00,9207.0 -2014-07-18 04:00:00,8951.0 -2014-07-18 05:00:00,8811.0 -2014-07-18 06:00:00,8936.0 -2014-07-18 07:00:00,9341.0 -2014-07-18 08:00:00,10010.0 -2014-07-18 09:00:00,10908.0 -2014-07-18 10:00:00,11680.0 -2014-07-18 11:00:00,12362.0 -2014-07-18 12:00:00,12968.0 -2014-07-18 13:00:00,13342.0 -2014-07-18 14:00:00,13601.0 -2014-07-18 15:00:00,13904.0 -2014-07-18 16:00:00,14080.0 -2014-07-18 17:00:00,14165.0 -2014-07-18 18:00:00,14128.0 -2014-07-18 19:00:00,13908.0 -2014-07-18 20:00:00,13387.0 -2014-07-18 21:00:00,12860.0 -2014-07-18 22:00:00,12703.0 -2014-07-18 23:00:00,12435.0 -2014-07-19 00:00:00,11623.0 -2014-07-17 01:00:00,9830.0 -2014-07-17 02:00:00,9185.0 -2014-07-17 03:00:00,8773.0 -2014-07-17 04:00:00,8499.0 -2014-07-17 05:00:00,8420.0 -2014-07-17 06:00:00,8538.0 -2014-07-17 07:00:00,8904.0 -2014-07-17 08:00:00,9671.0 -2014-07-17 09:00:00,10532.0 -2014-07-17 10:00:00,11227.0 -2014-07-17 11:00:00,11742.0 -2014-07-17 12:00:00,12205.0 -2014-07-17 13:00:00,12516.0 -2014-07-17 14:00:00,12765.0 -2014-07-17 15:00:00,13053.0 -2014-07-17 16:00:00,13151.0 -2014-07-17 17:00:00,13174.0 -2014-07-17 18:00:00,13221.0 -2014-07-17 19:00:00,13121.0 -2014-07-17 20:00:00,12787.0 -2014-07-17 21:00:00,12368.0 -2014-07-17 22:00:00,12398.0 -2014-07-17 23:00:00,12149.0 -2014-07-18 00:00:00,11299.0 -2014-07-16 01:00:00,9729.0 -2014-07-16 02:00:00,9163.0 -2014-07-16 03:00:00,8799.0 -2014-07-16 04:00:00,8545.0 -2014-07-16 05:00:00,8474.0 -2014-07-16 06:00:00,8602.0 -2014-07-16 07:00:00,8994.0 -2014-07-16 08:00:00,9692.0 -2014-07-16 09:00:00,10520.0 -2014-07-16 10:00:00,11184.0 -2014-07-16 11:00:00,11626.0 -2014-07-16 12:00:00,11991.0 -2014-07-16 13:00:00,12152.0 -2014-07-16 14:00:00,12258.0 -2014-07-16 15:00:00,12420.0 -2014-07-16 16:00:00,12438.0 -2014-07-16 17:00:00,12444.0 -2014-07-16 18:00:00,12412.0 -2014-07-16 19:00:00,12274.0 -2014-07-16 20:00:00,11896.0 -2014-07-16 21:00:00,11571.0 -2014-07-16 22:00:00,11598.0 -2014-07-16 23:00:00,11479.0 -2014-07-17 00:00:00,10710.0 -2014-07-15 01:00:00,10556.0 -2014-07-15 02:00:00,9790.0 -2014-07-15 03:00:00,9274.0 -2014-07-15 04:00:00,8909.0 -2014-07-15 05:00:00,8760.0 -2014-07-15 06:00:00,8868.0 -2014-07-15 07:00:00,9163.0 -2014-07-15 08:00:00,9808.0 -2014-07-15 09:00:00,10631.0 -2014-07-15 10:00:00,11202.0 -2014-07-15 11:00:00,11505.0 -2014-07-15 12:00:00,11842.0 -2014-07-15 13:00:00,12023.0 -2014-07-15 14:00:00,12136.0 -2014-07-15 15:00:00,12261.0 -2014-07-15 16:00:00,12263.0 -2014-07-15 17:00:00,12163.0 -2014-07-15 18:00:00,12061.0 -2014-07-15 19:00:00,11834.0 -2014-07-15 20:00:00,11491.0 -2014-07-15 21:00:00,11283.0 -2014-07-15 22:00:00,11439.0 -2014-07-15 23:00:00,11273.0 -2014-07-16 00:00:00,10569.0 -2014-07-14 01:00:00,11857.0 -2014-07-14 02:00:00,10809.0 -2014-07-14 03:00:00,10144.0 -2014-07-14 04:00:00,9683.0 -2014-07-14 05:00:00,9460.0 -2014-07-14 06:00:00,9552.0 -2014-07-14 07:00:00,10015.0 -2014-07-14 08:00:00,10828.0 -2014-07-14 09:00:00,11792.0 -2014-07-14 10:00:00,12437.0 -2014-07-14 11:00:00,13113.0 -2014-07-14 12:00:00,13622.0 -2014-07-14 13:00:00,14005.0 -2014-07-14 14:00:00,14268.0 -2014-07-14 15:00:00,14769.0 -2014-07-14 16:00:00,15097.0 -2014-07-14 17:00:00,15214.0 -2014-07-14 18:00:00,15161.0 -2014-07-14 19:00:00,14794.0 -2014-07-14 20:00:00,14030.0 -2014-07-14 21:00:00,13298.0 -2014-07-14 22:00:00,12933.0 -2014-07-14 23:00:00,12589.0 -2014-07-15 00:00:00,11572.0 -2014-07-13 01:00:00,12554.0 -2014-07-13 02:00:00,11423.0 -2014-07-13 03:00:00,10694.0 -2014-07-13 04:00:00,10151.0 -2014-07-13 05:00:00,9925.0 -2014-07-13 06:00:00,9687.0 -2014-07-13 07:00:00,9447.0 -2014-07-13 08:00:00,9401.0 -2014-07-13 09:00:00,9827.0 -2014-07-13 10:00:00,10406.0 -2014-07-13 11:00:00,11206.0 -2014-07-13 12:00:00,12113.0 -2014-07-13 13:00:00,12969.0 -2014-07-13 14:00:00,13587.0 -2014-07-13 15:00:00,14091.0 -2014-07-13 16:00:00,14573.0 -2014-07-13 17:00:00,15070.0 -2014-07-13 18:00:00,15316.0 -2014-07-13 19:00:00,15360.0 -2014-07-13 20:00:00,14974.0 -2014-07-13 21:00:00,14546.0 -2014-07-13 22:00:00,14413.0 -2014-07-13 23:00:00,14080.0 -2014-07-14 00:00:00,13095.0 -2014-07-12 01:00:00,11549.0 -2014-07-12 02:00:00,10810.0 -2014-07-12 03:00:00,10313.0 -2014-07-12 04:00:00,9945.0 -2014-07-12 05:00:00,9748.0 -2014-07-12 06:00:00,9691.0 -2014-07-12 07:00:00,9802.0 -2014-07-12 08:00:00,10061.0 -2014-07-12 09:00:00,10635.0 -2014-07-12 10:00:00,11381.0 -2014-07-12 11:00:00,12020.0 -2014-07-12 12:00:00,12426.0 -2014-07-12 13:00:00,12559.0 -2014-07-12 14:00:00,12910.0 -2014-07-12 15:00:00,13163.0 -2014-07-12 16:00:00,13258.0 -2014-07-12 17:00:00,13418.0 -2014-07-12 18:00:00,13639.0 -2014-07-12 19:00:00,13691.0 -2014-07-12 20:00:00,13563.0 -2014-07-12 21:00:00,13588.0 -2014-07-12 22:00:00,13837.0 -2014-07-12 23:00:00,13835.0 -2014-07-13 00:00:00,13386.0 -2014-07-11 01:00:00,10832.0 -2014-07-11 02:00:00,10051.0 -2014-07-11 03:00:00,9513.0 -2014-07-11 04:00:00,9171.0 -2014-07-11 05:00:00,9036.0 -2014-07-11 06:00:00,9099.0 -2014-07-11 07:00:00,9411.0 -2014-07-11 08:00:00,10215.0 -2014-07-11 09:00:00,11250.0 -2014-07-11 10:00:00,12159.0 -2014-07-11 11:00:00,12835.0 -2014-07-11 12:00:00,13495.0 -2014-07-11 13:00:00,13963.0 -2014-07-11 14:00:00,14353.0 -2014-07-11 15:00:00,14690.0 -2014-07-11 16:00:00,14712.0 -2014-07-11 17:00:00,14561.0 -2014-07-11 18:00:00,14233.0 -2014-07-11 19:00:00,13764.0 -2014-07-11 20:00:00,13389.0 -2014-07-11 21:00:00,13143.0 -2014-07-11 22:00:00,13262.0 -2014-07-11 23:00:00,13141.0 -2014-07-12 00:00:00,12400.0 -2014-07-10 01:00:00,10858.0 -2014-07-10 02:00:00,10063.0 -2014-07-10 03:00:00,9536.0 -2014-07-10 04:00:00,9155.0 -2014-07-10 05:00:00,9015.0 -2014-07-10 06:00:00,9115.0 -2014-07-10 07:00:00,9455.0 -2014-07-10 08:00:00,10317.0 -2014-07-10 09:00:00,11380.0 -2014-07-10 10:00:00,12183.0 -2014-07-10 11:00:00,12782.0 -2014-07-10 12:00:00,13326.0 -2014-07-10 13:00:00,13760.0 -2014-07-10 14:00:00,14099.0 -2014-07-10 15:00:00,14472.0 -2014-07-10 16:00:00,14715.0 -2014-07-10 17:00:00,14899.0 -2014-07-10 18:00:00,14942.0 -2014-07-10 19:00:00,14729.0 -2014-07-10 20:00:00,14197.0 -2014-07-10 21:00:00,13509.0 -2014-07-10 22:00:00,13172.0 -2014-07-10 23:00:00,12849.0 -2014-07-11 00:00:00,11894.0 -2014-07-09 01:00:00,11685.0 -2014-07-09 02:00:00,10753.0 -2014-07-09 03:00:00,10115.0 -2014-07-09 04:00:00,9698.0 -2014-07-09 05:00:00,9499.0 -2014-07-09 06:00:00,9566.0 -2014-07-09 07:00:00,9884.0 -2014-07-09 08:00:00,10678.0 -2014-07-09 09:00:00,11712.0 -2014-07-09 10:00:00,12561.0 -2014-07-09 11:00:00,13264.0 -2014-07-09 12:00:00,13869.0 -2014-07-09 13:00:00,14179.0 -2014-07-09 14:00:00,14410.0 -2014-07-09 15:00:00,14670.0 -2014-07-09 16:00:00,14839.0 -2014-07-09 17:00:00,14979.0 -2014-07-09 18:00:00,14999.0 -2014-07-09 19:00:00,14817.0 -2014-07-09 20:00:00,14263.0 -2014-07-09 21:00:00,13611.0 -2014-07-09 22:00:00,13184.0 -2014-07-09 23:00:00,12902.0 -2014-07-10 00:00:00,11927.0 -2014-07-08 01:00:00,13452.0 -2014-07-08 02:00:00,12421.0 -2014-07-08 03:00:00,11754.0 -2014-07-08 04:00:00,11227.0 -2014-07-08 05:00:00,11056.0 -2014-07-08 06:00:00,11133.0 -2014-07-08 07:00:00,11666.0 -2014-07-08 08:00:00,12341.0 -2014-07-08 09:00:00,13049.0 -2014-07-08 10:00:00,13820.0 -2014-07-08 11:00:00,14502.0 -2014-07-08 12:00:00,15219.0 -2014-07-08 13:00:00,15735.0 -2014-07-08 14:00:00,16003.0 -2014-07-08 15:00:00,16254.0 -2014-07-08 16:00:00,16413.0 -2014-07-08 17:00:00,16526.0 -2014-07-08 18:00:00,16502.0 -2014-07-08 19:00:00,16261.0 -2014-07-08 20:00:00,15631.0 -2014-07-08 21:00:00,14894.0 -2014-07-08 22:00:00,14349.0 -2014-07-08 23:00:00,13991.0 -2014-07-09 00:00:00,12895.0 -2014-07-07 01:00:00,12938.0 -2014-07-07 02:00:00,12128.0 -2014-07-07 03:00:00,11409.0 -2014-07-07 04:00:00,10815.0 -2014-07-07 05:00:00,10547.0 -2014-07-07 06:00:00,10583.0 -2014-07-07 07:00:00,11105.0 -2014-07-07 08:00:00,12125.0 -2014-07-07 09:00:00,13471.0 -2014-07-07 10:00:00,14494.0 -2014-07-07 11:00:00,15551.0 -2014-07-07 12:00:00,16483.0 -2014-07-07 13:00:00,17187.0 -2014-07-07 14:00:00,17695.0 -2014-07-07 15:00:00,18109.0 -2014-07-07 16:00:00,18342.0 -2014-07-07 17:00:00,18489.0 -2014-07-07 18:00:00,18544.0 -2014-07-07 19:00:00,18431.0 -2014-07-07 20:00:00,17887.0 -2014-07-07 21:00:00,17107.0 -2014-07-07 22:00:00,16563.0 -2014-07-07 23:00:00,16073.0 -2014-07-08 00:00:00,14783.0 -2014-07-06 01:00:00,9832.0 -2014-07-06 02:00:00,9225.0 -2014-07-06 03:00:00,8799.0 -2014-07-06 04:00:00,8490.0 -2014-07-06 05:00:00,8241.0 -2014-07-06 06:00:00,8214.0 -2014-07-06 07:00:00,8025.0 -2014-07-06 08:00:00,8042.0 -2014-07-06 09:00:00,8468.0 -2014-07-06 10:00:00,9002.0 -2014-07-06 11:00:00,9992.0 -2014-07-06 12:00:00,10948.0 -2014-07-06 13:00:00,11953.0 -2014-07-06 14:00:00,12550.0 -2014-07-06 15:00:00,13160.0 -2014-07-06 16:00:00,13943.0 -2014-07-06 17:00:00,14731.0 -2014-07-06 18:00:00,15167.0 -2014-07-06 19:00:00,15224.0 -2014-07-06 20:00:00,15006.0 -2014-07-06 21:00:00,14700.0 -2014-07-06 22:00:00,14598.0 -2014-07-06 23:00:00,14607.0 -2014-07-07 00:00:00,13860.0 -2014-07-05 01:00:00,9356.0 -2014-07-05 02:00:00,8816.0 -2014-07-05 03:00:00,8351.0 -2014-07-05 04:00:00,8067.0 -2014-07-05 05:00:00,7979.0 -2014-07-05 06:00:00,7918.0 -2014-07-05 07:00:00,7918.0 -2014-07-05 08:00:00,8038.0 -2014-07-05 09:00:00,8677.0 -2014-07-05 10:00:00,9373.0 -2014-07-05 11:00:00,10008.0 -2014-07-05 12:00:00,10608.0 -2014-07-05 13:00:00,10916.0 -2014-07-05 14:00:00,11147.0 -2014-07-05 15:00:00,11174.0 -2014-07-05 16:00:00,11240.0 -2014-07-05 17:00:00,11266.0 -2014-07-05 18:00:00,11314.0 -2014-07-05 19:00:00,11164.0 -2014-07-05 20:00:00,10904.0 -2014-07-05 21:00:00,10739.0 -2014-07-05 22:00:00,10938.0 -2014-07-05 23:00:00,10879.0 -2014-07-06 00:00:00,10441.0 -2014-07-04 01:00:00,9654.0 -2014-07-04 02:00:00,8972.0 -2014-07-04 03:00:00,8534.0 -2014-07-04 04:00:00,8232.0 -2014-07-04 05:00:00,8036.0 -2014-07-04 06:00:00,7992.0 -2014-07-04 07:00:00,7861.0 -2014-07-04 08:00:00,7925.0 -2014-07-04 09:00:00,8364.0 -2014-07-04 10:00:00,8906.0 -2014-07-04 11:00:00,9445.0 -2014-07-04 12:00:00,9927.0 -2014-07-04 13:00:00,10311.0 -2014-07-04 14:00:00,10606.0 -2014-07-04 15:00:00,10848.0 -2014-07-04 16:00:00,11040.0 -2014-07-04 17:00:00,11246.0 -2014-07-04 18:00:00,11364.0 -2014-07-04 19:00:00,11334.0 -2014-07-04 20:00:00,11103.0 -2014-07-04 21:00:00,10637.0 -2014-07-04 22:00:00,10262.0 -2014-07-04 23:00:00,10128.0 -2014-07-05 00:00:00,9767.0 -2014-07-03 01:00:00,9634.0 -2014-07-03 02:00:00,9086.0 -2014-07-03 03:00:00,8749.0 -2014-07-03 04:00:00,8523.0 -2014-07-03 05:00:00,8451.0 -2014-07-03 06:00:00,8542.0 -2014-07-03 07:00:00,8861.0 -2014-07-03 08:00:00,9460.0 -2014-07-03 09:00:00,10335.0 -2014-07-03 10:00:00,11057.0 -2014-07-03 11:00:00,11554.0 -2014-07-03 12:00:00,12001.0 -2014-07-03 13:00:00,12241.0 -2014-07-03 14:00:00,12293.0 -2014-07-03 15:00:00,12446.0 -2014-07-03 16:00:00,12493.0 -2014-07-03 17:00:00,12525.0 -2014-07-03 18:00:00,12501.0 -2014-07-03 19:00:00,12375.0 -2014-07-03 20:00:00,11983.0 -2014-07-03 21:00:00,11570.0 -2014-07-03 22:00:00,11321.0 -2014-07-03 23:00:00,11141.0 -2014-07-04 00:00:00,10443.0 -2014-07-02 01:00:00,11802.0 -2014-07-02 02:00:00,10843.0 -2014-07-02 03:00:00,10200.0 -2014-07-02 04:00:00,9682.0 -2014-07-02 05:00:00,9402.0 -2014-07-02 06:00:00,9383.0 -2014-07-02 07:00:00,9656.0 -2014-07-02 08:00:00,10400.0 -2014-07-02 09:00:00,11271.0 -2014-07-02 10:00:00,12007.0 -2014-07-02 11:00:00,12488.0 -2014-07-02 12:00:00,12863.0 -2014-07-02 13:00:00,13020.0 -2014-07-02 14:00:00,12939.0 -2014-07-02 15:00:00,12815.0 -2014-07-02 16:00:00,12486.0 -2014-07-02 17:00:00,12172.0 -2014-07-02 18:00:00,11887.0 -2014-07-02 19:00:00,11632.0 -2014-07-02 20:00:00,11281.0 -2014-07-02 21:00:00,11181.0 -2014-07-02 22:00:00,11346.0 -2014-07-02 23:00:00,11148.0 -2014-07-03 00:00:00,10453.0 -2014-07-01 01:00:00,11331.0 -2014-07-01 02:00:00,10522.0 -2014-07-01 03:00:00,10003.0 -2014-07-01 04:00:00,9670.0 -2014-07-01 05:00:00,9534.0 -2014-07-01 06:00:00,9609.0 -2014-07-01 07:00:00,10092.0 -2014-07-01 08:00:00,10921.0 -2014-07-01 09:00:00,11981.0 -2014-07-01 10:00:00,12967.0 -2014-07-01 11:00:00,13727.0 -2014-07-01 12:00:00,14367.0 -2014-07-01 13:00:00,14815.0 -2014-07-01 14:00:00,15187.0 -2014-07-01 15:00:00,15519.0 -2014-07-01 16:00:00,15584.0 -2014-07-01 17:00:00,15702.0 -2014-07-01 18:00:00,15686.0 -2014-07-01 19:00:00,15486.0 -2014-07-01 20:00:00,15028.0 -2014-07-01 21:00:00,14474.0 -2014-07-01 22:00:00,14115.0 -2014-07-01 23:00:00,13879.0 -2014-07-02 00:00:00,12931.0 -2014-06-30 01:00:00,13495.0 -2014-06-30 02:00:00,12544.0 -2014-06-30 03:00:00,11884.0 -2014-06-30 04:00:00,11405.0 -2014-06-30 05:00:00,11058.0 -2014-06-30 06:00:00,11123.0 -2014-06-30 07:00:00,11655.0 -2014-06-30 08:00:00,12498.0 -2014-06-30 09:00:00,13355.0 -2014-06-30 10:00:00,14256.0 -2014-06-30 11:00:00,15010.0 -2014-06-30 12:00:00,15593.0 -2014-06-30 13:00:00,15958.0 -2014-06-30 14:00:00,16591.0 -2014-06-30 15:00:00,17556.0 -2014-06-30 16:00:00,18422.0 -2014-06-30 17:00:00,18874.0 -2014-06-30 18:00:00,18483.0 -2014-06-30 19:00:00,18012.0 -2014-06-30 20:00:00,17542.0 -2014-06-30 21:00:00,16111.0 -2014-06-30 22:00:00,15059.0 -2014-06-30 23:00:00,14189.0 -2014-07-01 00:00:00,12568.0 -2014-06-29 01:00:00,12791.0 -2014-06-29 02:00:00,11989.0 -2014-06-29 03:00:00,11367.0 -2014-06-29 04:00:00,10897.0 -2014-06-29 05:00:00,10582.0 -2014-06-29 06:00:00,10261.0 -2014-06-29 07:00:00,9871.0 -2014-06-29 08:00:00,9814.0 -2014-06-29 09:00:00,10363.0 -2014-06-29 10:00:00,11095.0 -2014-06-29 11:00:00,12025.0 -2014-06-29 12:00:00,13015.0 -2014-06-29 13:00:00,13869.0 -2014-06-29 14:00:00,14463.0 -2014-06-29 15:00:00,15017.0 -2014-06-29 16:00:00,15447.0 -2014-06-29 17:00:00,15868.0 -2014-06-29 18:00:00,16230.0 -2014-06-29 19:00:00,16378.0 -2014-06-29 20:00:00,16231.0 -2014-06-29 21:00:00,15850.0 -2014-06-29 22:00:00,15497.0 -2014-06-29 23:00:00,15404.0 -2014-06-30 00:00:00,14515.0 -2014-06-28 01:00:00,13192.0 -2014-06-28 02:00:00,12136.0 -2014-06-28 03:00:00,11427.0 -2014-06-28 04:00:00,10780.0 -2014-06-28 05:00:00,10502.0 -2014-06-28 06:00:00,10339.0 -2014-06-28 07:00:00,10355.0 -2014-06-28 08:00:00,10691.0 -2014-06-28 09:00:00,11441.0 -2014-06-28 10:00:00,12491.0 -2014-06-28 11:00:00,13601.0 -2014-06-28 12:00:00,14514.0 -2014-06-28 13:00:00,15379.0 -2014-06-28 14:00:00,15935.0 -2014-06-28 15:00:00,16097.0 -2014-06-28 16:00:00,16357.0 -2014-06-28 17:00:00,16540.0 -2014-06-28 18:00:00,16687.0 -2014-06-28 19:00:00,16505.0 -2014-06-28 20:00:00,15902.0 -2014-06-28 21:00:00,15083.0 -2014-06-28 22:00:00,14614.0 -2014-06-28 23:00:00,14435.0 -2014-06-29 00:00:00,13655.0 -2014-06-27 01:00:00,10649.0 -2014-06-27 02:00:00,9886.0 -2014-06-27 03:00:00,9381.0 -2014-06-27 04:00:00,9125.0 -2014-06-27 05:00:00,8996.0 -2014-06-27 06:00:00,9137.0 -2014-06-27 07:00:00,9538.0 -2014-06-27 08:00:00,10320.0 -2014-06-27 09:00:00,11346.0 -2014-06-27 10:00:00,12345.0 -2014-06-27 11:00:00,13238.0 -2014-06-27 12:00:00,14211.0 -2014-06-27 13:00:00,15053.0 -2014-06-27 14:00:00,15760.0 -2014-06-27 15:00:00,16401.0 -2014-06-27 16:00:00,16888.0 -2014-06-27 17:00:00,17154.0 -2014-06-27 18:00:00,17251.0 -2014-06-27 19:00:00,17086.0 -2014-06-27 20:00:00,16507.0 -2014-06-27 21:00:00,15861.0 -2014-06-27 22:00:00,15567.0 -2014-06-27 23:00:00,15299.0 -2014-06-28 00:00:00,14323.0 -2014-06-26 01:00:00,10510.0 -2014-06-26 02:00:00,9817.0 -2014-06-26 03:00:00,9358.0 -2014-06-26 04:00:00,9106.0 -2014-06-26 05:00:00,8971.0 -2014-06-26 06:00:00,9088.0 -2014-06-26 07:00:00,9515.0 -2014-06-26 08:00:00,10249.0 -2014-06-26 09:00:00,11085.0 -2014-06-26 10:00:00,11578.0 -2014-06-26 11:00:00,11946.0 -2014-06-26 12:00:00,12386.0 -2014-06-26 13:00:00,12771.0 -2014-06-26 14:00:00,13199.0 -2014-06-26 15:00:00,13728.0 -2014-06-26 16:00:00,14150.0 -2014-06-26 17:00:00,14488.0 -2014-06-26 18:00:00,14658.0 -2014-06-26 19:00:00,14519.0 -2014-06-26 20:00:00,13925.0 -2014-06-26 21:00:00,13254.0 -2014-06-26 22:00:00,12925.0 -2014-06-26 23:00:00,12618.0 -2014-06-27 00:00:00,11644.0 -2014-06-25 01:00:00,12388.0 -2014-06-25 02:00:00,11385.0 -2014-06-25 03:00:00,10669.0 -2014-06-25 04:00:00,10189.0 -2014-06-25 05:00:00,9870.0 -2014-06-25 06:00:00,9909.0 -2014-06-25 07:00:00,10238.0 -2014-06-25 08:00:00,11000.0 -2014-06-25 09:00:00,11738.0 -2014-06-25 10:00:00,12398.0 -2014-06-25 11:00:00,12947.0 -2014-06-25 12:00:00,13778.0 -2014-06-25 13:00:00,14447.0 -2014-06-25 14:00:00,14970.0 -2014-06-25 15:00:00,15362.0 -2014-06-25 16:00:00,15130.0 -2014-06-25 17:00:00,14720.0 -2014-06-25 18:00:00,14271.0 -2014-06-25 19:00:00,13568.0 -2014-06-25 20:00:00,12895.0 -2014-06-25 21:00:00,12415.0 -2014-06-25 22:00:00,12199.0 -2014-06-25 23:00:00,12322.0 -2014-06-26 00:00:00,11434.0 -2014-06-24 01:00:00,12153.0 -2014-06-24 02:00:00,11247.0 -2014-06-24 03:00:00,10632.0 -2014-06-24 04:00:00,10263.0 -2014-06-24 05:00:00,10078.0 -2014-06-24 06:00:00,10224.0 -2014-06-24 07:00:00,10745.0 -2014-06-24 08:00:00,11683.0 -2014-06-24 09:00:00,12603.0 -2014-06-24 10:00:00,13325.0 -2014-06-24 11:00:00,14023.0 -2014-06-24 12:00:00,14951.0 -2014-06-24 13:00:00,15595.0 -2014-06-24 14:00:00,16245.0 -2014-06-24 15:00:00,16786.0 -2014-06-24 16:00:00,17241.0 -2014-06-24 17:00:00,17563.0 -2014-06-24 18:00:00,17684.0 -2014-06-24 19:00:00,17405.0 -2014-06-24 20:00:00,16585.0 -2014-06-24 21:00:00,15735.0 -2014-06-24 22:00:00,15262.0 -2014-06-24 23:00:00,14791.0 -2014-06-25 00:00:00,13646.0 -2014-06-23 01:00:00,10676.0 -2014-06-23 02:00:00,9951.0 -2014-06-23 03:00:00,9566.0 -2014-06-23 04:00:00,9267.0 -2014-06-23 05:00:00,9225.0 -2014-06-23 06:00:00,9470.0 -2014-06-23 07:00:00,10096.0 -2014-06-23 08:00:00,11072.0 -2014-06-23 09:00:00,12031.0 -2014-06-23 10:00:00,12643.0 -2014-06-23 11:00:00,13067.0 -2014-06-23 12:00:00,13519.0 -2014-06-23 13:00:00,13993.0 -2014-06-23 14:00:00,14316.0 -2014-06-23 15:00:00,14673.0 -2014-06-23 16:00:00,15165.0 -2014-06-23 17:00:00,15545.0 -2014-06-23 18:00:00,15754.0 -2014-06-23 19:00:00,15760.0 -2014-06-23 20:00:00,15387.0 -2014-06-23 21:00:00,14864.0 -2014-06-23 22:00:00,14636.0 -2014-06-23 23:00:00,14379.0 -2014-06-24 00:00:00,13362.0 -2014-06-22 01:00:00,10466.0 -2014-06-22 02:00:00,9845.0 -2014-06-22 03:00:00,9337.0 -2014-06-22 04:00:00,8977.0 -2014-06-22 05:00:00,8743.0 -2014-06-22 06:00:00,8654.0 -2014-06-22 07:00:00,8505.0 -2014-06-22 08:00:00,8566.0 -2014-06-22 09:00:00,8905.0 -2014-06-22 10:00:00,9554.0 -2014-06-22 11:00:00,10309.0 -2014-06-22 12:00:00,11246.0 -2014-06-22 13:00:00,11990.0 -2014-06-22 14:00:00,12690.0 -2014-06-22 15:00:00,13187.0 -2014-06-22 16:00:00,13583.0 -2014-06-22 17:00:00,13832.0 -2014-06-22 18:00:00,14060.0 -2014-06-22 19:00:00,13926.0 -2014-06-22 20:00:00,13457.0 -2014-06-22 21:00:00,12832.0 -2014-06-22 22:00:00,12687.0 -2014-06-22 23:00:00,12431.0 -2014-06-23 00:00:00,11619.0 -2014-06-21 01:00:00,11285.0 -2014-06-21 02:00:00,10430.0 -2014-06-21 03:00:00,9862.0 -2014-06-21 04:00:00,9449.0 -2014-06-21 05:00:00,9196.0 -2014-06-21 06:00:00,9120.0 -2014-06-21 07:00:00,9106.0 -2014-06-21 08:00:00,9248.0 -2014-06-21 09:00:00,9813.0 -2014-06-21 10:00:00,10361.0 -2014-06-21 11:00:00,11049.0 -2014-06-21 12:00:00,11714.0 -2014-06-21 13:00:00,12406.0 -2014-06-21 14:00:00,13124.0 -2014-06-21 15:00:00,13631.0 -2014-06-21 16:00:00,13847.0 -2014-06-21 17:00:00,14090.0 -2014-06-21 18:00:00,13909.0 -2014-06-21 19:00:00,13415.0 -2014-06-21 20:00:00,12933.0 -2014-06-21 21:00:00,12314.0 -2014-06-21 22:00:00,12073.0 -2014-06-21 23:00:00,11890.0 -2014-06-22 00:00:00,11263.0 -2014-06-20 01:00:00,11336.0 -2014-06-20 02:00:00,10639.0 -2014-06-20 03:00:00,10145.0 -2014-06-20 04:00:00,9832.0 -2014-06-20 05:00:00,9718.0 -2014-06-20 06:00:00,9869.0 -2014-06-20 07:00:00,10335.0 -2014-06-20 08:00:00,11246.0 -2014-06-20 09:00:00,12193.0 -2014-06-20 10:00:00,12996.0 -2014-06-20 11:00:00,13740.0 -2014-06-20 12:00:00,14511.0 -2014-06-20 13:00:00,15121.0 -2014-06-20 14:00:00,15590.0 -2014-06-20 15:00:00,16121.0 -2014-06-20 16:00:00,16487.0 -2014-06-20 17:00:00,16766.0 -2014-06-20 18:00:00,16775.0 -2014-06-20 19:00:00,16390.0 -2014-06-20 20:00:00,15590.0 -2014-06-20 21:00:00,14736.0 -2014-06-20 22:00:00,13918.0 -2014-06-20 23:00:00,13410.0 -2014-06-21 00:00:00,12333.0 -2014-06-19 01:00:00,10874.0 -2014-06-19 02:00:00,10162.0 -2014-06-19 03:00:00,9693.0 -2014-06-19 04:00:00,9361.0 -2014-06-19 05:00:00,9195.0 -2014-06-19 06:00:00,9290.0 -2014-06-19 07:00:00,9684.0 -2014-06-19 08:00:00,10466.0 -2014-06-19 09:00:00,11291.0 -2014-06-19 10:00:00,11909.0 -2014-06-19 11:00:00,12407.0 -2014-06-19 12:00:00,12847.0 -2014-06-19 13:00:00,13401.0 -2014-06-19 14:00:00,14069.0 -2014-06-19 15:00:00,14718.0 -2014-06-19 16:00:00,15164.0 -2014-06-19 17:00:00,15399.0 -2014-06-19 18:00:00,15282.0 -2014-06-19 19:00:00,14954.0 -2014-06-19 20:00:00,14254.0 -2014-06-19 21:00:00,13834.0 -2014-06-19 22:00:00,13848.0 -2014-06-19 23:00:00,13397.0 -2014-06-20 00:00:00,12470.0 -2014-06-18 01:00:00,14419.0 -2014-06-18 02:00:00,13445.0 -2014-06-18 03:00:00,12789.0 -2014-06-18 04:00:00,12346.0 -2014-06-18 05:00:00,12082.0 -2014-06-18 06:00:00,12098.0 -2014-06-18 07:00:00,12508.0 -2014-06-18 08:00:00,13404.0 -2014-06-18 09:00:00,14557.0 -2014-06-18 10:00:00,15428.0 -2014-06-18 11:00:00,15970.0 -2014-06-18 12:00:00,16361.0 -2014-06-18 13:00:00,15763.0 -2014-06-18 14:00:00,14905.0 -2014-06-18 15:00:00,15019.0 -2014-06-18 16:00:00,15348.0 -2014-06-18 17:00:00,15433.0 -2014-06-18 18:00:00,15440.0 -2014-06-18 19:00:00,15266.0 -2014-06-18 20:00:00,14651.0 -2014-06-18 21:00:00,14045.0 -2014-06-18 22:00:00,13528.0 -2014-06-18 23:00:00,12856.0 -2014-06-19 00:00:00,11893.0 -2014-06-17 01:00:00,13950.0 -2014-06-17 02:00:00,13012.0 -2014-06-17 03:00:00,12380.0 -2014-06-17 04:00:00,11882.0 -2014-06-17 05:00:00,11601.0 -2014-06-17 06:00:00,11641.0 -2014-06-17 07:00:00,12226.0 -2014-06-17 08:00:00,13152.0 -2014-06-17 09:00:00,14090.0 -2014-06-17 10:00:00,14702.0 -2014-06-17 11:00:00,15197.0 -2014-06-17 12:00:00,16061.0 -2014-06-17 13:00:00,16780.0 -2014-06-17 14:00:00,17387.0 -2014-06-17 15:00:00,18011.0 -2014-06-17 16:00:00,18592.0 -2014-06-17 17:00:00,19125.0 -2014-06-17 18:00:00,19412.0 -2014-06-17 19:00:00,19275.0 -2014-06-17 20:00:00,18758.0 -2014-06-17 21:00:00,18032.0 -2014-06-17 22:00:00,17563.0 -2014-06-17 23:00:00,17075.0 -2014-06-18 00:00:00,15732.0 -2014-06-16 01:00:00,11594.0 -2014-06-16 02:00:00,10759.0 -2014-06-16 03:00:00,10169.0 -2014-06-16 04:00:00,9752.0 -2014-06-16 05:00:00,9565.0 -2014-06-16 06:00:00,9598.0 -2014-06-16 07:00:00,10001.0 -2014-06-16 08:00:00,11092.0 -2014-06-16 09:00:00,12436.0 -2014-06-16 10:00:00,13642.0 -2014-06-16 11:00:00,14628.0 -2014-06-16 12:00:00,15505.0 -2014-06-16 13:00:00,16121.0 -2014-06-16 14:00:00,16650.0 -2014-06-16 15:00:00,17170.0 -2014-06-16 16:00:00,17621.0 -2014-06-16 17:00:00,17940.0 -2014-06-16 18:00:00,18122.0 -2014-06-16 19:00:00,17933.0 -2014-06-16 20:00:00,17159.0 -2014-06-16 21:00:00,16571.0 -2014-06-16 22:00:00,16499.0 -2014-06-16 23:00:00,16241.0 -2014-06-17 00:00:00,15188.0 -2014-06-15 01:00:00,9563.0 -2014-06-15 02:00:00,8943.0 -2014-06-15 03:00:00,8536.0 -2014-06-15 04:00:00,8223.0 -2014-06-15 05:00:00,8015.0 -2014-06-15 06:00:00,7934.0 -2014-06-15 07:00:00,7725.0 -2014-06-15 08:00:00,7861.0 -2014-06-15 09:00:00,8314.0 -2014-06-15 10:00:00,8967.0 -2014-06-15 11:00:00,9522.0 -2014-06-15 12:00:00,10078.0 -2014-06-15 13:00:00,10542.0 -2014-06-15 14:00:00,10937.0 -2014-06-15 15:00:00,11317.0 -2014-06-15 16:00:00,11784.0 -2014-06-15 17:00:00,12108.0 -2014-06-15 18:00:00,12461.0 -2014-06-15 19:00:00,12787.0 -2014-06-15 20:00:00,12966.0 -2014-06-15 21:00:00,12923.0 -2014-06-15 22:00:00,13049.0 -2014-06-15 23:00:00,13206.0 -2014-06-16 00:00:00,12570.0 -2014-06-14 01:00:00,9667.0 -2014-06-14 02:00:00,9013.0 -2014-06-14 03:00:00,8549.0 -2014-06-14 04:00:00,8287.0 -2014-06-14 05:00:00,8123.0 -2014-06-14 06:00:00,8127.0 -2014-06-14 07:00:00,8047.0 -2014-06-14 08:00:00,8385.0 -2014-06-14 09:00:00,8958.0 -2014-06-14 10:00:00,9596.0 -2014-06-14 11:00:00,10117.0 -2014-06-14 12:00:00,10564.0 -2014-06-14 13:00:00,10796.0 -2014-06-14 14:00:00,10941.0 -2014-06-14 15:00:00,10954.0 -2014-06-14 16:00:00,11068.0 -2014-06-14 17:00:00,11204.0 -2014-06-14 18:00:00,11362.0 -2014-06-14 19:00:00,11349.0 -2014-06-14 20:00:00,11152.0 -2014-06-14 21:00:00,10866.0 -2014-06-14 22:00:00,10780.0 -2014-06-14 23:00:00,10796.0 -2014-06-15 00:00:00,10234.0 -2014-06-13 01:00:00,10162.0 -2014-06-13 02:00:00,9404.0 -2014-06-13 03:00:00,8895.0 -2014-06-13 04:00:00,8588.0 -2014-06-13 05:00:00,8419.0 -2014-06-13 06:00:00,8515.0 -2014-06-13 07:00:00,8759.0 -2014-06-13 08:00:00,9527.0 -2014-06-13 09:00:00,10394.0 -2014-06-13 10:00:00,11009.0 -2014-06-13 11:00:00,11384.0 -2014-06-13 12:00:00,11742.0 -2014-06-13 13:00:00,11900.0 -2014-06-13 14:00:00,11995.0 -2014-06-13 15:00:00,12127.0 -2014-06-13 16:00:00,12128.0 -2014-06-13 17:00:00,12156.0 -2014-06-13 18:00:00,12148.0 -2014-06-13 19:00:00,12000.0 -2014-06-13 20:00:00,11687.0 -2014-06-13 21:00:00,11287.0 -2014-06-13 22:00:00,11238.0 -2014-06-13 23:00:00,11168.0 -2014-06-14 00:00:00,10416.0 -2014-06-12 01:00:00,9665.0 -2014-06-12 02:00:00,9116.0 -2014-06-12 03:00:00,8748.0 -2014-06-12 04:00:00,8510.0 -2014-06-12 05:00:00,8435.0 -2014-06-12 06:00:00,8609.0 -2014-06-12 07:00:00,9094.0 -2014-06-12 08:00:00,9937.0 -2014-06-12 09:00:00,10822.0 -2014-06-12 10:00:00,11544.0 -2014-06-12 11:00:00,12152.0 -2014-06-12 12:00:00,12760.0 -2014-06-12 13:00:00,13268.0 -2014-06-12 14:00:00,13625.0 -2014-06-12 15:00:00,13745.0 -2014-06-12 16:00:00,13656.0 -2014-06-12 17:00:00,13575.0 -2014-06-12 18:00:00,13507.0 -2014-06-12 19:00:00,13234.0 -2014-06-12 20:00:00,12717.0 -2014-06-12 21:00:00,12320.0 -2014-06-12 22:00:00,12270.0 -2014-06-12 23:00:00,12037.0 -2014-06-13 00:00:00,11146.0 -2014-06-11 01:00:00,9660.0 -2014-06-11 02:00:00,9133.0 -2014-06-11 03:00:00,8778.0 -2014-06-11 04:00:00,8570.0 -2014-06-11 05:00:00,8503.0 -2014-06-11 06:00:00,8645.0 -2014-06-11 07:00:00,9158.0 -2014-06-11 08:00:00,9934.0 -2014-06-11 09:00:00,10720.0 -2014-06-11 10:00:00,11204.0 -2014-06-11 11:00:00,11518.0 -2014-06-11 12:00:00,11761.0 -2014-06-11 13:00:00,11877.0 -2014-06-11 14:00:00,11922.0 -2014-06-11 15:00:00,11998.0 -2014-06-11 16:00:00,12008.0 -2014-06-11 17:00:00,11897.0 -2014-06-11 18:00:00,11759.0 -2014-06-11 19:00:00,11563.0 -2014-06-11 20:00:00,11276.0 -2014-06-11 21:00:00,11183.0 -2014-06-11 22:00:00,11484.0 -2014-06-11 23:00:00,11230.0 -2014-06-12 00:00:00,10491.0 -2014-06-10 01:00:00,9676.0 -2014-06-10 02:00:00,9071.0 -2014-06-10 03:00:00,8691.0 -2014-06-10 04:00:00,8482.0 -2014-06-10 05:00:00,8413.0 -2014-06-10 06:00:00,8562.0 -2014-06-10 07:00:00,9011.0 -2014-06-10 08:00:00,9845.0 -2014-06-10 09:00:00,10619.0 -2014-06-10 10:00:00,11075.0 -2014-06-10 11:00:00,11369.0 -2014-06-10 12:00:00,11672.0 -2014-06-10 13:00:00,11807.0 -2014-06-10 14:00:00,11873.0 -2014-06-10 15:00:00,11963.0 -2014-06-10 16:00:00,11728.0 -2014-06-10 17:00:00,11752.0 -2014-06-10 18:00:00,11630.0 -2014-06-10 19:00:00,11449.0 -2014-06-10 20:00:00,11375.0 -2014-06-10 21:00:00,11376.0 -2014-06-10 22:00:00,11477.0 -2014-06-10 23:00:00,11136.0 -2014-06-11 00:00:00,10460.0 -2014-06-09 01:00:00,8807.0 -2014-06-09 02:00:00,8365.0 -2014-06-09 03:00:00,8018.0 -2014-06-09 04:00:00,7848.0 -2014-06-09 05:00:00,7824.0 -2014-06-09 06:00:00,8028.0 -2014-06-09 07:00:00,8438.0 -2014-06-09 08:00:00,9375.0 -2014-06-09 09:00:00,10415.0 -2014-06-09 10:00:00,11126.0 -2014-06-09 11:00:00,11591.0 -2014-06-09 12:00:00,12077.0 -2014-06-09 13:00:00,12394.0 -2014-06-09 14:00:00,12701.0 -2014-06-09 15:00:00,13041.0 -2014-06-09 16:00:00,13217.0 -2014-06-09 17:00:00,13263.0 -2014-06-09 18:00:00,13153.0 -2014-06-09 19:00:00,12794.0 -2014-06-09 20:00:00,12237.0 -2014-06-09 21:00:00,11827.0 -2014-06-09 22:00:00,11809.0 -2014-06-09 23:00:00,11511.0 -2014-06-10 00:00:00,10601.0 -2014-06-08 01:00:00,9992.0 -2014-06-08 02:00:00,9294.0 -2014-06-08 03:00:00,8793.0 -2014-06-08 04:00:00,8343.0 -2014-06-08 05:00:00,8092.0 -2014-06-08 06:00:00,7971.0 -2014-06-08 07:00:00,7875.0 -2014-06-08 08:00:00,7758.0 -2014-06-08 09:00:00,8015.0 -2014-06-08 10:00:00,8538.0 -2014-06-08 11:00:00,9111.0 -2014-06-08 12:00:00,9462.0 -2014-06-08 13:00:00,9720.0 -2014-06-08 14:00:00,9795.0 -2014-06-08 15:00:00,9904.0 -2014-06-08 16:00:00,10036.0 -2014-06-08 17:00:00,10096.0 -2014-06-08 18:00:00,10102.0 -2014-06-08 19:00:00,10008.0 -2014-06-08 20:00:00,9763.0 -2014-06-08 21:00:00,9615.0 -2014-06-08 22:00:00,9897.0 -2014-06-08 23:00:00,9980.0 -2014-06-09 00:00:00,9496.0 -2014-06-07 01:00:00,10182.0 -2014-06-07 02:00:00,9442.0 -2014-06-07 03:00:00,8912.0 -2014-06-07 04:00:00,8556.0 -2014-06-07 05:00:00,8388.0 -2014-06-07 06:00:00,8368.0 -2014-06-07 07:00:00,8260.0 -2014-06-07 08:00:00,8670.0 -2014-06-07 09:00:00,9378.0 -2014-06-07 10:00:00,10271.0 -2014-06-07 11:00:00,11017.0 -2014-06-07 12:00:00,11639.0 -2014-06-07 13:00:00,12063.0 -2014-06-07 14:00:00,12456.0 -2014-06-07 15:00:00,12703.0 -2014-06-07 16:00:00,12863.0 -2014-06-07 17:00:00,12603.0 -2014-06-07 18:00:00,12306.0 -2014-06-07 19:00:00,11925.0 -2014-06-07 20:00:00,11643.0 -2014-06-07 21:00:00,11457.0 -2014-06-07 22:00:00,11609.0 -2014-06-07 23:00:00,11407.0 -2014-06-08 00:00:00,10775.0 -2014-06-06 01:00:00,9790.0 -2014-06-06 02:00:00,9174.0 -2014-06-06 03:00:00,8736.0 -2014-06-06 04:00:00,8501.0 -2014-06-06 05:00:00,8350.0 -2014-06-06 06:00:00,8514.0 -2014-06-06 07:00:00,8776.0 -2014-06-06 08:00:00,9638.0 -2014-06-06 09:00:00,10600.0 -2014-06-06 10:00:00,11303.0 -2014-06-06 11:00:00,11804.0 -2014-06-06 12:00:00,12325.0 -2014-06-06 13:00:00,12623.0 -2014-06-06 14:00:00,12911.0 -2014-06-06 15:00:00,13246.0 -2014-06-06 16:00:00,13494.0 -2014-06-06 17:00:00,13712.0 -2014-06-06 18:00:00,13816.0 -2014-06-06 19:00:00,13634.0 -2014-06-06 20:00:00,13146.0 -2014-06-06 21:00:00,12544.0 -2014-06-06 22:00:00,12173.0 -2014-06-06 23:00:00,11966.0 -2014-06-07 00:00:00,11120.0 -2014-06-05 01:00:00,9642.0 -2014-06-05 02:00:00,9038.0 -2014-06-05 03:00:00,8665.0 -2014-06-05 04:00:00,8392.0 -2014-06-05 05:00:00,8294.0 -2014-06-05 06:00:00,8421.0 -2014-06-05 07:00:00,8747.0 -2014-06-05 08:00:00,9648.0 -2014-06-05 09:00:00,10547.0 -2014-06-05 10:00:00,11119.0 -2014-06-05 11:00:00,11558.0 -2014-06-05 12:00:00,11964.0 -2014-06-05 13:00:00,12178.0 -2014-06-05 14:00:00,12289.0 -2014-06-05 15:00:00,12393.0 -2014-06-05 16:00:00,12574.0 -2014-06-05 17:00:00,12710.0 -2014-06-05 18:00:00,12758.0 -2014-06-05 19:00:00,12607.0 -2014-06-05 20:00:00,12213.0 -2014-06-05 21:00:00,11854.0 -2014-06-05 22:00:00,11785.0 -2014-06-05 23:00:00,11640.0 -2014-06-06 00:00:00,10782.0 -2014-06-04 01:00:00,11041.0 -2014-06-04 02:00:00,10273.0 -2014-06-04 03:00:00,9723.0 -2014-06-04 04:00:00,9412.0 -2014-06-04 05:00:00,9276.0 -2014-06-04 06:00:00,9349.0 -2014-06-04 07:00:00,9823.0 -2014-06-04 08:00:00,10610.0 -2014-06-04 09:00:00,11332.0 -2014-06-04 10:00:00,11808.0 -2014-06-04 11:00:00,12128.0 -2014-06-04 12:00:00,12231.0 -2014-06-04 13:00:00,12182.0 -2014-06-04 14:00:00,12096.0 -2014-06-04 15:00:00,12109.0 -2014-06-04 16:00:00,12055.0 -2014-06-04 17:00:00,11937.0 -2014-06-04 18:00:00,11786.0 -2014-06-04 19:00:00,11656.0 -2014-06-04 20:00:00,11458.0 -2014-06-04 21:00:00,11255.0 -2014-06-04 22:00:00,11366.0 -2014-06-04 23:00:00,11279.0 -2014-06-05 00:00:00,10497.0 -2014-06-03 01:00:00,12031.0 -2014-06-03 02:00:00,11100.0 -2014-06-03 03:00:00,10481.0 -2014-06-03 04:00:00,10064.0 -2014-06-03 05:00:00,9854.0 -2014-06-03 06:00:00,9896.0 -2014-06-03 07:00:00,10216.0 -2014-06-03 08:00:00,11252.0 -2014-06-03 09:00:00,12380.0 -2014-06-03 10:00:00,13118.0 -2014-06-03 11:00:00,13554.0 -2014-06-03 12:00:00,13964.0 -2014-06-03 13:00:00,14257.0 -2014-06-03 14:00:00,14532.0 -2014-06-03 15:00:00,14838.0 -2014-06-03 16:00:00,15127.0 -2014-06-03 17:00:00,15424.0 -2014-06-03 18:00:00,15563.0 -2014-06-03 19:00:00,15443.0 -2014-06-03 20:00:00,14912.0 -2014-06-03 21:00:00,14187.0 -2014-06-03 22:00:00,13797.0 -2014-06-03 23:00:00,13346.0 -2014-06-04 00:00:00,12221.0 -2014-06-02 01:00:00,11873.0 -2014-06-02 02:00:00,11082.0 -2014-06-02 03:00:00,10579.0 -2014-06-02 04:00:00,10202.0 -2014-06-02 05:00:00,10091.0 -2014-06-02 06:00:00,10305.0 -2014-06-02 07:00:00,10907.0 -2014-06-02 08:00:00,12168.0 -2014-06-02 09:00:00,13441.0 -2014-06-02 10:00:00,14274.0 -2014-06-02 11:00:00,14833.0 -2014-06-02 12:00:00,14938.0 -2014-06-02 13:00:00,15090.0 -2014-06-02 14:00:00,14828.0 -2014-06-02 15:00:00,15161.0 -2014-06-02 16:00:00,15585.0 -2014-06-02 17:00:00,15891.0 -2014-06-02 18:00:00,16121.0 -2014-06-02 19:00:00,16075.0 -2014-06-02 20:00:00,15635.0 -2014-06-02 21:00:00,15093.0 -2014-06-02 22:00:00,14760.0 -2014-06-02 23:00:00,14391.0 -2014-06-03 00:00:00,13293.0 -2014-06-01 01:00:00,10490.0 -2014-06-01 02:00:00,9741.0 -2014-06-01 03:00:00,9253.0 -2014-06-01 04:00:00,8880.0 -2014-06-01 05:00:00,8609.0 -2014-06-01 06:00:00,8513.0 -2014-06-01 07:00:00,8283.0 -2014-06-01 08:00:00,8413.0 -2014-06-01 09:00:00,9057.0 -2014-06-01 10:00:00,9938.0 -2014-06-01 11:00:00,10813.0 -2014-06-01 12:00:00,11739.0 -2014-06-01 13:00:00,12427.0 -2014-06-01 14:00:00,13310.0 -2014-06-01 15:00:00,13908.0 -2014-06-01 16:00:00,14345.0 -2014-06-01 17:00:00,14607.0 -2014-06-01 18:00:00,14858.0 -2014-06-01 19:00:00,15028.0 -2014-06-01 20:00:00,14665.0 -2014-06-01 21:00:00,14283.0 -2014-06-01 22:00:00,14041.0 -2014-06-01 23:00:00,13684.0 -2014-06-02 00:00:00,12867.0 -2014-05-31 01:00:00,10087.0 -2014-05-31 02:00:00,9316.0 -2014-05-31 03:00:00,8818.0 -2014-05-31 04:00:00,8506.0 -2014-05-31 05:00:00,8312.0 -2014-05-31 06:00:00,8307.0 -2014-05-31 07:00:00,8177.0 -2014-05-31 08:00:00,8479.0 -2014-05-31 09:00:00,9192.0 -2014-05-31 10:00:00,10007.0 -2014-05-31 11:00:00,10827.0 -2014-05-31 12:00:00,11511.0 -2014-05-31 13:00:00,12013.0 -2014-05-31 14:00:00,12549.0 -2014-05-31 15:00:00,12866.0 -2014-05-31 16:00:00,13214.0 -2014-05-31 17:00:00,13576.0 -2014-05-31 18:00:00,13752.0 -2014-05-31 19:00:00,13766.0 -2014-05-31 20:00:00,13426.0 -2014-05-31 21:00:00,12827.0 -2014-05-31 22:00:00,12522.0 -2014-05-31 23:00:00,12229.0 -2014-06-01 00:00:00,11425.0 -2014-05-30 01:00:00,9697.0 -2014-05-30 02:00:00,9054.0 -2014-05-30 03:00:00,8649.0 -2014-05-30 04:00:00,8357.0 -2014-05-30 05:00:00,8254.0 -2014-05-30 06:00:00,8403.0 -2014-05-30 07:00:00,8768.0 -2014-05-30 08:00:00,9696.0 -2014-05-30 09:00:00,10702.0 -2014-05-30 10:00:00,11362.0 -2014-05-30 11:00:00,11861.0 -2014-05-30 12:00:00,12349.0 -2014-05-30 13:00:00,12792.0 -2014-05-30 14:00:00,13071.0 -2014-05-30 15:00:00,13352.0 -2014-05-30 16:00:00,13559.0 -2014-05-30 17:00:00,13715.0 -2014-05-30 18:00:00,13757.0 -2014-05-30 19:00:00,13533.0 -2014-05-30 20:00:00,13066.0 -2014-05-30 21:00:00,12447.0 -2014-05-30 22:00:00,12211.0 -2014-05-30 23:00:00,11913.0 -2014-05-31 00:00:00,11015.0 -2014-05-29 01:00:00,9614.0 -2014-05-29 02:00:00,8962.0 -2014-05-29 03:00:00,8586.0 -2014-05-29 04:00:00,8356.0 -2014-05-29 05:00:00,8274.0 -2014-05-29 06:00:00,8435.0 -2014-05-29 07:00:00,8910.0 -2014-05-29 08:00:00,9789.0 -2014-05-29 09:00:00,10660.0 -2014-05-29 10:00:00,11272.0 -2014-05-29 11:00:00,11713.0 -2014-05-29 12:00:00,12191.0 -2014-05-29 13:00:00,12488.0 -2014-05-29 14:00:00,12755.0 -2014-05-29 15:00:00,13013.0 -2014-05-29 16:00:00,13066.0 -2014-05-29 17:00:00,13160.0 -2014-05-29 18:00:00,13231.0 -2014-05-29 19:00:00,13051.0 -2014-05-29 20:00:00,12595.0 -2014-05-29 21:00:00,12127.0 -2014-05-29 22:00:00,12009.0 -2014-05-29 23:00:00,11679.0 -2014-05-30 00:00:00,10705.0 -2014-05-28 01:00:00,10656.0 -2014-05-28 02:00:00,9877.0 -2014-05-28 03:00:00,9437.0 -2014-05-28 04:00:00,9189.0 -2014-05-28 05:00:00,9042.0 -2014-05-28 06:00:00,9131.0 -2014-05-28 07:00:00,9574.0 -2014-05-28 08:00:00,10351.0 -2014-05-28 09:00:00,11139.0 -2014-05-28 10:00:00,11606.0 -2014-05-28 11:00:00,11965.0 -2014-05-28 12:00:00,12331.0 -2014-05-28 13:00:00,12552.0 -2014-05-28 14:00:00,12757.0 -2014-05-28 15:00:00,13006.0 -2014-05-28 16:00:00,13113.0 -2014-05-28 17:00:00,13119.0 -2014-05-28 18:00:00,12999.0 -2014-05-28 19:00:00,12631.0 -2014-05-28 20:00:00,11998.0 -2014-05-28 21:00:00,11559.0 -2014-05-28 22:00:00,11610.0 -2014-05-28 23:00:00,11324.0 -2014-05-29 00:00:00,10488.0 -2014-05-27 01:00:00,10633.0 -2014-05-27 02:00:00,9994.0 -2014-05-27 03:00:00,9545.0 -2014-05-27 04:00:00,9253.0 -2014-05-27 05:00:00,9109.0 -2014-05-27 06:00:00,9307.0 -2014-05-27 07:00:00,9896.0 -2014-05-27 08:00:00,11027.0 -2014-05-27 09:00:00,12249.0 -2014-05-27 10:00:00,12982.0 -2014-05-27 11:00:00,13523.0 -2014-05-27 12:00:00,14264.0 -2014-05-27 13:00:00,14928.0 -2014-05-27 14:00:00,15475.0 -2014-05-27 15:00:00,15948.0 -2014-05-27 16:00:00,16141.0 -2014-05-27 17:00:00,16287.0 -2014-05-27 18:00:00,16122.0 -2014-05-27 19:00:00,15397.0 -2014-05-27 20:00:00,14514.0 -2014-05-27 21:00:00,13964.0 -2014-05-27 22:00:00,13809.0 -2014-05-27 23:00:00,13146.0 -2014-05-28 00:00:00,11891.0 -2014-05-26 01:00:00,9233.0 -2014-05-26 02:00:00,8690.0 -2014-05-26 03:00:00,8342.0 -2014-05-26 04:00:00,8081.0 -2014-05-26 05:00:00,7927.0 -2014-05-26 06:00:00,7912.0 -2014-05-26 07:00:00,7889.0 -2014-05-26 08:00:00,7981.0 -2014-05-26 09:00:00,8501.0 -2014-05-26 10:00:00,9135.0 -2014-05-26 11:00:00,9833.0 -2014-05-26 12:00:00,10600.0 -2014-05-26 13:00:00,11391.0 -2014-05-26 14:00:00,12104.0 -2014-05-26 15:00:00,12639.0 -2014-05-26 16:00:00,13032.0 -2014-05-26 17:00:00,13328.0 -2014-05-26 18:00:00,13371.0 -2014-05-26 19:00:00,13163.0 -2014-05-26 20:00:00,12797.0 -2014-05-26 21:00:00,12603.0 -2014-05-26 22:00:00,12726.0 -2014-05-26 23:00:00,12388.0 -2014-05-27 00:00:00,11530.0 -2014-05-25 01:00:00,8598.0 -2014-05-25 02:00:00,8119.0 -2014-05-25 03:00:00,7785.0 -2014-05-25 04:00:00,7600.0 -2014-05-25 05:00:00,7443.0 -2014-05-25 06:00:00,7447.0 -2014-05-25 07:00:00,7237.0 -2014-05-25 08:00:00,7269.0 -2014-05-25 09:00:00,7657.0 -2014-05-25 10:00:00,8239.0 -2014-05-25 11:00:00,8718.0 -2014-05-25 12:00:00,9203.0 -2014-05-25 13:00:00,9507.0 -2014-05-25 14:00:00,9775.0 -2014-05-25 15:00:00,9990.0 -2014-05-25 16:00:00,10260.0 -2014-05-25 17:00:00,10512.0 -2014-05-25 18:00:00,10728.0 -2014-05-25 19:00:00,10837.0 -2014-05-25 20:00:00,10600.0 -2014-05-25 21:00:00,10299.0 -2014-05-25 22:00:00,10421.0 -2014-05-25 23:00:00,10383.0 -2014-05-26 00:00:00,9816.0 -2014-05-24 01:00:00,9064.0 -2014-05-24 02:00:00,8532.0 -2014-05-24 03:00:00,8124.0 -2014-05-24 04:00:00,7951.0 -2014-05-24 05:00:00,7817.0 -2014-05-24 06:00:00,7815.0 -2014-05-24 07:00:00,7745.0 -2014-05-24 08:00:00,7940.0 -2014-05-24 09:00:00,8332.0 -2014-05-24 10:00:00,8894.0 -2014-05-24 11:00:00,9278.0 -2014-05-24 12:00:00,9567.0 -2014-05-24 13:00:00,9728.0 -2014-05-24 14:00:00,9799.0 -2014-05-24 15:00:00,9814.0 -2014-05-24 16:00:00,9812.0 -2014-05-24 17:00:00,9843.0 -2014-05-24 18:00:00,9785.0 -2014-05-24 19:00:00,9703.0 -2014-05-24 20:00:00,9525.0 -2014-05-24 21:00:00,9444.0 -2014-05-24 22:00:00,9764.0 -2014-05-24 23:00:00,9719.0 -2014-05-25 00:00:00,9215.0 -2014-05-23 01:00:00,9447.0 -2014-05-23 02:00:00,8880.0 -2014-05-23 03:00:00,8492.0 -2014-05-23 04:00:00,8282.0 -2014-05-23 05:00:00,8206.0 -2014-05-23 06:00:00,8347.0 -2014-05-23 07:00:00,8690.0 -2014-05-23 08:00:00,9512.0 -2014-05-23 09:00:00,10392.0 -2014-05-23 10:00:00,10915.0 -2014-05-23 11:00:00,11243.0 -2014-05-23 12:00:00,11497.0 -2014-05-23 13:00:00,11573.0 -2014-05-23 14:00:00,11601.0 -2014-05-23 15:00:00,11666.0 -2014-05-23 16:00:00,11644.0 -2014-05-23 17:00:00,11567.0 -2014-05-23 18:00:00,11460.0 -2014-05-23 19:00:00,11198.0 -2014-05-23 20:00:00,10852.0 -2014-05-23 21:00:00,10521.0 -2014-05-23 22:00:00,10696.0 -2014-05-23 23:00:00,10537.0 -2014-05-24 00:00:00,9847.0 -2014-05-22 01:00:00,11068.0 -2014-05-22 02:00:00,10127.0 -2014-05-22 03:00:00,9477.0 -2014-05-22 04:00:00,9080.0 -2014-05-22 05:00:00,8861.0 -2014-05-22 06:00:00,8926.0 -2014-05-22 07:00:00,9217.0 -2014-05-22 08:00:00,10065.0 -2014-05-22 09:00:00,10976.0 -2014-05-22 10:00:00,11590.0 -2014-05-22 11:00:00,11999.0 -2014-05-22 12:00:00,12282.0 -2014-05-22 13:00:00,12371.0 -2014-05-22 14:00:00,12421.0 -2014-05-22 15:00:00,12537.0 -2014-05-22 16:00:00,12501.0 -2014-05-22 17:00:00,12399.0 -2014-05-22 18:00:00,12172.0 -2014-05-22 19:00:00,11846.0 -2014-05-22 20:00:00,11501.0 -2014-05-22 21:00:00,11189.0 -2014-05-22 22:00:00,11357.0 -2014-05-22 23:00:00,11159.0 -2014-05-23 00:00:00,10301.0 -2014-05-21 01:00:00,11066.0 -2014-05-21 02:00:00,10344.0 -2014-05-21 03:00:00,9842.0 -2014-05-21 04:00:00,9465.0 -2014-05-21 05:00:00,9257.0 -2014-05-21 06:00:00,9351.0 -2014-05-21 07:00:00,9918.0 -2014-05-21 08:00:00,10884.0 -2014-05-21 09:00:00,11787.0 -2014-05-21 10:00:00,12357.0 -2014-05-21 11:00:00,12935.0 -2014-05-21 12:00:00,13556.0 -2014-05-21 13:00:00,14038.0 -2014-05-21 14:00:00,14435.0 -2014-05-21 15:00:00,14851.0 -2014-05-21 16:00:00,15107.0 -2014-05-21 17:00:00,15361.0 -2014-05-21 18:00:00,15512.0 -2014-05-21 19:00:00,15395.0 -2014-05-21 20:00:00,14993.0 -2014-05-21 21:00:00,14441.0 -2014-05-21 22:00:00,14299.0 -2014-05-21 23:00:00,13715.0 -2014-05-22 00:00:00,12376.0 -2014-05-20 01:00:00,9273.0 -2014-05-20 02:00:00,8759.0 -2014-05-20 03:00:00,8432.0 -2014-05-20 04:00:00,8237.0 -2014-05-20 05:00:00,8193.0 -2014-05-20 06:00:00,8360.0 -2014-05-20 07:00:00,8880.0 -2014-05-20 08:00:00,9775.0 -2014-05-20 09:00:00,10694.0 -2014-05-20 10:00:00,11314.0 -2014-05-20 11:00:00,11815.0 -2014-05-20 12:00:00,12289.0 -2014-05-20 13:00:00,12671.0 -2014-05-20 14:00:00,13041.0 -2014-05-20 15:00:00,13487.0 -2014-05-20 16:00:00,13753.0 -2014-05-20 17:00:00,14025.0 -2014-05-20 18:00:00,14202.0 -2014-05-20 19:00:00,14176.0 -2014-05-20 20:00:00,13903.0 -2014-05-20 21:00:00,14069.0 -2014-05-20 22:00:00,14008.0 -2014-05-20 23:00:00,13252.0 -2014-05-21 00:00:00,12137.0 -2014-05-19 01:00:00,8600.0 -2014-05-19 02:00:00,8141.0 -2014-05-19 03:00:00,7942.0 -2014-05-19 04:00:00,7857.0 -2014-05-19 05:00:00,7867.0 -2014-05-19 06:00:00,8119.0 -2014-05-19 07:00:00,8683.0 -2014-05-19 08:00:00,9569.0 -2014-05-19 09:00:00,10440.0 -2014-05-19 10:00:00,10853.0 -2014-05-19 11:00:00,11155.0 -2014-05-19 12:00:00,11412.0 -2014-05-19 13:00:00,11499.0 -2014-05-19 14:00:00,11504.0 -2014-05-19 15:00:00,11559.0 -2014-05-19 16:00:00,11495.0 -2014-05-19 17:00:00,11373.0 -2014-05-19 18:00:00,11197.0 -2014-05-19 19:00:00,11003.0 -2014-05-19 20:00:00,10864.0 -2014-05-19 21:00:00,11017.0 -2014-05-19 22:00:00,11205.0 -2014-05-19 23:00:00,10811.0 -2014-05-20 00:00:00,10005.0 -2014-05-18 01:00:00,8723.0 -2014-05-18 02:00:00,8309.0 -2014-05-18 03:00:00,8087.0 -2014-05-18 04:00:00,7904.0 -2014-05-18 05:00:00,7828.0 -2014-05-18 06:00:00,7821.0 -2014-05-18 07:00:00,7833.0 -2014-05-18 08:00:00,7760.0 -2014-05-18 09:00:00,8050.0 -2014-05-18 10:00:00,8306.0 -2014-05-18 11:00:00,8634.0 -2014-05-18 12:00:00,8792.0 -2014-05-18 13:00:00,8916.0 -2014-05-18 14:00:00,8957.0 -2014-05-18 15:00:00,8975.0 -2014-05-18 16:00:00,8979.0 -2014-05-18 17:00:00,9032.0 -2014-05-18 18:00:00,9048.0 -2014-05-18 19:00:00,9109.0 -2014-05-18 20:00:00,9135.0 -2014-05-18 21:00:00,9258.0 -2014-05-18 22:00:00,9766.0 -2014-05-18 23:00:00,9712.0 -2014-05-19 00:00:00,9188.0 -2014-05-17 01:00:00,9510.0 -2014-05-17 02:00:00,8952.0 -2014-05-17 03:00:00,8657.0 -2014-05-17 04:00:00,8476.0 -2014-05-17 05:00:00,8416.0 -2014-05-17 06:00:00,8490.0 -2014-05-17 07:00:00,8602.0 -2014-05-17 08:00:00,8834.0 -2014-05-17 09:00:00,9176.0 -2014-05-17 10:00:00,9500.0 -2014-05-17 11:00:00,9653.0 -2014-05-17 12:00:00,9787.0 -2014-05-17 13:00:00,9713.0 -2014-05-17 14:00:00,9592.0 -2014-05-17 15:00:00,9446.0 -2014-05-17 16:00:00,9315.0 -2014-05-17 17:00:00,9216.0 -2014-05-17 18:00:00,9177.0 -2014-05-17 19:00:00,9144.0 -2014-05-17 20:00:00,9089.0 -2014-05-17 21:00:00,9183.0 -2014-05-17 22:00:00,9681.0 -2014-05-17 23:00:00,9658.0 -2014-05-18 00:00:00,9207.0 -2014-05-16 01:00:00,9399.0 -2014-05-16 02:00:00,8909.0 -2014-05-16 03:00:00,8624.0 -2014-05-16 04:00:00,8454.0 -2014-05-16 05:00:00,8405.0 -2014-05-16 06:00:00,8639.0 -2014-05-16 07:00:00,9292.0 -2014-05-16 08:00:00,10227.0 -2014-05-16 09:00:00,10970.0 -2014-05-16 10:00:00,11354.0 -2014-05-16 11:00:00,11567.0 -2014-05-16 12:00:00,11616.0 -2014-05-16 13:00:00,11591.0 -2014-05-16 14:00:00,11454.0 -2014-05-16 15:00:00,11400.0 -2014-05-16 16:00:00,11285.0 -2014-05-16 17:00:00,11096.0 -2014-05-16 18:00:00,10981.0 -2014-05-16 19:00:00,10810.0 -2014-05-16 20:00:00,10737.0 -2014-05-16 21:00:00,10852.0 -2014-05-16 22:00:00,11114.0 -2014-05-16 23:00:00,10871.0 -2014-05-17 00:00:00,10208.0 -2014-05-15 01:00:00,9254.0 -2014-05-15 02:00:00,8781.0 -2014-05-15 03:00:00,8533.0 -2014-05-15 04:00:00,8321.0 -2014-05-15 05:00:00,8300.0 -2014-05-15 06:00:00,8511.0 -2014-05-15 07:00:00,9203.0 -2014-05-15 08:00:00,10144.0 -2014-05-15 09:00:00,10924.0 -2014-05-15 10:00:00,11342.0 -2014-05-15 11:00:00,11480.0 -2014-05-15 12:00:00,11497.0 -2014-05-15 13:00:00,11330.0 -2014-05-15 14:00:00,11167.0 -2014-05-15 15:00:00,11194.0 -2014-05-15 16:00:00,11100.0 -2014-05-15 17:00:00,10977.0 -2014-05-15 18:00:00,10851.0 -2014-05-15 19:00:00,10692.0 -2014-05-15 20:00:00,10563.0 -2014-05-15 21:00:00,10635.0 -2014-05-15 22:00:00,11096.0 -2014-05-15 23:00:00,10858.0 -2014-05-16 00:00:00,10116.0 -2014-05-14 01:00:00,9184.0 -2014-05-14 02:00:00,8651.0 -2014-05-14 03:00:00,8332.0 -2014-05-14 04:00:00,8147.0 -2014-05-14 05:00:00,8090.0 -2014-05-14 06:00:00,8257.0 -2014-05-14 07:00:00,8786.0 -2014-05-14 08:00:00,9576.0 -2014-05-14 09:00:00,10359.0 -2014-05-14 10:00:00,10700.0 -2014-05-14 11:00:00,10858.0 -2014-05-14 12:00:00,10994.0 -2014-05-14 13:00:00,11009.0 -2014-05-14 14:00:00,10997.0 -2014-05-14 15:00:00,11030.0 -2014-05-14 16:00:00,10914.0 -2014-05-14 17:00:00,10809.0 -2014-05-14 18:00:00,10723.0 -2014-05-14 19:00:00,10653.0 -2014-05-14 20:00:00,10614.0 -2014-05-14 21:00:00,10683.0 -2014-05-14 22:00:00,11029.0 -2014-05-14 23:00:00,10685.0 -2014-05-15 00:00:00,9919.0 -2014-05-13 01:00:00,11165.0 -2014-05-13 02:00:00,10160.0 -2014-05-13 03:00:00,9669.0 -2014-05-13 04:00:00,9360.0 -2014-05-13 05:00:00,9276.0 -2014-05-13 06:00:00,9368.0 -2014-05-13 07:00:00,9896.0 -2014-05-13 08:00:00,10741.0 -2014-05-13 09:00:00,11577.0 -2014-05-13 10:00:00,12002.0 -2014-05-13 11:00:00,12082.0 -2014-05-13 12:00:00,12132.0 -2014-05-13 13:00:00,12046.0 -2014-05-13 14:00:00,11988.0 -2014-05-13 15:00:00,11919.0 -2014-05-13 16:00:00,11682.0 -2014-05-13 17:00:00,11507.0 -2014-05-13 18:00:00,11282.0 -2014-05-13 19:00:00,11115.0 -2014-05-13 20:00:00,10923.0 -2014-05-13 21:00:00,10939.0 -2014-05-13 22:00:00,11168.0 -2014-05-13 23:00:00,10780.0 -2014-05-14 00:00:00,9999.0 -2014-05-12 01:00:00,9212.0 -2014-05-12 02:00:00,8708.0 -2014-05-12 03:00:00,8440.0 -2014-05-12 04:00:00,8248.0 -2014-05-12 05:00:00,8227.0 -2014-05-12 06:00:00,8500.0 -2014-05-12 07:00:00,9193.0 -2014-05-12 08:00:00,10247.0 -2014-05-12 09:00:00,11394.0 -2014-05-12 10:00:00,12154.0 -2014-05-12 11:00:00,12718.0 -2014-05-12 12:00:00,13281.0 -2014-05-12 13:00:00,13604.0 -2014-05-12 14:00:00,13843.0 -2014-05-12 15:00:00,14151.0 -2014-05-12 16:00:00,14299.0 -2014-05-12 17:00:00,14384.0 -2014-05-12 18:00:00,14494.0 -2014-05-12 19:00:00,14482.0 -2014-05-12 20:00:00,14172.0 -2014-05-12 21:00:00,14060.0 -2014-05-12 22:00:00,14135.0 -2014-05-12 23:00:00,13442.0 -2014-05-13 00:00:00,12363.0 -2014-05-11 01:00:00,8869.0 -2014-05-11 02:00:00,8353.0 -2014-05-11 03:00:00,8039.0 -2014-05-11 04:00:00,7787.0 -2014-05-11 05:00:00,7649.0 -2014-05-11 06:00:00,7583.0 -2014-05-11 07:00:00,7565.0 -2014-05-11 08:00:00,7506.0 -2014-05-11 09:00:00,8012.0 -2014-05-11 10:00:00,8411.0 -2014-05-11 11:00:00,8875.0 -2014-05-11 12:00:00,9245.0 -2014-05-11 13:00:00,9554.0 -2014-05-11 14:00:00,9841.0 -2014-05-11 15:00:00,10117.0 -2014-05-11 16:00:00,10401.0 -2014-05-11 17:00:00,10490.0 -2014-05-11 18:00:00,10516.0 -2014-05-11 19:00:00,10721.0 -2014-05-11 20:00:00,10721.0 -2014-05-11 21:00:00,10696.0 -2014-05-11 22:00:00,10768.0 -2014-05-11 23:00:00,10480.0 -2014-05-12 00:00:00,9860.0 -2014-05-10 01:00:00,9277.0 -2014-05-10 02:00:00,8713.0 -2014-05-10 03:00:00,8282.0 -2014-05-10 04:00:00,7980.0 -2014-05-10 05:00:00,7868.0 -2014-05-10 06:00:00,7906.0 -2014-05-10 07:00:00,7933.0 -2014-05-10 08:00:00,8133.0 -2014-05-10 09:00:00,8619.0 -2014-05-10 10:00:00,9172.0 -2014-05-10 11:00:00,9507.0 -2014-05-10 12:00:00,9801.0 -2014-05-10 13:00:00,9909.0 -2014-05-10 14:00:00,9951.0 -2014-05-10 15:00:00,9863.0 -2014-05-10 16:00:00,9907.0 -2014-05-10 17:00:00,9898.0 -2014-05-10 18:00:00,9955.0 -2014-05-10 19:00:00,9935.0 -2014-05-10 20:00:00,9832.0 -2014-05-10 21:00:00,9740.0 -2014-05-10 22:00:00,10064.0 -2014-05-10 23:00:00,9987.0 -2014-05-11 00:00:00,9449.0 -2014-05-09 01:00:00,10891.0 -2014-05-09 02:00:00,10097.0 -2014-05-09 03:00:00,9589.0 -2014-05-09 04:00:00,9240.0 -2014-05-09 05:00:00,9017.0 -2014-05-09 06:00:00,9141.0 -2014-05-09 07:00:00,9720.0 -2014-05-09 08:00:00,10663.0 -2014-05-09 09:00:00,11496.0 -2014-05-09 10:00:00,11914.0 -2014-05-09 11:00:00,12081.0 -2014-05-09 12:00:00,12300.0 -2014-05-09 13:00:00,12428.0 -2014-05-09 14:00:00,12505.0 -2014-05-09 15:00:00,12546.0 -2014-05-09 16:00:00,12397.0 -2014-05-09 17:00:00,12253.0 -2014-05-09 18:00:00,12080.0 -2014-05-09 19:00:00,11750.0 -2014-05-09 20:00:00,11315.0 -2014-05-09 21:00:00,10932.0 -2014-05-09 22:00:00,11112.0 -2014-05-09 23:00:00,10792.0 -2014-05-10 00:00:00,10085.0 -2014-05-08 01:00:00,9200.0 -2014-05-08 02:00:00,8733.0 -2014-05-08 03:00:00,8435.0 -2014-05-08 04:00:00,8219.0 -2014-05-08 05:00:00,8187.0 -2014-05-08 06:00:00,8379.0 -2014-05-08 07:00:00,9001.0 -2014-05-08 08:00:00,9820.0 -2014-05-08 09:00:00,10730.0 -2014-05-08 10:00:00,11290.0 -2014-05-08 11:00:00,11637.0 -2014-05-08 12:00:00,11947.0 -2014-05-08 13:00:00,12197.0 -2014-05-08 14:00:00,12455.0 -2014-05-08 15:00:00,12863.0 -2014-05-08 16:00:00,13090.0 -2014-05-08 17:00:00,13222.0 -2014-05-08 18:00:00,13366.0 -2014-05-08 19:00:00,13197.0 -2014-05-08 20:00:00,12876.0 -2014-05-08 21:00:00,12850.0 -2014-05-08 22:00:00,13223.0 -2014-05-08 23:00:00,12849.0 -2014-05-09 00:00:00,11929.0 -2014-05-07 01:00:00,9064.0 -2014-05-07 02:00:00,8564.0 -2014-05-07 03:00:00,8267.0 -2014-05-07 04:00:00,8106.0 -2014-05-07 05:00:00,8071.0 -2014-05-07 06:00:00,8282.0 -2014-05-07 07:00:00,8890.0 -2014-05-07 08:00:00,9629.0 -2014-05-07 09:00:00,10324.0 -2014-05-07 10:00:00,10649.0 -2014-05-07 11:00:00,10865.0 -2014-05-07 12:00:00,11091.0 -2014-05-07 13:00:00,11165.0 -2014-05-07 14:00:00,11254.0 -2014-05-07 15:00:00,11411.0 -2014-05-07 16:00:00,11413.0 -2014-05-07 17:00:00,11408.0 -2014-05-07 18:00:00,11098.0 -2014-05-07 19:00:00,10900.0 -2014-05-07 20:00:00,10622.0 -2014-05-07 21:00:00,10561.0 -2014-05-07 22:00:00,11068.0 -2014-05-07 23:00:00,10753.0 -2014-05-08 00:00:00,9997.0 -2014-05-06 01:00:00,9199.0 -2014-05-06 02:00:00,8768.0 -2014-05-06 03:00:00,8496.0 -2014-05-06 04:00:00,8342.0 -2014-05-06 05:00:00,8314.0 -2014-05-06 06:00:00,8531.0 -2014-05-06 07:00:00,9172.0 -2014-05-06 08:00:00,9919.0 -2014-05-06 09:00:00,10552.0 -2014-05-06 10:00:00,10774.0 -2014-05-06 11:00:00,10879.0 -2014-05-06 12:00:00,11011.0 -2014-05-06 13:00:00,11016.0 -2014-05-06 14:00:00,11002.0 -2014-05-06 15:00:00,11010.0 -2014-05-06 16:00:00,10906.0 -2014-05-06 17:00:00,10759.0 -2014-05-06 18:00:00,10627.0 -2014-05-06 19:00:00,10452.0 -2014-05-06 20:00:00,10299.0 -2014-05-06 21:00:00,10472.0 -2014-05-06 22:00:00,10942.0 -2014-05-06 23:00:00,10561.0 -2014-05-07 00:00:00,9816.0 -2014-05-05 01:00:00,8506.0 -2014-05-05 02:00:00,8169.0 -2014-05-05 03:00:00,8025.0 -2014-05-05 04:00:00,7934.0 -2014-05-05 05:00:00,7982.0 -2014-05-05 06:00:00,8239.0 -2014-05-05 07:00:00,9011.0 -2014-05-05 08:00:00,10021.0 -2014-05-05 09:00:00,10669.0 -2014-05-05 10:00:00,10893.0 -2014-05-05 11:00:00,11008.0 -2014-05-05 12:00:00,11041.0 -2014-05-05 13:00:00,10991.0 -2014-05-05 14:00:00,10920.0 -2014-05-05 15:00:00,10909.0 -2014-05-05 16:00:00,10783.0 -2014-05-05 17:00:00,10617.0 -2014-05-05 18:00:00,10502.0 -2014-05-05 19:00:00,10367.0 -2014-05-05 20:00:00,10225.0 -2014-05-05 21:00:00,10370.0 -2014-05-05 22:00:00,10953.0 -2014-05-05 23:00:00,10664.0 -2014-05-06 00:00:00,9934.0 -2014-05-04 01:00:00,8408.0 -2014-05-04 02:00:00,8009.0 -2014-05-04 03:00:00,7743.0 -2014-05-04 04:00:00,7560.0 -2014-05-04 05:00:00,7465.0 -2014-05-04 06:00:00,7483.0 -2014-05-04 07:00:00,7524.0 -2014-05-04 08:00:00,7469.0 -2014-05-04 09:00:00,7752.0 -2014-05-04 10:00:00,8112.0 -2014-05-04 11:00:00,8381.0 -2014-05-04 12:00:00,8582.0 -2014-05-04 13:00:00,8602.0 -2014-05-04 14:00:00,8635.0 -2014-05-04 15:00:00,8663.0 -2014-05-04 16:00:00,8623.0 -2014-05-04 17:00:00,8607.0 -2014-05-04 18:00:00,8603.0 -2014-05-04 19:00:00,8666.0 -2014-05-04 20:00:00,8710.0 -2014-05-04 21:00:00,9001.0 -2014-05-04 22:00:00,9639.0 -2014-05-04 23:00:00,9538.0 -2014-05-05 00:00:00,9042.0 -2014-05-03 01:00:00,9325.0 -2014-05-03 02:00:00,8747.0 -2014-05-03 03:00:00,8439.0 -2014-05-03 04:00:00,8207.0 -2014-05-03 05:00:00,8127.0 -2014-05-03 06:00:00,8153.0 -2014-05-03 07:00:00,8360.0 -2014-05-03 08:00:00,8528.0 -2014-05-03 09:00:00,8857.0 -2014-05-03 10:00:00,9159.0 -2014-05-03 11:00:00,9344.0 -2014-05-03 12:00:00,9482.0 -2014-05-03 13:00:00,9437.0 -2014-05-03 14:00:00,9390.0 -2014-05-03 15:00:00,9237.0 -2014-05-03 16:00:00,9107.0 -2014-05-03 17:00:00,9046.0 -2014-05-03 18:00:00,8995.0 -2014-05-03 19:00:00,8987.0 -2014-05-03 20:00:00,8950.0 -2014-05-03 21:00:00,9167.0 -2014-05-03 22:00:00,9623.0 -2014-05-03 23:00:00,9429.0 -2014-05-04 00:00:00,8931.0 -2014-05-02 01:00:00,9406.0 -2014-05-02 02:00:00,8909.0 -2014-05-02 03:00:00,8596.0 -2014-05-02 04:00:00,8394.0 -2014-05-02 05:00:00,8376.0 -2014-05-02 06:00:00,8588.0 -2014-05-02 07:00:00,9227.0 -2014-05-02 08:00:00,10038.0 -2014-05-02 09:00:00,10689.0 -2014-05-02 10:00:00,11008.0 -2014-05-02 11:00:00,11160.0 -2014-05-02 12:00:00,11233.0 -2014-05-02 13:00:00,11191.0 -2014-05-02 14:00:00,11170.0 -2014-05-02 15:00:00,11157.0 -2014-05-02 16:00:00,11046.0 -2014-05-02 17:00:00,10900.0 -2014-05-02 18:00:00,10803.0 -2014-05-02 19:00:00,10610.0 -2014-05-02 20:00:00,10514.0 -2014-05-02 21:00:00,10711.0 -2014-05-02 22:00:00,10890.0 -2014-05-02 23:00:00,10624.0 -2014-05-03 00:00:00,9992.0 -2014-05-01 01:00:00,9352.0 -2014-05-01 02:00:00,8880.0 -2014-05-01 03:00:00,8602.0 -2014-05-01 04:00:00,8468.0 -2014-05-01 05:00:00,8419.0 -2014-05-01 06:00:00,8660.0 -2014-05-01 07:00:00,9335.0 -2014-05-01 08:00:00,10308.0 -2014-05-01 09:00:00,10973.0 -2014-05-01 10:00:00,11273.0 -2014-05-01 11:00:00,11439.0 -2014-05-01 12:00:00,11543.0 -2014-05-01 13:00:00,11482.0 -2014-05-01 14:00:00,11375.0 -2014-05-01 15:00:00,11407.0 -2014-05-01 16:00:00,11291.0 -2014-05-01 17:00:00,11206.0 -2014-05-01 18:00:00,11192.0 -2014-05-01 19:00:00,11126.0 -2014-05-01 20:00:00,10964.0 -2014-05-01 21:00:00,11103.0 -2014-05-01 22:00:00,11343.0 -2014-05-01 23:00:00,10941.0 -2014-05-02 00:00:00,10190.0 -2014-04-30 01:00:00,9103.0 -2014-04-30 02:00:00,8621.0 -2014-04-30 03:00:00,8358.0 -2014-04-30 04:00:00,8171.0 -2014-04-30 05:00:00,8150.0 -2014-04-30 06:00:00,8318.0 -2014-04-30 07:00:00,9011.0 -2014-04-30 08:00:00,9868.0 -2014-04-30 09:00:00,10545.0 -2014-04-30 10:00:00,10877.0 -2014-04-30 11:00:00,11019.0 -2014-04-30 12:00:00,11154.0 -2014-04-30 13:00:00,11132.0 -2014-04-30 14:00:00,11080.0 -2014-04-30 15:00:00,11105.0 -2014-04-30 16:00:00,11020.0 -2014-04-30 17:00:00,10945.0 -2014-04-30 18:00:00,10927.0 -2014-04-30 19:00:00,10887.0 -2014-04-30 20:00:00,10777.0 -2014-04-30 21:00:00,10902.0 -2014-04-30 22:00:00,11199.0 -2014-04-30 23:00:00,10844.0 -2014-05-01 00:00:00,10097.0 -2014-04-29 01:00:00,9233.0 -2014-04-29 02:00:00,8772.0 -2014-04-29 03:00:00,8466.0 -2014-04-29 04:00:00,8317.0 -2014-04-29 05:00:00,8247.0 -2014-04-29 06:00:00,8432.0 -2014-04-29 07:00:00,9078.0 -2014-04-29 08:00:00,9949.0 -2014-04-29 09:00:00,10611.0 -2014-04-29 10:00:00,10887.0 -2014-04-29 11:00:00,11001.0 -2014-04-29 12:00:00,11105.0 -2014-04-29 13:00:00,11149.0 -2014-04-29 14:00:00,11184.0 -2014-04-29 15:00:00,11155.0 -2014-04-29 16:00:00,11047.0 -2014-04-29 17:00:00,10885.0 -2014-04-29 18:00:00,10792.0 -2014-04-29 19:00:00,10703.0 -2014-04-29 20:00:00,10548.0 -2014-04-29 21:00:00,10777.0 -2014-04-29 22:00:00,11011.0 -2014-04-29 23:00:00,10603.0 -2014-04-30 00:00:00,9832.0 -2014-04-28 01:00:00,8574.0 -2014-04-28 02:00:00,8287.0 -2014-04-28 03:00:00,8125.0 -2014-04-28 04:00:00,7949.0 -2014-04-28 05:00:00,8020.0 -2014-04-28 06:00:00,8255.0 -2014-04-28 07:00:00,9086.0 -2014-04-28 08:00:00,10200.0 -2014-04-28 09:00:00,10975.0 -2014-04-28 10:00:00,11137.0 -2014-04-28 11:00:00,11215.0 -2014-04-28 12:00:00,11390.0 -2014-04-28 13:00:00,11527.0 -2014-04-28 14:00:00,11412.0 -2014-04-28 15:00:00,11329.0 -2014-04-28 16:00:00,11176.0 -2014-04-28 17:00:00,11052.0 -2014-04-28 18:00:00,11018.0 -2014-04-28 19:00:00,11099.0 -2014-04-28 20:00:00,11052.0 -2014-04-28 21:00:00,11214.0 -2014-04-28 22:00:00,11269.0 -2014-04-28 23:00:00,10797.0 -2014-04-29 00:00:00,9972.0 -2014-04-27 01:00:00,8842.0 -2014-04-27 02:00:00,8390.0 -2014-04-27 03:00:00,8153.0 -2014-04-27 04:00:00,7970.0 -2014-04-27 05:00:00,7883.0 -2014-04-27 06:00:00,7846.0 -2014-04-27 07:00:00,8028.0 -2014-04-27 08:00:00,8060.0 -2014-04-27 09:00:00,8307.0 -2014-04-27 10:00:00,8667.0 -2014-04-27 11:00:00,8886.0 -2014-04-27 12:00:00,8911.0 -2014-04-27 13:00:00,8988.0 -2014-04-27 14:00:00,8946.0 -2014-04-27 15:00:00,8916.0 -2014-04-27 16:00:00,8869.0 -2014-04-27 17:00:00,8837.0 -2014-04-27 18:00:00,8882.0 -2014-04-27 19:00:00,9116.0 -2014-04-27 20:00:00,9391.0 -2014-04-27 21:00:00,9734.0 -2014-04-27 22:00:00,9821.0 -2014-04-27 23:00:00,9558.0 -2014-04-28 00:00:00,9050.0 -2014-04-26 01:00:00,9119.0 -2014-04-26 02:00:00,8584.0 -2014-04-26 03:00:00,8230.0 -2014-04-26 04:00:00,8036.0 -2014-04-26 05:00:00,7899.0 -2014-04-26 06:00:00,7962.0 -2014-04-26 07:00:00,8149.0 -2014-04-26 08:00:00,8280.0 -2014-04-26 09:00:00,8683.0 -2014-04-26 10:00:00,9106.0 -2014-04-26 11:00:00,9315.0 -2014-04-26 12:00:00,9465.0 -2014-04-26 13:00:00,9452.0 -2014-04-26 14:00:00,9355.0 -2014-04-26 15:00:00,9179.0 -2014-04-26 16:00:00,9089.0 -2014-04-26 17:00:00,9002.0 -2014-04-26 18:00:00,9005.0 -2014-04-26 19:00:00,9007.0 -2014-04-26 20:00:00,9088.0 -2014-04-26 21:00:00,9431.0 -2014-04-26 22:00:00,10004.0 -2014-04-26 23:00:00,9829.0 -2014-04-27 00:00:00,9391.0 -2014-04-25 01:00:00,9179.0 -2014-04-25 02:00:00,8688.0 -2014-04-25 03:00:00,8447.0 -2014-04-25 04:00:00,8300.0 -2014-04-25 05:00:00,8217.0 -2014-04-25 06:00:00,8407.0 -2014-04-25 07:00:00,9076.0 -2014-04-25 08:00:00,9996.0 -2014-04-25 09:00:00,10628.0 -2014-04-25 10:00:00,10850.0 -2014-04-25 11:00:00,10864.0 -2014-04-25 12:00:00,10946.0 -2014-04-25 13:00:00,10974.0 -2014-04-25 14:00:00,10978.0 -2014-04-25 15:00:00,11023.0 -2014-04-25 16:00:00,10975.0 -2014-04-25 17:00:00,10825.0 -2014-04-25 18:00:00,10678.0 -2014-04-25 19:00:00,10473.0 -2014-04-25 20:00:00,10242.0 -2014-04-25 21:00:00,10348.0 -2014-04-25 22:00:00,10704.0 -2014-04-25 23:00:00,10409.0 -2014-04-26 00:00:00,9752.0 -2014-04-24 01:00:00,9497.0 -2014-04-24 02:00:00,9000.0 -2014-04-24 03:00:00,8705.0 -2014-04-24 04:00:00,8545.0 -2014-04-24 05:00:00,8498.0 -2014-04-24 06:00:00,8763.0 -2014-04-24 07:00:00,9439.0 -2014-04-24 08:00:00,10288.0 -2014-04-24 09:00:00,10942.0 -2014-04-24 10:00:00,11196.0 -2014-04-24 11:00:00,11213.0 -2014-04-24 12:00:00,11278.0 -2014-04-24 13:00:00,11253.0 -2014-04-24 14:00:00,11199.0 -2014-04-24 15:00:00,11235.0 -2014-04-24 16:00:00,11107.0 -2014-04-24 17:00:00,10957.0 -2014-04-24 18:00:00,10856.0 -2014-04-24 19:00:00,10766.0 -2014-04-24 20:00:00,10704.0 -2014-04-24 21:00:00,11043.0 -2014-04-24 22:00:00,11145.0 -2014-04-24 23:00:00,10663.0 -2014-04-25 00:00:00,9886.0 -2014-04-23 01:00:00,9296.0 -2014-04-23 02:00:00,8853.0 -2014-04-23 03:00:00,8558.0 -2014-04-23 04:00:00,8436.0 -2014-04-23 05:00:00,8446.0 -2014-04-23 06:00:00,8722.0 -2014-04-23 07:00:00,9437.0 -2014-04-23 08:00:00,10247.0 -2014-04-23 09:00:00,10836.0 -2014-04-23 10:00:00,11108.0 -2014-04-23 11:00:00,11176.0 -2014-04-23 12:00:00,11210.0 -2014-04-23 13:00:00,11158.0 -2014-04-23 14:00:00,11091.0 -2014-04-23 15:00:00,11084.0 -2014-04-23 16:00:00,10973.0 -2014-04-23 17:00:00,10805.0 -2014-04-23 18:00:00,10696.0 -2014-04-23 19:00:00,10644.0 -2014-04-23 20:00:00,10656.0 -2014-04-23 21:00:00,11091.0 -2014-04-23 22:00:00,11317.0 -2014-04-23 23:00:00,10903.0 -2014-04-24 00:00:00,10191.0 -2014-04-22 01:00:00,9224.0 -2014-04-22 02:00:00,8687.0 -2014-04-22 03:00:00,8340.0 -2014-04-22 04:00:00,8098.0 -2014-04-22 05:00:00,8011.0 -2014-04-22 06:00:00,8211.0 -2014-04-22 07:00:00,8822.0 -2014-04-22 08:00:00,9615.0 -2014-04-22 09:00:00,10316.0 -2014-04-22 10:00:00,10646.0 -2014-04-22 11:00:00,10804.0 -2014-04-22 12:00:00,10903.0 -2014-04-22 13:00:00,10879.0 -2014-04-22 14:00:00,10853.0 -2014-04-22 15:00:00,10903.0 -2014-04-22 16:00:00,10799.0 -2014-04-22 17:00:00,10658.0 -2014-04-22 18:00:00,10537.0 -2014-04-22 19:00:00,10429.0 -2014-04-22 20:00:00,10273.0 -2014-04-22 21:00:00,10555.0 -2014-04-22 22:00:00,11041.0 -2014-04-22 23:00:00,10688.0 -2014-04-23 00:00:00,10006.0 -2014-04-21 01:00:00,8299.0 -2014-04-21 02:00:00,7913.0 -2014-04-21 03:00:00,7728.0 -2014-04-21 04:00:00,7554.0 -2014-04-21 05:00:00,7563.0 -2014-04-21 06:00:00,7777.0 -2014-04-21 07:00:00,8472.0 -2014-04-21 08:00:00,9313.0 -2014-04-21 09:00:00,10142.0 -2014-04-21 10:00:00,10638.0 -2014-04-21 11:00:00,10929.0 -2014-04-21 12:00:00,11170.0 -2014-04-21 13:00:00,11275.0 -2014-04-21 14:00:00,11387.0 -2014-04-21 15:00:00,11526.0 -2014-04-21 16:00:00,11511.0 -2014-04-21 17:00:00,11367.0 -2014-04-21 18:00:00,11280.0 -2014-04-21 19:00:00,11238.0 -2014-04-21 20:00:00,11136.0 -2014-04-21 21:00:00,11299.0 -2014-04-21 22:00:00,11352.0 -2014-04-21 23:00:00,10827.0 -2014-04-22 00:00:00,10019.0 -2014-04-20 01:00:00,8582.0 -2014-04-20 02:00:00,8148.0 -2014-04-20 03:00:00,7833.0 -2014-04-20 04:00:00,7678.0 -2014-04-20 05:00:00,7548.0 -2014-04-20 06:00:00,7610.0 -2014-04-20 07:00:00,7681.0 -2014-04-20 08:00:00,7705.0 -2014-04-20 09:00:00,7878.0 -2014-04-20 10:00:00,8174.0 -2014-04-20 11:00:00,8309.0 -2014-04-20 12:00:00,8464.0 -2014-04-20 13:00:00,8501.0 -2014-04-20 14:00:00,8547.0 -2014-04-20 15:00:00,8480.0 -2014-04-20 16:00:00,8474.0 -2014-04-20 17:00:00,8433.0 -2014-04-20 18:00:00,8479.0 -2014-04-20 19:00:00,8502.0 -2014-04-20 20:00:00,8557.0 -2014-04-20 21:00:00,8961.0 -2014-04-20 22:00:00,9530.0 -2014-04-20 23:00:00,9341.0 -2014-04-21 00:00:00,8838.0 -2014-04-19 01:00:00,9149.0 -2014-04-19 02:00:00,8726.0 -2014-04-19 03:00:00,8438.0 -2014-04-19 04:00:00,8282.0 -2014-04-19 05:00:00,8208.0 -2014-04-19 06:00:00,8280.0 -2014-04-19 07:00:00,8533.0 -2014-04-19 08:00:00,8677.0 -2014-04-19 09:00:00,8977.0 -2014-04-19 10:00:00,9291.0 -2014-04-19 11:00:00,9461.0 -2014-04-19 12:00:00,9502.0 -2014-04-19 13:00:00,9430.0 -2014-04-19 14:00:00,9334.0 -2014-04-19 15:00:00,9200.0 -2014-04-19 16:00:00,9069.0 -2014-04-19 17:00:00,9049.0 -2014-04-19 18:00:00,8983.0 -2014-04-19 19:00:00,8989.0 -2014-04-19 20:00:00,8982.0 -2014-04-19 21:00:00,9326.0 -2014-04-19 22:00:00,9790.0 -2014-04-19 23:00:00,9572.0 -2014-04-20 00:00:00,9156.0 -2014-04-18 01:00:00,9192.0 -2014-04-18 02:00:00,8633.0 -2014-04-18 03:00:00,8353.0 -2014-04-18 04:00:00,8179.0 -2014-04-18 05:00:00,8132.0 -2014-04-18 06:00:00,8310.0 -2014-04-18 07:00:00,8843.0 -2014-04-18 08:00:00,9318.0 -2014-04-18 09:00:00,9713.0 -2014-04-18 10:00:00,9986.0 -2014-04-18 11:00:00,10138.0 -2014-04-18 12:00:00,10251.0 -2014-04-18 13:00:00,10217.0 -2014-04-18 14:00:00,10152.0 -2014-04-18 15:00:00,10107.0 -2014-04-18 16:00:00,9962.0 -2014-04-18 17:00:00,9852.0 -2014-04-18 18:00:00,9755.0 -2014-04-18 19:00:00,9702.0 -2014-04-18 20:00:00,9641.0 -2014-04-18 21:00:00,9979.0 -2014-04-18 22:00:00,10419.0 -2014-04-18 23:00:00,10277.0 -2014-04-19 00:00:00,9729.0 -2014-04-17 01:00:00,9758.0 -2014-04-17 02:00:00,9319.0 -2014-04-17 03:00:00,9012.0 -2014-04-17 04:00:00,8834.0 -2014-04-17 05:00:00,8860.0 -2014-04-17 06:00:00,9098.0 -2014-04-17 07:00:00,9798.0 -2014-04-17 08:00:00,10645.0 -2014-04-17 09:00:00,11153.0 -2014-04-17 10:00:00,11297.0 -2014-04-17 11:00:00,11342.0 -2014-04-17 12:00:00,11476.0 -2014-04-17 13:00:00,11268.0 -2014-04-17 14:00:00,11171.0 -2014-04-17 15:00:00,11176.0 -2014-04-17 16:00:00,11027.0 -2014-04-17 17:00:00,10831.0 -2014-04-17 18:00:00,10674.0 -2014-04-17 19:00:00,10512.0 -2014-04-17 20:00:00,10383.0 -2014-04-17 21:00:00,10712.0 -2014-04-17 22:00:00,10983.0 -2014-04-17 23:00:00,10612.0 -2014-04-18 00:00:00,9904.0 -2014-04-16 01:00:00,10172.0 -2014-04-16 02:00:00,9704.0 -2014-04-16 03:00:00,9455.0 -2014-04-16 04:00:00,9313.0 -2014-04-16 05:00:00,9346.0 -2014-04-16 06:00:00,9575.0 -2014-04-16 07:00:00,10285.0 -2014-04-16 08:00:00,11132.0 -2014-04-16 09:00:00,11630.0 -2014-04-16 10:00:00,11796.0 -2014-04-16 11:00:00,11818.0 -2014-04-16 12:00:00,11853.0 -2014-04-16 13:00:00,11735.0 -2014-04-16 14:00:00,11606.0 -2014-04-16 15:00:00,11499.0 -2014-04-16 16:00:00,11362.0 -2014-04-16 17:00:00,11109.0 -2014-04-16 18:00:00,11012.0 -2014-04-16 19:00:00,10928.0 -2014-04-16 20:00:00,10837.0 -2014-04-16 21:00:00,11347.0 -2014-04-16 22:00:00,11634.0 -2014-04-16 23:00:00,11246.0 -2014-04-17 00:00:00,10487.0 -2014-04-15 01:00:00,10120.0 -2014-04-15 02:00:00,9673.0 -2014-04-15 03:00:00,9404.0 -2014-04-15 04:00:00,9268.0 -2014-04-15 05:00:00,9285.0 -2014-04-15 06:00:00,9535.0 -2014-04-15 07:00:00,10266.0 -2014-04-15 08:00:00,11136.0 -2014-04-15 09:00:00,11664.0 -2014-04-15 10:00:00,11843.0 -2014-04-15 11:00:00,11882.0 -2014-04-15 12:00:00,11946.0 -2014-04-15 13:00:00,11940.0 -2014-04-15 14:00:00,11872.0 -2014-04-15 15:00:00,11853.0 -2014-04-15 16:00:00,11681.0 -2014-04-15 17:00:00,11567.0 -2014-04-15 18:00:00,11437.0 -2014-04-15 19:00:00,11410.0 -2014-04-15 20:00:00,11299.0 -2014-04-15 21:00:00,11681.0 -2014-04-15 22:00:00,12034.0 -2014-04-15 23:00:00,11627.0 -2014-04-16 00:00:00,10915.0 -2014-04-14 01:00:00,8709.0 -2014-04-14 02:00:00,8306.0 -2014-04-14 03:00:00,8110.0 -2014-04-14 04:00:00,7990.0 -2014-04-14 05:00:00,8015.0 -2014-04-14 06:00:00,8210.0 -2014-04-14 07:00:00,8968.0 -2014-04-14 08:00:00,10099.0 -2014-04-14 09:00:00,10908.0 -2014-04-14 10:00:00,11278.0 -2014-04-14 11:00:00,11460.0 -2014-04-14 12:00:00,11605.0 -2014-04-14 13:00:00,11642.0 -2014-04-14 14:00:00,11615.0 -2014-04-14 15:00:00,11566.0 -2014-04-14 16:00:00,11515.0 -2014-04-14 17:00:00,11544.0 -2014-04-14 18:00:00,11606.0 -2014-04-14 19:00:00,11702.0 -2014-04-14 20:00:00,11734.0 -2014-04-14 21:00:00,12043.0 -2014-04-14 22:00:00,12103.0 -2014-04-14 23:00:00,11631.0 -2014-04-15 00:00:00,10854.0 -2014-04-13 01:00:00,8841.0 -2014-04-13 02:00:00,8357.0 -2014-04-13 03:00:00,8053.0 -2014-04-13 04:00:00,7829.0 -2014-04-13 05:00:00,7724.0 -2014-04-13 06:00:00,7652.0 -2014-04-13 07:00:00,7718.0 -2014-04-13 08:00:00,7754.0 -2014-04-13 09:00:00,7893.0 -2014-04-13 10:00:00,8314.0 -2014-04-13 11:00:00,8620.0 -2014-04-13 12:00:00,8970.0 -2014-04-13 13:00:00,9038.0 -2014-04-13 14:00:00,9037.0 -2014-04-13 15:00:00,9036.0 -2014-04-13 16:00:00,9083.0 -2014-04-13 17:00:00,9164.0 -2014-04-13 18:00:00,9234.0 -2014-04-13 19:00:00,9347.0 -2014-04-13 20:00:00,9681.0 -2014-04-13 21:00:00,10031.0 -2014-04-13 22:00:00,10063.0 -2014-04-13 23:00:00,9752.0 -2014-04-14 00:00:00,9214.0 -2014-04-12 01:00:00,9188.0 -2014-04-12 02:00:00,8663.0 -2014-04-12 03:00:00,8304.0 -2014-04-12 04:00:00,8123.0 -2014-04-12 05:00:00,8026.0 -2014-04-12 06:00:00,8089.0 -2014-04-12 07:00:00,8345.0 -2014-04-12 08:00:00,8613.0 -2014-04-12 09:00:00,8937.0 -2014-04-12 10:00:00,9357.0 -2014-04-12 11:00:00,9626.0 -2014-04-12 12:00:00,9918.0 -2014-04-12 13:00:00,9975.0 -2014-04-12 14:00:00,9866.0 -2014-04-12 15:00:00,9706.0 -2014-04-12 16:00:00,9675.0 -2014-04-12 17:00:00,9605.0 -2014-04-12 18:00:00,9591.0 -2014-04-12 19:00:00,9571.0 -2014-04-12 20:00:00,9588.0 -2014-04-12 21:00:00,9986.0 -2014-04-12 22:00:00,10210.0 -2014-04-12 23:00:00,9936.0 -2014-04-13 00:00:00,9408.0 -2014-04-11 01:00:00,9232.0 -2014-04-11 02:00:00,8765.0 -2014-04-11 03:00:00,8509.0 -2014-04-11 04:00:00,8395.0 -2014-04-11 05:00:00,8394.0 -2014-04-11 06:00:00,8595.0 -2014-04-11 07:00:00,9311.0 -2014-04-11 08:00:00,10217.0 -2014-04-11 09:00:00,10710.0 -2014-04-11 10:00:00,10920.0 -2014-04-11 11:00:00,10988.0 -2014-04-11 12:00:00,11099.0 -2014-04-11 13:00:00,11099.0 -2014-04-11 14:00:00,11053.0 -2014-04-11 15:00:00,11066.0 -2014-04-11 16:00:00,10950.0 -2014-04-11 17:00:00,10790.0 -2014-04-11 18:00:00,10641.0 -2014-04-11 19:00:00,10459.0 -2014-04-11 20:00:00,10294.0 -2014-04-11 21:00:00,10590.0 -2014-04-11 22:00:00,10817.0 -2014-04-11 23:00:00,10442.0 -2014-04-12 00:00:00,9872.0 -2014-04-10 01:00:00,9376.0 -2014-04-10 02:00:00,8896.0 -2014-04-10 03:00:00,8614.0 -2014-04-10 04:00:00,8464.0 -2014-04-10 05:00:00,8443.0 -2014-04-10 06:00:00,8638.0 -2014-04-10 07:00:00,9320.0 -2014-04-10 08:00:00,10200.0 -2014-04-10 09:00:00,10761.0 -2014-04-10 10:00:00,11022.0 -2014-04-10 11:00:00,11137.0 -2014-04-10 12:00:00,11240.0 -2014-04-10 13:00:00,11222.0 -2014-04-10 14:00:00,11178.0 -2014-04-10 15:00:00,11175.0 -2014-04-10 16:00:00,11075.0 -2014-04-10 17:00:00,10907.0 -2014-04-10 18:00:00,10816.0 -2014-04-10 19:00:00,10699.0 -2014-04-10 20:00:00,10635.0 -2014-04-10 21:00:00,10899.0 -2014-04-10 22:00:00,11127.0 -2014-04-10 23:00:00,10707.0 -2014-04-11 00:00:00,9934.0 -2014-04-09 01:00:00,9476.0 -2014-04-09 02:00:00,9068.0 -2014-04-09 03:00:00,8810.0 -2014-04-09 04:00:00,8669.0 -2014-04-09 05:00:00,8723.0 -2014-04-09 06:00:00,8934.0 -2014-04-09 07:00:00,9693.0 -2014-04-09 08:00:00,10665.0 -2014-04-09 09:00:00,11073.0 -2014-04-09 10:00:00,11217.0 -2014-04-09 11:00:00,11214.0 -2014-04-09 12:00:00,11247.0 -2014-04-09 13:00:00,11204.0 -2014-04-09 14:00:00,11162.0 -2014-04-09 15:00:00,11132.0 -2014-04-09 16:00:00,11003.0 -2014-04-09 17:00:00,10886.0 -2014-04-09 18:00:00,10759.0 -2014-04-09 19:00:00,10646.0 -2014-04-09 20:00:00,10538.0 -2014-04-09 21:00:00,11064.0 -2014-04-09 22:00:00,11290.0 -2014-04-09 23:00:00,10840.0 -2014-04-10 00:00:00,10093.0 -2014-04-08 01:00:00,9582.0 -2014-04-08 02:00:00,9111.0 -2014-04-08 03:00:00,8809.0 -2014-04-08 04:00:00,8696.0 -2014-04-08 05:00:00,8658.0 -2014-04-08 06:00:00,8886.0 -2014-04-08 07:00:00,9587.0 -2014-04-08 08:00:00,10600.0 -2014-04-08 09:00:00,11121.0 -2014-04-08 10:00:00,11329.0 -2014-04-08 11:00:00,11360.0 -2014-04-08 12:00:00,11395.0 -2014-04-08 13:00:00,11305.0 -2014-04-08 14:00:00,11230.0 -2014-04-08 15:00:00,11224.0 -2014-04-08 16:00:00,11081.0 -2014-04-08 17:00:00,10941.0 -2014-04-08 18:00:00,10866.0 -2014-04-08 19:00:00,10795.0 -2014-04-08 20:00:00,10665.0 -2014-04-08 21:00:00,11084.0 -2014-04-08 22:00:00,11342.0 -2014-04-08 23:00:00,10916.0 -2014-04-09 00:00:00,10188.0 -2014-04-07 01:00:00,8896.0 -2014-04-07 02:00:00,8575.0 -2014-04-07 03:00:00,8418.0 -2014-04-07 04:00:00,8389.0 -2014-04-07 05:00:00,8465.0 -2014-04-07 06:00:00,8782.0 -2014-04-07 07:00:00,9564.0 -2014-04-07 08:00:00,10609.0 -2014-04-07 09:00:00,11246.0 -2014-04-07 10:00:00,11512.0 -2014-04-07 11:00:00,11568.0 -2014-04-07 12:00:00,11549.0 -2014-04-07 13:00:00,11465.0 -2014-04-07 14:00:00,11374.0 -2014-04-07 15:00:00,11373.0 -2014-04-07 16:00:00,11245.0 -2014-04-07 17:00:00,11137.0 -2014-04-07 18:00:00,11077.0 -2014-04-07 19:00:00,11097.0 -2014-04-07 20:00:00,11074.0 -2014-04-07 21:00:00,11513.0 -2014-04-07 22:00:00,11538.0 -2014-04-07 23:00:00,11104.0 -2014-04-08 00:00:00,10362.0 -2014-04-06 01:00:00,9321.0 -2014-04-06 02:00:00,8926.0 -2014-04-06 03:00:00,8698.0 -2014-04-06 04:00:00,8572.0 -2014-04-06 05:00:00,8503.0 -2014-04-06 06:00:00,8569.0 -2014-04-06 07:00:00,8716.0 -2014-04-06 08:00:00,8838.0 -2014-04-06 09:00:00,8884.0 -2014-04-06 10:00:00,9015.0 -2014-04-06 11:00:00,9130.0 -2014-04-06 12:00:00,9148.0 -2014-04-06 13:00:00,9105.0 -2014-04-06 14:00:00,9108.0 -2014-04-06 15:00:00,9040.0 -2014-04-06 16:00:00,8942.0 -2014-04-06 17:00:00,8848.0 -2014-04-06 18:00:00,8960.0 -2014-04-06 19:00:00,9105.0 -2014-04-06 20:00:00,9293.0 -2014-04-06 21:00:00,9842.0 -2014-04-06 22:00:00,10131.0 -2014-04-06 23:00:00,9849.0 -2014-04-07 00:00:00,9399.0 -2014-04-05 01:00:00,10347.0 -2014-04-05 02:00:00,9759.0 -2014-04-05 03:00:00,9424.0 -2014-04-05 04:00:00,9194.0 -2014-04-05 05:00:00,9125.0 -2014-04-05 06:00:00,9176.0 -2014-04-05 07:00:00,9475.0 -2014-04-05 08:00:00,9828.0 -2014-04-05 09:00:00,9999.0 -2014-04-05 10:00:00,10228.0 -2014-04-05 11:00:00,10298.0 -2014-04-05 12:00:00,10259.0 -2014-04-05 13:00:00,10109.0 -2014-04-05 14:00:00,9938.0 -2014-04-05 15:00:00,9734.0 -2014-04-05 16:00:00,9564.0 -2014-04-05 17:00:00,9406.0 -2014-04-05 18:00:00,9349.0 -2014-04-05 19:00:00,9351.0 -2014-04-05 20:00:00,9451.0 -2014-04-05 21:00:00,9971.0 -2014-04-05 22:00:00,10355.0 -2014-04-05 23:00:00,10215.0 -2014-04-06 00:00:00,9801.0 -2014-04-04 01:00:00,10164.0 -2014-04-04 02:00:00,9662.0 -2014-04-04 03:00:00,9305.0 -2014-04-04 04:00:00,9164.0 -2014-04-04 05:00:00,9111.0 -2014-04-04 06:00:00,9330.0 -2014-04-04 07:00:00,9995.0 -2014-04-04 08:00:00,11075.0 -2014-04-04 09:00:00,11678.0 -2014-04-04 10:00:00,11833.0 -2014-04-04 11:00:00,11910.0 -2014-04-04 12:00:00,12086.0 -2014-04-04 13:00:00,12149.0 -2014-04-04 14:00:00,12121.0 -2014-04-04 15:00:00,12165.0 -2014-04-04 16:00:00,12053.0 -2014-04-04 17:00:00,11990.0 -2014-04-04 18:00:00,11939.0 -2014-04-04 19:00:00,11871.0 -2014-04-04 20:00:00,11857.0 -2014-04-04 21:00:00,12097.0 -2014-04-04 22:00:00,11988.0 -2014-04-04 23:00:00,11621.0 -2014-04-05 00:00:00,11004.0 -2014-04-03 01:00:00,10083.0 -2014-04-03 02:00:00,9599.0 -2014-04-03 03:00:00,9297.0 -2014-04-03 04:00:00,9142.0 -2014-04-03 05:00:00,9067.0 -2014-04-03 06:00:00,9309.0 -2014-04-03 07:00:00,10069.0 -2014-04-03 08:00:00,11245.0 -2014-04-03 09:00:00,12072.0 -2014-04-03 10:00:00,12389.0 -2014-04-03 11:00:00,12454.0 -2014-04-03 12:00:00,12436.0 -2014-04-03 13:00:00,12371.0 -2014-04-03 14:00:00,12297.0 -2014-04-03 15:00:00,12299.0 -2014-04-03 16:00:00,12213.0 -2014-04-03 17:00:00,12093.0 -2014-04-03 18:00:00,12086.0 -2014-04-03 19:00:00,12038.0 -2014-04-03 20:00:00,11987.0 -2014-04-03 21:00:00,12322.0 -2014-04-03 22:00:00,12191.0 -2014-04-03 23:00:00,11720.0 -2014-04-04 00:00:00,10895.0 -2014-04-02 01:00:00,9795.0 -2014-04-02 02:00:00,9308.0 -2014-04-02 03:00:00,9034.0 -2014-04-02 04:00:00,8858.0 -2014-04-02 05:00:00,8879.0 -2014-04-02 06:00:00,9153.0 -2014-04-02 07:00:00,9898.0 -2014-04-02 08:00:00,10966.0 -2014-04-02 09:00:00,11405.0 -2014-04-02 10:00:00,11598.0 -2014-04-02 11:00:00,11642.0 -2014-04-02 12:00:00,11636.0 -2014-04-02 13:00:00,11573.0 -2014-04-02 14:00:00,11472.0 -2014-04-02 15:00:00,11479.0 -2014-04-02 16:00:00,11342.0 -2014-04-02 17:00:00,11203.0 -2014-04-02 18:00:00,11215.0 -2014-04-02 19:00:00,11329.0 -2014-04-02 20:00:00,11500.0 -2014-04-02 21:00:00,12030.0 -2014-04-02 22:00:00,12002.0 -2014-04-02 23:00:00,11574.0 -2014-04-03 00:00:00,10801.0 -2014-04-01 01:00:00,9407.0 -2014-04-01 02:00:00,8906.0 -2014-04-01 03:00:00,8580.0 -2014-04-01 04:00:00,8468.0 -2014-04-01 05:00:00,8477.0 -2014-04-01 06:00:00,8718.0 -2014-04-01 07:00:00,9433.0 -2014-04-01 08:00:00,10727.0 -2014-04-01 09:00:00,11429.0 -2014-04-01 10:00:00,11804.0 -2014-04-01 11:00:00,11924.0 -2014-04-01 12:00:00,12088.0 -2014-04-01 13:00:00,12072.0 -2014-04-01 14:00:00,11956.0 -2014-04-01 15:00:00,11856.0 -2014-04-01 16:00:00,11637.0 -2014-04-01 17:00:00,11390.0 -2014-04-01 18:00:00,11166.0 -2014-04-01 19:00:00,11081.0 -2014-04-01 20:00:00,11103.0 -2014-04-01 21:00:00,11707.0 -2014-04-01 22:00:00,11722.0 -2014-04-01 23:00:00,11277.0 -2014-04-02 00:00:00,10535.0 -2014-03-31 01:00:00,9112.0 -2014-03-31 02:00:00,8791.0 -2014-03-31 03:00:00,8598.0 -2014-03-31 04:00:00,8532.0 -2014-03-31 05:00:00,8566.0 -2014-03-31 06:00:00,8860.0 -2014-03-31 07:00:00,9644.0 -2014-03-31 08:00:00,10647.0 -2014-03-31 09:00:00,11147.0 -2014-03-31 10:00:00,11354.0 -2014-03-31 11:00:00,11404.0 -2014-03-31 12:00:00,11412.0 -2014-03-31 13:00:00,11366.0 -2014-03-31 14:00:00,11327.0 -2014-03-31 15:00:00,11351.0 -2014-03-31 16:00:00,11196.0 -2014-03-31 17:00:00,11087.0 -2014-03-31 18:00:00,10991.0 -2014-03-31 19:00:00,10932.0 -2014-03-31 20:00:00,10839.0 -2014-03-31 21:00:00,11324.0 -2014-03-31 22:00:00,11355.0 -2014-03-31 23:00:00,10893.0 -2014-04-01 00:00:00,10104.0 -2014-03-30 01:00:00,9950.0 -2014-03-30 02:00:00,9585.0 -2014-03-30 03:00:00,9301.0 -2014-03-30 04:00:00,9172.0 -2014-03-30 05:00:00,9108.0 -2014-03-30 06:00:00,9170.0 -2014-03-30 07:00:00,9304.0 -2014-03-30 08:00:00,9481.0 -2014-03-30 09:00:00,9458.0 -2014-03-30 10:00:00,9604.0 -2014-03-30 11:00:00,9645.0 -2014-03-30 12:00:00,9641.0 -2014-03-30 13:00:00,9589.0 -2014-03-30 14:00:00,9495.0 -2014-03-30 15:00:00,9380.0 -2014-03-30 16:00:00,9258.0 -2014-03-30 17:00:00,9173.0 -2014-03-30 18:00:00,9147.0 -2014-03-30 19:00:00,9255.0 -2014-03-30 20:00:00,9430.0 -2014-03-30 21:00:00,10186.0 -2014-03-30 22:00:00,10353.0 -2014-03-30 23:00:00,10121.0 -2014-03-31 00:00:00,9627.0 -2014-03-29 01:00:00,10225.0 -2014-03-29 02:00:00,9737.0 -2014-03-29 03:00:00,9469.0 -2014-03-29 04:00:00,9261.0 -2014-03-29 05:00:00,9228.0 -2014-03-29 06:00:00,9220.0 -2014-03-29 07:00:00,9544.0 -2014-03-29 08:00:00,9985.0 -2014-03-29 09:00:00,10345.0 -2014-03-29 10:00:00,10749.0 -2014-03-29 11:00:00,11011.0 -2014-03-29 12:00:00,11162.0 -2014-03-29 13:00:00,11086.0 -2014-03-29 14:00:00,10914.0 -2014-03-29 15:00:00,10653.0 -2014-03-29 16:00:00,10425.0 -2014-03-29 17:00:00,10136.0 -2014-03-29 18:00:00,10035.0 -2014-03-29 19:00:00,9947.0 -2014-03-29 20:00:00,10082.0 -2014-03-29 21:00:00,10760.0 -2014-03-29 22:00:00,11011.0 -2014-03-29 23:00:00,10857.0 -2014-03-30 00:00:00,10425.0 -2014-03-28 01:00:00,9988.0 -2014-03-28 02:00:00,9489.0 -2014-03-28 03:00:00,9135.0 -2014-03-28 04:00:00,9006.0 -2014-03-28 05:00:00,9032.0 -2014-03-28 06:00:00,9254.0 -2014-03-28 07:00:00,9938.0 -2014-03-28 08:00:00,10993.0 -2014-03-28 09:00:00,11667.0 -2014-03-28 10:00:00,11885.0 -2014-03-28 11:00:00,12073.0 -2014-03-28 12:00:00,12159.0 -2014-03-28 13:00:00,12018.0 -2014-03-28 14:00:00,11905.0 -2014-03-28 15:00:00,11813.0 -2014-03-28 16:00:00,11716.0 -2014-03-28 17:00:00,11564.0 -2014-03-28 18:00:00,11516.0 -2014-03-28 19:00:00,11521.0 -2014-03-28 20:00:00,11623.0 -2014-03-28 21:00:00,12003.0 -2014-03-28 22:00:00,11867.0 -2014-03-28 23:00:00,11533.0 -2014-03-29 00:00:00,10944.0 -2014-03-27 01:00:00,10485.0 -2014-03-27 02:00:00,10000.0 -2014-03-27 03:00:00,9672.0 -2014-03-27 04:00:00,9490.0 -2014-03-27 05:00:00,9477.0 -2014-03-27 06:00:00,9747.0 -2014-03-27 07:00:00,10433.0 -2014-03-27 08:00:00,11501.0 -2014-03-27 09:00:00,12094.0 -2014-03-27 10:00:00,12398.0 -2014-03-27 11:00:00,12530.0 -2014-03-27 12:00:00,12657.0 -2014-03-27 13:00:00,12642.0 -2014-03-27 14:00:00,12564.0 -2014-03-27 15:00:00,12502.0 -2014-03-27 16:00:00,12316.0 -2014-03-27 17:00:00,12109.0 -2014-03-27 18:00:00,12066.0 -2014-03-27 19:00:00,12010.0 -2014-03-27 20:00:00,12089.0 -2014-03-27 21:00:00,12306.0 -2014-03-27 22:00:00,12082.0 -2014-03-27 23:00:00,11573.0 -2014-03-28 00:00:00,10772.0 -2014-03-26 01:00:00,11005.0 -2014-03-26 02:00:00,10542.0 -2014-03-26 03:00:00,10325.0 -2014-03-26 04:00:00,10227.0 -2014-03-26 05:00:00,10249.0 -2014-03-26 06:00:00,10505.0 -2014-03-26 07:00:00,11231.0 -2014-03-26 08:00:00,12223.0 -2014-03-26 09:00:00,12591.0 -2014-03-26 10:00:00,12632.0 -2014-03-26 11:00:00,12590.0 -2014-03-26 12:00:00,12562.0 -2014-03-26 13:00:00,12438.0 -2014-03-26 14:00:00,12260.0 -2014-03-26 15:00:00,12163.0 -2014-03-26 16:00:00,11966.0 -2014-03-26 17:00:00,11711.0 -2014-03-26 18:00:00,11602.0 -2014-03-26 19:00:00,11627.0 -2014-03-26 20:00:00,11814.0 -2014-03-26 21:00:00,12410.0 -2014-03-26 22:00:00,12369.0 -2014-03-26 23:00:00,11941.0 -2014-03-27 00:00:00,11201.0 -2014-03-25 01:00:00,10631.0 -2014-03-25 02:00:00,10150.0 -2014-03-25 03:00:00,9908.0 -2014-03-25 04:00:00,9781.0 -2014-03-25 05:00:00,9806.0 -2014-03-25 06:00:00,10102.0 -2014-03-25 07:00:00,10828.0 -2014-03-25 08:00:00,11923.0 -2014-03-25 09:00:00,12332.0 -2014-03-25 10:00:00,12461.0 -2014-03-25 11:00:00,12564.0 -2014-03-25 12:00:00,12676.0 -2014-03-25 13:00:00,12698.0 -2014-03-25 14:00:00,12577.0 -2014-03-25 15:00:00,12565.0 -2014-03-25 16:00:00,12393.0 -2014-03-25 17:00:00,12218.0 -2014-03-25 18:00:00,12137.0 -2014-03-25 19:00:00,12096.0 -2014-03-25 20:00:00,12143.0 -2014-03-25 21:00:00,12781.0 -2014-03-25 22:00:00,12821.0 -2014-03-25 23:00:00,12379.0 -2014-03-26 00:00:00,11719.0 -2014-03-24 01:00:00,10264.0 -2014-03-24 02:00:00,9981.0 -2014-03-24 03:00:00,9809.0 -2014-03-24 04:00:00,9753.0 -2014-03-24 05:00:00,9842.0 -2014-03-24 06:00:00,10140.0 -2014-03-24 07:00:00,10814.0 -2014-03-24 08:00:00,11954.0 -2014-03-24 09:00:00,12462.0 -2014-03-24 10:00:00,12571.0 -2014-03-24 11:00:00,12506.0 -2014-03-24 12:00:00,12430.0 -2014-03-24 13:00:00,12322.0 -2014-03-24 14:00:00,12176.0 -2014-03-24 15:00:00,12114.0 -2014-03-24 16:00:00,11904.0 -2014-03-24 17:00:00,11705.0 -2014-03-24 18:00:00,11572.0 -2014-03-24 19:00:00,11573.0 -2014-03-24 20:00:00,11649.0 -2014-03-24 21:00:00,12347.0 -2014-03-24 22:00:00,12404.0 -2014-03-24 23:00:00,11997.0 -2014-03-25 00:00:00,11336.0 -2014-03-23 01:00:00,9954.0 -2014-03-23 02:00:00,9575.0 -2014-03-23 03:00:00,9401.0 -2014-03-23 04:00:00,9280.0 -2014-03-23 05:00:00,9266.0 -2014-03-23 06:00:00,9311.0 -2014-03-23 07:00:00,9516.0 -2014-03-23 08:00:00,9790.0 -2014-03-23 09:00:00,9888.0 -2014-03-23 10:00:00,10139.0 -2014-03-23 11:00:00,10261.0 -2014-03-23 12:00:00,10254.0 -2014-03-23 13:00:00,10280.0 -2014-03-23 14:00:00,10254.0 -2014-03-23 15:00:00,10147.0 -2014-03-23 16:00:00,10042.0 -2014-03-23 17:00:00,10031.0 -2014-03-23 18:00:00,10009.0 -2014-03-23 19:00:00,10208.0 -2014-03-23 20:00:00,10508.0 -2014-03-23 21:00:00,11285.0 -2014-03-23 22:00:00,11425.0 -2014-03-23 23:00:00,11177.0 -2014-03-24 00:00:00,10754.0 -2014-03-22 01:00:00,9889.0 -2014-03-22 02:00:00,9439.0 -2014-03-22 03:00:00,9114.0 -2014-03-22 04:00:00,8961.0 -2014-03-22 05:00:00,8873.0 -2014-03-22 06:00:00,8946.0 -2014-03-22 07:00:00,9202.0 -2014-03-22 08:00:00,9710.0 -2014-03-22 09:00:00,9979.0 -2014-03-22 10:00:00,10384.0 -2014-03-22 11:00:00,10578.0 -2014-03-22 12:00:00,10705.0 -2014-03-22 13:00:00,10656.0 -2014-03-22 14:00:00,10539.0 -2014-03-22 15:00:00,10331.0 -2014-03-22 16:00:00,10133.0 -2014-03-22 17:00:00,10099.0 -2014-03-22 18:00:00,10041.0 -2014-03-22 19:00:00,10091.0 -2014-03-22 20:00:00,10295.0 -2014-03-22 21:00:00,10975.0 -2014-03-22 22:00:00,11051.0 -2014-03-22 23:00:00,10863.0 -2014-03-23 00:00:00,10426.0 -2014-03-21 01:00:00,10257.0 -2014-03-21 02:00:00,9754.0 -2014-03-21 03:00:00,9450.0 -2014-03-21 04:00:00,9331.0 -2014-03-21 05:00:00,9303.0 -2014-03-21 06:00:00,9543.0 -2014-03-21 07:00:00,10254.0 -2014-03-21 08:00:00,11399.0 -2014-03-21 09:00:00,11853.0 -2014-03-21 10:00:00,11926.0 -2014-03-21 11:00:00,11852.0 -2014-03-21 12:00:00,11767.0 -2014-03-21 13:00:00,11617.0 -2014-03-21 14:00:00,11446.0 -2014-03-21 15:00:00,11401.0 -2014-03-21 16:00:00,11197.0 -2014-03-21 17:00:00,11009.0 -2014-03-21 18:00:00,10975.0 -2014-03-21 19:00:00,11000.0 -2014-03-21 20:00:00,11138.0 -2014-03-21 21:00:00,11540.0 -2014-03-21 22:00:00,11370.0 -2014-03-21 23:00:00,11041.0 -2014-03-22 00:00:00,10487.0 -2014-03-20 01:00:00,10477.0 -2014-03-20 02:00:00,9955.0 -2014-03-20 03:00:00,9655.0 -2014-03-20 04:00:00,9490.0 -2014-03-20 05:00:00,9486.0 -2014-03-20 06:00:00,9741.0 -2014-03-20 07:00:00,10440.0 -2014-03-20 08:00:00,11641.0 -2014-03-20 09:00:00,12218.0 -2014-03-20 10:00:00,12333.0 -2014-03-20 11:00:00,12213.0 -2014-03-20 12:00:00,12134.0 -2014-03-20 13:00:00,11972.0 -2014-03-20 14:00:00,11806.0 -2014-03-20 15:00:00,11731.0 -2014-03-20 16:00:00,11538.0 -2014-03-20 17:00:00,11343.0 -2014-03-20 18:00:00,11230.0 -2014-03-20 19:00:00,11159.0 -2014-03-20 20:00:00,11214.0 -2014-03-20 21:00:00,11897.0 -2014-03-20 22:00:00,11982.0 -2014-03-20 23:00:00,11646.0 -2014-03-21 00:00:00,10960.0 -2014-03-19 01:00:00,10129.0 -2014-03-19 02:00:00,9633.0 -2014-03-19 03:00:00,9310.0 -2014-03-19 04:00:00,9159.0 -2014-03-19 05:00:00,9163.0 -2014-03-19 06:00:00,9380.0 -2014-03-19 07:00:00,10047.0 -2014-03-19 08:00:00,11274.0 -2014-03-19 09:00:00,12035.0 -2014-03-19 10:00:00,12169.0 -2014-03-19 11:00:00,12173.0 -2014-03-19 12:00:00,12272.0 -2014-03-19 13:00:00,12329.0 -2014-03-19 14:00:00,12307.0 -2014-03-19 15:00:00,12336.0 -2014-03-19 16:00:00,12253.0 -2014-03-19 17:00:00,12208.0 -2014-03-19 18:00:00,12227.0 -2014-03-19 19:00:00,12361.0 -2014-03-19 20:00:00,12453.0 -2014-03-19 21:00:00,12700.0 -2014-03-19 22:00:00,12503.0 -2014-03-19 23:00:00,11984.0 -2014-03-20 00:00:00,11230.0 -2014-03-18 01:00:00,10689.0 -2014-03-18 02:00:00,10218.0 -2014-03-18 03:00:00,9959.0 -2014-03-18 04:00:00,9869.0 -2014-03-18 05:00:00,9874.0 -2014-03-18 06:00:00,10122.0 -2014-03-18 07:00:00,10847.0 -2014-03-18 08:00:00,12012.0 -2014-03-18 09:00:00,12396.0 -2014-03-18 10:00:00,12438.0 -2014-03-18 11:00:00,12361.0 -2014-03-18 12:00:00,12257.0 -2014-03-18 13:00:00,12135.0 -2014-03-18 14:00:00,11980.0 -2014-03-18 15:00:00,11968.0 -2014-03-18 16:00:00,11760.0 -2014-03-18 17:00:00,11617.0 -2014-03-18 18:00:00,11488.0 -2014-03-18 19:00:00,11393.0 -2014-03-18 20:00:00,11547.0 -2014-03-18 21:00:00,12172.0 -2014-03-18 22:00:00,12136.0 -2014-03-18 23:00:00,11633.0 -2014-03-19 00:00:00,10911.0 -2014-03-17 01:00:00,10713.0 -2014-03-17 02:00:00,10411.0 -2014-03-17 03:00:00,10291.0 -2014-03-17 04:00:00,10213.0 -2014-03-17 05:00:00,10334.0 -2014-03-17 06:00:00,10583.0 -2014-03-17 07:00:00,11395.0 -2014-03-17 08:00:00,12572.0 -2014-03-17 09:00:00,13036.0 -2014-03-17 10:00:00,12994.0 -2014-03-17 11:00:00,12940.0 -2014-03-17 12:00:00,12845.0 -2014-03-17 13:00:00,12742.0 -2014-03-17 14:00:00,12572.0 -2014-03-17 15:00:00,12475.0 -2014-03-17 16:00:00,12209.0 -2014-03-17 17:00:00,11995.0 -2014-03-17 18:00:00,11868.0 -2014-03-17 19:00:00,11809.0 -2014-03-17 20:00:00,11908.0 -2014-03-17 21:00:00,12619.0 -2014-03-17 22:00:00,12601.0 -2014-03-17 23:00:00,12195.0 -2014-03-18 00:00:00,11448.0 -2014-03-16 01:00:00,10385.0 -2014-03-16 02:00:00,9958.0 -2014-03-16 03:00:00,9758.0 -2014-03-16 04:00:00,9620.0 -2014-03-16 05:00:00,9655.0 -2014-03-16 06:00:00,9752.0 -2014-03-16 07:00:00,9935.0 -2014-03-16 08:00:00,10304.0 -2014-03-16 09:00:00,10441.0 -2014-03-16 10:00:00,10734.0 -2014-03-16 11:00:00,10958.0 -2014-03-16 12:00:00,11047.0 -2014-03-16 13:00:00,11050.0 -2014-03-16 14:00:00,10983.0 -2014-03-16 15:00:00,10894.0 -2014-03-16 16:00:00,10791.0 -2014-03-16 17:00:00,10661.0 -2014-03-16 18:00:00,10764.0 -2014-03-16 19:00:00,10912.0 -2014-03-16 20:00:00,11230.0 -2014-03-16 21:00:00,12047.0 -2014-03-16 22:00:00,12166.0 -2014-03-16 23:00:00,11854.0 -2014-03-17 00:00:00,11292.0 -2014-03-15 01:00:00,10290.0 -2014-03-15 02:00:00,9799.0 -2014-03-15 03:00:00,9478.0 -2014-03-15 04:00:00,9297.0 -2014-03-15 05:00:00,9186.0 -2014-03-15 06:00:00,9263.0 -2014-03-15 07:00:00,9593.0 -2014-03-15 08:00:00,10130.0 -2014-03-15 09:00:00,10426.0 -2014-03-15 10:00:00,10715.0 -2014-03-15 11:00:00,10917.0 -2014-03-15 12:00:00,10886.0 -2014-03-15 13:00:00,10670.0 -2014-03-15 14:00:00,10592.0 -2014-03-15 15:00:00,10314.0 -2014-03-15 16:00:00,10181.0 -2014-03-15 17:00:00,10109.0 -2014-03-15 18:00:00,10155.0 -2014-03-15 19:00:00,10349.0 -2014-03-15 20:00:00,10686.0 -2014-03-15 21:00:00,11355.0 -2014-03-15 22:00:00,11388.0 -2014-03-15 23:00:00,11240.0 -2014-03-16 00:00:00,10831.0 -2014-03-14 01:00:00,10995.0 -2014-03-14 02:00:00,10461.0 -2014-03-14 03:00:00,10105.0 -2014-03-14 04:00:00,9954.0 -2014-03-14 05:00:00,9878.0 -2014-03-14 06:00:00,10047.0 -2014-03-14 07:00:00,10727.0 -2014-03-14 08:00:00,11868.0 -2014-03-14 09:00:00,12271.0 -2014-03-14 10:00:00,12201.0 -2014-03-14 11:00:00,12131.0 -2014-03-14 12:00:00,12061.0 -2014-03-14 13:00:00,11940.0 -2014-03-14 14:00:00,11743.0 -2014-03-14 15:00:00,11631.0 -2014-03-14 16:00:00,11430.0 -2014-03-14 17:00:00,11217.0 -2014-03-14 18:00:00,11103.0 -2014-03-14 19:00:00,11058.0 -2014-03-14 20:00:00,11256.0 -2014-03-14 21:00:00,11886.0 -2014-03-14 22:00:00,11837.0 -2014-03-14 23:00:00,11574.0 -2014-03-15 00:00:00,10941.0 -2014-03-13 01:00:00,11362.0 -2014-03-13 02:00:00,10874.0 -2014-03-13 03:00:00,10618.0 -2014-03-13 04:00:00,10545.0 -2014-03-13 05:00:00,10566.0 -2014-03-13 06:00:00,10822.0 -2014-03-13 07:00:00,11489.0 -2014-03-13 08:00:00,12672.0 -2014-03-13 09:00:00,13136.0 -2014-03-13 10:00:00,13042.0 -2014-03-13 11:00:00,12999.0 -2014-03-13 12:00:00,12967.0 -2014-03-13 13:00:00,12997.0 -2014-03-13 14:00:00,13006.0 -2014-03-13 15:00:00,12964.0 -2014-03-13 16:00:00,12789.0 -2014-03-13 17:00:00,12551.0 -2014-03-13 18:00:00,12315.0 -2014-03-13 19:00:00,12210.0 -2014-03-13 20:00:00,12373.0 -2014-03-13 21:00:00,13073.0 -2014-03-13 22:00:00,12970.0 -2014-03-13 23:00:00,12558.0 -2014-03-14 00:00:00,11789.0 -2014-03-12 01:00:00,10430.0 -2014-03-12 02:00:00,10095.0 -2014-03-12 03:00:00,9844.0 -2014-03-12 04:00:00,9742.0 -2014-03-12 05:00:00,9735.0 -2014-03-12 06:00:00,9972.0 -2014-03-12 07:00:00,10602.0 -2014-03-12 08:00:00,11716.0 -2014-03-12 09:00:00,12313.0 -2014-03-12 10:00:00,12522.0 -2014-03-12 11:00:00,12650.0 -2014-03-12 12:00:00,12639.0 -2014-03-12 13:00:00,12530.0 -2014-03-12 14:00:00,12468.0 -2014-03-12 15:00:00,12500.0 -2014-03-12 16:00:00,12355.0 -2014-03-12 17:00:00,12211.0 -2014-03-12 18:00:00,12181.0 -2014-03-12 19:00:00,12213.0 -2014-03-12 20:00:00,12411.0 -2014-03-12 21:00:00,13161.0 -2014-03-12 22:00:00,13174.0 -2014-03-12 23:00:00,12774.0 -2014-03-13 00:00:00,12079.0 -2014-03-11 01:00:00,9963.0 -2014-03-11 02:00:00,9517.0 -2014-03-11 03:00:00,9196.0 -2014-03-11 04:00:00,9059.0 -2014-03-11 05:00:00,9016.0 -2014-03-11 06:00:00,9205.0 -2014-03-11 07:00:00,9910.0 -2014-03-11 08:00:00,11147.0 -2014-03-11 09:00:00,11804.0 -2014-03-11 10:00:00,11877.0 -2014-03-11 11:00:00,11865.0 -2014-03-11 12:00:00,11881.0 -2014-03-11 13:00:00,11774.0 -2014-03-11 14:00:00,11688.0 -2014-03-11 15:00:00,11753.0 -2014-03-11 16:00:00,11705.0 -2014-03-11 17:00:00,11700.0 -2014-03-11 18:00:00,11761.0 -2014-03-11 19:00:00,11874.0 -2014-03-11 20:00:00,12070.0 -2014-03-11 21:00:00,12577.0 -2014-03-11 22:00:00,12426.0 -2014-03-11 23:00:00,12000.0 -2014-03-12 00:00:00,11305.0 -2014-03-10 01:00:00,10076.0 -2014-03-10 02:00:00,9655.0 -2014-03-10 03:00:00,9416.0 -2014-03-10 04:00:00,9261.0 -2014-03-10 05:00:00,9233.0 -2014-03-10 06:00:00,9496.0 -2014-03-10 07:00:00,10223.0 -2014-03-10 08:00:00,11397.0 -2014-03-10 09:00:00,11996.0 -2014-03-10 10:00:00,11928.0 -2014-03-10 11:00:00,11831.0 -2014-03-10 12:00:00,11798.0 -2014-03-10 13:00:00,11669.0 -2014-03-10 14:00:00,11515.0 -2014-03-10 15:00:00,11467.0 -2014-03-10 16:00:00,11284.0 -2014-03-10 17:00:00,11111.0 -2014-03-10 18:00:00,11034.0 -2014-03-10 19:00:00,11029.0 -2014-03-10 20:00:00,11236.0 -2014-03-10 21:00:00,11978.0 -2014-03-10 22:00:00,11850.0 -2014-03-10 23:00:00,11418.0 -2014-03-11 00:00:00,10709.0 -2014-03-09 01:00:00,10586.0 -2014-03-09 02:00:00,10268.0 -2014-03-09 04:00:00,10030.0 -2014-03-09 05:00:00,9854.0 -2014-03-09 06:00:00,9912.0 -2014-03-09 07:00:00,9976.0 -2014-03-09 08:00:00,10219.0 -2014-03-09 09:00:00,10237.0 -2014-03-09 10:00:00,10290.0 -2014-03-09 11:00:00,10327.0 -2014-03-09 12:00:00,10371.0 -2014-03-09 13:00:00,10332.0 -2014-03-09 14:00:00,10275.0 -2014-03-09 15:00:00,10171.0 -2014-03-09 16:00:00,10084.0 -2014-03-09 17:00:00,10052.0 -2014-03-09 18:00:00,10203.0 -2014-03-09 19:00:00,10422.0 -2014-03-09 20:00:00,10854.0 -2014-03-09 21:00:00,11575.0 -2014-03-09 22:00:00,11494.0 -2014-03-09 23:00:00,11220.0 -2014-03-10 00:00:00,10618.0 -2014-03-08 01:00:00,10359.0 -2014-03-08 02:00:00,9901.0 -2014-03-08 03:00:00,9701.0 -2014-03-08 04:00:00,9574.0 -2014-03-08 05:00:00,9555.0 -2014-03-08 06:00:00,9688.0 -2014-03-08 07:00:00,10028.0 -2014-03-08 08:00:00,10401.0 -2014-03-08 09:00:00,10775.0 -2014-03-08 10:00:00,11228.0 -2014-03-08 11:00:00,11516.0 -2014-03-08 12:00:00,11555.0 -2014-03-08 13:00:00,11443.0 -2014-03-08 14:00:00,11238.0 -2014-03-08 15:00:00,11068.0 -2014-03-08 16:00:00,10883.0 -2014-03-08 17:00:00,10842.0 -2014-03-08 18:00:00,10916.0 -2014-03-08 19:00:00,11240.0 -2014-03-08 20:00:00,11940.0 -2014-03-08 21:00:00,11980.0 -2014-03-08 22:00:00,11807.0 -2014-03-08 23:00:00,11590.0 -2014-03-09 00:00:00,11090.0 -2014-03-07 01:00:00,11360.0 -2014-03-07 02:00:00,11001.0 -2014-03-07 03:00:00,10755.0 -2014-03-07 04:00:00,10639.0 -2014-03-07 05:00:00,10594.0 -2014-03-07 06:00:00,10912.0 -2014-03-07 07:00:00,11572.0 -2014-03-07 08:00:00,12391.0 -2014-03-07 09:00:00,12671.0 -2014-03-07 10:00:00,12617.0 -2014-03-07 11:00:00,12469.0 -2014-03-07 12:00:00,12348.0 -2014-03-07 13:00:00,12184.0 -2014-03-07 14:00:00,12003.0 -2014-03-07 15:00:00,11895.0 -2014-03-07 16:00:00,11749.0 -2014-03-07 17:00:00,11615.0 -2014-03-07 18:00:00,11724.0 -2014-03-07 19:00:00,12022.0 -2014-03-07 20:00:00,12558.0 -2014-03-07 21:00:00,12377.0 -2014-03-07 22:00:00,12095.0 -2014-03-07 23:00:00,11687.0 -2014-03-08 00:00:00,10998.0 -2014-03-06 01:00:00,11665.0 -2014-03-06 02:00:00,11214.0 -2014-03-06 03:00:00,10989.0 -2014-03-06 04:00:00,10873.0 -2014-03-06 05:00:00,10903.0 -2014-03-06 06:00:00,11201.0 -2014-03-06 07:00:00,11911.0 -2014-03-06 08:00:00,12799.0 -2014-03-06 09:00:00,13112.0 -2014-03-06 10:00:00,13120.0 -2014-03-06 11:00:00,13026.0 -2014-03-06 12:00:00,12958.0 -2014-03-06 13:00:00,12808.0 -2014-03-06 14:00:00,12620.0 -2014-03-06 15:00:00,12536.0 -2014-03-06 16:00:00,12401.0 -2014-03-06 17:00:00,12305.0 -2014-03-06 18:00:00,12396.0 -2014-03-06 19:00:00,12732.0 -2014-03-06 20:00:00,13527.0 -2014-03-06 21:00:00,13550.0 -2014-03-06 22:00:00,13340.0 -2014-03-06 23:00:00,12885.0 -2014-03-07 00:00:00,12053.0 -2014-03-05 01:00:00,11523.0 -2014-03-05 02:00:00,11076.0 -2014-03-05 03:00:00,10839.0 -2014-03-05 04:00:00,10742.0 -2014-03-05 05:00:00,10793.0 -2014-03-05 06:00:00,11067.0 -2014-03-05 07:00:00,11798.0 -2014-03-05 08:00:00,12739.0 -2014-03-05 09:00:00,13232.0 -2014-03-05 10:00:00,13565.0 -2014-03-05 11:00:00,13441.0 -2014-03-05 12:00:00,13515.0 -2014-03-05 13:00:00,13419.0 -2014-03-05 14:00:00,13277.0 -2014-03-05 15:00:00,13199.0 -2014-03-05 16:00:00,13066.0 -2014-03-05 17:00:00,12995.0 -2014-03-05 18:00:00,13125.0 -2014-03-05 19:00:00,13557.0 -2014-03-05 20:00:00,14131.0 -2014-03-05 21:00:00,13991.0 -2014-03-05 22:00:00,13712.0 -2014-03-05 23:00:00,13176.0 -2014-03-06 00:00:00,12379.0 -2014-03-04 01:00:00,12006.0 -2014-03-04 02:00:00,11548.0 -2014-03-04 03:00:00,11308.0 -2014-03-04 04:00:00,11189.0 -2014-03-04 05:00:00,11183.0 -2014-03-04 06:00:00,11406.0 -2014-03-04 07:00:00,12090.0 -2014-03-04 08:00:00,13026.0 -2014-03-04 09:00:00,13457.0 -2014-03-04 10:00:00,13658.0 -2014-03-04 11:00:00,13625.0 -2014-03-04 12:00:00,13538.0 -2014-03-04 13:00:00,13293.0 -2014-03-04 14:00:00,13104.0 -2014-03-04 15:00:00,13010.0 -2014-03-04 16:00:00,12792.0 -2014-03-04 17:00:00,12683.0 -2014-03-04 18:00:00,12800.0 -2014-03-04 19:00:00,13203.0 -2014-03-04 20:00:00,13879.0 -2014-03-04 21:00:00,13825.0 -2014-03-04 22:00:00,13586.0 -2014-03-04 23:00:00,13023.0 -2014-03-05 00:00:00,12233.0 -2014-03-03 01:00:00,11911.0 -2014-03-03 02:00:00,11603.0 -2014-03-03 03:00:00,11453.0 -2014-03-03 04:00:00,11382.0 -2014-03-03 05:00:00,11464.0 -2014-03-03 06:00:00,11703.0 -2014-03-03 07:00:00,12494.0 -2014-03-03 08:00:00,13350.0 -2014-03-03 09:00:00,13847.0 -2014-03-03 10:00:00,13928.0 -2014-03-03 11:00:00,13945.0 -2014-03-03 12:00:00,13848.0 -2014-03-03 13:00:00,13729.0 -2014-03-03 14:00:00,13541.0 -2014-03-03 15:00:00,13380.0 -2014-03-03 16:00:00,13182.0 -2014-03-03 17:00:00,13026.0 -2014-03-03 18:00:00,13192.0 -2014-03-03 19:00:00,13717.0 -2014-03-03 20:00:00,14466.0 -2014-03-03 21:00:00,14387.0 -2014-03-03 22:00:00,14110.0 -2014-03-03 23:00:00,13557.0 -2014-03-04 00:00:00,12753.0 -2014-03-02 01:00:00,11503.0 -2014-03-02 02:00:00,11124.0 -2014-03-02 03:00:00,10934.0 -2014-03-02 04:00:00,10724.0 -2014-03-02 05:00:00,10681.0 -2014-03-02 06:00:00,10749.0 -2014-03-02 07:00:00,10920.0 -2014-03-02 08:00:00,11052.0 -2014-03-02 09:00:00,11172.0 -2014-03-02 10:00:00,11443.0 -2014-03-02 11:00:00,11626.0 -2014-03-02 12:00:00,11619.0 -2014-03-02 13:00:00,11525.0 -2014-03-02 14:00:00,11451.0 -2014-03-02 15:00:00,11449.0 -2014-03-02 16:00:00,11484.0 -2014-03-02 17:00:00,11636.0 -2014-03-02 18:00:00,11915.0 -2014-03-02 19:00:00,12530.0 -2014-03-02 20:00:00,13377.0 -2014-03-02 21:00:00,13386.0 -2014-03-02 22:00:00,13224.0 -2014-03-02 23:00:00,12883.0 -2014-03-03 00:00:00,12429.0 -2014-03-01 01:00:00,11406.0 -2014-03-01 02:00:00,10816.0 -2014-03-01 03:00:00,10484.0 -2014-03-01 04:00:00,10291.0 -2014-03-01 05:00:00,10202.0 -2014-03-01 06:00:00,10294.0 -2014-03-01 07:00:00,10577.0 -2014-03-01 08:00:00,11035.0 -2014-03-01 09:00:00,11371.0 -2014-03-01 10:00:00,11799.0 -2014-03-01 11:00:00,12003.0 -2014-03-01 12:00:00,12092.0 -2014-03-01 13:00:00,11990.0 -2014-03-01 14:00:00,11807.0 -2014-03-01 15:00:00,11671.0 -2014-03-01 16:00:00,11664.0 -2014-03-01 17:00:00,11757.0 -2014-03-01 18:00:00,12057.0 -2014-03-01 19:00:00,12584.0 -2014-03-01 20:00:00,13051.0 -2014-03-01 21:00:00,13005.0 -2014-03-01 22:00:00,12833.0 -2014-03-01 23:00:00,12521.0 -2014-03-02 00:00:00,12028.0 -2014-02-28 01:00:00,12586.0 -2014-02-28 02:00:00,12201.0 -2014-02-28 03:00:00,11983.0 -2014-02-28 04:00:00,11865.0 -2014-02-28 05:00:00,11885.0 -2014-02-28 06:00:00,12084.0 -2014-02-28 07:00:00,12705.0 -2014-02-28 08:00:00,13500.0 -2014-02-28 09:00:00,13918.0 -2014-02-28 10:00:00,14178.0 -2014-02-28 11:00:00,14147.0 -2014-02-28 12:00:00,14071.0 -2014-02-28 13:00:00,13783.0 -2014-02-28 14:00:00,13483.0 -2014-02-28 15:00:00,13366.0 -2014-02-28 16:00:00,13170.0 -2014-02-28 17:00:00,13017.0 -2014-02-28 18:00:00,12994.0 -2014-02-28 19:00:00,13348.0 -2014-02-28 20:00:00,13839.0 -2014-02-28 21:00:00,13632.0 -2014-02-28 22:00:00,13285.0 -2014-02-28 23:00:00,12884.0 -2014-03-01 00:00:00,12100.0 -2014-02-27 01:00:00,12421.0 -2014-02-27 02:00:00,11964.0 -2014-02-27 03:00:00,11739.0 -2014-02-27 04:00:00,11683.0 -2014-02-27 05:00:00,11725.0 -2014-02-27 06:00:00,12043.0 -2014-02-27 07:00:00,12733.0 -2014-02-27 08:00:00,13648.0 -2014-02-27 09:00:00,14017.0 -2014-02-27 10:00:00,14081.0 -2014-02-27 11:00:00,14098.0 -2014-02-27 12:00:00,14081.0 -2014-02-27 13:00:00,13952.0 -2014-02-27 14:00:00,13797.0 -2014-02-27 15:00:00,13668.0 -2014-02-27 16:00:00,13509.0 -2014-02-27 17:00:00,13367.0 -2014-02-27 18:00:00,13470.0 -2014-02-27 19:00:00,14022.0 -2014-02-27 20:00:00,14745.0 -2014-02-27 21:00:00,14735.0 -2014-02-27 22:00:00,14548.0 -2014-02-27 23:00:00,14087.0 -2014-02-28 00:00:00,13298.0 -2014-02-26 01:00:00,12446.0 -2014-02-26 02:00:00,12073.0 -2014-02-26 03:00:00,11875.0 -2014-02-26 04:00:00,11759.0 -2014-02-26 05:00:00,11783.0 -2014-02-26 06:00:00,12051.0 -2014-02-26 07:00:00,12735.0 -2014-02-26 08:00:00,13655.0 -2014-02-26 09:00:00,13985.0 -2014-02-26 10:00:00,14050.0 -2014-02-26 11:00:00,14057.0 -2014-02-26 12:00:00,14094.0 -2014-02-26 13:00:00,13982.0 -2014-02-26 14:00:00,13909.0 -2014-02-26 15:00:00,13874.0 -2014-02-26 16:00:00,13794.0 -2014-02-26 17:00:00,13745.0 -2014-02-26 18:00:00,13884.0 -2014-02-26 19:00:00,14451.0 -2014-02-26 20:00:00,15065.0 -2014-02-26 21:00:00,14928.0 -2014-02-26 22:00:00,14631.0 -2014-02-26 23:00:00,14071.0 -2014-02-27 00:00:00,13208.0 -2014-02-25 01:00:00,11545.0 -2014-02-25 02:00:00,11121.0 -2014-02-25 03:00:00,10867.0 -2014-02-25 04:00:00,10758.0 -2014-02-25 05:00:00,10757.0 -2014-02-25 06:00:00,11048.0 -2014-02-25 07:00:00,11825.0 -2014-02-25 08:00:00,12805.0 -2014-02-25 09:00:00,13161.0 -2014-02-25 10:00:00,13225.0 -2014-02-25 11:00:00,13196.0 -2014-02-25 12:00:00,13163.0 -2014-02-25 13:00:00,13084.0 -2014-02-25 14:00:00,12995.0 -2014-02-25 15:00:00,13036.0 -2014-02-25 16:00:00,13030.0 -2014-02-25 17:00:00,12999.0 -2014-02-25 18:00:00,13141.0 -2014-02-25 19:00:00,13736.0 -2014-02-25 20:00:00,14477.0 -2014-02-25 21:00:00,14501.0 -2014-02-25 22:00:00,14294.0 -2014-02-25 23:00:00,13897.0 -2014-02-26 00:00:00,13132.0 -2014-02-24 01:00:00,11045.0 -2014-02-24 02:00:00,10758.0 -2014-02-24 03:00:00,10679.0 -2014-02-24 04:00:00,10653.0 -2014-02-24 05:00:00,10701.0 -2014-02-24 06:00:00,11029.0 -2014-02-24 07:00:00,11765.0 -2014-02-24 08:00:00,12815.0 -2014-02-24 09:00:00,13187.0 -2014-02-24 10:00:00,13254.0 -2014-02-24 11:00:00,13202.0 -2014-02-24 12:00:00,13163.0 -2014-02-24 13:00:00,13085.0 -2014-02-24 14:00:00,12957.0 -2014-02-24 15:00:00,12900.0 -2014-02-24 16:00:00,12742.0 -2014-02-24 17:00:00,12708.0 -2014-02-24 18:00:00,12921.0 -2014-02-24 19:00:00,13548.0 -2014-02-24 20:00:00,14017.0 -2014-02-24 21:00:00,13898.0 -2014-02-24 22:00:00,13599.0 -2014-02-24 23:00:00,13106.0 -2014-02-25 00:00:00,12273.0 -2014-02-23 01:00:00,10730.0 -2014-02-23 02:00:00,10347.0 -2014-02-23 03:00:00,10168.0 -2014-02-23 04:00:00,10026.0 -2014-02-23 05:00:00,9992.0 -2014-02-23 06:00:00,10032.0 -2014-02-23 07:00:00,10231.0 -2014-02-23 08:00:00,10429.0 -2014-02-23 09:00:00,10531.0 -2014-02-23 10:00:00,10784.0 -2014-02-23 11:00:00,10856.0 -2014-02-23 12:00:00,10792.0 -2014-02-23 13:00:00,10686.0 -2014-02-23 14:00:00,10645.0 -2014-02-23 15:00:00,10587.0 -2014-02-23 16:00:00,10481.0 -2014-02-23 17:00:00,10527.0 -2014-02-23 18:00:00,10840.0 -2014-02-23 19:00:00,11618.0 -2014-02-23 20:00:00,12435.0 -2014-02-23 21:00:00,12545.0 -2014-02-23 22:00:00,12353.0 -2014-02-23 23:00:00,12071.0 -2014-02-24 00:00:00,11574.0 -2014-02-22 01:00:00,11124.0 -2014-02-22 02:00:00,10628.0 -2014-02-22 03:00:00,10338.0 -2014-02-22 04:00:00,10207.0 -2014-02-22 05:00:00,10157.0 -2014-02-22 06:00:00,10283.0 -2014-02-22 07:00:00,10615.0 -2014-02-22 08:00:00,10984.0 -2014-02-22 09:00:00,11080.0 -2014-02-22 10:00:00,11467.0 -2014-02-22 11:00:00,11628.0 -2014-02-22 12:00:00,11708.0 -2014-02-22 13:00:00,11535.0 -2014-02-22 14:00:00,11156.0 -2014-02-22 15:00:00,10907.0 -2014-02-22 16:00:00,10788.0 -2014-02-22 17:00:00,10755.0 -2014-02-22 18:00:00,10976.0 -2014-02-22 19:00:00,11518.0 -2014-02-22 20:00:00,12084.0 -2014-02-22 21:00:00,11994.0 -2014-02-22 22:00:00,11882.0 -2014-02-22 23:00:00,11580.0 -2014-02-23 00:00:00,11121.0 -2014-02-21 01:00:00,10832.0 -2014-02-21 02:00:00,10367.0 -2014-02-21 03:00:00,10072.0 -2014-02-21 04:00:00,9946.0 -2014-02-21 05:00:00,10010.0 -2014-02-21 06:00:00,10277.0 -2014-02-21 07:00:00,11069.0 -2014-02-21 08:00:00,12265.0 -2014-02-21 09:00:00,12708.0 -2014-02-21 10:00:00,12993.0 -2014-02-21 11:00:00,13039.0 -2014-02-21 12:00:00,13009.0 -2014-02-21 13:00:00,12938.0 -2014-02-21 14:00:00,12811.0 -2014-02-21 15:00:00,12683.0 -2014-02-21 16:00:00,12476.0 -2014-02-21 17:00:00,12313.0 -2014-02-21 18:00:00,12356.0 -2014-02-21 19:00:00,12869.0 -2014-02-21 20:00:00,13313.0 -2014-02-21 21:00:00,13104.0 -2014-02-21 22:00:00,12769.0 -2014-02-21 23:00:00,12396.0 -2014-02-22 00:00:00,11738.0 -2014-02-20 01:00:00,10493.0 -2014-02-20 02:00:00,10018.0 -2014-02-20 03:00:00,9757.0 -2014-02-20 04:00:00,9624.0 -2014-02-20 05:00:00,9654.0 -2014-02-20 06:00:00,9998.0 -2014-02-20 07:00:00,10802.0 -2014-02-20 08:00:00,11896.0 -2014-02-20 09:00:00,12425.0 -2014-02-20 10:00:00,12559.0 -2014-02-20 11:00:00,12619.0 -2014-02-20 12:00:00,12725.0 -2014-02-20 13:00:00,12728.0 -2014-02-20 14:00:00,12743.0 -2014-02-20 15:00:00,12742.0 -2014-02-20 16:00:00,12722.0 -2014-02-20 17:00:00,12754.0 -2014-02-20 18:00:00,12802.0 -2014-02-20 19:00:00,13122.0 -2014-02-20 20:00:00,13079.0 -2014-02-20 21:00:00,12862.0 -2014-02-20 22:00:00,12717.0 -2014-02-20 23:00:00,12269.0 -2014-02-21 00:00:00,11523.0 -2014-02-19 01:00:00,10615.0 -2014-02-19 02:00:00,10191.0 -2014-02-19 03:00:00,9902.0 -2014-02-19 04:00:00,9748.0 -2014-02-19 05:00:00,9760.0 -2014-02-19 06:00:00,10019.0 -2014-02-19 07:00:00,10864.0 -2014-02-19 08:00:00,11897.0 -2014-02-19 09:00:00,12200.0 -2014-02-19 10:00:00,12172.0 -2014-02-19 11:00:00,12104.0 -2014-02-19 12:00:00,12056.0 -2014-02-19 13:00:00,11975.0 -2014-02-19 14:00:00,11861.0 -2014-02-19 15:00:00,11786.0 -2014-02-19 16:00:00,11637.0 -2014-02-19 17:00:00,11562.0 -2014-02-19 18:00:00,11705.0 -2014-02-19 19:00:00,12357.0 -2014-02-19 20:00:00,12853.0 -2014-02-19 21:00:00,12728.0 -2014-02-19 22:00:00,12552.0 -2014-02-19 23:00:00,12030.0 -2014-02-20 00:00:00,11233.0 -2014-02-18 01:00:00,11271.0 -2014-02-18 02:00:00,10834.0 -2014-02-18 03:00:00,10529.0 -2014-02-18 04:00:00,10378.0 -2014-02-18 05:00:00,10352.0 -2014-02-18 06:00:00,10603.0 -2014-02-18 07:00:00,11321.0 -2014-02-18 08:00:00,12380.0 -2014-02-18 09:00:00,12731.0 -2014-02-18 10:00:00,12635.0 -2014-02-18 11:00:00,12540.0 -2014-02-18 12:00:00,12458.0 -2014-02-18 13:00:00,12321.0 -2014-02-18 14:00:00,12193.0 -2014-02-18 15:00:00,12112.0 -2014-02-18 16:00:00,12001.0 -2014-02-18 17:00:00,12072.0 -2014-02-18 18:00:00,12191.0 -2014-02-18 19:00:00,12753.0 -2014-02-18 20:00:00,13179.0 -2014-02-18 21:00:00,12967.0 -2014-02-18 22:00:00,12686.0 -2014-02-18 23:00:00,12100.0 -2014-02-19 00:00:00,11378.0 -2014-02-17 01:00:00,10825.0 -2014-02-17 02:00:00,10490.0 -2014-02-17 03:00:00,10315.0 -2014-02-17 04:00:00,10271.0 -2014-02-17 05:00:00,10287.0 -2014-02-17 06:00:00,10589.0 -2014-02-17 07:00:00,11236.0 -2014-02-17 08:00:00,12228.0 -2014-02-17 09:00:00,12720.0 -2014-02-17 10:00:00,13137.0 -2014-02-17 11:00:00,13421.0 -2014-02-17 12:00:00,13550.0 -2014-02-17 13:00:00,13629.0 -2014-02-17 14:00:00,13610.0 -2014-02-17 15:00:00,13620.0 -2014-02-17 16:00:00,13571.0 -2014-02-17 17:00:00,13241.0 -2014-02-17 18:00:00,13236.0 -2014-02-17 19:00:00,13776.0 -2014-02-17 20:00:00,13984.0 -2014-02-17 21:00:00,13815.0 -2014-02-17 22:00:00,13452.0 -2014-02-17 23:00:00,12859.0 -2014-02-18 00:00:00,12010.0 -2014-02-16 01:00:00,11314.0 -2014-02-16 02:00:00,10836.0 -2014-02-16 03:00:00,10544.0 -2014-02-16 04:00:00,10343.0 -2014-02-16 05:00:00,10303.0 -2014-02-16 06:00:00,10235.0 -2014-02-16 07:00:00,10353.0 -2014-02-16 08:00:00,10650.0 -2014-02-16 09:00:00,10673.0 -2014-02-16 10:00:00,10950.0 -2014-02-16 11:00:00,11031.0 -2014-02-16 12:00:00,10978.0 -2014-02-16 13:00:00,10849.0 -2014-02-16 14:00:00,10791.0 -2014-02-16 15:00:00,10699.0 -2014-02-16 16:00:00,10713.0 -2014-02-16 17:00:00,10836.0 -2014-02-16 18:00:00,11280.0 -2014-02-16 19:00:00,12067.0 -2014-02-16 20:00:00,12514.0 -2014-02-16 21:00:00,12444.0 -2014-02-16 22:00:00,12229.0 -2014-02-16 23:00:00,11889.0 -2014-02-17 00:00:00,11380.0 -2014-02-15 01:00:00,11840.0 -2014-02-15 02:00:00,11342.0 -2014-02-15 03:00:00,11155.0 -2014-02-15 04:00:00,11024.0 -2014-02-15 05:00:00,10937.0 -2014-02-15 06:00:00,11030.0 -2014-02-15 07:00:00,11318.0 -2014-02-15 08:00:00,11800.0 -2014-02-15 09:00:00,11918.0 -2014-02-15 10:00:00,12037.0 -2014-02-15 11:00:00,12064.0 -2014-02-15 12:00:00,12004.0 -2014-02-15 13:00:00,11815.0 -2014-02-15 14:00:00,11636.0 -2014-02-15 15:00:00,11520.0 -2014-02-15 16:00:00,11529.0 -2014-02-15 17:00:00,11653.0 -2014-02-15 18:00:00,11885.0 -2014-02-15 19:00:00,12571.0 -2014-02-15 20:00:00,12854.0 -2014-02-15 21:00:00,12797.0 -2014-02-15 22:00:00,12651.0 -2014-02-15 23:00:00,12373.0 -2014-02-16 00:00:00,11830.0 -2014-02-14 01:00:00,11593.0 -2014-02-14 02:00:00,11107.0 -2014-02-14 03:00:00,10868.0 -2014-02-14 04:00:00,10793.0 -2014-02-14 05:00:00,10772.0 -2014-02-14 06:00:00,11102.0 -2014-02-14 07:00:00,11791.0 -2014-02-14 08:00:00,12812.0 -2014-02-14 09:00:00,13204.0 -2014-02-14 10:00:00,13436.0 -2014-02-14 11:00:00,13532.0 -2014-02-14 12:00:00,13493.0 -2014-02-14 13:00:00,13361.0 -2014-02-14 14:00:00,13156.0 -2014-02-14 15:00:00,13113.0 -2014-02-14 16:00:00,12977.0 -2014-02-14 17:00:00,12847.0 -2014-02-14 18:00:00,12901.0 -2014-02-14 19:00:00,13484.0 -2014-02-14 20:00:00,13843.0 -2014-02-14 21:00:00,13666.0 -2014-02-14 22:00:00,13441.0 -2014-02-14 23:00:00,12998.0 -2014-02-15 00:00:00,12464.0 -2014-02-13 01:00:00,11776.0 -2014-02-13 02:00:00,11309.0 -2014-02-13 03:00:00,11125.0 -2014-02-13 04:00:00,10957.0 -2014-02-13 05:00:00,10964.0 -2014-02-13 06:00:00,11194.0 -2014-02-13 07:00:00,11883.0 -2014-02-13 08:00:00,12939.0 -2014-02-13 09:00:00,13272.0 -2014-02-13 10:00:00,13360.0 -2014-02-13 11:00:00,13361.0 -2014-02-13 12:00:00,13426.0 -2014-02-13 13:00:00,13472.0 -2014-02-13 14:00:00,13446.0 -2014-02-13 15:00:00,13259.0 -2014-02-13 16:00:00,12868.0 -2014-02-13 17:00:00,12758.0 -2014-02-13 18:00:00,13019.0 -2014-02-13 19:00:00,13688.0 -2014-02-13 20:00:00,13845.0 -2014-02-13 21:00:00,13660.0 -2014-02-13 22:00:00,13405.0 -2014-02-13 23:00:00,13039.0 -2014-02-14 00:00:00,12346.0 -2014-02-12 01:00:00,12605.0 -2014-02-12 02:00:00,12194.0 -2014-02-12 03:00:00,11942.0 -2014-02-12 04:00:00,11838.0 -2014-02-12 05:00:00,11881.0 -2014-02-12 06:00:00,12087.0 -2014-02-12 07:00:00,12687.0 -2014-02-12 08:00:00,13637.0 -2014-02-12 09:00:00,13969.0 -2014-02-12 10:00:00,14149.0 -2014-02-12 11:00:00,14167.0 -2014-02-12 12:00:00,14067.0 -2014-02-12 13:00:00,13870.0 -2014-02-12 14:00:00,13690.0 -2014-02-12 15:00:00,13497.0 -2014-02-12 16:00:00,13317.0 -2014-02-12 17:00:00,13147.0 -2014-02-12 18:00:00,13251.0 -2014-02-12 19:00:00,14025.0 -2014-02-12 20:00:00,14254.0 -2014-02-12 21:00:00,14090.0 -2014-02-12 22:00:00,13834.0 -2014-02-12 23:00:00,13328.0 -2014-02-13 00:00:00,12511.0 -2014-02-11 01:00:00,12557.0 -2014-02-11 02:00:00,12194.0 -2014-02-11 03:00:00,11988.0 -2014-02-11 04:00:00,11868.0 -2014-02-11 05:00:00,11889.0 -2014-02-11 06:00:00,12085.0 -2014-02-11 07:00:00,12783.0 -2014-02-11 08:00:00,13829.0 -2014-02-11 09:00:00,14231.0 -2014-02-11 10:00:00,14208.0 -2014-02-11 11:00:00,14126.0 -2014-02-11 12:00:00,14014.0 -2014-02-11 13:00:00,13901.0 -2014-02-11 14:00:00,13743.0 -2014-02-11 15:00:00,13646.0 -2014-02-11 16:00:00,13459.0 -2014-02-11 17:00:00,13387.0 -2014-02-11 18:00:00,13570.0 -2014-02-11 19:00:00,14366.0 -2014-02-11 20:00:00,14804.0 -2014-02-11 21:00:00,14717.0 -2014-02-11 22:00:00,14543.0 -2014-02-11 23:00:00,14063.0 -2014-02-12 00:00:00,13341.0 -2014-02-10 01:00:00,11752.0 -2014-02-10 02:00:00,11459.0 -2014-02-10 03:00:00,11323.0 -2014-02-10 04:00:00,11280.0 -2014-02-10 05:00:00,11393.0 -2014-02-10 06:00:00,11695.0 -2014-02-10 07:00:00,12502.0 -2014-02-10 08:00:00,13548.0 -2014-02-10 09:00:00,14019.0 -2014-02-10 10:00:00,14078.0 -2014-02-10 11:00:00,14030.0 -2014-02-10 12:00:00,13968.0 -2014-02-10 13:00:00,13840.0 -2014-02-10 14:00:00,13729.0 -2014-02-10 15:00:00,13619.0 -2014-02-10 16:00:00,13493.0 -2014-02-10 17:00:00,13418.0 -2014-02-10 18:00:00,13649.0 -2014-02-10 19:00:00,14479.0 -2014-02-10 20:00:00,14931.0 -2014-02-10 21:00:00,14855.0 -2014-02-10 22:00:00,14618.0 -2014-02-10 23:00:00,14125.0 -2014-02-11 00:00:00,13270.0 -2014-02-09 01:00:00,11551.0 -2014-02-09 02:00:00,11117.0 -2014-02-09 03:00:00,10864.0 -2014-02-09 04:00:00,10689.0 -2014-02-09 05:00:00,10659.0 -2014-02-09 06:00:00,10658.0 -2014-02-09 07:00:00,10846.0 -2014-02-09 08:00:00,11137.0 -2014-02-09 09:00:00,11192.0 -2014-02-09 10:00:00,11321.0 -2014-02-09 11:00:00,11421.0 -2014-02-09 12:00:00,11421.0 -2014-02-09 13:00:00,11408.0 -2014-02-09 14:00:00,11323.0 -2014-02-09 15:00:00,11301.0 -2014-02-09 16:00:00,11330.0 -2014-02-09 17:00:00,11506.0 -2014-02-09 18:00:00,11798.0 -2014-02-09 19:00:00,12822.0 -2014-02-09 20:00:00,13271.0 -2014-02-09 21:00:00,13249.0 -2014-02-09 22:00:00,13062.0 -2014-02-09 23:00:00,12811.0 -2014-02-10 00:00:00,12227.0 -2014-02-08 01:00:00,12448.0 -2014-02-08 02:00:00,12023.0 -2014-02-08 03:00:00,11706.0 -2014-02-08 04:00:00,11547.0 -2014-02-08 05:00:00,11394.0 -2014-02-08 06:00:00,11454.0 -2014-02-08 07:00:00,11654.0 -2014-02-08 08:00:00,12090.0 -2014-02-08 09:00:00,12237.0 -2014-02-08 10:00:00,12588.0 -2014-02-08 11:00:00,12737.0 -2014-02-08 12:00:00,12805.0 -2014-02-08 13:00:00,12720.0 -2014-02-08 14:00:00,12737.0 -2014-02-08 15:00:00,12595.0 -2014-02-08 16:00:00,12437.0 -2014-02-08 17:00:00,12374.0 -2014-02-08 18:00:00,12546.0 -2014-02-08 19:00:00,13202.0 -2014-02-08 20:00:00,13470.0 -2014-02-08 21:00:00,13283.0 -2014-02-08 22:00:00,13071.0 -2014-02-08 23:00:00,12711.0 -2014-02-09 00:00:00,12119.0 -2014-02-07 01:00:00,12946.0 -2014-02-07 02:00:00,12507.0 -2014-02-07 03:00:00,12258.0 -2014-02-07 04:00:00,12114.0 -2014-02-07 05:00:00,12104.0 -2014-02-07 06:00:00,12355.0 -2014-02-07 07:00:00,12960.0 -2014-02-07 08:00:00,14006.0 -2014-02-07 09:00:00,14320.0 -2014-02-07 10:00:00,14318.0 -2014-02-07 11:00:00,14229.0 -2014-02-07 12:00:00,14152.0 -2014-02-07 13:00:00,14047.0 -2014-02-07 14:00:00,13921.0 -2014-02-07 15:00:00,13893.0 -2014-02-07 16:00:00,13676.0 -2014-02-07 17:00:00,13580.0 -2014-02-07 18:00:00,13746.0 -2014-02-07 19:00:00,14478.0 -2014-02-07 20:00:00,14800.0 -2014-02-07 21:00:00,14646.0 -2014-02-07 22:00:00,14318.0 -2014-02-07 23:00:00,13947.0 -2014-02-08 00:00:00,13221.0 -2014-02-06 01:00:00,12121.0 -2014-02-06 02:00:00,11675.0 -2014-02-06 03:00:00,11477.0 -2014-02-06 04:00:00,11438.0 -2014-02-06 05:00:00,11473.0 -2014-02-06 06:00:00,11738.0 -2014-02-06 07:00:00,12460.0 -2014-02-06 08:00:00,13592.0 -2014-02-06 09:00:00,13993.0 -2014-02-06 10:00:00,14048.0 -2014-02-06 11:00:00,13999.0 -2014-02-06 12:00:00,14043.0 -2014-02-06 13:00:00,13989.0 -2014-02-06 14:00:00,13926.0 -2014-02-06 15:00:00,13859.0 -2014-02-06 16:00:00,13761.0 -2014-02-06 17:00:00,13754.0 -2014-02-06 18:00:00,14010.0 -2014-02-06 19:00:00,14856.0 -2014-02-06 20:00:00,15256.0 -2014-02-06 21:00:00,15155.0 -2014-02-06 22:00:00,14952.0 -2014-02-06 23:00:00,14458.0 -2014-02-07 00:00:00,13666.0 -2014-02-05 01:00:00,11776.0 -2014-02-05 02:00:00,11305.0 -2014-02-05 03:00:00,11031.0 -2014-02-05 04:00:00,10906.0 -2014-02-05 05:00:00,10930.0 -2014-02-05 06:00:00,11140.0 -2014-02-05 07:00:00,11808.0 -2014-02-05 08:00:00,12837.0 -2014-02-05 09:00:00,13206.0 -2014-02-05 10:00:00,13357.0 -2014-02-05 11:00:00,13448.0 -2014-02-05 12:00:00,13513.0 -2014-02-05 13:00:00,13467.0 -2014-02-05 14:00:00,13409.0 -2014-02-05 15:00:00,13432.0 -2014-02-05 16:00:00,13365.0 -2014-02-05 17:00:00,13348.0 -2014-02-05 18:00:00,13510.0 -2014-02-05 19:00:00,14223.0 -2014-02-05 20:00:00,14458.0 -2014-02-05 21:00:00,14340.0 -2014-02-05 22:00:00,14095.0 -2014-02-05 23:00:00,13588.0 -2014-02-06 00:00:00,12806.0 -2014-02-04 01:00:00,12030.0 -2014-02-04 02:00:00,11603.0 -2014-02-04 03:00:00,11337.0 -2014-02-04 04:00:00,11239.0 -2014-02-04 05:00:00,11224.0 -2014-02-04 06:00:00,11437.0 -2014-02-04 07:00:00,12078.0 -2014-02-04 08:00:00,13185.0 -2014-02-04 09:00:00,13689.0 -2014-02-04 10:00:00,13702.0 -2014-02-04 11:00:00,13715.0 -2014-02-04 12:00:00,13714.0 -2014-02-04 13:00:00,13649.0 -2014-02-04 14:00:00,13623.0 -2014-02-04 15:00:00,13594.0 -2014-02-04 16:00:00,13502.0 -2014-02-04 17:00:00,13480.0 -2014-02-04 18:00:00,13744.0 -2014-02-04 19:00:00,14431.0 -2014-02-04 20:00:00,14448.0 -2014-02-04 21:00:00,14273.0 -2014-02-04 22:00:00,13962.0 -2014-02-04 23:00:00,13374.0 -2014-02-05 00:00:00,12535.0 -2014-02-03 01:00:00,11416.0 -2014-02-03 02:00:00,11158.0 -2014-02-03 03:00:00,11020.0 -2014-02-03 04:00:00,11047.0 -2014-02-03 05:00:00,11102.0 -2014-02-03 06:00:00,11428.0 -2014-02-03 07:00:00,12188.0 -2014-02-03 08:00:00,13349.0 -2014-02-03 09:00:00,13753.0 -2014-02-03 10:00:00,13821.0 -2014-02-03 11:00:00,13654.0 -2014-02-03 12:00:00,13579.0 -2014-02-03 13:00:00,13455.0 -2014-02-03 14:00:00,13305.0 -2014-02-03 15:00:00,13231.0 -2014-02-03 16:00:00,13072.0 -2014-02-03 17:00:00,12938.0 -2014-02-03 18:00:00,13274.0 -2014-02-03 19:00:00,14135.0 -2014-02-03 20:00:00,14406.0 -2014-02-03 21:00:00,14294.0 -2014-02-03 22:00:00,14044.0 -2014-02-03 23:00:00,13574.0 -2014-02-04 00:00:00,12749.0 -2014-02-02 01:00:00,11024.0 -2014-02-02 02:00:00,10589.0 -2014-02-02 03:00:00,10333.0 -2014-02-02 04:00:00,10201.0 -2014-02-02 05:00:00,10087.0 -2014-02-02 06:00:00,10210.0 -2014-02-02 07:00:00,10418.0 -2014-02-02 08:00:00,10726.0 -2014-02-02 09:00:00,10791.0 -2014-02-02 10:00:00,10964.0 -2014-02-02 11:00:00,11085.0 -2014-02-02 12:00:00,11150.0 -2014-02-02 13:00:00,11209.0 -2014-02-02 14:00:00,11185.0 -2014-02-02 15:00:00,11208.0 -2014-02-02 16:00:00,11298.0 -2014-02-02 17:00:00,11378.0 -2014-02-02 18:00:00,11751.0 -2014-02-02 19:00:00,12526.0 -2014-02-02 20:00:00,12731.0 -2014-02-02 21:00:00,12677.0 -2014-02-02 22:00:00,12616.0 -2014-02-02 23:00:00,12386.0 -2014-02-03 00:00:00,11863.0 -2014-02-01 01:00:00,11639.0 -2014-02-01 02:00:00,11078.0 -2014-02-01 03:00:00,10766.0 -2014-02-01 04:00:00,10538.0 -2014-02-01 05:00:00,10467.0 -2014-02-01 06:00:00,10548.0 -2014-02-01 07:00:00,10838.0 -2014-02-01 08:00:00,11234.0 -2014-02-01 09:00:00,11426.0 -2014-02-01 10:00:00,11679.0 -2014-02-01 11:00:00,11860.0 -2014-02-01 12:00:00,12037.0 -2014-02-01 13:00:00,12023.0 -2014-02-01 14:00:00,11922.0 -2014-02-01 15:00:00,11739.0 -2014-02-01 16:00:00,11687.0 -2014-02-01 17:00:00,11710.0 -2014-02-01 18:00:00,11916.0 -2014-02-01 19:00:00,12704.0 -2014-02-01 20:00:00,12806.0 -2014-02-01 21:00:00,12666.0 -2014-02-01 22:00:00,12418.0 -2014-02-01 23:00:00,12058.0 -2014-02-02 00:00:00,11549.0 -2014-01-31 01:00:00,11551.0 -2014-01-31 02:00:00,11139.0 -2014-01-31 03:00:00,10938.0 -2014-01-31 04:00:00,10808.0 -2014-01-31 05:00:00,10798.0 -2014-01-31 06:00:00,11042.0 -2014-01-31 07:00:00,11724.0 -2014-01-31 08:00:00,12795.0 -2014-01-31 09:00:00,13301.0 -2014-01-31 10:00:00,13386.0 -2014-01-31 11:00:00,13430.0 -2014-01-31 12:00:00,13404.0 -2014-01-31 13:00:00,13388.0 -2014-01-31 14:00:00,13253.0 -2014-01-31 15:00:00,13180.0 -2014-01-31 16:00:00,13071.0 -2014-01-31 17:00:00,12997.0 -2014-01-31 18:00:00,13233.0 -2014-01-31 19:00:00,13869.0 -2014-01-31 20:00:00,13864.0 -2014-01-31 21:00:00,13687.0 -2014-01-31 22:00:00,13376.0 -2014-01-31 23:00:00,13013.0 -2014-02-01 00:00:00,12371.0 -2014-01-30 01:00:00,12423.0 -2014-01-30 02:00:00,11920.0 -2014-01-30 03:00:00,11628.0 -2014-01-30 04:00:00,11528.0 -2014-01-30 05:00:00,11499.0 -2014-01-30 06:00:00,11693.0 -2014-01-30 07:00:00,12329.0 -2014-01-30 08:00:00,13364.0 -2014-01-30 09:00:00,13826.0 -2014-01-30 10:00:00,13914.0 -2014-01-30 11:00:00,13981.0 -2014-01-30 12:00:00,14075.0 -2014-01-30 13:00:00,13920.0 -2014-01-30 14:00:00,13820.0 -2014-01-30 15:00:00,13860.0 -2014-01-30 16:00:00,13845.0 -2014-01-30 17:00:00,13809.0 -2014-01-30 18:00:00,14013.0 -2014-01-30 19:00:00,14533.0 -2014-01-30 20:00:00,14403.0 -2014-01-30 21:00:00,14122.0 -2014-01-30 22:00:00,13752.0 -2014-01-30 23:00:00,13190.0 -2014-01-31 00:00:00,12324.0 -2014-01-29 01:00:00,13295.0 -2014-01-29 02:00:00,12815.0 -2014-01-29 03:00:00,12590.0 -2014-01-29 04:00:00,12396.0 -2014-01-29 05:00:00,12415.0 -2014-01-29 06:00:00,12646.0 -2014-01-29 07:00:00,13266.0 -2014-01-29 08:00:00,14296.0 -2014-01-29 09:00:00,14640.0 -2014-01-29 10:00:00,14590.0 -2014-01-29 11:00:00,14448.0 -2014-01-29 12:00:00,14357.0 -2014-01-29 13:00:00,14188.0 -2014-01-29 14:00:00,14009.0 -2014-01-29 15:00:00,13912.0 -2014-01-29 16:00:00,13735.0 -2014-01-29 17:00:00,13621.0 -2014-01-29 18:00:00,13912.0 -2014-01-29 19:00:00,14759.0 -2014-01-29 20:00:00,14925.0 -2014-01-29 21:00:00,14758.0 -2014-01-29 22:00:00,14461.0 -2014-01-29 23:00:00,13959.0 -2014-01-30 00:00:00,13172.0 -2014-01-28 01:00:00,13525.0 -2014-01-28 02:00:00,13154.0 -2014-01-28 03:00:00,12901.0 -2014-01-28 04:00:00,12778.0 -2014-01-28 05:00:00,12723.0 -2014-01-28 06:00:00,12905.0 -2014-01-28 07:00:00,13491.0 -2014-01-28 08:00:00,14313.0 -2014-01-28 09:00:00,14670.0 -2014-01-28 10:00:00,14873.0 -2014-01-28 11:00:00,14932.0 -2014-01-28 12:00:00,15002.0 -2014-01-28 13:00:00,14950.0 -2014-01-28 14:00:00,14847.0 -2014-01-28 15:00:00,14783.0 -2014-01-28 16:00:00,14619.0 -2014-01-28 17:00:00,14531.0 -2014-01-28 18:00:00,14819.0 -2014-01-28 19:00:00,15720.0 -2014-01-28 20:00:00,15862.0 -2014-01-28 21:00:00,15651.0 -2014-01-28 22:00:00,15328.0 -2014-01-28 23:00:00,14749.0 -2014-01-29 00:00:00,13959.0 -2014-01-27 01:00:00,12203.0 -2014-01-27 02:00:00,11921.0 -2014-01-27 03:00:00,11831.0 -2014-01-27 04:00:00,11787.0 -2014-01-27 05:00:00,11880.0 -2014-01-27 06:00:00,12078.0 -2014-01-27 07:00:00,12769.0 -2014-01-27 08:00:00,13710.0 -2014-01-27 09:00:00,14162.0 -2014-01-27 10:00:00,14352.0 -2014-01-27 11:00:00,14578.0 -2014-01-27 12:00:00,14699.0 -2014-01-27 13:00:00,14738.0 -2014-01-27 14:00:00,14728.0 -2014-01-27 15:00:00,14754.0 -2014-01-27 16:00:00,14649.0 -2014-01-27 17:00:00,14634.0 -2014-01-27 18:00:00,14932.0 -2014-01-27 19:00:00,15872.0 -2014-01-27 20:00:00,16064.0 -2014-01-27 21:00:00,15875.0 -2014-01-27 22:00:00,15615.0 -2014-01-27 23:00:00,15068.0 -2014-01-28 00:00:00,14229.0 -2014-01-26 01:00:00,12082.0 -2014-01-26 02:00:00,11619.0 -2014-01-26 03:00:00,11391.0 -2014-01-26 04:00:00,11216.0 -2014-01-26 05:00:00,11196.0 -2014-01-26 06:00:00,11162.0 -2014-01-26 07:00:00,11360.0 -2014-01-26 08:00:00,11560.0 -2014-01-26 09:00:00,11684.0 -2014-01-26 10:00:00,11810.0 -2014-01-26 11:00:00,11966.0 -2014-01-26 12:00:00,11865.0 -2014-01-26 13:00:00,11950.0 -2014-01-26 14:00:00,11825.0 -2014-01-26 15:00:00,11658.0 -2014-01-26 16:00:00,11456.0 -2014-01-26 17:00:00,11567.0 -2014-01-26 18:00:00,12076.0 -2014-01-26 19:00:00,12987.0 -2014-01-26 20:00:00,13078.0 -2014-01-26 21:00:00,13114.0 -2014-01-26 22:00:00,13037.0 -2014-01-26 23:00:00,12837.0 -2014-01-27 00:00:00,12567.0 -2014-01-25 01:00:00,12269.0 -2014-01-25 02:00:00,11649.0 -2014-01-25 03:00:00,11193.0 -2014-01-25 04:00:00,10920.0 -2014-01-25 05:00:00,10788.0 -2014-01-25 06:00:00,10706.0 -2014-01-25 07:00:00,10992.0 -2014-01-25 08:00:00,11517.0 -2014-01-25 09:00:00,11730.0 -2014-01-25 10:00:00,12148.0 -2014-01-25 11:00:00,12381.0 -2014-01-25 12:00:00,12440.0 -2014-01-25 13:00:00,12408.0 -2014-01-25 14:00:00,12347.0 -2014-01-25 15:00:00,12195.0 -2014-01-25 16:00:00,12086.0 -2014-01-25 17:00:00,12175.0 -2014-01-25 18:00:00,12555.0 -2014-01-25 19:00:00,13614.0 -2014-01-25 20:00:00,13790.0 -2014-01-25 21:00:00,13762.0 -2014-01-25 22:00:00,13530.0 -2014-01-25 23:00:00,13249.0 -2014-01-26 00:00:00,12631.0 -2014-01-24 01:00:00,13337.0 -2014-01-24 02:00:00,12921.0 -2014-01-24 03:00:00,12637.0 -2014-01-24 04:00:00,12519.0 -2014-01-24 05:00:00,12528.0 -2014-01-24 06:00:00,12739.0 -2014-01-24 07:00:00,13255.0 -2014-01-24 08:00:00,14306.0 -2014-01-24 09:00:00,14802.0 -2014-01-24 10:00:00,15031.0 -2014-01-24 11:00:00,15082.0 -2014-01-24 12:00:00,14982.0 -2014-01-24 13:00:00,14760.0 -2014-01-24 14:00:00,14649.0 -2014-01-24 15:00:00,14623.0 -2014-01-24 16:00:00,14497.0 -2014-01-24 17:00:00,14410.0 -2014-01-24 18:00:00,14631.0 -2014-01-24 19:00:00,15142.0 -2014-01-24 20:00:00,15011.0 -2014-01-24 21:00:00,14687.0 -2014-01-24 22:00:00,14336.0 -2014-01-24 23:00:00,13827.0 -2014-01-25 00:00:00,13035.0 -2014-01-23 01:00:00,13119.0 -2014-01-23 02:00:00,12701.0 -2014-01-23 03:00:00,12509.0 -2014-01-23 04:00:00,12387.0 -2014-01-23 05:00:00,12381.0 -2014-01-23 06:00:00,12604.0 -2014-01-23 07:00:00,13255.0 -2014-01-23 08:00:00,14331.0 -2014-01-23 09:00:00,14703.0 -2014-01-23 10:00:00,14810.0 -2014-01-23 11:00:00,14770.0 -2014-01-23 12:00:00,14727.0 -2014-01-23 13:00:00,14582.0 -2014-01-23 14:00:00,14407.0 -2014-01-23 15:00:00,14313.0 -2014-01-23 16:00:00,14154.0 -2014-01-23 17:00:00,14165.0 -2014-01-23 18:00:00,14538.0 -2014-01-23 19:00:00,15469.0 -2014-01-23 20:00:00,15554.0 -2014-01-23 21:00:00,15476.0 -2014-01-23 22:00:00,15290.0 -2014-01-23 23:00:00,14813.0 -2014-01-24 00:00:00,14015.0 -2014-01-22 01:00:00,12664.0 -2014-01-22 02:00:00,12229.0 -2014-01-22 03:00:00,11935.0 -2014-01-22 04:00:00,11812.0 -2014-01-22 05:00:00,11831.0 -2014-01-22 06:00:00,11976.0 -2014-01-22 07:00:00,12684.0 -2014-01-22 08:00:00,13747.0 -2014-01-22 09:00:00,14304.0 -2014-01-22 10:00:00,14416.0 -2014-01-22 11:00:00,14440.0 -2014-01-22 12:00:00,14532.0 -2014-01-22 13:00:00,14409.0 -2014-01-22 14:00:00,14245.0 -2014-01-22 15:00:00,14250.0 -2014-01-22 16:00:00,14145.0 -2014-01-22 17:00:00,14214.0 -2014-01-22 18:00:00,14500.0 -2014-01-22 19:00:00,15226.0 -2014-01-22 20:00:00,15406.0 -2014-01-22 21:00:00,15233.0 -2014-01-22 22:00:00,15047.0 -2014-01-22 23:00:00,14552.0 -2014-01-23 00:00:00,13821.0 -2014-01-21 01:00:00,11786.0 -2014-01-21 02:00:00,11436.0 -2014-01-21 03:00:00,11305.0 -2014-01-21 04:00:00,11274.0 -2014-01-21 05:00:00,11388.0 -2014-01-21 06:00:00,11694.0 -2014-01-21 07:00:00,12430.0 -2014-01-21 08:00:00,13558.0 -2014-01-21 09:00:00,14016.0 -2014-01-21 10:00:00,14062.0 -2014-01-21 11:00:00,14045.0 -2014-01-21 12:00:00,14029.0 -2014-01-21 13:00:00,13915.0 -2014-01-21 14:00:00,13821.0 -2014-01-21 15:00:00,13715.0 -2014-01-21 16:00:00,13625.0 -2014-01-21 17:00:00,13613.0 -2014-01-21 18:00:00,14077.0 -2014-01-21 19:00:00,15013.0 -2014-01-21 20:00:00,15061.0 -2014-01-21 21:00:00,14903.0 -2014-01-21 22:00:00,14698.0 -2014-01-21 23:00:00,14205.0 -2014-01-22 00:00:00,13408.0 -2014-01-20 01:00:00,10566.0 -2014-01-20 02:00:00,10230.0 -2014-01-20 03:00:00,9993.0 -2014-01-20 04:00:00,9856.0 -2014-01-20 05:00:00,9830.0 -2014-01-20 06:00:00,10101.0 -2014-01-20 07:00:00,10762.0 -2014-01-20 08:00:00,11662.0 -2014-01-20 09:00:00,12193.0 -2014-01-20 10:00:00,12395.0 -2014-01-20 11:00:00,12507.0 -2014-01-20 12:00:00,12658.0 -2014-01-20 13:00:00,12696.0 -2014-01-20 14:00:00,12561.0 -2014-01-20 15:00:00,12527.0 -2014-01-20 16:00:00,12486.0 -2014-01-20 17:00:00,12619.0 -2014-01-20 18:00:00,13192.0 -2014-01-20 19:00:00,13868.0 -2014-01-20 20:00:00,13871.0 -2014-01-20 21:00:00,13794.0 -2014-01-20 22:00:00,13638.0 -2014-01-20 23:00:00,13129.0 -2014-01-21 00:00:00,12400.0 -2014-01-19 01:00:00,11453.0 -2014-01-19 02:00:00,11069.0 -2014-01-19 03:00:00,10888.0 -2014-01-19 04:00:00,10790.0 -2014-01-19 05:00:00,10766.0 -2014-01-19 06:00:00,10787.0 -2014-01-19 07:00:00,11002.0 -2014-01-19 08:00:00,11259.0 -2014-01-19 09:00:00,11351.0 -2014-01-19 10:00:00,11423.0 -2014-01-19 11:00:00,11512.0 -2014-01-19 12:00:00,11478.0 -2014-01-19 13:00:00,11390.0 -2014-01-19 14:00:00,11257.0 -2014-01-19 15:00:00,11187.0 -2014-01-19 16:00:00,11256.0 -2014-01-19 17:00:00,11311.0 -2014-01-19 18:00:00,11639.0 -2014-01-19 19:00:00,12572.0 -2014-01-19 20:00:00,12667.0 -2014-01-19 21:00:00,12422.0 -2014-01-19 22:00:00,12156.0 -2014-01-19 23:00:00,11720.0 -2014-01-20 00:00:00,11198.0 -2014-01-18 01:00:00,12116.0 -2014-01-18 02:00:00,11614.0 -2014-01-18 03:00:00,11322.0 -2014-01-18 04:00:00,11147.0 -2014-01-18 05:00:00,11092.0 -2014-01-18 06:00:00,11150.0 -2014-01-18 07:00:00,11440.0 -2014-01-18 08:00:00,11888.0 -2014-01-18 09:00:00,12153.0 -2014-01-18 10:00:00,12472.0 -2014-01-18 11:00:00,12800.0 -2014-01-18 12:00:00,12944.0 -2014-01-18 13:00:00,12890.0 -2014-01-18 14:00:00,12749.0 -2014-01-18 15:00:00,12578.0 -2014-01-18 16:00:00,12379.0 -2014-01-18 17:00:00,12347.0 -2014-01-18 18:00:00,12611.0 -2014-01-18 19:00:00,13358.0 -2014-01-18 20:00:00,13311.0 -2014-01-18 21:00:00,13157.0 -2014-01-18 22:00:00,12865.0 -2014-01-18 23:00:00,12546.0 -2014-01-19 00:00:00,11984.0 -2014-01-17 01:00:00,11677.0 -2014-01-17 02:00:00,11246.0 -2014-01-17 03:00:00,11027.0 -2014-01-17 04:00:00,10917.0 -2014-01-17 05:00:00,10935.0 -2014-01-17 06:00:00,11232.0 -2014-01-17 07:00:00,11906.0 -2014-01-17 08:00:00,13033.0 -2014-01-17 09:00:00,13644.0 -2014-01-17 10:00:00,13732.0 -2014-01-17 11:00:00,13843.0 -2014-01-17 12:00:00,13886.0 -2014-01-17 13:00:00,13812.0 -2014-01-17 14:00:00,13675.0 -2014-01-17 15:00:00,13662.0 -2014-01-17 16:00:00,13621.0 -2014-01-17 17:00:00,13628.0 -2014-01-17 18:00:00,14022.0 -2014-01-17 19:00:00,14528.0 -2014-01-17 20:00:00,14389.0 -2014-01-17 21:00:00,14166.0 -2014-01-17 22:00:00,13819.0 -2014-01-17 23:00:00,13473.0 -2014-01-18 00:00:00,12768.0 -2014-01-16 01:00:00,11846.0 -2014-01-16 02:00:00,11383.0 -2014-01-16 03:00:00,11065.0 -2014-01-16 04:00:00,10914.0 -2014-01-16 05:00:00,10855.0 -2014-01-16 06:00:00,11054.0 -2014-01-16 07:00:00,11711.0 -2014-01-16 08:00:00,12797.0 -2014-01-16 09:00:00,13462.0 -2014-01-16 10:00:00,13539.0 -2014-01-16 11:00:00,13539.0 -2014-01-16 12:00:00,13504.0 -2014-01-16 13:00:00,13433.0 -2014-01-16 14:00:00,13283.0 -2014-01-16 15:00:00,13289.0 -2014-01-16 16:00:00,13192.0 -2014-01-16 17:00:00,13144.0 -2014-01-16 18:00:00,13541.0 -2014-01-16 19:00:00,14083.0 -2014-01-16 20:00:00,13938.0 -2014-01-16 21:00:00,13822.0 -2014-01-16 22:00:00,13605.0 -2014-01-16 23:00:00,13152.0 -2014-01-17 00:00:00,12378.0 -2014-01-15 01:00:00,11502.0 -2014-01-15 02:00:00,10998.0 -2014-01-15 03:00:00,10763.0 -2014-01-15 04:00:00,10636.0 -2014-01-15 05:00:00,10671.0 -2014-01-15 06:00:00,10930.0 -2014-01-15 07:00:00,11643.0 -2014-01-15 08:00:00,12823.0 -2014-01-15 09:00:00,13443.0 -2014-01-15 10:00:00,13370.0 -2014-01-15 11:00:00,13269.0 -2014-01-15 12:00:00,13234.0 -2014-01-15 13:00:00,13204.0 -2014-01-15 14:00:00,13165.0 -2014-01-15 15:00:00,13267.0 -2014-01-15 16:00:00,13181.0 -2014-01-15 17:00:00,13135.0 -2014-01-15 18:00:00,13564.0 -2014-01-15 19:00:00,14473.0 -2014-01-15 20:00:00,14416.0 -2014-01-15 21:00:00,14203.0 -2014-01-15 22:00:00,13918.0 -2014-01-15 23:00:00,13404.0 -2014-01-16 00:00:00,12585.0 -2014-01-14 01:00:00,10825.0 -2014-01-14 02:00:00,10366.0 -2014-01-14 03:00:00,10101.0 -2014-01-14 04:00:00,9950.0 -2014-01-14 05:00:00,9989.0 -2014-01-14 06:00:00,10293.0 -2014-01-14 07:00:00,11036.0 -2014-01-14 08:00:00,12200.0 -2014-01-14 09:00:00,12646.0 -2014-01-14 10:00:00,12700.0 -2014-01-14 11:00:00,12831.0 -2014-01-14 12:00:00,13016.0 -2014-01-14 13:00:00,13036.0 -2014-01-14 14:00:00,13045.0 -2014-01-14 15:00:00,13122.0 -2014-01-14 16:00:00,13135.0 -2014-01-14 17:00:00,13219.0 -2014-01-14 18:00:00,13723.0 -2014-01-14 19:00:00,14269.0 -2014-01-14 20:00:00,14124.0 -2014-01-14 21:00:00,13904.0 -2014-01-14 22:00:00,13601.0 -2014-01-14 23:00:00,13075.0 -2014-01-15 00:00:00,12265.0 -2014-01-13 01:00:00,10229.0 -2014-01-13 02:00:00,9861.0 -2014-01-13 03:00:00,9600.0 -2014-01-13 04:00:00,9519.0 -2014-01-13 05:00:00,9497.0 -2014-01-13 06:00:00,9824.0 -2014-01-13 07:00:00,10558.0 -2014-01-13 08:00:00,11822.0 -2014-01-13 09:00:00,12421.0 -2014-01-13 10:00:00,12366.0 -2014-01-13 11:00:00,12350.0 -2014-01-13 12:00:00,12283.0 -2014-01-13 13:00:00,12164.0 -2014-01-13 14:00:00,12007.0 -2014-01-13 15:00:00,11938.0 -2014-01-13 16:00:00,11840.0 -2014-01-13 17:00:00,11844.0 -2014-01-13 18:00:00,12325.0 -2014-01-13 19:00:00,13210.0 -2014-01-13 20:00:00,13200.0 -2014-01-13 21:00:00,13013.0 -2014-01-13 22:00:00,12809.0 -2014-01-13 23:00:00,12323.0 -2014-01-14 00:00:00,11531.0 -2014-01-12 01:00:00,10713.0 -2014-01-12 02:00:00,10257.0 -2014-01-12 03:00:00,9967.0 -2014-01-12 04:00:00,9786.0 -2014-01-12 05:00:00,9748.0 -2014-01-12 06:00:00,9760.0 -2014-01-12 07:00:00,9934.0 -2014-01-12 08:00:00,10205.0 -2014-01-12 09:00:00,10353.0 -2014-01-12 10:00:00,10525.0 -2014-01-12 11:00:00,10694.0 -2014-01-12 12:00:00,10652.0 -2014-01-12 13:00:00,10773.0 -2014-01-12 14:00:00,10760.0 -2014-01-12 15:00:00,10716.0 -2014-01-12 16:00:00,10604.0 -2014-01-12 17:00:00,10725.0 -2014-01-12 18:00:00,11265.0 -2014-01-12 19:00:00,12126.0 -2014-01-12 20:00:00,12145.0 -2014-01-12 21:00:00,12062.0 -2014-01-12 22:00:00,11846.0 -2014-01-12 23:00:00,11389.0 -2014-01-13 00:00:00,10828.0 -2014-01-11 01:00:00,11084.0 -2014-01-11 02:00:00,10553.0 -2014-01-11 03:00:00,10120.0 -2014-01-11 04:00:00,9894.0 -2014-01-11 05:00:00,9784.0 -2014-01-11 06:00:00,9803.0 -2014-01-11 07:00:00,10095.0 -2014-01-11 08:00:00,10578.0 -2014-01-11 09:00:00,11048.0 -2014-01-11 10:00:00,11334.0 -2014-01-11 11:00:00,11759.0 -2014-01-11 12:00:00,11887.0 -2014-01-11 13:00:00,11902.0 -2014-01-11 14:00:00,11731.0 -2014-01-11 15:00:00,11549.0 -2014-01-11 16:00:00,11461.0 -2014-01-11 17:00:00,11480.0 -2014-01-11 18:00:00,11952.0 -2014-01-11 19:00:00,12556.0 -2014-01-11 20:00:00,12506.0 -2014-01-11 21:00:00,12384.0 -2014-01-11 22:00:00,12159.0 -2014-01-11 23:00:00,11810.0 -2014-01-12 00:00:00,11328.0 -2014-01-10 01:00:00,11881.0 -2014-01-10 02:00:00,11324.0 -2014-01-10 03:00:00,10994.0 -2014-01-10 04:00:00,10756.0 -2014-01-10 05:00:00,10678.0 -2014-01-10 06:00:00,10845.0 -2014-01-10 07:00:00,11457.0 -2014-01-10 08:00:00,12555.0 -2014-01-10 09:00:00,13142.0 -2014-01-10 10:00:00,13126.0 -2014-01-10 11:00:00,13174.0 -2014-01-10 12:00:00,13229.0 -2014-01-10 13:00:00,13206.0 -2014-01-10 14:00:00,13135.0 -2014-01-10 15:00:00,13178.0 -2014-01-10 16:00:00,13261.0 -2014-01-10 17:00:00,13243.0 -2014-01-10 18:00:00,13801.0 -2014-01-10 19:00:00,14075.0 -2014-01-10 20:00:00,13841.0 -2014-01-10 21:00:00,13525.0 -2014-01-10 22:00:00,13050.0 -2014-01-10 23:00:00,12585.0 -2014-01-11 00:00:00,11890.0 -2014-01-09 01:00:00,12871.0 -2014-01-09 02:00:00,12381.0 -2014-01-09 03:00:00,12158.0 -2014-01-09 04:00:00,11985.0 -2014-01-09 05:00:00,11981.0 -2014-01-09 06:00:00,12166.0 -2014-01-09 07:00:00,12812.0 -2014-01-09 08:00:00,13883.0 -2014-01-09 09:00:00,14373.0 -2014-01-09 10:00:00,14404.0 -2014-01-09 11:00:00,14407.0 -2014-01-09 12:00:00,14348.0 -2014-01-09 13:00:00,14245.0 -2014-01-09 14:00:00,14203.0 -2014-01-09 15:00:00,14218.0 -2014-01-09 16:00:00,14038.0 -2014-01-09 17:00:00,13985.0 -2014-01-09 18:00:00,14451.0 -2014-01-09 19:00:00,14945.0 -2014-01-09 20:00:00,14771.0 -2014-01-09 21:00:00,14604.0 -2014-01-09 22:00:00,14267.0 -2014-01-09 23:00:00,13612.0 -2014-01-10 00:00:00,12717.0 -2014-01-08 01:00:00,13204.0 -2014-01-08 02:00:00,12723.0 -2014-01-08 03:00:00,12467.0 -2014-01-08 04:00:00,12289.0 -2014-01-08 05:00:00,12269.0 -2014-01-08 06:00:00,12469.0 -2014-01-08 07:00:00,13040.0 -2014-01-08 08:00:00,14088.0 -2014-01-08 09:00:00,14580.0 -2014-01-08 10:00:00,14612.0 -2014-01-08 11:00:00,14599.0 -2014-01-08 12:00:00,14549.0 -2014-01-08 13:00:00,14377.0 -2014-01-08 14:00:00,14209.0 -2014-01-08 15:00:00,14092.0 -2014-01-08 16:00:00,14006.0 -2014-01-08 17:00:00,14028.0 -2014-01-08 18:00:00,14586.0 -2014-01-08 19:00:00,15356.0 -2014-01-08 20:00:00,15337.0 -2014-01-08 21:00:00,15199.0 -2014-01-08 22:00:00,14932.0 -2014-01-08 23:00:00,14439.0 -2014-01-09 00:00:00,13604.0 -2014-01-07 01:00:00,13829.0 -2014-01-07 02:00:00,13349.0 -2014-01-07 03:00:00,13034.0 -2014-01-07 04:00:00,12892.0 -2014-01-07 05:00:00,12866.0 -2014-01-07 06:00:00,13023.0 -2014-01-07 07:00:00,13513.0 -2014-01-07 08:00:00,14255.0 -2014-01-07 09:00:00,14604.0 -2014-01-07 10:00:00,14785.0 -2014-01-07 11:00:00,14990.0 -2014-01-07 12:00:00,15226.0 -2014-01-07 13:00:00,15276.0 -2014-01-07 14:00:00,15218.0 -2014-01-07 15:00:00,15180.0 -2014-01-07 16:00:00,14974.0 -2014-01-07 17:00:00,14831.0 -2014-01-07 18:00:00,15284.0 -2014-01-07 19:00:00,16099.0 -2014-01-07 20:00:00,16047.0 -2014-01-07 21:00:00,15830.0 -2014-01-07 22:00:00,15529.0 -2014-01-07 23:00:00,14826.0 -2014-01-08 00:00:00,13950.0 -2014-01-06 01:00:00,12633.0 -2014-01-06 02:00:00,12324.0 -2014-01-06 03:00:00,12127.0 -2014-01-06 04:00:00,12205.0 -2014-01-06 05:00:00,12165.0 -2014-01-06 06:00:00,12370.0 -2014-01-06 07:00:00,12893.0 -2014-01-06 08:00:00,13744.0 -2014-01-06 09:00:00,14177.0 -2014-01-06 10:00:00,14446.0 -2014-01-06 11:00:00,14598.0 -2014-01-06 12:00:00,14861.0 -2014-01-06 13:00:00,14961.0 -2014-01-06 14:00:00,14971.0 -2014-01-06 15:00:00,14956.0 -2014-01-06 16:00:00,15006.0 -2014-01-06 17:00:00,15059.0 -2014-01-06 18:00:00,15613.0 -2014-01-06 19:00:00,16514.0 -2014-01-06 20:00:00,16427.0 -2014-01-06 21:00:00,16143.0 -2014-01-06 22:00:00,15839.0 -2014-01-06 23:00:00,15335.0 -2014-01-07 00:00:00,14589.0 -2014-01-05 01:00:00,11272.0 -2014-01-05 02:00:00,10835.0 -2014-01-05 03:00:00,10509.0 -2014-01-05 04:00:00,10379.0 -2014-01-05 05:00:00,10308.0 -2014-01-05 06:00:00,10337.0 -2014-01-05 07:00:00,10496.0 -2014-01-05 08:00:00,10770.0 -2014-01-05 09:00:00,10930.0 -2014-01-05 10:00:00,11213.0 -2014-01-05 11:00:00,11559.0 -2014-01-05 12:00:00,11822.0 -2014-01-05 13:00:00,12053.0 -2014-01-05 14:00:00,12191.0 -2014-01-05 15:00:00,12303.0 -2014-01-05 16:00:00,12403.0 -2014-01-05 17:00:00,12592.0 -2014-01-05 18:00:00,13366.0 -2014-01-05 19:00:00,14250.0 -2014-01-05 20:00:00,14253.0 -2014-01-05 21:00:00,14200.0 -2014-01-05 22:00:00,13959.0 -2014-01-05 23:00:00,13678.0 -2014-01-06 00:00:00,13174.0 -2014-01-04 01:00:00,12566.0 -2014-01-04 02:00:00,11995.0 -2014-01-04 03:00:00,11593.0 -2014-01-04 04:00:00,11340.0 -2014-01-04 05:00:00,11163.0 -2014-01-04 06:00:00,11209.0 -2014-01-04 07:00:00,11339.0 -2014-01-04 08:00:00,11701.0 -2014-01-04 09:00:00,11826.0 -2014-01-04 10:00:00,12025.0 -2014-01-04 11:00:00,12197.0 -2014-01-04 12:00:00,12430.0 -2014-01-04 13:00:00,12444.0 -2014-01-04 14:00:00,12365.0 -2014-01-04 15:00:00,12157.0 -2014-01-04 16:00:00,12084.0 -2014-01-04 17:00:00,12092.0 -2014-01-04 18:00:00,12759.0 -2014-01-04 19:00:00,13288.0 -2014-01-04 20:00:00,13245.0 -2014-01-04 21:00:00,13097.0 -2014-01-04 22:00:00,12890.0 -2014-01-04 23:00:00,12493.0 -2014-01-05 00:00:00,11940.0 -2014-01-03 01:00:00,12353.0 -2014-01-03 02:00:00,11868.0 -2014-01-03 03:00:00,11606.0 -2014-01-03 04:00:00,11458.0 -2014-01-03 05:00:00,11468.0 -2014-01-03 06:00:00,11694.0 -2014-01-03 07:00:00,12267.0 -2014-01-03 08:00:00,13126.0 -2014-01-03 09:00:00,13567.0 -2014-01-03 10:00:00,13710.0 -2014-01-03 11:00:00,13775.0 -2014-01-03 12:00:00,13897.0 -2014-01-03 13:00:00,13946.0 -2014-01-03 14:00:00,13979.0 -2014-01-03 15:00:00,13971.0 -2014-01-03 16:00:00,13893.0 -2014-01-03 17:00:00,13940.0 -2014-01-03 18:00:00,14385.0 -2014-01-03 19:00:00,15100.0 -2014-01-03 20:00:00,14998.0 -2014-01-03 21:00:00,14784.0 -2014-01-03 22:00:00,14456.0 -2014-01-03 23:00:00,14023.0 -2014-01-04 00:00:00,13297.0 -2014-01-02 01:00:00,11004.0 -2014-01-02 02:00:00,10568.0 -2014-01-02 03:00:00,10345.0 -2014-01-02 04:00:00,10215.0 -2014-01-02 05:00:00,10255.0 -2014-01-02 06:00:00,10509.0 -2014-01-02 07:00:00,11167.0 -2014-01-02 08:00:00,12153.0 -2014-01-02 09:00:00,12777.0 -2014-01-02 10:00:00,13008.0 -2014-01-02 11:00:00,13292.0 -2014-01-02 12:00:00,13462.0 -2014-01-02 13:00:00,13492.0 -2014-01-02 14:00:00,13473.0 -2014-01-02 15:00:00,13474.0 -2014-01-02 16:00:00,13365.0 -2014-01-02 17:00:00,13276.0 -2014-01-02 18:00:00,13856.0 -2014-01-02 19:00:00,14744.0 -2014-01-02 20:00:00,14715.0 -2014-01-02 21:00:00,14567.0 -2014-01-02 22:00:00,14306.0 -2014-01-02 23:00:00,13830.0 -2014-01-03 00:00:00,13079.0 -2014-01-01 01:00:00,11562.0 -2014-01-01 02:00:00,11135.0 -2014-01-01 03:00:00,10766.0 -2014-01-01 04:00:00,10489.0 -2014-01-01 05:00:00,10326.0 -2014-01-01 06:00:00,10296.0 -2014-01-01 07:00:00,10433.0 -2014-01-01 08:00:00,10626.0 -2014-01-01 09:00:00,10640.0 -2014-01-01 10:00:00,10657.0 -2014-01-01 11:00:00,10927.0 -2014-01-01 12:00:00,11137.0 -2014-01-01 13:00:00,11292.0 -2014-01-01 14:00:00,11344.0 -2014-01-01 15:00:00,11371.0 -2014-01-01 16:00:00,11389.0 -2014-01-01 17:00:00,11501.0 -2014-01-01 18:00:00,12263.0 -2014-01-01 19:00:00,13062.0 -2014-01-01 20:00:00,13015.0 -2014-01-01 21:00:00,12831.0 -2014-01-01 22:00:00,12598.0 -2014-01-01 23:00:00,12231.0 -2014-01-02 00:00:00,11605.0 -2015-12-31 01:00:00,10419.0 -2015-12-31 02:00:00,9893.0 -2015-12-31 03:00:00,9544.0 -2015-12-31 04:00:00,9341.0 -2015-12-31 05:00:00,9318.0 -2015-12-31 06:00:00,9507.0 -2015-12-31 07:00:00,9992.0 -2015-12-31 08:00:00,10692.0 -2015-12-31 09:00:00,10984.0 -2015-12-31 10:00:00,11162.0 -2015-12-31 11:00:00,11289.0 -2015-12-31 12:00:00,11306.0 -2015-12-31 13:00:00,11273.0 -2015-12-31 14:00:00,11196.0 -2015-12-31 15:00:00,11229.0 -2015-12-31 16:00:00,11226.0 -2015-12-31 17:00:00,11286.0 -2015-12-31 18:00:00,11937.0 -2015-12-31 19:00:00,12564.0 -2015-12-31 20:00:00,12297.0 -2015-12-31 21:00:00,11944.0 -2015-12-31 22:00:00,11607.0 -2015-12-31 23:00:00,11277.0 -2016-01-01 00:00:00,10802.0 -2015-12-30 01:00:00,10309.0 -2015-12-30 02:00:00,9794.0 -2015-12-30 03:00:00,9477.0 -2015-12-30 04:00:00,9300.0 -2015-12-30 05:00:00,9260.0 -2015-12-30 06:00:00,9488.0 -2015-12-30 07:00:00,10061.0 -2015-12-30 08:00:00,10921.0 -2015-12-30 09:00:00,11486.0 -2015-12-30 10:00:00,11670.0 -2015-12-30 11:00:00,11771.0 -2015-12-30 12:00:00,11854.0 -2015-12-30 13:00:00,11865.0 -2015-12-30 14:00:00,11837.0 -2015-12-30 15:00:00,11897.0 -2015-12-30 16:00:00,11851.0 -2015-12-30 17:00:00,11870.0 -2015-12-30 18:00:00,12459.0 -2015-12-30 19:00:00,13039.0 -2015-12-30 20:00:00,12781.0 -2015-12-30 21:00:00,12562.0 -2015-12-30 22:00:00,12281.0 -2015-12-30 23:00:00,11843.0 -2015-12-31 00:00:00,11168.0 -2015-12-29 01:00:00,10199.0 -2015-12-29 02:00:00,9664.0 -2015-12-29 03:00:00,9309.0 -2015-12-29 04:00:00,9168.0 -2015-12-29 05:00:00,9174.0 -2015-12-29 06:00:00,9393.0 -2015-12-29 07:00:00,10002.0 -2015-12-29 08:00:00,10851.0 -2015-12-29 09:00:00,11338.0 -2015-12-29 10:00:00,11519.0 -2015-12-29 11:00:00,11691.0 -2015-12-29 12:00:00,11834.0 -2015-12-29 13:00:00,11843.0 -2015-12-29 14:00:00,11851.0 -2015-12-29 15:00:00,11873.0 -2015-12-29 16:00:00,11847.0 -2015-12-29 17:00:00,11902.0 -2015-12-29 18:00:00,12520.0 -2015-12-29 19:00:00,12973.0 -2015-12-29 20:00:00,12707.0 -2015-12-29 21:00:00,12464.0 -2015-12-29 22:00:00,12204.0 -2015-12-29 23:00:00,11773.0 -2015-12-30 00:00:00,11045.0 -2015-12-28 01:00:00,9714.0 -2015-12-28 02:00:00,9346.0 -2015-12-28 03:00:00,9077.0 -2015-12-28 04:00:00,9004.0 -2015-12-28 05:00:00,9041.0 -2015-12-28 06:00:00,9341.0 -2015-12-28 07:00:00,10054.0 -2015-12-28 08:00:00,11018.0 -2015-12-28 09:00:00,11720.0 -2015-12-28 10:00:00,12063.0 -2015-12-28 11:00:00,12280.0 -2015-12-28 12:00:00,12568.0 -2015-12-28 13:00:00,12678.0 -2015-12-28 14:00:00,12724.0 -2015-12-28 15:00:00,12727.0 -2015-12-28 16:00:00,12651.0 -2015-12-28 17:00:00,12720.0 -2015-12-28 18:00:00,13195.0 -2015-12-28 19:00:00,13398.0 -2015-12-28 20:00:00,13068.0 -2015-12-28 21:00:00,12738.0 -2015-12-28 22:00:00,12361.0 -2015-12-28 23:00:00,11797.0 -2015-12-29 00:00:00,10920.0 -2015-12-27 01:00:00,9371.0 -2015-12-27 02:00:00,8914.0 -2015-12-27 03:00:00,8674.0 -2015-12-27 04:00:00,8447.0 -2015-12-27 05:00:00,8403.0 -2015-12-27 06:00:00,8399.0 -2015-12-27 07:00:00,8542.0 -2015-12-27 08:00:00,8810.0 -2015-12-27 09:00:00,9017.0 -2015-12-27 10:00:00,9219.0 -2015-12-27 11:00:00,9471.0 -2015-12-27 12:00:00,9632.0 -2015-12-27 13:00:00,9710.0 -2015-12-27 14:00:00,9741.0 -2015-12-27 15:00:00,9786.0 -2015-12-27 16:00:00,9848.0 -2015-12-27 17:00:00,10035.0 -2015-12-27 18:00:00,10824.0 -2015-12-27 19:00:00,11169.0 -2015-12-27 20:00:00,11384.0 -2015-12-27 21:00:00,11271.0 -2015-12-27 22:00:00,11111.0 -2015-12-27 23:00:00,10783.0 -2015-12-28 00:00:00,10315.0 -2015-12-26 01:00:00,9017.0 -2015-12-26 02:00:00,8610.0 -2015-12-26 03:00:00,8344.0 -2015-12-26 04:00:00,8170.0 -2015-12-26 05:00:00,8105.0 -2015-12-26 06:00:00,8229.0 -2015-12-26 07:00:00,8474.0 -2015-12-26 08:00:00,8871.0 -2015-12-26 09:00:00,9195.0 -2015-12-26 10:00:00,9454.0 -2015-12-26 11:00:00,9785.0 -2015-12-26 12:00:00,10112.0 -2015-12-26 13:00:00,10214.0 -2015-12-26 14:00:00,10099.0 -2015-12-26 15:00:00,9943.0 -2015-12-26 16:00:00,9923.0 -2015-12-26 17:00:00,10039.0 -2015-12-26 18:00:00,10593.0 -2015-12-26 19:00:00,10879.0 -2015-12-26 20:00:00,10876.0 -2015-12-26 21:00:00,10770.0 -2015-12-26 22:00:00,10602.0 -2015-12-26 23:00:00,10372.0 -2015-12-27 00:00:00,9902.0 -2015-12-25 01:00:00,9099.0 -2015-12-25 02:00:00,8618.0 -2015-12-25 03:00:00,8322.0 -2015-12-25 04:00:00,8086.0 -2015-12-25 05:00:00,8036.0 -2015-12-25 06:00:00,8106.0 -2015-12-25 07:00:00,8323.0 -2015-12-25 08:00:00,8622.0 -2015-12-25 09:00:00,8685.0 -2015-12-25 10:00:00,8698.0 -2015-12-25 11:00:00,8770.0 -2015-12-25 12:00:00,8818.0 -2015-12-25 13:00:00,8821.0 -2015-12-25 14:00:00,8776.0 -2015-12-25 15:00:00,8716.0 -2015-12-25 16:00:00,8689.0 -2015-12-25 17:00:00,8824.0 -2015-12-25 18:00:00,9554.0 -2015-12-25 19:00:00,10062.0 -2015-12-25 20:00:00,10054.0 -2015-12-25 21:00:00,10017.0 -2015-12-25 22:00:00,9980.0 -2015-12-25 23:00:00,9858.0 -2015-12-26 00:00:00,9521.0 -2015-12-24 01:00:00,9620.0 -2015-12-24 02:00:00,9032.0 -2015-12-24 03:00:00,8687.0 -2015-12-24 04:00:00,8510.0 -2015-12-24 05:00:00,8459.0 -2015-12-24 06:00:00,8548.0 -2015-12-24 07:00:00,8936.0 -2015-12-24 08:00:00,9491.0 -2015-12-24 09:00:00,9721.0 -2015-12-24 10:00:00,9859.0 -2015-12-24 11:00:00,9976.0 -2015-12-24 12:00:00,10025.0 -2015-12-24 13:00:00,10039.0 -2015-12-24 14:00:00,9956.0 -2015-12-24 15:00:00,9987.0 -2015-12-24 16:00:00,9961.0 -2015-12-24 17:00:00,10111.0 -2015-12-24 18:00:00,10718.0 -2015-12-24 19:00:00,10992.0 -2015-12-24 20:00:00,10608.0 -2015-12-24 21:00:00,10325.0 -2015-12-24 22:00:00,10133.0 -2015-12-24 23:00:00,9907.0 -2015-12-25 00:00:00,9539.0 -2015-12-23 01:00:00,10200.0 -2015-12-23 02:00:00,9550.0 -2015-12-23 03:00:00,9141.0 -2015-12-23 04:00:00,8853.0 -2015-12-23 05:00:00,8758.0 -2015-12-23 06:00:00,8943.0 -2015-12-23 07:00:00,9493.0 -2015-12-23 08:00:00,10320.0 -2015-12-23 09:00:00,10886.0 -2015-12-23 10:00:00,11180.0 -2015-12-23 11:00:00,11491.0 -2015-12-23 12:00:00,11726.0 -2015-12-23 13:00:00,11733.0 -2015-12-23 14:00:00,11537.0 -2015-12-23 15:00:00,11325.0 -2015-12-23 16:00:00,11172.0 -2015-12-23 17:00:00,11200.0 -2015-12-23 18:00:00,11621.0 -2015-12-23 19:00:00,12146.0 -2015-12-23 20:00:00,11902.0 -2015-12-23 21:00:00,11704.0 -2015-12-23 22:00:00,11426.0 -2015-12-23 23:00:00,10982.0 -2015-12-24 00:00:00,10344.0 -2015-12-22 01:00:00,9863.0 -2015-12-22 02:00:00,9275.0 -2015-12-22 03:00:00,8967.0 -2015-12-22 04:00:00,8762.0 -2015-12-22 05:00:00,8734.0 -2015-12-22 06:00:00,8977.0 -2015-12-22 07:00:00,9658.0 -2015-12-22 08:00:00,10604.0 -2015-12-22 09:00:00,11264.0 -2015-12-22 10:00:00,11514.0 -2015-12-22 11:00:00,11684.0 -2015-12-22 12:00:00,11789.0 -2015-12-22 13:00:00,11836.0 -2015-12-22 14:00:00,11845.0 -2015-12-22 15:00:00,11894.0 -2015-12-22 16:00:00,11831.0 -2015-12-22 17:00:00,11858.0 -2015-12-22 18:00:00,12533.0 -2015-12-22 19:00:00,12969.0 -2015-12-22 20:00:00,12732.0 -2015-12-22 21:00:00,12541.0 -2015-12-22 22:00:00,12273.0 -2015-12-22 23:00:00,11809.0 -2015-12-23 00:00:00,11043.0 -2015-12-21 01:00:00,9670.0 -2015-12-21 02:00:00,9285.0 -2015-12-21 03:00:00,9084.0 -2015-12-21 04:00:00,8979.0 -2015-12-21 05:00:00,8984.0 -2015-12-21 06:00:00,9246.0 -2015-12-21 07:00:00,9891.0 -2015-12-21 08:00:00,10866.0 -2015-12-21 09:00:00,11580.0 -2015-12-21 10:00:00,11732.0 -2015-12-21 11:00:00,11908.0 -2015-12-21 12:00:00,11919.0 -2015-12-21 13:00:00,11939.0 -2015-12-21 14:00:00,11842.0 -2015-12-21 15:00:00,11709.0 -2015-12-21 16:00:00,11640.0 -2015-12-21 17:00:00,11690.0 -2015-12-21 18:00:00,12227.0 -2015-12-21 19:00:00,12574.0 -2015-12-21 20:00:00,12340.0 -2015-12-21 21:00:00,12137.0 -2015-12-21 22:00:00,11878.0 -2015-12-21 23:00:00,11428.0 -2015-12-22 00:00:00,10651.0 -2015-12-20 01:00:00,10631.0 -2015-12-20 02:00:00,10106.0 -2015-12-20 03:00:00,9748.0 -2015-12-20 04:00:00,9569.0 -2015-12-20 05:00:00,9426.0 -2015-12-20 06:00:00,9459.0 -2015-12-20 07:00:00,9523.0 -2015-12-20 08:00:00,9814.0 -2015-12-20 09:00:00,9915.0 -2015-12-20 10:00:00,9995.0 -2015-12-20 11:00:00,10094.0 -2015-12-20 12:00:00,10017.0 -2015-12-20 13:00:00,9951.0 -2015-12-20 14:00:00,9851.0 -2015-12-20 15:00:00,9779.0 -2015-12-20 16:00:00,9779.0 -2015-12-20 17:00:00,9986.0 -2015-12-20 18:00:00,10911.0 -2015-12-20 19:00:00,11437.0 -2015-12-20 20:00:00,11463.0 -2015-12-20 21:00:00,11379.0 -2015-12-20 22:00:00,11236.0 -2015-12-20 23:00:00,10871.0 -2015-12-21 00:00:00,10300.0 -2015-12-19 01:00:00,11050.0 -2015-12-19 02:00:00,10488.0 -2015-12-19 03:00:00,10244.0 -2015-12-19 04:00:00,10065.0 -2015-12-19 05:00:00,9998.0 -2015-12-19 06:00:00,10091.0 -2015-12-19 07:00:00,10457.0 -2015-12-19 08:00:00,10964.0 -2015-12-19 09:00:00,11206.0 -2015-12-19 10:00:00,11392.0 -2015-12-19 11:00:00,11476.0 -2015-12-19 12:00:00,11477.0 -2015-12-19 13:00:00,11333.0 -2015-12-19 14:00:00,11127.0 -2015-12-19 15:00:00,10868.0 -2015-12-19 16:00:00,10720.0 -2015-12-19 17:00:00,10775.0 -2015-12-19 18:00:00,11612.0 -2015-12-19 19:00:00,12319.0 -2015-12-19 20:00:00,12330.0 -2015-12-19 21:00:00,12191.0 -2015-12-19 22:00:00,12037.0 -2015-12-19 23:00:00,11780.0 -2015-12-20 00:00:00,11252.0 -2015-12-18 01:00:00,10770.0 -2015-12-18 02:00:00,10207.0 -2015-12-18 03:00:00,9893.0 -2015-12-18 04:00:00,9741.0 -2015-12-18 05:00:00,9733.0 -2015-12-18 06:00:00,10007.0 -2015-12-18 07:00:00,10725.0 -2015-12-18 08:00:00,11854.0 -2015-12-18 09:00:00,12452.0 -2015-12-18 10:00:00,12558.0 -2015-12-18 11:00:00,12535.0 -2015-12-18 12:00:00,12479.0 -2015-12-18 13:00:00,12395.0 -2015-12-18 14:00:00,12301.0 -2015-12-18 15:00:00,12255.0 -2015-12-18 16:00:00,12087.0 -2015-12-18 17:00:00,12210.0 -2015-12-18 18:00:00,13041.0 -2015-12-18 19:00:00,13553.0 -2015-12-18 20:00:00,13272.0 -2015-12-18 21:00:00,13070.0 -2015-12-18 22:00:00,12799.0 -2015-12-18 23:00:00,12421.0 -2015-12-19 00:00:00,11751.0 -2015-12-17 01:00:00,10421.0 -2015-12-17 02:00:00,9817.0 -2015-12-17 03:00:00,9492.0 -2015-12-17 04:00:00,9342.0 -2015-12-17 05:00:00,9349.0 -2015-12-17 06:00:00,9620.0 -2015-12-17 07:00:00,10385.0 -2015-12-17 08:00:00,11563.0 -2015-12-17 09:00:00,12202.0 -2015-12-17 10:00:00,12345.0 -2015-12-17 11:00:00,12389.0 -2015-12-17 12:00:00,12456.0 -2015-12-17 13:00:00,12360.0 -2015-12-17 14:00:00,12186.0 -2015-12-17 15:00:00,12122.0 -2015-12-17 16:00:00,12047.0 -2015-12-17 17:00:00,12134.0 -2015-12-17 18:00:00,12893.0 -2015-12-17 19:00:00,13458.0 -2015-12-17 20:00:00,13254.0 -2015-12-17 21:00:00,13117.0 -2015-12-17 22:00:00,12881.0 -2015-12-17 23:00:00,12399.0 -2015-12-18 00:00:00,11565.0 -2015-12-16 01:00:00,10182.0 -2015-12-16 02:00:00,9598.0 -2015-12-16 03:00:00,9272.0 -2015-12-16 04:00:00,9074.0 -2015-12-16 05:00:00,9045.0 -2015-12-16 06:00:00,9304.0 -2015-12-16 07:00:00,10014.0 -2015-12-16 08:00:00,11149.0 -2015-12-16 09:00:00,11755.0 -2015-12-16 10:00:00,11891.0 -2015-12-16 11:00:00,11980.0 -2015-12-16 12:00:00,12000.0 -2015-12-16 13:00:00,11936.0 -2015-12-16 14:00:00,11692.0 -2015-12-16 15:00:00,11571.0 -2015-12-16 16:00:00,11498.0 -2015-12-16 17:00:00,11480.0 -2015-12-16 18:00:00,12234.0 -2015-12-16 19:00:00,12960.0 -2015-12-16 20:00:00,12892.0 -2015-12-16 21:00:00,12735.0 -2015-12-16 22:00:00,12503.0 -2015-12-16 23:00:00,12023.0 -2015-12-17 00:00:00,11183.0 -2015-12-15 01:00:00,9860.0 -2015-12-15 02:00:00,9270.0 -2015-12-15 03:00:00,8966.0 -2015-12-15 04:00:00,8800.0 -2015-12-15 05:00:00,8765.0 -2015-12-15 06:00:00,9032.0 -2015-12-15 07:00:00,9753.0 -2015-12-15 08:00:00,10913.0 -2015-12-15 09:00:00,11575.0 -2015-12-15 10:00:00,11678.0 -2015-12-15 11:00:00,11746.0 -2015-12-15 12:00:00,11836.0 -2015-12-15 13:00:00,11797.0 -2015-12-15 14:00:00,11752.0 -2015-12-15 15:00:00,11792.0 -2015-12-15 16:00:00,11771.0 -2015-12-15 17:00:00,11849.0 -2015-12-15 18:00:00,12607.0 -2015-12-15 19:00:00,12986.0 -2015-12-15 20:00:00,12815.0 -2015-12-15 21:00:00,12675.0 -2015-12-15 22:00:00,12386.0 -2015-12-15 23:00:00,11879.0 -2015-12-16 00:00:00,11028.0 -2015-12-14 01:00:00,9083.0 -2015-12-14 02:00:00,8666.0 -2015-12-14 03:00:00,8443.0 -2015-12-14 04:00:00,8261.0 -2015-12-14 05:00:00,8282.0 -2015-12-14 06:00:00,8489.0 -2015-12-14 07:00:00,9187.0 -2015-12-14 08:00:00,10417.0 -2015-12-14 09:00:00,11083.0 -2015-12-14 10:00:00,11243.0 -2015-12-14 11:00:00,11342.0 -2015-12-14 12:00:00,11488.0 -2015-12-14 13:00:00,11523.0 -2015-12-14 14:00:00,11544.0 -2015-12-14 15:00:00,11527.0 -2015-12-14 16:00:00,11457.0 -2015-12-14 17:00:00,11599.0 -2015-12-14 18:00:00,12314.0 -2015-12-14 19:00:00,12725.0 -2015-12-14 20:00:00,12570.0 -2015-12-14 21:00:00,12378.0 -2015-12-14 22:00:00,12052.0 -2015-12-14 23:00:00,11534.0 -2015-12-15 00:00:00,10692.0 -2015-12-13 01:00:00,9109.0 -2015-12-13 02:00:00,8597.0 -2015-12-13 03:00:00,8252.0 -2015-12-13 04:00:00,8044.0 -2015-12-13 05:00:00,7927.0 -2015-12-13 06:00:00,7957.0 -2015-12-13 07:00:00,8067.0 -2015-12-13 08:00:00,8361.0 -2015-12-13 09:00:00,8601.0 -2015-12-13 10:00:00,8873.0 -2015-12-13 11:00:00,9134.0 -2015-12-13 12:00:00,9354.0 -2015-12-13 13:00:00,9461.0 -2015-12-13 14:00:00,9564.0 -2015-12-13 15:00:00,9555.0 -2015-12-13 16:00:00,9680.0 -2015-12-13 17:00:00,10053.0 -2015-12-13 18:00:00,10805.0 -2015-12-13 19:00:00,11124.0 -2015-12-13 20:00:00,11093.0 -2015-12-13 21:00:00,11015.0 -2015-12-13 22:00:00,10743.0 -2015-12-13 23:00:00,10342.0 -2015-12-14 00:00:00,9694.0 -2015-12-12 01:00:00,9545.0 -2015-12-12 02:00:00,8926.0 -2015-12-12 03:00:00,8606.0 -2015-12-12 04:00:00,8417.0 -2015-12-12 05:00:00,8305.0 -2015-12-12 06:00:00,8356.0 -2015-12-12 07:00:00,8657.0 -2015-12-12 08:00:00,9114.0 -2015-12-12 09:00:00,9445.0 -2015-12-12 10:00:00,9785.0 -2015-12-12 11:00:00,9992.0 -2015-12-12 12:00:00,10120.0 -2015-12-12 13:00:00,10087.0 -2015-12-12 14:00:00,9952.0 -2015-12-12 15:00:00,9850.0 -2015-12-12 16:00:00,9784.0 -2015-12-12 17:00:00,9939.0 -2015-12-12 18:00:00,10681.0 -2015-12-12 19:00:00,11023.0 -2015-12-12 20:00:00,10924.0 -2015-12-12 21:00:00,10785.0 -2015-12-12 22:00:00,10572.0 -2015-12-12 23:00:00,10294.0 -2015-12-13 00:00:00,9750.0 -2015-12-11 01:00:00,9809.0 -2015-12-11 02:00:00,9258.0 -2015-12-11 03:00:00,8932.0 -2015-12-11 04:00:00,8740.0 -2015-12-11 05:00:00,8723.0 -2015-12-11 06:00:00,8955.0 -2015-12-11 07:00:00,9682.0 -2015-12-11 08:00:00,10819.0 -2015-12-11 09:00:00,11274.0 -2015-12-11 10:00:00,11283.0 -2015-12-11 11:00:00,11356.0 -2015-12-11 12:00:00,11370.0 -2015-12-11 13:00:00,11264.0 -2015-12-11 14:00:00,11142.0 -2015-12-11 15:00:00,11088.0 -2015-12-11 16:00:00,10991.0 -2015-12-11 17:00:00,11044.0 -2015-12-11 18:00:00,11877.0 -2015-12-11 19:00:00,12308.0 -2015-12-11 20:00:00,12095.0 -2015-12-11 21:00:00,11841.0 -2015-12-11 22:00:00,11560.0 -2015-12-11 23:00:00,11113.0 -2015-12-12 00:00:00,10429.0 -2015-12-10 01:00:00,9854.0 -2015-12-10 02:00:00,9317.0 -2015-12-10 03:00:00,9006.0 -2015-12-10 04:00:00,8864.0 -2015-12-10 05:00:00,8828.0 -2015-12-10 06:00:00,9052.0 -2015-12-10 07:00:00,9761.0 -2015-12-10 08:00:00,10904.0 -2015-12-10 09:00:00,11470.0 -2015-12-10 10:00:00,11562.0 -2015-12-10 11:00:00,11611.0 -2015-12-10 12:00:00,11605.0 -2015-12-10 13:00:00,11521.0 -2015-12-10 14:00:00,11470.0 -2015-12-10 15:00:00,11535.0 -2015-12-10 16:00:00,11410.0 -2015-12-10 17:00:00,11441.0 -2015-12-10 18:00:00,12182.0 -2015-12-10 19:00:00,12616.0 -2015-12-10 20:00:00,12458.0 -2015-12-10 21:00:00,12282.0 -2015-12-10 22:00:00,12015.0 -2015-12-10 23:00:00,11520.0 -2015-12-11 00:00:00,10681.0 -2015-12-09 01:00:00,9990.0 -2015-12-09 02:00:00,9415.0 -2015-12-09 03:00:00,9087.0 -2015-12-09 04:00:00,8865.0 -2015-12-09 05:00:00,8828.0 -2015-12-09 06:00:00,9098.0 -2015-12-09 07:00:00,9820.0 -2015-12-09 08:00:00,10965.0 -2015-12-09 09:00:00,11442.0 -2015-12-09 10:00:00,11461.0 -2015-12-09 11:00:00,11443.0 -2015-12-09 12:00:00,11462.0 -2015-12-09 13:00:00,11370.0 -2015-12-09 14:00:00,11256.0 -2015-12-09 15:00:00,11233.0 -2015-12-09 16:00:00,11166.0 -2015-12-09 17:00:00,11230.0 -2015-12-09 18:00:00,12041.0 -2015-12-09 19:00:00,12616.0 -2015-12-09 20:00:00,12462.0 -2015-12-09 21:00:00,12266.0 -2015-12-09 22:00:00,12033.0 -2015-12-09 23:00:00,11477.0 -2015-12-10 00:00:00,10661.0 -2015-12-08 01:00:00,10282.0 -2015-12-08 02:00:00,9723.0 -2015-12-08 03:00:00,9436.0 -2015-12-08 04:00:00,9193.0 -2015-12-08 05:00:00,9198.0 -2015-12-08 06:00:00,9457.0 -2015-12-08 07:00:00,10173.0 -2015-12-08 08:00:00,11349.0 -2015-12-08 09:00:00,11838.0 -2015-12-08 10:00:00,11885.0 -2015-12-08 11:00:00,11920.0 -2015-12-08 12:00:00,11900.0 -2015-12-08 13:00:00,11830.0 -2015-12-08 14:00:00,11756.0 -2015-12-08 15:00:00,11709.0 -2015-12-08 16:00:00,11598.0 -2015-12-08 17:00:00,11690.0 -2015-12-08 18:00:00,12418.0 -2015-12-08 19:00:00,12939.0 -2015-12-08 20:00:00,12741.0 -2015-12-08 21:00:00,12522.0 -2015-12-08 22:00:00,12223.0 -2015-12-08 23:00:00,11697.0 -2015-12-09 00:00:00,10835.0 -2015-12-07 01:00:00,9548.0 -2015-12-07 02:00:00,9209.0 -2015-12-07 03:00:00,8995.0 -2015-12-07 04:00:00,8963.0 -2015-12-07 05:00:00,8976.0 -2015-12-07 06:00:00,9349.0 -2015-12-07 07:00:00,10165.0 -2015-12-07 08:00:00,11398.0 -2015-12-07 09:00:00,12030.0 -2015-12-07 10:00:00,12120.0 -2015-12-07 11:00:00,12192.0 -2015-12-07 12:00:00,12205.0 -2015-12-07 13:00:00,12170.0 -2015-12-07 14:00:00,12146.0 -2015-12-07 15:00:00,12156.0 -2015-12-07 16:00:00,12094.0 -2015-12-07 17:00:00,12176.0 -2015-12-07 18:00:00,12828.0 -2015-12-07 19:00:00,13220.0 -2015-12-07 20:00:00,13053.0 -2015-12-07 21:00:00,12839.0 -2015-12-07 22:00:00,12557.0 -2015-12-07 23:00:00,11968.0 -2015-12-08 00:00:00,11104.0 -2015-12-06 01:00:00,9851.0 -2015-12-06 02:00:00,9374.0 -2015-12-06 03:00:00,9023.0 -2015-12-06 04:00:00,8853.0 -2015-12-06 05:00:00,8772.0 -2015-12-06 06:00:00,8822.0 -2015-12-06 07:00:00,8962.0 -2015-12-06 08:00:00,9274.0 -2015-12-06 09:00:00,9322.0 -2015-12-06 10:00:00,9510.0 -2015-12-06 11:00:00,9572.0 -2015-12-06 12:00:00,9590.0 -2015-12-06 13:00:00,9505.0 -2015-12-06 14:00:00,9523.0 -2015-12-06 15:00:00,9504.0 -2015-12-06 16:00:00,9568.0 -2015-12-06 17:00:00,9725.0 -2015-12-06 18:00:00,10642.0 -2015-12-06 19:00:00,11226.0 -2015-12-06 20:00:00,11307.0 -2015-12-06 21:00:00,11250.0 -2015-12-06 22:00:00,11107.0 -2015-12-06 23:00:00,10745.0 -2015-12-07 00:00:00,10139.0 -2015-12-05 01:00:00,10180.0 -2015-12-05 02:00:00,9600.0 -2015-12-05 03:00:00,9294.0 -2015-12-05 04:00:00,9078.0 -2015-12-05 05:00:00,9076.0 -2015-12-05 06:00:00,9159.0 -2015-12-05 07:00:00,9492.0 -2015-12-05 08:00:00,10008.0 -2015-12-05 09:00:00,10298.0 -2015-12-05 10:00:00,10623.0 -2015-12-05 11:00:00,10730.0 -2015-12-05 12:00:00,10615.0 -2015-12-05 13:00:00,10469.0 -2015-12-05 14:00:00,10268.0 -2015-12-05 15:00:00,10042.0 -2015-12-05 16:00:00,9890.0 -2015-12-05 17:00:00,10009.0 -2015-12-05 18:00:00,10847.0 -2015-12-05 19:00:00,11524.0 -2015-12-05 20:00:00,11479.0 -2015-12-05 21:00:00,11395.0 -2015-12-05 22:00:00,11224.0 -2015-12-05 23:00:00,10987.0 -2015-12-06 00:00:00,10439.0 -2015-12-04 01:00:00,10413.0 -2015-12-04 02:00:00,9868.0 -2015-12-04 03:00:00,9569.0 -2015-12-04 04:00:00,9389.0 -2015-12-04 05:00:00,9393.0 -2015-12-04 06:00:00,9655.0 -2015-12-04 07:00:00,10360.0 -2015-12-04 08:00:00,11491.0 -2015-12-04 09:00:00,11959.0 -2015-12-04 10:00:00,12016.0 -2015-12-04 11:00:00,11959.0 -2015-12-04 12:00:00,11830.0 -2015-12-04 13:00:00,11668.0 -2015-12-04 14:00:00,11490.0 -2015-12-04 15:00:00,11407.0 -2015-12-04 16:00:00,11274.0 -2015-12-04 17:00:00,11249.0 -2015-12-04 18:00:00,11939.0 -2015-12-04 19:00:00,12516.0 -2015-12-04 20:00:00,12385.0 -2015-12-04 21:00:00,12184.0 -2015-12-04 22:00:00,11912.0 -2015-12-04 23:00:00,11551.0 -2015-12-05 00:00:00,10871.0 -2015-12-03 01:00:00,10426.0 -2015-12-03 02:00:00,9930.0 -2015-12-03 03:00:00,9642.0 -2015-12-03 04:00:00,9448.0 -2015-12-03 05:00:00,9402.0 -2015-12-03 06:00:00,9672.0 -2015-12-03 07:00:00,10387.0 -2015-12-03 08:00:00,11514.0 -2015-12-03 09:00:00,12036.0 -2015-12-03 10:00:00,12210.0 -2015-12-03 11:00:00,12296.0 -2015-12-03 12:00:00,12384.0 -2015-12-03 13:00:00,12355.0 -2015-12-03 14:00:00,12289.0 -2015-12-03 15:00:00,12328.0 -2015-12-03 16:00:00,12258.0 -2015-12-03 17:00:00,12346.0 -2015-12-03 18:00:00,12952.0 -2015-12-03 19:00:00,13256.0 -2015-12-03 20:00:00,13037.0 -2015-12-03 21:00:00,12813.0 -2015-12-03 22:00:00,12530.0 -2015-12-03 23:00:00,12036.0 -2015-12-04 00:00:00,11235.0 -2015-12-02 01:00:00,9989.0 -2015-12-02 02:00:00,9504.0 -2015-12-02 03:00:00,9226.0 -2015-12-02 04:00:00,9073.0 -2015-12-02 05:00:00,9067.0 -2015-12-02 06:00:00,9451.0 -2015-12-02 07:00:00,10220.0 -2015-12-02 08:00:00,11350.0 -2015-12-02 09:00:00,11941.0 -2015-12-02 10:00:00,12140.0 -2015-12-02 11:00:00,12180.0 -2015-12-02 12:00:00,12204.0 -2015-12-02 13:00:00,12137.0 -2015-12-02 14:00:00,12091.0 -2015-12-02 15:00:00,12102.0 -2015-12-02 16:00:00,12021.0 -2015-12-02 17:00:00,12140.0 -2015-12-02 18:00:00,12868.0 -2015-12-02 19:00:00,13263.0 -2015-12-02 20:00:00,13081.0 -2015-12-02 21:00:00,12894.0 -2015-12-02 22:00:00,12581.0 -2015-12-02 23:00:00,12048.0 -2015-12-03 00:00:00,11222.0 -2015-12-01 01:00:00,9843.0 -2015-12-01 02:00:00,9360.0 -2015-12-01 03:00:00,9075.0 -2015-12-01 04:00:00,8911.0 -2015-12-01 05:00:00,8840.0 -2015-12-01 06:00:00,9068.0 -2015-12-01 07:00:00,9770.0 -2015-12-01 08:00:00,10996.0 -2015-12-01 09:00:00,11570.0 -2015-12-01 10:00:00,11696.0 -2015-12-01 11:00:00,11766.0 -2015-12-01 12:00:00,11722.0 -2015-12-01 13:00:00,11573.0 -2015-12-01 14:00:00,11524.0 -2015-12-01 15:00:00,11577.0 -2015-12-01 16:00:00,11473.0 -2015-12-01 17:00:00,11501.0 -2015-12-01 18:00:00,12193.0 -2015-12-01 19:00:00,12709.0 -2015-12-01 20:00:00,12540.0 -2015-12-01 21:00:00,12367.0 -2015-12-01 22:00:00,12073.0 -2015-12-01 23:00:00,11539.0 -2015-12-02 00:00:00,10745.0 -2015-11-30 01:00:00,9332.0 -2015-11-30 02:00:00,8931.0 -2015-11-30 03:00:00,8743.0 -2015-11-30 04:00:00,8671.0 -2015-11-30 05:00:00,8694.0 -2015-11-30 06:00:00,8998.0 -2015-11-30 07:00:00,9825.0 -2015-11-30 08:00:00,11003.0 -2015-11-30 09:00:00,11599.0 -2015-11-30 10:00:00,11724.0 -2015-11-30 11:00:00,11822.0 -2015-11-30 12:00:00,11917.0 -2015-11-30 13:00:00,11947.0 -2015-11-30 14:00:00,11904.0 -2015-11-30 15:00:00,11871.0 -2015-11-30 16:00:00,11850.0 -2015-11-30 17:00:00,11908.0 -2015-11-30 18:00:00,12523.0 -2015-11-30 19:00:00,12842.0 -2015-11-30 20:00:00,12609.0 -2015-11-30 21:00:00,12408.0 -2015-11-30 22:00:00,12071.0 -2015-11-30 23:00:00,11514.0 -2015-12-01 00:00:00,10670.0 -2015-11-29 01:00:00,9528.0 -2015-11-29 02:00:00,9115.0 -2015-11-29 03:00:00,8863.0 -2015-11-29 04:00:00,8714.0 -2015-11-29 05:00:00,8692.0 -2015-11-29 06:00:00,8715.0 -2015-11-29 07:00:00,8903.0 -2015-11-29 08:00:00,9141.0 -2015-11-29 09:00:00,9226.0 -2015-11-29 10:00:00,9417.0 -2015-11-29 11:00:00,9542.0 -2015-11-29 12:00:00,9585.0 -2015-11-29 13:00:00,9614.0 -2015-11-29 14:00:00,9570.0 -2015-11-29 15:00:00,9557.0 -2015-11-29 16:00:00,9628.0 -2015-11-29 17:00:00,9852.0 -2015-11-29 18:00:00,10675.0 -2015-11-29 19:00:00,11270.0 -2015-11-29 20:00:00,11253.0 -2015-11-29 21:00:00,11180.0 -2015-11-29 22:00:00,10876.0 -2015-11-29 23:00:00,10498.0 -2015-11-30 00:00:00,9847.0 -2015-11-28 01:00:00,9302.0 -2015-11-28 02:00:00,8864.0 -2015-11-28 03:00:00,8605.0 -2015-11-28 04:00:00,8446.0 -2015-11-28 05:00:00,8382.0 -2015-11-28 06:00:00,8467.0 -2015-11-28 07:00:00,8711.0 -2015-11-28 08:00:00,9159.0 -2015-11-28 09:00:00,9381.0 -2015-11-28 10:00:00,9769.0 -2015-11-28 11:00:00,10000.0 -2015-11-28 12:00:00,10114.0 -2015-11-28 13:00:00,10063.0 -2015-11-28 14:00:00,10012.0 -2015-11-28 15:00:00,9879.0 -2015-11-28 16:00:00,9871.0 -2015-11-28 17:00:00,9965.0 -2015-11-28 18:00:00,10642.0 -2015-11-28 19:00:00,11071.0 -2015-11-28 20:00:00,10997.0 -2015-11-28 21:00:00,10862.0 -2015-11-28 22:00:00,10711.0 -2015-11-28 23:00:00,10486.0 -2015-11-29 00:00:00,9989.0 -2015-11-27 01:00:00,8284.0 -2015-11-27 02:00:00,7984.0 -2015-11-27 03:00:00,7812.0 -2015-11-27 04:00:00,7690.0 -2015-11-27 05:00:00,7691.0 -2015-11-27 06:00:00,7841.0 -2015-11-27 07:00:00,8294.0 -2015-11-27 08:00:00,8907.0 -2015-11-27 09:00:00,9213.0 -2015-11-27 10:00:00,9539.0 -2015-11-27 11:00:00,9829.0 -2015-11-27 12:00:00,10067.0 -2015-11-27 13:00:00,10164.0 -2015-11-27 14:00:00,10204.0 -2015-11-27 15:00:00,10282.0 -2015-11-27 16:00:00,10337.0 -2015-11-27 17:00:00,10526.0 -2015-11-27 18:00:00,11082.0 -2015-11-27 19:00:00,11269.0 -2015-11-27 20:00:00,11128.0 -2015-11-27 21:00:00,10917.0 -2015-11-27 22:00:00,10684.0 -2015-11-27 23:00:00,10368.0 -2015-11-28 00:00:00,9834.0 -2015-11-26 01:00:00,9230.0 -2015-11-26 02:00:00,8734.0 -2015-11-26 03:00:00,8406.0 -2015-11-26 04:00:00,8214.0 -2015-11-26 05:00:00,8069.0 -2015-11-26 06:00:00,8081.0 -2015-11-26 07:00:00,8251.0 -2015-11-26 08:00:00,8445.0 -2015-11-26 09:00:00,8442.0 -2015-11-26 10:00:00,8738.0 -2015-11-26 11:00:00,9066.0 -2015-11-26 12:00:00,9318.0 -2015-11-26 13:00:00,9448.0 -2015-11-26 14:00:00,9357.0 -2015-11-26 15:00:00,9223.0 -2015-11-26 16:00:00,9119.0 -2015-11-26 17:00:00,9021.0 -2015-11-26 18:00:00,9316.0 -2015-11-26 19:00:00,9399.0 -2015-11-26 20:00:00,9240.0 -2015-11-26 21:00:00,9149.0 -2015-11-26 22:00:00,9053.0 -2015-11-26 23:00:00,8895.0 -2015-11-27 00:00:00,8651.0 -2015-11-25 01:00:00,10075.0 -2015-11-25 02:00:00,9578.0 -2015-11-25 03:00:00,9295.0 -2015-11-25 04:00:00,9133.0 -2015-11-25 05:00:00,9126.0 -2015-11-25 06:00:00,9330.0 -2015-11-25 07:00:00,9954.0 -2015-11-25 08:00:00,10819.0 -2015-11-25 09:00:00,11155.0 -2015-11-25 10:00:00,11324.0 -2015-11-25 11:00:00,11385.0 -2015-11-25 12:00:00,11434.0 -2015-11-25 13:00:00,11422.0 -2015-11-25 14:00:00,11345.0 -2015-11-25 15:00:00,11438.0 -2015-11-25 16:00:00,11390.0 -2015-11-25 17:00:00,11395.0 -2015-11-25 18:00:00,11829.0 -2015-11-25 19:00:00,12040.0 -2015-11-25 20:00:00,11715.0 -2015-11-25 21:00:00,11440.0 -2015-11-25 22:00:00,11138.0 -2015-11-25 23:00:00,10678.0 -2015-11-26 00:00:00,9978.0 -2015-11-24 01:00:00,10219.0 -2015-11-24 02:00:00,9767.0 -2015-11-24 03:00:00,9557.0 -2015-11-24 04:00:00,9432.0 -2015-11-24 05:00:00,9442.0 -2015-11-24 06:00:00,9742.0 -2015-11-24 07:00:00,10430.0 -2015-11-24 08:00:00,11470.0 -2015-11-24 09:00:00,11837.0 -2015-11-24 10:00:00,11999.0 -2015-11-24 11:00:00,11949.0 -2015-11-24 12:00:00,11839.0 -2015-11-24 13:00:00,11861.0 -2015-11-24 14:00:00,11570.0 -2015-11-24 15:00:00,11503.0 -2015-11-24 16:00:00,11426.0 -2015-11-24 17:00:00,11452.0 -2015-11-24 18:00:00,12087.0 -2015-11-24 19:00:00,12603.0 -2015-11-24 20:00:00,12397.0 -2015-11-24 21:00:00,12222.0 -2015-11-24 22:00:00,11935.0 -2015-11-24 23:00:00,11500.0 -2015-11-25 00:00:00,10791.0 -2015-11-23 01:00:00,10111.0 -2015-11-23 02:00:00,9731.0 -2015-11-23 03:00:00,9523.0 -2015-11-23 04:00:00,9402.0 -2015-11-23 05:00:00,9454.0 -2015-11-23 06:00:00,9817.0 -2015-11-23 07:00:00,10494.0 -2015-11-23 08:00:00,11585.0 -2015-11-23 09:00:00,11916.0 -2015-11-23 10:00:00,12033.0 -2015-11-23 11:00:00,12068.0 -2015-11-23 12:00:00,12119.0 -2015-11-23 13:00:00,12111.0 -2015-11-23 14:00:00,12010.0 -2015-11-23 15:00:00,11860.0 -2015-11-23 16:00:00,11673.0 -2015-11-23 17:00:00,11623.0 -2015-11-23 18:00:00,12220.0 -2015-11-23 19:00:00,12878.0 -2015-11-23 20:00:00,12711.0 -2015-11-23 21:00:00,12499.0 -2015-11-23 22:00:00,12196.0 -2015-11-23 23:00:00,11661.0 -2015-11-24 00:00:00,10903.0 -2015-11-22 01:00:00,10216.0 -2015-11-22 02:00:00,9859.0 -2015-11-22 03:00:00,9608.0 -2015-11-22 04:00:00,9502.0 -2015-11-22 05:00:00,9462.0 -2015-11-22 06:00:00,9388.0 -2015-11-22 07:00:00,9585.0 -2015-11-22 08:00:00,9836.0 -2015-11-22 09:00:00,9929.0 -2015-11-22 10:00:00,10113.0 -2015-11-22 11:00:00,10176.0 -2015-11-22 12:00:00,10220.0 -2015-11-22 13:00:00,10212.0 -2015-11-22 14:00:00,10219.0 -2015-11-22 15:00:00,10191.0 -2015-11-22 16:00:00,10226.0 -2015-11-22 17:00:00,10479.0 -2015-11-22 18:00:00,11246.0 -2015-11-22 19:00:00,11925.0 -2015-11-22 20:00:00,11912.0 -2015-11-22 21:00:00,11830.0 -2015-11-22 22:00:00,11572.0 -2015-11-22 23:00:00,11236.0 -2015-11-23 00:00:00,10589.0 -2015-11-21 01:00:00,10087.0 -2015-11-21 02:00:00,9587.0 -2015-11-21 03:00:00,9294.0 -2015-11-21 04:00:00,9116.0 -2015-11-21 05:00:00,9079.0 -2015-11-21 06:00:00,9157.0 -2015-11-21 07:00:00,9412.0 -2015-11-21 08:00:00,9915.0 -2015-11-21 09:00:00,10159.0 -2015-11-21 10:00:00,10631.0 -2015-11-21 11:00:00,10929.0 -2015-11-21 12:00:00,11155.0 -2015-11-21 13:00:00,11179.0 -2015-11-21 14:00:00,11162.0 -2015-11-21 15:00:00,10995.0 -2015-11-21 16:00:00,10911.0 -2015-11-21 17:00:00,10916.0 -2015-11-21 18:00:00,11461.0 -2015-11-21 19:00:00,11919.0 -2015-11-21 20:00:00,11876.0 -2015-11-21 21:00:00,11698.0 -2015-11-21 22:00:00,11497.0 -2015-11-21 23:00:00,11161.0 -2015-11-22 00:00:00,10700.0 -2015-11-20 01:00:00,9890.0 -2015-11-20 02:00:00,9463.0 -2015-11-20 03:00:00,9182.0 -2015-11-20 04:00:00,9029.0 -2015-11-20 05:00:00,9020.0 -2015-11-20 06:00:00,9287.0 -2015-11-20 07:00:00,10009.0 -2015-11-20 08:00:00,11057.0 -2015-11-20 09:00:00,11399.0 -2015-11-20 10:00:00,11501.0 -2015-11-20 11:00:00,11543.0 -2015-11-20 12:00:00,11493.0 -2015-11-20 13:00:00,11468.0 -2015-11-20 14:00:00,11460.0 -2015-11-20 15:00:00,11455.0 -2015-11-20 16:00:00,11439.0 -2015-11-20 17:00:00,11474.0 -2015-11-20 18:00:00,12012.0 -2015-11-20 19:00:00,12287.0 -2015-11-20 20:00:00,12091.0 -2015-11-20 21:00:00,11917.0 -2015-11-20 22:00:00,11679.0 -2015-11-20 23:00:00,11328.0 -2015-11-21 00:00:00,10720.0 -2015-11-19 01:00:00,9234.0 -2015-11-19 02:00:00,8808.0 -2015-11-19 03:00:00,8546.0 -2015-11-19 04:00:00,8400.0 -2015-11-19 05:00:00,8388.0 -2015-11-19 06:00:00,8634.0 -2015-11-19 07:00:00,9368.0 -2015-11-19 08:00:00,10523.0 -2015-11-19 09:00:00,11024.0 -2015-11-19 10:00:00,11145.0 -2015-11-19 11:00:00,11256.0 -2015-11-19 12:00:00,11317.0 -2015-11-19 13:00:00,11319.0 -2015-11-19 14:00:00,11266.0 -2015-11-19 15:00:00,11285.0 -2015-11-19 16:00:00,11243.0 -2015-11-19 17:00:00,11243.0 -2015-11-19 18:00:00,11815.0 -2015-11-19 19:00:00,12379.0 -2015-11-19 20:00:00,12245.0 -2015-11-19 21:00:00,12009.0 -2015-11-19 22:00:00,11759.0 -2015-11-19 23:00:00,11305.0 -2015-11-20 00:00:00,10580.0 -2015-11-18 01:00:00,9216.0 -2015-11-18 02:00:00,8752.0 -2015-11-18 03:00:00,8493.0 -2015-11-18 04:00:00,8321.0 -2015-11-18 05:00:00,8310.0 -2015-11-18 06:00:00,8470.0 -2015-11-18 07:00:00,9089.0 -2015-11-18 08:00:00,10187.0 -2015-11-18 09:00:00,10793.0 -2015-11-18 10:00:00,11152.0 -2015-11-18 11:00:00,11334.0 -2015-11-18 12:00:00,11405.0 -2015-11-18 13:00:00,11349.0 -2015-11-18 14:00:00,11304.0 -2015-11-18 15:00:00,11298.0 -2015-11-18 16:00:00,11163.0 -2015-11-18 17:00:00,11050.0 -2015-11-18 18:00:00,11426.0 -2015-11-18 19:00:00,11914.0 -2015-11-18 20:00:00,11711.0 -2015-11-18 21:00:00,11497.0 -2015-11-18 22:00:00,11205.0 -2015-11-18 23:00:00,10696.0 -2015-11-19 00:00:00,9970.0 -2015-11-17 01:00:00,9234.0 -2015-11-17 02:00:00,8756.0 -2015-11-17 03:00:00,8482.0 -2015-11-17 04:00:00,8318.0 -2015-11-17 05:00:00,8293.0 -2015-11-17 06:00:00,8514.0 -2015-11-17 07:00:00,9254.0 -2015-11-17 08:00:00,10333.0 -2015-11-17 09:00:00,10887.0 -2015-11-17 10:00:00,11102.0 -2015-11-17 11:00:00,11224.0 -2015-11-17 12:00:00,11355.0 -2015-11-17 13:00:00,11414.0 -2015-11-17 14:00:00,11473.0 -2015-11-17 15:00:00,11611.0 -2015-11-17 16:00:00,11640.0 -2015-11-17 17:00:00,11604.0 -2015-11-17 18:00:00,11982.0 -2015-11-17 19:00:00,12122.0 -2015-11-17 20:00:00,11858.0 -2015-11-17 21:00:00,11548.0 -2015-11-17 22:00:00,11251.0 -2015-11-17 23:00:00,10688.0 -2015-11-18 00:00:00,9929.0 -2015-11-16 01:00:00,8404.0 -2015-11-16 02:00:00,8102.0 -2015-11-16 03:00:00,7957.0 -2015-11-16 04:00:00,7850.0 -2015-11-16 05:00:00,7907.0 -2015-11-16 06:00:00,8194.0 -2015-11-16 07:00:00,8990.0 -2015-11-16 08:00:00,10086.0 -2015-11-16 09:00:00,10608.0 -2015-11-16 10:00:00,10799.0 -2015-11-16 11:00:00,10929.0 -2015-11-16 12:00:00,11092.0 -2015-11-16 13:00:00,11118.0 -2015-11-16 14:00:00,11087.0 -2015-11-16 15:00:00,11168.0 -2015-11-16 16:00:00,11109.0 -2015-11-16 17:00:00,11131.0 -2015-11-16 18:00:00,11644.0 -2015-11-16 19:00:00,12007.0 -2015-11-16 20:00:00,11787.0 -2015-11-16 21:00:00,11538.0 -2015-11-16 22:00:00,11252.0 -2015-11-16 23:00:00,10695.0 -2015-11-17 00:00:00,9924.0 -2015-11-15 01:00:00,8834.0 -2015-11-15 02:00:00,8424.0 -2015-11-15 03:00:00,8163.0 -2015-11-15 04:00:00,8008.0 -2015-11-15 05:00:00,7968.0 -2015-11-15 06:00:00,8008.0 -2015-11-15 07:00:00,8118.0 -2015-11-15 08:00:00,8343.0 -2015-11-15 09:00:00,8310.0 -2015-11-15 10:00:00,8541.0 -2015-11-15 11:00:00,8659.0 -2015-11-15 12:00:00,8797.0 -2015-11-15 13:00:00,8875.0 -2015-11-15 14:00:00,8893.0 -2015-11-15 15:00:00,8856.0 -2015-11-15 16:00:00,8792.0 -2015-11-15 17:00:00,8827.0 -2015-11-15 18:00:00,9352.0 -2015-11-15 19:00:00,10120.0 -2015-11-15 20:00:00,10100.0 -2015-11-15 21:00:00,10014.0 -2015-11-15 22:00:00,9789.0 -2015-11-15 23:00:00,9435.0 -2015-11-16 00:00:00,8924.0 -2015-11-14 01:00:00,9683.0 -2015-11-14 02:00:00,9232.0 -2015-11-14 03:00:00,8960.0 -2015-11-14 04:00:00,8808.0 -2015-11-14 05:00:00,8799.0 -2015-11-14 06:00:00,8875.0 -2015-11-14 07:00:00,9215.0 -2015-11-14 08:00:00,9610.0 -2015-11-14 09:00:00,9739.0 -2015-11-14 10:00:00,9981.0 -2015-11-14 11:00:00,10059.0 -2015-11-14 12:00:00,10040.0 -2015-11-14 13:00:00,9921.0 -2015-11-14 14:00:00,9766.0 -2015-11-14 15:00:00,9524.0 -2015-11-14 16:00:00,9397.0 -2015-11-14 17:00:00,9370.0 -2015-11-14 18:00:00,9874.0 -2015-11-14 19:00:00,10492.0 -2015-11-14 20:00:00,10410.0 -2015-11-14 21:00:00,10242.0 -2015-11-14 22:00:00,10062.0 -2015-11-14 23:00:00,9782.0 -2015-11-15 00:00:00,9289.0 -2015-11-13 01:00:00,9595.0 -2015-11-13 02:00:00,9173.0 -2015-11-13 03:00:00,8912.0 -2015-11-13 04:00:00,8784.0 -2015-11-13 05:00:00,8745.0 -2015-11-13 06:00:00,9020.0 -2015-11-13 07:00:00,9760.0 -2015-11-13 08:00:00,10751.0 -2015-11-13 09:00:00,11164.0 -2015-11-13 10:00:00,11342.0 -2015-11-13 11:00:00,11404.0 -2015-11-13 12:00:00,11419.0 -2015-11-13 13:00:00,11383.0 -2015-11-13 14:00:00,11282.0 -2015-11-13 15:00:00,11237.0 -2015-11-13 16:00:00,11062.0 -2015-11-13 17:00:00,10999.0 -2015-11-13 18:00:00,11384.0 -2015-11-13 19:00:00,11954.0 -2015-11-13 20:00:00,11820.0 -2015-11-13 21:00:00,11597.0 -2015-11-13 22:00:00,11288.0 -2015-11-13 23:00:00,10908.0 -2015-11-14 00:00:00,10255.0 -2015-11-12 01:00:00,9123.0 -2015-11-12 02:00:00,8681.0 -2015-11-12 03:00:00,8451.0 -2015-11-12 04:00:00,8291.0 -2015-11-12 05:00:00,8343.0 -2015-11-12 06:00:00,8623.0 -2015-11-12 07:00:00,9352.0 -2015-11-12 08:00:00,10523.0 -2015-11-12 09:00:00,11175.0 -2015-11-12 10:00:00,11451.0 -2015-11-12 11:00:00,11576.0 -2015-11-12 12:00:00,11643.0 -2015-11-12 13:00:00,11679.0 -2015-11-12 14:00:00,11657.0 -2015-11-12 15:00:00,11673.0 -2015-11-12 16:00:00,11611.0 -2015-11-12 17:00:00,11517.0 -2015-11-12 18:00:00,11754.0 -2015-11-12 19:00:00,12254.0 -2015-11-12 20:00:00,12057.0 -2015-11-12 21:00:00,11855.0 -2015-11-12 22:00:00,11550.0 -2015-11-12 23:00:00,11014.0 -2015-11-13 00:00:00,10273.0 -2015-11-11 01:00:00,9283.0 -2015-11-11 02:00:00,8837.0 -2015-11-11 03:00:00,8587.0 -2015-11-11 04:00:00,8450.0 -2015-11-11 05:00:00,8444.0 -2015-11-11 06:00:00,8677.0 -2015-11-11 07:00:00,9344.0 -2015-11-11 08:00:00,10250.0 -2015-11-11 09:00:00,10611.0 -2015-11-11 10:00:00,10766.0 -2015-11-11 11:00:00,10814.0 -2015-11-11 12:00:00,10921.0 -2015-11-11 13:00:00,11006.0 -2015-11-11 14:00:00,11012.0 -2015-11-11 15:00:00,11019.0 -2015-11-11 16:00:00,10982.0 -2015-11-11 17:00:00,11058.0 -2015-11-11 18:00:00,11566.0 -2015-11-11 19:00:00,11855.0 -2015-11-11 20:00:00,11677.0 -2015-11-11 21:00:00,11421.0 -2015-11-11 22:00:00,11026.0 -2015-11-11 23:00:00,10491.0 -2015-11-12 00:00:00,9770.0 -2015-11-10 01:00:00,9229.0 -2015-11-10 02:00:00,8804.0 -2015-11-10 03:00:00,8580.0 -2015-11-10 04:00:00,8464.0 -2015-11-10 05:00:00,8461.0 -2015-11-10 06:00:00,8702.0 -2015-11-10 07:00:00,9469.0 -2015-11-10 08:00:00,10569.0 -2015-11-10 09:00:00,10982.0 -2015-11-10 10:00:00,11048.0 -2015-11-10 11:00:00,11028.0 -2015-11-10 12:00:00,11040.0 -2015-11-10 13:00:00,11027.0 -2015-11-10 14:00:00,10960.0 -2015-11-10 15:00:00,10962.0 -2015-11-10 16:00:00,10838.0 -2015-11-10 17:00:00,10732.0 -2015-11-10 18:00:00,11039.0 -2015-11-10 19:00:00,11708.0 -2015-11-10 20:00:00,11560.0 -2015-11-10 21:00:00,11387.0 -2015-11-10 22:00:00,11109.0 -2015-11-10 23:00:00,10597.0 -2015-11-11 00:00:00,9917.0 -2015-11-09 01:00:00,8665.0 -2015-11-09 02:00:00,8407.0 -2015-11-09 03:00:00,8254.0 -2015-11-09 04:00:00,8214.0 -2015-11-09 05:00:00,8283.0 -2015-11-09 06:00:00,8614.0 -2015-11-09 07:00:00,9424.0 -2015-11-09 08:00:00,10481.0 -2015-11-09 09:00:00,10923.0 -2015-11-09 10:00:00,11084.0 -2015-11-09 11:00:00,11010.0 -2015-11-09 12:00:00,11041.0 -2015-11-09 13:00:00,11005.0 -2015-11-09 14:00:00,10911.0 -2015-11-09 15:00:00,10936.0 -2015-11-09 16:00:00,10803.0 -2015-11-09 17:00:00,10711.0 -2015-11-09 18:00:00,11076.0 -2015-11-09 19:00:00,11742.0 -2015-11-09 20:00:00,11627.0 -2015-11-09 21:00:00,11428.0 -2015-11-09 22:00:00,11103.0 -2015-11-09 23:00:00,10600.0 -2015-11-10 00:00:00,9913.0 -2015-11-08 01:00:00,8846.0 -2015-11-08 02:00:00,8506.0 -2015-11-08 03:00:00,8263.0 -2015-11-08 04:00:00,8171.0 -2015-11-08 05:00:00,8107.0 -2015-11-08 06:00:00,8153.0 -2015-11-08 07:00:00,8319.0 -2015-11-08 08:00:00,8472.0 -2015-11-08 09:00:00,8511.0 -2015-11-08 10:00:00,8699.0 -2015-11-08 11:00:00,8836.0 -2015-11-08 12:00:00,8891.0 -2015-11-08 13:00:00,8930.0 -2015-11-08 14:00:00,8884.0 -2015-11-08 15:00:00,8841.0 -2015-11-08 16:00:00,8801.0 -2015-11-08 17:00:00,8867.0 -2015-11-08 18:00:00,9391.0 -2015-11-08 19:00:00,10201.0 -2015-11-08 20:00:00,10256.0 -2015-11-08 21:00:00,10199.0 -2015-11-08 22:00:00,9991.0 -2015-11-08 23:00:00,9662.0 -2015-11-09 00:00:00,9133.0 -2015-11-07 01:00:00,9128.0 -2015-11-07 02:00:00,8690.0 -2015-11-07 03:00:00,8389.0 -2015-11-07 04:00:00,8237.0 -2015-11-07 05:00:00,8171.0 -2015-11-07 06:00:00,8284.0 -2015-11-07 07:00:00,8564.0 -2015-11-07 08:00:00,8918.0 -2015-11-07 09:00:00,9117.0 -2015-11-07 10:00:00,9430.0 -2015-11-07 11:00:00,9560.0 -2015-11-07 12:00:00,9642.0 -2015-11-07 13:00:00,9592.0 -2015-11-07 14:00:00,9539.0 -2015-11-07 15:00:00,9341.0 -2015-11-07 16:00:00,9228.0 -2015-11-07 17:00:00,9200.0 -2015-11-07 18:00:00,9595.0 -2015-11-07 19:00:00,10292.0 -2015-11-07 20:00:00,10270.0 -2015-11-07 21:00:00,10185.0 -2015-11-07 22:00:00,10050.0 -2015-11-07 23:00:00,9775.0 -2015-11-08 00:00:00,9310.0 -2015-11-06 01:00:00,9268.0 -2015-11-06 02:00:00,8767.0 -2015-11-06 03:00:00,8488.0 -2015-11-06 04:00:00,8236.0 -2015-11-06 05:00:00,8101.0 -2015-11-06 06:00:00,8219.0 -2015-11-06 07:00:00,8770.0 -2015-11-06 08:00:00,9749.0 -2015-11-06 09:00:00,10249.0 -2015-11-06 10:00:00,10580.0 -2015-11-06 11:00:00,10790.0 -2015-11-06 12:00:00,10885.0 -2015-11-06 13:00:00,10855.0 -2015-11-06 14:00:00,10829.0 -2015-11-06 15:00:00,10843.0 -2015-11-06 16:00:00,10787.0 -2015-11-06 17:00:00,10753.0 -2015-11-06 18:00:00,11159.0 -2015-11-06 19:00:00,11534.0 -2015-11-06 20:00:00,11343.0 -2015-11-06 21:00:00,11082.0 -2015-11-06 22:00:00,10793.0 -2015-11-06 23:00:00,10410.0 -2015-11-07 00:00:00,9764.0 -2015-11-05 01:00:00,9184.0 -2015-11-05 02:00:00,8720.0 -2015-11-05 03:00:00,8427.0 -2015-11-05 04:00:00,8253.0 -2015-11-05 05:00:00,8228.0 -2015-11-05 06:00:00,8441.0 -2015-11-05 07:00:00,9114.0 -2015-11-05 08:00:00,10109.0 -2015-11-05 09:00:00,10727.0 -2015-11-05 10:00:00,11083.0 -2015-11-05 11:00:00,11256.0 -2015-11-05 12:00:00,11456.0 -2015-11-05 13:00:00,11619.0 -2015-11-05 14:00:00,11641.0 -2015-11-05 15:00:00,11605.0 -2015-11-05 16:00:00,11530.0 -2015-11-05 17:00:00,11447.0 -2015-11-05 18:00:00,11812.0 -2015-11-05 19:00:00,12200.0 -2015-11-05 20:00:00,11968.0 -2015-11-05 21:00:00,11661.0 -2015-11-05 22:00:00,11308.0 -2015-11-05 23:00:00,10729.0 -2015-11-06 00:00:00,9959.0 -2015-11-04 01:00:00,8981.0 -2015-11-04 02:00:00,8545.0 -2015-11-04 03:00:00,8274.0 -2015-11-04 04:00:00,8085.0 -2015-11-04 05:00:00,8016.0 -2015-11-04 06:00:00,8287.0 -2015-11-04 07:00:00,8941.0 -2015-11-04 08:00:00,9891.0 -2015-11-04 09:00:00,10475.0 -2015-11-04 10:00:00,10833.0 -2015-11-04 11:00:00,11031.0 -2015-11-04 12:00:00,11224.0 -2015-11-04 13:00:00,11389.0 -2015-11-04 14:00:00,11515.0 -2015-11-04 15:00:00,11633.0 -2015-11-04 16:00:00,11605.0 -2015-11-04 17:00:00,11495.0 -2015-11-04 18:00:00,11667.0 -2015-11-04 19:00:00,12156.0 -2015-11-04 20:00:00,11937.0 -2015-11-04 21:00:00,11641.0 -2015-11-04 22:00:00,11310.0 -2015-11-04 23:00:00,10715.0 -2015-11-05 00:00:00,9924.0 -2015-11-03 01:00:00,8908.0 -2015-11-03 02:00:00,8468.0 -2015-11-03 03:00:00,8187.0 -2015-11-03 04:00:00,8044.0 -2015-11-03 05:00:00,7996.0 -2015-11-03 06:00:00,8232.0 -2015-11-03 07:00:00,8901.0 -2015-11-03 08:00:00,9827.0 -2015-11-03 09:00:00,10344.0 -2015-11-03 10:00:00,10652.0 -2015-11-03 11:00:00,10933.0 -2015-11-03 12:00:00,11195.0 -2015-11-03 13:00:00,11341.0 -2015-11-03 14:00:00,11378.0 -2015-11-03 15:00:00,11378.0 -2015-11-03 16:00:00,11394.0 -2015-11-03 17:00:00,11269.0 -2015-11-03 18:00:00,11351.0 -2015-11-03 19:00:00,11945.0 -2015-11-03 20:00:00,11739.0 -2015-11-03 21:00:00,11449.0 -2015-11-03 22:00:00,11054.0 -2015-11-03 23:00:00,10454.0 -2015-11-04 00:00:00,9700.0 -2015-11-02 01:00:00,8354.0 -2015-11-02 02:00:00,8045.0 -2015-11-02 03:00:00,7915.0 -2015-11-02 04:00:00,7849.0 -2015-11-02 05:00:00,7844.0 -2015-11-02 06:00:00,8186.0 -2015-11-02 07:00:00,8955.0 -2015-11-02 08:00:00,9901.0 -2015-11-02 09:00:00,10442.0 -2015-11-02 10:00:00,10713.0 -2015-11-02 11:00:00,10888.0 -2015-11-02 12:00:00,11121.0 -2015-11-02 13:00:00,11209.0 -2015-11-02 14:00:00,11237.0 -2015-11-02 15:00:00,11300.0 -2015-11-02 16:00:00,11229.0 -2015-11-02 17:00:00,11130.0 -2015-11-02 18:00:00,11228.0 -2015-11-02 19:00:00,11813.0 -2015-11-02 20:00:00,11644.0 -2015-11-02 21:00:00,11382.0 -2015-11-02 22:00:00,10991.0 -2015-11-02 23:00:00,10386.0 -2015-11-03 00:00:00,9574.0 -2015-11-01 01:00:00,8663.0 -2015-11-01 02:00:00,8270.0 -2015-11-01 02:00:00,7923.0 -2015-11-01 03:00:00,7902.0 -2015-11-01 04:00:00,7761.0 -2015-11-01 05:00:00,7744.0 -2015-11-01 06:00:00,7792.0 -2015-11-01 07:00:00,7981.0 -2015-11-01 08:00:00,8149.0 -2015-11-01 09:00:00,8264.0 -2015-11-01 10:00:00,8509.0 -2015-11-01 11:00:00,8676.0 -2015-11-01 12:00:00,8762.0 -2015-11-01 13:00:00,8798.0 -2015-11-01 14:00:00,8851.0 -2015-11-01 15:00:00,8821.0 -2015-11-01 16:00:00,8812.0 -2015-11-01 17:00:00,8829.0 -2015-11-01 18:00:00,9181.0 -2015-11-01 19:00:00,10004.0 -2015-11-01 20:00:00,10086.0 -2015-11-01 21:00:00,9981.0 -2015-11-01 22:00:00,9686.0 -2015-11-01 23:00:00,9327.0 -2015-11-02 00:00:00,8782.0 -2015-10-31 01:00:00,9110.0 -2015-10-31 02:00:00,8587.0 -2015-10-31 03:00:00,8314.0 -2015-10-31 04:00:00,8085.0 -2015-10-31 05:00:00,8041.0 -2015-10-31 06:00:00,8068.0 -2015-10-31 07:00:00,8329.0 -2015-10-31 08:00:00,8772.0 -2015-10-31 09:00:00,9220.0 -2015-10-31 10:00:00,9558.0 -2015-10-31 11:00:00,9941.0 -2015-10-31 12:00:00,10156.0 -2015-10-31 13:00:00,10239.0 -2015-10-31 14:00:00,10147.0 -2015-10-31 15:00:00,10051.0 -2015-10-31 16:00:00,9899.0 -2015-10-31 17:00:00,9874.0 -2015-10-31 18:00:00,9876.0 -2015-10-31 19:00:00,10079.0 -2015-10-31 20:00:00,10241.0 -2015-10-31 21:00:00,10096.0 -2015-10-31 22:00:00,9876.0 -2015-10-31 23:00:00,9592.0 -2015-11-01 00:00:00,9158.0 -2015-10-30 01:00:00,9337.0 -2015-10-30 02:00:00,8855.0 -2015-10-30 03:00:00,8571.0 -2015-10-30 04:00:00,8452.0 -2015-10-30 05:00:00,8432.0 -2015-10-30 06:00:00,8624.0 -2015-10-30 07:00:00,9349.0 -2015-10-30 08:00:00,10544.0 -2015-10-30 09:00:00,11163.0 -2015-10-30 10:00:00,11137.0 -2015-10-30 11:00:00,11084.0 -2015-10-30 12:00:00,11064.0 -2015-10-30 13:00:00,10977.0 -2015-10-30 14:00:00,10868.0 -2015-10-30 15:00:00,10822.0 -2015-10-30 16:00:00,10660.0 -2015-10-30 17:00:00,10494.0 -2015-10-30 18:00:00,10393.0 -2015-10-30 19:00:00,10590.0 -2015-10-30 20:00:00,11106.0 -2015-10-30 21:00:00,11002.0 -2015-10-30 22:00:00,10737.0 -2015-10-30 23:00:00,10384.0 -2015-10-31 00:00:00,9726.0 -2015-10-29 01:00:00,9406.0 -2015-10-29 02:00:00,8982.0 -2015-10-29 03:00:00,8705.0 -2015-10-29 04:00:00,8539.0 -2015-10-29 05:00:00,8551.0 -2015-10-29 06:00:00,8834.0 -2015-10-29 07:00:00,9561.0 -2015-10-29 08:00:00,10751.0 -2015-10-29 09:00:00,11492.0 -2015-10-29 10:00:00,11581.0 -2015-10-29 11:00:00,11652.0 -2015-10-29 12:00:00,11660.0 -2015-10-29 13:00:00,11570.0 -2015-10-29 14:00:00,11452.0 -2015-10-29 15:00:00,11464.0 -2015-10-29 16:00:00,11315.0 -2015-10-29 17:00:00,11246.0 -2015-10-29 18:00:00,11294.0 -2015-10-29 19:00:00,11563.0 -2015-10-29 20:00:00,11740.0 -2015-10-29 21:00:00,11591.0 -2015-10-29 22:00:00,11349.0 -2015-10-29 23:00:00,10811.0 -2015-10-30 00:00:00,10048.0 -2015-10-28 01:00:00,9181.0 -2015-10-28 02:00:00,8701.0 -2015-10-28 03:00:00,8375.0 -2015-10-28 04:00:00,8200.0 -2015-10-28 05:00:00,8131.0 -2015-10-28 06:00:00,8330.0 -2015-10-28 07:00:00,8964.0 -2015-10-28 08:00:00,10139.0 -2015-10-28 09:00:00,10918.0 -2015-10-28 10:00:00,11079.0 -2015-10-28 11:00:00,11158.0 -2015-10-28 12:00:00,11299.0 -2015-10-28 13:00:00,11298.0 -2015-10-28 14:00:00,11249.0 -2015-10-28 15:00:00,11251.0 -2015-10-28 16:00:00,11182.0 -2015-10-28 17:00:00,11115.0 -2015-10-28 18:00:00,11189.0 -2015-10-28 19:00:00,11481.0 -2015-10-28 20:00:00,11662.0 -2015-10-28 21:00:00,11548.0 -2015-10-28 22:00:00,11303.0 -2015-10-28 23:00:00,10843.0 -2015-10-29 00:00:00,10086.0 -2015-10-27 01:00:00,8996.0 -2015-10-27 02:00:00,8552.0 -2015-10-27 03:00:00,8271.0 -2015-10-27 04:00:00,8074.0 -2015-10-27 05:00:00,8015.0 -2015-10-27 06:00:00,8221.0 -2015-10-27 07:00:00,8922.0 -2015-10-27 08:00:00,10119.0 -2015-10-27 09:00:00,10740.0 -2015-10-27 10:00:00,10816.0 -2015-10-27 11:00:00,10937.0 -2015-10-27 12:00:00,11063.0 -2015-10-27 13:00:00,11118.0 -2015-10-27 14:00:00,11098.0 -2015-10-27 15:00:00,11062.0 -2015-10-27 16:00:00,10985.0 -2015-10-27 17:00:00,10915.0 -2015-10-27 18:00:00,11019.0 -2015-10-27 19:00:00,11346.0 -2015-10-27 20:00:00,11587.0 -2015-10-27 21:00:00,11422.0 -2015-10-27 22:00:00,11178.0 -2015-10-27 23:00:00,10660.0 -2015-10-28 00:00:00,9893.0 -2015-10-26 01:00:00,8433.0 -2015-10-26 02:00:00,8116.0 -2015-10-26 03:00:00,7927.0 -2015-10-26 04:00:00,7859.0 -2015-10-26 05:00:00,7859.0 -2015-10-26 06:00:00,8141.0 -2015-10-26 07:00:00,8845.0 -2015-10-26 08:00:00,10113.0 -2015-10-26 09:00:00,10737.0 -2015-10-26 10:00:00,10804.0 -2015-10-26 11:00:00,10792.0 -2015-10-26 12:00:00,10943.0 -2015-10-26 13:00:00,10948.0 -2015-10-26 14:00:00,10949.0 -2015-10-26 15:00:00,10960.0 -2015-10-26 16:00:00,10860.0 -2015-10-26 17:00:00,10709.0 -2015-10-26 18:00:00,10628.0 -2015-10-26 19:00:00,10774.0 -2015-10-26 20:00:00,11304.0 -2015-10-26 21:00:00,11217.0 -2015-10-26 22:00:00,10981.0 -2015-10-26 23:00:00,10476.0 -2015-10-27 00:00:00,9758.0 -2015-10-25 01:00:00,8471.0 -2015-10-25 02:00:00,8055.0 -2015-10-25 03:00:00,7796.0 -2015-10-25 04:00:00,7597.0 -2015-10-25 05:00:00,7526.0 -2015-10-25 06:00:00,7504.0 -2015-10-25 07:00:00,7669.0 -2015-10-25 08:00:00,7968.0 -2015-10-25 09:00:00,8080.0 -2015-10-25 10:00:00,8286.0 -2015-10-25 11:00:00,8507.0 -2015-10-25 12:00:00,8647.0 -2015-10-25 13:00:00,8736.0 -2015-10-25 14:00:00,8748.0 -2015-10-25 15:00:00,8747.0 -2015-10-25 16:00:00,8712.0 -2015-10-25 17:00:00,8726.0 -2015-10-25 18:00:00,8769.0 -2015-10-25 19:00:00,8995.0 -2015-10-25 20:00:00,9722.0 -2015-10-25 21:00:00,9868.0 -2015-10-25 22:00:00,9684.0 -2015-10-25 23:00:00,9356.0 -2015-10-26 00:00:00,8918.0 -2015-10-24 01:00:00,9167.0 -2015-10-24 02:00:00,8643.0 -2015-10-24 03:00:00,8326.0 -2015-10-24 04:00:00,8087.0 -2015-10-24 05:00:00,8047.0 -2015-10-24 06:00:00,8077.0 -2015-10-24 07:00:00,8364.0 -2015-10-24 08:00:00,8817.0 -2015-10-24 09:00:00,9200.0 -2015-10-24 10:00:00,9631.0 -2015-10-24 11:00:00,10028.0 -2015-10-24 12:00:00,10191.0 -2015-10-24 13:00:00,10058.0 -2015-10-24 14:00:00,9895.0 -2015-10-24 15:00:00,9721.0 -2015-10-24 16:00:00,9564.0 -2015-10-24 17:00:00,9477.0 -2015-10-24 18:00:00,9623.0 -2015-10-24 19:00:00,9802.0 -2015-10-24 20:00:00,10109.0 -2015-10-24 21:00:00,9970.0 -2015-10-24 22:00:00,9808.0 -2015-10-24 23:00:00,9449.0 -2015-10-25 00:00:00,8999.0 -2015-10-23 01:00:00,8989.0 -2015-10-23 02:00:00,8509.0 -2015-10-23 03:00:00,8186.0 -2015-10-23 04:00:00,8041.0 -2015-10-23 05:00:00,7982.0 -2015-10-23 06:00:00,8159.0 -2015-10-23 07:00:00,8802.0 -2015-10-23 08:00:00,9910.0 -2015-10-23 09:00:00,10544.0 -2015-10-23 10:00:00,10715.0 -2015-10-23 11:00:00,10864.0 -2015-10-23 12:00:00,10921.0 -2015-10-23 13:00:00,10960.0 -2015-10-23 14:00:00,10953.0 -2015-10-23 15:00:00,10970.0 -2015-10-23 16:00:00,10880.0 -2015-10-23 17:00:00,10764.0 -2015-10-23 18:00:00,10788.0 -2015-10-23 19:00:00,11041.0 -2015-10-23 20:00:00,11260.0 -2015-10-23 21:00:00,11042.0 -2015-10-23 22:00:00,10773.0 -2015-10-23 23:00:00,10440.0 -2015-10-24 00:00:00,9806.0 -2015-10-22 01:00:00,9204.0 -2015-10-22 02:00:00,8722.0 -2015-10-22 03:00:00,8324.0 -2015-10-22 04:00:00,8085.0 -2015-10-22 05:00:00,8019.0 -2015-10-22 06:00:00,8184.0 -2015-10-22 07:00:00,8774.0 -2015-10-22 08:00:00,9866.0 -2015-10-22 09:00:00,10539.0 -2015-10-22 10:00:00,10699.0 -2015-10-22 11:00:00,10822.0 -2015-10-22 12:00:00,10927.0 -2015-10-22 13:00:00,10966.0 -2015-10-22 14:00:00,11017.0 -2015-10-22 15:00:00,11134.0 -2015-10-22 16:00:00,11074.0 -2015-10-22 17:00:00,10960.0 -2015-10-22 18:00:00,10806.0 -2015-10-22 19:00:00,10771.0 -2015-10-22 20:00:00,11234.0 -2015-10-22 21:00:00,11220.0 -2015-10-22 22:00:00,10905.0 -2015-10-22 23:00:00,10406.0 -2015-10-23 00:00:00,9682.0 -2015-10-21 01:00:00,9061.0 -2015-10-21 02:00:00,8534.0 -2015-10-21 03:00:00,8216.0 -2015-10-21 04:00:00,7993.0 -2015-10-21 05:00:00,7959.0 -2015-10-21 06:00:00,8134.0 -2015-10-21 07:00:00,8773.0 -2015-10-21 08:00:00,9915.0 -2015-10-21 09:00:00,10507.0 -2015-10-21 10:00:00,10748.0 -2015-10-21 11:00:00,10956.0 -2015-10-21 12:00:00,11191.0 -2015-10-21 13:00:00,11362.0 -2015-10-21 14:00:00,11504.0 -2015-10-21 15:00:00,11583.0 -2015-10-21 16:00:00,11574.0 -2015-10-21 17:00:00,11471.0 -2015-10-21 18:00:00,11393.0 -2015-10-21 19:00:00,11317.0 -2015-10-21 20:00:00,11764.0 -2015-10-21 21:00:00,11792.0 -2015-10-21 22:00:00,11413.0 -2015-10-21 23:00:00,10864.0 -2015-10-22 00:00:00,10038.0 -2015-10-20 01:00:00,9000.0 -2015-10-20 02:00:00,8522.0 -2015-10-20 03:00:00,8183.0 -2015-10-20 04:00:00,8001.0 -2015-10-20 05:00:00,7946.0 -2015-10-20 06:00:00,8148.0 -2015-10-20 07:00:00,8791.0 -2015-10-20 08:00:00,9928.0 -2015-10-20 09:00:00,10511.0 -2015-10-20 10:00:00,10675.0 -2015-10-20 11:00:00,10824.0 -2015-10-20 12:00:00,11037.0 -2015-10-20 13:00:00,11122.0 -2015-10-20 14:00:00,11134.0 -2015-10-20 15:00:00,11238.0 -2015-10-20 16:00:00,11226.0 -2015-10-20 17:00:00,11136.0 -2015-10-20 18:00:00,11066.0 -2015-10-20 19:00:00,11054.0 -2015-10-20 20:00:00,11525.0 -2015-10-20 21:00:00,11432.0 -2015-10-20 22:00:00,11123.0 -2015-10-20 23:00:00,10597.0 -2015-10-21 00:00:00,9818.0 -2015-10-19 01:00:00,8490.0 -2015-10-19 02:00:00,8141.0 -2015-10-19 03:00:00,7962.0 -2015-10-19 04:00:00,7878.0 -2015-10-19 05:00:00,7927.0 -2015-10-19 06:00:00,8175.0 -2015-10-19 07:00:00,8936.0 -2015-10-19 08:00:00,10113.0 -2015-10-19 09:00:00,10699.0 -2015-10-19 10:00:00,10829.0 -2015-10-19 11:00:00,10913.0 -2015-10-19 12:00:00,11051.0 -2015-10-19 13:00:00,11118.0 -2015-10-19 14:00:00,11168.0 -2015-10-19 15:00:00,11212.0 -2015-10-19 16:00:00,11164.0 -2015-10-19 17:00:00,11033.0 -2015-10-19 18:00:00,10988.0 -2015-10-19 19:00:00,10969.0 -2015-10-19 20:00:00,11414.0 -2015-10-19 21:00:00,11444.0 -2015-10-19 22:00:00,11118.0 -2015-10-19 23:00:00,10553.0 -2015-10-20 00:00:00,9740.0 -2015-10-18 01:00:00,8752.0 -2015-10-18 02:00:00,8396.0 -2015-10-18 03:00:00,8127.0 -2015-10-18 04:00:00,8031.0 -2015-10-18 05:00:00,7955.0 -2015-10-18 06:00:00,8000.0 -2015-10-18 07:00:00,8100.0 -2015-10-18 08:00:00,8391.0 -2015-10-18 09:00:00,8448.0 -2015-10-18 10:00:00,8670.0 -2015-10-18 11:00:00,8776.0 -2015-10-18 12:00:00,8897.0 -2015-10-18 13:00:00,8899.0 -2015-10-18 14:00:00,8920.0 -2015-10-18 15:00:00,8866.0 -2015-10-18 16:00:00,8780.0 -2015-10-18 17:00:00,8791.0 -2015-10-18 18:00:00,8845.0 -2015-10-18 19:00:00,9034.0 -2015-10-18 20:00:00,9718.0 -2015-10-18 21:00:00,9960.0 -2015-10-18 22:00:00,9779.0 -2015-10-18 23:00:00,9500.0 -2015-10-19 00:00:00,8969.0 -2015-10-17 01:00:00,9128.0 -2015-10-17 02:00:00,8639.0 -2015-10-17 03:00:00,8404.0 -2015-10-17 04:00:00,8226.0 -2015-10-17 05:00:00,8159.0 -2015-10-17 06:00:00,8246.0 -2015-10-17 07:00:00,8506.0 -2015-10-17 08:00:00,9046.0 -2015-10-17 09:00:00,9298.0 -2015-10-17 10:00:00,9586.0 -2015-10-17 11:00:00,9716.0 -2015-10-17 12:00:00,9755.0 -2015-10-17 13:00:00,9663.0 -2015-10-17 14:00:00,9528.0 -2015-10-17 15:00:00,9322.0 -2015-10-17 16:00:00,9197.0 -2015-10-17 17:00:00,9106.0 -2015-10-17 18:00:00,9108.0 -2015-10-17 19:00:00,9217.0 -2015-10-17 20:00:00,9792.0 -2015-10-17 21:00:00,10001.0 -2015-10-17 22:00:00,9882.0 -2015-10-17 23:00:00,9666.0 -2015-10-18 00:00:00,9222.0 -2015-10-16 01:00:00,8950.0 -2015-10-16 02:00:00,8499.0 -2015-10-16 03:00:00,8229.0 -2015-10-16 04:00:00,8070.0 -2015-10-16 05:00:00,8044.0 -2015-10-16 06:00:00,8285.0 -2015-10-16 07:00:00,8820.0 -2015-10-16 08:00:00,10053.0 -2015-10-16 09:00:00,10543.0 -2015-10-16 10:00:00,10728.0 -2015-10-16 11:00:00,10783.0 -2015-10-16 12:00:00,10875.0 -2015-10-16 13:00:00,10825.0 -2015-10-16 14:00:00,10763.0 -2015-10-16 15:00:00,10776.0 -2015-10-16 16:00:00,10624.0 -2015-10-16 17:00:00,10490.0 -2015-10-16 18:00:00,10384.0 -2015-10-16 19:00:00,10350.0 -2015-10-16 20:00:00,10833.0 -2015-10-16 21:00:00,10885.0 -2015-10-16 22:00:00,10660.0 -2015-10-16 23:00:00,10337.0 -2015-10-17 00:00:00,9754.0 -2015-10-15 01:00:00,8887.0 -2015-10-15 02:00:00,8471.0 -2015-10-15 03:00:00,8186.0 -2015-10-15 04:00:00,8007.0 -2015-10-15 05:00:00,7968.0 -2015-10-15 06:00:00,8151.0 -2015-10-15 07:00:00,8804.0 -2015-10-15 08:00:00,9973.0 -2015-10-15 09:00:00,10517.0 -2015-10-15 10:00:00,10763.0 -2015-10-15 11:00:00,10861.0 -2015-10-15 12:00:00,11025.0 -2015-10-15 13:00:00,11081.0 -2015-10-15 14:00:00,11049.0 -2015-10-15 15:00:00,11101.0 -2015-10-15 16:00:00,11089.0 -2015-10-15 17:00:00,11007.0 -2015-10-15 18:00:00,10896.0 -2015-10-15 19:00:00,10787.0 -2015-10-15 20:00:00,11142.0 -2015-10-15 21:00:00,11241.0 -2015-10-15 22:00:00,10938.0 -2015-10-15 23:00:00,10409.0 -2015-10-16 00:00:00,9665.0 -2015-10-14 01:00:00,8824.0 -2015-10-14 02:00:00,8360.0 -2015-10-14 03:00:00,8065.0 -2015-10-14 04:00:00,7912.0 -2015-10-14 05:00:00,7866.0 -2015-10-14 06:00:00,8062.0 -2015-10-14 07:00:00,8709.0 -2015-10-14 08:00:00,9881.0 -2015-10-14 09:00:00,10432.0 -2015-10-14 10:00:00,10627.0 -2015-10-14 11:00:00,10656.0 -2015-10-14 12:00:00,10828.0 -2015-10-14 13:00:00,10870.0 -2015-10-14 14:00:00,10867.0 -2015-10-14 15:00:00,10992.0 -2015-10-14 16:00:00,10925.0 -2015-10-14 17:00:00,10862.0 -2015-10-14 18:00:00,10774.0 -2015-10-14 19:00:00,10677.0 -2015-10-14 20:00:00,11048.0 -2015-10-14 21:00:00,11132.0 -2015-10-14 22:00:00,10869.0 -2015-10-14 23:00:00,10321.0 -2015-10-15 00:00:00,9604.0 -2015-10-13 01:00:00,8895.0 -2015-10-13 02:00:00,8373.0 -2015-10-13 03:00:00,8061.0 -2015-10-13 04:00:00,7861.0 -2015-10-13 05:00:00,7769.0 -2015-10-13 06:00:00,7963.0 -2015-10-13 07:00:00,8644.0 -2015-10-13 08:00:00,9799.0 -2015-10-13 09:00:00,10267.0 -2015-10-13 10:00:00,10532.0 -2015-10-13 11:00:00,10692.0 -2015-10-13 12:00:00,10831.0 -2015-10-13 13:00:00,10870.0 -2015-10-13 14:00:00,10858.0 -2015-10-13 15:00:00,10873.0 -2015-10-13 16:00:00,10836.0 -2015-10-13 17:00:00,10780.0 -2015-10-13 18:00:00,10710.0 -2015-10-13 19:00:00,10663.0 -2015-10-13 20:00:00,10993.0 -2015-10-13 21:00:00,11154.0 -2015-10-13 22:00:00,10820.0 -2015-10-13 23:00:00,10296.0 -2015-10-14 00:00:00,9541.0 -2015-10-12 01:00:00,8606.0 -2015-10-12 02:00:00,8172.0 -2015-10-12 03:00:00,7938.0 -2015-10-12 04:00:00,7768.0 -2015-10-12 05:00:00,7726.0 -2015-10-12 06:00:00,7962.0 -2015-10-12 07:00:00,8570.0 -2015-10-12 08:00:00,9569.0 -2015-10-12 09:00:00,10077.0 -2015-10-12 10:00:00,10631.0 -2015-10-12 11:00:00,11020.0 -2015-10-12 12:00:00,11294.0 -2015-10-12 13:00:00,11332.0 -2015-10-12 14:00:00,11299.0 -2015-10-12 15:00:00,11319.0 -2015-10-12 16:00:00,11402.0 -2015-10-12 17:00:00,11409.0 -2015-10-12 18:00:00,11293.0 -2015-10-12 19:00:00,11150.0 -2015-10-12 20:00:00,11383.0 -2015-10-12 21:00:00,11371.0 -2015-10-12 22:00:00,11003.0 -2015-10-12 23:00:00,10471.0 -2015-10-13 00:00:00,9678.0 -2015-10-11 01:00:00,8446.0 -2015-10-11 02:00:00,8029.0 -2015-10-11 03:00:00,7758.0 -2015-10-11 04:00:00,7556.0 -2015-10-11 05:00:00,7479.0 -2015-10-11 06:00:00,7470.0 -2015-10-11 07:00:00,7600.0 -2015-10-11 08:00:00,7728.0 -2015-10-11 09:00:00,7789.0 -2015-10-11 10:00:00,8135.0 -2015-10-11 11:00:00,8436.0 -2015-10-11 12:00:00,8710.0 -2015-10-11 13:00:00,8923.0 -2015-10-11 14:00:00,9127.0 -2015-10-11 15:00:00,9309.0 -2015-10-11 16:00:00,9436.0 -2015-10-11 17:00:00,9548.0 -2015-10-11 18:00:00,9674.0 -2015-10-11 19:00:00,9716.0 -2015-10-11 20:00:00,10074.0 -2015-10-11 21:00:00,10351.0 -2015-10-11 22:00:00,10072.0 -2015-10-11 23:00:00,9733.0 -2015-10-12 00:00:00,9154.0 -2015-10-10 01:00:00,8872.0 -2015-10-10 02:00:00,8370.0 -2015-10-10 03:00:00,8064.0 -2015-10-10 04:00:00,7891.0 -2015-10-10 05:00:00,7782.0 -2015-10-10 06:00:00,7861.0 -2015-10-10 07:00:00,8085.0 -2015-10-10 08:00:00,8563.0 -2015-10-10 09:00:00,8759.0 -2015-10-10 10:00:00,9124.0 -2015-10-10 11:00:00,9342.0 -2015-10-10 12:00:00,9516.0 -2015-10-10 13:00:00,9527.0 -2015-10-10 14:00:00,9485.0 -2015-10-10 15:00:00,9392.0 -2015-10-10 16:00:00,9344.0 -2015-10-10 17:00:00,9314.0 -2015-10-10 18:00:00,9286.0 -2015-10-10 19:00:00,9284.0 -2015-10-10 20:00:00,9704.0 -2015-10-10 21:00:00,9857.0 -2015-10-10 22:00:00,9756.0 -2015-10-10 23:00:00,9420.0 -2015-10-11 00:00:00,8976.0 -2015-10-09 01:00:00,9590.0 -2015-10-09 02:00:00,9013.0 -2015-10-09 03:00:00,8630.0 -2015-10-09 04:00:00,8351.0 -2015-10-09 05:00:00,8202.0 -2015-10-09 06:00:00,8311.0 -2015-10-09 07:00:00,8903.0 -2015-10-09 08:00:00,9935.0 -2015-10-09 09:00:00,10467.0 -2015-10-09 10:00:00,10712.0 -2015-10-09 11:00:00,10906.0 -2015-10-09 12:00:00,11045.0 -2015-10-09 13:00:00,11023.0 -2015-10-09 14:00:00,10994.0 -2015-10-09 15:00:00,11024.0 -2015-10-09 16:00:00,10897.0 -2015-10-09 17:00:00,10667.0 -2015-10-09 18:00:00,10518.0 -2015-10-09 19:00:00,10445.0 -2015-10-09 20:00:00,10735.0 -2015-10-09 21:00:00,10857.0 -2015-10-09 22:00:00,10583.0 -2015-10-09 23:00:00,10168.0 -2015-10-10 00:00:00,9531.0 -2015-10-08 01:00:00,9157.0 -2015-10-08 02:00:00,8611.0 -2015-10-08 03:00:00,8287.0 -2015-10-08 04:00:00,8096.0 -2015-10-08 05:00:00,7997.0 -2015-10-08 06:00:00,8172.0 -2015-10-08 07:00:00,8760.0 -2015-10-08 08:00:00,9859.0 -2015-10-08 09:00:00,10409.0 -2015-10-08 10:00:00,10796.0 -2015-10-08 11:00:00,11094.0 -2015-10-08 12:00:00,11474.0 -2015-10-08 13:00:00,11757.0 -2015-10-08 14:00:00,11952.0 -2015-10-08 15:00:00,12131.0 -2015-10-08 16:00:00,12161.0 -2015-10-08 17:00:00,11995.0 -2015-10-08 18:00:00,11873.0 -2015-10-08 19:00:00,11753.0 -2015-10-08 20:00:00,12019.0 -2015-10-08 21:00:00,12168.0 -2015-10-08 22:00:00,11829.0 -2015-10-08 23:00:00,11244.0 -2015-10-09 00:00:00,10375.0 -2015-10-07 01:00:00,9104.0 -2015-10-07 02:00:00,8606.0 -2015-10-07 03:00:00,8293.0 -2015-10-07 04:00:00,8099.0 -2015-10-07 05:00:00,8049.0 -2015-10-07 06:00:00,8258.0 -2015-10-07 07:00:00,8893.0 -2015-10-07 08:00:00,10020.0 -2015-10-07 09:00:00,10519.0 -2015-10-07 10:00:00,10875.0 -2015-10-07 11:00:00,11135.0 -2015-10-07 12:00:00,11445.0 -2015-10-07 13:00:00,11643.0 -2015-10-07 14:00:00,11764.0 -2015-10-07 15:00:00,11956.0 -2015-10-07 16:00:00,11906.0 -2015-10-07 17:00:00,11735.0 -2015-10-07 18:00:00,11530.0 -2015-10-07 19:00:00,11244.0 -2015-10-07 20:00:00,11417.0 -2015-10-07 21:00:00,11544.0 -2015-10-07 22:00:00,11197.0 -2015-10-07 23:00:00,10667.0 -2015-10-08 00:00:00,9913.0 -2015-10-06 01:00:00,9025.0 -2015-10-06 02:00:00,8564.0 -2015-10-06 03:00:00,8262.0 -2015-10-06 04:00:00,8079.0 -2015-10-06 05:00:00,7997.0 -2015-10-06 06:00:00,8185.0 -2015-10-06 07:00:00,8871.0 -2015-10-06 08:00:00,9977.0 -2015-10-06 09:00:00,10581.0 -2015-10-06 10:00:00,10812.0 -2015-10-06 11:00:00,10988.0 -2015-10-06 12:00:00,11164.0 -2015-10-06 13:00:00,11212.0 -2015-10-06 14:00:00,11235.0 -2015-10-06 15:00:00,11395.0 -2015-10-06 16:00:00,11358.0 -2015-10-06 17:00:00,11334.0 -2015-10-06 18:00:00,11255.0 -2015-10-06 19:00:00,11094.0 -2015-10-06 20:00:00,11235.0 -2015-10-06 21:00:00,11471.0 -2015-10-06 22:00:00,11158.0 -2015-10-06 23:00:00,10605.0 -2015-10-07 00:00:00,9827.0 -2015-10-05 01:00:00,8350.0 -2015-10-05 02:00:00,7968.0 -2015-10-05 03:00:00,7796.0 -2015-10-05 04:00:00,7656.0 -2015-10-05 05:00:00,7693.0 -2015-10-05 06:00:00,7900.0 -2015-10-05 07:00:00,8623.0 -2015-10-05 08:00:00,9766.0 -2015-10-05 09:00:00,10477.0 -2015-10-05 10:00:00,10735.0 -2015-10-05 11:00:00,10904.0 -2015-10-05 12:00:00,11063.0 -2015-10-05 13:00:00,11109.0 -2015-10-05 14:00:00,11113.0 -2015-10-05 15:00:00,11127.0 -2015-10-05 16:00:00,11025.0 -2015-10-05 17:00:00,10917.0 -2015-10-05 18:00:00,10861.0 -2015-10-05 19:00:00,10946.0 -2015-10-05 20:00:00,11259.0 -2015-10-05 21:00:00,11325.0 -2015-10-05 22:00:00,11022.0 -2015-10-05 23:00:00,10474.0 -2015-10-06 00:00:00,9715.0 -2015-10-04 01:00:00,8505.0 -2015-10-04 02:00:00,8155.0 -2015-10-04 03:00:00,7792.0 -2015-10-04 04:00:00,7619.0 -2015-10-04 05:00:00,7505.0 -2015-10-04 06:00:00,7522.0 -2015-10-04 07:00:00,7592.0 -2015-10-04 08:00:00,7865.0 -2015-10-04 09:00:00,7982.0 -2015-10-04 10:00:00,8345.0 -2015-10-04 11:00:00,8657.0 -2015-10-04 12:00:00,8846.0 -2015-10-04 13:00:00,8948.0 -2015-10-04 14:00:00,8970.0 -2015-10-04 15:00:00,8957.0 -2015-10-04 16:00:00,8945.0 -2015-10-04 17:00:00,8951.0 -2015-10-04 18:00:00,9071.0 -2015-10-04 19:00:00,9237.0 -2015-10-04 20:00:00,9696.0 -2015-10-04 21:00:00,9949.0 -2015-10-04 22:00:00,9768.0 -2015-10-04 23:00:00,9408.0 -2015-10-05 00:00:00,8860.0 -2015-10-03 01:00:00,8948.0 -2015-10-03 02:00:00,8490.0 -2015-10-03 03:00:00,8149.0 -2015-10-03 04:00:00,7954.0 -2015-10-03 05:00:00,7844.0 -2015-10-03 06:00:00,7864.0 -2015-10-03 07:00:00,8082.0 -2015-10-03 08:00:00,8518.0 -2015-10-03 09:00:00,8707.0 -2015-10-03 10:00:00,9139.0 -2015-10-03 11:00:00,9382.0 -2015-10-03 12:00:00,9537.0 -2015-10-03 13:00:00,9476.0 -2015-10-03 14:00:00,9446.0 -2015-10-03 15:00:00,9327.0 -2015-10-03 16:00:00,9261.0 -2015-10-03 17:00:00,9232.0 -2015-10-03 18:00:00,9284.0 -2015-10-03 19:00:00,9476.0 -2015-10-03 20:00:00,9855.0 -2015-10-03 21:00:00,10028.0 -2015-10-03 22:00:00,9873.0 -2015-10-03 23:00:00,9554.0 -2015-10-04 00:00:00,9052.0 -2015-10-02 01:00:00,8980.0 -2015-10-02 02:00:00,8496.0 -2015-10-02 03:00:00,8210.0 -2015-10-02 04:00:00,8002.0 -2015-10-02 05:00:00,7947.0 -2015-10-02 06:00:00,8140.0 -2015-10-02 07:00:00,8739.0 -2015-10-02 08:00:00,9832.0 -2015-10-02 09:00:00,10324.0 -2015-10-02 10:00:00,10543.0 -2015-10-02 11:00:00,10733.0 -2015-10-02 12:00:00,10886.0 -2015-10-02 13:00:00,10909.0 -2015-10-02 14:00:00,10890.0 -2015-10-02 15:00:00,10911.0 -2015-10-02 16:00:00,10811.0 -2015-10-02 17:00:00,10680.0 -2015-10-02 18:00:00,10555.0 -2015-10-02 19:00:00,10413.0 -2015-10-02 20:00:00,10564.0 -2015-10-02 21:00:00,10916.0 -2015-10-02 22:00:00,10645.0 -2015-10-02 23:00:00,10242.0 -2015-10-03 00:00:00,9562.0 -2015-10-01 01:00:00,8931.0 -2015-10-01 02:00:00,8425.0 -2015-10-01 03:00:00,8119.0 -2015-10-01 04:00:00,7926.0 -2015-10-01 05:00:00,7879.0 -2015-10-01 06:00:00,8053.0 -2015-10-01 07:00:00,8705.0 -2015-10-01 08:00:00,9779.0 -2015-10-01 09:00:00,10256.0 -2015-10-01 10:00:00,10587.0 -2015-10-01 11:00:00,10761.0 -2015-10-01 12:00:00,10894.0 -2015-10-01 13:00:00,10927.0 -2015-10-01 14:00:00,10891.0 -2015-10-01 15:00:00,10981.0 -2015-10-01 16:00:00,10883.0 -2015-10-01 17:00:00,10768.0 -2015-10-01 18:00:00,10677.0 -2015-10-01 19:00:00,10586.0 -2015-10-01 20:00:00,10728.0 -2015-10-01 21:00:00,11122.0 -2015-10-01 22:00:00,10951.0 -2015-10-01 23:00:00,10424.0 -2015-10-02 00:00:00,9681.0 -2015-09-30 01:00:00,9017.0 -2015-09-30 02:00:00,8512.0 -2015-09-30 03:00:00,8173.0 -2015-09-30 04:00:00,8003.0 -2015-09-30 05:00:00,7941.0 -2015-09-30 06:00:00,8121.0 -2015-09-30 07:00:00,8740.0 -2015-09-30 08:00:00,9824.0 -2015-09-30 09:00:00,10304.0 -2015-09-30 10:00:00,10570.0 -2015-09-30 11:00:00,10753.0 -2015-09-30 12:00:00,10956.0 -2015-09-30 13:00:00,11032.0 -2015-09-30 14:00:00,11063.0 -2015-09-30 15:00:00,11121.0 -2015-09-30 16:00:00,11047.0 -2015-09-30 17:00:00,10981.0 -2015-09-30 18:00:00,10849.0 -2015-09-30 19:00:00,10694.0 -2015-09-30 20:00:00,10730.0 -2015-09-30 21:00:00,11153.0 -2015-09-30 22:00:00,10917.0 -2015-09-30 23:00:00,10398.0 -2015-10-01 00:00:00,9674.0 -2015-09-29 01:00:00,10656.0 -2015-09-29 02:00:00,9920.0 -2015-09-29 03:00:00,9418.0 -2015-09-29 04:00:00,9113.0 -2015-09-29 05:00:00,8961.0 -2015-09-29 06:00:00,9107.0 -2015-09-29 07:00:00,9844.0 -2015-09-29 08:00:00,11079.0 -2015-09-29 09:00:00,11647.0 -2015-09-29 10:00:00,11814.0 -2015-09-29 11:00:00,11951.0 -2015-09-29 12:00:00,11992.0 -2015-09-29 13:00:00,11929.0 -2015-09-29 14:00:00,11828.0 -2015-09-29 15:00:00,11813.0 -2015-09-29 16:00:00,11679.0 -2015-09-29 17:00:00,11457.0 -2015-09-29 18:00:00,11316.0 -2015-09-29 19:00:00,11134.0 -2015-09-29 20:00:00,11111.0 -2015-09-29 21:00:00,11408.0 -2015-09-29 22:00:00,11134.0 -2015-09-29 23:00:00,10576.0 -2015-09-30 00:00:00,9784.0 -2015-09-28 01:00:00,9461.0 -2015-09-28 02:00:00,8973.0 -2015-09-28 03:00:00,8674.0 -2015-09-28 04:00:00,8508.0 -2015-09-28 05:00:00,8469.0 -2015-09-28 06:00:00,8711.0 -2015-09-28 07:00:00,9524.0 -2015-09-28 08:00:00,10767.0 -2015-09-28 09:00:00,11496.0 -2015-09-28 10:00:00,11932.0 -2015-09-28 11:00:00,12314.0 -2015-09-28 12:00:00,12705.0 -2015-09-28 13:00:00,13044.0 -2015-09-28 14:00:00,13360.0 -2015-09-28 15:00:00,13722.0 -2015-09-28 16:00:00,13881.0 -2015-09-28 17:00:00,13889.0 -2015-09-28 18:00:00,13794.0 -2015-09-28 19:00:00,13570.0 -2015-09-28 20:00:00,13371.0 -2015-09-28 21:00:00,13835.0 -2015-09-28 22:00:00,13461.0 -2015-09-28 23:00:00,12762.0 -2015-09-29 00:00:00,11705.0 -2015-09-27 01:00:00,9413.0 -2015-09-27 02:00:00,8905.0 -2015-09-27 03:00:00,8551.0 -2015-09-27 04:00:00,8260.0 -2015-09-27 05:00:00,8107.0 -2015-09-27 06:00:00,8048.0 -2015-09-27 07:00:00,8177.0 -2015-09-27 08:00:00,8330.0 -2015-09-27 09:00:00,8545.0 -2015-09-27 10:00:00,8908.0 -2015-09-27 11:00:00,9280.0 -2015-09-27 12:00:00,9616.0 -2015-09-27 13:00:00,9887.0 -2015-09-27 14:00:00,10103.0 -2015-09-27 15:00:00,10276.0 -2015-09-27 16:00:00,10483.0 -2015-09-27 17:00:00,10526.0 -2015-09-27 18:00:00,10705.0 -2015-09-27 19:00:00,10820.0 -2015-09-27 20:00:00,10932.0 -2015-09-27 21:00:00,11387.0 -2015-09-27 22:00:00,11195.0 -2015-09-27 23:00:00,10770.0 -2015-09-28 00:00:00,10132.0 -2015-09-26 01:00:00,9888.0 -2015-09-26 02:00:00,9202.0 -2015-09-26 03:00:00,8744.0 -2015-09-26 04:00:00,8454.0 -2015-09-26 05:00:00,8264.0 -2015-09-26 06:00:00,8204.0 -2015-09-26 07:00:00,8459.0 -2015-09-26 08:00:00,8819.0 -2015-09-26 09:00:00,9052.0 -2015-09-26 10:00:00,9682.0 -2015-09-26 11:00:00,10230.0 -2015-09-26 12:00:00,10680.0 -2015-09-26 13:00:00,10995.0 -2015-09-26 14:00:00,11221.0 -2015-09-26 15:00:00,11339.0 -2015-09-26 16:00:00,11494.0 -2015-09-26 17:00:00,11582.0 -2015-09-26 18:00:00,11602.0 -2015-09-26 19:00:00,11319.0 -2015-09-26 20:00:00,11068.0 -2015-09-26 21:00:00,11340.0 -2015-09-26 22:00:00,11086.0 -2015-09-26 23:00:00,10613.0 -2015-09-27 00:00:00,10029.0 -2015-09-25 01:00:00,9854.0 -2015-09-25 02:00:00,9273.0 -2015-09-25 03:00:00,8857.0 -2015-09-25 04:00:00,8597.0 -2015-09-25 05:00:00,8463.0 -2015-09-25 06:00:00,8603.0 -2015-09-25 07:00:00,9279.0 -2015-09-25 08:00:00,10296.0 -2015-09-25 09:00:00,10893.0 -2015-09-25 10:00:00,11485.0 -2015-09-25 11:00:00,11949.0 -2015-09-25 12:00:00,12338.0 -2015-09-25 13:00:00,12688.0 -2015-09-25 14:00:00,12868.0 -2015-09-25 15:00:00,13084.0 -2015-09-25 16:00:00,13153.0 -2015-09-25 17:00:00,13090.0 -2015-09-25 18:00:00,12942.0 -2015-09-25 19:00:00,12527.0 -2015-09-25 20:00:00,12114.0 -2015-09-25 21:00:00,12267.0 -2015-09-25 22:00:00,11922.0 -2015-09-25 23:00:00,11437.0 -2015-09-26 00:00:00,10659.0 -2015-09-24 01:00:00,9834.0 -2015-09-24 02:00:00,9227.0 -2015-09-24 03:00:00,8830.0 -2015-09-24 04:00:00,8561.0 -2015-09-24 05:00:00,8439.0 -2015-09-24 06:00:00,8558.0 -2015-09-24 07:00:00,9194.0 -2015-09-24 08:00:00,10237.0 -2015-09-24 09:00:00,10807.0 -2015-09-24 10:00:00,11315.0 -2015-09-24 11:00:00,11782.0 -2015-09-24 12:00:00,12261.0 -2015-09-24 13:00:00,12587.0 -2015-09-24 14:00:00,12842.0 -2015-09-24 15:00:00,13179.0 -2015-09-24 16:00:00,13325.0 -2015-09-24 17:00:00,13282.0 -2015-09-24 18:00:00,13142.0 -2015-09-24 19:00:00,12833.0 -2015-09-24 20:00:00,12442.0 -2015-09-24 21:00:00,12693.0 -2015-09-24 22:00:00,12292.0 -2015-09-24 23:00:00,11636.0 -2015-09-25 00:00:00,10724.0 -2015-09-23 01:00:00,9683.0 -2015-09-23 02:00:00,9083.0 -2015-09-23 03:00:00,8692.0 -2015-09-23 04:00:00,8429.0 -2015-09-23 05:00:00,8308.0 -2015-09-23 06:00:00,8474.0 -2015-09-23 07:00:00,9088.0 -2015-09-23 08:00:00,10084.0 -2015-09-23 09:00:00,10677.0 -2015-09-23 10:00:00,11311.0 -2015-09-23 11:00:00,11834.0 -2015-09-23 12:00:00,12322.0 -2015-09-23 13:00:00,12718.0 -2015-09-23 14:00:00,13028.0 -2015-09-23 15:00:00,13408.0 -2015-09-23 16:00:00,13607.0 -2015-09-23 17:00:00,13683.0 -2015-09-23 18:00:00,13562.0 -2015-09-23 19:00:00,13181.0 -2015-09-23 20:00:00,12694.0 -2015-09-23 21:00:00,12858.0 -2015-09-23 22:00:00,12421.0 -2015-09-23 23:00:00,11692.0 -2015-09-24 00:00:00,10739.0 -2015-09-22 01:00:00,9292.0 -2015-09-22 02:00:00,8749.0 -2015-09-22 03:00:00,8378.0 -2015-09-22 04:00:00,8168.0 -2015-09-22 05:00:00,8050.0 -2015-09-22 06:00:00,8208.0 -2015-09-22 07:00:00,8839.0 -2015-09-22 08:00:00,9831.0 -2015-09-22 09:00:00,10421.0 -2015-09-22 10:00:00,10969.0 -2015-09-22 11:00:00,11444.0 -2015-09-22 12:00:00,11913.0 -2015-09-22 13:00:00,12231.0 -2015-09-22 14:00:00,12524.0 -2015-09-22 15:00:00,12850.0 -2015-09-22 16:00:00,13054.0 -2015-09-22 17:00:00,13152.0 -2015-09-22 18:00:00,13078.0 -2015-09-22 19:00:00,12800.0 -2015-09-22 20:00:00,12443.0 -2015-09-22 21:00:00,12674.0 -2015-09-22 22:00:00,12287.0 -2015-09-22 23:00:00,11562.0 -2015-09-23 00:00:00,10568.0 -2015-09-21 01:00:00,8632.0 -2015-09-21 02:00:00,8200.0 -2015-09-21 03:00:00,7947.0 -2015-09-21 04:00:00,7800.0 -2015-09-21 05:00:00,7760.0 -2015-09-21 06:00:00,7961.0 -2015-09-21 07:00:00,8664.0 -2015-09-21 08:00:00,9689.0 -2015-09-21 09:00:00,10287.0 -2015-09-21 10:00:00,10835.0 -2015-09-21 11:00:00,11221.0 -2015-09-21 12:00:00,11595.0 -2015-09-21 13:00:00,11848.0 -2015-09-21 14:00:00,12037.0 -2015-09-21 15:00:00,12241.0 -2015-09-21 16:00:00,12331.0 -2015-09-21 17:00:00,12337.0 -2015-09-21 18:00:00,12308.0 -2015-09-21 19:00:00,12081.0 -2015-09-21 20:00:00,11695.0 -2015-09-21 21:00:00,11943.0 -2015-09-21 22:00:00,11654.0 -2015-09-21 23:00:00,11014.0 -2015-09-22 00:00:00,10101.0 -2015-09-20 01:00:00,8784.0 -2015-09-20 02:00:00,8289.0 -2015-09-20 03:00:00,7956.0 -2015-09-20 04:00:00,7709.0 -2015-09-20 05:00:00,7580.0 -2015-09-20 06:00:00,7503.0 -2015-09-20 07:00:00,7581.0 -2015-09-20 08:00:00,7683.0 -2015-09-20 09:00:00,7798.0 -2015-09-20 10:00:00,8254.0 -2015-09-20 11:00:00,8693.0 -2015-09-20 12:00:00,9059.0 -2015-09-20 13:00:00,9337.0 -2015-09-20 14:00:00,9504.0 -2015-09-20 15:00:00,9627.0 -2015-09-20 16:00:00,9766.0 -2015-09-20 17:00:00,9904.0 -2015-09-20 18:00:00,9994.0 -2015-09-20 19:00:00,10033.0 -2015-09-20 20:00:00,9960.0 -2015-09-20 21:00:00,10414.0 -2015-09-20 22:00:00,10233.0 -2015-09-20 23:00:00,9785.0 -2015-09-21 00:00:00,9194.0 -2015-09-19 01:00:00,10918.0 -2015-09-19 02:00:00,10145.0 -2015-09-19 03:00:00,9629.0 -2015-09-19 04:00:00,9233.0 -2015-09-19 05:00:00,8955.0 -2015-09-19 06:00:00,8796.0 -2015-09-19 07:00:00,8947.0 -2015-09-19 08:00:00,9175.0 -2015-09-19 09:00:00,9364.0 -2015-09-19 10:00:00,9791.0 -2015-09-19 11:00:00,10160.0 -2015-09-19 12:00:00,10374.0 -2015-09-19 13:00:00,10544.0 -2015-09-19 14:00:00,10625.0 -2015-09-19 15:00:00,10642.0 -2015-09-19 16:00:00,10588.0 -2015-09-19 17:00:00,10642.0 -2015-09-19 18:00:00,10611.0 -2015-09-19 19:00:00,10506.0 -2015-09-19 20:00:00,10252.0 -2015-09-19 21:00:00,10519.0 -2015-09-19 22:00:00,10320.0 -2015-09-19 23:00:00,9974.0 -2015-09-20 00:00:00,9358.0 -2015-09-18 01:00:00,11785.0 -2015-09-18 02:00:00,11022.0 -2015-09-18 03:00:00,10346.0 -2015-09-18 04:00:00,9923.0 -2015-09-18 05:00:00,9761.0 -2015-09-18 06:00:00,9878.0 -2015-09-18 07:00:00,10422.0 -2015-09-18 08:00:00,11527.0 -2015-09-18 09:00:00,12233.0 -2015-09-18 10:00:00,12779.0 -2015-09-18 11:00:00,13353.0 -2015-09-18 12:00:00,13840.0 -2015-09-18 13:00:00,13989.0 -2015-09-18 14:00:00,14082.0 -2015-09-18 15:00:00,14307.0 -2015-09-18 16:00:00,14338.0 -2015-09-18 17:00:00,14220.0 -2015-09-18 18:00:00,14201.0 -2015-09-18 19:00:00,14017.0 -2015-09-18 20:00:00,13752.0 -2015-09-18 21:00:00,13641.0 -2015-09-18 22:00:00,13316.0 -2015-09-18 23:00:00,12650.0 -2015-09-19 00:00:00,11793.0 -2015-09-17 01:00:00,10917.0 -2015-09-17 02:00:00,10131.0 -2015-09-17 03:00:00,9560.0 -2015-09-17 04:00:00,9220.0 -2015-09-17 05:00:00,9075.0 -2015-09-17 06:00:00,9172.0 -2015-09-17 07:00:00,9811.0 -2015-09-17 08:00:00,10854.0 -2015-09-17 09:00:00,11546.0 -2015-09-17 10:00:00,12165.0 -2015-09-17 11:00:00,12773.0 -2015-09-17 12:00:00,13541.0 -2015-09-17 13:00:00,14234.0 -2015-09-17 14:00:00,14970.0 -2015-09-17 15:00:00,15586.0 -2015-09-17 16:00:00,15972.0 -2015-09-17 17:00:00,16002.0 -2015-09-17 18:00:00,15559.0 -2015-09-17 19:00:00,14849.0 -2015-09-17 20:00:00,14516.0 -2015-09-17 21:00:00,14751.0 -2015-09-17 22:00:00,14529.0 -2015-09-17 23:00:00,13925.0 -2015-09-18 00:00:00,12809.0 -2015-09-16 01:00:00,10358.0 -2015-09-16 02:00:00,9617.0 -2015-09-16 03:00:00,9181.0 -2015-09-16 04:00:00,8879.0 -2015-09-16 05:00:00,8753.0 -2015-09-16 06:00:00,8876.0 -2015-09-16 07:00:00,9482.0 -2015-09-16 08:00:00,10467.0 -2015-09-16 09:00:00,11103.0 -2015-09-16 10:00:00,11767.0 -2015-09-16 11:00:00,12319.0 -2015-09-16 12:00:00,12967.0 -2015-09-16 13:00:00,13444.0 -2015-09-16 14:00:00,13934.0 -2015-09-16 15:00:00,14471.0 -2015-09-16 16:00:00,14870.0 -2015-09-16 17:00:00,15143.0 -2015-09-16 18:00:00,15219.0 -2015-09-16 19:00:00,14905.0 -2015-09-16 20:00:00,14290.0 -2015-09-16 21:00:00,14285.0 -2015-09-16 22:00:00,13916.0 -2015-09-16 23:00:00,13074.0 -2015-09-17 00:00:00,11994.0 -2015-09-15 01:00:00,10184.0 -2015-09-15 02:00:00,9529.0 -2015-09-15 03:00:00,9123.0 -2015-09-15 04:00:00,8812.0 -2015-09-15 05:00:00,8699.0 -2015-09-15 06:00:00,8825.0 -2015-09-15 07:00:00,9451.0 -2015-09-15 08:00:00,10471.0 -2015-09-15 09:00:00,11109.0 -2015-09-15 10:00:00,11650.0 -2015-09-15 11:00:00,12198.0 -2015-09-15 12:00:00,12730.0 -2015-09-15 13:00:00,13183.0 -2015-09-15 14:00:00,13610.0 -2015-09-15 15:00:00,14109.0 -2015-09-15 16:00:00,14430.0 -2015-09-15 17:00:00,14606.0 -2015-09-15 18:00:00,14627.0 -2015-09-15 19:00:00,14329.0 -2015-09-15 20:00:00,13661.0 -2015-09-15 21:00:00,13662.0 -2015-09-15 22:00:00,13330.0 -2015-09-15 23:00:00,12512.0 -2015-09-16 00:00:00,11417.0 -2015-09-14 01:00:00,8512.0 -2015-09-14 02:00:00,8109.0 -2015-09-14 03:00:00,7881.0 -2015-09-14 04:00:00,7727.0 -2015-09-14 05:00:00,7722.0 -2015-09-14 06:00:00,7953.0 -2015-09-14 07:00:00,8653.0 -2015-09-14 08:00:00,9638.0 -2015-09-14 09:00:00,10261.0 -2015-09-14 10:00:00,10764.0 -2015-09-14 11:00:00,11158.0 -2015-09-14 12:00:00,11551.0 -2015-09-14 13:00:00,11869.0 -2015-09-14 14:00:00,12148.0 -2015-09-14 15:00:00,12522.0 -2015-09-14 16:00:00,12788.0 -2015-09-14 17:00:00,13000.0 -2015-09-14 18:00:00,13133.0 -2015-09-14 19:00:00,13038.0 -2015-09-14 20:00:00,12716.0 -2015-09-14 21:00:00,13015.0 -2015-09-14 22:00:00,12835.0 -2015-09-14 23:00:00,12191.0 -2015-09-15 00:00:00,11195.0 -2015-09-13 01:00:00,8400.0 -2015-09-13 02:00:00,7981.0 -2015-09-13 03:00:00,7667.0 -2015-09-13 04:00:00,7517.0 -2015-09-13 05:00:00,7376.0 -2015-09-13 06:00:00,7396.0 -2015-09-13 07:00:00,7464.0 -2015-09-13 08:00:00,7570.0 -2015-09-13 09:00:00,7667.0 -2015-09-13 10:00:00,8137.0 -2015-09-13 11:00:00,8528.0 -2015-09-13 12:00:00,8877.0 -2015-09-13 13:00:00,9086.0 -2015-09-13 14:00:00,9218.0 -2015-09-13 15:00:00,9254.0 -2015-09-13 16:00:00,9337.0 -2015-09-13 17:00:00,9383.0 -2015-09-13 18:00:00,9531.0 -2015-09-13 19:00:00,9586.0 -2015-09-13 20:00:00,9545.0 -2015-09-13 21:00:00,9989.0 -2015-09-13 22:00:00,9980.0 -2015-09-13 23:00:00,9638.0 -2015-09-14 00:00:00,9048.0 -2015-09-12 01:00:00,9139.0 -2015-09-12 02:00:00,8537.0 -2015-09-12 03:00:00,8214.0 -2015-09-12 04:00:00,7945.0 -2015-09-12 05:00:00,7842.0 -2015-09-12 06:00:00,7839.0 -2015-09-12 07:00:00,8073.0 -2015-09-12 08:00:00,8354.0 -2015-09-12 09:00:00,8684.0 -2015-09-12 10:00:00,9072.0 -2015-09-12 11:00:00,9447.0 -2015-09-12 12:00:00,9662.0 -2015-09-12 13:00:00,9660.0 -2015-09-12 14:00:00,9615.0 -2015-09-12 15:00:00,9542.0 -2015-09-12 16:00:00,9472.0 -2015-09-12 17:00:00,9410.0 -2015-09-12 18:00:00,9407.0 -2015-09-12 19:00:00,9327.0 -2015-09-12 20:00:00,9292.0 -2015-09-12 21:00:00,9687.0 -2015-09-12 22:00:00,9754.0 -2015-09-12 23:00:00,9419.0 -2015-09-13 00:00:00,8945.0 -2015-09-11 01:00:00,9626.0 -2015-09-11 02:00:00,9074.0 -2015-09-11 03:00:00,8753.0 -2015-09-11 04:00:00,8547.0 -2015-09-11 05:00:00,8467.0 -2015-09-11 06:00:00,8638.0 -2015-09-11 07:00:00,9304.0 -2015-09-11 08:00:00,10379.0 -2015-09-11 09:00:00,11036.0 -2015-09-11 10:00:00,11323.0 -2015-09-11 11:00:00,11569.0 -2015-09-11 12:00:00,11817.0 -2015-09-11 13:00:00,11860.0 -2015-09-11 14:00:00,11881.0 -2015-09-11 15:00:00,11954.0 -2015-09-11 16:00:00,11883.0 -2015-09-11 17:00:00,11657.0 -2015-09-11 18:00:00,11378.0 -2015-09-11 19:00:00,11060.0 -2015-09-11 20:00:00,10785.0 -2015-09-11 21:00:00,11024.0 -2015-09-11 22:00:00,10959.0 -2015-09-11 23:00:00,10522.0 -2015-09-12 00:00:00,9822.0 -2015-09-10 01:00:00,10074.0 -2015-09-10 02:00:00,9440.0 -2015-09-10 03:00:00,8996.0 -2015-09-10 04:00:00,8703.0 -2015-09-10 05:00:00,8632.0 -2015-09-10 06:00:00,8766.0 -2015-09-10 07:00:00,9401.0 -2015-09-10 08:00:00,10349.0 -2015-09-10 09:00:00,11060.0 -2015-09-10 10:00:00,11645.0 -2015-09-10 11:00:00,11995.0 -2015-09-10 12:00:00,12269.0 -2015-09-10 13:00:00,12572.0 -2015-09-10 14:00:00,12654.0 -2015-09-10 15:00:00,12579.0 -2015-09-10 16:00:00,12290.0 -2015-09-10 17:00:00,12103.0 -2015-09-10 18:00:00,12094.0 -2015-09-10 19:00:00,11954.0 -2015-09-10 20:00:00,11740.0 -2015-09-10 21:00:00,11983.0 -2015-09-10 22:00:00,11939.0 -2015-09-10 23:00:00,11337.0 -2015-09-11 00:00:00,10460.0 -2015-09-09 01:00:00,11438.0 -2015-09-09 02:00:00,10611.0 -2015-09-09 03:00:00,10071.0 -2015-09-09 04:00:00,9748.0 -2015-09-09 05:00:00,9605.0 -2015-09-09 06:00:00,9744.0 -2015-09-09 07:00:00,10404.0 -2015-09-09 08:00:00,11516.0 -2015-09-09 09:00:00,12114.0 -2015-09-09 10:00:00,12496.0 -2015-09-09 11:00:00,12776.0 -2015-09-09 12:00:00,13101.0 -2015-09-09 13:00:00,13382.0 -2015-09-09 14:00:00,13684.0 -2015-09-09 15:00:00,14025.0 -2015-09-09 16:00:00,14244.0 -2015-09-09 17:00:00,14503.0 -2015-09-09 18:00:00,14600.0 -2015-09-09 19:00:00,14287.0 -2015-09-09 20:00:00,13607.0 -2015-09-09 21:00:00,13353.0 -2015-09-09 22:00:00,13023.0 -2015-09-09 23:00:00,12198.0 -2015-09-10 00:00:00,11085.0 -2015-09-08 01:00:00,12794.0 -2015-09-08 02:00:00,12014.0 -2015-09-08 03:00:00,11461.0 -2015-09-08 04:00:00,11056.0 -2015-09-08 05:00:00,10897.0 -2015-09-08 06:00:00,11082.0 -2015-09-08 07:00:00,11898.0 -2015-09-08 08:00:00,13165.0 -2015-09-08 09:00:00,14029.0 -2015-09-08 10:00:00,14638.0 -2015-09-08 11:00:00,15361.0 -2015-09-08 12:00:00,16061.0 -2015-09-08 13:00:00,16330.0 -2015-09-08 14:00:00,16215.0 -2015-09-08 15:00:00,15658.0 -2015-09-08 16:00:00,15272.0 -2015-09-08 17:00:00,15047.0 -2015-09-08 18:00:00,14978.0 -2015-09-08 19:00:00,14818.0 -2015-09-08 20:00:00,14582.0 -2015-09-08 21:00:00,14859.0 -2015-09-08 22:00:00,14521.0 -2015-09-08 23:00:00,13722.0 -2015-09-09 00:00:00,12553.0 -2015-09-07 01:00:00,13037.0 -2015-09-07 02:00:00,12235.0 -2015-09-07 03:00:00,11483.0 -2015-09-07 04:00:00,10992.0 -2015-09-07 05:00:00,10635.0 -2015-09-07 06:00:00,10505.0 -2015-09-07 07:00:00,10648.0 -2015-09-07 08:00:00,10725.0 -2015-09-07 09:00:00,10860.0 -2015-09-07 10:00:00,11455.0 -2015-09-07 11:00:00,12636.0 -2015-09-07 12:00:00,14113.0 -2015-09-07 13:00:00,15262.0 -2015-09-07 14:00:00,15865.0 -2015-09-07 15:00:00,16122.0 -2015-09-07 16:00:00,16484.0 -2015-09-07 17:00:00,16611.0 -2015-09-07 18:00:00,16455.0 -2015-09-07 19:00:00,16513.0 -2015-09-07 20:00:00,16108.0 -2015-09-07 21:00:00,16120.0 -2015-09-07 22:00:00,15874.0 -2015-09-07 23:00:00,15046.0 -2015-09-08 00:00:00,13855.0 -2015-09-06 01:00:00,11880.0 -2015-09-06 02:00:00,11023.0 -2015-09-06 03:00:00,10520.0 -2015-09-06 04:00:00,10082.0 -2015-09-06 05:00:00,9769.0 -2015-09-06 06:00:00,9619.0 -2015-09-06 07:00:00,9619.0 -2015-09-06 08:00:00,9568.0 -2015-09-06 09:00:00,9958.0 -2015-09-06 10:00:00,11067.0 -2015-09-06 11:00:00,12380.0 -2015-09-06 12:00:00,13816.0 -2015-09-06 13:00:00,15037.0 -2015-09-06 14:00:00,16022.0 -2015-09-06 15:00:00,16585.0 -2015-09-06 16:00:00,17105.0 -2015-09-06 17:00:00,17322.0 -2015-09-06 18:00:00,17341.0 -2015-09-06 19:00:00,17073.0 -2015-09-06 20:00:00,16372.0 -2015-09-06 21:00:00,15974.0 -2015-09-06 22:00:00,15628.0 -2015-09-06 23:00:00,14930.0 -2015-09-07 00:00:00,14008.0 -2015-09-05 01:00:00,12364.0 -2015-09-05 02:00:00,11472.0 -2015-09-05 03:00:00,10801.0 -2015-09-05 04:00:00,10320.0 -2015-09-05 05:00:00,10008.0 -2015-09-05 06:00:00,9934.0 -2015-09-05 07:00:00,10091.0 -2015-09-05 08:00:00,10264.0 -2015-09-05 09:00:00,10682.0 -2015-09-05 10:00:00,11407.0 -2015-09-05 11:00:00,12143.0 -2015-09-05 12:00:00,12745.0 -2015-09-05 13:00:00,13432.0 -2015-09-05 14:00:00,13958.0 -2015-09-05 15:00:00,14485.0 -2015-09-05 16:00:00,14871.0 -2015-09-05 17:00:00,15157.0 -2015-09-05 18:00:00,15266.0 -2015-09-05 19:00:00,15099.0 -2015-09-05 20:00:00,14578.0 -2015-09-05 21:00:00,14402.0 -2015-09-05 22:00:00,14166.0 -2015-09-05 23:00:00,13568.0 -2015-09-06 00:00:00,12739.0 -2015-09-04 01:00:00,13277.0 -2015-09-04 02:00:00,12222.0 -2015-09-04 03:00:00,11465.0 -2015-09-04 04:00:00,10946.0 -2015-09-04 05:00:00,10635.0 -2015-09-04 06:00:00,10652.0 -2015-09-04 07:00:00,11260.0 -2015-09-04 08:00:00,12167.0 -2015-09-04 09:00:00,13031.0 -2015-09-04 10:00:00,13893.0 -2015-09-04 11:00:00,14666.0 -2015-09-04 12:00:00,15554.0 -2015-09-04 13:00:00,16333.0 -2015-09-04 14:00:00,17062.0 -2015-09-04 15:00:00,17782.0 -2015-09-04 16:00:00,18180.0 -2015-09-04 17:00:00,18234.0 -2015-09-04 18:00:00,18114.0 -2015-09-04 19:00:00,17483.0 -2015-09-04 20:00:00,16441.0 -2015-09-04 21:00:00,15751.0 -2015-09-04 22:00:00,15336.0 -2015-09-04 23:00:00,14502.0 -2015-09-05 00:00:00,13439.0 -2015-09-03 01:00:00,13869.0 -2015-09-03 02:00:00,12823.0 -2015-09-03 03:00:00,12101.0 -2015-09-03 04:00:00,11538.0 -2015-09-03 05:00:00,11255.0 -2015-09-03 06:00:00,11326.0 -2015-09-03 07:00:00,11965.0 -2015-09-03 08:00:00,12897.0 -2015-09-03 09:00:00,13867.0 -2015-09-03 10:00:00,14958.0 -2015-09-03 11:00:00,16086.0 -2015-09-03 12:00:00,17254.0 -2015-09-03 13:00:00,18196.0 -2015-09-03 14:00:00,18917.0 -2015-09-03 15:00:00,19491.0 -2015-09-03 16:00:00,19847.0 -2015-09-03 17:00:00,20099.0 -2015-09-03 18:00:00,20162.0 -2015-09-03 19:00:00,19936.0 -2015-09-03 20:00:00,19033.0 -2015-09-03 21:00:00,18169.0 -2015-09-03 22:00:00,17436.0 -2015-09-03 23:00:00,16230.0 -2015-09-04 00:00:00,14712.0 -2015-09-02 01:00:00,14015.0 -2015-09-02 02:00:00,12825.0 -2015-09-02 03:00:00,11998.0 -2015-09-02 04:00:00,11420.0 -2015-09-02 05:00:00,11093.0 -2015-09-02 06:00:00,11146.0 -2015-09-02 07:00:00,11754.0 -2015-09-02 08:00:00,12731.0 -2015-09-02 09:00:00,13634.0 -2015-09-02 10:00:00,14591.0 -2015-09-02 11:00:00,15613.0 -2015-09-02 12:00:00,16813.0 -2015-09-02 13:00:00,17919.0 -2015-09-02 14:00:00,18759.0 -2015-09-02 15:00:00,19388.0 -2015-09-02 16:00:00,19715.0 -2015-09-02 17:00:00,19705.0 -2015-09-02 18:00:00,19507.0 -2015-09-02 19:00:00,19066.0 -2015-09-02 20:00:00,18259.0 -2015-09-02 21:00:00,17925.0 -2015-09-02 22:00:00,17645.0 -2015-09-02 23:00:00,16644.0 -2015-09-03 00:00:00,15142.0 -2015-09-01 01:00:00,12248.0 -2015-09-01 02:00:00,11352.0 -2015-09-01 03:00:00,10740.0 -2015-09-01 04:00:00,10334.0 -2015-09-01 05:00:00,10112.0 -2015-09-01 06:00:00,10298.0 -2015-09-01 07:00:00,11001.0 -2015-09-01 08:00:00,12053.0 -2015-09-01 09:00:00,13007.0 -2015-09-01 10:00:00,13986.0 -2015-09-01 11:00:00,15033.0 -2015-09-01 12:00:00,16178.0 -2015-09-01 13:00:00,17200.0 -2015-09-01 14:00:00,18060.0 -2015-09-01 15:00:00,18593.0 -2015-09-01 16:00:00,18983.0 -2015-09-01 17:00:00,19301.0 -2015-09-01 18:00:00,19424.0 -2015-09-01 19:00:00,19185.0 -2015-09-01 20:00:00,18560.0 -2015-09-01 21:00:00,18199.0 -2015-09-01 22:00:00,17888.0 -2015-09-01 23:00:00,16917.0 -2015-09-02 00:00:00,15499.0 -2015-08-31 01:00:00,9877.0 -2015-08-31 02:00:00,9319.0 -2015-08-31 03:00:00,8928.0 -2015-08-31 04:00:00,8688.0 -2015-08-31 05:00:00,8624.0 -2015-08-31 06:00:00,8858.0 -2015-08-31 07:00:00,9557.0 -2015-08-31 08:00:00,10550.0 -2015-08-31 09:00:00,11333.0 -2015-08-31 10:00:00,12057.0 -2015-08-31 11:00:00,12802.0 -2015-08-31 12:00:00,13654.0 -2015-08-31 13:00:00,14434.0 -2015-08-31 14:00:00,15184.0 -2015-08-31 15:00:00,15921.0 -2015-08-31 16:00:00,16454.0 -2015-08-31 17:00:00,16798.0 -2015-08-31 18:00:00,17001.0 -2015-08-31 19:00:00,16821.0 -2015-08-31 20:00:00,16236.0 -2015-08-31 21:00:00,15950.0 -2015-08-31 22:00:00,15727.0 -2015-08-31 23:00:00,14774.0 -2015-09-01 00:00:00,13495.0 -2015-08-30 01:00:00,9720.0 -2015-08-30 02:00:00,9102.0 -2015-08-30 03:00:00,8752.0 -2015-08-30 04:00:00,8478.0 -2015-08-30 05:00:00,8323.0 -2015-08-30 06:00:00,8258.0 -2015-08-30 07:00:00,8336.0 -2015-08-30 08:00:00,8355.0 -2015-08-30 09:00:00,8555.0 -2015-08-30 10:00:00,9049.0 -2015-08-30 11:00:00,9603.0 -2015-08-30 12:00:00,10090.0 -2015-08-30 13:00:00,10565.0 -2015-08-30 14:00:00,10827.0 -2015-08-30 15:00:00,11061.0 -2015-08-30 16:00:00,11326.0 -2015-08-30 17:00:00,11646.0 -2015-08-30 18:00:00,11935.0 -2015-08-30 19:00:00,11994.0 -2015-08-30 20:00:00,11813.0 -2015-08-30 21:00:00,11893.0 -2015-08-30 22:00:00,12010.0 -2015-08-30 23:00:00,11484.0 -2015-08-31 00:00:00,10700.0 -2015-08-29 01:00:00,10094.0 -2015-08-29 02:00:00,9471.0 -2015-08-29 03:00:00,9033.0 -2015-08-29 04:00:00,8718.0 -2015-08-29 05:00:00,8583.0 -2015-08-29 06:00:00,8556.0 -2015-08-29 07:00:00,8778.0 -2015-08-29 08:00:00,9152.0 -2015-08-29 09:00:00,9505.0 -2015-08-29 10:00:00,10016.0 -2015-08-29 11:00:00,10493.0 -2015-08-29 12:00:00,10790.0 -2015-08-29 13:00:00,10974.0 -2015-08-29 14:00:00,10980.0 -2015-08-29 15:00:00,10961.0 -2015-08-29 16:00:00,10919.0 -2015-08-29 17:00:00,10883.0 -2015-08-29 18:00:00,10865.0 -2015-08-29 19:00:00,10873.0 -2015-08-29 20:00:00,10899.0 -2015-08-29 21:00:00,11183.0 -2015-08-29 22:00:00,11219.0 -2015-08-29 23:00:00,10920.0 -2015-08-30 00:00:00,10303.0 -2015-08-28 01:00:00,9696.0 -2015-08-28 02:00:00,9121.0 -2015-08-28 03:00:00,8732.0 -2015-08-28 04:00:00,8493.0 -2015-08-28 05:00:00,8367.0 -2015-08-28 06:00:00,8550.0 -2015-08-28 07:00:00,9145.0 -2015-08-28 08:00:00,9975.0 -2015-08-28 09:00:00,10741.0 -2015-08-28 10:00:00,11337.0 -2015-08-28 11:00:00,11768.0 -2015-08-28 12:00:00,12148.0 -2015-08-28 13:00:00,12323.0 -2015-08-28 14:00:00,12333.0 -2015-08-28 15:00:00,12365.0 -2015-08-28 16:00:00,12275.0 -2015-08-28 17:00:00,12155.0 -2015-08-28 18:00:00,11998.0 -2015-08-28 19:00:00,11807.0 -2015-08-28 20:00:00,11661.0 -2015-08-28 21:00:00,11835.0 -2015-08-28 22:00:00,11849.0 -2015-08-28 23:00:00,11549.0 -2015-08-29 00:00:00,10840.0 -2015-08-27 01:00:00,9320.0 -2015-08-27 02:00:00,8765.0 -2015-08-27 03:00:00,8448.0 -2015-08-27 04:00:00,8219.0 -2015-08-27 05:00:00,8125.0 -2015-08-27 06:00:00,8277.0 -2015-08-27 07:00:00,8855.0 -2015-08-27 08:00:00,9698.0 -2015-08-27 09:00:00,10432.0 -2015-08-27 10:00:00,10942.0 -2015-08-27 11:00:00,11326.0 -2015-08-27 12:00:00,11609.0 -2015-08-27 13:00:00,11819.0 -2015-08-27 14:00:00,12005.0 -2015-08-27 15:00:00,12271.0 -2015-08-27 16:00:00,12434.0 -2015-08-27 17:00:00,12494.0 -2015-08-27 18:00:00,12474.0 -2015-08-27 19:00:00,12162.0 -2015-08-27 20:00:00,11747.0 -2015-08-27 21:00:00,11792.0 -2015-08-27 22:00:00,11966.0 -2015-08-27 23:00:00,11412.0 -2015-08-28 00:00:00,10545.0 -2015-08-26 01:00:00,9588.0 -2015-08-26 02:00:00,8988.0 -2015-08-26 03:00:00,8578.0 -2015-08-26 04:00:00,8336.0 -2015-08-26 05:00:00,8222.0 -2015-08-26 06:00:00,8386.0 -2015-08-26 07:00:00,9002.0 -2015-08-26 08:00:00,9879.0 -2015-08-26 09:00:00,10625.0 -2015-08-26 10:00:00,11062.0 -2015-08-26 11:00:00,11280.0 -2015-08-26 12:00:00,11454.0 -2015-08-26 13:00:00,11531.0 -2015-08-26 14:00:00,11533.0 -2015-08-26 15:00:00,11607.0 -2015-08-26 16:00:00,11590.0 -2015-08-26 17:00:00,11512.0 -2015-08-26 18:00:00,11384.0 -2015-08-26 19:00:00,11245.0 -2015-08-26 20:00:00,11065.0 -2015-08-26 21:00:00,11234.0 -2015-08-26 22:00:00,11419.0 -2015-08-26 23:00:00,10922.0 -2015-08-27 00:00:00,10104.0 -2015-08-25 01:00:00,9533.0 -2015-08-25 02:00:00,8996.0 -2015-08-25 03:00:00,8649.0 -2015-08-25 04:00:00,8431.0 -2015-08-25 05:00:00,8318.0 -2015-08-25 06:00:00,8508.0 -2015-08-25 07:00:00,9109.0 -2015-08-25 08:00:00,9940.0 -2015-08-25 09:00:00,10594.0 -2015-08-25 10:00:00,11065.0 -2015-08-25 11:00:00,11344.0 -2015-08-25 12:00:00,11557.0 -2015-08-25 13:00:00,11672.0 -2015-08-25 14:00:00,11770.0 -2015-08-25 15:00:00,11922.0 -2015-08-25 16:00:00,11756.0 -2015-08-25 17:00:00,11683.0 -2015-08-25 18:00:00,11796.0 -2015-08-25 19:00:00,11659.0 -2015-08-25 20:00:00,11401.0 -2015-08-25 21:00:00,11583.0 -2015-08-25 22:00:00,11704.0 -2015-08-25 23:00:00,11203.0 -2015-08-26 00:00:00,10389.0 -2015-08-24 01:00:00,9694.0 -2015-08-24 02:00:00,9123.0 -2015-08-24 03:00:00,8729.0 -2015-08-24 04:00:00,8490.0 -2015-08-24 05:00:00,8414.0 -2015-08-24 06:00:00,8591.0 -2015-08-24 07:00:00,9200.0 -2015-08-24 08:00:00,9884.0 -2015-08-24 09:00:00,10642.0 -2015-08-24 10:00:00,11211.0 -2015-08-24 11:00:00,11712.0 -2015-08-24 12:00:00,12166.0 -2015-08-24 13:00:00,12500.0 -2015-08-24 14:00:00,12599.0 -2015-08-24 15:00:00,12770.0 -2015-08-24 16:00:00,12817.0 -2015-08-24 17:00:00,12705.0 -2015-08-24 18:00:00,12513.0 -2015-08-24 19:00:00,12146.0 -2015-08-24 20:00:00,11745.0 -2015-08-24 21:00:00,11779.0 -2015-08-24 22:00:00,11777.0 -2015-08-24 23:00:00,11121.0 -2015-08-25 00:00:00,10337.0 -2015-08-23 01:00:00,10591.0 -2015-08-23 02:00:00,9984.0 -2015-08-23 03:00:00,9535.0 -2015-08-23 04:00:00,9215.0 -2015-08-23 05:00:00,9000.0 -2015-08-23 06:00:00,8910.0 -2015-08-23 07:00:00,8969.0 -2015-08-23 08:00:00,8872.0 -2015-08-23 09:00:00,9296.0 -2015-08-23 10:00:00,9839.0 -2015-08-23 11:00:00,10436.0 -2015-08-23 12:00:00,10880.0 -2015-08-23 13:00:00,11188.0 -2015-08-23 14:00:00,11476.0 -2015-08-23 15:00:00,11743.0 -2015-08-23 16:00:00,12050.0 -2015-08-23 17:00:00,12206.0 -2015-08-23 18:00:00,12192.0 -2015-08-23 19:00:00,12008.0 -2015-08-23 20:00:00,11664.0 -2015-08-23 21:00:00,11450.0 -2015-08-23 22:00:00,11607.0 -2015-08-23 23:00:00,11147.0 -2015-08-24 00:00:00,10391.0 -2015-08-22 01:00:00,11210.0 -2015-08-22 02:00:00,10414.0 -2015-08-22 03:00:00,9764.0 -2015-08-22 04:00:00,9370.0 -2015-08-22 05:00:00,9029.0 -2015-08-22 06:00:00,8999.0 -2015-08-22 07:00:00,9077.0 -2015-08-22 08:00:00,9215.0 -2015-08-22 09:00:00,9780.0 -2015-08-22 10:00:00,10663.0 -2015-08-22 11:00:00,11488.0 -2015-08-22 12:00:00,12207.0 -2015-08-22 13:00:00,12700.0 -2015-08-22 14:00:00,13003.0 -2015-08-22 15:00:00,13214.0 -2015-08-22 16:00:00,13338.0 -2015-08-22 17:00:00,13456.0 -2015-08-22 18:00:00,13555.0 -2015-08-22 19:00:00,13398.0 -2015-08-22 20:00:00,12890.0 -2015-08-22 21:00:00,12426.0 -2015-08-22 22:00:00,12427.0 -2015-08-22 23:00:00,12014.0 -2015-08-23 00:00:00,11276.0 -2015-08-21 01:00:00,9870.0 -2015-08-21 02:00:00,9256.0 -2015-08-21 03:00:00,8861.0 -2015-08-21 04:00:00,8583.0 -2015-08-21 05:00:00,8476.0 -2015-08-21 06:00:00,8611.0 -2015-08-21 07:00:00,9200.0 -2015-08-21 08:00:00,9917.0 -2015-08-21 09:00:00,10775.0 -2015-08-21 10:00:00,11551.0 -2015-08-21 11:00:00,12183.0 -2015-08-21 12:00:00,12720.0 -2015-08-21 13:00:00,13157.0 -2015-08-21 14:00:00,13585.0 -2015-08-21 15:00:00,14109.0 -2015-08-21 16:00:00,14520.0 -2015-08-21 17:00:00,14707.0 -2015-08-21 18:00:00,14749.0 -2015-08-21 19:00:00,14662.0 -2015-08-21 20:00:00,14185.0 -2015-08-21 21:00:00,13716.0 -2015-08-21 22:00:00,13689.0 -2015-08-21 23:00:00,13184.0 -2015-08-22 00:00:00,12239.0 -2015-08-20 01:00:00,10278.0 -2015-08-20 02:00:00,9646.0 -2015-08-20 03:00:00,9184.0 -2015-08-20 04:00:00,8885.0 -2015-08-20 05:00:00,8810.0 -2015-08-20 06:00:00,8963.0 -2015-08-20 07:00:00,9566.0 -2015-08-20 08:00:00,10304.0 -2015-08-20 09:00:00,10991.0 -2015-08-20 10:00:00,11458.0 -2015-08-20 11:00:00,11714.0 -2015-08-20 12:00:00,11992.0 -2015-08-20 13:00:00,12148.0 -2015-08-20 14:00:00,12213.0 -2015-08-20 15:00:00,12521.0 -2015-08-20 16:00:00,12646.0 -2015-08-20 17:00:00,12699.0 -2015-08-20 18:00:00,12703.0 -2015-08-20 19:00:00,12529.0 -2015-08-20 20:00:00,12143.0 -2015-08-20 21:00:00,11919.0 -2015-08-20 22:00:00,12121.0 -2015-08-20 23:00:00,11637.0 -2015-08-21 00:00:00,10774.0 -2015-08-19 01:00:00,12117.0 -2015-08-19 02:00:00,11308.0 -2015-08-19 03:00:00,10737.0 -2015-08-19 04:00:00,10394.0 -2015-08-19 05:00:00,10220.0 -2015-08-19 06:00:00,10361.0 -2015-08-19 07:00:00,11098.0 -2015-08-19 08:00:00,12094.0 -2015-08-19 09:00:00,12950.0 -2015-08-19 10:00:00,13526.0 -2015-08-19 11:00:00,13942.0 -2015-08-19 12:00:00,14207.0 -2015-08-19 13:00:00,14477.0 -2015-08-19 14:00:00,14667.0 -2015-08-19 15:00:00,14946.0 -2015-08-19 16:00:00,15068.0 -2015-08-19 17:00:00,15020.0 -2015-08-19 18:00:00,14430.0 -2015-08-19 19:00:00,13752.0 -2015-08-19 20:00:00,13035.0 -2015-08-19 21:00:00,12763.0 -2015-08-19 22:00:00,12726.0 -2015-08-19 23:00:00,12105.0 -2015-08-20 00:00:00,11206.0 -2015-08-18 01:00:00,12474.0 -2015-08-18 02:00:00,11605.0 -2015-08-18 03:00:00,10959.0 -2015-08-18 04:00:00,10568.0 -2015-08-18 05:00:00,10418.0 -2015-08-18 06:00:00,10491.0 -2015-08-18 07:00:00,11148.0 -2015-08-18 08:00:00,12027.0 -2015-08-18 09:00:00,12819.0 -2015-08-18 10:00:00,13674.0 -2015-08-18 11:00:00,14433.0 -2015-08-18 12:00:00,15146.0 -2015-08-18 13:00:00,15811.0 -2015-08-18 14:00:00,16448.0 -2015-08-18 15:00:00,16953.0 -2015-08-18 16:00:00,17243.0 -2015-08-18 17:00:00,17425.0 -2015-08-18 18:00:00,17359.0 -2015-08-18 19:00:00,16892.0 -2015-08-18 20:00:00,16366.0 -2015-08-18 21:00:00,16249.0 -2015-08-18 22:00:00,15554.0 -2015-08-18 23:00:00,14464.0 -2015-08-19 00:00:00,13274.0 -2015-08-17 01:00:00,13213.0 -2015-08-17 02:00:00,12248.0 -2015-08-17 03:00:00,11630.0 -2015-08-17 04:00:00,11131.0 -2015-08-17 05:00:00,10912.0 -2015-08-17 06:00:00,10978.0 -2015-08-17 07:00:00,11609.0 -2015-08-17 08:00:00,12461.0 -2015-08-17 09:00:00,13639.0 -2015-08-17 10:00:00,14984.0 -2015-08-17 11:00:00,16152.0 -2015-08-17 12:00:00,17339.0 -2015-08-17 13:00:00,18070.0 -2015-08-17 14:00:00,18435.0 -2015-08-17 15:00:00,18694.0 -2015-08-17 16:00:00,18667.0 -2015-08-17 17:00:00,18348.0 -2015-08-17 18:00:00,17865.0 -2015-08-17 19:00:00,17263.0 -2015-08-17 20:00:00,16649.0 -2015-08-17 21:00:00,16227.0 -2015-08-17 22:00:00,15944.0 -2015-08-17 23:00:00,14939.0 -2015-08-18 00:00:00,13667.0 -2015-08-16 01:00:00,12577.0 -2015-08-16 02:00:00,11748.0 -2015-08-16 03:00:00,11058.0 -2015-08-16 04:00:00,10564.0 -2015-08-16 05:00:00,10186.0 -2015-08-16 06:00:00,10015.0 -2015-08-16 07:00:00,9963.0 -2015-08-16 08:00:00,9917.0 -2015-08-16 09:00:00,10554.0 -2015-08-16 10:00:00,11865.0 -2015-08-16 11:00:00,13210.0 -2015-08-16 12:00:00,14490.0 -2015-08-16 13:00:00,15443.0 -2015-08-16 14:00:00,16154.0 -2015-08-16 15:00:00,16676.0 -2015-08-16 16:00:00,17094.0 -2015-08-16 17:00:00,17467.0 -2015-08-16 18:00:00,17583.0 -2015-08-16 19:00:00,17563.0 -2015-08-16 20:00:00,17100.0 -2015-08-16 21:00:00,16373.0 -2015-08-16 22:00:00,16212.0 -2015-08-16 23:00:00,15485.0 -2015-08-17 00:00:00,14362.0 -2015-08-15 01:00:00,12701.0 -2015-08-15 02:00:00,11659.0 -2015-08-15 03:00:00,10880.0 -2015-08-15 04:00:00,10369.0 -2015-08-15 05:00:00,10008.0 -2015-08-15 06:00:00,9939.0 -2015-08-15 07:00:00,10056.0 -2015-08-15 08:00:00,10216.0 -2015-08-15 09:00:00,10958.0 -2015-08-15 10:00:00,12064.0 -2015-08-15 11:00:00,13199.0 -2015-08-15 12:00:00,14387.0 -2015-08-15 13:00:00,15467.0 -2015-08-15 14:00:00,16404.0 -2015-08-15 15:00:00,16996.0 -2015-08-15 16:00:00,17323.0 -2015-08-15 17:00:00,17107.0 -2015-08-15 18:00:00,16481.0 -2015-08-15 19:00:00,15881.0 -2015-08-15 20:00:00,15569.0 -2015-08-15 21:00:00,15035.0 -2015-08-15 22:00:00,14934.0 -2015-08-15 23:00:00,14443.0 -2015-08-16 00:00:00,13617.0 -2015-08-14 01:00:00,12626.0 -2015-08-14 02:00:00,11787.0 -2015-08-14 03:00:00,11191.0 -2015-08-14 04:00:00,10731.0 -2015-08-14 05:00:00,10448.0 -2015-08-14 06:00:00,10634.0 -2015-08-14 07:00:00,11267.0 -2015-08-14 08:00:00,12050.0 -2015-08-14 09:00:00,12869.0 -2015-08-14 10:00:00,13618.0 -2015-08-14 11:00:00,14099.0 -2015-08-14 12:00:00,14737.0 -2015-08-14 13:00:00,15394.0 -2015-08-14 14:00:00,16240.0 -2015-08-14 15:00:00,17186.0 -2015-08-14 16:00:00,17914.0 -2015-08-14 17:00:00,18469.0 -2015-08-14 18:00:00,18741.0 -2015-08-14 19:00:00,18508.0 -2015-08-14 20:00:00,17873.0 -2015-08-14 21:00:00,17240.0 -2015-08-14 22:00:00,16991.0 -2015-08-14 23:00:00,15845.0 -2015-08-15 00:00:00,14098.0 -2015-08-13 01:00:00,11478.0 -2015-08-13 02:00:00,10630.0 -2015-08-13 03:00:00,10054.0 -2015-08-13 04:00:00,9651.0 -2015-08-13 05:00:00,9472.0 -2015-08-13 06:00:00,9539.0 -2015-08-13 07:00:00,10107.0 -2015-08-13 08:00:00,10883.0 -2015-08-13 09:00:00,11889.0 -2015-08-13 10:00:00,12917.0 -2015-08-13 11:00:00,13818.0 -2015-08-13 12:00:00,14738.0 -2015-08-13 13:00:00,15415.0 -2015-08-13 14:00:00,15999.0 -2015-08-13 15:00:00,16564.0 -2015-08-13 16:00:00,16951.0 -2015-08-13 17:00:00,17126.0 -2015-08-13 18:00:00,17216.0 -2015-08-13 19:00:00,16802.0 -2015-08-13 20:00:00,15897.0 -2015-08-13 21:00:00,15392.0 -2015-08-13 22:00:00,15384.0 -2015-08-13 23:00:00,14800.0 -2015-08-14 00:00:00,13779.0 -2015-08-12 01:00:00,11067.0 -2015-08-12 02:00:00,10270.0 -2015-08-12 03:00:00,9730.0 -2015-08-12 04:00:00,9382.0 -2015-08-12 05:00:00,9216.0 -2015-08-12 06:00:00,9333.0 -2015-08-12 07:00:00,9887.0 -2015-08-12 08:00:00,10633.0 -2015-08-12 09:00:00,11645.0 -2015-08-12 10:00:00,12549.0 -2015-08-12 11:00:00,13237.0 -2015-08-12 12:00:00,13851.0 -2015-08-12 13:00:00,14309.0 -2015-08-12 14:00:00,14584.0 -2015-08-12 15:00:00,15151.0 -2015-08-12 16:00:00,15563.0 -2015-08-12 17:00:00,15826.0 -2015-08-12 18:00:00,15950.0 -2015-08-12 19:00:00,15810.0 -2015-08-12 20:00:00,15240.0 -2015-08-12 21:00:00,14551.0 -2015-08-12 22:00:00,14369.0 -2015-08-12 23:00:00,13715.0 -2015-08-13 00:00:00,12631.0 -2015-08-11 01:00:00,11686.0 -2015-08-11 02:00:00,10823.0 -2015-08-11 03:00:00,10272.0 -2015-08-11 04:00:00,9849.0 -2015-08-11 05:00:00,9624.0 -2015-08-11 06:00:00,9727.0 -2015-08-11 07:00:00,10281.0 -2015-08-11 08:00:00,10990.0 -2015-08-11 09:00:00,12065.0 -2015-08-11 10:00:00,13042.0 -2015-08-11 11:00:00,13845.0 -2015-08-11 12:00:00,14672.0 -2015-08-11 13:00:00,15238.0 -2015-08-11 14:00:00,15618.0 -2015-08-11 15:00:00,15963.0 -2015-08-11 16:00:00,16172.0 -2015-08-11 17:00:00,16170.0 -2015-08-11 18:00:00,16031.0 -2015-08-11 19:00:00,15690.0 -2015-08-11 20:00:00,14967.0 -2015-08-11 21:00:00,14172.0 -2015-08-11 22:00:00,13896.0 -2015-08-11 23:00:00,13272.0 -2015-08-12 00:00:00,12175.0 -2015-08-10 01:00:00,11340.0 -2015-08-10 02:00:00,10702.0 -2015-08-10 03:00:00,10234.0 -2015-08-10 04:00:00,9970.0 -2015-08-10 05:00:00,9879.0 -2015-08-10 06:00:00,10146.0 -2015-08-10 07:00:00,10855.0 -2015-08-10 08:00:00,11756.0 -2015-08-10 09:00:00,12690.0 -2015-08-10 10:00:00,13701.0 -2015-08-10 11:00:00,14584.0 -2015-08-10 12:00:00,15550.0 -2015-08-10 13:00:00,16312.0 -2015-08-10 14:00:00,16690.0 -2015-08-10 15:00:00,16602.0 -2015-08-10 16:00:00,16551.0 -2015-08-10 17:00:00,16185.0 -2015-08-10 18:00:00,15649.0 -2015-08-10 19:00:00,15331.0 -2015-08-10 20:00:00,14966.0 -2015-08-10 21:00:00,14581.0 -2015-08-10 22:00:00,14497.0 -2015-08-10 23:00:00,13960.0 -2015-08-11 00:00:00,12856.0 -2015-08-09 01:00:00,11064.0 -2015-08-09 02:00:00,10342.0 -2015-08-09 03:00:00,9843.0 -2015-08-09 04:00:00,9419.0 -2015-08-09 05:00:00,9198.0 -2015-08-09 06:00:00,9116.0 -2015-08-09 07:00:00,9133.0 -2015-08-09 08:00:00,9114.0 -2015-08-09 09:00:00,9443.0 -2015-08-09 10:00:00,9942.0 -2015-08-09 11:00:00,10429.0 -2015-08-09 12:00:00,10888.0 -2015-08-09 13:00:00,11250.0 -2015-08-09 14:00:00,11633.0 -2015-08-09 15:00:00,12177.0 -2015-08-09 16:00:00,12779.0 -2015-08-09 17:00:00,12652.0 -2015-08-09 18:00:00,12617.0 -2015-08-09 19:00:00,12740.0 -2015-08-09 20:00:00,12716.0 -2015-08-09 21:00:00,12604.0 -2015-08-09 22:00:00,12977.0 -2015-08-09 23:00:00,12771.0 -2015-08-10 00:00:00,12149.0 -2015-08-08 01:00:00,12152.0 -2015-08-08 02:00:00,11310.0 -2015-08-08 03:00:00,10643.0 -2015-08-08 04:00:00,10209.0 -2015-08-08 05:00:00,9858.0 -2015-08-08 06:00:00,9773.0 -2015-08-08 07:00:00,9848.0 -2015-08-08 08:00:00,10012.0 -2015-08-08 09:00:00,10777.0 -2015-08-08 10:00:00,11902.0 -2015-08-08 11:00:00,13064.0 -2015-08-08 12:00:00,14090.0 -2015-08-08 13:00:00,14678.0 -2015-08-08 14:00:00,14660.0 -2015-08-08 15:00:00,14554.0 -2015-08-08 16:00:00,14280.0 -2015-08-08 17:00:00,14036.0 -2015-08-08 18:00:00,13767.0 -2015-08-08 19:00:00,13431.0 -2015-08-08 20:00:00,12982.0 -2015-08-08 21:00:00,12682.0 -2015-08-08 22:00:00,12817.0 -2015-08-08 23:00:00,12533.0 -2015-08-09 00:00:00,11819.0 -2015-08-07 01:00:00,11983.0 -2015-08-07 02:00:00,11084.0 -2015-08-07 03:00:00,10453.0 -2015-08-07 04:00:00,9991.0 -2015-08-07 05:00:00,9732.0 -2015-08-07 06:00:00,9788.0 -2015-08-07 07:00:00,10253.0 -2015-08-07 08:00:00,10853.0 -2015-08-07 09:00:00,11883.0 -2015-08-07 10:00:00,12813.0 -2015-08-07 11:00:00,13620.0 -2015-08-07 12:00:00,14397.0 -2015-08-07 13:00:00,14986.0 -2015-08-07 14:00:00,15427.0 -2015-08-07 15:00:00,15912.0 -2015-08-07 16:00:00,16226.0 -2015-08-07 17:00:00,16285.0 -2015-08-07 18:00:00,16065.0 -2015-08-07 19:00:00,15622.0 -2015-08-07 20:00:00,15026.0 -2015-08-07 21:00:00,14577.0 -2015-08-07 22:00:00,14504.0 -2015-08-07 23:00:00,14105.0 -2015-08-08 00:00:00,13175.0 -2015-08-06 01:00:00,11710.0 -2015-08-06 02:00:00,10859.0 -2015-08-06 03:00:00,10285.0 -2015-08-06 04:00:00,9907.0 -2015-08-06 05:00:00,9693.0 -2015-08-06 06:00:00,9827.0 -2015-08-06 07:00:00,10411.0 -2015-08-06 08:00:00,11086.0 -2015-08-06 09:00:00,12090.0 -2015-08-06 10:00:00,13018.0 -2015-08-06 11:00:00,13867.0 -2015-08-06 12:00:00,14640.0 -2015-08-06 13:00:00,15285.0 -2015-08-06 14:00:00,15751.0 -2015-08-06 15:00:00,16235.0 -2015-08-06 16:00:00,16453.0 -2015-08-06 17:00:00,16555.0 -2015-08-06 18:00:00,16548.0 -2015-08-06 19:00:00,16355.0 -2015-08-06 20:00:00,15668.0 -2015-08-06 21:00:00,14902.0 -2015-08-06 22:00:00,14651.0 -2015-08-06 23:00:00,14080.0 -2015-08-07 00:00:00,13058.0 -2015-08-05 01:00:00,11411.0 -2015-08-05 02:00:00,10574.0 -2015-08-05 03:00:00,9991.0 -2015-08-05 04:00:00,9597.0 -2015-08-05 05:00:00,9382.0 -2015-08-05 06:00:00,9483.0 -2015-08-05 07:00:00,9988.0 -2015-08-05 08:00:00,10650.0 -2015-08-05 09:00:00,11752.0 -2015-08-05 10:00:00,12676.0 -2015-08-05 11:00:00,13523.0 -2015-08-05 12:00:00,14269.0 -2015-08-05 13:00:00,14862.0 -2015-08-05 14:00:00,15409.0 -2015-08-05 15:00:00,15859.0 -2015-08-05 16:00:00,16125.0 -2015-08-05 17:00:00,16197.0 -2015-08-05 18:00:00,16020.0 -2015-08-05 19:00:00,15598.0 -2015-08-05 20:00:00,14853.0 -2015-08-05 21:00:00,14256.0 -2015-08-05 22:00:00,14226.0 -2015-08-05 23:00:00,13743.0 -2015-08-06 00:00:00,12745.0 -2015-08-04 01:00:00,11594.0 -2015-08-04 02:00:00,10692.0 -2015-08-04 03:00:00,10059.0 -2015-08-04 04:00:00,9609.0 -2015-08-04 05:00:00,9393.0 -2015-08-04 06:00:00,9474.0 -2015-08-04 07:00:00,9912.0 -2015-08-04 08:00:00,10629.0 -2015-08-04 09:00:00,11675.0 -2015-08-04 10:00:00,12620.0 -2015-08-04 11:00:00,13406.0 -2015-08-04 12:00:00,14131.0 -2015-08-04 13:00:00,14677.0 -2015-08-04 14:00:00,15103.0 -2015-08-04 15:00:00,15579.0 -2015-08-04 16:00:00,15956.0 -2015-08-04 17:00:00,16060.0 -2015-08-04 18:00:00,15939.0 -2015-08-04 19:00:00,15732.0 -2015-08-04 20:00:00,15154.0 -2015-08-04 21:00:00,14412.0 -2015-08-04 22:00:00,14210.0 -2015-08-04 23:00:00,13642.0 -2015-08-05 00:00:00,12564.0 -2015-08-03 01:00:00,13229.0 -2015-08-03 02:00:00,12031.0 -2015-08-03 03:00:00,11206.0 -2015-08-03 04:00:00,10693.0 -2015-08-03 05:00:00,10458.0 -2015-08-03 06:00:00,10457.0 -2015-08-03 07:00:00,10917.0 -2015-08-03 08:00:00,11663.0 -2015-08-03 09:00:00,12742.0 -2015-08-03 10:00:00,13687.0 -2015-08-03 11:00:00,14453.0 -2015-08-03 12:00:00,15170.0 -2015-08-03 13:00:00,15664.0 -2015-08-03 14:00:00,15964.0 -2015-08-03 15:00:00,16251.0 -2015-08-03 16:00:00,16482.0 -2015-08-03 17:00:00,16648.0 -2015-08-03 18:00:00,16691.0 -2015-08-03 19:00:00,16511.0 -2015-08-03 20:00:00,15858.0 -2015-08-03 21:00:00,15046.0 -2015-08-03 22:00:00,14590.0 -2015-08-03 23:00:00,13952.0 -2015-08-04 00:00:00,12801.0 -2015-08-02 01:00:00,12395.0 -2015-08-02 02:00:00,11573.0 -2015-08-02 03:00:00,10890.0 -2015-08-02 04:00:00,10401.0 -2015-08-02 05:00:00,10066.0 -2015-08-02 06:00:00,9842.0 -2015-08-02 07:00:00,9828.0 -2015-08-02 08:00:00,9790.0 -2015-08-02 09:00:00,10595.0 -2015-08-02 10:00:00,11813.0 -2015-08-02 11:00:00,13108.0 -2015-08-02 12:00:00,14273.0 -2015-08-02 13:00:00,15287.0 -2015-08-02 14:00:00,16038.0 -2015-08-02 15:00:00,16741.0 -2015-08-02 16:00:00,16890.0 -2015-08-02 17:00:00,16778.0 -2015-08-02 18:00:00,17461.0 -2015-08-02 19:00:00,17673.0 -2015-08-02 20:00:00,17449.0 -2015-08-02 21:00:00,16942.0 -2015-08-02 22:00:00,16841.0 -2015-08-02 23:00:00,16451.0 -2015-08-03 00:00:00,15181.0 -2015-08-01 01:00:00,11952.0 -2015-08-01 02:00:00,10993.0 -2015-08-01 03:00:00,10269.0 -2015-08-01 04:00:00,9714.0 -2015-08-01 05:00:00,9415.0 -2015-08-01 06:00:00,9261.0 -2015-08-01 07:00:00,9323.0 -2015-08-01 08:00:00,9413.0 -2015-08-01 09:00:00,10208.0 -2015-08-01 10:00:00,11211.0 -2015-08-01 11:00:00,12253.0 -2015-08-01 12:00:00,13142.0 -2015-08-01 13:00:00,13791.0 -2015-08-01 14:00:00,14334.0 -2015-08-01 15:00:00,14831.0 -2015-08-01 16:00:00,15316.0 -2015-08-01 17:00:00,15705.0 -2015-08-01 18:00:00,15863.0 -2015-08-01 19:00:00,15757.0 -2015-08-01 20:00:00,15294.0 -2015-08-01 21:00:00,14712.0 -2015-08-01 22:00:00,14497.0 -2015-08-01 23:00:00,14120.0 -2015-08-02 00:00:00,13280.0 -2015-07-31 01:00:00,13168.0 -2015-07-31 02:00:00,12077.0 -2015-07-31 03:00:00,11237.0 -2015-07-31 04:00:00,10653.0 -2015-07-31 05:00:00,10295.0 -2015-07-31 06:00:00,10301.0 -2015-07-31 07:00:00,10704.0 -2015-07-31 08:00:00,11421.0 -2015-07-31 09:00:00,12531.0 -2015-07-31 10:00:00,13362.0 -2015-07-31 11:00:00,14549.0 -2015-07-31 12:00:00,15764.0 -2015-07-31 13:00:00,16496.0 -2015-07-31 14:00:00,16933.0 -2015-07-31 15:00:00,17283.0 -2015-07-31 16:00:00,17475.0 -2015-07-31 17:00:00,17571.0 -2015-07-31 18:00:00,17549.0 -2015-07-31 19:00:00,17245.0 -2015-07-31 20:00:00,16531.0 -2015-07-31 21:00:00,15577.0 -2015-07-31 22:00:00,14863.0 -2015-07-31 23:00:00,14273.0 -2015-08-01 00:00:00,13096.0 -2015-07-30 01:00:00,12774.0 -2015-07-30 02:00:00,11744.0 -2015-07-30 03:00:00,10964.0 -2015-07-30 04:00:00,10441.0 -2015-07-30 05:00:00,10160.0 -2015-07-30 06:00:00,10224.0 -2015-07-30 07:00:00,10662.0 -2015-07-30 08:00:00,11379.0 -2015-07-30 09:00:00,12511.0 -2015-07-30 10:00:00,13553.0 -2015-07-30 11:00:00,14490.0 -2015-07-30 12:00:00,15454.0 -2015-07-30 13:00:00,16202.0 -2015-07-30 14:00:00,16781.0 -2015-07-30 15:00:00,17269.0 -2015-07-30 16:00:00,17665.0 -2015-07-30 17:00:00,18038.0 -2015-07-30 18:00:00,18218.0 -2015-07-30 19:00:00,18171.0 -2015-07-30 20:00:00,17621.0 -2015-07-30 21:00:00,16845.0 -2015-07-30 22:00:00,16334.0 -2015-07-30 23:00:00,15753.0 -2015-07-31 00:00:00,14512.0 -2015-07-29 01:00:00,14828.0 -2015-07-29 02:00:00,13829.0 -2015-07-29 03:00:00,13125.0 -2015-07-29 04:00:00,12546.0 -2015-07-29 05:00:00,12176.0 -2015-07-29 06:00:00,12178.0 -2015-07-29 07:00:00,12655.0 -2015-07-29 08:00:00,13466.0 -2015-07-29 09:00:00,14293.0 -2015-07-29 10:00:00,15079.0 -2015-07-29 11:00:00,15901.0 -2015-07-29 12:00:00,16849.0 -2015-07-29 13:00:00,17469.0 -2015-07-29 14:00:00,17938.0 -2015-07-29 15:00:00,18298.0 -2015-07-29 16:00:00,18519.0 -2015-07-29 17:00:00,18586.0 -2015-07-29 18:00:00,18564.0 -2015-07-29 19:00:00,18310.0 -2015-07-29 20:00:00,17662.0 -2015-07-29 21:00:00,16705.0 -2015-07-29 22:00:00,16072.0 -2015-07-29 23:00:00,15385.0 -2015-07-30 00:00:00,14081.0 -2015-07-28 01:00:00,13157.0 -2015-07-28 02:00:00,12160.0 -2015-07-28 03:00:00,11445.0 -2015-07-28 04:00:00,10891.0 -2015-07-28 05:00:00,10602.0 -2015-07-28 06:00:00,10643.0 -2015-07-28 07:00:00,11100.0 -2015-07-28 08:00:00,11974.0 -2015-07-28 09:00:00,13280.0 -2015-07-28 10:00:00,14623.0 -2015-07-28 11:00:00,15812.0 -2015-07-28 12:00:00,17023.0 -2015-07-28 13:00:00,17850.0 -2015-07-28 14:00:00,18510.0 -2015-07-28 15:00:00,19153.0 -2015-07-28 16:00:00,19547.0 -2015-07-28 17:00:00,19765.0 -2015-07-28 18:00:00,19707.0 -2015-07-28 19:00:00,19368.0 -2015-07-28 20:00:00,18728.0 -2015-07-28 21:00:00,18276.0 -2015-07-28 22:00:00,18092.0 -2015-07-28 23:00:00,17482.0 -2015-07-29 00:00:00,16157.0 -2015-07-27 01:00:00,11430.0 -2015-07-27 02:00:00,10722.0 -2015-07-27 03:00:00,10169.0 -2015-07-27 04:00:00,9927.0 -2015-07-27 05:00:00,9810.0 -2015-07-27 06:00:00,10014.0 -2015-07-27 07:00:00,10620.0 -2015-07-27 08:00:00,11560.0 -2015-07-27 09:00:00,12902.0 -2015-07-27 10:00:00,14202.0 -2015-07-27 11:00:00,15390.0 -2015-07-27 12:00:00,16415.0 -2015-07-27 13:00:00,17142.0 -2015-07-27 14:00:00,17666.0 -2015-07-27 15:00:00,18248.0 -2015-07-27 16:00:00,18615.0 -2015-07-27 17:00:00,18828.0 -2015-07-27 18:00:00,18829.0 -2015-07-27 19:00:00,18349.0 -2015-07-27 20:00:00,17489.0 -2015-07-27 21:00:00,16668.0 -2015-07-27 22:00:00,16329.0 -2015-07-27 23:00:00,15728.0 -2015-07-28 00:00:00,14481.0 -2015-07-26 01:00:00,12650.0 -2015-07-26 02:00:00,11668.0 -2015-07-26 03:00:00,10840.0 -2015-07-26 04:00:00,10259.0 -2015-07-26 05:00:00,9892.0 -2015-07-26 06:00:00,9589.0 -2015-07-26 07:00:00,9426.0 -2015-07-26 08:00:00,9456.0 -2015-07-26 09:00:00,10115.0 -2015-07-26 10:00:00,11031.0 -2015-07-26 11:00:00,11905.0 -2015-07-26 12:00:00,12423.0 -2015-07-26 13:00:00,12717.0 -2015-07-26 14:00:00,12874.0 -2015-07-26 15:00:00,13033.0 -2015-07-26 16:00:00,13240.0 -2015-07-26 17:00:00,13323.0 -2015-07-26 18:00:00,13437.0 -2015-07-26 19:00:00,13349.0 -2015-07-26 20:00:00,13240.0 -2015-07-26 21:00:00,13055.0 -2015-07-26 22:00:00,13195.0 -2015-07-26 23:00:00,12989.0 -2015-07-27 00:00:00,12301.0 -2015-07-25 01:00:00,12665.0 -2015-07-25 02:00:00,11789.0 -2015-07-25 03:00:00,11185.0 -2015-07-25 04:00:00,10703.0 -2015-07-25 05:00:00,10427.0 -2015-07-25 06:00:00,10285.0 -2015-07-25 07:00:00,10324.0 -2015-07-25 08:00:00,10576.0 -2015-07-25 09:00:00,11541.0 -2015-07-25 10:00:00,12821.0 -2015-07-25 11:00:00,14055.0 -2015-07-25 12:00:00,15292.0 -2015-07-25 13:00:00,16277.0 -2015-07-25 14:00:00,16859.0 -2015-07-25 15:00:00,17183.0 -2015-07-25 16:00:00,17518.0 -2015-07-25 17:00:00,17703.0 -2015-07-25 18:00:00,17653.0 -2015-07-25 19:00:00,17415.0 -2015-07-25 20:00:00,16770.0 -2015-07-25 21:00:00,15882.0 -2015-07-25 22:00:00,15331.0 -2015-07-25 23:00:00,14799.0 -2015-07-26 00:00:00,13777.0 -2015-07-24 01:00:00,12298.0 -2015-07-24 02:00:00,11333.0 -2015-07-24 03:00:00,10616.0 -2015-07-24 04:00:00,10150.0 -2015-07-24 05:00:00,9866.0 -2015-07-24 06:00:00,9913.0 -2015-07-24 07:00:00,10342.0 -2015-07-24 08:00:00,11155.0 -2015-07-24 09:00:00,12427.0 -2015-07-24 10:00:00,13642.0 -2015-07-24 11:00:00,14650.0 -2015-07-24 12:00:00,15516.0 -2015-07-24 13:00:00,16234.0 -2015-07-24 14:00:00,16785.0 -2015-07-24 15:00:00,17228.0 -2015-07-24 16:00:00,17238.0 -2015-07-24 17:00:00,17024.0 -2015-07-24 18:00:00,16776.0 -2015-07-24 19:00:00,16792.0 -2015-07-24 20:00:00,16443.0 -2015-07-24 21:00:00,15765.0 -2015-07-24 22:00:00,15270.0 -2015-07-24 23:00:00,14881.0 -2015-07-25 00:00:00,13786.0 -2015-07-23 01:00:00,11677.0 -2015-07-23 02:00:00,10761.0 -2015-07-23 03:00:00,10120.0 -2015-07-23 04:00:00,9685.0 -2015-07-23 05:00:00,9461.0 -2015-07-23 06:00:00,9547.0 -2015-07-23 07:00:00,9983.0 -2015-07-23 08:00:00,10803.0 -2015-07-23 09:00:00,12017.0 -2015-07-23 10:00:00,13096.0 -2015-07-23 11:00:00,14047.0 -2015-07-23 12:00:00,14920.0 -2015-07-23 13:00:00,15574.0 -2015-07-23 14:00:00,16069.0 -2015-07-23 15:00:00,16541.0 -2015-07-23 16:00:00,16845.0 -2015-07-23 17:00:00,17047.0 -2015-07-23 18:00:00,17132.0 -2015-07-23 19:00:00,16976.0 -2015-07-23 20:00:00,16413.0 -2015-07-23 21:00:00,15682.0 -2015-07-23 22:00:00,15294.0 -2015-07-23 23:00:00,14755.0 -2015-07-24 00:00:00,13546.0 -2015-07-22 01:00:00,11509.0 -2015-07-22 02:00:00,10630.0 -2015-07-22 03:00:00,10068.0 -2015-07-22 04:00:00,9683.0 -2015-07-22 05:00:00,9498.0 -2015-07-22 06:00:00,9564.0 -2015-07-22 07:00:00,9984.0 -2015-07-22 08:00:00,10798.0 -2015-07-22 09:00:00,11882.0 -2015-07-22 10:00:00,12740.0 -2015-07-22 11:00:00,13454.0 -2015-07-22 12:00:00,14198.0 -2015-07-22 13:00:00,14667.0 -2015-07-22 14:00:00,15157.0 -2015-07-22 15:00:00,15642.0 -2015-07-22 16:00:00,15903.0 -2015-07-22 17:00:00,16039.0 -2015-07-22 18:00:00,16028.0 -2015-07-22 19:00:00,15799.0 -2015-07-22 20:00:00,15227.0 -2015-07-22 21:00:00,14569.0 -2015-07-22 22:00:00,14264.0 -2015-07-22 23:00:00,13865.0 -2015-07-23 00:00:00,12852.0 -2015-07-21 01:00:00,12659.0 -2015-07-21 02:00:00,11668.0 -2015-07-21 03:00:00,10921.0 -2015-07-21 04:00:00,10371.0 -2015-07-21 05:00:00,10066.0 -2015-07-21 06:00:00,10033.0 -2015-07-21 07:00:00,10363.0 -2015-07-21 08:00:00,11208.0 -2015-07-21 09:00:00,12397.0 -2015-07-21 10:00:00,13266.0 -2015-07-21 11:00:00,13985.0 -2015-07-21 12:00:00,14542.0 -2015-07-21 13:00:00,14913.0 -2015-07-21 14:00:00,15217.0 -2015-07-21 15:00:00,15499.0 -2015-07-21 16:00:00,15762.0 -2015-07-21 17:00:00,15966.0 -2015-07-21 18:00:00,16157.0 -2015-07-21 19:00:00,16018.0 -2015-07-21 20:00:00,15482.0 -2015-07-21 21:00:00,14810.0 -2015-07-21 22:00:00,14319.0 -2015-07-21 23:00:00,13823.0 -2015-07-22 00:00:00,12705.0 -2015-07-20 01:00:00,11684.0 -2015-07-20 02:00:00,10865.0 -2015-07-20 03:00:00,10188.0 -2015-07-20 04:00:00,9822.0 -2015-07-20 05:00:00,9587.0 -2015-07-20 06:00:00,9747.0 -2015-07-20 07:00:00,10149.0 -2015-07-20 08:00:00,11047.0 -2015-07-20 09:00:00,12214.0 -2015-07-20 10:00:00,13272.0 -2015-07-20 11:00:00,14217.0 -2015-07-20 12:00:00,14926.0 -2015-07-20 13:00:00,15423.0 -2015-07-20 14:00:00,16198.0 -2015-07-20 15:00:00,16908.0 -2015-07-20 16:00:00,17350.0 -2015-07-20 17:00:00,17202.0 -2015-07-20 18:00:00,16855.0 -2015-07-20 19:00:00,16447.0 -2015-07-20 20:00:00,16191.0 -2015-07-20 21:00:00,15900.0 -2015-07-20 22:00:00,15542.0 -2015-07-20 23:00:00,15133.0 -2015-07-21 00:00:00,13964.0 -2015-07-19 01:00:00,13090.0 -2015-07-19 02:00:00,12312.0 -2015-07-19 03:00:00,11679.0 -2015-07-19 04:00:00,11149.0 -2015-07-19 05:00:00,10829.0 -2015-07-19 06:00:00,10602.0 -2015-07-19 07:00:00,10435.0 -2015-07-19 08:00:00,10483.0 -2015-07-19 09:00:00,11191.0 -2015-07-19 10:00:00,12324.0 -2015-07-19 11:00:00,13406.0 -2015-07-19 12:00:00,14492.0 -2015-07-19 13:00:00,15280.0 -2015-07-19 14:00:00,15676.0 -2015-07-19 15:00:00,15887.0 -2015-07-19 16:00:00,16051.0 -2015-07-19 17:00:00,16078.0 -2015-07-19 18:00:00,15914.0 -2015-07-19 19:00:00,15772.0 -2015-07-19 20:00:00,15250.0 -2015-07-19 21:00:00,14549.0 -2015-07-19 22:00:00,14079.0 -2015-07-19 23:00:00,13733.0 -2015-07-20 00:00:00,12766.0 -2015-07-18 01:00:00,13944.0 -2015-07-18 02:00:00,12967.0 -2015-07-18 03:00:00,12169.0 -2015-07-18 04:00:00,11571.0 -2015-07-18 05:00:00,11197.0 -2015-07-18 06:00:00,11022.0 -2015-07-18 07:00:00,10997.0 -2015-07-18 08:00:00,11368.0 -2015-07-18 09:00:00,12536.0 -2015-07-18 10:00:00,13926.0 -2015-07-18 11:00:00,14948.0 -2015-07-18 12:00:00,15509.0 -2015-07-18 13:00:00,16047.0 -2015-07-18 14:00:00,16506.0 -2015-07-18 15:00:00,17055.0 -2015-07-18 16:00:00,17377.0 -2015-07-18 17:00:00,16954.0 -2015-07-18 18:00:00,16560.0 -2015-07-18 19:00:00,16125.0 -2015-07-18 20:00:00,15829.0 -2015-07-18 21:00:00,15361.0 -2015-07-18 22:00:00,14970.0 -2015-07-18 23:00:00,14721.0 -2015-07-19 00:00:00,13937.0 -2015-07-17 01:00:00,10959.0 -2015-07-17 02:00:00,10353.0 -2015-07-17 03:00:00,9957.0 -2015-07-17 04:00:00,9691.0 -2015-07-17 05:00:00,9614.0 -2015-07-17 06:00:00,9778.0 -2015-07-17 07:00:00,10365.0 -2015-07-17 08:00:00,11306.0 -2015-07-17 09:00:00,12734.0 -2015-07-17 10:00:00,14159.0 -2015-07-17 11:00:00,15400.0 -2015-07-17 12:00:00,16561.0 -2015-07-17 13:00:00,17567.0 -2015-07-17 14:00:00,18221.0 -2015-07-17 15:00:00,18841.0 -2015-07-17 16:00:00,19302.0 -2015-07-17 17:00:00,19525.0 -2015-07-17 18:00:00,19398.0 -2015-07-17 19:00:00,18889.0 -2015-07-17 20:00:00,18153.0 -2015-07-17 21:00:00,17479.0 -2015-07-17 22:00:00,16804.0 -2015-07-17 23:00:00,16326.0 -2015-07-18 00:00:00,15196.0 -2015-07-16 01:00:00,9973.0 -2015-07-16 02:00:00,9290.0 -2015-07-16 03:00:00,8884.0 -2015-07-16 04:00:00,8612.0 -2015-07-16 05:00:00,8510.0 -2015-07-16 06:00:00,8677.0 -2015-07-16 07:00:00,9136.0 -2015-07-16 08:00:00,9942.0 -2015-07-16 09:00:00,10863.0 -2015-07-16 10:00:00,11505.0 -2015-07-16 11:00:00,11896.0 -2015-07-16 12:00:00,12223.0 -2015-07-16 13:00:00,12408.0 -2015-07-16 14:00:00,12524.0 -2015-07-16 15:00:00,12707.0 -2015-07-16 16:00:00,12823.0 -2015-07-16 17:00:00,12962.0 -2015-07-16 18:00:00,12944.0 -2015-07-16 19:00:00,12900.0 -2015-07-16 20:00:00,12682.0 -2015-07-16 21:00:00,12579.0 -2015-07-16 22:00:00,12689.0 -2015-07-16 23:00:00,12399.0 -2015-07-17 00:00:00,11727.0 -2015-07-15 01:00:00,10281.0 -2015-07-15 02:00:00,9517.0 -2015-07-15 03:00:00,9022.0 -2015-07-15 04:00:00,8712.0 -2015-07-15 05:00:00,8566.0 -2015-07-15 06:00:00,8667.0 -2015-07-15 07:00:00,9041.0 -2015-07-15 08:00:00,9784.0 -2015-07-15 09:00:00,10703.0 -2015-07-15 10:00:00,11451.0 -2015-07-15 11:00:00,12065.0 -2015-07-15 12:00:00,12599.0 -2015-07-15 13:00:00,12987.0 -2015-07-15 14:00:00,13274.0 -2015-07-15 15:00:00,13643.0 -2015-07-15 16:00:00,13914.0 -2015-07-15 17:00:00,14032.0 -2015-07-15 18:00:00,13844.0 -2015-07-15 19:00:00,13343.0 -2015-07-15 20:00:00,12630.0 -2015-07-15 21:00:00,12108.0 -2015-07-15 22:00:00,12017.0 -2015-07-15 23:00:00,11752.0 -2015-07-16 00:00:00,10894.0 -2015-07-14 01:00:00,12913.0 -2015-07-14 02:00:00,11877.0 -2015-07-14 03:00:00,11216.0 -2015-07-14 04:00:00,10740.0 -2015-07-14 05:00:00,10486.0 -2015-07-14 06:00:00,10530.0 -2015-07-14 07:00:00,10951.0 -2015-07-14 08:00:00,11959.0 -2015-07-14 09:00:00,13255.0 -2015-07-14 10:00:00,14307.0 -2015-07-14 11:00:00,15013.0 -2015-07-14 12:00:00,15455.0 -2015-07-14 13:00:00,15834.0 -2015-07-14 14:00:00,15989.0 -2015-07-14 15:00:00,16224.0 -2015-07-14 16:00:00,16419.0 -2015-07-14 17:00:00,16203.0 -2015-07-14 18:00:00,15766.0 -2015-07-14 19:00:00,15195.0 -2015-07-14 20:00:00,14462.0 -2015-07-14 21:00:00,13584.0 -2015-07-14 22:00:00,12944.0 -2015-07-14 23:00:00,12451.0 -2015-07-15 00:00:00,11365.0 -2015-07-13 01:00:00,11867.0 -2015-07-13 02:00:00,11091.0 -2015-07-13 03:00:00,10578.0 -2015-07-13 04:00:00,10194.0 -2015-07-13 05:00:00,10052.0 -2015-07-13 06:00:00,10185.0 -2015-07-13 07:00:00,10802.0 -2015-07-13 08:00:00,11729.0 -2015-07-13 09:00:00,12559.0 -2015-07-13 10:00:00,12874.0 -2015-07-13 11:00:00,13285.0 -2015-07-13 12:00:00,14119.0 -2015-07-13 13:00:00,14972.0 -2015-07-13 14:00:00,15749.0 -2015-07-13 15:00:00,16485.0 -2015-07-13 16:00:00,17319.0 -2015-07-13 17:00:00,17910.0 -2015-07-13 18:00:00,18135.0 -2015-07-13 19:00:00,18221.0 -2015-07-13 20:00:00,17956.0 -2015-07-13 21:00:00,17528.0 -2015-07-13 22:00:00,17071.0 -2015-07-13 23:00:00,16114.0 -2015-07-14 00:00:00,14405.0 -2015-07-12 01:00:00,10324.0 -2015-07-12 02:00:00,9732.0 -2015-07-12 03:00:00,9245.0 -2015-07-12 04:00:00,8888.0 -2015-07-12 05:00:00,8678.0 -2015-07-12 06:00:00,8654.0 -2015-07-12 07:00:00,8530.0 -2015-07-12 08:00:00,8488.0 -2015-07-12 09:00:00,8874.0 -2015-07-12 10:00:00,9415.0 -2015-07-12 11:00:00,10298.0 -2015-07-12 12:00:00,11231.0 -2015-07-12 13:00:00,12072.0 -2015-07-12 14:00:00,12625.0 -2015-07-12 15:00:00,13221.0 -2015-07-12 16:00:00,13681.0 -2015-07-12 17:00:00,14079.0 -2015-07-12 18:00:00,14434.0 -2015-07-12 19:00:00,14598.0 -2015-07-12 20:00:00,14471.0 -2015-07-12 21:00:00,14029.0 -2015-07-12 22:00:00,13760.0 -2015-07-12 23:00:00,13568.0 -2015-07-13 00:00:00,12761.0 -2015-07-11 01:00:00,10663.0 -2015-07-11 02:00:00,9835.0 -2015-07-11 03:00:00,9306.0 -2015-07-11 04:00:00,8860.0 -2015-07-11 05:00:00,8663.0 -2015-07-11 06:00:00,8559.0 -2015-07-11 07:00:00,8560.0 -2015-07-11 08:00:00,8733.0 -2015-07-11 09:00:00,9517.0 -2015-07-11 10:00:00,10226.0 -2015-07-11 11:00:00,11057.0 -2015-07-11 12:00:00,11643.0 -2015-07-11 13:00:00,12121.0 -2015-07-11 14:00:00,12388.0 -2015-07-11 15:00:00,12439.0 -2015-07-11 16:00:00,12257.0 -2015-07-11 17:00:00,12056.0 -2015-07-11 18:00:00,11789.0 -2015-07-11 19:00:00,11613.0 -2015-07-11 20:00:00,11402.0 -2015-07-11 21:00:00,11234.0 -2015-07-11 22:00:00,11486.0 -2015-07-11 23:00:00,11436.0 -2015-07-12 00:00:00,10944.0 -2015-07-10 01:00:00,9996.0 -2015-07-10 02:00:00,9302.0 -2015-07-10 03:00:00,8856.0 -2015-07-10 04:00:00,8614.0 -2015-07-10 05:00:00,8469.0 -2015-07-10 06:00:00,8614.0 -2015-07-10 07:00:00,9018.0 -2015-07-10 08:00:00,9719.0 -2015-07-10 09:00:00,10659.0 -2015-07-10 10:00:00,11296.0 -2015-07-10 11:00:00,11851.0 -2015-07-10 12:00:00,12427.0 -2015-07-10 13:00:00,12906.0 -2015-07-10 14:00:00,13245.0 -2015-07-10 15:00:00,13667.0 -2015-07-10 16:00:00,14062.0 -2015-07-10 17:00:00,14286.0 -2015-07-10 18:00:00,14435.0 -2015-07-10 19:00:00,14291.0 -2015-07-10 20:00:00,13783.0 -2015-07-10 21:00:00,13159.0 -2015-07-10 22:00:00,12873.0 -2015-07-10 23:00:00,12589.0 -2015-07-11 00:00:00,11642.0 -2015-07-09 01:00:00,9508.0 -2015-07-09 02:00:00,8967.0 -2015-07-09 03:00:00,8614.0 -2015-07-09 04:00:00,8396.0 -2015-07-09 05:00:00,8304.0 -2015-07-09 06:00:00,8429.0 -2015-07-09 07:00:00,8920.0 -2015-07-09 08:00:00,9677.0 -2015-07-09 09:00:00,10406.0 -2015-07-09 10:00:00,10957.0 -2015-07-09 11:00:00,11273.0 -2015-07-09 12:00:00,11550.0 -2015-07-09 13:00:00,11825.0 -2015-07-09 14:00:00,12050.0 -2015-07-09 15:00:00,12224.0 -2015-07-09 16:00:00,12372.0 -2015-07-09 17:00:00,12521.0 -2015-07-09 18:00:00,12595.0 -2015-07-09 19:00:00,12627.0 -2015-07-09 20:00:00,12222.0 -2015-07-09 21:00:00,11876.0 -2015-07-09 22:00:00,11799.0 -2015-07-09 23:00:00,11674.0 -2015-07-10 00:00:00,10873.0 -2015-07-08 01:00:00,9417.0 -2015-07-08 02:00:00,8841.0 -2015-07-08 03:00:00,8443.0 -2015-07-08 04:00:00,8255.0 -2015-07-08 05:00:00,8169.0 -2015-07-08 06:00:00,8308.0 -2015-07-08 07:00:00,8752.0 -2015-07-08 08:00:00,9414.0 -2015-07-08 09:00:00,10223.0 -2015-07-08 10:00:00,10819.0 -2015-07-08 11:00:00,11191.0 -2015-07-08 12:00:00,11499.0 -2015-07-08 13:00:00,11642.0 -2015-07-08 14:00:00,11699.0 -2015-07-08 15:00:00,11822.0 -2015-07-08 16:00:00,11789.0 -2015-07-08 17:00:00,11601.0 -2015-07-08 18:00:00,11432.0 -2015-07-08 19:00:00,11221.0 -2015-07-08 20:00:00,11010.0 -2015-07-08 21:00:00,11064.0 -2015-07-08 22:00:00,11236.0 -2015-07-08 23:00:00,10984.0 -2015-07-09 00:00:00,10232.0 -2015-07-07 01:00:00,12187.0 -2015-07-07 02:00:00,11294.0 -2015-07-07 03:00:00,10723.0 -2015-07-07 04:00:00,10354.0 -2015-07-07 05:00:00,10136.0 -2015-07-07 06:00:00,10052.0 -2015-07-07 07:00:00,10192.0 -2015-07-07 08:00:00,10757.0 -2015-07-07 09:00:00,11194.0 -2015-07-07 10:00:00,11540.0 -2015-07-07 11:00:00,11742.0 -2015-07-07 12:00:00,12110.0 -2015-07-07 13:00:00,12359.0 -2015-07-07 14:00:00,12489.0 -2015-07-07 15:00:00,12565.0 -2015-07-07 16:00:00,12543.0 -2015-07-07 17:00:00,12475.0 -2015-07-07 18:00:00,12329.0 -2015-07-07 19:00:00,12058.0 -2015-07-07 20:00:00,11615.0 -2015-07-07 21:00:00,11218.0 -2015-07-07 22:00:00,11153.0 -2015-07-07 23:00:00,11036.0 -2015-07-08 00:00:00,10271.0 -2015-07-06 01:00:00,11048.0 -2015-07-06 02:00:00,10258.0 -2015-07-06 03:00:00,9747.0 -2015-07-06 04:00:00,9411.0 -2015-07-06 05:00:00,9214.0 -2015-07-06 06:00:00,9375.0 -2015-07-06 07:00:00,9850.0 -2015-07-06 08:00:00,10775.0 -2015-07-06 09:00:00,11947.0 -2015-07-06 10:00:00,13009.0 -2015-07-06 11:00:00,13976.0 -2015-07-06 12:00:00,14966.0 -2015-07-06 13:00:00,15772.0 -2015-07-06 14:00:00,16505.0 -2015-07-06 15:00:00,16917.0 -2015-07-06 16:00:00,16346.0 -2015-07-06 17:00:00,15696.0 -2015-07-06 18:00:00,15808.0 -2015-07-06 19:00:00,16019.0 -2015-07-06 20:00:00,15630.0 -2015-07-06 21:00:00,15270.0 -2015-07-06 22:00:00,15197.0 -2015-07-06 23:00:00,14574.0 -2015-07-07 00:00:00,13386.0 -2015-07-05 01:00:00,9751.0 -2015-07-05 02:00:00,9216.0 -2015-07-05 03:00:00,8663.0 -2015-07-05 04:00:00,8316.0 -2015-07-05 05:00:00,8052.0 -2015-07-05 06:00:00,7988.0 -2015-07-05 07:00:00,7836.0 -2015-07-05 08:00:00,7845.0 -2015-07-05 09:00:00,8305.0 -2015-07-05 10:00:00,9110.0 -2015-07-05 11:00:00,9984.0 -2015-07-05 12:00:00,10921.0 -2015-07-05 13:00:00,11675.0 -2015-07-05 14:00:00,12288.0 -2015-07-05 15:00:00,12804.0 -2015-07-05 16:00:00,13176.0 -2015-07-05 17:00:00,13556.0 -2015-07-05 18:00:00,13723.0 -2015-07-05 19:00:00,13775.0 -2015-07-05 20:00:00,13463.0 -2015-07-05 21:00:00,13078.0 -2015-07-05 22:00:00,12913.0 -2015-07-05 23:00:00,12762.0 -2015-07-06 00:00:00,11954.0 -2015-07-04 01:00:00,9307.0 -2015-07-04 02:00:00,8677.0 -2015-07-04 03:00:00,8254.0 -2015-07-04 04:00:00,7944.0 -2015-07-04 05:00:00,7712.0 -2015-07-04 06:00:00,7655.0 -2015-07-04 07:00:00,7506.0 -2015-07-04 08:00:00,7535.0 -2015-07-04 09:00:00,7972.0 -2015-07-04 10:00:00,8591.0 -2015-07-04 11:00:00,9263.0 -2015-07-04 12:00:00,9904.0 -2015-07-04 13:00:00,10466.0 -2015-07-04 14:00:00,10968.0 -2015-07-04 15:00:00,11327.0 -2015-07-04 16:00:00,11638.0 -2015-07-04 17:00:00,11852.0 -2015-07-04 18:00:00,11951.0 -2015-07-04 19:00:00,11893.0 -2015-07-04 20:00:00,11533.0 -2015-07-04 21:00:00,11101.0 -2015-07-04 22:00:00,10827.0 -2015-07-04 23:00:00,10635.0 -2015-07-05 00:00:00,10279.0 -2015-07-03 01:00:00,9151.0 -2015-07-03 02:00:00,8580.0 -2015-07-03 03:00:00,8197.0 -2015-07-03 04:00:00,7971.0 -2015-07-03 05:00:00,7838.0 -2015-07-03 06:00:00,7874.0 -2015-07-03 07:00:00,7927.0 -2015-07-03 08:00:00,8060.0 -2015-07-03 09:00:00,8552.0 -2015-07-03 10:00:00,9059.0 -2015-07-03 11:00:00,9550.0 -2015-07-03 12:00:00,10006.0 -2015-07-03 13:00:00,10338.0 -2015-07-03 14:00:00,10630.0 -2015-07-03 15:00:00,10878.0 -2015-07-03 16:00:00,11090.0 -2015-07-03 17:00:00,11342.0 -2015-07-03 18:00:00,11511.0 -2015-07-03 19:00:00,11495.0 -2015-07-03 20:00:00,11209.0 -2015-07-03 21:00:00,10830.0 -2015-07-03 22:00:00,10688.0 -2015-07-03 23:00:00,10547.0 -2015-07-04 00:00:00,9958.0 -2015-07-02 01:00:00,9288.0 -2015-07-02 02:00:00,8742.0 -2015-07-02 03:00:00,8359.0 -2015-07-02 04:00:00,8127.0 -2015-07-02 05:00:00,8052.0 -2015-07-02 06:00:00,8169.0 -2015-07-02 07:00:00,8552.0 -2015-07-02 08:00:00,9218.0 -2015-07-02 09:00:00,10026.0 -2015-07-02 10:00:00,10600.0 -2015-07-02 11:00:00,10990.0 -2015-07-02 12:00:00,11306.0 -2015-07-02 13:00:00,11518.0 -2015-07-02 14:00:00,11621.0 -2015-07-02 15:00:00,11768.0 -2015-07-02 16:00:00,11728.0 -2015-07-02 17:00:00,11642.0 -2015-07-02 18:00:00,11661.0 -2015-07-02 19:00:00,11523.0 -2015-07-02 20:00:00,11063.0 -2015-07-02 21:00:00,10695.0 -2015-07-02 22:00:00,10742.0 -2015-07-02 23:00:00,10629.0 -2015-07-03 00:00:00,9953.0 -2015-07-01 01:00:00,9904.0 -2015-07-01 02:00:00,9236.0 -2015-07-01 03:00:00,8755.0 -2015-07-01 04:00:00,8491.0 -2015-07-01 05:00:00,8353.0 -2015-07-01 06:00:00,8463.0 -2015-07-01 07:00:00,8846.0 -2015-07-01 08:00:00,9546.0 -2015-07-01 09:00:00,10327.0 -2015-07-01 10:00:00,10867.0 -2015-07-01 11:00:00,11320.0 -2015-07-01 12:00:00,11719.0 -2015-07-01 13:00:00,11988.0 -2015-07-01 14:00:00,12125.0 -2015-07-01 15:00:00,12275.0 -2015-07-01 16:00:00,12276.0 -2015-07-01 17:00:00,12196.0 -2015-07-01 18:00:00,11958.0 -2015-07-01 19:00:00,11670.0 -2015-07-01 20:00:00,11156.0 -2015-07-01 21:00:00,10842.0 -2015-07-01 22:00:00,10959.0 -2015-07-01 23:00:00,10817.0 -2015-07-02 00:00:00,10077.0 -2015-06-30 01:00:00,10451.0 -2015-06-30 02:00:00,9758.0 -2015-06-30 03:00:00,9275.0 -2015-06-30 04:00:00,8972.0 -2015-06-30 05:00:00,8844.0 -2015-06-30 06:00:00,8967.0 -2015-06-30 07:00:00,9365.0 -2015-06-30 08:00:00,10156.0 -2015-06-30 09:00:00,11106.0 -2015-06-30 10:00:00,11826.0 -2015-06-30 11:00:00,12426.0 -2015-06-30 12:00:00,12994.0 -2015-06-30 13:00:00,13433.0 -2015-06-30 14:00:00,13787.0 -2015-06-30 15:00:00,14062.0 -2015-06-30 16:00:00,14192.0 -2015-06-30 17:00:00,14205.0 -2015-06-30 18:00:00,14024.0 -2015-06-30 19:00:00,13652.0 -2015-06-30 20:00:00,13016.0 -2015-06-30 21:00:00,12427.0 -2015-06-30 22:00:00,12131.0 -2015-06-30 23:00:00,11816.0 -2015-07-01 00:00:00,10899.0 -2015-06-29 01:00:00,9696.0 -2015-06-29 02:00:00,9185.0 -2015-06-29 03:00:00,8808.0 -2015-06-29 04:00:00,8634.0 -2015-06-29 05:00:00,8577.0 -2015-06-29 06:00:00,8821.0 -2015-06-29 07:00:00,9414.0 -2015-06-29 08:00:00,10278.0 -2015-06-29 09:00:00,11128.0 -2015-06-29 10:00:00,11686.0 -2015-06-29 11:00:00,12158.0 -2015-06-29 12:00:00,12664.0 -2015-06-29 13:00:00,13018.0 -2015-06-29 14:00:00,13236.0 -2015-06-29 15:00:00,13505.0 -2015-06-29 16:00:00,13519.0 -2015-06-29 17:00:00,13383.0 -2015-06-29 18:00:00,13145.0 -2015-06-29 19:00:00,12952.0 -2015-06-29 20:00:00,12710.0 -2015-06-29 21:00:00,12443.0 -2015-06-29 22:00:00,12415.0 -2015-06-29 23:00:00,12247.0 -2015-06-30 00:00:00,11413.0 -2015-06-28 01:00:00,9301.0 -2015-06-28 02:00:00,8686.0 -2015-06-28 03:00:00,8233.0 -2015-06-28 04:00:00,7972.0 -2015-06-28 05:00:00,7825.0 -2015-06-28 06:00:00,7739.0 -2015-06-28 07:00:00,7579.0 -2015-06-28 08:00:00,7647.0 -2015-06-28 09:00:00,8125.0 -2015-06-28 10:00:00,8755.0 -2015-06-28 11:00:00,9361.0 -2015-06-28 12:00:00,9954.0 -2015-06-28 13:00:00,10366.0 -2015-06-28 14:00:00,10820.0 -2015-06-28 15:00:00,11114.0 -2015-06-28 16:00:00,11260.0 -2015-06-28 17:00:00,11248.0 -2015-06-28 18:00:00,11267.0 -2015-06-28 19:00:00,11131.0 -2015-06-28 20:00:00,11028.0 -2015-06-28 21:00:00,10992.0 -2015-06-28 22:00:00,11111.0 -2015-06-28 23:00:00,10974.0 -2015-06-29 00:00:00,10366.0 -2015-06-27 01:00:00,9624.0 -2015-06-27 02:00:00,9011.0 -2015-06-27 03:00:00,8641.0 -2015-06-27 04:00:00,8322.0 -2015-06-27 05:00:00,8233.0 -2015-06-27 06:00:00,8146.0 -2015-06-27 07:00:00,8187.0 -2015-06-27 08:00:00,8362.0 -2015-06-27 09:00:00,8915.0 -2015-06-27 10:00:00,9530.0 -2015-06-27 11:00:00,10088.0 -2015-06-27 12:00:00,10403.0 -2015-06-27 13:00:00,10536.0 -2015-06-27 14:00:00,10659.0 -2015-06-27 15:00:00,10716.0 -2015-06-27 16:00:00,10789.0 -2015-06-27 17:00:00,10910.0 -2015-06-27 18:00:00,11060.0 -2015-06-27 19:00:00,11114.0 -2015-06-27 20:00:00,10968.0 -2015-06-27 21:00:00,10718.0 -2015-06-27 22:00:00,10563.0 -2015-06-27 23:00:00,10535.0 -2015-06-28 00:00:00,9955.0 -2015-06-26 01:00:00,10530.0 -2015-06-26 02:00:00,9758.0 -2015-06-26 03:00:00,9258.0 -2015-06-26 04:00:00,8923.0 -2015-06-26 05:00:00,8794.0 -2015-06-26 06:00:00,8881.0 -2015-06-26 07:00:00,9271.0 -2015-06-26 08:00:00,10051.0 -2015-06-26 09:00:00,10875.0 -2015-06-26 10:00:00,11457.0 -2015-06-26 11:00:00,11845.0 -2015-06-26 12:00:00,12173.0 -2015-06-26 13:00:00,12282.0 -2015-06-26 14:00:00,12297.0 -2015-06-26 15:00:00,12349.0 -2015-06-26 16:00:00,12197.0 -2015-06-26 17:00:00,11968.0 -2015-06-26 18:00:00,11754.0 -2015-06-26 19:00:00,11532.0 -2015-06-26 20:00:00,11277.0 -2015-06-26 21:00:00,11143.0 -2015-06-26 22:00:00,11156.0 -2015-06-26 23:00:00,10986.0 -2015-06-27 00:00:00,10355.0 -2015-06-25 01:00:00,11087.0 -2015-06-25 02:00:00,10321.0 -2015-06-25 03:00:00,9799.0 -2015-06-25 04:00:00,9522.0 -2015-06-25 05:00:00,9407.0 -2015-06-25 06:00:00,9519.0 -2015-06-25 07:00:00,10020.0 -2015-06-25 08:00:00,10889.0 -2015-06-25 09:00:00,11757.0 -2015-06-25 10:00:00,12510.0 -2015-06-25 11:00:00,13235.0 -2015-06-25 12:00:00,14160.0 -2015-06-25 13:00:00,14726.0 -2015-06-25 14:00:00,14905.0 -2015-06-25 15:00:00,14898.0 -2015-06-25 16:00:00,14714.0 -2015-06-25 17:00:00,14385.0 -2015-06-25 18:00:00,14064.0 -2015-06-25 19:00:00,13715.0 -2015-06-25 20:00:00,13214.0 -2015-06-25 21:00:00,12882.0 -2015-06-25 22:00:00,12828.0 -2015-06-25 23:00:00,12513.0 -2015-06-26 00:00:00,11585.0 -2015-06-24 01:00:00,11139.0 -2015-06-24 02:00:00,10282.0 -2015-06-24 03:00:00,9656.0 -2015-06-24 04:00:00,9248.0 -2015-06-24 05:00:00,9079.0 -2015-06-24 06:00:00,9181.0 -2015-06-24 07:00:00,9502.0 -2015-06-24 08:00:00,10460.0 -2015-06-24 09:00:00,11369.0 -2015-06-24 10:00:00,11982.0 -2015-06-24 11:00:00,12459.0 -2015-06-24 12:00:00,12995.0 -2015-06-24 13:00:00,13585.0 -2015-06-24 14:00:00,14043.0 -2015-06-24 15:00:00,14390.0 -2015-06-24 16:00:00,14607.0 -2015-06-24 17:00:00,14823.0 -2015-06-24 18:00:00,14755.0 -2015-06-24 19:00:00,14278.0 -2015-06-24 20:00:00,13744.0 -2015-06-24 21:00:00,13401.0 -2015-06-24 22:00:00,13320.0 -2015-06-24 23:00:00,13089.0 -2015-06-25 00:00:00,12127.0 -2015-06-23 01:00:00,12720.0 -2015-06-23 02:00:00,11790.0 -2015-06-23 03:00:00,11150.0 -2015-06-23 04:00:00,10733.0 -2015-06-23 05:00:00,10418.0 -2015-06-23 06:00:00,10332.0 -2015-06-23 07:00:00,10470.0 -2015-06-23 08:00:00,11333.0 -2015-06-23 09:00:00,12314.0 -2015-06-23 10:00:00,12996.0 -2015-06-23 11:00:00,13509.0 -2015-06-23 12:00:00,13993.0 -2015-06-23 13:00:00,14312.0 -2015-06-23 14:00:00,14593.0 -2015-06-23 15:00:00,14856.0 -2015-06-23 16:00:00,15092.0 -2015-06-23 17:00:00,15342.0 -2015-06-23 18:00:00,15493.0 -2015-06-23 19:00:00,15398.0 -2015-06-23 20:00:00,14914.0 -2015-06-23 21:00:00,14251.0 -2015-06-23 22:00:00,13695.0 -2015-06-23 23:00:00,13404.0 -2015-06-24 00:00:00,12342.0 -2015-06-22 01:00:00,11699.0 -2015-06-22 02:00:00,10862.0 -2015-06-22 03:00:00,10212.0 -2015-06-22 04:00:00,9827.0 -2015-06-22 05:00:00,9634.0 -2015-06-22 06:00:00,9852.0 -2015-06-22 07:00:00,10394.0 -2015-06-22 08:00:00,11618.0 -2015-06-22 09:00:00,12946.0 -2015-06-22 10:00:00,13754.0 -2015-06-22 11:00:00,14428.0 -2015-06-22 12:00:00,14739.0 -2015-06-22 13:00:00,14875.0 -2015-06-22 14:00:00,14768.0 -2015-06-22 15:00:00,14873.0 -2015-06-22 16:00:00,15422.0 -2015-06-22 17:00:00,15458.0 -2015-06-22 18:00:00,15692.0 -2015-06-22 19:00:00,15951.0 -2015-06-22 20:00:00,16023.0 -2015-06-22 21:00:00,15934.0 -2015-06-22 22:00:00,16055.0 -2015-06-22 23:00:00,15655.0 -2015-06-23 00:00:00,14181.0 -2015-06-21 01:00:00,10126.0 -2015-06-21 02:00:00,9483.0 -2015-06-21 03:00:00,9061.0 -2015-06-21 04:00:00,8720.0 -2015-06-21 05:00:00,8554.0 -2015-06-21 06:00:00,8494.0 -2015-06-21 07:00:00,8347.0 -2015-06-21 08:00:00,8363.0 -2015-06-21 09:00:00,8869.0 -2015-06-21 10:00:00,9718.0 -2015-06-21 11:00:00,10497.0 -2015-06-21 12:00:00,11488.0 -2015-06-21 13:00:00,12229.0 -2015-06-21 14:00:00,12754.0 -2015-06-21 15:00:00,13306.0 -2015-06-21 16:00:00,13778.0 -2015-06-21 17:00:00,14094.0 -2015-06-21 18:00:00,14011.0 -2015-06-21 19:00:00,13832.0 -2015-06-21 20:00:00,13712.0 -2015-06-21 21:00:00,13573.0 -2015-06-21 22:00:00,13495.0 -2015-06-21 23:00:00,13500.0 -2015-06-22 00:00:00,12814.0 -2015-06-20 01:00:00,9443.0 -2015-06-20 02:00:00,8818.0 -2015-06-20 03:00:00,8459.0 -2015-06-20 04:00:00,8195.0 -2015-06-20 05:00:00,8048.0 -2015-06-20 06:00:00,8080.0 -2015-06-20 07:00:00,8085.0 -2015-06-20 08:00:00,8311.0 -2015-06-20 09:00:00,8824.0 -2015-06-20 10:00:00,9472.0 -2015-06-20 11:00:00,9947.0 -2015-06-20 12:00:00,10349.0 -2015-06-20 13:00:00,10539.0 -2015-06-20 14:00:00,10742.0 -2015-06-20 15:00:00,10881.0 -2015-06-20 16:00:00,11171.0 -2015-06-20 17:00:00,11450.0 -2015-06-20 18:00:00,11698.0 -2015-06-20 19:00:00,11711.0 -2015-06-20 20:00:00,11611.0 -2015-06-20 21:00:00,11598.0 -2015-06-20 22:00:00,11750.0 -2015-06-20 23:00:00,11550.0 -2015-06-21 00:00:00,10861.0 -2015-06-19 01:00:00,10220.0 -2015-06-19 02:00:00,9414.0 -2015-06-19 03:00:00,8918.0 -2015-06-19 04:00:00,8565.0 -2015-06-19 05:00:00,8417.0 -2015-06-19 06:00:00,8547.0 -2015-06-19 07:00:00,8738.0 -2015-06-19 08:00:00,9478.0 -2015-06-19 09:00:00,10319.0 -2015-06-19 10:00:00,10931.0 -2015-06-19 11:00:00,11377.0 -2015-06-19 12:00:00,11708.0 -2015-06-19 13:00:00,11897.0 -2015-06-19 14:00:00,12045.0 -2015-06-19 15:00:00,12212.0 -2015-06-19 16:00:00,12209.0 -2015-06-19 17:00:00,12072.0 -2015-06-19 18:00:00,11913.0 -2015-06-19 19:00:00,11641.0 -2015-06-19 20:00:00,11240.0 -2015-06-19 21:00:00,10891.0 -2015-06-19 22:00:00,10850.0 -2015-06-19 23:00:00,10898.0 -2015-06-20 00:00:00,10216.0 -2015-06-18 01:00:00,10350.0 -2015-06-18 02:00:00,9708.0 -2015-06-18 03:00:00,9282.0 -2015-06-18 04:00:00,8999.0 -2015-06-18 05:00:00,8955.0 -2015-06-18 06:00:00,9138.0 -2015-06-18 07:00:00,9644.0 -2015-06-18 08:00:00,10794.0 -2015-06-18 09:00:00,11910.0 -2015-06-18 10:00:00,12646.0 -2015-06-18 11:00:00,13267.0 -2015-06-18 12:00:00,13998.0 -2015-06-18 13:00:00,14557.0 -2015-06-18 14:00:00,14939.0 -2015-06-18 15:00:00,15311.0 -2015-06-18 16:00:00,15420.0 -2015-06-18 17:00:00,15261.0 -2015-06-18 18:00:00,14957.0 -2015-06-18 19:00:00,14602.0 -2015-06-18 20:00:00,13916.0 -2015-06-18 21:00:00,13194.0 -2015-06-18 22:00:00,12812.0 -2015-06-18 23:00:00,12357.0 -2015-06-19 00:00:00,11302.0 -2015-06-17 01:00:00,9870.0 -2015-06-17 02:00:00,9221.0 -2015-06-17 03:00:00,8798.0 -2015-06-17 04:00:00,8517.0 -2015-06-17 05:00:00,8395.0 -2015-06-17 06:00:00,8559.0 -2015-06-17 07:00:00,8967.0 -2015-06-17 08:00:00,9758.0 -2015-06-17 09:00:00,10614.0 -2015-06-17 10:00:00,11099.0 -2015-06-17 11:00:00,11345.0 -2015-06-17 12:00:00,11614.0 -2015-06-17 13:00:00,11810.0 -2015-06-17 14:00:00,11990.0 -2015-06-17 15:00:00,12192.0 -2015-06-17 16:00:00,12259.0 -2015-06-17 17:00:00,12293.0 -2015-06-17 18:00:00,12299.0 -2015-06-17 19:00:00,12208.0 -2015-06-17 20:00:00,11931.0 -2015-06-17 21:00:00,11795.0 -2015-06-17 22:00:00,11992.0 -2015-06-17 23:00:00,11917.0 -2015-06-18 00:00:00,11192.0 -2015-06-16 01:00:00,11867.0 -2015-06-16 02:00:00,10937.0 -2015-06-16 03:00:00,10234.0 -2015-06-16 04:00:00,9781.0 -2015-06-16 05:00:00,9556.0 -2015-06-16 06:00:00,9606.0 -2015-06-16 07:00:00,9942.0 -2015-06-16 08:00:00,10843.0 -2015-06-16 09:00:00,11818.0 -2015-06-16 10:00:00,12477.0 -2015-06-16 11:00:00,12900.0 -2015-06-16 12:00:00,13282.0 -2015-06-16 13:00:00,13487.0 -2015-06-16 14:00:00,13610.0 -2015-06-16 15:00:00,13828.0 -2015-06-16 16:00:00,13933.0 -2015-06-16 17:00:00,14027.0 -2015-06-16 18:00:00,13924.0 -2015-06-16 19:00:00,13582.0 -2015-06-16 20:00:00,12886.0 -2015-06-16 21:00:00,12203.0 -2015-06-16 22:00:00,11918.0 -2015-06-16 23:00:00,11697.0 -2015-06-17 00:00:00,10836.0 -2015-06-15 01:00:00,11715.0 -2015-06-15 02:00:00,10918.0 -2015-06-15 03:00:00,10317.0 -2015-06-15 04:00:00,9990.0 -2015-06-15 05:00:00,9884.0 -2015-06-15 06:00:00,10049.0 -2015-06-15 07:00:00,10602.0 -2015-06-15 08:00:00,11736.0 -2015-06-15 09:00:00,12826.0 -2015-06-15 10:00:00,13629.0 -2015-06-15 11:00:00,14272.0 -2015-06-15 12:00:00,14821.0 -2015-06-15 13:00:00,14988.0 -2015-06-15 14:00:00,14973.0 -2015-06-15 15:00:00,15349.0 -2015-06-15 16:00:00,15831.0 -2015-06-15 17:00:00,16148.0 -2015-06-15 18:00:00,16009.0 -2015-06-15 19:00:00,15471.0 -2015-06-15 20:00:00,15052.0 -2015-06-15 21:00:00,14589.0 -2015-06-15 22:00:00,14274.0 -2015-06-15 23:00:00,13780.0 -2015-06-16 00:00:00,12882.0 -2015-06-14 01:00:00,10712.0 -2015-06-14 02:00:00,10093.0 -2015-06-14 03:00:00,9608.0 -2015-06-14 04:00:00,9214.0 -2015-06-14 05:00:00,8971.0 -2015-06-14 06:00:00,8858.0 -2015-06-14 07:00:00,8905.0 -2015-06-14 08:00:00,9020.0 -2015-06-14 09:00:00,9337.0 -2015-06-14 10:00:00,9919.0 -2015-06-14 11:00:00,10570.0 -2015-06-14 12:00:00,11292.0 -2015-06-14 13:00:00,11950.0 -2015-06-14 14:00:00,12608.0 -2015-06-14 15:00:00,13107.0 -2015-06-14 16:00:00,13434.0 -2015-06-14 17:00:00,13867.0 -2015-06-14 18:00:00,14160.0 -2015-06-14 19:00:00,14239.0 -2015-06-14 20:00:00,14042.0 -2015-06-14 21:00:00,13803.0 -2015-06-14 22:00:00,13658.0 -2015-06-14 23:00:00,13504.0 -2015-06-15 00:00:00,12664.0 -2015-06-13 01:00:00,9301.0 -2015-06-13 02:00:00,8749.0 -2015-06-13 03:00:00,8396.0 -2015-06-13 04:00:00,8149.0 -2015-06-13 05:00:00,8005.0 -2015-06-13 06:00:00,8103.0 -2015-06-13 07:00:00,8164.0 -2015-06-13 08:00:00,8354.0 -2015-06-13 09:00:00,8914.0 -2015-06-13 10:00:00,9554.0 -2015-06-13 11:00:00,10276.0 -2015-06-13 12:00:00,10867.0 -2015-06-13 13:00:00,11578.0 -2015-06-13 14:00:00,12159.0 -2015-06-13 15:00:00,12703.0 -2015-06-13 16:00:00,13144.0 -2015-06-13 17:00:00,13367.0 -2015-06-13 18:00:00,13594.0 -2015-06-13 19:00:00,13313.0 -2015-06-13 20:00:00,12873.0 -2015-06-13 21:00:00,12556.0 -2015-06-13 22:00:00,12471.0 -2015-06-13 23:00:00,12113.0 -2015-06-14 00:00:00,11507.0 -2015-06-12 01:00:00,10268.0 -2015-06-12 02:00:00,9640.0 -2015-06-12 03:00:00,9190.0 -2015-06-12 04:00:00,9005.0 -2015-06-12 05:00:00,8851.0 -2015-06-12 06:00:00,9011.0 -2015-06-12 07:00:00,9409.0 -2015-06-12 08:00:00,10372.0 -2015-06-12 09:00:00,11350.0 -2015-06-12 10:00:00,11895.0 -2015-06-12 11:00:00,12339.0 -2015-06-12 12:00:00,12560.0 -2015-06-12 13:00:00,12418.0 -2015-06-12 14:00:00,12217.0 -2015-06-12 15:00:00,12041.0 -2015-06-12 16:00:00,11794.0 -2015-06-12 17:00:00,11465.0 -2015-06-12 18:00:00,11255.0 -2015-06-12 19:00:00,10966.0 -2015-06-12 20:00:00,10748.0 -2015-06-12 21:00:00,10670.0 -2015-06-12 22:00:00,10828.0 -2015-06-12 23:00:00,10651.0 -2015-06-13 00:00:00,10009.0 -2015-06-11 01:00:00,12212.0 -2015-06-11 02:00:00,11199.0 -2015-06-11 03:00:00,10494.0 -2015-06-11 04:00:00,10017.0 -2015-06-11 05:00:00,9767.0 -2015-06-11 06:00:00,9757.0 -2015-06-11 07:00:00,10052.0 -2015-06-11 08:00:00,10883.0 -2015-06-11 09:00:00,11794.0 -2015-06-11 10:00:00,12456.0 -2015-06-11 11:00:00,12806.0 -2015-06-11 12:00:00,13163.0 -2015-06-11 13:00:00,13418.0 -2015-06-11 14:00:00,13848.0 -2015-06-11 15:00:00,13976.0 -2015-06-11 16:00:00,13889.0 -2015-06-11 17:00:00,13652.0 -2015-06-11 18:00:00,13286.0 -2015-06-11 19:00:00,12756.0 -2015-06-11 20:00:00,12281.0 -2015-06-11 21:00:00,12185.0 -2015-06-11 22:00:00,12347.0 -2015-06-11 23:00:00,12047.0 -2015-06-12 00:00:00,11194.0 -2015-06-10 01:00:00,12245.0 -2015-06-10 02:00:00,11325.0 -2015-06-10 03:00:00,10669.0 -2015-06-10 04:00:00,10198.0 -2015-06-10 05:00:00,10027.0 -2015-06-10 06:00:00,10116.0 -2015-06-10 07:00:00,10537.0 -2015-06-10 08:00:00,11741.0 -2015-06-10 09:00:00,13136.0 -2015-06-10 10:00:00,14345.0 -2015-06-10 11:00:00,15292.0 -2015-06-10 12:00:00,16165.0 -2015-06-10 13:00:00,16912.0 -2015-06-10 14:00:00,17534.0 -2015-06-10 15:00:00,18170.0 -2015-06-10 16:00:00,18489.0 -2015-06-10 17:00:00,18472.0 -2015-06-10 18:00:00,18222.0 -2015-06-10 19:00:00,17629.0 -2015-06-10 20:00:00,16655.0 -2015-06-10 21:00:00,15818.0 -2015-06-10 22:00:00,15266.0 -2015-06-10 23:00:00,14790.0 -2015-06-11 00:00:00,13589.0 -2015-06-09 01:00:00,10186.0 -2015-06-09 02:00:00,9483.0 -2015-06-09 03:00:00,9023.0 -2015-06-09 04:00:00,8714.0 -2015-06-09 05:00:00,8524.0 -2015-06-09 06:00:00,8674.0 -2015-06-09 07:00:00,9143.0 -2015-06-09 08:00:00,10022.0 -2015-06-09 09:00:00,10943.0 -2015-06-09 10:00:00,11583.0 -2015-06-09 11:00:00,12120.0 -2015-06-09 12:00:00,12737.0 -2015-06-09 13:00:00,13282.0 -2015-06-09 14:00:00,13793.0 -2015-06-09 15:00:00,14363.0 -2015-06-09 16:00:00,14892.0 -2015-06-09 17:00:00,15342.0 -2015-06-09 18:00:00,15664.0 -2015-06-09 19:00:00,15742.0 -2015-06-09 20:00:00,15430.0 -2015-06-09 21:00:00,15001.0 -2015-06-09 22:00:00,14771.0 -2015-06-09 23:00:00,14537.0 -2015-06-10 00:00:00,13478.0 -2015-06-08 01:00:00,10137.0 -2015-06-08 02:00:00,9488.0 -2015-06-08 03:00:00,9076.0 -2015-06-08 04:00:00,8780.0 -2015-06-08 05:00:00,8687.0 -2015-06-08 06:00:00,8881.0 -2015-06-08 07:00:00,9393.0 -2015-06-08 08:00:00,10422.0 -2015-06-08 09:00:00,11570.0 -2015-06-08 10:00:00,12424.0 -2015-06-08 11:00:00,13163.0 -2015-06-08 12:00:00,13875.0 -2015-06-08 13:00:00,14408.0 -2015-06-08 14:00:00,14772.0 -2015-06-08 15:00:00,15171.0 -2015-06-08 16:00:00,15346.0 -2015-06-08 17:00:00,15132.0 -2015-06-08 18:00:00,14432.0 -2015-06-08 19:00:00,13863.0 -2015-06-08 20:00:00,13511.0 -2015-06-08 21:00:00,13057.0 -2015-06-08 22:00:00,12715.0 -2015-06-08 23:00:00,12253.0 -2015-06-09 00:00:00,11297.0 -2015-06-07 01:00:00,8796.0 -2015-06-07 02:00:00,8289.0 -2015-06-07 03:00:00,7941.0 -2015-06-07 04:00:00,7716.0 -2015-06-07 05:00:00,7613.0 -2015-06-07 06:00:00,7589.0 -2015-06-07 07:00:00,7537.0 -2015-06-07 08:00:00,7615.0 -2015-06-07 09:00:00,8028.0 -2015-06-07 10:00:00,8658.0 -2015-06-07 11:00:00,9075.0 -2015-06-07 12:00:00,9432.0 -2015-06-07 13:00:00,9745.0 -2015-06-07 14:00:00,9910.0 -2015-06-07 15:00:00,10067.0 -2015-06-07 16:00:00,10368.0 -2015-06-07 17:00:00,10848.0 -2015-06-07 18:00:00,11275.0 -2015-06-07 19:00:00,11428.0 -2015-06-07 20:00:00,11423.0 -2015-06-07 21:00:00,11378.0 -2015-06-07 22:00:00,11651.0 -2015-06-07 23:00:00,11594.0 -2015-06-08 00:00:00,10909.0 -2015-06-06 01:00:00,9162.0 -2015-06-06 02:00:00,8574.0 -2015-06-06 03:00:00,8168.0 -2015-06-06 04:00:00,7913.0 -2015-06-06 05:00:00,7805.0 -2015-06-06 06:00:00,7809.0 -2015-06-06 07:00:00,7825.0 -2015-06-06 08:00:00,8030.0 -2015-06-06 09:00:00,8455.0 -2015-06-06 10:00:00,9057.0 -2015-06-06 11:00:00,9514.0 -2015-06-06 12:00:00,9872.0 -2015-06-06 13:00:00,10073.0 -2015-06-06 14:00:00,10119.0 -2015-06-06 15:00:00,10139.0 -2015-06-06 16:00:00,10202.0 -2015-06-06 17:00:00,10283.0 -2015-06-06 18:00:00,10389.0 -2015-06-06 19:00:00,10399.0 -2015-06-06 20:00:00,10140.0 -2015-06-06 21:00:00,9861.0 -2015-06-06 22:00:00,9909.0 -2015-06-06 23:00:00,9917.0 -2015-06-07 00:00:00,9395.0 -2015-06-05 01:00:00,11096.0 -2015-06-05 02:00:00,10249.0 -2015-06-05 03:00:00,9627.0 -2015-06-05 04:00:00,9184.0 -2015-06-05 05:00:00,8956.0 -2015-06-05 06:00:00,9002.0 -2015-06-05 07:00:00,9332.0 -2015-06-05 08:00:00,10045.0 -2015-06-05 09:00:00,10765.0 -2015-06-05 10:00:00,11161.0 -2015-06-05 11:00:00,11472.0 -2015-06-05 12:00:00,11678.0 -2015-06-05 13:00:00,11808.0 -2015-06-05 14:00:00,11914.0 -2015-06-05 15:00:00,12014.0 -2015-06-05 16:00:00,11960.0 -2015-06-05 17:00:00,11831.0 -2015-06-05 18:00:00,11613.0 -2015-06-05 19:00:00,11356.0 -2015-06-05 20:00:00,10929.0 -2015-06-05 21:00:00,10580.0 -2015-06-05 22:00:00,10627.0 -2015-06-05 23:00:00,10577.0 -2015-06-06 00:00:00,9920.0 -2015-06-04 01:00:00,9808.0 -2015-06-04 02:00:00,9109.0 -2015-06-04 03:00:00,8688.0 -2015-06-04 04:00:00,8413.0 -2015-06-04 05:00:00,8296.0 -2015-06-04 06:00:00,8460.0 -2015-06-04 07:00:00,8902.0 -2015-06-04 08:00:00,9863.0 -2015-06-04 09:00:00,10804.0 -2015-06-04 10:00:00,11494.0 -2015-06-04 11:00:00,12019.0 -2015-06-04 12:00:00,12513.0 -2015-06-04 13:00:00,12831.0 -2015-06-04 14:00:00,13096.0 -2015-06-04 15:00:00,13515.0 -2015-06-04 16:00:00,13879.0 -2015-06-04 17:00:00,14128.0 -2015-06-04 18:00:00,14233.0 -2015-06-04 19:00:00,14071.0 -2015-06-04 20:00:00,13737.0 -2015-06-04 21:00:00,13473.0 -2015-06-04 22:00:00,13483.0 -2015-06-04 23:00:00,13238.0 -2015-06-05 00:00:00,12220.0 -2015-06-03 01:00:00,9197.0 -2015-06-03 02:00:00,8614.0 -2015-06-03 03:00:00,8266.0 -2015-06-03 04:00:00,8086.0 -2015-06-03 05:00:00,7995.0 -2015-06-03 06:00:00,8173.0 -2015-06-03 07:00:00,8545.0 -2015-06-03 08:00:00,9411.0 -2015-06-03 09:00:00,10334.0 -2015-06-03 10:00:00,10930.0 -2015-06-03 11:00:00,11311.0 -2015-06-03 12:00:00,11691.0 -2015-06-03 13:00:00,11911.0 -2015-06-03 14:00:00,12072.0 -2015-06-03 15:00:00,12338.0 -2015-06-03 16:00:00,12493.0 -2015-06-03 17:00:00,12574.0 -2015-06-03 18:00:00,12608.0 -2015-06-03 19:00:00,12460.0 -2015-06-03 20:00:00,12040.0 -2015-06-03 21:00:00,11796.0 -2015-06-03 22:00:00,11819.0 -2015-06-03 23:00:00,11643.0 -2015-06-04 00:00:00,10786.0 -2015-06-02 01:00:00,8913.0 -2015-06-02 02:00:00,8429.0 -2015-06-02 03:00:00,8131.0 -2015-06-02 04:00:00,7948.0 -2015-06-02 05:00:00,7918.0 -2015-06-02 06:00:00,8130.0 -2015-06-02 07:00:00,8502.0 -2015-06-02 08:00:00,9343.0 -2015-06-02 09:00:00,10115.0 -2015-06-02 10:00:00,10587.0 -2015-06-02 11:00:00,10904.0 -2015-06-02 12:00:00,11181.0 -2015-06-02 13:00:00,11275.0 -2015-06-02 14:00:00,11362.0 -2015-06-02 15:00:00,11462.0 -2015-06-02 16:00:00,11484.0 -2015-06-02 17:00:00,11466.0 -2015-06-02 18:00:00,11427.0 -2015-06-02 19:00:00,11320.0 -2015-06-02 20:00:00,11013.0 -2015-06-02 21:00:00,10805.0 -2015-06-02 22:00:00,10961.0 -2015-06-02 23:00:00,10847.0 -2015-06-03 00:00:00,10027.0 -2015-06-01 01:00:00,8422.0 -2015-06-01 02:00:00,8042.0 -2015-06-01 03:00:00,7846.0 -2015-06-01 04:00:00,7711.0 -2015-06-01 05:00:00,7746.0 -2015-06-01 06:00:00,7922.0 -2015-06-01 07:00:00,8466.0 -2015-06-01 08:00:00,9371.0 -2015-06-01 09:00:00,10145.0 -2015-06-01 10:00:00,10505.0 -2015-06-01 11:00:00,10699.0 -2015-06-01 12:00:00,10855.0 -2015-06-01 13:00:00,10900.0 -2015-06-01 14:00:00,10914.0 -2015-06-01 15:00:00,10977.0 -2015-06-01 16:00:00,10950.0 -2015-06-01 17:00:00,10820.0 -2015-06-01 18:00:00,10723.0 -2015-06-01 19:00:00,10570.0 -2015-06-01 20:00:00,10323.0 -2015-06-01 21:00:00,10257.0 -2015-06-01 22:00:00,10504.0 -2015-06-01 23:00:00,10408.0 -2015-06-02 00:00:00,9683.0 -2015-05-31 01:00:00,8643.0 -2015-05-31 02:00:00,8168.0 -2015-05-31 03:00:00,7890.0 -2015-05-31 04:00:00,7657.0 -2015-05-31 05:00:00,7556.0 -2015-05-31 06:00:00,7536.0 -2015-05-31 07:00:00,7501.0 -2015-05-31 08:00:00,7545.0 -2015-05-31 09:00:00,7855.0 -2015-05-31 10:00:00,8247.0 -2015-05-31 11:00:00,8557.0 -2015-05-31 12:00:00,8733.0 -2015-05-31 13:00:00,8817.0 -2015-05-31 14:00:00,8830.0 -2015-05-31 15:00:00,8827.0 -2015-05-31 16:00:00,8785.0 -2015-05-31 17:00:00,8752.0 -2015-05-31 18:00:00,8782.0 -2015-05-31 19:00:00,8824.0 -2015-05-31 20:00:00,8882.0 -2015-05-31 21:00:00,9021.0 -2015-05-31 22:00:00,9409.0 -2015-05-31 23:00:00,9421.0 -2015-06-01 00:00:00,8948.0 -2015-05-30 01:00:00,11433.0 -2015-05-30 02:00:00,10573.0 -2015-05-30 03:00:00,9978.0 -2015-05-30 04:00:00,9572.0 -2015-05-30 05:00:00,9326.0 -2015-05-30 06:00:00,9302.0 -2015-05-30 07:00:00,9387.0 -2015-05-30 08:00:00,9560.0 -2015-05-30 09:00:00,10083.0 -2015-05-30 10:00:00,10657.0 -2015-05-30 11:00:00,11036.0 -2015-05-30 12:00:00,11130.0 -2015-05-30 13:00:00,10847.0 -2015-05-30 14:00:00,10517.0 -2015-05-30 15:00:00,10044.0 -2015-05-30 16:00:00,9756.0 -2015-05-30 17:00:00,9620.0 -2015-05-30 18:00:00,9555.0 -2015-05-30 19:00:00,9500.0 -2015-05-30 20:00:00,9422.0 -2015-05-30 21:00:00,9518.0 -2015-05-30 22:00:00,9745.0 -2015-05-30 23:00:00,9661.0 -2015-05-31 00:00:00,9200.0 -2015-05-29 01:00:00,10633.0 -2015-05-29 02:00:00,9839.0 -2015-05-29 03:00:00,9340.0 -2015-05-29 04:00:00,9093.0 -2015-05-29 05:00:00,8953.0 -2015-05-29 06:00:00,9140.0 -2015-05-29 07:00:00,9636.0 -2015-05-29 08:00:00,10636.0 -2015-05-29 09:00:00,11789.0 -2015-05-29 10:00:00,12767.0 -2015-05-29 11:00:00,13558.0 -2015-05-29 12:00:00,14093.0 -2015-05-29 13:00:00,14158.0 -2015-05-29 14:00:00,14321.0 -2015-05-29 15:00:00,14716.0 -2015-05-29 16:00:00,14800.0 -2015-05-29 17:00:00,14791.0 -2015-05-29 18:00:00,14659.0 -2015-05-29 19:00:00,14483.0 -2015-05-29 20:00:00,14150.0 -2015-05-29 21:00:00,13698.0 -2015-05-29 22:00:00,13705.0 -2015-05-29 23:00:00,13423.0 -2015-05-30 00:00:00,12487.0 -2015-05-28 01:00:00,9951.0 -2015-05-28 02:00:00,9257.0 -2015-05-28 03:00:00,8858.0 -2015-05-28 04:00:00,8591.0 -2015-05-28 05:00:00,8453.0 -2015-05-28 06:00:00,8608.0 -2015-05-28 07:00:00,8997.0 -2015-05-28 08:00:00,9916.0 -2015-05-28 09:00:00,11017.0 -2015-05-28 10:00:00,11709.0 -2015-05-28 11:00:00,12337.0 -2015-05-28 12:00:00,12927.0 -2015-05-28 13:00:00,13316.0 -2015-05-28 14:00:00,13649.0 -2015-05-28 15:00:00,14088.0 -2015-05-28 16:00:00,14343.0 -2015-05-28 17:00:00,14579.0 -2015-05-28 18:00:00,14644.0 -2015-05-28 19:00:00,14457.0 -2015-05-28 20:00:00,13976.0 -2015-05-28 21:00:00,13397.0 -2015-05-28 22:00:00,13196.0 -2015-05-28 23:00:00,12786.0 -2015-05-29 00:00:00,11747.0 -2015-05-27 01:00:00,10250.0 -2015-05-27 02:00:00,9618.0 -2015-05-27 03:00:00,9197.0 -2015-05-27 04:00:00,8899.0 -2015-05-27 05:00:00,8757.0 -2015-05-27 06:00:00,8911.0 -2015-05-27 07:00:00,9361.0 -2015-05-27 08:00:00,10398.0 -2015-05-27 09:00:00,11374.0 -2015-05-27 10:00:00,11955.0 -2015-05-27 11:00:00,12302.0 -2015-05-27 12:00:00,12650.0 -2015-05-27 13:00:00,12794.0 -2015-05-27 14:00:00,12833.0 -2015-05-27 15:00:00,12980.0 -2015-05-27 16:00:00,12953.0 -2015-05-27 17:00:00,12844.0 -2015-05-27 18:00:00,12905.0 -2015-05-27 19:00:00,12900.0 -2015-05-27 20:00:00,12587.0 -2015-05-27 21:00:00,12227.0 -2015-05-27 22:00:00,12264.0 -2015-05-27 23:00:00,11866.0 -2015-05-28 00:00:00,10944.0 -2015-05-26 01:00:00,9759.0 -2015-05-26 02:00:00,9131.0 -2015-05-26 03:00:00,8729.0 -2015-05-26 04:00:00,8456.0 -2015-05-26 05:00:00,8398.0 -2015-05-26 06:00:00,8622.0 -2015-05-26 07:00:00,9305.0 -2015-05-26 08:00:00,10424.0 -2015-05-26 09:00:00,11467.0 -2015-05-26 10:00:00,12073.0 -2015-05-26 11:00:00,12489.0 -2015-05-26 12:00:00,12813.0 -2015-05-26 13:00:00,12976.0 -2015-05-26 14:00:00,13002.0 -2015-05-26 15:00:00,13016.0 -2015-05-26 16:00:00,13155.0 -2015-05-26 17:00:00,13295.0 -2015-05-26 18:00:00,13319.0 -2015-05-26 19:00:00,13149.0 -2015-05-26 20:00:00,12710.0 -2015-05-26 21:00:00,12353.0 -2015-05-26 22:00:00,12363.0 -2015-05-26 23:00:00,12089.0 -2015-05-27 00:00:00,11177.0 -2015-05-25 01:00:00,8979.0 -2015-05-25 02:00:00,8506.0 -2015-05-25 03:00:00,8252.0 -2015-05-25 04:00:00,8053.0 -2015-05-25 05:00:00,7969.0 -2015-05-25 06:00:00,7940.0 -2015-05-25 07:00:00,8095.0 -2015-05-25 08:00:00,8129.0 -2015-05-25 09:00:00,8530.0 -2015-05-25 10:00:00,8974.0 -2015-05-25 11:00:00,9515.0 -2015-05-25 12:00:00,9841.0 -2015-05-25 13:00:00,10138.0 -2015-05-25 14:00:00,10334.0 -2015-05-25 15:00:00,10490.0 -2015-05-25 16:00:00,10687.0 -2015-05-25 17:00:00,10866.0 -2015-05-25 18:00:00,11041.0 -2015-05-25 19:00:00,11197.0 -2015-05-25 20:00:00,11023.0 -2015-05-25 21:00:00,10985.0 -2015-05-25 22:00:00,11318.0 -2015-05-25 23:00:00,11273.0 -2015-05-26 00:00:00,10563.0 -2015-05-24 01:00:00,8933.0 -2015-05-24 02:00:00,8407.0 -2015-05-24 03:00:00,8015.0 -2015-05-24 04:00:00,7761.0 -2015-05-24 05:00:00,7613.0 -2015-05-24 06:00:00,7561.0 -2015-05-24 07:00:00,7495.0 -2015-05-24 08:00:00,7484.0 -2015-05-24 09:00:00,7786.0 -2015-05-24 10:00:00,8259.0 -2015-05-24 11:00:00,8643.0 -2015-05-24 12:00:00,8989.0 -2015-05-24 13:00:00,9202.0 -2015-05-24 14:00:00,9289.0 -2015-05-24 15:00:00,9367.0 -2015-05-24 16:00:00,9405.0 -2015-05-24 17:00:00,9462.0 -2015-05-24 18:00:00,9462.0 -2015-05-24 19:00:00,9450.0 -2015-05-24 20:00:00,9430.0 -2015-05-24 21:00:00,9566.0 -2015-05-24 22:00:00,9830.0 -2015-05-24 23:00:00,9817.0 -2015-05-25 00:00:00,9430.0 -2015-05-23 01:00:00,8835.0 -2015-05-23 02:00:00,8331.0 -2015-05-23 03:00:00,7932.0 -2015-05-23 04:00:00,7797.0 -2015-05-23 05:00:00,7654.0 -2015-05-23 06:00:00,7697.0 -2015-05-23 07:00:00,7606.0 -2015-05-23 08:00:00,7788.0 -2015-05-23 09:00:00,8239.0 -2015-05-23 10:00:00,8790.0 -2015-05-23 11:00:00,9209.0 -2015-05-23 12:00:00,9481.0 -2015-05-23 13:00:00,9633.0 -2015-05-23 14:00:00,9763.0 -2015-05-23 15:00:00,9798.0 -2015-05-23 16:00:00,9871.0 -2015-05-23 17:00:00,10025.0 -2015-05-23 18:00:00,10138.0 -2015-05-23 19:00:00,10216.0 -2015-05-23 20:00:00,10141.0 -2015-05-23 21:00:00,9960.0 -2015-05-23 22:00:00,10176.0 -2015-05-23 23:00:00,10067.0 -2015-05-24 00:00:00,9531.0 -2015-05-22 01:00:00,9118.0 -2015-05-22 02:00:00,8625.0 -2015-05-22 03:00:00,8266.0 -2015-05-22 04:00:00,8061.0 -2015-05-22 05:00:00,7983.0 -2015-05-22 06:00:00,8139.0 -2015-05-22 07:00:00,8521.0 -2015-05-22 08:00:00,9331.0 -2015-05-22 09:00:00,10082.0 -2015-05-22 10:00:00,10532.0 -2015-05-22 11:00:00,10788.0 -2015-05-22 12:00:00,10978.0 -2015-05-22 13:00:00,11047.0 -2015-05-22 14:00:00,11063.0 -2015-05-22 15:00:00,11105.0 -2015-05-22 16:00:00,11059.0 -2015-05-22 17:00:00,10929.0 -2015-05-22 18:00:00,10727.0 -2015-05-22 19:00:00,10442.0 -2015-05-22 20:00:00,10129.0 -2015-05-22 21:00:00,10040.0 -2015-05-22 22:00:00,10359.0 -2015-05-22 23:00:00,10200.0 -2015-05-23 00:00:00,9550.0 -2015-05-21 01:00:00,9285.0 -2015-05-21 02:00:00,8817.0 -2015-05-21 03:00:00,8521.0 -2015-05-21 04:00:00,8321.0 -2015-05-21 05:00:00,8278.0 -2015-05-21 06:00:00,8493.0 -2015-05-21 07:00:00,9081.0 -2015-05-21 08:00:00,9944.0 -2015-05-21 09:00:00,10661.0 -2015-05-21 10:00:00,10929.0 -2015-05-21 11:00:00,10997.0 -2015-05-21 12:00:00,11082.0 -2015-05-21 13:00:00,11123.0 -2015-05-21 14:00:00,11153.0 -2015-05-21 15:00:00,11236.0 -2015-05-21 16:00:00,11141.0 -2015-05-21 17:00:00,11080.0 -2015-05-21 18:00:00,10989.0 -2015-05-21 19:00:00,10848.0 -2015-05-21 20:00:00,10588.0 -2015-05-21 21:00:00,10485.0 -2015-05-21 22:00:00,10836.0 -2015-05-21 23:00:00,10654.0 -2015-05-22 00:00:00,9928.0 -2015-05-20 01:00:00,9141.0 -2015-05-20 02:00:00,8685.0 -2015-05-20 03:00:00,8354.0 -2015-05-20 04:00:00,8190.0 -2015-05-20 05:00:00,8116.0 -2015-05-20 06:00:00,8347.0 -2015-05-20 07:00:00,8876.0 -2015-05-20 08:00:00,9751.0 -2015-05-20 09:00:00,10513.0 -2015-05-20 10:00:00,10773.0 -2015-05-20 11:00:00,10900.0 -2015-05-20 12:00:00,11020.0 -2015-05-20 13:00:00,11070.0 -2015-05-20 14:00:00,11058.0 -2015-05-20 15:00:00,11152.0 -2015-05-20 16:00:00,11062.0 -2015-05-20 17:00:00,10992.0 -2015-05-20 18:00:00,11003.0 -2015-05-20 19:00:00,10931.0 -2015-05-20 20:00:00,10783.0 -2015-05-20 21:00:00,10766.0 -2015-05-20 22:00:00,11084.0 -2015-05-20 23:00:00,10746.0 -2015-05-21 00:00:00,10011.0 -2015-05-19 01:00:00,9223.0 -2015-05-19 02:00:00,8672.0 -2015-05-19 03:00:00,8322.0 -2015-05-19 04:00:00,8083.0 -2015-05-19 05:00:00,8020.0 -2015-05-19 06:00:00,8205.0 -2015-05-19 07:00:00,8717.0 -2015-05-19 08:00:00,9555.0 -2015-05-19 09:00:00,10297.0 -2015-05-19 10:00:00,10640.0 -2015-05-19 11:00:00,10821.0 -2015-05-19 12:00:00,10936.0 -2015-05-19 13:00:00,10962.0 -2015-05-19 14:00:00,10974.0 -2015-05-19 15:00:00,10982.0 -2015-05-19 16:00:00,10867.0 -2015-05-19 17:00:00,10701.0 -2015-05-19 18:00:00,10587.0 -2015-05-19 19:00:00,10476.0 -2015-05-19 20:00:00,10221.0 -2015-05-19 21:00:00,10256.0 -2015-05-19 22:00:00,10773.0 -2015-05-19 23:00:00,10543.0 -2015-05-20 00:00:00,9883.0 -2015-05-18 01:00:00,10405.0 -2015-05-18 02:00:00,9742.0 -2015-05-18 03:00:00,9349.0 -2015-05-18 04:00:00,9056.0 -2015-05-18 05:00:00,8988.0 -2015-05-18 06:00:00,9143.0 -2015-05-18 07:00:00,9631.0 -2015-05-18 08:00:00,10723.0 -2015-05-18 09:00:00,11842.0 -2015-05-18 10:00:00,12447.0 -2015-05-18 11:00:00,12899.0 -2015-05-18 12:00:00,13384.0 -2015-05-18 13:00:00,13735.0 -2015-05-18 14:00:00,13847.0 -2015-05-18 15:00:00,13960.0 -2015-05-18 16:00:00,13852.0 -2015-05-18 17:00:00,13688.0 -2015-05-18 18:00:00,13384.0 -2015-05-18 19:00:00,12876.0 -2015-05-18 20:00:00,12077.0 -2015-05-18 21:00:00,11504.0 -2015-05-18 22:00:00,11436.0 -2015-05-18 23:00:00,10995.0 -2015-05-19 00:00:00,10065.0 -2015-05-17 01:00:00,10101.0 -2015-05-17 02:00:00,9440.0 -2015-05-17 03:00:00,9283.0 -2015-05-17 04:00:00,8980.0 -2015-05-17 05:00:00,8441.0 -2015-05-17 06:00:00,8408.0 -2015-05-17 07:00:00,8372.0 -2015-05-17 08:00:00,8441.0 -2015-05-17 09:00:00,8787.0 -2015-05-17 10:00:00,9322.0 -2015-05-17 11:00:00,9814.0 -2015-05-17 12:00:00,10303.0 -2015-05-17 13:00:00,10798.0 -2015-05-17 14:00:00,11175.0 -2015-05-17 15:00:00,11728.0 -2015-05-17 16:00:00,12152.0 -2015-05-17 17:00:00,12381.0 -2015-05-17 18:00:00,12175.0 -2015-05-17 19:00:00,12245.0 -2015-05-17 20:00:00,12263.0 -2015-05-17 21:00:00,12004.0 -2015-05-17 22:00:00,12518.0 -2015-05-17 23:00:00,12342.0 -2015-05-18 00:00:00,11214.0 -2015-05-16 01:00:00,9821.0 -2015-05-16 02:00:00,9141.0 -2015-05-16 03:00:00,8755.0 -2015-05-16 04:00:00,8457.0 -2015-05-16 05:00:00,8341.0 -2015-05-16 06:00:00,8271.0 -2015-05-16 07:00:00,8433.0 -2015-05-16 08:00:00,8635.0 -2015-05-16 09:00:00,9148.0 -2015-05-16 10:00:00,9759.0 -2015-05-16 11:00:00,10254.0 -2015-05-16 12:00:00,10590.0 -2015-05-16 13:00:00,10888.0 -2015-05-16 14:00:00,11002.0 -2015-05-16 15:00:00,11059.0 -2015-05-16 16:00:00,11244.0 -2015-05-16 17:00:00,11477.0 -2015-05-16 18:00:00,11695.0 -2015-05-16 19:00:00,11680.0 -2015-05-16 20:00:00,11522.0 -2015-05-16 21:00:00,11336.0 -2015-05-16 22:00:00,11646.0 -2015-05-16 23:00:00,11476.0 -2015-05-17 00:00:00,10862.0 -2015-05-15 01:00:00,9108.0 -2015-05-15 02:00:00,8598.0 -2015-05-15 03:00:00,8289.0 -2015-05-15 04:00:00,8094.0 -2015-05-15 05:00:00,8040.0 -2015-05-15 06:00:00,8243.0 -2015-05-15 07:00:00,8773.0 -2015-05-15 08:00:00,9615.0 -2015-05-15 09:00:00,10497.0 -2015-05-15 10:00:00,11002.0 -2015-05-15 11:00:00,11341.0 -2015-05-15 12:00:00,11609.0 -2015-05-15 13:00:00,11722.0 -2015-05-15 14:00:00,11839.0 -2015-05-15 15:00:00,11969.0 -2015-05-15 16:00:00,11963.0 -2015-05-15 17:00:00,11934.0 -2015-05-15 18:00:00,11891.0 -2015-05-15 19:00:00,11729.0 -2015-05-15 20:00:00,11408.0 -2015-05-15 21:00:00,11309.0 -2015-05-15 22:00:00,11613.0 -2015-05-15 23:00:00,11344.0 -2015-05-16 00:00:00,10593.0 -2015-05-14 01:00:00,9181.0 -2015-05-14 02:00:00,8722.0 -2015-05-14 03:00:00,8458.0 -2015-05-14 04:00:00,8269.0 -2015-05-14 05:00:00,8240.0 -2015-05-14 06:00:00,8431.0 -2015-05-14 07:00:00,8957.0 -2015-05-14 08:00:00,9776.0 -2015-05-14 09:00:00,10508.0 -2015-05-14 10:00:00,10819.0 -2015-05-14 11:00:00,10967.0 -2015-05-14 12:00:00,11151.0 -2015-05-14 13:00:00,11198.0 -2015-05-14 14:00:00,11195.0 -2015-05-14 15:00:00,11235.0 -2015-05-14 16:00:00,11175.0 -2015-05-14 17:00:00,11037.0 -2015-05-14 18:00:00,10912.0 -2015-05-14 19:00:00,10748.0 -2015-05-14 20:00:00,10698.0 -2015-05-14 21:00:00,10821.0 -2015-05-14 22:00:00,10991.0 -2015-05-14 23:00:00,10593.0 -2015-05-15 00:00:00,9847.0 -2015-05-13 01:00:00,9168.0 -2015-05-13 02:00:00,8701.0 -2015-05-13 03:00:00,8466.0 -2015-05-13 04:00:00,8309.0 -2015-05-13 05:00:00,8297.0 -2015-05-13 06:00:00,8539.0 -2015-05-13 07:00:00,9091.0 -2015-05-13 08:00:00,9936.0 -2015-05-13 09:00:00,10556.0 -2015-05-13 10:00:00,10828.0 -2015-05-13 11:00:00,10964.0 -2015-05-13 12:00:00,10979.0 -2015-05-13 13:00:00,10977.0 -2015-05-13 14:00:00,11004.0 -2015-05-13 15:00:00,10996.0 -2015-05-13 16:00:00,10909.0 -2015-05-13 17:00:00,10751.0 -2015-05-13 18:00:00,10639.0 -2015-05-13 19:00:00,10479.0 -2015-05-13 20:00:00,10300.0 -2015-05-13 21:00:00,10347.0 -2015-05-13 22:00:00,10880.0 -2015-05-13 23:00:00,10651.0 -2015-05-14 00:00:00,9888.0 -2015-05-12 01:00:00,9142.0 -2015-05-12 02:00:00,8654.0 -2015-05-12 03:00:00,8380.0 -2015-05-12 04:00:00,8230.0 -2015-05-12 05:00:00,8148.0 -2015-05-12 06:00:00,8383.0 -2015-05-12 07:00:00,8972.0 -2015-05-12 08:00:00,9809.0 -2015-05-12 09:00:00,10575.0 -2015-05-12 10:00:00,10922.0 -2015-05-12 11:00:00,11062.0 -2015-05-12 12:00:00,11165.0 -2015-05-12 13:00:00,11144.0 -2015-05-12 14:00:00,11110.0 -2015-05-12 15:00:00,11114.0 -2015-05-12 16:00:00,11016.0 -2015-05-12 17:00:00,10832.0 -2015-05-12 18:00:00,10703.0 -2015-05-12 19:00:00,10570.0 -2015-05-12 20:00:00,10390.0 -2015-05-12 21:00:00,10561.0 -2015-05-12 22:00:00,10986.0 -2015-05-12 23:00:00,10660.0 -2015-05-13 00:00:00,9989.0 -2015-05-11 01:00:00,8467.0 -2015-05-11 02:00:00,8123.0 -2015-05-11 03:00:00,7867.0 -2015-05-11 04:00:00,7840.0 -2015-05-11 05:00:00,7837.0 -2015-05-11 06:00:00,8112.0 -2015-05-11 07:00:00,8723.0 -2015-05-11 08:00:00,9729.0 -2015-05-11 09:00:00,10764.0 -2015-05-11 10:00:00,11243.0 -2015-05-11 11:00:00,11457.0 -2015-05-11 12:00:00,11638.0 -2015-05-11 13:00:00,11734.0 -2015-05-11 14:00:00,11767.0 -2015-05-11 15:00:00,11795.0 -2015-05-11 16:00:00,11679.0 -2015-05-11 17:00:00,11522.0 -2015-05-11 18:00:00,11425.0 -2015-05-11 19:00:00,11201.0 -2015-05-11 20:00:00,10854.0 -2015-05-11 21:00:00,10740.0 -2015-05-11 22:00:00,11055.0 -2015-05-11 23:00:00,10658.0 -2015-05-12 00:00:00,9875.0 -2015-05-10 01:00:00,8659.0 -2015-05-10 02:00:00,8165.0 -2015-05-10 03:00:00,7872.0 -2015-05-10 04:00:00,7669.0 -2015-05-10 05:00:00,7601.0 -2015-05-10 06:00:00,7583.0 -2015-05-10 07:00:00,7665.0 -2015-05-10 08:00:00,7688.0 -2015-05-10 09:00:00,7952.0 -2015-05-10 10:00:00,8426.0 -2015-05-10 11:00:00,8713.0 -2015-05-10 12:00:00,8910.0 -2015-05-10 13:00:00,8964.0 -2015-05-10 14:00:00,8921.0 -2015-05-10 15:00:00,8838.0 -2015-05-10 16:00:00,8791.0 -2015-05-10 17:00:00,8753.0 -2015-05-10 18:00:00,8782.0 -2015-05-10 19:00:00,8781.0 -2015-05-10 20:00:00,8883.0 -2015-05-10 21:00:00,9170.0 -2015-05-10 22:00:00,9682.0 -2015-05-10 23:00:00,9526.0 -2015-05-11 00:00:00,9051.0 -2015-05-09 01:00:00,10108.0 -2015-05-09 02:00:00,9451.0 -2015-05-09 03:00:00,9048.0 -2015-05-09 04:00:00,8763.0 -2015-05-09 05:00:00,8588.0 -2015-05-09 06:00:00,8576.0 -2015-05-09 07:00:00,8655.0 -2015-05-09 08:00:00,8728.0 -2015-05-09 09:00:00,9036.0 -2015-05-09 10:00:00,9483.0 -2015-05-09 11:00:00,9787.0 -2015-05-09 12:00:00,9850.0 -2015-05-09 13:00:00,9907.0 -2015-05-09 14:00:00,9970.0 -2015-05-09 15:00:00,9842.0 -2015-05-09 16:00:00,9759.0 -2015-05-09 17:00:00,9583.0 -2015-05-09 18:00:00,9517.0 -2015-05-09 19:00:00,9369.0 -2015-05-09 20:00:00,9319.0 -2015-05-09 21:00:00,9425.0 -2015-05-09 22:00:00,9866.0 -2015-05-09 23:00:00,9673.0 -2015-05-10 00:00:00,9191.0 -2015-05-08 01:00:00,10338.0 -2015-05-08 02:00:00,9655.0 -2015-05-08 03:00:00,9218.0 -2015-05-08 04:00:00,8924.0 -2015-05-08 05:00:00,8776.0 -2015-05-08 06:00:00,8908.0 -2015-05-08 07:00:00,9496.0 -2015-05-08 08:00:00,10308.0 -2015-05-08 09:00:00,11277.0 -2015-05-08 10:00:00,12006.0 -2015-05-08 11:00:00,12519.0 -2015-05-08 12:00:00,12913.0 -2015-05-08 13:00:00,13270.0 -2015-05-08 14:00:00,13611.0 -2015-05-08 15:00:00,13947.0 -2015-05-08 16:00:00,13979.0 -2015-05-08 17:00:00,13793.0 -2015-05-08 18:00:00,13310.0 -2015-05-08 19:00:00,12822.0 -2015-05-08 20:00:00,12339.0 -2015-05-08 21:00:00,12281.0 -2015-05-08 22:00:00,12258.0 -2015-05-08 23:00:00,11862.0 -2015-05-09 00:00:00,10998.0 -2015-05-07 01:00:00,9372.0 -2015-05-07 02:00:00,8756.0 -2015-05-07 03:00:00,8406.0 -2015-05-07 04:00:00,8223.0 -2015-05-07 05:00:00,8148.0 -2015-05-07 06:00:00,8342.0 -2015-05-07 07:00:00,8920.0 -2015-05-07 08:00:00,9779.0 -2015-05-07 09:00:00,10731.0 -2015-05-07 10:00:00,11371.0 -2015-05-07 11:00:00,11818.0 -2015-05-07 12:00:00,12309.0 -2015-05-07 13:00:00,12669.0 -2015-05-07 14:00:00,12800.0 -2015-05-07 15:00:00,13003.0 -2015-05-07 16:00:00,13143.0 -2015-05-07 17:00:00,13090.0 -2015-05-07 18:00:00,13087.0 -2015-05-07 19:00:00,12969.0 -2015-05-07 20:00:00,12623.0 -2015-05-07 21:00:00,12494.0 -2015-05-07 22:00:00,12692.0 -2015-05-07 23:00:00,12197.0 -2015-05-08 00:00:00,11269.0 -2015-05-06 01:00:00,9076.0 -2015-05-06 02:00:00,8606.0 -2015-05-06 03:00:00,8325.0 -2015-05-06 04:00:00,8119.0 -2015-05-06 05:00:00,8096.0 -2015-05-06 06:00:00,8240.0 -2015-05-06 07:00:00,8894.0 -2015-05-06 08:00:00,9756.0 -2015-05-06 09:00:00,10496.0 -2015-05-06 10:00:00,10869.0 -2015-05-06 11:00:00,11162.0 -2015-05-06 12:00:00,11428.0 -2015-05-06 13:00:00,11632.0 -2015-05-06 14:00:00,11811.0 -2015-05-06 15:00:00,12056.0 -2015-05-06 16:00:00,12118.0 -2015-05-06 17:00:00,12056.0 -2015-05-06 18:00:00,11923.0 -2015-05-06 19:00:00,11697.0 -2015-05-06 20:00:00,11306.0 -2015-05-06 21:00:00,11212.0 -2015-05-06 22:00:00,11542.0 -2015-05-06 23:00:00,11125.0 -2015-05-07 00:00:00,10250.0 -2015-05-05 01:00:00,9077.0 -2015-05-05 02:00:00,8552.0 -2015-05-05 03:00:00,8229.0 -2015-05-05 04:00:00,8031.0 -2015-05-05 05:00:00,8027.0 -2015-05-05 06:00:00,8250.0 -2015-05-05 07:00:00,8882.0 -2015-05-05 08:00:00,9745.0 -2015-05-05 09:00:00,10439.0 -2015-05-05 10:00:00,10780.0 -2015-05-05 11:00:00,11028.0 -2015-05-05 12:00:00,11192.0 -2015-05-05 13:00:00,11195.0 -2015-05-05 14:00:00,11175.0 -2015-05-05 15:00:00,11181.0 -2015-05-05 16:00:00,11071.0 -2015-05-05 17:00:00,10943.0 -2015-05-05 18:00:00,10842.0 -2015-05-05 19:00:00,10705.0 -2015-05-05 20:00:00,10564.0 -2015-05-05 21:00:00,10703.0 -2015-05-05 22:00:00,11026.0 -2015-05-05 23:00:00,10634.0 -2015-05-06 00:00:00,9852.0 -2015-05-04 01:00:00,8720.0 -2015-05-04 02:00:00,8319.0 -2015-05-04 03:00:00,8046.0 -2015-05-04 04:00:00,7936.0 -2015-05-04 05:00:00,7856.0 -2015-05-04 06:00:00,8087.0 -2015-05-04 07:00:00,8773.0 -2015-05-04 08:00:00,9743.0 -2015-05-04 09:00:00,10551.0 -2015-05-04 10:00:00,10991.0 -2015-05-04 11:00:00,11293.0 -2015-05-04 12:00:00,11613.0 -2015-05-04 13:00:00,11800.0 -2015-05-04 14:00:00,11903.0 -2015-05-04 15:00:00,11954.0 -2015-05-04 16:00:00,11885.0 -2015-05-04 17:00:00,11765.0 -2015-05-04 18:00:00,11578.0 -2015-05-04 19:00:00,11306.0 -2015-05-04 20:00:00,10982.0 -2015-05-04 21:00:00,10908.0 -2015-05-04 22:00:00,11165.0 -2015-05-04 23:00:00,10723.0 -2015-05-05 00:00:00,9886.0 -2015-05-03 01:00:00,8601.0 -2015-05-03 02:00:00,8198.0 -2015-05-03 03:00:00,7867.0 -2015-05-03 04:00:00,7631.0 -2015-05-03 05:00:00,7510.0 -2015-05-03 06:00:00,7473.0 -2015-05-03 07:00:00,7523.0 -2015-05-03 08:00:00,7433.0 -2015-05-03 09:00:00,7727.0 -2015-05-03 10:00:00,8175.0 -2015-05-03 11:00:00,8553.0 -2015-05-03 12:00:00,8881.0 -2015-05-03 13:00:00,9093.0 -2015-05-03 14:00:00,9287.0 -2015-05-03 15:00:00,9284.0 -2015-05-03 16:00:00,9336.0 -2015-05-03 17:00:00,9361.0 -2015-05-03 18:00:00,9483.0 -2015-05-03 19:00:00,9694.0 -2015-05-03 20:00:00,9931.0 -2015-05-03 21:00:00,10046.0 -2015-05-03 22:00:00,10175.0 -2015-05-03 23:00:00,9830.0 -2015-05-04 00:00:00,9280.0 -2015-05-02 01:00:00,9041.0 -2015-05-02 02:00:00,8455.0 -2015-05-02 03:00:00,8177.0 -2015-05-02 04:00:00,7920.0 -2015-05-02 05:00:00,7864.0 -2015-05-02 06:00:00,7881.0 -2015-05-02 07:00:00,8078.0 -2015-05-02 08:00:00,8189.0 -2015-05-02 09:00:00,8628.0 -2015-05-02 10:00:00,9093.0 -2015-05-02 11:00:00,9390.0 -2015-05-02 12:00:00,9656.0 -2015-05-02 13:00:00,9700.0 -2015-05-02 14:00:00,9726.0 -2015-05-02 15:00:00,9607.0 -2015-05-02 16:00:00,9568.0 -2015-05-02 17:00:00,9541.0 -2015-05-02 18:00:00,9555.0 -2015-05-02 19:00:00,9490.0 -2015-05-02 20:00:00,9444.0 -2015-05-02 21:00:00,9505.0 -2015-05-02 22:00:00,9923.0 -2015-05-02 23:00:00,9689.0 -2015-05-03 00:00:00,9167.0 -2015-05-01 01:00:00,9254.0 -2015-05-01 02:00:00,8753.0 -2015-05-01 03:00:00,8509.0 -2015-05-01 04:00:00,8385.0 -2015-05-01 05:00:00,8389.0 -2015-05-01 06:00:00,8634.0 -2015-05-01 07:00:00,9248.0 -2015-05-01 08:00:00,9961.0 -2015-05-01 09:00:00,10500.0 -2015-05-01 10:00:00,10678.0 -2015-05-01 11:00:00,10772.0 -2015-05-01 12:00:00,10925.0 -2015-05-01 13:00:00,10916.0 -2015-05-01 14:00:00,10914.0 -2015-05-01 15:00:00,10972.0 -2015-05-01 16:00:00,10846.0 -2015-05-01 17:00:00,10701.0 -2015-05-01 18:00:00,10549.0 -2015-05-01 19:00:00,10328.0 -2015-05-01 20:00:00,10136.0 -2015-05-01 21:00:00,10251.0 -2015-05-01 22:00:00,10611.0 -2015-05-01 23:00:00,10323.0 -2015-05-02 00:00:00,9670.0 -2015-04-30 01:00:00,9058.0 -2015-04-30 02:00:00,8593.0 -2015-04-30 03:00:00,8343.0 -2015-04-30 04:00:00,8209.0 -2015-04-30 05:00:00,8187.0 -2015-04-30 06:00:00,8425.0 -2015-04-30 07:00:00,9108.0 -2015-04-30 08:00:00,9962.0 -2015-04-30 09:00:00,10676.0 -2015-04-30 10:00:00,10919.0 -2015-04-30 11:00:00,10944.0 -2015-04-30 12:00:00,11004.0 -2015-04-30 13:00:00,10985.0 -2015-04-30 14:00:00,10887.0 -2015-04-30 15:00:00,10911.0 -2015-04-30 16:00:00,10804.0 -2015-04-30 17:00:00,10620.0 -2015-04-30 18:00:00,10478.0 -2015-04-30 19:00:00,10357.0 -2015-04-30 20:00:00,10138.0 -2015-04-30 21:00:00,10338.0 -2015-04-30 22:00:00,10899.0 -2015-04-30 23:00:00,10615.0 -2015-05-01 00:00:00,9914.0 -2015-04-29 01:00:00,9061.0 -2015-04-29 02:00:00,8639.0 -2015-04-29 03:00:00,8391.0 -2015-04-29 04:00:00,8259.0 -2015-04-29 05:00:00,8268.0 -2015-04-29 06:00:00,8517.0 -2015-04-29 07:00:00,9160.0 -2015-04-29 08:00:00,9921.0 -2015-04-29 09:00:00,10474.0 -2015-04-29 10:00:00,10664.0 -2015-04-29 11:00:00,10794.0 -2015-04-29 12:00:00,10901.0 -2015-04-29 13:00:00,10929.0 -2015-04-29 14:00:00,10903.0 -2015-04-29 15:00:00,10926.0 -2015-04-29 16:00:00,10823.0 -2015-04-29 17:00:00,10701.0 -2015-04-29 18:00:00,10552.0 -2015-04-29 19:00:00,10336.0 -2015-04-29 20:00:00,10290.0 -2015-04-29 21:00:00,10495.0 -2015-04-29 22:00:00,10862.0 -2015-04-29 23:00:00,10466.0 -2015-04-30 00:00:00,9766.0 -2015-04-28 01:00:00,9178.0 -2015-04-28 02:00:00,8759.0 -2015-04-28 03:00:00,8541.0 -2015-04-28 04:00:00,8416.0 -2015-04-28 05:00:00,8410.0 -2015-04-28 06:00:00,8670.0 -2015-04-28 07:00:00,9324.0 -2015-04-28 08:00:00,10122.0 -2015-04-28 09:00:00,10686.0 -2015-04-28 10:00:00,10841.0 -2015-04-28 11:00:00,10914.0 -2015-04-28 12:00:00,10984.0 -2015-04-28 13:00:00,10970.0 -2015-04-28 14:00:00,10954.0 -2015-04-28 15:00:00,10947.0 -2015-04-28 16:00:00,10851.0 -2015-04-28 17:00:00,10697.0 -2015-04-28 18:00:00,10589.0 -2015-04-28 19:00:00,10404.0 -2015-04-28 20:00:00,10227.0 -2015-04-28 21:00:00,10388.0 -2015-04-28 22:00:00,10887.0 -2015-04-28 23:00:00,10494.0 -2015-04-29 00:00:00,9791.0 -2015-04-27 01:00:00,8693.0 -2015-04-27 02:00:00,8353.0 -2015-04-27 03:00:00,8210.0 -2015-04-27 04:00:00,8132.0 -2015-04-27 05:00:00,8271.0 -2015-04-27 06:00:00,8578.0 -2015-04-27 07:00:00,9325.0 -2015-04-27 08:00:00,10117.0 -2015-04-27 09:00:00,10703.0 -2015-04-27 10:00:00,10802.0 -2015-04-27 11:00:00,10887.0 -2015-04-27 12:00:00,10974.0 -2015-04-27 13:00:00,10952.0 -2015-04-27 14:00:00,10904.0 -2015-04-27 15:00:00,10922.0 -2015-04-27 16:00:00,10793.0 -2015-04-27 17:00:00,10656.0 -2015-04-27 18:00:00,10539.0 -2015-04-27 19:00:00,10402.0 -2015-04-27 20:00:00,10281.0 -2015-04-27 21:00:00,10510.0 -2015-04-27 22:00:00,10994.0 -2015-04-27 23:00:00,10608.0 -2015-04-28 00:00:00,9886.0 -2015-04-26 01:00:00,9052.0 -2015-04-26 02:00:00,8683.0 -2015-04-26 03:00:00,8359.0 -2015-04-26 04:00:00,8269.0 -2015-04-26 05:00:00,8174.0 -2015-04-26 06:00:00,8221.0 -2015-04-26 07:00:00,8280.0 -2015-04-26 08:00:00,8273.0 -2015-04-26 09:00:00,8414.0 -2015-04-26 10:00:00,8654.0 -2015-04-26 11:00:00,8772.0 -2015-04-26 12:00:00,8859.0 -2015-04-26 13:00:00,8853.0 -2015-04-26 14:00:00,8836.0 -2015-04-26 15:00:00,8790.0 -2015-04-26 16:00:00,8711.0 -2015-04-26 17:00:00,8649.0 -2015-04-26 18:00:00,8645.0 -2015-04-26 19:00:00,8753.0 -2015-04-26 20:00:00,8872.0 -2015-04-26 21:00:00,9147.0 -2015-04-26 22:00:00,9775.0 -2015-04-26 23:00:00,9584.0 -2015-04-27 00:00:00,9141.0 -2015-04-25 01:00:00,9472.0 -2015-04-25 02:00:00,8998.0 -2015-04-25 03:00:00,8684.0 -2015-04-25 04:00:00,8499.0 -2015-04-25 05:00:00,8417.0 -2015-04-25 06:00:00,8435.0 -2015-04-25 07:00:00,8659.0 -2015-04-25 08:00:00,9008.0 -2015-04-25 09:00:00,9349.0 -2015-04-25 10:00:00,9854.0 -2015-04-25 11:00:00,10159.0 -2015-04-25 12:00:00,10339.0 -2015-04-25 13:00:00,10319.0 -2015-04-25 14:00:00,10239.0 -2015-04-25 15:00:00,10003.0 -2015-04-25 16:00:00,9779.0 -2015-04-25 17:00:00,9625.0 -2015-04-25 18:00:00,9556.0 -2015-04-25 19:00:00,9616.0 -2015-04-25 20:00:00,9726.0 -2015-04-25 21:00:00,10022.0 -2015-04-25 22:00:00,10285.0 -2015-04-25 23:00:00,10035.0 -2015-04-26 00:00:00,9614.0 -2015-04-24 01:00:00,9416.0 -2015-04-24 02:00:00,9018.0 -2015-04-24 03:00:00,8780.0 -2015-04-24 04:00:00,8665.0 -2015-04-24 05:00:00,8670.0 -2015-04-24 06:00:00,8923.0 -2015-04-24 07:00:00,9574.0 -2015-04-24 08:00:00,10347.0 -2015-04-24 09:00:00,10850.0 -2015-04-24 10:00:00,11021.0 -2015-04-24 11:00:00,11087.0 -2015-04-24 12:00:00,11103.0 -2015-04-24 13:00:00,11030.0 -2015-04-24 14:00:00,10972.0 -2015-04-24 15:00:00,10998.0 -2015-04-24 16:00:00,10838.0 -2015-04-24 17:00:00,10720.0 -2015-04-24 18:00:00,10636.0 -2015-04-24 19:00:00,10589.0 -2015-04-24 20:00:00,10613.0 -2015-04-24 21:00:00,10899.0 -2015-04-24 22:00:00,11003.0 -2015-04-24 23:00:00,10751.0 -2015-04-25 00:00:00,10098.0 -2015-04-23 01:00:00,9690.0 -2015-04-23 02:00:00,9234.0 -2015-04-23 03:00:00,8996.0 -2015-04-23 04:00:00,8861.0 -2015-04-23 05:00:00,8850.0 -2015-04-23 06:00:00,9142.0 -2015-04-23 07:00:00,9800.0 -2015-04-23 08:00:00,10579.0 -2015-04-23 09:00:00,11042.0 -2015-04-23 10:00:00,11200.0 -2015-04-23 11:00:00,11191.0 -2015-04-23 12:00:00,11239.0 -2015-04-23 13:00:00,11170.0 -2015-04-23 14:00:00,11073.0 -2015-04-23 15:00:00,11033.0 -2015-04-23 16:00:00,10924.0 -2015-04-23 17:00:00,10761.0 -2015-04-23 18:00:00,10628.0 -2015-04-23 19:00:00,10465.0 -2015-04-23 20:00:00,10337.0 -2015-04-23 21:00:00,10583.0 -2015-04-23 22:00:00,11101.0 -2015-04-23 23:00:00,10779.0 -2015-04-24 00:00:00,10119.0 -2015-04-22 01:00:00,9536.0 -2015-04-22 02:00:00,9148.0 -2015-04-22 03:00:00,8878.0 -2015-04-22 04:00:00,8686.0 -2015-04-22 05:00:00,8694.0 -2015-04-22 06:00:00,8959.0 -2015-04-22 07:00:00,9659.0 -2015-04-22 08:00:00,10589.0 -2015-04-22 09:00:00,11277.0 -2015-04-22 10:00:00,11583.0 -2015-04-22 11:00:00,11633.0 -2015-04-22 12:00:00,11721.0 -2015-04-22 13:00:00,11705.0 -2015-04-22 14:00:00,11657.0 -2015-04-22 15:00:00,11572.0 -2015-04-22 16:00:00,11456.0 -2015-04-22 17:00:00,11299.0 -2015-04-22 18:00:00,11165.0 -2015-04-22 19:00:00,11078.0 -2015-04-22 20:00:00,10954.0 -2015-04-22 21:00:00,11220.0 -2015-04-22 22:00:00,11481.0 -2015-04-22 23:00:00,11018.0 -2015-04-23 00:00:00,10330.0 -2015-04-21 01:00:00,9362.0 -2015-04-21 02:00:00,8919.0 -2015-04-21 03:00:00,8645.0 -2015-04-21 04:00:00,8535.0 -2015-04-21 05:00:00,8506.0 -2015-04-21 06:00:00,8791.0 -2015-04-21 07:00:00,9485.0 -2015-04-21 08:00:00,10338.0 -2015-04-21 09:00:00,10981.0 -2015-04-21 10:00:00,11217.0 -2015-04-21 11:00:00,11372.0 -2015-04-21 12:00:00,11429.0 -2015-04-21 13:00:00,11393.0 -2015-04-21 14:00:00,11302.0 -2015-04-21 15:00:00,11206.0 -2015-04-21 16:00:00,11053.0 -2015-04-21 17:00:00,10930.0 -2015-04-21 18:00:00,10760.0 -2015-04-21 19:00:00,10649.0 -2015-04-21 20:00:00,10590.0 -2015-04-21 21:00:00,10905.0 -2015-04-21 22:00:00,11332.0 -2015-04-21 23:00:00,10917.0 -2015-04-22 00:00:00,10194.0 -2015-04-20 01:00:00,8388.0 -2015-04-20 02:00:00,8054.0 -2015-04-20 03:00:00,7879.0 -2015-04-20 04:00:00,7789.0 -2015-04-20 05:00:00,7811.0 -2015-04-20 06:00:00,8100.0 -2015-04-20 07:00:00,8792.0 -2015-04-20 08:00:00,9876.0 -2015-04-20 09:00:00,10628.0 -2015-04-20 10:00:00,10953.0 -2015-04-20 11:00:00,11131.0 -2015-04-20 12:00:00,11297.0 -2015-04-20 13:00:00,11332.0 -2015-04-20 14:00:00,11308.0 -2015-04-20 15:00:00,11409.0 -2015-04-20 16:00:00,11263.0 -2015-04-20 17:00:00,11132.0 -2015-04-20 18:00:00,11095.0 -2015-04-20 19:00:00,11049.0 -2015-04-20 20:00:00,10937.0 -2015-04-20 21:00:00,11062.0 -2015-04-20 22:00:00,11317.0 -2015-04-20 23:00:00,10831.0 -2015-04-21 00:00:00,10087.0 -2015-04-19 01:00:00,8434.0 -2015-04-19 02:00:00,8027.0 -2015-04-19 03:00:00,7705.0 -2015-04-19 04:00:00,7495.0 -2015-04-19 05:00:00,7439.0 -2015-04-19 06:00:00,7432.0 -2015-04-19 07:00:00,7541.0 -2015-04-19 08:00:00,7505.0 -2015-04-19 09:00:00,7694.0 -2015-04-19 10:00:00,8052.0 -2015-04-19 11:00:00,8371.0 -2015-04-19 12:00:00,8594.0 -2015-04-19 13:00:00,8765.0 -2015-04-19 14:00:00,8782.0 -2015-04-19 15:00:00,8841.0 -2015-04-19 16:00:00,8794.0 -2015-04-19 17:00:00,8781.0 -2015-04-19 18:00:00,9025.0 -2015-04-19 19:00:00,9183.0 -2015-04-19 20:00:00,9352.0 -2015-04-19 21:00:00,9713.0 -2015-04-19 22:00:00,9795.0 -2015-04-19 23:00:00,9438.0 -2015-04-20 00:00:00,8928.0 -2015-04-18 01:00:00,9178.0 -2015-04-18 02:00:00,8612.0 -2015-04-18 03:00:00,8260.0 -2015-04-18 04:00:00,7978.0 -2015-04-18 05:00:00,7883.0 -2015-04-18 06:00:00,7891.0 -2015-04-18 07:00:00,8097.0 -2015-04-18 08:00:00,8174.0 -2015-04-18 09:00:00,8597.0 -2015-04-18 10:00:00,9074.0 -2015-04-18 11:00:00,9348.0 -2015-04-18 12:00:00,9482.0 -2015-04-18 13:00:00,9487.0 -2015-04-18 14:00:00,9449.0 -2015-04-18 15:00:00,9344.0 -2015-04-18 16:00:00,9195.0 -2015-04-18 17:00:00,9107.0 -2015-04-18 18:00:00,9017.0 -2015-04-18 19:00:00,9009.0 -2015-04-18 20:00:00,8932.0 -2015-04-18 21:00:00,9259.0 -2015-04-18 22:00:00,9613.0 -2015-04-18 23:00:00,9342.0 -2015-04-19 00:00:00,8947.0 -2015-04-17 01:00:00,8954.0 -2015-04-17 02:00:00,8442.0 -2015-04-17 03:00:00,8174.0 -2015-04-17 04:00:00,8034.0 -2015-04-17 05:00:00,8013.0 -2015-04-17 06:00:00,8202.0 -2015-04-17 07:00:00,8846.0 -2015-04-17 08:00:00,9625.0 -2015-04-17 09:00:00,10208.0 -2015-04-17 10:00:00,10585.0 -2015-04-17 11:00:00,10914.0 -2015-04-17 12:00:00,11145.0 -2015-04-17 13:00:00,11260.0 -2015-04-17 14:00:00,11303.0 -2015-04-17 15:00:00,11368.0 -2015-04-17 16:00:00,11296.0 -2015-04-17 17:00:00,11185.0 -2015-04-17 18:00:00,11017.0 -2015-04-17 19:00:00,10790.0 -2015-04-17 20:00:00,10595.0 -2015-04-17 21:00:00,10773.0 -2015-04-17 22:00:00,10927.0 -2015-04-17 23:00:00,10541.0 -2015-04-18 00:00:00,9870.0 -2015-04-16 01:00:00,9113.0 -2015-04-16 02:00:00,8606.0 -2015-04-16 03:00:00,8314.0 -2015-04-16 04:00:00,8114.0 -2015-04-16 05:00:00,8065.0 -2015-04-16 06:00:00,8287.0 -2015-04-16 07:00:00,8926.0 -2015-04-16 08:00:00,9930.0 -2015-04-16 09:00:00,10551.0 -2015-04-16 10:00:00,10793.0 -2015-04-16 11:00:00,10916.0 -2015-04-16 12:00:00,11008.0 -2015-04-16 13:00:00,10960.0 -2015-04-16 14:00:00,10898.0 -2015-04-16 15:00:00,10931.0 -2015-04-16 16:00:00,10872.0 -2015-04-16 17:00:00,10756.0 -2015-04-16 18:00:00,10668.0 -2015-04-16 19:00:00,10531.0 -2015-04-16 20:00:00,10322.0 -2015-04-16 21:00:00,10557.0 -2015-04-16 22:00:00,10856.0 -2015-04-16 23:00:00,10404.0 -2015-04-17 00:00:00,9674.0 -2015-04-15 01:00:00,9071.0 -2015-04-15 02:00:00,8602.0 -2015-04-15 03:00:00,8324.0 -2015-04-15 04:00:00,8183.0 -2015-04-15 05:00:00,8154.0 -2015-04-15 06:00:00,8409.0 -2015-04-15 07:00:00,9077.0 -2015-04-15 08:00:00,9955.0 -2015-04-15 09:00:00,10563.0 -2015-04-15 10:00:00,10799.0 -2015-04-15 11:00:00,10883.0 -2015-04-15 12:00:00,11059.0 -2015-04-15 13:00:00,11110.0 -2015-04-15 14:00:00,11089.0 -2015-04-15 15:00:00,11133.0 -2015-04-15 16:00:00,11046.0 -2015-04-15 17:00:00,10856.0 -2015-04-15 18:00:00,10744.0 -2015-04-15 19:00:00,10581.0 -2015-04-15 20:00:00,10399.0 -2015-04-15 21:00:00,10739.0 -2015-04-15 22:00:00,10973.0 -2015-04-15 23:00:00,10571.0 -2015-04-16 00:00:00,9842.0 -2015-04-14 01:00:00,9016.0 -2015-04-14 02:00:00,8538.0 -2015-04-14 03:00:00,8282.0 -2015-04-14 04:00:00,8133.0 -2015-04-14 05:00:00,8117.0 -2015-04-14 06:00:00,8338.0 -2015-04-14 07:00:00,9048.0 -2015-04-14 08:00:00,9892.0 -2015-04-14 09:00:00,10428.0 -2015-04-14 10:00:00,10668.0 -2015-04-14 11:00:00,10835.0 -2015-04-14 12:00:00,10979.0 -2015-04-14 13:00:00,11000.0 -2015-04-14 14:00:00,10994.0 -2015-04-14 15:00:00,11044.0 -2015-04-14 16:00:00,10974.0 -2015-04-14 17:00:00,10849.0 -2015-04-14 18:00:00,10747.0 -2015-04-14 19:00:00,10584.0 -2015-04-14 20:00:00,10379.0 -2015-04-14 21:00:00,10684.0 -2015-04-14 22:00:00,11002.0 -2015-04-14 23:00:00,10490.0 -2015-04-15 00:00:00,9661.0 -2015-04-13 01:00:00,8474.0 -2015-04-13 02:00:00,8154.0 -2015-04-13 03:00:00,7945.0 -2015-04-13 04:00:00,7828.0 -2015-04-13 05:00:00,7841.0 -2015-04-13 06:00:00,8119.0 -2015-04-13 07:00:00,8879.0 -2015-04-13 08:00:00,9966.0 -2015-04-13 09:00:00,10585.0 -2015-04-13 10:00:00,10799.0 -2015-04-13 11:00:00,11039.0 -2015-04-13 12:00:00,11173.0 -2015-04-13 13:00:00,11241.0 -2015-04-13 14:00:00,11210.0 -2015-04-13 15:00:00,11228.0 -2015-04-13 16:00:00,11128.0 -2015-04-13 17:00:00,10995.0 -2015-04-13 18:00:00,10827.0 -2015-04-13 19:00:00,10628.0 -2015-04-13 20:00:00,10443.0 -2015-04-13 21:00:00,10687.0 -2015-04-13 22:00:00,10934.0 -2015-04-13 23:00:00,10465.0 -2015-04-14 00:00:00,9722.0 -2015-04-12 01:00:00,8667.0 -2015-04-12 02:00:00,8326.0 -2015-04-12 03:00:00,8042.0 -2015-04-12 04:00:00,7892.0 -2015-04-12 05:00:00,7826.0 -2015-04-12 06:00:00,7845.0 -2015-04-12 07:00:00,7978.0 -2015-04-12 08:00:00,8026.0 -2015-04-12 09:00:00,8143.0 -2015-04-12 10:00:00,8451.0 -2015-04-12 11:00:00,8654.0 -2015-04-12 12:00:00,8833.0 -2015-04-12 13:00:00,8914.0 -2015-04-12 14:00:00,8944.0 -2015-04-12 15:00:00,8916.0 -2015-04-12 16:00:00,8923.0 -2015-04-12 17:00:00,8858.0 -2015-04-12 18:00:00,8982.0 -2015-04-12 19:00:00,9069.0 -2015-04-12 20:00:00,9222.0 -2015-04-12 21:00:00,9685.0 -2015-04-12 22:00:00,9817.0 -2015-04-12 23:00:00,9479.0 -2015-04-13 00:00:00,9002.0 -2015-04-11 01:00:00,9314.0 -2015-04-11 02:00:00,8902.0 -2015-04-11 03:00:00,8636.0 -2015-04-11 04:00:00,8534.0 -2015-04-11 05:00:00,8418.0 -2015-04-11 06:00:00,8554.0 -2015-04-11 07:00:00,8793.0 -2015-04-11 08:00:00,9054.0 -2015-04-11 09:00:00,9227.0 -2015-04-11 10:00:00,9469.0 -2015-04-11 11:00:00,9585.0 -2015-04-11 12:00:00,9668.0 -2015-04-11 13:00:00,9603.0 -2015-04-11 14:00:00,9505.0 -2015-04-11 15:00:00,9314.0 -2015-04-11 16:00:00,9203.0 -2015-04-11 17:00:00,9123.0 -2015-04-11 18:00:00,9060.0 -2015-04-11 19:00:00,9052.0 -2015-04-11 20:00:00,9031.0 -2015-04-11 21:00:00,9448.0 -2015-04-11 22:00:00,9855.0 -2015-04-11 23:00:00,9602.0 -2015-04-12 00:00:00,9205.0 -2015-04-10 01:00:00,9340.0 -2015-04-10 02:00:00,8816.0 -2015-04-10 03:00:00,8525.0 -2015-04-10 04:00:00,8396.0 -2015-04-10 05:00:00,8372.0 -2015-04-10 06:00:00,8635.0 -2015-04-10 07:00:00,9319.0 -2015-04-10 08:00:00,10284.0 -2015-04-10 09:00:00,10912.0 -2015-04-10 10:00:00,11156.0 -2015-04-10 11:00:00,11100.0 -2015-04-10 12:00:00,11125.0 -2015-04-10 13:00:00,11082.0 -2015-04-10 14:00:00,11024.0 -2015-04-10 15:00:00,11024.0 -2015-04-10 16:00:00,10908.0 -2015-04-10 17:00:00,10678.0 -2015-04-10 18:00:00,10486.0 -2015-04-10 19:00:00,10326.0 -2015-04-10 20:00:00,10283.0 -2015-04-10 21:00:00,10718.0 -2015-04-10 22:00:00,10809.0 -2015-04-10 23:00:00,10520.0 -2015-04-11 00:00:00,9952.0 -2015-04-09 01:00:00,9669.0 -2015-04-09 02:00:00,9176.0 -2015-04-09 03:00:00,8834.0 -2015-04-09 04:00:00,8658.0 -2015-04-09 05:00:00,8622.0 -2015-04-09 06:00:00,8853.0 -2015-04-09 07:00:00,9545.0 -2015-04-09 08:00:00,10555.0 -2015-04-09 09:00:00,11180.0 -2015-04-09 10:00:00,11372.0 -2015-04-09 11:00:00,11491.0 -2015-04-09 12:00:00,11721.0 -2015-04-09 13:00:00,11858.0 -2015-04-09 14:00:00,11777.0 -2015-04-09 15:00:00,11702.0 -2015-04-09 16:00:00,11627.0 -2015-04-09 17:00:00,11608.0 -2015-04-09 18:00:00,11375.0 -2015-04-09 19:00:00,11253.0 -2015-04-09 20:00:00,11245.0 -2015-04-09 21:00:00,11509.0 -2015-04-09 22:00:00,11475.0 -2015-04-09 23:00:00,11029.0 -2015-04-10 00:00:00,10166.0 -2015-04-08 01:00:00,9573.0 -2015-04-08 02:00:00,9086.0 -2015-04-08 03:00:00,8799.0 -2015-04-08 04:00:00,8582.0 -2015-04-08 05:00:00,8535.0 -2015-04-08 06:00:00,8764.0 -2015-04-08 07:00:00,9455.0 -2015-04-08 08:00:00,10452.0 -2015-04-08 09:00:00,10998.0 -2015-04-08 10:00:00,11295.0 -2015-04-08 11:00:00,11335.0 -2015-04-08 12:00:00,11428.0 -2015-04-08 13:00:00,11448.0 -2015-04-08 14:00:00,11326.0 -2015-04-08 15:00:00,11258.0 -2015-04-08 16:00:00,11138.0 -2015-04-08 17:00:00,11083.0 -2015-04-08 18:00:00,11049.0 -2015-04-08 19:00:00,11023.0 -2015-04-08 20:00:00,11044.0 -2015-04-08 21:00:00,11439.0 -2015-04-08 22:00:00,11503.0 -2015-04-08 23:00:00,11070.0 -2015-04-09 00:00:00,10392.0 -2015-04-07 01:00:00,9393.0 -2015-04-07 02:00:00,8969.0 -2015-04-07 03:00:00,8721.0 -2015-04-07 04:00:00,8548.0 -2015-04-07 05:00:00,8503.0 -2015-04-07 06:00:00,8751.0 -2015-04-07 07:00:00,9491.0 -2015-04-07 08:00:00,10498.0 -2015-04-07 09:00:00,11082.0 -2015-04-07 10:00:00,11385.0 -2015-04-07 11:00:00,11587.0 -2015-04-07 12:00:00,11725.0 -2015-04-07 13:00:00,11700.0 -2015-04-07 14:00:00,11633.0 -2015-04-07 15:00:00,11635.0 -2015-04-07 16:00:00,11515.0 -2015-04-07 17:00:00,11414.0 -2015-04-07 18:00:00,11343.0 -2015-04-07 19:00:00,11327.0 -2015-04-07 20:00:00,11298.0 -2015-04-07 21:00:00,11660.0 -2015-04-07 22:00:00,11529.0 -2015-04-07 23:00:00,11040.0 -2015-04-08 00:00:00,10287.0 -2015-04-06 01:00:00,8270.0 -2015-04-06 02:00:00,7982.0 -2015-04-06 03:00:00,7840.0 -2015-04-06 04:00:00,7780.0 -2015-04-06 05:00:00,7856.0 -2015-04-06 06:00:00,8126.0 -2015-04-06 07:00:00,8931.0 -2015-04-06 08:00:00,9832.0 -2015-04-06 09:00:00,10426.0 -2015-04-06 10:00:00,10753.0 -2015-04-06 11:00:00,10928.0 -2015-04-06 12:00:00,10998.0 -2015-04-06 13:00:00,11018.0 -2015-04-06 14:00:00,10918.0 -2015-04-06 15:00:00,10912.0 -2015-04-06 16:00:00,10867.0 -2015-04-06 17:00:00,10723.0 -2015-04-06 18:00:00,10603.0 -2015-04-06 19:00:00,10591.0 -2015-04-06 20:00:00,10593.0 -2015-04-06 21:00:00,11070.0 -2015-04-06 22:00:00,11078.0 -2015-04-06 23:00:00,10703.0 -2015-04-07 00:00:00,10071.0 -2015-04-05 01:00:00,8592.0 -2015-04-05 02:00:00,8142.0 -2015-04-05 03:00:00,7891.0 -2015-04-05 04:00:00,7703.0 -2015-04-05 05:00:00,7653.0 -2015-04-05 06:00:00,7603.0 -2015-04-05 07:00:00,7726.0 -2015-04-05 08:00:00,7829.0 -2015-04-05 09:00:00,7886.0 -2015-04-05 10:00:00,8053.0 -2015-04-05 11:00:00,8223.0 -2015-04-05 12:00:00,8257.0 -2015-04-05 13:00:00,8217.0 -2015-04-05 14:00:00,8256.0 -2015-04-05 15:00:00,8092.0 -2015-04-05 16:00:00,8014.0 -2015-04-05 17:00:00,7866.0 -2015-04-05 18:00:00,7881.0 -2015-04-05 19:00:00,7914.0 -2015-04-05 20:00:00,8038.0 -2015-04-05 21:00:00,8621.0 -2015-04-05 22:00:00,9017.0 -2015-04-05 23:00:00,8853.0 -2015-04-06 00:00:00,8500.0 -2015-04-04 01:00:00,9295.0 -2015-04-04 02:00:00,8828.0 -2015-04-04 03:00:00,8615.0 -2015-04-04 04:00:00,8456.0 -2015-04-04 05:00:00,8442.0 -2015-04-04 06:00:00,8493.0 -2015-04-04 07:00:00,8797.0 -2015-04-04 08:00:00,9069.0 -2015-04-04 09:00:00,9275.0 -2015-04-04 10:00:00,9499.0 -2015-04-04 11:00:00,9665.0 -2015-04-04 12:00:00,9645.0 -2015-04-04 13:00:00,9593.0 -2015-04-04 14:00:00,9459.0 -2015-04-04 15:00:00,9242.0 -2015-04-04 16:00:00,9030.0 -2015-04-04 17:00:00,8968.0 -2015-04-04 18:00:00,8926.0 -2015-04-04 19:00:00,8920.0 -2015-04-04 20:00:00,8960.0 -2015-04-04 21:00:00,9496.0 -2015-04-04 22:00:00,9796.0 -2015-04-04 23:00:00,9648.0 -2015-04-05 00:00:00,9243.0 -2015-04-03 01:00:00,8978.0 -2015-04-03 02:00:00,8458.0 -2015-04-03 03:00:00,8170.0 -2015-04-03 04:00:00,8002.0 -2015-04-03 05:00:00,7945.0 -2015-04-03 06:00:00,8069.0 -2015-04-03 07:00:00,8577.0 -2015-04-03 08:00:00,9210.0 -2015-04-03 09:00:00,9612.0 -2015-04-03 10:00:00,10000.0 -2015-04-03 11:00:00,10113.0 -2015-04-03 12:00:00,10200.0 -2015-04-03 13:00:00,10198.0 -2015-04-03 14:00:00,10080.0 -2015-04-03 15:00:00,10123.0 -2015-04-03 16:00:00,10064.0 -2015-04-03 17:00:00,9942.0 -2015-04-03 18:00:00,9904.0 -2015-04-03 19:00:00,9983.0 -2015-04-03 20:00:00,10054.0 -2015-04-03 21:00:00,10405.0 -2015-04-03 22:00:00,10558.0 -2015-04-03 23:00:00,10332.0 -2015-04-04 00:00:00,9827.0 -2015-04-02 01:00:00,9152.0 -2015-04-02 02:00:00,8644.0 -2015-04-02 03:00:00,8307.0 -2015-04-02 04:00:00,8147.0 -2015-04-02 05:00:00,8132.0 -2015-04-02 06:00:00,8296.0 -2015-04-02 07:00:00,8909.0 -2015-04-02 08:00:00,9887.0 -2015-04-02 09:00:00,10585.0 -2015-04-02 10:00:00,10904.0 -2015-04-02 11:00:00,11085.0 -2015-04-02 12:00:00,11208.0 -2015-04-02 13:00:00,11218.0 -2015-04-02 14:00:00,11228.0 -2015-04-02 15:00:00,11221.0 -2015-04-02 16:00:00,11019.0 -2015-04-02 17:00:00,10821.0 -2015-04-02 18:00:00,10682.0 -2015-04-02 19:00:00,10533.0 -2015-04-02 20:00:00,10308.0 -2015-04-02 21:00:00,10701.0 -2015-04-02 22:00:00,10730.0 -2015-04-02 23:00:00,10359.0 -2015-04-03 00:00:00,9666.0 -2015-04-01 01:00:00,9491.0 -2015-04-01 02:00:00,9056.0 -2015-04-01 03:00:00,8813.0 -2015-04-01 04:00:00,8666.0 -2015-04-01 05:00:00,8639.0 -2015-04-01 06:00:00,8891.0 -2015-04-01 07:00:00,9561.0 -2015-04-01 08:00:00,10533.0 -2015-04-01 09:00:00,10953.0 -2015-04-01 10:00:00,11088.0 -2015-04-01 11:00:00,11109.0 -2015-04-01 12:00:00,11115.0 -2015-04-01 13:00:00,11086.0 -2015-04-01 14:00:00,11072.0 -2015-04-01 15:00:00,11106.0 -2015-04-01 16:00:00,11035.0 -2015-04-01 17:00:00,10944.0 -2015-04-01 18:00:00,10833.0 -2015-04-01 19:00:00,10668.0 -2015-04-01 20:00:00,10489.0 -2015-04-01 21:00:00,10895.0 -2015-04-01 22:00:00,11060.0 -2015-04-01 23:00:00,10622.0 -2015-04-02 00:00:00,9892.0 -2015-03-31 01:00:00,9397.0 -2015-03-31 02:00:00,8945.0 -2015-03-31 03:00:00,8663.0 -2015-03-31 04:00:00,8490.0 -2015-03-31 05:00:00,8459.0 -2015-03-31 06:00:00,8690.0 -2015-03-31 07:00:00,9437.0 -2015-03-31 08:00:00,10515.0 -2015-03-31 09:00:00,11022.0 -2015-03-31 10:00:00,11315.0 -2015-03-31 11:00:00,11383.0 -2015-03-31 12:00:00,11353.0 -2015-03-31 13:00:00,11262.0 -2015-03-31 14:00:00,11165.0 -2015-03-31 15:00:00,11114.0 -2015-03-31 16:00:00,10951.0 -2015-03-31 17:00:00,10793.0 -2015-03-31 18:00:00,10663.0 -2015-03-31 19:00:00,10559.0 -2015-03-31 20:00:00,10495.0 -2015-03-31 21:00:00,10992.0 -2015-03-31 22:00:00,11147.0 -2015-03-31 23:00:00,10772.0 -2015-04-01 00:00:00,10138.0 -2015-03-30 01:00:00,9330.0 -2015-03-30 02:00:00,8983.0 -2015-03-30 03:00:00,8804.0 -2015-03-30 04:00:00,8801.0 -2015-03-30 05:00:00,8910.0 -2015-03-30 06:00:00,9244.0 -2015-03-30 07:00:00,10021.0 -2015-03-30 08:00:00,11037.0 -2015-03-30 09:00:00,11314.0 -2015-03-30 10:00:00,11438.0 -2015-03-30 11:00:00,11397.0 -2015-03-30 12:00:00,11379.0 -2015-03-30 13:00:00,11276.0 -2015-03-30 14:00:00,11174.0 -2015-03-30 15:00:00,11141.0 -2015-03-30 16:00:00,10956.0 -2015-03-30 17:00:00,10829.0 -2015-03-30 18:00:00,10775.0 -2015-03-30 19:00:00,10755.0 -2015-03-30 20:00:00,10799.0 -2015-03-30 21:00:00,11189.0 -2015-03-30 22:00:00,11181.0 -2015-03-30 23:00:00,10775.0 -2015-03-31 00:00:00,10081.0 -2015-03-29 01:00:00,9946.0 -2015-03-29 02:00:00,9579.0 -2015-03-29 03:00:00,9328.0 -2015-03-29 04:00:00,9239.0 -2015-03-29 05:00:00,9144.0 -2015-03-29 06:00:00,9201.0 -2015-03-29 07:00:00,9303.0 -2015-03-29 08:00:00,9557.0 -2015-03-29 09:00:00,9487.0 -2015-03-29 10:00:00,9731.0 -2015-03-29 11:00:00,9919.0 -2015-03-29 12:00:00,10204.0 -2015-03-29 13:00:00,10436.0 -2015-03-29 14:00:00,10550.0 -2015-03-29 15:00:00,10544.0 -2015-03-29 16:00:00,10452.0 -2015-03-29 17:00:00,10118.0 -2015-03-29 18:00:00,10006.0 -2015-03-29 19:00:00,10093.0 -2015-03-29 20:00:00,10389.0 -2015-03-29 21:00:00,10720.0 -2015-03-29 22:00:00,10643.0 -2015-03-29 23:00:00,10310.0 -2015-03-30 00:00:00,9848.0 -2015-03-28 01:00:00,10603.0 -2015-03-28 02:00:00,10135.0 -2015-03-28 03:00:00,9895.0 -2015-03-28 04:00:00,9761.0 -2015-03-28 05:00:00,9716.0 -2015-03-28 06:00:00,9829.0 -2015-03-28 07:00:00,10111.0 -2015-03-28 08:00:00,10545.0 -2015-03-28 09:00:00,10628.0 -2015-03-28 10:00:00,10830.0 -2015-03-28 11:00:00,10898.0 -2015-03-28 12:00:00,10885.0 -2015-03-28 13:00:00,10778.0 -2015-03-28 14:00:00,10603.0 -2015-03-28 15:00:00,10329.0 -2015-03-28 16:00:00,10125.0 -2015-03-28 17:00:00,9916.0 -2015-03-28 18:00:00,9857.0 -2015-03-28 19:00:00,9841.0 -2015-03-28 20:00:00,10025.0 -2015-03-28 21:00:00,10703.0 -2015-03-28 22:00:00,11000.0 -2015-03-28 23:00:00,10814.0 -2015-03-29 00:00:00,10428.0 -2015-03-27 01:00:00,10173.0 -2015-03-27 02:00:00,9721.0 -2015-03-27 03:00:00,9490.0 -2015-03-27 04:00:00,9416.0 -2015-03-27 05:00:00,9429.0 -2015-03-27 06:00:00,9743.0 -2015-03-27 07:00:00,10503.0 -2015-03-27 08:00:00,11576.0 -2015-03-27 09:00:00,12076.0 -2015-03-27 10:00:00,12210.0 -2015-03-27 11:00:00,12205.0 -2015-03-27 12:00:00,12247.0 -2015-03-27 13:00:00,12232.0 -2015-03-27 14:00:00,12031.0 -2015-03-27 15:00:00,11973.0 -2015-03-27 16:00:00,11750.0 -2015-03-27 17:00:00,11521.0 -2015-03-27 18:00:00,11382.0 -2015-03-27 19:00:00,11329.0 -2015-03-27 20:00:00,11415.0 -2015-03-27 21:00:00,12069.0 -2015-03-27 22:00:00,12154.0 -2015-03-27 23:00:00,11857.0 -2015-03-28 00:00:00,11261.0 -2015-03-26 01:00:00,10161.0 -2015-03-26 02:00:00,9678.0 -2015-03-26 03:00:00,9426.0 -2015-03-26 04:00:00,9274.0 -2015-03-26 05:00:00,9267.0 -2015-03-26 06:00:00,9532.0 -2015-03-26 07:00:00,10268.0 -2015-03-26 08:00:00,11363.0 -2015-03-26 09:00:00,11803.0 -2015-03-26 10:00:00,11925.0 -2015-03-26 11:00:00,11895.0 -2015-03-26 12:00:00,11839.0 -2015-03-26 13:00:00,11775.0 -2015-03-26 14:00:00,11659.0 -2015-03-26 15:00:00,11661.0 -2015-03-26 16:00:00,11530.0 -2015-03-26 17:00:00,11409.0 -2015-03-26 18:00:00,11396.0 -2015-03-26 19:00:00,11472.0 -2015-03-26 20:00:00,11524.0 -2015-03-26 21:00:00,12007.0 -2015-03-26 22:00:00,11941.0 -2015-03-26 23:00:00,11505.0 -2015-03-27 00:00:00,10822.0 -2015-03-25 01:00:00,10244.0 -2015-03-25 02:00:00,9814.0 -2015-03-25 03:00:00,9541.0 -2015-03-25 04:00:00,9375.0 -2015-03-25 05:00:00,9323.0 -2015-03-25 06:00:00,9514.0 -2015-03-25 07:00:00,10189.0 -2015-03-25 08:00:00,11259.0 -2015-03-25 09:00:00,11855.0 -2015-03-25 10:00:00,12049.0 -2015-03-25 11:00:00,12148.0 -2015-03-25 12:00:00,12160.0 -2015-03-25 13:00:00,12089.0 -2015-03-25 14:00:00,12096.0 -2015-03-25 15:00:00,12084.0 -2015-03-25 16:00:00,11950.0 -2015-03-25 17:00:00,11859.0 -2015-03-25 18:00:00,11791.0 -2015-03-25 19:00:00,11723.0 -2015-03-25 20:00:00,11631.0 -2015-03-25 21:00:00,12062.0 -2015-03-25 22:00:00,12022.0 -2015-03-25 23:00:00,11579.0 -2015-03-26 00:00:00,10836.0 -2015-03-24 01:00:00,10460.0 -2015-03-24 02:00:00,10018.0 -2015-03-24 03:00:00,9783.0 -2015-03-24 04:00:00,9650.0 -2015-03-24 05:00:00,9694.0 -2015-03-24 06:00:00,9958.0 -2015-03-24 07:00:00,10730.0 -2015-03-24 08:00:00,11788.0 -2015-03-24 09:00:00,12170.0 -2015-03-24 10:00:00,12261.0 -2015-03-24 11:00:00,12218.0 -2015-03-24 12:00:00,12140.0 -2015-03-24 13:00:00,12038.0 -2015-03-24 14:00:00,11907.0 -2015-03-24 15:00:00,11863.0 -2015-03-24 16:00:00,11676.0 -2015-03-24 17:00:00,11577.0 -2015-03-24 18:00:00,11585.0 -2015-03-24 19:00:00,11635.0 -2015-03-24 20:00:00,11699.0 -2015-03-24 21:00:00,12242.0 -2015-03-24 22:00:00,12127.0 -2015-03-24 23:00:00,11659.0 -2015-03-25 00:00:00,10961.0 -2015-03-23 01:00:00,9708.0 -2015-03-23 02:00:00,9375.0 -2015-03-23 03:00:00,9212.0 -2015-03-23 04:00:00,9172.0 -2015-03-23 05:00:00,9217.0 -2015-03-23 06:00:00,9522.0 -2015-03-23 07:00:00,10285.0 -2015-03-23 08:00:00,11455.0 -2015-03-23 09:00:00,12020.0 -2015-03-23 10:00:00,12322.0 -2015-03-23 11:00:00,12440.0 -2015-03-23 12:00:00,12543.0 -2015-03-23 13:00:00,12480.0 -2015-03-23 14:00:00,12351.0 -2015-03-23 15:00:00,12342.0 -2015-03-23 16:00:00,12164.0 -2015-03-23 17:00:00,12008.0 -2015-03-23 18:00:00,12032.0 -2015-03-23 19:00:00,12022.0 -2015-03-23 20:00:00,12039.0 -2015-03-23 21:00:00,12483.0 -2015-03-23 22:00:00,12363.0 -2015-03-23 23:00:00,11886.0 -2015-03-24 00:00:00,11158.0 -2015-03-22 01:00:00,9250.0 -2015-03-22 02:00:00,8917.0 -2015-03-22 03:00:00,8676.0 -2015-03-22 04:00:00,8561.0 -2015-03-22 05:00:00,8473.0 -2015-03-22 06:00:00,8541.0 -2015-03-22 07:00:00,8697.0 -2015-03-22 08:00:00,9003.0 -2015-03-22 09:00:00,9061.0 -2015-03-22 10:00:00,9371.0 -2015-03-22 11:00:00,9613.0 -2015-03-22 12:00:00,9740.0 -2015-03-22 13:00:00,9745.0 -2015-03-22 14:00:00,9742.0 -2015-03-22 15:00:00,9694.0 -2015-03-22 16:00:00,9723.0 -2015-03-22 17:00:00,9672.0 -2015-03-22 18:00:00,9789.0 -2015-03-22 19:00:00,10030.0 -2015-03-22 20:00:00,10402.0 -2015-03-22 21:00:00,11039.0 -2015-03-22 22:00:00,11025.0 -2015-03-22 23:00:00,10728.0 -2015-03-23 00:00:00,10201.0 -2015-03-21 01:00:00,9681.0 -2015-03-21 02:00:00,9154.0 -2015-03-21 03:00:00,8904.0 -2015-03-21 04:00:00,8681.0 -2015-03-21 05:00:00,8673.0 -2015-03-21 06:00:00,8699.0 -2015-03-21 07:00:00,9007.0 -2015-03-21 08:00:00,9451.0 -2015-03-21 09:00:00,9608.0 -2015-03-21 10:00:00,9784.0 -2015-03-21 11:00:00,9914.0 -2015-03-21 12:00:00,9921.0 -2015-03-21 13:00:00,9826.0 -2015-03-21 14:00:00,9680.0 -2015-03-21 15:00:00,9462.0 -2015-03-21 16:00:00,9312.0 -2015-03-21 17:00:00,9165.0 -2015-03-21 18:00:00,9162.0 -2015-03-21 19:00:00,9203.0 -2015-03-21 20:00:00,9414.0 -2015-03-21 21:00:00,10085.0 -2015-03-21 22:00:00,10280.0 -2015-03-21 23:00:00,10072.0 -2015-03-22 00:00:00,9734.0 -2015-03-20 01:00:00,9731.0 -2015-03-20 02:00:00,9263.0 -2015-03-20 03:00:00,8994.0 -2015-03-20 04:00:00,8836.0 -2015-03-20 05:00:00,8797.0 -2015-03-20 06:00:00,9001.0 -2015-03-20 07:00:00,9670.0 -2015-03-20 08:00:00,10784.0 -2015-03-20 09:00:00,11294.0 -2015-03-20 10:00:00,11376.0 -2015-03-20 11:00:00,11364.0 -2015-03-20 12:00:00,11280.0 -2015-03-20 13:00:00,11131.0 -2015-03-20 14:00:00,11029.0 -2015-03-20 15:00:00,11021.0 -2015-03-20 16:00:00,10895.0 -2015-03-20 17:00:00,10736.0 -2015-03-20 18:00:00,10658.0 -2015-03-20 19:00:00,10549.0 -2015-03-20 20:00:00,10607.0 -2015-03-20 21:00:00,11144.0 -2015-03-20 22:00:00,11117.0 -2015-03-20 23:00:00,10832.0 -2015-03-21 00:00:00,10270.0 -2015-03-19 01:00:00,9648.0 -2015-03-19 02:00:00,9197.0 -2015-03-19 03:00:00,8941.0 -2015-03-19 04:00:00,8793.0 -2015-03-19 05:00:00,8801.0 -2015-03-19 06:00:00,9024.0 -2015-03-19 07:00:00,9747.0 -2015-03-19 08:00:00,10926.0 -2015-03-19 09:00:00,11316.0 -2015-03-19 10:00:00,11479.0 -2015-03-19 11:00:00,11527.0 -2015-03-19 12:00:00,11523.0 -2015-03-19 13:00:00,11491.0 -2015-03-19 14:00:00,11390.0 -2015-03-19 15:00:00,11334.0 -2015-03-19 16:00:00,11196.0 -2015-03-19 17:00:00,11126.0 -2015-03-19 18:00:00,11112.0 -2015-03-19 19:00:00,11187.0 -2015-03-19 20:00:00,11341.0 -2015-03-19 21:00:00,11776.0 -2015-03-19 22:00:00,11624.0 -2015-03-19 23:00:00,11164.0 -2015-03-20 00:00:00,10414.0 -2015-03-18 01:00:00,9879.0 -2015-03-18 02:00:00,9454.0 -2015-03-18 03:00:00,9186.0 -2015-03-18 04:00:00,9062.0 -2015-03-18 05:00:00,9066.0 -2015-03-18 06:00:00,9313.0 -2015-03-18 07:00:00,10078.0 -2015-03-18 08:00:00,11224.0 -2015-03-18 09:00:00,11655.0 -2015-03-18 10:00:00,11668.0 -2015-03-18 11:00:00,11530.0 -2015-03-18 12:00:00,11481.0 -2015-03-18 13:00:00,11351.0 -2015-03-18 14:00:00,11215.0 -2015-03-18 15:00:00,11149.0 -2015-03-18 16:00:00,11024.0 -2015-03-18 17:00:00,10942.0 -2015-03-18 18:00:00,10925.0 -2015-03-18 19:00:00,10963.0 -2015-03-18 20:00:00,11187.0 -2015-03-18 21:00:00,11636.0 -2015-03-18 22:00:00,11495.0 -2015-03-18 23:00:00,11026.0 -2015-03-19 00:00:00,10309.0 -2015-03-17 01:00:00,9139.0 -2015-03-17 02:00:00,8737.0 -2015-03-17 03:00:00,8505.0 -2015-03-17 04:00:00,8401.0 -2015-03-17 05:00:00,8395.0 -2015-03-17 06:00:00,8722.0 -2015-03-17 07:00:00,9506.0 -2015-03-17 08:00:00,10766.0 -2015-03-17 09:00:00,11463.0 -2015-03-17 10:00:00,11573.0 -2015-03-17 11:00:00,11537.0 -2015-03-17 12:00:00,11550.0 -2015-03-17 13:00:00,11473.0 -2015-03-17 14:00:00,11399.0 -2015-03-17 15:00:00,11328.0 -2015-03-17 16:00:00,11172.0 -2015-03-17 17:00:00,10977.0 -2015-03-17 18:00:00,10879.0 -2015-03-17 19:00:00,10828.0 -2015-03-17 20:00:00,10927.0 -2015-03-17 21:00:00,11614.0 -2015-03-17 22:00:00,11588.0 -2015-03-17 23:00:00,11199.0 -2015-03-18 00:00:00,10541.0 -2015-03-16 01:00:00,8640.0 -2015-03-16 02:00:00,8355.0 -2015-03-16 03:00:00,8139.0 -2015-03-16 04:00:00,8076.0 -2015-03-16 05:00:00,8081.0 -2015-03-16 06:00:00,8346.0 -2015-03-16 07:00:00,9088.0 -2015-03-16 08:00:00,10356.0 -2015-03-16 09:00:00,10894.0 -2015-03-16 10:00:00,10958.0 -2015-03-16 11:00:00,11032.0 -2015-03-16 12:00:00,11158.0 -2015-03-16 13:00:00,11172.0 -2015-03-16 14:00:00,11113.0 -2015-03-16 15:00:00,11121.0 -2015-03-16 16:00:00,11039.0 -2015-03-16 17:00:00,10904.0 -2015-03-16 18:00:00,10773.0 -2015-03-16 19:00:00,10710.0 -2015-03-16 20:00:00,10656.0 -2015-03-16 21:00:00,11238.0 -2015-03-16 22:00:00,11132.0 -2015-03-16 23:00:00,10582.0 -2015-03-17 00:00:00,9842.0 -2015-03-15 01:00:00,8950.0 -2015-03-15 02:00:00,8576.0 -2015-03-15 03:00:00,8307.0 -2015-03-15 04:00:00,8170.0 -2015-03-15 05:00:00,8169.0 -2015-03-15 06:00:00,8183.0 -2015-03-15 07:00:00,8406.0 -2015-03-15 08:00:00,8684.0 -2015-03-15 09:00:00,8742.0 -2015-03-15 10:00:00,8876.0 -2015-03-15 11:00:00,9026.0 -2015-03-15 12:00:00,9056.0 -2015-03-15 13:00:00,9026.0 -2015-03-15 14:00:00,9009.0 -2015-03-15 15:00:00,8964.0 -2015-03-15 16:00:00,8887.0 -2015-03-15 17:00:00,8836.0 -2015-03-15 18:00:00,8899.0 -2015-03-15 19:00:00,9017.0 -2015-03-15 20:00:00,9449.0 -2015-03-15 21:00:00,10090.0 -2015-03-15 22:00:00,10031.0 -2015-03-15 23:00:00,9663.0 -2015-03-16 00:00:00,9194.0 -2015-03-14 01:00:00,9367.0 -2015-03-14 02:00:00,8852.0 -2015-03-14 03:00:00,8586.0 -2015-03-14 04:00:00,8427.0 -2015-03-14 05:00:00,8372.0 -2015-03-14 06:00:00,8435.0 -2015-03-14 07:00:00,8709.0 -2015-03-14 08:00:00,9165.0 -2015-03-14 09:00:00,9459.0 -2015-03-14 10:00:00,9741.0 -2015-03-14 11:00:00,9895.0 -2015-03-14 12:00:00,9886.0 -2015-03-14 13:00:00,9839.0 -2015-03-14 14:00:00,9672.0 -2015-03-14 15:00:00,9528.0 -2015-03-14 16:00:00,9381.0 -2015-03-14 17:00:00,9257.0 -2015-03-14 18:00:00,9210.0 -2015-03-14 19:00:00,9210.0 -2015-03-14 20:00:00,9348.0 -2015-03-14 21:00:00,9980.0 -2015-03-14 22:00:00,10027.0 -2015-03-14 23:00:00,9800.0 -2015-03-15 00:00:00,9434.0 -2015-03-13 01:00:00,9766.0 -2015-03-13 02:00:00,9270.0 -2015-03-13 03:00:00,8988.0 -2015-03-13 04:00:00,8827.0 -2015-03-13 05:00:00,8811.0 -2015-03-13 06:00:00,9046.0 -2015-03-13 07:00:00,9728.0 -2015-03-13 08:00:00,10860.0 -2015-03-13 09:00:00,11355.0 -2015-03-13 10:00:00,11377.0 -2015-03-13 11:00:00,11289.0 -2015-03-13 12:00:00,11265.0 -2015-03-13 13:00:00,11225.0 -2015-03-13 14:00:00,11125.0 -2015-03-13 15:00:00,11074.0 -2015-03-13 16:00:00,10914.0 -2015-03-13 17:00:00,10739.0 -2015-03-13 18:00:00,10590.0 -2015-03-13 19:00:00,10481.0 -2015-03-13 20:00:00,10479.0 -2015-03-13 21:00:00,11018.0 -2015-03-13 22:00:00,10865.0 -2015-03-13 23:00:00,10594.0 -2015-03-14 00:00:00,9959.0 -2015-03-12 01:00:00,9859.0 -2015-03-12 02:00:00,9483.0 -2015-03-12 03:00:00,9205.0 -2015-03-12 04:00:00,9111.0 -2015-03-12 05:00:00,9099.0 -2015-03-12 06:00:00,9364.0 -2015-03-12 07:00:00,10136.0 -2015-03-12 08:00:00,11363.0 -2015-03-12 09:00:00,11861.0 -2015-03-12 10:00:00,11776.0 -2015-03-12 11:00:00,11706.0 -2015-03-12 12:00:00,11658.0 -2015-03-12 13:00:00,11529.0 -2015-03-12 14:00:00,11432.0 -2015-03-12 15:00:00,11362.0 -2015-03-12 16:00:00,11175.0 -2015-03-12 17:00:00,11001.0 -2015-03-12 18:00:00,10844.0 -2015-03-12 19:00:00,10772.0 -2015-03-12 20:00:00,10840.0 -2015-03-12 21:00:00,11532.0 -2015-03-12 22:00:00,11509.0 -2015-03-12 23:00:00,11116.0 -2015-03-13 00:00:00,10423.0 -2015-03-11 01:00:00,9875.0 -2015-03-11 02:00:00,9408.0 -2015-03-11 03:00:00,9189.0 -2015-03-11 04:00:00,9048.0 -2015-03-11 05:00:00,9043.0 -2015-03-11 06:00:00,9306.0 -2015-03-11 07:00:00,10050.0 -2015-03-11 08:00:00,11191.0 -2015-03-11 09:00:00,11687.0 -2015-03-11 10:00:00,11637.0 -2015-03-11 11:00:00,11539.0 -2015-03-11 12:00:00,11457.0 -2015-03-11 13:00:00,11388.0 -2015-03-11 14:00:00,11311.0 -2015-03-11 15:00:00,11280.0 -2015-03-11 16:00:00,11134.0 -2015-03-11 17:00:00,10977.0 -2015-03-11 18:00:00,10852.0 -2015-03-11 19:00:00,10805.0 -2015-03-11 20:00:00,10911.0 -2015-03-11 21:00:00,11596.0 -2015-03-11 22:00:00,11583.0 -2015-03-11 23:00:00,11163.0 -2015-03-12 00:00:00,10537.0 -2015-03-10 01:00:00,9878.0 -2015-03-10 02:00:00,9439.0 -2015-03-10 03:00:00,9192.0 -2015-03-10 04:00:00,9029.0 -2015-03-10 05:00:00,8999.0 -2015-03-10 06:00:00,9226.0 -2015-03-10 07:00:00,9918.0 -2015-03-10 08:00:00,11080.0 -2015-03-10 09:00:00,11777.0 -2015-03-10 10:00:00,11803.0 -2015-03-10 11:00:00,11803.0 -2015-03-10 12:00:00,11796.0 -2015-03-10 13:00:00,11677.0 -2015-03-10 14:00:00,11501.0 -2015-03-10 15:00:00,11422.0 -2015-03-10 16:00:00,11200.0 -2015-03-10 17:00:00,11015.0 -2015-03-10 18:00:00,10904.0 -2015-03-10 19:00:00,10869.0 -2015-03-10 20:00:00,11009.0 -2015-03-10 21:00:00,11659.0 -2015-03-10 22:00:00,11624.0 -2015-03-10 23:00:00,11215.0 -2015-03-11 00:00:00,10556.0 -2015-03-09 01:00:00,9808.0 -2015-03-09 02:00:00,9457.0 -2015-03-09 03:00:00,9314.0 -2015-03-09 04:00:00,9243.0 -2015-03-09 05:00:00,9353.0 -2015-03-09 06:00:00,9652.0 -2015-03-09 07:00:00,10418.0 -2015-03-09 08:00:00,11590.0 -2015-03-09 09:00:00,12204.0 -2015-03-09 10:00:00,12063.0 -2015-03-09 11:00:00,11888.0 -2015-03-09 12:00:00,11768.0 -2015-03-09 13:00:00,11597.0 -2015-03-09 14:00:00,11462.0 -2015-03-09 15:00:00,11395.0 -2015-03-09 16:00:00,11192.0 -2015-03-09 17:00:00,11000.0 -2015-03-09 18:00:00,10883.0 -2015-03-09 19:00:00,10805.0 -2015-03-09 20:00:00,10973.0 -2015-03-09 21:00:00,11722.0 -2015-03-09 22:00:00,11673.0 -2015-03-09 23:00:00,11253.0 -2015-03-10 00:00:00,10564.0 -2015-03-08 01:00:00,9825.0 -2015-03-08 02:00:00,9433.0 -2015-03-08 04:00:00,9239.0 -2015-03-08 05:00:00,9082.0 -2015-03-08 06:00:00,9071.0 -2015-03-08 07:00:00,9205.0 -2015-03-08 08:00:00,9444.0 -2015-03-08 09:00:00,9495.0 -2015-03-08 10:00:00,9679.0 -2015-03-08 11:00:00,9849.0 -2015-03-08 12:00:00,9957.0 -2015-03-08 13:00:00,9954.0 -2015-03-08 14:00:00,9890.0 -2015-03-08 15:00:00,9767.0 -2015-03-08 16:00:00,9681.0 -2015-03-08 17:00:00,9750.0 -2015-03-08 18:00:00,9828.0 -2015-03-08 19:00:00,10056.0 -2015-03-08 20:00:00,10410.0 -2015-03-08 21:00:00,11061.0 -2015-03-08 22:00:00,10987.0 -2015-03-08 23:00:00,10746.0 -2015-03-09 00:00:00,10268.0 -2015-03-07 01:00:00,11182.0 -2015-03-07 02:00:00,10671.0 -2015-03-07 03:00:00,10439.0 -2015-03-07 04:00:00,10207.0 -2015-03-07 05:00:00,10184.0 -2015-03-07 06:00:00,10192.0 -2015-03-07 07:00:00,10428.0 -2015-03-07 08:00:00,10585.0 -2015-03-07 09:00:00,10795.0 -2015-03-07 10:00:00,10865.0 -2015-03-07 11:00:00,10910.0 -2015-03-07 12:00:00,10729.0 -2015-03-07 13:00:00,10609.0 -2015-03-07 14:00:00,10357.0 -2015-03-07 15:00:00,10128.0 -2015-03-07 16:00:00,9949.0 -2015-03-07 17:00:00,9877.0 -2015-03-07 18:00:00,9954.0 -2015-03-07 19:00:00,10340.0 -2015-03-07 20:00:00,11088.0 -2015-03-07 21:00:00,11141.0 -2015-03-07 22:00:00,10995.0 -2015-03-07 23:00:00,10763.0 -2015-03-08 00:00:00,10299.0 -2015-03-06 01:00:00,11983.0 -2015-03-06 02:00:00,11559.0 -2015-03-06 03:00:00,11360.0 -2015-03-06 04:00:00,11275.0 -2015-03-06 05:00:00,11314.0 -2015-03-06 06:00:00,11546.0 -2015-03-06 07:00:00,12237.0 -2015-03-06 08:00:00,13022.0 -2015-03-06 09:00:00,13361.0 -2015-03-06 10:00:00,13366.0 -2015-03-06 11:00:00,13332.0 -2015-03-06 12:00:00,13283.0 -2015-03-06 13:00:00,13125.0 -2015-03-06 14:00:00,12872.0 -2015-03-06 15:00:00,12780.0 -2015-03-06 16:00:00,12566.0 -2015-03-06 17:00:00,12395.0 -2015-03-06 18:00:00,12454.0 -2015-03-06 19:00:00,12736.0 -2015-03-06 20:00:00,13415.0 -2015-03-06 21:00:00,13275.0 -2015-03-06 22:00:00,12979.0 -2015-03-06 23:00:00,12548.0 -2015-03-07 00:00:00,11862.0 -2015-03-05 01:00:00,11761.0 -2015-03-05 02:00:00,11399.0 -2015-03-05 03:00:00,11187.0 -2015-03-05 04:00:00,11081.0 -2015-03-05 05:00:00,11108.0 -2015-03-05 06:00:00,11386.0 -2015-03-05 07:00:00,12090.0 -2015-03-05 08:00:00,13035.0 -2015-03-05 09:00:00,13331.0 -2015-03-05 10:00:00,13376.0 -2015-03-05 11:00:00,13273.0 -2015-03-05 12:00:00,13214.0 -2015-03-05 13:00:00,13136.0 -2015-03-05 14:00:00,13022.0 -2015-03-05 15:00:00,12952.0 -2015-03-05 16:00:00,12792.0 -2015-03-05 17:00:00,12686.0 -2015-03-05 18:00:00,12783.0 -2015-03-05 19:00:00,13200.0 -2015-03-05 20:00:00,14002.0 -2015-03-05 21:00:00,14032.0 -2015-03-05 22:00:00,13829.0 -2015-03-05 23:00:00,13353.0 -2015-03-06 00:00:00,12648.0 -2015-03-04 01:00:00,10813.0 -2015-03-04 02:00:00,10368.0 -2015-03-04 03:00:00,10144.0 -2015-03-04 04:00:00,10055.0 -2015-03-04 05:00:00,10146.0 -2015-03-04 06:00:00,10504.0 -2015-03-04 07:00:00,11314.0 -2015-03-04 08:00:00,12276.0 -2015-03-04 09:00:00,12738.0 -2015-03-04 10:00:00,12863.0 -2015-03-04 11:00:00,12969.0 -2015-03-04 12:00:00,12972.0 -2015-03-04 13:00:00,12879.0 -2015-03-04 14:00:00,12742.0 -2015-03-04 15:00:00,12760.0 -2015-03-04 16:00:00,12649.0 -2015-03-04 17:00:00,12600.0 -2015-03-04 18:00:00,12803.0 -2015-03-04 19:00:00,13215.0 -2015-03-04 20:00:00,13897.0 -2015-03-04 21:00:00,13835.0 -2015-03-04 22:00:00,13623.0 -2015-03-04 23:00:00,13124.0 -2015-03-05 00:00:00,12397.0 -2015-03-03 01:00:00,10940.0 -2015-03-03 02:00:00,10516.0 -2015-03-03 03:00:00,10286.0 -2015-03-03 04:00:00,10154.0 -2015-03-03 05:00:00,10190.0 -2015-03-03 06:00:00,10515.0 -2015-03-03 07:00:00,11261.0 -2015-03-03 08:00:00,12214.0 -2015-03-03 09:00:00,12689.0 -2015-03-03 10:00:00,12927.0 -2015-03-03 11:00:00,13057.0 -2015-03-03 12:00:00,13175.0 -2015-03-03 13:00:00,13105.0 -2015-03-03 14:00:00,12979.0 -2015-03-03 15:00:00,12944.0 -2015-03-03 16:00:00,12814.0 -2015-03-03 17:00:00,12704.0 -2015-03-03 18:00:00,12737.0 -2015-03-03 19:00:00,13092.0 -2015-03-03 20:00:00,13299.0 -2015-03-03 21:00:00,13159.0 -2015-03-03 22:00:00,12813.0 -2015-03-03 23:00:00,12269.0 -2015-03-04 00:00:00,11535.0 -2015-03-02 01:00:00,10979.0 -2015-03-02 02:00:00,10657.0 -2015-03-02 03:00:00,10562.0 -2015-03-02 04:00:00,10484.0 -2015-03-02 05:00:00,10511.0 -2015-03-02 06:00:00,10808.0 -2015-03-02 07:00:00,11575.0 -2015-03-02 08:00:00,12522.0 -2015-03-02 09:00:00,12873.0 -2015-03-02 10:00:00,12826.0 -2015-03-02 11:00:00,12735.0 -2015-03-02 12:00:00,12657.0 -2015-03-02 13:00:00,12536.0 -2015-03-02 14:00:00,12379.0 -2015-03-02 15:00:00,12287.0 -2015-03-02 16:00:00,12131.0 -2015-03-02 17:00:00,12033.0 -2015-03-02 18:00:00,12238.0 -2015-03-02 19:00:00,12769.0 -2015-03-02 20:00:00,13329.0 -2015-03-02 21:00:00,13214.0 -2015-03-02 22:00:00,12943.0 -2015-03-02 23:00:00,12426.0 -2015-03-03 00:00:00,11638.0 -2015-03-01 01:00:00,11149.0 -2015-03-01 02:00:00,10762.0 -2015-03-01 03:00:00,10530.0 -2015-03-01 04:00:00,10380.0 -2015-03-01 05:00:00,10347.0 -2015-03-01 06:00:00,10342.0 -2015-03-01 07:00:00,10513.0 -2015-03-01 08:00:00,10601.0 -2015-03-01 09:00:00,10619.0 -2015-03-01 10:00:00,10924.0 -2015-03-01 11:00:00,11110.0 -2015-03-01 12:00:00,11149.0 -2015-03-01 13:00:00,11120.0 -2015-03-01 14:00:00,10929.0 -2015-03-01 15:00:00,10873.0 -2015-03-01 16:00:00,10742.0 -2015-03-01 17:00:00,10742.0 -2015-03-01 18:00:00,10980.0 -2015-03-01 19:00:00,11632.0 -2015-03-01 20:00:00,12483.0 -2015-03-01 21:00:00,12495.0 -2015-03-01 22:00:00,12310.0 -2015-03-01 23:00:00,11981.0 -2015-03-02 00:00:00,11437.0 -2015-02-28 01:00:00,12256.0 -2015-02-28 02:00:00,11795.0 -2015-02-28 03:00:00,11632.0 -2015-02-28 04:00:00,11518.0 -2015-02-28 05:00:00,11502.0 -2015-02-28 06:00:00,11603.0 -2015-02-28 07:00:00,11805.0 -2015-02-28 08:00:00,12145.0 -2015-02-28 09:00:00,12286.0 -2015-02-28 10:00:00,12407.0 -2015-02-28 11:00:00,12414.0 -2015-02-28 12:00:00,12300.0 -2015-02-28 13:00:00,12150.0 -2015-02-28 14:00:00,11852.0 -2015-02-28 15:00:00,11592.0 -2015-02-28 16:00:00,11385.0 -2015-02-28 17:00:00,11374.0 -2015-02-28 18:00:00,11626.0 -2015-02-28 19:00:00,12195.0 -2015-02-28 20:00:00,12673.0 -2015-02-28 21:00:00,12630.0 -2015-02-28 22:00:00,12456.0 -2015-02-28 23:00:00,12168.0 -2015-03-01 00:00:00,11663.0 -2015-02-27 01:00:00,12236.0 -2015-02-27 02:00:00,11835.0 -2015-02-27 03:00:00,11637.0 -2015-02-27 04:00:00,11553.0 -2015-02-27 05:00:00,11582.0 -2015-02-27 06:00:00,11791.0 -2015-02-27 07:00:00,12427.0 -2015-02-27 08:00:00,13251.0 -2015-02-27 09:00:00,13560.0 -2015-02-27 10:00:00,13560.0 -2015-02-27 11:00:00,13567.0 -2015-02-27 12:00:00,13489.0 -2015-02-27 13:00:00,13416.0 -2015-02-27 14:00:00,13242.0 -2015-02-27 15:00:00,13138.0 -2015-02-27 16:00:00,12935.0 -2015-02-27 17:00:00,12792.0 -2015-02-27 18:00:00,12895.0 -2015-02-27 19:00:00,13368.0 -2015-02-27 20:00:00,14048.0 -2015-02-27 21:00:00,13976.0 -2015-02-27 22:00:00,13810.0 -2015-02-27 23:00:00,13472.0 -2015-02-28 00:00:00,12885.0 -2015-02-26 01:00:00,11738.0 -2015-02-26 02:00:00,11325.0 -2015-02-26 03:00:00,11083.0 -2015-02-26 04:00:00,10954.0 -2015-02-26 05:00:00,11006.0 -2015-02-26 06:00:00,11260.0 -2015-02-26 07:00:00,11916.0 -2015-02-26 08:00:00,12832.0 -2015-02-26 09:00:00,13262.0 -2015-02-26 10:00:00,13442.0 -2015-02-26 11:00:00,13518.0 -2015-02-26 12:00:00,13554.0 -2015-02-26 13:00:00,13418.0 -2015-02-26 14:00:00,13273.0 -2015-02-26 15:00:00,13259.0 -2015-02-26 16:00:00,13133.0 -2015-02-26 17:00:00,13038.0 -2015-02-26 18:00:00,13161.0 -2015-02-26 19:00:00,13664.0 -2015-02-26 20:00:00,14283.0 -2015-02-26 21:00:00,14229.0 -2015-02-26 22:00:00,14039.0 -2015-02-26 23:00:00,13598.0 -2015-02-27 00:00:00,12852.0 -2015-02-25 01:00:00,11570.0 -2015-02-25 02:00:00,11174.0 -2015-02-25 03:00:00,10955.0 -2015-02-25 04:00:00,10851.0 -2015-02-25 05:00:00,10906.0 -2015-02-25 06:00:00,11200.0 -2015-02-25 07:00:00,11916.0 -2015-02-25 08:00:00,12860.0 -2015-02-25 09:00:00,13133.0 -2015-02-25 10:00:00,13202.0 -2015-02-25 11:00:00,13187.0 -2015-02-25 12:00:00,13136.0 -2015-02-25 13:00:00,13011.0 -2015-02-25 14:00:00,12948.0 -2015-02-25 15:00:00,12966.0 -2015-02-25 16:00:00,12946.0 -2015-02-25 17:00:00,12995.0 -2015-02-25 18:00:00,13240.0 -2015-02-25 19:00:00,13702.0 -2015-02-25 20:00:00,14001.0 -2015-02-25 21:00:00,13883.0 -2015-02-25 22:00:00,13654.0 -2015-02-25 23:00:00,13175.0 -2015-02-26 00:00:00,12444.0 -2015-02-24 01:00:00,12473.0 -2015-02-24 02:00:00,12084.0 -2015-02-24 03:00:00,11876.0 -2015-02-24 04:00:00,11792.0 -2015-02-24 05:00:00,11798.0 -2015-02-24 06:00:00,12028.0 -2015-02-24 07:00:00,12682.0 -2015-02-24 08:00:00,13610.0 -2015-02-24 09:00:00,13974.0 -2015-02-24 10:00:00,14141.0 -2015-02-24 11:00:00,14234.0 -2015-02-24 12:00:00,14226.0 -2015-02-24 13:00:00,14134.0 -2015-02-24 14:00:00,13970.0 -2015-02-24 15:00:00,13915.0 -2015-02-24 16:00:00,13647.0 -2015-02-24 17:00:00,13344.0 -2015-02-24 18:00:00,13231.0 -2015-02-24 19:00:00,13645.0 -2015-02-24 20:00:00,14089.0 -2015-02-24 21:00:00,13936.0 -2015-02-24 22:00:00,13613.0 -2015-02-24 23:00:00,13119.0 -2015-02-25 00:00:00,12503.0 -2015-02-23 01:00:00,11790.0 -2015-02-23 02:00:00,11526.0 -2015-02-23 03:00:00,11400.0 -2015-02-23 04:00:00,11380.0 -2015-02-23 05:00:00,11475.0 -2015-02-23 06:00:00,11782.0 -2015-02-23 07:00:00,12543.0 -2015-02-23 08:00:00,13565.0 -2015-02-23 09:00:00,13948.0 -2015-02-23 10:00:00,13989.0 -2015-02-23 11:00:00,13920.0 -2015-02-23 12:00:00,13858.0 -2015-02-23 13:00:00,13741.0 -2015-02-23 14:00:00,13599.0 -2015-02-23 15:00:00,13487.0 -2015-02-23 16:00:00,13311.0 -2015-02-23 17:00:00,13206.0 -2015-02-23 18:00:00,13350.0 -2015-02-23 19:00:00,14009.0 -2015-02-23 20:00:00,14664.0 -2015-02-23 21:00:00,14612.0 -2015-02-23 22:00:00,14383.0 -2015-02-23 23:00:00,13907.0 -2015-02-24 00:00:00,13134.0 -2015-02-22 01:00:00,10687.0 -2015-02-22 02:00:00,10331.0 -2015-02-22 03:00:00,10094.0 -2015-02-22 04:00:00,9980.0 -2015-02-22 05:00:00,9991.0 -2015-02-22 06:00:00,10082.0 -2015-02-22 07:00:00,10308.0 -2015-02-22 08:00:00,10531.0 -2015-02-22 09:00:00,10593.0 -2015-02-22 10:00:00,10906.0 -2015-02-22 11:00:00,11153.0 -2015-02-22 12:00:00,11167.0 -2015-02-22 13:00:00,11138.0 -2015-02-22 14:00:00,11089.0 -2015-02-22 15:00:00,11064.0 -2015-02-22 16:00:00,10985.0 -2015-02-22 17:00:00,11080.0 -2015-02-22 18:00:00,11460.0 -2015-02-22 19:00:00,12290.0 -2015-02-22 20:00:00,13062.0 -2015-02-22 21:00:00,13094.0 -2015-02-22 22:00:00,12898.0 -2015-02-22 23:00:00,12680.0 -2015-02-23 00:00:00,12221.0 -2015-02-21 01:00:00,12125.0 -2015-02-21 02:00:00,11592.0 -2015-02-21 03:00:00,11261.0 -2015-02-21 04:00:00,11023.0 -2015-02-21 05:00:00,10923.0 -2015-02-21 06:00:00,10900.0 -2015-02-21 07:00:00,11181.0 -2015-02-21 08:00:00,11542.0 -2015-02-21 09:00:00,11672.0 -2015-02-21 10:00:00,11986.0 -2015-02-21 11:00:00,12157.0 -2015-02-21 12:00:00,12081.0 -2015-02-21 13:00:00,11826.0 -2015-02-21 14:00:00,11574.0 -2015-02-21 15:00:00,11371.0 -2015-02-21 16:00:00,11280.0 -2015-02-21 17:00:00,11227.0 -2015-02-21 18:00:00,11449.0 -2015-02-21 19:00:00,11993.0 -2015-02-21 20:00:00,12309.0 -2015-02-21 21:00:00,12172.0 -2015-02-21 22:00:00,11989.0 -2015-02-21 23:00:00,11687.0 -2015-02-22 00:00:00,11180.0 -2015-02-20 01:00:00,12920.0 -2015-02-20 02:00:00,12522.0 -2015-02-20 03:00:00,12317.0 -2015-02-20 04:00:00,12191.0 -2015-02-20 05:00:00,12193.0 -2015-02-20 06:00:00,12409.0 -2015-02-20 07:00:00,13020.0 -2015-02-20 08:00:00,13917.0 -2015-02-20 09:00:00,14332.0 -2015-02-20 10:00:00,14540.0 -2015-02-20 11:00:00,14600.0 -2015-02-20 12:00:00,14593.0 -2015-02-20 13:00:00,14506.0 -2015-02-20 14:00:00,14283.0 -2015-02-20 15:00:00,14237.0 -2015-02-20 16:00:00,14099.0 -2015-02-20 17:00:00,14021.0 -2015-02-20 18:00:00,14002.0 -2015-02-20 19:00:00,14342.0 -2015-02-20 20:00:00,14631.0 -2015-02-20 21:00:00,14393.0 -2015-02-20 22:00:00,14058.0 -2015-02-20 23:00:00,13612.0 -2015-02-21 00:00:00,12830.0 -2015-02-19 01:00:00,13080.0 -2015-02-19 02:00:00,12693.0 -2015-02-19 03:00:00,12425.0 -2015-02-19 04:00:00,12293.0 -2015-02-19 05:00:00,12280.0 -2015-02-19 06:00:00,12529.0 -2015-02-19 07:00:00,13097.0 -2015-02-19 08:00:00,13974.0 -2015-02-19 09:00:00,14332.0 -2015-02-19 10:00:00,14519.0 -2015-02-19 11:00:00,14570.0 -2015-02-19 12:00:00,14604.0 -2015-02-19 13:00:00,14511.0 -2015-02-19 14:00:00,14401.0 -2015-02-19 15:00:00,14272.0 -2015-02-19 16:00:00,14073.0 -2015-02-19 17:00:00,13920.0 -2015-02-19 18:00:00,14054.0 -2015-02-19 19:00:00,14712.0 -2015-02-19 20:00:00,15183.0 -2015-02-19 21:00:00,15106.0 -2015-02-19 22:00:00,14819.0 -2015-02-19 23:00:00,14361.0 -2015-02-20 00:00:00,13634.0 -2015-02-18 01:00:00,12177.0 -2015-02-18 02:00:00,11776.0 -2015-02-18 03:00:00,11570.0 -2015-02-18 04:00:00,11481.0 -2015-02-18 05:00:00,11530.0 -2015-02-18 06:00:00,11774.0 -2015-02-18 07:00:00,12492.0 -2015-02-18 08:00:00,13445.0 -2015-02-18 09:00:00,13884.0 -2015-02-18 10:00:00,13971.0 -2015-02-18 11:00:00,14010.0 -2015-02-18 12:00:00,14046.0 -2015-02-18 13:00:00,13994.0 -2015-02-18 14:00:00,13938.0 -2015-02-18 15:00:00,13990.0 -2015-02-18 16:00:00,13960.0 -2015-02-18 17:00:00,14003.0 -2015-02-18 18:00:00,14244.0 -2015-02-18 19:00:00,14882.0 -2015-02-18 20:00:00,15279.0 -2015-02-18 21:00:00,15135.0 -2015-02-18 22:00:00,14895.0 -2015-02-18 23:00:00,14433.0 -2015-02-19 00:00:00,13709.0 -2015-02-17 01:00:00,11808.0 -2015-02-17 02:00:00,11336.0 -2015-02-17 03:00:00,11104.0 -2015-02-17 04:00:00,10974.0 -2015-02-17 05:00:00,10973.0 -2015-02-17 06:00:00,11210.0 -2015-02-17 07:00:00,11884.0 -2015-02-17 08:00:00,12912.0 -2015-02-17 09:00:00,13328.0 -2015-02-17 10:00:00,13396.0 -2015-02-17 11:00:00,13476.0 -2015-02-17 12:00:00,13489.0 -2015-02-17 13:00:00,13406.0 -2015-02-17 14:00:00,13290.0 -2015-02-17 15:00:00,13301.0 -2015-02-17 16:00:00,13250.0 -2015-02-17 17:00:00,13215.0 -2015-02-17 18:00:00,13447.0 -2015-02-17 19:00:00,14032.0 -2015-02-17 20:00:00,14385.0 -2015-02-17 21:00:00,14276.0 -2015-02-17 22:00:00,14052.0 -2015-02-17 23:00:00,13510.0 -2015-02-18 00:00:00,12865.0 -2015-02-16 01:00:00,11434.0 -2015-02-16 02:00:00,11110.0 -2015-02-16 03:00:00,10925.0 -2015-02-16 04:00:00,10881.0 -2015-02-16 05:00:00,10910.0 -2015-02-16 06:00:00,11177.0 -2015-02-16 07:00:00,11804.0 -2015-02-16 08:00:00,12672.0 -2015-02-16 09:00:00,13105.0 -2015-02-16 10:00:00,13391.0 -2015-02-16 11:00:00,13517.0 -2015-02-16 12:00:00,13453.0 -2015-02-16 13:00:00,13274.0 -2015-02-16 14:00:00,13183.0 -2015-02-16 15:00:00,13146.0 -2015-02-16 16:00:00,13002.0 -2015-02-16 17:00:00,12959.0 -2015-02-16 18:00:00,13101.0 -2015-02-16 19:00:00,13832.0 -2015-02-16 20:00:00,14162.0 -2015-02-16 21:00:00,14041.0 -2015-02-16 22:00:00,13765.0 -2015-02-16 23:00:00,13230.0 -2015-02-17 00:00:00,12456.0 -2015-02-15 01:00:00,12028.0 -2015-02-15 02:00:00,11685.0 -2015-02-15 03:00:00,11477.0 -2015-02-15 04:00:00,11373.0 -2015-02-15 05:00:00,11312.0 -2015-02-15 06:00:00,11300.0 -2015-02-15 07:00:00,11415.0 -2015-02-15 08:00:00,11616.0 -2015-02-15 09:00:00,11610.0 -2015-02-15 10:00:00,11816.0 -2015-02-15 11:00:00,11915.0 -2015-02-15 12:00:00,11910.0 -2015-02-15 13:00:00,11839.0 -2015-02-15 14:00:00,11736.0 -2015-02-15 15:00:00,11603.0 -2015-02-15 16:00:00,11584.0 -2015-02-15 17:00:00,11648.0 -2015-02-15 18:00:00,11935.0 -2015-02-15 19:00:00,12653.0 -2015-02-15 20:00:00,13040.0 -2015-02-15 21:00:00,12956.0 -2015-02-15 22:00:00,12734.0 -2015-02-15 23:00:00,12429.0 -2015-02-16 00:00:00,11900.0 -2015-02-14 01:00:00,11353.0 -2015-02-14 02:00:00,10845.0 -2015-02-14 03:00:00,10526.0 -2015-02-14 04:00:00,10353.0 -2015-02-14 05:00:00,10313.0 -2015-02-14 06:00:00,10424.0 -2015-02-14 07:00:00,10732.0 -2015-02-14 08:00:00,11242.0 -2015-02-14 09:00:00,11600.0 -2015-02-14 10:00:00,12037.0 -2015-02-14 11:00:00,12319.0 -2015-02-14 12:00:00,12405.0 -2015-02-14 13:00:00,12398.0 -2015-02-14 14:00:00,12302.0 -2015-02-14 15:00:00,12191.0 -2015-02-14 16:00:00,12053.0 -2015-02-14 17:00:00,12042.0 -2015-02-14 18:00:00,12247.0 -2015-02-14 19:00:00,13009.0 -2015-02-14 20:00:00,13501.0 -2015-02-14 21:00:00,13392.0 -2015-02-14 22:00:00,13284.0 -2015-02-14 23:00:00,12980.0 -2015-02-15 00:00:00,12515.0 -2015-02-13 01:00:00,11911.0 -2015-02-13 02:00:00,11516.0 -2015-02-13 03:00:00,11334.0 -2015-02-13 04:00:00,11234.0 -2015-02-13 05:00:00,11276.0 -2015-02-13 06:00:00,11515.0 -2015-02-13 07:00:00,12082.0 -2015-02-13 08:00:00,13128.0 -2015-02-13 09:00:00,13463.0 -2015-02-13 10:00:00,13620.0 -2015-02-13 11:00:00,13701.0 -2015-02-13 12:00:00,13734.0 -2015-02-13 13:00:00,13690.0 -2015-02-13 14:00:00,13601.0 -2015-02-13 15:00:00,13538.0 -2015-02-13 16:00:00,13444.0 -2015-02-13 17:00:00,13371.0 -2015-02-13 18:00:00,13427.0 -2015-02-13 19:00:00,13837.0 -2015-02-13 20:00:00,13930.0 -2015-02-13 21:00:00,13656.0 -2015-02-13 22:00:00,13269.0 -2015-02-13 23:00:00,12870.0 -2015-02-14 00:00:00,12115.0 -2015-02-12 01:00:00,11480.0 -2015-02-12 02:00:00,11061.0 -2015-02-12 03:00:00,10825.0 -2015-02-12 04:00:00,10779.0 -2015-02-12 05:00:00,10850.0 -2015-02-12 06:00:00,11185.0 -2015-02-12 07:00:00,11955.0 -2015-02-12 08:00:00,13075.0 -2015-02-12 09:00:00,13461.0 -2015-02-12 10:00:00,13503.0 -2015-02-12 11:00:00,13497.0 -2015-02-12 12:00:00,13482.0 -2015-02-12 13:00:00,13334.0 -2015-02-12 14:00:00,13196.0 -2015-02-12 15:00:00,13086.0 -2015-02-12 16:00:00,12928.0 -2015-02-12 17:00:00,12839.0 -2015-02-12 18:00:00,13139.0 -2015-02-12 19:00:00,13863.0 -2015-02-12 20:00:00,14189.0 -2015-02-12 21:00:00,14028.0 -2015-02-12 22:00:00,13774.0 -2015-02-12 23:00:00,13295.0 -2015-02-13 00:00:00,12579.0 -2015-02-11 01:00:00,10754.0 -2015-02-11 02:00:00,10303.0 -2015-02-11 03:00:00,10061.0 -2015-02-11 04:00:00,9933.0 -2015-02-11 05:00:00,9945.0 -2015-02-11 06:00:00,10186.0 -2015-02-11 07:00:00,10941.0 -2015-02-11 08:00:00,12052.0 -2015-02-11 09:00:00,12516.0 -2015-02-11 10:00:00,12626.0 -2015-02-11 11:00:00,12695.0 -2015-02-11 12:00:00,12702.0 -2015-02-11 13:00:00,12647.0 -2015-02-11 14:00:00,12633.0 -2015-02-11 15:00:00,12689.0 -2015-02-11 16:00:00,12648.0 -2015-02-11 17:00:00,12622.0 -2015-02-11 18:00:00,12798.0 -2015-02-11 19:00:00,13388.0 -2015-02-11 20:00:00,13569.0 -2015-02-11 21:00:00,13438.0 -2015-02-11 22:00:00,13219.0 -2015-02-11 23:00:00,12782.0 -2015-02-12 00:00:00,12092.0 -2015-02-10 01:00:00,11057.0 -2015-02-10 02:00:00,10625.0 -2015-02-10 03:00:00,10383.0 -2015-02-10 04:00:00,10266.0 -2015-02-10 05:00:00,10278.0 -2015-02-10 06:00:00,10567.0 -2015-02-10 07:00:00,11302.0 -2015-02-10 08:00:00,12377.0 -2015-02-10 09:00:00,12700.0 -2015-02-10 10:00:00,12763.0 -2015-02-10 11:00:00,12741.0 -2015-02-10 12:00:00,12592.0 -2015-02-10 13:00:00,12388.0 -2015-02-10 14:00:00,12266.0 -2015-02-10 15:00:00,12162.0 -2015-02-10 16:00:00,12046.0 -2015-02-10 17:00:00,11969.0 -2015-02-10 18:00:00,12246.0 -2015-02-10 19:00:00,12994.0 -2015-02-10 20:00:00,13203.0 -2015-02-10 21:00:00,13073.0 -2015-02-10 22:00:00,12859.0 -2015-02-10 23:00:00,12328.0 -2015-02-11 00:00:00,11481.0 -2015-02-09 01:00:00,10136.0 -2015-02-09 02:00:00,9857.0 -2015-02-09 03:00:00,9711.0 -2015-02-09 04:00:00,9653.0 -2015-02-09 05:00:00,9720.0 -2015-02-09 06:00:00,10065.0 -2015-02-09 07:00:00,10860.0 -2015-02-09 08:00:00,12083.0 -2015-02-09 09:00:00,12696.0 -2015-02-09 10:00:00,12883.0 -2015-02-09 11:00:00,13009.0 -2015-02-09 12:00:00,13088.0 -2015-02-09 13:00:00,13076.0 -2015-02-09 14:00:00,12969.0 -2015-02-09 15:00:00,12934.0 -2015-02-09 16:00:00,12824.0 -2015-02-09 17:00:00,12746.0 -2015-02-09 18:00:00,12898.0 -2015-02-09 19:00:00,13457.0 -2015-02-09 20:00:00,13591.0 -2015-02-09 21:00:00,13389.0 -2015-02-09 22:00:00,13115.0 -2015-02-09 23:00:00,12568.0 -2015-02-10 00:00:00,11790.0 -2015-02-08 01:00:00,9928.0 -2015-02-08 02:00:00,9511.0 -2015-02-08 03:00:00,9261.0 -2015-02-08 04:00:00,9073.0 -2015-02-08 05:00:00,8988.0 -2015-02-08 06:00:00,8971.0 -2015-02-08 07:00:00,9108.0 -2015-02-08 08:00:00,9367.0 -2015-02-08 09:00:00,9373.0 -2015-02-08 10:00:00,9643.0 -2015-02-08 11:00:00,9904.0 -2015-02-08 12:00:00,10048.0 -2015-02-08 13:00:00,10134.0 -2015-02-08 14:00:00,10189.0 -2015-02-08 15:00:00,10224.0 -2015-02-08 16:00:00,10297.0 -2015-02-08 17:00:00,10513.0 -2015-02-08 18:00:00,10953.0 -2015-02-08 19:00:00,11663.0 -2015-02-08 20:00:00,11846.0 -2015-02-08 21:00:00,11741.0 -2015-02-08 22:00:00,11527.0 -2015-02-08 23:00:00,11193.0 -2015-02-09 00:00:00,10668.0 -2015-02-07 01:00:00,11102.0 -2015-02-07 02:00:00,10615.0 -2015-02-07 03:00:00,10334.0 -2015-02-07 04:00:00,10103.0 -2015-02-07 05:00:00,10047.0 -2015-02-07 06:00:00,10043.0 -2015-02-07 07:00:00,10402.0 -2015-02-07 08:00:00,10838.0 -2015-02-07 09:00:00,11002.0 -2015-02-07 10:00:00,11122.0 -2015-02-07 11:00:00,11131.0 -2015-02-07 12:00:00,11025.0 -2015-02-07 13:00:00,10872.0 -2015-02-07 14:00:00,10613.0 -2015-02-07 15:00:00,10396.0 -2015-02-07 16:00:00,10266.0 -2015-02-07 17:00:00,10210.0 -2015-02-07 18:00:00,10384.0 -2015-02-07 19:00:00,11249.0 -2015-02-07 20:00:00,11531.0 -2015-02-07 21:00:00,11409.0 -2015-02-07 22:00:00,11165.0 -2015-02-07 23:00:00,10900.0 -2015-02-08 00:00:00,10408.0 -2015-02-06 01:00:00,12104.0 -2015-02-06 02:00:00,11673.0 -2015-02-06 03:00:00,11390.0 -2015-02-06 04:00:00,11206.0 -2015-02-06 05:00:00,11129.0 -2015-02-06 06:00:00,11304.0 -2015-02-06 07:00:00,11926.0 -2015-02-06 08:00:00,12979.0 -2015-02-06 09:00:00,13333.0 -2015-02-06 10:00:00,13334.0 -2015-02-06 11:00:00,13397.0 -2015-02-06 12:00:00,13275.0 -2015-02-06 13:00:00,13082.0 -2015-02-06 14:00:00,12811.0 -2015-02-06 15:00:00,12662.0 -2015-02-06 16:00:00,12471.0 -2015-02-06 17:00:00,12242.0 -2015-02-06 18:00:00,12376.0 -2015-02-06 19:00:00,13105.0 -2015-02-06 20:00:00,13342.0 -2015-02-06 21:00:00,13101.0 -2015-02-06 22:00:00,12819.0 -2015-02-06 23:00:00,12470.0 -2015-02-07 00:00:00,11746.0 -2015-02-05 01:00:00,11856.0 -2015-02-05 02:00:00,11473.0 -2015-02-05 03:00:00,11238.0 -2015-02-05 04:00:00,11144.0 -2015-02-05 05:00:00,11213.0 -2015-02-05 06:00:00,11517.0 -2015-02-05 07:00:00,12239.0 -2015-02-05 08:00:00,13350.0 -2015-02-05 09:00:00,13693.0 -2015-02-05 10:00:00,13698.0 -2015-02-05 11:00:00,13624.0 -2015-02-05 12:00:00,13569.0 -2015-02-05 13:00:00,13417.0 -2015-02-05 14:00:00,13292.0 -2015-02-05 15:00:00,13214.0 -2015-02-05 16:00:00,13066.0 -2015-02-05 17:00:00,13085.0 -2015-02-05 18:00:00,13339.0 -2015-02-05 19:00:00,14189.0 -2015-02-05 20:00:00,14484.0 -2015-02-05 21:00:00,14365.0 -2015-02-05 22:00:00,14109.0 -2015-02-05 23:00:00,13581.0 -2015-02-06 00:00:00,12797.0 -2015-02-04 01:00:00,11025.0 -2015-02-04 02:00:00,10548.0 -2015-02-04 03:00:00,10278.0 -2015-02-04 04:00:00,10149.0 -2015-02-04 05:00:00,10186.0 -2015-02-04 06:00:00,10466.0 -2015-02-04 07:00:00,11182.0 -2015-02-04 08:00:00,12398.0 -2015-02-04 09:00:00,12881.0 -2015-02-04 10:00:00,13065.0 -2015-02-04 11:00:00,13198.0 -2015-02-04 12:00:00,13277.0 -2015-02-04 13:00:00,13242.0 -2015-02-04 14:00:00,13169.0 -2015-02-04 15:00:00,13264.0 -2015-02-04 16:00:00,13159.0 -2015-02-04 17:00:00,13072.0 -2015-02-04 18:00:00,13196.0 -2015-02-04 19:00:00,13927.0 -2015-02-04 20:00:00,14172.0 -2015-02-04 21:00:00,14043.0 -2015-02-04 22:00:00,13810.0 -2015-02-04 23:00:00,13310.0 -2015-02-05 00:00:00,12554.0 -2015-02-03 01:00:00,11620.0 -2015-02-03 02:00:00,11265.0 -2015-02-03 03:00:00,11058.0 -2015-02-03 04:00:00,10902.0 -2015-02-03 05:00:00,10935.0 -2015-02-03 06:00:00,11142.0 -2015-02-03 07:00:00,11806.0 -2015-02-03 08:00:00,12836.0 -2015-02-03 09:00:00,13201.0 -2015-02-03 10:00:00,13243.0 -2015-02-03 11:00:00,13160.0 -2015-02-03 12:00:00,13042.0 -2015-02-03 13:00:00,12900.0 -2015-02-03 14:00:00,12766.0 -2015-02-03 15:00:00,12780.0 -2015-02-03 16:00:00,12817.0 -2015-02-03 17:00:00,12925.0 -2015-02-03 18:00:00,13130.0 -2015-02-03 19:00:00,13727.0 -2015-02-03 20:00:00,13673.0 -2015-02-03 21:00:00,13495.0 -2015-02-03 22:00:00,13175.0 -2015-02-03 23:00:00,12581.0 -2015-02-04 00:00:00,11762.0 -2015-02-02 01:00:00,10811.0 -2015-02-02 02:00:00,10423.0 -2015-02-02 03:00:00,10254.0 -2015-02-02 04:00:00,10182.0 -2015-02-02 05:00:00,10252.0 -2015-02-02 06:00:00,10549.0 -2015-02-02 07:00:00,11125.0 -2015-02-02 08:00:00,12010.0 -2015-02-02 09:00:00,12388.0 -2015-02-02 10:00:00,12509.0 -2015-02-02 11:00:00,12613.0 -2015-02-02 12:00:00,12661.0 -2015-02-02 13:00:00,12623.0 -2015-02-02 14:00:00,12517.0 -2015-02-02 15:00:00,12463.0 -2015-02-02 16:00:00,12411.0 -2015-02-02 17:00:00,12417.0 -2015-02-02 18:00:00,12724.0 -2015-02-02 19:00:00,13732.0 -2015-02-02 20:00:00,13940.0 -2015-02-02 21:00:00,13780.0 -2015-02-02 22:00:00,13532.0 -2015-02-02 23:00:00,13038.0 -2015-02-03 00:00:00,12282.0 -2015-02-01 01:00:00,10403.0 -2015-02-01 02:00:00,9994.0 -2015-02-01 03:00:00,9765.0 -2015-02-01 04:00:00,9544.0 -2015-02-01 05:00:00,9488.0 -2015-02-01 06:00:00,9540.0 -2015-02-01 07:00:00,9717.0 -2015-02-01 08:00:00,9969.0 -2015-02-01 09:00:00,10094.0 -2015-02-01 10:00:00,10379.0 -2015-02-01 11:00:00,10677.0 -2015-02-01 12:00:00,10872.0 -2015-02-01 13:00:00,11029.0 -2015-02-01 14:00:00,11178.0 -2015-02-01 15:00:00,11258.0 -2015-02-01 16:00:00,11330.0 -2015-02-01 17:00:00,11429.0 -2015-02-01 18:00:00,11750.0 -2015-02-01 19:00:00,12453.0 -2015-02-01 20:00:00,12437.0 -2015-02-01 21:00:00,12288.0 -2015-02-01 22:00:00,12084.0 -2015-02-01 23:00:00,11860.0 -2015-02-02 00:00:00,11347.0 -2015-01-31 01:00:00,11125.0 -2015-01-31 02:00:00,10642.0 -2015-01-31 03:00:00,10316.0 -2015-01-31 04:00:00,10093.0 -2015-01-31 05:00:00,10018.0 -2015-01-31 06:00:00,10088.0 -2015-01-31 07:00:00,10386.0 -2015-01-31 08:00:00,10851.0 -2015-01-31 09:00:00,10981.0 -2015-01-31 10:00:00,11235.0 -2015-01-31 11:00:00,11225.0 -2015-01-31 12:00:00,11210.0 -2015-01-31 13:00:00,11131.0 -2015-01-31 14:00:00,10988.0 -2015-01-31 15:00:00,10799.0 -2015-01-31 16:00:00,10772.0 -2015-01-31 17:00:00,10758.0 -2015-01-31 18:00:00,11072.0 -2015-01-31 19:00:00,11706.0 -2015-01-31 20:00:00,11782.0 -2015-01-31 21:00:00,11656.0 -2015-01-31 22:00:00,11564.0 -2015-01-31 23:00:00,11321.0 -2015-02-01 00:00:00,10901.0 -2015-01-30 01:00:00,10775.0 -2015-01-30 02:00:00,10354.0 -2015-01-30 03:00:00,10130.0 -2015-01-30 04:00:00,10052.0 -2015-01-30 05:00:00,10072.0 -2015-01-30 06:00:00,10382.0 -2015-01-30 07:00:00,11142.0 -2015-01-30 08:00:00,12276.0 -2015-01-30 09:00:00,12687.0 -2015-01-30 10:00:00,12639.0 -2015-01-30 11:00:00,12573.0 -2015-01-30 12:00:00,12537.0 -2015-01-30 13:00:00,12415.0 -2015-01-30 14:00:00,12252.0 -2015-01-30 15:00:00,12185.0 -2015-01-30 16:00:00,12034.0 -2015-01-30 17:00:00,11913.0 -2015-01-30 18:00:00,12168.0 -2015-01-30 19:00:00,12943.0 -2015-01-30 20:00:00,13098.0 -2015-01-30 21:00:00,12928.0 -2015-01-30 22:00:00,12655.0 -2015-01-30 23:00:00,12349.0 -2015-01-31 00:00:00,11748.0 -2015-01-29 01:00:00,10833.0 -2015-01-29 02:00:00,10325.0 -2015-01-29 03:00:00,10000.0 -2015-01-29 04:00:00,9836.0 -2015-01-29 05:00:00,9786.0 -2015-01-29 06:00:00,10054.0 -2015-01-29 07:00:00,10751.0 -2015-01-29 08:00:00,11901.0 -2015-01-29 09:00:00,12506.0 -2015-01-29 10:00:00,12687.0 -2015-01-29 11:00:00,12758.0 -2015-01-29 12:00:00,12791.0 -2015-01-29 13:00:00,12828.0 -2015-01-29 14:00:00,12852.0 -2015-01-29 15:00:00,12850.0 -2015-01-29 16:00:00,12777.0 -2015-01-29 17:00:00,12772.0 -2015-01-29 18:00:00,13044.0 -2015-01-29 19:00:00,13448.0 -2015-01-29 20:00:00,13295.0 -2015-01-29 21:00:00,13059.0 -2015-01-29 22:00:00,12728.0 -2015-01-29 23:00:00,12163.0 -2015-01-30 00:00:00,11465.0 -2015-01-28 01:00:00,10964.0 -2015-01-28 02:00:00,10492.0 -2015-01-28 03:00:00,10246.0 -2015-01-28 04:00:00,10148.0 -2015-01-28 05:00:00,10140.0 -2015-01-28 06:00:00,10456.0 -2015-01-28 07:00:00,11212.0 -2015-01-28 08:00:00,12386.0 -2015-01-28 09:00:00,12806.0 -2015-01-28 10:00:00,12799.0 -2015-01-28 11:00:00,12724.0 -2015-01-28 12:00:00,12681.0 -2015-01-28 13:00:00,12555.0 -2015-01-28 14:00:00,12421.0 -2015-01-28 15:00:00,12401.0 -2015-01-28 16:00:00,12368.0 -2015-01-28 17:00:00,12376.0 -2015-01-28 18:00:00,12673.0 -2015-01-28 19:00:00,13454.0 -2015-01-28 20:00:00,13436.0 -2015-01-28 21:00:00,13222.0 -2015-01-28 22:00:00,12902.0 -2015-01-28 23:00:00,12347.0 -2015-01-29 00:00:00,11564.0 -2015-01-27 01:00:00,10952.0 -2015-01-27 02:00:00,10516.0 -2015-01-27 03:00:00,10184.0 -2015-01-27 04:00:00,10022.0 -2015-01-27 05:00:00,9968.0 -2015-01-27 06:00:00,10243.0 -2015-01-27 07:00:00,11041.0 -2015-01-27 08:00:00,12224.0 -2015-01-27 09:00:00,12755.0 -2015-01-27 10:00:00,12784.0 -2015-01-27 11:00:00,12908.0 -2015-01-27 12:00:00,12904.0 -2015-01-27 13:00:00,12725.0 -2015-01-27 14:00:00,12485.0 -2015-01-27 15:00:00,12385.0 -2015-01-27 16:00:00,12269.0 -2015-01-27 17:00:00,12216.0 -2015-01-27 18:00:00,12490.0 -2015-01-27 19:00:00,13333.0 -2015-01-27 20:00:00,13376.0 -2015-01-27 21:00:00,13197.0 -2015-01-27 22:00:00,12954.0 -2015-01-27 23:00:00,12439.0 -2015-01-28 00:00:00,11644.0 -2015-01-26 01:00:00,10570.0 -2015-01-26 02:00:00,10292.0 -2015-01-26 03:00:00,10131.0 -2015-01-26 04:00:00,10065.0 -2015-01-26 05:00:00,10145.0 -2015-01-26 06:00:00,10492.0 -2015-01-26 07:00:00,11320.0 -2015-01-26 08:00:00,12564.0 -2015-01-26 09:00:00,13046.0 -2015-01-26 10:00:00,12994.0 -2015-01-26 11:00:00,12949.0 -2015-01-26 12:00:00,12996.0 -2015-01-26 13:00:00,12930.0 -2015-01-26 14:00:00,12858.0 -2015-01-26 15:00:00,12837.0 -2015-01-26 16:00:00,12793.0 -2015-01-26 17:00:00,12816.0 -2015-01-26 18:00:00,13202.0 -2015-01-26 19:00:00,13781.0 -2015-01-26 20:00:00,13644.0 -2015-01-26 21:00:00,13406.0 -2015-01-26 22:00:00,13065.0 -2015-01-26 23:00:00,12467.0 -2015-01-27 00:00:00,11680.0 -2015-01-25 01:00:00,9917.0 -2015-01-25 02:00:00,9558.0 -2015-01-25 03:00:00,9284.0 -2015-01-25 04:00:00,9170.0 -2015-01-25 05:00:00,9127.0 -2015-01-25 06:00:00,9194.0 -2015-01-25 07:00:00,9321.0 -2015-01-25 08:00:00,9704.0 -2015-01-25 09:00:00,9954.0 -2015-01-25 10:00:00,10297.0 -2015-01-25 11:00:00,10570.0 -2015-01-25 12:00:00,10793.0 -2015-01-25 13:00:00,10866.0 -2015-01-25 14:00:00,10973.0 -2015-01-25 15:00:00,10993.0 -2015-01-25 16:00:00,11028.0 -2015-01-25 17:00:00,11126.0 -2015-01-25 18:00:00,11498.0 -2015-01-25 19:00:00,12275.0 -2015-01-25 20:00:00,12363.0 -2015-01-25 21:00:00,12209.0 -2015-01-25 22:00:00,12040.0 -2015-01-25 23:00:00,11649.0 -2015-01-26 00:00:00,11124.0 -2015-01-24 01:00:00,10798.0 -2015-01-24 02:00:00,10325.0 -2015-01-24 03:00:00,9983.0 -2015-01-24 04:00:00,9831.0 -2015-01-24 05:00:00,9734.0 -2015-01-24 06:00:00,9823.0 -2015-01-24 07:00:00,10029.0 -2015-01-24 08:00:00,10540.0 -2015-01-24 09:00:00,10711.0 -2015-01-24 10:00:00,10864.0 -2015-01-24 11:00:00,10968.0 -2015-01-24 12:00:00,10977.0 -2015-01-24 13:00:00,10822.0 -2015-01-24 14:00:00,10718.0 -2015-01-24 15:00:00,10496.0 -2015-01-24 16:00:00,10375.0 -2015-01-24 17:00:00,10415.0 -2015-01-24 18:00:00,10801.0 -2015-01-24 19:00:00,11483.0 -2015-01-24 20:00:00,11529.0 -2015-01-24 21:00:00,11382.0 -2015-01-24 22:00:00,11234.0 -2015-01-24 23:00:00,10900.0 -2015-01-25 00:00:00,10421.0 -2015-01-23 01:00:00,10792.0 -2015-01-23 02:00:00,10292.0 -2015-01-23 03:00:00,10045.0 -2015-01-23 04:00:00,9901.0 -2015-01-23 05:00:00,9875.0 -2015-01-23 06:00:00,10187.0 -2015-01-23 07:00:00,10868.0 -2015-01-23 08:00:00,11962.0 -2015-01-23 09:00:00,12616.0 -2015-01-23 10:00:00,12696.0 -2015-01-23 11:00:00,12750.0 -2015-01-23 12:00:00,12782.0 -2015-01-23 13:00:00,12691.0 -2015-01-23 14:00:00,12578.0 -2015-01-23 15:00:00,12513.0 -2015-01-23 16:00:00,12387.0 -2015-01-23 17:00:00,12290.0 -2015-01-23 18:00:00,12522.0 -2015-01-23 19:00:00,13181.0 -2015-01-23 20:00:00,13101.0 -2015-01-23 21:00:00,12894.0 -2015-01-23 22:00:00,12564.0 -2015-01-23 23:00:00,12201.0 -2015-01-24 00:00:00,11466.0 -2015-01-22 01:00:00,10668.0 -2015-01-22 02:00:00,10193.0 -2015-01-22 03:00:00,9944.0 -2015-01-22 04:00:00,9807.0 -2015-01-22 05:00:00,9840.0 -2015-01-22 06:00:00,10096.0 -2015-01-22 07:00:00,10803.0 -2015-01-22 08:00:00,11930.0 -2015-01-22 09:00:00,12537.0 -2015-01-22 10:00:00,12601.0 -2015-01-22 11:00:00,12631.0 -2015-01-22 12:00:00,12714.0 -2015-01-22 13:00:00,12707.0 -2015-01-22 14:00:00,12670.0 -2015-01-22 15:00:00,12679.0 -2015-01-22 16:00:00,12585.0 -2015-01-22 17:00:00,12595.0 -2015-01-22 18:00:00,12908.0 -2015-01-22 19:00:00,13404.0 -2015-01-22 20:00:00,13251.0 -2015-01-22 21:00:00,13038.0 -2015-01-22 22:00:00,12760.0 -2015-01-22 23:00:00,12218.0 -2015-01-23 00:00:00,11475.0 -2015-01-21 01:00:00,10546.0 -2015-01-21 02:00:00,10079.0 -2015-01-21 03:00:00,9814.0 -2015-01-21 04:00:00,9681.0 -2015-01-21 05:00:00,9672.0 -2015-01-21 06:00:00,9941.0 -2015-01-21 07:00:00,10670.0 -2015-01-21 08:00:00,11836.0 -2015-01-21 09:00:00,12495.0 -2015-01-21 10:00:00,12545.0 -2015-01-21 11:00:00,12627.0 -2015-01-21 12:00:00,12738.0 -2015-01-21 13:00:00,12700.0 -2015-01-21 14:00:00,12635.0 -2015-01-21 15:00:00,12666.0 -2015-01-21 16:00:00,12569.0 -2015-01-21 17:00:00,12576.0 -2015-01-21 18:00:00,12889.0 -2015-01-21 19:00:00,13385.0 -2015-01-21 20:00:00,13250.0 -2015-01-21 21:00:00,13032.0 -2015-01-21 22:00:00,12707.0 -2015-01-21 23:00:00,12167.0 -2015-01-22 00:00:00,11389.0 -2015-01-20 01:00:00,10345.0 -2015-01-20 02:00:00,9908.0 -2015-01-20 03:00:00,9653.0 -2015-01-20 04:00:00,9504.0 -2015-01-20 05:00:00,9523.0 -2015-01-20 06:00:00,9786.0 -2015-01-20 07:00:00,10559.0 -2015-01-20 08:00:00,11667.0 -2015-01-20 09:00:00,12236.0 -2015-01-20 10:00:00,12234.0 -2015-01-20 11:00:00,12262.0 -2015-01-20 12:00:00,12359.0 -2015-01-20 13:00:00,12362.0 -2015-01-20 14:00:00,12345.0 -2015-01-20 15:00:00,12356.0 -2015-01-20 16:00:00,12335.0 -2015-01-20 17:00:00,12435.0 -2015-01-20 18:00:00,12897.0 -2015-01-20 19:00:00,13258.0 -2015-01-20 20:00:00,13107.0 -2015-01-20 21:00:00,12901.0 -2015-01-20 22:00:00,12604.0 -2015-01-20 23:00:00,12065.0 -2015-01-21 00:00:00,11279.0 -2015-01-19 01:00:00,10009.0 -2015-01-19 02:00:00,9766.0 -2015-01-19 03:00:00,9555.0 -2015-01-19 04:00:00,9530.0 -2015-01-19 05:00:00,9560.0 -2015-01-19 06:00:00,9915.0 -2015-01-19 07:00:00,10548.0 -2015-01-19 08:00:00,11489.0 -2015-01-19 09:00:00,11903.0 -2015-01-19 10:00:00,11954.0 -2015-01-19 11:00:00,11889.0 -2015-01-19 12:00:00,11887.0 -2015-01-19 13:00:00,11823.0 -2015-01-19 14:00:00,11724.0 -2015-01-19 15:00:00,11733.0 -2015-01-19 16:00:00,11709.0 -2015-01-19 17:00:00,11828.0 -2015-01-19 18:00:00,12279.0 -2015-01-19 19:00:00,12889.0 -2015-01-19 20:00:00,12779.0 -2015-01-19 21:00:00,12555.0 -2015-01-19 22:00:00,12255.0 -2015-01-19 23:00:00,11744.0 -2015-01-20 00:00:00,11012.0 -2015-01-18 01:00:00,10110.0 -2015-01-18 02:00:00,9735.0 -2015-01-18 03:00:00,9421.0 -2015-01-18 04:00:00,9293.0 -2015-01-18 05:00:00,9195.0 -2015-01-18 06:00:00,9232.0 -2015-01-18 07:00:00,9378.0 -2015-01-18 08:00:00,9658.0 -2015-01-18 09:00:00,9806.0 -2015-01-18 10:00:00,10050.0 -2015-01-18 11:00:00,10189.0 -2015-01-18 12:00:00,10253.0 -2015-01-18 13:00:00,10240.0 -2015-01-18 14:00:00,10159.0 -2015-01-18 15:00:00,10009.0 -2015-01-18 16:00:00,9991.0 -2015-01-18 17:00:00,9980.0 -2015-01-18 18:00:00,10449.0 -2015-01-18 19:00:00,11313.0 -2015-01-18 20:00:00,11476.0 -2015-01-18 21:00:00,11384.0 -2015-01-18 22:00:00,11258.0 -2015-01-18 23:00:00,10923.0 -2015-01-19 00:00:00,10522.0 -2015-01-17 01:00:00,11096.0 -2015-01-17 02:00:00,10581.0 -2015-01-17 03:00:00,10301.0 -2015-01-17 04:00:00,10088.0 -2015-01-17 05:00:00,9995.0 -2015-01-17 06:00:00,9998.0 -2015-01-17 07:00:00,10281.0 -2015-01-17 08:00:00,10666.0 -2015-01-17 09:00:00,10899.0 -2015-01-17 10:00:00,11016.0 -2015-01-17 11:00:00,11060.0 -2015-01-17 12:00:00,11082.0 -2015-01-17 13:00:00,11015.0 -2015-01-17 14:00:00,10882.0 -2015-01-17 15:00:00,10664.0 -2015-01-17 16:00:00,10627.0 -2015-01-17 17:00:00,10731.0 -2015-01-17 18:00:00,11150.0 -2015-01-17 19:00:00,11804.0 -2015-01-17 20:00:00,11789.0 -2015-01-17 21:00:00,11606.0 -2015-01-17 22:00:00,11455.0 -2015-01-17 23:00:00,11102.0 -2015-01-18 00:00:00,10646.0 -2015-01-16 01:00:00,11197.0 -2015-01-16 02:00:00,10753.0 -2015-01-16 03:00:00,10518.0 -2015-01-16 04:00:00,10394.0 -2015-01-16 05:00:00,10404.0 -2015-01-16 06:00:00,10645.0 -2015-01-16 07:00:00,11324.0 -2015-01-16 08:00:00,12351.0 -2015-01-16 09:00:00,12839.0 -2015-01-16 10:00:00,12916.0 -2015-01-16 11:00:00,12890.0 -2015-01-16 12:00:00,12732.0 -2015-01-16 13:00:00,12540.0 -2015-01-16 14:00:00,12389.0 -2015-01-16 15:00:00,12341.0 -2015-01-16 16:00:00,12214.0 -2015-01-16 17:00:00,12147.0 -2015-01-16 18:00:00,12506.0 -2015-01-16 19:00:00,13314.0 -2015-01-16 20:00:00,13266.0 -2015-01-16 21:00:00,13099.0 -2015-01-16 22:00:00,12773.0 -2015-01-16 23:00:00,12442.0 -2015-01-17 00:00:00,11746.0 -2015-01-15 01:00:00,11938.0 -2015-01-15 02:00:00,11470.0 -2015-01-15 03:00:00,11191.0 -2015-01-15 04:00:00,11018.0 -2015-01-15 05:00:00,11043.0 -2015-01-15 06:00:00,11248.0 -2015-01-15 07:00:00,11908.0 -2015-01-15 08:00:00,13003.0 -2015-01-15 09:00:00,13472.0 -2015-01-15 10:00:00,13551.0 -2015-01-15 11:00:00,13527.0 -2015-01-15 12:00:00,13294.0 -2015-01-15 13:00:00,13027.0 -2015-01-15 14:00:00,12822.0 -2015-01-15 15:00:00,12732.0 -2015-01-15 16:00:00,12552.0 -2015-01-15 17:00:00,12461.0 -2015-01-15 18:00:00,12870.0 -2015-01-15 19:00:00,13742.0 -2015-01-15 20:00:00,13690.0 -2015-01-15 21:00:00,13521.0 -2015-01-15 22:00:00,13220.0 -2015-01-15 23:00:00,12674.0 -2015-01-16 00:00:00,11912.0 -2015-01-14 01:00:00,12123.0 -2015-01-14 02:00:00,11726.0 -2015-01-14 03:00:00,11513.0 -2015-01-14 04:00:00,11433.0 -2015-01-14 05:00:00,11459.0 -2015-01-14 06:00:00,11719.0 -2015-01-14 07:00:00,12417.0 -2015-01-14 08:00:00,13533.0 -2015-01-14 09:00:00,13994.0 -2015-01-14 10:00:00,14026.0 -2015-01-14 11:00:00,14054.0 -2015-01-14 12:00:00,13964.0 -2015-01-14 13:00:00,13737.0 -2015-01-14 14:00:00,13629.0 -2015-01-14 15:00:00,13544.0 -2015-01-14 16:00:00,13453.0 -2015-01-14 17:00:00,13360.0 -2015-01-14 18:00:00,13757.0 -2015-01-14 19:00:00,14551.0 -2015-01-14 20:00:00,14490.0 -2015-01-14 21:00:00,14341.0 -2015-01-14 22:00:00,14034.0 -2015-01-14 23:00:00,13496.0 -2015-01-15 00:00:00,12702.0 -2015-01-13 01:00:00,11905.0 -2015-01-13 02:00:00,11401.0 -2015-01-13 03:00:00,11136.0 -2015-01-13 04:00:00,10955.0 -2015-01-13 05:00:00,10973.0 -2015-01-13 06:00:00,11223.0 -2015-01-13 07:00:00,11934.0 -2015-01-13 08:00:00,13040.0 -2015-01-13 09:00:00,13584.0 -2015-01-13 10:00:00,13598.0 -2015-01-13 11:00:00,13627.0 -2015-01-13 12:00:00,13581.0 -2015-01-13 13:00:00,13432.0 -2015-01-13 14:00:00,13278.0 -2015-01-13 15:00:00,13203.0 -2015-01-13 16:00:00,13071.0 -2015-01-13 17:00:00,13041.0 -2015-01-13 18:00:00,13464.0 -2015-01-13 19:00:00,14360.0 -2015-01-13 20:00:00,14363.0 -2015-01-13 21:00:00,14213.0 -2015-01-13 22:00:00,14002.0 -2015-01-13 23:00:00,13512.0 -2015-01-14 00:00:00,12799.0 -2015-01-12 01:00:00,10837.0 -2015-01-12 02:00:00,10446.0 -2015-01-12 03:00:00,10254.0 -2015-01-12 04:00:00,10128.0 -2015-01-12 05:00:00,10201.0 -2015-01-12 06:00:00,10521.0 -2015-01-12 07:00:00,11310.0 -2015-01-12 08:00:00,12574.0 -2015-01-12 09:00:00,13137.0 -2015-01-12 10:00:00,13255.0 -2015-01-12 11:00:00,13249.0 -2015-01-12 12:00:00,13274.0 -2015-01-12 13:00:00,13206.0 -2015-01-12 14:00:00,13184.0 -2015-01-12 15:00:00,13200.0 -2015-01-12 16:00:00,13150.0 -2015-01-12 17:00:00,13199.0 -2015-01-12 18:00:00,13685.0 -2015-01-12 19:00:00,14456.0 -2015-01-12 20:00:00,14465.0 -2015-01-12 21:00:00,14280.0 -2015-01-12 22:00:00,13975.0 -2015-01-12 23:00:00,13444.0 -2015-01-13 00:00:00,12676.0 -2015-01-11 01:00:00,11634.0 -2015-01-11 02:00:00,11169.0 -2015-01-11 03:00:00,10882.0 -2015-01-11 04:00:00,10719.0 -2015-01-11 05:00:00,10574.0 -2015-01-11 06:00:00,10589.0 -2015-01-11 07:00:00,10695.0 -2015-01-11 08:00:00,10946.0 -2015-01-11 09:00:00,10981.0 -2015-01-11 10:00:00,11142.0 -2015-01-11 11:00:00,11241.0 -2015-01-11 12:00:00,11293.0 -2015-01-11 13:00:00,11220.0 -2015-01-11 14:00:00,11239.0 -2015-01-11 15:00:00,11166.0 -2015-01-11 16:00:00,11259.0 -2015-01-11 17:00:00,11483.0 -2015-01-11 18:00:00,12140.0 -2015-01-11 19:00:00,12788.0 -2015-01-11 20:00:00,12767.0 -2015-01-11 21:00:00,12610.0 -2015-01-11 22:00:00,12306.0 -2015-01-11 23:00:00,11920.0 -2015-01-12 00:00:00,11336.0 -2015-01-10 01:00:00,13132.0 -2015-01-10 02:00:00,12665.0 -2015-01-10 03:00:00,12315.0 -2015-01-10 04:00:00,12145.0 -2015-01-10 05:00:00,12110.0 -2015-01-10 06:00:00,12184.0 -2015-01-10 07:00:00,12442.0 -2015-01-10 08:00:00,12865.0 -2015-01-10 09:00:00,13056.0 -2015-01-10 10:00:00,13175.0 -2015-01-10 11:00:00,13250.0 -2015-01-10 12:00:00,13223.0 -2015-01-10 13:00:00,13110.0 -2015-01-10 14:00:00,12846.0 -2015-01-10 15:00:00,12615.0 -2015-01-10 16:00:00,12815.0 -2015-01-10 17:00:00,12993.0 -2015-01-10 18:00:00,13505.0 -2015-01-10 19:00:00,13924.0 -2015-01-10 20:00:00,13800.0 -2015-01-10 21:00:00,13568.0 -2015-01-10 22:00:00,13266.0 -2015-01-10 23:00:00,12890.0 -2015-01-11 00:00:00,12270.0 -2015-01-09 01:00:00,12880.0 -2015-01-09 02:00:00,12505.0 -2015-01-09 03:00:00,12247.0 -2015-01-09 04:00:00,12059.0 -2015-01-09 05:00:00,12025.0 -2015-01-09 06:00:00,12229.0 -2015-01-09 07:00:00,12814.0 -2015-01-09 08:00:00,13813.0 -2015-01-09 09:00:00,14298.0 -2015-01-09 10:00:00,14312.0 -2015-01-09 11:00:00,14382.0 -2015-01-09 12:00:00,14452.0 -2015-01-09 13:00:00,14474.0 -2015-01-09 14:00:00,14451.0 -2015-01-09 15:00:00,14455.0 -2015-01-09 16:00:00,14364.0 -2015-01-09 17:00:00,14358.0 -2015-01-09 18:00:00,14798.0 -2015-01-09 19:00:00,15509.0 -2015-01-09 20:00:00,15502.0 -2015-01-09 21:00:00,15316.0 -2015-01-09 22:00:00,14998.0 -2015-01-09 23:00:00,14593.0 -2015-01-10 00:00:00,13817.0 -2015-01-08 01:00:00,13356.0 -2015-01-08 02:00:00,12965.0 -2015-01-08 03:00:00,12730.0 -2015-01-08 04:00:00,12596.0 -2015-01-08 05:00:00,12590.0 -2015-01-08 06:00:00,12792.0 -2015-01-08 07:00:00,13293.0 -2015-01-08 08:00:00,14159.0 -2015-01-08 09:00:00,14662.0 -2015-01-08 10:00:00,14893.0 -2015-01-08 11:00:00,15113.0 -2015-01-08 12:00:00,15373.0 -2015-01-08 13:00:00,15398.0 -2015-01-08 14:00:00,15381.0 -2015-01-08 15:00:00,15412.0 -2015-01-08 16:00:00,15281.0 -2015-01-08 17:00:00,15145.0 -2015-01-08 18:00:00,15473.0 -2015-01-08 19:00:00,15951.0 -2015-01-08 20:00:00,15775.0 -2015-01-08 21:00:00,15494.0 -2015-01-08 22:00:00,15100.0 -2015-01-08 23:00:00,14470.0 -2015-01-09 00:00:00,13667.0 -2015-01-07 01:00:00,12832.0 -2015-01-07 02:00:00,12396.0 -2015-01-07 03:00:00,12113.0 -2015-01-07 04:00:00,12027.0 -2015-01-07 05:00:00,12006.0 -2015-01-07 06:00:00,12249.0 -2015-01-07 07:00:00,12866.0 -2015-01-07 08:00:00,13792.0 -2015-01-07 09:00:00,14244.0 -2015-01-07 10:00:00,14353.0 -2015-01-07 11:00:00,14518.0 -2015-01-07 12:00:00,14620.0 -2015-01-07 13:00:00,14643.0 -2015-01-07 14:00:00,14545.0 -2015-01-07 15:00:00,14540.0 -2015-01-07 16:00:00,14445.0 -2015-01-07 17:00:00,14450.0 -2015-01-07 18:00:00,14969.0 -2015-01-07 19:00:00,15816.0 -2015-01-07 20:00:00,15778.0 -2015-01-07 21:00:00,15578.0 -2015-01-07 22:00:00,15314.0 -2015-01-07 23:00:00,14824.0 -2015-01-08 00:00:00,14072.0 -2015-01-06 01:00:00,12414.0 -2015-01-06 02:00:00,11904.0 -2015-01-06 03:00:00,11598.0 -2015-01-06 04:00:00,11472.0 -2015-01-06 05:00:00,11492.0 -2015-01-06 06:00:00,11712.0 -2015-01-06 07:00:00,12421.0 -2015-01-06 08:00:00,13479.0 -2015-01-06 09:00:00,13897.0 -2015-01-06 10:00:00,13859.0 -2015-01-06 11:00:00,13838.0 -2015-01-06 12:00:00,13875.0 -2015-01-06 13:00:00,13748.0 -2015-01-06 14:00:00,13713.0 -2015-01-06 15:00:00,13785.0 -2015-01-06 16:00:00,13783.0 -2015-01-06 17:00:00,13920.0 -2015-01-06 18:00:00,14644.0 -2015-01-06 19:00:00,15347.0 -2015-01-06 20:00:00,15297.0 -2015-01-06 21:00:00,15099.0 -2015-01-06 22:00:00,14804.0 -2015-01-06 23:00:00,14301.0 -2015-01-07 00:00:00,13549.0 -2015-01-05 01:00:00,11601.0 -2015-01-05 02:00:00,11325.0 -2015-01-05 03:00:00,11209.0 -2015-01-05 04:00:00,11181.0 -2015-01-05 05:00:00,11217.0 -2015-01-05 06:00:00,11570.0 -2015-01-05 07:00:00,12312.0 -2015-01-05 08:00:00,13465.0 -2015-01-05 09:00:00,14010.0 -2015-01-05 10:00:00,14073.0 -2015-01-05 11:00:00,14079.0 -2015-01-05 12:00:00,14133.0 -2015-01-05 13:00:00,14176.0 -2015-01-05 14:00:00,14122.0 -2015-01-05 15:00:00,14234.0 -2015-01-05 16:00:00,14187.0 -2015-01-05 17:00:00,14248.0 -2015-01-05 18:00:00,14865.0 -2015-01-05 19:00:00,15447.0 -2015-01-05 20:00:00,15322.0 -2015-01-05 21:00:00,15122.0 -2015-01-05 22:00:00,14827.0 -2015-01-05 23:00:00,14198.0 -2015-01-06 00:00:00,13262.0 -2015-01-04 01:00:00,10217.0 -2015-01-04 02:00:00,9752.0 -2015-01-04 03:00:00,9384.0 -2015-01-04 04:00:00,9251.0 -2015-01-04 05:00:00,9197.0 -2015-01-04 06:00:00,9251.0 -2015-01-04 07:00:00,9404.0 -2015-01-04 08:00:00,9677.0 -2015-01-04 09:00:00,9805.0 -2015-01-04 10:00:00,10078.0 -2015-01-04 11:00:00,10324.0 -2015-01-04 12:00:00,10657.0 -2015-01-04 13:00:00,10858.0 -2015-01-04 14:00:00,11023.0 -2015-01-04 15:00:00,11060.0 -2015-01-04 16:00:00,11119.0 -2015-01-04 17:00:00,11253.0 -2015-01-04 18:00:00,12008.0 -2015-01-04 19:00:00,12975.0 -2015-01-04 20:00:00,13078.0 -2015-01-04 21:00:00,13106.0 -2015-01-04 22:00:00,12990.0 -2015-01-04 23:00:00,12627.0 -2015-01-05 00:00:00,12084.0 -2015-01-03 01:00:00,10520.0 -2015-01-03 02:00:00,9963.0 -2015-01-03 03:00:00,9622.0 -2015-01-03 04:00:00,9475.0 -2015-01-03 05:00:00,9423.0 -2015-01-03 06:00:00,9494.0 -2015-01-03 07:00:00,9733.0 -2015-01-03 08:00:00,10135.0 -2015-01-03 09:00:00,10432.0 -2015-01-03 10:00:00,10696.0 -2015-01-03 11:00:00,11014.0 -2015-01-03 12:00:00,11244.0 -2015-01-03 13:00:00,11249.0 -2015-01-03 14:00:00,11328.0 -2015-01-03 15:00:00,11200.0 -2015-01-03 16:00:00,11073.0 -2015-01-03 17:00:00,11033.0 -2015-01-03 18:00:00,11517.0 -2015-01-03 19:00:00,12009.0 -2015-01-03 20:00:00,12010.0 -2015-01-03 21:00:00,11840.0 -2015-01-03 22:00:00,11648.0 -2015-01-03 23:00:00,11302.0 -2015-01-04 00:00:00,10823.0 -2015-01-02 01:00:00,10304.0 -2015-01-02 02:00:00,9884.0 -2015-01-02 03:00:00,9620.0 -2015-01-02 04:00:00,9513.0 -2015-01-02 05:00:00,9533.0 -2015-01-02 06:00:00,9791.0 -2015-01-02 07:00:00,10346.0 -2015-01-02 08:00:00,11115.0 -2015-01-02 09:00:00,11543.0 -2015-01-02 10:00:00,11598.0 -2015-01-02 11:00:00,11661.0 -2015-01-02 12:00:00,11701.0 -2015-01-02 13:00:00,11628.0 -2015-01-02 14:00:00,11469.0 -2015-01-02 15:00:00,11445.0 -2015-01-02 16:00:00,11394.0 -2015-01-02 17:00:00,11478.0 -2015-01-02 18:00:00,12211.0 -2015-01-02 19:00:00,12804.0 -2015-01-02 20:00:00,12666.0 -2015-01-02 21:00:00,12436.0 -2015-01-02 22:00:00,12185.0 -2015-01-02 23:00:00,11823.0 -2015-01-03 00:00:00,11194.0 -2015-01-01 01:00:00,11341.0 -2015-01-01 02:00:00,10969.0 -2015-01-01 03:00:00,10651.0 -2015-01-01 04:00:00,10425.0 -2015-01-01 05:00:00,10299.0 -2015-01-01 06:00:00,10294.0 -2015-01-01 07:00:00,10443.0 -2015-01-01 08:00:00,10634.0 -2015-01-01 09:00:00,10532.0 -2015-01-01 10:00:00,10433.0 -2015-01-01 11:00:00,10543.0 -2015-01-01 12:00:00,10635.0 -2015-01-01 13:00:00,10623.0 -2015-01-01 14:00:00,10592.0 -2015-01-01 15:00:00,10511.0 -2015-01-01 16:00:00,10503.0 -2015-01-01 17:00:00,10585.0 -2015-01-01 18:00:00,11404.0 -2015-01-01 19:00:00,12140.0 -2015-01-01 20:00:00,12076.0 -2015-01-01 21:00:00,11919.0 -2015-01-01 22:00:00,11712.0 -2015-01-01 23:00:00,11397.0 -2015-01-02 00:00:00,10826.0 -2016-12-31 01:00:00,10419.0 -2016-12-31 02:00:00,9940.0 -2016-12-31 03:00:00,9604.0 -2016-12-31 04:00:00,9318.0 -2016-12-31 05:00:00,9224.0 -2016-12-31 06:00:00,9209.0 -2016-12-31 07:00:00,9343.0 -2016-12-31 08:00:00,9715.0 -2016-12-31 09:00:00,9809.0 -2016-12-31 10:00:00,9974.0 -2016-12-31 11:00:00,10087.0 -2016-12-31 12:00:00,10187.0 -2016-12-31 13:00:00,10299.0 -2016-12-31 14:00:00,10385.0 -2016-12-31 15:00:00,10261.0 -2016-12-31 16:00:00,10313.0 -2016-12-31 17:00:00,10416.0 -2016-12-31 18:00:00,11037.0 -2016-12-31 19:00:00,11587.0 -2016-12-31 20:00:00,11549.0 -2016-12-31 21:00:00,11273.0 -2016-12-31 22:00:00,11085.0 -2016-12-31 23:00:00,10801.0 -2017-01-01 00:00:00,10500.0 -2016-12-30 01:00:00,10596.0 -2016-12-30 02:00:00,10137.0 -2016-12-30 03:00:00,9838.0 -2016-12-30 04:00:00,9715.0 -2016-12-30 05:00:00,9657.0 -2016-12-30 06:00:00,9885.0 -2016-12-30 07:00:00,10392.0 -2016-12-30 08:00:00,11013.0 -2016-12-30 09:00:00,11286.0 -2016-12-30 10:00:00,11457.0 -2016-12-30 11:00:00,11507.0 -2016-12-30 12:00:00,11576.0 -2016-12-30 13:00:00,11506.0 -2016-12-30 14:00:00,11367.0 -2016-12-30 15:00:00,11331.0 -2016-12-30 16:00:00,11369.0 -2016-12-30 17:00:00,11363.0 -2016-12-30 18:00:00,11930.0 -2016-12-30 19:00:00,12533.0 -2016-12-30 20:00:00,12375.0 -2016-12-30 21:00:00,12229.0 -2016-12-30 22:00:00,11982.0 -2016-12-30 23:00:00,11613.0 -2016-12-31 00:00:00,11056.0 -2016-12-29 01:00:00,10387.0 -2016-12-29 02:00:00,9969.0 -2016-12-29 03:00:00,9694.0 -2016-12-29 04:00:00,9529.0 -2016-12-29 05:00:00,9567.0 -2016-12-29 06:00:00,9770.0 -2016-12-29 07:00:00,10340.0 -2016-12-29 08:00:00,11180.0 -2016-12-29 09:00:00,11573.0 -2016-12-29 10:00:00,11632.0 -2016-12-29 11:00:00,11755.0 -2016-12-29 12:00:00,11844.0 -2016-12-29 13:00:00,12104.0 -2016-12-29 14:00:00,12226.0 -2016-12-29 15:00:00,12241.0 -2016-12-29 16:00:00,12251.0 -2016-12-29 17:00:00,12198.0 -2016-12-29 18:00:00,12693.0 -2016-12-29 19:00:00,13087.0 -2016-12-29 20:00:00,12797.0 -2016-12-29 21:00:00,12599.0 -2016-12-29 22:00:00,12293.0 -2016-12-29 23:00:00,11900.0 -2016-12-30 00:00:00,11254.0 -2016-12-28 01:00:00,10693.0 -2016-12-28 02:00:00,10244.0 -2016-12-28 03:00:00,9967.0 -2016-12-28 04:00:00,9820.0 -2016-12-28 05:00:00,9836.0 -2016-12-28 06:00:00,10109.0 -2016-12-28 07:00:00,10653.0 -2016-12-28 08:00:00,11432.0 -2016-12-28 09:00:00,11805.0 -2016-12-28 10:00:00,11856.0 -2016-12-28 11:00:00,11878.0 -2016-12-28 12:00:00,11845.0 -2016-12-28 13:00:00,11758.0 -2016-12-28 14:00:00,11727.0 -2016-12-28 15:00:00,11703.0 -2016-12-28 16:00:00,11568.0 -2016-12-28 17:00:00,11644.0 -2016-12-28 18:00:00,12216.0 -2016-12-28 19:00:00,12699.0 -2016-12-28 20:00:00,12476.0 -2016-12-28 21:00:00,12292.0 -2016-12-28 22:00:00,11993.0 -2016-12-28 23:00:00,11596.0 -2016-12-29 00:00:00,10987.0 -2016-12-27 01:00:00,10095.0 -2016-12-27 02:00:00,9700.0 -2016-12-27 03:00:00,9524.0 -2016-12-27 04:00:00,9399.0 -2016-12-27 05:00:00,9432.0 -2016-12-27 06:00:00,9659.0 -2016-12-27 07:00:00,10313.0 -2016-12-27 08:00:00,11115.0 -2016-12-27 09:00:00,11560.0 -2016-12-27 10:00:00,11743.0 -2016-12-27 11:00:00,11900.0 -2016-12-27 12:00:00,12075.0 -2016-12-27 13:00:00,12146.0 -2016-12-27 14:00:00,12060.0 -2016-12-27 15:00:00,11995.0 -2016-12-27 16:00:00,11915.0 -2016-12-27 17:00:00,11929.0 -2016-12-27 18:00:00,12508.0 -2016-12-27 19:00:00,13086.0 -2016-12-27 20:00:00,12919.0 -2016-12-27 21:00:00,12724.0 -2016-12-27 22:00:00,12459.0 -2016-12-27 23:00:00,12038.0 -2016-12-28 00:00:00,11360.0 -2016-12-26 01:00:00,9677.0 -2016-12-26 02:00:00,9222.0 -2016-12-26 03:00:00,8820.0 -2016-12-26 04:00:00,8636.0 -2016-12-26 05:00:00,8498.0 -2016-12-26 06:00:00,8613.0 -2016-12-26 07:00:00,8848.0 -2016-12-26 08:00:00,9187.0 -2016-12-26 09:00:00,9326.0 -2016-12-26 10:00:00,9417.0 -2016-12-26 11:00:00,9519.0 -2016-12-26 12:00:00,9732.0 -2016-12-26 13:00:00,9709.0 -2016-12-26 14:00:00,9635.0 -2016-12-26 15:00:00,9576.0 -2016-12-26 16:00:00,9522.0 -2016-12-26 17:00:00,9611.0 -2016-12-26 18:00:00,10417.0 -2016-12-26 19:00:00,11279.0 -2016-12-26 20:00:00,11431.0 -2016-12-26 21:00:00,11436.0 -2016-12-26 22:00:00,11335.0 -2016-12-26 23:00:00,11144.0 -2016-12-27 00:00:00,10617.0 -2016-12-25 01:00:00,9755.0 -2016-12-25 02:00:00,9348.0 -2016-12-25 03:00:00,9079.0 -2016-12-25 04:00:00,8880.0 -2016-12-25 05:00:00,8782.0 -2016-12-25 06:00:00,8800.0 -2016-12-25 07:00:00,8912.0 -2016-12-25 08:00:00,9155.0 -2016-12-25 09:00:00,9384.0 -2016-12-25 10:00:00,9581.0 -2016-12-25 11:00:00,9870.0 -2016-12-25 12:00:00,9974.0 -2016-12-25 13:00:00,10052.0 -2016-12-25 14:00:00,10049.0 -2016-12-25 15:00:00,9992.0 -2016-12-25 16:00:00,9949.0 -2016-12-25 17:00:00,10086.0 -2016-12-25 18:00:00,10599.0 -2016-12-25 19:00:00,10890.0 -2016-12-25 20:00:00,10837.0 -2016-12-25 21:00:00,10769.0 -2016-12-25 22:00:00,10709.0 -2016-12-25 23:00:00,10518.0 -2016-12-26 00:00:00,10135.0 -2016-12-24 01:00:00,10318.0 -2016-12-24 02:00:00,9743.0 -2016-12-24 03:00:00,9377.0 -2016-12-24 04:00:00,9122.0 -2016-12-24 05:00:00,9025.0 -2016-12-24 06:00:00,9025.0 -2016-12-24 07:00:00,9205.0 -2016-12-24 08:00:00,9515.0 -2016-12-24 09:00:00,9756.0 -2016-12-24 10:00:00,9999.0 -2016-12-24 11:00:00,10214.0 -2016-12-24 12:00:00,10328.0 -2016-12-24 13:00:00,10340.0 -2016-12-24 14:00:00,10284.0 -2016-12-24 15:00:00,10190.0 -2016-12-24 16:00:00,10179.0 -2016-12-24 17:00:00,10280.0 -2016-12-24 18:00:00,10823.0 -2016-12-24 19:00:00,11107.0 -2016-12-24 20:00:00,10944.0 -2016-12-24 21:00:00,10766.0 -2016-12-24 22:00:00,10637.0 -2016-12-24 23:00:00,10489.0 -2016-12-25 00:00:00,10194.0 -2016-12-23 01:00:00,10985.0 -2016-12-23 02:00:00,10417.0 -2016-12-23 03:00:00,10070.0 -2016-12-23 04:00:00,9859.0 -2016-12-23 05:00:00,9792.0 -2016-12-23 06:00:00,9987.0 -2016-12-23 07:00:00,10461.0 -2016-12-23 08:00:00,11104.0 -2016-12-23 09:00:00,11515.0 -2016-12-23 10:00:00,11716.0 -2016-12-23 11:00:00,11812.0 -2016-12-23 12:00:00,11892.0 -2016-12-23 13:00:00,11772.0 -2016-12-23 14:00:00,11682.0 -2016-12-23 15:00:00,11638.0 -2016-12-23 16:00:00,11488.0 -2016-12-23 17:00:00,11615.0 -2016-12-23 18:00:00,12348.0 -2016-12-23 19:00:00,12712.0 -2016-12-23 20:00:00,12436.0 -2016-12-23 21:00:00,12188.0 -2016-12-23 22:00:00,12020.0 -2016-12-23 23:00:00,11683.0 -2016-12-24 00:00:00,11038.0 -2016-12-22 01:00:00,11280.0 -2016-12-22 02:00:00,10666.0 -2016-12-22 03:00:00,10329.0 -2016-12-22 04:00:00,10106.0 -2016-12-22 05:00:00,10026.0 -2016-12-22 06:00:00,10248.0 -2016-12-22 07:00:00,10849.0 -2016-12-22 08:00:00,11768.0 -2016-12-22 09:00:00,12189.0 -2016-12-22 10:00:00,12233.0 -2016-12-22 11:00:00,12168.0 -2016-12-22 12:00:00,12149.0 -2016-12-22 13:00:00,12142.0 -2016-12-22 14:00:00,11859.0 -2016-12-22 15:00:00,11823.0 -2016-12-22 16:00:00,11704.0 -2016-12-22 17:00:00,11803.0 -2016-12-22 18:00:00,12596.0 -2016-12-22 19:00:00,13255.0 -2016-12-22 20:00:00,13150.0 -2016-12-22 21:00:00,13033.0 -2016-12-22 22:00:00,12801.0 -2016-12-22 23:00:00,12416.0 -2016-12-23 00:00:00,11746.0 -2016-12-21 01:00:00,11725.0 -2016-12-21 02:00:00,11166.0 -2016-12-21 03:00:00,10877.0 -2016-12-21 04:00:00,10707.0 -2016-12-21 05:00:00,10694.0 -2016-12-21 06:00:00,10929.0 -2016-12-21 07:00:00,11574.0 -2016-12-21 08:00:00,12408.0 -2016-12-21 09:00:00,13001.0 -2016-12-21 10:00:00,13134.0 -2016-12-21 11:00:00,13110.0 -2016-12-21 12:00:00,13101.0 -2016-12-21 13:00:00,12972.0 -2016-12-21 14:00:00,12912.0 -2016-12-21 15:00:00,12895.0 -2016-12-21 16:00:00,12852.0 -2016-12-21 17:00:00,12908.0 -2016-12-21 18:00:00,13509.0 -2016-12-21 19:00:00,13905.0 -2016-12-21 20:00:00,13707.0 -2016-12-21 21:00:00,13494.0 -2016-12-21 22:00:00,13224.0 -2016-12-21 23:00:00,12772.0 -2016-12-22 00:00:00,11998.0 -2016-12-20 01:00:00,12723.0 -2016-12-20 02:00:00,12127.0 -2016-12-20 03:00:00,11823.0 -2016-12-20 04:00:00,11658.0 -2016-12-20 05:00:00,11656.0 -2016-12-20 06:00:00,11846.0 -2016-12-20 07:00:00,12348.0 -2016-12-20 08:00:00,13345.0 -2016-12-20 09:00:00,13806.0 -2016-12-20 10:00:00,13789.0 -2016-12-20 11:00:00,13662.0 -2016-12-20 12:00:00,13511.0 -2016-12-20 13:00:00,13327.0 -2016-12-20 14:00:00,13179.0 -2016-12-20 15:00:00,13193.0 -2016-12-20 16:00:00,13034.0 -2016-12-20 17:00:00,13047.0 -2016-12-20 18:00:00,13811.0 -2016-12-20 19:00:00,14411.0 -2016-12-20 20:00:00,14294.0 -2016-12-20 21:00:00,14099.0 -2016-12-20 22:00:00,13854.0 -2016-12-20 23:00:00,13373.0 -2016-12-21 00:00:00,12543.0 -2016-12-19 01:00:00,12603.0 -2016-12-19 02:00:00,12179.0 -2016-12-19 03:00:00,11956.0 -2016-12-19 04:00:00,11902.0 -2016-12-19 05:00:00,11964.0 -2016-12-19 06:00:00,12191.0 -2016-12-19 07:00:00,12890.0 -2016-12-19 08:00:00,13815.0 -2016-12-19 09:00:00,14345.0 -2016-12-19 10:00:00,14432.0 -2016-12-19 11:00:00,14363.0 -2016-12-19 12:00:00,14355.0 -2016-12-19 13:00:00,14224.0 -2016-12-19 14:00:00,14062.0 -2016-12-19 15:00:00,13968.0 -2016-12-19 16:00:00,13860.0 -2016-12-19 17:00:00,13927.0 -2016-12-19 18:00:00,14639.0 -2016-12-19 19:00:00,15316.0 -2016-12-19 20:00:00,15329.0 -2016-12-19 21:00:00,15139.0 -2016-12-19 22:00:00,14775.0 -2016-12-19 23:00:00,14276.0 -2016-12-20 00:00:00,13528.0 -2016-12-18 01:00:00,11646.0 -2016-12-18 02:00:00,11208.0 -2016-12-18 03:00:00,10912.0 -2016-12-18 04:00:00,10763.0 -2016-12-18 05:00:00,10691.0 -2016-12-18 06:00:00,10787.0 -2016-12-18 07:00:00,10987.0 -2016-12-18 08:00:00,11342.0 -2016-12-18 09:00:00,11447.0 -2016-12-18 10:00:00,11635.0 -2016-12-18 11:00:00,11784.0 -2016-12-18 12:00:00,11849.0 -2016-12-18 13:00:00,11902.0 -2016-12-18 14:00:00,11879.0 -2016-12-18 15:00:00,11871.0 -2016-12-18 16:00:00,11873.0 -2016-12-18 17:00:00,12138.0 -2016-12-18 18:00:00,13116.0 -2016-12-18 19:00:00,14079.0 -2016-12-18 20:00:00,14199.0 -2016-12-18 21:00:00,14230.0 -2016-12-18 22:00:00,14073.0 -2016-12-18 23:00:00,13736.0 -2016-12-19 00:00:00,13153.0 -2016-12-17 01:00:00,11983.0 -2016-12-17 02:00:00,11311.0 -2016-12-17 03:00:00,10956.0 -2016-12-17 04:00:00,10688.0 -2016-12-17 05:00:00,10589.0 -2016-12-17 06:00:00,10608.0 -2016-12-17 07:00:00,10862.0 -2016-12-17 08:00:00,11286.0 -2016-12-17 09:00:00,11554.0 -2016-12-17 10:00:00,11855.0 -2016-12-17 11:00:00,12152.0 -2016-12-17 12:00:00,12240.0 -2016-12-17 13:00:00,12223.0 -2016-12-17 14:00:00,12190.0 -2016-12-17 15:00:00,12032.0 -2016-12-17 16:00:00,12036.0 -2016-12-17 17:00:00,12167.0 -2016-12-17 18:00:00,12947.0 -2016-12-17 19:00:00,13393.0 -2016-12-17 20:00:00,13352.0 -2016-12-17 21:00:00,13144.0 -2016-12-17 22:00:00,12992.0 -2016-12-17 23:00:00,12723.0 -2016-12-18 00:00:00,12226.0 -2016-12-16 01:00:00,12615.0 -2016-12-16 02:00:00,12046.0 -2016-12-16 03:00:00,11631.0 -2016-12-16 04:00:00,11540.0 -2016-12-16 05:00:00,11515.0 -2016-12-16 06:00:00,11734.0 -2016-12-16 07:00:00,12343.0 -2016-12-16 08:00:00,13298.0 -2016-12-16 09:00:00,13808.0 -2016-12-16 10:00:00,13902.0 -2016-12-16 11:00:00,13973.0 -2016-12-16 12:00:00,14025.0 -2016-12-16 13:00:00,13938.0 -2016-12-16 14:00:00,13782.0 -2016-12-16 15:00:00,13743.0 -2016-12-16 16:00:00,13673.0 -2016-12-16 17:00:00,13704.0 -2016-12-16 18:00:00,14398.0 -2016-12-16 19:00:00,14672.0 -2016-12-16 20:00:00,14523.0 -2016-12-16 21:00:00,14261.0 -2016-12-16 22:00:00,13925.0 -2016-12-16 23:00:00,13484.0 -2016-12-17 00:00:00,12720.0 -2016-12-15 01:00:00,12828.0 -2016-12-15 02:00:00,12306.0 -2016-12-15 03:00:00,12039.0 -2016-12-15 04:00:00,11925.0 -2016-12-15 05:00:00,11908.0 -2016-12-15 06:00:00,12147.0 -2016-12-15 07:00:00,12834.0 -2016-12-15 08:00:00,13842.0 -2016-12-15 09:00:00,14276.0 -2016-12-15 10:00:00,14351.0 -2016-12-15 11:00:00,14303.0 -2016-12-15 12:00:00,14279.0 -2016-12-15 13:00:00,14130.0 -2016-12-15 14:00:00,14008.0 -2016-12-15 15:00:00,13939.0 -2016-12-15 16:00:00,13792.0 -2016-12-15 17:00:00,13802.0 -2016-12-15 18:00:00,14734.0 -2016-12-15 19:00:00,15385.0 -2016-12-15 20:00:00,15301.0 -2016-12-15 21:00:00,15119.0 -2016-12-15 22:00:00,14851.0 -2016-12-15 23:00:00,14299.0 -2016-12-16 00:00:00,13438.0 -2016-12-14 01:00:00,12287.0 -2016-12-14 02:00:00,11839.0 -2016-12-14 03:00:00,11590.0 -2016-12-14 04:00:00,11448.0 -2016-12-14 05:00:00,11446.0 -2016-12-14 06:00:00,11678.0 -2016-12-14 07:00:00,12286.0 -2016-12-14 08:00:00,13313.0 -2016-12-14 09:00:00,13693.0 -2016-12-14 10:00:00,13722.0 -2016-12-14 11:00:00,13809.0 -2016-12-14 12:00:00,13748.0 -2016-12-14 13:00:00,13581.0 -2016-12-14 14:00:00,13464.0 -2016-12-14 15:00:00,13409.0 -2016-12-14 16:00:00,13420.0 -2016-12-14 17:00:00,13560.0 -2016-12-14 18:00:00,14440.0 -2016-12-14 19:00:00,15122.0 -2016-12-14 20:00:00,15148.0 -2016-12-14 21:00:00,15069.0 -2016-12-14 22:00:00,14861.0 -2016-12-14 23:00:00,14391.0 -2016-12-15 00:00:00,13571.0 -2016-12-13 01:00:00,11355.0 -2016-12-13 02:00:00,11079.0 -2016-12-13 03:00:00,10737.0 -2016-12-13 04:00:00,10616.0 -2016-12-13 05:00:00,10559.0 -2016-12-13 06:00:00,10816.0 -2016-12-13 07:00:00,11470.0 -2016-12-13 08:00:00,12539.0 -2016-12-13 09:00:00,13094.0 -2016-12-13 10:00:00,13243.0 -2016-12-13 11:00:00,13282.0 -2016-12-13 12:00:00,13235.0 -2016-12-13 13:00:00,13042.0 -2016-12-13 14:00:00,12965.0 -2016-12-13 15:00:00,12960.0 -2016-12-13 16:00:00,12937.0 -2016-12-13 17:00:00,13037.0 -2016-12-13 18:00:00,13887.0 -2016-12-13 19:00:00,14650.0 -2016-12-13 20:00:00,14617.0 -2016-12-13 21:00:00,14525.0 -2016-12-13 22:00:00,14331.0 -2016-12-13 23:00:00,13831.0 -2016-12-14 00:00:00,13085.0 -2016-12-12 01:00:00,10354.0 -2016-12-12 02:00:00,10010.0 -2016-12-12 03:00:00,9800.0 -2016-12-12 04:00:00,9769.0 -2016-12-12 05:00:00,9843.0 -2016-12-12 06:00:00,10173.0 -2016-12-12 07:00:00,10995.0 -2016-12-12 08:00:00,12067.0 -2016-12-12 09:00:00,12660.0 -2016-12-12 10:00:00,12801.0 -2016-12-12 11:00:00,12931.0 -2016-12-12 12:00:00,12961.0 -2016-12-12 13:00:00,12863.0 -2016-12-12 14:00:00,12935.0 -2016-12-12 15:00:00,12733.0 -2016-12-12 16:00:00,12614.0 -2016-12-12 17:00:00,12719.0 -2016-12-12 18:00:00,13602.0 -2016-12-12 19:00:00,14312.0 -2016-12-12 20:00:00,14241.0 -2016-12-12 21:00:00,14087.0 -2016-12-12 22:00:00,13806.0 -2016-12-12 23:00:00,13290.0 -2016-12-13 00:00:00,12466.0 -2016-12-11 01:00:00,10998.0 -2016-12-11 02:00:00,10578.0 -2016-12-11 03:00:00,10209.0 -2016-12-11 04:00:00,10058.0 -2016-12-11 05:00:00,9894.0 -2016-12-11 06:00:00,9890.0 -2016-12-11 07:00:00,9999.0 -2016-12-11 08:00:00,10248.0 -2016-12-11 09:00:00,10396.0 -2016-12-11 10:00:00,10609.0 -2016-12-11 11:00:00,10899.0 -2016-12-11 12:00:00,11050.0 -2016-12-11 13:00:00,11081.0 -2016-12-11 14:00:00,11178.0 -2016-12-11 15:00:00,11132.0 -2016-12-11 16:00:00,11211.0 -2016-12-11 17:00:00,11347.0 -2016-12-11 18:00:00,12226.0 -2016-12-11 19:00:00,12634.0 -2016-12-11 20:00:00,12619.0 -2016-12-11 21:00:00,12494.0 -2016-12-11 22:00:00,12024.0 -2016-12-11 23:00:00,11657.0 -2016-12-12 00:00:00,11012.0 -2016-12-10 01:00:00,11424.0 -2016-12-10 02:00:00,10847.0 -2016-12-10 03:00:00,10537.0 -2016-12-10 04:00:00,10316.0 -2016-12-10 05:00:00,10265.0 -2016-12-10 06:00:00,10331.0 -2016-12-10 07:00:00,10657.0 -2016-12-10 08:00:00,11141.0 -2016-12-10 09:00:00,11427.0 -2016-12-10 10:00:00,11668.0 -2016-12-10 11:00:00,11734.0 -2016-12-10 12:00:00,11687.0 -2016-12-10 13:00:00,11673.0 -2016-12-10 14:00:00,11596.0 -2016-12-10 15:00:00,11508.0 -2016-12-10 16:00:00,11423.0 -2016-12-10 17:00:00,11663.0 -2016-12-10 18:00:00,12513.0 -2016-12-10 19:00:00,12966.0 -2016-12-10 20:00:00,12870.0 -2016-12-10 21:00:00,12787.0 -2016-12-10 22:00:00,12566.0 -2016-12-10 23:00:00,12217.0 -2016-12-11 00:00:00,11674.0 -2016-12-09 01:00:00,11457.0 -2016-12-09 02:00:00,10905.0 -2016-12-09 03:00:00,10593.0 -2016-12-09 04:00:00,10423.0 -2016-12-09 05:00:00,10417.0 -2016-12-09 06:00:00,10663.0 -2016-12-09 07:00:00,11311.0 -2016-12-09 08:00:00,12336.0 -2016-12-09 09:00:00,12807.0 -2016-12-09 10:00:00,12937.0 -2016-12-09 11:00:00,12976.0 -2016-12-09 12:00:00,12984.0 -2016-12-09 13:00:00,12909.0 -2016-12-09 14:00:00,12811.0 -2016-12-09 15:00:00,12735.0 -2016-12-09 16:00:00,12633.0 -2016-12-09 17:00:00,12670.0 -2016-12-09 18:00:00,13341.0 -2016-12-09 19:00:00,13854.0 -2016-12-09 20:00:00,13721.0 -2016-12-09 21:00:00,13502.0 -2016-12-09 22:00:00,13242.0 -2016-12-09 23:00:00,12869.0 -2016-12-10 00:00:00,12133.0 -2016-12-08 01:00:00,11286.0 -2016-12-08 02:00:00,10762.0 -2016-12-08 03:00:00,10468.0 -2016-12-08 04:00:00,10272.0 -2016-12-08 05:00:00,10326.0 -2016-12-08 06:00:00,10607.0 -2016-12-08 07:00:00,11332.0 -2016-12-08 08:00:00,12489.0 -2016-12-08 09:00:00,13097.0 -2016-12-08 10:00:00,13274.0 -2016-12-08 11:00:00,13362.0 -2016-12-08 12:00:00,13387.0 -2016-12-08 13:00:00,13321.0 -2016-12-08 14:00:00,13314.0 -2016-12-08 15:00:00,13328.0 -2016-12-08 16:00:00,13316.0 -2016-12-08 17:00:00,13362.0 -2016-12-08 18:00:00,14036.0 -2016-12-08 19:00:00,14310.0 -2016-12-08 20:00:00,14148.0 -2016-12-08 21:00:00,13890.0 -2016-12-08 22:00:00,13649.0 -2016-12-08 23:00:00,13104.0 -2016-12-09 00:00:00,12247.0 -2016-12-07 01:00:00,10868.0 -2016-12-07 02:00:00,10372.0 -2016-12-07 03:00:00,10085.0 -2016-12-07 04:00:00,9949.0 -2016-12-07 05:00:00,9945.0 -2016-12-07 06:00:00,10238.0 -2016-12-07 07:00:00,10953.0 -2016-12-07 08:00:00,12054.0 -2016-12-07 09:00:00,12559.0 -2016-12-07 10:00:00,12637.0 -2016-12-07 11:00:00,12511.0 -2016-12-07 12:00:00,12545.0 -2016-12-07 13:00:00,12383.0 -2016-12-07 14:00:00,12325.0 -2016-12-07 15:00:00,12426.0 -2016-12-07 16:00:00,12421.0 -2016-12-07 17:00:00,12609.0 -2016-12-07 18:00:00,13401.0 -2016-12-07 19:00:00,13927.0 -2016-12-07 20:00:00,13812.0 -2016-12-07 21:00:00,13676.0 -2016-12-07 22:00:00,13433.0 -2016-12-07 23:00:00,12891.0 -2016-12-08 00:00:00,12080.0 -2016-12-06 01:00:00,10453.0 -2016-12-06 02:00:00,9934.0 -2016-12-06 03:00:00,9604.0 -2016-12-06 04:00:00,9473.0 -2016-12-06 05:00:00,9470.0 -2016-12-06 06:00:00,9726.0 -2016-12-06 07:00:00,10461.0 -2016-12-06 08:00:00,11582.0 -2016-12-06 09:00:00,12026.0 -2016-12-06 10:00:00,12098.0 -2016-12-06 11:00:00,12116.0 -2016-12-06 12:00:00,12020.0 -2016-12-06 13:00:00,11891.0 -2016-12-06 14:00:00,11994.0 -2016-12-06 15:00:00,12174.0 -2016-12-06 16:00:00,12205.0 -2016-12-06 17:00:00,12179.0 -2016-12-06 18:00:00,12800.0 -2016-12-06 19:00:00,13444.0 -2016-12-06 20:00:00,13288.0 -2016-12-06 21:00:00,13145.0 -2016-12-06 22:00:00,12912.0 -2016-12-06 23:00:00,12402.0 -2016-12-07 00:00:00,11561.0 -2016-12-05 01:00:00,9928.0 -2016-12-05 02:00:00,9518.0 -2016-12-05 03:00:00,9301.0 -2016-12-05 04:00:00,9193.0 -2016-12-05 05:00:00,9200.0 -2016-12-05 06:00:00,9552.0 -2016-12-05 07:00:00,10307.0 -2016-12-05 08:00:00,11440.0 -2016-12-05 09:00:00,11989.0 -2016-12-05 10:00:00,12093.0 -2016-12-05 11:00:00,12225.0 -2016-12-05 12:00:00,12248.0 -2016-12-05 13:00:00,12327.0 -2016-12-05 14:00:00,12318.0 -2016-12-05 15:00:00,12300.0 -2016-12-05 16:00:00,12265.0 -2016-12-05 17:00:00,12256.0 -2016-12-05 18:00:00,12867.0 -2016-12-05 19:00:00,13311.0 -2016-12-05 20:00:00,13153.0 -2016-12-05 21:00:00,12960.0 -2016-12-05 22:00:00,12674.0 -2016-12-05 23:00:00,12123.0 -2016-12-06 00:00:00,11290.0 -2016-12-04 01:00:00,9894.0 -2016-12-04 02:00:00,9428.0 -2016-12-04 03:00:00,9098.0 -2016-12-04 04:00:00,8940.0 -2016-12-04 05:00:00,8841.0 -2016-12-04 06:00:00,8859.0 -2016-12-04 07:00:00,8991.0 -2016-12-04 08:00:00,9280.0 -2016-12-04 09:00:00,9440.0 -2016-12-04 10:00:00,9743.0 -2016-12-04 11:00:00,10029.0 -2016-12-04 12:00:00,10340.0 -2016-12-04 13:00:00,10555.0 -2016-12-04 14:00:00,10671.0 -2016-12-04 15:00:00,10676.0 -2016-12-04 16:00:00,10725.0 -2016-12-04 17:00:00,10863.0 -2016-12-04 18:00:00,11562.0 -2016-12-04 19:00:00,11987.0 -2016-12-04 20:00:00,11923.0 -2016-12-04 21:00:00,11723.0 -2016-12-04 22:00:00,11526.0 -2016-12-04 23:00:00,11039.0 -2016-12-05 00:00:00,10528.0 -2016-12-03 01:00:00,10121.0 -2016-12-03 02:00:00,9628.0 -2016-12-03 03:00:00,9311.0 -2016-12-03 04:00:00,9083.0 -2016-12-03 05:00:00,9044.0 -2016-12-03 06:00:00,9097.0 -2016-12-03 07:00:00,9418.0 -2016-12-03 08:00:00,9881.0 -2016-12-03 09:00:00,10194.0 -2016-12-03 10:00:00,10563.0 -2016-12-03 11:00:00,10707.0 -2016-12-03 12:00:00,10877.0 -2016-12-03 13:00:00,10777.0 -2016-12-03 14:00:00,10747.0 -2016-12-03 15:00:00,10594.0 -2016-12-03 16:00:00,10500.0 -2016-12-03 17:00:00,10526.0 -2016-12-03 18:00:00,11203.0 -2016-12-03 19:00:00,11643.0 -2016-12-03 20:00:00,11607.0 -2016-12-03 21:00:00,11438.0 -2016-12-03 22:00:00,11279.0 -2016-12-03 23:00:00,10970.0 -2016-12-04 00:00:00,10511.0 -2016-12-02 01:00:00,10060.0 -2016-12-02 02:00:00,9539.0 -2016-12-02 03:00:00,9276.0 -2016-12-02 04:00:00,9103.0 -2016-12-02 05:00:00,9071.0 -2016-12-02 06:00:00,9340.0 -2016-12-02 07:00:00,10015.0 -2016-12-02 08:00:00,11049.0 -2016-12-02 09:00:00,11560.0 -2016-12-02 10:00:00,11716.0 -2016-12-02 11:00:00,11816.0 -2016-12-02 12:00:00,11916.0 -2016-12-02 13:00:00,11877.0 -2016-12-02 14:00:00,11808.0 -2016-12-02 15:00:00,11827.0 -2016-12-02 16:00:00,11745.0 -2016-12-02 17:00:00,11750.0 -2016-12-02 18:00:00,12329.0 -2016-12-02 19:00:00,12642.0 -2016-12-02 20:00:00,12456.0 -2016-12-02 21:00:00,12207.0 -2016-12-02 22:00:00,11936.0 -2016-12-02 23:00:00,11566.0 -2016-12-03 00:00:00,10841.0 -2016-12-01 01:00:00,10147.0 -2016-12-01 02:00:00,9657.0 -2016-12-01 03:00:00,9365.0 -2016-12-01 04:00:00,9200.0 -2016-12-01 05:00:00,9198.0 -2016-12-01 06:00:00,9455.0 -2016-12-01 07:00:00,10128.0 -2016-12-01 08:00:00,11205.0 -2016-12-01 09:00:00,11740.0 -2016-12-01 10:00:00,11849.0 -2016-12-01 11:00:00,11915.0 -2016-12-01 12:00:00,11966.0 -2016-12-01 13:00:00,11937.0 -2016-12-01 14:00:00,11889.0 -2016-12-01 15:00:00,11906.0 -2016-12-01 16:00:00,11828.0 -2016-12-01 17:00:00,11910.0 -2016-12-01 18:00:00,12482.0 -2016-12-01 19:00:00,12748.0 -2016-12-01 20:00:00,12556.0 -2016-12-01 21:00:00,12354.0 -2016-12-01 22:00:00,12075.0 -2016-12-01 23:00:00,11560.0 -2016-12-02 00:00:00,10798.0 -2016-11-30 01:00:00,9598.0 -2016-11-30 02:00:00,9152.0 -2016-11-30 03:00:00,8870.0 -2016-11-30 04:00:00,8739.0 -2016-11-30 05:00:00,8744.0 -2016-11-30 06:00:00,9066.0 -2016-11-30 07:00:00,9869.0 -2016-11-30 08:00:00,10994.0 -2016-11-30 09:00:00,11413.0 -2016-11-30 10:00:00,11452.0 -2016-11-30 11:00:00,11412.0 -2016-11-30 12:00:00,11504.0 -2016-11-30 13:00:00,11663.0 -2016-11-30 14:00:00,11765.0 -2016-11-30 15:00:00,11908.0 -2016-11-30 16:00:00,11910.0 -2016-11-30 17:00:00,12011.0 -2016-11-30 18:00:00,12594.0 -2016-11-30 19:00:00,12882.0 -2016-11-30 20:00:00,12699.0 -2016-11-30 21:00:00,12499.0 -2016-11-30 22:00:00,12176.0 -2016-11-30 23:00:00,11662.0 -2016-12-01 00:00:00,10908.0 -2016-11-29 01:00:00,9705.0 -2016-11-29 02:00:00,9244.0 -2016-11-29 03:00:00,8947.0 -2016-11-29 04:00:00,8767.0 -2016-11-29 05:00:00,8747.0 -2016-11-29 06:00:00,8972.0 -2016-11-29 07:00:00,9743.0 -2016-11-29 08:00:00,10848.0 -2016-11-29 09:00:00,11298.0 -2016-11-29 10:00:00,11400.0 -2016-11-29 11:00:00,11344.0 -2016-11-29 12:00:00,11350.0 -2016-11-29 13:00:00,11267.0 -2016-11-29 14:00:00,11170.0 -2016-11-29 15:00:00,11169.0 -2016-11-29 16:00:00,11052.0 -2016-11-29 17:00:00,11045.0 -2016-11-29 18:00:00,11664.0 -2016-11-29 19:00:00,12165.0 -2016-11-29 20:00:00,12031.0 -2016-11-29 21:00:00,11856.0 -2016-11-29 22:00:00,11560.0 -2016-11-29 23:00:00,11067.0 -2016-11-30 00:00:00,10332.0 -2016-11-28 01:00:00,9336.0 -2016-11-28 02:00:00,8994.0 -2016-11-28 03:00:00,8794.0 -2016-11-28 04:00:00,8753.0 -2016-11-28 05:00:00,8725.0 -2016-11-28 06:00:00,9018.0 -2016-11-28 07:00:00,9815.0 -2016-11-28 08:00:00,10866.0 -2016-11-28 09:00:00,11461.0 -2016-11-28 10:00:00,11626.0 -2016-11-28 11:00:00,11769.0 -2016-11-28 12:00:00,11894.0 -2016-11-28 13:00:00,11948.0 -2016-11-28 14:00:00,12068.0 -2016-11-28 15:00:00,12035.0 -2016-11-28 16:00:00,11963.0 -2016-11-28 17:00:00,11989.0 -2016-11-28 18:00:00,12395.0 -2016-11-28 19:00:00,12527.0 -2016-11-28 20:00:00,12280.0 -2016-11-28 21:00:00,12049.0 -2016-11-28 22:00:00,11728.0 -2016-11-28 23:00:00,11227.0 -2016-11-29 00:00:00,10455.0 -2016-11-27 01:00:00,9202.0 -2016-11-27 02:00:00,8782.0 -2016-11-27 03:00:00,8553.0 -2016-11-27 04:00:00,8377.0 -2016-11-27 05:00:00,8350.0 -2016-11-27 06:00:00,8380.0 -2016-11-27 07:00:00,8596.0 -2016-11-27 08:00:00,8797.0 -2016-11-27 09:00:00,8845.0 -2016-11-27 10:00:00,9122.0 -2016-11-27 11:00:00,9363.0 -2016-11-27 12:00:00,9555.0 -2016-11-27 13:00:00,9646.0 -2016-11-27 14:00:00,9717.0 -2016-11-27 15:00:00,9777.0 -2016-11-27 16:00:00,9827.0 -2016-11-27 17:00:00,9944.0 -2016-11-27 18:00:00,10665.0 -2016-11-27 19:00:00,11084.0 -2016-11-27 20:00:00,11094.0 -2016-11-27 21:00:00,10994.0 -2016-11-27 22:00:00,10758.0 -2016-11-27 23:00:00,10334.0 -2016-11-28 00:00:00,9878.0 -2016-11-26 01:00:00,9283.0 -2016-11-26 02:00:00,8908.0 -2016-11-26 03:00:00,8646.0 -2016-11-26 04:00:00,8463.0 -2016-11-26 05:00:00,8417.0 -2016-11-26 06:00:00,8465.0 -2016-11-26 07:00:00,8731.0 -2016-11-26 08:00:00,9079.0 -2016-11-26 09:00:00,9268.0 -2016-11-26 10:00:00,9537.0 -2016-11-26 11:00:00,9644.0 -2016-11-26 12:00:00,9676.0 -2016-11-26 13:00:00,9646.0 -2016-11-26 14:00:00,9506.0 -2016-11-26 15:00:00,9407.0 -2016-11-26 16:00:00,9286.0 -2016-11-26 17:00:00,9430.0 -2016-11-26 18:00:00,10070.0 -2016-11-26 19:00:00,10602.0 -2016-11-26 20:00:00,10540.0 -2016-11-26 21:00:00,10438.0 -2016-11-26 22:00:00,10276.0 -2016-11-26 23:00:00,10090.0 -2016-11-27 00:00:00,9648.0 -2016-11-25 01:00:00,8720.0 -2016-11-25 02:00:00,8432.0 -2016-11-25 03:00:00,8234.0 -2016-11-25 04:00:00,8111.0 -2016-11-25 05:00:00,8084.0 -2016-11-25 06:00:00,8238.0 -2016-11-25 07:00:00,8636.0 -2016-11-25 08:00:00,9118.0 -2016-11-25 09:00:00,9307.0 -2016-11-25 10:00:00,9502.0 -2016-11-25 11:00:00,9685.0 -2016-11-25 12:00:00,9798.0 -2016-11-25 13:00:00,9872.0 -2016-11-25 14:00:00,9938.0 -2016-11-25 15:00:00,10012.0 -2016-11-25 16:00:00,10016.0 -2016-11-25 17:00:00,10208.0 -2016-11-25 18:00:00,10725.0 -2016-11-25 19:00:00,10930.0 -2016-11-25 20:00:00,10765.0 -2016-11-25 21:00:00,10600.0 -2016-11-25 22:00:00,10402.0 -2016-11-25 23:00:00,10176.0 -2016-11-26 00:00:00,9713.0 -2016-11-24 01:00:00,9277.0 -2016-11-24 02:00:00,8770.0 -2016-11-24 03:00:00,8468.0 -2016-11-24 04:00:00,8275.0 -2016-11-24 05:00:00,8192.0 -2016-11-24 06:00:00,8246.0 -2016-11-24 07:00:00,8472.0 -2016-11-24 08:00:00,8728.0 -2016-11-24 09:00:00,8765.0 -2016-11-24 10:00:00,9003.0 -2016-11-24 11:00:00,9283.0 -2016-11-24 12:00:00,9501.0 -2016-11-24 13:00:00,9623.0 -2016-11-24 14:00:00,9604.0 -2016-11-24 15:00:00,9563.0 -2016-11-24 16:00:00,9442.0 -2016-11-24 17:00:00,9461.0 -2016-11-24 18:00:00,9757.0 -2016-11-24 19:00:00,9757.0 -2016-11-24 20:00:00,9630.0 -2016-11-24 21:00:00,9574.0 -2016-11-24 22:00:00,9506.0 -2016-11-24 23:00:00,9369.0 -2016-11-25 00:00:00,9077.0 -2016-11-23 01:00:00,9921.0 -2016-11-23 02:00:00,9477.0 -2016-11-23 03:00:00,9210.0 -2016-11-23 04:00:00,9061.0 -2016-11-23 05:00:00,9007.0 -2016-11-23 06:00:00,9235.0 -2016-11-23 07:00:00,9871.0 -2016-11-23 08:00:00,10744.0 -2016-11-23 09:00:00,11309.0 -2016-11-23 10:00:00,11647.0 -2016-11-23 11:00:00,11841.0 -2016-11-23 12:00:00,11956.0 -2016-11-23 13:00:00,11962.0 -2016-11-23 14:00:00,11875.0 -2016-11-23 15:00:00,11802.0 -2016-11-23 16:00:00,11657.0 -2016-11-23 17:00:00,11549.0 -2016-11-23 18:00:00,11830.0 -2016-11-23 19:00:00,11950.0 -2016-11-23 20:00:00,11610.0 -2016-11-23 21:00:00,11340.0 -2016-11-23 22:00:00,11049.0 -2016-11-23 23:00:00,10600.0 -2016-11-24 00:00:00,9941.0 -2016-11-22 01:00:00,10076.0 -2016-11-22 02:00:00,9677.0 -2016-11-22 03:00:00,9439.0 -2016-11-22 04:00:00,9306.0 -2016-11-22 05:00:00,9296.0 -2016-11-22 06:00:00,9532.0 -2016-11-22 07:00:00,10234.0 -2016-11-22 08:00:00,11292.0 -2016-11-22 09:00:00,11742.0 -2016-11-22 10:00:00,11923.0 -2016-11-22 11:00:00,12062.0 -2016-11-22 12:00:00,12067.0 -2016-11-22 13:00:00,11994.0 -2016-11-22 14:00:00,11872.0 -2016-11-22 15:00:00,11837.0 -2016-11-22 16:00:00,11729.0 -2016-11-22 17:00:00,11775.0 -2016-11-22 18:00:00,12282.0 -2016-11-22 19:00:00,12509.0 -2016-11-22 20:00:00,12279.0 -2016-11-22 21:00:00,12070.0 -2016-11-22 22:00:00,11767.0 -2016-11-22 23:00:00,11289.0 -2016-11-23 00:00:00,10593.0 -2016-11-21 01:00:00,9600.0 -2016-11-21 02:00:00,9313.0 -2016-11-21 03:00:00,9184.0 -2016-11-21 04:00:00,9138.0 -2016-11-21 05:00:00,9216.0 -2016-11-21 06:00:00,9531.0 -2016-11-21 07:00:00,10315.0 -2016-11-21 08:00:00,11327.0 -2016-11-21 09:00:00,11749.0 -2016-11-21 10:00:00,11834.0 -2016-11-21 11:00:00,11694.0 -2016-11-21 12:00:00,11790.0 -2016-11-21 13:00:00,11690.0 -2016-11-21 14:00:00,11564.0 -2016-11-21 15:00:00,11511.0 -2016-11-21 16:00:00,11384.0 -2016-11-21 17:00:00,11391.0 -2016-11-21 18:00:00,12073.0 -2016-11-21 19:00:00,12580.0 -2016-11-21 20:00:00,12452.0 -2016-11-21 21:00:00,12240.0 -2016-11-21 22:00:00,11915.0 -2016-11-21 23:00:00,11449.0 -2016-11-22 00:00:00,10713.0 -2016-11-20 01:00:00,9571.0 -2016-11-20 02:00:00,9219.0 -2016-11-20 03:00:00,8973.0 -2016-11-20 04:00:00,8863.0 -2016-11-20 05:00:00,8734.0 -2016-11-20 06:00:00,8875.0 -2016-11-20 07:00:00,9041.0 -2016-11-20 08:00:00,9154.0 -2016-11-20 09:00:00,9255.0 -2016-11-20 10:00:00,9461.0 -2016-11-20 11:00:00,9770.0 -2016-11-20 12:00:00,9804.0 -2016-11-20 13:00:00,9710.0 -2016-11-20 14:00:00,9699.0 -2016-11-20 15:00:00,9630.0 -2016-11-20 16:00:00,9531.0 -2016-11-20 17:00:00,9654.0 -2016-11-20 18:00:00,10341.0 -2016-11-20 19:00:00,10886.0 -2016-11-20 20:00:00,11213.0 -2016-11-20 21:00:00,10992.0 -2016-11-20 22:00:00,10759.0 -2016-11-20 23:00:00,10600.0 -2016-11-21 00:00:00,10130.0 -2016-11-19 01:00:00,9249.0 -2016-11-19 02:00:00,8894.0 -2016-11-19 03:00:00,8664.0 -2016-11-19 04:00:00,8531.0 -2016-11-19 05:00:00,8519.0 -2016-11-19 06:00:00,8624.0 -2016-11-19 07:00:00,9009.0 -2016-11-19 08:00:00,9407.0 -2016-11-19 09:00:00,9644.0 -2016-11-19 10:00:00,10103.0 -2016-11-19 11:00:00,10434.0 -2016-11-19 12:00:00,10499.0 -2016-11-19 13:00:00,10555.0 -2016-11-19 14:00:00,10341.0 -2016-11-19 15:00:00,10247.0 -2016-11-19 16:00:00,10113.0 -2016-11-19 17:00:00,10171.0 -2016-11-19 18:00:00,10426.0 -2016-11-19 19:00:00,10816.0 -2016-11-19 20:00:00,11052.0 -2016-11-19 21:00:00,10872.0 -2016-11-19 22:00:00,10690.0 -2016-11-19 23:00:00,10479.0 -2016-11-20 00:00:00,10114.0 -2016-11-18 01:00:00,9036.0 -2016-11-18 02:00:00,8550.0 -2016-11-18 03:00:00,8242.0 -2016-11-18 04:00:00,8059.0 -2016-11-18 05:00:00,7980.0 -2016-11-18 06:00:00,8214.0 -2016-11-18 07:00:00,8835.0 -2016-11-18 08:00:00,9849.0 -2016-11-18 09:00:00,10379.0 -2016-11-18 10:00:00,10728.0 -2016-11-18 11:00:00,11407.0 -2016-11-18 12:00:00,11088.0 -2016-11-18 13:00:00,11190.0 -2016-11-18 14:00:00,11159.0 -2016-11-18 15:00:00,11517.0 -2016-11-18 16:00:00,11344.0 -2016-11-18 17:00:00,11347.0 -2016-11-18 18:00:00,11388.0 -2016-11-18 19:00:00,11586.0 -2016-11-18 20:00:00,11686.0 -2016-11-18 21:00:00,11281.0 -2016-11-18 22:00:00,10816.0 -2016-11-18 23:00:00,10631.0 -2016-11-19 00:00:00,10042.0 -2016-11-17 01:00:00,9126.0 -2016-11-17 02:00:00,8731.0 -2016-11-17 03:00:00,8467.0 -2016-11-17 04:00:00,8328.0 -2016-11-17 05:00:00,8277.0 -2016-11-17 06:00:00,8512.0 -2016-11-17 07:00:00,9163.0 -2016-11-17 08:00:00,10158.0 -2016-11-17 09:00:00,10591.0 -2016-11-17 10:00:00,10739.0 -2016-11-17 11:00:00,10871.0 -2016-11-17 12:00:00,11009.0 -2016-11-17 13:00:00,11108.0 -2016-11-17 14:00:00,11193.0 -2016-11-17 15:00:00,11212.0 -2016-11-17 16:00:00,11174.0 -2016-11-17 17:00:00,11044.0 -2016-11-17 18:00:00,11383.0 -2016-11-17 19:00:00,11827.0 -2016-11-17 20:00:00,11584.0 -2016-11-17 21:00:00,11311.0 -2016-11-17 22:00:00,10978.0 -2016-11-17 23:00:00,10477.0 -2016-11-18 00:00:00,9697.0 -2016-11-16 01:00:00,9111.0 -2016-11-16 02:00:00,8673.0 -2016-11-16 03:00:00,8420.0 -2016-11-16 04:00:00,8326.0 -2016-11-16 05:00:00,8324.0 -2016-11-16 06:00:00,8593.0 -2016-11-16 07:00:00,9306.0 -2016-11-16 08:00:00,10354.0 -2016-11-16 09:00:00,10726.0 -2016-11-16 10:00:00,10836.0 -2016-11-16 11:00:00,10877.0 -2016-11-16 12:00:00,10914.0 -2016-11-16 13:00:00,10952.0 -2016-11-16 14:00:00,10928.0 -2016-11-16 15:00:00,10960.0 -2016-11-16 16:00:00,10897.0 -2016-11-16 17:00:00,10824.0 -2016-11-16 18:00:00,11174.0 -2016-11-16 19:00:00,11647.0 -2016-11-16 20:00:00,11472.0 -2016-11-16 21:00:00,11290.0 -2016-11-16 22:00:00,10972.0 -2016-11-16 23:00:00,10507.0 -2016-11-17 00:00:00,9804.0 -2016-11-15 01:00:00,8963.0 -2016-11-15 02:00:00,8580.0 -2016-11-15 03:00:00,8361.0 -2016-11-15 04:00:00,8220.0 -2016-11-15 05:00:00,8238.0 -2016-11-15 06:00:00,8486.0 -2016-11-15 07:00:00,9212.0 -2016-11-15 08:00:00,10305.0 -2016-11-15 09:00:00,10754.0 -2016-11-15 10:00:00,10917.0 -2016-11-15 11:00:00,10956.0 -2016-11-15 12:00:00,11002.0 -2016-11-15 13:00:00,10983.0 -2016-11-15 14:00:00,10926.0 -2016-11-15 15:00:00,10872.0 -2016-11-15 16:00:00,10782.0 -2016-11-15 17:00:00,10723.0 -2016-11-15 18:00:00,11113.0 -2016-11-15 19:00:00,11636.0 -2016-11-15 20:00:00,11455.0 -2016-11-15 21:00:00,11262.0 -2016-11-15 22:00:00,10975.0 -2016-11-15 23:00:00,10454.0 -2016-11-16 00:00:00,9760.0 -2016-11-14 01:00:00,8616.0 -2016-11-14 02:00:00,8318.0 -2016-11-14 03:00:00,8186.0 -2016-11-14 04:00:00,8098.0 -2016-11-14 05:00:00,8118.0 -2016-11-14 06:00:00,8490.0 -2016-11-14 07:00:00,9330.0 -2016-11-14 08:00:00,10388.0 -2016-11-14 09:00:00,10864.0 -2016-11-14 10:00:00,10963.0 -2016-11-14 11:00:00,10910.0 -2016-11-14 12:00:00,10921.0 -2016-11-14 13:00:00,10889.0 -2016-11-14 14:00:00,10847.0 -2016-11-14 15:00:00,10886.0 -2016-11-14 16:00:00,10807.0 -2016-11-14 17:00:00,10708.0 -2016-11-14 18:00:00,11088.0 -2016-11-14 19:00:00,11570.0 -2016-11-14 20:00:00,11379.0 -2016-11-14 21:00:00,11132.0 -2016-11-14 22:00:00,10839.0 -2016-11-14 23:00:00,10277.0 -2016-11-15 00:00:00,9604.0 -2016-11-13 01:00:00,8832.0 -2016-11-13 02:00:00,8518.0 -2016-11-13 03:00:00,8297.0 -2016-11-13 04:00:00,8193.0 -2016-11-13 05:00:00,8057.0 -2016-11-13 06:00:00,8129.0 -2016-11-13 07:00:00,8274.0 -2016-11-13 08:00:00,8495.0 -2016-11-13 09:00:00,8539.0 -2016-11-13 10:00:00,8752.0 -2016-11-13 11:00:00,8849.0 -2016-11-13 12:00:00,8959.0 -2016-11-13 13:00:00,8924.0 -2016-11-13 14:00:00,8912.0 -2016-11-13 15:00:00,8877.0 -2016-11-13 16:00:00,8815.0 -2016-11-13 17:00:00,8895.0 -2016-11-13 18:00:00,9406.0 -2016-11-13 19:00:00,10141.0 -2016-11-13 20:00:00,10145.0 -2016-11-13 21:00:00,10082.0 -2016-11-13 22:00:00,9849.0 -2016-11-13 23:00:00,9551.0 -2016-11-14 00:00:00,9044.0 -2016-11-12 01:00:00,9245.0 -2016-11-12 02:00:00,8795.0 -2016-11-12 03:00:00,8570.0 -2016-11-12 04:00:00,8434.0 -2016-11-12 05:00:00,8401.0 -2016-11-12 06:00:00,8488.0 -2016-11-12 07:00:00,8796.0 -2016-11-12 08:00:00,9141.0 -2016-11-12 09:00:00,9336.0 -2016-11-12 10:00:00,9551.0 -2016-11-12 11:00:00,9703.0 -2016-11-12 12:00:00,9677.0 -2016-11-12 13:00:00,9634.0 -2016-11-12 14:00:00,9481.0 -2016-11-12 15:00:00,9289.0 -2016-11-12 16:00:00,9171.0 -2016-11-12 17:00:00,9161.0 -2016-11-12 18:00:00,9597.0 -2016-11-12 19:00:00,10250.0 -2016-11-12 20:00:00,10234.0 -2016-11-12 21:00:00,10055.0 -2016-11-12 22:00:00,9943.0 -2016-11-12 23:00:00,9663.0 -2016-11-13 00:00:00,9269.0 -2016-11-11 01:00:00,9063.0 -2016-11-11 02:00:00,8588.0 -2016-11-11 03:00:00,8348.0 -2016-11-11 04:00:00,8219.0 -2016-11-11 05:00:00,8218.0 -2016-11-11 06:00:00,8440.0 -2016-11-11 07:00:00,9064.0 -2016-11-11 08:00:00,9884.0 -2016-11-11 09:00:00,10374.0 -2016-11-11 10:00:00,10603.0 -2016-11-11 11:00:00,10752.0 -2016-11-11 12:00:00,10898.0 -2016-11-11 13:00:00,10802.0 -2016-11-11 14:00:00,10774.0 -2016-11-11 15:00:00,10774.0 -2016-11-11 16:00:00,10661.0 -2016-11-11 17:00:00,10633.0 -2016-11-11 18:00:00,10999.0 -2016-11-11 19:00:00,11467.0 -2016-11-11 20:00:00,11383.0 -2016-11-11 21:00:00,11113.0 -2016-11-11 22:00:00,10849.0 -2016-11-11 23:00:00,10426.0 -2016-11-12 00:00:00,9806.0 -2016-11-10 01:00:00,9100.0 -2016-11-10 02:00:00,8708.0 -2016-11-10 03:00:00,8502.0 -2016-11-10 04:00:00,8392.0 -2016-11-10 05:00:00,8369.0 -2016-11-10 06:00:00,8639.0 -2016-11-10 07:00:00,9343.0 -2016-11-10 08:00:00,10337.0 -2016-11-10 09:00:00,10754.0 -2016-11-10 10:00:00,10907.0 -2016-11-10 11:00:00,10993.0 -2016-11-10 12:00:00,11038.0 -2016-11-10 13:00:00,11048.0 -2016-11-10 14:00:00,11093.0 -2016-11-10 15:00:00,11049.0 -2016-11-10 16:00:00,10976.0 -2016-11-10 17:00:00,10885.0 -2016-11-10 18:00:00,11094.0 -2016-11-10 19:00:00,11685.0 -2016-11-10 20:00:00,11463.0 -2016-11-10 21:00:00,11224.0 -2016-11-10 22:00:00,10879.0 -2016-11-10 23:00:00,10377.0 -2016-11-11 00:00:00,9687.0 -2016-11-09 01:00:00,9123.0 -2016-11-09 02:00:00,8717.0 -2016-11-09 03:00:00,8418.0 -2016-11-09 04:00:00,8199.0 -2016-11-09 05:00:00,8191.0 -2016-11-09 06:00:00,8461.0 -2016-11-09 07:00:00,9189.0 -2016-11-09 08:00:00,10156.0 -2016-11-09 09:00:00,10594.0 -2016-11-09 10:00:00,10803.0 -2016-11-09 11:00:00,10883.0 -2016-11-09 12:00:00,10990.0 -2016-11-09 13:00:00,10962.0 -2016-11-09 14:00:00,10949.0 -2016-11-09 15:00:00,10956.0 -2016-11-09 16:00:00,10851.0 -2016-11-09 17:00:00,10768.0 -2016-11-09 18:00:00,10991.0 -2016-11-09 19:00:00,11527.0 -2016-11-09 20:00:00,11438.0 -2016-11-09 21:00:00,11252.0 -2016-11-09 22:00:00,10942.0 -2016-11-09 23:00:00,10430.0 -2016-11-10 00:00:00,9671.0 -2016-11-08 01:00:00,8809.0 -2016-11-08 02:00:00,8420.0 -2016-11-08 03:00:00,8135.0 -2016-11-08 04:00:00,7998.0 -2016-11-08 05:00:00,7988.0 -2016-11-08 06:00:00,8201.0 -2016-11-08 07:00:00,8871.0 -2016-11-08 08:00:00,9920.0 -2016-11-08 09:00:00,10487.0 -2016-11-08 10:00:00,10774.0 -2016-11-08 11:00:00,10852.0 -2016-11-08 12:00:00,10935.0 -2016-11-08 13:00:00,10969.0 -2016-11-08 14:00:00,11023.0 -2016-11-08 15:00:00,11071.0 -2016-11-08 16:00:00,10911.0 -2016-11-08 17:00:00,10751.0 -2016-11-08 18:00:00,11055.0 -2016-11-08 19:00:00,11594.0 -2016-11-08 20:00:00,11432.0 -2016-11-08 21:00:00,11220.0 -2016-11-08 22:00:00,10890.0 -2016-11-08 23:00:00,10352.0 -2016-11-09 00:00:00,9699.0 -2016-11-07 01:00:00,8128.0 -2016-11-07 02:00:00,7882.0 -2016-11-07 03:00:00,7737.0 -2016-11-07 04:00:00,7687.0 -2016-11-07 05:00:00,7733.0 -2016-11-07 06:00:00,8046.0 -2016-11-07 07:00:00,8688.0 -2016-11-07 08:00:00,9770.0 -2016-11-07 09:00:00,10338.0 -2016-11-07 10:00:00,10574.0 -2016-11-07 11:00:00,10698.0 -2016-11-07 12:00:00,10954.0 -2016-11-07 13:00:00,10989.0 -2016-11-07 14:00:00,11029.0 -2016-11-07 15:00:00,11077.0 -2016-11-07 16:00:00,10984.0 -2016-11-07 17:00:00,10856.0 -2016-11-07 18:00:00,11084.0 -2016-11-07 19:00:00,11540.0 -2016-11-07 20:00:00,11327.0 -2016-11-07 21:00:00,11062.0 -2016-11-07 22:00:00,10691.0 -2016-11-07 23:00:00,10127.0 -2016-11-08 00:00:00,9429.0 -2016-11-06 01:00:00,8475.0 -2016-11-06 02:00:00,7814.0 -2016-11-06 02:00:00,8028.0 -2016-11-06 03:00:00,7557.0 -2016-11-06 04:00:00,7543.0 -2016-11-06 05:00:00,7509.0 -2016-11-06 06:00:00,7509.0 -2016-11-06 07:00:00,7704.0 -2016-11-06 08:00:00,7900.0 -2016-11-06 09:00:00,8075.0 -2016-11-06 10:00:00,8390.0 -2016-11-06 11:00:00,8591.0 -2016-11-06 12:00:00,8771.0 -2016-11-06 13:00:00,8943.0 -2016-11-06 14:00:00,9001.0 -2016-11-06 15:00:00,9002.0 -2016-11-06 16:00:00,9027.0 -2016-11-06 17:00:00,9019.0 -2016-11-06 18:00:00,9348.0 -2016-11-06 19:00:00,9995.0 -2016-11-06 20:00:00,9963.0 -2016-11-06 21:00:00,9769.0 -2016-11-06 22:00:00,9482.0 -2016-11-06 23:00:00,9046.0 -2016-11-07 00:00:00,8595.0 -2016-11-05 01:00:00,8995.0 -2016-11-05 02:00:00,8484.0 -2016-11-05 03:00:00,8241.0 -2016-11-05 04:00:00,8027.0 -2016-11-05 05:00:00,7924.0 -2016-11-05 06:00:00,7964.0 -2016-11-05 07:00:00,8230.0 -2016-11-05 08:00:00,8658.0 -2016-11-05 09:00:00,8971.0 -2016-11-05 10:00:00,9148.0 -2016-11-05 11:00:00,9366.0 -2016-11-05 12:00:00,9484.0 -2016-11-05 13:00:00,9530.0 -2016-11-05 14:00:00,9500.0 -2016-11-05 15:00:00,9397.0 -2016-11-05 16:00:00,9307.0 -2016-11-05 17:00:00,9266.0 -2016-11-05 18:00:00,9252.0 -2016-11-05 19:00:00,9473.0 -2016-11-05 20:00:00,9954.0 -2016-11-05 21:00:00,9861.0 -2016-11-05 22:00:00,9674.0 -2016-11-05 23:00:00,9390.0 -2016-11-06 00:00:00,8936.0 -2016-11-04 01:00:00,8954.0 -2016-11-04 02:00:00,8489.0 -2016-11-04 03:00:00,8192.0 -2016-11-04 04:00:00,8043.0 -2016-11-04 05:00:00,7997.0 -2016-11-04 06:00:00,8213.0 -2016-11-04 07:00:00,8817.0 -2016-11-04 08:00:00,9899.0 -2016-11-04 09:00:00,10595.0 -2016-11-04 10:00:00,10669.0 -2016-11-04 11:00:00,10752.0 -2016-11-04 12:00:00,10890.0 -2016-11-04 13:00:00,10859.0 -2016-11-04 14:00:00,10823.0 -2016-11-04 15:00:00,10872.0 -2016-11-04 16:00:00,10787.0 -2016-11-04 17:00:00,10613.0 -2016-11-04 18:00:00,10510.0 -2016-11-04 19:00:00,10576.0 -2016-11-04 20:00:00,11006.0 -2016-11-04 21:00:00,10837.0 -2016-11-04 22:00:00,10547.0 -2016-11-04 23:00:00,10147.0 -2016-11-05 00:00:00,9573.0 -2016-11-03 01:00:00,9322.0 -2016-11-03 02:00:00,8832.0 -2016-11-03 03:00:00,8411.0 -2016-11-03 04:00:00,8168.0 -2016-11-03 05:00:00,8080.0 -2016-11-03 06:00:00,8170.0 -2016-11-03 07:00:00,8749.0 -2016-11-03 08:00:00,9822.0 -2016-11-03 09:00:00,10620.0 -2016-11-03 10:00:00,10663.0 -2016-11-03 11:00:00,10727.0 -2016-11-03 12:00:00,10829.0 -2016-11-03 13:00:00,10870.0 -2016-11-03 14:00:00,10939.0 -2016-11-03 15:00:00,11004.0 -2016-11-03 16:00:00,11013.0 -2016-11-03 17:00:00,10898.0 -2016-11-03 18:00:00,10807.0 -2016-11-03 19:00:00,10912.0 -2016-11-03 20:00:00,11213.0 -2016-11-03 21:00:00,11117.0 -2016-11-03 22:00:00,10800.0 -2016-11-03 23:00:00,10310.0 -2016-11-04 00:00:00,9621.0 -2016-11-02 01:00:00,9657.0 -2016-11-02 02:00:00,9137.0 -2016-11-02 03:00:00,8783.0 -2016-11-02 04:00:00,8607.0 -2016-11-02 05:00:00,8549.0 -2016-11-02 06:00:00,8651.0 -2016-11-02 07:00:00,9145.0 -2016-11-02 08:00:00,10353.0 -2016-11-02 09:00:00,11164.0 -2016-11-02 10:00:00,11262.0 -2016-11-02 11:00:00,11407.0 -2016-11-02 12:00:00,11674.0 -2016-11-02 13:00:00,11639.0 -2016-11-02 14:00:00,11634.0 -2016-11-02 15:00:00,11738.0 -2016-11-02 16:00:00,11676.0 -2016-11-02 17:00:00,11600.0 -2016-11-02 18:00:00,11651.0 -2016-11-02 19:00:00,11993.0 -2016-11-02 20:00:00,12038.0 -2016-11-02 21:00:00,11676.0 -2016-11-02 22:00:00,11235.0 -2016-11-02 23:00:00,10614.0 -2016-11-03 00:00:00,9898.0 -2016-11-01 01:00:00,9058.0 -2016-11-01 02:00:00,8618.0 -2016-11-01 03:00:00,8353.0 -2016-11-01 04:00:00,8217.0 -2016-11-01 05:00:00,8177.0 -2016-11-01 06:00:00,8340.0 -2016-11-01 07:00:00,9010.0 -2016-11-01 08:00:00,10190.0 -2016-11-01 09:00:00,10865.0 -2016-11-01 10:00:00,10973.0 -2016-11-01 11:00:00,11171.0 -2016-11-01 12:00:00,11419.0 -2016-11-01 13:00:00,11491.0 -2016-11-01 14:00:00,11641.0 -2016-11-01 15:00:00,11811.0 -2016-11-01 16:00:00,11760.0 -2016-11-01 17:00:00,11674.0 -2016-11-01 18:00:00,11568.0 -2016-11-01 19:00:00,11657.0 -2016-11-01 20:00:00,12028.0 -2016-11-01 21:00:00,11830.0 -2016-11-01 22:00:00,11406.0 -2016-11-01 23:00:00,10887.0 -2016-11-02 00:00:00,10211.0 -2016-10-31 01:00:00,8621.0 -2016-10-31 02:00:00,8226.0 -2016-10-31 03:00:00,8053.0 -2016-10-31 04:00:00,7939.0 -2016-10-31 05:00:00,7943.0 -2016-10-31 06:00:00,8240.0 -2016-10-31 07:00:00,8975.0 -2016-10-31 08:00:00,10254.0 -2016-10-31 09:00:00,10961.0 -2016-10-31 10:00:00,11004.0 -2016-10-31 11:00:00,10993.0 -2016-10-31 12:00:00,11058.0 -2016-10-31 13:00:00,10955.0 -2016-10-31 14:00:00,10874.0 -2016-10-31 15:00:00,10916.0 -2016-10-31 16:00:00,10809.0 -2016-10-31 17:00:00,10663.0 -2016-10-31 18:00:00,10535.0 -2016-10-31 19:00:00,10748.0 -2016-10-31 20:00:00,11115.0 -2016-10-31 21:00:00,11094.0 -2016-10-31 22:00:00,10859.0 -2016-10-31 23:00:00,10423.0 -2016-11-01 00:00:00,9737.0 -2016-10-30 01:00:00,8659.0 -2016-10-30 02:00:00,8252.0 -2016-10-30 03:00:00,7978.0 -2016-10-30 04:00:00,7787.0 -2016-10-30 05:00:00,7756.0 -2016-10-30 06:00:00,7820.0 -2016-10-30 07:00:00,7784.0 -2016-10-30 08:00:00,7960.0 -2016-10-30 09:00:00,8119.0 -2016-10-30 10:00:00,8278.0 -2016-10-30 11:00:00,8567.0 -2016-10-30 12:00:00,8904.0 -2016-10-30 13:00:00,9082.0 -2016-10-30 14:00:00,9197.0 -2016-10-30 15:00:00,9213.0 -2016-10-30 16:00:00,9204.0 -2016-10-30 17:00:00,9214.0 -2016-10-30 18:00:00,9340.0 -2016-10-30 19:00:00,9657.0 -2016-10-30 20:00:00,10191.0 -2016-10-30 21:00:00,10227.0 -2016-10-30 22:00:00,9950.0 -2016-10-30 23:00:00,9579.0 -2016-10-31 00:00:00,9172.0 -2016-10-29 01:00:00,9455.0 -2016-10-29 02:00:00,9039.0 -2016-10-29 03:00:00,8712.0 -2016-10-29 04:00:00,8418.0 -2016-10-29 05:00:00,8315.0 -2016-10-29 06:00:00,8274.0 -2016-10-29 07:00:00,8532.0 -2016-10-29 08:00:00,8924.0 -2016-10-29 09:00:00,9216.0 -2016-10-29 10:00:00,9577.0 -2016-10-29 11:00:00,9895.0 -2016-10-29 12:00:00,10150.0 -2016-10-29 13:00:00,10227.0 -2016-10-29 14:00:00,10274.0 -2016-10-29 15:00:00,10200.0 -2016-10-29 16:00:00,10142.0 -2016-10-29 17:00:00,10176.0 -2016-10-29 18:00:00,10209.0 -2016-10-29 19:00:00,10399.0 -2016-10-29 20:00:00,10625.0 -2016-10-29 21:00:00,10555.0 -2016-10-29 22:00:00,10232.0 -2016-10-29 23:00:00,9745.0 -2016-10-30 00:00:00,9183.0 -2016-10-28 01:00:00,9450.0 -2016-10-28 02:00:00,8945.0 -2016-10-28 03:00:00,8679.0 -2016-10-28 04:00:00,8582.0 -2016-10-28 05:00:00,8518.0 -2016-10-28 06:00:00,8692.0 -2016-10-28 07:00:00,9345.0 -2016-10-28 08:00:00,10561.0 -2016-10-28 09:00:00,11109.0 -2016-10-28 10:00:00,11153.0 -2016-10-28 11:00:00,11184.0 -2016-10-28 12:00:00,11278.0 -2016-10-28 13:00:00,11180.0 -2016-10-28 14:00:00,11204.0 -2016-10-28 15:00:00,11244.0 -2016-10-28 16:00:00,11180.0 -2016-10-28 17:00:00,11009.0 -2016-10-28 18:00:00,10950.0 -2016-10-28 19:00:00,11078.0 -2016-10-28 20:00:00,11572.0 -2016-10-28 21:00:00,11444.0 -2016-10-28 22:00:00,11154.0 -2016-10-28 23:00:00,10801.0 -2016-10-29 00:00:00,10147.0 -2016-10-27 01:00:00,9395.0 -2016-10-27 02:00:00,9005.0 -2016-10-27 03:00:00,8669.0 -2016-10-27 04:00:00,8451.0 -2016-10-27 05:00:00,8419.0 -2016-10-27 06:00:00,8520.0 -2016-10-27 07:00:00,9236.0 -2016-10-27 08:00:00,10455.0 -2016-10-27 09:00:00,11040.0 -2016-10-27 10:00:00,11180.0 -2016-10-27 11:00:00,11257.0 -2016-10-27 12:00:00,11393.0 -2016-10-27 13:00:00,11291.0 -2016-10-27 14:00:00,11308.0 -2016-10-27 15:00:00,11323.0 -2016-10-27 16:00:00,11233.0 -2016-10-27 17:00:00,11127.0 -2016-10-27 18:00:00,11135.0 -2016-10-27 19:00:00,11226.0 -2016-10-27 20:00:00,11539.0 -2016-10-27 21:00:00,11460.0 -2016-10-27 22:00:00,11138.0 -2016-10-27 23:00:00,10787.0 -2016-10-28 00:00:00,10130.0 -2016-10-26 01:00:00,9257.0 -2016-10-26 02:00:00,8852.0 -2016-10-26 03:00:00,8584.0 -2016-10-26 04:00:00,8477.0 -2016-10-26 05:00:00,8414.0 -2016-10-26 06:00:00,8603.0 -2016-10-26 07:00:00,9307.0 -2016-10-26 08:00:00,10380.0 -2016-10-26 09:00:00,11292.0 -2016-10-26 10:00:00,11514.0 -2016-10-26 11:00:00,11569.0 -2016-10-26 12:00:00,11790.0 -2016-10-26 13:00:00,11890.0 -2016-10-26 14:00:00,11792.0 -2016-10-26 15:00:00,11893.0 -2016-10-26 16:00:00,11851.0 -2016-10-26 17:00:00,11691.0 -2016-10-26 18:00:00,11800.0 -2016-10-26 19:00:00,11934.0 -2016-10-26 20:00:00,12022.0 -2016-10-26 21:00:00,11811.0 -2016-10-26 22:00:00,11550.0 -2016-10-26 23:00:00,11002.0 -2016-10-27 00:00:00,10128.0 -2016-10-25 01:00:00,9088.0 -2016-10-25 02:00:00,8707.0 -2016-10-25 03:00:00,8493.0 -2016-10-25 04:00:00,8340.0 -2016-10-25 05:00:00,8200.0 -2016-10-25 06:00:00,8315.0 -2016-10-25 07:00:00,8997.0 -2016-10-25 08:00:00,10197.0 -2016-10-25 09:00:00,10843.0 -2016-10-25 10:00:00,10895.0 -2016-10-25 11:00:00,11073.0 -2016-10-25 12:00:00,11139.0 -2016-10-25 13:00:00,11106.0 -2016-10-25 14:00:00,11050.0 -2016-10-25 15:00:00,11066.0 -2016-10-25 16:00:00,10959.0 -2016-10-25 17:00:00,10926.0 -2016-10-25 18:00:00,10963.0 -2016-10-25 19:00:00,11233.0 -2016-10-25 20:00:00,11647.0 -2016-10-25 21:00:00,11634.0 -2016-10-25 22:00:00,11335.0 -2016-10-25 23:00:00,10791.0 -2016-10-26 00:00:00,9933.0 -2016-10-24 01:00:00,8458.0 -2016-10-24 02:00:00,8129.0 -2016-10-24 03:00:00,7948.0 -2016-10-24 04:00:00,7749.0 -2016-10-24 05:00:00,7793.0 -2016-10-24 06:00:00,8062.0 -2016-10-24 07:00:00,8706.0 -2016-10-24 08:00:00,9964.0 -2016-10-24 09:00:00,10463.0 -2016-10-24 10:00:00,10497.0 -2016-10-24 11:00:00,10606.0 -2016-10-24 12:00:00,10731.0 -2016-10-24 13:00:00,10770.0 -2016-10-24 14:00:00,10770.0 -2016-10-24 15:00:00,10780.0 -2016-10-24 16:00:00,10750.0 -2016-10-24 17:00:00,10655.0 -2016-10-24 18:00:00,10586.0 -2016-10-24 19:00:00,10591.0 -2016-10-24 20:00:00,11170.0 -2016-10-24 21:00:00,11162.0 -2016-10-24 22:00:00,10872.0 -2016-10-24 23:00:00,10385.0 -2016-10-25 00:00:00,9668.0 -2016-10-23 01:00:00,8743.0 -2016-10-23 02:00:00,8415.0 -2016-10-23 03:00:00,8126.0 -2016-10-23 04:00:00,7954.0 -2016-10-23 05:00:00,7807.0 -2016-10-23 06:00:00,7822.0 -2016-10-23 07:00:00,7932.0 -2016-10-23 08:00:00,8163.0 -2016-10-23 09:00:00,8222.0 -2016-10-23 10:00:00,8372.0 -2016-10-23 11:00:00,8570.0 -2016-10-23 12:00:00,8712.0 -2016-10-23 13:00:00,8869.0 -2016-10-23 14:00:00,8965.0 -2016-10-23 15:00:00,9016.0 -2016-10-23 16:00:00,9142.0 -2016-10-23 17:00:00,9214.0 -2016-10-23 18:00:00,9233.0 -2016-10-23 19:00:00,9409.0 -2016-10-23 20:00:00,10128.0 -2016-10-23 21:00:00,10066.0 -2016-10-23 22:00:00,9836.0 -2016-10-23 23:00:00,9419.0 -2016-10-24 00:00:00,8920.0 -2016-10-22 01:00:00,9143.0 -2016-10-22 02:00:00,8678.0 -2016-10-22 03:00:00,8379.0 -2016-10-22 04:00:00,8180.0 -2016-10-22 05:00:00,8078.0 -2016-10-22 06:00:00,8070.0 -2016-10-22 07:00:00,8312.0 -2016-10-22 08:00:00,8826.0 -2016-10-22 09:00:00,9024.0 -2016-10-22 10:00:00,9363.0 -2016-10-22 11:00:00,9473.0 -2016-10-22 12:00:00,9522.0 -2016-10-22 13:00:00,9500.0 -2016-10-22 14:00:00,9405.0 -2016-10-22 15:00:00,9303.0 -2016-10-22 16:00:00,9204.0 -2016-10-22 17:00:00,9237.0 -2016-10-22 18:00:00,9188.0 -2016-10-22 19:00:00,9276.0 -2016-10-22 20:00:00,9919.0 -2016-10-22 21:00:00,9950.0 -2016-10-22 22:00:00,9811.0 -2016-10-22 23:00:00,9536.0 -2016-10-23 00:00:00,9164.0 -2016-10-21 01:00:00,9236.0 -2016-10-21 02:00:00,8809.0 -2016-10-21 03:00:00,8545.0 -2016-10-21 04:00:00,8389.0 -2016-10-21 05:00:00,8298.0 -2016-10-21 06:00:00,8465.0 -2016-10-21 07:00:00,9026.0 -2016-10-21 08:00:00,10136.0 -2016-10-21 09:00:00,10766.0 -2016-10-21 10:00:00,10850.0 -2016-10-21 11:00:00,10841.0 -2016-10-21 12:00:00,10911.0 -2016-10-21 13:00:00,10887.0 -2016-10-21 14:00:00,10818.0 -2016-10-21 15:00:00,10764.0 -2016-10-21 16:00:00,10588.0 -2016-10-21 17:00:00,10549.0 -2016-10-21 18:00:00,10444.0 -2016-10-21 19:00:00,10490.0 -2016-10-21 20:00:00,10958.0 -2016-10-21 21:00:00,10919.0 -2016-10-21 22:00:00,10654.0 -2016-10-21 23:00:00,10305.0 -2016-10-22 00:00:00,9719.0 -2016-10-20 01:00:00,9291.0 -2016-10-20 02:00:00,8762.0 -2016-10-20 03:00:00,8417.0 -2016-10-20 04:00:00,8170.0 -2016-10-20 05:00:00,8055.0 -2016-10-20 06:00:00,8227.0 -2016-10-20 07:00:00,8796.0 -2016-10-20 08:00:00,9849.0 -2016-10-20 09:00:00,10541.0 -2016-10-20 10:00:00,10829.0 -2016-10-20 11:00:00,11034.0 -2016-10-20 12:00:00,11157.0 -2016-10-20 13:00:00,11198.0 -2016-10-20 14:00:00,11126.0 -2016-10-20 15:00:00,11177.0 -2016-10-20 16:00:00,11073.0 -2016-10-20 17:00:00,11017.0 -2016-10-20 18:00:00,10958.0 -2016-10-20 19:00:00,11022.0 -2016-10-20 20:00:00,11322.0 -2016-10-20 21:00:00,11295.0 -2016-10-20 22:00:00,11015.0 -2016-10-20 23:00:00,10588.0 -2016-10-21 00:00:00,9878.0 -2016-10-19 01:00:00,9485.0 -2016-10-19 02:00:00,8914.0 -2016-10-19 03:00:00,8510.0 -2016-10-19 04:00:00,8238.0 -2016-10-19 05:00:00,8083.0 -2016-10-19 06:00:00,8259.0 -2016-10-19 07:00:00,8746.0 -2016-10-19 08:00:00,9745.0 -2016-10-19 09:00:00,10306.0 -2016-10-19 10:00:00,10590.0 -2016-10-19 11:00:00,11014.0 -2016-10-19 12:00:00,11348.0 -2016-10-19 13:00:00,11507.0 -2016-10-19 14:00:00,11642.0 -2016-10-19 15:00:00,11769.0 -2016-10-19 16:00:00,11725.0 -2016-10-19 17:00:00,11559.0 -2016-10-19 18:00:00,11447.0 -2016-10-19 19:00:00,11448.0 -2016-10-19 20:00:00,11804.0 -2016-10-19 21:00:00,11739.0 -2016-10-19 22:00:00,11292.0 -2016-10-19 23:00:00,10724.0 -2016-10-20 00:00:00,10018.0 -2016-10-18 01:00:00,10769.0 -2016-10-18 02:00:00,10179.0 -2016-10-18 03:00:00,9714.0 -2016-10-18 04:00:00,9404.0 -2016-10-18 05:00:00,9185.0 -2016-10-18 06:00:00,9221.0 -2016-10-18 07:00:00,9815.0 -2016-10-18 08:00:00,11006.0 -2016-10-18 09:00:00,11731.0 -2016-10-18 10:00:00,12074.0 -2016-10-18 11:00:00,12494.0 -2016-10-18 12:00:00,12872.0 -2016-10-18 13:00:00,13147.0 -2016-10-18 14:00:00,13264.0 -2016-10-18 15:00:00,13401.0 -2016-10-18 16:00:00,13422.0 -2016-10-18 17:00:00,13289.0 -2016-10-18 18:00:00,13096.0 -2016-10-18 19:00:00,12658.0 -2016-10-18 20:00:00,12671.0 -2016-10-18 21:00:00,12455.0 -2016-10-18 22:00:00,11968.0 -2016-10-18 23:00:00,11217.0 -2016-10-19 00:00:00,10287.0 -2016-10-17 01:00:00,9239.0 -2016-10-17 02:00:00,8929.0 -2016-10-17 03:00:00,8647.0 -2016-10-17 04:00:00,8505.0 -2016-10-17 05:00:00,8428.0 -2016-10-17 06:00:00,8617.0 -2016-10-17 07:00:00,9394.0 -2016-10-17 08:00:00,10496.0 -2016-10-17 09:00:00,11119.0 -2016-10-17 10:00:00,11548.0 -2016-10-17 11:00:00,11774.0 -2016-10-17 12:00:00,12180.0 -2016-10-17 13:00:00,12513.0 -2016-10-17 14:00:00,12748.0 -2016-10-17 15:00:00,13031.0 -2016-10-17 16:00:00,13425.0 -2016-10-17 17:00:00,13635.0 -2016-10-17 18:00:00,13629.0 -2016-10-17 19:00:00,13394.0 -2016-10-17 20:00:00,13634.0 -2016-10-17 21:00:00,13678.0 -2016-10-17 22:00:00,13285.0 -2016-10-17 23:00:00,12586.0 -2016-10-18 00:00:00,11738.0 -2016-10-16 01:00:00,8908.0 -2016-10-16 02:00:00,8521.0 -2016-10-16 03:00:00,8230.0 -2016-10-16 04:00:00,8031.0 -2016-10-16 05:00:00,7944.0 -2016-10-16 06:00:00,7885.0 -2016-10-16 07:00:00,7995.0 -2016-10-16 08:00:00,8167.0 -2016-10-16 09:00:00,8404.0 -2016-10-16 10:00:00,8660.0 -2016-10-16 11:00:00,8955.0 -2016-10-16 12:00:00,9188.0 -2016-10-16 13:00:00,9540.0 -2016-10-16 14:00:00,9684.0 -2016-10-16 15:00:00,9725.0 -2016-10-16 16:00:00,9744.0 -2016-10-16 17:00:00,9839.0 -2016-10-16 18:00:00,9941.0 -2016-10-16 19:00:00,10139.0 -2016-10-16 20:00:00,10658.0 -2016-10-16 21:00:00,10931.0 -2016-10-16 22:00:00,10711.0 -2016-10-16 23:00:00,10346.0 -2016-10-17 00:00:00,9798.0 -2016-10-15 01:00:00,9184.0 -2016-10-15 02:00:00,8619.0 -2016-10-15 03:00:00,8325.0 -2016-10-15 04:00:00,8075.0 -2016-10-15 05:00:00,7923.0 -2016-10-15 06:00:00,7959.0 -2016-10-15 07:00:00,8185.0 -2016-10-15 08:00:00,8667.0 -2016-10-15 09:00:00,8973.0 -2016-10-15 10:00:00,9334.0 -2016-10-15 11:00:00,9685.0 -2016-10-15 12:00:00,9843.0 -2016-10-15 13:00:00,9971.0 -2016-10-15 14:00:00,9921.0 -2016-10-15 15:00:00,9847.0 -2016-10-15 16:00:00,9771.0 -2016-10-15 17:00:00,9806.0 -2016-10-15 18:00:00,9748.0 -2016-10-15 19:00:00,9722.0 -2016-10-15 20:00:00,10216.0 -2016-10-15 21:00:00,10378.0 -2016-10-15 22:00:00,10213.0 -2016-10-15 23:00:00,9837.0 -2016-10-16 00:00:00,9312.0 -2016-10-14 01:00:00,8950.0 -2016-10-14 02:00:00,8489.0 -2016-10-14 03:00:00,8191.0 -2016-10-14 04:00:00,8044.0 -2016-10-14 05:00:00,8012.0 -2016-10-14 06:00:00,8210.0 -2016-10-14 07:00:00,8836.0 -2016-10-14 08:00:00,9942.0 -2016-10-14 09:00:00,10466.0 -2016-10-14 10:00:00,10612.0 -2016-10-14 11:00:00,10705.0 -2016-10-14 12:00:00,10823.0 -2016-10-14 13:00:00,10846.0 -2016-10-14 14:00:00,10834.0 -2016-10-14 15:00:00,10887.0 -2016-10-14 16:00:00,10815.0 -2016-10-14 17:00:00,10691.0 -2016-10-14 18:00:00,10595.0 -2016-10-14 19:00:00,10475.0 -2016-10-14 20:00:00,10805.0 -2016-10-14 21:00:00,10828.0 -2016-10-14 22:00:00,10668.0 -2016-10-14 23:00:00,10376.0 -2016-10-15 00:00:00,9772.0 -2016-10-13 01:00:00,8805.0 -2016-10-13 02:00:00,8352.0 -2016-10-13 03:00:00,8035.0 -2016-10-13 04:00:00,7871.0 -2016-10-13 05:00:00,7868.0 -2016-10-13 06:00:00,8083.0 -2016-10-13 07:00:00,8747.0 -2016-10-13 08:00:00,9890.0 -2016-10-13 09:00:00,10419.0 -2016-10-13 10:00:00,10615.0 -2016-10-13 11:00:00,10703.0 -2016-10-13 12:00:00,10755.0 -2016-10-13 13:00:00,10735.0 -2016-10-13 14:00:00,10700.0 -2016-10-13 15:00:00,10725.0 -2016-10-13 16:00:00,10650.0 -2016-10-13 17:00:00,10541.0 -2016-10-13 18:00:00,10486.0 -2016-10-13 19:00:00,10425.0 -2016-10-13 20:00:00,10861.0 -2016-10-13 21:00:00,11006.0 -2016-10-13 22:00:00,10764.0 -2016-10-13 23:00:00,10323.0 -2016-10-14 00:00:00,9607.0 -2016-10-12 01:00:00,9197.0 -2016-10-12 02:00:00,8652.0 -2016-10-12 03:00:00,8319.0 -2016-10-12 04:00:00,8146.0 -2016-10-12 05:00:00,8112.0 -2016-10-12 06:00:00,8314.0 -2016-10-12 07:00:00,8959.0 -2016-10-12 08:00:00,10083.0 -2016-10-12 09:00:00,10604.0 -2016-10-12 10:00:00,10959.0 -2016-10-12 11:00:00,11261.0 -2016-10-12 12:00:00,11524.0 -2016-10-12 13:00:00,11612.0 -2016-10-12 14:00:00,11665.0 -2016-10-12 15:00:00,11589.0 -2016-10-12 16:00:00,11482.0 -2016-10-12 17:00:00,11405.0 -2016-10-12 18:00:00,11222.0 -2016-10-12 19:00:00,11006.0 -2016-10-12 20:00:00,11125.0 -2016-10-12 21:00:00,11074.0 -2016-10-12 22:00:00,10782.0 -2016-10-12 23:00:00,10248.0 -2016-10-13 00:00:00,9489.0 -2016-10-11 01:00:00,9066.0 -2016-10-11 02:00:00,8608.0 -2016-10-11 03:00:00,8313.0 -2016-10-11 04:00:00,7953.0 -2016-10-11 05:00:00,7938.0 -2016-10-11 06:00:00,8140.0 -2016-10-11 07:00:00,8759.0 -2016-10-11 08:00:00,9855.0 -2016-10-11 09:00:00,10406.0 -2016-10-11 10:00:00,10731.0 -2016-10-11 11:00:00,11015.0 -2016-10-11 12:00:00,11227.0 -2016-10-11 13:00:00,11332.0 -2016-10-11 14:00:00,11427.0 -2016-10-11 15:00:00,11568.0 -2016-10-11 16:00:00,11606.0 -2016-10-11 17:00:00,11553.0 -2016-10-11 18:00:00,11495.0 -2016-10-11 19:00:00,11347.0 -2016-10-11 20:00:00,11552.0 -2016-10-11 21:00:00,11635.0 -2016-10-11 22:00:00,11272.0 -2016-10-11 23:00:00,10697.0 -2016-10-12 00:00:00,9938.0 -2016-10-10 01:00:00,8293.0 -2016-10-10 02:00:00,7938.0 -2016-10-10 03:00:00,7691.0 -2016-10-10 04:00:00,7646.0 -2016-10-10 05:00:00,7606.0 -2016-10-10 06:00:00,7832.0 -2016-10-10 07:00:00,8394.0 -2016-10-10 08:00:00,9388.0 -2016-10-10 09:00:00,9859.0 -2016-10-10 10:00:00,10162.0 -2016-10-10 11:00:00,10448.0 -2016-10-10 12:00:00,10724.0 -2016-10-10 13:00:00,10892.0 -2016-10-10 14:00:00,11005.0 -2016-10-10 15:00:00,11116.0 -2016-10-10 16:00:00,11130.0 -2016-10-10 17:00:00,11054.0 -2016-10-10 18:00:00,10994.0 -2016-10-10 19:00:00,10889.0 -2016-10-10 20:00:00,11246.0 -2016-10-10 21:00:00,11288.0 -2016-10-10 22:00:00,10969.0 -2016-10-10 23:00:00,10431.0 -2016-10-11 00:00:00,9747.0 -2016-10-09 01:00:00,8290.0 -2016-10-09 02:00:00,7904.0 -2016-10-09 03:00:00,7641.0 -2016-10-09 04:00:00,7458.0 -2016-10-09 05:00:00,7359.0 -2016-10-09 06:00:00,7333.0 -2016-10-09 07:00:00,7466.0 -2016-10-09 08:00:00,7662.0 -2016-10-09 09:00:00,7691.0 -2016-10-09 10:00:00,8006.0 -2016-10-09 11:00:00,8334.0 -2016-10-09 12:00:00,8525.0 -2016-10-09 13:00:00,8676.0 -2016-10-09 14:00:00,8758.0 -2016-10-09 15:00:00,8799.0 -2016-10-09 16:00:00,8819.0 -2016-10-09 17:00:00,8831.0 -2016-10-09 18:00:00,8871.0 -2016-10-09 19:00:00,8931.0 -2016-10-09 20:00:00,9376.0 -2016-10-09 21:00:00,9665.0 -2016-10-09 22:00:00,9453.0 -2016-10-09 23:00:00,9160.0 -2016-10-10 00:00:00,8789.0 -2016-10-08 01:00:00,8766.0 -2016-10-08 02:00:00,8225.0 -2016-10-08 03:00:00,7984.0 -2016-10-08 04:00:00,7694.0 -2016-10-08 05:00:00,7589.0 -2016-10-08 06:00:00,7653.0 -2016-10-08 07:00:00,7886.0 -2016-10-08 08:00:00,8274.0 -2016-10-08 09:00:00,8463.0 -2016-10-08 10:00:00,8828.0 -2016-10-08 11:00:00,9072.0 -2016-10-08 12:00:00,9200.0 -2016-10-08 13:00:00,9292.0 -2016-10-08 14:00:00,9266.0 -2016-10-08 15:00:00,9134.0 -2016-10-08 16:00:00,9138.0 -2016-10-08 17:00:00,9106.0 -2016-10-08 18:00:00,9070.0 -2016-10-08 19:00:00,9110.0 -2016-10-08 20:00:00,9441.0 -2016-10-08 21:00:00,9695.0 -2016-10-08 22:00:00,9505.0 -2016-10-08 23:00:00,9227.0 -2016-10-09 00:00:00,8797.0 -2016-10-07 01:00:00,9997.0 -2016-10-07 02:00:00,9411.0 -2016-10-07 03:00:00,9069.0 -2016-10-07 04:00:00,8690.0 -2016-10-07 05:00:00,8621.0 -2016-10-07 06:00:00,8789.0 -2016-10-07 07:00:00,9399.0 -2016-10-07 08:00:00,10473.0 -2016-10-07 09:00:00,11015.0 -2016-10-07 10:00:00,11541.0 -2016-10-07 11:00:00,11864.0 -2016-10-07 12:00:00,12188.0 -2016-10-07 13:00:00,11994.0 -2016-10-07 14:00:00,11722.0 -2016-10-07 15:00:00,11445.0 -2016-10-07 16:00:00,11245.0 -2016-10-07 17:00:00,11008.0 -2016-10-07 18:00:00,10791.0 -2016-10-07 19:00:00,10618.0 -2016-10-07 20:00:00,10732.0 -2016-10-07 21:00:00,10837.0 -2016-10-07 22:00:00,10527.0 -2016-10-07 23:00:00,10095.0 -2016-10-08 00:00:00,9489.0 -2016-10-06 01:00:00,9695.0 -2016-10-06 02:00:00,9067.0 -2016-10-06 03:00:00,8703.0 -2016-10-06 04:00:00,8498.0 -2016-10-06 05:00:00,8401.0 -2016-10-06 06:00:00,8601.0 -2016-10-06 07:00:00,9347.0 -2016-10-06 08:00:00,10526.0 -2016-10-06 09:00:00,11316.0 -2016-10-06 10:00:00,11678.0 -2016-10-06 11:00:00,11920.0 -2016-10-06 12:00:00,12105.0 -2016-10-06 13:00:00,12139.0 -2016-10-06 14:00:00,12348.0 -2016-10-06 15:00:00,12444.0 -2016-10-06 16:00:00,12345.0 -2016-10-06 17:00:00,12343.0 -2016-10-06 18:00:00,12308.0 -2016-10-06 19:00:00,12193.0 -2016-10-06 20:00:00,12451.0 -2016-10-06 21:00:00,12496.0 -2016-10-06 22:00:00,12196.0 -2016-10-06 23:00:00,11639.0 -2016-10-07 00:00:00,10825.0 -2016-10-05 01:00:00,9495.0 -2016-10-05 02:00:00,8953.0 -2016-10-05 03:00:00,8601.0 -2016-10-05 04:00:00,8432.0 -2016-10-05 05:00:00,8378.0 -2016-10-05 06:00:00,8550.0 -2016-10-05 07:00:00,9217.0 -2016-10-05 08:00:00,10384.0 -2016-10-05 09:00:00,10960.0 -2016-10-05 10:00:00,11430.0 -2016-10-05 11:00:00,11835.0 -2016-10-05 12:00:00,12289.0 -2016-10-05 13:00:00,12387.0 -2016-10-05 14:00:00,12510.0 -2016-10-05 15:00:00,12639.0 -2016-10-05 16:00:00,12970.0 -2016-10-05 17:00:00,13030.0 -2016-10-05 18:00:00,13006.0 -2016-10-05 19:00:00,12736.0 -2016-10-05 20:00:00,12650.0 -2016-10-05 21:00:00,12673.0 -2016-10-05 22:00:00,12234.0 -2016-10-05 23:00:00,11479.0 -2016-10-06 00:00:00,10562.0 -2016-10-04 01:00:00,9197.0 -2016-10-04 02:00:00,8701.0 -2016-10-04 03:00:00,8404.0 -2016-10-04 04:00:00,8227.0 -2016-10-04 05:00:00,8162.0 -2016-10-04 06:00:00,8363.0 -2016-10-04 07:00:00,9011.0 -2016-10-04 08:00:00,10128.0 -2016-10-04 09:00:00,10736.0 -2016-10-04 10:00:00,11050.0 -2016-10-04 11:00:00,11282.0 -2016-10-04 12:00:00,11583.0 -2016-10-04 13:00:00,11837.0 -2016-10-04 14:00:00,11905.0 -2016-10-04 15:00:00,12005.0 -2016-10-04 16:00:00,12015.0 -2016-10-04 17:00:00,12007.0 -2016-10-04 18:00:00,11951.0 -2016-10-04 19:00:00,11731.0 -2016-10-04 20:00:00,11747.0 -2016-10-04 21:00:00,12025.0 -2016-10-04 22:00:00,11731.0 -2016-10-04 23:00:00,11146.0 -2016-10-05 00:00:00,10298.0 -2016-10-03 01:00:00,8514.0 -2016-10-03 02:00:00,8181.0 -2016-10-03 03:00:00,7958.0 -2016-10-03 04:00:00,7869.0 -2016-10-03 05:00:00,7851.0 -2016-10-03 06:00:00,8097.0 -2016-10-03 07:00:00,8754.0 -2016-10-03 08:00:00,9912.0 -2016-10-03 09:00:00,10500.0 -2016-10-03 10:00:00,10885.0 -2016-10-03 11:00:00,11070.0 -2016-10-03 12:00:00,11341.0 -2016-10-03 13:00:00,11450.0 -2016-10-03 14:00:00,11480.0 -2016-10-03 15:00:00,11537.0 -2016-10-03 16:00:00,11434.0 -2016-10-03 17:00:00,11326.0 -2016-10-03 18:00:00,11249.0 -2016-10-03 19:00:00,11160.0 -2016-10-03 20:00:00,11389.0 -2016-10-03 21:00:00,11567.0 -2016-10-03 22:00:00,11255.0 -2016-10-03 23:00:00,10727.0 -2016-10-04 00:00:00,9955.0 -2016-10-02 01:00:00,8637.0 -2016-10-02 02:00:00,8213.0 -2016-10-02 03:00:00,7900.0 -2016-10-02 04:00:00,7734.0 -2016-10-02 05:00:00,7594.0 -2016-10-02 06:00:00,7595.0 -2016-10-02 07:00:00,7633.0 -2016-10-02 08:00:00,7861.0 -2016-10-02 09:00:00,7872.0 -2016-10-02 10:00:00,8278.0 -2016-10-02 11:00:00,8548.0 -2016-10-02 12:00:00,8849.0 -2016-10-02 13:00:00,9038.0 -2016-10-02 14:00:00,9106.0 -2016-10-02 15:00:00,9114.0 -2016-10-02 16:00:00,9101.0 -2016-10-02 17:00:00,9191.0 -2016-10-02 18:00:00,9261.0 -2016-10-02 19:00:00,9346.0 -2016-10-02 20:00:00,9735.0 -2016-10-02 21:00:00,10042.0 -2016-10-02 22:00:00,9835.0 -2016-10-02 23:00:00,9483.0 -2016-10-03 00:00:00,9043.0 -2016-10-01 01:00:00,9142.0 -2016-10-01 02:00:00,8616.0 -2016-10-01 03:00:00,8302.0 -2016-10-01 04:00:00,8102.0 -2016-10-01 05:00:00,7964.0 -2016-10-01 06:00:00,7979.0 -2016-10-01 07:00:00,8222.0 -2016-10-01 08:00:00,8603.0 -2016-10-01 09:00:00,8909.0 -2016-10-01 10:00:00,9375.0 -2016-10-01 11:00:00,9784.0 -2016-10-01 12:00:00,9969.0 -2016-10-01 13:00:00,10061.0 -2016-10-01 14:00:00,9950.0 -2016-10-01 15:00:00,9795.0 -2016-10-01 16:00:00,9698.0 -2016-10-01 17:00:00,9595.0 -2016-10-01 18:00:00,9573.0 -2016-10-01 19:00:00,9610.0 -2016-10-01 20:00:00,9864.0 -2016-10-01 21:00:00,10074.0 -2016-10-01 22:00:00,9950.0 -2016-10-01 23:00:00,9623.0 -2016-10-02 00:00:00,9182.0 -2016-09-30 01:00:00,9163.0 -2016-09-30 02:00:00,8694.0 -2016-09-30 03:00:00,8429.0 -2016-09-30 04:00:00,8234.0 -2016-09-30 05:00:00,8212.0 -2016-09-30 06:00:00,8339.0 -2016-09-30 07:00:00,8949.0 -2016-09-30 08:00:00,10036.0 -2016-09-30 09:00:00,10766.0 -2016-09-30 10:00:00,11079.0 -2016-09-30 11:00:00,11331.0 -2016-09-30 12:00:00,11503.0 -2016-09-30 13:00:00,11449.0 -2016-09-30 14:00:00,11399.0 -2016-09-30 15:00:00,11394.0 -2016-09-30 16:00:00,11267.0 -2016-09-30 17:00:00,11138.0 -2016-09-30 18:00:00,11042.0 -2016-09-30 19:00:00,10998.0 -2016-09-30 20:00:00,11152.0 -2016-09-30 21:00:00,11121.0 -2016-09-30 22:00:00,10832.0 -2016-09-30 23:00:00,10458.0 -2016-10-01 00:00:00,9808.0 -2016-09-29 01:00:00,9000.0 -2016-09-29 02:00:00,8586.0 -2016-09-29 03:00:00,8273.0 -2016-09-29 04:00:00,8091.0 -2016-09-29 05:00:00,8091.0 -2016-09-29 06:00:00,8253.0 -2016-09-29 07:00:00,8918.0 -2016-09-29 08:00:00,10056.0 -2016-09-29 09:00:00,10695.0 -2016-09-29 10:00:00,11026.0 -2016-09-29 11:00:00,11246.0 -2016-09-29 12:00:00,11410.0 -2016-09-29 13:00:00,11432.0 -2016-09-29 14:00:00,11462.0 -2016-09-29 15:00:00,11492.0 -2016-09-29 16:00:00,11408.0 -2016-09-29 17:00:00,11281.0 -2016-09-29 18:00:00,11204.0 -2016-09-29 19:00:00,11078.0 -2016-09-29 20:00:00,11225.0 -2016-09-29 21:00:00,11434.0 -2016-09-29 22:00:00,11146.0 -2016-09-29 23:00:00,10666.0 -2016-09-30 00:00:00,9902.0 -2016-09-28 01:00:00,8947.0 -2016-09-28 02:00:00,8459.0 -2016-09-28 03:00:00,8160.0 -2016-09-28 04:00:00,8019.0 -2016-09-28 05:00:00,7966.0 -2016-09-28 06:00:00,8151.0 -2016-09-28 07:00:00,8756.0 -2016-09-28 08:00:00,9778.0 -2016-09-28 09:00:00,10302.0 -2016-09-28 10:00:00,10562.0 -2016-09-28 11:00:00,10773.0 -2016-09-28 12:00:00,10946.0 -2016-09-28 13:00:00,10988.0 -2016-09-28 14:00:00,10973.0 -2016-09-28 15:00:00,10999.0 -2016-09-28 16:00:00,10956.0 -2016-09-28 17:00:00,10891.0 -2016-09-28 18:00:00,10864.0 -2016-09-28 19:00:00,10778.0 -2016-09-28 20:00:00,10925.0 -2016-09-28 21:00:00,11191.0 -2016-09-28 22:00:00,10922.0 -2016-09-28 23:00:00,10409.0 -2016-09-29 00:00:00,9640.0 -2016-09-27 01:00:00,9080.0 -2016-09-27 02:00:00,8583.0 -2016-09-27 03:00:00,8222.0 -2016-09-27 04:00:00,8048.0 -2016-09-27 05:00:00,7957.0 -2016-09-27 06:00:00,8136.0 -2016-09-27 07:00:00,8713.0 -2016-09-27 08:00:00,9712.0 -2016-09-27 09:00:00,10189.0 -2016-09-27 10:00:00,10509.0 -2016-09-27 11:00:00,10823.0 -2016-09-27 12:00:00,11116.0 -2016-09-27 13:00:00,11333.0 -2016-09-27 14:00:00,11416.0 -2016-09-27 15:00:00,11556.0 -2016-09-27 16:00:00,11542.0 -2016-09-27 17:00:00,11398.0 -2016-09-27 18:00:00,11292.0 -2016-09-27 19:00:00,11074.0 -2016-09-27 20:00:00,11085.0 -2016-09-27 21:00:00,11356.0 -2016-09-27 22:00:00,11087.0 -2016-09-27 23:00:00,10485.0 -2016-09-28 00:00:00,9679.0 -2016-09-26 01:00:00,10275.0 -2016-09-26 02:00:00,9453.0 -2016-09-26 03:00:00,8947.0 -2016-09-26 04:00:00,8659.0 -2016-09-26 05:00:00,8498.0 -2016-09-26 06:00:00,8636.0 -2016-09-26 07:00:00,9301.0 -2016-09-26 08:00:00,10304.0 -2016-09-26 09:00:00,10811.0 -2016-09-26 10:00:00,11085.0 -2016-09-26 11:00:00,11430.0 -2016-09-26 12:00:00,11834.0 -2016-09-26 13:00:00,11927.0 -2016-09-26 14:00:00,11968.0 -2016-09-26 15:00:00,12075.0 -2016-09-26 16:00:00,12104.0 -2016-09-26 17:00:00,12050.0 -2016-09-26 18:00:00,11903.0 -2016-09-26 19:00:00,11582.0 -2016-09-26 20:00:00,11359.0 -2016-09-26 21:00:00,11600.0 -2016-09-26 22:00:00,11217.0 -2016-09-26 23:00:00,10650.0 -2016-09-27 00:00:00,9879.0 -2016-09-25 01:00:00,10013.0 -2016-09-25 02:00:00,9362.0 -2016-09-25 03:00:00,8944.0 -2016-09-25 04:00:00,8633.0 -2016-09-25 05:00:00,8460.0 -2016-09-25 06:00:00,8354.0 -2016-09-25 07:00:00,8430.0 -2016-09-25 08:00:00,8499.0 -2016-09-25 09:00:00,8565.0 -2016-09-25 10:00:00,9146.0 -2016-09-25 11:00:00,9888.0 -2016-09-25 12:00:00,10681.0 -2016-09-25 13:00:00,11486.0 -2016-09-25 14:00:00,12179.0 -2016-09-25 15:00:00,13028.0 -2016-09-25 16:00:00,13554.0 -2016-09-25 17:00:00,13876.0 -2016-09-25 18:00:00,13965.0 -2016-09-25 19:00:00,13809.0 -2016-09-25 20:00:00,13567.0 -2016-09-25 21:00:00,13847.0 -2016-09-25 22:00:00,13510.0 -2016-09-25 23:00:00,12790.0 -2016-09-26 00:00:00,11502.0 -2016-09-24 01:00:00,10670.0 -2016-09-24 02:00:00,9942.0 -2016-09-24 03:00:00,9501.0 -2016-09-24 04:00:00,9156.0 -2016-09-24 05:00:00,8961.0 -2016-09-24 06:00:00,8946.0 -2016-09-24 07:00:00,9191.0 -2016-09-24 08:00:00,9579.0 -2016-09-24 09:00:00,9869.0 -2016-09-24 10:00:00,10363.0 -2016-09-24 11:00:00,10786.0 -2016-09-24 12:00:00,11071.0 -2016-09-24 13:00:00,11318.0 -2016-09-24 14:00:00,11555.0 -2016-09-24 15:00:00,11810.0 -2016-09-24 16:00:00,12096.0 -2016-09-24 17:00:00,12286.0 -2016-09-24 18:00:00,12219.0 -2016-09-24 19:00:00,11917.0 -2016-09-24 20:00:00,11647.0 -2016-09-24 21:00:00,11871.0 -2016-09-24 22:00:00,11645.0 -2016-09-24 23:00:00,11256.0 -2016-09-25 00:00:00,10623.0 -2016-09-23 01:00:00,11473.0 -2016-09-23 02:00:00,10653.0 -2016-09-23 03:00:00,10096.0 -2016-09-23 04:00:00,9707.0 -2016-09-23 05:00:00,9564.0 -2016-09-23 06:00:00,9719.0 -2016-09-23 07:00:00,10437.0 -2016-09-23 08:00:00,11616.0 -2016-09-23 09:00:00,12269.0 -2016-09-23 10:00:00,12699.0 -2016-09-23 11:00:00,13082.0 -2016-09-23 12:00:00,13300.0 -2016-09-23 13:00:00,13423.0 -2016-09-23 14:00:00,13593.0 -2016-09-23 15:00:00,13842.0 -2016-09-23 16:00:00,13900.0 -2016-09-23 17:00:00,13846.0 -2016-09-23 18:00:00,13664.0 -2016-09-23 19:00:00,13312.0 -2016-09-23 20:00:00,13039.0 -2016-09-23 21:00:00,13187.0 -2016-09-23 22:00:00,12849.0 -2016-09-23 23:00:00,12343.0 -2016-09-24 00:00:00,11533.0 -2016-09-22 01:00:00,10484.0 -2016-09-22 02:00:00,9818.0 -2016-09-22 03:00:00,9410.0 -2016-09-22 04:00:00,9181.0 -2016-09-22 05:00:00,9068.0 -2016-09-22 06:00:00,9298.0 -2016-09-22 07:00:00,9977.0 -2016-09-22 08:00:00,11058.0 -2016-09-22 09:00:00,11691.0 -2016-09-22 10:00:00,12296.0 -2016-09-22 11:00:00,12750.0 -2016-09-22 12:00:00,13229.0 -2016-09-22 13:00:00,13513.0 -2016-09-22 14:00:00,13744.0 -2016-09-22 15:00:00,14107.0 -2016-09-22 16:00:00,14443.0 -2016-09-22 17:00:00,14588.0 -2016-09-22 18:00:00,14705.0 -2016-09-22 19:00:00,14565.0 -2016-09-22 20:00:00,14499.0 -2016-09-22 21:00:00,14739.0 -2016-09-22 22:00:00,14362.0 -2016-09-22 23:00:00,13620.0 -2016-09-23 00:00:00,12519.0 -2016-09-21 01:00:00,10983.0 -2016-09-21 02:00:00,10315.0 -2016-09-21 03:00:00,9875.0 -2016-09-21 04:00:00,9610.0 -2016-09-21 05:00:00,9482.0 -2016-09-21 06:00:00,9686.0 -2016-09-21 07:00:00,10396.0 -2016-09-21 08:00:00,11581.0 -2016-09-21 09:00:00,12505.0 -2016-09-21 10:00:00,12791.0 -2016-09-21 11:00:00,12875.0 -2016-09-21 12:00:00,13413.0 -2016-09-21 13:00:00,13691.0 -2016-09-21 14:00:00,13685.0 -2016-09-21 15:00:00,13617.0 -2016-09-21 16:00:00,13444.0 -2016-09-21 17:00:00,13201.0 -2016-09-21 18:00:00,13191.0 -2016-09-21 19:00:00,13281.0 -2016-09-21 20:00:00,13155.0 -2016-09-21 21:00:00,13403.0 -2016-09-21 22:00:00,13036.0 -2016-09-21 23:00:00,12364.0 -2016-09-22 00:00:00,11415.0 -2016-09-20 01:00:00,11399.0 -2016-09-20 02:00:00,10608.0 -2016-09-20 03:00:00,10105.0 -2016-09-20 04:00:00,9762.0 -2016-09-20 05:00:00,9609.0 -2016-09-20 06:00:00,9726.0 -2016-09-20 07:00:00,10451.0 -2016-09-20 08:00:00,11556.0 -2016-09-20 09:00:00,12228.0 -2016-09-20 10:00:00,13004.0 -2016-09-20 11:00:00,13789.0 -2016-09-20 12:00:00,14519.0 -2016-09-20 13:00:00,15038.0 -2016-09-20 14:00:00,15445.0 -2016-09-20 15:00:00,15860.0 -2016-09-20 16:00:00,16127.0 -2016-09-20 17:00:00,16276.0 -2016-09-20 18:00:00,16189.0 -2016-09-20 19:00:00,15713.0 -2016-09-20 20:00:00,14787.0 -2016-09-20 21:00:00,14511.0 -2016-09-20 22:00:00,13912.0 -2016-09-20 23:00:00,12993.0 -2016-09-21 00:00:00,11955.0 -2016-09-19 01:00:00,10050.0 -2016-09-19 02:00:00,9409.0 -2016-09-19 03:00:00,9023.0 -2016-09-19 04:00:00,8756.0 -2016-09-19 05:00:00,8684.0 -2016-09-19 06:00:00,8853.0 -2016-09-19 07:00:00,9573.0 -2016-09-19 08:00:00,10512.0 -2016-09-19 09:00:00,11264.0 -2016-09-19 10:00:00,11990.0 -2016-09-19 11:00:00,12693.0 -2016-09-19 12:00:00,13457.0 -2016-09-19 13:00:00,14147.0 -2016-09-19 14:00:00,14747.0 -2016-09-19 15:00:00,15436.0 -2016-09-19 16:00:00,15930.0 -2016-09-19 17:00:00,16299.0 -2016-09-19 18:00:00,16430.0 -2016-09-19 19:00:00,15931.0 -2016-09-19 20:00:00,15346.0 -2016-09-19 21:00:00,15283.0 -2016-09-19 22:00:00,14697.0 -2016-09-19 23:00:00,13736.0 -2016-09-20 00:00:00,12559.0 -2016-09-18 01:00:00,10106.0 -2016-09-18 02:00:00,9388.0 -2016-09-18 03:00:00,8940.0 -2016-09-18 04:00:00,8578.0 -2016-09-18 05:00:00,8404.0 -2016-09-18 06:00:00,8268.0 -2016-09-18 07:00:00,8331.0 -2016-09-18 08:00:00,8368.0 -2016-09-18 09:00:00,8516.0 -2016-09-18 10:00:00,9155.0 -2016-09-18 11:00:00,9820.0 -2016-09-18 12:00:00,10450.0 -2016-09-18 13:00:00,10965.0 -2016-09-18 14:00:00,11467.0 -2016-09-18 15:00:00,11929.0 -2016-09-18 16:00:00,12428.0 -2016-09-18 17:00:00,12792.0 -2016-09-18 18:00:00,13078.0 -2016-09-18 19:00:00,13014.0 -2016-09-18 20:00:00,12644.0 -2016-09-18 21:00:00,12736.0 -2016-09-18 22:00:00,12387.0 -2016-09-18 23:00:00,11770.0 -2016-09-19 00:00:00,10909.0 -2016-09-17 01:00:00,11940.0 -2016-09-17 02:00:00,11000.0 -2016-09-17 03:00:00,10405.0 -2016-09-17 04:00:00,9919.0 -2016-09-17 05:00:00,9613.0 -2016-09-17 06:00:00,9506.0 -2016-09-17 07:00:00,9694.0 -2016-09-17 08:00:00,10046.0 -2016-09-17 09:00:00,10348.0 -2016-09-17 10:00:00,10991.0 -2016-09-17 11:00:00,11793.0 -2016-09-17 12:00:00,12425.0 -2016-09-17 13:00:00,12962.0 -2016-09-17 14:00:00,13378.0 -2016-09-17 15:00:00,13647.0 -2016-09-17 16:00:00,13849.0 -2016-09-17 17:00:00,13922.0 -2016-09-17 18:00:00,13874.0 -2016-09-17 19:00:00,13428.0 -2016-09-17 20:00:00,12751.0 -2016-09-17 21:00:00,12626.0 -2016-09-17 22:00:00,12184.0 -2016-09-17 23:00:00,11611.0 -2016-09-18 00:00:00,10841.0 -2016-09-16 01:00:00,10564.0 -2016-09-16 02:00:00,9852.0 -2016-09-16 03:00:00,9370.0 -2016-09-16 04:00:00,9073.0 -2016-09-16 05:00:00,8935.0 -2016-09-16 06:00:00,9024.0 -2016-09-16 07:00:00,9731.0 -2016-09-16 08:00:00,10800.0 -2016-09-16 09:00:00,11427.0 -2016-09-16 10:00:00,12022.0 -2016-09-16 11:00:00,12608.0 -2016-09-16 12:00:00,13476.0 -2016-09-16 13:00:00,14266.0 -2016-09-16 14:00:00,14500.0 -2016-09-16 15:00:00,14721.0 -2016-09-16 16:00:00,15273.0 -2016-09-16 17:00:00,15660.0 -2016-09-16 18:00:00,15647.0 -2016-09-16 19:00:00,15238.0 -2016-09-16 20:00:00,14729.0 -2016-09-16 21:00:00,14697.0 -2016-09-16 22:00:00,14335.0 -2016-09-16 23:00:00,13761.0 -2016-09-17 00:00:00,12866.0 -2016-09-15 01:00:00,10192.0 -2016-09-15 02:00:00,9537.0 -2016-09-15 03:00:00,9161.0 -2016-09-15 04:00:00,8908.0 -2016-09-15 05:00:00,8772.0 -2016-09-15 06:00:00,8964.0 -2016-09-15 07:00:00,9583.0 -2016-09-15 08:00:00,10523.0 -2016-09-15 09:00:00,11152.0 -2016-09-15 10:00:00,11750.0 -2016-09-15 11:00:00,12209.0 -2016-09-15 12:00:00,12638.0 -2016-09-15 13:00:00,12991.0 -2016-09-15 14:00:00,13396.0 -2016-09-15 15:00:00,13821.0 -2016-09-15 16:00:00,14096.0 -2016-09-15 17:00:00,14330.0 -2016-09-15 18:00:00,14388.0 -2016-09-15 19:00:00,14040.0 -2016-09-15 20:00:00,13398.0 -2016-09-15 21:00:00,13469.0 -2016-09-15 22:00:00,13168.0 -2016-09-15 23:00:00,12487.0 -2016-09-16 00:00:00,11476.0 -2016-09-14 01:00:00,11502.0 -2016-09-14 02:00:00,10685.0 -2016-09-14 03:00:00,10111.0 -2016-09-14 04:00:00,9747.0 -2016-09-14 05:00:00,9600.0 -2016-09-14 06:00:00,9672.0 -2016-09-14 07:00:00,10297.0 -2016-09-14 08:00:00,11358.0 -2016-09-14 09:00:00,11886.0 -2016-09-14 10:00:00,12258.0 -2016-09-14 11:00:00,12754.0 -2016-09-14 12:00:00,13177.0 -2016-09-14 13:00:00,13479.0 -2016-09-14 14:00:00,13689.0 -2016-09-14 15:00:00,13828.0 -2016-09-14 16:00:00,13844.0 -2016-09-14 17:00:00,13989.0 -2016-09-14 18:00:00,13900.0 -2016-09-14 19:00:00,13485.0 -2016-09-14 20:00:00,12842.0 -2016-09-14 21:00:00,12963.0 -2016-09-14 22:00:00,12698.0 -2016-09-14 23:00:00,11994.0 -2016-09-15 00:00:00,11087.0 -2016-09-13 01:00:00,10406.0 -2016-09-13 02:00:00,9787.0 -2016-09-13 03:00:00,9353.0 -2016-09-13 04:00:00,9060.0 -2016-09-13 05:00:00,8971.0 -2016-09-13 06:00:00,9091.0 -2016-09-13 07:00:00,9786.0 -2016-09-13 08:00:00,10724.0 -2016-09-13 09:00:00,11406.0 -2016-09-13 10:00:00,12171.0 -2016-09-13 11:00:00,12868.0 -2016-09-13 12:00:00,13556.0 -2016-09-13 13:00:00,14180.0 -2016-09-13 14:00:00,14873.0 -2016-09-13 15:00:00,15555.0 -2016-09-13 16:00:00,15941.0 -2016-09-13 17:00:00,16156.0 -2016-09-13 18:00:00,16143.0 -2016-09-13 19:00:00,15690.0 -2016-09-13 20:00:00,15044.0 -2016-09-13 21:00:00,15052.0 -2016-09-13 22:00:00,14679.0 -2016-09-13 23:00:00,13840.0 -2016-09-14 00:00:00,12677.0 -2016-09-12 01:00:00,9320.0 -2016-09-12 02:00:00,8847.0 -2016-09-12 03:00:00,8500.0 -2016-09-12 04:00:00,8383.0 -2016-09-12 05:00:00,8252.0 -2016-09-12 06:00:00,8513.0 -2016-09-12 07:00:00,9183.0 -2016-09-12 08:00:00,10128.0 -2016-09-12 09:00:00,10798.0 -2016-09-12 10:00:00,11494.0 -2016-09-12 11:00:00,12031.0 -2016-09-12 12:00:00,12581.0 -2016-09-12 13:00:00,12907.0 -2016-09-12 14:00:00,13254.0 -2016-09-12 15:00:00,13616.0 -2016-09-12 16:00:00,13899.0 -2016-09-12 17:00:00,14151.0 -2016-09-12 18:00:00,14227.0 -2016-09-12 19:00:00,13922.0 -2016-09-12 20:00:00,13347.0 -2016-09-12 21:00:00,13368.0 -2016-09-12 22:00:00,13071.0 -2016-09-12 23:00:00,12328.0 -2016-09-13 00:00:00,11360.0 -2016-09-11 01:00:00,9272.0 -2016-09-11 02:00:00,8723.0 -2016-09-11 03:00:00,8302.0 -2016-09-11 04:00:00,8115.0 -2016-09-11 05:00:00,7953.0 -2016-09-11 06:00:00,7850.0 -2016-09-11 07:00:00,7871.0 -2016-09-11 08:00:00,8009.0 -2016-09-11 09:00:00,8204.0 -2016-09-11 10:00:00,8707.0 -2016-09-11 11:00:00,9225.0 -2016-09-11 12:00:00,9787.0 -2016-09-11 13:00:00,10081.0 -2016-09-11 14:00:00,10414.0 -2016-09-11 15:00:00,10711.0 -2016-09-11 16:00:00,10965.0 -2016-09-11 17:00:00,11193.0 -2016-09-11 18:00:00,11341.0 -2016-09-11 19:00:00,11393.0 -2016-09-11 20:00:00,11170.0 -2016-09-11 21:00:00,11241.0 -2016-09-11 22:00:00,11140.0 -2016-09-11 23:00:00,10667.0 -2016-09-12 00:00:00,9900.0 -2016-09-10 01:00:00,11386.0 -2016-09-10 02:00:00,10654.0 -2016-09-10 03:00:00,10075.0 -2016-09-10 04:00:00,9700.0 -2016-09-10 05:00:00,9505.0 -2016-09-10 06:00:00,9467.0 -2016-09-10 07:00:00,9713.0 -2016-09-10 08:00:00,10068.0 -2016-09-10 09:00:00,10471.0 -2016-09-10 10:00:00,11081.0 -2016-09-10 11:00:00,11500.0 -2016-09-10 12:00:00,11714.0 -2016-09-10 13:00:00,11628.0 -2016-09-10 14:00:00,11636.0 -2016-09-10 15:00:00,11690.0 -2016-09-10 16:00:00,11685.0 -2016-09-10 17:00:00,11686.0 -2016-09-10 18:00:00,11712.0 -2016-09-10 19:00:00,11455.0 -2016-09-10 20:00:00,11100.0 -2016-09-10 21:00:00,11118.0 -2016-09-10 22:00:00,11039.0 -2016-09-10 23:00:00,10566.0 -2016-09-11 00:00:00,9974.0 -2016-09-09 01:00:00,12475.0 -2016-09-09 02:00:00,11505.0 -2016-09-09 03:00:00,10809.0 -2016-09-09 04:00:00,10355.0 -2016-09-09 05:00:00,10086.0 -2016-09-09 06:00:00,10169.0 -2016-09-09 07:00:00,10797.0 -2016-09-09 08:00:00,11728.0 -2016-09-09 09:00:00,12451.0 -2016-09-09 10:00:00,13125.0 -2016-09-09 11:00:00,13653.0 -2016-09-09 12:00:00,14144.0 -2016-09-09 13:00:00,14598.0 -2016-09-09 14:00:00,14945.0 -2016-09-09 15:00:00,15372.0 -2016-09-09 16:00:00,15725.0 -2016-09-09 17:00:00,15827.0 -2016-09-09 18:00:00,15571.0 -2016-09-09 19:00:00,15022.0 -2016-09-09 20:00:00,14283.0 -2016-09-09 21:00:00,14051.0 -2016-09-09 22:00:00,13757.0 -2016-09-09 23:00:00,13191.0 -2016-09-10 00:00:00,12354.0 -2016-09-08 01:00:00,13940.0 -2016-09-08 02:00:00,13007.0 -2016-09-08 03:00:00,12343.0 -2016-09-08 04:00:00,11903.0 -2016-09-08 05:00:00,11686.0 -2016-09-08 06:00:00,11778.0 -2016-09-08 07:00:00,12410.0 -2016-09-08 08:00:00,13376.0 -2016-09-08 09:00:00,13948.0 -2016-09-08 10:00:00,14290.0 -2016-09-08 11:00:00,14569.0 -2016-09-08 12:00:00,14808.0 -2016-09-08 13:00:00,15090.0 -2016-09-08 14:00:00,15532.0 -2016-09-08 15:00:00,16140.0 -2016-09-08 16:00:00,16453.0 -2016-09-08 17:00:00,16713.0 -2016-09-08 18:00:00,16996.0 -2016-09-08 19:00:00,17092.0 -2016-09-08 20:00:00,16544.0 -2016-09-08 21:00:00,16317.0 -2016-09-08 22:00:00,15992.0 -2016-09-08 23:00:00,15014.0 -2016-09-09 00:00:00,13763.0 -2016-09-07 01:00:00,15205.0 -2016-09-07 02:00:00,14212.0 -2016-09-07 03:00:00,13418.0 -2016-09-07 04:00:00,12871.0 -2016-09-07 05:00:00,12580.0 -2016-09-07 06:00:00,12597.0 -2016-09-07 07:00:00,13293.0 -2016-09-07 08:00:00,14314.0 -2016-09-07 09:00:00,15212.0 -2016-09-07 10:00:00,16061.0 -2016-09-07 11:00:00,16910.0 -2016-09-07 12:00:00,17731.0 -2016-09-07 13:00:00,18517.0 -2016-09-07 14:00:00,18693.0 -2016-09-07 15:00:00,18968.0 -2016-09-07 16:00:00,19653.0 -2016-09-07 17:00:00,20153.0 -2016-09-07 18:00:00,20014.0 -2016-09-07 19:00:00,19336.0 -2016-09-07 20:00:00,18812.0 -2016-09-07 21:00:00,18493.0 -2016-09-07 22:00:00,17688.0 -2016-09-07 23:00:00,16568.0 -2016-09-08 00:00:00,15227.0 -2016-09-06 01:00:00,12826.0 -2016-09-06 02:00:00,12023.0 -2016-09-06 03:00:00,11518.0 -2016-09-06 04:00:00,11118.0 -2016-09-06 05:00:00,10945.0 -2016-09-06 06:00:00,11133.0 -2016-09-06 07:00:00,11893.0 -2016-09-06 08:00:00,13070.0 -2016-09-06 09:00:00,14196.0 -2016-09-06 10:00:00,15374.0 -2016-09-06 11:00:00,16582.0 -2016-09-06 12:00:00,17838.0 -2016-09-06 13:00:00,18906.0 -2016-09-06 14:00:00,19808.0 -2016-09-06 15:00:00,20319.0 -2016-09-06 16:00:00,20612.0 -2016-09-06 17:00:00,20927.0 -2016-09-06 18:00:00,20949.0 -2016-09-06 19:00:00,20740.0 -2016-09-06 20:00:00,20061.0 -2016-09-06 21:00:00,19686.0 -2016-09-06 22:00:00,19193.0 -2016-09-06 23:00:00,18102.0 -2016-09-07 00:00:00,16651.0 -2016-09-05 01:00:00,9957.0 -2016-09-05 02:00:00,9353.0 -2016-09-05 03:00:00,8920.0 -2016-09-05 04:00:00,8617.0 -2016-09-05 05:00:00,8496.0 -2016-09-05 06:00:00,8432.0 -2016-09-05 07:00:00,8592.0 -2016-09-05 08:00:00,8619.0 -2016-09-05 09:00:00,8927.0 -2016-09-05 10:00:00,9647.0 -2016-09-05 11:00:00,10693.0 -2016-09-05 12:00:00,11930.0 -2016-09-05 13:00:00,13134.0 -2016-09-05 14:00:00,14166.0 -2016-09-05 15:00:00,14921.0 -2016-09-05 16:00:00,15535.0 -2016-09-05 17:00:00,15993.0 -2016-09-05 18:00:00,16295.0 -2016-09-05 19:00:00,16350.0 -2016-09-05 20:00:00,15958.0 -2016-09-05 21:00:00,15800.0 -2016-09-05 22:00:00,15643.0 -2016-09-05 23:00:00,14939.0 -2016-09-06 00:00:00,13829.0 -2016-09-04 01:00:00,9723.0 -2016-09-04 02:00:00,9151.0 -2016-09-04 03:00:00,8723.0 -2016-09-04 04:00:00,8459.0 -2016-09-04 05:00:00,8224.0 -2016-09-04 06:00:00,8160.0 -2016-09-04 07:00:00,8128.0 -2016-09-04 08:00:00,8074.0 -2016-09-04 09:00:00,8290.0 -2016-09-04 10:00:00,8935.0 -2016-09-04 11:00:00,9611.0 -2016-09-04 12:00:00,10388.0 -2016-09-04 13:00:00,10936.0 -2016-09-04 14:00:00,11172.0 -2016-09-04 15:00:00,11597.0 -2016-09-04 16:00:00,11959.0 -2016-09-04 17:00:00,12126.0 -2016-09-04 18:00:00,12017.0 -2016-09-04 19:00:00,11823.0 -2016-09-04 20:00:00,11493.0 -2016-09-04 21:00:00,11525.0 -2016-09-04 22:00:00,11536.0 -2016-09-04 23:00:00,11187.0 -2016-09-05 00:00:00,10597.0 -2016-09-03 01:00:00,10109.0 -2016-09-03 02:00:00,9454.0 -2016-09-03 03:00:00,9038.0 -2016-09-03 04:00:00,8718.0 -2016-09-03 05:00:00,8525.0 -2016-09-03 06:00:00,8470.0 -2016-09-03 07:00:00,8588.0 -2016-09-03 08:00:00,8646.0 -2016-09-03 09:00:00,8994.0 -2016-09-03 10:00:00,9665.0 -2016-09-03 11:00:00,10276.0 -2016-09-03 12:00:00,10734.0 -2016-09-03 13:00:00,11134.0 -2016-09-03 14:00:00,11401.0 -2016-09-03 15:00:00,11613.0 -2016-09-03 16:00:00,11834.0 -2016-09-03 17:00:00,12029.0 -2016-09-03 18:00:00,12109.0 -2016-09-03 19:00:00,11946.0 -2016-09-03 20:00:00,11466.0 -2016-09-03 21:00:00,11324.0 -2016-09-03 22:00:00,11312.0 -2016-09-03 23:00:00,10930.0 -2016-09-04 00:00:00,10349.0 -2016-09-02 01:00:00,10157.0 -2016-09-02 02:00:00,9541.0 -2016-09-02 03:00:00,9125.0 -2016-09-02 04:00:00,8883.0 -2016-09-02 05:00:00,8772.0 -2016-09-02 06:00:00,8887.0 -2016-09-02 07:00:00,9498.0 -2016-09-02 08:00:00,10254.0 -2016-09-02 09:00:00,11006.0 -2016-09-02 10:00:00,11674.0 -2016-09-02 11:00:00,12172.0 -2016-09-02 12:00:00,12544.0 -2016-09-02 13:00:00,12800.0 -2016-09-02 14:00:00,12977.0 -2016-09-02 15:00:00,13146.0 -2016-09-02 16:00:00,13281.0 -2016-09-02 17:00:00,13311.0 -2016-09-02 18:00:00,13292.0 -2016-09-02 19:00:00,13009.0 -2016-09-02 20:00:00,12411.0 -2016-09-02 21:00:00,12187.0 -2016-09-02 22:00:00,12074.0 -2016-09-02 23:00:00,11645.0 -2016-09-03 00:00:00,10883.0 -2016-09-01 01:00:00,10633.0 -2016-09-01 02:00:00,9937.0 -2016-09-01 03:00:00,9471.0 -2016-09-01 04:00:00,9158.0 -2016-09-01 05:00:00,9160.0 -2016-09-01 06:00:00,9295.0 -2016-09-01 07:00:00,9902.0 -2016-09-01 08:00:00,10831.0 -2016-09-01 09:00:00,11501.0 -2016-09-01 10:00:00,12071.0 -2016-09-01 11:00:00,12480.0 -2016-09-01 12:00:00,12877.0 -2016-09-01 13:00:00,13044.0 -2016-09-01 14:00:00,13096.0 -2016-09-01 15:00:00,13188.0 -2016-09-01 16:00:00,13256.0 -2016-09-01 17:00:00,13161.0 -2016-09-01 18:00:00,13087.0 -2016-09-01 19:00:00,12865.0 -2016-09-01 20:00:00,12361.0 -2016-09-01 21:00:00,12298.0 -2016-09-01 22:00:00,12340.0 -2016-09-01 23:00:00,11827.0 -2016-09-02 00:00:00,10977.0 -2016-08-31 01:00:00,12473.0 -2016-08-31 02:00:00,11616.0 -2016-08-31 03:00:00,10971.0 -2016-08-31 04:00:00,10593.0 -2016-08-31 05:00:00,10362.0 -2016-08-31 06:00:00,10475.0 -2016-08-31 07:00:00,11189.0 -2016-08-31 08:00:00,12109.0 -2016-08-31 09:00:00,12756.0 -2016-08-31 10:00:00,13373.0 -2016-08-31 11:00:00,14074.0 -2016-08-31 12:00:00,14761.0 -2016-08-31 13:00:00,15198.0 -2016-08-31 14:00:00,15416.0 -2016-08-31 15:00:00,15650.0 -2016-08-31 16:00:00,15769.0 -2016-08-31 17:00:00,15720.0 -2016-08-31 18:00:00,15481.0 -2016-08-31 19:00:00,14858.0 -2016-08-31 20:00:00,13944.0 -2016-08-31 21:00:00,13500.0 -2016-08-31 22:00:00,13311.0 -2016-08-31 23:00:00,12583.0 -2016-09-01 00:00:00,11593.0 -2016-08-30 01:00:00,12647.0 -2016-08-30 02:00:00,11815.0 -2016-08-30 03:00:00,11208.0 -2016-08-30 04:00:00,10805.0 -2016-08-30 05:00:00,10620.0 -2016-08-30 06:00:00,10795.0 -2016-08-30 07:00:00,11584.0 -2016-08-30 08:00:00,12812.0 -2016-08-30 09:00:00,13540.0 -2016-08-30 10:00:00,13879.0 -2016-08-30 11:00:00,14397.0 -2016-08-30 12:00:00,15188.0 -2016-08-30 13:00:00,15727.0 -2016-08-30 14:00:00,16486.0 -2016-08-30 15:00:00,17380.0 -2016-08-30 16:00:00,17923.0 -2016-08-30 17:00:00,18180.0 -2016-08-30 18:00:00,17992.0 -2016-08-30 19:00:00,17216.0 -2016-08-30 20:00:00,16438.0 -2016-08-30 21:00:00,16054.0 -2016-08-30 22:00:00,15833.0 -2016-08-30 23:00:00,14924.0 -2016-08-31 00:00:00,13660.0 -2016-08-29 01:00:00,11732.0 -2016-08-29 02:00:00,11015.0 -2016-08-29 03:00:00,10471.0 -2016-08-29 04:00:00,10151.0 -2016-08-29 05:00:00,9990.0 -2016-08-29 06:00:00,10233.0 -2016-08-29 07:00:00,11000.0 -2016-08-29 08:00:00,12012.0 -2016-08-29 09:00:00,13159.0 -2016-08-29 10:00:00,14267.0 -2016-08-29 11:00:00,15253.0 -2016-08-29 12:00:00,16328.0 -2016-08-29 13:00:00,17206.0 -2016-08-29 14:00:00,17945.0 -2016-08-29 15:00:00,18517.0 -2016-08-29 16:00:00,18466.0 -2016-08-29 17:00:00,17880.0 -2016-08-29 18:00:00,17259.0 -2016-08-29 19:00:00,16687.0 -2016-08-29 20:00:00,16271.0 -2016-08-29 21:00:00,16122.0 -2016-08-29 22:00:00,15814.0 -2016-08-29 23:00:00,15004.0 -2016-08-30 00:00:00,13786.0 -2016-08-28 01:00:00,12261.0 -2016-08-28 02:00:00,11464.0 -2016-08-28 03:00:00,10833.0 -2016-08-28 04:00:00,10368.0 -2016-08-28 05:00:00,10046.0 -2016-08-28 06:00:00,9876.0 -2016-08-28 07:00:00,9821.0 -2016-08-28 08:00:00,9798.0 -2016-08-28 09:00:00,10222.0 -2016-08-28 10:00:00,11320.0 -2016-08-28 11:00:00,12522.0 -2016-08-28 12:00:00,13669.0 -2016-08-28 13:00:00,14595.0 -2016-08-28 14:00:00,15231.0 -2016-08-28 15:00:00,15599.0 -2016-08-28 16:00:00,15464.0 -2016-08-28 17:00:00,15296.0 -2016-08-28 18:00:00,15161.0 -2016-08-28 19:00:00,14725.0 -2016-08-28 20:00:00,14290.0 -2016-08-28 21:00:00,14183.0 -2016-08-28 22:00:00,14153.0 -2016-08-28 23:00:00,13513.0 -2016-08-29 00:00:00,12674.0 -2016-08-27 01:00:00,11614.0 -2016-08-27 02:00:00,10904.0 -2016-08-27 03:00:00,10303.0 -2016-08-27 04:00:00,9915.0 -2016-08-27 05:00:00,9707.0 -2016-08-27 06:00:00,9702.0 -2016-08-27 07:00:00,9982.0 -2016-08-27 08:00:00,10421.0 -2016-08-27 09:00:00,10806.0 -2016-08-27 10:00:00,11371.0 -2016-08-27 11:00:00,11919.0 -2016-08-27 12:00:00,12633.0 -2016-08-27 13:00:00,13112.0 -2016-08-27 14:00:00,13692.0 -2016-08-27 15:00:00,14301.0 -2016-08-27 16:00:00,14900.0 -2016-08-27 17:00:00,15315.0 -2016-08-27 18:00:00,15468.0 -2016-08-27 19:00:00,15291.0 -2016-08-27 20:00:00,14742.0 -2016-08-27 21:00:00,14551.0 -2016-08-27 22:00:00,14504.0 -2016-08-27 23:00:00,13976.0 -2016-08-28 00:00:00,13158.0 -2016-08-26 01:00:00,11815.0 -2016-08-26 02:00:00,10937.0 -2016-08-26 03:00:00,10342.0 -2016-08-26 04:00:00,9923.0 -2016-08-26 05:00:00,9696.0 -2016-08-26 06:00:00,9807.0 -2016-08-26 07:00:00,10386.0 -2016-08-26 08:00:00,11218.0 -2016-08-26 09:00:00,12035.0 -2016-08-26 10:00:00,12817.0 -2016-08-26 11:00:00,13423.0 -2016-08-26 12:00:00,14191.0 -2016-08-26 13:00:00,14801.0 -2016-08-26 14:00:00,15142.0 -2016-08-26 15:00:00,15709.0 -2016-08-26 16:00:00,16072.0 -2016-08-26 17:00:00,16038.0 -2016-08-26 18:00:00,15948.0 -2016-08-26 19:00:00,15429.0 -2016-08-26 20:00:00,14667.0 -2016-08-26 21:00:00,14177.0 -2016-08-26 22:00:00,13974.0 -2016-08-26 23:00:00,13451.0 -2016-08-27 00:00:00,12618.0 -2016-08-25 01:00:00,13200.0 -2016-08-25 02:00:00,12278.0 -2016-08-25 03:00:00,11598.0 -2016-08-25 04:00:00,11131.0 -2016-08-25 05:00:00,10903.0 -2016-08-25 06:00:00,10946.0 -2016-08-25 07:00:00,11584.0 -2016-08-25 08:00:00,12563.0 -2016-08-25 09:00:00,13364.0 -2016-08-25 10:00:00,13915.0 -2016-08-25 11:00:00,14375.0 -2016-08-25 12:00:00,14930.0 -2016-08-25 13:00:00,15458.0 -2016-08-25 14:00:00,16133.0 -2016-08-25 15:00:00,16825.0 -2016-08-25 16:00:00,17308.0 -2016-08-25 17:00:00,17415.0 -2016-08-25 18:00:00,17036.0 -2016-08-25 19:00:00,16367.0 -2016-08-25 20:00:00,15740.0 -2016-08-25 21:00:00,15352.0 -2016-08-25 22:00:00,15171.0 -2016-08-25 23:00:00,14272.0 -2016-08-26 00:00:00,13059.0 -2016-08-24 01:00:00,12043.0 -2016-08-24 02:00:00,11286.0 -2016-08-24 03:00:00,10721.0 -2016-08-24 04:00:00,10386.0 -2016-08-24 05:00:00,10216.0 -2016-08-24 06:00:00,10344.0 -2016-08-24 07:00:00,11058.0 -2016-08-24 08:00:00,12034.0 -2016-08-24 09:00:00,13025.0 -2016-08-24 10:00:00,13624.0 -2016-08-24 11:00:00,13850.0 -2016-08-24 12:00:00,14262.0 -2016-08-24 13:00:00,14554.0 -2016-08-24 14:00:00,15161.0 -2016-08-24 15:00:00,15625.0 -2016-08-24 16:00:00,16209.0 -2016-08-24 17:00:00,16888.0 -2016-08-24 18:00:00,17458.0 -2016-08-24 19:00:00,17623.0 -2016-08-24 20:00:00,17194.0 -2016-08-24 21:00:00,16729.0 -2016-08-24 22:00:00,16585.0 -2016-08-24 23:00:00,15752.0 -2016-08-25 00:00:00,14444.0 -2016-08-23 01:00:00,10782.0 -2016-08-23 02:00:00,10088.0 -2016-08-23 03:00:00,9565.0 -2016-08-23 04:00:00,9218.0 -2016-08-23 05:00:00,9099.0 -2016-08-23 06:00:00,9242.0 -2016-08-23 07:00:00,9867.0 -2016-08-23 08:00:00,10618.0 -2016-08-23 09:00:00,11518.0 -2016-08-23 10:00:00,12328.0 -2016-08-23 11:00:00,13051.0 -2016-08-23 12:00:00,13812.0 -2016-08-23 13:00:00,14330.0 -2016-08-23 14:00:00,14784.0 -2016-08-23 15:00:00,15216.0 -2016-08-23 16:00:00,15595.0 -2016-08-23 17:00:00,15925.0 -2016-08-23 18:00:00,16005.0 -2016-08-23 19:00:00,15632.0 -2016-08-23 20:00:00,14992.0 -2016-08-23 21:00:00,14700.0 -2016-08-23 22:00:00,14694.0 -2016-08-23 23:00:00,14038.0 -2016-08-24 00:00:00,13071.0 -2016-08-22 01:00:00,9535.0 -2016-08-22 02:00:00,8997.0 -2016-08-22 03:00:00,8576.0 -2016-08-22 04:00:00,8421.0 -2016-08-22 05:00:00,8392.0 -2016-08-22 06:00:00,8586.0 -2016-08-22 07:00:00,9203.0 -2016-08-22 08:00:00,10004.0 -2016-08-22 09:00:00,10877.0 -2016-08-22 10:00:00,11636.0 -2016-08-22 11:00:00,12211.0 -2016-08-22 12:00:00,12710.0 -2016-08-22 13:00:00,13095.0 -2016-08-22 14:00:00,13401.0 -2016-08-22 15:00:00,13814.0 -2016-08-22 16:00:00,14145.0 -2016-08-22 17:00:00,14455.0 -2016-08-22 18:00:00,14625.0 -2016-08-22 19:00:00,14543.0 -2016-08-22 20:00:00,14062.0 -2016-08-22 21:00:00,13574.0 -2016-08-22 22:00:00,13510.0 -2016-08-22 23:00:00,12802.0 -2016-08-23 00:00:00,11768.0 -2016-08-21 01:00:00,10244.0 -2016-08-21 02:00:00,9561.0 -2016-08-21 03:00:00,9072.0 -2016-08-21 04:00:00,8708.0 -2016-08-21 05:00:00,8468.0 -2016-08-21 06:00:00,8377.0 -2016-08-21 07:00:00,8366.0 -2016-08-21 08:00:00,8300.0 -2016-08-21 09:00:00,8513.0 -2016-08-21 10:00:00,8889.0 -2016-08-21 11:00:00,9356.0 -2016-08-21 12:00:00,9815.0 -2016-08-21 13:00:00,10296.0 -2016-08-21 14:00:00,10628.0 -2016-08-21 15:00:00,10889.0 -2016-08-21 16:00:00,11152.0 -2016-08-21 17:00:00,11309.0 -2016-08-21 18:00:00,11426.0 -2016-08-21 19:00:00,11404.0 -2016-08-21 20:00:00,11298.0 -2016-08-21 21:00:00,11082.0 -2016-08-21 22:00:00,11282.0 -2016-08-21 23:00:00,10889.0 -2016-08-22 00:00:00,10178.0 -2016-08-20 01:00:00,13039.0 -2016-08-20 02:00:00,12096.0 -2016-08-20 03:00:00,11410.0 -2016-08-20 04:00:00,10937.0 -2016-08-20 05:00:00,10726.0 -2016-08-20 06:00:00,10662.0 -2016-08-20 07:00:00,10895.0 -2016-08-20 08:00:00,11144.0 -2016-08-20 09:00:00,11620.0 -2016-08-20 10:00:00,12260.0 -2016-08-20 11:00:00,12791.0 -2016-08-20 12:00:00,13237.0 -2016-08-20 13:00:00,13627.0 -2016-08-20 14:00:00,13981.0 -2016-08-20 15:00:00,14357.0 -2016-08-20 16:00:00,14471.0 -2016-08-20 17:00:00,14466.0 -2016-08-20 18:00:00,14126.0 -2016-08-20 19:00:00,13613.0 -2016-08-20 20:00:00,12968.0 -2016-08-20 21:00:00,12322.0 -2016-08-20 22:00:00,12256.0 -2016-08-20 23:00:00,11772.0 -2016-08-21 00:00:00,11079.0 -2016-08-19 01:00:00,13405.0 -2016-08-19 02:00:00,12369.0 -2016-08-19 03:00:00,11588.0 -2016-08-19 04:00:00,11060.0 -2016-08-19 05:00:00,10790.0 -2016-08-19 06:00:00,10825.0 -2016-08-19 07:00:00,11363.0 -2016-08-19 08:00:00,12176.0 -2016-08-19 09:00:00,12990.0 -2016-08-19 10:00:00,13782.0 -2016-08-19 11:00:00,14464.0 -2016-08-19 12:00:00,14588.0 -2016-08-19 13:00:00,14763.0 -2016-08-19 14:00:00,15393.0 -2016-08-19 15:00:00,16188.0 -2016-08-19 16:00:00,16946.0 -2016-08-19 17:00:00,17623.0 -2016-08-19 18:00:00,17925.0 -2016-08-19 19:00:00,17806.0 -2016-08-19 20:00:00,17106.0 -2016-08-19 21:00:00,16378.0 -2016-08-19 22:00:00,16110.0 -2016-08-19 23:00:00,15349.0 -2016-08-20 00:00:00,14180.0 -2016-08-18 01:00:00,13322.0 -2016-08-18 02:00:00,12308.0 -2016-08-18 03:00:00,11609.0 -2016-08-18 04:00:00,11143.0 -2016-08-18 05:00:00,10909.0 -2016-08-18 06:00:00,11040.0 -2016-08-18 07:00:00,11777.0 -2016-08-18 08:00:00,12771.0 -2016-08-18 09:00:00,13477.0 -2016-08-18 10:00:00,14058.0 -2016-08-18 11:00:00,15013.0 -2016-08-18 12:00:00,16106.0 -2016-08-18 13:00:00,17054.0 -2016-08-18 14:00:00,17943.0 -2016-08-18 15:00:00,18714.0 -2016-08-18 16:00:00,18902.0 -2016-08-18 17:00:00,19033.0 -2016-08-18 18:00:00,19170.0 -2016-08-18 19:00:00,18884.0 -2016-08-18 20:00:00,17984.0 -2016-08-18 21:00:00,17297.0 -2016-08-18 22:00:00,17043.0 -2016-08-18 23:00:00,16095.0 -2016-08-19 00:00:00,14720.0 -2016-08-17 01:00:00,13174.0 -2016-08-17 02:00:00,12156.0 -2016-08-17 03:00:00,11378.0 -2016-08-17 04:00:00,10880.0 -2016-08-17 05:00:00,10585.0 -2016-08-17 06:00:00,10710.0 -2016-08-17 07:00:00,11335.0 -2016-08-17 08:00:00,12194.0 -2016-08-17 09:00:00,13247.0 -2016-08-17 10:00:00,14227.0 -2016-08-17 11:00:00,15110.0 -2016-08-17 12:00:00,16170.0 -2016-08-17 13:00:00,17110.0 -2016-08-17 14:00:00,17883.0 -2016-08-17 15:00:00,18521.0 -2016-08-17 16:00:00,18871.0 -2016-08-17 17:00:00,19019.0 -2016-08-17 18:00:00,18899.0 -2016-08-17 19:00:00,18439.0 -2016-08-17 20:00:00,17746.0 -2016-08-17 21:00:00,16986.0 -2016-08-17 22:00:00,16853.0 -2016-08-17 23:00:00,15851.0 -2016-08-18 00:00:00,14679.0 -2016-08-16 01:00:00,12209.0 -2016-08-16 02:00:00,11476.0 -2016-08-16 03:00:00,10988.0 -2016-08-16 04:00:00,10656.0 -2016-08-16 05:00:00,10476.0 -2016-08-16 06:00:00,10652.0 -2016-08-16 07:00:00,11351.0 -2016-08-16 08:00:00,12165.0 -2016-08-16 09:00:00,13119.0 -2016-08-16 10:00:00,14014.0 -2016-08-16 11:00:00,14888.0 -2016-08-16 12:00:00,15919.0 -2016-08-16 13:00:00,16591.0 -2016-08-16 14:00:00,17215.0 -2016-08-16 15:00:00,17770.0 -2016-08-16 16:00:00,17919.0 -2016-08-16 17:00:00,18132.0 -2016-08-16 18:00:00,18413.0 -2016-08-16 19:00:00,18370.0 -2016-08-16 20:00:00,17696.0 -2016-08-16 21:00:00,16928.0 -2016-08-16 22:00:00,16613.0 -2016-08-16 23:00:00,15757.0 -2016-08-17 00:00:00,14478.0 -2016-08-15 01:00:00,12255.0 -2016-08-15 02:00:00,11431.0 -2016-08-15 03:00:00,10838.0 -2016-08-15 04:00:00,10474.0 -2016-08-15 05:00:00,10250.0 -2016-08-15 06:00:00,10452.0 -2016-08-15 07:00:00,11079.0 -2016-08-15 08:00:00,11869.0 -2016-08-15 09:00:00,12733.0 -2016-08-15 10:00:00,13542.0 -2016-08-15 11:00:00,14152.0 -2016-08-15 12:00:00,14761.0 -2016-08-15 13:00:00,15200.0 -2016-08-15 14:00:00,15483.0 -2016-08-15 15:00:00,15684.0 -2016-08-15 16:00:00,15734.0 -2016-08-15 17:00:00,15585.0 -2016-08-15 18:00:00,15405.0 -2016-08-15 19:00:00,15216.0 -2016-08-15 20:00:00,14838.0 -2016-08-15 21:00:00,14712.0 -2016-08-15 22:00:00,14726.0 -2016-08-15 23:00:00,14143.0 -2016-08-16 00:00:00,13231.0 -2016-08-14 01:00:00,12299.0 -2016-08-14 02:00:00,11422.0 -2016-08-14 03:00:00,10694.0 -2016-08-14 04:00:00,10176.0 -2016-08-14 05:00:00,9812.0 -2016-08-14 06:00:00,9642.0 -2016-08-14 07:00:00,9544.0 -2016-08-14 08:00:00,9484.0 -2016-08-14 09:00:00,10126.0 -2016-08-14 10:00:00,11206.0 -2016-08-14 11:00:00,12357.0 -2016-08-14 12:00:00,13366.0 -2016-08-14 13:00:00,14125.0 -2016-08-14 14:00:00,14725.0 -2016-08-14 15:00:00,15129.0 -2016-08-14 16:00:00,15542.0 -2016-08-14 17:00:00,15691.0 -2016-08-14 18:00:00,15852.0 -2016-08-14 19:00:00,15819.0 -2016-08-14 20:00:00,15514.0 -2016-08-14 21:00:00,14922.0 -2016-08-14 22:00:00,14829.0 -2016-08-14 23:00:00,14244.0 -2016-08-15 00:00:00,13331.0 -2016-08-13 01:00:00,13193.0 -2016-08-13 02:00:00,12319.0 -2016-08-13 03:00:00,11745.0 -2016-08-13 04:00:00,11430.0 -2016-08-13 05:00:00,11209.0 -2016-08-13 06:00:00,11197.0 -2016-08-13 07:00:00,11390.0 -2016-08-13 08:00:00,11652.0 -2016-08-13 09:00:00,12214.0 -2016-08-13 10:00:00,13111.0 -2016-08-13 11:00:00,14119.0 -2016-08-13 12:00:00,15035.0 -2016-08-13 13:00:00,15656.0 -2016-08-13 14:00:00,15947.0 -2016-08-13 15:00:00,15997.0 -2016-08-13 16:00:00,15941.0 -2016-08-13 17:00:00,15935.0 -2016-08-13 18:00:00,16162.0 -2016-08-13 19:00:00,16141.0 -2016-08-13 20:00:00,15702.0 -2016-08-13 21:00:00,15009.0 -2016-08-13 22:00:00,14826.0 -2016-08-13 23:00:00,14260.0 -2016-08-14 00:00:00,13340.0 -2016-08-12 01:00:00,15641.0 -2016-08-12 02:00:00,14665.0 -2016-08-12 03:00:00,13991.0 -2016-08-12 04:00:00,13521.0 -2016-08-12 05:00:00,13141.0 -2016-08-12 06:00:00,13060.0 -2016-08-12 07:00:00,13547.0 -2016-08-12 08:00:00,14194.0 -2016-08-12 09:00:00,14998.0 -2016-08-12 10:00:00,15704.0 -2016-08-12 11:00:00,16158.0 -2016-08-12 12:00:00,16509.0 -2016-08-12 13:00:00,16384.0 -2016-08-12 14:00:00,16343.0 -2016-08-12 15:00:00,16479.0 -2016-08-12 16:00:00,16443.0 -2016-08-12 17:00:00,16337.0 -2016-08-12 18:00:00,16399.0 -2016-08-12 19:00:00,16271.0 -2016-08-12 20:00:00,16097.0 -2016-08-12 21:00:00,15907.0 -2016-08-12 22:00:00,15635.0 -2016-08-12 23:00:00,15074.0 -2016-08-13 00:00:00,14151.0 -2016-08-11 01:00:00,14577.0 -2016-08-11 02:00:00,13478.0 -2016-08-11 03:00:00,12700.0 -2016-08-11 04:00:00,12146.0 -2016-08-11 05:00:00,11843.0 -2016-08-11 06:00:00,11867.0 -2016-08-11 07:00:00,12468.0 -2016-08-11 08:00:00,13256.0 -2016-08-11 09:00:00,14575.0 -2016-08-11 10:00:00,16078.0 -2016-08-11 11:00:00,17444.0 -2016-08-11 12:00:00,18812.0 -2016-08-11 13:00:00,19792.0 -2016-08-11 14:00:00,20430.0 -2016-08-11 15:00:00,20885.0 -2016-08-11 16:00:00,21175.0 -2016-08-11 17:00:00,21086.0 -2016-08-11 18:00:00,20292.0 -2016-08-11 19:00:00,19336.0 -2016-08-11 20:00:00,18909.0 -2016-08-11 21:00:00,18537.0 -2016-08-11 22:00:00,18484.0 -2016-08-11 23:00:00,17935.0 -2016-08-12 00:00:00,16817.0 -2016-08-10 01:00:00,13802.0 -2016-08-10 02:00:00,12712.0 -2016-08-10 03:00:00,11926.0 -2016-08-10 04:00:00,11368.0 -2016-08-10 05:00:00,11053.0 -2016-08-10 06:00:00,11122.0 -2016-08-10 07:00:00,11685.0 -2016-08-10 08:00:00,12461.0 -2016-08-10 09:00:00,13788.0 -2016-08-10 10:00:00,15113.0 -2016-08-10 11:00:00,16370.0 -2016-08-10 12:00:00,17515.0 -2016-08-10 13:00:00,18327.0 -2016-08-10 14:00:00,19051.0 -2016-08-10 15:00:00,19520.0 -2016-08-10 16:00:00,19829.0 -2016-08-10 17:00:00,20017.0 -2016-08-10 18:00:00,20082.0 -2016-08-10 19:00:00,19867.0 -2016-08-10 20:00:00,19266.0 -2016-08-10 21:00:00,18547.0 -2016-08-10 22:00:00,18226.0 -2016-08-10 23:00:00,17332.0 -2016-08-11 00:00:00,16017.0 -2016-08-09 01:00:00,12924.0 -2016-08-09 02:00:00,11969.0 -2016-08-09 03:00:00,11213.0 -2016-08-09 04:00:00,10680.0 -2016-08-09 05:00:00,10395.0 -2016-08-09 06:00:00,10470.0 -2016-08-09 07:00:00,11017.0 -2016-08-09 08:00:00,11717.0 -2016-08-09 09:00:00,12793.0 -2016-08-09 10:00:00,13839.0 -2016-08-09 11:00:00,14793.0 -2016-08-09 12:00:00,15864.0 -2016-08-09 13:00:00,16801.0 -2016-08-09 14:00:00,17603.0 -2016-08-09 15:00:00,18282.0 -2016-08-09 16:00:00,18635.0 -2016-08-09 17:00:00,18729.0 -2016-08-09 18:00:00,18880.0 -2016-08-09 19:00:00,18799.0 -2016-08-09 20:00:00,18114.0 -2016-08-09 21:00:00,17439.0 -2016-08-09 22:00:00,17157.0 -2016-08-09 23:00:00,16417.0 -2016-08-10 00:00:00,15147.0 -2016-08-08 01:00:00,11547.0 -2016-08-08 02:00:00,10838.0 -2016-08-08 03:00:00,10343.0 -2016-08-08 04:00:00,9999.0 -2016-08-08 05:00:00,9883.0 -2016-08-08 06:00:00,10112.0 -2016-08-08 07:00:00,10775.0 -2016-08-08 08:00:00,11604.0 -2016-08-08 09:00:00,12688.0 -2016-08-08 10:00:00,13865.0 -2016-08-08 11:00:00,14742.0 -2016-08-08 12:00:00,15663.0 -2016-08-08 13:00:00,16417.0 -2016-08-08 14:00:00,17005.0 -2016-08-08 15:00:00,17523.0 -2016-08-08 16:00:00,17857.0 -2016-08-08 17:00:00,18041.0 -2016-08-08 18:00:00,18070.0 -2016-08-08 19:00:00,17877.0 -2016-08-08 20:00:00,17228.0 -2016-08-08 21:00:00,16351.0 -2016-08-08 22:00:00,15995.0 -2016-08-08 23:00:00,15337.0 -2016-08-09 00:00:00,14144.0 -2016-08-07 01:00:00,11264.0 -2016-08-07 02:00:00,10493.0 -2016-08-07 03:00:00,9888.0 -2016-08-07 04:00:00,9466.0 -2016-08-07 05:00:00,9113.0 -2016-08-07 06:00:00,8972.0 -2016-08-07 07:00:00,8857.0 -2016-08-07 08:00:00,8828.0 -2016-08-07 09:00:00,9382.0 -2016-08-07 10:00:00,10264.0 -2016-08-07 11:00:00,11192.0 -2016-08-07 12:00:00,12158.0 -2016-08-07 13:00:00,12934.0 -2016-08-07 14:00:00,13528.0 -2016-08-07 15:00:00,13945.0 -2016-08-07 16:00:00,14193.0 -2016-08-07 17:00:00,14260.0 -2016-08-07 18:00:00,14248.0 -2016-08-07 19:00:00,13944.0 -2016-08-07 20:00:00,13555.0 -2016-08-07 21:00:00,13175.0 -2016-08-07 22:00:00,13279.0 -2016-08-07 23:00:00,13020.0 -2016-08-08 00:00:00,12311.0 -2016-08-06 01:00:00,12864.0 -2016-08-06 02:00:00,11763.0 -2016-08-06 03:00:00,11033.0 -2016-08-06 04:00:00,10442.0 -2016-08-06 05:00:00,10006.0 -2016-08-06 06:00:00,9860.0 -2016-08-06 07:00:00,9937.0 -2016-08-06 08:00:00,10019.0 -2016-08-06 09:00:00,10794.0 -2016-08-06 10:00:00,11839.0 -2016-08-06 11:00:00,12893.0 -2016-08-06 12:00:00,13727.0 -2016-08-06 13:00:00,14295.0 -2016-08-06 14:00:00,14600.0 -2016-08-06 15:00:00,14832.0 -2016-08-06 16:00:00,15026.0 -2016-08-06 17:00:00,15177.0 -2016-08-06 18:00:00,15213.0 -2016-08-06 19:00:00,15050.0 -2016-08-06 20:00:00,14550.0 -2016-08-06 21:00:00,13827.0 -2016-08-06 22:00:00,13538.0 -2016-08-06 23:00:00,13031.0 -2016-08-07 00:00:00,12209.0 -2016-08-05 01:00:00,15050.0 -2016-08-05 02:00:00,13731.0 -2016-08-05 03:00:00,12861.0 -2016-08-05 04:00:00,12277.0 -2016-08-05 05:00:00,11980.0 -2016-08-05 06:00:00,11976.0 -2016-08-05 07:00:00,12473.0 -2016-08-05 08:00:00,13110.0 -2016-08-05 09:00:00,13867.0 -2016-08-05 10:00:00,14504.0 -2016-08-05 11:00:00,14984.0 -2016-08-05 12:00:00,15557.0 -2016-08-05 13:00:00,15911.0 -2016-08-05 14:00:00,16395.0 -2016-08-05 15:00:00,17230.0 -2016-08-05 16:00:00,18056.0 -2016-08-05 17:00:00,18119.0 -2016-08-05 18:00:00,18169.0 -2016-08-05 19:00:00,17944.0 -2016-08-05 20:00:00,17295.0 -2016-08-05 21:00:00,16432.0 -2016-08-05 22:00:00,15881.0 -2016-08-05 23:00:00,15241.0 -2016-08-06 00:00:00,14040.0 -2016-08-04 01:00:00,14181.0 -2016-08-04 02:00:00,13090.0 -2016-08-04 03:00:00,12254.0 -2016-08-04 04:00:00,11599.0 -2016-08-04 05:00:00,11274.0 -2016-08-04 06:00:00,11285.0 -2016-08-04 07:00:00,11750.0 -2016-08-04 08:00:00,12515.0 -2016-08-04 09:00:00,13875.0 -2016-08-04 10:00:00,15264.0 -2016-08-04 11:00:00,16533.0 -2016-08-04 12:00:00,17686.0 -2016-08-04 13:00:00,18551.0 -2016-08-04 14:00:00,19257.0 -2016-08-04 15:00:00,19858.0 -2016-08-04 16:00:00,20175.0 -2016-08-04 17:00:00,20421.0 -2016-08-04 18:00:00,20501.0 -2016-08-04 19:00:00,20323.0 -2016-08-04 20:00:00,19797.0 -2016-08-04 21:00:00,18959.0 -2016-08-04 22:00:00,18636.0 -2016-08-04 23:00:00,17791.0 -2016-08-05 00:00:00,16507.0 -2016-08-03 01:00:00,13486.0 -2016-08-03 02:00:00,12449.0 -2016-08-03 03:00:00,11719.0 -2016-08-03 04:00:00,11193.0 -2016-08-03 05:00:00,10891.0 -2016-08-03 06:00:00,10928.0 -2016-08-03 07:00:00,11442.0 -2016-08-03 08:00:00,12255.0 -2016-08-03 09:00:00,13566.0 -2016-08-03 10:00:00,14929.0 -2016-08-03 11:00:00,16199.0 -2016-08-03 12:00:00,17388.0 -2016-08-03 13:00:00,18263.0 -2016-08-03 14:00:00,18998.0 -2016-08-03 15:00:00,19505.0 -2016-08-03 16:00:00,19840.0 -2016-08-03 17:00:00,20009.0 -2016-08-03 18:00:00,19983.0 -2016-08-03 19:00:00,19648.0 -2016-08-03 20:00:00,18932.0 -2016-08-03 21:00:00,18176.0 -2016-08-03 22:00:00,17740.0 -2016-08-03 23:00:00,16953.0 -2016-08-04 00:00:00,15570.0 -2016-08-02 01:00:00,12583.0 -2016-08-02 02:00:00,11611.0 -2016-08-02 03:00:00,10939.0 -2016-08-02 04:00:00,10514.0 -2016-08-02 05:00:00,10304.0 -2016-08-02 06:00:00,10421.0 -2016-08-02 07:00:00,10937.0 -2016-08-02 08:00:00,11772.0 -2016-08-02 09:00:00,12959.0 -2016-08-02 10:00:00,14102.0 -2016-08-02 11:00:00,15214.0 -2016-08-02 12:00:00,16170.0 -2016-08-02 13:00:00,16974.0 -2016-08-02 14:00:00,17745.0 -2016-08-02 15:00:00,18369.0 -2016-08-02 16:00:00,18695.0 -2016-08-02 17:00:00,18883.0 -2016-08-02 18:00:00,18823.0 -2016-08-02 19:00:00,18552.0 -2016-08-02 20:00:00,17920.0 -2016-08-02 21:00:00,17141.0 -2016-08-02 22:00:00,16744.0 -2016-08-02 23:00:00,16075.0 -2016-08-03 00:00:00,14846.0 -2016-08-01 01:00:00,11463.0 -2016-08-01 02:00:00,10684.0 -2016-08-01 03:00:00,10167.0 -2016-08-01 04:00:00,9787.0 -2016-08-01 05:00:00,9666.0 -2016-08-01 06:00:00,9836.0 -2016-08-01 07:00:00,10452.0 -2016-08-01 08:00:00,11275.0 -2016-08-01 09:00:00,12561.0 -2016-08-01 10:00:00,13640.0 -2016-08-01 11:00:00,14556.0 -2016-08-01 12:00:00,15533.0 -2016-08-01 13:00:00,16295.0 -2016-08-01 14:00:00,16936.0 -2016-08-01 15:00:00,17488.0 -2016-08-01 16:00:00,17803.0 -2016-08-01 17:00:00,17911.0 -2016-08-01 18:00:00,17893.0 -2016-08-01 19:00:00,17586.0 -2016-08-01 20:00:00,16980.0 -2016-08-01 21:00:00,16174.0 -2016-08-01 22:00:00,15674.0 -2016-08-01 23:00:00,14973.0 -2016-08-02 00:00:00,13865.0 -2016-07-31 01:00:00,10985.0 -2016-07-31 02:00:00,10278.0 -2016-07-31 03:00:00,9759.0 -2016-07-31 04:00:00,9325.0 -2016-07-31 05:00:00,9094.0 -2016-07-31 06:00:00,8926.0 -2016-07-31 07:00:00,8877.0 -2016-07-31 08:00:00,8809.0 -2016-07-31 09:00:00,9362.0 -2016-07-31 10:00:00,10175.0 -2016-07-31 11:00:00,11121.0 -2016-07-31 12:00:00,12075.0 -2016-07-31 13:00:00,12821.0 -2016-07-31 14:00:00,13462.0 -2016-07-31 15:00:00,13916.0 -2016-07-31 16:00:00,14332.0 -2016-07-31 17:00:00,14522.0 -2016-07-31 18:00:00,14584.0 -2016-07-31 19:00:00,14475.0 -2016-07-31 20:00:00,14139.0 -2016-07-31 21:00:00,13529.0 -2016-07-31 22:00:00,13414.0 -2016-07-31 23:00:00,13163.0 -2016-08-01 00:00:00,12385.0 -2016-07-30 01:00:00,11806.0 -2016-07-30 02:00:00,11046.0 -2016-07-30 03:00:00,10521.0 -2016-07-30 04:00:00,10090.0 -2016-07-30 05:00:00,9868.0 -2016-07-30 06:00:00,9784.0 -2016-07-30 07:00:00,9936.0 -2016-07-30 08:00:00,10102.0 -2016-07-30 09:00:00,10598.0 -2016-07-30 10:00:00,11442.0 -2016-07-30 11:00:00,12120.0 -2016-07-30 12:00:00,12564.0 -2016-07-30 13:00:00,12821.0 -2016-07-30 14:00:00,13138.0 -2016-07-30 15:00:00,13463.0 -2016-07-30 16:00:00,13730.0 -2016-07-30 17:00:00,13876.0 -2016-07-30 18:00:00,14011.0 -2016-07-30 19:00:00,13851.0 -2016-07-30 20:00:00,13300.0 -2016-07-30 21:00:00,12689.0 -2016-07-30 22:00:00,12551.0 -2016-07-30 23:00:00,12336.0 -2016-07-31 00:00:00,11665.0 -2016-07-29 01:00:00,12527.0 -2016-07-29 02:00:00,11681.0 -2016-07-29 03:00:00,11132.0 -2016-07-29 04:00:00,10708.0 -2016-07-29 05:00:00,10496.0 -2016-07-29 06:00:00,10650.0 -2016-07-29 07:00:00,11181.0 -2016-07-29 08:00:00,11966.0 -2016-07-29 09:00:00,13058.0 -2016-07-29 10:00:00,14064.0 -2016-07-29 11:00:00,14874.0 -2016-07-29 12:00:00,15670.0 -2016-07-29 13:00:00,16204.0 -2016-07-29 14:00:00,16590.0 -2016-07-29 15:00:00,16573.0 -2016-07-29 16:00:00,15950.0 -2016-07-29 17:00:00,15264.0 -2016-07-29 18:00:00,14891.0 -2016-07-29 19:00:00,14472.0 -2016-07-29 20:00:00,14051.0 -2016-07-29 21:00:00,13724.0 -2016-07-29 22:00:00,13746.0 -2016-07-29 23:00:00,13450.0 -2016-07-30 00:00:00,12632.0 -2016-07-28 01:00:00,14098.0 -2016-07-28 02:00:00,12982.0 -2016-07-28 03:00:00,12177.0 -2016-07-28 04:00:00,11596.0 -2016-07-28 05:00:00,11280.0 -2016-07-28 06:00:00,11292.0 -2016-07-28 07:00:00,11794.0 -2016-07-28 08:00:00,12601.0 -2016-07-28 09:00:00,13822.0 -2016-07-28 10:00:00,14960.0 -2016-07-28 11:00:00,15967.0 -2016-07-28 12:00:00,16993.0 -2016-07-28 13:00:00,17629.0 -2016-07-28 14:00:00,17990.0 -2016-07-28 15:00:00,17845.0 -2016-07-28 16:00:00,17198.0 -2016-07-28 17:00:00,16513.0 -2016-07-28 18:00:00,15930.0 -2016-07-28 19:00:00,15742.0 -2016-07-28 20:00:00,15388.0 -2016-07-28 21:00:00,14989.0 -2016-07-28 22:00:00,14868.0 -2016-07-28 23:00:00,14476.0 -2016-07-29 00:00:00,13582.0 -2016-07-27 01:00:00,13555.0 -2016-07-27 02:00:00,12464.0 -2016-07-27 03:00:00,11652.0 -2016-07-27 04:00:00,11147.0 -2016-07-27 05:00:00,10799.0 -2016-07-27 06:00:00,10862.0 -2016-07-27 07:00:00,11300.0 -2016-07-27 08:00:00,12153.0 -2016-07-27 09:00:00,13461.0 -2016-07-27 10:00:00,14744.0 -2016-07-27 11:00:00,15932.0 -2016-07-27 12:00:00,17000.0 -2016-07-27 13:00:00,17860.0 -2016-07-27 14:00:00,18432.0 -2016-07-27 15:00:00,18982.0 -2016-07-27 16:00:00,19370.0 -2016-07-27 17:00:00,19612.0 -2016-07-27 18:00:00,19627.0 -2016-07-27 19:00:00,19396.0 -2016-07-27 20:00:00,18725.0 -2016-07-27 21:00:00,17891.0 -2016-07-27 22:00:00,17521.0 -2016-07-27 23:00:00,16789.0 -2016-07-28 00:00:00,15453.0 -2016-07-26 01:00:00,13474.0 -2016-07-26 02:00:00,12383.0 -2016-07-26 03:00:00,11600.0 -2016-07-26 04:00:00,11070.0 -2016-07-26 05:00:00,10767.0 -2016-07-26 06:00:00,10809.0 -2016-07-26 07:00:00,11280.0 -2016-07-26 08:00:00,12127.0 -2016-07-26 09:00:00,13414.0 -2016-07-26 10:00:00,14626.0 -2016-07-26 11:00:00,15704.0 -2016-07-26 12:00:00,16688.0 -2016-07-26 13:00:00,17312.0 -2016-07-26 14:00:00,17860.0 -2016-07-26 15:00:00,18352.0 -2016-07-26 16:00:00,18657.0 -2016-07-26 17:00:00,18879.0 -2016-07-26 18:00:00,18982.0 -2016-07-26 19:00:00,18865.0 -2016-07-26 20:00:00,18369.0 -2016-07-26 21:00:00,17567.0 -2016-07-26 22:00:00,16972.0 -2016-07-26 23:00:00,16288.0 -2016-07-27 00:00:00,14933.0 -2016-07-25 01:00:00,13351.0 -2016-07-25 02:00:00,12512.0 -2016-07-25 03:00:00,11822.0 -2016-07-25 04:00:00,11384.0 -2016-07-25 05:00:00,11143.0 -2016-07-25 06:00:00,11256.0 -2016-07-25 07:00:00,11828.0 -2016-07-25 08:00:00,12643.0 -2016-07-25 09:00:00,13963.0 -2016-07-25 10:00:00,15088.0 -2016-07-25 11:00:00,15991.0 -2016-07-25 12:00:00,16861.0 -2016-07-25 13:00:00,17473.0 -2016-07-25 14:00:00,17985.0 -2016-07-25 15:00:00,18503.0 -2016-07-25 16:00:00,18829.0 -2016-07-25 17:00:00,19026.0 -2016-07-25 18:00:00,19043.0 -2016-07-25 19:00:00,18871.0 -2016-07-25 20:00:00,18383.0 -2016-07-25 21:00:00,17561.0 -2016-07-25 22:00:00,16926.0 -2016-07-25 23:00:00,16222.0 -2016-07-26 00:00:00,14852.0 -2016-07-24 01:00:00,12881.0 -2016-07-24 02:00:00,12038.0 -2016-07-24 03:00:00,11365.0 -2016-07-24 04:00:00,10945.0 -2016-07-24 05:00:00,10644.0 -2016-07-24 06:00:00,10431.0 -2016-07-24 07:00:00,10373.0 -2016-07-24 08:00:00,10399.0 -2016-07-24 09:00:00,11058.0 -2016-07-24 10:00:00,12079.0 -2016-07-24 11:00:00,13506.0 -2016-07-24 12:00:00,15072.0 -2016-07-24 13:00:00,16335.0 -2016-07-24 14:00:00,17318.0 -2016-07-24 15:00:00,18150.0 -2016-07-24 16:00:00,18755.0 -2016-07-24 17:00:00,18895.0 -2016-07-24 18:00:00,19004.0 -2016-07-24 19:00:00,18814.0 -2016-07-24 20:00:00,17779.0 -2016-07-24 21:00:00,16443.0 -2016-07-24 22:00:00,15896.0 -2016-07-24 23:00:00,15367.0 -2016-07-25 00:00:00,14434.0 -2016-07-23 01:00:00,15479.0 -2016-07-23 02:00:00,14489.0 -2016-07-23 03:00:00,13758.0 -2016-07-23 04:00:00,13152.0 -2016-07-23 05:00:00,12735.0 -2016-07-23 06:00:00,12495.0 -2016-07-23 07:00:00,12479.0 -2016-07-23 08:00:00,12662.0 -2016-07-23 09:00:00,13667.0 -2016-07-23 10:00:00,15214.0 -2016-07-23 11:00:00,16611.0 -2016-07-23 12:00:00,17725.0 -2016-07-23 13:00:00,18420.0 -2016-07-23 14:00:00,18656.0 -2016-07-23 15:00:00,18857.0 -2016-07-23 16:00:00,18877.0 -2016-07-23 17:00:00,18850.0 -2016-07-23 18:00:00,18554.0 -2016-07-23 19:00:00,17977.0 -2016-07-23 20:00:00,17058.0 -2016-07-23 21:00:00,15956.0 -2016-07-23 22:00:00,15375.0 -2016-07-23 23:00:00,14763.0 -2016-07-24 00:00:00,13822.0 -2016-07-22 01:00:00,14177.0 -2016-07-22 02:00:00,13089.0 -2016-07-22 03:00:00,12396.0 -2016-07-22 04:00:00,11945.0 -2016-07-22 05:00:00,11578.0 -2016-07-22 06:00:00,11575.0 -2016-07-22 07:00:00,11979.0 -2016-07-22 08:00:00,12657.0 -2016-07-22 09:00:00,13735.0 -2016-07-22 10:00:00,14632.0 -2016-07-22 11:00:00,15495.0 -2016-07-22 12:00:00,16511.0 -2016-07-22 13:00:00,17336.0 -2016-07-22 14:00:00,18004.0 -2016-07-22 15:00:00,18610.0 -2016-07-22 16:00:00,19134.0 -2016-07-22 17:00:00,19555.0 -2016-07-22 18:00:00,19929.0 -2016-07-22 19:00:00,19972.0 -2016-07-22 20:00:00,19641.0 -2016-07-22 21:00:00,18905.0 -2016-07-22 22:00:00,18423.0 -2016-07-22 23:00:00,17879.0 -2016-07-23 00:00:00,16710.0 -2016-07-21 01:00:00,14399.0 -2016-07-21 02:00:00,13340.0 -2016-07-21 03:00:00,12666.0 -2016-07-21 04:00:00,12122.0 -2016-07-21 05:00:00,11844.0 -2016-07-21 06:00:00,11929.0 -2016-07-21 07:00:00,12488.0 -2016-07-21 08:00:00,13454.0 -2016-07-21 09:00:00,14786.0 -2016-07-21 10:00:00,16209.0 -2016-07-21 11:00:00,17599.0 -2016-07-21 12:00:00,18768.0 -2016-07-21 13:00:00,19616.0 -2016-07-21 14:00:00,19788.0 -2016-07-21 15:00:00,19308.0 -2016-07-21 16:00:00,19100.0 -2016-07-21 17:00:00,19340.0 -2016-07-21 18:00:00,19871.0 -2016-07-21 19:00:00,20296.0 -2016-07-21 20:00:00,20104.0 -2016-07-21 21:00:00,19458.0 -2016-07-21 22:00:00,18828.0 -2016-07-21 23:00:00,17130.0 -2016-07-22 00:00:00,15578.0 -2016-07-20 01:00:00,13462.0 -2016-07-20 02:00:00,12389.0 -2016-07-20 03:00:00,11616.0 -2016-07-20 04:00:00,11093.0 -2016-07-20 05:00:00,10815.0 -2016-07-20 06:00:00,10863.0 -2016-07-20 07:00:00,11329.0 -2016-07-20 08:00:00,12281.0 -2016-07-20 09:00:00,13644.0 -2016-07-20 10:00:00,14996.0 -2016-07-20 11:00:00,16259.0 -2016-07-20 12:00:00,17401.0 -2016-07-20 13:00:00,18306.0 -2016-07-20 14:00:00,18914.0 -2016-07-20 15:00:00,18788.0 -2016-07-20 16:00:00,18303.0 -2016-07-20 17:00:00,18314.0 -2016-07-20 18:00:00,18595.0 -2016-07-20 19:00:00,18713.0 -2016-07-20 20:00:00,18574.0 -2016-07-20 21:00:00,17979.0 -2016-07-20 22:00:00,17402.0 -2016-07-20 23:00:00,16820.0 -2016-07-21 00:00:00,15657.0 -2016-07-19 01:00:00,12861.0 -2016-07-19 02:00:00,11815.0 -2016-07-19 03:00:00,11068.0 -2016-07-19 04:00:00,10585.0 -2016-07-19 05:00:00,10325.0 -2016-07-19 06:00:00,10376.0 -2016-07-19 07:00:00,10830.0 -2016-07-19 08:00:00,11779.0 -2016-07-19 09:00:00,13097.0 -2016-07-19 10:00:00,14303.0 -2016-07-19 11:00:00,15401.0 -2016-07-19 12:00:00,16453.0 -2016-07-19 13:00:00,17277.0 -2016-07-19 14:00:00,17964.0 -2016-07-19 15:00:00,18569.0 -2016-07-19 16:00:00,18904.0 -2016-07-19 17:00:00,19029.0 -2016-07-19 18:00:00,18873.0 -2016-07-19 19:00:00,18494.0 -2016-07-19 20:00:00,17811.0 -2016-07-19 21:00:00,17039.0 -2016-07-19 22:00:00,16584.0 -2016-07-19 23:00:00,16067.0 -2016-07-20 00:00:00,14856.0 -2016-07-18 01:00:00,12784.0 -2016-07-18 02:00:00,11922.0 -2016-07-18 03:00:00,11321.0 -2016-07-18 04:00:00,10810.0 -2016-07-18 05:00:00,10544.0 -2016-07-18 06:00:00,10600.0 -2016-07-18 07:00:00,11095.0 -2016-07-18 08:00:00,12057.0 -2016-07-18 09:00:00,13362.0 -2016-07-18 10:00:00,14420.0 -2016-07-18 11:00:00,15139.0 -2016-07-18 12:00:00,15618.0 -2016-07-18 13:00:00,15807.0 -2016-07-18 14:00:00,16220.0 -2016-07-18 15:00:00,16785.0 -2016-07-18 16:00:00,17261.0 -2016-07-18 17:00:00,17617.0 -2016-07-18 18:00:00,17934.0 -2016-07-18 19:00:00,17907.0 -2016-07-18 20:00:00,17572.0 -2016-07-18 21:00:00,16834.0 -2016-07-18 22:00:00,16135.0 -2016-07-18 23:00:00,15501.0 -2016-07-19 00:00:00,14194.0 -2016-07-17 01:00:00,10341.0 -2016-07-17 02:00:00,9660.0 -2016-07-17 03:00:00,9137.0 -2016-07-17 04:00:00,8748.0 -2016-07-17 05:00:00,8610.0 -2016-07-17 06:00:00,8501.0 -2016-07-17 07:00:00,8456.0 -2016-07-17 08:00:00,8473.0 -2016-07-17 09:00:00,8891.0 -2016-07-17 10:00:00,9466.0 -2016-07-17 11:00:00,9974.0 -2016-07-17 12:00:00,10447.0 -2016-07-17 13:00:00,11102.0 -2016-07-17 14:00:00,11859.0 -2016-07-17 15:00:00,12591.0 -2016-07-17 16:00:00,13260.0 -2016-07-17 17:00:00,13848.0 -2016-07-17 18:00:00,14247.0 -2016-07-17 19:00:00,14507.0 -2016-07-17 20:00:00,14732.0 -2016-07-17 21:00:00,14681.0 -2016-07-17 22:00:00,14715.0 -2016-07-17 23:00:00,14719.0 -2016-07-18 00:00:00,13982.0 -2016-07-16 01:00:00,10263.0 -2016-07-16 02:00:00,9644.0 -2016-07-16 03:00:00,9187.0 -2016-07-16 04:00:00,8911.0 -2016-07-16 05:00:00,8708.0 -2016-07-16 06:00:00,8703.0 -2016-07-16 07:00:00,8692.0 -2016-07-16 08:00:00,8885.0 -2016-07-16 09:00:00,9504.0 -2016-07-16 10:00:00,10282.0 -2016-07-16 11:00:00,10918.0 -2016-07-16 12:00:00,11424.0 -2016-07-16 13:00:00,11767.0 -2016-07-16 14:00:00,12029.0 -2016-07-16 15:00:00,12225.0 -2016-07-16 16:00:00,12438.0 -2016-07-16 17:00:00,12765.0 -2016-07-16 18:00:00,12955.0 -2016-07-16 19:00:00,13012.0 -2016-07-16 20:00:00,12740.0 -2016-07-16 21:00:00,12274.0 -2016-07-16 22:00:00,11958.0 -2016-07-16 23:00:00,11725.0 -2016-07-17 00:00:00,11082.0 -2016-07-15 01:00:00,12712.0 -2016-07-15 02:00:00,11686.0 -2016-07-15 03:00:00,10953.0 -2016-07-15 04:00:00,10421.0 -2016-07-15 05:00:00,10045.0 -2016-07-15 06:00:00,10047.0 -2016-07-15 07:00:00,10363.0 -2016-07-15 08:00:00,11095.0 -2016-07-15 09:00:00,12111.0 -2016-07-15 10:00:00,13012.0 -2016-07-15 11:00:00,13651.0 -2016-07-15 12:00:00,14027.0 -2016-07-15 13:00:00,14153.0 -2016-07-15 14:00:00,14188.0 -2016-07-15 15:00:00,14152.0 -2016-07-15 16:00:00,13923.0 -2016-07-15 17:00:00,13571.0 -2016-07-15 18:00:00,13211.0 -2016-07-15 19:00:00,12807.0 -2016-07-15 20:00:00,12350.0 -2016-07-15 21:00:00,12005.0 -2016-07-15 22:00:00,11975.0 -2016-07-15 23:00:00,11851.0 -2016-07-16 00:00:00,11107.0 -2016-07-14 01:00:00,12479.0 -2016-07-14 02:00:00,11550.0 -2016-07-14 03:00:00,10931.0 -2016-07-14 04:00:00,10480.0 -2016-07-14 05:00:00,10261.0 -2016-07-14 06:00:00,10391.0 -2016-07-14 07:00:00,10898.0 -2016-07-14 08:00:00,11818.0 -2016-07-14 09:00:00,13033.0 -2016-07-14 10:00:00,14191.0 -2016-07-14 11:00:00,15225.0 -2016-07-14 12:00:00,16216.0 -2016-07-14 13:00:00,16862.0 -2016-07-14 14:00:00,17417.0 -2016-07-14 15:00:00,17863.0 -2016-07-14 16:00:00,18153.0 -2016-07-14 17:00:00,18281.0 -2016-07-14 18:00:00,18244.0 -2016-07-14 19:00:00,17951.0 -2016-07-14 20:00:00,17285.0 -2016-07-14 21:00:00,16296.0 -2016-07-14 22:00:00,15724.0 -2016-07-14 23:00:00,15182.0 -2016-07-15 00:00:00,13996.0 -2016-07-13 01:00:00,13940.0 -2016-07-13 02:00:00,12811.0 -2016-07-13 03:00:00,12055.0 -2016-07-13 04:00:00,11577.0 -2016-07-13 05:00:00,11259.0 -2016-07-13 06:00:00,11202.0 -2016-07-13 07:00:00,11545.0 -2016-07-13 08:00:00,12550.0 -2016-07-13 09:00:00,13987.0 -2016-07-13 10:00:00,14878.0 -2016-07-13 11:00:00,15425.0 -2016-07-13 12:00:00,16249.0 -2016-07-13 13:00:00,16979.0 -2016-07-13 14:00:00,17371.0 -2016-07-13 15:00:00,18056.0 -2016-07-13 16:00:00,18376.0 -2016-07-13 17:00:00,18421.0 -2016-07-13 18:00:00,18331.0 -2016-07-13 19:00:00,17689.0 -2016-07-13 20:00:00,17114.0 -2016-07-13 21:00:00,16336.0 -2016-07-13 22:00:00,15387.0 -2016-07-13 23:00:00,14791.0 -2016-07-14 00:00:00,13667.0 -2016-07-12 01:00:00,14964.0 -2016-07-12 02:00:00,13833.0 -2016-07-12 03:00:00,12992.0 -2016-07-12 04:00:00,12348.0 -2016-07-12 05:00:00,11976.0 -2016-07-12 06:00:00,11996.0 -2016-07-12 07:00:00,12363.0 -2016-07-12 08:00:00,13341.0 -2016-07-12 09:00:00,14468.0 -2016-07-12 10:00:00,15447.0 -2016-07-12 11:00:00,16492.0 -2016-07-12 12:00:00,17661.0 -2016-07-12 13:00:00,18604.0 -2016-07-12 14:00:00,19286.0 -2016-07-12 15:00:00,19740.0 -2016-07-12 16:00:00,19841.0 -2016-07-12 17:00:00,19712.0 -2016-07-12 18:00:00,19480.0 -2016-07-12 19:00:00,19285.0 -2016-07-12 20:00:00,18731.0 -2016-07-12 21:00:00,17956.0 -2016-07-12 22:00:00,17231.0 -2016-07-12 23:00:00,16668.0 -2016-07-13 00:00:00,15364.0 -2016-07-11 01:00:00,11678.0 -2016-07-11 02:00:00,10920.0 -2016-07-11 03:00:00,10440.0 -2016-07-11 04:00:00,10049.0 -2016-07-11 05:00:00,9940.0 -2016-07-11 06:00:00,10094.0 -2016-07-11 07:00:00,10665.0 -2016-07-11 08:00:00,11715.0 -2016-07-11 09:00:00,13182.0 -2016-07-11 10:00:00,14538.0 -2016-07-11 11:00:00,15895.0 -2016-07-11 12:00:00,17162.0 -2016-07-11 13:00:00,18190.0 -2016-07-11 14:00:00,18964.0 -2016-07-11 15:00:00,19552.0 -2016-07-11 16:00:00,19960.0 -2016-07-11 17:00:00,20154.0 -2016-07-11 18:00:00,20268.0 -2016-07-11 19:00:00,20109.0 -2016-07-11 20:00:00,19554.0 -2016-07-11 21:00:00,18859.0 -2016-07-11 22:00:00,18201.0 -2016-07-11 23:00:00,17679.0 -2016-07-12 00:00:00,16360.0 -2016-07-10 01:00:00,10680.0 -2016-07-10 02:00:00,9923.0 -2016-07-10 03:00:00,9393.0 -2016-07-10 04:00:00,8979.0 -2016-07-10 05:00:00,8751.0 -2016-07-10 06:00:00,8600.0 -2016-07-10 07:00:00,8446.0 -2016-07-10 08:00:00,8483.0 -2016-07-10 09:00:00,9170.0 -2016-07-10 10:00:00,9983.0 -2016-07-10 11:00:00,10882.0 -2016-07-10 12:00:00,11692.0 -2016-07-10 13:00:00,12432.0 -2016-07-10 14:00:00,12935.0 -2016-07-10 15:00:00,13378.0 -2016-07-10 16:00:00,13743.0 -2016-07-10 17:00:00,13899.0 -2016-07-10 18:00:00,13846.0 -2016-07-10 19:00:00,13837.0 -2016-07-10 20:00:00,13547.0 -2016-07-10 21:00:00,13270.0 -2016-07-10 22:00:00,13174.0 -2016-07-10 23:00:00,13179.0 -2016-07-11 00:00:00,12502.0 -2016-07-09 01:00:00,12217.0 -2016-07-09 02:00:00,11213.0 -2016-07-09 03:00:00,10494.0 -2016-07-09 04:00:00,10008.0 -2016-07-09 05:00:00,9703.0 -2016-07-09 06:00:00,9558.0 -2016-07-09 07:00:00,9500.0 -2016-07-09 08:00:00,9791.0 -2016-07-09 09:00:00,10537.0 -2016-07-09 10:00:00,11354.0 -2016-07-09 11:00:00,12143.0 -2016-07-09 12:00:00,12773.0 -2016-07-09 13:00:00,13205.0 -2016-07-09 14:00:00,13517.0 -2016-07-09 15:00:00,13684.0 -2016-07-09 16:00:00,13878.0 -2016-07-09 17:00:00,14023.0 -2016-07-09 18:00:00,14128.0 -2016-07-09 19:00:00,14015.0 -2016-07-09 20:00:00,13621.0 -2016-07-09 21:00:00,12968.0 -2016-07-09 22:00:00,12415.0 -2016-07-09 23:00:00,12210.0 -2016-07-10 00:00:00,11485.0 -2016-07-08 01:00:00,12400.0 -2016-07-08 02:00:00,11415.0 -2016-07-08 03:00:00,10726.0 -2016-07-08 04:00:00,10286.0 -2016-07-08 05:00:00,10088.0 -2016-07-08 06:00:00,10248.0 -2016-07-08 07:00:00,10711.0 -2016-07-08 08:00:00,11617.0 -2016-07-08 09:00:00,12885.0 -2016-07-08 10:00:00,14158.0 -2016-07-08 11:00:00,15433.0 -2016-07-08 12:00:00,16525.0 -2016-07-08 13:00:00,17222.0 -2016-07-08 14:00:00,17680.0 -2016-07-08 15:00:00,17868.0 -2016-07-08 16:00:00,17869.0 -2016-07-08 17:00:00,17884.0 -2016-07-08 18:00:00,17834.0 -2016-07-08 19:00:00,17504.0 -2016-07-08 20:00:00,16692.0 -2016-07-08 21:00:00,15802.0 -2016-07-08 22:00:00,15028.0 -2016-07-08 23:00:00,14520.0 -2016-07-09 00:00:00,13396.0 -2016-07-07 01:00:00,13474.0 -2016-07-07 02:00:00,12437.0 -2016-07-07 03:00:00,11651.0 -2016-07-07 04:00:00,11070.0 -2016-07-07 05:00:00,10756.0 -2016-07-07 06:00:00,10778.0 -2016-07-07 07:00:00,11163.0 -2016-07-07 08:00:00,12043.0 -2016-07-07 09:00:00,13150.0 -2016-07-07 10:00:00,14337.0 -2016-07-07 11:00:00,15168.0 -2016-07-07 12:00:00,15731.0 -2016-07-07 13:00:00,15936.0 -2016-07-07 14:00:00,16073.0 -2016-07-07 15:00:00,16120.0 -2016-07-07 16:00:00,16082.0 -2016-07-07 17:00:00,16103.0 -2016-07-07 18:00:00,16434.0 -2016-07-07 19:00:00,16197.0 -2016-07-07 20:00:00,15740.0 -2016-07-07 21:00:00,15287.0 -2016-07-07 22:00:00,14974.0 -2016-07-07 23:00:00,14610.0 -2016-07-08 00:00:00,13554.0 -2016-07-06 01:00:00,13476.0 -2016-07-06 02:00:00,12459.0 -2016-07-06 03:00:00,11781.0 -2016-07-06 04:00:00,11239.0 -2016-07-06 05:00:00,10635.0 -2016-07-06 06:00:00,10524.0 -2016-07-06 07:00:00,11052.0 -2016-07-06 08:00:00,11881.0 -2016-07-06 09:00:00,12529.0 -2016-07-06 10:00:00,12935.0 -2016-07-06 11:00:00,13478.0 -2016-07-06 12:00:00,14718.0 -2016-07-06 13:00:00,15638.0 -2016-07-06 14:00:00,16442.0 -2016-07-06 15:00:00,17162.0 -2016-07-06 16:00:00,17842.0 -2016-07-06 17:00:00,18348.0 -2016-07-06 18:00:00,18416.0 -2016-07-06 19:00:00,18026.0 -2016-07-06 20:00:00,17399.0 -2016-07-06 21:00:00,16823.0 -2016-07-06 22:00:00,16423.0 -2016-07-06 23:00:00,16079.0 -2016-07-07 00:00:00,14820.0 -2016-07-05 01:00:00,10430.0 -2016-07-05 02:00:00,9742.0 -2016-07-05 03:00:00,9229.0 -2016-07-05 04:00:00,8899.0 -2016-07-05 05:00:00,8816.0 -2016-07-05 06:00:00,9018.0 -2016-07-05 07:00:00,9599.0 -2016-07-05 08:00:00,10531.0 -2016-07-05 09:00:00,11712.0 -2016-07-05 10:00:00,12650.0 -2016-07-05 11:00:00,13473.0 -2016-07-05 12:00:00,14410.0 -2016-07-05 13:00:00,15094.0 -2016-07-05 14:00:00,15748.0 -2016-07-05 15:00:00,16370.0 -2016-07-05 16:00:00,16897.0 -2016-07-05 17:00:00,17403.0 -2016-07-05 18:00:00,17799.0 -2016-07-05 19:00:00,17914.0 -2016-07-05 20:00:00,17621.0 -2016-07-05 21:00:00,17047.0 -2016-07-05 22:00:00,16437.0 -2016-07-05 23:00:00,16091.0 -2016-07-06 00:00:00,14810.0 -2016-07-04 01:00:00,9522.0 -2016-07-04 02:00:00,9046.0 -2016-07-04 03:00:00,8631.0 -2016-07-04 04:00:00,8369.0 -2016-07-04 05:00:00,8189.0 -2016-07-04 06:00:00,8174.0 -2016-07-04 07:00:00,8191.0 -2016-07-04 08:00:00,8236.0 -2016-07-04 09:00:00,8476.0 -2016-07-04 10:00:00,8823.0 -2016-07-04 11:00:00,9316.0 -2016-07-04 12:00:00,9787.0 -2016-07-04 13:00:00,10298.0 -2016-07-04 14:00:00,10863.0 -2016-07-04 15:00:00,11286.0 -2016-07-04 16:00:00,11679.0 -2016-07-04 17:00:00,11860.0 -2016-07-04 18:00:00,11844.0 -2016-07-04 19:00:00,11726.0 -2016-07-04 20:00:00,11477.0 -2016-07-04 21:00:00,11288.0 -2016-07-04 22:00:00,11355.0 -2016-07-04 23:00:00,11320.0 -2016-07-05 00:00:00,10991.0 -2016-07-03 01:00:00,9258.0 -2016-07-03 02:00:00,8714.0 -2016-07-03 03:00:00,8297.0 -2016-07-03 04:00:00,8004.0 -2016-07-03 05:00:00,7762.0 -2016-07-03 06:00:00,7769.0 -2016-07-03 07:00:00,7516.0 -2016-07-03 08:00:00,7589.0 -2016-07-03 09:00:00,7913.0 -2016-07-03 10:00:00,8437.0 -2016-07-03 11:00:00,8998.0 -2016-07-03 12:00:00,9397.0 -2016-07-03 13:00:00,9854.0 -2016-07-03 14:00:00,10184.0 -2016-07-03 15:00:00,10490.0 -2016-07-03 16:00:00,10712.0 -2016-07-03 17:00:00,10892.0 -2016-07-03 18:00:00,10933.0 -2016-07-03 19:00:00,10953.0 -2016-07-03 20:00:00,10737.0 -2016-07-03 21:00:00,10408.0 -2016-07-03 22:00:00,10285.0 -2016-07-03 23:00:00,10314.0 -2016-07-04 00:00:00,10002.0 -2016-07-02 01:00:00,9647.0 -2016-07-02 02:00:00,8979.0 -2016-07-02 03:00:00,8571.0 -2016-07-02 04:00:00,8273.0 -2016-07-02 05:00:00,8105.0 -2016-07-02 06:00:00,8040.0 -2016-07-02 07:00:00,7996.0 -2016-07-02 08:00:00,8124.0 -2016-07-02 09:00:00,8504.0 -2016-07-02 10:00:00,9015.0 -2016-07-02 11:00:00,9558.0 -2016-07-02 12:00:00,9997.0 -2016-07-02 13:00:00,10299.0 -2016-07-02 14:00:00,10437.0 -2016-07-02 15:00:00,10606.0 -2016-07-02 16:00:00,10661.0 -2016-07-02 17:00:00,10784.0 -2016-07-02 18:00:00,10720.0 -2016-07-02 19:00:00,10530.0 -2016-07-02 20:00:00,10294.0 -2016-07-02 21:00:00,10147.0 -2016-07-02 22:00:00,10226.0 -2016-07-02 23:00:00,10270.0 -2016-07-03 00:00:00,9762.0 -2016-07-01 01:00:00,11152.0 -2016-07-01 02:00:00,10447.0 -2016-07-01 03:00:00,9872.0 -2016-07-01 04:00:00,9432.0 -2016-07-01 05:00:00,9187.0 -2016-07-01 06:00:00,9244.0 -2016-07-01 07:00:00,9541.0 -2016-07-01 08:00:00,10220.0 -2016-07-01 09:00:00,10982.0 -2016-07-01 10:00:00,11603.0 -2016-07-01 11:00:00,12027.0 -2016-07-01 12:00:00,12403.0 -2016-07-01 13:00:00,12650.0 -2016-07-01 14:00:00,12807.0 -2016-07-01 15:00:00,12983.0 -2016-07-01 16:00:00,13022.0 -2016-07-01 17:00:00,12989.0 -2016-07-01 18:00:00,12918.0 -2016-07-01 19:00:00,12717.0 -2016-07-01 20:00:00,12247.0 -2016-07-01 21:00:00,11667.0 -2016-07-01 22:00:00,11290.0 -2016-07-01 23:00:00,11147.0 -2016-07-02 00:00:00,10384.0 -2016-06-30 01:00:00,10406.0 -2016-06-30 02:00:00,9681.0 -2016-06-30 03:00:00,9143.0 -2016-06-30 04:00:00,8854.0 -2016-06-30 05:00:00,8692.0 -2016-06-30 06:00:00,8773.0 -2016-06-30 07:00:00,9144.0 -2016-06-30 08:00:00,10016.0 -2016-06-30 09:00:00,11090.0 -2016-06-30 10:00:00,11916.0 -2016-06-30 11:00:00,12570.0 -2016-06-30 12:00:00,13091.0 -2016-06-30 13:00:00,13390.0 -2016-06-30 14:00:00,13645.0 -2016-06-30 15:00:00,13913.0 -2016-06-30 16:00:00,14092.0 -2016-06-30 17:00:00,14041.0 -2016-06-30 18:00:00,14045.0 -2016-06-30 19:00:00,13953.0 -2016-06-30 20:00:00,13641.0 -2016-06-30 21:00:00,13197.0 -2016-06-30 22:00:00,13074.0 -2016-06-30 23:00:00,12934.0 -2016-07-01 00:00:00,12135.0 -2016-06-29 01:00:00,9949.0 -2016-06-29 02:00:00,9304.0 -2016-06-29 03:00:00,8858.0 -2016-06-29 04:00:00,8597.0 -2016-06-29 05:00:00,8465.0 -2016-06-29 06:00:00,8587.0 -2016-06-29 07:00:00,8933.0 -2016-06-29 08:00:00,9791.0 -2016-06-29 09:00:00,10783.0 -2016-06-29 10:00:00,11541.0 -2016-06-29 11:00:00,12065.0 -2016-06-29 12:00:00,12536.0 -2016-06-29 13:00:00,12881.0 -2016-06-29 14:00:00,13181.0 -2016-06-29 15:00:00,13570.0 -2016-06-29 16:00:00,13866.0 -2016-06-29 17:00:00,14016.0 -2016-06-29 18:00:00,14068.0 -2016-06-29 19:00:00,13863.0 -2016-06-29 20:00:00,13281.0 -2016-06-29 21:00:00,12881.0 -2016-06-29 22:00:00,12554.0 -2016-06-29 23:00:00,12294.0 -2016-06-30 00:00:00,11414.0 -2016-06-28 01:00:00,12393.0 -2016-06-28 02:00:00,11379.0 -2016-06-28 03:00:00,10717.0 -2016-06-28 04:00:00,10161.0 -2016-06-28 05:00:00,9851.0 -2016-06-28 06:00:00,9829.0 -2016-06-28 07:00:00,10129.0 -2016-06-28 08:00:00,10818.0 -2016-06-28 09:00:00,11729.0 -2016-06-28 10:00:00,12252.0 -2016-06-28 11:00:00,12489.0 -2016-06-28 12:00:00,12711.0 -2016-06-28 13:00:00,12719.0 -2016-06-28 14:00:00,12753.0 -2016-06-28 15:00:00,12905.0 -2016-06-28 16:00:00,13001.0 -2016-06-28 17:00:00,13019.0 -2016-06-28 18:00:00,13023.0 -2016-06-28 19:00:00,12903.0 -2016-06-28 20:00:00,12506.0 -2016-06-28 21:00:00,12016.0 -2016-06-28 22:00:00,11766.0 -2016-06-28 23:00:00,11613.0 -2016-06-29 00:00:00,10836.0 -2016-06-27 01:00:00,12780.0 -2016-06-27 02:00:00,11813.0 -2016-06-27 03:00:00,11134.0 -2016-06-27 04:00:00,10648.0 -2016-06-27 05:00:00,10510.0 -2016-06-27 06:00:00,10624.0 -2016-06-27 07:00:00,11011.0 -2016-06-27 08:00:00,12199.0 -2016-06-27 09:00:00,13621.0 -2016-06-27 10:00:00,14864.0 -2016-06-27 11:00:00,15962.0 -2016-06-27 12:00:00,16931.0 -2016-06-27 13:00:00,17692.0 -2016-06-27 14:00:00,18251.0 -2016-06-27 15:00:00,18708.0 -2016-06-27 16:00:00,18971.0 -2016-06-27 17:00:00,19069.0 -2016-06-27 18:00:00,18926.0 -2016-06-27 19:00:00,18559.0 -2016-06-27 20:00:00,17737.0 -2016-06-27 21:00:00,16713.0 -2016-06-27 22:00:00,15724.0 -2016-06-27 23:00:00,15020.0 -2016-06-28 00:00:00,13749.0 -2016-06-26 01:00:00,14312.0 -2016-06-26 02:00:00,13346.0 -2016-06-26 03:00:00,12600.0 -2016-06-26 04:00:00,11929.0 -2016-06-26 05:00:00,11539.0 -2016-06-26 06:00:00,11247.0 -2016-06-26 07:00:00,11005.0 -2016-06-26 08:00:00,10998.0 -2016-06-26 09:00:00,11334.0 -2016-06-26 10:00:00,11904.0 -2016-06-26 11:00:00,12773.0 -2016-06-26 12:00:00,13875.0 -2016-06-26 13:00:00,14672.0 -2016-06-26 14:00:00,15291.0 -2016-06-26 15:00:00,15704.0 -2016-06-26 16:00:00,16263.0 -2016-06-26 17:00:00,16720.0 -2016-06-26 18:00:00,17096.0 -2016-06-26 19:00:00,17195.0 -2016-06-26 20:00:00,16935.0 -2016-06-26 21:00:00,16177.0 -2016-06-26 22:00:00,15382.0 -2016-06-26 23:00:00,14975.0 -2016-06-27 00:00:00,13926.0 -2016-06-25 01:00:00,11740.0 -2016-06-25 02:00:00,10824.0 -2016-06-25 03:00:00,10191.0 -2016-06-25 04:00:00,9770.0 -2016-06-25 05:00:00,9511.0 -2016-06-25 06:00:00,9378.0 -2016-06-25 07:00:00,9327.0 -2016-06-25 08:00:00,9804.0 -2016-06-25 09:00:00,10814.0 -2016-06-25 10:00:00,11979.0 -2016-06-25 11:00:00,13208.0 -2016-06-25 12:00:00,14326.0 -2016-06-25 13:00:00,15254.0 -2016-06-25 14:00:00,15972.0 -2016-06-25 15:00:00,16507.0 -2016-06-25 16:00:00,17040.0 -2016-06-25 17:00:00,17499.0 -2016-06-25 18:00:00,17794.0 -2016-06-25 19:00:00,17807.0 -2016-06-25 20:00:00,17539.0 -2016-06-25 21:00:00,16994.0 -2016-06-25 22:00:00,16493.0 -2016-06-25 23:00:00,16250.0 -2016-06-26 00:00:00,15365.0 -2016-06-24 01:00:00,10756.0 -2016-06-24 02:00:00,10005.0 -2016-06-24 03:00:00,9460.0 -2016-06-24 04:00:00,9144.0 -2016-06-24 05:00:00,9032.0 -2016-06-24 06:00:00,9109.0 -2016-06-24 07:00:00,9467.0 -2016-06-24 08:00:00,10403.0 -2016-06-24 09:00:00,11492.0 -2016-06-24 10:00:00,12469.0 -2016-06-24 11:00:00,13289.0 -2016-06-24 12:00:00,13987.0 -2016-06-24 13:00:00,14603.0 -2016-06-24 14:00:00,15073.0 -2016-06-24 15:00:00,15546.0 -2016-06-24 16:00:00,15904.0 -2016-06-24 17:00:00,16184.0 -2016-06-24 18:00:00,16335.0 -2016-06-24 19:00:00,16238.0 -2016-06-24 20:00:00,15697.0 -2016-06-24 21:00:00,14944.0 -2016-06-24 22:00:00,14233.0 -2016-06-24 23:00:00,13847.0 -2016-06-25 00:00:00,12846.0 -2016-06-23 01:00:00,11486.0 -2016-06-23 02:00:00,10759.0 -2016-06-23 03:00:00,10234.0 -2016-06-23 04:00:00,9944.0 -2016-06-23 05:00:00,9795.0 -2016-06-23 06:00:00,9876.0 -2016-06-23 07:00:00,10377.0 -2016-06-23 08:00:00,11270.0 -2016-06-23 09:00:00,12141.0 -2016-06-23 10:00:00,12732.0 -2016-06-23 11:00:00,13128.0 -2016-06-23 12:00:00,13449.0 -2016-06-23 13:00:00,13712.0 -2016-06-23 14:00:00,13915.0 -2016-06-23 15:00:00,14140.0 -2016-06-23 16:00:00,14762.0 -2016-06-23 17:00:00,15154.0 -2016-06-23 18:00:00,15312.0 -2016-06-23 19:00:00,15038.0 -2016-06-23 20:00:00,14351.0 -2016-06-23 21:00:00,13564.0 -2016-06-23 22:00:00,13061.0 -2016-06-23 23:00:00,12712.0 -2016-06-24 00:00:00,11794.0 -2016-06-22 01:00:00,11981.0 -2016-06-22 02:00:00,11025.0 -2016-06-22 03:00:00,10429.0 -2016-06-22 04:00:00,10085.0 -2016-06-22 05:00:00,9905.0 -2016-06-22 06:00:00,10047.0 -2016-06-22 07:00:00,10635.0 -2016-06-22 08:00:00,11480.0 -2016-06-22 09:00:00,12189.0 -2016-06-22 10:00:00,12722.0 -2016-06-22 11:00:00,13214.0 -2016-06-22 12:00:00,13575.0 -2016-06-22 13:00:00,13612.0 -2016-06-22 14:00:00,13856.0 -2016-06-22 15:00:00,14279.0 -2016-06-22 16:00:00,14501.0 -2016-06-22 17:00:00,14462.0 -2016-06-22 18:00:00,14420.0 -2016-06-22 19:00:00,14403.0 -2016-06-22 20:00:00,14184.0 -2016-06-22 21:00:00,13999.0 -2016-06-22 22:00:00,13920.0 -2016-06-22 23:00:00,13478.0 -2016-06-23 00:00:00,12497.0 -2016-06-21 01:00:00,13275.0 -2016-06-21 02:00:00,12050.0 -2016-06-21 03:00:00,11103.0 -2016-06-21 04:00:00,10524.0 -2016-06-21 05:00:00,10119.0 -2016-06-21 06:00:00,10081.0 -2016-06-21 07:00:00,10353.0 -2016-06-21 08:00:00,11337.0 -2016-06-21 09:00:00,12497.0 -2016-06-21 10:00:00,13454.0 -2016-06-21 11:00:00,14152.0 -2016-06-21 12:00:00,14777.0 -2016-06-21 13:00:00,15194.0 -2016-06-21 14:00:00,15544.0 -2016-06-21 15:00:00,16016.0 -2016-06-21 16:00:00,16376.0 -2016-06-21 17:00:00,16727.0 -2016-06-21 18:00:00,16919.0 -2016-06-21 19:00:00,16852.0 -2016-06-21 20:00:00,16354.0 -2016-06-21 21:00:00,15715.0 -2016-06-21 22:00:00,14993.0 -2016-06-21 23:00:00,14488.0 -2016-06-22 00:00:00,13291.0 -2016-06-20 01:00:00,13284.0 -2016-06-20 02:00:00,12458.0 -2016-06-20 03:00:00,11846.0 -2016-06-20 04:00:00,11518.0 -2016-06-20 05:00:00,11343.0 -2016-06-20 06:00:00,11506.0 -2016-06-20 07:00:00,12021.0 -2016-06-20 08:00:00,13181.0 -2016-06-20 09:00:00,14804.0 -2016-06-20 10:00:00,15976.0 -2016-06-20 11:00:00,16658.0 -2016-06-20 12:00:00,17820.0 -2016-06-20 13:00:00,18698.0 -2016-06-20 14:00:00,18976.0 -2016-06-20 15:00:00,19052.0 -2016-06-20 16:00:00,19488.0 -2016-06-20 17:00:00,19898.0 -2016-06-20 18:00:00,20189.0 -2016-06-20 19:00:00,20040.0 -2016-06-20 20:00:00,19429.0 -2016-06-20 21:00:00,18483.0 -2016-06-20 22:00:00,17367.0 -2016-06-20 23:00:00,16487.0 -2016-06-21 00:00:00,14921.0 -2016-06-19 01:00:00,11338.0 -2016-06-19 02:00:00,10495.0 -2016-06-19 03:00:00,9818.0 -2016-06-19 04:00:00,9352.0 -2016-06-19 05:00:00,9059.0 -2016-06-19 06:00:00,8862.0 -2016-06-19 07:00:00,8639.0 -2016-06-19 08:00:00,8886.0 -2016-06-19 09:00:00,9647.0 -2016-06-19 10:00:00,10791.0 -2016-06-19 11:00:00,12075.0 -2016-06-19 12:00:00,13287.0 -2016-06-19 13:00:00,14190.0 -2016-06-19 14:00:00,14864.0 -2016-06-19 15:00:00,15423.0 -2016-06-19 16:00:00,15923.0 -2016-06-19 17:00:00,16291.0 -2016-06-19 18:00:00,16565.0 -2016-06-19 19:00:00,16613.0 -2016-06-19 20:00:00,16412.0 -2016-06-19 21:00:00,16004.0 -2016-06-19 22:00:00,15477.0 -2016-06-19 23:00:00,15185.0 -2016-06-20 00:00:00,14341.0 -2016-06-18 01:00:00,11133.0 -2016-06-18 02:00:00,10200.0 -2016-06-18 03:00:00,9559.0 -2016-06-18 04:00:00,9106.0 -2016-06-18 05:00:00,8855.0 -2016-06-18 06:00:00,8760.0 -2016-06-18 07:00:00,8694.0 -2016-06-18 08:00:00,9020.0 -2016-06-18 09:00:00,9859.0 -2016-06-18 10:00:00,10791.0 -2016-06-18 11:00:00,11815.0 -2016-06-18 12:00:00,12726.0 -2016-06-18 13:00:00,13549.0 -2016-06-18 14:00:00,14127.0 -2016-06-18 15:00:00,14572.0 -2016-06-18 16:00:00,14877.0 -2016-06-18 17:00:00,15155.0 -2016-06-18 18:00:00,15186.0 -2016-06-18 19:00:00,14973.0 -2016-06-18 20:00:00,14679.0 -2016-06-18 21:00:00,14081.0 -2016-06-18 22:00:00,13483.0 -2016-06-18 23:00:00,13154.0 -2016-06-19 00:00:00,12326.0 -2016-06-17 01:00:00,10481.0 -2016-06-17 02:00:00,9779.0 -2016-06-17 03:00:00,9284.0 -2016-06-17 04:00:00,8994.0 -2016-06-17 05:00:00,8852.0 -2016-06-17 06:00:00,8988.0 -2016-06-17 07:00:00,9306.0 -2016-06-17 08:00:00,10245.0 -2016-06-17 09:00:00,11373.0 -2016-06-17 10:00:00,12303.0 -2016-06-17 11:00:00,13000.0 -2016-06-17 12:00:00,13644.0 -2016-06-17 13:00:00,14127.0 -2016-06-17 14:00:00,14570.0 -2016-06-17 15:00:00,15074.0 -2016-06-17 16:00:00,15494.0 -2016-06-17 17:00:00,15844.0 -2016-06-17 18:00:00,16045.0 -2016-06-17 19:00:00,15900.0 -2016-06-17 20:00:00,15368.0 -2016-06-17 21:00:00,14498.0 -2016-06-17 22:00:00,13787.0 -2016-06-17 23:00:00,13269.0 -2016-06-18 00:00:00,12221.0 -2016-06-16 01:00:00,14745.0 -2016-06-16 02:00:00,13392.0 -2016-06-16 03:00:00,12325.0 -2016-06-16 04:00:00,11553.0 -2016-06-16 05:00:00,11021.0 -2016-06-16 06:00:00,10921.0 -2016-06-16 07:00:00,11157.0 -2016-06-16 08:00:00,12155.0 -2016-06-16 09:00:00,13206.0 -2016-06-16 10:00:00,13611.0 -2016-06-16 11:00:00,13677.0 -2016-06-16 12:00:00,13747.0 -2016-06-16 13:00:00,13887.0 -2016-06-16 14:00:00,14072.0 -2016-06-16 15:00:00,14185.0 -2016-06-16 16:00:00,13920.0 -2016-06-16 17:00:00,13619.0 -2016-06-16 18:00:00,13513.0 -2016-06-16 19:00:00,13316.0 -2016-06-16 20:00:00,12822.0 -2016-06-16 21:00:00,12416.0 -2016-06-16 22:00:00,12380.0 -2016-06-16 23:00:00,12222.0 -2016-06-17 00:00:00,11400.0 -2016-06-15 01:00:00,12932.0 -2016-06-15 02:00:00,11974.0 -2016-06-15 03:00:00,11273.0 -2016-06-15 04:00:00,10815.0 -2016-06-15 05:00:00,10444.0 -2016-06-15 06:00:00,10433.0 -2016-06-15 07:00:00,10929.0 -2016-06-15 08:00:00,11845.0 -2016-06-15 09:00:00,13161.0 -2016-06-15 10:00:00,14445.0 -2016-06-15 11:00:00,15492.0 -2016-06-15 12:00:00,16573.0 -2016-06-15 13:00:00,17439.0 -2016-06-15 14:00:00,18078.0 -2016-06-15 15:00:00,18652.0 -2016-06-15 16:00:00,19025.0 -2016-06-15 17:00:00,19375.0 -2016-06-15 18:00:00,19520.0 -2016-06-15 19:00:00,19434.0 -2016-06-15 20:00:00,18995.0 -2016-06-15 21:00:00,18319.0 -2016-06-15 22:00:00,17774.0 -2016-06-15 23:00:00,17407.0 -2016-06-16 00:00:00,16217.0 -2016-06-14 01:00:00,11599.0 -2016-06-14 02:00:00,10672.0 -2016-06-14 03:00:00,9999.0 -2016-06-14 04:00:00,9609.0 -2016-06-14 05:00:00,9358.0 -2016-06-14 06:00:00,9462.0 -2016-06-14 07:00:00,9925.0 -2016-06-14 08:00:00,10792.0 -2016-06-14 09:00:00,11892.0 -2016-06-14 10:00:00,12896.0 -2016-06-14 11:00:00,13740.0 -2016-06-14 12:00:00,14700.0 -2016-06-14 13:00:00,15615.0 -2016-06-14 14:00:00,16328.0 -2016-06-14 15:00:00,17040.0 -2016-06-14 16:00:00,17424.0 -2016-06-14 17:00:00,17573.0 -2016-06-14 18:00:00,17424.0 -2016-06-14 19:00:00,16716.0 -2016-06-14 20:00:00,16032.0 -2016-06-14 21:00:00,15629.0 -2016-06-14 22:00:00,15479.0 -2016-06-14 23:00:00,15120.0 -2016-06-15 00:00:00,14103.0 -2016-06-13 01:00:00,9057.0 -2016-06-13 02:00:00,8587.0 -2016-06-13 03:00:00,8307.0 -2016-06-13 04:00:00,8160.0 -2016-06-13 05:00:00,8073.0 -2016-06-13 06:00:00,8252.0 -2016-06-13 07:00:00,8665.0 -2016-06-13 08:00:00,9662.0 -2016-06-13 09:00:00,10719.0 -2016-06-13 10:00:00,11567.0 -2016-06-13 11:00:00,12114.0 -2016-06-13 12:00:00,12730.0 -2016-06-13 13:00:00,13205.0 -2016-06-13 14:00:00,13754.0 -2016-06-13 15:00:00,14338.0 -2016-06-13 16:00:00,14808.0 -2016-06-13 17:00:00,15381.0 -2016-06-13 18:00:00,15937.0 -2016-06-13 19:00:00,16166.0 -2016-06-13 20:00:00,15790.0 -2016-06-13 21:00:00,15131.0 -2016-06-13 22:00:00,14653.0 -2016-06-13 23:00:00,14050.0 -2016-06-14 00:00:00,12816.0 -2016-06-12 01:00:00,13993.0 -2016-06-12 02:00:00,12932.0 -2016-06-12 03:00:00,11835.0 -2016-06-12 04:00:00,10767.0 -2016-06-12 05:00:00,10049.0 -2016-06-12 06:00:00,9537.0 -2016-06-12 07:00:00,9049.0 -2016-06-12 08:00:00,8973.0 -2016-06-12 09:00:00,9394.0 -2016-06-12 10:00:00,9888.0 -2016-06-12 11:00:00,10312.0 -2016-06-12 12:00:00,10417.0 -2016-06-12 13:00:00,10393.0 -2016-06-12 14:00:00,10394.0 -2016-06-12 15:00:00,10400.0 -2016-06-12 16:00:00,10419.0 -2016-06-12 17:00:00,10514.0 -2016-06-12 18:00:00,10675.0 -2016-06-12 19:00:00,10665.0 -2016-06-12 20:00:00,10468.0 -2016-06-12 21:00:00,10198.0 -2016-06-12 22:00:00,10134.0 -2016-06-12 23:00:00,10165.0 -2016-06-13 00:00:00,9635.0 -2016-06-11 01:00:00,13518.0 -2016-06-11 02:00:00,12318.0 -2016-06-11 03:00:00,11388.0 -2016-06-11 04:00:00,10752.0 -2016-06-11 05:00:00,10438.0 -2016-06-11 06:00:00,10247.0 -2016-06-11 07:00:00,10180.0 -2016-06-11 08:00:00,10612.0 -2016-06-11 09:00:00,11756.0 -2016-06-11 10:00:00,13077.0 -2016-06-11 11:00:00,14420.0 -2016-06-11 12:00:00,15559.0 -2016-06-11 13:00:00,16381.0 -2016-06-11 14:00:00,16885.0 -2016-06-11 15:00:00,17251.0 -2016-06-11 16:00:00,17496.0 -2016-06-11 17:00:00,17722.0 -2016-06-11 18:00:00,17861.0 -2016-06-11 19:00:00,17760.0 -2016-06-11 20:00:00,17445.0 -2016-06-11 21:00:00,16931.0 -2016-06-11 22:00:00,16456.0 -2016-06-11 23:00:00,16136.0 -2016-06-12 00:00:00,15129.0 -2016-06-10 01:00:00,9922.0 -2016-06-10 02:00:00,9340.0 -2016-06-10 03:00:00,8912.0 -2016-06-10 04:00:00,8676.0 -2016-06-10 05:00:00,8601.0 -2016-06-10 06:00:00,8747.0 -2016-06-10 07:00:00,9206.0 -2016-06-10 08:00:00,10068.0 -2016-06-10 09:00:00,11089.0 -2016-06-10 10:00:00,12139.0 -2016-06-10 11:00:00,13097.0 -2016-06-10 12:00:00,14071.0 -2016-06-10 13:00:00,14938.0 -2016-06-10 14:00:00,15693.0 -2016-06-10 15:00:00,16409.0 -2016-06-10 16:00:00,16977.0 -2016-06-10 17:00:00,17412.0 -2016-06-10 18:00:00,17744.0 -2016-06-10 19:00:00,17790.0 -2016-06-10 20:00:00,17437.0 -2016-06-10 21:00:00,16796.0 -2016-06-10 22:00:00,16281.0 -2016-06-10 23:00:00,15731.0 -2016-06-11 00:00:00,14690.0 -2016-06-09 01:00:00,9336.0 -2016-06-09 02:00:00,8734.0 -2016-06-09 03:00:00,8369.0 -2016-06-09 04:00:00,8126.0 -2016-06-09 05:00:00,8059.0 -2016-06-09 06:00:00,8213.0 -2016-06-09 07:00:00,8588.0 -2016-06-09 08:00:00,9432.0 -2016-06-09 09:00:00,10296.0 -2016-06-09 10:00:00,10870.0 -2016-06-09 11:00:00,11249.0 -2016-06-09 12:00:00,11564.0 -2016-06-09 13:00:00,11762.0 -2016-06-09 14:00:00,11862.0 -2016-06-09 15:00:00,11936.0 -2016-06-09 16:00:00,11868.0 -2016-06-09 17:00:00,11766.0 -2016-06-09 18:00:00,11704.0 -2016-06-09 19:00:00,11617.0 -2016-06-09 20:00:00,11454.0 -2016-06-09 21:00:00,11378.0 -2016-06-09 22:00:00,11621.0 -2016-06-09 23:00:00,11491.0 -2016-06-10 00:00:00,10761.0 -2016-06-08 01:00:00,9272.0 -2016-06-08 02:00:00,8680.0 -2016-06-08 03:00:00,8298.0 -2016-06-08 04:00:00,8077.0 -2016-06-08 05:00:00,7998.0 -2016-06-08 06:00:00,8147.0 -2016-06-08 07:00:00,8464.0 -2016-06-08 08:00:00,9343.0 -2016-06-08 09:00:00,10279.0 -2016-06-08 10:00:00,10908.0 -2016-06-08 11:00:00,11305.0 -2016-06-08 12:00:00,11649.0 -2016-06-08 13:00:00,11853.0 -2016-06-08 14:00:00,11994.0 -2016-06-08 15:00:00,12170.0 -2016-06-08 16:00:00,12207.0 -2016-06-08 17:00:00,12224.0 -2016-06-08 18:00:00,12214.0 -2016-06-08 19:00:00,12106.0 -2016-06-08 20:00:00,11703.0 -2016-06-08 21:00:00,11278.0 -2016-06-08 22:00:00,11157.0 -2016-06-08 23:00:00,10954.0 -2016-06-09 00:00:00,10146.0 -2016-06-07 01:00:00,10232.0 -2016-06-07 02:00:00,9446.0 -2016-06-07 03:00:00,8964.0 -2016-06-07 04:00:00,8642.0 -2016-06-07 05:00:00,8508.0 -2016-06-07 06:00:00,8618.0 -2016-06-07 07:00:00,8946.0 -2016-06-07 08:00:00,9765.0 -2016-06-07 09:00:00,10580.0 -2016-06-07 10:00:00,11104.0 -2016-06-07 11:00:00,11416.0 -2016-06-07 12:00:00,11668.0 -2016-06-07 13:00:00,11828.0 -2016-06-07 14:00:00,11943.0 -2016-06-07 15:00:00,12057.0 -2016-06-07 16:00:00,11998.0 -2016-06-07 17:00:00,11833.0 -2016-06-07 18:00:00,11670.0 -2016-06-07 19:00:00,11456.0 -2016-06-07 20:00:00,11097.0 -2016-06-07 21:00:00,10898.0 -2016-06-07 22:00:00,10953.0 -2016-06-07 23:00:00,10849.0 -2016-06-08 00:00:00,10076.0 -2016-06-06 01:00:00,10054.0 -2016-06-06 02:00:00,9391.0 -2016-06-06 03:00:00,8935.0 -2016-06-06 04:00:00,8650.0 -2016-06-06 05:00:00,8521.0 -2016-06-06 06:00:00,8683.0 -2016-06-06 07:00:00,9107.0 -2016-06-06 08:00:00,10201.0 -2016-06-06 09:00:00,11257.0 -2016-06-06 10:00:00,12193.0 -2016-06-06 11:00:00,12899.0 -2016-06-06 12:00:00,13517.0 -2016-06-06 13:00:00,13999.0 -2016-06-06 14:00:00,14216.0 -2016-06-06 15:00:00,14432.0 -2016-06-06 16:00:00,14523.0 -2016-06-06 17:00:00,14559.0 -2016-06-06 18:00:00,14474.0 -2016-06-06 19:00:00,14201.0 -2016-06-06 20:00:00,13584.0 -2016-06-06 21:00:00,12915.0 -2016-06-06 22:00:00,12611.0 -2016-06-06 23:00:00,12279.0 -2016-06-07 00:00:00,11343.0 -2016-06-05 01:00:00,9838.0 -2016-06-05 02:00:00,9254.0 -2016-06-05 03:00:00,8822.0 -2016-06-05 04:00:00,8538.0 -2016-06-05 05:00:00,8329.0 -2016-06-05 06:00:00,8262.0 -2016-06-05 07:00:00,8062.0 -2016-06-05 08:00:00,8177.0 -2016-06-05 09:00:00,8551.0 -2016-06-05 10:00:00,8998.0 -2016-06-05 11:00:00,9435.0 -2016-06-05 12:00:00,9911.0 -2016-06-05 13:00:00,10234.0 -2016-06-05 14:00:00,10544.0 -2016-06-05 15:00:00,10813.0 -2016-06-05 16:00:00,11116.0 -2016-06-05 17:00:00,11370.0 -2016-06-05 18:00:00,11697.0 -2016-06-05 19:00:00,11933.0 -2016-06-05 20:00:00,11889.0 -2016-06-05 21:00:00,11646.0 -2016-06-05 22:00:00,11700.0 -2016-06-05 23:00:00,11666.0 -2016-06-06 00:00:00,10923.0 -2016-06-04 01:00:00,11251.0 -2016-06-04 02:00:00,10355.0 -2016-06-04 03:00:00,9776.0 -2016-06-04 04:00:00,9298.0 -2016-06-04 05:00:00,9032.0 -2016-06-04 06:00:00,8932.0 -2016-06-04 07:00:00,8954.0 -2016-06-04 08:00:00,9202.0 -2016-06-04 09:00:00,9974.0 -2016-06-04 10:00:00,10634.0 -2016-06-04 11:00:00,11169.0 -2016-06-04 12:00:00,11378.0 -2016-06-04 13:00:00,11508.0 -2016-06-04 14:00:00,11419.0 -2016-06-04 15:00:00,11298.0 -2016-06-04 16:00:00,11114.0 -2016-06-04 17:00:00,11078.0 -2016-06-04 18:00:00,11203.0 -2016-06-04 19:00:00,11313.0 -2016-06-04 20:00:00,11235.0 -2016-06-04 21:00:00,11120.0 -2016-06-04 22:00:00,11177.0 -2016-06-04 23:00:00,11157.0 -2016-06-05 00:00:00,10598.0 -2016-06-03 01:00:00,10490.0 -2016-06-03 02:00:00,9656.0 -2016-06-03 03:00:00,9133.0 -2016-06-03 04:00:00,8786.0 -2016-06-03 05:00:00,8625.0 -2016-06-03 06:00:00,8747.0 -2016-06-03 07:00:00,9035.0 -2016-06-03 08:00:00,9992.0 -2016-06-03 09:00:00,11034.0 -2016-06-03 10:00:00,11852.0 -2016-06-03 11:00:00,12482.0 -2016-06-03 12:00:00,13027.0 -2016-06-03 13:00:00,13484.0 -2016-06-03 14:00:00,13894.0 -2016-06-03 15:00:00,14437.0 -2016-06-03 16:00:00,14818.0 -2016-06-03 17:00:00,15169.0 -2016-06-03 18:00:00,15395.0 -2016-06-03 19:00:00,15252.0 -2016-06-03 20:00:00,14670.0 -2016-06-03 21:00:00,13954.0 -2016-06-03 22:00:00,13642.0 -2016-06-03 23:00:00,13290.0 -2016-06-04 00:00:00,12300.0 -2016-06-02 01:00:00,10884.0 -2016-06-02 02:00:00,9999.0 -2016-06-02 03:00:00,9387.0 -2016-06-02 04:00:00,8970.0 -2016-06-02 05:00:00,8809.0 -2016-06-02 06:00:00,8913.0 -2016-06-02 07:00:00,9271.0 -2016-06-02 08:00:00,10212.0 -2016-06-02 09:00:00,11304.0 -2016-06-02 10:00:00,12103.0 -2016-06-02 11:00:00,12654.0 -2016-06-02 12:00:00,13121.0 -2016-06-02 13:00:00,13450.0 -2016-06-02 14:00:00,13720.0 -2016-06-02 15:00:00,14152.0 -2016-06-02 16:00:00,14438.0 -2016-06-02 17:00:00,14661.0 -2016-06-02 18:00:00,14826.0 -2016-06-02 19:00:00,14713.0 -2016-06-02 20:00:00,14228.0 -2016-06-02 21:00:00,13565.0 -2016-06-02 22:00:00,13109.0 -2016-06-02 23:00:00,12668.0 -2016-06-03 00:00:00,11553.0 -2016-06-01 01:00:00,10301.0 -2016-06-01 02:00:00,9651.0 -2016-06-01 03:00:00,9220.0 -2016-06-01 04:00:00,8981.0 -2016-06-01 05:00:00,8869.0 -2016-06-01 06:00:00,9042.0 -2016-06-01 07:00:00,9566.0 -2016-06-01 08:00:00,10527.0 -2016-06-01 09:00:00,11356.0 -2016-06-01 10:00:00,11825.0 -2016-06-01 11:00:00,12111.0 -2016-06-01 12:00:00,12451.0 -2016-06-01 13:00:00,12827.0 -2016-06-01 14:00:00,13341.0 -2016-06-01 15:00:00,13780.0 -2016-06-01 16:00:00,14164.0 -2016-06-01 17:00:00,14488.0 -2016-06-01 18:00:00,14766.0 -2016-06-01 19:00:00,14772.0 -2016-06-01 20:00:00,14423.0 -2016-06-01 21:00:00,13933.0 -2016-06-01 22:00:00,13553.0 -2016-06-01 23:00:00,13149.0 -2016-06-02 00:00:00,12094.0 -2016-05-31 01:00:00,10556.0 -2016-05-31 02:00:00,9739.0 -2016-05-31 03:00:00,9249.0 -2016-05-31 04:00:00,8900.0 -2016-05-31 05:00:00,8784.0 -2016-05-31 06:00:00,8950.0 -2016-05-31 07:00:00,9405.0 -2016-05-31 08:00:00,10511.0 -2016-05-31 09:00:00,11738.0 -2016-05-31 10:00:00,12605.0 -2016-05-31 11:00:00,13301.0 -2016-05-31 12:00:00,14143.0 -2016-05-31 13:00:00,14740.0 -2016-05-31 14:00:00,15244.0 -2016-05-31 15:00:00,15473.0 -2016-05-31 16:00:00,15205.0 -2016-05-31 17:00:00,14366.0 -2016-05-31 18:00:00,13636.0 -2016-05-31 19:00:00,13214.0 -2016-05-31 20:00:00,12694.0 -2016-05-31 21:00:00,12446.0 -2016-05-31 22:00:00,12508.0 -2016-05-31 23:00:00,12157.0 -2016-06-01 00:00:00,11259.0 -2016-05-30 01:00:00,10082.0 -2016-05-30 02:00:00,9374.0 -2016-05-30 03:00:00,8874.0 -2016-05-30 04:00:00,8514.0 -2016-05-30 05:00:00,8314.0 -2016-05-30 06:00:00,8263.0 -2016-05-30 07:00:00,8181.0 -2016-05-30 08:00:00,8424.0 -2016-05-30 09:00:00,9071.0 -2016-05-30 10:00:00,9902.0 -2016-05-30 11:00:00,10817.0 -2016-05-30 12:00:00,11699.0 -2016-05-30 13:00:00,12377.0 -2016-05-30 14:00:00,12875.0 -2016-05-30 15:00:00,13239.0 -2016-05-30 16:00:00,13544.0 -2016-05-30 17:00:00,13859.0 -2016-05-30 18:00:00,14061.0 -2016-05-30 19:00:00,14096.0 -2016-05-30 20:00:00,13796.0 -2016-05-30 21:00:00,13233.0 -2016-05-30 22:00:00,12980.0 -2016-05-30 23:00:00,12578.0 -2016-05-31 00:00:00,11582.0 -2016-05-29 01:00:00,10595.0 -2016-05-29 02:00:00,9835.0 -2016-05-29 03:00:00,9203.0 -2016-05-29 04:00:00,8804.0 -2016-05-29 05:00:00,8575.0 -2016-05-29 06:00:00,8418.0 -2016-05-29 07:00:00,8258.0 -2016-05-29 08:00:00,8415.0 -2016-05-29 09:00:00,9092.0 -2016-05-29 10:00:00,9921.0 -2016-05-29 11:00:00,10698.0 -2016-05-29 12:00:00,11238.0 -2016-05-29 13:00:00,11493.0 -2016-05-29 14:00:00,11808.0 -2016-05-29 15:00:00,12019.0 -2016-05-29 16:00:00,12298.0 -2016-05-29 17:00:00,12503.0 -2016-05-29 18:00:00,12648.0 -2016-05-29 19:00:00,12653.0 -2016-05-29 20:00:00,12421.0 -2016-05-29 21:00:00,11898.0 -2016-05-29 22:00:00,11676.0 -2016-05-29 23:00:00,11509.0 -2016-05-30 00:00:00,10868.0 -2016-05-28 01:00:00,11108.0 -2016-05-28 02:00:00,10268.0 -2016-05-28 03:00:00,9790.0 -2016-05-28 04:00:00,9390.0 -2016-05-28 05:00:00,9183.0 -2016-05-28 06:00:00,9139.0 -2016-05-28 07:00:00,9183.0 -2016-05-28 08:00:00,9369.0 -2016-05-28 09:00:00,9946.0 -2016-05-28 10:00:00,10700.0 -2016-05-28 11:00:00,11436.0 -2016-05-28 12:00:00,12049.0 -2016-05-28 13:00:00,12275.0 -2016-05-28 14:00:00,12309.0 -2016-05-28 15:00:00,12386.0 -2016-05-28 16:00:00,12751.0 -2016-05-28 17:00:00,13180.0 -2016-05-28 18:00:00,13534.0 -2016-05-28 19:00:00,13657.0 -2016-05-28 20:00:00,13440.0 -2016-05-28 21:00:00,12946.0 -2016-05-28 22:00:00,12786.0 -2016-05-28 23:00:00,12495.0 -2016-05-29 00:00:00,11591.0 -2016-05-27 01:00:00,12095.0 -2016-05-27 02:00:00,11213.0 -2016-05-27 03:00:00,10586.0 -2016-05-27 04:00:00,10134.0 -2016-05-27 05:00:00,9855.0 -2016-05-27 06:00:00,9879.0 -2016-05-27 07:00:00,10279.0 -2016-05-27 08:00:00,11178.0 -2016-05-27 09:00:00,12228.0 -2016-05-27 10:00:00,12956.0 -2016-05-27 11:00:00,13465.0 -2016-05-27 12:00:00,14085.0 -2016-05-27 13:00:00,14617.0 -2016-05-27 14:00:00,15179.0 -2016-05-27 15:00:00,15414.0 -2016-05-27 16:00:00,15353.0 -2016-05-27 17:00:00,14905.0 -2016-05-27 18:00:00,14726.0 -2016-05-27 19:00:00,14356.0 -2016-05-27 20:00:00,13924.0 -2016-05-27 21:00:00,13550.0 -2016-05-27 22:00:00,13484.0 -2016-05-27 23:00:00,13085.0 -2016-05-28 00:00:00,12162.0 -2016-05-26 01:00:00,11032.0 -2016-05-26 02:00:00,10236.0 -2016-05-26 03:00:00,9759.0 -2016-05-26 04:00:00,9423.0 -2016-05-26 05:00:00,9296.0 -2016-05-26 06:00:00,9470.0 -2016-05-26 07:00:00,9951.0 -2016-05-26 08:00:00,11033.0 -2016-05-26 09:00:00,12144.0 -2016-05-26 10:00:00,12811.0 -2016-05-26 11:00:00,13217.0 -2016-05-26 12:00:00,13528.0 -2016-05-26 13:00:00,13875.0 -2016-05-26 14:00:00,14355.0 -2016-05-26 15:00:00,14903.0 -2016-05-26 16:00:00,15278.0 -2016-05-26 17:00:00,15661.0 -2016-05-26 18:00:00,16013.0 -2016-05-26 19:00:00,16044.0 -2016-05-26 20:00:00,15669.0 -2016-05-26 21:00:00,15094.0 -2016-05-26 22:00:00,14930.0 -2016-05-26 23:00:00,14416.0 -2016-05-27 00:00:00,13271.0 -2016-05-25 01:00:00,11488.0 -2016-05-25 02:00:00,10673.0 -2016-05-25 03:00:00,10145.0 -2016-05-25 04:00:00,9732.0 -2016-05-25 05:00:00,9483.0 -2016-05-25 06:00:00,9543.0 -2016-05-25 07:00:00,9919.0 -2016-05-25 08:00:00,10954.0 -2016-05-25 09:00:00,12164.0 -2016-05-25 10:00:00,13068.0 -2016-05-25 11:00:00,13821.0 -2016-05-25 12:00:00,14500.0 -2016-05-25 13:00:00,15041.0 -2016-05-25 14:00:00,15467.0 -2016-05-25 15:00:00,15951.0 -2016-05-25 16:00:00,16228.0 -2016-05-25 17:00:00,16396.0 -2016-05-25 18:00:00,15960.0 -2016-05-25 19:00:00,15167.0 -2016-05-25 20:00:00,14468.0 -2016-05-25 21:00:00,13889.0 -2016-05-25 22:00:00,13685.0 -2016-05-25 23:00:00,13143.0 -2016-05-26 00:00:00,12138.0 -2016-05-24 01:00:00,9769.0 -2016-05-24 02:00:00,9101.0 -2016-05-24 03:00:00,8696.0 -2016-05-24 04:00:00,8413.0 -2016-05-24 05:00:00,8294.0 -2016-05-24 06:00:00,8442.0 -2016-05-24 07:00:00,8793.0 -2016-05-24 08:00:00,9738.0 -2016-05-24 09:00:00,10686.0 -2016-05-24 10:00:00,11433.0 -2016-05-24 11:00:00,12070.0 -2016-05-24 12:00:00,12664.0 -2016-05-24 13:00:00,13069.0 -2016-05-24 14:00:00,13415.0 -2016-05-24 15:00:00,13785.0 -2016-05-24 16:00:00,14051.0 -2016-05-24 17:00:00,14248.0 -2016-05-24 18:00:00,14457.0 -2016-05-24 19:00:00,14442.0 -2016-05-24 20:00:00,14107.0 -2016-05-24 21:00:00,13783.0 -2016-05-24 22:00:00,13853.0 -2016-05-24 23:00:00,13537.0 -2016-05-25 00:00:00,12558.0 -2016-05-23 01:00:00,8625.0 -2016-05-23 02:00:00,8116.0 -2016-05-23 03:00:00,7835.0 -2016-05-23 04:00:00,7644.0 -2016-05-23 05:00:00,7643.0 -2016-05-23 06:00:00,7845.0 -2016-05-23 07:00:00,8363.0 -2016-05-23 08:00:00,9266.0 -2016-05-23 09:00:00,10243.0 -2016-05-23 10:00:00,10805.0 -2016-05-23 11:00:00,11273.0 -2016-05-23 12:00:00,11672.0 -2016-05-23 13:00:00,11919.0 -2016-05-23 14:00:00,12142.0 -2016-05-23 15:00:00,12401.0 -2016-05-23 16:00:00,12553.0 -2016-05-23 17:00:00,12692.0 -2016-05-23 18:00:00,12763.0 -2016-05-23 19:00:00,12662.0 -2016-05-23 20:00:00,12278.0 -2016-05-23 21:00:00,11919.0 -2016-05-23 22:00:00,11953.0 -2016-05-23 23:00:00,11562.0 -2016-05-24 00:00:00,10687.0 -2016-05-22 01:00:00,8510.0 -2016-05-22 02:00:00,8041.0 -2016-05-22 03:00:00,7722.0 -2016-05-22 04:00:00,7534.0 -2016-05-22 05:00:00,7416.0 -2016-05-22 06:00:00,7415.0 -2016-05-22 07:00:00,7290.0 -2016-05-22 08:00:00,7304.0 -2016-05-22 09:00:00,7764.0 -2016-05-22 10:00:00,8262.0 -2016-05-22 11:00:00,8647.0 -2016-05-22 12:00:00,9050.0 -2016-05-22 13:00:00,9263.0 -2016-05-22 14:00:00,9449.0 -2016-05-22 15:00:00,9546.0 -2016-05-22 16:00:00,9674.0 -2016-05-22 17:00:00,9774.0 -2016-05-22 18:00:00,9881.0 -2016-05-22 19:00:00,9966.0 -2016-05-22 20:00:00,9876.0 -2016-05-22 21:00:00,9815.0 -2016-05-22 22:00:00,10040.0 -2016-05-22 23:00:00,9955.0 -2016-05-23 00:00:00,9288.0 -2016-05-21 01:00:00,8826.0 -2016-05-21 02:00:00,8312.0 -2016-05-21 03:00:00,8013.0 -2016-05-21 04:00:00,7796.0 -2016-05-21 05:00:00,7693.0 -2016-05-21 06:00:00,7749.0 -2016-05-21 07:00:00,7836.0 -2016-05-21 08:00:00,8014.0 -2016-05-21 09:00:00,8485.0 -2016-05-21 10:00:00,8989.0 -2016-05-21 11:00:00,9314.0 -2016-05-21 12:00:00,9546.0 -2016-05-21 13:00:00,9632.0 -2016-05-21 14:00:00,9629.0 -2016-05-21 15:00:00,9625.0 -2016-05-21 16:00:00,9593.0 -2016-05-21 17:00:00,9618.0 -2016-05-21 18:00:00,9607.0 -2016-05-21 19:00:00,9599.0 -2016-05-21 20:00:00,9468.0 -2016-05-21 21:00:00,9417.0 -2016-05-21 22:00:00,9661.0 -2016-05-21 23:00:00,9622.0 -2016-05-22 00:00:00,9105.0 -2016-05-20 01:00:00,8900.0 -2016-05-20 02:00:00,8410.0 -2016-05-20 03:00:00,8114.0 -2016-05-20 04:00:00,7942.0 -2016-05-20 05:00:00,7866.0 -2016-05-20 06:00:00,8051.0 -2016-05-20 07:00:00,8514.0 -2016-05-20 08:00:00,9321.0 -2016-05-20 09:00:00,10162.0 -2016-05-20 10:00:00,10567.0 -2016-05-20 11:00:00,10820.0 -2016-05-20 12:00:00,11034.0 -2016-05-20 13:00:00,11077.0 -2016-05-20 14:00:00,11077.0 -2016-05-20 15:00:00,11117.0 -2016-05-20 16:00:00,10973.0 -2016-05-20 17:00:00,10780.0 -2016-05-20 18:00:00,10611.0 -2016-05-20 19:00:00,10402.0 -2016-05-20 20:00:00,10185.0 -2016-05-20 21:00:00,10130.0 -2016-05-20 22:00:00,10412.0 -2016-05-20 23:00:00,10167.0 -2016-05-21 00:00:00,9513.0 -2016-05-19 01:00:00,8926.0 -2016-05-19 02:00:00,8441.0 -2016-05-19 03:00:00,8188.0 -2016-05-19 04:00:00,8026.0 -2016-05-19 05:00:00,8001.0 -2016-05-19 06:00:00,8247.0 -2016-05-19 07:00:00,8729.0 -2016-05-19 08:00:00,9569.0 -2016-05-19 09:00:00,10285.0 -2016-05-19 10:00:00,10636.0 -2016-05-19 11:00:00,10878.0 -2016-05-19 12:00:00,11085.0 -2016-05-19 13:00:00,11184.0 -2016-05-19 14:00:00,11210.0 -2016-05-19 15:00:00,11278.0 -2016-05-19 16:00:00,11233.0 -2016-05-19 17:00:00,11119.0 -2016-05-19 18:00:00,10936.0 -2016-05-19 19:00:00,10779.0 -2016-05-19 20:00:00,10554.0 -2016-05-19 21:00:00,10384.0 -2016-05-19 22:00:00,10643.0 -2016-05-19 23:00:00,10421.0 -2016-05-20 00:00:00,9655.0 -2016-05-18 01:00:00,8924.0 -2016-05-18 02:00:00,8484.0 -2016-05-18 03:00:00,8224.0 -2016-05-18 04:00:00,8074.0 -2016-05-18 05:00:00,8043.0 -2016-05-18 06:00:00,8254.0 -2016-05-18 07:00:00,8835.0 -2016-05-18 08:00:00,9657.0 -2016-05-18 09:00:00,10367.0 -2016-05-18 10:00:00,10623.0 -2016-05-18 11:00:00,10755.0 -2016-05-18 12:00:00,10869.0 -2016-05-18 13:00:00,10944.0 -2016-05-18 14:00:00,10985.0 -2016-05-18 15:00:00,11029.0 -2016-05-18 16:00:00,10904.0 -2016-05-18 17:00:00,10820.0 -2016-05-18 18:00:00,10668.0 -2016-05-18 19:00:00,10511.0 -2016-05-18 20:00:00,10271.0 -2016-05-18 21:00:00,10194.0 -2016-05-18 22:00:00,10546.0 -2016-05-18 23:00:00,10342.0 -2016-05-19 00:00:00,9639.0 -2016-05-17 01:00:00,8848.0 -2016-05-17 02:00:00,8385.0 -2016-05-17 03:00:00,8104.0 -2016-05-17 04:00:00,7918.0 -2016-05-17 05:00:00,7888.0 -2016-05-17 06:00:00,8067.0 -2016-05-17 07:00:00,8633.0 -2016-05-17 08:00:00,9529.0 -2016-05-17 09:00:00,10277.0 -2016-05-17 10:00:00,10622.0 -2016-05-17 11:00:00,10771.0 -2016-05-17 12:00:00,10910.0 -2016-05-17 13:00:00,10950.0 -2016-05-17 14:00:00,10948.0 -2016-05-17 15:00:00,10952.0 -2016-05-17 16:00:00,10840.0 -2016-05-17 17:00:00,10695.0 -2016-05-17 18:00:00,10580.0 -2016-05-17 19:00:00,10414.0 -2016-05-17 20:00:00,10219.0 -2016-05-17 21:00:00,10245.0 -2016-05-17 22:00:00,10629.0 -2016-05-17 23:00:00,10397.0 -2016-05-18 00:00:00,9633.0 -2016-05-16 01:00:00,8359.0 -2016-05-16 02:00:00,8067.0 -2016-05-16 03:00:00,7902.0 -2016-05-16 04:00:00,7879.0 -2016-05-16 05:00:00,7912.0 -2016-05-16 06:00:00,8153.0 -2016-05-16 07:00:00,8737.0 -2016-05-16 08:00:00,9653.0 -2016-05-16 09:00:00,10235.0 -2016-05-16 10:00:00,10552.0 -2016-05-16 11:00:00,10727.0 -2016-05-16 12:00:00,10948.0 -2016-05-16 13:00:00,11014.0 -2016-05-16 14:00:00,11058.0 -2016-05-16 15:00:00,11113.0 -2016-05-16 16:00:00,11042.0 -2016-05-16 17:00:00,10893.0 -2016-05-16 18:00:00,10763.0 -2016-05-16 19:00:00,10611.0 -2016-05-16 20:00:00,10393.0 -2016-05-16 21:00:00,10441.0 -2016-05-16 22:00:00,10791.0 -2016-05-16 23:00:00,10379.0 -2016-05-17 00:00:00,9604.0 -2016-05-15 01:00:00,8648.0 -2016-05-15 02:00:00,8223.0 -2016-05-15 03:00:00,8025.0 -2016-05-15 04:00:00,7840.0 -2016-05-15 05:00:00,7780.0 -2016-05-15 06:00:00,7842.0 -2016-05-15 07:00:00,7795.0 -2016-05-15 08:00:00,7851.0 -2016-05-15 09:00:00,8064.0 -2016-05-15 10:00:00,8355.0 -2016-05-15 11:00:00,8580.0 -2016-05-15 12:00:00,8709.0 -2016-05-15 13:00:00,8753.0 -2016-05-15 14:00:00,8709.0 -2016-05-15 15:00:00,8676.0 -2016-05-15 16:00:00,8589.0 -2016-05-15 17:00:00,8590.0 -2016-05-15 18:00:00,8603.0 -2016-05-15 19:00:00,8686.0 -2016-05-15 20:00:00,8708.0 -2016-05-15 21:00:00,8863.0 -2016-05-15 22:00:00,9383.0 -2016-05-15 23:00:00,9337.0 -2016-05-16 00:00:00,8808.0 -2016-05-14 01:00:00,9008.0 -2016-05-14 02:00:00,8461.0 -2016-05-14 03:00:00,8113.0 -2016-05-14 04:00:00,8010.0 -2016-05-14 05:00:00,7928.0 -2016-05-14 06:00:00,8006.0 -2016-05-14 07:00:00,8111.0 -2016-05-14 08:00:00,8343.0 -2016-05-14 09:00:00,8817.0 -2016-05-14 10:00:00,9218.0 -2016-05-14 11:00:00,9502.0 -2016-05-14 12:00:00,9581.0 -2016-05-14 13:00:00,9615.0 -2016-05-14 14:00:00,9584.0 -2016-05-14 15:00:00,9466.0 -2016-05-14 16:00:00,9364.0 -2016-05-14 17:00:00,9307.0 -2016-05-14 18:00:00,9257.0 -2016-05-14 19:00:00,9262.0 -2016-05-14 20:00:00,9212.0 -2016-05-14 21:00:00,9287.0 -2016-05-14 22:00:00,9675.0 -2016-05-14 23:00:00,9580.0 -2016-05-15 00:00:00,9115.0 -2016-05-13 01:00:00,8949.0 -2016-05-13 02:00:00,8427.0 -2016-05-13 03:00:00,8092.0 -2016-05-13 04:00:00,7927.0 -2016-05-13 05:00:00,7866.0 -2016-05-13 06:00:00,8059.0 -2016-05-13 07:00:00,8512.0 -2016-05-13 08:00:00,9339.0 -2016-05-13 09:00:00,10084.0 -2016-05-13 10:00:00,10497.0 -2016-05-13 11:00:00,10761.0 -2016-05-13 12:00:00,10993.0 -2016-05-13 13:00:00,11048.0 -2016-05-13 14:00:00,11041.0 -2016-05-13 15:00:00,11088.0 -2016-05-13 16:00:00,11019.0 -2016-05-13 17:00:00,10816.0 -2016-05-13 18:00:00,10677.0 -2016-05-13 19:00:00,10548.0 -2016-05-13 20:00:00,10334.0 -2016-05-13 21:00:00,10492.0 -2016-05-13 22:00:00,10660.0 -2016-05-13 23:00:00,10333.0 -2016-05-14 00:00:00,9680.0 -2016-05-12 01:00:00,9088.0 -2016-05-12 02:00:00,8596.0 -2016-05-12 03:00:00,8294.0 -2016-05-12 04:00:00,8155.0 -2016-05-12 05:00:00,8100.0 -2016-05-12 06:00:00,8296.0 -2016-05-12 07:00:00,8855.0 -2016-05-12 08:00:00,9741.0 -2016-05-12 09:00:00,10641.0 -2016-05-12 10:00:00,11137.0 -2016-05-12 11:00:00,11426.0 -2016-05-12 12:00:00,11623.0 -2016-05-12 13:00:00,11610.0 -2016-05-12 14:00:00,11589.0 -2016-05-12 15:00:00,11595.0 -2016-05-12 16:00:00,11500.0 -2016-05-12 17:00:00,11342.0 -2016-05-12 18:00:00,11282.0 -2016-05-12 19:00:00,11094.0 -2016-05-12 20:00:00,10708.0 -2016-05-12 21:00:00,10563.0 -2016-05-12 22:00:00,10856.0 -2016-05-12 23:00:00,10506.0 -2016-05-13 00:00:00,9732.0 -2016-05-11 01:00:00,9055.0 -2016-05-11 02:00:00,8537.0 -2016-05-11 03:00:00,8217.0 -2016-05-11 04:00:00,8051.0 -2016-05-11 05:00:00,7990.0 -2016-05-11 06:00:00,8166.0 -2016-05-11 07:00:00,8760.0 -2016-05-11 08:00:00,9594.0 -2016-05-11 09:00:00,10338.0 -2016-05-11 10:00:00,10692.0 -2016-05-11 11:00:00,10911.0 -2016-05-11 12:00:00,11121.0 -2016-05-11 13:00:00,11203.0 -2016-05-11 14:00:00,11263.0 -2016-05-11 15:00:00,11341.0 -2016-05-11 16:00:00,11333.0 -2016-05-11 17:00:00,11251.0 -2016-05-11 18:00:00,11128.0 -2016-05-11 19:00:00,11001.0 -2016-05-11 20:00:00,10717.0 -2016-05-11 21:00:00,10716.0 -2016-05-11 22:00:00,11058.0 -2016-05-11 23:00:00,10645.0 -2016-05-12 00:00:00,9844.0 -2016-05-10 01:00:00,8923.0 -2016-05-10 02:00:00,8477.0 -2016-05-10 03:00:00,8212.0 -2016-05-10 04:00:00,8045.0 -2016-05-10 05:00:00,7977.0 -2016-05-10 06:00:00,8187.0 -2016-05-10 07:00:00,8826.0 -2016-05-10 08:00:00,9787.0 -2016-05-10 09:00:00,10543.0 -2016-05-10 10:00:00,10886.0 -2016-05-10 11:00:00,11073.0 -2016-05-10 12:00:00,11258.0 -2016-05-10 13:00:00,11321.0 -2016-05-10 14:00:00,11271.0 -2016-05-10 15:00:00,11231.0 -2016-05-10 16:00:00,11160.0 -2016-05-10 17:00:00,11044.0 -2016-05-10 18:00:00,10991.0 -2016-05-10 19:00:00,10900.0 -2016-05-10 20:00:00,10655.0 -2016-05-10 21:00:00,10628.0 -2016-05-10 22:00:00,10928.0 -2016-05-10 23:00:00,10543.0 -2016-05-11 00:00:00,9785.0 -2016-05-09 01:00:00,8193.0 -2016-05-09 02:00:00,7851.0 -2016-05-09 03:00:00,7672.0 -2016-05-09 04:00:00,7584.0 -2016-05-09 05:00:00,7581.0 -2016-05-09 06:00:00,7825.0 -2016-05-09 07:00:00,8441.0 -2016-05-09 08:00:00,9350.0 -2016-05-09 09:00:00,10154.0 -2016-05-09 10:00:00,10558.0 -2016-05-09 11:00:00,10749.0 -2016-05-09 12:00:00,10935.0 -2016-05-09 13:00:00,10985.0 -2016-05-09 14:00:00,10946.0 -2016-05-09 15:00:00,10961.0 -2016-05-09 16:00:00,10833.0 -2016-05-09 17:00:00,10691.0 -2016-05-09 18:00:00,10644.0 -2016-05-09 19:00:00,10659.0 -2016-05-09 20:00:00,10668.0 -2016-05-09 21:00:00,10840.0 -2016-05-09 22:00:00,10854.0 -2016-05-09 23:00:00,10419.0 -2016-05-10 00:00:00,9659.0 -2016-05-08 01:00:00,8267.0 -2016-05-08 02:00:00,7872.0 -2016-05-08 03:00:00,7592.0 -2016-05-08 04:00:00,7392.0 -2016-05-08 05:00:00,7383.0 -2016-05-08 06:00:00,7366.0 -2016-05-08 07:00:00,7421.0 -2016-05-08 08:00:00,7372.0 -2016-05-08 09:00:00,7652.0 -2016-05-08 10:00:00,8026.0 -2016-05-08 11:00:00,8293.0 -2016-05-08 12:00:00,8476.0 -2016-05-08 13:00:00,8569.0 -2016-05-08 14:00:00,8619.0 -2016-05-08 15:00:00,8586.0 -2016-05-08 16:00:00,8613.0 -2016-05-08 17:00:00,8578.0 -2016-05-08 18:00:00,8648.0 -2016-05-08 19:00:00,8581.0 -2016-05-08 20:00:00,8617.0 -2016-05-08 21:00:00,8771.0 -2016-05-08 22:00:00,9296.0 -2016-05-08 23:00:00,9154.0 -2016-05-09 00:00:00,8718.0 -2016-05-07 01:00:00,9460.0 -2016-05-07 02:00:00,8856.0 -2016-05-07 03:00:00,8457.0 -2016-05-07 04:00:00,8259.0 -2016-05-07 05:00:00,8071.0 -2016-05-07 06:00:00,8102.0 -2016-05-07 07:00:00,8203.0 -2016-05-07 08:00:00,8357.0 -2016-05-07 09:00:00,8808.0 -2016-05-07 10:00:00,9223.0 -2016-05-07 11:00:00,9393.0 -2016-05-07 12:00:00,9472.0 -2016-05-07 13:00:00,9457.0 -2016-05-07 14:00:00,9319.0 -2016-05-07 15:00:00,9186.0 -2016-05-07 16:00:00,9078.0 -2016-05-07 17:00:00,9011.0 -2016-05-07 18:00:00,8926.0 -2016-05-07 19:00:00,8892.0 -2016-05-07 20:00:00,8816.0 -2016-05-07 21:00:00,8942.0 -2016-05-07 22:00:00,9364.0 -2016-05-07 23:00:00,9222.0 -2016-05-08 00:00:00,8808.0 -2016-05-06 01:00:00,8911.0 -2016-05-06 02:00:00,8472.0 -2016-05-06 03:00:00,8179.0 -2016-05-06 04:00:00,8045.0 -2016-05-06 05:00:00,8013.0 -2016-05-06 06:00:00,8214.0 -2016-05-06 07:00:00,8818.0 -2016-05-06 08:00:00,9587.0 -2016-05-06 09:00:00,10265.0 -2016-05-06 10:00:00,10571.0 -2016-05-06 11:00:00,10804.0 -2016-05-06 12:00:00,11031.0 -2016-05-06 13:00:00,11133.0 -2016-05-06 14:00:00,11217.0 -2016-05-06 15:00:00,11363.0 -2016-05-06 16:00:00,11412.0 -2016-05-06 17:00:00,11435.0 -2016-05-06 18:00:00,11451.0 -2016-05-06 19:00:00,11357.0 -2016-05-06 20:00:00,11180.0 -2016-05-06 21:00:00,11023.0 -2016-05-06 22:00:00,11290.0 -2016-05-06 23:00:00,10968.0 -2016-05-07 00:00:00,10222.0 -2016-05-05 01:00:00,9177.0 -2016-05-05 02:00:00,8748.0 -2016-05-05 03:00:00,8514.0 -2016-05-05 04:00:00,8342.0 -2016-05-05 05:00:00,8319.0 -2016-05-05 06:00:00,8542.0 -2016-05-05 07:00:00,9185.0 -2016-05-05 08:00:00,10048.0 -2016-05-05 09:00:00,10647.0 -2016-05-05 10:00:00,10832.0 -2016-05-05 11:00:00,10893.0 -2016-05-05 12:00:00,10963.0 -2016-05-05 13:00:00,10944.0 -2016-05-05 14:00:00,10943.0 -2016-05-05 15:00:00,10964.0 -2016-05-05 16:00:00,10856.0 -2016-05-05 17:00:00,10724.0 -2016-05-05 18:00:00,10575.0 -2016-05-05 19:00:00,10404.0 -2016-05-05 20:00:00,10134.0 -2016-05-05 21:00:00,10183.0 -2016-05-05 22:00:00,10627.0 -2016-05-05 23:00:00,10347.0 -2016-05-06 00:00:00,9621.0 -2016-05-04 01:00:00,8950.0 -2016-05-04 02:00:00,8449.0 -2016-05-04 03:00:00,8231.0 -2016-05-04 04:00:00,8053.0 -2016-05-04 05:00:00,8000.0 -2016-05-04 06:00:00,8212.0 -2016-05-04 07:00:00,8847.0 -2016-05-04 08:00:00,9685.0 -2016-05-04 09:00:00,10359.0 -2016-05-04 10:00:00,10656.0 -2016-05-04 11:00:00,10791.0 -2016-05-04 12:00:00,10961.0 -2016-05-04 13:00:00,10951.0 -2016-05-04 14:00:00,11006.0 -2016-05-04 15:00:00,11043.0 -2016-05-04 16:00:00,10927.0 -2016-05-04 17:00:00,10813.0 -2016-05-04 18:00:00,10683.0 -2016-05-04 19:00:00,10623.0 -2016-05-04 20:00:00,10509.0 -2016-05-04 21:00:00,10653.0 -2016-05-04 22:00:00,10933.0 -2016-05-04 23:00:00,10593.0 -2016-05-05 00:00:00,9863.0 -2016-05-03 01:00:00,8963.0 -2016-05-03 02:00:00,8525.0 -2016-05-03 03:00:00,8279.0 -2016-05-03 04:00:00,8179.0 -2016-05-03 05:00:00,8151.0 -2016-05-03 06:00:00,8427.0 -2016-05-03 07:00:00,9038.0 -2016-05-03 08:00:00,9859.0 -2016-05-03 09:00:00,10461.0 -2016-05-03 10:00:00,10652.0 -2016-05-03 11:00:00,10755.0 -2016-05-03 12:00:00,10927.0 -2016-05-03 13:00:00,10971.0 -2016-05-03 14:00:00,11023.0 -2016-05-03 15:00:00,11021.0 -2016-05-03 16:00:00,10920.0 -2016-05-03 17:00:00,10788.0 -2016-05-03 18:00:00,10672.0 -2016-05-03 19:00:00,10517.0 -2016-05-03 20:00:00,10334.0 -2016-05-03 21:00:00,10484.0 -2016-05-03 22:00:00,10833.0 -2016-05-03 23:00:00,10464.0 -2016-05-04 00:00:00,9695.0 -2016-05-02 01:00:00,8647.0 -2016-05-02 02:00:00,8305.0 -2016-05-02 03:00:00,8146.0 -2016-05-02 04:00:00,8029.0 -2016-05-02 05:00:00,8097.0 -2016-05-02 06:00:00,8318.0 -2016-05-02 07:00:00,9056.0 -2016-05-02 08:00:00,9897.0 -2016-05-02 09:00:00,10513.0 -2016-05-02 10:00:00,10693.0 -2016-05-02 11:00:00,10771.0 -2016-05-02 12:00:00,10850.0 -2016-05-02 13:00:00,10822.0 -2016-05-02 14:00:00,10774.0 -2016-05-02 15:00:00,10793.0 -2016-05-02 16:00:00,10687.0 -2016-05-02 17:00:00,10510.0 -2016-05-02 18:00:00,10379.0 -2016-05-02 19:00:00,10278.0 -2016-05-02 20:00:00,10156.0 -2016-05-02 21:00:00,10228.0 -2016-05-02 22:00:00,10705.0 -2016-05-02 23:00:00,10372.0 -2016-05-03 00:00:00,9679.0 -2016-05-01 01:00:00,8906.0 -2016-05-01 02:00:00,8473.0 -2016-05-01 03:00:00,8170.0 -2016-05-01 04:00:00,7999.0 -2016-05-01 05:00:00,7893.0 -2016-05-01 06:00:00,7902.0 -2016-05-01 07:00:00,8004.0 -2016-05-01 08:00:00,8070.0 -2016-05-01 09:00:00,8309.0 -2016-05-01 10:00:00,8667.0 -2016-05-01 11:00:00,8959.0 -2016-05-01 12:00:00,9087.0 -2016-05-01 13:00:00,9147.0 -2016-05-01 14:00:00,9081.0 -2016-05-01 15:00:00,9011.0 -2016-05-01 16:00:00,8923.0 -2016-05-01 17:00:00,8965.0 -2016-05-01 18:00:00,9029.0 -2016-05-01 19:00:00,9165.0 -2016-05-01 20:00:00,9368.0 -2016-05-01 21:00:00,9814.0 -2016-05-01 22:00:00,9925.0 -2016-05-01 23:00:00,9648.0 -2016-05-02 00:00:00,9157.0 -2016-04-30 01:00:00,9196.0 -2016-04-30 02:00:00,8627.0 -2016-04-30 03:00:00,8393.0 -2016-04-30 04:00:00,8197.0 -2016-04-30 05:00:00,8116.0 -2016-04-30 06:00:00,8160.0 -2016-04-30 07:00:00,8398.0 -2016-04-30 08:00:00,8628.0 -2016-04-30 09:00:00,8984.0 -2016-04-30 10:00:00,9455.0 -2016-04-30 11:00:00,9765.0 -2016-04-30 12:00:00,9931.0 -2016-04-30 13:00:00,9974.0 -2016-04-30 14:00:00,9965.0 -2016-04-30 15:00:00,9843.0 -2016-04-30 16:00:00,9798.0 -2016-04-30 17:00:00,9800.0 -2016-04-30 18:00:00,9774.0 -2016-04-30 19:00:00,9794.0 -2016-04-30 20:00:00,9857.0 -2016-04-30 21:00:00,10013.0 -2016-04-30 22:00:00,10174.0 -2016-04-30 23:00:00,9897.0 -2016-05-01 00:00:00,9429.0 -2016-04-29 01:00:00,9189.0 -2016-04-29 02:00:00,8706.0 -2016-04-29 03:00:00,8427.0 -2016-04-29 04:00:00,8267.0 -2016-04-29 05:00:00,8234.0 -2016-04-29 06:00:00,8457.0 -2016-04-29 07:00:00,9052.0 -2016-04-29 08:00:00,9929.0 -2016-04-29 09:00:00,10669.0 -2016-04-29 10:00:00,10965.0 -2016-04-29 11:00:00,11140.0 -2016-04-29 12:00:00,11180.0 -2016-04-29 13:00:00,11078.0 -2016-04-29 14:00:00,10967.0 -2016-04-29 15:00:00,10900.0 -2016-04-29 16:00:00,10720.0 -2016-04-29 17:00:00,10526.0 -2016-04-29 18:00:00,10342.0 -2016-04-29 19:00:00,10216.0 -2016-04-29 20:00:00,10180.0 -2016-04-29 21:00:00,10431.0 -2016-04-29 22:00:00,10683.0 -2016-04-29 23:00:00,10441.0 -2016-04-30 00:00:00,9802.0 -2016-04-28 01:00:00,9236.0 -2016-04-28 02:00:00,8788.0 -2016-04-28 03:00:00,8559.0 -2016-04-28 04:00:00,8404.0 -2016-04-28 05:00:00,8384.0 -2016-04-28 06:00:00,8641.0 -2016-04-28 07:00:00,9296.0 -2016-04-28 08:00:00,10160.0 -2016-04-28 09:00:00,10787.0 -2016-04-28 10:00:00,11095.0 -2016-04-28 11:00:00,11197.0 -2016-04-28 12:00:00,11249.0 -2016-04-28 13:00:00,11212.0 -2016-04-28 14:00:00,11110.0 -2016-04-28 15:00:00,11057.0 -2016-04-28 16:00:00,10941.0 -2016-04-28 17:00:00,10771.0 -2016-04-28 18:00:00,10687.0 -2016-04-28 19:00:00,10632.0 -2016-04-28 20:00:00,10566.0 -2016-04-28 21:00:00,10818.0 -2016-04-28 22:00:00,10985.0 -2016-04-28 23:00:00,10576.0 -2016-04-29 00:00:00,9874.0 -2016-04-27 01:00:00,9044.0 -2016-04-27 02:00:00,8604.0 -2016-04-27 03:00:00,8345.0 -2016-04-27 04:00:00,8182.0 -2016-04-27 05:00:00,8160.0 -2016-04-27 06:00:00,8392.0 -2016-04-27 07:00:00,9099.0 -2016-04-27 08:00:00,9989.0 -2016-04-27 09:00:00,10681.0 -2016-04-27 10:00:00,10959.0 -2016-04-27 11:00:00,11087.0 -2016-04-27 12:00:00,11196.0 -2016-04-27 13:00:00,11146.0 -2016-04-27 14:00:00,11099.0 -2016-04-27 15:00:00,11093.0 -2016-04-27 16:00:00,11053.0 -2016-04-27 17:00:00,11071.0 -2016-04-27 18:00:00,11046.0 -2016-04-27 19:00:00,11092.0 -2016-04-27 20:00:00,11050.0 -2016-04-27 21:00:00,11124.0 -2016-04-27 22:00:00,11187.0 -2016-04-27 23:00:00,10730.0 -2016-04-28 00:00:00,9944.0 -2016-04-26 01:00:00,9874.0 -2016-04-26 02:00:00,9160.0 -2016-04-26 03:00:00,8586.0 -2016-04-26 04:00:00,8261.0 -2016-04-26 05:00:00,8045.0 -2016-04-26 06:00:00,8167.0 -2016-04-26 07:00:00,8747.0 -2016-04-26 08:00:00,9538.0 -2016-04-26 09:00:00,10217.0 -2016-04-26 10:00:00,10584.0 -2016-04-26 11:00:00,10706.0 -2016-04-26 12:00:00,10805.0 -2016-04-26 13:00:00,10821.0 -2016-04-26 14:00:00,10784.0 -2016-04-26 15:00:00,10829.0 -2016-04-26 16:00:00,10737.0 -2016-04-26 17:00:00,10618.0 -2016-04-26 18:00:00,10552.0 -2016-04-26 19:00:00,10566.0 -2016-04-26 20:00:00,10503.0 -2016-04-26 21:00:00,10743.0 -2016-04-26 22:00:00,10932.0 -2016-04-26 23:00:00,10524.0 -2016-04-27 00:00:00,9735.0 -2016-04-25 01:00:00,8491.0 -2016-04-25 02:00:00,8106.0 -2016-04-25 03:00:00,7874.0 -2016-04-25 04:00:00,7694.0 -2016-04-25 05:00:00,7725.0 -2016-04-25 06:00:00,7910.0 -2016-04-25 07:00:00,8598.0 -2016-04-25 08:00:00,9416.0 -2016-04-25 09:00:00,10285.0 -2016-04-25 10:00:00,10812.0 -2016-04-25 11:00:00,11092.0 -2016-04-25 12:00:00,11466.0 -2016-04-25 13:00:00,11728.0 -2016-04-25 14:00:00,11927.0 -2016-04-25 15:00:00,12156.0 -2016-04-25 16:00:00,12232.0 -2016-04-25 17:00:00,12300.0 -2016-04-25 18:00:00,12298.0 -2016-04-25 19:00:00,12134.0 -2016-04-25 20:00:00,11822.0 -2016-04-25 21:00:00,11810.0 -2016-04-25 22:00:00,12088.0 -2016-04-25 23:00:00,11600.0 -2016-04-26 00:00:00,10747.0 -2016-04-24 01:00:00,8393.0 -2016-04-24 02:00:00,7996.0 -2016-04-24 03:00:00,7750.0 -2016-04-24 04:00:00,7601.0 -2016-04-24 05:00:00,7554.0 -2016-04-24 06:00:00,7519.0 -2016-04-24 07:00:00,7667.0 -2016-04-24 08:00:00,7562.0 -2016-04-24 09:00:00,7772.0 -2016-04-24 10:00:00,8133.0 -2016-04-24 11:00:00,8382.0 -2016-04-24 12:00:00,8584.0 -2016-04-24 13:00:00,8706.0 -2016-04-24 14:00:00,8831.0 -2016-04-24 15:00:00,8906.0 -2016-04-24 16:00:00,9053.0 -2016-04-24 17:00:00,9067.0 -2016-04-24 18:00:00,9116.0 -2016-04-24 19:00:00,9169.0 -2016-04-24 20:00:00,9183.0 -2016-04-24 21:00:00,9474.0 -2016-04-24 22:00:00,9868.0 -2016-04-24 23:00:00,9587.0 -2016-04-25 00:00:00,9091.0 -2016-04-23 01:00:00,9016.0 -2016-04-23 02:00:00,8509.0 -2016-04-23 03:00:00,8181.0 -2016-04-23 04:00:00,8008.0 -2016-04-23 05:00:00,7999.0 -2016-04-23 06:00:00,8043.0 -2016-04-23 07:00:00,8288.0 -2016-04-23 08:00:00,8449.0 -2016-04-23 09:00:00,8782.0 -2016-04-23 10:00:00,9076.0 -2016-04-23 11:00:00,9288.0 -2016-04-23 12:00:00,9349.0 -2016-04-23 13:00:00,9341.0 -2016-04-23 14:00:00,9256.0 -2016-04-23 15:00:00,9114.0 -2016-04-23 16:00:00,9005.0 -2016-04-23 17:00:00,8924.0 -2016-04-23 18:00:00,8911.0 -2016-04-23 19:00:00,8892.0 -2016-04-23 20:00:00,8872.0 -2016-04-23 21:00:00,9090.0 -2016-04-23 22:00:00,9504.0 -2016-04-23 23:00:00,9247.0 -2016-04-24 00:00:00,8852.0 -2016-04-22 01:00:00,8965.0 -2016-04-22 02:00:00,8454.0 -2016-04-22 03:00:00,8141.0 -2016-04-22 04:00:00,7952.0 -2016-04-22 05:00:00,7869.0 -2016-04-22 06:00:00,8026.0 -2016-04-22 07:00:00,8625.0 -2016-04-22 08:00:00,9444.0 -2016-04-22 09:00:00,10156.0 -2016-04-22 10:00:00,10578.0 -2016-04-22 11:00:00,10805.0 -2016-04-22 12:00:00,10895.0 -2016-04-22 13:00:00,10834.0 -2016-04-22 14:00:00,10754.0 -2016-04-22 15:00:00,10716.0 -2016-04-22 16:00:00,10598.0 -2016-04-22 17:00:00,10404.0 -2016-04-22 18:00:00,10259.0 -2016-04-22 19:00:00,10086.0 -2016-04-22 20:00:00,9924.0 -2016-04-22 21:00:00,10092.0 -2016-04-22 22:00:00,10492.0 -2016-04-22 23:00:00,10229.0 -2016-04-23 00:00:00,9627.0 -2016-04-21 01:00:00,9014.0 -2016-04-21 02:00:00,8514.0 -2016-04-21 03:00:00,8221.0 -2016-04-21 04:00:00,8036.0 -2016-04-21 05:00:00,7966.0 -2016-04-21 06:00:00,8167.0 -2016-04-21 07:00:00,8805.0 -2016-04-21 08:00:00,9688.0 -2016-04-21 09:00:00,10405.0 -2016-04-21 10:00:00,10795.0 -2016-04-21 11:00:00,11036.0 -2016-04-21 12:00:00,11264.0 -2016-04-21 13:00:00,11394.0 -2016-04-21 14:00:00,11405.0 -2016-04-21 15:00:00,11442.0 -2016-04-21 16:00:00,11359.0 -2016-04-21 17:00:00,11217.0 -2016-04-21 18:00:00,11113.0 -2016-04-21 19:00:00,10924.0 -2016-04-21 20:00:00,10647.0 -2016-04-21 21:00:00,10664.0 -2016-04-21 22:00:00,10848.0 -2016-04-21 23:00:00,10400.0 -2016-04-22 00:00:00,9696.0 -2016-04-20 01:00:00,8877.0 -2016-04-20 02:00:00,8423.0 -2016-04-20 03:00:00,8158.0 -2016-04-20 04:00:00,7992.0 -2016-04-20 05:00:00,7952.0 -2016-04-20 06:00:00,8209.0 -2016-04-20 07:00:00,8883.0 -2016-04-20 08:00:00,9619.0 -2016-04-20 09:00:00,10250.0 -2016-04-20 10:00:00,10609.0 -2016-04-20 11:00:00,10762.0 -2016-04-20 12:00:00,10934.0 -2016-04-20 13:00:00,11027.0 -2016-04-20 14:00:00,11082.0 -2016-04-20 15:00:00,11181.0 -2016-04-20 16:00:00,11128.0 -2016-04-20 17:00:00,10980.0 -2016-04-20 18:00:00,10823.0 -2016-04-20 19:00:00,10747.0 -2016-04-20 20:00:00,10624.0 -2016-04-20 21:00:00,10949.0 -2016-04-20 22:00:00,10971.0 -2016-04-20 23:00:00,10494.0 -2016-04-21 00:00:00,9707.0 -2016-04-19 01:00:00,9139.0 -2016-04-19 02:00:00,8559.0 -2016-04-19 03:00:00,8220.0 -2016-04-19 04:00:00,7999.0 -2016-04-19 05:00:00,7914.0 -2016-04-19 06:00:00,8072.0 -2016-04-19 07:00:00,8666.0 -2016-04-19 08:00:00,9476.0 -2016-04-19 09:00:00,10142.0 -2016-04-19 10:00:00,10506.0 -2016-04-19 11:00:00,10677.0 -2016-04-19 12:00:00,10798.0 -2016-04-19 13:00:00,10852.0 -2016-04-19 14:00:00,10842.0 -2016-04-19 15:00:00,10849.0 -2016-04-19 16:00:00,10784.0 -2016-04-19 17:00:00,10666.0 -2016-04-19 18:00:00,10556.0 -2016-04-19 19:00:00,10461.0 -2016-04-19 20:00:00,10444.0 -2016-04-19 21:00:00,10682.0 -2016-04-19 22:00:00,10765.0 -2016-04-19 23:00:00,10336.0 -2016-04-20 00:00:00,9606.0 -2016-04-18 01:00:00,8367.0 -2016-04-18 02:00:00,7956.0 -2016-04-18 03:00:00,7728.0 -2016-04-18 04:00:00,7578.0 -2016-04-18 05:00:00,7568.0 -2016-04-18 06:00:00,7797.0 -2016-04-18 07:00:00,8466.0 -2016-04-18 08:00:00,9278.0 -2016-04-18 09:00:00,10076.0 -2016-04-18 10:00:00,10581.0 -2016-04-18 11:00:00,10938.0 -2016-04-18 12:00:00,11336.0 -2016-04-18 13:00:00,11547.0 -2016-04-18 14:00:00,11746.0 -2016-04-18 15:00:00,11939.0 -2016-04-18 16:00:00,12003.0 -2016-04-18 17:00:00,11974.0 -2016-04-18 18:00:00,11871.0 -2016-04-18 19:00:00,11620.0 -2016-04-18 20:00:00,11228.0 -2016-04-18 21:00:00,11307.0 -2016-04-18 22:00:00,11403.0 -2016-04-18 23:00:00,10871.0 -2016-04-19 00:00:00,9987.0 -2016-04-17 01:00:00,8423.0 -2016-04-17 02:00:00,7962.0 -2016-04-17 03:00:00,7693.0 -2016-04-17 04:00:00,7558.0 -2016-04-17 05:00:00,7426.0 -2016-04-17 06:00:00,7436.0 -2016-04-17 07:00:00,7507.0 -2016-04-17 08:00:00,7502.0 -2016-04-17 09:00:00,7697.0 -2016-04-17 10:00:00,8098.0 -2016-04-17 11:00:00,8413.0 -2016-04-17 12:00:00,8696.0 -2016-04-17 13:00:00,8925.0 -2016-04-17 14:00:00,9053.0 -2016-04-17 15:00:00,9156.0 -2016-04-17 16:00:00,9247.0 -2016-04-17 17:00:00,9301.0 -2016-04-17 18:00:00,9378.0 -2016-04-17 19:00:00,9441.0 -2016-04-17 20:00:00,9368.0 -2016-04-17 21:00:00,9580.0 -2016-04-17 22:00:00,9895.0 -2016-04-17 23:00:00,9506.0 -2016-04-18 00:00:00,8960.0 -2016-04-16 01:00:00,8969.0 -2016-04-16 02:00:00,8472.0 -2016-04-16 03:00:00,8192.0 -2016-04-16 04:00:00,7966.0 -2016-04-16 05:00:00,7945.0 -2016-04-16 06:00:00,8003.0 -2016-04-16 07:00:00,8253.0 -2016-04-16 08:00:00,8414.0 -2016-04-16 09:00:00,8665.0 -2016-04-16 10:00:00,9001.0 -2016-04-16 11:00:00,9270.0 -2016-04-16 12:00:00,9442.0 -2016-04-16 13:00:00,9499.0 -2016-04-16 14:00:00,9501.0 -2016-04-16 15:00:00,9433.0 -2016-04-16 16:00:00,9348.0 -2016-04-16 17:00:00,9311.0 -2016-04-16 18:00:00,9297.0 -2016-04-16 19:00:00,9266.0 -2016-04-16 20:00:00,9159.0 -2016-04-16 21:00:00,9327.0 -2016-04-16 22:00:00,9623.0 -2016-04-16 23:00:00,9456.0 -2016-04-17 00:00:00,8945.0 -2016-04-15 01:00:00,9160.0 -2016-04-15 02:00:00,8708.0 -2016-04-15 03:00:00,8448.0 -2016-04-15 04:00:00,8315.0 -2016-04-15 05:00:00,8312.0 -2016-04-15 06:00:00,8557.0 -2016-04-15 07:00:00,9255.0 -2016-04-15 08:00:00,10028.0 -2016-04-15 09:00:00,10525.0 -2016-04-15 10:00:00,10724.0 -2016-04-15 11:00:00,10857.0 -2016-04-15 12:00:00,10931.0 -2016-04-15 13:00:00,10933.0 -2016-04-15 14:00:00,10939.0 -2016-04-15 15:00:00,10971.0 -2016-04-15 16:00:00,10879.0 -2016-04-15 17:00:00,10713.0 -2016-04-15 18:00:00,10549.0 -2016-04-15 19:00:00,10354.0 -2016-04-15 20:00:00,10149.0 -2016-04-15 21:00:00,10299.0 -2016-04-15 22:00:00,10549.0 -2016-04-15 23:00:00,10251.0 -2016-04-16 00:00:00,9615.0 -2016-04-14 01:00:00,9435.0 -2016-04-14 02:00:00,9002.0 -2016-04-14 03:00:00,8732.0 -2016-04-14 04:00:00,8587.0 -2016-04-14 05:00:00,8600.0 -2016-04-14 06:00:00,8850.0 -2016-04-14 07:00:00,9589.0 -2016-04-14 08:00:00,10435.0 -2016-04-14 09:00:00,10920.0 -2016-04-14 10:00:00,11062.0 -2016-04-14 11:00:00,11053.0 -2016-04-14 12:00:00,11088.0 -2016-04-14 13:00:00,11059.0 -2016-04-14 14:00:00,11009.0 -2016-04-14 15:00:00,11021.0 -2016-04-14 16:00:00,10874.0 -2016-04-14 17:00:00,10712.0 -2016-04-14 18:00:00,10582.0 -2016-04-14 19:00:00,10465.0 -2016-04-14 20:00:00,10285.0 -2016-04-14 21:00:00,10605.0 -2016-04-14 22:00:00,10909.0 -2016-04-14 23:00:00,10534.0 -2016-04-15 00:00:00,9849.0 -2016-04-13 01:00:00,9570.0 -2016-04-13 02:00:00,9148.0 -2016-04-13 03:00:00,8909.0 -2016-04-13 04:00:00,8776.0 -2016-04-13 05:00:00,8780.0 -2016-04-13 06:00:00,9037.0 -2016-04-13 07:00:00,9777.0 -2016-04-13 08:00:00,10698.0 -2016-04-13 09:00:00,11126.0 -2016-04-13 10:00:00,11282.0 -2016-04-13 11:00:00,11249.0 -2016-04-13 12:00:00,11218.0 -2016-04-13 13:00:00,11130.0 -2016-04-13 14:00:00,11036.0 -2016-04-13 15:00:00,11034.0 -2016-04-13 16:00:00,10949.0 -2016-04-13 17:00:00,10818.0 -2016-04-13 18:00:00,10742.0 -2016-04-13 19:00:00,10661.0 -2016-04-13 20:00:00,10538.0 -2016-04-13 21:00:00,10872.0 -2016-04-13 22:00:00,11179.0 -2016-04-13 23:00:00,10783.0 -2016-04-14 00:00:00,10093.0 -2016-04-12 01:00:00,9466.0 -2016-04-12 02:00:00,9087.0 -2016-04-12 03:00:00,8853.0 -2016-04-12 04:00:00,8727.0 -2016-04-12 05:00:00,8768.0 -2016-04-12 06:00:00,9061.0 -2016-04-12 07:00:00,9855.0 -2016-04-12 08:00:00,10788.0 -2016-04-12 09:00:00,11221.0 -2016-04-12 10:00:00,11339.0 -2016-04-12 11:00:00,11319.0 -2016-04-12 12:00:00,11331.0 -2016-04-12 13:00:00,11256.0 -2016-04-12 14:00:00,11127.0 -2016-04-12 15:00:00,11110.0 -2016-04-12 16:00:00,10962.0 -2016-04-12 17:00:00,10818.0 -2016-04-12 18:00:00,10681.0 -2016-04-12 19:00:00,10618.0 -2016-04-12 20:00:00,10470.0 -2016-04-12 21:00:00,10870.0 -2016-04-12 22:00:00,11269.0 -2016-04-12 23:00:00,10906.0 -2016-04-13 00:00:00,10241.0 -2016-04-11 01:00:00,8963.0 -2016-04-11 02:00:00,8566.0 -2016-04-11 03:00:00,8381.0 -2016-04-11 04:00:00,8274.0 -2016-04-11 05:00:00,8291.0 -2016-04-11 06:00:00,8599.0 -2016-04-11 07:00:00,9380.0 -2016-04-11 08:00:00,10320.0 -2016-04-11 09:00:00,10850.0 -2016-04-11 10:00:00,11112.0 -2016-04-11 11:00:00,11198.0 -2016-04-11 12:00:00,11241.0 -2016-04-11 13:00:00,11204.0 -2016-04-11 14:00:00,11108.0 -2016-04-11 15:00:00,11113.0 -2016-04-11 16:00:00,10944.0 -2016-04-11 17:00:00,10773.0 -2016-04-11 18:00:00,10639.0 -2016-04-11 19:00:00,10537.0 -2016-04-11 20:00:00,10439.0 -2016-04-11 21:00:00,10811.0 -2016-04-11 22:00:00,11134.0 -2016-04-11 23:00:00,10773.0 -2016-04-12 00:00:00,10092.0 -2016-04-10 01:00:00,9634.0 -2016-04-10 02:00:00,9283.0 -2016-04-10 03:00:00,9046.0 -2016-04-10 04:00:00,8888.0 -2016-04-10 05:00:00,8811.0 -2016-04-10 06:00:00,8817.0 -2016-04-10 07:00:00,8937.0 -2016-04-10 08:00:00,9112.0 -2016-04-10 09:00:00,9169.0 -2016-04-10 10:00:00,9474.0 -2016-04-10 11:00:00,9667.0 -2016-04-10 12:00:00,9899.0 -2016-04-10 13:00:00,9973.0 -2016-04-10 14:00:00,9961.0 -2016-04-10 15:00:00,9899.0 -2016-04-10 16:00:00,9898.0 -2016-04-10 17:00:00,9890.0 -2016-04-10 18:00:00,9862.0 -2016-04-10 19:00:00,9972.0 -2016-04-10 20:00:00,10126.0 -2016-04-10 21:00:00,10399.0 -2016-04-10 22:00:00,10347.0 -2016-04-10 23:00:00,9997.0 -2016-04-11 00:00:00,9481.0 -2016-04-09 01:00:00,10261.0 -2016-04-09 02:00:00,9875.0 -2016-04-09 03:00:00,9633.0 -2016-04-09 04:00:00,9431.0 -2016-04-09 05:00:00,9417.0 -2016-04-09 06:00:00,9491.0 -2016-04-09 07:00:00,9829.0 -2016-04-09 08:00:00,10080.0 -2016-04-09 09:00:00,10251.0 -2016-04-09 10:00:00,10449.0 -2016-04-09 11:00:00,10625.0 -2016-04-09 12:00:00,10616.0 -2016-04-09 13:00:00,10510.0 -2016-04-09 14:00:00,10355.0 -2016-04-09 15:00:00,10086.0 -2016-04-09 16:00:00,9910.0 -2016-04-09 17:00:00,9685.0 -2016-04-09 18:00:00,9636.0 -2016-04-09 19:00:00,9638.0 -2016-04-09 20:00:00,9726.0 -2016-04-09 21:00:00,10289.0 -2016-04-09 22:00:00,10698.0 -2016-04-09 23:00:00,10526.0 -2016-04-10 00:00:00,10154.0 -2016-04-08 01:00:00,9712.0 -2016-04-08 02:00:00,9286.0 -2016-04-08 03:00:00,9023.0 -2016-04-08 04:00:00,8878.0 -2016-04-08 05:00:00,8890.0 -2016-04-08 06:00:00,9160.0 -2016-04-08 07:00:00,9823.0 -2016-04-08 08:00:00,10814.0 -2016-04-08 09:00:00,11393.0 -2016-04-08 10:00:00,11750.0 -2016-04-08 11:00:00,11928.0 -2016-04-08 12:00:00,11958.0 -2016-04-08 13:00:00,11838.0 -2016-04-08 14:00:00,11650.0 -2016-04-08 15:00:00,11636.0 -2016-04-08 16:00:00,11538.0 -2016-04-08 17:00:00,11394.0 -2016-04-08 18:00:00,11217.0 -2016-04-08 19:00:00,11118.0 -2016-04-08 20:00:00,11105.0 -2016-04-08 21:00:00,11390.0 -2016-04-08 22:00:00,11617.0 -2016-04-08 23:00:00,11392.0 -2016-04-09 00:00:00,10777.0 -2016-04-07 01:00:00,9465.0 -2016-04-07 02:00:00,9028.0 -2016-04-07 03:00:00,8825.0 -2016-04-07 04:00:00,8669.0 -2016-04-07 05:00:00,8653.0 -2016-04-07 06:00:00,8899.0 -2016-04-07 07:00:00,9660.0 -2016-04-07 08:00:00,10796.0 -2016-04-07 09:00:00,11403.0 -2016-04-07 10:00:00,11665.0 -2016-04-07 11:00:00,11791.0 -2016-04-07 12:00:00,11835.0 -2016-04-07 13:00:00,11752.0 -2016-04-07 14:00:00,11713.0 -2016-04-07 15:00:00,11751.0 -2016-04-07 16:00:00,11639.0 -2016-04-07 17:00:00,11554.0 -2016-04-07 18:00:00,11483.0 -2016-04-07 19:00:00,11337.0 -2016-04-07 20:00:00,11242.0 -2016-04-07 21:00:00,11609.0 -2016-04-07 22:00:00,11598.0 -2016-04-07 23:00:00,11145.0 -2016-04-08 00:00:00,10414.0 -2016-04-06 01:00:00,9678.0 -2016-04-06 02:00:00,9184.0 -2016-04-06 03:00:00,8916.0 -2016-04-06 04:00:00,8775.0 -2016-04-06 05:00:00,8743.0 -2016-04-06 06:00:00,8954.0 -2016-04-06 07:00:00,9646.0 -2016-04-06 08:00:00,10661.0 -2016-04-06 09:00:00,11281.0 -2016-04-06 10:00:00,11548.0 -2016-04-06 11:00:00,11631.0 -2016-04-06 12:00:00,11784.0 -2016-04-06 13:00:00,11787.0 -2016-04-06 14:00:00,11751.0 -2016-04-06 15:00:00,11624.0 -2016-04-06 16:00:00,11519.0 -2016-04-06 17:00:00,11339.0 -2016-04-06 18:00:00,11332.0 -2016-04-06 19:00:00,11390.0 -2016-04-06 20:00:00,11311.0 -2016-04-06 21:00:00,11489.0 -2016-04-06 22:00:00,11350.0 -2016-04-06 23:00:00,10905.0 -2016-04-07 00:00:00,10141.0 -2016-04-05 01:00:00,10035.0 -2016-04-05 02:00:00,9622.0 -2016-04-05 03:00:00,9378.0 -2016-04-05 04:00:00,9287.0 -2016-04-05 05:00:00,9280.0 -2016-04-05 06:00:00,9597.0 -2016-04-05 07:00:00,10338.0 -2016-04-05 08:00:00,11325.0 -2016-04-05 09:00:00,11692.0 -2016-04-05 10:00:00,11754.0 -2016-04-05 11:00:00,11730.0 -2016-04-05 12:00:00,11674.0 -2016-04-05 13:00:00,11613.0 -2016-04-05 14:00:00,11486.0 -2016-04-05 15:00:00,11411.0 -2016-04-05 16:00:00,11201.0 -2016-04-05 17:00:00,11037.0 -2016-04-05 18:00:00,11004.0 -2016-04-05 19:00:00,11106.0 -2016-04-05 20:00:00,11220.0 -2016-04-05 21:00:00,11654.0 -2016-04-05 22:00:00,11609.0 -2016-04-05 23:00:00,11160.0 -2016-04-06 00:00:00,10417.0 -2016-04-04 01:00:00,8554.0 -2016-04-04 02:00:00,8288.0 -2016-04-04 03:00:00,8159.0 -2016-04-04 04:00:00,8138.0 -2016-04-04 05:00:00,8259.0 -2016-04-04 06:00:00,8629.0 -2016-04-04 07:00:00,9438.0 -2016-04-04 08:00:00,10617.0 -2016-04-04 09:00:00,11235.0 -2016-04-04 10:00:00,11540.0 -2016-04-04 11:00:00,11685.0 -2016-04-04 12:00:00,11839.0 -2016-04-04 13:00:00,11859.0 -2016-04-04 14:00:00,11824.0 -2016-04-04 15:00:00,11833.0 -2016-04-04 16:00:00,11729.0 -2016-04-04 17:00:00,11620.0 -2016-04-04 18:00:00,11578.0 -2016-04-04 19:00:00,11585.0 -2016-04-04 20:00:00,11530.0 -2016-04-04 21:00:00,11880.0 -2016-04-04 22:00:00,11916.0 -2016-04-04 23:00:00,11477.0 -2016-04-05 00:00:00,10777.0 -2016-04-03 01:00:00,9565.0 -2016-04-03 02:00:00,9176.0 -2016-04-03 03:00:00,8946.0 -2016-04-03 04:00:00,8752.0 -2016-04-03 05:00:00,8701.0 -2016-04-03 06:00:00,8720.0 -2016-04-03 07:00:00,8871.0 -2016-04-03 08:00:00,9043.0 -2016-04-03 09:00:00,9154.0 -2016-04-03 10:00:00,9325.0 -2016-04-03 11:00:00,9535.0 -2016-04-03 12:00:00,9501.0 -2016-04-03 13:00:00,9440.0 -2016-04-03 14:00:00,9265.0 -2016-04-03 15:00:00,9141.0 -2016-04-03 16:00:00,8968.0 -2016-04-03 17:00:00,8891.0 -2016-04-03 18:00:00,8852.0 -2016-04-03 19:00:00,8957.0 -2016-04-03 20:00:00,9073.0 -2016-04-03 21:00:00,9671.0 -2016-04-03 22:00:00,9765.0 -2016-04-03 23:00:00,9454.0 -2016-04-04 00:00:00,8993.0 -2016-04-02 01:00:00,9489.0 -2016-04-02 02:00:00,9040.0 -2016-04-02 03:00:00,8837.0 -2016-04-02 04:00:00,8714.0 -2016-04-02 05:00:00,8665.0 -2016-04-02 06:00:00,8779.0 -2016-04-02 07:00:00,9056.0 -2016-04-02 08:00:00,9492.0 -2016-04-02 09:00:00,9754.0 -2016-04-02 10:00:00,10230.0 -2016-04-02 11:00:00,10427.0 -2016-04-02 12:00:00,10517.0 -2016-04-02 13:00:00,10423.0 -2016-04-02 14:00:00,10359.0 -2016-04-02 15:00:00,10248.0 -2016-04-02 16:00:00,10139.0 -2016-04-02 17:00:00,9976.0 -2016-04-02 18:00:00,9960.0 -2016-04-02 19:00:00,9896.0 -2016-04-02 20:00:00,9922.0 -2016-04-02 21:00:00,10481.0 -2016-04-02 22:00:00,10707.0 -2016-04-02 23:00:00,10531.0 -2016-04-03 00:00:00,10067.0 -2016-04-01 01:00:00,9116.0 -2016-04-01 02:00:00,8655.0 -2016-04-01 03:00:00,8387.0 -2016-04-01 04:00:00,8236.0 -2016-04-01 05:00:00,8232.0 -2016-04-01 06:00:00,8489.0 -2016-04-01 07:00:00,9098.0 -2016-04-01 08:00:00,10063.0 -2016-04-01 09:00:00,10579.0 -2016-04-01 10:00:00,10901.0 -2016-04-01 11:00:00,11105.0 -2016-04-01 12:00:00,11188.0 -2016-04-01 13:00:00,11104.0 -2016-04-01 14:00:00,10917.0 -2016-04-01 15:00:00,10851.0 -2016-04-01 16:00:00,10704.0 -2016-04-01 17:00:00,10562.0 -2016-04-01 18:00:00,10491.0 -2016-04-01 19:00:00,10547.0 -2016-04-01 20:00:00,10514.0 -2016-04-01 21:00:00,10799.0 -2016-04-01 22:00:00,10942.0 -2016-04-01 23:00:00,10533.0 -2016-04-02 00:00:00,10042.0 -2016-03-31 01:00:00,9179.0 -2016-03-31 02:00:00,8711.0 -2016-03-31 03:00:00,8390.0 -2016-03-31 04:00:00,8230.0 -2016-03-31 05:00:00,8166.0 -2016-03-31 06:00:00,8341.0 -2016-03-31 07:00:00,8913.0 -2016-03-31 08:00:00,9919.0 -2016-03-31 09:00:00,10512.0 -2016-03-31 10:00:00,10915.0 -2016-03-31 11:00:00,11041.0 -2016-03-31 12:00:00,11142.0 -2016-03-31 13:00:00,11213.0 -2016-03-31 14:00:00,11181.0 -2016-03-31 15:00:00,11175.0 -2016-03-31 16:00:00,11012.0 -2016-03-31 17:00:00,10793.0 -2016-03-31 18:00:00,10595.0 -2016-03-31 19:00:00,10529.0 -2016-03-31 20:00:00,10527.0 -2016-03-31 21:00:00,10863.0 -2016-03-31 22:00:00,10786.0 -2016-03-31 23:00:00,10419.0 -2016-04-01 00:00:00,9799.0 -2016-03-30 01:00:00,9184.0 -2016-03-30 02:00:00,8748.0 -2016-03-30 03:00:00,8490.0 -2016-03-30 04:00:00,8334.0 -2016-03-30 05:00:00,8361.0 -2016-03-30 06:00:00,8583.0 -2016-03-30 07:00:00,9224.0 -2016-03-30 08:00:00,10231.0 -2016-03-30 09:00:00,10659.0 -2016-03-30 10:00:00,10960.0 -2016-03-30 11:00:00,11133.0 -2016-03-30 12:00:00,11240.0 -2016-03-30 13:00:00,11204.0 -2016-03-30 14:00:00,11062.0 -2016-03-30 15:00:00,11031.0 -2016-03-30 16:00:00,10897.0 -2016-03-30 17:00:00,10717.0 -2016-03-30 18:00:00,10699.0 -2016-03-30 19:00:00,10726.0 -2016-03-30 20:00:00,10846.0 -2016-03-30 21:00:00,11160.0 -2016-03-30 22:00:00,11001.0 -2016-03-30 23:00:00,10543.0 -2016-03-31 00:00:00,9856.0 -2016-03-29 01:00:00,9411.0 -2016-03-29 02:00:00,9000.0 -2016-03-29 03:00:00,8790.0 -2016-03-29 04:00:00,8661.0 -2016-03-29 05:00:00,8653.0 -2016-03-29 06:00:00,8915.0 -2016-03-29 07:00:00,9650.0 -2016-03-29 08:00:00,10619.0 -2016-03-29 09:00:00,10960.0 -2016-03-29 10:00:00,11021.0 -2016-03-29 11:00:00,10990.0 -2016-03-29 12:00:00,11005.0 -2016-03-29 13:00:00,10952.0 -2016-03-29 14:00:00,10865.0 -2016-03-29 15:00:00,10864.0 -2016-03-29 16:00:00,10737.0 -2016-03-29 17:00:00,10584.0 -2016-03-29 18:00:00,10443.0 -2016-03-29 19:00:00,10313.0 -2016-03-29 20:00:00,10269.0 -2016-03-29 21:00:00,10793.0 -2016-03-29 22:00:00,10870.0 -2016-03-29 23:00:00,10486.0 -2016-03-30 00:00:00,9828.0 -2016-03-28 01:00:00,8488.0 -2016-03-28 02:00:00,8225.0 -2016-03-28 03:00:00,8030.0 -2016-03-28 04:00:00,7979.0 -2016-03-28 05:00:00,8028.0 -2016-03-28 06:00:00,8326.0 -2016-03-28 07:00:00,9058.0 -2016-03-28 08:00:00,10108.0 -2016-03-28 09:00:00,10643.0 -2016-03-28 10:00:00,10861.0 -2016-03-28 11:00:00,11021.0 -2016-03-28 12:00:00,11091.0 -2016-03-28 13:00:00,11061.0 -2016-03-28 14:00:00,10925.0 -2016-03-28 15:00:00,10874.0 -2016-03-28 16:00:00,10728.0 -2016-03-28 17:00:00,10525.0 -2016-03-28 18:00:00,10398.0 -2016-03-28 19:00:00,10322.0 -2016-03-28 20:00:00,10239.0 -2016-03-28 21:00:00,10792.0 -2016-03-28 22:00:00,10971.0 -2016-03-28 23:00:00,10635.0 -2016-03-29 00:00:00,10024.0 -2016-03-27 01:00:00,8794.0 -2016-03-27 02:00:00,8371.0 -2016-03-27 03:00:00,8164.0 -2016-03-27 04:00:00,8009.0 -2016-03-27 05:00:00,7928.0 -2016-03-27 06:00:00,7914.0 -2016-03-27 07:00:00,8069.0 -2016-03-27 08:00:00,8233.0 -2016-03-27 09:00:00,8228.0 -2016-03-27 10:00:00,8423.0 -2016-03-27 11:00:00,8544.0 -2016-03-27 12:00:00,8548.0 -2016-03-27 13:00:00,8552.0 -2016-03-27 14:00:00,8502.0 -2016-03-27 15:00:00,8427.0 -2016-03-27 16:00:00,8378.0 -2016-03-27 17:00:00,8398.0 -2016-03-27 18:00:00,8581.0 -2016-03-27 19:00:00,8799.0 -2016-03-27 20:00:00,9053.0 -2016-03-27 21:00:00,9516.0 -2016-03-27 22:00:00,9592.0 -2016-03-27 23:00:00,9360.0 -2016-03-28 00:00:00,8949.0 -2016-03-26 01:00:00,9277.0 -2016-03-26 02:00:00,8802.0 -2016-03-26 03:00:00,8592.0 -2016-03-26 04:00:00,8442.0 -2016-03-26 05:00:00,8421.0 -2016-03-26 06:00:00,8466.0 -2016-03-26 07:00:00,8749.0 -2016-03-26 08:00:00,9068.0 -2016-03-26 09:00:00,9190.0 -2016-03-26 10:00:00,9448.0 -2016-03-26 11:00:00,9520.0 -2016-03-26 12:00:00,9521.0 -2016-03-26 13:00:00,9404.0 -2016-03-26 14:00:00,9300.0 -2016-03-26 15:00:00,9073.0 -2016-03-26 16:00:00,8948.0 -2016-03-26 17:00:00,8828.0 -2016-03-26 18:00:00,8885.0 -2016-03-26 19:00:00,8952.0 -2016-03-26 20:00:00,9133.0 -2016-03-26 21:00:00,9661.0 -2016-03-26 22:00:00,9830.0 -2016-03-26 23:00:00,9619.0 -2016-03-27 00:00:00,9232.0 -2016-03-25 01:00:00,9939.0 -2016-03-25 02:00:00,9453.0 -2016-03-25 03:00:00,9165.0 -2016-03-25 04:00:00,8978.0 -2016-03-25 05:00:00,8943.0 -2016-03-25 06:00:00,9097.0 -2016-03-25 07:00:00,9616.0 -2016-03-25 08:00:00,10292.0 -2016-03-25 09:00:00,10565.0 -2016-03-25 10:00:00,10756.0 -2016-03-25 11:00:00,10776.0 -2016-03-25 12:00:00,10687.0 -2016-03-25 13:00:00,10553.0 -2016-03-25 14:00:00,10369.0 -2016-03-25 15:00:00,10223.0 -2016-03-25 16:00:00,10049.0 -2016-03-25 17:00:00,9843.0 -2016-03-25 18:00:00,9775.0 -2016-03-25 19:00:00,9726.0 -2016-03-25 20:00:00,9736.0 -2016-03-25 21:00:00,10325.0 -2016-03-25 22:00:00,10455.0 -2016-03-25 23:00:00,10254.0 -2016-03-26 00:00:00,9775.0 -2016-03-24 01:00:00,9723.0 -2016-03-24 02:00:00,9241.0 -2016-03-24 03:00:00,8973.0 -2016-03-24 04:00:00,8802.0 -2016-03-24 05:00:00,8739.0 -2016-03-24 06:00:00,8912.0 -2016-03-24 07:00:00,9576.0 -2016-03-24 08:00:00,10695.0 -2016-03-24 09:00:00,11374.0 -2016-03-24 10:00:00,11576.0 -2016-03-24 11:00:00,11773.0 -2016-03-24 12:00:00,11990.0 -2016-03-24 13:00:00,12108.0 -2016-03-24 14:00:00,12088.0 -2016-03-24 15:00:00,11903.0 -2016-03-24 16:00:00,11629.0 -2016-03-24 17:00:00,11515.0 -2016-03-24 18:00:00,11686.0 -2016-03-24 19:00:00,11628.0 -2016-03-24 20:00:00,11542.0 -2016-03-24 21:00:00,11860.0 -2016-03-24 22:00:00,11777.0 -2016-03-24 23:00:00,11372.0 -2016-03-25 00:00:00,10655.0 -2016-03-23 01:00:00,9178.0 -2016-03-23 02:00:00,8768.0 -2016-03-23 03:00:00,8546.0 -2016-03-23 04:00:00,8419.0 -2016-03-23 05:00:00,8439.0 -2016-03-23 06:00:00,8705.0 -2016-03-23 07:00:00,9411.0 -2016-03-23 08:00:00,10571.0 -2016-03-23 09:00:00,11175.0 -2016-03-23 10:00:00,11361.0 -2016-03-23 11:00:00,11432.0 -2016-03-23 12:00:00,11526.0 -2016-03-23 13:00:00,11415.0 -2016-03-23 14:00:00,11276.0 -2016-03-23 15:00:00,11266.0 -2016-03-23 16:00:00,11282.0 -2016-03-23 17:00:00,11332.0 -2016-03-23 18:00:00,11455.0 -2016-03-23 19:00:00,11537.0 -2016-03-23 20:00:00,11563.0 -2016-03-23 21:00:00,11750.0 -2016-03-23 22:00:00,11501.0 -2016-03-23 23:00:00,11039.0 -2016-03-24 00:00:00,10366.0 -2016-03-22 01:00:00,9429.0 -2016-03-22 02:00:00,9028.0 -2016-03-22 03:00:00,8783.0 -2016-03-22 04:00:00,8642.0 -2016-03-22 05:00:00,8628.0 -2016-03-22 06:00:00,8889.0 -2016-03-22 07:00:00,9588.0 -2016-03-22 08:00:00,10682.0 -2016-03-22 09:00:00,11086.0 -2016-03-22 10:00:00,11261.0 -2016-03-22 11:00:00,11281.0 -2016-03-22 12:00:00,11313.0 -2016-03-22 13:00:00,11199.0 -2016-03-22 14:00:00,11078.0 -2016-03-22 15:00:00,11029.0 -2016-03-22 16:00:00,10855.0 -2016-03-22 17:00:00,10692.0 -2016-03-22 18:00:00,10596.0 -2016-03-22 19:00:00,10509.0 -2016-03-22 20:00:00,10452.0 -2016-03-22 21:00:00,10978.0 -2016-03-22 22:00:00,10905.0 -2016-03-22 23:00:00,10467.0 -2016-03-23 00:00:00,9814.0 -2016-03-21 01:00:00,9088.0 -2016-03-21 02:00:00,8808.0 -2016-03-21 03:00:00,8622.0 -2016-03-21 04:00:00,8588.0 -2016-03-21 05:00:00,8649.0 -2016-03-21 06:00:00,8994.0 -2016-03-21 07:00:00,9737.0 -2016-03-21 08:00:00,10929.0 -2016-03-21 09:00:00,11359.0 -2016-03-21 10:00:00,11443.0 -2016-03-21 11:00:00,11274.0 -2016-03-21 12:00:00,11286.0 -2016-03-21 13:00:00,11189.0 -2016-03-21 14:00:00,11071.0 -2016-03-21 15:00:00,11012.0 -2016-03-21 16:00:00,10834.0 -2016-03-21 17:00:00,10638.0 -2016-03-21 18:00:00,10540.0 -2016-03-21 19:00:00,10526.0 -2016-03-21 20:00:00,10603.0 -2016-03-21 21:00:00,11220.0 -2016-03-21 22:00:00,11202.0 -2016-03-21 23:00:00,10779.0 -2016-03-22 00:00:00,10076.0 -2016-03-20 01:00:00,9176.0 -2016-03-20 02:00:00,8787.0 -2016-03-20 03:00:00,8567.0 -2016-03-20 04:00:00,8394.0 -2016-03-20 05:00:00,8330.0 -2016-03-20 06:00:00,8326.0 -2016-03-20 07:00:00,8503.0 -2016-03-20 08:00:00,8764.0 -2016-03-20 09:00:00,8842.0 -2016-03-20 10:00:00,9024.0 -2016-03-20 11:00:00,9164.0 -2016-03-20 12:00:00,9192.0 -2016-03-20 13:00:00,9230.0 -2016-03-20 14:00:00,9142.0 -2016-03-20 15:00:00,9145.0 -2016-03-20 16:00:00,9069.0 -2016-03-20 17:00:00,9025.0 -2016-03-20 18:00:00,9100.0 -2016-03-20 19:00:00,9277.0 -2016-03-20 20:00:00,9448.0 -2016-03-20 21:00:00,10173.0 -2016-03-20 22:00:00,10264.0 -2016-03-20 23:00:00,9987.0 -2016-03-21 00:00:00,9538.0 -2016-03-19 01:00:00,9496.0 -2016-03-19 02:00:00,9008.0 -2016-03-19 03:00:00,8732.0 -2016-03-19 04:00:00,8554.0 -2016-03-19 05:00:00,8487.0 -2016-03-19 06:00:00,8542.0 -2016-03-19 07:00:00,8835.0 -2016-03-19 08:00:00,9277.0 -2016-03-19 09:00:00,9533.0 -2016-03-19 10:00:00,9822.0 -2016-03-19 11:00:00,9970.0 -2016-03-19 12:00:00,9975.0 -2016-03-19 13:00:00,9878.0 -2016-03-19 14:00:00,9752.0 -2016-03-19 15:00:00,9528.0 -2016-03-19 16:00:00,9441.0 -2016-03-19 17:00:00,9355.0 -2016-03-19 18:00:00,9383.0 -2016-03-19 19:00:00,9480.0 -2016-03-19 20:00:00,9735.0 -2016-03-19 21:00:00,10228.0 -2016-03-19 22:00:00,10286.0 -2016-03-19 23:00:00,10056.0 -2016-03-20 00:00:00,9680.0 -2016-03-18 01:00:00,9247.0 -2016-03-18 02:00:00,8815.0 -2016-03-18 03:00:00,8579.0 -2016-03-18 04:00:00,8467.0 -2016-03-18 05:00:00,8484.0 -2016-03-18 06:00:00,8762.0 -2016-03-18 07:00:00,9507.0 -2016-03-18 08:00:00,10644.0 -2016-03-18 09:00:00,11101.0 -2016-03-18 10:00:00,11201.0 -2016-03-18 11:00:00,11212.0 -2016-03-18 12:00:00,11213.0 -2016-03-18 13:00:00,11093.0 -2016-03-18 14:00:00,10960.0 -2016-03-18 15:00:00,10890.0 -2016-03-18 16:00:00,10721.0 -2016-03-18 17:00:00,10557.0 -2016-03-18 18:00:00,10538.0 -2016-03-18 19:00:00,10606.0 -2016-03-18 20:00:00,10650.0 -2016-03-18 21:00:00,11118.0 -2016-03-18 22:00:00,11010.0 -2016-03-18 23:00:00,10709.0 -2016-03-19 00:00:00,10128.0 -2016-03-17 01:00:00,9312.0 -2016-03-17 02:00:00,8843.0 -2016-03-17 03:00:00,8602.0 -2016-03-17 04:00:00,8448.0 -2016-03-17 05:00:00,8450.0 -2016-03-17 06:00:00,8730.0 -2016-03-17 07:00:00,9418.0 -2016-03-17 08:00:00,10584.0 -2016-03-17 09:00:00,11028.0 -2016-03-17 10:00:00,11066.0 -2016-03-17 11:00:00,11071.0 -2016-03-17 12:00:00,11112.0 -2016-03-17 13:00:00,11071.0 -2016-03-17 14:00:00,10989.0 -2016-03-17 15:00:00,10980.0 -2016-03-17 16:00:00,10801.0 -2016-03-17 17:00:00,10648.0 -2016-03-17 18:00:00,10533.0 -2016-03-17 19:00:00,10435.0 -2016-03-17 20:00:00,10411.0 -2016-03-17 21:00:00,10955.0 -2016-03-17 22:00:00,10964.0 -2016-03-17 23:00:00,10533.0 -2016-03-18 00:00:00,9893.0 -2016-03-16 01:00:00,9220.0 -2016-03-16 02:00:00,8718.0 -2016-03-16 03:00:00,8434.0 -2016-03-16 04:00:00,8291.0 -2016-03-16 05:00:00,8269.0 -2016-03-16 06:00:00,8491.0 -2016-03-16 07:00:00,9175.0 -2016-03-16 08:00:00,10297.0 -2016-03-16 09:00:00,10969.0 -2016-03-16 10:00:00,11097.0 -2016-03-16 11:00:00,11194.0 -2016-03-16 12:00:00,11178.0 -2016-03-16 13:00:00,11104.0 -2016-03-16 14:00:00,11045.0 -2016-03-16 15:00:00,11015.0 -2016-03-16 16:00:00,10873.0 -2016-03-16 17:00:00,10722.0 -2016-03-16 18:00:00,10627.0 -2016-03-16 19:00:00,10581.0 -2016-03-16 20:00:00,10630.0 -2016-03-16 21:00:00,11152.0 -2016-03-16 22:00:00,11083.0 -2016-03-16 23:00:00,10655.0 -2016-03-17 00:00:00,9986.0 -2016-03-15 01:00:00,9282.0 -2016-03-15 02:00:00,8791.0 -2016-03-15 03:00:00,8504.0 -2016-03-15 04:00:00,8339.0 -2016-03-15 05:00:00,8290.0 -2016-03-15 06:00:00,8504.0 -2016-03-15 07:00:00,9170.0 -2016-03-15 08:00:00,10271.0 -2016-03-15 09:00:00,10865.0 -2016-03-15 10:00:00,11011.0 -2016-03-15 11:00:00,11115.0 -2016-03-15 12:00:00,11210.0 -2016-03-15 13:00:00,11151.0 -2016-03-15 14:00:00,11075.0 -2016-03-15 15:00:00,11057.0 -2016-03-15 16:00:00,10924.0 -2016-03-15 17:00:00,10748.0 -2016-03-15 18:00:00,10662.0 -2016-03-15 19:00:00,10573.0 -2016-03-15 20:00:00,10606.0 -2016-03-15 21:00:00,11192.0 -2016-03-15 22:00:00,11062.0 -2016-03-15 23:00:00,10617.0 -2016-03-16 00:00:00,9958.0 -2016-03-14 01:00:00,8878.0 -2016-03-14 02:00:00,8535.0 -2016-03-14 03:00:00,8308.0 -2016-03-14 04:00:00,8184.0 -2016-03-14 05:00:00,8198.0 -2016-03-14 06:00:00,8432.0 -2016-03-14 07:00:00,9160.0 -2016-03-14 08:00:00,10342.0 -2016-03-14 09:00:00,11033.0 -2016-03-14 10:00:00,11167.0 -2016-03-14 11:00:00,11161.0 -2016-03-14 12:00:00,11219.0 -2016-03-14 13:00:00,11156.0 -2016-03-14 14:00:00,11101.0 -2016-03-14 15:00:00,11097.0 -2016-03-14 16:00:00,10959.0 -2016-03-14 17:00:00,10803.0 -2016-03-14 18:00:00,10693.0 -2016-03-14 19:00:00,10652.0 -2016-03-14 20:00:00,10771.0 -2016-03-14 21:00:00,11261.0 -2016-03-14 22:00:00,11079.0 -2016-03-14 23:00:00,10630.0 -2016-03-15 00:00:00,9962.0 -2016-03-13 01:00:00,8648.0 -2016-03-13 02:00:00,8325.0 -2016-03-13 04:00:00,8078.0 -2016-03-13 05:00:00,7969.0 -2016-03-13 06:00:00,7891.0 -2016-03-13 07:00:00,8011.0 -2016-03-13 08:00:00,8225.0 -2016-03-13 09:00:00,8393.0 -2016-03-13 10:00:00,8623.0 -2016-03-13 11:00:00,8909.0 -2016-03-13 12:00:00,9169.0 -2016-03-13 13:00:00,9334.0 -2016-03-13 14:00:00,9420.0 -2016-03-13 15:00:00,9445.0 -2016-03-13 16:00:00,9410.0 -2016-03-13 17:00:00,9405.0 -2016-03-13 18:00:00,9463.0 -2016-03-13 19:00:00,9643.0 -2016-03-13 20:00:00,10052.0 -2016-03-13 21:00:00,10409.0 -2016-03-13 22:00:00,10312.0 -2016-03-13 23:00:00,9970.0 -2016-03-14 00:00:00,9431.0 -2016-03-12 01:00:00,9455.0 -2016-03-12 02:00:00,8961.0 -2016-03-12 03:00:00,8712.0 -2016-03-12 04:00:00,8531.0 -2016-03-12 05:00:00,8482.0 -2016-03-12 06:00:00,8558.0 -2016-03-12 07:00:00,8856.0 -2016-03-12 08:00:00,9054.0 -2016-03-12 09:00:00,9392.0 -2016-03-12 10:00:00,9674.0 -2016-03-12 11:00:00,9847.0 -2016-03-12 12:00:00,9811.0 -2016-03-12 13:00:00,9697.0 -2016-03-12 14:00:00,9524.0 -2016-03-12 15:00:00,9351.0 -2016-03-12 16:00:00,9257.0 -2016-03-12 17:00:00,9227.0 -2016-03-12 18:00:00,9376.0 -2016-03-12 19:00:00,9687.0 -2016-03-12 20:00:00,10136.0 -2016-03-12 21:00:00,10077.0 -2016-03-12 22:00:00,9931.0 -2016-03-12 23:00:00,9586.0 -2016-03-13 00:00:00,9144.0 -2016-03-11 01:00:00,9330.0 -2016-03-11 02:00:00,8949.0 -2016-03-11 03:00:00,8715.0 -2016-03-11 04:00:00,8568.0 -2016-03-11 05:00:00,8590.0 -2016-03-11 06:00:00,8845.0 -2016-03-11 07:00:00,9597.0 -2016-03-11 08:00:00,10734.0 -2016-03-11 09:00:00,10919.0 -2016-03-11 10:00:00,10980.0 -2016-03-11 11:00:00,10970.0 -2016-03-11 12:00:00,10993.0 -2016-03-11 13:00:00,10941.0 -2016-03-11 14:00:00,10850.0 -2016-03-11 15:00:00,10829.0 -2016-03-11 16:00:00,10663.0 -2016-03-11 17:00:00,10529.0 -2016-03-11 18:00:00,10494.0 -2016-03-11 19:00:00,10590.0 -2016-03-11 20:00:00,11198.0 -2016-03-11 21:00:00,11178.0 -2016-03-11 22:00:00,10949.0 -2016-03-11 23:00:00,10649.0 -2016-03-12 00:00:00,10047.0 -2016-03-10 01:00:00,8984.0 -2016-03-10 02:00:00,8539.0 -2016-03-10 03:00:00,8251.0 -2016-03-10 04:00:00,8128.0 -2016-03-10 05:00:00,8116.0 -2016-03-10 06:00:00,8343.0 -2016-03-10 07:00:00,9091.0 -2016-03-10 08:00:00,10029.0 -2016-03-10 09:00:00,10655.0 -2016-03-10 10:00:00,10930.0 -2016-03-10 11:00:00,11054.0 -2016-03-10 12:00:00,11075.0 -2016-03-10 13:00:00,11072.0 -2016-03-10 14:00:00,11031.0 -2016-03-10 15:00:00,11036.0 -2016-03-10 16:00:00,10956.0 -2016-03-10 17:00:00,10864.0 -2016-03-10 18:00:00,10919.0 -2016-03-10 19:00:00,11112.0 -2016-03-10 20:00:00,11566.0 -2016-03-10 21:00:00,11489.0 -2016-03-10 22:00:00,11227.0 -2016-03-10 23:00:00,10747.0 -2016-03-11 00:00:00,9981.0 -2016-03-09 01:00:00,9043.0 -2016-03-09 02:00:00,8573.0 -2016-03-09 03:00:00,8293.0 -2016-03-09 04:00:00,8158.0 -2016-03-09 05:00:00,8119.0 -2016-03-09 06:00:00,8307.0 -2016-03-09 07:00:00,8964.0 -2016-03-09 08:00:00,9896.0 -2016-03-09 09:00:00,10467.0 -2016-03-09 10:00:00,10801.0 -2016-03-09 11:00:00,10926.0 -2016-03-09 12:00:00,11085.0 -2016-03-09 13:00:00,11101.0 -2016-03-09 14:00:00,11140.0 -2016-03-09 15:00:00,11150.0 -2016-03-09 16:00:00,11012.0 -2016-03-09 17:00:00,10860.0 -2016-03-09 18:00:00,10862.0 -2016-03-09 19:00:00,11034.0 -2016-03-09 20:00:00,11433.0 -2016-03-09 21:00:00,11251.0 -2016-03-09 22:00:00,10921.0 -2016-03-09 23:00:00,10425.0 -2016-03-10 00:00:00,9660.0 -2016-03-08 01:00:00,9127.0 -2016-03-08 02:00:00,8662.0 -2016-03-08 03:00:00,8399.0 -2016-03-08 04:00:00,8240.0 -2016-03-08 05:00:00,8212.0 -2016-03-08 06:00:00,8442.0 -2016-03-08 07:00:00,9106.0 -2016-03-08 08:00:00,9971.0 -2016-03-08 09:00:00,10578.0 -2016-03-08 10:00:00,10862.0 -2016-03-08 11:00:00,10947.0 -2016-03-08 12:00:00,11096.0 -2016-03-08 13:00:00,11149.0 -2016-03-08 14:00:00,11136.0 -2016-03-08 15:00:00,11168.0 -2016-03-08 16:00:00,11078.0 -2016-03-08 17:00:00,10954.0 -2016-03-08 18:00:00,10924.0 -2016-03-08 19:00:00,10968.0 -2016-03-08 20:00:00,11480.0 -2016-03-08 21:00:00,11386.0 -2016-03-08 22:00:00,11044.0 -2016-03-08 23:00:00,10496.0 -2016-03-09 00:00:00,9730.0 -2016-03-07 01:00:00,9113.0 -2016-03-07 02:00:00,8786.0 -2016-03-07 03:00:00,8608.0 -2016-03-07 04:00:00,8574.0 -2016-03-07 05:00:00,8561.0 -2016-03-07 06:00:00,8887.0 -2016-03-07 07:00:00,9587.0 -2016-03-07 08:00:00,10480.0 -2016-03-07 09:00:00,10864.0 -2016-03-07 10:00:00,11006.0 -2016-03-07 11:00:00,11017.0 -2016-03-07 12:00:00,11169.0 -2016-03-07 13:00:00,11155.0 -2016-03-07 14:00:00,11053.0 -2016-03-07 15:00:00,11049.0 -2016-03-07 16:00:00,10939.0 -2016-03-07 17:00:00,10788.0 -2016-03-07 18:00:00,10846.0 -2016-03-07 19:00:00,11138.0 -2016-03-07 20:00:00,11540.0 -2016-03-07 21:00:00,11369.0 -2016-03-07 22:00:00,11066.0 -2016-03-07 23:00:00,10570.0 -2016-03-08 00:00:00,9814.0 -2016-03-06 01:00:00,9599.0 -2016-03-06 02:00:00,9234.0 -2016-03-06 03:00:00,8974.0 -2016-03-06 04:00:00,8863.0 -2016-03-06 05:00:00,8758.0 -2016-03-06 06:00:00,8821.0 -2016-03-06 07:00:00,9001.0 -2016-03-06 08:00:00,9107.0 -2016-03-06 09:00:00,9237.0 -2016-03-06 10:00:00,9441.0 -2016-03-06 11:00:00,9575.0 -2016-03-06 12:00:00,9613.0 -2016-03-06 13:00:00,9534.0 -2016-03-06 14:00:00,9446.0 -2016-03-06 15:00:00,9318.0 -2016-03-06 16:00:00,9248.0 -2016-03-06 17:00:00,9220.0 -2016-03-06 18:00:00,9337.0 -2016-03-06 19:00:00,9782.0 -2016-03-06 20:00:00,10561.0 -2016-03-06 21:00:00,10588.0 -2016-03-06 22:00:00,10408.0 -2016-03-06 23:00:00,10072.0 -2016-03-07 00:00:00,9594.0 -2016-03-05 01:00:00,10345.0 -2016-03-05 02:00:00,9852.0 -2016-03-05 03:00:00,9600.0 -2016-03-05 04:00:00,9370.0 -2016-03-05 05:00:00,9347.0 -2016-03-05 06:00:00,9365.0 -2016-03-05 07:00:00,9672.0 -2016-03-05 08:00:00,9930.0 -2016-03-05 09:00:00,10207.0 -2016-03-05 10:00:00,10570.0 -2016-03-05 11:00:00,10813.0 -2016-03-05 12:00:00,10867.0 -2016-03-05 13:00:00,10812.0 -2016-03-05 14:00:00,10702.0 -2016-03-05 15:00:00,10564.0 -2016-03-05 16:00:00,10458.0 -2016-03-05 17:00:00,10412.0 -2016-03-05 18:00:00,10541.0 -2016-03-05 19:00:00,10852.0 -2016-03-05 20:00:00,11170.0 -2016-03-05 21:00:00,11031.0 -2016-03-05 22:00:00,10886.0 -2016-03-05 23:00:00,10600.0 -2016-03-06 00:00:00,10101.0 -2016-03-04 01:00:00,10480.0 -2016-03-04 02:00:00,10054.0 -2016-03-04 03:00:00,9835.0 -2016-03-04 04:00:00,9723.0 -2016-03-04 05:00:00,9795.0 -2016-03-04 06:00:00,10107.0 -2016-03-04 07:00:00,10848.0 -2016-03-04 08:00:00,11692.0 -2016-03-04 09:00:00,12011.0 -2016-03-04 10:00:00,12006.0 -2016-03-04 11:00:00,11927.0 -2016-03-04 12:00:00,11885.0 -2016-03-04 13:00:00,11769.0 -2016-03-04 14:00:00,11632.0 -2016-03-04 15:00:00,11558.0 -2016-03-04 16:00:00,11473.0 -2016-03-04 17:00:00,11316.0 -2016-03-04 18:00:00,11396.0 -2016-03-04 19:00:00,11776.0 -2016-03-04 20:00:00,12250.0 -2016-03-04 21:00:00,12114.0 -2016-03-04 22:00:00,11873.0 -2016-03-04 23:00:00,11560.0 -2016-03-05 00:00:00,10932.0 -2016-03-03 01:00:00,10457.0 -2016-03-03 02:00:00,10075.0 -2016-03-03 03:00:00,9821.0 -2016-03-03 04:00:00,9718.0 -2016-03-03 05:00:00,9697.0 -2016-03-03 06:00:00,9977.0 -2016-03-03 07:00:00,10703.0 -2016-03-03 08:00:00,11699.0 -2016-03-03 09:00:00,12131.0 -2016-03-03 10:00:00,12316.0 -2016-03-03 11:00:00,12404.0 -2016-03-03 12:00:00,12433.0 -2016-03-03 13:00:00,12401.0 -2016-03-03 14:00:00,12321.0 -2016-03-03 15:00:00,12313.0 -2016-03-03 16:00:00,12256.0 -2016-03-03 17:00:00,12178.0 -2016-03-03 18:00:00,12268.0 -2016-03-03 19:00:00,12598.0 -2016-03-03 20:00:00,12840.0 -2016-03-03 21:00:00,12647.0 -2016-03-03 22:00:00,12347.0 -2016-03-03 23:00:00,11864.0 -2016-03-04 00:00:00,11116.0 -2016-03-02 01:00:00,10934.0 -2016-03-02 02:00:00,10539.0 -2016-03-02 03:00:00,10336.0 -2016-03-02 04:00:00,10263.0 -2016-03-02 05:00:00,10279.0 -2016-03-02 06:00:00,10563.0 -2016-03-02 07:00:00,11307.0 -2016-03-02 08:00:00,12226.0 -2016-03-02 09:00:00,12569.0 -2016-03-02 10:00:00,12563.0 -2016-03-02 11:00:00,12460.0 -2016-03-02 12:00:00,12451.0 -2016-03-02 13:00:00,12318.0 -2016-03-02 14:00:00,12152.0 -2016-03-02 15:00:00,12081.0 -2016-03-02 16:00:00,11859.0 -2016-03-02 17:00:00,11713.0 -2016-03-02 18:00:00,11926.0 -2016-03-02 19:00:00,12383.0 -2016-03-02 20:00:00,12815.0 -2016-03-02 21:00:00,12645.0 -2016-03-02 22:00:00,12366.0 -2016-03-02 23:00:00,11845.0 -2016-03-03 00:00:00,11100.0 -2016-03-01 01:00:00,10152.0 -2016-03-01 02:00:00,9794.0 -2016-03-01 03:00:00,9614.0 -2016-03-01 04:00:00,9494.0 -2016-03-01 05:00:00,9501.0 -2016-03-01 06:00:00,9796.0 -2016-03-01 07:00:00,10534.0 -2016-03-01 08:00:00,11599.0 -2016-03-01 09:00:00,12125.0 -2016-03-01 10:00:00,12381.0 -2016-03-01 11:00:00,12541.0 -2016-03-01 12:00:00,12757.0 -2016-03-01 13:00:00,12837.0 -2016-03-01 14:00:00,12780.0 -2016-03-01 15:00:00,12755.0 -2016-03-01 16:00:00,12673.0 -2016-03-01 17:00:00,12570.0 -2016-03-01 18:00:00,12615.0 -2016-03-01 19:00:00,12928.0 -2016-03-01 20:00:00,13384.0 -2016-03-01 21:00:00,13233.0 -2016-03-01 22:00:00,12933.0 -2016-03-01 23:00:00,12382.0 -2016-03-02 00:00:00,11568.0 -2016-02-29 01:00:00,9084.0 -2016-02-29 02:00:00,8776.0 -2016-02-29 03:00:00,8635.0 -2016-02-29 04:00:00,8604.0 -2016-02-29 05:00:00,8700.0 -2016-02-29 06:00:00,9008.0 -2016-02-29 07:00:00,9868.0 -2016-02-29 08:00:00,10893.0 -2016-02-29 09:00:00,11372.0 -2016-02-29 10:00:00,11455.0 -2016-02-29 11:00:00,11486.0 -2016-02-29 12:00:00,11520.0 -2016-02-29 13:00:00,11408.0 -2016-02-29 14:00:00,11258.0 -2016-02-29 15:00:00,11171.0 -2016-02-29 16:00:00,11047.0 -2016-02-29 17:00:00,11054.0 -2016-02-29 18:00:00,11338.0 -2016-02-29 19:00:00,11889.0 -2016-02-29 20:00:00,12235.0 -2016-02-29 21:00:00,12133.0 -2016-02-29 22:00:00,11912.0 -2016-02-29 23:00:00,11460.0 -2016-03-01 00:00:00,10785.0 -2016-02-28 01:00:00,9210.0 -2016-02-28 02:00:00,8870.0 -2016-02-28 03:00:00,8600.0 -2016-02-28 04:00:00,8448.0 -2016-02-28 05:00:00,8342.0 -2016-02-28 06:00:00,8388.0 -2016-02-28 07:00:00,8524.0 -2016-02-28 08:00:00,8644.0 -2016-02-28 09:00:00,8628.0 -2016-02-28 10:00:00,8861.0 -2016-02-28 11:00:00,8985.0 -2016-02-28 12:00:00,9055.0 -2016-02-28 13:00:00,9061.0 -2016-02-28 14:00:00,9037.0 -2016-02-28 15:00:00,9018.0 -2016-02-28 16:00:00,9132.0 -2016-02-28 17:00:00,9265.0 -2016-02-28 18:00:00,9361.0 -2016-02-28 19:00:00,9750.0 -2016-02-28 20:00:00,10287.0 -2016-02-28 21:00:00,10240.0 -2016-02-28 22:00:00,10148.0 -2016-02-28 23:00:00,9894.0 -2016-02-29 00:00:00,9469.0 -2016-02-27 01:00:00,10451.0 -2016-02-27 02:00:00,9990.0 -2016-02-27 03:00:00,9727.0 -2016-02-27 04:00:00,9560.0 -2016-02-27 05:00:00,9544.0 -2016-02-27 06:00:00,9642.0 -2016-02-27 07:00:00,9918.0 -2016-02-27 08:00:00,10201.0 -2016-02-27 09:00:00,10310.0 -2016-02-27 10:00:00,10477.0 -2016-02-27 11:00:00,10528.0 -2016-02-27 12:00:00,10398.0 -2016-02-27 13:00:00,10257.0 -2016-02-27 14:00:00,10020.0 -2016-02-27 15:00:00,9785.0 -2016-02-27 16:00:00,9611.0 -2016-02-27 17:00:00,9504.0 -2016-02-27 18:00:00,9579.0 -2016-02-27 19:00:00,9971.0 -2016-02-27 20:00:00,10615.0 -2016-02-27 21:00:00,10577.0 -2016-02-27 22:00:00,10422.0 -2016-02-27 23:00:00,10178.0 -2016-02-28 00:00:00,9742.0 -2016-02-26 01:00:00,10326.0 -2016-02-26 02:00:00,9898.0 -2016-02-26 03:00:00,9654.0 -2016-02-26 04:00:00,9526.0 -2016-02-26 05:00:00,9528.0 -2016-02-26 06:00:00,9816.0 -2016-02-26 07:00:00,10597.0 -2016-02-26 08:00:00,11513.0 -2016-02-26 09:00:00,11808.0 -2016-02-26 10:00:00,11870.0 -2016-02-26 11:00:00,11962.0 -2016-02-26 12:00:00,12043.0 -2016-02-26 13:00:00,12016.0 -2016-02-26 14:00:00,11977.0 -2016-02-26 15:00:00,12003.0 -2016-02-26 16:00:00,11972.0 -2016-02-26 17:00:00,11945.0 -2016-02-26 18:00:00,12070.0 -2016-02-26 19:00:00,12396.0 -2016-02-26 20:00:00,12483.0 -2016-02-26 21:00:00,12262.0 -2016-02-26 22:00:00,12009.0 -2016-02-26 23:00:00,11677.0 -2016-02-27 00:00:00,11036.0 -2016-02-25 01:00:00,10401.0 -2016-02-25 02:00:00,9971.0 -2016-02-25 03:00:00,9717.0 -2016-02-25 04:00:00,9546.0 -2016-02-25 05:00:00,9595.0 -2016-02-25 06:00:00,9887.0 -2016-02-25 07:00:00,10625.0 -2016-02-25 08:00:00,11652.0 -2016-02-25 09:00:00,12073.0 -2016-02-25 10:00:00,12215.0 -2016-02-25 11:00:00,12241.0 -2016-02-25 12:00:00,12192.0 -2016-02-25 13:00:00,12047.0 -2016-02-25 14:00:00,11962.0 -2016-02-25 15:00:00,12010.0 -2016-02-25 16:00:00,12053.0 -2016-02-25 17:00:00,12059.0 -2016-02-25 18:00:00,12229.0 -2016-02-25 19:00:00,12630.0 -2016-02-25 20:00:00,12736.0 -2016-02-25 21:00:00,12523.0 -2016-02-25 22:00:00,12211.0 -2016-02-25 23:00:00,11726.0 -2016-02-26 00:00:00,10981.0 -2016-02-24 01:00:00,9982.0 -2016-02-24 02:00:00,9562.0 -2016-02-24 03:00:00,9369.0 -2016-02-24 04:00:00,9238.0 -2016-02-24 05:00:00,9260.0 -2016-02-24 06:00:00,9577.0 -2016-02-24 07:00:00,10347.0 -2016-02-24 08:00:00,11431.0 -2016-02-24 09:00:00,11967.0 -2016-02-24 10:00:00,12201.0 -2016-02-24 11:00:00,12357.0 -2016-02-24 12:00:00,12546.0 -2016-02-24 13:00:00,12590.0 -2016-02-24 14:00:00,12563.0 -2016-02-24 15:00:00,12604.0 -2016-02-24 16:00:00,12467.0 -2016-02-24 17:00:00,12440.0 -2016-02-24 18:00:00,12551.0 -2016-02-24 19:00:00,12945.0 -2016-02-24 20:00:00,13051.0 -2016-02-24 21:00:00,12802.0 -2016-02-24 22:00:00,12417.0 -2016-02-24 23:00:00,11873.0 -2016-02-25 00:00:00,11095.0 -2016-02-23 01:00:00,9849.0 -2016-02-23 02:00:00,9480.0 -2016-02-23 03:00:00,9286.0 -2016-02-23 04:00:00,9233.0 -2016-02-23 05:00:00,9244.0 -2016-02-23 06:00:00,9513.0 -2016-02-23 07:00:00,10314.0 -2016-02-23 08:00:00,11314.0 -2016-02-23 09:00:00,11736.0 -2016-02-23 10:00:00,11818.0 -2016-02-23 11:00:00,11722.0 -2016-02-23 12:00:00,11626.0 -2016-02-23 13:00:00,11507.0 -2016-02-23 14:00:00,11376.0 -2016-02-23 15:00:00,11302.0 -2016-02-23 16:00:00,11215.0 -2016-02-23 17:00:00,11098.0 -2016-02-23 18:00:00,11201.0 -2016-02-23 19:00:00,11758.0 -2016-02-23 20:00:00,12185.0 -2016-02-23 21:00:00,12074.0 -2016-02-23 22:00:00,11820.0 -2016-02-23 23:00:00,11342.0 -2016-02-24 00:00:00,10655.0 -2016-02-22 01:00:00,9271.0 -2016-02-22 02:00:00,9024.0 -2016-02-22 03:00:00,8924.0 -2016-02-22 04:00:00,8873.0 -2016-02-22 05:00:00,8980.0 -2016-02-22 06:00:00,9316.0 -2016-02-22 07:00:00,10123.0 -2016-02-22 08:00:00,11185.0 -2016-02-22 09:00:00,11630.0 -2016-02-22 10:00:00,11678.0 -2016-02-22 11:00:00,11580.0 -2016-02-22 12:00:00,11526.0 -2016-02-22 13:00:00,11457.0 -2016-02-22 14:00:00,11358.0 -2016-02-22 15:00:00,11332.0 -2016-02-22 16:00:00,11201.0 -2016-02-22 17:00:00,11137.0 -2016-02-22 18:00:00,11311.0 -2016-02-22 19:00:00,11889.0 -2016-02-22 20:00:00,12116.0 -2016-02-22 21:00:00,11941.0 -2016-02-22 22:00:00,11680.0 -2016-02-22 23:00:00,11212.0 -2016-02-23 00:00:00,10491.0 -2016-02-21 01:00:00,8929.0 -2016-02-21 02:00:00,8631.0 -2016-02-21 03:00:00,8388.0 -2016-02-21 04:00:00,8300.0 -2016-02-21 05:00:00,8262.0 -2016-02-21 06:00:00,8320.0 -2016-02-21 07:00:00,8475.0 -2016-02-21 08:00:00,8708.0 -2016-02-21 09:00:00,8744.0 -2016-02-21 10:00:00,9053.0 -2016-02-21 11:00:00,9301.0 -2016-02-21 12:00:00,9514.0 -2016-02-21 13:00:00,9550.0 -2016-02-21 14:00:00,9607.0 -2016-02-21 15:00:00,9518.0 -2016-02-21 16:00:00,9511.0 -2016-02-21 17:00:00,9536.0 -2016-02-21 18:00:00,9823.0 -2016-02-21 19:00:00,10513.0 -2016-02-21 20:00:00,10887.0 -2016-02-21 21:00:00,10818.0 -2016-02-21 22:00:00,10520.0 -2016-02-21 23:00:00,10215.0 -2016-02-22 00:00:00,9735.0 -2016-02-20 01:00:00,9552.0 -2016-02-20 02:00:00,9118.0 -2016-02-20 03:00:00,8860.0 -2016-02-20 04:00:00,8699.0 -2016-02-20 05:00:00,8630.0 -2016-02-20 06:00:00,8719.0 -2016-02-20 07:00:00,8992.0 -2016-02-20 08:00:00,9307.0 -2016-02-20 09:00:00,9404.0 -2016-02-20 10:00:00,9654.0 -2016-02-20 11:00:00,9766.0 -2016-02-20 12:00:00,9716.0 -2016-02-20 13:00:00,9618.0 -2016-02-20 14:00:00,9523.0 -2016-02-20 15:00:00,9348.0 -2016-02-20 16:00:00,9167.0 -2016-02-20 17:00:00,9071.0 -2016-02-20 18:00:00,9171.0 -2016-02-20 19:00:00,9694.0 -2016-02-20 20:00:00,10234.0 -2016-02-20 21:00:00,10146.0 -2016-02-20 22:00:00,10049.0 -2016-02-20 23:00:00,9807.0 -2016-02-21 00:00:00,9409.0 -2016-02-19 01:00:00,10270.0 -2016-02-19 02:00:00,9794.0 -2016-02-19 03:00:00,9551.0 -2016-02-19 04:00:00,9385.0 -2016-02-19 05:00:00,9330.0 -2016-02-19 06:00:00,9528.0 -2016-02-19 07:00:00,10155.0 -2016-02-19 08:00:00,11142.0 -2016-02-19 09:00:00,11434.0 -2016-02-19 10:00:00,11450.0 -2016-02-19 11:00:00,11403.0 -2016-02-19 12:00:00,11327.0 -2016-02-19 13:00:00,11221.0 -2016-02-19 14:00:00,11069.0 -2016-02-19 15:00:00,10920.0 -2016-02-19 16:00:00,10762.0 -2016-02-19 17:00:00,10726.0 -2016-02-19 18:00:00,10845.0 -2016-02-19 19:00:00,11160.0 -2016-02-19 20:00:00,11525.0 -2016-02-19 21:00:00,11396.0 -2016-02-19 22:00:00,11132.0 -2016-02-19 23:00:00,10753.0 -2016-02-20 00:00:00,10140.0 -2016-02-18 01:00:00,10759.0 -2016-02-18 02:00:00,10325.0 -2016-02-18 03:00:00,10107.0 -2016-02-18 04:00:00,10009.0 -2016-02-18 05:00:00,10022.0 -2016-02-18 06:00:00,10315.0 -2016-02-18 07:00:00,11059.0 -2016-02-18 08:00:00,12124.0 -2016-02-18 09:00:00,12446.0 -2016-02-18 10:00:00,12628.0 -2016-02-18 11:00:00,12674.0 -2016-02-18 12:00:00,12564.0 -2016-02-18 13:00:00,12482.0 -2016-02-18 14:00:00,12283.0 -2016-02-18 15:00:00,12154.0 -2016-02-18 16:00:00,12071.0 -2016-02-18 17:00:00,12081.0 -2016-02-18 18:00:00,12188.0 -2016-02-18 19:00:00,12687.0 -2016-02-18 20:00:00,12858.0 -2016-02-18 21:00:00,12652.0 -2016-02-18 22:00:00,12304.0 -2016-02-18 23:00:00,11778.0 -2016-02-19 00:00:00,11004.0 -2016-02-17 01:00:00,10662.0 -2016-02-17 02:00:00,10221.0 -2016-02-17 03:00:00,9958.0 -2016-02-17 04:00:00,9900.0 -2016-02-17 05:00:00,9887.0 -2016-02-17 06:00:00,10190.0 -2016-02-17 07:00:00,10948.0 -2016-02-17 08:00:00,12007.0 -2016-02-17 09:00:00,12322.0 -2016-02-17 10:00:00,12334.0 -2016-02-17 11:00:00,12233.0 -2016-02-17 12:00:00,12198.0 -2016-02-17 13:00:00,12133.0 -2016-02-17 14:00:00,12051.0 -2016-02-17 15:00:00,12014.0 -2016-02-17 16:00:00,11856.0 -2016-02-17 17:00:00,11782.0 -2016-02-17 18:00:00,11965.0 -2016-02-17 19:00:00,12603.0 -2016-02-17 20:00:00,13012.0 -2016-02-17 21:00:00,12900.0 -2016-02-17 22:00:00,12648.0 -2016-02-17 23:00:00,12156.0 -2016-02-18 00:00:00,11444.0 -2016-02-16 01:00:00,10853.0 -2016-02-16 02:00:00,10424.0 -2016-02-16 03:00:00,10190.0 -2016-02-16 04:00:00,10041.0 -2016-02-16 05:00:00,10060.0 -2016-02-16 06:00:00,10356.0 -2016-02-16 07:00:00,11074.0 -2016-02-16 08:00:00,12131.0 -2016-02-16 09:00:00,12468.0 -2016-02-16 10:00:00,12654.0 -2016-02-16 11:00:00,12671.0 -2016-02-16 12:00:00,12598.0 -2016-02-16 13:00:00,12434.0 -2016-02-16 14:00:00,12284.0 -2016-02-16 15:00:00,12326.0 -2016-02-16 16:00:00,12297.0 -2016-02-16 17:00:00,12365.0 -2016-02-16 18:00:00,12484.0 -2016-02-16 19:00:00,12981.0 -2016-02-16 20:00:00,13123.0 -2016-02-16 21:00:00,12962.0 -2016-02-16 22:00:00,12684.0 -2016-02-16 23:00:00,12092.0 -2016-02-17 00:00:00,11318.0 -2016-02-15 01:00:00,11061.0 -2016-02-15 02:00:00,10686.0 -2016-02-15 03:00:00,10533.0 -2016-02-15 04:00:00,10348.0 -2016-02-15 05:00:00,10413.0 -2016-02-15 06:00:00,10591.0 -2016-02-15 07:00:00,11204.0 -2016-02-15 08:00:00,12013.0 -2016-02-15 09:00:00,12379.0 -2016-02-15 10:00:00,12706.0 -2016-02-15 11:00:00,12906.0 -2016-02-15 12:00:00,12983.0 -2016-02-15 13:00:00,12904.0 -2016-02-15 14:00:00,12790.0 -2016-02-15 15:00:00,12755.0 -2016-02-15 16:00:00,12693.0 -2016-02-15 17:00:00,12648.0 -2016-02-15 18:00:00,12808.0 -2016-02-15 19:00:00,13294.0 -2016-02-15 20:00:00,13385.0 -2016-02-15 21:00:00,13164.0 -2016-02-15 22:00:00,12858.0 -2016-02-15 23:00:00,12358.0 -2016-02-16 00:00:00,11567.0 -2016-02-14 01:00:00,11428.0 -2016-02-14 02:00:00,11018.0 -2016-02-14 03:00:00,10820.0 -2016-02-14 04:00:00,10650.0 -2016-02-14 05:00:00,10571.0 -2016-02-14 06:00:00,10604.0 -2016-02-14 07:00:00,10741.0 -2016-02-14 08:00:00,11018.0 -2016-02-14 09:00:00,11019.0 -2016-02-14 10:00:00,11312.0 -2016-02-14 11:00:00,11565.0 -2016-02-14 12:00:00,11783.0 -2016-02-14 13:00:00,11939.0 -2016-02-14 14:00:00,11885.0 -2016-02-14 15:00:00,11845.0 -2016-02-14 16:00:00,11704.0 -2016-02-14 17:00:00,11751.0 -2016-02-14 18:00:00,11938.0 -2016-02-14 19:00:00,12590.0 -2016-02-14 20:00:00,12877.0 -2016-02-14 21:00:00,12751.0 -2016-02-14 22:00:00,12442.0 -2016-02-14 23:00:00,12113.0 -2016-02-15 00:00:00,11573.0 -2016-02-13 01:00:00,12079.0 -2016-02-13 02:00:00,11649.0 -2016-02-13 03:00:00,11416.0 -2016-02-13 04:00:00,11236.0 -2016-02-13 05:00:00,11232.0 -2016-02-13 06:00:00,11310.0 -2016-02-13 07:00:00,11578.0 -2016-02-13 08:00:00,11953.0 -2016-02-13 09:00:00,12066.0 -2016-02-13 10:00:00,12263.0 -2016-02-13 11:00:00,12348.0 -2016-02-13 12:00:00,12326.0 -2016-02-13 13:00:00,12215.0 -2016-02-13 14:00:00,11995.0 -2016-02-13 15:00:00,11751.0 -2016-02-13 16:00:00,11493.0 -2016-02-13 17:00:00,11416.0 -2016-02-13 18:00:00,11625.0 -2016-02-13 19:00:00,12471.0 -2016-02-13 20:00:00,12842.0 -2016-02-13 21:00:00,12869.0 -2016-02-13 22:00:00,12653.0 -2016-02-13 23:00:00,12394.0 -2016-02-14 00:00:00,11927.0 -2016-02-12 01:00:00,11415.0 -2016-02-12 02:00:00,10970.0 -2016-02-12 03:00:00,10697.0 -2016-02-12 04:00:00,10577.0 -2016-02-12 05:00:00,10590.0 -2016-02-12 06:00:00,10838.0 -2016-02-12 07:00:00,11536.0 -2016-02-12 08:00:00,12534.0 -2016-02-12 09:00:00,13038.0 -2016-02-12 10:00:00,13295.0 -2016-02-12 11:00:00,13387.0 -2016-02-12 12:00:00,13298.0 -2016-02-12 13:00:00,13199.0 -2016-02-12 14:00:00,13031.0 -2016-02-12 15:00:00,12941.0 -2016-02-12 16:00:00,12834.0 -2016-02-12 17:00:00,12771.0 -2016-02-12 18:00:00,12906.0 -2016-02-12 19:00:00,13558.0 -2016-02-12 20:00:00,13910.0 -2016-02-12 21:00:00,13755.0 -2016-02-12 22:00:00,13538.0 -2016-02-12 23:00:00,13241.0 -2016-02-13 00:00:00,12640.0 -2016-02-11 01:00:00,11756.0 -2016-02-11 02:00:00,11350.0 -2016-02-11 03:00:00,11127.0 -2016-02-11 04:00:00,11026.0 -2016-02-11 05:00:00,11072.0 -2016-02-11 06:00:00,11375.0 -2016-02-11 07:00:00,12094.0 -2016-02-11 08:00:00,13092.0 -2016-02-11 09:00:00,13446.0 -2016-02-11 10:00:00,13437.0 -2016-02-11 11:00:00,13353.0 -2016-02-11 12:00:00,13260.0 -2016-02-11 13:00:00,13107.0 -2016-02-11 14:00:00,12929.0 -2016-02-11 15:00:00,12843.0 -2016-02-11 16:00:00,12606.0 -2016-02-11 17:00:00,12512.0 -2016-02-11 18:00:00,12685.0 -2016-02-11 19:00:00,13386.0 -2016-02-11 20:00:00,13670.0 -2016-02-11 21:00:00,13566.0 -2016-02-11 22:00:00,13298.0 -2016-02-11 23:00:00,12830.0 -2016-02-12 00:00:00,12130.0 -2016-02-10 01:00:00,11644.0 -2016-02-10 02:00:00,11246.0 -2016-02-10 03:00:00,11046.0 -2016-02-10 04:00:00,10933.0 -2016-02-10 05:00:00,10957.0 -2016-02-10 06:00:00,11261.0 -2016-02-10 07:00:00,12008.0 -2016-02-10 08:00:00,13047.0 -2016-02-10 09:00:00,13387.0 -2016-02-10 10:00:00,13385.0 -2016-02-10 11:00:00,13384.0 -2016-02-10 12:00:00,13373.0 -2016-02-10 13:00:00,13235.0 -2016-02-10 14:00:00,13087.0 -2016-02-10 15:00:00,13040.0 -2016-02-10 16:00:00,12892.0 -2016-02-10 17:00:00,12820.0 -2016-02-10 18:00:00,13034.0 -2016-02-10 19:00:00,13754.0 -2016-02-10 20:00:00,13978.0 -2016-02-10 21:00:00,13835.0 -2016-02-10 22:00:00,13611.0 -2016-02-10 23:00:00,13149.0 -2016-02-11 00:00:00,12396.0 -2016-02-09 01:00:00,10941.0 -2016-02-09 02:00:00,10546.0 -2016-02-09 03:00:00,10393.0 -2016-02-09 04:00:00,10294.0 -2016-02-09 05:00:00,10352.0 -2016-02-09 06:00:00,10661.0 -2016-02-09 07:00:00,11412.0 -2016-02-09 08:00:00,12530.0 -2016-02-09 09:00:00,12949.0 -2016-02-09 10:00:00,13110.0 -2016-02-09 11:00:00,13155.0 -2016-02-09 12:00:00,13130.0 -2016-02-09 13:00:00,13014.0 -2016-02-09 14:00:00,13016.0 -2016-02-09 15:00:00,13141.0 -2016-02-09 16:00:00,13150.0 -2016-02-09 17:00:00,13153.0 -2016-02-09 18:00:00,13356.0 -2016-02-09 19:00:00,13941.0 -2016-02-09 20:00:00,14012.0 -2016-02-09 21:00:00,13817.0 -2016-02-09 22:00:00,13578.0 -2016-02-09 23:00:00,13071.0 -2016-02-10 00:00:00,12308.0 -2016-02-08 01:00:00,9627.0 -2016-02-08 02:00:00,9296.0 -2016-02-08 03:00:00,9168.0 -2016-02-08 04:00:00,9119.0 -2016-02-08 05:00:00,9211.0 -2016-02-08 06:00:00,9517.0 -2016-02-08 07:00:00,10307.0 -2016-02-08 08:00:00,11448.0 -2016-02-08 09:00:00,12076.0 -2016-02-08 10:00:00,12261.0 -2016-02-08 11:00:00,12370.0 -2016-02-08 12:00:00,12485.0 -2016-02-08 13:00:00,12552.0 -2016-02-08 14:00:00,12564.0 -2016-02-08 15:00:00,12581.0 -2016-02-08 16:00:00,12502.0 -2016-02-08 17:00:00,12502.0 -2016-02-08 18:00:00,12729.0 -2016-02-08 19:00:00,13289.0 -2016-02-08 20:00:00,13291.0 -2016-02-08 21:00:00,13106.0 -2016-02-08 22:00:00,12808.0 -2016-02-08 23:00:00,12323.0 -2016-02-09 00:00:00,11584.0 -2016-02-07 01:00:00,9870.0 -2016-02-07 02:00:00,9526.0 -2016-02-07 03:00:00,9248.0 -2016-02-07 04:00:00,9070.0 -2016-02-07 05:00:00,9020.0 -2016-02-07 06:00:00,8972.0 -2016-02-07 07:00:00,9116.0 -2016-02-07 08:00:00,9375.0 -2016-02-07 09:00:00,9400.0 -2016-02-07 10:00:00,9597.0 -2016-02-07 11:00:00,9727.0 -2016-02-07 12:00:00,9814.0 -2016-02-07 13:00:00,9887.0 -2016-02-07 14:00:00,9834.0 -2016-02-07 15:00:00,9901.0 -2016-02-07 16:00:00,9867.0 -2016-02-07 17:00:00,9833.0 -2016-02-07 18:00:00,10002.0 -2016-02-07 19:00:00,10702.0 -2016-02-07 20:00:00,10910.0 -2016-02-07 21:00:00,10847.0 -2016-02-07 22:00:00,10697.0 -2016-02-07 23:00:00,10508.0 -2016-02-08 00:00:00,10092.0 -2016-02-06 01:00:00,10530.0 -2016-02-06 02:00:00,10160.0 -2016-02-06 03:00:00,9827.0 -2016-02-06 04:00:00,9671.0 -2016-02-06 05:00:00,9632.0 -2016-02-06 06:00:00,9720.0 -2016-02-06 07:00:00,9996.0 -2016-02-06 08:00:00,10498.0 -2016-02-06 09:00:00,10675.0 -2016-02-06 10:00:00,10844.0 -2016-02-06 11:00:00,10915.0 -2016-02-06 12:00:00,10920.0 -2016-02-06 13:00:00,10886.0 -2016-02-06 14:00:00,10668.0 -2016-02-06 15:00:00,10397.0 -2016-02-06 16:00:00,10205.0 -2016-02-06 17:00:00,10143.0 -2016-02-06 18:00:00,10259.0 -2016-02-06 19:00:00,11048.0 -2016-02-06 20:00:00,11303.0 -2016-02-06 21:00:00,11234.0 -2016-02-06 22:00:00,11100.0 -2016-02-06 23:00:00,10826.0 -2016-02-07 00:00:00,10409.0 -2016-02-05 01:00:00,10616.0 -2016-02-05 02:00:00,10197.0 -2016-02-05 03:00:00,9926.0 -2016-02-05 04:00:00,9820.0 -2016-02-05 05:00:00,9800.0 -2016-02-05 06:00:00,10040.0 -2016-02-05 07:00:00,10761.0 -2016-02-05 08:00:00,11815.0 -2016-02-05 09:00:00,12330.0 -2016-02-05 10:00:00,12503.0 -2016-02-05 11:00:00,12553.0 -2016-02-05 12:00:00,12505.0 -2016-02-05 13:00:00,12356.0 -2016-02-05 14:00:00,12210.0 -2016-02-05 15:00:00,12133.0 -2016-02-05 16:00:00,11980.0 -2016-02-05 17:00:00,11983.0 -2016-02-05 18:00:00,12226.0 -2016-02-05 19:00:00,12746.0 -2016-02-05 20:00:00,12651.0 -2016-02-05 21:00:00,12409.0 -2016-02-05 22:00:00,12213.0 -2016-02-05 23:00:00,11842.0 -2016-02-06 00:00:00,11205.0 -2016-02-04 01:00:00,10634.0 -2016-02-04 02:00:00,10184.0 -2016-02-04 03:00:00,9944.0 -2016-02-04 04:00:00,9817.0 -2016-02-04 05:00:00,9838.0 -2016-02-04 06:00:00,10167.0 -2016-02-04 07:00:00,10956.0 -2016-02-04 08:00:00,12165.0 -2016-02-04 09:00:00,12647.0 -2016-02-04 10:00:00,12628.0 -2016-02-04 11:00:00,12545.0 -2016-02-04 12:00:00,12498.0 -2016-02-04 13:00:00,12393.0 -2016-02-04 14:00:00,12280.0 -2016-02-04 15:00:00,12140.0 -2016-02-04 16:00:00,11978.0 -2016-02-04 17:00:00,11939.0 -2016-02-04 18:00:00,12212.0 -2016-02-04 19:00:00,12910.0 -2016-02-04 20:00:00,13011.0 -2016-02-04 21:00:00,12826.0 -2016-02-04 22:00:00,12532.0 -2016-02-04 23:00:00,12080.0 -2016-02-05 00:00:00,11300.0 -2016-02-03 01:00:00,10117.0 -2016-02-03 02:00:00,9647.0 -2016-02-03 03:00:00,9393.0 -2016-02-03 04:00:00,9315.0 -2016-02-03 05:00:00,9306.0 -2016-02-03 06:00:00,9587.0 -2016-02-03 07:00:00,10338.0 -2016-02-03 08:00:00,11454.0 -2016-02-03 09:00:00,11989.0 -2016-02-03 10:00:00,12147.0 -2016-02-03 11:00:00,12235.0 -2016-02-03 12:00:00,12400.0 -2016-02-03 13:00:00,12396.0 -2016-02-03 14:00:00,12401.0 -2016-02-03 15:00:00,12440.0 -2016-02-03 16:00:00,12333.0 -2016-02-03 17:00:00,12363.0 -2016-02-03 18:00:00,12674.0 -2016-02-03 19:00:00,13131.0 -2016-02-03 20:00:00,13004.0 -2016-02-03 21:00:00,12791.0 -2016-02-03 22:00:00,12501.0 -2016-02-03 23:00:00,12008.0 -2016-02-04 00:00:00,11292.0 -2016-02-02 01:00:00,10112.0 -2016-02-02 02:00:00,9684.0 -2016-02-02 03:00:00,9423.0 -2016-02-02 04:00:00,9278.0 -2016-02-02 05:00:00,9300.0 -2016-02-02 06:00:00,9607.0 -2016-02-02 07:00:00,10385.0 -2016-02-02 08:00:00,11515.0 -2016-02-02 09:00:00,12105.0 -2016-02-02 10:00:00,12232.0 -2016-02-02 11:00:00,12354.0 -2016-02-02 12:00:00,12555.0 -2016-02-02 13:00:00,12680.0 -2016-02-02 14:00:00,12789.0 -2016-02-02 15:00:00,12852.0 -2016-02-02 16:00:00,12734.0 -2016-02-02 17:00:00,12789.0 -2016-02-02 18:00:00,12966.0 -2016-02-02 19:00:00,13173.0 -2016-02-02 20:00:00,12908.0 -2016-02-02 21:00:00,12604.0 -2016-02-02 22:00:00,12243.0 -2016-02-02 23:00:00,11621.0 -2016-02-03 00:00:00,10847.0 -2016-02-01 01:00:00,9487.0 -2016-02-01 02:00:00,9210.0 -2016-02-01 03:00:00,9074.0 -2016-02-01 04:00:00,9047.0 -2016-02-01 05:00:00,9070.0 -2016-02-01 06:00:00,9446.0 -2016-02-01 07:00:00,10219.0 -2016-02-01 08:00:00,11426.0 -2016-02-01 09:00:00,11864.0 -2016-02-01 10:00:00,11964.0 -2016-02-01 11:00:00,11931.0 -2016-02-01 12:00:00,11805.0 -2016-02-01 13:00:00,11672.0 -2016-02-01 14:00:00,11550.0 -2016-02-01 15:00:00,11485.0 -2016-02-01 16:00:00,11312.0 -2016-02-01 17:00:00,11234.0 -2016-02-01 18:00:00,11433.0 -2016-02-01 19:00:00,12251.0 -2016-02-01 20:00:00,12375.0 -2016-02-01 21:00:00,12230.0 -2016-02-01 22:00:00,11973.0 -2016-02-01 23:00:00,11544.0 -2016-02-02 00:00:00,10841.0 -2016-01-31 01:00:00,9323.0 -2016-01-31 02:00:00,8930.0 -2016-01-31 03:00:00,8688.0 -2016-01-31 04:00:00,8498.0 -2016-01-31 05:00:00,8443.0 -2016-01-31 06:00:00,8457.0 -2016-01-31 07:00:00,8616.0 -2016-01-31 08:00:00,8885.0 -2016-01-31 09:00:00,9036.0 -2016-01-31 10:00:00,9215.0 -2016-01-31 11:00:00,9448.0 -2016-01-31 12:00:00,9586.0 -2016-01-31 13:00:00,9773.0 -2016-01-31 14:00:00,9778.0 -2016-01-31 15:00:00,9745.0 -2016-01-31 16:00:00,9761.0 -2016-01-31 17:00:00,9922.0 -2016-01-31 18:00:00,10281.0 -2016-01-31 19:00:00,10951.0 -2016-01-31 20:00:00,11057.0 -2016-01-31 21:00:00,10988.0 -2016-01-31 22:00:00,10770.0 -2016-01-31 23:00:00,10464.0 -2016-02-01 00:00:00,9968.0 -2016-01-30 01:00:00,10479.0 -2016-01-30 02:00:00,10092.0 -2016-01-30 03:00:00,9647.0 -2016-01-30 04:00:00,9391.0 -2016-01-30 05:00:00,9367.0 -2016-01-30 06:00:00,9388.0 -2016-01-30 07:00:00,9643.0 -2016-01-30 08:00:00,10050.0 -2016-01-30 09:00:00,10189.0 -2016-01-30 10:00:00,10488.0 -2016-01-30 11:00:00,10697.0 -2016-01-30 12:00:00,10566.0 -2016-01-30 13:00:00,10401.0 -2016-01-30 14:00:00,10193.0 -2016-01-30 15:00:00,10021.0 -2016-01-30 16:00:00,9922.0 -2016-01-30 17:00:00,9906.0 -2016-01-30 18:00:00,10166.0 -2016-01-30 19:00:00,10872.0 -2016-01-30 20:00:00,10926.0 -2016-01-30 21:00:00,10811.0 -2016-01-30 22:00:00,10596.0 -2016-01-30 23:00:00,10320.0 -2016-01-31 00:00:00,9815.0 -2016-01-29 01:00:00,10471.0 -2016-01-29 02:00:00,10050.0 -2016-01-29 03:00:00,9836.0 -2016-01-29 04:00:00,9726.0 -2016-01-29 05:00:00,9759.0 -2016-01-29 06:00:00,10058.0 -2016-01-29 07:00:00,10803.0 -2016-01-29 08:00:00,11965.0 -2016-01-29 09:00:00,12401.0 -2016-01-29 10:00:00,12363.0 -2016-01-29 11:00:00,12315.0 -2016-01-29 12:00:00,12266.0 -2016-01-29 13:00:00,12148.0 -2016-01-29 14:00:00,12016.0 -2016-01-29 15:00:00,11936.0 -2016-01-29 16:00:00,11812.0 -2016-01-29 17:00:00,11769.0 -2016-01-29 18:00:00,11993.0 -2016-01-29 19:00:00,12671.0 -2016-01-29 20:00:00,12675.0 -2016-01-29 21:00:00,12460.0 -2016-01-29 22:00:00,12177.0 -2016-01-29 23:00:00,11801.0 -2016-01-30 00:00:00,11086.0 -2016-01-28 01:00:00,10628.0 -2016-01-28 02:00:00,10188.0 -2016-01-28 03:00:00,9922.0 -2016-01-28 04:00:00,9794.0 -2016-01-28 05:00:00,9792.0 -2016-01-28 06:00:00,10033.0 -2016-01-28 07:00:00,10777.0 -2016-01-28 08:00:00,11874.0 -2016-01-28 09:00:00,12264.0 -2016-01-28 10:00:00,12265.0 -2016-01-28 11:00:00,12319.0 -2016-01-28 12:00:00,12349.0 -2016-01-28 13:00:00,12352.0 -2016-01-28 14:00:00,12270.0 -2016-01-28 15:00:00,12250.0 -2016-01-28 16:00:00,12181.0 -2016-01-28 17:00:00,12298.0 -2016-01-28 18:00:00,12616.0 -2016-01-28 19:00:00,13043.0 -2016-01-28 20:00:00,12877.0 -2016-01-28 21:00:00,12674.0 -2016-01-28 22:00:00,12416.0 -2016-01-28 23:00:00,11924.0 -2016-01-29 00:00:00,11172.0 -2016-01-27 01:00:00,10816.0 -2016-01-27 02:00:00,10371.0 -2016-01-27 03:00:00,10117.0 -2016-01-27 04:00:00,9980.0 -2016-01-27 05:00:00,10006.0 -2016-01-27 06:00:00,10225.0 -2016-01-27 07:00:00,11015.0 -2016-01-27 08:00:00,12135.0 -2016-01-27 09:00:00,12691.0 -2016-01-27 10:00:00,12803.0 -2016-01-27 11:00:00,12861.0 -2016-01-27 12:00:00,12852.0 -2016-01-27 13:00:00,12621.0 -2016-01-27 14:00:00,12384.0 -2016-01-27 15:00:00,12265.0 -2016-01-27 16:00:00,12111.0 -2016-01-27 17:00:00,12051.0 -2016-01-27 18:00:00,12421.0 -2016-01-27 19:00:00,13209.0 -2016-01-27 20:00:00,13126.0 -2016-01-27 21:00:00,12954.0 -2016-01-27 22:00:00,12661.0 -2016-01-27 23:00:00,12119.0 -2016-01-28 00:00:00,11340.0 -2016-01-26 01:00:00,10314.0 -2016-01-26 02:00:00,9904.0 -2016-01-26 03:00:00,9719.0 -2016-01-26 04:00:00,9646.0 -2016-01-26 05:00:00,9716.0 -2016-01-26 06:00:00,10003.0 -2016-01-26 07:00:00,10777.0 -2016-01-26 08:00:00,11920.0 -2016-01-26 09:00:00,12566.0 -2016-01-26 10:00:00,12638.0 -2016-01-26 11:00:00,12677.0 -2016-01-26 12:00:00,12765.0 -2016-01-26 13:00:00,12798.0 -2016-01-26 14:00:00,12792.0 -2016-01-26 15:00:00,12815.0 -2016-01-26 16:00:00,12733.0 -2016-01-26 17:00:00,12746.0 -2016-01-26 18:00:00,13082.0 -2016-01-26 19:00:00,13547.0 -2016-01-26 20:00:00,13399.0 -2016-01-26 21:00:00,13193.0 -2016-01-26 22:00:00,12867.0 -2016-01-26 23:00:00,12273.0 -2016-01-27 00:00:00,11526.0 -2016-01-25 01:00:00,10053.0 -2016-01-25 02:00:00,9760.0 -2016-01-25 03:00:00,9639.0 -2016-01-25 04:00:00,9571.0 -2016-01-25 05:00:00,9619.0 -2016-01-25 06:00:00,9958.0 -2016-01-25 07:00:00,10728.0 -2016-01-25 08:00:00,11856.0 -2016-01-25 09:00:00,12469.0 -2016-01-25 10:00:00,12428.0 -2016-01-25 11:00:00,12381.0 -2016-01-25 12:00:00,12283.0 -2016-01-25 13:00:00,12164.0 -2016-01-25 14:00:00,12086.0 -2016-01-25 15:00:00,12171.0 -2016-01-25 16:00:00,12172.0 -2016-01-25 17:00:00,12298.0 -2016-01-25 18:00:00,12751.0 -2016-01-25 19:00:00,13165.0 -2016-01-25 20:00:00,12929.0 -2016-01-25 21:00:00,12674.0 -2016-01-25 22:00:00,12328.0 -2016-01-25 23:00:00,11754.0 -2016-01-26 00:00:00,10951.0 -2016-01-24 01:00:00,10461.0 -2016-01-24 02:00:00,10082.0 -2016-01-24 03:00:00,9894.0 -2016-01-24 04:00:00,9732.0 -2016-01-24 05:00:00,9699.0 -2016-01-24 06:00:00,9745.0 -2016-01-24 07:00:00,9900.0 -2016-01-24 08:00:00,10148.0 -2016-01-24 09:00:00,10247.0 -2016-01-24 10:00:00,10406.0 -2016-01-24 11:00:00,10692.0 -2016-01-24 12:00:00,10725.0 -2016-01-24 13:00:00,10672.0 -2016-01-24 14:00:00,10562.0 -2016-01-24 15:00:00,10473.0 -2016-01-24 16:00:00,10408.0 -2016-01-24 17:00:00,10485.0 -2016-01-24 18:00:00,10928.0 -2016-01-24 19:00:00,11718.0 -2016-01-24 20:00:00,11815.0 -2016-01-24 21:00:00,11714.0 -2016-01-24 22:00:00,11443.0 -2016-01-24 23:00:00,11076.0 -2016-01-25 00:00:00,10499.0 -2016-01-23 01:00:00,11039.0 -2016-01-23 02:00:00,10538.0 -2016-01-23 03:00:00,10259.0 -2016-01-23 04:00:00,10045.0 -2016-01-23 05:00:00,10006.0 -2016-01-23 06:00:00,10056.0 -2016-01-23 07:00:00,10331.0 -2016-01-23 08:00:00,10773.0 -2016-01-23 09:00:00,11017.0 -2016-01-23 10:00:00,11226.0 -2016-01-23 11:00:00,11457.0 -2016-01-23 12:00:00,11394.0 -2016-01-23 13:00:00,11288.0 -2016-01-23 14:00:00,11178.0 -2016-01-23 15:00:00,10996.0 -2016-01-23 16:00:00,10871.0 -2016-01-23 17:00:00,10813.0 -2016-01-23 18:00:00,11080.0 -2016-01-23 19:00:00,11874.0 -2016-01-23 20:00:00,11944.0 -2016-01-23 21:00:00,11804.0 -2016-01-23 22:00:00,11632.0 -2016-01-23 23:00:00,11402.0 -2016-01-24 00:00:00,10931.0 -2016-01-22 01:00:00,11155.0 -2016-01-22 02:00:00,10787.0 -2016-01-22 03:00:00,10536.0 -2016-01-22 04:00:00,10400.0 -2016-01-22 05:00:00,10407.0 -2016-01-22 06:00:00,10654.0 -2016-01-22 07:00:00,11285.0 -2016-01-22 08:00:00,12363.0 -2016-01-22 09:00:00,13023.0 -2016-01-22 10:00:00,13160.0 -2016-01-22 11:00:00,13217.0 -2016-01-22 12:00:00,13261.0 -2016-01-22 13:00:00,13191.0 -2016-01-22 14:00:00,13093.0 -2016-01-22 15:00:00,13023.0 -2016-01-22 16:00:00,12886.0 -2016-01-22 17:00:00,12669.0 -2016-01-22 18:00:00,12998.0 -2016-01-22 19:00:00,13567.0 -2016-01-22 20:00:00,13402.0 -2016-01-22 21:00:00,13115.0 -2016-01-22 22:00:00,12761.0 -2016-01-22 23:00:00,12348.0 -2016-01-23 00:00:00,11660.0 -2016-01-21 01:00:00,11561.0 -2016-01-21 02:00:00,11167.0 -2016-01-21 03:00:00,10930.0 -2016-01-21 04:00:00,10839.0 -2016-01-21 05:00:00,10813.0 -2016-01-21 06:00:00,11062.0 -2016-01-21 07:00:00,11781.0 -2016-01-21 08:00:00,12842.0 -2016-01-21 09:00:00,13363.0 -2016-01-21 10:00:00,13361.0 -2016-01-21 11:00:00,13214.0 -2016-01-21 12:00:00,13075.0 -2016-01-21 13:00:00,12914.0 -2016-01-21 14:00:00,12744.0 -2016-01-21 15:00:00,12684.0 -2016-01-21 16:00:00,12526.0 -2016-01-21 17:00:00,12478.0 -2016-01-21 18:00:00,12858.0 -2016-01-21 19:00:00,13641.0 -2016-01-21 20:00:00,13591.0 -2016-01-21 21:00:00,13434.0 -2016-01-21 22:00:00,13164.0 -2016-01-21 23:00:00,12632.0 -2016-01-22 00:00:00,11869.0 -2016-01-20 01:00:00,11930.0 -2016-01-20 02:00:00,11516.0 -2016-01-20 03:00:00,11292.0 -2016-01-20 04:00:00,11139.0 -2016-01-20 05:00:00,11135.0 -2016-01-20 06:00:00,11379.0 -2016-01-20 07:00:00,12016.0 -2016-01-20 08:00:00,13014.0 -2016-01-20 09:00:00,13479.0 -2016-01-20 10:00:00,13517.0 -2016-01-20 11:00:00,13580.0 -2016-01-20 12:00:00,13572.0 -2016-01-20 13:00:00,13423.0 -2016-01-20 14:00:00,13217.0 -2016-01-20 15:00:00,13148.0 -2016-01-20 16:00:00,12944.0 -2016-01-20 17:00:00,12858.0 -2016-01-20 18:00:00,13198.0 -2016-01-20 19:00:00,14018.0 -2016-01-20 20:00:00,13988.0 -2016-01-20 21:00:00,13845.0 -2016-01-20 22:00:00,13533.0 -2016-01-20 23:00:00,12971.0 -2016-01-21 00:00:00,12257.0 -2016-01-19 01:00:00,12518.0 -2016-01-19 02:00:00,12154.0 -2016-01-19 03:00:00,11968.0 -2016-01-19 04:00:00,11818.0 -2016-01-19 05:00:00,11812.0 -2016-01-19 06:00:00,12050.0 -2016-01-19 07:00:00,12689.0 -2016-01-19 08:00:00,13767.0 -2016-01-19 09:00:00,14253.0 -2016-01-19 10:00:00,14194.0 -2016-01-19 11:00:00,14117.0 -2016-01-19 12:00:00,14001.0 -2016-01-19 13:00:00,13837.0 -2016-01-19 14:00:00,13685.0 -2016-01-19 15:00:00,13541.0 -2016-01-19 16:00:00,13386.0 -2016-01-19 17:00:00,13398.0 -2016-01-19 18:00:00,13806.0 -2016-01-19 19:00:00,14508.0 -2016-01-19 20:00:00,14445.0 -2016-01-19 21:00:00,14275.0 -2016-01-19 22:00:00,13975.0 -2016-01-19 23:00:00,13398.0 -2016-01-20 00:00:00,12639.0 -2016-01-18 01:00:00,12191.0 -2016-01-18 02:00:00,11875.0 -2016-01-18 03:00:00,11727.0 -2016-01-18 04:00:00,11664.0 -2016-01-18 05:00:00,11657.0 -2016-01-18 06:00:00,11892.0 -2016-01-18 07:00:00,12435.0 -2016-01-18 08:00:00,13335.0 -2016-01-18 09:00:00,13816.0 -2016-01-18 10:00:00,13951.0 -2016-01-18 11:00:00,14063.0 -2016-01-18 12:00:00,14095.0 -2016-01-18 13:00:00,14072.0 -2016-01-18 14:00:00,13938.0 -2016-01-18 15:00:00,13857.0 -2016-01-18 16:00:00,13693.0 -2016-01-18 17:00:00,13676.0 -2016-01-18 18:00:00,14102.0 -2016-01-18 19:00:00,14956.0 -2016-01-18 20:00:00,14938.0 -2016-01-18 21:00:00,14782.0 -2016-01-18 22:00:00,14451.0 -2016-01-18 23:00:00,13947.0 -2016-01-19 00:00:00,13234.0 -2016-01-17 01:00:00,11117.0 -2016-01-17 02:00:00,10866.0 -2016-01-17 03:00:00,10618.0 -2016-01-17 04:00:00,10527.0 -2016-01-17 05:00:00,10481.0 -2016-01-17 06:00:00,10507.0 -2016-01-17 07:00:00,10712.0 -2016-01-17 08:00:00,11073.0 -2016-01-17 09:00:00,11281.0 -2016-01-17 10:00:00,11495.0 -2016-01-17 11:00:00,11759.0 -2016-01-17 12:00:00,11851.0 -2016-01-17 13:00:00,11975.0 -2016-01-17 14:00:00,11961.0 -2016-01-17 15:00:00,11957.0 -2016-01-17 16:00:00,11920.0 -2016-01-17 17:00:00,12101.0 -2016-01-17 18:00:00,12625.0 -2016-01-17 19:00:00,13626.0 -2016-01-17 20:00:00,13663.0 -2016-01-17 21:00:00,13595.0 -2016-01-17 22:00:00,13413.0 -2016-01-17 23:00:00,13145.0 -2016-01-18 00:00:00,12634.0 -2016-01-16 01:00:00,10540.0 -2016-01-16 02:00:00,10067.0 -2016-01-16 03:00:00,9776.0 -2016-01-16 04:00:00,9598.0 -2016-01-16 05:00:00,9590.0 -2016-01-16 06:00:00,9687.0 -2016-01-16 07:00:00,10009.0 -2016-01-16 08:00:00,10452.0 -2016-01-16 09:00:00,10709.0 -2016-01-16 10:00:00,10853.0 -2016-01-16 11:00:00,10990.0 -2016-01-16 12:00:00,11081.0 -2016-01-16 13:00:00,11114.0 -2016-01-16 14:00:00,10995.0 -2016-01-16 15:00:00,10810.0 -2016-01-16 16:00:00,10689.0 -2016-01-16 17:00:00,10764.0 -2016-01-16 18:00:00,11212.0 -2016-01-16 19:00:00,12114.0 -2016-01-16 20:00:00,12249.0 -2016-01-16 21:00:00,12147.0 -2016-01-16 22:00:00,12062.0 -2016-01-16 23:00:00,11816.0 -2016-01-17 00:00:00,11501.0 -2016-01-15 01:00:00,10284.0 -2016-01-15 02:00:00,9783.0 -2016-01-15 03:00:00,9561.0 -2016-01-15 04:00:00,9390.0 -2016-01-15 05:00:00,9369.0 -2016-01-15 06:00:00,9581.0 -2016-01-15 07:00:00,10278.0 -2016-01-15 08:00:00,11334.0 -2016-01-15 09:00:00,11949.0 -2016-01-15 10:00:00,12053.0 -2016-01-15 11:00:00,12146.0 -2016-01-15 12:00:00,12206.0 -2016-01-15 13:00:00,12235.0 -2016-01-15 14:00:00,12236.0 -2016-01-15 15:00:00,12242.0 -2016-01-15 16:00:00,12219.0 -2016-01-15 17:00:00,12274.0 -2016-01-15 18:00:00,12696.0 -2016-01-15 19:00:00,13035.0 -2016-01-15 20:00:00,12866.0 -2016-01-15 21:00:00,12605.0 -2016-01-15 22:00:00,12312.0 -2016-01-15 23:00:00,11928.0 -2016-01-16 00:00:00,11205.0 -2016-01-14 01:00:00,11577.0 -2016-01-14 02:00:00,11052.0 -2016-01-14 03:00:00,10782.0 -2016-01-14 04:00:00,10603.0 -2016-01-14 05:00:00,10575.0 -2016-01-14 06:00:00,10752.0 -2016-01-14 07:00:00,11378.0 -2016-01-14 08:00:00,12407.0 -2016-01-14 09:00:00,12857.0 -2016-01-14 10:00:00,12777.0 -2016-01-14 11:00:00,12657.0 -2016-01-14 12:00:00,12547.0 -2016-01-14 13:00:00,12358.0 -2016-01-14 14:00:00,12188.0 -2016-01-14 15:00:00,12120.0 -2016-01-14 16:00:00,11982.0 -2016-01-14 17:00:00,11893.0 -2016-01-14 18:00:00,12300.0 -2016-01-14 19:00:00,12985.0 -2016-01-14 20:00:00,12847.0 -2016-01-14 21:00:00,12641.0 -2016-01-14 22:00:00,12326.0 -2016-01-14 23:00:00,11764.0 -2016-01-15 00:00:00,11007.0 -2016-01-13 01:00:00,12378.0 -2016-01-13 02:00:00,11964.0 -2016-01-13 03:00:00,11748.0 -2016-01-13 04:00:00,11612.0 -2016-01-13 05:00:00,11618.0 -2016-01-13 06:00:00,11881.0 -2016-01-13 07:00:00,12498.0 -2016-01-13 08:00:00,13591.0 -2016-01-13 09:00:00,14090.0 -2016-01-13 10:00:00,14155.0 -2016-01-13 11:00:00,14351.0 -2016-01-13 12:00:00,14441.0 -2016-01-13 13:00:00,14426.0 -2016-01-13 14:00:00,14322.0 -2016-01-13 15:00:00,14166.0 -2016-01-13 16:00:00,14001.0 -2016-01-13 17:00:00,13890.0 -2016-01-13 18:00:00,14082.0 -2016-01-13 19:00:00,14622.0 -2016-01-13 20:00:00,14416.0 -2016-01-13 21:00:00,14148.0 -2016-01-13 22:00:00,13823.0 -2016-01-13 23:00:00,13224.0 -2016-01-14 00:00:00,12366.0 -2016-01-12 01:00:00,11752.0 -2016-01-12 02:00:00,11328.0 -2016-01-12 03:00:00,11087.0 -2016-01-12 04:00:00,10981.0 -2016-01-12 05:00:00,10967.0 -2016-01-12 06:00:00,11220.0 -2016-01-12 07:00:00,11956.0 -2016-01-12 08:00:00,13144.0 -2016-01-12 09:00:00,13686.0 -2016-01-12 10:00:00,13704.0 -2016-01-12 11:00:00,13738.0 -2016-01-12 12:00:00,13758.0 -2016-01-12 13:00:00,13720.0 -2016-01-12 14:00:00,13671.0 -2016-01-12 15:00:00,13632.0 -2016-01-12 16:00:00,13481.0 -2016-01-12 17:00:00,13534.0 -2016-01-12 18:00:00,14032.0 -2016-01-12 19:00:00,14852.0 -2016-01-12 20:00:00,14823.0 -2016-01-12 21:00:00,14657.0 -2016-01-12 22:00:00,14390.0 -2016-01-12 23:00:00,13889.0 -2016-01-13 00:00:00,13057.0 -2016-01-11 01:00:00,11513.0 -2016-01-11 02:00:00,11278.0 -2016-01-11 03:00:00,11090.0 -2016-01-11 04:00:00,11048.0 -2016-01-11 05:00:00,11097.0 -2016-01-11 06:00:00,11349.0 -2016-01-11 07:00:00,12058.0 -2016-01-11 08:00:00,13235.0 -2016-01-11 09:00:00,13758.0 -2016-01-11 10:00:00,13769.0 -2016-01-11 11:00:00,13830.0 -2016-01-11 12:00:00,13938.0 -2016-01-11 13:00:00,13887.0 -2016-01-11 14:00:00,13871.0 -2016-01-11 15:00:00,13867.0 -2016-01-11 16:00:00,13729.0 -2016-01-11 17:00:00,13681.0 -2016-01-11 18:00:00,14085.0 -2016-01-11 19:00:00,14565.0 -2016-01-11 20:00:00,14409.0 -2016-01-11 21:00:00,14129.0 -2016-01-11 22:00:00,13774.0 -2016-01-11 23:00:00,13223.0 -2016-01-12 00:00:00,12449.0 -2016-01-10 01:00:00,10429.0 -2016-01-10 02:00:00,10028.0 -2016-01-10 03:00:00,9839.0 -2016-01-10 04:00:00,9725.0 -2016-01-10 05:00:00,9662.0 -2016-01-10 06:00:00,9730.0 -2016-01-10 07:00:00,9895.0 -2016-01-10 08:00:00,10242.0 -2016-01-10 09:00:00,10334.0 -2016-01-10 10:00:00,10602.0 -2016-01-10 11:00:00,10799.0 -2016-01-10 12:00:00,10928.0 -2016-01-10 13:00:00,11008.0 -2016-01-10 14:00:00,11118.0 -2016-01-10 15:00:00,11114.0 -2016-01-10 16:00:00,11186.0 -2016-01-10 17:00:00,11350.0 -2016-01-10 18:00:00,12012.0 -2016-01-10 19:00:00,12998.0 -2016-01-10 20:00:00,13135.0 -2016-01-10 21:00:00,13044.0 -2016-01-10 22:00:00,12863.0 -2016-01-10 23:00:00,12540.0 -2016-01-11 00:00:00,11994.0 -2016-01-09 01:00:00,9982.0 -2016-01-09 02:00:00,9475.0 -2016-01-09 03:00:00,9167.0 -2016-01-09 04:00:00,8989.0 -2016-01-09 05:00:00,8906.0 -2016-01-09 06:00:00,8997.0 -2016-01-09 07:00:00,9243.0 -2016-01-09 08:00:00,9743.0 -2016-01-09 09:00:00,10091.0 -2016-01-09 10:00:00,10418.0 -2016-01-09 11:00:00,10732.0 -2016-01-09 12:00:00,10902.0 -2016-01-09 13:00:00,10902.0 -2016-01-09 14:00:00,10956.0 -2016-01-09 15:00:00,10919.0 -2016-01-09 16:00:00,11028.0 -2016-01-09 17:00:00,11189.0 -2016-01-09 18:00:00,11608.0 -2016-01-09 19:00:00,12019.0 -2016-01-09 20:00:00,11981.0 -2016-01-09 21:00:00,11789.0 -2016-01-09 22:00:00,11593.0 -2016-01-09 23:00:00,11299.0 -2016-01-10 00:00:00,10867.0 -2016-01-08 01:00:00,10087.0 -2016-01-08 02:00:00,9630.0 -2016-01-08 03:00:00,9331.0 -2016-01-08 04:00:00,9198.0 -2016-01-08 05:00:00,9161.0 -2016-01-08 06:00:00,9400.0 -2016-01-08 07:00:00,10086.0 -2016-01-08 08:00:00,11197.0 -2016-01-08 09:00:00,11878.0 -2016-01-08 10:00:00,11973.0 -2016-01-08 11:00:00,12049.0 -2016-01-08 12:00:00,12108.0 -2016-01-08 13:00:00,12088.0 -2016-01-08 14:00:00,11988.0 -2016-01-08 15:00:00,11972.0 -2016-01-08 16:00:00,11895.0 -2016-01-08 17:00:00,11907.0 -2016-01-08 18:00:00,12391.0 -2016-01-08 19:00:00,12643.0 -2016-01-08 20:00:00,12414.0 -2016-01-08 21:00:00,12100.0 -2016-01-08 22:00:00,11734.0 -2016-01-08 23:00:00,11340.0 -2016-01-09 00:00:00,10626.0 -2016-01-07 01:00:00,10445.0 -2016-01-07 02:00:00,9926.0 -2016-01-07 03:00:00,9664.0 -2016-01-07 04:00:00,9483.0 -2016-01-07 05:00:00,9458.0 -2016-01-07 06:00:00,9704.0 -2016-01-07 07:00:00,10408.0 -2016-01-07 08:00:00,11512.0 -2016-01-07 09:00:00,12081.0 -2016-01-07 10:00:00,12104.0 -2016-01-07 11:00:00,12004.0 -2016-01-07 12:00:00,12008.0 -2016-01-07 13:00:00,12015.0 -2016-01-07 14:00:00,12003.0 -2016-01-07 15:00:00,12073.0 -2016-01-07 16:00:00,11988.0 -2016-01-07 17:00:00,11949.0 -2016-01-07 18:00:00,12405.0 -2016-01-07 19:00:00,12872.0 -2016-01-07 20:00:00,12683.0 -2016-01-07 21:00:00,12450.0 -2016-01-07 22:00:00,12136.0 -2016-01-07 23:00:00,11594.0 -2016-01-08 00:00:00,10802.0 -2016-01-06 01:00:00,10835.0 -2016-01-06 02:00:00,10385.0 -2016-01-06 03:00:00,10108.0 -2016-01-06 04:00:00,9997.0 -2016-01-06 05:00:00,9974.0 -2016-01-06 06:00:00,10261.0 -2016-01-06 07:00:00,10977.0 -2016-01-06 08:00:00,12109.0 -2016-01-06 09:00:00,12590.0 -2016-01-06 10:00:00,12559.0 -2016-01-06 11:00:00,12410.0 -2016-01-06 12:00:00,12388.0 -2016-01-06 13:00:00,12278.0 -2016-01-06 14:00:00,12183.0 -2016-01-06 15:00:00,12220.0 -2016-01-06 16:00:00,12180.0 -2016-01-06 17:00:00,12179.0 -2016-01-06 18:00:00,12647.0 -2016-01-06 19:00:00,13221.0 -2016-01-06 20:00:00,13015.0 -2016-01-06 21:00:00,12775.0 -2016-01-06 22:00:00,12498.0 -2016-01-06 23:00:00,11979.0 -2016-01-07 00:00:00,11209.0 -2016-01-05 01:00:00,10371.0 -2016-01-05 02:00:00,10391.0 -2016-01-05 03:00:00,10158.0 -2016-01-05 04:00:00,10066.0 -2016-01-05 05:00:00,10101.0 -2016-01-05 06:00:00,10395.0 -2016-01-05 07:00:00,11138.0 -2016-01-05 08:00:00,12289.0 -2016-01-05 09:00:00,12796.0 -2016-01-05 10:00:00,12728.0 -2016-01-05 11:00:00,12668.0 -2016-01-05 12:00:00,12579.0 -2016-01-05 13:00:00,12459.0 -2016-01-05 14:00:00,12326.0 -2016-01-05 15:00:00,12298.0 -2016-01-05 16:00:00,12230.0 -2016-01-05 17:00:00,12264.0 -2016-01-05 18:00:00,12841.0 -2016-01-05 19:00:00,13521.0 -2016-01-05 20:00:00,13377.0 -2016-01-05 21:00:00,13214.0 -2016-01-05 22:00:00,12920.0 -2016-01-05 23:00:00,12360.0 -2016-01-06 00:00:00,11573.0 -2016-01-04 01:00:00,10150.0 -2016-01-04 02:00:00,9808.0 -2016-01-04 03:00:00,9625.0 -2016-01-04 04:00:00,9520.0 -2016-01-04 05:00:00,9603.0 -2016-01-04 06:00:00,9953.0 -2016-01-04 07:00:00,10652.0 -2016-01-04 08:00:00,11812.0 -2016-01-04 09:00:00,12346.0 -2016-01-04 10:00:00,12403.0 -2016-01-04 11:00:00,12415.0 -2016-01-04 12:00:00,12480.0 -2016-01-04 13:00:00,12480.0 -2016-01-04 14:00:00,12454.0 -2016-01-04 15:00:00,12437.0 -2016-01-04 16:00:00,12394.0 -2016-01-04 17:00:00,12428.0 -2016-01-04 18:00:00,12990.0 -2016-01-04 19:00:00,13560.0 -2016-01-04 20:00:00,13376.0 -2016-01-04 21:00:00,13155.0 -2016-01-04 22:00:00,12856.0 -2016-01-04 23:00:00,12306.0 -2016-01-05 00:00:00,11534.0 -2016-01-03 01:00:00,10373.0 -2016-01-03 02:00:00,9915.0 -2016-01-03 03:00:00,9638.0 -2016-01-03 04:00:00,9455.0 -2016-01-03 05:00:00,9395.0 -2016-01-03 06:00:00,9401.0 -2016-01-03 07:00:00,9587.0 -2016-01-03 08:00:00,9785.0 -2016-01-03 09:00:00,9910.0 -2016-01-03 10:00:00,10042.0 -2016-01-03 11:00:00,10312.0 -2016-01-03 12:00:00,10445.0 -2016-01-03 13:00:00,10566.0 -2016-01-03 14:00:00,10617.0 -2016-01-03 15:00:00,10667.0 -2016-01-03 16:00:00,10673.0 -2016-01-03 17:00:00,10899.0 -2016-01-03 18:00:00,11562.0 -2016-01-03 19:00:00,12138.0 -2016-01-03 20:00:00,12084.0 -2016-01-03 21:00:00,11982.0 -2016-01-03 22:00:00,11705.0 -2016-01-03 23:00:00,11399.0 -2016-01-04 00:00:00,10704.0 -2016-01-02 01:00:00,10170.0 -2016-01-02 02:00:00,9709.0 -2016-01-02 03:00:00,9464.0 -2016-01-02 04:00:00,9289.0 -2016-01-02 05:00:00,9266.0 -2016-01-02 06:00:00,9316.0 -2016-01-02 07:00:00,9608.0 -2016-01-02 08:00:00,10016.0 -2016-01-02 09:00:00,10228.0 -2016-01-02 10:00:00,10361.0 -2016-01-02 11:00:00,10506.0 -2016-01-02 12:00:00,10518.0 -2016-01-02 13:00:00,10530.0 -2016-01-02 14:00:00,10401.0 -2016-01-02 15:00:00,10247.0 -2016-01-02 16:00:00,10110.0 -2016-01-02 17:00:00,10252.0 -2016-01-02 18:00:00,10916.0 -2016-01-02 19:00:00,11846.0 -2016-01-02 20:00:00,11855.0 -2016-01-02 21:00:00,11718.0 -2016-01-02 22:00:00,11580.0 -2016-01-02 23:00:00,11389.0 -2016-01-03 00:00:00,10871.0 -2016-01-01 01:00:00,10407.0 -2016-01-01 02:00:00,10053.0 -2016-01-01 03:00:00,9791.0 -2016-01-01 04:00:00,9617.0 -2016-01-01 05:00:00,9563.0 -2016-01-01 06:00:00,9598.0 -2016-01-01 07:00:00,9766.0 -2016-01-01 08:00:00,9926.0 -2016-01-01 09:00:00,9903.0 -2016-01-01 10:00:00,9948.0 -2016-01-01 11:00:00,10203.0 -2016-01-01 12:00:00,10437.0 -2016-01-01 13:00:00,10557.0 -2016-01-01 14:00:00,10519.0 -2016-01-01 15:00:00,10446.0 -2016-01-01 16:00:00,10375.0 -2016-01-01 17:00:00,10430.0 -2016-01-01 18:00:00,11150.0 -2016-01-01 19:00:00,11849.0 -2016-01-01 20:00:00,11827.0 -2016-01-01 21:00:00,11678.0 -2016-01-01 22:00:00,11487.0 -2016-01-01 23:00:00,11084.0 -2016-01-02 00:00:00,10838.0 -2017-12-31 01:00:00,12132.0 -2017-12-31 02:00:00,11711.0 -2017-12-31 03:00:00,11405.0 -2017-12-31 04:00:00,11235.0 -2017-12-31 05:00:00,11145.0 -2017-12-31 06:00:00,11192.0 -2017-12-31 07:00:00,11288.0 -2017-12-31 08:00:00,11507.0 -2017-12-31 09:00:00,11554.0 -2017-12-31 10:00:00,11624.0 -2017-12-31 11:00:00,11673.0 -2017-12-31 12:00:00,11787.0 -2017-12-31 13:00:00,11883.0 -2017-12-31 14:00:00,11799.0 -2017-12-31 15:00:00,11704.0 -2017-12-31 16:00:00,11661.0 -2017-12-31 17:00:00,11840.0 -2017-12-31 18:00:00,12581.0 -2017-12-31 19:00:00,13452.0 -2017-12-31 20:00:00,13393.0 -2017-12-31 21:00:00,13192.0 -2017-12-31 22:00:00,13116.0 -2017-12-31 23:00:00,12809.0 -2018-01-01 00:00:00,12563.0 -2017-12-30 01:00:00,11644.0 -2017-12-30 02:00:00,11233.0 -2017-12-30 03:00:00,10947.0 -2017-12-30 04:00:00,10828.0 -2017-12-30 05:00:00,10782.0 -2017-12-30 06:00:00,10917.0 -2017-12-30 07:00:00,11169.0 -2017-12-30 08:00:00,11621.0 -2017-12-30 09:00:00,11832.0 -2017-12-30 10:00:00,12099.0 -2017-12-30 11:00:00,12146.0 -2017-12-30 12:00:00,12280.0 -2017-12-30 13:00:00,12261.0 -2017-12-30 14:00:00,12179.0 -2017-12-30 15:00:00,12059.0 -2017-12-30 16:00:00,11974.0 -2017-12-30 17:00:00,12154.0 -2017-12-30 18:00:00,12878.0 -2017-12-30 19:00:00,13673.0 -2017-12-30 20:00:00,13686.0 -2017-12-30 21:00:00,13632.0 -2017-12-30 22:00:00,13423.0 -2017-12-30 23:00:00,13191.0 -2017-12-31 00:00:00,12684.0 -2017-12-29 01:00:00,12009.0 -2017-12-29 02:00:00,11531.0 -2017-12-29 03:00:00,11222.0 -2017-12-29 04:00:00,11005.0 -2017-12-29 05:00:00,10961.0 -2017-12-29 06:00:00,11130.0 -2017-12-29 07:00:00,11552.0 -2017-12-29 08:00:00,12196.0 -2017-12-29 09:00:00,12542.0 -2017-12-29 10:00:00,12746.0 -2017-12-29 11:00:00,12906.0 -2017-12-29 12:00:00,12991.0 -2017-12-29 13:00:00,13023.0 -2017-12-29 14:00:00,12988.0 -2017-12-29 15:00:00,12999.0 -2017-12-29 16:00:00,12907.0 -2017-12-29 17:00:00,12917.0 -2017-12-29 18:00:00,13437.0 -2017-12-29 19:00:00,13862.0 -2017-12-29 20:00:00,13767.0 -2017-12-29 21:00:00,13572.0 -2017-12-29 22:00:00,13294.0 -2017-12-29 23:00:00,12917.0 -2017-12-30 00:00:00,12340.0 -2017-12-28 01:00:00,12475.0 -2017-12-28 02:00:00,12015.0 -2017-12-28 03:00:00,11751.0 -2017-12-28 04:00:00,11595.0 -2017-12-28 05:00:00,11578.0 -2017-12-28 06:00:00,11714.0 -2017-12-28 07:00:00,12108.0 -2017-12-28 08:00:00,12758.0 -2017-12-28 09:00:00,13145.0 -2017-12-28 10:00:00,13379.0 -2017-12-28 11:00:00,13386.0 -2017-12-28 12:00:00,13529.0 -2017-12-28 13:00:00,13608.0 -2017-12-28 14:00:00,13445.0 -2017-12-28 15:00:00,13504.0 -2017-12-28 16:00:00,13432.0 -2017-12-28 17:00:00,13409.0 -2017-12-28 18:00:00,13992.0 -2017-12-28 19:00:00,14435.0 -2017-12-28 20:00:00,14245.0 -2017-12-28 21:00:00,14055.0 -2017-12-28 22:00:00,13782.0 -2017-12-28 23:00:00,13353.0 -2017-12-29 00:00:00,12638.0 -2017-12-27 01:00:00,12171.0 -2017-12-27 02:00:00,11767.0 -2017-12-27 03:00:00,11473.0 -2017-12-27 04:00:00,11415.0 -2017-12-27 05:00:00,11408.0 -2017-12-27 06:00:00,11636.0 -2017-12-27 07:00:00,12163.0 -2017-12-27 08:00:00,12914.0 -2017-12-27 09:00:00,13317.0 -2017-12-27 10:00:00,13496.0 -2017-12-27 11:00:00,13574.0 -2017-12-27 12:00:00,13624.0 -2017-12-27 13:00:00,13540.0 -2017-12-27 14:00:00,13420.0 -2017-12-27 15:00:00,13354.0 -2017-12-27 16:00:00,13261.0 -2017-12-27 17:00:00,13315.0 -2017-12-27 18:00:00,14028.0 -2017-12-27 19:00:00,14694.0 -2017-12-27 20:00:00,14610.0 -2017-12-27 21:00:00,14446.0 -2017-12-27 22:00:00,14214.0 -2017-12-27 23:00:00,13757.0 -2017-12-28 00:00:00,13105.0 -2017-12-26 01:00:00,11091.0 -2017-12-26 02:00:00,10798.0 -2017-12-26 03:00:00,10642.0 -2017-12-26 04:00:00,10577.0 -2017-12-26 05:00:00,10587.0 -2017-12-26 06:00:00,10807.0 -2017-12-26 07:00:00,11283.0 -2017-12-26 08:00:00,11949.0 -2017-12-26 09:00:00,12326.0 -2017-12-26 10:00:00,12602.0 -2017-12-26 11:00:00,12788.0 -2017-12-26 12:00:00,12888.0 -2017-12-26 13:00:00,12869.0 -2017-12-26 14:00:00,12787.0 -2017-12-26 15:00:00,12726.0 -2017-12-26 16:00:00,12653.0 -2017-12-26 17:00:00,12708.0 -2017-12-26 18:00:00,13459.0 -2017-12-26 19:00:00,14251.0 -2017-12-26 20:00:00,14183.0 -2017-12-26 21:00:00,14019.0 -2017-12-26 22:00:00,13813.0 -2017-12-26 23:00:00,13438.0 -2017-12-27 00:00:00,12814.0 -2017-12-25 01:00:00,10596.0 -2017-12-25 02:00:00,10223.0 -2017-12-25 03:00:00,10004.0 -2017-12-25 04:00:00,9801.0 -2017-12-25 05:00:00,9754.0 -2017-12-25 06:00:00,9853.0 -2017-12-25 07:00:00,10060.0 -2017-12-25 08:00:00,10354.0 -2017-12-25 09:00:00,10412.0 -2017-12-25 10:00:00,10512.0 -2017-12-25 11:00:00,10577.0 -2017-12-25 12:00:00,10585.0 -2017-12-25 13:00:00,10601.0 -2017-12-25 14:00:00,10572.0 -2017-12-25 15:00:00,10571.0 -2017-12-25 16:00:00,10629.0 -2017-12-25 17:00:00,10830.0 -2017-12-25 18:00:00,11444.0 -2017-12-25 19:00:00,12013.0 -2017-12-25 20:00:00,11977.0 -2017-12-25 21:00:00,11983.0 -2017-12-25 22:00:00,11960.0 -2017-12-25 23:00:00,11847.0 -2017-12-26 00:00:00,11525.0 -2017-12-24 01:00:00,10490.0 -2017-12-24 02:00:00,9971.0 -2017-12-24 03:00:00,9723.0 -2017-12-24 04:00:00,9501.0 -2017-12-24 05:00:00,9451.0 -2017-12-24 06:00:00,9412.0 -2017-12-24 07:00:00,9561.0 -2017-12-24 08:00:00,9798.0 -2017-12-24 09:00:00,10060.0 -2017-12-24 10:00:00,10358.0 -2017-12-24 11:00:00,10616.0 -2017-12-24 12:00:00,10807.0 -2017-12-24 13:00:00,10834.0 -2017-12-24 14:00:00,10814.0 -2017-12-24 15:00:00,10816.0 -2017-12-24 16:00:00,10809.0 -2017-12-24 17:00:00,10870.0 -2017-12-24 18:00:00,11460.0 -2017-12-24 19:00:00,11871.0 -2017-12-24 20:00:00,11744.0 -2017-12-24 21:00:00,11608.0 -2017-12-24 22:00:00,11479.0 -2017-12-24 23:00:00,11375.0 -2017-12-25 00:00:00,10975.0 -2017-12-23 01:00:00,10222.0 -2017-12-23 02:00:00,9755.0 -2017-12-23 03:00:00,9402.0 -2017-12-23 04:00:00,9193.0 -2017-12-23 05:00:00,9112.0 -2017-12-23 06:00:00,9222.0 -2017-12-23 07:00:00,9515.0 -2017-12-23 08:00:00,9934.0 -2017-12-23 09:00:00,10247.0 -2017-12-23 10:00:00,10369.0 -2017-12-23 11:00:00,10443.0 -2017-12-23 12:00:00,10463.0 -2017-12-23 13:00:00,10452.0 -2017-12-23 14:00:00,10395.0 -2017-12-23 15:00:00,10294.0 -2017-12-23 16:00:00,10190.0 -2017-12-23 17:00:00,10329.0 -2017-12-23 18:00:00,11112.0 -2017-12-23 19:00:00,11785.0 -2017-12-23 20:00:00,11813.0 -2017-12-23 21:00:00,11764.0 -2017-12-23 22:00:00,11675.0 -2017-12-23 23:00:00,11453.0 -2017-12-24 00:00:00,10993.0 -2017-12-22 01:00:00,10307.0 -2017-12-22 02:00:00,9758.0 -2017-12-22 03:00:00,9428.0 -2017-12-22 04:00:00,9224.0 -2017-12-22 05:00:00,9201.0 -2017-12-22 06:00:00,9416.0 -2017-12-22 07:00:00,9980.0 -2017-12-22 08:00:00,10890.0 -2017-12-22 09:00:00,11440.0 -2017-12-22 10:00:00,11536.0 -2017-12-22 11:00:00,11595.0 -2017-12-22 12:00:00,11642.0 -2017-12-22 13:00:00,11630.0 -2017-12-22 14:00:00,11554.0 -2017-12-22 15:00:00,11536.0 -2017-12-22 16:00:00,11504.0 -2017-12-22 17:00:00,11595.0 -2017-12-22 18:00:00,12210.0 -2017-12-22 19:00:00,12504.0 -2017-12-22 20:00:00,12271.0 -2017-12-22 21:00:00,12032.0 -2017-12-22 22:00:00,11779.0 -2017-12-22 23:00:00,11457.0 -2017-12-23 00:00:00,10860.0 -2017-12-21 01:00:00,10574.0 -2017-12-21 02:00:00,10046.0 -2017-12-21 03:00:00,9702.0 -2017-12-21 04:00:00,9554.0 -2017-12-21 05:00:00,9541.0 -2017-12-21 06:00:00,9774.0 -2017-12-21 07:00:00,10472.0 -2017-12-21 08:00:00,11496.0 -2017-12-21 09:00:00,12092.0 -2017-12-21 10:00:00,12161.0 -2017-12-21 11:00:00,12178.0 -2017-12-21 12:00:00,12171.0 -2017-12-21 13:00:00,12076.0 -2017-12-21 14:00:00,12054.0 -2017-12-21 15:00:00,12101.0 -2017-12-21 16:00:00,12081.0 -2017-12-21 17:00:00,12090.0 -2017-12-21 18:00:00,12740.0 -2017-12-21 19:00:00,12990.0 -2017-12-21 20:00:00,12766.0 -2017-12-21 21:00:00,12548.0 -2017-12-21 22:00:00,12306.0 -2017-12-21 23:00:00,11829.0 -2017-12-22 00:00:00,11069.0 -2017-12-20 01:00:00,10256.0 -2017-12-20 02:00:00,9757.0 -2017-12-20 03:00:00,9468.0 -2017-12-20 04:00:00,9338.0 -2017-12-20 05:00:00,9362.0 -2017-12-20 06:00:00,9644.0 -2017-12-20 07:00:00,10387.0 -2017-12-20 08:00:00,11491.0 -2017-12-20 09:00:00,11991.0 -2017-12-20 10:00:00,12028.0 -2017-12-20 11:00:00,12008.0 -2017-12-20 12:00:00,11978.0 -2017-12-20 13:00:00,11882.0 -2017-12-20 14:00:00,11820.0 -2017-12-20 15:00:00,11851.0 -2017-12-20 16:00:00,11884.0 -2017-12-20 17:00:00,11995.0 -2017-12-20 18:00:00,12708.0 -2017-12-20 19:00:00,13266.0 -2017-12-20 20:00:00,13121.0 -2017-12-20 21:00:00,12935.0 -2017-12-20 22:00:00,12641.0 -2017-12-20 23:00:00,12171.0 -2017-12-21 00:00:00,11373.0 -2017-12-19 01:00:00,10256.0 -2017-12-19 02:00:00,9694.0 -2017-12-19 03:00:00,9389.0 -2017-12-19 04:00:00,9229.0 -2017-12-19 05:00:00,9220.0 -2017-12-19 06:00:00,9502.0 -2017-12-19 07:00:00,10205.0 -2017-12-19 08:00:00,11264.0 -2017-12-19 09:00:00,11732.0 -2017-12-19 10:00:00,11784.0 -2017-12-19 11:00:00,11745.0 -2017-12-19 12:00:00,11659.0 -2017-12-19 13:00:00,11506.0 -2017-12-19 14:00:00,11354.0 -2017-12-19 15:00:00,11333.0 -2017-12-19 16:00:00,11230.0 -2017-12-19 17:00:00,11294.0 -2017-12-19 18:00:00,12044.0 -2017-12-19 19:00:00,12606.0 -2017-12-19 20:00:00,12489.0 -2017-12-19 21:00:00,12350.0 -2017-12-19 22:00:00,12140.0 -2017-12-19 23:00:00,11698.0 -2017-12-20 00:00:00,10984.0 -2017-12-18 01:00:00,9560.0 -2017-12-18 02:00:00,9159.0 -2017-12-18 03:00:00,8890.0 -2017-12-18 04:00:00,8822.0 -2017-12-18 05:00:00,8828.0 -2017-12-18 06:00:00,9148.0 -2017-12-18 07:00:00,9887.0 -2017-12-18 08:00:00,10995.0 -2017-12-18 09:00:00,11710.0 -2017-12-18 10:00:00,11852.0 -2017-12-18 11:00:00,11843.0 -2017-12-18 12:00:00,11899.0 -2017-12-18 13:00:00,11851.0 -2017-12-18 14:00:00,11716.0 -2017-12-18 15:00:00,11578.0 -2017-12-18 16:00:00,11474.0 -2017-12-18 17:00:00,11460.0 -2017-12-18 18:00:00,12157.0 -2017-12-18 19:00:00,12798.0 -2017-12-18 20:00:00,12710.0 -2017-12-18 21:00:00,12567.0 -2017-12-18 22:00:00,12308.0 -2017-12-18 23:00:00,11800.0 -2017-12-19 00:00:00,11013.0 -2017-12-17 01:00:00,9964.0 -2017-12-17 02:00:00,9528.0 -2017-12-17 03:00:00,9198.0 -2017-12-17 04:00:00,9002.0 -2017-12-17 05:00:00,8914.0 -2017-12-17 06:00:00,8948.0 -2017-12-17 07:00:00,9094.0 -2017-12-17 08:00:00,9385.0 -2017-12-17 09:00:00,9559.0 -2017-12-17 10:00:00,9815.0 -2017-12-17 11:00:00,10005.0 -2017-12-17 12:00:00,10202.0 -2017-12-17 13:00:00,10148.0 -2017-12-17 14:00:00,10080.0 -2017-12-17 15:00:00,9943.0 -2017-12-17 16:00:00,9960.0 -2017-12-17 17:00:00,10130.0 -2017-12-17 18:00:00,10851.0 -2017-12-17 19:00:00,11362.0 -2017-12-17 20:00:00,11366.0 -2017-12-17 21:00:00,11330.0 -2017-12-17 22:00:00,11126.0 -2017-12-17 23:00:00,10746.0 -2017-12-18 00:00:00,10161.0 -2017-12-16 01:00:00,10781.0 -2017-12-16 02:00:00,10210.0 -2017-12-16 03:00:00,9863.0 -2017-12-16 04:00:00,9562.0 -2017-12-16 05:00:00,9453.0 -2017-12-16 06:00:00,9580.0 -2017-12-16 07:00:00,9867.0 -2017-12-16 08:00:00,10281.0 -2017-12-16 09:00:00,10562.0 -2017-12-16 10:00:00,10764.0 -2017-12-16 11:00:00,10765.0 -2017-12-16 12:00:00,10718.0 -2017-12-16 13:00:00,10507.0 -2017-12-16 14:00:00,10343.0 -2017-12-16 15:00:00,10070.0 -2017-12-16 16:00:00,9937.0 -2017-12-16 17:00:00,9983.0 -2017-12-16 18:00:00,10734.0 -2017-12-16 19:00:00,11433.0 -2017-12-16 20:00:00,11431.0 -2017-12-16 21:00:00,11369.0 -2017-12-16 22:00:00,11192.0 -2017-12-16 23:00:00,10970.0 -2017-12-17 00:00:00,10500.0 -2017-12-15 01:00:00,11220.0 -2017-12-15 02:00:00,10720.0 -2017-12-15 03:00:00,10425.0 -2017-12-15 04:00:00,10253.0 -2017-12-15 05:00:00,10249.0 -2017-12-15 06:00:00,10519.0 -2017-12-15 07:00:00,11208.0 -2017-12-15 08:00:00,12226.0 -2017-12-15 09:00:00,12856.0 -2017-12-15 10:00:00,12944.0 -2017-12-15 11:00:00,12929.0 -2017-12-15 12:00:00,12840.0 -2017-12-15 13:00:00,12598.0 -2017-12-15 14:00:00,12557.0 -2017-12-15 15:00:00,12522.0 -2017-12-15 16:00:00,12515.0 -2017-12-15 17:00:00,12568.0 -2017-12-15 18:00:00,13145.0 -2017-12-15 19:00:00,13379.0 -2017-12-15 20:00:00,13120.0 -2017-12-15 21:00:00,12886.0 -2017-12-15 22:00:00,12569.0 -2017-12-15 23:00:00,12197.0 -2017-12-16 00:00:00,11462.0 -2017-12-14 01:00:00,11014.0 -2017-12-14 02:00:00,10516.0 -2017-12-14 03:00:00,10245.0 -2017-12-14 04:00:00,10166.0 -2017-12-14 05:00:00,10194.0 -2017-12-14 06:00:00,10555.0 -2017-12-14 07:00:00,11281.0 -2017-12-14 08:00:00,12388.0 -2017-12-14 09:00:00,12902.0 -2017-12-14 10:00:00,12906.0 -2017-12-14 11:00:00,12860.0 -2017-12-14 12:00:00,12751.0 -2017-12-14 13:00:00,12571.0 -2017-12-14 14:00:00,12437.0 -2017-12-14 15:00:00,12438.0 -2017-12-14 16:00:00,12371.0 -2017-12-14 17:00:00,12503.0 -2017-12-14 18:00:00,13261.0 -2017-12-14 19:00:00,13746.0 -2017-12-14 20:00:00,13613.0 -2017-12-14 21:00:00,13442.0 -2017-12-14 22:00:00,13205.0 -2017-12-14 23:00:00,12758.0 -2017-12-15 00:00:00,11976.0 -2017-12-13 01:00:00,11335.0 -2017-12-13 02:00:00,10825.0 -2017-12-13 03:00:00,10522.0 -2017-12-13 04:00:00,10385.0 -2017-12-13 05:00:00,10371.0 -2017-12-13 06:00:00,10634.0 -2017-12-13 07:00:00,11335.0 -2017-12-13 08:00:00,12414.0 -2017-12-13 09:00:00,13003.0 -2017-12-13 10:00:00,13033.0 -2017-12-13 11:00:00,13017.0 -2017-12-13 12:00:00,12981.0 -2017-12-13 13:00:00,12851.0 -2017-12-13 14:00:00,12725.0 -2017-12-13 15:00:00,12639.0 -2017-12-13 16:00:00,12634.0 -2017-12-13 17:00:00,12819.0 -2017-12-13 18:00:00,13490.0 -2017-12-13 19:00:00,13803.0 -2017-12-13 20:00:00,13684.0 -2017-12-13 21:00:00,13529.0 -2017-12-13 22:00:00,13219.0 -2017-12-13 23:00:00,12681.0 -2017-12-14 00:00:00,11784.0 -2017-12-12 01:00:00,10817.0 -2017-12-12 02:00:00,10350.0 -2017-12-12 03:00:00,10173.0 -2017-12-12 04:00:00,10085.0 -2017-12-12 05:00:00,10169.0 -2017-12-12 06:00:00,10532.0 -2017-12-12 07:00:00,11338.0 -2017-12-12 08:00:00,12505.0 -2017-12-12 09:00:00,12924.0 -2017-12-12 10:00:00,12899.0 -2017-12-12 11:00:00,12864.0 -2017-12-12 12:00:00,12837.0 -2017-12-12 13:00:00,12724.0 -2017-12-12 14:00:00,12676.0 -2017-12-12 15:00:00,12645.0 -2017-12-12 16:00:00,12553.0 -2017-12-12 17:00:00,12750.0 -2017-12-12 18:00:00,13505.0 -2017-12-12 19:00:00,14111.0 -2017-12-12 20:00:00,13984.0 -2017-12-12 21:00:00,13803.0 -2017-12-12 22:00:00,13510.0 -2017-12-12 23:00:00,12952.0 -2017-12-13 00:00:00,12158.0 -2017-12-11 01:00:00,10198.0 -2017-12-11 02:00:00,9913.0 -2017-12-11 03:00:00,9694.0 -2017-12-11 04:00:00,9612.0 -2017-12-11 05:00:00,9693.0 -2017-12-11 06:00:00,10055.0 -2017-12-11 07:00:00,10824.0 -2017-12-11 08:00:00,12013.0 -2017-12-11 09:00:00,12536.0 -2017-12-11 10:00:00,12660.0 -2017-12-11 11:00:00,12627.0 -2017-12-11 12:00:00,12677.0 -2017-12-11 13:00:00,12664.0 -2017-12-11 14:00:00,12546.0 -2017-12-11 15:00:00,12450.0 -2017-12-11 16:00:00,12430.0 -2017-12-11 17:00:00,12465.0 -2017-12-11 18:00:00,13140.0 -2017-12-11 19:00:00,13568.0 -2017-12-11 20:00:00,13481.0 -2017-12-11 21:00:00,13267.0 -2017-12-11 22:00:00,13018.0 -2017-12-11 23:00:00,12546.0 -2017-12-12 00:00:00,11708.0 -2017-12-10 01:00:00,10717.0 -2017-12-10 02:00:00,10315.0 -2017-12-10 03:00:00,10020.0 -2017-12-10 04:00:00,9844.0 -2017-12-10 05:00:00,9710.0 -2017-12-10 06:00:00,9784.0 -2017-12-10 07:00:00,9935.0 -2017-12-10 08:00:00,10224.0 -2017-12-10 09:00:00,10271.0 -2017-12-10 10:00:00,10454.0 -2017-12-10 11:00:00,10426.0 -2017-12-10 12:00:00,10513.0 -2017-12-10 13:00:00,10462.0 -2017-12-10 14:00:00,10417.0 -2017-12-10 15:00:00,10408.0 -2017-12-10 16:00:00,10580.0 -2017-12-10 17:00:00,10723.0 -2017-12-10 18:00:00,11472.0 -2017-12-10 19:00:00,12017.0 -2017-12-10 20:00:00,12048.0 -2017-12-10 21:00:00,11998.0 -2017-12-10 22:00:00,11779.0 -2017-12-10 23:00:00,11388.0 -2017-12-11 00:00:00,10755.0 -2017-12-09 01:00:00,10743.0 -2017-12-09 02:00:00,10286.0 -2017-12-09 03:00:00,9943.0 -2017-12-09 04:00:00,9818.0 -2017-12-09 05:00:00,9668.0 -2017-12-09 06:00:00,9772.0 -2017-12-09 07:00:00,10058.0 -2017-12-09 08:00:00,10573.0 -2017-12-09 09:00:00,10873.0 -2017-12-09 10:00:00,11247.0 -2017-12-09 11:00:00,11520.0 -2017-12-09 12:00:00,11559.0 -2017-12-09 13:00:00,11575.0 -2017-12-09 14:00:00,11421.0 -2017-12-09 15:00:00,11304.0 -2017-12-09 16:00:00,11276.0 -2017-12-09 17:00:00,11313.0 -2017-12-09 18:00:00,11985.0 -2017-12-09 19:00:00,12422.0 -2017-12-09 20:00:00,12387.0 -2017-12-09 21:00:00,12232.0 -2017-12-09 22:00:00,12086.0 -2017-12-09 23:00:00,11806.0 -2017-12-10 00:00:00,11290.0 -2017-12-08 01:00:00,11183.0 -2017-12-08 02:00:00,10721.0 -2017-12-08 03:00:00,10445.0 -2017-12-08 04:00:00,10318.0 -2017-12-08 05:00:00,10338.0 -2017-12-08 06:00:00,10635.0 -2017-12-08 07:00:00,11294.0 -2017-12-08 08:00:00,12364.0 -2017-12-08 09:00:00,12763.0 -2017-12-08 10:00:00,12737.0 -2017-12-08 11:00:00,12603.0 -2017-12-08 12:00:00,12549.0 -2017-12-08 13:00:00,12369.0 -2017-12-08 14:00:00,12201.0 -2017-12-08 15:00:00,12042.0 -2017-12-08 16:00:00,11897.0 -2017-12-08 17:00:00,11878.0 -2017-12-08 18:00:00,12631.0 -2017-12-08 19:00:00,13191.0 -2017-12-08 20:00:00,13077.0 -2017-12-08 21:00:00,12885.0 -2017-12-08 22:00:00,12642.0 -2017-12-08 23:00:00,12224.0 -2017-12-09 00:00:00,11452.0 -2017-12-07 01:00:00,10738.0 -2017-12-07 02:00:00,10229.0 -2017-12-07 03:00:00,10016.0 -2017-12-07 04:00:00,9906.0 -2017-12-07 05:00:00,9975.0 -2017-12-07 06:00:00,10263.0 -2017-12-07 07:00:00,11025.0 -2017-12-07 08:00:00,12125.0 -2017-12-07 09:00:00,12571.0 -2017-12-07 10:00:00,12700.0 -2017-12-07 11:00:00,12831.0 -2017-12-07 12:00:00,12901.0 -2017-12-07 13:00:00,12734.0 -2017-12-07 14:00:00,12504.0 -2017-12-07 15:00:00,12404.0 -2017-12-07 16:00:00,12315.0 -2017-12-07 17:00:00,12328.0 -2017-12-07 18:00:00,13078.0 -2017-12-07 19:00:00,13645.0 -2017-12-07 20:00:00,13534.0 -2017-12-07 21:00:00,13382.0 -2017-12-07 22:00:00,13157.0 -2017-12-07 23:00:00,12678.0 -2017-12-08 00:00:00,11910.0 -2017-12-06 01:00:00,10651.0 -2017-12-06 02:00:00,10153.0 -2017-12-06 03:00:00,9888.0 -2017-12-06 04:00:00,9732.0 -2017-12-06 05:00:00,9708.0 -2017-12-06 06:00:00,9897.0 -2017-12-06 07:00:00,10629.0 -2017-12-06 08:00:00,11726.0 -2017-12-06 09:00:00,12152.0 -2017-12-06 10:00:00,12150.0 -2017-12-06 11:00:00,12146.0 -2017-12-06 12:00:00,12186.0 -2017-12-06 13:00:00,12131.0 -2017-12-06 14:00:00,12023.0 -2017-12-06 15:00:00,12006.0 -2017-12-06 16:00:00,11901.0 -2017-12-06 17:00:00,11980.0 -2017-12-06 18:00:00,12722.0 -2017-12-06 19:00:00,13306.0 -2017-12-06 20:00:00,13194.0 -2017-12-06 21:00:00,13021.0 -2017-12-06 22:00:00,12786.0 -2017-12-06 23:00:00,12265.0 -2017-12-07 00:00:00,11471.0 -2017-12-05 01:00:00,9390.0 -2017-12-05 02:00:00,8990.0 -2017-12-05 03:00:00,8802.0 -2017-12-05 04:00:00,8765.0 -2017-12-05 05:00:00,8823.0 -2017-12-05 06:00:00,9121.0 -2017-12-05 07:00:00,9935.0 -2017-12-05 08:00:00,11133.0 -2017-12-05 09:00:00,11727.0 -2017-12-05 10:00:00,11890.0 -2017-12-05 11:00:00,11944.0 -2017-12-05 12:00:00,11990.0 -2017-12-05 13:00:00,11983.0 -2017-12-05 14:00:00,11968.0 -2017-12-05 15:00:00,12035.0 -2017-12-05 16:00:00,12000.0 -2017-12-05 17:00:00,12019.0 -2017-12-05 18:00:00,12739.0 -2017-12-05 19:00:00,13296.0 -2017-12-05 20:00:00,13093.0 -2017-12-05 21:00:00,12911.0 -2017-12-05 22:00:00,12649.0 -2017-12-05 23:00:00,12127.0 -2017-12-06 00:00:00,11388.0 -2017-12-04 01:00:00,8993.0 -2017-12-04 02:00:00,8583.0 -2017-12-04 03:00:00,8358.0 -2017-12-04 04:00:00,8286.0 -2017-12-04 05:00:00,8296.0 -2017-12-04 06:00:00,8623.0 -2017-12-04 07:00:00,9336.0 -2017-12-04 08:00:00,10468.0 -2017-12-04 09:00:00,10989.0 -2017-12-04 10:00:00,11092.0 -2017-12-04 11:00:00,11145.0 -2017-12-04 12:00:00,11206.0 -2017-12-04 13:00:00,11158.0 -2017-12-04 14:00:00,11132.0 -2017-12-04 15:00:00,11136.0 -2017-12-04 16:00:00,11111.0 -2017-12-04 17:00:00,11158.0 -2017-12-04 18:00:00,11787.0 -2017-12-04 19:00:00,12084.0 -2017-12-04 20:00:00,11910.0 -2017-12-04 21:00:00,11704.0 -2017-12-04 22:00:00,11394.0 -2017-12-04 23:00:00,10855.0 -2017-12-05 00:00:00,10058.0 -2017-12-03 01:00:00,9285.0 -2017-12-03 02:00:00,8917.0 -2017-12-03 03:00:00,8636.0 -2017-12-03 04:00:00,8530.0 -2017-12-03 05:00:00,8457.0 -2017-12-03 06:00:00,8542.0 -2017-12-03 07:00:00,8728.0 -2017-12-03 08:00:00,9010.0 -2017-12-03 09:00:00,9024.0 -2017-12-03 10:00:00,9168.0 -2017-12-03 11:00:00,9199.0 -2017-12-03 12:00:00,9224.0 -2017-12-03 13:00:00,9211.0 -2017-12-03 14:00:00,9151.0 -2017-12-03 15:00:00,9121.0 -2017-12-03 16:00:00,9054.0 -2017-12-03 17:00:00,9189.0 -2017-12-03 18:00:00,9973.0 -2017-12-03 19:00:00,10603.0 -2017-12-03 20:00:00,10662.0 -2017-12-03 21:00:00,10619.0 -2017-12-03 22:00:00,10441.0 -2017-12-03 23:00:00,10050.0 -2017-12-04 00:00:00,9543.0 -2017-12-02 01:00:00,9816.0 -2017-12-02 02:00:00,9383.0 -2017-12-02 03:00:00,9051.0 -2017-12-02 04:00:00,8891.0 -2017-12-02 05:00:00,8785.0 -2017-12-02 06:00:00,8858.0 -2017-12-02 07:00:00,9132.0 -2017-12-02 08:00:00,9655.0 -2017-12-02 09:00:00,9794.0 -2017-12-02 10:00:00,10021.0 -2017-12-02 11:00:00,9995.0 -2017-12-02 12:00:00,9982.0 -2017-12-02 13:00:00,9886.0 -2017-12-02 14:00:00,9749.0 -2017-12-02 15:00:00,9584.0 -2017-12-02 16:00:00,9436.0 -2017-12-02 17:00:00,9512.0 -2017-12-02 18:00:00,10153.0 -2017-12-02 19:00:00,10718.0 -2017-12-02 20:00:00,10646.0 -2017-12-02 21:00:00,10578.0 -2017-12-02 22:00:00,10440.0 -2017-12-02 23:00:00,10210.0 -2017-12-03 00:00:00,9814.0 -2017-12-01 01:00:00,9791.0 -2017-12-01 02:00:00,9385.0 -2017-12-01 03:00:00,9100.0 -2017-12-01 04:00:00,9005.0 -2017-12-01 05:00:00,9036.0 -2017-12-01 06:00:00,9328.0 -2017-12-01 07:00:00,10042.0 -2017-12-01 08:00:00,11134.0 -2017-12-01 09:00:00,11521.0 -2017-12-01 10:00:00,11524.0 -2017-12-01 11:00:00,11404.0 -2017-12-01 12:00:00,11312.0 -2017-12-01 13:00:00,11195.0 -2017-12-01 14:00:00,11075.0 -2017-12-01 15:00:00,11032.0 -2017-12-01 16:00:00,10908.0 -2017-12-01 17:00:00,10848.0 -2017-12-01 18:00:00,11465.0 -2017-12-01 19:00:00,11953.0 -2017-12-01 20:00:00,11823.0 -2017-12-01 21:00:00,11655.0 -2017-12-01 22:00:00,11457.0 -2017-12-01 23:00:00,11119.0 -2017-12-02 00:00:00,10491.0 -2017-11-30 01:00:00,9701.0 -2017-11-30 02:00:00,9205.0 -2017-11-30 03:00:00,8909.0 -2017-11-30 04:00:00,8799.0 -2017-11-30 05:00:00,8745.0 -2017-11-30 06:00:00,9015.0 -2017-11-30 07:00:00,9767.0 -2017-11-30 08:00:00,10842.0 -2017-11-30 09:00:00,11342.0 -2017-11-30 10:00:00,11368.0 -2017-11-30 11:00:00,11333.0 -2017-11-30 12:00:00,11339.0 -2017-11-30 13:00:00,11243.0 -2017-11-30 14:00:00,11171.0 -2017-11-30 15:00:00,11174.0 -2017-11-30 16:00:00,11054.0 -2017-11-30 17:00:00,11035.0 -2017-11-30 18:00:00,11570.0 -2017-11-30 19:00:00,12100.0 -2017-11-30 20:00:00,11966.0 -2017-11-30 21:00:00,11838.0 -2017-11-30 22:00:00,11629.0 -2017-11-30 23:00:00,11139.0 -2017-12-01 00:00:00,10447.0 -2017-11-29 01:00:00,9541.0 -2017-11-29 02:00:00,9112.0 -2017-11-29 03:00:00,8905.0 -2017-11-29 04:00:00,8805.0 -2017-11-29 05:00:00,8812.0 -2017-11-29 06:00:00,9100.0 -2017-11-29 07:00:00,9912.0 -2017-11-29 08:00:00,10988.0 -2017-11-29 09:00:00,11412.0 -2017-11-29 10:00:00,11424.0 -2017-11-29 11:00:00,11395.0 -2017-11-29 12:00:00,11442.0 -2017-11-29 13:00:00,11332.0 -2017-11-29 14:00:00,11268.0 -2017-11-29 15:00:00,11302.0 -2017-11-29 16:00:00,11164.0 -2017-11-29 17:00:00,11173.0 -2017-11-29 18:00:00,11797.0 -2017-11-29 19:00:00,12318.0 -2017-11-29 20:00:00,12171.0 -2017-11-29 21:00:00,11998.0 -2017-11-29 22:00:00,11681.0 -2017-11-29 23:00:00,11148.0 -2017-11-30 00:00:00,10391.0 -2017-11-28 01:00:00,9554.0 -2017-11-28 02:00:00,9113.0 -2017-11-28 03:00:00,8804.0 -2017-11-28 04:00:00,8641.0 -2017-11-28 05:00:00,8621.0 -2017-11-28 06:00:00,8843.0 -2017-11-28 07:00:00,9542.0 -2017-11-28 08:00:00,10617.0 -2017-11-28 09:00:00,11035.0 -2017-11-28 10:00:00,11271.0 -2017-11-28 11:00:00,11353.0 -2017-11-28 12:00:00,11337.0 -2017-11-28 13:00:00,11314.0 -2017-11-28 14:00:00,11275.0 -2017-11-28 15:00:00,11321.0 -2017-11-28 16:00:00,11253.0 -2017-11-28 17:00:00,11350.0 -2017-11-28 18:00:00,11887.0 -2017-11-28 19:00:00,12054.0 -2017-11-28 20:00:00,11847.0 -2017-11-28 21:00:00,11642.0 -2017-11-28 22:00:00,11418.0 -2017-11-28 23:00:00,10888.0 -2017-11-29 00:00:00,10184.0 -2017-11-27 01:00:00,8900.0 -2017-11-27 02:00:00,8666.0 -2017-11-27 03:00:00,8535.0 -2017-11-27 04:00:00,8470.0 -2017-11-27 05:00:00,8574.0 -2017-11-27 06:00:00,8922.0 -2017-11-27 07:00:00,9741.0 -2017-11-27 08:00:00,10884.0 -2017-11-27 09:00:00,11338.0 -2017-11-27 10:00:00,11359.0 -2017-11-27 11:00:00,11255.0 -2017-11-27 12:00:00,11258.0 -2017-11-27 13:00:00,11167.0 -2017-11-27 14:00:00,11076.0 -2017-11-27 15:00:00,11100.0 -2017-11-27 16:00:00,11069.0 -2017-11-27 17:00:00,11067.0 -2017-11-27 18:00:00,11661.0 -2017-11-27 19:00:00,12097.0 -2017-11-27 20:00:00,11943.0 -2017-11-27 21:00:00,11757.0 -2017-11-27 22:00:00,11497.0 -2017-11-27 23:00:00,10962.0 -2017-11-28 00:00:00,10247.0 -2017-11-26 01:00:00,9130.0 -2017-11-26 02:00:00,8832.0 -2017-11-26 03:00:00,8631.0 -2017-11-26 04:00:00,8510.0 -2017-11-26 05:00:00,8478.0 -2017-11-26 06:00:00,8576.0 -2017-11-26 07:00:00,8738.0 -2017-11-26 08:00:00,9045.0 -2017-11-26 09:00:00,9070.0 -2017-11-26 10:00:00,9156.0 -2017-11-26 11:00:00,9278.0 -2017-11-26 12:00:00,9318.0 -2017-11-26 13:00:00,9329.0 -2017-11-26 14:00:00,9269.0 -2017-11-26 15:00:00,9154.0 -2017-11-26 16:00:00,9089.0 -2017-11-26 17:00:00,9229.0 -2017-11-26 18:00:00,9900.0 -2017-11-26 19:00:00,10605.0 -2017-11-26 20:00:00,10596.0 -2017-11-26 21:00:00,10458.0 -2017-11-26 22:00:00,10265.0 -2017-11-26 23:00:00,9891.0 -2017-11-27 00:00:00,9378.0 -2017-11-25 01:00:00,8733.0 -2017-11-25 02:00:00,8310.0 -2017-11-25 03:00:00,8076.0 -2017-11-25 04:00:00,7940.0 -2017-11-25 05:00:00,7910.0 -2017-11-25 06:00:00,8045.0 -2017-11-25 07:00:00,8327.0 -2017-11-25 08:00:00,8737.0 -2017-11-25 09:00:00,8913.0 -2017-11-25 10:00:00,9185.0 -2017-11-25 11:00:00,9370.0 -2017-11-25 12:00:00,9453.0 -2017-11-25 13:00:00,9444.0 -2017-11-25 14:00:00,9387.0 -2017-11-25 15:00:00,9226.0 -2017-11-25 16:00:00,9119.0 -2017-11-25 17:00:00,9211.0 -2017-11-25 18:00:00,9765.0 -2017-11-25 19:00:00,10334.0 -2017-11-25 20:00:00,10377.0 -2017-11-25 21:00:00,10279.0 -2017-11-25 22:00:00,10190.0 -2017-11-25 23:00:00,9960.0 -2017-11-26 00:00:00,9594.0 -2017-11-24 01:00:00,8860.0 -2017-11-24 02:00:00,8644.0 -2017-11-24 03:00:00,8477.0 -2017-11-24 04:00:00,8397.0 -2017-11-24 05:00:00,8405.0 -2017-11-24 06:00:00,8567.0 -2017-11-24 07:00:00,8975.0 -2017-11-24 08:00:00,9399.0 -2017-11-24 09:00:00,9446.0 -2017-11-24 10:00:00,9538.0 -2017-11-24 11:00:00,9564.0 -2017-11-24 12:00:00,9549.0 -2017-11-24 13:00:00,9532.0 -2017-11-24 14:00:00,9487.0 -2017-11-24 15:00:00,9419.0 -2017-11-24 16:00:00,9369.0 -2017-11-24 17:00:00,9386.0 -2017-11-24 18:00:00,9932.0 -2017-11-24 19:00:00,10420.0 -2017-11-24 20:00:00,10294.0 -2017-11-24 21:00:00,10169.0 -2017-11-24 22:00:00,9938.0 -2017-11-24 23:00:00,9656.0 -2017-11-25 00:00:00,9162.0 -2017-11-23 01:00:00,9998.0 -2017-11-23 02:00:00,9513.0 -2017-11-23 03:00:00,9187.0 -2017-11-23 04:00:00,8968.0 -2017-11-23 05:00:00,8891.0 -2017-11-23 06:00:00,8936.0 -2017-11-23 07:00:00,9081.0 -2017-11-23 08:00:00,9274.0 -2017-11-23 09:00:00,9240.0 -2017-11-23 10:00:00,9344.0 -2017-11-23 11:00:00,9388.0 -2017-11-23 12:00:00,9467.0 -2017-11-23 13:00:00,9470.0 -2017-11-23 14:00:00,9357.0 -2017-11-23 15:00:00,9151.0 -2017-11-23 16:00:00,8953.0 -2017-11-23 17:00:00,8901.0 -2017-11-23 18:00:00,9261.0 -2017-11-23 19:00:00,9609.0 -2017-11-23 20:00:00,9549.0 -2017-11-23 21:00:00,9504.0 -2017-11-23 22:00:00,9483.0 -2017-11-23 23:00:00,9378.0 -2017-11-24 00:00:00,9140.0 -2017-11-22 01:00:00,10412.0 -2017-11-22 02:00:00,9991.0 -2017-11-22 03:00:00,9785.0 -2017-11-22 04:00:00,9667.0 -2017-11-22 05:00:00,9670.0 -2017-11-22 06:00:00,9959.0 -2017-11-22 07:00:00,10639.0 -2017-11-22 08:00:00,11494.0 -2017-11-22 09:00:00,11814.0 -2017-11-22 10:00:00,11923.0 -2017-11-22 11:00:00,11976.0 -2017-11-22 12:00:00,11945.0 -2017-11-22 13:00:00,11840.0 -2017-11-22 14:00:00,11721.0 -2017-11-22 15:00:00,11611.0 -2017-11-22 16:00:00,11463.0 -2017-11-22 17:00:00,11536.0 -2017-11-22 18:00:00,12096.0 -2017-11-22 19:00:00,12445.0 -2017-11-22 20:00:00,12207.0 -2017-11-22 21:00:00,11968.0 -2017-11-22 22:00:00,11706.0 -2017-11-22 23:00:00,11290.0 -2017-11-23 00:00:00,10612.0 -2017-11-21 01:00:00,9888.0 -2017-11-21 02:00:00,9447.0 -2017-11-21 03:00:00,9189.0 -2017-11-21 04:00:00,9028.0 -2017-11-21 05:00:00,9025.0 -2017-11-21 06:00:00,9279.0 -2017-11-21 07:00:00,10012.0 -2017-11-21 08:00:00,10988.0 -2017-11-21 09:00:00,11405.0 -2017-11-21 10:00:00,11717.0 -2017-11-21 11:00:00,11850.0 -2017-11-21 12:00:00,11990.0 -2017-11-21 13:00:00,11948.0 -2017-11-21 14:00:00,11974.0 -2017-11-21 15:00:00,12018.0 -2017-11-21 16:00:00,11914.0 -2017-11-21 17:00:00,11812.0 -2017-11-21 18:00:00,12299.0 -2017-11-21 19:00:00,12643.0 -2017-11-21 20:00:00,12417.0 -2017-11-21 21:00:00,12254.0 -2017-11-21 22:00:00,12029.0 -2017-11-21 23:00:00,11565.0 -2017-11-22 00:00:00,11008.0 -2017-11-20 01:00:00,9858.0 -2017-11-20 02:00:00,9527.0 -2017-11-20 03:00:00,9405.0 -2017-11-20 04:00:00,9328.0 -2017-11-20 05:00:00,9382.0 -2017-11-20 06:00:00,9699.0 -2017-11-20 07:00:00,10414.0 -2017-11-20 08:00:00,11433.0 -2017-11-20 09:00:00,11797.0 -2017-11-20 10:00:00,11797.0 -2017-11-20 11:00:00,11776.0 -2017-11-20 12:00:00,11731.0 -2017-11-20 13:00:00,11616.0 -2017-11-20 14:00:00,11489.0 -2017-11-20 15:00:00,11418.0 -2017-11-20 16:00:00,11301.0 -2017-11-20 17:00:00,11257.0 -2017-11-20 18:00:00,11778.0 -2017-11-20 19:00:00,12240.0 -2017-11-20 20:00:00,12080.0 -2017-11-20 21:00:00,11891.0 -2017-11-20 22:00:00,11644.0 -2017-11-20 23:00:00,11173.0 -2017-11-21 00:00:00,10509.0 -2017-11-19 01:00:00,9595.0 -2017-11-19 02:00:00,9237.0 -2017-11-19 03:00:00,8985.0 -2017-11-19 04:00:00,8885.0 -2017-11-19 05:00:00,8852.0 -2017-11-19 06:00:00,8922.0 -2017-11-19 07:00:00,9110.0 -2017-11-19 08:00:00,9407.0 -2017-11-19 09:00:00,9463.0 -2017-11-19 10:00:00,9685.0 -2017-11-19 11:00:00,9800.0 -2017-11-19 12:00:00,9911.0 -2017-11-19 13:00:00,9904.0 -2017-11-19 14:00:00,9902.0 -2017-11-19 15:00:00,9847.0 -2017-11-19 16:00:00,9845.0 -2017-11-19 17:00:00,9969.0 -2017-11-19 18:00:00,10614.0 -2017-11-19 19:00:00,11191.0 -2017-11-19 20:00:00,11286.0 -2017-11-19 21:00:00,11262.0 -2017-11-19 22:00:00,11050.0 -2017-11-19 23:00:00,10783.0 -2017-11-20 00:00:00,10297.0 -2017-11-18 01:00:00,9645.0 -2017-11-18 02:00:00,9171.0 -2017-11-18 03:00:00,8829.0 -2017-11-18 04:00:00,8635.0 -2017-11-18 05:00:00,8553.0 -2017-11-18 06:00:00,8639.0 -2017-11-18 07:00:00,8990.0 -2017-11-18 08:00:00,9534.0 -2017-11-18 09:00:00,10027.0 -2017-11-18 10:00:00,10522.0 -2017-11-18 11:00:00,10928.0 -2017-11-18 12:00:00,11149.0 -2017-11-18 13:00:00,11147.0 -2017-11-18 14:00:00,11108.0 -2017-11-18 15:00:00,10995.0 -2017-11-18 16:00:00,10899.0 -2017-11-18 17:00:00,10909.0 -2017-11-18 18:00:00,11198.0 -2017-11-18 19:00:00,11394.0 -2017-11-18 20:00:00,11243.0 -2017-11-18 21:00:00,11080.0 -2017-11-18 22:00:00,10825.0 -2017-11-18 23:00:00,10563.0 -2017-11-19 00:00:00,10070.0 -2017-11-17 01:00:00,9993.0 -2017-11-17 02:00:00,9584.0 -2017-11-17 03:00:00,9307.0 -2017-11-17 04:00:00,9223.0 -2017-11-17 05:00:00,9244.0 -2017-11-17 06:00:00,9527.0 -2017-11-17 07:00:00,10256.0 -2017-11-17 08:00:00,11295.0 -2017-11-17 09:00:00,11681.0 -2017-11-17 10:00:00,11867.0 -2017-11-17 11:00:00,11930.0 -2017-11-17 12:00:00,11977.0 -2017-11-17 13:00:00,11897.0 -2017-11-17 14:00:00,11787.0 -2017-11-17 15:00:00,11821.0 -2017-11-17 16:00:00,11834.0 -2017-11-17 17:00:00,11828.0 -2017-11-17 18:00:00,12186.0 -2017-11-17 19:00:00,12199.0 -2017-11-17 20:00:00,11973.0 -2017-11-17 21:00:00,11714.0 -2017-11-17 22:00:00,11432.0 -2017-11-17 23:00:00,10925.0 -2017-11-18 00:00:00,10310.0 -2017-11-16 01:00:00,9993.0 -2017-11-16 02:00:00,9588.0 -2017-11-16 03:00:00,9325.0 -2017-11-16 04:00:00,9207.0 -2017-11-16 05:00:00,9190.0 -2017-11-16 06:00:00,9483.0 -2017-11-16 07:00:00,10234.0 -2017-11-16 08:00:00,11268.0 -2017-11-16 09:00:00,11741.0 -2017-11-16 10:00:00,11974.0 -2017-11-16 11:00:00,12008.0 -2017-11-16 12:00:00,12071.0 -2017-11-16 13:00:00,11980.0 -2017-11-16 14:00:00,11919.0 -2017-11-16 15:00:00,11936.0 -2017-11-16 16:00:00,11896.0 -2017-11-16 17:00:00,11976.0 -2017-11-16 18:00:00,12439.0 -2017-11-16 19:00:00,12634.0 -2017-11-16 20:00:00,12389.0 -2017-11-16 21:00:00,12133.0 -2017-11-16 22:00:00,11808.0 -2017-11-16 23:00:00,11360.0 -2017-11-17 00:00:00,10643.0 -2017-11-15 01:00:00,9634.0 -2017-11-15 02:00:00,9219.0 -2017-11-15 03:00:00,8943.0 -2017-11-15 04:00:00,8796.0 -2017-11-15 05:00:00,8813.0 -2017-11-15 06:00:00,9083.0 -2017-11-15 07:00:00,9816.0 -2017-11-15 08:00:00,10826.0 -2017-11-15 09:00:00,11499.0 -2017-11-15 10:00:00,11803.0 -2017-11-15 11:00:00,11858.0 -2017-11-15 12:00:00,11939.0 -2017-11-15 13:00:00,11890.0 -2017-11-15 14:00:00,11802.0 -2017-11-15 15:00:00,11741.0 -2017-11-15 16:00:00,11563.0 -2017-11-15 17:00:00,11429.0 -2017-11-15 18:00:00,11740.0 -2017-11-15 19:00:00,12213.0 -2017-11-15 20:00:00,12078.0 -2017-11-15 21:00:00,11967.0 -2017-11-15 22:00:00,11754.0 -2017-11-15 23:00:00,11329.0 -2017-11-16 00:00:00,10644.0 -2017-11-14 01:00:00,10043.0 -2017-11-14 02:00:00,9701.0 -2017-11-14 03:00:00,9459.0 -2017-11-14 04:00:00,9356.0 -2017-11-14 05:00:00,9344.0 -2017-11-14 06:00:00,9638.0 -2017-11-14 07:00:00,10409.0 -2017-11-14 08:00:00,11428.0 -2017-11-14 09:00:00,11718.0 -2017-11-14 10:00:00,11796.0 -2017-11-14 11:00:00,11782.0 -2017-11-14 12:00:00,11749.0 -2017-11-14 13:00:00,11719.0 -2017-11-14 14:00:00,11701.0 -2017-11-14 15:00:00,11725.0 -2017-11-14 16:00:00,11643.0 -2017-11-14 17:00:00,11668.0 -2017-11-14 18:00:00,12031.0 -2017-11-14 19:00:00,12285.0 -2017-11-14 20:00:00,12069.0 -2017-11-14 21:00:00,11824.0 -2017-11-14 22:00:00,11498.0 -2017-11-14 23:00:00,10983.0 -2017-11-15 00:00:00,10260.0 -2017-11-13 01:00:00,9221.0 -2017-11-13 02:00:00,8975.0 -2017-11-13 03:00:00,8870.0 -2017-11-13 04:00:00,8819.0 -2017-11-13 05:00:00,8991.0 -2017-11-13 06:00:00,9319.0 -2017-11-13 07:00:00,10149.0 -2017-11-13 08:00:00,11229.0 -2017-11-13 09:00:00,11705.0 -2017-11-13 10:00:00,11723.0 -2017-11-13 11:00:00,11745.0 -2017-11-13 12:00:00,11768.0 -2017-11-13 13:00:00,11747.0 -2017-11-13 14:00:00,11776.0 -2017-11-13 15:00:00,11822.0 -2017-11-13 16:00:00,11758.0 -2017-11-13 17:00:00,11764.0 -2017-11-13 18:00:00,12159.0 -2017-11-13 19:00:00,12516.0 -2017-11-13 20:00:00,12307.0 -2017-11-13 21:00:00,12103.0 -2017-11-13 22:00:00,11835.0 -2017-11-13 23:00:00,11331.0 -2017-11-14 00:00:00,10647.0 -2017-11-12 01:00:00,9524.0 -2017-11-12 02:00:00,9150.0 -2017-11-12 03:00:00,8934.0 -2017-11-12 04:00:00,8740.0 -2017-11-12 05:00:00,8725.0 -2017-11-12 06:00:00,8736.0 -2017-11-12 07:00:00,8881.0 -2017-11-12 08:00:00,9118.0 -2017-11-12 09:00:00,9196.0 -2017-11-12 10:00:00,9491.0 -2017-11-12 11:00:00,9795.0 -2017-11-12 12:00:00,9954.0 -2017-11-12 13:00:00,10061.0 -2017-11-12 14:00:00,10140.0 -2017-11-12 15:00:00,10097.0 -2017-11-12 16:00:00,10042.0 -2017-11-12 17:00:00,10060.0 -2017-11-12 18:00:00,10551.0 -2017-11-12 19:00:00,10888.0 -2017-11-12 20:00:00,10843.0 -2017-11-12 21:00:00,10729.0 -2017-11-12 22:00:00,10544.0 -2017-11-12 23:00:00,10162.0 -2017-11-13 00:00:00,9654.0 -2017-11-11 01:00:00,10605.0 -2017-11-11 02:00:00,10192.0 -2017-11-11 03:00:00,9942.0 -2017-11-11 04:00:00,9746.0 -2017-11-11 05:00:00,9668.0 -2017-11-11 06:00:00,9755.0 -2017-11-11 07:00:00,10075.0 -2017-11-11 08:00:00,10466.0 -2017-11-11 09:00:00,10763.0 -2017-11-11 10:00:00,11119.0 -2017-11-11 11:00:00,11362.0 -2017-11-11 12:00:00,11340.0 -2017-11-11 13:00:00,11217.0 -2017-11-11 14:00:00,11080.0 -2017-11-11 15:00:00,10916.0 -2017-11-11 16:00:00,10706.0 -2017-11-11 17:00:00,10753.0 -2017-11-11 18:00:00,11116.0 -2017-11-11 19:00:00,11415.0 -2017-11-11 20:00:00,11257.0 -2017-11-11 21:00:00,11063.0 -2017-11-11 22:00:00,10797.0 -2017-11-11 23:00:00,10495.0 -2017-11-12 00:00:00,9971.0 -2017-11-10 01:00:00,10424.0 -2017-11-10 02:00:00,10088.0 -2017-11-10 03:00:00,9878.0 -2017-11-10 04:00:00,9763.0 -2017-11-10 05:00:00,9811.0 -2017-11-10 06:00:00,10122.0 -2017-11-10 07:00:00,10806.0 -2017-11-10 08:00:00,11747.0 -2017-11-10 09:00:00,12214.0 -2017-11-10 10:00:00,12493.0 -2017-11-10 11:00:00,12524.0 -2017-11-10 12:00:00,12561.0 -2017-11-10 13:00:00,12526.0 -2017-11-10 14:00:00,12450.0 -2017-11-10 15:00:00,12428.0 -2017-11-10 16:00:00,12281.0 -2017-11-10 17:00:00,12178.0 -2017-11-10 18:00:00,12539.0 -2017-11-10 19:00:00,12856.0 -2017-11-10 20:00:00,12653.0 -2017-11-10 21:00:00,12486.0 -2017-11-10 22:00:00,12185.0 -2017-11-10 23:00:00,11786.0 -2017-11-11 00:00:00,11174.0 -2017-11-09 01:00:00,9814.0 -2017-11-09 02:00:00,9396.0 -2017-11-09 03:00:00,9207.0 -2017-11-09 04:00:00,9086.0 -2017-11-09 05:00:00,9092.0 -2017-11-09 06:00:00,9363.0 -2017-11-09 07:00:00,10034.0 -2017-11-09 08:00:00,11041.0 -2017-11-09 09:00:00,11506.0 -2017-11-09 10:00:00,11649.0 -2017-11-09 11:00:00,11583.0 -2017-11-09 12:00:00,11593.0 -2017-11-09 13:00:00,11580.0 -2017-11-09 14:00:00,11475.0 -2017-11-09 15:00:00,11437.0 -2017-11-09 16:00:00,11377.0 -2017-11-09 17:00:00,11395.0 -2017-11-09 18:00:00,11795.0 -2017-11-09 19:00:00,12411.0 -2017-11-09 20:00:00,12333.0 -2017-11-09 21:00:00,12227.0 -2017-11-09 22:00:00,12039.0 -2017-11-09 23:00:00,11591.0 -2017-11-10 00:00:00,10980.0 -2017-11-08 01:00:00,9751.0 -2017-11-08 02:00:00,9377.0 -2017-11-08 03:00:00,9184.0 -2017-11-08 04:00:00,9074.0 -2017-11-08 05:00:00,9097.0 -2017-11-08 06:00:00,9419.0 -2017-11-08 07:00:00,10161.0 -2017-11-08 08:00:00,11116.0 -2017-11-08 09:00:00,11448.0 -2017-11-08 10:00:00,11462.0 -2017-11-08 11:00:00,11426.0 -2017-11-08 12:00:00,11447.0 -2017-11-08 13:00:00,11344.0 -2017-11-08 14:00:00,11252.0 -2017-11-08 15:00:00,11204.0 -2017-11-08 16:00:00,11073.0 -2017-11-08 17:00:00,11034.0 -2017-11-08 18:00:00,11432.0 -2017-11-08 19:00:00,12035.0 -2017-11-08 20:00:00,11923.0 -2017-11-08 21:00:00,11777.0 -2017-11-08 22:00:00,11540.0 -2017-11-08 23:00:00,11068.0 -2017-11-09 00:00:00,10398.0 -2017-11-07 01:00:00,9454.0 -2017-11-07 02:00:00,9079.0 -2017-11-07 03:00:00,8835.0 -2017-11-07 04:00:00,8766.0 -2017-11-07 05:00:00,8781.0 -2017-11-07 06:00:00,9080.0 -2017-11-07 07:00:00,9844.0 -2017-11-07 08:00:00,10859.0 -2017-11-07 09:00:00,11242.0 -2017-11-07 10:00:00,11330.0 -2017-11-07 11:00:00,11378.0 -2017-11-07 12:00:00,11504.0 -2017-11-07 13:00:00,11454.0 -2017-11-07 14:00:00,11416.0 -2017-11-07 15:00:00,11431.0 -2017-11-07 16:00:00,11358.0 -2017-11-07 17:00:00,11380.0 -2017-11-07 18:00:00,11676.0 -2017-11-07 19:00:00,12137.0 -2017-11-07 20:00:00,11984.0 -2017-11-07 21:00:00,11803.0 -2017-11-07 22:00:00,11519.0 -2017-11-07 23:00:00,11018.0 -2017-11-08 00:00:00,10353.0 -2017-11-06 01:00:00,8530.0 -2017-11-06 02:00:00,8339.0 -2017-11-06 03:00:00,8207.0 -2017-11-06 04:00:00,8242.0 -2017-11-06 05:00:00,8343.0 -2017-11-06 06:00:00,8723.0 -2017-11-06 07:00:00,9647.0 -2017-11-06 08:00:00,10678.0 -2017-11-06 09:00:00,11160.0 -2017-11-06 10:00:00,11286.0 -2017-11-06 11:00:00,11272.0 -2017-11-06 12:00:00,11343.0 -2017-11-06 13:00:00,11363.0 -2017-11-06 14:00:00,11278.0 -2017-11-06 15:00:00,11290.0 -2017-11-06 16:00:00,11170.0 -2017-11-06 17:00:00,11152.0 -2017-11-06 18:00:00,11595.0 -2017-11-06 19:00:00,12124.0 -2017-11-06 20:00:00,11934.0 -2017-11-06 21:00:00,11700.0 -2017-11-06 22:00:00,11341.0 -2017-11-06 23:00:00,10795.0 -2017-11-07 00:00:00,10069.0 -2017-11-05 01:00:00,8576.0 -2017-11-05 02:00:00,8198.0 -2017-11-05 02:00:00,7878.0 -2017-11-05 03:00:00,7889.0 -2017-11-05 04:00:00,7636.0 -2017-11-05 05:00:00,7598.0 -2017-11-05 06:00:00,7642.0 -2017-11-05 07:00:00,7805.0 -2017-11-05 08:00:00,8130.0 -2017-11-05 09:00:00,8300.0 -2017-11-05 10:00:00,8658.0 -2017-11-05 11:00:00,8920.0 -2017-11-05 12:00:00,9067.0 -2017-11-05 13:00:00,9149.0 -2017-11-05 14:00:00,9225.0 -2017-11-05 15:00:00,9213.0 -2017-11-05 16:00:00,9234.0 -2017-11-05 17:00:00,9251.0 -2017-11-05 18:00:00,9740.0 -2017-11-05 19:00:00,10096.0 -2017-11-05 20:00:00,10002.0 -2017-11-05 21:00:00,9854.0 -2017-11-05 22:00:00,9663.0 -2017-11-05 23:00:00,9276.0 -2017-11-06 00:00:00,8818.0 -2017-11-04 01:00:00,9441.0 -2017-11-04 02:00:00,9024.0 -2017-11-04 03:00:00,8699.0 -2017-11-04 04:00:00,8555.0 -2017-11-04 05:00:00,8465.0 -2017-11-04 06:00:00,8529.0 -2017-11-04 07:00:00,8755.0 -2017-11-04 08:00:00,9177.0 -2017-11-04 09:00:00,9624.0 -2017-11-04 10:00:00,9973.0 -2017-11-04 11:00:00,10268.0 -2017-11-04 12:00:00,10370.0 -2017-11-04 13:00:00,10357.0 -2017-11-04 14:00:00,10234.0 -2017-11-04 15:00:00,10054.0 -2017-11-04 16:00:00,9879.0 -2017-11-04 17:00:00,9782.0 -2017-11-04 18:00:00,9784.0 -2017-11-04 19:00:00,10089.0 -2017-11-04 20:00:00,10268.0 -2017-11-04 21:00:00,10128.0 -2017-11-04 22:00:00,9947.0 -2017-11-04 23:00:00,9665.0 -2017-11-05 00:00:00,9242.0 -2017-11-03 01:00:00,9250.0 -2017-11-03 02:00:00,8832.0 -2017-11-03 03:00:00,8589.0 -2017-11-03 04:00:00,8462.0 -2017-11-03 05:00:00,8438.0 -2017-11-03 06:00:00,8698.0 -2017-11-03 07:00:00,9393.0 -2017-11-03 08:00:00,10432.0 -2017-11-03 09:00:00,11122.0 -2017-11-03 10:00:00,11213.0 -2017-11-03 11:00:00,11300.0 -2017-11-03 12:00:00,11342.0 -2017-11-03 13:00:00,11274.0 -2017-11-03 14:00:00,11150.0 -2017-11-03 15:00:00,10982.0 -2017-11-03 16:00:00,10845.0 -2017-11-03 17:00:00,10714.0 -2017-11-03 18:00:00,10681.0 -2017-11-03 19:00:00,10985.0 -2017-11-03 20:00:00,11259.0 -2017-11-03 21:00:00,11177.0 -2017-11-03 22:00:00,10982.0 -2017-11-03 23:00:00,10575.0 -2017-11-04 00:00:00,10043.0 -2017-11-02 01:00:00,9519.0 -2017-11-02 02:00:00,9062.0 -2017-11-02 03:00:00,8704.0 -2017-11-02 04:00:00,8492.0 -2017-11-02 05:00:00,8485.0 -2017-11-02 06:00:00,8655.0 -2017-11-02 07:00:00,9298.0 -2017-11-02 08:00:00,10410.0 -2017-11-02 09:00:00,11133.0 -2017-11-02 10:00:00,11243.0 -2017-11-02 11:00:00,11264.0 -2017-11-02 12:00:00,11280.0 -2017-11-02 13:00:00,11246.0 -2017-11-02 14:00:00,11262.0 -2017-11-02 15:00:00,11291.0 -2017-11-02 16:00:00,11212.0 -2017-11-02 17:00:00,11137.0 -2017-11-02 18:00:00,11182.0 -2017-11-02 19:00:00,11386.0 -2017-11-02 20:00:00,11439.0 -2017-11-02 21:00:00,11256.0 -2017-11-02 22:00:00,10990.0 -2017-11-02 23:00:00,10513.0 -2017-11-03 00:00:00,9858.0 -2017-11-01 01:00:00,9626.0 -2017-11-01 02:00:00,9242.0 -2017-11-01 03:00:00,8982.0 -2017-11-01 04:00:00,8830.0 -2017-11-01 05:00:00,8810.0 -2017-11-01 06:00:00,9086.0 -2017-11-01 07:00:00,9778.0 -2017-11-01 08:00:00,10884.0 -2017-11-01 09:00:00,11578.0 -2017-11-01 10:00:00,11596.0 -2017-11-01 11:00:00,11665.0 -2017-11-01 12:00:00,11653.0 -2017-11-01 13:00:00,11659.0 -2017-11-01 14:00:00,11559.0 -2017-11-01 15:00:00,11554.0 -2017-11-01 16:00:00,11494.0 -2017-11-01 17:00:00,11504.0 -2017-11-01 18:00:00,11644.0 -2017-11-01 19:00:00,11912.0 -2017-11-01 20:00:00,11964.0 -2017-11-01 21:00:00,11791.0 -2017-11-01 22:00:00,11447.0 -2017-11-01 23:00:00,10929.0 -2017-11-02 00:00:00,10166.0 -2017-10-31 01:00:00,9667.0 -2017-10-31 02:00:00,9291.0 -2017-10-31 03:00:00,9031.0 -2017-10-31 04:00:00,8922.0 -2017-10-31 05:00:00,8934.0 -2017-10-31 06:00:00,9158.0 -2017-10-31 07:00:00,9848.0 -2017-10-31 08:00:00,10963.0 -2017-10-31 09:00:00,11653.0 -2017-10-31 10:00:00,11734.0 -2017-10-31 11:00:00,11671.0 -2017-10-31 12:00:00,11655.0 -2017-10-31 13:00:00,11557.0 -2017-10-31 14:00:00,11418.0 -2017-10-31 15:00:00,11345.0 -2017-10-31 16:00:00,11178.0 -2017-10-31 17:00:00,11098.0 -2017-10-31 18:00:00,11124.0 -2017-10-31 19:00:00,11329.0 -2017-10-31 20:00:00,11667.0 -2017-10-31 21:00:00,11550.0 -2017-10-31 22:00:00,11337.0 -2017-10-31 23:00:00,10909.0 -2017-11-01 00:00:00,10258.0 -2017-10-30 01:00:00,8865.0 -2017-10-30 02:00:00,8557.0 -2017-10-30 03:00:00,8372.0 -2017-10-30 04:00:00,8279.0 -2017-10-30 05:00:00,8307.0 -2017-10-30 06:00:00,8571.0 -2017-10-30 07:00:00,9343.0 -2017-10-30 08:00:00,10549.0 -2017-10-30 09:00:00,11271.0 -2017-10-30 10:00:00,11373.0 -2017-10-30 11:00:00,11403.0 -2017-10-30 12:00:00,11384.0 -2017-10-30 13:00:00,11307.0 -2017-10-30 14:00:00,11218.0 -2017-10-30 15:00:00,11256.0 -2017-10-30 16:00:00,11213.0 -2017-10-30 17:00:00,11242.0 -2017-10-30 18:00:00,11350.0 -2017-10-30 19:00:00,11622.0 -2017-10-30 20:00:00,11867.0 -2017-10-30 21:00:00,11740.0 -2017-10-30 22:00:00,11467.0 -2017-10-30 23:00:00,10982.0 -2017-10-31 00:00:00,10287.0 -2017-10-29 01:00:00,9119.0 -2017-10-29 02:00:00,8748.0 -2017-10-29 03:00:00,8520.0 -2017-10-29 04:00:00,8346.0 -2017-10-29 05:00:00,8304.0 -2017-10-29 06:00:00,8296.0 -2017-10-29 07:00:00,8462.0 -2017-10-29 08:00:00,8740.0 -2017-10-29 09:00:00,8881.0 -2017-10-29 10:00:00,8970.0 -2017-10-29 11:00:00,9118.0 -2017-10-29 12:00:00,9152.0 -2017-10-29 13:00:00,9158.0 -2017-10-29 14:00:00,9127.0 -2017-10-29 15:00:00,9036.0 -2017-10-29 16:00:00,8963.0 -2017-10-29 17:00:00,8934.0 -2017-10-29 18:00:00,9049.0 -2017-10-29 19:00:00,9404.0 -2017-10-29 20:00:00,10121.0 -2017-10-29 21:00:00,10150.0 -2017-10-29 22:00:00,10017.0 -2017-10-29 23:00:00,9704.0 -2017-10-30 00:00:00,9287.0 -2017-10-28 01:00:00,9620.0 -2017-10-28 02:00:00,9170.0 -2017-10-28 03:00:00,8897.0 -2017-10-28 04:00:00,8659.0 -2017-10-28 05:00:00,8615.0 -2017-10-28 06:00:00,8657.0 -2017-10-28 07:00:00,8973.0 -2017-10-28 08:00:00,9441.0 -2017-10-28 09:00:00,9783.0 -2017-10-28 10:00:00,9980.0 -2017-10-28 11:00:00,10239.0 -2017-10-28 12:00:00,10258.0 -2017-10-28 13:00:00,10292.0 -2017-10-28 14:00:00,10249.0 -2017-10-28 15:00:00,10093.0 -2017-10-28 16:00:00,9948.0 -2017-10-28 17:00:00,9910.0 -2017-10-28 18:00:00,9964.0 -2017-10-28 19:00:00,10230.0 -2017-10-28 20:00:00,10542.0 -2017-10-28 21:00:00,10444.0 -2017-10-28 22:00:00,10253.0 -2017-10-28 23:00:00,9977.0 -2017-10-29 00:00:00,9556.0 -2017-10-27 01:00:00,9182.0 -2017-10-27 02:00:00,8740.0 -2017-10-27 03:00:00,8492.0 -2017-10-27 04:00:00,8375.0 -2017-10-27 05:00:00,8340.0 -2017-10-27 06:00:00,8583.0 -2017-10-27 07:00:00,9265.0 -2017-10-27 08:00:00,10390.0 -2017-10-27 09:00:00,11117.0 -2017-10-27 10:00:00,11325.0 -2017-10-27 11:00:00,11450.0 -2017-10-27 12:00:00,11608.0 -2017-10-27 13:00:00,11556.0 -2017-10-27 14:00:00,11419.0 -2017-10-27 15:00:00,11429.0 -2017-10-27 16:00:00,11355.0 -2017-10-27 17:00:00,11243.0 -2017-10-27 18:00:00,11259.0 -2017-10-27 19:00:00,11456.0 -2017-10-27 20:00:00,11617.0 -2017-10-27 21:00:00,11389.0 -2017-10-27 22:00:00,11217.0 -2017-10-27 23:00:00,10833.0 -2017-10-28 00:00:00,10238.0 -2017-10-26 01:00:00,9330.0 -2017-10-26 02:00:00,8915.0 -2017-10-26 03:00:00,8691.0 -2017-10-26 04:00:00,8549.0 -2017-10-26 05:00:00,8526.0 -2017-10-26 06:00:00,8735.0 -2017-10-26 07:00:00,9497.0 -2017-10-26 08:00:00,10652.0 -2017-10-26 09:00:00,11127.0 -2017-10-26 10:00:00,11137.0 -2017-10-26 11:00:00,11111.0 -2017-10-26 12:00:00,11094.0 -2017-10-26 13:00:00,11067.0 -2017-10-26 14:00:00,11049.0 -2017-10-26 15:00:00,11058.0 -2017-10-26 16:00:00,10983.0 -2017-10-26 17:00:00,10852.0 -2017-10-26 18:00:00,10781.0 -2017-10-26 19:00:00,10923.0 -2017-10-26 20:00:00,11360.0 -2017-10-26 21:00:00,11252.0 -2017-10-26 22:00:00,10998.0 -2017-10-26 23:00:00,10524.0 -2017-10-27 00:00:00,9829.0 -2017-10-25 01:00:00,9290.0 -2017-10-25 02:00:00,8858.0 -2017-10-25 03:00:00,8602.0 -2017-10-25 04:00:00,8474.0 -2017-10-25 05:00:00,8467.0 -2017-10-25 06:00:00,8683.0 -2017-10-25 07:00:00,9431.0 -2017-10-25 08:00:00,10564.0 -2017-10-25 09:00:00,11173.0 -2017-10-25 10:00:00,11214.0 -2017-10-25 11:00:00,11175.0 -2017-10-25 12:00:00,11091.0 -2017-10-25 13:00:00,11057.0 -2017-10-25 14:00:00,10944.0 -2017-10-25 15:00:00,10906.0 -2017-10-25 16:00:00,10795.0 -2017-10-25 17:00:00,10647.0 -2017-10-25 18:00:00,10537.0 -2017-10-25 19:00:00,10610.0 -2017-10-25 20:00:00,11158.0 -2017-10-25 21:00:00,11172.0 -2017-10-25 22:00:00,10972.0 -2017-10-25 23:00:00,10537.0 -2017-10-26 00:00:00,9907.0 -2017-10-24 01:00:00,8943.0 -2017-10-24 02:00:00,8540.0 -2017-10-24 03:00:00,8299.0 -2017-10-24 04:00:00,8090.0 -2017-10-24 05:00:00,8087.0 -2017-10-24 06:00:00,8287.0 -2017-10-24 07:00:00,8944.0 -2017-10-24 08:00:00,10093.0 -2017-10-24 09:00:00,10907.0 -2017-10-24 10:00:00,11139.0 -2017-10-24 11:00:00,11265.0 -2017-10-24 12:00:00,11401.0 -2017-10-24 13:00:00,11426.0 -2017-10-24 14:00:00,11376.0 -2017-10-24 15:00:00,11406.0 -2017-10-24 16:00:00,11277.0 -2017-10-24 17:00:00,11184.0 -2017-10-24 18:00:00,11221.0 -2017-10-24 19:00:00,11469.0 -2017-10-24 20:00:00,11613.0 -2017-10-24 21:00:00,11461.0 -2017-10-24 22:00:00,11134.0 -2017-10-24 23:00:00,10596.0 -2017-10-25 00:00:00,9911.0 -2017-10-23 01:00:00,8210.0 -2017-10-23 02:00:00,7939.0 -2017-10-23 03:00:00,7727.0 -2017-10-23 04:00:00,7629.0 -2017-10-23 05:00:00,7624.0 -2017-10-23 06:00:00,7910.0 -2017-10-23 07:00:00,8582.0 -2017-10-23 08:00:00,9695.0 -2017-10-23 09:00:00,10554.0 -2017-10-23 10:00:00,10737.0 -2017-10-23 11:00:00,10864.0 -2017-10-23 12:00:00,10999.0 -2017-10-23 13:00:00,11071.0 -2017-10-23 14:00:00,11015.0 -2017-10-23 15:00:00,11010.0 -2017-10-23 16:00:00,10870.0 -2017-10-23 17:00:00,10705.0 -2017-10-23 18:00:00,10705.0 -2017-10-23 19:00:00,10847.0 -2017-10-23 20:00:00,11108.0 -2017-10-23 21:00:00,10965.0 -2017-10-23 22:00:00,10638.0 -2017-10-23 23:00:00,10148.0 -2017-10-24 00:00:00,9532.0 -2017-10-22 01:00:00,8883.0 -2017-10-22 02:00:00,8397.0 -2017-10-22 03:00:00,8091.0 -2017-10-22 04:00:00,7834.0 -2017-10-22 05:00:00,7695.0 -2017-10-22 06:00:00,7686.0 -2017-10-22 07:00:00,7773.0 -2017-10-22 08:00:00,7995.0 -2017-10-22 09:00:00,8041.0 -2017-10-22 10:00:00,8421.0 -2017-10-22 11:00:00,8836.0 -2017-10-22 12:00:00,9146.0 -2017-10-22 13:00:00,9321.0 -2017-10-22 14:00:00,9456.0 -2017-10-22 15:00:00,9451.0 -2017-10-22 16:00:00,9375.0 -2017-10-22 17:00:00,9218.0 -2017-10-22 18:00:00,9178.0 -2017-10-22 19:00:00,9367.0 -2017-10-22 20:00:00,9676.0 -2017-10-22 21:00:00,9640.0 -2017-10-22 22:00:00,9419.0 -2017-10-22 23:00:00,9127.0 -2017-10-23 00:00:00,8674.0 -2017-10-21 01:00:00,9122.0 -2017-10-21 02:00:00,8631.0 -2017-10-21 03:00:00,8238.0 -2017-10-21 04:00:00,8021.0 -2017-10-21 05:00:00,7923.0 -2017-10-21 06:00:00,7941.0 -2017-10-21 07:00:00,8169.0 -2017-10-21 08:00:00,8600.0 -2017-10-21 09:00:00,8878.0 -2017-10-21 10:00:00,9194.0 -2017-10-21 11:00:00,9523.0 -2017-10-21 12:00:00,9696.0 -2017-10-21 13:00:00,9875.0 -2017-10-21 14:00:00,9977.0 -2017-10-21 15:00:00,10066.0 -2017-10-21 16:00:00,10130.0 -2017-10-21 17:00:00,10182.0 -2017-10-21 18:00:00,10117.0 -2017-10-21 19:00:00,10080.0 -2017-10-21 20:00:00,10507.0 -2017-10-21 21:00:00,10468.0 -2017-10-21 22:00:00,10261.0 -2017-10-21 23:00:00,9923.0 -2017-10-22 00:00:00,9420.0 -2017-10-20 01:00:00,8847.0 -2017-10-20 02:00:00,8388.0 -2017-10-20 03:00:00,8122.0 -2017-10-20 04:00:00,7935.0 -2017-10-20 05:00:00,7901.0 -2017-10-20 06:00:00,8095.0 -2017-10-20 07:00:00,8615.0 -2017-10-20 08:00:00,9671.0 -2017-10-20 09:00:00,10241.0 -2017-10-20 10:00:00,10511.0 -2017-10-20 11:00:00,10751.0 -2017-10-20 12:00:00,11067.0 -2017-10-20 13:00:00,11264.0 -2017-10-20 14:00:00,11408.0 -2017-10-20 15:00:00,11616.0 -2017-10-20 16:00:00,11646.0 -2017-10-20 17:00:00,11557.0 -2017-10-20 18:00:00,11401.0 -2017-10-20 19:00:00,11158.0 -2017-10-20 20:00:00,11316.0 -2017-10-20 21:00:00,11150.0 -2017-10-20 22:00:00,10816.0 -2017-10-20 23:00:00,10392.0 -2017-10-21 00:00:00,9763.0 -2017-10-19 01:00:00,8906.0 -2017-10-19 02:00:00,8413.0 -2017-10-19 03:00:00,8108.0 -2017-10-19 04:00:00,7945.0 -2017-10-19 05:00:00,7893.0 -2017-10-19 06:00:00,8064.0 -2017-10-19 07:00:00,8659.0 -2017-10-19 08:00:00,9713.0 -2017-10-19 09:00:00,10242.0 -2017-10-19 10:00:00,10460.0 -2017-10-19 11:00:00,10681.0 -2017-10-19 12:00:00,10929.0 -2017-10-19 13:00:00,11080.0 -2017-10-19 14:00:00,11065.0 -2017-10-19 15:00:00,11118.0 -2017-10-19 16:00:00,11183.0 -2017-10-19 17:00:00,11080.0 -2017-10-19 18:00:00,10901.0 -2017-10-19 19:00:00,10749.0 -2017-10-19 20:00:00,11052.0 -2017-10-19 21:00:00,10960.0 -2017-10-19 22:00:00,10671.0 -2017-10-19 23:00:00,10158.0 -2017-10-20 00:00:00,9476.0 -2017-10-18 01:00:00,8810.0 -2017-10-18 02:00:00,8381.0 -2017-10-18 03:00:00,8234.0 -2017-10-18 04:00:00,8053.0 -2017-10-18 05:00:00,7996.0 -2017-10-18 06:00:00,8197.0 -2017-10-18 07:00:00,8816.0 -2017-10-18 08:00:00,9911.0 -2017-10-18 09:00:00,10454.0 -2017-10-18 10:00:00,10619.0 -2017-10-18 11:00:00,10744.0 -2017-10-18 12:00:00,10925.0 -2017-10-18 13:00:00,11058.0 -2017-10-18 14:00:00,11121.0 -2017-10-18 15:00:00,11184.0 -2017-10-18 16:00:00,11146.0 -2017-10-18 17:00:00,11141.0 -2017-10-18 18:00:00,11041.0 -2017-10-18 19:00:00,10902.0 -2017-10-18 20:00:00,11185.0 -2017-10-18 21:00:00,11126.0 -2017-10-18 22:00:00,10819.0 -2017-10-18 23:00:00,10298.0 -2017-10-19 00:00:00,9562.0 -2017-10-17 01:00:00,8677.0 -2017-10-17 02:00:00,8231.0 -2017-10-17 03:00:00,7985.0 -2017-10-17 04:00:00,7828.0 -2017-10-17 05:00:00,7756.0 -2017-10-17 06:00:00,7993.0 -2017-10-17 07:00:00,8648.0 -2017-10-17 08:00:00,9719.0 -2017-10-17 09:00:00,10230.0 -2017-10-17 10:00:00,10429.0 -2017-10-17 11:00:00,10551.0 -2017-10-17 12:00:00,10763.0 -2017-10-17 13:00:00,10789.0 -2017-10-17 14:00:00,10846.0 -2017-10-17 15:00:00,11033.0 -2017-10-17 16:00:00,11005.0 -2017-10-17 17:00:00,10963.0 -2017-10-17 18:00:00,10887.0 -2017-10-17 19:00:00,10804.0 -2017-10-17 20:00:00,11065.0 -2017-10-17 21:00:00,11071.0 -2017-10-17 22:00:00,10696.0 -2017-10-17 23:00:00,10177.0 -2017-10-18 00:00:00,9458.0 -2017-10-16 01:00:00,8168.0 -2017-10-16 02:00:00,7850.0 -2017-10-16 03:00:00,7658.0 -2017-10-16 04:00:00,7581.0 -2017-10-16 05:00:00,7596.0 -2017-10-16 06:00:00,7809.0 -2017-10-16 07:00:00,8557.0 -2017-10-16 08:00:00,9674.0 -2017-10-16 09:00:00,10229.0 -2017-10-16 10:00:00,10424.0 -2017-10-16 11:00:00,10531.0 -2017-10-16 12:00:00,10623.0 -2017-10-16 13:00:00,10704.0 -2017-10-16 14:00:00,10758.0 -2017-10-16 15:00:00,10824.0 -2017-10-16 16:00:00,10780.0 -2017-10-16 17:00:00,10679.0 -2017-10-16 18:00:00,10576.0 -2017-10-16 19:00:00,10524.0 -2017-10-16 20:00:00,10868.0 -2017-10-16 21:00:00,10960.0 -2017-10-16 22:00:00,10659.0 -2017-10-16 23:00:00,10081.0 -2017-10-17 00:00:00,9384.0 -2017-10-15 01:00:00,9017.0 -2017-10-15 02:00:00,8586.0 -2017-10-15 03:00:00,8235.0 -2017-10-15 04:00:00,8029.0 -2017-10-15 05:00:00,7918.0 -2017-10-15 06:00:00,7909.0 -2017-10-15 07:00:00,7928.0 -2017-10-15 08:00:00,8066.0 -2017-10-15 09:00:00,8129.0 -2017-10-15 10:00:00,8284.0 -2017-10-15 11:00:00,8513.0 -2017-10-15 12:00:00,8710.0 -2017-10-15 13:00:00,8819.0 -2017-10-15 14:00:00,8846.0 -2017-10-15 15:00:00,8831.0 -2017-10-15 16:00:00,8767.0 -2017-10-15 17:00:00,8759.0 -2017-10-15 18:00:00,8788.0 -2017-10-15 19:00:00,8995.0 -2017-10-15 20:00:00,9524.0 -2017-10-15 21:00:00,9600.0 -2017-10-15 22:00:00,9422.0 -2017-10-15 23:00:00,9100.0 -2017-10-16 00:00:00,8631.0 -2017-10-14 01:00:00,9208.0 -2017-10-14 02:00:00,8693.0 -2017-10-14 03:00:00,8339.0 -2017-10-14 04:00:00,8149.0 -2017-10-14 05:00:00,8102.0 -2017-10-14 06:00:00,8102.0 -2017-10-14 07:00:00,8295.0 -2017-10-14 08:00:00,8688.0 -2017-10-14 09:00:00,9127.0 -2017-10-14 10:00:00,9555.0 -2017-10-14 11:00:00,9892.0 -2017-10-14 12:00:00,10044.0 -2017-10-14 13:00:00,10217.0 -2017-10-14 14:00:00,10283.0 -2017-10-14 15:00:00,10105.0 -2017-10-14 16:00:00,9947.0 -2017-10-14 17:00:00,9864.0 -2017-10-14 18:00:00,9957.0 -2017-10-14 19:00:00,10088.0 -2017-10-14 20:00:00,10357.0 -2017-10-14 21:00:00,10395.0 -2017-10-14 22:00:00,10279.0 -2017-10-14 23:00:00,9923.0 -2017-10-15 00:00:00,9474.0 -2017-10-13 01:00:00,9023.0 -2017-10-13 02:00:00,8488.0 -2017-10-13 03:00:00,8189.0 -2017-10-13 04:00:00,8009.0 -2017-10-13 05:00:00,7969.0 -2017-10-13 06:00:00,8102.0 -2017-10-13 07:00:00,8694.0 -2017-10-13 08:00:00,9714.0 -2017-10-13 09:00:00,10237.0 -2017-10-13 10:00:00,10481.0 -2017-10-13 11:00:00,10742.0 -2017-10-13 12:00:00,10900.0 -2017-10-13 13:00:00,11049.0 -2017-10-13 14:00:00,11137.0 -2017-10-13 15:00:00,11297.0 -2017-10-13 16:00:00,11388.0 -2017-10-13 17:00:00,11338.0 -2017-10-13 18:00:00,11223.0 -2017-10-13 19:00:00,11103.0 -2017-10-13 20:00:00,11314.0 -2017-10-13 21:00:00,11193.0 -2017-10-13 22:00:00,10939.0 -2017-10-13 23:00:00,10513.0 -2017-10-14 00:00:00,9897.0 -2017-10-12 01:00:00,8921.0 -2017-10-12 02:00:00,8428.0 -2017-10-12 03:00:00,8128.0 -2017-10-12 04:00:00,7958.0 -2017-10-12 05:00:00,7916.0 -2017-10-12 06:00:00,8080.0 -2017-10-12 07:00:00,8710.0 -2017-10-12 08:00:00,9768.0 -2017-10-12 09:00:00,10412.0 -2017-10-12 10:00:00,10671.0 -2017-10-12 11:00:00,10834.0 -2017-10-12 12:00:00,10976.0 -2017-10-12 13:00:00,10993.0 -2017-10-12 14:00:00,10961.0 -2017-10-12 15:00:00,10996.0 -2017-10-12 16:00:00,10878.0 -2017-10-12 17:00:00,10842.0 -2017-10-12 18:00:00,10762.0 -2017-10-12 19:00:00,10787.0 -2017-10-12 20:00:00,11027.0 -2017-10-12 21:00:00,11057.0 -2017-10-12 22:00:00,10770.0 -2017-10-12 23:00:00,10240.0 -2017-10-13 00:00:00,9600.0 -2017-10-11 01:00:00,9142.0 -2017-10-11 02:00:00,8714.0 -2017-10-11 03:00:00,8497.0 -2017-10-11 04:00:00,8301.0 -2017-10-11 05:00:00,8162.0 -2017-10-11 06:00:00,8350.0 -2017-10-11 07:00:00,8960.0 -2017-10-11 08:00:00,9987.0 -2017-10-11 09:00:00,10596.0 -2017-10-11 10:00:00,10857.0 -2017-10-11 11:00:00,11002.0 -2017-10-11 12:00:00,11105.0 -2017-10-11 13:00:00,11116.0 -2017-10-11 14:00:00,11128.0 -2017-10-11 15:00:00,11153.0 -2017-10-11 16:00:00,11132.0 -2017-10-11 17:00:00,10970.0 -2017-10-11 18:00:00,10975.0 -2017-10-11 19:00:00,11005.0 -2017-10-11 20:00:00,11215.0 -2017-10-11 21:00:00,11154.0 -2017-10-11 22:00:00,10879.0 -2017-10-11 23:00:00,10273.0 -2017-10-12 00:00:00,9593.0 -2017-10-10 01:00:00,9516.0 -2017-10-10 02:00:00,8907.0 -2017-10-10 03:00:00,8563.0 -2017-10-10 04:00:00,8357.0 -2017-10-10 05:00:00,8296.0 -2017-10-10 06:00:00,8400.0 -2017-10-10 07:00:00,9036.0 -2017-10-10 08:00:00,10090.0 -2017-10-10 09:00:00,10632.0 -2017-10-10 10:00:00,10958.0 -2017-10-10 11:00:00,11185.0 -2017-10-10 12:00:00,11355.0 -2017-10-10 13:00:00,11429.0 -2017-10-10 14:00:00,11419.0 -2017-10-10 15:00:00,11415.0 -2017-10-10 16:00:00,11287.0 -2017-10-10 17:00:00,11148.0 -2017-10-10 18:00:00,11102.0 -2017-10-10 19:00:00,11250.0 -2017-10-10 20:00:00,11360.0 -2017-10-10 21:00:00,11301.0 -2017-10-10 22:00:00,11044.0 -2017-10-10 23:00:00,10471.0 -2017-10-11 00:00:00,9788.0 -2017-10-09 01:00:00,8643.0 -2017-10-09 02:00:00,8265.0 -2017-10-09 03:00:00,8020.0 -2017-10-09 04:00:00,7818.0 -2017-10-09 05:00:00,7770.0 -2017-10-09 06:00:00,7984.0 -2017-10-09 07:00:00,8560.0 -2017-10-09 08:00:00,9334.0 -2017-10-09 09:00:00,9966.0 -2017-10-09 10:00:00,10517.0 -2017-10-09 11:00:00,11097.0 -2017-10-09 12:00:00,11525.0 -2017-10-09 13:00:00,11933.0 -2017-10-09 14:00:00,12247.0 -2017-10-09 15:00:00,12646.0 -2017-10-09 16:00:00,12795.0 -2017-10-09 17:00:00,12865.0 -2017-10-09 18:00:00,12788.0 -2017-10-09 19:00:00,12475.0 -2017-10-09 20:00:00,12435.0 -2017-10-09 21:00:00,12330.0 -2017-10-09 22:00:00,11714.0 -2017-10-09 23:00:00,11071.0 -2017-10-10 00:00:00,10257.0 -2017-10-08 01:00:00,8699.0 -2017-10-08 02:00:00,8209.0 -2017-10-08 03:00:00,7901.0 -2017-10-08 04:00:00,7621.0 -2017-10-08 05:00:00,7501.0 -2017-10-08 06:00:00,7421.0 -2017-10-08 07:00:00,7567.0 -2017-10-08 08:00:00,7716.0 -2017-10-08 09:00:00,7778.0 -2017-10-08 10:00:00,8121.0 -2017-10-08 11:00:00,8614.0 -2017-10-08 12:00:00,9013.0 -2017-10-08 13:00:00,9420.0 -2017-10-08 14:00:00,9705.0 -2017-10-08 15:00:00,9919.0 -2017-10-08 16:00:00,10118.0 -2017-10-08 17:00:00,10342.0 -2017-10-08 18:00:00,10417.0 -2017-10-08 19:00:00,10363.0 -2017-10-08 20:00:00,10435.0 -2017-10-08 21:00:00,10529.0 -2017-10-08 22:00:00,10249.0 -2017-10-08 23:00:00,9787.0 -2017-10-09 00:00:00,9252.0 -2017-10-07 01:00:00,9673.0 -2017-10-07 02:00:00,9106.0 -2017-10-07 03:00:00,8768.0 -2017-10-07 04:00:00,8501.0 -2017-10-07 05:00:00,8376.0 -2017-10-07 06:00:00,8322.0 -2017-10-07 07:00:00,8616.0 -2017-10-07 08:00:00,9019.0 -2017-10-07 09:00:00,9310.0 -2017-10-07 10:00:00,9823.0 -2017-10-07 11:00:00,10300.0 -2017-10-07 12:00:00,10736.0 -2017-10-07 13:00:00,11072.0 -2017-10-07 14:00:00,11186.0 -2017-10-07 15:00:00,11061.0 -2017-10-07 16:00:00,11037.0 -2017-10-07 17:00:00,10882.0 -2017-10-07 18:00:00,10440.0 -2017-10-07 19:00:00,10117.0 -2017-10-07 20:00:00,10134.0 -2017-10-07 21:00:00,10266.0 -2017-10-07 22:00:00,10058.0 -2017-10-07 23:00:00,9779.0 -2017-10-08 00:00:00,9226.0 -2017-10-06 01:00:00,9636.0 -2017-10-06 02:00:00,9044.0 -2017-10-06 03:00:00,8715.0 -2017-10-06 04:00:00,8491.0 -2017-10-06 05:00:00,8421.0 -2017-10-06 06:00:00,8561.0 -2017-10-06 07:00:00,9176.0 -2017-10-06 08:00:00,10251.0 -2017-10-06 09:00:00,10900.0 -2017-10-06 10:00:00,11236.0 -2017-10-06 11:00:00,11333.0 -2017-10-06 12:00:00,11691.0 -2017-10-06 13:00:00,11753.0 -2017-10-06 14:00:00,11775.0 -2017-10-06 15:00:00,11817.0 -2017-10-06 16:00:00,11819.0 -2017-10-06 17:00:00,11698.0 -2017-10-06 18:00:00,11673.0 -2017-10-06 19:00:00,11689.0 -2017-10-06 20:00:00,11808.0 -2017-10-06 21:00:00,11701.0 -2017-10-06 22:00:00,11361.0 -2017-10-06 23:00:00,10990.0 -2017-10-07 00:00:00,10307.0 -2017-10-05 01:00:00,9512.0 -2017-10-05 02:00:00,8912.0 -2017-10-05 03:00:00,8568.0 -2017-10-05 04:00:00,8314.0 -2017-10-05 05:00:00,8243.0 -2017-10-05 06:00:00,8382.0 -2017-10-05 07:00:00,8985.0 -2017-10-05 08:00:00,9936.0 -2017-10-05 09:00:00,10387.0 -2017-10-05 10:00:00,10799.0 -2017-10-05 11:00:00,11207.0 -2017-10-05 12:00:00,11569.0 -2017-10-05 13:00:00,11829.0 -2017-10-05 14:00:00,12064.0 -2017-10-05 15:00:00,12324.0 -2017-10-05 16:00:00,12403.0 -2017-10-05 17:00:00,12428.0 -2017-10-05 18:00:00,12312.0 -2017-10-05 19:00:00,11994.0 -2017-10-05 20:00:00,12056.0 -2017-10-05 21:00:00,12137.0 -2017-10-05 22:00:00,11771.0 -2017-10-05 23:00:00,11203.0 -2017-10-06 00:00:00,10392.0 -2017-10-04 01:00:00,10621.0 -2017-10-04 02:00:00,9993.0 -2017-10-04 03:00:00,9631.0 -2017-10-04 04:00:00,9369.0 -2017-10-04 05:00:00,9252.0 -2017-10-04 06:00:00,9500.0 -2017-10-04 07:00:00,10269.0 -2017-10-04 08:00:00,11474.0 -2017-10-04 09:00:00,12198.0 -2017-10-04 10:00:00,12515.0 -2017-10-04 11:00:00,12591.0 -2017-10-04 12:00:00,12628.0 -2017-10-04 13:00:00,12550.0 -2017-10-04 14:00:00,12493.0 -2017-10-04 15:00:00,12592.0 -2017-10-04 16:00:00,12732.0 -2017-10-04 17:00:00,12828.0 -2017-10-04 18:00:00,12678.0 -2017-10-04 19:00:00,12306.0 -2017-10-04 20:00:00,12190.0 -2017-10-04 21:00:00,12186.0 -2017-10-04 22:00:00,11809.0 -2017-10-04 23:00:00,11162.0 -2017-10-05 00:00:00,10304.0 -2017-10-03 01:00:00,9966.0 -2017-10-03 02:00:00,9374.0 -2017-10-03 03:00:00,8985.0 -2017-10-03 04:00:00,8761.0 -2017-10-03 05:00:00,8676.0 -2017-10-03 06:00:00,8844.0 -2017-10-03 07:00:00,9553.0 -2017-10-03 08:00:00,10738.0 -2017-10-03 09:00:00,11287.0 -2017-10-03 10:00:00,11767.0 -2017-10-03 11:00:00,12123.0 -2017-10-03 12:00:00,12531.0 -2017-10-03 13:00:00,12742.0 -2017-10-03 14:00:00,12900.0 -2017-10-03 15:00:00,13087.0 -2017-10-03 16:00:00,13284.0 -2017-10-03 17:00:00,13322.0 -2017-10-03 18:00:00,13172.0 -2017-10-03 19:00:00,13112.0 -2017-10-03 20:00:00,13132.0 -2017-10-03 21:00:00,13357.0 -2017-10-03 22:00:00,13004.0 -2017-10-03 23:00:00,12383.0 -2017-10-04 00:00:00,11453.0 -2017-10-02 01:00:00,8370.0 -2017-10-02 02:00:00,8049.0 -2017-10-02 03:00:00,7798.0 -2017-10-02 04:00:00,7687.0 -2017-10-02 05:00:00,7698.0 -2017-10-02 06:00:00,7955.0 -2017-10-02 07:00:00,8679.0 -2017-10-02 08:00:00,9802.0 -2017-10-02 09:00:00,10412.0 -2017-10-02 10:00:00,10864.0 -2017-10-02 11:00:00,11302.0 -2017-10-02 12:00:00,11677.0 -2017-10-02 13:00:00,11985.0 -2017-10-02 14:00:00,12277.0 -2017-10-02 15:00:00,12657.0 -2017-10-02 16:00:00,12790.0 -2017-10-02 17:00:00,12780.0 -2017-10-02 18:00:00,12651.0 -2017-10-02 19:00:00,12477.0 -2017-10-02 20:00:00,12471.0 -2017-10-02 21:00:00,12660.0 -2017-10-02 22:00:00,12391.0 -2017-10-02 23:00:00,11699.0 -2017-10-03 00:00:00,10804.0 -2017-10-01 01:00:00,8467.0 -2017-10-01 02:00:00,8027.0 -2017-10-01 03:00:00,7765.0 -2017-10-01 04:00:00,7563.0 -2017-10-01 05:00:00,7458.0 -2017-10-01 06:00:00,7409.0 -2017-10-01 07:00:00,7539.0 -2017-10-01 08:00:00,7668.0 -2017-10-01 09:00:00,7755.0 -2017-10-01 10:00:00,8072.0 -2017-10-01 11:00:00,8370.0 -2017-10-01 12:00:00,8646.0 -2017-10-01 13:00:00,8867.0 -2017-10-01 14:00:00,8981.0 -2017-10-01 15:00:00,9071.0 -2017-10-01 16:00:00,9133.0 -2017-10-01 17:00:00,9167.0 -2017-10-01 18:00:00,9237.0 -2017-10-01 19:00:00,9262.0 -2017-10-01 20:00:00,9643.0 -2017-10-01 21:00:00,9901.0 -2017-10-01 22:00:00,9707.0 -2017-10-01 23:00:00,9420.0 -2017-10-02 00:00:00,8948.0 -2017-09-30 01:00:00,8902.0 -2017-09-30 02:00:00,8478.0 -2017-09-30 03:00:00,8176.0 -2017-09-30 04:00:00,7952.0 -2017-09-30 05:00:00,7837.0 -2017-09-30 06:00:00,7855.0 -2017-09-30 07:00:00,8067.0 -2017-09-30 08:00:00,8416.0 -2017-09-30 09:00:00,8578.0 -2017-09-30 10:00:00,9058.0 -2017-09-30 11:00:00,9382.0 -2017-09-30 12:00:00,9655.0 -2017-09-30 13:00:00,9748.0 -2017-09-30 14:00:00,9802.0 -2017-09-30 15:00:00,9767.0 -2017-09-30 16:00:00,9754.0 -2017-09-30 17:00:00,9796.0 -2017-09-30 18:00:00,9774.0 -2017-09-30 19:00:00,9584.0 -2017-09-30 20:00:00,9563.0 -2017-09-30 21:00:00,9881.0 -2017-09-30 22:00:00,9698.0 -2017-09-30 23:00:00,9413.0 -2017-10-01 00:00:00,8974.0 -2017-09-29 01:00:00,9174.0 -2017-09-29 02:00:00,8705.0 -2017-09-29 03:00:00,8382.0 -2017-09-29 04:00:00,8212.0 -2017-09-29 05:00:00,8153.0 -2017-09-29 06:00:00,8327.0 -2017-09-29 07:00:00,8877.0 -2017-09-29 08:00:00,9834.0 -2017-09-29 09:00:00,10290.0 -2017-09-29 10:00:00,10759.0 -2017-09-29 11:00:00,11072.0 -2017-09-29 12:00:00,11358.0 -2017-09-29 13:00:00,11467.0 -2017-09-29 14:00:00,11560.0 -2017-09-29 15:00:00,11651.0 -2017-09-29 16:00:00,11609.0 -2017-09-29 17:00:00,11495.0 -2017-09-29 18:00:00,11288.0 -2017-09-29 19:00:00,10956.0 -2017-09-29 20:00:00,10811.0 -2017-09-29 21:00:00,10958.0 -2017-09-29 22:00:00,10651.0 -2017-09-29 23:00:00,10238.0 -2017-09-30 00:00:00,9536.0 -2017-09-28 01:00:00,9188.0 -2017-09-28 02:00:00,8668.0 -2017-09-28 03:00:00,8326.0 -2017-09-28 04:00:00,8112.0 -2017-09-28 05:00:00,8034.0 -2017-09-28 06:00:00,8193.0 -2017-09-28 07:00:00,8784.0 -2017-09-28 08:00:00,9747.0 -2017-09-28 09:00:00,10225.0 -2017-09-28 10:00:00,10625.0 -2017-09-28 11:00:00,11035.0 -2017-09-28 12:00:00,11357.0 -2017-09-28 13:00:00,11520.0 -2017-09-28 14:00:00,11642.0 -2017-09-28 15:00:00,11857.0 -2017-09-28 16:00:00,11986.0 -2017-09-28 17:00:00,12042.0 -2017-09-28 18:00:00,12026.0 -2017-09-28 19:00:00,11767.0 -2017-09-28 20:00:00,11532.0 -2017-09-28 21:00:00,11661.0 -2017-09-28 22:00:00,11307.0 -2017-09-28 23:00:00,10685.0 -2017-09-29 00:00:00,9901.0 -2017-09-27 01:00:00,12455.0 -2017-09-27 02:00:00,11472.0 -2017-09-27 03:00:00,10760.0 -2017-09-27 04:00:00,10219.0 -2017-09-27 05:00:00,9843.0 -2017-09-27 06:00:00,9790.0 -2017-09-27 07:00:00,10266.0 -2017-09-27 08:00:00,11166.0 -2017-09-27 09:00:00,11512.0 -2017-09-27 10:00:00,11811.0 -2017-09-27 11:00:00,11922.0 -2017-09-27 12:00:00,12155.0 -2017-09-27 13:00:00,12472.0 -2017-09-27 14:00:00,12847.0 -2017-09-27 15:00:00,13075.0 -2017-09-27 16:00:00,13073.0 -2017-09-27 17:00:00,12982.0 -2017-09-27 18:00:00,12660.0 -2017-09-27 19:00:00,12125.0 -2017-09-27 20:00:00,11943.0 -2017-09-27 21:00:00,12051.0 -2017-09-27 22:00:00,11546.0 -2017-09-27 23:00:00,10899.0 -2017-09-28 00:00:00,10038.0 -2017-09-26 01:00:00,12406.0 -2017-09-26 02:00:00,11476.0 -2017-09-26 03:00:00,10843.0 -2017-09-26 04:00:00,10394.0 -2017-09-26 05:00:00,10169.0 -2017-09-26 06:00:00,10241.0 -2017-09-26 07:00:00,10911.0 -2017-09-26 08:00:00,12025.0 -2017-09-26 09:00:00,12651.0 -2017-09-26 10:00:00,13452.0 -2017-09-26 11:00:00,14383.0 -2017-09-26 12:00:00,15564.0 -2017-09-26 13:00:00,16745.0 -2017-09-26 14:00:00,17736.0 -2017-09-26 15:00:00,18358.0 -2017-09-26 16:00:00,18608.0 -2017-09-26 17:00:00,18734.0 -2017-09-26 18:00:00,18647.0 -2017-09-26 19:00:00,18033.0 -2017-09-26 20:00:00,17352.0 -2017-09-26 21:00:00,17071.0 -2017-09-26 22:00:00,16318.0 -2017-09-26 23:00:00,15208.0 -2017-09-27 00:00:00,13786.0 -2017-09-25 01:00:00,11965.0 -2017-09-25 02:00:00,11149.0 -2017-09-25 03:00:00,10541.0 -2017-09-25 04:00:00,10181.0 -2017-09-25 05:00:00,9946.0 -2017-09-25 06:00:00,10077.0 -2017-09-25 07:00:00,10760.0 -2017-09-25 08:00:00,11794.0 -2017-09-25 09:00:00,12437.0 -2017-09-25 10:00:00,13358.0 -2017-09-25 11:00:00,14463.0 -2017-09-25 12:00:00,15649.0 -2017-09-25 13:00:00,16822.0 -2017-09-25 14:00:00,17688.0 -2017-09-25 15:00:00,18373.0 -2017-09-25 16:00:00,18684.0 -2017-09-25 17:00:00,18852.0 -2017-09-25 18:00:00,18735.0 -2017-09-25 19:00:00,18213.0 -2017-09-25 20:00:00,17386.0 -2017-09-25 21:00:00,16994.0 -2017-09-25 22:00:00,16131.0 -2017-09-25 23:00:00,15005.0 -2017-09-26 00:00:00,13706.0 -2017-09-24 01:00:00,12818.0 -2017-09-24 02:00:00,11883.0 -2017-09-24 03:00:00,11175.0 -2017-09-24 04:00:00,10580.0 -2017-09-24 05:00:00,10196.0 -2017-09-24 06:00:00,9980.0 -2017-09-24 07:00:00,9934.0 -2017-09-24 08:00:00,10018.0 -2017-09-24 09:00:00,10194.0 -2017-09-24 10:00:00,11272.0 -2017-09-24 11:00:00,12622.0 -2017-09-24 12:00:00,13971.0 -2017-09-24 13:00:00,15088.0 -2017-09-24 14:00:00,15979.0 -2017-09-24 15:00:00,16559.0 -2017-09-24 16:00:00,16986.0 -2017-09-24 17:00:00,17165.0 -2017-09-24 18:00:00,17149.0 -2017-09-24 19:00:00,16705.0 -2017-09-24 20:00:00,16009.0 -2017-09-24 21:00:00,15757.0 -2017-09-24 22:00:00,15016.0 -2017-09-24 23:00:00,14142.0 -2017-09-25 00:00:00,13025.0 -2017-09-23 01:00:00,13675.0 -2017-09-23 02:00:00,12653.0 -2017-09-23 03:00:00,11882.0 -2017-09-23 04:00:00,11303.0 -2017-09-23 05:00:00,10899.0 -2017-09-23 06:00:00,10730.0 -2017-09-23 07:00:00,10855.0 -2017-09-23 08:00:00,11134.0 -2017-09-23 09:00:00,11567.0 -2017-09-23 10:00:00,12764.0 -2017-09-23 11:00:00,14190.0 -2017-09-23 12:00:00,15593.0 -2017-09-23 13:00:00,16823.0 -2017-09-23 14:00:00,17582.0 -2017-09-23 15:00:00,18056.0 -2017-09-23 16:00:00,18364.0 -2017-09-23 17:00:00,18510.0 -2017-09-23 18:00:00,18373.0 -2017-09-23 19:00:00,17816.0 -2017-09-23 20:00:00,16973.0 -2017-09-23 21:00:00,16530.0 -2017-09-23 22:00:00,15793.0 -2017-09-23 23:00:00,14901.0 -2017-09-24 00:00:00,13868.0 -2017-09-22 01:00:00,13860.0 -2017-09-22 02:00:00,12838.0 -2017-09-22 03:00:00,12104.0 -2017-09-22 04:00:00,11536.0 -2017-09-22 05:00:00,11230.0 -2017-09-22 06:00:00,11227.0 -2017-09-22 07:00:00,11802.0 -2017-09-22 08:00:00,12747.0 -2017-09-22 09:00:00,13516.0 -2017-09-22 10:00:00,14580.0 -2017-09-22 11:00:00,15859.0 -2017-09-22 12:00:00,17237.0 -2017-09-22 13:00:00,18190.0 -2017-09-22 14:00:00,18944.0 -2017-09-22 15:00:00,19533.0 -2017-09-22 16:00:00,19889.0 -2017-09-22 17:00:00,20040.0 -2017-09-22 18:00:00,19979.0 -2017-09-22 19:00:00,19391.0 -2017-09-22 20:00:00,18477.0 -2017-09-22 21:00:00,17950.0 -2017-09-22 22:00:00,17070.0 -2017-09-22 23:00:00,16145.0 -2017-09-23 00:00:00,14893.0 -2017-09-21 01:00:00,13235.0 -2017-09-21 02:00:00,12307.0 -2017-09-21 03:00:00,11632.0 -2017-09-21 04:00:00,11097.0 -2017-09-21 05:00:00,10797.0 -2017-09-21 06:00:00,10877.0 -2017-09-21 07:00:00,11491.0 -2017-09-21 08:00:00,12542.0 -2017-09-21 09:00:00,13289.0 -2017-09-21 10:00:00,14312.0 -2017-09-21 11:00:00,15376.0 -2017-09-21 12:00:00,16513.0 -2017-09-21 13:00:00,17512.0 -2017-09-21 14:00:00,18347.0 -2017-09-21 15:00:00,19003.0 -2017-09-21 16:00:00,19333.0 -2017-09-21 17:00:00,19518.0 -2017-09-21 18:00:00,19443.0 -2017-09-21 19:00:00,18836.0 -2017-09-21 20:00:00,18130.0 -2017-09-21 21:00:00,17932.0 -2017-09-21 22:00:00,17301.0 -2017-09-21 23:00:00,16403.0 -2017-09-22 00:00:00,15091.0 -2017-09-20 01:00:00,10413.0 -2017-09-20 02:00:00,9721.0 -2017-09-20 03:00:00,9284.0 -2017-09-20 04:00:00,9018.0 -2017-09-20 05:00:00,8885.0 -2017-09-20 06:00:00,9043.0 -2017-09-20 07:00:00,9729.0 -2017-09-20 08:00:00,10827.0 -2017-09-20 09:00:00,11505.0 -2017-09-20 10:00:00,12304.0 -2017-09-20 11:00:00,13167.0 -2017-09-20 12:00:00,14141.0 -2017-09-20 13:00:00,15128.0 -2017-09-20 14:00:00,16112.0 -2017-09-20 15:00:00,17084.0 -2017-09-20 16:00:00,17800.0 -2017-09-20 17:00:00,18347.0 -2017-09-20 18:00:00,18585.0 -2017-09-20 19:00:00,18356.0 -2017-09-20 20:00:00,17711.0 -2017-09-20 21:00:00,17494.0 -2017-09-20 22:00:00,16839.0 -2017-09-20 23:00:00,15803.0 -2017-09-21 00:00:00,14456.0 -2017-09-19 01:00:00,10127.0 -2017-09-19 02:00:00,9614.0 -2017-09-19 03:00:00,9231.0 -2017-09-19 04:00:00,8999.0 -2017-09-19 05:00:00,8855.0 -2017-09-19 06:00:00,9039.0 -2017-09-19 07:00:00,9730.0 -2017-09-19 08:00:00,10919.0 -2017-09-19 09:00:00,11629.0 -2017-09-19 10:00:00,12067.0 -2017-09-19 11:00:00,12451.0 -2017-09-19 12:00:00,12865.0 -2017-09-19 13:00:00,13341.0 -2017-09-19 14:00:00,13938.0 -2017-09-19 15:00:00,14426.0 -2017-09-19 16:00:00,14598.0 -2017-09-19 17:00:00,14719.0 -2017-09-19 18:00:00,14773.0 -2017-09-19 19:00:00,14415.0 -2017-09-19 20:00:00,13775.0 -2017-09-19 21:00:00,13721.0 -2017-09-19 22:00:00,13185.0 -2017-09-19 23:00:00,12359.0 -2017-09-20 00:00:00,11291.0 -2017-09-18 01:00:00,9987.0 -2017-09-18 02:00:00,9277.0 -2017-09-18 03:00:00,8777.0 -2017-09-18 04:00:00,8487.0 -2017-09-18 05:00:00,8380.0 -2017-09-18 06:00:00,8546.0 -2017-09-18 07:00:00,9205.0 -2017-09-18 08:00:00,10158.0 -2017-09-18 09:00:00,10791.0 -2017-09-18 10:00:00,11503.0 -2017-09-18 11:00:00,12048.0 -2017-09-18 12:00:00,12604.0 -2017-09-18 13:00:00,13018.0 -2017-09-18 14:00:00,13370.0 -2017-09-18 15:00:00,13766.0 -2017-09-18 16:00:00,14024.0 -2017-09-18 17:00:00,14176.0 -2017-09-18 18:00:00,13970.0 -2017-09-18 19:00:00,13293.0 -2017-09-18 20:00:00,12757.0 -2017-09-18 21:00:00,12882.0 -2017-09-18 22:00:00,12538.0 -2017-09-18 23:00:00,11900.0 -2017-09-19 00:00:00,11031.0 -2017-09-17 01:00:00,11269.0 -2017-09-17 02:00:00,10482.0 -2017-09-17 03:00:00,9958.0 -2017-09-17 04:00:00,9508.0 -2017-09-17 05:00:00,9204.0 -2017-09-17 06:00:00,9106.0 -2017-09-17 07:00:00,9111.0 -2017-09-17 08:00:00,9181.0 -2017-09-17 09:00:00,9341.0 -2017-09-17 10:00:00,10102.0 -2017-09-17 11:00:00,11083.0 -2017-09-17 12:00:00,12273.0 -2017-09-17 13:00:00,13321.0 -2017-09-17 14:00:00,14091.0 -2017-09-17 15:00:00,14260.0 -2017-09-17 16:00:00,14102.0 -2017-09-17 17:00:00,14048.0 -2017-09-17 18:00:00,14064.0 -2017-09-17 19:00:00,13756.0 -2017-09-17 20:00:00,13257.0 -2017-09-17 21:00:00,13252.0 -2017-09-17 22:00:00,12780.0 -2017-09-17 23:00:00,11971.0 -2017-09-18 00:00:00,11000.0 -2017-09-16 01:00:00,11474.0 -2017-09-16 02:00:00,10678.0 -2017-09-16 03:00:00,10002.0 -2017-09-16 04:00:00,9542.0 -2017-09-16 05:00:00,9313.0 -2017-09-16 06:00:00,9187.0 -2017-09-16 07:00:00,9319.0 -2017-09-16 08:00:00,9540.0 -2017-09-16 09:00:00,9929.0 -2017-09-16 10:00:00,10937.0 -2017-09-16 11:00:00,11925.0 -2017-09-16 12:00:00,12927.0 -2017-09-16 13:00:00,13852.0 -2017-09-16 14:00:00,14427.0 -2017-09-16 15:00:00,14709.0 -2017-09-16 16:00:00,15013.0 -2017-09-16 17:00:00,15226.0 -2017-09-16 18:00:00,15216.0 -2017-09-16 19:00:00,14855.0 -2017-09-16 20:00:00,14234.0 -2017-09-16 21:00:00,14020.0 -2017-09-16 22:00:00,13527.0 -2017-09-16 23:00:00,12963.0 -2017-09-17 00:00:00,12105.0 -2017-09-15 01:00:00,10357.0 -2017-09-15 02:00:00,9665.0 -2017-09-15 03:00:00,9194.0 -2017-09-15 04:00:00,8868.0 -2017-09-15 05:00:00,8695.0 -2017-09-15 06:00:00,8821.0 -2017-09-15 07:00:00,9412.0 -2017-09-15 08:00:00,10375.0 -2017-09-15 09:00:00,11057.0 -2017-09-15 10:00:00,11742.0 -2017-09-15 11:00:00,12436.0 -2017-09-15 12:00:00,13200.0 -2017-09-15 13:00:00,13890.0 -2017-09-15 14:00:00,14479.0 -2017-09-15 15:00:00,15074.0 -2017-09-15 16:00:00,15479.0 -2017-09-15 17:00:00,15796.0 -2017-09-15 18:00:00,15944.0 -2017-09-15 19:00:00,15644.0 -2017-09-15 20:00:00,14958.0 -2017-09-15 21:00:00,14707.0 -2017-09-15 22:00:00,14148.0 -2017-09-15 23:00:00,13499.0 -2017-09-16 00:00:00,12544.0 -2017-09-14 01:00:00,9690.0 -2017-09-14 02:00:00,9072.0 -2017-09-14 03:00:00,8688.0 -2017-09-14 04:00:00,8421.0 -2017-09-14 05:00:00,8300.0 -2017-09-14 06:00:00,8478.0 -2017-09-14 07:00:00,9113.0 -2017-09-14 08:00:00,10078.0 -2017-09-14 09:00:00,10720.0 -2017-09-14 10:00:00,11172.0 -2017-09-14 11:00:00,11561.0 -2017-09-14 12:00:00,11983.0 -2017-09-14 13:00:00,12400.0 -2017-09-14 14:00:00,12797.0 -2017-09-14 15:00:00,13247.0 -2017-09-14 16:00:00,13568.0 -2017-09-14 17:00:00,13850.0 -2017-09-14 18:00:00,13976.0 -2017-09-14 19:00:00,13801.0 -2017-09-14 20:00:00,13309.0 -2017-09-14 21:00:00,13353.0 -2017-09-14 22:00:00,13066.0 -2017-09-14 23:00:00,12341.0 -2017-09-15 00:00:00,11305.0 -2017-09-13 01:00:00,9366.0 -2017-09-13 02:00:00,8876.0 -2017-09-13 03:00:00,8543.0 -2017-09-13 04:00:00,8307.0 -2017-09-13 05:00:00,8270.0 -2017-09-13 06:00:00,8428.0 -2017-09-13 07:00:00,9072.0 -2017-09-13 08:00:00,10128.0 -2017-09-13 09:00:00,10764.0 -2017-09-13 10:00:00,11150.0 -2017-09-13 11:00:00,11368.0 -2017-09-13 12:00:00,11733.0 -2017-09-13 13:00:00,12018.0 -2017-09-13 14:00:00,12200.0 -2017-09-13 15:00:00,12427.0 -2017-09-13 16:00:00,12440.0 -2017-09-13 17:00:00,12326.0 -2017-09-13 18:00:00,12235.0 -2017-09-13 19:00:00,12096.0 -2017-09-13 20:00:00,11860.0 -2017-09-13 21:00:00,12120.0 -2017-09-13 22:00:00,11940.0 -2017-09-13 23:00:00,11347.0 -2017-09-14 00:00:00,10515.0 -2017-09-12 01:00:00,9021.0 -2017-09-12 02:00:00,8568.0 -2017-09-12 03:00:00,8263.0 -2017-09-12 04:00:00,8099.0 -2017-09-12 05:00:00,8047.0 -2017-09-12 06:00:00,8210.0 -2017-09-12 07:00:00,8847.0 -2017-09-12 08:00:00,9771.0 -2017-09-12 09:00:00,10378.0 -2017-09-12 10:00:00,10931.0 -2017-09-12 11:00:00,11310.0 -2017-09-12 12:00:00,11637.0 -2017-09-12 13:00:00,11837.0 -2017-09-12 14:00:00,12011.0 -2017-09-12 15:00:00,12207.0 -2017-09-12 16:00:00,12310.0 -2017-09-12 17:00:00,12272.0 -2017-09-12 18:00:00,12220.0 -2017-09-12 19:00:00,11886.0 -2017-09-12 20:00:00,11506.0 -2017-09-12 21:00:00,11678.0 -2017-09-12 22:00:00,11508.0 -2017-09-12 23:00:00,10902.0 -2017-09-13 00:00:00,10116.0 -2017-09-11 01:00:00,8416.0 -2017-09-11 02:00:00,8053.0 -2017-09-11 03:00:00,7799.0 -2017-09-11 04:00:00,7677.0 -2017-09-11 05:00:00,7601.0 -2017-09-11 06:00:00,7800.0 -2017-09-11 07:00:00,8449.0 -2017-09-11 08:00:00,9351.0 -2017-09-11 09:00:00,9960.0 -2017-09-11 10:00:00,10369.0 -2017-09-11 11:00:00,10738.0 -2017-09-11 12:00:00,11105.0 -2017-09-11 13:00:00,11308.0 -2017-09-11 14:00:00,11468.0 -2017-09-11 15:00:00,11655.0 -2017-09-11 16:00:00,11731.0 -2017-09-11 17:00:00,11699.0 -2017-09-11 18:00:00,11547.0 -2017-09-11 19:00:00,11280.0 -2017-09-11 20:00:00,11004.0 -2017-09-11 21:00:00,11216.0 -2017-09-11 22:00:00,11081.0 -2017-09-11 23:00:00,10499.0 -2017-09-12 00:00:00,9774.0 -2017-09-10 01:00:00,8528.0 -2017-09-10 02:00:00,8083.0 -2017-09-10 03:00:00,7730.0 -2017-09-10 04:00:00,7468.0 -2017-09-10 05:00:00,7314.0 -2017-09-10 06:00:00,7263.0 -2017-09-10 07:00:00,7351.0 -2017-09-10 08:00:00,7402.0 -2017-09-10 09:00:00,7520.0 -2017-09-10 10:00:00,7934.0 -2017-09-10 11:00:00,8390.0 -2017-09-10 12:00:00,8669.0 -2017-09-10 13:00:00,8896.0 -2017-09-10 14:00:00,9033.0 -2017-09-10 15:00:00,9128.0 -2017-09-10 16:00:00,9270.0 -2017-09-10 17:00:00,9363.0 -2017-09-10 18:00:00,9470.0 -2017-09-10 19:00:00,9481.0 -2017-09-10 20:00:00,9439.0 -2017-09-10 21:00:00,9799.0 -2017-09-10 22:00:00,9847.0 -2017-09-10 23:00:00,9464.0 -2017-09-11 00:00:00,8964.0 -2017-09-09 01:00:00,8909.0 -2017-09-09 02:00:00,8494.0 -2017-09-09 03:00:00,8140.0 -2017-09-09 04:00:00,7949.0 -2017-09-09 05:00:00,7823.0 -2017-09-09 06:00:00,7840.0 -2017-09-09 07:00:00,8100.0 -2017-09-09 08:00:00,8318.0 -2017-09-09 09:00:00,8622.0 -2017-09-09 10:00:00,9128.0 -2017-09-09 11:00:00,9519.0 -2017-09-09 12:00:00,9774.0 -2017-09-09 13:00:00,9868.0 -2017-09-09 14:00:00,9929.0 -2017-09-09 15:00:00,9904.0 -2017-09-09 16:00:00,9913.0 -2017-09-09 17:00:00,9931.0 -2017-09-09 18:00:00,9950.0 -2017-09-09 19:00:00,9847.0 -2017-09-09 20:00:00,9636.0 -2017-09-09 21:00:00,9875.0 -2017-09-09 22:00:00,9833.0 -2017-09-09 23:00:00,9552.0 -2017-09-10 00:00:00,9055.0 -2017-09-08 01:00:00,8892.0 -2017-09-08 02:00:00,8427.0 -2017-09-08 03:00:00,8122.0 -2017-09-08 04:00:00,7938.0 -2017-09-08 05:00:00,7862.0 -2017-09-08 06:00:00,8007.0 -2017-09-08 07:00:00,8598.0 -2017-09-08 08:00:00,9607.0 -2017-09-08 09:00:00,10207.0 -2017-09-08 10:00:00,10648.0 -2017-09-08 11:00:00,10972.0 -2017-09-08 12:00:00,11263.0 -2017-09-08 13:00:00,11379.0 -2017-09-08 14:00:00,11382.0 -2017-09-08 15:00:00,11423.0 -2017-09-08 16:00:00,11345.0 -2017-09-08 17:00:00,11211.0 -2017-09-08 18:00:00,10987.0 -2017-09-08 19:00:00,10707.0 -2017-09-08 20:00:00,10491.0 -2017-09-08 21:00:00,10659.0 -2017-09-08 22:00:00,10643.0 -2017-09-08 23:00:00,10231.0 -2017-09-09 00:00:00,9637.0 -2017-09-07 01:00:00,8740.0 -2017-09-07 02:00:00,8285.0 -2017-09-07 03:00:00,7989.0 -2017-09-07 04:00:00,7831.0 -2017-09-07 05:00:00,7809.0 -2017-09-07 06:00:00,7961.0 -2017-09-07 07:00:00,8559.0 -2017-09-07 08:00:00,9475.0 -2017-09-07 09:00:00,10031.0 -2017-09-07 10:00:00,10418.0 -2017-09-07 11:00:00,10724.0 -2017-09-07 12:00:00,11012.0 -2017-09-07 13:00:00,11096.0 -2017-09-07 14:00:00,11147.0 -2017-09-07 15:00:00,11200.0 -2017-09-07 16:00:00,11118.0 -2017-09-07 17:00:00,11032.0 -2017-09-07 18:00:00,10905.0 -2017-09-07 19:00:00,10738.0 -2017-09-07 20:00:00,10510.0 -2017-09-07 21:00:00,10829.0 -2017-09-07 22:00:00,10827.0 -2017-09-07 23:00:00,10313.0 -2017-09-08 00:00:00,9577.0 -2017-09-06 01:00:00,8866.0 -2017-09-06 02:00:00,8381.0 -2017-09-06 03:00:00,8091.0 -2017-09-06 04:00:00,7888.0 -2017-09-06 05:00:00,7836.0 -2017-09-06 06:00:00,8008.0 -2017-09-06 07:00:00,8575.0 -2017-09-06 08:00:00,9433.0 -2017-09-06 09:00:00,10007.0 -2017-09-06 10:00:00,10350.0 -2017-09-06 11:00:00,10612.0 -2017-09-06 12:00:00,10875.0 -2017-09-06 13:00:00,11040.0 -2017-09-06 14:00:00,11086.0 -2017-09-06 15:00:00,11178.0 -2017-09-06 16:00:00,11077.0 -2017-09-06 17:00:00,10913.0 -2017-09-06 18:00:00,10805.0 -2017-09-06 19:00:00,10649.0 -2017-09-06 20:00:00,10488.0 -2017-09-06 21:00:00,10741.0 -2017-09-06 22:00:00,10668.0 -2017-09-06 23:00:00,10154.0 -2017-09-07 00:00:00,9448.0 -2017-09-05 01:00:00,8889.0 -2017-09-05 02:00:00,8376.0 -2017-09-05 03:00:00,8065.0 -2017-09-05 04:00:00,7853.0 -2017-09-05 05:00:00,7725.0 -2017-09-05 06:00:00,7889.0 -2017-09-05 07:00:00,8534.0 -2017-09-05 08:00:00,9417.0 -2017-09-05 09:00:00,10076.0 -2017-09-05 10:00:00,10546.0 -2017-09-05 11:00:00,10888.0 -2017-09-05 12:00:00,11298.0 -2017-09-05 13:00:00,11536.0 -2017-09-05 14:00:00,11650.0 -2017-09-05 15:00:00,11801.0 -2017-09-05 16:00:00,11763.0 -2017-09-05 17:00:00,11625.0 -2017-09-05 18:00:00,11367.0 -2017-09-05 19:00:00,11007.0 -2017-09-05 20:00:00,10711.0 -2017-09-05 21:00:00,10905.0 -2017-09-05 22:00:00,10838.0 -2017-09-05 23:00:00,10301.0 -2017-09-06 00:00:00,9557.0 -2017-09-04 01:00:00,9306.0 -2017-09-04 02:00:00,8786.0 -2017-09-04 03:00:00,8370.0 -2017-09-04 04:00:00,8107.0 -2017-09-04 05:00:00,7928.0 -2017-09-04 06:00:00,7932.0 -2017-09-04 07:00:00,8062.0 -2017-09-04 08:00:00,8202.0 -2017-09-04 09:00:00,8294.0 -2017-09-04 10:00:00,8814.0 -2017-09-04 11:00:00,9592.0 -2017-09-04 12:00:00,10478.0 -2017-09-04 13:00:00,11352.0 -2017-09-04 14:00:00,11898.0 -2017-09-04 15:00:00,11937.0 -2017-09-04 16:00:00,11678.0 -2017-09-04 17:00:00,11540.0 -2017-09-04 18:00:00,11455.0 -2017-09-04 19:00:00,11322.0 -2017-09-04 20:00:00,11026.0 -2017-09-04 21:00:00,11163.0 -2017-09-04 22:00:00,10979.0 -2017-09-04 23:00:00,10341.0 -2017-09-05 00:00:00,9592.0 -2017-09-03 01:00:00,8573.0 -2017-09-03 02:00:00,8179.0 -2017-09-03 03:00:00,7796.0 -2017-09-03 04:00:00,7591.0 -2017-09-03 05:00:00,7467.0 -2017-09-03 06:00:00,7443.0 -2017-09-03 07:00:00,7513.0 -2017-09-03 08:00:00,7512.0 -2017-09-03 09:00:00,7748.0 -2017-09-03 10:00:00,8266.0 -2017-09-03 11:00:00,8853.0 -2017-09-03 12:00:00,9378.0 -2017-09-03 13:00:00,9877.0 -2017-09-03 14:00:00,10186.0 -2017-09-03 15:00:00,10509.0 -2017-09-03 16:00:00,10906.0 -2017-09-03 17:00:00,11197.0 -2017-09-03 18:00:00,11404.0 -2017-09-03 19:00:00,11392.0 -2017-09-03 20:00:00,11061.0 -2017-09-03 21:00:00,10971.0 -2017-09-03 22:00:00,10932.0 -2017-09-03 23:00:00,10540.0 -2017-09-04 00:00:00,9925.0 -2017-09-02 01:00:00,8989.0 -2017-09-02 02:00:00,8411.0 -2017-09-02 03:00:00,8084.0 -2017-09-02 04:00:00,7837.0 -2017-09-02 05:00:00,7688.0 -2017-09-02 06:00:00,7692.0 -2017-09-02 07:00:00,7820.0 -2017-09-02 08:00:00,7919.0 -2017-09-02 09:00:00,8115.0 -2017-09-02 10:00:00,8680.0 -2017-09-02 11:00:00,9115.0 -2017-09-02 12:00:00,9406.0 -2017-09-02 13:00:00,9559.0 -2017-09-02 14:00:00,9674.0 -2017-09-02 15:00:00,9708.0 -2017-09-02 16:00:00,9639.0 -2017-09-02 17:00:00,9712.0 -2017-09-02 18:00:00,9753.0 -2017-09-02 19:00:00,9713.0 -2017-09-02 20:00:00,9639.0 -2017-09-02 21:00:00,9887.0 -2017-09-02 22:00:00,9862.0 -2017-09-02 23:00:00,9581.0 -2017-09-03 00:00:00,9149.0 -2017-09-01 01:00:00,9543.0 -2017-09-01 02:00:00,9011.0 -2017-09-01 03:00:00,8620.0 -2017-09-01 04:00:00,8322.0 -2017-09-01 05:00:00,8193.0 -2017-09-01 06:00:00,8309.0 -2017-09-01 07:00:00,8813.0 -2017-09-01 08:00:00,9543.0 -2017-09-01 09:00:00,10119.0 -2017-09-01 10:00:00,10604.0 -2017-09-01 11:00:00,10877.0 -2017-09-01 12:00:00,11169.0 -2017-09-01 13:00:00,11304.0 -2017-09-01 14:00:00,11427.0 -2017-09-01 15:00:00,11554.0 -2017-09-01 16:00:00,11578.0 -2017-09-01 17:00:00,11503.0 -2017-09-01 18:00:00,11404.0 -2017-09-01 19:00:00,11187.0 -2017-09-01 20:00:00,10775.0 -2017-09-01 21:00:00,10709.0 -2017-09-01 22:00:00,10734.0 -2017-09-01 23:00:00,10319.0 -2017-09-02 00:00:00,9660.0 -2017-08-31 01:00:00,10500.0 -2017-08-31 02:00:00,9809.0 -2017-08-31 03:00:00,9342.0 -2017-08-31 04:00:00,9079.0 -2017-08-31 05:00:00,8944.0 -2017-08-31 06:00:00,9099.0 -2017-08-31 07:00:00,9694.0 -2017-08-31 08:00:00,10572.0 -2017-08-31 09:00:00,11238.0 -2017-08-31 10:00:00,11741.0 -2017-08-31 11:00:00,12159.0 -2017-08-31 12:00:00,12585.0 -2017-08-31 13:00:00,12794.0 -2017-08-31 14:00:00,12930.0 -2017-08-31 15:00:00,13044.0 -2017-08-31 16:00:00,13006.0 -2017-08-31 17:00:00,12906.0 -2017-08-31 18:00:00,12697.0 -2017-08-31 19:00:00,12248.0 -2017-08-31 20:00:00,11666.0 -2017-08-31 21:00:00,11668.0 -2017-08-31 22:00:00,11674.0 -2017-08-31 23:00:00,11152.0 -2017-09-01 00:00:00,10388.0 -2017-08-30 01:00:00,10053.0 -2017-08-30 02:00:00,9407.0 -2017-08-30 03:00:00,8986.0 -2017-08-30 04:00:00,8696.0 -2017-08-30 05:00:00,8608.0 -2017-08-30 06:00:00,8755.0 -2017-08-30 07:00:00,9344.0 -2017-08-30 08:00:00,10164.0 -2017-08-30 09:00:00,10923.0 -2017-08-30 10:00:00,11590.0 -2017-08-30 11:00:00,12166.0 -2017-08-30 12:00:00,12778.0 -2017-08-30 13:00:00,13154.0 -2017-08-30 14:00:00,13547.0 -2017-08-30 15:00:00,13922.0 -2017-08-30 16:00:00,14196.0 -2017-08-30 17:00:00,14367.0 -2017-08-30 18:00:00,14382.0 -2017-08-30 19:00:00,14208.0 -2017-08-30 20:00:00,13664.0 -2017-08-30 21:00:00,13408.0 -2017-08-30 22:00:00,13265.0 -2017-08-30 23:00:00,12523.0 -2017-08-31 00:00:00,11458.0 -2017-08-29 01:00:00,10081.0 -2017-08-29 02:00:00,9453.0 -2017-08-29 03:00:00,9028.0 -2017-08-29 04:00:00,8797.0 -2017-08-29 05:00:00,8686.0 -2017-08-29 06:00:00,8828.0 -2017-08-29 07:00:00,9427.0 -2017-08-29 08:00:00,10310.0 -2017-08-29 09:00:00,11081.0 -2017-08-29 10:00:00,11770.0 -2017-08-29 11:00:00,12352.0 -2017-08-29 12:00:00,12813.0 -2017-08-29 13:00:00,13013.0 -2017-08-29 14:00:00,12996.0 -2017-08-29 15:00:00,13033.0 -2017-08-29 16:00:00,12921.0 -2017-08-29 17:00:00,12659.0 -2017-08-29 18:00:00,12522.0 -2017-08-29 19:00:00,12608.0 -2017-08-29 20:00:00,12371.0 -2017-08-29 21:00:00,12377.0 -2017-08-29 22:00:00,12372.0 -2017-08-29 23:00:00,11817.0 -2017-08-30 00:00:00,10921.0 -2017-08-28 01:00:00,9423.0 -2017-08-28 02:00:00,8980.0 -2017-08-28 03:00:00,8689.0 -2017-08-28 04:00:00,8527.0 -2017-08-28 05:00:00,8477.0 -2017-08-28 06:00:00,8673.0 -2017-08-28 07:00:00,9469.0 -2017-08-28 08:00:00,10413.0 -2017-08-28 09:00:00,11099.0 -2017-08-28 10:00:00,11646.0 -2017-08-28 11:00:00,12122.0 -2017-08-28 12:00:00,12677.0 -2017-08-28 13:00:00,13126.0 -2017-08-28 14:00:00,13516.0 -2017-08-28 15:00:00,13898.0 -2017-08-28 16:00:00,14069.0 -2017-08-28 17:00:00,13860.0 -2017-08-28 18:00:00,13456.0 -2017-08-28 19:00:00,13026.0 -2017-08-28 20:00:00,12586.0 -2017-08-28 21:00:00,12515.0 -2017-08-28 22:00:00,12444.0 -2017-08-28 23:00:00,11848.0 -2017-08-29 00:00:00,10983.0 -2017-08-27 01:00:00,9314.0 -2017-08-27 02:00:00,8868.0 -2017-08-27 03:00:00,8509.0 -2017-08-27 04:00:00,8213.0 -2017-08-27 05:00:00,8031.0 -2017-08-27 06:00:00,7952.0 -2017-08-27 07:00:00,7944.0 -2017-08-27 08:00:00,7838.0 -2017-08-27 09:00:00,7993.0 -2017-08-27 10:00:00,8499.0 -2017-08-27 11:00:00,8904.0 -2017-08-27 12:00:00,9197.0 -2017-08-27 13:00:00,9428.0 -2017-08-27 14:00:00,9608.0 -2017-08-27 15:00:00,9898.0 -2017-08-27 16:00:00,10290.0 -2017-08-27 17:00:00,10484.0 -2017-08-27 18:00:00,10474.0 -2017-08-27 19:00:00,10500.0 -2017-08-27 20:00:00,10550.0 -2017-08-27 21:00:00,10860.0 -2017-08-27 22:00:00,11007.0 -2017-08-27 23:00:00,10673.0 -2017-08-28 00:00:00,10147.0 -2017-08-26 01:00:00,9519.0 -2017-08-26 02:00:00,8949.0 -2017-08-26 03:00:00,8593.0 -2017-08-26 04:00:00,8288.0 -2017-08-26 05:00:00,8146.0 -2017-08-26 06:00:00,8132.0 -2017-08-26 07:00:00,8357.0 -2017-08-26 08:00:00,8563.0 -2017-08-26 09:00:00,8940.0 -2017-08-26 10:00:00,9486.0 -2017-08-26 11:00:00,10002.0 -2017-08-26 12:00:00,10449.0 -2017-08-26 13:00:00,10765.0 -2017-08-26 14:00:00,10967.0 -2017-08-26 15:00:00,11113.0 -2017-08-26 16:00:00,11122.0 -2017-08-26 17:00:00,11008.0 -2017-08-26 18:00:00,10834.0 -2017-08-26 19:00:00,10683.0 -2017-08-26 20:00:00,10548.0 -2017-08-26 21:00:00,10720.0 -2017-08-26 22:00:00,10728.0 -2017-08-26 23:00:00,10424.0 -2017-08-27 00:00:00,9901.0 -2017-08-25 01:00:00,9393.0 -2017-08-25 02:00:00,8853.0 -2017-08-25 03:00:00,8503.0 -2017-08-25 04:00:00,8230.0 -2017-08-25 05:00:00,8112.0 -2017-08-25 06:00:00,8281.0 -2017-08-25 07:00:00,8780.0 -2017-08-25 08:00:00,9486.0 -2017-08-25 09:00:00,10171.0 -2017-08-25 10:00:00,10745.0 -2017-08-25 11:00:00,11261.0 -2017-08-25 12:00:00,11624.0 -2017-08-25 13:00:00,11853.0 -2017-08-25 14:00:00,12006.0 -2017-08-25 15:00:00,12219.0 -2017-08-25 16:00:00,12342.0 -2017-08-25 17:00:00,12399.0 -2017-08-25 18:00:00,12384.0 -2017-08-25 19:00:00,12165.0 -2017-08-25 20:00:00,11713.0 -2017-08-25 21:00:00,11393.0 -2017-08-25 22:00:00,11427.0 -2017-08-25 23:00:00,10945.0 -2017-08-26 00:00:00,10273.0 -2017-08-24 01:00:00,10073.0 -2017-08-24 02:00:00,9431.0 -2017-08-24 03:00:00,9010.0 -2017-08-24 04:00:00,8706.0 -2017-08-24 05:00:00,8581.0 -2017-08-24 06:00:00,8733.0 -2017-08-24 07:00:00,9262.0 -2017-08-24 08:00:00,10080.0 -2017-08-24 09:00:00,10760.0 -2017-08-24 10:00:00,11336.0 -2017-08-24 11:00:00,11885.0 -2017-08-24 12:00:00,12347.0 -2017-08-24 13:00:00,12595.0 -2017-08-24 14:00:00,12802.0 -2017-08-24 15:00:00,12891.0 -2017-08-24 16:00:00,12834.0 -2017-08-24 17:00:00,12704.0 -2017-08-24 18:00:00,12604.0 -2017-08-24 19:00:00,12278.0 -2017-08-24 20:00:00,11763.0 -2017-08-24 21:00:00,11539.0 -2017-08-24 22:00:00,11592.0 -2017-08-24 23:00:00,11029.0 -2017-08-25 00:00:00,10202.0 -2017-08-23 01:00:00,10416.0 -2017-08-23 02:00:00,9703.0 -2017-08-23 03:00:00,9217.0 -2017-08-23 04:00:00,8907.0 -2017-08-23 05:00:00,8734.0 -2017-08-23 06:00:00,8840.0 -2017-08-23 07:00:00,9381.0 -2017-08-23 08:00:00,10123.0 -2017-08-23 09:00:00,10924.0 -2017-08-23 10:00:00,11608.0 -2017-08-23 11:00:00,12089.0 -2017-08-23 12:00:00,12594.0 -2017-08-23 13:00:00,12967.0 -2017-08-23 14:00:00,13254.0 -2017-08-23 15:00:00,13539.0 -2017-08-23 16:00:00,13736.0 -2017-08-23 17:00:00,13831.0 -2017-08-23 18:00:00,13782.0 -2017-08-23 19:00:00,13521.0 -2017-08-23 20:00:00,12959.0 -2017-08-23 21:00:00,12558.0 -2017-08-23 22:00:00,12593.0 -2017-08-23 23:00:00,11923.0 -2017-08-24 00:00:00,10960.0 -2017-08-22 01:00:00,12788.0 -2017-08-22 02:00:00,11767.0 -2017-08-22 03:00:00,11236.0 -2017-08-22 04:00:00,10930.0 -2017-08-22 05:00:00,10746.0 -2017-08-22 06:00:00,10899.0 -2017-08-22 07:00:00,11465.0 -2017-08-22 08:00:00,12308.0 -2017-08-22 09:00:00,12881.0 -2017-08-22 10:00:00,13386.0 -2017-08-22 11:00:00,13734.0 -2017-08-22 12:00:00,14114.0 -2017-08-22 13:00:00,14356.0 -2017-08-22 14:00:00,14789.0 -2017-08-22 15:00:00,15312.0 -2017-08-22 16:00:00,15536.0 -2017-08-22 17:00:00,15641.0 -2017-08-22 18:00:00,15548.0 -2017-08-22 19:00:00,15192.0 -2017-08-22 20:00:00,14382.0 -2017-08-22 21:00:00,13607.0 -2017-08-22 22:00:00,13358.0 -2017-08-22 23:00:00,12553.0 -2017-08-23 00:00:00,11478.0 -2017-08-21 01:00:00,12211.0 -2017-08-21 02:00:00,11444.0 -2017-08-21 03:00:00,10871.0 -2017-08-21 04:00:00,10479.0 -2017-08-21 05:00:00,10340.0 -2017-08-21 06:00:00,10478.0 -2017-08-21 07:00:00,11216.0 -2017-08-21 08:00:00,12095.0 -2017-08-21 09:00:00,13079.0 -2017-08-21 10:00:00,13913.0 -2017-08-21 11:00:00,14800.0 -2017-08-21 12:00:00,15724.0 -2017-08-21 13:00:00,16140.0 -2017-08-21 14:00:00,16319.0 -2017-08-21 15:00:00,15908.0 -2017-08-21 16:00:00,16202.0 -2017-08-21 17:00:00,16750.0 -2017-08-21 18:00:00,17313.0 -2017-08-21 19:00:00,17064.0 -2017-08-21 20:00:00,16384.0 -2017-08-21 21:00:00,15938.0 -2017-08-21 22:00:00,15778.0 -2017-08-21 23:00:00,15017.0 -2017-08-22 00:00:00,13930.0 -2017-08-20 01:00:00,10816.0 -2017-08-20 02:00:00,10076.0 -2017-08-20 03:00:00,9551.0 -2017-08-20 04:00:00,9088.0 -2017-08-20 05:00:00,8831.0 -2017-08-20 06:00:00,8677.0 -2017-08-20 07:00:00,8725.0 -2017-08-20 08:00:00,8638.0 -2017-08-20 09:00:00,9094.0 -2017-08-20 10:00:00,9935.0 -2017-08-20 11:00:00,10918.0 -2017-08-20 12:00:00,11924.0 -2017-08-20 13:00:00,12980.0 -2017-08-20 14:00:00,13953.0 -2017-08-20 15:00:00,14708.0 -2017-08-20 16:00:00,15112.0 -2017-08-20 17:00:00,14964.0 -2017-08-20 18:00:00,14848.0 -2017-08-20 19:00:00,14590.0 -2017-08-20 20:00:00,14424.0 -2017-08-20 21:00:00,14422.0 -2017-08-20 22:00:00,14444.0 -2017-08-20 23:00:00,13940.0 -2017-08-21 00:00:00,13176.0 -2017-08-19 01:00:00,11690.0 -2017-08-19 02:00:00,10835.0 -2017-08-19 03:00:00,10197.0 -2017-08-19 04:00:00,9738.0 -2017-08-19 05:00:00,9760.0 -2017-08-19 06:00:00,9573.0 -2017-08-19 07:00:00,9697.0 -2017-08-19 08:00:00,9765.0 -2017-08-19 09:00:00,10227.0 -2017-08-19 10:00:00,11202.0 -2017-08-19 11:00:00,12146.0 -2017-08-19 12:00:00,13000.0 -2017-08-19 13:00:00,13743.0 -2017-08-19 14:00:00,14374.0 -2017-08-19 15:00:00,14814.0 -2017-08-19 16:00:00,15242.0 -2017-08-19 17:00:00,15585.0 -2017-08-19 18:00:00,15692.0 -2017-08-19 19:00:00,15564.0 -2017-08-19 20:00:00,14919.0 -2017-08-19 21:00:00,13968.0 -2017-08-19 22:00:00,13508.0 -2017-08-19 23:00:00,12850.0 -2017-08-20 00:00:00,11957.0 -2017-08-18 01:00:00,12036.0 -2017-08-18 02:00:00,11176.0 -2017-08-18 03:00:00,10524.0 -2017-08-18 04:00:00,10060.0 -2017-08-18 05:00:00,9834.0 -2017-08-18 06:00:00,9864.0 -2017-08-18 07:00:00,10401.0 -2017-08-18 08:00:00,11120.0 -2017-08-18 09:00:00,12021.0 -2017-08-18 10:00:00,12710.0 -2017-08-18 11:00:00,13190.0 -2017-08-18 12:00:00,13587.0 -2017-08-18 13:00:00,14063.0 -2017-08-18 14:00:00,14650.0 -2017-08-18 15:00:00,15240.0 -2017-08-18 16:00:00,15708.0 -2017-08-18 17:00:00,16105.0 -2017-08-18 18:00:00,16229.0 -2017-08-18 19:00:00,15941.0 -2017-08-18 20:00:00,15232.0 -2017-08-18 21:00:00,14489.0 -2017-08-18 22:00:00,14307.0 -2017-08-18 23:00:00,13670.0 -2017-08-19 00:00:00,12724.0 -2017-08-17 01:00:00,13266.0 -2017-08-17 02:00:00,12442.0 -2017-08-17 03:00:00,11819.0 -2017-08-17 04:00:00,11312.0 -2017-08-17 05:00:00,11011.0 -2017-08-17 06:00:00,11019.0 -2017-08-17 07:00:00,11656.0 -2017-08-17 08:00:00,12611.0 -2017-08-17 09:00:00,13344.0 -2017-08-17 10:00:00,13788.0 -2017-08-17 11:00:00,14251.0 -2017-08-17 12:00:00,14575.0 -2017-08-17 13:00:00,14982.0 -2017-08-17 14:00:00,15687.0 -2017-08-17 15:00:00,16265.0 -2017-08-17 16:00:00,16501.0 -2017-08-17 17:00:00,16662.0 -2017-08-17 18:00:00,16676.0 -2017-08-17 19:00:00,16422.0 -2017-08-17 20:00:00,15742.0 -2017-08-17 21:00:00,15161.0 -2017-08-17 22:00:00,15050.0 -2017-08-17 23:00:00,14344.0 -2017-08-18 00:00:00,13169.0 -2017-08-16 01:00:00,11418.0 -2017-08-16 02:00:00,10626.0 -2017-08-16 03:00:00,10074.0 -2017-08-16 04:00:00,9732.0 -2017-08-16 05:00:00,9532.0 -2017-08-16 06:00:00,9635.0 -2017-08-16 07:00:00,10237.0 -2017-08-16 08:00:00,11101.0 -2017-08-16 09:00:00,12116.0 -2017-08-16 10:00:00,13105.0 -2017-08-16 11:00:00,14056.0 -2017-08-16 12:00:00,15092.0 -2017-08-16 13:00:00,16106.0 -2017-08-16 14:00:00,16994.0 -2017-08-16 15:00:00,17802.0 -2017-08-16 16:00:00,18237.0 -2017-08-16 17:00:00,18282.0 -2017-08-16 18:00:00,17547.0 -2017-08-16 19:00:00,16633.0 -2017-08-16 20:00:00,16042.0 -2017-08-16 21:00:00,15727.0 -2017-08-16 22:00:00,15768.0 -2017-08-16 23:00:00,15274.0 -2017-08-17 00:00:00,14254.0 -2017-08-15 01:00:00,11878.0 -2017-08-15 02:00:00,11038.0 -2017-08-15 03:00:00,10419.0 -2017-08-15 04:00:00,10056.0 -2017-08-15 05:00:00,9789.0 -2017-08-15 06:00:00,9909.0 -2017-08-15 07:00:00,10486.0 -2017-08-15 08:00:00,11253.0 -2017-08-15 09:00:00,12275.0 -2017-08-15 10:00:00,13299.0 -2017-08-15 11:00:00,14164.0 -2017-08-15 12:00:00,15038.0 -2017-08-15 13:00:00,15687.0 -2017-08-15 14:00:00,16249.0 -2017-08-15 15:00:00,16784.0 -2017-08-15 16:00:00,16993.0 -2017-08-15 17:00:00,16984.0 -2017-08-15 18:00:00,16777.0 -2017-08-15 19:00:00,16252.0 -2017-08-15 20:00:00,15236.0 -2017-08-15 21:00:00,14498.0 -2017-08-15 22:00:00,14272.0 -2017-08-15 23:00:00,13553.0 -2017-08-16 00:00:00,12514.0 -2017-08-14 01:00:00,9869.0 -2017-08-14 02:00:00,9357.0 -2017-08-14 03:00:00,8970.0 -2017-08-14 04:00:00,8743.0 -2017-08-14 05:00:00,8616.0 -2017-08-14 06:00:00,8830.0 -2017-08-14 07:00:00,9466.0 -2017-08-14 08:00:00,10192.0 -2017-08-14 09:00:00,11121.0 -2017-08-14 10:00:00,11998.0 -2017-08-14 11:00:00,12803.0 -2017-08-14 12:00:00,13428.0 -2017-08-14 13:00:00,13760.0 -2017-08-14 14:00:00,14066.0 -2017-08-14 15:00:00,14471.0 -2017-08-14 16:00:00,14942.0 -2017-08-14 17:00:00,15328.0 -2017-08-14 18:00:00,15433.0 -2017-08-14 19:00:00,15235.0 -2017-08-14 20:00:00,14731.0 -2017-08-14 21:00:00,14473.0 -2017-08-14 22:00:00,14535.0 -2017-08-14 23:00:00,13990.0 -2017-08-15 00:00:00,12969.0 -2017-08-13 01:00:00,9528.0 -2017-08-13 02:00:00,8985.0 -2017-08-13 03:00:00,8565.0 -2017-08-13 04:00:00,8234.0 -2017-08-13 05:00:00,8038.0 -2017-08-13 06:00:00,7980.0 -2017-08-13 07:00:00,7982.0 -2017-08-13 08:00:00,7866.0 -2017-08-13 09:00:00,8272.0 -2017-08-13 10:00:00,8883.0 -2017-08-13 11:00:00,9542.0 -2017-08-13 12:00:00,10066.0 -2017-08-13 13:00:00,10492.0 -2017-08-13 14:00:00,10899.0 -2017-08-13 15:00:00,11289.0 -2017-08-13 16:00:00,11493.0 -2017-08-13 17:00:00,11611.0 -2017-08-13 18:00:00,11623.0 -2017-08-13 19:00:00,11526.0 -2017-08-13 20:00:00,11273.0 -2017-08-13 21:00:00,11144.0 -2017-08-13 22:00:00,11384.0 -2017-08-13 23:00:00,11044.0 -2017-08-14 00:00:00,10562.0 -2017-08-12 01:00:00,10513.0 -2017-08-12 02:00:00,9799.0 -2017-08-12 03:00:00,9222.0 -2017-08-12 04:00:00,8923.0 -2017-08-12 05:00:00,8695.0 -2017-08-12 06:00:00,8642.0 -2017-08-12 07:00:00,8699.0 -2017-08-12 08:00:00,8822.0 -2017-08-12 09:00:00,9306.0 -2017-08-12 10:00:00,10073.0 -2017-08-12 11:00:00,10543.0 -2017-08-12 12:00:00,10973.0 -2017-08-12 13:00:00,11151.0 -2017-08-12 14:00:00,11279.0 -2017-08-12 15:00:00,11334.0 -2017-08-12 16:00:00,11492.0 -2017-08-12 17:00:00,11671.0 -2017-08-12 18:00:00,11816.0 -2017-08-12 19:00:00,11794.0 -2017-08-12 20:00:00,11540.0 -2017-08-12 21:00:00,11096.0 -2017-08-12 22:00:00,11101.0 -2017-08-12 23:00:00,10821.0 -2017-08-13 00:00:00,10151.0 -2017-08-11 01:00:00,11421.0 -2017-08-11 02:00:00,10598.0 -2017-08-11 03:00:00,10007.0 -2017-08-11 04:00:00,9631.0 -2017-08-11 05:00:00,9477.0 -2017-08-11 06:00:00,9551.0 -2017-08-11 07:00:00,10070.0 -2017-08-11 08:00:00,10747.0 -2017-08-11 09:00:00,11498.0 -2017-08-11 10:00:00,12342.0 -2017-08-11 11:00:00,13155.0 -2017-08-11 12:00:00,13690.0 -2017-08-11 13:00:00,14054.0 -2017-08-11 14:00:00,14302.0 -2017-08-11 15:00:00,14533.0 -2017-08-11 16:00:00,14599.0 -2017-08-11 17:00:00,14486.0 -2017-08-11 18:00:00,14260.0 -2017-08-11 19:00:00,13949.0 -2017-08-11 20:00:00,13391.0 -2017-08-11 21:00:00,12790.0 -2017-08-11 22:00:00,12751.0 -2017-08-11 23:00:00,12259.0 -2017-08-12 00:00:00,11457.0 -2017-08-10 01:00:00,11216.0 -2017-08-10 02:00:00,10374.0 -2017-08-10 03:00:00,9813.0 -2017-08-10 04:00:00,9393.0 -2017-08-10 05:00:00,9197.0 -2017-08-10 06:00:00,9259.0 -2017-08-10 07:00:00,9822.0 -2017-08-10 08:00:00,10461.0 -2017-08-10 09:00:00,11336.0 -2017-08-10 10:00:00,12136.0 -2017-08-10 11:00:00,12890.0 -2017-08-10 12:00:00,13564.0 -2017-08-10 13:00:00,14219.0 -2017-08-10 14:00:00,15077.0 -2017-08-10 15:00:00,15777.0 -2017-08-10 16:00:00,16176.0 -2017-08-10 17:00:00,16306.0 -2017-08-10 18:00:00,15942.0 -2017-08-10 19:00:00,15669.0 -2017-08-10 20:00:00,14884.0 -2017-08-10 21:00:00,14300.0 -2017-08-10 22:00:00,14224.0 -2017-08-10 23:00:00,13627.0 -2017-08-11 00:00:00,12595.0 -2017-08-09 01:00:00,10853.0 -2017-08-09 02:00:00,10083.0 -2017-08-09 03:00:00,9508.0 -2017-08-09 04:00:00,9142.0 -2017-08-09 05:00:00,8982.0 -2017-08-09 06:00:00,9050.0 -2017-08-09 07:00:00,9550.0 -2017-08-09 08:00:00,10179.0 -2017-08-09 09:00:00,11081.0 -2017-08-09 10:00:00,12020.0 -2017-08-09 11:00:00,12796.0 -2017-08-09 12:00:00,13516.0 -2017-08-09 13:00:00,14049.0 -2017-08-09 14:00:00,14485.0 -2017-08-09 15:00:00,14932.0 -2017-08-09 16:00:00,15153.0 -2017-08-09 17:00:00,15308.0 -2017-08-09 18:00:00,15491.0 -2017-08-09 19:00:00,15298.0 -2017-08-09 20:00:00,14778.0 -2017-08-09 21:00:00,14182.0 -2017-08-09 22:00:00,13955.0 -2017-08-09 23:00:00,13315.0 -2017-08-10 00:00:00,12202.0 -2017-08-08 01:00:00,9809.0 -2017-08-08 02:00:00,9181.0 -2017-08-08 03:00:00,8774.0 -2017-08-08 04:00:00,8495.0 -2017-08-08 05:00:00,8377.0 -2017-08-08 06:00:00,8479.0 -2017-08-08 07:00:00,8995.0 -2017-08-08 08:00:00,9633.0 -2017-08-08 09:00:00,10536.0 -2017-08-08 10:00:00,11253.0 -2017-08-08 11:00:00,11873.0 -2017-08-08 12:00:00,12510.0 -2017-08-08 13:00:00,13025.0 -2017-08-08 14:00:00,13488.0 -2017-08-08 15:00:00,14024.0 -2017-08-08 16:00:00,14426.0 -2017-08-08 17:00:00,14706.0 -2017-08-08 18:00:00,14847.0 -2017-08-08 19:00:00,14716.0 -2017-08-08 20:00:00,14306.0 -2017-08-08 21:00:00,13701.0 -2017-08-08 22:00:00,13555.0 -2017-08-08 23:00:00,12940.0 -2017-08-09 00:00:00,11918.0 -2017-08-07 01:00:00,9595.0 -2017-08-07 02:00:00,9110.0 -2017-08-07 03:00:00,8698.0 -2017-08-07 04:00:00,8501.0 -2017-08-07 05:00:00,8384.0 -2017-08-07 06:00:00,8618.0 -2017-08-07 07:00:00,9270.0 -2017-08-07 08:00:00,9985.0 -2017-08-07 09:00:00,10893.0 -2017-08-07 10:00:00,11619.0 -2017-08-07 11:00:00,12180.0 -2017-08-07 12:00:00,12675.0 -2017-08-07 13:00:00,12961.0 -2017-08-07 14:00:00,13225.0 -2017-08-07 15:00:00,13449.0 -2017-08-07 16:00:00,13535.0 -2017-08-07 17:00:00,13642.0 -2017-08-07 18:00:00,13683.0 -2017-08-07 19:00:00,13483.0 -2017-08-07 20:00:00,12962.0 -2017-08-07 21:00:00,12346.0 -2017-08-07 22:00:00,12128.0 -2017-08-07 23:00:00,11634.0 -2017-08-08 00:00:00,10747.0 -2017-08-06 01:00:00,9695.0 -2017-08-06 02:00:00,9138.0 -2017-08-06 03:00:00,8738.0 -2017-08-06 04:00:00,8444.0 -2017-08-06 05:00:00,8247.0 -2017-08-06 06:00:00,8164.0 -2017-08-06 07:00:00,8180.0 -2017-08-06 08:00:00,8175.0 -2017-08-06 09:00:00,8405.0 -2017-08-06 10:00:00,8879.0 -2017-08-06 11:00:00,9396.0 -2017-08-06 12:00:00,9893.0 -2017-08-06 13:00:00,10407.0 -2017-08-06 14:00:00,10635.0 -2017-08-06 15:00:00,10799.0 -2017-08-06 16:00:00,10890.0 -2017-08-06 17:00:00,11188.0 -2017-08-06 18:00:00,11434.0 -2017-08-06 19:00:00,11542.0 -2017-08-06 20:00:00,11359.0 -2017-08-06 21:00:00,11077.0 -2017-08-06 22:00:00,11202.0 -2017-08-06 23:00:00,10937.0 -2017-08-07 00:00:00,10353.0 -2017-08-05 01:00:00,9227.0 -2017-08-05 02:00:00,8747.0 -2017-08-05 03:00:00,8385.0 -2017-08-05 04:00:00,8197.0 -2017-08-05 05:00:00,8047.0 -2017-08-05 06:00:00,8076.0 -2017-08-05 07:00:00,8216.0 -2017-08-05 08:00:00,8307.0 -2017-08-05 09:00:00,8834.0 -2017-08-05 10:00:00,9451.0 -2017-08-05 11:00:00,10048.0 -2017-08-05 12:00:00,10547.0 -2017-08-05 13:00:00,10928.0 -2017-08-05 14:00:00,11240.0 -2017-08-05 15:00:00,11394.0 -2017-08-05 16:00:00,11542.0 -2017-08-05 17:00:00,11671.0 -2017-08-05 18:00:00,11748.0 -2017-08-05 19:00:00,11681.0 -2017-08-05 20:00:00,11322.0 -2017-08-05 21:00:00,11040.0 -2017-08-05 22:00:00,11246.0 -2017-08-05 23:00:00,10956.0 -2017-08-06 00:00:00,10338.0 -2017-08-04 01:00:00,11363.0 -2017-08-04 02:00:00,10344.0 -2017-08-04 03:00:00,9612.0 -2017-08-04 04:00:00,9175.0 -2017-08-04 05:00:00,8941.0 -2017-08-04 06:00:00,8976.0 -2017-08-04 07:00:00,9373.0 -2017-08-04 08:00:00,9867.0 -2017-08-04 09:00:00,10428.0 -2017-08-04 10:00:00,10871.0 -2017-08-04 11:00:00,11084.0 -2017-08-04 12:00:00,11271.0 -2017-08-04 13:00:00,11332.0 -2017-08-04 14:00:00,11324.0 -2017-08-04 15:00:00,11315.0 -2017-08-04 16:00:00,11207.0 -2017-08-04 17:00:00,11049.0 -2017-08-04 18:00:00,10934.0 -2017-08-04 19:00:00,10779.0 -2017-08-04 20:00:00,10608.0 -2017-08-04 21:00:00,10450.0 -2017-08-04 22:00:00,10639.0 -2017-08-04 23:00:00,10446.0 -2017-08-05 00:00:00,9859.0 -2017-08-03 01:00:00,12560.0 -2017-08-03 02:00:00,11671.0 -2017-08-03 03:00:00,11010.0 -2017-08-03 04:00:00,10567.0 -2017-08-03 05:00:00,10298.0 -2017-08-03 06:00:00,10387.0 -2017-08-03 07:00:00,10881.0 -2017-08-03 08:00:00,11680.0 -2017-08-03 09:00:00,12660.0 -2017-08-03 10:00:00,13595.0 -2017-08-03 11:00:00,14666.0 -2017-08-03 12:00:00,15761.0 -2017-08-03 13:00:00,16514.0 -2017-08-03 14:00:00,16740.0 -2017-08-03 15:00:00,16781.0 -2017-08-03 16:00:00,16421.0 -2017-08-03 17:00:00,16122.0 -2017-08-03 18:00:00,16282.0 -2017-08-03 19:00:00,16086.0 -2017-08-03 20:00:00,15203.0 -2017-08-03 21:00:00,14462.0 -2017-08-03 22:00:00,14259.0 -2017-08-03 23:00:00,13553.0 -2017-08-04 00:00:00,12464.0 -2017-08-02 01:00:00,12508.0 -2017-08-02 02:00:00,11504.0 -2017-08-02 03:00:00,10811.0 -2017-08-02 04:00:00,10340.0 -2017-08-02 05:00:00,10068.0 -2017-08-02 06:00:00,10148.0 -2017-08-02 07:00:00,10632.0 -2017-08-02 08:00:00,11364.0 -2017-08-02 09:00:00,12492.0 -2017-08-02 10:00:00,13560.0 -2017-08-02 11:00:00,14553.0 -2017-08-02 12:00:00,15527.0 -2017-08-02 13:00:00,16112.0 -2017-08-02 14:00:00,16902.0 -2017-08-02 15:00:00,17489.0 -2017-08-02 16:00:00,17846.0 -2017-08-02 17:00:00,17997.0 -2017-08-02 18:00:00,17899.0 -2017-08-02 19:00:00,17440.0 -2017-08-02 20:00:00,16602.0 -2017-08-02 21:00:00,15905.0 -2017-08-02 22:00:00,15581.0 -2017-08-02 23:00:00,14886.0 -2017-08-03 00:00:00,13733.0 -2017-08-01 01:00:00,12008.0 -2017-08-01 02:00:00,11152.0 -2017-08-01 03:00:00,10564.0 -2017-08-01 04:00:00,10136.0 -2017-08-01 05:00:00,9906.0 -2017-08-01 06:00:00,9981.0 -2017-08-01 07:00:00,10525.0 -2017-08-01 08:00:00,11289.0 -2017-08-01 09:00:00,12293.0 -2017-08-01 10:00:00,13278.0 -2017-08-01 11:00:00,14288.0 -2017-08-01 12:00:00,15299.0 -2017-08-01 13:00:00,15950.0 -2017-08-01 14:00:00,16425.0 -2017-08-01 15:00:00,16939.0 -2017-08-01 16:00:00,17096.0 -2017-08-01 17:00:00,17017.0 -2017-08-01 18:00:00,17146.0 -2017-08-01 19:00:00,17219.0 -2017-08-01 20:00:00,16692.0 -2017-08-01 21:00:00,16037.0 -2017-08-01 22:00:00,15637.0 -2017-08-01 23:00:00,15025.0 -2017-08-02 00:00:00,13765.0 -2017-07-31 01:00:00,10973.0 -2017-07-31 02:00:00,10256.0 -2017-07-31 03:00:00,9695.0 -2017-07-31 04:00:00,9403.0 -2017-07-31 05:00:00,9230.0 -2017-07-31 06:00:00,9380.0 -2017-07-31 07:00:00,9869.0 -2017-07-31 08:00:00,10614.0 -2017-07-31 09:00:00,11653.0 -2017-07-31 10:00:00,12691.0 -2017-07-31 11:00:00,13614.0 -2017-07-31 12:00:00,14495.0 -2017-07-31 13:00:00,15111.0 -2017-07-31 14:00:00,15671.0 -2017-07-31 15:00:00,16200.0 -2017-07-31 16:00:00,16604.0 -2017-07-31 17:00:00,16873.0 -2017-07-31 18:00:00,17005.0 -2017-07-31 19:00:00,16885.0 -2017-07-31 20:00:00,16304.0 -2017-07-31 21:00:00,15519.0 -2017-07-31 22:00:00,15088.0 -2017-07-31 23:00:00,14419.0 -2017-08-01 00:00:00,13238.0 -2017-07-30 01:00:00,10136.0 -2017-07-30 02:00:00,9526.0 -2017-07-30 03:00:00,9039.0 -2017-07-30 04:00:00,8701.0 -2017-07-30 05:00:00,8465.0 -2017-07-30 06:00:00,8375.0 -2017-07-30 07:00:00,8314.0 -2017-07-30 08:00:00,8277.0 -2017-07-30 09:00:00,8804.0 -2017-07-30 10:00:00,9637.0 -2017-07-30 11:00:00,10492.0 -2017-07-30 12:00:00,11374.0 -2017-07-30 13:00:00,12011.0 -2017-07-30 14:00:00,12508.0 -2017-07-30 15:00:00,12947.0 -2017-07-30 16:00:00,13332.0 -2017-07-30 17:00:00,13742.0 -2017-07-30 18:00:00,14013.0 -2017-07-30 19:00:00,14111.0 -2017-07-30 20:00:00,13858.0 -2017-07-30 21:00:00,13327.0 -2017-07-30 22:00:00,13009.0 -2017-07-30 23:00:00,12711.0 -2017-07-31 00:00:00,11891.0 -2017-07-29 01:00:00,11086.0 -2017-07-29 02:00:00,10320.0 -2017-07-29 03:00:00,9789.0 -2017-07-29 04:00:00,9401.0 -2017-07-29 05:00:00,9103.0 -2017-07-29 06:00:00,8899.0 -2017-07-29 07:00:00,8929.0 -2017-07-29 08:00:00,9110.0 -2017-07-29 09:00:00,9808.0 -2017-07-29 10:00:00,10561.0 -2017-07-29 11:00:00,11164.0 -2017-07-29 12:00:00,11676.0 -2017-07-29 13:00:00,11966.0 -2017-07-29 14:00:00,12202.0 -2017-07-29 15:00:00,12376.0 -2017-07-29 16:00:00,12579.0 -2017-07-29 17:00:00,12858.0 -2017-07-29 18:00:00,13010.0 -2017-07-29 19:00:00,13007.0 -2017-07-29 20:00:00,12695.0 -2017-07-29 21:00:00,12130.0 -2017-07-29 22:00:00,11755.0 -2017-07-29 23:00:00,11508.0 -2017-07-30 00:00:00,10845.0 -2017-07-28 01:00:00,11617.0 -2017-07-28 02:00:00,10857.0 -2017-07-28 03:00:00,10322.0 -2017-07-28 04:00:00,9983.0 -2017-07-28 05:00:00,9771.0 -2017-07-28 06:00:00,9904.0 -2017-07-28 07:00:00,10372.0 -2017-07-28 08:00:00,11133.0 -2017-07-28 09:00:00,12129.0 -2017-07-28 10:00:00,12950.0 -2017-07-28 11:00:00,13642.0 -2017-07-28 12:00:00,14222.0 -2017-07-28 13:00:00,14643.0 -2017-07-28 14:00:00,14986.0 -2017-07-28 15:00:00,15298.0 -2017-07-28 16:00:00,15498.0 -2017-07-28 17:00:00,15581.0 -2017-07-28 18:00:00,15559.0 -2017-07-28 19:00:00,15298.0 -2017-07-28 20:00:00,14671.0 -2017-07-28 21:00:00,13760.0 -2017-07-28 22:00:00,13232.0 -2017-07-28 23:00:00,12783.0 -2017-07-29 00:00:00,11929.0 -2017-07-27 01:00:00,13324.0 -2017-07-27 02:00:00,12382.0 -2017-07-27 03:00:00,11696.0 -2017-07-27 04:00:00,11208.0 -2017-07-27 05:00:00,10940.0 -2017-07-27 06:00:00,11027.0 -2017-07-27 07:00:00,11584.0 -2017-07-27 08:00:00,12364.0 -2017-07-27 09:00:00,13467.0 -2017-07-27 10:00:00,14398.0 -2017-07-27 11:00:00,15174.0 -2017-07-27 12:00:00,15899.0 -2017-07-27 13:00:00,16500.0 -2017-07-27 14:00:00,16962.0 -2017-07-27 15:00:00,17277.0 -2017-07-27 16:00:00,17429.0 -2017-07-27 17:00:00,17484.0 -2017-07-27 18:00:00,17395.0 -2017-07-27 19:00:00,17043.0 -2017-07-27 20:00:00,16193.0 -2017-07-27 21:00:00,15082.0 -2017-07-27 22:00:00,14322.0 -2017-07-27 23:00:00,13733.0 -2017-07-28 00:00:00,12663.0 -2017-07-26 01:00:00,11421.0 -2017-07-26 02:00:00,10659.0 -2017-07-26 03:00:00,10059.0 -2017-07-26 04:00:00,9703.0 -2017-07-26 05:00:00,9475.0 -2017-07-26 06:00:00,9620.0 -2017-07-26 07:00:00,10114.0 -2017-07-26 08:00:00,10855.0 -2017-07-26 09:00:00,11827.0 -2017-07-26 10:00:00,12692.0 -2017-07-26 11:00:00,13603.0 -2017-07-26 12:00:00,14591.0 -2017-07-26 13:00:00,15550.0 -2017-07-26 14:00:00,16527.0 -2017-07-26 15:00:00,17377.0 -2017-07-26 16:00:00,17628.0 -2017-07-26 17:00:00,17450.0 -2017-07-26 18:00:00,17115.0 -2017-07-26 19:00:00,16749.0 -2017-07-26 20:00:00,16342.0 -2017-07-26 21:00:00,16071.0 -2017-07-26 22:00:00,16014.0 -2017-07-26 23:00:00,15515.0 -2017-07-27 00:00:00,14501.0 -2017-07-25 01:00:00,10709.0 -2017-07-25 02:00:00,10051.0 -2017-07-25 03:00:00,9555.0 -2017-07-25 04:00:00,9256.0 -2017-07-25 05:00:00,9108.0 -2017-07-25 06:00:00,9240.0 -2017-07-25 07:00:00,9656.0 -2017-07-25 08:00:00,10479.0 -2017-07-25 09:00:00,11536.0 -2017-07-25 10:00:00,12348.0 -2017-07-25 11:00:00,12928.0 -2017-07-25 12:00:00,13506.0 -2017-07-25 13:00:00,13958.0 -2017-07-25 14:00:00,14388.0 -2017-07-25 15:00:00,14946.0 -2017-07-25 16:00:00,15395.0 -2017-07-25 17:00:00,15720.0 -2017-07-25 18:00:00,15797.0 -2017-07-25 19:00:00,15455.0 -2017-07-25 20:00:00,14857.0 -2017-07-25 21:00:00,14286.0 -2017-07-25 22:00:00,13958.0 -2017-07-25 23:00:00,13479.0 -2017-07-26 00:00:00,12459.0 -2017-07-24 01:00:00,11513.0 -2017-07-24 02:00:00,10749.0 -2017-07-24 03:00:00,10209.0 -2017-07-24 04:00:00,9862.0 -2017-07-24 05:00:00,9690.0 -2017-07-24 06:00:00,9790.0 -2017-07-24 07:00:00,10325.0 -2017-07-24 08:00:00,10979.0 -2017-07-24 09:00:00,11902.0 -2017-07-24 10:00:00,12530.0 -2017-07-24 11:00:00,13002.0 -2017-07-24 12:00:00,13339.0 -2017-07-24 13:00:00,13552.0 -2017-07-24 14:00:00,13731.0 -2017-07-24 15:00:00,14005.0 -2017-07-24 16:00:00,14214.0 -2017-07-24 17:00:00,14411.0 -2017-07-24 18:00:00,14485.0 -2017-07-24 19:00:00,14386.0 -2017-07-24 20:00:00,13902.0 -2017-07-24 21:00:00,13271.0 -2017-07-24 22:00:00,12973.0 -2017-07-24 23:00:00,12597.0 -2017-07-25 00:00:00,11690.0 -2017-07-23 01:00:00,12618.0 -2017-07-23 02:00:00,11727.0 -2017-07-23 03:00:00,11058.0 -2017-07-23 04:00:00,10607.0 -2017-07-23 05:00:00,10288.0 -2017-07-23 06:00:00,10116.0 -2017-07-23 07:00:00,9982.0 -2017-07-23 08:00:00,10100.0 -2017-07-23 09:00:00,10827.0 -2017-07-23 10:00:00,11985.0 -2017-07-23 11:00:00,13313.0 -2017-07-23 12:00:00,14555.0 -2017-07-23 13:00:00,15500.0 -2017-07-23 14:00:00,16122.0 -2017-07-23 15:00:00,16632.0 -2017-07-23 16:00:00,16837.0 -2017-07-23 17:00:00,16796.0 -2017-07-23 18:00:00,16207.0 -2017-07-23 19:00:00,15026.0 -2017-07-23 20:00:00,14318.0 -2017-07-23 21:00:00,13765.0 -2017-07-23 22:00:00,13477.0 -2017-07-23 23:00:00,13102.0 -2017-07-24 00:00:00,12413.0 -2017-07-22 01:00:00,13141.0 -2017-07-22 02:00:00,12146.0 -2017-07-22 03:00:00,11437.0 -2017-07-22 04:00:00,11009.0 -2017-07-22 05:00:00,10693.0 -2017-07-22 06:00:00,10612.0 -2017-07-22 07:00:00,10714.0 -2017-07-22 08:00:00,10981.0 -2017-07-22 09:00:00,11264.0 -2017-07-22 10:00:00,11776.0 -2017-07-22 11:00:00,12550.0 -2017-07-22 12:00:00,13403.0 -2017-07-22 13:00:00,14200.0 -2017-07-22 14:00:00,14900.0 -2017-07-22 15:00:00,15465.0 -2017-07-22 16:00:00,15880.0 -2017-07-22 17:00:00,16112.0 -2017-07-22 18:00:00,16411.0 -2017-07-22 19:00:00,16397.0 -2017-07-22 20:00:00,16074.0 -2017-07-22 21:00:00,15400.0 -2017-07-22 22:00:00,14981.0 -2017-07-22 23:00:00,14516.0 -2017-07-23 00:00:00,13624.0 -2017-07-21 01:00:00,13408.0 -2017-07-21 02:00:00,12371.0 -2017-07-21 03:00:00,11674.0 -2017-07-21 04:00:00,11241.0 -2017-07-21 05:00:00,10935.0 -2017-07-21 06:00:00,10979.0 -2017-07-21 07:00:00,11426.0 -2017-07-21 08:00:00,12215.0 -2017-07-21 09:00:00,13405.0 -2017-07-21 10:00:00,14596.0 -2017-07-21 11:00:00,15581.0 -2017-07-21 12:00:00,16459.0 -2017-07-21 13:00:00,17122.0 -2017-07-21 14:00:00,17679.0 -2017-07-21 15:00:00,18309.0 -2017-07-21 16:00:00,18626.0 -2017-07-21 17:00:00,18506.0 -2017-07-21 18:00:00,17952.0 -2017-07-21 19:00:00,17237.0 -2017-07-21 20:00:00,16656.0 -2017-07-21 21:00:00,16227.0 -2017-07-21 22:00:00,15986.0 -2017-07-21 23:00:00,15187.0 -2017-07-22 00:00:00,14179.0 -2017-07-20 01:00:00,12910.0 -2017-07-20 02:00:00,11914.0 -2017-07-20 03:00:00,11268.0 -2017-07-20 04:00:00,10823.0 -2017-07-20 05:00:00,10582.0 -2017-07-20 06:00:00,10685.0 -2017-07-20 07:00:00,11306.0 -2017-07-20 08:00:00,12145.0 -2017-07-20 09:00:00,12827.0 -2017-07-20 10:00:00,13270.0 -2017-07-20 11:00:00,13725.0 -2017-07-20 12:00:00,14662.0 -2017-07-20 13:00:00,15840.0 -2017-07-20 14:00:00,16936.0 -2017-07-20 15:00:00,17936.0 -2017-07-20 16:00:00,18287.0 -2017-07-20 17:00:00,18023.0 -2017-07-20 18:00:00,17857.0 -2017-07-20 19:00:00,17713.0 -2017-07-20 20:00:00,17209.0 -2017-07-20 21:00:00,16694.0 -2017-07-20 22:00:00,16341.0 -2017-07-20 23:00:00,15811.0 -2017-07-21 00:00:00,14627.0 -2017-07-19 01:00:00,13511.0 -2017-07-19 02:00:00,12507.0 -2017-07-19 03:00:00,11708.0 -2017-07-19 04:00:00,11056.0 -2017-07-19 05:00:00,10726.0 -2017-07-19 06:00:00,10777.0 -2017-07-19 07:00:00,11168.0 -2017-07-19 08:00:00,12099.0 -2017-07-19 09:00:00,13403.0 -2017-07-19 10:00:00,14618.0 -2017-07-19 11:00:00,15613.0 -2017-07-19 12:00:00,16660.0 -2017-07-19 13:00:00,17533.0 -2017-07-19 14:00:00,18175.0 -2017-07-19 15:00:00,18650.0 -2017-07-19 16:00:00,18840.0 -2017-07-19 17:00:00,18836.0 -2017-07-19 18:00:00,18842.0 -2017-07-19 19:00:00,18521.0 -2017-07-19 20:00:00,17976.0 -2017-07-19 21:00:00,17209.0 -2017-07-19 22:00:00,16660.0 -2017-07-19 23:00:00,15843.0 -2017-07-20 00:00:00,14197.0 -2017-07-18 01:00:00,10855.0 -2017-07-18 02:00:00,10054.0 -2017-07-18 03:00:00,9527.0 -2017-07-18 04:00:00,9190.0 -2017-07-18 05:00:00,9013.0 -2017-07-18 06:00:00,9100.0 -2017-07-18 07:00:00,9529.0 -2017-07-18 08:00:00,10349.0 -2017-07-18 09:00:00,11450.0 -2017-07-18 10:00:00,12359.0 -2017-07-18 11:00:00,13119.0 -2017-07-18 12:00:00,13911.0 -2017-07-18 13:00:00,14605.0 -2017-07-18 14:00:00,15243.0 -2017-07-18 15:00:00,15967.0 -2017-07-18 16:00:00,16653.0 -2017-07-18 17:00:00,17233.0 -2017-07-18 18:00:00,17649.0 -2017-07-18 19:00:00,17604.0 -2017-07-18 20:00:00,17259.0 -2017-07-18 21:00:00,16790.0 -2017-07-18 22:00:00,16448.0 -2017-07-18 23:00:00,15973.0 -2017-07-19 00:00:00,14761.0 -2017-07-17 01:00:00,9602.0 -2017-07-17 02:00:00,8982.0 -2017-07-17 03:00:00,8607.0 -2017-07-17 04:00:00,8387.0 -2017-07-17 05:00:00,8325.0 -2017-07-17 06:00:00,8518.0 -2017-07-17 07:00:00,8926.0 -2017-07-17 08:00:00,9741.0 -2017-07-17 09:00:00,10728.0 -2017-07-17 10:00:00,11548.0 -2017-07-17 11:00:00,12181.0 -2017-07-17 12:00:00,12715.0 -2017-07-17 13:00:00,13136.0 -2017-07-17 14:00:00,13509.0 -2017-07-17 15:00:00,13950.0 -2017-07-17 16:00:00,14319.0 -2017-07-17 17:00:00,14616.0 -2017-07-17 18:00:00,14821.0 -2017-07-17 19:00:00,14822.0 -2017-07-17 20:00:00,14381.0 -2017-07-17 21:00:00,13766.0 -2017-07-17 22:00:00,13320.0 -2017-07-17 23:00:00,12914.0 -2017-07-18 00:00:00,11931.0 -2017-07-16 01:00:00,11042.0 -2017-07-16 02:00:00,10399.0 -2017-07-16 03:00:00,9816.0 -2017-07-16 04:00:00,9441.0 -2017-07-16 05:00:00,9267.0 -2017-07-16 06:00:00,9178.0 -2017-07-16 07:00:00,9028.0 -2017-07-16 08:00:00,8993.0 -2017-07-16 09:00:00,9372.0 -2017-07-16 10:00:00,9839.0 -2017-07-16 11:00:00,10297.0 -2017-07-16 12:00:00,10777.0 -2017-07-16 13:00:00,11051.0 -2017-07-16 14:00:00,11147.0 -2017-07-16 15:00:00,11286.0 -2017-07-16 16:00:00,11402.0 -2017-07-16 17:00:00,11567.0 -2017-07-16 18:00:00,11818.0 -2017-07-16 19:00:00,11874.0 -2017-07-16 20:00:00,11662.0 -2017-07-16 21:00:00,11219.0 -2017-07-16 22:00:00,10982.0 -2017-07-16 23:00:00,10858.0 -2017-07-17 00:00:00,10316.0 -2017-07-15 01:00:00,10296.0 -2017-07-15 02:00:00,9631.0 -2017-07-15 03:00:00,9136.0 -2017-07-15 04:00:00,8802.0 -2017-07-15 05:00:00,8634.0 -2017-07-15 06:00:00,8565.0 -2017-07-15 07:00:00,8611.0 -2017-07-15 08:00:00,8850.0 -2017-07-15 09:00:00,9505.0 -2017-07-15 10:00:00,10291.0 -2017-07-15 11:00:00,11009.0 -2017-07-15 12:00:00,11647.0 -2017-07-15 13:00:00,12090.0 -2017-07-15 14:00:00,12504.0 -2017-07-15 15:00:00,12833.0 -2017-07-15 16:00:00,13208.0 -2017-07-15 17:00:00,13624.0 -2017-07-15 18:00:00,13767.0 -2017-07-15 19:00:00,13614.0 -2017-07-15 20:00:00,13348.0 -2017-07-15 21:00:00,13030.0 -2017-07-15 22:00:00,12722.0 -2017-07-15 23:00:00,12483.0 -2017-07-16 00:00:00,11818.0 -2017-07-14 01:00:00,12371.0 -2017-07-14 02:00:00,11408.0 -2017-07-14 03:00:00,10689.0 -2017-07-14 04:00:00,10197.0 -2017-07-14 05:00:00,9933.0 -2017-07-14 06:00:00,9976.0 -2017-07-14 07:00:00,10296.0 -2017-07-14 08:00:00,10950.0 -2017-07-14 09:00:00,11718.0 -2017-07-14 10:00:00,12322.0 -2017-07-14 11:00:00,12660.0 -2017-07-14 12:00:00,12866.0 -2017-07-14 13:00:00,13043.0 -2017-07-14 14:00:00,13194.0 -2017-07-14 15:00:00,13391.0 -2017-07-14 16:00:00,13394.0 -2017-07-14 17:00:00,13210.0 -2017-07-14 18:00:00,13094.0 -2017-07-14 19:00:00,12731.0 -2017-07-14 20:00:00,12244.0 -2017-07-14 21:00:00,11935.0 -2017-07-14 22:00:00,11890.0 -2017-07-14 23:00:00,11778.0 -2017-07-15 00:00:00,11055.0 -2017-07-13 01:00:00,12960.0 -2017-07-13 02:00:00,12172.0 -2017-07-13 03:00:00,11476.0 -2017-07-13 04:00:00,10968.0 -2017-07-13 05:00:00,10697.0 -2017-07-13 06:00:00,10806.0 -2017-07-13 07:00:00,11341.0 -2017-07-13 08:00:00,12160.0 -2017-07-13 09:00:00,13089.0 -2017-07-13 10:00:00,13742.0 -2017-07-13 11:00:00,14172.0 -2017-07-13 12:00:00,14590.0 -2017-07-13 13:00:00,15093.0 -2017-07-13 14:00:00,15535.0 -2017-07-13 15:00:00,16056.0 -2017-07-13 16:00:00,16526.0 -2017-07-13 17:00:00,16790.0 -2017-07-13 18:00:00,16963.0 -2017-07-13 19:00:00,16864.0 -2017-07-13 20:00:00,16550.0 -2017-07-13 21:00:00,15864.0 -2017-07-13 22:00:00,15207.0 -2017-07-13 23:00:00,14724.0 -2017-07-14 00:00:00,13590.0 -2017-07-12 01:00:00,13155.0 -2017-07-12 02:00:00,12256.0 -2017-07-12 03:00:00,11743.0 -2017-07-12 04:00:00,11326.0 -2017-07-12 05:00:00,11120.0 -2017-07-12 06:00:00,11081.0 -2017-07-12 07:00:00,11568.0 -2017-07-12 08:00:00,12346.0 -2017-07-12 09:00:00,13382.0 -2017-07-12 10:00:00,14046.0 -2017-07-12 11:00:00,14311.0 -2017-07-12 12:00:00,14355.0 -2017-07-12 13:00:00,14353.0 -2017-07-12 14:00:00,14258.0 -2017-07-12 15:00:00,14163.0 -2017-07-12 16:00:00,14284.0 -2017-07-12 17:00:00,14609.0 -2017-07-12 18:00:00,14839.0 -2017-07-12 19:00:00,14908.0 -2017-07-12 20:00:00,14941.0 -2017-07-12 21:00:00,14981.0 -2017-07-12 22:00:00,14993.0 -2017-07-12 23:00:00,14739.0 -2017-07-13 00:00:00,13906.0 -2017-07-11 01:00:00,12362.0 -2017-07-11 02:00:00,11437.0 -2017-07-11 03:00:00,10800.0 -2017-07-11 04:00:00,10394.0 -2017-07-11 05:00:00,10172.0 -2017-07-11 06:00:00,10296.0 -2017-07-11 07:00:00,10739.0 -2017-07-11 08:00:00,11712.0 -2017-07-11 09:00:00,12885.0 -2017-07-11 10:00:00,13975.0 -2017-07-11 11:00:00,14796.0 -2017-07-11 12:00:00,15456.0 -2017-07-11 13:00:00,16049.0 -2017-07-11 14:00:00,16772.0 -2017-07-11 15:00:00,17401.0 -2017-07-11 16:00:00,17535.0 -2017-07-11 17:00:00,17114.0 -2017-07-11 18:00:00,16628.0 -2017-07-11 19:00:00,16378.0 -2017-07-11 20:00:00,16089.0 -2017-07-11 21:00:00,15750.0 -2017-07-11 22:00:00,15564.0 -2017-07-11 23:00:00,15224.0 -2017-07-12 00:00:00,14296.0 -2017-07-10 01:00:00,12523.0 -2017-07-10 02:00:00,11705.0 -2017-07-10 03:00:00,11136.0 -2017-07-10 04:00:00,10782.0 -2017-07-10 05:00:00,10660.0 -2017-07-10 06:00:00,10790.0 -2017-07-10 07:00:00,11243.0 -2017-07-10 08:00:00,11847.0 -2017-07-10 09:00:00,12408.0 -2017-07-10 10:00:00,12836.0 -2017-07-10 11:00:00,13402.0 -2017-07-10 12:00:00,14194.0 -2017-07-10 13:00:00,14705.0 -2017-07-10 14:00:00,15056.0 -2017-07-10 15:00:00,15662.0 -2017-07-10 16:00:00,16147.0 -2017-07-10 17:00:00,15987.0 -2017-07-10 18:00:00,15924.0 -2017-07-10 19:00:00,16101.0 -2017-07-10 20:00:00,15862.0 -2017-07-10 21:00:00,15439.0 -2017-07-10 22:00:00,15028.0 -2017-07-10 23:00:00,14764.0 -2017-07-11 00:00:00,13570.0 -2017-07-09 01:00:00,10044.0 -2017-07-09 02:00:00,9394.0 -2017-07-09 03:00:00,8855.0 -2017-07-09 04:00:00,8537.0 -2017-07-09 05:00:00,8276.0 -2017-07-09 06:00:00,8168.0 -2017-07-09 07:00:00,7949.0 -2017-07-09 08:00:00,8091.0 -2017-07-09 09:00:00,8615.0 -2017-07-09 10:00:00,9404.0 -2017-07-09 11:00:00,10362.0 -2017-07-09 12:00:00,11197.0 -2017-07-09 13:00:00,11990.0 -2017-07-09 14:00:00,12641.0 -2017-07-09 15:00:00,13319.0 -2017-07-09 16:00:00,13861.0 -2017-07-09 17:00:00,14219.0 -2017-07-09 18:00:00,14631.0 -2017-07-09 19:00:00,14716.0 -2017-07-09 20:00:00,14550.0 -2017-07-09 21:00:00,14257.0 -2017-07-09 22:00:00,14220.0 -2017-07-09 23:00:00,14133.0 -2017-07-10 00:00:00,13452.0 -2017-07-08 01:00:00,11519.0 -2017-07-08 02:00:00,10625.0 -2017-07-08 03:00:00,9953.0 -2017-07-08 04:00:00,9503.0 -2017-07-08 05:00:00,9224.0 -2017-07-08 06:00:00,9108.0 -2017-07-08 07:00:00,9016.0 -2017-07-08 08:00:00,9328.0 -2017-07-08 09:00:00,9999.0 -2017-07-08 10:00:00,10749.0 -2017-07-08 11:00:00,11433.0 -2017-07-08 12:00:00,11973.0 -2017-07-08 13:00:00,12357.0 -2017-07-08 14:00:00,12567.0 -2017-07-08 15:00:00,12730.0 -2017-07-08 16:00:00,12908.0 -2017-07-08 17:00:00,13149.0 -2017-07-08 18:00:00,13256.0 -2017-07-08 19:00:00,13179.0 -2017-07-08 20:00:00,12901.0 -2017-07-08 21:00:00,12321.0 -2017-07-08 22:00:00,11790.0 -2017-07-08 23:00:00,11515.0 -2017-07-09 00:00:00,10830.0 -2017-07-07 01:00:00,14605.0 -2017-07-07 02:00:00,13542.0 -2017-07-07 03:00:00,12736.0 -2017-07-07 04:00:00,12047.0 -2017-07-07 05:00:00,11575.0 -2017-07-07 06:00:00,11603.0 -2017-07-07 07:00:00,12012.0 -2017-07-07 08:00:00,12857.0 -2017-07-07 09:00:00,13850.0 -2017-07-07 10:00:00,14686.0 -2017-07-07 11:00:00,15479.0 -2017-07-07 12:00:00,16434.0 -2017-07-07 13:00:00,17034.0 -2017-07-07 14:00:00,17336.0 -2017-07-07 15:00:00,17648.0 -2017-07-07 16:00:00,17730.0 -2017-07-07 17:00:00,17586.0 -2017-07-07 18:00:00,17371.0 -2017-07-07 19:00:00,16943.0 -2017-07-07 20:00:00,16099.0 -2017-07-07 21:00:00,15090.0 -2017-07-07 22:00:00,14375.0 -2017-07-07 23:00:00,13895.0 -2017-07-08 00:00:00,12760.0 -2017-07-06 01:00:00,12322.0 -2017-07-06 02:00:00,11422.0 -2017-07-06 03:00:00,10809.0 -2017-07-06 04:00:00,10340.0 -2017-07-06 05:00:00,10095.0 -2017-07-06 06:00:00,10149.0 -2017-07-06 07:00:00,10531.0 -2017-07-06 08:00:00,11523.0 -2017-07-06 09:00:00,12904.0 -2017-07-06 10:00:00,14201.0 -2017-07-06 11:00:00,15371.0 -2017-07-06 12:00:00,16453.0 -2017-07-06 13:00:00,17311.0 -2017-07-06 14:00:00,18052.0 -2017-07-06 15:00:00,18687.0 -2017-07-06 16:00:00,19054.0 -2017-07-06 17:00:00,19269.0 -2017-07-06 18:00:00,19408.0 -2017-07-06 19:00:00,19354.0 -2017-07-06 20:00:00,19005.0 -2017-07-06 21:00:00,18389.0 -2017-07-06 22:00:00,17753.0 -2017-07-06 23:00:00,17205.0 -2017-07-07 00:00:00,15918.0 -2017-07-05 01:00:00,10778.0 -2017-07-05 02:00:00,10057.0 -2017-07-05 03:00:00,9530.0 -2017-07-05 04:00:00,9165.0 -2017-07-05 05:00:00,8984.0 -2017-07-05 06:00:00,9122.0 -2017-07-05 07:00:00,9573.0 -2017-07-05 08:00:00,10500.0 -2017-07-05 09:00:00,11680.0 -2017-07-05 10:00:00,12759.0 -2017-07-05 11:00:00,13781.0 -2017-07-05 12:00:00,14845.0 -2017-07-05 13:00:00,15680.0 -2017-07-05 14:00:00,16392.0 -2017-07-05 15:00:00,17044.0 -2017-07-05 16:00:00,17404.0 -2017-07-05 17:00:00,17531.0 -2017-07-05 18:00:00,17527.0 -2017-07-05 19:00:00,17020.0 -2017-07-05 20:00:00,16187.0 -2017-07-05 21:00:00,15415.0 -2017-07-05 22:00:00,15061.0 -2017-07-05 23:00:00,14533.0 -2017-07-06 00:00:00,13470.0 -2017-07-04 01:00:00,10085.0 -2017-07-04 02:00:00,9421.0 -2017-07-04 03:00:00,8945.0 -2017-07-04 04:00:00,8604.0 -2017-07-04 05:00:00,8420.0 -2017-07-04 06:00:00,8384.0 -2017-07-04 07:00:00,8306.0 -2017-07-04 08:00:00,8469.0 -2017-07-04 09:00:00,8993.0 -2017-07-04 10:00:00,9856.0 -2017-07-04 11:00:00,10827.0 -2017-07-04 12:00:00,11836.0 -2017-07-04 13:00:00,12708.0 -2017-07-04 14:00:00,13450.0 -2017-07-04 15:00:00,13906.0 -2017-07-04 16:00:00,14159.0 -2017-07-04 17:00:00,14202.0 -2017-07-04 18:00:00,14150.0 -2017-07-04 19:00:00,13960.0 -2017-07-04 20:00:00,13443.0 -2017-07-04 21:00:00,12768.0 -2017-07-04 22:00:00,12282.0 -2017-07-04 23:00:00,11939.0 -2017-07-05 00:00:00,11463.0 -2017-07-03 01:00:00,11112.0 -2017-07-03 02:00:00,10347.0 -2017-07-03 03:00:00,9819.0 -2017-07-03 04:00:00,9438.0 -2017-07-03 05:00:00,9250.0 -2017-07-03 06:00:00,9267.0 -2017-07-03 07:00:00,9503.0 -2017-07-03 08:00:00,10034.0 -2017-07-03 09:00:00,10853.0 -2017-07-03 10:00:00,11700.0 -2017-07-03 11:00:00,12530.0 -2017-07-03 12:00:00,13310.0 -2017-07-03 13:00:00,13901.0 -2017-07-03 14:00:00,14348.0 -2017-07-03 15:00:00,14725.0 -2017-07-03 16:00:00,14912.0 -2017-07-03 17:00:00,14981.0 -2017-07-03 18:00:00,14588.0 -2017-07-03 19:00:00,13889.0 -2017-07-03 20:00:00,13028.0 -2017-07-03 21:00:00,12300.0 -2017-07-03 22:00:00,11857.0 -2017-07-03 23:00:00,11544.0 -2017-07-04 00:00:00,10858.0 -2017-07-02 01:00:00,10487.0 -2017-07-02 02:00:00,9710.0 -2017-07-02 03:00:00,9153.0 -2017-07-02 04:00:00,8712.0 -2017-07-02 05:00:00,8472.0 -2017-07-02 06:00:00,8301.0 -2017-07-02 07:00:00,8132.0 -2017-07-02 08:00:00,8244.0 -2017-07-02 09:00:00,8847.0 -2017-07-02 10:00:00,9603.0 -2017-07-02 11:00:00,10424.0 -2017-07-02 12:00:00,11161.0 -2017-07-02 13:00:00,11944.0 -2017-07-02 14:00:00,12601.0 -2017-07-02 15:00:00,13334.0 -2017-07-02 16:00:00,14119.0 -2017-07-02 17:00:00,14698.0 -2017-07-02 18:00:00,14893.0 -2017-07-02 19:00:00,14799.0 -2017-07-02 20:00:00,14167.0 -2017-07-02 21:00:00,13395.0 -2017-07-02 22:00:00,12910.0 -2017-07-02 23:00:00,12657.0 -2017-07-03 00:00:00,11943.0 -2017-07-01 01:00:00,11781.0 -2017-07-01 02:00:00,10873.0 -2017-07-01 03:00:00,10203.0 -2017-07-01 04:00:00,9625.0 -2017-07-01 05:00:00,9335.0 -2017-07-01 06:00:00,9235.0 -2017-07-01 07:00:00,9108.0 -2017-07-01 08:00:00,9400.0 -2017-07-01 09:00:00,10117.0 -2017-07-01 10:00:00,11067.0 -2017-07-01 11:00:00,12045.0 -2017-07-01 12:00:00,12835.0 -2017-07-01 13:00:00,13351.0 -2017-07-01 14:00:00,13771.0 -2017-07-01 15:00:00,13933.0 -2017-07-01 16:00:00,14019.0 -2017-07-01 17:00:00,14034.0 -2017-07-01 18:00:00,14108.0 -2017-07-01 19:00:00,13955.0 -2017-07-01 20:00:00,13530.0 -2017-07-01 21:00:00,12817.0 -2017-07-01 22:00:00,12235.0 -2017-07-01 23:00:00,11967.0 -2017-07-02 00:00:00,11228.0 -2017-06-30 01:00:00,11161.0 -2017-06-30 02:00:00,10392.0 -2017-06-30 03:00:00,9852.0 -2017-06-30 04:00:00,9449.0 -2017-06-30 05:00:00,9275.0 -2017-06-30 06:00:00,9427.0 -2017-06-30 07:00:00,9807.0 -2017-06-30 08:00:00,10683.0 -2017-06-30 09:00:00,11717.0 -2017-06-30 10:00:00,12632.0 -2017-06-30 11:00:00,13259.0 -2017-06-30 12:00:00,14024.0 -2017-06-30 13:00:00,14695.0 -2017-06-30 14:00:00,15177.0 -2017-06-30 15:00:00,15505.0 -2017-06-30 16:00:00,15623.0 -2017-06-30 17:00:00,15495.0 -2017-06-30 18:00:00,15354.0 -2017-06-30 19:00:00,15369.0 -2017-06-30 20:00:00,14990.0 -2017-06-30 21:00:00,14478.0 -2017-06-30 22:00:00,14053.0 -2017-06-30 23:00:00,13763.0 -2017-07-01 00:00:00,12837.0 -2017-06-29 01:00:00,10527.0 -2017-06-29 02:00:00,9821.0 -2017-06-29 03:00:00,9392.0 -2017-06-29 04:00:00,9099.0 -2017-06-29 05:00:00,9010.0 -2017-06-29 06:00:00,9193.0 -2017-06-29 07:00:00,9693.0 -2017-06-29 08:00:00,10595.0 -2017-06-29 09:00:00,11607.0 -2017-06-29 10:00:00,12433.0 -2017-06-29 11:00:00,13124.0 -2017-06-29 12:00:00,13793.0 -2017-06-29 13:00:00,14429.0 -2017-06-29 14:00:00,15032.0 -2017-06-29 15:00:00,15372.0 -2017-06-29 16:00:00,15613.0 -2017-06-29 17:00:00,15522.0 -2017-06-29 18:00:00,15252.0 -2017-06-29 19:00:00,14687.0 -2017-06-29 20:00:00,14131.0 -2017-06-29 21:00:00,13768.0 -2017-06-29 22:00:00,13393.0 -2017-06-29 23:00:00,13049.0 -2017-06-30 00:00:00,12160.0 -2017-06-28 01:00:00,9796.0 -2017-06-28 02:00:00,9164.0 -2017-06-28 03:00:00,8751.0 -2017-06-28 04:00:00,8492.0 -2017-06-28 05:00:00,8356.0 -2017-06-28 06:00:00,8526.0 -2017-06-28 07:00:00,8867.0 -2017-06-28 08:00:00,9679.0 -2017-06-28 09:00:00,10516.0 -2017-06-28 10:00:00,11056.0 -2017-06-28 11:00:00,11366.0 -2017-06-28 12:00:00,11651.0 -2017-06-28 13:00:00,11762.0 -2017-06-28 14:00:00,11708.0 -2017-06-28 15:00:00,11930.0 -2017-06-28 16:00:00,12160.0 -2017-06-28 17:00:00,12371.0 -2017-06-28 18:00:00,12507.0 -2017-06-28 19:00:00,12364.0 -2017-06-28 20:00:00,12174.0 -2017-06-28 21:00:00,12140.0 -2017-06-28 22:00:00,12333.0 -2017-06-28 23:00:00,12148.0 -2017-06-29 00:00:00,11367.0 -2017-06-27 01:00:00,9153.0 -2017-06-27 02:00:00,8616.0 -2017-06-27 03:00:00,8264.0 -2017-06-27 04:00:00,8052.0 -2017-06-27 05:00:00,7931.0 -2017-06-27 06:00:00,8057.0 -2017-06-27 07:00:00,8444.0 -2017-06-27 08:00:00,9170.0 -2017-06-27 09:00:00,10089.0 -2017-06-27 10:00:00,10751.0 -2017-06-27 11:00:00,11184.0 -2017-06-27 12:00:00,11562.0 -2017-06-27 13:00:00,11801.0 -2017-06-27 14:00:00,11954.0 -2017-06-27 15:00:00,12212.0 -2017-06-27 16:00:00,12342.0 -2017-06-27 17:00:00,12451.0 -2017-06-27 18:00:00,12539.0 -2017-06-27 19:00:00,12498.0 -2017-06-27 20:00:00,12228.0 -2017-06-27 21:00:00,11875.0 -2017-06-27 22:00:00,11624.0 -2017-06-27 23:00:00,11449.0 -2017-06-28 00:00:00,10677.0 -2017-06-26 01:00:00,8682.0 -2017-06-26 02:00:00,8241.0 -2017-06-26 03:00:00,7946.0 -2017-06-26 04:00:00,7739.0 -2017-06-26 05:00:00,7699.0 -2017-06-26 06:00:00,7921.0 -2017-06-26 07:00:00,8322.0 -2017-06-26 08:00:00,9137.0 -2017-06-26 09:00:00,10080.0 -2017-06-26 10:00:00,10694.0 -2017-06-26 11:00:00,11101.0 -2017-06-26 12:00:00,11424.0 -2017-06-26 13:00:00,11614.0 -2017-06-26 14:00:00,11717.0 -2017-06-26 15:00:00,11833.0 -2017-06-26 16:00:00,11842.0 -2017-06-26 17:00:00,11800.0 -2017-06-26 18:00:00,11711.0 -2017-06-26 19:00:00,11471.0 -2017-06-26 20:00:00,11024.0 -2017-06-26 21:00:00,10749.0 -2017-06-26 22:00:00,10712.0 -2017-06-26 23:00:00,10613.0 -2017-06-27 00:00:00,9909.0 -2017-06-25 01:00:00,9036.0 -2017-06-25 02:00:00,8532.0 -2017-06-25 03:00:00,8182.0 -2017-06-25 04:00:00,7899.0 -2017-06-25 05:00:00,7728.0 -2017-06-25 06:00:00,7626.0 -2017-06-25 07:00:00,7481.0 -2017-06-25 08:00:00,7605.0 -2017-06-25 09:00:00,7984.0 -2017-06-25 10:00:00,8494.0 -2017-06-25 11:00:00,8951.0 -2017-06-25 12:00:00,9305.0 -2017-06-25 13:00:00,9502.0 -2017-06-25 14:00:00,9576.0 -2017-06-25 15:00:00,9716.0 -2017-06-25 16:00:00,9758.0 -2017-06-25 17:00:00,9803.0 -2017-06-25 18:00:00,9903.0 -2017-06-25 19:00:00,9968.0 -2017-06-25 20:00:00,9834.0 -2017-06-25 21:00:00,9622.0 -2017-06-25 22:00:00,9639.0 -2017-06-25 23:00:00,9712.0 -2017-06-26 00:00:00,9272.0 -2017-06-24 01:00:00,10871.0 -2017-06-24 02:00:00,10049.0 -2017-06-24 03:00:00,9462.0 -2017-06-24 04:00:00,9092.0 -2017-06-24 05:00:00,8782.0 -2017-06-24 06:00:00,8727.0 -2017-06-24 07:00:00,8670.0 -2017-06-24 08:00:00,9065.0 -2017-06-24 09:00:00,9697.0 -2017-06-24 10:00:00,10357.0 -2017-06-24 11:00:00,10971.0 -2017-06-24 12:00:00,11333.0 -2017-06-24 13:00:00,11461.0 -2017-06-24 14:00:00,11462.0 -2017-06-24 15:00:00,11409.0 -2017-06-24 16:00:00,11344.0 -2017-06-24 17:00:00,11370.0 -2017-06-24 18:00:00,11378.0 -2017-06-24 19:00:00,11242.0 -2017-06-24 20:00:00,10964.0 -2017-06-24 21:00:00,10479.0 -2017-06-24 22:00:00,10254.0 -2017-06-24 23:00:00,10118.0 -2017-06-25 00:00:00,9612.0 -2017-06-23 01:00:00,13586.0 -2017-06-23 02:00:00,12627.0 -2017-06-23 03:00:00,11966.0 -2017-06-23 04:00:00,11525.0 -2017-06-23 05:00:00,11315.0 -2017-06-23 06:00:00,11343.0 -2017-06-23 07:00:00,11475.0 -2017-06-23 08:00:00,11899.0 -2017-06-23 09:00:00,12470.0 -2017-06-23 10:00:00,12905.0 -2017-06-23 11:00:00,13236.0 -2017-06-23 12:00:00,13820.0 -2017-06-23 13:00:00,14243.0 -2017-06-23 14:00:00,14670.0 -2017-06-23 15:00:00,15045.0 -2017-06-23 16:00:00,15277.0 -2017-06-23 17:00:00,15507.0 -2017-06-23 18:00:00,15623.0 -2017-06-23 19:00:00,15313.0 -2017-06-23 20:00:00,14622.0 -2017-06-23 21:00:00,13880.0 -2017-06-23 22:00:00,13228.0 -2017-06-23 23:00:00,12819.0 -2017-06-24 00:00:00,11911.0 -2017-06-22 01:00:00,11059.0 -2017-06-22 02:00:00,10365.0 -2017-06-22 03:00:00,9893.0 -2017-06-22 04:00:00,9662.0 -2017-06-22 05:00:00,9748.0 -2017-06-22 06:00:00,10077.0 -2017-06-22 07:00:00,10695.0 -2017-06-22 08:00:00,11711.0 -2017-06-22 09:00:00,12721.0 -2017-06-22 10:00:00,13676.0 -2017-06-22 11:00:00,14766.0 -2017-06-22 12:00:00,15791.0 -2017-06-22 13:00:00,16620.0 -2017-06-22 14:00:00,17047.0 -2017-06-22 15:00:00,17249.0 -2017-06-22 16:00:00,17891.0 -2017-06-22 17:00:00,18189.0 -2017-06-22 18:00:00,18002.0 -2017-06-22 19:00:00,17805.0 -2017-06-22 20:00:00,17469.0 -2017-06-22 21:00:00,16894.0 -2017-06-22 22:00:00,16490.0 -2017-06-22 23:00:00,16008.0 -2017-06-23 00:00:00,14844.0 -2017-06-21 01:00:00,9798.0 -2017-06-21 02:00:00,9188.0 -2017-06-21 03:00:00,8776.0 -2017-06-21 04:00:00,8524.0 -2017-06-21 05:00:00,8419.0 -2017-06-21 06:00:00,8583.0 -2017-06-21 07:00:00,8917.0 -2017-06-21 08:00:00,9851.0 -2017-06-21 09:00:00,10822.0 -2017-06-21 10:00:00,11554.0 -2017-06-21 11:00:00,12049.0 -2017-06-21 12:00:00,12470.0 -2017-06-21 13:00:00,12819.0 -2017-06-21 14:00:00,13126.0 -2017-06-21 15:00:00,13550.0 -2017-06-21 16:00:00,13884.0 -2017-06-21 17:00:00,13775.0 -2017-06-21 18:00:00,13706.0 -2017-06-21 19:00:00,13625.0 -2017-06-21 20:00:00,13276.0 -2017-06-21 21:00:00,13005.0 -2017-06-21 22:00:00,12938.0 -2017-06-21 23:00:00,12768.0 -2017-06-22 00:00:00,11953.0 -2017-06-20 01:00:00,9942.0 -2017-06-20 02:00:00,9331.0 -2017-06-20 03:00:00,8912.0 -2017-06-20 04:00:00,8675.0 -2017-06-20 05:00:00,8557.0 -2017-06-20 06:00:00,8661.0 -2017-06-20 07:00:00,9043.0 -2017-06-20 08:00:00,9997.0 -2017-06-20 09:00:00,10953.0 -2017-06-20 10:00:00,11708.0 -2017-06-20 11:00:00,12283.0 -2017-06-20 12:00:00,12777.0 -2017-06-20 13:00:00,13173.0 -2017-06-20 14:00:00,13350.0 -2017-06-20 15:00:00,13432.0 -2017-06-20 16:00:00,13364.0 -2017-06-20 17:00:00,12939.0 -2017-06-20 18:00:00,12458.0 -2017-06-20 19:00:00,12258.0 -2017-06-20 20:00:00,12072.0 -2017-06-20 21:00:00,11773.0 -2017-06-20 22:00:00,11583.0 -2017-06-20 23:00:00,11436.0 -2017-06-21 00:00:00,10669.0 -2017-06-19 01:00:00,10495.0 -2017-06-19 02:00:00,9817.0 -2017-06-19 03:00:00,9355.0 -2017-06-19 04:00:00,9089.0 -2017-06-19 05:00:00,8922.0 -2017-06-19 06:00:00,9107.0 -2017-06-19 07:00:00,9480.0 -2017-06-19 08:00:00,10452.0 -2017-06-19 09:00:00,11399.0 -2017-06-19 10:00:00,12065.0 -2017-06-19 11:00:00,12789.0 -2017-06-19 12:00:00,13390.0 -2017-06-19 13:00:00,13722.0 -2017-06-19 14:00:00,14046.0 -2017-06-19 15:00:00,14313.0 -2017-06-19 16:00:00,14265.0 -2017-06-19 17:00:00,13844.0 -2017-06-19 18:00:00,13315.0 -2017-06-19 19:00:00,12921.0 -2017-06-19 20:00:00,12442.0 -2017-06-19 21:00:00,12059.0 -2017-06-19 22:00:00,11870.0 -2017-06-19 23:00:00,11668.0 -2017-06-20 00:00:00,10853.0 -2017-06-18 01:00:00,11910.0 -2017-06-18 02:00:00,11101.0 -2017-06-18 03:00:00,10557.0 -2017-06-18 04:00:00,10151.0 -2017-06-18 05:00:00,9959.0 -2017-06-18 06:00:00,9808.0 -2017-06-18 07:00:00,9705.0 -2017-06-18 08:00:00,9701.0 -2017-06-18 09:00:00,10189.0 -2017-06-18 10:00:00,11027.0 -2017-06-18 11:00:00,11706.0 -2017-06-18 12:00:00,12251.0 -2017-06-18 13:00:00,12488.0 -2017-06-18 14:00:00,12598.0 -2017-06-18 15:00:00,12687.0 -2017-06-18 16:00:00,12738.0 -2017-06-18 17:00:00,12765.0 -2017-06-18 18:00:00,12838.0 -2017-06-18 19:00:00,12731.0 -2017-06-18 20:00:00,12344.0 -2017-06-18 21:00:00,11906.0 -2017-06-18 22:00:00,11840.0 -2017-06-18 23:00:00,11856.0 -2017-06-19 00:00:00,11293.0 -2017-06-17 01:00:00,13116.0 -2017-06-17 02:00:00,12217.0 -2017-06-17 03:00:00,11550.0 -2017-06-17 04:00:00,11103.0 -2017-06-17 05:00:00,10771.0 -2017-06-17 06:00:00,10603.0 -2017-06-17 07:00:00,10529.0 -2017-06-17 08:00:00,10570.0 -2017-06-17 09:00:00,11119.0 -2017-06-17 10:00:00,12010.0 -2017-06-17 11:00:00,13197.0 -2017-06-17 12:00:00,13923.0 -2017-06-17 13:00:00,14534.0 -2017-06-17 14:00:00,15078.0 -2017-06-17 15:00:00,15504.0 -2017-06-17 16:00:00,15967.0 -2017-06-17 17:00:00,16061.0 -2017-06-17 18:00:00,15758.0 -2017-06-17 19:00:00,15059.0 -2017-06-17 20:00:00,14529.0 -2017-06-17 21:00:00,14101.0 -2017-06-17 22:00:00,14063.0 -2017-06-17 23:00:00,13592.0 -2017-06-18 00:00:00,12796.0 -2017-06-16 01:00:00,13283.0 -2017-06-16 02:00:00,12288.0 -2017-06-16 03:00:00,11413.0 -2017-06-16 04:00:00,10694.0 -2017-06-16 05:00:00,10379.0 -2017-06-16 06:00:00,10367.0 -2017-06-16 07:00:00,10788.0 -2017-06-16 08:00:00,11404.0 -2017-06-16 09:00:00,11984.0 -2017-06-16 10:00:00,12602.0 -2017-06-16 11:00:00,13105.0 -2017-06-16 12:00:00,13497.0 -2017-06-16 13:00:00,13918.0 -2017-06-16 14:00:00,14680.0 -2017-06-16 15:00:00,15609.0 -2017-06-16 16:00:00,16301.0 -2017-06-16 17:00:00,16907.0 -2017-06-16 18:00:00,17309.0 -2017-06-16 19:00:00,17398.0 -2017-06-16 20:00:00,17083.0 -2017-06-16 21:00:00,16339.0 -2017-06-16 22:00:00,15726.0 -2017-06-16 23:00:00,15213.0 -2017-06-17 00:00:00,14226.0 -2017-06-15 01:00:00,11655.0 -2017-06-15 02:00:00,10965.0 -2017-06-15 03:00:00,10447.0 -2017-06-15 04:00:00,10094.0 -2017-06-15 05:00:00,9960.0 -2017-06-15 06:00:00,10118.0 -2017-06-15 07:00:00,10519.0 -2017-06-15 08:00:00,11489.0 -2017-06-15 09:00:00,12454.0 -2017-06-15 10:00:00,13345.0 -2017-06-15 11:00:00,14253.0 -2017-06-15 12:00:00,14989.0 -2017-06-15 13:00:00,15729.0 -2017-06-15 14:00:00,16568.0 -2017-06-15 15:00:00,17354.0 -2017-06-15 16:00:00,17845.0 -2017-06-15 17:00:00,18254.0 -2017-06-15 18:00:00,18466.0 -2017-06-15 19:00:00,18413.0 -2017-06-15 20:00:00,18121.0 -2017-06-15 21:00:00,17408.0 -2017-06-15 22:00:00,16571.0 -2017-06-15 23:00:00,15831.0 -2017-06-16 00:00:00,14607.0 -2017-06-14 01:00:00,12354.0 -2017-06-14 02:00:00,11488.0 -2017-06-14 03:00:00,10902.0 -2017-06-14 04:00:00,10483.0 -2017-06-14 05:00:00,10283.0 -2017-06-14 06:00:00,10390.0 -2017-06-14 07:00:00,10742.0 -2017-06-14 08:00:00,11867.0 -2017-06-14 09:00:00,13254.0 -2017-06-14 10:00:00,14622.0 -2017-06-14 11:00:00,15904.0 -2017-06-14 12:00:00,17137.0 -2017-06-14 13:00:00,18067.0 -2017-06-14 14:00:00,18627.0 -2017-06-14 15:00:00,18973.0 -2017-06-14 16:00:00,18349.0 -2017-06-14 17:00:00,17435.0 -2017-06-14 18:00:00,16789.0 -2017-06-14 19:00:00,15715.0 -2017-06-14 20:00:00,14811.0 -2017-06-14 21:00:00,14316.0 -2017-06-14 22:00:00,14019.0 -2017-06-14 23:00:00,13566.0 -2017-06-15 00:00:00,12682.0 -2017-06-13 01:00:00,14384.0 -2017-06-13 02:00:00,13160.0 -2017-06-13 03:00:00,12262.0 -2017-06-13 04:00:00,11657.0 -2017-06-13 05:00:00,11256.0 -2017-06-13 06:00:00,11256.0 -2017-06-13 07:00:00,11561.0 -2017-06-13 08:00:00,12555.0 -2017-06-13 09:00:00,13821.0 -2017-06-13 10:00:00,15159.0 -2017-06-13 11:00:00,15972.0 -2017-06-13 12:00:00,16840.0 -2017-06-13 13:00:00,17616.0 -2017-06-13 14:00:00,18180.0 -2017-06-13 15:00:00,18577.0 -2017-06-13 16:00:00,18784.0 -2017-06-13 17:00:00,18502.0 -2017-06-13 18:00:00,17600.0 -2017-06-13 19:00:00,16740.0 -2017-06-13 20:00:00,15984.0 -2017-06-13 21:00:00,15231.0 -2017-06-13 22:00:00,14792.0 -2017-06-13 23:00:00,14478.0 -2017-06-14 00:00:00,13510.0 -2017-06-12 01:00:00,13461.0 -2017-06-12 02:00:00,12469.0 -2017-06-12 03:00:00,11712.0 -2017-06-12 04:00:00,11244.0 -2017-06-12 05:00:00,10944.0 -2017-06-12 06:00:00,10982.0 -2017-06-12 07:00:00,11352.0 -2017-06-12 08:00:00,12498.0 -2017-06-12 09:00:00,14046.0 -2017-06-12 10:00:00,15274.0 -2017-06-12 11:00:00,16387.0 -2017-06-12 12:00:00,17435.0 -2017-06-12 13:00:00,18267.0 -2017-06-12 14:00:00,18944.0 -2017-06-12 15:00:00,19527.0 -2017-06-12 16:00:00,19864.0 -2017-06-12 17:00:00,20166.0 -2017-06-12 18:00:00,20351.0 -2017-06-12 19:00:00,20266.0 -2017-06-12 20:00:00,19851.0 -2017-06-12 21:00:00,19063.0 -2017-06-12 22:00:00,18628.0 -2017-06-12 23:00:00,17764.0 -2017-06-13 00:00:00,15996.0 -2017-06-11 01:00:00,11907.0 -2017-06-11 02:00:00,11087.0 -2017-06-11 03:00:00,10457.0 -2017-06-11 04:00:00,10008.0 -2017-06-11 05:00:00,9627.0 -2017-06-11 06:00:00,9418.0 -2017-06-11 07:00:00,9199.0 -2017-06-11 08:00:00,9421.0 -2017-06-11 09:00:00,10311.0 -2017-06-11 10:00:00,11331.0 -2017-06-11 11:00:00,12381.0 -2017-06-11 12:00:00,13330.0 -2017-06-11 13:00:00,14291.0 -2017-06-11 14:00:00,15119.0 -2017-06-11 15:00:00,15801.0 -2017-06-11 16:00:00,16402.0 -2017-06-11 17:00:00,16828.0 -2017-06-11 18:00:00,17098.0 -2017-06-11 19:00:00,17124.0 -2017-06-11 20:00:00,16865.0 -2017-06-11 21:00:00,16390.0 -2017-06-11 22:00:00,15894.0 -2017-06-11 23:00:00,15614.0 -2017-06-12 00:00:00,14625.0 -2017-06-10 01:00:00,10968.0 -2017-06-10 02:00:00,10227.0 -2017-06-10 03:00:00,9635.0 -2017-06-10 04:00:00,9273.0 -2017-06-10 05:00:00,9044.0 -2017-06-10 06:00:00,9014.0 -2017-06-10 07:00:00,8996.0 -2017-06-10 08:00:00,9303.0 -2017-06-10 09:00:00,10056.0 -2017-06-10 10:00:00,10958.0 -2017-06-10 11:00:00,11879.0 -2017-06-10 12:00:00,12750.0 -2017-06-10 13:00:00,13466.0 -2017-06-10 14:00:00,13922.0 -2017-06-10 15:00:00,14382.0 -2017-06-10 16:00:00,14841.0 -2017-06-10 17:00:00,15245.0 -2017-06-10 18:00:00,15499.0 -2017-06-10 19:00:00,15466.0 -2017-06-10 20:00:00,15156.0 -2017-06-10 21:00:00,14510.0 -2017-06-10 22:00:00,13959.0 -2017-06-10 23:00:00,13620.0 -2017-06-11 00:00:00,12832.0 -2017-06-09 01:00:00,10543.0 -2017-06-09 02:00:00,9810.0 -2017-06-09 03:00:00,9262.0 -2017-06-09 04:00:00,8931.0 -2017-06-09 05:00:00,8799.0 -2017-06-09 06:00:00,8894.0 -2017-06-09 07:00:00,9195.0 -2017-06-09 08:00:00,10083.0 -2017-06-09 09:00:00,11127.0 -2017-06-09 10:00:00,12110.0 -2017-06-09 11:00:00,12912.0 -2017-06-09 12:00:00,13740.0 -2017-06-09 13:00:00,14382.0 -2017-06-09 14:00:00,14903.0 -2017-06-09 15:00:00,15441.0 -2017-06-09 16:00:00,15701.0 -2017-06-09 17:00:00,15805.0 -2017-06-09 18:00:00,15746.0 -2017-06-09 19:00:00,15289.0 -2017-06-09 20:00:00,14496.0 -2017-06-09 21:00:00,13572.0 -2017-06-09 22:00:00,13251.0 -2017-06-09 23:00:00,12850.0 -2017-06-10 00:00:00,12002.0 -2017-06-08 01:00:00,9682.0 -2017-06-08 02:00:00,9017.0 -2017-06-08 03:00:00,8569.0 -2017-06-08 04:00:00,8332.0 -2017-06-08 05:00:00,8181.0 -2017-06-08 06:00:00,8284.0 -2017-06-08 07:00:00,8634.0 -2017-06-08 08:00:00,9545.0 -2017-06-08 09:00:00,10551.0 -2017-06-08 10:00:00,11245.0 -2017-06-08 11:00:00,11759.0 -2017-06-08 12:00:00,12243.0 -2017-06-08 13:00:00,12613.0 -2017-06-08 14:00:00,12928.0 -2017-06-08 15:00:00,13294.0 -2017-06-08 16:00:00,13586.0 -2017-06-08 17:00:00,13794.0 -2017-06-08 18:00:00,13810.0 -2017-06-08 19:00:00,13506.0 -2017-06-08 20:00:00,13066.0 -2017-06-08 21:00:00,12738.0 -2017-06-08 22:00:00,12598.0 -2017-06-08 23:00:00,12351.0 -2017-06-09 00:00:00,11483.0 -2017-06-07 01:00:00,9416.0 -2017-06-07 02:00:00,8863.0 -2017-06-07 03:00:00,8484.0 -2017-06-07 04:00:00,8211.0 -2017-06-07 05:00:00,8097.0 -2017-06-07 06:00:00,8250.0 -2017-06-07 07:00:00,8548.0 -2017-06-07 08:00:00,9471.0 -2017-06-07 09:00:00,10435.0 -2017-06-07 10:00:00,11036.0 -2017-06-07 11:00:00,11462.0 -2017-06-07 12:00:00,11807.0 -2017-06-07 13:00:00,12025.0 -2017-06-07 14:00:00,12172.0 -2017-06-07 15:00:00,12363.0 -2017-06-07 16:00:00,12465.0 -2017-06-07 17:00:00,12523.0 -2017-06-07 18:00:00,12578.0 -2017-06-07 19:00:00,12511.0 -2017-06-07 20:00:00,12190.0 -2017-06-07 21:00:00,11810.0 -2017-06-07 22:00:00,11623.0 -2017-06-07 23:00:00,11374.0 -2017-06-08 00:00:00,10559.0 -2017-06-06 01:00:00,9510.0 -2017-06-06 02:00:00,8917.0 -2017-06-06 03:00:00,8495.0 -2017-06-06 04:00:00,8258.0 -2017-06-06 05:00:00,8139.0 -2017-06-06 06:00:00,8254.0 -2017-06-06 07:00:00,8582.0 -2017-06-06 08:00:00,9463.0 -2017-06-06 09:00:00,10371.0 -2017-06-06 10:00:00,10956.0 -2017-06-06 11:00:00,11327.0 -2017-06-06 12:00:00,11659.0 -2017-06-06 13:00:00,11868.0 -2017-06-06 14:00:00,12023.0 -2017-06-06 15:00:00,12214.0 -2017-06-06 16:00:00,12321.0 -2017-06-06 17:00:00,12330.0 -2017-06-06 18:00:00,12354.0 -2017-06-06 19:00:00,12181.0 -2017-06-06 20:00:00,11796.0 -2017-06-06 21:00:00,11413.0 -2017-06-06 22:00:00,11266.0 -2017-06-06 23:00:00,11028.0 -2017-06-07 00:00:00,10270.0 -2017-06-05 01:00:00,11140.0 -2017-06-05 02:00:00,10283.0 -2017-06-05 03:00:00,9698.0 -2017-06-05 04:00:00,9253.0 -2017-06-05 05:00:00,9036.0 -2017-06-05 06:00:00,9139.0 -2017-06-05 07:00:00,9534.0 -2017-06-05 08:00:00,10545.0 -2017-06-05 09:00:00,11695.0 -2017-06-05 10:00:00,12516.0 -2017-06-05 11:00:00,12989.0 -2017-06-05 12:00:00,13257.0 -2017-06-05 13:00:00,13275.0 -2017-06-05 14:00:00,13293.0 -2017-06-05 15:00:00,13302.0 -2017-06-05 16:00:00,13263.0 -2017-06-05 17:00:00,13170.0 -2017-06-05 18:00:00,13060.0 -2017-06-05 19:00:00,12776.0 -2017-06-05 20:00:00,12292.0 -2017-06-05 21:00:00,11748.0 -2017-06-05 22:00:00,11465.0 -2017-06-05 23:00:00,11186.0 -2017-06-06 00:00:00,10353.0 -2017-06-04 01:00:00,11081.0 -2017-06-04 02:00:00,10467.0 -2017-06-04 03:00:00,9889.0 -2017-06-04 04:00:00,9497.0 -2017-06-04 05:00:00,9177.0 -2017-06-04 06:00:00,9027.0 -2017-06-04 07:00:00,8867.0 -2017-06-04 08:00:00,8959.0 -2017-06-04 09:00:00,9404.0 -2017-06-04 10:00:00,10077.0 -2017-06-04 11:00:00,10996.0 -2017-06-04 12:00:00,12058.0 -2017-06-04 13:00:00,13073.0 -2017-06-04 14:00:00,13759.0 -2017-06-04 15:00:00,14301.0 -2017-06-04 16:00:00,14651.0 -2017-06-04 17:00:00,15002.0 -2017-06-04 18:00:00,15110.0 -2017-06-04 19:00:00,15126.0 -2017-06-04 20:00:00,14848.0 -2017-06-04 21:00:00,14396.0 -2017-06-04 22:00:00,13799.0 -2017-06-04 23:00:00,13379.0 -2017-06-05 00:00:00,12284.0 -2017-06-03 01:00:00,10333.0 -2017-06-03 02:00:00,9566.0 -2017-06-03 03:00:00,9018.0 -2017-06-03 04:00:00,8610.0 -2017-06-03 05:00:00,8382.0 -2017-06-03 06:00:00,8321.0 -2017-06-03 07:00:00,8238.0 -2017-06-03 08:00:00,8574.0 -2017-06-03 09:00:00,9323.0 -2017-06-03 10:00:00,10158.0 -2017-06-03 11:00:00,10867.0 -2017-06-03 12:00:00,11442.0 -2017-06-03 13:00:00,12021.0 -2017-06-03 14:00:00,12358.0 -2017-06-03 15:00:00,12671.0 -2017-06-03 16:00:00,12975.0 -2017-06-03 17:00:00,13300.0 -2017-06-03 18:00:00,13396.0 -2017-06-03 19:00:00,13401.0 -2017-06-03 20:00:00,13080.0 -2017-06-03 21:00:00,12756.0 -2017-06-03 22:00:00,12661.0 -2017-06-03 23:00:00,12512.0 -2017-06-04 00:00:00,11853.0 -2017-06-02 01:00:00,9505.0 -2017-06-02 02:00:00,8898.0 -2017-06-02 03:00:00,8479.0 -2017-06-02 04:00:00,8221.0 -2017-06-02 05:00:00,8103.0 -2017-06-02 06:00:00,8204.0 -2017-06-02 07:00:00,8570.0 -2017-06-02 08:00:00,9452.0 -2017-06-02 09:00:00,10422.0 -2017-06-02 10:00:00,11132.0 -2017-06-02 11:00:00,11726.0 -2017-06-02 12:00:00,12219.0 -2017-06-02 13:00:00,12626.0 -2017-06-02 14:00:00,12898.0 -2017-06-02 15:00:00,13227.0 -2017-06-02 16:00:00,13490.0 -2017-06-02 17:00:00,13690.0 -2017-06-02 18:00:00,13843.0 -2017-06-02 19:00:00,13784.0 -2017-06-02 20:00:00,13433.0 -2017-06-02 21:00:00,12911.0 -2017-06-02 22:00:00,12486.0 -2017-06-02 23:00:00,12177.0 -2017-06-03 00:00:00,11316.0 -2017-06-01 01:00:00,9307.0 -2017-06-01 02:00:00,8736.0 -2017-06-01 03:00:00,8374.0 -2017-06-01 04:00:00,8104.0 -2017-06-01 05:00:00,8013.0 -2017-06-01 06:00:00,8139.0 -2017-06-01 07:00:00,8487.0 -2017-06-01 08:00:00,9369.0 -2017-06-01 09:00:00,10237.0 -2017-06-01 10:00:00,10847.0 -2017-06-01 11:00:00,11269.0 -2017-06-01 12:00:00,11682.0 -2017-06-01 13:00:00,11904.0 -2017-06-01 14:00:00,12033.0 -2017-06-01 15:00:00,12260.0 -2017-06-01 16:00:00,12385.0 -2017-06-01 17:00:00,12474.0 -2017-06-01 18:00:00,12555.0 -2017-06-01 19:00:00,12447.0 -2017-06-01 20:00:00,12076.0 -2017-06-01 21:00:00,11658.0 -2017-06-01 22:00:00,11537.0 -2017-06-01 23:00:00,11247.0 -2017-06-02 00:00:00,10404.0 -2017-05-31 01:00:00,9224.0 -2017-05-31 02:00:00,8676.0 -2017-05-31 03:00:00,8315.0 -2017-05-31 04:00:00,8068.0 -2017-05-31 05:00:00,7979.0 -2017-05-31 06:00:00,8117.0 -2017-05-31 07:00:00,8500.0 -2017-05-31 08:00:00,9342.0 -2017-05-31 09:00:00,10172.0 -2017-05-31 10:00:00,10728.0 -2017-05-31 11:00:00,11065.0 -2017-05-31 12:00:00,11344.0 -2017-05-31 13:00:00,11485.0 -2017-05-31 14:00:00,11587.0 -2017-05-31 15:00:00,11726.0 -2017-05-31 16:00:00,11758.0 -2017-05-31 17:00:00,11790.0 -2017-05-31 18:00:00,11792.0 -2017-05-31 19:00:00,11674.0 -2017-05-31 20:00:00,11396.0 -2017-05-31 21:00:00,11141.0 -2017-05-31 22:00:00,11129.0 -2017-05-31 23:00:00,10916.0 -2017-06-01 00:00:00,10125.0 -2017-05-30 01:00:00,8417.0 -2017-05-30 02:00:00,7973.0 -2017-05-30 03:00:00,7679.0 -2017-05-30 04:00:00,7513.0 -2017-05-30 05:00:00,7469.0 -2017-05-30 06:00:00,7646.0 -2017-05-30 07:00:00,8141.0 -2017-05-30 08:00:00,9095.0 -2017-05-30 09:00:00,10050.0 -2017-05-30 10:00:00,10691.0 -2017-05-30 11:00:00,11116.0 -2017-05-30 12:00:00,11450.0 -2017-05-30 13:00:00,11642.0 -2017-05-30 14:00:00,11718.0 -2017-05-30 15:00:00,11678.0 -2017-05-30 16:00:00,11642.0 -2017-05-30 17:00:00,11630.0 -2017-05-30 18:00:00,11541.0 -2017-05-30 19:00:00,11389.0 -2017-05-30 20:00:00,11038.0 -2017-05-30 21:00:00,10826.0 -2017-05-30 22:00:00,10932.0 -2017-05-30 23:00:00,10718.0 -2017-05-31 00:00:00,9991.0 -2017-05-29 01:00:00,8575.0 -2017-05-29 02:00:00,8094.0 -2017-05-29 03:00:00,7776.0 -2017-05-29 04:00:00,7587.0 -2017-05-29 05:00:00,7480.0 -2017-05-29 06:00:00,7463.0 -2017-05-29 07:00:00,7442.0 -2017-05-29 08:00:00,7543.0 -2017-05-29 09:00:00,7950.0 -2017-05-29 10:00:00,8448.0 -2017-05-29 11:00:00,8954.0 -2017-05-29 12:00:00,9319.0 -2017-05-29 13:00:00,9593.0 -2017-05-29 14:00:00,9732.0 -2017-05-29 15:00:00,9804.0 -2017-05-29 16:00:00,9793.0 -2017-05-29 17:00:00,9856.0 -2017-05-29 18:00:00,9913.0 -2017-05-29 19:00:00,9905.0 -2017-05-29 20:00:00,9761.0 -2017-05-29 21:00:00,9571.0 -2017-05-29 22:00:00,9711.0 -2017-05-29 23:00:00,9657.0 -2017-05-30 00:00:00,9026.0 -2017-05-28 01:00:00,8696.0 -2017-05-28 02:00:00,8220.0 -2017-05-28 03:00:00,7886.0 -2017-05-28 04:00:00,7650.0 -2017-05-28 05:00:00,7515.0 -2017-05-28 06:00:00,7425.0 -2017-05-28 07:00:00,7318.0 -2017-05-28 08:00:00,7331.0 -2017-05-28 09:00:00,7714.0 -2017-05-28 10:00:00,8243.0 -2017-05-28 11:00:00,8697.0 -2017-05-28 12:00:00,9081.0 -2017-05-28 13:00:00,9320.0 -2017-05-28 14:00:00,9474.0 -2017-05-28 15:00:00,9540.0 -2017-05-28 16:00:00,9644.0 -2017-05-28 17:00:00,9719.0 -2017-05-28 18:00:00,9886.0 -2017-05-28 19:00:00,9937.0 -2017-05-28 20:00:00,9748.0 -2017-05-28 21:00:00,9601.0 -2017-05-28 22:00:00,9663.0 -2017-05-28 23:00:00,9632.0 -2017-05-29 00:00:00,9127.0 -2017-05-27 01:00:00,8789.0 -2017-05-27 02:00:00,8289.0 -2017-05-27 03:00:00,7986.0 -2017-05-27 04:00:00,7730.0 -2017-05-27 05:00:00,7637.0 -2017-05-27 06:00:00,7653.0 -2017-05-27 07:00:00,7669.0 -2017-05-27 08:00:00,7789.0 -2017-05-27 09:00:00,8208.0 -2017-05-27 10:00:00,8740.0 -2017-05-27 11:00:00,9156.0 -2017-05-27 12:00:00,9466.0 -2017-05-27 13:00:00,9611.0 -2017-05-27 14:00:00,9726.0 -2017-05-27 15:00:00,9825.0 -2017-05-27 16:00:00,9907.0 -2017-05-27 17:00:00,10024.0 -2017-05-27 18:00:00,10124.0 -2017-05-27 19:00:00,10153.0 -2017-05-27 20:00:00,10025.0 -2017-05-27 21:00:00,9786.0 -2017-05-27 22:00:00,9905.0 -2017-05-27 23:00:00,9692.0 -2017-05-28 00:00:00,9161.0 -2017-05-26 01:00:00,9012.0 -2017-05-26 02:00:00,8533.0 -2017-05-26 03:00:00,8192.0 -2017-05-26 04:00:00,7987.0 -2017-05-26 05:00:00,7888.0 -2017-05-26 06:00:00,8048.0 -2017-05-26 07:00:00,8462.0 -2017-05-26 08:00:00,9247.0 -2017-05-26 09:00:00,10071.0 -2017-05-26 10:00:00,10589.0 -2017-05-26 11:00:00,10904.0 -2017-05-26 12:00:00,11107.0 -2017-05-26 13:00:00,11151.0 -2017-05-26 14:00:00,11125.0 -2017-05-26 15:00:00,11118.0 -2017-05-26 16:00:00,11089.0 -2017-05-26 17:00:00,10912.0 -2017-05-26 18:00:00,10762.0 -2017-05-26 19:00:00,10542.0 -2017-05-26 20:00:00,10354.0 -2017-05-26 21:00:00,10257.0 -2017-05-26 22:00:00,10259.0 -2017-05-26 23:00:00,10026.0 -2017-05-27 00:00:00,9362.0 -2017-05-25 01:00:00,8998.0 -2017-05-25 02:00:00,8553.0 -2017-05-25 03:00:00,8243.0 -2017-05-25 04:00:00,8062.0 -2017-05-25 05:00:00,7984.0 -2017-05-25 06:00:00,8188.0 -2017-05-25 07:00:00,8695.0 -2017-05-25 08:00:00,9535.0 -2017-05-25 09:00:00,10317.0 -2017-05-25 10:00:00,10744.0 -2017-05-25 11:00:00,10909.0 -2017-05-25 12:00:00,11014.0 -2017-05-25 13:00:00,11096.0 -2017-05-25 14:00:00,11139.0 -2017-05-25 15:00:00,11189.0 -2017-05-25 16:00:00,11143.0 -2017-05-25 17:00:00,11061.0 -2017-05-25 18:00:00,10916.0 -2017-05-25 19:00:00,10745.0 -2017-05-25 20:00:00,10461.0 -2017-05-25 21:00:00,10333.0 -2017-05-25 22:00:00,10583.0 -2017-05-25 23:00:00,10414.0 -2017-05-26 00:00:00,9751.0 -2017-05-24 01:00:00,9013.0 -2017-05-24 02:00:00,8583.0 -2017-05-24 03:00:00,8276.0 -2017-05-24 04:00:00,8109.0 -2017-05-24 05:00:00,8050.0 -2017-05-24 06:00:00,8244.0 -2017-05-24 07:00:00,8785.0 -2017-05-24 08:00:00,9608.0 -2017-05-24 09:00:00,10313.0 -2017-05-24 10:00:00,10675.0 -2017-05-24 11:00:00,10863.0 -2017-05-24 12:00:00,10987.0 -2017-05-24 13:00:00,10994.0 -2017-05-24 14:00:00,10952.0 -2017-05-24 15:00:00,11002.0 -2017-05-24 16:00:00,10874.0 -2017-05-24 17:00:00,10766.0 -2017-05-24 18:00:00,10667.0 -2017-05-24 19:00:00,10542.0 -2017-05-24 20:00:00,10389.0 -2017-05-24 21:00:00,10405.0 -2017-05-24 22:00:00,10632.0 -2017-05-24 23:00:00,10363.0 -2017-05-25 00:00:00,9660.0 -2017-05-23 01:00:00,8997.0 -2017-05-23 02:00:00,8517.0 -2017-05-23 03:00:00,8193.0 -2017-05-23 04:00:00,8020.0 -2017-05-23 05:00:00,7949.0 -2017-05-23 06:00:00,8149.0 -2017-05-23 07:00:00,8651.0 -2017-05-23 08:00:00,9526.0 -2017-05-23 09:00:00,10325.0 -2017-05-23 10:00:00,10775.0 -2017-05-23 11:00:00,10976.0 -2017-05-23 12:00:00,11126.0 -2017-05-23 13:00:00,11182.0 -2017-05-23 14:00:00,11157.0 -2017-05-23 15:00:00,11171.0 -2017-05-23 16:00:00,11095.0 -2017-05-23 17:00:00,10983.0 -2017-05-23 18:00:00,10943.0 -2017-05-23 19:00:00,10833.0 -2017-05-23 20:00:00,10690.0 -2017-05-23 21:00:00,10655.0 -2017-05-23 22:00:00,10730.0 -2017-05-23 23:00:00,10371.0 -2017-05-24 00:00:00,9712.0 -2017-05-22 01:00:00,8291.0 -2017-05-22 02:00:00,7924.0 -2017-05-22 03:00:00,7761.0 -2017-05-22 04:00:00,7680.0 -2017-05-22 05:00:00,7712.0 -2017-05-22 06:00:00,7947.0 -2017-05-22 07:00:00,8471.0 -2017-05-22 08:00:00,9377.0 -2017-05-22 09:00:00,10123.0 -2017-05-22 10:00:00,10443.0 -2017-05-22 11:00:00,10679.0 -2017-05-22 12:00:00,10882.0 -2017-05-22 13:00:00,11024.0 -2017-05-22 14:00:00,11099.0 -2017-05-22 15:00:00,11256.0 -2017-05-22 16:00:00,11244.0 -2017-05-22 17:00:00,11208.0 -2017-05-22 18:00:00,11090.0 -2017-05-22 19:00:00,10926.0 -2017-05-22 20:00:00,10670.0 -2017-05-22 21:00:00,10623.0 -2017-05-22 22:00:00,10820.0 -2017-05-22 23:00:00,10449.0 -2017-05-23 00:00:00,9707.0 -2017-05-21 01:00:00,8566.0 -2017-05-21 02:00:00,8180.0 -2017-05-21 03:00:00,7869.0 -2017-05-21 04:00:00,7736.0 -2017-05-21 05:00:00,7654.0 -2017-05-21 06:00:00,7641.0 -2017-05-21 07:00:00,7623.0 -2017-05-21 08:00:00,7548.0 -2017-05-21 09:00:00,7805.0 -2017-05-21 10:00:00,8214.0 -2017-05-21 11:00:00,8543.0 -2017-05-21 12:00:00,8748.0 -2017-05-21 13:00:00,8801.0 -2017-05-21 14:00:00,8890.0 -2017-05-21 15:00:00,8854.0 -2017-05-21 16:00:00,8808.0 -2017-05-21 17:00:00,8781.0 -2017-05-21 18:00:00,8830.0 -2017-05-21 19:00:00,8930.0 -2017-05-21 20:00:00,9006.0 -2017-05-21 21:00:00,9042.0 -2017-05-21 22:00:00,9346.0 -2017-05-21 23:00:00,9179.0 -2017-05-22 00:00:00,8754.0 -2017-05-20 01:00:00,9000.0 -2017-05-20 02:00:00,8505.0 -2017-05-20 03:00:00,8204.0 -2017-05-20 04:00:00,8010.0 -2017-05-20 05:00:00,7942.0 -2017-05-20 06:00:00,7970.0 -2017-05-20 07:00:00,8143.0 -2017-05-20 08:00:00,8331.0 -2017-05-20 09:00:00,8745.0 -2017-05-20 10:00:00,9254.0 -2017-05-20 11:00:00,9614.0 -2017-05-20 12:00:00,9993.0 -2017-05-20 13:00:00,9917.0 -2017-05-20 14:00:00,9765.0 -2017-05-20 15:00:00,9544.0 -2017-05-20 16:00:00,9434.0 -2017-05-20 17:00:00,9342.0 -2017-05-20 18:00:00,9284.0 -2017-05-20 19:00:00,9260.0 -2017-05-20 20:00:00,9203.0 -2017-05-20 21:00:00,9242.0 -2017-05-20 22:00:00,9565.0 -2017-05-20 23:00:00,9517.0 -2017-05-21 00:00:00,9092.0 -2017-05-19 01:00:00,9238.0 -2017-05-19 02:00:00,8659.0 -2017-05-19 03:00:00,8293.0 -2017-05-19 04:00:00,8048.0 -2017-05-19 05:00:00,7961.0 -2017-05-19 06:00:00,8133.0 -2017-05-19 07:00:00,8629.0 -2017-05-19 08:00:00,9433.0 -2017-05-19 09:00:00,10225.0 -2017-05-19 10:00:00,10646.0 -2017-05-19 11:00:00,10917.0 -2017-05-19 12:00:00,10963.0 -2017-05-19 13:00:00,10923.0 -2017-05-19 14:00:00,10869.0 -2017-05-19 15:00:00,10909.0 -2017-05-19 16:00:00,10807.0 -2017-05-19 17:00:00,10685.0 -2017-05-19 18:00:00,10536.0 -2017-05-19 19:00:00,10465.0 -2017-05-19 20:00:00,10284.0 -2017-05-19 21:00:00,10237.0 -2017-05-19 22:00:00,10482.0 -2017-05-19 23:00:00,10279.0 -2017-05-20 00:00:00,9642.0 -2017-05-18 01:00:00,11320.0 -2017-05-18 02:00:00,10274.0 -2017-05-18 03:00:00,9664.0 -2017-05-18 04:00:00,9347.0 -2017-05-18 05:00:00,9269.0 -2017-05-18 06:00:00,9432.0 -2017-05-18 07:00:00,9921.0 -2017-05-18 08:00:00,10968.0 -2017-05-18 09:00:00,12122.0 -2017-05-18 10:00:00,12718.0 -2017-05-18 11:00:00,13016.0 -2017-05-18 12:00:00,13311.0 -2017-05-18 13:00:00,13457.0 -2017-05-18 14:00:00,13608.0 -2017-05-18 15:00:00,13857.0 -2017-05-18 16:00:00,13993.0 -2017-05-18 17:00:00,14162.0 -2017-05-18 18:00:00,14260.0 -2017-05-18 19:00:00,14082.0 -2017-05-18 20:00:00,13423.0 -2017-05-18 21:00:00,12564.0 -2017-05-18 22:00:00,11963.0 -2017-05-18 23:00:00,11189.0 -2017-05-19 00:00:00,10157.0 -2017-05-17 01:00:00,11516.0 -2017-05-17 02:00:00,10721.0 -2017-05-17 03:00:00,10190.0 -2017-05-17 04:00:00,9781.0 -2017-05-17 05:00:00,9580.0 -2017-05-17 06:00:00,9646.0 -2017-05-17 07:00:00,10104.0 -2017-05-17 08:00:00,11056.0 -2017-05-17 09:00:00,12147.0 -2017-05-17 10:00:00,13045.0 -2017-05-17 11:00:00,13647.0 -2017-05-17 12:00:00,14193.0 -2017-05-17 13:00:00,14535.0 -2017-05-17 14:00:00,14623.0 -2017-05-17 15:00:00,14886.0 -2017-05-17 16:00:00,14981.0 -2017-05-17 17:00:00,14983.0 -2017-05-17 18:00:00,15057.0 -2017-05-17 19:00:00,14722.0 -2017-05-17 20:00:00,14196.0 -2017-05-17 21:00:00,13788.0 -2017-05-17 22:00:00,13812.0 -2017-05-17 23:00:00,13386.0 -2017-05-18 00:00:00,12381.0 -2017-05-16 01:00:00,9889.0 -2017-05-16 02:00:00,9308.0 -2017-05-16 03:00:00,8854.0 -2017-05-16 04:00:00,8624.0 -2017-05-16 05:00:00,8485.0 -2017-05-16 06:00:00,8663.0 -2017-05-16 07:00:00,9141.0 -2017-05-16 08:00:00,10104.0 -2017-05-16 09:00:00,11120.0 -2017-05-16 10:00:00,11827.0 -2017-05-16 11:00:00,12407.0 -2017-05-16 12:00:00,12997.0 -2017-05-16 13:00:00,13492.0 -2017-05-16 14:00:00,13984.0 -2017-05-16 15:00:00,14422.0 -2017-05-16 16:00:00,14650.0 -2017-05-16 17:00:00,14750.0 -2017-05-16 18:00:00,14862.0 -2017-05-16 19:00:00,14753.0 -2017-05-16 20:00:00,14332.0 -2017-05-16 21:00:00,13941.0 -2017-05-16 22:00:00,13970.0 -2017-05-16 23:00:00,13564.0 -2017-05-17 00:00:00,12568.0 -2017-05-15 01:00:00,8120.0 -2017-05-15 02:00:00,7760.0 -2017-05-15 03:00:00,7567.0 -2017-05-15 04:00:00,7468.0 -2017-05-15 05:00:00,7468.0 -2017-05-15 06:00:00,7712.0 -2017-05-15 07:00:00,8254.0 -2017-05-15 08:00:00,9074.0 -2017-05-15 09:00:00,9967.0 -2017-05-15 10:00:00,10441.0 -2017-05-15 11:00:00,10836.0 -2017-05-15 12:00:00,11201.0 -2017-05-15 13:00:00,11446.0 -2017-05-15 14:00:00,11684.0 -2017-05-15 15:00:00,11926.0 -2017-05-15 16:00:00,12059.0 -2017-05-15 17:00:00,12091.0 -2017-05-15 18:00:00,12089.0 -2017-05-15 19:00:00,12030.0 -2017-05-15 20:00:00,11801.0 -2017-05-15 21:00:00,11761.0 -2017-05-15 22:00:00,12054.0 -2017-05-15 23:00:00,11605.0 -2017-05-16 00:00:00,10726.0 -2017-05-14 01:00:00,8674.0 -2017-05-14 02:00:00,8135.0 -2017-05-14 03:00:00,7797.0 -2017-05-14 04:00:00,7509.0 -2017-05-14 05:00:00,7422.0 -2017-05-14 06:00:00,7313.0 -2017-05-14 07:00:00,7308.0 -2017-05-14 08:00:00,7289.0 -2017-05-14 09:00:00,7640.0 -2017-05-14 10:00:00,8129.0 -2017-05-14 11:00:00,8509.0 -2017-05-14 12:00:00,8829.0 -2017-05-14 13:00:00,9053.0 -2017-05-14 14:00:00,9173.0 -2017-05-14 15:00:00,9166.0 -2017-05-14 16:00:00,9137.0 -2017-05-14 17:00:00,9049.0 -2017-05-14 18:00:00,8983.0 -2017-05-14 19:00:00,8893.0 -2017-05-14 20:00:00,8827.0 -2017-05-14 21:00:00,8742.0 -2017-05-14 22:00:00,9179.0 -2017-05-14 23:00:00,9069.0 -2017-05-15 00:00:00,8614.0 -2017-05-13 01:00:00,8851.0 -2017-05-13 02:00:00,8303.0 -2017-05-13 03:00:00,8033.0 -2017-05-13 04:00:00,7802.0 -2017-05-13 05:00:00,7691.0 -2017-05-13 06:00:00,7719.0 -2017-05-13 07:00:00,7834.0 -2017-05-13 08:00:00,8018.0 -2017-05-13 09:00:00,8481.0 -2017-05-13 10:00:00,8941.0 -2017-05-13 11:00:00,9277.0 -2017-05-13 12:00:00,9511.0 -2017-05-13 13:00:00,9602.0 -2017-05-13 14:00:00,9613.0 -2017-05-13 15:00:00,9558.0 -2017-05-13 16:00:00,9580.0 -2017-05-13 17:00:00,9627.0 -2017-05-13 18:00:00,9764.0 -2017-05-13 19:00:00,9827.0 -2017-05-13 20:00:00,9795.0 -2017-05-13 21:00:00,9740.0 -2017-05-13 22:00:00,9963.0 -2017-05-13 23:00:00,9813.0 -2017-05-14 00:00:00,9265.0 -2017-05-12 01:00:00,8861.0 -2017-05-12 02:00:00,8399.0 -2017-05-12 03:00:00,8103.0 -2017-05-12 04:00:00,7945.0 -2017-05-12 05:00:00,7864.0 -2017-05-12 06:00:00,8092.0 -2017-05-12 07:00:00,8603.0 -2017-05-12 08:00:00,9379.0 -2017-05-12 09:00:00,10056.0 -2017-05-12 10:00:00,10425.0 -2017-05-12 11:00:00,10593.0 -2017-05-12 12:00:00,10801.0 -2017-05-12 13:00:00,10938.0 -2017-05-12 14:00:00,10967.0 -2017-05-12 15:00:00,11039.0 -2017-05-12 16:00:00,10889.0 -2017-05-12 17:00:00,10762.0 -2017-05-12 18:00:00,10560.0 -2017-05-12 19:00:00,10354.0 -2017-05-12 20:00:00,10108.0 -2017-05-12 21:00:00,9984.0 -2017-05-12 22:00:00,10243.0 -2017-05-12 23:00:00,10038.0 -2017-05-13 00:00:00,9459.0 -2017-05-11 01:00:00,8898.0 -2017-05-11 02:00:00,8501.0 -2017-05-11 03:00:00,8240.0 -2017-05-11 04:00:00,8086.0 -2017-05-11 05:00:00,8070.0 -2017-05-11 06:00:00,8273.0 -2017-05-11 07:00:00,8880.0 -2017-05-11 08:00:00,9670.0 -2017-05-11 09:00:00,10352.0 -2017-05-11 10:00:00,10630.0 -2017-05-11 11:00:00,10751.0 -2017-05-11 12:00:00,10848.0 -2017-05-11 13:00:00,10870.0 -2017-05-11 14:00:00,10854.0 -2017-05-11 15:00:00,10878.0 -2017-05-11 16:00:00,10797.0 -2017-05-11 17:00:00,10729.0 -2017-05-11 18:00:00,10679.0 -2017-05-11 19:00:00,10557.0 -2017-05-11 20:00:00,10304.0 -2017-05-11 21:00:00,10214.0 -2017-05-11 22:00:00,10562.0 -2017-05-11 23:00:00,10280.0 -2017-05-12 00:00:00,9545.0 -2017-05-10 01:00:00,8935.0 -2017-05-10 02:00:00,8459.0 -2017-05-10 03:00:00,8193.0 -2017-05-10 04:00:00,8060.0 -2017-05-10 05:00:00,8029.0 -2017-05-10 06:00:00,8258.0 -2017-05-10 07:00:00,8836.0 -2017-05-10 08:00:00,9617.0 -2017-05-10 09:00:00,10289.0 -2017-05-10 10:00:00,10488.0 -2017-05-10 11:00:00,10653.0 -2017-05-10 12:00:00,10856.0 -2017-05-10 13:00:00,10972.0 -2017-05-10 14:00:00,10980.0 -2017-05-10 15:00:00,11025.0 -2017-05-10 16:00:00,10832.0 -2017-05-10 17:00:00,10711.0 -2017-05-10 18:00:00,10644.0 -2017-05-10 19:00:00,10648.0 -2017-05-10 20:00:00,10614.0 -2017-05-10 21:00:00,10817.0 -2017-05-10 22:00:00,10863.0 -2017-05-10 23:00:00,10346.0 -2017-05-11 00:00:00,9587.0 -2017-05-09 01:00:00,9024.0 -2017-05-09 02:00:00,8620.0 -2017-05-09 03:00:00,8414.0 -2017-05-09 04:00:00,8245.0 -2017-05-09 05:00:00,8285.0 -2017-05-09 06:00:00,8515.0 -2017-05-09 07:00:00,9198.0 -2017-05-09 08:00:00,10110.0 -2017-05-09 09:00:00,10725.0 -2017-05-09 10:00:00,11019.0 -2017-05-09 11:00:00,11132.0 -2017-05-09 12:00:00,11180.0 -2017-05-09 13:00:00,11113.0 -2017-05-09 14:00:00,10928.0 -2017-05-09 15:00:00,10874.0 -2017-05-09 16:00:00,10718.0 -2017-05-09 17:00:00,10525.0 -2017-05-09 18:00:00,10457.0 -2017-05-09 19:00:00,10358.0 -2017-05-09 20:00:00,10184.0 -2017-05-09 21:00:00,10320.0 -2017-05-09 22:00:00,10648.0 -2017-05-09 23:00:00,10261.0 -2017-05-10 00:00:00,9577.0 -2017-05-08 01:00:00,8353.0 -2017-05-08 02:00:00,8094.0 -2017-05-08 03:00:00,7944.0 -2017-05-08 04:00:00,7888.0 -2017-05-08 05:00:00,7954.0 -2017-05-08 06:00:00,8272.0 -2017-05-08 07:00:00,8947.0 -2017-05-08 08:00:00,9777.0 -2017-05-08 09:00:00,10396.0 -2017-05-08 10:00:00,10610.0 -2017-05-08 11:00:00,10735.0 -2017-05-08 12:00:00,10806.0 -2017-05-08 13:00:00,10791.0 -2017-05-08 14:00:00,10719.0 -2017-05-08 15:00:00,10743.0 -2017-05-08 16:00:00,10625.0 -2017-05-08 17:00:00,10492.0 -2017-05-08 18:00:00,10346.0 -2017-05-08 19:00:00,10284.0 -2017-05-08 20:00:00,10123.0 -2017-05-08 21:00:00,10307.0 -2017-05-08 22:00:00,10693.0 -2017-05-08 23:00:00,10308.0 -2017-05-09 00:00:00,9655.0 -2017-05-07 01:00:00,8314.0 -2017-05-07 02:00:00,7998.0 -2017-05-07 03:00:00,7793.0 -2017-05-07 04:00:00,7663.0 -2017-05-07 05:00:00,7606.0 -2017-05-07 06:00:00,7664.0 -2017-05-07 07:00:00,7730.0 -2017-05-07 08:00:00,7745.0 -2017-05-07 09:00:00,7914.0 -2017-05-07 10:00:00,8150.0 -2017-05-07 11:00:00,8331.0 -2017-05-07 12:00:00,8442.0 -2017-05-07 13:00:00,8459.0 -2017-05-07 14:00:00,8472.0 -2017-05-07 15:00:00,8436.0 -2017-05-07 16:00:00,8381.0 -2017-05-07 17:00:00,8349.0 -2017-05-07 18:00:00,8396.0 -2017-05-07 19:00:00,8495.0 -2017-05-07 20:00:00,8556.0 -2017-05-07 21:00:00,8744.0 -2017-05-07 22:00:00,9311.0 -2017-05-07 23:00:00,9228.0 -2017-05-08 00:00:00,8817.0 -2017-05-06 01:00:00,8954.0 -2017-05-06 02:00:00,8416.0 -2017-05-06 03:00:00,8222.0 -2017-05-06 04:00:00,8077.0 -2017-05-06 05:00:00,7997.0 -2017-05-06 06:00:00,8061.0 -2017-05-06 07:00:00,8196.0 -2017-05-06 08:00:00,8399.0 -2017-05-06 09:00:00,8752.0 -2017-05-06 10:00:00,9047.0 -2017-05-06 11:00:00,9202.0 -2017-05-06 12:00:00,9289.0 -2017-05-06 13:00:00,9308.0 -2017-05-06 14:00:00,9195.0 -2017-05-06 15:00:00,9048.0 -2017-05-06 16:00:00,8895.0 -2017-05-06 17:00:00,8805.0 -2017-05-06 18:00:00,8753.0 -2017-05-06 19:00:00,8709.0 -2017-05-06 20:00:00,8715.0 -2017-05-06 21:00:00,8762.0 -2017-05-06 22:00:00,9193.0 -2017-05-06 23:00:00,9099.0 -2017-05-07 00:00:00,8739.0 -2017-05-05 01:00:00,9067.0 -2017-05-05 02:00:00,8680.0 -2017-05-05 03:00:00,8434.0 -2017-05-05 04:00:00,8318.0 -2017-05-05 05:00:00,8305.0 -2017-05-05 06:00:00,8540.0 -2017-05-05 07:00:00,9162.0 -2017-05-05 08:00:00,9942.0 -2017-05-05 09:00:00,10593.0 -2017-05-05 10:00:00,10807.0 -2017-05-05 11:00:00,10854.0 -2017-05-05 12:00:00,10904.0 -2017-05-05 13:00:00,10881.0 -2017-05-05 14:00:00,10799.0 -2017-05-05 15:00:00,10776.0 -2017-05-05 16:00:00,10609.0 -2017-05-05 17:00:00,10417.0 -2017-05-05 18:00:00,10246.0 -2017-05-05 19:00:00,10058.0 -2017-05-05 20:00:00,9887.0 -2017-05-05 21:00:00,9906.0 -2017-05-05 22:00:00,10271.0 -2017-05-05 23:00:00,10067.0 -2017-05-06 00:00:00,9565.0 -2017-05-04 01:00:00,9020.0 -2017-05-04 02:00:00,8613.0 -2017-05-04 03:00:00,8344.0 -2017-05-04 04:00:00,8219.0 -2017-05-04 05:00:00,8183.0 -2017-05-04 06:00:00,8401.0 -2017-05-04 07:00:00,9053.0 -2017-05-04 08:00:00,9960.0 -2017-05-04 09:00:00,10624.0 -2017-05-04 10:00:00,10873.0 -2017-05-04 11:00:00,11007.0 -2017-05-04 12:00:00,11098.0 -2017-05-04 13:00:00,11033.0 -2017-05-04 14:00:00,10992.0 -2017-05-04 15:00:00,10959.0 -2017-05-04 16:00:00,10796.0 -2017-05-04 17:00:00,10648.0 -2017-05-04 18:00:00,10555.0 -2017-05-04 19:00:00,10475.0 -2017-05-04 20:00:00,10272.0 -2017-05-04 21:00:00,10238.0 -2017-05-04 22:00:00,10663.0 -2017-05-04 23:00:00,10392.0 -2017-05-05 00:00:00,9703.0 -2017-05-03 01:00:00,9281.0 -2017-05-03 02:00:00,8843.0 -2017-05-03 03:00:00,8632.0 -2017-05-03 04:00:00,8497.0 -2017-05-03 05:00:00,8488.0 -2017-05-03 06:00:00,8761.0 -2017-05-03 07:00:00,9456.0 -2017-05-03 08:00:00,10241.0 -2017-05-03 09:00:00,10758.0 -2017-05-03 10:00:00,10902.0 -2017-05-03 11:00:00,10903.0 -2017-05-03 12:00:00,10900.0 -2017-05-03 13:00:00,10886.0 -2017-05-03 14:00:00,10795.0 -2017-05-03 15:00:00,10777.0 -2017-05-03 16:00:00,10593.0 -2017-05-03 17:00:00,10428.0 -2017-05-03 18:00:00,10360.0 -2017-05-03 19:00:00,10265.0 -2017-05-03 20:00:00,10178.0 -2017-05-03 21:00:00,10388.0 -2017-05-03 22:00:00,10693.0 -2017-05-03 23:00:00,10351.0 -2017-05-04 00:00:00,9673.0 -2017-05-02 01:00:00,9306.0 -2017-05-02 02:00:00,8883.0 -2017-05-02 03:00:00,8618.0 -2017-05-02 04:00:00,8494.0 -2017-05-02 05:00:00,8504.0 -2017-05-02 06:00:00,8746.0 -2017-05-02 07:00:00,9426.0 -2017-05-02 08:00:00,10317.0 -2017-05-02 09:00:00,10979.0 -2017-05-02 10:00:00,11208.0 -2017-05-02 11:00:00,11271.0 -2017-05-02 12:00:00,11340.0 -2017-05-02 13:00:00,11309.0 -2017-05-02 14:00:00,11231.0 -2017-05-02 15:00:00,11161.0 -2017-05-02 16:00:00,10998.0 -2017-05-02 17:00:00,10895.0 -2017-05-02 18:00:00,10828.0 -2017-05-02 19:00:00,10828.0 -2017-05-02 20:00:00,10651.0 -2017-05-02 21:00:00,10750.0 -2017-05-02 22:00:00,10971.0 -2017-05-02 23:00:00,10578.0 -2017-05-03 00:00:00,9908.0 -2017-05-01 01:00:00,8858.0 -2017-05-01 02:00:00,8483.0 -2017-05-01 03:00:00,8275.0 -2017-05-01 04:00:00,8141.0 -2017-05-01 05:00:00,8071.0 -2017-05-01 06:00:00,8291.0 -2017-05-01 07:00:00,8940.0 -2017-05-01 08:00:00,9752.0 -2017-05-01 09:00:00,10441.0 -2017-05-01 10:00:00,10620.0 -2017-05-01 11:00:00,10759.0 -2017-05-01 12:00:00,10885.0 -2017-05-01 13:00:00,10915.0 -2017-05-01 14:00:00,10906.0 -2017-05-01 15:00:00,10895.0 -2017-05-01 16:00:00,10777.0 -2017-05-01 17:00:00,10646.0 -2017-05-01 18:00:00,10578.0 -2017-05-01 19:00:00,10563.0 -2017-05-01 20:00:00,10628.0 -2017-05-01 21:00:00,10814.0 -2017-05-01 22:00:00,10982.0 -2017-05-01 23:00:00,10564.0 -2017-05-02 00:00:00,9916.0 -2017-04-30 01:00:00,8988.0 -2017-04-30 02:00:00,8598.0 -2017-04-30 03:00:00,8260.0 -2017-04-30 04:00:00,8119.0 -2017-04-30 05:00:00,8012.0 -2017-04-30 06:00:00,8051.0 -2017-04-30 07:00:00,8185.0 -2017-04-30 08:00:00,8285.0 -2017-04-30 09:00:00,8517.0 -2017-04-30 10:00:00,8886.0 -2017-04-30 11:00:00,9105.0 -2017-04-30 12:00:00,9276.0 -2017-04-30 13:00:00,9392.0 -2017-04-30 14:00:00,9316.0 -2017-04-30 15:00:00,9277.0 -2017-04-30 16:00:00,9233.0 -2017-04-30 17:00:00,9259.0 -2017-04-30 18:00:00,9330.0 -2017-04-30 19:00:00,9540.0 -2017-04-30 20:00:00,9722.0 -2017-04-30 21:00:00,10029.0 -2017-04-30 22:00:00,9955.0 -2017-04-30 23:00:00,9665.0 -2017-05-01 00:00:00,9192.0 -2017-04-29 01:00:00,8802.0 -2017-04-29 02:00:00,8387.0 -2017-04-29 03:00:00,8035.0 -2017-04-29 04:00:00,7941.0 -2017-04-29 05:00:00,7908.0 -2017-04-29 06:00:00,7977.0 -2017-04-29 07:00:00,8259.0 -2017-04-29 08:00:00,8503.0 -2017-04-29 09:00:00,8889.0 -2017-04-29 10:00:00,9288.0 -2017-04-29 11:00:00,9527.0 -2017-04-29 12:00:00,9638.0 -2017-04-29 13:00:00,9783.0 -2017-04-29 14:00:00,9804.0 -2017-04-29 15:00:00,9769.0 -2017-04-29 16:00:00,9769.0 -2017-04-29 17:00:00,9792.0 -2017-04-29 18:00:00,9924.0 -2017-04-29 19:00:00,10040.0 -2017-04-29 20:00:00,10186.0 -2017-04-29 21:00:00,10300.0 -2017-04-29 22:00:00,10387.0 -2017-04-29 23:00:00,10096.0 -2017-04-30 00:00:00,9549.0 -2017-04-28 01:00:00,8957.0 -2017-04-28 02:00:00,8509.0 -2017-04-28 03:00:00,8263.0 -2017-04-28 04:00:00,8114.0 -2017-04-28 05:00:00,8061.0 -2017-04-28 06:00:00,8283.0 -2017-04-28 07:00:00,8948.0 -2017-04-28 08:00:00,9689.0 -2017-04-28 09:00:00,10273.0 -2017-04-28 10:00:00,10473.0 -2017-04-28 11:00:00,10575.0 -2017-04-28 12:00:00,10640.0 -2017-04-28 13:00:00,10662.0 -2017-04-28 14:00:00,10608.0 -2017-04-28 15:00:00,10621.0 -2017-04-28 16:00:00,10467.0 -2017-04-28 17:00:00,10282.0 -2017-04-28 18:00:00,10143.0 -2017-04-28 19:00:00,10061.0 -2017-04-28 20:00:00,9971.0 -2017-04-28 21:00:00,10138.0 -2017-04-28 22:00:00,10272.0 -2017-04-28 23:00:00,9996.0 -2017-04-29 00:00:00,9411.0 -2017-04-27 01:00:00,9248.0 -2017-04-27 02:00:00,8705.0 -2017-04-27 03:00:00,8386.0 -2017-04-27 04:00:00,8178.0 -2017-04-27 05:00:00,8135.0 -2017-04-27 06:00:00,8323.0 -2017-04-27 07:00:00,8883.0 -2017-04-27 08:00:00,9651.0 -2017-04-27 09:00:00,10223.0 -2017-04-27 10:00:00,10511.0 -2017-04-27 11:00:00,10629.0 -2017-04-27 12:00:00,10710.0 -2017-04-27 13:00:00,10766.0 -2017-04-27 14:00:00,10784.0 -2017-04-27 15:00:00,10792.0 -2017-04-27 16:00:00,10735.0 -2017-04-27 17:00:00,10642.0 -2017-04-27 18:00:00,10538.0 -2017-04-27 19:00:00,10483.0 -2017-04-27 20:00:00,10373.0 -2017-04-27 21:00:00,10556.0 -2017-04-27 22:00:00,10692.0 -2017-04-27 23:00:00,10225.0 -2017-04-28 00:00:00,9592.0 -2017-04-26 01:00:00,9273.0 -2017-04-26 02:00:00,8746.0 -2017-04-26 03:00:00,8405.0 -2017-04-26 04:00:00,8218.0 -2017-04-26 05:00:00,8133.0 -2017-04-26 06:00:00,8306.0 -2017-04-26 07:00:00,8937.0 -2017-04-26 08:00:00,9726.0 -2017-04-26 09:00:00,10519.0 -2017-04-26 10:00:00,10931.0 -2017-04-26 11:00:00,11239.0 -2017-04-26 12:00:00,11515.0 -2017-04-26 13:00:00,11605.0 -2017-04-26 14:00:00,11713.0 -2017-04-26 15:00:00,11813.0 -2017-04-26 16:00:00,11750.0 -2017-04-26 17:00:00,11628.0 -2017-04-26 18:00:00,11500.0 -2017-04-26 19:00:00,11291.0 -2017-04-26 20:00:00,10963.0 -2017-04-26 21:00:00,11031.0 -2017-04-26 22:00:00,11165.0 -2017-04-26 23:00:00,10741.0 -2017-04-27 00:00:00,9964.0 -2017-04-25 01:00:00,8779.0 -2017-04-25 02:00:00,8353.0 -2017-04-25 03:00:00,8055.0 -2017-04-25 04:00:00,7895.0 -2017-04-25 05:00:00,7829.0 -2017-04-25 06:00:00,8030.0 -2017-04-25 07:00:00,8657.0 -2017-04-25 08:00:00,9369.0 -2017-04-25 09:00:00,10129.0 -2017-04-25 10:00:00,10525.0 -2017-04-25 11:00:00,10811.0 -2017-04-25 12:00:00,11049.0 -2017-04-25 13:00:00,11205.0 -2017-04-25 14:00:00,11327.0 -2017-04-25 15:00:00,11445.0 -2017-04-25 16:00:00,11398.0 -2017-04-25 17:00:00,11290.0 -2017-04-25 18:00:00,11280.0 -2017-04-25 19:00:00,11183.0 -2017-04-25 20:00:00,10927.0 -2017-04-25 21:00:00,10990.0 -2017-04-25 22:00:00,11249.0 -2017-04-25 23:00:00,10839.0 -2017-04-26 00:00:00,10043.0 -2017-04-24 01:00:00,8135.0 -2017-04-24 02:00:00,7851.0 -2017-04-24 03:00:00,7685.0 -2017-04-24 04:00:00,7633.0 -2017-04-24 05:00:00,7702.0 -2017-04-24 06:00:00,8006.0 -2017-04-24 07:00:00,8679.0 -2017-04-24 08:00:00,9487.0 -2017-04-24 09:00:00,10128.0 -2017-04-24 10:00:00,10436.0 -2017-04-24 11:00:00,10648.0 -2017-04-24 12:00:00,10872.0 -2017-04-24 13:00:00,10951.0 -2017-04-24 14:00:00,10976.0 -2017-04-24 15:00:00,11060.0 -2017-04-24 16:00:00,11028.0 -2017-04-24 17:00:00,10959.0 -2017-04-24 18:00:00,10879.0 -2017-04-24 19:00:00,10697.0 -2017-04-24 20:00:00,10420.0 -2017-04-24 21:00:00,10445.0 -2017-04-24 22:00:00,10727.0 -2017-04-24 23:00:00,10269.0 -2017-04-25 00:00:00,9555.0 -2017-04-23 01:00:00,8334.0 -2017-04-23 02:00:00,7982.0 -2017-04-23 03:00:00,7785.0 -2017-04-23 04:00:00,7638.0 -2017-04-23 05:00:00,7613.0 -2017-04-23 06:00:00,7633.0 -2017-04-23 07:00:00,7787.0 -2017-04-23 08:00:00,7734.0 -2017-04-23 09:00:00,7946.0 -2017-04-23 10:00:00,8117.0 -2017-04-23 11:00:00,8342.0 -2017-04-23 12:00:00,8498.0 -2017-04-23 13:00:00,8565.0 -2017-04-23 14:00:00,8581.0 -2017-04-23 15:00:00,8654.0 -2017-04-23 16:00:00,8624.0 -2017-04-23 17:00:00,8712.0 -2017-04-23 18:00:00,8767.0 -2017-04-23 19:00:00,8863.0 -2017-04-23 20:00:00,8845.0 -2017-04-23 21:00:00,9038.0 -2017-04-23 22:00:00,9395.0 -2017-04-23 23:00:00,9133.0 -2017-04-24 00:00:00,8640.0 -2017-04-22 01:00:00,8940.0 -2017-04-22 02:00:00,8474.0 -2017-04-22 03:00:00,8157.0 -2017-04-22 04:00:00,7980.0 -2017-04-22 05:00:00,7943.0 -2017-04-22 06:00:00,8004.0 -2017-04-22 07:00:00,8288.0 -2017-04-22 08:00:00,8475.0 -2017-04-22 09:00:00,8816.0 -2017-04-22 10:00:00,9098.0 -2017-04-22 11:00:00,9249.0 -2017-04-22 12:00:00,9296.0 -2017-04-22 13:00:00,9252.0 -2017-04-22 14:00:00,9150.0 -2017-04-22 15:00:00,9019.0 -2017-04-22 16:00:00,8880.0 -2017-04-22 17:00:00,8773.0 -2017-04-22 18:00:00,8758.0 -2017-04-22 19:00:00,8750.0 -2017-04-22 20:00:00,8752.0 -2017-04-22 21:00:00,8924.0 -2017-04-22 22:00:00,9348.0 -2017-04-22 23:00:00,9181.0 -2017-04-23 00:00:00,8761.0 -2017-04-21 01:00:00,8865.0 -2017-04-21 02:00:00,8407.0 -2017-04-21 03:00:00,8139.0 -2017-04-21 04:00:00,7974.0 -2017-04-21 05:00:00,7932.0 -2017-04-21 06:00:00,8138.0 -2017-04-21 07:00:00,8760.0 -2017-04-21 08:00:00,9512.0 -2017-04-21 09:00:00,10135.0 -2017-04-21 10:00:00,10425.0 -2017-04-21 11:00:00,10527.0 -2017-04-21 12:00:00,10613.0 -2017-04-21 13:00:00,10556.0 -2017-04-21 14:00:00,10504.0 -2017-04-21 15:00:00,10465.0 -2017-04-21 16:00:00,10321.0 -2017-04-21 17:00:00,10153.0 -2017-04-21 18:00:00,10064.0 -2017-04-21 19:00:00,9917.0 -2017-04-21 20:00:00,9859.0 -2017-04-21 21:00:00,10126.0 -2017-04-21 22:00:00,10400.0 -2017-04-21 23:00:00,10122.0 -2017-04-22 00:00:00,9543.0 -2017-04-20 01:00:00,8935.0 -2017-04-20 02:00:00,8465.0 -2017-04-20 03:00:00,8214.0 -2017-04-20 04:00:00,8078.0 -2017-04-20 05:00:00,8047.0 -2017-04-20 06:00:00,8232.0 -2017-04-20 07:00:00,8882.0 -2017-04-20 08:00:00,9619.0 -2017-04-20 09:00:00,10307.0 -2017-04-20 10:00:00,10704.0 -2017-04-20 11:00:00,11046.0 -2017-04-20 12:00:00,11373.0 -2017-04-20 13:00:00,11432.0 -2017-04-20 14:00:00,11314.0 -2017-04-20 15:00:00,11274.0 -2017-04-20 16:00:00,11155.0 -2017-04-20 17:00:00,10951.0 -2017-04-20 18:00:00,10783.0 -2017-04-20 19:00:00,10544.0 -2017-04-20 20:00:00,10300.0 -2017-04-20 21:00:00,10431.0 -2017-04-20 22:00:00,10640.0 -2017-04-20 23:00:00,10234.0 -2017-04-21 00:00:00,9511.0 -2017-04-19 01:00:00,9176.0 -2017-04-19 02:00:00,8694.0 -2017-04-19 03:00:00,8371.0 -2017-04-19 04:00:00,8143.0 -2017-04-19 05:00:00,8102.0 -2017-04-19 06:00:00,8301.0 -2017-04-19 07:00:00,8919.0 -2017-04-19 08:00:00,9718.0 -2017-04-19 09:00:00,10443.0 -2017-04-19 10:00:00,10882.0 -2017-04-19 11:00:00,11137.0 -2017-04-19 12:00:00,11314.0 -2017-04-19 13:00:00,11315.0 -2017-04-19 14:00:00,11200.0 -2017-04-19 15:00:00,11082.0 -2017-04-19 16:00:00,10691.0 -2017-04-19 17:00:00,10617.0 -2017-04-19 18:00:00,10449.0 -2017-04-19 19:00:00,10388.0 -2017-04-19 20:00:00,10216.0 -2017-04-19 21:00:00,10429.0 -2017-04-19 22:00:00,10692.0 -2017-04-19 23:00:00,10277.0 -2017-04-20 00:00:00,9588.0 -2017-04-18 01:00:00,8847.0 -2017-04-18 02:00:00,8432.0 -2017-04-18 03:00:00,8165.0 -2017-04-18 04:00:00,7994.0 -2017-04-18 05:00:00,7959.0 -2017-04-18 06:00:00,8225.0 -2017-04-18 07:00:00,8851.0 -2017-04-18 08:00:00,9672.0 -2017-04-18 09:00:00,10232.0 -2017-04-18 10:00:00,10508.0 -2017-04-18 11:00:00,10694.0 -2017-04-18 12:00:00,10923.0 -2017-04-18 13:00:00,11036.0 -2017-04-18 14:00:00,11091.0 -2017-04-18 15:00:00,11175.0 -2017-04-18 16:00:00,11150.0 -2017-04-18 17:00:00,11044.0 -2017-04-18 18:00:00,10921.0 -2017-04-18 19:00:00,10877.0 -2017-04-18 20:00:00,10676.0 -2017-04-18 21:00:00,10862.0 -2017-04-18 22:00:00,11068.0 -2017-04-18 23:00:00,10626.0 -2017-04-19 00:00:00,9921.0 -2017-04-17 01:00:00,7942.0 -2017-04-17 02:00:00,7594.0 -2017-04-17 03:00:00,7385.0 -2017-04-17 04:00:00,7275.0 -2017-04-17 05:00:00,7286.0 -2017-04-17 06:00:00,7546.0 -2017-04-17 07:00:00,8211.0 -2017-04-17 08:00:00,8979.0 -2017-04-17 09:00:00,9724.0 -2017-04-17 10:00:00,10217.0 -2017-04-17 11:00:00,10525.0 -2017-04-17 12:00:00,10789.0 -2017-04-17 13:00:00,10854.0 -2017-04-17 14:00:00,10874.0 -2017-04-17 15:00:00,10910.0 -2017-04-17 16:00:00,10865.0 -2017-04-17 17:00:00,10756.0 -2017-04-17 18:00:00,10602.0 -2017-04-17 19:00:00,10392.0 -2017-04-17 20:00:00,10160.0 -2017-04-17 21:00:00,10302.0 -2017-04-17 22:00:00,10576.0 -2017-04-17 23:00:00,10170.0 -2017-04-18 00:00:00,9492.0 -2017-04-16 01:00:00,8857.0 -2017-04-16 02:00:00,8359.0 -2017-04-16 03:00:00,7956.0 -2017-04-16 04:00:00,7698.0 -2017-04-16 05:00:00,7525.0 -2017-04-16 06:00:00,7465.0 -2017-04-16 07:00:00,7488.0 -2017-04-16 08:00:00,7453.0 -2017-04-16 09:00:00,7651.0 -2017-04-16 10:00:00,8040.0 -2017-04-16 11:00:00,8367.0 -2017-04-16 12:00:00,8573.0 -2017-04-16 13:00:00,8724.0 -2017-04-16 14:00:00,8770.0 -2017-04-16 15:00:00,8794.0 -2017-04-16 16:00:00,8783.0 -2017-04-16 17:00:00,8694.0 -2017-04-16 18:00:00,8648.0 -2017-04-16 19:00:00,8632.0 -2017-04-16 20:00:00,8561.0 -2017-04-16 21:00:00,8761.0 -2017-04-16 22:00:00,9126.0 -2017-04-16 23:00:00,8915.0 -2017-04-17 00:00:00,8413.0 -2017-04-15 01:00:00,8531.0 -2017-04-15 02:00:00,8106.0 -2017-04-15 03:00:00,7810.0 -2017-04-15 04:00:00,7612.0 -2017-04-15 05:00:00,7522.0 -2017-04-15 06:00:00,7563.0 -2017-04-15 07:00:00,7765.0 -2017-04-15 08:00:00,7897.0 -2017-04-15 09:00:00,8226.0 -2017-04-15 10:00:00,8715.0 -2017-04-15 11:00:00,9165.0 -2017-04-15 12:00:00,9462.0 -2017-04-15 13:00:00,9622.0 -2017-04-15 14:00:00,9674.0 -2017-04-15 15:00:00,9634.0 -2017-04-15 16:00:00,9663.0 -2017-04-15 17:00:00,9661.0 -2017-04-15 18:00:00,9692.0 -2017-04-15 19:00:00,9665.0 -2017-04-15 20:00:00,9592.0 -2017-04-15 21:00:00,9895.0 -2017-04-15 22:00:00,10119.0 -2017-04-15 23:00:00,9897.0 -2017-04-16 00:00:00,9436.0 -2017-04-14 01:00:00,8934.0 -2017-04-14 02:00:00,8453.0 -2017-04-14 03:00:00,8202.0 -2017-04-14 04:00:00,8047.0 -2017-04-14 05:00:00,7993.0 -2017-04-14 06:00:00,8119.0 -2017-04-14 07:00:00,8595.0 -2017-04-14 08:00:00,9071.0 -2017-04-14 09:00:00,9421.0 -2017-04-14 10:00:00,9660.0 -2017-04-14 11:00:00,9830.0 -2017-04-14 12:00:00,9890.0 -2017-04-14 13:00:00,9890.0 -2017-04-14 14:00:00,9846.0 -2017-04-14 15:00:00,9869.0 -2017-04-14 16:00:00,9797.0 -2017-04-14 17:00:00,9697.0 -2017-04-14 18:00:00,9659.0 -2017-04-14 19:00:00,9615.0 -2017-04-14 20:00:00,9539.0 -2017-04-14 21:00:00,9882.0 -2017-04-14 22:00:00,9878.0 -2017-04-14 23:00:00,9602.0 -2017-04-15 00:00:00,9096.0 -2017-04-13 01:00:00,8940.0 -2017-04-13 02:00:00,8555.0 -2017-04-13 03:00:00,8303.0 -2017-04-13 04:00:00,8121.0 -2017-04-13 05:00:00,8106.0 -2017-04-13 06:00:00,8332.0 -2017-04-13 07:00:00,9033.0 -2017-04-13 08:00:00,10039.0 -2017-04-13 09:00:00,10726.0 -2017-04-13 10:00:00,11006.0 -2017-04-13 11:00:00,11118.0 -2017-04-13 12:00:00,11159.0 -2017-04-13 13:00:00,11050.0 -2017-04-13 14:00:00,10919.0 -2017-04-13 15:00:00,10841.0 -2017-04-13 16:00:00,10675.0 -2017-04-13 17:00:00,10519.0 -2017-04-13 18:00:00,10390.0 -2017-04-13 19:00:00,10229.0 -2017-04-13 20:00:00,10072.0 -2017-04-13 21:00:00,10347.0 -2017-04-13 22:00:00,10573.0 -2017-04-13 23:00:00,10216.0 -2017-04-14 00:00:00,9558.0 -2017-04-12 01:00:00,9100.0 -2017-04-12 02:00:00,8698.0 -2017-04-12 03:00:00,8478.0 -2017-04-12 04:00:00,8344.0 -2017-04-12 05:00:00,8322.0 -2017-04-12 06:00:00,8592.0 -2017-04-12 07:00:00,9319.0 -2017-04-12 08:00:00,10122.0 -2017-04-12 09:00:00,10587.0 -2017-04-12 10:00:00,10698.0 -2017-04-12 11:00:00,10687.0 -2017-04-12 12:00:00,10758.0 -2017-04-12 13:00:00,10708.0 -2017-04-12 14:00:00,10666.0 -2017-04-12 15:00:00,10671.0 -2017-04-12 16:00:00,10536.0 -2017-04-12 17:00:00,10376.0 -2017-04-12 18:00:00,10285.0 -2017-04-12 19:00:00,10217.0 -2017-04-12 20:00:00,10207.0 -2017-04-12 21:00:00,10618.0 -2017-04-12 22:00:00,10694.0 -2017-04-12 23:00:00,10265.0 -2017-04-13 00:00:00,9551.0 -2017-04-11 01:00:00,8882.0 -2017-04-11 02:00:00,8474.0 -2017-04-11 03:00:00,8191.0 -2017-04-11 04:00:00,8070.0 -2017-04-11 05:00:00,8033.0 -2017-04-11 06:00:00,8290.0 -2017-04-11 07:00:00,8946.0 -2017-04-11 08:00:00,9895.0 -2017-04-11 09:00:00,10476.0 -2017-04-11 10:00:00,10762.0 -2017-04-11 11:00:00,10882.0 -2017-04-11 12:00:00,10981.0 -2017-04-11 13:00:00,10910.0 -2017-04-11 14:00:00,10818.0 -2017-04-11 15:00:00,10766.0 -2017-04-11 16:00:00,10655.0 -2017-04-11 17:00:00,10521.0 -2017-04-11 18:00:00,10419.0 -2017-04-11 19:00:00,10349.0 -2017-04-11 20:00:00,10257.0 -2017-04-11 21:00:00,10591.0 -2017-04-11 22:00:00,10778.0 -2017-04-11 23:00:00,10352.0 -2017-04-12 00:00:00,9726.0 -2017-04-10 01:00:00,8483.0 -2017-04-10 02:00:00,8157.0 -2017-04-10 03:00:00,7911.0 -2017-04-10 04:00:00,7751.0 -2017-04-10 05:00:00,7733.0 -2017-04-10 06:00:00,7981.0 -2017-04-10 07:00:00,8678.0 -2017-04-10 08:00:00,9596.0 -2017-04-10 09:00:00,10274.0 -2017-04-10 10:00:00,10608.0 -2017-04-10 11:00:00,10777.0 -2017-04-10 12:00:00,11084.0 -2017-04-10 13:00:00,11300.0 -2017-04-10 14:00:00,11255.0 -2017-04-10 15:00:00,11298.0 -2017-04-10 16:00:00,11162.0 -2017-04-10 17:00:00,11008.0 -2017-04-10 18:00:00,10998.0 -2017-04-10 19:00:00,10933.0 -2017-04-10 20:00:00,10594.0 -2017-04-10 21:00:00,10628.0 -2017-04-10 22:00:00,10603.0 -2017-04-10 23:00:00,10160.0 -2017-04-11 00:00:00,9511.0 -2017-04-09 01:00:00,8468.0 -2017-04-09 02:00:00,8061.0 -2017-04-09 03:00:00,7837.0 -2017-04-09 04:00:00,7635.0 -2017-04-09 05:00:00,7590.0 -2017-04-09 06:00:00,7570.0 -2017-04-09 07:00:00,7732.0 -2017-04-09 08:00:00,7830.0 -2017-04-09 09:00:00,7946.0 -2017-04-09 10:00:00,8236.0 -2017-04-09 11:00:00,8413.0 -2017-04-09 12:00:00,8632.0 -2017-04-09 13:00:00,8710.0 -2017-04-09 14:00:00,8760.0 -2017-04-09 15:00:00,8795.0 -2017-04-09 16:00:00,8817.0 -2017-04-09 17:00:00,8879.0 -2017-04-09 18:00:00,8928.0 -2017-04-09 19:00:00,8973.0 -2017-04-09 20:00:00,8990.0 -2017-04-09 21:00:00,9544.0 -2017-04-09 22:00:00,9768.0 -2017-04-09 23:00:00,9483.0 -2017-04-10 00:00:00,9024.0 -2017-04-08 01:00:00,9309.0 -2017-04-08 02:00:00,8857.0 -2017-04-08 03:00:00,8614.0 -2017-04-08 04:00:00,8444.0 -2017-04-08 05:00:00,8449.0 -2017-04-08 06:00:00,8515.0 -2017-04-08 07:00:00,8815.0 -2017-04-08 08:00:00,9030.0 -2017-04-08 09:00:00,9240.0 -2017-04-08 10:00:00,9433.0 -2017-04-08 11:00:00,9535.0 -2017-04-08 12:00:00,9571.0 -2017-04-08 13:00:00,9492.0 -2017-04-08 14:00:00,9367.0 -2017-04-08 15:00:00,9215.0 -2017-04-08 16:00:00,9086.0 -2017-04-08 17:00:00,9021.0 -2017-04-08 18:00:00,9002.0 -2017-04-08 19:00:00,8963.0 -2017-04-08 20:00:00,8994.0 -2017-04-08 21:00:00,9374.0 -2017-04-08 22:00:00,9619.0 -2017-04-08 23:00:00,9361.0 -2017-04-09 00:00:00,8948.0 -2017-04-07 01:00:00,9678.0 -2017-04-07 02:00:00,9258.0 -2017-04-07 03:00:00,9005.0 -2017-04-07 04:00:00,8879.0 -2017-04-07 05:00:00,8888.0 -2017-04-07 06:00:00,9152.0 -2017-04-07 07:00:00,9846.0 -2017-04-07 08:00:00,10745.0 -2017-04-07 09:00:00,11138.0 -2017-04-07 10:00:00,11273.0 -2017-04-07 11:00:00,11229.0 -2017-04-07 12:00:00,11213.0 -2017-04-07 13:00:00,11105.0 -2017-04-07 14:00:00,10979.0 -2017-04-07 15:00:00,10899.0 -2017-04-07 16:00:00,10710.0 -2017-04-07 17:00:00,10468.0 -2017-04-07 18:00:00,10280.0 -2017-04-07 19:00:00,10138.0 -2017-04-07 20:00:00,10057.0 -2017-04-07 21:00:00,10463.0 -2017-04-07 22:00:00,10637.0 -2017-04-07 23:00:00,10391.0 -2017-04-08 00:00:00,9855.0 -2017-04-06 01:00:00,9985.0 -2017-04-06 02:00:00,9538.0 -2017-04-06 03:00:00,9284.0 -2017-04-06 04:00:00,9117.0 -2017-04-06 05:00:00,9140.0 -2017-04-06 06:00:00,9389.0 -2017-04-06 07:00:00,10094.0 -2017-04-06 08:00:00,11060.0 -2017-04-06 09:00:00,11559.0 -2017-04-06 10:00:00,11575.0 -2017-04-06 11:00:00,11587.0 -2017-04-06 12:00:00,11548.0 -2017-04-06 13:00:00,11428.0 -2017-04-06 14:00:00,11299.0 -2017-04-06 15:00:00,11294.0 -2017-04-06 16:00:00,11152.0 -2017-04-06 17:00:00,11005.0 -2017-04-06 18:00:00,10903.0 -2017-04-06 19:00:00,10779.0 -2017-04-06 20:00:00,10732.0 -2017-04-06 21:00:00,11124.0 -2017-04-06 22:00:00,11294.0 -2017-04-06 23:00:00,10963.0 -2017-04-07 00:00:00,10300.0 -2017-04-05 01:00:00,9256.0 -2017-04-05 02:00:00,8823.0 -2017-04-05 03:00:00,8553.0 -2017-04-05 04:00:00,8426.0 -2017-04-05 05:00:00,8412.0 -2017-04-05 06:00:00,8699.0 -2017-04-05 07:00:00,9449.0 -2017-04-05 08:00:00,10526.0 -2017-04-05 09:00:00,11215.0 -2017-04-05 10:00:00,11497.0 -2017-04-05 11:00:00,11631.0 -2017-04-05 12:00:00,11734.0 -2017-04-05 13:00:00,11718.0 -2017-04-05 14:00:00,11670.0 -2017-04-05 15:00:00,11787.0 -2017-04-05 16:00:00,11756.0 -2017-04-05 17:00:00,11728.0 -2017-04-05 18:00:00,11824.0 -2017-04-05 19:00:00,11835.0 -2017-04-05 20:00:00,11737.0 -2017-04-05 21:00:00,11960.0 -2017-04-05 22:00:00,11790.0 -2017-04-05 23:00:00,11318.0 -2017-04-06 00:00:00,10640.0 -2017-04-04 01:00:00,9268.0 -2017-04-04 02:00:00,8859.0 -2017-04-04 03:00:00,8602.0 -2017-04-04 04:00:00,8451.0 -2017-04-04 05:00:00,8440.0 -2017-04-04 06:00:00,8676.0 -2017-04-04 07:00:00,9357.0 -2017-04-04 08:00:00,10396.0 -2017-04-04 09:00:00,10943.0 -2017-04-04 10:00:00,11159.0 -2017-04-04 11:00:00,11249.0 -2017-04-04 12:00:00,11287.0 -2017-04-04 13:00:00,11200.0 -2017-04-04 14:00:00,11094.0 -2017-04-04 15:00:00,11013.0 -2017-04-04 16:00:00,10821.0 -2017-04-04 17:00:00,10727.0 -2017-04-04 18:00:00,10678.0 -2017-04-04 19:00:00,10627.0 -2017-04-04 20:00:00,10575.0 -2017-04-04 21:00:00,10923.0 -2017-04-04 22:00:00,10966.0 -2017-04-04 23:00:00,10560.0 -2017-04-05 00:00:00,9906.0 -2017-04-03 01:00:00,8596.0 -2017-04-03 02:00:00,8271.0 -2017-04-03 03:00:00,8113.0 -2017-04-03 04:00:00,8017.0 -2017-04-03 05:00:00,7932.0 -2017-04-03 06:00:00,8238.0 -2017-04-03 07:00:00,8898.0 -2017-04-03 08:00:00,10133.0 -2017-04-03 09:00:00,10588.0 -2017-04-03 10:00:00,10895.0 -2017-04-03 11:00:00,11007.0 -2017-04-03 12:00:00,11134.0 -2017-04-03 13:00:00,11112.0 -2017-04-03 14:00:00,11046.0 -2017-04-03 15:00:00,11088.0 -2017-04-03 16:00:00,11080.0 -2017-04-03 17:00:00,11030.0 -2017-04-03 18:00:00,11013.0 -2017-04-03 19:00:00,11033.0 -2017-04-03 20:00:00,11016.0 -2017-04-03 21:00:00,11176.0 -2017-04-03 22:00:00,11029.0 -2017-04-03 23:00:00,10603.0 -2017-04-04 00:00:00,9955.0 -2017-04-02 01:00:00,8804.0 -2017-04-02 02:00:00,8458.0 -2017-04-02 03:00:00,8196.0 -2017-04-02 04:00:00,8073.0 -2017-04-02 05:00:00,8013.0 -2017-04-02 06:00:00,8032.0 -2017-04-02 07:00:00,8128.0 -2017-04-02 08:00:00,8413.0 -2017-04-02 09:00:00,8411.0 -2017-04-02 10:00:00,8694.0 -2017-04-02 11:00:00,8854.0 -2017-04-02 12:00:00,8847.0 -2017-04-02 13:00:00,8883.0 -2017-04-02 14:00:00,8845.0 -2017-04-02 15:00:00,8890.0 -2017-04-02 16:00:00,8866.0 -2017-04-02 17:00:00,8904.0 -2017-04-02 18:00:00,9086.0 -2017-04-02 19:00:00,9202.0 -2017-04-02 20:00:00,9373.0 -2017-04-02 21:00:00,9788.0 -2017-04-02 22:00:00,9793.0 -2017-04-02 23:00:00,9460.0 -2017-04-03 00:00:00,9069.0 -2017-04-01 01:00:00,9743.0 -2017-04-01 02:00:00,9292.0 -2017-04-01 03:00:00,9014.0 -2017-04-01 04:00:00,8850.0 -2017-04-01 05:00:00,8812.0 -2017-04-01 06:00:00,8887.0 -2017-04-01 07:00:00,9149.0 -2017-04-01 08:00:00,9492.0 -2017-04-01 09:00:00,9629.0 -2017-04-01 10:00:00,9806.0 -2017-04-01 11:00:00,9760.0 -2017-04-01 12:00:00,9779.0 -2017-04-01 13:00:00,9650.0 -2017-04-01 14:00:00,9469.0 -2017-04-01 15:00:00,9265.0 -2017-04-01 16:00:00,9152.0 -2017-04-01 17:00:00,9058.0 -2017-04-01 18:00:00,8986.0 -2017-04-01 19:00:00,9005.0 -2017-04-01 20:00:00,9137.0 -2017-04-01 21:00:00,9661.0 -2017-04-01 22:00:00,9784.0 -2017-04-01 23:00:00,9592.0 -2017-04-02 00:00:00,9204.0 -2017-03-31 01:00:00,9770.0 -2017-03-31 02:00:00,9381.0 -2017-03-31 03:00:00,9118.0 -2017-03-31 04:00:00,8947.0 -2017-03-31 05:00:00,8927.0 -2017-03-31 06:00:00,9173.0 -2017-03-31 07:00:00,9832.0 -2017-03-31 08:00:00,10759.0 -2017-03-31 09:00:00,11320.0 -2017-03-31 10:00:00,11562.0 -2017-03-31 11:00:00,11717.0 -2017-03-31 12:00:00,11811.0 -2017-03-31 13:00:00,11758.0 -2017-03-31 14:00:00,11664.0 -2017-03-31 15:00:00,11615.0 -2017-03-31 16:00:00,11442.0 -2017-03-31 17:00:00,11301.0 -2017-03-31 18:00:00,11170.0 -2017-03-31 19:00:00,11013.0 -2017-03-31 20:00:00,10927.0 -2017-03-31 21:00:00,11255.0 -2017-03-31 22:00:00,11185.0 -2017-03-31 23:00:00,10878.0 -2017-04-01 00:00:00,10302.0 -2017-03-30 01:00:00,9663.0 -2017-03-30 02:00:00,9289.0 -2017-03-30 03:00:00,9094.0 -2017-03-30 04:00:00,8978.0 -2017-03-30 05:00:00,8994.0 -2017-03-30 06:00:00,9182.0 -2017-03-30 07:00:00,9869.0 -2017-03-30 08:00:00,10918.0 -2017-03-30 09:00:00,11405.0 -2017-03-30 10:00:00,11584.0 -2017-03-30 11:00:00,11719.0 -2017-03-30 12:00:00,11884.0 -2017-03-30 13:00:00,12020.0 -2017-03-30 14:00:00,12010.0 -2017-03-30 15:00:00,12053.0 -2017-03-30 16:00:00,11890.0 -2017-03-30 17:00:00,11540.0 -2017-03-30 18:00:00,11318.0 -2017-03-30 19:00:00,11316.0 -2017-03-30 20:00:00,11310.0 -2017-03-30 21:00:00,11584.0 -2017-03-30 22:00:00,11417.0 -2017-03-30 23:00:00,10992.0 -2017-03-31 00:00:00,10364.0 -2017-03-29 01:00:00,9504.0 -2017-03-29 02:00:00,9064.0 -2017-03-29 03:00:00,8898.0 -2017-03-29 04:00:00,8713.0 -2017-03-29 05:00:00,8727.0 -2017-03-29 06:00:00,9054.0 -2017-03-29 07:00:00,9788.0 -2017-03-29 08:00:00,10766.0 -2017-03-29 09:00:00,11189.0 -2017-03-29 10:00:00,11264.0 -2017-03-29 11:00:00,11228.0 -2017-03-29 12:00:00,11160.0 -2017-03-29 13:00:00,11167.0 -2017-03-29 14:00:00,11158.0 -2017-03-29 15:00:00,11201.0 -2017-03-29 16:00:00,11225.0 -2017-03-29 17:00:00,11140.0 -2017-03-29 18:00:00,11136.0 -2017-03-29 19:00:00,11193.0 -2017-03-29 20:00:00,11254.0 -2017-03-29 21:00:00,11340.0 -2017-03-29 22:00:00,11138.0 -2017-03-29 23:00:00,10826.0 -2017-03-30 00:00:00,10249.0 -2017-03-28 01:00:00,9293.0 -2017-03-28 02:00:00,8859.0 -2017-03-28 03:00:00,8595.0 -2017-03-28 04:00:00,8514.0 -2017-03-28 05:00:00,8518.0 -2017-03-28 06:00:00,8835.0 -2017-03-28 07:00:00,9543.0 -2017-03-28 08:00:00,10544.0 -2017-03-28 09:00:00,10926.0 -2017-03-28 10:00:00,11187.0 -2017-03-28 11:00:00,11276.0 -2017-03-28 12:00:00,11292.0 -2017-03-28 13:00:00,11235.0 -2017-03-28 14:00:00,11147.0 -2017-03-28 15:00:00,11044.0 -2017-03-28 16:00:00,10912.0 -2017-03-28 17:00:00,10775.0 -2017-03-28 18:00:00,10748.0 -2017-03-28 19:00:00,10619.0 -2017-03-28 20:00:00,10640.0 -2017-03-28 21:00:00,11067.0 -2017-03-28 22:00:00,11114.0 -2017-03-28 23:00:00,10659.0 -2017-03-29 00:00:00,9981.0 -2017-03-27 01:00:00,8475.0 -2017-03-27 02:00:00,8165.0 -2017-03-27 03:00:00,7998.0 -2017-03-27 04:00:00,7814.0 -2017-03-27 05:00:00,7841.0 -2017-03-27 06:00:00,8111.0 -2017-03-27 07:00:00,8794.0 -2017-03-27 08:00:00,9838.0 -2017-03-27 09:00:00,10434.0 -2017-03-27 10:00:00,10753.0 -2017-03-27 11:00:00,10899.0 -2017-03-27 12:00:00,11015.0 -2017-03-27 13:00:00,11003.0 -2017-03-27 14:00:00,10927.0 -2017-03-27 15:00:00,10928.0 -2017-03-27 16:00:00,10836.0 -2017-03-27 17:00:00,10724.0 -2017-03-27 18:00:00,10656.0 -2017-03-27 19:00:00,10660.0 -2017-03-27 20:00:00,10650.0 -2017-03-27 21:00:00,10985.0 -2017-03-27 22:00:00,10897.0 -2017-03-27 23:00:00,10526.0 -2017-03-28 00:00:00,9919.0 -2017-03-26 01:00:00,8880.0 -2017-03-26 02:00:00,8457.0 -2017-03-26 03:00:00,8205.0 -2017-03-26 04:00:00,8017.0 -2017-03-26 05:00:00,7937.0 -2017-03-26 06:00:00,7952.0 -2017-03-26 07:00:00,8069.0 -2017-03-26 08:00:00,8362.0 -2017-03-26 09:00:00,8482.0 -2017-03-26 10:00:00,8752.0 -2017-03-26 11:00:00,8918.0 -2017-03-26 12:00:00,9033.0 -2017-03-26 13:00:00,9053.0 -2017-03-26 14:00:00,9027.0 -2017-03-26 15:00:00,8955.0 -2017-03-26 16:00:00,8882.0 -2017-03-26 17:00:00,8788.0 -2017-03-26 18:00:00,8886.0 -2017-03-26 19:00:00,8999.0 -2017-03-26 20:00:00,9191.0 -2017-03-26 21:00:00,9615.0 -2017-03-26 22:00:00,9538.0 -2017-03-26 23:00:00,9290.0 -2017-03-27 00:00:00,8878.0 -2017-03-25 01:00:00,9098.0 -2017-03-25 02:00:00,8649.0 -2017-03-25 03:00:00,8427.0 -2017-03-25 04:00:00,8247.0 -2017-03-25 05:00:00,8219.0 -2017-03-25 06:00:00,8288.0 -2017-03-25 07:00:00,8646.0 -2017-03-25 08:00:00,9103.0 -2017-03-25 09:00:00,9480.0 -2017-03-25 10:00:00,9872.0 -2017-03-25 11:00:00,10242.0 -2017-03-25 12:00:00,10413.0 -2017-03-25 13:00:00,10383.0 -2017-03-25 14:00:00,10189.0 -2017-03-25 15:00:00,9949.0 -2017-03-25 16:00:00,9799.0 -2017-03-25 17:00:00,9734.0 -2017-03-25 18:00:00,9769.0 -2017-03-25 19:00:00,9769.0 -2017-03-25 20:00:00,9857.0 -2017-03-25 21:00:00,10149.0 -2017-03-25 22:00:00,10035.0 -2017-03-25 23:00:00,9765.0 -2017-03-26 00:00:00,9345.0 -2017-03-24 01:00:00,9520.0 -2017-03-24 02:00:00,9123.0 -2017-03-24 03:00:00,8846.0 -2017-03-24 04:00:00,8662.0 -2017-03-24 05:00:00,8623.0 -2017-03-24 06:00:00,8807.0 -2017-03-24 07:00:00,9420.0 -2017-03-24 08:00:00,10439.0 -2017-03-24 09:00:00,10785.0 -2017-03-24 10:00:00,10901.0 -2017-03-24 11:00:00,10874.0 -2017-03-24 12:00:00,10961.0 -2017-03-24 13:00:00,11039.0 -2017-03-24 14:00:00,10995.0 -2017-03-24 15:00:00,11064.0 -2017-03-24 16:00:00,11021.0 -2017-03-24 17:00:00,10831.0 -2017-03-24 18:00:00,10632.0 -2017-03-24 19:00:00,10400.0 -2017-03-24 20:00:00,10313.0 -2017-03-24 21:00:00,10661.0 -2017-03-24 22:00:00,10470.0 -2017-03-24 23:00:00,10172.0 -2017-03-25 00:00:00,9613.0 -2017-03-23 01:00:00,10143.0 -2017-03-23 02:00:00,9676.0 -2017-03-23 03:00:00,9427.0 -2017-03-23 04:00:00,9342.0 -2017-03-23 05:00:00,9368.0 -2017-03-23 06:00:00,9553.0 -2017-03-23 07:00:00,10408.0 -2017-03-23 08:00:00,11488.0 -2017-03-23 09:00:00,11892.0 -2017-03-23 10:00:00,11966.0 -2017-03-23 11:00:00,12033.0 -2017-03-23 12:00:00,11927.0 -2017-03-23 13:00:00,11601.0 -2017-03-23 14:00:00,11523.0 -2017-03-23 15:00:00,11469.0 -2017-03-23 16:00:00,11366.0 -2017-03-23 17:00:00,11246.0 -2017-03-23 18:00:00,11088.0 -2017-03-23 19:00:00,11174.0 -2017-03-23 20:00:00,11287.0 -2017-03-23 21:00:00,11537.0 -2017-03-23 22:00:00,11356.0 -2017-03-23 23:00:00,10896.0 -2017-03-24 00:00:00,10105.0 -2017-03-22 01:00:00,9947.0 -2017-03-22 02:00:00,9570.0 -2017-03-22 03:00:00,9387.0 -2017-03-22 04:00:00,9273.0 -2017-03-22 05:00:00,9293.0 -2017-03-22 06:00:00,9593.0 -2017-03-22 07:00:00,10355.0 -2017-03-22 08:00:00,11451.0 -2017-03-22 09:00:00,11888.0 -2017-03-22 10:00:00,12011.0 -2017-03-22 11:00:00,12057.0 -2017-03-22 12:00:00,11940.0 -2017-03-22 13:00:00,11743.0 -2017-03-22 14:00:00,11605.0 -2017-03-22 15:00:00,11523.0 -2017-03-22 16:00:00,11329.0 -2017-03-22 17:00:00,11157.0 -2017-03-22 18:00:00,11082.0 -2017-03-22 19:00:00,11085.0 -2017-03-22 20:00:00,11158.0 -2017-03-22 21:00:00,11786.0 -2017-03-22 22:00:00,11807.0 -2017-03-22 23:00:00,11421.0 -2017-03-23 00:00:00,10761.0 -2017-03-21 01:00:00,9453.0 -2017-03-21 02:00:00,9079.0 -2017-03-21 03:00:00,8851.0 -2017-03-21 04:00:00,8742.0 -2017-03-21 05:00:00,8763.0 -2017-03-21 06:00:00,9052.0 -2017-03-21 07:00:00,9771.0 -2017-03-21 08:00:00,10913.0 -2017-03-21 09:00:00,11318.0 -2017-03-21 10:00:00,11312.0 -2017-03-21 11:00:00,11289.0 -2017-03-21 12:00:00,11255.0 -2017-03-21 13:00:00,11187.0 -2017-03-21 14:00:00,11136.0 -2017-03-21 15:00:00,11215.0 -2017-03-21 16:00:00,11070.0 -2017-03-21 17:00:00,10890.0 -2017-03-21 18:00:00,10772.0 -2017-03-21 19:00:00,10711.0 -2017-03-21 20:00:00,10780.0 -2017-03-21 21:00:00,11388.0 -2017-03-21 22:00:00,11471.0 -2017-03-21 23:00:00,11132.0 -2017-03-22 00:00:00,10546.0 -2017-03-20 01:00:00,8929.0 -2017-03-20 02:00:00,8598.0 -2017-03-20 03:00:00,8398.0 -2017-03-20 04:00:00,8256.0 -2017-03-20 05:00:00,8375.0 -2017-03-20 06:00:00,8689.0 -2017-03-20 07:00:00,9480.0 -2017-03-20 08:00:00,10590.0 -2017-03-20 09:00:00,11224.0 -2017-03-20 10:00:00,11289.0 -2017-03-20 11:00:00,11330.0 -2017-03-20 12:00:00,11364.0 -2017-03-20 13:00:00,11235.0 -2017-03-20 14:00:00,11173.0 -2017-03-20 15:00:00,11116.0 -2017-03-20 16:00:00,10985.0 -2017-03-20 17:00:00,10916.0 -2017-03-20 18:00:00,10864.0 -2017-03-20 19:00:00,10858.0 -2017-03-20 20:00:00,10834.0 -2017-03-20 21:00:00,11308.0 -2017-03-20 22:00:00,11194.0 -2017-03-20 23:00:00,10744.0 -2017-03-21 00:00:00,10106.0 -2017-03-19 01:00:00,9425.0 -2017-03-19 02:00:00,9090.0 -2017-03-19 03:00:00,8902.0 -2017-03-19 04:00:00,8802.0 -2017-03-19 05:00:00,8736.0 -2017-03-19 06:00:00,8818.0 -2017-03-19 07:00:00,8941.0 -2017-03-19 08:00:00,9246.0 -2017-03-19 09:00:00,9250.0 -2017-03-19 10:00:00,9362.0 -2017-03-19 11:00:00,9446.0 -2017-03-19 12:00:00,9482.0 -2017-03-19 13:00:00,9428.0 -2017-03-19 14:00:00,9391.0 -2017-03-19 15:00:00,9257.0 -2017-03-19 16:00:00,9145.0 -2017-03-19 17:00:00,9051.0 -2017-03-19 18:00:00,9082.0 -2017-03-19 19:00:00,9145.0 -2017-03-19 20:00:00,9384.0 -2017-03-19 21:00:00,10052.0 -2017-03-19 22:00:00,10107.0 -2017-03-19 23:00:00,9834.0 -2017-03-20 00:00:00,9412.0 -2017-03-18 01:00:00,9914.0 -2017-03-18 02:00:00,9399.0 -2017-03-18 03:00:00,9195.0 -2017-03-18 04:00:00,9046.0 -2017-03-18 05:00:00,8976.0 -2017-03-18 06:00:00,9000.0 -2017-03-18 07:00:00,9251.0 -2017-03-18 08:00:00,9756.0 -2017-03-18 09:00:00,9999.0 -2017-03-18 10:00:00,10345.0 -2017-03-18 11:00:00,10587.0 -2017-03-18 12:00:00,10713.0 -2017-03-18 13:00:00,10630.0 -2017-03-18 14:00:00,10431.0 -2017-03-18 15:00:00,10152.0 -2017-03-18 16:00:00,10014.0 -2017-03-18 17:00:00,9925.0 -2017-03-18 18:00:00,10025.0 -2017-03-18 19:00:00,10076.0 -2017-03-18 20:00:00,10282.0 -2017-03-18 21:00:00,10594.0 -2017-03-18 22:00:00,10543.0 -2017-03-18 23:00:00,10297.0 -2017-03-19 00:00:00,9863.0 -2017-03-17 01:00:00,10113.0 -2017-03-17 02:00:00,9661.0 -2017-03-17 03:00:00,9381.0 -2017-03-17 04:00:00,9269.0 -2017-03-17 05:00:00,9301.0 -2017-03-17 06:00:00,9507.0 -2017-03-17 07:00:00,10200.0 -2017-03-17 08:00:00,11259.0 -2017-03-17 09:00:00,11913.0 -2017-03-17 10:00:00,12095.0 -2017-03-17 11:00:00,12169.0 -2017-03-17 12:00:00,12257.0 -2017-03-17 13:00:00,12173.0 -2017-03-17 14:00:00,12021.0 -2017-03-17 15:00:00,11902.0 -2017-03-17 16:00:00,11724.0 -2017-03-17 17:00:00,11498.0 -2017-03-17 18:00:00,11339.0 -2017-03-17 19:00:00,11114.0 -2017-03-17 20:00:00,10996.0 -2017-03-17 21:00:00,11465.0 -2017-03-17 22:00:00,11367.0 -2017-03-17 23:00:00,11064.0 -2017-03-18 00:00:00,10510.0 -2017-03-16 01:00:00,10678.0 -2017-03-16 02:00:00,10293.0 -2017-03-16 03:00:00,10047.0 -2017-03-16 04:00:00,9947.0 -2017-03-16 05:00:00,9968.0 -2017-03-16 06:00:00,10237.0 -2017-03-16 07:00:00,10957.0 -2017-03-16 08:00:00,12057.0 -2017-03-16 09:00:00,12490.0 -2017-03-16 10:00:00,12441.0 -2017-03-16 11:00:00,12274.0 -2017-03-16 12:00:00,12205.0 -2017-03-16 13:00:00,12057.0 -2017-03-16 14:00:00,11887.0 -2017-03-16 15:00:00,11773.0 -2017-03-16 16:00:00,11573.0 -2017-03-16 17:00:00,11326.0 -2017-03-16 18:00:00,11195.0 -2017-03-16 19:00:00,11169.0 -2017-03-16 20:00:00,11231.0 -2017-03-16 21:00:00,11866.0 -2017-03-16 22:00:00,11843.0 -2017-03-16 23:00:00,11465.0 -2017-03-17 00:00:00,10784.0 -2017-03-15 01:00:00,11044.0 -2017-03-15 02:00:00,10629.0 -2017-03-15 03:00:00,10381.0 -2017-03-15 04:00:00,10304.0 -2017-03-15 05:00:00,10295.0 -2017-03-15 06:00:00,10566.0 -2017-03-15 07:00:00,11255.0 -2017-03-15 08:00:00,12384.0 -2017-03-15 09:00:00,12856.0 -2017-03-15 10:00:00,12815.0 -2017-03-15 11:00:00,12704.0 -2017-03-15 12:00:00,12622.0 -2017-03-15 13:00:00,12483.0 -2017-03-15 14:00:00,12336.0 -2017-03-15 15:00:00,12218.0 -2017-03-15 16:00:00,12028.0 -2017-03-15 17:00:00,11831.0 -2017-03-15 18:00:00,11686.0 -2017-03-15 19:00:00,11677.0 -2017-03-15 20:00:00,11749.0 -2017-03-15 21:00:00,12414.0 -2017-03-15 22:00:00,12415.0 -2017-03-15 23:00:00,12023.0 -2017-03-16 00:00:00,11344.0 -2017-03-14 01:00:00,10659.0 -2017-03-14 02:00:00,10248.0 -2017-03-14 03:00:00,9989.0 -2017-03-14 04:00:00,9868.0 -2017-03-14 05:00:00,9909.0 -2017-03-14 06:00:00,10188.0 -2017-03-14 07:00:00,10895.0 -2017-03-14 08:00:00,12015.0 -2017-03-14 09:00:00,12547.0 -2017-03-14 10:00:00,12653.0 -2017-03-14 11:00:00,12627.0 -2017-03-14 12:00:00,12592.0 -2017-03-14 13:00:00,12513.0 -2017-03-14 14:00:00,12421.0 -2017-03-14 15:00:00,12362.0 -2017-03-14 16:00:00,12239.0 -2017-03-14 17:00:00,12152.0 -2017-03-14 18:00:00,12112.0 -2017-03-14 19:00:00,12099.0 -2017-03-14 20:00:00,12210.0 -2017-03-14 21:00:00,12831.0 -2017-03-14 22:00:00,12803.0 -2017-03-14 23:00:00,12373.0 -2017-03-15 00:00:00,11669.0 -2017-03-13 01:00:00,9872.0 -2017-03-13 02:00:00,9554.0 -2017-03-13 03:00:00,9408.0 -2017-03-13 04:00:00,9354.0 -2017-03-13 05:00:00,9414.0 -2017-03-13 06:00:00,9746.0 -2017-03-13 07:00:00,10475.0 -2017-03-13 08:00:00,11619.0 -2017-03-13 09:00:00,12166.0 -2017-03-13 10:00:00,12289.0 -2017-03-13 11:00:00,12336.0 -2017-03-13 12:00:00,12414.0 -2017-03-13 13:00:00,12378.0 -2017-03-13 14:00:00,12324.0 -2017-03-13 15:00:00,12290.0 -2017-03-13 16:00:00,12168.0 -2017-03-13 17:00:00,12047.0 -2017-03-13 18:00:00,12062.0 -2017-03-13 19:00:00,12183.0 -2017-03-13 20:00:00,12327.0 -2017-03-13 21:00:00,12643.0 -2017-03-13 22:00:00,12476.0 -2017-03-13 23:00:00,12038.0 -2017-03-14 00:00:00,11335.0 -2017-03-12 01:00:00,9870.0 -2017-03-12 02:00:00,9582.0 -2017-03-12 04:00:00,9464.0 -2017-03-12 05:00:00,9373.0 -2017-03-12 06:00:00,9405.0 -2017-03-12 07:00:00,9537.0 -2017-03-12 08:00:00,9799.0 -2017-03-12 09:00:00,9797.0 -2017-03-12 10:00:00,9876.0 -2017-03-12 11:00:00,9912.0 -2017-03-12 12:00:00,9985.0 -2017-03-12 13:00:00,9925.0 -2017-03-12 14:00:00,9897.0 -2017-03-12 15:00:00,9778.0 -2017-03-12 16:00:00,9683.0 -2017-03-12 17:00:00,9596.0 -2017-03-12 18:00:00,9667.0 -2017-03-12 19:00:00,9907.0 -2017-03-12 20:00:00,10355.0 -2017-03-12 21:00:00,11001.0 -2017-03-12 22:00:00,10948.0 -2017-03-12 23:00:00,10666.0 -2017-03-13 00:00:00,10304.0 -2017-03-11 01:00:00,10473.0 -2017-03-11 02:00:00,10122.0 -2017-03-11 03:00:00,9871.0 -2017-03-11 04:00:00,9775.0 -2017-03-11 05:00:00,9728.0 -2017-03-11 06:00:00,9875.0 -2017-03-11 07:00:00,10179.0 -2017-03-11 08:00:00,10419.0 -2017-03-11 09:00:00,10579.0 -2017-03-11 10:00:00,10861.0 -2017-03-11 11:00:00,10926.0 -2017-03-11 12:00:00,10892.0 -2017-03-11 13:00:00,10825.0 -2017-03-11 14:00:00,10584.0 -2017-03-11 15:00:00,10329.0 -2017-03-11 16:00:00,10122.0 -2017-03-11 17:00:00,10076.0 -2017-03-11 18:00:00,10188.0 -2017-03-11 19:00:00,10598.0 -2017-03-11 20:00:00,11220.0 -2017-03-11 21:00:00,11198.0 -2017-03-11 22:00:00,11093.0 -2017-03-11 23:00:00,10795.0 -2017-03-12 00:00:00,10385.0 -2017-03-10 01:00:00,9853.0 -2017-03-10 02:00:00,9514.0 -2017-03-10 03:00:00,9345.0 -2017-03-10 04:00:00,9286.0 -2017-03-10 05:00:00,9337.0 -2017-03-10 06:00:00,9646.0 -2017-03-10 07:00:00,10401.0 -2017-03-10 08:00:00,11203.0 -2017-03-10 09:00:00,11581.0 -2017-03-10 10:00:00,11771.0 -2017-03-10 11:00:00,11785.0 -2017-03-10 12:00:00,11832.0 -2017-03-10 13:00:00,11768.0 -2017-03-10 14:00:00,11668.0 -2017-03-10 15:00:00,11629.0 -2017-03-10 16:00:00,11445.0 -2017-03-10 17:00:00,11319.0 -2017-03-10 18:00:00,11378.0 -2017-03-10 19:00:00,11691.0 -2017-03-10 20:00:00,12258.0 -2017-03-10 21:00:00,12207.0 -2017-03-10 22:00:00,12000.0 -2017-03-10 23:00:00,11665.0 -2017-03-11 00:00:00,11076.0 -2017-03-09 01:00:00,9516.0 -2017-03-09 02:00:00,9155.0 -2017-03-09 03:00:00,8924.0 -2017-03-09 04:00:00,8849.0 -2017-03-09 05:00:00,8878.0 -2017-03-09 06:00:00,9167.0 -2017-03-09 07:00:00,9905.0 -2017-03-09 08:00:00,10779.0 -2017-03-09 09:00:00,11270.0 -2017-03-09 10:00:00,11377.0 -2017-03-09 11:00:00,11412.0 -2017-03-09 12:00:00,11429.0 -2017-03-09 13:00:00,11366.0 -2017-03-09 14:00:00,11235.0 -2017-03-09 15:00:00,11250.0 -2017-03-09 16:00:00,11187.0 -2017-03-09 17:00:00,11181.0 -2017-03-09 18:00:00,11389.0 -2017-03-09 19:00:00,11716.0 -2017-03-09 20:00:00,11990.0 -2017-03-09 21:00:00,11855.0 -2017-03-09 22:00:00,11607.0 -2017-03-09 23:00:00,11142.0 -2017-03-10 00:00:00,10467.0 -2017-03-08 01:00:00,9410.0 -2017-03-08 02:00:00,9012.0 -2017-03-08 03:00:00,8801.0 -2017-03-08 04:00:00,8704.0 -2017-03-08 05:00:00,8718.0 -2017-03-08 06:00:00,9043.0 -2017-03-08 07:00:00,9809.0 -2017-03-08 08:00:00,10662.0 -2017-03-08 09:00:00,11087.0 -2017-03-08 10:00:00,11215.0 -2017-03-08 11:00:00,11251.0 -2017-03-08 12:00:00,11214.0 -2017-03-08 13:00:00,11253.0 -2017-03-08 14:00:00,11118.0 -2017-03-08 15:00:00,11061.0 -2017-03-08 16:00:00,10871.0 -2017-03-08 17:00:00,10747.0 -2017-03-08 18:00:00,10748.0 -2017-03-08 19:00:00,10951.0 -2017-03-08 20:00:00,11502.0 -2017-03-08 21:00:00,11507.0 -2017-03-08 22:00:00,11235.0 -2017-03-08 23:00:00,10786.0 -2017-03-09 00:00:00,10134.0 -2017-03-07 01:00:00,9124.0 -2017-03-07 02:00:00,8665.0 -2017-03-07 03:00:00,8417.0 -2017-03-07 04:00:00,8285.0 -2017-03-07 05:00:00,8219.0 -2017-03-07 06:00:00,8448.0 -2017-03-07 07:00:00,9188.0 -2017-03-07 08:00:00,10144.0 -2017-03-07 09:00:00,10696.0 -2017-03-07 10:00:00,10938.0 -2017-03-07 11:00:00,11001.0 -2017-03-07 12:00:00,11062.0 -2017-03-07 13:00:00,11035.0 -2017-03-07 14:00:00,10954.0 -2017-03-07 15:00:00,10903.0 -2017-03-07 16:00:00,10756.0 -2017-03-07 17:00:00,10633.0 -2017-03-07 18:00:00,10652.0 -2017-03-07 19:00:00,10911.0 -2017-03-07 20:00:00,11416.0 -2017-03-07 21:00:00,11386.0 -2017-03-07 22:00:00,11145.0 -2017-03-07 23:00:00,10698.0 -2017-03-08 00:00:00,10033.0 -2017-03-06 01:00:00,8817.0 -2017-03-06 02:00:00,8515.0 -2017-03-06 03:00:00,8351.0 -2017-03-06 04:00:00,8275.0 -2017-03-06 05:00:00,8319.0 -2017-03-06 06:00:00,8621.0 -2017-03-06 07:00:00,9367.0 -2017-03-06 08:00:00,10276.0 -2017-03-06 09:00:00,10757.0 -2017-03-06 10:00:00,11077.0 -2017-03-06 11:00:00,11135.0 -2017-03-06 12:00:00,11182.0 -2017-03-06 13:00:00,11193.0 -2017-03-06 14:00:00,11152.0 -2017-03-06 15:00:00,11170.0 -2017-03-06 16:00:00,11101.0 -2017-03-06 17:00:00,11020.0 -2017-03-06 18:00:00,11079.0 -2017-03-06 19:00:00,11378.0 -2017-03-06 20:00:00,11544.0 -2017-03-06 21:00:00,11334.0 -2017-03-06 22:00:00,10997.0 -2017-03-06 23:00:00,10509.0 -2017-03-07 00:00:00,9771.0 -2017-03-05 01:00:00,9794.0 -2017-03-05 02:00:00,9425.0 -2017-03-05 03:00:00,9183.0 -2017-03-05 04:00:00,9056.0 -2017-03-05 05:00:00,8979.0 -2017-03-05 06:00:00,8999.0 -2017-03-05 07:00:00,9172.0 -2017-03-05 08:00:00,9188.0 -2017-03-05 09:00:00,9245.0 -2017-03-05 10:00:00,9419.0 -2017-03-05 11:00:00,9565.0 -2017-03-05 12:00:00,9521.0 -2017-03-05 13:00:00,9458.0 -2017-03-05 14:00:00,9408.0 -2017-03-05 15:00:00,9318.0 -2017-03-05 16:00:00,9282.0 -2017-03-05 17:00:00,9278.0 -2017-03-05 18:00:00,9458.0 -2017-03-05 19:00:00,9742.0 -2017-03-05 20:00:00,10315.0 -2017-03-05 21:00:00,10251.0 -2017-03-05 22:00:00,10063.0 -2017-03-05 23:00:00,9698.0 -2017-03-06 00:00:00,9259.0 -2017-03-04 01:00:00,10174.0 -2017-03-04 02:00:00,9761.0 -2017-03-04 03:00:00,9563.0 -2017-03-04 04:00:00,9405.0 -2017-03-04 05:00:00,9377.0 -2017-03-04 06:00:00,9476.0 -2017-03-04 07:00:00,9794.0 -2017-03-04 08:00:00,10099.0 -2017-03-04 09:00:00,10290.0 -2017-03-04 10:00:00,10550.0 -2017-03-04 11:00:00,10632.0 -2017-03-04 12:00:00,10597.0 -2017-03-04 13:00:00,10401.0 -2017-03-04 14:00:00,10209.0 -2017-03-04 15:00:00,10013.0 -2017-03-04 16:00:00,9867.0 -2017-03-04 17:00:00,9794.0 -2017-03-04 18:00:00,9877.0 -2017-03-04 19:00:00,10299.0 -2017-03-04 20:00:00,11020.0 -2017-03-04 21:00:00,11003.0 -2017-03-04 22:00:00,10926.0 -2017-03-04 23:00:00,10672.0 -2017-03-05 00:00:00,10237.0 -2017-03-03 01:00:00,10366.0 -2017-03-03 02:00:00,9984.0 -2017-03-03 03:00:00,9805.0 -2017-03-03 04:00:00,9720.0 -2017-03-03 05:00:00,9723.0 -2017-03-03 06:00:00,10007.0 -2017-03-03 07:00:00,10763.0 -2017-03-03 08:00:00,11630.0 -2017-03-03 09:00:00,11991.0 -2017-03-03 10:00:00,12101.0 -2017-03-03 11:00:00,12115.0 -2017-03-03 12:00:00,12079.0 -2017-03-03 13:00:00,11967.0 -2017-03-03 14:00:00,11814.0 -2017-03-03 15:00:00,11706.0 -2017-03-03 16:00:00,11485.0 -2017-03-03 17:00:00,11315.0 -2017-03-03 18:00:00,11324.0 -2017-03-03 19:00:00,11725.0 -2017-03-03 20:00:00,12220.0 -2017-03-03 21:00:00,12052.0 -2017-03-03 22:00:00,11772.0 -2017-03-03 23:00:00,11395.0 -2017-03-04 00:00:00,10775.0 -2017-03-02 01:00:00,10061.0 -2017-03-02 02:00:00,9641.0 -2017-03-02 03:00:00,9423.0 -2017-03-02 04:00:00,9308.0 -2017-03-02 05:00:00,9316.0 -2017-03-02 06:00:00,9593.0 -2017-03-02 07:00:00,10287.0 -2017-03-02 08:00:00,11240.0 -2017-03-02 09:00:00,11690.0 -2017-03-02 10:00:00,11855.0 -2017-03-02 11:00:00,11791.0 -2017-03-02 12:00:00,11744.0 -2017-03-02 13:00:00,11746.0 -2017-03-02 14:00:00,11816.0 -2017-03-02 15:00:00,11831.0 -2017-03-02 16:00:00,11799.0 -2017-03-02 17:00:00,11820.0 -2017-03-02 18:00:00,11878.0 -2017-03-02 19:00:00,12170.0 -2017-03-02 20:00:00,12487.0 -2017-03-02 21:00:00,12366.0 -2017-03-02 22:00:00,12106.0 -2017-03-02 23:00:00,11651.0 -2017-03-03 00:00:00,10946.0 -2017-03-01 01:00:00,9285.0 -2017-03-01 02:00:00,8782.0 -2017-03-01 03:00:00,8517.0 -2017-03-01 04:00:00,8381.0 -2017-03-01 05:00:00,8335.0 -2017-03-01 06:00:00,8615.0 -2017-03-01 07:00:00,9343.0 -2017-03-01 08:00:00,10378.0 -2017-03-01 09:00:00,10996.0 -2017-03-01 10:00:00,11279.0 -2017-03-01 11:00:00,11428.0 -2017-03-01 12:00:00,11579.0 -2017-03-01 13:00:00,11597.0 -2017-03-01 14:00:00,11593.0 -2017-03-01 15:00:00,11680.0 -2017-03-01 16:00:00,11650.0 -2017-03-01 17:00:00,11700.0 -2017-03-01 18:00:00,11860.0 -2017-03-01 19:00:00,12204.0 -2017-03-01 20:00:00,12437.0 -2017-03-01 21:00:00,12264.0 -2017-03-01 22:00:00,11959.0 -2017-03-01 23:00:00,11459.0 -2017-03-02 00:00:00,10700.0 -2017-02-28 01:00:00,9356.0 -2017-02-28 02:00:00,8960.0 -2017-02-28 03:00:00,8723.0 -2017-02-28 04:00:00,8622.0 -2017-02-28 05:00:00,8575.0 -2017-02-28 06:00:00,8763.0 -2017-02-28 07:00:00,9462.0 -2017-02-28 08:00:00,10354.0 -2017-02-28 09:00:00,10796.0 -2017-02-28 10:00:00,11065.0 -2017-02-28 11:00:00,11131.0 -2017-02-28 12:00:00,11216.0 -2017-02-28 13:00:00,11186.0 -2017-02-28 14:00:00,11120.0 -2017-02-28 15:00:00,11121.0 -2017-02-28 16:00:00,11017.0 -2017-02-28 17:00:00,11007.0 -2017-02-28 18:00:00,11182.0 -2017-02-28 19:00:00,11639.0 -2017-02-28 20:00:00,11595.0 -2017-02-28 21:00:00,11328.0 -2017-02-28 22:00:00,10994.0 -2017-02-28 23:00:00,10552.0 -2017-03-01 00:00:00,9869.0 -2017-02-27 01:00:00,9218.0 -2017-02-27 02:00:00,8884.0 -2017-02-27 03:00:00,8783.0 -2017-02-27 04:00:00,8664.0 -2017-02-27 05:00:00,8718.0 -2017-02-27 06:00:00,9036.0 -2017-02-27 07:00:00,9864.0 -2017-02-27 08:00:00,10862.0 -2017-02-27 09:00:00,11237.0 -2017-02-27 10:00:00,11299.0 -2017-02-27 11:00:00,11234.0 -2017-02-27 12:00:00,11182.0 -2017-02-27 13:00:00,11079.0 -2017-02-27 14:00:00,11012.0 -2017-02-27 15:00:00,10938.0 -2017-02-27 16:00:00,10788.0 -2017-02-27 17:00:00,10705.0 -2017-02-27 18:00:00,10779.0 -2017-02-27 19:00:00,11174.0 -2017-02-27 20:00:00,11534.0 -2017-02-27 21:00:00,11410.0 -2017-02-27 22:00:00,11162.0 -2017-02-27 23:00:00,10692.0 -2017-02-28 00:00:00,9967.0 -2017-02-26 01:00:00,10032.0 -2017-02-26 02:00:00,9684.0 -2017-02-26 03:00:00,9418.0 -2017-02-26 04:00:00,9357.0 -2017-02-26 05:00:00,9241.0 -2017-02-26 06:00:00,9288.0 -2017-02-26 07:00:00,9361.0 -2017-02-26 08:00:00,9620.0 -2017-02-26 09:00:00,9600.0 -2017-02-26 10:00:00,9846.0 -2017-02-26 11:00:00,10023.0 -2017-02-26 12:00:00,9932.0 -2017-02-26 13:00:00,9932.0 -2017-02-26 14:00:00,9673.0 -2017-02-26 15:00:00,9534.0 -2017-02-26 16:00:00,9373.0 -2017-02-26 17:00:00,9347.0 -2017-02-26 18:00:00,9461.0 -2017-02-26 19:00:00,10044.0 -2017-02-26 20:00:00,10641.0 -2017-02-26 21:00:00,10530.0 -2017-02-26 22:00:00,10373.0 -2017-02-26 23:00:00,10049.0 -2017-02-27 00:00:00,9640.0 -2017-02-25 01:00:00,9781.0 -2017-02-25 02:00:00,9420.0 -2017-02-25 03:00:00,9195.0 -2017-02-25 04:00:00,9025.0 -2017-02-25 05:00:00,9039.0 -2017-02-25 06:00:00,9173.0 -2017-02-25 07:00:00,9564.0 -2017-02-25 08:00:00,10031.0 -2017-02-25 09:00:00,10280.0 -2017-02-25 10:00:00,10791.0 -2017-02-25 11:00:00,11081.0 -2017-02-25 12:00:00,11211.0 -2017-02-25 13:00:00,11119.0 -2017-02-25 14:00:00,10989.0 -2017-02-25 15:00:00,10802.0 -2017-02-25 16:00:00,10676.0 -2017-02-25 17:00:00,10709.0 -2017-02-25 18:00:00,10713.0 -2017-02-25 19:00:00,11133.0 -2017-02-25 20:00:00,11509.0 -2017-02-25 21:00:00,11441.0 -2017-02-25 22:00:00,11230.0 -2017-02-25 23:00:00,10982.0 -2017-02-26 00:00:00,10542.0 -2017-02-24 01:00:00,9599.0 -2017-02-24 02:00:00,9201.0 -2017-02-24 03:00:00,8965.0 -2017-02-24 04:00:00,8755.0 -2017-02-24 05:00:00,8768.0 -2017-02-24 06:00:00,8946.0 -2017-02-24 07:00:00,9629.0 -2017-02-24 08:00:00,10637.0 -2017-02-24 09:00:00,11124.0 -2017-02-24 10:00:00,11364.0 -2017-02-24 11:00:00,11553.0 -2017-02-24 12:00:00,11679.0 -2017-02-24 13:00:00,11626.0 -2017-02-24 14:00:00,11613.0 -2017-02-24 15:00:00,11463.0 -2017-02-24 16:00:00,11373.0 -2017-02-24 17:00:00,11294.0 -2017-02-24 18:00:00,11382.0 -2017-02-24 19:00:00,11649.0 -2017-02-24 20:00:00,11741.0 -2017-02-24 21:00:00,11463.0 -2017-02-24 22:00:00,11276.0 -2017-02-24 23:00:00,10893.0 -2017-02-25 00:00:00,10320.0 -2017-02-23 01:00:00,8943.0 -2017-02-23 02:00:00,8502.0 -2017-02-23 03:00:00,8236.0 -2017-02-23 04:00:00,8093.0 -2017-02-23 05:00:00,8067.0 -2017-02-23 06:00:00,8231.0 -2017-02-23 07:00:00,8902.0 -2017-02-23 08:00:00,9841.0 -2017-02-23 09:00:00,10268.0 -2017-02-23 10:00:00,10470.0 -2017-02-23 11:00:00,10655.0 -2017-02-23 12:00:00,10819.0 -2017-02-23 13:00:00,10916.0 -2017-02-23 14:00:00,10962.0 -2017-02-23 15:00:00,11091.0 -2017-02-23 16:00:00,11035.0 -2017-02-23 17:00:00,11096.0 -2017-02-23 18:00:00,11356.0 -2017-02-23 19:00:00,11749.0 -2017-02-23 20:00:00,11852.0 -2017-02-23 21:00:00,11691.0 -2017-02-23 22:00:00,11423.0 -2017-02-23 23:00:00,10917.0 -2017-02-24 00:00:00,10196.0 -2017-02-22 01:00:00,9018.0 -2017-02-22 02:00:00,8654.0 -2017-02-22 03:00:00,8374.0 -2017-02-22 04:00:00,8257.0 -2017-02-22 05:00:00,8225.0 -2017-02-22 06:00:00,8442.0 -2017-02-22 07:00:00,9020.0 -2017-02-22 08:00:00,10036.0 -2017-02-22 09:00:00,10477.0 -2017-02-22 10:00:00,10723.0 -2017-02-22 11:00:00,10826.0 -2017-02-22 12:00:00,10900.0 -2017-02-22 13:00:00,10867.0 -2017-02-22 14:00:00,10855.0 -2017-02-22 15:00:00,10858.0 -2017-02-22 16:00:00,10795.0 -2017-02-22 17:00:00,10748.0 -2017-02-22 18:00:00,10686.0 -2017-02-22 19:00:00,10927.0 -2017-02-22 20:00:00,11331.0 -2017-02-22 21:00:00,11133.0 -2017-02-22 22:00:00,10825.0 -2017-02-22 23:00:00,10318.0 -2017-02-23 00:00:00,9566.0 -2017-02-21 01:00:00,8858.0 -2017-02-21 02:00:00,8403.0 -2017-02-21 03:00:00,8122.0 -2017-02-21 04:00:00,7995.0 -2017-02-21 05:00:00,8008.0 -2017-02-21 06:00:00,8251.0 -2017-02-21 07:00:00,8903.0 -2017-02-21 08:00:00,9923.0 -2017-02-21 09:00:00,10435.0 -2017-02-21 10:00:00,10686.0 -2017-02-21 11:00:00,10797.0 -2017-02-21 12:00:00,10878.0 -2017-02-21 13:00:00,10896.0 -2017-02-21 14:00:00,10895.0 -2017-02-21 15:00:00,10975.0 -2017-02-21 16:00:00,10890.0 -2017-02-21 17:00:00,10757.0 -2017-02-21 18:00:00,10720.0 -2017-02-21 19:00:00,10982.0 -2017-02-21 20:00:00,11321.0 -2017-02-21 21:00:00,11121.0 -2017-02-21 22:00:00,10774.0 -2017-02-21 23:00:00,10267.0 -2017-02-22 00:00:00,9630.0 -2017-02-20 01:00:00,8529.0 -2017-02-20 02:00:00,8244.0 -2017-02-20 03:00:00,8065.0 -2017-02-20 04:00:00,7991.0 -2017-02-20 05:00:00,8033.0 -2017-02-20 06:00:00,8302.0 -2017-02-20 07:00:00,8917.0 -2017-02-20 08:00:00,9734.0 -2017-02-20 09:00:00,10112.0 -2017-02-20 10:00:00,10350.0 -2017-02-20 11:00:00,10485.0 -2017-02-20 12:00:00,10606.0 -2017-02-20 13:00:00,10665.0 -2017-02-20 14:00:00,10609.0 -2017-02-20 15:00:00,10645.0 -2017-02-20 16:00:00,10554.0 -2017-02-20 17:00:00,10494.0 -2017-02-20 18:00:00,10523.0 -2017-02-20 19:00:00,11023.0 -2017-02-20 20:00:00,11235.0 -2017-02-20 21:00:00,11046.0 -2017-02-20 22:00:00,10681.0 -2017-02-20 23:00:00,10165.0 -2017-02-21 00:00:00,9472.0 -2017-02-19 01:00:00,8474.0 -2017-02-19 02:00:00,8192.0 -2017-02-19 03:00:00,7974.0 -2017-02-19 04:00:00,7886.0 -2017-02-19 05:00:00,7783.0 -2017-02-19 06:00:00,7904.0 -2017-02-19 07:00:00,8060.0 -2017-02-19 08:00:00,8272.0 -2017-02-19 09:00:00,8311.0 -2017-02-19 10:00:00,8460.0 -2017-02-19 11:00:00,8608.0 -2017-02-19 12:00:00,8689.0 -2017-02-19 13:00:00,8728.0 -2017-02-19 14:00:00,8693.0 -2017-02-19 15:00:00,8649.0 -2017-02-19 16:00:00,8612.0 -2017-02-19 17:00:00,8592.0 -2017-02-19 18:00:00,8742.0 -2017-02-19 19:00:00,9278.0 -2017-02-19 20:00:00,9754.0 -2017-02-19 21:00:00,9683.0 -2017-02-19 22:00:00,9547.0 -2017-02-19 23:00:00,9297.0 -2017-02-20 00:00:00,8914.0 -2017-02-18 01:00:00,9146.0 -2017-02-18 02:00:00,8699.0 -2017-02-18 03:00:00,8454.0 -2017-02-18 04:00:00,8299.0 -2017-02-18 05:00:00,8289.0 -2017-02-18 06:00:00,8299.0 -2017-02-18 07:00:00,8672.0 -2017-02-18 08:00:00,9005.0 -2017-02-18 09:00:00,9105.0 -2017-02-18 10:00:00,9335.0 -2017-02-18 11:00:00,9448.0 -2017-02-18 12:00:00,9490.0 -2017-02-18 13:00:00,9421.0 -2017-02-18 14:00:00,9354.0 -2017-02-18 15:00:00,9192.0 -2017-02-18 16:00:00,9078.0 -2017-02-18 17:00:00,8994.0 -2017-02-18 18:00:00,9030.0 -2017-02-18 19:00:00,9398.0 -2017-02-18 20:00:00,9806.0 -2017-02-18 21:00:00,9677.0 -2017-02-18 22:00:00,9546.0 -2017-02-18 23:00:00,9281.0 -2017-02-19 00:00:00,8871.0 -2017-02-17 01:00:00,9893.0 -2017-02-17 02:00:00,9543.0 -2017-02-17 03:00:00,9298.0 -2017-02-17 04:00:00,9178.0 -2017-02-17 05:00:00,9171.0 -2017-02-17 06:00:00,9461.0 -2017-02-17 07:00:00,10175.0 -2017-02-17 08:00:00,11126.0 -2017-02-17 09:00:00,11376.0 -2017-02-17 10:00:00,11388.0 -2017-02-17 11:00:00,11283.0 -2017-02-17 12:00:00,11179.0 -2017-02-17 13:00:00,11110.0 -2017-02-17 14:00:00,10920.0 -2017-02-17 15:00:00,10855.0 -2017-02-17 16:00:00,10739.0 -2017-02-17 17:00:00,10613.0 -2017-02-17 18:00:00,10554.0 -2017-02-17 19:00:00,10874.0 -2017-02-17 20:00:00,11146.0 -2017-02-17 21:00:00,10950.0 -2017-02-17 22:00:00,10679.0 -2017-02-17 23:00:00,10266.0 -2017-02-18 00:00:00,9753.0 -2017-02-16 01:00:00,10129.0 -2017-02-16 02:00:00,9711.0 -2017-02-16 03:00:00,9514.0 -2017-02-16 04:00:00,9398.0 -2017-02-16 05:00:00,9383.0 -2017-02-16 06:00:00,9655.0 -2017-02-16 07:00:00,10324.0 -2017-02-16 08:00:00,11374.0 -2017-02-16 09:00:00,11784.0 -2017-02-16 10:00:00,11969.0 -2017-02-16 11:00:00,11987.0 -2017-02-16 12:00:00,11902.0 -2017-02-16 13:00:00,11773.0 -2017-02-16 14:00:00,11648.0 -2017-02-16 15:00:00,11603.0 -2017-02-16 16:00:00,11419.0 -2017-02-16 17:00:00,11459.0 -2017-02-16 18:00:00,11623.0 -2017-02-16 19:00:00,12122.0 -2017-02-16 20:00:00,12167.0 -2017-02-16 21:00:00,11963.0 -2017-02-16 22:00:00,11721.0 -2017-02-16 23:00:00,11218.0 -2017-02-17 00:00:00,10523.0 -2017-02-15 01:00:00,9848.0 -2017-02-15 02:00:00,9513.0 -2017-02-15 03:00:00,9351.0 -2017-02-15 04:00:00,9218.0 -2017-02-15 05:00:00,9292.0 -2017-02-15 06:00:00,9628.0 -2017-02-15 07:00:00,10440.0 -2017-02-15 08:00:00,11425.0 -2017-02-15 09:00:00,11836.0 -2017-02-15 10:00:00,11854.0 -2017-02-15 11:00:00,11873.0 -2017-02-15 12:00:00,11885.0 -2017-02-15 13:00:00,11840.0 -2017-02-15 14:00:00,11770.0 -2017-02-15 15:00:00,11697.0 -2017-02-15 16:00:00,11540.0 -2017-02-15 17:00:00,11534.0 -2017-02-15 18:00:00,11607.0 -2017-02-15 19:00:00,12227.0 -2017-02-15 20:00:00,12438.0 -2017-02-15 21:00:00,12255.0 -2017-02-15 22:00:00,11971.0 -2017-02-15 23:00:00,11427.0 -2017-02-16 00:00:00,10762.0 -2017-02-14 01:00:00,9992.0 -2017-02-14 02:00:00,9594.0 -2017-02-14 03:00:00,9370.0 -2017-02-14 04:00:00,9264.0 -2017-02-14 05:00:00,9273.0 -2017-02-14 06:00:00,9569.0 -2017-02-14 07:00:00,10296.0 -2017-02-14 08:00:00,11299.0 -2017-02-14 09:00:00,11640.0 -2017-02-14 10:00:00,11645.0 -2017-02-14 11:00:00,11562.0 -2017-02-14 12:00:00,11517.0 -2017-02-14 13:00:00,11387.0 -2017-02-14 14:00:00,11244.0 -2017-02-14 15:00:00,11190.0 -2017-02-14 16:00:00,11023.0 -2017-02-14 17:00:00,10917.0 -2017-02-14 18:00:00,11016.0 -2017-02-14 19:00:00,11609.0 -2017-02-14 20:00:00,11853.0 -2017-02-14 21:00:00,11769.0 -2017-02-14 22:00:00,11514.0 -2017-02-14 23:00:00,11106.0 -2017-02-15 00:00:00,10474.0 -2017-02-13 01:00:00,9572.0 -2017-02-13 02:00:00,9305.0 -2017-02-13 03:00:00,9181.0 -2017-02-13 04:00:00,9066.0 -2017-02-13 05:00:00,9172.0 -2017-02-13 06:00:00,9512.0 -2017-02-13 07:00:00,10343.0 -2017-02-13 08:00:00,11453.0 -2017-02-13 09:00:00,11832.0 -2017-02-13 10:00:00,11818.0 -2017-02-13 11:00:00,11738.0 -2017-02-13 12:00:00,11672.0 -2017-02-13 13:00:00,11580.0 -2017-02-13 14:00:00,11460.0 -2017-02-13 15:00:00,11369.0 -2017-02-13 16:00:00,11226.0 -2017-02-13 17:00:00,11088.0 -2017-02-13 18:00:00,11212.0 -2017-02-13 19:00:00,11836.0 -2017-02-13 20:00:00,12155.0 -2017-02-13 21:00:00,12012.0 -2017-02-13 22:00:00,11773.0 -2017-02-13 23:00:00,11296.0 -2017-02-14 00:00:00,10661.0 -2017-02-12 01:00:00,9431.0 -2017-02-12 02:00:00,9089.0 -2017-02-12 03:00:00,8849.0 -2017-02-12 04:00:00,8729.0 -2017-02-12 05:00:00,8665.0 -2017-02-12 06:00:00,8666.0 -2017-02-12 07:00:00,8836.0 -2017-02-12 08:00:00,9105.0 -2017-02-12 09:00:00,9160.0 -2017-02-12 10:00:00,9319.0 -2017-02-12 11:00:00,9403.0 -2017-02-12 12:00:00,9478.0 -2017-02-12 13:00:00,9559.0 -2017-02-12 14:00:00,9526.0 -2017-02-12 15:00:00,9476.0 -2017-02-12 16:00:00,9444.0 -2017-02-12 17:00:00,9469.0 -2017-02-12 18:00:00,9680.0 -2017-02-12 19:00:00,10460.0 -2017-02-12 20:00:00,10942.0 -2017-02-12 21:00:00,10890.0 -2017-02-12 22:00:00,10744.0 -2017-02-12 23:00:00,10429.0 -2017-02-13 00:00:00,9976.0 -2017-02-11 01:00:00,10065.0 -2017-02-11 02:00:00,9573.0 -2017-02-11 03:00:00,9311.0 -2017-02-11 04:00:00,9134.0 -2017-02-11 05:00:00,9067.0 -2017-02-11 06:00:00,9161.0 -2017-02-11 07:00:00,9365.0 -2017-02-11 08:00:00,9878.0 -2017-02-11 09:00:00,10064.0 -2017-02-11 10:00:00,10378.0 -2017-02-11 11:00:00,10421.0 -2017-02-11 12:00:00,10399.0 -2017-02-11 13:00:00,10312.0 -2017-02-11 14:00:00,10136.0 -2017-02-11 15:00:00,9940.0 -2017-02-11 16:00:00,9875.0 -2017-02-11 17:00:00,9895.0 -2017-02-11 18:00:00,10059.0 -2017-02-11 19:00:00,10647.0 -2017-02-11 20:00:00,10804.0 -2017-02-11 21:00:00,10725.0 -2017-02-11 22:00:00,10544.0 -2017-02-11 23:00:00,10327.0 -2017-02-12 00:00:00,9899.0 -2017-02-10 01:00:00,11200.0 -2017-02-10 02:00:00,10806.0 -2017-02-10 03:00:00,10587.0 -2017-02-10 04:00:00,10455.0 -2017-02-10 05:00:00,10402.0 -2017-02-10 06:00:00,10694.0 -2017-02-10 07:00:00,11349.0 -2017-02-10 08:00:00,12386.0 -2017-02-10 09:00:00,12834.0 -2017-02-10 10:00:00,12854.0 -2017-02-10 11:00:00,12761.0 -2017-02-10 12:00:00,12719.0 -2017-02-10 13:00:00,12522.0 -2017-02-10 14:00:00,12324.0 -2017-02-10 15:00:00,12109.0 -2017-02-10 16:00:00,11853.0 -2017-02-10 17:00:00,11636.0 -2017-02-10 18:00:00,11746.0 -2017-02-10 19:00:00,12302.0 -2017-02-10 20:00:00,12366.0 -2017-02-10 21:00:00,12136.0 -2017-02-10 22:00:00,11827.0 -2017-02-10 23:00:00,11390.0 -2017-02-11 00:00:00,10740.0 -2017-02-09 01:00:00,10965.0 -2017-02-09 02:00:00,10650.0 -2017-02-09 03:00:00,10499.0 -2017-02-09 04:00:00,10415.0 -2017-02-09 05:00:00,10453.0 -2017-02-09 06:00:00,10772.0 -2017-02-09 07:00:00,11557.0 -2017-02-09 08:00:00,12572.0 -2017-02-09 09:00:00,12947.0 -2017-02-09 10:00:00,12970.0 -2017-02-09 11:00:00,12937.0 -2017-02-09 12:00:00,12870.0 -2017-02-09 13:00:00,12771.0 -2017-02-09 14:00:00,12657.0 -2017-02-09 15:00:00,12578.0 -2017-02-09 16:00:00,12400.0 -2017-02-09 17:00:00,12300.0 -2017-02-09 18:00:00,12459.0 -2017-02-09 19:00:00,13145.0 -2017-02-09 20:00:00,13371.0 -2017-02-09 21:00:00,13265.0 -2017-02-09 22:00:00,13043.0 -2017-02-09 23:00:00,12562.0 -2017-02-10 00:00:00,11841.0 -2017-02-08 01:00:00,10052.0 -2017-02-08 02:00:00,9655.0 -2017-02-08 03:00:00,9454.0 -2017-02-08 04:00:00,9337.0 -2017-02-08 05:00:00,9328.0 -2017-02-08 06:00:00,9631.0 -2017-02-08 07:00:00,10418.0 -2017-02-08 08:00:00,11595.0 -2017-02-08 09:00:00,12071.0 -2017-02-08 10:00:00,12233.0 -2017-02-08 11:00:00,12323.0 -2017-02-08 12:00:00,12448.0 -2017-02-08 13:00:00,12502.0 -2017-02-08 14:00:00,12528.0 -2017-02-08 15:00:00,12526.0 -2017-02-08 16:00:00,12405.0 -2017-02-08 17:00:00,12327.0 -2017-02-08 18:00:00,12392.0 -2017-02-08 19:00:00,12879.0 -2017-02-08 20:00:00,13053.0 -2017-02-08 21:00:00,12910.0 -2017-02-08 22:00:00,12663.0 -2017-02-08 23:00:00,12234.0 -2017-02-09 00:00:00,11551.0 -2017-02-07 01:00:00,9561.0 -2017-02-07 02:00:00,9150.0 -2017-02-07 03:00:00,8951.0 -2017-02-07 04:00:00,8825.0 -2017-02-07 05:00:00,8806.0 -2017-02-07 06:00:00,9047.0 -2017-02-07 07:00:00,9781.0 -2017-02-07 08:00:00,10832.0 -2017-02-07 09:00:00,11401.0 -2017-02-07 10:00:00,11484.0 -2017-02-07 11:00:00,11637.0 -2017-02-07 12:00:00,11705.0 -2017-02-07 13:00:00,11607.0 -2017-02-07 14:00:00,11572.0 -2017-02-07 15:00:00,11592.0 -2017-02-07 16:00:00,11571.0 -2017-02-07 17:00:00,11644.0 -2017-02-07 18:00:00,11839.0 -2017-02-07 19:00:00,12311.0 -2017-02-07 20:00:00,12258.0 -2017-02-07 21:00:00,12085.0 -2017-02-07 22:00:00,11864.0 -2017-02-07 23:00:00,11380.0 -2017-02-08 00:00:00,10716.0 -2017-02-06 01:00:00,9754.0 -2017-02-06 02:00:00,9482.0 -2017-02-06 03:00:00,9328.0 -2017-02-06 04:00:00,9298.0 -2017-02-06 05:00:00,9419.0 -2017-02-06 06:00:00,9724.0 -2017-02-06 07:00:00,10501.0 -2017-02-06 08:00:00,11565.0 -2017-02-06 09:00:00,11963.0 -2017-02-06 10:00:00,11963.0 -2017-02-06 11:00:00,11831.0 -2017-02-06 12:00:00,11698.0 -2017-02-06 13:00:00,11512.0 -2017-02-06 14:00:00,11373.0 -2017-02-06 15:00:00,11263.0 -2017-02-06 16:00:00,11112.0 -2017-02-06 17:00:00,11079.0 -2017-02-06 18:00:00,11413.0 -2017-02-06 19:00:00,11995.0 -2017-02-06 20:00:00,11970.0 -2017-02-06 21:00:00,11765.0 -2017-02-06 22:00:00,11449.0 -2017-02-06 23:00:00,10924.0 -2017-02-07 00:00:00,10229.0 -2017-02-05 01:00:00,10256.0 -2017-02-05 02:00:00,9883.0 -2017-02-05 03:00:00,9652.0 -2017-02-05 04:00:00,9422.0 -2017-02-05 05:00:00,9394.0 -2017-02-05 06:00:00,9423.0 -2017-02-05 07:00:00,9555.0 -2017-02-05 08:00:00,9827.0 -2017-02-05 09:00:00,9829.0 -2017-02-05 10:00:00,9904.0 -2017-02-05 11:00:00,9969.0 -2017-02-05 12:00:00,9994.0 -2017-02-05 13:00:00,10016.0 -2017-02-05 14:00:00,9996.0 -2017-02-05 15:00:00,9944.0 -2017-02-05 16:00:00,9835.0 -2017-02-05 17:00:00,9843.0 -2017-02-05 18:00:00,10114.0 -2017-02-05 19:00:00,10829.0 -2017-02-05 20:00:00,11036.0 -2017-02-05 21:00:00,10925.0 -2017-02-05 22:00:00,10821.0 -2017-02-05 23:00:00,10602.0 -2017-02-06 00:00:00,10238.0 -2017-02-04 01:00:00,11167.0 -2017-02-04 02:00:00,10735.0 -2017-02-04 03:00:00,10549.0 -2017-02-04 04:00:00,10364.0 -2017-02-04 05:00:00,10344.0 -2017-02-04 06:00:00,10393.0 -2017-02-04 07:00:00,10753.0 -2017-02-04 08:00:00,11170.0 -2017-02-04 09:00:00,11322.0 -2017-02-04 10:00:00,11535.0 -2017-02-04 11:00:00,11687.0 -2017-02-04 12:00:00,11767.0 -2017-02-04 13:00:00,11726.0 -2017-02-04 14:00:00,11626.0 -2017-02-04 15:00:00,11477.0 -2017-02-04 16:00:00,11344.0 -2017-02-04 17:00:00,11299.0 -2017-02-04 18:00:00,11507.0 -2017-02-04 19:00:00,12003.0 -2017-02-04 20:00:00,11959.0 -2017-02-04 21:00:00,11796.0 -2017-02-04 22:00:00,11543.0 -2017-02-04 23:00:00,11269.0 -2017-02-05 00:00:00,10726.0 -2017-02-03 01:00:00,11305.0 -2017-02-03 02:00:00,10909.0 -2017-02-03 03:00:00,10684.0 -2017-02-03 04:00:00,10575.0 -2017-02-03 05:00:00,10613.0 -2017-02-03 06:00:00,10844.0 -2017-02-03 07:00:00,11552.0 -2017-02-03 08:00:00,12530.0 -2017-02-03 09:00:00,12987.0 -2017-02-03 10:00:00,13018.0 -2017-02-03 11:00:00,12882.0 -2017-02-03 12:00:00,12817.0 -2017-02-03 13:00:00,12658.0 -2017-02-03 14:00:00,12490.0 -2017-02-03 15:00:00,12410.0 -2017-02-03 16:00:00,12227.0 -2017-02-03 17:00:00,12098.0 -2017-02-03 18:00:00,12267.0 -2017-02-03 19:00:00,12926.0 -2017-02-03 20:00:00,13034.0 -2017-02-03 21:00:00,12847.0 -2017-02-03 22:00:00,12640.0 -2017-02-03 23:00:00,12314.0 -2017-02-04 00:00:00,11732.0 -2017-02-02 01:00:00,10880.0 -2017-02-02 02:00:00,10551.0 -2017-02-02 03:00:00,10404.0 -2017-02-02 04:00:00,10355.0 -2017-02-02 05:00:00,10396.0 -2017-02-02 06:00:00,10696.0 -2017-02-02 07:00:00,11401.0 -2017-02-02 08:00:00,12497.0 -2017-02-02 09:00:00,12868.0 -2017-02-02 10:00:00,12884.0 -2017-02-02 11:00:00,12862.0 -2017-02-02 12:00:00,12810.0 -2017-02-02 13:00:00,12707.0 -2017-02-02 14:00:00,12594.0 -2017-02-02 15:00:00,12518.0 -2017-02-02 16:00:00,12460.0 -2017-02-02 17:00:00,12520.0 -2017-02-02 18:00:00,12683.0 -2017-02-02 19:00:00,13361.0 -2017-02-02 20:00:00,13427.0 -2017-02-02 21:00:00,13275.0 -2017-02-02 22:00:00,13065.0 -2017-02-02 23:00:00,12585.0 -2017-02-03 00:00:00,11869.0 -2017-02-01 01:00:00,10366.0 -2017-02-01 02:00:00,9909.0 -2017-02-01 03:00:00,9669.0 -2017-02-01 04:00:00,9510.0 -2017-02-01 05:00:00,9514.0 -2017-02-01 06:00:00,9796.0 -2017-02-01 07:00:00,10513.0 -2017-02-01 08:00:00,11592.0 -2017-02-01 09:00:00,12014.0 -2017-02-01 10:00:00,12092.0 -2017-02-01 11:00:00,12140.0 -2017-02-01 12:00:00,12132.0 -2017-02-01 13:00:00,11985.0 -2017-02-01 14:00:00,11971.0 -2017-02-01 15:00:00,12013.0 -2017-02-01 16:00:00,11990.0 -2017-02-01 17:00:00,11920.0 -2017-02-01 18:00:00,12114.0 -2017-02-01 19:00:00,12760.0 -2017-02-01 20:00:00,12894.0 -2017-02-01 21:00:00,12768.0 -2017-02-01 22:00:00,12499.0 -2017-02-01 23:00:00,12087.0 -2017-02-02 00:00:00,11453.0 -2017-01-31 01:00:00,10529.0 -2017-01-31 02:00:00,10109.0 -2017-01-31 03:00:00,9815.0 -2017-01-31 04:00:00,9748.0 -2017-01-31 05:00:00,9735.0 -2017-01-31 06:00:00,9958.0 -2017-01-31 07:00:00,10645.0 -2017-01-31 08:00:00,11719.0 -2017-01-31 09:00:00,12298.0 -2017-01-31 10:00:00,12382.0 -2017-01-31 11:00:00,12440.0 -2017-01-31 12:00:00,12486.0 -2017-01-31 13:00:00,12490.0 -2017-01-31 14:00:00,12389.0 -2017-01-31 15:00:00,12343.0 -2017-01-31 16:00:00,12086.0 -2017-01-31 17:00:00,12130.0 -2017-01-31 18:00:00,12308.0 -2017-01-31 19:00:00,12785.0 -2017-01-31 20:00:00,12735.0 -2017-01-31 21:00:00,12497.0 -2017-01-31 22:00:00,12224.0 -2017-01-31 23:00:00,11691.0 -2017-02-01 00:00:00,10996.0 -2017-01-30 01:00:00,10393.0 -2017-01-30 02:00:00,10145.0 -2017-01-30 03:00:00,10031.0 -2017-01-30 04:00:00,10003.0 -2017-01-30 05:00:00,10079.0 -2017-01-30 06:00:00,10506.0 -2017-01-30 07:00:00,11263.0 -2017-01-30 08:00:00,12420.0 -2017-01-30 09:00:00,12850.0 -2017-01-30 10:00:00,12889.0 -2017-01-30 11:00:00,12728.0 -2017-01-30 12:00:00,12706.0 -2017-01-30 13:00:00,12640.0 -2017-01-30 14:00:00,12617.0 -2017-01-30 15:00:00,12710.0 -2017-01-30 16:00:00,12678.0 -2017-01-30 17:00:00,12601.0 -2017-01-30 18:00:00,12959.0 -2017-01-30 19:00:00,13457.0 -2017-01-30 20:00:00,13267.0 -2017-01-30 21:00:00,12973.0 -2017-01-30 22:00:00,12631.0 -2017-01-30 23:00:00,12072.0 -2017-01-31 00:00:00,11272.0 -2017-01-29 01:00:00,10278.0 -2017-01-29 02:00:00,9955.0 -2017-01-29 03:00:00,9629.0 -2017-01-29 04:00:00,9460.0 -2017-01-29 05:00:00,9395.0 -2017-01-29 06:00:00,9445.0 -2017-01-29 07:00:00,9527.0 -2017-01-29 08:00:00,9863.0 -2017-01-29 09:00:00,10015.0 -2017-01-29 10:00:00,10276.0 -2017-01-29 11:00:00,10511.0 -2017-01-29 12:00:00,10712.0 -2017-01-29 13:00:00,10774.0 -2017-01-29 14:00:00,10850.0 -2017-01-29 15:00:00,10827.0 -2017-01-29 16:00:00,10906.0 -2017-01-29 17:00:00,10998.0 -2017-01-29 18:00:00,11424.0 -2017-01-29 19:00:00,11967.0 -2017-01-29 20:00:00,11977.0 -2017-01-29 21:00:00,11908.0 -2017-01-29 22:00:00,11678.0 -2017-01-29 23:00:00,11328.0 -2017-01-30 00:00:00,10890.0 -2017-01-28 01:00:00,10962.0 -2017-01-28 02:00:00,10510.0 -2017-01-28 03:00:00,10265.0 -2017-01-28 04:00:00,10058.0 -2017-01-28 05:00:00,9984.0 -2017-01-28 06:00:00,10020.0 -2017-01-28 07:00:00,10248.0 -2017-01-28 08:00:00,10637.0 -2017-01-28 09:00:00,10973.0 -2017-01-28 10:00:00,11264.0 -2017-01-28 11:00:00,11501.0 -2017-01-28 12:00:00,11644.0 -2017-01-28 13:00:00,11638.0 -2017-01-28 14:00:00,11580.0 -2017-01-28 15:00:00,11389.0 -2017-01-28 16:00:00,11300.0 -2017-01-28 17:00:00,11239.0 -2017-01-28 18:00:00,11573.0 -2017-01-28 19:00:00,11958.0 -2017-01-28 20:00:00,11932.0 -2017-01-28 21:00:00,11784.0 -2017-01-28 22:00:00,11585.0 -2017-01-28 23:00:00,11238.0 -2017-01-29 00:00:00,10798.0 -2017-01-27 01:00:00,10437.0 -2017-01-27 02:00:00,10076.0 -2017-01-27 03:00:00,9847.0 -2017-01-27 04:00:00,9766.0 -2017-01-27 05:00:00,9790.0 -2017-01-27 06:00:00,10045.0 -2017-01-27 07:00:00,10740.0 -2017-01-27 08:00:00,11812.0 -2017-01-27 09:00:00,12469.0 -2017-01-27 10:00:00,12610.0 -2017-01-27 11:00:00,12777.0 -2017-01-27 12:00:00,12873.0 -2017-01-27 13:00:00,12851.0 -2017-01-27 14:00:00,12773.0 -2017-01-27 15:00:00,12765.0 -2017-01-27 16:00:00,12749.0 -2017-01-27 17:00:00,12752.0 -2017-01-27 18:00:00,12954.0 -2017-01-27 19:00:00,13351.0 -2017-01-27 20:00:00,13160.0 -2017-01-27 21:00:00,12905.0 -2017-01-27 22:00:00,12627.0 -2017-01-27 23:00:00,12218.0 -2017-01-28 00:00:00,11554.0 -2017-01-26 01:00:00,10307.0 -2017-01-26 02:00:00,9869.0 -2017-01-26 03:00:00,9608.0 -2017-01-26 04:00:00,9505.0 -2017-01-26 05:00:00,9457.0 -2017-01-26 06:00:00,9775.0 -2017-01-26 07:00:00,10536.0 -2017-01-26 08:00:00,11625.0 -2017-01-26 09:00:00,12158.0 -2017-01-26 10:00:00,12290.0 -2017-01-26 11:00:00,12343.0 -2017-01-26 12:00:00,12443.0 -2017-01-26 13:00:00,12451.0 -2017-01-26 14:00:00,12388.0 -2017-01-26 15:00:00,12360.0 -2017-01-26 16:00:00,12287.0 -2017-01-26 17:00:00,12290.0 -2017-01-26 18:00:00,12533.0 -2017-01-26 19:00:00,12973.0 -2017-01-26 20:00:00,12870.0 -2017-01-26 21:00:00,12632.0 -2017-01-26 22:00:00,12356.0 -2017-01-26 23:00:00,11821.0 -2017-01-27 00:00:00,11097.0 -2017-01-25 01:00:00,10165.0 -2017-01-25 02:00:00,9755.0 -2017-01-25 03:00:00,9514.0 -2017-01-25 04:00:00,9441.0 -2017-01-25 05:00:00,9414.0 -2017-01-25 06:00:00,9685.0 -2017-01-25 07:00:00,10381.0 -2017-01-25 08:00:00,11397.0 -2017-01-25 09:00:00,11901.0 -2017-01-25 10:00:00,11961.0 -2017-01-25 11:00:00,12003.0 -2017-01-25 12:00:00,12064.0 -2017-01-25 13:00:00,12060.0 -2017-01-25 14:00:00,12116.0 -2017-01-25 15:00:00,12169.0 -2017-01-25 16:00:00,12172.0 -2017-01-25 17:00:00,12175.0 -2017-01-25 18:00:00,12481.0 -2017-01-25 19:00:00,12794.0 -2017-01-25 20:00:00,12624.0 -2017-01-25 21:00:00,12425.0 -2017-01-25 22:00:00,12131.0 -2017-01-25 23:00:00,11592.0 -2017-01-26 00:00:00,10928.0 -2017-01-24 01:00:00,9845.0 -2017-01-24 02:00:00,9427.0 -2017-01-24 03:00:00,9159.0 -2017-01-24 04:00:00,9058.0 -2017-01-24 05:00:00,9076.0 -2017-01-24 06:00:00,9368.0 -2017-01-24 07:00:00,10043.0 -2017-01-24 08:00:00,11089.0 -2017-01-24 09:00:00,11785.0 -2017-01-24 10:00:00,11928.0 -2017-01-24 11:00:00,11994.0 -2017-01-24 12:00:00,12062.0 -2017-01-24 13:00:00,12090.0 -2017-01-24 14:00:00,12009.0 -2017-01-24 15:00:00,11999.0 -2017-01-24 16:00:00,11954.0 -2017-01-24 17:00:00,11905.0 -2017-01-24 18:00:00,12156.0 -2017-01-24 19:00:00,12640.0 -2017-01-24 20:00:00,12532.0 -2017-01-24 21:00:00,12346.0 -2017-01-24 22:00:00,12023.0 -2017-01-24 23:00:00,11532.0 -2017-01-25 00:00:00,10834.0 -2017-01-23 01:00:00,9561.0 -2017-01-23 02:00:00,8914.0 -2017-01-23 03:00:00,8781.0 -2017-01-23 04:00:00,8687.0 -2017-01-23 05:00:00,8771.0 -2017-01-23 06:00:00,9052.0 -2017-01-23 07:00:00,9872.0 -2017-01-23 08:00:00,11056.0 -2017-01-23 09:00:00,11711.0 -2017-01-23 10:00:00,11737.0 -2017-01-23 11:00:00,11866.0 -2017-01-23 12:00:00,11949.0 -2017-01-23 13:00:00,11891.0 -2017-01-23 14:00:00,11870.0 -2017-01-23 15:00:00,11839.0 -2017-01-23 16:00:00,11691.0 -2017-01-23 17:00:00,11608.0 -2017-01-23 18:00:00,11886.0 -2017-01-23 19:00:00,12371.0 -2017-01-23 20:00:00,12239.0 -2017-01-23 21:00:00,12013.0 -2017-01-23 22:00:00,11720.0 -2017-01-23 23:00:00,11213.0 -2017-01-24 00:00:00,10516.0 -2017-01-22 01:00:00,9009.0 -2017-01-22 02:00:00,8673.0 -2017-01-22 03:00:00,8416.0 -2017-01-22 04:00:00,8272.0 -2017-01-22 05:00:00,8218.0 -2017-01-22 06:00:00,8289.0 -2017-01-22 07:00:00,8401.0 -2017-01-22 08:00:00,8720.0 -2017-01-22 09:00:00,8813.0 -2017-01-22 10:00:00,9079.0 -2017-01-22 11:00:00,9300.0 -2017-01-22 12:00:00,9440.0 -2017-01-22 13:00:00,9610.0 -2017-01-22 14:00:00,9668.0 -2017-01-22 15:00:00,9654.0 -2017-01-22 16:00:00,9678.0 -2017-01-22 17:00:00,9744.0 -2017-01-22 18:00:00,10187.0 -2017-01-22 19:00:00,10840.0 -2017-01-22 20:00:00,10890.0 -2017-01-22 21:00:00,10739.0 -2017-01-22 22:00:00,10567.0 -2017-01-22 23:00:00,10172.0 -2017-01-23 00:00:00,9708.0 -2017-01-21 01:00:00,9855.0 -2017-01-21 02:00:00,9405.0 -2017-01-21 03:00:00,9063.0 -2017-01-21 04:00:00,8905.0 -2017-01-21 05:00:00,8765.0 -2017-01-21 06:00:00,8865.0 -2017-01-21 07:00:00,9080.0 -2017-01-21 08:00:00,9496.0 -2017-01-21 09:00:00,9754.0 -2017-01-21 10:00:00,9869.0 -2017-01-21 11:00:00,10007.0 -2017-01-21 12:00:00,10016.0 -2017-01-21 13:00:00,9909.0 -2017-01-21 14:00:00,9812.0 -2017-01-21 15:00:00,9635.0 -2017-01-21 16:00:00,9483.0 -2017-01-21 17:00:00,9414.0 -2017-01-21 18:00:00,9613.0 -2017-01-21 19:00:00,10302.0 -2017-01-21 20:00:00,10347.0 -2017-01-21 21:00:00,10246.0 -2017-01-21 22:00:00,10098.0 -2017-01-21 23:00:00,9858.0 -2017-01-22 00:00:00,9477.0 -2017-01-20 01:00:00,10184.0 -2017-01-20 02:00:00,9768.0 -2017-01-20 03:00:00,9509.0 -2017-01-20 04:00:00,9378.0 -2017-01-20 05:00:00,9351.0 -2017-01-20 06:00:00,9593.0 -2017-01-20 07:00:00,10230.0 -2017-01-20 08:00:00,11236.0 -2017-01-20 09:00:00,11862.0 -2017-01-20 10:00:00,11950.0 -2017-01-20 11:00:00,12040.0 -2017-01-20 12:00:00,12035.0 -2017-01-20 13:00:00,11980.0 -2017-01-20 14:00:00,11909.0 -2017-01-20 15:00:00,11831.0 -2017-01-20 16:00:00,11765.0 -2017-01-20 17:00:00,11739.0 -2017-01-20 18:00:00,12033.0 -2017-01-20 19:00:00,12342.0 -2017-01-20 20:00:00,12133.0 -2017-01-20 21:00:00,11904.0 -2017-01-20 22:00:00,11508.0 -2017-01-20 23:00:00,11168.0 -2017-01-21 00:00:00,10509.0 -2017-01-19 01:00:00,10470.0 -2017-01-19 02:00:00,10030.0 -2017-01-19 03:00:00,9780.0 -2017-01-19 04:00:00,9599.0 -2017-01-19 05:00:00,9562.0 -2017-01-19 06:00:00,9780.0 -2017-01-19 07:00:00,10479.0 -2017-01-19 08:00:00,11525.0 -2017-01-19 09:00:00,12060.0 -2017-01-19 10:00:00,12089.0 -2017-01-19 11:00:00,12105.0 -2017-01-19 12:00:00,12145.0 -2017-01-19 13:00:00,12083.0 -2017-01-19 14:00:00,11966.0 -2017-01-19 15:00:00,11976.0 -2017-01-19 16:00:00,11917.0 -2017-01-19 17:00:00,12011.0 -2017-01-19 18:00:00,12393.0 -2017-01-19 19:00:00,12756.0 -2017-01-19 20:00:00,12559.0 -2017-01-19 21:00:00,12337.0 -2017-01-19 22:00:00,12115.0 -2017-01-19 23:00:00,11580.0 -2017-01-20 00:00:00,10827.0 -2017-01-18 01:00:00,10305.0 -2017-01-18 02:00:00,9865.0 -2017-01-18 03:00:00,9649.0 -2017-01-18 04:00:00,9480.0 -2017-01-18 05:00:00,9498.0 -2017-01-18 06:00:00,9774.0 -2017-01-18 07:00:00,10467.0 -2017-01-18 08:00:00,11521.0 -2017-01-18 09:00:00,12156.0 -2017-01-18 10:00:00,12199.0 -2017-01-18 11:00:00,12243.0 -2017-01-18 12:00:00,12285.0 -2017-01-18 13:00:00,12330.0 -2017-01-18 14:00:00,12339.0 -2017-01-18 15:00:00,12341.0 -2017-01-18 16:00:00,12159.0 -2017-01-18 17:00:00,12045.0 -2017-01-18 18:00:00,12391.0 -2017-01-18 19:00:00,12910.0 -2017-01-18 20:00:00,12731.0 -2017-01-18 21:00:00,12571.0 -2017-01-18 22:00:00,12273.0 -2017-01-18 23:00:00,11775.0 -2017-01-19 00:00:00,11087.0 -2017-01-17 01:00:00,10210.0 -2017-01-17 02:00:00,9802.0 -2017-01-17 03:00:00,9587.0 -2017-01-17 04:00:00,9394.0 -2017-01-17 05:00:00,9281.0 -2017-01-17 06:00:00,9548.0 -2017-01-17 07:00:00,10273.0 -2017-01-17 08:00:00,11376.0 -2017-01-17 09:00:00,12004.0 -2017-01-17 10:00:00,12021.0 -2017-01-17 11:00:00,12110.0 -2017-01-17 12:00:00,12193.0 -2017-01-17 13:00:00,12156.0 -2017-01-17 14:00:00,12161.0 -2017-01-17 15:00:00,12151.0 -2017-01-17 16:00:00,12159.0 -2017-01-17 17:00:00,12094.0 -2017-01-17 18:00:00,12526.0 -2017-01-17 19:00:00,12920.0 -2017-01-17 20:00:00,12684.0 -2017-01-17 21:00:00,12505.0 -2017-01-17 22:00:00,12180.0 -2017-01-17 23:00:00,11697.0 -2017-01-18 00:00:00,10990.0 -2017-01-16 01:00:00,10021.0 -2017-01-16 02:00:00,9678.0 -2017-01-16 03:00:00,9486.0 -2017-01-16 04:00:00,9408.0 -2017-01-16 05:00:00,9421.0 -2017-01-16 06:00:00,9619.0 -2017-01-16 07:00:00,10236.0 -2017-01-16 08:00:00,11083.0 -2017-01-16 09:00:00,11646.0 -2017-01-16 10:00:00,11891.0 -2017-01-16 11:00:00,12113.0 -2017-01-16 12:00:00,12316.0 -2017-01-16 13:00:00,12417.0 -2017-01-16 14:00:00,12399.0 -2017-01-16 15:00:00,12479.0 -2017-01-16 16:00:00,12487.0 -2017-01-16 17:00:00,12417.0 -2017-01-16 18:00:00,12774.0 -2017-01-16 19:00:00,12984.0 -2017-01-16 20:00:00,12738.0 -2017-01-16 21:00:00,12507.0 -2017-01-16 22:00:00,12160.0 -2017-01-16 23:00:00,11572.0 -2017-01-17 00:00:00,10888.0 -2017-01-15 01:00:00,10395.0 -2017-01-15 02:00:00,10046.0 -2017-01-15 03:00:00,9849.0 -2017-01-15 04:00:00,9709.0 -2017-01-15 05:00:00,9670.0 -2017-01-15 06:00:00,9713.0 -2017-01-15 07:00:00,9865.0 -2017-01-15 08:00:00,10168.0 -2017-01-15 09:00:00,10271.0 -2017-01-15 10:00:00,10407.0 -2017-01-15 11:00:00,10672.0 -2017-01-15 12:00:00,10700.0 -2017-01-15 13:00:00,10614.0 -2017-01-15 14:00:00,10520.0 -2017-01-15 15:00:00,10363.0 -2017-01-15 16:00:00,10312.0 -2017-01-15 17:00:00,10404.0 -2017-01-15 18:00:00,10905.0 -2017-01-15 19:00:00,11554.0 -2017-01-15 20:00:00,11526.0 -2017-01-15 21:00:00,11418.0 -2017-01-15 22:00:00,11212.0 -2017-01-15 23:00:00,10930.0 -2017-01-16 00:00:00,10492.0 -2017-01-14 01:00:00,11074.0 -2017-01-14 02:00:00,10558.0 -2017-01-14 03:00:00,10296.0 -2017-01-14 04:00:00,10081.0 -2017-01-14 05:00:00,9986.0 -2017-01-14 06:00:00,10059.0 -2017-01-14 07:00:00,10302.0 -2017-01-14 08:00:00,10748.0 -2017-01-14 09:00:00,11014.0 -2017-01-14 10:00:00,11229.0 -2017-01-14 11:00:00,11439.0 -2017-01-14 12:00:00,11563.0 -2017-01-14 13:00:00,11472.0 -2017-01-14 14:00:00,11304.0 -2017-01-14 15:00:00,11049.0 -2017-01-14 16:00:00,10929.0 -2017-01-14 17:00:00,10851.0 -2017-01-14 18:00:00,11127.0 -2017-01-14 19:00:00,11825.0 -2017-01-14 20:00:00,11791.0 -2017-01-14 21:00:00,11711.0 -2017-01-14 22:00:00,11532.0 -2017-01-14 23:00:00,11328.0 -2017-01-15 00:00:00,10863.0 -2017-01-13 01:00:00,11321.0 -2017-01-13 02:00:00,10937.0 -2017-01-13 03:00:00,10715.0 -2017-01-13 04:00:00,10576.0 -2017-01-13 05:00:00,10571.0 -2017-01-13 06:00:00,10842.0 -2017-01-13 07:00:00,11506.0 -2017-01-13 08:00:00,12537.0 -2017-01-13 09:00:00,13074.0 -2017-01-13 10:00:00,13123.0 -2017-01-13 11:00:00,13179.0 -2017-01-13 12:00:00,13243.0 -2017-01-13 13:00:00,13175.0 -2017-01-13 14:00:00,13118.0 -2017-01-13 15:00:00,13077.0 -2017-01-13 16:00:00,12977.0 -2017-01-13 17:00:00,12916.0 -2017-01-13 18:00:00,13278.0 -2017-01-13 19:00:00,13613.0 -2017-01-13 20:00:00,13402.0 -2017-01-13 21:00:00,13127.0 -2017-01-13 22:00:00,12805.0 -2017-01-13 23:00:00,12350.0 -2017-01-14 00:00:00,11693.0 -2017-01-12 01:00:00,10671.0 -2017-01-12 02:00:00,10285.0 -2017-01-12 03:00:00,10092.0 -2017-01-12 04:00:00,9997.0 -2017-01-12 05:00:00,10025.0 -2017-01-12 06:00:00,10327.0 -2017-01-12 07:00:00,11149.0 -2017-01-12 08:00:00,12266.0 -2017-01-12 09:00:00,12968.0 -2017-01-12 10:00:00,13124.0 -2017-01-12 11:00:00,13185.0 -2017-01-12 12:00:00,13288.0 -2017-01-12 13:00:00,13232.0 -2017-01-12 14:00:00,13219.0 -2017-01-12 15:00:00,13204.0 -2017-01-12 16:00:00,13109.0 -2017-01-12 17:00:00,13001.0 -2017-01-12 18:00:00,13288.0 -2017-01-12 19:00:00,13859.0 -2017-01-12 20:00:00,13726.0 -2017-01-12 21:00:00,13497.0 -2017-01-12 22:00:00,13248.0 -2017-01-12 23:00:00,12767.0 -2017-01-13 00:00:00,12049.0 -2017-01-11 01:00:00,11036.0 -2017-01-11 02:00:00,10570.0 -2017-01-11 03:00:00,10322.0 -2017-01-11 04:00:00,10178.0 -2017-01-11 05:00:00,10128.0 -2017-01-11 06:00:00,10353.0 -2017-01-11 07:00:00,11075.0 -2017-01-11 08:00:00,12157.0 -2017-01-11 09:00:00,12769.0 -2017-01-11 10:00:00,12793.0 -2017-01-11 11:00:00,12735.0 -2017-01-11 12:00:00,12772.0 -2017-01-11 13:00:00,12734.0 -2017-01-11 14:00:00,12620.0 -2017-01-11 15:00:00,12585.0 -2017-01-11 16:00:00,12410.0 -2017-01-11 17:00:00,12399.0 -2017-01-11 18:00:00,12789.0 -2017-01-11 19:00:00,13012.0 -2017-01-11 20:00:00,12946.0 -2017-01-11 21:00:00,12807.0 -2017-01-11 22:00:00,12540.0 -2017-01-11 23:00:00,12002.0 -2017-01-12 00:00:00,11348.0 -2017-01-10 01:00:00,11132.0 -2017-01-10 02:00:00,10601.0 -2017-01-10 03:00:00,10310.0 -2017-01-10 04:00:00,10134.0 -2017-01-10 05:00:00,10113.0 -2017-01-10 06:00:00,10274.0 -2017-01-10 07:00:00,10973.0 -2017-01-10 08:00:00,11939.0 -2017-01-10 09:00:00,12682.0 -2017-01-10 10:00:00,12794.0 -2017-01-10 11:00:00,12836.0 -2017-01-10 12:00:00,12934.0 -2017-01-10 13:00:00,12844.0 -2017-01-10 14:00:00,12770.0 -2017-01-10 15:00:00,12700.0 -2017-01-10 16:00:00,12563.0 -2017-01-10 17:00:00,12483.0 -2017-01-10 18:00:00,12927.0 -2017-01-10 19:00:00,13310.0 -2017-01-10 20:00:00,13204.0 -2017-01-10 21:00:00,13148.0 -2017-01-10 22:00:00,12885.0 -2017-01-10 23:00:00,12445.0 -2017-01-11 00:00:00,11776.0 -2017-01-09 01:00:00,11518.0 -2017-01-09 02:00:00,11116.0 -2017-01-09 03:00:00,10919.0 -2017-01-09 04:00:00,10798.0 -2017-01-09 05:00:00,10807.0 -2017-01-09 06:00:00,10991.0 -2017-01-09 07:00:00,11609.0 -2017-01-09 08:00:00,12668.0 -2017-01-09 09:00:00,13214.0 -2017-01-09 10:00:00,13247.0 -2017-01-09 11:00:00,13248.0 -2017-01-09 12:00:00,13255.0 -2017-01-09 13:00:00,13219.0 -2017-01-09 14:00:00,13205.0 -2017-01-09 15:00:00,13090.0 -2017-01-09 16:00:00,13061.0 -2017-01-09 17:00:00,13144.0 -2017-01-09 18:00:00,13638.0 -2017-01-09 19:00:00,13973.0 -2017-01-09 20:00:00,13804.0 -2017-01-09 21:00:00,13622.0 -2017-01-09 22:00:00,13216.0 -2017-01-09 23:00:00,12602.0 -2017-01-10 00:00:00,11888.0 -2017-01-08 01:00:00,12029.0 -2017-01-08 02:00:00,11636.0 -2017-01-08 03:00:00,11406.0 -2017-01-08 04:00:00,11233.0 -2017-01-08 05:00:00,11190.0 -2017-01-08 06:00:00,11215.0 -2017-01-08 07:00:00,11396.0 -2017-01-08 08:00:00,11683.0 -2017-01-08 09:00:00,11763.0 -2017-01-08 10:00:00,11804.0 -2017-01-08 11:00:00,11879.0 -2017-01-08 12:00:00,11871.0 -2017-01-08 13:00:00,11834.0 -2017-01-08 14:00:00,11777.0 -2017-01-08 15:00:00,11664.0 -2017-01-08 16:00:00,11690.0 -2017-01-08 17:00:00,11829.0 -2017-01-08 18:00:00,12509.0 -2017-01-08 19:00:00,13243.0 -2017-01-08 20:00:00,13335.0 -2017-01-08 21:00:00,13181.0 -2017-01-08 22:00:00,13051.0 -2017-01-08 23:00:00,12651.0 -2017-01-09 00:00:00,12044.0 -2017-01-07 01:00:00,12667.0 -2017-01-07 02:00:00,12204.0 -2017-01-07 03:00:00,11951.0 -2017-01-07 04:00:00,11724.0 -2017-01-07 05:00:00,11679.0 -2017-01-07 06:00:00,11716.0 -2017-01-07 07:00:00,12017.0 -2017-01-07 08:00:00,12348.0 -2017-01-07 09:00:00,12573.0 -2017-01-07 10:00:00,12664.0 -2017-01-07 11:00:00,12742.0 -2017-01-07 12:00:00,12755.0 -2017-01-07 13:00:00,12618.0 -2017-01-07 14:00:00,12501.0 -2017-01-07 15:00:00,12304.0 -2017-01-07 16:00:00,12147.0 -2017-01-07 17:00:00,12100.0 -2017-01-07 18:00:00,12615.0 -2017-01-07 19:00:00,13377.0 -2017-01-07 20:00:00,13458.0 -2017-01-07 21:00:00,13358.0 -2017-01-07 22:00:00,13187.0 -2017-01-07 23:00:00,12970.0 -2017-01-08 00:00:00,12551.0 -2017-01-06 01:00:00,12485.0 -2017-01-06 02:00:00,12072.0 -2017-01-06 03:00:00,11820.0 -2017-01-06 04:00:00,11666.0 -2017-01-06 05:00:00,11646.0 -2017-01-06 06:00:00,11903.0 -2017-01-06 07:00:00,12518.0 -2017-01-06 08:00:00,13386.0 -2017-01-06 09:00:00,13946.0 -2017-01-06 10:00:00,13981.0 -2017-01-06 11:00:00,13997.0 -2017-01-06 12:00:00,14022.0 -2017-01-06 13:00:00,13967.0 -2017-01-06 14:00:00,13894.0 -2017-01-06 15:00:00,13851.0 -2017-01-06 16:00:00,13729.0 -2017-01-06 17:00:00,13643.0 -2017-01-06 18:00:00,14064.0 -2017-01-06 19:00:00,14730.0 -2017-01-06 20:00:00,14675.0 -2017-01-06 21:00:00,14490.0 -2017-01-06 22:00:00,14224.0 -2017-01-06 23:00:00,13866.0 -2017-01-07 00:00:00,13231.0 -2017-01-05 01:00:00,12018.0 -2017-01-05 02:00:00,11622.0 -2017-01-05 03:00:00,11363.0 -2017-01-05 04:00:00,11281.0 -2017-01-05 05:00:00,11264.0 -2017-01-05 06:00:00,11527.0 -2017-01-05 07:00:00,12109.0 -2017-01-05 08:00:00,12983.0 -2017-01-05 09:00:00,13550.0 -2017-01-05 10:00:00,13682.0 -2017-01-05 11:00:00,13650.0 -2017-01-05 12:00:00,13683.0 -2017-01-05 13:00:00,13707.0 -2017-01-05 14:00:00,13777.0 -2017-01-05 15:00:00,13772.0 -2017-01-05 16:00:00,13636.0 -2017-01-05 17:00:00,13597.0 -2017-01-05 18:00:00,14110.0 -2017-01-05 19:00:00,14681.0 -2017-01-05 20:00:00,14580.0 -2017-01-05 21:00:00,14455.0 -2017-01-05 22:00:00,14209.0 -2017-01-05 23:00:00,13787.0 -2017-01-06 00:00:00,13111.0 -2017-01-04 01:00:00,11005.0 -2017-01-04 02:00:00,10672.0 -2017-01-04 03:00:00,10492.0 -2017-01-04 04:00:00,10502.0 -2017-01-04 05:00:00,10568.0 -2017-01-04 06:00:00,10854.0 -2017-01-04 07:00:00,11517.0 -2017-01-04 08:00:00,12547.0 -2017-01-04 09:00:00,13031.0 -2017-01-04 10:00:00,13065.0 -2017-01-04 11:00:00,13163.0 -2017-01-04 12:00:00,13207.0 -2017-01-04 13:00:00,13161.0 -2017-01-04 14:00:00,13105.0 -2017-01-04 15:00:00,13046.0 -2017-01-04 16:00:00,12954.0 -2017-01-04 17:00:00,12966.0 -2017-01-04 18:00:00,13631.0 -2017-01-04 19:00:00,14300.0 -2017-01-04 20:00:00,14197.0 -2017-01-04 21:00:00,14028.0 -2017-01-04 22:00:00,13801.0 -2017-01-04 23:00:00,13330.0 -2017-01-05 00:00:00,12635.0 -2017-01-03 01:00:00,9519.0 -2017-01-03 02:00:00,9133.0 -2017-01-03 03:00:00,8918.0 -2017-01-03 04:00:00,8810.0 -2017-01-03 05:00:00,8832.0 -2017-01-03 06:00:00,9080.0 -2017-01-03 07:00:00,9796.0 -2017-01-03 08:00:00,10746.0 -2017-01-03 09:00:00,11421.0 -2017-01-03 10:00:00,11591.0 -2017-01-03 11:00:00,11744.0 -2017-01-03 12:00:00,11957.0 -2017-01-03 13:00:00,12074.0 -2017-01-03 14:00:00,12203.0 -2017-01-03 15:00:00,12206.0 -2017-01-03 16:00:00,12182.0 -2017-01-03 17:00:00,12143.0 -2017-01-03 18:00:00,12640.0 -2017-01-03 19:00:00,13032.0 -2017-01-03 20:00:00,12830.0 -2017-01-03 21:00:00,12672.0 -2017-01-03 22:00:00,12413.0 -2017-01-03 23:00:00,12071.0 -2017-01-04 00:00:00,11484.0 -2017-01-02 01:00:00,9678.0 -2017-01-02 02:00:00,9296.0 -2017-01-02 03:00:00,9076.0 -2017-01-02 04:00:00,8995.0 -2017-01-02 05:00:00,8960.0 -2017-01-02 06:00:00,9119.0 -2017-01-02 07:00:00,9399.0 -2017-01-02 08:00:00,9747.0 -2017-01-02 09:00:00,9866.0 -2017-01-02 10:00:00,9978.0 -2017-01-02 11:00:00,10222.0 -2017-01-02 12:00:00,10367.0 -2017-01-02 13:00:00,10458.0 -2017-01-02 14:00:00,10503.0 -2017-01-02 15:00:00,10629.0 -2017-01-02 16:00:00,10674.0 -2017-01-02 17:00:00,10878.0 -2017-01-02 18:00:00,11381.0 -2017-01-02 19:00:00,11686.0 -2017-01-02 20:00:00,11508.0 -2017-01-02 21:00:00,11355.0 -2017-01-02 22:00:00,11070.0 -2017-01-02 23:00:00,10666.0 -2017-01-03 00:00:00,10106.0 -2017-01-01 01:00:00,10197.0 -2017-01-01 02:00:00,9893.0 -2017-01-01 03:00:00,9654.0 -2017-01-01 04:00:00,9437.0 -2017-01-01 05:00:00,9370.0 -2017-01-01 06:00:00,9322.0 -2017-01-01 07:00:00,9459.0 -2017-01-01 08:00:00,9602.0 -2017-01-01 09:00:00,9587.0 -2017-01-01 10:00:00,9530.0 -2017-01-01 11:00:00,9513.0 -2017-01-01 12:00:00,9477.0 -2017-01-01 13:00:00,9511.0 -2017-01-01 14:00:00,9459.0 -2017-01-01 15:00:00,9367.0 -2017-01-01 16:00:00,9328.0 -2017-01-01 17:00:00,9428.0 -2017-01-01 18:00:00,10134.0 -2017-01-01 19:00:00,10851.0 -2017-01-01 20:00:00,10882.0 -2017-01-01 21:00:00,10831.0 -2017-01-01 22:00:00,10691.0 -2017-01-01 23:00:00,10484.0 -2017-01-02 00:00:00,10114.0 -2018-08-02 01:00:00,11916.0 -2018-08-02 02:00:00,11095.0 -2018-08-02 03:00:00,10530.0 -2018-08-02 04:00:00,10165.0 -2018-08-02 05:00:00,9931.0 -2018-08-02 06:00:00,9996.0 -2018-08-02 07:00:00,10482.0 -2018-08-02 08:00:00,11200.0 -2018-08-02 09:00:00,12179.0 -2018-08-02 10:00:00,13042.0 -2018-08-02 11:00:00,13828.0 -2018-08-02 12:00:00,14790.0 -2018-08-02 13:00:00,15527.0 -2018-08-02 14:00:00,16074.0 -2018-08-02 15:00:00,16584.0 -2018-08-02 16:00:00,16869.0 -2018-08-02 17:00:00,17015.0 -2018-08-02 18:00:00,17068.0 -2018-08-02 19:00:00,16897.0 -2018-08-02 20:00:00,16437.0 -2018-08-02 21:00:00,15590.0 -2018-08-02 22:00:00,15086.0 -2018-08-02 23:00:00,14448.0 -2018-08-03 00:00:00,13335.0 -2018-08-01 01:00:00,10975.0 -2018-08-01 02:00:00,10233.0 -2018-08-01 03:00:00,9734.0 -2018-08-01 04:00:00,9374.0 -2018-08-01 05:00:00,9156.0 -2018-08-01 06:00:00,9270.0 -2018-08-01 07:00:00,9753.0 -2018-08-01 08:00:00,10466.0 -2018-08-01 09:00:00,11424.0 -2018-08-01 10:00:00,12294.0 -2018-08-01 11:00:00,13025.0 -2018-08-01 12:00:00,13814.0 -2018-08-01 13:00:00,14516.0 -2018-08-01 14:00:00,15203.0 -2018-08-01 15:00:00,15881.0 -2018-08-01 16:00:00,16123.0 -2018-08-01 17:00:00,15766.0 -2018-08-01 18:00:00,15574.0 -2018-08-01 19:00:00,15569.0 -2018-08-01 20:00:00,15122.0 -2018-08-01 21:00:00,14449.0 -2018-08-01 22:00:00,14246.0 -2018-08-01 23:00:00,13778.0 -2018-08-02 00:00:00,12892.0 -2018-07-31 01:00:00,11206.0 -2018-07-31 02:00:00,10442.0 -2018-07-31 03:00:00,9918.0 -2018-07-31 04:00:00,9548.0 -2018-07-31 05:00:00,9391.0 -2018-07-31 06:00:00,9511.0 -2018-07-31 07:00:00,10119.0 -2018-07-31 08:00:00,10896.0 -2018-07-31 09:00:00,11737.0 -2018-07-31 10:00:00,12272.0 -2018-07-31 11:00:00,12716.0 -2018-07-31 12:00:00,13366.0 -2018-07-31 13:00:00,13826.0 -2018-07-31 14:00:00,14103.0 -2018-07-31 15:00:00,14435.0 -2018-07-31 16:00:00,14484.0 -2018-07-31 17:00:00,14501.0 -2018-07-31 18:00:00,14242.0 -2018-07-31 19:00:00,13921.0 -2018-07-31 20:00:00,13532.0 -2018-07-31 21:00:00,13300.0 -2018-07-31 22:00:00,13346.0 -2018-07-31 23:00:00,12898.0 -2018-08-01 00:00:00,11945.0 -2018-07-30 01:00:00,10223.0 -2018-07-30 02:00:00,9632.0 -2018-07-30 03:00:00,9140.0 -2018-07-30 04:00:00,8872.0 -2018-07-30 05:00:00,8700.0 -2018-07-30 06:00:00,8921.0 -2018-07-30 07:00:00,9472.0 -2018-07-30 08:00:00,10201.0 -2018-07-30 09:00:00,11180.0 -2018-07-30 10:00:00,12085.0 -2018-07-30 11:00:00,12792.0 -2018-07-30 12:00:00,13417.0 -2018-07-30 13:00:00,13960.0 -2018-07-30 14:00:00,14386.0 -2018-07-30 15:00:00,14900.0 -2018-07-30 16:00:00,15242.0 -2018-07-30 17:00:00,15414.0 -2018-07-30 18:00:00,15532.0 -2018-07-30 19:00:00,15434.0 -2018-07-30 20:00:00,14930.0 -2018-07-30 21:00:00,14168.0 -2018-07-30 22:00:00,13735.0 -2018-07-30 23:00:00,13194.0 -2018-07-31 00:00:00,12223.0 -2018-07-29 01:00:00,10031.0 -2018-07-29 02:00:00,9400.0 -2018-07-29 03:00:00,8950.0 -2018-07-29 04:00:00,8587.0 -2018-07-29 05:00:00,8325.0 -2018-07-29 06:00:00,8255.0 -2018-07-29 07:00:00,8205.0 -2018-07-29 08:00:00,8172.0 -2018-07-29 09:00:00,8629.0 -2018-07-29 10:00:00,9353.0 -2018-07-29 11:00:00,10143.0 -2018-07-29 12:00:00,10853.0 -2018-07-29 13:00:00,11423.0 -2018-07-29 14:00:00,11856.0 -2018-07-29 15:00:00,12252.0 -2018-07-29 16:00:00,12384.0 -2018-07-29 17:00:00,12520.0 -2018-07-29 18:00:00,12455.0 -2018-07-29 19:00:00,12448.0 -2018-07-29 20:00:00,12186.0 -2018-07-29 21:00:00,11845.0 -2018-07-29 22:00:00,11809.0 -2018-07-29 23:00:00,11569.0 -2018-07-30 00:00:00,10989.0 -2018-07-28 01:00:00,10501.0 -2018-07-28 02:00:00,9727.0 -2018-07-28 03:00:00,9251.0 -2018-07-28 04:00:00,8865.0 -2018-07-28 05:00:00,8644.0 -2018-07-28 06:00:00,8606.0 -2018-07-28 07:00:00,8672.0 -2018-07-28 08:00:00,8834.0 -2018-07-28 09:00:00,9375.0 -2018-07-28 10:00:00,10152.0 -2018-07-28 11:00:00,10872.0 -2018-07-28 12:00:00,11418.0 -2018-07-28 13:00:00,11796.0 -2018-07-28 14:00:00,12111.0 -2018-07-28 15:00:00,12382.0 -2018-07-28 16:00:00,12520.0 -2018-07-28 17:00:00,12665.0 -2018-07-28 18:00:00,12546.0 -2018-07-28 19:00:00,12369.0 -2018-07-28 20:00:00,12035.0 -2018-07-28 21:00:00,11714.0 -2018-07-28 22:00:00,11591.0 -2018-07-28 23:00:00,11384.0 -2018-07-29 00:00:00,10728.0 -2018-07-27 01:00:00,11143.0 -2018-07-27 02:00:00,10307.0 -2018-07-27 03:00:00,9719.0 -2018-07-27 04:00:00,9325.0 -2018-07-27 05:00:00,9142.0 -2018-07-27 06:00:00,9167.0 -2018-07-27 07:00:00,9610.0 -2018-07-27 08:00:00,10224.0 -2018-07-27 09:00:00,11121.0 -2018-07-27 10:00:00,11791.0 -2018-07-27 11:00:00,12345.0 -2018-07-27 12:00:00,12845.0 -2018-07-27 13:00:00,13219.0 -2018-07-27 14:00:00,13483.0 -2018-07-27 15:00:00,13674.0 -2018-07-27 16:00:00,13781.0 -2018-07-27 17:00:00,13827.0 -2018-07-27 18:00:00,13799.0 -2018-07-27 19:00:00,13586.0 -2018-07-27 20:00:00,13179.0 -2018-07-27 21:00:00,12672.0 -2018-07-27 22:00:00,12502.0 -2018-07-27 23:00:00,12155.0 -2018-07-28 00:00:00,11383.0 -2018-07-26 01:00:00,13054.0 -2018-07-26 02:00:00,12096.0 -2018-07-26 03:00:00,11432.0 -2018-07-26 04:00:00,10929.0 -2018-07-26 05:00:00,10600.0 -2018-07-26 06:00:00,10645.0 -2018-07-26 07:00:00,11085.0 -2018-07-26 08:00:00,11769.0 -2018-07-26 09:00:00,12686.0 -2018-07-26 10:00:00,13498.0 -2018-07-26 11:00:00,14258.0 -2018-07-26 12:00:00,14902.0 -2018-07-26 13:00:00,15314.0 -2018-07-26 14:00:00,15585.0 -2018-07-26 15:00:00,15810.0 -2018-07-26 16:00:00,15942.0 -2018-07-26 17:00:00,15924.0 -2018-07-26 18:00:00,15715.0 -2018-07-26 19:00:00,15244.0 -2018-07-26 20:00:00,14579.0 -2018-07-26 21:00:00,13945.0 -2018-07-26 22:00:00,13626.0 -2018-07-26 23:00:00,13174.0 -2018-07-27 00:00:00,12206.0 -2018-07-25 01:00:00,12425.0 -2018-07-25 02:00:00,11506.0 -2018-07-25 03:00:00,10763.0 -2018-07-25 04:00:00,10251.0 -2018-07-25 05:00:00,9907.0 -2018-07-25 06:00:00,9939.0 -2018-07-25 07:00:00,10312.0 -2018-07-25 08:00:00,11117.0 -2018-07-25 09:00:00,12191.0 -2018-07-25 10:00:00,13179.0 -2018-07-25 11:00:00,14081.0 -2018-07-25 12:00:00,15008.0 -2018-07-25 13:00:00,15677.0 -2018-07-25 14:00:00,16225.0 -2018-07-25 15:00:00,16943.0 -2018-07-25 16:00:00,17465.0 -2018-07-25 17:00:00,17846.0 -2018-07-25 18:00:00,18050.0 -2018-07-25 19:00:00,17927.0 -2018-07-25 20:00:00,17313.0 -2018-07-25 21:00:00,16532.0 -2018-07-25 22:00:00,15997.0 -2018-07-25 23:00:00,15321.0 -2018-07-26 00:00:00,14185.0 -2018-07-24 01:00:00,11311.0 -2018-07-24 02:00:00,10550.0 -2018-07-24 03:00:00,9981.0 -2018-07-24 04:00:00,9576.0 -2018-07-24 05:00:00,9428.0 -2018-07-24 06:00:00,9538.0 -2018-07-24 07:00:00,10024.0 -2018-07-24 08:00:00,10798.0 -2018-07-24 09:00:00,11869.0 -2018-07-24 10:00:00,12803.0 -2018-07-24 11:00:00,13679.0 -2018-07-24 12:00:00,14676.0 -2018-07-24 13:00:00,15478.0 -2018-07-24 14:00:00,16100.0 -2018-07-24 15:00:00,16671.0 -2018-07-24 16:00:00,17067.0 -2018-07-24 17:00:00,17317.0 -2018-07-24 18:00:00,17371.0 -2018-07-24 19:00:00,17194.0 -2018-07-24 20:00:00,16678.0 -2018-07-24 21:00:00,15973.0 -2018-07-24 22:00:00,15443.0 -2018-07-24 23:00:00,14873.0 -2018-07-25 00:00:00,13701.0 -2018-07-23 01:00:00,10092.0 -2018-07-23 02:00:00,9581.0 -2018-07-23 03:00:00,9196.0 -2018-07-23 04:00:00,8962.0 -2018-07-23 05:00:00,8901.0 -2018-07-23 06:00:00,9085.0 -2018-07-23 07:00:00,9635.0 -2018-07-23 08:00:00,10393.0 -2018-07-23 09:00:00,11480.0 -2018-07-23 10:00:00,12309.0 -2018-07-23 11:00:00,13075.0 -2018-07-23 12:00:00,13802.0 -2018-07-23 13:00:00,14405.0 -2018-07-23 14:00:00,15004.0 -2018-07-23 15:00:00,15405.0 -2018-07-23 16:00:00,15542.0 -2018-07-23 17:00:00,15439.0 -2018-07-23 18:00:00,15217.0 -2018-07-23 19:00:00,14927.0 -2018-07-23 20:00:00,14490.0 -2018-07-23 21:00:00,13991.0 -2018-07-23 22:00:00,13725.0 -2018-07-23 23:00:00,13347.0 -2018-07-24 00:00:00,12381.0 -2018-07-22 01:00:00,10520.0 -2018-07-22 02:00:00,9947.0 -2018-07-22 03:00:00,9501.0 -2018-07-22 04:00:00,9236.0 -2018-07-22 05:00:00,9034.0 -2018-07-22 06:00:00,9028.0 -2018-07-22 07:00:00,9003.0 -2018-07-22 08:00:00,9022.0 -2018-07-22 09:00:00,9283.0 -2018-07-22 10:00:00,9727.0 -2018-07-22 11:00:00,10121.0 -2018-07-22 12:00:00,10485.0 -2018-07-22 13:00:00,10747.0 -2018-07-22 14:00:00,11045.0 -2018-07-22 15:00:00,11193.0 -2018-07-22 16:00:00,11379.0 -2018-07-22 17:00:00,11611.0 -2018-07-22 18:00:00,11776.0 -2018-07-22 19:00:00,11797.0 -2018-07-22 20:00:00,11722.0 -2018-07-22 21:00:00,11513.0 -2018-07-22 22:00:00,11451.0 -2018-07-22 23:00:00,11357.0 -2018-07-23 00:00:00,10824.0 -2018-07-21 01:00:00,11256.0 -2018-07-21 02:00:00,10581.0 -2018-07-21 03:00:00,10058.0 -2018-07-21 04:00:00,9708.0 -2018-07-21 05:00:00,9558.0 -2018-07-21 06:00:00,9504.0 -2018-07-21 07:00:00,9666.0 -2018-07-21 08:00:00,9915.0 -2018-07-21 09:00:00,10364.0 -2018-07-21 10:00:00,10912.0 -2018-07-21 11:00:00,11395.0 -2018-07-21 12:00:00,11717.0 -2018-07-21 13:00:00,12027.0 -2018-07-21 14:00:00,12205.0 -2018-07-21 15:00:00,12395.0 -2018-07-21 16:00:00,12447.0 -2018-07-21 17:00:00,12571.0 -2018-07-21 18:00:00,12589.0 -2018-07-21 19:00:00,12460.0 -2018-07-21 20:00:00,12184.0 -2018-07-21 21:00:00,11836.0 -2018-07-21 22:00:00,11783.0 -2018-07-21 23:00:00,11645.0 -2018-07-22 00:00:00,11107.0 -2018-07-20 01:00:00,12682.0 -2018-07-20 02:00:00,11929.0 -2018-07-20 03:00:00,11327.0 -2018-07-20 04:00:00,10895.0 -2018-07-20 05:00:00,10676.0 -2018-07-20 06:00:00,10784.0 -2018-07-20 07:00:00,11178.0 -2018-07-20 08:00:00,11930.0 -2018-07-20 09:00:00,13035.0 -2018-07-20 10:00:00,13687.0 -2018-07-20 11:00:00,14158.0 -2018-07-20 12:00:00,14361.0 -2018-07-20 13:00:00,14404.0 -2018-07-20 14:00:00,14491.0 -2018-07-20 15:00:00,14456.0 -2018-07-20 16:00:00,14420.0 -2018-07-20 17:00:00,14242.0 -2018-07-20 18:00:00,14080.0 -2018-07-20 19:00:00,13773.0 -2018-07-20 20:00:00,13333.0 -2018-07-20 21:00:00,12964.0 -2018-07-20 22:00:00,12896.0 -2018-07-20 23:00:00,12687.0 -2018-07-21 00:00:00,12004.0 -2018-07-19 01:00:00,11089.0 -2018-07-19 02:00:00,10331.0 -2018-07-19 03:00:00,9791.0 -2018-07-19 04:00:00,9422.0 -2018-07-19 05:00:00,9209.0 -2018-07-19 06:00:00,9287.0 -2018-07-19 07:00:00,9746.0 -2018-07-19 08:00:00,10560.0 -2018-07-19 09:00:00,11705.0 -2018-07-19 10:00:00,12536.0 -2018-07-19 11:00:00,13275.0 -2018-07-19 12:00:00,14133.0 -2018-07-19 13:00:00,14881.0 -2018-07-19 14:00:00,15420.0 -2018-07-19 15:00:00,15988.0 -2018-07-19 16:00:00,16323.0 -2018-07-19 17:00:00,16236.0 -2018-07-19 18:00:00,15929.0 -2018-07-19 19:00:00,15534.0 -2018-07-19 20:00:00,15171.0 -2018-07-19 21:00:00,14879.0 -2018-07-19 22:00:00,14729.0 -2018-07-19 23:00:00,14495.0 -2018-07-20 00:00:00,13645.0 -2018-07-18 01:00:00,11393.0 -2018-07-18 02:00:00,10572.0 -2018-07-18 03:00:00,9976.0 -2018-07-18 04:00:00,9556.0 -2018-07-18 05:00:00,9338.0 -2018-07-18 06:00:00,9380.0 -2018-07-18 07:00:00,9755.0 -2018-07-18 08:00:00,10616.0 -2018-07-18 09:00:00,11699.0 -2018-07-18 10:00:00,12532.0 -2018-07-18 11:00:00,13118.0 -2018-07-18 12:00:00,13684.0 -2018-07-18 13:00:00,14098.0 -2018-07-18 14:00:00,14512.0 -2018-07-18 15:00:00,14891.0 -2018-07-18 16:00:00,15188.0 -2018-07-18 17:00:00,15387.0 -2018-07-18 18:00:00,15486.0 -2018-07-18 19:00:00,15311.0 -2018-07-18 20:00:00,14717.0 -2018-07-18 21:00:00,13832.0 -2018-07-18 22:00:00,13416.0 -2018-07-18 23:00:00,12990.0 -2018-07-19 00:00:00,12111.0 -2018-07-17 01:00:00,13173.0 -2018-07-17 02:00:00,12050.0 -2018-07-17 03:00:00,11198.0 -2018-07-17 04:00:00,10633.0 -2018-07-17 05:00:00,10296.0 -2018-07-17 06:00:00,10340.0 -2018-07-17 07:00:00,10731.0 -2018-07-17 08:00:00,11660.0 -2018-07-17 09:00:00,12802.0 -2018-07-17 10:00:00,13830.0 -2018-07-17 11:00:00,14677.0 -2018-07-17 12:00:00,15477.0 -2018-07-17 13:00:00,16073.0 -2018-07-17 14:00:00,16426.0 -2018-07-17 15:00:00,16643.0 -2018-07-17 16:00:00,16780.0 -2018-07-17 17:00:00,16879.0 -2018-07-17 18:00:00,16834.0 -2018-07-17 19:00:00,16534.0 -2018-07-17 20:00:00,15873.0 -2018-07-17 21:00:00,14938.0 -2018-07-17 22:00:00,14160.0 -2018-07-17 23:00:00,13569.0 -2018-07-18 00:00:00,12504.0 -2018-07-16 01:00:00,13757.0 -2018-07-16 02:00:00,12869.0 -2018-07-16 03:00:00,12186.0 -2018-07-16 04:00:00,11721.0 -2018-07-16 05:00:00,11532.0 -2018-07-16 06:00:00,11621.0 -2018-07-16 07:00:00,12155.0 -2018-07-16 08:00:00,13133.0 -2018-07-16 09:00:00,14504.0 -2018-07-16 10:00:00,15832.0 -2018-07-16 11:00:00,16988.0 -2018-07-16 12:00:00,17964.0 -2018-07-16 13:00:00,18857.0 -2018-07-16 14:00:00,19167.0 -2018-07-16 15:00:00,19266.0 -2018-07-16 16:00:00,19005.0 -2018-07-16 17:00:00,18960.0 -2018-07-16 18:00:00,19032.0 -2018-07-16 19:00:00,18990.0 -2018-07-16 20:00:00,18568.0 -2018-07-16 21:00:00,17710.0 -2018-07-16 22:00:00,16736.0 -2018-07-16 23:00:00,15925.0 -2018-07-17 00:00:00,14540.0 -2018-07-15 01:00:00,12381.0 -2018-07-15 02:00:00,11527.0 -2018-07-15 03:00:00,10870.0 -2018-07-15 04:00:00,10413.0 -2018-07-15 05:00:00,10183.0 -2018-07-15 06:00:00,9966.0 -2018-07-15 07:00:00,9875.0 -2018-07-15 08:00:00,10025.0 -2018-07-15 09:00:00,10940.0 -2018-07-15 10:00:00,12192.0 -2018-07-15 11:00:00,13439.0 -2018-07-15 12:00:00,14594.0 -2018-07-15 13:00:00,15497.0 -2018-07-15 14:00:00,16147.0 -2018-07-15 15:00:00,16558.0 -2018-07-15 16:00:00,16976.0 -2018-07-15 17:00:00,17224.0 -2018-07-15 18:00:00,17320.0 -2018-07-15 19:00:00,17307.0 -2018-07-15 20:00:00,17092.0 -2018-07-15 21:00:00,16663.0 -2018-07-15 22:00:00,16199.0 -2018-07-15 23:00:00,15806.0 -2018-07-16 00:00:00,14879.0 -2018-07-14 01:00:00,14362.0 -2018-07-14 02:00:00,13304.0 -2018-07-14 03:00:00,12420.0 -2018-07-14 04:00:00,11830.0 -2018-07-14 05:00:00,11500.0 -2018-07-14 06:00:00,11260.0 -2018-07-14 07:00:00,11353.0 -2018-07-14 08:00:00,11505.0 -2018-07-14 09:00:00,11851.0 -2018-07-14 10:00:00,12354.0 -2018-07-14 11:00:00,12940.0 -2018-07-14 12:00:00,13414.0 -2018-07-14 13:00:00,13515.0 -2018-07-14 14:00:00,13508.0 -2018-07-14 15:00:00,13642.0 -2018-07-14 16:00:00,13788.0 -2018-07-14 17:00:00,14180.0 -2018-07-14 18:00:00,14653.0 -2018-07-14 19:00:00,14905.0 -2018-07-14 20:00:00,14664.0 -2018-07-14 21:00:00,14315.0 -2018-07-14 22:00:00,14013.0 -2018-07-14 23:00:00,13841.0 -2018-07-15 00:00:00,13186.0 -2018-07-13 01:00:00,13357.0 -2018-07-13 02:00:00,12296.0 -2018-07-13 03:00:00,11618.0 -2018-07-13 04:00:00,11076.0 -2018-07-13 05:00:00,10709.0 -2018-07-13 06:00:00,10699.0 -2018-07-13 07:00:00,11080.0 -2018-07-13 08:00:00,11937.0 -2018-07-13 09:00:00,13237.0 -2018-07-13 10:00:00,14347.0 -2018-07-13 11:00:00,15396.0 -2018-07-13 12:00:00,16430.0 -2018-07-13 13:00:00,17257.0 -2018-07-13 14:00:00,18027.0 -2018-07-13 15:00:00,18756.0 -2018-07-13 16:00:00,19279.0 -2018-07-13 17:00:00,19599.0 -2018-07-13 18:00:00,19773.0 -2018-07-13 19:00:00,19682.0 -2018-07-13 20:00:00,19208.0 -2018-07-13 21:00:00,18332.0 -2018-07-13 22:00:00,17525.0 -2018-07-13 23:00:00,16735.0 -2018-07-14 00:00:00,15608.0 -2018-07-12 01:00:00,12715.0 -2018-07-12 02:00:00,11750.0 -2018-07-12 03:00:00,11046.0 -2018-07-12 04:00:00,10513.0 -2018-07-12 05:00:00,10231.0 -2018-07-12 06:00:00,10299.0 -2018-07-12 07:00:00,10760.0 -2018-07-12 08:00:00,11643.0 -2018-07-12 09:00:00,12805.0 -2018-07-12 10:00:00,13811.0 -2018-07-12 11:00:00,14568.0 -2018-07-12 12:00:00,15421.0 -2018-07-12 13:00:00,16236.0 -2018-07-12 14:00:00,16855.0 -2018-07-12 15:00:00,17366.0 -2018-07-12 16:00:00,17581.0 -2018-07-12 17:00:00,17713.0 -2018-07-12 18:00:00,17751.0 -2018-07-12 19:00:00,17622.0 -2018-07-12 20:00:00,17173.0 -2018-07-12 21:00:00,16594.0 -2018-07-12 22:00:00,16067.0 -2018-07-12 23:00:00,15557.0 -2018-07-13 00:00:00,14475.0 -2018-07-11 01:00:00,12391.0 -2018-07-11 02:00:00,11420.0 -2018-07-11 03:00:00,10721.0 -2018-07-11 04:00:00,10242.0 -2018-07-11 05:00:00,9981.0 -2018-07-11 06:00:00,10003.0 -2018-07-11 07:00:00,10420.0 -2018-07-11 08:00:00,11328.0 -2018-07-11 09:00:00,12551.0 -2018-07-11 10:00:00,13566.0 -2018-07-11 11:00:00,14482.0 -2018-07-11 12:00:00,15374.0 -2018-07-11 13:00:00,16066.0 -2018-07-11 14:00:00,16712.0 -2018-07-11 15:00:00,17347.0 -2018-07-11 16:00:00,17782.0 -2018-07-11 17:00:00,18084.0 -2018-07-11 18:00:00,18135.0 -2018-07-11 19:00:00,17944.0 -2018-07-11 20:00:00,17327.0 -2018-07-11 21:00:00,16450.0 -2018-07-11 22:00:00,15621.0 -2018-07-11 23:00:00,15012.0 -2018-07-12 00:00:00,13895.0 -2018-07-10 01:00:00,14232.0 -2018-07-10 02:00:00,13214.0 -2018-07-10 03:00:00,12485.0 -2018-07-10 04:00:00,11964.0 -2018-07-10 05:00:00,11681.0 -2018-07-10 06:00:00,11702.0 -2018-07-10 07:00:00,12098.0 -2018-07-10 08:00:00,13077.0 -2018-07-10 09:00:00,14477.0 -2018-07-10 10:00:00,15589.0 -2018-07-10 11:00:00,16496.0 -2018-07-10 12:00:00,17310.0 -2018-07-10 13:00:00,17793.0 -2018-07-10 14:00:00,18121.0 -2018-07-10 15:00:00,18420.0 -2018-07-10 16:00:00,18515.0 -2018-07-10 17:00:00,18514.0 -2018-07-10 18:00:00,18318.0 -2018-07-10 19:00:00,17850.0 -2018-07-10 20:00:00,16923.0 -2018-07-10 21:00:00,15969.0 -2018-07-10 22:00:00,15217.0 -2018-07-10 23:00:00,14704.0 -2018-07-11 00:00:00,13596.0 -2018-07-09 01:00:00,11660.0 -2018-07-09 02:00:00,10817.0 -2018-07-09 03:00:00,10213.0 -2018-07-09 04:00:00,9759.0 -2018-07-09 05:00:00,9588.0 -2018-07-09 06:00:00,9715.0 -2018-07-09 07:00:00,10140.0 -2018-07-09 08:00:00,11073.0 -2018-07-09 09:00:00,12453.0 -2018-07-09 10:00:00,13593.0 -2018-07-09 11:00:00,14683.0 -2018-07-09 12:00:00,15689.0 -2018-07-09 13:00:00,16510.0 -2018-07-09 14:00:00,17248.0 -2018-07-09 15:00:00,17951.0 -2018-07-09 16:00:00,18528.0 -2018-07-09 17:00:00,19037.0 -2018-07-09 18:00:00,19325.0 -2018-07-09 19:00:00,19096.0 -2018-07-09 20:00:00,18642.0 -2018-07-09 21:00:00,18103.0 -2018-07-09 22:00:00,17393.0 -2018-07-09 23:00:00,16777.0 -2018-07-10 00:00:00,15533.0 -2018-07-08 01:00:00,10198.0 -2018-07-08 02:00:00,9532.0 -2018-07-08 03:00:00,9072.0 -2018-07-08 04:00:00,8691.0 -2018-07-08 05:00:00,8466.0 -2018-07-08 06:00:00,8339.0 -2018-07-08 07:00:00,8228.0 -2018-07-08 08:00:00,8334.0 -2018-07-08 09:00:00,8914.0 -2018-07-08 10:00:00,9667.0 -2018-07-08 11:00:00,10550.0 -2018-07-08 12:00:00,11297.0 -2018-07-08 13:00:00,12028.0 -2018-07-08 14:00:00,12625.0 -2018-07-08 15:00:00,13232.0 -2018-07-08 16:00:00,13855.0 -2018-07-08 17:00:00,14422.0 -2018-07-08 18:00:00,14933.0 -2018-07-08 19:00:00,15116.0 -2018-07-08 20:00:00,14988.0 -2018-07-08 21:00:00,14546.0 -2018-07-08 22:00:00,13953.0 -2018-07-08 23:00:00,13635.0 -2018-07-09 00:00:00,12699.0 -2018-07-07 01:00:00,10248.0 -2018-07-07 02:00:00,9600.0 -2018-07-07 03:00:00,9144.0 -2018-07-07 04:00:00,8806.0 -2018-07-07 05:00:00,8626.0 -2018-07-07 06:00:00,8599.0 -2018-07-07 07:00:00,8517.0 -2018-07-07 08:00:00,8858.0 -2018-07-07 09:00:00,9497.0 -2018-07-07 10:00:00,10171.0 -2018-07-07 11:00:00,10731.0 -2018-07-07 12:00:00,11283.0 -2018-07-07 13:00:00,11668.0 -2018-07-07 14:00:00,12011.0 -2018-07-07 15:00:00,12201.0 -2018-07-07 16:00:00,12601.0 -2018-07-07 17:00:00,12934.0 -2018-07-07 18:00:00,13277.0 -2018-07-07 19:00:00,13302.0 -2018-07-07 20:00:00,13043.0 -2018-07-07 21:00:00,12489.0 -2018-07-07 22:00:00,11924.0 -2018-07-07 23:00:00,11603.0 -2018-07-08 00:00:00,10929.0 -2018-07-06 01:00:00,13178.0 -2018-07-06 02:00:00,12136.0 -2018-07-06 03:00:00,11303.0 -2018-07-06 04:00:00,10677.0 -2018-07-06 05:00:00,10317.0 -2018-07-06 06:00:00,10275.0 -2018-07-06 07:00:00,10455.0 -2018-07-06 08:00:00,11248.0 -2018-07-06 09:00:00,12290.0 -2018-07-06 10:00:00,13028.0 -2018-07-06 11:00:00,13513.0 -2018-07-06 12:00:00,13875.0 -2018-07-06 13:00:00,13973.0 -2018-07-06 14:00:00,14025.0 -2018-07-06 15:00:00,14200.0 -2018-07-06 16:00:00,14286.0 -2018-07-06 17:00:00,14381.0 -2018-07-06 18:00:00,14375.0 -2018-07-06 19:00:00,14183.0 -2018-07-06 20:00:00,13590.0 -2018-07-06 21:00:00,12901.0 -2018-07-06 22:00:00,12191.0 -2018-07-06 23:00:00,11826.0 -2018-07-07 00:00:00,11082.0 -2018-07-05 01:00:00,13451.0 -2018-07-05 02:00:00,12563.0 -2018-07-05 03:00:00,11826.0 -2018-07-05 04:00:00,11305.0 -2018-07-05 05:00:00,11086.0 -2018-07-05 06:00:00,11140.0 -2018-07-05 07:00:00,11533.0 -2018-07-05 08:00:00,12568.0 -2018-07-05 09:00:00,14000.0 -2018-07-05 10:00:00,15428.0 -2018-07-05 11:00:00,16799.0 -2018-07-05 12:00:00,18001.0 -2018-07-05 13:00:00,18945.0 -2018-07-05 14:00:00,19530.0 -2018-07-05 15:00:00,19488.0 -2018-07-05 16:00:00,18228.0 -2018-07-05 17:00:00,17390.0 -2018-07-05 18:00:00,17069.0 -2018-07-05 19:00:00,16812.0 -2018-07-05 20:00:00,16374.0 -2018-07-05 21:00:00,16103.0 -2018-07-05 22:00:00,15679.0 -2018-07-05 23:00:00,15347.0 -2018-07-06 00:00:00,14329.0 -2018-07-04 01:00:00,14123.0 -2018-07-04 02:00:00,13088.0 -2018-07-04 03:00:00,12394.0 -2018-07-04 04:00:00,11815.0 -2018-07-04 05:00:00,11435.0 -2018-07-04 06:00:00,11185.0 -2018-07-04 07:00:00,11026.0 -2018-07-04 08:00:00,11339.0 -2018-07-04 09:00:00,12266.0 -2018-07-04 10:00:00,13470.0 -2018-07-04 11:00:00,14834.0 -2018-07-04 12:00:00,16089.0 -2018-07-04 13:00:00,16913.0 -2018-07-04 14:00:00,17377.0 -2018-07-04 15:00:00,17818.0 -2018-07-04 16:00:00,17984.0 -2018-07-04 17:00:00,18021.0 -2018-07-04 18:00:00,17834.0 -2018-07-04 19:00:00,17700.0 -2018-07-04 20:00:00,17134.0 -2018-07-04 21:00:00,16365.0 -2018-07-04 22:00:00,15949.0 -2018-07-04 23:00:00,15347.0 -2018-07-05 00:00:00,14424.0 -2018-07-03 01:00:00,11743.0 -2018-07-03 02:00:00,10726.0 -2018-07-03 03:00:00,10103.0 -2018-07-03 04:00:00,9653.0 -2018-07-03 05:00:00,9461.0 -2018-07-03 06:00:00,9557.0 -2018-07-03 07:00:00,9977.0 -2018-07-03 08:00:00,10947.0 -2018-07-03 09:00:00,12122.0 -2018-07-03 10:00:00,13286.0 -2018-07-03 11:00:00,14391.0 -2018-07-03 12:00:00,15475.0 -2018-07-03 13:00:00,16418.0 -2018-07-03 14:00:00,17310.0 -2018-07-03 15:00:00,18058.0 -2018-07-03 16:00:00,18492.0 -2018-07-03 17:00:00,18765.0 -2018-07-03 18:00:00,19005.0 -2018-07-03 19:00:00,18939.0 -2018-07-03 20:00:00,18328.0 -2018-07-03 21:00:00,17483.0 -2018-07-03 22:00:00,16798.0 -2018-07-03 23:00:00,16263.0 -2018-07-04 00:00:00,15221.0 -2018-07-02 01:00:00,13134.0 -2018-07-02 02:00:00,12278.0 -2018-07-02 03:00:00,11615.0 -2018-07-02 04:00:00,11131.0 -2018-07-02 05:00:00,10787.0 -2018-07-02 06:00:00,10806.0 -2018-07-02 07:00:00,11065.0 -2018-07-02 08:00:00,11845.0 -2018-07-02 09:00:00,13042.0 -2018-07-02 10:00:00,13947.0 -2018-07-02 11:00:00,14665.0 -2018-07-02 12:00:00,15322.0 -2018-07-02 13:00:00,15786.0 -2018-07-02 14:00:00,16229.0 -2018-07-02 15:00:00,16688.0 -2018-07-02 16:00:00,17072.0 -2018-07-02 17:00:00,17356.0 -2018-07-02 18:00:00,17486.0 -2018-07-02 19:00:00,17413.0 -2018-07-02 20:00:00,16871.0 -2018-07-02 21:00:00,15962.0 -2018-07-02 22:00:00,14964.0 -2018-07-02 23:00:00,14266.0 -2018-07-03 00:00:00,13017.0 -2018-07-01 01:00:00,15466.0 -2018-07-01 02:00:00,14452.0 -2018-07-01 03:00:00,13586.0 -2018-07-01 04:00:00,12906.0 -2018-07-01 05:00:00,12416.0 -2018-07-01 06:00:00,12099.0 -2018-07-01 07:00:00,11849.0 -2018-07-01 08:00:00,11960.0 -2018-07-01 09:00:00,12831.0 -2018-07-01 10:00:00,14181.0 -2018-07-01 11:00:00,15384.0 -2018-07-01 12:00:00,16494.0 -2018-07-01 13:00:00,17474.0 -2018-07-01 14:00:00,18150.0 -2018-07-01 15:00:00,18634.0 -2018-07-01 16:00:00,18932.0 -2018-07-01 17:00:00,18909.0 -2018-07-01 18:00:00,18056.0 -2018-07-01 19:00:00,16710.0 -2018-07-01 20:00:00,15828.0 -2018-07-01 21:00:00,15320.0 -2018-07-01 22:00:00,14968.0 -2018-07-01 23:00:00,14812.0 -2018-07-02 00:00:00,14068.0 -2018-06-30 01:00:00,16412.0 -2018-06-30 02:00:00,15313.0 -2018-06-30 03:00:00,14478.0 -2018-06-30 04:00:00,13758.0 -2018-06-30 05:00:00,13263.0 -2018-06-30 06:00:00,12937.0 -2018-06-30 07:00:00,12778.0 -2018-06-30 08:00:00,13219.0 -2018-06-30 09:00:00,14318.0 -2018-06-30 10:00:00,15662.0 -2018-06-30 11:00:00,17009.0 -2018-06-30 12:00:00,18114.0 -2018-06-30 13:00:00,18832.0 -2018-06-30 14:00:00,19302.0 -2018-06-30 15:00:00,19605.0 -2018-06-30 16:00:00,19821.0 -2018-06-30 17:00:00,19937.0 -2018-06-30 18:00:00,19954.0 -2018-06-30 19:00:00,19790.0 -2018-06-30 20:00:00,19376.0 -2018-06-30 21:00:00,18709.0 -2018-06-30 22:00:00,18086.0 -2018-06-30 23:00:00,17611.0 -2018-07-01 00:00:00,16600.0 -2018-06-29 01:00:00,13028.0 -2018-06-29 02:00:00,11948.0 -2018-06-29 03:00:00,11243.0 -2018-06-29 04:00:00,10764.0 -2018-06-29 05:00:00,10529.0 -2018-06-29 06:00:00,10599.0 -2018-06-29 07:00:00,11035.0 -2018-06-29 08:00:00,12137.0 -2018-06-29 09:00:00,13605.0 -2018-06-29 10:00:00,14950.0 -2018-06-29 11:00:00,16120.0 -2018-06-29 12:00:00,17214.0 -2018-06-29 13:00:00,18179.0 -2018-06-29 14:00:00,18953.0 -2018-06-29 15:00:00,19756.0 -2018-06-29 16:00:00,20375.0 -2018-06-29 17:00:00,20745.0 -2018-06-29 18:00:00,20996.0 -2018-06-29 19:00:00,20915.0 -2018-06-29 20:00:00,20530.0 -2018-06-29 21:00:00,19965.0 -2018-06-29 22:00:00,19389.0 -2018-06-29 23:00:00,18848.0 -2018-06-30 00:00:00,17633.0 -2018-06-28 01:00:00,11297.0 -2018-06-28 02:00:00,10486.0 -2018-06-28 03:00:00,9927.0 -2018-06-28 04:00:00,9542.0 -2018-06-28 05:00:00,9374.0 -2018-06-28 06:00:00,9474.0 -2018-06-28 07:00:00,9888.0 -2018-06-28 08:00:00,10956.0 -2018-06-28 09:00:00,12254.0 -2018-06-28 10:00:00,13390.0 -2018-06-28 11:00:00,14381.0 -2018-06-28 12:00:00,15291.0 -2018-06-28 13:00:00,16059.0 -2018-06-28 14:00:00,16715.0 -2018-06-28 15:00:00,17322.0 -2018-06-28 16:00:00,17736.0 -2018-06-28 17:00:00,17998.0 -2018-06-28 18:00:00,18075.0 -2018-06-28 19:00:00,17677.0 -2018-06-28 20:00:00,16999.0 -2018-06-28 21:00:00,16355.0 -2018-06-28 22:00:00,15863.0 -2018-06-28 23:00:00,15400.0 -2018-06-29 00:00:00,14306.0 -2018-06-27 01:00:00,11521.0 -2018-06-27 02:00:00,10748.0 -2018-06-27 03:00:00,10212.0 -2018-06-27 04:00:00,9850.0 -2018-06-27 05:00:00,9697.0 -2018-06-27 06:00:00,9790.0 -2018-06-27 07:00:00,10334.0 -2018-06-27 08:00:00,11146.0 -2018-06-27 09:00:00,11983.0 -2018-06-27 10:00:00,12537.0 -2018-06-27 11:00:00,12968.0 -2018-06-27 12:00:00,13463.0 -2018-06-27 13:00:00,13743.0 -2018-06-27 14:00:00,14092.0 -2018-06-27 15:00:00,14671.0 -2018-06-27 16:00:00,15095.0 -2018-06-27 17:00:00,15163.0 -2018-06-27 18:00:00,15069.0 -2018-06-27 19:00:00,15012.0 -2018-06-27 20:00:00,14652.0 -2018-06-27 21:00:00,14115.0 -2018-06-27 22:00:00,13663.0 -2018-06-27 23:00:00,13308.0 -2018-06-28 00:00:00,12389.0 -2018-06-26 01:00:00,11152.0 -2018-06-26 02:00:00,10402.0 -2018-06-26 03:00:00,9867.0 -2018-06-26 04:00:00,9491.0 -2018-06-26 05:00:00,9269.0 -2018-06-26 06:00:00,9378.0 -2018-06-26 07:00:00,9769.0 -2018-06-26 08:00:00,10528.0 -2018-06-26 09:00:00,11358.0 -2018-06-26 10:00:00,11865.0 -2018-06-26 11:00:00,12237.0 -2018-06-26 12:00:00,12833.0 -2018-06-26 13:00:00,13211.0 -2018-06-26 14:00:00,13702.0 -2018-06-26 15:00:00,14279.0 -2018-06-26 16:00:00,15081.0 -2018-06-26 17:00:00,15453.0 -2018-06-26 18:00:00,15369.0 -2018-06-26 19:00:00,15177.0 -2018-06-26 20:00:00,14546.0 -2018-06-26 21:00:00,14039.0 -2018-06-26 22:00:00,13750.0 -2018-06-26 23:00:00,13373.0 -2018-06-27 00:00:00,12504.0 -2018-06-25 01:00:00,9864.0 -2018-06-25 02:00:00,9280.0 -2018-06-25 03:00:00,8841.0 -2018-06-25 04:00:00,8543.0 -2018-06-25 05:00:00,8460.0 -2018-06-25 06:00:00,8664.0 -2018-06-25 07:00:00,9136.0 -2018-06-25 08:00:00,10081.0 -2018-06-25 09:00:00,11174.0 -2018-06-25 10:00:00,11918.0 -2018-06-25 11:00:00,12556.0 -2018-06-25 12:00:00,13146.0 -2018-06-25 13:00:00,13595.0 -2018-06-25 14:00:00,13949.0 -2018-06-25 15:00:00,14293.0 -2018-06-25 16:00:00,14543.0 -2018-06-25 17:00:00,14746.0 -2018-06-25 18:00:00,14845.0 -2018-06-25 19:00:00,14688.0 -2018-06-25 20:00:00,14147.0 -2018-06-25 21:00:00,13468.0 -2018-06-25 22:00:00,13192.0 -2018-06-25 23:00:00,12935.0 -2018-06-26 00:00:00,12110.0 -2018-06-24 01:00:00,9944.0 -2018-06-24 02:00:00,9379.0 -2018-06-24 03:00:00,8941.0 -2018-06-24 04:00:00,8592.0 -2018-06-24 05:00:00,8363.0 -2018-06-24 06:00:00,8259.0 -2018-06-24 07:00:00,8108.0 -2018-06-24 08:00:00,8275.0 -2018-06-24 09:00:00,8900.0 -2018-06-24 10:00:00,9743.0 -2018-06-24 11:00:00,10592.0 -2018-06-24 12:00:00,11376.0 -2018-06-24 13:00:00,12005.0 -2018-06-24 14:00:00,12548.0 -2018-06-24 15:00:00,12928.0 -2018-06-24 16:00:00,13256.0 -2018-06-24 17:00:00,13346.0 -2018-06-24 18:00:00,13215.0 -2018-06-24 19:00:00,13103.0 -2018-06-24 20:00:00,12696.0 -2018-06-24 21:00:00,12002.0 -2018-06-24 22:00:00,11547.0 -2018-06-24 23:00:00,11301.0 -2018-06-25 00:00:00,10623.0 -2018-06-23 01:00:00,9391.0 -2018-06-23 02:00:00,8899.0 -2018-06-23 03:00:00,8572.0 -2018-06-23 04:00:00,8332.0 -2018-06-23 05:00:00,8210.0 -2018-06-23 06:00:00,8207.0 -2018-06-23 07:00:00,8354.0 -2018-06-23 08:00:00,8577.0 -2018-06-23 09:00:00,9054.0 -2018-06-23 10:00:00,9648.0 -2018-06-23 11:00:00,10159.0 -2018-06-23 12:00:00,10541.0 -2018-06-23 13:00:00,10928.0 -2018-06-23 14:00:00,11238.0 -2018-06-23 15:00:00,11486.0 -2018-06-23 16:00:00,11706.0 -2018-06-23 17:00:00,11846.0 -2018-06-23 18:00:00,11959.0 -2018-06-23 19:00:00,11929.0 -2018-06-23 20:00:00,11610.0 -2018-06-23 21:00:00,11244.0 -2018-06-23 22:00:00,11221.0 -2018-06-23 23:00:00,11131.0 -2018-06-24 00:00:00,10572.0 -2018-06-22 01:00:00,9914.0 -2018-06-22 02:00:00,9378.0 -2018-06-22 03:00:00,9012.0 -2018-06-22 04:00:00,8822.0 -2018-06-22 05:00:00,8687.0 -2018-06-22 06:00:00,8843.0 -2018-06-22 07:00:00,9253.0 -2018-06-22 08:00:00,10029.0 -2018-06-22 09:00:00,10700.0 -2018-06-22 10:00:00,11146.0 -2018-06-22 11:00:00,11371.0 -2018-06-22 12:00:00,11508.0 -2018-06-22 13:00:00,11529.0 -2018-06-22 14:00:00,11472.0 -2018-06-22 15:00:00,11485.0 -2018-06-22 16:00:00,11385.0 -2018-06-22 17:00:00,11195.0 -2018-06-22 18:00:00,11037.0 -2018-06-22 19:00:00,10861.0 -2018-06-22 20:00:00,10663.0 -2018-06-22 21:00:00,10541.0 -2018-06-22 22:00:00,10631.0 -2018-06-22 23:00:00,10543.0 -2018-06-23 00:00:00,9989.0 -2018-06-21 01:00:00,10648.0 -2018-06-21 02:00:00,10016.0 -2018-06-21 03:00:00,9573.0 -2018-06-21 04:00:00,9231.0 -2018-06-21 05:00:00,9143.0 -2018-06-21 06:00:00,9289.0 -2018-06-21 07:00:00,9818.0 -2018-06-21 08:00:00,10657.0 -2018-06-21 09:00:00,11481.0 -2018-06-21 10:00:00,11963.0 -2018-06-21 11:00:00,12251.0 -2018-06-21 12:00:00,12485.0 -2018-06-21 13:00:00,12497.0 -2018-06-21 14:00:00,12455.0 -2018-06-21 15:00:00,12426.0 -2018-06-21 16:00:00,12266.0 -2018-06-21 17:00:00,12064.0 -2018-06-21 18:00:00,11920.0 -2018-06-21 19:00:00,11782.0 -2018-06-21 20:00:00,11615.0 -2018-06-21 21:00:00,11528.0 -2018-06-21 22:00:00,11505.0 -2018-06-21 23:00:00,11258.0 -2018-06-22 00:00:00,10622.0 -2018-06-20 01:00:00,11069.0 -2018-06-20 02:00:00,10334.0 -2018-06-20 03:00:00,9832.0 -2018-06-20 04:00:00,9510.0 -2018-06-20 05:00:00,9381.0 -2018-06-20 06:00:00,9469.0 -2018-06-20 07:00:00,9949.0 -2018-06-20 08:00:00,10810.0 -2018-06-20 09:00:00,11640.0 -2018-06-20 10:00:00,12103.0 -2018-06-20 11:00:00,12611.0 -2018-06-20 12:00:00,13265.0 -2018-06-20 13:00:00,13722.0 -2018-06-20 14:00:00,14063.0 -2018-06-20 15:00:00,14514.0 -2018-06-20 16:00:00,14786.0 -2018-06-20 17:00:00,14741.0 -2018-06-20 18:00:00,14671.0 -2018-06-20 19:00:00,14418.0 -2018-06-20 20:00:00,13817.0 -2018-06-20 21:00:00,13058.0 -2018-06-20 22:00:00,12712.0 -2018-06-20 23:00:00,12310.0 -2018-06-21 00:00:00,11492.0 -2018-06-19 01:00:00,13394.0 -2018-06-19 02:00:00,12273.0 -2018-06-19 03:00:00,11426.0 -2018-06-19 04:00:00,10841.0 -2018-06-19 05:00:00,10524.0 -2018-06-19 06:00:00,10509.0 -2018-06-19 07:00:00,10855.0 -2018-06-19 08:00:00,11523.0 -2018-06-19 09:00:00,12339.0 -2018-06-19 10:00:00,13025.0 -2018-06-19 11:00:00,13651.0 -2018-06-19 12:00:00,14293.0 -2018-06-19 13:00:00,15008.0 -2018-06-19 14:00:00,15630.0 -2018-06-19 15:00:00,16160.0 -2018-06-19 16:00:00,16387.0 -2018-06-19 17:00:00,16030.0 -2018-06-19 18:00:00,15315.0 -2018-06-19 19:00:00,14758.0 -2018-06-19 20:00:00,14127.0 -2018-06-19 21:00:00,13556.0 -2018-06-19 22:00:00,13296.0 -2018-06-19 23:00:00,12891.0 -2018-06-20 00:00:00,12016.0 -2018-06-18 01:00:00,14753.0 -2018-06-18 02:00:00,13812.0 -2018-06-18 03:00:00,13239.0 -2018-06-18 04:00:00,12770.0 -2018-06-18 05:00:00,12495.0 -2018-06-18 06:00:00,12578.0 -2018-06-18 07:00:00,13042.0 -2018-06-18 08:00:00,14139.0 -2018-06-18 09:00:00,15689.0 -2018-06-18 10:00:00,16983.0 -2018-06-18 11:00:00,18085.0 -2018-06-18 12:00:00,19090.0 -2018-06-18 13:00:00,19926.0 -2018-06-18 14:00:00,20498.0 -2018-06-18 15:00:00,20941.0 -2018-06-18 16:00:00,21209.0 -2018-06-18 17:00:00,21349.0 -2018-06-18 18:00:00,21344.0 -2018-06-18 19:00:00,20727.0 -2018-06-18 20:00:00,19188.0 -2018-06-18 21:00:00,18050.0 -2018-06-18 22:00:00,17122.0 -2018-06-18 23:00:00,16209.0 -2018-06-19 00:00:00,14819.0 -2018-06-17 01:00:00,14147.0 -2018-06-17 02:00:00,13278.0 -2018-06-17 03:00:00,12563.0 -2018-06-17 04:00:00,11966.0 -2018-06-17 05:00:00,11474.0 -2018-06-17 06:00:00,11226.0 -2018-06-17 07:00:00,10926.0 -2018-06-17 08:00:00,11219.0 -2018-06-17 09:00:00,12275.0 -2018-06-17 10:00:00,13646.0 -2018-06-17 11:00:00,15034.0 -2018-06-17 12:00:00,16198.0 -2018-06-17 13:00:00,17102.0 -2018-06-17 14:00:00,17760.0 -2018-06-17 15:00:00,18241.0 -2018-06-17 16:00:00,18449.0 -2018-06-17 17:00:00,18390.0 -2018-06-17 18:00:00,18215.0 -2018-06-17 19:00:00,17974.0 -2018-06-17 20:00:00,17599.0 -2018-06-17 21:00:00,17213.0 -2018-06-17 22:00:00,16949.0 -2018-06-17 23:00:00,16789.0 -2018-06-18 00:00:00,15823.0 -2018-06-16 01:00:00,13472.0 -2018-06-16 02:00:00,12619.0 -2018-06-16 03:00:00,11781.0 -2018-06-16 04:00:00,10973.0 -2018-06-16 05:00:00,10456.0 -2018-06-16 06:00:00,10275.0 -2018-06-16 07:00:00,10211.0 -2018-06-16 08:00:00,10410.0 -2018-06-16 09:00:00,11334.0 -2018-06-16 10:00:00,12681.0 -2018-06-16 11:00:00,14007.0 -2018-06-16 12:00:00,15050.0 -2018-06-16 13:00:00,15834.0 -2018-06-16 14:00:00,16284.0 -2018-06-16 15:00:00,16632.0 -2018-06-16 16:00:00,17057.0 -2018-06-16 17:00:00,17496.0 -2018-06-16 18:00:00,17801.0 -2018-06-16 19:00:00,17910.0 -2018-06-16 20:00:00,17464.0 -2018-06-16 21:00:00,16834.0 -2018-06-16 22:00:00,16384.0 -2018-06-16 23:00:00,16000.0 -2018-06-17 00:00:00,15129.0 -2018-06-15 01:00:00,10585.0 -2018-06-15 02:00:00,9850.0 -2018-06-15 03:00:00,9382.0 -2018-06-15 04:00:00,8982.0 -2018-06-15 05:00:00,8848.0 -2018-06-15 06:00:00,8954.0 -2018-06-15 07:00:00,9326.0 -2018-06-15 08:00:00,10283.0 -2018-06-15 09:00:00,11329.0 -2018-06-15 10:00:00,12082.0 -2018-06-15 11:00:00,12501.0 -2018-06-15 12:00:00,12818.0 -2018-06-15 13:00:00,13178.0 -2018-06-15 14:00:00,13851.0 -2018-06-15 15:00:00,14763.0 -2018-06-15 16:00:00,15616.0 -2018-06-15 17:00:00,16326.0 -2018-06-15 18:00:00,16833.0 -2018-06-15 19:00:00,16994.0 -2018-06-15 20:00:00,16684.0 -2018-06-15 21:00:00,16062.0 -2018-06-15 22:00:00,15713.0 -2018-06-15 23:00:00,15411.0 -2018-06-16 00:00:00,14555.0 -2018-06-14 01:00:00,10507.0 -2018-06-14 02:00:00,9709.0 -2018-06-14 03:00:00,9180.0 -2018-06-14 04:00:00,8904.0 -2018-06-14 05:00:00,8766.0 -2018-06-14 06:00:00,8882.0 -2018-06-14 07:00:00,9260.0 -2018-06-14 08:00:00,10199.0 -2018-06-14 09:00:00,11085.0 -2018-06-14 10:00:00,12004.0 -2018-06-14 11:00:00,12748.0 -2018-06-14 12:00:00,13344.0 -2018-06-14 13:00:00,13829.0 -2018-06-14 14:00:00,14156.0 -2018-06-14 15:00:00,14394.0 -2018-06-14 16:00:00,14347.0 -2018-06-14 17:00:00,14149.0 -2018-06-14 18:00:00,13910.0 -2018-06-14 19:00:00,13617.0 -2018-06-14 20:00:00,13147.0 -2018-06-14 21:00:00,12760.0 -2018-06-14 22:00:00,12630.0 -2018-06-14 23:00:00,12451.0 -2018-06-15 00:00:00,11567.0 -2018-06-13 01:00:00,10997.0 -2018-06-13 02:00:00,10254.0 -2018-06-13 03:00:00,9767.0 -2018-06-13 04:00:00,9463.0 -2018-06-13 05:00:00,9358.0 -2018-06-13 06:00:00,9460.0 -2018-06-13 07:00:00,9917.0 -2018-06-13 08:00:00,10939.0 -2018-06-13 09:00:00,11978.0 -2018-06-13 10:00:00,12823.0 -2018-06-13 11:00:00,13454.0 -2018-06-13 12:00:00,13981.0 -2018-06-13 13:00:00,14261.0 -2018-06-13 14:00:00,14467.0 -2018-06-13 15:00:00,14749.0 -2018-06-13 16:00:00,14970.0 -2018-06-13 17:00:00,15134.0 -2018-06-13 18:00:00,15236.0 -2018-06-13 19:00:00,15119.0 -2018-06-13 20:00:00,14622.0 -2018-06-13 21:00:00,13925.0 -2018-06-13 22:00:00,13228.0 -2018-06-13 23:00:00,12672.0 -2018-06-14 00:00:00,11591.0 -2018-06-12 01:00:00,9669.0 -2018-06-12 02:00:00,9117.0 -2018-06-12 03:00:00,8789.0 -2018-06-12 04:00:00,8583.0 -2018-06-12 05:00:00,8559.0 -2018-06-12 06:00:00,8730.0 -2018-06-12 07:00:00,9215.0 -2018-06-12 08:00:00,10120.0 -2018-06-12 09:00:00,11002.0 -2018-06-12 10:00:00,11576.0 -2018-06-12 11:00:00,12006.0 -2018-06-12 12:00:00,12449.0 -2018-06-12 13:00:00,12711.0 -2018-06-12 14:00:00,12948.0 -2018-06-12 15:00:00,13340.0 -2018-06-12 16:00:00,13674.0 -2018-06-12 17:00:00,13879.0 -2018-06-12 18:00:00,13856.0 -2018-06-12 19:00:00,13718.0 -2018-06-12 20:00:00,13389.0 -2018-06-12 21:00:00,13190.0 -2018-06-12 22:00:00,13194.0 -2018-06-12 23:00:00,12955.0 -2018-06-13 00:00:00,12019.0 -2018-06-11 01:00:00,8940.0 -2018-06-11 02:00:00,8590.0 -2018-06-11 03:00:00,8338.0 -2018-06-11 04:00:00,8232.0 -2018-06-11 05:00:00,8153.0 -2018-06-11 06:00:00,8404.0 -2018-06-11 07:00:00,8848.0 -2018-06-11 08:00:00,9679.0 -2018-06-11 09:00:00,10501.0 -2018-06-11 10:00:00,10976.0 -2018-06-11 11:00:00,11284.0 -2018-06-11 12:00:00,11566.0 -2018-06-11 13:00:00,11704.0 -2018-06-11 14:00:00,11765.0 -2018-06-11 15:00:00,11852.0 -2018-06-11 16:00:00,11854.0 -2018-06-11 17:00:00,11714.0 -2018-06-11 18:00:00,11629.0 -2018-06-11 19:00:00,11514.0 -2018-06-11 20:00:00,11383.0 -2018-06-11 21:00:00,11243.0 -2018-06-11 22:00:00,11361.0 -2018-06-11 23:00:00,11113.0 -2018-06-12 00:00:00,10432.0 -2018-06-10 01:00:00,9447.0 -2018-06-10 02:00:00,8863.0 -2018-06-10 03:00:00,8478.0 -2018-06-10 04:00:00,8207.0 -2018-06-10 05:00:00,8165.0 -2018-06-10 06:00:00,8148.0 -2018-06-10 07:00:00,8169.0 -2018-06-10 08:00:00,8121.0 -2018-06-10 09:00:00,8431.0 -2018-06-10 10:00:00,8876.0 -2018-06-10 11:00:00,9307.0 -2018-06-10 12:00:00,9654.0 -2018-06-10 13:00:00,9900.0 -2018-06-10 14:00:00,9973.0 -2018-06-10 15:00:00,9967.0 -2018-06-10 16:00:00,9895.0 -2018-06-10 17:00:00,9832.0 -2018-06-10 18:00:00,9740.0 -2018-06-10 19:00:00,9808.0 -2018-06-10 20:00:00,9748.0 -2018-06-10 21:00:00,9717.0 -2018-06-10 22:00:00,9953.0 -2018-06-10 23:00:00,9900.0 -2018-06-11 00:00:00,9494.0 -2018-06-09 01:00:00,9552.0 -2018-06-09 02:00:00,8960.0 -2018-06-09 03:00:00,8538.0 -2018-06-09 04:00:00,8283.0 -2018-06-09 05:00:00,8103.0 -2018-06-09 06:00:00,8111.0 -2018-06-09 07:00:00,8379.0 -2018-06-09 08:00:00,8701.0 -2018-06-09 09:00:00,9148.0 -2018-06-09 10:00:00,9664.0 -2018-06-09 11:00:00,10038.0 -2018-06-09 12:00:00,10292.0 -2018-06-09 13:00:00,10492.0 -2018-06-09 14:00:00,10745.0 -2018-06-09 15:00:00,10991.0 -2018-06-09 16:00:00,11332.0 -2018-06-09 17:00:00,11722.0 -2018-06-09 18:00:00,12046.0 -2018-06-09 19:00:00,12152.0 -2018-06-09 20:00:00,11850.0 -2018-06-09 21:00:00,11282.0 -2018-06-09 22:00:00,11043.0 -2018-06-09 23:00:00,10743.0 -2018-06-10 00:00:00,10072.0 -2018-06-08 01:00:00,10020.0 -2018-06-08 02:00:00,9316.0 -2018-06-08 03:00:00,8893.0 -2018-06-08 04:00:00,8578.0 -2018-06-08 05:00:00,8458.0 -2018-06-08 06:00:00,8577.0 -2018-06-08 07:00:00,8975.0 -2018-06-08 08:00:00,9642.0 -2018-06-08 09:00:00,10363.0 -2018-06-08 10:00:00,10905.0 -2018-06-08 11:00:00,11192.0 -2018-06-08 12:00:00,11428.0 -2018-06-08 13:00:00,11533.0 -2018-06-08 14:00:00,11661.0 -2018-06-08 15:00:00,11893.0 -2018-06-08 16:00:00,11970.0 -2018-06-08 17:00:00,11899.0 -2018-06-08 18:00:00,11908.0 -2018-06-08 19:00:00,11850.0 -2018-06-08 20:00:00,11637.0 -2018-06-08 21:00:00,11283.0 -2018-06-08 22:00:00,11250.0 -2018-06-08 23:00:00,11021.0 -2018-06-09 00:00:00,10343.0 -2018-06-07 01:00:00,10099.0 -2018-06-07 02:00:00,9475.0 -2018-06-07 03:00:00,9034.0 -2018-06-07 04:00:00,8694.0 -2018-06-07 05:00:00,8622.0 -2018-06-07 06:00:00,8717.0 -2018-06-07 07:00:00,9154.0 -2018-06-07 08:00:00,9951.0 -2018-06-07 09:00:00,10855.0 -2018-06-07 10:00:00,11627.0 -2018-06-07 11:00:00,12357.0 -2018-06-07 12:00:00,13000.0 -2018-06-07 13:00:00,13495.0 -2018-06-07 14:00:00,13896.0 -2018-06-07 15:00:00,14357.0 -2018-06-07 16:00:00,14782.0 -2018-06-07 17:00:00,15001.0 -2018-06-07 18:00:00,15029.0 -2018-06-07 19:00:00,14674.0 -2018-06-07 20:00:00,13865.0 -2018-06-07 21:00:00,12944.0 -2018-06-07 22:00:00,12448.0 -2018-06-07 23:00:00,11903.0 -2018-06-08 00:00:00,10977.0 -2018-06-06 01:00:00,8997.0 -2018-06-06 02:00:00,8508.0 -2018-06-06 03:00:00,8222.0 -2018-06-06 04:00:00,8020.0 -2018-06-06 05:00:00,7922.0 -2018-06-06 06:00:00,8062.0 -2018-06-06 07:00:00,8394.0 -2018-06-06 08:00:00,9190.0 -2018-06-06 09:00:00,10026.0 -2018-06-06 10:00:00,10630.0 -2018-06-06 11:00:00,11055.0 -2018-06-06 12:00:00,11439.0 -2018-06-06 13:00:00,11619.0 -2018-06-06 14:00:00,11672.0 -2018-06-06 15:00:00,11684.0 -2018-06-06 16:00:00,11927.0 -2018-06-06 17:00:00,12127.0 -2018-06-06 18:00:00,12137.0 -2018-06-06 19:00:00,12190.0 -2018-06-06 20:00:00,12145.0 -2018-06-06 21:00:00,11921.0 -2018-06-06 22:00:00,11976.0 -2018-06-06 23:00:00,11770.0 -2018-06-07 00:00:00,10987.0 -2018-06-05 01:00:00,10466.0 -2018-06-05 02:00:00,9801.0 -2018-06-05 03:00:00,9317.0 -2018-06-05 04:00:00,8985.0 -2018-06-05 05:00:00,8818.0 -2018-06-05 06:00:00,8927.0 -2018-06-05 07:00:00,9367.0 -2018-06-05 08:00:00,10177.0 -2018-06-05 09:00:00,10955.0 -2018-06-05 10:00:00,11494.0 -2018-06-05 11:00:00,11863.0 -2018-06-05 12:00:00,12116.0 -2018-06-05 13:00:00,12188.0 -2018-06-05 14:00:00,12186.0 -2018-06-05 15:00:00,12185.0 -2018-06-05 16:00:00,12053.0 -2018-06-05 17:00:00,11854.0 -2018-06-05 18:00:00,11599.0 -2018-06-05 19:00:00,11377.0 -2018-06-05 20:00:00,10941.0 -2018-06-05 21:00:00,10607.0 -2018-06-05 22:00:00,10529.0 -2018-06-05 23:00:00,10382.0 -2018-06-06 00:00:00,9720.0 -2018-06-04 01:00:00,8984.0 -2018-06-04 02:00:00,8474.0 -2018-06-04 03:00:00,8154.0 -2018-06-04 04:00:00,7968.0 -2018-06-04 05:00:00,7928.0 -2018-06-04 06:00:00,8126.0 -2018-06-04 07:00:00,8547.0 -2018-06-04 08:00:00,9435.0 -2018-06-04 09:00:00,10372.0 -2018-06-04 10:00:00,11007.0 -2018-06-04 11:00:00,11408.0 -2018-06-04 12:00:00,11773.0 -2018-06-04 13:00:00,12034.0 -2018-06-04 14:00:00,12264.0 -2018-06-04 15:00:00,12579.0 -2018-06-04 16:00:00,12870.0 -2018-06-04 17:00:00,13177.0 -2018-06-04 18:00:00,13399.0 -2018-06-04 19:00:00,13449.0 -2018-06-04 20:00:00,13244.0 -2018-06-04 21:00:00,12872.0 -2018-06-04 22:00:00,12588.0 -2018-06-04 23:00:00,12285.0 -2018-06-05 00:00:00,11407.0 -2018-06-03 01:00:00,9184.0 -2018-06-03 02:00:00,8687.0 -2018-06-03 03:00:00,8321.0 -2018-06-03 04:00:00,8104.0 -2018-06-03 05:00:00,7953.0 -2018-06-03 06:00:00,7927.0 -2018-06-03 07:00:00,7785.0 -2018-06-03 08:00:00,7930.0 -2018-06-03 09:00:00,8315.0 -2018-06-03 10:00:00,8870.0 -2018-06-03 11:00:00,9361.0 -2018-06-03 12:00:00,9786.0 -2018-06-03 13:00:00,10136.0 -2018-06-03 14:00:00,10415.0 -2018-06-03 15:00:00,10602.0 -2018-06-03 16:00:00,10766.0 -2018-06-03 17:00:00,10848.0 -2018-06-03 18:00:00,10931.0 -2018-06-03 19:00:00,10864.0 -2018-06-03 20:00:00,10680.0 -2018-06-03 21:00:00,10372.0 -2018-06-03 22:00:00,10265.0 -2018-06-03 23:00:00,10175.0 -2018-06-04 00:00:00,9624.0 -2018-06-02 01:00:00,9357.0 -2018-06-02 02:00:00,8799.0 -2018-06-02 03:00:00,8388.0 -2018-06-02 04:00:00,8153.0 -2018-06-02 05:00:00,8014.0 -2018-06-02 06:00:00,8003.0 -2018-06-02 07:00:00,8030.0 -2018-06-02 08:00:00,8275.0 -2018-06-02 09:00:00,8761.0 -2018-06-02 10:00:00,9253.0 -2018-06-02 11:00:00,9671.0 -2018-06-02 12:00:00,9893.0 -2018-06-02 13:00:00,10082.0 -2018-06-02 14:00:00,10112.0 -2018-06-02 15:00:00,10169.0 -2018-06-02 16:00:00,10282.0 -2018-06-02 17:00:00,10469.0 -2018-06-02 18:00:00,10665.0 -2018-06-02 19:00:00,10645.0 -2018-06-02 20:00:00,10495.0 -2018-06-02 21:00:00,10336.0 -2018-06-02 22:00:00,10457.0 -2018-06-02 23:00:00,10311.0 -2018-06-03 00:00:00,9808.0 -2018-06-01 01:00:00,13078.0 -2018-06-01 02:00:00,11985.0 -2018-06-01 03:00:00,11243.0 -2018-06-01 04:00:00,10649.0 -2018-06-01 05:00:00,10360.0 -2018-06-01 06:00:00,10355.0 -2018-06-01 07:00:00,10790.0 -2018-06-01 08:00:00,11856.0 -2018-06-01 09:00:00,13120.0 -2018-06-01 10:00:00,13593.0 -2018-06-01 11:00:00,13743.0 -2018-06-01 12:00:00,13736.0 -2018-06-01 13:00:00,13724.0 -2018-06-01 14:00:00,13640.0 -2018-06-01 15:00:00,13648.0 -2018-06-01 16:00:00,13623.0 -2018-06-01 17:00:00,13536.0 -2018-06-01 18:00:00,13278.0 -2018-06-01 19:00:00,12835.0 -2018-06-01 20:00:00,12161.0 -2018-06-01 21:00:00,11488.0 -2018-06-01 22:00:00,11168.0 -2018-06-01 23:00:00,10852.0 -2018-06-02 00:00:00,10148.0 -2018-05-31 01:00:00,11765.0 -2018-05-31 02:00:00,11010.0 -2018-05-31 03:00:00,10433.0 -2018-05-31 04:00:00,10054.0 -2018-05-31 05:00:00,9885.0 -2018-05-31 06:00:00,9985.0 -2018-05-31 07:00:00,10460.0 -2018-05-31 08:00:00,11579.0 -2018-05-31 09:00:00,12863.0 -2018-05-31 10:00:00,13880.0 -2018-05-31 11:00:00,14915.0 -2018-05-31 12:00:00,15973.0 -2018-05-31 13:00:00,16686.0 -2018-05-31 14:00:00,17306.0 -2018-05-31 15:00:00,17842.0 -2018-05-31 16:00:00,18048.0 -2018-05-31 17:00:00,18151.0 -2018-05-31 18:00:00,18397.0 -2018-05-31 19:00:00,18352.0 -2018-05-31 20:00:00,17781.0 -2018-05-31 21:00:00,17018.0 -2018-05-31 22:00:00,16370.0 -2018-05-31 23:00:00,15752.0 -2018-06-01 00:00:00,14417.0 -2018-05-30 01:00:00,11916.0 -2018-05-30 02:00:00,11025.0 -2018-05-30 03:00:00,10443.0 -2018-05-30 04:00:00,10052.0 -2018-05-30 05:00:00,9886.0 -2018-05-30 06:00:00,10020.0 -2018-05-30 07:00:00,10579.0 -2018-05-30 08:00:00,11524.0 -2018-05-30 09:00:00,12495.0 -2018-05-30 10:00:00,13202.0 -2018-05-30 11:00:00,13857.0 -2018-05-30 12:00:00,14567.0 -2018-05-30 13:00:00,15382.0 -2018-05-30 14:00:00,16066.0 -2018-05-30 15:00:00,16592.0 -2018-05-30 16:00:00,16763.0 -2018-05-30 17:00:00,16345.0 -2018-05-30 18:00:00,15872.0 -2018-05-30 19:00:00,15462.0 -2018-05-30 20:00:00,14964.0 -2018-05-30 21:00:00,14455.0 -2018-05-30 22:00:00,14250.0 -2018-05-30 23:00:00,13820.0 -2018-05-31 00:00:00,12863.0 -2018-05-29 01:00:00,12167.0 -2018-05-29 02:00:00,11210.0 -2018-05-29 03:00:00,10513.0 -2018-05-29 04:00:00,10037.0 -2018-05-29 05:00:00,9805.0 -2018-05-29 06:00:00,9846.0 -2018-05-29 07:00:00,10298.0 -2018-05-29 08:00:00,11363.0 -2018-05-29 09:00:00,12590.0 -2018-05-29 10:00:00,13642.0 -2018-05-29 11:00:00,14471.0 -2018-05-29 12:00:00,15241.0 -2018-05-29 13:00:00,15769.0 -2018-05-29 14:00:00,16374.0 -2018-05-29 15:00:00,16891.0 -2018-05-29 16:00:00,17081.0 -2018-05-29 17:00:00,17065.0 -2018-05-29 18:00:00,16896.0 -2018-05-29 19:00:00,16619.0 -2018-05-29 20:00:00,15783.0 -2018-05-29 21:00:00,14962.0 -2018-05-29 22:00:00,14669.0 -2018-05-29 23:00:00,14183.0 -2018-05-30 00:00:00,13069.0 -2018-05-28 01:00:00,12791.0 -2018-05-28 02:00:00,11788.0 -2018-05-28 03:00:00,11015.0 -2018-05-28 04:00:00,10532.0 -2018-05-28 05:00:00,10164.0 -2018-05-28 06:00:00,9924.0 -2018-05-28 07:00:00,9845.0 -2018-05-28 08:00:00,10260.0 -2018-05-28 09:00:00,11213.0 -2018-05-28 10:00:00,12510.0 -2018-05-28 11:00:00,13794.0 -2018-05-28 12:00:00,14627.0 -2018-05-28 13:00:00,14768.0 -2018-05-28 14:00:00,14962.0 -2018-05-28 15:00:00,15255.0 -2018-05-28 16:00:00,15741.0 -2018-05-28 17:00:00,16394.0 -2018-05-28 18:00:00,17002.0 -2018-05-28 19:00:00,17254.0 -2018-05-28 20:00:00,16910.0 -2018-05-28 21:00:00,16147.0 -2018-05-28 22:00:00,15485.0 -2018-05-28 23:00:00,14768.0 -2018-05-29 00:00:00,13487.0 -2018-05-27 01:00:00,11795.0 -2018-05-27 02:00:00,10951.0 -2018-05-27 03:00:00,10262.0 -2018-05-27 04:00:00,9781.0 -2018-05-27 05:00:00,9406.0 -2018-05-27 06:00:00,9243.0 -2018-05-27 07:00:00,9028.0 -2018-05-27 08:00:00,9350.0 -2018-05-27 09:00:00,10276.0 -2018-05-27 10:00:00,11589.0 -2018-05-27 11:00:00,12974.0 -2018-05-27 12:00:00,14161.0 -2018-05-27 13:00:00,15126.0 -2018-05-27 14:00:00,15768.0 -2018-05-27 15:00:00,16225.0 -2018-05-27 16:00:00,16602.0 -2018-05-27 17:00:00,16888.0 -2018-05-27 18:00:00,17001.0 -2018-05-27 19:00:00,16883.0 -2018-05-27 20:00:00,16525.0 -2018-05-27 21:00:00,15825.0 -2018-05-27 22:00:00,15320.0 -2018-05-27 23:00:00,14761.0 -2018-05-28 00:00:00,13811.0 -2018-05-26 01:00:00,11981.0 -2018-05-26 02:00:00,11112.0 -2018-05-26 03:00:00,10430.0 -2018-05-26 04:00:00,9919.0 -2018-05-26 05:00:00,9633.0 -2018-05-26 06:00:00,9477.0 -2018-05-26 07:00:00,9395.0 -2018-05-26 08:00:00,9617.0 -2018-05-26 09:00:00,10373.0 -2018-05-26 10:00:00,11357.0 -2018-05-26 11:00:00,12278.0 -2018-05-26 12:00:00,13214.0 -2018-05-26 13:00:00,13941.0 -2018-05-26 14:00:00,14463.0 -2018-05-26 15:00:00,14890.0 -2018-05-26 16:00:00,15247.0 -2018-05-26 17:00:00,15577.0 -2018-05-26 18:00:00,15768.0 -2018-05-26 19:00:00,15755.0 -2018-05-26 20:00:00,15410.0 -2018-05-26 21:00:00,14819.0 -2018-05-26 22:00:00,14221.0 -2018-05-26 23:00:00,13717.0 -2018-05-27 00:00:00,12798.0 -2018-05-25 01:00:00,10915.0 -2018-05-25 02:00:00,10062.0 -2018-05-25 03:00:00,9480.0 -2018-05-25 04:00:00,9089.0 -2018-05-25 05:00:00,8862.0 -2018-05-25 06:00:00,8940.0 -2018-05-25 07:00:00,9309.0 -2018-05-25 08:00:00,10284.0 -2018-05-25 09:00:00,11435.0 -2018-05-25 10:00:00,12444.0 -2018-05-25 11:00:00,13288.0 -2018-05-25 12:00:00,14122.0 -2018-05-25 13:00:00,14820.0 -2018-05-25 14:00:00,15351.0 -2018-05-25 15:00:00,15854.0 -2018-05-25 16:00:00,16168.0 -2018-05-25 17:00:00,16159.0 -2018-05-25 18:00:00,15925.0 -2018-05-25 19:00:00,15412.0 -2018-05-25 20:00:00,14785.0 -2018-05-25 21:00:00,14342.0 -2018-05-25 22:00:00,14172.0 -2018-05-25 23:00:00,13789.0 -2018-05-26 00:00:00,12979.0 -2018-05-24 01:00:00,9660.0 -2018-05-24 02:00:00,9019.0 -2018-05-24 03:00:00,8575.0 -2018-05-24 04:00:00,8343.0 -2018-05-24 05:00:00,8246.0 -2018-05-24 06:00:00,8366.0 -2018-05-24 07:00:00,8792.0 -2018-05-24 08:00:00,9686.0 -2018-05-24 09:00:00,10730.0 -2018-05-24 10:00:00,11437.0 -2018-05-24 11:00:00,11984.0 -2018-05-24 12:00:00,12533.0 -2018-05-24 13:00:00,12987.0 -2018-05-24 14:00:00,13381.0 -2018-05-24 15:00:00,13864.0 -2018-05-24 16:00:00,14216.0 -2018-05-24 17:00:00,14517.0 -2018-05-24 18:00:00,14775.0 -2018-05-24 19:00:00,14710.0 -2018-05-24 20:00:00,14354.0 -2018-05-24 21:00:00,13868.0 -2018-05-24 22:00:00,13583.0 -2018-05-24 23:00:00,13066.0 -2018-05-25 00:00:00,12043.0 -2018-05-23 01:00:00,9079.0 -2018-05-23 02:00:00,8593.0 -2018-05-23 03:00:00,8319.0 -2018-05-23 04:00:00,8104.0 -2018-05-23 05:00:00,8079.0 -2018-05-23 06:00:00,8253.0 -2018-05-23 07:00:00,8688.0 -2018-05-23 08:00:00,9518.0 -2018-05-23 09:00:00,10359.0 -2018-05-23 10:00:00,10859.0 -2018-05-23 11:00:00,11239.0 -2018-05-23 12:00:00,11583.0 -2018-05-23 13:00:00,11808.0 -2018-05-23 14:00:00,12003.0 -2018-05-23 15:00:00,12312.0 -2018-05-23 16:00:00,12478.0 -2018-05-23 17:00:00,12587.0 -2018-05-23 18:00:00,12673.0 -2018-05-23 19:00:00,12612.0 -2018-05-23 20:00:00,12244.0 -2018-05-23 21:00:00,11906.0 -2018-05-23 22:00:00,11823.0 -2018-05-23 23:00:00,11445.0 -2018-05-24 00:00:00,10567.0 -2018-05-22 01:00:00,9065.0 -2018-05-22 02:00:00,8618.0 -2018-05-22 03:00:00,8333.0 -2018-05-22 04:00:00,8145.0 -2018-05-22 05:00:00,8110.0 -2018-05-22 06:00:00,8278.0 -2018-05-22 07:00:00,8838.0 -2018-05-22 08:00:00,9669.0 -2018-05-22 09:00:00,10371.0 -2018-05-22 10:00:00,10753.0 -2018-05-22 11:00:00,10976.0 -2018-05-22 12:00:00,11145.0 -2018-05-22 13:00:00,11289.0 -2018-05-22 14:00:00,11322.0 -2018-05-22 15:00:00,11381.0 -2018-05-22 16:00:00,11292.0 -2018-05-22 17:00:00,11196.0 -2018-05-22 18:00:00,11091.0 -2018-05-22 19:00:00,10956.0 -2018-05-22 20:00:00,10739.0 -2018-05-22 21:00:00,10616.0 -2018-05-22 22:00:00,10776.0 -2018-05-22 23:00:00,10517.0 -2018-05-23 00:00:00,9819.0 -2018-05-21 01:00:00,8317.0 -2018-05-21 02:00:00,8003.0 -2018-05-21 03:00:00,7811.0 -2018-05-21 04:00:00,7723.0 -2018-05-21 05:00:00,7728.0 -2018-05-21 06:00:00,7960.0 -2018-05-21 07:00:00,8666.0 -2018-05-21 08:00:00,9631.0 -2018-05-21 09:00:00,10464.0 -2018-05-21 10:00:00,10752.0 -2018-05-21 11:00:00,10929.0 -2018-05-21 12:00:00,11211.0 -2018-05-21 13:00:00,11333.0 -2018-05-21 14:00:00,11276.0 -2018-05-21 15:00:00,11298.0 -2018-05-21 16:00:00,11170.0 -2018-05-21 17:00:00,11076.0 -2018-05-21 18:00:00,11033.0 -2018-05-21 19:00:00,10948.0 -2018-05-21 20:00:00,10756.0 -2018-05-21 21:00:00,10649.0 -2018-05-21 22:00:00,10808.0 -2018-05-21 23:00:00,10479.0 -2018-05-22 00:00:00,9763.0 -2018-05-20 01:00:00,8650.0 -2018-05-20 02:00:00,8233.0 -2018-05-20 03:00:00,7878.0 -2018-05-20 04:00:00,7670.0 -2018-05-20 05:00:00,7547.0 -2018-05-20 06:00:00,7510.0 -2018-05-20 07:00:00,7488.0 -2018-05-20 08:00:00,7552.0 -2018-05-20 09:00:00,7775.0 -2018-05-20 10:00:00,8141.0 -2018-05-20 11:00:00,8510.0 -2018-05-20 12:00:00,8784.0 -2018-05-20 13:00:00,8931.0 -2018-05-20 14:00:00,8927.0 -2018-05-20 15:00:00,8809.0 -2018-05-20 16:00:00,8820.0 -2018-05-20 17:00:00,8763.0 -2018-05-20 18:00:00,8767.0 -2018-05-20 19:00:00,8895.0 -2018-05-20 20:00:00,8953.0 -2018-05-20 21:00:00,9082.0 -2018-05-20 22:00:00,9385.0 -2018-05-20 23:00:00,9266.0 -2018-05-21 00:00:00,8791.0 -2018-05-19 01:00:00,9018.0 -2018-05-19 02:00:00,8535.0 -2018-05-19 03:00:00,8236.0 -2018-05-19 04:00:00,8006.0 -2018-05-19 05:00:00,7873.0 -2018-05-19 06:00:00,7951.0 -2018-05-19 07:00:00,8107.0 -2018-05-19 08:00:00,8344.0 -2018-05-19 09:00:00,8764.0 -2018-05-19 10:00:00,9207.0 -2018-05-19 11:00:00,9565.0 -2018-05-19 12:00:00,9752.0 -2018-05-19 13:00:00,9874.0 -2018-05-19 14:00:00,9789.0 -2018-05-19 15:00:00,9733.0 -2018-05-19 16:00:00,9634.0 -2018-05-19 17:00:00,9607.0 -2018-05-19 18:00:00,9663.0 -2018-05-19 19:00:00,9643.0 -2018-05-19 20:00:00,9623.0 -2018-05-19 21:00:00,9618.0 -2018-05-19 22:00:00,9826.0 -2018-05-19 23:00:00,9639.0 -2018-05-20 00:00:00,9198.0 -2018-05-18 01:00:00,8968.0 -2018-05-18 02:00:00,8482.0 -2018-05-18 03:00:00,8191.0 -2018-05-18 04:00:00,8004.0 -2018-05-18 05:00:00,7944.0 -2018-05-18 06:00:00,8145.0 -2018-05-18 07:00:00,8585.0 -2018-05-18 08:00:00,9282.0 -2018-05-18 09:00:00,10030.0 -2018-05-18 10:00:00,10505.0 -2018-05-18 11:00:00,10763.0 -2018-05-18 12:00:00,10982.0 -2018-05-18 13:00:00,11149.0 -2018-05-18 14:00:00,11178.0 -2018-05-18 15:00:00,11257.0 -2018-05-18 16:00:00,11151.0 -2018-05-18 17:00:00,10914.0 -2018-05-18 18:00:00,10725.0 -2018-05-18 19:00:00,10503.0 -2018-05-18 20:00:00,10356.0 -2018-05-18 21:00:00,10373.0 -2018-05-18 22:00:00,10502.0 -2018-05-18 23:00:00,10254.0 -2018-05-19 00:00:00,9625.0 -2018-05-17 01:00:00,9478.0 -2018-05-17 02:00:00,8879.0 -2018-05-17 03:00:00,8506.0 -2018-05-17 04:00:00,8287.0 -2018-05-17 05:00:00,8162.0 -2018-05-17 06:00:00,8275.0 -2018-05-17 07:00:00,8752.0 -2018-05-17 08:00:00,9591.0 -2018-05-17 09:00:00,10490.0 -2018-05-17 10:00:00,10995.0 -2018-05-17 11:00:00,11298.0 -2018-05-17 12:00:00,11554.0 -2018-05-17 13:00:00,11756.0 -2018-05-17 14:00:00,11885.0 -2018-05-17 15:00:00,12082.0 -2018-05-17 16:00:00,12103.0 -2018-05-17 17:00:00,11997.0 -2018-05-17 18:00:00,11817.0 -2018-05-17 19:00:00,11503.0 -2018-05-17 20:00:00,10981.0 -2018-05-17 21:00:00,10673.0 -2018-05-17 22:00:00,10817.0 -2018-05-17 23:00:00,10474.0 -2018-05-18 00:00:00,9725.0 -2018-05-16 01:00:00,8917.0 -2018-05-16 02:00:00,8467.0 -2018-05-16 03:00:00,8165.0 -2018-05-16 04:00:00,7984.0 -2018-05-16 05:00:00,7934.0 -2018-05-16 06:00:00,8093.0 -2018-05-16 07:00:00,8584.0 -2018-05-16 08:00:00,9405.0 -2018-05-16 09:00:00,10183.0 -2018-05-16 10:00:00,10647.0 -2018-05-16 11:00:00,10991.0 -2018-05-16 12:00:00,11366.0 -2018-05-16 13:00:00,11601.0 -2018-05-16 14:00:00,11788.0 -2018-05-16 15:00:00,12038.0 -2018-05-16 16:00:00,12221.0 -2018-05-16 17:00:00,12346.0 -2018-05-16 18:00:00,12436.0 -2018-05-16 19:00:00,12378.0 -2018-05-16 20:00:00,12049.0 -2018-05-16 21:00:00,11734.0 -2018-05-16 22:00:00,11682.0 -2018-05-16 23:00:00,11254.0 -2018-05-17 00:00:00,10338.0 -2018-05-15 01:00:00,9551.0 -2018-05-15 02:00:00,9026.0 -2018-05-15 03:00:00,8645.0 -2018-05-15 04:00:00,8412.0 -2018-05-15 05:00:00,8341.0 -2018-05-15 06:00:00,8528.0 -2018-05-15 07:00:00,9085.0 -2018-05-15 08:00:00,10030.0 -2018-05-15 09:00:00,10766.0 -2018-05-15 10:00:00,10897.0 -2018-05-15 11:00:00,11276.0 -2018-05-15 12:00:00,11583.0 -2018-05-15 13:00:00,11692.0 -2018-05-15 14:00:00,11786.0 -2018-05-15 15:00:00,11957.0 -2018-05-15 16:00:00,11868.0 -2018-05-15 17:00:00,11547.0 -2018-05-15 18:00:00,11319.0 -2018-05-15 19:00:00,11130.0 -2018-05-15 20:00:00,10805.0 -2018-05-15 21:00:00,10567.0 -2018-05-15 22:00:00,10707.0 -2018-05-15 23:00:00,10416.0 -2018-05-16 00:00:00,9634.0 -2018-05-14 01:00:00,8201.0 -2018-05-14 02:00:00,7882.0 -2018-05-14 03:00:00,7684.0 -2018-05-14 04:00:00,7620.0 -2018-05-14 05:00:00,7632.0 -2018-05-14 06:00:00,7871.0 -2018-05-14 07:00:00,8504.0 -2018-05-14 08:00:00,9600.0 -2018-05-14 09:00:00,10455.0 -2018-05-14 10:00:00,10684.0 -2018-05-14 11:00:00,10998.0 -2018-05-14 12:00:00,11206.0 -2018-05-14 13:00:00,11304.0 -2018-05-14 14:00:00,11552.0 -2018-05-14 15:00:00,11913.0 -2018-05-14 16:00:00,12098.0 -2018-05-14 17:00:00,12229.0 -2018-05-14 18:00:00,12242.0 -2018-05-14 19:00:00,12104.0 -2018-05-14 20:00:00,11793.0 -2018-05-14 21:00:00,11464.0 -2018-05-14 22:00:00,11519.0 -2018-05-14 23:00:00,11044.0 -2018-05-15 00:00:00,10361.0 -2018-05-13 01:00:00,8478.0 -2018-05-13 02:00:00,8122.0 -2018-05-13 03:00:00,7884.0 -2018-05-13 04:00:00,7668.0 -2018-05-13 05:00:00,7580.0 -2018-05-13 06:00:00,7542.0 -2018-05-13 07:00:00,7621.0 -2018-05-13 08:00:00,7662.0 -2018-05-13 09:00:00,7917.0 -2018-05-13 10:00:00,8287.0 -2018-05-13 11:00:00,8543.0 -2018-05-13 12:00:00,8611.0 -2018-05-13 13:00:00,8637.0 -2018-05-13 14:00:00,8686.0 -2018-05-13 15:00:00,8658.0 -2018-05-13 16:00:00,8659.0 -2018-05-13 17:00:00,8667.0 -2018-05-13 18:00:00,8654.0 -2018-05-13 19:00:00,8712.0 -2018-05-13 20:00:00,8810.0 -2018-05-13 21:00:00,8879.0 -2018-05-13 22:00:00,9202.0 -2018-05-13 23:00:00,9084.0 -2018-05-14 00:00:00,8677.0 -2018-05-12 01:00:00,8872.0 -2018-05-12 02:00:00,8391.0 -2018-05-12 03:00:00,8145.0 -2018-05-12 04:00:00,7920.0 -2018-05-12 05:00:00,7861.0 -2018-05-12 06:00:00,7920.0 -2018-05-12 07:00:00,8118.0 -2018-05-12 08:00:00,8392.0 -2018-05-12 09:00:00,8880.0 -2018-05-12 10:00:00,9271.0 -2018-05-12 11:00:00,9462.0 -2018-05-12 12:00:00,9605.0 -2018-05-12 13:00:00,9506.0 -2018-05-12 14:00:00,9345.0 -2018-05-12 15:00:00,9269.0 -2018-05-12 16:00:00,9302.0 -2018-05-12 17:00:00,9164.0 -2018-05-12 18:00:00,9060.0 -2018-05-12 19:00:00,9080.0 -2018-05-12 20:00:00,9080.0 -2018-05-12 21:00:00,9181.0 -2018-05-12 22:00:00,9472.0 -2018-05-12 23:00:00,9349.0 -2018-05-13 00:00:00,8946.0 -2018-05-11 01:00:00,9118.0 -2018-05-11 02:00:00,8542.0 -2018-05-11 03:00:00,8192.0 -2018-05-11 04:00:00,7959.0 -2018-05-11 05:00:00,7895.0 -2018-05-11 06:00:00,8072.0 -2018-05-11 07:00:00,8594.0 -2018-05-11 08:00:00,9338.0 -2018-05-11 09:00:00,9999.0 -2018-05-11 10:00:00,10362.0 -2018-05-11 11:00:00,10607.0 -2018-05-11 12:00:00,10720.0 -2018-05-11 13:00:00,10775.0 -2018-05-11 14:00:00,10757.0 -2018-05-11 15:00:00,10742.0 -2018-05-11 16:00:00,10631.0 -2018-05-11 17:00:00,10420.0 -2018-05-11 18:00:00,10297.0 -2018-05-11 19:00:00,10125.0 -2018-05-11 20:00:00,10011.0 -2018-05-11 21:00:00,10039.0 -2018-05-11 22:00:00,10334.0 -2018-05-11 23:00:00,10080.0 -2018-05-12 00:00:00,9470.0 -2018-05-10 01:00:00,9804.0 -2018-05-10 02:00:00,9093.0 -2018-05-10 03:00:00,8671.0 -2018-05-10 04:00:00,8397.0 -2018-05-10 05:00:00,8267.0 -2018-05-10 06:00:00,8394.0 -2018-05-10 07:00:00,8877.0 -2018-05-10 08:00:00,9642.0 -2018-05-10 09:00:00,10509.0 -2018-05-10 10:00:00,10944.0 -2018-05-10 11:00:00,11116.0 -2018-05-10 12:00:00,11394.0 -2018-05-10 13:00:00,11654.0 -2018-05-10 14:00:00,11865.0 -2018-05-10 15:00:00,12098.0 -2018-05-10 16:00:00,12212.0 -2018-05-10 17:00:00,12025.0 -2018-05-10 18:00:00,11823.0 -2018-05-10 19:00:00,11562.0 -2018-05-10 20:00:00,11228.0 -2018-05-10 21:00:00,10978.0 -2018-05-10 22:00:00,11125.0 -2018-05-10 23:00:00,10709.0 -2018-05-11 00:00:00,9897.0 -2018-05-09 01:00:00,9696.0 -2018-05-09 02:00:00,9061.0 -2018-05-09 03:00:00,8665.0 -2018-05-09 04:00:00,8405.0 -2018-05-09 05:00:00,8296.0 -2018-05-09 06:00:00,8449.0 -2018-05-09 07:00:00,8979.0 -2018-05-09 08:00:00,9902.0 -2018-05-09 09:00:00,10725.0 -2018-05-09 10:00:00,10926.0 -2018-05-09 11:00:00,11171.0 -2018-05-09 12:00:00,11487.0 -2018-05-09 13:00:00,11747.0 -2018-05-09 14:00:00,12062.0 -2018-05-09 15:00:00,12456.0 -2018-05-09 16:00:00,12526.0 -2018-05-09 17:00:00,12697.0 -2018-05-09 18:00:00,12943.0 -2018-05-09 19:00:00,12760.0 -2018-05-09 20:00:00,12495.0 -2018-05-09 21:00:00,12201.0 -2018-05-09 22:00:00,12171.0 -2018-05-09 23:00:00,11650.0 -2018-05-10 00:00:00,10707.0 -2018-05-08 01:00:00,9176.0 -2018-05-08 02:00:00,8666.0 -2018-05-08 03:00:00,8331.0 -2018-05-08 04:00:00,8111.0 -2018-05-08 05:00:00,8048.0 -2018-05-08 06:00:00,8191.0 -2018-05-08 07:00:00,8684.0 -2018-05-08 08:00:00,9504.0 -2018-05-08 09:00:00,10370.0 -2018-05-08 10:00:00,10914.0 -2018-05-08 11:00:00,11337.0 -2018-05-08 12:00:00,11712.0 -2018-05-08 13:00:00,12002.0 -2018-05-08 14:00:00,12308.0 -2018-05-08 15:00:00,12627.0 -2018-05-08 16:00:00,12868.0 -2018-05-08 17:00:00,13018.0 -2018-05-08 18:00:00,13116.0 -2018-05-08 19:00:00,12907.0 -2018-05-08 20:00:00,12336.0 -2018-05-08 21:00:00,11973.0 -2018-05-08 22:00:00,12018.0 -2018-05-08 23:00:00,11506.0 -2018-05-09 00:00:00,10583.0 -2018-05-07 01:00:00,8245.0 -2018-05-07 02:00:00,7880.0 -2018-05-07 03:00:00,7627.0 -2018-05-07 04:00:00,7509.0 -2018-05-07 05:00:00,7532.0 -2018-05-07 06:00:00,7809.0 -2018-05-07 07:00:00,8344.0 -2018-05-07 08:00:00,9219.0 -2018-05-07 09:00:00,10054.0 -2018-05-07 10:00:00,10485.0 -2018-05-07 11:00:00,10803.0 -2018-05-07 12:00:00,11114.0 -2018-05-07 13:00:00,11286.0 -2018-05-07 14:00:00,11388.0 -2018-05-07 15:00:00,11552.0 -2018-05-07 16:00:00,11626.0 -2018-05-07 17:00:00,11665.0 -2018-05-07 18:00:00,11675.0 -2018-05-07 19:00:00,11595.0 -2018-05-07 20:00:00,11310.0 -2018-05-07 21:00:00,11061.0 -2018-05-07 22:00:00,11211.0 -2018-05-07 23:00:00,10785.0 -2018-05-08 00:00:00,9985.0 -2018-05-06 01:00:00,8777.0 -2018-05-06 02:00:00,8259.0 -2018-05-06 03:00:00,7900.0 -2018-05-06 04:00:00,7665.0 -2018-05-06 05:00:00,7472.0 -2018-05-06 06:00:00,7418.0 -2018-05-06 07:00:00,7438.0 -2018-05-06 08:00:00,7425.0 -2018-05-06 09:00:00,7683.0 -2018-05-06 10:00:00,8081.0 -2018-05-06 11:00:00,8461.0 -2018-05-06 12:00:00,8787.0 -2018-05-06 13:00:00,8992.0 -2018-05-06 14:00:00,9199.0 -2018-05-06 15:00:00,9280.0 -2018-05-06 16:00:00,9393.0 -2018-05-06 17:00:00,9484.0 -2018-05-06 18:00:00,9520.0 -2018-05-06 19:00:00,9529.0 -2018-05-06 20:00:00,9382.0 -2018-05-06 21:00:00,9289.0 -2018-05-06 22:00:00,9531.0 -2018-05-06 23:00:00,9321.0 -2018-05-07 00:00:00,8763.0 -2018-05-05 01:00:00,9356.0 -2018-05-05 02:00:00,8770.0 -2018-05-05 03:00:00,8365.0 -2018-05-05 04:00:00,8072.0 -2018-05-05 05:00:00,7901.0 -2018-05-05 06:00:00,7939.0 -2018-05-05 07:00:00,8036.0 -2018-05-05 08:00:00,8188.0 -2018-05-05 09:00:00,8700.0 -2018-05-05 10:00:00,9297.0 -2018-05-05 11:00:00,9744.0 -2018-05-05 12:00:00,10154.0 -2018-05-05 13:00:00,10374.0 -2018-05-05 14:00:00,10537.0 -2018-05-05 15:00:00,10655.0 -2018-05-05 16:00:00,10887.0 -2018-05-05 17:00:00,11085.0 -2018-05-05 18:00:00,11305.0 -2018-05-05 19:00:00,11313.0 -2018-05-05 20:00:00,11063.0 -2018-05-05 21:00:00,10679.0 -2018-05-05 22:00:00,10648.0 -2018-05-05 23:00:00,10156.0 -2018-05-06 00:00:00,9446.0 -2018-05-04 01:00:00,9408.0 -2018-05-04 02:00:00,8913.0 -2018-05-04 03:00:00,8616.0 -2018-05-04 04:00:00,8440.0 -2018-05-04 05:00:00,8361.0 -2018-05-04 06:00:00,8534.0 -2018-05-04 07:00:00,9079.0 -2018-05-04 08:00:00,9883.0 -2018-05-04 09:00:00,10615.0 -2018-05-04 10:00:00,10961.0 -2018-05-04 11:00:00,11134.0 -2018-05-04 12:00:00,11230.0 -2018-05-04 13:00:00,11338.0 -2018-05-04 14:00:00,11465.0 -2018-05-04 15:00:00,11642.0 -2018-05-04 16:00:00,11713.0 -2018-05-04 17:00:00,11725.0 -2018-05-04 18:00:00,11773.0 -2018-05-04 19:00:00,11674.0 -2018-05-04 20:00:00,11386.0 -2018-05-04 21:00:00,11071.0 -2018-05-04 22:00:00,11129.0 -2018-05-04 23:00:00,10798.0 -2018-05-05 00:00:00,10065.0 -2018-05-03 01:00:00,10307.0 -2018-05-03 02:00:00,9648.0 -2018-05-03 03:00:00,9160.0 -2018-05-03 04:00:00,8862.0 -2018-05-03 05:00:00,8776.0 -2018-05-03 06:00:00,8917.0 -2018-05-03 07:00:00,9406.0 -2018-05-03 08:00:00,10244.0 -2018-05-03 09:00:00,10986.0 -2018-05-03 10:00:00,11306.0 -2018-05-03 11:00:00,11612.0 -2018-05-03 12:00:00,11883.0 -2018-05-03 13:00:00,11881.0 -2018-05-03 14:00:00,11937.0 -2018-05-03 15:00:00,12025.0 -2018-05-03 16:00:00,11846.0 -2018-05-03 17:00:00,11547.0 -2018-05-03 18:00:00,11441.0 -2018-05-03 19:00:00,11298.0 -2018-05-03 20:00:00,11080.0 -2018-05-03 21:00:00,11055.0 -2018-05-03 22:00:00,11177.0 -2018-05-03 23:00:00,10788.0 -2018-05-04 00:00:00,10083.0 -2018-05-02 01:00:00,9874.0 -2018-05-02 02:00:00,9313.0 -2018-05-02 03:00:00,8893.0 -2018-05-02 04:00:00,8591.0 -2018-05-02 05:00:00,8459.0 -2018-05-02 06:00:00,8577.0 -2018-05-02 07:00:00,9164.0 -2018-05-02 08:00:00,9990.0 -2018-05-02 09:00:00,10840.0 -2018-05-02 10:00:00,11373.0 -2018-05-02 11:00:00,11752.0 -2018-05-02 12:00:00,12070.0 -2018-05-02 13:00:00,12418.0 -2018-05-02 14:00:00,12731.0 -2018-05-02 15:00:00,13164.0 -2018-05-02 16:00:00,13417.0 -2018-05-02 17:00:00,13545.0 -2018-05-02 18:00:00,13370.0 -2018-05-02 19:00:00,13106.0 -2018-05-02 20:00:00,12898.0 -2018-05-02 21:00:00,12914.0 -2018-05-02 22:00:00,12670.0 -2018-05-02 23:00:00,12055.0 -2018-05-03 00:00:00,11160.0 -2018-05-01 01:00:00,8937.0 -2018-05-01 02:00:00,8411.0 -2018-05-01 03:00:00,8110.0 -2018-05-01 04:00:00,7917.0 -2018-05-01 05:00:00,7844.0 -2018-05-01 06:00:00,8020.0 -2018-05-01 07:00:00,8519.0 -2018-05-01 08:00:00,9293.0 -2018-05-01 09:00:00,10048.0 -2018-05-01 10:00:00,10537.0 -2018-05-01 11:00:00,10896.0 -2018-05-01 12:00:00,11235.0 -2018-05-01 13:00:00,11482.0 -2018-05-01 14:00:00,11667.0 -2018-05-01 15:00:00,11909.0 -2018-05-01 16:00:00,11998.0 -2018-05-01 17:00:00,12011.0 -2018-05-01 18:00:00,11971.0 -2018-05-01 19:00:00,11851.0 -2018-05-01 20:00:00,11609.0 -2018-05-01 21:00:00,11592.0 -2018-05-01 22:00:00,11870.0 -2018-05-01 23:00:00,11448.0 -2018-05-02 00:00:00,10647.0 -2018-04-30 01:00:00,8381.0 -2018-04-30 02:00:00,8098.0 -2018-04-30 03:00:00,7916.0 -2018-04-30 04:00:00,7813.0 -2018-04-30 05:00:00,7975.0 -2018-04-30 06:00:00,8300.0 -2018-04-30 07:00:00,9010.0 -2018-04-30 08:00:00,9799.0 -2018-04-30 09:00:00,10368.0 -2018-04-30 10:00:00,10514.0 -2018-04-30 11:00:00,10676.0 -2018-04-30 12:00:00,10815.0 -2018-04-30 13:00:00,10858.0 -2018-04-30 14:00:00,10905.0 -2018-04-30 15:00:00,10977.0 -2018-04-30 16:00:00,10963.0 -2018-04-30 17:00:00,10867.0 -2018-04-30 18:00:00,10788.0 -2018-04-30 19:00:00,10707.0 -2018-04-30 20:00:00,10491.0 -2018-04-30 21:00:00,10461.0 -2018-04-30 22:00:00,10755.0 -2018-04-30 23:00:00,10358.0 -2018-05-01 00:00:00,9658.0 -2018-04-29 01:00:00,8752.0 -2018-04-29 02:00:00,8403.0 -2018-04-29 03:00:00,8243.0 -2018-04-29 04:00:00,8098.0 -2018-04-29 05:00:00,8087.0 -2018-04-29 06:00:00,8113.0 -2018-04-29 07:00:00,8254.0 -2018-04-29 08:00:00,8231.0 -2018-04-29 09:00:00,8415.0 -2018-04-29 10:00:00,8560.0 -2018-04-29 11:00:00,8705.0 -2018-04-29 12:00:00,8747.0 -2018-04-29 13:00:00,8733.0 -2018-04-29 14:00:00,8713.0 -2018-04-29 15:00:00,8645.0 -2018-04-29 16:00:00,8605.0 -2018-04-29 17:00:00,8547.0 -2018-04-29 18:00:00,8581.0 -2018-04-29 19:00:00,8664.0 -2018-04-29 20:00:00,8696.0 -2018-04-29 21:00:00,8928.0 -2018-04-29 22:00:00,9391.0 -2018-04-29 23:00:00,9228.0 -2018-04-30 00:00:00,8808.0 -2018-04-28 01:00:00,9127.0 -2018-04-28 02:00:00,8698.0 -2018-04-28 03:00:00,8478.0 -2018-04-28 04:00:00,8289.0 -2018-04-28 05:00:00,8281.0 -2018-04-28 06:00:00,8376.0 -2018-04-28 07:00:00,8624.0 -2018-04-28 08:00:00,8806.0 -2018-04-28 09:00:00,9061.0 -2018-04-28 10:00:00,9317.0 -2018-04-28 11:00:00,9466.0 -2018-04-28 12:00:00,9544.0 -2018-04-28 13:00:00,9455.0 -2018-04-28 14:00:00,9331.0 -2018-04-28 15:00:00,9164.0 -2018-04-28 16:00:00,9039.0 -2018-04-28 17:00:00,8901.0 -2018-04-28 18:00:00,8896.0 -2018-04-28 19:00:00,8858.0 -2018-04-28 20:00:00,8908.0 -2018-04-28 21:00:00,9110.0 -2018-04-28 22:00:00,9618.0 -2018-04-28 23:00:00,9491.0 -2018-04-29 00:00:00,9139.0 -2018-04-27 01:00:00,8832.0 -2018-04-27 02:00:00,8399.0 -2018-04-27 03:00:00,8146.0 -2018-04-27 04:00:00,8021.0 -2018-04-27 05:00:00,8015.0 -2018-04-27 06:00:00,8270.0 -2018-04-27 07:00:00,8900.0 -2018-04-27 08:00:00,9617.0 -2018-04-27 09:00:00,10161.0 -2018-04-27 10:00:00,10395.0 -2018-04-27 11:00:00,10491.0 -2018-04-27 12:00:00,10590.0 -2018-04-27 13:00:00,10582.0 -2018-04-27 14:00:00,10581.0 -2018-04-27 15:00:00,10605.0 -2018-04-27 16:00:00,10463.0 -2018-04-27 17:00:00,10312.0 -2018-04-27 18:00:00,10209.0 -2018-04-27 19:00:00,10160.0 -2018-04-27 20:00:00,10232.0 -2018-04-27 21:00:00,10362.0 -2018-04-27 22:00:00,10518.0 -2018-04-27 23:00:00,10251.0 -2018-04-28 00:00:00,9685.0 -2018-04-26 01:00:00,9056.0 -2018-04-26 02:00:00,8720.0 -2018-04-26 03:00:00,8516.0 -2018-04-26 04:00:00,8389.0 -2018-04-26 05:00:00,8435.0 -2018-04-26 06:00:00,8692.0 -2018-04-26 07:00:00,9363.0 -2018-04-26 08:00:00,10095.0 -2018-04-26 09:00:00,10596.0 -2018-04-26 10:00:00,10727.0 -2018-04-26 11:00:00,10693.0 -2018-04-26 12:00:00,10767.0 -2018-04-26 13:00:00,10719.0 -2018-04-26 14:00:00,10722.0 -2018-04-26 15:00:00,10739.0 -2018-04-26 16:00:00,10659.0 -2018-04-26 17:00:00,10542.0 -2018-04-26 18:00:00,10409.0 -2018-04-26 19:00:00,10263.0 -2018-04-26 20:00:00,10077.0 -2018-04-26 21:00:00,10159.0 -2018-04-26 22:00:00,10454.0 -2018-04-26 23:00:00,10082.0 -2018-04-27 00:00:00,9430.0 -2018-04-25 01:00:00,8876.0 -2018-04-25 02:00:00,8474.0 -2018-04-25 03:00:00,8251.0 -2018-04-25 04:00:00,8156.0 -2018-04-25 05:00:00,8156.0 -2018-04-25 06:00:00,8432.0 -2018-04-25 07:00:00,9091.0 -2018-04-25 08:00:00,9872.0 -2018-04-25 09:00:00,10456.0 -2018-04-25 10:00:00,10629.0 -2018-04-25 11:00:00,10708.0 -2018-04-25 12:00:00,10764.0 -2018-04-25 13:00:00,10713.0 -2018-04-25 14:00:00,10643.0 -2018-04-25 15:00:00,10666.0 -2018-04-25 16:00:00,10556.0 -2018-04-25 17:00:00,10437.0 -2018-04-25 18:00:00,10286.0 -2018-04-25 19:00:00,10184.0 -2018-04-25 20:00:00,10037.0 -2018-04-25 21:00:00,10223.0 -2018-04-25 22:00:00,10592.0 -2018-04-25 23:00:00,10251.0 -2018-04-26 00:00:00,9662.0 -2018-04-24 01:00:00,8927.0 -2018-04-24 02:00:00,8519.0 -2018-04-24 03:00:00,8288.0 -2018-04-24 04:00:00,8131.0 -2018-04-24 05:00:00,8104.0 -2018-04-24 06:00:00,8324.0 -2018-04-24 07:00:00,9007.0 -2018-04-24 08:00:00,9785.0 -2018-04-24 09:00:00,10395.0 -2018-04-24 10:00:00,10565.0 -2018-04-24 11:00:00,10621.0 -2018-04-24 12:00:00,10729.0 -2018-04-24 13:00:00,10770.0 -2018-04-24 14:00:00,10811.0 -2018-04-24 15:00:00,10854.0 -2018-04-24 16:00:00,10776.0 -2018-04-24 17:00:00,10689.0 -2018-04-24 18:00:00,10558.0 -2018-04-24 19:00:00,10446.0 -2018-04-24 20:00:00,10207.0 -2018-04-24 21:00:00,10307.0 -2018-04-24 22:00:00,10603.0 -2018-04-24 23:00:00,10176.0 -2018-04-25 00:00:00,9499.0 -2018-04-23 01:00:00,8403.0 -2018-04-23 02:00:00,8158.0 -2018-04-23 03:00:00,8046.0 -2018-04-23 04:00:00,7993.0 -2018-04-23 05:00:00,8108.0 -2018-04-23 06:00:00,8397.0 -2018-04-23 07:00:00,9159.0 -2018-04-23 08:00:00,9916.0 -2018-04-23 09:00:00,10598.0 -2018-04-23 10:00:00,10716.0 -2018-04-23 11:00:00,10763.0 -2018-04-23 12:00:00,10755.0 -2018-04-23 13:00:00,10733.0 -2018-04-23 14:00:00,10702.0 -2018-04-23 15:00:00,10714.0 -2018-04-23 16:00:00,10615.0 -2018-04-23 17:00:00,10506.0 -2018-04-23 18:00:00,10377.0 -2018-04-23 19:00:00,10287.0 -2018-04-23 20:00:00,10198.0 -2018-04-23 21:00:00,10402.0 -2018-04-23 22:00:00,10601.0 -2018-04-23 23:00:00,10194.0 -2018-04-24 00:00:00,9519.0 -2018-04-22 01:00:00,8801.0 -2018-04-22 02:00:00,8429.0 -2018-04-22 03:00:00,8188.0 -2018-04-22 04:00:00,8024.0 -2018-04-22 05:00:00,7973.0 -2018-04-22 06:00:00,7996.0 -2018-04-22 07:00:00,8148.0 -2018-04-22 08:00:00,8204.0 -2018-04-22 09:00:00,8429.0 -2018-04-22 10:00:00,8625.0 -2018-04-22 11:00:00,8727.0 -2018-04-22 12:00:00,8808.0 -2018-04-22 13:00:00,8817.0 -2018-04-22 14:00:00,8773.0 -2018-04-22 15:00:00,8754.0 -2018-04-22 16:00:00,8661.0 -2018-04-22 17:00:00,8627.0 -2018-04-22 18:00:00,8690.0 -2018-04-22 19:00:00,8747.0 -2018-04-22 20:00:00,8792.0 -2018-04-22 21:00:00,9049.0 -2018-04-22 22:00:00,9499.0 -2018-04-22 23:00:00,9305.0 -2018-04-23 00:00:00,8891.0 -2018-04-21 01:00:00,9142.0 -2018-04-21 02:00:00,8801.0 -2018-04-21 03:00:00,8545.0 -2018-04-21 04:00:00,8426.0 -2018-04-21 05:00:00,8357.0 -2018-04-21 06:00:00,8454.0 -2018-04-21 07:00:00,8716.0 -2018-04-21 08:00:00,9022.0 -2018-04-21 09:00:00,9359.0 -2018-04-21 10:00:00,9722.0 -2018-04-21 11:00:00,9866.0 -2018-04-21 12:00:00,9968.0 -2018-04-21 13:00:00,9857.0 -2018-04-21 14:00:00,9733.0 -2018-04-21 15:00:00,9509.0 -2018-04-21 16:00:00,9365.0 -2018-04-21 17:00:00,9230.0 -2018-04-21 18:00:00,9191.0 -2018-04-21 19:00:00,9152.0 -2018-04-21 20:00:00,9268.0 -2018-04-21 21:00:00,9600.0 -2018-04-21 22:00:00,9820.0 -2018-04-21 23:00:00,9649.0 -2018-04-22 00:00:00,9199.0 -2018-04-20 01:00:00,9584.0 -2018-04-20 02:00:00,9240.0 -2018-04-20 03:00:00,9053.0 -2018-04-20 04:00:00,8930.0 -2018-04-20 05:00:00,8959.0 -2018-04-20 06:00:00,9236.0 -2018-04-20 07:00:00,9932.0 -2018-04-20 08:00:00,10661.0 -2018-04-20 09:00:00,11084.0 -2018-04-20 10:00:00,11112.0 -2018-04-20 11:00:00,11016.0 -2018-04-20 12:00:00,10964.0 -2018-04-20 13:00:00,10851.0 -2018-04-20 14:00:00,10775.0 -2018-04-20 15:00:00,10744.0 -2018-04-20 16:00:00,10618.0 -2018-04-20 17:00:00,10420.0 -2018-04-20 18:00:00,10275.0 -2018-04-20 19:00:00,10109.0 -2018-04-20 20:00:00,9984.0 -2018-04-20 21:00:00,10159.0 -2018-04-20 22:00:00,10474.0 -2018-04-20 23:00:00,10199.0 -2018-04-21 00:00:00,9695.0 -2018-04-19 01:00:00,10210.0 -2018-04-19 02:00:00,9845.0 -2018-04-19 03:00:00,9597.0 -2018-04-19 04:00:00,9447.0 -2018-04-19 05:00:00,9376.0 -2018-04-19 06:00:00,9613.0 -2018-04-19 07:00:00,10296.0 -2018-04-19 08:00:00,11058.0 -2018-04-19 09:00:00,11436.0 -2018-04-19 10:00:00,11531.0 -2018-04-19 11:00:00,11492.0 -2018-04-19 12:00:00,11447.0 -2018-04-19 13:00:00,11335.0 -2018-04-19 14:00:00,11194.0 -2018-04-19 15:00:00,11107.0 -2018-04-19 16:00:00,10926.0 -2018-04-19 17:00:00,10726.0 -2018-04-19 18:00:00,10548.0 -2018-04-19 19:00:00,10450.0 -2018-04-19 20:00:00,10372.0 -2018-04-19 21:00:00,10609.0 -2018-04-19 22:00:00,10949.0 -2018-04-19 23:00:00,10686.0 -2018-04-20 00:00:00,10111.0 -2018-04-18 01:00:00,9935.0 -2018-04-18 02:00:00,9624.0 -2018-04-18 03:00:00,9451.0 -2018-04-18 04:00:00,9315.0 -2018-04-18 05:00:00,9301.0 -2018-04-18 06:00:00,9596.0 -2018-04-18 07:00:00,10323.0 -2018-04-18 08:00:00,11130.0 -2018-04-18 09:00:00,11684.0 -2018-04-18 10:00:00,11896.0 -2018-04-18 11:00:00,12044.0 -2018-04-18 12:00:00,12111.0 -2018-04-18 13:00:00,12160.0 -2018-04-18 14:00:00,12137.0 -2018-04-18 15:00:00,12164.0 -2018-04-18 16:00:00,12042.0 -2018-04-18 17:00:00,11952.0 -2018-04-18 18:00:00,11909.0 -2018-04-18 19:00:00,11942.0 -2018-04-18 20:00:00,11870.0 -2018-04-18 21:00:00,12009.0 -2018-04-18 22:00:00,11923.0 -2018-04-18 23:00:00,11455.0 -2018-04-19 00:00:00,10805.0 -2018-04-17 01:00:00,10266.0 -2018-04-17 02:00:00,9843.0 -2018-04-17 03:00:00,9677.0 -2018-04-17 04:00:00,9537.0 -2018-04-17 05:00:00,9582.0 -2018-04-17 06:00:00,9857.0 -2018-04-17 07:00:00,10605.0 -2018-04-17 08:00:00,11386.0 -2018-04-17 09:00:00,11792.0 -2018-04-17 10:00:00,11889.0 -2018-04-17 11:00:00,11859.0 -2018-04-17 12:00:00,11846.0 -2018-04-17 13:00:00,11705.0 -2018-04-17 14:00:00,11556.0 -2018-04-17 15:00:00,11437.0 -2018-04-17 16:00:00,11262.0 -2018-04-17 17:00:00,11035.0 -2018-04-17 18:00:00,10931.0 -2018-04-17 19:00:00,10848.0 -2018-04-17 20:00:00,10758.0 -2018-04-17 21:00:00,11112.0 -2018-04-17 22:00:00,11462.0 -2018-04-17 23:00:00,11163.0 -2018-04-18 00:00:00,10576.0 -2018-04-16 01:00:00,9691.0 -2018-04-16 02:00:00,9436.0 -2018-04-16 03:00:00,9293.0 -2018-04-16 04:00:00,9241.0 -2018-04-16 05:00:00,9360.0 -2018-04-16 06:00:00,9709.0 -2018-04-16 07:00:00,10482.0 -2018-04-16 08:00:00,11432.0 -2018-04-16 09:00:00,12079.0 -2018-04-16 10:00:00,12327.0 -2018-04-16 11:00:00,12355.0 -2018-04-16 12:00:00,12378.0 -2018-04-16 13:00:00,12305.0 -2018-04-16 14:00:00,12277.0 -2018-04-16 15:00:00,12272.0 -2018-04-16 16:00:00,12174.0 -2018-04-16 17:00:00,12073.0 -2018-04-16 18:00:00,12028.0 -2018-04-16 19:00:00,12009.0 -2018-04-16 20:00:00,11873.0 -2018-04-16 21:00:00,12005.0 -2018-04-16 22:00:00,12019.0 -2018-04-16 23:00:00,11552.0 -2018-04-17 00:00:00,10874.0 -2018-04-15 01:00:00,9604.0 -2018-04-15 02:00:00,9193.0 -2018-04-15 03:00:00,9016.0 -2018-04-15 04:00:00,8823.0 -2018-04-15 05:00:00,8796.0 -2018-04-15 06:00:00,8819.0 -2018-04-15 07:00:00,9013.0 -2018-04-15 08:00:00,9223.0 -2018-04-15 09:00:00,9414.0 -2018-04-15 10:00:00,9863.0 -2018-04-15 11:00:00,10136.0 -2018-04-15 12:00:00,10326.0 -2018-04-15 13:00:00,10319.0 -2018-04-15 14:00:00,10181.0 -2018-04-15 15:00:00,10121.0 -2018-04-15 16:00:00,10044.0 -2018-04-15 17:00:00,10076.0 -2018-04-15 18:00:00,10134.0 -2018-04-15 19:00:00,10292.0 -2018-04-15 20:00:00,10391.0 -2018-04-15 21:00:00,10716.0 -2018-04-15 22:00:00,10800.0 -2018-04-15 23:00:00,10544.0 -2018-04-16 00:00:00,10117.0 -2018-04-14 01:00:00,9498.0 -2018-04-14 02:00:00,9161.0 -2018-04-14 03:00:00,8948.0 -2018-04-14 04:00:00,8839.0 -2018-04-14 05:00:00,8712.0 -2018-04-14 06:00:00,8838.0 -2018-04-14 07:00:00,9098.0 -2018-04-14 08:00:00,9537.0 -2018-04-14 09:00:00,9880.0 -2018-04-14 10:00:00,10282.0 -2018-04-14 11:00:00,10612.0 -2018-04-14 12:00:00,10709.0 -2018-04-14 13:00:00,10673.0 -2018-04-14 14:00:00,10529.0 -2018-04-14 15:00:00,10377.0 -2018-04-14 16:00:00,10352.0 -2018-04-14 17:00:00,10330.0 -2018-04-14 18:00:00,10450.0 -2018-04-14 19:00:00,10494.0 -2018-04-14 20:00:00,10595.0 -2018-04-14 21:00:00,10772.0 -2018-04-14 22:00:00,10688.0 -2018-04-14 23:00:00,10473.0 -2018-04-15 00:00:00,10034.0 -2018-04-13 01:00:00,9067.0 -2018-04-13 02:00:00,8703.0 -2018-04-13 03:00:00,8472.0 -2018-04-13 04:00:00,8363.0 -2018-04-13 05:00:00,8364.0 -2018-04-13 06:00:00,8575.0 -2018-04-13 07:00:00,9260.0 -2018-04-13 08:00:00,10101.0 -2018-04-13 09:00:00,10576.0 -2018-04-13 10:00:00,10811.0 -2018-04-13 11:00:00,10913.0 -2018-04-13 12:00:00,11081.0 -2018-04-13 13:00:00,11136.0 -2018-04-13 14:00:00,11149.0 -2018-04-13 15:00:00,11207.0 -2018-04-13 16:00:00,11169.0 -2018-04-13 17:00:00,10954.0 -2018-04-13 18:00:00,10929.0 -2018-04-13 19:00:00,10792.0 -2018-04-13 20:00:00,10780.0 -2018-04-13 21:00:00,11004.0 -2018-04-13 22:00:00,10961.0 -2018-04-13 23:00:00,10632.0 -2018-04-14 00:00:00,10033.0 -2018-04-12 01:00:00,9111.0 -2018-04-12 02:00:00,8749.0 -2018-04-12 03:00:00,8502.0 -2018-04-12 04:00:00,8366.0 -2018-04-12 05:00:00,8333.0 -2018-04-12 06:00:00,8536.0 -2018-04-12 07:00:00,9228.0 -2018-04-12 08:00:00,10053.0 -2018-04-12 09:00:00,10523.0 -2018-04-12 10:00:00,10808.0 -2018-04-12 11:00:00,10781.0 -2018-04-12 12:00:00,10867.0 -2018-04-12 13:00:00,10882.0 -2018-04-12 14:00:00,10877.0 -2018-04-12 15:00:00,10924.0 -2018-04-12 16:00:00,10798.0 -2018-04-12 17:00:00,10579.0 -2018-04-12 18:00:00,10404.0 -2018-04-12 19:00:00,10215.0 -2018-04-12 20:00:00,10065.0 -2018-04-12 21:00:00,10464.0 -2018-04-12 22:00:00,10604.0 -2018-04-12 23:00:00,10223.0 -2018-04-13 00:00:00,9645.0 -2018-04-11 01:00:00,9619.0 -2018-04-11 02:00:00,9245.0 -2018-04-11 03:00:00,9004.0 -2018-04-11 04:00:00,8853.0 -2018-04-11 05:00:00,8890.0 -2018-04-11 06:00:00,9129.0 -2018-04-11 07:00:00,9916.0 -2018-04-11 08:00:00,10760.0 -2018-04-11 09:00:00,11171.0 -2018-04-11 10:00:00,11210.0 -2018-04-11 11:00:00,11139.0 -2018-04-11 12:00:00,11084.0 -2018-04-11 13:00:00,10997.0 -2018-04-11 14:00:00,10883.0 -2018-04-11 15:00:00,10875.0 -2018-04-11 16:00:00,10769.0 -2018-04-11 17:00:00,10636.0 -2018-04-11 18:00:00,10525.0 -2018-04-11 19:00:00,10466.0 -2018-04-11 20:00:00,10446.0 -2018-04-11 21:00:00,10784.0 -2018-04-11 22:00:00,10779.0 -2018-04-11 23:00:00,10361.0 -2018-04-12 00:00:00,9774.0 -2018-04-10 01:00:00,9974.0 -2018-04-10 02:00:00,9589.0 -2018-04-10 03:00:00,9430.0 -2018-04-10 04:00:00,9337.0 -2018-04-10 05:00:00,9348.0 -2018-04-10 06:00:00,9650.0 -2018-04-10 07:00:00,10388.0 -2018-04-10 08:00:00,11255.0 -2018-04-10 09:00:00,11657.0 -2018-04-10 10:00:00,11650.0 -2018-04-10 11:00:00,11552.0 -2018-04-10 12:00:00,11492.0 -2018-04-10 13:00:00,11360.0 -2018-04-10 14:00:00,11228.0 -2018-04-10 15:00:00,11221.0 -2018-04-10 16:00:00,11080.0 -2018-04-10 17:00:00,10977.0 -2018-04-10 18:00:00,10906.0 -2018-04-10 19:00:00,10849.0 -2018-04-10 20:00:00,10831.0 -2018-04-10 21:00:00,11166.0 -2018-04-10 22:00:00,11257.0 -2018-04-10 23:00:00,10820.0 -2018-04-11 00:00:00,10204.0 -2018-04-09 01:00:00,9427.0 -2018-04-09 02:00:00,9184.0 -2018-04-09 03:00:00,9047.0 -2018-04-09 04:00:00,9006.0 -2018-04-09 05:00:00,9067.0 -2018-04-09 06:00:00,9352.0 -2018-04-09 07:00:00,10139.0 -2018-04-09 08:00:00,11058.0 -2018-04-09 09:00:00,11612.0 -2018-04-09 10:00:00,11824.0 -2018-04-09 11:00:00,11858.0 -2018-04-09 12:00:00,11855.0 -2018-04-09 13:00:00,11822.0 -2018-04-09 14:00:00,11802.0 -2018-04-09 15:00:00,11766.0 -2018-04-09 16:00:00,11641.0 -2018-04-09 17:00:00,11579.0 -2018-04-09 18:00:00,11505.0 -2018-04-09 19:00:00,11465.0 -2018-04-09 20:00:00,11399.0 -2018-04-09 21:00:00,11634.0 -2018-04-09 22:00:00,11637.0 -2018-04-09 23:00:00,11192.0 -2018-04-10 00:00:00,10510.0 -2018-04-08 01:00:00,9691.0 -2018-04-08 02:00:00,9396.0 -2018-04-08 03:00:00,9210.0 -2018-04-08 04:00:00,9116.0 -2018-04-08 05:00:00,9073.0 -2018-04-08 06:00:00,9148.0 -2018-04-08 07:00:00,9324.0 -2018-04-08 08:00:00,9471.0 -2018-04-08 09:00:00,9461.0 -2018-04-08 10:00:00,9569.0 -2018-04-08 11:00:00,9657.0 -2018-04-08 12:00:00,9599.0 -2018-04-08 13:00:00,9612.0 -2018-04-08 14:00:00,9529.0 -2018-04-08 15:00:00,9483.0 -2018-04-08 16:00:00,9399.0 -2018-04-08 17:00:00,9327.0 -2018-04-08 18:00:00,9482.0 -2018-04-08 19:00:00,9649.0 -2018-04-08 20:00:00,9934.0 -2018-04-08 21:00:00,10397.0 -2018-04-08 22:00:00,10516.0 -2018-04-08 23:00:00,10212.0 -2018-04-09 00:00:00,9774.0 -2018-04-07 01:00:00,10379.0 -2018-04-07 02:00:00,9992.0 -2018-04-07 03:00:00,9774.0 -2018-04-07 04:00:00,9625.0 -2018-04-07 05:00:00,9602.0 -2018-04-07 06:00:00,9723.0 -2018-04-07 07:00:00,10042.0 -2018-04-07 08:00:00,10355.0 -2018-04-07 09:00:00,10570.0 -2018-04-07 10:00:00,10742.0 -2018-04-07 11:00:00,10811.0 -2018-04-07 12:00:00,10786.0 -2018-04-07 13:00:00,10675.0 -2018-04-07 14:00:00,10502.0 -2018-04-07 15:00:00,10205.0 -2018-04-07 16:00:00,10019.0 -2018-04-07 17:00:00,9812.0 -2018-04-07 18:00:00,9730.0 -2018-04-07 19:00:00,9706.0 -2018-04-07 20:00:00,9787.0 -2018-04-07 21:00:00,10293.0 -2018-04-07 22:00:00,10609.0 -2018-04-07 23:00:00,10416.0 -2018-04-08 00:00:00,10036.0 -2018-04-06 01:00:00,9943.0 -2018-04-06 02:00:00,9565.0 -2018-04-06 03:00:00,9334.0 -2018-04-06 04:00:00,9180.0 -2018-04-06 05:00:00,9177.0 -2018-04-06 06:00:00,9411.0 -2018-04-06 07:00:00,10073.0 -2018-04-06 08:00:00,10993.0 -2018-04-06 09:00:00,11501.0 -2018-04-06 10:00:00,11787.0 -2018-04-06 11:00:00,11913.0 -2018-04-06 12:00:00,11887.0 -2018-04-06 13:00:00,11785.0 -2018-04-06 14:00:00,11855.0 -2018-04-06 15:00:00,11899.0 -2018-04-06 16:00:00,11767.0 -2018-04-06 17:00:00,11512.0 -2018-04-06 18:00:00,11371.0 -2018-04-06 19:00:00,11315.0 -2018-04-06 20:00:00,11322.0 -2018-04-06 21:00:00,11646.0 -2018-04-06 22:00:00,11719.0 -2018-04-06 23:00:00,11446.0 -2018-04-07 00:00:00,10889.0 -2018-04-05 01:00:00,10243.0 -2018-04-05 02:00:00,9865.0 -2018-04-05 03:00:00,9660.0 -2018-04-05 04:00:00,9541.0 -2018-04-05 05:00:00,9562.0 -2018-04-05 06:00:00,9777.0 -2018-04-05 07:00:00,10505.0 -2018-04-05 08:00:00,11493.0 -2018-04-05 09:00:00,11831.0 -2018-04-05 10:00:00,11856.0 -2018-04-05 11:00:00,11725.0 -2018-04-05 12:00:00,11649.0 -2018-04-05 13:00:00,11540.0 -2018-04-05 14:00:00,11376.0 -2018-04-05 15:00:00,11353.0 -2018-04-05 16:00:00,11295.0 -2018-04-05 17:00:00,11272.0 -2018-04-05 18:00:00,11313.0 -2018-04-05 19:00:00,11408.0 -2018-04-05 20:00:00,11381.0 -2018-04-05 21:00:00,11680.0 -2018-04-05 22:00:00,11620.0 -2018-04-05 23:00:00,11223.0 -2018-04-06 00:00:00,10556.0 -2018-04-04 01:00:00,10014.0 -2018-04-04 02:00:00,9669.0 -2018-04-04 03:00:00,9463.0 -2018-04-04 04:00:00,9415.0 -2018-04-04 05:00:00,9471.0 -2018-04-04 06:00:00,9811.0 -2018-04-04 07:00:00,10528.0 -2018-04-04 08:00:00,11462.0 -2018-04-04 09:00:00,11957.0 -2018-04-04 10:00:00,12104.0 -2018-04-04 11:00:00,12013.0 -2018-04-04 12:00:00,12020.0 -2018-04-04 13:00:00,11975.0 -2018-04-04 14:00:00,11900.0 -2018-04-04 15:00:00,11922.0 -2018-04-04 16:00:00,11829.0 -2018-04-04 17:00:00,11658.0 -2018-04-04 18:00:00,11568.0 -2018-04-04 19:00:00,11499.0 -2018-04-04 20:00:00,11428.0 -2018-04-04 21:00:00,11796.0 -2018-04-04 22:00:00,11919.0 -2018-04-04 23:00:00,11501.0 -2018-04-05 00:00:00,10826.0 -2018-04-03 01:00:00,9577.0 -2018-04-03 02:00:00,9171.0 -2018-04-03 03:00:00,8956.0 -2018-04-03 04:00:00,8841.0 -2018-04-03 05:00:00,8800.0 -2018-04-03 06:00:00,9042.0 -2018-04-03 07:00:00,9745.0 -2018-04-03 08:00:00,10768.0 -2018-04-03 09:00:00,11354.0 -2018-04-03 10:00:00,11628.0 -2018-04-03 11:00:00,11712.0 -2018-04-03 12:00:00,11832.0 -2018-04-03 13:00:00,11859.0 -2018-04-03 14:00:00,11782.0 -2018-04-03 15:00:00,11806.0 -2018-04-03 16:00:00,11763.0 -2018-04-03 17:00:00,11667.0 -2018-04-03 18:00:00,11642.0 -2018-04-03 19:00:00,11656.0 -2018-04-03 20:00:00,11631.0 -2018-04-03 21:00:00,11820.0 -2018-04-03 22:00:00,11676.0 -2018-04-03 23:00:00,11185.0 -2018-04-04 00:00:00,10577.0 -2018-04-02 01:00:00,9259.0 -2018-04-02 02:00:00,9073.0 -2018-04-02 03:00:00,8960.0 -2018-04-02 04:00:00,8889.0 -2018-04-02 05:00:00,9056.0 -2018-04-02 06:00:00,9383.0 -2018-04-02 07:00:00,10172.0 -2018-04-02 08:00:00,11112.0 -2018-04-02 09:00:00,11557.0 -2018-04-02 10:00:00,11656.0 -2018-04-02 11:00:00,11504.0 -2018-04-02 12:00:00,11478.0 -2018-04-02 13:00:00,11431.0 -2018-04-02 14:00:00,11345.0 -2018-04-02 15:00:00,11293.0 -2018-04-02 16:00:00,11211.0 -2018-04-02 17:00:00,11031.0 -2018-04-02 18:00:00,10900.0 -2018-04-02 19:00:00,10828.0 -2018-04-02 20:00:00,10885.0 -2018-04-02 21:00:00,11345.0 -2018-04-02 22:00:00,11300.0 -2018-04-02 23:00:00,10903.0 -2018-04-03 00:00:00,10225.0 -2018-04-01 01:00:00,9127.0 -2018-04-01 02:00:00,8829.0 -2018-04-01 03:00:00,8632.0 -2018-04-01 04:00:00,8571.0 -2018-04-01 05:00:00,8543.0 -2018-04-01 06:00:00,8691.0 -2018-04-01 07:00:00,8881.0 -2018-04-01 08:00:00,9055.0 -2018-04-01 09:00:00,9050.0 -2018-04-01 10:00:00,9201.0 -2018-04-01 11:00:00,9348.0 -2018-04-01 12:00:00,9390.0 -2018-04-01 13:00:00,9338.0 -2018-04-01 14:00:00,9194.0 -2018-04-01 15:00:00,9111.0 -2018-04-01 16:00:00,9049.0 -2018-04-01 17:00:00,9027.0 -2018-04-01 18:00:00,9056.0 -2018-04-01 19:00:00,9192.0 -2018-04-01 20:00:00,9454.0 -2018-04-01 21:00:00,9995.0 -2018-04-01 22:00:00,10172.0 -2018-04-01 23:00:00,9983.0 -2018-04-02 00:00:00,9660.0 -2018-03-31 01:00:00,9096.0 -2018-03-31 02:00:00,8768.0 -2018-03-31 03:00:00,8551.0 -2018-03-31 04:00:00,8467.0 -2018-03-31 05:00:00,8382.0 -2018-03-31 06:00:00,8468.0 -2018-03-31 07:00:00,8666.0 -2018-03-31 08:00:00,9106.0 -2018-03-31 09:00:00,9383.0 -2018-03-31 10:00:00,9918.0 -2018-03-31 11:00:00,10099.0 -2018-03-31 12:00:00,10193.0 -2018-03-31 13:00:00,10095.0 -2018-03-31 14:00:00,9817.0 -2018-03-31 15:00:00,9583.0 -2018-03-31 16:00:00,9394.0 -2018-03-31 17:00:00,9228.0 -2018-03-31 18:00:00,9152.0 -2018-03-31 19:00:00,9214.0 -2018-03-31 20:00:00,9325.0 -2018-03-31 21:00:00,9850.0 -2018-03-31 22:00:00,10016.0 -2018-03-31 23:00:00,9857.0 -2018-04-01 00:00:00,9556.0 -2018-03-30 01:00:00,9512.0 -2018-03-30 02:00:00,9118.0 -2018-03-30 03:00:00,8835.0 -2018-03-30 04:00:00,8705.0 -2018-03-30 05:00:00,8693.0 -2018-03-30 06:00:00,8883.0 -2018-03-30 07:00:00,9384.0 -2018-03-30 08:00:00,10022.0 -2018-03-30 09:00:00,10298.0 -2018-03-30 10:00:00,10516.0 -2018-03-30 11:00:00,10521.0 -2018-03-30 12:00:00,10434.0 -2018-03-30 13:00:00,10282.0 -2018-03-30 14:00:00,10104.0 -2018-03-30 15:00:00,9991.0 -2018-03-30 16:00:00,9852.0 -2018-03-30 17:00:00,9727.0 -2018-03-30 18:00:00,9670.0 -2018-03-30 19:00:00,9733.0 -2018-03-30 20:00:00,9822.0 -2018-03-30 21:00:00,10154.0 -2018-03-30 22:00:00,10193.0 -2018-03-30 23:00:00,9973.0 -2018-03-31 00:00:00,9545.0 -2018-03-29 01:00:00,9299.0 -2018-03-29 02:00:00,8897.0 -2018-03-29 03:00:00,8664.0 -2018-03-29 04:00:00,8522.0 -2018-03-29 05:00:00,8513.0 -2018-03-29 06:00:00,8769.0 -2018-03-29 07:00:00,9317.0 -2018-03-29 08:00:00,10247.0 -2018-03-29 09:00:00,10702.0 -2018-03-29 10:00:00,10991.0 -2018-03-29 11:00:00,11067.0 -2018-03-29 12:00:00,11078.0 -2018-03-29 13:00:00,11030.0 -2018-03-29 14:00:00,10994.0 -2018-03-29 15:00:00,10994.0 -2018-03-29 16:00:00,10936.0 -2018-03-29 17:00:00,10866.0 -2018-03-29 18:00:00,10845.0 -2018-03-29 19:00:00,10804.0 -2018-03-29 20:00:00,10707.0 -2018-03-29 21:00:00,11048.0 -2018-03-29 22:00:00,11009.0 -2018-03-29 23:00:00,10674.0 -2018-03-30 00:00:00,10095.0 -2018-03-28 01:00:00,9485.0 -2018-03-28 02:00:00,9094.0 -2018-03-28 03:00:00,8894.0 -2018-03-28 04:00:00,8723.0 -2018-03-28 05:00:00,8699.0 -2018-03-28 06:00:00,8953.0 -2018-03-28 07:00:00,9583.0 -2018-03-28 08:00:00,10483.0 -2018-03-28 09:00:00,10905.0 -2018-03-28 10:00:00,11129.0 -2018-03-28 11:00:00,11133.0 -2018-03-28 12:00:00,11099.0 -2018-03-28 13:00:00,10976.0 -2018-03-28 14:00:00,10911.0 -2018-03-28 15:00:00,10847.0 -2018-03-28 16:00:00,10710.0 -2018-03-28 17:00:00,10567.0 -2018-03-28 18:00:00,10473.0 -2018-03-28 19:00:00,10490.0 -2018-03-28 20:00:00,10478.0 -2018-03-28 21:00:00,10890.0 -2018-03-28 22:00:00,10823.0 -2018-03-28 23:00:00,10435.0 -2018-03-29 00:00:00,9866.0 -2018-03-27 01:00:00,9560.0 -2018-03-27 02:00:00,9152.0 -2018-03-27 03:00:00,8890.0 -2018-03-27 04:00:00,8743.0 -2018-03-27 05:00:00,8650.0 -2018-03-27 06:00:00,8878.0 -2018-03-27 07:00:00,9469.0 -2018-03-27 08:00:00,10402.0 -2018-03-27 09:00:00,10904.0 -2018-03-27 10:00:00,11133.0 -2018-03-27 11:00:00,11260.0 -2018-03-27 12:00:00,11356.0 -2018-03-27 13:00:00,11297.0 -2018-03-27 14:00:00,11193.0 -2018-03-27 15:00:00,11174.0 -2018-03-27 16:00:00,11099.0 -2018-03-27 17:00:00,10990.0 -2018-03-27 18:00:00,10909.0 -2018-03-27 19:00:00,10860.0 -2018-03-27 20:00:00,10801.0 -2018-03-27 21:00:00,11085.0 -2018-03-27 22:00:00,10973.0 -2018-03-27 23:00:00,10604.0 -2018-03-28 00:00:00,10034.0 -2018-03-26 01:00:00,9554.0 -2018-03-26 02:00:00,9289.0 -2018-03-26 03:00:00,9157.0 -2018-03-26 04:00:00,9128.0 -2018-03-26 05:00:00,9168.0 -2018-03-26 06:00:00,9462.0 -2018-03-26 07:00:00,10198.0 -2018-03-26 08:00:00,11165.0 -2018-03-26 09:00:00,11549.0 -2018-03-26 10:00:00,11606.0 -2018-03-26 11:00:00,11470.0 -2018-03-26 12:00:00,11410.0 -2018-03-26 13:00:00,11365.0 -2018-03-26 14:00:00,11271.0 -2018-03-26 15:00:00,11212.0 -2018-03-26 16:00:00,11112.0 -2018-03-26 17:00:00,11023.0 -2018-03-26 18:00:00,10964.0 -2018-03-26 19:00:00,10937.0 -2018-03-26 20:00:00,10944.0 -2018-03-26 21:00:00,11234.0 -2018-03-26 22:00:00,11061.0 -2018-03-26 23:00:00,10669.0 -2018-03-27 00:00:00,10111.0 -2018-03-25 01:00:00,9590.0 -2018-03-25 02:00:00,9321.0 -2018-03-25 03:00:00,9096.0 -2018-03-25 04:00:00,8993.0 -2018-03-25 05:00:00,9047.0 -2018-03-25 06:00:00,9113.0 -2018-03-25 07:00:00,9289.0 -2018-03-25 08:00:00,9569.0 -2018-03-25 09:00:00,9535.0 -2018-03-25 10:00:00,9687.0 -2018-03-25 11:00:00,9716.0 -2018-03-25 12:00:00,9715.0 -2018-03-25 13:00:00,9699.0 -2018-03-25 14:00:00,9624.0 -2018-03-25 15:00:00,9501.0 -2018-03-25 16:00:00,9373.0 -2018-03-25 17:00:00,9354.0 -2018-03-25 18:00:00,9394.0 -2018-03-25 19:00:00,9527.0 -2018-03-25 20:00:00,9678.0 -2018-03-25 21:00:00,10322.0 -2018-03-25 22:00:00,10488.0 -2018-03-25 23:00:00,10265.0 -2018-03-26 00:00:00,9908.0 -2018-03-24 01:00:00,9954.0 -2018-03-24 02:00:00,9545.0 -2018-03-24 03:00:00,9230.0 -2018-03-24 04:00:00,9084.0 -2018-03-24 05:00:00,9007.0 -2018-03-24 06:00:00,9157.0 -2018-03-24 07:00:00,9363.0 -2018-03-24 08:00:00,9912.0 -2018-03-24 09:00:00,10132.0 -2018-03-24 10:00:00,10549.0 -2018-03-24 11:00:00,10782.0 -2018-03-24 12:00:00,10881.0 -2018-03-24 13:00:00,10902.0 -2018-03-24 14:00:00,10737.0 -2018-03-24 15:00:00,10544.0 -2018-03-24 16:00:00,10411.0 -2018-03-24 17:00:00,10252.0 -2018-03-24 18:00:00,10221.0 -2018-03-24 19:00:00,10266.0 -2018-03-24 20:00:00,10301.0 -2018-03-24 21:00:00,10657.0 -2018-03-24 22:00:00,10684.0 -2018-03-24 23:00:00,10459.0 -2018-03-25 00:00:00,10085.0 -2018-03-23 01:00:00,9774.0 -2018-03-23 02:00:00,9374.0 -2018-03-23 03:00:00,9148.0 -2018-03-23 04:00:00,9073.0 -2018-03-23 05:00:00,9089.0 -2018-03-23 06:00:00,9410.0 -2018-03-23 07:00:00,10116.0 -2018-03-23 08:00:00,11147.0 -2018-03-23 09:00:00,11521.0 -2018-03-23 10:00:00,11544.0 -2018-03-23 11:00:00,11464.0 -2018-03-23 12:00:00,11412.0 -2018-03-23 13:00:00,11272.0 -2018-03-23 14:00:00,11170.0 -2018-03-23 15:00:00,11065.0 -2018-03-23 16:00:00,10921.0 -2018-03-23 17:00:00,10733.0 -2018-03-23 18:00:00,10722.0 -2018-03-23 19:00:00,10768.0 -2018-03-23 20:00:00,10886.0 -2018-03-23 21:00:00,11381.0 -2018-03-23 22:00:00,11357.0 -2018-03-23 23:00:00,11120.0 -2018-03-24 00:00:00,10551.0 -2018-03-22 01:00:00,10005.0 -2018-03-22 02:00:00,9628.0 -2018-03-22 03:00:00,9432.0 -2018-03-22 04:00:00,9351.0 -2018-03-22 05:00:00,9378.0 -2018-03-22 06:00:00,9685.0 -2018-03-22 07:00:00,10391.0 -2018-03-22 08:00:00,11494.0 -2018-03-22 09:00:00,11846.0 -2018-03-22 10:00:00,11797.0 -2018-03-22 11:00:00,11652.0 -2018-03-22 12:00:00,11552.0 -2018-03-22 13:00:00,11418.0 -2018-03-22 14:00:00,11242.0 -2018-03-22 15:00:00,11177.0 -2018-03-22 16:00:00,10994.0 -2018-03-22 17:00:00,10783.0 -2018-03-22 18:00:00,10667.0 -2018-03-22 19:00:00,10610.0 -2018-03-22 20:00:00,10663.0 -2018-03-22 21:00:00,11217.0 -2018-03-22 22:00:00,11248.0 -2018-03-22 23:00:00,10913.0 -2018-03-23 00:00:00,10345.0 -2018-03-21 01:00:00,10141.0 -2018-03-21 02:00:00,9756.0 -2018-03-21 03:00:00,9485.0 -2018-03-21 04:00:00,9378.0 -2018-03-21 05:00:00,9369.0 -2018-03-21 06:00:00,9618.0 -2018-03-21 07:00:00,10349.0 -2018-03-21 08:00:00,11461.0 -2018-03-21 09:00:00,11845.0 -2018-03-21 10:00:00,11923.0 -2018-03-21 11:00:00,11837.0 -2018-03-21 12:00:00,11749.0 -2018-03-21 13:00:00,11615.0 -2018-03-21 14:00:00,11470.0 -2018-03-21 15:00:00,11414.0 -2018-03-21 16:00:00,11214.0 -2018-03-21 17:00:00,11032.0 -2018-03-21 18:00:00,10954.0 -2018-03-21 19:00:00,10966.0 -2018-03-21 20:00:00,10968.0 -2018-03-21 21:00:00,11552.0 -2018-03-21 22:00:00,11583.0 -2018-03-21 23:00:00,11207.0 -2018-03-22 00:00:00,10616.0 -2018-03-20 01:00:00,9919.0 -2018-03-20 02:00:00,9564.0 -2018-03-20 03:00:00,9354.0 -2018-03-20 04:00:00,9269.0 -2018-03-20 05:00:00,9283.0 -2018-03-20 06:00:00,9599.0 -2018-03-20 07:00:00,10354.0 -2018-03-20 08:00:00,11447.0 -2018-03-20 09:00:00,11821.0 -2018-03-20 10:00:00,11954.0 -2018-03-20 11:00:00,11916.0 -2018-03-20 12:00:00,11828.0 -2018-03-20 13:00:00,11737.0 -2018-03-20 14:00:00,11609.0 -2018-03-20 15:00:00,11563.0 -2018-03-20 16:00:00,11422.0 -2018-03-20 17:00:00,11315.0 -2018-03-20 18:00:00,11308.0 -2018-03-20 19:00:00,11387.0 -2018-03-20 20:00:00,11487.0 -2018-03-20 21:00:00,11948.0 -2018-03-20 22:00:00,11839.0 -2018-03-20 23:00:00,11409.0 -2018-03-21 00:00:00,10791.0 -2018-03-19 01:00:00,8915.0 -2018-03-19 02:00:00,8630.0 -2018-03-19 03:00:00,8491.0 -2018-03-19 04:00:00,8450.0 -2018-03-19 05:00:00,8503.0 -2018-03-19 06:00:00,8841.0 -2018-03-19 07:00:00,9652.0 -2018-03-19 08:00:00,10871.0 -2018-03-19 09:00:00,11410.0 -2018-03-19 10:00:00,11466.0 -2018-03-19 11:00:00,11337.0 -2018-03-19 12:00:00,11319.0 -2018-03-19 13:00:00,11245.0 -2018-03-19 14:00:00,11171.0 -2018-03-19 15:00:00,11135.0 -2018-03-19 16:00:00,11021.0 -2018-03-19 17:00:00,10913.0 -2018-03-19 18:00:00,10904.0 -2018-03-19 19:00:00,10957.0 -2018-03-19 20:00:00,11043.0 -2018-03-19 21:00:00,11612.0 -2018-03-19 22:00:00,11571.0 -2018-03-19 23:00:00,11203.0 -2018-03-20 00:00:00,10524.0 -2018-03-18 01:00:00,9518.0 -2018-03-18 02:00:00,9171.0 -2018-03-18 03:00:00,8994.0 -2018-03-18 04:00:00,8857.0 -2018-03-18 05:00:00,8862.0 -2018-03-18 06:00:00,8815.0 -2018-03-18 07:00:00,9029.0 -2018-03-18 08:00:00,9285.0 -2018-03-18 09:00:00,9305.0 -2018-03-18 10:00:00,9408.0 -2018-03-18 11:00:00,9406.0 -2018-03-18 12:00:00,9378.0 -2018-03-18 13:00:00,9279.0 -2018-03-18 14:00:00,9192.0 -2018-03-18 15:00:00,9076.0 -2018-03-18 16:00:00,8971.0 -2018-03-18 17:00:00,8922.0 -2018-03-18 18:00:00,8907.0 -2018-03-18 19:00:00,9017.0 -2018-03-18 20:00:00,9199.0 -2018-03-18 21:00:00,9830.0 -2018-03-18 22:00:00,9886.0 -2018-03-18 23:00:00,9664.0 -2018-03-19 00:00:00,9302.0 -2018-03-17 01:00:00,10118.0 -2018-03-17 02:00:00,9767.0 -2018-03-17 03:00:00,9484.0 -2018-03-17 04:00:00,9291.0 -2018-03-17 05:00:00,9316.0 -2018-03-17 06:00:00,9361.0 -2018-03-17 07:00:00,9672.0 -2018-03-17 08:00:00,10090.0 -2018-03-17 09:00:00,10379.0 -2018-03-17 10:00:00,10694.0 -2018-03-17 11:00:00,10835.0 -2018-03-17 12:00:00,10801.0 -2018-03-17 13:00:00,10659.0 -2018-03-17 14:00:00,10464.0 -2018-03-17 15:00:00,10097.0 -2018-03-17 16:00:00,9899.0 -2018-03-17 17:00:00,9642.0 -2018-03-17 18:00:00,9574.0 -2018-03-17 19:00:00,9508.0 -2018-03-17 20:00:00,9692.0 -2018-03-17 21:00:00,10272.0 -2018-03-17 22:00:00,10372.0 -2018-03-17 23:00:00,10251.0 -2018-03-18 00:00:00,9888.0 -2018-03-16 01:00:00,10100.0 -2018-03-16 02:00:00,9700.0 -2018-03-16 03:00:00,9510.0 -2018-03-16 04:00:00,9414.0 -2018-03-16 05:00:00,9429.0 -2018-03-16 06:00:00,9714.0 -2018-03-16 07:00:00,10386.0 -2018-03-16 08:00:00,11480.0 -2018-03-16 09:00:00,12015.0 -2018-03-16 10:00:00,12131.0 -2018-03-16 11:00:00,12142.0 -2018-03-16 12:00:00,12000.0 -2018-03-16 13:00:00,11851.0 -2018-03-16 14:00:00,11712.0 -2018-03-16 15:00:00,11586.0 -2018-03-16 16:00:00,11416.0 -2018-03-16 17:00:00,11304.0 -2018-03-16 18:00:00,11257.0 -2018-03-16 19:00:00,11322.0 -2018-03-16 20:00:00,11394.0 -2018-03-16 21:00:00,11855.0 -2018-03-16 22:00:00,11711.0 -2018-03-16 23:00:00,11322.0 -2018-03-17 00:00:00,10710.0 -2018-03-15 01:00:00,9896.0 -2018-03-15 02:00:00,9499.0 -2018-03-15 03:00:00,9246.0 -2018-03-15 04:00:00,9167.0 -2018-03-15 05:00:00,9212.0 -2018-03-15 06:00:00,9482.0 -2018-03-15 07:00:00,10226.0 -2018-03-15 08:00:00,11364.0 -2018-03-15 09:00:00,11785.0 -2018-03-15 10:00:00,11759.0 -2018-03-15 11:00:00,11634.0 -2018-03-15 12:00:00,11510.0 -2018-03-15 13:00:00,11413.0 -2018-03-15 14:00:00,11269.0 -2018-03-15 15:00:00,11220.0 -2018-03-15 16:00:00,11045.0 -2018-03-15 17:00:00,10853.0 -2018-03-15 18:00:00,10812.0 -2018-03-15 19:00:00,10816.0 -2018-03-15 20:00:00,10927.0 -2018-03-15 21:00:00,11571.0 -2018-03-15 22:00:00,11574.0 -2018-03-15 23:00:00,11257.0 -2018-03-16 00:00:00,10665.0 -2018-03-14 01:00:00,10599.0 -2018-03-14 02:00:00,10213.0 -2018-03-14 03:00:00,9994.0 -2018-03-14 04:00:00,9913.0 -2018-03-14 05:00:00,9939.0 -2018-03-14 06:00:00,10164.0 -2018-03-14 07:00:00,10892.0 -2018-03-14 08:00:00,11951.0 -2018-03-14 09:00:00,12388.0 -2018-03-14 10:00:00,12347.0 -2018-03-14 11:00:00,12152.0 -2018-03-14 12:00:00,12059.0 -2018-03-14 13:00:00,11965.0 -2018-03-14 14:00:00,11832.0 -2018-03-14 15:00:00,11646.0 -2018-03-14 16:00:00,11459.0 -2018-03-14 17:00:00,11211.0 -2018-03-14 18:00:00,11092.0 -2018-03-14 19:00:00,11041.0 -2018-03-14 20:00:00,11107.0 -2018-03-14 21:00:00,11616.0 -2018-03-14 22:00:00,11549.0 -2018-03-14 23:00:00,11159.0 -2018-03-15 00:00:00,10513.0 -2018-03-13 01:00:00,10104.0 -2018-03-13 02:00:00,9746.0 -2018-03-13 03:00:00,9535.0 -2018-03-13 04:00:00,9407.0 -2018-03-13 05:00:00,9464.0 -2018-03-13 06:00:00,9755.0 -2018-03-13 07:00:00,10463.0 -2018-03-13 08:00:00,11581.0 -2018-03-13 09:00:00,12044.0 -2018-03-13 10:00:00,12030.0 -2018-03-13 11:00:00,11935.0 -2018-03-13 12:00:00,11922.0 -2018-03-13 13:00:00,11958.0 -2018-03-13 14:00:00,11919.0 -2018-03-13 15:00:00,11984.0 -2018-03-13 16:00:00,11937.0 -2018-03-13 17:00:00,11950.0 -2018-03-13 18:00:00,11926.0 -2018-03-13 19:00:00,11900.0 -2018-03-13 20:00:00,11996.0 -2018-03-13 21:00:00,12411.0 -2018-03-13 22:00:00,12312.0 -2018-03-13 23:00:00,11883.0 -2018-03-14 00:00:00,11227.0 -2018-03-12 01:00:00,9629.0 -2018-03-12 02:00:00,9249.0 -2018-03-12 03:00:00,9071.0 -2018-03-12 04:00:00,8970.0 -2018-03-12 05:00:00,9100.0 -2018-03-12 06:00:00,9255.0 -2018-03-12 07:00:00,10031.0 -2018-03-12 08:00:00,11161.0 -2018-03-12 09:00:00,11826.0 -2018-03-12 10:00:00,11923.0 -2018-03-12 11:00:00,11973.0 -2018-03-12 12:00:00,11960.0 -2018-03-12 13:00:00,11914.0 -2018-03-12 14:00:00,11725.0 -2018-03-12 15:00:00,11599.0 -2018-03-12 16:00:00,11449.0 -2018-03-12 17:00:00,11304.0 -2018-03-12 18:00:00,11276.0 -2018-03-12 19:00:00,11321.0 -2018-03-12 20:00:00,11468.0 -2018-03-12 21:00:00,11865.0 -2018-03-12 22:00:00,11750.0 -2018-03-12 23:00:00,11362.0 -2018-03-13 00:00:00,10740.0 -2018-03-11 01:00:00,9632.0 -2018-03-11 02:00:00,9360.0 -2018-03-11 04:00:00,9126.0 -2018-03-11 05:00:00,9031.0 -2018-03-11 06:00:00,8966.0 -2018-03-11 07:00:00,9127.0 -2018-03-11 08:00:00,9384.0 -2018-03-11 09:00:00,9431.0 -2018-03-11 10:00:00,9485.0 -2018-03-11 11:00:00,9568.0 -2018-03-11 12:00:00,9628.0 -2018-03-11 13:00:00,9638.0 -2018-03-11 14:00:00,9609.0 -2018-03-11 15:00:00,9562.0 -2018-03-11 16:00:00,9460.0 -2018-03-11 17:00:00,9440.0 -2018-03-11 18:00:00,9441.0 -2018-03-11 19:00:00,9523.0 -2018-03-11 20:00:00,9877.0 -2018-03-11 21:00:00,10574.0 -2018-03-11 22:00:00,10647.0 -2018-03-11 23:00:00,10463.0 -2018-03-12 00:00:00,10052.0 -2018-03-10 01:00:00,10230.0 -2018-03-10 02:00:00,9851.0 -2018-03-10 03:00:00,9638.0 -2018-03-10 04:00:00,9545.0 -2018-03-10 05:00:00,9473.0 -2018-03-10 06:00:00,9621.0 -2018-03-10 07:00:00,9890.0 -2018-03-10 08:00:00,10109.0 -2018-03-10 09:00:00,10263.0 -2018-03-10 10:00:00,10443.0 -2018-03-10 11:00:00,10523.0 -2018-03-10 12:00:00,10483.0 -2018-03-10 13:00:00,10378.0 -2018-03-10 14:00:00,10182.0 -2018-03-10 15:00:00,9978.0 -2018-03-10 16:00:00,9807.0 -2018-03-10 17:00:00,9745.0 -2018-03-10 18:00:00,9871.0 -2018-03-10 19:00:00,10177.0 -2018-03-10 20:00:00,10783.0 -2018-03-10 21:00:00,10860.0 -2018-03-10 22:00:00,10743.0 -2018-03-10 23:00:00,10489.0 -2018-03-11 00:00:00,10056.0 -2018-03-09 01:00:00,10381.0 -2018-03-09 02:00:00,10037.0 -2018-03-09 03:00:00,9882.0 -2018-03-09 04:00:00,9762.0 -2018-03-09 05:00:00,9772.0 -2018-03-09 06:00:00,10087.0 -2018-03-09 07:00:00,10793.0 -2018-03-09 08:00:00,11543.0 -2018-03-09 09:00:00,11860.0 -2018-03-09 10:00:00,11875.0 -2018-03-09 11:00:00,11839.0 -2018-03-09 12:00:00,11792.0 -2018-03-09 13:00:00,11701.0 -2018-03-09 14:00:00,11510.0 -2018-03-09 15:00:00,11405.0 -2018-03-09 16:00:00,11200.0 -2018-03-09 17:00:00,11067.0 -2018-03-09 18:00:00,11103.0 -2018-03-09 19:00:00,11414.0 -2018-03-09 20:00:00,11984.0 -2018-03-09 21:00:00,11853.0 -2018-03-09 22:00:00,11621.0 -2018-03-09 23:00:00,11318.0 -2018-03-10 00:00:00,10750.0 -2018-03-08 01:00:00,10464.0 -2018-03-08 02:00:00,10101.0 -2018-03-08 03:00:00,9924.0 -2018-03-08 04:00:00,9822.0 -2018-03-08 05:00:00,9834.0 -2018-03-08 06:00:00,10163.0 -2018-03-08 07:00:00,10857.0 -2018-03-08 08:00:00,11710.0 -2018-03-08 09:00:00,12068.0 -2018-03-08 10:00:00,12120.0 -2018-03-08 11:00:00,12072.0 -2018-03-08 12:00:00,12062.0 -2018-03-08 13:00:00,11981.0 -2018-03-08 14:00:00,11894.0 -2018-03-08 15:00:00,11826.0 -2018-03-08 16:00:00,11735.0 -2018-03-08 17:00:00,11639.0 -2018-03-08 18:00:00,11707.0 -2018-03-08 19:00:00,11930.0 -2018-03-08 20:00:00,12442.0 -2018-03-08 21:00:00,12354.0 -2018-03-08 22:00:00,12119.0 -2018-03-08 23:00:00,11633.0 -2018-03-09 00:00:00,10952.0 -2018-03-07 01:00:00,10244.0 -2018-03-07 02:00:00,9854.0 -2018-03-07 03:00:00,9682.0 -2018-03-07 04:00:00,9613.0 -2018-03-07 05:00:00,9672.0 -2018-03-07 06:00:00,9984.0 -2018-03-07 07:00:00,10704.0 -2018-03-07 08:00:00,11611.0 -2018-03-07 09:00:00,11994.0 -2018-03-07 10:00:00,12017.0 -2018-03-07 11:00:00,11952.0 -2018-03-07 12:00:00,11958.0 -2018-03-07 13:00:00,11882.0 -2018-03-07 14:00:00,11818.0 -2018-03-07 15:00:00,11810.0 -2018-03-07 16:00:00,11711.0 -2018-03-07 17:00:00,11726.0 -2018-03-07 18:00:00,11878.0 -2018-03-07 19:00:00,12247.0 -2018-03-07 20:00:00,12535.0 -2018-03-07 21:00:00,12401.0 -2018-03-07 22:00:00,12203.0 -2018-03-07 23:00:00,11703.0 -2018-03-08 00:00:00,11031.0 -2018-03-06 01:00:00,10138.0 -2018-03-06 02:00:00,9699.0 -2018-03-06 03:00:00,9420.0 -2018-03-06 04:00:00,9329.0 -2018-03-06 05:00:00,9330.0 -2018-03-06 06:00:00,9567.0 -2018-03-06 07:00:00,10268.0 -2018-03-06 08:00:00,11185.0 -2018-03-06 09:00:00,11674.0 -2018-03-06 10:00:00,11840.0 -2018-03-06 11:00:00,11904.0 -2018-03-06 12:00:00,11893.0 -2018-03-06 13:00:00,11769.0 -2018-03-06 14:00:00,11799.0 -2018-03-06 15:00:00,11815.0 -2018-03-06 16:00:00,11706.0 -2018-03-06 17:00:00,11609.0 -2018-03-06 18:00:00,11711.0 -2018-03-06 19:00:00,11990.0 -2018-03-06 20:00:00,12274.0 -2018-03-06 21:00:00,12146.0 -2018-03-06 22:00:00,11891.0 -2018-03-06 23:00:00,11432.0 -2018-03-07 00:00:00,10776.0 -2018-03-05 01:00:00,9379.0 -2018-03-05 02:00:00,9124.0 -2018-03-05 03:00:00,9030.0 -2018-03-05 04:00:00,8977.0 -2018-03-05 05:00:00,9021.0 -2018-03-05 06:00:00,9335.0 -2018-03-05 07:00:00,10129.0 -2018-03-05 08:00:00,11029.0 -2018-03-05 09:00:00,11666.0 -2018-03-05 10:00:00,11897.0 -2018-03-05 11:00:00,12058.0 -2018-03-05 12:00:00,12037.0 -2018-03-05 13:00:00,12011.0 -2018-03-05 14:00:00,11981.0 -2018-03-05 15:00:00,11900.0 -2018-03-05 16:00:00,11826.0 -2018-03-05 17:00:00,11816.0 -2018-03-05 18:00:00,12004.0 -2018-03-05 19:00:00,12362.0 -2018-03-05 20:00:00,12446.0 -2018-03-05 21:00:00,12261.0 -2018-03-05 22:00:00,11956.0 -2018-03-05 23:00:00,11448.0 -2018-03-06 00:00:00,10750.0 -2018-03-04 01:00:00,9450.0 -2018-03-04 02:00:00,9128.0 -2018-03-04 03:00:00,8968.0 -2018-03-04 04:00:00,8831.0 -2018-03-04 05:00:00,8769.0 -2018-03-04 06:00:00,8854.0 -2018-03-04 07:00:00,8999.0 -2018-03-04 08:00:00,9087.0 -2018-03-04 09:00:00,9139.0 -2018-03-04 10:00:00,9279.0 -2018-03-04 11:00:00,9373.0 -2018-03-04 12:00:00,9383.0 -2018-03-04 13:00:00,9357.0 -2018-03-04 14:00:00,9336.0 -2018-03-04 15:00:00,9152.0 -2018-03-04 16:00:00,9092.0 -2018-03-04 17:00:00,9153.0 -2018-03-04 18:00:00,9338.0 -2018-03-04 19:00:00,9820.0 -2018-03-04 20:00:00,10486.0 -2018-03-04 21:00:00,10554.0 -2018-03-04 22:00:00,10384.0 -2018-03-04 23:00:00,10167.0 -2018-03-05 00:00:00,9700.0 -2018-03-03 01:00:00,9943.0 -2018-03-03 02:00:00,9526.0 -2018-03-03 03:00:00,9281.0 -2018-03-03 04:00:00,9102.0 -2018-03-03 05:00:00,9041.0 -2018-03-03 06:00:00,9159.0 -2018-03-03 07:00:00,9434.0 -2018-03-03 08:00:00,9744.0 -2018-03-03 09:00:00,9919.0 -2018-03-03 10:00:00,10083.0 -2018-03-03 11:00:00,10148.0 -2018-03-03 12:00:00,10156.0 -2018-03-03 13:00:00,10085.0 -2018-03-03 14:00:00,9915.0 -2018-03-03 15:00:00,9679.0 -2018-03-03 16:00:00,9518.0 -2018-03-03 17:00:00,9472.0 -2018-03-03 18:00:00,9586.0 -2018-03-03 19:00:00,9938.0 -2018-03-03 20:00:00,10568.0 -2018-03-03 21:00:00,10575.0 -2018-03-03 22:00:00,10480.0 -2018-03-03 23:00:00,10280.0 -2018-03-04 00:00:00,9863.0 -2018-03-02 01:00:00,10035.0 -2018-03-02 02:00:00,9635.0 -2018-03-02 03:00:00,9480.0 -2018-03-02 04:00:00,9346.0 -2018-03-02 05:00:00,9350.0 -2018-03-02 06:00:00,9639.0 -2018-03-02 07:00:00,10347.0 -2018-03-02 08:00:00,11147.0 -2018-03-02 09:00:00,11409.0 -2018-03-02 10:00:00,11403.0 -2018-03-02 11:00:00,11403.0 -2018-03-02 12:00:00,11420.0 -2018-03-02 13:00:00,11299.0 -2018-03-02 14:00:00,11162.0 -2018-03-02 15:00:00,11079.0 -2018-03-02 16:00:00,10908.0 -2018-03-02 17:00:00,10738.0 -2018-03-02 18:00:00,10742.0 -2018-03-02 19:00:00,11021.0 -2018-03-02 20:00:00,11530.0 -2018-03-02 21:00:00,11446.0 -2018-03-02 22:00:00,11258.0 -2018-03-02 23:00:00,11033.0 -2018-03-03 00:00:00,10475.0 -2018-03-01 01:00:00,9616.0 -2018-03-01 02:00:00,9217.0 -2018-03-01 03:00:00,9008.0 -2018-03-01 04:00:00,8887.0 -2018-03-01 05:00:00,8929.0 -2018-03-01 06:00:00,9226.0 -2018-03-01 07:00:00,10012.0 -2018-03-01 08:00:00,11129.0 -2018-03-01 09:00:00,11753.0 -2018-03-01 10:00:00,11945.0 -2018-03-01 11:00:00,11935.0 -2018-03-01 12:00:00,11984.0 -2018-03-01 13:00:00,11917.0 -2018-03-01 14:00:00,11713.0 -2018-03-01 15:00:00,11596.0 -2018-03-01 16:00:00,11334.0 -2018-03-01 17:00:00,11247.0 -2018-03-01 18:00:00,11250.0 -2018-03-01 19:00:00,11488.0 -2018-03-01 20:00:00,12004.0 -2018-03-01 21:00:00,11872.0 -2018-03-01 22:00:00,11640.0 -2018-03-01 23:00:00,11251.0 -2018-03-02 00:00:00,10558.0 -2018-02-28 01:00:00,9282.0 -2018-02-28 02:00:00,8881.0 -2018-02-28 03:00:00,8654.0 -2018-02-28 04:00:00,8510.0 -2018-02-28 05:00:00,8467.0 -2018-02-28 06:00:00,8697.0 -2018-02-28 07:00:00,9365.0 -2018-02-28 08:00:00,10280.0 -2018-02-28 09:00:00,10662.0 -2018-02-28 10:00:00,10734.0 -2018-02-28 11:00:00,10781.0 -2018-02-28 12:00:00,10836.0 -2018-02-28 13:00:00,10807.0 -2018-02-28 14:00:00,10740.0 -2018-02-28 15:00:00,10788.0 -2018-02-28 16:00:00,10807.0 -2018-02-28 17:00:00,10789.0 -2018-02-28 18:00:00,11030.0 -2018-02-28 19:00:00,11406.0 -2018-02-28 20:00:00,11604.0 -2018-02-28 21:00:00,11494.0 -2018-02-28 22:00:00,11278.0 -2018-02-28 23:00:00,10833.0 -2018-03-01 00:00:00,10185.0 -2018-02-27 01:00:00,9695.0 -2018-02-27 02:00:00,9337.0 -2018-02-27 03:00:00,9113.0 -2018-02-27 04:00:00,9009.0 -2018-02-27 05:00:00,8994.0 -2018-02-27 06:00:00,9224.0 -2018-02-27 07:00:00,9929.0 -2018-02-27 08:00:00,10873.0 -2018-02-27 09:00:00,11225.0 -2018-02-27 10:00:00,11360.0 -2018-02-27 11:00:00,11299.0 -2018-02-27 12:00:00,11183.0 -2018-02-27 13:00:00,11105.0 -2018-02-27 14:00:00,10988.0 -2018-02-27 15:00:00,10955.0 -2018-02-27 16:00:00,10812.0 -2018-02-27 17:00:00,10625.0 -2018-02-27 18:00:00,10654.0 -2018-02-27 19:00:00,10925.0 -2018-02-27 20:00:00,11345.0 -2018-02-27 21:00:00,11176.0 -2018-02-27 22:00:00,10943.0 -2018-02-27 23:00:00,10488.0 -2018-02-28 00:00:00,9857.0 -2018-02-26 01:00:00,9414.0 -2018-02-26 02:00:00,9123.0 -2018-02-26 03:00:00,9060.0 -2018-02-26 04:00:00,9040.0 -2018-02-26 05:00:00,9113.0 -2018-02-26 06:00:00,9442.0 -2018-02-26 07:00:00,10275.0 -2018-02-26 08:00:00,11223.0 -2018-02-26 09:00:00,11573.0 -2018-02-26 10:00:00,11579.0 -2018-02-26 11:00:00,11441.0 -2018-02-26 12:00:00,11382.0 -2018-02-26 13:00:00,11288.0 -2018-02-26 14:00:00,11162.0 -2018-02-26 15:00:00,11095.0 -2018-02-26 16:00:00,10940.0 -2018-02-26 17:00:00,10789.0 -2018-02-26 18:00:00,10800.0 -2018-02-26 19:00:00,11096.0 -2018-02-26 20:00:00,11545.0 -2018-02-26 21:00:00,11444.0 -2018-02-26 22:00:00,11221.0 -2018-02-26 23:00:00,10773.0 -2018-02-27 00:00:00,10210.0 -2018-02-25 01:00:00,9383.0 -2018-02-25 02:00:00,9053.0 -2018-02-25 03:00:00,8896.0 -2018-02-25 04:00:00,8886.0 -2018-02-25 05:00:00,8902.0 -2018-02-25 06:00:00,8990.0 -2018-02-25 07:00:00,9131.0 -2018-02-25 08:00:00,9327.0 -2018-02-25 09:00:00,9409.0 -2018-02-25 10:00:00,9566.0 -2018-02-25 11:00:00,9648.0 -2018-02-25 12:00:00,9674.0 -2018-02-25 13:00:00,9642.0 -2018-02-25 14:00:00,9575.0 -2018-02-25 15:00:00,9502.0 -2018-02-25 16:00:00,9408.0 -2018-02-25 17:00:00,9426.0 -2018-02-25 18:00:00,9591.0 -2018-02-25 19:00:00,10113.0 -2018-02-25 20:00:00,10683.0 -2018-02-25 21:00:00,10668.0 -2018-02-25 22:00:00,10561.0 -2018-02-25 23:00:00,10224.0 -2018-02-26 00:00:00,9810.0 -2018-02-24 01:00:00,10072.0 -2018-02-24 02:00:00,9631.0 -2018-02-24 03:00:00,9429.0 -2018-02-24 04:00:00,9267.0 -2018-02-24 05:00:00,9209.0 -2018-02-24 06:00:00,9316.0 -2018-02-24 07:00:00,9606.0 -2018-02-24 08:00:00,10052.0 -2018-02-24 09:00:00,10304.0 -2018-02-24 10:00:00,10639.0 -2018-02-24 11:00:00,10787.0 -2018-02-24 12:00:00,10859.0 -2018-02-24 13:00:00,10682.0 -2018-02-24 14:00:00,10576.0 -2018-02-24 15:00:00,10406.0 -2018-02-24 16:00:00,10300.0 -2018-02-24 17:00:00,10360.0 -2018-02-24 18:00:00,10545.0 -2018-02-24 19:00:00,10927.0 -2018-02-24 20:00:00,10989.0 -2018-02-24 21:00:00,10859.0 -2018-02-24 22:00:00,10658.0 -2018-02-24 23:00:00,10243.0 -2018-02-25 00:00:00,9771.0 -2018-02-23 01:00:00,10068.0 -2018-02-23 02:00:00,9651.0 -2018-02-23 03:00:00,9417.0 -2018-02-23 04:00:00,9251.0 -2018-02-23 05:00:00,9277.0 -2018-02-23 06:00:00,9500.0 -2018-02-23 07:00:00,10150.0 -2018-02-23 08:00:00,11070.0 -2018-02-23 09:00:00,11481.0 -2018-02-23 10:00:00,11632.0 -2018-02-23 11:00:00,11790.0 -2018-02-23 12:00:00,11866.0 -2018-02-23 13:00:00,11862.0 -2018-02-23 14:00:00,11796.0 -2018-02-23 15:00:00,11749.0 -2018-02-23 16:00:00,11647.0 -2018-02-23 17:00:00,11530.0 -2018-02-23 18:00:00,11599.0 -2018-02-23 19:00:00,11862.0 -2018-02-23 20:00:00,11954.0 -2018-02-23 21:00:00,11786.0 -2018-02-23 22:00:00,11508.0 -2018-02-23 23:00:00,11183.0 -2018-02-24 00:00:00,10584.0 -2018-02-22 01:00:00,10582.0 -2018-02-22 02:00:00,10211.0 -2018-02-22 03:00:00,9953.0 -2018-02-22 04:00:00,9807.0 -2018-02-22 05:00:00,9773.0 -2018-02-22 06:00:00,10046.0 -2018-02-22 07:00:00,10699.0 -2018-02-22 08:00:00,11715.0 -2018-02-22 09:00:00,12125.0 -2018-02-22 10:00:00,12212.0 -2018-02-22 11:00:00,12168.0 -2018-02-22 12:00:00,12138.0 -2018-02-22 13:00:00,11947.0 -2018-02-22 14:00:00,11796.0 -2018-02-22 15:00:00,11796.0 -2018-02-22 16:00:00,11764.0 -2018-02-22 17:00:00,11766.0 -2018-02-22 18:00:00,11831.0 -2018-02-22 19:00:00,12192.0 -2018-02-22 20:00:00,12300.0 -2018-02-22 21:00:00,12101.0 -2018-02-22 22:00:00,11821.0 -2018-02-22 23:00:00,11350.0 -2018-02-23 00:00:00,10681.0 -2018-02-21 01:00:00,10102.0 -2018-02-21 02:00:00,9775.0 -2018-02-21 03:00:00,9599.0 -2018-02-21 04:00:00,9537.0 -2018-02-21 05:00:00,9519.0 -2018-02-21 06:00:00,9821.0 -2018-02-21 07:00:00,10558.0 -2018-02-21 08:00:00,11601.0 -2018-02-21 09:00:00,12059.0 -2018-02-21 10:00:00,12231.0 -2018-02-21 11:00:00,12279.0 -2018-02-21 12:00:00,12242.0 -2018-02-21 13:00:00,12199.0 -2018-02-21 14:00:00,12091.0 -2018-02-21 15:00:00,12106.0 -2018-02-21 16:00:00,12032.0 -2018-02-21 17:00:00,12045.0 -2018-02-21 18:00:00,12195.0 -2018-02-21 19:00:00,12585.0 -2018-02-21 20:00:00,12739.0 -2018-02-21 21:00:00,12611.0 -2018-02-21 22:00:00,12346.0 -2018-02-21 23:00:00,11870.0 -2018-02-22 00:00:00,11153.0 -2018-02-20 01:00:00,9614.0 -2018-02-20 02:00:00,9158.0 -2018-02-20 03:00:00,8836.0 -2018-02-20 04:00:00,8726.0 -2018-02-20 05:00:00,8709.0 -2018-02-20 06:00:00,8953.0 -2018-02-20 07:00:00,9578.0 -2018-02-20 08:00:00,10555.0 -2018-02-20 09:00:00,11017.0 -2018-02-20 10:00:00,11157.0 -2018-02-20 11:00:00,11200.0 -2018-02-20 12:00:00,11329.0 -2018-02-20 13:00:00,11349.0 -2018-02-20 14:00:00,11413.0 -2018-02-20 15:00:00,11516.0 -2018-02-20 16:00:00,11375.0 -2018-02-20 17:00:00,11302.0 -2018-02-20 18:00:00,11426.0 -2018-02-20 19:00:00,11809.0 -2018-02-20 20:00:00,11927.0 -2018-02-20 21:00:00,11803.0 -2018-02-20 22:00:00,11596.0 -2018-02-20 23:00:00,11213.0 -2018-02-21 00:00:00,10610.0 -2018-02-19 01:00:00,9668.0 -2018-02-19 02:00:00,9361.0 -2018-02-19 03:00:00,9183.0 -2018-02-19 04:00:00,9055.0 -2018-02-19 05:00:00,9074.0 -2018-02-19 06:00:00,9339.0 -2018-02-19 07:00:00,9918.0 -2018-02-19 08:00:00,10793.0 -2018-02-19 09:00:00,11150.0 -2018-02-19 10:00:00,11455.0 -2018-02-19 11:00:00,11665.0 -2018-02-19 12:00:00,11765.0 -2018-02-19 13:00:00,11756.0 -2018-02-19 14:00:00,11645.0 -2018-02-19 15:00:00,11576.0 -2018-02-19 16:00:00,11441.0 -2018-02-19 17:00:00,11365.0 -2018-02-19 18:00:00,11466.0 -2018-02-19 19:00:00,11789.0 -2018-02-19 20:00:00,11752.0 -2018-02-19 21:00:00,11514.0 -2018-02-19 22:00:00,11249.0 -2018-02-19 23:00:00,10834.0 -2018-02-20 00:00:00,10186.0 -2018-02-18 01:00:00,10152.0 -2018-02-18 02:00:00,9824.0 -2018-02-18 03:00:00,9634.0 -2018-02-18 04:00:00,9544.0 -2018-02-18 05:00:00,9557.0 -2018-02-18 06:00:00,9655.0 -2018-02-18 07:00:00,9847.0 -2018-02-18 08:00:00,10061.0 -2018-02-18 09:00:00,10127.0 -2018-02-18 10:00:00,10236.0 -2018-02-18 11:00:00,10307.0 -2018-02-18 12:00:00,10370.0 -2018-02-18 13:00:00,10242.0 -2018-02-18 14:00:00,10135.0 -2018-02-18 15:00:00,9986.0 -2018-02-18 16:00:00,9924.0 -2018-02-18 17:00:00,9928.0 -2018-02-18 18:00:00,10144.0 -2018-02-18 19:00:00,10750.0 -2018-02-18 20:00:00,11218.0 -2018-02-18 21:00:00,11141.0 -2018-02-18 22:00:00,10945.0 -2018-02-18 23:00:00,10628.0 -2018-02-19 00:00:00,10135.0 -2018-02-17 01:00:00,10823.0 -2018-02-17 02:00:00,10421.0 -2018-02-17 03:00:00,10280.0 -2018-02-17 04:00:00,10122.0 -2018-02-17 05:00:00,10123.0 -2018-02-17 06:00:00,10176.0 -2018-02-17 07:00:00,10503.0 -2018-02-17 08:00:00,10882.0 -2018-02-17 09:00:00,11078.0 -2018-02-17 10:00:00,11339.0 -2018-02-17 11:00:00,11503.0 -2018-02-17 12:00:00,11390.0 -2018-02-17 13:00:00,11164.0 -2018-02-17 14:00:00,10986.0 -2018-02-17 15:00:00,10825.0 -2018-02-17 16:00:00,10812.0 -2018-02-17 17:00:00,10906.0 -2018-02-17 18:00:00,11057.0 -2018-02-17 19:00:00,11382.0 -2018-02-17 20:00:00,11609.0 -2018-02-17 21:00:00,11502.0 -2018-02-17 22:00:00,11357.0 -2018-02-17 23:00:00,11051.0 -2018-02-18 00:00:00,10603.0 -2018-02-16 01:00:00,9995.0 -2018-02-16 02:00:00,9673.0 -2018-02-16 03:00:00,9478.0 -2018-02-16 04:00:00,9367.0 -2018-02-16 05:00:00,9360.0 -2018-02-16 06:00:00,9641.0 -2018-02-16 07:00:00,10323.0 -2018-02-16 08:00:00,11334.0 -2018-02-16 09:00:00,11788.0 -2018-02-16 10:00:00,12005.0 -2018-02-16 11:00:00,12135.0 -2018-02-16 12:00:00,12101.0 -2018-02-16 13:00:00,11884.0 -2018-02-16 14:00:00,11719.0 -2018-02-16 15:00:00,11621.0 -2018-02-16 16:00:00,11474.0 -2018-02-16 17:00:00,11329.0 -2018-02-16 18:00:00,11430.0 -2018-02-16 19:00:00,11975.0 -2018-02-16 20:00:00,12368.0 -2018-02-16 21:00:00,12287.0 -2018-02-16 22:00:00,12095.0 -2018-02-16 23:00:00,11829.0 -2018-02-17 00:00:00,11282.0 -2018-02-15 01:00:00,10106.0 -2018-02-15 02:00:00,9686.0 -2018-02-15 03:00:00,9428.0 -2018-02-15 04:00:00,9287.0 -2018-02-15 05:00:00,9266.0 -2018-02-15 06:00:00,9486.0 -2018-02-15 07:00:00,10182.0 -2018-02-15 08:00:00,11219.0 -2018-02-15 09:00:00,11621.0 -2018-02-15 10:00:00,11818.0 -2018-02-15 11:00:00,11872.0 -2018-02-15 12:00:00,11871.0 -2018-02-15 13:00:00,11822.0 -2018-02-15 14:00:00,11665.0 -2018-02-15 15:00:00,11634.0 -2018-02-15 16:00:00,11480.0 -2018-02-15 17:00:00,11414.0 -2018-02-15 18:00:00,11533.0 -2018-02-15 19:00:00,12002.0 -2018-02-15 20:00:00,12082.0 -2018-02-15 21:00:00,11904.0 -2018-02-15 22:00:00,11659.0 -2018-02-15 23:00:00,11220.0 -2018-02-16 00:00:00,10538.0 -2018-02-14 01:00:00,10896.0 -2018-02-14 02:00:00,10512.0 -2018-02-14 03:00:00,10284.0 -2018-02-14 04:00:00,10145.0 -2018-02-14 05:00:00,10125.0 -2018-02-14 06:00:00,10391.0 -2018-02-14 07:00:00,11081.0 -2018-02-14 08:00:00,12054.0 -2018-02-14 09:00:00,12352.0 -2018-02-14 10:00:00,12276.0 -2018-02-14 11:00:00,12103.0 -2018-02-14 12:00:00,11997.0 -2018-02-14 13:00:00,11905.0 -2018-02-14 14:00:00,11746.0 -2018-02-14 15:00:00,11674.0 -2018-02-14 16:00:00,11585.0 -2018-02-14 17:00:00,11613.0 -2018-02-14 18:00:00,11818.0 -2018-02-14 19:00:00,12307.0 -2018-02-14 20:00:00,12386.0 -2018-02-14 21:00:00,12197.0 -2018-02-14 22:00:00,11908.0 -2018-02-14 23:00:00,11428.0 -2018-02-15 00:00:00,10725.0 -2018-02-13 01:00:00,11347.0 -2018-02-13 02:00:00,11011.0 -2018-02-13 03:00:00,10852.0 -2018-02-13 04:00:00,10736.0 -2018-02-13 05:00:00,10747.0 -2018-02-13 06:00:00,10989.0 -2018-02-13 07:00:00,11636.0 -2018-02-13 08:00:00,12590.0 -2018-02-13 09:00:00,12935.0 -2018-02-13 10:00:00,13070.0 -2018-02-13 11:00:00,13079.0 -2018-02-13 12:00:00,13008.0 -2018-02-13 13:00:00,12808.0 -2018-02-13 14:00:00,12617.0 -2018-02-13 15:00:00,12451.0 -2018-02-13 16:00:00,12339.0 -2018-02-13 17:00:00,12250.0 -2018-02-13 18:00:00,12293.0 -2018-02-13 19:00:00,12735.0 -2018-02-13 20:00:00,13003.0 -2018-02-13 21:00:00,12870.0 -2018-02-13 22:00:00,12653.0 -2018-02-13 23:00:00,12185.0 -2018-02-14 00:00:00,11516.0 -2018-02-12 01:00:00,11045.0 -2018-02-12 02:00:00,10827.0 -2018-02-12 03:00:00,10643.0 -2018-02-12 04:00:00,10623.0 -2018-02-12 05:00:00,10707.0 -2018-02-12 06:00:00,10993.0 -2018-02-12 07:00:00,11719.0 -2018-02-12 08:00:00,12739.0 -2018-02-12 09:00:00,13059.0 -2018-02-12 10:00:00,13053.0 -2018-02-12 11:00:00,12894.0 -2018-02-12 12:00:00,12789.0 -2018-02-12 13:00:00,12663.0 -2018-02-12 14:00:00,12586.0 -2018-02-12 15:00:00,12495.0 -2018-02-12 16:00:00,12385.0 -2018-02-12 17:00:00,12394.0 -2018-02-12 18:00:00,12611.0 -2018-02-12 19:00:00,13184.0 -2018-02-12 20:00:00,13436.0 -2018-02-12 21:00:00,13300.0 -2018-02-12 22:00:00,13090.0 -2018-02-12 23:00:00,12634.0 -2018-02-13 00:00:00,11966.0 -2018-02-11 01:00:00,10794.0 -2018-02-11 02:00:00,10443.0 -2018-02-11 03:00:00,10244.0 -2018-02-11 04:00:00,10099.0 -2018-02-11 05:00:00,10051.0 -2018-02-11 06:00:00,10082.0 -2018-02-11 07:00:00,10252.0 -2018-02-11 08:00:00,10451.0 -2018-02-11 09:00:00,10489.0 -2018-02-11 10:00:00,10749.0 -2018-02-11 11:00:00,10969.0 -2018-02-11 12:00:00,11056.0 -2018-02-11 13:00:00,11041.0 -2018-02-11 14:00:00,11072.0 -2018-02-11 15:00:00,11028.0 -2018-02-11 16:00:00,10924.0 -2018-02-11 17:00:00,10847.0 -2018-02-11 18:00:00,11217.0 -2018-02-11 19:00:00,11953.0 -2018-02-11 20:00:00,12468.0 -2018-02-11 21:00:00,12327.0 -2018-02-11 22:00:00,12249.0 -2018-02-11 23:00:00,11940.0 -2018-02-12 00:00:00,11453.0 -2018-02-10 01:00:00,11062.0 -2018-02-10 02:00:00,10683.0 -2018-02-10 03:00:00,10479.0 -2018-02-10 04:00:00,10305.0 -2018-02-10 05:00:00,10294.0 -2018-02-10 06:00:00,10433.0 -2018-02-10 07:00:00,10740.0 -2018-02-10 08:00:00,11125.0 -2018-02-10 09:00:00,11300.0 -2018-02-10 10:00:00,11671.0 -2018-02-10 11:00:00,11887.0 -2018-02-10 12:00:00,11906.0 -2018-02-10 13:00:00,11862.0 -2018-02-10 14:00:00,11712.0 -2018-02-10 15:00:00,11555.0 -2018-02-10 16:00:00,11428.0 -2018-02-10 17:00:00,11424.0 -2018-02-10 18:00:00,11589.0 -2018-02-10 19:00:00,12123.0 -2018-02-10 20:00:00,12328.0 -2018-02-10 21:00:00,12198.0 -2018-02-10 22:00:00,11978.0 -2018-02-10 23:00:00,11669.0 -2018-02-11 00:00:00,11140.0 -2018-02-09 01:00:00,11362.0 -2018-02-09 02:00:00,10973.0 -2018-02-09 03:00:00,10686.0 -2018-02-09 04:00:00,10534.0 -2018-02-09 05:00:00,10502.0 -2018-02-09 06:00:00,10670.0 -2018-02-09 07:00:00,11136.0 -2018-02-09 08:00:00,11797.0 -2018-02-09 09:00:00,12074.0 -2018-02-09 10:00:00,12436.0 -2018-02-09 11:00:00,12622.0 -2018-02-09 12:00:00,12758.0 -2018-02-09 13:00:00,12804.0 -2018-02-09 14:00:00,12796.0 -2018-02-09 15:00:00,12733.0 -2018-02-09 16:00:00,12596.0 -2018-02-09 17:00:00,12522.0 -2018-02-09 18:00:00,12595.0 -2018-02-09 19:00:00,13138.0 -2018-02-09 20:00:00,13139.0 -2018-02-09 21:00:00,12884.0 -2018-02-09 22:00:00,12596.0 -2018-02-09 23:00:00,12228.0 -2018-02-10 00:00:00,11611.0 -2018-02-08 01:00:00,11729.0 -2018-02-08 02:00:00,11412.0 -2018-02-08 03:00:00,11223.0 -2018-02-08 04:00:00,11128.0 -2018-02-08 05:00:00,11156.0 -2018-02-08 06:00:00,11378.0 -2018-02-08 07:00:00,12014.0 -2018-02-08 08:00:00,12988.0 -2018-02-08 09:00:00,13400.0 -2018-02-08 10:00:00,13492.0 -2018-02-08 11:00:00,13485.0 -2018-02-08 12:00:00,13427.0 -2018-02-08 13:00:00,13168.0 -2018-02-08 14:00:00,12967.0 -2018-02-08 15:00:00,12912.0 -2018-02-08 16:00:00,12866.0 -2018-02-08 17:00:00,12813.0 -2018-02-08 18:00:00,13010.0 -2018-02-08 19:00:00,13544.0 -2018-02-08 20:00:00,13643.0 -2018-02-08 21:00:00,13464.0 -2018-02-08 22:00:00,13180.0 -2018-02-08 23:00:00,12670.0 -2018-02-09 00:00:00,11974.0 -2018-02-07 01:00:00,11402.0 -2018-02-07 02:00:00,11013.0 -2018-02-07 03:00:00,10805.0 -2018-02-07 04:00:00,10756.0 -2018-02-07 05:00:00,10770.0 -2018-02-07 06:00:00,11040.0 -2018-02-07 07:00:00,11705.0 -2018-02-07 08:00:00,12700.0 -2018-02-07 09:00:00,13080.0 -2018-02-07 10:00:00,13186.0 -2018-02-07 11:00:00,13132.0 -2018-02-07 12:00:00,13155.0 -2018-02-07 13:00:00,13071.0 -2018-02-07 14:00:00,13022.0 -2018-02-07 15:00:00,12918.0 -2018-02-07 16:00:00,12884.0 -2018-02-07 17:00:00,12976.0 -2018-02-07 18:00:00,13251.0 -2018-02-07 19:00:00,13778.0 -2018-02-07 20:00:00,13877.0 -2018-02-07 21:00:00,13710.0 -2018-02-07 22:00:00,13471.0 -2018-02-07 23:00:00,12966.0 -2018-02-08 00:00:00,12298.0 -2018-02-06 01:00:00,11845.0 -2018-02-06 02:00:00,11498.0 -2018-02-06 03:00:00,11288.0 -2018-02-06 04:00:00,11248.0 -2018-02-06 05:00:00,11227.0 -2018-02-06 06:00:00,11523.0 -2018-02-06 07:00:00,12155.0 -2018-02-06 08:00:00,13108.0 -2018-02-06 09:00:00,13445.0 -2018-02-06 10:00:00,13374.0 -2018-02-06 11:00:00,13253.0 -2018-02-06 12:00:00,13158.0 -2018-02-06 13:00:00,13001.0 -2018-02-06 14:00:00,12828.0 -2018-02-06 15:00:00,12801.0 -2018-02-06 16:00:00,12742.0 -2018-02-06 17:00:00,12800.0 -2018-02-06 18:00:00,13037.0 -2018-02-06 19:00:00,13595.0 -2018-02-06 20:00:00,13675.0 -2018-02-06 21:00:00,13475.0 -2018-02-06 22:00:00,13177.0 -2018-02-06 23:00:00,12670.0 -2018-02-07 00:00:00,11988.0 -2018-02-05 01:00:00,11318.0 -2018-02-05 02:00:00,11098.0 -2018-02-05 03:00:00,10995.0 -2018-02-05 04:00:00,10974.0 -2018-02-05 05:00:00,11108.0 -2018-02-05 06:00:00,11349.0 -2018-02-05 07:00:00,12178.0 -2018-02-05 08:00:00,13223.0 -2018-02-05 09:00:00,13649.0 -2018-02-05 10:00:00,13775.0 -2018-02-05 11:00:00,13739.0 -2018-02-05 12:00:00,13599.0 -2018-02-05 13:00:00,13517.0 -2018-02-05 14:00:00,13612.0 -2018-02-05 15:00:00,13676.0 -2018-02-05 16:00:00,13598.0 -2018-02-05 17:00:00,13573.0 -2018-02-05 18:00:00,13772.0 -2018-02-05 19:00:00,14292.0 -2018-02-05 20:00:00,14292.0 -2018-02-05 21:00:00,14103.0 -2018-02-05 22:00:00,13781.0 -2018-02-05 23:00:00,13236.0 -2018-02-06 00:00:00,12478.0 -2018-02-04 01:00:00,10330.0 -2018-02-04 02:00:00,9937.0 -2018-02-04 03:00:00,9638.0 -2018-02-04 04:00:00,9484.0 -2018-02-04 05:00:00,9418.0 -2018-02-04 06:00:00,9519.0 -2018-02-04 07:00:00,9684.0 -2018-02-04 08:00:00,10091.0 -2018-02-04 09:00:00,10275.0 -2018-02-04 10:00:00,10643.0 -2018-02-04 11:00:00,11021.0 -2018-02-04 12:00:00,11310.0 -2018-02-04 13:00:00,11459.0 -2018-02-04 14:00:00,11598.0 -2018-02-04 15:00:00,11636.0 -2018-02-04 16:00:00,11610.0 -2018-02-04 17:00:00,11610.0 -2018-02-04 18:00:00,11750.0 -2018-02-04 19:00:00,12356.0 -2018-02-04 20:00:00,12541.0 -2018-02-04 21:00:00,12444.0 -2018-02-04 22:00:00,12323.0 -2018-02-04 23:00:00,12110.0 -2018-02-05 00:00:00,11715.0 -2018-02-03 01:00:00,11501.0 -2018-02-03 02:00:00,11038.0 -2018-02-03 03:00:00,10763.0 -2018-02-03 04:00:00,10509.0 -2018-02-03 05:00:00,10451.0 -2018-02-03 06:00:00,10492.0 -2018-02-03 07:00:00,10775.0 -2018-02-03 08:00:00,11188.0 -2018-02-03 09:00:00,11427.0 -2018-02-03 10:00:00,11689.0 -2018-02-03 11:00:00,11831.0 -2018-02-03 12:00:00,11881.0 -2018-02-03 13:00:00,11765.0 -2018-02-03 14:00:00,11643.0 -2018-02-03 15:00:00,11414.0 -2018-02-03 16:00:00,11253.0 -2018-02-03 17:00:00,11122.0 -2018-02-03 18:00:00,11254.0 -2018-02-03 19:00:00,11768.0 -2018-02-03 20:00:00,11845.0 -2018-02-03 21:00:00,11705.0 -2018-02-03 22:00:00,11522.0 -2018-02-03 23:00:00,11280.0 -2018-02-04 00:00:00,10833.0 -2018-02-02 01:00:00,11743.0 -2018-02-02 02:00:00,11444.0 -2018-02-02 03:00:00,11265.0 -2018-02-02 04:00:00,11140.0 -2018-02-02 05:00:00,11173.0 -2018-02-02 06:00:00,11408.0 -2018-02-02 07:00:00,12073.0 -2018-02-02 08:00:00,13050.0 -2018-02-02 09:00:00,13456.0 -2018-02-02 10:00:00,13509.0 -2018-02-02 11:00:00,13435.0 -2018-02-02 12:00:00,13414.0 -2018-02-02 13:00:00,13263.0 -2018-02-02 14:00:00,13099.0 -2018-02-02 15:00:00,12895.0 -2018-02-02 16:00:00,12719.0 -2018-02-02 17:00:00,12613.0 -2018-02-02 18:00:00,12852.0 -2018-02-02 19:00:00,13495.0 -2018-02-02 20:00:00,13527.0 -2018-02-02 21:00:00,13341.0 -2018-02-02 22:00:00,13102.0 -2018-02-02 23:00:00,12708.0 -2018-02-03 00:00:00,12069.0 -2018-02-01 01:00:00,10355.0 -2018-02-01 02:00:00,9978.0 -2018-02-01 03:00:00,9803.0 -2018-02-01 04:00:00,9686.0 -2018-02-01 05:00:00,9720.0 -2018-02-01 06:00:00,10014.0 -2018-02-01 07:00:00,10772.0 -2018-02-01 08:00:00,11874.0 -2018-02-01 09:00:00,12451.0 -2018-02-01 10:00:00,12719.0 -2018-02-01 11:00:00,12582.0 -2018-02-01 12:00:00,12664.0 -2018-02-01 13:00:00,12623.0 -2018-02-01 14:00:00,12603.0 -2018-02-01 15:00:00,12553.0 -2018-02-01 16:00:00,12522.0 -2018-02-01 17:00:00,12554.0 -2018-02-01 18:00:00,12927.0 -2018-02-01 19:00:00,13665.0 -2018-02-01 20:00:00,13820.0 -2018-02-01 21:00:00,13700.0 -2018-02-01 22:00:00,13436.0 -2018-02-01 23:00:00,12994.0 -2018-02-02 00:00:00,12286.0 -2018-01-31 01:00:00,10986.0 -2018-01-31 02:00:00,10603.0 -2018-01-31 03:00:00,10346.0 -2018-01-31 04:00:00,10183.0 -2018-01-31 05:00:00,10141.0 -2018-01-31 06:00:00,10400.0 -2018-01-31 07:00:00,11098.0 -2018-01-31 08:00:00,12151.0 -2018-01-31 09:00:00,12519.0 -2018-01-31 10:00:00,12494.0 -2018-01-31 11:00:00,12293.0 -2018-01-31 12:00:00,12322.0 -2018-01-31 13:00:00,12256.0 -2018-01-31 14:00:00,12132.0 -2018-01-31 15:00:00,12062.0 -2018-01-31 16:00:00,11975.0 -2018-01-31 17:00:00,11923.0 -2018-01-31 18:00:00,12144.0 -2018-01-31 19:00:00,12665.0 -2018-01-31 20:00:00,12597.0 -2018-01-31 21:00:00,12379.0 -2018-01-31 22:00:00,12114.0 -2018-01-31 23:00:00,11614.0 -2018-02-01 00:00:00,10975.0 -2018-01-30 01:00:00,11111.0 -2018-01-30 02:00:00,10802.0 -2018-01-30 03:00:00,10624.0 -2018-01-30 04:00:00,10509.0 -2018-01-30 05:00:00,10536.0 -2018-01-30 06:00:00,10833.0 -2018-01-30 07:00:00,11577.0 -2018-01-30 08:00:00,12643.0 -2018-01-30 09:00:00,13066.0 -2018-01-30 10:00:00,12949.0 -2018-01-30 11:00:00,12809.0 -2018-01-30 12:00:00,12749.0 -2018-01-30 13:00:00,12650.0 -2018-01-30 14:00:00,12445.0 -2018-01-30 15:00:00,12375.0 -2018-01-30 16:00:00,12399.0 -2018-01-30 17:00:00,12466.0 -2018-01-30 18:00:00,12814.0 -2018-01-30 19:00:00,13373.0 -2018-01-30 20:00:00,13268.0 -2018-01-30 21:00:00,13110.0 -2018-01-30 22:00:00,12771.0 -2018-01-30 23:00:00,12282.0 -2018-01-31 00:00:00,11646.0 -2018-01-29 01:00:00,9728.0 -2018-01-29 02:00:00,9499.0 -2018-01-29 03:00:00,9425.0 -2018-01-29 04:00:00,9377.0 -2018-01-29 05:00:00,9474.0 -2018-01-29 06:00:00,9785.0 -2018-01-29 07:00:00,10648.0 -2018-01-29 08:00:00,11785.0 -2018-01-29 09:00:00,12434.0 -2018-01-29 10:00:00,12545.0 -2018-01-29 11:00:00,12684.0 -2018-01-29 12:00:00,12780.0 -2018-01-29 13:00:00,12742.0 -2018-01-29 14:00:00,12670.0 -2018-01-29 15:00:00,12642.0 -2018-01-29 16:00:00,12606.0 -2018-01-29 17:00:00,12591.0 -2018-01-29 18:00:00,12795.0 -2018-01-29 19:00:00,13321.0 -2018-01-29 20:00:00,13247.0 -2018-01-29 21:00:00,13058.0 -2018-01-29 22:00:00,12794.0 -2018-01-29 23:00:00,12341.0 -2018-01-30 00:00:00,11699.0 -2018-01-28 01:00:00,9405.0 -2018-01-28 02:00:00,9027.0 -2018-01-28 03:00:00,8857.0 -2018-01-28 04:00:00,8796.0 -2018-01-28 05:00:00,8730.0 -2018-01-28 06:00:00,8838.0 -2018-01-28 07:00:00,8982.0 -2018-01-28 08:00:00,9331.0 -2018-01-28 09:00:00,9359.0 -2018-01-28 10:00:00,9504.0 -2018-01-28 11:00:00,9562.0 -2018-01-28 12:00:00,9619.0 -2018-01-28 13:00:00,9648.0 -2018-01-28 14:00:00,9693.0 -2018-01-28 15:00:00,9765.0 -2018-01-28 16:00:00,9938.0 -2018-01-28 17:00:00,10139.0 -2018-01-28 18:00:00,10555.0 -2018-01-28 19:00:00,11119.0 -2018-01-28 20:00:00,11175.0 -2018-01-28 21:00:00,11113.0 -2018-01-28 22:00:00,10938.0 -2018-01-28 23:00:00,10605.0 -2018-01-29 00:00:00,10201.0 -2018-01-27 01:00:00,9836.0 -2018-01-27 02:00:00,9417.0 -2018-01-27 03:00:00,9124.0 -2018-01-27 04:00:00,8901.0 -2018-01-27 05:00:00,8757.0 -2018-01-27 06:00:00,8852.0 -2018-01-27 07:00:00,9070.0 -2018-01-27 08:00:00,9549.0 -2018-01-27 09:00:00,9825.0 -2018-01-27 10:00:00,10054.0 -2018-01-27 11:00:00,10130.0 -2018-01-27 12:00:00,10180.0 -2018-01-27 13:00:00,10025.0 -2018-01-27 14:00:00,9907.0 -2018-01-27 15:00:00,9671.0 -2018-01-27 16:00:00,9576.0 -2018-01-27 17:00:00,9532.0 -2018-01-27 18:00:00,9770.0 -2018-01-27 19:00:00,10489.0 -2018-01-27 20:00:00,10619.0 -2018-01-27 21:00:00,10540.0 -2018-01-27 22:00:00,10401.0 -2018-01-27 23:00:00,10191.0 -2018-01-28 00:00:00,9798.0 -2018-01-26 01:00:00,10334.0 -2018-01-26 02:00:00,9956.0 -2018-01-26 03:00:00,9721.0 -2018-01-26 04:00:00,9587.0 -2018-01-26 05:00:00,9562.0 -2018-01-26 06:00:00,9810.0 -2018-01-26 07:00:00,10480.0 -2018-01-26 08:00:00,11508.0 -2018-01-26 09:00:00,11868.0 -2018-01-26 10:00:00,11846.0 -2018-01-26 11:00:00,11777.0 -2018-01-26 12:00:00,11746.0 -2018-01-26 13:00:00,11624.0 -2018-01-26 14:00:00,11550.0 -2018-01-26 15:00:00,11544.0 -2018-01-26 16:00:00,11510.0 -2018-01-26 17:00:00,11392.0 -2018-01-26 18:00:00,11621.0 -2018-01-26 19:00:00,12087.0 -2018-01-26 20:00:00,11923.0 -2018-01-26 21:00:00,11711.0 -2018-01-26 22:00:00,11425.0 -2018-01-26 23:00:00,11000.0 -2018-01-27 00:00:00,10428.0 -2018-01-25 01:00:00,10742.0 -2018-01-25 02:00:00,10359.0 -2018-01-25 03:00:00,10161.0 -2018-01-25 04:00:00,10057.0 -2018-01-25 05:00:00,10036.0 -2018-01-25 06:00:00,10310.0 -2018-01-25 07:00:00,11010.0 -2018-01-25 08:00:00,12064.0 -2018-01-25 09:00:00,12515.0 -2018-01-25 10:00:00,12491.0 -2018-01-25 11:00:00,12371.0 -2018-01-25 12:00:00,12243.0 -2018-01-25 13:00:00,12086.0 -2018-01-25 14:00:00,11904.0 -2018-01-25 15:00:00,11814.0 -2018-01-25 16:00:00,11660.0 -2018-01-25 17:00:00,11591.0 -2018-01-25 18:00:00,11812.0 -2018-01-25 19:00:00,12472.0 -2018-01-25 20:00:00,12459.0 -2018-01-25 21:00:00,12332.0 -2018-01-25 22:00:00,12061.0 -2018-01-25 23:00:00,11622.0 -2018-01-26 00:00:00,10935.0 -2018-01-24 01:00:00,10523.0 -2018-01-24 02:00:00,10156.0 -2018-01-24 03:00:00,9945.0 -2018-01-24 04:00:00,9804.0 -2018-01-24 05:00:00,9787.0 -2018-01-24 06:00:00,10080.0 -2018-01-24 07:00:00,10806.0 -2018-01-24 08:00:00,11844.0 -2018-01-24 09:00:00,12397.0 -2018-01-24 10:00:00,12467.0 -2018-01-24 11:00:00,12522.0 -2018-01-24 12:00:00,12620.0 -2018-01-24 13:00:00,12631.0 -2018-01-24 14:00:00,12617.0 -2018-01-24 15:00:00,12675.0 -2018-01-24 16:00:00,12545.0 -2018-01-24 17:00:00,12470.0 -2018-01-24 18:00:00,12775.0 -2018-01-24 19:00:00,13194.0 -2018-01-24 20:00:00,13037.0 -2018-01-24 21:00:00,12869.0 -2018-01-24 22:00:00,12542.0 -2018-01-24 23:00:00,12072.0 -2018-01-25 00:00:00,11315.0 -2018-01-23 01:00:00,9848.0 -2018-01-23 02:00:00,9512.0 -2018-01-23 03:00:00,9321.0 -2018-01-23 04:00:00,9226.0 -2018-01-23 05:00:00,9263.0 -2018-01-23 06:00:00,9596.0 -2018-01-23 07:00:00,10350.0 -2018-01-23 08:00:00,11495.0 -2018-01-23 09:00:00,12108.0 -2018-01-23 10:00:00,12210.0 -2018-01-23 11:00:00,12337.0 -2018-01-23 12:00:00,12491.0 -2018-01-23 13:00:00,12499.0 -2018-01-23 14:00:00,12485.0 -2018-01-23 15:00:00,12517.0 -2018-01-23 16:00:00,12445.0 -2018-01-23 17:00:00,12450.0 -2018-01-23 18:00:00,12698.0 -2018-01-23 19:00:00,13078.0 -2018-01-23 20:00:00,12911.0 -2018-01-23 21:00:00,12638.0 -2018-01-23 22:00:00,12358.0 -2018-01-23 23:00:00,11845.0 -2018-01-24 00:00:00,11177.0 -2018-01-22 01:00:00,9313.0 -2018-01-22 02:00:00,9022.0 -2018-01-22 03:00:00,8900.0 -2018-01-22 04:00:00,8788.0 -2018-01-22 05:00:00,8904.0 -2018-01-22 06:00:00,9173.0 -2018-01-22 07:00:00,9979.0 -2018-01-22 08:00:00,10990.0 -2018-01-22 09:00:00,11673.0 -2018-01-22 10:00:00,11697.0 -2018-01-22 11:00:00,11711.0 -2018-01-22 12:00:00,11779.0 -2018-01-22 13:00:00,11802.0 -2018-01-22 14:00:00,11751.0 -2018-01-22 15:00:00,11699.0 -2018-01-22 16:00:00,11449.0 -2018-01-22 17:00:00,11288.0 -2018-01-22 18:00:00,11536.0 -2018-01-22 19:00:00,12058.0 -2018-01-22 20:00:00,11968.0 -2018-01-22 21:00:00,11792.0 -2018-01-22 22:00:00,11555.0 -2018-01-22 23:00:00,11050.0 -2018-01-23 00:00:00,10434.0 -2018-01-21 01:00:00,9446.0 -2018-01-21 02:00:00,9108.0 -2018-01-21 03:00:00,8875.0 -2018-01-21 04:00:00,8706.0 -2018-01-21 05:00:00,8674.0 -2018-01-21 06:00:00,8669.0 -2018-01-21 07:00:00,8876.0 -2018-01-21 08:00:00,9117.0 -2018-01-21 09:00:00,9283.0 -2018-01-21 10:00:00,9494.0 -2018-01-21 11:00:00,9741.0 -2018-01-21 12:00:00,9916.0 -2018-01-21 13:00:00,10005.0 -2018-01-21 14:00:00,10044.0 -2018-01-21 15:00:00,10007.0 -2018-01-21 16:00:00,10087.0 -2018-01-21 17:00:00,10172.0 -2018-01-21 18:00:00,10550.0 -2018-01-21 19:00:00,11060.0 -2018-01-21 20:00:00,11011.0 -2018-01-21 21:00:00,10910.0 -2018-01-21 22:00:00,10621.0 -2018-01-21 23:00:00,10263.0 -2018-01-22 00:00:00,9769.0 -2018-01-20 01:00:00,10423.0 -2018-01-20 02:00:00,10067.0 -2018-01-20 03:00:00,9756.0 -2018-01-20 04:00:00,9533.0 -2018-01-20 05:00:00,9498.0 -2018-01-20 06:00:00,9527.0 -2018-01-20 07:00:00,9895.0 -2018-01-20 08:00:00,10316.0 -2018-01-20 09:00:00,10565.0 -2018-01-20 10:00:00,10681.0 -2018-01-20 11:00:00,10698.0 -2018-01-20 12:00:00,10682.0 -2018-01-20 13:00:00,10505.0 -2018-01-20 14:00:00,10357.0 -2018-01-20 15:00:00,10077.0 -2018-01-20 16:00:00,9976.0 -2018-01-20 17:00:00,9917.0 -2018-01-20 18:00:00,10296.0 -2018-01-20 19:00:00,10962.0 -2018-01-20 20:00:00,10997.0 -2018-01-20 21:00:00,10849.0 -2018-01-20 22:00:00,10657.0 -2018-01-20 23:00:00,10377.0 -2018-01-21 00:00:00,9920.0 -2018-01-19 01:00:00,11099.0 -2018-01-19 02:00:00,10726.0 -2018-01-19 03:00:00,10482.0 -2018-01-19 04:00:00,10362.0 -2018-01-19 05:00:00,10375.0 -2018-01-19 06:00:00,10604.0 -2018-01-19 07:00:00,11245.0 -2018-01-19 08:00:00,12284.0 -2018-01-19 09:00:00,12667.0 -2018-01-19 10:00:00,12586.0 -2018-01-19 11:00:00,12502.0 -2018-01-19 12:00:00,12432.0 -2018-01-19 13:00:00,12291.0 -2018-01-19 14:00:00,12148.0 -2018-01-19 15:00:00,12111.0 -2018-01-19 16:00:00,11961.0 -2018-01-19 17:00:00,11851.0 -2018-01-19 18:00:00,12244.0 -2018-01-19 19:00:00,12851.0 -2018-01-19 20:00:00,12729.0 -2018-01-19 21:00:00,12444.0 -2018-01-19 22:00:00,12132.0 -2018-01-19 23:00:00,11692.0 -2018-01-20 00:00:00,11073.0 -2018-01-18 01:00:00,11968.0 -2018-01-18 02:00:00,11601.0 -2018-01-18 03:00:00,11363.0 -2018-01-18 04:00:00,11239.0 -2018-01-18 05:00:00,11195.0 -2018-01-18 06:00:00,11476.0 -2018-01-18 07:00:00,12074.0 -2018-01-18 08:00:00,13134.0 -2018-01-18 09:00:00,13549.0 -2018-01-18 10:00:00,13483.0 -2018-01-18 11:00:00,13353.0 -2018-01-18 12:00:00,13253.0 -2018-01-18 13:00:00,13018.0 -2018-01-18 14:00:00,12850.0 -2018-01-18 15:00:00,12734.0 -2018-01-18 16:00:00,12588.0 -2018-01-18 17:00:00,12527.0 -2018-01-18 18:00:00,12846.0 -2018-01-18 19:00:00,13572.0 -2018-01-18 20:00:00,13496.0 -2018-01-18 21:00:00,13306.0 -2018-01-18 22:00:00,13001.0 -2018-01-18 23:00:00,12454.0 -2018-01-19 00:00:00,11745.0 -2018-01-17 01:00:00,11905.0 -2018-01-17 02:00:00,11578.0 -2018-01-17 03:00:00,11368.0 -2018-01-17 04:00:00,11326.0 -2018-01-17 05:00:00,11340.0 -2018-01-17 06:00:00,11593.0 -2018-01-17 07:00:00,12262.0 -2018-01-17 08:00:00,13351.0 -2018-01-17 09:00:00,13864.0 -2018-01-17 10:00:00,13798.0 -2018-01-17 11:00:00,13648.0 -2018-01-17 12:00:00,13572.0 -2018-01-17 13:00:00,13410.0 -2018-01-17 14:00:00,13279.0 -2018-01-17 15:00:00,13218.0 -2018-01-17 16:00:00,13098.0 -2018-01-17 17:00:00,13085.0 -2018-01-17 18:00:00,13484.0 -2018-01-17 19:00:00,14257.0 -2018-01-17 20:00:00,14244.0 -2018-01-17 21:00:00,14128.0 -2018-01-17 22:00:00,13848.0 -2018-01-17 23:00:00,13298.0 -2018-01-18 00:00:00,12590.0 -2018-01-16 01:00:00,12073.0 -2018-01-16 02:00:00,11735.0 -2018-01-16 03:00:00,11559.0 -2018-01-16 04:00:00,11475.0 -2018-01-16 05:00:00,11506.0 -2018-01-16 06:00:00,11684.0 -2018-01-16 07:00:00,12285.0 -2018-01-16 08:00:00,13219.0 -2018-01-16 09:00:00,13690.0 -2018-01-16 10:00:00,13687.0 -2018-01-16 11:00:00,13603.0 -2018-01-16 12:00:00,13511.0 -2018-01-16 13:00:00,13355.0 -2018-01-16 14:00:00,13110.0 -2018-01-16 15:00:00,13080.0 -2018-01-16 16:00:00,12958.0 -2018-01-16 17:00:00,12918.0 -2018-01-16 18:00:00,13319.0 -2018-01-16 19:00:00,14023.0 -2018-01-16 20:00:00,13992.0 -2018-01-16 21:00:00,13848.0 -2018-01-16 22:00:00,13586.0 -2018-01-16 23:00:00,13124.0 -2018-01-17 00:00:00,12487.0 -2018-01-15 01:00:00,11433.0 -2018-01-15 02:00:00,11170.0 -2018-01-15 03:00:00,10984.0 -2018-01-15 04:00:00,10789.0 -2018-01-15 05:00:00,10837.0 -2018-01-15 06:00:00,11060.0 -2018-01-15 07:00:00,11497.0 -2018-01-15 08:00:00,12318.0 -2018-01-15 09:00:00,12763.0 -2018-01-15 10:00:00,12967.0 -2018-01-15 11:00:00,13168.0 -2018-01-15 12:00:00,13137.0 -2018-01-15 13:00:00,13149.0 -2018-01-15 14:00:00,13190.0 -2018-01-15 15:00:00,13298.0 -2018-01-15 16:00:00,13310.0 -2018-01-15 17:00:00,13342.0 -2018-01-15 18:00:00,13667.0 -2018-01-15 19:00:00,14304.0 -2018-01-15 20:00:00,14191.0 -2018-01-15 21:00:00,14006.0 -2018-01-15 22:00:00,13756.0 -2018-01-15 23:00:00,13260.0 -2018-01-16 00:00:00,12642.0 -2018-01-14 01:00:00,11673.0 -2018-01-14 02:00:00,11383.0 -2018-01-14 03:00:00,11200.0 -2018-01-14 04:00:00,11063.0 -2018-01-14 05:00:00,11014.0 -2018-01-14 06:00:00,11049.0 -2018-01-14 07:00:00,11291.0 -2018-01-14 08:00:00,11567.0 -2018-01-14 09:00:00,11671.0 -2018-01-14 10:00:00,11656.0 -2018-01-14 11:00:00,11727.0 -2018-01-14 12:00:00,11812.0 -2018-01-14 13:00:00,11762.0 -2018-01-14 14:00:00,11746.0 -2018-01-14 15:00:00,11699.0 -2018-01-14 16:00:00,11778.0 -2018-01-14 17:00:00,11918.0 -2018-01-14 18:00:00,12467.0 -2018-01-14 19:00:00,12986.0 -2018-01-14 20:00:00,13004.0 -2018-01-14 21:00:00,12966.0 -2018-01-14 22:00:00,12738.0 -2018-01-14 23:00:00,12423.0 -2018-01-15 00:00:00,11942.0 -2018-01-13 01:00:00,11805.0 -2018-01-13 02:00:00,11415.0 -2018-01-13 03:00:00,11171.0 -2018-01-13 04:00:00,11024.0 -2018-01-13 05:00:00,10992.0 -2018-01-13 06:00:00,11143.0 -2018-01-13 07:00:00,11467.0 -2018-01-13 08:00:00,11884.0 -2018-01-13 09:00:00,12150.0 -2018-01-13 10:00:00,12231.0 -2018-01-13 11:00:00,12245.0 -2018-01-13 12:00:00,12191.0 -2018-01-13 13:00:00,12210.0 -2018-01-13 14:00:00,12077.0 -2018-01-13 15:00:00,11848.0 -2018-01-13 16:00:00,11714.0 -2018-01-13 17:00:00,11765.0 -2018-01-13 18:00:00,12172.0 -2018-01-13 19:00:00,12938.0 -2018-01-13 20:00:00,12928.0 -2018-01-13 21:00:00,12830.0 -2018-01-13 22:00:00,12706.0 -2018-01-13 23:00:00,12483.0 -2018-01-14 00:00:00,12113.0 -2018-01-12 01:00:00,10608.0 -2018-01-12 02:00:00,10278.0 -2018-01-12 03:00:00,10093.0 -2018-01-12 04:00:00,10058.0 -2018-01-12 05:00:00,10071.0 -2018-01-12 06:00:00,10345.0 -2018-01-12 07:00:00,11055.0 -2018-01-12 08:00:00,12101.0 -2018-01-12 09:00:00,12762.0 -2018-01-12 10:00:00,12967.0 -2018-01-12 11:00:00,13109.0 -2018-01-12 12:00:00,13232.0 -2018-01-12 13:00:00,13223.0 -2018-01-12 14:00:00,13221.0 -2018-01-12 15:00:00,13264.0 -2018-01-12 16:00:00,13175.0 -2018-01-12 17:00:00,13115.0 -2018-01-12 18:00:00,13425.0 -2018-01-12 19:00:00,13868.0 -2018-01-12 20:00:00,13749.0 -2018-01-12 21:00:00,13550.0 -2018-01-12 22:00:00,13321.0 -2018-01-12 23:00:00,12985.0 -2018-01-13 00:00:00,12391.0 -2018-01-11 01:00:00,9886.0 -2018-01-11 02:00:00,9427.0 -2018-01-11 03:00:00,9110.0 -2018-01-11 04:00:00,8947.0 -2018-01-11 05:00:00,8919.0 -2018-01-11 06:00:00,9125.0 -2018-01-11 07:00:00,9760.0 -2018-01-11 08:00:00,10694.0 -2018-01-11 09:00:00,11334.0 -2018-01-11 10:00:00,11396.0 -2018-01-11 11:00:00,11479.0 -2018-01-11 12:00:00,11528.0 -2018-01-11 13:00:00,11528.0 -2018-01-11 14:00:00,11527.0 -2018-01-11 15:00:00,11555.0 -2018-01-11 16:00:00,11470.0 -2018-01-11 17:00:00,11521.0 -2018-01-11 18:00:00,11955.0 -2018-01-11 19:00:00,12264.0 -2018-01-11 20:00:00,12242.0 -2018-01-11 21:00:00,12173.0 -2018-01-11 22:00:00,12074.0 -2018-01-11 23:00:00,11750.0 -2018-01-12 00:00:00,11155.0 -2018-01-10 01:00:00,10814.0 -2018-01-10 02:00:00,10372.0 -2018-01-10 03:00:00,10134.0 -2018-01-10 04:00:00,9995.0 -2018-01-10 05:00:00,9945.0 -2018-01-10 06:00:00,10193.0 -2018-01-10 07:00:00,10793.0 -2018-01-10 08:00:00,11782.0 -2018-01-10 09:00:00,12305.0 -2018-01-10 10:00:00,12317.0 -2018-01-10 11:00:00,12357.0 -2018-01-10 12:00:00,12408.0 -2018-01-10 13:00:00,12350.0 -2018-01-10 14:00:00,12278.0 -2018-01-10 15:00:00,12219.0 -2018-01-10 16:00:00,12134.0 -2018-01-10 17:00:00,12086.0 -2018-01-10 18:00:00,12429.0 -2018-01-10 19:00:00,12673.0 -2018-01-10 20:00:00,12405.0 -2018-01-10 21:00:00,12141.0 -2018-01-10 22:00:00,11797.0 -2018-01-10 23:00:00,11256.0 -2018-01-11 00:00:00,10546.0 -2018-01-09 01:00:00,10891.0 -2018-01-09 02:00:00,10452.0 -2018-01-09 03:00:00,10180.0 -2018-01-09 04:00:00,10039.0 -2018-01-09 05:00:00,10075.0 -2018-01-09 06:00:00,10362.0 -2018-01-09 07:00:00,11023.0 -2018-01-09 08:00:00,12075.0 -2018-01-09 09:00:00,12602.0 -2018-01-09 10:00:00,12597.0 -2018-01-09 11:00:00,12636.0 -2018-01-09 12:00:00,12602.0 -2018-01-09 13:00:00,12450.0 -2018-01-09 14:00:00,12302.0 -2018-01-09 15:00:00,12236.0 -2018-01-09 16:00:00,12138.0 -2018-01-09 17:00:00,12162.0 -2018-01-09 18:00:00,12663.0 -2018-01-09 19:00:00,13189.0 -2018-01-09 20:00:00,13064.0 -2018-01-09 21:00:00,12876.0 -2018-01-09 22:00:00,12610.0 -2018-01-09 23:00:00,12137.0 -2018-01-10 00:00:00,11442.0 -2018-01-08 01:00:00,10866.0 -2018-01-08 02:00:00,10525.0 -2018-01-08 03:00:00,10254.0 -2018-01-08 04:00:00,10060.0 -2018-01-08 05:00:00,10099.0 -2018-01-08 06:00:00,10312.0 -2018-01-08 07:00:00,10977.0 -2018-01-08 08:00:00,11998.0 -2018-01-08 09:00:00,12515.0 -2018-01-08 10:00:00,12514.0 -2018-01-08 11:00:00,12522.0 -2018-01-08 12:00:00,12445.0 -2018-01-08 13:00:00,12315.0 -2018-01-08 14:00:00,12221.0 -2018-01-08 15:00:00,12175.0 -2018-01-08 16:00:00,12032.0 -2018-01-08 17:00:00,11992.0 -2018-01-08 18:00:00,12469.0 -2018-01-08 19:00:00,13199.0 -2018-01-08 20:00:00,13105.0 -2018-01-08 21:00:00,12981.0 -2018-01-08 22:00:00,12733.0 -2018-01-08 23:00:00,12240.0 -2018-01-09 00:00:00,11540.0 -2018-01-07 01:00:00,12231.0 -2018-01-07 02:00:00,11820.0 -2018-01-07 03:00:00,11560.0 -2018-01-07 04:00:00,11310.0 -2018-01-07 05:00:00,11230.0 -2018-01-07 06:00:00,11187.0 -2018-01-07 07:00:00,11306.0 -2018-01-07 08:00:00,11417.0 -2018-01-07 09:00:00,11507.0 -2018-01-07 10:00:00,11727.0 -2018-01-07 11:00:00,11890.0 -2018-01-07 12:00:00,12053.0 -2018-01-07 13:00:00,12111.0 -2018-01-07 14:00:00,12066.0 -2018-01-07 15:00:00,12002.0 -2018-01-07 16:00:00,11988.0 -2018-01-07 17:00:00,11992.0 -2018-01-07 18:00:00,12483.0 -2018-01-07 19:00:00,12924.0 -2018-01-07 20:00:00,12828.0 -2018-01-07 21:00:00,12607.0 -2018-01-07 22:00:00,12361.0 -2018-01-07 23:00:00,11982.0 -2018-01-08 00:00:00,11409.0 -2018-01-06 01:00:00,12846.0 -2018-01-06 02:00:00,12423.0 -2018-01-06 03:00:00,12167.0 -2018-01-06 04:00:00,12013.0 -2018-01-06 05:00:00,11970.0 -2018-01-06 06:00:00,12032.0 -2018-01-06 07:00:00,12306.0 -2018-01-06 08:00:00,12691.0 -2018-01-06 09:00:00,12900.0 -2018-01-06 10:00:00,13008.0 -2018-01-06 11:00:00,13060.0 -2018-01-06 12:00:00,12967.0 -2018-01-06 13:00:00,12878.0 -2018-01-06 14:00:00,12630.0 -2018-01-06 15:00:00,12437.0 -2018-01-06 16:00:00,12235.0 -2018-01-06 17:00:00,12289.0 -2018-01-06 18:00:00,12814.0 -2018-01-06 19:00:00,13630.0 -2018-01-06 20:00:00,13666.0 -2018-01-06 21:00:00,13612.0 -2018-01-06 22:00:00,13461.0 -2018-01-06 23:00:00,13217.0 -2018-01-07 00:00:00,12725.0 -2018-01-05 01:00:00,12806.0 -2018-01-05 02:00:00,12388.0 -2018-01-05 03:00:00,12167.0 -2018-01-05 04:00:00,12032.0 -2018-01-05 05:00:00,12057.0 -2018-01-05 06:00:00,12243.0 -2018-01-05 07:00:00,12795.0 -2018-01-05 08:00:00,13647.0 -2018-01-05 09:00:00,14065.0 -2018-01-05 10:00:00,14152.0 -2018-01-05 11:00:00,14135.0 -2018-01-05 12:00:00,14123.0 -2018-01-05 13:00:00,13998.0 -2018-01-05 14:00:00,13844.0 -2018-01-05 15:00:00,13765.0 -2018-01-05 16:00:00,13640.0 -2018-01-05 17:00:00,13628.0 -2018-01-05 18:00:00,14143.0 -2018-01-05 19:00:00,14785.0 -2018-01-05 20:00:00,14783.0 -2018-01-05 21:00:00,14628.0 -2018-01-05 22:00:00,14418.0 -2018-01-05 23:00:00,14072.0 -2018-01-06 00:00:00,13426.0 -2018-01-04 01:00:00,12649.0 -2018-01-04 02:00:00,12250.0 -2018-01-04 03:00:00,12010.0 -2018-01-04 04:00:00,11867.0 -2018-01-04 05:00:00,11890.0 -2018-01-04 06:00:00,12099.0 -2018-01-04 07:00:00,12669.0 -2018-01-04 08:00:00,13507.0 -2018-01-04 09:00:00,13939.0 -2018-01-04 10:00:00,14054.0 -2018-01-04 11:00:00,14022.0 -2018-01-04 12:00:00,14000.0 -2018-01-04 13:00:00,13915.0 -2018-01-04 14:00:00,13794.0 -2018-01-04 15:00:00,13721.0 -2018-01-04 16:00:00,13624.0 -2018-01-04 17:00:00,13607.0 -2018-01-04 18:00:00,14149.0 -2018-01-04 19:00:00,14906.0 -2018-01-04 20:00:00,14875.0 -2018-01-04 21:00:00,14735.0 -2018-01-04 22:00:00,14518.0 -2018-01-04 23:00:00,14027.0 -2018-01-05 00:00:00,13453.0 -2018-01-03 01:00:00,12995.0 -2018-01-03 02:00:00,12494.0 -2018-01-03 03:00:00,12135.0 -2018-01-03 04:00:00,11901.0 -2018-01-03 05:00:00,11787.0 -2018-01-03 06:00:00,11923.0 -2018-01-03 07:00:00,12395.0 -2018-01-03 08:00:00,13135.0 -2018-01-03 09:00:00,13575.0 -2018-01-03 10:00:00,13753.0 -2018-01-03 11:00:00,13858.0 -2018-01-03 12:00:00,13913.0 -2018-01-03 13:00:00,13847.0 -2018-01-03 14:00:00,13802.0 -2018-01-03 15:00:00,13739.0 -2018-01-03 16:00:00,13672.0 -2018-01-03 17:00:00,13744.0 -2018-01-03 18:00:00,14294.0 -2018-01-03 19:00:00,14887.0 -2018-01-03 20:00:00,14866.0 -2018-01-03 21:00:00,14595.0 -2018-01-03 22:00:00,14314.0 -2018-01-03 23:00:00,13880.0 -2018-01-04 00:00:00,13234.0 -2018-01-02 01:00:00,12315.0 -2018-01-02 02:00:00,11981.0 -2018-01-02 03:00:00,11805.0 -2018-01-02 04:00:00,11684.0 -2018-01-02 05:00:00,11728.0 -2018-01-02 06:00:00,11991.0 -2018-01-02 07:00:00,12559.0 -2018-01-02 08:00:00,13379.0 -2018-01-02 09:00:00,13854.0 -2018-01-02 10:00:00,14040.0 -2018-01-02 11:00:00,14179.0 -2018-01-02 12:00:00,14241.0 -2018-01-02 13:00:00,14204.0 -2018-01-02 14:00:00,14138.0 -2018-01-02 15:00:00,14078.0 -2018-01-02 16:00:00,13984.0 -2018-01-02 17:00:00,14011.0 -2018-01-02 18:00:00,14614.0 -2018-01-02 19:00:00,15405.0 -2018-01-02 20:00:00,15408.0 -2018-01-02 21:00:00,15246.0 -2018-01-02 22:00:00,14933.0 -2018-01-02 23:00:00,14437.0 -2018-01-03 00:00:00,13704.0 -2018-01-01 01:00:00,12263.0 -2018-01-01 02:00:00,11982.0 -2018-01-01 03:00:00,11747.0 -2018-01-01 04:00:00,11581.0 -2018-01-01 05:00:00,11565.0 -2018-01-01 06:00:00,11585.0 -2018-01-01 07:00:00,11773.0 -2018-01-01 08:00:00,11949.0 -2018-01-01 09:00:00,11940.0 -2018-01-01 10:00:00,11891.0 -2018-01-01 11:00:00,11979.0 -2018-01-01 12:00:00,12096.0 -2018-01-01 13:00:00,12155.0 -2018-01-01 14:00:00,12131.0 -2018-01-01 15:00:00,12064.0 -2018-01-01 16:00:00,12053.0 -2018-01-01 17:00:00,12183.0 -2018-01-01 18:00:00,12926.0 -2018-01-01 19:00:00,13809.0 -2018-01-01 20:00:00,13858.0 -2018-01-01 21:00:00,13758.0 -2018-01-01 22:00:00,13627.0 -2018-01-01 23:00:00,13336.0 -2018-01-02 00:00:00,12816.0 diff --git a/docker/images/pinot-thirdeye/config/ephemeral/data/pageviews.csv b/docker/images/pinot-thirdeye/config/ephemeral/data/pageviews.csv deleted file mode 100644 index ccba33d4efe8..000000000000 --- a/docker/images/pinot-thirdeye/config/ephemeral/data/pageviews.csv +++ /dev/null @@ -1,67 +0,0 @@ -timestamp,country,device,pageviews -2011-01-01,us,android,1985 -2011-01-01,us,ios,985 -2011-01-01,in,android,2000 -2011-01-01,in,ios,909 -2011-01-01,cn,android,1983 -2011-01-01,cn,ios,1200 -2011-01-02,us,android,1785 -2011-01-02,us,ios,1085 -2011-01-02,in,android,2100 -2011-01-02,in,ios,919 -2011-01-02,cn,android,1883 -2011-01-02,cn,ios,1100 -2011-01-03,us,android,1885 -2011-01-03,us,ios,985 -2011-01-03,in,android,1990 -2011-01-03,in,ios,920 -2011-01-03,cn,android,1893 -2011-01-03,cn,ios,1200 -2011-01-04,us,android,1985 -2011-01-04,us,ios,885 -2011-01-04,in,android,1890 -2011-01-04,in,ios,820 -2011-01-04,cn,android,1993 -2011-01-04,cn,ios,1280 -2011-01-05,us,android,1890 -2011-01-05,us,ios,905 -2011-01-05,in,android,1890 -2011-01-05,in,ios,870 -2011-01-05,cn,android,1693 -2011-01-05,cn,ios,900 -2011-01-06,us,android,1690 -2011-01-06,us,ios,995 -2011-01-06,in,android,1790 -2011-01-06,in,ios,890 -2011-01-06,cn,android,1613 -2011-01-06,cn,ios,880 -2011-01-07,us,android,1785 -2011-01-07,us,ios,1085 -2011-01-07,in,android,2100 -2011-01-07,in,ios,919 -2011-01-07,cn,android,1883 -2011-01-07,cn,ios,1100 -2011-01-08,us,android,1995 -2011-01-08,us,ios,1085 -2011-01-08,in,android,950 -2011-01-08,in,ios,999 -2011-01-08,cn,android,1183 -2011-01-08,cn,ios,800 -2011-01-09,us,android,1990 -2011-01-09,us,ios,995 -2011-01-09,in,android,1790 -2011-01-09,in,ios,890 -2011-01-09,cn,android,1613 -2011-01-09,cn,ios,980 -2011-01-10,us,android,1690 -2011-01-10,us,ios,1095 -2011-01-10,in,android,1590 -2011-01-10,in,ios,790 -2011-01-10,cn,android,1713 -2011-01-10,cn,ios,999 -2011-01-11,us,android,1590 -2011-01-11,us,ios,1195 -2011-01-11,in,android,1890 -2011-01-11,in,ios,990 -2011-01-11,cn,android,1513 -2011-01-11,cn,ios,1300 \ No newline at end of file diff --git a/docker/images/pinot-thirdeye/config/ephemeral/detector-config/anomaly-functions/alertFilterAutotune.properties b/docker/images/pinot-thirdeye/config/ephemeral/detector-config/anomaly-functions/alertFilterAutotune.properties deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/docker/images/pinot-thirdeye/config/ephemeral/detector-config/anomaly-functions/anomalyClassifier.properties b/docker/images/pinot-thirdeye/config/ephemeral/detector-config/anomaly-functions/anomalyClassifier.properties deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/docker/images/pinot-thirdeye/config/ephemeral/detector-config/anomaly-functions/functions.properties b/docker/images/pinot-thirdeye/config/ephemeral/detector-config/anomaly-functions/functions.properties deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/docker/images/pinot-thirdeye/config/ephemeral/detector.yml b/docker/images/pinot-thirdeye/config/ephemeral/detector.yml deleted file mode 100644 index a8516ec45e30..000000000000 --- a/docker/images/pinot-thirdeye/config/ephemeral/detector.yml +++ /dev/null @@ -1,127 +0,0 @@ -logging: - type: external -server: - requestLog: - type: external - type: default - rootPath: '/api/*' - applicationContextPath: / - adminContextPath: /admin - applicationConnectors: - - type: http - port: 1867 - adminConnectors: - - type: http - port: 1868 -alert: false -autoload: false -classifier: false -holidayEventsLoader: false -mockEventsLoader: true -monitor: false -pinotProxy: false -scheduler: false -worker: false -detectionPipeline: false -detectionAlert: false -dashboardHost: "http://thirdeye-dashboard:1426" -id: 0 -alerterConfiguration: - smtpConfiguration: - smtpHost: "localhost" - smtpPort: 25 -# jiraConfiguration: -# jiraUser: -# jiraPassword: -# jiraUrl: -# jiraDefaultProject: -# jiraIssueTypeId: 19 -failureFromAddress: "thirdeye@localhost" -failureToAddress: "thirdeye@localhost" -phantomJsPath: "/usr/local/bin/jstf" -swagger: - resourcePackage: "org.apache.pinot.thirdeye.dashboard.resources,org.apache.pinot.thirdeye.dashboard.resources.v2,org.apache.pinot.thirdeye.anomaly.onboard" -holidayEventsLoaderConfiguration: - calendars: - - "en.australian#holiday@group.v.calendar.google.com" - - "en.austrian#holiday@group.v.calendar.google.com" - - "en.brazilian#holiday@group.v.calendar.google.com" - - "en.canadian#holiday@group.v.calendar.google.com" - - "en.china#holiday@group.v.calendar.google.com" - - "en.christian#holiday@group.v.calendar.google.com" - - "en.danish#holiday@group.v.calendar.google.com" - - "en.dutch#holiday@group.v.calendar.google.com" - - "en.finnish#holiday@group.v.calendar.google.com" - - "en.french#holiday@group.v.calendar.google.com" - - "en.german#holiday@group.v.calendar.google.com" - - "en.greek#holiday@group.v.calendar.google.com" - - "en.hong_kong#holiday@group.v.calendar.google.com" - - "en.indian#holiday@group.v.calendar.google.com" - - "en.indonesian#holiday@group.v.calendar.google.com" - - "en.irish#holiday@group.v.calendar.google.com" - - "en.islamic#holiday@group.v.calendar.google.com" - - "en.italian#holiday@group.v.calendar.google.com" - - "en.japanese#holiday@group.v.calendar.google.com" - - "en.jewish#holiday@group.v.calendar.google.com" - - "en.malaysia#holiday@group.v.calendar.google.com" - - "en.mexican#holiday@group.v.calendar.google.com" - - "en.new_zealand#holiday@group.v.calendar.google.com" - - "en.norwegian#holiday@group.v.calendar.google.com" - - "en.philippines#holiday@group.v.calendar.google.com" - - "en.polish#holiday@group.v.calendar.google.com" - - "en.portuguese#holiday@group.v.calendar.google.com" - - "en.russian#holiday@group.v.calendar.google.com" - - "en.singapore#holiday@group.v.calendar.google.com" - - "en.sa#holiday@group.v.calendar.google.com" - - "en.south_korea#holiday@group.v.calendar.google.com" - - "en.spain#holiday@group.v.calendar.google.com" - - "en.swedish#holiday@group.v.calendar.google.com" - - "en.taiwan#holiday@group.v.calendar.google.com" - - "en.thai#holiday@group.v.calendar.google.com" - - "en.uk#holiday@group.v.calendar.google.com" - - "en.usa#holiday@group.v.calendar.google.com" - - "en.vietnamese#holiday@group.v.calendar.google.com" - holidayLoadRange: 2592000000 - runFrequency: 7 -mockEventsLoaderConfiguration: - generators: - - type: HOLIDAY - arrivalType: exponential - arrivalMean: 86400000 - durationType: fixed - durationMean: 86400000 - seed: 0 - namePrefixes: [First, Second, Third, Last, Funky, Happy, Sad, Glorious, Jolly, Unity, Pinot's] - nameSuffixes: [day, day, days, celebration, rememberance, occurrence, moment] - - type: INFORMED - arrivalType: exponential - arrivalMean: 43200000 - durationType: exponential - durationMean: 3600000 - seed: 1 - namePrefixes: [Login, Web, Search, Catalog, Integration, Network, Backup, Ingress, Proxy, Failure, Pinot, ThirdEye] - nameSuffixes: [backend, frontend, v1.1, v1.2, v1.3, v2.0, v3, v4, v5, storage, topic, container, database] - - type: CM - arrivalType: exponential - arrivalMean: 21600000 - durationType: fixed - durationMean: 1800000 - seed: 2 - namePrefixes: [Database, Web, Search, Catalog, Integration, Network, Backup, Ingress, Proxy, Failure, Pinot, ThirdEye] - - type: CUSTOM - arrivalType: exponential - arrivalMean: 432000000 - durationType: exponential - durationMean: 86400000 - seed: 3 - namePrefixes: [Marketing, Onboarding, Vaction, Outreach, InDay] - nameSuffixes: [integration, campaign, meeting] - - type: LIX - arrivalType: exponential - arrivalMean: 259200000 - durationType: exponential - durationMean: 604800000 - seed: 4 - namePrefixes: [System, Model, Campaign, Welcome, Pinot, ThirdEye] - nameSuffixes: [tuning, bugfix, rollout, test] - diff --git a/docker/images/pinot-thirdeye/config/ephemeral/persistence.yml b/docker/images/pinot-thirdeye/config/ephemeral/persistence.yml deleted file mode 100644 index 991d18dcf07e..000000000000 --- a/docker/images/pinot-thirdeye/config/ephemeral/persistence.yml +++ /dev/null @@ -1,5 +0,0 @@ -databaseConfiguration: - url: jdbc:h2:tcp://localhost/h2db - user: sa - password: sa - driver: org.h2.Driver diff --git a/docker/images/pinot-thirdeye/config/ephemeral/rca.yml b/docker/images/pinot-thirdeye/config/ephemeral/rca.yml deleted file mode 100644 index aecfd76a85fe..000000000000 --- a/docker/images/pinot-thirdeye/config/ephemeral/rca.yml +++ /dev/null @@ -1,98 +0,0 @@ -frameworks: - identity: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.NullPipeline - - metricRelated: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.MetricMappingPipeline - - metricAnalysis: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.MetricAnalysisPipeline - - metricBreakdown: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.MetricBreakdownPipeline - - metricComponentAnalysis: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.MetricComponentAnalysisPipeline - properties: - excludeDimensions: ["environment", "continent"] - parallelism: 5 - k: 5 - - eventExperiment: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.ThirdEyeEventsPipeline - properties: - strategy: COMPOUND - k: 500 - eventType: LIX - - eventHoliday: - - outputName: METRIC_RELATED - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.MetricAnalysisPipeline - - - outputName: OUTPUT - inputNames: [INPUT, METRIC_RELATED] - className: org.apache.pinot.thirdeye.rootcause.impl.ThirdEyeEventsPipeline - properties: - strategy: COMPOUND - k: 500 - eventType: HOLIDAY - - eventCustom: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.ThirdEyeEventsPipeline - properties: - strategy: COMPOUND - k: 500 - eventType: CUSTOM - - eventAnomaly: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.AnomalyEventsPipeline - properties: - strategyClass: org.apache.pinot.thirdeye.rootcause.impl.AnomalyEventsPipeline$TimeRangeScoreStrategy - strategyProperties: - type: HYPERBOLA - k: 500 - - eventIssue: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.EmptyPipeline - - eventChange: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.ThirdEyeEventsPipeline - properties: - strategy: COMPOUND - k: 500 - eventType: CM - - eventDeployment: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.ThirdEyeEventsPipeline - properties: - strategy: COMPOUND - k: 500 - eventType: INFORMED - - eventAC: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.EmptyPipeline diff --git a/docker/images/pinot-thirdeye/config/pinot-quickstart/bootstrap.sql b/docker/images/pinot-thirdeye/config/pinot-quickstart/bootstrap.sql deleted file mode 100644 index b9ac3d0b85a0..000000000000 --- a/docker/images/pinot-thirdeye/config/pinot-quickstart/bootstrap.sql +++ /dev/null @@ -1,4 +0,0 @@ -INSERT INTO generic_json_entity (id, json_val, beanClass) VALUES (null, '{"id":null,"application":"test","recipients":""}', 'org.apache.pinot.thirdeye.datalayer.pojo.ApplicationBean'); - -INSERT INTO application_index (base_id, application, recipients) VALUES (1, 'test', ''); - diff --git a/docker/images/pinot-thirdeye/config/pinot-quickstart/dashboard.yml b/docker/images/pinot-thirdeye/config/pinot-quickstart/dashboard.yml deleted file mode 100644 index b039b8dac040..000000000000 --- a/docker/images/pinot-thirdeye/config/pinot-quickstart/dashboard.yml +++ /dev/null @@ -1,40 +0,0 @@ -logging: - type: external -authConfig: - authEnabled: false - authKey: "" - ldapUrl: "" - domainSuffix: [] - cacheTTL: 3600 - cookieTTL: 604800 -resourceConfig: [] -rootCause: - definitionsPath: rca.yml - parallelism: 5 - formatters: - - org.apache.pinot.thirdeye.dashboard.resources.v2.rootcause.AnomalyEventFormatter - - org.apache.pinot.thirdeye.dashboard.resources.v2.rootcause.ThirdEyeEventFormatter - - org.apache.pinot.thirdeye.dashboard.resources.v2.rootcause.MetricEntityFormatter - - org.apache.pinot.thirdeye.dashboard.resources.v2.rootcause.DimensionEntityFormatter - - org.apache.pinot.thirdeye.dashboard.resources.v2.rootcause.ServiceEntityFormatter - - org.apache.pinot.thirdeye.dashboard.resources.v2.rootcause.DefaultEventEntityFormatter -dashboardHost: "http://localhost:1426" -failureFromAddress: thirdeye@localhost -failureToAddress: user@localhost -alerterConfiguration: - smtpConfiguration: - smtpHost: localhost - smtpPort: 25 -server: - requestLog: - type: external - type: default - applicationConnectors: - - type: http - port: 1426 - adminConnectors: - - type: http - port: 1427 -whitelistDatasets: [] -swagger: - resourcePackage: "org.apache.pinot.thirdeye.dashboard.resources,org.apache.pinot.thirdeye.dashboard.resources.v2,org.apache.pinot.thirdeye.anomaly.onboard,org.apache.pinot.thirdeye.detection,org.apache.pinot.thirdeye.detection.yaml" diff --git a/docker/images/pinot-thirdeye/config/pinot-quickstart/data-sources/cache-config.yml b/docker/images/pinot-thirdeye/config/pinot-quickstart/data-sources/cache-config.yml deleted file mode 100644 index 5672706ba36f..000000000000 --- a/docker/images/pinot-thirdeye/config/pinot-quickstart/data-sources/cache-config.yml +++ /dev/null @@ -1,34 +0,0 @@ -useInMemoryCache: true -useCentralizedCache: false - -centralizedCacheSettings: - # TTL (time-to-live) for documents in seconds - ttl: 3600 - # if inserting data points individually, max number of threads to spawn to parallel insert at a time - maxParallelInserts: 10 - # which store to use - cacheDataStoreName: 'couchbase' - cacheDataSources: - couchbase: - className: org.apache.pinot.thirdeye.detection.cache.CouchbaseCacheDAO - config: - useCertificateBasedAuthentication: false - # at least 1 host needed - hosts: - - 'host1' # ex. http://localhost:8091 - - 'host2' # ex. http://localhost:8092 - - 'host3' # ex. http://localhost:8093 - - 'host4' # and so on... - bucketName: 'your_bucket_name' - # if using certificate-based authentication, authUsername and authPassword values don't matter and won't be used - authUsername: 'your_bucket_user_username' - authPassword: 'your_bucket_user_password' - enableDnsSrv: false - # certificate based authentication is only available in Couchbase enterprise edition. - keyStoreFilePath: 'key/store/path/keystore_file' # e.g. '/var/identity.p12' - # if your keystore has a password, enter it here. by default, Java uses 'work_around_jdk-6879539' to enable empty passwords for certificates. - keyStorePassword: 'work_around_jdk-6879539' - trustStoreFilePath: 'trust/store/path/truststore_file' # e.g. '/etc/riddler/cacerts' - trustStorePassword: '' - # add your store of choice here - diff --git a/docker/images/pinot-thirdeye/config/pinot-quickstart/data-sources/data-sources-config.yml b/docker/images/pinot-thirdeye/config/pinot-quickstart/data-sources/data-sources-config.yml deleted file mode 100644 index 9f1158ffcfa3..000000000000 --- a/docker/images/pinot-thirdeye/config/pinot-quickstart/data-sources/data-sources-config.yml +++ /dev/null @@ -1,14 +0,0 @@ - -# Please put the mock data source as the first in this configuration. -dataSourceConfigs: - - className: org.apache.pinot.thirdeye.datasource.pinot.PinotThirdEyeDataSource - properties: - zookeeperUrl: 'pinot-quickstart:2123' - clusterName: 'QuickStartCluster' - controllerConnectionScheme: 'http' - controllerHost: 'pinot-quickstart' - controllerPort: 9000 - cacheLoaderClassName: org.apache.pinot.thirdeye.datasource.pinot.PinotControllerResponseCacheLoader - metadataSourceConfigs: - - className: org.apache.pinot.thirdeye.auto.onboard.AutoOnboardPinotMetadataSource - diff --git a/docker/images/pinot-thirdeye/config/pinot-quickstart/data/README.md b/docker/images/pinot-thirdeye/config/pinot-quickstart/data/README.md deleted file mode 100644 index 6f12739ddeee..000000000000 --- a/docker/images/pinot-thirdeye/config/pinot-quickstart/data/README.md +++ /dev/null @@ -1,7 +0,0 @@ -`daily.csv`: daily data - -From Kaggle: https://www.kaggle.com/marklvl/bike-sharing-dataset#Bike-Sharing-Dataset.zip - - `hourly.csv`: hourly data - - From Kaggle: https://www.kaggle.com/robikscube/hourly-energy-consumption \ No newline at end of file diff --git a/docker/images/pinot-thirdeye/config/pinot-quickstart/data/daily.csv b/docker/images/pinot-thirdeye/config/pinot-quickstart/data/daily.csv deleted file mode 100644 index 094897484692..000000000000 --- a/docker/images/pinot-thirdeye/config/pinot-quickstart/data/daily.csv +++ /dev/null @@ -1,732 +0,0 @@ -date,value -2011-01-01,985 -2011-01-02,801 -2011-01-03,1349 -2011-01-04,1562 -2011-01-05,1600 -2011-01-06,1606 -2011-01-07,1510 -2011-01-08,959 -2011-01-09,822 -2011-01-10,1321 -2011-01-11,1263 -2011-01-12,1162 -2011-01-13,1406 -2011-01-14,1421 -2011-01-15,1248 -2011-01-16,1204 -2011-01-17,1000 -2011-01-18,683 -2011-01-19,1650 -2011-01-20,1927 -2011-01-21,1543 -2011-01-22,981 -2011-01-23,986 -2011-01-24,1416 -2011-01-25,1985 -2011-01-26,506 -2011-01-27,431 -2011-01-28,1167 -2011-01-29,1098 -2011-01-30,1096 -2011-01-31,1501 -2011-02-01,1360 -2011-02-02,1526 -2011-02-03,1550 -2011-02-04,1708 -2011-02-05,1005 -2011-02-06,1623 -2011-02-07,1712 -2011-02-08,1530 -2011-02-09,1605 -2011-02-10,1538 -2011-02-11,1746 -2011-02-12,1472 -2011-02-13,1589 -2011-02-14,1913 -2011-02-15,1815 -2011-02-16,2115 -2011-02-17,2475 -2011-02-18,2927 -2011-02-19,1635 -2011-02-20,1812 -2011-02-21,1107 -2011-02-22,1450 -2011-02-23,1917 -2011-02-24,1807 -2011-02-25,1461 -2011-02-26,1969 -2011-02-27,2402 -2011-02-28,1446 -2011-03-01,1851 -2011-03-02,2134 -2011-03-03,1685 -2011-03-04,1944 -2011-03-05,2077 -2011-03-06,605 -2011-03-07,1872 -2011-03-08,2133 -2011-03-09,1891 -2011-03-10,623 -2011-03-11,1977 -2011-03-12,2132 -2011-03-13,2417 -2011-03-14,2046 -2011-03-15,2056 -2011-03-16,2192 -2011-03-17,2744 -2011-03-18,3239 -2011-03-19,3117 -2011-03-20,2471 -2011-03-21,2077 -2011-03-22,2703 -2011-03-23,2121 -2011-03-24,1865 -2011-03-25,2210 -2011-03-26,2496 -2011-03-27,1693 -2011-03-28,2028 -2011-03-29,2425 -2011-03-30,1536 -2011-03-31,1685 -2011-04-01,2227 -2011-04-02,2252 -2011-04-03,3249 -2011-04-04,3115 -2011-04-05,1795 -2011-04-06,2808 -2011-04-07,3141 -2011-04-08,1471 -2011-04-09,2455 -2011-04-10,2895 -2011-04-11,3348 -2011-04-12,2034 -2011-04-13,2162 -2011-04-14,3267 -2011-04-15,3126 -2011-04-16,795 -2011-04-17,3744 -2011-04-18,3429 -2011-04-19,3204 -2011-04-20,3944 -2011-04-21,4189 -2011-04-22,1683 -2011-04-23,4036 -2011-04-24,4191 -2011-04-25,4073 -2011-04-26,4400 -2011-04-27,3872 -2011-04-28,4058 -2011-04-29,4595 -2011-04-30,5312 -2011-05-01,3351 -2011-05-02,4401 -2011-05-03,4451 -2011-05-04,2633 -2011-05-05,4433 -2011-05-06,4608 -2011-05-07,4714 -2011-05-08,4333 -2011-05-09,4362 -2011-05-10,4803 -2011-05-11,4182 -2011-05-12,4864 -2011-05-13,4105 -2011-05-14,3409 -2011-05-15,4553 -2011-05-16,3958 -2011-05-17,4123 -2011-05-18,3855 -2011-05-19,4575 -2011-05-20,4917 -2011-05-21,5805 -2011-05-22,4660 -2011-05-23,4274 -2011-05-24,4492 -2011-05-25,4978 -2011-05-26,4677 -2011-05-27,4679 -2011-05-28,4758 -2011-05-29,4788 -2011-05-30,4098 -2011-05-31,3982 -2011-06-01,3974 -2011-06-02,4968 -2011-06-03,5312 -2011-06-04,5342 -2011-06-05,4906 -2011-06-06,4548 -2011-06-07,4833 -2011-06-08,4401 -2011-06-09,3915 -2011-06-10,4586 -2011-06-11,4966 -2011-06-12,4460 -2011-06-13,5020 -2011-06-14,4891 -2011-06-15,5180 -2011-06-16,3767 -2011-06-17,4844 -2011-06-18,5119 -2011-06-19,4744 -2011-06-20,4010 -2011-06-21,4835 -2011-06-22,4507 -2011-06-23,4790 -2011-06-24,4991 -2011-06-25,5202 -2011-06-26,5305 -2011-06-27,4708 -2011-06-28,4648 -2011-06-29,5225 -2011-06-30,5515 -2011-07-01,5362 -2011-07-02,5119 -2011-07-03,4649 -2011-07-04,6043 -2011-07-05,4665 -2011-07-06,4629 -2011-07-07,4592 -2011-07-08,4040 -2011-07-09,5336 -2011-07-10,4881 -2011-07-11,4086 -2011-07-12,4258 -2011-07-13,4342 -2011-07-14,5084 -2011-07-15,5538 -2011-07-16,5923 -2011-07-17,5302 -2011-07-18,4458 -2011-07-19,4541 -2011-07-20,4332 -2011-07-21,3784 -2011-07-22,3387 -2011-07-23,3285 -2011-07-24,3606 -2011-07-25,3840 -2011-07-26,4590 -2011-07-27,4656 -2011-07-28,4390 -2011-07-29,3846 -2011-07-30,4475 -2011-07-31,4302 -2011-08-01,4266 -2011-08-02,4845 -2011-08-03,3574 -2011-08-04,4576 -2011-08-05,4866 -2011-08-06,4294 -2011-08-07,3785 -2011-08-08,4326 -2011-08-09,4602 -2011-08-10,4780 -2011-08-11,4792 -2011-08-12,4905 -2011-08-13,4150 -2011-08-14,3820 -2011-08-15,4338 -2011-08-16,4725 -2011-08-17,4694 -2011-08-18,3805 -2011-08-19,4153 -2011-08-20,5191 -2011-08-21,3873 -2011-08-22,4758 -2011-08-23,5895 -2011-08-24,5130 -2011-08-25,3542 -2011-08-26,4661 -2011-08-27,1115 -2011-08-28,4334 -2011-08-29,4634 -2011-08-30,5204 -2011-08-31,5058 -2011-09-01,5115 -2011-09-02,4727 -2011-09-03,4484 -2011-09-04,4940 -2011-09-05,3351 -2011-09-06,2710 -2011-09-07,1996 -2011-09-08,1842 -2011-09-09,3544 -2011-09-10,5345 -2011-09-11,5046 -2011-09-12,4713 -2011-09-13,4763 -2011-09-14,4785 -2011-09-15,3659 -2011-09-16,4760 -2011-09-17,4511 -2011-09-18,4274 -2011-09-19,4539 -2011-09-20,3641 -2011-09-21,4352 -2011-09-22,4795 -2011-09-23,2395 -2011-09-24,5423 -2011-09-25,5010 -2011-09-26,4630 -2011-09-27,4120 -2011-09-28,3907 -2011-09-29,4839 -2011-09-30,5202 -2011-10-01,2429 -2011-10-02,2918 -2011-10-03,3570 -2011-10-04,4456 -2011-10-05,4826 -2011-10-06,4765 -2011-10-07,4985 -2011-10-08,5409 -2011-10-09,5511 -2011-10-10,5117 -2011-10-11,4563 -2011-10-12,2416 -2011-10-13,2913 -2011-10-14,3644 -2011-10-15,5217 -2011-10-16,5041 -2011-10-17,4570 -2011-10-18,4748 -2011-10-19,2424 -2011-10-20,4195 -2011-10-21,4304 -2011-10-22,4308 -2011-10-23,4381 -2011-10-24,4187 -2011-10-25,4687 -2011-10-26,3894 -2011-10-27,2659 -2011-10-28,3747 -2011-10-29,627 -2011-10-30,3331 -2011-10-31,3669 -2011-11-01,4068 -2011-11-02,4186 -2011-11-03,3974 -2011-11-04,4046 -2011-11-05,3926 -2011-11-06,3649 -2011-11-07,4035 -2011-11-08,4205 -2011-11-09,4109 -2011-11-10,2933 -2011-11-11,3368 -2011-11-12,4067 -2011-11-13,3717 -2011-11-14,4486 -2011-11-15,4195 -2011-11-16,1817 -2011-11-17,3053 -2011-11-18,3392 -2011-11-19,3663 -2011-11-20,3520 -2011-11-21,2765 -2011-11-22,1607 -2011-11-23,2566 -2011-11-24,1495 -2011-11-25,2792 -2011-11-26,3068 -2011-11-27,3071 -2011-11-28,3867 -2011-11-29,2914 -2011-11-30,3613 -2011-12-01,3727 -2011-12-02,3940 -2011-12-03,3614 -2011-12-04,3485 -2011-12-05,3811 -2011-12-06,2594 -2011-12-07,705 -2011-12-08,3322 -2011-12-09,3620 -2011-12-10,3190 -2011-12-11,2743 -2011-12-12,3310 -2011-12-13,3523 -2011-12-14,3740 -2011-12-15,3709 -2011-12-16,3577 -2011-12-17,2739 -2011-12-18,2431 -2011-12-19,3403 -2011-12-20,3750 -2011-12-21,2660 -2011-12-22,3068 -2011-12-23,2209 -2011-12-24,1011 -2011-12-25,754 -2011-12-26,1317 -2011-12-27,1162 -2011-12-28,2302 -2011-12-29,2423 -2011-12-30,2999 -2011-12-31,2485 -2012-01-01,2294 -2012-01-02,1951 -2012-01-03,2236 -2012-01-04,2368 -2012-01-05,3272 -2012-01-06,4098 -2012-01-07,4521 -2012-01-08,3425 -2012-01-09,2376 -2012-01-10,3598 -2012-01-11,2177 -2012-01-12,4097 -2012-01-13,3214 -2012-01-14,2493 -2012-01-15,2311 -2012-01-16,2298 -2012-01-17,2935 -2012-01-18,3376 -2012-01-19,3292 -2012-01-20,3163 -2012-01-21,1301 -2012-01-22,1977 -2012-01-23,2432 -2012-01-24,4339 -2012-01-25,4270 -2012-01-26,4075 -2012-01-27,3456 -2012-01-28,4023 -2012-01-29,3243 -2012-01-30,3624 -2012-01-31,4509 -2012-02-01,4579 -2012-02-02,3761 -2012-02-03,4151 -2012-02-04,2832 -2012-02-05,2947 -2012-02-06,3784 -2012-02-07,4375 -2012-02-08,2802 -2012-02-09,3830 -2012-02-10,3831 -2012-02-11,2169 -2012-02-12,1529 -2012-02-13,3422 -2012-02-14,3922 -2012-02-15,4169 -2012-02-16,3005 -2012-02-17,4154 -2012-02-18,4318 -2012-02-19,2689 -2012-02-20,3129 -2012-02-21,3777 -2012-02-22,4773 -2012-02-23,5062 -2012-02-24,3487 -2012-02-25,2732 -2012-02-26,3389 -2012-02-27,4322 -2012-02-28,4363 -2012-02-29,1834 -2012-03-01,4990 -2012-03-02,3194 -2012-03-03,4066 -2012-03-04,3423 -2012-03-05,3333 -2012-03-06,3956 -2012-03-07,4916 -2012-03-08,5382 -2012-03-09,4569 -2012-03-10,4118 -2012-03-11,4911 -2012-03-12,5298 -2012-03-13,5847 -2012-03-14,6312 -2012-03-15,6192 -2012-03-16,4378 -2012-03-17,7836 -2012-03-18,5892 -2012-03-19,6153 -2012-03-20,6093 -2012-03-21,6230 -2012-03-22,6871 -2012-03-23,8362 -2012-03-24,3372 -2012-03-25,4996 -2012-03-26,5558 -2012-03-27,5102 -2012-03-28,5698 -2012-03-29,6133 -2012-03-30,5459 -2012-03-31,6235 -2012-04-01,6041 -2012-04-02,5936 -2012-04-03,6772 -2012-04-04,6436 -2012-04-05,6457 -2012-04-06,6460 -2012-04-07,6857 -2012-04-08,5169 -2012-04-09,5585 -2012-04-10,5918 -2012-04-11,4862 -2012-04-12,5409 -2012-04-13,6398 -2012-04-14,7460 -2012-04-15,7132 -2012-04-16,6370 -2012-04-17,6691 -2012-04-18,4367 -2012-04-19,6565 -2012-04-20,7290 -2012-04-21,6624 -2012-04-22,1027 -2012-04-23,3214 -2012-04-24,5633 -2012-04-25,6196 -2012-04-26,5026 -2012-04-27,6233 -2012-04-28,4220 -2012-04-29,6304 -2012-04-30,5572 -2012-05-01,5740 -2012-05-02,6169 -2012-05-03,6421 -2012-05-04,6296 -2012-05-05,6883 -2012-05-06,6359 -2012-05-07,6273 -2012-05-08,5728 -2012-05-09,4717 -2012-05-10,6572 -2012-05-11,7030 -2012-05-12,7429 -2012-05-13,6118 -2012-05-14,2843 -2012-05-15,5115 -2012-05-16,7424 -2012-05-17,7384 -2012-05-18,7639 -2012-05-19,8294 -2012-05-20,7129 -2012-05-21,4359 -2012-05-22,6073 -2012-05-23,5260 -2012-05-24,6770 -2012-05-25,6734 -2012-05-26,6536 -2012-05-27,6591 -2012-05-28,6043 -2012-05-29,5743 -2012-05-30,6855 -2012-05-31,7338 -2012-06-01,4127 -2012-06-02,8120 -2012-06-03,7641 -2012-06-04,6998 -2012-06-05,7001 -2012-06-06,7055 -2012-06-07,7494 -2012-06-08,7736 -2012-06-09,7498 -2012-06-10,6598 -2012-06-11,6664 -2012-06-12,4972 -2012-06-13,7421 -2012-06-14,7363 -2012-06-15,7665 -2012-06-16,7702 -2012-06-17,6978 -2012-06-18,5099 -2012-06-19,6825 -2012-06-20,6211 -2012-06-21,5905 -2012-06-22,5823 -2012-06-23,7458 -2012-06-24,6891 -2012-06-25,6779 -2012-06-26,7442 -2012-06-27,7335 -2012-06-28,6879 -2012-06-29,5463 -2012-06-30,5687 -2012-07-01,5531 -2012-07-02,6227 -2012-07-03,6660 -2012-07-04,7403 -2012-07-05,6241 -2012-07-06,6207 -2012-07-07,4840 -2012-07-08,4672 -2012-07-09,6569 -2012-07-10,6290 -2012-07-11,7264 -2012-07-12,7446 -2012-07-13,7499 -2012-07-14,6969 -2012-07-15,6031 -2012-07-16,6830 -2012-07-17,6786 -2012-07-18,5713 -2012-07-19,6591 -2012-07-20,5870 -2012-07-21,4459 -2012-07-22,7410 -2012-07-23,6966 -2012-07-24,7592 -2012-07-25,8173 -2012-07-26,6861 -2012-07-27,6904 -2012-07-28,6685 -2012-07-29,6597 -2012-07-30,7105 -2012-07-31,7216 -2012-08-01,7580 -2012-08-02,7261 -2012-08-03,7175 -2012-08-04,6824 -2012-08-05,5464 -2012-08-06,7013 -2012-08-07,7273 -2012-08-08,7534 -2012-08-09,7286 -2012-08-10,5786 -2012-08-11,6299 -2012-08-12,6544 -2012-08-13,6883 -2012-08-14,6784 -2012-08-15,7347 -2012-08-16,7605 -2012-08-17,7148 -2012-08-18,7865 -2012-08-19,4549 -2012-08-20,6530 -2012-08-21,7006 -2012-08-22,7375 -2012-08-23,7765 -2012-08-24,7582 -2012-08-25,6053 -2012-08-26,5255 -2012-08-27,6917 -2012-08-28,7040 -2012-08-29,7697 -2012-08-30,7713 -2012-08-31,7350 -2012-09-01,6140 -2012-09-02,5810 -2012-09-03,6034 -2012-09-04,6864 -2012-09-05,7112 -2012-09-06,6203 -2012-09-07,7504 -2012-09-08,5976 -2012-09-09,8227 -2012-09-10,7525 -2012-09-11,7767 -2012-09-12,7870 -2012-09-13,7804 -2012-09-14,8009 -2012-09-15,8714 -2012-09-16,7333 -2012-09-17,6869 -2012-09-18,4073 -2012-09-19,7591 -2012-09-20,7720 -2012-09-21,8167 -2012-09-22,8395 -2012-09-23,7907 -2012-09-24,7436 -2012-09-25,7538 -2012-09-26,7733 -2012-09-27,7393 -2012-09-28,7415 -2012-09-29,8555 -2012-09-30,6889 -2012-10-01,6778 -2012-10-02,4639 -2012-10-03,7572 -2012-10-04,7328 -2012-10-05,8156 -2012-10-06,7965 -2012-10-07,3510 -2012-10-08,5478 -2012-10-09,6392 -2012-10-10,7691 -2012-10-11,7570 -2012-10-12,7282 -2012-10-13,7109 -2012-10-14,6639 -2012-10-15,5875 -2012-10-16,7534 -2012-10-17,7461 -2012-10-18,7509 -2012-10-19,5424 -2012-10-20,8090 -2012-10-21,6824 -2012-10-22,7058 -2012-10-23,7466 -2012-10-24,7693 -2012-10-25,7359 -2012-10-26,7444 -2012-10-27,7852 -2012-10-28,4459 -2012-10-29,22 -2012-10-30,1096 -2012-10-31,5566 -2012-11-01,5986 -2012-11-02,5847 -2012-11-03,5138 -2012-11-04,5107 -2012-11-05,5259 -2012-11-06,5686 -2012-11-07,5035 -2012-11-08,5315 -2012-11-09,5992 -2012-11-10,6536 -2012-11-11,6852 -2012-11-12,6269 -2012-11-13,4094 -2012-11-14,5495 -2012-11-15,5445 -2012-11-16,5698 -2012-11-17,5629 -2012-11-18,4669 -2012-11-19,5499 -2012-11-20,5634 -2012-11-21,5146 -2012-11-22,2425 -2012-11-23,3910 -2012-11-24,2277 -2012-11-25,2424 -2012-11-26,5087 -2012-11-27,3959 -2012-11-28,5260 -2012-11-29,5323 -2012-11-30,5668 -2012-12-01,5191 -2012-12-02,4649 -2012-12-03,6234 -2012-12-04,6606 -2012-12-05,5729 -2012-12-06,5375 -2012-12-07,5008 -2012-12-08,5582 -2012-12-09,3228 -2012-12-10,5170 -2012-12-11,5501 -2012-12-12,5319 -2012-12-13,5532 -2012-12-14,5611 -2012-12-15,5047 -2012-12-16,3786 -2012-12-17,4585 -2012-12-18,5557 -2012-12-19,5267 -2012-12-20,4128 -2012-12-21,3623 -2012-12-22,1749 -2012-12-23,1787 -2012-12-24,920 -2012-12-25,1013 -2012-12-26,441 -2012-12-27,2114 -2012-12-28,3095 -2012-12-29,1341 -2012-12-30,1796 -2012-12-31,2729 diff --git a/docker/images/pinot-thirdeye/config/pinot-quickstart/data/hourly.csv b/docker/images/pinot-thirdeye/config/pinot-quickstart/data/hourly.csv deleted file mode 100755 index 7714a9380334..000000000000 --- a/docker/images/pinot-thirdeye/config/pinot-quickstart/data/hourly.csv +++ /dev/null @@ -1,66498 +0,0 @@ -datetime,value -2011-12-31 01:00:00,9970.0 -2011-12-31 02:00:00,9428.0 -2011-12-31 03:00:00,9059.0 -2011-12-31 04:00:00,8817.0 -2011-12-31 05:00:00,8743.0 -2011-12-31 06:00:00,8735.0 -2011-12-31 07:00:00,8993.0 -2011-12-31 08:00:00,9363.0 -2011-12-31 09:00:00,9545.0 -2011-12-31 10:00:00,9676.0 -2011-12-31 11:00:00,9937.0 -2011-12-31 12:00:00,10139.0 -2011-12-31 13:00:00,10326.0 -2011-12-31 14:00:00,10359.0 -2011-12-31 15:00:00,10293.0 -2011-12-31 16:00:00,10240.0 -2011-12-31 17:00:00,10416.0 -2011-12-31 18:00:00,11225.0 -2011-12-31 19:00:00,11907.0 -2011-12-31 20:00:00,11812.0 -2011-12-31 21:00:00,11542.0 -2011-12-31 22:00:00,11149.0 -2011-12-31 23:00:00,10855.0 -2012-01-01 00:00:00,10335.0 -2011-12-30 01:00:00,10126.0 -2011-12-30 02:00:00,9535.0 -2011-12-30 03:00:00,9182.0 -2011-12-30 04:00:00,8945.0 -2011-12-30 05:00:00,8874.0 -2011-12-30 06:00:00,9070.0 -2011-12-30 07:00:00,9583.0 -2011-12-30 08:00:00,10329.0 -2011-12-30 09:00:00,10804.0 -2011-12-30 10:00:00,11100.0 -2011-12-30 11:00:00,11396.0 -2011-12-30 12:00:00,11586.0 -2011-12-30 13:00:00,11598.0 -2011-12-30 14:00:00,11511.0 -2011-12-30 15:00:00,11429.0 -2011-12-30 16:00:00,11317.0 -2011-12-30 17:00:00,11384.0 -2011-12-30 18:00:00,11996.0 -2011-12-30 19:00:00,12440.0 -2011-12-30 20:00:00,12248.0 -2011-12-30 21:00:00,12023.0 -2011-12-30 22:00:00,11717.0 -2011-12-30 23:00:00,11318.0 -2011-12-31 00:00:00,10728.0 -2011-12-29 01:00:00,10743.0 -2011-12-29 02:00:00,10083.0 -2011-12-29 03:00:00,9701.0 -2011-12-29 04:00:00,9448.0 -2011-12-29 05:00:00,9353.0 -2011-12-29 06:00:00,9531.0 -2011-12-29 07:00:00,10111.0 -2011-12-29 08:00:00,10975.0 -2011-12-29 09:00:00,11415.0 -2011-12-29 10:00:00,11541.0 -2011-12-29 11:00:00,11634.0 -2011-12-29 12:00:00,11659.0 -2011-12-29 13:00:00,11564.0 -2011-12-29 14:00:00,11432.0 -2011-12-29 15:00:00,11343.0 -2011-12-29 16:00:00,11216.0 -2011-12-29 17:00:00,11140.0 -2011-12-29 18:00:00,11904.0 -2011-12-29 19:00:00,12754.0 -2011-12-29 20:00:00,12593.0 -2011-12-29 21:00:00,12406.0 -2011-12-29 22:00:00,12140.0 -2011-12-29 23:00:00,11661.0 -2011-12-30 00:00:00,10980.0 -2011-12-28 01:00:00,10615.0 -2011-12-28 02:00:00,10017.0 -2011-12-28 03:00:00,9736.0 -2011-12-28 04:00:00,9552.0 -2011-12-28 05:00:00,9557.0 -2011-12-28 06:00:00,9797.0 -2011-12-28 07:00:00,10448.0 -2011-12-28 08:00:00,11364.0 -2011-12-28 09:00:00,11861.0 -2011-12-28 10:00:00,12095.0 -2011-12-28 11:00:00,12287.0 -2011-12-28 12:00:00,12392.0 -2011-12-28 13:00:00,12385.0 -2011-12-28 14:00:00,12344.0 -2011-12-28 15:00:00,12268.0 -2011-12-28 16:00:00,12222.0 -2011-12-28 17:00:00,12249.0 -2011-12-28 18:00:00,12971.0 -2011-12-28 19:00:00,13628.0 -2011-12-28 20:00:00,13447.0 -2011-12-28 21:00:00,13216.0 -2011-12-28 22:00:00,12936.0 -2011-12-28 23:00:00,12432.0 -2011-12-29 00:00:00,11590.0 -2011-12-27 01:00:00,9950.0 -2011-12-27 02:00:00,9391.0 -2011-12-27 03:00:00,9030.0 -2011-12-27 04:00:00,8868.0 -2011-12-27 05:00:00,8832.0 -2011-12-27 06:00:00,9077.0 -2011-12-27 07:00:00,9737.0 -2011-12-27 08:00:00,10649.0 -2011-12-27 09:00:00,11243.0 -2011-12-27 10:00:00,11551.0 -2011-12-27 11:00:00,11809.0 -2011-12-27 12:00:00,11973.0 -2011-12-27 13:00:00,12054.0 -2011-12-27 14:00:00,12040.0 -2011-12-27 15:00:00,12062.0 -2011-12-27 16:00:00,12062.0 -2011-12-27 17:00:00,12133.0 -2011-12-27 18:00:00,12849.0 -2011-12-27 19:00:00,13311.0 -2011-12-27 20:00:00,13093.0 -2011-12-27 21:00:00,12874.0 -2011-12-27 22:00:00,12629.0 -2011-12-27 23:00:00,12122.0 -2011-12-28 00:00:00,11386.0 -2011-12-26 01:00:00,9620.0 -2011-12-26 02:00:00,9171.0 -2011-12-26 03:00:00,8892.0 -2011-12-26 04:00:00,8728.0 -2011-12-26 05:00:00,8723.0 -2011-12-26 06:00:00,8875.0 -2011-12-26 07:00:00,9212.0 -2011-12-26 08:00:00,9625.0 -2011-12-26 09:00:00,9707.0 -2011-12-26 10:00:00,9786.0 -2011-12-26 11:00:00,9954.0 -2011-12-26 12:00:00,10040.0 -2011-12-26 13:00:00,10062.0 -2011-12-26 14:00:00,9983.0 -2011-12-26 15:00:00,9887.0 -2011-12-26 16:00:00,9898.0 -2011-12-26 17:00:00,10172.0 -2011-12-26 18:00:00,11219.0 -2011-12-26 19:00:00,11963.0 -2011-12-26 20:00:00,11908.0 -2011-12-26 21:00:00,11820.0 -2011-12-26 22:00:00,11667.0 -2011-12-26 23:00:00,11280.0 -2011-12-27 00:00:00,10655.0 -2011-12-25 01:00:00,10042.0 -2011-12-25 02:00:00,9487.0 -2011-12-25 03:00:00,9073.0 -2011-12-25 04:00:00,8800.0 -2011-12-25 05:00:00,8662.0 -2011-12-25 06:00:00,8634.0 -2011-12-25 07:00:00,8760.0 -2011-12-25 08:00:00,9005.0 -2011-12-25 09:00:00,9076.0 -2011-12-25 10:00:00,9133.0 -2011-12-25 11:00:00,9219.0 -2011-12-25 12:00:00,9213.0 -2011-12-25 13:00:00,9209.0 -2011-12-25 14:00:00,9137.0 -2011-12-25 15:00:00,9056.0 -2011-12-25 16:00:00,9031.0 -2011-12-25 17:00:00,9087.0 -2011-12-25 18:00:00,9751.0 -2011-12-25 19:00:00,10526.0 -2011-12-25 20:00:00,10577.0 -2011-12-25 21:00:00,10630.0 -2011-12-25 22:00:00,10627.0 -2011-12-25 23:00:00,10563.0 -2011-12-26 00:00:00,10187.0 -2011-12-24 01:00:00,10698.0 -2011-12-24 02:00:00,10021.0 -2011-12-24 03:00:00,9505.0 -2011-12-24 04:00:00,9355.0 -2011-12-24 05:00:00,9237.0 -2011-12-24 06:00:00,9261.0 -2011-12-24 07:00:00,9498.0 -2011-12-24 08:00:00,9864.0 -2011-12-24 09:00:00,10029.0 -2011-12-24 10:00:00,10187.0 -2011-12-24 11:00:00,10327.0 -2011-12-24 12:00:00,10329.0 -2011-12-24 13:00:00,10292.0 -2011-12-24 14:00:00,10211.0 -2011-12-24 15:00:00,10034.0 -2011-12-24 16:00:00,9904.0 -2011-12-24 17:00:00,9919.0 -2011-12-24 18:00:00,10743.0 -2011-12-24 19:00:00,11519.0 -2011-12-24 20:00:00,11437.0 -2011-12-24 21:00:00,11235.0 -2011-12-24 22:00:00,11126.0 -2011-12-24 23:00:00,10934.0 -2011-12-25 00:00:00,10584.0 -2011-12-23 01:00:00,10853.0 -2011-12-23 02:00:00,10177.0 -2011-12-23 03:00:00,9758.0 -2011-12-23 04:00:00,9560.0 -2011-12-23 05:00:00,9504.0 -2011-12-23 06:00:00,9718.0 -2011-12-23 07:00:00,10265.0 -2011-12-23 08:00:00,11162.0 -2011-12-23 09:00:00,11534.0 -2011-12-23 10:00:00,11686.0 -2011-12-23 11:00:00,11707.0 -2011-12-23 12:00:00,11686.0 -2011-12-23 13:00:00,11509.0 -2011-12-23 14:00:00,11374.0 -2011-12-23 15:00:00,11301.0 -2011-12-23 16:00:00,11395.0 -2011-12-23 17:00:00,11668.0 -2011-12-23 18:00:00,12618.0 -2011-12-23 19:00:00,13158.0 -2011-12-23 20:00:00,12967.0 -2011-12-23 21:00:00,12736.0 -2011-12-23 22:00:00,12512.0 -2011-12-23 23:00:00,12165.0 -2011-12-24 00:00:00,11489.0 -2011-12-22 01:00:00,10668.0 -2011-12-22 02:00:00,10013.0 -2011-12-22 03:00:00,9584.0 -2011-12-22 04:00:00,9325.0 -2011-12-22 05:00:00,9303.0 -2011-12-22 06:00:00,9550.0 -2011-12-22 07:00:00,10219.0 -2011-12-22 08:00:00,11347.0 -2011-12-22 09:00:00,11903.0 -2011-12-22 10:00:00,12167.0 -2011-12-22 11:00:00,12327.0 -2011-12-22 12:00:00,12451.0 -2011-12-22 13:00:00,12476.0 -2011-12-22 14:00:00,12405.0 -2011-12-22 15:00:00,12544.0 -2011-12-22 16:00:00,12593.0 -2011-12-22 17:00:00,12763.0 -2011-12-22 18:00:00,13401.0 -2011-12-22 19:00:00,13766.0 -2011-12-22 20:00:00,13530.0 -2011-12-22 21:00:00,13326.0 -2011-12-22 22:00:00,13032.0 -2011-12-22 23:00:00,12580.0 -2011-12-23 00:00:00,11779.0 -2011-12-21 01:00:00,10682.0 -2011-12-21 02:00:00,9935.0 -2011-12-21 03:00:00,9518.0 -2011-12-21 04:00:00,9292.0 -2011-12-21 05:00:00,9248.0 -2011-12-21 06:00:00,9439.0 -2011-12-21 07:00:00,10028.0 -2011-12-21 08:00:00,11174.0 -2011-12-21 09:00:00,11891.0 -2011-12-21 10:00:00,12165.0 -2011-12-21 11:00:00,12344.0 -2011-12-21 12:00:00,12374.0 -2011-12-21 13:00:00,12319.0 -2011-12-21 14:00:00,12366.0 -2011-12-21 15:00:00,12476.0 -2011-12-21 16:00:00,12467.0 -2011-12-21 17:00:00,12612.0 -2011-12-21 18:00:00,13355.0 -2011-12-21 19:00:00,13655.0 -2011-12-21 20:00:00,13412.0 -2011-12-21 21:00:00,13205.0 -2011-12-21 22:00:00,12939.0 -2011-12-21 23:00:00,12466.0 -2011-12-22 00:00:00,11630.0 -2011-12-20 01:00:00,10576.0 -2011-12-20 02:00:00,9883.0 -2011-12-20 03:00:00,9525.0 -2011-12-20 04:00:00,9305.0 -2011-12-20 05:00:00,9276.0 -2011-12-20 06:00:00,9538.0 -2011-12-20 07:00:00,10244.0 -2011-12-20 08:00:00,11431.0 -2011-12-20 09:00:00,12162.0 -2011-12-20 10:00:00,12330.0 -2011-12-20 11:00:00,12362.0 -2011-12-20 12:00:00,12456.0 -2011-12-20 13:00:00,12486.0 -2011-12-20 14:00:00,12454.0 -2011-12-20 15:00:00,12476.0 -2011-12-20 16:00:00,12426.0 -2011-12-20 17:00:00,12590.0 -2011-12-20 18:00:00,13293.0 -2011-12-20 19:00:00,13796.0 -2011-12-20 20:00:00,13616.0 -2011-12-20 21:00:00,13380.0 -2011-12-20 22:00:00,13078.0 -2011-12-20 23:00:00,12559.0 -2011-12-21 00:00:00,11634.0 -2011-12-19 01:00:00,10102.0 -2011-12-19 02:00:00,9535.0 -2011-12-19 03:00:00,9225.0 -2011-12-19 04:00:00,9070.0 -2011-12-19 05:00:00,9072.0 -2011-12-19 06:00:00,9330.0 -2011-12-19 07:00:00,10038.0 -2011-12-19 08:00:00,11245.0 -2011-12-19 09:00:00,11838.0 -2011-12-19 10:00:00,11903.0 -2011-12-19 11:00:00,11960.0 -2011-12-19 12:00:00,11970.0 -2011-12-19 13:00:00,11896.0 -2011-12-19 14:00:00,11829.0 -2011-12-19 15:00:00,11869.0 -2011-12-19 16:00:00,11856.0 -2011-12-19 17:00:00,12113.0 -2011-12-19 18:00:00,13039.0 -2011-12-19 19:00:00,13522.0 -2011-12-19 20:00:00,13371.0 -2011-12-19 21:00:00,13170.0 -2011-12-19 22:00:00,12904.0 -2011-12-19 23:00:00,12402.0 -2011-12-20 00:00:00,11514.0 -2011-12-18 01:00:00,10664.0 -2011-12-18 02:00:00,10059.0 -2011-12-18 03:00:00,9583.0 -2011-12-18 04:00:00,9400.0 -2011-12-18 05:00:00,9280.0 -2011-12-18 06:00:00,9277.0 -2011-12-18 07:00:00,9472.0 -2011-12-18 08:00:00,9810.0 -2011-12-18 09:00:00,10037.0 -2011-12-18 10:00:00,10272.0 -2011-12-18 11:00:00,10477.0 -2011-12-18 12:00:00,10425.0 -2011-12-18 13:00:00,10351.0 -2011-12-18 14:00:00,10285.0 -2011-12-18 15:00:00,10175.0 -2011-12-18 16:00:00,10142.0 -2011-12-18 17:00:00,10223.0 -2011-12-18 18:00:00,11250.0 -2011-12-18 19:00:00,12159.0 -2011-12-18 20:00:00,12308.0 -2011-12-18 21:00:00,12234.0 -2011-12-18 22:00:00,12058.0 -2011-12-18 23:00:00,11626.0 -2011-12-19 00:00:00,10939.0 -2011-12-17 01:00:00,11120.0 -2011-12-17 02:00:00,10415.0 -2011-12-17 03:00:00,10010.0 -2011-12-17 04:00:00,9770.0 -2011-12-17 05:00:00,9682.0 -2011-12-17 06:00:00,9775.0 -2011-12-17 07:00:00,10113.0 -2011-12-17 08:00:00,10607.0 -2011-12-17 09:00:00,10939.0 -2011-12-17 10:00:00,11264.0 -2011-12-17 11:00:00,11566.0 -2011-12-17 12:00:00,11700.0 -2011-12-17 13:00:00,11730.0 -2011-12-17 14:00:00,11550.0 -2011-12-17 15:00:00,11395.0 -2011-12-17 16:00:00,11277.0 -2011-12-17 17:00:00,11462.0 -2011-12-17 18:00:00,12203.0 -2011-12-17 19:00:00,12734.0 -2011-12-17 20:00:00,12666.0 -2011-12-17 21:00:00,12527.0 -2011-12-17 22:00:00,12334.0 -2011-12-17 23:00:00,11999.0 -2011-12-18 00:00:00,11395.0 -2011-12-16 01:00:00,10981.0 -2011-12-16 02:00:00,10314.0 -2011-12-16 03:00:00,9944.0 -2011-12-16 04:00:00,9797.0 -2011-12-16 05:00:00,9809.0 -2011-12-16 06:00:00,10044.0 -2011-12-16 07:00:00,10831.0 -2011-12-16 08:00:00,12069.0 -2011-12-16 09:00:00,12676.0 -2011-12-16 10:00:00,12735.0 -2011-12-16 11:00:00,12637.0 -2011-12-16 12:00:00,12576.0 -2011-12-16 13:00:00,12352.0 -2011-12-16 14:00:00,12191.0 -2011-12-16 15:00:00,12119.0 -2011-12-16 16:00:00,11987.0 -2011-12-16 17:00:00,12060.0 -2011-12-16 18:00:00,12946.0 -2011-12-16 19:00:00,13744.0 -2011-12-16 20:00:00,13666.0 -2011-12-16 21:00:00,13399.0 -2011-12-16 22:00:00,13124.0 -2011-12-16 23:00:00,12759.0 -2011-12-17 00:00:00,11979.0 -2011-12-15 01:00:00,10211.0 -2011-12-15 02:00:00,9511.0 -2011-12-15 03:00:00,9126.0 -2011-12-15 04:00:00,8896.0 -2011-12-15 05:00:00,8864.0 -2011-12-15 06:00:00,9066.0 -2011-12-15 07:00:00,9808.0 -2011-12-15 08:00:00,11073.0 -2011-12-15 09:00:00,11843.0 -2011-12-15 10:00:00,12094.0 -2011-12-15 11:00:00,12185.0 -2011-12-15 12:00:00,12308.0 -2011-12-15 13:00:00,12368.0 -2011-12-15 14:00:00,12408.0 -2011-12-15 15:00:00,12564.0 -2011-12-15 16:00:00,12607.0 -2011-12-15 17:00:00,12790.0 -2011-12-15 18:00:00,13511.0 -2011-12-15 19:00:00,14016.0 -2011-12-15 20:00:00,13872.0 -2011-12-15 21:00:00,13698.0 -2011-12-15 22:00:00,13340.0 -2011-12-15 23:00:00,12863.0 -2011-12-16 00:00:00,11935.0 -2011-12-14 01:00:00,10517.0 -2011-12-14 02:00:00,9852.0 -2011-12-14 03:00:00,9448.0 -2011-12-14 04:00:00,9284.0 -2011-12-14 05:00:00,9228.0 -2011-12-14 06:00:00,9439.0 -2011-12-14 07:00:00,10186.0 -2011-12-14 08:00:00,11439.0 -2011-12-14 09:00:00,12193.0 -2011-12-14 10:00:00,12480.0 -2011-12-14 11:00:00,12741.0 -2011-12-14 12:00:00,12739.0 -2011-12-14 13:00:00,12584.0 -2011-12-14 14:00:00,12477.0 -2011-12-14 15:00:00,12514.0 -2011-12-14 16:00:00,12521.0 -2011-12-14 17:00:00,12688.0 -2011-12-14 18:00:00,13394.0 -2011-12-14 19:00:00,13628.0 -2011-12-14 20:00:00,13361.0 -2011-12-14 21:00:00,13112.0 -2011-12-14 22:00:00,12789.0 -2011-12-14 23:00:00,12206.0 -2011-12-15 00:00:00,11214.0 -2011-12-13 01:00:00,10490.0 -2011-12-13 02:00:00,9902.0 -2011-12-13 03:00:00,9531.0 -2011-12-13 04:00:00,9334.0 -2011-12-13 05:00:00,9290.0 -2011-12-13 06:00:00,9534.0 -2011-12-13 07:00:00,10297.0 -2011-12-13 08:00:00,11545.0 -2011-12-13 09:00:00,12265.0 -2011-12-13 10:00:00,12345.0 -2011-12-13 11:00:00,12382.0 -2011-12-13 12:00:00,12576.0 -2011-12-13 13:00:00,12405.0 -2011-12-13 14:00:00,12307.0 -2011-12-13 15:00:00,12323.0 -2011-12-13 16:00:00,12219.0 -2011-12-13 17:00:00,12351.0 -2011-12-13 18:00:00,13234.0 -2011-12-13 19:00:00,13637.0 -2011-12-13 20:00:00,13455.0 -2011-12-13 21:00:00,13231.0 -2011-12-13 22:00:00,12939.0 -2011-12-13 23:00:00,12386.0 -2011-12-14 00:00:00,11474.0 -2011-12-12 01:00:00,10335.0 -2011-12-12 02:00:00,9882.0 -2011-12-12 03:00:00,9609.0 -2011-12-12 04:00:00,9536.0 -2011-12-12 05:00:00,9585.0 -2011-12-12 06:00:00,9905.0 -2011-12-12 07:00:00,10698.0 -2011-12-12 08:00:00,12007.0 -2011-12-12 09:00:00,12594.0 -2011-12-12 10:00:00,12641.0 -2011-12-12 11:00:00,12553.0 -2011-12-12 12:00:00,12436.0 -2011-12-12 13:00:00,12358.0 -2011-12-12 14:00:00,12198.0 -2011-12-12 15:00:00,12141.0 -2011-12-12 16:00:00,12122.0 -2011-12-12 17:00:00,12175.0 -2011-12-12 18:00:00,13078.0 -2011-12-12 19:00:00,13766.0 -2011-12-12 20:00:00,13557.0 -2011-12-12 21:00:00,13309.0 -2011-12-12 22:00:00,13019.0 -2011-12-12 23:00:00,12461.0 -2011-12-13 00:00:00,11463.0 -2011-12-11 01:00:00,10985.0 -2011-12-11 02:00:00,10416.0 -2011-12-11 03:00:00,9967.0 -2011-12-11 04:00:00,9730.0 -2011-12-11 05:00:00,9700.0 -2011-12-11 06:00:00,9689.0 -2011-12-11 07:00:00,9850.0 -2011-12-11 08:00:00,10135.0 -2011-12-11 09:00:00,10186.0 -2011-12-11 10:00:00,10342.0 -2011-12-11 11:00:00,10457.0 -2011-12-11 12:00:00,10492.0 -2011-12-11 13:00:00,10428.0 -2011-12-11 14:00:00,10368.0 -2011-12-11 15:00:00,10234.0 -2011-12-11 16:00:00,10183.0 -2011-12-11 17:00:00,10314.0 -2011-12-11 18:00:00,11315.0 -2011-12-11 19:00:00,12238.0 -2011-12-11 20:00:00,12396.0 -2011-12-11 21:00:00,12389.0 -2011-12-11 22:00:00,12186.0 -2011-12-11 23:00:00,11823.0 -2011-12-12 00:00:00,11079.0 -2011-12-10 01:00:00,11502.0 -2011-12-10 02:00:00,10836.0 -2011-12-10 03:00:00,10510.0 -2011-12-10 04:00:00,10311.0 -2011-12-10 05:00:00,10272.0 -2011-12-10 06:00:00,10350.0 -2011-12-10 07:00:00,10703.0 -2011-12-10 08:00:00,11274.0 -2011-12-10 09:00:00,11550.0 -2011-12-10 10:00:00,11748.0 -2011-12-10 11:00:00,11889.0 -2011-12-10 12:00:00,11888.0 -2011-12-10 13:00:00,11789.0 -2011-12-10 14:00:00,11553.0 -2011-12-10 15:00:00,11331.0 -2011-12-10 16:00:00,11224.0 -2011-12-10 17:00:00,11318.0 -2011-12-10 18:00:00,12308.0 -2011-12-10 19:00:00,13128.0 -2011-12-10 20:00:00,13050.0 -2011-12-10 21:00:00,12945.0 -2011-12-10 22:00:00,12677.0 -2011-12-10 23:00:00,12363.0 -2011-12-11 00:00:00,11705.0 -2011-12-09 01:00:00,10825.0 -2011-12-09 02:00:00,10237.0 -2011-12-09 03:00:00,9938.0 -2011-12-09 04:00:00,9762.0 -2011-12-09 05:00:00,9802.0 -2011-12-09 06:00:00,10064.0 -2011-12-09 07:00:00,10730.0 -2011-12-09 08:00:00,11993.0 -2011-12-09 09:00:00,12563.0 -2011-12-09 10:00:00,12736.0 -2011-12-09 11:00:00,12689.0 -2011-12-09 12:00:00,12695.0 -2011-12-09 13:00:00,12610.0 -2011-12-09 14:00:00,12539.0 -2011-12-09 15:00:00,12446.0 -2011-12-09 16:00:00,12375.0 -2011-12-09 17:00:00,12405.0 -2011-12-09 18:00:00,13367.0 -2011-12-09 19:00:00,14066.0 -2011-12-09 20:00:00,13947.0 -2011-12-09 21:00:00,13742.0 -2011-12-09 22:00:00,13478.0 -2011-12-09 23:00:00,13098.0 -2011-12-10 00:00:00,12315.0 -2011-12-08 01:00:00,10947.0 -2011-12-08 02:00:00,10329.0 -2011-12-08 03:00:00,10000.0 -2011-12-08 04:00:00,9807.0 -2011-12-08 05:00:00,9784.0 -2011-12-08 06:00:00,10039.0 -2011-12-08 07:00:00,10798.0 -2011-12-08 08:00:00,12064.0 -2011-12-08 09:00:00,12629.0 -2011-12-08 10:00:00,12718.0 -2011-12-08 11:00:00,12691.0 -2011-12-08 12:00:00,12688.0 -2011-12-08 13:00:00,12585.0 -2011-12-08 14:00:00,12481.0 -2011-12-08 15:00:00,12390.0 -2011-12-08 16:00:00,12307.0 -2011-12-08 17:00:00,12493.0 -2011-12-08 18:00:00,13399.0 -2011-12-08 19:00:00,13910.0 -2011-12-08 20:00:00,13781.0 -2011-12-08 21:00:00,13567.0 -2011-12-08 22:00:00,13230.0 -2011-12-08 23:00:00,12705.0 -2011-12-09 00:00:00,11738.0 -2011-12-07 01:00:00,10740.0 -2011-12-07 02:00:00,10122.0 -2011-12-07 03:00:00,9758.0 -2011-12-07 04:00:00,9520.0 -2011-12-07 05:00:00,9530.0 -2011-12-07 06:00:00,9791.0 -2011-12-07 07:00:00,10570.0 -2011-12-07 08:00:00,11858.0 -2011-12-07 09:00:00,12370.0 -2011-12-07 10:00:00,12497.0 -2011-12-07 11:00:00,12468.0 -2011-12-07 12:00:00,12522.0 -2011-12-07 13:00:00,12519.0 -2011-12-07 14:00:00,12350.0 -2011-12-07 15:00:00,12334.0 -2011-12-07 16:00:00,12249.0 -2011-12-07 17:00:00,12398.0 -2011-12-07 18:00:00,13331.0 -2011-12-07 19:00:00,14021.0 -2011-12-07 20:00:00,13890.0 -2011-12-07 21:00:00,13700.0 -2011-12-07 22:00:00,13392.0 -2011-12-07 23:00:00,12818.0 -2011-12-08 00:00:00,11873.0 -2011-12-06 01:00:00,10443.0 -2011-12-06 02:00:00,9834.0 -2011-12-06 03:00:00,9495.0 -2011-12-06 04:00:00,9320.0 -2011-12-06 05:00:00,9296.0 -2011-12-06 06:00:00,9590.0 -2011-12-06 07:00:00,10343.0 -2011-12-06 08:00:00,11633.0 -2011-12-06 09:00:00,12231.0 -2011-12-06 10:00:00,12414.0 -2011-12-06 11:00:00,12490.0 -2011-12-06 12:00:00,12560.0 -2011-12-06 13:00:00,12571.0 -2011-12-06 14:00:00,12495.0 -2011-12-06 15:00:00,12450.0 -2011-12-06 16:00:00,12339.0 -2011-12-06 17:00:00,12356.0 -2011-12-06 18:00:00,13219.0 -2011-12-06 19:00:00,13910.0 -2011-12-06 20:00:00,13774.0 -2011-12-06 21:00:00,13584.0 -2011-12-06 22:00:00,13301.0 -2011-12-06 23:00:00,12709.0 -2011-12-07 00:00:00,11701.0 -2011-12-05 01:00:00,9716.0 -2011-12-05 02:00:00,9266.0 -2011-12-05 03:00:00,9014.0 -2011-12-05 04:00:00,8926.0 -2011-12-05 05:00:00,8947.0 -2011-12-05 06:00:00,9268.0 -2011-12-05 07:00:00,10057.0 -2011-12-05 08:00:00,11419.0 -2011-12-05 09:00:00,11991.0 -2011-12-05 10:00:00,12165.0 -2011-12-05 11:00:00,12268.0 -2011-12-05 12:00:00,12356.0 -2011-12-05 13:00:00,12357.0 -2011-12-05 14:00:00,12352.0 -2011-12-05 15:00:00,12409.0 -2011-12-05 16:00:00,12411.0 -2011-12-05 17:00:00,12547.0 -2011-12-05 18:00:00,13304.0 -2011-12-05 19:00:00,13694.0 -2011-12-05 20:00:00,13485.0 -2011-12-05 21:00:00,13253.0 -2011-12-05 22:00:00,12955.0 -2011-12-05 23:00:00,12382.0 -2011-12-06 00:00:00,11383.0 -2011-12-04 01:00:00,9800.0 -2011-12-04 02:00:00,9175.0 -2011-12-04 03:00:00,8715.0 -2011-12-04 04:00:00,8542.0 -2011-12-04 05:00:00,8409.0 -2011-12-04 06:00:00,8375.0 -2011-12-04 07:00:00,8449.0 -2011-12-04 08:00:00,8703.0 -2011-12-04 09:00:00,8892.0 -2011-12-04 10:00:00,9218.0 -2011-12-04 11:00:00,9583.0 -2011-12-04 12:00:00,9863.0 -2011-12-04 13:00:00,10022.0 -2011-12-04 14:00:00,10165.0 -2011-12-04 15:00:00,10237.0 -2011-12-04 16:00:00,10322.0 -2011-12-04 17:00:00,10588.0 -2011-12-04 18:00:00,11359.0 -2011-12-04 19:00:00,11862.0 -2011-12-04 20:00:00,11844.0 -2011-12-04 21:00:00,11765.0 -2011-12-04 22:00:00,11516.0 -2011-12-04 23:00:00,11114.0 -2011-12-05 00:00:00,10400.0 -2011-12-03 01:00:00,10615.0 -2011-12-03 02:00:00,9917.0 -2011-12-03 03:00:00,9494.0 -2011-12-03 04:00:00,9195.0 -2011-12-03 05:00:00,9041.0 -2011-12-03 06:00:00,9014.0 -2011-12-03 07:00:00,9327.0 -2011-12-03 08:00:00,9808.0 -2011-12-03 09:00:00,10255.0 -2011-12-03 10:00:00,10549.0 -2011-12-03 11:00:00,10781.0 -2011-12-03 12:00:00,10884.0 -2011-12-03 13:00:00,10882.0 -2011-12-03 14:00:00,10786.0 -2011-12-03 15:00:00,10483.0 -2011-12-03 16:00:00,10304.0 -2011-12-03 17:00:00,10241.0 -2011-12-03 18:00:00,11142.0 -2011-12-03 19:00:00,11710.0 -2011-12-03 20:00:00,11689.0 -2011-12-03 21:00:00,11542.0 -2011-12-03 22:00:00,11358.0 -2011-12-03 23:00:00,11018.0 -2011-12-04 00:00:00,10488.0 -2011-12-02 01:00:00,10471.0 -2011-12-02 02:00:00,9923.0 -2011-12-02 03:00:00,9631.0 -2011-12-02 04:00:00,9434.0 -2011-12-02 05:00:00,9421.0 -2011-12-02 06:00:00,9650.0 -2011-12-02 07:00:00,10371.0 -2011-12-02 08:00:00,11618.0 -2011-12-02 09:00:00,12095.0 -2011-12-02 10:00:00,12174.0 -2011-12-02 11:00:00,12216.0 -2011-12-02 12:00:00,12172.0 -2011-12-02 13:00:00,12100.0 -2011-12-02 14:00:00,11925.0 -2011-12-02 15:00:00,11909.0 -2011-12-02 16:00:00,11811.0 -2011-12-02 17:00:00,11830.0 -2011-12-02 18:00:00,12647.0 -2011-12-02 19:00:00,13239.0 -2011-12-02 20:00:00,13121.0 -2011-12-02 21:00:00,12910.0 -2011-12-02 22:00:00,12627.0 -2011-12-02 23:00:00,12242.0 -2011-12-03 00:00:00,11475.0 -2011-12-01 01:00:00,10543.0 -2011-12-01 02:00:00,9986.0 -2011-12-01 03:00:00,9688.0 -2011-12-01 04:00:00,9476.0 -2011-12-01 05:00:00,9466.0 -2011-12-01 06:00:00,9742.0 -2011-12-01 07:00:00,10479.0 -2011-12-01 08:00:00,11728.0 -2011-12-01 09:00:00,12176.0 -2011-12-01 10:00:00,12288.0 -2011-12-01 11:00:00,12291.0 -2011-12-01 12:00:00,12286.0 -2011-12-01 13:00:00,12130.0 -2011-12-01 14:00:00,12038.0 -2011-12-01 15:00:00,11984.0 -2011-12-01 16:00:00,11982.0 -2011-12-01 17:00:00,12204.0 -2011-12-01 18:00:00,12906.0 -2011-12-01 19:00:00,13361.0 -2011-12-01 20:00:00,13269.0 -2011-12-01 21:00:00,13053.0 -2011-12-01 22:00:00,12740.0 -2011-12-01 23:00:00,12219.0 -2011-12-02 00:00:00,11298.0 -2011-11-30 01:00:00,10646.0 -2011-11-30 02:00:00,10080.0 -2011-11-30 03:00:00,9803.0 -2011-11-30 04:00:00,9658.0 -2011-11-30 05:00:00,9636.0 -2011-11-30 06:00:00,9908.0 -2011-11-30 07:00:00,10679.0 -2011-11-30 08:00:00,11919.0 -2011-11-30 09:00:00,12374.0 -2011-11-30 10:00:00,12448.0 -2011-11-30 11:00:00,12412.0 -2011-11-30 12:00:00,12356.0 -2011-11-30 13:00:00,12286.0 -2011-11-30 14:00:00,12118.0 -2011-11-30 15:00:00,12028.0 -2011-11-30 16:00:00,11901.0 -2011-11-30 17:00:00,11987.0 -2011-11-30 18:00:00,12831.0 -2011-11-30 19:00:00,13499.0 -2011-11-30 20:00:00,13379.0 -2011-11-30 21:00:00,13200.0 -2011-11-30 22:00:00,12912.0 -2011-11-30 23:00:00,12346.0 -2011-12-01 00:00:00,11408.0 -2011-11-29 01:00:00,10470.0 -2011-11-29 02:00:00,9879.0 -2011-11-29 03:00:00,9541.0 -2011-11-29 04:00:00,9353.0 -2011-11-29 05:00:00,9347.0 -2011-11-29 06:00:00,9572.0 -2011-11-29 07:00:00,10335.0 -2011-11-29 08:00:00,11618.0 -2011-11-29 09:00:00,12253.0 -2011-11-29 10:00:00,12387.0 -2011-11-29 11:00:00,12505.0 -2011-11-29 12:00:00,12568.0 -2011-11-29 13:00:00,12555.0 -2011-11-29 14:00:00,12458.0 -2011-11-29 15:00:00,12477.0 -2011-11-29 16:00:00,12430.0 -2011-11-29 17:00:00,12454.0 -2011-11-29 18:00:00,13153.0 -2011-11-29 19:00:00,13678.0 -2011-11-29 20:00:00,13531.0 -2011-11-29 21:00:00,13327.0 -2011-11-29 22:00:00,12984.0 -2011-11-29 23:00:00,12419.0 -2011-11-30 00:00:00,11519.0 -2011-11-28 01:00:00,9555.0 -2011-11-28 02:00:00,9090.0 -2011-11-28 03:00:00,8902.0 -2011-11-28 04:00:00,8741.0 -2011-11-28 05:00:00,8812.0 -2011-11-28 06:00:00,9075.0 -2011-11-28 07:00:00,9963.0 -2011-11-28 08:00:00,11258.0 -2011-11-28 09:00:00,11894.0 -2011-11-28 10:00:00,12029.0 -2011-11-28 11:00:00,12145.0 -2011-11-28 12:00:00,12166.0 -2011-11-28 13:00:00,12232.0 -2011-11-28 14:00:00,12206.0 -2011-11-28 15:00:00,12284.0 -2011-11-28 16:00:00,12265.0 -2011-11-28 17:00:00,12329.0 -2011-11-28 18:00:00,13027.0 -2011-11-28 19:00:00,13486.0 -2011-11-28 20:00:00,13326.0 -2011-11-28 21:00:00,13117.0 -2011-11-28 22:00:00,12824.0 -2011-11-28 23:00:00,12248.0 -2011-11-29 00:00:00,11322.0 -2011-11-27 01:00:00,9110.0 -2011-11-27 02:00:00,8628.0 -2011-11-27 03:00:00,8262.0 -2011-11-27 04:00:00,8068.0 -2011-11-27 05:00:00,7987.0 -2011-11-27 06:00:00,8064.0 -2011-11-27 07:00:00,8182.0 -2011-11-27 08:00:00,8525.0 -2011-11-27 09:00:00,8720.0 -2011-11-27 10:00:00,9128.0 -2011-11-27 11:00:00,9530.0 -2011-11-27 12:00:00,9822.0 -2011-11-27 13:00:00,9991.0 -2011-11-27 14:00:00,10132.0 -2011-11-27 15:00:00,10172.0 -2011-11-27 16:00:00,10262.0 -2011-11-27 17:00:00,10454.0 -2011-11-27 18:00:00,11161.0 -2011-11-27 19:00:00,11606.0 -2011-11-27 20:00:00,11574.0 -2011-11-27 21:00:00,11477.0 -2011-11-27 22:00:00,11219.0 -2011-11-27 23:00:00,10789.0 -2011-11-28 00:00:00,10146.0 -2011-11-26 01:00:00,8970.0 -2011-11-26 02:00:00,8441.0 -2011-11-26 03:00:00,8162.0 -2011-11-26 04:00:00,7919.0 -2011-11-26 05:00:00,7873.0 -2011-11-26 06:00:00,7873.0 -2011-11-26 07:00:00,8106.0 -2011-11-26 08:00:00,8501.0 -2011-11-26 09:00:00,8792.0 -2011-11-26 10:00:00,9253.0 -2011-11-26 11:00:00,9601.0 -2011-11-26 12:00:00,9920.0 -2011-11-26 13:00:00,10030.0 -2011-11-26 14:00:00,10085.0 -2011-11-26 15:00:00,10006.0 -2011-11-26 16:00:00,9962.0 -2011-11-26 17:00:00,10058.0 -2011-11-26 18:00:00,10590.0 -2011-11-26 19:00:00,10903.0 -2011-11-26 20:00:00,10838.0 -2011-11-26 21:00:00,10764.0 -2011-11-26 22:00:00,10505.0 -2011-11-26 23:00:00,10258.0 -2011-11-27 00:00:00,9763.0 -2011-11-25 01:00:00,8804.0 -2011-11-25 02:00:00,8450.0 -2011-11-25 03:00:00,8242.0 -2011-11-25 04:00:00,8110.0 -2011-11-25 05:00:00,8108.0 -2011-11-25 06:00:00,8284.0 -2011-11-25 07:00:00,8704.0 -2011-11-25 08:00:00,9181.0 -2011-11-25 09:00:00,9246.0 -2011-11-25 10:00:00,9427.0 -2011-11-25 11:00:00,9587.0 -2011-11-25 12:00:00,9636.0 -2011-11-25 13:00:00,9650.0 -2011-11-25 14:00:00,9568.0 -2011-11-25 15:00:00,9486.0 -2011-11-25 16:00:00,9548.0 -2011-11-25 17:00:00,9658.0 -2011-11-25 18:00:00,10523.0 -2011-11-25 19:00:00,10991.0 -2011-11-25 20:00:00,10843.0 -2011-11-25 21:00:00,10673.0 -2011-11-25 22:00:00,10409.0 -2011-11-25 23:00:00,10120.0 -2011-11-26 00:00:00,9556.0 -2011-11-24 01:00:00,9766.0 -2011-11-24 02:00:00,9161.0 -2011-11-24 03:00:00,8802.0 -2011-11-24 04:00:00,8542.0 -2011-11-24 05:00:00,8382.0 -2011-11-24 06:00:00,8395.0 -2011-11-24 07:00:00,8547.0 -2011-11-24 08:00:00,8774.0 -2011-11-24 09:00:00,8827.0 -2011-11-24 10:00:00,9096.0 -2011-11-24 11:00:00,9436.0 -2011-11-24 12:00:00,9659.0 -2011-11-24 13:00:00,9752.0 -2011-11-24 14:00:00,9673.0 -2011-11-24 15:00:00,9513.0 -2011-11-24 16:00:00,9299.0 -2011-11-24 17:00:00,9195.0 -2011-11-24 18:00:00,9571.0 -2011-11-24 19:00:00,9883.0 -2011-11-24 20:00:00,9777.0 -2011-11-24 21:00:00,9716.0 -2011-11-24 22:00:00,9640.0 -2011-11-24 23:00:00,9520.0 -2011-11-25 00:00:00,9223.0 -2011-11-23 01:00:00,10065.0 -2011-11-23 02:00:00,9524.0 -2011-11-23 03:00:00,9181.0 -2011-11-23 04:00:00,8995.0 -2011-11-23 05:00:00,8969.0 -2011-11-23 06:00:00,9226.0 -2011-11-23 07:00:00,9936.0 -2011-11-23 08:00:00,10926.0 -2011-11-23 09:00:00,11383.0 -2011-11-23 10:00:00,11672.0 -2011-11-23 11:00:00,11819.0 -2011-11-23 12:00:00,11847.0 -2011-11-23 13:00:00,11754.0 -2011-11-23 14:00:00,11551.0 -2011-11-23 15:00:00,11451.0 -2011-11-23 16:00:00,11291.0 -2011-11-23 17:00:00,11176.0 -2011-11-23 18:00:00,11737.0 -2011-11-23 19:00:00,12366.0 -2011-11-23 20:00:00,12178.0 -2011-11-23 21:00:00,11925.0 -2011-11-23 22:00:00,11677.0 -2011-11-23 23:00:00,11211.0 -2011-11-24 00:00:00,10557.0 -2011-11-22 01:00:00,9879.0 -2011-11-22 02:00:00,9363.0 -2011-11-22 03:00:00,9033.0 -2011-11-22 04:00:00,8832.0 -2011-11-22 05:00:00,8793.0 -2011-11-22 06:00:00,9032.0 -2011-11-22 07:00:00,9747.0 -2011-11-22 08:00:00,10938.0 -2011-11-22 09:00:00,11712.0 -2011-11-22 10:00:00,12020.0 -2011-11-22 11:00:00,12179.0 -2011-11-22 12:00:00,12323.0 -2011-11-22 13:00:00,12268.0 -2011-11-22 14:00:00,12290.0 -2011-11-22 15:00:00,12292.0 -2011-11-22 16:00:00,12252.0 -2011-11-22 17:00:00,12294.0 -2011-11-22 18:00:00,12773.0 -2011-11-22 19:00:00,13016.0 -2011-11-22 20:00:00,12774.0 -2011-11-22 21:00:00,12494.0 -2011-11-22 22:00:00,12144.0 -2011-11-22 23:00:00,11641.0 -2011-11-23 00:00:00,10862.0 -2011-11-21 01:00:00,9393.0 -2011-11-21 02:00:00,8975.0 -2011-11-21 03:00:00,8760.0 -2011-11-21 04:00:00,8660.0 -2011-11-21 05:00:00,8691.0 -2011-11-21 06:00:00,8971.0 -2011-11-21 07:00:00,9775.0 -2011-11-21 08:00:00,10939.0 -2011-11-21 09:00:00,11529.0 -2011-11-21 10:00:00,11796.0 -2011-11-21 11:00:00,11882.0 -2011-11-21 12:00:00,11938.0 -2011-11-21 13:00:00,11850.0 -2011-11-21 14:00:00,11709.0 -2011-11-21 15:00:00,11702.0 -2011-11-21 16:00:00,11618.0 -2011-11-21 17:00:00,11710.0 -2011-11-21 18:00:00,12370.0 -2011-11-21 19:00:00,12812.0 -2011-11-21 20:00:00,12604.0 -2011-11-21 21:00:00,12358.0 -2011-11-21 22:00:00,12054.0 -2011-11-21 23:00:00,11503.0 -2011-11-22 00:00:00,10693.0 -2011-11-20 01:00:00,9203.0 -2011-11-20 02:00:00,8709.0 -2011-11-20 03:00:00,8418.0 -2011-11-20 04:00:00,8272.0 -2011-11-20 05:00:00,8139.0 -2011-11-20 06:00:00,8160.0 -2011-11-20 07:00:00,8347.0 -2011-11-20 08:00:00,8614.0 -2011-11-20 09:00:00,8630.0 -2011-11-20 10:00:00,8980.0 -2011-11-20 11:00:00,9330.0 -2011-11-20 12:00:00,9599.0 -2011-11-20 13:00:00,9643.0 -2011-11-20 14:00:00,9789.0 -2011-11-20 15:00:00,9855.0 -2011-11-20 16:00:00,9919.0 -2011-11-20 17:00:00,10051.0 -2011-11-20 18:00:00,10739.0 -2011-11-20 19:00:00,11243.0 -2011-11-20 20:00:00,11244.0 -2011-11-20 21:00:00,11107.0 -2011-11-20 22:00:00,10891.0 -2011-11-20 23:00:00,10508.0 -2011-11-21 00:00:00,9950.0 -2011-11-19 01:00:00,10117.0 -2011-11-19 02:00:00,9514.0 -2011-11-19 03:00:00,9149.0 -2011-11-19 04:00:00,8903.0 -2011-11-19 05:00:00,8788.0 -2011-11-19 06:00:00,8873.0 -2011-11-19 07:00:00,9133.0 -2011-11-19 08:00:00,9646.0 -2011-11-19 09:00:00,9946.0 -2011-11-19 10:00:00,10383.0 -2011-11-19 11:00:00,10691.0 -2011-11-19 12:00:00,10784.0 -2011-11-19 13:00:00,10571.0 -2011-11-19 14:00:00,10388.0 -2011-11-19 15:00:00,10376.0 -2011-11-19 16:00:00,10290.0 -2011-11-19 17:00:00,10287.0 -2011-11-19 18:00:00,10794.0 -2011-11-19 19:00:00,11209.0 -2011-11-19 20:00:00,11118.0 -2011-11-19 21:00:00,10886.0 -2011-11-19 22:00:00,10693.0 -2011-11-19 23:00:00,10330.0 -2011-11-20 00:00:00,9766.0 -2011-11-18 01:00:00,10613.0 -2011-11-18 02:00:00,10120.0 -2011-11-18 03:00:00,9835.0 -2011-11-18 04:00:00,9655.0 -2011-11-18 05:00:00,9656.0 -2011-11-18 06:00:00,9886.0 -2011-11-18 07:00:00,10629.0 -2011-11-18 08:00:00,11720.0 -2011-11-18 09:00:00,12129.0 -2011-11-18 10:00:00,12220.0 -2011-11-18 11:00:00,12206.0 -2011-11-18 12:00:00,12231.0 -2011-11-18 13:00:00,12130.0 -2011-11-18 14:00:00,11998.0 -2011-11-18 15:00:00,11921.0 -2011-11-18 16:00:00,11706.0 -2011-11-18 17:00:00,11648.0 -2011-11-18 18:00:00,12170.0 -2011-11-18 19:00:00,12700.0 -2011-11-18 20:00:00,12528.0 -2011-11-18 21:00:00,12237.0 -2011-11-18 22:00:00,11929.0 -2011-11-18 23:00:00,11497.0 -2011-11-19 00:00:00,10812.0 -2011-11-17 01:00:00,10279.0 -2011-11-17 02:00:00,9795.0 -2011-11-17 03:00:00,9545.0 -2011-11-17 04:00:00,9367.0 -2011-11-17 05:00:00,9379.0 -2011-11-17 06:00:00,9658.0 -2011-11-17 07:00:00,10459.0 -2011-11-17 08:00:00,11604.0 -2011-11-17 09:00:00,12056.0 -2011-11-17 10:00:00,12195.0 -2011-11-17 11:00:00,12285.0 -2011-11-17 12:00:00,12331.0 -2011-11-17 13:00:00,12280.0 -2011-11-17 14:00:00,12196.0 -2011-11-17 15:00:00,12191.0 -2011-11-17 16:00:00,12073.0 -2011-11-17 17:00:00,12060.0 -2011-11-17 18:00:00,12638.0 -2011-11-17 19:00:00,13235.0 -2011-11-17 20:00:00,13171.0 -2011-11-17 21:00:00,12982.0 -2011-11-17 22:00:00,12704.0 -2011-11-17 23:00:00,12186.0 -2011-11-18 00:00:00,11400.0 -2011-11-16 01:00:00,9626.0 -2011-11-16 02:00:00,9160.0 -2011-11-16 03:00:00,8902.0 -2011-11-16 04:00:00,8745.0 -2011-11-16 05:00:00,8749.0 -2011-11-16 06:00:00,9050.0 -2011-11-16 07:00:00,9850.0 -2011-11-16 08:00:00,11063.0 -2011-11-16 09:00:00,11563.0 -2011-11-16 10:00:00,11783.0 -2011-11-16 11:00:00,11937.0 -2011-11-16 12:00:00,11990.0 -2011-11-16 13:00:00,11966.0 -2011-11-16 14:00:00,11925.0 -2011-11-16 15:00:00,11892.0 -2011-11-16 16:00:00,11790.0 -2011-11-16 17:00:00,11786.0 -2011-11-16 18:00:00,12336.0 -2011-11-16 19:00:00,12978.0 -2011-11-16 20:00:00,12810.0 -2011-11-16 21:00:00,12622.0 -2011-11-16 22:00:00,12329.0 -2011-11-16 23:00:00,11825.0 -2011-11-17 00:00:00,11040.0 -2011-11-15 01:00:00,9605.0 -2011-11-15 02:00:00,9095.0 -2011-11-15 03:00:00,8841.0 -2011-11-15 04:00:00,8689.0 -2011-11-15 05:00:00,8691.0 -2011-11-15 06:00:00,8978.0 -2011-11-15 07:00:00,9740.0 -2011-11-15 08:00:00,10898.0 -2011-11-15 09:00:00,11339.0 -2011-11-15 10:00:00,11455.0 -2011-11-15 11:00:00,11533.0 -2011-11-15 12:00:00,11588.0 -2011-11-15 13:00:00,11598.0 -2011-11-15 14:00:00,11536.0 -2011-11-15 15:00:00,11513.0 -2011-11-15 16:00:00,11427.0 -2011-11-15 17:00:00,11351.0 -2011-11-15 18:00:00,11733.0 -2011-11-15 19:00:00,12318.0 -2011-11-15 20:00:00,12200.0 -2011-11-15 21:00:00,11986.0 -2011-11-15 22:00:00,11677.0 -2011-11-15 23:00:00,11141.0 -2011-11-16 00:00:00,10349.0 -2011-11-14 01:00:00,8792.0 -2011-11-14 02:00:00,8423.0 -2011-11-14 03:00:00,8243.0 -2011-11-14 04:00:00,8129.0 -2011-11-14 05:00:00,8215.0 -2011-11-14 06:00:00,8498.0 -2011-11-14 07:00:00,9383.0 -2011-11-14 08:00:00,10601.0 -2011-11-14 09:00:00,11202.0 -2011-11-14 10:00:00,11485.0 -2011-11-14 11:00:00,11673.0 -2011-11-14 12:00:00,11841.0 -2011-11-14 13:00:00,11829.0 -2011-11-14 14:00:00,11791.0 -2011-11-14 15:00:00,11718.0 -2011-11-14 16:00:00,11528.0 -2011-11-14 17:00:00,11433.0 -2011-11-14 18:00:00,11954.0 -2011-11-14 19:00:00,12513.0 -2011-11-14 20:00:00,12333.0 -2011-11-14 21:00:00,12100.0 -2011-11-14 22:00:00,11754.0 -2011-11-14 23:00:00,11235.0 -2011-11-15 00:00:00,10404.0 -2011-11-13 01:00:00,9151.0 -2011-11-13 02:00:00,8673.0 -2011-11-13 03:00:00,8419.0 -2011-11-13 04:00:00,8140.0 -2011-11-13 05:00:00,8067.0 -2011-11-13 06:00:00,8054.0 -2011-11-13 07:00:00,8162.0 -2011-11-13 08:00:00,8341.0 -2011-11-13 09:00:00,8402.0 -2011-11-13 10:00:00,8767.0 -2011-11-13 11:00:00,9058.0 -2011-11-13 12:00:00,9306.0 -2011-11-13 13:00:00,9455.0 -2011-11-13 14:00:00,9415.0 -2011-11-13 15:00:00,9399.0 -2011-11-13 16:00:00,9329.0 -2011-11-13 17:00:00,9377.0 -2011-11-13 18:00:00,9850.0 -2011-11-13 19:00:00,10672.0 -2011-11-13 20:00:00,10682.0 -2011-11-13 21:00:00,10545.0 -2011-11-13 22:00:00,10337.0 -2011-11-13 23:00:00,9937.0 -2011-11-14 00:00:00,9376.0 -2011-11-12 01:00:00,10047.0 -2011-11-12 02:00:00,9577.0 -2011-11-12 03:00:00,9214.0 -2011-11-12 04:00:00,9021.0 -2011-11-12 05:00:00,8917.0 -2011-11-12 06:00:00,8979.0 -2011-11-12 07:00:00,9287.0 -2011-11-12 08:00:00,9641.0 -2011-11-12 09:00:00,9817.0 -2011-11-12 10:00:00,10042.0 -2011-11-12 11:00:00,10253.0 -2011-11-12 12:00:00,10255.0 -2011-11-12 13:00:00,10202.0 -2011-11-12 14:00:00,10013.0 -2011-11-12 15:00:00,9877.0 -2011-11-12 16:00:00,9732.0 -2011-11-12 17:00:00,9816.0 -2011-11-12 18:00:00,10399.0 -2011-11-12 19:00:00,11033.0 -2011-11-12 20:00:00,10958.0 -2011-11-12 21:00:00,10786.0 -2011-11-12 22:00:00,10523.0 -2011-11-12 23:00:00,10218.0 -2011-11-13 00:00:00,9703.0 -2011-11-11 01:00:00,10312.0 -2011-11-11 02:00:00,9799.0 -2011-11-11 03:00:00,9527.0 -2011-11-11 04:00:00,9325.0 -2011-11-11 05:00:00,9305.0 -2011-11-11 06:00:00,9536.0 -2011-11-11 07:00:00,10192.0 -2011-11-11 08:00:00,11116.0 -2011-11-11 09:00:00,11555.0 -2011-11-11 10:00:00,11908.0 -2011-11-11 11:00:00,12108.0 -2011-11-11 12:00:00,12178.0 -2011-11-11 13:00:00,12109.0 -2011-11-11 14:00:00,11929.0 -2011-11-11 15:00:00,11839.0 -2011-11-11 16:00:00,11623.0 -2011-11-11 17:00:00,11480.0 -2011-11-11 18:00:00,11896.0 -2011-11-11 19:00:00,12590.0 -2011-11-11 20:00:00,12400.0 -2011-11-11 21:00:00,12176.0 -2011-11-11 22:00:00,11854.0 -2011-11-11 23:00:00,11388.0 -2011-11-12 00:00:00,10785.0 -2011-11-10 01:00:00,10075.0 -2011-11-10 02:00:00,9549.0 -2011-11-10 03:00:00,9277.0 -2011-11-10 04:00:00,9117.0 -2011-11-10 05:00:00,9126.0 -2011-11-10 06:00:00,9404.0 -2011-11-10 07:00:00,10155.0 -2011-11-10 08:00:00,11312.0 -2011-11-10 09:00:00,11843.0 -2011-11-10 10:00:00,12105.0 -2011-11-10 11:00:00,12185.0 -2011-11-10 12:00:00,12407.0 -2011-11-10 13:00:00,12406.0 -2011-11-10 14:00:00,12346.0 -2011-11-10 15:00:00,12326.0 -2011-11-10 16:00:00,12215.0 -2011-11-10 17:00:00,12192.0 -2011-11-10 18:00:00,12588.0 -2011-11-10 19:00:00,13088.0 -2011-11-10 20:00:00,12886.0 -2011-11-10 21:00:00,12650.0 -2011-11-10 22:00:00,12298.0 -2011-11-10 23:00:00,11801.0 -2011-11-11 00:00:00,11024.0 -2011-11-09 01:00:00,9667.0 -2011-11-09 02:00:00,9169.0 -2011-11-09 03:00:00,8853.0 -2011-11-09 04:00:00,8678.0 -2011-11-09 05:00:00,8588.0 -2011-11-09 06:00:00,8834.0 -2011-11-09 07:00:00,9578.0 -2011-11-09 08:00:00,10867.0 -2011-11-09 09:00:00,11617.0 -2011-11-09 10:00:00,11770.0 -2011-11-09 11:00:00,11749.0 -2011-11-09 12:00:00,11831.0 -2011-11-09 13:00:00,11916.0 -2011-11-09 14:00:00,11905.0 -2011-11-09 15:00:00,11994.0 -2011-11-09 16:00:00,11969.0 -2011-11-09 17:00:00,12062.0 -2011-11-09 18:00:00,12579.0 -2011-11-09 19:00:00,13026.0 -2011-11-09 20:00:00,12809.0 -2011-11-09 21:00:00,12537.0 -2011-11-09 22:00:00,12177.0 -2011-11-09 23:00:00,11641.0 -2011-11-10 00:00:00,10838.0 -2011-11-08 01:00:00,9508.0 -2011-11-08 02:00:00,8967.0 -2011-11-08 03:00:00,8724.0 -2011-11-08 04:00:00,8544.0 -2011-11-08 05:00:00,8498.0 -2011-11-08 06:00:00,8724.0 -2011-11-08 07:00:00,9462.0 -2011-11-08 08:00:00,10670.0 -2011-11-08 09:00:00,11440.0 -2011-11-08 10:00:00,11905.0 -2011-11-08 11:00:00,12043.0 -2011-11-08 12:00:00,12146.0 -2011-11-08 13:00:00,12078.0 -2011-11-08 14:00:00,12041.0 -2011-11-08 15:00:00,12093.0 -2011-11-08 16:00:00,11967.0 -2011-11-08 17:00:00,12031.0 -2011-11-08 18:00:00,12477.0 -2011-11-08 19:00:00,12731.0 -2011-11-08 20:00:00,12483.0 -2011-11-08 21:00:00,12205.0 -2011-11-08 22:00:00,11848.0 -2011-11-08 23:00:00,11272.0 -2011-11-09 00:00:00,10424.0 -2011-11-07 01:00:00,8737.0 -2011-11-07 02:00:00,8366.0 -2011-11-07 03:00:00,8186.0 -2011-11-07 04:00:00,8058.0 -2011-11-07 05:00:00,8101.0 -2011-11-07 06:00:00,8358.0 -2011-11-07 07:00:00,9158.0 -2011-11-07 08:00:00,10396.0 -2011-11-07 09:00:00,11054.0 -2011-11-07 10:00:00,11349.0 -2011-11-07 11:00:00,11485.0 -2011-11-07 12:00:00,11663.0 -2011-11-07 13:00:00,11633.0 -2011-11-07 14:00:00,11596.0 -2011-11-07 15:00:00,11672.0 -2011-11-07 16:00:00,11550.0 -2011-11-07 17:00:00,11579.0 -2011-11-07 18:00:00,11985.0 -2011-11-07 19:00:00,12391.0 -2011-11-07 20:00:00,12198.0 -2011-11-07 21:00:00,11971.0 -2011-11-07 22:00:00,11591.0 -2011-11-07 23:00:00,11067.0 -2011-11-08 00:00:00,10276.0 -2011-11-06 01:00:00,9164.0 -2011-11-06 03:00:00,8298.0 -2011-11-06 04:00:00,8061.0 -2011-11-06 05:00:00,8045.0 -2011-11-06 06:00:00,8041.0 -2011-11-06 07:00:00,8266.0 -2011-11-06 08:00:00,8366.0 -2011-11-06 09:00:00,8574.0 -2011-11-06 10:00:00,8888.0 -2011-11-06 11:00:00,9126.0 -2011-11-06 12:00:00,9236.0 -2011-11-06 13:00:00,9403.0 -2011-11-06 14:00:00,9453.0 -2011-11-06 15:00:00,9534.0 -2011-11-06 16:00:00,9484.0 -2011-11-06 17:00:00,9588.0 -2011-11-06 18:00:00,10071.0 -2011-11-06 19:00:00,10750.0 -2011-11-06 20:00:00,10707.0 -2011-11-06 21:00:00,10597.0 -2011-11-06 22:00:00,10296.0 -2011-11-06 23:00:00,9861.0 -2011-11-07 00:00:00,9262.0 -2011-11-05 01:00:00,9781.0 -2011-11-05 02:00:00,9206.0 -2011-11-05 03:00:00,8922.0 -2011-11-05 04:00:00,8672.0 -2011-11-05 05:00:00,8600.0 -2011-11-05 06:00:00,8602.0 -2011-11-05 07:00:00,8945.0 -2011-11-05 08:00:00,9444.0 -2011-11-05 09:00:00,9879.0 -2011-11-05 10:00:00,10040.0 -2011-11-05 11:00:00,10199.0 -2011-11-05 12:00:00,10206.0 -2011-11-05 13:00:00,10171.0 -2011-11-05 14:00:00,9988.0 -2011-11-05 15:00:00,9792.0 -2011-11-05 16:00:00,9616.0 -2011-11-05 17:00:00,9522.0 -2011-11-05 18:00:00,9528.0 -2011-11-05 19:00:00,9857.0 -2011-11-05 20:00:00,10621.0 -2011-11-05 21:00:00,10539.0 -2011-11-05 22:00:00,10413.0 -2011-11-05 23:00:00,10124.0 -2011-11-06 00:00:00,9685.0 -2011-11-04 01:00:00,9861.0 -2011-11-04 02:00:00,9316.0 -2011-11-04 03:00:00,9008.0 -2011-11-04 04:00:00,8778.0 -2011-11-04 05:00:00,8752.0 -2011-11-04 06:00:00,8960.0 -2011-11-04 07:00:00,9648.0 -2011-11-04 08:00:00,10917.0 -2011-11-04 09:00:00,11612.0 -2011-11-04 10:00:00,11653.0 -2011-11-04 11:00:00,11658.0 -2011-11-04 12:00:00,11691.0 -2011-11-04 13:00:00,11626.0 -2011-11-04 14:00:00,11491.0 -2011-11-04 15:00:00,11468.0 -2011-11-04 16:00:00,11243.0 -2011-11-04 17:00:00,11088.0 -2011-11-04 18:00:00,11002.0 -2011-11-04 19:00:00,11137.0 -2011-11-04 20:00:00,11824.0 -2011-11-04 21:00:00,11727.0 -2011-11-04 22:00:00,11456.0 -2011-11-04 23:00:00,11107.0 -2011-11-05 00:00:00,10461.0 -2011-11-03 01:00:00,9509.0 -2011-11-03 02:00:00,9001.0 -2011-11-03 03:00:00,8690.0 -2011-11-03 04:00:00,8518.0 -2011-11-03 05:00:00,8452.0 -2011-11-03 06:00:00,8693.0 -2011-11-03 07:00:00,9405.0 -2011-11-03 08:00:00,10637.0 -2011-11-03 09:00:00,11661.0 -2011-11-03 10:00:00,11943.0 -2011-11-03 11:00:00,12106.0 -2011-11-03 12:00:00,12189.0 -2011-11-03 13:00:00,12192.0 -2011-11-03 14:00:00,12188.0 -2011-11-03 15:00:00,12192.0 -2011-11-03 16:00:00,12085.0 -2011-11-03 17:00:00,12021.0 -2011-11-03 18:00:00,12006.0 -2011-11-03 19:00:00,12247.0 -2011-11-03 20:00:00,12465.0 -2011-11-03 21:00:00,12283.0 -2011-11-03 22:00:00,11955.0 -2011-11-03 23:00:00,11418.0 -2011-11-04 00:00:00,10612.0 -2011-11-02 01:00:00,9496.0 -2011-11-02 02:00:00,8940.0 -2011-11-02 03:00:00,8623.0 -2011-11-02 04:00:00,8406.0 -2011-11-02 05:00:00,8362.0 -2011-11-02 06:00:00,8522.0 -2011-11-02 07:00:00,9224.0 -2011-11-02 08:00:00,10556.0 -2011-11-02 09:00:00,11270.0 -2011-11-02 10:00:00,11330.0 -2011-11-02 11:00:00,11454.0 -2011-11-02 12:00:00,11593.0 -2011-11-02 13:00:00,11638.0 -2011-11-02 14:00:00,11564.0 -2011-11-02 15:00:00,11596.0 -2011-11-02 16:00:00,11516.0 -2011-11-02 17:00:00,11411.0 -2011-11-02 18:00:00,11366.0 -2011-11-02 19:00:00,11637.0 -2011-11-02 20:00:00,12152.0 -2011-11-02 21:00:00,11981.0 -2011-11-02 22:00:00,11626.0 -2011-11-02 23:00:00,11101.0 -2011-11-03 00:00:00,10258.0 -2011-11-01 01:00:00,9614.0 -2011-11-01 02:00:00,9123.0 -2011-11-01 03:00:00,8842.0 -2011-11-01 04:00:00,8669.0 -2011-11-01 05:00:00,8655.0 -2011-11-01 06:00:00,8887.0 -2011-11-01 07:00:00,9605.0 -2011-11-01 08:00:00,10940.0 -2011-11-01 09:00:00,11670.0 -2011-11-01 10:00:00,11648.0 -2011-11-01 11:00:00,11627.0 -2011-11-01 12:00:00,11660.0 -2011-11-01 13:00:00,11587.0 -2011-11-01 14:00:00,11549.0 -2011-11-01 15:00:00,11549.0 -2011-11-01 16:00:00,11528.0 -2011-11-01 17:00:00,11459.0 -2011-11-01 18:00:00,11381.0 -2011-11-01 19:00:00,11535.0 -2011-11-01 20:00:00,12155.0 -2011-11-01 21:00:00,12044.0 -2011-11-01 22:00:00,11758.0 -2011-11-01 23:00:00,11178.0 -2011-11-02 00:00:00,10325.0 -2011-10-31 01:00:00,9009.0 -2011-10-31 02:00:00,8598.0 -2011-10-31 03:00:00,8367.0 -2011-10-31 04:00:00,8262.0 -2011-10-31 05:00:00,8281.0 -2011-10-31 06:00:00,8586.0 -2011-10-31 07:00:00,9371.0 -2011-10-31 08:00:00,10776.0 -2011-10-31 09:00:00,11477.0 -2011-10-31 10:00:00,11525.0 -2011-10-31 11:00:00,11482.0 -2011-10-31 12:00:00,11516.0 -2011-10-31 13:00:00,11493.0 -2011-10-31 14:00:00,11408.0 -2011-10-31 15:00:00,11465.0 -2011-10-31 16:00:00,11306.0 -2011-10-31 17:00:00,11233.0 -2011-10-31 18:00:00,11162.0 -2011-10-31 19:00:00,11338.0 -2011-10-31 20:00:00,11826.0 -2011-10-31 21:00:00,11782.0 -2011-10-31 22:00:00,11561.0 -2011-10-31 23:00:00,11116.0 -2011-11-01 00:00:00,10369.0 -2011-10-30 01:00:00,9158.0 -2011-10-30 02:00:00,8790.0 -2011-10-30 03:00:00,8515.0 -2011-10-30 04:00:00,8365.0 -2011-10-30 05:00:00,8218.0 -2011-10-30 06:00:00,8252.0 -2011-10-30 07:00:00,8333.0 -2011-10-30 08:00:00,8647.0 -2011-10-30 09:00:00,8841.0 -2011-10-30 10:00:00,9067.0 -2011-10-30 11:00:00,9288.0 -2011-10-30 12:00:00,9384.0 -2011-10-30 13:00:00,9346.0 -2011-10-30 14:00:00,9385.0 -2011-10-30 15:00:00,9391.0 -2011-10-30 16:00:00,9407.0 -2011-10-30 17:00:00,9627.0 -2011-10-30 18:00:00,9945.0 -2011-10-30 19:00:00,10322.0 -2011-10-30 20:00:00,10761.0 -2011-10-30 21:00:00,10718.0 -2011-10-30 22:00:00,10498.0 -2011-10-30 23:00:00,10160.0 -2011-10-31 00:00:00,9595.0 -2011-10-29 01:00:00,9577.0 -2011-10-29 02:00:00,9015.0 -2011-10-29 03:00:00,8694.0 -2011-10-29 04:00:00,8506.0 -2011-10-29 05:00:00,8426.0 -2011-10-29 06:00:00,8475.0 -2011-10-29 07:00:00,8757.0 -2011-10-29 08:00:00,9315.0 -2011-10-29 09:00:00,9636.0 -2011-10-29 10:00:00,9880.0 -2011-10-29 11:00:00,10033.0 -2011-10-29 12:00:00,10099.0 -2011-10-29 13:00:00,10054.0 -2011-10-29 14:00:00,9907.0 -2011-10-29 15:00:00,9742.0 -2011-10-29 16:00:00,9574.0 -2011-10-29 17:00:00,9544.0 -2011-10-29 18:00:00,9511.0 -2011-10-29 19:00:00,9814.0 -2011-10-29 20:00:00,10580.0 -2011-10-29 21:00:00,10629.0 -2011-10-29 22:00:00,10426.0 -2011-10-29 23:00:00,10206.0 -2011-10-30 00:00:00,9697.0 -2011-10-28 01:00:00,9791.0 -2011-10-28 02:00:00,9301.0 -2011-10-28 03:00:00,8972.0 -2011-10-28 04:00:00,8764.0 -2011-10-28 05:00:00,8719.0 -2011-10-28 06:00:00,8935.0 -2011-10-28 07:00:00,9645.0 -2011-10-28 08:00:00,10871.0 -2011-10-28 09:00:00,11574.0 -2011-10-28 10:00:00,11574.0 -2011-10-28 11:00:00,11530.0 -2011-10-28 12:00:00,11562.0 -2011-10-28 13:00:00,11466.0 -2011-10-28 14:00:00,11397.0 -2011-10-28 15:00:00,11323.0 -2011-10-28 16:00:00,11158.0 -2011-10-28 17:00:00,10988.0 -2011-10-28 18:00:00,10911.0 -2011-10-28 19:00:00,11080.0 -2011-10-28 20:00:00,11674.0 -2011-10-28 21:00:00,11576.0 -2011-10-28 22:00:00,11295.0 -2011-10-28 23:00:00,10939.0 -2011-10-29 00:00:00,10290.0 -2011-10-27 01:00:00,9546.0 -2011-10-27 02:00:00,9012.0 -2011-10-27 03:00:00,8747.0 -2011-10-27 04:00:00,8543.0 -2011-10-27 05:00:00,8452.0 -2011-10-27 06:00:00,8658.0 -2011-10-27 07:00:00,9380.0 -2011-10-27 08:00:00,10654.0 -2011-10-27 09:00:00,11467.0 -2011-10-27 10:00:00,11577.0 -2011-10-27 11:00:00,11609.0 -2011-10-27 12:00:00,11651.0 -2011-10-27 13:00:00,11565.0 -2011-10-27 14:00:00,11485.0 -2011-10-27 15:00:00,11497.0 -2011-10-27 16:00:00,11373.0 -2011-10-27 17:00:00,11255.0 -2011-10-27 18:00:00,11203.0 -2011-10-27 19:00:00,11394.0 -2011-10-27 20:00:00,12074.0 -2011-10-27 21:00:00,12080.0 -2011-10-27 22:00:00,11746.0 -2011-10-27 23:00:00,11327.0 -2011-10-28 00:00:00,10479.0 -2011-10-26 01:00:00,9381.0 -2011-10-26 02:00:00,8830.0 -2011-10-26 03:00:00,8512.0 -2011-10-26 04:00:00,8278.0 -2011-10-26 05:00:00,8242.0 -2011-10-26 06:00:00,8427.0 -2011-10-26 07:00:00,9065.0 -2011-10-26 08:00:00,10265.0 -2011-10-26 09:00:00,11182.0 -2011-10-26 10:00:00,11411.0 -2011-10-26 11:00:00,11545.0 -2011-10-26 12:00:00,11726.0 -2011-10-26 13:00:00,11731.0 -2011-10-26 14:00:00,11715.0 -2011-10-26 15:00:00,11776.0 -2011-10-26 16:00:00,11658.0 -2011-10-26 17:00:00,11648.0 -2011-10-26 18:00:00,11691.0 -2011-10-26 19:00:00,11898.0 -2011-10-26 20:00:00,12149.0 -2011-10-26 21:00:00,11978.0 -2011-10-26 22:00:00,11689.0 -2011-10-26 23:00:00,11168.0 -2011-10-27 00:00:00,10330.0 -2011-10-25 01:00:00,9413.0 -2011-10-25 02:00:00,8882.0 -2011-10-25 03:00:00,8580.0 -2011-10-25 04:00:00,8383.0 -2011-10-25 05:00:00,8326.0 -2011-10-25 06:00:00,8521.0 -2011-10-25 07:00:00,9213.0 -2011-10-25 08:00:00,10489.0 -2011-10-25 09:00:00,11217.0 -2011-10-25 10:00:00,11322.0 -2011-10-25 11:00:00,11479.0 -2011-10-25 12:00:00,11691.0 -2011-10-25 13:00:00,11760.0 -2011-10-25 14:00:00,11773.0 -2011-10-25 15:00:00,11797.0 -2011-10-25 16:00:00,11730.0 -2011-10-25 17:00:00,11627.0 -2011-10-25 18:00:00,11568.0 -2011-10-25 19:00:00,11836.0 -2011-10-25 20:00:00,12266.0 -2011-10-25 21:00:00,12140.0 -2011-10-25 22:00:00,11707.0 -2011-10-25 23:00:00,11106.0 -2011-10-26 00:00:00,10206.0 -2011-10-24 01:00:00,8684.0 -2011-10-24 02:00:00,8338.0 -2011-10-24 03:00:00,8081.0 -2011-10-24 04:00:00,7956.0 -2011-10-24 05:00:00,7922.0 -2011-10-24 06:00:00,8197.0 -2011-10-24 07:00:00,8888.0 -2011-10-24 08:00:00,10289.0 -2011-10-24 09:00:00,10924.0 -2011-10-24 10:00:00,11093.0 -2011-10-24 11:00:00,11250.0 -2011-10-24 12:00:00,11388.0 -2011-10-24 13:00:00,11449.0 -2011-10-24 14:00:00,11514.0 -2011-10-24 15:00:00,11540.0 -2011-10-24 16:00:00,11455.0 -2011-10-24 17:00:00,11335.0 -2011-10-24 18:00:00,11207.0 -2011-10-24 19:00:00,11242.0 -2011-10-24 20:00:00,11891.0 -2011-10-24 21:00:00,11845.0 -2011-10-24 22:00:00,11556.0 -2011-10-24 23:00:00,11035.0 -2011-10-25 00:00:00,10191.0 -2011-10-23 01:00:00,9083.0 -2011-10-23 02:00:00,8543.0 -2011-10-23 03:00:00,8234.0 -2011-10-23 04:00:00,8042.0 -2011-10-23 05:00:00,7979.0 -2011-10-23 06:00:00,7975.0 -2011-10-23 07:00:00,8239.0 -2011-10-23 08:00:00,8509.0 -2011-10-23 09:00:00,8603.0 -2011-10-23 10:00:00,8743.0 -2011-10-23 11:00:00,8934.0 -2011-10-23 12:00:00,9102.0 -2011-10-23 13:00:00,9257.0 -2011-10-23 14:00:00,9199.0 -2011-10-23 15:00:00,9274.0 -2011-10-23 16:00:00,9229.0 -2011-10-23 17:00:00,9246.0 -2011-10-23 18:00:00,9207.0 -2011-10-23 19:00:00,9489.0 -2011-10-23 20:00:00,10248.0 -2011-10-23 21:00:00,10430.0 -2011-10-23 22:00:00,10198.0 -2011-10-23 23:00:00,9817.0 -2011-10-24 00:00:00,9281.0 -2011-10-22 01:00:00,9773.0 -2011-10-22 02:00:00,9198.0 -2011-10-22 03:00:00,8753.0 -2011-10-22 04:00:00,8680.0 -2011-10-22 05:00:00,8576.0 -2011-10-22 06:00:00,8620.0 -2011-10-22 07:00:00,8890.0 -2011-10-22 08:00:00,9385.0 -2011-10-22 09:00:00,9580.0 -2011-10-22 10:00:00,9874.0 -2011-10-22 11:00:00,10075.0 -2011-10-22 12:00:00,10095.0 -2011-10-22 13:00:00,10097.0 -2011-10-22 14:00:00,9947.0 -2011-10-22 15:00:00,9848.0 -2011-10-22 16:00:00,9711.0 -2011-10-22 17:00:00,9638.0 -2011-10-22 18:00:00,9582.0 -2011-10-22 19:00:00,9671.0 -2011-10-22 20:00:00,10324.0 -2011-10-22 21:00:00,10351.0 -2011-10-22 22:00:00,10353.0 -2011-10-22 23:00:00,9995.0 -2011-10-23 00:00:00,9566.0 -2011-10-21 01:00:00,9851.0 -2011-10-21 02:00:00,9414.0 -2011-10-21 03:00:00,9078.0 -2011-10-21 04:00:00,8854.0 -2011-10-21 05:00:00,8803.0 -2011-10-21 06:00:00,9025.0 -2011-10-21 07:00:00,9731.0 -2011-10-21 08:00:00,11011.0 -2011-10-21 09:00:00,11539.0 -2011-10-21 10:00:00,11594.0 -2011-10-21 11:00:00,11594.0 -2011-10-21 12:00:00,11573.0 -2011-10-21 13:00:00,11554.0 -2011-10-21 14:00:00,11472.0 -2011-10-21 15:00:00,11384.0 -2011-10-21 16:00:00,11257.0 -2011-10-21 17:00:00,11036.0 -2011-10-21 18:00:00,10952.0 -2011-10-21 19:00:00,10908.0 -2011-10-21 20:00:00,11539.0 -2011-10-21 21:00:00,11555.0 -2011-10-21 22:00:00,11329.0 -2011-10-21 23:00:00,11054.0 -2011-10-22 00:00:00,10425.0 -2011-10-20 01:00:00,9825.0 -2011-10-20 02:00:00,9273.0 -2011-10-20 03:00:00,8987.0 -2011-10-20 04:00:00,8799.0 -2011-10-20 05:00:00,8725.0 -2011-10-20 06:00:00,8976.0 -2011-10-20 07:00:00,9676.0 -2011-10-20 08:00:00,10955.0 -2011-10-20 09:00:00,11743.0 -2011-10-20 10:00:00,11840.0 -2011-10-20 11:00:00,11983.0 -2011-10-20 12:00:00,12101.0 -2011-10-20 13:00:00,12098.0 -2011-10-20 14:00:00,12051.0 -2011-10-20 15:00:00,12024.0 -2011-10-20 16:00:00,11891.0 -2011-10-20 17:00:00,11766.0 -2011-10-20 18:00:00,11750.0 -2011-10-20 19:00:00,11916.0 -2011-10-20 20:00:00,12354.0 -2011-10-20 21:00:00,12281.0 -2011-10-20 22:00:00,11925.0 -2011-10-20 23:00:00,11443.0 -2011-10-21 00:00:00,10666.0 -2011-10-19 01:00:00,9554.0 -2011-10-19 02:00:00,9007.0 -2011-10-19 03:00:00,8693.0 -2011-10-19 04:00:00,8528.0 -2011-10-19 05:00:00,8460.0 -2011-10-19 06:00:00,8651.0 -2011-10-19 07:00:00,9391.0 -2011-10-19 08:00:00,10680.0 -2011-10-19 09:00:00,11436.0 -2011-10-19 10:00:00,11708.0 -2011-10-19 11:00:00,11763.0 -2011-10-19 12:00:00,11961.0 -2011-10-19 13:00:00,11953.0 -2011-10-19 14:00:00,11862.0 -2011-10-19 15:00:00,11888.0 -2011-10-19 16:00:00,11887.0 -2011-10-19 17:00:00,11919.0 -2011-10-19 18:00:00,12035.0 -2011-10-19 19:00:00,12215.0 -2011-10-19 20:00:00,12469.0 -2011-10-19 21:00:00,12328.0 -2011-10-19 22:00:00,12015.0 -2011-10-19 23:00:00,11490.0 -2011-10-20 00:00:00,10643.0 -2011-10-18 01:00:00,9402.0 -2011-10-18 02:00:00,8897.0 -2011-10-18 03:00:00,8599.0 -2011-10-18 04:00:00,8432.0 -2011-10-18 05:00:00,8390.0 -2011-10-18 06:00:00,8590.0 -2011-10-18 07:00:00,9289.0 -2011-10-18 08:00:00,10648.0 -2011-10-18 09:00:00,11329.0 -2011-10-18 10:00:00,11442.0 -2011-10-18 11:00:00,11566.0 -2011-10-18 12:00:00,11604.0 -2011-10-18 13:00:00,11576.0 -2011-10-18 14:00:00,11526.0 -2011-10-18 15:00:00,11512.0 -2011-10-18 16:00:00,11401.0 -2011-10-18 17:00:00,11536.0 -2011-10-18 18:00:00,11582.0 -2011-10-18 19:00:00,11731.0 -2011-10-18 20:00:00,12090.0 -2011-10-18 21:00:00,12019.0 -2011-10-18 22:00:00,11713.0 -2011-10-18 23:00:00,11165.0 -2011-10-19 00:00:00,10334.0 -2011-10-17 01:00:00,8596.0 -2011-10-17 02:00:00,8191.0 -2011-10-17 03:00:00,8027.0 -2011-10-17 04:00:00,7896.0 -2011-10-17 05:00:00,7934.0 -2011-10-17 06:00:00,8147.0 -2011-10-17 07:00:00,8958.0 -2011-10-17 08:00:00,10252.0 -2011-10-17 09:00:00,10939.0 -2011-10-17 10:00:00,11109.0 -2011-10-17 11:00:00,11202.0 -2011-10-17 12:00:00,11326.0 -2011-10-17 13:00:00,11385.0 -2011-10-17 14:00:00,11384.0 -2011-10-17 15:00:00,11410.0 -2011-10-17 16:00:00,11289.0 -2011-10-17 17:00:00,11153.0 -2011-10-17 18:00:00,11067.0 -2011-10-17 19:00:00,11183.0 -2011-10-17 20:00:00,11795.0 -2011-10-17 21:00:00,11853.0 -2011-10-17 22:00:00,11589.0 -2011-10-17 23:00:00,11037.0 -2011-10-18 00:00:00,10193.0 -2011-10-16 01:00:00,8785.0 -2011-10-16 02:00:00,8247.0 -2011-10-16 03:00:00,7950.0 -2011-10-16 04:00:00,7764.0 -2011-10-16 05:00:00,7645.0 -2011-10-16 06:00:00,7645.0 -2011-10-16 07:00:00,7746.0 -2011-10-16 08:00:00,8022.0 -2011-10-16 09:00:00,8291.0 -2011-10-16 10:00:00,8553.0 -2011-10-16 11:00:00,8847.0 -2011-10-16 12:00:00,9063.0 -2011-10-16 13:00:00,9128.0 -2011-10-16 14:00:00,9190.0 -2011-10-16 15:00:00,9108.0 -2011-10-16 16:00:00,9121.0 -2011-10-16 17:00:00,9050.0 -2011-10-16 18:00:00,9137.0 -2011-10-16 19:00:00,9173.0 -2011-10-16 20:00:00,9946.0 -2011-10-16 21:00:00,10259.0 -2011-10-16 22:00:00,10060.0 -2011-10-16 23:00:00,9753.0 -2011-10-17 00:00:00,9139.0 -2011-10-15 01:00:00,9383.0 -2011-10-15 02:00:00,8827.0 -2011-10-15 03:00:00,8433.0 -2011-10-15 04:00:00,8229.0 -2011-10-15 05:00:00,8101.0 -2011-10-15 06:00:00,8113.0 -2011-10-15 07:00:00,8366.0 -2011-10-15 08:00:00,8807.0 -2011-10-15 09:00:00,8972.0 -2011-10-15 10:00:00,9386.0 -2011-10-15 11:00:00,9619.0 -2011-10-15 12:00:00,9775.0 -2011-10-15 13:00:00,9784.0 -2011-10-15 14:00:00,9735.0 -2011-10-15 15:00:00,9619.0 -2011-10-15 16:00:00,9469.0 -2011-10-15 17:00:00,9430.0 -2011-10-15 18:00:00,9405.0 -2011-10-15 19:00:00,9463.0 -2011-10-15 20:00:00,10075.0 -2011-10-15 21:00:00,10240.0 -2011-10-15 22:00:00,10140.0 -2011-10-15 23:00:00,9827.0 -2011-10-16 00:00:00,9339.0 -2011-10-14 01:00:00,9500.0 -2011-10-14 02:00:00,8866.0 -2011-10-14 03:00:00,8543.0 -2011-10-14 04:00:00,8304.0 -2011-10-14 05:00:00,8270.0 -2011-10-14 06:00:00,8391.0 -2011-10-14 07:00:00,9027.0 -2011-10-14 08:00:00,10222.0 -2011-10-14 09:00:00,10754.0 -2011-10-14 10:00:00,10948.0 -2011-10-14 11:00:00,11095.0 -2011-10-14 12:00:00,11288.0 -2011-10-14 13:00:00,11311.0 -2011-10-14 14:00:00,11320.0 -2011-10-14 15:00:00,11332.0 -2011-10-14 16:00:00,11227.0 -2011-10-14 17:00:00,11058.0 -2011-10-14 18:00:00,10906.0 -2011-10-14 19:00:00,10840.0 -2011-10-14 20:00:00,11334.0 -2011-10-14 21:00:00,11468.0 -2011-10-14 22:00:00,11195.0 -2011-10-14 23:00:00,10773.0 -2011-10-15 00:00:00,10081.0 -2011-10-13 01:00:00,9804.0 -2011-10-13 02:00:00,9208.0 -2011-10-13 03:00:00,8836.0 -2011-10-13 04:00:00,8591.0 -2011-10-13 05:00:00,8560.0 -2011-10-13 06:00:00,8690.0 -2011-10-13 07:00:00,9314.0 -2011-10-13 08:00:00,10564.0 -2011-10-13 09:00:00,11387.0 -2011-10-13 10:00:00,11713.0 -2011-10-13 11:00:00,11912.0 -2011-10-13 12:00:00,12118.0 -2011-10-13 13:00:00,12173.0 -2011-10-13 14:00:00,12159.0 -2011-10-13 15:00:00,12197.0 -2011-10-13 16:00:00,12044.0 -2011-10-13 17:00:00,11905.0 -2011-10-13 18:00:00,11834.0 -2011-10-13 19:00:00,11812.0 -2011-10-13 20:00:00,12038.0 -2011-10-13 21:00:00,12140.0 -2011-10-13 22:00:00,11792.0 -2011-10-13 23:00:00,11250.0 -2011-10-14 00:00:00,10321.0 -2011-10-12 01:00:00,9865.0 -2011-10-12 02:00:00,9248.0 -2011-10-12 03:00:00,8832.0 -2011-10-12 04:00:00,8536.0 -2011-10-12 05:00:00,8428.0 -2011-10-12 06:00:00,8579.0 -2011-10-12 07:00:00,9154.0 -2011-10-12 08:00:00,10412.0 -2011-10-12 09:00:00,11010.0 -2011-10-12 10:00:00,11379.0 -2011-10-12 11:00:00,11800.0 -2011-10-12 12:00:00,12246.0 -2011-10-12 13:00:00,12554.0 -2011-10-12 14:00:00,12748.0 -2011-10-12 15:00:00,12951.0 -2011-10-12 16:00:00,12875.0 -2011-10-12 17:00:00,12758.0 -2011-10-12 18:00:00,12480.0 -2011-10-12 19:00:00,12287.0 -2011-10-12 20:00:00,12581.0 -2011-10-12 21:00:00,12649.0 -2011-10-12 22:00:00,12282.0 -2011-10-12 23:00:00,11671.0 -2011-10-13 00:00:00,10726.0 -2011-10-11 01:00:00,9740.0 -2011-10-11 02:00:00,9103.0 -2011-10-11 03:00:00,8750.0 -2011-10-11 04:00:00,8494.0 -2011-10-11 05:00:00,8387.0 -2011-10-11 06:00:00,8540.0 -2011-10-11 07:00:00,9193.0 -2011-10-11 08:00:00,10424.0 -2011-10-11 09:00:00,10981.0 -2011-10-11 10:00:00,11469.0 -2011-10-11 11:00:00,11949.0 -2011-10-11 12:00:00,12415.0 -2011-10-11 13:00:00,12696.0 -2011-10-11 14:00:00,12859.0 -2011-10-11 15:00:00,13015.0 -2011-10-11 16:00:00,12903.0 -2011-10-11 17:00:00,12807.0 -2011-10-11 18:00:00,12660.0 -2011-10-11 19:00:00,12428.0 -2011-10-11 20:00:00,12639.0 -2011-10-11 21:00:00,12778.0 -2011-10-11 22:00:00,12391.0 -2011-10-11 23:00:00,11770.0 -2011-10-12 00:00:00,10790.0 -2011-10-10 01:00:00,9126.0 -2011-10-10 02:00:00,8596.0 -2011-10-10 03:00:00,8311.0 -2011-10-10 04:00:00,8090.0 -2011-10-10 05:00:00,8074.0 -2011-10-10 06:00:00,8202.0 -2011-10-10 07:00:00,8816.0 -2011-10-10 08:00:00,9819.0 -2011-10-10 09:00:00,10417.0 -2011-10-10 10:00:00,10897.0 -2011-10-10 11:00:00,11432.0 -2011-10-10 12:00:00,11770.0 -2011-10-10 13:00:00,12008.0 -2011-10-10 14:00:00,12182.0 -2011-10-10 15:00:00,12388.0 -2011-10-10 16:00:00,12420.0 -2011-10-10 17:00:00,12400.0 -2011-10-10 18:00:00,12282.0 -2011-10-10 19:00:00,12078.0 -2011-10-10 20:00:00,12342.0 -2011-10-10 21:00:00,12464.0 -2011-10-10 22:00:00,12085.0 -2011-10-10 23:00:00,11498.0 -2011-10-11 00:00:00,10628.0 -2011-10-09 01:00:00,9490.0 -2011-10-09 02:00:00,8919.0 -2011-10-09 03:00:00,8468.0 -2011-10-09 04:00:00,8191.0 -2011-10-09 05:00:00,7945.0 -2011-10-09 06:00:00,7887.0 -2011-10-09 07:00:00,7890.0 -2011-10-09 08:00:00,8082.0 -2011-10-09 09:00:00,8020.0 -2011-10-09 10:00:00,8518.0 -2011-10-09 11:00:00,9031.0 -2011-10-09 12:00:00,9542.0 -2011-10-09 13:00:00,9909.0 -2011-10-09 14:00:00,10182.0 -2011-10-09 15:00:00,10395.0 -2011-10-09 16:00:00,10504.0 -2011-10-09 17:00:00,10595.0 -2011-10-09 18:00:00,10579.0 -2011-10-09 19:00:00,10509.0 -2011-10-09 20:00:00,10756.0 -2011-10-09 21:00:00,11034.0 -2011-10-09 22:00:00,10728.0 -2011-10-09 23:00:00,10355.0 -2011-10-10 00:00:00,9747.0 -2011-10-08 01:00:00,10140.0 -2011-10-08 02:00:00,9476.0 -2011-10-08 03:00:00,8962.0 -2011-10-08 04:00:00,8654.0 -2011-10-08 05:00:00,8447.0 -2011-10-08 06:00:00,8444.0 -2011-10-08 07:00:00,8593.0 -2011-10-08 08:00:00,9002.0 -2011-10-08 09:00:00,9139.0 -2011-10-08 10:00:00,9775.0 -2011-10-08 11:00:00,10304.0 -2011-10-08 12:00:00,10822.0 -2011-10-08 13:00:00,11145.0 -2011-10-08 14:00:00,11306.0 -2011-10-08 15:00:00,11404.0 -2011-10-08 16:00:00,11552.0 -2011-10-08 17:00:00,11623.0 -2011-10-08 18:00:00,11485.0 -2011-10-08 19:00:00,11319.0 -2011-10-08 20:00:00,11424.0 -2011-10-08 21:00:00,11580.0 -2011-10-08 22:00:00,11320.0 -2011-10-08 23:00:00,10862.0 -2011-10-09 00:00:00,10212.0 -2011-10-07 01:00:00,9784.0 -2011-10-07 02:00:00,9169.0 -2011-10-07 03:00:00,8753.0 -2011-10-07 04:00:00,8459.0 -2011-10-07 05:00:00,8356.0 -2011-10-07 06:00:00,8500.0 -2011-10-07 07:00:00,9127.0 -2011-10-07 08:00:00,10205.0 -2011-10-07 09:00:00,10821.0 -2011-10-07 10:00:00,11345.0 -2011-10-07 11:00:00,11798.0 -2011-10-07 12:00:00,12295.0 -2011-10-07 13:00:00,12638.0 -2011-10-07 14:00:00,12882.0 -2011-10-07 15:00:00,13047.0 -2011-10-07 16:00:00,13100.0 -2011-10-07 17:00:00,13091.0 -2011-10-07 18:00:00,12922.0 -2011-10-07 19:00:00,12554.0 -2011-10-07 20:00:00,12604.0 -2011-10-07 21:00:00,12714.0 -2011-10-07 22:00:00,12371.0 -2011-10-07 23:00:00,11823.0 -2011-10-08 00:00:00,11010.0 -2011-10-06 01:00:00,9555.0 -2011-10-06 02:00:00,8940.0 -2011-10-06 03:00:00,8552.0 -2011-10-06 04:00:00,8322.0 -2011-10-06 05:00:00,8230.0 -2011-10-06 06:00:00,8398.0 -2011-10-06 07:00:00,8984.0 -2011-10-06 08:00:00,10198.0 -2011-10-06 09:00:00,10805.0 -2011-10-06 10:00:00,11250.0 -2011-10-06 11:00:00,11641.0 -2011-10-06 12:00:00,12117.0 -2011-10-06 13:00:00,12349.0 -2011-10-06 14:00:00,12574.0 -2011-10-06 15:00:00,12762.0 -2011-10-06 16:00:00,12782.0 -2011-10-06 17:00:00,12786.0 -2011-10-06 18:00:00,12626.0 -2011-10-06 19:00:00,12356.0 -2011-10-06 20:00:00,12407.0 -2011-10-06 21:00:00,12593.0 -2011-10-06 22:00:00,12270.0 -2011-10-06 23:00:00,11624.0 -2011-10-07 00:00:00,10712.0 -2011-10-05 01:00:00,9311.0 -2011-10-05 02:00:00,8763.0 -2011-10-05 03:00:00,8435.0 -2011-10-05 04:00:00,8196.0 -2011-10-05 05:00:00,8112.0 -2011-10-05 06:00:00,8299.0 -2011-10-05 07:00:00,8937.0 -2011-10-05 08:00:00,10075.0 -2011-10-05 09:00:00,10658.0 -2011-10-05 10:00:00,11052.0 -2011-10-05 11:00:00,11400.0 -2011-10-05 12:00:00,11760.0 -2011-10-05 13:00:00,12020.0 -2011-10-05 14:00:00,12139.0 -2011-10-05 15:00:00,12360.0 -2011-10-05 16:00:00,12342.0 -2011-10-05 17:00:00,12292.0 -2011-10-05 18:00:00,12191.0 -2011-10-05 19:00:00,11941.0 -2011-10-05 20:00:00,11976.0 -2011-10-05 21:00:00,12264.0 -2011-10-05 22:00:00,11922.0 -2011-10-05 23:00:00,11302.0 -2011-10-06 00:00:00,10413.0 -2011-10-04 01:00:00,9201.0 -2011-10-04 02:00:00,8660.0 -2011-10-04 03:00:00,8356.0 -2011-10-04 04:00:00,8163.0 -2011-10-04 05:00:00,8105.0 -2011-10-04 06:00:00,8295.0 -2011-10-04 07:00:00,8955.0 -2011-10-04 08:00:00,10157.0 -2011-10-04 09:00:00,10670.0 -2011-10-04 10:00:00,11008.0 -2011-10-04 11:00:00,11279.0 -2011-10-04 12:00:00,11571.0 -2011-10-04 13:00:00,11711.0 -2011-10-04 14:00:00,11812.0 -2011-10-04 15:00:00,11937.0 -2011-10-04 16:00:00,11947.0 -2011-10-04 17:00:00,11863.0 -2011-10-04 18:00:00,11732.0 -2011-10-04 19:00:00,11519.0 -2011-10-04 20:00:00,11626.0 -2011-10-04 21:00:00,11981.0 -2011-10-04 22:00:00,11663.0 -2011-10-04 23:00:00,11068.0 -2011-10-05 00:00:00,10150.0 -2011-10-03 01:00:00,8532.0 -2011-10-03 02:00:00,8093.0 -2011-10-03 03:00:00,7916.0 -2011-10-03 04:00:00,7746.0 -2011-10-03 05:00:00,7797.0 -2011-10-03 06:00:00,8034.0 -2011-10-03 07:00:00,8736.0 -2011-10-03 08:00:00,9985.0 -2011-10-03 09:00:00,10593.0 -2011-10-03 10:00:00,10914.0 -2011-10-03 11:00:00,11130.0 -2011-10-03 12:00:00,11430.0 -2011-10-03 13:00:00,11555.0 -2011-10-03 14:00:00,11618.0 -2011-10-03 15:00:00,11682.0 -2011-10-03 16:00:00,11658.0 -2011-10-03 17:00:00,11500.0 -2011-10-03 18:00:00,11328.0 -2011-10-03 19:00:00,11145.0 -2011-10-03 20:00:00,11277.0 -2011-10-03 21:00:00,11684.0 -2011-10-03 22:00:00,11373.0 -2011-10-03 23:00:00,10863.0 -2011-10-04 00:00:00,10036.0 -2011-10-02 01:00:00,8769.0 -2011-10-02 02:00:00,8287.0 -2011-10-02 03:00:00,7997.0 -2011-10-02 04:00:00,7833.0 -2011-10-02 05:00:00,7730.0 -2011-10-02 06:00:00,7777.0 -2011-10-02 07:00:00,7867.0 -2011-10-02 08:00:00,8113.0 -2011-10-02 09:00:00,8140.0 -2011-10-02 10:00:00,8484.0 -2011-10-02 11:00:00,8700.0 -2011-10-02 12:00:00,8944.0 -2011-10-02 13:00:00,9024.0 -2011-10-02 14:00:00,9084.0 -2011-10-02 15:00:00,9058.0 -2011-10-02 16:00:00,9032.0 -2011-10-02 17:00:00,9025.0 -2011-10-02 18:00:00,9075.0 -2011-10-02 19:00:00,9155.0 -2011-10-02 20:00:00,9523.0 -2011-10-02 21:00:00,10175.0 -2011-10-02 22:00:00,10017.0 -2011-10-02 23:00:00,9672.0 -2011-10-03 00:00:00,9069.0 -2011-10-01 01:00:00,9365.0 -2011-10-01 02:00:00,8821.0 -2011-10-01 03:00:00,8481.0 -2011-10-01 04:00:00,8236.0 -2011-10-01 05:00:00,8143.0 -2011-10-01 06:00:00,8112.0 -2011-10-01 07:00:00,8382.0 -2011-10-01 08:00:00,8797.0 -2011-10-01 09:00:00,9005.0 -2011-10-01 10:00:00,9384.0 -2011-10-01 11:00:00,9654.0 -2011-10-01 12:00:00,9774.0 -2011-10-01 13:00:00,9739.0 -2011-10-01 14:00:00,9661.0 -2011-10-01 15:00:00,9517.0 -2011-10-01 16:00:00,9399.0 -2011-10-01 17:00:00,9307.0 -2011-10-01 18:00:00,9285.0 -2011-10-01 19:00:00,9282.0 -2011-10-01 20:00:00,9585.0 -2011-10-01 21:00:00,10149.0 -2011-10-01 22:00:00,10058.0 -2011-10-01 23:00:00,9785.0 -2011-10-02 00:00:00,9321.0 -2011-09-30 01:00:00,9380.0 -2011-09-30 02:00:00,8846.0 -2011-09-30 03:00:00,8532.0 -2011-09-30 04:00:00,8318.0 -2011-09-30 05:00:00,8279.0 -2011-09-30 06:00:00,8436.0 -2011-09-30 07:00:00,9075.0 -2011-09-30 08:00:00,10224.0 -2011-09-30 09:00:00,10810.0 -2011-09-30 10:00:00,10998.0 -2011-09-30 11:00:00,11104.0 -2011-09-30 12:00:00,11232.0 -2011-09-30 13:00:00,11214.0 -2011-09-30 14:00:00,11247.0 -2011-09-30 15:00:00,11270.0 -2011-09-30 16:00:00,11127.0 -2011-09-30 17:00:00,10950.0 -2011-09-30 18:00:00,10839.0 -2011-09-30 19:00:00,10734.0 -2011-09-30 20:00:00,11025.0 -2011-09-30 21:00:00,11305.0 -2011-09-30 22:00:00,11111.0 -2011-09-30 23:00:00,10746.0 -2011-10-01 00:00:00,10068.0 -2011-09-29 01:00:00,9352.0 -2011-09-29 02:00:00,8807.0 -2011-09-29 03:00:00,8483.0 -2011-09-29 04:00:00,8272.0 -2011-09-29 05:00:00,8241.0 -2011-09-29 06:00:00,8399.0 -2011-09-29 07:00:00,9050.0 -2011-09-29 08:00:00,10168.0 -2011-09-29 09:00:00,10735.0 -2011-09-29 10:00:00,11095.0 -2011-09-29 11:00:00,11369.0 -2011-09-29 12:00:00,11585.0 -2011-09-29 13:00:00,11750.0 -2011-09-29 14:00:00,11814.0 -2011-09-29 15:00:00,11863.0 -2011-09-29 16:00:00,11670.0 -2011-09-29 17:00:00,11510.0 -2011-09-29 18:00:00,11431.0 -2011-09-29 19:00:00,11365.0 -2011-09-29 20:00:00,11465.0 -2011-09-29 21:00:00,11800.0 -2011-09-29 22:00:00,11497.0 -2011-09-29 23:00:00,11023.0 -2011-09-30 00:00:00,10150.0 -2011-09-28 01:00:00,9295.0 -2011-09-28 02:00:00,8822.0 -2011-09-28 03:00:00,8503.0 -2011-09-28 04:00:00,8275.0 -2011-09-28 05:00:00,8226.0 -2011-09-28 06:00:00,8388.0 -2011-09-28 07:00:00,8994.0 -2011-09-28 08:00:00,10169.0 -2011-09-28 09:00:00,10845.0 -2011-09-28 10:00:00,11191.0 -2011-09-28 11:00:00,11393.0 -2011-09-28 12:00:00,11575.0 -2011-09-28 13:00:00,11661.0 -2011-09-28 14:00:00,11625.0 -2011-09-28 15:00:00,11707.0 -2011-09-28 16:00:00,11569.0 -2011-09-28 17:00:00,11505.0 -2011-09-28 18:00:00,11392.0 -2011-09-28 19:00:00,11325.0 -2011-09-28 20:00:00,11522.0 -2011-09-28 21:00:00,11822.0 -2011-09-28 22:00:00,11545.0 -2011-09-28 23:00:00,11018.0 -2011-09-29 00:00:00,10177.0 -2011-09-27 01:00:00,9300.0 -2011-09-27 02:00:00,8776.0 -2011-09-27 03:00:00,8477.0 -2011-09-27 04:00:00,8263.0 -2011-09-27 05:00:00,8199.0 -2011-09-27 06:00:00,8369.0 -2011-09-27 07:00:00,9039.0 -2011-09-27 08:00:00,10255.0 -2011-09-27 09:00:00,10931.0 -2011-09-27 10:00:00,11208.0 -2011-09-27 11:00:00,11459.0 -2011-09-27 12:00:00,11596.0 -2011-09-27 13:00:00,11647.0 -2011-09-27 14:00:00,11615.0 -2011-09-27 15:00:00,11639.0 -2011-09-27 16:00:00,11553.0 -2011-09-27 17:00:00,11413.0 -2011-09-27 18:00:00,11308.0 -2011-09-27 19:00:00,11260.0 -2011-09-27 20:00:00,11449.0 -2011-09-27 21:00:00,11779.0 -2011-09-27 22:00:00,11522.0 -2011-09-27 23:00:00,10968.0 -2011-09-28 00:00:00,10119.0 -2011-09-26 01:00:00,8581.0 -2011-09-26 02:00:00,8192.0 -2011-09-26 03:00:00,8007.0 -2011-09-26 04:00:00,7935.0 -2011-09-26 05:00:00,7931.0 -2011-09-26 06:00:00,8205.0 -2011-09-26 07:00:00,8897.0 -2011-09-26 08:00:00,10070.0 -2011-09-26 09:00:00,10824.0 -2011-09-26 10:00:00,11113.0 -2011-09-26 11:00:00,11314.0 -2011-09-26 12:00:00,11553.0 -2011-09-26 13:00:00,11576.0 -2011-09-26 14:00:00,11590.0 -2011-09-26 15:00:00,11610.0 -2011-09-26 16:00:00,11535.0 -2011-09-26 17:00:00,11362.0 -2011-09-26 18:00:00,11293.0 -2011-09-26 19:00:00,11254.0 -2011-09-26 20:00:00,11404.0 -2011-09-26 21:00:00,11765.0 -2011-09-26 22:00:00,11515.0 -2011-09-26 23:00:00,10971.0 -2011-09-27 00:00:00,10107.0 -2011-09-25 01:00:00,8712.0 -2011-09-25 02:00:00,8183.0 -2011-09-25 03:00:00,7925.0 -2011-09-25 04:00:00,7682.0 -2011-09-25 05:00:00,7575.0 -2011-09-25 06:00:00,7527.0 -2011-09-25 07:00:00,7630.0 -2011-09-25 08:00:00,7797.0 -2011-09-25 09:00:00,7960.0 -2011-09-25 10:00:00,8391.0 -2011-09-25 11:00:00,8786.0 -2011-09-25 12:00:00,9041.0 -2011-09-25 13:00:00,9211.0 -2011-09-25 14:00:00,9222.0 -2011-09-25 15:00:00,9284.0 -2011-09-25 16:00:00,9233.0 -2011-09-25 17:00:00,9221.0 -2011-09-25 18:00:00,9241.0 -2011-09-25 19:00:00,9312.0 -2011-09-25 20:00:00,9594.0 -2011-09-25 21:00:00,10235.0 -2011-09-25 22:00:00,10073.0 -2011-09-25 23:00:00,9716.0 -2011-09-26 00:00:00,9142.0 -2011-09-24 01:00:00,9181.0 -2011-09-24 02:00:00,8609.0 -2011-09-24 03:00:00,8252.0 -2011-09-24 04:00:00,8019.0 -2011-09-24 05:00:00,7871.0 -2011-09-24 06:00:00,7938.0 -2011-09-24 07:00:00,8134.0 -2011-09-24 08:00:00,8546.0 -2011-09-24 09:00:00,8740.0 -2011-09-24 10:00:00,9228.0 -2011-09-24 11:00:00,9580.0 -2011-09-24 12:00:00,9804.0 -2011-09-24 13:00:00,9865.0 -2011-09-24 14:00:00,9740.0 -2011-09-24 15:00:00,9639.0 -2011-09-24 16:00:00,9526.0 -2011-09-24 17:00:00,9461.0 -2011-09-24 18:00:00,9451.0 -2011-09-24 19:00:00,9449.0 -2011-09-24 20:00:00,9613.0 -2011-09-24 21:00:00,10093.0 -2011-09-24 22:00:00,10023.0 -2011-09-24 23:00:00,9755.0 -2011-09-25 00:00:00,9230.0 -2011-09-23 01:00:00,9294.0 -2011-09-23 02:00:00,8760.0 -2011-09-23 03:00:00,8435.0 -2011-09-23 04:00:00,8187.0 -2011-09-23 05:00:00,8131.0 -2011-09-23 06:00:00,8287.0 -2011-09-23 07:00:00,8875.0 -2011-09-23 08:00:00,9935.0 -2011-09-23 09:00:00,10535.0 -2011-09-23 10:00:00,10811.0 -2011-09-23 11:00:00,11014.0 -2011-09-23 12:00:00,11198.0 -2011-09-23 13:00:00,11243.0 -2011-09-23 14:00:00,11295.0 -2011-09-23 15:00:00,11336.0 -2011-09-23 16:00:00,11222.0 -2011-09-23 17:00:00,11017.0 -2011-09-23 18:00:00,10857.0 -2011-09-23 19:00:00,10742.0 -2011-09-23 20:00:00,10894.0 -2011-09-23 21:00:00,11205.0 -2011-09-23 22:00:00,10936.0 -2011-09-23 23:00:00,10571.0 -2011-09-24 00:00:00,9875.0 -2011-09-22 01:00:00,9397.0 -2011-09-22 02:00:00,8865.0 -2011-09-22 03:00:00,8511.0 -2011-09-22 04:00:00,8284.0 -2011-09-22 05:00:00,8245.0 -2011-09-22 06:00:00,8392.0 -2011-09-22 07:00:00,8971.0 -2011-09-22 08:00:00,10037.0 -2011-09-22 09:00:00,10596.0 -2011-09-22 10:00:00,11001.0 -2011-09-22 11:00:00,11290.0 -2011-09-22 12:00:00,11503.0 -2011-09-22 13:00:00,11534.0 -2011-09-22 14:00:00,11510.0 -2011-09-22 15:00:00,11551.0 -2011-09-22 16:00:00,11478.0 -2011-09-22 17:00:00,11368.0 -2011-09-22 18:00:00,11273.0 -2011-09-22 19:00:00,11086.0 -2011-09-22 20:00:00,11123.0 -2011-09-22 21:00:00,11649.0 -2011-09-22 22:00:00,11456.0 -2011-09-22 23:00:00,10936.0 -2011-09-23 00:00:00,10079.0 -2011-09-21 01:00:00,9723.0 -2011-09-21 02:00:00,9154.0 -2011-09-21 03:00:00,8753.0 -2011-09-21 04:00:00,8547.0 -2011-09-21 05:00:00,8433.0 -2011-09-21 06:00:00,8584.0 -2011-09-21 07:00:00,9206.0 -2011-09-21 08:00:00,10358.0 -2011-09-21 09:00:00,10989.0 -2011-09-21 10:00:00,11379.0 -2011-09-21 11:00:00,11683.0 -2011-09-21 12:00:00,11953.0 -2011-09-21 13:00:00,12100.0 -2011-09-21 14:00:00,12202.0 -2011-09-21 15:00:00,12307.0 -2011-09-21 16:00:00,12239.0 -2011-09-21 17:00:00,12130.0 -2011-09-21 18:00:00,11941.0 -2011-09-21 19:00:00,11654.0 -2011-09-21 20:00:00,11523.0 -2011-09-21 21:00:00,11987.0 -2011-09-21 22:00:00,11733.0 -2011-09-21 23:00:00,11153.0 -2011-09-22 00:00:00,10259.0 -2011-09-20 01:00:00,9425.0 -2011-09-20 02:00:00,8855.0 -2011-09-20 03:00:00,8513.0 -2011-09-20 04:00:00,8290.0 -2011-09-20 05:00:00,8209.0 -2011-09-20 06:00:00,8380.0 -2011-09-20 07:00:00,9004.0 -2011-09-20 08:00:00,10084.0 -2011-09-20 09:00:00,10679.0 -2011-09-20 10:00:00,11161.0 -2011-09-20 11:00:00,11515.0 -2011-09-20 12:00:00,11865.0 -2011-09-20 13:00:00,12069.0 -2011-09-20 14:00:00,12178.0 -2011-09-20 15:00:00,12319.0 -2011-09-20 16:00:00,12353.0 -2011-09-20 17:00:00,12313.0 -2011-09-20 18:00:00,12214.0 -2011-09-20 19:00:00,11980.0 -2011-09-20 20:00:00,11771.0 -2011-09-20 21:00:00,12264.0 -2011-09-20 22:00:00,12077.0 -2011-09-20 23:00:00,11464.0 -2011-09-21 00:00:00,10567.0 -2011-09-19 01:00:00,8809.0 -2011-09-19 02:00:00,8468.0 -2011-09-19 03:00:00,8208.0 -2011-09-19 04:00:00,8163.0 -2011-09-19 05:00:00,8122.0 -2011-09-19 06:00:00,8368.0 -2011-09-19 07:00:00,9027.0 -2011-09-19 08:00:00,10316.0 -2011-09-19 09:00:00,11165.0 -2011-09-19 10:00:00,11599.0 -2011-09-19 11:00:00,11793.0 -2011-09-19 12:00:00,12002.0 -2011-09-19 13:00:00,12072.0 -2011-09-19 14:00:00,12055.0 -2011-09-19 15:00:00,12087.0 -2011-09-19 16:00:00,11999.0 -2011-09-19 17:00:00,11859.0 -2011-09-19 18:00:00,11772.0 -2011-09-19 19:00:00,11608.0 -2011-09-19 20:00:00,11447.0 -2011-09-19 21:00:00,11958.0 -2011-09-19 22:00:00,11751.0 -2011-09-19 23:00:00,11164.0 -2011-09-20 00:00:00,10270.0 -2011-09-18 01:00:00,8712.0 -2011-09-18 02:00:00,8263.0 -2011-09-18 03:00:00,7921.0 -2011-09-18 04:00:00,7718.0 -2011-09-18 05:00:00,7582.0 -2011-09-18 06:00:00,7523.0 -2011-09-18 07:00:00,7651.0 -2011-09-18 08:00:00,7834.0 -2011-09-18 09:00:00,7977.0 -2011-09-18 10:00:00,8416.0 -2011-09-18 11:00:00,8769.0 -2011-09-18 12:00:00,9063.0 -2011-09-18 13:00:00,9258.0 -2011-09-18 14:00:00,9355.0 -2011-09-18 15:00:00,9391.0 -2011-09-18 16:00:00,9440.0 -2011-09-18 17:00:00,9490.0 -2011-09-18 18:00:00,9573.0 -2011-09-18 19:00:00,9730.0 -2011-09-18 20:00:00,10022.0 -2011-09-18 21:00:00,10448.0 -2011-09-18 22:00:00,10322.0 -2011-09-18 23:00:00,9963.0 -2011-09-19 00:00:00,9428.0 -2011-09-17 01:00:00,9223.0 -2011-09-17 02:00:00,8622.0 -2011-09-17 03:00:00,8320.0 -2011-09-17 04:00:00,8061.0 -2011-09-17 05:00:00,7976.0 -2011-09-17 06:00:00,7987.0 -2011-09-17 07:00:00,8213.0 -2011-09-17 08:00:00,8537.0 -2011-09-17 09:00:00,8765.0 -2011-09-17 10:00:00,9280.0 -2011-09-17 11:00:00,9620.0 -2011-09-17 12:00:00,9878.0 -2011-09-17 13:00:00,9941.0 -2011-09-17 14:00:00,9863.0 -2011-09-17 15:00:00,9815.0 -2011-09-17 16:00:00,9727.0 -2011-09-17 17:00:00,9738.0 -2011-09-17 18:00:00,9647.0 -2011-09-17 19:00:00,9552.0 -2011-09-17 20:00:00,9580.0 -2011-09-17 21:00:00,10152.0 -2011-09-17 22:00:00,10151.0 -2011-09-17 23:00:00,9817.0 -2011-09-18 00:00:00,9289.0 -2011-09-16 01:00:00,9233.0 -2011-09-16 02:00:00,8760.0 -2011-09-16 03:00:00,8437.0 -2011-09-16 04:00:00,8224.0 -2011-09-16 05:00:00,8139.0 -2011-09-16 06:00:00,8338.0 -2011-09-16 07:00:00,8957.0 -2011-09-16 08:00:00,10023.0 -2011-09-16 09:00:00,10667.0 -2011-09-16 10:00:00,11021.0 -2011-09-16 11:00:00,11241.0 -2011-09-16 12:00:00,11359.0 -2011-09-16 13:00:00,11417.0 -2011-09-16 14:00:00,11359.0 -2011-09-16 15:00:00,11311.0 -2011-09-16 16:00:00,11208.0 -2011-09-16 17:00:00,11058.0 -2011-09-16 18:00:00,10941.0 -2011-09-16 19:00:00,10804.0 -2011-09-16 20:00:00,10828.0 -2011-09-16 21:00:00,11216.0 -2011-09-16 22:00:00,11031.0 -2011-09-16 23:00:00,10650.0 -2011-09-17 00:00:00,9926.0 -2011-09-15 01:00:00,9257.0 -2011-09-15 02:00:00,8704.0 -2011-09-15 03:00:00,8377.0 -2011-09-15 04:00:00,8172.0 -2011-09-15 05:00:00,8116.0 -2011-09-15 06:00:00,8286.0 -2011-09-15 07:00:00,8914.0 -2011-09-15 08:00:00,9965.0 -2011-09-15 09:00:00,10548.0 -2011-09-15 10:00:00,10925.0 -2011-09-15 11:00:00,11084.0 -2011-09-15 12:00:00,11255.0 -2011-09-15 13:00:00,11304.0 -2011-09-15 14:00:00,11321.0 -2011-09-15 15:00:00,11384.0 -2011-09-15 16:00:00,11299.0 -2011-09-15 17:00:00,11191.0 -2011-09-15 18:00:00,11062.0 -2011-09-15 19:00:00,10864.0 -2011-09-15 20:00:00,10816.0 -2011-09-15 21:00:00,11388.0 -2011-09-15 22:00:00,11300.0 -2011-09-15 23:00:00,10864.0 -2011-09-16 00:00:00,9957.0 -2011-09-14 01:00:00,10012.0 -2011-09-14 02:00:00,9363.0 -2011-09-14 03:00:00,8959.0 -2011-09-14 04:00:00,8707.0 -2011-09-14 05:00:00,8590.0 -2011-09-14 06:00:00,8720.0 -2011-09-14 07:00:00,9334.0 -2011-09-14 08:00:00,10379.0 -2011-09-14 09:00:00,11023.0 -2011-09-14 10:00:00,11538.0 -2011-09-14 11:00:00,11731.0 -2011-09-14 12:00:00,11851.0 -2011-09-14 13:00:00,11887.0 -2011-09-14 14:00:00,11802.0 -2011-09-14 15:00:00,11781.0 -2011-09-14 16:00:00,11615.0 -2011-09-14 17:00:00,11396.0 -2011-09-14 18:00:00,11259.0 -2011-09-14 19:00:00,11060.0 -2011-09-14 20:00:00,10877.0 -2011-09-14 21:00:00,11395.0 -2011-09-14 22:00:00,11393.0 -2011-09-14 23:00:00,10876.0 -2011-09-15 00:00:00,10042.0 -2011-09-13 01:00:00,11723.0 -2011-09-13 02:00:00,10848.0 -2011-09-13 03:00:00,10265.0 -2011-09-13 04:00:00,9872.0 -2011-09-13 05:00:00,9667.0 -2011-09-13 06:00:00,9755.0 -2011-09-13 07:00:00,10402.0 -2011-09-13 08:00:00,11474.0 -2011-09-13 09:00:00,12120.0 -2011-09-13 10:00:00,12754.0 -2011-09-13 11:00:00,13169.0 -2011-09-13 12:00:00,13574.0 -2011-09-13 13:00:00,13757.0 -2011-09-13 14:00:00,13872.0 -2011-09-13 15:00:00,14058.0 -2011-09-13 16:00:00,14053.0 -2011-09-13 17:00:00,13987.0 -2011-09-13 18:00:00,13738.0 -2011-09-13 19:00:00,13266.0 -2011-09-13 20:00:00,12715.0 -2011-09-13 21:00:00,12909.0 -2011-09-13 22:00:00,12706.0 -2011-09-13 23:00:00,11989.0 -2011-09-14 00:00:00,10956.0 -2011-09-12 01:00:00,10448.0 -2011-09-12 02:00:00,9781.0 -2011-09-12 03:00:00,9372.0 -2011-09-12 04:00:00,9057.0 -2011-09-12 05:00:00,8948.0 -2011-09-12 06:00:00,9140.0 -2011-09-12 07:00:00,9910.0 -2011-09-12 08:00:00,11025.0 -2011-09-12 09:00:00,11901.0 -2011-09-12 10:00:00,12695.0 -2011-09-12 11:00:00,13490.0 -2011-09-12 12:00:00,14160.0 -2011-09-12 13:00:00,14782.0 -2011-09-12 14:00:00,15382.0 -2011-09-12 15:00:00,15931.0 -2011-09-12 16:00:00,16158.0 -2011-09-12 17:00:00,16325.0 -2011-09-12 18:00:00,16278.0 -2011-09-12 19:00:00,15891.0 -2011-09-12 20:00:00,15171.0 -2011-09-12 21:00:00,15253.0 -2011-09-12 22:00:00,15049.0 -2011-09-12 23:00:00,14173.0 -2011-09-13 00:00:00,12935.0 -2011-09-11 01:00:00,9860.0 -2011-09-11 02:00:00,9274.0 -2011-09-11 03:00:00,8798.0 -2011-09-11 04:00:00,8523.0 -2011-09-11 05:00:00,8308.0 -2011-09-11 06:00:00,8245.0 -2011-09-11 07:00:00,8232.0 -2011-09-11 08:00:00,8261.0 -2011-09-11 09:00:00,8429.0 -2011-09-11 10:00:00,9095.0 -2011-09-11 11:00:00,9756.0 -2011-09-11 12:00:00,10379.0 -2011-09-11 13:00:00,10933.0 -2011-09-11 14:00:00,11462.0 -2011-09-11 15:00:00,11794.0 -2011-09-11 16:00:00,12128.0 -2011-09-11 17:00:00,12333.0 -2011-09-11 18:00:00,12538.0 -2011-09-11 19:00:00,12554.0 -2011-09-11 20:00:00,12381.0 -2011-09-11 21:00:00,12644.0 -2011-09-11 22:00:00,12666.0 -2011-09-11 23:00:00,12146.0 -2011-09-12 00:00:00,11317.0 -2011-09-10 01:00:00,10144.0 -2011-09-10 02:00:00,9458.0 -2011-09-10 03:00:00,9055.0 -2011-09-10 04:00:00,8729.0 -2011-09-10 05:00:00,8587.0 -2011-09-10 06:00:00,8525.0 -2011-09-10 07:00:00,8687.0 -2011-09-10 08:00:00,8963.0 -2011-09-10 09:00:00,9314.0 -2011-09-10 10:00:00,9958.0 -2011-09-10 11:00:00,10509.0 -2011-09-10 12:00:00,10965.0 -2011-09-10 13:00:00,11270.0 -2011-09-10 14:00:00,11487.0 -2011-09-10 15:00:00,11508.0 -2011-09-10 16:00:00,11464.0 -2011-09-10 17:00:00,11448.0 -2011-09-10 18:00:00,11458.0 -2011-09-10 19:00:00,11416.0 -2011-09-10 20:00:00,11233.0 -2011-09-10 21:00:00,11569.0 -2011-09-10 22:00:00,11569.0 -2011-09-10 23:00:00,11169.0 -2011-09-11 00:00:00,10561.0 -2011-09-09 01:00:00,9973.0 -2011-09-09 02:00:00,9393.0 -2011-09-09 03:00:00,9089.0 -2011-09-09 04:00:00,8792.0 -2011-09-09 05:00:00,8713.0 -2011-09-09 06:00:00,8902.0 -2011-09-09 07:00:00,9558.0 -2011-09-09 08:00:00,10690.0 -2011-09-09 09:00:00,11363.0 -2011-09-09 10:00:00,11835.0 -2011-09-09 11:00:00,12199.0 -2011-09-09 12:00:00,12426.0 -2011-09-09 13:00:00,12487.0 -2011-09-09 14:00:00,12484.0 -2011-09-09 15:00:00,12538.0 -2011-09-09 16:00:00,12486.0 -2011-09-09 17:00:00,12453.0 -2011-09-09 18:00:00,12272.0 -2011-09-09 19:00:00,12001.0 -2011-09-09 20:00:00,11856.0 -2011-09-09 21:00:00,12193.0 -2011-09-09 22:00:00,12141.0 -2011-09-09 23:00:00,11680.0 -2011-09-10 00:00:00,10882.0 -2011-09-08 01:00:00,9527.0 -2011-09-08 02:00:00,8986.0 -2011-09-08 03:00:00,8641.0 -2011-09-08 04:00:00,8432.0 -2011-09-08 05:00:00,8361.0 -2011-09-08 06:00:00,8551.0 -2011-09-08 07:00:00,9189.0 -2011-09-08 08:00:00,10245.0 -2011-09-08 09:00:00,10942.0 -2011-09-08 10:00:00,11493.0 -2011-09-08 11:00:00,11895.0 -2011-09-08 12:00:00,12282.0 -2011-09-08 13:00:00,12461.0 -2011-09-08 14:00:00,12523.0 -2011-09-08 15:00:00,12625.0 -2011-09-08 16:00:00,12460.0 -2011-09-08 17:00:00,12304.0 -2011-09-08 18:00:00,12131.0 -2011-09-08 19:00:00,11971.0 -2011-09-08 20:00:00,11896.0 -2011-09-08 21:00:00,12279.0 -2011-09-08 22:00:00,12173.0 -2011-09-08 23:00:00,11659.0 -2011-09-09 00:00:00,10782.0 -2011-09-07 01:00:00,9429.0 -2011-09-07 02:00:00,8880.0 -2011-09-07 03:00:00,8545.0 -2011-09-07 04:00:00,8317.0 -2011-09-07 05:00:00,8244.0 -2011-09-07 06:00:00,8415.0 -2011-09-07 07:00:00,9032.0 -2011-09-07 08:00:00,9971.0 -2011-09-07 09:00:00,10667.0 -2011-09-07 10:00:00,11219.0 -2011-09-07 11:00:00,11604.0 -2011-09-07 12:00:00,11909.0 -2011-09-07 13:00:00,11999.0 -2011-09-07 14:00:00,12045.0 -2011-09-07 15:00:00,12185.0 -2011-09-07 16:00:00,12129.0 -2011-09-07 17:00:00,12085.0 -2011-09-07 18:00:00,11980.0 -2011-09-07 19:00:00,11756.0 -2011-09-07 20:00:00,11385.0 -2011-09-07 21:00:00,11681.0 -2011-09-07 22:00:00,11780.0 -2011-09-07 23:00:00,11225.0 -2011-09-08 00:00:00,10351.0 -2011-09-06 01:00:00,8437.0 -2011-09-06 02:00:00,8015.0 -2011-09-06 03:00:00,7803.0 -2011-09-06 04:00:00,7675.0 -2011-09-06 05:00:00,7686.0 -2011-09-06 06:00:00,7930.0 -2011-09-06 07:00:00,8655.0 -2011-09-06 08:00:00,9669.0 -2011-09-06 09:00:00,10399.0 -2011-09-06 10:00:00,10934.0 -2011-09-06 11:00:00,11272.0 -2011-09-06 12:00:00,11396.0 -2011-09-06 13:00:00,11754.0 -2011-09-06 14:00:00,11781.0 -2011-09-06 15:00:00,11940.0 -2011-09-06 16:00:00,11918.0 -2011-09-06 17:00:00,11838.0 -2011-09-06 18:00:00,11757.0 -2011-09-06 19:00:00,11522.0 -2011-09-06 20:00:00,11217.0 -2011-09-06 21:00:00,11533.0 -2011-09-06 22:00:00,11645.0 -2011-09-06 23:00:00,11078.0 -2011-09-07 00:00:00,10248.0 -2011-09-05 01:00:00,9270.0 -2011-09-05 02:00:00,8683.0 -2011-09-05 03:00:00,8324.0 -2011-09-05 04:00:00,8013.0 -2011-09-05 05:00:00,7891.0 -2011-09-05 06:00:00,7815.0 -2011-09-05 07:00:00,7948.0 -2011-09-05 08:00:00,7896.0 -2011-09-05 09:00:00,8012.0 -2011-09-05 10:00:00,8375.0 -2011-09-05 11:00:00,8834.0 -2011-09-05 12:00:00,9184.0 -2011-09-05 13:00:00,9350.0 -2011-09-05 14:00:00,9378.0 -2011-09-05 15:00:00,9345.0 -2011-09-05 16:00:00,9274.0 -2011-09-05 17:00:00,9255.0 -2011-09-05 18:00:00,9267.0 -2011-09-05 19:00:00,9311.0 -2011-09-05 20:00:00,9304.0 -2011-09-05 21:00:00,9718.0 -2011-09-05 22:00:00,9983.0 -2011-09-05 23:00:00,9650.0 -2011-09-06 00:00:00,9032.0 -2011-09-04 01:00:00,11655.0 -2011-09-04 02:00:00,10938.0 -2011-09-04 03:00:00,10275.0 -2011-09-04 04:00:00,9872.0 -2011-09-04 05:00:00,9593.0 -2011-09-04 06:00:00,9440.0 -2011-09-04 07:00:00,9429.0 -2011-09-04 08:00:00,9305.0 -2011-09-04 09:00:00,9317.0 -2011-09-04 10:00:00,9662.0 -2011-09-04 11:00:00,10095.0 -2011-09-04 12:00:00,10473.0 -2011-09-04 13:00:00,10883.0 -2011-09-04 14:00:00,11000.0 -2011-09-04 15:00:00,11139.0 -2011-09-04 16:00:00,11131.0 -2011-09-04 17:00:00,11073.0 -2011-09-04 18:00:00,10922.0 -2011-09-04 19:00:00,10697.0 -2011-09-04 20:00:00,10369.0 -2011-09-04 21:00:00,10486.0 -2011-09-04 22:00:00,10617.0 -2011-09-04 23:00:00,10363.0 -2011-09-05 00:00:00,9804.0 -2011-09-03 01:00:00,15121.0 -2011-09-03 02:00:00,13948.0 -2011-09-03 03:00:00,13139.0 -2011-09-03 04:00:00,12445.0 -2011-09-03 05:00:00,12002.0 -2011-09-03 06:00:00,11710.0 -2011-09-03 07:00:00,11721.0 -2011-09-03 08:00:00,11782.0 -2011-09-03 09:00:00,12271.0 -2011-09-03 10:00:00,13242.0 -2011-09-03 11:00:00,14440.0 -2011-09-03 12:00:00,15616.0 -2011-09-03 13:00:00,16476.0 -2011-09-03 14:00:00,17088.0 -2011-09-03 15:00:00,17126.0 -2011-09-03 16:00:00,16496.0 -2011-09-03 17:00:00,15421.0 -2011-09-03 18:00:00,14895.0 -2011-09-03 19:00:00,14456.0 -2011-09-03 20:00:00,14137.0 -2011-09-03 21:00:00,14017.0 -2011-09-03 22:00:00,13823.0 -2011-09-03 23:00:00,13233.0 -2011-09-04 00:00:00,12485.0 -2011-09-02 01:00:00,16524.0 -2011-09-02 02:00:00,15381.0 -2011-09-02 03:00:00,14539.0 -2011-09-02 04:00:00,13879.0 -2011-09-02 05:00:00,13445.0 -2011-09-02 06:00:00,13312.0 -2011-09-02 07:00:00,13841.0 -2011-09-02 08:00:00,14830.0 -2011-09-02 09:00:00,15591.0 -2011-09-02 10:00:00,16350.0 -2011-09-02 11:00:00,17170.0 -2011-09-02 12:00:00,18099.0 -2011-09-02 13:00:00,19109.0 -2011-09-02 14:00:00,20033.0 -2011-09-02 15:00:00,20745.0 -2011-09-02 16:00:00,21055.0 -2011-09-02 17:00:00,21169.0 -2011-09-02 18:00:00,21028.0 -2011-09-02 19:00:00,20567.0 -2011-09-02 20:00:00,19704.0 -2011-09-02 21:00:00,19148.0 -2011-09-02 22:00:00,18731.0 -2011-09-02 23:00:00,17808.0 -2011-09-03 00:00:00,16435.0 -2011-09-01 01:00:00,14098.0 -2011-09-01 02:00:00,13024.0 -2011-09-01 03:00:00,12219.0 -2011-09-01 04:00:00,11616.0 -2011-09-01 05:00:00,11343.0 -2011-09-01 06:00:00,11394.0 -2011-09-01 07:00:00,12029.0 -2011-09-01 08:00:00,13119.0 -2011-09-01 09:00:00,14253.0 -2011-09-01 10:00:00,15354.0 -2011-09-01 11:00:00,16552.0 -2011-09-01 12:00:00,17751.0 -2011-09-01 13:00:00,18772.0 -2011-09-01 14:00:00,19786.0 -2011-09-01 15:00:00,20587.0 -2011-09-01 16:00:00,20937.0 -2011-09-01 17:00:00,21525.0 -2011-09-01 18:00:00,21900.0 -2011-09-01 19:00:00,21635.0 -2011-09-01 20:00:00,21014.0 -2011-09-01 21:00:00,20666.0 -2011-09-01 22:00:00,20436.0 -2011-09-01 23:00:00,19448.0 -2011-09-02 00:00:00,17900.0 -2011-08-31 01:00:00,10992.0 -2011-08-31 02:00:00,10284.0 -2011-08-31 03:00:00,9811.0 -2011-08-31 04:00:00,9461.0 -2011-08-31 05:00:00,9322.0 -2011-08-31 06:00:00,9482.0 -2011-08-31 07:00:00,10201.0 -2011-08-31 08:00:00,11255.0 -2011-08-31 09:00:00,12091.0 -2011-08-31 10:00:00,12800.0 -2011-08-31 11:00:00,13490.0 -2011-08-31 12:00:00,14277.0 -2011-08-31 13:00:00,14937.0 -2011-08-31 14:00:00,15593.0 -2011-08-31 15:00:00,16422.0 -2011-08-31 16:00:00,17129.0 -2011-08-31 17:00:00,17823.0 -2011-08-31 18:00:00,18299.0 -2011-08-31 19:00:00,18358.0 -2011-08-31 20:00:00,17944.0 -2011-08-31 21:00:00,17767.0 -2011-08-31 22:00:00,17747.0 -2011-08-31 23:00:00,16902.0 -2011-09-01 00:00:00,15468.0 -2011-08-30 01:00:00,10971.0 -2011-08-30 02:00:00,10228.0 -2011-08-30 03:00:00,9712.0 -2011-08-30 04:00:00,9352.0 -2011-08-30 05:00:00,9181.0 -2011-08-30 06:00:00,9331.0 -2011-08-30 07:00:00,9966.0 -2011-08-30 08:00:00,10917.0 -2011-08-30 09:00:00,11685.0 -2011-08-30 10:00:00,12244.0 -2011-08-30 11:00:00,12775.0 -2011-08-30 12:00:00,13254.0 -2011-08-30 13:00:00,13497.0 -2011-08-30 14:00:00,13772.0 -2011-08-30 15:00:00,13912.0 -2011-08-30 16:00:00,13953.0 -2011-08-30 17:00:00,14001.0 -2011-08-30 18:00:00,13875.0 -2011-08-30 19:00:00,13571.0 -2011-08-30 20:00:00,13320.0 -2011-08-30 21:00:00,13549.0 -2011-08-30 22:00:00,13615.0 -2011-08-30 23:00:00,12992.0 -2011-08-31 00:00:00,11980.0 -2011-08-29 01:00:00,10043.0 -2011-08-29 02:00:00,9501.0 -2011-08-29 03:00:00,9115.0 -2011-08-29 04:00:00,8875.0 -2011-08-29 05:00:00,8799.0 -2011-08-29 06:00:00,8994.0 -2011-08-29 07:00:00,9668.0 -2011-08-29 08:00:00,10588.0 -2011-08-29 09:00:00,11579.0 -2011-08-29 10:00:00,12433.0 -2011-08-29 11:00:00,13052.0 -2011-08-29 12:00:00,13628.0 -2011-08-29 13:00:00,13952.0 -2011-08-29 14:00:00,14223.0 -2011-08-29 15:00:00,14558.0 -2011-08-29 16:00:00,14818.0 -2011-08-29 17:00:00,15033.0 -2011-08-29 18:00:00,15071.0 -2011-08-29 19:00:00,14859.0 -2011-08-29 20:00:00,14306.0 -2011-08-29 21:00:00,14047.0 -2011-08-29 22:00:00,14002.0 -2011-08-29 23:00:00,13222.0 -2011-08-30 00:00:00,12066.0 -2011-08-28 01:00:00,10578.0 -2011-08-28 02:00:00,9878.0 -2011-08-28 03:00:00,9381.0 -2011-08-28 04:00:00,8920.0 -2011-08-28 05:00:00,8770.0 -2011-08-28 06:00:00,8641.0 -2011-08-28 07:00:00,8682.0 -2011-08-28 08:00:00,8539.0 -2011-08-28 09:00:00,8872.0 -2011-08-28 10:00:00,9575.0 -2011-08-28 11:00:00,10260.0 -2011-08-28 12:00:00,10854.0 -2011-08-28 13:00:00,11223.0 -2011-08-28 14:00:00,11558.0 -2011-08-28 15:00:00,11792.0 -2011-08-28 16:00:00,12032.0 -2011-08-28 17:00:00,12129.0 -2011-08-28 18:00:00,12139.0 -2011-08-28 19:00:00,11924.0 -2011-08-28 20:00:00,11679.0 -2011-08-28 21:00:00,11726.0 -2011-08-28 22:00:00,11991.0 -2011-08-28 23:00:00,11585.0 -2011-08-29 00:00:00,10819.0 -2011-08-27 01:00:00,11769.0 -2011-08-27 02:00:00,10975.0 -2011-08-27 03:00:00,10383.0 -2011-08-27 04:00:00,9915.0 -2011-08-27 05:00:00,9733.0 -2011-08-27 06:00:00,9638.0 -2011-08-27 07:00:00,9810.0 -2011-08-27 08:00:00,9955.0 -2011-08-27 09:00:00,10663.0 -2011-08-27 10:00:00,11749.0 -2011-08-27 11:00:00,12858.0 -2011-08-27 12:00:00,13801.0 -2011-08-27 13:00:00,14423.0 -2011-08-27 14:00:00,14898.0 -2011-08-27 15:00:00,15026.0 -2011-08-27 16:00:00,15119.0 -2011-08-27 17:00:00,15062.0 -2011-08-27 18:00:00,14896.0 -2011-08-27 19:00:00,14442.0 -2011-08-27 20:00:00,13608.0 -2011-08-27 21:00:00,12989.0 -2011-08-27 22:00:00,12848.0 -2011-08-27 23:00:00,12273.0 -2011-08-28 00:00:00,11421.0 -2011-08-26 01:00:00,11554.0 -2011-08-26 02:00:00,10694.0 -2011-08-26 03:00:00,10059.0 -2011-08-26 04:00:00,9680.0 -2011-08-26 05:00:00,9460.0 -2011-08-26 06:00:00,9585.0 -2011-08-26 07:00:00,10102.0 -2011-08-26 08:00:00,10914.0 -2011-08-26 09:00:00,11832.0 -2011-08-26 10:00:00,12703.0 -2011-08-26 11:00:00,13393.0 -2011-08-26 12:00:00,14027.0 -2011-08-26 13:00:00,14490.0 -2011-08-26 14:00:00,14949.0 -2011-08-26 15:00:00,15461.0 -2011-08-26 16:00:00,15815.0 -2011-08-26 17:00:00,15863.0 -2011-08-26 18:00:00,15634.0 -2011-08-26 19:00:00,15223.0 -2011-08-26 20:00:00,14569.0 -2011-08-26 21:00:00,14264.0 -2011-08-26 22:00:00,14318.0 -2011-08-26 23:00:00,13745.0 -2011-08-27 00:00:00,12799.0 -2011-08-25 01:00:00,12677.0 -2011-08-25 02:00:00,11541.0 -2011-08-25 03:00:00,10758.0 -2011-08-25 04:00:00,10231.0 -2011-08-25 05:00:00,9968.0 -2011-08-25 06:00:00,10018.0 -2011-08-25 07:00:00,10568.0 -2011-08-25 08:00:00,11412.0 -2011-08-25 09:00:00,12361.0 -2011-08-25 10:00:00,13141.0 -2011-08-25 11:00:00,13843.0 -2011-08-25 12:00:00,14538.0 -2011-08-25 13:00:00,14984.0 -2011-08-25 14:00:00,15411.0 -2011-08-25 15:00:00,15882.0 -2011-08-25 16:00:00,16235.0 -2011-08-25 17:00:00,16534.0 -2011-08-25 18:00:00,16676.0 -2011-08-25 19:00:00,16392.0 -2011-08-25 20:00:00,15578.0 -2011-08-25 21:00:00,14944.0 -2011-08-25 22:00:00,14839.0 -2011-08-25 23:00:00,13987.0 -2011-08-26 00:00:00,12745.0 -2011-08-24 01:00:00,11252.0 -2011-08-24 02:00:00,10598.0 -2011-08-24 03:00:00,10154.0 -2011-08-24 04:00:00,9880.0 -2011-08-24 05:00:00,9774.0 -2011-08-24 06:00:00,9988.0 -2011-08-24 07:00:00,10719.0 -2011-08-24 08:00:00,11838.0 -2011-08-24 09:00:00,13031.0 -2011-08-24 10:00:00,14017.0 -2011-08-24 11:00:00,15213.0 -2011-08-24 12:00:00,16452.0 -2011-08-24 13:00:00,17490.0 -2011-08-24 14:00:00,18425.0 -2011-08-24 15:00:00,19367.0 -2011-08-24 16:00:00,20080.0 -2011-08-24 17:00:00,20495.0 -2011-08-24 18:00:00,20515.0 -2011-08-24 19:00:00,19966.0 -2011-08-24 20:00:00,18971.0 -2011-08-24 21:00:00,18029.0 -2011-08-24 22:00:00,17394.0 -2011-08-24 23:00:00,16033.0 -2011-08-25 00:00:00,14285.0 -2011-08-23 01:00:00,11787.0 -2011-08-23 02:00:00,10838.0 -2011-08-23 03:00:00,10205.0 -2011-08-23 04:00:00,9791.0 -2011-08-23 05:00:00,9568.0 -2011-08-23 06:00:00,9703.0 -2011-08-23 07:00:00,10304.0 -2011-08-23 08:00:00,11185.0 -2011-08-23 09:00:00,12069.0 -2011-08-23 10:00:00,12737.0 -2011-08-23 11:00:00,13219.0 -2011-08-23 12:00:00,13586.0 -2011-08-23 13:00:00,13637.0 -2011-08-23 14:00:00,13494.0 -2011-08-23 15:00:00,13480.0 -2011-08-23 16:00:00,13513.0 -2011-08-23 17:00:00,13500.0 -2011-08-23 18:00:00,13660.0 -2011-08-23 19:00:00,13788.0 -2011-08-23 20:00:00,13463.0 -2011-08-23 21:00:00,13456.0 -2011-08-23 22:00:00,13678.0 -2011-08-23 23:00:00,13191.0 -2011-08-24 00:00:00,12237.0 -2011-08-22 01:00:00,10750.0 -2011-08-22 02:00:00,10022.0 -2011-08-22 03:00:00,9529.0 -2011-08-22 04:00:00,9221.0 -2011-08-22 05:00:00,9128.0 -2011-08-22 06:00:00,9306.0 -2011-08-22 07:00:00,9934.0 -2011-08-22 08:00:00,10913.0 -2011-08-22 09:00:00,11862.0 -2011-08-22 10:00:00,12797.0 -2011-08-22 11:00:00,13518.0 -2011-08-22 12:00:00,14138.0 -2011-08-22 13:00:00,14604.0 -2011-08-22 14:00:00,15122.0 -2011-08-22 15:00:00,15565.0 -2011-08-22 16:00:00,15860.0 -2011-08-22 17:00:00,15991.0 -2011-08-22 18:00:00,16129.0 -2011-08-22 19:00:00,15977.0 -2011-08-22 20:00:00,15422.0 -2011-08-22 21:00:00,14964.0 -2011-08-22 22:00:00,14931.0 -2011-08-22 23:00:00,14136.0 -2011-08-23 00:00:00,12970.0 -2011-08-21 01:00:00,11719.0 -2011-08-21 02:00:00,10975.0 -2011-08-21 03:00:00,10319.0 -2011-08-21 04:00:00,9910.0 -2011-08-21 05:00:00,9650.0 -2011-08-21 06:00:00,9416.0 -2011-08-21 07:00:00,9367.0 -2011-08-21 08:00:00,9214.0 -2011-08-21 09:00:00,9559.0 -2011-08-21 10:00:00,10240.0 -2011-08-21 11:00:00,11087.0 -2011-08-21 12:00:00,11862.0 -2011-08-21 13:00:00,12485.0 -2011-08-21 14:00:00,12916.0 -2011-08-21 15:00:00,13219.0 -2011-08-21 16:00:00,13514.0 -2011-08-21 17:00:00,13745.0 -2011-08-21 18:00:00,13891.0 -2011-08-21 19:00:00,13692.0 -2011-08-21 20:00:00,13302.0 -2011-08-21 21:00:00,12903.0 -2011-08-21 22:00:00,13035.0 -2011-08-21 23:00:00,12573.0 -2011-08-22 00:00:00,11707.0 -2011-08-20 01:00:00,13582.0 -2011-08-20 02:00:00,12579.0 -2011-08-20 03:00:00,11838.0 -2011-08-20 04:00:00,11269.0 -2011-08-20 05:00:00,10911.0 -2011-08-20 06:00:00,10786.0 -2011-08-20 07:00:00,10931.0 -2011-08-20 08:00:00,11088.0 -2011-08-20 09:00:00,11564.0 -2011-08-20 10:00:00,12343.0 -2011-08-20 11:00:00,12902.0 -2011-08-20 12:00:00,13354.0 -2011-08-20 13:00:00,13175.0 -2011-08-20 14:00:00,13021.0 -2011-08-20 15:00:00,12963.0 -2011-08-20 16:00:00,13379.0 -2011-08-20 17:00:00,13832.0 -2011-08-20 18:00:00,14164.0 -2011-08-20 19:00:00,14251.0 -2011-08-20 20:00:00,13906.0 -2011-08-20 21:00:00,13569.0 -2011-08-20 22:00:00,13711.0 -2011-08-20 23:00:00,13251.0 -2011-08-21 00:00:00,12559.0 -2011-08-19 01:00:00,12340.0 -2011-08-19 02:00:00,11442.0 -2011-08-19 03:00:00,10762.0 -2011-08-19 04:00:00,10350.0 -2011-08-19 05:00:00,10130.0 -2011-08-19 06:00:00,10194.0 -2011-08-19 07:00:00,10697.0 -2011-08-19 08:00:00,11498.0 -2011-08-19 09:00:00,12416.0 -2011-08-19 10:00:00,13339.0 -2011-08-19 11:00:00,14287.0 -2011-08-19 12:00:00,15312.0 -2011-08-19 13:00:00,16205.0 -2011-08-19 14:00:00,16963.0 -2011-08-19 15:00:00,17590.0 -2011-08-19 16:00:00,18023.0 -2011-08-19 17:00:00,18344.0 -2011-08-19 18:00:00,18391.0 -2011-08-19 19:00:00,18122.0 -2011-08-19 20:00:00,17444.0 -2011-08-19 21:00:00,16840.0 -2011-08-19 22:00:00,16684.0 -2011-08-19 23:00:00,15947.0 -2011-08-20 00:00:00,14831.0 -2011-08-18 01:00:00,13137.0 -2011-08-18 02:00:00,12169.0 -2011-08-18 03:00:00,11543.0 -2011-08-18 04:00:00,11003.0 -2011-08-18 05:00:00,10733.0 -2011-08-18 06:00:00,10772.0 -2011-08-18 07:00:00,11289.0 -2011-08-18 08:00:00,12002.0 -2011-08-18 09:00:00,13061.0 -2011-08-18 10:00:00,14060.0 -2011-08-18 11:00:00,14924.0 -2011-08-18 12:00:00,15843.0 -2011-08-18 13:00:00,16430.0 -2011-08-18 14:00:00,16961.0 -2011-08-18 15:00:00,17513.0 -2011-08-18 16:00:00,17615.0 -2011-08-18 17:00:00,17609.0 -2011-08-18 18:00:00,17455.0 -2011-08-18 19:00:00,17221.0 -2011-08-18 20:00:00,16544.0 -2011-08-18 21:00:00,15848.0 -2011-08-18 22:00:00,15716.0 -2011-08-18 23:00:00,14857.0 -2011-08-19 00:00:00,13561.0 -2011-08-17 01:00:00,11885.0 -2011-08-17 02:00:00,10983.0 -2011-08-17 03:00:00,10345.0 -2011-08-17 04:00:00,9952.0 -2011-08-17 05:00:00,9734.0 -2011-08-17 06:00:00,9850.0 -2011-08-17 07:00:00,10459.0 -2011-08-17 08:00:00,11233.0 -2011-08-17 09:00:00,12099.0 -2011-08-17 10:00:00,13037.0 -2011-08-17 11:00:00,13936.0 -2011-08-17 12:00:00,14773.0 -2011-08-17 13:00:00,15377.0 -2011-08-17 14:00:00,15696.0 -2011-08-17 15:00:00,16097.0 -2011-08-17 16:00:00,16592.0 -2011-08-17 17:00:00,17113.0 -2011-08-17 18:00:00,17117.0 -2011-08-17 19:00:00,16834.0 -2011-08-17 20:00:00,16373.0 -2011-08-17 21:00:00,16066.0 -2011-08-17 22:00:00,16208.0 -2011-08-17 23:00:00,15609.0 -2011-08-18 00:00:00,14397.0 -2011-08-16 01:00:00,11249.0 -2011-08-16 02:00:00,10451.0 -2011-08-16 03:00:00,9933.0 -2011-08-16 04:00:00,9541.0 -2011-08-16 05:00:00,9388.0 -2011-08-16 06:00:00,9522.0 -2011-08-16 07:00:00,10082.0 -2011-08-16 08:00:00,10834.0 -2011-08-16 09:00:00,11830.0 -2011-08-16 10:00:00,12837.0 -2011-08-16 11:00:00,13659.0 -2011-08-16 12:00:00,14405.0 -2011-08-16 13:00:00,14931.0 -2011-08-16 14:00:00,15439.0 -2011-08-16 15:00:00,15894.0 -2011-08-16 16:00:00,16170.0 -2011-08-16 17:00:00,16376.0 -2011-08-16 18:00:00,16451.0 -2011-08-16 19:00:00,16230.0 -2011-08-16 20:00:00,15631.0 -2011-08-16 21:00:00,15037.0 -2011-08-16 22:00:00,14972.0 -2011-08-16 23:00:00,14315.0 -2011-08-17 00:00:00,13102.0 -2011-08-15 01:00:00,10033.0 -2011-08-15 02:00:00,9460.0 -2011-08-15 03:00:00,9032.0 -2011-08-15 04:00:00,8832.0 -2011-08-15 05:00:00,8768.0 -2011-08-15 06:00:00,8968.0 -2011-08-15 07:00:00,9542.0 -2011-08-15 08:00:00,10352.0 -2011-08-15 09:00:00,11362.0 -2011-08-15 10:00:00,12210.0 -2011-08-15 11:00:00,12969.0 -2011-08-15 12:00:00,13568.0 -2011-08-15 13:00:00,13969.0 -2011-08-15 14:00:00,14322.0 -2011-08-15 15:00:00,14696.0 -2011-08-15 16:00:00,14954.0 -2011-08-15 17:00:00,15134.0 -2011-08-15 18:00:00,15199.0 -2011-08-15 19:00:00,15057.0 -2011-08-15 20:00:00,14560.0 -2011-08-15 21:00:00,14065.0 -2011-08-15 22:00:00,14032.0 -2011-08-15 23:00:00,13380.0 -2011-08-16 00:00:00,12330.0 -2011-08-14 01:00:00,10680.0 -2011-08-14 02:00:00,9976.0 -2011-08-14 03:00:00,9531.0 -2011-08-14 04:00:00,9130.0 -2011-08-14 05:00:00,8955.0 -2011-08-14 06:00:00,8806.0 -2011-08-14 07:00:00,8865.0 -2011-08-14 08:00:00,8735.0 -2011-08-14 09:00:00,9019.0 -2011-08-14 10:00:00,9569.0 -2011-08-14 11:00:00,10123.0 -2011-08-14 12:00:00,10499.0 -2011-08-14 13:00:00,10758.0 -2011-08-14 14:00:00,10860.0 -2011-08-14 15:00:00,11029.0 -2011-08-14 16:00:00,11205.0 -2011-08-14 17:00:00,11447.0 -2011-08-14 18:00:00,11631.0 -2011-08-14 19:00:00,11729.0 -2011-08-14 20:00:00,11552.0 -2011-08-14 21:00:00,11397.0 -2011-08-14 22:00:00,11661.0 -2011-08-14 23:00:00,11436.0 -2011-08-15 00:00:00,10799.0 -2011-08-13 01:00:00,12027.0 -2011-08-13 02:00:00,11194.0 -2011-08-13 03:00:00,10671.0 -2011-08-13 04:00:00,10211.0 -2011-08-13 05:00:00,9993.0 -2011-08-13 06:00:00,9895.0 -2011-08-13 07:00:00,10053.0 -2011-08-13 08:00:00,10150.0 -2011-08-13 09:00:00,10680.0 -2011-08-13 10:00:00,11630.0 -2011-08-13 11:00:00,12761.0 -2011-08-13 12:00:00,13887.0 -2011-08-13 13:00:00,14579.0 -2011-08-13 14:00:00,15010.0 -2011-08-13 15:00:00,14668.0 -2011-08-13 16:00:00,13874.0 -2011-08-13 17:00:00,13447.0 -2011-08-13 18:00:00,13084.0 -2011-08-13 19:00:00,12684.0 -2011-08-13 20:00:00,12298.0 -2011-08-13 21:00:00,12142.0 -2011-08-13 22:00:00,12321.0 -2011-08-13 23:00:00,12052.0 -2011-08-14 00:00:00,11414.0 -2011-08-12 01:00:00,11619.0 -2011-08-12 02:00:00,10761.0 -2011-08-12 03:00:00,10179.0 -2011-08-12 04:00:00,9767.0 -2011-08-12 05:00:00,9544.0 -2011-08-12 06:00:00,9631.0 -2011-08-12 07:00:00,10180.0 -2011-08-12 08:00:00,10834.0 -2011-08-12 09:00:00,11790.0 -2011-08-12 10:00:00,12663.0 -2011-08-12 11:00:00,13378.0 -2011-08-12 12:00:00,13945.0 -2011-08-12 13:00:00,14417.0 -2011-08-12 14:00:00,14745.0 -2011-08-12 15:00:00,15090.0 -2011-08-12 16:00:00,15393.0 -2011-08-12 17:00:00,15579.0 -2011-08-12 18:00:00,15490.0 -2011-08-12 19:00:00,15190.0 -2011-08-12 20:00:00,14623.0 -2011-08-12 21:00:00,14270.0 -2011-08-12 22:00:00,14342.0 -2011-08-12 23:00:00,13897.0 -2011-08-13 00:00:00,12936.0 -2011-08-11 01:00:00,10965.0 -2011-08-11 02:00:00,10251.0 -2011-08-11 03:00:00,9752.0 -2011-08-11 04:00:00,9406.0 -2011-08-11 05:00:00,9287.0 -2011-08-11 06:00:00,9402.0 -2011-08-11 07:00:00,9887.0 -2011-08-11 08:00:00,10533.0 -2011-08-11 09:00:00,11546.0 -2011-08-11 10:00:00,12420.0 -2011-08-11 11:00:00,13038.0 -2011-08-11 12:00:00,13615.0 -2011-08-11 13:00:00,14042.0 -2011-08-11 14:00:00,14411.0 -2011-08-11 15:00:00,14814.0 -2011-08-11 16:00:00,15111.0 -2011-08-11 17:00:00,15325.0 -2011-08-11 18:00:00,15399.0 -2011-08-11 19:00:00,15256.0 -2011-08-11 20:00:00,14734.0 -2011-08-11 21:00:00,14237.0 -2011-08-11 22:00:00,14145.0 -2011-08-11 23:00:00,13697.0 -2011-08-12 00:00:00,12660.0 -2011-08-10 01:00:00,11932.0 -2011-08-10 02:00:00,11050.0 -2011-08-10 03:00:00,10373.0 -2011-08-10 04:00:00,9936.0 -2011-08-10 05:00:00,9684.0 -2011-08-10 06:00:00,9757.0 -2011-08-10 07:00:00,10235.0 -2011-08-10 08:00:00,10911.0 -2011-08-10 09:00:00,11867.0 -2011-08-10 10:00:00,12658.0 -2011-08-10 11:00:00,13238.0 -2011-08-10 12:00:00,13694.0 -2011-08-10 13:00:00,13967.0 -2011-08-10 14:00:00,14284.0 -2011-08-10 15:00:00,14560.0 -2011-08-10 16:00:00,14694.0 -2011-08-10 17:00:00,14697.0 -2011-08-10 18:00:00,14643.0 -2011-08-10 19:00:00,14397.0 -2011-08-10 20:00:00,13834.0 -2011-08-10 21:00:00,13345.0 -2011-08-10 22:00:00,13453.0 -2011-08-10 23:00:00,12977.0 -2011-08-11 00:00:00,12012.0 -2011-08-09 01:00:00,12158.0 -2011-08-09 02:00:00,11335.0 -2011-08-09 03:00:00,10707.0 -2011-08-09 04:00:00,10283.0 -2011-08-09 05:00:00,10082.0 -2011-08-09 06:00:00,10194.0 -2011-08-09 07:00:00,10717.0 -2011-08-09 08:00:00,11480.0 -2011-08-09 09:00:00,12541.0 -2011-08-09 10:00:00,13560.0 -2011-08-09 11:00:00,14480.0 -2011-08-09 12:00:00,15395.0 -2011-08-09 13:00:00,16007.0 -2011-08-09 14:00:00,16354.0 -2011-08-09 15:00:00,16496.0 -2011-08-09 16:00:00,16481.0 -2011-08-09 17:00:00,16507.0 -2011-08-09 18:00:00,16416.0 -2011-08-09 19:00:00,16052.0 -2011-08-09 20:00:00,15402.0 -2011-08-09 21:00:00,14702.0 -2011-08-09 22:00:00,14717.0 -2011-08-09 23:00:00,14196.0 -2011-08-10 00:00:00,13112.0 -2011-08-08 01:00:00,12546.0 -2011-08-08 02:00:00,11742.0 -2011-08-08 03:00:00,11111.0 -2011-08-08 04:00:00,10727.0 -2011-08-08 05:00:00,10515.0 -2011-08-08 06:00:00,10701.0 -2011-08-08 07:00:00,11220.0 -2011-08-08 08:00:00,12092.0 -2011-08-08 09:00:00,13273.0 -2011-08-08 10:00:00,14426.0 -2011-08-08 11:00:00,15204.0 -2011-08-08 12:00:00,16003.0 -2011-08-08 13:00:00,16665.0 -2011-08-08 14:00:00,17144.0 -2011-08-08 15:00:00,17590.0 -2011-08-08 16:00:00,17640.0 -2011-08-08 17:00:00,16955.0 -2011-08-08 18:00:00,16355.0 -2011-08-08 19:00:00,15765.0 -2011-08-08 20:00:00,15161.0 -2011-08-08 21:00:00,14749.0 -2011-08-08 22:00:00,14693.0 -2011-08-08 23:00:00,14194.0 -2011-08-09 00:00:00,13246.0 -2011-08-07 01:00:00,13210.0 -2011-08-07 02:00:00,12314.0 -2011-08-07 03:00:00,11624.0 -2011-08-07 04:00:00,10994.0 -2011-08-07 05:00:00,10726.0 -2011-08-07 06:00:00,10620.0 -2011-08-07 07:00:00,10565.0 -2011-08-07 08:00:00,10522.0 -2011-08-07 09:00:00,10842.0 -2011-08-07 10:00:00,11532.0 -2011-08-07 11:00:00,12254.0 -2011-08-07 12:00:00,12725.0 -2011-08-07 13:00:00,13745.0 -2011-08-07 14:00:00,14798.0 -2011-08-07 15:00:00,15700.0 -2011-08-07 16:00:00,16469.0 -2011-08-07 17:00:00,16957.0 -2011-08-07 18:00:00,16982.0 -2011-08-07 19:00:00,16387.0 -2011-08-07 20:00:00,15574.0 -2011-08-07 21:00:00,14943.0 -2011-08-07 22:00:00,14874.0 -2011-08-07 23:00:00,14436.0 -2011-08-08 00:00:00,13581.0 -2011-08-06 01:00:00,13984.0 -2011-08-06 02:00:00,13085.0 -2011-08-06 03:00:00,12380.0 -2011-08-06 04:00:00,11879.0 -2011-08-06 05:00:00,11474.0 -2011-08-06 06:00:00,11420.0 -2011-08-06 07:00:00,11519.0 -2011-08-06 08:00:00,11765.0 -2011-08-06 09:00:00,12207.0 -2011-08-06 10:00:00,12961.0 -2011-08-06 11:00:00,13674.0 -2011-08-06 12:00:00,14440.0 -2011-08-06 13:00:00,15397.0 -2011-08-06 14:00:00,16165.0 -2011-08-06 15:00:00,16423.0 -2011-08-06 16:00:00,16605.0 -2011-08-06 17:00:00,16718.0 -2011-08-06 18:00:00,16732.0 -2011-08-06 19:00:00,16609.0 -2011-08-06 20:00:00,16250.0 -2011-08-06 21:00:00,15742.0 -2011-08-06 22:00:00,15554.0 -2011-08-06 23:00:00,15122.0 -2011-08-07 00:00:00,14181.0 -2011-08-05 01:00:00,13331.0 -2011-08-05 02:00:00,12357.0 -2011-08-05 03:00:00,11686.0 -2011-08-05 04:00:00,11180.0 -2011-08-05 05:00:00,10936.0 -2011-08-05 06:00:00,11028.0 -2011-08-05 07:00:00,11523.0 -2011-08-05 08:00:00,12297.0 -2011-08-05 09:00:00,13484.0 -2011-08-05 10:00:00,14533.0 -2011-08-05 11:00:00,15370.0 -2011-08-05 12:00:00,16281.0 -2011-08-05 13:00:00,17039.0 -2011-08-05 14:00:00,17645.0 -2011-08-05 15:00:00,18013.0 -2011-08-05 16:00:00,18099.0 -2011-08-05 17:00:00,18216.0 -2011-08-05 18:00:00,18302.0 -2011-08-05 19:00:00,18025.0 -2011-08-05 20:00:00,17173.0 -2011-08-05 21:00:00,16493.0 -2011-08-05 22:00:00,16394.0 -2011-08-05 23:00:00,15959.0 -2011-08-06 00:00:00,14935.0 -2011-08-04 01:00:00,13615.0 -2011-08-04 02:00:00,12555.0 -2011-08-04 03:00:00,11826.0 -2011-08-04 04:00:00,11295.0 -2011-08-04 05:00:00,11018.0 -2011-08-04 06:00:00,11066.0 -2011-08-04 07:00:00,11573.0 -2011-08-04 08:00:00,12329.0 -2011-08-04 09:00:00,13520.0 -2011-08-04 10:00:00,14551.0 -2011-08-04 11:00:00,15317.0 -2011-08-04 12:00:00,16029.0 -2011-08-04 13:00:00,16725.0 -2011-08-04 14:00:00,17427.0 -2011-08-04 15:00:00,18059.0 -2011-08-04 16:00:00,18380.0 -2011-08-04 17:00:00,18588.0 -2011-08-04 18:00:00,18618.0 -2011-08-04 19:00:00,18317.0 -2011-08-04 20:00:00,17581.0 -2011-08-04 21:00:00,16686.0 -2011-08-04 22:00:00,16378.0 -2011-08-04 23:00:00,15770.0 -2011-08-05 00:00:00,14573.0 -2011-08-03 01:00:00,15308.0 -2011-08-03 02:00:00,14163.0 -2011-08-03 03:00:00,13418.0 -2011-08-03 04:00:00,12948.0 -2011-08-03 05:00:00,12688.0 -2011-08-03 06:00:00,12806.0 -2011-08-03 07:00:00,13355.0 -2011-08-03 08:00:00,14117.0 -2011-08-03 09:00:00,15069.0 -2011-08-03 10:00:00,15874.0 -2011-08-03 11:00:00,16489.0 -2011-08-03 12:00:00,17380.0 -2011-08-03 13:00:00,18212.0 -2011-08-03 14:00:00,18821.0 -2011-08-03 15:00:00,19354.0 -2011-08-03 16:00:00,19615.0 -2011-08-03 17:00:00,19670.0 -2011-08-03 18:00:00,19530.0 -2011-08-03 19:00:00,19157.0 -2011-08-03 20:00:00,18430.0 -2011-08-03 21:00:00,17499.0 -2011-08-03 22:00:00,17036.0 -2011-08-03 23:00:00,16343.0 -2011-08-04 00:00:00,14974.0 -2011-08-02 01:00:00,15873.0 -2011-08-02 02:00:00,14789.0 -2011-08-02 03:00:00,14048.0 -2011-08-02 04:00:00,13524.0 -2011-08-02 05:00:00,13224.0 -2011-08-02 06:00:00,13311.0 -2011-08-02 07:00:00,13844.0 -2011-08-02 08:00:00,14674.0 -2011-08-02 09:00:00,15751.0 -2011-08-02 10:00:00,16954.0 -2011-08-02 11:00:00,18132.0 -2011-08-02 12:00:00,19191.0 -2011-08-02 13:00:00,20073.0 -2011-08-02 14:00:00,20807.0 -2011-08-02 15:00:00,21481.0 -2011-08-02 16:00:00,22044.0 -2011-08-02 17:00:00,22409.0 -2011-08-02 18:00:00,22576.0 -2011-08-02 19:00:00,22343.0 -2011-08-02 20:00:00,21567.0 -2011-08-02 21:00:00,20767.0 -2011-08-02 22:00:00,20244.0 -2011-08-02 23:00:00,19164.0 -2011-08-03 00:00:00,16834.0 -2011-08-01 01:00:00,15079.0 -2011-08-01 02:00:00,14026.0 -2011-08-01 03:00:00,13313.0 -2011-08-01 04:00:00,12742.0 -2011-08-01 05:00:00,12432.0 -2011-08-01 06:00:00,12471.0 -2011-08-01 07:00:00,12912.0 -2011-08-01 08:00:00,13791.0 -2011-08-01 09:00:00,15173.0 -2011-08-01 10:00:00,16563.0 -2011-08-01 11:00:00,17876.0 -2011-08-01 12:00:00,19019.0 -2011-08-01 13:00:00,19779.0 -2011-08-01 14:00:00,20515.0 -2011-08-01 15:00:00,21091.0 -2011-08-01 16:00:00,21435.0 -2011-08-01 17:00:00,21472.0 -2011-08-01 18:00:00,21053.0 -2011-08-01 19:00:00,20550.0 -2011-08-01 20:00:00,19977.0 -2011-08-01 21:00:00,19413.0 -2011-08-01 22:00:00,19335.0 -2011-08-01 23:00:00,18632.0 -2011-08-02 00:00:00,17213.0 -2011-07-31 01:00:00,13847.0 -2011-07-31 02:00:00,12792.0 -2011-07-31 03:00:00,12031.0 -2011-07-31 04:00:00,11352.0 -2011-07-31 05:00:00,10954.0 -2011-07-31 06:00:00,10647.0 -2011-07-31 07:00:00,10449.0 -2011-07-31 08:00:00,10363.0 -2011-07-31 09:00:00,11235.0 -2011-07-31 10:00:00,12510.0 -2011-07-31 11:00:00,13948.0 -2011-07-31 12:00:00,15271.0 -2011-07-31 13:00:00,16413.0 -2011-07-31 14:00:00,17189.0 -2011-07-31 15:00:00,17862.0 -2011-07-31 16:00:00,18285.0 -2011-07-31 17:00:00,18606.0 -2011-07-31 18:00:00,18876.0 -2011-07-31 19:00:00,18945.0 -2011-07-31 20:00:00,18653.0 -2011-07-31 21:00:00,18116.0 -2011-07-31 22:00:00,17853.0 -2011-07-31 23:00:00,17460.0 -2011-08-01 00:00:00,16342.0 -2011-07-30 01:00:00,13493.0 -2011-07-30 02:00:00,12434.0 -2011-07-30 03:00:00,11647.0 -2011-07-30 04:00:00,11078.0 -2011-07-30 05:00:00,10665.0 -2011-07-30 06:00:00,10543.0 -2011-07-30 07:00:00,10537.0 -2011-07-30 08:00:00,10771.0 -2011-07-30 09:00:00,11693.0 -2011-07-30 10:00:00,12909.0 -2011-07-30 11:00:00,14077.0 -2011-07-30 12:00:00,15248.0 -2011-07-30 13:00:00,16086.0 -2011-07-30 14:00:00,16769.0 -2011-07-30 15:00:00,17180.0 -2011-07-30 16:00:00,17615.0 -2011-07-30 17:00:00,17960.0 -2011-07-30 18:00:00,18202.0 -2011-07-30 19:00:00,18154.0 -2011-07-30 20:00:00,17710.0 -2011-07-30 21:00:00,17005.0 -2011-07-30 22:00:00,16497.0 -2011-07-30 23:00:00,15994.0 -2011-07-31 00:00:00,14933.0 -2011-07-29 01:00:00,14641.0 -2011-07-29 02:00:00,13644.0 -2011-07-29 03:00:00,12869.0 -2011-07-29 04:00:00,12303.0 -2011-07-29 05:00:00,11991.0 -2011-07-29 06:00:00,12055.0 -2011-07-29 07:00:00,12547.0 -2011-07-29 08:00:00,13314.0 -2011-07-29 09:00:00,14220.0 -2011-07-29 10:00:00,15126.0 -2011-07-29 11:00:00,15752.0 -2011-07-29 12:00:00,16337.0 -2011-07-29 13:00:00,16863.0 -2011-07-29 14:00:00,17323.0 -2011-07-29 15:00:00,17789.0 -2011-07-29 16:00:00,18273.0 -2011-07-29 17:00:00,18811.0 -2011-07-29 18:00:00,19052.0 -2011-07-29 19:00:00,18840.0 -2011-07-29 20:00:00,18187.0 -2011-07-29 21:00:00,17160.0 -2011-07-29 22:00:00,16613.0 -2011-07-29 23:00:00,16004.0 -2011-07-30 00:00:00,14791.0 -2011-07-28 01:00:00,15063.0 -2011-07-28 02:00:00,13652.0 -2011-07-28 03:00:00,12608.0 -2011-07-28 04:00:00,12084.0 -2011-07-28 05:00:00,11778.0 -2011-07-28 06:00:00,11838.0 -2011-07-28 07:00:00,12433.0 -2011-07-28 08:00:00,13355.0 -2011-07-28 09:00:00,14200.0 -2011-07-28 10:00:00,14835.0 -2011-07-28 11:00:00,15349.0 -2011-07-28 12:00:00,15875.0 -2011-07-28 13:00:00,16129.0 -2011-07-28 14:00:00,16297.0 -2011-07-28 15:00:00,16572.0 -2011-07-28 16:00:00,17187.0 -2011-07-28 17:00:00,17833.0 -2011-07-28 18:00:00,18313.0 -2011-07-28 19:00:00,18327.0 -2011-07-28 20:00:00,17969.0 -2011-07-28 21:00:00,17521.0 -2011-07-28 22:00:00,17517.0 -2011-07-28 23:00:00,17052.0 -2011-07-29 00:00:00,15874.0 -2011-07-27 01:00:00,12864.0 -2011-07-27 02:00:00,11973.0 -2011-07-27 03:00:00,11361.0 -2011-07-27 04:00:00,10941.0 -2011-07-27 05:00:00,10740.0 -2011-07-27 06:00:00,10851.0 -2011-07-27 07:00:00,11325.0 -2011-07-27 08:00:00,12156.0 -2011-07-27 09:00:00,12998.0 -2011-07-27 10:00:00,13579.0 -2011-07-27 11:00:00,14088.0 -2011-07-27 12:00:00,14476.0 -2011-07-27 13:00:00,14820.0 -2011-07-27 14:00:00,15703.0 -2011-07-27 15:00:00,16553.0 -2011-07-27 16:00:00,17401.0 -2011-07-27 17:00:00,18288.0 -2011-07-27 18:00:00,18825.0 -2011-07-27 19:00:00,19011.0 -2011-07-27 20:00:00,18789.0 -2011-07-27 21:00:00,18406.0 -2011-07-27 22:00:00,18540.0 -2011-07-27 23:00:00,18036.0 -2011-07-28 00:00:00,16762.0 -2011-07-26 01:00:00,13991.0 -2011-07-26 02:00:00,12857.0 -2011-07-26 03:00:00,12052.0 -2011-07-26 04:00:00,11519.0 -2011-07-26 05:00:00,11266.0 -2011-07-26 06:00:00,11345.0 -2011-07-26 07:00:00,11701.0 -2011-07-26 08:00:00,12558.0 -2011-07-26 09:00:00,13990.0 -2011-07-26 10:00:00,15207.0 -2011-07-26 11:00:00,16277.0 -2011-07-26 12:00:00,17276.0 -2011-07-26 13:00:00,17982.0 -2011-07-26 14:00:00,18422.0 -2011-07-26 15:00:00,18832.0 -2011-07-26 16:00:00,18992.0 -2011-07-26 17:00:00,19041.0 -2011-07-26 18:00:00,18900.0 -2011-07-26 19:00:00,18432.0 -2011-07-26 20:00:00,17491.0 -2011-07-26 21:00:00,16472.0 -2011-07-26 22:00:00,15843.0 -2011-07-26 23:00:00,15308.0 -2011-07-27 00:00:00,14094.0 -2011-07-25 01:00:00,13275.0 -2011-07-25 02:00:00,12481.0 -2011-07-25 03:00:00,11884.0 -2011-07-25 04:00:00,11437.0 -2011-07-25 05:00:00,11293.0 -2011-07-25 06:00:00,11455.0 -2011-07-25 07:00:00,11955.0 -2011-07-25 08:00:00,12888.0 -2011-07-25 09:00:00,14144.0 -2011-07-25 10:00:00,15276.0 -2011-07-25 11:00:00,16117.0 -2011-07-25 12:00:00,16954.0 -2011-07-25 13:00:00,17529.0 -2011-07-25 14:00:00,18051.0 -2011-07-25 15:00:00,18503.0 -2011-07-25 16:00:00,18915.0 -2011-07-25 17:00:00,19249.0 -2011-07-25 18:00:00,19457.0 -2011-07-25 19:00:00,19422.0 -2011-07-25 20:00:00,18928.0 -2011-07-25 21:00:00,18168.0 -2011-07-25 22:00:00,17557.0 -2011-07-25 23:00:00,16918.0 -2011-07-26 00:00:00,15479.0 -2011-07-24 01:00:00,13905.0 -2011-07-24 02:00:00,12967.0 -2011-07-24 03:00:00,12183.0 -2011-07-24 04:00:00,11519.0 -2011-07-24 05:00:00,11197.0 -2011-07-24 06:00:00,11033.0 -2011-07-24 07:00:00,10955.0 -2011-07-24 08:00:00,11030.0 -2011-07-24 09:00:00,11463.0 -2011-07-24 10:00:00,12029.0 -2011-07-24 11:00:00,12297.0 -2011-07-24 12:00:00,12690.0 -2011-07-24 13:00:00,13192.0 -2011-07-24 14:00:00,13858.0 -2011-07-24 15:00:00,14701.0 -2011-07-24 16:00:00,15363.0 -2011-07-24 17:00:00,15539.0 -2011-07-24 18:00:00,15889.0 -2011-07-24 19:00:00,16252.0 -2011-07-24 20:00:00,16103.0 -2011-07-24 21:00:00,15600.0 -2011-07-24 22:00:00,15372.0 -2011-07-24 23:00:00,15155.0 -2011-07-25 00:00:00,14318.0 -2011-07-23 01:00:00,15569.0 -2011-07-23 02:00:00,14490.0 -2011-07-23 03:00:00,13337.0 -2011-07-23 04:00:00,12458.0 -2011-07-23 05:00:00,11959.0 -2011-07-23 06:00:00,11621.0 -2011-07-23 07:00:00,11611.0 -2011-07-23 08:00:00,11683.0 -2011-07-23 09:00:00,12211.0 -2011-07-23 10:00:00,12773.0 -2011-07-23 11:00:00,13400.0 -2011-07-23 12:00:00,13994.0 -2011-07-23 13:00:00,14538.0 -2011-07-23 14:00:00,14855.0 -2011-07-23 15:00:00,15161.0 -2011-07-23 16:00:00,15554.0 -2011-07-23 17:00:00,16039.0 -2011-07-23 18:00:00,16519.0 -2011-07-23 19:00:00,16591.0 -2011-07-23 20:00:00,16483.0 -2011-07-23 21:00:00,16209.0 -2011-07-23 22:00:00,15968.0 -2011-07-23 23:00:00,15720.0 -2011-07-24 00:00:00,14918.0 -2011-07-22 01:00:00,17671.0 -2011-07-22 02:00:00,16259.0 -2011-07-22 03:00:00,15164.0 -2011-07-22 04:00:00,14314.0 -2011-07-22 05:00:00,13798.0 -2011-07-22 06:00:00,13705.0 -2011-07-22 07:00:00,14039.0 -2011-07-22 08:00:00,14700.0 -2011-07-22 09:00:00,15529.0 -2011-07-22 10:00:00,16185.0 -2011-07-22 11:00:00,16511.0 -2011-07-22 12:00:00,16288.0 -2011-07-22 13:00:00,16099.0 -2011-07-22 14:00:00,16446.0 -2011-07-22 15:00:00,17205.0 -2011-07-22 16:00:00,17932.0 -2011-07-22 17:00:00,18677.0 -2011-07-22 18:00:00,19309.0 -2011-07-22 19:00:00,19551.0 -2011-07-22 20:00:00,19317.0 -2011-07-22 21:00:00,18811.0 -2011-07-22 22:00:00,18326.0 -2011-07-22 23:00:00,17829.0 -2011-07-23 00:00:00,16698.0 -2011-07-21 01:00:00,18510.0 -2011-07-21 02:00:00,17311.0 -2011-07-21 03:00:00,16438.0 -2011-07-21 04:00:00,15702.0 -2011-07-21 05:00:00,15201.0 -2011-07-21 06:00:00,15081.0 -2011-07-21 07:00:00,15341.0 -2011-07-21 08:00:00,16237.0 -2011-07-21 09:00:00,17725.0 -2011-07-21 10:00:00,19091.0 -2011-07-21 11:00:00,20346.0 -2011-07-21 12:00:00,21622.0 -2011-07-21 13:00:00,22510.0 -2011-07-21 14:00:00,23119.0 -2011-07-21 15:00:00,23446.0 -2011-07-21 16:00:00,23587.0 -2011-07-21 17:00:00,23670.0 -2011-07-21 18:00:00,23542.0 -2011-07-21 19:00:00,23160.0 -2011-07-21 20:00:00,22596.0 -2011-07-21 21:00:00,21843.0 -2011-07-21 22:00:00,21320.0 -2011-07-21 23:00:00,20673.0 -2011-07-22 00:00:00,19229.0 -2011-07-20 01:00:00,16669.0 -2011-07-20 02:00:00,15488.0 -2011-07-20 03:00:00,14599.0 -2011-07-20 04:00:00,13998.0 -2011-07-20 05:00:00,13630.0 -2011-07-20 06:00:00,13505.0 -2011-07-20 07:00:00,13902.0 -2011-07-20 08:00:00,14668.0 -2011-07-20 09:00:00,16041.0 -2011-07-20 10:00:00,17411.0 -2011-07-20 11:00:00,18980.0 -2011-07-20 12:00:00,20550.0 -2011-07-20 13:00:00,21773.0 -2011-07-20 14:00:00,22543.0 -2011-07-20 15:00:00,23194.0 -2011-07-20 16:00:00,23524.0 -2011-07-20 17:00:00,23713.0 -2011-07-20 18:00:00,23753.0 -2011-07-20 19:00:00,23582.0 -2011-07-20 20:00:00,23089.0 -2011-07-20 21:00:00,22458.0 -2011-07-20 22:00:00,22012.0 -2011-07-20 23:00:00,21407.0 -2011-07-21 00:00:00,20016.0 -2011-07-19 01:00:00,17146.0 -2011-07-19 02:00:00,16064.0 -2011-07-19 03:00:00,15301.0 -2011-07-19 04:00:00,14670.0 -2011-07-19 05:00:00,14287.0 -2011-07-19 06:00:00,14182.0 -2011-07-19 07:00:00,14420.0 -2011-07-19 08:00:00,15250.0 -2011-07-19 09:00:00,16672.0 -2011-07-19 10:00:00,18034.0 -2011-07-19 11:00:00,19274.0 -2011-07-19 12:00:00,20429.0 -2011-07-19 13:00:00,21191.0 -2011-07-19 14:00:00,21768.0 -2011-07-19 15:00:00,22278.0 -2011-07-19 16:00:00,22429.0 -2011-07-19 17:00:00,22463.0 -2011-07-19 18:00:00,22330.0 -2011-07-19 19:00:00,21969.0 -2011-07-19 20:00:00,21281.0 -2011-07-19 21:00:00,20584.0 -2011-07-19 22:00:00,20155.0 -2011-07-19 23:00:00,19599.0 -2011-07-20 00:00:00,18164.0 -2011-07-18 01:00:00,16257.0 -2011-07-18 02:00:00,15301.0 -2011-07-18 03:00:00,14622.0 -2011-07-18 04:00:00,14125.0 -2011-07-18 05:00:00,13933.0 -2011-07-18 06:00:00,13968.0 -2011-07-18 07:00:00,14500.0 -2011-07-18 08:00:00,15382.0 -2011-07-18 09:00:00,16669.0 -2011-07-18 10:00:00,17483.0 -2011-07-18 11:00:00,18443.0 -2011-07-18 12:00:00,19065.0 -2011-07-18 13:00:00,19535.0 -2011-07-18 14:00:00,20122.0 -2011-07-18 15:00:00,20650.0 -2011-07-18 16:00:00,20844.0 -2011-07-18 17:00:00,21037.0 -2011-07-18 18:00:00,21294.0 -2011-07-18 19:00:00,21278.0 -2011-07-18 20:00:00,20951.0 -2011-07-18 21:00:00,20461.0 -2011-07-18 22:00:00,20239.0 -2011-07-18 23:00:00,19866.0 -2011-07-19 00:00:00,18514.0 -2011-07-17 01:00:00,13153.0 -2011-07-17 02:00:00,12262.0 -2011-07-17 03:00:00,11504.0 -2011-07-17 04:00:00,11007.0 -2011-07-17 05:00:00,10582.0 -2011-07-17 06:00:00,10336.0 -2011-07-17 07:00:00,10133.0 -2011-07-17 08:00:00,10262.0 -2011-07-17 09:00:00,11105.0 -2011-07-17 10:00:00,12434.0 -2011-07-17 11:00:00,13928.0 -2011-07-17 12:00:00,15305.0 -2011-07-17 13:00:00,16467.0 -2011-07-17 14:00:00,17435.0 -2011-07-17 15:00:00,18224.0 -2011-07-17 16:00:00,18788.0 -2011-07-17 17:00:00,19225.0 -2011-07-17 18:00:00,19524.0 -2011-07-17 19:00:00,19566.0 -2011-07-17 20:00:00,19353.0 -2011-07-17 21:00:00,18862.0 -2011-07-17 22:00:00,18670.0 -2011-07-17 23:00:00,18448.0 -2011-07-18 00:00:00,17431.0 -2011-07-16 01:00:00,13141.0 -2011-07-16 02:00:00,12168.0 -2011-07-16 03:00:00,11417.0 -2011-07-16 04:00:00,10880.0 -2011-07-16 05:00:00,10677.0 -2011-07-16 06:00:00,10554.0 -2011-07-16 07:00:00,10666.0 -2011-07-16 08:00:00,10900.0 -2011-07-16 09:00:00,11506.0 -2011-07-16 10:00:00,12303.0 -2011-07-16 11:00:00,13012.0 -2011-07-16 12:00:00,13639.0 -2011-07-16 13:00:00,14143.0 -2011-07-16 14:00:00,14825.0 -2011-07-16 15:00:00,15460.0 -2011-07-16 16:00:00,15990.0 -2011-07-16 17:00:00,16365.0 -2011-07-16 18:00:00,16711.0 -2011-07-16 19:00:00,16682.0 -2011-07-16 20:00:00,16484.0 -2011-07-16 21:00:00,15920.0 -2011-07-16 22:00:00,15493.0 -2011-07-16 23:00:00,15153.0 -2011-07-17 00:00:00,14243.0 -2011-07-15 01:00:00,11911.0 -2011-07-15 02:00:00,11065.0 -2011-07-15 03:00:00,10472.0 -2011-07-15 04:00:00,10092.0 -2011-07-15 05:00:00,9949.0 -2011-07-15 06:00:00,10061.0 -2011-07-15 07:00:00,10559.0 -2011-07-15 08:00:00,11405.0 -2011-07-15 09:00:00,12623.0 -2011-07-15 10:00:00,13780.0 -2011-07-15 11:00:00,14837.0 -2011-07-15 12:00:00,15759.0 -2011-07-15 13:00:00,16480.0 -2011-07-15 14:00:00,17031.0 -2011-07-15 15:00:00,17573.0 -2011-07-15 16:00:00,17850.0 -2011-07-15 17:00:00,17810.0 -2011-07-15 18:00:00,17562.0 -2011-07-15 19:00:00,17314.0 -2011-07-15 20:00:00,16681.0 -2011-07-15 21:00:00,15839.0 -2011-07-15 22:00:00,15565.0 -2011-07-15 23:00:00,15249.0 -2011-07-16 00:00:00,14245.0 -2011-07-14 01:00:00,10980.0 -2011-07-14 02:00:00,10206.0 -2011-07-14 03:00:00,9694.0 -2011-07-14 04:00:00,9359.0 -2011-07-14 05:00:00,9225.0 -2011-07-14 06:00:00,9371.0 -2011-07-14 07:00:00,9784.0 -2011-07-14 08:00:00,10655.0 -2011-07-14 09:00:00,11683.0 -2011-07-14 10:00:00,12495.0 -2011-07-14 11:00:00,13226.0 -2011-07-14 12:00:00,13887.0 -2011-07-14 13:00:00,14354.0 -2011-07-14 14:00:00,14777.0 -2011-07-14 15:00:00,15251.0 -2011-07-14 16:00:00,15587.0 -2011-07-14 17:00:00,15821.0 -2011-07-14 18:00:00,15908.0 -2011-07-14 19:00:00,15634.0 -2011-07-14 20:00:00,14883.0 -2011-07-14 21:00:00,14235.0 -2011-07-14 22:00:00,13984.0 -2011-07-14 23:00:00,13855.0 -2011-07-15 00:00:00,12958.0 -2011-07-13 01:00:00,12527.0 -2011-07-13 02:00:00,11535.0 -2011-07-13 03:00:00,10899.0 -2011-07-13 04:00:00,10437.0 -2011-07-13 05:00:00,10194.0 -2011-07-13 06:00:00,10263.0 -2011-07-13 07:00:00,10636.0 -2011-07-13 08:00:00,11510.0 -2011-07-13 09:00:00,12471.0 -2011-07-13 10:00:00,13029.0 -2011-07-13 11:00:00,13446.0 -2011-07-13 12:00:00,13909.0 -2011-07-13 13:00:00,14255.0 -2011-07-13 14:00:00,14498.0 -2011-07-13 15:00:00,14660.0 -2011-07-13 16:00:00,14751.0 -2011-07-13 17:00:00,14780.0 -2011-07-13 18:00:00,14727.0 -2011-07-13 19:00:00,14426.0 -2011-07-13 20:00:00,13825.0 -2011-07-13 21:00:00,13254.0 -2011-07-13 22:00:00,12991.0 -2011-07-13 23:00:00,12817.0 -2011-07-14 00:00:00,11949.0 -2011-07-12 01:00:00,14050.0 -2011-07-12 02:00:00,12935.0 -2011-07-12 03:00:00,12146.0 -2011-07-12 04:00:00,11544.0 -2011-07-12 05:00:00,11188.0 -2011-07-12 06:00:00,11133.0 -2011-07-12 07:00:00,11363.0 -2011-07-12 08:00:00,12318.0 -2011-07-12 09:00:00,13732.0 -2011-07-12 10:00:00,14944.0 -2011-07-12 11:00:00,15977.0 -2011-07-12 12:00:00,16892.0 -2011-07-12 13:00:00,17421.0 -2011-07-12 14:00:00,17784.0 -2011-07-12 15:00:00,18194.0 -2011-07-12 16:00:00,18519.0 -2011-07-12 17:00:00,18644.0 -2011-07-12 18:00:00,18447.0 -2011-07-12 19:00:00,18021.0 -2011-07-12 20:00:00,17047.0 -2011-07-12 21:00:00,16275.0 -2011-07-12 22:00:00,15754.0 -2011-07-12 23:00:00,15015.0 -2011-07-13 00:00:00,13788.0 -2011-07-11 01:00:00,14707.0 -2011-07-11 02:00:00,13777.0 -2011-07-11 03:00:00,13115.0 -2011-07-11 04:00:00,12697.0 -2011-07-11 05:00:00,12536.0 -2011-07-11 06:00:00,12683.0 -2011-07-11 07:00:00,13218.0 -2011-07-11 08:00:00,14257.0 -2011-07-11 09:00:00,15368.0 -2011-07-11 10:00:00,13866.0 -2011-07-11 11:00:00,13831.0 -2011-07-11 12:00:00,14016.0 -2011-07-11 13:00:00,14366.0 -2011-07-11 14:00:00,14808.0 -2011-07-11 15:00:00,15813.0 -2011-07-11 16:00:00,16715.0 -2011-07-11 17:00:00,17321.0 -2011-07-11 18:00:00,17804.0 -2011-07-11 19:00:00,18023.0 -2011-07-11 20:00:00,17748.0 -2011-07-11 21:00:00,17258.0 -2011-07-11 22:00:00,16972.0 -2011-07-11 23:00:00,16630.0 -2011-07-12 00:00:00,15432.0 -2011-07-10 01:00:00,13861.0 -2011-07-10 02:00:00,12946.0 -2011-07-10 03:00:00,12245.0 -2011-07-10 04:00:00,11623.0 -2011-07-10 05:00:00,11198.0 -2011-07-10 06:00:00,10950.0 -2011-07-10 07:00:00,10642.0 -2011-07-10 08:00:00,10783.0 -2011-07-10 09:00:00,11641.0 -2011-07-10 10:00:00,12978.0 -2011-07-10 11:00:00,14349.0 -2011-07-10 12:00:00,15488.0 -2011-07-10 13:00:00,16345.0 -2011-07-10 14:00:00,16989.0 -2011-07-10 15:00:00,17488.0 -2011-07-10 16:00:00,17795.0 -2011-07-10 17:00:00,18001.0 -2011-07-10 18:00:00,18073.0 -2011-07-10 19:00:00,17833.0 -2011-07-10 20:00:00,17350.0 -2011-07-10 21:00:00,16982.0 -2011-07-10 22:00:00,16781.0 -2011-07-10 23:00:00,16700.0 -2011-07-11 00:00:00,15795.0 -2011-07-09 01:00:00,12393.0 -2011-07-09 02:00:00,11387.0 -2011-07-09 03:00:00,10628.0 -2011-07-09 04:00:00,10083.0 -2011-07-09 05:00:00,9782.0 -2011-07-09 06:00:00,9610.0 -2011-07-09 07:00:00,9524.0 -2011-07-09 08:00:00,9838.0 -2011-07-09 09:00:00,10771.0 -2011-07-09 10:00:00,11921.0 -2011-07-09 11:00:00,13075.0 -2011-07-09 12:00:00,14194.0 -2011-07-09 13:00:00,15066.0 -2011-07-09 14:00:00,15737.0 -2011-07-09 15:00:00,16221.0 -2011-07-09 16:00:00,16618.0 -2011-07-09 17:00:00,16978.0 -2011-07-09 18:00:00,17273.0 -2011-07-09 19:00:00,17278.0 -2011-07-09 20:00:00,16947.0 -2011-07-09 21:00:00,16399.0 -2011-07-09 22:00:00,16000.0 -2011-07-09 23:00:00,15770.0 -2011-07-10 00:00:00,14868.0 -2011-07-08 01:00:00,11595.0 -2011-07-08 02:00:00,10758.0 -2011-07-08 03:00:00,10185.0 -2011-07-08 04:00:00,9782.0 -2011-07-08 05:00:00,9574.0 -2011-07-08 06:00:00,9663.0 -2011-07-08 07:00:00,10006.0 -2011-07-08 08:00:00,10875.0 -2011-07-08 09:00:00,12037.0 -2011-07-08 10:00:00,13100.0 -2011-07-08 11:00:00,13991.0 -2011-07-08 12:00:00,14760.0 -2011-07-08 13:00:00,15374.0 -2011-07-08 14:00:00,15889.0 -2011-07-08 15:00:00,16440.0 -2011-07-08 16:00:00,16802.0 -2011-07-08 17:00:00,17098.0 -2011-07-08 18:00:00,17227.0 -2011-07-08 19:00:00,17053.0 -2011-07-08 20:00:00,16487.0 -2011-07-08 21:00:00,15730.0 -2011-07-08 22:00:00,15060.0 -2011-07-08 23:00:00,14711.0 -2011-07-09 00:00:00,13586.0 -2011-07-07 01:00:00,12381.0 -2011-07-07 02:00:00,11440.0 -2011-07-07 03:00:00,10792.0 -2011-07-07 04:00:00,10357.0 -2011-07-07 05:00:00,10158.0 -2011-07-07 06:00:00,10188.0 -2011-07-07 07:00:00,10606.0 -2011-07-07 08:00:00,11423.0 -2011-07-07 09:00:00,12392.0 -2011-07-07 10:00:00,13292.0 -2011-07-07 11:00:00,13884.0 -2011-07-07 12:00:00,14535.0 -2011-07-07 13:00:00,14895.0 -2011-07-07 14:00:00,15163.0 -2011-07-07 15:00:00,15435.0 -2011-07-07 16:00:00,15512.0 -2011-07-07 17:00:00,15476.0 -2011-07-07 18:00:00,15385.0 -2011-07-07 19:00:00,15363.0 -2011-07-07 20:00:00,14899.0 -2011-07-07 21:00:00,14342.0 -2011-07-07 22:00:00,13963.0 -2011-07-07 23:00:00,13749.0 -2011-07-08 00:00:00,12756.0 -2011-07-06 01:00:00,14566.0 -2011-07-06 02:00:00,13436.0 -2011-07-06 03:00:00,12596.0 -2011-07-06 04:00:00,11996.0 -2011-07-06 05:00:00,11660.0 -2011-07-06 06:00:00,11607.0 -2011-07-06 07:00:00,11868.0 -2011-07-06 08:00:00,12827.0 -2011-07-06 09:00:00,14194.0 -2011-07-06 10:00:00,15437.0 -2011-07-06 11:00:00,16547.0 -2011-07-06 12:00:00,17562.0 -2011-07-06 13:00:00,18207.0 -2011-07-06 14:00:00,18636.0 -2011-07-06 15:00:00,18950.0 -2011-07-06 16:00:00,19027.0 -2011-07-06 17:00:00,18936.0 -2011-07-06 18:00:00,18711.0 -2011-07-06 19:00:00,18288.0 -2011-07-06 20:00:00,17362.0 -2011-07-06 21:00:00,16267.0 -2011-07-06 22:00:00,15493.0 -2011-07-06 23:00:00,14943.0 -2011-07-07 00:00:00,13689.0 -2011-07-05 01:00:00,11997.0 -2011-07-05 02:00:00,11042.0 -2011-07-05 03:00:00,10338.0 -2011-07-05 04:00:00,9847.0 -2011-07-05 05:00:00,9640.0 -2011-07-05 06:00:00,9712.0 -2011-07-05 07:00:00,10088.0 -2011-07-05 08:00:00,11178.0 -2011-07-05 09:00:00,12691.0 -2011-07-05 10:00:00,14055.0 -2011-07-05 11:00:00,15300.0 -2011-07-05 12:00:00,16439.0 -2011-07-05 13:00:00,17334.0 -2011-07-05 14:00:00,18031.0 -2011-07-05 15:00:00,18712.0 -2011-07-05 16:00:00,19220.0 -2011-07-05 17:00:00,19570.0 -2011-07-05 18:00:00,19759.0 -2011-07-05 19:00:00,19593.0 -2011-07-05 20:00:00,18940.0 -2011-07-05 21:00:00,18158.0 -2011-07-05 22:00:00,17867.0 -2011-07-05 23:00:00,17371.0 -2011-07-06 00:00:00,16030.0 -2011-07-04 01:00:00,11163.0 -2011-07-04 02:00:00,10477.0 -2011-07-04 03:00:00,9855.0 -2011-07-04 04:00:00,9410.0 -2011-07-04 05:00:00,9161.0 -2011-07-04 06:00:00,9086.0 -2011-07-04 07:00:00,8938.0 -2011-07-04 08:00:00,9088.0 -2011-07-04 09:00:00,9739.0 -2011-07-04 10:00:00,10717.0 -2011-07-04 11:00:00,11779.0 -2011-07-04 12:00:00,12733.0 -2011-07-04 13:00:00,13517.0 -2011-07-04 14:00:00,14088.0 -2011-07-04 15:00:00,14515.0 -2011-07-04 16:00:00,14923.0 -2011-07-04 17:00:00,15290.0 -2011-07-04 18:00:00,15550.0 -2011-07-04 19:00:00,15553.0 -2011-07-04 20:00:00,15226.0 -2011-07-04 21:00:00,14657.0 -2011-07-04 22:00:00,14009.0 -2011-07-04 23:00:00,13531.0 -2011-07-05 00:00:00,12833.0 -2011-07-03 01:00:00,13038.0 -2011-07-03 02:00:00,11994.0 -2011-07-03 03:00:00,11183.0 -2011-07-03 04:00:00,10561.0 -2011-07-03 05:00:00,10183.0 -2011-07-03 06:00:00,9875.0 -2011-07-03 07:00:00,9604.0 -2011-07-03 08:00:00,9688.0 -2011-07-03 09:00:00,10446.0 -2011-07-03 10:00:00,11448.0 -2011-07-03 11:00:00,12395.0 -2011-07-03 12:00:00,13359.0 -2011-07-03 13:00:00,14095.0 -2011-07-03 14:00:00,14476.0 -2011-07-03 15:00:00,14564.0 -2011-07-03 16:00:00,14497.0 -2011-07-03 17:00:00,14513.0 -2011-07-03 18:00:00,14443.0 -2011-07-03 19:00:00,14234.0 -2011-07-03 20:00:00,13461.0 -2011-07-03 21:00:00,12838.0 -2011-07-03 22:00:00,12599.0 -2011-07-03 23:00:00,12463.0 -2011-07-04 00:00:00,11921.0 -2011-07-02 01:00:00,13191.0 -2011-07-02 02:00:00,12256.0 -2011-07-02 03:00:00,11640.0 -2011-07-02 04:00:00,11185.0 -2011-07-02 05:00:00,10931.0 -2011-07-02 06:00:00,10794.0 -2011-07-02 07:00:00,10756.0 -2011-07-02 08:00:00,11194.0 -2011-07-02 09:00:00,12398.0 -2011-07-02 10:00:00,13991.0 -2011-07-02 11:00:00,15398.0 -2011-07-02 12:00:00,16528.0 -2011-07-02 13:00:00,17292.0 -2011-07-02 14:00:00,17865.0 -2011-07-02 15:00:00,18082.0 -2011-07-02 16:00:00,18338.0 -2011-07-02 17:00:00,18387.0 -2011-07-02 18:00:00,18186.0 -2011-07-02 19:00:00,17823.0 -2011-07-02 20:00:00,17239.0 -2011-07-02 21:00:00,16377.0 -2011-07-02 22:00:00,15668.0 -2011-07-02 23:00:00,15182.0 -2011-07-03 00:00:00,14123.0 -2011-07-01 01:00:00,13174.0 -2011-07-01 02:00:00,12170.0 -2011-07-01 03:00:00,11461.0 -2011-07-01 04:00:00,10959.0 -2011-07-01 05:00:00,10726.0 -2011-07-01 06:00:00,10808.0 -2011-07-01 07:00:00,11245.0 -2011-07-01 08:00:00,12044.0 -2011-07-01 09:00:00,13198.0 -2011-07-01 10:00:00,13899.0 -2011-07-01 11:00:00,14275.0 -2011-07-01 12:00:00,14553.0 -2011-07-01 13:00:00,14711.0 -2011-07-01 14:00:00,14858.0 -2011-07-01 15:00:00,14924.0 -2011-07-01 16:00:00,15003.0 -2011-07-01 17:00:00,15316.0 -2011-07-01 18:00:00,15812.0 -2011-07-01 19:00:00,16202.0 -2011-07-01 20:00:00,16132.0 -2011-07-01 21:00:00,15697.0 -2011-07-01 22:00:00,15332.0 -2011-07-01 23:00:00,15177.0 -2011-07-02 00:00:00,14255.0 -2011-06-30 01:00:00,11474.0 -2011-06-30 02:00:00,10564.0 -2011-06-30 03:00:00,9929.0 -2011-06-30 04:00:00,9532.0 -2011-06-30 05:00:00,9320.0 -2011-06-30 06:00:00,9432.0 -2011-06-30 07:00:00,9798.0 -2011-06-30 08:00:00,10921.0 -2011-06-30 09:00:00,12166.0 -2011-06-30 10:00:00,13218.0 -2011-06-30 11:00:00,14210.0 -2011-06-30 12:00:00,15153.0 -2011-06-30 13:00:00,15858.0 -2011-06-30 14:00:00,16575.0 -2011-06-30 15:00:00,17272.0 -2011-06-30 16:00:00,17810.0 -2011-06-30 17:00:00,18276.0 -2011-06-30 18:00:00,18435.0 -2011-06-30 19:00:00,18178.0 -2011-06-30 20:00:00,17797.0 -2011-06-30 21:00:00,17257.0 -2011-06-30 22:00:00,16754.0 -2011-06-30 23:00:00,15669.0 -2011-07-01 00:00:00,14323.0 -2011-06-29 01:00:00,11468.0 -2011-06-29 02:00:00,10509.0 -2011-06-29 03:00:00,9877.0 -2011-06-29 04:00:00,9458.0 -2011-06-29 05:00:00,9265.0 -2011-06-29 06:00:00,9355.0 -2011-06-29 07:00:00,9654.0 -2011-06-29 08:00:00,10658.0 -2011-06-29 09:00:00,11860.0 -2011-06-29 10:00:00,12800.0 -2011-06-29 11:00:00,13522.0 -2011-06-29 12:00:00,14164.0 -2011-06-29 13:00:00,14653.0 -2011-06-29 14:00:00,15071.0 -2011-06-29 15:00:00,15519.0 -2011-06-29 16:00:00,15817.0 -2011-06-29 17:00:00,15991.0 -2011-06-29 18:00:00,16049.0 -2011-06-29 19:00:00,15874.0 -2011-06-29 20:00:00,15259.0 -2011-06-29 21:00:00,14531.0 -2011-06-29 22:00:00,14019.0 -2011-06-29 23:00:00,13716.0 -2011-06-30 00:00:00,12632.0 -2011-06-28 01:00:00,12421.0 -2011-06-28 02:00:00,11256.0 -2011-06-28 03:00:00,10454.0 -2011-06-28 04:00:00,9864.0 -2011-06-28 05:00:00,9562.0 -2011-06-28 06:00:00,9597.0 -2011-06-28 07:00:00,9915.0 -2011-06-28 08:00:00,10847.0 -2011-06-28 09:00:00,11874.0 -2011-06-28 10:00:00,12627.0 -2011-06-28 11:00:00,13207.0 -2011-06-28 12:00:00,13773.0 -2011-06-28 13:00:00,14140.0 -2011-06-28 14:00:00,14449.0 -2011-06-28 15:00:00,14807.0 -2011-06-28 16:00:00,15105.0 -2011-06-28 17:00:00,15349.0 -2011-06-28 18:00:00,15523.0 -2011-06-28 19:00:00,15417.0 -2011-06-28 20:00:00,15044.0 -2011-06-28 21:00:00,14465.0 -2011-06-28 22:00:00,14042.0 -2011-06-28 23:00:00,13781.0 -2011-06-29 00:00:00,12681.0 -2011-06-27 01:00:00,10363.0 -2011-06-27 02:00:00,9730.0 -2011-06-27 03:00:00,9331.0 -2011-06-27 04:00:00,9072.0 -2011-06-27 05:00:00,9030.0 -2011-06-27 06:00:00,9279.0 -2011-06-27 07:00:00,9859.0 -2011-06-27 08:00:00,10884.0 -2011-06-27 09:00:00,11894.0 -2011-06-27 10:00:00,12551.0 -2011-06-27 11:00:00,13048.0 -2011-06-27 12:00:00,13724.0 -2011-06-27 13:00:00,14240.0 -2011-06-27 14:00:00,14620.0 -2011-06-27 15:00:00,15212.0 -2011-06-27 16:00:00,15548.0 -2011-06-27 17:00:00,15612.0 -2011-06-27 18:00:00,15802.0 -2011-06-27 19:00:00,15811.0 -2011-06-27 20:00:00,15546.0 -2011-06-27 21:00:00,15210.0 -2011-06-27 22:00:00,14918.0 -2011-06-27 23:00:00,14836.0 -2011-06-28 00:00:00,13793.0 -2011-06-26 01:00:00,9961.0 -2011-06-26 02:00:00,9297.0 -2011-06-26 03:00:00,8862.0 -2011-06-26 04:00:00,8443.0 -2011-06-26 05:00:00,8302.0 -2011-06-26 06:00:00,8222.0 -2011-06-26 07:00:00,8052.0 -2011-06-26 08:00:00,8114.0 -2011-06-26 09:00:00,8651.0 -2011-06-26 10:00:00,9390.0 -2011-06-26 11:00:00,10114.0 -2011-06-26 12:00:00,10768.0 -2011-06-26 13:00:00,11177.0 -2011-06-26 14:00:00,11513.0 -2011-06-26 15:00:00,11789.0 -2011-06-26 16:00:00,12047.0 -2011-06-26 17:00:00,12287.0 -2011-06-26 18:00:00,12454.0 -2011-06-26 19:00:00,12421.0 -2011-06-26 20:00:00,12240.0 -2011-06-26 21:00:00,11900.0 -2011-06-26 22:00:00,11883.0 -2011-06-26 23:00:00,11881.0 -2011-06-27 00:00:00,11281.0 -2011-06-25 01:00:00,10039.0 -2011-06-25 02:00:00,9349.0 -2011-06-25 03:00:00,8948.0 -2011-06-25 04:00:00,8641.0 -2011-06-25 05:00:00,8510.0 -2011-06-25 06:00:00,8469.0 -2011-06-25 07:00:00,8406.0 -2011-06-25 08:00:00,8714.0 -2011-06-25 09:00:00,9489.0 -2011-06-25 10:00:00,10247.0 -2011-06-25 11:00:00,10942.0 -2011-06-25 12:00:00,11463.0 -2011-06-25 13:00:00,11794.0 -2011-06-25 14:00:00,11997.0 -2011-06-25 15:00:00,12032.0 -2011-06-25 16:00:00,12002.0 -2011-06-25 17:00:00,11834.0 -2011-06-25 18:00:00,11753.0 -2011-06-25 19:00:00,11465.0 -2011-06-25 20:00:00,11232.0 -2011-06-25 21:00:00,11068.0 -2011-06-25 22:00:00,11242.0 -2011-06-25 23:00:00,11236.0 -2011-06-26 00:00:00,10654.0 -2011-06-24 01:00:00,10105.0 -2011-06-24 02:00:00,9470.0 -2011-06-24 03:00:00,9055.0 -2011-06-24 04:00:00,8782.0 -2011-06-24 05:00:00,8663.0 -2011-06-24 06:00:00,8781.0 -2011-06-24 07:00:00,9224.0 -2011-06-24 08:00:00,9973.0 -2011-06-24 09:00:00,10777.0 -2011-06-24 10:00:00,11336.0 -2011-06-24 11:00:00,11716.0 -2011-06-24 12:00:00,11999.0 -2011-06-24 13:00:00,12069.0 -2011-06-24 14:00:00,12088.0 -2011-06-24 15:00:00,12117.0 -2011-06-24 16:00:00,11996.0 -2011-06-24 17:00:00,11811.0 -2011-06-24 18:00:00,11687.0 -2011-06-24 19:00:00,11483.0 -2011-06-24 20:00:00,11213.0 -2011-06-24 21:00:00,11212.0 -2011-06-24 22:00:00,11474.0 -2011-06-24 23:00:00,11336.0 -2011-06-25 00:00:00,10729.0 -2011-06-23 01:00:00,10455.0 -2011-06-23 02:00:00,9775.0 -2011-06-23 03:00:00,9297.0 -2011-06-23 04:00:00,9019.0 -2011-06-23 05:00:00,8896.0 -2011-06-23 06:00:00,9076.0 -2011-06-23 07:00:00,9602.0 -2011-06-23 08:00:00,10475.0 -2011-06-23 09:00:00,11416.0 -2011-06-23 10:00:00,12022.0 -2011-06-23 11:00:00,12374.0 -2011-06-23 12:00:00,12698.0 -2011-06-23 13:00:00,12938.0 -2011-06-23 14:00:00,13052.0 -2011-06-23 15:00:00,13078.0 -2011-06-23 16:00:00,12964.0 -2011-06-23 17:00:00,12790.0 -2011-06-23 18:00:00,12560.0 -2011-06-23 19:00:00,12287.0 -2011-06-23 20:00:00,11900.0 -2011-06-23 21:00:00,11773.0 -2011-06-23 22:00:00,11979.0 -2011-06-23 23:00:00,11798.0 -2011-06-24 00:00:00,10990.0 -2011-06-22 01:00:00,11919.0 -2011-06-22 02:00:00,11031.0 -2011-06-22 03:00:00,10446.0 -2011-06-22 04:00:00,10013.0 -2011-06-22 05:00:00,9797.0 -2011-06-22 06:00:00,9909.0 -2011-06-22 07:00:00,10317.0 -2011-06-22 08:00:00,11186.0 -2011-06-22 09:00:00,12148.0 -2011-06-22 10:00:00,12905.0 -2011-06-22 11:00:00,13361.0 -2011-06-22 12:00:00,13535.0 -2011-06-22 13:00:00,13595.0 -2011-06-22 14:00:00,13570.0 -2011-06-22 15:00:00,13668.0 -2011-06-22 16:00:00,13501.0 -2011-06-22 17:00:00,13241.0 -2011-06-22 18:00:00,13018.0 -2011-06-22 19:00:00,12694.0 -2011-06-22 20:00:00,12290.0 -2011-06-22 21:00:00,12130.0 -2011-06-22 22:00:00,12226.0 -2011-06-22 23:00:00,12103.0 -2011-06-23 00:00:00,11335.0 -2011-06-21 01:00:00,12172.0 -2011-06-21 02:00:00,11331.0 -2011-06-21 03:00:00,10774.0 -2011-06-21 04:00:00,10405.0 -2011-06-21 05:00:00,10277.0 -2011-06-21 06:00:00,10450.0 -2011-06-21 07:00:00,11010.0 -2011-06-21 08:00:00,12141.0 -2011-06-21 09:00:00,13635.0 -2011-06-21 10:00:00,14965.0 -2011-06-21 11:00:00,16119.0 -2011-06-21 12:00:00,17222.0 -2011-06-21 13:00:00,18048.0 -2011-06-21 14:00:00,18653.0 -2011-06-21 15:00:00,19140.0 -2011-06-21 16:00:00,19123.0 -2011-06-21 17:00:00,19225.0 -2011-06-21 18:00:00,19183.0 -2011-06-21 19:00:00,18971.0 -2011-06-21 20:00:00,18208.0 -2011-06-21 21:00:00,17719.0 -2011-06-21 22:00:00,16713.0 -2011-06-21 23:00:00,14233.0 -2011-06-22 00:00:00,13065.0 -2011-06-20 01:00:00,11629.0 -2011-06-20 02:00:00,10873.0 -2011-06-20 03:00:00,10259.0 -2011-06-20 04:00:00,9925.0 -2011-06-20 05:00:00,9732.0 -2011-06-20 06:00:00,9930.0 -2011-06-20 07:00:00,10538.0 -2011-06-20 08:00:00,11747.0 -2011-06-20 09:00:00,12620.0 -2011-06-20 10:00:00,13032.0 -2011-06-20 11:00:00,13438.0 -2011-06-20 12:00:00,13829.0 -2011-06-20 13:00:00,14013.0 -2011-06-20 14:00:00,14142.0 -2011-06-20 15:00:00,14556.0 -2011-06-20 16:00:00,14990.0 -2011-06-20 17:00:00,15331.0 -2011-06-20 18:00:00,15493.0 -2011-06-20 19:00:00,15327.0 -2011-06-20 20:00:00,14878.0 -2011-06-20 21:00:00,14487.0 -2011-06-20 22:00:00,14492.0 -2011-06-20 23:00:00,14311.0 -2011-06-21 00:00:00,13290.0 -2011-06-19 01:00:00,11001.0 -2011-06-19 02:00:00,10179.0 -2011-06-19 03:00:00,9598.0 -2011-06-19 04:00:00,9085.0 -2011-06-19 05:00:00,8881.0 -2011-06-19 06:00:00,8737.0 -2011-06-19 07:00:00,8516.0 -2011-06-19 08:00:00,8629.0 -2011-06-19 09:00:00,9155.0 -2011-06-19 10:00:00,9963.0 -2011-06-19 11:00:00,10719.0 -2011-06-19 12:00:00,11527.0 -2011-06-19 13:00:00,12359.0 -2011-06-19 14:00:00,12896.0 -2011-06-19 15:00:00,13247.0 -2011-06-19 16:00:00,13525.0 -2011-06-19 17:00:00,13674.0 -2011-06-19 18:00:00,13692.0 -2011-06-19 19:00:00,13714.0 -2011-06-19 20:00:00,13555.0 -2011-06-19 21:00:00,13308.0 -2011-06-19 22:00:00,13310.0 -2011-06-19 23:00:00,13417.0 -2011-06-20 00:00:00,12729.0 -2011-06-18 01:00:00,11166.0 -2011-06-18 02:00:00,10311.0 -2011-06-18 03:00:00,9736.0 -2011-06-18 04:00:00,9315.0 -2011-06-18 05:00:00,9076.0 -2011-06-18 06:00:00,8947.0 -2011-06-18 07:00:00,8895.0 -2011-06-18 08:00:00,9093.0 -2011-06-18 09:00:00,9835.0 -2011-06-18 10:00:00,10751.0 -2011-06-18 11:00:00,11774.0 -2011-06-18 12:00:00,12742.0 -2011-06-18 13:00:00,13389.0 -2011-06-18 14:00:00,13782.0 -2011-06-18 15:00:00,13999.0 -2011-06-18 16:00:00,14297.0 -2011-06-18 17:00:00,14473.0 -2011-06-18 18:00:00,14444.0 -2011-06-18 19:00:00,13907.0 -2011-06-18 20:00:00,13321.0 -2011-06-18 21:00:00,12847.0 -2011-06-18 22:00:00,12745.0 -2011-06-18 23:00:00,12583.0 -2011-06-19 00:00:00,11854.0 -2011-06-17 01:00:00,11021.0 -2011-06-17 02:00:00,10158.0 -2011-06-17 03:00:00,9623.0 -2011-06-17 04:00:00,9278.0 -2011-06-17 05:00:00,9115.0 -2011-06-17 06:00:00,9195.0 -2011-06-17 07:00:00,9499.0 -2011-06-17 08:00:00,10491.0 -2011-06-17 09:00:00,11673.0 -2011-06-17 10:00:00,12673.0 -2011-06-17 11:00:00,13493.0 -2011-06-17 12:00:00,14262.0 -2011-06-17 13:00:00,14821.0 -2011-06-17 14:00:00,15232.0 -2011-06-17 15:00:00,15629.0 -2011-06-17 16:00:00,15824.0 -2011-06-17 17:00:00,15868.0 -2011-06-17 18:00:00,15733.0 -2011-06-17 19:00:00,15293.0 -2011-06-17 20:00:00,14443.0 -2011-06-17 21:00:00,13675.0 -2011-06-17 22:00:00,13456.0 -2011-06-17 23:00:00,13146.0 -2011-06-18 00:00:00,12169.0 -2011-06-16 01:00:00,10240.0 -2011-06-16 02:00:00,9560.0 -2011-06-16 03:00:00,9119.0 -2011-06-16 04:00:00,8834.0 -2011-06-16 05:00:00,8724.0 -2011-06-16 06:00:00,8865.0 -2011-06-16 07:00:00,9327.0 -2011-06-16 08:00:00,10223.0 -2011-06-16 09:00:00,11069.0 -2011-06-16 10:00:00,11761.0 -2011-06-16 11:00:00,12366.0 -2011-06-16 12:00:00,12854.0 -2011-06-16 13:00:00,13107.0 -2011-06-16 14:00:00,13341.0 -2011-06-16 15:00:00,13679.0 -2011-06-16 16:00:00,13845.0 -2011-06-16 17:00:00,13975.0 -2011-06-16 18:00:00,14037.0 -2011-06-16 19:00:00,13925.0 -2011-06-16 20:00:00,13575.0 -2011-06-16 21:00:00,13254.0 -2011-06-16 22:00:00,13137.0 -2011-06-16 23:00:00,13014.0 -2011-06-17 00:00:00,12090.0 -2011-06-15 01:00:00,10171.0 -2011-06-15 02:00:00,9527.0 -2011-06-15 03:00:00,9096.0 -2011-06-15 04:00:00,8775.0 -2011-06-15 05:00:00,8669.0 -2011-06-15 06:00:00,8797.0 -2011-06-15 07:00:00,9329.0 -2011-06-15 08:00:00,10161.0 -2011-06-15 09:00:00,10826.0 -2011-06-15 10:00:00,11334.0 -2011-06-15 11:00:00,11800.0 -2011-06-15 12:00:00,12244.0 -2011-06-15 13:00:00,12527.0 -2011-06-15 14:00:00,12619.0 -2011-06-15 15:00:00,12687.0 -2011-06-15 16:00:00,12573.0 -2011-06-15 17:00:00,12438.0 -2011-06-15 18:00:00,12337.0 -2011-06-15 19:00:00,12243.0 -2011-06-15 20:00:00,11974.0 -2011-06-15 21:00:00,11903.0 -2011-06-15 22:00:00,12078.0 -2011-06-15 23:00:00,11932.0 -2011-06-16 00:00:00,11139.0 -2011-06-14 01:00:00,9683.0 -2011-06-14 02:00:00,9045.0 -2011-06-14 03:00:00,8638.0 -2011-06-14 04:00:00,8380.0 -2011-06-14 05:00:00,8289.0 -2011-06-14 06:00:00,8442.0 -2011-06-14 07:00:00,8775.0 -2011-06-14 08:00:00,9655.0 -2011-06-14 09:00:00,10576.0 -2011-06-14 10:00:00,11266.0 -2011-06-14 11:00:00,11662.0 -2011-06-14 12:00:00,12096.0 -2011-06-14 13:00:00,12397.0 -2011-06-14 14:00:00,12595.0 -2011-06-14 15:00:00,12863.0 -2011-06-14 16:00:00,12995.0 -2011-06-14 17:00:00,12981.0 -2011-06-14 18:00:00,12808.0 -2011-06-14 19:00:00,12517.0 -2011-06-14 20:00:00,12068.0 -2011-06-14 21:00:00,11791.0 -2011-06-14 22:00:00,12010.0 -2011-06-14 23:00:00,11891.0 -2011-06-15 00:00:00,11082.0 -2011-06-13 01:00:00,8795.0 -2011-06-13 02:00:00,8357.0 -2011-06-13 03:00:00,8065.0 -2011-06-13 04:00:00,7922.0 -2011-06-13 05:00:00,7892.0 -2011-06-13 06:00:00,8169.0 -2011-06-13 07:00:00,8641.0 -2011-06-13 08:00:00,9452.0 -2011-06-13 09:00:00,10451.0 -2011-06-13 10:00:00,11110.0 -2011-06-13 11:00:00,11630.0 -2011-06-13 12:00:00,12001.0 -2011-06-13 13:00:00,12157.0 -2011-06-13 14:00:00,12275.0 -2011-06-13 15:00:00,12439.0 -2011-06-13 16:00:00,12449.0 -2011-06-13 17:00:00,12425.0 -2011-06-13 18:00:00,12342.0 -2011-06-13 19:00:00,12118.0 -2011-06-13 20:00:00,11644.0 -2011-06-13 21:00:00,11334.0 -2011-06-13 22:00:00,11521.0 -2011-06-13 23:00:00,11425.0 -2011-06-14 00:00:00,10596.0 -2011-06-12 01:00:00,9038.0 -2011-06-12 02:00:00,8501.0 -2011-06-12 03:00:00,8193.0 -2011-06-12 04:00:00,7909.0 -2011-06-12 05:00:00,7774.0 -2011-06-12 06:00:00,7757.0 -2011-06-12 07:00:00,7583.0 -2011-06-12 08:00:00,7584.0 -2011-06-12 09:00:00,7857.0 -2011-06-12 10:00:00,8368.0 -2011-06-12 11:00:00,8811.0 -2011-06-12 12:00:00,9139.0 -2011-06-12 13:00:00,9256.0 -2011-06-12 14:00:00,9327.0 -2011-06-12 15:00:00,9316.0 -2011-06-12 16:00:00,9336.0 -2011-06-12 17:00:00,9325.0 -2011-06-12 18:00:00,9381.0 -2011-06-12 19:00:00,9440.0 -2011-06-12 20:00:00,9394.0 -2011-06-12 21:00:00,9416.0 -2011-06-12 22:00:00,9812.0 -2011-06-12 23:00:00,9868.0 -2011-06-13 00:00:00,9412.0 -2011-06-11 01:00:00,9726.0 -2011-06-11 02:00:00,9065.0 -2011-06-11 03:00:00,8639.0 -2011-06-11 04:00:00,8388.0 -2011-06-11 05:00:00,8243.0 -2011-06-11 06:00:00,8301.0 -2011-06-11 07:00:00,8346.0 -2011-06-11 08:00:00,8591.0 -2011-06-11 09:00:00,9044.0 -2011-06-11 10:00:00,9656.0 -2011-06-11 11:00:00,10043.0 -2011-06-11 12:00:00,10366.0 -2011-06-11 13:00:00,10324.0 -2011-06-11 14:00:00,10265.0 -2011-06-11 15:00:00,10163.0 -2011-06-11 16:00:00,10055.0 -2011-06-11 17:00:00,9996.0 -2011-06-11 18:00:00,9918.0 -2011-06-11 19:00:00,9813.0 -2011-06-11 20:00:00,9761.0 -2011-06-11 21:00:00,9779.0 -2011-06-11 22:00:00,10082.0 -2011-06-11 23:00:00,10079.0 -2011-06-12 00:00:00,9587.0 -2011-06-10 01:00:00,9533.0 -2011-06-10 02:00:00,8943.0 -2011-06-10 03:00:00,8572.0 -2011-06-10 04:00:00,8341.0 -2011-06-10 05:00:00,8215.0 -2011-06-10 06:00:00,8435.0 -2011-06-10 07:00:00,8857.0 -2011-06-10 08:00:00,9580.0 -2011-06-10 09:00:00,10466.0 -2011-06-10 10:00:00,11004.0 -2011-06-10 11:00:00,11408.0 -2011-06-10 12:00:00,11783.0 -2011-06-10 13:00:00,12033.0 -2011-06-10 14:00:00,12108.0 -2011-06-10 15:00:00,12210.0 -2011-06-10 16:00:00,12191.0 -2011-06-10 17:00:00,12112.0 -2011-06-10 18:00:00,11980.0 -2011-06-10 19:00:00,11686.0 -2011-06-10 20:00:00,11331.0 -2011-06-10 21:00:00,11196.0 -2011-06-10 22:00:00,11350.0 -2011-06-10 23:00:00,11270.0 -2011-06-11 00:00:00,10556.0 -2011-06-09 01:00:00,14194.0 -2011-06-09 02:00:00,12627.0 -2011-06-09 03:00:00,11723.0 -2011-06-09 04:00:00,11120.0 -2011-06-09 05:00:00,10661.0 -2011-06-09 06:00:00,10639.0 -2011-06-09 07:00:00,11131.0 -2011-06-09 08:00:00,12069.0 -2011-06-09 09:00:00,12958.0 -2011-06-09 10:00:00,13359.0 -2011-06-09 11:00:00,13269.0 -2011-06-09 12:00:00,13153.0 -2011-06-09 13:00:00,12956.0 -2011-06-09 14:00:00,12715.0 -2011-06-09 15:00:00,12535.0 -2011-06-09 16:00:00,12223.0 -2011-06-09 17:00:00,11909.0 -2011-06-09 18:00:00,11659.0 -2011-06-09 19:00:00,11388.0 -2011-06-09 20:00:00,11091.0 -2011-06-09 21:00:00,10966.0 -2011-06-09 22:00:00,11208.0 -2011-06-09 23:00:00,11075.0 -2011-06-10 00:00:00,10380.0 -2011-06-08 01:00:00,15690.0 -2011-06-08 02:00:00,14493.0 -2011-06-08 03:00:00,13656.0 -2011-06-08 04:00:00,13069.0 -2011-06-08 05:00:00,12727.0 -2011-06-08 06:00:00,12737.0 -2011-06-08 07:00:00,13101.0 -2011-06-08 08:00:00,14162.0 -2011-06-08 09:00:00,15637.0 -2011-06-08 10:00:00,16915.0 -2011-06-08 11:00:00,18064.0 -2011-06-08 12:00:00,19190.0 -2011-06-08 13:00:00,19908.0 -2011-06-08 14:00:00,20328.0 -2011-06-08 15:00:00,20707.0 -2011-06-08 16:00:00,21036.0 -2011-06-08 17:00:00,21236.0 -2011-06-08 18:00:00,21174.0 -2011-06-08 19:00:00,20819.0 -2011-06-08 20:00:00,20033.0 -2011-06-08 21:00:00,19268.0 -2011-06-08 22:00:00,19079.0 -2011-06-08 23:00:00,18361.0 -2011-06-09 00:00:00,16400.0 -2011-06-07 01:00:00,14996.0 -2011-06-07 02:00:00,13851.0 -2011-06-07 03:00:00,13007.0 -2011-06-07 04:00:00,12388.0 -2011-06-07 05:00:00,12046.0 -2011-06-07 06:00:00,12040.0 -2011-06-07 07:00:00,12426.0 -2011-06-07 08:00:00,13607.0 -2011-06-07 09:00:00,15152.0 -2011-06-07 10:00:00,16511.0 -2011-06-07 11:00:00,17573.0 -2011-06-07 12:00:00,18667.0 -2011-06-07 13:00:00,19608.0 -2011-06-07 14:00:00,20330.0 -2011-06-07 15:00:00,20722.0 -2011-06-07 16:00:00,21429.0 -2011-06-07 17:00:00,21423.0 -2011-06-07 18:00:00,21455.0 -2011-06-07 19:00:00,21218.0 -2011-06-07 20:00:00,20660.0 -2011-06-07 21:00:00,19600.0 -2011-06-07 22:00:00,19201.0 -2011-06-07 23:00:00,18713.0 -2011-06-08 00:00:00,17202.0 -2011-06-06 01:00:00,10123.0 -2011-06-06 02:00:00,9468.0 -2011-06-06 03:00:00,9023.0 -2011-06-06 04:00:00,8790.0 -2011-06-06 05:00:00,8718.0 -2011-06-06 06:00:00,8980.0 -2011-06-06 07:00:00,9424.0 -2011-06-06 08:00:00,10631.0 -2011-06-06 09:00:00,11907.0 -2011-06-06 10:00:00,12816.0 -2011-06-06 11:00:00,13441.0 -2011-06-06 12:00:00,14231.0 -2011-06-06 13:00:00,15057.0 -2011-06-06 14:00:00,16200.0 -2011-06-06 15:00:00,17245.0 -2011-06-06 16:00:00,18124.0 -2011-06-06 17:00:00,18810.0 -2011-06-06 18:00:00,19287.0 -2011-06-06 19:00:00,19375.0 -2011-06-06 20:00:00,19074.0 -2011-06-06 21:00:00,18573.0 -2011-06-06 22:00:00,18358.0 -2011-06-06 23:00:00,17972.0 -2011-06-07 00:00:00,16559.0 -2011-06-05 01:00:00,11140.0 -2011-06-05 02:00:00,10245.0 -2011-06-05 03:00:00,9663.0 -2011-06-05 04:00:00,9200.0 -2011-06-05 05:00:00,8943.0 -2011-06-05 06:00:00,8814.0 -2011-06-05 07:00:00,8531.0 -2011-06-05 08:00:00,8710.0 -2011-06-05 09:00:00,9213.0 -2011-06-05 10:00:00,9797.0 -2011-06-05 11:00:00,10318.0 -2011-06-05 12:00:00,10832.0 -2011-06-05 13:00:00,11516.0 -2011-06-05 14:00:00,12212.0 -2011-06-05 15:00:00,12681.0 -2011-06-05 16:00:00,13074.0 -2011-06-05 17:00:00,13423.0 -2011-06-05 18:00:00,13686.0 -2011-06-05 19:00:00,13663.0 -2011-06-05 20:00:00,13299.0 -2011-06-05 21:00:00,12704.0 -2011-06-05 22:00:00,12338.0 -2011-06-05 23:00:00,12028.0 -2011-06-06 00:00:00,11093.0 -2011-06-04 01:00:00,13157.0 -2011-06-04 02:00:00,12136.0 -2011-06-04 03:00:00,11352.0 -2011-06-04 04:00:00,10815.0 -2011-06-04 05:00:00,10475.0 -2011-06-04 06:00:00,10372.0 -2011-06-04 07:00:00,10347.0 -2011-06-04 08:00:00,10839.0 -2011-06-04 09:00:00,11989.0 -2011-06-04 10:00:00,13430.0 -2011-06-04 11:00:00,14804.0 -2011-06-04 12:00:00,15873.0 -2011-06-04 13:00:00,16624.0 -2011-06-04 14:00:00,17062.0 -2011-06-04 15:00:00,17272.0 -2011-06-04 16:00:00,17154.0 -2011-06-04 17:00:00,16392.0 -2011-06-04 18:00:00,15302.0 -2011-06-04 19:00:00,14651.0 -2011-06-04 20:00:00,14253.0 -2011-06-04 21:00:00,13560.0 -2011-06-04 22:00:00,13207.0 -2011-06-04 23:00:00,12968.0 -2011-06-05 00:00:00,12066.0 -2011-06-03 01:00:00,9798.0 -2011-06-03 02:00:00,9125.0 -2011-06-03 03:00:00,8764.0 -2011-06-03 04:00:00,8524.0 -2011-06-03 05:00:00,8492.0 -2011-06-03 06:00:00,8697.0 -2011-06-03 07:00:00,9174.0 -2011-06-03 08:00:00,10195.0 -2011-06-03 09:00:00,11197.0 -2011-06-03 10:00:00,11968.0 -2011-06-03 11:00:00,12579.0 -2011-06-03 12:00:00,13217.0 -2011-06-03 13:00:00,13786.0 -2011-06-03 14:00:00,14361.0 -2011-06-03 15:00:00,15060.0 -2011-06-03 16:00:00,15631.0 -2011-06-03 17:00:00,16134.0 -2011-06-03 18:00:00,16446.0 -2011-06-03 19:00:00,16454.0 -2011-06-03 20:00:00,16104.0 -2011-06-03 21:00:00,15652.0 -2011-06-03 22:00:00,15523.0 -2011-06-03 23:00:00,15343.0 -2011-06-04 00:00:00,14316.0 -2011-06-02 01:00:00,11020.0 -2011-06-02 02:00:00,10146.0 -2011-06-02 03:00:00,9520.0 -2011-06-02 04:00:00,9147.0 -2011-06-02 05:00:00,8883.0 -2011-06-02 06:00:00,8919.0 -2011-06-02 07:00:00,9132.0 -2011-06-02 08:00:00,10003.0 -2011-06-02 09:00:00,10945.0 -2011-06-02 10:00:00,11465.0 -2011-06-02 11:00:00,11790.0 -2011-06-02 12:00:00,12062.0 -2011-06-02 13:00:00,12270.0 -2011-06-02 14:00:00,12426.0 -2011-06-02 15:00:00,12596.0 -2011-06-02 16:00:00,12611.0 -2011-06-02 17:00:00,12599.0 -2011-06-02 18:00:00,12500.0 -2011-06-02 19:00:00,12152.0 -2011-06-02 20:00:00,11673.0 -2011-06-02 21:00:00,11489.0 -2011-06-02 22:00:00,11768.0 -2011-06-02 23:00:00,11565.0 -2011-06-03 00:00:00,10727.0 -2011-06-01 01:00:00,11714.0 -2011-06-01 02:00:00,10886.0 -2011-06-01 03:00:00,10325.0 -2011-06-01 04:00:00,9773.0 -2011-06-01 05:00:00,9330.0 -2011-06-01 06:00:00,9347.0 -2011-06-01 07:00:00,9644.0 -2011-06-01 08:00:00,10627.0 -2011-06-01 09:00:00,11710.0 -2011-06-01 10:00:00,12365.0 -2011-06-01 11:00:00,12871.0 -2011-06-01 12:00:00,13300.0 -2011-06-01 13:00:00,13592.0 -2011-06-01 14:00:00,13820.0 -2011-06-01 15:00:00,14106.0 -2011-06-01 16:00:00,14288.0 -2011-06-01 17:00:00,14521.0 -2011-06-01 18:00:00,14690.0 -2011-06-01 19:00:00,14628.0 -2011-06-01 20:00:00,14250.0 -2011-06-01 21:00:00,13742.0 -2011-06-01 22:00:00,13612.0 -2011-06-01 23:00:00,13347.0 -2011-06-02 00:00:00,12225.0 -2011-05-31 01:00:00,11692.0 -2011-05-31 02:00:00,10897.0 -2011-05-31 03:00:00,10328.0 -2011-05-31 04:00:00,9972.0 -2011-05-31 05:00:00,9796.0 -2011-05-31 06:00:00,9950.0 -2011-05-31 07:00:00,10407.0 -2011-05-31 08:00:00,11664.0 -2011-05-31 09:00:00,13261.0 -2011-05-31 10:00:00,14448.0 -2011-05-31 11:00:00,15194.0 -2011-05-31 12:00:00,15868.0 -2011-05-31 13:00:00,16224.0 -2011-05-31 14:00:00,16593.0 -2011-05-31 15:00:00,16753.0 -2011-05-31 16:00:00,16740.0 -2011-05-31 17:00:00,16324.0 -2011-05-31 18:00:00,16076.0 -2011-05-31 19:00:00,15862.0 -2011-05-31 20:00:00,15614.0 -2011-05-31 21:00:00,15162.0 -2011-05-31 22:00:00,14843.0 -2011-05-31 23:00:00,14397.0 -2011-06-01 00:00:00,13058.0 -2011-05-30 01:00:00,8938.0 -2011-05-30 02:00:00,8419.0 -2011-05-30 03:00:00,8072.0 -2011-05-30 04:00:00,7862.0 -2011-05-30 05:00:00,7758.0 -2011-05-30 06:00:00,7817.0 -2011-05-30 07:00:00,7786.0 -2011-05-30 08:00:00,7970.0 -2011-05-30 09:00:00,8462.0 -2011-05-30 10:00:00,9207.0 -2011-05-30 11:00:00,9992.0 -2011-05-30 12:00:00,10772.0 -2011-05-30 13:00:00,11465.0 -2011-05-30 14:00:00,11989.0 -2011-05-30 15:00:00,12471.0 -2011-05-30 16:00:00,12918.0 -2011-05-30 17:00:00,13315.0 -2011-05-30 18:00:00,13649.0 -2011-05-30 19:00:00,13835.0 -2011-05-30 20:00:00,13740.0 -2011-05-30 21:00:00,13493.0 -2011-05-30 22:00:00,13684.0 -2011-05-30 23:00:00,13608.0 -2011-05-31 00:00:00,12730.0 -2011-05-29 01:00:00,8730.0 -2011-05-29 02:00:00,8232.0 -2011-05-29 03:00:00,7912.0 -2011-05-29 04:00:00,7687.0 -2011-05-29 05:00:00,7544.0 -2011-05-29 06:00:00,7611.0 -2011-05-29 07:00:00,7504.0 -2011-05-29 08:00:00,7533.0 -2011-05-29 09:00:00,7859.0 -2011-05-29 10:00:00,8319.0 -2011-05-29 11:00:00,8734.0 -2011-05-29 12:00:00,9068.0 -2011-05-29 13:00:00,9593.0 -2011-05-29 14:00:00,9809.0 -2011-05-29 15:00:00,9931.0 -2011-05-29 16:00:00,9745.0 -2011-05-29 17:00:00,9491.0 -2011-05-29 18:00:00,9366.0 -2011-05-29 19:00:00,9329.0 -2011-05-29 20:00:00,9293.0 -2011-05-29 21:00:00,9362.0 -2011-05-29 22:00:00,9758.0 -2011-05-29 23:00:00,9897.0 -2011-05-30 00:00:00,9493.0 -2011-05-28 01:00:00,9099.0 -2011-05-28 02:00:00,8549.0 -2011-05-28 03:00:00,8180.0 -2011-05-28 04:00:00,7969.0 -2011-05-28 05:00:00,7849.0 -2011-05-28 06:00:00,7881.0 -2011-05-28 07:00:00,7899.0 -2011-05-28 08:00:00,8047.0 -2011-05-28 09:00:00,8496.0 -2011-05-28 10:00:00,9024.0 -2011-05-28 11:00:00,9398.0 -2011-05-28 12:00:00,9645.0 -2011-05-28 13:00:00,9671.0 -2011-05-28 14:00:00,9694.0 -2011-05-28 15:00:00,9579.0 -2011-05-28 16:00:00,9585.0 -2011-05-28 17:00:00,9542.0 -2011-05-28 18:00:00,9599.0 -2011-05-28 19:00:00,9615.0 -2011-05-28 20:00:00,9509.0 -2011-05-28 21:00:00,9565.0 -2011-05-28 22:00:00,9882.0 -2011-05-28 23:00:00,9756.0 -2011-05-29 00:00:00,9273.0 -2011-05-27 01:00:00,9690.0 -2011-05-27 02:00:00,9109.0 -2011-05-27 03:00:00,8801.0 -2011-05-27 04:00:00,8573.0 -2011-05-27 05:00:00,8519.0 -2011-05-27 06:00:00,8707.0 -2011-05-27 07:00:00,9129.0 -2011-05-27 08:00:00,9919.0 -2011-05-27 09:00:00,10688.0 -2011-05-27 10:00:00,11039.0 -2011-05-27 11:00:00,11126.0 -2011-05-27 12:00:00,11242.0 -2011-05-27 13:00:00,11228.0 -2011-05-27 14:00:00,11225.0 -2011-05-27 15:00:00,11224.0 -2011-05-27 16:00:00,11078.0 -2011-05-27 17:00:00,10891.0 -2011-05-27 18:00:00,10695.0 -2011-05-27 19:00:00,10562.0 -2011-05-27 20:00:00,10341.0 -2011-05-27 21:00:00,10410.0 -2011-05-27 22:00:00,10681.0 -2011-05-27 23:00:00,10504.0 -2011-05-28 00:00:00,9857.0 -2011-05-26 01:00:00,9527.0 -2011-05-26 02:00:00,8961.0 -2011-05-26 03:00:00,8626.0 -2011-05-26 04:00:00,8454.0 -2011-05-26 05:00:00,8426.0 -2011-05-26 06:00:00,8619.0 -2011-05-26 07:00:00,9194.0 -2011-05-26 08:00:00,10115.0 -2011-05-26 09:00:00,10958.0 -2011-05-26 10:00:00,11405.0 -2011-05-26 11:00:00,11597.0 -2011-05-26 12:00:00,11778.0 -2011-05-26 13:00:00,11778.0 -2011-05-26 14:00:00,11737.0 -2011-05-26 15:00:00,11710.0 -2011-05-26 16:00:00,11534.0 -2011-05-26 17:00:00,11410.0 -2011-05-26 18:00:00,11352.0 -2011-05-26 19:00:00,11228.0 -2011-05-26 20:00:00,11064.0 -2011-05-26 21:00:00,11126.0 -2011-05-26 22:00:00,11381.0 -2011-05-26 23:00:00,11276.0 -2011-05-27 00:00:00,10490.0 -2011-05-25 01:00:00,9318.0 -2011-05-25 02:00:00,8752.0 -2011-05-25 03:00:00,8408.0 -2011-05-25 04:00:00,8201.0 -2011-05-25 05:00:00,8152.0 -2011-05-25 06:00:00,8321.0 -2011-05-25 07:00:00,8822.0 -2011-05-25 08:00:00,9794.0 -2011-05-25 09:00:00,10998.0 -2011-05-25 10:00:00,11648.0 -2011-05-25 11:00:00,11931.0 -2011-05-25 12:00:00,12003.0 -2011-05-25 13:00:00,11980.0 -2011-05-25 14:00:00,11997.0 -2011-05-25 15:00:00,12128.0 -2011-05-25 16:00:00,12089.0 -2011-05-25 17:00:00,12041.0 -2011-05-25 18:00:00,11946.0 -2011-05-25 19:00:00,11678.0 -2011-05-25 20:00:00,11288.0 -2011-05-25 21:00:00,11277.0 -2011-05-25 22:00:00,11541.0 -2011-05-25 23:00:00,11182.0 -2011-05-26 00:00:00,10371.0 -2011-05-24 01:00:00,10556.0 -2011-05-24 02:00:00,9790.0 -2011-05-24 03:00:00,9285.0 -2011-05-24 04:00:00,8923.0 -2011-05-24 05:00:00,8774.0 -2011-05-24 06:00:00,8860.0 -2011-05-24 07:00:00,9183.0 -2011-05-24 08:00:00,10068.0 -2011-05-24 09:00:00,11063.0 -2011-05-24 10:00:00,11564.0 -2011-05-24 11:00:00,11867.0 -2011-05-24 12:00:00,12079.0 -2011-05-24 13:00:00,12167.0 -2011-05-24 14:00:00,12143.0 -2011-05-24 15:00:00,12144.0 -2011-05-24 16:00:00,11995.0 -2011-05-24 17:00:00,11849.0 -2011-05-24 18:00:00,11666.0 -2011-05-24 19:00:00,11365.0 -2011-05-24 20:00:00,10955.0 -2011-05-24 21:00:00,10813.0 -2011-05-24 22:00:00,11184.0 -2011-05-24 23:00:00,10927.0 -2011-05-25 00:00:00,10143.0 -2011-05-23 01:00:00,9658.0 -2011-05-23 02:00:00,9067.0 -2011-05-23 03:00:00,8690.0 -2011-05-23 04:00:00,8475.0 -2011-05-23 05:00:00,8521.0 -2011-05-23 06:00:00,8709.0 -2011-05-23 07:00:00,9264.0 -2011-05-23 08:00:00,10289.0 -2011-05-23 09:00:00,11487.0 -2011-05-23 10:00:00,12074.0 -2011-05-23 11:00:00,12460.0 -2011-05-23 12:00:00,12961.0 -2011-05-23 13:00:00,13328.0 -2011-05-23 14:00:00,13516.0 -2011-05-23 15:00:00,13735.0 -2011-05-23 16:00:00,13821.0 -2011-05-23 17:00:00,13836.0 -2011-05-23 18:00:00,13788.0 -2011-05-23 19:00:00,13505.0 -2011-05-23 20:00:00,12970.0 -2011-05-23 21:00:00,12631.0 -2011-05-23 22:00:00,12859.0 -2011-05-23 23:00:00,12626.0 -2011-05-24 00:00:00,11638.0 -2011-05-22 01:00:00,9532.0 -2011-05-22 02:00:00,8906.0 -2011-05-22 03:00:00,8511.0 -2011-05-22 04:00:00,8183.0 -2011-05-22 05:00:00,8065.0 -2011-05-22 06:00:00,7909.0 -2011-05-22 07:00:00,7816.0 -2011-05-22 08:00:00,7859.0 -2011-05-22 09:00:00,8318.0 -2011-05-22 10:00:00,8960.0 -2011-05-22 11:00:00,9548.0 -2011-05-22 12:00:00,10132.0 -2011-05-22 13:00:00,10698.0 -2011-05-22 14:00:00,11131.0 -2011-05-22 15:00:00,11571.0 -2011-05-22 16:00:00,11878.0 -2011-05-22 17:00:00,12039.0 -2011-05-22 18:00:00,11933.0 -2011-05-22 19:00:00,11557.0 -2011-05-22 20:00:00,11102.0 -2011-05-22 21:00:00,10900.0 -2011-05-22 22:00:00,11154.0 -2011-05-22 23:00:00,11016.0 -2011-05-23 00:00:00,10486.0 -2011-05-21 01:00:00,9565.0 -2011-05-21 02:00:00,8924.0 -2011-05-21 03:00:00,8531.0 -2011-05-21 04:00:00,8247.0 -2011-05-21 05:00:00,8085.0 -2011-05-21 06:00:00,8149.0 -2011-05-21 07:00:00,8158.0 -2011-05-21 08:00:00,8388.0 -2011-05-21 09:00:00,8943.0 -2011-05-21 10:00:00,9571.0 -2011-05-21 11:00:00,10043.0 -2011-05-21 12:00:00,10354.0 -2011-05-21 13:00:00,10446.0 -2011-05-21 14:00:00,10499.0 -2011-05-21 15:00:00,10373.0 -2011-05-21 16:00:00,10284.0 -2011-05-21 17:00:00,10252.0 -2011-05-21 18:00:00,10333.0 -2011-05-21 19:00:00,10342.0 -2011-05-21 20:00:00,10333.0 -2011-05-21 21:00:00,10302.0 -2011-05-21 22:00:00,10698.0 -2011-05-21 23:00:00,10630.0 -2011-05-22 00:00:00,10135.0 -2011-05-20 01:00:00,9516.0 -2011-05-20 02:00:00,8972.0 -2011-05-20 03:00:00,8575.0 -2011-05-20 04:00:00,8359.0 -2011-05-20 05:00:00,8307.0 -2011-05-20 06:00:00,8423.0 -2011-05-20 07:00:00,8854.0 -2011-05-20 08:00:00,9682.0 -2011-05-20 09:00:00,10676.0 -2011-05-20 10:00:00,11189.0 -2011-05-20 11:00:00,11609.0 -2011-05-20 12:00:00,11963.0 -2011-05-20 13:00:00,12064.0 -2011-05-20 14:00:00,12140.0 -2011-05-20 15:00:00,12302.0 -2011-05-20 16:00:00,12339.0 -2011-05-20 17:00:00,12289.0 -2011-05-20 18:00:00,12156.0 -2011-05-20 19:00:00,11880.0 -2011-05-20 20:00:00,11429.0 -2011-05-20 21:00:00,11177.0 -2011-05-20 22:00:00,11442.0 -2011-05-20 23:00:00,11232.0 -2011-05-21 00:00:00,10458.0 -2011-05-19 01:00:00,9371.0 -2011-05-19 02:00:00,8819.0 -2011-05-19 03:00:00,8484.0 -2011-05-19 04:00:00,8312.0 -2011-05-19 05:00:00,8252.0 -2011-05-19 06:00:00,8442.0 -2011-05-19 07:00:00,8980.0 -2011-05-19 08:00:00,9885.0 -2011-05-19 09:00:00,10693.0 -2011-05-19 10:00:00,11111.0 -2011-05-19 11:00:00,11381.0 -2011-05-19 12:00:00,11627.0 -2011-05-19 13:00:00,11735.0 -2011-05-19 14:00:00,11784.0 -2011-05-19 15:00:00,11883.0 -2011-05-19 16:00:00,11890.0 -2011-05-19 17:00:00,11828.0 -2011-05-19 18:00:00,11697.0 -2011-05-19 19:00:00,11495.0 -2011-05-19 20:00:00,11227.0 -2011-05-19 21:00:00,11135.0 -2011-05-19 22:00:00,11486.0 -2011-05-19 23:00:00,11267.0 -2011-05-20 00:00:00,10406.0 -2011-05-18 01:00:00,9336.0 -2011-05-18 02:00:00,8792.0 -2011-05-18 03:00:00,8466.0 -2011-05-18 04:00:00,8267.0 -2011-05-18 05:00:00,8230.0 -2011-05-18 06:00:00,8420.0 -2011-05-18 07:00:00,8968.0 -2011-05-18 08:00:00,9843.0 -2011-05-18 09:00:00,10687.0 -2011-05-18 10:00:00,11128.0 -2011-05-18 11:00:00,11333.0 -2011-05-18 12:00:00,11458.0 -2011-05-18 13:00:00,11500.0 -2011-05-18 14:00:00,11464.0 -2011-05-18 15:00:00,11490.0 -2011-05-18 16:00:00,11380.0 -2011-05-18 17:00:00,11268.0 -2011-05-18 18:00:00,11130.0 -2011-05-18 19:00:00,10978.0 -2011-05-18 20:00:00,10786.0 -2011-05-18 21:00:00,10862.0 -2011-05-18 22:00:00,11285.0 -2011-05-18 23:00:00,10994.0 -2011-05-19 00:00:00,10188.0 -2011-05-17 01:00:00,9438.0 -2011-05-17 02:00:00,8960.0 -2011-05-17 03:00:00,8667.0 -2011-05-17 04:00:00,8502.0 -2011-05-17 05:00:00,8486.0 -2011-05-17 06:00:00,8710.0 -2011-05-17 07:00:00,9238.0 -2011-05-17 08:00:00,10092.0 -2011-05-17 09:00:00,10858.0 -2011-05-17 10:00:00,11172.0 -2011-05-17 11:00:00,11297.0 -2011-05-17 12:00:00,11433.0 -2011-05-17 13:00:00,11405.0 -2011-05-17 14:00:00,11393.0 -2011-05-17 15:00:00,11406.0 -2011-05-17 16:00:00,11282.0 -2011-05-17 17:00:00,11144.0 -2011-05-17 18:00:00,11002.0 -2011-05-17 19:00:00,10806.0 -2011-05-17 20:00:00,10577.0 -2011-05-17 21:00:00,10552.0 -2011-05-17 22:00:00,11120.0 -2011-05-17 23:00:00,10965.0 -2011-05-18 00:00:00,10152.0 -2011-05-16 01:00:00,8957.0 -2011-05-16 02:00:00,8560.0 -2011-05-16 03:00:00,8354.0 -2011-05-16 04:00:00,8229.0 -2011-05-16 05:00:00,8269.0 -2011-05-16 06:00:00,8542.0 -2011-05-16 07:00:00,9170.0 -2011-05-16 08:00:00,10020.0 -2011-05-16 09:00:00,10843.0 -2011-05-16 10:00:00,11190.0 -2011-05-16 11:00:00,11330.0 -2011-05-16 12:00:00,11425.0 -2011-05-16 13:00:00,11406.0 -2011-05-16 14:00:00,11371.0 -2011-05-16 15:00:00,11350.0 -2011-05-16 16:00:00,11202.0 -2011-05-16 17:00:00,11058.0 -2011-05-16 18:00:00,10924.0 -2011-05-16 19:00:00,10775.0 -2011-05-16 20:00:00,10561.0 -2011-05-16 21:00:00,10620.0 -2011-05-16 22:00:00,11192.0 -2011-05-16 23:00:00,11040.0 -2011-05-17 00:00:00,10242.0 -2011-05-15 01:00:00,8871.0 -2011-05-15 02:00:00,8452.0 -2011-05-15 03:00:00,8141.0 -2011-05-15 04:00:00,7897.0 -2011-05-15 05:00:00,7852.0 -2011-05-15 06:00:00,7804.0 -2011-05-15 07:00:00,7912.0 -2011-05-15 08:00:00,7921.0 -2011-05-15 09:00:00,8248.0 -2011-05-15 10:00:00,8682.0 -2011-05-15 11:00:00,9048.0 -2011-05-15 12:00:00,9309.0 -2011-05-15 13:00:00,9396.0 -2011-05-15 14:00:00,9489.0 -2011-05-15 15:00:00,9432.0 -2011-05-15 16:00:00,9422.0 -2011-05-15 17:00:00,9389.0 -2011-05-15 18:00:00,9435.0 -2011-05-15 19:00:00,9532.0 -2011-05-15 20:00:00,9588.0 -2011-05-15 21:00:00,9765.0 -2011-05-15 22:00:00,10172.0 -2011-05-15 23:00:00,10008.0 -2011-05-16 00:00:00,9497.0 -2011-05-14 01:00:00,9326.0 -2011-05-14 02:00:00,8692.0 -2011-05-14 03:00:00,8353.0 -2011-05-14 04:00:00,8103.0 -2011-05-14 05:00:00,7992.0 -2011-05-14 06:00:00,8000.0 -2011-05-14 07:00:00,8175.0 -2011-05-14 08:00:00,8371.0 -2011-05-14 09:00:00,8856.0 -2011-05-14 10:00:00,9328.0 -2011-05-14 11:00:00,9744.0 -2011-05-14 12:00:00,9911.0 -2011-05-14 13:00:00,9930.0 -2011-05-14 14:00:00,9852.0 -2011-05-14 15:00:00,9735.0 -2011-05-14 16:00:00,9636.0 -2011-05-14 17:00:00,9606.0 -2011-05-14 18:00:00,9626.0 -2011-05-14 19:00:00,9723.0 -2011-05-14 20:00:00,9748.0 -2011-05-14 21:00:00,9921.0 -2011-05-14 22:00:00,10194.0 -2011-05-14 23:00:00,9962.0 -2011-05-15 00:00:00,9444.0 -2011-05-13 01:00:00,11525.0 -2011-05-13 02:00:00,10616.0 -2011-05-13 03:00:00,9999.0 -2011-05-13 04:00:00,9547.0 -2011-05-13 05:00:00,9394.0 -2011-05-13 06:00:00,9423.0 -2011-05-13 07:00:00,9865.0 -2011-05-13 08:00:00,10732.0 -2011-05-13 09:00:00,11944.0 -2011-05-13 10:00:00,12740.0 -2011-05-13 11:00:00,13357.0 -2011-05-13 12:00:00,13626.0 -2011-05-13 13:00:00,13433.0 -2011-05-13 14:00:00,13242.0 -2011-05-13 15:00:00,12878.0 -2011-05-13 16:00:00,12326.0 -2011-05-13 17:00:00,11911.0 -2011-05-13 18:00:00,11544.0 -2011-05-13 19:00:00,11257.0 -2011-05-13 20:00:00,10989.0 -2011-05-13 21:00:00,10929.0 -2011-05-13 22:00:00,11166.0 -2011-05-13 23:00:00,10839.0 -2011-05-14 00:00:00,10081.0 -2011-05-12 01:00:00,11143.0 -2011-05-12 02:00:00,10282.0 -2011-05-12 03:00:00,9688.0 -2011-05-12 04:00:00,9283.0 -2011-05-12 05:00:00,9089.0 -2011-05-12 06:00:00,9178.0 -2011-05-12 07:00:00,9699.0 -2011-05-12 08:00:00,10622.0 -2011-05-12 09:00:00,11650.0 -2011-05-12 10:00:00,12425.0 -2011-05-12 11:00:00,13079.0 -2011-05-12 12:00:00,13647.0 -2011-05-12 13:00:00,14098.0 -2011-05-12 14:00:00,14679.0 -2011-05-12 15:00:00,15278.0 -2011-05-12 16:00:00,15632.0 -2011-05-12 17:00:00,15951.0 -2011-05-12 18:00:00,16049.0 -2011-05-12 19:00:00,15883.0 -2011-05-12 20:00:00,15309.0 -2011-05-12 21:00:00,14855.0 -2011-05-12 22:00:00,14820.0 -2011-05-12 23:00:00,14145.0 -2011-05-13 00:00:00,12843.0 -2011-05-11 01:00:00,11618.0 -2011-05-11 02:00:00,10767.0 -2011-05-11 03:00:00,10175.0 -2011-05-11 04:00:00,9733.0 -2011-05-11 05:00:00,9486.0 -2011-05-11 06:00:00,9573.0 -2011-05-11 07:00:00,10061.0 -2011-05-11 08:00:00,11036.0 -2011-05-11 09:00:00,12254.0 -2011-05-11 10:00:00,13172.0 -2011-05-11 11:00:00,13921.0 -2011-05-11 12:00:00,14705.0 -2011-05-11 13:00:00,15356.0 -2011-05-11 14:00:00,15851.0 -2011-05-11 15:00:00,16127.0 -2011-05-11 16:00:00,16223.0 -2011-05-11 17:00:00,16169.0 -2011-05-11 18:00:00,15926.0 -2011-05-11 19:00:00,15539.0 -2011-05-11 20:00:00,14840.0 -2011-05-11 21:00:00,14377.0 -2011-05-11 22:00:00,14298.0 -2011-05-11 23:00:00,13619.0 -2011-05-12 00:00:00,12359.0 -2011-05-10 01:00:00,9288.0 -2011-05-10 02:00:00,8723.0 -2011-05-10 03:00:00,8373.0 -2011-05-10 04:00:00,8116.0 -2011-05-10 05:00:00,8065.0 -2011-05-10 06:00:00,8235.0 -2011-05-10 07:00:00,8801.0 -2011-05-10 08:00:00,9720.0 -2011-05-10 09:00:00,10680.0 -2011-05-10 10:00:00,11309.0 -2011-05-10 11:00:00,11796.0 -2011-05-10 12:00:00,12321.0 -2011-05-10 13:00:00,12711.0 -2011-05-10 14:00:00,13114.0 -2011-05-10 15:00:00,13524.0 -2011-05-10 16:00:00,13725.0 -2011-05-10 17:00:00,13954.0 -2011-05-10 18:00:00,14077.0 -2011-05-10 19:00:00,14072.0 -2011-05-10 20:00:00,13835.0 -2011-05-10 21:00:00,13732.0 -2011-05-10 22:00:00,14134.0 -2011-05-10 23:00:00,13773.0 -2011-05-11 00:00:00,12722.0 -2011-05-09 01:00:00,8378.0 -2011-05-09 02:00:00,8015.0 -2011-05-09 03:00:00,7787.0 -2011-05-09 04:00:00,7690.0 -2011-05-09 05:00:00,7696.0 -2011-05-09 06:00:00,7957.0 -2011-05-09 07:00:00,8537.0 -2011-05-09 08:00:00,9456.0 -2011-05-09 09:00:00,10391.0 -2011-05-09 10:00:00,10939.0 -2011-05-09 11:00:00,11123.0 -2011-05-09 12:00:00,11354.0 -2011-05-09 13:00:00,11459.0 -2011-05-09 14:00:00,11475.0 -2011-05-09 15:00:00,11531.0 -2011-05-09 16:00:00,11453.0 -2011-05-09 17:00:00,11371.0 -2011-05-09 18:00:00,11211.0 -2011-05-09 19:00:00,11049.0 -2011-05-09 20:00:00,10816.0 -2011-05-09 21:00:00,10944.0 -2011-05-09 22:00:00,11393.0 -2011-05-09 23:00:00,11026.0 -2011-05-10 00:00:00,10138.0 -2011-05-08 01:00:00,8749.0 -2011-05-08 02:00:00,8272.0 -2011-05-08 03:00:00,7982.0 -2011-05-08 04:00:00,7768.0 -2011-05-08 05:00:00,7626.0 -2011-05-08 06:00:00,7698.0 -2011-05-08 07:00:00,7719.0 -2011-05-08 08:00:00,7692.0 -2011-05-08 09:00:00,7932.0 -2011-05-08 10:00:00,8304.0 -2011-05-08 11:00:00,8582.0 -2011-05-08 12:00:00,8743.0 -2011-05-08 13:00:00,8801.0 -2011-05-08 14:00:00,8808.0 -2011-05-08 15:00:00,8759.0 -2011-05-08 16:00:00,8756.0 -2011-05-08 17:00:00,8679.0 -2011-05-08 18:00:00,8696.0 -2011-05-08 19:00:00,8694.0 -2011-05-08 20:00:00,8819.0 -2011-05-08 21:00:00,9034.0 -2011-05-08 22:00:00,9627.0 -2011-05-08 23:00:00,9510.0 -2011-05-09 00:00:00,8998.0 -2011-05-07 01:00:00,9209.0 -2011-05-07 02:00:00,8646.0 -2011-05-07 03:00:00,8206.0 -2011-05-07 04:00:00,8010.0 -2011-05-07 05:00:00,7931.0 -2011-05-07 06:00:00,7928.0 -2011-05-07 07:00:00,8116.0 -2011-05-07 08:00:00,8313.0 -2011-05-07 09:00:00,8754.0 -2011-05-07 10:00:00,9276.0 -2011-05-07 11:00:00,9584.0 -2011-05-07 12:00:00,9762.0 -2011-05-07 13:00:00,9710.0 -2011-05-07 14:00:00,9668.0 -2011-05-07 15:00:00,9508.0 -2011-05-07 16:00:00,9456.0 -2011-05-07 17:00:00,9370.0 -2011-05-07 18:00:00,9326.0 -2011-05-07 19:00:00,9277.0 -2011-05-07 20:00:00,9219.0 -2011-05-07 21:00:00,9312.0 -2011-05-07 22:00:00,9836.0 -2011-05-07 23:00:00,9741.0 -2011-05-08 00:00:00,9324.0 -2011-05-06 01:00:00,9424.0 -2011-05-06 02:00:00,8889.0 -2011-05-06 03:00:00,8551.0 -2011-05-06 04:00:00,8410.0 -2011-05-06 05:00:00,8369.0 -2011-05-06 06:00:00,8473.0 -2011-05-06 07:00:00,8997.0 -2011-05-06 08:00:00,9815.0 -2011-05-06 09:00:00,10559.0 -2011-05-06 10:00:00,10953.0 -2011-05-06 11:00:00,11115.0 -2011-05-06 12:00:00,11275.0 -2011-05-06 13:00:00,11298.0 -2011-05-06 14:00:00,11263.0 -2011-05-06 15:00:00,11267.0 -2011-05-06 16:00:00,11127.0 -2011-05-06 17:00:00,10935.0 -2011-05-06 18:00:00,10762.0 -2011-05-06 19:00:00,10525.0 -2011-05-06 20:00:00,10342.0 -2011-05-06 21:00:00,10429.0 -2011-05-06 22:00:00,10857.0 -2011-05-06 23:00:00,10628.0 -2011-05-07 00:00:00,9911.0 -2011-05-05 01:00:00,9403.0 -2011-05-05 02:00:00,8888.0 -2011-05-05 03:00:00,8565.0 -2011-05-05 04:00:00,8400.0 -2011-05-05 05:00:00,8406.0 -2011-05-05 06:00:00,8645.0 -2011-05-05 07:00:00,9233.0 -2011-05-05 08:00:00,10069.0 -2011-05-05 09:00:00,10835.0 -2011-05-05 10:00:00,11180.0 -2011-05-05 11:00:00,11317.0 -2011-05-05 12:00:00,11439.0 -2011-05-05 13:00:00,11325.0 -2011-05-05 14:00:00,11320.0 -2011-05-05 15:00:00,11330.0 -2011-05-05 16:00:00,11265.0 -2011-05-05 17:00:00,11235.0 -2011-05-05 18:00:00,11089.0 -2011-05-05 19:00:00,10934.0 -2011-05-05 20:00:00,10828.0 -2011-05-05 21:00:00,11079.0 -2011-05-05 22:00:00,11403.0 -2011-05-05 23:00:00,11010.0 -2011-05-06 00:00:00,10206.0 -2011-05-04 01:00:00,9652.0 -2011-05-04 02:00:00,9167.0 -2011-05-04 03:00:00,8871.0 -2011-05-04 04:00:00,8649.0 -2011-05-04 05:00:00,8603.0 -2011-05-04 06:00:00,8851.0 -2011-05-04 07:00:00,9505.0 -2011-05-04 08:00:00,10459.0 -2011-05-04 09:00:00,11158.0 -2011-05-04 10:00:00,11343.0 -2011-05-04 11:00:00,11383.0 -2011-05-04 12:00:00,11449.0 -2011-05-04 13:00:00,11381.0 -2011-05-04 14:00:00,11314.0 -2011-05-04 15:00:00,11316.0 -2011-05-04 16:00:00,11189.0 -2011-05-04 17:00:00,11071.0 -2011-05-04 18:00:00,10880.0 -2011-05-04 19:00:00,10727.0 -2011-05-04 20:00:00,10490.0 -2011-05-04 21:00:00,10608.0 -2011-05-04 22:00:00,11212.0 -2011-05-04 23:00:00,10938.0 -2011-05-05 00:00:00,10147.0 -2011-05-03 01:00:00,9334.0 -2011-05-03 02:00:00,8877.0 -2011-05-03 03:00:00,8594.0 -2011-05-03 04:00:00,8410.0 -2011-05-03 05:00:00,8395.0 -2011-05-03 06:00:00,8613.0 -2011-05-03 07:00:00,9259.0 -2011-05-03 08:00:00,10107.0 -2011-05-03 09:00:00,10785.0 -2011-05-03 10:00:00,11061.0 -2011-05-03 11:00:00,11173.0 -2011-05-03 12:00:00,11268.0 -2011-05-03 13:00:00,11286.0 -2011-05-03 14:00:00,11310.0 -2011-05-03 15:00:00,11397.0 -2011-05-03 16:00:00,11336.0 -2011-05-03 17:00:00,11282.0 -2011-05-03 18:00:00,11338.0 -2011-05-03 19:00:00,11412.0 -2011-05-03 20:00:00,11394.0 -2011-05-03 21:00:00,11532.0 -2011-05-03 22:00:00,11716.0 -2011-05-03 23:00:00,11274.0 -2011-05-04 00:00:00,10435.0 -2011-05-02 01:00:00,8621.0 -2011-05-02 02:00:00,8209.0 -2011-05-02 03:00:00,8001.0 -2011-05-02 04:00:00,7831.0 -2011-05-02 05:00:00,7848.0 -2011-05-02 06:00:00,8124.0 -2011-05-02 07:00:00,8790.0 -2011-05-02 08:00:00,9725.0 -2011-05-02 09:00:00,10484.0 -2011-05-02 10:00:00,10893.0 -2011-05-02 11:00:00,11045.0 -2011-05-02 12:00:00,11152.0 -2011-05-02 13:00:00,11164.0 -2011-05-02 14:00:00,11166.0 -2011-05-02 15:00:00,11201.0 -2011-05-02 16:00:00,11112.0 -2011-05-02 17:00:00,10954.0 -2011-05-02 18:00:00,10792.0 -2011-05-02 19:00:00,10661.0 -2011-05-02 20:00:00,10515.0 -2011-05-02 21:00:00,10718.0 -2011-05-02 22:00:00,11250.0 -2011-05-02 23:00:00,10859.0 -2011-05-03 00:00:00,10090.0 -2011-05-01 01:00:00,8829.0 -2011-05-01 02:00:00,8373.0 -2011-05-01 03:00:00,7993.0 -2011-05-01 04:00:00,7802.0 -2011-05-01 05:00:00,7637.0 -2011-05-01 06:00:00,7630.0 -2011-05-01 07:00:00,7698.0 -2011-05-01 08:00:00,7556.0 -2011-05-01 09:00:00,7854.0 -2011-05-01 10:00:00,8272.0 -2011-05-01 11:00:00,8597.0 -2011-05-01 12:00:00,8794.0 -2011-05-01 13:00:00,8892.0 -2011-05-01 14:00:00,8941.0 -2011-05-01 15:00:00,8904.0 -2011-05-01 16:00:00,8883.0 -2011-05-01 17:00:00,8835.0 -2011-05-01 18:00:00,8913.0 -2011-05-01 19:00:00,8973.0 -2011-05-01 20:00:00,9107.0 -2011-05-01 21:00:00,9420.0 -2011-05-01 22:00:00,9872.0 -2011-05-01 23:00:00,9643.0 -2011-05-02 00:00:00,9101.0 -2011-04-30 01:00:00,9360.0 -2011-04-30 02:00:00,8767.0 -2011-04-30 03:00:00,8434.0 -2011-04-30 04:00:00,8207.0 -2011-04-30 05:00:00,8137.0 -2011-04-30 06:00:00,8131.0 -2011-04-30 07:00:00,8418.0 -2011-04-30 08:00:00,8580.0 -2011-04-30 09:00:00,9028.0 -2011-04-30 10:00:00,9500.0 -2011-04-30 11:00:00,9704.0 -2011-04-30 12:00:00,9783.0 -2011-04-30 13:00:00,9796.0 -2011-04-30 14:00:00,9711.0 -2011-04-30 15:00:00,9592.0 -2011-04-30 16:00:00,9486.0 -2011-04-30 17:00:00,9466.0 -2011-04-30 18:00:00,9502.0 -2011-04-30 19:00:00,9598.0 -2011-04-30 20:00:00,9668.0 -2011-04-30 21:00:00,9910.0 -2011-04-30 22:00:00,10178.0 -2011-04-30 23:00:00,9939.0 -2011-05-01 00:00:00,9428.0 -2011-04-29 01:00:00,9745.0 -2011-04-29 02:00:00,9225.0 -2011-04-29 03:00:00,8895.0 -2011-04-29 04:00:00,8716.0 -2011-04-29 05:00:00,8648.0 -2011-04-29 06:00:00,8980.0 -2011-04-29 07:00:00,9591.0 -2011-04-29 08:00:00,10386.0 -2011-04-29 09:00:00,10965.0 -2011-04-29 10:00:00,11151.0 -2011-04-29 11:00:00,11207.0 -2011-04-29 12:00:00,11286.0 -2011-04-29 13:00:00,11279.0 -2011-04-29 14:00:00,11213.0 -2011-04-29 15:00:00,11203.0 -2011-04-29 16:00:00,11057.0 -2011-04-29 17:00:00,10846.0 -2011-04-29 18:00:00,10678.0 -2011-04-29 19:00:00,10472.0 -2011-04-29 20:00:00,10299.0 -2011-04-29 21:00:00,10551.0 -2011-04-29 22:00:00,10923.0 -2011-04-29 23:00:00,10676.0 -2011-04-30 00:00:00,10003.0 -2011-04-28 01:00:00,9508.0 -2011-04-28 02:00:00,9003.0 -2011-04-28 03:00:00,8709.0 -2011-04-28 04:00:00,8527.0 -2011-04-28 05:00:00,8506.0 -2011-04-28 06:00:00,8725.0 -2011-04-28 07:00:00,9380.0 -2011-04-28 08:00:00,10320.0 -2011-04-28 09:00:00,11103.0 -2011-04-28 10:00:00,11437.0 -2011-04-28 11:00:00,11573.0 -2011-04-28 12:00:00,11704.0 -2011-04-28 13:00:00,11730.0 -2011-04-28 14:00:00,11693.0 -2011-04-28 15:00:00,11661.0 -2011-04-28 16:00:00,11590.0 -2011-04-28 17:00:00,11486.0 -2011-04-28 18:00:00,11423.0 -2011-04-28 19:00:00,11369.0 -2011-04-28 20:00:00,11298.0 -2011-04-28 21:00:00,11522.0 -2011-04-28 22:00:00,11692.0 -2011-04-28 23:00:00,11342.0 -2011-04-29 00:00:00,10552.0 -2011-04-27 01:00:00,9475.0 -2011-04-27 02:00:00,8896.0 -2011-04-27 03:00:00,8574.0 -2011-04-27 04:00:00,8370.0 -2011-04-27 05:00:00,8287.0 -2011-04-27 06:00:00,8448.0 -2011-04-27 07:00:00,9086.0 -2011-04-27 08:00:00,10112.0 -2011-04-27 09:00:00,10964.0 -2011-04-27 10:00:00,11362.0 -2011-04-27 11:00:00,11514.0 -2011-04-27 12:00:00,11548.0 -2011-04-27 13:00:00,11523.0 -2011-04-27 14:00:00,11498.0 -2011-04-27 15:00:00,11551.0 -2011-04-27 16:00:00,11428.0 -2011-04-27 17:00:00,11261.0 -2011-04-27 18:00:00,11162.0 -2011-04-27 19:00:00,11152.0 -2011-04-27 20:00:00,11097.0 -2011-04-27 21:00:00,11302.0 -2011-04-27 22:00:00,11497.0 -2011-04-27 23:00:00,11075.0 -2011-04-28 00:00:00,10268.0 -2011-04-26 01:00:00,9584.0 -2011-04-26 02:00:00,9050.0 -2011-04-26 03:00:00,8744.0 -2011-04-26 04:00:00,8552.0 -2011-04-26 05:00:00,8511.0 -2011-04-26 06:00:00,8692.0 -2011-04-26 07:00:00,9366.0 -2011-04-26 08:00:00,10327.0 -2011-04-26 09:00:00,11041.0 -2011-04-26 10:00:00,11354.0 -2011-04-26 11:00:00,11528.0 -2011-04-26 12:00:00,11711.0 -2011-04-26 13:00:00,11736.0 -2011-04-26 14:00:00,11719.0 -2011-04-26 15:00:00,11726.0 -2011-04-26 16:00:00,11596.0 -2011-04-26 17:00:00,11450.0 -2011-04-26 18:00:00,11339.0 -2011-04-26 19:00:00,11181.0 -2011-04-26 20:00:00,11024.0 -2011-04-26 21:00:00,11196.0 -2011-04-26 22:00:00,11465.0 -2011-04-26 23:00:00,11004.0 -2011-04-27 00:00:00,10232.0 -2011-04-25 01:00:00,8418.0 -2011-04-25 02:00:00,8089.0 -2011-04-25 03:00:00,7886.0 -2011-04-25 04:00:00,7826.0 -2011-04-25 05:00:00,7827.0 -2011-04-25 06:00:00,8131.0 -2011-04-25 07:00:00,8868.0 -2011-04-25 08:00:00,9854.0 -2011-04-25 09:00:00,10699.0 -2011-04-25 10:00:00,11163.0 -2011-04-25 11:00:00,11353.0 -2011-04-25 12:00:00,11501.0 -2011-04-25 13:00:00,11449.0 -2011-04-25 14:00:00,11397.0 -2011-04-25 15:00:00,11351.0 -2011-04-25 16:00:00,11222.0 -2011-04-25 17:00:00,11104.0 -2011-04-25 18:00:00,11117.0 -2011-04-25 19:00:00,11160.0 -2011-04-25 20:00:00,11172.0 -2011-04-25 21:00:00,11467.0 -2011-04-25 22:00:00,11538.0 -2011-04-25 23:00:00,11068.0 -2011-04-26 00:00:00,10322.0 -2011-04-24 01:00:00,8678.0 -2011-04-24 02:00:00,8162.0 -2011-04-24 03:00:00,7893.0 -2011-04-24 04:00:00,7647.0 -2011-04-24 05:00:00,7570.0 -2011-04-24 06:00:00,7539.0 -2011-04-24 07:00:00,7671.0 -2011-04-24 08:00:00,7605.0 -2011-04-24 09:00:00,7875.0 -2011-04-24 10:00:00,8186.0 -2011-04-24 11:00:00,8407.0 -2011-04-24 12:00:00,8452.0 -2011-04-24 13:00:00,8530.0 -2011-04-24 14:00:00,8490.0 -2011-04-24 15:00:00,8429.0 -2011-04-24 16:00:00,8310.0 -2011-04-24 17:00:00,8246.0 -2011-04-24 18:00:00,8188.0 -2011-04-24 19:00:00,8267.0 -2011-04-24 20:00:00,8486.0 -2011-04-24 21:00:00,8977.0 -2011-04-24 22:00:00,9479.0 -2011-04-24 23:00:00,9341.0 -2011-04-25 00:00:00,8927.0 -2011-04-23 01:00:00,9243.0 -2011-04-23 02:00:00,8708.0 -2011-04-23 03:00:00,8372.0 -2011-04-23 04:00:00,8150.0 -2011-04-23 05:00:00,8044.0 -2011-04-23 06:00:00,8070.0 -2011-04-23 07:00:00,8260.0 -2011-04-23 08:00:00,8485.0 -2011-04-23 09:00:00,8709.0 -2011-04-23 10:00:00,9139.0 -2011-04-23 11:00:00,9466.0 -2011-04-23 12:00:00,9506.0 -2011-04-23 13:00:00,9519.0 -2011-04-23 14:00:00,9412.0 -2011-04-23 15:00:00,9271.0 -2011-04-23 16:00:00,9116.0 -2011-04-23 17:00:00,8991.0 -2011-04-23 18:00:00,8955.0 -2011-04-23 19:00:00,8932.0 -2011-04-23 20:00:00,8955.0 -2011-04-23 21:00:00,9295.0 -2011-04-23 22:00:00,9822.0 -2011-04-23 23:00:00,9671.0 -2011-04-24 00:00:00,9203.0 -2011-04-22 01:00:00,9713.0 -2011-04-22 02:00:00,9118.0 -2011-04-22 03:00:00,8857.0 -2011-04-22 04:00:00,8625.0 -2011-04-22 05:00:00,8591.0 -2011-04-22 06:00:00,8737.0 -2011-04-22 07:00:00,9247.0 -2011-04-22 08:00:00,9912.0 -2011-04-22 09:00:00,10399.0 -2011-04-22 10:00:00,10848.0 -2011-04-22 11:00:00,11102.0 -2011-04-22 12:00:00,11324.0 -2011-04-22 13:00:00,11425.0 -2011-04-22 14:00:00,11458.0 -2011-04-22 15:00:00,11309.0 -2011-04-22 16:00:00,11026.0 -2011-04-22 17:00:00,10754.0 -2011-04-22 18:00:00,10672.0 -2011-04-22 19:00:00,10743.0 -2011-04-22 20:00:00,10831.0 -2011-04-22 21:00:00,10966.0 -2011-04-22 22:00:00,10931.0 -2011-04-22 23:00:00,10597.0 -2011-04-23 00:00:00,9915.0 -2011-04-21 01:00:00,10013.0 -2011-04-21 02:00:00,9519.0 -2011-04-21 03:00:00,9229.0 -2011-04-21 04:00:00,9066.0 -2011-04-21 05:00:00,9067.0 -2011-04-21 06:00:00,9307.0 -2011-04-21 07:00:00,9962.0 -2011-04-21 08:00:00,10778.0 -2011-04-21 09:00:00,11350.0 -2011-04-21 10:00:00,11541.0 -2011-04-21 11:00:00,11525.0 -2011-04-21 12:00:00,11575.0 -2011-04-21 13:00:00,11519.0 -2011-04-21 14:00:00,11427.0 -2011-04-21 15:00:00,11396.0 -2011-04-21 16:00:00,11234.0 -2011-04-21 17:00:00,11052.0 -2011-04-21 18:00:00,10929.0 -2011-04-21 19:00:00,10939.0 -2011-04-21 20:00:00,10956.0 -2011-04-21 21:00:00,11309.0 -2011-04-21 22:00:00,11521.0 -2011-04-21 23:00:00,11200.0 -2011-04-22 00:00:00,10487.0 -2011-04-20 01:00:00,10279.0 -2011-04-20 02:00:00,9772.0 -2011-04-20 03:00:00,9479.0 -2011-04-20 04:00:00,9255.0 -2011-04-20 05:00:00,9238.0 -2011-04-20 06:00:00,9429.0 -2011-04-20 07:00:00,10128.0 -2011-04-20 08:00:00,11173.0 -2011-04-20 09:00:00,11832.0 -2011-04-20 10:00:00,12119.0 -2011-04-20 11:00:00,12226.0 -2011-04-20 12:00:00,12272.0 -2011-04-20 13:00:00,12176.0 -2011-04-20 14:00:00,12065.0 -2011-04-20 15:00:00,12029.0 -2011-04-20 16:00:00,11961.0 -2011-04-20 17:00:00,11917.0 -2011-04-20 18:00:00,11878.0 -2011-04-20 19:00:00,11800.0 -2011-04-20 20:00:00,11634.0 -2011-04-20 21:00:00,11801.0 -2011-04-20 22:00:00,12049.0 -2011-04-20 23:00:00,11611.0 -2011-04-21 00:00:00,10770.0 -2011-04-19 01:00:00,10136.0 -2011-04-19 02:00:00,9621.0 -2011-04-19 03:00:00,9312.0 -2011-04-19 04:00:00,9131.0 -2011-04-19 05:00:00,9123.0 -2011-04-19 06:00:00,9335.0 -2011-04-19 07:00:00,9985.0 -2011-04-19 08:00:00,11082.0 -2011-04-19 09:00:00,11887.0 -2011-04-19 10:00:00,12253.0 -2011-04-19 11:00:00,12468.0 -2011-04-19 12:00:00,12745.0 -2011-04-19 13:00:00,12726.0 -2011-04-19 14:00:00,12673.0 -2011-04-19 15:00:00,12676.0 -2011-04-19 16:00:00,12632.0 -2011-04-19 17:00:00,12562.0 -2011-04-19 18:00:00,12480.0 -2011-04-19 19:00:00,12452.0 -2011-04-19 20:00:00,12386.0 -2011-04-19 21:00:00,12674.0 -2011-04-19 22:00:00,12600.0 -2011-04-19 23:00:00,12074.0 -2011-04-20 00:00:00,11144.0 -2011-04-18 01:00:00,8922.0 -2011-04-18 02:00:00,8594.0 -2011-04-18 03:00:00,8489.0 -2011-04-18 04:00:00,8453.0 -2011-04-18 05:00:00,8587.0 -2011-04-18 06:00:00,8932.0 -2011-04-18 07:00:00,9739.0 -2011-04-18 08:00:00,10876.0 -2011-04-18 09:00:00,11618.0 -2011-04-18 10:00:00,11995.0 -2011-04-18 11:00:00,12082.0 -2011-04-18 12:00:00,12248.0 -2011-04-18 13:00:00,12166.0 -2011-04-18 14:00:00,12051.0 -2011-04-18 15:00:00,11957.0 -2011-04-18 16:00:00,11722.0 -2011-04-18 17:00:00,11450.0 -2011-04-18 18:00:00,11353.0 -2011-04-18 19:00:00,11349.0 -2011-04-18 20:00:00,11378.0 -2011-04-18 21:00:00,11839.0 -2011-04-18 22:00:00,12071.0 -2011-04-18 23:00:00,11636.0 -2011-04-19 00:00:00,10885.0 -2011-04-17 01:00:00,9542.0 -2011-04-17 02:00:00,9081.0 -2011-04-17 03:00:00,8710.0 -2011-04-17 04:00:00,8581.0 -2011-04-17 05:00:00,8510.0 -2011-04-17 06:00:00,8511.0 -2011-04-17 07:00:00,8696.0 -2011-04-17 08:00:00,8674.0 -2011-04-17 09:00:00,8886.0 -2011-04-17 10:00:00,9196.0 -2011-04-17 11:00:00,9375.0 -2011-04-17 12:00:00,9373.0 -2011-04-17 13:00:00,9304.0 -2011-04-17 14:00:00,9282.0 -2011-04-17 15:00:00,9195.0 -2011-04-17 16:00:00,9098.0 -2011-04-17 17:00:00,9022.0 -2011-04-17 18:00:00,9066.0 -2011-04-17 19:00:00,9142.0 -2011-04-17 20:00:00,9296.0 -2011-04-17 21:00:00,9893.0 -2011-04-17 22:00:00,10251.0 -2011-04-17 23:00:00,9964.0 -2011-04-18 00:00:00,9438.0 -2011-04-16 01:00:00,9712.0 -2011-04-16 02:00:00,9084.0 -2011-04-16 03:00:00,8728.0 -2011-04-16 04:00:00,8434.0 -2011-04-16 05:00:00,8338.0 -2011-04-16 06:00:00,8376.0 -2011-04-16 07:00:00,8641.0 -2011-04-16 08:00:00,8965.0 -2011-04-16 09:00:00,9366.0 -2011-04-16 10:00:00,9818.0 -2011-04-16 11:00:00,10166.0 -2011-04-16 12:00:00,10301.0 -2011-04-16 13:00:00,10331.0 -2011-04-16 14:00:00,10269.0 -2011-04-16 15:00:00,10285.0 -2011-04-16 16:00:00,10258.0 -2011-04-16 17:00:00,10288.0 -2011-04-16 18:00:00,10308.0 -2011-04-16 19:00:00,10438.0 -2011-04-16 20:00:00,10499.0 -2011-04-16 21:00:00,10800.0 -2011-04-16 22:00:00,10915.0 -2011-04-16 23:00:00,10607.0 -2011-04-17 00:00:00,10153.0 -2011-04-15 01:00:00,9751.0 -2011-04-15 02:00:00,9197.0 -2011-04-15 03:00:00,8923.0 -2011-04-15 04:00:00,8730.0 -2011-04-15 05:00:00,8732.0 -2011-04-15 06:00:00,8966.0 -2011-04-15 07:00:00,9678.0 -2011-04-15 08:00:00,10626.0 -2011-04-15 09:00:00,11215.0 -2011-04-15 10:00:00,11487.0 -2011-04-15 11:00:00,11562.0 -2011-04-15 12:00:00,11717.0 -2011-04-15 13:00:00,11675.0 -2011-04-15 14:00:00,11626.0 -2011-04-15 15:00:00,11604.0 -2011-04-15 16:00:00,11565.0 -2011-04-15 17:00:00,11532.0 -2011-04-15 18:00:00,11593.0 -2011-04-15 19:00:00,11617.0 -2011-04-15 20:00:00,11588.0 -2011-04-15 21:00:00,11712.0 -2011-04-15 22:00:00,11586.0 -2011-04-15 23:00:00,11215.0 -2011-04-16 00:00:00,10461.0 -2011-04-14 01:00:00,9276.0 -2011-04-14 02:00:00,8742.0 -2011-04-14 03:00:00,8379.0 -2011-04-14 04:00:00,8204.0 -2011-04-14 05:00:00,8166.0 -2011-04-14 06:00:00,8366.0 -2011-04-14 07:00:00,9050.0 -2011-04-14 08:00:00,10085.0 -2011-04-14 09:00:00,10830.0 -2011-04-14 10:00:00,11174.0 -2011-04-14 11:00:00,11310.0 -2011-04-14 12:00:00,11447.0 -2011-04-14 13:00:00,11424.0 -2011-04-14 14:00:00,11353.0 -2011-04-14 15:00:00,11357.0 -2011-04-14 16:00:00,11223.0 -2011-04-14 17:00:00,11085.0 -2011-04-14 18:00:00,10988.0 -2011-04-14 19:00:00,10931.0 -2011-04-14 20:00:00,11006.0 -2011-04-14 21:00:00,11545.0 -2011-04-14 22:00:00,11790.0 -2011-04-14 23:00:00,11353.0 -2011-04-15 00:00:00,10511.0 -2011-04-13 01:00:00,9429.0 -2011-04-13 02:00:00,8918.0 -2011-04-13 03:00:00,8622.0 -2011-04-13 04:00:00,8449.0 -2011-04-13 05:00:00,8414.0 -2011-04-13 06:00:00,8648.0 -2011-04-13 07:00:00,9350.0 -2011-04-13 08:00:00,10212.0 -2011-04-13 09:00:00,10855.0 -2011-04-13 10:00:00,11110.0 -2011-04-13 11:00:00,11169.0 -2011-04-13 12:00:00,11315.0 -2011-04-13 13:00:00,11333.0 -2011-04-13 14:00:00,11333.0 -2011-04-13 15:00:00,11405.0 -2011-04-13 16:00:00,11326.0 -2011-04-13 17:00:00,11240.0 -2011-04-13 18:00:00,11102.0 -2011-04-13 19:00:00,10913.0 -2011-04-13 20:00:00,10749.0 -2011-04-13 21:00:00,11091.0 -2011-04-13 22:00:00,11428.0 -2011-04-13 23:00:00,11008.0 -2011-04-14 00:00:00,10151.0 -2011-04-12 01:00:00,9274.0 -2011-04-12 02:00:00,8705.0 -2011-04-12 03:00:00,8412.0 -2011-04-12 04:00:00,8246.0 -2011-04-12 05:00:00,8188.0 -2011-04-12 06:00:00,8432.0 -2011-04-12 07:00:00,9120.0 -2011-04-12 08:00:00,10117.0 -2011-04-12 09:00:00,10781.0 -2011-04-12 10:00:00,11083.0 -2011-04-12 11:00:00,11185.0 -2011-04-12 12:00:00,11307.0 -2011-04-12 13:00:00,11288.0 -2011-04-12 14:00:00,11283.0 -2011-04-12 15:00:00,11314.0 -2011-04-12 16:00:00,11180.0 -2011-04-12 17:00:00,11028.0 -2011-04-12 18:00:00,10874.0 -2011-04-12 19:00:00,10719.0 -2011-04-12 20:00:00,10590.0 -2011-04-12 21:00:00,11003.0 -2011-04-12 22:00:00,11433.0 -2011-04-12 23:00:00,11007.0 -2011-04-13 00:00:00,10232.0 -2011-04-11 01:00:00,9607.0 -2011-04-11 02:00:00,9043.0 -2011-04-11 03:00:00,8582.0 -2011-04-11 04:00:00,8302.0 -2011-04-11 05:00:00,8105.0 -2011-04-11 06:00:00,8269.0 -2011-04-11 07:00:00,8866.0 -2011-04-11 08:00:00,9783.0 -2011-04-11 09:00:00,10449.0 -2011-04-11 10:00:00,10908.0 -2011-04-11 11:00:00,11099.0 -2011-04-11 12:00:00,11203.0 -2011-04-11 13:00:00,11298.0 -2011-04-11 14:00:00,11319.0 -2011-04-11 15:00:00,11374.0 -2011-04-11 16:00:00,11325.0 -2011-04-11 17:00:00,11241.0 -2011-04-11 18:00:00,11126.0 -2011-04-11 19:00:00,10989.0 -2011-04-11 20:00:00,10955.0 -2011-04-11 21:00:00,11332.0 -2011-04-11 22:00:00,11407.0 -2011-04-11 23:00:00,10907.0 -2011-04-12 00:00:00,10072.0 -2011-04-10 01:00:00,8934.0 -2011-04-10 02:00:00,8495.0 -2011-04-10 03:00:00,8168.0 -2011-04-10 04:00:00,7972.0 -2011-04-10 05:00:00,7793.0 -2011-04-10 06:00:00,7833.0 -2011-04-10 07:00:00,7923.0 -2011-04-10 08:00:00,7981.0 -2011-04-10 09:00:00,8099.0 -2011-04-10 10:00:00,8578.0 -2011-04-10 11:00:00,8959.0 -2011-04-10 12:00:00,9352.0 -2011-04-10 13:00:00,9561.0 -2011-04-10 14:00:00,9787.0 -2011-04-10 15:00:00,9924.0 -2011-04-10 16:00:00,10027.0 -2011-04-10 17:00:00,10128.0 -2011-04-10 18:00:00,10176.0 -2011-04-10 19:00:00,10423.0 -2011-04-10 20:00:00,10520.0 -2011-04-10 21:00:00,11012.0 -2011-04-10 22:00:00,11324.0 -2011-04-10 23:00:00,11001.0 -2011-04-11 00:00:00,10357.0 -2011-04-09 01:00:00,9695.0 -2011-04-09 02:00:00,9132.0 -2011-04-09 03:00:00,8807.0 -2011-04-09 04:00:00,8626.0 -2011-04-09 05:00:00,8478.0 -2011-04-09 06:00:00,8554.0 -2011-04-09 07:00:00,8779.0 -2011-04-09 08:00:00,9203.0 -2011-04-09 09:00:00,9534.0 -2011-04-09 10:00:00,9934.0 -2011-04-09 11:00:00,10110.0 -2011-04-09 12:00:00,10162.0 -2011-04-09 13:00:00,10038.0 -2011-04-09 14:00:00,9900.0 -2011-04-09 15:00:00,9682.0 -2011-04-09 16:00:00,9593.0 -2011-04-09 17:00:00,9445.0 -2011-04-09 18:00:00,9432.0 -2011-04-09 19:00:00,9383.0 -2011-04-09 20:00:00,9420.0 -2011-04-09 21:00:00,9848.0 -2011-04-09 22:00:00,10259.0 -2011-04-09 23:00:00,9983.0 -2011-04-10 00:00:00,9502.0 -2011-04-08 01:00:00,9944.0 -2011-04-08 02:00:00,9377.0 -2011-04-08 03:00:00,9067.0 -2011-04-08 04:00:00,8909.0 -2011-04-08 05:00:00,8947.0 -2011-04-08 06:00:00,9092.0 -2011-04-08 07:00:00,9768.0 -2011-04-08 08:00:00,10886.0 -2011-04-08 09:00:00,11611.0 -2011-04-08 10:00:00,11774.0 -2011-04-08 11:00:00,11882.0 -2011-04-08 12:00:00,11960.0 -2011-04-08 13:00:00,11970.0 -2011-04-08 14:00:00,11898.0 -2011-04-08 15:00:00,11851.0 -2011-04-08 16:00:00,11717.0 -2011-04-08 17:00:00,11558.0 -2011-04-08 18:00:00,11399.0 -2011-04-08 19:00:00,11234.0 -2011-04-08 20:00:00,11207.0 -2011-04-08 21:00:00,11548.0 -2011-04-08 22:00:00,11543.0 -2011-04-08 23:00:00,11184.0 -2011-04-09 00:00:00,10491.0 -2011-04-07 01:00:00,9661.0 -2011-04-07 02:00:00,9185.0 -2011-04-07 03:00:00,8884.0 -2011-04-07 04:00:00,8711.0 -2011-04-07 05:00:00,8687.0 -2011-04-07 06:00:00,8896.0 -2011-04-07 07:00:00,9573.0 -2011-04-07 08:00:00,10637.0 -2011-04-07 09:00:00,11331.0 -2011-04-07 10:00:00,11617.0 -2011-04-07 11:00:00,11712.0 -2011-04-07 12:00:00,11761.0 -2011-04-07 13:00:00,11698.0 -2011-04-07 14:00:00,11594.0 -2011-04-07 15:00:00,11621.0 -2011-04-07 16:00:00,11523.0 -2011-04-07 17:00:00,11401.0 -2011-04-07 18:00:00,11332.0 -2011-04-07 19:00:00,11383.0 -2011-04-07 20:00:00,11543.0 -2011-04-07 21:00:00,12019.0 -2011-04-07 22:00:00,11940.0 -2011-04-07 23:00:00,11513.0 -2011-04-08 00:00:00,10733.0 -2011-04-06 01:00:00,9757.0 -2011-04-06 02:00:00,9231.0 -2011-04-06 03:00:00,8909.0 -2011-04-06 04:00:00,8692.0 -2011-04-06 05:00:00,8616.0 -2011-04-06 06:00:00,8818.0 -2011-04-06 07:00:00,9519.0 -2011-04-06 08:00:00,10609.0 -2011-04-06 09:00:00,11245.0 -2011-04-06 10:00:00,11528.0 -2011-04-06 11:00:00,11674.0 -2011-04-06 12:00:00,11742.0 -2011-04-06 13:00:00,11646.0 -2011-04-06 14:00:00,11543.0 -2011-04-06 15:00:00,11545.0 -2011-04-06 16:00:00,11441.0 -2011-04-06 17:00:00,11305.0 -2011-04-06 18:00:00,11155.0 -2011-04-06 19:00:00,11016.0 -2011-04-06 20:00:00,10913.0 -2011-04-06 21:00:00,11417.0 -2011-04-06 22:00:00,11655.0 -2011-04-06 23:00:00,11225.0 -2011-04-07 00:00:00,10438.0 -2011-04-05 01:00:00,9839.0 -2011-04-05 02:00:00,9343.0 -2011-04-05 03:00:00,9026.0 -2011-04-05 04:00:00,8853.0 -2011-04-05 05:00:00,8866.0 -2011-04-05 06:00:00,9131.0 -2011-04-05 07:00:00,9915.0 -2011-04-05 08:00:00,10957.0 -2011-04-05 09:00:00,11460.0 -2011-04-05 10:00:00,11613.0 -2011-04-05 11:00:00,11635.0 -2011-04-05 12:00:00,11700.0 -2011-04-05 13:00:00,11693.0 -2011-04-05 14:00:00,11641.0 -2011-04-05 15:00:00,11560.0 -2011-04-05 16:00:00,11400.0 -2011-04-05 17:00:00,11202.0 -2011-04-05 18:00:00,11063.0 -2011-04-05 19:00:00,10960.0 -2011-04-05 20:00:00,10957.0 -2011-04-05 21:00:00,11491.0 -2011-04-05 22:00:00,11713.0 -2011-04-05 23:00:00,11321.0 -2011-04-06 00:00:00,10535.0 -2011-04-04 01:00:00,8802.0 -2011-04-04 02:00:00,8402.0 -2011-04-04 03:00:00,8035.0 -2011-04-04 04:00:00,8057.0 -2011-04-04 05:00:00,8059.0 -2011-04-04 06:00:00,8214.0 -2011-04-04 07:00:00,8910.0 -2011-04-04 08:00:00,10096.0 -2011-04-04 09:00:00,10908.0 -2011-04-04 10:00:00,11266.0 -2011-04-04 11:00:00,11593.0 -2011-04-04 12:00:00,11578.0 -2011-04-04 13:00:00,11582.0 -2011-04-04 14:00:00,11625.0 -2011-04-04 15:00:00,11676.0 -2011-04-04 16:00:00,11572.0 -2011-04-04 17:00:00,11413.0 -2011-04-04 18:00:00,11389.0 -2011-04-04 19:00:00,11446.0 -2011-04-04 20:00:00,11456.0 -2011-04-04 21:00:00,11893.0 -2011-04-04 22:00:00,11905.0 -2011-04-04 23:00:00,11442.0 -2011-04-05 00:00:00,10646.0 -2011-04-03 01:00:00,9318.0 -2011-04-03 02:00:00,8851.0 -2011-04-03 03:00:00,8539.0 -2011-04-03 04:00:00,8392.0 -2011-04-03 05:00:00,8263.0 -2011-04-03 06:00:00,8290.0 -2011-04-03 07:00:00,8416.0 -2011-04-03 08:00:00,8526.0 -2011-04-03 09:00:00,8755.0 -2011-04-03 10:00:00,9080.0 -2011-04-03 11:00:00,9345.0 -2011-04-03 12:00:00,9567.0 -2011-04-03 13:00:00,9633.0 -2011-04-03 14:00:00,9448.0 -2011-04-03 15:00:00,9482.0 -2011-04-03 16:00:00,9481.0 -2011-04-03 17:00:00,9453.0 -2011-04-03 18:00:00,9407.0 -2011-04-03 19:00:00,9465.0 -2011-04-03 20:00:00,9578.0 -2011-04-03 21:00:00,10151.0 -2011-04-03 22:00:00,10235.0 -2011-04-03 23:00:00,9908.0 -2011-04-04 00:00:00,9418.0 -2011-04-02 01:00:00,10059.0 -2011-04-02 02:00:00,9498.0 -2011-04-02 03:00:00,9143.0 -2011-04-02 04:00:00,8916.0 -2011-04-02 05:00:00,8833.0 -2011-04-02 06:00:00,8890.0 -2011-04-02 07:00:00,9106.0 -2011-04-02 08:00:00,9566.0 -2011-04-02 09:00:00,9727.0 -2011-04-02 10:00:00,10016.0 -2011-04-02 11:00:00,10122.0 -2011-04-02 12:00:00,10202.0 -2011-04-02 13:00:00,10157.0 -2011-04-02 14:00:00,10076.0 -2011-04-02 15:00:00,9857.0 -2011-04-02 16:00:00,9667.0 -2011-04-02 17:00:00,9514.0 -2011-04-02 18:00:00,9457.0 -2011-04-02 19:00:00,9402.0 -2011-04-02 20:00:00,9501.0 -2011-04-02 21:00:00,10145.0 -2011-04-02 22:00:00,10509.0 -2011-04-02 23:00:00,10345.0 -2011-04-03 00:00:00,9855.0 -2011-04-01 01:00:00,10059.0 -2011-04-01 02:00:00,9592.0 -2011-04-01 03:00:00,9317.0 -2011-04-01 04:00:00,9159.0 -2011-04-01 05:00:00,9134.0 -2011-04-01 06:00:00,9351.0 -2011-04-01 07:00:00,9980.0 -2011-04-01 08:00:00,10915.0 -2011-04-01 09:00:00,11441.0 -2011-04-01 10:00:00,11686.0 -2011-04-01 11:00:00,11796.0 -2011-04-01 12:00:00,11915.0 -2011-04-01 13:00:00,11927.0 -2011-04-01 14:00:00,12000.0 -2011-04-01 15:00:00,12110.0 -2011-04-01 16:00:00,11993.0 -2011-04-01 17:00:00,11790.0 -2011-04-01 18:00:00,11632.0 -2011-04-01 19:00:00,11562.0 -2011-04-01 20:00:00,11463.0 -2011-04-01 21:00:00,11800.0 -2011-04-01 22:00:00,11763.0 -2011-04-01 23:00:00,11403.0 -2011-04-02 00:00:00,10760.0 -2011-03-31 01:00:00,10220.0 -2011-03-31 02:00:00,9737.0 -2011-03-31 03:00:00,9466.0 -2011-03-31 04:00:00,9320.0 -2011-03-31 05:00:00,9347.0 -2011-03-31 06:00:00,9598.0 -2011-03-31 07:00:00,10289.0 -2011-03-31 08:00:00,11293.0 -2011-03-31 09:00:00,11744.0 -2011-03-31 10:00:00,11899.0 -2011-03-31 11:00:00,11899.0 -2011-03-31 12:00:00,11860.0 -2011-03-31 13:00:00,11740.0 -2011-03-31 14:00:00,11621.0 -2011-03-31 15:00:00,11557.0 -2011-03-31 16:00:00,11401.0 -2011-03-31 17:00:00,11211.0 -2011-03-31 18:00:00,11073.0 -2011-03-31 19:00:00,10972.0 -2011-03-31 20:00:00,10933.0 -2011-03-31 21:00:00,11557.0 -2011-03-31 22:00:00,11708.0 -2011-03-31 23:00:00,11398.0 -2011-04-01 00:00:00,10737.0 -2011-03-30 01:00:00,10374.0 -2011-03-30 02:00:00,9845.0 -2011-03-30 03:00:00,9508.0 -2011-03-30 04:00:00,9324.0 -2011-03-30 05:00:00,9309.0 -2011-03-30 06:00:00,9523.0 -2011-03-30 07:00:00,10196.0 -2011-03-30 08:00:00,11222.0 -2011-03-30 09:00:00,11771.0 -2011-03-30 10:00:00,12044.0 -2011-03-30 11:00:00,12060.0 -2011-03-30 12:00:00,12015.0 -2011-03-30 13:00:00,11905.0 -2011-03-30 14:00:00,11743.0 -2011-03-30 15:00:00,11649.0 -2011-03-30 16:00:00,11509.0 -2011-03-30 17:00:00,11307.0 -2011-03-30 18:00:00,11176.0 -2011-03-30 19:00:00,11085.0 -2011-03-30 20:00:00,11062.0 -2011-03-30 21:00:00,11680.0 -2011-03-30 22:00:00,11906.0 -2011-03-30 23:00:00,11547.0 -2011-03-31 00:00:00,10897.0 -2011-03-29 01:00:00,10480.0 -2011-03-29 02:00:00,9978.0 -2011-03-29 03:00:00,9712.0 -2011-03-29 04:00:00,9532.0 -2011-03-29 05:00:00,9530.0 -2011-03-29 06:00:00,9731.0 -2011-03-29 07:00:00,10419.0 -2011-03-29 08:00:00,11452.0 -2011-03-29 09:00:00,11952.0 -2011-03-29 10:00:00,12177.0 -2011-03-29 11:00:00,12153.0 -2011-03-29 12:00:00,12121.0 -2011-03-29 13:00:00,12024.0 -2011-03-29 14:00:00,11917.0 -2011-03-29 15:00:00,11821.0 -2011-03-29 16:00:00,11694.0 -2011-03-29 17:00:00,11524.0 -2011-03-29 18:00:00,11355.0 -2011-03-29 19:00:00,11323.0 -2011-03-29 20:00:00,11346.0 -2011-03-29 21:00:00,12047.0 -2011-03-29 22:00:00,12177.0 -2011-03-29 23:00:00,11769.0 -2011-03-30 00:00:00,11089.0 -2011-03-28 01:00:00,10056.0 -2011-03-28 02:00:00,9706.0 -2011-03-28 03:00:00,9501.0 -2011-03-28 04:00:00,9452.0 -2011-03-28 05:00:00,9499.0 -2011-03-28 06:00:00,9810.0 -2011-03-28 07:00:00,10558.0 -2011-03-28 08:00:00,11609.0 -2011-03-28 09:00:00,12104.0 -2011-03-28 10:00:00,12284.0 -2011-03-28 11:00:00,12263.0 -2011-03-28 12:00:00,12297.0 -2011-03-28 13:00:00,12201.0 -2011-03-28 14:00:00,12095.0 -2011-03-28 15:00:00,12035.0 -2011-03-28 16:00:00,11854.0 -2011-03-28 17:00:00,11612.0 -2011-03-28 18:00:00,11499.0 -2011-03-28 19:00:00,11444.0 -2011-03-28 20:00:00,11460.0 -2011-03-28 21:00:00,12202.0 -2011-03-28 22:00:00,12340.0 -2011-03-28 23:00:00,11940.0 -2011-03-29 00:00:00,11190.0 -2011-03-27 01:00:00,10244.0 -2011-03-27 02:00:00,9751.0 -2011-03-27 03:00:00,9484.0 -2011-03-27 04:00:00,9288.0 -2011-03-27 05:00:00,9195.0 -2011-03-27 06:00:00,9274.0 -2011-03-27 07:00:00,9402.0 -2011-03-27 08:00:00,9632.0 -2011-03-27 09:00:00,9561.0 -2011-03-27 10:00:00,9784.0 -2011-03-27 11:00:00,9948.0 -2011-03-27 12:00:00,10049.0 -2011-03-27 13:00:00,10066.0 -2011-03-27 14:00:00,9967.0 -2011-03-27 15:00:00,9895.0 -2011-03-27 16:00:00,9819.0 -2011-03-27 17:00:00,9786.0 -2011-03-27 18:00:00,9797.0 -2011-03-27 19:00:00,9916.0 -2011-03-27 20:00:00,10128.0 -2011-03-27 21:00:00,11042.0 -2011-03-27 22:00:00,11247.0 -2011-03-27 23:00:00,11032.0 -2011-03-28 00:00:00,10561.0 -2011-03-26 01:00:00,10695.0 -2011-03-26 02:00:00,10131.0 -2011-03-26 03:00:00,9841.0 -2011-03-26 04:00:00,9615.0 -2011-03-26 05:00:00,9572.0 -2011-03-26 06:00:00,9661.0 -2011-03-26 07:00:00,9947.0 -2011-03-26 08:00:00,10422.0 -2011-03-26 09:00:00,10657.0 -2011-03-26 10:00:00,11005.0 -2011-03-26 11:00:00,11285.0 -2011-03-26 12:00:00,11302.0 -2011-03-26 13:00:00,11221.0 -2011-03-26 14:00:00,10976.0 -2011-03-26 15:00:00,10759.0 -2011-03-26 16:00:00,10551.0 -2011-03-26 17:00:00,10405.0 -2011-03-26 18:00:00,10352.0 -2011-03-26 19:00:00,10461.0 -2011-03-26 20:00:00,10660.0 -2011-03-26 21:00:00,11394.0 -2011-03-26 22:00:00,11507.0 -2011-03-26 23:00:00,11247.0 -2011-03-27 00:00:00,10778.0 -2011-03-25 01:00:00,10608.0 -2011-03-25 02:00:00,10068.0 -2011-03-25 03:00:00,9801.0 -2011-03-25 04:00:00,9621.0 -2011-03-25 05:00:00,9607.0 -2011-03-25 06:00:00,9831.0 -2011-03-25 07:00:00,10498.0 -2011-03-25 08:00:00,11586.0 -2011-03-25 09:00:00,12127.0 -2011-03-25 10:00:00,12377.0 -2011-03-25 11:00:00,12453.0 -2011-03-25 12:00:00,12505.0 -2011-03-25 13:00:00,12305.0 -2011-03-25 14:00:00,12126.0 -2011-03-25 15:00:00,12039.0 -2011-03-25 16:00:00,11843.0 -2011-03-25 17:00:00,11639.0 -2011-03-25 18:00:00,11509.0 -2011-03-25 19:00:00,11400.0 -2011-03-25 20:00:00,11486.0 -2011-03-25 21:00:00,12158.0 -2011-03-25 22:00:00,12211.0 -2011-03-25 23:00:00,11899.0 -2011-03-26 00:00:00,11276.0 -2011-03-24 01:00:00,10575.0 -2011-03-24 02:00:00,10076.0 -2011-03-24 03:00:00,9788.0 -2011-03-24 04:00:00,9615.0 -2011-03-24 05:00:00,9613.0 -2011-03-24 06:00:00,9841.0 -2011-03-24 07:00:00,10523.0 -2011-03-24 08:00:00,11721.0 -2011-03-24 09:00:00,12234.0 -2011-03-24 10:00:00,12436.0 -2011-03-24 11:00:00,12451.0 -2011-03-24 12:00:00,12508.0 -2011-03-24 13:00:00,12395.0 -2011-03-24 14:00:00,12301.0 -2011-03-24 15:00:00,12189.0 -2011-03-24 16:00:00,12067.0 -2011-03-24 17:00:00,11898.0 -2011-03-24 18:00:00,11758.0 -2011-03-24 19:00:00,11689.0 -2011-03-24 20:00:00,11686.0 -2011-03-24 21:00:00,12381.0 -2011-03-24 22:00:00,12477.0 -2011-03-24 23:00:00,12067.0 -2011-03-25 00:00:00,11331.0 -2011-03-23 01:00:00,10228.0 -2011-03-23 02:00:00,9690.0 -2011-03-23 03:00:00,9351.0 -2011-03-23 04:00:00,9181.0 -2011-03-23 05:00:00,9133.0 -2011-03-23 06:00:00,9365.0 -2011-03-23 07:00:00,10052.0 -2011-03-23 08:00:00,11208.0 -2011-03-23 09:00:00,11879.0 -2011-03-23 10:00:00,12196.0 -2011-03-23 11:00:00,12285.0 -2011-03-23 12:00:00,12337.0 -2011-03-23 13:00:00,12197.0 -2011-03-23 14:00:00,12102.0 -2011-03-23 15:00:00,12047.0 -2011-03-23 16:00:00,12031.0 -2011-03-23 17:00:00,12025.0 -2011-03-23 18:00:00,12077.0 -2011-03-23 19:00:00,12148.0 -2011-03-23 20:00:00,12205.0 -2011-03-23 21:00:00,12646.0 -2011-03-23 22:00:00,12558.0 -2011-03-23 23:00:00,12125.0 -2011-03-24 00:00:00,11353.0 -2011-03-22 01:00:00,9767.0 -2011-03-22 02:00:00,9284.0 -2011-03-22 03:00:00,8986.0 -2011-03-22 04:00:00,8832.0 -2011-03-22 05:00:00,8797.0 -2011-03-22 06:00:00,9027.0 -2011-03-22 07:00:00,9694.0 -2011-03-22 08:00:00,10924.0 -2011-03-22 09:00:00,11789.0 -2011-03-22 10:00:00,12044.0 -2011-03-22 11:00:00,12202.0 -2011-03-22 12:00:00,12298.0 -2011-03-22 13:00:00,12269.0 -2011-03-22 14:00:00,12226.0 -2011-03-22 15:00:00,12291.0 -2011-03-22 16:00:00,12258.0 -2011-03-22 17:00:00,12282.0 -2011-03-22 18:00:00,12284.0 -2011-03-22 19:00:00,12308.0 -2011-03-22 20:00:00,12314.0 -2011-03-22 21:00:00,12593.0 -2011-03-22 22:00:00,12374.0 -2011-03-22 23:00:00,11829.0 -2011-03-23 00:00:00,11042.0 -2011-03-21 01:00:00,8971.0 -2011-03-21 02:00:00,8569.0 -2011-03-21 03:00:00,8303.0 -2011-03-21 04:00:00,8198.0 -2011-03-21 05:00:00,8215.0 -2011-03-21 06:00:00,8460.0 -2011-03-21 07:00:00,9226.0 -2011-03-21 08:00:00,10467.0 -2011-03-21 09:00:00,11198.0 -2011-03-21 10:00:00,11439.0 -2011-03-21 11:00:00,11567.0 -2011-03-21 12:00:00,11612.0 -2011-03-21 13:00:00,11531.0 -2011-03-21 14:00:00,11413.0 -2011-03-21 15:00:00,11389.0 -2011-03-21 16:00:00,11259.0 -2011-03-21 17:00:00,11104.0 -2011-03-21 18:00:00,10986.0 -2011-03-21 19:00:00,10953.0 -2011-03-21 20:00:00,11092.0 -2011-03-21 21:00:00,11757.0 -2011-03-21 22:00:00,11719.0 -2011-03-21 23:00:00,11266.0 -2011-03-22 00:00:00,10492.0 -2011-03-20 01:00:00,9400.0 -2011-03-20 02:00:00,8919.0 -2011-03-20 03:00:00,8573.0 -2011-03-20 04:00:00,8347.0 -2011-03-20 05:00:00,8267.0 -2011-03-20 06:00:00,8224.0 -2011-03-20 07:00:00,8363.0 -2011-03-20 08:00:00,8645.0 -2011-03-20 09:00:00,8807.0 -2011-03-20 10:00:00,9174.0 -2011-03-20 11:00:00,9563.0 -2011-03-20 12:00:00,9864.0 -2011-03-20 13:00:00,10145.0 -2011-03-20 14:00:00,10192.0 -2011-03-20 15:00:00,10066.0 -2011-03-20 16:00:00,9942.0 -2011-03-20 17:00:00,9863.0 -2011-03-20 18:00:00,9734.0 -2011-03-20 19:00:00,9685.0 -2011-03-20 20:00:00,9902.0 -2011-03-20 21:00:00,10559.0 -2011-03-20 22:00:00,10513.0 -2011-03-20 23:00:00,10149.0 -2011-03-21 00:00:00,9580.0 -2011-03-19 01:00:00,9751.0 -2011-03-19 02:00:00,9199.0 -2011-03-19 03:00:00,8905.0 -2011-03-19 04:00:00,8719.0 -2011-03-19 05:00:00,8640.0 -2011-03-19 06:00:00,8732.0 -2011-03-19 07:00:00,8988.0 -2011-03-19 08:00:00,9510.0 -2011-03-19 09:00:00,9715.0 -2011-03-19 10:00:00,10018.0 -2011-03-19 11:00:00,10164.0 -2011-03-19 12:00:00,10247.0 -2011-03-19 13:00:00,10128.0 -2011-03-19 14:00:00,9961.0 -2011-03-19 15:00:00,9754.0 -2011-03-19 16:00:00,9568.0 -2011-03-19 17:00:00,9445.0 -2011-03-19 18:00:00,9411.0 -2011-03-19 19:00:00,9495.0 -2011-03-19 20:00:00,9751.0 -2011-03-19 21:00:00,10500.0 -2011-03-19 22:00:00,10594.0 -2011-03-19 23:00:00,10385.0 -2011-03-20 00:00:00,9927.0 -2011-03-18 01:00:00,9529.0 -2011-03-18 02:00:00,8931.0 -2011-03-18 03:00:00,8585.0 -2011-03-18 04:00:00,8374.0 -2011-03-18 05:00:00,8343.0 -2011-03-18 06:00:00,8563.0 -2011-03-18 07:00:00,9204.0 -2011-03-18 08:00:00,10453.0 -2011-03-18 09:00:00,11043.0 -2011-03-18 10:00:00,11293.0 -2011-03-18 11:00:00,11361.0 -2011-03-18 12:00:00,11479.0 -2011-03-18 13:00:00,11387.0 -2011-03-18 14:00:00,11329.0 -2011-03-18 15:00:00,11284.0 -2011-03-18 16:00:00,11166.0 -2011-03-18 17:00:00,10967.0 -2011-03-18 18:00:00,10827.0 -2011-03-18 19:00:00,10703.0 -2011-03-18 20:00:00,10740.0 -2011-03-18 21:00:00,11349.0 -2011-03-18 22:00:00,11331.0 -2011-03-18 23:00:00,11034.0 -2011-03-19 00:00:00,10433.0 -2011-03-17 01:00:00,9770.0 -2011-03-17 02:00:00,9235.0 -2011-03-17 03:00:00,8912.0 -2011-03-17 04:00:00,8729.0 -2011-03-17 05:00:00,8670.0 -2011-03-17 06:00:00,8865.0 -2011-03-17 07:00:00,9530.0 -2011-03-17 08:00:00,10778.0 -2011-03-17 09:00:00,11420.0 -2011-03-17 10:00:00,11547.0 -2011-03-17 11:00:00,11572.0 -2011-03-17 12:00:00,11687.0 -2011-03-17 13:00:00,11697.0 -2011-03-17 14:00:00,11701.0 -2011-03-17 15:00:00,11744.0 -2011-03-17 16:00:00,11681.0 -2011-03-17 17:00:00,11577.0 -2011-03-17 18:00:00,11527.0 -2011-03-17 19:00:00,11430.0 -2011-03-17 20:00:00,11511.0 -2011-03-17 21:00:00,11879.0 -2011-03-17 22:00:00,11708.0 -2011-03-17 23:00:00,11203.0 -2011-03-18 00:00:00,10334.0 -2011-03-16 01:00:00,10377.0 -2011-03-16 02:00:00,9853.0 -2011-03-16 03:00:00,9534.0 -2011-03-16 04:00:00,9351.0 -2011-03-16 05:00:00,9327.0 -2011-03-16 06:00:00,9576.0 -2011-03-16 07:00:00,10281.0 -2011-03-16 08:00:00,11516.0 -2011-03-16 09:00:00,12053.0 -2011-03-16 10:00:00,12070.0 -2011-03-16 11:00:00,11986.0 -2011-03-16 12:00:00,11916.0 -2011-03-16 13:00:00,11849.0 -2011-03-16 14:00:00,11719.0 -2011-03-16 15:00:00,11684.0 -2011-03-16 16:00:00,11564.0 -2011-03-16 17:00:00,11380.0 -2011-03-16 18:00:00,11228.0 -2011-03-16 19:00:00,11042.0 -2011-03-16 20:00:00,11050.0 -2011-03-16 21:00:00,11707.0 -2011-03-16 22:00:00,11646.0 -2011-03-16 23:00:00,11259.0 -2011-03-17 00:00:00,10547.0 -2011-03-15 01:00:00,10527.0 -2011-03-15 02:00:00,9944.0 -2011-03-15 03:00:00,9644.0 -2011-03-15 04:00:00,9458.0 -2011-03-15 05:00:00,9448.0 -2011-03-15 06:00:00,9651.0 -2011-03-15 07:00:00,10372.0 -2011-03-15 08:00:00,11624.0 -2011-03-15 09:00:00,12340.0 -2011-03-15 10:00:00,12438.0 -2011-03-15 11:00:00,12432.0 -2011-03-15 12:00:00,12478.0 -2011-03-15 13:00:00,12481.0 -2011-03-15 14:00:00,12430.0 -2011-03-15 15:00:00,12462.0 -2011-03-15 16:00:00,12341.0 -2011-03-15 17:00:00,12183.0 -2011-03-15 18:00:00,12139.0 -2011-03-15 19:00:00,12202.0 -2011-03-15 20:00:00,12365.0 -2011-03-15 21:00:00,12706.0 -2011-03-15 22:00:00,12504.0 -2011-03-15 23:00:00,11978.0 -2011-03-16 00:00:00,11115.0 -2011-03-14 01:00:00,9943.0 -2011-03-14 02:00:00,9580.0 -2011-03-14 03:00:00,9349.0 -2011-03-14 04:00:00,9252.0 -2011-03-14 05:00:00,9298.0 -2011-03-14 06:00:00,9582.0 -2011-03-14 07:00:00,10345.0 -2011-03-14 08:00:00,11524.0 -2011-03-14 09:00:00,12226.0 -2011-03-14 10:00:00,12470.0 -2011-03-14 11:00:00,12480.0 -2011-03-14 12:00:00,12400.0 -2011-03-14 13:00:00,12218.0 -2011-03-14 14:00:00,12084.0 -2011-03-14 15:00:00,12000.0 -2011-03-14 16:00:00,11827.0 -2011-03-14 17:00:00,11638.0 -2011-03-14 18:00:00,11545.0 -2011-03-14 19:00:00,11502.0 -2011-03-14 20:00:00,11593.0 -2011-03-14 21:00:00,12458.0 -2011-03-14 22:00:00,12458.0 -2011-03-14 23:00:00,12039.0 -2011-03-15 00:00:00,11298.0 -2011-03-13 01:00:00,10149.0 -2011-03-13 02:00:00,9719.0 -2011-03-13 04:00:00,9477.0 -2011-03-13 05:00:00,9266.0 -2011-03-13 06:00:00,9290.0 -2011-03-13 07:00:00,9332.0 -2011-03-13 08:00:00,9621.0 -2011-03-13 09:00:00,9626.0 -2011-03-13 10:00:00,9831.0 -2011-03-13 11:00:00,10028.0 -2011-03-13 12:00:00,10079.0 -2011-03-13 13:00:00,10063.0 -2011-03-13 14:00:00,10006.0 -2011-03-13 15:00:00,9881.0 -2011-03-13 16:00:00,9842.0 -2011-03-13 17:00:00,9798.0 -2011-03-13 18:00:00,9929.0 -2011-03-13 19:00:00,10165.0 -2011-03-13 20:00:00,10505.0 -2011-03-13 21:00:00,11250.0 -2011-03-13 22:00:00,11262.0 -2011-03-13 23:00:00,11028.0 -2011-03-14 00:00:00,10529.0 -2011-03-12 01:00:00,10256.0 -2011-03-12 02:00:00,9653.0 -2011-03-12 03:00:00,9274.0 -2011-03-12 04:00:00,9098.0 -2011-03-12 05:00:00,9030.0 -2011-03-12 06:00:00,9091.0 -2011-03-12 07:00:00,9396.0 -2011-03-12 08:00:00,9779.0 -2011-03-12 09:00:00,10169.0 -2011-03-12 10:00:00,10653.0 -2011-03-12 11:00:00,11014.0 -2011-03-12 12:00:00,11200.0 -2011-03-12 13:00:00,11204.0 -2011-03-12 14:00:00,11078.0 -2011-03-12 15:00:00,10914.0 -2011-03-12 16:00:00,10757.0 -2011-03-12 17:00:00,10732.0 -2011-03-12 18:00:00,10795.0 -2011-03-12 19:00:00,11154.0 -2011-03-12 20:00:00,11704.0 -2011-03-12 21:00:00,11664.0 -2011-03-12 22:00:00,11477.0 -2011-03-12 23:00:00,11163.0 -2011-03-13 00:00:00,10650.0 -2011-03-11 01:00:00,10811.0 -2011-03-11 02:00:00,10288.0 -2011-03-11 03:00:00,10017.0 -2011-03-11 04:00:00,9844.0 -2011-03-11 05:00:00,9873.0 -2011-03-11 06:00:00,10151.0 -2011-03-11 07:00:00,10838.0 -2011-03-11 08:00:00,11679.0 -2011-03-11 09:00:00,12125.0 -2011-03-11 10:00:00,12206.0 -2011-03-11 11:00:00,12156.0 -2011-03-11 12:00:00,12154.0 -2011-03-11 13:00:00,12004.0 -2011-03-11 14:00:00,11851.0 -2011-03-11 15:00:00,11724.0 -2011-03-11 16:00:00,11523.0 -2011-03-11 17:00:00,11330.0 -2011-03-11 18:00:00,11323.0 -2011-03-11 19:00:00,11653.0 -2011-03-11 20:00:00,12272.0 -2011-03-11 21:00:00,12240.0 -2011-03-11 22:00:00,11964.0 -2011-03-11 23:00:00,11555.0 -2011-03-12 00:00:00,10931.0 -2011-03-10 01:00:00,10482.0 -2011-03-10 02:00:00,10003.0 -2011-03-10 03:00:00,9716.0 -2011-03-10 04:00:00,9588.0 -2011-03-10 05:00:00,9625.0 -2011-03-10 06:00:00,9913.0 -2011-03-10 07:00:00,10646.0 -2011-03-10 08:00:00,11751.0 -2011-03-10 09:00:00,12400.0 -2011-03-10 10:00:00,12683.0 -2011-03-10 11:00:00,12796.0 -2011-03-10 12:00:00,12897.0 -2011-03-10 13:00:00,12865.0 -2011-03-10 14:00:00,12805.0 -2011-03-10 15:00:00,12774.0 -2011-03-10 16:00:00,12678.0 -2011-03-10 17:00:00,12511.0 -2011-03-10 18:00:00,12383.0 -2011-03-10 19:00:00,12497.0 -2011-03-10 20:00:00,13189.0 -2011-03-10 21:00:00,13156.0 -2011-03-10 22:00:00,12839.0 -2011-03-10 23:00:00,12369.0 -2011-03-11 00:00:00,11533.0 -2011-03-09 01:00:00,10325.0 -2011-03-09 02:00:00,9796.0 -2011-03-09 03:00:00,9537.0 -2011-03-09 04:00:00,9420.0 -2011-03-09 05:00:00,9421.0 -2011-03-09 06:00:00,9723.0 -2011-03-09 07:00:00,10440.0 -2011-03-09 08:00:00,11487.0 -2011-03-09 09:00:00,12138.0 -2011-03-09 10:00:00,12321.0 -2011-03-09 11:00:00,12405.0 -2011-03-09 12:00:00,12455.0 -2011-03-09 13:00:00,12346.0 -2011-03-09 14:00:00,12250.0 -2011-03-09 15:00:00,12254.0 -2011-03-09 16:00:00,12217.0 -2011-03-09 17:00:00,12261.0 -2011-03-09 18:00:00,12409.0 -2011-03-09 19:00:00,12824.0 -2011-03-09 20:00:00,13007.0 -2011-03-09 21:00:00,12838.0 -2011-03-09 22:00:00,12542.0 -2011-03-09 23:00:00,12034.0 -2011-03-10 00:00:00,11250.0 -2011-03-08 01:00:00,10596.0 -2011-03-08 02:00:00,10116.0 -2011-03-08 03:00:00,9859.0 -2011-03-08 04:00:00,9691.0 -2011-03-08 05:00:00,9659.0 -2011-03-08 06:00:00,9923.0 -2011-03-08 07:00:00,10662.0 -2011-03-08 08:00:00,11599.0 -2011-03-08 09:00:00,12137.0 -2011-03-08 10:00:00,12247.0 -2011-03-08 11:00:00,12162.0 -2011-03-08 12:00:00,12161.0 -2011-03-08 13:00:00,12083.0 -2011-03-08 14:00:00,12020.0 -2011-03-08 15:00:00,12012.0 -2011-03-08 16:00:00,11943.0 -2011-03-08 17:00:00,11973.0 -2011-03-08 18:00:00,12292.0 -2011-03-08 19:00:00,12707.0 -2011-03-08 20:00:00,13144.0 -2011-03-08 21:00:00,12850.0 -2011-03-08 22:00:00,12529.0 -2011-03-08 23:00:00,11942.0 -2011-03-09 00:00:00,11073.0 -2011-03-07 01:00:00,10241.0 -2011-03-07 02:00:00,9882.0 -2011-03-07 03:00:00,9726.0 -2011-03-07 04:00:00,9626.0 -2011-03-07 05:00:00,9673.0 -2011-03-07 06:00:00,9989.0 -2011-03-07 07:00:00,10742.0 -2011-03-07 08:00:00,11687.0 -2011-03-07 09:00:00,12234.0 -2011-03-07 10:00:00,12547.0 -2011-03-07 11:00:00,12677.0 -2011-03-07 12:00:00,12770.0 -2011-03-07 13:00:00,12652.0 -2011-03-07 14:00:00,12610.0 -2011-03-07 15:00:00,12613.0 -2011-03-07 16:00:00,12553.0 -2011-03-07 17:00:00,12449.0 -2011-03-07 18:00:00,12511.0 -2011-03-07 19:00:00,12836.0 -2011-03-07 20:00:00,13273.0 -2011-03-07 21:00:00,13099.0 -2011-03-07 22:00:00,12800.0 -2011-03-07 23:00:00,12225.0 -2011-03-08 00:00:00,11337.0 -2011-03-06 01:00:00,10482.0 -2011-03-06 02:00:00,10075.0 -2011-03-06 03:00:00,9711.0 -2011-03-06 04:00:00,9564.0 -2011-03-06 05:00:00,9429.0 -2011-03-06 06:00:00,9519.0 -2011-03-06 07:00:00,9645.0 -2011-03-06 08:00:00,9745.0 -2011-03-06 09:00:00,9889.0 -2011-03-06 10:00:00,10247.0 -2011-03-06 11:00:00,10428.0 -2011-03-06 12:00:00,10549.0 -2011-03-06 13:00:00,10551.0 -2011-03-06 14:00:00,10431.0 -2011-03-06 15:00:00,10329.0 -2011-03-06 16:00:00,10204.0 -2011-03-06 17:00:00,10189.0 -2011-03-06 18:00:00,10379.0 -2011-03-06 19:00:00,10902.0 -2011-03-06 20:00:00,11816.0 -2011-03-06 21:00:00,11826.0 -2011-03-06 22:00:00,11681.0 -2011-03-06 23:00:00,11333.0 -2011-03-07 00:00:00,10783.0 -2011-03-05 01:00:00,10480.0 -2011-03-05 02:00:00,10000.0 -2011-03-05 03:00:00,9696.0 -2011-03-05 04:00:00,9506.0 -2011-03-05 05:00:00,9451.0 -2011-03-05 06:00:00,9505.0 -2011-03-05 07:00:00,9839.0 -2011-03-05 08:00:00,10234.0 -2011-03-05 09:00:00,10671.0 -2011-03-05 10:00:00,11082.0 -2011-03-05 11:00:00,11463.0 -2011-03-05 12:00:00,11585.0 -2011-03-05 13:00:00,11578.0 -2011-03-05 14:00:00,11500.0 -2011-03-05 15:00:00,11439.0 -2011-03-05 16:00:00,11320.0 -2011-03-05 17:00:00,11306.0 -2011-03-05 18:00:00,11377.0 -2011-03-05 19:00:00,11745.0 -2011-03-05 20:00:00,12180.0 -2011-03-05 21:00:00,12086.0 -2011-03-05 22:00:00,11854.0 -2011-03-05 23:00:00,11565.0 -2011-03-06 00:00:00,11025.0 -2011-03-04 01:00:00,10416.0 -2011-03-04 02:00:00,9909.0 -2011-03-04 03:00:00,9622.0 -2011-03-04 04:00:00,9418.0 -2011-03-04 05:00:00,9391.0 -2011-03-04 06:00:00,9596.0 -2011-03-04 07:00:00,10226.0 -2011-03-04 08:00:00,11347.0 -2011-03-04 09:00:00,11976.0 -2011-03-04 10:00:00,12190.0 -2011-03-04 11:00:00,12271.0 -2011-03-04 12:00:00,12166.0 -2011-03-04 13:00:00,11981.0 -2011-03-04 14:00:00,11830.0 -2011-03-04 15:00:00,11793.0 -2011-03-04 16:00:00,11765.0 -2011-03-04 17:00:00,11871.0 -2011-03-04 18:00:00,12206.0 -2011-03-04 19:00:00,12527.0 -2011-03-04 20:00:00,12606.0 -2011-03-04 21:00:00,12478.0 -2011-03-04 22:00:00,12300.0 -2011-03-04 23:00:00,11912.0 -2011-03-05 00:00:00,11180.0 -2011-03-03 01:00:00,11021.0 -2011-03-03 02:00:00,10536.0 -2011-03-03 03:00:00,10256.0 -2011-03-03 04:00:00,10098.0 -2011-03-03 05:00:00,10089.0 -2011-03-03 06:00:00,10333.0 -2011-03-03 07:00:00,11043.0 -2011-03-03 08:00:00,12099.0 -2011-03-03 09:00:00,12609.0 -2011-03-03 10:00:00,12742.0 -2011-03-03 11:00:00,12748.0 -2011-03-03 12:00:00,12705.0 -2011-03-03 13:00:00,12598.0 -2011-03-03 14:00:00,12429.0 -2011-03-03 15:00:00,12308.0 -2011-03-03 16:00:00,12170.0 -2011-03-03 17:00:00,12118.0 -2011-03-03 18:00:00,12231.0 -2011-03-03 19:00:00,12592.0 -2011-03-03 20:00:00,13092.0 -2011-03-03 21:00:00,12919.0 -2011-03-03 22:00:00,12565.0 -2011-03-03 23:00:00,12033.0 -2011-03-04 00:00:00,11166.0 -2011-03-02 01:00:00,10505.0 -2011-03-02 02:00:00,10043.0 -2011-03-02 03:00:00,9790.0 -2011-03-02 04:00:00,9649.0 -2011-03-02 05:00:00,9672.0 -2011-03-02 06:00:00,9965.0 -2011-03-02 07:00:00,10764.0 -2011-03-02 08:00:00,11779.0 -2011-03-02 09:00:00,12269.0 -2011-03-02 10:00:00,12412.0 -2011-03-02 11:00:00,12475.0 -2011-03-02 12:00:00,12527.0 -2011-03-02 13:00:00,12437.0 -2011-03-02 14:00:00,12362.0 -2011-03-02 15:00:00,12306.0 -2011-03-02 16:00:00,12166.0 -2011-03-02 17:00:00,12118.0 -2011-03-02 18:00:00,12224.0 -2011-03-02 19:00:00,12694.0 -2011-03-02 20:00:00,13402.0 -2011-03-02 21:00:00,13350.0 -2011-03-02 22:00:00,13090.0 -2011-03-02 23:00:00,12595.0 -2011-03-03 00:00:00,11750.0 -2011-03-01 01:00:00,10795.0 -2011-03-01 02:00:00,10351.0 -2011-03-01 03:00:00,10131.0 -2011-03-01 04:00:00,10005.0 -2011-03-01 05:00:00,10026.0 -2011-03-01 06:00:00,10314.0 -2011-03-01 07:00:00,11036.0 -2011-03-01 08:00:00,12041.0 -2011-03-01 09:00:00,12508.0 -2011-03-01 10:00:00,12621.0 -2011-03-01 11:00:00,12618.0 -2011-03-01 12:00:00,12581.0 -2011-03-01 13:00:00,12462.0 -2011-03-01 14:00:00,12307.0 -2011-03-01 15:00:00,12229.0 -2011-03-01 16:00:00,12086.0 -2011-03-01 17:00:00,12058.0 -2011-03-01 18:00:00,12128.0 -2011-03-01 19:00:00,12409.0 -2011-03-01 20:00:00,12987.0 -2011-03-01 21:00:00,12871.0 -2011-03-01 22:00:00,12587.0 -2011-03-01 23:00:00,12074.0 -2011-03-02 00:00:00,11278.0 -2011-02-28 01:00:00,10288.0 -2011-02-28 02:00:00,9917.0 -2011-02-28 03:00:00,9707.0 -2011-02-28 04:00:00,9652.0 -2011-02-28 05:00:00,9694.0 -2011-02-28 06:00:00,9998.0 -2011-02-28 07:00:00,10809.0 -2011-02-28 08:00:00,11976.0 -2011-02-28 09:00:00,12620.0 -2011-02-28 10:00:00,12764.0 -2011-02-28 11:00:00,12752.0 -2011-02-28 12:00:00,12769.0 -2011-02-28 13:00:00,12635.0 -2011-02-28 14:00:00,12479.0 -2011-02-28 15:00:00,12373.0 -2011-02-28 16:00:00,12198.0 -2011-02-28 17:00:00,12059.0 -2011-02-28 18:00:00,12126.0 -2011-02-28 19:00:00,12583.0 -2011-02-28 20:00:00,13257.0 -2011-02-28 21:00:00,13169.0 -2011-02-28 22:00:00,12900.0 -2011-02-28 23:00:00,12400.0 -2011-03-01 00:00:00,11527.0 -2011-02-27 01:00:00,10356.0 -2011-02-27 02:00:00,9890.0 -2011-02-27 03:00:00,9518.0 -2011-02-27 04:00:00,9372.0 -2011-02-27 05:00:00,9313.0 -2011-02-27 06:00:00,9371.0 -2011-02-27 07:00:00,9525.0 -2011-02-27 08:00:00,9664.0 -2011-02-27 09:00:00,9681.0 -2011-02-27 10:00:00,9917.0 -2011-02-27 11:00:00,9984.0 -2011-02-27 12:00:00,10072.0 -2011-02-27 13:00:00,10149.0 -2011-02-27 14:00:00,10307.0 -2011-02-27 15:00:00,10367.0 -2011-02-27 16:00:00,10516.0 -2011-02-27 17:00:00,10624.0 -2011-02-27 18:00:00,11018.0 -2011-02-27 19:00:00,11594.0 -2011-02-27 20:00:00,11941.0 -2011-02-27 21:00:00,11836.0 -2011-02-27 22:00:00,11679.0 -2011-02-27 23:00:00,11366.0 -2011-02-28 00:00:00,10878.0 -2011-02-26 01:00:00,10806.0 -2011-02-26 02:00:00,10280.0 -2011-02-26 03:00:00,9972.0 -2011-02-26 04:00:00,9785.0 -2011-02-26 05:00:00,9739.0 -2011-02-26 06:00:00,9779.0 -2011-02-26 07:00:00,10096.0 -2011-02-26 08:00:00,10466.0 -2011-02-26 09:00:00,10751.0 -2011-02-26 10:00:00,11216.0 -2011-02-26 11:00:00,11411.0 -2011-02-26 12:00:00,11588.0 -2011-02-26 13:00:00,11574.0 -2011-02-26 14:00:00,11458.0 -2011-02-26 15:00:00,11428.0 -2011-02-26 16:00:00,11267.0 -2011-02-26 17:00:00,11314.0 -2011-02-26 18:00:00,11456.0 -2011-02-26 19:00:00,11925.0 -2011-02-26 20:00:00,12181.0 -2011-02-26 21:00:00,12039.0 -2011-02-26 22:00:00,11845.0 -2011-02-26 23:00:00,11478.0 -2011-02-27 00:00:00,10967.0 -2011-02-25 01:00:00,10975.0 -2011-02-25 02:00:00,10444.0 -2011-02-25 03:00:00,10195.0 -2011-02-25 04:00:00,10064.0 -2011-02-25 05:00:00,10048.0 -2011-02-25 06:00:00,10286.0 -2011-02-25 07:00:00,10965.0 -2011-02-25 08:00:00,11942.0 -2011-02-25 09:00:00,12381.0 -2011-02-25 10:00:00,12623.0 -2011-02-25 11:00:00,12673.0 -2011-02-25 12:00:00,12689.0 -2011-02-25 13:00:00,12518.0 -2011-02-25 14:00:00,12428.0 -2011-02-25 15:00:00,12336.0 -2011-02-25 16:00:00,12122.0 -2011-02-25 17:00:00,11994.0 -2011-02-25 18:00:00,12124.0 -2011-02-25 19:00:00,12655.0 -2011-02-25 20:00:00,12977.0 -2011-02-25 21:00:00,12770.0 -2011-02-25 22:00:00,12495.0 -2011-02-25 23:00:00,12048.0 -2011-02-26 00:00:00,11438.0 -2011-02-24 01:00:00,10890.0 -2011-02-24 02:00:00,10336.0 -2011-02-24 03:00:00,9989.0 -2011-02-24 04:00:00,9817.0 -2011-02-24 05:00:00,9793.0 -2011-02-24 06:00:00,10042.0 -2011-02-24 07:00:00,10771.0 -2011-02-24 08:00:00,11901.0 -2011-02-24 09:00:00,12468.0 -2011-02-24 10:00:00,12683.0 -2011-02-24 11:00:00,12683.0 -2011-02-24 12:00:00,12651.0 -2011-02-24 13:00:00,12566.0 -2011-02-24 14:00:00,12544.0 -2011-02-24 15:00:00,12576.0 -2011-02-24 16:00:00,12525.0 -2011-02-24 17:00:00,12603.0 -2011-02-24 18:00:00,12851.0 -2011-02-24 19:00:00,13392.0 -2011-02-24 20:00:00,13584.0 -2011-02-24 21:00:00,13386.0 -2011-02-24 22:00:00,13015.0 -2011-02-24 23:00:00,12538.0 -2011-02-25 00:00:00,11757.0 -2011-02-23 01:00:00,11079.0 -2011-02-23 02:00:00,10578.0 -2011-02-23 03:00:00,10264.0 -2011-02-23 04:00:00,10095.0 -2011-02-23 05:00:00,10112.0 -2011-02-23 06:00:00,10394.0 -2011-02-23 07:00:00,11156.0 -2011-02-23 08:00:00,12316.0 -2011-02-23 09:00:00,12882.0 -2011-02-23 10:00:00,13108.0 -2011-02-23 11:00:00,13194.0 -2011-02-23 12:00:00,13226.0 -2011-02-23 13:00:00,13142.0 -2011-02-23 14:00:00,13020.0 -2011-02-23 15:00:00,12917.0 -2011-02-23 16:00:00,12754.0 -2011-02-23 17:00:00,12730.0 -2011-02-23 18:00:00,12925.0 -2011-02-23 19:00:00,13327.0 -2011-02-23 20:00:00,13616.0 -2011-02-23 21:00:00,13432.0 -2011-02-23 22:00:00,13122.0 -2011-02-23 23:00:00,12578.0 -2011-02-24 00:00:00,11699.0 -2011-02-22 01:00:00,11016.0 -2011-02-22 02:00:00,10550.0 -2011-02-22 03:00:00,10234.0 -2011-02-22 04:00:00,10073.0 -2011-02-22 05:00:00,10069.0 -2011-02-22 06:00:00,10349.0 -2011-02-22 07:00:00,11137.0 -2011-02-22 08:00:00,12262.0 -2011-02-22 09:00:00,12768.0 -2011-02-22 10:00:00,12965.0 -2011-02-22 11:00:00,13040.0 -2011-02-22 12:00:00,13126.0 -2011-02-22 13:00:00,13102.0 -2011-02-22 14:00:00,13082.0 -2011-02-22 15:00:00,13066.0 -2011-02-22 16:00:00,12950.0 -2011-02-22 17:00:00,12927.0 -2011-02-22 18:00:00,13020.0 -2011-02-22 19:00:00,13538.0 -2011-02-22 20:00:00,13823.0 -2011-02-22 21:00:00,13625.0 -2011-02-22 22:00:00,13283.0 -2011-02-22 23:00:00,12719.0 -2011-02-23 00:00:00,11855.0 -2011-02-21 01:00:00,10270.0 -2011-02-21 02:00:00,9838.0 -2011-02-21 03:00:00,9598.0 -2011-02-21 04:00:00,9514.0 -2011-02-21 05:00:00,9545.0 -2011-02-21 06:00:00,9809.0 -2011-02-21 07:00:00,10483.0 -2011-02-21 08:00:00,11411.0 -2011-02-21 09:00:00,12031.0 -2011-02-21 10:00:00,12446.0 -2011-02-21 11:00:00,12777.0 -2011-02-21 12:00:00,13026.0 -2011-02-21 13:00:00,13101.0 -2011-02-21 14:00:00,13086.0 -2011-02-21 15:00:00,13083.0 -2011-02-21 16:00:00,12988.0 -2011-02-21 17:00:00,12907.0 -2011-02-21 18:00:00,13085.0 -2011-02-21 19:00:00,13568.0 -2011-02-21 20:00:00,13721.0 -2011-02-21 21:00:00,13444.0 -2011-02-21 22:00:00,13137.0 -2011-02-21 23:00:00,12577.0 -2011-02-22 00:00:00,11764.0 -2011-02-20 01:00:00,10311.0 -2011-02-20 02:00:00,9854.0 -2011-02-20 03:00:00,9622.0 -2011-02-20 04:00:00,9338.0 -2011-02-20 05:00:00,9306.0 -2011-02-20 06:00:00,9277.0 -2011-02-20 07:00:00,9427.0 -2011-02-20 08:00:00,9682.0 -2011-02-20 09:00:00,9872.0 -2011-02-20 10:00:00,10207.0 -2011-02-20 11:00:00,10676.0 -2011-02-20 12:00:00,10794.0 -2011-02-20 13:00:00,10988.0 -2011-02-20 14:00:00,11006.0 -2011-02-20 15:00:00,11008.0 -2011-02-20 16:00:00,10978.0 -2011-02-20 17:00:00,11047.0 -2011-02-20 18:00:00,11370.0 -2011-02-20 19:00:00,11884.0 -2011-02-20 20:00:00,12027.0 -2011-02-20 21:00:00,11886.0 -2011-02-20 22:00:00,11679.0 -2011-02-20 23:00:00,11310.0 -2011-02-21 00:00:00,10815.0 -2011-02-19 01:00:00,10439.0 -2011-02-19 02:00:00,9912.0 -2011-02-19 03:00:00,9646.0 -2011-02-19 04:00:00,9447.0 -2011-02-19 05:00:00,9392.0 -2011-02-19 06:00:00,9506.0 -2011-02-19 07:00:00,9836.0 -2011-02-19 08:00:00,10269.0 -2011-02-19 09:00:00,10461.0 -2011-02-19 10:00:00,10772.0 -2011-02-19 11:00:00,10944.0 -2011-02-19 12:00:00,10990.0 -2011-02-19 13:00:00,10914.0 -2011-02-19 14:00:00,10760.0 -2011-02-19 15:00:00,10533.0 -2011-02-19 16:00:00,10317.0 -2011-02-19 17:00:00,10290.0 -2011-02-19 18:00:00,10442.0 -2011-02-19 19:00:00,11119.0 -2011-02-19 20:00:00,11738.0 -2011-02-19 21:00:00,11743.0 -2011-02-19 22:00:00,11595.0 -2011-02-19 23:00:00,11341.0 -2011-02-20 00:00:00,10810.0 -2011-02-18 01:00:00,9948.0 -2011-02-18 02:00:00,9394.0 -2011-02-18 03:00:00,9186.0 -2011-02-18 04:00:00,9024.0 -2011-02-18 05:00:00,9068.0 -2011-02-18 06:00:00,9361.0 -2011-02-18 07:00:00,10083.0 -2011-02-18 08:00:00,11179.0 -2011-02-18 09:00:00,11715.0 -2011-02-18 10:00:00,11836.0 -2011-02-18 11:00:00,11918.0 -2011-02-18 12:00:00,11963.0 -2011-02-18 13:00:00,11900.0 -2011-02-18 14:00:00,11803.0 -2011-02-18 15:00:00,11729.0 -2011-02-18 16:00:00,11549.0 -2011-02-18 17:00:00,11411.0 -2011-02-18 18:00:00,11480.0 -2011-02-18 19:00:00,11948.0 -2011-02-18 20:00:00,12402.0 -2011-02-18 21:00:00,12224.0 -2011-02-18 22:00:00,11979.0 -2011-02-18 23:00:00,11673.0 -2011-02-19 00:00:00,11026.0 -2011-02-17 01:00:00,9997.0 -2011-02-17 02:00:00,9511.0 -2011-02-17 03:00:00,9195.0 -2011-02-17 04:00:00,9008.0 -2011-02-17 05:00:00,8935.0 -2011-02-17 06:00:00,9178.0 -2011-02-17 07:00:00,9904.0 -2011-02-17 08:00:00,11017.0 -2011-02-17 09:00:00,11690.0 -2011-02-17 10:00:00,11886.0 -2011-02-17 11:00:00,12054.0 -2011-02-17 12:00:00,12139.0 -2011-02-17 13:00:00,12095.0 -2011-02-17 14:00:00,12058.0 -2011-02-17 15:00:00,12068.0 -2011-02-17 16:00:00,11983.0 -2011-02-17 17:00:00,11947.0 -2011-02-17 18:00:00,12042.0 -2011-02-17 19:00:00,12353.0 -2011-02-17 20:00:00,12627.0 -2011-02-17 21:00:00,12427.0 -2011-02-17 22:00:00,12067.0 -2011-02-17 23:00:00,11560.0 -2011-02-18 00:00:00,10722.0 -2011-02-16 01:00:00,10678.0 -2011-02-16 02:00:00,10103.0 -2011-02-16 03:00:00,9777.0 -2011-02-16 04:00:00,9614.0 -2011-02-16 05:00:00,9568.0 -2011-02-16 06:00:00,9811.0 -2011-02-16 07:00:00,10513.0 -2011-02-16 08:00:00,11607.0 -2011-02-16 09:00:00,12080.0 -2011-02-16 10:00:00,12232.0 -2011-02-16 11:00:00,12183.0 -2011-02-16 12:00:00,12120.0 -2011-02-16 13:00:00,12025.0 -2011-02-16 14:00:00,11858.0 -2011-02-16 15:00:00,11810.0 -2011-02-16 16:00:00,11724.0 -2011-02-16 17:00:00,11650.0 -2011-02-16 18:00:00,11629.0 -2011-02-16 19:00:00,12300.0 -2011-02-16 20:00:00,12697.0 -2011-02-16 21:00:00,12535.0 -2011-02-16 22:00:00,12185.0 -2011-02-16 23:00:00,11633.0 -2011-02-17 00:00:00,10814.0 -2011-02-15 01:00:00,10697.0 -2011-02-15 02:00:00,10233.0 -2011-02-15 03:00:00,9959.0 -2011-02-15 04:00:00,9814.0 -2011-02-15 05:00:00,9844.0 -2011-02-15 06:00:00,10132.0 -2011-02-15 07:00:00,10945.0 -2011-02-15 08:00:00,12091.0 -2011-02-15 09:00:00,12596.0 -2011-02-15 10:00:00,12666.0 -2011-02-15 11:00:00,12610.0 -2011-02-15 12:00:00,12697.0 -2011-02-15 13:00:00,12684.0 -2011-02-15 14:00:00,12608.0 -2011-02-15 15:00:00,12579.0 -2011-02-15 16:00:00,12505.0 -2011-02-15 17:00:00,12442.0 -2011-02-15 18:00:00,12617.0 -2011-02-15 19:00:00,13184.0 -2011-02-15 20:00:00,13472.0 -2011-02-15 21:00:00,13251.0 -2011-02-15 22:00:00,12919.0 -2011-02-15 23:00:00,12337.0 -2011-02-16 00:00:00,11480.0 -2011-02-14 01:00:00,9986.0 -2011-02-14 02:00:00,9583.0 -2011-02-14 03:00:00,9442.0 -2011-02-14 04:00:00,9387.0 -2011-02-14 05:00:00,9428.0 -2011-02-14 06:00:00,9704.0 -2011-02-14 07:00:00,10469.0 -2011-02-14 08:00:00,11676.0 -2011-02-14 09:00:00,12253.0 -2011-02-14 10:00:00,12492.0 -2011-02-14 11:00:00,12589.0 -2011-02-14 12:00:00,12503.0 -2011-02-14 13:00:00,12409.0 -2011-02-14 14:00:00,12295.0 -2011-02-14 15:00:00,12149.0 -2011-02-14 16:00:00,11980.0 -2011-02-14 17:00:00,11846.0 -2011-02-14 18:00:00,11941.0 -2011-02-14 19:00:00,12581.0 -2011-02-14 20:00:00,13013.0 -2011-02-14 21:00:00,12874.0 -2011-02-14 22:00:00,12629.0 -2011-02-14 23:00:00,12180.0 -2011-02-15 00:00:00,11431.0 -2011-02-13 01:00:00,10424.0 -2011-02-13 02:00:00,9944.0 -2011-02-13 03:00:00,9685.0 -2011-02-13 04:00:00,9497.0 -2011-02-13 05:00:00,9396.0 -2011-02-13 06:00:00,9490.0 -2011-02-13 07:00:00,9618.0 -2011-02-13 08:00:00,9878.0 -2011-02-13 09:00:00,9852.0 -2011-02-13 10:00:00,10175.0 -2011-02-13 11:00:00,10283.0 -2011-02-13 12:00:00,10300.0 -2011-02-13 13:00:00,10181.0 -2011-02-13 14:00:00,10128.0 -2011-02-13 15:00:00,9988.0 -2011-02-13 16:00:00,9912.0 -2011-02-13 17:00:00,9896.0 -2011-02-13 18:00:00,10164.0 -2011-02-13 19:00:00,11022.0 -2011-02-13 20:00:00,11726.0 -2011-02-13 21:00:00,11670.0 -2011-02-13 22:00:00,11499.0 -2011-02-13 23:00:00,11138.0 -2011-02-14 00:00:00,10560.0 -2011-02-12 01:00:00,11532.0 -2011-02-12 02:00:00,10968.0 -2011-02-12 03:00:00,10562.0 -2011-02-12 04:00:00,10299.0 -2011-02-12 05:00:00,10260.0 -2011-02-12 06:00:00,10302.0 -2011-02-12 07:00:00,10638.0 -2011-02-12 08:00:00,11059.0 -2011-02-12 09:00:00,11229.0 -2011-02-12 10:00:00,11430.0 -2011-02-12 11:00:00,11568.0 -2011-02-12 12:00:00,11533.0 -2011-02-12 13:00:00,11429.0 -2011-02-12 14:00:00,11213.0 -2011-02-12 15:00:00,11074.0 -2011-02-12 16:00:00,10984.0 -2011-02-12 17:00:00,11032.0 -2011-02-12 18:00:00,11221.0 -2011-02-12 19:00:00,11975.0 -2011-02-12 20:00:00,12212.0 -2011-02-12 21:00:00,12119.0 -2011-02-12 22:00:00,11923.0 -2011-02-12 23:00:00,11551.0 -2011-02-13 00:00:00,10995.0 -2011-02-11 01:00:00,12311.0 -2011-02-11 02:00:00,11892.0 -2011-02-11 03:00:00,11588.0 -2011-02-11 04:00:00,11482.0 -2011-02-11 05:00:00,11446.0 -2011-02-11 06:00:00,11669.0 -2011-02-11 07:00:00,12287.0 -2011-02-11 08:00:00,13296.0 -2011-02-11 09:00:00,13682.0 -2011-02-11 10:00:00,13832.0 -2011-02-11 11:00:00,13944.0 -2011-02-11 12:00:00,13971.0 -2011-02-11 13:00:00,13810.0 -2011-02-11 14:00:00,13674.0 -2011-02-11 15:00:00,13688.0 -2011-02-11 16:00:00,13620.0 -2011-02-11 17:00:00,13433.0 -2011-02-11 18:00:00,13524.0 -2011-02-11 19:00:00,14076.0 -2011-02-11 20:00:00,14173.0 -2011-02-11 21:00:00,13887.0 -2011-02-11 22:00:00,13513.0 -2011-02-11 23:00:00,13069.0 -2011-02-12 00:00:00,12302.0 -2011-02-10 01:00:00,12685.0 -2011-02-10 02:00:00,12249.0 -2011-02-10 03:00:00,12031.0 -2011-02-10 04:00:00,11918.0 -2011-02-10 05:00:00,11957.0 -2011-02-10 06:00:00,12206.0 -2011-02-10 07:00:00,12838.0 -2011-02-10 08:00:00,13973.0 -2011-02-10 09:00:00,14371.0 -2011-02-10 10:00:00,14402.0 -2011-02-10 11:00:00,14328.0 -2011-02-10 12:00:00,14259.0 -2011-02-10 13:00:00,14140.0 -2011-02-10 14:00:00,13991.0 -2011-02-10 15:00:00,13899.0 -2011-02-10 16:00:00,13758.0 -2011-02-10 17:00:00,13662.0 -2011-02-10 18:00:00,13821.0 -2011-02-10 19:00:00,14582.0 -2011-02-10 20:00:00,14965.0 -2011-02-10 21:00:00,14831.0 -2011-02-10 22:00:00,14469.0 -2011-02-10 23:00:00,13965.0 -2011-02-11 00:00:00,13120.0 -2011-02-09 01:00:00,12435.0 -2011-02-09 02:00:00,11947.0 -2011-02-09 03:00:00,11720.0 -2011-02-09 04:00:00,11593.0 -2011-02-09 05:00:00,11615.0 -2011-02-09 06:00:00,11848.0 -2011-02-09 07:00:00,12548.0 -2011-02-09 08:00:00,13669.0 -2011-02-09 09:00:00,14098.0 -2011-02-09 10:00:00,14219.0 -2011-02-09 11:00:00,14226.0 -2011-02-09 12:00:00,14162.0 -2011-02-09 13:00:00,14025.0 -2011-02-09 14:00:00,13860.0 -2011-02-09 15:00:00,13807.0 -2011-02-09 16:00:00,13681.0 -2011-02-09 17:00:00,13677.0 -2011-02-09 18:00:00,13873.0 -2011-02-09 19:00:00,14705.0 -2011-02-09 20:00:00,15052.0 -2011-02-09 21:00:00,14978.0 -2011-02-09 22:00:00,14728.0 -2011-02-09 23:00:00,14230.0 -2011-02-10 00:00:00,13420.0 -2011-02-08 01:00:00,11387.0 -2011-02-08 02:00:00,10950.0 -2011-02-08 03:00:00,10715.0 -2011-02-08 04:00:00,10628.0 -2011-02-08 05:00:00,10698.0 -2011-02-08 06:00:00,11005.0 -2011-02-08 07:00:00,11794.0 -2011-02-08 08:00:00,12997.0 -2011-02-08 09:00:00,13470.0 -2011-02-08 10:00:00,13618.0 -2011-02-08 11:00:00,13580.0 -2011-02-08 12:00:00,13547.0 -2011-02-08 13:00:00,13445.0 -2011-02-08 14:00:00,13335.0 -2011-02-08 15:00:00,13294.0 -2011-02-08 16:00:00,13191.0 -2011-02-08 17:00:00,13203.0 -2011-02-08 18:00:00,13503.0 -2011-02-08 19:00:00,14294.0 -2011-02-08 20:00:00,14795.0 -2011-02-08 21:00:00,14726.0 -2011-02-08 22:00:00,14527.0 -2011-02-08 23:00:00,13998.0 -2011-02-09 00:00:00,13195.0 -2011-02-07 01:00:00,10486.0 -2011-02-07 02:00:00,10087.0 -2011-02-07 03:00:00,9919.0 -2011-02-07 04:00:00,9782.0 -2011-02-07 05:00:00,9841.0 -2011-02-07 06:00:00,10185.0 -2011-02-07 07:00:00,11003.0 -2011-02-07 08:00:00,12238.0 -2011-02-07 09:00:00,12714.0 -2011-02-07 10:00:00,12851.0 -2011-02-07 11:00:00,12834.0 -2011-02-07 12:00:00,12803.0 -2011-02-07 13:00:00,12717.0 -2011-02-07 14:00:00,12667.0 -2011-02-07 15:00:00,12788.0 -2011-02-07 16:00:00,12809.0 -2011-02-07 17:00:00,12875.0 -2011-02-07 18:00:00,13095.0 -2011-02-07 19:00:00,13756.0 -2011-02-07 20:00:00,13894.0 -2011-02-07 21:00:00,13701.0 -2011-02-07 22:00:00,13445.0 -2011-02-07 23:00:00,12913.0 -2011-02-08 00:00:00,12116.0 -2011-02-06 01:00:00,10900.0 -2011-02-06 02:00:00,10404.0 -2011-02-06 03:00:00,10150.0 -2011-02-06 04:00:00,9912.0 -2011-02-06 05:00:00,9827.0 -2011-02-06 06:00:00,9818.0 -2011-02-06 07:00:00,9988.0 -2011-02-06 08:00:00,10253.0 -2011-02-06 09:00:00,10348.0 -2011-02-06 10:00:00,10571.0 -2011-02-06 11:00:00,10854.0 -2011-02-06 12:00:00,10988.0 -2011-02-06 13:00:00,11075.0 -2011-02-06 14:00:00,11088.0 -2011-02-06 15:00:00,11099.0 -2011-02-06 16:00:00,11144.0 -2011-02-06 17:00:00,11193.0 -2011-02-06 18:00:00,11487.0 -2011-02-06 19:00:00,12069.0 -2011-02-06 20:00:00,12143.0 -2011-02-06 21:00:00,11957.0 -2011-02-06 22:00:00,11795.0 -2011-02-06 23:00:00,11564.0 -2011-02-07 00:00:00,11071.0 -2011-02-05 01:00:00,11902.0 -2011-02-05 02:00:00,11404.0 -2011-02-05 03:00:00,11067.0 -2011-02-05 04:00:00,10888.0 -2011-02-05 05:00:00,10778.0 -2011-02-05 06:00:00,10879.0 -2011-02-05 07:00:00,11138.0 -2011-02-05 08:00:00,11600.0 -2011-02-05 09:00:00,11731.0 -2011-02-05 10:00:00,12002.0 -2011-02-05 11:00:00,12101.0 -2011-02-05 12:00:00,12126.0 -2011-02-05 13:00:00,11979.0 -2011-02-05 14:00:00,11747.0 -2011-02-05 15:00:00,11517.0 -2011-02-05 16:00:00,11296.0 -2011-02-05 17:00:00,11227.0 -2011-02-05 18:00:00,11512.0 -2011-02-05 19:00:00,12335.0 -2011-02-05 20:00:00,12675.0 -2011-02-05 21:00:00,12555.0 -2011-02-05 22:00:00,12337.0 -2011-02-05 23:00:00,12027.0 -2011-02-06 00:00:00,11457.0 -2011-02-04 01:00:00,12319.0 -2011-02-04 02:00:00,11865.0 -2011-02-04 03:00:00,11604.0 -2011-02-04 04:00:00,11428.0 -2011-02-04 05:00:00,11467.0 -2011-02-04 06:00:00,11642.0 -2011-02-04 07:00:00,12302.0 -2011-02-04 08:00:00,13349.0 -2011-02-04 09:00:00,13767.0 -2011-02-04 10:00:00,13739.0 -2011-02-04 11:00:00,13744.0 -2011-02-04 12:00:00,13671.0 -2011-02-04 13:00:00,13528.0 -2011-02-04 14:00:00,13364.0 -2011-02-04 15:00:00,13216.0 -2011-02-04 16:00:00,13014.0 -2011-02-04 17:00:00,12875.0 -2011-02-04 18:00:00,13047.0 -2011-02-04 19:00:00,13838.0 -2011-02-04 20:00:00,14093.0 -2011-02-04 21:00:00,13924.0 -2011-02-04 22:00:00,13619.0 -2011-02-04 23:00:00,13278.0 -2011-02-05 00:00:00,12631.0 -2011-02-03 01:00:00,11068.0 -2011-02-03 02:00:00,10773.0 -2011-02-03 03:00:00,10667.0 -2011-02-03 04:00:00,10618.0 -2011-02-03 05:00:00,10716.0 -2011-02-03 06:00:00,11028.0 -2011-02-03 07:00:00,11709.0 -2011-02-03 08:00:00,12649.0 -2011-02-03 09:00:00,13098.0 -2011-02-03 10:00:00,13350.0 -2011-02-03 11:00:00,13510.0 -2011-02-03 12:00:00,13592.0 -2011-02-03 13:00:00,13559.0 -2011-02-03 14:00:00,13518.0 -2011-02-03 15:00:00,13458.0 -2011-02-03 16:00:00,13356.0 -2011-02-03 17:00:00,13212.0 -2011-02-03 18:00:00,13445.0 -2011-02-03 19:00:00,14378.0 -2011-02-03 20:00:00,14721.0 -2011-02-03 21:00:00,14654.0 -2011-02-03 22:00:00,14382.0 -2011-02-03 23:00:00,13889.0 -2011-02-04 00:00:00,13072.0 -2011-02-02 01:00:00,11056.0 -2011-02-02 02:00:00,10554.0 -2011-02-02 03:00:00,10295.0 -2011-02-02 04:00:00,10153.0 -2011-02-02 05:00:00,10116.0 -2011-02-02 06:00:00,10207.0 -2011-02-02 07:00:00,10475.0 -2011-02-02 08:00:00,11086.0 -2011-02-02 09:00:00,11147.0 -2011-02-02 10:00:00,11352.0 -2011-02-02 11:00:00,11439.0 -2011-02-02 12:00:00,11394.0 -2011-02-02 13:00:00,11318.0 -2011-02-02 14:00:00,11176.0 -2011-02-02 15:00:00,11045.0 -2011-02-02 16:00:00,10999.0 -2011-02-02 17:00:00,11179.0 -2011-02-02 18:00:00,11605.0 -2011-02-02 19:00:00,12666.0 -2011-02-02 20:00:00,12960.0 -2011-02-02 21:00:00,12810.0 -2011-02-02 22:00:00,12552.0 -2011-02-02 23:00:00,12182.0 -2011-02-03 00:00:00,11580.0 -2011-02-01 01:00:00,11665.0 -2011-02-01 02:00:00,11152.0 -2011-02-01 03:00:00,10883.0 -2011-02-01 04:00:00,10762.0 -2011-02-01 05:00:00,10776.0 -2011-02-01 06:00:00,11058.0 -2011-02-01 07:00:00,11786.0 -2011-02-01 08:00:00,12961.0 -2011-02-01 09:00:00,13502.0 -2011-02-01 10:00:00,13695.0 -2011-02-01 11:00:00,13811.0 -2011-02-01 12:00:00,13937.0 -2011-02-01 13:00:00,13944.0 -2011-02-01 14:00:00,13892.0 -2011-02-01 15:00:00,13847.0 -2011-02-01 16:00:00,13629.0 -2011-02-01 17:00:00,13314.0 -2011-02-01 18:00:00,13517.0 -2011-02-01 19:00:00,14175.0 -2011-02-01 20:00:00,13967.0 -2011-02-01 21:00:00,13467.0 -2011-02-01 22:00:00,12972.0 -2011-02-01 23:00:00,12462.0 -2011-02-02 00:00:00,11754.0 -2011-01-31 01:00:00,10565.0 -2011-01-31 02:00:00,10175.0 -2011-01-31 03:00:00,10056.0 -2011-01-31 04:00:00,9992.0 -2011-01-31 05:00:00,10089.0 -2011-01-31 06:00:00,10470.0 -2011-01-31 07:00:00,11255.0 -2011-01-31 08:00:00,12599.0 -2011-01-31 09:00:00,13226.0 -2011-01-31 10:00:00,13447.0 -2011-01-31 11:00:00,13488.0 -2011-01-31 12:00:00,13521.0 -2011-01-31 13:00:00,13316.0 -2011-01-31 14:00:00,13210.0 -2011-01-31 15:00:00,13273.0 -2011-01-31 16:00:00,13249.0 -2011-01-31 17:00:00,13324.0 -2011-01-31 18:00:00,13631.0 -2011-01-31 19:00:00,14332.0 -2011-01-31 20:00:00,14390.0 -2011-01-31 21:00:00,14165.0 -2011-01-31 22:00:00,13907.0 -2011-01-31 23:00:00,13332.0 -2011-02-01 00:00:00,12453.0 -2011-01-30 01:00:00,10436.0 -2011-01-30 02:00:00,9949.0 -2011-01-30 03:00:00,9611.0 -2011-01-30 04:00:00,9433.0 -2011-01-30 05:00:00,9361.0 -2011-01-30 06:00:00,9339.0 -2011-01-30 07:00:00,9520.0 -2011-01-30 08:00:00,9816.0 -2011-01-30 09:00:00,9959.0 -2011-01-30 10:00:00,10282.0 -2011-01-30 11:00:00,10607.0 -2011-01-30 12:00:00,10829.0 -2011-01-30 13:00:00,10946.0 -2011-01-30 14:00:00,10953.0 -2011-01-30 15:00:00,10947.0 -2011-01-30 16:00:00,10923.0 -2011-01-30 17:00:00,10985.0 -2011-01-30 18:00:00,11351.0 -2011-01-30 19:00:00,12239.0 -2011-01-30 20:00:00,12435.0 -2011-01-30 21:00:00,12346.0 -2011-01-30 22:00:00,12140.0 -2011-01-30 23:00:00,11759.0 -2011-01-31 00:00:00,11165.0 -2011-01-29 01:00:00,11256.0 -2011-01-29 02:00:00,10679.0 -2011-01-29 03:00:00,10336.0 -2011-01-29 04:00:00,10057.0 -2011-01-29 05:00:00,10005.0 -2011-01-29 06:00:00,10013.0 -2011-01-29 07:00:00,10299.0 -2011-01-29 08:00:00,10750.0 -2011-01-29 09:00:00,11031.0 -2011-01-29 10:00:00,11375.0 -2011-01-29 11:00:00,11658.0 -2011-01-29 12:00:00,11704.0 -2011-01-29 13:00:00,11665.0 -2011-01-29 14:00:00,11464.0 -2011-01-29 15:00:00,11254.0 -2011-01-29 16:00:00,11073.0 -2011-01-29 17:00:00,11093.0 -2011-01-29 18:00:00,11382.0 -2011-01-29 19:00:00,12161.0 -2011-01-29 20:00:00,12223.0 -2011-01-29 21:00:00,12045.0 -2011-01-29 22:00:00,11866.0 -2011-01-29 23:00:00,11542.0 -2011-01-30 00:00:00,11001.0 -2011-01-28 01:00:00,11462.0 -2011-01-28 02:00:00,10937.0 -2011-01-28 03:00:00,10668.0 -2011-01-28 04:00:00,10445.0 -2011-01-28 05:00:00,10493.0 -2011-01-28 06:00:00,10737.0 -2011-01-28 07:00:00,11451.0 -2011-01-28 08:00:00,12591.0 -2011-01-28 09:00:00,13023.0 -2011-01-28 10:00:00,13058.0 -2011-01-28 11:00:00,13017.0 -2011-01-28 12:00:00,12893.0 -2011-01-28 13:00:00,12773.0 -2011-01-28 14:00:00,12650.0 -2011-01-28 15:00:00,12693.0 -2011-01-28 16:00:00,12619.0 -2011-01-28 17:00:00,12632.0 -2011-01-28 18:00:00,12966.0 -2011-01-28 19:00:00,13631.0 -2011-01-28 20:00:00,13557.0 -2011-01-28 21:00:00,13348.0 -2011-01-28 22:00:00,13073.0 -2011-01-28 23:00:00,12705.0 -2011-01-29 00:00:00,11965.0 -2011-01-27 01:00:00,11531.0 -2011-01-27 02:00:00,11061.0 -2011-01-27 03:00:00,10814.0 -2011-01-27 04:00:00,10643.0 -2011-01-27 05:00:00,10685.0 -2011-01-27 06:00:00,10967.0 -2011-01-27 07:00:00,11637.0 -2011-01-27 08:00:00,12812.0 -2011-01-27 09:00:00,13477.0 -2011-01-27 10:00:00,13580.0 -2011-01-27 11:00:00,13673.0 -2011-01-27 12:00:00,13743.0 -2011-01-27 13:00:00,13657.0 -2011-01-27 14:00:00,13504.0 -2011-01-27 15:00:00,13466.0 -2011-01-27 16:00:00,13365.0 -2011-01-27 17:00:00,13296.0 -2011-01-27 18:00:00,13541.0 -2011-01-27 19:00:00,14227.0 -2011-01-27 20:00:00,14222.0 -2011-01-27 21:00:00,14007.0 -2011-01-27 22:00:00,13644.0 -2011-01-27 23:00:00,13124.0 -2011-01-28 00:00:00,12248.0 -2011-01-26 01:00:00,11092.0 -2011-01-26 02:00:00,10603.0 -2011-01-26 03:00:00,10318.0 -2011-01-26 04:00:00,10146.0 -2011-01-26 05:00:00,10165.0 -2011-01-26 06:00:00,10425.0 -2011-01-26 07:00:00,11150.0 -2011-01-26 08:00:00,12347.0 -2011-01-26 09:00:00,13025.0 -2011-01-26 10:00:00,13166.0 -2011-01-26 11:00:00,13274.0 -2011-01-26 12:00:00,13423.0 -2011-01-26 13:00:00,13406.0 -2011-01-26 14:00:00,13391.0 -2011-01-26 15:00:00,13431.0 -2011-01-26 16:00:00,13385.0 -2011-01-26 17:00:00,13413.0 -2011-01-26 18:00:00,13796.0 -2011-01-26 19:00:00,14383.0 -2011-01-26 20:00:00,14267.0 -2011-01-26 21:00:00,14032.0 -2011-01-26 22:00:00,13722.0 -2011-01-26 23:00:00,13186.0 -2011-01-27 00:00:00,12318.0 -2011-01-25 01:00:00,11544.0 -2011-01-25 02:00:00,11034.0 -2011-01-25 03:00:00,10705.0 -2011-01-25 04:00:00,10515.0 -2011-01-25 05:00:00,10522.0 -2011-01-25 06:00:00,10784.0 -2011-01-25 07:00:00,11434.0 -2011-01-25 08:00:00,12621.0 -2011-01-25 09:00:00,13226.0 -2011-01-25 10:00:00,13318.0 -2011-01-25 11:00:00,13371.0 -2011-01-25 12:00:00,13459.0 -2011-01-25 13:00:00,13374.0 -2011-01-25 14:00:00,13195.0 -2011-01-25 15:00:00,13083.0 -2011-01-25 16:00:00,12981.0 -2011-01-25 17:00:00,13039.0 -2011-01-25 18:00:00,13386.0 -2011-01-25 19:00:00,14043.0 -2011-01-25 20:00:00,13949.0 -2011-01-25 21:00:00,13713.0 -2011-01-25 22:00:00,13393.0 -2011-01-25 23:00:00,12804.0 -2011-01-26 00:00:00,11914.0 -2011-01-24 01:00:00,11401.0 -2011-01-24 02:00:00,11005.0 -2011-01-24 03:00:00,10828.0 -2011-01-24 04:00:00,10756.0 -2011-01-24 05:00:00,10804.0 -2011-01-24 06:00:00,11059.0 -2011-01-24 07:00:00,11725.0 -2011-01-24 08:00:00,12900.0 -2011-01-24 09:00:00,13516.0 -2011-01-24 10:00:00,13630.0 -2011-01-24 11:00:00,13684.0 -2011-01-24 12:00:00,13740.0 -2011-01-24 13:00:00,13643.0 -2011-01-24 14:00:00,13576.0 -2011-01-24 15:00:00,13562.0 -2011-01-24 16:00:00,13469.0 -2011-01-24 17:00:00,13477.0 -2011-01-24 18:00:00,13763.0 -2011-01-24 19:00:00,14411.0 -2011-01-24 20:00:00,14334.0 -2011-01-24 21:00:00,14121.0 -2011-01-24 22:00:00,13799.0 -2011-01-24 23:00:00,13249.0 -2011-01-25 00:00:00,12371.0 -2011-01-23 01:00:00,11908.0 -2011-01-23 02:00:00,11503.0 -2011-01-23 03:00:00,11280.0 -2011-01-23 04:00:00,11068.0 -2011-01-23 05:00:00,11044.0 -2011-01-23 06:00:00,11026.0 -2011-01-23 07:00:00,11195.0 -2011-01-23 08:00:00,11471.0 -2011-01-23 09:00:00,11505.0 -2011-01-23 10:00:00,11715.0 -2011-01-23 11:00:00,11728.0 -2011-01-23 12:00:00,11728.0 -2011-01-23 13:00:00,11652.0 -2011-01-23 14:00:00,11635.0 -2011-01-23 15:00:00,11498.0 -2011-01-23 16:00:00,11373.0 -2011-01-23 17:00:00,11383.0 -2011-01-23 18:00:00,11847.0 -2011-01-23 19:00:00,12869.0 -2011-01-23 20:00:00,13215.0 -2011-01-23 21:00:00,13188.0 -2011-01-23 22:00:00,12980.0 -2011-01-23 23:00:00,12591.0 -2011-01-24 00:00:00,11924.0 -2011-01-22 01:00:00,12449.0 -2011-01-22 02:00:00,11964.0 -2011-01-22 03:00:00,11630.0 -2011-01-22 04:00:00,11388.0 -2011-01-22 05:00:00,11341.0 -2011-01-22 06:00:00,11402.0 -2011-01-22 07:00:00,11709.0 -2011-01-22 08:00:00,12111.0 -2011-01-22 09:00:00,12392.0 -2011-01-22 10:00:00,12665.0 -2011-01-22 11:00:00,12888.0 -2011-01-22 12:00:00,12900.0 -2011-01-22 13:00:00,12727.0 -2011-01-22 14:00:00,12459.0 -2011-01-22 15:00:00,12224.0 -2011-01-22 16:00:00,12165.0 -2011-01-22 17:00:00,12301.0 -2011-01-22 18:00:00,12693.0 -2011-01-22 19:00:00,13544.0 -2011-01-22 20:00:00,13573.0 -2011-01-22 21:00:00,13491.0 -2011-01-22 22:00:00,13287.0 -2011-01-22 23:00:00,12995.0 -2011-01-23 00:00:00,12447.0 -2011-01-21 01:00:00,12417.0 -2011-01-21 02:00:00,11993.0 -2011-01-21 03:00:00,11757.0 -2011-01-21 04:00:00,11632.0 -2011-01-21 05:00:00,11666.0 -2011-01-21 06:00:00,11953.0 -2011-01-21 07:00:00,12580.0 -2011-01-21 08:00:00,13734.0 -2011-01-21 09:00:00,14255.0 -2011-01-21 10:00:00,14292.0 -2011-01-21 11:00:00,14289.0 -2011-01-21 12:00:00,14262.0 -2011-01-21 13:00:00,14148.0 -2011-01-21 14:00:00,14030.0 -2011-01-21 15:00:00,13941.0 -2011-01-21 16:00:00,13802.0 -2011-01-21 17:00:00,13784.0 -2011-01-21 18:00:00,14153.0 -2011-01-21 19:00:00,14949.0 -2011-01-21 20:00:00,14945.0 -2011-01-21 21:00:00,14717.0 -2011-01-21 22:00:00,14449.0 -2011-01-21 23:00:00,13973.0 -2011-01-22 00:00:00,13245.0 -2011-01-20 01:00:00,11651.0 -2011-01-20 02:00:00,11176.0 -2011-01-20 03:00:00,10892.0 -2011-01-20 04:00:00,10713.0 -2011-01-20 05:00:00,10690.0 -2011-01-20 06:00:00,10952.0 -2011-01-20 07:00:00,11659.0 -2011-01-20 08:00:00,12925.0 -2011-01-20 09:00:00,13577.0 -2011-01-20 10:00:00,13719.0 -2011-01-20 11:00:00,13779.0 -2011-01-20 12:00:00,13836.0 -2011-01-20 13:00:00,13692.0 -2011-01-20 14:00:00,13573.0 -2011-01-20 15:00:00,13550.0 -2011-01-20 16:00:00,13418.0 -2011-01-20 17:00:00,13364.0 -2011-01-20 18:00:00,13766.0 -2011-01-20 19:00:00,14702.0 -2011-01-20 20:00:00,14725.0 -2011-01-20 21:00:00,14598.0 -2011-01-20 22:00:00,14363.0 -2011-01-20 23:00:00,13882.0 -2011-01-21 00:00:00,13108.0 -2011-01-19 01:00:00,11359.0 -2011-01-19 02:00:00,10884.0 -2011-01-19 03:00:00,10628.0 -2011-01-19 04:00:00,10469.0 -2011-01-19 05:00:00,10506.0 -2011-01-19 06:00:00,10780.0 -2011-01-19 07:00:00,11515.0 -2011-01-19 08:00:00,12736.0 -2011-01-19 09:00:00,13406.0 -2011-01-19 10:00:00,13477.0 -2011-01-19 11:00:00,13499.0 -2011-01-19 12:00:00,13511.0 -2011-01-19 13:00:00,13358.0 -2011-01-19 14:00:00,13185.0 -2011-01-19 15:00:00,13056.0 -2011-01-19 16:00:00,12943.0 -2011-01-19 17:00:00,12909.0 -2011-01-19 18:00:00,13370.0 -2011-01-19 19:00:00,14307.0 -2011-01-19 20:00:00,14311.0 -2011-01-19 21:00:00,14111.0 -2011-01-19 22:00:00,13821.0 -2011-01-19 23:00:00,13251.0 -2011-01-20 00:00:00,12420.0 -2011-01-18 01:00:00,10988.0 -2011-01-18 02:00:00,10481.0 -2011-01-18 03:00:00,10163.0 -2011-01-18 04:00:00,9974.0 -2011-01-18 05:00:00,9949.0 -2011-01-18 06:00:00,10203.0 -2011-01-18 07:00:00,10945.0 -2011-01-18 08:00:00,12172.0 -2011-01-18 09:00:00,12909.0 -2011-01-18 10:00:00,13043.0 -2011-01-18 11:00:00,13128.0 -2011-01-18 12:00:00,13213.0 -2011-01-18 13:00:00,13219.0 -2011-01-18 14:00:00,13204.0 -2011-01-18 15:00:00,13273.0 -2011-01-18 16:00:00,13295.0 -2011-01-18 17:00:00,13392.0 -2011-01-18 18:00:00,13830.0 -2011-01-18 19:00:00,14339.0 -2011-01-18 20:00:00,14155.0 -2011-01-18 21:00:00,13937.0 -2011-01-18 22:00:00,13648.0 -2011-01-18 23:00:00,13053.0 -2011-01-19 00:00:00,12164.0 -2011-01-17 01:00:00,11005.0 -2011-01-17 02:00:00,10594.0 -2011-01-17 03:00:00,10438.0 -2011-01-17 04:00:00,10329.0 -2011-01-17 05:00:00,10378.0 -2011-01-17 06:00:00,10642.0 -2011-01-17 07:00:00,11288.0 -2011-01-17 08:00:00,12217.0 -2011-01-17 09:00:00,12790.0 -2011-01-17 10:00:00,13115.0 -2011-01-17 11:00:00,13330.0 -2011-01-17 12:00:00,13451.0 -2011-01-17 13:00:00,13363.0 -2011-01-17 14:00:00,13213.0 -2011-01-17 15:00:00,13205.0 -2011-01-17 16:00:00,13142.0 -2011-01-17 17:00:00,13088.0 -2011-01-17 18:00:00,13531.0 -2011-01-17 19:00:00,14075.0 -2011-01-17 20:00:00,13871.0 -2011-01-17 21:00:00,13596.0 -2011-01-17 22:00:00,13231.0 -2011-01-17 23:00:00,12637.0 -2011-01-18 00:00:00,11764.0 -2011-01-16 01:00:00,11200.0 -2011-01-16 02:00:00,10725.0 -2011-01-16 03:00:00,10485.0 -2011-01-16 04:00:00,10306.0 -2011-01-16 05:00:00,10249.0 -2011-01-16 06:00:00,10362.0 -2011-01-16 07:00:00,10495.0 -2011-01-16 08:00:00,10836.0 -2011-01-16 09:00:00,10878.0 -2011-01-16 10:00:00,11154.0 -2011-01-16 11:00:00,11288.0 -2011-01-16 12:00:00,11423.0 -2011-01-16 13:00:00,11429.0 -2011-01-16 14:00:00,11411.0 -2011-01-16 15:00:00,11336.0 -2011-01-16 16:00:00,11257.0 -2011-01-16 17:00:00,11299.0 -2011-01-16 18:00:00,11847.0 -2011-01-16 19:00:00,12647.0 -2011-01-16 20:00:00,12667.0 -2011-01-16 21:00:00,12569.0 -2011-01-16 22:00:00,12332.0 -2011-01-16 23:00:00,12048.0 -2011-01-17 00:00:00,11518.0 -2011-01-15 01:00:00,11449.0 -2011-01-15 02:00:00,10892.0 -2011-01-15 03:00:00,10573.0 -2011-01-15 04:00:00,10343.0 -2011-01-15 05:00:00,10264.0 -2011-01-15 06:00:00,10317.0 -2011-01-15 07:00:00,10610.0 -2011-01-15 08:00:00,11076.0 -2011-01-15 09:00:00,11458.0 -2011-01-15 10:00:00,11750.0 -2011-01-15 11:00:00,11993.0 -2011-01-15 12:00:00,12001.0 -2011-01-15 13:00:00,12004.0 -2011-01-15 14:00:00,11787.0 -2011-01-15 15:00:00,11730.0 -2011-01-15 16:00:00,11633.0 -2011-01-15 17:00:00,11725.0 -2011-01-15 18:00:00,12095.0 -2011-01-15 19:00:00,12899.0 -2011-01-15 20:00:00,12841.0 -2011-01-15 21:00:00,12730.0 -2011-01-15 22:00:00,12555.0 -2011-01-15 23:00:00,12279.0 -2011-01-16 00:00:00,11734.0 -2011-01-14 01:00:00,11643.0 -2011-01-14 02:00:00,11173.0 -2011-01-14 03:00:00,10881.0 -2011-01-14 04:00:00,10703.0 -2011-01-14 05:00:00,10707.0 -2011-01-14 06:00:00,10916.0 -2011-01-14 07:00:00,11582.0 -2011-01-14 08:00:00,12694.0 -2011-01-14 09:00:00,13254.0 -2011-01-14 10:00:00,13345.0 -2011-01-14 11:00:00,13390.0 -2011-01-14 12:00:00,13386.0 -2011-01-14 13:00:00,13250.0 -2011-01-14 14:00:00,13114.0 -2011-01-14 15:00:00,13068.0 -2011-01-14 16:00:00,13000.0 -2011-01-14 17:00:00,12967.0 -2011-01-14 18:00:00,13336.0 -2011-01-14 19:00:00,13980.0 -2011-01-14 20:00:00,13812.0 -2011-01-14 21:00:00,13553.0 -2011-01-14 22:00:00,13231.0 -2011-01-14 23:00:00,12833.0 -2011-01-15 00:00:00,12120.0 -2011-01-13 01:00:00,11348.0 -2011-01-13 02:00:00,10848.0 -2011-01-13 03:00:00,10574.0 -2011-01-13 04:00:00,10405.0 -2011-01-13 05:00:00,10450.0 -2011-01-13 06:00:00,10711.0 -2011-01-13 07:00:00,11419.0 -2011-01-13 08:00:00,12626.0 -2011-01-13 09:00:00,13262.0 -2011-01-13 10:00:00,13315.0 -2011-01-13 11:00:00,13358.0 -2011-01-13 12:00:00,13324.0 -2011-01-13 13:00:00,13268.0 -2011-01-13 14:00:00,13098.0 -2011-01-13 15:00:00,13302.0 -2011-01-13 16:00:00,13310.0 -2011-01-13 17:00:00,13318.0 -2011-01-13 18:00:00,13767.0 -2011-01-13 19:00:00,14455.0 -2011-01-13 20:00:00,14322.0 -2011-01-13 21:00:00,14137.0 -2011-01-13 22:00:00,13850.0 -2011-01-13 23:00:00,13300.0 -2011-01-14 00:00:00,12455.0 -2011-01-12 01:00:00,11510.0 -2011-01-12 02:00:00,11043.0 -2011-01-12 03:00:00,10736.0 -2011-01-12 04:00:00,10593.0 -2011-01-12 05:00:00,10589.0 -2011-01-12 06:00:00,10853.0 -2011-01-12 07:00:00,11551.0 -2011-01-12 08:00:00,12726.0 -2011-01-12 09:00:00,13311.0 -2011-01-12 10:00:00,13364.0 -2011-01-12 11:00:00,13318.0 -2011-01-12 12:00:00,13293.0 -2011-01-12 13:00:00,13135.0 -2011-01-12 14:00:00,13083.0 -2011-01-12 15:00:00,13132.0 -2011-01-12 16:00:00,13154.0 -2011-01-12 17:00:00,13189.0 -2011-01-12 18:00:00,13615.0 -2011-01-12 19:00:00,14272.0 -2011-01-12 20:00:00,14129.0 -2011-01-12 21:00:00,13921.0 -2011-01-12 22:00:00,13606.0 -2011-01-12 23:00:00,13048.0 -2011-01-13 00:00:00,12149.0 -2011-01-11 01:00:00,11263.0 -2011-01-11 02:00:00,10747.0 -2011-01-11 03:00:00,10457.0 -2011-01-11 04:00:00,10320.0 -2011-01-11 05:00:00,10339.0 -2011-01-11 06:00:00,10630.0 -2011-01-11 07:00:00,11359.0 -2011-01-11 08:00:00,12537.0 -2011-01-11 09:00:00,13201.0 -2011-01-11 10:00:00,13279.0 -2011-01-11 11:00:00,13380.0 -2011-01-11 12:00:00,13468.0 -2011-01-11 13:00:00,13369.0 -2011-01-11 14:00:00,13317.0 -2011-01-11 15:00:00,13295.0 -2011-01-11 16:00:00,13226.0 -2011-01-11 17:00:00,13256.0 -2011-01-11 18:00:00,13699.0 -2011-01-11 19:00:00,14402.0 -2011-01-11 20:00:00,14278.0 -2011-01-11 21:00:00,14043.0 -2011-01-11 22:00:00,13739.0 -2011-01-11 23:00:00,13141.0 -2011-01-12 00:00:00,12275.0 -2011-01-10 01:00:00,10817.0 -2011-01-10 02:00:00,10462.0 -2011-01-10 03:00:00,10274.0 -2011-01-10 04:00:00,10202.0 -2011-01-10 05:00:00,10285.0 -2011-01-10 06:00:00,10606.0 -2011-01-10 07:00:00,11369.0 -2011-01-10 08:00:00,12574.0 -2011-01-10 09:00:00,13321.0 -2011-01-10 10:00:00,13208.0 -2011-01-10 11:00:00,13159.0 -2011-01-10 12:00:00,13089.0 -2011-01-10 13:00:00,13036.0 -2011-01-10 14:00:00,12909.0 -2011-01-10 15:00:00,12901.0 -2011-01-10 16:00:00,12871.0 -2011-01-10 17:00:00,13011.0 -2011-01-10 18:00:00,13620.0 -2011-01-10 19:00:00,14197.0 -2011-01-10 20:00:00,14018.0 -2011-01-10 21:00:00,13818.0 -2011-01-10 22:00:00,13482.0 -2011-01-10 23:00:00,12949.0 -2011-01-11 00:00:00,12073.0 -2011-01-09 01:00:00,11469.0 -2011-01-09 02:00:00,10936.0 -2011-01-09 03:00:00,10634.0 -2011-01-09 04:00:00,10531.0 -2011-01-09 05:00:00,10478.0 -2011-01-09 06:00:00,10499.0 -2011-01-09 07:00:00,10670.0 -2011-01-09 08:00:00,10955.0 -2011-01-09 09:00:00,11053.0 -2011-01-09 10:00:00,11113.0 -2011-01-09 11:00:00,11179.0 -2011-01-09 12:00:00,11154.0 -2011-01-09 13:00:00,11116.0 -2011-01-09 14:00:00,11028.0 -2011-01-09 15:00:00,10959.0 -2011-01-09 16:00:00,10880.0 -2011-01-09 17:00:00,11105.0 -2011-01-09 18:00:00,11772.0 -2011-01-09 19:00:00,12671.0 -2011-01-09 20:00:00,12716.0 -2011-01-09 21:00:00,12619.0 -2011-01-09 22:00:00,12368.0 -2011-01-09 23:00:00,11975.0 -2011-01-10 00:00:00,11382.0 -2011-01-08 01:00:00,12044.0 -2011-01-08 02:00:00,11494.0 -2011-01-08 03:00:00,11218.0 -2011-01-08 04:00:00,11035.0 -2011-01-08 05:00:00,10989.0 -2011-01-08 06:00:00,11025.0 -2011-01-08 07:00:00,11326.0 -2011-01-08 08:00:00,11757.0 -2011-01-08 09:00:00,11991.0 -2011-01-08 10:00:00,12164.0 -2011-01-08 11:00:00,12315.0 -2011-01-08 12:00:00,12376.0 -2011-01-08 13:00:00,12308.0 -2011-01-08 14:00:00,12151.0 -2011-01-08 15:00:00,11919.0 -2011-01-08 16:00:00,11717.0 -2011-01-08 17:00:00,11750.0 -2011-01-08 18:00:00,12316.0 -2011-01-08 19:00:00,13212.0 -2011-01-08 20:00:00,13196.0 -2011-01-08 21:00:00,13104.0 -2011-01-08 22:00:00,12897.0 -2011-01-08 23:00:00,12607.0 -2011-01-09 00:00:00,12011.0 -2011-01-07 01:00:00,11582.0 -2011-01-07 02:00:00,11132.0 -2011-01-07 03:00:00,10833.0 -2011-01-07 04:00:00,10653.0 -2011-01-07 05:00:00,10652.0 -2011-01-07 06:00:00,10913.0 -2011-01-07 07:00:00,11538.0 -2011-01-07 08:00:00,12665.0 -2011-01-07 09:00:00,13414.0 -2011-01-07 10:00:00,13499.0 -2011-01-07 11:00:00,13552.0 -2011-01-07 12:00:00,13553.0 -2011-01-07 13:00:00,13441.0 -2011-01-07 14:00:00,13323.0 -2011-01-07 15:00:00,13415.0 -2011-01-07 16:00:00,13504.0 -2011-01-07 17:00:00,13558.0 -2011-01-07 18:00:00,14028.0 -2011-01-07 19:00:00,14500.0 -2011-01-07 20:00:00,14361.0 -2011-01-07 21:00:00,14143.0 -2011-01-07 22:00:00,13832.0 -2011-01-07 23:00:00,13438.0 -2011-01-08 00:00:00,12724.0 -2011-01-06 01:00:00,11268.0 -2011-01-06 02:00:00,10736.0 -2011-01-06 03:00:00,10418.0 -2011-01-06 04:00:00,10252.0 -2011-01-06 05:00:00,10254.0 -2011-01-06 06:00:00,10522.0 -2011-01-06 07:00:00,11221.0 -2011-01-06 08:00:00,12410.0 -2011-01-06 09:00:00,13236.0 -2011-01-06 10:00:00,13349.0 -2011-01-06 11:00:00,13440.0 -2011-01-06 12:00:00,13426.0 -2011-01-06 13:00:00,13377.0 -2011-01-06 14:00:00,13247.0 -2011-01-06 15:00:00,13112.0 -2011-01-06 16:00:00,12965.0 -2011-01-06 17:00:00,12993.0 -2011-01-06 18:00:00,13607.0 -2011-01-06 19:00:00,14420.0 -2011-01-06 20:00:00,14296.0 -2011-01-06 21:00:00,14099.0 -2011-01-06 22:00:00,13771.0 -2011-01-06 23:00:00,13253.0 -2011-01-07 00:00:00,12388.0 -2011-01-05 01:00:00,11829.0 -2011-01-05 02:00:00,11324.0 -2011-01-05 03:00:00,11062.0 -2011-01-05 04:00:00,10908.0 -2011-01-05 05:00:00,10930.0 -2011-01-05 06:00:00,11194.0 -2011-01-05 07:00:00,11868.0 -2011-01-05 08:00:00,13012.0 -2011-01-05 09:00:00,13580.0 -2011-01-05 10:00:00,13579.0 -2011-01-05 11:00:00,13561.0 -2011-01-05 12:00:00,13584.0 -2011-01-05 13:00:00,13530.0 -2011-01-05 14:00:00,13442.0 -2011-01-05 15:00:00,13374.0 -2011-01-05 16:00:00,13244.0 -2011-01-05 17:00:00,13265.0 -2011-01-05 18:00:00,13826.0 -2011-01-05 19:00:00,14335.0 -2011-01-05 20:00:00,14122.0 -2011-01-05 21:00:00,13861.0 -2011-01-05 22:00:00,13526.0 -2011-01-05 23:00:00,12968.0 -2011-01-06 00:00:00,12062.0 -2011-01-04 01:00:00,10941.0 -2011-01-04 02:00:00,10426.0 -2011-01-04 03:00:00,10171.0 -2011-01-04 04:00:00,10044.0 -2011-01-04 05:00:00,10065.0 -2011-01-04 06:00:00,10370.0 -2011-01-04 07:00:00,11063.0 -2011-01-04 08:00:00,12341.0 -2011-01-04 09:00:00,13040.0 -2011-01-04 10:00:00,13114.0 -2011-01-04 11:00:00,13194.0 -2011-01-04 12:00:00,13257.0 -2011-01-04 13:00:00,13223.0 -2011-01-04 14:00:00,13173.0 -2011-01-04 15:00:00,13144.0 -2011-01-04 16:00:00,13049.0 -2011-01-04 17:00:00,13070.0 -2011-01-04 18:00:00,13705.0 -2011-01-04 19:00:00,14527.0 -2011-01-04 20:00:00,14466.0 -2011-01-04 21:00:00,14312.0 -2011-01-04 22:00:00,14038.0 -2011-01-04 23:00:00,13498.0 -2011-01-05 00:00:00,12600.0 -2011-01-03 01:00:00,10897.0 -2011-01-03 02:00:00,10494.0 -2011-01-03 03:00:00,10279.0 -2011-01-03 04:00:00,10165.0 -2011-01-03 05:00:00,10199.0 -2011-01-03 06:00:00,10440.0 -2011-01-03 07:00:00,11096.0 -2011-01-03 08:00:00,12125.0 -2011-01-03 09:00:00,12687.0 -2011-01-03 10:00:00,12682.0 -2011-01-03 11:00:00,12707.0 -2011-01-03 12:00:00,12662.0 -2011-01-03 13:00:00,12573.0 -2011-01-03 14:00:00,12514.0 -2011-01-03 15:00:00,12463.0 -2011-01-03 16:00:00,12500.0 -2011-01-03 17:00:00,12594.0 -2011-01-03 18:00:00,13206.0 -2011-01-03 19:00:00,13873.0 -2011-01-03 20:00:00,13712.0 -2011-01-03 21:00:00,13505.0 -2011-01-03 22:00:00,13175.0 -2011-01-03 23:00:00,12613.0 -2011-01-04 00:00:00,11763.0 -2011-01-02 01:00:00,11199.0 -2011-01-02 02:00:00,10682.0 -2011-01-02 03:00:00,10376.0 -2011-01-02 04:00:00,10247.0 -2011-01-02 05:00:00,10161.0 -2011-01-02 06:00:00,10220.0 -2011-01-02 07:00:00,10346.0 -2011-01-02 08:00:00,10651.0 -2011-01-02 09:00:00,10710.0 -2011-01-02 10:00:00,10834.0 -2011-01-02 11:00:00,10945.0 -2011-01-02 12:00:00,11078.0 -2011-01-02 13:00:00,11127.0 -2011-01-02 14:00:00,11141.0 -2011-01-02 15:00:00,11057.0 -2011-01-02 16:00:00,10973.0 -2011-01-02 17:00:00,11074.0 -2011-01-02 18:00:00,11929.0 -2011-01-02 19:00:00,12841.0 -2011-01-02 20:00:00,12956.0 -2011-01-02 21:00:00,12849.0 -2011-01-02 22:00:00,12689.0 -2011-01-02 23:00:00,12274.0 -2011-01-03 00:00:00,11662.0 -2011-01-01 01:00:00,9631.0 -2011-01-01 02:00:00,9273.0 -2011-01-01 03:00:00,9011.0 -2011-01-01 04:00:00,8741.0 -2011-01-01 05:00:00,8694.0 -2011-01-01 06:00:00,8711.0 -2011-01-01 07:00:00,8943.0 -2011-01-01 08:00:00,9222.0 -2011-01-01 09:00:00,9430.0 -2011-01-01 10:00:00,9670.0 -2011-01-01 11:00:00,10125.0 -2011-01-01 12:00:00,10538.0 -2011-01-01 13:00:00,10782.0 -2011-01-01 14:00:00,10861.0 -2011-01-01 15:00:00,10784.0 -2011-01-01 16:00:00,10928.0 -2011-01-01 17:00:00,11133.0 -2011-01-01 18:00:00,11924.0 -2011-01-01 19:00:00,12665.0 -2011-01-01 20:00:00,12701.0 -2011-01-01 21:00:00,12630.0 -2011-01-01 22:00:00,12513.0 -2011-01-01 23:00:00,12252.0 -2011-01-02 00:00:00,11778.0 -2012-12-31 01:00:00,10445.0 -2012-12-31 02:00:00,9897.0 -2012-12-31 03:00:00,9538.0 -2012-12-31 04:00:00,9333.0 -2012-12-31 05:00:00,9275.0 -2012-12-31 06:00:00,9463.0 -2012-12-31 07:00:00,9830.0 -2012-12-31 08:00:00,10443.0 -2012-12-31 09:00:00,10713.0 -2012-12-31 10:00:00,10845.0 -2012-12-31 11:00:00,11044.0 -2012-12-31 12:00:00,11269.0 -2012-12-31 13:00:00,11311.0 -2012-12-31 14:00:00,11349.0 -2012-12-31 15:00:00,11372.0 -2012-12-31 16:00:00,11297.0 -2012-12-31 17:00:00,11378.0 -2012-12-31 18:00:00,12062.0 -2012-12-31 19:00:00,12779.0 -2012-12-31 20:00:00,12508.0 -2012-12-31 21:00:00,12154.0 -2012-12-31 22:00:00,11812.0 -2012-12-31 23:00:00,11437.0 -2013-01-01 00:00:00,11009.0 -2012-12-30 01:00:00,10857.0 -2012-12-30 02:00:00,10294.0 -2012-12-30 03:00:00,10002.0 -2012-12-30 04:00:00,9823.0 -2012-12-30 05:00:00,9768.0 -2012-12-30 06:00:00,9810.0 -2012-12-30 07:00:00,9986.0 -2012-12-30 08:00:00,10244.0 -2012-12-30 09:00:00,10289.0 -2012-12-30 10:00:00,10399.0 -2012-12-30 11:00:00,10452.0 -2012-12-30 12:00:00,10528.0 -2012-12-30 13:00:00,10498.0 -2012-12-30 14:00:00,10459.0 -2012-12-30 15:00:00,10346.0 -2012-12-30 16:00:00,10337.0 -2012-12-30 17:00:00,10446.0 -2012-12-30 18:00:00,11412.0 -2012-12-30 19:00:00,12278.0 -2012-12-30 20:00:00,12309.0 -2012-12-30 21:00:00,12257.0 -2012-12-30 22:00:00,12031.0 -2012-12-30 23:00:00,11690.0 -2012-12-31 00:00:00,11071.0 -2012-12-29 01:00:00,10594.0 -2012-12-29 02:00:00,9985.0 -2012-12-29 03:00:00,9602.0 -2012-12-29 04:00:00,9378.0 -2012-12-29 05:00:00,9292.0 -2012-12-29 06:00:00,9357.0 -2012-12-29 07:00:00,9608.0 -2012-12-29 08:00:00,10059.0 -2012-12-29 09:00:00,10394.0 -2012-12-29 10:00:00,10587.0 -2012-12-29 11:00:00,10901.0 -2012-12-29 12:00:00,11107.0 -2012-12-29 13:00:00,11208.0 -2012-12-29 14:00:00,11146.0 -2012-12-29 15:00:00,11078.0 -2012-12-29 16:00:00,11051.0 -2012-12-29 17:00:00,11219.0 -2012-12-29 18:00:00,11992.0 -2012-12-29 19:00:00,12591.0 -2012-12-29 20:00:00,12579.0 -2012-12-29 21:00:00,12462.0 -2012-12-29 22:00:00,12320.0 -2012-12-29 23:00:00,12037.0 -2012-12-30 00:00:00,11455.0 -2012-12-28 01:00:00,10617.0 -2012-12-28 02:00:00,10002.0 -2012-12-28 03:00:00,9629.0 -2012-12-28 04:00:00,9443.0 -2012-12-28 05:00:00,9405.0 -2012-12-28 06:00:00,9593.0 -2012-12-28 07:00:00,10160.0 -2012-12-28 08:00:00,10999.0 -2012-12-28 09:00:00,11566.0 -2012-12-28 10:00:00,11845.0 -2012-12-28 11:00:00,12091.0 -2012-12-28 12:00:00,12193.0 -2012-12-28 13:00:00,12277.0 -2012-12-28 14:00:00,12265.0 -2012-12-28 15:00:00,12279.0 -2012-12-28 16:00:00,12223.0 -2012-12-28 17:00:00,12287.0 -2012-12-28 18:00:00,12946.0 -2012-12-28 19:00:00,13325.0 -2012-12-28 20:00:00,13075.0 -2012-12-28 21:00:00,12833.0 -2012-12-28 22:00:00,12498.0 -2012-12-28 23:00:00,12071.0 -2012-12-29 00:00:00,11339.0 -2012-12-27 01:00:00,10736.0 -2012-12-27 02:00:00,10073.0 -2012-12-27 03:00:00,9741.0 -2012-12-27 04:00:00,9534.0 -2012-12-27 05:00:00,9493.0 -2012-12-27 06:00:00,9726.0 -2012-12-27 07:00:00,10306.0 -2012-12-27 08:00:00,11186.0 -2012-12-27 09:00:00,11802.0 -2012-12-27 10:00:00,12050.0 -2012-12-27 11:00:00,12276.0 -2012-12-27 12:00:00,12469.0 -2012-12-27 13:00:00,12432.0 -2012-12-27 14:00:00,12318.0 -2012-12-27 15:00:00,12343.0 -2012-12-27 16:00:00,12293.0 -2012-12-27 17:00:00,12300.0 -2012-12-27 18:00:00,12986.0 -2012-12-27 19:00:00,13521.0 -2012-12-27 20:00:00,13300.0 -2012-12-27 21:00:00,13057.0 -2012-12-27 22:00:00,12749.0 -2012-12-27 23:00:00,12229.0 -2012-12-28 00:00:00,11425.0 -2012-12-26 01:00:00,9966.0 -2012-12-26 02:00:00,9487.0 -2012-12-26 03:00:00,9209.0 -2012-12-26 04:00:00,9093.0 -2012-12-26 05:00:00,9090.0 -2012-12-26 06:00:00,9342.0 -2012-12-26 07:00:00,10001.0 -2012-12-26 08:00:00,10954.0 -2012-12-26 09:00:00,11603.0 -2012-12-26 10:00:00,11908.0 -2012-12-26 11:00:00,12192.0 -2012-12-26 12:00:00,12418.0 -2012-12-26 13:00:00,12533.0 -2012-12-26 14:00:00,12558.0 -2012-12-26 15:00:00,12562.0 -2012-12-26 16:00:00,12511.0 -2012-12-26 17:00:00,12576.0 -2012-12-26 18:00:00,13239.0 -2012-12-26 19:00:00,13715.0 -2012-12-26 20:00:00,13449.0 -2012-12-26 21:00:00,13181.0 -2012-12-26 22:00:00,12881.0 -2012-12-26 23:00:00,12411.0 -2012-12-27 00:00:00,11588.0 -2012-12-25 01:00:00,10158.0 -2012-12-25 02:00:00,9654.0 -2012-12-25 03:00:00,9313.0 -2012-12-25 04:00:00,9095.0 -2012-12-25 05:00:00,9030.0 -2012-12-25 06:00:00,9073.0 -2012-12-25 07:00:00,9267.0 -2012-12-25 08:00:00,9571.0 -2012-12-25 09:00:00,9689.0 -2012-12-25 10:00:00,9813.0 -2012-12-25 11:00:00,10021.0 -2012-12-25 12:00:00,10109.0 -2012-12-25 13:00:00,10208.0 -2012-12-25 14:00:00,10227.0 -2012-12-25 15:00:00,10203.0 -2012-12-25 16:00:00,10185.0 -2012-12-25 17:00:00,10296.0 -2012-12-25 18:00:00,10957.0 -2012-12-25 19:00:00,11354.0 -2012-12-25 20:00:00,11309.0 -2012-12-25 21:00:00,11247.0 -2012-12-25 22:00:00,11198.0 -2012-12-25 23:00:00,10993.0 -2012-12-26 00:00:00,10547.0 -2012-12-24 01:00:00,10345.0 -2012-12-24 02:00:00,9777.0 -2012-12-24 03:00:00,9356.0 -2012-12-24 04:00:00,9180.0 -2012-12-24 05:00:00,9123.0 -2012-12-24 06:00:00,9291.0 -2012-12-24 07:00:00,9618.0 -2012-12-24 08:00:00,10155.0 -2012-12-24 09:00:00,10424.0 -2012-12-24 10:00:00,10616.0 -2012-12-24 11:00:00,10922.0 -2012-12-24 12:00:00,11133.0 -2012-12-24 13:00:00,11238.0 -2012-12-24 14:00:00,11227.0 -2012-12-24 15:00:00,11210.0 -2012-12-24 16:00:00,11195.0 -2012-12-24 17:00:00,11311.0 -2012-12-24 18:00:00,11934.0 -2012-12-24 19:00:00,12183.0 -2012-12-24 20:00:00,11808.0 -2012-12-24 21:00:00,11537.0 -2012-12-24 22:00:00,11336.0 -2012-12-24 23:00:00,11109.0 -2012-12-25 00:00:00,10695.0 -2012-12-23 01:00:00,10824.0 -2012-12-23 02:00:00,10248.0 -2012-12-23 03:00:00,9802.0 -2012-12-23 04:00:00,9627.0 -2012-12-23 05:00:00,9531.0 -2012-12-23 06:00:00,9618.0 -2012-12-23 07:00:00,9752.0 -2012-12-23 08:00:00,10079.0 -2012-12-23 09:00:00,10133.0 -2012-12-23 10:00:00,10276.0 -2012-12-23 11:00:00,10408.0 -2012-12-23 12:00:00,10470.0 -2012-12-23 13:00:00,10491.0 -2012-12-23 14:00:00,10465.0 -2012-12-23 15:00:00,10406.0 -2012-12-23 16:00:00,10458.0 -2012-12-23 17:00:00,10600.0 -2012-12-23 18:00:00,11498.0 -2012-12-23 19:00:00,12194.0 -2012-12-23 20:00:00,12223.0 -2012-12-23 21:00:00,12146.0 -2012-12-23 22:00:00,12003.0 -2012-12-23 23:00:00,11695.0 -2012-12-24 00:00:00,11114.0 -2012-12-22 01:00:00,11473.0 -2012-12-22 02:00:00,10813.0 -2012-12-22 03:00:00,10434.0 -2012-12-22 04:00:00,10210.0 -2012-12-22 05:00:00,10137.0 -2012-12-22 06:00:00,10178.0 -2012-12-22 07:00:00,10472.0 -2012-12-22 08:00:00,10970.0 -2012-12-22 09:00:00,11139.0 -2012-12-22 10:00:00,11337.0 -2012-12-22 11:00:00,11422.0 -2012-12-22 12:00:00,11360.0 -2012-12-22 13:00:00,11229.0 -2012-12-22 14:00:00,10978.0 -2012-12-22 15:00:00,10773.0 -2012-12-22 16:00:00,10691.0 -2012-12-22 17:00:00,10780.0 -2012-12-22 18:00:00,11662.0 -2012-12-22 19:00:00,12522.0 -2012-12-22 20:00:00,12564.0 -2012-12-22 21:00:00,12448.0 -2012-12-22 22:00:00,12338.0 -2012-12-22 23:00:00,12044.0 -2012-12-23 00:00:00,11528.0 -2012-12-21 01:00:00,11018.0 -2012-12-21 02:00:00,10378.0 -2012-12-21 03:00:00,10002.0 -2012-12-21 04:00:00,9849.0 -2012-12-21 05:00:00,9878.0 -2012-12-21 06:00:00,10136.0 -2012-12-21 07:00:00,10854.0 -2012-12-21 08:00:00,12041.0 -2012-12-21 09:00:00,12679.0 -2012-12-21 10:00:00,12802.0 -2012-12-21 11:00:00,12889.0 -2012-12-21 12:00:00,12826.0 -2012-12-21 13:00:00,12699.0 -2012-12-21 14:00:00,12553.0 -2012-12-21 15:00:00,12528.0 -2012-12-21 16:00:00,12449.0 -2012-12-21 17:00:00,12472.0 -2012-12-21 18:00:00,13387.0 -2012-12-21 19:00:00,14173.0 -2012-12-21 20:00:00,14040.0 -2012-12-21 21:00:00,13796.0 -2012-12-21 22:00:00,13510.0 -2012-12-21 23:00:00,13121.0 -2012-12-22 00:00:00,12345.0 -2012-12-20 01:00:00,10772.0 -2012-12-20 02:00:00,10101.0 -2012-12-20 03:00:00,9678.0 -2012-12-20 04:00:00,9469.0 -2012-12-20 05:00:00,9459.0 -2012-12-20 06:00:00,9717.0 -2012-12-20 07:00:00,10381.0 -2012-12-20 08:00:00,11518.0 -2012-12-20 09:00:00,12305.0 -2012-12-20 10:00:00,12462.0 -2012-12-20 11:00:00,12478.0 -2012-12-20 12:00:00,12520.0 -2012-12-20 13:00:00,12528.0 -2012-12-20 14:00:00,12505.0 -2012-12-20 15:00:00,12567.0 -2012-12-20 16:00:00,12477.0 -2012-12-20 17:00:00,12590.0 -2012-12-20 18:00:00,13306.0 -2012-12-20 19:00:00,13752.0 -2012-12-20 20:00:00,13708.0 -2012-12-20 21:00:00,13509.0 -2012-12-20 22:00:00,13284.0 -2012-12-20 23:00:00,12798.0 -2012-12-21 00:00:00,11915.0 -2012-12-19 01:00:00,10638.0 -2012-12-19 02:00:00,9997.0 -2012-12-19 03:00:00,9611.0 -2012-12-19 04:00:00,9410.0 -2012-12-19 05:00:00,9396.0 -2012-12-19 06:00:00,9627.0 -2012-12-19 07:00:00,10354.0 -2012-12-19 08:00:00,11581.0 -2012-12-19 09:00:00,12244.0 -2012-12-19 10:00:00,12322.0 -2012-12-19 11:00:00,12365.0 -2012-12-19 12:00:00,12395.0 -2012-12-19 13:00:00,12298.0 -2012-12-19 14:00:00,12184.0 -2012-12-19 15:00:00,12270.0 -2012-12-19 16:00:00,12250.0 -2012-12-19 17:00:00,12413.0 -2012-12-19 18:00:00,13260.0 -2012-12-19 19:00:00,13750.0 -2012-12-19 20:00:00,13593.0 -2012-12-19 21:00:00,13420.0 -2012-12-19 22:00:00,13175.0 -2012-12-19 23:00:00,12644.0 -2012-12-20 00:00:00,11696.0 -2012-12-18 01:00:00,10622.0 -2012-12-18 02:00:00,9948.0 -2012-12-18 03:00:00,9609.0 -2012-12-18 04:00:00,9405.0 -2012-12-18 05:00:00,9388.0 -2012-12-18 06:00:00,9600.0 -2012-12-18 07:00:00,10320.0 -2012-12-18 08:00:00,11614.0 -2012-12-18 09:00:00,12281.0 -2012-12-18 10:00:00,12412.0 -2012-12-18 11:00:00,12500.0 -2012-12-18 12:00:00,12590.0 -2012-12-18 13:00:00,12620.0 -2012-12-18 14:00:00,12514.0 -2012-12-18 15:00:00,12551.0 -2012-12-18 16:00:00,12544.0 -2012-12-18 17:00:00,12817.0 -2012-12-18 18:00:00,13556.0 -2012-12-18 19:00:00,13823.0 -2012-12-18 20:00:00,13555.0 -2012-12-18 21:00:00,13321.0 -2012-12-18 22:00:00,13027.0 -2012-12-18 23:00:00,12498.0 -2012-12-19 00:00:00,11558.0 -2012-12-17 01:00:00,9776.0 -2012-12-17 02:00:00,9225.0 -2012-12-17 03:00:00,8918.0 -2012-12-17 04:00:00,8837.0 -2012-12-17 05:00:00,8841.0 -2012-12-17 06:00:00,9163.0 -2012-12-17 07:00:00,9924.0 -2012-12-17 08:00:00,11258.0 -2012-12-17 09:00:00,12018.0 -2012-12-17 10:00:00,12135.0 -2012-12-17 11:00:00,12190.0 -2012-12-17 12:00:00,12236.0 -2012-12-17 13:00:00,12203.0 -2012-12-17 14:00:00,12167.0 -2012-12-17 15:00:00,12181.0 -2012-12-17 16:00:00,12126.0 -2012-12-17 17:00:00,12157.0 -2012-12-17 18:00:00,12978.0 -2012-12-17 19:00:00,13596.0 -2012-12-17 20:00:00,13517.0 -2012-12-17 21:00:00,13331.0 -2012-12-17 22:00:00,13059.0 -2012-12-17 23:00:00,12539.0 -2012-12-18 00:00:00,11566.0 -2012-12-16 01:00:00,9816.0 -2012-12-16 02:00:00,9147.0 -2012-12-16 03:00:00,8679.0 -2012-12-16 04:00:00,8470.0 -2012-12-16 05:00:00,8299.0 -2012-12-16 06:00:00,8356.0 -2012-12-16 07:00:00,8460.0 -2012-12-16 08:00:00,8799.0 -2012-12-16 09:00:00,9040.0 -2012-12-16 10:00:00,9404.0 -2012-12-16 11:00:00,9666.0 -2012-12-16 12:00:00,9879.0 -2012-12-16 13:00:00,10016.0 -2012-12-16 14:00:00,10124.0 -2012-12-16 15:00:00,10174.0 -2012-12-16 16:00:00,10275.0 -2012-12-16 17:00:00,10525.0 -2012-12-16 18:00:00,11410.0 -2012-12-16 19:00:00,11860.0 -2012-12-16 20:00:00,11920.0 -2012-12-16 21:00:00,11785.0 -2012-12-16 22:00:00,11671.0 -2012-12-16 23:00:00,11213.0 -2012-12-17 00:00:00,10556.0 -2012-12-15 01:00:00,10466.0 -2012-12-15 02:00:00,9743.0 -2012-12-15 03:00:00,9298.0 -2012-12-15 04:00:00,9085.0 -2012-12-15 05:00:00,8948.0 -2012-12-15 06:00:00,9020.0 -2012-12-15 07:00:00,9318.0 -2012-12-15 08:00:00,9868.0 -2012-12-15 09:00:00,10325.0 -2012-12-15 10:00:00,10811.0 -2012-12-15 11:00:00,11122.0 -2012-12-15 12:00:00,11337.0 -2012-12-15 13:00:00,11381.0 -2012-12-15 14:00:00,11328.0 -2012-12-15 15:00:00,11118.0 -2012-12-15 16:00:00,11089.0 -2012-12-15 17:00:00,11221.0 -2012-12-15 18:00:00,11779.0 -2012-12-15 19:00:00,12045.0 -2012-12-15 20:00:00,11964.0 -2012-12-15 21:00:00,11729.0 -2012-12-15 22:00:00,11498.0 -2012-12-15 23:00:00,11140.0 -2012-12-16 00:00:00,10546.0 -2012-12-14 01:00:00,10539.0 -2012-12-14 02:00:00,9931.0 -2012-12-14 03:00:00,9588.0 -2012-12-14 04:00:00,9404.0 -2012-12-14 05:00:00,9406.0 -2012-12-14 06:00:00,9687.0 -2012-12-14 07:00:00,10430.0 -2012-12-14 08:00:00,11687.0 -2012-12-14 09:00:00,12143.0 -2012-12-14 10:00:00,12169.0 -2012-12-14 11:00:00,12058.0 -2012-12-14 12:00:00,11963.0 -2012-12-14 13:00:00,11800.0 -2012-12-14 14:00:00,11618.0 -2012-12-14 15:00:00,11621.0 -2012-12-14 16:00:00,11538.0 -2012-12-14 17:00:00,11626.0 -2012-12-14 18:00:00,12626.0 -2012-12-14 19:00:00,13242.0 -2012-12-14 20:00:00,13108.0 -2012-12-14 21:00:00,12838.0 -2012-12-14 22:00:00,12515.0 -2012-12-14 23:00:00,12159.0 -2012-12-15 00:00:00,11313.0 -2012-12-13 01:00:00,10825.0 -2012-12-13 02:00:00,10195.0 -2012-12-13 03:00:00,9822.0 -2012-12-13 04:00:00,9627.0 -2012-12-13 05:00:00,9647.0 -2012-12-13 06:00:00,9895.0 -2012-12-13 07:00:00,10632.0 -2012-12-13 08:00:00,11862.0 -2012-12-13 09:00:00,12351.0 -2012-12-13 10:00:00,12347.0 -2012-12-13 11:00:00,12274.0 -2012-12-13 12:00:00,12163.0 -2012-12-13 13:00:00,12077.0 -2012-12-13 14:00:00,11962.0 -2012-12-13 15:00:00,11892.0 -2012-12-13 16:00:00,11800.0 -2012-12-13 17:00:00,11822.0 -2012-12-13 18:00:00,12784.0 -2012-12-13 19:00:00,13581.0 -2012-12-13 20:00:00,13427.0 -2012-12-13 21:00:00,13252.0 -2012-12-13 22:00:00,12952.0 -2012-12-13 23:00:00,12424.0 -2012-12-14 00:00:00,11468.0 -2012-12-12 01:00:00,11029.0 -2012-12-12 02:00:00,10428.0 -2012-12-12 03:00:00,10047.0 -2012-12-12 04:00:00,9866.0 -2012-12-12 05:00:00,9884.0 -2012-12-12 06:00:00,10166.0 -2012-12-12 07:00:00,10866.0 -2012-12-12 08:00:00,12122.0 -2012-12-12 09:00:00,12599.0 -2012-12-12 10:00:00,12543.0 -2012-12-12 11:00:00,12457.0 -2012-12-12 12:00:00,12413.0 -2012-12-12 13:00:00,12283.0 -2012-12-12 14:00:00,12109.0 -2012-12-12 15:00:00,12047.0 -2012-12-12 16:00:00,11909.0 -2012-12-12 17:00:00,11916.0 -2012-12-12 18:00:00,12838.0 -2012-12-12 19:00:00,13669.0 -2012-12-12 20:00:00,13626.0 -2012-12-12 21:00:00,13482.0 -2012-12-12 22:00:00,13230.0 -2012-12-12 23:00:00,12718.0 -2012-12-13 00:00:00,11742.0 -2012-12-11 01:00:00,10837.0 -2012-12-11 02:00:00,10219.0 -2012-12-11 03:00:00,9889.0 -2012-12-11 04:00:00,9695.0 -2012-12-11 05:00:00,9717.0 -2012-12-11 06:00:00,10007.0 -2012-12-11 07:00:00,10777.0 -2012-12-11 08:00:00,12082.0 -2012-12-11 09:00:00,12597.0 -2012-12-11 10:00:00,12586.0 -2012-12-11 11:00:00,12560.0 -2012-12-11 12:00:00,12564.0 -2012-12-11 13:00:00,12503.0 -2012-12-11 14:00:00,12383.0 -2012-12-11 15:00:00,12360.0 -2012-12-11 16:00:00,12263.0 -2012-12-11 17:00:00,12342.0 -2012-12-11 18:00:00,13265.0 -2012-12-11 19:00:00,14096.0 -2012-12-11 20:00:00,13929.0 -2012-12-11 21:00:00,13763.0 -2012-12-11 22:00:00,13496.0 -2012-12-11 23:00:00,12936.0 -2012-12-12 00:00:00,11990.0 -2012-12-10 01:00:00,9854.0 -2012-12-10 02:00:00,9334.0 -2012-12-10 03:00:00,9129.0 -2012-12-10 04:00:00,9067.0 -2012-12-10 05:00:00,9137.0 -2012-12-10 06:00:00,9471.0 -2012-12-10 07:00:00,10364.0 -2012-12-10 08:00:00,11637.0 -2012-12-10 09:00:00,12443.0 -2012-12-10 10:00:00,12528.0 -2012-12-10 11:00:00,12669.0 -2012-12-10 12:00:00,12782.0 -2012-12-10 13:00:00,12741.0 -2012-12-10 14:00:00,12696.0 -2012-12-10 15:00:00,12624.0 -2012-12-10 16:00:00,12559.0 -2012-12-10 17:00:00,12689.0 -2012-12-10 18:00:00,13495.0 -2012-12-10 19:00:00,14040.0 -2012-12-10 20:00:00,13873.0 -2012-12-10 21:00:00,13731.0 -2012-12-10 22:00:00,13407.0 -2012-12-10 23:00:00,12841.0 -2012-12-11 00:00:00,11797.0 -2012-12-09 01:00:00,10063.0 -2012-12-09 02:00:00,9464.0 -2012-12-09 03:00:00,9110.0 -2012-12-09 04:00:00,8933.0 -2012-12-09 05:00:00,8809.0 -2012-12-09 06:00:00,8814.0 -2012-12-09 07:00:00,8946.0 -2012-12-09 08:00:00,9257.0 -2012-12-09 09:00:00,9424.0 -2012-12-09 10:00:00,9749.0 -2012-12-09 11:00:00,10131.0 -2012-12-09 12:00:00,10410.0 -2012-12-09 13:00:00,10552.0 -2012-12-09 14:00:00,10607.0 -2012-12-09 15:00:00,10582.0 -2012-12-09 16:00:00,10618.0 -2012-12-09 17:00:00,10778.0 -2012-12-09 18:00:00,11508.0 -2012-12-09 19:00:00,12002.0 -2012-12-09 20:00:00,11976.0 -2012-12-09 21:00:00,11930.0 -2012-12-09 22:00:00,11685.0 -2012-12-09 23:00:00,11321.0 -2012-12-10 00:00:00,10576.0 -2012-12-08 01:00:00,10249.0 -2012-12-08 02:00:00,9634.0 -2012-12-08 03:00:00,9272.0 -2012-12-08 04:00:00,9021.0 -2012-12-08 05:00:00,8971.0 -2012-12-08 06:00:00,9006.0 -2012-12-08 07:00:00,9280.0 -2012-12-08 08:00:00,9820.0 -2012-12-08 09:00:00,10200.0 -2012-12-08 10:00:00,10517.0 -2012-12-08 11:00:00,10763.0 -2012-12-08 12:00:00,10898.0 -2012-12-08 13:00:00,10865.0 -2012-12-08 14:00:00,10709.0 -2012-12-08 15:00:00,10527.0 -2012-12-08 16:00:00,10462.0 -2012-12-08 17:00:00,10635.0 -2012-12-08 18:00:00,11526.0 -2012-12-08 19:00:00,12120.0 -2012-12-08 20:00:00,12060.0 -2012-12-08 21:00:00,11936.0 -2012-12-08 22:00:00,11726.0 -2012-12-08 23:00:00,11374.0 -2012-12-09 00:00:00,10765.0 -2012-12-07 01:00:00,10117.0 -2012-12-07 02:00:00,9506.0 -2012-12-07 03:00:00,9157.0 -2012-12-07 04:00:00,8999.0 -2012-12-07 05:00:00,8956.0 -2012-12-07 06:00:00,9185.0 -2012-12-07 07:00:00,9907.0 -2012-12-07 08:00:00,11073.0 -2012-12-07 09:00:00,11695.0 -2012-12-07 10:00:00,11794.0 -2012-12-07 11:00:00,11858.0 -2012-12-07 12:00:00,11903.0 -2012-12-07 13:00:00,11877.0 -2012-12-07 14:00:00,11899.0 -2012-12-07 15:00:00,11934.0 -2012-12-07 16:00:00,11892.0 -2012-12-07 17:00:00,12025.0 -2012-12-07 18:00:00,12807.0 -2012-12-07 19:00:00,13177.0 -2012-12-07 20:00:00,12971.0 -2012-12-07 21:00:00,12674.0 -2012-12-07 22:00:00,12361.0 -2012-12-07 23:00:00,11929.0 -2012-12-08 00:00:00,11106.0 -2012-12-06 01:00:00,10550.0 -2012-12-06 02:00:00,9929.0 -2012-12-06 03:00:00,9606.0 -2012-12-06 04:00:00,9427.0 -2012-12-06 05:00:00,9407.0 -2012-12-06 06:00:00,9655.0 -2012-12-06 07:00:00,10402.0 -2012-12-06 08:00:00,11641.0 -2012-12-06 09:00:00,12179.0 -2012-12-06 10:00:00,12299.0 -2012-12-06 11:00:00,12330.0 -2012-12-06 12:00:00,12320.0 -2012-12-06 13:00:00,12203.0 -2012-12-06 14:00:00,12082.0 -2012-12-06 15:00:00,12091.0 -2012-12-06 16:00:00,12053.0 -2012-12-06 17:00:00,12216.0 -2012-12-06 18:00:00,13058.0 -2012-12-06 19:00:00,13451.0 -2012-12-06 20:00:00,13239.0 -2012-12-06 21:00:00,13022.0 -2012-12-06 22:00:00,12670.0 -2012-12-06 23:00:00,12045.0 -2012-12-07 00:00:00,11080.0 -2012-12-05 01:00:00,9987.0 -2012-12-05 02:00:00,9448.0 -2012-12-05 03:00:00,9158.0 -2012-12-05 04:00:00,9055.0 -2012-12-05 05:00:00,9098.0 -2012-12-05 06:00:00,9413.0 -2012-12-05 07:00:00,10196.0 -2012-12-05 08:00:00,11519.0 -2012-12-05 09:00:00,11971.0 -2012-12-05 10:00:00,12009.0 -2012-12-05 11:00:00,12017.0 -2012-12-05 12:00:00,11999.0 -2012-12-05 13:00:00,11837.0 -2012-12-05 14:00:00,11825.0 -2012-12-05 15:00:00,11800.0 -2012-12-05 16:00:00,11735.0 -2012-12-05 17:00:00,11852.0 -2012-12-05 18:00:00,12826.0 -2012-12-05 19:00:00,13567.0 -2012-12-05 20:00:00,13451.0 -2012-12-05 21:00:00,13257.0 -2012-12-05 22:00:00,13011.0 -2012-12-05 23:00:00,12431.0 -2012-12-06 00:00:00,11446.0 -2012-12-04 01:00:00,9665.0 -2012-12-04 02:00:00,9016.0 -2012-12-04 03:00:00,8616.0 -2012-12-04 04:00:00,8441.0 -2012-12-04 05:00:00,8388.0 -2012-12-04 06:00:00,8581.0 -2012-12-04 07:00:00,9284.0 -2012-12-04 08:00:00,10506.0 -2012-12-04 09:00:00,11111.0 -2012-12-04 10:00:00,11281.0 -2012-12-04 11:00:00,11314.0 -2012-12-04 12:00:00,11357.0 -2012-12-04 13:00:00,11311.0 -2012-12-04 14:00:00,11278.0 -2012-12-04 15:00:00,11348.0 -2012-12-04 16:00:00,11241.0 -2012-12-04 17:00:00,11231.0 -2012-12-04 18:00:00,12030.0 -2012-12-04 19:00:00,12802.0 -2012-12-04 20:00:00,12732.0 -2012-12-04 21:00:00,12560.0 -2012-12-04 22:00:00,12289.0 -2012-12-04 23:00:00,11784.0 -2012-12-05 00:00:00,10851.0 -2012-12-03 01:00:00,9058.0 -2012-12-03 02:00:00,8655.0 -2012-12-03 03:00:00,8389.0 -2012-12-03 04:00:00,8254.0 -2012-12-03 05:00:00,8285.0 -2012-12-03 06:00:00,8512.0 -2012-12-03 07:00:00,9286.0 -2012-12-03 08:00:00,10530.0 -2012-12-03 09:00:00,11280.0 -2012-12-03 10:00:00,11551.0 -2012-12-03 11:00:00,11649.0 -2012-12-03 12:00:00,11799.0 -2012-12-03 13:00:00,11829.0 -2012-12-03 14:00:00,11801.0 -2012-12-03 15:00:00,11814.0 -2012-12-03 16:00:00,11726.0 -2012-12-03 17:00:00,11732.0 -2012-12-03 18:00:00,12544.0 -2012-12-03 19:00:00,13095.0 -2012-12-03 20:00:00,12963.0 -2012-12-03 21:00:00,12686.0 -2012-12-03 22:00:00,12317.0 -2012-12-03 23:00:00,11709.0 -2012-12-04 00:00:00,10689.0 -2012-12-02 01:00:00,9299.0 -2012-12-02 02:00:00,8724.0 -2012-12-02 03:00:00,8389.0 -2012-12-02 04:00:00,8080.0 -2012-12-02 05:00:00,7984.0 -2012-12-02 06:00:00,7932.0 -2012-12-02 07:00:00,8035.0 -2012-12-02 08:00:00,8362.0 -2012-12-02 09:00:00,8443.0 -2012-12-02 10:00:00,8702.0 -2012-12-02 11:00:00,8959.0 -2012-12-02 12:00:00,9171.0 -2012-12-02 13:00:00,9227.0 -2012-12-02 14:00:00,9295.0 -2012-12-02 15:00:00,9257.0 -2012-12-02 16:00:00,9230.0 -2012-12-02 17:00:00,9318.0 -2012-12-02 18:00:00,10329.0 -2012-12-02 19:00:00,11089.0 -2012-12-02 20:00:00,11190.0 -2012-12-02 21:00:00,11055.0 -2012-12-02 22:00:00,10873.0 -2012-12-02 23:00:00,10437.0 -2012-12-03 00:00:00,9762.0 -2012-12-01 01:00:00,10037.0 -2012-12-01 02:00:00,9512.0 -2012-12-01 03:00:00,9103.0 -2012-12-01 04:00:00,8918.0 -2012-12-01 05:00:00,8847.0 -2012-12-01 06:00:00,8936.0 -2012-12-01 07:00:00,9216.0 -2012-12-01 08:00:00,9740.0 -2012-12-01 09:00:00,9943.0 -2012-12-01 10:00:00,10198.0 -2012-12-01 11:00:00,10321.0 -2012-12-01 12:00:00,10316.0 -2012-12-01 13:00:00,10277.0 -2012-12-01 14:00:00,10174.0 -2012-12-01 15:00:00,10163.0 -2012-12-01 16:00:00,10084.0 -2012-12-01 17:00:00,10188.0 -2012-12-01 18:00:00,11054.0 -2012-12-01 19:00:00,11450.0 -2012-12-01 20:00:00,11331.0 -2012-12-01 21:00:00,11170.0 -2012-12-01 22:00:00,10966.0 -2012-12-01 23:00:00,10593.0 -2012-12-02 00:00:00,9988.0 -2012-11-30 01:00:00,10065.0 -2012-11-30 02:00:00,9537.0 -2012-11-30 03:00:00,9169.0 -2012-11-30 04:00:00,8979.0 -2012-11-30 05:00:00,8931.0 -2012-11-30 06:00:00,9171.0 -2012-11-30 07:00:00,9857.0 -2012-11-30 08:00:00,10987.0 -2012-11-30 09:00:00,11479.0 -2012-11-30 10:00:00,11524.0 -2012-11-30 11:00:00,11531.0 -2012-11-30 12:00:00,11582.0 -2012-11-30 13:00:00,11481.0 -2012-11-30 14:00:00,11336.0 -2012-11-30 15:00:00,11324.0 -2012-11-30 16:00:00,11217.0 -2012-11-30 17:00:00,11254.0 -2012-11-30 18:00:00,12091.0 -2012-11-30 19:00:00,12644.0 -2012-11-30 20:00:00,12488.0 -2012-11-30 21:00:00,12262.0 -2012-11-30 22:00:00,12005.0 -2012-11-30 23:00:00,11564.0 -2012-12-01 00:00:00,10861.0 -2012-11-29 01:00:00,10540.0 -2012-11-29 02:00:00,9960.0 -2012-11-29 03:00:00,9625.0 -2012-11-29 04:00:00,9468.0 -2012-11-29 05:00:00,9455.0 -2012-11-29 06:00:00,9747.0 -2012-11-29 07:00:00,10467.0 -2012-11-29 08:00:00,11650.0 -2012-11-29 09:00:00,12059.0 -2012-11-29 10:00:00,12063.0 -2012-11-29 11:00:00,12018.0 -2012-11-29 12:00:00,11976.0 -2012-11-29 13:00:00,11877.0 -2012-11-29 14:00:00,11727.0 -2012-11-29 15:00:00,11660.0 -2012-11-29 16:00:00,11587.0 -2012-11-29 17:00:00,11618.0 -2012-11-29 18:00:00,12463.0 -2012-11-29 19:00:00,13012.0 -2012-11-29 20:00:00,12855.0 -2012-11-29 21:00:00,12681.0 -2012-11-29 22:00:00,12322.0 -2012-11-29 23:00:00,11782.0 -2012-11-30 00:00:00,10900.0 -2012-11-28 01:00:00,10731.0 -2012-11-28 02:00:00,10158.0 -2012-11-28 03:00:00,9844.0 -2012-11-28 04:00:00,9663.0 -2012-11-28 05:00:00,9676.0 -2012-11-28 06:00:00,9936.0 -2012-11-28 07:00:00,10686.0 -2012-11-28 08:00:00,11900.0 -2012-11-28 09:00:00,12311.0 -2012-11-28 10:00:00,12308.0 -2012-11-28 11:00:00,12258.0 -2012-11-28 12:00:00,12226.0 -2012-11-28 13:00:00,12094.0 -2012-11-28 14:00:00,11974.0 -2012-11-28 15:00:00,11886.0 -2012-11-28 16:00:00,11784.0 -2012-11-28 17:00:00,11812.0 -2012-11-28 18:00:00,12588.0 -2012-11-28 19:00:00,13338.0 -2012-11-28 20:00:00,13265.0 -2012-11-28 21:00:00,13119.0 -2012-11-28 22:00:00,12857.0 -2012-11-28 23:00:00,12336.0 -2012-11-29 00:00:00,11423.0 -2012-11-27 01:00:00,10734.0 -2012-11-27 02:00:00,10260.0 -2012-11-27 03:00:00,9995.0 -2012-11-27 04:00:00,9880.0 -2012-11-27 05:00:00,9927.0 -2012-11-27 06:00:00,10238.0 -2012-11-27 07:00:00,11012.0 -2012-11-27 08:00:00,12220.0 -2012-11-27 09:00:00,12594.0 -2012-11-27 10:00:00,12609.0 -2012-11-27 11:00:00,12560.0 -2012-11-27 12:00:00,12535.0 -2012-11-27 13:00:00,12484.0 -2012-11-27 14:00:00,12352.0 -2012-11-27 15:00:00,12293.0 -2012-11-27 16:00:00,12156.0 -2012-11-27 17:00:00,12257.0 -2012-11-27 18:00:00,13103.0 -2012-11-27 19:00:00,13782.0 -2012-11-27 20:00:00,13648.0 -2012-11-27 21:00:00,13447.0 -2012-11-27 22:00:00,13138.0 -2012-11-27 23:00:00,12549.0 -2012-11-28 00:00:00,11625.0 -2012-11-26 01:00:00,9626.0 -2012-11-26 02:00:00,9259.0 -2012-11-26 03:00:00,9086.0 -2012-11-26 04:00:00,9043.0 -2012-11-26 05:00:00,9144.0 -2012-11-26 06:00:00,9530.0 -2012-11-26 07:00:00,10367.0 -2012-11-26 08:00:00,11620.0 -2012-11-26 09:00:00,12122.0 -2012-11-26 10:00:00,12329.0 -2012-11-26 11:00:00,12380.0 -2012-11-26 12:00:00,12421.0 -2012-11-26 13:00:00,12314.0 -2012-11-26 14:00:00,12228.0 -2012-11-26 15:00:00,12305.0 -2012-11-26 16:00:00,12309.0 -2012-11-26 17:00:00,12402.0 -2012-11-26 18:00:00,13150.0 -2012-11-26 19:00:00,13635.0 -2012-11-26 20:00:00,13491.0 -2012-11-26 21:00:00,13298.0 -2012-11-26 22:00:00,13012.0 -2012-11-26 23:00:00,12454.0 -2012-11-27 00:00:00,11547.0 -2012-11-25 01:00:00,9950.0 -2012-11-25 02:00:00,9397.0 -2012-11-25 03:00:00,9075.0 -2012-11-25 04:00:00,8825.0 -2012-11-25 05:00:00,8774.0 -2012-11-25 06:00:00,8781.0 -2012-11-25 07:00:00,8986.0 -2012-11-25 08:00:00,9233.0 -2012-11-25 09:00:00,9277.0 -2012-11-25 10:00:00,9461.0 -2012-11-25 11:00:00,9641.0 -2012-11-25 12:00:00,9716.0 -2012-11-25 13:00:00,9756.0 -2012-11-25 14:00:00,9727.0 -2012-11-25 15:00:00,9695.0 -2012-11-25 16:00:00,9724.0 -2012-11-25 17:00:00,9921.0 -2012-11-25 18:00:00,10917.0 -2012-11-25 19:00:00,11658.0 -2012-11-25 20:00:00,11772.0 -2012-11-25 21:00:00,11671.0 -2012-11-25 22:00:00,11421.0 -2012-11-25 23:00:00,10971.0 -2012-11-26 00:00:00,10302.0 -2012-11-24 01:00:00,9728.0 -2012-11-24 02:00:00,9221.0 -2012-11-24 03:00:00,8957.0 -2012-11-24 04:00:00,8786.0 -2012-11-24 05:00:00,8752.0 -2012-11-24 06:00:00,8864.0 -2012-11-24 07:00:00,9163.0 -2012-11-24 08:00:00,9618.0 -2012-11-24 09:00:00,9804.0 -2012-11-24 10:00:00,10231.0 -2012-11-24 11:00:00,10593.0 -2012-11-24 12:00:00,10713.0 -2012-11-24 13:00:00,10637.0 -2012-11-24 14:00:00,10479.0 -2012-11-24 15:00:00,10253.0 -2012-11-24 16:00:00,10149.0 -2012-11-24 17:00:00,10255.0 -2012-11-24 18:00:00,11162.0 -2012-11-24 19:00:00,11786.0 -2012-11-24 20:00:00,11761.0 -2012-11-24 21:00:00,11688.0 -2012-11-24 22:00:00,11469.0 -2012-11-24 23:00:00,11131.0 -2012-11-25 00:00:00,10603.0 -2012-11-23 01:00:00,8316.0 -2012-11-23 02:00:00,8014.0 -2012-11-23 03:00:00,7841.0 -2012-11-23 04:00:00,7742.0 -2012-11-23 05:00:00,7759.0 -2012-11-23 06:00:00,8010.0 -2012-11-23 07:00:00,8477.0 -2012-11-23 08:00:00,9154.0 -2012-11-23 09:00:00,9494.0 -2012-11-23 10:00:00,9845.0 -2012-11-23 11:00:00,10172.0 -2012-11-23 12:00:00,10367.0 -2012-11-23 13:00:00,10474.0 -2012-11-23 14:00:00,10446.0 -2012-11-23 15:00:00,10376.0 -2012-11-23 16:00:00,10446.0 -2012-11-23 17:00:00,10632.0 -2012-11-23 18:00:00,11328.0 -2012-11-23 19:00:00,11850.0 -2012-11-23 20:00:00,11714.0 -2012-11-23 21:00:00,11527.0 -2012-11-23 22:00:00,11247.0 -2012-11-23 23:00:00,10914.0 -2012-11-24 00:00:00,10365.0 -2012-11-22 01:00:00,9212.0 -2012-11-22 02:00:00,8624.0 -2012-11-22 03:00:00,8217.0 -2012-11-22 04:00:00,7970.0 -2012-11-22 05:00:00,7836.0 -2012-11-22 06:00:00,7869.0 -2012-11-22 07:00:00,8041.0 -2012-11-22 08:00:00,8249.0 -2012-11-22 09:00:00,8177.0 -2012-11-22 10:00:00,8389.0 -2012-11-22 11:00:00,8621.0 -2012-11-22 12:00:00,8792.0 -2012-11-22 13:00:00,8870.0 -2012-11-22 14:00:00,8817.0 -2012-11-22 15:00:00,8708.0 -2012-11-22 16:00:00,8593.0 -2012-11-22 17:00:00,8612.0 -2012-11-22 18:00:00,9226.0 -2012-11-22 19:00:00,9362.0 -2012-11-22 20:00:00,9271.0 -2012-11-22 21:00:00,9170.0 -2012-11-22 22:00:00,9081.0 -2012-11-22 23:00:00,8951.0 -2012-11-23 00:00:00,8666.0 -2012-11-21 01:00:00,9772.0 -2012-11-21 02:00:00,9238.0 -2012-11-21 03:00:00,8900.0 -2012-11-21 04:00:00,8717.0 -2012-11-21 05:00:00,8684.0 -2012-11-21 06:00:00,8938.0 -2012-11-21 07:00:00,9631.0 -2012-11-21 08:00:00,10635.0 -2012-11-21 09:00:00,11174.0 -2012-11-21 10:00:00,11515.0 -2012-11-21 11:00:00,11657.0 -2012-11-21 12:00:00,11697.0 -2012-11-21 13:00:00,11611.0 -2012-11-21 14:00:00,11532.0 -2012-11-21 15:00:00,11417.0 -2012-11-21 16:00:00,11243.0 -2012-11-21 17:00:00,11097.0 -2012-11-21 18:00:00,11562.0 -2012-11-21 19:00:00,12151.0 -2012-11-21 20:00:00,11961.0 -2012-11-21 21:00:00,11654.0 -2012-11-21 22:00:00,11377.0 -2012-11-21 23:00:00,10924.0 -2012-11-22 00:00:00,10071.0 -2012-11-20 01:00:00,9548.0 -2012-11-20 02:00:00,9035.0 -2012-11-20 03:00:00,8741.0 -2012-11-20 04:00:00,8496.0 -2012-11-20 05:00:00,8474.0 -2012-11-20 06:00:00,8737.0 -2012-11-20 07:00:00,9464.0 -2012-11-20 08:00:00,10577.0 -2012-11-20 09:00:00,11221.0 -2012-11-20 10:00:00,11456.0 -2012-11-20 11:00:00,11589.0 -2012-11-20 12:00:00,11637.0 -2012-11-20 13:00:00,11628.0 -2012-11-20 14:00:00,11599.0 -2012-11-20 15:00:00,11546.0 -2012-11-20 16:00:00,11438.0 -2012-11-20 17:00:00,11421.0 -2012-11-20 18:00:00,11911.0 -2012-11-20 19:00:00,12412.0 -2012-11-20 20:00:00,12239.0 -2012-11-20 21:00:00,12050.0 -2012-11-20 22:00:00,11791.0 -2012-11-20 23:00:00,11367.0 -2012-11-21 00:00:00,10596.0 -2012-11-19 01:00:00,9179.0 -2012-11-19 02:00:00,8771.0 -2012-11-19 03:00:00,8586.0 -2012-11-19 04:00:00,8498.0 -2012-11-19 05:00:00,8526.0 -2012-11-19 06:00:00,8808.0 -2012-11-19 07:00:00,9603.0 -2012-11-19 08:00:00,10772.0 -2012-11-19 09:00:00,11320.0 -2012-11-19 10:00:00,11591.0 -2012-11-19 11:00:00,11595.0 -2012-11-19 12:00:00,11582.0 -2012-11-19 13:00:00,11489.0 -2012-11-19 14:00:00,11404.0 -2012-11-19 15:00:00,11397.0 -2012-11-19 16:00:00,11323.0 -2012-11-19 17:00:00,11433.0 -2012-11-19 18:00:00,12143.0 -2012-11-19 19:00:00,12465.0 -2012-11-19 20:00:00,12211.0 -2012-11-19 21:00:00,12004.0 -2012-11-19 22:00:00,11710.0 -2012-11-19 23:00:00,11194.0 -2012-11-20 00:00:00,10372.0 -2012-11-18 01:00:00,9388.0 -2012-11-18 02:00:00,9023.0 -2012-11-18 03:00:00,8711.0 -2012-11-18 04:00:00,8524.0 -2012-11-18 05:00:00,8499.0 -2012-11-18 06:00:00,8560.0 -2012-11-18 07:00:00,8700.0 -2012-11-18 08:00:00,8897.0 -2012-11-18 09:00:00,8968.0 -2012-11-18 10:00:00,9135.0 -2012-11-18 11:00:00,9291.0 -2012-11-18 12:00:00,9324.0 -2012-11-18 13:00:00,9388.0 -2012-11-18 14:00:00,9351.0 -2012-11-18 15:00:00,9360.0 -2012-11-18 16:00:00,9300.0 -2012-11-18 17:00:00,9448.0 -2012-11-18 18:00:00,10151.0 -2012-11-18 19:00:00,10921.0 -2012-11-18 20:00:00,10910.0 -2012-11-18 21:00:00,10838.0 -2012-11-18 22:00:00,10587.0 -2012-11-18 23:00:00,10265.0 -2012-11-19 00:00:00,9701.0 -2012-11-17 01:00:00,9894.0 -2012-11-17 02:00:00,9415.0 -2012-11-17 03:00:00,9122.0 -2012-11-17 04:00:00,8931.0 -2012-11-17 05:00:00,8849.0 -2012-11-17 06:00:00,8954.0 -2012-11-17 07:00:00,9286.0 -2012-11-17 08:00:00,9723.0 -2012-11-17 09:00:00,9931.0 -2012-11-17 10:00:00,10177.0 -2012-11-17 11:00:00,10296.0 -2012-11-17 12:00:00,10307.0 -2012-11-17 13:00:00,10192.0 -2012-11-17 14:00:00,10074.0 -2012-11-17 15:00:00,9824.0 -2012-11-17 16:00:00,9741.0 -2012-11-17 17:00:00,9770.0 -2012-11-17 18:00:00,10395.0 -2012-11-17 19:00:00,11007.0 -2012-11-17 20:00:00,10973.0 -2012-11-17 21:00:00,10807.0 -2012-11-17 22:00:00,10673.0 -2012-11-17 23:00:00,10366.0 -2012-11-18 00:00:00,9939.0 -2012-11-16 01:00:00,10098.0 -2012-11-16 02:00:00,9600.0 -2012-11-16 03:00:00,9333.0 -2012-11-16 04:00:00,9152.0 -2012-11-16 05:00:00,9149.0 -2012-11-16 06:00:00,9429.0 -2012-11-16 07:00:00,10135.0 -2012-11-16 08:00:00,11223.0 -2012-11-16 09:00:00,11605.0 -2012-11-16 10:00:00,11638.0 -2012-11-16 11:00:00,11601.0 -2012-11-16 12:00:00,11584.0 -2012-11-16 13:00:00,11464.0 -2012-11-16 14:00:00,11371.0 -2012-11-16 15:00:00,11332.0 -2012-11-16 16:00:00,11214.0 -2012-11-16 17:00:00,11157.0 -2012-11-16 18:00:00,11675.0 -2012-11-16 19:00:00,12276.0 -2012-11-16 20:00:00,12144.0 -2012-11-16 21:00:00,11912.0 -2012-11-16 22:00:00,11613.0 -2012-11-16 23:00:00,11257.0 -2012-11-17 00:00:00,10543.0 -2012-11-15 01:00:00,10218.0 -2012-11-15 02:00:00,9736.0 -2012-11-15 03:00:00,9474.0 -2012-11-15 04:00:00,9299.0 -2012-11-15 05:00:00,9325.0 -2012-11-15 06:00:00,9562.0 -2012-11-15 07:00:00,10328.0 -2012-11-15 08:00:00,11406.0 -2012-11-15 09:00:00,11883.0 -2012-11-15 10:00:00,12028.0 -2012-11-15 11:00:00,12059.0 -2012-11-15 12:00:00,11994.0 -2012-11-15 13:00:00,11857.0 -2012-11-15 14:00:00,11697.0 -2012-11-15 15:00:00,11646.0 -2012-11-15 16:00:00,11517.0 -2012-11-15 17:00:00,11467.0 -2012-11-15 18:00:00,12015.0 -2012-11-15 19:00:00,12711.0 -2012-11-15 20:00:00,12581.0 -2012-11-15 21:00:00,12371.0 -2012-11-15 22:00:00,12101.0 -2012-11-15 23:00:00,11608.0 -2012-11-16 00:00:00,10803.0 -2012-11-14 01:00:00,10308.0 -2012-11-14 02:00:00,9854.0 -2012-11-14 03:00:00,9589.0 -2012-11-14 04:00:00,9431.0 -2012-11-14 05:00:00,9396.0 -2012-11-14 06:00:00,9654.0 -2012-11-14 07:00:00,10369.0 -2012-11-14 08:00:00,11468.0 -2012-11-14 09:00:00,11932.0 -2012-11-14 10:00:00,12005.0 -2012-11-14 11:00:00,12010.0 -2012-11-14 12:00:00,12027.0 -2012-11-14 13:00:00,11924.0 -2012-11-14 14:00:00,11776.0 -2012-11-14 15:00:00,11712.0 -2012-11-14 16:00:00,11626.0 -2012-11-14 17:00:00,11555.0 -2012-11-14 18:00:00,12108.0 -2012-11-14 19:00:00,12819.0 -2012-11-14 20:00:00,12721.0 -2012-11-14 21:00:00,12540.0 -2012-11-14 22:00:00,12274.0 -2012-11-14 23:00:00,11739.0 -2012-11-15 00:00:00,10955.0 -2012-11-13 01:00:00,10288.0 -2012-11-13 02:00:00,9824.0 -2012-11-13 03:00:00,9565.0 -2012-11-13 04:00:00,9455.0 -2012-11-13 05:00:00,9466.0 -2012-11-13 06:00:00,9749.0 -2012-11-13 07:00:00,10521.0 -2012-11-13 08:00:00,11625.0 -2012-11-13 09:00:00,12031.0 -2012-11-13 10:00:00,12130.0 -2012-11-13 11:00:00,12129.0 -2012-11-13 12:00:00,12115.0 -2012-11-13 13:00:00,12028.0 -2012-11-13 14:00:00,11914.0 -2012-11-13 15:00:00,11888.0 -2012-11-13 16:00:00,11696.0 -2012-11-13 17:00:00,11709.0 -2012-11-13 18:00:00,12245.0 -2012-11-13 19:00:00,12969.0 -2012-11-13 20:00:00,12880.0 -2012-11-13 21:00:00,12732.0 -2012-11-13 22:00:00,12415.0 -2012-11-13 23:00:00,11889.0 -2012-11-14 00:00:00,11064.0 -2012-11-12 01:00:00,8874.0 -2012-11-12 02:00:00,8564.0 -2012-11-12 03:00:00,8381.0 -2012-11-12 04:00:00,8358.0 -2012-11-12 05:00:00,8428.0 -2012-11-12 06:00:00,8788.0 -2012-11-12 07:00:00,9582.0 -2012-11-12 08:00:00,10806.0 -2012-11-12 09:00:00,11338.0 -2012-11-12 10:00:00,11560.0 -2012-11-12 11:00:00,11763.0 -2012-11-12 12:00:00,11884.0 -2012-11-12 13:00:00,11903.0 -2012-11-12 14:00:00,11863.0 -2012-11-12 15:00:00,11896.0 -2012-11-12 16:00:00,11942.0 -2012-11-12 17:00:00,12107.0 -2012-11-12 18:00:00,12731.0 -2012-11-12 19:00:00,13208.0 -2012-11-12 20:00:00,13005.0 -2012-11-12 21:00:00,12746.0 -2012-11-12 22:00:00,12402.0 -2012-11-12 23:00:00,11839.0 -2012-11-13 00:00:00,11014.0 -2012-11-11 01:00:00,8980.0 -2012-11-11 02:00:00,8486.0 -2012-11-11 03:00:00,8165.0 -2012-11-11 04:00:00,7922.0 -2012-11-11 05:00:00,7851.0 -2012-11-11 06:00:00,7787.0 -2012-11-11 07:00:00,7969.0 -2012-11-11 08:00:00,8121.0 -2012-11-11 09:00:00,8274.0 -2012-11-11 10:00:00,8605.0 -2012-11-11 11:00:00,8905.0 -2012-11-11 12:00:00,9126.0 -2012-11-11 13:00:00,9222.0 -2012-11-11 14:00:00,9278.0 -2012-11-11 15:00:00,9253.0 -2012-11-11 16:00:00,9325.0 -2012-11-11 17:00:00,9619.0 -2012-11-11 18:00:00,10343.0 -2012-11-11 19:00:00,10695.0 -2012-11-11 20:00:00,10640.0 -2012-11-11 21:00:00,10449.0 -2012-11-11 22:00:00,10199.0 -2012-11-11 23:00:00,9909.0 -2012-11-12 00:00:00,9432.0 -2012-11-10 01:00:00,9446.0 -2012-11-10 02:00:00,8948.0 -2012-11-10 03:00:00,8585.0 -2012-11-10 04:00:00,8381.0 -2012-11-10 05:00:00,8260.0 -2012-11-10 06:00:00,8362.0 -2012-11-10 07:00:00,8610.0 -2012-11-10 08:00:00,8957.0 -2012-11-10 09:00:00,9242.0 -2012-11-10 10:00:00,9660.0 -2012-11-10 11:00:00,9914.0 -2012-11-10 12:00:00,10031.0 -2012-11-10 13:00:00,10143.0 -2012-11-10 14:00:00,10158.0 -2012-11-10 15:00:00,10077.0 -2012-11-10 16:00:00,9872.0 -2012-11-10 17:00:00,9784.0 -2012-11-10 18:00:00,10265.0 -2012-11-10 19:00:00,10863.0 -2012-11-10 20:00:00,10790.0 -2012-11-10 21:00:00,10536.0 -2012-11-10 22:00:00,10400.0 -2012-11-10 23:00:00,10041.0 -2012-11-11 00:00:00,9521.0 -2012-11-09 01:00:00,9911.0 -2012-11-09 02:00:00,9436.0 -2012-11-09 03:00:00,9140.0 -2012-11-09 04:00:00,8970.0 -2012-11-09 05:00:00,8974.0 -2012-11-09 06:00:00,9197.0 -2012-11-09 07:00:00,9900.0 -2012-11-09 08:00:00,10930.0 -2012-11-09 09:00:00,11320.0 -2012-11-09 10:00:00,11481.0 -2012-11-09 11:00:00,11549.0 -2012-11-09 12:00:00,11514.0 -2012-11-09 13:00:00,11415.0 -2012-11-09 14:00:00,11359.0 -2012-11-09 15:00:00,11350.0 -2012-11-09 16:00:00,11264.0 -2012-11-09 17:00:00,11211.0 -2012-11-09 18:00:00,11608.0 -2012-11-09 19:00:00,12110.0 -2012-11-09 20:00:00,11899.0 -2012-11-09 21:00:00,11610.0 -2012-11-09 22:00:00,11299.0 -2012-11-09 23:00:00,10890.0 -2012-11-10 00:00:00,10196.0 -2012-11-08 01:00:00,9749.0 -2012-11-08 02:00:00,9277.0 -2012-11-08 03:00:00,8978.0 -2012-11-08 04:00:00,8783.0 -2012-11-08 05:00:00,8763.0 -2012-11-08 06:00:00,9007.0 -2012-11-08 07:00:00,9716.0 -2012-11-08 08:00:00,10868.0 -2012-11-08 09:00:00,11467.0 -2012-11-08 10:00:00,11677.0 -2012-11-08 11:00:00,11770.0 -2012-11-08 12:00:00,11909.0 -2012-11-08 13:00:00,11867.0 -2012-11-08 14:00:00,11814.0 -2012-11-08 15:00:00,11755.0 -2012-11-08 16:00:00,11673.0 -2012-11-08 17:00:00,11581.0 -2012-11-08 18:00:00,11909.0 -2012-11-08 19:00:00,12615.0 -2012-11-08 20:00:00,12511.0 -2012-11-08 21:00:00,12315.0 -2012-11-08 22:00:00,12006.0 -2012-11-08 23:00:00,11503.0 -2012-11-09 00:00:00,10662.0 -2012-11-07 01:00:00,10021.0 -2012-11-07 02:00:00,9546.0 -2012-11-07 03:00:00,9204.0 -2012-11-07 04:00:00,8984.0 -2012-11-07 05:00:00,8949.0 -2012-11-07 06:00:00,9194.0 -2012-11-07 07:00:00,9886.0 -2012-11-07 08:00:00,11064.0 -2012-11-07 09:00:00,11627.0 -2012-11-07 10:00:00,11851.0 -2012-11-07 11:00:00,11909.0 -2012-11-07 12:00:00,11892.0 -2012-11-07 13:00:00,11890.0 -2012-11-07 14:00:00,11814.0 -2012-11-07 15:00:00,11815.0 -2012-11-07 16:00:00,11749.0 -2012-11-07 17:00:00,11808.0 -2012-11-07 18:00:00,12282.0 -2012-11-07 19:00:00,12688.0 -2012-11-07 20:00:00,12471.0 -2012-11-07 21:00:00,12231.0 -2012-11-07 22:00:00,11888.0 -2012-11-07 23:00:00,11341.0 -2012-11-08 00:00:00,10530.0 -2012-11-06 01:00:00,9941.0 -2012-11-06 02:00:00,9487.0 -2012-11-06 03:00:00,9247.0 -2012-11-06 04:00:00,9117.0 -2012-11-06 05:00:00,9111.0 -2012-11-06 06:00:00,9422.0 -2012-11-06 07:00:00,10227.0 -2012-11-06 08:00:00,11213.0 -2012-11-06 09:00:00,11590.0 -2012-11-06 10:00:00,11776.0 -2012-11-06 11:00:00,11912.0 -2012-11-06 12:00:00,12104.0 -2012-11-06 13:00:00,12150.0 -2012-11-06 14:00:00,12109.0 -2012-11-06 15:00:00,12148.0 -2012-11-06 16:00:00,12077.0 -2012-11-06 17:00:00,12044.0 -2012-11-06 18:00:00,12537.0 -2012-11-06 19:00:00,12946.0 -2012-11-06 20:00:00,12730.0 -2012-11-06 21:00:00,12520.0 -2012-11-06 22:00:00,12171.0 -2012-11-06 23:00:00,11558.0 -2012-11-07 00:00:00,10753.0 -2012-11-05 01:00:00,9100.0 -2012-11-05 02:00:00,8797.0 -2012-11-05 03:00:00,8665.0 -2012-11-05 04:00:00,8600.0 -2012-11-05 05:00:00,8678.0 -2012-11-05 06:00:00,9003.0 -2012-11-05 07:00:00,9840.0 -2012-11-05 08:00:00,11004.0 -2012-11-05 09:00:00,11603.0 -2012-11-05 10:00:00,11777.0 -2012-11-05 11:00:00,11875.0 -2012-11-05 12:00:00,11907.0 -2012-11-05 13:00:00,11894.0 -2012-11-05 14:00:00,11848.0 -2012-11-05 15:00:00,11851.0 -2012-11-05 16:00:00,11776.0 -2012-11-05 17:00:00,11777.0 -2012-11-05 18:00:00,12182.0 -2012-11-05 19:00:00,12771.0 -2012-11-05 20:00:00,12599.0 -2012-11-05 21:00:00,12364.0 -2012-11-05 22:00:00,12052.0 -2012-11-05 23:00:00,11489.0 -2012-11-06 00:00:00,10660.0 -2012-11-04 01:00:00,9382.0 -2012-11-04 03:00:00,8546.0 -2012-11-04 04:00:00,8495.0 -2012-11-04 05:00:00,8424.0 -2012-11-04 06:00:00,8412.0 -2012-11-04 07:00:00,8621.0 -2012-11-04 08:00:00,8817.0 -2012-11-04 09:00:00,9009.0 -2012-11-04 10:00:00,9186.0 -2012-11-04 11:00:00,9106.0 -2012-11-04 12:00:00,9324.0 -2012-11-04 13:00:00,9331.0 -2012-11-04 14:00:00,9272.0 -2012-11-04 15:00:00,9223.0 -2012-11-04 16:00:00,9211.0 -2012-11-04 17:00:00,9260.0 -2012-11-04 18:00:00,9864.0 -2012-11-04 19:00:00,10779.0 -2012-11-04 20:00:00,10868.0 -2012-11-04 21:00:00,10808.0 -2012-11-04 22:00:00,10566.0 -2012-11-04 23:00:00,10240.0 -2012-11-05 00:00:00,9541.0 -2012-11-03 01:00:00,9726.0 -2012-11-03 02:00:00,9140.0 -2012-11-03 03:00:00,8827.0 -2012-11-03 04:00:00,8623.0 -2012-11-03 05:00:00,8537.0 -2012-11-03 06:00:00,8577.0 -2012-11-03 07:00:00,8830.0 -2012-11-03 08:00:00,9388.0 -2012-11-03 09:00:00,9839.0 -2012-11-03 10:00:00,10104.0 -2012-11-03 11:00:00,10347.0 -2012-11-03 12:00:00,10464.0 -2012-11-03 13:00:00,10415.0 -2012-11-03 14:00:00,10290.0 -2012-11-03 15:00:00,10078.0 -2012-11-03 16:00:00,9995.0 -2012-11-03 17:00:00,9915.0 -2012-11-03 18:00:00,10072.0 -2012-11-03 19:00:00,10411.0 -2012-11-03 20:00:00,10881.0 -2012-11-03 21:00:00,10775.0 -2012-11-03 22:00:00,10629.0 -2012-11-03 23:00:00,10337.0 -2012-11-04 00:00:00,9948.0 -2012-11-02 01:00:00,9729.0 -2012-11-02 02:00:00,9211.0 -2012-11-02 03:00:00,8924.0 -2012-11-02 04:00:00,8767.0 -2012-11-02 05:00:00,8779.0 -2012-11-02 06:00:00,9010.0 -2012-11-02 07:00:00,9729.0 -2012-11-02 08:00:00,10978.0 -2012-11-02 09:00:00,11704.0 -2012-11-02 10:00:00,11676.0 -2012-11-02 11:00:00,11664.0 -2012-11-02 12:00:00,11612.0 -2012-11-02 13:00:00,11493.0 -2012-11-02 14:00:00,11354.0 -2012-11-02 15:00:00,11282.0 -2012-11-02 16:00:00,11120.0 -2012-11-02 17:00:00,10948.0 -2012-11-02 18:00:00,10933.0 -2012-11-02 19:00:00,11280.0 -2012-11-02 20:00:00,11873.0 -2012-11-02 21:00:00,11744.0 -2012-11-02 22:00:00,11491.0 -2012-11-02 23:00:00,11093.0 -2012-11-03 00:00:00,10405.0 -2012-11-01 01:00:00,9753.0 -2012-11-01 02:00:00,9314.0 -2012-11-01 03:00:00,9054.0 -2012-11-01 04:00:00,8913.0 -2012-11-01 05:00:00,8912.0 -2012-11-01 06:00:00,9131.0 -2012-11-01 07:00:00,9891.0 -2012-11-01 08:00:00,11179.0 -2012-11-01 09:00:00,11863.0 -2012-11-01 10:00:00,11841.0 -2012-11-01 11:00:00,11785.0 -2012-11-01 12:00:00,11777.0 -2012-11-01 13:00:00,11687.0 -2012-11-01 14:00:00,11570.0 -2012-11-01 15:00:00,11497.0 -2012-11-01 16:00:00,11400.0 -2012-11-01 17:00:00,11262.0 -2012-11-01 18:00:00,11209.0 -2012-11-01 19:00:00,11494.0 -2012-11-01 20:00:00,12105.0 -2012-11-01 21:00:00,12023.0 -2012-11-01 22:00:00,11772.0 -2012-11-01 23:00:00,11288.0 -2012-11-02 00:00:00,10500.0 -2012-10-31 01:00:00,9878.0 -2012-10-31 02:00:00,9347.0 -2012-10-31 03:00:00,9032.0 -2012-10-31 04:00:00,8878.0 -2012-10-31 05:00:00,8856.0 -2012-10-31 06:00:00,9095.0 -2012-10-31 07:00:00,9852.0 -2012-10-31 08:00:00,11163.0 -2012-10-31 09:00:00,11931.0 -2012-10-31 10:00:00,11956.0 -2012-10-31 11:00:00,11951.0 -2012-10-31 12:00:00,11885.0 -2012-10-31 13:00:00,11764.0 -2012-10-31 14:00:00,11659.0 -2012-10-31 15:00:00,11620.0 -2012-10-31 16:00:00,11397.0 -2012-10-31 17:00:00,11302.0 -2012-10-31 18:00:00,11216.0 -2012-10-31 19:00:00,11463.0 -2012-10-31 20:00:00,11962.0 -2012-10-31 21:00:00,11878.0 -2012-10-31 22:00:00,11655.0 -2012-10-31 23:00:00,11228.0 -2012-11-01 00:00:00,10466.0 -2012-10-30 01:00:00,9739.0 -2012-10-30 02:00:00,9232.0 -2012-10-30 03:00:00,8913.0 -2012-10-30 04:00:00,8786.0 -2012-10-30 05:00:00,8756.0 -2012-10-30 06:00:00,9012.0 -2012-10-30 07:00:00,9745.0 -2012-10-30 08:00:00,11067.0 -2012-10-30 09:00:00,11890.0 -2012-10-30 10:00:00,11909.0 -2012-10-30 11:00:00,11998.0 -2012-10-30 12:00:00,12055.0 -2012-10-30 13:00:00,11915.0 -2012-10-30 14:00:00,11768.0 -2012-10-30 15:00:00,11724.0 -2012-10-30 16:00:00,11672.0 -2012-10-30 17:00:00,11546.0 -2012-10-30 18:00:00,11581.0 -2012-10-30 19:00:00,11873.0 -2012-10-30 20:00:00,12359.0 -2012-10-30 21:00:00,12275.0 -2012-10-30 22:00:00,11994.0 -2012-10-30 23:00:00,11507.0 -2012-10-31 00:00:00,10672.0 -2012-10-29 01:00:00,9084.0 -2012-10-29 02:00:00,8749.0 -2012-10-29 03:00:00,8525.0 -2012-10-29 04:00:00,8458.0 -2012-10-29 05:00:00,8412.0 -2012-10-29 06:00:00,8738.0 -2012-10-29 07:00:00,9544.0 -2012-10-29 08:00:00,10865.0 -2012-10-29 09:00:00,11656.0 -2012-10-29 10:00:00,11595.0 -2012-10-29 11:00:00,11566.0 -2012-10-29 12:00:00,11598.0 -2012-10-29 13:00:00,11545.0 -2012-10-29 14:00:00,11471.0 -2012-10-29 15:00:00,11427.0 -2012-10-29 16:00:00,11279.0 -2012-10-29 17:00:00,11170.0 -2012-10-29 18:00:00,11149.0 -2012-10-29 19:00:00,11396.0 -2012-10-29 20:00:00,12132.0 -2012-10-29 21:00:00,12076.0 -2012-10-29 22:00:00,11807.0 -2012-10-29 23:00:00,11315.0 -2012-10-30 00:00:00,10506.0 -2012-10-28 01:00:00,9182.0 -2012-10-28 02:00:00,8771.0 -2012-10-28 03:00:00,8454.0 -2012-10-28 04:00:00,8325.0 -2012-10-28 05:00:00,8218.0 -2012-10-28 06:00:00,8207.0 -2012-10-28 07:00:00,8329.0 -2012-10-28 08:00:00,8590.0 -2012-10-28 09:00:00,8826.0 -2012-10-28 10:00:00,8986.0 -2012-10-28 11:00:00,9201.0 -2012-10-28 12:00:00,9299.0 -2012-10-28 13:00:00,9366.0 -2012-10-28 14:00:00,9345.0 -2012-10-28 15:00:00,9319.0 -2012-10-28 16:00:00,9239.0 -2012-10-28 17:00:00,9220.0 -2012-10-28 18:00:00,9301.0 -2012-10-28 19:00:00,9715.0 -2012-10-28 20:00:00,10519.0 -2012-10-28 21:00:00,10636.0 -2012-10-28 22:00:00,10526.0 -2012-10-28 23:00:00,10193.0 -2012-10-29 00:00:00,9676.0 -2012-10-27 01:00:00,9576.0 -2012-10-27 02:00:00,9056.0 -2012-10-27 03:00:00,8752.0 -2012-10-27 04:00:00,8554.0 -2012-10-27 05:00:00,8471.0 -2012-10-27 06:00:00,8463.0 -2012-10-27 07:00:00,8796.0 -2012-10-27 08:00:00,9280.0 -2012-10-27 09:00:00,9666.0 -2012-10-27 10:00:00,9846.0 -2012-10-27 11:00:00,10028.0 -2012-10-27 12:00:00,10068.0 -2012-10-27 13:00:00,9997.0 -2012-10-27 14:00:00,9829.0 -2012-10-27 15:00:00,9664.0 -2012-10-27 16:00:00,9479.0 -2012-10-27 17:00:00,9413.0 -2012-10-27 18:00:00,9469.0 -2012-10-27 19:00:00,9814.0 -2012-10-27 20:00:00,10599.0 -2012-10-27 21:00:00,10625.0 -2012-10-27 22:00:00,10484.0 -2012-10-27 23:00:00,10196.0 -2012-10-28 00:00:00,9705.0 -2012-10-26 01:00:00,9283.0 -2012-10-26 02:00:00,8760.0 -2012-10-26 03:00:00,8406.0 -2012-10-26 04:00:00,8247.0 -2012-10-26 05:00:00,8183.0 -2012-10-26 06:00:00,8423.0 -2012-10-26 07:00:00,9150.0 -2012-10-26 08:00:00,10386.0 -2012-10-26 09:00:00,11115.0 -2012-10-26 10:00:00,11251.0 -2012-10-26 11:00:00,11374.0 -2012-10-26 12:00:00,11426.0 -2012-10-26 13:00:00,11373.0 -2012-10-26 14:00:00,11272.0 -2012-10-26 15:00:00,11209.0 -2012-10-26 16:00:00,11034.0 -2012-10-26 17:00:00,10867.0 -2012-10-26 18:00:00,10778.0 -2012-10-26 19:00:00,11022.0 -2012-10-26 20:00:00,11573.0 -2012-10-26 21:00:00,11499.0 -2012-10-26 22:00:00,11252.0 -2012-10-26 23:00:00,10894.0 -2012-10-27 00:00:00,10224.0 -2012-10-25 01:00:00,9989.0 -2012-10-25 02:00:00,9437.0 -2012-10-25 03:00:00,8881.0 -2012-10-25 04:00:00,8645.0 -2012-10-25 05:00:00,8515.0 -2012-10-25 06:00:00,8667.0 -2012-10-25 07:00:00,9334.0 -2012-10-25 08:00:00,10624.0 -2012-10-25 09:00:00,11290.0 -2012-10-25 10:00:00,11567.0 -2012-10-25 11:00:00,11860.0 -2012-10-25 12:00:00,12190.0 -2012-10-25 13:00:00,12315.0 -2012-10-25 14:00:00,12425.0 -2012-10-25 15:00:00,12527.0 -2012-10-25 16:00:00,12468.0 -2012-10-25 17:00:00,12397.0 -2012-10-25 18:00:00,12372.0 -2012-10-25 19:00:00,12365.0 -2012-10-25 20:00:00,12328.0 -2012-10-25 21:00:00,11938.0 -2012-10-25 22:00:00,11503.0 -2012-10-25 23:00:00,10935.0 -2012-10-26 00:00:00,10059.0 -2012-10-24 01:00:00,9549.0 -2012-10-24 02:00:00,8999.0 -2012-10-24 03:00:00,8582.0 -2012-10-24 04:00:00,8367.0 -2012-10-24 05:00:00,8286.0 -2012-10-24 06:00:00,8487.0 -2012-10-24 07:00:00,9120.0 -2012-10-24 08:00:00,10382.0 -2012-10-24 09:00:00,11094.0 -2012-10-24 10:00:00,11311.0 -2012-10-24 11:00:00,11562.0 -2012-10-24 12:00:00,11862.0 -2012-10-24 13:00:00,12032.0 -2012-10-24 14:00:00,12181.0 -2012-10-24 15:00:00,12405.0 -2012-10-24 16:00:00,12421.0 -2012-10-24 17:00:00,12409.0 -2012-10-24 18:00:00,12350.0 -2012-10-24 19:00:00,12377.0 -2012-10-24 20:00:00,12831.0 -2012-10-24 21:00:00,12778.0 -2012-10-24 22:00:00,12412.0 -2012-10-24 23:00:00,11794.0 -2012-10-25 00:00:00,10849.0 -2012-10-23 01:00:00,9507.0 -2012-10-23 02:00:00,8902.0 -2012-10-23 03:00:00,8502.0 -2012-10-23 04:00:00,8287.0 -2012-10-23 05:00:00,8224.0 -2012-10-23 06:00:00,8355.0 -2012-10-23 07:00:00,9028.0 -2012-10-23 08:00:00,10314.0 -2012-10-23 09:00:00,11175.0 -2012-10-23 10:00:00,11474.0 -2012-10-23 11:00:00,11608.0 -2012-10-23 12:00:00,11771.0 -2012-10-23 13:00:00,11809.0 -2012-10-23 14:00:00,11880.0 -2012-10-23 15:00:00,11961.0 -2012-10-23 16:00:00,11894.0 -2012-10-23 17:00:00,11796.0 -2012-10-23 18:00:00,11696.0 -2012-10-23 19:00:00,11643.0 -2012-10-23 20:00:00,12149.0 -2012-10-23 21:00:00,12083.0 -2012-10-23 22:00:00,11765.0 -2012-10-23 23:00:00,11181.0 -2012-10-24 00:00:00,10384.0 -2012-10-22 01:00:00,8576.0 -2012-10-22 02:00:00,8167.0 -2012-10-22 03:00:00,7931.0 -2012-10-22 04:00:00,7786.0 -2012-10-22 05:00:00,7800.0 -2012-10-22 06:00:00,8089.0 -2012-10-22 07:00:00,8797.0 -2012-10-22 08:00:00,10082.0 -2012-10-22 09:00:00,10834.0 -2012-10-22 10:00:00,11143.0 -2012-10-22 11:00:00,11345.0 -2012-10-22 12:00:00,11635.0 -2012-10-22 13:00:00,11845.0 -2012-10-22 14:00:00,11843.0 -2012-10-22 15:00:00,11784.0 -2012-10-22 16:00:00,11643.0 -2012-10-22 17:00:00,11569.0 -2012-10-22 18:00:00,11611.0 -2012-10-22 19:00:00,11842.0 -2012-10-22 20:00:00,12164.0 -2012-10-22 21:00:00,12048.0 -2012-10-22 22:00:00,11682.0 -2012-10-22 23:00:00,11155.0 -2012-10-23 00:00:00,10368.0 -2012-10-21 01:00:00,8914.0 -2012-10-21 02:00:00,8458.0 -2012-10-21 03:00:00,8208.0 -2012-10-21 04:00:00,7976.0 -2012-10-21 05:00:00,7909.0 -2012-10-21 06:00:00,7919.0 -2012-10-21 07:00:00,8102.0 -2012-10-21 08:00:00,8343.0 -2012-10-21 09:00:00,8420.0 -2012-10-21 10:00:00,8607.0 -2012-10-21 11:00:00,8799.0 -2012-10-21 12:00:00,8896.0 -2012-10-21 13:00:00,8988.0 -2012-10-21 14:00:00,8994.0 -2012-10-21 15:00:00,9019.0 -2012-10-21 16:00:00,8990.0 -2012-10-21 17:00:00,8998.0 -2012-10-21 18:00:00,9059.0 -2012-10-21 19:00:00,9280.0 -2012-10-21 20:00:00,10091.0 -2012-10-21 21:00:00,10311.0 -2012-10-21 22:00:00,10108.0 -2012-10-21 23:00:00,9746.0 -2012-10-22 00:00:00,9149.0 -2012-10-20 01:00:00,9426.0 -2012-10-20 02:00:00,8895.0 -2012-10-20 03:00:00,8599.0 -2012-10-20 04:00:00,8384.0 -2012-10-20 05:00:00,8285.0 -2012-10-20 06:00:00,8286.0 -2012-10-20 07:00:00,8628.0 -2012-10-20 08:00:00,9140.0 -2012-10-20 09:00:00,9415.0 -2012-10-20 10:00:00,9694.0 -2012-10-20 11:00:00,9821.0 -2012-10-20 12:00:00,9879.0 -2012-10-20 13:00:00,9806.0 -2012-10-20 14:00:00,9713.0 -2012-10-20 15:00:00,9519.0 -2012-10-20 16:00:00,9401.0 -2012-10-20 17:00:00,9283.0 -2012-10-20 18:00:00,9274.0 -2012-10-20 19:00:00,9400.0 -2012-10-20 20:00:00,10107.0 -2012-10-20 21:00:00,10266.0 -2012-10-20 22:00:00,10122.0 -2012-10-20 23:00:00,9885.0 -2012-10-21 00:00:00,9380.0 -2012-10-19 01:00:00,9530.0 -2012-10-19 02:00:00,8985.0 -2012-10-19 03:00:00,8646.0 -2012-10-19 04:00:00,8465.0 -2012-10-19 05:00:00,8403.0 -2012-10-19 06:00:00,8592.0 -2012-10-19 07:00:00,9252.0 -2012-10-19 08:00:00,10413.0 -2012-10-19 09:00:00,11165.0 -2012-10-19 10:00:00,11316.0 -2012-10-19 11:00:00,11384.0 -2012-10-19 12:00:00,11444.0 -2012-10-19 13:00:00,11401.0 -2012-10-19 14:00:00,11321.0 -2012-10-19 15:00:00,11277.0 -2012-10-19 16:00:00,11118.0 -2012-10-19 17:00:00,10997.0 -2012-10-19 18:00:00,10922.0 -2012-10-19 19:00:00,11058.0 -2012-10-19 20:00:00,11522.0 -2012-10-19 21:00:00,11443.0 -2012-10-19 22:00:00,11114.0 -2012-10-19 23:00:00,10751.0 -2012-10-20 00:00:00,10141.0 -2012-10-18 01:00:00,9271.0 -2012-10-18 02:00:00,8750.0 -2012-10-18 03:00:00,8416.0 -2012-10-18 04:00:00,8227.0 -2012-10-18 05:00:00,8161.0 -2012-10-18 06:00:00,8346.0 -2012-10-18 07:00:00,9050.0 -2012-10-18 08:00:00,10290.0 -2012-10-18 09:00:00,10968.0 -2012-10-18 10:00:00,11110.0 -2012-10-18 11:00:00,11200.0 -2012-10-18 12:00:00,11312.0 -2012-10-18 13:00:00,11397.0 -2012-10-18 14:00:00,11380.0 -2012-10-18 15:00:00,11407.0 -2012-10-18 16:00:00,11338.0 -2012-10-18 17:00:00,11279.0 -2012-10-18 18:00:00,11313.0 -2012-10-18 19:00:00,11509.0 -2012-10-18 20:00:00,11902.0 -2012-10-18 21:00:00,11875.0 -2012-10-18 22:00:00,11608.0 -2012-10-18 23:00:00,11116.0 -2012-10-19 00:00:00,10306.0 -2012-10-17 01:00:00,9445.0 -2012-10-17 02:00:00,8910.0 -2012-10-17 03:00:00,8575.0 -2012-10-17 04:00:00,8349.0 -2012-10-17 05:00:00,8252.0 -2012-10-17 06:00:00,8424.0 -2012-10-17 07:00:00,9063.0 -2012-10-17 08:00:00,10288.0 -2012-10-17 09:00:00,10819.0 -2012-10-17 10:00:00,11072.0 -2012-10-17 11:00:00,11284.0 -2012-10-17 12:00:00,11533.0 -2012-10-17 13:00:00,11592.0 -2012-10-17 14:00:00,11631.0 -2012-10-17 15:00:00,11669.0 -2012-10-17 16:00:00,11605.0 -2012-10-17 17:00:00,11595.0 -2012-10-17 18:00:00,11813.0 -2012-10-17 19:00:00,11887.0 -2012-10-17 20:00:00,12090.0 -2012-10-17 21:00:00,12005.0 -2012-10-17 22:00:00,11648.0 -2012-10-17 23:00:00,11041.0 -2012-10-18 00:00:00,10081.0 -2012-10-16 01:00:00,9420.0 -2012-10-16 02:00:00,8870.0 -2012-10-16 03:00:00,8564.0 -2012-10-16 04:00:00,8429.0 -2012-10-16 05:00:00,8413.0 -2012-10-16 06:00:00,8646.0 -2012-10-16 07:00:00,9344.0 -2012-10-16 08:00:00,10565.0 -2012-10-16 09:00:00,11131.0 -2012-10-16 10:00:00,11242.0 -2012-10-16 11:00:00,11290.0 -2012-10-16 12:00:00,11373.0 -2012-10-16 13:00:00,11422.0 -2012-10-16 14:00:00,11430.0 -2012-10-16 15:00:00,11531.0 -2012-10-16 16:00:00,11481.0 -2012-10-16 17:00:00,11367.0 -2012-10-16 18:00:00,11277.0 -2012-10-16 19:00:00,11277.0 -2012-10-16 20:00:00,11820.0 -2012-10-16 21:00:00,11945.0 -2012-10-16 22:00:00,11599.0 -2012-10-16 23:00:00,11080.0 -2012-10-17 00:00:00,10239.0 -2012-10-15 01:00:00,8624.0 -2012-10-15 02:00:00,8237.0 -2012-10-15 03:00:00,8030.0 -2012-10-15 04:00:00,7953.0 -2012-10-15 05:00:00,7962.0 -2012-10-15 06:00:00,8198.0 -2012-10-15 07:00:00,8975.0 -2012-10-15 08:00:00,10252.0 -2012-10-15 09:00:00,10889.0 -2012-10-15 10:00:00,11145.0 -2012-10-15 11:00:00,11185.0 -2012-10-15 12:00:00,11275.0 -2012-10-15 13:00:00,11244.0 -2012-10-15 14:00:00,11193.0 -2012-10-15 15:00:00,11225.0 -2012-10-15 16:00:00,11133.0 -2012-10-15 17:00:00,10996.0 -2012-10-15 18:00:00,10896.0 -2012-10-15 19:00:00,10927.0 -2012-10-15 20:00:00,11522.0 -2012-10-15 21:00:00,11678.0 -2012-10-15 22:00:00,11448.0 -2012-10-15 23:00:00,10959.0 -2012-10-16 00:00:00,10153.0 -2012-10-14 01:00:00,8972.0 -2012-10-14 02:00:00,8543.0 -2012-10-14 03:00:00,8160.0 -2012-10-14 04:00:00,8017.0 -2012-10-14 05:00:00,7911.0 -2012-10-14 06:00:00,7847.0 -2012-10-14 07:00:00,7969.0 -2012-10-14 08:00:00,8171.0 -2012-10-14 09:00:00,8341.0 -2012-10-14 10:00:00,8604.0 -2012-10-14 11:00:00,9037.0 -2012-10-14 12:00:00,9299.0 -2012-10-14 13:00:00,9549.0 -2012-10-14 14:00:00,9507.0 -2012-10-14 15:00:00,9508.0 -2012-10-14 16:00:00,9544.0 -2012-10-14 17:00:00,9567.0 -2012-10-14 18:00:00,9641.0 -2012-10-14 19:00:00,9882.0 -2012-10-14 20:00:00,10262.0 -2012-10-14 21:00:00,10339.0 -2012-10-14 22:00:00,10119.0 -2012-10-14 23:00:00,9776.0 -2012-10-15 00:00:00,9191.0 -2012-10-13 01:00:00,9508.0 -2012-10-13 02:00:00,8946.0 -2012-10-13 03:00:00,8593.0 -2012-10-13 04:00:00,8369.0 -2012-10-13 05:00:00,8271.0 -2012-10-13 06:00:00,8291.0 -2012-10-13 07:00:00,8538.0 -2012-10-13 08:00:00,8956.0 -2012-10-13 09:00:00,9256.0 -2012-10-13 10:00:00,9564.0 -2012-10-13 11:00:00,9894.0 -2012-10-13 12:00:00,10158.0 -2012-10-13 13:00:00,10321.0 -2012-10-13 14:00:00,10229.0 -2012-10-13 15:00:00,10057.0 -2012-10-13 16:00:00,9918.0 -2012-10-13 17:00:00,9956.0 -2012-10-13 18:00:00,10023.0 -2012-10-13 19:00:00,10221.0 -2012-10-13 20:00:00,10545.0 -2012-10-13 21:00:00,10534.0 -2012-10-13 22:00:00,10375.0 -2012-10-13 23:00:00,10074.0 -2012-10-14 00:00:00,9558.0 -2012-10-12 01:00:00,9413.0 -2012-10-12 02:00:00,8906.0 -2012-10-12 03:00:00,8554.0 -2012-10-12 04:00:00,8405.0 -2012-10-12 05:00:00,8397.0 -2012-10-12 06:00:00,8615.0 -2012-10-12 07:00:00,9360.0 -2012-10-12 08:00:00,10567.0 -2012-10-12 09:00:00,11098.0 -2012-10-12 10:00:00,11249.0 -2012-10-12 11:00:00,11274.0 -2012-10-12 12:00:00,11366.0 -2012-10-12 13:00:00,11283.0 -2012-10-12 14:00:00,11208.0 -2012-10-12 15:00:00,11166.0 -2012-10-12 16:00:00,11012.0 -2012-10-12 17:00:00,10854.0 -2012-10-12 18:00:00,10734.0 -2012-10-12 19:00:00,10694.0 -2012-10-12 20:00:00,11211.0 -2012-10-12 21:00:00,11401.0 -2012-10-12 22:00:00,11180.0 -2012-10-12 23:00:00,10847.0 -2012-10-13 00:00:00,10176.0 -2012-10-11 01:00:00,9656.0 -2012-10-11 02:00:00,9183.0 -2012-10-11 03:00:00,8883.0 -2012-10-11 04:00:00,8746.0 -2012-10-11 05:00:00,8708.0 -2012-10-11 06:00:00,8888.0 -2012-10-11 07:00:00,9626.0 -2012-10-11 08:00:00,10821.0 -2012-10-11 09:00:00,11337.0 -2012-10-11 10:00:00,11435.0 -2012-10-11 11:00:00,11462.0 -2012-10-11 12:00:00,11531.0 -2012-10-11 13:00:00,11524.0 -2012-10-11 14:00:00,11505.0 -2012-10-11 15:00:00,11522.0 -2012-10-11 16:00:00,11438.0 -2012-10-11 17:00:00,11312.0 -2012-10-11 18:00:00,11213.0 -2012-10-11 19:00:00,11207.0 -2012-10-11 20:00:00,11680.0 -2012-10-11 21:00:00,11790.0 -2012-10-11 22:00:00,11521.0 -2012-10-11 23:00:00,11013.0 -2012-10-12 00:00:00,10221.0 -2012-10-10 01:00:00,9445.0 -2012-10-10 02:00:00,8920.0 -2012-10-10 03:00:00,8598.0 -2012-10-10 04:00:00,8479.0 -2012-10-10 05:00:00,8455.0 -2012-10-10 06:00:00,8675.0 -2012-10-10 07:00:00,9413.0 -2012-10-10 08:00:00,10750.0 -2012-10-10 09:00:00,11293.0 -2012-10-10 10:00:00,11426.0 -2012-10-10 11:00:00,11486.0 -2012-10-10 12:00:00,11556.0 -2012-10-10 13:00:00,11493.0 -2012-10-10 14:00:00,11448.0 -2012-10-10 15:00:00,11405.0 -2012-10-10 16:00:00,11323.0 -2012-10-10 17:00:00,11182.0 -2012-10-10 18:00:00,11088.0 -2012-10-10 19:00:00,11084.0 -2012-10-10 20:00:00,11594.0 -2012-10-10 21:00:00,11883.0 -2012-10-10 22:00:00,11683.0 -2012-10-10 23:00:00,11219.0 -2012-10-11 00:00:00,10400.0 -2012-10-09 01:00:00,9368.0 -2012-10-09 02:00:00,8886.0 -2012-10-09 03:00:00,8571.0 -2012-10-09 04:00:00,8394.0 -2012-10-09 05:00:00,8380.0 -2012-10-09 06:00:00,8554.0 -2012-10-09 07:00:00,9283.0 -2012-10-09 08:00:00,10540.0 -2012-10-09 09:00:00,11022.0 -2012-10-09 10:00:00,11219.0 -2012-10-09 11:00:00,11339.0 -2012-10-09 12:00:00,11435.0 -2012-10-09 13:00:00,11436.0 -2012-10-09 14:00:00,11502.0 -2012-10-09 15:00:00,11496.0 -2012-10-09 16:00:00,11441.0 -2012-10-09 17:00:00,11312.0 -2012-10-09 18:00:00,11202.0 -2012-10-09 19:00:00,11234.0 -2012-10-09 20:00:00,11749.0 -2012-10-09 21:00:00,11910.0 -2012-10-09 22:00:00,11571.0 -2012-10-09 23:00:00,11018.0 -2012-10-10 00:00:00,10213.0 -2012-10-08 01:00:00,8961.0 -2012-10-08 02:00:00,8595.0 -2012-10-08 03:00:00,8423.0 -2012-10-08 04:00:00,8341.0 -2012-10-08 05:00:00,8378.0 -2012-10-08 06:00:00,8641.0 -2012-10-08 07:00:00,9401.0 -2012-10-08 08:00:00,10380.0 -2012-10-08 09:00:00,10924.0 -2012-10-08 10:00:00,11174.0 -2012-10-08 11:00:00,11326.0 -2012-10-08 12:00:00,11407.0 -2012-10-08 13:00:00,11367.0 -2012-10-08 14:00:00,11334.0 -2012-10-08 15:00:00,11299.0 -2012-10-08 16:00:00,11184.0 -2012-10-08 17:00:00,11032.0 -2012-10-08 18:00:00,10932.0 -2012-10-08 19:00:00,10956.0 -2012-10-08 20:00:00,11449.0 -2012-10-08 21:00:00,11753.0 -2012-10-08 22:00:00,11489.0 -2012-10-08 23:00:00,11014.0 -2012-10-09 00:00:00,10157.0 -2012-10-07 01:00:00,8960.0 -2012-10-07 02:00:00,8479.0 -2012-10-07 03:00:00,8189.0 -2012-10-07 04:00:00,8010.0 -2012-10-07 05:00:00,7902.0 -2012-10-07 06:00:00,7941.0 -2012-10-07 07:00:00,8061.0 -2012-10-07 08:00:00,8351.0 -2012-10-07 09:00:00,8410.0 -2012-10-07 10:00:00,8716.0 -2012-10-07 11:00:00,8943.0 -2012-10-07 12:00:00,9064.0 -2012-10-07 13:00:00,9090.0 -2012-10-07 14:00:00,9121.0 -2012-10-07 15:00:00,9048.0 -2012-10-07 16:00:00,9010.0 -2012-10-07 17:00:00,8966.0 -2012-10-07 18:00:00,8952.0 -2012-10-07 19:00:00,9127.0 -2012-10-07 20:00:00,9744.0 -2012-10-07 21:00:00,10279.0 -2012-10-07 22:00:00,10151.0 -2012-10-07 23:00:00,9924.0 -2012-10-08 00:00:00,9433.0 -2012-10-06 01:00:00,9488.0 -2012-10-06 02:00:00,8969.0 -2012-10-06 03:00:00,8600.0 -2012-10-06 04:00:00,8435.0 -2012-10-06 05:00:00,8260.0 -2012-10-06 06:00:00,8406.0 -2012-10-06 07:00:00,8627.0 -2012-10-06 08:00:00,9124.0 -2012-10-06 09:00:00,9310.0 -2012-10-06 10:00:00,9634.0 -2012-10-06 11:00:00,9866.0 -2012-10-06 12:00:00,9905.0 -2012-10-06 13:00:00,9880.0 -2012-10-06 14:00:00,9712.0 -2012-10-06 15:00:00,9597.0 -2012-10-06 16:00:00,9492.0 -2012-10-06 17:00:00,9476.0 -2012-10-06 18:00:00,9570.0 -2012-10-06 19:00:00,9736.0 -2012-10-06 20:00:00,10252.0 -2012-10-06 21:00:00,10449.0 -2012-10-06 22:00:00,10267.0 -2012-10-06 23:00:00,9961.0 -2012-10-07 00:00:00,9516.0 -2012-10-05 01:00:00,9277.0 -2012-10-05 02:00:00,8726.0 -2012-10-05 03:00:00,8376.0 -2012-10-05 04:00:00,8165.0 -2012-10-05 05:00:00,8082.0 -2012-10-05 06:00:00,8252.0 -2012-10-05 07:00:00,8877.0 -2012-10-05 08:00:00,9991.0 -2012-10-05 09:00:00,10582.0 -2012-10-05 10:00:00,10829.0 -2012-10-05 11:00:00,11019.0 -2012-10-05 12:00:00,11193.0 -2012-10-05 13:00:00,11235.0 -2012-10-05 14:00:00,11235.0 -2012-10-05 15:00:00,11259.0 -2012-10-05 16:00:00,11177.0 -2012-10-05 17:00:00,11053.0 -2012-10-05 18:00:00,10992.0 -2012-10-05 19:00:00,11025.0 -2012-10-05 20:00:00,11383.0 -2012-10-05 21:00:00,11385.0 -2012-10-05 22:00:00,11195.0 -2012-10-05 23:00:00,10789.0 -2012-10-06 00:00:00,10117.0 -2012-10-04 01:00:00,9520.0 -2012-10-04 02:00:00,8934.0 -2012-10-04 03:00:00,8572.0 -2012-10-04 04:00:00,8346.0 -2012-10-04 05:00:00,8264.0 -2012-10-04 06:00:00,8443.0 -2012-10-04 07:00:00,9034.0 -2012-10-04 08:00:00,10210.0 -2012-10-04 09:00:00,10790.0 -2012-10-04 10:00:00,11193.0 -2012-10-04 11:00:00,11538.0 -2012-10-04 12:00:00,11954.0 -2012-10-04 13:00:00,12191.0 -2012-10-04 14:00:00,12363.0 -2012-10-04 15:00:00,12545.0 -2012-10-04 16:00:00,12432.0 -2012-10-04 17:00:00,12161.0 -2012-10-04 18:00:00,11891.0 -2012-10-04 19:00:00,11794.0 -2012-10-04 20:00:00,11799.0 -2012-10-04 21:00:00,11784.0 -2012-10-04 22:00:00,11420.0 -2012-10-04 23:00:00,10890.0 -2012-10-05 00:00:00,10058.0 -2012-10-03 01:00:00,9366.0 -2012-10-03 02:00:00,8860.0 -2012-10-03 03:00:00,8536.0 -2012-10-03 04:00:00,8355.0 -2012-10-03 05:00:00,8277.0 -2012-10-03 06:00:00,8492.0 -2012-10-03 07:00:00,9151.0 -2012-10-03 08:00:00,10401.0 -2012-10-03 09:00:00,11202.0 -2012-10-03 10:00:00,11543.0 -2012-10-03 11:00:00,11743.0 -2012-10-03 12:00:00,11931.0 -2012-10-03 13:00:00,11971.0 -2012-10-03 14:00:00,11956.0 -2012-10-03 15:00:00,11972.0 -2012-10-03 16:00:00,11886.0 -2012-10-03 17:00:00,11821.0 -2012-10-03 18:00:00,11750.0 -2012-10-03 19:00:00,11688.0 -2012-10-03 20:00:00,11958.0 -2012-10-03 21:00:00,12132.0 -2012-10-03 22:00:00,11824.0 -2012-10-03 23:00:00,11230.0 -2012-10-04 00:00:00,10366.0 -2012-10-02 01:00:00,9190.0 -2012-10-02 02:00:00,8670.0 -2012-10-02 03:00:00,8332.0 -2012-10-02 04:00:00,8165.0 -2012-10-02 05:00:00,8086.0 -2012-10-02 06:00:00,8279.0 -2012-10-02 07:00:00,8940.0 -2012-10-02 08:00:00,10091.0 -2012-10-02 09:00:00,10646.0 -2012-10-02 10:00:00,11047.0 -2012-10-02 11:00:00,11362.0 -2012-10-02 12:00:00,11623.0 -2012-10-02 13:00:00,11769.0 -2012-10-02 14:00:00,11806.0 -2012-10-02 15:00:00,11909.0 -2012-10-02 16:00:00,11833.0 -2012-10-02 17:00:00,11734.0 -2012-10-02 18:00:00,11568.0 -2012-10-02 19:00:00,11345.0 -2012-10-02 20:00:00,11464.0 -2012-10-02 21:00:00,11838.0 -2012-10-02 22:00:00,11617.0 -2012-10-02 23:00:00,11054.0 -2012-10-03 00:00:00,10195.0 -2012-10-01 01:00:00,8452.0 -2012-10-01 02:00:00,8086.0 -2012-10-01 03:00:00,7870.0 -2012-10-01 04:00:00,7774.0 -2012-10-01 05:00:00,7773.0 -2012-10-01 06:00:00,8012.0 -2012-10-01 07:00:00,8722.0 -2012-10-01 08:00:00,9910.0 -2012-10-01 09:00:00,10512.0 -2012-10-01 10:00:00,10858.0 -2012-10-01 11:00:00,11055.0 -2012-10-01 12:00:00,11302.0 -2012-10-01 13:00:00,11426.0 -2012-10-01 14:00:00,11522.0 -2012-10-01 15:00:00,11574.0 -2012-10-01 16:00:00,11489.0 -2012-10-01 17:00:00,11309.0 -2012-10-01 18:00:00,11174.0 -2012-10-01 19:00:00,11082.0 -2012-10-01 20:00:00,11396.0 -2012-10-01 21:00:00,11710.0 -2012-10-01 22:00:00,11407.0 -2012-10-01 23:00:00,10843.0 -2012-10-02 00:00:00,9995.0 -2012-09-30 01:00:00,8746.0 -2012-09-30 02:00:00,8249.0 -2012-09-30 03:00:00,7882.0 -2012-09-30 04:00:00,7689.0 -2012-09-30 05:00:00,7555.0 -2012-09-30 06:00:00,7558.0 -2012-09-30 07:00:00,7636.0 -2012-09-30 08:00:00,7804.0 -2012-09-30 09:00:00,7844.0 -2012-09-30 10:00:00,8261.0 -2012-09-30 11:00:00,8636.0 -2012-09-30 12:00:00,8893.0 -2012-09-30 13:00:00,9064.0 -2012-09-30 14:00:00,9134.0 -2012-09-30 15:00:00,9182.0 -2012-09-30 16:00:00,9196.0 -2012-09-30 17:00:00,9194.0 -2012-09-30 18:00:00,9252.0 -2012-09-30 19:00:00,9273.0 -2012-09-30 20:00:00,9670.0 -2012-09-30 21:00:00,10132.0 -2012-09-30 22:00:00,10003.0 -2012-09-30 23:00:00,9598.0 -2012-10-01 00:00:00,9042.0 -2012-09-29 01:00:00,9115.0 -2012-09-29 02:00:00,8581.0 -2012-09-29 03:00:00,8187.0 -2012-09-29 04:00:00,7989.0 -2012-09-29 05:00:00,7850.0 -2012-09-29 06:00:00,7904.0 -2012-09-29 07:00:00,8121.0 -2012-09-29 08:00:00,8513.0 -2012-09-29 09:00:00,8670.0 -2012-09-29 10:00:00,9166.0 -2012-09-29 11:00:00,9521.0 -2012-09-29 12:00:00,9830.0 -2012-09-29 13:00:00,9941.0 -2012-09-29 14:00:00,10010.0 -2012-09-29 15:00:00,9951.0 -2012-09-29 16:00:00,10020.0 -2012-09-29 17:00:00,9955.0 -2012-09-29 18:00:00,9984.0 -2012-09-29 19:00:00,9844.0 -2012-09-29 20:00:00,9995.0 -2012-09-29 21:00:00,10364.0 -2012-09-29 22:00:00,10252.0 -2012-09-29 23:00:00,9868.0 -2012-09-30 00:00:00,9356.0 -2012-09-28 01:00:00,9272.0 -2012-09-28 02:00:00,8732.0 -2012-09-28 03:00:00,8418.0 -2012-09-28 04:00:00,8172.0 -2012-09-28 05:00:00,8095.0 -2012-09-28 06:00:00,8272.0 -2012-09-28 07:00:00,8923.0 -2012-09-28 08:00:00,9971.0 -2012-09-28 09:00:00,10516.0 -2012-09-28 10:00:00,10867.0 -2012-09-28 11:00:00,11140.0 -2012-09-28 12:00:00,11395.0 -2012-09-28 13:00:00,11473.0 -2012-09-28 14:00:00,11474.0 -2012-09-28 15:00:00,11537.0 -2012-09-28 16:00:00,11453.0 -2012-09-28 17:00:00,11325.0 -2012-09-28 18:00:00,11176.0 -2012-09-28 19:00:00,10993.0 -2012-09-28 20:00:00,11023.0 -2012-09-28 21:00:00,11307.0 -2012-09-28 22:00:00,11075.0 -2012-09-28 23:00:00,10574.0 -2012-09-29 00:00:00,9871.0 -2012-09-27 01:00:00,9302.0 -2012-09-27 02:00:00,8745.0 -2012-09-27 03:00:00,8406.0 -2012-09-27 04:00:00,8215.0 -2012-09-27 05:00:00,8134.0 -2012-09-27 06:00:00,8300.0 -2012-09-27 07:00:00,8893.0 -2012-09-27 08:00:00,10003.0 -2012-09-27 09:00:00,10551.0 -2012-09-27 10:00:00,10856.0 -2012-09-27 11:00:00,11160.0 -2012-09-27 12:00:00,11438.0 -2012-09-27 13:00:00,11535.0 -2012-09-27 14:00:00,11597.0 -2012-09-27 15:00:00,11601.0 -2012-09-27 16:00:00,11551.0 -2012-09-27 17:00:00,11407.0 -2012-09-27 18:00:00,11303.0 -2012-09-27 19:00:00,11090.0 -2012-09-27 20:00:00,11187.0 -2012-09-27 21:00:00,11702.0 -2012-09-27 22:00:00,11459.0 -2012-09-27 23:00:00,10932.0 -2012-09-28 00:00:00,10069.0 -2012-09-26 01:00:00,9728.0 -2012-09-26 02:00:00,9143.0 -2012-09-26 03:00:00,8728.0 -2012-09-26 04:00:00,8505.0 -2012-09-26 05:00:00,8396.0 -2012-09-26 06:00:00,8533.0 -2012-09-26 07:00:00,9120.0 -2012-09-26 08:00:00,10268.0 -2012-09-26 09:00:00,10808.0 -2012-09-26 10:00:00,11251.0 -2012-09-26 11:00:00,11527.0 -2012-09-26 12:00:00,11752.0 -2012-09-26 13:00:00,11868.0 -2012-09-26 14:00:00,11948.0 -2012-09-26 15:00:00,12056.0 -2012-09-26 16:00:00,11997.0 -2012-09-26 17:00:00,11857.0 -2012-09-26 18:00:00,11699.0 -2012-09-26 19:00:00,11459.0 -2012-09-26 20:00:00,11411.0 -2012-09-26 21:00:00,11805.0 -2012-09-26 22:00:00,11541.0 -2012-09-26 23:00:00,10986.0 -2012-09-27 00:00:00,10107.0 -2012-09-25 01:00:00,9253.0 -2012-09-25 02:00:00,8660.0 -2012-09-25 03:00:00,8295.0 -2012-09-25 04:00:00,8140.0 -2012-09-25 05:00:00,8063.0 -2012-09-25 06:00:00,8246.0 -2012-09-25 07:00:00,8914.0 -2012-09-25 08:00:00,10014.0 -2012-09-25 09:00:00,10619.0 -2012-09-25 10:00:00,11038.0 -2012-09-25 11:00:00,11262.0 -2012-09-25 12:00:00,11518.0 -2012-09-25 13:00:00,11716.0 -2012-09-25 14:00:00,11921.0 -2012-09-25 15:00:00,12149.0 -2012-09-25 16:00:00,12159.0 -2012-09-25 17:00:00,12156.0 -2012-09-25 18:00:00,12108.0 -2012-09-25 19:00:00,11969.0 -2012-09-25 20:00:00,11937.0 -2012-09-25 21:00:00,12390.0 -2012-09-25 22:00:00,12108.0 -2012-09-25 23:00:00,11519.0 -2012-09-26 00:00:00,10594.0 -2012-09-24 01:00:00,8435.0 -2012-09-24 02:00:00,8062.0 -2012-09-24 03:00:00,7867.0 -2012-09-24 04:00:00,7775.0 -2012-09-24 05:00:00,7806.0 -2012-09-24 06:00:00,8023.0 -2012-09-24 07:00:00,8804.0 -2012-09-24 08:00:00,10004.0 -2012-09-24 09:00:00,10597.0 -2012-09-24 10:00:00,10918.0 -2012-09-24 11:00:00,11067.0 -2012-09-24 12:00:00,11353.0 -2012-09-24 13:00:00,11378.0 -2012-09-24 14:00:00,11366.0 -2012-09-24 15:00:00,11465.0 -2012-09-24 16:00:00,11446.0 -2012-09-24 17:00:00,11401.0 -2012-09-24 18:00:00,11298.0 -2012-09-24 19:00:00,11205.0 -2012-09-24 20:00:00,11176.0 -2012-09-24 21:00:00,11745.0 -2012-09-24 22:00:00,11505.0 -2012-09-24 23:00:00,10945.0 -2012-09-25 00:00:00,10075.0 -2012-09-23 01:00:00,8680.0 -2012-09-23 02:00:00,8211.0 -2012-09-23 03:00:00,7906.0 -2012-09-23 04:00:00,7724.0 -2012-09-23 05:00:00,7604.0 -2012-09-23 06:00:00,7599.0 -2012-09-23 07:00:00,7669.0 -2012-09-23 08:00:00,7837.0 -2012-09-23 09:00:00,7910.0 -2012-09-23 10:00:00,8307.0 -2012-09-23 11:00:00,8558.0 -2012-09-23 12:00:00,8785.0 -2012-09-23 13:00:00,8918.0 -2012-09-23 14:00:00,8982.0 -2012-09-23 15:00:00,8981.0 -2012-09-23 16:00:00,8921.0 -2012-09-23 17:00:00,8948.0 -2012-09-23 18:00:00,8940.0 -2012-09-23 19:00:00,9080.0 -2012-09-23 20:00:00,9263.0 -2012-09-23 21:00:00,10002.0 -2012-09-23 22:00:00,9910.0 -2012-09-23 23:00:00,9584.0 -2012-09-24 00:00:00,8987.0 -2012-09-22 01:00:00,9164.0 -2012-09-22 02:00:00,8645.0 -2012-09-22 03:00:00,8258.0 -2012-09-22 04:00:00,8023.0 -2012-09-22 05:00:00,7894.0 -2012-09-22 06:00:00,7941.0 -2012-09-22 07:00:00,8149.0 -2012-09-22 08:00:00,8522.0 -2012-09-22 09:00:00,8762.0 -2012-09-22 10:00:00,9258.0 -2012-09-22 11:00:00,9570.0 -2012-09-22 12:00:00,9727.0 -2012-09-22 13:00:00,9775.0 -2012-09-22 14:00:00,9667.0 -2012-09-22 15:00:00,9501.0 -2012-09-22 16:00:00,9388.0 -2012-09-22 17:00:00,9354.0 -2012-09-22 18:00:00,9334.0 -2012-09-22 19:00:00,9346.0 -2012-09-22 20:00:00,9481.0 -2012-09-22 21:00:00,10089.0 -2012-09-22 22:00:00,10033.0 -2012-09-22 23:00:00,9755.0 -2012-09-23 00:00:00,9230.0 -2012-09-21 01:00:00,9239.0 -2012-09-21 02:00:00,8715.0 -2012-09-21 03:00:00,8409.0 -2012-09-21 04:00:00,8166.0 -2012-09-21 05:00:00,8145.0 -2012-09-21 06:00:00,8288.0 -2012-09-21 07:00:00,8874.0 -2012-09-21 08:00:00,9897.0 -2012-09-21 09:00:00,10495.0 -2012-09-21 10:00:00,10867.0 -2012-09-21 11:00:00,11214.0 -2012-09-21 12:00:00,11314.0 -2012-09-21 13:00:00,11375.0 -2012-09-21 14:00:00,11368.0 -2012-09-21 15:00:00,11369.0 -2012-09-21 16:00:00,11215.0 -2012-09-21 17:00:00,11086.0 -2012-09-21 18:00:00,11012.0 -2012-09-21 19:00:00,11025.0 -2012-09-21 20:00:00,11103.0 -2012-09-21 21:00:00,11212.0 -2012-09-21 22:00:00,11000.0 -2012-09-21 23:00:00,10557.0 -2012-09-22 00:00:00,9908.0 -2012-09-20 01:00:00,9368.0 -2012-09-20 02:00:00,8836.0 -2012-09-20 03:00:00,8488.0 -2012-09-20 04:00:00,8261.0 -2012-09-20 05:00:00,8156.0 -2012-09-20 06:00:00,8308.0 -2012-09-20 07:00:00,8953.0 -2012-09-20 08:00:00,10057.0 -2012-09-20 09:00:00,10592.0 -2012-09-20 10:00:00,11007.0 -2012-09-20 11:00:00,11418.0 -2012-09-20 12:00:00,11731.0 -2012-09-20 13:00:00,11910.0 -2012-09-20 14:00:00,11974.0 -2012-09-20 15:00:00,12030.0 -2012-09-20 16:00:00,11940.0 -2012-09-20 17:00:00,11861.0 -2012-09-20 18:00:00,11649.0 -2012-09-20 19:00:00,11405.0 -2012-09-20 20:00:00,11324.0 -2012-09-20 21:00:00,11805.0 -2012-09-20 22:00:00,11584.0 -2012-09-20 23:00:00,10965.0 -2012-09-21 00:00:00,10060.0 -2012-09-19 01:00:00,9164.0 -2012-09-19 02:00:00,8635.0 -2012-09-19 03:00:00,8294.0 -2012-09-19 04:00:00,8117.0 -2012-09-19 05:00:00,8072.0 -2012-09-19 06:00:00,8241.0 -2012-09-19 07:00:00,8880.0 -2012-09-19 08:00:00,9931.0 -2012-09-19 09:00:00,10522.0 -2012-09-19 10:00:00,10859.0 -2012-09-19 11:00:00,11108.0 -2012-09-19 12:00:00,11374.0 -2012-09-19 13:00:00,11480.0 -2012-09-19 14:00:00,11555.0 -2012-09-19 15:00:00,11712.0 -2012-09-19 16:00:00,11710.0 -2012-09-19 17:00:00,11676.0 -2012-09-19 18:00:00,11605.0 -2012-09-19 19:00:00,11449.0 -2012-09-19 20:00:00,11328.0 -2012-09-19 21:00:00,11879.0 -2012-09-19 22:00:00,11678.0 -2012-09-19 23:00:00,11099.0 -2012-09-20 00:00:00,10212.0 -2012-09-18 01:00:00,9718.0 -2012-09-18 02:00:00,9085.0 -2012-09-18 03:00:00,8690.0 -2012-09-18 04:00:00,8411.0 -2012-09-18 05:00:00,8273.0 -2012-09-18 06:00:00,8395.0 -2012-09-18 07:00:00,8986.0 -2012-09-18 08:00:00,10018.0 -2012-09-18 09:00:00,10533.0 -2012-09-18 10:00:00,10932.0 -2012-09-18 11:00:00,11196.0 -2012-09-18 12:00:00,11435.0 -2012-09-18 13:00:00,11513.0 -2012-09-18 14:00:00,11578.0 -2012-09-18 15:00:00,11619.0 -2012-09-18 16:00:00,11546.0 -2012-09-18 17:00:00,11422.0 -2012-09-18 18:00:00,11260.0 -2012-09-18 19:00:00,11058.0 -2012-09-18 20:00:00,10938.0 -2012-09-18 21:00:00,11489.0 -2012-09-18 22:00:00,11349.0 -2012-09-18 23:00:00,10814.0 -2012-09-19 00:00:00,9943.0 -2012-09-17 01:00:00,9092.0 -2012-09-17 02:00:00,8608.0 -2012-09-17 03:00:00,8300.0 -2012-09-17 04:00:00,8177.0 -2012-09-17 05:00:00,8115.0 -2012-09-17 06:00:00,8349.0 -2012-09-17 07:00:00,9034.0 -2012-09-17 08:00:00,10152.0 -2012-09-17 09:00:00,10856.0 -2012-09-17 10:00:00,11520.0 -2012-09-17 11:00:00,12031.0 -2012-09-17 12:00:00,12492.0 -2012-09-17 13:00:00,12745.0 -2012-09-17 14:00:00,12936.0 -2012-09-17 15:00:00,13240.0 -2012-09-17 16:00:00,13319.0 -2012-09-17 17:00:00,13361.0 -2012-09-17 18:00:00,13257.0 -2012-09-17 19:00:00,13000.0 -2012-09-17 20:00:00,12847.0 -2012-09-17 21:00:00,12826.0 -2012-09-17 22:00:00,12446.0 -2012-09-17 23:00:00,11664.0 -2012-09-18 00:00:00,10658.0 -2012-09-16 01:00:00,9252.0 -2012-09-16 02:00:00,8650.0 -2012-09-16 03:00:00,8301.0 -2012-09-16 04:00:00,8003.0 -2012-09-16 05:00:00,7842.0 -2012-09-16 06:00:00,7798.0 -2012-09-16 07:00:00,7838.0 -2012-09-16 08:00:00,7872.0 -2012-09-16 09:00:00,7976.0 -2012-09-16 10:00:00,8506.0 -2012-09-16 11:00:00,9066.0 -2012-09-16 12:00:00,9530.0 -2012-09-16 13:00:00,9911.0 -2012-09-16 14:00:00,10130.0 -2012-09-16 15:00:00,10334.0 -2012-09-16 16:00:00,10462.0 -2012-09-16 17:00:00,10595.0 -2012-09-16 18:00:00,10613.0 -2012-09-16 19:00:00,10591.0 -2012-09-16 20:00:00,10595.0 -2012-09-16 21:00:00,11081.0 -2012-09-16 22:00:00,10958.0 -2012-09-16 23:00:00,10495.0 -2012-09-17 00:00:00,9805.0 -2012-09-15 01:00:00,9633.0 -2012-09-15 02:00:00,9022.0 -2012-09-15 03:00:00,8613.0 -2012-09-15 04:00:00,8306.0 -2012-09-15 05:00:00,8146.0 -2012-09-15 06:00:00,8097.0 -2012-09-15 07:00:00,8302.0 -2012-09-15 08:00:00,8515.0 -2012-09-15 09:00:00,8846.0 -2012-09-15 10:00:00,9441.0 -2012-09-15 11:00:00,10013.0 -2012-09-15 12:00:00,10464.0 -2012-09-15 13:00:00,10716.0 -2012-09-15 14:00:00,10884.0 -2012-09-15 15:00:00,10950.0 -2012-09-15 16:00:00,11120.0 -2012-09-15 17:00:00,11235.0 -2012-09-15 18:00:00,11306.0 -2012-09-15 19:00:00,11152.0 -2012-09-15 20:00:00,10855.0 -2012-09-15 21:00:00,11173.0 -2012-09-15 22:00:00,11019.0 -2012-09-15 23:00:00,10599.0 -2012-09-16 00:00:00,9900.0 -2012-09-14 01:00:00,9634.0 -2012-09-14 02:00:00,9098.0 -2012-09-14 03:00:00,8657.0 -2012-09-14 04:00:00,8501.0 -2012-09-14 05:00:00,8374.0 -2012-09-14 06:00:00,8537.0 -2012-09-14 07:00:00,9089.0 -2012-09-14 08:00:00,10076.0 -2012-09-14 09:00:00,10601.0 -2012-09-14 10:00:00,11114.0 -2012-09-14 11:00:00,11510.0 -2012-09-14 12:00:00,11941.0 -2012-09-14 13:00:00,12090.0 -2012-09-14 14:00:00,12228.0 -2012-09-14 15:00:00,12409.0 -2012-09-14 16:00:00,12486.0 -2012-09-14 17:00:00,12498.0 -2012-09-14 18:00:00,12418.0 -2012-09-14 19:00:00,12201.0 -2012-09-14 20:00:00,11806.0 -2012-09-14 21:00:00,11983.0 -2012-09-14 22:00:00,11857.0 -2012-09-14 23:00:00,11271.0 -2012-09-15 00:00:00,10512.0 -2012-09-13 01:00:00,11515.0 -2012-09-13 02:00:00,10639.0 -2012-09-13 03:00:00,10075.0 -2012-09-13 04:00:00,9656.0 -2012-09-13 05:00:00,9460.0 -2012-09-13 06:00:00,9531.0 -2012-09-13 07:00:00,10160.0 -2012-09-13 08:00:00,11170.0 -2012-09-13 09:00:00,11775.0 -2012-09-13 10:00:00,12182.0 -2012-09-13 11:00:00,12599.0 -2012-09-13 12:00:00,12977.0 -2012-09-13 13:00:00,13112.0 -2012-09-13 14:00:00,13190.0 -2012-09-13 15:00:00,13054.0 -2012-09-13 16:00:00,12732.0 -2012-09-13 17:00:00,12407.0 -2012-09-13 18:00:00,12173.0 -2012-09-13 19:00:00,12000.0 -2012-09-13 20:00:00,12020.0 -2012-09-13 21:00:00,12165.0 -2012-09-13 22:00:00,11883.0 -2012-09-13 23:00:00,11404.0 -2012-09-14 00:00:00,10525.0 -2012-09-12 01:00:00,10907.0 -2012-09-12 02:00:00,10119.0 -2012-09-12 03:00:00,9586.0 -2012-09-12 04:00:00,9228.0 -2012-09-12 05:00:00,9078.0 -2012-09-12 06:00:00,9220.0 -2012-09-12 07:00:00,9849.0 -2012-09-12 08:00:00,10858.0 -2012-09-12 09:00:00,11575.0 -2012-09-12 10:00:00,12332.0 -2012-09-12 11:00:00,12987.0 -2012-09-12 12:00:00,13731.0 -2012-09-12 13:00:00,14405.0 -2012-09-12 14:00:00,14993.0 -2012-09-12 15:00:00,15624.0 -2012-09-12 16:00:00,16000.0 -2012-09-12 17:00:00,16346.0 -2012-09-12 18:00:00,16481.0 -2012-09-12 19:00:00,16190.0 -2012-09-12 20:00:00,15474.0 -2012-09-12 21:00:00,15422.0 -2012-09-12 22:00:00,14999.0 -2012-09-12 23:00:00,14072.0 -2012-09-13 00:00:00,12774.0 -2012-09-11 01:00:00,9991.0 -2012-09-11 02:00:00,9338.0 -2012-09-11 03:00:00,8928.0 -2012-09-11 04:00:00,8624.0 -2012-09-11 05:00:00,8536.0 -2012-09-11 06:00:00,8677.0 -2012-09-11 07:00:00,9364.0 -2012-09-11 08:00:00,10304.0 -2012-09-11 09:00:00,10966.0 -2012-09-11 10:00:00,11650.0 -2012-09-11 11:00:00,12164.0 -2012-09-11 12:00:00,12788.0 -2012-09-11 13:00:00,13147.0 -2012-09-11 14:00:00,13480.0 -2012-09-11 15:00:00,13900.0 -2012-09-11 16:00:00,14189.0 -2012-09-11 17:00:00,14447.0 -2012-09-11 18:00:00,14582.0 -2012-09-11 19:00:00,14393.0 -2012-09-11 20:00:00,13867.0 -2012-09-11 21:00:00,14006.0 -2012-09-11 22:00:00,13831.0 -2012-09-11 23:00:00,13080.0 -2012-09-12 00:00:00,11972.0 -2012-09-10 01:00:00,8963.0 -2012-09-10 02:00:00,8487.0 -2012-09-10 03:00:00,8200.0 -2012-09-10 04:00:00,8085.0 -2012-09-10 05:00:00,8038.0 -2012-09-10 06:00:00,8282.0 -2012-09-10 07:00:00,8928.0 -2012-09-10 08:00:00,9904.0 -2012-09-10 09:00:00,10620.0 -2012-09-10 10:00:00,11353.0 -2012-09-10 11:00:00,11806.0 -2012-09-10 12:00:00,12296.0 -2012-09-10 13:00:00,12470.0 -2012-09-10 14:00:00,12646.0 -2012-09-10 15:00:00,12845.0 -2012-09-10 16:00:00,12985.0 -2012-09-10 17:00:00,13068.0 -2012-09-10 18:00:00,13087.0 -2012-09-10 19:00:00,12899.0 -2012-09-10 20:00:00,12524.0 -2012-09-10 21:00:00,12740.0 -2012-09-10 22:00:00,12638.0 -2012-09-10 23:00:00,11952.0 -2012-09-11 00:00:00,10912.0 -2012-09-09 01:00:00,9341.0 -2012-09-09 02:00:00,8761.0 -2012-09-09 03:00:00,8402.0 -2012-09-09 04:00:00,8147.0 -2012-09-09 05:00:00,8025.0 -2012-09-09 06:00:00,7884.0 -2012-09-09 07:00:00,7989.0 -2012-09-09 08:00:00,7960.0 -2012-09-09 09:00:00,8074.0 -2012-09-09 10:00:00,8631.0 -2012-09-09 11:00:00,9080.0 -2012-09-09 12:00:00,9538.0 -2012-09-09 13:00:00,9807.0 -2012-09-09 14:00:00,9999.0 -2012-09-09 15:00:00,10052.0 -2012-09-09 16:00:00,10169.0 -2012-09-09 17:00:00,10203.0 -2012-09-09 18:00:00,10302.0 -2012-09-09 19:00:00,10287.0 -2012-09-09 20:00:00,10217.0 -2012-09-09 21:00:00,10574.0 -2012-09-09 22:00:00,10696.0 -2012-09-09 23:00:00,10286.0 -2012-09-10 00:00:00,9645.0 -2012-09-08 01:00:00,10243.0 -2012-09-08 02:00:00,9504.0 -2012-09-08 03:00:00,9019.0 -2012-09-08 04:00:00,8587.0 -2012-09-08 05:00:00,8485.0 -2012-09-08 06:00:00,8438.0 -2012-09-08 07:00:00,8633.0 -2012-09-08 08:00:00,8778.0 -2012-09-08 09:00:00,9096.0 -2012-09-08 10:00:00,9657.0 -2012-09-08 11:00:00,10164.0 -2012-09-08 12:00:00,10570.0 -2012-09-08 13:00:00,10734.0 -2012-09-08 14:00:00,10805.0 -2012-09-08 15:00:00,10792.0 -2012-09-08 16:00:00,10829.0 -2012-09-08 17:00:00,10837.0 -2012-09-08 18:00:00,10920.0 -2012-09-08 19:00:00,10754.0 -2012-09-08 20:00:00,10588.0 -2012-09-08 21:00:00,10872.0 -2012-09-08 22:00:00,10925.0 -2012-09-08 23:00:00,10609.0 -2012-09-09 00:00:00,10029.0 -2012-09-07 01:00:00,12152.0 -2012-09-07 02:00:00,11204.0 -2012-09-07 03:00:00,10625.0 -2012-09-07 04:00:00,10228.0 -2012-09-07 05:00:00,10089.0 -2012-09-07 06:00:00,10196.0 -2012-09-07 07:00:00,10827.0 -2012-09-07 08:00:00,12045.0 -2012-09-07 09:00:00,12759.0 -2012-09-07 10:00:00,13168.0 -2012-09-07 11:00:00,13330.0 -2012-09-07 12:00:00,13818.0 -2012-09-07 13:00:00,14247.0 -2012-09-07 14:00:00,14579.0 -2012-09-07 15:00:00,14991.0 -2012-09-07 16:00:00,14909.0 -2012-09-07 17:00:00,14682.0 -2012-09-07 18:00:00,14337.0 -2012-09-07 19:00:00,13848.0 -2012-09-07 20:00:00,13035.0 -2012-09-07 21:00:00,12979.0 -2012-09-07 22:00:00,12708.0 -2012-09-07 23:00:00,12097.0 -2012-09-08 00:00:00,11151.0 -2012-09-06 01:00:00,12918.0 -2012-09-06 02:00:00,11904.0 -2012-09-06 03:00:00,11206.0 -2012-09-06 04:00:00,10715.0 -2012-09-06 05:00:00,10441.0 -2012-09-06 06:00:00,10506.0 -2012-09-06 07:00:00,11121.0 -2012-09-06 08:00:00,12157.0 -2012-09-06 09:00:00,13029.0 -2012-09-06 10:00:00,13911.0 -2012-09-06 11:00:00,14814.0 -2012-09-06 12:00:00,15651.0 -2012-09-06 13:00:00,16202.0 -2012-09-06 14:00:00,16679.0 -2012-09-06 15:00:00,17177.0 -2012-09-06 16:00:00,17538.0 -2012-09-06 17:00:00,17828.0 -2012-09-06 18:00:00,17896.0 -2012-09-06 19:00:00,17514.0 -2012-09-06 20:00:00,16592.0 -2012-09-06 21:00:00,16204.0 -2012-09-06 22:00:00,15845.0 -2012-09-06 23:00:00,14850.0 -2012-09-07 00:00:00,13499.0 -2012-09-05 01:00:00,13087.0 -2012-09-05 02:00:00,11703.0 -2012-09-05 03:00:00,10892.0 -2012-09-05 04:00:00,10447.0 -2012-09-05 05:00:00,10219.0 -2012-09-05 06:00:00,10298.0 -2012-09-05 07:00:00,10962.0 -2012-09-05 08:00:00,12060.0 -2012-09-05 09:00:00,12807.0 -2012-09-05 10:00:00,13108.0 -2012-09-05 11:00:00,13422.0 -2012-09-05 12:00:00,14035.0 -2012-09-05 13:00:00,14731.0 -2012-09-05 14:00:00,15565.0 -2012-09-05 15:00:00,16403.0 -2012-09-05 16:00:00,16943.0 -2012-09-05 17:00:00,17294.0 -2012-09-05 18:00:00,17548.0 -2012-09-05 19:00:00,17512.0 -2012-09-05 20:00:00,16895.0 -2012-09-05 21:00:00,16758.0 -2012-09-05 22:00:00,16564.0 -2012-09-05 23:00:00,15651.0 -2012-09-06 00:00:00,14297.0 -2012-09-04 01:00:00,12910.0 -2012-09-04 02:00:00,12004.0 -2012-09-04 03:00:00,11415.0 -2012-09-04 04:00:00,11022.0 -2012-09-04 05:00:00,10850.0 -2012-09-04 06:00:00,10972.0 -2012-09-04 07:00:00,11728.0 -2012-09-04 08:00:00,12939.0 -2012-09-04 09:00:00,13904.0 -2012-09-04 10:00:00,14956.0 -2012-09-04 11:00:00,16023.0 -2012-09-04 12:00:00,17100.0 -2012-09-04 13:00:00,18011.0 -2012-09-04 14:00:00,18813.0 -2012-09-04 15:00:00,19496.0 -2012-09-04 16:00:00,19948.0 -2012-09-04 17:00:00,20216.0 -2012-09-04 18:00:00,20263.0 -2012-09-04 19:00:00,19827.0 -2012-09-04 20:00:00,18837.0 -2012-09-04 21:00:00,18335.0 -2012-09-04 22:00:00,17891.0 -2012-09-04 23:00:00,16773.0 -2012-09-05 00:00:00,15040.0 -2012-09-03 01:00:00,11668.0 -2012-09-03 02:00:00,10948.0 -2012-09-03 03:00:00,10401.0 -2012-09-03 04:00:00,9978.0 -2012-09-03 05:00:00,9746.0 -2012-09-03 06:00:00,9704.0 -2012-09-03 07:00:00,9825.0 -2012-09-03 08:00:00,9918.0 -2012-09-03 09:00:00,10258.0 -2012-09-03 10:00:00,11223.0 -2012-09-03 11:00:00,12367.0 -2012-09-03 12:00:00,13658.0 -2012-09-03 13:00:00,14737.0 -2012-09-03 14:00:00,15594.0 -2012-09-03 15:00:00,16268.0 -2012-09-03 16:00:00,16743.0 -2012-09-03 17:00:00,17052.0 -2012-09-03 18:00:00,17167.0 -2012-09-03 19:00:00,16970.0 -2012-09-03 20:00:00,16403.0 -2012-09-03 21:00:00,16240.0 -2012-09-03 22:00:00,16062.0 -2012-09-03 23:00:00,15224.0 -2012-09-04 00:00:00,14096.0 -2012-09-02 01:00:00,11262.0 -2012-09-02 02:00:00,10697.0 -2012-09-02 03:00:00,10231.0 -2012-09-02 04:00:00,9956.0 -2012-09-02 05:00:00,9748.0 -2012-09-02 06:00:00,9721.0 -2012-09-02 07:00:00,9777.0 -2012-09-02 08:00:00,9889.0 -2012-09-02 09:00:00,10078.0 -2012-09-02 10:00:00,10704.0 -2012-09-02 11:00:00,11303.0 -2012-09-02 12:00:00,11970.0 -2012-09-02 13:00:00,12526.0 -2012-09-02 14:00:00,13007.0 -2012-09-02 15:00:00,13464.0 -2012-09-02 16:00:00,13904.0 -2012-09-02 17:00:00,14088.0 -2012-09-02 18:00:00,14287.0 -2012-09-02 19:00:00,14157.0 -2012-09-02 20:00:00,13769.0 -2012-09-02 21:00:00,13642.0 -2012-09-02 22:00:00,13647.0 -2012-09-02 23:00:00,13191.0 -2012-09-03 00:00:00,12519.0 -2012-09-01 01:00:00,13371.0 -2012-09-01 02:00:00,12295.0 -2012-09-01 03:00:00,11555.0 -2012-09-01 04:00:00,11053.0 -2012-09-01 05:00:00,10723.0 -2012-09-01 06:00:00,10613.0 -2012-09-01 07:00:00,10744.0 -2012-09-01 08:00:00,10946.0 -2012-09-01 09:00:00,11251.0 -2012-09-01 10:00:00,11860.0 -2012-09-01 11:00:00,12362.0 -2012-09-01 12:00:00,12715.0 -2012-09-01 13:00:00,12904.0 -2012-09-01 14:00:00,12880.0 -2012-09-01 15:00:00,12780.0 -2012-09-01 16:00:00,12717.0 -2012-09-01 17:00:00,12618.0 -2012-09-01 18:00:00,12441.0 -2012-09-01 19:00:00,12253.0 -2012-09-01 20:00:00,12157.0 -2012-09-01 21:00:00,12460.0 -2012-09-01 22:00:00,12621.0 -2012-09-01 23:00:00,12394.0 -2012-09-02 00:00:00,11910.0 -2012-08-31 01:00:00,13461.0 -2012-08-31 02:00:00,12313.0 -2012-08-31 03:00:00,11473.0 -2012-08-31 04:00:00,10924.0 -2012-08-31 05:00:00,10603.0 -2012-08-31 06:00:00,10638.0 -2012-08-31 07:00:00,11237.0 -2012-08-31 08:00:00,12182.0 -2012-08-31 09:00:00,13212.0 -2012-08-31 10:00:00,14428.0 -2012-08-31 11:00:00,15821.0 -2012-08-31 12:00:00,17191.0 -2012-08-31 13:00:00,18392.0 -2012-08-31 14:00:00,19290.0 -2012-08-31 15:00:00,20015.0 -2012-08-31 16:00:00,20360.0 -2012-08-31 17:00:00,20190.0 -2012-08-31 18:00:00,19830.0 -2012-08-31 19:00:00,19181.0 -2012-08-31 20:00:00,18156.0 -2012-08-31 21:00:00,17598.0 -2012-08-31 22:00:00,17143.0 -2012-08-31 23:00:00,16076.0 -2012-09-01 00:00:00,14697.0 -2012-08-30 01:00:00,12639.0 -2012-08-30 02:00:00,11645.0 -2012-08-30 03:00:00,10929.0 -2012-08-30 04:00:00,10481.0 -2012-08-30 05:00:00,10202.0 -2012-08-30 06:00:00,10251.0 -2012-08-30 07:00:00,10877.0 -2012-08-30 08:00:00,11857.0 -2012-08-30 09:00:00,12767.0 -2012-08-30 10:00:00,13658.0 -2012-08-30 11:00:00,14608.0 -2012-08-30 12:00:00,15638.0 -2012-08-30 13:00:00,16488.0 -2012-08-30 14:00:00,17263.0 -2012-08-30 15:00:00,18074.0 -2012-08-30 16:00:00,18725.0 -2012-08-30 17:00:00,19199.0 -2012-08-30 18:00:00,19385.0 -2012-08-30 19:00:00,19110.0 -2012-08-30 20:00:00,18347.0 -2012-08-30 21:00:00,17755.0 -2012-08-30 22:00:00,17483.0 -2012-08-30 23:00:00,16457.0 -2012-08-31 00:00:00,14937.0 -2012-08-29 01:00:00,11909.0 -2012-08-29 02:00:00,11020.0 -2012-08-29 03:00:00,10413.0 -2012-08-29 04:00:00,10031.0 -2012-08-29 05:00:00,9856.0 -2012-08-29 06:00:00,9962.0 -2012-08-29 07:00:00,10631.0 -2012-08-29 08:00:00,11621.0 -2012-08-29 09:00:00,12524.0 -2012-08-29 10:00:00,13334.0 -2012-08-29 11:00:00,14061.0 -2012-08-29 12:00:00,14814.0 -2012-08-29 13:00:00,15498.0 -2012-08-29 14:00:00,16107.0 -2012-08-29 15:00:00,16784.0 -2012-08-29 16:00:00,17291.0 -2012-08-29 17:00:00,17645.0 -2012-08-29 18:00:00,17768.0 -2012-08-29 19:00:00,17480.0 -2012-08-29 20:00:00,16714.0 -2012-08-29 21:00:00,16210.0 -2012-08-29 22:00:00,16041.0 -2012-08-29 23:00:00,15182.0 -2012-08-30 00:00:00,13905.0 -2012-08-28 01:00:00,12077.0 -2012-08-28 02:00:00,11111.0 -2012-08-28 03:00:00,10445.0 -2012-08-28 04:00:00,10010.0 -2012-08-28 05:00:00,9798.0 -2012-08-28 06:00:00,9893.0 -2012-08-28 07:00:00,10533.0 -2012-08-28 08:00:00,11450.0 -2012-08-28 09:00:00,12438.0 -2012-08-28 10:00:00,13388.0 -2012-08-28 11:00:00,14224.0 -2012-08-28 12:00:00,15030.0 -2012-08-28 13:00:00,15735.0 -2012-08-28 14:00:00,16402.0 -2012-08-28 15:00:00,17045.0 -2012-08-28 16:00:00,17499.0 -2012-08-28 17:00:00,17793.0 -2012-08-28 18:00:00,17745.0 -2012-08-28 19:00:00,17197.0 -2012-08-28 20:00:00,16164.0 -2012-08-28 21:00:00,15557.0 -2012-08-28 22:00:00,15348.0 -2012-08-28 23:00:00,14439.0 -2012-08-29 00:00:00,13154.0 -2012-08-27 01:00:00,11064.0 -2012-08-27 02:00:00,10368.0 -2012-08-27 03:00:00,9977.0 -2012-08-27 04:00:00,9691.0 -2012-08-27 05:00:00,9697.0 -2012-08-27 06:00:00,9916.0 -2012-08-27 07:00:00,10704.0 -2012-08-27 08:00:00,11883.0 -2012-08-27 09:00:00,12881.0 -2012-08-27 10:00:00,13710.0 -2012-08-27 11:00:00,14599.0 -2012-08-27 12:00:00,15523.0 -2012-08-27 13:00:00,16277.0 -2012-08-27 14:00:00,16833.0 -2012-08-27 15:00:00,17278.0 -2012-08-27 16:00:00,17665.0 -2012-08-27 17:00:00,17947.0 -2012-08-27 18:00:00,18068.0 -2012-08-27 19:00:00,17746.0 -2012-08-27 20:00:00,16972.0 -2012-08-27 21:00:00,16228.0 -2012-08-27 22:00:00,15849.0 -2012-08-27 23:00:00,14815.0 -2012-08-28 00:00:00,13376.0 -2012-08-26 01:00:00,13151.0 -2012-08-26 02:00:00,12235.0 -2012-08-26 03:00:00,11474.0 -2012-08-26 04:00:00,10927.0 -2012-08-26 05:00:00,10509.0 -2012-08-26 06:00:00,10193.0 -2012-08-26 07:00:00,10157.0 -2012-08-26 08:00:00,10036.0 -2012-08-26 09:00:00,10462.0 -2012-08-26 10:00:00,11545.0 -2012-08-26 11:00:00,12407.0 -2012-08-26 12:00:00,12750.0 -2012-08-26 13:00:00,12792.0 -2012-08-26 14:00:00,12708.0 -2012-08-26 15:00:00,12730.0 -2012-08-26 16:00:00,12687.0 -2012-08-26 17:00:00,12710.0 -2012-08-26 18:00:00,12757.0 -2012-08-26 19:00:00,12867.0 -2012-08-26 20:00:00,13019.0 -2012-08-26 21:00:00,13245.0 -2012-08-26 22:00:00,13185.0 -2012-08-26 23:00:00,12680.0 -2012-08-27 00:00:00,11891.0 -2012-08-25 01:00:00,13708.0 -2012-08-25 02:00:00,12637.0 -2012-08-25 03:00:00,11836.0 -2012-08-25 04:00:00,11143.0 -2012-08-25 05:00:00,10685.0 -2012-08-25 06:00:00,10414.0 -2012-08-25 07:00:00,10483.0 -2012-08-25 08:00:00,10587.0 -2012-08-25 09:00:00,11273.0 -2012-08-25 10:00:00,12518.0 -2012-08-25 11:00:00,13910.0 -2012-08-25 12:00:00,15262.0 -2012-08-25 13:00:00,16400.0 -2012-08-25 14:00:00,17170.0 -2012-08-25 15:00:00,17545.0 -2012-08-25 16:00:00,17824.0 -2012-08-25 17:00:00,17940.0 -2012-08-25 18:00:00,17864.0 -2012-08-25 19:00:00,17553.0 -2012-08-25 20:00:00,16800.0 -2012-08-25 21:00:00,16317.0 -2012-08-25 22:00:00,16053.0 -2012-08-25 23:00:00,15346.0 -2012-08-26 00:00:00,14278.0 -2012-08-24 01:00:00,13198.0 -2012-08-24 02:00:00,12136.0 -2012-08-24 03:00:00,11373.0 -2012-08-24 04:00:00,10851.0 -2012-08-24 05:00:00,10521.0 -2012-08-24 06:00:00,10532.0 -2012-08-24 07:00:00,11088.0 -2012-08-24 08:00:00,11928.0 -2012-08-24 09:00:00,12881.0 -2012-08-24 10:00:00,13824.0 -2012-08-24 11:00:00,14855.0 -2012-08-24 12:00:00,16033.0 -2012-08-24 13:00:00,17075.0 -2012-08-24 14:00:00,17976.0 -2012-08-24 15:00:00,18804.0 -2012-08-24 16:00:00,19365.0 -2012-08-24 17:00:00,19602.0 -2012-08-24 18:00:00,19494.0 -2012-08-24 19:00:00,19045.0 -2012-08-24 20:00:00,18230.0 -2012-08-24 21:00:00,17644.0 -2012-08-24 22:00:00,17352.0 -2012-08-24 23:00:00,16445.0 -2012-08-25 00:00:00,15149.0 -2012-08-23 01:00:00,11522.0 -2012-08-23 02:00:00,10690.0 -2012-08-23 03:00:00,10142.0 -2012-08-23 04:00:00,9761.0 -2012-08-23 05:00:00,9582.0 -2012-08-23 06:00:00,9725.0 -2012-08-23 07:00:00,10369.0 -2012-08-23 08:00:00,11297.0 -2012-08-23 09:00:00,12240.0 -2012-08-23 10:00:00,12894.0 -2012-08-23 11:00:00,13412.0 -2012-08-23 12:00:00,14195.0 -2012-08-23 13:00:00,15089.0 -2012-08-23 14:00:00,16020.0 -2012-08-23 15:00:00,16915.0 -2012-08-23 16:00:00,17477.0 -2012-08-23 17:00:00,17881.0 -2012-08-23 18:00:00,18183.0 -2012-08-23 19:00:00,18082.0 -2012-08-23 20:00:00,17478.0 -2012-08-23 21:00:00,16990.0 -2012-08-23 22:00:00,16892.0 -2012-08-23 23:00:00,16027.0 -2012-08-24 00:00:00,14645.0 -2012-08-22 01:00:00,10440.0 -2012-08-22 02:00:00,9708.0 -2012-08-22 03:00:00,9229.0 -2012-08-22 04:00:00,8911.0 -2012-08-22 05:00:00,8774.0 -2012-08-22 06:00:00,8913.0 -2012-08-22 07:00:00,9517.0 -2012-08-22 08:00:00,10342.0 -2012-08-22 09:00:00,11205.0 -2012-08-22 10:00:00,11967.0 -2012-08-22 11:00:00,12664.0 -2012-08-22 12:00:00,13258.0 -2012-08-22 13:00:00,13721.0 -2012-08-22 14:00:00,14097.0 -2012-08-22 15:00:00,14588.0 -2012-08-22 16:00:00,14927.0 -2012-08-22 17:00:00,15269.0 -2012-08-22 18:00:00,15454.0 -2012-08-22 19:00:00,15373.0 -2012-08-22 20:00:00,14857.0 -2012-08-22 21:00:00,14505.0 -2012-08-22 22:00:00,14529.0 -2012-08-22 23:00:00,13856.0 -2012-08-23 00:00:00,12697.0 -2012-08-21 01:00:00,10283.0 -2012-08-21 02:00:00,9623.0 -2012-08-21 03:00:00,9170.0 -2012-08-21 04:00:00,8837.0 -2012-08-21 05:00:00,8694.0 -2012-08-21 06:00:00,8852.0 -2012-08-21 07:00:00,9437.0 -2012-08-21 08:00:00,10214.0 -2012-08-21 09:00:00,11074.0 -2012-08-21 10:00:00,11848.0 -2012-08-21 11:00:00,12438.0 -2012-08-21 12:00:00,12940.0 -2012-08-21 13:00:00,13264.0 -2012-08-21 14:00:00,13511.0 -2012-08-21 15:00:00,13837.0 -2012-08-21 16:00:00,14016.0 -2012-08-21 17:00:00,14091.0 -2012-08-21 18:00:00,14089.0 -2012-08-21 19:00:00,13906.0 -2012-08-21 20:00:00,13390.0 -2012-08-21 21:00:00,13054.0 -2012-08-21 22:00:00,13177.0 -2012-08-21 23:00:00,12584.0 -2012-08-22 00:00:00,11535.0 -2012-08-20 01:00:00,9900.0 -2012-08-20 02:00:00,9263.0 -2012-08-20 03:00:00,8899.0 -2012-08-20 04:00:00,8685.0 -2012-08-20 05:00:00,8615.0 -2012-08-20 06:00:00,8861.0 -2012-08-20 07:00:00,9474.0 -2012-08-20 08:00:00,10384.0 -2012-08-20 09:00:00,11218.0 -2012-08-20 10:00:00,12026.0 -2012-08-20 11:00:00,12567.0 -2012-08-20 12:00:00,13056.0 -2012-08-20 13:00:00,13245.0 -2012-08-20 14:00:00,13342.0 -2012-08-20 15:00:00,13439.0 -2012-08-20 16:00:00,13359.0 -2012-08-20 17:00:00,13344.0 -2012-08-20 18:00:00,13318.0 -2012-08-20 19:00:00,13120.0 -2012-08-20 20:00:00,12683.0 -2012-08-20 21:00:00,12514.0 -2012-08-20 22:00:00,12725.0 -2012-08-20 23:00:00,12191.0 -2012-08-21 00:00:00,11216.0 -2012-08-19 01:00:00,9753.0 -2012-08-19 02:00:00,9227.0 -2012-08-19 03:00:00,8726.0 -2012-08-19 04:00:00,8450.0 -2012-08-19 05:00:00,8238.0 -2012-08-19 06:00:00,8192.0 -2012-08-19 07:00:00,8228.0 -2012-08-19 08:00:00,8107.0 -2012-08-19 09:00:00,8306.0 -2012-08-19 10:00:00,8879.0 -2012-08-19 11:00:00,9394.0 -2012-08-19 12:00:00,9975.0 -2012-08-19 13:00:00,10522.0 -2012-08-19 14:00:00,10839.0 -2012-08-19 15:00:00,11087.0 -2012-08-19 16:00:00,11248.0 -2012-08-19 17:00:00,11429.0 -2012-08-19 18:00:00,11510.0 -2012-08-19 19:00:00,11543.0 -2012-08-19 20:00:00,11333.0 -2012-08-19 21:00:00,11294.0 -2012-08-19 22:00:00,11565.0 -2012-08-19 23:00:00,11305.0 -2012-08-20 00:00:00,10597.0 -2012-08-18 01:00:00,10202.0 -2012-08-18 02:00:00,9451.0 -2012-08-18 03:00:00,8970.0 -2012-08-18 04:00:00,8639.0 -2012-08-18 05:00:00,8506.0 -2012-08-18 06:00:00,8470.0 -2012-08-18 07:00:00,8611.0 -2012-08-18 08:00:00,8664.0 -2012-08-18 09:00:00,9200.0 -2012-08-18 10:00:00,9813.0 -2012-08-18 11:00:00,10409.0 -2012-08-18 12:00:00,10818.0 -2012-08-18 13:00:00,11061.0 -2012-08-18 14:00:00,11235.0 -2012-08-18 15:00:00,11255.0 -2012-08-18 16:00:00,11347.0 -2012-08-18 17:00:00,11503.0 -2012-08-18 18:00:00,11563.0 -2012-08-18 19:00:00,11508.0 -2012-08-18 20:00:00,11193.0 -2012-08-18 21:00:00,11057.0 -2012-08-18 22:00:00,11359.0 -2012-08-18 23:00:00,11025.0 -2012-08-19 00:00:00,10464.0 -2012-08-17 01:00:00,10575.0 -2012-08-17 02:00:00,9852.0 -2012-08-17 03:00:00,9323.0 -2012-08-17 04:00:00,8975.0 -2012-08-17 05:00:00,8822.0 -2012-08-17 06:00:00,8943.0 -2012-08-17 07:00:00,9461.0 -2012-08-17 08:00:00,10049.0 -2012-08-17 09:00:00,10832.0 -2012-08-17 10:00:00,11535.0 -2012-08-17 11:00:00,12055.0 -2012-08-17 12:00:00,12502.0 -2012-08-17 13:00:00,12723.0 -2012-08-17 14:00:00,12895.0 -2012-08-17 15:00:00,13087.0 -2012-08-17 16:00:00,13224.0 -2012-08-17 17:00:00,13315.0 -2012-08-17 18:00:00,13317.0 -2012-08-17 19:00:00,13077.0 -2012-08-17 20:00:00,12613.0 -2012-08-17 21:00:00,12196.0 -2012-08-17 22:00:00,12296.0 -2012-08-17 23:00:00,11879.0 -2012-08-18 00:00:00,11002.0 -2012-08-16 01:00:00,12420.0 -2012-08-16 02:00:00,11535.0 -2012-08-16 03:00:00,10868.0 -2012-08-16 04:00:00,10420.0 -2012-08-16 05:00:00,10177.0 -2012-08-16 06:00:00,10258.0 -2012-08-16 07:00:00,10867.0 -2012-08-16 08:00:00,11761.0 -2012-08-16 09:00:00,12710.0 -2012-08-16 10:00:00,13295.0 -2012-08-16 11:00:00,13470.0 -2012-08-16 12:00:00,13733.0 -2012-08-16 13:00:00,13843.0 -2012-08-16 14:00:00,14129.0 -2012-08-16 15:00:00,14349.0 -2012-08-16 16:00:00,14226.0 -2012-08-16 17:00:00,14064.0 -2012-08-16 18:00:00,13929.0 -2012-08-16 19:00:00,13714.0 -2012-08-16 20:00:00,13361.0 -2012-08-16 21:00:00,13034.0 -2012-08-16 22:00:00,13127.0 -2012-08-16 23:00:00,12590.0 -2012-08-17 00:00:00,11582.0 -2012-08-15 01:00:00,11240.0 -2012-08-15 02:00:00,10418.0 -2012-08-15 03:00:00,9865.0 -2012-08-15 04:00:00,9555.0 -2012-08-15 05:00:00,9402.0 -2012-08-15 06:00:00,9549.0 -2012-08-15 07:00:00,10167.0 -2012-08-15 08:00:00,11031.0 -2012-08-15 09:00:00,11823.0 -2012-08-15 10:00:00,12430.0 -2012-08-15 11:00:00,12935.0 -2012-08-15 12:00:00,13717.0 -2012-08-15 13:00:00,14524.0 -2012-08-15 14:00:00,15183.0 -2012-08-15 15:00:00,15795.0 -2012-08-15 16:00:00,16167.0 -2012-08-15 17:00:00,16479.0 -2012-08-15 18:00:00,16586.0 -2012-08-15 19:00:00,16425.0 -2012-08-15 20:00:00,15780.0 -2012-08-15 21:00:00,15352.0 -2012-08-15 22:00:00,15403.0 -2012-08-15 23:00:00,14765.0 -2012-08-16 00:00:00,13626.0 -2012-08-14 01:00:00,10175.0 -2012-08-14 02:00:00,9516.0 -2012-08-14 03:00:00,9118.0 -2012-08-14 04:00:00,8840.0 -2012-08-14 05:00:00,8733.0 -2012-08-14 06:00:00,8893.0 -2012-08-14 07:00:00,9435.0 -2012-08-14 08:00:00,10163.0 -2012-08-14 09:00:00,11060.0 -2012-08-14 10:00:00,11838.0 -2012-08-14 11:00:00,12523.0 -2012-08-14 12:00:00,13158.0 -2012-08-14 13:00:00,13659.0 -2012-08-14 14:00:00,14043.0 -2012-08-14 15:00:00,14408.0 -2012-08-14 16:00:00,14574.0 -2012-08-14 17:00:00,14655.0 -2012-08-14 18:00:00,14643.0 -2012-08-14 19:00:00,14419.0 -2012-08-14 20:00:00,13971.0 -2012-08-14 21:00:00,13712.0 -2012-08-14 22:00:00,13831.0 -2012-08-14 23:00:00,13317.0 -2012-08-15 00:00:00,12322.0 -2012-08-13 01:00:00,10021.0 -2012-08-13 02:00:00,9485.0 -2012-08-13 03:00:00,9147.0 -2012-08-13 04:00:00,8949.0 -2012-08-13 05:00:00,8882.0 -2012-08-13 06:00:00,9087.0 -2012-08-13 07:00:00,9784.0 -2012-08-13 08:00:00,10743.0 -2012-08-13 09:00:00,11467.0 -2012-08-13 10:00:00,11990.0 -2012-08-13 11:00:00,12418.0 -2012-08-13 12:00:00,12735.0 -2012-08-13 13:00:00,12793.0 -2012-08-13 14:00:00,12786.0 -2012-08-13 15:00:00,12769.0 -2012-08-13 16:00:00,12643.0 -2012-08-13 17:00:00,12495.0 -2012-08-13 18:00:00,12360.0 -2012-08-13 19:00:00,12200.0 -2012-08-13 20:00:00,11929.0 -2012-08-13 21:00:00,11895.0 -2012-08-13 22:00:00,12194.0 -2012-08-13 23:00:00,11825.0 -2012-08-14 00:00:00,11037.0 -2012-08-12 01:00:00,10015.0 -2012-08-12 02:00:00,9398.0 -2012-08-12 03:00:00,8901.0 -2012-08-12 04:00:00,8640.0 -2012-08-12 05:00:00,8429.0 -2012-08-12 06:00:00,8311.0 -2012-08-12 07:00:00,8283.0 -2012-08-12 08:00:00,8155.0 -2012-08-12 09:00:00,8438.0 -2012-08-12 10:00:00,8946.0 -2012-08-12 11:00:00,9442.0 -2012-08-12 12:00:00,9884.0 -2012-08-12 13:00:00,10249.0 -2012-08-12 14:00:00,10464.0 -2012-08-12 15:00:00,10630.0 -2012-08-12 16:00:00,10700.0 -2012-08-12 17:00:00,10766.0 -2012-08-12 18:00:00,10808.0 -2012-08-12 19:00:00,10879.0 -2012-08-12 20:00:00,10883.0 -2012-08-12 21:00:00,11058.0 -2012-08-12 22:00:00,11395.0 -2012-08-12 23:00:00,11231.0 -2012-08-13 00:00:00,10682.0 -2012-08-11 01:00:00,10244.0 -2012-08-11 02:00:00,9551.0 -2012-08-11 03:00:00,9081.0 -2012-08-11 04:00:00,8720.0 -2012-08-11 05:00:00,8549.0 -2012-08-11 06:00:00,8493.0 -2012-08-11 07:00:00,8618.0 -2012-08-11 08:00:00,8652.0 -2012-08-11 09:00:00,9117.0 -2012-08-11 10:00:00,9694.0 -2012-08-11 11:00:00,10323.0 -2012-08-11 12:00:00,10796.0 -2012-08-11 13:00:00,11112.0 -2012-08-11 14:00:00,11259.0 -2012-08-11 15:00:00,11409.0 -2012-08-11 16:00:00,11568.0 -2012-08-11 17:00:00,11750.0 -2012-08-11 18:00:00,11889.0 -2012-08-11 19:00:00,11916.0 -2012-08-11 20:00:00,11677.0 -2012-08-11 21:00:00,11437.0 -2012-08-11 22:00:00,11600.0 -2012-08-11 23:00:00,11382.0 -2012-08-12 00:00:00,10711.0 -2012-08-10 01:00:00,10817.0 -2012-08-10 02:00:00,10134.0 -2012-08-10 03:00:00,9680.0 -2012-08-10 04:00:00,9407.0 -2012-08-10 05:00:00,9252.0 -2012-08-10 06:00:00,9396.0 -2012-08-10 07:00:00,9925.0 -2012-08-10 08:00:00,10606.0 -2012-08-10 09:00:00,11320.0 -2012-08-10 10:00:00,11956.0 -2012-08-10 11:00:00,12409.0 -2012-08-10 12:00:00,12755.0 -2012-08-10 13:00:00,12990.0 -2012-08-10 14:00:00,13139.0 -2012-08-10 15:00:00,13271.0 -2012-08-10 16:00:00,13284.0 -2012-08-10 17:00:00,13220.0 -2012-08-10 18:00:00,12975.0 -2012-08-10 19:00:00,12654.0 -2012-08-10 20:00:00,12207.0 -2012-08-10 21:00:00,11885.0 -2012-08-10 22:00:00,12101.0 -2012-08-10 23:00:00,11840.0 -2012-08-11 00:00:00,11092.0 -2012-08-09 01:00:00,12192.0 -2012-08-09 02:00:00,11373.0 -2012-08-09 03:00:00,10823.0 -2012-08-09 04:00:00,10409.0 -2012-08-09 05:00:00,10246.0 -2012-08-09 06:00:00,10364.0 -2012-08-09 07:00:00,10982.0 -2012-08-09 08:00:00,11795.0 -2012-08-09 09:00:00,12613.0 -2012-08-09 10:00:00,13272.0 -2012-08-09 11:00:00,13744.0 -2012-08-09 12:00:00,14154.0 -2012-08-09 13:00:00,14386.0 -2012-08-09 14:00:00,14513.0 -2012-08-09 15:00:00,14649.0 -2012-08-09 16:00:00,14554.0 -2012-08-09 17:00:00,14268.0 -2012-08-09 18:00:00,13862.0 -2012-08-09 19:00:00,13454.0 -2012-08-09 20:00:00,13005.0 -2012-08-09 21:00:00,12848.0 -2012-08-09 22:00:00,12981.0 -2012-08-09 23:00:00,12586.0 -2012-08-10 00:00:00,11758.0 -2012-08-08 01:00:00,14792.0 -2012-08-08 02:00:00,13618.0 -2012-08-08 03:00:00,12801.0 -2012-08-08 04:00:00,12171.0 -2012-08-08 05:00:00,11787.0 -2012-08-08 06:00:00,11741.0 -2012-08-08 07:00:00,12197.0 -2012-08-08 08:00:00,12928.0 -2012-08-08 09:00:00,14077.0 -2012-08-08 10:00:00,15151.0 -2012-08-08 11:00:00,16024.0 -2012-08-08 12:00:00,16860.0 -2012-08-08 13:00:00,17359.0 -2012-08-08 14:00:00,17760.0 -2012-08-08 15:00:00,17803.0 -2012-08-08 16:00:00,17336.0 -2012-08-08 17:00:00,16692.0 -2012-08-08 18:00:00,15993.0 -2012-08-08 19:00:00,15387.0 -2012-08-08 20:00:00,14877.0 -2012-08-08 21:00:00,14516.0 -2012-08-08 22:00:00,14628.0 -2012-08-08 23:00:00,14159.0 -2012-08-09 00:00:00,13216.0 -2012-08-07 01:00:00,12471.0 -2012-08-07 02:00:00,11456.0 -2012-08-07 03:00:00,10762.0 -2012-08-07 04:00:00,10279.0 -2012-08-07 05:00:00,10028.0 -2012-08-07 06:00:00,10096.0 -2012-08-07 07:00:00,10606.0 -2012-08-07 08:00:00,11349.0 -2012-08-07 09:00:00,12414.0 -2012-08-07 10:00:00,13461.0 -2012-08-07 11:00:00,14499.0 -2012-08-07 12:00:00,15509.0 -2012-08-07 13:00:00,16429.0 -2012-08-07 14:00:00,17279.0 -2012-08-07 15:00:00,18395.0 -2012-08-07 16:00:00,19184.0 -2012-08-07 17:00:00,19659.0 -2012-08-07 18:00:00,19934.0 -2012-08-07 19:00:00,19989.0 -2012-08-07 20:00:00,19547.0 -2012-08-07 21:00:00,18835.0 -2012-08-07 22:00:00,18546.0 -2012-08-07 23:00:00,17784.0 -2012-08-08 00:00:00,16334.0 -2012-08-06 01:00:00,11568.0 -2012-08-06 02:00:00,10695.0 -2012-08-06 03:00:00,10157.0 -2012-08-06 04:00:00,9756.0 -2012-08-06 05:00:00,9630.0 -2012-08-06 06:00:00,9746.0 -2012-08-06 07:00:00,10297.0 -2012-08-06 08:00:00,11009.0 -2012-08-06 09:00:00,12210.0 -2012-08-06 10:00:00,13137.0 -2012-08-06 11:00:00,13960.0 -2012-08-06 12:00:00,14733.0 -2012-08-06 13:00:00,15364.0 -2012-08-06 14:00:00,15908.0 -2012-08-06 15:00:00,16497.0 -2012-08-06 16:00:00,16953.0 -2012-08-06 17:00:00,17338.0 -2012-08-06 18:00:00,17569.0 -2012-08-06 19:00:00,17571.0 -2012-08-06 20:00:00,17051.0 -2012-08-06 21:00:00,16282.0 -2012-08-06 22:00:00,15850.0 -2012-08-06 23:00:00,15088.0 -2012-08-07 00:00:00,13756.0 -2012-08-05 01:00:00,12357.0 -2012-08-05 02:00:00,11526.0 -2012-08-05 03:00:00,10995.0 -2012-08-05 04:00:00,10607.0 -2012-08-05 05:00:00,10290.0 -2012-08-05 06:00:00,10095.0 -2012-08-05 07:00:00,9889.0 -2012-08-05 08:00:00,9720.0 -2012-08-05 09:00:00,10223.0 -2012-08-05 10:00:00,11135.0 -2012-08-05 11:00:00,11982.0 -2012-08-05 12:00:00,12812.0 -2012-08-05 13:00:00,13373.0 -2012-08-05 14:00:00,13812.0 -2012-08-05 15:00:00,14172.0 -2012-08-05 16:00:00,14426.0 -2012-08-05 17:00:00,14705.0 -2012-08-05 18:00:00,14895.0 -2012-08-05 19:00:00,14916.0 -2012-08-05 20:00:00,14580.0 -2012-08-05 21:00:00,14027.0 -2012-08-05 22:00:00,13751.0 -2012-08-05 23:00:00,13419.0 -2012-08-06 00:00:00,12515.0 -2012-08-04 01:00:00,15392.0 -2012-08-04 02:00:00,14168.0 -2012-08-04 03:00:00,13303.0 -2012-08-04 04:00:00,12532.0 -2012-08-04 05:00:00,12077.0 -2012-08-04 06:00:00,11862.0 -2012-08-04 07:00:00,11874.0 -2012-08-04 08:00:00,11960.0 -2012-08-04 09:00:00,12894.0 -2012-08-04 10:00:00,14311.0 -2012-08-04 11:00:00,15831.0 -2012-08-04 12:00:00,17093.0 -2012-08-04 13:00:00,18070.0 -2012-08-04 14:00:00,18753.0 -2012-08-04 15:00:00,19271.0 -2012-08-04 16:00:00,19513.0 -2012-08-04 17:00:00,18617.0 -2012-08-04 18:00:00,16490.0 -2012-08-04 19:00:00,15274.0 -2012-08-04 20:00:00,14603.0 -2012-08-04 21:00:00,14148.0 -2012-08-04 22:00:00,14165.0 -2012-08-04 23:00:00,13945.0 -2012-08-05 00:00:00,13169.0 -2012-08-03 01:00:00,14335.0 -2012-08-03 02:00:00,13149.0 -2012-08-03 03:00:00,12336.0 -2012-08-03 04:00:00,11779.0 -2012-08-03 05:00:00,11486.0 -2012-08-03 06:00:00,11524.0 -2012-08-03 07:00:00,11985.0 -2012-08-03 08:00:00,12709.0 -2012-08-03 09:00:00,14045.0 -2012-08-03 10:00:00,15397.0 -2012-08-03 11:00:00,16684.0 -2012-08-03 12:00:00,17916.0 -2012-08-03 13:00:00,18951.0 -2012-08-03 14:00:00,19770.0 -2012-08-03 15:00:00,20415.0 -2012-08-03 16:00:00,20834.0 -2012-08-03 17:00:00,21053.0 -2012-08-03 18:00:00,21078.0 -2012-08-03 19:00:00,20709.0 -2012-08-03 20:00:00,20004.0 -2012-08-03 21:00:00,19116.0 -2012-08-03 22:00:00,18635.0 -2012-08-03 23:00:00,17957.0 -2012-08-04 00:00:00,16689.0 -2012-08-02 01:00:00,14139.0 -2012-08-02 02:00:00,12987.0 -2012-08-02 03:00:00,12191.0 -2012-08-02 04:00:00,11570.0 -2012-08-02 05:00:00,11255.0 -2012-08-02 06:00:00,11207.0 -2012-08-02 07:00:00,11622.0 -2012-08-02 08:00:00,12242.0 -2012-08-02 09:00:00,13413.0 -2012-08-02 10:00:00,14499.0 -2012-08-02 11:00:00,15359.0 -2012-08-02 12:00:00,16401.0 -2012-08-02 13:00:00,17559.0 -2012-08-02 14:00:00,18610.0 -2012-08-02 15:00:00,19603.0 -2012-08-02 16:00:00,20192.0 -2012-08-02 17:00:00,20240.0 -2012-08-02 18:00:00,19981.0 -2012-08-02 19:00:00,19800.0 -2012-08-02 20:00:00,19135.0 -2012-08-02 21:00:00,18288.0 -2012-08-02 22:00:00,17805.0 -2012-08-02 23:00:00,17333.0 -2012-08-03 00:00:00,15852.0 -2012-08-01 01:00:00,13303.0 -2012-08-01 02:00:00,12284.0 -2012-08-01 03:00:00,11497.0 -2012-08-01 04:00:00,10980.0 -2012-08-01 05:00:00,10707.0 -2012-08-01 06:00:00,10796.0 -2012-08-01 07:00:00,11314.0 -2012-08-01 08:00:00,12066.0 -2012-08-01 09:00:00,13280.0 -2012-08-01 10:00:00,14433.0 -2012-08-01 11:00:00,15381.0 -2012-08-01 12:00:00,16445.0 -2012-08-01 13:00:00,17277.0 -2012-08-01 14:00:00,17983.0 -2012-08-01 15:00:00,18601.0 -2012-08-01 16:00:00,19114.0 -2012-08-01 17:00:00,19507.0 -2012-08-01 18:00:00,19743.0 -2012-08-01 19:00:00,19632.0 -2012-08-01 20:00:00,19067.0 -2012-08-01 21:00:00,18215.0 -2012-08-01 22:00:00,17785.0 -2012-08-01 23:00:00,17029.0 -2012-08-02 00:00:00,15604.0 -2012-07-31 01:00:00,15400.0 -2012-07-31 02:00:00,14175.0 -2012-07-31 03:00:00,13299.0 -2012-07-31 04:00:00,12707.0 -2012-07-31 05:00:00,12107.0 -2012-07-31 06:00:00,11890.0 -2012-07-31 07:00:00,12251.0 -2012-07-31 08:00:00,12927.0 -2012-07-31 09:00:00,14120.0 -2012-07-31 10:00:00,15234.0 -2012-07-31 11:00:00,16237.0 -2012-07-31 12:00:00,17020.0 -2012-07-31 13:00:00,17713.0 -2012-07-31 14:00:00,18326.0 -2012-07-31 15:00:00,18953.0 -2012-07-31 16:00:00,19144.0 -2012-07-31 17:00:00,19258.0 -2012-07-31 18:00:00,19242.0 -2012-07-31 19:00:00,18940.0 -2012-07-31 20:00:00,18196.0 -2012-07-31 21:00:00,17174.0 -2012-07-31 22:00:00,16634.0 -2012-07-31 23:00:00,15963.0 -2012-08-01 00:00:00,14721.0 -2012-07-30 01:00:00,12343.0 -2012-07-30 02:00:00,11612.0 -2012-07-30 03:00:00,10946.0 -2012-07-30 04:00:00,10610.0 -2012-07-30 05:00:00,10446.0 -2012-07-30 06:00:00,10652.0 -2012-07-30 07:00:00,11182.0 -2012-07-30 08:00:00,11983.0 -2012-07-30 09:00:00,13123.0 -2012-07-30 10:00:00,14373.0 -2012-07-30 11:00:00,15638.0 -2012-07-30 12:00:00,16961.0 -2012-07-30 13:00:00,18111.0 -2012-07-30 14:00:00,18949.0 -2012-07-30 15:00:00,19635.0 -2012-07-30 16:00:00,20072.0 -2012-07-30 17:00:00,20295.0 -2012-07-30 18:00:00,20481.0 -2012-07-30 19:00:00,20473.0 -2012-07-30 20:00:00,20010.0 -2012-07-30 21:00:00,19329.0 -2012-07-30 22:00:00,19028.0 -2012-07-30 23:00:00,18307.0 -2012-07-31 00:00:00,16845.0 -2012-07-29 01:00:00,11669.0 -2012-07-29 02:00:00,10867.0 -2012-07-29 03:00:00,10235.0 -2012-07-29 04:00:00,9825.0 -2012-07-29 05:00:00,9467.0 -2012-07-29 06:00:00,9285.0 -2012-07-29 07:00:00,9176.0 -2012-07-29 08:00:00,9089.0 -2012-07-29 09:00:00,9625.0 -2012-07-29 10:00:00,10490.0 -2012-07-29 11:00:00,11511.0 -2012-07-29 12:00:00,12590.0 -2012-07-29 13:00:00,13546.0 -2012-07-29 14:00:00,14173.0 -2012-07-29 15:00:00,14611.0 -2012-07-29 16:00:00,14674.0 -2012-07-29 17:00:00,14438.0 -2012-07-29 18:00:00,14141.0 -2012-07-29 19:00:00,14022.0 -2012-07-29 20:00:00,13946.0 -2012-07-29 21:00:00,13790.0 -2012-07-29 22:00:00,14040.0 -2012-07-29 23:00:00,13908.0 -2012-07-30 00:00:00,13263.0 -2012-07-28 01:00:00,12442.0 -2012-07-28 02:00:00,11508.0 -2012-07-28 03:00:00,10857.0 -2012-07-28 04:00:00,10322.0 -2012-07-28 05:00:00,10074.0 -2012-07-28 06:00:00,9967.0 -2012-07-28 07:00:00,10029.0 -2012-07-28 08:00:00,10218.0 -2012-07-28 09:00:00,11073.0 -2012-07-28 10:00:00,12091.0 -2012-07-28 11:00:00,13012.0 -2012-07-28 12:00:00,13828.0 -2012-07-28 13:00:00,14341.0 -2012-07-28 14:00:00,14732.0 -2012-07-28 15:00:00,14988.0 -2012-07-28 16:00:00,15227.0 -2012-07-28 17:00:00,15421.0 -2012-07-28 18:00:00,15455.0 -2012-07-28 19:00:00,15206.0 -2012-07-28 20:00:00,14804.0 -2012-07-28 21:00:00,14109.0 -2012-07-28 22:00:00,13825.0 -2012-07-28 23:00:00,13472.0 -2012-07-29 00:00:00,12587.0 -2012-07-27 01:00:00,13604.0 -2012-07-27 02:00:00,12544.0 -2012-07-27 03:00:00,11768.0 -2012-07-27 04:00:00,11206.0 -2012-07-27 05:00:00,10948.0 -2012-07-27 06:00:00,10979.0 -2012-07-27 07:00:00,11429.0 -2012-07-27 08:00:00,12105.0 -2012-07-27 09:00:00,13030.0 -2012-07-27 10:00:00,13697.0 -2012-07-27 11:00:00,14428.0 -2012-07-27 12:00:00,15347.0 -2012-07-27 13:00:00,16216.0 -2012-07-27 14:00:00,16791.0 -2012-07-27 15:00:00,17244.0 -2012-07-27 16:00:00,17586.0 -2012-07-27 17:00:00,17674.0 -2012-07-27 18:00:00,17476.0 -2012-07-27 19:00:00,16929.0 -2012-07-27 20:00:00,16141.0 -2012-07-27 21:00:00,15308.0 -2012-07-27 22:00:00,14923.0 -2012-07-27 23:00:00,14531.0 -2012-07-28 00:00:00,13476.0 -2012-07-26 01:00:00,17368.0 -2012-07-26 02:00:00,16170.0 -2012-07-26 03:00:00,15370.0 -2012-07-26 04:00:00,14648.0 -2012-07-26 05:00:00,13951.0 -2012-07-26 06:00:00,13450.0 -2012-07-26 07:00:00,13652.0 -2012-07-26 08:00:00,14318.0 -2012-07-26 09:00:00,15015.0 -2012-07-26 10:00:00,15534.0 -2012-07-26 11:00:00,15920.0 -2012-07-26 12:00:00,16420.0 -2012-07-26 13:00:00,16860.0 -2012-07-26 14:00:00,17696.0 -2012-07-26 15:00:00,18362.0 -2012-07-26 16:00:00,18769.0 -2012-07-26 17:00:00,19293.0 -2012-07-26 18:00:00,19561.0 -2012-07-26 19:00:00,19046.0 -2012-07-26 20:00:00,18082.0 -2012-07-26 21:00:00,17272.0 -2012-07-26 22:00:00,16716.0 -2012-07-26 23:00:00,16256.0 -2012-07-27 00:00:00,14983.0 -2012-07-25 01:00:00,13388.0 -2012-07-25 02:00:00,12461.0 -2012-07-25 03:00:00,11800.0 -2012-07-25 04:00:00,11307.0 -2012-07-25 05:00:00,11081.0 -2012-07-25 06:00:00,11151.0 -2012-07-25 07:00:00,11642.0 -2012-07-25 08:00:00,12508.0 -2012-07-25 09:00:00,13478.0 -2012-07-25 10:00:00,14556.0 -2012-07-25 11:00:00,15656.0 -2012-07-25 12:00:00,17089.0 -2012-07-25 13:00:00,18377.0 -2012-07-25 14:00:00,19668.0 -2012-07-25 15:00:00,20855.0 -2012-07-25 16:00:00,21714.0 -2012-07-25 17:00:00,22311.0 -2012-07-25 18:00:00,22505.0 -2012-07-25 19:00:00,22431.0 -2012-07-25 20:00:00,21984.0 -2012-07-25 21:00:00,21264.0 -2012-07-25 22:00:00,20840.0 -2012-07-25 23:00:00,20259.0 -2012-07-26 00:00:00,18851.0 -2012-07-24 01:00:00,16872.0 -2012-07-24 02:00:00,15789.0 -2012-07-24 03:00:00,14908.0 -2012-07-24 04:00:00,14313.0 -2012-07-24 05:00:00,13947.0 -2012-07-24 06:00:00,13882.0 -2012-07-24 07:00:00,14229.0 -2012-07-24 08:00:00,14441.0 -2012-07-24 09:00:00,14537.0 -2012-07-24 10:00:00,14748.0 -2012-07-24 11:00:00,15223.0 -2012-07-24 12:00:00,15788.0 -2012-07-24 13:00:00,16325.0 -2012-07-24 14:00:00,16680.0 -2012-07-24 15:00:00,17217.0 -2012-07-24 16:00:00,17642.0 -2012-07-24 17:00:00,17794.0 -2012-07-24 18:00:00,17976.0 -2012-07-24 19:00:00,17872.0 -2012-07-24 20:00:00,17283.0 -2012-07-24 21:00:00,16406.0 -2012-07-24 22:00:00,16040.0 -2012-07-24 23:00:00,15655.0 -2012-07-25 00:00:00,14594.0 -2012-07-23 01:00:00,14096.0 -2012-07-23 02:00:00,13273.0 -2012-07-23 03:00:00,12710.0 -2012-07-23 04:00:00,12365.0 -2012-07-23 05:00:00,12162.0 -2012-07-23 06:00:00,12345.0 -2012-07-23 07:00:00,12977.0 -2012-07-23 08:00:00,13895.0 -2012-07-23 09:00:00,15244.0 -2012-07-23 10:00:00,16585.0 -2012-07-23 11:00:00,18014.0 -2012-07-23 12:00:00,19267.0 -2012-07-23 13:00:00,20194.0 -2012-07-23 14:00:00,20929.0 -2012-07-23 15:00:00,21273.0 -2012-07-23 16:00:00,21651.0 -2012-07-23 17:00:00,21876.0 -2012-07-23 18:00:00,21933.0 -2012-07-23 19:00:00,21611.0 -2012-07-23 20:00:00,20917.0 -2012-07-23 21:00:00,20423.0 -2012-07-23 22:00:00,20263.0 -2012-07-23 23:00:00,19640.0 -2012-07-24 00:00:00,18301.0 -2012-07-22 01:00:00,13711.0 -2012-07-22 02:00:00,12688.0 -2012-07-22 03:00:00,11983.0 -2012-07-22 04:00:00,11368.0 -2012-07-22 05:00:00,10945.0 -2012-07-22 06:00:00,10642.0 -2012-07-22 07:00:00,10341.0 -2012-07-22 08:00:00,10417.0 -2012-07-22 09:00:00,11080.0 -2012-07-22 10:00:00,11989.0 -2012-07-22 11:00:00,12980.0 -2012-07-22 12:00:00,13596.0 -2012-07-22 13:00:00,13976.0 -2012-07-22 14:00:00,14301.0 -2012-07-22 15:00:00,14650.0 -2012-07-22 16:00:00,14746.0 -2012-07-22 17:00:00,14989.0 -2012-07-22 18:00:00,15823.0 -2012-07-22 19:00:00,16407.0 -2012-07-22 20:00:00,16549.0 -2012-07-22 21:00:00,16189.0 -2012-07-22 22:00:00,16060.0 -2012-07-22 23:00:00,15892.0 -2012-07-23 00:00:00,15072.0 -2012-07-21 01:00:00,12120.0 -2012-07-21 02:00:00,11196.0 -2012-07-21 03:00:00,10539.0 -2012-07-21 04:00:00,10102.0 -2012-07-21 05:00:00,9864.0 -2012-07-21 06:00:00,9751.0 -2012-07-21 07:00:00,9772.0 -2012-07-21 08:00:00,10023.0 -2012-07-21 09:00:00,10870.0 -2012-07-21 10:00:00,11926.0 -2012-07-21 11:00:00,12995.0 -2012-07-21 12:00:00,14065.0 -2012-07-21 13:00:00,14963.0 -2012-07-21 14:00:00,15709.0 -2012-07-21 15:00:00,16274.0 -2012-07-21 16:00:00,16575.0 -2012-07-21 17:00:00,16619.0 -2012-07-21 18:00:00,16662.0 -2012-07-21 19:00:00,16425.0 -2012-07-21 20:00:00,16124.0 -2012-07-21 21:00:00,15816.0 -2012-07-21 22:00:00,15725.0 -2012-07-21 23:00:00,15572.0 -2012-07-22 00:00:00,14714.0 -2012-07-20 01:00:00,13141.0 -2012-07-20 02:00:00,12183.0 -2012-07-20 03:00:00,11581.0 -2012-07-20 04:00:00,11116.0 -2012-07-20 05:00:00,10913.0 -2012-07-20 06:00:00,11010.0 -2012-07-20 07:00:00,11534.0 -2012-07-20 08:00:00,12266.0 -2012-07-20 09:00:00,13041.0 -2012-07-20 10:00:00,13732.0 -2012-07-20 11:00:00,14473.0 -2012-07-20 12:00:00,15261.0 -2012-07-20 13:00:00,15785.0 -2012-07-20 14:00:00,16183.0 -2012-07-20 15:00:00,16519.0 -2012-07-20 16:00:00,16659.0 -2012-07-20 17:00:00,16856.0 -2012-07-20 18:00:00,16895.0 -2012-07-20 19:00:00,16601.0 -2012-07-20 20:00:00,15848.0 -2012-07-20 21:00:00,14987.0 -2012-07-20 22:00:00,14423.0 -2012-07-20 23:00:00,14090.0 -2012-07-21 00:00:00,13106.0 -2012-07-19 01:00:00,14568.0 -2012-07-19 02:00:00,13599.0 -2012-07-19 03:00:00,12741.0 -2012-07-19 04:00:00,12287.0 -2012-07-19 05:00:00,12046.0 -2012-07-19 06:00:00,12086.0 -2012-07-19 07:00:00,12651.0 -2012-07-19 08:00:00,13463.0 -2012-07-19 09:00:00,14308.0 -2012-07-19 10:00:00,15084.0 -2012-07-19 11:00:00,15734.0 -2012-07-19 12:00:00,16666.0 -2012-07-19 13:00:00,17319.0 -2012-07-19 14:00:00,17700.0 -2012-07-19 15:00:00,17982.0 -2012-07-19 16:00:00,18027.0 -2012-07-19 17:00:00,18213.0 -2012-07-19 18:00:00,18205.0 -2012-07-19 19:00:00,17725.0 -2012-07-19 20:00:00,16889.0 -2012-07-19 21:00:00,16190.0 -2012-07-19 22:00:00,15878.0 -2012-07-19 23:00:00,15419.0 -2012-07-20 00:00:00,14334.0 -2012-07-18 01:00:00,17414.0 -2012-07-18 02:00:00,16104.0 -2012-07-18 03:00:00,15210.0 -2012-07-18 04:00:00,14448.0 -2012-07-18 05:00:00,13940.0 -2012-07-18 06:00:00,13829.0 -2012-07-18 07:00:00,14131.0 -2012-07-18 08:00:00,14913.0 -2012-07-18 09:00:00,16194.0 -2012-07-18 10:00:00,17182.0 -2012-07-18 11:00:00,18058.0 -2012-07-18 12:00:00,18943.0 -2012-07-18 13:00:00,19767.0 -2012-07-18 14:00:00,20552.0 -2012-07-18 15:00:00,21034.0 -2012-07-18 16:00:00,21242.0 -2012-07-18 17:00:00,21434.0 -2012-07-18 18:00:00,21471.0 -2012-07-18 19:00:00,21149.0 -2012-07-18 20:00:00,20389.0 -2012-07-18 21:00:00,19451.0 -2012-07-18 22:00:00,18877.0 -2012-07-18 23:00:00,17931.0 -2012-07-19 00:00:00,15898.0 -2012-07-17 01:00:00,17035.0 -2012-07-17 02:00:00,15850.0 -2012-07-17 03:00:00,14963.0 -2012-07-17 04:00:00,14308.0 -2012-07-17 05:00:00,13882.0 -2012-07-17 06:00:00,13802.0 -2012-07-17 07:00:00,14121.0 -2012-07-17 08:00:00,15111.0 -2012-07-17 09:00:00,16691.0 -2012-07-17 10:00:00,18215.0 -2012-07-17 11:00:00,19653.0 -2012-07-17 12:00:00,20927.0 -2012-07-17 13:00:00,21883.0 -2012-07-17 14:00:00,22450.0 -2012-07-17 15:00:00,22915.0 -2012-07-17 16:00:00,23138.0 -2012-07-17 17:00:00,23269.0 -2012-07-17 18:00:00,23265.0 -2012-07-17 19:00:00,23064.0 -2012-07-17 20:00:00,22569.0 -2012-07-17 21:00:00,21902.0 -2012-07-17 22:00:00,21351.0 -2012-07-17 23:00:00,20605.0 -2012-07-18 00:00:00,19005.0 -2012-07-16 01:00:00,14692.0 -2012-07-16 02:00:00,13605.0 -2012-07-16 03:00:00,12892.0 -2012-07-16 04:00:00,12330.0 -2012-07-16 05:00:00,12035.0 -2012-07-16 06:00:00,12120.0 -2012-07-16 07:00:00,12552.0 -2012-07-16 08:00:00,13620.0 -2012-07-16 09:00:00,15209.0 -2012-07-16 10:00:00,16855.0 -2012-07-16 11:00:00,18204.0 -2012-07-16 12:00:00,19525.0 -2012-07-16 13:00:00,20504.0 -2012-07-16 14:00:00,21192.0 -2012-07-16 15:00:00,21695.0 -2012-07-16 16:00:00,22014.0 -2012-07-16 17:00:00,22301.0 -2012-07-16 18:00:00,22350.0 -2012-07-16 19:00:00,22233.0 -2012-07-16 20:00:00,21804.0 -2012-07-16 21:00:00,21051.0 -2012-07-16 22:00:00,20461.0 -2012-07-16 23:00:00,19914.0 -2012-07-17 00:00:00,18492.0 -2012-07-15 01:00:00,13066.0 -2012-07-15 02:00:00,12103.0 -2012-07-15 03:00:00,11448.0 -2012-07-15 04:00:00,10857.0 -2012-07-15 05:00:00,10456.0 -2012-07-15 06:00:00,10226.0 -2012-07-15 07:00:00,10002.0 -2012-07-15 08:00:00,10136.0 -2012-07-15 09:00:00,10984.0 -2012-07-15 10:00:00,12312.0 -2012-07-15 11:00:00,13778.0 -2012-07-15 12:00:00,15200.0 -2012-07-15 13:00:00,16275.0 -2012-07-15 14:00:00,16972.0 -2012-07-15 15:00:00,17544.0 -2012-07-15 16:00:00,18061.0 -2012-07-15 17:00:00,18404.0 -2012-07-15 18:00:00,18612.0 -2012-07-15 19:00:00,18659.0 -2012-07-15 20:00:00,18338.0 -2012-07-15 21:00:00,17735.0 -2012-07-15 22:00:00,17407.0 -2012-07-15 23:00:00,17034.0 -2012-07-16 00:00:00,15941.0 -2012-07-14 01:00:00,13413.0 -2012-07-14 02:00:00,12411.0 -2012-07-14 03:00:00,11738.0 -2012-07-14 04:00:00,11206.0 -2012-07-14 05:00:00,10888.0 -2012-07-14 06:00:00,10765.0 -2012-07-14 07:00:00,10782.0 -2012-07-14 08:00:00,11083.0 -2012-07-14 09:00:00,11951.0 -2012-07-14 10:00:00,13080.0 -2012-07-14 11:00:00,14250.0 -2012-07-14 12:00:00,15389.0 -2012-07-14 13:00:00,16279.0 -2012-07-14 14:00:00,16926.0 -2012-07-14 15:00:00,16852.0 -2012-07-14 16:00:00,16524.0 -2012-07-14 17:00:00,16380.0 -2012-07-14 18:00:00,16347.0 -2012-07-14 19:00:00,16379.0 -2012-07-14 20:00:00,16158.0 -2012-07-14 21:00:00,15703.0 -2012-07-14 22:00:00,15241.0 -2012-07-14 23:00:00,14986.0 -2012-07-15 00:00:00,14092.0 -2012-07-13 01:00:00,13981.0 -2012-07-13 02:00:00,12935.0 -2012-07-13 03:00:00,12125.0 -2012-07-13 04:00:00,11560.0 -2012-07-13 05:00:00,11236.0 -2012-07-13 06:00:00,11261.0 -2012-07-13 07:00:00,11594.0 -2012-07-13 08:00:00,12451.0 -2012-07-13 09:00:00,13806.0 -2012-07-13 10:00:00,15175.0 -2012-07-13 11:00:00,16463.0 -2012-07-13 12:00:00,17881.0 -2012-07-13 13:00:00,18898.0 -2012-07-13 14:00:00,19436.0 -2012-07-13 15:00:00,19407.0 -2012-07-13 16:00:00,18955.0 -2012-07-13 17:00:00,18555.0 -2012-07-13 18:00:00,18013.0 -2012-07-13 19:00:00,17160.0 -2012-07-13 20:00:00,16351.0 -2012-07-13 21:00:00,15809.0 -2012-07-13 22:00:00,15644.0 -2012-07-13 23:00:00,15460.0 -2012-07-14 00:00:00,14484.0 -2012-07-12 01:00:00,13168.0 -2012-07-12 02:00:00,12146.0 -2012-07-12 03:00:00,11376.0 -2012-07-12 04:00:00,10871.0 -2012-07-12 05:00:00,10550.0 -2012-07-12 06:00:00,10594.0 -2012-07-12 07:00:00,10960.0 -2012-07-12 08:00:00,11815.0 -2012-07-12 09:00:00,12969.0 -2012-07-12 10:00:00,14065.0 -2012-07-12 11:00:00,14987.0 -2012-07-12 12:00:00,16005.0 -2012-07-12 13:00:00,16667.0 -2012-07-12 14:00:00,17691.0 -2012-07-12 15:00:00,18299.0 -2012-07-12 16:00:00,18750.0 -2012-07-12 17:00:00,19004.0 -2012-07-12 18:00:00,19104.0 -2012-07-12 19:00:00,18909.0 -2012-07-12 20:00:00,18324.0 -2012-07-12 21:00:00,17523.0 -2012-07-12 22:00:00,16982.0 -2012-07-12 23:00:00,16568.0 -2012-07-13 00:00:00,15320.0 -2012-07-11 01:00:00,12119.0 -2012-07-11 02:00:00,11240.0 -2012-07-11 03:00:00,10616.0 -2012-07-11 04:00:00,10169.0 -2012-07-11 05:00:00,9981.0 -2012-07-11 06:00:00,10119.0 -2012-07-11 07:00:00,10476.0 -2012-07-11 08:00:00,11435.0 -2012-07-11 09:00:00,12626.0 -2012-07-11 10:00:00,13650.0 -2012-07-11 11:00:00,14511.0 -2012-07-11 12:00:00,15385.0 -2012-07-11 13:00:00,16109.0 -2012-07-11 14:00:00,16674.0 -2012-07-11 15:00:00,17264.0 -2012-07-11 16:00:00,17624.0 -2012-07-11 17:00:00,17896.0 -2012-07-11 18:00:00,18029.0 -2012-07-11 19:00:00,17874.0 -2012-07-11 20:00:00,17251.0 -2012-07-11 21:00:00,16528.0 -2012-07-11 22:00:00,15975.0 -2012-07-11 23:00:00,15587.0 -2012-07-12 00:00:00,14436.0 -2012-07-10 01:00:00,12978.0 -2012-07-10 02:00:00,12031.0 -2012-07-10 03:00:00,11354.0 -2012-07-10 04:00:00,10912.0 -2012-07-10 05:00:00,10628.0 -2012-07-10 06:00:00,10737.0 -2012-07-10 07:00:00,11150.0 -2012-07-10 08:00:00,12119.0 -2012-07-10 09:00:00,13261.0 -2012-07-10 10:00:00,14105.0 -2012-07-10 11:00:00,14879.0 -2012-07-10 12:00:00,15564.0 -2012-07-10 13:00:00,16107.0 -2012-07-10 14:00:00,16392.0 -2012-07-10 15:00:00,16548.0 -2012-07-10 16:00:00,16538.0 -2012-07-10 17:00:00,16535.0 -2012-07-10 18:00:00,16475.0 -2012-07-10 19:00:00,16289.0 -2012-07-10 20:00:00,15693.0 -2012-07-10 21:00:00,15016.0 -2012-07-10 22:00:00,14599.0 -2012-07-10 23:00:00,14367.0 -2012-07-11 00:00:00,13283.0 -2012-07-09 01:00:00,12154.0 -2012-07-09 02:00:00,11344.0 -2012-07-09 03:00:00,10736.0 -2012-07-09 04:00:00,10385.0 -2012-07-09 05:00:00,10193.0 -2012-07-09 06:00:00,10367.0 -2012-07-09 07:00:00,10701.0 -2012-07-09 08:00:00,11722.0 -2012-07-09 09:00:00,13045.0 -2012-07-09 10:00:00,14170.0 -2012-07-09 11:00:00,15220.0 -2012-07-09 12:00:00,16275.0 -2012-07-09 13:00:00,17194.0 -2012-07-09 14:00:00,17928.0 -2012-07-09 15:00:00,18492.0 -2012-07-09 16:00:00,18567.0 -2012-07-09 17:00:00,18425.0 -2012-07-09 18:00:00,18134.0 -2012-07-09 19:00:00,17533.0 -2012-07-09 20:00:00,16747.0 -2012-07-09 21:00:00,16121.0 -2012-07-09 22:00:00,15665.0 -2012-07-09 23:00:00,15339.0 -2012-07-10 00:00:00,14215.0 -2012-07-08 01:00:00,12912.0 -2012-07-08 02:00:00,12102.0 -2012-07-08 03:00:00,11518.0 -2012-07-08 04:00:00,11126.0 -2012-07-08 05:00:00,10788.0 -2012-07-08 06:00:00,10690.0 -2012-07-08 07:00:00,10527.0 -2012-07-08 08:00:00,10524.0 -2012-07-08 09:00:00,10920.0 -2012-07-08 10:00:00,11676.0 -2012-07-08 11:00:00,12497.0 -2012-07-08 12:00:00,13233.0 -2012-07-08 13:00:00,13738.0 -2012-07-08 14:00:00,14123.0 -2012-07-08 15:00:00,14378.0 -2012-07-08 16:00:00,14685.0 -2012-07-08 17:00:00,14988.0 -2012-07-08 18:00:00,15172.0 -2012-07-08 19:00:00,15180.0 -2012-07-08 20:00:00,14983.0 -2012-07-08 21:00:00,14443.0 -2012-07-08 22:00:00,14054.0 -2012-07-08 23:00:00,13932.0 -2012-07-09 00:00:00,13159.0 -2012-07-07 01:00:00,18165.0 -2012-07-07 02:00:00,16808.0 -2012-07-07 03:00:00,15853.0 -2012-07-07 04:00:00,15051.0 -2012-07-07 05:00:00,14511.0 -2012-07-07 06:00:00,14139.0 -2012-07-07 07:00:00,13916.0 -2012-07-07 08:00:00,14187.0 -2012-07-07 09:00:00,15446.0 -2012-07-07 10:00:00,16988.0 -2012-07-07 11:00:00,18441.0 -2012-07-07 12:00:00,19534.0 -2012-07-07 13:00:00,20109.0 -2012-07-07 14:00:00,20181.0 -2012-07-07 15:00:00,20056.0 -2012-07-07 16:00:00,19873.0 -2012-07-07 17:00:00,19603.0 -2012-07-07 18:00:00,19096.0 -2012-07-07 19:00:00,18075.0 -2012-07-07 20:00:00,16970.0 -2012-07-07 21:00:00,15852.0 -2012-07-07 22:00:00,15220.0 -2012-07-07 23:00:00,14728.0 -2012-07-08 00:00:00,13808.0 -2012-07-06 01:00:00,18076.0 -2012-07-06 02:00:00,16934.0 -2012-07-06 03:00:00,16045.0 -2012-07-06 04:00:00,15354.0 -2012-07-06 05:00:00,14876.0 -2012-07-06 06:00:00,14741.0 -2012-07-06 07:00:00,14903.0 -2012-07-06 08:00:00,15739.0 -2012-07-06 09:00:00,17176.0 -2012-07-06 10:00:00,18657.0 -2012-07-06 11:00:00,20078.0 -2012-07-06 12:00:00,21449.0 -2012-07-06 13:00:00,22430.0 -2012-07-06 14:00:00,23022.0 -2012-07-06 15:00:00,23376.0 -2012-07-06 16:00:00,23533.0 -2012-07-06 17:00:00,23603.0 -2012-07-06 18:00:00,23516.0 -2012-07-06 19:00:00,23230.0 -2012-07-06 20:00:00,22755.0 -2012-07-06 21:00:00,22117.0 -2012-07-06 22:00:00,21547.0 -2012-07-06 23:00:00,21011.0 -2012-07-07 00:00:00,19604.0 -2012-07-05 01:00:00,16796.0 -2012-07-05 02:00:00,15696.0 -2012-07-05 03:00:00,14751.0 -2012-07-05 04:00:00,14033.0 -2012-07-05 05:00:00,13546.0 -2012-07-05 06:00:00,13445.0 -2012-07-05 07:00:00,13637.0 -2012-07-05 08:00:00,14569.0 -2012-07-05 09:00:00,16119.0 -2012-07-05 10:00:00,17657.0 -2012-07-05 11:00:00,19115.0 -2012-07-05 12:00:00,20545.0 -2012-07-05 13:00:00,21708.0 -2012-07-05 14:00:00,22511.0 -2012-07-05 15:00:00,23046.0 -2012-07-05 16:00:00,23134.0 -2012-07-05 17:00:00,22647.0 -2012-07-05 18:00:00,22307.0 -2012-07-05 19:00:00,22213.0 -2012-07-05 20:00:00,22023.0 -2012-07-05 21:00:00,21413.0 -2012-07-05 22:00:00,21077.0 -2012-07-05 23:00:00,20684.0 -2012-07-06 00:00:00,19429.0 -2012-07-04 01:00:00,16751.0 -2012-07-04 02:00:00,15558.0 -2012-07-04 03:00:00,14594.0 -2012-07-04 04:00:00,13835.0 -2012-07-04 05:00:00,13330.0 -2012-07-04 06:00:00,13027.0 -2012-07-04 07:00:00,12770.0 -2012-07-04 08:00:00,12903.0 -2012-07-04 09:00:00,13794.0 -2012-07-04 10:00:00,15120.0 -2012-07-04 11:00:00,16659.0 -2012-07-04 12:00:00,18118.0 -2012-07-04 13:00:00,19219.0 -2012-07-04 14:00:00,19946.0 -2012-07-04 15:00:00,20355.0 -2012-07-04 16:00:00,20644.0 -2012-07-04 17:00:00,20769.0 -2012-07-04 18:00:00,20784.0 -2012-07-04 19:00:00,20646.0 -2012-07-04 20:00:00,20155.0 -2012-07-04 21:00:00,19534.0 -2012-07-04 22:00:00,19072.0 -2012-07-04 23:00:00,18661.0 -2012-07-05 00:00:00,17792.0 -2012-07-03 01:00:00,16861.0 -2012-07-03 02:00:00,15562.0 -2012-07-03 03:00:00,14516.0 -2012-07-03 04:00:00,13655.0 -2012-07-03 05:00:00,13132.0 -2012-07-03 06:00:00,12958.0 -2012-07-03 07:00:00,13123.0 -2012-07-03 08:00:00,13883.0 -2012-07-03 09:00:00,15026.0 -2012-07-03 10:00:00,16302.0 -2012-07-03 11:00:00,17422.0 -2012-07-03 12:00:00,18348.0 -2012-07-03 13:00:00,19266.0 -2012-07-03 14:00:00,20248.0 -2012-07-03 15:00:00,21101.0 -2012-07-03 16:00:00,21579.0 -2012-07-03 17:00:00,21837.0 -2012-07-03 18:00:00,21903.0 -2012-07-03 19:00:00,21721.0 -2012-07-03 20:00:00,21263.0 -2012-07-03 21:00:00,20526.0 -2012-07-03 22:00:00,19874.0 -2012-07-03 23:00:00,19313.0 -2012-07-04 00:00:00,18078.0 -2012-07-02 01:00:00,13413.0 -2012-07-02 02:00:00,12475.0 -2012-07-02 03:00:00,11815.0 -2012-07-02 04:00:00,11329.0 -2012-07-02 05:00:00,11088.0 -2012-07-02 06:00:00,11181.0 -2012-07-02 07:00:00,11558.0 -2012-07-02 08:00:00,12620.0 -2012-07-02 09:00:00,14114.0 -2012-07-02 10:00:00,15629.0 -2012-07-02 11:00:00,16926.0 -2012-07-02 12:00:00,18324.0 -2012-07-02 13:00:00,19402.0 -2012-07-02 14:00:00,20253.0 -2012-07-02 15:00:00,20891.0 -2012-07-02 16:00:00,21280.0 -2012-07-02 17:00:00,21537.0 -2012-07-02 18:00:00,21672.0 -2012-07-02 19:00:00,21567.0 -2012-07-02 20:00:00,21020.0 -2012-07-02 21:00:00,20345.0 -2012-07-02 22:00:00,19957.0 -2012-07-02 23:00:00,19540.0 -2012-07-03 00:00:00,18287.0 -2012-07-01 01:00:00,14048.0 -2012-07-01 02:00:00,12982.0 -2012-07-01 03:00:00,12215.0 -2012-07-01 04:00:00,11528.0 -2012-07-01 05:00:00,11105.0 -2012-07-01 06:00:00,10835.0 -2012-07-01 07:00:00,10509.0 -2012-07-01 08:00:00,10753.0 -2012-07-01 09:00:00,11684.0 -2012-07-01 10:00:00,12928.0 -2012-07-01 11:00:00,14225.0 -2012-07-01 12:00:00,15343.0 -2012-07-01 13:00:00,16127.0 -2012-07-01 14:00:00,15451.0 -2012-07-01 15:00:00,14084.0 -2012-07-01 16:00:00,14424.0 -2012-07-01 17:00:00,15323.0 -2012-07-01 18:00:00,15915.0 -2012-07-01 19:00:00,16247.0 -2012-07-01 20:00:00,16178.0 -2012-07-01 21:00:00,15827.0 -2012-07-01 22:00:00,15507.0 -2012-07-01 23:00:00,15351.0 -2012-07-02 00:00:00,14462.0 -2012-06-30 01:00:00,13421.0 -2012-06-30 02:00:00,12310.0 -2012-06-30 03:00:00,11507.0 -2012-06-30 04:00:00,10951.0 -2012-06-30 05:00:00,10637.0 -2012-06-30 06:00:00,10489.0 -2012-06-30 07:00:00,10395.0 -2012-06-30 08:00:00,10693.0 -2012-06-30 09:00:00,11605.0 -2012-06-30 10:00:00,12863.0 -2012-06-30 11:00:00,13996.0 -2012-06-30 12:00:00,15007.0 -2012-06-30 13:00:00,15619.0 -2012-06-30 14:00:00,16101.0 -2012-06-30 15:00:00,16761.0 -2012-06-30 16:00:00,17332.0 -2012-06-30 17:00:00,17719.0 -2012-06-30 18:00:00,17942.0 -2012-06-30 19:00:00,17970.0 -2012-06-30 20:00:00,17664.0 -2012-06-30 21:00:00,17039.0 -2012-06-30 22:00:00,16548.0 -2012-06-30 23:00:00,16149.0 -2012-07-01 00:00:00,15171.0 -2012-06-29 01:00:00,16987.0 -2012-06-29 02:00:00,15761.0 -2012-06-29 03:00:00,14838.0 -2012-06-29 04:00:00,14029.0 -2012-06-29 05:00:00,13350.0 -2012-06-29 06:00:00,13039.0 -2012-06-29 07:00:00,13055.0 -2012-06-29 08:00:00,13770.0 -2012-06-29 09:00:00,14997.0 -2012-06-29 10:00:00,16081.0 -2012-06-29 11:00:00,16980.0 -2012-06-29 12:00:00,17160.0 -2012-06-29 13:00:00,16526.0 -2012-06-29 14:00:00,15669.0 -2012-06-29 15:00:00,15598.0 -2012-06-29 16:00:00,16249.0 -2012-06-29 17:00:00,16895.0 -2012-06-29 18:00:00,17567.0 -2012-06-29 19:00:00,17901.0 -2012-06-29 20:00:00,17603.0 -2012-06-29 21:00:00,16823.0 -2012-06-29 22:00:00,16334.0 -2012-06-29 23:00:00,15837.0 -2012-06-30 00:00:00,14537.0 -2012-06-28 01:00:00,14449.0 -2012-06-28 02:00:00,13319.0 -2012-06-28 03:00:00,12520.0 -2012-06-28 04:00:00,11952.0 -2012-06-28 05:00:00,11615.0 -2012-06-28 06:00:00,11648.0 -2012-06-28 07:00:00,12052.0 -2012-06-28 08:00:00,13179.0 -2012-06-28 09:00:00,14774.0 -2012-06-28 10:00:00,16520.0 -2012-06-28 11:00:00,18159.0 -2012-06-28 12:00:00,19618.0 -2012-06-28 13:00:00,20637.0 -2012-06-28 14:00:00,21456.0 -2012-06-28 15:00:00,22187.0 -2012-06-28 16:00:00,22508.0 -2012-06-28 17:00:00,22749.0 -2012-06-28 18:00:00,22830.0 -2012-06-28 19:00:00,22363.0 -2012-06-28 20:00:00,21493.0 -2012-06-28 21:00:00,20729.0 -2012-06-28 22:00:00,20358.0 -2012-06-28 23:00:00,19889.0 -2012-06-29 00:00:00,18478.0 -2012-06-27 01:00:00,11590.0 -2012-06-27 02:00:00,10671.0 -2012-06-27 03:00:00,10062.0 -2012-06-27 04:00:00,9614.0 -2012-06-27 05:00:00,9417.0 -2012-06-27 06:00:00,9519.0 -2012-06-27 07:00:00,9849.0 -2012-06-27 08:00:00,10840.0 -2012-06-27 09:00:00,12024.0 -2012-06-27 10:00:00,13044.0 -2012-06-27 11:00:00,13892.0 -2012-06-27 12:00:00,14695.0 -2012-06-27 13:00:00,15299.0 -2012-06-27 14:00:00,16061.0 -2012-06-27 15:00:00,16894.0 -2012-06-27 16:00:00,17555.0 -2012-06-27 17:00:00,18153.0 -2012-06-27 18:00:00,18565.0 -2012-06-27 19:00:00,18790.0 -2012-06-27 20:00:00,18473.0 -2012-06-27 21:00:00,17917.0 -2012-06-27 22:00:00,17442.0 -2012-06-27 23:00:00,17129.0 -2012-06-28 00:00:00,15892.0 -2012-06-26 01:00:00,10452.0 -2012-06-26 02:00:00,9773.0 -2012-06-26 03:00:00,9315.0 -2012-06-26 04:00:00,9017.0 -2012-06-26 05:00:00,8876.0 -2012-06-26 06:00:00,9054.0 -2012-06-26 07:00:00,9348.0 -2012-06-26 08:00:00,10308.0 -2012-06-26 09:00:00,11369.0 -2012-06-26 10:00:00,12209.0 -2012-06-26 11:00:00,12815.0 -2012-06-26 12:00:00,13367.0 -2012-06-26 13:00:00,13775.0 -2012-06-26 14:00:00,14141.0 -2012-06-26 15:00:00,14661.0 -2012-06-26 16:00:00,15018.0 -2012-06-26 17:00:00,15283.0 -2012-06-26 18:00:00,15490.0 -2012-06-26 19:00:00,15401.0 -2012-06-26 20:00:00,14952.0 -2012-06-26 21:00:00,14415.0 -2012-06-26 22:00:00,14067.0 -2012-06-26 23:00:00,13828.0 -2012-06-27 00:00:00,12816.0 -2012-06-25 01:00:00,12228.0 -2012-06-25 02:00:00,11280.0 -2012-06-25 03:00:00,10571.0 -2012-06-25 04:00:00,10139.0 -2012-06-25 05:00:00,9837.0 -2012-06-25 06:00:00,9868.0 -2012-06-25 07:00:00,10072.0 -2012-06-25 08:00:00,11060.0 -2012-06-25 09:00:00,12032.0 -2012-06-25 10:00:00,12795.0 -2012-06-25 11:00:00,13263.0 -2012-06-25 12:00:00,13697.0 -2012-06-25 13:00:00,13893.0 -2012-06-25 14:00:00,14039.0 -2012-06-25 15:00:00,14142.0 -2012-06-25 16:00:00,14164.0 -2012-06-25 17:00:00,14142.0 -2012-06-25 18:00:00,14052.0 -2012-06-25 19:00:00,13778.0 -2012-06-25 20:00:00,13191.0 -2012-06-25 21:00:00,12612.0 -2012-06-25 22:00:00,12333.0 -2012-06-25 23:00:00,12241.0 -2012-06-26 00:00:00,11406.0 -2012-06-24 01:00:00,11654.0 -2012-06-24 02:00:00,10857.0 -2012-06-24 03:00:00,10301.0 -2012-06-24 04:00:00,9773.0 -2012-06-24 05:00:00,9502.0 -2012-06-24 06:00:00,9356.0 -2012-06-24 07:00:00,9154.0 -2012-06-24 08:00:00,9201.0 -2012-06-24 09:00:00,9947.0 -2012-06-24 10:00:00,10712.0 -2012-06-24 11:00:00,11609.0 -2012-06-24 12:00:00,12438.0 -2012-06-24 13:00:00,13371.0 -2012-06-24 14:00:00,14263.0 -2012-06-24 15:00:00,14986.0 -2012-06-24 16:00:00,15716.0 -2012-06-24 17:00:00,16197.0 -2012-06-24 18:00:00,16544.0 -2012-06-24 19:00:00,16395.0 -2012-06-24 20:00:00,15870.0 -2012-06-24 21:00:00,15142.0 -2012-06-24 22:00:00,14783.0 -2012-06-24 23:00:00,14422.0 -2012-06-25 00:00:00,13411.0 -2012-06-23 01:00:00,12037.0 -2012-06-23 02:00:00,11049.0 -2012-06-23 03:00:00,10385.0 -2012-06-23 04:00:00,9886.0 -2012-06-23 05:00:00,9591.0 -2012-06-23 06:00:00,9462.0 -2012-06-23 07:00:00,9347.0 -2012-06-23 08:00:00,9774.0 -2012-06-23 09:00:00,10633.0 -2012-06-23 10:00:00,11594.0 -2012-06-23 11:00:00,12542.0 -2012-06-23 12:00:00,13449.0 -2012-06-23 13:00:00,14168.0 -2012-06-23 14:00:00,14670.0 -2012-06-23 15:00:00,14924.0 -2012-06-23 16:00:00,15044.0 -2012-06-23 17:00:00,14795.0 -2012-06-23 18:00:00,14609.0 -2012-06-23 19:00:00,14271.0 -2012-06-23 20:00:00,13712.0 -2012-06-23 21:00:00,13357.0 -2012-06-23 22:00:00,13318.0 -2012-06-23 23:00:00,13166.0 -2012-06-24 00:00:00,12452.0 -2012-06-22 01:00:00,12617.0 -2012-06-22 02:00:00,11534.0 -2012-06-22 03:00:00,10733.0 -2012-06-22 04:00:00,10219.0 -2012-06-22 05:00:00,9899.0 -2012-06-22 06:00:00,9909.0 -2012-06-22 07:00:00,10175.0 -2012-06-22 08:00:00,11082.0 -2012-06-22 09:00:00,12322.0 -2012-06-22 10:00:00,13305.0 -2012-06-22 11:00:00,14039.0 -2012-06-22 12:00:00,14741.0 -2012-06-22 13:00:00,15190.0 -2012-06-22 14:00:00,15579.0 -2012-06-22 15:00:00,15976.0 -2012-06-22 16:00:00,16221.0 -2012-06-22 17:00:00,16414.0 -2012-06-22 18:00:00,16464.0 -2012-06-22 19:00:00,16233.0 -2012-06-22 20:00:00,15714.0 -2012-06-22 21:00:00,15041.0 -2012-06-22 22:00:00,14506.0 -2012-06-22 23:00:00,14234.0 -2012-06-23 00:00:00,13184.0 -2012-06-21 01:00:00,15398.0 -2012-06-21 02:00:00,14255.0 -2012-06-21 03:00:00,13447.0 -2012-06-21 04:00:00,12854.0 -2012-06-21 05:00:00,12551.0 -2012-06-21 06:00:00,12529.0 -2012-06-21 07:00:00,12769.0 -2012-06-21 08:00:00,13874.0 -2012-06-21 09:00:00,14991.0 -2012-06-21 10:00:00,15771.0 -2012-06-21 11:00:00,16281.0 -2012-06-21 12:00:00,16448.0 -2012-06-21 13:00:00,16284.0 -2012-06-21 14:00:00,16121.0 -2012-06-21 15:00:00,16170.0 -2012-06-21 16:00:00,16046.0 -2012-06-21 17:00:00,16384.0 -2012-06-21 18:00:00,16866.0 -2012-06-21 19:00:00,17030.0 -2012-06-21 20:00:00,16695.0 -2012-06-21 21:00:00,16107.0 -2012-06-21 22:00:00,15535.0 -2012-06-21 23:00:00,15151.0 -2012-06-22 00:00:00,13942.0 -2012-06-20 01:00:00,15974.0 -2012-06-20 02:00:00,14699.0 -2012-06-20 03:00:00,13765.0 -2012-06-20 04:00:00,13078.0 -2012-06-20 05:00:00,12635.0 -2012-06-20 06:00:00,12519.0 -2012-06-20 07:00:00,12778.0 -2012-06-20 08:00:00,13915.0 -2012-06-20 09:00:00,15276.0 -2012-06-20 10:00:00,16460.0 -2012-06-20 11:00:00,17542.0 -2012-06-20 12:00:00,18622.0 -2012-06-20 13:00:00,19469.0 -2012-06-20 14:00:00,20225.0 -2012-06-20 15:00:00,20861.0 -2012-06-20 16:00:00,21170.0 -2012-06-20 17:00:00,21303.0 -2012-06-20 18:00:00,21297.0 -2012-06-20 19:00:00,21027.0 -2012-06-20 20:00:00,20459.0 -2012-06-20 21:00:00,19617.0 -2012-06-20 22:00:00,18889.0 -2012-06-20 23:00:00,18358.0 -2012-06-21 00:00:00,16927.0 -2012-06-19 01:00:00,15494.0 -2012-06-19 02:00:00,14320.0 -2012-06-19 03:00:00,13399.0 -2012-06-19 04:00:00,12693.0 -2012-06-19 05:00:00,12319.0 -2012-06-19 06:00:00,12303.0 -2012-06-19 07:00:00,12591.0 -2012-06-19 08:00:00,13776.0 -2012-06-19 09:00:00,15230.0 -2012-06-19 10:00:00,16599.0 -2012-06-19 11:00:00,17733.0 -2012-06-19 12:00:00,18837.0 -2012-06-19 13:00:00,19704.0 -2012-06-19 14:00:00,20405.0 -2012-06-19 15:00:00,20969.0 -2012-06-19 16:00:00,21273.0 -2012-06-19 17:00:00,21368.0 -2012-06-19 18:00:00,21391.0 -2012-06-19 19:00:00,21165.0 -2012-06-19 20:00:00,20684.0 -2012-06-19 21:00:00,20034.0 -2012-06-19 22:00:00,19424.0 -2012-06-19 23:00:00,18944.0 -2012-06-20 00:00:00,17522.0 -2012-06-18 01:00:00,12279.0 -2012-06-18 02:00:00,11310.0 -2012-06-18 03:00:00,10742.0 -2012-06-18 04:00:00,10355.0 -2012-06-18 05:00:00,10280.0 -2012-06-18 06:00:00,10498.0 -2012-06-18 07:00:00,11101.0 -2012-06-18 08:00:00,12369.0 -2012-06-18 09:00:00,13742.0 -2012-06-18 10:00:00,14933.0 -2012-06-18 11:00:00,16253.0 -2012-06-18 12:00:00,17641.0 -2012-06-18 13:00:00,18686.0 -2012-06-18 14:00:00,19472.0 -2012-06-18 15:00:00,20156.0 -2012-06-18 16:00:00,20495.0 -2012-06-18 17:00:00,20633.0 -2012-06-18 18:00:00,20632.0 -2012-06-18 19:00:00,20458.0 -2012-06-18 20:00:00,19983.0 -2012-06-18 21:00:00,19299.0 -2012-06-18 22:00:00,18830.0 -2012-06-18 23:00:00,18431.0 -2012-06-19 00:00:00,17011.0 -2012-06-17 01:00:00,12564.0 -2012-06-17 02:00:00,11587.0 -2012-06-17 03:00:00,10804.0 -2012-06-17 04:00:00,10293.0 -2012-06-17 05:00:00,9906.0 -2012-06-17 06:00:00,9764.0 -2012-06-17 07:00:00,9563.0 -2012-06-17 08:00:00,9523.0 -2012-06-17 09:00:00,10085.0 -2012-06-17 10:00:00,10999.0 -2012-06-17 11:00:00,12144.0 -2012-06-17 12:00:00,13082.0 -2012-06-17 13:00:00,13809.0 -2012-06-17 14:00:00,14223.0 -2012-06-17 15:00:00,14482.0 -2012-06-17 16:00:00,14791.0 -2012-06-17 17:00:00,15179.0 -2012-06-17 18:00:00,15431.0 -2012-06-17 19:00:00,15521.0 -2012-06-17 20:00:00,15208.0 -2012-06-17 21:00:00,14726.0 -2012-06-17 22:00:00,14407.0 -2012-06-17 23:00:00,14267.0 -2012-06-18 00:00:00,13358.0 -2012-06-16 01:00:00,13163.0 -2012-06-16 02:00:00,12053.0 -2012-06-16 03:00:00,11158.0 -2012-06-16 04:00:00,10600.0 -2012-06-16 05:00:00,10211.0 -2012-06-16 06:00:00,10064.0 -2012-06-16 07:00:00,10059.0 -2012-06-16 08:00:00,10299.0 -2012-06-16 09:00:00,11097.0 -2012-06-16 10:00:00,12158.0 -2012-06-16 11:00:00,13327.0 -2012-06-16 12:00:00,14520.0 -2012-06-16 13:00:00,15395.0 -2012-06-16 14:00:00,16015.0 -2012-06-16 15:00:00,16319.0 -2012-06-16 16:00:00,16725.0 -2012-06-16 17:00:00,17135.0 -2012-06-16 18:00:00,17310.0 -2012-06-16 19:00:00,17229.0 -2012-06-16 20:00:00,16645.0 -2012-06-16 21:00:00,15912.0 -2012-06-16 22:00:00,15488.0 -2012-06-16 23:00:00,14899.0 -2012-06-17 00:00:00,13782.0 -2012-06-15 01:00:00,11436.0 -2012-06-15 02:00:00,10574.0 -2012-06-15 03:00:00,9954.0 -2012-06-15 04:00:00,9489.0 -2012-06-15 05:00:00,9203.0 -2012-06-15 06:00:00,9278.0 -2012-06-15 07:00:00,9563.0 -2012-06-15 08:00:00,10528.0 -2012-06-15 09:00:00,11762.0 -2012-06-15 10:00:00,12782.0 -2012-06-15 11:00:00,13677.0 -2012-06-15 12:00:00,14630.0 -2012-06-15 13:00:00,15389.0 -2012-06-15 14:00:00,16085.0 -2012-06-15 15:00:00,16830.0 -2012-06-15 16:00:00,17442.0 -2012-06-15 17:00:00,17860.0 -2012-06-15 18:00:00,18155.0 -2012-06-15 19:00:00,18054.0 -2012-06-15 20:00:00,17548.0 -2012-06-15 21:00:00,16804.0 -2012-06-15 22:00:00,16295.0 -2012-06-15 23:00:00,15798.0 -2012-06-16 00:00:00,14517.0 -2012-06-14 01:00:00,10049.0 -2012-06-14 02:00:00,9363.0 -2012-06-14 03:00:00,8959.0 -2012-06-14 04:00:00,8676.0 -2012-06-14 05:00:00,8605.0 -2012-06-14 06:00:00,8706.0 -2012-06-14 07:00:00,8989.0 -2012-06-14 08:00:00,9919.0 -2012-06-14 09:00:00,10956.0 -2012-06-14 10:00:00,11712.0 -2012-06-14 11:00:00,12279.0 -2012-06-14 12:00:00,12877.0 -2012-06-14 13:00:00,13355.0 -2012-06-14 14:00:00,13772.0 -2012-06-14 15:00:00,14238.0 -2012-06-14 16:00:00,14601.0 -2012-06-14 17:00:00,14835.0 -2012-06-14 18:00:00,14996.0 -2012-06-14 19:00:00,14899.0 -2012-06-14 20:00:00,14415.0 -2012-06-14 21:00:00,13954.0 -2012-06-14 22:00:00,13809.0 -2012-06-14 23:00:00,13594.0 -2012-06-15 00:00:00,12567.0 -2012-06-13 01:00:00,10395.0 -2012-06-13 02:00:00,9641.0 -2012-06-13 03:00:00,9132.0 -2012-06-13 04:00:00,8804.0 -2012-06-13 05:00:00,8667.0 -2012-06-13 06:00:00,8767.0 -2012-06-13 07:00:00,9062.0 -2012-06-13 08:00:00,10033.0 -2012-06-13 09:00:00,11079.0 -2012-06-13 10:00:00,11715.0 -2012-06-13 11:00:00,12188.0 -2012-06-13 12:00:00,12562.0 -2012-06-13 13:00:00,12775.0 -2012-06-13 14:00:00,12897.0 -2012-06-13 15:00:00,13023.0 -2012-06-13 16:00:00,12861.0 -2012-06-13 17:00:00,12757.0 -2012-06-13 18:00:00,12609.0 -2012-06-13 19:00:00,12291.0 -2012-06-13 20:00:00,11878.0 -2012-06-13 21:00:00,11621.0 -2012-06-13 22:00:00,11717.0 -2012-06-13 23:00:00,11720.0 -2012-06-14 00:00:00,10942.0 -2012-06-12 01:00:00,13461.0 -2012-06-12 02:00:00,12210.0 -2012-06-12 03:00:00,11294.0 -2012-06-12 04:00:00,10586.0 -2012-06-12 05:00:00,10167.0 -2012-06-12 06:00:00,10041.0 -2012-06-12 07:00:00,10171.0 -2012-06-12 08:00:00,11096.0 -2012-06-12 09:00:00,12102.0 -2012-06-12 10:00:00,12810.0 -2012-06-12 11:00:00,13268.0 -2012-06-12 12:00:00,13662.0 -2012-06-12 13:00:00,13829.0 -2012-06-12 14:00:00,14011.0 -2012-06-12 15:00:00,14289.0 -2012-06-12 16:00:00,14432.0 -2012-06-12 17:00:00,14519.0 -2012-06-12 18:00:00,14460.0 -2012-06-12 19:00:00,14149.0 -2012-06-12 20:00:00,13528.0 -2012-06-12 21:00:00,12873.0 -2012-06-12 22:00:00,12576.0 -2012-06-12 23:00:00,12381.0 -2012-06-13 00:00:00,11405.0 -2012-06-11 01:00:00,13223.0 -2012-06-11 02:00:00,12273.0 -2012-06-11 03:00:00,11588.0 -2012-06-11 04:00:00,11034.0 -2012-06-11 05:00:00,10757.0 -2012-06-11 06:00:00,10779.0 -2012-06-11 07:00:00,11136.0 -2012-06-11 08:00:00,12321.0 -2012-06-11 09:00:00,13566.0 -2012-06-11 10:00:00,14535.0 -2012-06-11 11:00:00,15665.0 -2012-06-11 12:00:00,16664.0 -2012-06-11 13:00:00,17239.0 -2012-06-11 14:00:00,17722.0 -2012-06-11 15:00:00,18282.0 -2012-06-11 16:00:00,18565.0 -2012-06-11 17:00:00,18762.0 -2012-06-11 18:00:00,18738.0 -2012-06-11 19:00:00,18313.0 -2012-06-11 20:00:00,17670.0 -2012-06-11 21:00:00,16987.0 -2012-06-11 22:00:00,16613.0 -2012-06-11 23:00:00,16220.0 -2012-06-12 00:00:00,14907.0 -2012-06-10 01:00:00,12206.0 -2012-06-10 02:00:00,11299.0 -2012-06-10 03:00:00,10570.0 -2012-06-10 04:00:00,10041.0 -2012-06-10 05:00:00,9635.0 -2012-06-10 06:00:00,9475.0 -2012-06-10 07:00:00,9107.0 -2012-06-10 08:00:00,9336.0 -2012-06-10 09:00:00,10104.0 -2012-06-10 10:00:00,11298.0 -2012-06-10 11:00:00,12558.0 -2012-06-10 12:00:00,13714.0 -2012-06-10 13:00:00,14616.0 -2012-06-10 14:00:00,15187.0 -2012-06-10 15:00:00,15615.0 -2012-06-10 16:00:00,15980.0 -2012-06-10 17:00:00,16296.0 -2012-06-10 18:00:00,16461.0 -2012-06-10 19:00:00,16494.0 -2012-06-10 20:00:00,16079.0 -2012-06-10 21:00:00,15504.0 -2012-06-10 22:00:00,15304.0 -2012-06-10 23:00:00,15194.0 -2012-06-11 00:00:00,14263.0 -2012-06-09 01:00:00,12061.0 -2012-06-09 02:00:00,11096.0 -2012-06-09 03:00:00,10302.0 -2012-06-09 04:00:00,9808.0 -2012-06-09 05:00:00,9489.0 -2012-06-09 06:00:00,9245.0 -2012-06-09 07:00:00,9107.0 -2012-06-09 08:00:00,9567.0 -2012-06-09 09:00:00,10470.0 -2012-06-09 10:00:00,11558.0 -2012-06-09 11:00:00,12648.0 -2012-06-09 12:00:00,13548.0 -2012-06-09 13:00:00,14125.0 -2012-06-09 14:00:00,14693.0 -2012-06-09 15:00:00,15054.0 -2012-06-09 16:00:00,15484.0 -2012-06-09 17:00:00,15844.0 -2012-06-09 18:00:00,16076.0 -2012-06-09 19:00:00,15993.0 -2012-06-09 20:00:00,15696.0 -2012-06-09 21:00:00,15091.0 -2012-06-09 22:00:00,14606.0 -2012-06-09 23:00:00,14253.0 -2012-06-10 00:00:00,13297.0 -2012-06-08 01:00:00,10424.0 -2012-06-08 02:00:00,9666.0 -2012-06-08 03:00:00,9161.0 -2012-06-08 04:00:00,8810.0 -2012-06-08 05:00:00,8633.0 -2012-06-08 06:00:00,8720.0 -2012-06-08 07:00:00,9035.0 -2012-06-08 08:00:00,9975.0 -2012-06-08 09:00:00,11116.0 -2012-06-08 10:00:00,12064.0 -2012-06-08 11:00:00,12799.0 -2012-06-08 12:00:00,13521.0 -2012-06-08 13:00:00,13992.0 -2012-06-08 14:00:00,14449.0 -2012-06-08 15:00:00,14958.0 -2012-06-08 16:00:00,15309.0 -2012-06-08 17:00:00,15638.0 -2012-06-08 18:00:00,15865.0 -2012-06-08 19:00:00,15815.0 -2012-06-08 20:00:00,15424.0 -2012-06-08 21:00:00,14873.0 -2012-06-08 22:00:00,14532.0 -2012-06-08 23:00:00,14277.0 -2012-06-09 00:00:00,13254.0 -2012-06-07 01:00:00,9898.0 -2012-06-07 02:00:00,9218.0 -2012-06-07 03:00:00,8762.0 -2012-06-07 04:00:00,8487.0 -2012-06-07 05:00:00,8352.0 -2012-06-07 06:00:00,8453.0 -2012-06-07 07:00:00,8762.0 -2012-06-07 08:00:00,9711.0 -2012-06-07 09:00:00,10756.0 -2012-06-07 10:00:00,11525.0 -2012-06-07 11:00:00,12080.0 -2012-06-07 12:00:00,12600.0 -2012-06-07 13:00:00,12883.0 -2012-06-07 14:00:00,13152.0 -2012-06-07 15:00:00,13443.0 -2012-06-07 16:00:00,13552.0 -2012-06-07 17:00:00,13639.0 -2012-06-07 18:00:00,13679.0 -2012-06-07 19:00:00,13563.0 -2012-06-07 20:00:00,13132.0 -2012-06-07 21:00:00,12715.0 -2012-06-07 22:00:00,12631.0 -2012-06-07 23:00:00,12459.0 -2012-06-08 00:00:00,11462.0 -2012-06-06 01:00:00,9682.0 -2012-06-06 02:00:00,9027.0 -2012-06-06 03:00:00,8615.0 -2012-06-06 04:00:00,8369.0 -2012-06-06 05:00:00,8254.0 -2012-06-06 06:00:00,8393.0 -2012-06-06 07:00:00,8735.0 -2012-06-06 08:00:00,9687.0 -2012-06-06 09:00:00,10707.0 -2012-06-06 10:00:00,11443.0 -2012-06-06 11:00:00,11961.0 -2012-06-06 12:00:00,12306.0 -2012-06-06 13:00:00,12539.0 -2012-06-06 14:00:00,12664.0 -2012-06-06 15:00:00,12827.0 -2012-06-06 16:00:00,12882.0 -2012-06-06 17:00:00,12880.0 -2012-06-06 18:00:00,12853.0 -2012-06-06 19:00:00,12666.0 -2012-06-06 20:00:00,12233.0 -2012-06-06 21:00:00,11892.0 -2012-06-06 22:00:00,11857.0 -2012-06-06 23:00:00,11725.0 -2012-06-07 00:00:00,10858.0 -2012-06-05 01:00:00,9863.0 -2012-06-05 02:00:00,9188.0 -2012-06-05 03:00:00,8767.0 -2012-06-05 04:00:00,8477.0 -2012-06-05 05:00:00,8346.0 -2012-06-05 06:00:00,8498.0 -2012-06-05 07:00:00,8847.0 -2012-06-05 08:00:00,9795.0 -2012-06-05 09:00:00,10846.0 -2012-06-05 10:00:00,11482.0 -2012-06-05 11:00:00,11885.0 -2012-06-05 12:00:00,12252.0 -2012-06-05 13:00:00,12388.0 -2012-06-05 14:00:00,12473.0 -2012-06-05 15:00:00,12545.0 -2012-06-05 16:00:00,12517.0 -2012-06-05 17:00:00,12484.0 -2012-06-05 18:00:00,12399.0 -2012-06-05 19:00:00,12182.0 -2012-06-05 20:00:00,11769.0 -2012-06-05 21:00:00,11426.0 -2012-06-05 22:00:00,11478.0 -2012-06-05 23:00:00,11400.0 -2012-06-06 00:00:00,10586.0 -2012-06-04 01:00:00,9667.0 -2012-06-04 02:00:00,9094.0 -2012-06-04 03:00:00,8724.0 -2012-06-04 04:00:00,8469.0 -2012-06-04 05:00:00,8388.0 -2012-06-04 06:00:00,8566.0 -2012-06-04 07:00:00,9083.0 -2012-06-04 08:00:00,9978.0 -2012-06-04 09:00:00,10958.0 -2012-06-04 10:00:00,11538.0 -2012-06-04 11:00:00,12021.0 -2012-06-04 12:00:00,12464.0 -2012-06-04 13:00:00,12744.0 -2012-06-04 14:00:00,12923.0 -2012-06-04 15:00:00,13135.0 -2012-06-04 16:00:00,13186.0 -2012-06-04 17:00:00,13192.0 -2012-06-04 18:00:00,13104.0 -2012-06-04 19:00:00,12816.0 -2012-06-04 20:00:00,12313.0 -2012-06-04 21:00:00,11836.0 -2012-06-04 22:00:00,11836.0 -2012-06-04 23:00:00,11697.0 -2012-06-05 00:00:00,10838.0 -2012-06-03 01:00:00,9089.0 -2012-06-03 02:00:00,8511.0 -2012-06-03 03:00:00,8135.0 -2012-06-03 04:00:00,7930.0 -2012-06-03 05:00:00,7731.0 -2012-06-03 06:00:00,7751.0 -2012-06-03 07:00:00,7521.0 -2012-06-03 08:00:00,7595.0 -2012-06-03 09:00:00,7989.0 -2012-06-03 10:00:00,8602.0 -2012-06-03 11:00:00,9161.0 -2012-06-03 12:00:00,9617.0 -2012-06-03 13:00:00,9966.0 -2012-06-03 14:00:00,10185.0 -2012-06-03 15:00:00,10368.0 -2012-06-03 16:00:00,10535.0 -2012-06-03 17:00:00,10717.0 -2012-06-03 18:00:00,10727.0 -2012-06-03 19:00:00,10686.0 -2012-06-03 20:00:00,10604.0 -2012-06-03 21:00:00,10599.0 -2012-06-03 22:00:00,10964.0 -2012-06-03 23:00:00,11017.0 -2012-06-04 00:00:00,10395.0 -2012-06-02 01:00:00,9239.0 -2012-06-02 02:00:00,8639.0 -2012-06-02 03:00:00,8310.0 -2012-06-02 04:00:00,8035.0 -2012-06-02 05:00:00,7948.0 -2012-06-02 06:00:00,7963.0 -2012-06-02 07:00:00,7930.0 -2012-06-02 08:00:00,8185.0 -2012-06-02 09:00:00,8651.0 -2012-06-02 10:00:00,9236.0 -2012-06-02 11:00:00,9655.0 -2012-06-02 12:00:00,10046.0 -2012-06-02 13:00:00,10157.0 -2012-06-02 14:00:00,10195.0 -2012-06-02 15:00:00,10093.0 -2012-06-02 16:00:00,10061.0 -2012-06-02 17:00:00,10032.0 -2012-06-02 18:00:00,10058.0 -2012-06-02 19:00:00,10011.0 -2012-06-02 20:00:00,9879.0 -2012-06-02 21:00:00,9783.0 -2012-06-02 22:00:00,10113.0 -2012-06-02 23:00:00,10218.0 -2012-06-03 00:00:00,9675.0 -2012-06-01 01:00:00,9341.0 -2012-06-01 02:00:00,8799.0 -2012-06-01 03:00:00,8451.0 -2012-06-01 04:00:00,8240.0 -2012-06-01 05:00:00,8139.0 -2012-06-01 06:00:00,8313.0 -2012-06-01 07:00:00,8788.0 -2012-06-01 08:00:00,9645.0 -2012-06-01 09:00:00,10415.0 -2012-06-01 10:00:00,10842.0 -2012-06-01 11:00:00,11055.0 -2012-06-01 12:00:00,11217.0 -2012-06-01 13:00:00,11267.0 -2012-06-01 14:00:00,11255.0 -2012-06-01 15:00:00,11275.0 -2012-06-01 16:00:00,11187.0 -2012-06-01 17:00:00,11052.0 -2012-06-01 18:00:00,10903.0 -2012-06-01 19:00:00,10723.0 -2012-06-01 20:00:00,10464.0 -2012-06-01 21:00:00,10325.0 -2012-06-01 22:00:00,10583.0 -2012-06-01 23:00:00,10621.0 -2012-06-02 00:00:00,9993.0 -2012-05-31 01:00:00,9385.0 -2012-05-31 02:00:00,8805.0 -2012-05-31 03:00:00,8455.0 -2012-05-31 04:00:00,8236.0 -2012-05-31 05:00:00,8113.0 -2012-05-31 06:00:00,8282.0 -2012-05-31 07:00:00,8750.0 -2012-05-31 08:00:00,9580.0 -2012-05-31 09:00:00,10466.0 -2012-05-31 10:00:00,10987.0 -2012-05-31 11:00:00,11258.0 -2012-05-31 12:00:00,11461.0 -2012-05-31 13:00:00,11547.0 -2012-05-31 14:00:00,11600.0 -2012-05-31 15:00:00,11583.0 -2012-05-31 16:00:00,11492.0 -2012-05-31 17:00:00,11336.0 -2012-05-31 18:00:00,11216.0 -2012-05-31 19:00:00,11086.0 -2012-05-31 20:00:00,10982.0 -2012-05-31 21:00:00,11101.0 -2012-05-31 22:00:00,11248.0 -2012-05-31 23:00:00,10900.0 -2012-06-01 00:00:00,10130.0 -2012-05-30 01:00:00,10738.0 -2012-05-30 02:00:00,9908.0 -2012-05-30 03:00:00,9352.0 -2012-05-30 04:00:00,8991.0 -2012-05-30 05:00:00,8794.0 -2012-05-30 06:00:00,8880.0 -2012-05-30 07:00:00,9188.0 -2012-05-30 08:00:00,10129.0 -2012-05-30 09:00:00,11102.0 -2012-05-30 10:00:00,11761.0 -2012-05-30 11:00:00,12110.0 -2012-05-30 12:00:00,12330.0 -2012-05-30 13:00:00,12369.0 -2012-05-30 14:00:00,12411.0 -2012-05-30 15:00:00,12427.0 -2012-05-30 16:00:00,12361.0 -2012-05-30 17:00:00,12177.0 -2012-05-30 18:00:00,11967.0 -2012-05-30 19:00:00,11718.0 -2012-05-30 20:00:00,11290.0 -2012-05-30 21:00:00,10962.0 -2012-05-30 22:00:00,11188.0 -2012-05-30 23:00:00,11026.0 -2012-05-31 00:00:00,10223.0 -2012-05-29 01:00:00,12803.0 -2012-05-29 02:00:00,11809.0 -2012-05-29 03:00:00,11115.0 -2012-05-29 04:00:00,10567.0 -2012-05-29 05:00:00,10171.0 -2012-05-29 06:00:00,10084.0 -2012-05-29 07:00:00,10296.0 -2012-05-29 08:00:00,11254.0 -2012-05-29 09:00:00,12362.0 -2012-05-29 10:00:00,13204.0 -2012-05-29 11:00:00,13925.0 -2012-05-29 12:00:00,14541.0 -2012-05-29 13:00:00,14951.0 -2012-05-29 14:00:00,15245.0 -2012-05-29 15:00:00,15703.0 -2012-05-29 16:00:00,15949.0 -2012-05-29 17:00:00,16074.0 -2012-05-29 18:00:00,16047.0 -2012-05-29 19:00:00,15677.0 -2012-05-29 20:00:00,14966.0 -2012-05-29 21:00:00,14075.0 -2012-05-29 22:00:00,13639.0 -2012-05-29 23:00:00,13112.0 -2012-05-30 00:00:00,11902.0 -2012-05-28 01:00:00,13087.0 -2012-05-28 02:00:00,12174.0 -2012-05-28 03:00:00,11411.0 -2012-05-28 04:00:00,10871.0 -2012-05-28 05:00:00,10465.0 -2012-05-28 06:00:00,10285.0 -2012-05-28 07:00:00,10133.0 -2012-05-28 08:00:00,10354.0 -2012-05-28 09:00:00,11200.0 -2012-05-28 10:00:00,12264.0 -2012-05-28 11:00:00,13464.0 -2012-05-28 12:00:00,14598.0 -2012-05-28 13:00:00,15291.0 -2012-05-28 14:00:00,15796.0 -2012-05-28 15:00:00,16069.0 -2012-05-28 16:00:00,16200.0 -2012-05-28 17:00:00,16494.0 -2012-05-28 18:00:00,16641.0 -2012-05-28 19:00:00,16518.0 -2012-05-28 20:00:00,16240.0 -2012-05-28 21:00:00,15828.0 -2012-05-28 22:00:00,15829.0 -2012-05-28 23:00:00,15418.0 -2012-05-29 00:00:00,14133.0 -2012-05-27 01:00:00,9942.0 -2012-05-27 02:00:00,9404.0 -2012-05-27 03:00:00,8914.0 -2012-05-27 04:00:00,8654.0 -2012-05-27 05:00:00,8579.0 -2012-05-27 06:00:00,8506.0 -2012-05-27 07:00:00,8419.0 -2012-05-27 08:00:00,8489.0 -2012-05-27 09:00:00,9246.0 -2012-05-27 10:00:00,10397.0 -2012-05-27 11:00:00,11669.0 -2012-05-27 12:00:00,12872.0 -2012-05-27 13:00:00,13944.0 -2012-05-27 14:00:00,14807.0 -2012-05-27 15:00:00,15516.0 -2012-05-27 16:00:00,15995.0 -2012-05-27 17:00:00,16343.0 -2012-05-27 18:00:00,16557.0 -2012-05-27 19:00:00,16547.0 -2012-05-27 20:00:00,16230.0 -2012-05-27 21:00:00,15665.0 -2012-05-27 22:00:00,15339.0 -2012-05-27 23:00:00,15014.0 -2012-05-28 00:00:00,14154.0 -2012-05-26 01:00:00,10261.0 -2012-05-26 02:00:00,9500.0 -2012-05-26 03:00:00,9070.0 -2012-05-26 04:00:00,8761.0 -2012-05-26 05:00:00,8597.0 -2012-05-26 06:00:00,8539.0 -2012-05-26 07:00:00,8599.0 -2012-05-26 08:00:00,8795.0 -2012-05-26 09:00:00,9143.0 -2012-05-26 10:00:00,9606.0 -2012-05-26 11:00:00,10149.0 -2012-05-26 12:00:00,10550.0 -2012-05-26 13:00:00,10869.0 -2012-05-26 14:00:00,11239.0 -2012-05-26 15:00:00,11603.0 -2012-05-26 16:00:00,12004.0 -2012-05-26 17:00:00,12431.0 -2012-05-26 18:00:00,12551.0 -2012-05-26 19:00:00,12176.0 -2012-05-26 20:00:00,11564.0 -2012-05-26 21:00:00,11112.0 -2012-05-26 22:00:00,11188.0 -2012-05-26 23:00:00,11089.0 -2012-05-27 00:00:00,10557.0 -2012-05-25 01:00:00,12937.0 -2012-05-25 02:00:00,12008.0 -2012-05-25 03:00:00,11333.0 -2012-05-25 04:00:00,10793.0 -2012-05-25 05:00:00,10360.0 -2012-05-25 06:00:00,10200.0 -2012-05-25 07:00:00,10381.0 -2012-05-25 08:00:00,11263.0 -2012-05-25 09:00:00,12318.0 -2012-05-25 10:00:00,13046.0 -2012-05-25 11:00:00,13596.0 -2012-05-25 12:00:00,14049.0 -2012-05-25 13:00:00,14321.0 -2012-05-25 14:00:00,14482.0 -2012-05-25 15:00:00,14659.0 -2012-05-25 16:00:00,14585.0 -2012-05-25 17:00:00,14445.0 -2012-05-25 18:00:00,14215.0 -2012-05-25 19:00:00,13697.0 -2012-05-25 20:00:00,12907.0 -2012-05-25 21:00:00,12351.0 -2012-05-25 22:00:00,12384.0 -2012-05-25 23:00:00,12017.0 -2012-05-26 00:00:00,11191.0 -2012-05-24 01:00:00,10316.0 -2012-05-24 02:00:00,9644.0 -2012-05-24 03:00:00,9199.0 -2012-05-24 04:00:00,8877.0 -2012-05-24 05:00:00,8751.0 -2012-05-24 06:00:00,8866.0 -2012-05-24 07:00:00,9278.0 -2012-05-24 08:00:00,10401.0 -2012-05-24 09:00:00,11563.0 -2012-05-24 10:00:00,12479.0 -2012-05-24 11:00:00,13128.0 -2012-05-24 12:00:00,13751.0 -2012-05-24 13:00:00,14211.0 -2012-05-24 14:00:00,14654.0 -2012-05-24 15:00:00,15184.0 -2012-05-24 16:00:00,15584.0 -2012-05-24 17:00:00,15984.0 -2012-05-24 18:00:00,16261.0 -2012-05-24 19:00:00,16186.0 -2012-05-24 20:00:00,15780.0 -2012-05-24 21:00:00,15409.0 -2012-05-24 22:00:00,15517.0 -2012-05-24 23:00:00,15212.0 -2012-05-25 00:00:00,14130.0 -2012-05-23 01:00:00,9457.0 -2012-05-23 02:00:00,8856.0 -2012-05-23 03:00:00,8449.0 -2012-05-23 04:00:00,8235.0 -2012-05-23 05:00:00,8166.0 -2012-05-23 06:00:00,8308.0 -2012-05-23 07:00:00,8678.0 -2012-05-23 08:00:00,9593.0 -2012-05-23 09:00:00,10551.0 -2012-05-23 10:00:00,11164.0 -2012-05-23 11:00:00,11655.0 -2012-05-23 12:00:00,12032.0 -2012-05-23 13:00:00,12302.0 -2012-05-23 14:00:00,12512.0 -2012-05-23 15:00:00,12819.0 -2012-05-23 16:00:00,12949.0 -2012-05-23 17:00:00,13091.0 -2012-05-23 18:00:00,13209.0 -2012-05-23 19:00:00,13055.0 -2012-05-23 20:00:00,12699.0 -2012-05-23 21:00:00,12353.0 -2012-05-23 22:00:00,12477.0 -2012-05-23 23:00:00,12234.0 -2012-05-24 00:00:00,11298.0 -2012-05-22 01:00:00,9363.0 -2012-05-22 02:00:00,8798.0 -2012-05-22 03:00:00,8443.0 -2012-05-22 04:00:00,8211.0 -2012-05-22 05:00:00,8126.0 -2012-05-22 06:00:00,8283.0 -2012-05-22 07:00:00,8674.0 -2012-05-22 08:00:00,9622.0 -2012-05-22 09:00:00,10571.0 -2012-05-22 10:00:00,11170.0 -2012-05-22 11:00:00,11545.0 -2012-05-22 12:00:00,11811.0 -2012-05-22 13:00:00,11958.0 -2012-05-22 14:00:00,12069.0 -2012-05-22 15:00:00,12164.0 -2012-05-22 16:00:00,12167.0 -2012-05-22 17:00:00,12161.0 -2012-05-22 18:00:00,12094.0 -2012-05-22 19:00:00,11944.0 -2012-05-22 20:00:00,11580.0 -2012-05-22 21:00:00,11308.0 -2012-05-22 22:00:00,11556.0 -2012-05-22 23:00:00,11257.0 -2012-05-23 00:00:00,10393.0 -2012-05-21 01:00:00,10758.0 -2012-05-21 02:00:00,9960.0 -2012-05-21 03:00:00,9413.0 -2012-05-21 04:00:00,9051.0 -2012-05-21 05:00:00,8850.0 -2012-05-21 06:00:00,8938.0 -2012-05-21 07:00:00,9312.0 -2012-05-21 08:00:00,10153.0 -2012-05-21 09:00:00,10895.0 -2012-05-21 10:00:00,11377.0 -2012-05-21 11:00:00,11596.0 -2012-05-21 12:00:00,11809.0 -2012-05-21 13:00:00,11846.0 -2012-05-21 14:00:00,11892.0 -2012-05-21 15:00:00,12008.0 -2012-05-21 16:00:00,11948.0 -2012-05-21 17:00:00,11893.0 -2012-05-21 18:00:00,11834.0 -2012-05-21 19:00:00,11650.0 -2012-05-21 20:00:00,11338.0 -2012-05-21 21:00:00,11102.0 -2012-05-21 22:00:00,11366.0 -2012-05-21 23:00:00,11106.0 -2012-05-22 00:00:00,10237.0 -2012-05-20 01:00:00,11200.0 -2012-05-20 02:00:00,10430.0 -2012-05-20 03:00:00,9895.0 -2012-05-20 04:00:00,9409.0 -2012-05-20 05:00:00,9101.0 -2012-05-20 06:00:00,8891.0 -2012-05-20 07:00:00,8683.0 -2012-05-20 08:00:00,8814.0 -2012-05-20 09:00:00,9470.0 -2012-05-20 10:00:00,10534.0 -2012-05-20 11:00:00,11600.0 -2012-05-20 12:00:00,12694.0 -2012-05-20 13:00:00,13484.0 -2012-05-20 14:00:00,14032.0 -2012-05-20 15:00:00,14431.0 -2012-05-20 16:00:00,14778.0 -2012-05-20 17:00:00,14981.0 -2012-05-20 18:00:00,14975.0 -2012-05-20 19:00:00,14384.0 -2012-05-20 20:00:00,13756.0 -2012-05-20 21:00:00,13334.0 -2012-05-20 22:00:00,13418.0 -2012-05-20 23:00:00,13003.0 -2012-05-21 00:00:00,11951.0 -2012-05-19 01:00:00,10155.0 -2012-05-19 02:00:00,9469.0 -2012-05-19 03:00:00,8955.0 -2012-05-19 04:00:00,8601.0 -2012-05-19 05:00:00,8424.0 -2012-05-19 06:00:00,8352.0 -2012-05-19 07:00:00,8376.0 -2012-05-19 08:00:00,8625.0 -2012-05-19 09:00:00,9329.0 -2012-05-19 10:00:00,10206.0 -2012-05-19 11:00:00,11062.0 -2012-05-19 12:00:00,11874.0 -2012-05-19 13:00:00,12478.0 -2012-05-19 14:00:00,12922.0 -2012-05-19 15:00:00,13195.0 -2012-05-19 16:00:00,13469.0 -2012-05-19 17:00:00,13696.0 -2012-05-19 18:00:00,13914.0 -2012-05-19 19:00:00,13917.0 -2012-05-19 20:00:00,13630.0 -2012-05-19 21:00:00,13242.0 -2012-05-19 22:00:00,13185.0 -2012-05-19 23:00:00,12879.0 -2012-05-20 00:00:00,12069.0 -2012-05-18 01:00:00,9444.0 -2012-05-18 02:00:00,8807.0 -2012-05-18 03:00:00,8449.0 -2012-05-18 04:00:00,8206.0 -2012-05-18 05:00:00,8113.0 -2012-05-18 06:00:00,8241.0 -2012-05-18 07:00:00,8612.0 -2012-05-18 08:00:00,9499.0 -2012-05-18 09:00:00,10499.0 -2012-05-18 10:00:00,11127.0 -2012-05-18 11:00:00,11572.0 -2012-05-18 12:00:00,11998.0 -2012-05-18 13:00:00,12222.0 -2012-05-18 14:00:00,12403.0 -2012-05-18 15:00:00,12641.0 -2012-05-18 16:00:00,12774.0 -2012-05-18 17:00:00,12867.0 -2012-05-18 18:00:00,12900.0 -2012-05-18 19:00:00,12780.0 -2012-05-18 20:00:00,12405.0 -2012-05-18 21:00:00,12069.0 -2012-05-18 22:00:00,12168.0 -2012-05-18 23:00:00,11912.0 -2012-05-19 00:00:00,11076.0 -2012-05-17 01:00:00,9211.0 -2012-05-17 02:00:00,8712.0 -2012-05-17 03:00:00,8353.0 -2012-05-17 04:00:00,8136.0 -2012-05-17 05:00:00,8068.0 -2012-05-17 06:00:00,8222.0 -2012-05-17 07:00:00,8655.0 -2012-05-17 08:00:00,9531.0 -2012-05-17 09:00:00,10403.0 -2012-05-17 10:00:00,10962.0 -2012-05-17 11:00:00,11335.0 -2012-05-17 12:00:00,11611.0 -2012-05-17 13:00:00,11758.0 -2012-05-17 14:00:00,11858.0 -2012-05-17 15:00:00,11996.0 -2012-05-17 16:00:00,12020.0 -2012-05-17 17:00:00,11992.0 -2012-05-17 18:00:00,11868.0 -2012-05-17 19:00:00,11606.0 -2012-05-17 20:00:00,11255.0 -2012-05-17 21:00:00,11111.0 -2012-05-17 22:00:00,11474.0 -2012-05-17 23:00:00,11171.0 -2012-05-18 00:00:00,10303.0 -2012-05-16 01:00:00,10315.0 -2012-05-16 02:00:00,9384.0 -2012-05-16 03:00:00,8812.0 -2012-05-16 04:00:00,8421.0 -2012-05-16 05:00:00,8263.0 -2012-05-16 06:00:00,8337.0 -2012-05-16 07:00:00,8753.0 -2012-05-16 08:00:00,9553.0 -2012-05-16 09:00:00,10439.0 -2012-05-16 10:00:00,10898.0 -2012-05-16 11:00:00,11224.0 -2012-05-16 12:00:00,11427.0 -2012-05-16 13:00:00,11525.0 -2012-05-16 14:00:00,11578.0 -2012-05-16 15:00:00,11643.0 -2012-05-16 16:00:00,11592.0 -2012-05-16 17:00:00,11527.0 -2012-05-16 18:00:00,11424.0 -2012-05-16 19:00:00,11230.0 -2012-05-16 20:00:00,10885.0 -2012-05-16 21:00:00,10760.0 -2012-05-16 22:00:00,11136.0 -2012-05-16 23:00:00,10883.0 -2012-05-17 00:00:00,10069.0 -2012-05-15 01:00:00,9568.0 -2012-05-15 02:00:00,8957.0 -2012-05-15 03:00:00,8580.0 -2012-05-15 04:00:00,8297.0 -2012-05-15 05:00:00,8195.0 -2012-05-15 06:00:00,8349.0 -2012-05-15 07:00:00,8775.0 -2012-05-15 08:00:00,9754.0 -2012-05-15 09:00:00,10758.0 -2012-05-15 10:00:00,11418.0 -2012-05-15 11:00:00,11929.0 -2012-05-15 12:00:00,12396.0 -2012-05-15 13:00:00,12667.0 -2012-05-15 14:00:00,12866.0 -2012-05-15 15:00:00,13130.0 -2012-05-15 16:00:00,13288.0 -2012-05-15 17:00:00,13442.0 -2012-05-15 18:00:00,13468.0 -2012-05-15 19:00:00,13378.0 -2012-05-15 20:00:00,12996.0 -2012-05-15 21:00:00,12935.0 -2012-05-15 22:00:00,12958.0 -2012-05-15 23:00:00,12342.0 -2012-05-16 00:00:00,11296.0 -2012-05-14 01:00:00,8333.0 -2012-05-14 02:00:00,7953.0 -2012-05-14 03:00:00,7678.0 -2012-05-14 04:00:00,7586.0 -2012-05-14 05:00:00,7550.0 -2012-05-14 06:00:00,7824.0 -2012-05-14 07:00:00,8367.0 -2012-05-14 08:00:00,9330.0 -2012-05-14 09:00:00,10219.0 -2012-05-14 10:00:00,10904.0 -2012-05-14 11:00:00,11326.0 -2012-05-14 12:00:00,11690.0 -2012-05-14 13:00:00,11917.0 -2012-05-14 14:00:00,12039.0 -2012-05-14 15:00:00,12233.0 -2012-05-14 16:00:00,12256.0 -2012-05-14 17:00:00,12234.0 -2012-05-14 18:00:00,12246.0 -2012-05-14 19:00:00,12103.0 -2012-05-14 20:00:00,11769.0 -2012-05-14 21:00:00,11559.0 -2012-05-14 22:00:00,11878.0 -2012-05-14 23:00:00,11464.0 -2012-05-15 00:00:00,10506.0 -2012-05-13 01:00:00,8556.0 -2012-05-13 02:00:00,8100.0 -2012-05-13 03:00:00,7702.0 -2012-05-13 04:00:00,7542.0 -2012-05-13 05:00:00,7445.0 -2012-05-13 06:00:00,7432.0 -2012-05-13 07:00:00,7411.0 -2012-05-13 08:00:00,7382.0 -2012-05-13 09:00:00,7707.0 -2012-05-13 10:00:00,8147.0 -2012-05-13 11:00:00,8503.0 -2012-05-13 12:00:00,8718.0 -2012-05-13 13:00:00,8851.0 -2012-05-13 14:00:00,8884.0 -2012-05-13 15:00:00,8867.0 -2012-05-13 16:00:00,8870.0 -2012-05-13 17:00:00,8883.0 -2012-05-13 18:00:00,8934.0 -2012-05-13 19:00:00,8954.0 -2012-05-13 20:00:00,8967.0 -2012-05-13 21:00:00,9010.0 -2012-05-13 22:00:00,9607.0 -2012-05-13 23:00:00,9506.0 -2012-05-14 00:00:00,8985.0 -2012-05-12 01:00:00,9654.0 -2012-05-12 02:00:00,8981.0 -2012-05-12 03:00:00,8575.0 -2012-05-12 04:00:00,8271.0 -2012-05-12 05:00:00,8139.0 -2012-05-12 06:00:00,8149.0 -2012-05-12 07:00:00,8237.0 -2012-05-12 08:00:00,8410.0 -2012-05-12 09:00:00,8858.0 -2012-05-12 10:00:00,9331.0 -2012-05-12 11:00:00,9603.0 -2012-05-12 12:00:00,9792.0 -2012-05-12 13:00:00,9783.0 -2012-05-12 14:00:00,9690.0 -2012-05-12 15:00:00,9542.0 -2012-05-12 16:00:00,9391.0 -2012-05-12 17:00:00,9280.0 -2012-05-12 18:00:00,9239.0 -2012-05-12 19:00:00,9190.0 -2012-05-12 20:00:00,9208.0 -2012-05-12 21:00:00,9386.0 -2012-05-12 22:00:00,9772.0 -2012-05-12 23:00:00,9610.0 -2012-05-13 00:00:00,9146.0 -2012-05-11 01:00:00,9255.0 -2012-05-11 02:00:00,8769.0 -2012-05-11 03:00:00,8417.0 -2012-05-11 04:00:00,8227.0 -2012-05-11 05:00:00,8134.0 -2012-05-11 06:00:00,8335.0 -2012-05-11 07:00:00,8763.0 -2012-05-11 08:00:00,9596.0 -2012-05-11 09:00:00,10436.0 -2012-05-11 10:00:00,10914.0 -2012-05-11 11:00:00,11260.0 -2012-05-11 12:00:00,11487.0 -2012-05-11 13:00:00,11648.0 -2012-05-11 14:00:00,11695.0 -2012-05-11 15:00:00,11821.0 -2012-05-11 16:00:00,11834.0 -2012-05-11 17:00:00,11764.0 -2012-05-11 18:00:00,11721.0 -2012-05-11 19:00:00,11506.0 -2012-05-11 20:00:00,11214.0 -2012-05-11 21:00:00,11135.0 -2012-05-11 22:00:00,11510.0 -2012-05-11 23:00:00,11242.0 -2012-05-12 00:00:00,10469.0 -2012-05-10 01:00:00,9208.0 -2012-05-10 02:00:00,8701.0 -2012-05-10 03:00:00,8368.0 -2012-05-10 04:00:00,8137.0 -2012-05-10 05:00:00,8112.0 -2012-05-10 06:00:00,8328.0 -2012-05-10 07:00:00,8860.0 -2012-05-10 08:00:00,9731.0 -2012-05-10 09:00:00,10533.0 -2012-05-10 10:00:00,10930.0 -2012-05-10 11:00:00,11186.0 -2012-05-10 12:00:00,11386.0 -2012-05-10 13:00:00,11422.0 -2012-05-10 14:00:00,11469.0 -2012-05-10 15:00:00,11574.0 -2012-05-10 16:00:00,11511.0 -2012-05-10 17:00:00,11379.0 -2012-05-10 18:00:00,11271.0 -2012-05-10 19:00:00,11085.0 -2012-05-10 20:00:00,10834.0 -2012-05-10 21:00:00,10801.0 -2012-05-10 22:00:00,11217.0 -2012-05-10 23:00:00,10931.0 -2012-05-11 00:00:00,10140.0 -2012-05-09 01:00:00,9321.0 -2012-05-09 02:00:00,8729.0 -2012-05-09 03:00:00,8341.0 -2012-05-09 04:00:00,8134.0 -2012-05-09 05:00:00,8050.0 -2012-05-09 06:00:00,8211.0 -2012-05-09 07:00:00,8642.0 -2012-05-09 08:00:00,9708.0 -2012-05-09 09:00:00,10432.0 -2012-05-09 10:00:00,10858.0 -2012-05-09 11:00:00,11084.0 -2012-05-09 12:00:00,11363.0 -2012-05-09 13:00:00,11352.0 -2012-05-09 14:00:00,11327.0 -2012-05-09 15:00:00,11320.0 -2012-05-09 16:00:00,11225.0 -2012-05-09 17:00:00,11088.0 -2012-05-09 18:00:00,10940.0 -2012-05-09 19:00:00,10740.0 -2012-05-09 20:00:00,10512.0 -2012-05-09 21:00:00,10554.0 -2012-05-09 22:00:00,11095.0 -2012-05-09 23:00:00,10779.0 -2012-05-10 00:00:00,10012.0 -2012-05-08 01:00:00,9248.0 -2012-05-08 02:00:00,8701.0 -2012-05-08 03:00:00,8376.0 -2012-05-08 04:00:00,8158.0 -2012-05-08 05:00:00,8077.0 -2012-05-08 06:00:00,8270.0 -2012-05-08 07:00:00,8799.0 -2012-05-08 08:00:00,9707.0 -2012-05-08 09:00:00,10572.0 -2012-05-08 10:00:00,11061.0 -2012-05-08 11:00:00,11392.0 -2012-05-08 12:00:00,11673.0 -2012-05-08 13:00:00,11771.0 -2012-05-08 14:00:00,11833.0 -2012-05-08 15:00:00,11887.0 -2012-05-08 16:00:00,11788.0 -2012-05-08 17:00:00,11671.0 -2012-05-08 18:00:00,11522.0 -2012-05-08 19:00:00,11314.0 -2012-05-08 20:00:00,11025.0 -2012-05-08 21:00:00,11029.0 -2012-05-08 22:00:00,11425.0 -2012-05-08 23:00:00,10984.0 -2012-05-09 00:00:00,10143.0 -2012-05-07 01:00:00,8609.0 -2012-05-07 02:00:00,8257.0 -2012-05-07 03:00:00,7981.0 -2012-05-07 04:00:00,7908.0 -2012-05-07 05:00:00,7802.0 -2012-05-07 06:00:00,8075.0 -2012-05-07 07:00:00,8708.0 -2012-05-07 08:00:00,9744.0 -2012-05-07 09:00:00,10625.0 -2012-05-07 10:00:00,11058.0 -2012-05-07 11:00:00,11312.0 -2012-05-07 12:00:00,11565.0 -2012-05-07 13:00:00,11525.0 -2012-05-07 14:00:00,11551.0 -2012-05-07 15:00:00,11601.0 -2012-05-07 16:00:00,11498.0 -2012-05-07 17:00:00,11387.0 -2012-05-07 18:00:00,11308.0 -2012-05-07 19:00:00,11194.0 -2012-05-07 20:00:00,10947.0 -2012-05-07 21:00:00,10926.0 -2012-05-07 22:00:00,11312.0 -2012-05-07 23:00:00,10924.0 -2012-05-08 00:00:00,10073.0 -2012-05-06 01:00:00,8636.0 -2012-05-06 02:00:00,8123.0 -2012-05-06 03:00:00,7850.0 -2012-05-06 04:00:00,7633.0 -2012-05-06 05:00:00,7545.0 -2012-05-06 06:00:00,7519.0 -2012-05-06 07:00:00,7478.0 -2012-05-06 08:00:00,7473.0 -2012-05-06 09:00:00,7719.0 -2012-05-06 10:00:00,8289.0 -2012-05-06 11:00:00,8635.0 -2012-05-06 12:00:00,9008.0 -2012-05-06 13:00:00,9351.0 -2012-05-06 14:00:00,9610.0 -2012-05-06 15:00:00,9482.0 -2012-05-06 16:00:00,9404.0 -2012-05-06 17:00:00,9346.0 -2012-05-06 18:00:00,9419.0 -2012-05-06 19:00:00,9555.0 -2012-05-06 20:00:00,9554.0 -2012-05-06 21:00:00,9582.0 -2012-05-06 22:00:00,10052.0 -2012-05-06 23:00:00,9778.0 -2012-05-07 00:00:00,9192.0 -2012-05-05 01:00:00,9329.0 -2012-05-05 02:00:00,8694.0 -2012-05-05 03:00:00,8253.0 -2012-05-05 04:00:00,7992.0 -2012-05-05 05:00:00,7880.0 -2012-05-05 06:00:00,7861.0 -2012-05-05 07:00:00,8068.0 -2012-05-05 08:00:00,8256.0 -2012-05-05 09:00:00,8699.0 -2012-05-05 10:00:00,9181.0 -2012-05-05 11:00:00,9565.0 -2012-05-05 12:00:00,9766.0 -2012-05-05 13:00:00,9754.0 -2012-05-05 14:00:00,9676.0 -2012-05-05 15:00:00,9520.0 -2012-05-05 16:00:00,9440.0 -2012-05-05 17:00:00,9382.0 -2012-05-05 18:00:00,9405.0 -2012-05-05 19:00:00,9362.0 -2012-05-05 20:00:00,9368.0 -2012-05-05 21:00:00,9483.0 -2012-05-05 22:00:00,9926.0 -2012-05-05 23:00:00,9734.0 -2012-05-06 00:00:00,9200.0 -2012-05-04 01:00:00,11447.0 -2012-05-04 02:00:00,10713.0 -2012-05-04 03:00:00,10202.0 -2012-05-04 04:00:00,9713.0 -2012-05-04 05:00:00,9406.0 -2012-05-04 06:00:00,9359.0 -2012-05-04 07:00:00,9881.0 -2012-05-04 08:00:00,10732.0 -2012-05-04 09:00:00,11528.0 -2012-05-04 10:00:00,11824.0 -2012-05-04 11:00:00,12066.0 -2012-05-04 12:00:00,12307.0 -2012-05-04 13:00:00,12448.0 -2012-05-04 14:00:00,12566.0 -2012-05-04 15:00:00,12691.0 -2012-05-04 16:00:00,12649.0 -2012-05-04 17:00:00,12510.0 -2012-05-04 18:00:00,12185.0 -2012-05-04 19:00:00,11763.0 -2012-05-04 20:00:00,11203.0 -2012-05-04 21:00:00,11036.0 -2012-05-04 22:00:00,11269.0 -2012-05-04 23:00:00,10856.0 -2012-05-05 00:00:00,10112.0 -2012-05-03 01:00:00,10067.0 -2012-05-03 02:00:00,9442.0 -2012-05-03 03:00:00,9011.0 -2012-05-03 04:00:00,8768.0 -2012-05-03 05:00:00,8664.0 -2012-05-03 06:00:00,8858.0 -2012-05-03 07:00:00,9498.0 -2012-05-03 08:00:00,10411.0 -2012-05-03 09:00:00,11460.0 -2012-05-03 10:00:00,12181.0 -2012-05-03 11:00:00,12805.0 -2012-05-03 12:00:00,13365.0 -2012-05-03 13:00:00,13676.0 -2012-05-03 14:00:00,13900.0 -2012-05-03 15:00:00,14171.0 -2012-05-03 16:00:00,14368.0 -2012-05-03 17:00:00,14566.0 -2012-05-03 18:00:00,14630.0 -2012-05-03 19:00:00,14496.0 -2012-05-03 20:00:00,14161.0 -2012-05-03 21:00:00,14137.0 -2012-05-03 22:00:00,14304.0 -2012-05-03 23:00:00,13820.0 -2012-05-04 00:00:00,12673.0 -2012-05-02 01:00:00,9269.0 -2012-05-02 02:00:00,8729.0 -2012-05-02 03:00:00,8428.0 -2012-05-02 04:00:00,8213.0 -2012-05-02 05:00:00,8159.0 -2012-05-02 06:00:00,8364.0 -2012-05-02 07:00:00,9014.0 -2012-05-02 08:00:00,9937.0 -2012-05-02 09:00:00,10941.0 -2012-05-02 10:00:00,11445.0 -2012-05-02 11:00:00,11735.0 -2012-05-02 12:00:00,12031.0 -2012-05-02 13:00:00,12103.0 -2012-05-02 14:00:00,12097.0 -2012-05-02 15:00:00,12142.0 -2012-05-02 16:00:00,12111.0 -2012-05-02 17:00:00,12094.0 -2012-05-02 18:00:00,12077.0 -2012-05-02 19:00:00,11981.0 -2012-05-02 20:00:00,11731.0 -2012-05-02 21:00:00,11890.0 -2012-05-02 22:00:00,12272.0 -2012-05-02 23:00:00,11845.0 -2012-05-03 00:00:00,10937.0 -2012-05-01 01:00:00,9214.0 -2012-05-01 02:00:00,8678.0 -2012-05-01 03:00:00,8350.0 -2012-05-01 04:00:00,8153.0 -2012-05-01 05:00:00,8083.0 -2012-05-01 06:00:00,8272.0 -2012-05-01 07:00:00,8866.0 -2012-05-01 08:00:00,9759.0 -2012-05-01 09:00:00,10548.0 -2012-05-01 10:00:00,10892.0 -2012-05-01 11:00:00,11106.0 -2012-05-01 12:00:00,11247.0 -2012-05-01 13:00:00,11305.0 -2012-05-01 14:00:00,11357.0 -2012-05-01 15:00:00,11374.0 -2012-05-01 16:00:00,11261.0 -2012-05-01 17:00:00,11196.0 -2012-05-01 18:00:00,11147.0 -2012-05-01 19:00:00,11103.0 -2012-05-01 20:00:00,10995.0 -2012-05-01 21:00:00,11165.0 -2012-05-01 22:00:00,11396.0 -2012-05-01 23:00:00,10893.0 -2012-05-02 00:00:00,10074.0 -2012-04-30 01:00:00,8563.0 -2012-04-30 02:00:00,8194.0 -2012-04-30 03:00:00,7956.0 -2012-04-30 04:00:00,7851.0 -2012-04-30 05:00:00,7865.0 -2012-04-30 06:00:00,8101.0 -2012-04-30 07:00:00,8850.0 -2012-04-30 08:00:00,9852.0 -2012-04-30 09:00:00,10634.0 -2012-04-30 10:00:00,10976.0 -2012-04-30 11:00:00,11247.0 -2012-04-30 12:00:00,11413.0 -2012-04-30 13:00:00,11558.0 -2012-04-30 14:00:00,11584.0 -2012-04-30 15:00:00,11600.0 -2012-04-30 16:00:00,11405.0 -2012-04-30 17:00:00,11262.0 -2012-04-30 18:00:00,11137.0 -2012-04-30 19:00:00,11071.0 -2012-04-30 20:00:00,11004.0 -2012-04-30 21:00:00,11240.0 -2012-04-30 22:00:00,11296.0 -2012-04-30 23:00:00,10790.0 -2012-05-01 00:00:00,9993.0 -2012-04-29 01:00:00,8937.0 -2012-04-29 02:00:00,8511.0 -2012-04-29 03:00:00,8157.0 -2012-04-29 04:00:00,8040.0 -2012-04-29 05:00:00,7939.0 -2012-04-29 06:00:00,7951.0 -2012-04-29 07:00:00,8034.0 -2012-04-29 08:00:00,7901.0 -2012-04-29 09:00:00,8150.0 -2012-04-29 10:00:00,8490.0 -2012-04-29 11:00:00,8714.0 -2012-04-29 12:00:00,8806.0 -2012-04-29 13:00:00,8917.0 -2012-04-29 14:00:00,8912.0 -2012-04-29 15:00:00,8879.0 -2012-04-29 16:00:00,8849.0 -2012-04-29 17:00:00,8933.0 -2012-04-29 18:00:00,9127.0 -2012-04-29 19:00:00,9326.0 -2012-04-29 20:00:00,9521.0 -2012-04-29 21:00:00,9834.0 -2012-04-29 22:00:00,10041.0 -2012-04-29 23:00:00,9716.0 -2012-04-30 00:00:00,9156.0 -2012-04-28 01:00:00,9435.0 -2012-04-28 02:00:00,8890.0 -2012-04-28 03:00:00,8507.0 -2012-04-28 04:00:00,8324.0 -2012-04-28 05:00:00,8179.0 -2012-04-28 06:00:00,8247.0 -2012-04-28 07:00:00,8481.0 -2012-04-28 08:00:00,8909.0 -2012-04-28 09:00:00,9271.0 -2012-04-28 10:00:00,9773.0 -2012-04-28 11:00:00,10111.0 -2012-04-28 12:00:00,10276.0 -2012-04-28 13:00:00,10235.0 -2012-04-28 14:00:00,10137.0 -2012-04-28 15:00:00,9886.0 -2012-04-28 16:00:00,9709.0 -2012-04-28 17:00:00,9563.0 -2012-04-28 18:00:00,9551.0 -2012-04-28 19:00:00,9424.0 -2012-04-28 20:00:00,9430.0 -2012-04-28 21:00:00,9688.0 -2012-04-28 22:00:00,10189.0 -2012-04-28 23:00:00,9967.0 -2012-04-29 00:00:00,9512.0 -2012-04-27 01:00:00,9470.0 -2012-04-27 02:00:00,8972.0 -2012-04-27 03:00:00,8707.0 -2012-04-27 04:00:00,8525.0 -2012-04-27 05:00:00,8504.0 -2012-04-27 06:00:00,8753.0 -2012-04-27 07:00:00,9389.0 -2012-04-27 08:00:00,10213.0 -2012-04-27 09:00:00,10840.0 -2012-04-27 10:00:00,11121.0 -2012-04-27 11:00:00,11197.0 -2012-04-27 12:00:00,11249.0 -2012-04-27 13:00:00,11201.0 -2012-04-27 14:00:00,11090.0 -2012-04-27 15:00:00,11078.0 -2012-04-27 16:00:00,10929.0 -2012-04-27 17:00:00,10768.0 -2012-04-27 18:00:00,10702.0 -2012-04-27 19:00:00,10667.0 -2012-04-27 20:00:00,10601.0 -2012-04-27 21:00:00,10748.0 -2012-04-27 22:00:00,11117.0 -2012-04-27 23:00:00,10833.0 -2012-04-28 00:00:00,10155.0 -2012-04-26 01:00:00,9239.0 -2012-04-26 02:00:00,8712.0 -2012-04-26 03:00:00,8410.0 -2012-04-26 04:00:00,8213.0 -2012-04-26 05:00:00,8167.0 -2012-04-26 06:00:00,8373.0 -2012-04-26 07:00:00,9028.0 -2012-04-26 08:00:00,9856.0 -2012-04-26 09:00:00,10588.0 -2012-04-26 10:00:00,10942.0 -2012-04-26 11:00:00,11077.0 -2012-04-26 12:00:00,11304.0 -2012-04-26 13:00:00,11302.0 -2012-04-26 14:00:00,11242.0 -2012-04-26 15:00:00,11157.0 -2012-04-26 16:00:00,11004.0 -2012-04-26 17:00:00,10851.0 -2012-04-26 18:00:00,10732.0 -2012-04-26 19:00:00,10566.0 -2012-04-26 20:00:00,10390.0 -2012-04-26 21:00:00,10652.0 -2012-04-26 22:00:00,11228.0 -2012-04-26 23:00:00,10897.0 -2012-04-27 00:00:00,10193.0 -2012-04-25 01:00:00,9186.0 -2012-04-25 02:00:00,8687.0 -2012-04-25 03:00:00,8387.0 -2012-04-25 04:00:00,8215.0 -2012-04-25 05:00:00,8156.0 -2012-04-25 06:00:00,8356.0 -2012-04-25 07:00:00,9026.0 -2012-04-25 08:00:00,9913.0 -2012-04-25 09:00:00,10739.0 -2012-04-25 10:00:00,11169.0 -2012-04-25 11:00:00,11206.0 -2012-04-25 12:00:00,11384.0 -2012-04-25 13:00:00,11449.0 -2012-04-25 14:00:00,11451.0 -2012-04-25 15:00:00,11447.0 -2012-04-25 16:00:00,11319.0 -2012-04-25 17:00:00,11223.0 -2012-04-25 18:00:00,11084.0 -2012-04-25 19:00:00,10927.0 -2012-04-25 20:00:00,10676.0 -2012-04-25 21:00:00,10930.0 -2012-04-25 22:00:00,11256.0 -2012-04-25 23:00:00,10804.0 -2012-04-26 00:00:00,9981.0 -2012-04-24 01:00:00,9296.0 -2012-04-24 02:00:00,8823.0 -2012-04-24 03:00:00,8565.0 -2012-04-24 04:00:00,8380.0 -2012-04-24 05:00:00,8397.0 -2012-04-24 06:00:00,8614.0 -2012-04-24 07:00:00,9301.0 -2012-04-24 08:00:00,10175.0 -2012-04-24 09:00:00,10795.0 -2012-04-24 10:00:00,11038.0 -2012-04-24 11:00:00,11155.0 -2012-04-24 12:00:00,11252.0 -2012-04-24 13:00:00,11286.0 -2012-04-24 14:00:00,11269.0 -2012-04-24 15:00:00,11294.0 -2012-04-24 16:00:00,11179.0 -2012-04-24 17:00:00,11024.0 -2012-04-24 18:00:00,10873.0 -2012-04-24 19:00:00,10730.0 -2012-04-24 20:00:00,10515.0 -2012-04-24 21:00:00,10694.0 -2012-04-24 22:00:00,11153.0 -2012-04-24 23:00:00,10770.0 -2012-04-25 00:00:00,9958.0 -2012-04-23 01:00:00,8800.0 -2012-04-23 02:00:00,8469.0 -2012-04-23 03:00:00,8267.0 -2012-04-23 04:00:00,8198.0 -2012-04-23 05:00:00,8243.0 -2012-04-23 06:00:00,8546.0 -2012-04-23 07:00:00,9307.0 -2012-04-23 08:00:00,10229.0 -2012-04-23 09:00:00,10883.0 -2012-04-23 10:00:00,11157.0 -2012-04-23 11:00:00,11210.0 -2012-04-23 12:00:00,11362.0 -2012-04-23 13:00:00,11329.0 -2012-04-23 14:00:00,11284.0 -2012-04-23 15:00:00,11269.0 -2012-04-23 16:00:00,11173.0 -2012-04-23 17:00:00,11002.0 -2012-04-23 18:00:00,10866.0 -2012-04-23 19:00:00,10728.0 -2012-04-23 20:00:00,10576.0 -2012-04-23 21:00:00,10793.0 -2012-04-23 22:00:00,11224.0 -2012-04-23 23:00:00,10836.0 -2012-04-24 00:00:00,10079.0 -2012-04-22 01:00:00,8930.0 -2012-04-22 02:00:00,8480.0 -2012-04-22 03:00:00,8127.0 -2012-04-22 04:00:00,8011.0 -2012-04-22 05:00:00,7897.0 -2012-04-22 06:00:00,7913.0 -2012-04-22 07:00:00,8050.0 -2012-04-22 08:00:00,8039.0 -2012-04-22 09:00:00,8227.0 -2012-04-22 10:00:00,8576.0 -2012-04-22 11:00:00,8760.0 -2012-04-22 12:00:00,8946.0 -2012-04-22 13:00:00,8990.0 -2012-04-22 14:00:00,9000.0 -2012-04-22 15:00:00,8939.0 -2012-04-22 16:00:00,8888.0 -2012-04-22 17:00:00,8859.0 -2012-04-22 18:00:00,8913.0 -2012-04-22 19:00:00,9015.0 -2012-04-22 20:00:00,9109.0 -2012-04-22 21:00:00,9501.0 -2012-04-22 22:00:00,10072.0 -2012-04-22 23:00:00,9894.0 -2012-04-23 00:00:00,9341.0 -2012-04-21 01:00:00,9543.0 -2012-04-21 02:00:00,8987.0 -2012-04-21 03:00:00,8647.0 -2012-04-21 04:00:00,8406.0 -2012-04-21 05:00:00,8357.0 -2012-04-21 06:00:00,8398.0 -2012-04-21 07:00:00,8668.0 -2012-04-21 08:00:00,8800.0 -2012-04-21 09:00:00,9190.0 -2012-04-21 10:00:00,9534.0 -2012-04-21 11:00:00,9762.0 -2012-04-21 12:00:00,9827.0 -2012-04-21 13:00:00,9780.0 -2012-04-21 14:00:00,9603.0 -2012-04-21 15:00:00,9426.0 -2012-04-21 16:00:00,9248.0 -2012-04-21 17:00:00,9171.0 -2012-04-21 18:00:00,9113.0 -2012-04-21 19:00:00,9142.0 -2012-04-21 20:00:00,9213.0 -2012-04-21 21:00:00,9645.0 -2012-04-21 22:00:00,10078.0 -2012-04-21 23:00:00,9905.0 -2012-04-22 00:00:00,9452.0 -2012-04-20 01:00:00,9268.0 -2012-04-20 02:00:00,8766.0 -2012-04-20 03:00:00,8438.0 -2012-04-20 04:00:00,8235.0 -2012-04-20 05:00:00,8160.0 -2012-04-20 06:00:00,8367.0 -2012-04-20 07:00:00,9050.0 -2012-04-20 08:00:00,10103.0 -2012-04-20 09:00:00,10926.0 -2012-04-20 10:00:00,11297.0 -2012-04-20 11:00:00,11523.0 -2012-04-20 12:00:00,11672.0 -2012-04-20 13:00:00,11626.0 -2012-04-20 14:00:00,11536.0 -2012-04-20 15:00:00,11550.0 -2012-04-20 16:00:00,11407.0 -2012-04-20 17:00:00,11309.0 -2012-04-20 18:00:00,11192.0 -2012-04-20 19:00:00,11065.0 -2012-04-20 20:00:00,10926.0 -2012-04-20 21:00:00,11078.0 -2012-04-20 22:00:00,11312.0 -2012-04-20 23:00:00,11007.0 -2012-04-21 00:00:00,10287.0 -2012-04-19 01:00:00,9332.0 -2012-04-19 02:00:00,8755.0 -2012-04-19 03:00:00,8385.0 -2012-04-19 04:00:00,8159.0 -2012-04-19 05:00:00,8086.0 -2012-04-19 06:00:00,8262.0 -2012-04-19 07:00:00,8898.0 -2012-04-19 08:00:00,9829.0 -2012-04-19 09:00:00,10630.0 -2012-04-19 10:00:00,10986.0 -2012-04-19 11:00:00,11140.0 -2012-04-19 12:00:00,11303.0 -2012-04-19 13:00:00,11324.0 -2012-04-19 14:00:00,11363.0 -2012-04-19 15:00:00,11396.0 -2012-04-19 16:00:00,11279.0 -2012-04-19 17:00:00,11145.0 -2012-04-19 18:00:00,11037.0 -2012-04-19 19:00:00,10879.0 -2012-04-19 20:00:00,10844.0 -2012-04-19 21:00:00,11121.0 -2012-04-19 22:00:00,11316.0 -2012-04-19 23:00:00,10845.0 -2012-04-20 00:00:00,10042.0 -2012-04-18 01:00:00,9340.0 -2012-04-18 02:00:00,8840.0 -2012-04-18 03:00:00,8537.0 -2012-04-18 04:00:00,8350.0 -2012-04-18 05:00:00,8326.0 -2012-04-18 06:00:00,8533.0 -2012-04-18 07:00:00,9197.0 -2012-04-18 08:00:00,10107.0 -2012-04-18 09:00:00,10725.0 -2012-04-18 10:00:00,11030.0 -2012-04-18 11:00:00,11207.0 -2012-04-18 12:00:00,11384.0 -2012-04-18 13:00:00,11468.0 -2012-04-18 14:00:00,11508.0 -2012-04-18 15:00:00,11605.0 -2012-04-18 16:00:00,11540.0 -2012-04-18 17:00:00,11430.0 -2012-04-18 18:00:00,11302.0 -2012-04-18 19:00:00,11184.0 -2012-04-18 20:00:00,11025.0 -2012-04-18 21:00:00,11324.0 -2012-04-18 22:00:00,11543.0 -2012-04-18 23:00:00,11027.0 -2012-04-19 00:00:00,10148.0 -2012-04-17 01:00:00,9234.0 -2012-04-17 02:00:00,8751.0 -2012-04-17 03:00:00,8445.0 -2012-04-17 04:00:00,8259.0 -2012-04-17 05:00:00,8209.0 -2012-04-17 06:00:00,8449.0 -2012-04-17 07:00:00,9209.0 -2012-04-17 08:00:00,10115.0 -2012-04-17 09:00:00,10751.0 -2012-04-17 10:00:00,11037.0 -2012-04-17 11:00:00,11098.0 -2012-04-17 12:00:00,11226.0 -2012-04-17 13:00:00,11211.0 -2012-04-17 14:00:00,11162.0 -2012-04-17 15:00:00,11204.0 -2012-04-17 16:00:00,11111.0 -2012-04-17 17:00:00,10951.0 -2012-04-17 18:00:00,10827.0 -2012-04-17 19:00:00,10650.0 -2012-04-17 20:00:00,10476.0 -2012-04-17 21:00:00,10791.0 -2012-04-17 22:00:00,11238.0 -2012-04-17 23:00:00,10868.0 -2012-04-18 00:00:00,10092.0 -2012-04-16 01:00:00,9038.0 -2012-04-16 02:00:00,8581.0 -2012-04-16 03:00:00,8290.0 -2012-04-16 04:00:00,8035.0 -2012-04-16 05:00:00,7987.0 -2012-04-16 06:00:00,8110.0 -2012-04-16 07:00:00,8781.0 -2012-04-16 08:00:00,9685.0 -2012-04-16 09:00:00,10548.0 -2012-04-16 10:00:00,10946.0 -2012-04-16 11:00:00,11163.0 -2012-04-16 12:00:00,11337.0 -2012-04-16 13:00:00,11331.0 -2012-04-16 14:00:00,11309.0 -2012-04-16 15:00:00,11338.0 -2012-04-16 16:00:00,11219.0 -2012-04-16 17:00:00,11031.0 -2012-04-16 18:00:00,10861.0 -2012-04-16 19:00:00,10727.0 -2012-04-16 20:00:00,10578.0 -2012-04-16 21:00:00,10860.0 -2012-04-16 22:00:00,11262.0 -2012-04-16 23:00:00,10832.0 -2012-04-17 00:00:00,10027.0 -2012-04-15 01:00:00,8905.0 -2012-04-15 02:00:00,8438.0 -2012-04-15 03:00:00,8136.0 -2012-04-15 04:00:00,7836.0 -2012-04-15 05:00:00,7732.0 -2012-04-15 06:00:00,7631.0 -2012-04-15 07:00:00,7756.0 -2012-04-15 08:00:00,7751.0 -2012-04-15 09:00:00,7928.0 -2012-04-15 10:00:00,8361.0 -2012-04-15 11:00:00,8770.0 -2012-04-15 12:00:00,9104.0 -2012-04-15 13:00:00,9360.0 -2012-04-15 14:00:00,9428.0 -2012-04-15 15:00:00,9552.0 -2012-04-15 16:00:00,9526.0 -2012-04-15 17:00:00,9585.0 -2012-04-15 18:00:00,9622.0 -2012-04-15 19:00:00,9810.0 -2012-04-15 20:00:00,10058.0 -2012-04-15 21:00:00,10430.0 -2012-04-15 22:00:00,10549.0 -2012-04-15 23:00:00,10262.0 -2012-04-16 00:00:00,9700.0 -2012-04-14 01:00:00,9249.0 -2012-04-14 02:00:00,8679.0 -2012-04-14 03:00:00,8341.0 -2012-04-14 04:00:00,8102.0 -2012-04-14 05:00:00,8006.0 -2012-04-14 06:00:00,8005.0 -2012-04-14 07:00:00,8267.0 -2012-04-14 08:00:00,8489.0 -2012-04-14 09:00:00,8859.0 -2012-04-14 10:00:00,9313.0 -2012-04-14 11:00:00,9651.0 -2012-04-14 12:00:00,9838.0 -2012-04-14 13:00:00,9831.0 -2012-04-14 14:00:00,9818.0 -2012-04-14 15:00:00,9675.0 -2012-04-14 16:00:00,9613.0 -2012-04-14 17:00:00,9592.0 -2012-04-14 18:00:00,9565.0 -2012-04-14 19:00:00,9600.0 -2012-04-14 20:00:00,9551.0 -2012-04-14 21:00:00,9989.0 -2012-04-14 22:00:00,10207.0 -2012-04-14 23:00:00,9968.0 -2012-04-15 00:00:00,9490.0 -2012-04-13 01:00:00,9423.0 -2012-04-13 02:00:00,8883.0 -2012-04-13 03:00:00,8569.0 -2012-04-13 04:00:00,8381.0 -2012-04-13 05:00:00,8367.0 -2012-04-13 06:00:00,8550.0 -2012-04-13 07:00:00,9239.0 -2012-04-13 08:00:00,10081.0 -2012-04-13 09:00:00,10695.0 -2012-04-13 10:00:00,11021.0 -2012-04-13 11:00:00,11049.0 -2012-04-13 12:00:00,11229.0 -2012-04-13 13:00:00,11234.0 -2012-04-13 14:00:00,11232.0 -2012-04-13 15:00:00,11240.0 -2012-04-13 16:00:00,11097.0 -2012-04-13 17:00:00,10923.0 -2012-04-13 18:00:00,10769.0 -2012-04-13 19:00:00,10655.0 -2012-04-13 20:00:00,10521.0 -2012-04-13 21:00:00,10812.0 -2012-04-13 22:00:00,11036.0 -2012-04-13 23:00:00,10676.0 -2012-04-14 00:00:00,9969.0 -2012-04-12 01:00:00,9556.0 -2012-04-12 02:00:00,9090.0 -2012-04-12 03:00:00,8801.0 -2012-04-12 04:00:00,8647.0 -2012-04-12 05:00:00,8641.0 -2012-04-12 06:00:00,8867.0 -2012-04-12 07:00:00,9612.0 -2012-04-12 08:00:00,10538.0 -2012-04-12 09:00:00,11046.0 -2012-04-12 10:00:00,11228.0 -2012-04-12 11:00:00,11311.0 -2012-04-12 12:00:00,11372.0 -2012-04-12 13:00:00,11334.0 -2012-04-12 14:00:00,11305.0 -2012-04-12 15:00:00,11295.0 -2012-04-12 16:00:00,11204.0 -2012-04-12 17:00:00,11063.0 -2012-04-12 18:00:00,10907.0 -2012-04-12 19:00:00,10732.0 -2012-04-12 20:00:00,10636.0 -2012-04-12 21:00:00,11053.0 -2012-04-12 22:00:00,11312.0 -2012-04-12 23:00:00,10899.0 -2012-04-13 00:00:00,10110.0 -2012-04-11 01:00:00,9702.0 -2012-04-11 02:00:00,9256.0 -2012-04-11 03:00:00,8973.0 -2012-04-11 04:00:00,8795.0 -2012-04-11 05:00:00,8819.0 -2012-04-11 06:00:00,9025.0 -2012-04-11 07:00:00,9769.0 -2012-04-11 08:00:00,10709.0 -2012-04-11 09:00:00,11256.0 -2012-04-11 10:00:00,11446.0 -2012-04-11 11:00:00,11486.0 -2012-04-11 12:00:00,11493.0 -2012-04-11 13:00:00,11428.0 -2012-04-11 14:00:00,11365.0 -2012-04-11 15:00:00,11318.0 -2012-04-11 16:00:00,11171.0 -2012-04-11 17:00:00,11002.0 -2012-04-11 18:00:00,10849.0 -2012-04-11 19:00:00,10729.0 -2012-04-11 20:00:00,10617.0 -2012-04-11 21:00:00,11070.0 -2012-04-11 22:00:00,11462.0 -2012-04-11 23:00:00,11081.0 -2012-04-12 00:00:00,10311.0 -2012-04-10 01:00:00,9179.0 -2012-04-10 02:00:00,8733.0 -2012-04-10 03:00:00,8487.0 -2012-04-10 04:00:00,8323.0 -2012-04-10 05:00:00,8351.0 -2012-04-10 06:00:00,8609.0 -2012-04-10 07:00:00,9358.0 -2012-04-10 08:00:00,10374.0 -2012-04-10 09:00:00,11009.0 -2012-04-10 10:00:00,11317.0 -2012-04-10 11:00:00,11446.0 -2012-04-10 12:00:00,11522.0 -2012-04-10 13:00:00,11482.0 -2012-04-10 14:00:00,11466.0 -2012-04-10 15:00:00,11467.0 -2012-04-10 16:00:00,11325.0 -2012-04-10 17:00:00,11143.0 -2012-04-10 18:00:00,10998.0 -2012-04-10 19:00:00,10950.0 -2012-04-10 20:00:00,10859.0 -2012-04-10 21:00:00,11314.0 -2012-04-10 22:00:00,11638.0 -2012-04-10 23:00:00,11214.0 -2012-04-11 00:00:00,10451.0 -2012-04-09 01:00:00,8222.0 -2012-04-09 02:00:00,7858.0 -2012-04-09 03:00:00,7683.0 -2012-04-09 04:00:00,7564.0 -2012-04-09 05:00:00,7626.0 -2012-04-09 06:00:00,7895.0 -2012-04-09 07:00:00,8609.0 -2012-04-09 08:00:00,9610.0 -2012-04-09 09:00:00,10236.0 -2012-04-09 10:00:00,10624.0 -2012-04-09 11:00:00,10899.0 -2012-04-09 12:00:00,11087.0 -2012-04-09 13:00:00,11154.0 -2012-04-09 14:00:00,11153.0 -2012-04-09 15:00:00,11180.0 -2012-04-09 16:00:00,11096.0 -2012-04-09 17:00:00,10918.0 -2012-04-09 18:00:00,10775.0 -2012-04-09 19:00:00,10624.0 -2012-04-09 20:00:00,10484.0 -2012-04-09 21:00:00,10884.0 -2012-04-09 22:00:00,11170.0 -2012-04-09 23:00:00,10720.0 -2012-04-10 00:00:00,9924.0 -2012-04-08 01:00:00,8536.0 -2012-04-08 02:00:00,8012.0 -2012-04-08 03:00:00,7706.0 -2012-04-08 04:00:00,7557.0 -2012-04-08 05:00:00,7413.0 -2012-04-08 06:00:00,7471.0 -2012-04-08 07:00:00,7585.0 -2012-04-08 08:00:00,7679.0 -2012-04-08 09:00:00,7771.0 -2012-04-08 10:00:00,8092.0 -2012-04-08 11:00:00,8301.0 -2012-04-08 12:00:00,8411.0 -2012-04-08 13:00:00,8482.0 -2012-04-08 14:00:00,8448.0 -2012-04-08 15:00:00,8419.0 -2012-04-08 16:00:00,8291.0 -2012-04-08 17:00:00,8246.0 -2012-04-08 18:00:00,8200.0 -2012-04-08 19:00:00,8258.0 -2012-04-08 20:00:00,8509.0 -2012-04-08 21:00:00,9024.0 -2012-04-08 22:00:00,9366.0 -2012-04-08 23:00:00,9153.0 -2012-04-09 00:00:00,8699.0 -2012-04-07 01:00:00,8967.0 -2012-04-07 02:00:00,8480.0 -2012-04-07 03:00:00,8240.0 -2012-04-07 04:00:00,8039.0 -2012-04-07 05:00:00,7997.0 -2012-04-07 06:00:00,8040.0 -2012-04-07 07:00:00,8305.0 -2012-04-07 08:00:00,8480.0 -2012-04-07 09:00:00,8687.0 -2012-04-07 10:00:00,9011.0 -2012-04-07 11:00:00,9239.0 -2012-04-07 12:00:00,9351.0 -2012-04-07 13:00:00,9369.0 -2012-04-07 14:00:00,9285.0 -2012-04-07 15:00:00,9165.0 -2012-04-07 16:00:00,9065.0 -2012-04-07 17:00:00,9031.0 -2012-04-07 18:00:00,9001.0 -2012-04-07 19:00:00,9057.0 -2012-04-07 20:00:00,9220.0 -2012-04-07 21:00:00,9706.0 -2012-04-07 22:00:00,9773.0 -2012-04-07 23:00:00,9538.0 -2012-04-08 00:00:00,9059.0 -2012-04-06 01:00:00,9564.0 -2012-04-06 02:00:00,9050.0 -2012-04-06 03:00:00,8693.0 -2012-04-06 04:00:00,8531.0 -2012-04-06 05:00:00,8440.0 -2012-04-06 06:00:00,8645.0 -2012-04-06 07:00:00,9140.0 -2012-04-06 08:00:00,9718.0 -2012-04-06 09:00:00,9979.0 -2012-04-06 10:00:00,10215.0 -2012-04-06 11:00:00,10359.0 -2012-04-06 12:00:00,10429.0 -2012-04-06 13:00:00,10362.0 -2012-04-06 14:00:00,10254.0 -2012-04-06 15:00:00,10167.0 -2012-04-06 16:00:00,10023.0 -2012-04-06 17:00:00,9852.0 -2012-04-06 18:00:00,9776.0 -2012-04-06 19:00:00,9701.0 -2012-04-06 20:00:00,9630.0 -2012-04-06 21:00:00,10068.0 -2012-04-06 22:00:00,10375.0 -2012-04-06 23:00:00,10131.0 -2012-04-07 00:00:00,9618.0 -2012-04-05 01:00:00,9356.0 -2012-04-05 02:00:00,8837.0 -2012-04-05 03:00:00,8547.0 -2012-04-05 04:00:00,8349.0 -2012-04-05 05:00:00,8330.0 -2012-04-05 06:00:00,8573.0 -2012-04-05 07:00:00,9286.0 -2012-04-05 08:00:00,10237.0 -2012-04-05 09:00:00,10771.0 -2012-04-05 10:00:00,11066.0 -2012-04-05 11:00:00,11191.0 -2012-04-05 12:00:00,11239.0 -2012-04-05 13:00:00,11187.0 -2012-04-05 14:00:00,11132.0 -2012-04-05 15:00:00,11142.0 -2012-04-05 16:00:00,11010.0 -2012-04-05 17:00:00,10831.0 -2012-04-05 18:00:00,10701.0 -2012-04-05 19:00:00,10579.0 -2012-04-05 20:00:00,10587.0 -2012-04-05 21:00:00,11119.0 -2012-04-05 22:00:00,11355.0 -2012-04-05 23:00:00,11060.0 -2012-04-06 00:00:00,10307.0 -2012-04-04 01:00:00,9223.0 -2012-04-04 02:00:00,8717.0 -2012-04-04 03:00:00,8415.0 -2012-04-04 04:00:00,8185.0 -2012-04-04 05:00:00,8172.0 -2012-04-04 06:00:00,8382.0 -2012-04-04 07:00:00,9075.0 -2012-04-04 08:00:00,10047.0 -2012-04-04 09:00:00,10560.0 -2012-04-04 10:00:00,10823.0 -2012-04-04 11:00:00,10989.0 -2012-04-04 12:00:00,11115.0 -2012-04-04 13:00:00,11152.0 -2012-04-04 14:00:00,11109.0 -2012-04-04 15:00:00,11144.0 -2012-04-04 16:00:00,11039.0 -2012-04-04 17:00:00,10866.0 -2012-04-04 18:00:00,10732.0 -2012-04-04 19:00:00,10593.0 -2012-04-04 20:00:00,10484.0 -2012-04-04 21:00:00,11032.0 -2012-04-04 22:00:00,11274.0 -2012-04-04 23:00:00,10864.0 -2012-04-05 00:00:00,10094.0 -2012-04-03 01:00:00,9151.0 -2012-04-03 02:00:00,8607.0 -2012-04-03 03:00:00,8289.0 -2012-04-03 04:00:00,8093.0 -2012-04-03 05:00:00,8033.0 -2012-04-03 06:00:00,8207.0 -2012-04-03 07:00:00,8836.0 -2012-04-03 08:00:00,9832.0 -2012-04-03 09:00:00,10352.0 -2012-04-03 10:00:00,10753.0 -2012-04-03 11:00:00,10989.0 -2012-04-03 12:00:00,11229.0 -2012-04-03 13:00:00,11370.0 -2012-04-03 14:00:00,11472.0 -2012-04-03 15:00:00,11621.0 -2012-04-03 16:00:00,11504.0 -2012-04-03 17:00:00,11308.0 -2012-04-03 18:00:00,11008.0 -2012-04-03 19:00:00,10757.0 -2012-04-03 20:00:00,10617.0 -2012-04-03 21:00:00,11108.0 -2012-04-03 22:00:00,11250.0 -2012-04-03 23:00:00,10766.0 -2012-04-04 00:00:00,9988.0 -2012-04-02 01:00:00,8685.0 -2012-04-02 02:00:00,8339.0 -2012-04-02 03:00:00,8086.0 -2012-04-02 04:00:00,8011.0 -2012-04-02 05:00:00,8002.0 -2012-04-02 06:00:00,8327.0 -2012-04-02 07:00:00,9018.0 -2012-04-02 08:00:00,10074.0 -2012-04-02 09:00:00,10650.0 -2012-04-02 10:00:00,10841.0 -2012-04-02 11:00:00,10969.0 -2012-04-02 12:00:00,11082.0 -2012-04-02 13:00:00,11091.0 -2012-04-02 14:00:00,11083.0 -2012-04-02 15:00:00,11098.0 -2012-04-02 16:00:00,11010.0 -2012-04-02 17:00:00,10863.0 -2012-04-02 18:00:00,10739.0 -2012-04-02 19:00:00,10603.0 -2012-04-02 20:00:00,10463.0 -2012-04-02 21:00:00,10980.0 -2012-04-02 22:00:00,11183.0 -2012-04-02 23:00:00,10736.0 -2012-04-03 00:00:00,9928.0 -2012-04-01 01:00:00,8982.0 -2012-04-01 02:00:00,8533.0 -2012-04-01 03:00:00,8210.0 -2012-04-01 04:00:00,8022.0 -2012-04-01 05:00:00,7910.0 -2012-04-01 06:00:00,7868.0 -2012-04-01 07:00:00,7984.0 -2012-04-01 08:00:00,8143.0 -2012-04-01 09:00:00,8184.0 -2012-04-01 10:00:00,8415.0 -2012-04-01 11:00:00,8729.0 -2012-04-01 12:00:00,8862.0 -2012-04-01 13:00:00,8939.0 -2012-04-01 14:00:00,8915.0 -2012-04-01 15:00:00,8949.0 -2012-04-01 16:00:00,8871.0 -2012-04-01 17:00:00,8819.0 -2012-04-01 18:00:00,8864.0 -2012-04-01 19:00:00,9003.0 -2012-04-01 20:00:00,9213.0 -2012-04-01 21:00:00,9834.0 -2012-04-01 22:00:00,10031.0 -2012-04-01 23:00:00,9733.0 -2012-04-02 00:00:00,9249.0 -2012-03-31 01:00:00,9464.0 -2012-03-31 02:00:00,8962.0 -2012-03-31 03:00:00,8660.0 -2012-03-31 04:00:00,8407.0 -2012-03-31 05:00:00,8343.0 -2012-03-31 06:00:00,8336.0 -2012-03-31 07:00:00,8635.0 -2012-03-31 08:00:00,9073.0 -2012-03-31 09:00:00,9391.0 -2012-03-31 10:00:00,9785.0 -2012-03-31 11:00:00,10111.0 -2012-03-31 12:00:00,10237.0 -2012-03-31 13:00:00,10194.0 -2012-03-31 14:00:00,10018.0 -2012-03-31 15:00:00,9697.0 -2012-03-31 16:00:00,9502.0 -2012-03-31 17:00:00,9326.0 -2012-03-31 18:00:00,9310.0 -2012-03-31 19:00:00,9320.0 -2012-03-31 20:00:00,9436.0 -2012-03-31 21:00:00,10010.0 -2012-03-31 22:00:00,10220.0 -2012-03-31 23:00:00,9984.0 -2012-04-01 00:00:00,9531.0 -2012-03-30 01:00:00,9493.0 -2012-03-30 02:00:00,8977.0 -2012-03-30 03:00:00,8669.0 -2012-03-30 04:00:00,8446.0 -2012-03-30 05:00:00,8383.0 -2012-03-30 06:00:00,8531.0 -2012-03-30 07:00:00,9126.0 -2012-03-30 08:00:00,10159.0 -2012-03-30 09:00:00,10786.0 -2012-03-30 10:00:00,11079.0 -2012-03-30 11:00:00,11170.0 -2012-03-30 12:00:00,11253.0 -2012-03-30 13:00:00,11265.0 -2012-03-30 14:00:00,11117.0 -2012-03-30 15:00:00,11100.0 -2012-03-30 16:00:00,10912.0 -2012-03-30 17:00:00,10752.0 -2012-03-30 18:00:00,10677.0 -2012-03-30 19:00:00,10636.0 -2012-03-30 20:00:00,10703.0 -2012-03-30 21:00:00,11170.0 -2012-03-30 22:00:00,11074.0 -2012-03-30 23:00:00,10799.0 -2012-03-31 00:00:00,10149.0 -2012-03-29 01:00:00,9180.0 -2012-03-29 02:00:00,8656.0 -2012-03-29 03:00:00,8374.0 -2012-03-29 04:00:00,8182.0 -2012-03-29 05:00:00,8163.0 -2012-03-29 06:00:00,8372.0 -2012-03-29 07:00:00,9031.0 -2012-03-29 08:00:00,10044.0 -2012-03-29 09:00:00,10461.0 -2012-03-29 10:00:00,10837.0 -2012-03-29 11:00:00,10976.0 -2012-03-29 12:00:00,11123.0 -2012-03-29 13:00:00,11072.0 -2012-03-29 14:00:00,11014.0 -2012-03-29 15:00:00,11046.0 -2012-03-29 16:00:00,10903.0 -2012-03-29 17:00:00,10759.0 -2012-03-29 18:00:00,10673.0 -2012-03-29 19:00:00,10633.0 -2012-03-29 20:00:00,10671.0 -2012-03-29 21:00:00,11202.0 -2012-03-29 22:00:00,11304.0 -2012-03-29 23:00:00,10909.0 -2012-03-30 00:00:00,10202.0 -2012-03-28 01:00:00,9383.0 -2012-03-28 02:00:00,8782.0 -2012-03-28 03:00:00,8436.0 -2012-03-28 04:00:00,8221.0 -2012-03-28 05:00:00,8138.0 -2012-03-28 06:00:00,8283.0 -2012-03-28 07:00:00,8877.0 -2012-03-28 08:00:00,9817.0 -2012-03-28 09:00:00,10296.0 -2012-03-28 10:00:00,10717.0 -2012-03-28 11:00:00,10982.0 -2012-03-28 12:00:00,11295.0 -2012-03-28 13:00:00,11320.0 -2012-03-28 14:00:00,11321.0 -2012-03-28 15:00:00,11424.0 -2012-03-28 16:00:00,11324.0 -2012-03-28 17:00:00,11192.0 -2012-03-28 18:00:00,10978.0 -2012-03-28 19:00:00,10762.0 -2012-03-28 20:00:00,10541.0 -2012-03-28 21:00:00,10969.0 -2012-03-28 22:00:00,11052.0 -2012-03-28 23:00:00,10596.0 -2012-03-29 00:00:00,9886.0 -2012-03-27 01:00:00,9546.0 -2012-03-27 02:00:00,9065.0 -2012-03-27 03:00:00,8722.0 -2012-03-27 04:00:00,8536.0 -2012-03-27 05:00:00,8499.0 -2012-03-27 06:00:00,8720.0 -2012-03-27 07:00:00,9406.0 -2012-03-27 08:00:00,10495.0 -2012-03-27 09:00:00,10964.0 -2012-03-27 10:00:00,11183.0 -2012-03-27 11:00:00,11309.0 -2012-03-27 12:00:00,11380.0 -2012-03-27 13:00:00,11355.0 -2012-03-27 14:00:00,11337.0 -2012-03-27 15:00:00,11325.0 -2012-03-27 16:00:00,11265.0 -2012-03-27 17:00:00,11144.0 -2012-03-27 18:00:00,11039.0 -2012-03-27 19:00:00,10958.0 -2012-03-27 20:00:00,10836.0 -2012-03-27 21:00:00,11360.0 -2012-03-27 22:00:00,11415.0 -2012-03-27 23:00:00,10947.0 -2012-03-28 00:00:00,10143.0 -2012-03-26 01:00:00,8522.0 -2012-03-26 02:00:00,8170.0 -2012-03-26 03:00:00,7962.0 -2012-03-26 04:00:00,7886.0 -2012-03-26 05:00:00,7889.0 -2012-03-26 06:00:00,8200.0 -2012-03-26 07:00:00,8889.0 -2012-03-26 08:00:00,10102.0 -2012-03-26 09:00:00,10765.0 -2012-03-26 10:00:00,11197.0 -2012-03-26 11:00:00,11420.0 -2012-03-26 12:00:00,11464.0 -2012-03-26 13:00:00,11360.0 -2012-03-26 14:00:00,11282.0 -2012-03-26 15:00:00,11243.0 -2012-03-26 16:00:00,11104.0 -2012-03-26 17:00:00,10917.0 -2012-03-26 18:00:00,10854.0 -2012-03-26 19:00:00,10873.0 -2012-03-26 20:00:00,10997.0 -2012-03-26 21:00:00,11565.0 -2012-03-26 22:00:00,11544.0 -2012-03-26 23:00:00,11044.0 -2012-03-27 00:00:00,10324.0 -2012-03-25 01:00:00,8681.0 -2012-03-25 02:00:00,8239.0 -2012-03-25 03:00:00,7841.0 -2012-03-25 04:00:00,7722.0 -2012-03-25 05:00:00,7563.0 -2012-03-25 06:00:00,7569.0 -2012-03-25 07:00:00,7623.0 -2012-03-25 08:00:00,7862.0 -2012-03-25 09:00:00,7883.0 -2012-03-25 10:00:00,8243.0 -2012-03-25 11:00:00,8576.0 -2012-03-25 12:00:00,8857.0 -2012-03-25 13:00:00,9046.0 -2012-03-25 14:00:00,9146.0 -2012-03-25 15:00:00,9185.0 -2012-03-25 16:00:00,9176.0 -2012-03-25 17:00:00,9110.0 -2012-03-25 18:00:00,9092.0 -2012-03-25 19:00:00,9066.0 -2012-03-25 20:00:00,9122.0 -2012-03-25 21:00:00,9697.0 -2012-03-25 22:00:00,9825.0 -2012-03-25 23:00:00,9528.0 -2012-03-26 00:00:00,9053.0 -2012-03-24 01:00:00,9288.0 -2012-03-24 02:00:00,8664.0 -2012-03-24 03:00:00,8323.0 -2012-03-24 04:00:00,8043.0 -2012-03-24 05:00:00,7937.0 -2012-03-24 06:00:00,7958.0 -2012-03-24 07:00:00,8163.0 -2012-03-24 08:00:00,8564.0 -2012-03-24 09:00:00,8801.0 -2012-03-24 10:00:00,9255.0 -2012-03-24 11:00:00,9594.0 -2012-03-24 12:00:00,9833.0 -2012-03-24 13:00:00,9857.0 -2012-03-24 14:00:00,9781.0 -2012-03-24 15:00:00,9639.0 -2012-03-24 16:00:00,9508.0 -2012-03-24 17:00:00,9434.0 -2012-03-24 18:00:00,9434.0 -2012-03-24 19:00:00,9382.0 -2012-03-24 20:00:00,9450.0 -2012-03-24 21:00:00,9958.0 -2012-03-24 22:00:00,9954.0 -2012-03-24 23:00:00,9669.0 -2012-03-25 00:00:00,9256.0 -2012-03-23 01:00:00,10170.0 -2012-03-23 02:00:00,9446.0 -2012-03-23 03:00:00,8985.0 -2012-03-23 04:00:00,8611.0 -2012-03-23 05:00:00,8513.0 -2012-03-23 06:00:00,8711.0 -2012-03-23 07:00:00,9273.0 -2012-03-23 08:00:00,10453.0 -2012-03-23 09:00:00,10985.0 -2012-03-23 10:00:00,11400.0 -2012-03-23 11:00:00,11639.0 -2012-03-23 12:00:00,11850.0 -2012-03-23 13:00:00,11937.0 -2012-03-23 14:00:00,11862.0 -2012-03-23 15:00:00,11925.0 -2012-03-23 16:00:00,11775.0 -2012-03-23 17:00:00,11555.0 -2012-03-23 18:00:00,11337.0 -2012-03-23 19:00:00,11128.0 -2012-03-23 20:00:00,11036.0 -2012-03-23 21:00:00,11419.0 -2012-03-23 22:00:00,11164.0 -2012-03-23 23:00:00,10719.0 -2012-03-24 00:00:00,9996.0 -2012-03-22 01:00:00,10360.0 -2012-03-22 02:00:00,9689.0 -2012-03-22 03:00:00,9215.0 -2012-03-22 04:00:00,8975.0 -2012-03-22 05:00:00,8848.0 -2012-03-22 06:00:00,8949.0 -2012-03-22 07:00:00,9567.0 -2012-03-22 08:00:00,10721.0 -2012-03-22 09:00:00,11261.0 -2012-03-22 10:00:00,11842.0 -2012-03-22 11:00:00,12302.0 -2012-03-22 12:00:00,12807.0 -2012-03-22 13:00:00,13112.0 -2012-03-22 14:00:00,13420.0 -2012-03-22 15:00:00,13680.0 -2012-03-22 16:00:00,13583.0 -2012-03-22 17:00:00,13230.0 -2012-03-22 18:00:00,13013.0 -2012-03-22 19:00:00,12750.0 -2012-03-22 20:00:00,12631.0 -2012-03-22 21:00:00,13033.0 -2012-03-22 22:00:00,12716.0 -2012-03-22 23:00:00,12060.0 -2012-03-23 00:00:00,11079.0 -2012-03-21 01:00:00,10341.0 -2012-03-21 02:00:00,9641.0 -2012-03-21 03:00:00,9215.0 -2012-03-21 04:00:00,8910.0 -2012-03-21 05:00:00,8773.0 -2012-03-21 06:00:00,8903.0 -2012-03-21 07:00:00,9559.0 -2012-03-21 08:00:00,10766.0 -2012-03-21 09:00:00,11361.0 -2012-03-21 10:00:00,11941.0 -2012-03-21 11:00:00,12440.0 -2012-03-21 12:00:00,12915.0 -2012-03-21 13:00:00,13317.0 -2012-03-21 14:00:00,13618.0 -2012-03-21 15:00:00,13878.0 -2012-03-21 16:00:00,13958.0 -2012-03-21 17:00:00,13962.0 -2012-03-21 18:00:00,13819.0 -2012-03-21 19:00:00,13519.0 -2012-03-21 20:00:00,13089.0 -2012-03-21 21:00:00,13360.0 -2012-03-21 22:00:00,13149.0 -2012-03-21 23:00:00,12394.0 -2012-03-22 00:00:00,11331.0 -2012-03-20 01:00:00,10035.0 -2012-03-20 02:00:00,9383.0 -2012-03-20 03:00:00,8949.0 -2012-03-20 04:00:00,8683.0 -2012-03-20 05:00:00,8540.0 -2012-03-20 06:00:00,8708.0 -2012-03-20 07:00:00,9329.0 -2012-03-20 08:00:00,10505.0 -2012-03-20 09:00:00,11124.0 -2012-03-20 10:00:00,11537.0 -2012-03-20 11:00:00,11966.0 -2012-03-20 12:00:00,12429.0 -2012-03-20 13:00:00,12718.0 -2012-03-20 14:00:00,12926.0 -2012-03-20 15:00:00,13168.0 -2012-03-20 16:00:00,13287.0 -2012-03-20 17:00:00,13323.0 -2012-03-20 18:00:00,13266.0 -2012-03-20 19:00:00,13084.0 -2012-03-20 20:00:00,12729.0 -2012-03-20 21:00:00,13097.0 -2012-03-20 22:00:00,12996.0 -2012-03-20 23:00:00,12339.0 -2012-03-21 00:00:00,11318.0 -2012-03-19 01:00:00,9303.0 -2012-03-19 02:00:00,8823.0 -2012-03-19 03:00:00,8485.0 -2012-03-19 04:00:00,8264.0 -2012-03-19 05:00:00,8246.0 -2012-03-19 06:00:00,8421.0 -2012-03-19 07:00:00,9171.0 -2012-03-19 08:00:00,10427.0 -2012-03-19 09:00:00,11090.0 -2012-03-19 10:00:00,11553.0 -2012-03-19 11:00:00,11889.0 -2012-03-19 12:00:00,12116.0 -2012-03-19 13:00:00,12170.0 -2012-03-19 14:00:00,12187.0 -2012-03-19 15:00:00,12185.0 -2012-03-19 16:00:00,12151.0 -2012-03-19 17:00:00,12312.0 -2012-03-19 18:00:00,12375.0 -2012-03-19 19:00:00,12203.0 -2012-03-19 20:00:00,11957.0 -2012-03-19 21:00:00,12476.0 -2012-03-19 22:00:00,12383.0 -2012-03-19 23:00:00,11834.0 -2012-03-20 00:00:00,10936.0 -2012-03-18 01:00:00,9258.0 -2012-03-18 02:00:00,8718.0 -2012-03-18 03:00:00,8270.0 -2012-03-18 04:00:00,7947.0 -2012-03-18 05:00:00,7748.0 -2012-03-18 06:00:00,7774.0 -2012-03-18 07:00:00,7824.0 -2012-03-18 08:00:00,8000.0 -2012-03-18 09:00:00,8053.0 -2012-03-18 10:00:00,8412.0 -2012-03-18 11:00:00,8914.0 -2012-03-18 12:00:00,9346.0 -2012-03-18 13:00:00,9683.0 -2012-03-18 14:00:00,9903.0 -2012-03-18 15:00:00,10059.0 -2012-03-18 16:00:00,10197.0 -2012-03-18 17:00:00,10257.0 -2012-03-18 18:00:00,10328.0 -2012-03-18 19:00:00,10328.0 -2012-03-18 20:00:00,10323.0 -2012-03-18 21:00:00,10932.0 -2012-03-18 22:00:00,10981.0 -2012-03-18 23:00:00,10631.0 -2012-03-19 00:00:00,9975.0 -2012-03-17 01:00:00,9701.0 -2012-03-17 02:00:00,9005.0 -2012-03-17 03:00:00,8565.0 -2012-03-17 04:00:00,8294.0 -2012-03-17 05:00:00,8116.0 -2012-03-17 06:00:00,8114.0 -2012-03-17 07:00:00,8253.0 -2012-03-17 08:00:00,8676.0 -2012-03-17 09:00:00,8860.0 -2012-03-17 10:00:00,9429.0 -2012-03-17 11:00:00,9919.0 -2012-03-17 12:00:00,10288.0 -2012-03-17 13:00:00,10499.0 -2012-03-17 14:00:00,10554.0 -2012-03-17 15:00:00,10571.0 -2012-03-17 16:00:00,10542.0 -2012-03-17 17:00:00,10587.0 -2012-03-17 18:00:00,10583.0 -2012-03-17 19:00:00,10522.0 -2012-03-17 20:00:00,10430.0 -2012-03-17 21:00:00,10899.0 -2012-03-17 22:00:00,10849.0 -2012-03-17 23:00:00,10490.0 -2012-03-18 00:00:00,9923.0 -2012-03-16 01:00:00,9545.0 -2012-03-16 02:00:00,8921.0 -2012-03-16 03:00:00,8542.0 -2012-03-16 04:00:00,8303.0 -2012-03-16 05:00:00,8192.0 -2012-03-16 06:00:00,8347.0 -2012-03-16 07:00:00,8911.0 -2012-03-16 08:00:00,10084.0 -2012-03-16 09:00:00,10604.0 -2012-03-16 10:00:00,10911.0 -2012-03-16 11:00:00,11164.0 -2012-03-16 12:00:00,11471.0 -2012-03-16 13:00:00,11700.0 -2012-03-16 14:00:00,11827.0 -2012-03-16 15:00:00,12073.0 -2012-03-16 16:00:00,12094.0 -2012-03-16 17:00:00,12011.0 -2012-03-16 18:00:00,11846.0 -2012-03-16 19:00:00,11606.0 -2012-03-16 20:00:00,11392.0 -2012-03-16 21:00:00,11855.0 -2012-03-16 22:00:00,11721.0 -2012-03-16 23:00:00,11319.0 -2012-03-17 00:00:00,10518.0 -2012-03-15 01:00:00,9757.0 -2012-03-15 02:00:00,9139.0 -2012-03-15 03:00:00,8764.0 -2012-03-15 04:00:00,8504.0 -2012-03-15 05:00:00,8413.0 -2012-03-15 06:00:00,8538.0 -2012-03-15 07:00:00,9137.0 -2012-03-15 08:00:00,10355.0 -2012-03-15 09:00:00,10965.0 -2012-03-15 10:00:00,11337.0 -2012-03-15 11:00:00,11629.0 -2012-03-15 12:00:00,11940.0 -2012-03-15 13:00:00,12111.0 -2012-03-15 14:00:00,12291.0 -2012-03-15 15:00:00,12480.0 -2012-03-15 16:00:00,12550.0 -2012-03-15 17:00:00,12508.0 -2012-03-15 18:00:00,12384.0 -2012-03-15 19:00:00,12126.0 -2012-03-15 20:00:00,11858.0 -2012-03-15 21:00:00,12233.0 -2012-03-15 22:00:00,11960.0 -2012-03-15 23:00:00,11347.0 -2012-03-16 00:00:00,10352.0 -2012-03-14 01:00:00,9327.0 -2012-03-14 02:00:00,8760.0 -2012-03-14 03:00:00,8448.0 -2012-03-14 04:00:00,8240.0 -2012-03-14 05:00:00,8218.0 -2012-03-14 06:00:00,8372.0 -2012-03-14 07:00:00,9040.0 -2012-03-14 08:00:00,10292.0 -2012-03-14 09:00:00,10843.0 -2012-03-14 10:00:00,11104.0 -2012-03-14 11:00:00,11306.0 -2012-03-14 12:00:00,11528.0 -2012-03-14 13:00:00,11634.0 -2012-03-14 14:00:00,11807.0 -2012-03-14 15:00:00,11928.0 -2012-03-14 16:00:00,11955.0 -2012-03-14 17:00:00,11884.0 -2012-03-14 18:00:00,11798.0 -2012-03-14 19:00:00,11612.0 -2012-03-14 20:00:00,11465.0 -2012-03-14 21:00:00,12062.0 -2012-03-14 22:00:00,11966.0 -2012-03-14 23:00:00,11462.0 -2012-03-15 00:00:00,10625.0 -2012-03-13 01:00:00,9401.0 -2012-03-13 02:00:00,8844.0 -2012-03-13 03:00:00,8531.0 -2012-03-13 04:00:00,8305.0 -2012-03-13 05:00:00,8244.0 -2012-03-13 06:00:00,8444.0 -2012-03-13 07:00:00,9086.0 -2012-03-13 08:00:00,10347.0 -2012-03-13 09:00:00,10910.0 -2012-03-13 10:00:00,11055.0 -2012-03-13 11:00:00,11100.0 -2012-03-13 12:00:00,11266.0 -2012-03-13 13:00:00,11328.0 -2012-03-13 14:00:00,11322.0 -2012-03-13 15:00:00,11358.0 -2012-03-13 16:00:00,11257.0 -2012-03-13 17:00:00,11134.0 -2012-03-13 18:00:00,10995.0 -2012-03-13 19:00:00,10854.0 -2012-03-13 20:00:00,10780.0 -2012-03-13 21:00:00,11465.0 -2012-03-13 22:00:00,11368.0 -2012-03-13 23:00:00,10889.0 -2012-03-14 00:00:00,10109.0 -2012-03-12 01:00:00,8814.0 -2012-03-12 02:00:00,8387.0 -2012-03-12 03:00:00,8195.0 -2012-03-12 04:00:00,8070.0 -2012-03-12 05:00:00,8074.0 -2012-03-12 06:00:00,8256.0 -2012-03-12 07:00:00,8934.0 -2012-03-12 08:00:00,10179.0 -2012-03-12 09:00:00,11120.0 -2012-03-12 10:00:00,11246.0 -2012-03-12 11:00:00,11377.0 -2012-03-12 12:00:00,11550.0 -2012-03-12 13:00:00,11581.0 -2012-03-12 14:00:00,11569.0 -2012-03-12 15:00:00,11593.0 -2012-03-12 16:00:00,11454.0 -2012-03-12 17:00:00,11312.0 -2012-03-12 18:00:00,11159.0 -2012-03-12 19:00:00,10972.0 -2012-03-12 20:00:00,10919.0 -2012-03-12 21:00:00,11563.0 -2012-03-12 22:00:00,11475.0 -2012-03-12 23:00:00,10996.0 -2012-03-13 00:00:00,10167.0 -2012-03-11 01:00:00,9158.0 -2012-03-11 02:00:00,8773.0 -2012-03-11 04:00:00,8502.0 -2012-03-11 05:00:00,8387.0 -2012-03-11 06:00:00,8293.0 -2012-03-11 07:00:00,8428.0 -2012-03-11 08:00:00,8674.0 -2012-03-11 09:00:00,8684.0 -2012-03-11 10:00:00,8808.0 -2012-03-11 11:00:00,8953.0 -2012-03-11 12:00:00,9006.0 -2012-03-11 13:00:00,9116.0 -2012-03-11 14:00:00,9068.0 -2012-03-11 15:00:00,9041.0 -2012-03-11 16:00:00,8948.0 -2012-03-11 17:00:00,8944.0 -2012-03-11 18:00:00,8934.0 -2012-03-11 19:00:00,9145.0 -2012-03-11 20:00:00,9452.0 -2012-03-11 21:00:00,10199.0 -2012-03-11 22:00:00,10173.0 -2012-03-11 23:00:00,9925.0 -2012-03-12 00:00:00,9348.0 -2012-03-10 01:00:00,10302.0 -2012-03-10 02:00:00,9838.0 -2012-03-10 03:00:00,9530.0 -2012-03-10 04:00:00,9318.0 -2012-03-10 05:00:00,9257.0 -2012-03-10 06:00:00,9351.0 -2012-03-10 07:00:00,9625.0 -2012-03-10 08:00:00,9819.0 -2012-03-10 09:00:00,10168.0 -2012-03-10 10:00:00,10426.0 -2012-03-10 11:00:00,10597.0 -2012-03-10 12:00:00,10598.0 -2012-03-10 13:00:00,10442.0 -2012-03-10 14:00:00,10209.0 -2012-03-10 15:00:00,9919.0 -2012-03-10 16:00:00,9676.0 -2012-03-10 17:00:00,9523.0 -2012-03-10 18:00:00,9542.0 -2012-03-10 19:00:00,9745.0 -2012-03-10 20:00:00,10601.0 -2012-03-10 21:00:00,10553.0 -2012-03-10 22:00:00,10412.0 -2012-03-10 23:00:00,10106.0 -2012-03-11 00:00:00,9652.0 -2012-03-09 01:00:00,10103.0 -2012-03-09 02:00:00,9609.0 -2012-03-09 03:00:00,9343.0 -2012-03-09 04:00:00,9193.0 -2012-03-09 05:00:00,9213.0 -2012-03-09 06:00:00,9493.0 -2012-03-09 07:00:00,10265.0 -2012-03-09 08:00:00,11179.0 -2012-03-09 09:00:00,11671.0 -2012-03-09 10:00:00,11962.0 -2012-03-09 11:00:00,12024.0 -2012-03-09 12:00:00,12088.0 -2012-03-09 13:00:00,12018.0 -2012-03-09 14:00:00,11902.0 -2012-03-09 15:00:00,11779.0 -2012-03-09 16:00:00,11584.0 -2012-03-09 17:00:00,11394.0 -2012-03-09 18:00:00,11370.0 -2012-03-09 19:00:00,11592.0 -2012-03-09 20:00:00,12328.0 -2012-03-09 21:00:00,12300.0 -2012-03-09 22:00:00,12003.0 -2012-03-09 23:00:00,11671.0 -2012-03-10 00:00:00,11007.0 -2012-03-08 01:00:00,9414.0 -2012-03-08 02:00:00,8935.0 -2012-03-08 03:00:00,8678.0 -2012-03-08 04:00:00,8486.0 -2012-03-08 05:00:00,8454.0 -2012-03-08 06:00:00,8682.0 -2012-03-08 07:00:00,9428.0 -2012-03-08 08:00:00,10664.0 -2012-03-08 09:00:00,11413.0 -2012-03-08 10:00:00,11702.0 -2012-03-08 11:00:00,11756.0 -2012-03-08 12:00:00,11750.0 -2012-03-08 13:00:00,11679.0 -2012-03-08 14:00:00,11584.0 -2012-03-08 15:00:00,11490.0 -2012-03-08 16:00:00,11367.0 -2012-03-08 17:00:00,11330.0 -2012-03-08 18:00:00,11333.0 -2012-03-08 19:00:00,11599.0 -2012-03-08 20:00:00,12314.0 -2012-03-08 21:00:00,12325.0 -2012-03-08 22:00:00,12079.0 -2012-03-08 23:00:00,11588.0 -2012-03-09 00:00:00,10803.0 -2012-03-07 01:00:00,9534.0 -2012-03-07 02:00:00,9040.0 -2012-03-07 03:00:00,8762.0 -2012-03-07 04:00:00,8595.0 -2012-03-07 05:00:00,8574.0 -2012-03-07 06:00:00,8806.0 -2012-03-07 07:00:00,9495.0 -2012-03-07 08:00:00,10507.0 -2012-03-07 09:00:00,11072.0 -2012-03-07 10:00:00,11313.0 -2012-03-07 11:00:00,11408.0 -2012-03-07 12:00:00,11488.0 -2012-03-07 13:00:00,11490.0 -2012-03-07 14:00:00,11456.0 -2012-03-07 15:00:00,11416.0 -2012-03-07 16:00:00,11349.0 -2012-03-07 17:00:00,11248.0 -2012-03-07 18:00:00,11398.0 -2012-03-07 19:00:00,11695.0 -2012-03-07 20:00:00,12044.0 -2012-03-07 21:00:00,11884.0 -2012-03-07 22:00:00,11538.0 -2012-03-07 23:00:00,10952.0 -2012-03-08 00:00:00,10162.0 -2012-03-06 01:00:00,10473.0 -2012-03-06 02:00:00,9989.0 -2012-03-06 03:00:00,9716.0 -2012-03-06 04:00:00,9537.0 -2012-03-06 05:00:00,9524.0 -2012-03-06 06:00:00,9774.0 -2012-03-06 07:00:00,10538.0 -2012-03-06 08:00:00,11516.0 -2012-03-06 09:00:00,11931.0 -2012-03-06 10:00:00,11997.0 -2012-03-06 11:00:00,11915.0 -2012-03-06 12:00:00,11857.0 -2012-03-06 13:00:00,11723.0 -2012-03-06 14:00:00,11644.0 -2012-03-06 15:00:00,11573.0 -2012-03-06 16:00:00,11424.0 -2012-03-06 17:00:00,11210.0 -2012-03-06 18:00:00,11161.0 -2012-03-06 19:00:00,11379.0 -2012-03-06 20:00:00,12001.0 -2012-03-06 21:00:00,11886.0 -2012-03-06 22:00:00,11588.0 -2012-03-06 23:00:00,11007.0 -2012-03-07 00:00:00,10188.0 -2012-03-05 01:00:00,10100.0 -2012-03-05 02:00:00,9792.0 -2012-03-05 03:00:00,9602.0 -2012-03-05 04:00:00,9570.0 -2012-03-05 05:00:00,9625.0 -2012-03-05 06:00:00,9966.0 -2012-03-05 07:00:00,10787.0 -2012-03-05 08:00:00,11750.0 -2012-03-05 09:00:00,12153.0 -2012-03-05 10:00:00,12296.0 -2012-03-05 11:00:00,12336.0 -2012-03-05 12:00:00,12440.0 -2012-03-05 13:00:00,12396.0 -2012-03-05 14:00:00,12252.0 -2012-03-05 15:00:00,12148.0 -2012-03-05 16:00:00,11945.0 -2012-03-05 17:00:00,11839.0 -2012-03-05 18:00:00,11952.0 -2012-03-05 19:00:00,12411.0 -2012-03-05 20:00:00,12930.0 -2012-03-05 21:00:00,12836.0 -2012-03-05 22:00:00,12573.0 -2012-03-05 23:00:00,12058.0 -2012-03-06 00:00:00,11222.0 -2012-03-04 01:00:00,10086.0 -2012-03-04 02:00:00,9643.0 -2012-03-04 03:00:00,9329.0 -2012-03-04 04:00:00,9146.0 -2012-03-04 05:00:00,9065.0 -2012-03-04 06:00:00,8997.0 -2012-03-04 07:00:00,9229.0 -2012-03-04 08:00:00,9364.0 -2012-03-04 09:00:00,9465.0 -2012-03-04 10:00:00,9842.0 -2012-03-04 11:00:00,10071.0 -2012-03-04 12:00:00,10160.0 -2012-03-04 13:00:00,10146.0 -2012-03-04 14:00:00,10153.0 -2012-03-04 15:00:00,10032.0 -2012-03-04 16:00:00,10075.0 -2012-03-04 17:00:00,10158.0 -2012-03-04 18:00:00,10477.0 -2012-03-04 19:00:00,11124.0 -2012-03-04 20:00:00,11721.0 -2012-03-04 21:00:00,11695.0 -2012-03-04 22:00:00,11530.0 -2012-03-04 23:00:00,11161.0 -2012-03-05 00:00:00,10635.0 -2012-03-03 01:00:00,10466.0 -2012-03-03 02:00:00,9897.0 -2012-03-03 03:00:00,9661.0 -2012-03-03 04:00:00,9453.0 -2012-03-03 05:00:00,9374.0 -2012-03-03 06:00:00,9429.0 -2012-03-03 07:00:00,9724.0 -2012-03-03 08:00:00,10122.0 -2012-03-03 09:00:00,10386.0 -2012-03-03 10:00:00,10878.0 -2012-03-03 11:00:00,11174.0 -2012-03-03 12:00:00,11362.0 -2012-03-03 13:00:00,11259.0 -2012-03-03 14:00:00,11159.0 -2012-03-03 15:00:00,11014.0 -2012-03-03 16:00:00,10915.0 -2012-03-03 17:00:00,10828.0 -2012-03-03 18:00:00,10989.0 -2012-03-03 19:00:00,11386.0 -2012-03-03 20:00:00,11831.0 -2012-03-03 21:00:00,11714.0 -2012-03-03 22:00:00,11521.0 -2012-03-03 23:00:00,11197.0 -2012-03-04 00:00:00,10672.0 -2012-03-02 01:00:00,10192.0 -2012-03-02 02:00:00,9686.0 -2012-03-02 03:00:00,9411.0 -2012-03-02 04:00:00,9262.0 -2012-03-02 05:00:00,9192.0 -2012-03-02 06:00:00,9459.0 -2012-03-02 07:00:00,10181.0 -2012-03-02 08:00:00,11203.0 -2012-03-02 09:00:00,11793.0 -2012-03-02 10:00:00,12094.0 -2012-03-02 11:00:00,12294.0 -2012-03-02 12:00:00,12375.0 -2012-03-02 13:00:00,12406.0 -2012-03-02 14:00:00,12331.0 -2012-03-02 15:00:00,12225.0 -2012-03-02 16:00:00,12217.0 -2012-03-02 17:00:00,12322.0 -2012-03-02 18:00:00,12417.0 -2012-03-02 19:00:00,12664.0 -2012-03-02 20:00:00,12878.0 -2012-03-02 21:00:00,12683.0 -2012-03-02 22:00:00,12349.0 -2012-03-02 23:00:00,11825.0 -2012-03-03 00:00:00,11165.0 -2012-03-01 01:00:00,10241.0 -2012-03-01 02:00:00,9707.0 -2012-03-01 03:00:00,9419.0 -2012-03-01 04:00:00,9225.0 -2012-03-01 05:00:00,9199.0 -2012-03-01 06:00:00,9428.0 -2012-03-01 07:00:00,10145.0 -2012-03-01 08:00:00,11342.0 -2012-03-01 09:00:00,11939.0 -2012-03-01 10:00:00,12197.0 -2012-03-01 11:00:00,12315.0 -2012-03-01 12:00:00,12382.0 -2012-03-01 13:00:00,12325.0 -2012-03-01 14:00:00,12295.0 -2012-03-01 15:00:00,12331.0 -2012-03-01 16:00:00,12230.0 -2012-03-01 17:00:00,12112.0 -2012-03-01 18:00:00,12185.0 -2012-03-01 19:00:00,12550.0 -2012-03-01 20:00:00,12771.0 -2012-03-01 21:00:00,12587.0 -2012-03-01 22:00:00,12288.0 -2012-03-01 23:00:00,11757.0 -2012-03-02 00:00:00,10947.0 -2012-02-29 01:00:00,10327.0 -2012-02-29 02:00:00,9826.0 -2012-02-29 03:00:00,9513.0 -2012-02-29 04:00:00,9290.0 -2012-02-29 05:00:00,9243.0 -2012-02-29 06:00:00,9434.0 -2012-02-29 07:00:00,10100.0 -2012-02-29 08:00:00,11176.0 -2012-02-29 09:00:00,11482.0 -2012-02-29 10:00:00,11521.0 -2012-02-29 11:00:00,11541.0 -2012-02-29 12:00:00,11599.0 -2012-02-29 13:00:00,11598.0 -2012-02-29 14:00:00,11504.0 -2012-02-29 15:00:00,11518.0 -2012-02-29 16:00:00,11392.0 -2012-02-29 17:00:00,11425.0 -2012-02-29 18:00:00,11789.0 -2012-02-29 19:00:00,12303.0 -2012-02-29 20:00:00,12665.0 -2012-02-29 21:00:00,12535.0 -2012-02-29 22:00:00,12276.0 -2012-02-29 23:00:00,11809.0 -2012-03-01 00:00:00,10983.0 -2012-02-28 01:00:00,10571.0 -2012-02-28 02:00:00,10115.0 -2012-02-28 03:00:00,9888.0 -2012-02-28 04:00:00,9753.0 -2012-02-28 05:00:00,9739.0 -2012-02-28 06:00:00,9988.0 -2012-02-28 07:00:00,10758.0 -2012-02-28 08:00:00,11837.0 -2012-02-28 09:00:00,12293.0 -2012-02-28 10:00:00,12444.0 -2012-02-28 11:00:00,12519.0 -2012-02-28 12:00:00,12483.0 -2012-02-28 13:00:00,12310.0 -2012-02-28 14:00:00,12288.0 -2012-02-28 15:00:00,12219.0 -2012-02-28 16:00:00,12046.0 -2012-02-28 17:00:00,12009.0 -2012-02-28 18:00:00,12237.0 -2012-02-28 19:00:00,12684.0 -2012-02-28 20:00:00,12947.0 -2012-02-28 21:00:00,12772.0 -2012-02-28 22:00:00,12477.0 -2012-02-28 23:00:00,11921.0 -2012-02-29 00:00:00,11083.0 -2012-02-27 01:00:00,9564.0 -2012-02-27 02:00:00,9196.0 -2012-02-27 03:00:00,9042.0 -2012-02-27 04:00:00,8943.0 -2012-02-27 05:00:00,9075.0 -2012-02-27 06:00:00,9475.0 -2012-02-27 07:00:00,10321.0 -2012-02-27 08:00:00,11495.0 -2012-02-27 09:00:00,12105.0 -2012-02-27 10:00:00,12360.0 -2012-02-27 11:00:00,12532.0 -2012-02-27 12:00:00,12427.0 -2012-02-27 13:00:00,12341.0 -2012-02-27 14:00:00,12224.0 -2012-02-27 15:00:00,12097.0 -2012-02-27 16:00:00,11938.0 -2012-02-27 17:00:00,11839.0 -2012-02-27 18:00:00,11910.0 -2012-02-27 19:00:00,12409.0 -2012-02-27 20:00:00,13003.0 -2012-02-27 21:00:00,12920.0 -2012-02-27 22:00:00,12654.0 -2012-02-27 23:00:00,12133.0 -2012-02-28 00:00:00,11298.0 -2012-02-26 01:00:00,10482.0 -2012-02-26 02:00:00,10102.0 -2012-02-26 03:00:00,9740.0 -2012-02-26 04:00:00,9638.0 -2012-02-26 05:00:00,9570.0 -2012-02-26 06:00:00,9561.0 -2012-02-26 07:00:00,9760.0 -2012-02-26 08:00:00,9868.0 -2012-02-26 09:00:00,9864.0 -2012-02-26 10:00:00,10118.0 -2012-02-26 11:00:00,10184.0 -2012-02-26 12:00:00,10212.0 -2012-02-26 13:00:00,10188.0 -2012-02-26 14:00:00,10059.0 -2012-02-26 15:00:00,9888.0 -2012-02-26 16:00:00,9769.0 -2012-02-26 17:00:00,9679.0 -2012-02-26 18:00:00,9839.0 -2012-02-26 19:00:00,10431.0 -2012-02-26 20:00:00,11252.0 -2012-02-26 21:00:00,11162.0 -2012-02-26 22:00:00,10948.0 -2012-02-26 23:00:00,10605.0 -2012-02-27 00:00:00,10042.0 -2012-02-25 01:00:00,10882.0 -2012-02-25 02:00:00,10356.0 -2012-02-25 03:00:00,10046.0 -2012-02-25 04:00:00,9850.0 -2012-02-25 05:00:00,9859.0 -2012-02-25 06:00:00,9997.0 -2012-02-25 07:00:00,10290.0 -2012-02-25 08:00:00,10678.0 -2012-02-25 09:00:00,10924.0 -2012-02-25 10:00:00,11146.0 -2012-02-25 11:00:00,11287.0 -2012-02-25 12:00:00,11310.0 -2012-02-25 13:00:00,11229.0 -2012-02-25 14:00:00,11003.0 -2012-02-25 15:00:00,10775.0 -2012-02-25 16:00:00,10533.0 -2012-02-25 17:00:00,10485.0 -2012-02-25 18:00:00,10612.0 -2012-02-25 19:00:00,11223.0 -2012-02-25 20:00:00,11985.0 -2012-02-25 21:00:00,11936.0 -2012-02-25 22:00:00,11823.0 -2012-02-25 23:00:00,11496.0 -2012-02-26 00:00:00,11040.0 -2012-02-24 01:00:00,10532.0 -2012-02-24 02:00:00,10029.0 -2012-02-24 03:00:00,9758.0 -2012-02-24 04:00:00,9635.0 -2012-02-24 05:00:00,9635.0 -2012-02-24 06:00:00,9894.0 -2012-02-24 07:00:00,10547.0 -2012-02-24 08:00:00,11608.0 -2012-02-24 09:00:00,12135.0 -2012-02-24 10:00:00,12377.0 -2012-02-24 11:00:00,12463.0 -2012-02-24 12:00:00,12559.0 -2012-02-24 13:00:00,12511.0 -2012-02-24 14:00:00,12454.0 -2012-02-24 15:00:00,12368.0 -2012-02-24 16:00:00,12265.0 -2012-02-24 17:00:00,12256.0 -2012-02-24 18:00:00,12426.0 -2012-02-24 19:00:00,12913.0 -2012-02-24 20:00:00,13154.0 -2012-02-24 21:00:00,12935.0 -2012-02-24 22:00:00,12664.0 -2012-02-24 23:00:00,12215.0 -2012-02-25 00:00:00,11538.0 -2012-02-23 01:00:00,10182.0 -2012-02-23 02:00:00,9700.0 -2012-02-23 03:00:00,9404.0 -2012-02-23 04:00:00,9271.0 -2012-02-23 05:00:00,9259.0 -2012-02-23 06:00:00,9529.0 -2012-02-23 07:00:00,10331.0 -2012-02-23 08:00:00,11490.0 -2012-02-23 09:00:00,11989.0 -2012-02-23 10:00:00,12255.0 -2012-02-23 11:00:00,12330.0 -2012-02-23 12:00:00,12461.0 -2012-02-23 13:00:00,12446.0 -2012-02-23 14:00:00,12495.0 -2012-02-23 15:00:00,12552.0 -2012-02-23 16:00:00,12483.0 -2012-02-23 17:00:00,12496.0 -2012-02-23 18:00:00,12709.0 -2012-02-23 19:00:00,13144.0 -2012-02-23 20:00:00,13159.0 -2012-02-23 21:00:00,12925.0 -2012-02-23 22:00:00,12620.0 -2012-02-23 23:00:00,12085.0 -2012-02-24 00:00:00,11293.0 -2012-02-22 01:00:00,10331.0 -2012-02-22 02:00:00,9828.0 -2012-02-22 03:00:00,9547.0 -2012-02-22 04:00:00,9377.0 -2012-02-22 05:00:00,9357.0 -2012-02-22 06:00:00,9547.0 -2012-02-22 07:00:00,10251.0 -2012-02-22 08:00:00,11530.0 -2012-02-22 09:00:00,12048.0 -2012-02-22 10:00:00,12197.0 -2012-02-22 11:00:00,12266.0 -2012-02-22 12:00:00,12296.0 -2012-02-22 13:00:00,12194.0 -2012-02-22 14:00:00,12021.0 -2012-02-22 15:00:00,11982.0 -2012-02-22 16:00:00,11875.0 -2012-02-22 17:00:00,11825.0 -2012-02-22 18:00:00,11986.0 -2012-02-22 19:00:00,12433.0 -2012-02-22 20:00:00,12674.0 -2012-02-22 21:00:00,12475.0 -2012-02-22 22:00:00,12194.0 -2012-02-22 23:00:00,11714.0 -2012-02-23 00:00:00,10911.0 -2012-02-21 01:00:00,10264.0 -2012-02-21 02:00:00,9768.0 -2012-02-21 03:00:00,9479.0 -2012-02-21 04:00:00,9327.0 -2012-02-21 05:00:00,9330.0 -2012-02-21 06:00:00,9631.0 -2012-02-21 07:00:00,10431.0 -2012-02-21 08:00:00,11632.0 -2012-02-21 09:00:00,12170.0 -2012-02-21 10:00:00,12395.0 -2012-02-21 11:00:00,12467.0 -2012-02-21 12:00:00,12537.0 -2012-02-21 13:00:00,12550.0 -2012-02-21 14:00:00,12514.0 -2012-02-21 15:00:00,12603.0 -2012-02-21 16:00:00,12493.0 -2012-02-21 17:00:00,12301.0 -2012-02-21 18:00:00,12244.0 -2012-02-21 19:00:00,12670.0 -2012-02-21 20:00:00,12967.0 -2012-02-21 21:00:00,12795.0 -2012-02-21 22:00:00,12493.0 -2012-02-21 23:00:00,11978.0 -2012-02-22 00:00:00,11103.0 -2012-02-20 01:00:00,9966.0 -2012-02-20 02:00:00,9663.0 -2012-02-20 03:00:00,9461.0 -2012-02-20 04:00:00,9403.0 -2012-02-20 05:00:00,9443.0 -2012-02-20 06:00:00,9744.0 -2012-02-20 07:00:00,10389.0 -2012-02-20 08:00:00,11305.0 -2012-02-20 09:00:00,11678.0 -2012-02-20 10:00:00,11925.0 -2012-02-20 11:00:00,12015.0 -2012-02-20 12:00:00,12060.0 -2012-02-20 13:00:00,11970.0 -2012-02-20 14:00:00,11835.0 -2012-02-20 15:00:00,11752.0 -2012-02-20 16:00:00,11592.0 -2012-02-20 17:00:00,11536.0 -2012-02-20 18:00:00,11755.0 -2012-02-20 19:00:00,12490.0 -2012-02-20 20:00:00,12783.0 -2012-02-20 21:00:00,12633.0 -2012-02-20 22:00:00,12339.0 -2012-02-20 23:00:00,11836.0 -2012-02-21 00:00:00,11031.0 -2012-02-19 01:00:00,9976.0 -2012-02-19 02:00:00,9549.0 -2012-02-19 03:00:00,9289.0 -2012-02-19 04:00:00,9176.0 -2012-02-19 05:00:00,9084.0 -2012-02-19 06:00:00,9146.0 -2012-02-19 07:00:00,9297.0 -2012-02-19 08:00:00,9559.0 -2012-02-19 09:00:00,9519.0 -2012-02-19 10:00:00,9681.0 -2012-02-19 11:00:00,9809.0 -2012-02-19 12:00:00,9903.0 -2012-02-19 13:00:00,9879.0 -2012-02-19 14:00:00,9872.0 -2012-02-19 15:00:00,9738.0 -2012-02-19 16:00:00,9682.0 -2012-02-19 17:00:00,9618.0 -2012-02-19 18:00:00,9862.0 -2012-02-19 19:00:00,10616.0 -2012-02-19 20:00:00,11381.0 -2012-02-19 21:00:00,11321.0 -2012-02-19 22:00:00,11200.0 -2012-02-19 23:00:00,10899.0 -2012-02-20 00:00:00,10447.0 -2012-02-18 01:00:00,10155.0 -2012-02-18 02:00:00,9654.0 -2012-02-18 03:00:00,9390.0 -2012-02-18 04:00:00,9204.0 -2012-02-18 05:00:00,9178.0 -2012-02-18 06:00:00,9208.0 -2012-02-18 07:00:00,9554.0 -2012-02-18 08:00:00,10047.0 -2012-02-18 09:00:00,10213.0 -2012-02-18 10:00:00,10539.0 -2012-02-18 11:00:00,10687.0 -2012-02-18 12:00:00,10737.0 -2012-02-18 13:00:00,10661.0 -2012-02-18 14:00:00,10496.0 -2012-02-18 15:00:00,10271.0 -2012-02-18 16:00:00,10064.0 -2012-02-18 17:00:00,9985.0 -2012-02-18 18:00:00,10168.0 -2012-02-18 19:00:00,10833.0 -2012-02-18 20:00:00,11439.0 -2012-02-18 21:00:00,11353.0 -2012-02-18 22:00:00,11245.0 -2012-02-18 23:00:00,10955.0 -2012-02-19 00:00:00,10473.0 -2012-02-17 01:00:00,10429.0 -2012-02-17 02:00:00,9962.0 -2012-02-17 03:00:00,9629.0 -2012-02-17 04:00:00,9498.0 -2012-02-17 05:00:00,9506.0 -2012-02-17 06:00:00,9778.0 -2012-02-17 07:00:00,10505.0 -2012-02-17 08:00:00,11702.0 -2012-02-17 09:00:00,12051.0 -2012-02-17 10:00:00,12125.0 -2012-02-17 11:00:00,12073.0 -2012-02-17 12:00:00,12133.0 -2012-02-17 13:00:00,12016.0 -2012-02-17 14:00:00,11907.0 -2012-02-17 15:00:00,11785.0 -2012-02-17 16:00:00,11601.0 -2012-02-17 17:00:00,11455.0 -2012-02-17 18:00:00,11580.0 -2012-02-17 19:00:00,12086.0 -2012-02-17 20:00:00,12332.0 -2012-02-17 21:00:00,12133.0 -2012-02-17 22:00:00,11823.0 -2012-02-17 23:00:00,11488.0 -2012-02-18 00:00:00,10815.0 -2012-02-16 01:00:00,10323.0 -2012-02-16 02:00:00,9800.0 -2012-02-16 03:00:00,9489.0 -2012-02-16 04:00:00,9249.0 -2012-02-16 05:00:00,9243.0 -2012-02-16 06:00:00,9483.0 -2012-02-16 07:00:00,10216.0 -2012-02-16 08:00:00,11442.0 -2012-02-16 09:00:00,11946.0 -2012-02-16 10:00:00,12167.0 -2012-02-16 11:00:00,12138.0 -2012-02-16 12:00:00,12125.0 -2012-02-16 13:00:00,11993.0 -2012-02-16 14:00:00,11901.0 -2012-02-16 15:00:00,11886.0 -2012-02-16 16:00:00,11714.0 -2012-02-16 17:00:00,11573.0 -2012-02-16 18:00:00,11654.0 -2012-02-16 19:00:00,12290.0 -2012-02-16 20:00:00,12769.0 -2012-02-16 21:00:00,12700.0 -2012-02-16 22:00:00,12438.0 -2012-02-16 23:00:00,11987.0 -2012-02-17 00:00:00,11189.0 -2012-02-15 01:00:00,10558.0 -2012-02-15 02:00:00,10022.0 -2012-02-15 03:00:00,9769.0 -2012-02-15 04:00:00,9606.0 -2012-02-15 05:00:00,9580.0 -2012-02-15 06:00:00,9837.0 -2012-02-15 07:00:00,10575.0 -2012-02-15 08:00:00,11766.0 -2012-02-15 09:00:00,12206.0 -2012-02-15 10:00:00,12308.0 -2012-02-15 11:00:00,12189.0 -2012-02-15 12:00:00,12232.0 -2012-02-15 13:00:00,12239.0 -2012-02-15 14:00:00,12211.0 -2012-02-15 15:00:00,12304.0 -2012-02-15 16:00:00,12245.0 -2012-02-15 17:00:00,12295.0 -2012-02-15 18:00:00,12539.0 -2012-02-15 19:00:00,13010.0 -2012-02-15 20:00:00,13010.0 -2012-02-15 21:00:00,12810.0 -2012-02-15 22:00:00,12539.0 -2012-02-15 23:00:00,12047.0 -2012-02-16 00:00:00,11167.0 -2012-02-14 01:00:00,10952.0 -2012-02-14 02:00:00,10435.0 -2012-02-14 03:00:00,10085.0 -2012-02-14 04:00:00,9926.0 -2012-02-14 05:00:00,9896.0 -2012-02-14 06:00:00,10144.0 -2012-02-14 07:00:00,10870.0 -2012-02-14 08:00:00,12085.0 -2012-02-14 09:00:00,12565.0 -2012-02-14 10:00:00,12730.0 -2012-02-14 11:00:00,12794.0 -2012-02-14 12:00:00,12835.0 -2012-02-14 13:00:00,12780.0 -2012-02-14 14:00:00,12658.0 -2012-02-14 15:00:00,12651.0 -2012-02-14 16:00:00,12577.0 -2012-02-14 17:00:00,12570.0 -2012-02-14 18:00:00,12773.0 -2012-02-14 19:00:00,13235.0 -2012-02-14 20:00:00,13211.0 -2012-02-14 21:00:00,13001.0 -2012-02-14 22:00:00,12695.0 -2012-02-14 23:00:00,12128.0 -2012-02-15 00:00:00,11291.0 -2012-02-13 01:00:00,10471.0 -2012-02-13 02:00:00,10098.0 -2012-02-13 03:00:00,9944.0 -2012-02-13 04:00:00,9945.0 -2012-02-13 05:00:00,9940.0 -2012-02-13 06:00:00,10279.0 -2012-02-13 07:00:00,11144.0 -2012-02-13 08:00:00,12344.0 -2012-02-13 09:00:00,12883.0 -2012-02-13 10:00:00,13114.0 -2012-02-13 11:00:00,13186.0 -2012-02-13 12:00:00,13221.0 -2012-02-13 13:00:00,13144.0 -2012-02-13 14:00:00,13049.0 -2012-02-13 15:00:00,12993.0 -2012-02-13 16:00:00,12835.0 -2012-02-13 17:00:00,12720.0 -2012-02-13 18:00:00,12890.0 -2012-02-13 19:00:00,13513.0 -2012-02-13 20:00:00,13595.0 -2012-02-13 21:00:00,13435.0 -2012-02-13 22:00:00,13132.0 -2012-02-13 23:00:00,12596.0 -2012-02-14 00:00:00,11762.0 -2012-02-12 01:00:00,11294.0 -2012-02-12 02:00:00,10869.0 -2012-02-12 03:00:00,10548.0 -2012-02-12 04:00:00,10354.0 -2012-02-12 05:00:00,10323.0 -2012-02-12 06:00:00,10305.0 -2012-02-12 07:00:00,10479.0 -2012-02-12 08:00:00,10705.0 -2012-02-12 09:00:00,10670.0 -2012-02-12 10:00:00,10788.0 -2012-02-12 11:00:00,10852.0 -2012-02-12 12:00:00,10942.0 -2012-02-12 13:00:00,10885.0 -2012-02-12 14:00:00,10792.0 -2012-02-12 15:00:00,10611.0 -2012-02-12 16:00:00,10474.0 -2012-02-12 17:00:00,10419.0 -2012-02-12 18:00:00,10636.0 -2012-02-12 19:00:00,11546.0 -2012-02-12 20:00:00,12080.0 -2012-02-12 21:00:00,12060.0 -2012-02-12 22:00:00,11865.0 -2012-02-12 23:00:00,11548.0 -2012-02-13 00:00:00,11004.0 -2012-02-11 01:00:00,11664.0 -2012-02-11 02:00:00,11258.0 -2012-02-11 03:00:00,10932.0 -2012-02-11 04:00:00,10797.0 -2012-02-11 05:00:00,10684.0 -2012-02-11 06:00:00,10822.0 -2012-02-11 07:00:00,11134.0 -2012-02-11 08:00:00,11629.0 -2012-02-11 09:00:00,11773.0 -2012-02-11 10:00:00,12031.0 -2012-02-11 11:00:00,12144.0 -2012-02-11 12:00:00,12211.0 -2012-02-11 13:00:00,12077.0 -2012-02-11 14:00:00,11970.0 -2012-02-11 15:00:00,11733.0 -2012-02-11 16:00:00,11571.0 -2012-02-11 17:00:00,11510.0 -2012-02-11 18:00:00,11708.0 -2012-02-11 19:00:00,12525.0 -2012-02-11 20:00:00,12957.0 -2012-02-11 21:00:00,12874.0 -2012-02-11 22:00:00,12679.0 -2012-02-11 23:00:00,12373.0 -2012-02-12 00:00:00,11830.0 -2012-02-10 01:00:00,10717.0 -2012-02-10 02:00:00,10223.0 -2012-02-10 03:00:00,9924.0 -2012-02-10 04:00:00,9765.0 -2012-02-10 05:00:00,9721.0 -2012-02-10 06:00:00,9945.0 -2012-02-10 07:00:00,10624.0 -2012-02-10 08:00:00,11864.0 -2012-02-10 09:00:00,12538.0 -2012-02-10 10:00:00,12700.0 -2012-02-10 11:00:00,12756.0 -2012-02-10 12:00:00,12868.0 -2012-02-10 13:00:00,12870.0 -2012-02-10 14:00:00,12832.0 -2012-02-10 15:00:00,12882.0 -2012-02-10 16:00:00,12861.0 -2012-02-10 17:00:00,12866.0 -2012-02-10 18:00:00,13118.0 -2012-02-10 19:00:00,13716.0 -2012-02-10 20:00:00,13890.0 -2012-02-10 21:00:00,13737.0 -2012-02-10 22:00:00,13484.0 -2012-02-10 23:00:00,13089.0 -2012-02-11 00:00:00,12403.0 -2012-02-09 01:00:00,10875.0 -2012-02-09 02:00:00,10395.0 -2012-02-09 03:00:00,10161.0 -2012-02-09 04:00:00,10005.0 -2012-02-09 05:00:00,10045.0 -2012-02-09 06:00:00,10328.0 -2012-02-09 07:00:00,11119.0 -2012-02-09 08:00:00,12382.0 -2012-02-09 09:00:00,12790.0 -2012-02-09 10:00:00,12806.0 -2012-02-09 11:00:00,12740.0 -2012-02-09 12:00:00,12727.0 -2012-02-09 13:00:00,12622.0 -2012-02-09 14:00:00,12532.0 -2012-02-09 15:00:00,12432.0 -2012-02-09 16:00:00,12230.0 -2012-02-09 17:00:00,12157.0 -2012-02-09 18:00:00,12361.0 -2012-02-09 19:00:00,13182.0 -2012-02-09 20:00:00,13415.0 -2012-02-09 21:00:00,13247.0 -2012-02-09 22:00:00,12916.0 -2012-02-09 23:00:00,12365.0 -2012-02-10 00:00:00,11484.0 -2012-02-08 01:00:00,10778.0 -2012-02-08 02:00:00,10279.0 -2012-02-08 03:00:00,9995.0 -2012-02-08 04:00:00,9833.0 -2012-02-08 05:00:00,9814.0 -2012-02-08 06:00:00,10089.0 -2012-02-08 07:00:00,10840.0 -2012-02-08 08:00:00,12095.0 -2012-02-08 09:00:00,12616.0 -2012-02-08 10:00:00,12818.0 -2012-02-08 11:00:00,12800.0 -2012-02-08 12:00:00,12700.0 -2012-02-08 13:00:00,12544.0 -2012-02-08 14:00:00,12365.0 -2012-02-08 15:00:00,12337.0 -2012-02-08 16:00:00,12141.0 -2012-02-08 17:00:00,12068.0 -2012-02-08 18:00:00,12220.0 -2012-02-08 19:00:00,13022.0 -2012-02-08 20:00:00,13297.0 -2012-02-08 21:00:00,13170.0 -2012-02-08 22:00:00,12917.0 -2012-02-08 23:00:00,12432.0 -2012-02-09 00:00:00,11631.0 -2012-02-07 01:00:00,10613.0 -2012-02-07 02:00:00,10090.0 -2012-02-07 03:00:00,9807.0 -2012-02-07 04:00:00,9633.0 -2012-02-07 05:00:00,9618.0 -2012-02-07 06:00:00,9870.0 -2012-02-07 07:00:00,10593.0 -2012-02-07 08:00:00,11825.0 -2012-02-07 09:00:00,12442.0 -2012-02-07 10:00:00,12653.0 -2012-02-07 11:00:00,12830.0 -2012-02-07 12:00:00,12944.0 -2012-02-07 13:00:00,12947.0 -2012-02-07 14:00:00,12875.0 -2012-02-07 15:00:00,12895.0 -2012-02-07 16:00:00,12782.0 -2012-02-07 17:00:00,12832.0 -2012-02-07 18:00:00,13040.0 -2012-02-07 19:00:00,13599.0 -2012-02-07 20:00:00,13536.0 -2012-02-07 21:00:00,13296.0 -2012-02-07 22:00:00,12963.0 -2012-02-07 23:00:00,12421.0 -2012-02-08 00:00:00,11543.0 -2012-02-06 01:00:00,9696.0 -2012-02-06 02:00:00,9333.0 -2012-02-06 03:00:00,9158.0 -2012-02-06 04:00:00,9062.0 -2012-02-06 05:00:00,9209.0 -2012-02-06 06:00:00,9510.0 -2012-02-06 07:00:00,10360.0 -2012-02-06 08:00:00,11600.0 -2012-02-06 09:00:00,12341.0 -2012-02-06 10:00:00,12534.0 -2012-02-06 11:00:00,12660.0 -2012-02-06 12:00:00,12752.0 -2012-02-06 13:00:00,12704.0 -2012-02-06 14:00:00,12606.0 -2012-02-06 15:00:00,12619.0 -2012-02-06 16:00:00,12514.0 -2012-02-06 17:00:00,12515.0 -2012-02-06 18:00:00,12778.0 -2012-02-06 19:00:00,13422.0 -2012-02-06 20:00:00,13453.0 -2012-02-06 21:00:00,13252.0 -2012-02-06 22:00:00,12884.0 -2012-02-06 23:00:00,12294.0 -2012-02-07 00:00:00,11411.0 -2012-02-05 01:00:00,9831.0 -2012-02-05 02:00:00,9460.0 -2012-02-05 03:00:00,9142.0 -2012-02-05 04:00:00,8975.0 -2012-02-05 05:00:00,8962.0 -2012-02-05 06:00:00,8985.0 -2012-02-05 07:00:00,9124.0 -2012-02-05 08:00:00,9338.0 -2012-02-05 09:00:00,9385.0 -2012-02-05 10:00:00,9622.0 -2012-02-05 11:00:00,9825.0 -2012-02-05 12:00:00,9932.0 -2012-02-05 13:00:00,9988.0 -2012-02-05 14:00:00,9963.0 -2012-02-05 15:00:00,9833.0 -2012-02-05 16:00:00,9757.0 -2012-02-05 17:00:00,9653.0 -2012-02-05 18:00:00,9907.0 -2012-02-05 19:00:00,10811.0 -2012-02-05 20:00:00,11068.0 -2012-02-05 21:00:00,11021.0 -2012-02-05 22:00:00,10887.0 -2012-02-05 23:00:00,10699.0 -2012-02-06 00:00:00,10234.0 -2012-02-04 01:00:00,10328.0 -2012-02-04 02:00:00,9812.0 -2012-02-04 03:00:00,9455.0 -2012-02-04 04:00:00,9257.0 -2012-02-04 05:00:00,9139.0 -2012-02-04 06:00:00,9156.0 -2012-02-04 07:00:00,9396.0 -2012-02-04 08:00:00,9913.0 -2012-02-04 09:00:00,10216.0 -2012-02-04 10:00:00,10595.0 -2012-02-04 11:00:00,10858.0 -2012-02-04 12:00:00,10966.0 -2012-02-04 13:00:00,10823.0 -2012-02-04 14:00:00,10634.0 -2012-02-04 15:00:00,10411.0 -2012-02-04 16:00:00,10319.0 -2012-02-04 17:00:00,10287.0 -2012-02-04 18:00:00,10585.0 -2012-02-04 19:00:00,11329.0 -2012-02-04 20:00:00,11520.0 -2012-02-04 21:00:00,11342.0 -2012-02-04 22:00:00,11200.0 -2012-02-04 23:00:00,10880.0 -2012-02-05 00:00:00,10425.0 -2012-02-03 01:00:00,10174.0 -2012-02-03 02:00:00,9658.0 -2012-02-03 03:00:00,9393.0 -2012-02-03 04:00:00,9217.0 -2012-02-03 05:00:00,9189.0 -2012-02-03 06:00:00,9446.0 -2012-02-03 07:00:00,10164.0 -2012-02-03 08:00:00,11370.0 -2012-02-03 09:00:00,11981.0 -2012-02-03 10:00:00,12190.0 -2012-02-03 11:00:00,12285.0 -2012-02-03 12:00:00,12338.0 -2012-02-03 13:00:00,12265.0 -2012-02-03 14:00:00,12168.0 -2012-02-03 15:00:00,12158.0 -2012-02-03 16:00:00,12059.0 -2012-02-03 17:00:00,12052.0 -2012-02-03 18:00:00,12311.0 -2012-02-03 19:00:00,12879.0 -2012-02-03 20:00:00,12758.0 -2012-02-03 21:00:00,12470.0 -2012-02-03 22:00:00,12189.0 -2012-02-03 23:00:00,11716.0 -2012-02-04 00:00:00,11052.0 -2012-02-02 01:00:00,10167.0 -2012-02-02 02:00:00,9675.0 -2012-02-02 03:00:00,9425.0 -2012-02-02 04:00:00,9268.0 -2012-02-02 05:00:00,9244.0 -2012-02-02 06:00:00,9459.0 -2012-02-02 07:00:00,10192.0 -2012-02-02 08:00:00,11407.0 -2012-02-02 09:00:00,12032.0 -2012-02-02 10:00:00,12170.0 -2012-02-02 11:00:00,12234.0 -2012-02-02 12:00:00,12272.0 -2012-02-02 13:00:00,12142.0 -2012-02-02 14:00:00,12033.0 -2012-02-02 15:00:00,11926.0 -2012-02-02 16:00:00,11765.0 -2012-02-02 17:00:00,11710.0 -2012-02-02 18:00:00,11941.0 -2012-02-02 19:00:00,12676.0 -2012-02-02 20:00:00,12721.0 -2012-02-02 21:00:00,12562.0 -2012-02-02 22:00:00,12234.0 -2012-02-02 23:00:00,11770.0 -2012-02-03 00:00:00,10941.0 -2012-02-01 01:00:00,9792.0 -2012-02-01 02:00:00,9338.0 -2012-02-01 03:00:00,9077.0 -2012-02-01 04:00:00,8916.0 -2012-02-01 05:00:00,8944.0 -2012-02-01 06:00:00,9208.0 -2012-02-01 07:00:00,9977.0 -2012-02-01 08:00:00,11279.0 -2012-02-01 09:00:00,11918.0 -2012-02-01 10:00:00,12082.0 -2012-02-01 11:00:00,12114.0 -2012-02-01 12:00:00,12057.0 -2012-02-01 13:00:00,11919.0 -2012-02-01 14:00:00,11741.0 -2012-02-01 15:00:00,11690.0 -2012-02-01 16:00:00,11557.0 -2012-02-01 17:00:00,11524.0 -2012-02-01 18:00:00,11768.0 -2012-02-01 19:00:00,12617.0 -2012-02-01 20:00:00,12684.0 -2012-02-01 21:00:00,12539.0 -2012-02-01 22:00:00,12262.0 -2012-02-01 23:00:00,11755.0 -2012-02-02 00:00:00,10923.0 -2012-01-31 01:00:00,10155.0 -2012-01-31 02:00:00,9649.0 -2012-01-31 03:00:00,9333.0 -2012-01-31 04:00:00,9177.0 -2012-01-31 05:00:00,9148.0 -2012-01-31 06:00:00,9380.0 -2012-01-31 07:00:00,10108.0 -2012-01-31 08:00:00,11375.0 -2012-01-31 09:00:00,11778.0 -2012-01-31 10:00:00,11821.0 -2012-01-31 11:00:00,11793.0 -2012-01-31 12:00:00,11792.0 -2012-01-31 13:00:00,11787.0 -2012-01-31 14:00:00,11727.0 -2012-01-31 15:00:00,11751.0 -2012-01-31 16:00:00,11674.0 -2012-01-31 17:00:00,11686.0 -2012-01-31 18:00:00,11962.0 -2012-01-31 19:00:00,12531.0 -2012-01-31 20:00:00,12407.0 -2012-01-31 21:00:00,12194.0 -2012-01-31 22:00:00,11843.0 -2012-01-31 23:00:00,11323.0 -2012-02-01 00:00:00,10515.0 -2012-01-30 01:00:00,10430.0 -2012-01-30 02:00:00,10059.0 -2012-01-30 03:00:00,9865.0 -2012-01-30 04:00:00,9752.0 -2012-01-30 05:00:00,9770.0 -2012-01-30 06:00:00,10084.0 -2012-01-30 07:00:00,10863.0 -2012-01-30 08:00:00,12167.0 -2012-01-30 09:00:00,12845.0 -2012-01-30 10:00:00,13019.0 -2012-01-30 11:00:00,12996.0 -2012-01-30 12:00:00,12936.0 -2012-01-30 13:00:00,12744.0 -2012-01-30 14:00:00,12516.0 -2012-01-30 15:00:00,12295.0 -2012-01-30 16:00:00,11988.0 -2012-01-30 17:00:00,11862.0 -2012-01-30 18:00:00,12009.0 -2012-01-30 19:00:00,12834.0 -2012-01-30 20:00:00,12920.0 -2012-01-30 21:00:00,12701.0 -2012-01-30 22:00:00,12381.0 -2012-01-30 23:00:00,11834.0 -2012-01-31 00:00:00,10923.0 -2012-01-29 01:00:00,10382.0 -2012-01-29 02:00:00,9925.0 -2012-01-29 03:00:00,9666.0 -2012-01-29 04:00:00,9413.0 -2012-01-29 05:00:00,9391.0 -2012-01-29 06:00:00,9377.0 -2012-01-29 07:00:00,9522.0 -2012-01-29 08:00:00,9828.0 -2012-01-29 09:00:00,9977.0 -2012-01-29 10:00:00,10254.0 -2012-01-29 11:00:00,10534.0 -2012-01-29 12:00:00,10606.0 -2012-01-29 13:00:00,10652.0 -2012-01-29 14:00:00,10561.0 -2012-01-29 15:00:00,10435.0 -2012-01-29 16:00:00,10338.0 -2012-01-29 17:00:00,10386.0 -2012-01-29 18:00:00,10787.0 -2012-01-29 19:00:00,11904.0 -2012-01-29 20:00:00,12157.0 -2012-01-29 21:00:00,12125.0 -2012-01-29 22:00:00,11942.0 -2012-01-29 23:00:00,11594.0 -2012-01-30 00:00:00,11020.0 -2012-01-28 01:00:00,10522.0 -2012-01-28 02:00:00,9983.0 -2012-01-28 03:00:00,9702.0 -2012-01-28 04:00:00,9478.0 -2012-01-28 05:00:00,9413.0 -2012-01-28 06:00:00,9482.0 -2012-01-28 07:00:00,9824.0 -2012-01-28 08:00:00,10406.0 -2012-01-28 09:00:00,10653.0 -2012-01-28 10:00:00,10931.0 -2012-01-28 11:00:00,11085.0 -2012-01-28 12:00:00,11205.0 -2012-01-28 13:00:00,11163.0 -2012-01-28 14:00:00,11081.0 -2012-01-28 15:00:00,10842.0 -2012-01-28 16:00:00,10701.0 -2012-01-28 17:00:00,10657.0 -2012-01-28 18:00:00,11006.0 -2012-01-28 19:00:00,11938.0 -2012-01-28 20:00:00,12103.0 -2012-01-28 21:00:00,11969.0 -2012-01-28 22:00:00,11746.0 -2012-01-28 23:00:00,11432.0 -2012-01-29 00:00:00,10903.0 -2012-01-27 01:00:00,10551.0 -2012-01-27 02:00:00,10086.0 -2012-01-27 03:00:00,9775.0 -2012-01-27 04:00:00,9595.0 -2012-01-27 05:00:00,9560.0 -2012-01-27 06:00:00,9812.0 -2012-01-27 07:00:00,10471.0 -2012-01-27 08:00:00,11731.0 -2012-01-27 09:00:00,12148.0 -2012-01-27 10:00:00,12186.0 -2012-01-27 11:00:00,12172.0 -2012-01-27 12:00:00,12160.0 -2012-01-27 13:00:00,12049.0 -2012-01-27 14:00:00,11907.0 -2012-01-27 15:00:00,11849.0 -2012-01-27 16:00:00,11658.0 -2012-01-27 17:00:00,11621.0 -2012-01-27 18:00:00,12004.0 -2012-01-27 19:00:00,12716.0 -2012-01-27 20:00:00,12666.0 -2012-01-27 21:00:00,12411.0 -2012-01-27 22:00:00,12138.0 -2012-01-27 23:00:00,11798.0 -2012-01-28 00:00:00,11125.0 -2012-01-26 01:00:00,10694.0 -2012-01-26 02:00:00,10187.0 -2012-01-26 03:00:00,9897.0 -2012-01-26 04:00:00,9703.0 -2012-01-26 05:00:00,9698.0 -2012-01-26 06:00:00,9932.0 -2012-01-26 07:00:00,10636.0 -2012-01-26 08:00:00,11906.0 -2012-01-26 09:00:00,12512.0 -2012-01-26 10:00:00,12597.0 -2012-01-26 11:00:00,12692.0 -2012-01-26 12:00:00,12787.0 -2012-01-26 13:00:00,12730.0 -2012-01-26 14:00:00,12638.0 -2012-01-26 15:00:00,12580.0 -2012-01-26 16:00:00,12466.0 -2012-01-26 17:00:00,12475.0 -2012-01-26 18:00:00,12796.0 -2012-01-26 19:00:00,13337.0 -2012-01-26 20:00:00,13299.0 -2012-01-26 21:00:00,13044.0 -2012-01-26 22:00:00,12668.0 -2012-01-26 23:00:00,12160.0 -2012-01-27 00:00:00,11346.0 -2012-01-25 01:00:00,10894.0 -2012-01-25 02:00:00,10410.0 -2012-01-25 03:00:00,10174.0 -2012-01-25 04:00:00,10023.0 -2012-01-25 05:00:00,10019.0 -2012-01-25 06:00:00,10300.0 -2012-01-25 07:00:00,11026.0 -2012-01-25 08:00:00,12274.0 -2012-01-25 09:00:00,12819.0 -2012-01-25 10:00:00,12850.0 -2012-01-25 11:00:00,12831.0 -2012-01-25 12:00:00,12845.0 -2012-01-25 13:00:00,12840.0 -2012-01-25 14:00:00,12781.0 -2012-01-25 15:00:00,12811.0 -2012-01-25 16:00:00,12716.0 -2012-01-25 17:00:00,12720.0 -2012-01-25 18:00:00,13064.0 -2012-01-25 19:00:00,13611.0 -2012-01-25 20:00:00,13486.0 -2012-01-25 21:00:00,13299.0 -2012-01-25 22:00:00,12970.0 -2012-01-25 23:00:00,12397.0 -2012-01-26 00:00:00,11501.0 -2012-01-24 01:00:00,10977.0 -2012-01-24 02:00:00,10479.0 -2012-01-24 03:00:00,10181.0 -2012-01-24 04:00:00,10012.0 -2012-01-24 05:00:00,9990.0 -2012-01-24 06:00:00,10269.0 -2012-01-24 07:00:00,11093.0 -2012-01-24 08:00:00,12370.0 -2012-01-24 09:00:00,12941.0 -2012-01-24 10:00:00,12918.0 -2012-01-24 11:00:00,12860.0 -2012-01-24 12:00:00,12778.0 -2012-01-24 13:00:00,12642.0 -2012-01-24 14:00:00,12510.0 -2012-01-24 15:00:00,12430.0 -2012-01-24 16:00:00,12273.0 -2012-01-24 17:00:00,12225.0 -2012-01-24 18:00:00,12583.0 -2012-01-24 19:00:00,13472.0 -2012-01-24 20:00:00,13451.0 -2012-01-24 21:00:00,13266.0 -2012-01-24 22:00:00,12999.0 -2012-01-24 23:00:00,12489.0 -2012-01-25 00:00:00,11641.0 -2012-01-23 01:00:00,10192.0 -2012-01-23 02:00:00,9742.0 -2012-01-23 03:00:00,9466.0 -2012-01-23 04:00:00,9293.0 -2012-01-23 05:00:00,9313.0 -2012-01-23 06:00:00,9565.0 -2012-01-23 07:00:00,10308.0 -2012-01-23 08:00:00,11570.0 -2012-01-23 09:00:00,12327.0 -2012-01-23 10:00:00,12447.0 -2012-01-23 11:00:00,12642.0 -2012-01-23 12:00:00,12847.0 -2012-01-23 13:00:00,12929.0 -2012-01-23 14:00:00,12989.0 -2012-01-23 15:00:00,13049.0 -2012-01-23 16:00:00,13054.0 -2012-01-23 17:00:00,13172.0 -2012-01-23 18:00:00,13582.0 -2012-01-23 19:00:00,14074.0 -2012-01-23 20:00:00,13851.0 -2012-01-23 21:00:00,13571.0 -2012-01-23 22:00:00,13240.0 -2012-01-23 23:00:00,12666.0 -2012-01-24 00:00:00,11787.0 -2012-01-22 01:00:00,11098.0 -2012-01-22 02:00:00,10670.0 -2012-01-22 03:00:00,10396.0 -2012-01-22 04:00:00,10262.0 -2012-01-22 05:00:00,10107.0 -2012-01-22 06:00:00,10166.0 -2012-01-22 07:00:00,10246.0 -2012-01-22 08:00:00,10522.0 -2012-01-22 09:00:00,10604.0 -2012-01-22 10:00:00,10837.0 -2012-01-22 11:00:00,11095.0 -2012-01-22 12:00:00,11242.0 -2012-01-22 13:00:00,11302.0 -2012-01-22 14:00:00,11318.0 -2012-01-22 15:00:00,11248.0 -2012-01-22 16:00:00,11270.0 -2012-01-22 17:00:00,11343.0 -2012-01-22 18:00:00,11707.0 -2012-01-22 19:00:00,12403.0 -2012-01-22 20:00:00,12429.0 -2012-01-22 21:00:00,12202.0 -2012-01-22 22:00:00,11930.0 -2012-01-22 23:00:00,11479.0 -2012-01-23 00:00:00,10872.0 -2012-01-21 01:00:00,11578.0 -2012-01-21 02:00:00,11009.0 -2012-01-21 03:00:00,10693.0 -2012-01-21 04:00:00,10480.0 -2012-01-21 05:00:00,10418.0 -2012-01-21 06:00:00,10491.0 -2012-01-21 07:00:00,10746.0 -2012-01-21 08:00:00,11191.0 -2012-01-21 09:00:00,11471.0 -2012-01-21 10:00:00,11785.0 -2012-01-21 11:00:00,11993.0 -2012-01-21 12:00:00,12161.0 -2012-01-21 13:00:00,12091.0 -2012-01-21 14:00:00,11964.0 -2012-01-21 15:00:00,11728.0 -2012-01-21 16:00:00,11558.0 -2012-01-21 17:00:00,11524.0 -2012-01-21 18:00:00,11813.0 -2012-01-21 19:00:00,12698.0 -2012-01-21 20:00:00,12764.0 -2012-01-21 21:00:00,12638.0 -2012-01-21 22:00:00,12391.0 -2012-01-21 23:00:00,12132.0 -2012-01-22 00:00:00,11657.0 -2012-01-20 01:00:00,12316.0 -2012-01-20 02:00:00,11848.0 -2012-01-20 03:00:00,11541.0 -2012-01-20 04:00:00,11355.0 -2012-01-20 05:00:00,11321.0 -2012-01-20 06:00:00,11572.0 -2012-01-20 07:00:00,12183.0 -2012-01-20 08:00:00,13348.0 -2012-01-20 09:00:00,13978.0 -2012-01-20 10:00:00,13997.0 -2012-01-20 11:00:00,13981.0 -2012-01-20 12:00:00,14092.0 -2012-01-20 13:00:00,14083.0 -2012-01-20 14:00:00,14064.0 -2012-01-20 15:00:00,13977.0 -2012-01-20 16:00:00,13812.0 -2012-01-20 17:00:00,13660.0 -2012-01-20 18:00:00,13948.0 -2012-01-20 19:00:00,14536.0 -2012-01-20 20:00:00,14429.0 -2012-01-20 21:00:00,14093.0 -2012-01-20 22:00:00,13664.0 -2012-01-20 23:00:00,13160.0 -2012-01-21 00:00:00,12327.0 -2012-01-19 01:00:00,11487.0 -2012-01-19 02:00:00,10963.0 -2012-01-19 03:00:00,10617.0 -2012-01-19 04:00:00,10406.0 -2012-01-19 05:00:00,10382.0 -2012-01-19 06:00:00,10652.0 -2012-01-19 07:00:00,11383.0 -2012-01-19 08:00:00,12631.0 -2012-01-19 09:00:00,13353.0 -2012-01-19 10:00:00,13539.0 -2012-01-19 11:00:00,13698.0 -2012-01-19 12:00:00,13788.0 -2012-01-19 13:00:00,13717.0 -2012-01-19 14:00:00,13589.0 -2012-01-19 15:00:00,13503.0 -2012-01-19 16:00:00,13336.0 -2012-01-19 17:00:00,13298.0 -2012-01-19 18:00:00,13794.0 -2012-01-19 19:00:00,14721.0 -2012-01-19 20:00:00,14813.0 -2012-01-19 21:00:00,14649.0 -2012-01-19 22:00:00,14389.0 -2012-01-19 23:00:00,13864.0 -2012-01-20 00:00:00,13064.0 -2012-01-18 01:00:00,11441.0 -2012-01-18 02:00:00,10975.0 -2012-01-18 03:00:00,10736.0 -2012-01-18 04:00:00,10615.0 -2012-01-18 05:00:00,10619.0 -2012-01-18 06:00:00,10945.0 -2012-01-18 07:00:00,11662.0 -2012-01-18 08:00:00,12902.0 -2012-01-18 09:00:00,13448.0 -2012-01-18 10:00:00,13397.0 -2012-01-18 11:00:00,13377.0 -2012-01-18 12:00:00,13341.0 -2012-01-18 13:00:00,13209.0 -2012-01-18 14:00:00,13104.0 -2012-01-18 15:00:00,13042.0 -2012-01-18 16:00:00,12994.0 -2012-01-18 17:00:00,13061.0 -2012-01-18 18:00:00,13490.0 -2012-01-18 19:00:00,14249.0 -2012-01-18 20:00:00,14178.0 -2012-01-18 21:00:00,13964.0 -2012-01-18 22:00:00,13654.0 -2012-01-18 23:00:00,13136.0 -2012-01-19 00:00:00,12257.0 -2012-01-17 01:00:00,10209.0 -2012-01-17 02:00:00,9747.0 -2012-01-17 03:00:00,9476.0 -2012-01-17 04:00:00,9330.0 -2012-01-17 05:00:00,9355.0 -2012-01-17 06:00:00,9623.0 -2012-01-17 07:00:00,10393.0 -2012-01-17 08:00:00,11626.0 -2012-01-17 09:00:00,12493.0 -2012-01-17 10:00:00,12769.0 -2012-01-17 11:00:00,12914.0 -2012-01-17 12:00:00,13047.0 -2012-01-17 13:00:00,13145.0 -2012-01-17 14:00:00,13142.0 -2012-01-17 15:00:00,13177.0 -2012-01-17 16:00:00,13037.0 -2012-01-17 17:00:00,13045.0 -2012-01-17 18:00:00,13462.0 -2012-01-17 19:00:00,14080.0 -2012-01-17 20:00:00,13951.0 -2012-01-17 21:00:00,13787.0 -2012-01-17 22:00:00,13517.0 -2012-01-17 23:00:00,13009.0 -2012-01-18 00:00:00,12191.0 -2012-01-16 01:00:00,10603.0 -2012-01-16 02:00:00,10162.0 -2012-01-16 03:00:00,9958.0 -2012-01-16 04:00:00,9817.0 -2012-01-16 05:00:00,9805.0 -2012-01-16 06:00:00,10048.0 -2012-01-16 07:00:00,10626.0 -2012-01-16 08:00:00,11593.0 -2012-01-16 09:00:00,11988.0 -2012-01-16 10:00:00,12100.0 -2012-01-16 11:00:00,12106.0 -2012-01-16 12:00:00,12295.0 -2012-01-16 13:00:00,12208.0 -2012-01-16 14:00:00,12251.0 -2012-01-16 15:00:00,12333.0 -2012-01-16 16:00:00,12312.0 -2012-01-16 17:00:00,12348.0 -2012-01-16 18:00:00,12727.0 -2012-01-16 19:00:00,13161.0 -2012-01-16 20:00:00,12971.0 -2012-01-16 21:00:00,12709.0 -2012-01-16 22:00:00,12352.0 -2012-01-16 23:00:00,11823.0 -2012-01-17 00:00:00,10981.0 -2012-01-15 01:00:00,11051.0 -2012-01-15 02:00:00,10573.0 -2012-01-15 03:00:00,10319.0 -2012-01-15 04:00:00,10098.0 -2012-01-15 05:00:00,10079.0 -2012-01-15 06:00:00,10057.0 -2012-01-15 07:00:00,10157.0 -2012-01-15 08:00:00,10447.0 -2012-01-15 09:00:00,10493.0 -2012-01-15 10:00:00,10719.0 -2012-01-15 11:00:00,10788.0 -2012-01-15 12:00:00,10970.0 -2012-01-15 13:00:00,10928.0 -2012-01-15 14:00:00,10821.0 -2012-01-15 15:00:00,10667.0 -2012-01-15 16:00:00,10626.0 -2012-01-15 17:00:00,10718.0 -2012-01-15 18:00:00,11236.0 -2012-01-15 19:00:00,12311.0 -2012-01-15 20:00:00,12448.0 -2012-01-15 21:00:00,12339.0 -2012-01-15 22:00:00,12062.0 -2012-01-15 23:00:00,11726.0 -2012-01-16 00:00:00,11143.0 -2012-01-14 01:00:00,11423.0 -2012-01-14 02:00:00,10865.0 -2012-01-14 03:00:00,10604.0 -2012-01-14 04:00:00,10344.0 -2012-01-14 05:00:00,10299.0 -2012-01-14 06:00:00,10363.0 -2012-01-14 07:00:00,10674.0 -2012-01-14 08:00:00,11150.0 -2012-01-14 09:00:00,11482.0 -2012-01-14 10:00:00,11755.0 -2012-01-14 11:00:00,12014.0 -2012-01-14 12:00:00,11982.0 -2012-01-14 13:00:00,11915.0 -2012-01-14 14:00:00,11683.0 -2012-01-14 15:00:00,11397.0 -2012-01-14 16:00:00,11226.0 -2012-01-14 17:00:00,11292.0 -2012-01-14 18:00:00,11706.0 -2012-01-14 19:00:00,12710.0 -2012-01-14 20:00:00,12797.0 -2012-01-14 21:00:00,12651.0 -2012-01-14 22:00:00,12511.0 -2012-01-14 23:00:00,12148.0 -2012-01-15 00:00:00,11627.0 -2012-01-13 01:00:00,11291.0 -2012-01-13 02:00:00,10843.0 -2012-01-13 03:00:00,10582.0 -2012-01-13 04:00:00,10489.0 -2012-01-13 05:00:00,10496.0 -2012-01-13 06:00:00,10752.0 -2012-01-13 07:00:00,11449.0 -2012-01-13 08:00:00,12606.0 -2012-01-13 09:00:00,13108.0 -2012-01-13 10:00:00,13188.0 -2012-01-13 11:00:00,13249.0 -2012-01-13 12:00:00,13343.0 -2012-01-13 13:00:00,13291.0 -2012-01-13 14:00:00,13158.0 -2012-01-13 15:00:00,13121.0 -2012-01-13 16:00:00,13041.0 -2012-01-13 17:00:00,13000.0 -2012-01-13 18:00:00,13384.0 -2012-01-13 19:00:00,14011.0 -2012-01-13 20:00:00,13883.0 -2012-01-13 21:00:00,13611.0 -2012-01-13 22:00:00,13280.0 -2012-01-13 23:00:00,12837.0 -2012-01-14 00:00:00,12124.0 -2012-01-12 01:00:00,10139.0 -2012-01-12 02:00:00,9669.0 -2012-01-12 03:00:00,9385.0 -2012-01-12 04:00:00,9229.0 -2012-01-12 05:00:00,9227.0 -2012-01-12 06:00:00,9501.0 -2012-01-12 07:00:00,10291.0 -2012-01-12 08:00:00,11551.0 -2012-01-12 09:00:00,12310.0 -2012-01-12 10:00:00,12524.0 -2012-01-12 11:00:00,12649.0 -2012-01-12 12:00:00,12854.0 -2012-01-12 13:00:00,12887.0 -2012-01-12 14:00:00,12838.0 -2012-01-12 15:00:00,12852.0 -2012-01-12 16:00:00,12819.0 -2012-01-12 17:00:00,12812.0 -2012-01-12 18:00:00,13274.0 -2012-01-12 19:00:00,13983.0 -2012-01-12 20:00:00,13927.0 -2012-01-12 21:00:00,13760.0 -2012-01-12 22:00:00,13444.0 -2012-01-12 23:00:00,12919.0 -2012-01-13 00:00:00,12110.0 -2012-01-11 01:00:00,10106.0 -2012-01-11 02:00:00,9593.0 -2012-01-11 03:00:00,9328.0 -2012-01-11 04:00:00,9169.0 -2012-01-11 05:00:00,9188.0 -2012-01-11 06:00:00,9449.0 -2012-01-11 07:00:00,10221.0 -2012-01-11 08:00:00,11476.0 -2012-01-11 09:00:00,12140.0 -2012-01-11 10:00:00,12083.0 -2012-01-11 11:00:00,12021.0 -2012-01-11 12:00:00,11974.0 -2012-01-11 13:00:00,11864.0 -2012-01-11 14:00:00,11709.0 -2012-01-11 15:00:00,11661.0 -2012-01-11 16:00:00,11529.0 -2012-01-11 17:00:00,11485.0 -2012-01-11 18:00:00,11858.0 -2012-01-11 19:00:00,12685.0 -2012-01-11 20:00:00,12639.0 -2012-01-11 21:00:00,12475.0 -2012-01-11 22:00:00,12182.0 -2012-01-11 23:00:00,11703.0 -2012-01-12 00:00:00,10918.0 -2012-01-10 01:00:00,10354.0 -2012-01-10 02:00:00,9801.0 -2012-01-10 03:00:00,9524.0 -2012-01-10 04:00:00,9350.0 -2012-01-10 05:00:00,9321.0 -2012-01-10 06:00:00,9607.0 -2012-01-10 07:00:00,10353.0 -2012-01-10 08:00:00,11621.0 -2012-01-10 09:00:00,12214.0 -2012-01-10 10:00:00,12173.0 -2012-01-10 11:00:00,12137.0 -2012-01-10 12:00:00,12117.0 -2012-01-10 13:00:00,11940.0 -2012-01-10 14:00:00,11833.0 -2012-01-10 15:00:00,11784.0 -2012-01-10 16:00:00,11617.0 -2012-01-10 17:00:00,11530.0 -2012-01-10 18:00:00,11940.0 -2012-01-10 19:00:00,12771.0 -2012-01-10 20:00:00,12660.0 -2012-01-10 21:00:00,12466.0 -2012-01-10 22:00:00,12228.0 -2012-01-10 23:00:00,11689.0 -2012-01-11 00:00:00,10864.0 -2012-01-09 01:00:00,9786.0 -2012-01-09 02:00:00,9494.0 -2012-01-09 03:00:00,9301.0 -2012-01-09 04:00:00,9212.0 -2012-01-09 05:00:00,9268.0 -2012-01-09 06:00:00,9620.0 -2012-01-09 07:00:00,10395.0 -2012-01-09 08:00:00,11740.0 -2012-01-09 09:00:00,12435.0 -2012-01-09 10:00:00,12487.0 -2012-01-09 11:00:00,12611.0 -2012-01-09 12:00:00,12617.0 -2012-01-09 13:00:00,12516.0 -2012-01-09 14:00:00,12307.0 -2012-01-09 15:00:00,12195.0 -2012-01-09 16:00:00,11998.0 -2012-01-09 17:00:00,11965.0 -2012-01-09 18:00:00,12407.0 -2012-01-09 19:00:00,13192.0 -2012-01-09 20:00:00,13091.0 -2012-01-09 21:00:00,12882.0 -2012-01-09 22:00:00,12590.0 -2012-01-09 23:00:00,12022.0 -2012-01-10 00:00:00,11171.0 -2012-01-08 01:00:00,9926.0 -2012-01-08 02:00:00,9441.0 -2012-01-08 03:00:00,9182.0 -2012-01-08 04:00:00,9056.0 -2012-01-08 05:00:00,8965.0 -2012-01-08 06:00:00,9049.0 -2012-01-08 07:00:00,9187.0 -2012-01-08 08:00:00,9475.0 -2012-01-08 09:00:00,9528.0 -2012-01-08 10:00:00,9661.0 -2012-01-08 11:00:00,9790.0 -2012-01-08 12:00:00,9825.0 -2012-01-08 13:00:00,9887.0 -2012-01-08 14:00:00,9913.0 -2012-01-08 15:00:00,9838.0 -2012-01-08 16:00:00,9897.0 -2012-01-08 17:00:00,10162.0 -2012-01-08 18:00:00,10869.0 -2012-01-08 19:00:00,11529.0 -2012-01-08 20:00:00,11573.0 -2012-01-08 21:00:00,11454.0 -2012-01-08 22:00:00,11319.0 -2012-01-08 23:00:00,10952.0 -2012-01-09 00:00:00,10352.0 -2012-01-07 01:00:00,10120.0 -2012-01-07 02:00:00,9538.0 -2012-01-07 03:00:00,9199.0 -2012-01-07 04:00:00,8992.0 -2012-01-07 05:00:00,8937.0 -2012-01-07 06:00:00,9031.0 -2012-01-07 07:00:00,9346.0 -2012-01-07 08:00:00,9869.0 -2012-01-07 09:00:00,10100.0 -2012-01-07 10:00:00,10306.0 -2012-01-07 11:00:00,10463.0 -2012-01-07 12:00:00,10543.0 -2012-01-07 13:00:00,10515.0 -2012-01-07 14:00:00,10377.0 -2012-01-07 15:00:00,10172.0 -2012-01-07 16:00:00,9981.0 -2012-01-07 17:00:00,10039.0 -2012-01-07 18:00:00,10688.0 -2012-01-07 19:00:00,11534.0 -2012-01-07 20:00:00,11511.0 -2012-01-07 21:00:00,11426.0 -2012-01-07 22:00:00,11220.0 -2012-01-07 23:00:00,10990.0 -2012-01-08 00:00:00,10443.0 -2012-01-06 01:00:00,10301.0 -2012-01-06 02:00:00,9755.0 -2012-01-06 03:00:00,9393.0 -2012-01-06 04:00:00,9196.0 -2012-01-06 05:00:00,9170.0 -2012-01-06 06:00:00,9391.0 -2012-01-06 07:00:00,10031.0 -2012-01-06 08:00:00,11147.0 -2012-01-06 09:00:00,11787.0 -2012-01-06 10:00:00,11855.0 -2012-01-06 11:00:00,11821.0 -2012-01-06 12:00:00,11795.0 -2012-01-06 13:00:00,11702.0 -2012-01-06 14:00:00,11603.0 -2012-01-06 15:00:00,11526.0 -2012-01-06 16:00:00,11352.0 -2012-01-06 17:00:00,11248.0 -2012-01-06 18:00:00,11805.0 -2012-01-06 19:00:00,12473.0 -2012-01-06 20:00:00,12359.0 -2012-01-06 21:00:00,12134.0 -2012-01-06 22:00:00,11845.0 -2012-01-06 23:00:00,11500.0 -2012-01-07 00:00:00,10802.0 -2012-01-05 01:00:00,10877.0 -2012-01-05 02:00:00,10326.0 -2012-01-05 03:00:00,10003.0 -2012-01-05 04:00:00,9803.0 -2012-01-05 05:00:00,9804.0 -2012-01-05 06:00:00,10055.0 -2012-01-05 07:00:00,10742.0 -2012-01-05 08:00:00,11893.0 -2012-01-05 09:00:00,12431.0 -2012-01-05 10:00:00,12460.0 -2012-01-05 11:00:00,12373.0 -2012-01-05 12:00:00,12309.0 -2012-01-05 13:00:00,12245.0 -2012-01-05 14:00:00,12111.0 -2012-01-05 15:00:00,12067.0 -2012-01-05 16:00:00,11874.0 -2012-01-05 17:00:00,11756.0 -2012-01-05 18:00:00,12304.0 -2012-01-05 19:00:00,13126.0 -2012-01-05 20:00:00,13087.0 -2012-01-05 21:00:00,12861.0 -2012-01-05 22:00:00,12514.0 -2012-01-05 23:00:00,11971.0 -2012-01-06 00:00:00,11141.0 -2012-01-04 01:00:00,11391.0 -2012-01-04 02:00:00,10810.0 -2012-01-04 03:00:00,10471.0 -2012-01-04 04:00:00,10253.0 -2012-01-04 05:00:00,10166.0 -2012-01-04 06:00:00,10406.0 -2012-01-04 07:00:00,11046.0 -2012-01-04 08:00:00,12155.0 -2012-01-04 09:00:00,12692.0 -2012-01-04 10:00:00,12660.0 -2012-01-04 11:00:00,12598.0 -2012-01-04 12:00:00,12575.0 -2012-01-04 13:00:00,12472.0 -2012-01-04 14:00:00,12356.0 -2012-01-04 15:00:00,12404.0 -2012-01-04 16:00:00,12418.0 -2012-01-04 17:00:00,12450.0 -2012-01-04 18:00:00,13102.0 -2012-01-04 19:00:00,13701.0 -2012-01-04 20:00:00,13553.0 -2012-01-04 21:00:00,13362.0 -2012-01-04 22:00:00,13045.0 -2012-01-04 23:00:00,12501.0 -2012-01-05 00:00:00,11664.0 -2012-01-03 01:00:00,11130.0 -2012-01-03 02:00:00,10698.0 -2012-01-03 03:00:00,10440.0 -2012-01-03 04:00:00,10315.0 -2012-01-03 05:00:00,10348.0 -2012-01-03 06:00:00,10620.0 -2012-01-03 07:00:00,11331.0 -2012-01-03 08:00:00,12475.0 -2012-01-03 09:00:00,13078.0 -2012-01-03 10:00:00,13169.0 -2012-01-03 11:00:00,13180.0 -2012-01-03 12:00:00,13205.0 -2012-01-03 13:00:00,13140.0 -2012-01-03 14:00:00,13083.0 -2012-01-03 15:00:00,13062.0 -2012-01-03 16:00:00,12815.0 -2012-01-03 17:00:00,12811.0 -2012-01-03 18:00:00,13523.0 -2012-01-03 19:00:00,14300.0 -2012-01-03 20:00:00,14226.0 -2012-01-03 21:00:00,14046.0 -2012-01-03 22:00:00,13751.0 -2012-01-03 23:00:00,13152.0 -2012-01-04 00:00:00,12282.0 -2012-01-02 01:00:00,10170.0 -2012-01-02 02:00:00,9731.0 -2012-01-02 03:00:00,9459.0 -2012-01-02 04:00:00,9297.0 -2012-01-02 05:00:00,9323.0 -2012-01-02 06:00:00,9451.0 -2012-01-02 07:00:00,9880.0 -2012-01-02 08:00:00,10399.0 -2012-01-02 09:00:00,10675.0 -2012-01-02 10:00:00,10873.0 -2012-01-02 11:00:00,11237.0 -2012-01-02 12:00:00,11503.0 -2012-01-02 13:00:00,11639.0 -2012-01-02 14:00:00,11675.0 -2012-01-02 15:00:00,11673.0 -2012-01-02 16:00:00,11686.0 -2012-01-02 17:00:00,11855.0 -2012-01-02 18:00:00,12670.0 -2012-01-02 19:00:00,13332.0 -2012-01-02 20:00:00,13308.0 -2012-01-02 21:00:00,13179.0 -2012-01-02 22:00:00,12956.0 -2012-01-02 23:00:00,12536.0 -2012-01-03 00:00:00,11810.0 -2012-01-01 01:00:00,9906.0 -2012-01-01 02:00:00,9407.0 -2012-01-01 03:00:00,9086.0 -2012-01-01 04:00:00,8758.0 -2012-01-01 05:00:00,8483.0 -2012-01-01 06:00:00,8432.0 -2012-01-01 07:00:00,8487.0 -2012-01-01 08:00:00,8686.0 -2012-01-01 09:00:00,8953.0 -2012-01-01 10:00:00,9050.0 -2012-01-01 11:00:00,9366.0 -2012-01-01 12:00:00,9387.0 -2012-01-01 13:00:00,9597.0 -2012-01-01 14:00:00,9924.0 -2012-01-01 15:00:00,10089.0 -2012-01-01 16:00:00,10130.0 -2012-01-01 17:00:00,10363.0 -2012-01-01 18:00:00,11132.0 -2012-01-01 19:00:00,11757.0 -2012-01-01 20:00:00,11799.0 -2012-01-01 21:00:00,11681.0 -2012-01-01 22:00:00,11490.0 -2012-01-01 23:00:00,11158.0 -2012-01-02 00:00:00,10721.0 -2013-12-31 01:00:00,11979.0 -2013-12-31 02:00:00,11438.0 -2013-12-31 03:00:00,11113.0 -2013-12-31 04:00:00,10928.0 -2013-12-31 05:00:00,10926.0 -2013-12-31 06:00:00,11078.0 -2013-12-31 07:00:00,11492.0 -2013-12-31 08:00:00,12172.0 -2013-12-31 09:00:00,12473.0 -2013-12-31 10:00:00,12597.0 -2013-12-31 11:00:00,12732.0 -2013-12-31 12:00:00,12828.0 -2013-12-31 13:00:00,12902.0 -2013-12-31 14:00:00,12832.0 -2013-12-31 15:00:00,12794.0 -2013-12-31 16:00:00,12768.0 -2013-12-31 17:00:00,12735.0 -2013-12-31 18:00:00,13383.0 -2013-12-31 19:00:00,14041.0 -2013-12-31 20:00:00,13768.0 -2013-12-31 21:00:00,13358.0 -2013-12-31 22:00:00,12922.0 -2013-12-31 23:00:00,12503.0 -2014-01-01 00:00:00,12035.0 -2013-12-30 01:00:00,11183.0 -2013-12-30 02:00:00,10850.0 -2013-12-30 03:00:00,10682.0 -2013-12-30 04:00:00,10640.0 -2013-12-30 05:00:00,10702.0 -2013-12-30 06:00:00,10971.0 -2013-12-30 07:00:00,11538.0 -2013-12-30 08:00:00,12475.0 -2013-12-30 09:00:00,12903.0 -2013-12-30 10:00:00,13042.0 -2013-12-30 11:00:00,13193.0 -2013-12-30 12:00:00,13307.0 -2013-12-30 13:00:00,13328.0 -2013-12-30 14:00:00,13221.0 -2013-12-30 15:00:00,13127.0 -2013-12-30 16:00:00,13062.0 -2013-12-30 17:00:00,13069.0 -2013-12-30 18:00:00,13834.0 -2013-12-30 19:00:00,14525.0 -2013-12-30 20:00:00,14427.0 -2013-12-30 21:00:00,14260.0 -2013-12-30 22:00:00,14018.0 -2013-12-30 23:00:00,13530.0 -2013-12-31 00:00:00,12756.0 -2013-12-29 01:00:00,10088.0 -2013-12-29 02:00:00,9621.0 -2013-12-29 03:00:00,9309.0 -2013-12-29 04:00:00,9074.0 -2013-12-29 05:00:00,8998.0 -2013-12-29 06:00:00,8997.0 -2013-12-29 07:00:00,9180.0 -2013-12-29 08:00:00,9457.0 -2013-12-29 09:00:00,9634.0 -2013-12-29 10:00:00,9874.0 -2013-12-29 11:00:00,10336.0 -2013-12-29 12:00:00,10678.0 -2013-12-29 13:00:00,10933.0 -2013-12-29 14:00:00,11032.0 -2013-12-29 15:00:00,11157.0 -2013-12-29 16:00:00,11267.0 -2013-12-29 17:00:00,11444.0 -2013-12-29 18:00:00,12134.0 -2013-12-29 19:00:00,12695.0 -2013-12-29 20:00:00,12741.0 -2013-12-29 21:00:00,12694.0 -2013-12-29 22:00:00,12569.0 -2013-12-29 23:00:00,12273.0 -2013-12-30 00:00:00,11754.0 -2013-12-28 01:00:00,10643.0 -2013-12-28 02:00:00,10069.0 -2013-12-28 03:00:00,9710.0 -2013-12-28 04:00:00,9477.0 -2013-12-28 05:00:00,9404.0 -2013-12-28 06:00:00,9469.0 -2013-12-28 07:00:00,9699.0 -2013-12-28 08:00:00,10157.0 -2013-12-28 09:00:00,10341.0 -2013-12-28 10:00:00,10451.0 -2013-12-28 11:00:00,10541.0 -2013-12-28 12:00:00,10577.0 -2013-12-28 13:00:00,10531.0 -2013-12-28 14:00:00,10354.0 -2013-12-28 15:00:00,10112.0 -2013-12-28 16:00:00,9979.0 -2013-12-28 17:00:00,10070.0 -2013-12-28 18:00:00,10796.0 -2013-12-28 19:00:00,11684.0 -2013-12-28 20:00:00,11678.0 -2013-12-28 21:00:00,11598.0 -2013-12-28 22:00:00,11456.0 -2013-12-28 23:00:00,11176.0 -2013-12-29 00:00:00,10687.0 -2013-12-27 01:00:00,10909.0 -2013-12-27 02:00:00,10319.0 -2013-12-27 03:00:00,9958.0 -2013-12-27 04:00:00,9749.0 -2013-12-27 05:00:00,9732.0 -2013-12-27 06:00:00,9975.0 -2013-12-27 07:00:00,10572.0 -2013-12-27 08:00:00,11436.0 -2013-12-27 09:00:00,11814.0 -2013-12-27 10:00:00,11965.0 -2013-12-27 11:00:00,11983.0 -2013-12-27 12:00:00,12002.0 -2013-12-27 13:00:00,11917.0 -2013-12-27 14:00:00,11794.0 -2013-12-27 15:00:00,11712.0 -2013-12-27 16:00:00,11619.0 -2013-12-27 17:00:00,11583.0 -2013-12-27 18:00:00,12311.0 -2013-12-27 19:00:00,13092.0 -2013-12-27 20:00:00,12973.0 -2013-12-27 21:00:00,12770.0 -2013-12-27 22:00:00,12476.0 -2013-12-27 23:00:00,12116.0 -2013-12-28 00:00:00,11410.0 -2013-12-26 01:00:00,10587.0 -2013-12-26 02:00:00,10159.0 -2013-12-26 03:00:00,9979.0 -2013-12-26 04:00:00,9928.0 -2013-12-26 05:00:00,10002.0 -2013-12-26 06:00:00,10259.0 -2013-12-26 07:00:00,10883.0 -2013-12-26 08:00:00,11772.0 -2013-12-26 09:00:00,12194.0 -2013-12-26 10:00:00,12431.0 -2013-12-26 11:00:00,12706.0 -2013-12-26 12:00:00,12845.0 -2013-12-26 13:00:00,12885.0 -2013-12-26 14:00:00,12799.0 -2013-12-26 15:00:00,12628.0 -2013-12-26 16:00:00,12462.0 -2013-12-26 17:00:00,12487.0 -2013-12-26 18:00:00,13261.0 -2013-12-26 19:00:00,13895.0 -2013-12-26 20:00:00,13646.0 -2013-12-26 21:00:00,13345.0 -2013-12-26 22:00:00,13076.0 -2013-12-26 23:00:00,12530.0 -2013-12-27 00:00:00,11718.0 -2013-12-25 01:00:00,11426.0 -2013-12-25 02:00:00,10915.0 -2013-12-25 03:00:00,10537.0 -2013-12-25 04:00:00,10316.0 -2013-12-25 05:00:00,10204.0 -2013-12-25 06:00:00,10210.0 -2013-12-25 07:00:00,10359.0 -2013-12-25 08:00:00,10586.0 -2013-12-25 09:00:00,10732.0 -2013-12-25 10:00:00,10843.0 -2013-12-25 11:00:00,11057.0 -2013-12-25 12:00:00,11162.0 -2013-12-25 13:00:00,11176.0 -2013-12-25 14:00:00,11173.0 -2013-12-25 15:00:00,11078.0 -2013-12-25 16:00:00,11020.0 -2013-12-25 17:00:00,11045.0 -2013-12-25 18:00:00,11627.0 -2013-12-25 19:00:00,11996.0 -2013-12-25 20:00:00,11953.0 -2013-12-25 21:00:00,11874.0 -2013-12-25 22:00:00,11788.0 -2013-12-25 23:00:00,11603.0 -2013-12-26 00:00:00,11152.0 -2013-12-24 01:00:00,12691.0 -2013-12-24 02:00:00,12058.0 -2013-12-24 03:00:00,11664.0 -2013-12-24 04:00:00,11421.0 -2013-12-24 05:00:00,11370.0 -2013-12-24 06:00:00,11437.0 -2013-12-24 07:00:00,11794.0 -2013-12-24 08:00:00,12318.0 -2013-12-24 09:00:00,12609.0 -2013-12-24 10:00:00,12785.0 -2013-12-24 11:00:00,12924.0 -2013-12-24 12:00:00,12885.0 -2013-12-24 13:00:00,12695.0 -2013-12-24 14:00:00,12413.0 -2013-12-24 15:00:00,12162.0 -2013-12-24 16:00:00,11966.0 -2013-12-24 17:00:00,12003.0 -2013-12-24 18:00:00,12855.0 -2013-12-24 19:00:00,13438.0 -2013-12-24 20:00:00,13158.0 -2013-12-24 21:00:00,12857.0 -2013-12-24 22:00:00,12660.0 -2013-12-24 23:00:00,12396.0 -2013-12-25 00:00:00,11944.0 -2013-12-23 01:00:00,10968.0 -2013-12-23 02:00:00,10424.0 -2013-12-23 03:00:00,10161.0 -2013-12-23 04:00:00,10020.0 -2013-12-23 05:00:00,10013.0 -2013-12-23 06:00:00,10285.0 -2013-12-23 07:00:00,10938.0 -2013-12-23 08:00:00,11847.0 -2013-12-23 09:00:00,12478.0 -2013-12-23 10:00:00,12717.0 -2013-12-23 11:00:00,12896.0 -2013-12-23 12:00:00,12894.0 -2013-12-23 13:00:00,12894.0 -2013-12-23 14:00:00,12831.0 -2013-12-23 15:00:00,12831.0 -2013-12-23 16:00:00,12814.0 -2013-12-23 17:00:00,12931.0 -2013-12-23 18:00:00,13879.0 -2013-12-23 19:00:00,14828.0 -2013-12-23 20:00:00,14817.0 -2013-12-23 21:00:00,14733.0 -2013-12-23 22:00:00,14577.0 -2013-12-23 23:00:00,14239.0 -2013-12-24 00:00:00,13487.0 -2013-12-22 01:00:00,10813.0 -2013-12-22 02:00:00,10257.0 -2013-12-22 03:00:00,9913.0 -2013-12-22 04:00:00,9659.0 -2013-12-22 05:00:00,9572.0 -2013-12-22 06:00:00,9562.0 -2013-12-22 07:00:00,9722.0 -2013-12-22 08:00:00,10012.0 -2013-12-22 09:00:00,10226.0 -2013-12-22 10:00:00,10408.0 -2013-12-22 11:00:00,10597.0 -2013-12-22 12:00:00,10794.0 -2013-12-22 13:00:00,10844.0 -2013-12-22 14:00:00,10867.0 -2013-12-22 15:00:00,10885.0 -2013-12-22 16:00:00,10942.0 -2013-12-22 17:00:00,11150.0 -2013-12-22 18:00:00,12025.0 -2013-12-22 19:00:00,12727.0 -2013-12-22 20:00:00,12748.0 -2013-12-22 21:00:00,12734.0 -2013-12-22 22:00:00,12574.0 -2013-12-22 23:00:00,12263.0 -2013-12-23 00:00:00,11657.0 -2013-12-21 01:00:00,10950.0 -2013-12-21 02:00:00,10359.0 -2013-12-21 03:00:00,9949.0 -2013-12-21 04:00:00,9692.0 -2013-12-21 05:00:00,9646.0 -2013-12-21 06:00:00,9695.0 -2013-12-21 07:00:00,9975.0 -2013-12-21 08:00:00,10500.0 -2013-12-21 09:00:00,10969.0 -2013-12-21 10:00:00,11251.0 -2013-12-21 11:00:00,11551.0 -2013-12-21 12:00:00,11731.0 -2013-12-21 13:00:00,11762.0 -2013-12-21 14:00:00,11686.0 -2013-12-21 15:00:00,11587.0 -2013-12-21 16:00:00,11518.0 -2013-12-21 17:00:00,11724.0 -2013-12-21 18:00:00,12439.0 -2013-12-21 19:00:00,12815.0 -2013-12-21 20:00:00,12751.0 -2013-12-21 21:00:00,12628.0 -2013-12-21 22:00:00,12410.0 -2013-12-21 23:00:00,12083.0 -2013-12-22 00:00:00,11486.0 -2013-12-20 01:00:00,10932.0 -2013-12-20 02:00:00,10322.0 -2013-12-20 03:00:00,9925.0 -2013-12-20 04:00:00,9735.0 -2013-12-20 05:00:00,9712.0 -2013-12-20 06:00:00,9968.0 -2013-12-20 07:00:00,10664.0 -2013-12-20 08:00:00,11853.0 -2013-12-20 09:00:00,12618.0 -2013-12-20 10:00:00,12777.0 -2013-12-20 11:00:00,12880.0 -2013-12-20 12:00:00,12983.0 -2013-12-20 13:00:00,12933.0 -2013-12-20 14:00:00,12839.0 -2013-12-20 15:00:00,12826.0 -2013-12-20 16:00:00,12775.0 -2013-12-20 17:00:00,12808.0 -2013-12-20 18:00:00,13509.0 -2013-12-20 19:00:00,13792.0 -2013-12-20 20:00:00,13586.0 -2013-12-20 21:00:00,13353.0 -2013-12-20 22:00:00,12973.0 -2013-12-20 23:00:00,12526.0 -2013-12-21 00:00:00,11818.0 -2013-12-19 01:00:00,11438.0 -2013-12-19 02:00:00,10791.0 -2013-12-19 03:00:00,10401.0 -2013-12-19 04:00:00,10176.0 -2013-12-19 05:00:00,10146.0 -2013-12-19 06:00:00,10355.0 -2013-12-19 07:00:00,11099.0 -2013-12-19 08:00:00,12245.0 -2013-12-19 09:00:00,12766.0 -2013-12-19 10:00:00,12777.0 -2013-12-19 11:00:00,12676.0 -2013-12-19 12:00:00,12628.0 -2013-12-19 13:00:00,12538.0 -2013-12-19 14:00:00,12498.0 -2013-12-19 15:00:00,12573.0 -2013-12-19 16:00:00,12551.0 -2013-12-19 17:00:00,12677.0 -2013-12-19 18:00:00,13535.0 -2013-12-19 19:00:00,13984.0 -2013-12-19 20:00:00,13779.0 -2013-12-19 21:00:00,13570.0 -2013-12-19 22:00:00,13312.0 -2013-12-19 23:00:00,12818.0 -2013-12-20 00:00:00,11879.0 -2013-12-18 01:00:00,11688.0 -2013-12-18 02:00:00,11110.0 -2013-12-18 03:00:00,10896.0 -2013-12-18 04:00:00,10624.0 -2013-12-18 05:00:00,10731.0 -2013-12-18 06:00:00,11036.0 -2013-12-18 07:00:00,11825.0 -2013-12-18 08:00:00,12895.0 -2013-12-18 09:00:00,13473.0 -2013-12-18 10:00:00,13451.0 -2013-12-18 11:00:00,13476.0 -2013-12-18 12:00:00,13358.0 -2013-12-18 13:00:00,13114.0 -2013-12-18 14:00:00,12928.0 -2013-12-18 15:00:00,12892.0 -2013-12-18 16:00:00,12844.0 -2013-12-18 17:00:00,12923.0 -2013-12-18 18:00:00,13835.0 -2013-12-18 19:00:00,14588.0 -2013-12-18 20:00:00,14429.0 -2013-12-18 21:00:00,14212.0 -2013-12-18 22:00:00,13933.0 -2013-12-18 23:00:00,13339.0 -2013-12-19 00:00:00,12406.0 -2013-12-17 01:00:00,12083.0 -2013-12-17 02:00:00,11488.0 -2013-12-17 03:00:00,11213.0 -2013-12-17 04:00:00,11068.0 -2013-12-17 05:00:00,11037.0 -2013-12-17 06:00:00,11254.0 -2013-12-17 07:00:00,11975.0 -2013-12-17 08:00:00,13162.0 -2013-12-17 09:00:00,13650.0 -2013-12-17 10:00:00,13700.0 -2013-12-17 11:00:00,13637.0 -2013-12-17 12:00:00,13707.0 -2013-12-17 13:00:00,13445.0 -2013-12-17 14:00:00,13183.0 -2013-12-17 15:00:00,13052.0 -2013-12-17 16:00:00,12911.0 -2013-12-17 17:00:00,13027.0 -2013-12-17 18:00:00,13861.0 -2013-12-17 19:00:00,14629.0 -2013-12-17 20:00:00,14538.0 -2013-12-17 21:00:00,14362.0 -2013-12-17 22:00:00,14075.0 -2013-12-17 23:00:00,13519.0 -2013-12-18 00:00:00,12609.0 -2013-12-16 01:00:00,11707.0 -2013-12-16 02:00:00,11261.0 -2013-12-16 03:00:00,11018.0 -2013-12-16 04:00:00,10917.0 -2013-12-16 05:00:00,10932.0 -2013-12-16 06:00:00,11282.0 -2013-12-16 07:00:00,11989.0 -2013-12-16 08:00:00,13276.0 -2013-12-16 09:00:00,13740.0 -2013-12-16 10:00:00,13819.0 -2013-12-16 11:00:00,13841.0 -2013-12-16 12:00:00,13752.0 -2013-12-16 13:00:00,13717.0 -2013-12-16 14:00:00,13633.0 -2013-12-16 15:00:00,13614.0 -2013-12-16 16:00:00,13567.0 -2013-12-16 17:00:00,13728.0 -2013-12-16 18:00:00,14565.0 -2013-12-16 19:00:00,15091.0 -2013-12-16 20:00:00,14944.0 -2013-12-16 21:00:00,14743.0 -2013-12-16 22:00:00,14495.0 -2013-12-16 23:00:00,13932.0 -2013-12-17 00:00:00,13022.0 -2013-12-15 01:00:00,11178.0 -2013-12-15 02:00:00,10710.0 -2013-12-15 03:00:00,10360.0 -2013-12-15 04:00:00,10226.0 -2013-12-15 05:00:00,10186.0 -2013-12-15 06:00:00,10262.0 -2013-12-15 07:00:00,10442.0 -2013-12-15 08:00:00,10854.0 -2013-12-15 09:00:00,10967.0 -2013-12-15 10:00:00,11367.0 -2013-12-15 11:00:00,11580.0 -2013-12-15 12:00:00,11771.0 -2013-12-15 13:00:00,11831.0 -2013-12-15 14:00:00,11874.0 -2013-12-15 15:00:00,11821.0 -2013-12-15 16:00:00,11750.0 -2013-12-15 17:00:00,11886.0 -2013-12-15 18:00:00,12856.0 -2013-12-15 19:00:00,13755.0 -2013-12-15 20:00:00,13821.0 -2013-12-15 21:00:00,13811.0 -2013-12-15 22:00:00,13574.0 -2013-12-15 23:00:00,13188.0 -2013-12-16 00:00:00,12442.0 -2013-12-14 01:00:00,11618.0 -2013-12-14 02:00:00,10961.0 -2013-12-14 03:00:00,10516.0 -2013-12-14 04:00:00,10314.0 -2013-12-14 05:00:00,10212.0 -2013-12-14 06:00:00,10302.0 -2013-12-14 07:00:00,10587.0 -2013-12-14 08:00:00,11158.0 -2013-12-14 09:00:00,11452.0 -2013-12-14 10:00:00,11805.0 -2013-12-14 11:00:00,12055.0 -2013-12-14 12:00:00,12152.0 -2013-12-14 13:00:00,12118.0 -2013-12-14 14:00:00,11972.0 -2013-12-14 15:00:00,11900.0 -2013-12-14 16:00:00,11787.0 -2013-12-14 17:00:00,11929.0 -2013-12-14 18:00:00,12725.0 -2013-12-14 19:00:00,13352.0 -2013-12-14 20:00:00,13217.0 -2013-12-14 21:00:00,13058.0 -2013-12-14 22:00:00,12854.0 -2013-12-14 23:00:00,12468.0 -2013-12-15 00:00:00,11921.0 -2013-12-13 01:00:00,12245.0 -2013-12-13 02:00:00,11606.0 -2013-12-13 03:00:00,11239.0 -2013-12-13 04:00:00,11038.0 -2013-12-13 05:00:00,10978.0 -2013-12-13 06:00:00,11196.0 -2013-12-13 07:00:00,11833.0 -2013-12-13 08:00:00,12998.0 -2013-12-13 09:00:00,13416.0 -2013-12-13 10:00:00,13450.0 -2013-12-13 11:00:00,13370.0 -2013-12-13 12:00:00,13257.0 -2013-12-13 13:00:00,13037.0 -2013-12-13 14:00:00,12840.0 -2013-12-13 15:00:00,12852.0 -2013-12-13 16:00:00,12861.0 -2013-12-13 17:00:00,13039.0 -2013-12-13 18:00:00,13905.0 -2013-12-13 19:00:00,14309.0 -2013-12-13 20:00:00,14112.0 -2013-12-13 21:00:00,13897.0 -2013-12-13 22:00:00,13577.0 -2013-12-13 23:00:00,13203.0 -2013-12-14 00:00:00,12436.0 -2013-12-12 01:00:00,12870.0 -2013-12-12 02:00:00,12306.0 -2013-12-12 03:00:00,12005.0 -2013-12-12 04:00:00,11870.0 -2013-12-12 05:00:00,11901.0 -2013-12-12 06:00:00,12154.0 -2013-12-12 07:00:00,12827.0 -2013-12-12 08:00:00,13972.0 -2013-12-12 09:00:00,14507.0 -2013-12-12 10:00:00,14574.0 -2013-12-12 11:00:00,14634.0 -2013-12-12 12:00:00,14507.0 -2013-12-12 13:00:00,14257.0 -2013-12-12 14:00:00,14049.0 -2013-12-12 15:00:00,13882.0 -2013-12-12 16:00:00,13709.0 -2013-12-12 17:00:00,13782.0 -2013-12-12 18:00:00,14668.0 -2013-12-12 19:00:00,15346.0 -2013-12-12 20:00:00,15165.0 -2013-12-12 21:00:00,14976.0 -2013-12-12 22:00:00,14721.0 -2013-12-12 23:00:00,14108.0 -2013-12-13 00:00:00,13170.0 -2013-12-11 01:00:00,12299.0 -2013-12-11 02:00:00,11680.0 -2013-12-11 03:00:00,11391.0 -2013-12-11 04:00:00,11215.0 -2013-12-11 05:00:00,11111.0 -2013-12-11 06:00:00,11339.0 -2013-12-11 07:00:00,12027.0 -2013-12-11 08:00:00,13152.0 -2013-12-11 09:00:00,13612.0 -2013-12-11 10:00:00,13692.0 -2013-12-11 11:00:00,13410.0 -2013-12-11 12:00:00,13641.0 -2013-12-11 13:00:00,13611.0 -2013-12-11 14:00:00,13488.0 -2013-12-11 15:00:00,13488.0 -2013-12-11 16:00:00,13408.0 -2013-12-11 17:00:00,13549.0 -2013-12-11 18:00:00,14552.0 -2013-12-11 19:00:00,15443.0 -2013-12-11 20:00:00,15428.0 -2013-12-11 21:00:00,15310.0 -2013-12-11 22:00:00,15096.0 -2013-12-11 23:00:00,14608.0 -2013-12-12 00:00:00,13749.0 -2013-12-10 01:00:00,12568.0 -2013-12-10 02:00:00,12013.0 -2013-12-10 03:00:00,11736.0 -2013-12-10 04:00:00,11588.0 -2013-12-10 05:00:00,11608.0 -2013-12-10 06:00:00,11834.0 -2013-12-10 07:00:00,12521.0 -2013-12-10 08:00:00,13720.0 -2013-12-10 09:00:00,14239.0 -2013-12-10 10:00:00,14343.0 -2013-12-10 11:00:00,14299.0 -2013-12-10 12:00:00,14105.0 -2013-12-10 13:00:00,13928.0 -2013-12-10 14:00:00,13811.0 -2013-12-10 15:00:00,13736.0 -2013-12-10 16:00:00,13557.0 -2013-12-10 17:00:00,13627.0 -2013-12-10 18:00:00,14619.0 -2013-12-10 19:00:00,15372.0 -2013-12-10 20:00:00,15304.0 -2013-12-10 21:00:00,15126.0 -2013-12-10 22:00:00,14818.0 -2013-12-10 23:00:00,14180.0 -2013-12-11 00:00:00,13258.0 -2013-12-09 01:00:00,10946.0 -2013-12-09 02:00:00,10487.0 -2013-12-09 03:00:00,10323.0 -2013-12-09 04:00:00,10205.0 -2013-12-09 05:00:00,10333.0 -2013-12-09 06:00:00,10653.0 -2013-12-09 07:00:00,11396.0 -2013-12-09 08:00:00,12662.0 -2013-12-09 09:00:00,13265.0 -2013-12-09 10:00:00,13356.0 -2013-12-09 11:00:00,13440.0 -2013-12-09 12:00:00,13376.0 -2013-12-09 13:00:00,13272.0 -2013-12-09 14:00:00,13224.0 -2013-12-09 15:00:00,13277.0 -2013-12-09 16:00:00,13385.0 -2013-12-09 17:00:00,13608.0 -2013-12-09 18:00:00,14595.0 -2013-12-09 19:00:00,15338.0 -2013-12-09 20:00:00,15288.0 -2013-12-09 21:00:00,15147.0 -2013-12-09 22:00:00,14916.0 -2013-12-09 23:00:00,14399.0 -2013-12-10 00:00:00,13530.0 -2013-12-08 01:00:00,11575.0 -2013-12-08 02:00:00,11001.0 -2013-12-08 03:00:00,10691.0 -2013-12-08 04:00:00,10421.0 -2013-12-08 05:00:00,10366.0 -2013-12-08 06:00:00,10343.0 -2013-12-08 07:00:00,10483.0 -2013-12-08 08:00:00,10721.0 -2013-12-08 09:00:00,10836.0 -2013-12-08 10:00:00,11098.0 -2013-12-08 11:00:00,11362.0 -2013-12-08 12:00:00,11593.0 -2013-12-08 13:00:00,11718.0 -2013-12-08 14:00:00,11753.0 -2013-12-08 15:00:00,11739.0 -2013-12-08 16:00:00,11784.0 -2013-12-08 17:00:00,11960.0 -2013-12-08 18:00:00,12828.0 -2013-12-08 19:00:00,13421.0 -2013-12-08 20:00:00,13437.0 -2013-12-08 21:00:00,13332.0 -2013-12-08 22:00:00,13084.0 -2013-12-08 23:00:00,12550.0 -2013-12-09 00:00:00,11699.0 -2013-12-07 01:00:00,12003.0 -2013-12-07 02:00:00,11365.0 -2013-12-07 03:00:00,11057.0 -2013-12-07 04:00:00,10855.0 -2013-12-07 05:00:00,10798.0 -2013-12-07 06:00:00,10859.0 -2013-12-07 07:00:00,11212.0 -2013-12-07 08:00:00,11770.0 -2013-12-07 09:00:00,11987.0 -2013-12-07 10:00:00,12205.0 -2013-12-07 11:00:00,12345.0 -2013-12-07 12:00:00,12344.0 -2013-12-07 13:00:00,12209.0 -2013-12-07 14:00:00,12013.0 -2013-12-07 15:00:00,11757.0 -2013-12-07 16:00:00,11663.0 -2013-12-07 17:00:00,11763.0 -2013-12-07 18:00:00,12726.0 -2013-12-07 19:00:00,13504.0 -2013-12-07 20:00:00,13456.0 -2013-12-07 21:00:00,13366.0 -2013-12-07 22:00:00,13144.0 -2013-12-07 23:00:00,12923.0 -2013-12-08 00:00:00,12268.0 -2013-12-06 01:00:00,11360.0 -2013-12-06 02:00:00,10732.0 -2013-12-06 03:00:00,10413.0 -2013-12-06 04:00:00,10240.0 -2013-12-06 05:00:00,10257.0 -2013-12-06 06:00:00,10539.0 -2013-12-06 07:00:00,11254.0 -2013-12-06 08:00:00,12427.0 -2013-12-06 09:00:00,13115.0 -2013-12-06 10:00:00,13215.0 -2013-12-06 11:00:00,13358.0 -2013-12-06 12:00:00,13436.0 -2013-12-06 13:00:00,13365.0 -2013-12-06 14:00:00,13277.0 -2013-12-06 15:00:00,13234.0 -2013-12-06 16:00:00,13160.0 -2013-12-06 17:00:00,13078.0 -2013-12-06 18:00:00,13812.0 -2013-12-06 19:00:00,14435.0 -2013-12-06 20:00:00,14287.0 -2013-12-06 21:00:00,14088.0 -2013-12-06 22:00:00,13822.0 -2013-12-06 23:00:00,13517.0 -2013-12-07 00:00:00,12761.0 -2013-12-05 01:00:00,10095.0 -2013-12-05 02:00:00,9549.0 -2013-12-05 03:00:00,9312.0 -2013-12-05 04:00:00,9190.0 -2013-12-05 05:00:00,9256.0 -2013-12-05 06:00:00,9563.0 -2013-12-05 07:00:00,10413.0 -2013-12-05 08:00:00,11699.0 -2013-12-05 09:00:00,12413.0 -2013-12-05 10:00:00,12535.0 -2013-12-05 11:00:00,12558.0 -2013-12-05 12:00:00,12540.0 -2013-12-05 13:00:00,12518.0 -2013-12-05 14:00:00,12537.0 -2013-12-05 15:00:00,12651.0 -2013-12-05 16:00:00,12708.0 -2013-12-05 17:00:00,12854.0 -2013-12-05 18:00:00,13716.0 -2013-12-05 19:00:00,14206.0 -2013-12-05 20:00:00,14067.0 -2013-12-05 21:00:00,13867.0 -2013-12-05 22:00:00,13587.0 -2013-12-05 23:00:00,13064.0 -2013-12-06 00:00:00,12225.0 -2013-12-04 01:00:00,9944.0 -2013-12-04 02:00:00,9365.0 -2013-12-04 03:00:00,9020.0 -2013-12-04 04:00:00,8816.0 -2013-12-04 05:00:00,8779.0 -2013-12-04 06:00:00,8998.0 -2013-12-04 07:00:00,9730.0 -2013-12-04 08:00:00,10944.0 -2013-12-04 09:00:00,11581.0 -2013-12-04 10:00:00,11753.0 -2013-12-04 11:00:00,11894.0 -2013-12-04 12:00:00,11970.0 -2013-12-04 13:00:00,11961.0 -2013-12-04 14:00:00,11927.0 -2013-12-04 15:00:00,11959.0 -2013-12-04 16:00:00,11975.0 -2013-12-04 17:00:00,12122.0 -2013-12-04 18:00:00,12734.0 -2013-12-04 19:00:00,13002.0 -2013-12-04 20:00:00,12782.0 -2013-12-04 21:00:00,12573.0 -2013-12-04 22:00:00,12230.0 -2013-12-04 23:00:00,11659.0 -2013-12-05 00:00:00,10847.0 -2013-12-03 01:00:00,10426.0 -2013-12-03 02:00:00,9856.0 -2013-12-03 03:00:00,9505.0 -2013-12-03 04:00:00,9317.0 -2013-12-03 05:00:00,9312.0 -2013-12-03 06:00:00,9554.0 -2013-12-03 07:00:00,10241.0 -2013-12-03 08:00:00,11425.0 -2013-12-03 09:00:00,12088.0 -2013-12-03 10:00:00,12153.0 -2013-12-03 11:00:00,12221.0 -2013-12-03 12:00:00,12235.0 -2013-12-03 13:00:00,12135.0 -2013-12-03 14:00:00,12109.0 -2013-12-03 15:00:00,12132.0 -2013-12-03 16:00:00,12100.0 -2013-12-03 17:00:00,12188.0 -2013-12-03 18:00:00,12847.0 -2013-12-03 19:00:00,13131.0 -2013-12-03 20:00:00,12945.0 -2013-12-03 21:00:00,12661.0 -2013-12-03 22:00:00,12345.0 -2013-12-03 23:00:00,11738.0 -2013-12-04 00:00:00,10861.0 -2013-12-02 01:00:00,9790.0 -2013-12-02 02:00:00,9414.0 -2013-12-02 03:00:00,9163.0 -2013-12-02 04:00:00,9114.0 -2013-12-02 05:00:00,9130.0 -2013-12-02 06:00:00,9510.0 -2013-12-02 07:00:00,10313.0 -2013-12-02 08:00:00,11479.0 -2013-12-02 09:00:00,12066.0 -2013-12-02 10:00:00,12181.0 -2013-12-02 11:00:00,12145.0 -2013-12-02 12:00:00,12152.0 -2013-12-02 13:00:00,12081.0 -2013-12-02 14:00:00,12021.0 -2013-12-02 15:00:00,12056.0 -2013-12-02 16:00:00,12068.0 -2013-12-02 17:00:00,12142.0 -2013-12-02 18:00:00,12949.0 -2013-12-02 19:00:00,13467.0 -2013-12-02 20:00:00,13307.0 -2013-12-02 21:00:00,13108.0 -2013-12-02 22:00:00,12806.0 -2013-12-02 23:00:00,12243.0 -2013-12-03 00:00:00,11332.0 -2013-12-01 01:00:00,9777.0 -2013-12-01 02:00:00,9258.0 -2013-12-01 03:00:00,8953.0 -2013-12-01 04:00:00,8776.0 -2013-12-01 05:00:00,8788.0 -2013-12-01 06:00:00,8762.0 -2013-12-01 07:00:00,8978.0 -2013-12-01 08:00:00,9207.0 -2013-12-01 09:00:00,9312.0 -2013-12-01 10:00:00,9512.0 -2013-12-01 11:00:00,9634.0 -2013-12-01 12:00:00,9699.0 -2013-12-01 13:00:00,9721.0 -2013-12-01 14:00:00,9680.0 -2013-12-01 15:00:00,9676.0 -2013-12-01 16:00:00,9699.0 -2013-12-01 17:00:00,9868.0 -2013-12-01 18:00:00,10884.0 -2013-12-01 19:00:00,11655.0 -2013-12-01 20:00:00,11735.0 -2013-12-01 21:00:00,11650.0 -2013-12-01 22:00:00,11449.0 -2013-12-01 23:00:00,11035.0 -2013-12-02 00:00:00,10435.0 -2013-11-30 01:00:00,10135.0 -2013-11-30 02:00:00,9652.0 -2013-11-30 03:00:00,9366.0 -2013-11-30 04:00:00,9159.0 -2013-11-30 05:00:00,9105.0 -2013-11-30 06:00:00,9198.0 -2013-11-30 07:00:00,9492.0 -2013-11-30 08:00:00,9894.0 -2013-11-30 09:00:00,10134.0 -2013-11-30 10:00:00,10309.0 -2013-11-30 11:00:00,10405.0 -2013-11-30 12:00:00,10378.0 -2013-11-30 13:00:00,10300.0 -2013-11-30 14:00:00,10135.0 -2013-11-30 15:00:00,9884.0 -2013-11-30 16:00:00,9757.0 -2013-11-30 17:00:00,9835.0 -2013-11-30 18:00:00,10628.0 -2013-11-30 19:00:00,11272.0 -2013-11-30 20:00:00,11277.0 -2013-11-30 21:00:00,11162.0 -2013-11-30 22:00:00,11081.0 -2013-11-30 23:00:00,10774.0 -2013-12-01 00:00:00,10290.0 -2013-11-29 01:00:00,9938.0 -2013-11-29 02:00:00,9643.0 -2013-11-29 03:00:00,9476.0 -2013-11-29 04:00:00,9382.0 -2013-11-29 05:00:00,9429.0 -2013-11-29 06:00:00,9637.0 -2013-11-29 07:00:00,10076.0 -2013-11-29 08:00:00,10602.0 -2013-11-29 09:00:00,10730.0 -2013-11-29 10:00:00,10786.0 -2013-11-29 11:00:00,10847.0 -2013-11-29 12:00:00,10823.0 -2013-11-29 13:00:00,10761.0 -2013-11-29 14:00:00,10658.0 -2013-11-29 15:00:00,10563.0 -2013-11-29 16:00:00,10536.0 -2013-11-29 17:00:00,10691.0 -2013-11-29 18:00:00,11547.0 -2013-11-29 19:00:00,12098.0 -2013-11-29 20:00:00,11950.0 -2013-11-29 21:00:00,11816.0 -2013-11-29 22:00:00,11584.0 -2013-11-29 23:00:00,11271.0 -2013-11-30 00:00:00,10705.0 -2013-11-28 01:00:00,10886.0 -2013-11-28 02:00:00,10326.0 -2013-11-28 03:00:00,9965.0 -2013-11-28 04:00:00,9756.0 -2013-11-28 05:00:00,9649.0 -2013-11-28 06:00:00,9688.0 -2013-11-28 07:00:00,9858.0 -2013-11-28 08:00:00,10142.0 -2013-11-28 09:00:00,10171.0 -2013-11-28 10:00:00,10315.0 -2013-11-28 11:00:00,10490.0 -2013-11-28 12:00:00,10539.0 -2013-11-28 13:00:00,10458.0 -2013-11-28 14:00:00,10268.0 -2013-11-28 15:00:00,10049.0 -2013-11-28 16:00:00,9906.0 -2013-11-28 17:00:00,9911.0 -2013-11-28 18:00:00,10365.0 -2013-11-28 19:00:00,10763.0 -2013-11-28 20:00:00,10717.0 -2013-11-28 21:00:00,10702.0 -2013-11-28 22:00:00,10681.0 -2013-11-28 23:00:00,10572.0 -2013-11-29 00:00:00,10283.0 -2013-11-27 01:00:00,11324.0 -2013-11-27 02:00:00,10837.0 -2013-11-27 03:00:00,10588.0 -2013-11-27 04:00:00,10451.0 -2013-11-27 05:00:00,10454.0 -2013-11-27 06:00:00,10734.0 -2013-11-27 07:00:00,11456.0 -2013-11-27 08:00:00,12408.0 -2013-11-27 09:00:00,12816.0 -2013-11-27 10:00:00,12993.0 -2013-11-27 11:00:00,13071.0 -2013-11-27 12:00:00,13059.0 -2013-11-27 13:00:00,12946.0 -2013-11-27 14:00:00,12789.0 -2013-11-27 15:00:00,12636.0 -2013-11-27 16:00:00,12467.0 -2013-11-27 17:00:00,12433.0 -2013-11-27 18:00:00,13132.0 -2013-11-27 19:00:00,13696.0 -2013-11-27 20:00:00,13435.0 -2013-11-27 21:00:00,13193.0 -2013-11-27 22:00:00,12882.0 -2013-11-27 23:00:00,12410.0 -2013-11-28 00:00:00,11659.0 -2013-11-26 01:00:00,10842.0 -2013-11-26 02:00:00,10344.0 -2013-11-26 03:00:00,10022.0 -2013-11-26 04:00:00,9852.0 -2013-11-26 05:00:00,9846.0 -2013-11-26 06:00:00,10089.0 -2013-11-26 07:00:00,10830.0 -2013-11-26 08:00:00,11940.0 -2013-11-26 09:00:00,12515.0 -2013-11-26 10:00:00,12714.0 -2013-11-26 11:00:00,12833.0 -2013-11-26 12:00:00,12872.0 -2013-11-26 13:00:00,12825.0 -2013-11-26 14:00:00,12785.0 -2013-11-26 15:00:00,12899.0 -2013-11-26 16:00:00,12916.0 -2013-11-26 17:00:00,12963.0 -2013-11-26 18:00:00,13441.0 -2013-11-26 19:00:00,13791.0 -2013-11-26 20:00:00,13575.0 -2013-11-26 21:00:00,13452.0 -2013-11-26 22:00:00,13217.0 -2013-11-26 23:00:00,12780.0 -2013-11-27 00:00:00,12034.0 -2013-11-25 01:00:00,10800.0 -2013-11-25 02:00:00,10420.0 -2013-11-25 03:00:00,10207.0 -2013-11-25 04:00:00,10094.0 -2013-11-25 05:00:00,10087.0 -2013-11-25 06:00:00,10386.0 -2013-11-25 07:00:00,11167.0 -2013-11-25 08:00:00,12323.0 -2013-11-25 09:00:00,12899.0 -2013-11-25 10:00:00,13085.0 -2013-11-25 11:00:00,13193.0 -2013-11-25 12:00:00,13275.0 -2013-11-25 13:00:00,13254.0 -2013-11-25 14:00:00,13196.0 -2013-11-25 15:00:00,13221.0 -2013-11-25 16:00:00,13144.0 -2013-11-25 17:00:00,13116.0 -2013-11-25 18:00:00,13660.0 -2013-11-25 19:00:00,13928.0 -2013-11-25 20:00:00,13710.0 -2013-11-25 21:00:00,13429.0 -2013-11-25 22:00:00,13067.0 -2013-11-25 23:00:00,12455.0 -2013-11-26 00:00:00,11620.0 -2013-11-24 01:00:00,10971.0 -2013-11-24 02:00:00,10542.0 -2013-11-24 03:00:00,10311.0 -2013-11-24 04:00:00,10140.0 -2013-11-24 05:00:00,10118.0 -2013-11-24 06:00:00,10150.0 -2013-11-24 07:00:00,10304.0 -2013-11-24 08:00:00,10551.0 -2013-11-24 09:00:00,10567.0 -2013-11-24 10:00:00,10767.0 -2013-11-24 11:00:00,10883.0 -2013-11-24 12:00:00,10958.0 -2013-11-24 13:00:00,10973.0 -2013-11-24 14:00:00,10873.0 -2013-11-24 15:00:00,10792.0 -2013-11-24 16:00:00,10730.0 -2013-11-24 17:00:00,10881.0 -2013-11-24 18:00:00,11713.0 -2013-11-24 19:00:00,12513.0 -2013-11-24 20:00:00,12531.0 -2013-11-24 21:00:00,12489.0 -2013-11-24 22:00:00,12253.0 -2013-11-24 23:00:00,11939.0 -2013-11-25 00:00:00,11364.0 -2013-11-23 01:00:00,10590.0 -2013-11-23 02:00:00,10120.0 -2013-11-23 03:00:00,9881.0 -2013-11-23 04:00:00,9626.0 -2013-11-23 05:00:00,9603.0 -2013-11-23 06:00:00,9723.0 -2013-11-23 07:00:00,10072.0 -2013-11-23 08:00:00,10576.0 -2013-11-23 09:00:00,10801.0 -2013-11-23 10:00:00,11209.0 -2013-11-23 11:00:00,11453.0 -2013-11-23 12:00:00,11549.0 -2013-11-23 13:00:00,11516.0 -2013-11-23 14:00:00,11379.0 -2013-11-23 15:00:00,11158.0 -2013-11-23 16:00:00,11126.0 -2013-11-23 17:00:00,11215.0 -2013-11-23 18:00:00,11967.0 -2013-11-23 19:00:00,12658.0 -2013-11-23 20:00:00,12636.0 -2013-11-23 21:00:00,12514.0 -2013-11-23 22:00:00,12346.0 -2013-11-23 23:00:00,12067.0 -2013-11-24 00:00:00,11512.0 -2013-11-22 01:00:00,9797.0 -2013-11-22 02:00:00,9292.0 -2013-11-22 03:00:00,9011.0 -2013-11-22 04:00:00,8817.0 -2013-11-22 05:00:00,8842.0 -2013-11-22 06:00:00,9124.0 -2013-11-22 07:00:00,9875.0 -2013-11-22 08:00:00,11050.0 -2013-11-22 09:00:00,11854.0 -2013-11-22 10:00:00,12047.0 -2013-11-22 11:00:00,12195.0 -2013-11-22 12:00:00,12290.0 -2013-11-22 13:00:00,12134.0 -2013-11-22 14:00:00,11937.0 -2013-11-22 15:00:00,11963.0 -2013-11-22 16:00:00,11938.0 -2013-11-22 17:00:00,11932.0 -2013-11-22 18:00:00,12520.0 -2013-11-22 19:00:00,12961.0 -2013-11-22 20:00:00,12776.0 -2013-11-22 21:00:00,12548.0 -2013-11-22 22:00:00,12265.0 -2013-11-22 23:00:00,11903.0 -2013-11-23 00:00:00,11317.0 -2013-11-21 01:00:00,10007.0 -2013-11-21 02:00:00,9462.0 -2013-11-21 03:00:00,9161.0 -2013-11-21 04:00:00,8964.0 -2013-11-21 05:00:00,8919.0 -2013-11-21 06:00:00,9178.0 -2013-11-21 07:00:00,9913.0 -2013-11-21 08:00:00,11069.0 -2013-11-21 09:00:00,11545.0 -2013-11-21 10:00:00,11671.0 -2013-11-21 11:00:00,11710.0 -2013-11-21 12:00:00,11714.0 -2013-11-21 13:00:00,11793.0 -2013-11-21 14:00:00,11772.0 -2013-11-21 15:00:00,11838.0 -2013-11-21 16:00:00,11745.0 -2013-11-21 17:00:00,11857.0 -2013-11-21 18:00:00,12427.0 -2013-11-21 19:00:00,12634.0 -2013-11-21 20:00:00,12384.0 -2013-11-21 21:00:00,12134.0 -2013-11-21 22:00:00,11843.0 -2013-11-21 23:00:00,11283.0 -2013-11-22 00:00:00,10530.0 -2013-11-20 01:00:00,10235.0 -2013-11-20 02:00:00,9728.0 -2013-11-20 03:00:00,9447.0 -2013-11-20 04:00:00,9295.0 -2013-11-20 05:00:00,9329.0 -2013-11-20 06:00:00,9587.0 -2013-11-20 07:00:00,10388.0 -2013-11-20 08:00:00,11547.0 -2013-11-20 09:00:00,12017.0 -2013-11-20 10:00:00,12123.0 -2013-11-20 11:00:00,12103.0 -2013-11-20 12:00:00,12061.0 -2013-11-20 13:00:00,11968.0 -2013-11-20 14:00:00,11822.0 -2013-11-20 15:00:00,11843.0 -2013-11-20 16:00:00,11886.0 -2013-11-20 17:00:00,11983.0 -2013-11-20 18:00:00,12629.0 -2013-11-20 19:00:00,12990.0 -2013-11-20 20:00:00,12760.0 -2013-11-20 21:00:00,12502.0 -2013-11-20 22:00:00,12189.0 -2013-11-20 23:00:00,11615.0 -2013-11-21 00:00:00,10791.0 -2013-11-19 01:00:00,10190.0 -2013-11-19 02:00:00,9740.0 -2013-11-19 03:00:00,9497.0 -2013-11-19 04:00:00,9365.0 -2013-11-19 05:00:00,9385.0 -2013-11-19 06:00:00,9668.0 -2013-11-19 07:00:00,10489.0 -2013-11-19 08:00:00,11625.0 -2013-11-19 09:00:00,12004.0 -2013-11-19 10:00:00,12013.0 -2013-11-19 11:00:00,11987.0 -2013-11-19 12:00:00,11980.0 -2013-11-19 13:00:00,11891.0 -2013-11-19 14:00:00,11767.0 -2013-11-19 15:00:00,11763.0 -2013-11-19 16:00:00,11664.0 -2013-11-19 17:00:00,11696.0 -2013-11-19 18:00:00,12366.0 -2013-11-19 19:00:00,12964.0 -2013-11-19 20:00:00,12827.0 -2013-11-19 21:00:00,12630.0 -2013-11-19 22:00:00,12326.0 -2013-11-19 23:00:00,11795.0 -2013-11-20 00:00:00,11004.0 -2013-11-18 01:00:00,9016.0 -2013-11-18 02:00:00,8731.0 -2013-11-18 03:00:00,8564.0 -2013-11-18 04:00:00,8510.0 -2013-11-18 05:00:00,8587.0 -2013-11-18 06:00:00,8956.0 -2013-11-18 07:00:00,9790.0 -2013-11-18 08:00:00,11064.0 -2013-11-18 09:00:00,11554.0 -2013-11-18 10:00:00,11814.0 -2013-11-18 11:00:00,11890.0 -2013-11-18 12:00:00,11880.0 -2013-11-18 13:00:00,11818.0 -2013-11-18 14:00:00,11726.0 -2013-11-18 15:00:00,11693.0 -2013-11-18 16:00:00,11617.0 -2013-11-18 17:00:00,11599.0 -2013-11-18 18:00:00,12184.0 -2013-11-18 19:00:00,12887.0 -2013-11-18 20:00:00,12719.0 -2013-11-18 21:00:00,12520.0 -2013-11-18 22:00:00,12233.0 -2013-11-18 23:00:00,11716.0 -2013-11-19 00:00:00,10935.0 -2013-11-17 01:00:00,9072.0 -2013-11-17 02:00:00,8622.0 -2013-11-17 03:00:00,8293.0 -2013-11-17 04:00:00,8021.0 -2013-11-17 05:00:00,7942.0 -2013-11-17 06:00:00,7917.0 -2013-11-17 07:00:00,8040.0 -2013-11-17 08:00:00,8338.0 -2013-11-17 09:00:00,8439.0 -2013-11-17 10:00:00,8773.0 -2013-11-17 11:00:00,9122.0 -2013-11-17 12:00:00,9305.0 -2013-11-17 13:00:00,9726.0 -2013-11-17 14:00:00,9935.0 -2013-11-17 15:00:00,9693.0 -2013-11-17 16:00:00,9369.0 -2013-11-17 17:00:00,9622.0 -2013-11-17 18:00:00,10164.0 -2013-11-17 19:00:00,10692.0 -2013-11-17 20:00:00,10707.0 -2013-11-17 21:00:00,10631.0 -2013-11-17 22:00:00,10399.0 -2013-11-17 23:00:00,10034.0 -2013-11-18 00:00:00,9538.0 -2013-11-16 01:00:00,9975.0 -2013-11-16 02:00:00,9479.0 -2013-11-16 03:00:00,9097.0 -2013-11-16 04:00:00,8904.0 -2013-11-16 05:00:00,8768.0 -2013-11-16 06:00:00,8844.0 -2013-11-16 07:00:00,9131.0 -2013-11-16 08:00:00,9592.0 -2013-11-16 09:00:00,9939.0 -2013-11-16 10:00:00,10311.0 -2013-11-16 11:00:00,10612.0 -2013-11-16 12:00:00,10800.0 -2013-11-16 13:00:00,10780.0 -2013-11-16 14:00:00,10640.0 -2013-11-16 15:00:00,10499.0 -2013-11-16 16:00:00,10360.0 -2013-11-16 17:00:00,10398.0 -2013-11-16 18:00:00,10907.0 -2013-11-16 19:00:00,11181.0 -2013-11-16 20:00:00,11046.0 -2013-11-16 21:00:00,10876.0 -2013-11-16 22:00:00,10610.0 -2013-11-16 23:00:00,10204.0 -2013-11-17 00:00:00,9701.0 -2013-11-15 01:00:00,10100.0 -2013-11-15 02:00:00,9639.0 -2013-11-15 03:00:00,9361.0 -2013-11-15 04:00:00,9176.0 -2013-11-15 05:00:00,9184.0 -2013-11-15 06:00:00,9470.0 -2013-11-15 07:00:00,10198.0 -2013-11-15 08:00:00,11252.0 -2013-11-15 09:00:00,11724.0 -2013-11-15 10:00:00,11873.0 -2013-11-15 11:00:00,11873.0 -2013-11-15 12:00:00,11845.0 -2013-11-15 13:00:00,11770.0 -2013-11-15 14:00:00,11633.0 -2013-11-15 15:00:00,11648.0 -2013-11-15 16:00:00,11451.0 -2013-11-15 17:00:00,11404.0 -2013-11-15 18:00:00,11943.0 -2013-11-15 19:00:00,12488.0 -2013-11-15 20:00:00,12315.0 -2013-11-15 21:00:00,12056.0 -2013-11-15 22:00:00,11741.0 -2013-11-15 23:00:00,11344.0 -2013-11-16 00:00:00,10670.0 -2013-11-14 01:00:00,10659.0 -2013-11-14 02:00:00,10196.0 -2013-11-14 03:00:00,9889.0 -2013-11-14 04:00:00,9734.0 -2013-11-14 05:00:00,9744.0 -2013-11-14 06:00:00,10003.0 -2013-11-14 07:00:00,10725.0 -2013-11-14 08:00:00,11768.0 -2013-11-14 09:00:00,12138.0 -2013-11-14 10:00:00,12189.0 -2013-11-14 11:00:00,12170.0 -2013-11-14 12:00:00,12144.0 -2013-11-14 13:00:00,12007.0 -2013-11-14 14:00:00,11882.0 -2013-11-14 15:00:00,11850.0 -2013-11-14 16:00:00,11739.0 -2013-11-14 17:00:00,11774.0 -2013-11-14 18:00:00,12329.0 -2013-11-14 19:00:00,12909.0 -2013-11-14 20:00:00,12724.0 -2013-11-14 21:00:00,12506.0 -2013-11-14 22:00:00,12210.0 -2013-11-14 23:00:00,11643.0 -2013-11-15 00:00:00,10848.0 -2013-11-13 01:00:00,10786.0 -2013-11-13 02:00:00,10365.0 -2013-11-13 03:00:00,10132.0 -2013-11-13 04:00:00,10010.0 -2013-11-13 05:00:00,10020.0 -2013-11-13 06:00:00,10316.0 -2013-11-13 07:00:00,11085.0 -2013-11-13 08:00:00,12128.0 -2013-11-13 09:00:00,12495.0 -2013-11-13 10:00:00,12562.0 -2013-11-13 11:00:00,12564.0 -2013-11-13 12:00:00,12551.0 -2013-11-13 13:00:00,12473.0 -2013-11-13 14:00:00,12364.0 -2013-11-13 15:00:00,12292.0 -2013-11-13 16:00:00,12145.0 -2013-11-13 17:00:00,12174.0 -2013-11-13 18:00:00,12777.0 -2013-11-13 19:00:00,13513.0 -2013-11-13 20:00:00,13343.0 -2013-11-13 21:00:00,13107.0 -2013-11-13 22:00:00,12824.0 -2013-11-13 23:00:00,12281.0 -2013-11-14 00:00:00,11474.0 -2013-11-12 01:00:00,10494.0 -2013-11-12 02:00:00,10099.0 -2013-11-12 03:00:00,9840.0 -2013-11-12 04:00:00,9671.0 -2013-11-12 05:00:00,9731.0 -2013-11-12 06:00:00,10037.0 -2013-11-12 07:00:00,10797.0 -2013-11-12 08:00:00,11913.0 -2013-11-12 09:00:00,12294.0 -2013-11-12 10:00:00,12368.0 -2013-11-12 11:00:00,12381.0 -2013-11-12 12:00:00,12387.0 -2013-11-12 13:00:00,12324.0 -2013-11-12 14:00:00,12238.0 -2013-11-12 15:00:00,12189.0 -2013-11-12 16:00:00,12076.0 -2013-11-12 17:00:00,12088.0 -2013-11-12 18:00:00,12663.0 -2013-11-12 19:00:00,13432.0 -2013-11-12 20:00:00,13319.0 -2013-11-12 21:00:00,13111.0 -2013-11-12 22:00:00,12805.0 -2013-11-12 23:00:00,12275.0 -2013-11-13 00:00:00,11526.0 -2013-11-11 01:00:00,9042.0 -2013-11-11 02:00:00,8706.0 -2013-11-11 03:00:00,8521.0 -2013-11-11 04:00:00,8413.0 -2013-11-11 05:00:00,8432.0 -2013-11-11 06:00:00,8735.0 -2013-11-11 07:00:00,9487.0 -2013-11-11 08:00:00,10523.0 -2013-11-11 09:00:00,11068.0 -2013-11-11 10:00:00,11462.0 -2013-11-11 11:00:00,11682.0 -2013-11-11 12:00:00,11875.0 -2013-11-11 13:00:00,11993.0 -2013-11-11 14:00:00,12129.0 -2013-11-11 15:00:00,12243.0 -2013-11-11 16:00:00,12262.0 -2013-11-11 17:00:00,12441.0 -2013-11-11 18:00:00,12918.0 -2013-11-11 19:00:00,13326.0 -2013-11-11 20:00:00,13094.0 -2013-11-11 21:00:00,12877.0 -2013-11-11 22:00:00,12568.0 -2013-11-11 23:00:00,12027.0 -2013-11-12 00:00:00,11213.0 -2013-11-10 01:00:00,9226.0 -2013-11-10 02:00:00,8833.0 -2013-11-10 03:00:00,8590.0 -2013-11-10 04:00:00,8424.0 -2013-11-10 05:00:00,8376.0 -2013-11-10 06:00:00,8434.0 -2013-11-10 07:00:00,8618.0 -2013-11-10 08:00:00,8778.0 -2013-11-10 09:00:00,8792.0 -2013-11-10 10:00:00,9006.0 -2013-11-10 11:00:00,9173.0 -2013-11-10 12:00:00,9198.0 -2013-11-10 13:00:00,9246.0 -2013-11-10 14:00:00,9205.0 -2013-11-10 15:00:00,9144.0 -2013-11-10 16:00:00,9076.0 -2013-11-10 17:00:00,9172.0 -2013-11-10 18:00:00,9745.0 -2013-11-10 19:00:00,10618.0 -2013-11-10 20:00:00,10669.0 -2013-11-10 21:00:00,10582.0 -2013-11-10 22:00:00,10362.0 -2013-11-10 23:00:00,10029.0 -2013-11-11 00:00:00,9543.0 -2013-11-09 01:00:00,9836.0 -2013-11-09 02:00:00,9286.0 -2013-11-09 03:00:00,8954.0 -2013-11-09 04:00:00,8732.0 -2013-11-09 05:00:00,8644.0 -2013-11-09 06:00:00,8711.0 -2013-11-09 07:00:00,9033.0 -2013-11-09 08:00:00,9389.0 -2013-11-09 09:00:00,9561.0 -2013-11-09 10:00:00,9867.0 -2013-11-09 11:00:00,10023.0 -2013-11-09 12:00:00,10063.0 -2013-11-09 13:00:00,10024.0 -2013-11-09 14:00:00,9888.0 -2013-11-09 15:00:00,9741.0 -2013-11-09 16:00:00,9612.0 -2013-11-09 17:00:00,9635.0 -2013-11-09 18:00:00,10064.0 -2013-11-09 19:00:00,10796.0 -2013-11-09 20:00:00,10775.0 -2013-11-09 21:00:00,10622.0 -2013-11-09 22:00:00,10439.0 -2013-11-09 23:00:00,10158.0 -2013-11-10 00:00:00,9726.0 -2013-11-08 01:00:00,10170.0 -2013-11-08 02:00:00,9687.0 -2013-11-08 03:00:00,9361.0 -2013-11-08 04:00:00,9266.0 -2013-11-08 05:00:00,9252.0 -2013-11-08 06:00:00,9574.0 -2013-11-08 07:00:00,10312.0 -2013-11-08 08:00:00,11285.0 -2013-11-08 09:00:00,11724.0 -2013-11-08 10:00:00,11848.0 -2013-11-08 11:00:00,11835.0 -2013-11-08 12:00:00,11840.0 -2013-11-08 13:00:00,11733.0 -2013-11-08 14:00:00,11584.0 -2013-11-08 15:00:00,11549.0 -2013-11-08 16:00:00,11470.0 -2013-11-08 17:00:00,11530.0 -2013-11-08 18:00:00,11982.0 -2013-11-08 19:00:00,12534.0 -2013-11-08 20:00:00,12288.0 -2013-11-08 21:00:00,12041.0 -2013-11-08 22:00:00,11698.0 -2013-11-08 23:00:00,11238.0 -2013-11-09 00:00:00,10538.0 -2013-11-07 01:00:00,9889.0 -2013-11-07 02:00:00,9456.0 -2013-11-07 03:00:00,9211.0 -2013-11-07 04:00:00,9085.0 -2013-11-07 05:00:00,9095.0 -2013-11-07 06:00:00,9366.0 -2013-11-07 07:00:00,10141.0 -2013-11-07 08:00:00,11212.0 -2013-11-07 09:00:00,11624.0 -2013-11-07 10:00:00,11781.0 -2013-11-07 11:00:00,11789.0 -2013-11-07 12:00:00,11794.0 -2013-11-07 13:00:00,11781.0 -2013-11-07 14:00:00,11844.0 -2013-11-07 15:00:00,12005.0 -2013-11-07 16:00:00,11961.0 -2013-11-07 17:00:00,11908.0 -2013-11-07 18:00:00,12268.0 -2013-11-07 19:00:00,12917.0 -2013-11-07 20:00:00,12719.0 -2013-11-07 21:00:00,12512.0 -2013-11-07 22:00:00,12156.0 -2013-11-07 23:00:00,11617.0 -2013-11-08 00:00:00,10860.0 -2013-11-06 01:00:00,9526.0 -2013-11-06 02:00:00,9093.0 -2013-11-06 03:00:00,8775.0 -2013-11-06 04:00:00,8616.0 -2013-11-06 05:00:00,8550.0 -2013-11-06 06:00:00,8833.0 -2013-11-06 07:00:00,9533.0 -2013-11-06 08:00:00,10666.0 -2013-11-06 09:00:00,11392.0 -2013-11-06 10:00:00,11617.0 -2013-11-06 11:00:00,11712.0 -2013-11-06 12:00:00,11842.0 -2013-11-06 13:00:00,11871.0 -2013-11-06 14:00:00,11782.0 -2013-11-06 15:00:00,11815.0 -2013-11-06 16:00:00,11776.0 -2013-11-06 17:00:00,11791.0 -2013-11-06 18:00:00,12157.0 -2013-11-06 19:00:00,12607.0 -2013-11-06 20:00:00,12414.0 -2013-11-06 21:00:00,12158.0 -2013-11-06 22:00:00,11849.0 -2013-11-06 23:00:00,11342.0 -2013-11-07 00:00:00,10578.0 -2013-11-05 01:00:00,9494.0 -2013-11-05 02:00:00,8998.0 -2013-11-05 03:00:00,8728.0 -2013-11-05 04:00:00,8520.0 -2013-11-05 05:00:00,8500.0 -2013-11-05 06:00:00,8718.0 -2013-11-05 07:00:00,9435.0 -2013-11-05 08:00:00,10512.0 -2013-11-05 09:00:00,11055.0 -2013-11-05 10:00:00,11229.0 -2013-11-05 11:00:00,11360.0 -2013-11-05 12:00:00,11516.0 -2013-11-05 13:00:00,11585.0 -2013-11-05 14:00:00,11615.0 -2013-11-05 15:00:00,11650.0 -2013-11-05 16:00:00,11606.0 -2013-11-05 17:00:00,11622.0 -2013-11-05 18:00:00,12018.0 -2013-11-05 19:00:00,12444.0 -2013-11-05 20:00:00,12245.0 -2013-11-05 21:00:00,12003.0 -2013-11-05 22:00:00,11665.0 -2013-11-05 23:00:00,11049.0 -2013-11-06 00:00:00,10289.0 -2013-11-04 01:00:00,9005.0 -2013-11-04 02:00:00,8692.0 -2013-11-04 03:00:00,8546.0 -2013-11-04 04:00:00,8492.0 -2013-11-04 05:00:00,8536.0 -2013-11-04 06:00:00,8837.0 -2013-11-04 07:00:00,9681.0 -2013-11-04 08:00:00,10753.0 -2013-11-04 09:00:00,11343.0 -2013-11-04 10:00:00,11596.0 -2013-11-04 11:00:00,11612.0 -2013-11-04 12:00:00,11612.0 -2013-11-04 13:00:00,11581.0 -2013-11-04 14:00:00,11546.0 -2013-11-04 15:00:00,11475.0 -2013-11-04 16:00:00,11368.0 -2013-11-04 17:00:00,11295.0 -2013-11-04 18:00:00,11539.0 -2013-11-04 19:00:00,12224.0 -2013-11-04 20:00:00,12061.0 -2013-11-04 21:00:00,12011.0 -2013-11-04 22:00:00,11607.0 -2013-11-04 23:00:00,11047.0 -2013-11-05 00:00:00,10252.0 -2013-11-03 01:00:00,9230.0 -2013-11-03 03:00:00,8459.0 -2013-11-03 04:00:00,8336.0 -2013-11-03 05:00:00,8372.0 -2013-11-03 06:00:00,8386.0 -2013-11-03 07:00:00,8659.0 -2013-11-03 08:00:00,8813.0 -2013-11-03 09:00:00,8945.0 -2013-11-03 10:00:00,9137.0 -2013-11-03 11:00:00,9247.0 -2013-11-03 12:00:00,9287.0 -2013-11-03 13:00:00,9315.0 -2013-11-03 14:00:00,9300.0 -2013-11-03 15:00:00,9252.0 -2013-11-03 16:00:00,9227.0 -2013-11-03 17:00:00,9332.0 -2013-11-03 18:00:00,9861.0 -2013-11-03 19:00:00,10722.0 -2013-11-03 20:00:00,10819.0 -2013-11-03 21:00:00,10759.0 -2013-11-03 22:00:00,10525.0 -2013-11-03 23:00:00,10110.0 -2013-11-04 00:00:00,9555.0 -2013-11-02 01:00:00,9622.0 -2013-11-02 02:00:00,9090.0 -2013-11-02 03:00:00,8797.0 -2013-11-02 04:00:00,8577.0 -2013-11-02 05:00:00,8526.0 -2013-11-02 06:00:00,8559.0 -2013-11-02 07:00:00,8834.0 -2013-11-02 08:00:00,9358.0 -2013-11-02 09:00:00,9777.0 -2013-11-02 10:00:00,10019.0 -2013-11-02 11:00:00,10242.0 -2013-11-02 12:00:00,10343.0 -2013-11-02 13:00:00,10313.0 -2013-11-02 14:00:00,10153.0 -2013-11-02 15:00:00,10020.0 -2013-11-02 16:00:00,9888.0 -2013-11-02 17:00:00,9829.0 -2013-11-02 18:00:00,9936.0 -2013-11-02 19:00:00,10307.0 -2013-11-02 20:00:00,10768.0 -2013-11-02 21:00:00,10644.0 -2013-11-02 22:00:00,10495.0 -2013-11-02 23:00:00,10174.0 -2013-11-03 00:00:00,9726.0 -2013-11-01 01:00:00,9408.0 -2013-11-01 02:00:00,8925.0 -2013-11-01 03:00:00,8665.0 -2013-11-01 04:00:00,8475.0 -2013-11-01 05:00:00,8458.0 -2013-11-01 06:00:00,8680.0 -2013-11-01 07:00:00,9359.0 -2013-11-01 08:00:00,10526.0 -2013-11-01 09:00:00,11355.0 -2013-11-01 10:00:00,11523.0 -2013-11-01 11:00:00,11601.0 -2013-11-01 12:00:00,11703.0 -2013-11-01 13:00:00,11679.0 -2013-11-01 14:00:00,11590.0 -2013-11-01 15:00:00,11581.0 -2013-11-01 16:00:00,11508.0 -2013-11-01 17:00:00,11433.0 -2013-11-01 18:00:00,11541.0 -2013-11-01 19:00:00,11757.0 -2013-11-01 20:00:00,11920.0 -2013-11-01 21:00:00,11714.0 -2013-11-01 22:00:00,11390.0 -2013-11-01 23:00:00,10995.0 -2013-11-02 00:00:00,10317.0 -2013-10-31 01:00:00,9601.0 -2013-10-31 02:00:00,9058.0 -2013-10-31 03:00:00,8733.0 -2013-10-31 04:00:00,8580.0 -2013-10-31 05:00:00,8509.0 -2013-10-31 06:00:00,8715.0 -2013-10-31 07:00:00,9321.0 -2013-10-31 08:00:00,10494.0 -2013-10-31 09:00:00,11442.0 -2013-10-31 10:00:00,11710.0 -2013-10-31 11:00:00,11791.0 -2013-10-31 12:00:00,11870.0 -2013-10-31 13:00:00,11878.0 -2013-10-31 14:00:00,11879.0 -2013-10-31 15:00:00,11907.0 -2013-10-31 16:00:00,11840.0 -2013-10-31 17:00:00,11740.0 -2013-10-31 18:00:00,11715.0 -2013-10-31 19:00:00,11718.0 -2013-10-31 20:00:00,11879.0 -2013-10-31 21:00:00,11603.0 -2013-10-31 22:00:00,11275.0 -2013-10-31 23:00:00,10822.0 -2013-11-01 00:00:00,10119.0 -2013-10-30 01:00:00,9497.0 -2013-10-30 02:00:00,8988.0 -2013-10-30 03:00:00,8635.0 -2013-10-30 04:00:00,8457.0 -2013-10-30 05:00:00,8432.0 -2013-10-30 06:00:00,8637.0 -2013-10-30 07:00:00,9336.0 -2013-10-30 08:00:00,10556.0 -2013-10-30 09:00:00,11310.0 -2013-10-30 10:00:00,11358.0 -2013-10-30 11:00:00,11452.0 -2013-10-30 12:00:00,11550.0 -2013-10-30 13:00:00,11542.0 -2013-10-30 14:00:00,11326.0 -2013-10-30 15:00:00,11545.0 -2013-10-30 16:00:00,11495.0 -2013-10-30 17:00:00,11522.0 -2013-10-30 18:00:00,11636.0 -2013-10-30 19:00:00,12053.0 -2013-10-30 20:00:00,12189.0 -2013-10-30 21:00:00,11984.0 -2013-10-30 22:00:00,11667.0 -2013-10-30 23:00:00,11069.0 -2013-10-31 00:00:00,10300.0 -2013-10-29 01:00:00,9727.0 -2013-10-29 02:00:00,9226.0 -2013-10-29 03:00:00,8950.0 -2013-10-29 04:00:00,8779.0 -2013-10-29 05:00:00,8736.0 -2013-10-29 06:00:00,8973.0 -2013-10-29 07:00:00,9686.0 -2013-10-29 08:00:00,10846.0 -2013-10-29 09:00:00,11556.0 -2013-10-29 10:00:00,11590.0 -2013-10-29 11:00:00,11649.0 -2013-10-29 12:00:00,11701.0 -2013-10-29 13:00:00,11619.0 -2013-10-29 14:00:00,11505.0 -2013-10-29 15:00:00,11417.0 -2013-10-29 16:00:00,11275.0 -2013-10-29 17:00:00,11148.0 -2013-10-29 18:00:00,11129.0 -2013-10-29 19:00:00,11455.0 -2013-10-29 20:00:00,11981.0 -2013-10-29 21:00:00,11870.0 -2013-10-29 22:00:00,11568.0 -2013-10-29 23:00:00,11034.0 -2013-10-30 00:00:00,10263.0 -2013-10-28 01:00:00,9016.0 -2013-10-28 02:00:00,8655.0 -2013-10-28 03:00:00,8463.0 -2013-10-28 04:00:00,8369.0 -2013-10-28 05:00:00,8438.0 -2013-10-28 06:00:00,8764.0 -2013-10-28 07:00:00,9612.0 -2013-10-28 08:00:00,10893.0 -2013-10-28 09:00:00,11600.0 -2013-10-28 10:00:00,11574.0 -2013-10-28 11:00:00,11560.0 -2013-10-28 12:00:00,11625.0 -2013-10-28 13:00:00,11551.0 -2013-10-28 14:00:00,11494.0 -2013-10-28 15:00:00,11445.0 -2013-10-28 16:00:00,11314.0 -2013-10-28 17:00:00,11188.0 -2013-10-28 18:00:00,11239.0 -2013-10-28 19:00:00,11567.0 -2013-10-28 20:00:00,12153.0 -2013-10-28 21:00:00,12053.0 -2013-10-28 22:00:00,11783.0 -2013-10-28 23:00:00,11259.0 -2013-10-29 00:00:00,10491.0 -2013-10-27 01:00:00,9213.0 -2013-10-27 02:00:00,8802.0 -2013-10-27 03:00:00,8527.0 -2013-10-27 04:00:00,8425.0 -2013-10-27 05:00:00,8337.0 -2013-10-27 06:00:00,8406.0 -2013-10-27 07:00:00,8529.0 -2013-10-27 08:00:00,8883.0 -2013-10-27 09:00:00,8954.0 -2013-10-27 10:00:00,9154.0 -2013-10-27 11:00:00,9227.0 -2013-10-27 12:00:00,9297.0 -2013-10-27 13:00:00,9288.0 -2013-10-27 14:00:00,9292.0 -2013-10-27 15:00:00,9186.0 -2013-10-27 16:00:00,9131.0 -2013-10-27 17:00:00,9069.0 -2013-10-27 18:00:00,9201.0 -2013-10-27 19:00:00,9534.0 -2013-10-27 20:00:00,10409.0 -2013-10-27 21:00:00,10547.0 -2013-10-27 22:00:00,10438.0 -2013-10-27 23:00:00,10111.0 -2013-10-28 00:00:00,9607.0 -2013-10-26 01:00:00,9829.0 -2013-10-26 02:00:00,9340.0 -2013-10-26 03:00:00,8986.0 -2013-10-26 04:00:00,8823.0 -2013-10-26 05:00:00,8654.0 -2013-10-26 06:00:00,8753.0 -2013-10-26 07:00:00,8959.0 -2013-10-26 08:00:00,9471.0 -2013-10-26 09:00:00,9861.0 -2013-10-26 10:00:00,10013.0 -2013-10-26 11:00:00,10191.0 -2013-10-26 12:00:00,10206.0 -2013-10-26 13:00:00,10099.0 -2013-10-26 14:00:00,9969.0 -2013-10-26 15:00:00,9855.0 -2013-10-26 16:00:00,9731.0 -2013-10-26 17:00:00,9679.0 -2013-10-26 18:00:00,9761.0 -2013-10-26 19:00:00,9910.0 -2013-10-26 20:00:00,10571.0 -2013-10-26 21:00:00,10566.0 -2013-10-26 22:00:00,10470.0 -2013-10-26 23:00:00,10168.0 -2013-10-27 00:00:00,9711.0 -2013-10-25 01:00:00,10059.0 -2013-10-25 02:00:00,9579.0 -2013-10-25 03:00:00,9293.0 -2013-10-25 04:00:00,9204.0 -2013-10-25 05:00:00,9193.0 -2013-10-25 06:00:00,9395.0 -2013-10-25 07:00:00,10169.0 -2013-10-25 08:00:00,11397.0 -2013-10-25 09:00:00,11951.0 -2013-10-25 10:00:00,11954.0 -2013-10-25 11:00:00,11910.0 -2013-10-25 12:00:00,11841.0 -2013-10-25 13:00:00,11708.0 -2013-10-25 14:00:00,11585.0 -2013-10-25 15:00:00,11532.0 -2013-10-25 16:00:00,11354.0 -2013-10-25 17:00:00,11133.0 -2013-10-25 18:00:00,11039.0 -2013-10-25 19:00:00,11166.0 -2013-10-25 20:00:00,11781.0 -2013-10-25 21:00:00,11788.0 -2013-10-25 22:00:00,11544.0 -2013-10-25 23:00:00,11221.0 -2013-10-26 00:00:00,10559.0 -2013-10-24 01:00:00,9888.0 -2013-10-24 02:00:00,9376.0 -2013-10-24 03:00:00,9077.0 -2013-10-24 04:00:00,8916.0 -2013-10-24 05:00:00,8912.0 -2013-10-24 06:00:00,9164.0 -2013-10-24 07:00:00,9915.0 -2013-10-24 08:00:00,11183.0 -2013-10-24 09:00:00,11841.0 -2013-10-24 10:00:00,11916.0 -2013-10-24 11:00:00,11865.0 -2013-10-24 12:00:00,11849.0 -2013-10-24 13:00:00,11792.0 -2013-10-24 14:00:00,11759.0 -2013-10-24 15:00:00,11744.0 -2013-10-24 16:00:00,11613.0 -2013-10-24 17:00:00,11590.0 -2013-10-24 18:00:00,11693.0 -2013-10-24 19:00:00,11952.0 -2013-10-24 20:00:00,12355.0 -2013-10-24 21:00:00,12264.0 -2013-10-24 22:00:00,12027.0 -2013-10-24 23:00:00,11534.0 -2013-10-25 00:00:00,10789.0 -2013-10-23 01:00:00,9902.0 -2013-10-23 02:00:00,9409.0 -2013-10-23 03:00:00,9156.0 -2013-10-23 04:00:00,9010.0 -2013-10-23 05:00:00,9019.0 -2013-10-23 06:00:00,9267.0 -2013-10-23 07:00:00,10031.0 -2013-10-23 08:00:00,11247.0 -2013-10-23 09:00:00,11876.0 -2013-10-23 10:00:00,11848.0 -2013-10-23 11:00:00,11806.0 -2013-10-23 12:00:00,11791.0 -2013-10-23 13:00:00,11712.0 -2013-10-23 14:00:00,11616.0 -2013-10-23 15:00:00,11600.0 -2013-10-23 16:00:00,11609.0 -2013-10-23 17:00:00,11604.0 -2013-10-23 18:00:00,11651.0 -2013-10-23 19:00:00,11891.0 -2013-10-23 20:00:00,12345.0 -2013-10-23 21:00:00,12225.0 -2013-10-23 22:00:00,11961.0 -2013-10-23 23:00:00,11453.0 -2013-10-24 00:00:00,10634.0 -2013-10-22 01:00:00,9702.0 -2013-10-22 02:00:00,9252.0 -2013-10-22 03:00:00,9003.0 -2013-10-22 04:00:00,8831.0 -2013-10-22 05:00:00,8857.0 -2013-10-22 06:00:00,9126.0 -2013-10-22 07:00:00,9894.0 -2013-10-22 08:00:00,11176.0 -2013-10-22 09:00:00,11751.0 -2013-10-22 10:00:00,11785.0 -2013-10-22 11:00:00,11830.0 -2013-10-22 12:00:00,11821.0 -2013-10-22 13:00:00,11820.0 -2013-10-22 14:00:00,11830.0 -2013-10-22 15:00:00,11903.0 -2013-10-22 16:00:00,11863.0 -2013-10-22 17:00:00,11746.0 -2013-10-22 18:00:00,11744.0 -2013-10-22 19:00:00,11940.0 -2013-10-22 20:00:00,12374.0 -2013-10-22 21:00:00,12255.0 -2013-10-22 22:00:00,11970.0 -2013-10-22 23:00:00,11413.0 -2013-10-23 00:00:00,10639.0 -2013-10-21 01:00:00,8691.0 -2013-10-21 02:00:00,8327.0 -2013-10-21 03:00:00,8058.0 -2013-10-21 04:00:00,7974.0 -2013-10-21 05:00:00,7984.0 -2013-10-21 06:00:00,8255.0 -2013-10-21 07:00:00,9012.0 -2013-10-21 08:00:00,10315.0 -2013-10-21 09:00:00,11072.0 -2013-10-21 10:00:00,11244.0 -2013-10-21 11:00:00,11331.0 -2013-10-21 12:00:00,11434.0 -2013-10-21 13:00:00,11408.0 -2013-10-21 14:00:00,11388.0 -2013-10-21 15:00:00,11377.0 -2013-10-21 16:00:00,11268.0 -2013-10-21 17:00:00,11130.0 -2013-10-21 18:00:00,11086.0 -2013-10-21 19:00:00,11257.0 -2013-10-21 20:00:00,11929.0 -2013-10-21 21:00:00,12028.0 -2013-10-21 22:00:00,11795.0 -2013-10-21 23:00:00,11264.0 -2013-10-22 00:00:00,10479.0 -2013-10-20 01:00:00,9014.0 -2013-10-20 02:00:00,8542.0 -2013-10-20 03:00:00,8275.0 -2013-10-20 04:00:00,8078.0 -2013-10-20 05:00:00,8000.0 -2013-10-20 06:00:00,7991.0 -2013-10-20 07:00:00,8151.0 -2013-10-20 08:00:00,8435.0 -2013-10-20 09:00:00,8512.0 -2013-10-20 10:00:00,8783.0 -2013-10-20 11:00:00,8952.0 -2013-10-20 12:00:00,9090.0 -2013-10-20 13:00:00,9169.0 -2013-10-20 14:00:00,9199.0 -2013-10-20 15:00:00,9160.0 -2013-10-20 16:00:00,9146.0 -2013-10-20 17:00:00,9125.0 -2013-10-20 18:00:00,9233.0 -2013-10-20 19:00:00,9641.0 -2013-10-20 20:00:00,10336.0 -2013-10-20 21:00:00,10387.0 -2013-10-20 22:00:00,10198.0 -2013-10-20 23:00:00,9816.0 -2013-10-21 00:00:00,9250.0 -2013-10-19 01:00:00,9552.0 -2013-10-19 02:00:00,9023.0 -2013-10-19 03:00:00,8679.0 -2013-10-19 04:00:00,8468.0 -2013-10-19 05:00:00,8368.0 -2013-10-19 06:00:00,8367.0 -2013-10-19 07:00:00,8640.0 -2013-10-19 08:00:00,9163.0 -2013-10-19 09:00:00,9440.0 -2013-10-19 10:00:00,9649.0 -2013-10-19 11:00:00,9872.0 -2013-10-19 12:00:00,9881.0 -2013-10-19 13:00:00,9819.0 -2013-10-19 14:00:00,9725.0 -2013-10-19 15:00:00,9610.0 -2013-10-19 16:00:00,9546.0 -2013-10-19 17:00:00,9467.0 -2013-10-19 18:00:00,9603.0 -2013-10-19 19:00:00,9872.0 -2013-10-19 20:00:00,10457.0 -2013-10-19 21:00:00,10461.0 -2013-10-19 22:00:00,10282.0 -2013-10-19 23:00:00,10038.0 -2013-10-20 00:00:00,9513.0 -2013-10-18 01:00:00,9500.0 -2013-10-18 02:00:00,8995.0 -2013-10-18 03:00:00,8676.0 -2013-10-18 04:00:00,8468.0 -2013-10-18 05:00:00,8429.0 -2013-10-18 06:00:00,8669.0 -2013-10-18 07:00:00,9335.0 -2013-10-18 08:00:00,10513.0 -2013-10-18 09:00:00,11078.0 -2013-10-18 10:00:00,11241.0 -2013-10-18 11:00:00,11426.0 -2013-10-18 12:00:00,11573.0 -2013-10-18 13:00:00,11602.0 -2013-10-18 14:00:00,11524.0 -2013-10-18 15:00:00,11497.0 -2013-10-18 16:00:00,11353.0 -2013-10-18 17:00:00,11257.0 -2013-10-18 18:00:00,11248.0 -2013-10-18 19:00:00,11394.0 -2013-10-18 20:00:00,11764.0 -2013-10-18 21:00:00,11603.0 -2013-10-18 22:00:00,11337.0 -2013-10-18 23:00:00,10928.0 -2013-10-19 00:00:00,10273.0 -2013-10-17 01:00:00,9329.0 -2013-10-17 02:00:00,8811.0 -2013-10-17 03:00:00,8515.0 -2013-10-17 04:00:00,8311.0 -2013-10-17 05:00:00,8257.0 -2013-10-17 06:00:00,8468.0 -2013-10-17 07:00:00,9131.0 -2013-10-17 08:00:00,10306.0 -2013-10-17 09:00:00,11093.0 -2013-10-17 10:00:00,11287.0 -2013-10-17 11:00:00,11403.0 -2013-10-17 12:00:00,11519.0 -2013-10-17 13:00:00,11500.0 -2013-10-17 14:00:00,11386.0 -2013-10-17 15:00:00,11350.0 -2013-10-17 16:00:00,11283.0 -2013-10-17 17:00:00,11177.0 -2013-10-17 18:00:00,11115.0 -2013-10-17 19:00:00,11069.0 -2013-10-17 20:00:00,11620.0 -2013-10-17 21:00:00,11743.0 -2013-10-17 22:00:00,11497.0 -2013-10-17 23:00:00,10977.0 -2013-10-18 00:00:00,10268.0 -2013-10-16 01:00:00,9286.0 -2013-10-16 02:00:00,8747.0 -2013-10-16 03:00:00,8421.0 -2013-10-16 04:00:00,8238.0 -2013-10-16 05:00:00,8200.0 -2013-10-16 06:00:00,8414.0 -2013-10-16 07:00:00,9056.0 -2013-10-16 08:00:00,10223.0 -2013-10-16 09:00:00,10935.0 -2013-10-16 10:00:00,11120.0 -2013-10-16 11:00:00,11264.0 -2013-10-16 12:00:00,11386.0 -2013-10-16 13:00:00,11360.0 -2013-10-16 14:00:00,11277.0 -2013-10-16 15:00:00,11331.0 -2013-10-16 16:00:00,11281.0 -2013-10-16 17:00:00,11210.0 -2013-10-16 18:00:00,11198.0 -2013-10-16 19:00:00,11394.0 -2013-10-16 20:00:00,11846.0 -2013-10-16 21:00:00,11777.0 -2013-10-16 22:00:00,11467.0 -2013-10-16 23:00:00,10898.0 -2013-10-17 00:00:00,10086.0 -2013-10-15 01:00:00,9308.0 -2013-10-15 02:00:00,8785.0 -2013-10-15 03:00:00,8461.0 -2013-10-15 04:00:00,8242.0 -2013-10-15 05:00:00,8195.0 -2013-10-15 06:00:00,8372.0 -2013-10-15 07:00:00,9024.0 -2013-10-15 08:00:00,10175.0 -2013-10-15 09:00:00,10905.0 -2013-10-15 10:00:00,11131.0 -2013-10-15 11:00:00,11282.0 -2013-10-15 12:00:00,11529.0 -2013-10-15 13:00:00,11627.0 -2013-10-15 14:00:00,11715.0 -2013-10-15 15:00:00,11848.0 -2013-10-15 16:00:00,11794.0 -2013-10-15 17:00:00,11723.0 -2013-10-15 18:00:00,11673.0 -2013-10-15 19:00:00,11627.0 -2013-10-15 20:00:00,11998.0 -2013-10-15 21:00:00,12055.0 -2013-10-15 22:00:00,11667.0 -2013-10-15 23:00:00,10940.0 -2013-10-16 00:00:00,10066.0 -2013-10-14 01:00:00,8467.0 -2013-10-14 02:00:00,8100.0 -2013-10-14 03:00:00,7931.0 -2013-10-14 04:00:00,7806.0 -2013-10-14 05:00:00,7809.0 -2013-10-14 06:00:00,8016.0 -2013-10-14 07:00:00,8681.0 -2013-10-14 08:00:00,9650.0 -2013-10-14 09:00:00,10225.0 -2013-10-14 10:00:00,10569.0 -2013-10-14 11:00:00,10811.0 -2013-10-14 12:00:00,11108.0 -2013-10-14 13:00:00,11188.0 -2013-10-14 14:00:00,11189.0 -2013-10-14 15:00:00,11273.0 -2013-10-14 16:00:00,11215.0 -2013-10-14 17:00:00,11139.0 -2013-10-14 18:00:00,11063.0 -2013-10-14 19:00:00,11009.0 -2013-10-14 20:00:00,11487.0 -2013-10-14 21:00:00,11648.0 -2013-10-14 22:00:00,11345.0 -2013-10-14 23:00:00,10861.0 -2013-10-15 00:00:00,10081.0 -2013-10-13 01:00:00,8721.0 -2013-10-13 02:00:00,8300.0 -2013-10-13 03:00:00,7916.0 -2013-10-13 04:00:00,7742.0 -2013-10-13 05:00:00,7604.0 -2013-10-13 06:00:00,7618.0 -2013-10-13 07:00:00,7676.0 -2013-10-13 08:00:00,7910.0 -2013-10-13 09:00:00,7874.0 -2013-10-13 10:00:00,8207.0 -2013-10-13 11:00:00,8519.0 -2013-10-13 12:00:00,8779.0 -2013-10-13 13:00:00,8874.0 -2013-10-13 14:00:00,8911.0 -2013-10-13 15:00:00,8919.0 -2013-10-13 16:00:00,8984.0 -2013-10-13 17:00:00,8951.0 -2013-10-13 18:00:00,9002.0 -2013-10-13 19:00:00,9165.0 -2013-10-13 20:00:00,9705.0 -2013-10-13 21:00:00,9998.0 -2013-10-13 22:00:00,9804.0 -2013-10-13 23:00:00,9492.0 -2013-10-14 00:00:00,9013.0 -2013-10-12 01:00:00,9558.0 -2013-10-12 02:00:00,8901.0 -2013-10-12 03:00:00,8558.0 -2013-10-12 04:00:00,8281.0 -2013-10-12 05:00:00,8216.0 -2013-10-12 06:00:00,8180.0 -2013-10-12 07:00:00,8426.0 -2013-10-12 08:00:00,8816.0 -2013-10-12 09:00:00,9046.0 -2013-10-12 10:00:00,9529.0 -2013-10-12 11:00:00,9991.0 -2013-10-12 12:00:00,10378.0 -2013-10-12 13:00:00,10516.0 -2013-10-12 14:00:00,10465.0 -2013-10-12 15:00:00,10406.0 -2013-10-12 16:00:00,10275.0 -2013-10-12 17:00:00,10184.0 -2013-10-12 18:00:00,10202.0 -2013-10-12 19:00:00,10144.0 -2013-10-12 20:00:00,10521.0 -2013-10-12 21:00:00,10641.0 -2013-10-12 22:00:00,10358.0 -2013-10-12 23:00:00,9931.0 -2013-10-13 00:00:00,9366.0 -2013-10-11 01:00:00,9537.0 -2013-10-11 02:00:00,8944.0 -2013-10-11 03:00:00,8575.0 -2013-10-11 04:00:00,8323.0 -2013-10-11 05:00:00,8260.0 -2013-10-11 06:00:00,8398.0 -2013-10-11 07:00:00,8989.0 -2013-10-11 08:00:00,10052.0 -2013-10-11 09:00:00,10619.0 -2013-10-11 10:00:00,11022.0 -2013-10-11 11:00:00,11398.0 -2013-10-11 12:00:00,11771.0 -2013-10-11 13:00:00,11995.0 -2013-10-11 14:00:00,12179.0 -2013-10-11 15:00:00,12348.0 -2013-10-11 16:00:00,12323.0 -2013-10-11 17:00:00,12192.0 -2013-10-11 18:00:00,12016.0 -2013-10-11 19:00:00,11716.0 -2013-10-11 20:00:00,11833.0 -2013-10-11 21:00:00,11900.0 -2013-10-11 22:00:00,11549.0 -2013-10-11 23:00:00,11079.0 -2013-10-12 00:00:00,10288.0 -2013-10-10 01:00:00,9374.0 -2013-10-10 02:00:00,8822.0 -2013-10-10 03:00:00,8477.0 -2013-10-10 04:00:00,8271.0 -2013-10-10 05:00:00,8184.0 -2013-10-10 06:00:00,8349.0 -2013-10-10 07:00:00,9005.0 -2013-10-10 08:00:00,10099.0 -2013-10-10 09:00:00,10621.0 -2013-10-10 10:00:00,10939.0 -2013-10-10 11:00:00,11294.0 -2013-10-10 12:00:00,11693.0 -2013-10-10 13:00:00,11921.0 -2013-10-10 14:00:00,12054.0 -2013-10-10 15:00:00,12211.0 -2013-10-10 16:00:00,12252.0 -2013-10-10 17:00:00,12187.0 -2013-10-10 18:00:00,12061.0 -2013-10-10 19:00:00,11860.0 -2013-10-10 20:00:00,11992.0 -2013-10-10 21:00:00,12173.0 -2013-10-10 22:00:00,11812.0 -2013-10-10 23:00:00,11208.0 -2013-10-11 00:00:00,10354.0 -2013-10-09 01:00:00,9527.0 -2013-10-09 02:00:00,8928.0 -2013-10-09 03:00:00,8552.0 -2013-10-09 04:00:00,8340.0 -2013-10-09 05:00:00,8285.0 -2013-10-09 06:00:00,8430.0 -2013-10-09 07:00:00,9057.0 -2013-10-09 08:00:00,10125.0 -2013-10-09 09:00:00,10634.0 -2013-10-09 10:00:00,10987.0 -2013-10-09 11:00:00,11294.0 -2013-10-09 12:00:00,11620.0 -2013-10-09 13:00:00,11781.0 -2013-10-09 14:00:00,11887.0 -2013-10-09 15:00:00,12058.0 -2013-10-09 16:00:00,12059.0 -2013-10-09 17:00:00,11992.0 -2013-10-09 18:00:00,11897.0 -2013-10-09 19:00:00,11696.0 -2013-10-09 20:00:00,11816.0 -2013-10-09 21:00:00,11999.0 -2013-10-09 22:00:00,11645.0 -2013-10-09 23:00:00,11061.0 -2013-10-10 00:00:00,10217.0 -2013-10-08 01:00:00,9259.0 -2013-10-08 02:00:00,8724.0 -2013-10-08 03:00:00,8429.0 -2013-10-08 04:00:00,8218.0 -2013-10-08 05:00:00,8170.0 -2013-10-08 06:00:00,8367.0 -2013-10-08 07:00:00,9048.0 -2013-10-08 08:00:00,10167.0 -2013-10-08 09:00:00,10693.0 -2013-10-08 10:00:00,10952.0 -2013-10-08 11:00:00,11240.0 -2013-10-08 12:00:00,11572.0 -2013-10-08 13:00:00,11787.0 -2013-10-08 14:00:00,11945.0 -2013-10-08 15:00:00,12149.0 -2013-10-08 16:00:00,12207.0 -2013-10-08 17:00:00,12182.0 -2013-10-08 18:00:00,12112.0 -2013-10-08 19:00:00,11919.0 -2013-10-08 20:00:00,12038.0 -2013-10-08 21:00:00,12213.0 -2013-10-08 22:00:00,11850.0 -2013-10-08 23:00:00,11218.0 -2013-10-09 00:00:00,10339.0 -2013-10-07 01:00:00,8547.0 -2013-10-07 02:00:00,8169.0 -2013-10-07 03:00:00,7957.0 -2013-10-07 04:00:00,7901.0 -2013-10-07 05:00:00,7883.0 -2013-10-07 06:00:00,8135.0 -2013-10-07 07:00:00,8840.0 -2013-10-07 08:00:00,10020.0 -2013-10-07 09:00:00,10698.0 -2013-10-07 10:00:00,10978.0 -2013-10-07 11:00:00,11057.0 -2013-10-07 12:00:00,11143.0 -2013-10-07 13:00:00,11252.0 -2013-10-07 14:00:00,11337.0 -2013-10-07 15:00:00,11451.0 -2013-10-07 16:00:00,11489.0 -2013-10-07 17:00:00,11428.0 -2013-10-07 18:00:00,11372.0 -2013-10-07 19:00:00,11285.0 -2013-10-07 20:00:00,11506.0 -2013-10-07 21:00:00,11774.0 -2013-10-07 22:00:00,11468.0 -2013-10-07 23:00:00,10888.0 -2013-10-08 00:00:00,10074.0 -2013-10-06 01:00:00,10248.0 -2013-10-06 02:00:00,9543.0 -2013-10-06 03:00:00,8908.0 -2013-10-06 04:00:00,8463.0 -2013-10-06 05:00:00,8201.0 -2013-10-06 06:00:00,8046.0 -2013-10-06 07:00:00,8073.0 -2013-10-06 08:00:00,8241.0 -2013-10-06 09:00:00,8245.0 -2013-10-06 10:00:00,8579.0 -2013-10-06 11:00:00,8890.0 -2013-10-06 12:00:00,9148.0 -2013-10-06 13:00:00,9354.0 -2013-10-06 14:00:00,9499.0 -2013-10-06 15:00:00,9492.0 -2013-10-06 16:00:00,9421.0 -2013-10-06 17:00:00,9369.0 -2013-10-06 18:00:00,9483.0 -2013-10-06 19:00:00,9708.0 -2013-10-06 20:00:00,10082.0 -2013-10-06 21:00:00,10209.0 -2013-10-06 22:00:00,10015.0 -2013-10-06 23:00:00,9623.0 -2013-10-07 00:00:00,9118.0 -2013-10-05 01:00:00,10570.0 -2013-10-05 02:00:00,9832.0 -2013-10-05 03:00:00,9392.0 -2013-10-05 04:00:00,9098.0 -2013-10-05 05:00:00,8973.0 -2013-10-05 06:00:00,8978.0 -2013-10-05 07:00:00,9206.0 -2013-10-05 08:00:00,9692.0 -2013-10-05 09:00:00,9989.0 -2013-10-05 10:00:00,10718.0 -2013-10-05 11:00:00,11288.0 -2013-10-05 12:00:00,11799.0 -2013-10-05 13:00:00,12175.0 -2013-10-05 14:00:00,12474.0 -2013-10-05 15:00:00,12662.0 -2013-10-05 16:00:00,12851.0 -2013-10-05 17:00:00,12796.0 -2013-10-05 18:00:00,12621.0 -2013-10-05 19:00:00,12316.0 -2013-10-05 20:00:00,12389.0 -2013-10-05 21:00:00,12385.0 -2013-10-05 22:00:00,12102.0 -2013-10-05 23:00:00,11665.0 -2013-10-06 00:00:00,11037.0 -2013-10-04 01:00:00,10702.0 -2013-10-04 02:00:00,10067.0 -2013-10-04 03:00:00,9486.0 -2013-10-04 04:00:00,9179.0 -2013-10-04 05:00:00,9030.0 -2013-10-04 06:00:00,9211.0 -2013-10-04 07:00:00,9900.0 -2013-10-04 08:00:00,11114.0 -2013-10-04 09:00:00,11797.0 -2013-10-04 10:00:00,12364.0 -2013-10-04 11:00:00,12838.0 -2013-10-04 12:00:00,13181.0 -2013-10-04 13:00:00,13272.0 -2013-10-04 14:00:00,13381.0 -2013-10-04 15:00:00,13563.0 -2013-10-04 16:00:00,13494.0 -2013-10-04 17:00:00,13387.0 -2013-10-04 18:00:00,13254.0 -2013-10-04 19:00:00,13104.0 -2013-10-04 20:00:00,13186.0 -2013-10-04 21:00:00,13275.0 -2013-10-04 22:00:00,12836.0 -2013-10-04 23:00:00,12255.0 -2013-10-05 00:00:00,11422.0 -2013-10-03 01:00:00,10141.0 -2013-10-03 02:00:00,9463.0 -2013-10-03 03:00:00,9083.0 -2013-10-03 04:00:00,8874.0 -2013-10-03 05:00:00,8814.0 -2013-10-03 06:00:00,9025.0 -2013-10-03 07:00:00,9775.0 -2013-10-03 08:00:00,11126.0 -2013-10-03 09:00:00,11912.0 -2013-10-03 10:00:00,12412.0 -2013-10-03 11:00:00,12755.0 -2013-10-03 12:00:00,13019.0 -2013-10-03 13:00:00,12985.0 -2013-10-03 14:00:00,13065.0 -2013-10-03 15:00:00,13293.0 -2013-10-03 16:00:00,13539.0 -2013-10-03 17:00:00,13689.0 -2013-10-03 18:00:00,13595.0 -2013-10-03 19:00:00,13451.0 -2013-10-03 20:00:00,13457.0 -2013-10-03 21:00:00,13650.0 -2013-10-03 22:00:00,13321.0 -2013-10-03 23:00:00,12582.0 -2013-10-04 00:00:00,11610.0 -2013-10-02 01:00:00,10453.0 -2013-10-02 02:00:00,9818.0 -2013-10-02 03:00:00,9349.0 -2013-10-02 04:00:00,9055.0 -2013-10-02 05:00:00,8943.0 -2013-10-02 06:00:00,9128.0 -2013-10-02 07:00:00,9758.0 -2013-10-02 08:00:00,10864.0 -2013-10-02 09:00:00,11438.0 -2013-10-02 10:00:00,11870.0 -2013-10-02 11:00:00,12318.0 -2013-10-02 12:00:00,12668.0 -2013-10-02 13:00:00,12893.0 -2013-10-02 14:00:00,13080.0 -2013-10-02 15:00:00,13347.0 -2013-10-02 16:00:00,13483.0 -2013-10-02 17:00:00,13521.0 -2013-10-02 18:00:00,13357.0 -2013-10-02 19:00:00,12987.0 -2013-10-02 20:00:00,12748.0 -2013-10-02 21:00:00,12956.0 -2013-10-02 22:00:00,12606.0 -2013-10-02 23:00:00,11937.0 -2013-10-03 00:00:00,11001.0 -2013-10-01 01:00:00,9678.0 -2013-10-01 02:00:00,9126.0 -2013-10-01 03:00:00,8750.0 -2013-10-01 04:00:00,8577.0 -2013-10-01 05:00:00,8499.0 -2013-10-01 06:00:00,8701.0 -2013-10-01 07:00:00,9387.0 -2013-10-01 08:00:00,10613.0 -2013-10-01 09:00:00,11353.0 -2013-10-01 10:00:00,11779.0 -2013-10-01 11:00:00,12050.0 -2013-10-01 12:00:00,12296.0 -2013-10-01 13:00:00,12374.0 -2013-10-01 14:00:00,12483.0 -2013-10-01 15:00:00,12800.0 -2013-10-01 16:00:00,13034.0 -2013-10-01 17:00:00,13189.0 -2013-10-01 18:00:00,13243.0 -2013-10-01 19:00:00,13104.0 -2013-10-01 20:00:00,12974.0 -2013-10-01 21:00:00,13295.0 -2013-10-01 22:00:00,12986.0 -2013-10-01 23:00:00,12290.0 -2013-10-02 00:00:00,11387.0 -2013-09-30 01:00:00,8653.0 -2013-09-30 02:00:00,8268.0 -2013-09-30 03:00:00,8039.0 -2013-09-30 04:00:00,7947.0 -2013-09-30 05:00:00,7891.0 -2013-09-30 06:00:00,8137.0 -2013-09-30 07:00:00,8815.0 -2013-09-30 08:00:00,9966.0 -2013-09-30 09:00:00,10560.0 -2013-09-30 10:00:00,10977.0 -2013-09-30 11:00:00,11356.0 -2013-09-30 12:00:00,11750.0 -2013-09-30 13:00:00,11894.0 -2013-09-30 14:00:00,11997.0 -2013-09-30 15:00:00,12201.0 -2013-09-30 16:00:00,12262.0 -2013-09-30 17:00:00,12272.0 -2013-09-30 18:00:00,12205.0 -2013-09-30 19:00:00,12040.0 -2013-09-30 20:00:00,11968.0 -2013-09-30 21:00:00,12275.0 -2013-09-30 22:00:00,11930.0 -2013-09-30 23:00:00,11368.0 -2013-10-01 00:00:00,10513.0 -2013-09-29 01:00:00,10212.0 -2013-09-29 02:00:00,9600.0 -2013-09-29 03:00:00,9045.0 -2013-09-29 04:00:00,8533.0 -2013-09-29 05:00:00,8158.0 -2013-09-29 06:00:00,8041.0 -2013-09-29 07:00:00,8029.0 -2013-09-29 08:00:00,8112.0 -2013-09-29 09:00:00,8095.0 -2013-09-29 10:00:00,8567.0 -2013-09-29 11:00:00,8960.0 -2013-09-29 12:00:00,9360.0 -2013-09-29 13:00:00,9545.0 -2013-09-29 14:00:00,9787.0 -2013-09-29 15:00:00,9802.0 -2013-09-29 16:00:00,9915.0 -2013-09-29 17:00:00,9897.0 -2013-09-29 18:00:00,9948.0 -2013-09-29 19:00:00,9887.0 -2013-09-29 20:00:00,10061.0 -2013-09-29 21:00:00,10481.0 -2013-09-29 22:00:00,10277.0 -2013-09-29 23:00:00,9845.0 -2013-09-30 00:00:00,9298.0 -2013-09-28 01:00:00,9803.0 -2013-09-28 02:00:00,9175.0 -2013-09-28 03:00:00,8708.0 -2013-09-28 04:00:00,8455.0 -2013-09-28 05:00:00,8261.0 -2013-09-28 06:00:00,8283.0 -2013-09-28 07:00:00,8454.0 -2013-09-28 08:00:00,8806.0 -2013-09-28 09:00:00,9053.0 -2013-09-28 10:00:00,9628.0 -2013-09-28 11:00:00,10219.0 -2013-09-28 12:00:00,10671.0 -2013-09-28 13:00:00,11046.0 -2013-09-28 14:00:00,11366.0 -2013-09-28 15:00:00,11628.0 -2013-09-28 16:00:00,11809.0 -2013-09-28 17:00:00,11801.0 -2013-09-28 18:00:00,11743.0 -2013-09-28 19:00:00,11503.0 -2013-09-28 20:00:00,11724.0 -2013-09-28 21:00:00,11995.0 -2013-09-28 22:00:00,11895.0 -2013-09-28 23:00:00,11537.0 -2013-09-29 00:00:00,10948.0 -2013-09-27 01:00:00,9646.0 -2013-09-27 02:00:00,9007.0 -2013-09-27 03:00:00,8630.0 -2013-09-27 04:00:00,8377.0 -2013-09-27 05:00:00,8275.0 -2013-09-27 06:00:00,8415.0 -2013-09-27 07:00:00,9036.0 -2013-09-27 08:00:00,10032.0 -2013-09-27 09:00:00,10580.0 -2013-09-27 10:00:00,11098.0 -2013-09-27 11:00:00,11582.0 -2013-09-27 12:00:00,12008.0 -2013-09-27 13:00:00,12342.0 -2013-09-27 14:00:00,12610.0 -2013-09-27 15:00:00,12908.0 -2013-09-27 16:00:00,13035.0 -2013-09-27 17:00:00,13100.0 -2013-09-27 18:00:00,13021.0 -2013-09-27 19:00:00,12667.0 -2013-09-27 20:00:00,12308.0 -2013-09-27 21:00:00,12475.0 -2013-09-27 22:00:00,12055.0 -2013-09-27 23:00:00,11471.0 -2013-09-28 00:00:00,10689.0 -2013-09-26 01:00:00,9471.0 -2013-09-26 02:00:00,8916.0 -2013-09-26 03:00:00,8588.0 -2013-09-26 04:00:00,8354.0 -2013-09-26 05:00:00,8274.0 -2013-09-26 06:00:00,8430.0 -2013-09-26 07:00:00,9080.0 -2013-09-26 08:00:00,10108.0 -2013-09-26 09:00:00,10632.0 -2013-09-26 10:00:00,11132.0 -2013-09-26 11:00:00,11522.0 -2013-09-26 12:00:00,11882.0 -2013-09-26 13:00:00,12077.0 -2013-09-26 14:00:00,12223.0 -2013-09-26 15:00:00,12411.0 -2013-09-26 16:00:00,12521.0 -2013-09-26 17:00:00,12577.0 -2013-09-26 18:00:00,12509.0 -2013-09-26 19:00:00,12241.0 -2013-09-26 20:00:00,12013.0 -2013-09-26 21:00:00,12330.0 -2013-09-26 22:00:00,11989.0 -2013-09-26 23:00:00,11384.0 -2013-09-27 00:00:00,10459.0 -2013-09-25 01:00:00,9379.0 -2013-09-25 02:00:00,8810.0 -2013-09-25 03:00:00,8456.0 -2013-09-25 04:00:00,8266.0 -2013-09-25 05:00:00,8181.0 -2013-09-25 06:00:00,8385.0 -2013-09-25 07:00:00,9012.0 -2013-09-25 08:00:00,10038.0 -2013-09-25 09:00:00,10574.0 -2013-09-25 10:00:00,11011.0 -2013-09-25 11:00:00,11416.0 -2013-09-25 12:00:00,11730.0 -2013-09-25 13:00:00,11862.0 -2013-09-25 14:00:00,11954.0 -2013-09-25 15:00:00,12098.0 -2013-09-25 16:00:00,12145.0 -2013-09-25 17:00:00,12122.0 -2013-09-25 18:00:00,12027.0 -2013-09-25 19:00:00,11791.0 -2013-09-25 20:00:00,11576.0 -2013-09-25 21:00:00,11992.0 -2013-09-25 22:00:00,11699.0 -2013-09-25 23:00:00,11091.0 -2013-09-26 00:00:00,10260.0 -2013-09-24 01:00:00,9297.0 -2013-09-24 02:00:00,8749.0 -2013-09-24 03:00:00,8399.0 -2013-09-24 04:00:00,8203.0 -2013-09-24 05:00:00,8137.0 -2013-09-24 06:00:00,8281.0 -2013-09-24 07:00:00,8903.0 -2013-09-24 08:00:00,9938.0 -2013-09-24 09:00:00,10454.0 -2013-09-24 10:00:00,10829.0 -2013-09-24 11:00:00,11137.0 -2013-09-24 12:00:00,11489.0 -2013-09-24 13:00:00,11702.0 -2013-09-24 14:00:00,11781.0 -2013-09-24 15:00:00,11949.0 -2013-09-24 16:00:00,12002.0 -2013-09-24 17:00:00,11936.0 -2013-09-24 18:00:00,11851.0 -2013-09-24 19:00:00,11598.0 -2013-09-24 20:00:00,11422.0 -2013-09-24 21:00:00,11844.0 -2013-09-24 22:00:00,11600.0 -2013-09-24 23:00:00,10997.0 -2013-09-25 00:00:00,10156.0 -2013-09-23 01:00:00,8578.0 -2013-09-23 02:00:00,8152.0 -2013-09-23 03:00:00,7940.0 -2013-09-23 04:00:00,7830.0 -2013-09-23 05:00:00,7862.0 -2013-09-23 06:00:00,8062.0 -2013-09-23 07:00:00,8777.0 -2013-09-23 08:00:00,9842.0 -2013-09-23 09:00:00,10385.0 -2013-09-23 10:00:00,10706.0 -2013-09-23 11:00:00,11008.0 -2013-09-23 12:00:00,11273.0 -2013-09-23 13:00:00,11437.0 -2013-09-23 14:00:00,11519.0 -2013-09-23 15:00:00,11657.0 -2013-09-23 16:00:00,11698.0 -2013-09-23 17:00:00,11664.0 -2013-09-23 18:00:00,11589.0 -2013-09-23 19:00:00,11422.0 -2013-09-23 20:00:00,11314.0 -2013-09-23 21:00:00,11757.0 -2013-09-23 22:00:00,11502.0 -2013-09-23 23:00:00,10923.0 -2013-09-24 00:00:00,10113.0 -2013-09-22 01:00:00,8727.0 -2013-09-22 02:00:00,8215.0 -2013-09-22 03:00:00,7925.0 -2013-09-22 04:00:00,7686.0 -2013-09-22 05:00:00,7580.0 -2013-09-22 06:00:00,7549.0 -2013-09-22 07:00:00,7650.0 -2013-09-22 08:00:00,7722.0 -2013-09-22 09:00:00,7837.0 -2013-09-22 10:00:00,8270.0 -2013-09-22 11:00:00,8704.0 -2013-09-22 12:00:00,8998.0 -2013-09-22 13:00:00,9205.0 -2013-09-22 14:00:00,9291.0 -2013-09-22 15:00:00,9378.0 -2013-09-22 16:00:00,9387.0 -2013-09-22 17:00:00,9481.0 -2013-09-22 18:00:00,9490.0 -2013-09-22 19:00:00,9555.0 -2013-09-22 20:00:00,9660.0 -2013-09-22 21:00:00,10262.0 -2013-09-22 22:00:00,10137.0 -2013-09-22 23:00:00,9741.0 -2013-09-23 00:00:00,9186.0 -2013-09-21 01:00:00,9809.0 -2013-09-21 02:00:00,9237.0 -2013-09-21 03:00:00,8773.0 -2013-09-21 04:00:00,8512.0 -2013-09-21 05:00:00,8346.0 -2013-09-21 06:00:00,8336.0 -2013-09-21 07:00:00,8545.0 -2013-09-21 08:00:00,8799.0 -2013-09-21 09:00:00,8976.0 -2013-09-21 10:00:00,9484.0 -2013-09-21 11:00:00,9899.0 -2013-09-21 12:00:00,10151.0 -2013-09-21 13:00:00,10245.0 -2013-09-21 14:00:00,10258.0 -2013-09-21 15:00:00,10140.0 -2013-09-21 16:00:00,10073.0 -2013-09-21 17:00:00,9979.0 -2013-09-21 18:00:00,9947.0 -2013-09-21 19:00:00,9826.0 -2013-09-21 20:00:00,9808.0 -2013-09-21 21:00:00,10269.0 -2013-09-21 22:00:00,10152.0 -2013-09-21 23:00:00,9827.0 -2013-09-22 00:00:00,9287.0 -2013-09-20 01:00:00,12274.0 -2013-09-20 02:00:00,11342.0 -2013-09-20 03:00:00,10579.0 -2013-09-20 04:00:00,10017.0 -2013-09-20 05:00:00,9801.0 -2013-09-20 06:00:00,9883.0 -2013-09-20 07:00:00,10564.0 -2013-09-20 08:00:00,11762.0 -2013-09-20 09:00:00,12435.0 -2013-09-20 10:00:00,12855.0 -2013-09-20 11:00:00,13151.0 -2013-09-20 12:00:00,13455.0 -2013-09-20 13:00:00,13727.0 -2013-09-20 14:00:00,13929.0 -2013-09-20 15:00:00,14082.0 -2013-09-20 16:00:00,13866.0 -2013-09-20 17:00:00,13646.0 -2013-09-20 18:00:00,13223.0 -2013-09-20 19:00:00,12648.0 -2013-09-20 20:00:00,12229.0 -2013-09-20 21:00:00,12370.0 -2013-09-20 22:00:00,11953.0 -2013-09-20 23:00:00,11447.0 -2013-09-21 00:00:00,10624.0 -2013-09-19 01:00:00,10498.0 -2013-09-19 02:00:00,9864.0 -2013-09-19 03:00:00,9518.0 -2013-09-19 04:00:00,9270.0 -2013-09-19 05:00:00,9211.0 -2013-09-19 06:00:00,9350.0 -2013-09-19 07:00:00,10148.0 -2013-09-19 08:00:00,11439.0 -2013-09-19 09:00:00,12376.0 -2013-09-19 10:00:00,12843.0 -2013-09-19 11:00:00,13223.0 -2013-09-19 12:00:00,13596.0 -2013-09-19 13:00:00,13863.0 -2013-09-19 14:00:00,14264.0 -2013-09-19 15:00:00,14976.0 -2013-09-19 16:00:00,15499.0 -2013-09-19 17:00:00,15943.0 -2013-09-19 18:00:00,16253.0 -2013-09-19 19:00:00,16112.0 -2013-09-19 20:00:00,15648.0 -2013-09-19 21:00:00,15824.0 -2013-09-19 22:00:00,15466.0 -2013-09-19 23:00:00,14574.0 -2013-09-20 00:00:00,13429.0 -2013-09-18 01:00:00,9483.0 -2013-09-18 02:00:00,8920.0 -2013-09-18 03:00:00,8561.0 -2013-09-18 04:00:00,8371.0 -2013-09-18 05:00:00,8283.0 -2013-09-18 06:00:00,8465.0 -2013-09-18 07:00:00,9107.0 -2013-09-18 08:00:00,10252.0 -2013-09-18 09:00:00,10975.0 -2013-09-18 10:00:00,11406.0 -2013-09-18 11:00:00,11710.0 -2013-09-18 12:00:00,12216.0 -2013-09-18 13:00:00,12629.0 -2013-09-18 14:00:00,12985.0 -2013-09-18 15:00:00,13367.0 -2013-09-18 16:00:00,13439.0 -2013-09-18 17:00:00,13436.0 -2013-09-18 18:00:00,13380.0 -2013-09-18 19:00:00,13206.0 -2013-09-18 20:00:00,12938.0 -2013-09-18 21:00:00,13180.0 -2013-09-18 22:00:00,12975.0 -2013-09-18 23:00:00,12359.0 -2013-09-19 00:00:00,11469.0 -2013-09-17 01:00:00,9159.0 -2013-09-17 02:00:00,8622.0 -2013-09-17 03:00:00,8338.0 -2013-09-17 04:00:00,8154.0 -2013-09-17 05:00:00,8099.0 -2013-09-17 06:00:00,8290.0 -2013-09-17 07:00:00,8928.0 -2013-09-17 08:00:00,9865.0 -2013-09-17 09:00:00,10444.0 -2013-09-17 10:00:00,10829.0 -2013-09-17 11:00:00,11120.0 -2013-09-17 12:00:00,11440.0 -2013-09-17 13:00:00,11581.0 -2013-09-17 14:00:00,11688.0 -2013-09-17 15:00:00,11795.0 -2013-09-17 16:00:00,11856.0 -2013-09-17 17:00:00,11746.0 -2013-09-17 18:00:00,11593.0 -2013-09-17 19:00:00,11448.0 -2013-09-17 20:00:00,11436.0 -2013-09-17 21:00:00,11930.0 -2013-09-17 22:00:00,11747.0 -2013-09-17 23:00:00,11153.0 -2013-09-18 00:00:00,10334.0 -2013-09-16 01:00:00,8718.0 -2013-09-16 02:00:00,8353.0 -2013-09-16 03:00:00,8139.0 -2013-09-16 04:00:00,8003.0 -2013-09-16 05:00:00,7999.0 -2013-09-16 06:00:00,8250.0 -2013-09-16 07:00:00,8896.0 -2013-09-16 08:00:00,9970.0 -2013-09-16 09:00:00,10548.0 -2013-09-16 10:00:00,10843.0 -2013-09-16 11:00:00,11028.0 -2013-09-16 12:00:00,11275.0 -2013-09-16 13:00:00,11409.0 -2013-09-16 14:00:00,11480.0 -2013-09-16 15:00:00,11577.0 -2013-09-16 16:00:00,11536.0 -2013-09-16 17:00:00,11455.0 -2013-09-16 18:00:00,11344.0 -2013-09-16 19:00:00,11140.0 -2013-09-16 20:00:00,10973.0 -2013-09-16 21:00:00,11466.0 -2013-09-16 22:00:00,11346.0 -2013-09-16 23:00:00,10796.0 -2013-09-17 00:00:00,9966.0 -2013-09-15 01:00:00,8916.0 -2013-09-15 02:00:00,8468.0 -2013-09-15 03:00:00,8105.0 -2013-09-15 04:00:00,7868.0 -2013-09-15 05:00:00,7802.0 -2013-09-15 06:00:00,7734.0 -2013-09-15 07:00:00,7816.0 -2013-09-15 08:00:00,7939.0 -2013-09-15 09:00:00,8131.0 -2013-09-15 10:00:00,8505.0 -2013-09-15 11:00:00,8957.0 -2013-09-15 12:00:00,9215.0 -2013-09-15 13:00:00,9444.0 -2013-09-15 14:00:00,9475.0 -2013-09-15 15:00:00,9497.0 -2013-09-15 16:00:00,9543.0 -2013-09-15 17:00:00,9573.0 -2013-09-15 18:00:00,9683.0 -2013-09-15 19:00:00,9771.0 -2013-09-15 20:00:00,9943.0 -2013-09-15 21:00:00,10297.0 -2013-09-15 22:00:00,10196.0 -2013-09-15 23:00:00,9796.0 -2013-09-16 00:00:00,9290.0 -2013-09-14 01:00:00,9375.0 -2013-09-14 02:00:00,8764.0 -2013-09-14 03:00:00,8398.0 -2013-09-14 04:00:00,8136.0 -2013-09-14 05:00:00,8025.0 -2013-09-14 06:00:00,8020.0 -2013-09-14 07:00:00,8230.0 -2013-09-14 08:00:00,8488.0 -2013-09-14 09:00:00,8738.0 -2013-09-14 10:00:00,9270.0 -2013-09-14 11:00:00,9628.0 -2013-09-14 12:00:00,10007.0 -2013-09-14 13:00:00,10133.0 -2013-09-14 14:00:00,10192.0 -2013-09-14 15:00:00,10187.0 -2013-09-14 16:00:00,10215.0 -2013-09-14 17:00:00,10232.0 -2013-09-14 18:00:00,10216.0 -2013-09-14 19:00:00,10123.0 -2013-09-14 20:00:00,10053.0 -2013-09-14 21:00:00,10452.0 -2013-09-14 22:00:00,10388.0 -2013-09-14 23:00:00,10038.0 -2013-09-15 00:00:00,9477.0 -2013-09-13 01:00:00,10599.0 -2013-09-13 02:00:00,9892.0 -2013-09-13 03:00:00,9404.0 -2013-09-13 04:00:00,9064.0 -2013-09-13 05:00:00,8877.0 -2013-09-13 06:00:00,8910.0 -2013-09-13 07:00:00,9376.0 -2013-09-13 08:00:00,10149.0 -2013-09-13 09:00:00,10636.0 -2013-09-13 10:00:00,11013.0 -2013-09-13 11:00:00,11308.0 -2013-09-13 12:00:00,11644.0 -2013-09-13 13:00:00,11801.0 -2013-09-13 14:00:00,11881.0 -2013-09-13 15:00:00,11941.0 -2013-09-13 16:00:00,11921.0 -2013-09-13 17:00:00,11842.0 -2013-09-13 18:00:00,11687.0 -2013-09-13 19:00:00,11431.0 -2013-09-13 20:00:00,11022.0 -2013-09-13 21:00:00,11353.0 -2013-09-13 22:00:00,11236.0 -2013-09-13 23:00:00,10804.0 -2013-09-14 00:00:00,10103.0 -2013-09-12 01:00:00,13327.0 -2013-09-12 02:00:00,12402.0 -2013-09-12 03:00:00,11653.0 -2013-09-12 04:00:00,11146.0 -2013-09-12 05:00:00,10876.0 -2013-09-12 06:00:00,10887.0 -2013-09-12 07:00:00,11473.0 -2013-09-12 08:00:00,12439.0 -2013-09-12 09:00:00,13086.0 -2013-09-12 10:00:00,13786.0 -2013-09-12 11:00:00,14566.0 -2013-09-12 12:00:00,15326.0 -2013-09-12 13:00:00,15834.0 -2013-09-12 14:00:00,16183.0 -2013-09-12 15:00:00,16376.0 -2013-09-12 16:00:00,16407.0 -2013-09-12 17:00:00,16162.0 -2013-09-12 18:00:00,15621.0 -2013-09-12 19:00:00,14818.0 -2013-09-12 20:00:00,13902.0 -2013-09-12 21:00:00,13835.0 -2013-09-12 22:00:00,13512.0 -2013-09-12 23:00:00,12719.0 -2013-09-13 00:00:00,11663.0 -2013-09-11 01:00:00,15302.0 -2013-09-11 02:00:00,14159.0 -2013-09-11 03:00:00,13380.0 -2013-09-11 04:00:00,12808.0 -2013-09-11 05:00:00,12403.0 -2013-09-11 06:00:00,12347.0 -2013-09-11 07:00:00,12926.0 -2013-09-11 08:00:00,13897.0 -2013-09-11 09:00:00,14750.0 -2013-09-11 10:00:00,15625.0 -2013-09-11 11:00:00,16506.0 -2013-09-11 12:00:00,17509.0 -2013-09-11 13:00:00,18412.0 -2013-09-11 14:00:00,19157.0 -2013-09-11 15:00:00,19649.0 -2013-09-11 16:00:00,19353.0 -2013-09-11 17:00:00,18743.0 -2013-09-11 18:00:00,18227.0 -2013-09-11 19:00:00,17860.0 -2013-09-11 20:00:00,17315.0 -2013-09-11 21:00:00,17363.0 -2013-09-11 22:00:00,16984.0 -2013-09-11 23:00:00,16015.0 -2013-09-12 00:00:00,14666.0 -2013-09-10 01:00:00,15941.0 -2013-09-10 02:00:00,14771.0 -2013-09-10 03:00:00,13945.0 -2013-09-10 04:00:00,13288.0 -2013-09-10 05:00:00,12824.0 -2013-09-10 06:00:00,12736.0 -2013-09-10 07:00:00,13244.0 -2013-09-10 08:00:00,14154.0 -2013-09-10 09:00:00,14941.0 -2013-09-10 10:00:00,15985.0 -2013-09-10 11:00:00,17169.0 -2013-09-10 12:00:00,18369.0 -2013-09-10 13:00:00,19398.0 -2013-09-10 14:00:00,20215.0 -2013-09-10 15:00:00,20888.0 -2013-09-10 16:00:00,21315.0 -2013-09-10 17:00:00,21572.0 -2013-09-10 18:00:00,21550.0 -2013-09-10 19:00:00,21132.0 -2013-09-10 20:00:00,20319.0 -2013-09-10 21:00:00,20046.0 -2013-09-10 22:00:00,19489.0 -2013-09-10 23:00:00,18321.0 -2013-09-11 00:00:00,16762.0 -2013-09-09 01:00:00,11147.0 -2013-09-09 02:00:00,10479.0 -2013-09-09 03:00:00,10090.0 -2013-09-09 04:00:00,9848.0 -2013-09-09 05:00:00,9693.0 -2013-09-09 06:00:00,9984.0 -2013-09-09 07:00:00,10829.0 -2013-09-09 08:00:00,12035.0 -2013-09-09 09:00:00,12975.0 -2013-09-09 10:00:00,13908.0 -2013-09-09 11:00:00,14980.0 -2013-09-09 12:00:00,16187.0 -2013-09-09 13:00:00,17377.0 -2013-09-09 14:00:00,18455.0 -2013-09-09 15:00:00,19483.0 -2013-09-09 16:00:00,20246.0 -2013-09-09 17:00:00,20883.0 -2013-09-09 18:00:00,21256.0 -2013-09-09 19:00:00,21081.0 -2013-09-09 20:00:00,20478.0 -2013-09-09 21:00:00,20381.0 -2013-09-09 22:00:00,19974.0 -2013-09-09 23:00:00,18939.0 -2013-09-10 00:00:00,17422.0 -2013-09-08 01:00:00,12014.0 -2013-09-08 02:00:00,11330.0 -2013-09-08 03:00:00,10759.0 -2013-09-08 04:00:00,10294.0 -2013-09-08 05:00:00,10001.0 -2013-09-08 06:00:00,9808.0 -2013-09-08 07:00:00,9836.0 -2013-09-08 08:00:00,10020.0 -2013-09-08 09:00:00,10173.0 -2013-09-08 10:00:00,10709.0 -2013-09-08 11:00:00,11295.0 -2013-09-08 12:00:00,11870.0 -2013-09-08 13:00:00,12629.0 -2013-09-08 14:00:00,13072.0 -2013-09-08 15:00:00,13235.0 -2013-09-08 16:00:00,13278.0 -2013-09-08 17:00:00,13288.0 -2013-09-08 18:00:00,13360.0 -2013-09-08 19:00:00,13252.0 -2013-09-08 20:00:00,12968.0 -2013-09-08 21:00:00,13243.0 -2013-09-08 22:00:00,13299.0 -2013-09-08 23:00:00,12844.0 -2013-09-09 00:00:00,11988.0 -2013-09-07 01:00:00,12200.0 -2013-09-07 02:00:00,11266.0 -2013-09-07 03:00:00,10587.0 -2013-09-07 04:00:00,10101.0 -2013-09-07 05:00:00,9785.0 -2013-09-07 06:00:00,9688.0 -2013-09-07 07:00:00,9867.0 -2013-09-07 08:00:00,10094.0 -2013-09-07 09:00:00,10518.0 -2013-09-07 10:00:00,11408.0 -2013-09-07 11:00:00,12215.0 -2013-09-07 12:00:00,13189.0 -2013-09-07 13:00:00,14161.0 -2013-09-07 14:00:00,14849.0 -2013-09-07 15:00:00,15116.0 -2013-09-07 16:00:00,15133.0 -2013-09-07 17:00:00,15279.0 -2013-09-07 18:00:00,15206.0 -2013-09-07 19:00:00,15088.0 -2013-09-07 20:00:00,14633.0 -2013-09-07 21:00:00,14592.0 -2013-09-07 22:00:00,14446.0 -2013-09-07 23:00:00,13829.0 -2013-09-08 00:00:00,12960.0 -2013-09-06 01:00:00,10292.0 -2013-09-06 02:00:00,9634.0 -2013-09-06 03:00:00,9222.0 -2013-09-06 04:00:00,8955.0 -2013-09-06 05:00:00,8844.0 -2013-09-06 06:00:00,8997.0 -2013-09-06 07:00:00,9615.0 -2013-09-06 08:00:00,10516.0 -2013-09-06 09:00:00,11230.0 -2013-09-06 10:00:00,11776.0 -2013-09-06 11:00:00,12266.0 -2013-09-06 12:00:00,12862.0 -2013-09-06 13:00:00,13376.0 -2013-09-06 14:00:00,13933.0 -2013-09-06 15:00:00,14651.0 -2013-09-06 16:00:00,15187.0 -2013-09-06 17:00:00,15681.0 -2013-09-06 18:00:00,15973.0 -2013-09-06 19:00:00,15878.0 -2013-09-06 20:00:00,15410.0 -2013-09-06 21:00:00,15263.0 -2013-09-06 22:00:00,14993.0 -2013-09-06 23:00:00,14366.0 -2013-09-07 00:00:00,13311.0 -2013-09-05 01:00:00,11224.0 -2013-09-05 02:00:00,10450.0 -2013-09-05 03:00:00,9976.0 -2013-09-05 04:00:00,9722.0 -2013-09-05 05:00:00,9587.0 -2013-09-05 06:00:00,9796.0 -2013-09-05 07:00:00,10459.0 -2013-09-05 08:00:00,11551.0 -2013-09-05 09:00:00,12256.0 -2013-09-05 10:00:00,12639.0 -2013-09-05 11:00:00,12900.0 -2013-09-05 12:00:00,13208.0 -2013-09-05 13:00:00,13430.0 -2013-09-05 14:00:00,13642.0 -2013-09-05 15:00:00,13943.0 -2013-09-05 16:00:00,14079.0 -2013-09-05 17:00:00,14138.0 -2013-09-05 18:00:00,14046.0 -2013-09-05 19:00:00,13686.0 -2013-09-05 20:00:00,13049.0 -2013-09-05 21:00:00,12927.0 -2013-09-05 22:00:00,12831.0 -2013-09-05 23:00:00,12099.0 -2013-09-06 00:00:00,11184.0 -2013-09-04 01:00:00,10231.0 -2013-09-04 02:00:00,9584.0 -2013-09-04 03:00:00,9119.0 -2013-09-04 04:00:00,8856.0 -2013-09-04 05:00:00,8740.0 -2013-09-04 06:00:00,8914.0 -2013-09-04 07:00:00,9538.0 -2013-09-04 08:00:00,10419.0 -2013-09-04 09:00:00,11198.0 -2013-09-04 10:00:00,11874.0 -2013-09-04 11:00:00,12478.0 -2013-09-04 12:00:00,13020.0 -2013-09-04 13:00:00,13447.0 -2013-09-04 14:00:00,13785.0 -2013-09-04 15:00:00,14313.0 -2013-09-04 16:00:00,14705.0 -2013-09-04 17:00:00,15032.0 -2013-09-04 18:00:00,15259.0 -2013-09-04 19:00:00,15135.0 -2013-09-04 20:00:00,14563.0 -2013-09-04 21:00:00,14487.0 -2013-09-04 22:00:00,14348.0 -2013-09-04 23:00:00,13516.0 -2013-09-05 00:00:00,12373.0 -2013-09-03 01:00:00,9599.0 -2013-09-03 02:00:00,8996.0 -2013-09-03 03:00:00,8637.0 -2013-09-03 04:00:00,8435.0 -2013-09-03 05:00:00,8370.0 -2013-09-03 06:00:00,8565.0 -2013-09-03 07:00:00,9215.0 -2013-09-03 08:00:00,10172.0 -2013-09-03 09:00:00,10965.0 -2013-09-03 10:00:00,11689.0 -2013-09-03 11:00:00,12259.0 -2013-09-03 12:00:00,12653.0 -2013-09-03 13:00:00,12965.0 -2013-09-03 14:00:00,13186.0 -2013-09-03 15:00:00,13527.0 -2013-09-03 16:00:00,13771.0 -2013-09-03 17:00:00,13974.0 -2013-09-03 18:00:00,13843.0 -2013-09-03 19:00:00,13535.0 -2013-09-03 20:00:00,13028.0 -2013-09-03 21:00:00,13063.0 -2013-09-03 22:00:00,13045.0 -2013-09-03 23:00:00,12310.0 -2013-09-04 00:00:00,11242.0 -2013-09-02 01:00:00,11495.0 -2013-09-02 02:00:00,10670.0 -2013-09-02 03:00:00,10011.0 -2013-09-02 04:00:00,9607.0 -2013-09-02 05:00:00,9301.0 -2013-09-02 06:00:00,9192.0 -2013-09-02 07:00:00,9282.0 -2013-09-02 08:00:00,9243.0 -2013-09-02 09:00:00,9504.0 -2013-09-02 10:00:00,10216.0 -2013-09-02 11:00:00,10945.0 -2013-09-02 12:00:00,11604.0 -2013-09-02 13:00:00,12007.0 -2013-09-02 14:00:00,12378.0 -2013-09-02 15:00:00,12620.0 -2013-09-02 16:00:00,12747.0 -2013-09-02 17:00:00,12734.0 -2013-09-02 18:00:00,12620.0 -2013-09-02 19:00:00,12323.0 -2013-09-02 20:00:00,11835.0 -2013-09-02 21:00:00,11783.0 -2013-09-02 22:00:00,11849.0 -2013-09-02 23:00:00,11312.0 -2013-09-03 00:00:00,10439.0 -2013-09-01 01:00:00,11161.0 -2013-09-01 02:00:00,10435.0 -2013-09-01 03:00:00,9946.0 -2013-09-01 04:00:00,9542.0 -2013-09-01 05:00:00,9248.0 -2013-09-01 06:00:00,9181.0 -2013-09-01 07:00:00,9149.0 -2013-09-01 08:00:00,9148.0 -2013-09-01 09:00:00,9433.0 -2013-09-01 10:00:00,10348.0 -2013-09-01 11:00:00,11279.0 -2013-09-01 12:00:00,12227.0 -2013-09-01 13:00:00,12862.0 -2013-09-01 14:00:00,13526.0 -2013-09-01 15:00:00,14169.0 -2013-09-01 16:00:00,14690.0 -2013-09-01 17:00:00,15099.0 -2013-09-01 18:00:00,15172.0 -2013-09-01 19:00:00,15055.0 -2013-09-01 20:00:00,14590.0 -2013-09-01 21:00:00,14444.0 -2013-09-01 22:00:00,14446.0 -2013-09-01 23:00:00,13687.0 -2013-09-02 00:00:00,12527.0 -2013-08-31 01:00:00,12789.0 -2013-08-31 02:00:00,11898.0 -2013-08-31 03:00:00,11190.0 -2013-08-31 04:00:00,10634.0 -2013-08-31 05:00:00,10395.0 -2013-08-31 06:00:00,10251.0 -2013-08-31 07:00:00,10440.0 -2013-08-31 08:00:00,10578.0 -2013-08-31 09:00:00,10974.0 -2013-08-31 10:00:00,11712.0 -2013-08-31 11:00:00,12326.0 -2013-08-31 12:00:00,12665.0 -2013-08-31 13:00:00,12834.0 -2013-08-31 14:00:00,12895.0 -2013-08-31 15:00:00,13057.0 -2013-08-31 16:00:00,13276.0 -2013-08-31 17:00:00,13409.0 -2013-08-31 18:00:00,13262.0 -2013-08-31 19:00:00,13034.0 -2013-08-31 20:00:00,12685.0 -2013-08-31 21:00:00,12801.0 -2013-08-31 22:00:00,12895.0 -2013-08-31 23:00:00,12565.0 -2013-09-01 00:00:00,11921.0 -2013-08-30 01:00:00,13268.0 -2013-08-30 02:00:00,12223.0 -2013-08-30 03:00:00,11518.0 -2013-08-30 04:00:00,10991.0 -2013-08-30 05:00:00,10729.0 -2013-08-30 06:00:00,10823.0 -2013-08-30 07:00:00,11509.0 -2013-08-30 08:00:00,12460.0 -2013-08-30 09:00:00,13579.0 -2013-08-30 10:00:00,14740.0 -2013-08-30 11:00:00,16010.0 -2013-08-30 12:00:00,17298.0 -2013-08-30 13:00:00,18561.0 -2013-08-30 14:00:00,19688.0 -2013-08-30 15:00:00,20637.0 -2013-08-30 16:00:00,21204.0 -2013-08-30 17:00:00,21488.0 -2013-08-30 18:00:00,21519.0 -2013-08-30 19:00:00,20372.0 -2013-08-30 20:00:00,17922.0 -2013-08-30 21:00:00,16861.0 -2013-08-30 22:00:00,16081.0 -2013-08-30 23:00:00,15281.0 -2013-08-31 00:00:00,14093.0 -2013-08-29 01:00:00,12887.0 -2013-08-29 02:00:00,11977.0 -2013-08-29 03:00:00,11350.0 -2013-08-29 04:00:00,10933.0 -2013-08-29 05:00:00,10703.0 -2013-08-29 06:00:00,10797.0 -2013-08-29 07:00:00,11492.0 -2013-08-29 08:00:00,12542.0 -2013-08-29 09:00:00,13425.0 -2013-08-29 10:00:00,14197.0 -2013-08-29 11:00:00,15287.0 -2013-08-29 12:00:00,16446.0 -2013-08-29 13:00:00,17320.0 -2013-08-29 14:00:00,18108.0 -2013-08-29 15:00:00,18813.0 -2013-08-29 16:00:00,19274.0 -2013-08-29 17:00:00,19592.0 -2013-08-29 18:00:00,19702.0 -2013-08-29 19:00:00,19322.0 -2013-08-29 20:00:00,18404.0 -2013-08-29 21:00:00,17694.0 -2013-08-29 22:00:00,17357.0 -2013-08-29 23:00:00,16203.0 -2013-08-30 00:00:00,14684.0 -2013-08-28 01:00:00,15993.0 -2013-08-28 02:00:00,14782.0 -2013-08-28 03:00:00,13974.0 -2013-08-28 04:00:00,13326.0 -2013-08-28 05:00:00,12961.0 -2013-08-28 06:00:00,12883.0 -2013-08-28 07:00:00,13387.0 -2013-08-28 08:00:00,14317.0 -2013-08-28 09:00:00,15183.0 -2013-08-28 10:00:00,15948.0 -2013-08-28 11:00:00,16408.0 -2013-08-28 12:00:00,16820.0 -2013-08-28 13:00:00,17183.0 -2013-08-28 14:00:00,17505.0 -2013-08-28 15:00:00,18019.0 -2013-08-28 16:00:00,18323.0 -2013-08-28 17:00:00,18507.0 -2013-08-28 18:00:00,18388.0 -2013-08-28 19:00:00,17998.0 -2013-08-28 20:00:00,17308.0 -2013-08-28 21:00:00,16733.0 -2013-08-28 22:00:00,16483.0 -2013-08-28 23:00:00,15543.0 -2013-08-29 00:00:00,14178.0 -2013-08-27 01:00:00,14682.0 -2013-08-27 02:00:00,13609.0 -2013-08-27 03:00:00,12934.0 -2013-08-27 04:00:00,12440.0 -2013-08-27 05:00:00,12165.0 -2013-08-27 06:00:00,12292.0 -2013-08-27 07:00:00,12985.0 -2013-08-27 08:00:00,14026.0 -2013-08-27 09:00:00,14942.0 -2013-08-27 10:00:00,15780.0 -2013-08-27 11:00:00,16944.0 -2013-08-27 12:00:00,18057.0 -2013-08-27 13:00:00,19119.0 -2013-08-27 14:00:00,20112.0 -2013-08-27 15:00:00,20953.0 -2013-08-27 16:00:00,21495.0 -2013-08-27 17:00:00,21843.0 -2013-08-27 18:00:00,21960.0 -2013-08-27 19:00:00,21716.0 -2013-08-27 20:00:00,21076.0 -2013-08-27 21:00:00,20613.0 -2013-08-27 22:00:00,20392.0 -2013-08-27 23:00:00,19281.0 -2013-08-28 00:00:00,17580.0 -2013-08-26 01:00:00,13027.0 -2013-08-26 02:00:00,12011.0 -2013-08-26 03:00:00,11373.0 -2013-08-26 04:00:00,10955.0 -2013-08-26 05:00:00,10656.0 -2013-08-26 06:00:00,10718.0 -2013-08-26 07:00:00,11463.0 -2013-08-26 08:00:00,12509.0 -2013-08-26 09:00:00,13462.0 -2013-08-26 10:00:00,14287.0 -2013-08-26 11:00:00,14969.0 -2013-08-26 12:00:00,16171.0 -2013-08-26 13:00:00,17241.0 -2013-08-26 14:00:00,18364.0 -2013-08-26 15:00:00,19327.0 -2013-08-26 16:00:00,19882.0 -2013-08-26 17:00:00,20351.0 -2013-08-26 18:00:00,20460.0 -2013-08-26 19:00:00,20154.0 -2013-08-26 20:00:00,19611.0 -2013-08-26 21:00:00,19145.0 -2013-08-26 22:00:00,18888.0 -2013-08-26 23:00:00,17775.0 -2013-08-27 00:00:00,16212.0 -2013-08-25 01:00:00,11543.0 -2013-08-25 02:00:00,10704.0 -2013-08-25 03:00:00,10032.0 -2013-08-25 04:00:00,9577.0 -2013-08-25 05:00:00,9261.0 -2013-08-25 06:00:00,9134.0 -2013-08-25 07:00:00,9075.0 -2013-08-25 08:00:00,9024.0 -2013-08-25 09:00:00,9419.0 -2013-08-25 10:00:00,10308.0 -2013-08-25 11:00:00,11441.0 -2013-08-25 12:00:00,12708.0 -2013-08-25 13:00:00,13862.0 -2013-08-25 14:00:00,14854.0 -2013-08-25 15:00:00,15705.0 -2013-08-25 16:00:00,16409.0 -2013-08-25 17:00:00,16914.0 -2013-08-25 18:00:00,17236.0 -2013-08-25 19:00:00,17170.0 -2013-08-25 20:00:00,16726.0 -2013-08-25 21:00:00,16243.0 -2013-08-25 22:00:00,16232.0 -2013-08-25 23:00:00,15439.0 -2013-08-26 00:00:00,14307.0 -2013-08-24 01:00:00,11084.0 -2013-08-24 02:00:00,10234.0 -2013-08-24 03:00:00,9686.0 -2013-08-24 04:00:00,9306.0 -2013-08-24 05:00:00,9087.0 -2013-08-24 06:00:00,9002.0 -2013-08-24 07:00:00,9170.0 -2013-08-24 08:00:00,9259.0 -2013-08-24 09:00:00,9881.0 -2013-08-24 10:00:00,10735.0 -2013-08-24 11:00:00,11596.0 -2013-08-24 12:00:00,12432.0 -2013-08-24 13:00:00,13085.0 -2013-08-24 14:00:00,13797.0 -2013-08-24 15:00:00,14321.0 -2013-08-24 16:00:00,14804.0 -2013-08-24 17:00:00,15244.0 -2013-08-24 18:00:00,15387.0 -2013-08-24 19:00:00,15270.0 -2013-08-24 20:00:00,14771.0 -2013-08-24 21:00:00,14232.0 -2013-08-24 22:00:00,14140.0 -2013-08-24 23:00:00,13509.0 -2013-08-25 00:00:00,12546.0 -2013-08-23 01:00:00,11369.0 -2013-08-23 02:00:00,10539.0 -2013-08-23 03:00:00,10019.0 -2013-08-23 04:00:00,9636.0 -2013-08-23 05:00:00,9451.0 -2013-08-23 06:00:00,9595.0 -2013-08-23 07:00:00,10222.0 -2013-08-23 08:00:00,11059.0 -2013-08-23 09:00:00,12033.0 -2013-08-23 10:00:00,12871.0 -2013-08-23 11:00:00,13600.0 -2013-08-23 12:00:00,14192.0 -2013-08-23 13:00:00,14622.0 -2013-08-23 14:00:00,14962.0 -2013-08-23 15:00:00,15342.0 -2013-08-23 16:00:00,15602.0 -2013-08-23 17:00:00,15797.0 -2013-08-23 18:00:00,15772.0 -2013-08-23 19:00:00,15452.0 -2013-08-23 20:00:00,14697.0 -2013-08-23 21:00:00,13947.0 -2013-08-23 22:00:00,13721.0 -2013-08-23 23:00:00,13098.0 -2013-08-24 00:00:00,12075.0 -2013-08-22 01:00:00,14215.0 -2013-08-22 02:00:00,13111.0 -2013-08-22 03:00:00,12215.0 -2013-08-22 04:00:00,11595.0 -2013-08-22 05:00:00,11306.0 -2013-08-22 06:00:00,11379.0 -2013-08-22 07:00:00,12052.0 -2013-08-22 08:00:00,13085.0 -2013-08-22 09:00:00,13973.0 -2013-08-22 10:00:00,14370.0 -2013-08-22 11:00:00,14591.0 -2013-08-22 12:00:00,14841.0 -2013-08-22 13:00:00,14734.0 -2013-08-22 14:00:00,14502.0 -2013-08-22 15:00:00,14415.0 -2013-08-22 16:00:00,14255.0 -2013-08-22 17:00:00,14280.0 -2013-08-22 18:00:00,14221.0 -2013-08-22 19:00:00,14159.0 -2013-08-22 20:00:00,14016.0 -2013-08-22 21:00:00,13869.0 -2013-08-22 22:00:00,14078.0 -2013-08-22 23:00:00,13453.0 -2013-08-23 00:00:00,12413.0 -2013-08-21 01:00:00,12995.0 -2013-08-21 02:00:00,11993.0 -2013-08-21 03:00:00,11247.0 -2013-08-21 04:00:00,10795.0 -2013-08-21 05:00:00,10533.0 -2013-08-21 06:00:00,10607.0 -2013-08-21 07:00:00,11266.0 -2013-08-21 08:00:00,12202.0 -2013-08-21 09:00:00,13210.0 -2013-08-21 10:00:00,14196.0 -2013-08-21 11:00:00,15356.0 -2013-08-21 12:00:00,16523.0 -2013-08-21 13:00:00,17506.0 -2013-08-21 14:00:00,18210.0 -2013-08-21 15:00:00,18839.0 -2013-08-21 16:00:00,19354.0 -2013-08-21 17:00:00,19674.0 -2013-08-21 18:00:00,19741.0 -2013-08-21 19:00:00,19430.0 -2013-08-21 20:00:00,18700.0 -2013-08-21 21:00:00,18201.0 -2013-08-21 22:00:00,18057.0 -2013-08-21 23:00:00,17124.0 -2013-08-22 00:00:00,15627.0 -2013-08-20 01:00:00,12193.0 -2013-08-20 02:00:00,11261.0 -2013-08-20 03:00:00,10600.0 -2013-08-20 04:00:00,10207.0 -2013-08-20 05:00:00,9971.0 -2013-08-20 06:00:00,10089.0 -2013-08-20 07:00:00,10714.0 -2013-08-20 08:00:00,11568.0 -2013-08-20 09:00:00,12633.0 -2013-08-20 10:00:00,13703.0 -2013-08-20 11:00:00,14649.0 -2013-08-20 12:00:00,15593.0 -2013-08-20 13:00:00,16313.0 -2013-08-20 14:00:00,16887.0 -2013-08-20 15:00:00,17535.0 -2013-08-20 16:00:00,17866.0 -2013-08-20 17:00:00,18095.0 -2013-08-20 18:00:00,18187.0 -2013-08-20 19:00:00,18018.0 -2013-08-20 20:00:00,17436.0 -2013-08-20 21:00:00,16831.0 -2013-08-20 22:00:00,16713.0 -2013-08-20 23:00:00,15802.0 -2013-08-21 00:00:00,14428.0 -2013-08-19 01:00:00,10605.0 -2013-08-19 02:00:00,9901.0 -2013-08-19 03:00:00,9418.0 -2013-08-19 04:00:00,9088.0 -2013-08-19 05:00:00,9025.0 -2013-08-19 06:00:00,9166.0 -2013-08-19 07:00:00,9872.0 -2013-08-19 08:00:00,10671.0 -2013-08-19 09:00:00,11742.0 -2013-08-19 10:00:00,12638.0 -2013-08-19 11:00:00,13461.0 -2013-08-19 12:00:00,14338.0 -2013-08-19 13:00:00,14985.0 -2013-08-19 14:00:00,15535.0 -2013-08-19 15:00:00,16086.0 -2013-08-19 16:00:00,16487.0 -2013-08-19 17:00:00,16609.0 -2013-08-19 18:00:00,16441.0 -2013-08-19 19:00:00,16212.0 -2013-08-19 20:00:00,15752.0 -2013-08-19 21:00:00,15398.0 -2013-08-19 22:00:00,15484.0 -2013-08-19 23:00:00,14731.0 -2013-08-20 00:00:00,13440.0 -2013-08-18 01:00:00,10258.0 -2013-08-18 02:00:00,9597.0 -2013-08-18 03:00:00,9120.0 -2013-08-18 04:00:00,8792.0 -2013-08-18 05:00:00,8540.0 -2013-08-18 06:00:00,8453.0 -2013-08-18 07:00:00,8442.0 -2013-08-18 08:00:00,8269.0 -2013-08-18 09:00:00,8645.0 -2013-08-18 10:00:00,9416.0 -2013-08-18 11:00:00,10201.0 -2013-08-18 12:00:00,10967.0 -2013-08-18 13:00:00,11536.0 -2013-08-18 14:00:00,12043.0 -2013-08-18 15:00:00,12416.0 -2013-08-18 16:00:00,12853.0 -2013-08-18 17:00:00,13102.0 -2013-08-18 18:00:00,13324.0 -2013-08-18 19:00:00,13317.0 -2013-08-18 20:00:00,13032.0 -2013-08-18 21:00:00,12686.0 -2013-08-18 22:00:00,12848.0 -2013-08-18 23:00:00,12340.0 -2013-08-19 00:00:00,11567.0 -2013-08-17 01:00:00,10604.0 -2013-08-17 02:00:00,9831.0 -2013-08-17 03:00:00,9283.0 -2013-08-17 04:00:00,8967.0 -2013-08-17 05:00:00,8800.0 -2013-08-17 06:00:00,8774.0 -2013-08-17 07:00:00,8908.0 -2013-08-17 08:00:00,8963.0 -2013-08-17 09:00:00,9556.0 -2013-08-17 10:00:00,10405.0 -2013-08-17 11:00:00,11101.0 -2013-08-17 12:00:00,11712.0 -2013-08-17 13:00:00,12073.0 -2013-08-17 14:00:00,12343.0 -2013-08-17 15:00:00,12520.0 -2013-08-17 16:00:00,12712.0 -2013-08-17 17:00:00,12862.0 -2013-08-17 18:00:00,12993.0 -2013-08-17 19:00:00,12863.0 -2013-08-17 20:00:00,12447.0 -2013-08-17 21:00:00,12035.0 -2013-08-17 22:00:00,12115.0 -2013-08-17 23:00:00,11715.0 -2013-08-18 00:00:00,10998.0 -2013-08-16 01:00:00,10152.0 -2013-08-16 02:00:00,9502.0 -2013-08-16 03:00:00,9059.0 -2013-08-16 04:00:00,8750.0 -2013-08-16 05:00:00,8683.0 -2013-08-16 06:00:00,8766.0 -2013-08-16 07:00:00,9272.0 -2013-08-16 08:00:00,9953.0 -2013-08-16 09:00:00,10807.0 -2013-08-16 10:00:00,11541.0 -2013-08-16 11:00:00,12192.0 -2013-08-16 12:00:00,12782.0 -2013-08-16 13:00:00,13186.0 -2013-08-16 14:00:00,13433.0 -2013-08-16 15:00:00,13770.0 -2013-08-16 16:00:00,13957.0 -2013-08-16 17:00:00,14090.0 -2013-08-16 18:00:00,14092.0 -2013-08-16 19:00:00,13865.0 -2013-08-16 20:00:00,13378.0 -2013-08-16 21:00:00,12867.0 -2013-08-16 22:00:00,12895.0 -2013-08-16 23:00:00,12427.0 -2013-08-17 00:00:00,11551.0 -2013-08-15 01:00:00,9936.0 -2013-08-15 02:00:00,9296.0 -2013-08-15 03:00:00,8893.0 -2013-08-15 04:00:00,8600.0 -2013-08-15 05:00:00,8476.0 -2013-08-15 06:00:00,8651.0 -2013-08-15 07:00:00,9179.0 -2013-08-15 08:00:00,9812.0 -2013-08-15 09:00:00,10672.0 -2013-08-15 10:00:00,11330.0 -2013-08-15 11:00:00,11824.0 -2013-08-15 12:00:00,12286.0 -2013-08-15 13:00:00,12601.0 -2013-08-15 14:00:00,12784.0 -2013-08-15 15:00:00,12894.0 -2013-08-15 16:00:00,12833.0 -2013-08-15 17:00:00,12722.0 -2013-08-15 18:00:00,12599.0 -2013-08-15 19:00:00,12392.0 -2013-08-15 20:00:00,12086.0 -2013-08-15 21:00:00,12010.0 -2013-08-15 22:00:00,12309.0 -2013-08-15 23:00:00,11900.0 -2013-08-16 00:00:00,11064.0 -2013-08-14 01:00:00,10009.0 -2013-08-14 02:00:00,9375.0 -2013-08-14 03:00:00,8953.0 -2013-08-14 04:00:00,8680.0 -2013-08-14 05:00:00,8548.0 -2013-08-14 06:00:00,8716.0 -2013-08-14 07:00:00,9284.0 -2013-08-14 08:00:00,9920.0 -2013-08-14 09:00:00,10757.0 -2013-08-14 10:00:00,11360.0 -2013-08-14 11:00:00,11799.0 -2013-08-14 12:00:00,12172.0 -2013-08-14 13:00:00,12377.0 -2013-08-14 14:00:00,12503.0 -2013-08-14 15:00:00,12651.0 -2013-08-14 16:00:00,12712.0 -2013-08-14 17:00:00,12682.0 -2013-08-14 18:00:00,12653.0 -2013-08-14 19:00:00,12510.0 -2013-08-14 20:00:00,12125.0 -2013-08-14 21:00:00,11892.0 -2013-08-14 22:00:00,12138.0 -2013-08-14 23:00:00,11666.0 -2013-08-15 00:00:00,10836.0 -2013-08-13 01:00:00,11966.0 -2013-08-13 02:00:00,11076.0 -2013-08-13 03:00:00,10374.0 -2013-08-13 04:00:00,9911.0 -2013-08-13 05:00:00,9599.0 -2013-08-13 06:00:00,9622.0 -2013-08-13 07:00:00,10042.0 -2013-08-13 08:00:00,10625.0 -2013-08-13 09:00:00,11365.0 -2013-08-13 10:00:00,11976.0 -2013-08-13 11:00:00,12398.0 -2013-08-13 12:00:00,12766.0 -2013-08-13 13:00:00,12930.0 -2013-08-13 14:00:00,12979.0 -2013-08-13 15:00:00,13120.0 -2013-08-13 16:00:00,13108.0 -2013-08-13 17:00:00,12977.0 -2013-08-13 18:00:00,12806.0 -2013-08-13 19:00:00,12573.0 -2013-08-13 20:00:00,12167.0 -2013-08-13 21:00:00,11870.0 -2013-08-13 22:00:00,12113.0 -2013-08-13 23:00:00,11728.0 -2013-08-14 00:00:00,10907.0 -2013-08-12 01:00:00,11236.0 -2013-08-12 02:00:00,10540.0 -2013-08-12 03:00:00,10050.0 -2013-08-12 04:00:00,9758.0 -2013-08-12 05:00:00,9613.0 -2013-08-12 06:00:00,9831.0 -2013-08-12 07:00:00,10572.0 -2013-08-12 08:00:00,11471.0 -2013-08-12 09:00:00,12381.0 -2013-08-12 10:00:00,13264.0 -2013-08-12 11:00:00,14089.0 -2013-08-12 12:00:00,14961.0 -2013-08-12 13:00:00,15605.0 -2013-08-12 14:00:00,16185.0 -2013-08-12 15:00:00,16687.0 -2013-08-12 16:00:00,16649.0 -2013-08-12 17:00:00,16367.0 -2013-08-12 18:00:00,16221.0 -2013-08-12 19:00:00,16084.0 -2013-08-12 20:00:00,15621.0 -2013-08-12 21:00:00,15071.0 -2013-08-12 22:00:00,15032.0 -2013-08-12 23:00:00,14379.0 -2013-08-13 00:00:00,13191.0 -2013-08-11 01:00:00,10710.0 -2013-08-11 02:00:00,9986.0 -2013-08-11 03:00:00,9443.0 -2013-08-11 04:00:00,9091.0 -2013-08-11 05:00:00,8808.0 -2013-08-11 06:00:00,8677.0 -2013-08-11 07:00:00,8630.0 -2013-08-11 08:00:00,8482.0 -2013-08-11 09:00:00,8887.0 -2013-08-11 10:00:00,9675.0 -2013-08-11 11:00:00,10455.0 -2013-08-11 12:00:00,11093.0 -2013-08-11 13:00:00,11685.0 -2013-08-11 14:00:00,12014.0 -2013-08-11 15:00:00,12299.0 -2013-08-11 16:00:00,12755.0 -2013-08-11 17:00:00,13033.0 -2013-08-11 18:00:00,13145.0 -2013-08-11 19:00:00,12982.0 -2013-08-11 20:00:00,12823.0 -2013-08-11 21:00:00,12723.0 -2013-08-11 22:00:00,13024.0 -2013-08-11 23:00:00,12784.0 -2013-08-12 00:00:00,12093.0 -2013-08-10 01:00:00,12112.0 -2013-08-10 02:00:00,11246.0 -2013-08-10 03:00:00,10566.0 -2013-08-10 04:00:00,10065.0 -2013-08-10 05:00:00,9716.0 -2013-08-10 06:00:00,9590.0 -2013-08-10 07:00:00,9650.0 -2013-08-10 08:00:00,9691.0 -2013-08-10 09:00:00,10422.0 -2013-08-10 10:00:00,11439.0 -2013-08-10 11:00:00,12335.0 -2013-08-10 12:00:00,13019.0 -2013-08-10 13:00:00,13432.0 -2013-08-10 14:00:00,13733.0 -2013-08-10 15:00:00,13825.0 -2013-08-10 16:00:00,13941.0 -2013-08-10 17:00:00,13923.0 -2013-08-10 18:00:00,13852.0 -2013-08-10 19:00:00,13588.0 -2013-08-10 20:00:00,13042.0 -2013-08-10 21:00:00,12538.0 -2013-08-10 22:00:00,12570.0 -2013-08-10 23:00:00,12250.0 -2013-08-11 00:00:00,11523.0 -2013-08-09 01:00:00,11056.0 -2013-08-09 02:00:00,10329.0 -2013-08-09 03:00:00,9803.0 -2013-08-09 04:00:00,9441.0 -2013-08-09 05:00:00,9242.0 -2013-08-09 06:00:00,9334.0 -2013-08-09 07:00:00,9835.0 -2013-08-09 08:00:00,10405.0 -2013-08-09 09:00:00,11442.0 -2013-08-09 10:00:00,12271.0 -2013-08-09 11:00:00,13022.0 -2013-08-09 12:00:00,13844.0 -2013-08-09 13:00:00,14481.0 -2013-08-09 14:00:00,14934.0 -2013-08-09 15:00:00,15425.0 -2013-08-09 16:00:00,15675.0 -2013-08-09 17:00:00,15703.0 -2013-08-09 18:00:00,15635.0 -2013-08-09 19:00:00,15322.0 -2013-08-09 20:00:00,14854.0 -2013-08-09 21:00:00,14491.0 -2013-08-09 22:00:00,14553.0 -2013-08-09 23:00:00,14165.0 -2013-08-10 00:00:00,13184.0 -2013-08-08 01:00:00,12245.0 -2013-08-08 02:00:00,11238.0 -2013-08-08 03:00:00,10568.0 -2013-08-08 04:00:00,10132.0 -2013-08-08 05:00:00,9886.0 -2013-08-08 06:00:00,9964.0 -2013-08-08 07:00:00,10511.0 -2013-08-08 08:00:00,11251.0 -2013-08-08 09:00:00,12211.0 -2013-08-08 10:00:00,12933.0 -2013-08-08 11:00:00,13558.0 -2013-08-08 12:00:00,14118.0 -2013-08-08 13:00:00,14505.0 -2013-08-08 14:00:00,14836.0 -2013-08-08 15:00:00,15102.0 -2013-08-08 16:00:00,15224.0 -2013-08-08 17:00:00,15262.0 -2013-08-08 18:00:00,15180.0 -2013-08-08 19:00:00,14777.0 -2013-08-08 20:00:00,13974.0 -2013-08-08 21:00:00,13434.0 -2013-08-08 22:00:00,13517.0 -2013-08-08 23:00:00,13000.0 -2013-08-09 00:00:00,12062.0 -2013-08-07 01:00:00,12823.0 -2013-08-07 02:00:00,11875.0 -2013-08-07 03:00:00,11234.0 -2013-08-07 04:00:00,10764.0 -2013-08-07 05:00:00,10578.0 -2013-08-07 06:00:00,10718.0 -2013-08-07 07:00:00,11306.0 -2013-08-07 08:00:00,12194.0 -2013-08-07 09:00:00,13326.0 -2013-08-07 10:00:00,14528.0 -2013-08-07 11:00:00,15711.0 -2013-08-07 12:00:00,16791.0 -2013-08-07 13:00:00,17553.0 -2013-08-07 14:00:00,18003.0 -2013-08-07 15:00:00,18483.0 -2013-08-07 16:00:00,18613.0 -2013-08-07 17:00:00,18514.0 -2013-08-07 18:00:00,18497.0 -2013-08-07 19:00:00,18230.0 -2013-08-07 20:00:00,17324.0 -2013-08-07 21:00:00,16211.0 -2013-08-07 22:00:00,15672.0 -2013-08-07 23:00:00,14792.0 -2013-08-08 00:00:00,13537.0 -2013-08-06 01:00:00,10596.0 -2013-08-06 02:00:00,10023.0 -2013-08-06 03:00:00,9589.0 -2013-08-06 04:00:00,9339.0 -2013-08-06 05:00:00,9237.0 -2013-08-06 06:00:00,9441.0 -2013-08-06 07:00:00,10043.0 -2013-08-06 08:00:00,10909.0 -2013-08-06 09:00:00,11722.0 -2013-08-06 10:00:00,12423.0 -2013-08-06 11:00:00,12964.0 -2013-08-06 12:00:00,13521.0 -2013-08-06 13:00:00,14022.0 -2013-08-06 14:00:00,14570.0 -2013-08-06 15:00:00,15170.0 -2013-08-06 16:00:00,15467.0 -2013-08-06 17:00:00,15863.0 -2013-08-06 18:00:00,16307.0 -2013-08-06 19:00:00,16188.0 -2013-08-06 20:00:00,15773.0 -2013-08-06 21:00:00,15492.0 -2013-08-06 22:00:00,15549.0 -2013-08-06 23:00:00,15059.0 -2013-08-07 00:00:00,13993.0 -2013-08-05 01:00:00,9880.0 -2013-08-05 02:00:00,9292.0 -2013-08-05 03:00:00,8953.0 -2013-08-05 04:00:00,8713.0 -2013-08-05 05:00:00,8606.0 -2013-08-05 06:00:00,8796.0 -2013-08-05 07:00:00,9379.0 -2013-08-05 08:00:00,10051.0 -2013-08-05 09:00:00,10968.0 -2013-08-05 10:00:00,11572.0 -2013-08-05 11:00:00,11953.0 -2013-08-05 12:00:00,12337.0 -2013-08-05 13:00:00,12608.0 -2013-08-05 14:00:00,12803.0 -2013-08-05 15:00:00,13053.0 -2013-08-05 16:00:00,13156.0 -2013-08-05 17:00:00,12959.0 -2013-08-05 18:00:00,12688.0 -2013-08-05 19:00:00,12469.0 -2013-08-05 20:00:00,12155.0 -2013-08-05 21:00:00,12122.0 -2013-08-05 22:00:00,12453.0 -2013-08-05 23:00:00,12173.0 -2013-08-06 00:00:00,11468.0 -2013-08-04 01:00:00,10248.0 -2013-08-04 02:00:00,9636.0 -2013-08-04 03:00:00,9098.0 -2013-08-04 04:00:00,8805.0 -2013-08-04 05:00:00,8489.0 -2013-08-04 06:00:00,8398.0 -2013-08-04 07:00:00,8298.0 -2013-08-04 08:00:00,8228.0 -2013-08-04 09:00:00,8661.0 -2013-08-04 10:00:00,9366.0 -2013-08-04 11:00:00,9978.0 -2013-08-04 12:00:00,10499.0 -2013-08-04 13:00:00,10894.0 -2013-08-04 14:00:00,11153.0 -2013-08-04 15:00:00,11385.0 -2013-08-04 16:00:00,11509.0 -2013-08-04 17:00:00,11700.0 -2013-08-04 18:00:00,11708.0 -2013-08-04 19:00:00,11483.0 -2013-08-04 20:00:00,11132.0 -2013-08-04 21:00:00,10989.0 -2013-08-04 22:00:00,11277.0 -2013-08-04 23:00:00,11144.0 -2013-08-05 00:00:00,10555.0 -2013-08-03 01:00:00,11838.0 -2013-08-03 02:00:00,10880.0 -2013-08-03 03:00:00,10275.0 -2013-08-03 04:00:00,9736.0 -2013-08-03 05:00:00,9464.0 -2013-08-03 06:00:00,9244.0 -2013-08-03 07:00:00,9273.0 -2013-08-03 08:00:00,9379.0 -2013-08-03 09:00:00,10130.0 -2013-08-03 10:00:00,11006.0 -2013-08-03 11:00:00,11846.0 -2013-08-03 12:00:00,12433.0 -2013-08-03 13:00:00,12795.0 -2013-08-03 14:00:00,12949.0 -2013-08-03 15:00:00,12991.0 -2013-08-03 16:00:00,13092.0 -2013-08-03 17:00:00,13160.0 -2013-08-03 18:00:00,13136.0 -2013-08-03 19:00:00,12919.0 -2013-08-03 20:00:00,12542.0 -2013-08-03 21:00:00,11997.0 -2013-08-03 22:00:00,12005.0 -2013-08-03 23:00:00,11723.0 -2013-08-04 00:00:00,11055.0 -2013-08-02 01:00:00,10840.0 -2013-08-02 02:00:00,10101.0 -2013-08-02 03:00:00,9632.0 -2013-08-02 04:00:00,9376.0 -2013-08-02 05:00:00,9193.0 -2013-08-02 06:00:00,9321.0 -2013-08-02 07:00:00,9862.0 -2013-08-02 08:00:00,10566.0 -2013-08-02 09:00:00,11318.0 -2013-08-02 10:00:00,11983.0 -2013-08-02 11:00:00,12620.0 -2013-08-02 12:00:00,13363.0 -2013-08-02 13:00:00,14111.0 -2013-08-02 14:00:00,14795.0 -2013-08-02 15:00:00,15456.0 -2013-08-02 16:00:00,15668.0 -2013-08-02 17:00:00,15734.0 -2013-08-02 18:00:00,15515.0 -2013-08-02 19:00:00,15092.0 -2013-08-02 20:00:00,14576.0 -2013-08-02 21:00:00,14225.0 -2013-08-02 22:00:00,14246.0 -2013-08-02 23:00:00,13796.0 -2013-08-03 00:00:00,12826.0 -2013-08-01 01:00:00,10640.0 -2013-08-01 02:00:00,9914.0 -2013-08-01 03:00:00,9405.0 -2013-08-01 04:00:00,9077.0 -2013-08-01 05:00:00,8934.0 -2013-08-01 06:00:00,9055.0 -2013-08-01 07:00:00,9499.0 -2013-08-01 08:00:00,10217.0 -2013-08-01 09:00:00,11221.0 -2013-08-01 10:00:00,12023.0 -2013-08-01 11:00:00,12667.0 -2013-08-01 12:00:00,13264.0 -2013-08-01 13:00:00,13558.0 -2013-08-01 14:00:00,13671.0 -2013-08-01 15:00:00,14041.0 -2013-08-01 16:00:00,14232.0 -2013-08-01 17:00:00,14322.0 -2013-08-01 18:00:00,14320.0 -2013-08-01 19:00:00,14176.0 -2013-08-01 20:00:00,13718.0 -2013-08-01 21:00:00,13219.0 -2013-08-01 22:00:00,13149.0 -2013-08-01 23:00:00,12821.0 -2013-08-02 00:00:00,11841.0 -2013-07-31 01:00:00,10361.0 -2013-07-31 02:00:00,9721.0 -2013-07-31 03:00:00,9348.0 -2013-07-31 04:00:00,9108.0 -2013-07-31 05:00:00,9045.0 -2013-07-31 06:00:00,9208.0 -2013-07-31 07:00:00,9800.0 -2013-07-31 08:00:00,10713.0 -2013-07-31 09:00:00,11432.0 -2013-07-31 10:00:00,12020.0 -2013-07-31 11:00:00,12360.0 -2013-07-31 12:00:00,12705.0 -2013-07-31 13:00:00,12870.0 -2013-07-31 14:00:00,12976.0 -2013-07-31 15:00:00,13085.0 -2013-07-31 16:00:00,13067.0 -2013-07-31 17:00:00,13022.0 -2013-07-31 18:00:00,12984.0 -2013-07-31 19:00:00,12961.0 -2013-07-31 20:00:00,12808.0 -2013-07-31 21:00:00,12620.0 -2013-07-31 22:00:00,12847.0 -2013-07-31 23:00:00,12537.0 -2013-08-01 00:00:00,11619.0 -2013-07-30 01:00:00,10221.0 -2013-07-30 02:00:00,9534.0 -2013-07-30 03:00:00,9126.0 -2013-07-30 04:00:00,8833.0 -2013-07-30 05:00:00,8710.0 -2013-07-30 06:00:00,8832.0 -2013-07-30 07:00:00,9291.0 -2013-07-30 08:00:00,9964.0 -2013-07-30 09:00:00,10909.0 -2013-07-30 10:00:00,11584.0 -2013-07-30 11:00:00,12045.0 -2013-07-30 12:00:00,12465.0 -2013-07-30 13:00:00,12666.0 -2013-07-30 14:00:00,12674.0 -2013-07-30 15:00:00,12716.0 -2013-07-30 16:00:00,12641.0 -2013-07-30 17:00:00,12507.0 -2013-07-30 18:00:00,12396.0 -2013-07-30 19:00:00,12239.0 -2013-07-30 20:00:00,11983.0 -2013-07-30 21:00:00,11947.0 -2013-07-30 22:00:00,12238.0 -2013-07-30 23:00:00,11951.0 -2013-07-31 00:00:00,11160.0 -2013-07-29 01:00:00,8981.0 -2013-07-29 02:00:00,8474.0 -2013-07-29 03:00:00,8218.0 -2013-07-29 04:00:00,8023.0 -2013-07-29 05:00:00,8027.0 -2013-07-29 06:00:00,8222.0 -2013-07-29 07:00:00,8738.0 -2013-07-29 08:00:00,9450.0 -2013-07-29 09:00:00,10454.0 -2013-07-29 10:00:00,11099.0 -2013-07-29 11:00:00,11622.0 -2013-07-29 12:00:00,12032.0 -2013-07-29 13:00:00,12284.0 -2013-07-29 14:00:00,12472.0 -2013-07-29 15:00:00,12754.0 -2013-07-29 16:00:00,12883.0 -2013-07-29 17:00:00,12962.0 -2013-07-29 18:00:00,12958.0 -2013-07-29 19:00:00,12853.0 -2013-07-29 20:00:00,12488.0 -2013-07-29 21:00:00,12133.0 -2013-07-29 22:00:00,12229.0 -2013-07-29 23:00:00,11970.0 -2013-07-30 00:00:00,11148.0 -2013-07-28 01:00:00,8870.0 -2013-07-28 02:00:00,8371.0 -2013-07-28 03:00:00,8043.0 -2013-07-28 04:00:00,7814.0 -2013-07-28 05:00:00,7643.0 -2013-07-28 06:00:00,7664.0 -2013-07-28 07:00:00,7652.0 -2013-07-28 08:00:00,7607.0 -2013-07-28 09:00:00,7779.0 -2013-07-28 10:00:00,8219.0 -2013-07-28 11:00:00,8637.0 -2013-07-28 12:00:00,8930.0 -2013-07-28 13:00:00,9109.0 -2013-07-28 14:00:00,9229.0 -2013-07-28 15:00:00,9269.0 -2013-07-28 16:00:00,9387.0 -2013-07-28 17:00:00,9395.0 -2013-07-28 18:00:00,9500.0 -2013-07-28 19:00:00,9558.0 -2013-07-28 20:00:00,9576.0 -2013-07-28 21:00:00,9575.0 -2013-07-28 22:00:00,9900.0 -2013-07-28 23:00:00,9976.0 -2013-07-29 00:00:00,9489.0 -2013-07-27 01:00:00,10449.0 -2013-07-27 02:00:00,9696.0 -2013-07-27 03:00:00,9218.0 -2013-07-27 04:00:00,8841.0 -2013-07-27 05:00:00,8677.0 -2013-07-27 06:00:00,8601.0 -2013-07-27 07:00:00,8751.0 -2013-07-27 08:00:00,8807.0 -2013-07-27 09:00:00,9243.0 -2013-07-27 10:00:00,9667.0 -2013-07-27 11:00:00,10039.0 -2013-07-27 12:00:00,10280.0 -2013-07-27 13:00:00,10418.0 -2013-07-27 14:00:00,10428.0 -2013-07-27 15:00:00,10218.0 -2013-07-27 16:00:00,10000.0 -2013-07-27 17:00:00,9873.0 -2013-07-27 18:00:00,9787.0 -2013-07-27 19:00:00,9669.0 -2013-07-27 20:00:00,9533.0 -2013-07-27 21:00:00,9518.0 -2013-07-27 22:00:00,9840.0 -2013-07-27 23:00:00,9851.0 -2013-07-28 00:00:00,9393.0 -2013-07-26 01:00:00,11399.0 -2013-07-26 02:00:00,10588.0 -2013-07-26 03:00:00,10024.0 -2013-07-26 04:00:00,9676.0 -2013-07-26 05:00:00,9491.0 -2013-07-26 06:00:00,9581.0 -2013-07-26 07:00:00,10128.0 -2013-07-26 08:00:00,10877.0 -2013-07-26 09:00:00,11556.0 -2013-07-26 10:00:00,12145.0 -2013-07-26 11:00:00,12431.0 -2013-07-26 12:00:00,12687.0 -2013-07-26 13:00:00,12821.0 -2013-07-26 14:00:00,12801.0 -2013-07-26 15:00:00,12837.0 -2013-07-26 16:00:00,12764.0 -2013-07-26 17:00:00,12639.0 -2013-07-26 18:00:00,12522.0 -2013-07-26 19:00:00,12339.0 -2013-07-26 20:00:00,12104.0 -2013-07-26 21:00:00,11927.0 -2013-07-26 22:00:00,12025.0 -2013-07-26 23:00:00,11970.0 -2013-07-27 00:00:00,11285.0 -2013-07-25 01:00:00,10329.0 -2013-07-25 02:00:00,9625.0 -2013-07-25 03:00:00,9170.0 -2013-07-25 04:00:00,8879.0 -2013-07-25 05:00:00,8742.0 -2013-07-25 06:00:00,8913.0 -2013-07-25 07:00:00,9347.0 -2013-07-25 08:00:00,10015.0 -2013-07-25 09:00:00,11030.0 -2013-07-25 10:00:00,11751.0 -2013-07-25 11:00:00,12296.0 -2013-07-25 12:00:00,12819.0 -2013-07-25 13:00:00,13227.0 -2013-07-25 14:00:00,13615.0 -2013-07-25 15:00:00,13997.0 -2013-07-25 16:00:00,14264.0 -2013-07-25 17:00:00,14485.0 -2013-07-25 18:00:00,14549.0 -2013-07-25 19:00:00,14324.0 -2013-07-25 20:00:00,13791.0 -2013-07-25 21:00:00,13410.0 -2013-07-25 22:00:00,13524.0 -2013-07-25 23:00:00,13273.0 -2013-07-26 00:00:00,12403.0 -2013-07-24 01:00:00,10753.0 -2013-07-24 02:00:00,10038.0 -2013-07-24 03:00:00,9525.0 -2013-07-24 04:00:00,9196.0 -2013-07-24 05:00:00,9025.0 -2013-07-24 06:00:00,9140.0 -2013-07-24 07:00:00,9514.0 -2013-07-24 08:00:00,10188.0 -2013-07-24 09:00:00,11190.0 -2013-07-24 10:00:00,11855.0 -2013-07-24 11:00:00,12265.0 -2013-07-24 12:00:00,12644.0 -2013-07-24 13:00:00,12903.0 -2013-07-24 14:00:00,13104.0 -2013-07-24 15:00:00,13331.0 -2013-07-24 16:00:00,13428.0 -2013-07-24 17:00:00,13489.0 -2013-07-24 18:00:00,13458.0 -2013-07-24 19:00:00,13253.0 -2013-07-24 20:00:00,12807.0 -2013-07-24 21:00:00,12355.0 -2013-07-24 22:00:00,12264.0 -2013-07-24 23:00:00,12134.0 -2013-07-25 00:00:00,11259.0 -2013-07-23 01:00:00,14400.0 -2013-07-23 02:00:00,12938.0 -2013-07-23 03:00:00,11878.0 -2013-07-23 04:00:00,11366.0 -2013-07-23 05:00:00,11081.0 -2013-07-23 06:00:00,11036.0 -2013-07-23 07:00:00,11389.0 -2013-07-23 08:00:00,12103.0 -2013-07-23 09:00:00,13212.0 -2013-07-23 10:00:00,14067.0 -2013-07-23 11:00:00,14696.0 -2013-07-23 12:00:00,15133.0 -2013-07-23 13:00:00,15453.0 -2013-07-23 14:00:00,15711.0 -2013-07-23 15:00:00,16017.0 -2013-07-23 16:00:00,16107.0 -2013-07-23 17:00:00,16067.0 -2013-07-23 18:00:00,15818.0 -2013-07-23 19:00:00,15240.0 -2013-07-23 20:00:00,14336.0 -2013-07-23 21:00:00,13410.0 -2013-07-23 22:00:00,13052.0 -2013-07-23 23:00:00,12686.0 -2013-07-24 00:00:00,11778.0 -2013-07-22 01:00:00,12281.0 -2013-07-22 02:00:00,11530.0 -2013-07-22 03:00:00,11058.0 -2013-07-22 04:00:00,10738.0 -2013-07-22 05:00:00,10613.0 -2013-07-22 06:00:00,10804.0 -2013-07-22 07:00:00,11354.0 -2013-07-22 08:00:00,12169.0 -2013-07-22 09:00:00,13309.0 -2013-07-22 10:00:00,14422.0 -2013-07-22 11:00:00,15348.0 -2013-07-22 12:00:00,16404.0 -2013-07-22 13:00:00,17253.0 -2013-07-22 14:00:00,17927.0 -2013-07-22 15:00:00,18568.0 -2013-07-22 16:00:00,19018.0 -2013-07-22 17:00:00,19227.0 -2013-07-22 18:00:00,19427.0 -2013-07-22 19:00:00,19362.0 -2013-07-22 20:00:00,18901.0 -2013-07-22 21:00:00,18163.0 -2013-07-22 22:00:00,17696.0 -2013-07-22 23:00:00,17084.0 -2013-07-23 00:00:00,15773.0 -2013-07-21 01:00:00,12795.0 -2013-07-21 02:00:00,11936.0 -2013-07-21 03:00:00,11221.0 -2013-07-21 04:00:00,10655.0 -2013-07-21 05:00:00,10323.0 -2013-07-21 06:00:00,10097.0 -2013-07-21 07:00:00,9897.0 -2013-07-21 08:00:00,9892.0 -2013-07-21 09:00:00,10620.0 -2013-07-21 10:00:00,11541.0 -2013-07-21 11:00:00,12511.0 -2013-07-21 12:00:00,13470.0 -2013-07-21 13:00:00,14456.0 -2013-07-21 14:00:00,15280.0 -2013-07-21 15:00:00,15865.0 -2013-07-21 16:00:00,16269.0 -2013-07-21 17:00:00,16433.0 -2013-07-21 18:00:00,16311.0 -2013-07-21 19:00:00,16037.0 -2013-07-21 20:00:00,15509.0 -2013-07-21 21:00:00,14914.0 -2013-07-21 22:00:00,14687.0 -2013-07-21 23:00:00,14084.0 -2013-07-22 00:00:00,13190.0 -2013-07-20 01:00:00,15356.0 -2013-07-20 02:00:00,14217.0 -2013-07-20 03:00:00,13335.0 -2013-07-20 04:00:00,12631.0 -2013-07-20 05:00:00,12166.0 -2013-07-20 06:00:00,11758.0 -2013-07-20 07:00:00,11606.0 -2013-07-20 08:00:00,11732.0 -2013-07-20 09:00:00,12680.0 -2013-07-20 10:00:00,13771.0 -2013-07-20 11:00:00,14884.0 -2013-07-20 12:00:00,15833.0 -2013-07-20 13:00:00,16443.0 -2013-07-20 14:00:00,16874.0 -2013-07-20 15:00:00,17116.0 -2013-07-20 16:00:00,17381.0 -2013-07-20 17:00:00,17610.0 -2013-07-20 18:00:00,17702.0 -2013-07-20 19:00:00,17459.0 -2013-07-20 20:00:00,16856.0 -2013-07-20 21:00:00,15966.0 -2013-07-20 22:00:00,15600.0 -2013-07-20 23:00:00,14941.0 -2013-07-21 00:00:00,13944.0 -2013-07-19 01:00:00,17044.0 -2013-07-19 02:00:00,15856.0 -2013-07-19 03:00:00,14971.0 -2013-07-19 04:00:00,14315.0 -2013-07-19 05:00:00,13917.0 -2013-07-19 06:00:00,13858.0 -2013-07-19 07:00:00,14174.0 -2013-07-19 08:00:00,15013.0 -2013-07-19 09:00:00,16496.0 -2013-07-19 10:00:00,17839.0 -2013-07-19 11:00:00,19035.0 -2013-07-19 12:00:00,20165.0 -2013-07-19 13:00:00,20951.0 -2013-07-19 14:00:00,21469.0 -2013-07-19 15:00:00,21875.0 -2013-07-19 16:00:00,22032.0 -2013-07-19 17:00:00,22038.0 -2013-07-19 18:00:00,21955.0 -2013-07-19 19:00:00,21675.0 -2013-07-19 20:00:00,21177.0 -2013-07-19 21:00:00,20387.0 -2013-07-19 22:00:00,19788.0 -2013-07-19 23:00:00,18814.0 -2013-07-20 00:00:00,16805.0 -2013-07-18 01:00:00,16696.0 -2013-07-18 02:00:00,15428.0 -2013-07-18 03:00:00,14533.0 -2013-07-18 04:00:00,13888.0 -2013-07-18 05:00:00,13503.0 -2013-07-18 06:00:00,13438.0 -2013-07-18 07:00:00,13777.0 -2013-07-18 08:00:00,14740.0 -2013-07-18 09:00:00,16283.0 -2013-07-18 10:00:00,17764.0 -2013-07-18 11:00:00,19076.0 -2013-07-18 12:00:00,20267.0 -2013-07-18 13:00:00,20978.0 -2013-07-18 14:00:00,21501.0 -2013-07-18 15:00:00,21949.0 -2013-07-18 16:00:00,22208.0 -2013-07-18 17:00:00,22269.0 -2013-07-18 18:00:00,21989.0 -2013-07-18 19:00:00,21464.0 -2013-07-18 20:00:00,20961.0 -2013-07-18 21:00:00,20458.0 -2013-07-18 22:00:00,20082.0 -2013-07-18 23:00:00,19710.0 -2013-07-19 00:00:00,18424.0 -2013-07-17 01:00:00,16249.0 -2013-07-17 02:00:00,14982.0 -2013-07-17 03:00:00,14054.0 -2013-07-17 04:00:00,13381.0 -2013-07-17 05:00:00,12971.0 -2013-07-17 06:00:00,12919.0 -2013-07-17 07:00:00,13260.0 -2013-07-17 08:00:00,14244.0 -2013-07-17 09:00:00,15780.0 -2013-07-17 10:00:00,17126.0 -2013-07-17 11:00:00,18425.0 -2013-07-17 12:00:00,19601.0 -2013-07-17 13:00:00,20293.0 -2013-07-17 14:00:00,20883.0 -2013-07-17 15:00:00,21383.0 -2013-07-17 16:00:00,21616.0 -2013-07-17 17:00:00,21778.0 -2013-07-17 18:00:00,21801.0 -2013-07-17 19:00:00,21664.0 -2013-07-17 20:00:00,21262.0 -2013-07-17 21:00:00,20720.0 -2013-07-17 22:00:00,20250.0 -2013-07-17 23:00:00,19695.0 -2013-07-18 00:00:00,18244.0 -2013-07-16 01:00:00,14922.0 -2013-07-16 02:00:00,13818.0 -2013-07-16 03:00:00,13068.0 -2013-07-16 04:00:00,12490.0 -2013-07-16 05:00:00,12169.0 -2013-07-16 06:00:00,12219.0 -2013-07-16 07:00:00,12643.0 -2013-07-16 08:00:00,13665.0 -2013-07-16 09:00:00,15173.0 -2013-07-16 10:00:00,16577.0 -2013-07-16 11:00:00,17747.0 -2013-07-16 12:00:00,18778.0 -2013-07-16 13:00:00,19540.0 -2013-07-16 14:00:00,20146.0 -2013-07-16 15:00:00,20733.0 -2013-07-16 16:00:00,21088.0 -2013-07-16 17:00:00,21287.0 -2013-07-16 18:00:00,21381.0 -2013-07-16 19:00:00,21210.0 -2013-07-16 20:00:00,20802.0 -2013-07-16 21:00:00,20188.0 -2013-07-16 22:00:00,19727.0 -2013-07-16 23:00:00,19216.0 -2013-07-17 00:00:00,17810.0 -2013-07-15 01:00:00,13349.0 -2013-07-15 02:00:00,12419.0 -2013-07-15 03:00:00,11737.0 -2013-07-15 04:00:00,11291.0 -2013-07-15 05:00:00,11094.0 -2013-07-15 06:00:00,11209.0 -2013-07-15 07:00:00,11685.0 -2013-07-15 08:00:00,12841.0 -2013-07-15 09:00:00,14478.0 -2013-07-15 10:00:00,15936.0 -2013-07-15 11:00:00,17309.0 -2013-07-15 12:00:00,18522.0 -2013-07-15 13:00:00,19221.0 -2013-07-15 14:00:00,19550.0 -2013-07-15 15:00:00,19759.0 -2013-07-15 16:00:00,19781.0 -2013-07-15 17:00:00,19751.0 -2013-07-15 18:00:00,19536.0 -2013-07-15 19:00:00,19153.0 -2013-07-15 20:00:00,18745.0 -2013-07-15 21:00:00,18261.0 -2013-07-15 22:00:00,17989.0 -2013-07-15 23:00:00,17573.0 -2013-07-16 00:00:00,16347.0 -2013-07-14 01:00:00,11922.0 -2013-07-14 02:00:00,11095.0 -2013-07-14 03:00:00,10489.0 -2013-07-14 04:00:00,10113.0 -2013-07-14 05:00:00,9835.0 -2013-07-14 06:00:00,9674.0 -2013-07-14 07:00:00,9481.0 -2013-07-14 08:00:00,9664.0 -2013-07-14 09:00:00,10443.0 -2013-07-14 10:00:00,11526.0 -2013-07-14 11:00:00,12723.0 -2013-07-14 12:00:00,13924.0 -2013-07-14 13:00:00,14842.0 -2013-07-14 14:00:00,15528.0 -2013-07-14 15:00:00,16047.0 -2013-07-14 16:00:00,16399.0 -2013-07-14 17:00:00,16703.0 -2013-07-14 18:00:00,16933.0 -2013-07-14 19:00:00,16998.0 -2013-07-14 20:00:00,16774.0 -2013-07-14 21:00:00,16242.0 -2013-07-14 22:00:00,15862.0 -2013-07-14 23:00:00,15575.0 -2013-07-15 00:00:00,14535.0 -2013-07-13 01:00:00,11765.0 -2013-07-13 02:00:00,10844.0 -2013-07-13 03:00:00,10181.0 -2013-07-13 04:00:00,9735.0 -2013-07-13 05:00:00,9422.0 -2013-07-13 06:00:00,9301.0 -2013-07-13 07:00:00,9214.0 -2013-07-13 08:00:00,9570.0 -2013-07-13 09:00:00,10384.0 -2013-07-13 10:00:00,11388.0 -2013-07-13 11:00:00,12271.0 -2013-07-13 12:00:00,13084.0 -2013-07-13 13:00:00,13686.0 -2013-07-13 14:00:00,14110.0 -2013-07-13 15:00:00,14442.0 -2013-07-13 16:00:00,14781.0 -2013-07-13 17:00:00,15105.0 -2013-07-13 18:00:00,15321.0 -2013-07-13 19:00:00,15292.0 -2013-07-13 20:00:00,14918.0 -2013-07-13 21:00:00,14347.0 -2013-07-13 22:00:00,13887.0 -2013-07-13 23:00:00,13637.0 -2013-07-14 00:00:00,12815.0 -2013-07-12 01:00:00,11374.0 -2013-07-12 02:00:00,10470.0 -2013-07-12 03:00:00,9802.0 -2013-07-12 04:00:00,9360.0 -2013-07-12 05:00:00,9235.0 -2013-07-12 06:00:00,9245.0 -2013-07-12 07:00:00,9550.0 -2013-07-12 08:00:00,10351.0 -2013-07-12 09:00:00,11521.0 -2013-07-12 10:00:00,12453.0 -2013-07-12 11:00:00,13150.0 -2013-07-12 12:00:00,13806.0 -2013-07-12 13:00:00,14322.0 -2013-07-12 14:00:00,14733.0 -2013-07-12 15:00:00,15254.0 -2013-07-12 16:00:00,15687.0 -2013-07-12 17:00:00,15985.0 -2013-07-12 18:00:00,16185.0 -2013-07-12 19:00:00,16045.0 -2013-07-12 20:00:00,15559.0 -2013-07-12 21:00:00,14784.0 -2013-07-12 22:00:00,14165.0 -2013-07-12 23:00:00,13808.0 -2013-07-13 00:00:00,12806.0 -2013-07-11 01:00:00,11984.0 -2013-07-11 02:00:00,11016.0 -2013-07-11 03:00:00,10383.0 -2013-07-11 04:00:00,9991.0 -2013-07-11 05:00:00,9778.0 -2013-07-11 06:00:00,9840.0 -2013-07-11 07:00:00,10160.0 -2013-07-11 08:00:00,10994.0 -2013-07-11 09:00:00,12128.0 -2013-07-11 10:00:00,13069.0 -2013-07-11 11:00:00,13766.0 -2013-07-11 12:00:00,14375.0 -2013-07-11 13:00:00,14790.0 -2013-07-11 14:00:00,15104.0 -2013-07-11 15:00:00,15468.0 -2013-07-11 16:00:00,15724.0 -2013-07-11 17:00:00,15950.0 -2013-07-11 18:00:00,16049.0 -2013-07-11 19:00:00,15866.0 -2013-07-11 20:00:00,15275.0 -2013-07-11 21:00:00,14555.0 -2013-07-11 22:00:00,13956.0 -2013-07-11 23:00:00,13620.0 -2013-07-12 00:00:00,12555.0 -2013-07-10 01:00:00,14662.0 -2013-07-10 02:00:00,13599.0 -2013-07-10 03:00:00,12915.0 -2013-07-10 04:00:00,12366.0 -2013-07-10 05:00:00,12070.0 -2013-07-10 06:00:00,12142.0 -2013-07-10 07:00:00,12545.0 -2013-07-10 08:00:00,13616.0 -2013-07-10 09:00:00,14929.0 -2013-07-10 10:00:00,16060.0 -2013-07-10 11:00:00,16920.0 -2013-07-10 12:00:00,17676.0 -2013-07-10 13:00:00,18130.0 -2013-07-10 14:00:00,18381.0 -2013-07-10 15:00:00,18538.0 -2013-07-10 16:00:00,18477.0 -2013-07-10 17:00:00,18339.0 -2013-07-10 18:00:00,18109.0 -2013-07-10 19:00:00,17613.0 -2013-07-10 20:00:00,16797.0 -2013-07-10 21:00:00,15751.0 -2013-07-10 22:00:00,15059.0 -2013-07-10 23:00:00,14531.0 -2013-07-11 00:00:00,13326.0 -2013-07-09 01:00:00,14054.0 -2013-07-09 02:00:00,12955.0 -2013-07-09 03:00:00,12177.0 -2013-07-09 04:00:00,11638.0 -2013-07-09 05:00:00,11315.0 -2013-07-09 06:00:00,11353.0 -2013-07-09 07:00:00,11765.0 -2013-07-09 08:00:00,12824.0 -2013-07-09 09:00:00,14203.0 -2013-07-09 10:00:00,15322.0 -2013-07-09 11:00:00,16274.0 -2013-07-09 12:00:00,17352.0 -2013-07-09 13:00:00,17787.0 -2013-07-09 14:00:00,17623.0 -2013-07-09 15:00:00,17470.0 -2013-07-09 16:00:00,17415.0 -2013-07-09 17:00:00,17199.0 -2013-07-09 18:00:00,17536.0 -2013-07-09 19:00:00,17992.0 -2013-07-09 20:00:00,17861.0 -2013-07-09 21:00:00,17533.0 -2013-07-09 22:00:00,17342.0 -2013-07-09 23:00:00,17128.0 -2013-07-10 00:00:00,15957.0 -2013-07-08 01:00:00,13514.0 -2013-07-08 02:00:00,12587.0 -2013-07-08 03:00:00,11939.0 -2013-07-08 04:00:00,11582.0 -2013-07-08 05:00:00,11366.0 -2013-07-08 06:00:00,11485.0 -2013-07-08 07:00:00,12025.0 -2013-07-08 08:00:00,13047.0 -2013-07-08 09:00:00,13964.0 -2013-07-08 10:00:00,14382.0 -2013-07-08 11:00:00,14694.0 -2013-07-08 12:00:00,14899.0 -2013-07-08 13:00:00,15038.0 -2013-07-08 14:00:00,15305.0 -2013-07-08 15:00:00,16172.0 -2013-07-08 16:00:00,17111.0 -2013-07-08 17:00:00,17786.0 -2013-07-08 18:00:00,18074.0 -2013-07-08 19:00:00,18244.0 -2013-07-08 20:00:00,18029.0 -2013-07-08 21:00:00,17569.0 -2013-07-08 22:00:00,17171.0 -2013-07-08 23:00:00,16709.0 -2013-07-09 00:00:00,15462.0 -2013-07-07 01:00:00,11669.0 -2013-07-07 02:00:00,10920.0 -2013-07-07 03:00:00,10309.0 -2013-07-07 04:00:00,9906.0 -2013-07-07 05:00:00,9611.0 -2013-07-07 06:00:00,9486.0 -2013-07-07 07:00:00,9283.0 -2013-07-07 08:00:00,9197.0 -2013-07-07 09:00:00,9761.0 -2013-07-07 10:00:00,10709.0 -2013-07-07 11:00:00,11771.0 -2013-07-07 12:00:00,12745.0 -2013-07-07 13:00:00,13640.0 -2013-07-07 14:00:00,14522.0 -2013-07-07 15:00:00,15234.0 -2013-07-07 16:00:00,15805.0 -2013-07-07 17:00:00,16179.0 -2013-07-07 18:00:00,16194.0 -2013-07-07 19:00:00,16258.0 -2013-07-07 20:00:00,16051.0 -2013-07-07 21:00:00,15753.0 -2013-07-07 22:00:00,15674.0 -2013-07-07 23:00:00,15483.0 -2013-07-08 00:00:00,14605.0 -2013-07-06 01:00:00,11780.0 -2013-07-06 02:00:00,10849.0 -2013-07-06 03:00:00,10232.0 -2013-07-06 04:00:00,9730.0 -2013-07-06 05:00:00,9431.0 -2013-07-06 06:00:00,9273.0 -2013-07-06 07:00:00,9177.0 -2013-07-06 08:00:00,9397.0 -2013-07-06 09:00:00,10264.0 -2013-07-06 10:00:00,11325.0 -2013-07-06 11:00:00,12426.0 -2013-07-06 12:00:00,13324.0 -2013-07-06 13:00:00,13995.0 -2013-07-06 14:00:00,14361.0 -2013-07-06 15:00:00,14437.0 -2013-07-06 16:00:00,14415.0 -2013-07-06 17:00:00,14296.0 -2013-07-06 18:00:00,14174.0 -2013-07-06 19:00:00,13948.0 -2013-07-06 20:00:00,13613.0 -2013-07-06 21:00:00,13224.0 -2013-07-06 22:00:00,13205.0 -2013-07-06 23:00:00,13075.0 -2013-07-07 00:00:00,12449.0 -2013-07-05 01:00:00,10426.0 -2013-07-05 02:00:00,9718.0 -2013-07-05 03:00:00,9157.0 -2013-07-05 04:00:00,8741.0 -2013-07-05 05:00:00,8509.0 -2013-07-05 06:00:00,8538.0 -2013-07-05 07:00:00,8716.0 -2013-07-05 08:00:00,9309.0 -2013-07-05 09:00:00,10277.0 -2013-07-05 10:00:00,11327.0 -2013-07-05 11:00:00,12371.0 -2013-07-05 12:00:00,13285.0 -2013-07-05 13:00:00,14017.0 -2013-07-05 14:00:00,14535.0 -2013-07-05 15:00:00,14946.0 -2013-07-05 16:00:00,15228.0 -2013-07-05 17:00:00,15475.0 -2013-07-05 18:00:00,15638.0 -2013-07-05 19:00:00,15498.0 -2013-07-05 20:00:00,14992.0 -2013-07-05 21:00:00,14362.0 -2013-07-05 22:00:00,13971.0 -2013-07-05 23:00:00,13774.0 -2013-07-06 00:00:00,12795.0 -2013-07-04 01:00:00,9828.0 -2013-07-04 02:00:00,9197.0 -2013-07-04 03:00:00,8751.0 -2013-07-04 04:00:00,8399.0 -2013-07-04 05:00:00,8197.0 -2013-07-04 06:00:00,8139.0 -2013-07-04 07:00:00,7991.0 -2013-07-04 08:00:00,8005.0 -2013-07-04 09:00:00,8357.0 -2013-07-04 10:00:00,8921.0 -2013-07-04 11:00:00,9605.0 -2013-07-04 12:00:00,10273.0 -2013-07-04 13:00:00,10861.0 -2013-07-04 14:00:00,11348.0 -2013-07-04 15:00:00,11729.0 -2013-07-04 16:00:00,11982.0 -2013-07-04 17:00:00,12136.0 -2013-07-04 18:00:00,12282.0 -2013-07-04 19:00:00,12276.0 -2013-07-04 20:00:00,12017.0 -2013-07-04 21:00:00,11574.0 -2013-07-04 22:00:00,11405.0 -2013-07-04 23:00:00,11303.0 -2013-07-05 00:00:00,10952.0 -2013-07-03 01:00:00,9654.0 -2013-07-03 02:00:00,9066.0 -2013-07-03 03:00:00,8695.0 -2013-07-03 04:00:00,8454.0 -2013-07-03 05:00:00,8389.0 -2013-07-03 06:00:00,8522.0 -2013-07-03 07:00:00,9004.0 -2013-07-03 08:00:00,9805.0 -2013-07-03 09:00:00,10605.0 -2013-07-03 10:00:00,11157.0 -2013-07-03 11:00:00,11538.0 -2013-07-03 12:00:00,11845.0 -2013-07-03 13:00:00,11967.0 -2013-07-03 14:00:00,12023.0 -2013-07-03 15:00:00,12170.0 -2013-07-03 16:00:00,12184.0 -2013-07-03 17:00:00,12096.0 -2013-07-03 18:00:00,11941.0 -2013-07-03 19:00:00,11800.0 -2013-07-03 20:00:00,11458.0 -2013-07-03 21:00:00,11225.0 -2013-07-03 22:00:00,11284.0 -2013-07-03 23:00:00,11270.0 -2013-07-04 00:00:00,10596.0 -2013-07-02 01:00:00,10081.0 -2013-07-02 02:00:00,9380.0 -2013-07-02 03:00:00,8934.0 -2013-07-02 04:00:00,8637.0 -2013-07-02 05:00:00,8504.0 -2013-07-02 06:00:00,8637.0 -2013-07-02 07:00:00,9019.0 -2013-07-02 08:00:00,9735.0 -2013-07-02 09:00:00,10573.0 -2013-07-02 10:00:00,11148.0 -2013-07-02 11:00:00,11557.0 -2013-07-02 12:00:00,11850.0 -2013-07-02 13:00:00,11930.0 -2013-07-02 14:00:00,12030.0 -2013-07-02 15:00:00,12192.0 -2013-07-02 16:00:00,12154.0 -2013-07-02 17:00:00,12012.0 -2013-07-02 18:00:00,11793.0 -2013-07-02 19:00:00,11580.0 -2013-07-02 20:00:00,11224.0 -2013-07-02 21:00:00,11050.0 -2013-07-02 22:00:00,11237.0 -2013-07-02 23:00:00,11195.0 -2013-07-03 00:00:00,10445.0 -2013-07-01 01:00:00,9372.0 -2013-07-01 02:00:00,8872.0 -2013-07-01 03:00:00,8576.0 -2013-07-01 04:00:00,8392.0 -2013-07-01 05:00:00,8339.0 -2013-07-01 06:00:00,8515.0 -2013-07-01 07:00:00,8923.0 -2013-07-01 08:00:00,9744.0 -2013-07-01 09:00:00,10765.0 -2013-07-01 10:00:00,11473.0 -2013-07-01 11:00:00,11995.0 -2013-07-01 12:00:00,12430.0 -2013-07-01 13:00:00,12651.0 -2013-07-01 14:00:00,12845.0 -2013-07-01 15:00:00,13037.0 -2013-07-01 16:00:00,13115.0 -2013-07-01 17:00:00,13140.0 -2013-07-01 18:00:00,13050.0 -2013-07-01 19:00:00,12836.0 -2013-07-01 20:00:00,12410.0 -2013-07-01 21:00:00,11971.0 -2013-07-01 22:00:00,11861.0 -2013-07-01 23:00:00,11833.0 -2013-07-02 00:00:00,10997.0 -2013-06-30 01:00:00,9582.0 -2013-06-30 02:00:00,9065.0 -2013-06-30 03:00:00,8641.0 -2013-06-30 04:00:00,8464.0 -2013-06-30 05:00:00,8266.0 -2013-06-30 06:00:00,8215.0 -2013-06-30 07:00:00,8093.0 -2013-06-30 08:00:00,8067.0 -2013-06-30 09:00:00,8447.0 -2013-06-30 10:00:00,9020.0 -2013-06-30 11:00:00,9634.0 -2013-06-30 12:00:00,10174.0 -2013-06-30 13:00:00,10600.0 -2013-06-30 14:00:00,10929.0 -2013-06-30 15:00:00,11139.0 -2013-06-30 16:00:00,11341.0 -2013-06-30 17:00:00,11427.0 -2013-06-30 18:00:00,11457.0 -2013-06-30 19:00:00,11371.0 -2013-06-30 20:00:00,11030.0 -2013-06-30 21:00:00,10613.0 -2013-06-30 22:00:00,10600.0 -2013-06-30 23:00:00,10668.0 -2013-07-01 00:00:00,10116.0 -2013-06-29 01:00:00,11434.0 -2013-06-29 02:00:00,10570.0 -2013-06-29 03:00:00,9979.0 -2013-06-29 04:00:00,9519.0 -2013-06-29 05:00:00,9280.0 -2013-06-29 06:00:00,9173.0 -2013-06-29 07:00:00,9261.0 -2013-06-29 08:00:00,9425.0 -2013-06-29 09:00:00,9928.0 -2013-06-29 10:00:00,10466.0 -2013-06-29 11:00:00,10921.0 -2013-06-29 12:00:00,11187.0 -2013-06-29 13:00:00,11321.0 -2013-06-29 14:00:00,11284.0 -2013-06-29 15:00:00,11166.0 -2013-06-29 16:00:00,11121.0 -2013-06-29 17:00:00,11118.0 -2013-06-29 18:00:00,11126.0 -2013-06-29 19:00:00,11074.0 -2013-06-29 20:00:00,10769.0 -2013-06-29 21:00:00,10544.0 -2013-06-29 22:00:00,10650.0 -2013-06-29 23:00:00,10704.0 -2013-06-30 00:00:00,10228.0 -2013-06-28 01:00:00,13005.0 -2013-06-28 02:00:00,11860.0 -2013-06-28 03:00:00,11075.0 -2013-06-28 04:00:00,10529.0 -2013-06-28 05:00:00,10253.0 -2013-06-28 06:00:00,10220.0 -2013-06-28 07:00:00,10501.0 -2013-06-28 08:00:00,11497.0 -2013-06-28 09:00:00,12682.0 -2013-06-28 10:00:00,13653.0 -2013-06-28 11:00:00,14539.0 -2013-06-28 12:00:00,15290.0 -2013-06-28 13:00:00,15816.0 -2013-06-28 14:00:00,16115.0 -2013-06-28 15:00:00,16406.0 -2013-06-28 16:00:00,16253.0 -2013-06-28 17:00:00,15666.0 -2013-06-28 18:00:00,15296.0 -2013-06-28 19:00:00,15054.0 -2013-06-28 20:00:00,14489.0 -2013-06-28 21:00:00,13862.0 -2013-06-28 22:00:00,13506.0 -2013-06-28 23:00:00,13308.0 -2013-06-29 00:00:00,12472.0 -2013-06-27 01:00:00,11982.0 -2013-06-27 02:00:00,10981.0 -2013-06-27 03:00:00,10324.0 -2013-06-27 04:00:00,9896.0 -2013-06-27 05:00:00,9729.0 -2013-06-27 06:00:00,9840.0 -2013-06-27 07:00:00,10197.0 -2013-06-27 08:00:00,11337.0 -2013-06-27 09:00:00,12637.0 -2013-06-27 10:00:00,13878.0 -2013-06-27 11:00:00,15041.0 -2013-06-27 12:00:00,16195.0 -2013-06-27 13:00:00,17064.0 -2013-06-27 14:00:00,17784.0 -2013-06-27 15:00:00,18418.0 -2013-06-27 16:00:00,18832.0 -2013-06-27 17:00:00,19173.0 -2013-06-27 18:00:00,19232.0 -2013-06-27 19:00:00,18539.0 -2013-06-27 20:00:00,17337.0 -2013-06-27 21:00:00,16644.0 -2013-06-27 22:00:00,15994.0 -2013-06-27 23:00:00,15619.0 -2013-06-28 00:00:00,14434.0 -2013-06-26 01:00:00,12543.0 -2013-06-26 02:00:00,11561.0 -2013-06-26 03:00:00,10848.0 -2013-06-26 04:00:00,10389.0 -2013-06-26 05:00:00,10177.0 -2013-06-26 06:00:00,10313.0 -2013-06-26 07:00:00,10856.0 -2013-06-26 08:00:00,11843.0 -2013-06-26 09:00:00,12641.0 -2013-06-26 10:00:00,13026.0 -2013-06-26 11:00:00,13362.0 -2013-06-26 12:00:00,13690.0 -2013-06-26 13:00:00,14032.0 -2013-06-26 14:00:00,14488.0 -2013-06-26 15:00:00,15092.0 -2013-06-26 16:00:00,15294.0 -2013-06-26 17:00:00,15422.0 -2013-06-26 18:00:00,15554.0 -2013-06-26 19:00:00,15599.0 -2013-06-26 20:00:00,15268.0 -2013-06-26 21:00:00,14877.0 -2013-06-26 22:00:00,14607.0 -2013-06-26 23:00:00,14335.0 -2013-06-27 00:00:00,13272.0 -2013-06-25 01:00:00,11589.0 -2013-06-25 02:00:00,10745.0 -2013-06-25 03:00:00,10202.0 -2013-06-25 04:00:00,9907.0 -2013-06-25 05:00:00,9836.0 -2013-06-25 06:00:00,9904.0 -2013-06-25 07:00:00,10404.0 -2013-06-25 08:00:00,11331.0 -2013-06-25 09:00:00,12348.0 -2013-06-25 10:00:00,13091.0 -2013-06-25 11:00:00,13718.0 -2013-06-25 12:00:00,14214.0 -2013-06-25 13:00:00,14473.0 -2013-06-25 14:00:00,15080.0 -2013-06-25 15:00:00,16056.0 -2013-06-25 16:00:00,16647.0 -2013-06-25 17:00:00,16814.0 -2013-06-25 18:00:00,16985.0 -2013-06-25 19:00:00,16614.0 -2013-06-25 20:00:00,16134.0 -2013-06-25 21:00:00,15735.0 -2013-06-25 22:00:00,15462.0 -2013-06-25 23:00:00,15024.0 -2013-06-26 00:00:00,13858.0 -2013-06-24 01:00:00,12095.0 -2013-06-24 02:00:00,11312.0 -2013-06-24 03:00:00,10788.0 -2013-06-24 04:00:00,10388.0 -2013-06-24 05:00:00,10214.0 -2013-06-24 06:00:00,10370.0 -2013-06-24 07:00:00,10908.0 -2013-06-24 08:00:00,11965.0 -2013-06-24 09:00:00,12906.0 -2013-06-24 10:00:00,13416.0 -2013-06-24 11:00:00,13744.0 -2013-06-24 12:00:00,13989.0 -2013-06-24 13:00:00,14477.0 -2013-06-24 14:00:00,15128.0 -2013-06-24 15:00:00,16017.0 -2013-06-24 16:00:00,16747.0 -2013-06-24 17:00:00,17370.0 -2013-06-24 18:00:00,17702.0 -2013-06-24 19:00:00,17257.0 -2013-06-24 20:00:00,15189.0 -2013-06-24 21:00:00,14263.0 -2013-06-24 22:00:00,13899.0 -2013-06-24 23:00:00,13476.0 -2013-06-25 00:00:00,12618.0 -2013-06-23 01:00:00,11289.0 -2013-06-23 02:00:00,10483.0 -2013-06-23 03:00:00,9831.0 -2013-06-23 04:00:00,9414.0 -2013-06-23 05:00:00,9173.0 -2013-06-23 06:00:00,9074.0 -2013-06-23 07:00:00,8933.0 -2013-06-23 08:00:00,8904.0 -2013-06-23 09:00:00,9545.0 -2013-06-23 10:00:00,10616.0 -2013-06-23 11:00:00,11722.0 -2013-06-23 12:00:00,12687.0 -2013-06-23 13:00:00,13604.0 -2013-06-23 14:00:00,14347.0 -2013-06-23 15:00:00,15067.0 -2013-06-23 16:00:00,15655.0 -2013-06-23 17:00:00,16017.0 -2013-06-23 18:00:00,15918.0 -2013-06-23 19:00:00,15361.0 -2013-06-23 20:00:00,14565.0 -2013-06-23 21:00:00,14065.0 -2013-06-23 22:00:00,13926.0 -2013-06-23 23:00:00,13836.0 -2013-06-24 00:00:00,13044.0 -2013-06-22 01:00:00,11614.0 -2013-06-22 02:00:00,10911.0 -2013-06-22 03:00:00,10370.0 -2013-06-22 04:00:00,10021.0 -2013-06-22 05:00:00,9684.0 -2013-06-22 06:00:00,9666.0 -2013-06-22 07:00:00,9772.0 -2013-06-22 08:00:00,10039.0 -2013-06-22 09:00:00,10559.0 -2013-06-22 10:00:00,11233.0 -2013-06-22 11:00:00,11971.0 -2013-06-22 12:00:00,12548.0 -2013-06-22 13:00:00,13117.0 -2013-06-22 14:00:00,13444.0 -2013-06-22 15:00:00,13393.0 -2013-06-22 16:00:00,13255.0 -2013-06-22 17:00:00,13294.0 -2013-06-22 18:00:00,13368.0 -2013-06-22 19:00:00,13440.0 -2013-06-22 20:00:00,13404.0 -2013-06-22 21:00:00,13129.0 -2013-06-22 22:00:00,13037.0 -2013-06-22 23:00:00,12937.0 -2013-06-23 00:00:00,12140.0 -2013-06-21 01:00:00,12415.0 -2013-06-21 02:00:00,11432.0 -2013-06-21 03:00:00,10729.0 -2013-06-21 04:00:00,10298.0 -2013-06-21 05:00:00,10056.0 -2013-06-21 06:00:00,10102.0 -2013-06-21 07:00:00,10435.0 -2013-06-21 08:00:00,11517.0 -2013-06-21 09:00:00,12801.0 -2013-06-21 10:00:00,13971.0 -2013-06-21 11:00:00,14879.0 -2013-06-21 12:00:00,15551.0 -2013-06-21 13:00:00,15800.0 -2013-06-21 14:00:00,15592.0 -2013-06-21 15:00:00,14999.0 -2013-06-21 16:00:00,14374.0 -2013-06-21 17:00:00,13850.0 -2013-06-21 18:00:00,13641.0 -2013-06-21 19:00:00,13444.0 -2013-06-21 20:00:00,13169.0 -2013-06-21 21:00:00,12950.0 -2013-06-21 22:00:00,13061.0 -2013-06-21 23:00:00,13124.0 -2013-06-22 00:00:00,12464.0 -2013-06-20 01:00:00,10216.0 -2013-06-20 02:00:00,9476.0 -2013-06-20 03:00:00,8996.0 -2013-06-20 04:00:00,8729.0 -2013-06-20 05:00:00,8606.0 -2013-06-20 06:00:00,8760.0 -2013-06-20 07:00:00,9069.0 -2013-06-20 08:00:00,10033.0 -2013-06-20 09:00:00,11153.0 -2013-06-20 10:00:00,11918.0 -2013-06-20 11:00:00,12740.0 -2013-06-20 12:00:00,13574.0 -2013-06-20 13:00:00,14281.0 -2013-06-20 14:00:00,14830.0 -2013-06-20 15:00:00,15399.0 -2013-06-20 16:00:00,15797.0 -2013-06-20 17:00:00,16140.0 -2013-06-20 18:00:00,16336.0 -2013-06-20 19:00:00,16321.0 -2013-06-20 20:00:00,15906.0 -2013-06-20 21:00:00,15367.0 -2013-06-20 22:00:00,15022.0 -2013-06-20 23:00:00,14686.0 -2013-06-21 00:00:00,13638.0 -2013-06-19 01:00:00,9838.0 -2013-06-19 02:00:00,9169.0 -2013-06-19 03:00:00,8739.0 -2013-06-19 04:00:00,8466.0 -2013-06-19 05:00:00,8369.0 -2013-06-19 06:00:00,8493.0 -2013-06-19 07:00:00,8839.0 -2013-06-19 08:00:00,9711.0 -2013-06-19 09:00:00,10658.0 -2013-06-19 10:00:00,11351.0 -2013-06-19 11:00:00,11858.0 -2013-06-19 12:00:00,12308.0 -2013-06-19 13:00:00,12645.0 -2013-06-19 14:00:00,12848.0 -2013-06-19 15:00:00,13084.0 -2013-06-19 16:00:00,13208.0 -2013-06-19 17:00:00,13311.0 -2013-06-19 18:00:00,13316.0 -2013-06-19 19:00:00,13211.0 -2013-06-19 20:00:00,12842.0 -2013-06-19 21:00:00,12428.0 -2013-06-19 22:00:00,12215.0 -2013-06-19 23:00:00,12027.0 -2013-06-20 00:00:00,11209.0 -2013-06-18 01:00:00,11067.0 -2013-06-18 02:00:00,10154.0 -2013-06-18 03:00:00,9532.0 -2013-06-18 04:00:00,9088.0 -2013-06-18 05:00:00,8918.0 -2013-06-18 06:00:00,8949.0 -2013-06-18 07:00:00,9275.0 -2013-06-18 08:00:00,10057.0 -2013-06-18 09:00:00,10858.0 -2013-06-18 10:00:00,11387.0 -2013-06-18 11:00:00,11718.0 -2013-06-18 12:00:00,12007.0 -2013-06-18 13:00:00,12259.0 -2013-06-18 14:00:00,12461.0 -2013-06-18 15:00:00,12673.0 -2013-06-18 16:00:00,12753.0 -2013-06-18 17:00:00,12790.0 -2013-06-18 18:00:00,12753.0 -2013-06-18 19:00:00,12552.0 -2013-06-18 20:00:00,12074.0 -2013-06-18 21:00:00,11641.0 -2013-06-18 22:00:00,11552.0 -2013-06-18 23:00:00,11460.0 -2013-06-19 00:00:00,10689.0 -2013-06-17 01:00:00,11355.0 -2013-06-17 02:00:00,10500.0 -2013-06-17 03:00:00,9863.0 -2013-06-17 04:00:00,9449.0 -2013-06-17 05:00:00,9233.0 -2013-06-17 06:00:00,9352.0 -2013-06-17 07:00:00,9684.0 -2013-06-17 08:00:00,10860.0 -2013-06-17 09:00:00,12215.0 -2013-06-17 10:00:00,13368.0 -2013-06-17 11:00:00,14258.0 -2013-06-17 12:00:00,15087.0 -2013-06-17 13:00:00,15769.0 -2013-06-17 14:00:00,16312.0 -2013-06-17 15:00:00,16846.0 -2013-06-17 16:00:00,17160.0 -2013-06-17 17:00:00,17401.0 -2013-06-17 18:00:00,17430.0 -2013-06-17 19:00:00,17089.0 -2013-06-17 20:00:00,16384.0 -2013-06-17 21:00:00,15511.0 -2013-06-17 22:00:00,14683.0 -2013-06-17 23:00:00,13863.0 -2013-06-18 00:00:00,12375.0 -2013-06-16 01:00:00,10923.0 -2013-06-16 02:00:00,10221.0 -2013-06-16 03:00:00,9463.0 -2013-06-16 04:00:00,9068.0 -2013-06-16 05:00:00,8780.0 -2013-06-16 06:00:00,8584.0 -2013-06-16 07:00:00,8370.0 -2013-06-16 08:00:00,8489.0 -2013-06-16 09:00:00,9114.0 -2013-06-16 10:00:00,10003.0 -2013-06-16 11:00:00,10854.0 -2013-06-16 12:00:00,11682.0 -2013-06-16 13:00:00,12363.0 -2013-06-16 14:00:00,12812.0 -2013-06-16 15:00:00,13192.0 -2013-06-16 16:00:00,13528.0 -2013-06-16 17:00:00,13842.0 -2013-06-16 18:00:00,14011.0 -2013-06-16 19:00:00,14062.0 -2013-06-16 20:00:00,13857.0 -2013-06-16 21:00:00,13480.0 -2013-06-16 22:00:00,13291.0 -2013-06-16 23:00:00,13245.0 -2013-06-17 00:00:00,12424.0 -2013-06-15 01:00:00,9841.0 -2013-06-15 02:00:00,9119.0 -2013-06-15 03:00:00,8653.0 -2013-06-15 04:00:00,8444.0 -2013-06-15 05:00:00,8334.0 -2013-06-15 06:00:00,8353.0 -2013-06-15 07:00:00,8439.0 -2013-06-15 08:00:00,8664.0 -2013-06-15 09:00:00,9190.0 -2013-06-15 10:00:00,9798.0 -2013-06-15 11:00:00,10333.0 -2013-06-15 12:00:00,10641.0 -2013-06-15 13:00:00,10811.0 -2013-06-15 14:00:00,10959.0 -2013-06-15 15:00:00,11027.0 -2013-06-15 16:00:00,11152.0 -2013-06-15 17:00:00,11409.0 -2013-06-15 18:00:00,11785.0 -2013-06-15 19:00:00,12057.0 -2013-06-15 20:00:00,11994.0 -2013-06-15 21:00:00,11907.0 -2013-06-15 22:00:00,12132.0 -2013-06-15 23:00:00,12210.0 -2013-06-16 00:00:00,11701.0 -2013-06-14 01:00:00,9964.0 -2013-06-14 02:00:00,9270.0 -2013-06-14 03:00:00,8784.0 -2013-06-14 04:00:00,8554.0 -2013-06-14 05:00:00,8401.0 -2013-06-14 06:00:00,8535.0 -2013-06-14 07:00:00,8785.0 -2013-06-14 08:00:00,9606.0 -2013-06-14 09:00:00,10586.0 -2013-06-14 10:00:00,11241.0 -2013-06-14 11:00:00,11743.0 -2013-06-14 12:00:00,12162.0 -2013-06-14 13:00:00,12425.0 -2013-06-14 14:00:00,12605.0 -2013-06-14 15:00:00,12891.0 -2013-06-14 16:00:00,13071.0 -2013-06-14 17:00:00,13193.0 -2013-06-14 18:00:00,13207.0 -2013-06-14 19:00:00,13008.0 -2013-06-14 20:00:00,12492.0 -2013-06-14 21:00:00,11812.0 -2013-06-14 22:00:00,11644.0 -2013-06-14 23:00:00,11502.0 -2013-06-15 00:00:00,10722.0 -2013-06-13 01:00:00,10963.0 -2013-06-13 02:00:00,10085.0 -2013-06-13 03:00:00,9504.0 -2013-06-13 04:00:00,9172.0 -2013-06-13 05:00:00,9017.0 -2013-06-13 06:00:00,9121.0 -2013-06-13 07:00:00,9471.0 -2013-06-13 08:00:00,10220.0 -2013-06-13 09:00:00,11240.0 -2013-06-13 10:00:00,11987.0 -2013-06-13 11:00:00,12479.0 -2013-06-13 12:00:00,12875.0 -2013-06-13 13:00:00,13100.0 -2013-06-13 14:00:00,13270.0 -2013-06-13 15:00:00,13495.0 -2013-06-13 16:00:00,13563.0 -2013-06-13 17:00:00,13512.0 -2013-06-13 18:00:00,13447.0 -2013-06-13 19:00:00,13215.0 -2013-06-13 20:00:00,12648.0 -2013-06-13 21:00:00,12097.0 -2013-06-13 22:00:00,11939.0 -2013-06-13 23:00:00,11826.0 -2013-06-14 00:00:00,10966.0 -2013-06-12 01:00:00,12011.0 -2013-06-12 02:00:00,11036.0 -2013-06-12 03:00:00,10383.0 -2013-06-12 04:00:00,9943.0 -2013-06-12 05:00:00,9755.0 -2013-06-12 06:00:00,9800.0 -2013-06-12 07:00:00,10276.0 -2013-06-12 08:00:00,11203.0 -2013-06-12 09:00:00,12172.0 -2013-06-12 10:00:00,12788.0 -2013-06-12 11:00:00,13272.0 -2013-06-12 12:00:00,13761.0 -2013-06-12 13:00:00,14148.0 -2013-06-12 14:00:00,14643.0 -2013-06-12 15:00:00,15405.0 -2013-06-12 16:00:00,15863.0 -2013-06-12 17:00:00,15890.0 -2013-06-12 18:00:00,15698.0 -2013-06-12 19:00:00,15149.0 -2013-06-12 20:00:00,14590.0 -2013-06-12 21:00:00,14176.0 -2013-06-12 22:00:00,13842.0 -2013-06-12 23:00:00,13047.0 -2013-06-13 00:00:00,11989.0 -2013-06-11 01:00:00,10567.0 -2013-06-11 02:00:00,9813.0 -2013-06-11 03:00:00,9321.0 -2013-06-11 04:00:00,8989.0 -2013-06-11 05:00:00,8860.0 -2013-06-11 06:00:00,8978.0 -2013-06-11 07:00:00,9345.0 -2013-06-11 08:00:00,10421.0 -2013-06-11 09:00:00,11617.0 -2013-06-11 10:00:00,12561.0 -2013-06-11 11:00:00,13305.0 -2013-06-11 12:00:00,14022.0 -2013-06-11 13:00:00,14569.0 -2013-06-11 14:00:00,15005.0 -2013-06-11 15:00:00,15483.0 -2013-06-11 16:00:00,15712.0 -2013-06-11 17:00:00,15735.0 -2013-06-11 18:00:00,15290.0 -2013-06-11 19:00:00,15182.0 -2013-06-11 20:00:00,14971.0 -2013-06-11 21:00:00,14588.0 -2013-06-11 22:00:00,14573.0 -2013-06-11 23:00:00,14280.0 -2013-06-12 00:00:00,13244.0 -2013-06-10 01:00:00,9803.0 -2013-06-10 02:00:00,9244.0 -2013-06-10 03:00:00,8870.0 -2013-06-10 04:00:00,8618.0 -2013-06-10 05:00:00,8531.0 -2013-06-10 06:00:00,8715.0 -2013-06-10 07:00:00,9207.0 -2013-06-10 08:00:00,10173.0 -2013-06-10 09:00:00,11274.0 -2013-06-10 10:00:00,12020.0 -2013-06-10 11:00:00,12667.0 -2013-06-10 12:00:00,13173.0 -2013-06-10 13:00:00,13490.0 -2013-06-10 14:00:00,13636.0 -2013-06-10 15:00:00,13804.0 -2013-06-10 16:00:00,13892.0 -2013-06-10 17:00:00,13847.0 -2013-06-10 18:00:00,13712.0 -2013-06-10 19:00:00,13562.0 -2013-06-10 20:00:00,13239.0 -2013-06-10 21:00:00,12861.0 -2013-06-10 22:00:00,12814.0 -2013-06-10 23:00:00,12561.0 -2013-06-11 00:00:00,11586.0 -2013-06-09 01:00:00,8935.0 -2013-06-09 02:00:00,8350.0 -2013-06-09 03:00:00,8036.0 -2013-06-09 04:00:00,7803.0 -2013-06-09 05:00:00,7672.0 -2013-06-09 06:00:00,7632.0 -2013-06-09 07:00:00,7485.0 -2013-06-09 08:00:00,7523.0 -2013-06-09 09:00:00,7959.0 -2013-06-09 10:00:00,8519.0 -2013-06-09 11:00:00,9154.0 -2013-06-09 12:00:00,9694.0 -2013-06-09 13:00:00,10089.0 -2013-06-09 14:00:00,10358.0 -2013-06-09 15:00:00,10529.0 -2013-06-09 16:00:00,10625.0 -2013-06-09 17:00:00,10593.0 -2013-06-09 18:00:00,10705.0 -2013-06-09 19:00:00,10687.0 -2013-06-09 20:00:00,10634.0 -2013-06-09 21:00:00,10642.0 -2013-06-09 22:00:00,11029.0 -2013-06-09 23:00:00,11061.0 -2013-06-10 00:00:00,10546.0 -2013-06-08 01:00:00,9252.0 -2013-06-08 02:00:00,8636.0 -2013-06-08 03:00:00,8264.0 -2013-06-08 04:00:00,8008.0 -2013-06-08 05:00:00,7867.0 -2013-06-08 06:00:00,7908.0 -2013-06-08 07:00:00,7846.0 -2013-06-08 08:00:00,8120.0 -2013-06-08 09:00:00,8662.0 -2013-06-08 10:00:00,9246.0 -2013-06-08 11:00:00,9688.0 -2013-06-08 12:00:00,10017.0 -2013-06-08 13:00:00,10232.0 -2013-06-08 14:00:00,10271.0 -2013-06-08 15:00:00,10260.0 -2013-06-08 16:00:00,10204.0 -2013-06-08 17:00:00,10201.0 -2013-06-08 18:00:00,10160.0 -2013-06-08 19:00:00,10056.0 -2013-06-08 20:00:00,9903.0 -2013-06-08 21:00:00,9747.0 -2013-06-08 22:00:00,10024.0 -2013-06-08 23:00:00,10016.0 -2013-06-09 00:00:00,9542.0 -2013-06-07 01:00:00,9408.0 -2013-06-07 02:00:00,8785.0 -2013-06-07 03:00:00,8440.0 -2013-06-07 04:00:00,8208.0 -2013-06-07 05:00:00,8135.0 -2013-06-07 06:00:00,8283.0 -2013-06-07 07:00:00,8641.0 -2013-06-07 08:00:00,9412.0 -2013-06-07 09:00:00,10254.0 -2013-06-07 10:00:00,10728.0 -2013-06-07 11:00:00,11034.0 -2013-06-07 12:00:00,11310.0 -2013-06-07 13:00:00,11422.0 -2013-06-07 14:00:00,11461.0 -2013-06-07 15:00:00,11515.0 -2013-06-07 16:00:00,11477.0 -2013-06-07 17:00:00,11344.0 -2013-06-07 18:00:00,11252.0 -2013-06-07 19:00:00,11077.0 -2013-06-07 20:00:00,10818.0 -2013-06-07 21:00:00,10556.0 -2013-06-07 22:00:00,10649.0 -2013-06-07 23:00:00,10685.0 -2013-06-08 00:00:00,10000.0 -2013-06-06 01:00:00,9480.0 -2013-06-06 02:00:00,8869.0 -2013-06-06 03:00:00,8500.0 -2013-06-06 04:00:00,8298.0 -2013-06-06 05:00:00,8235.0 -2013-06-06 06:00:00,8377.0 -2013-06-06 07:00:00,8870.0 -2013-06-06 08:00:00,9710.0 -2013-06-06 09:00:00,10573.0 -2013-06-06 10:00:00,11058.0 -2013-06-06 11:00:00,11393.0 -2013-06-06 12:00:00,11589.0 -2013-06-06 13:00:00,11589.0 -2013-06-06 14:00:00,11584.0 -2013-06-06 15:00:00,11631.0 -2013-06-06 16:00:00,11518.0 -2013-06-06 17:00:00,11406.0 -2013-06-06 18:00:00,11289.0 -2013-06-06 19:00:00,11094.0 -2013-06-06 20:00:00,10813.0 -2013-06-06 21:00:00,10633.0 -2013-06-06 22:00:00,10901.0 -2013-06-06 23:00:00,10850.0 -2013-06-07 00:00:00,10245.0 -2013-06-05 01:00:00,9496.0 -2013-06-05 02:00:00,8902.0 -2013-06-05 03:00:00,8507.0 -2013-06-05 04:00:00,8276.0 -2013-06-05 05:00:00,8209.0 -2013-06-05 06:00:00,8353.0 -2013-06-05 07:00:00,8826.0 -2013-06-05 08:00:00,9620.0 -2013-06-05 09:00:00,10574.0 -2013-06-05 10:00:00,11177.0 -2013-06-05 11:00:00,11577.0 -2013-06-05 12:00:00,11842.0 -2013-06-05 13:00:00,12048.0 -2013-06-05 14:00:00,12081.0 -2013-06-05 15:00:00,12196.0 -2013-06-05 16:00:00,12128.0 -2013-06-05 17:00:00,11887.0 -2013-06-05 18:00:00,11682.0 -2013-06-05 19:00:00,11445.0 -2013-06-05 20:00:00,11172.0 -2013-06-05 21:00:00,11153.0 -2013-06-05 22:00:00,11391.0 -2013-06-05 23:00:00,11076.0 -2013-06-06 00:00:00,10286.0 -2013-06-04 01:00:00,9343.0 -2013-06-04 02:00:00,8763.0 -2013-06-04 03:00:00,8399.0 -2013-06-04 04:00:00,8172.0 -2013-06-04 05:00:00,8120.0 -2013-06-04 06:00:00,8314.0 -2013-06-04 07:00:00,8680.0 -2013-06-04 08:00:00,9547.0 -2013-06-04 09:00:00,10444.0 -2013-06-04 10:00:00,10971.0 -2013-06-04 11:00:00,11325.0 -2013-06-04 12:00:00,11617.0 -2013-06-04 13:00:00,11798.0 -2013-06-04 14:00:00,11939.0 -2013-06-04 15:00:00,12052.0 -2013-06-04 16:00:00,12044.0 -2013-06-04 17:00:00,11888.0 -2013-06-04 18:00:00,11733.0 -2013-06-04 19:00:00,11591.0 -2013-06-04 20:00:00,11237.0 -2013-06-04 21:00:00,11008.0 -2013-06-04 22:00:00,11275.0 -2013-06-04 23:00:00,11148.0 -2013-06-05 00:00:00,10371.0 -2013-06-03 01:00:00,8612.0 -2013-06-03 02:00:00,8209.0 -2013-06-03 03:00:00,7983.0 -2013-06-03 04:00:00,7857.0 -2013-06-03 05:00:00,7844.0 -2013-06-03 06:00:00,8093.0 -2013-06-03 07:00:00,8508.0 -2013-06-03 08:00:00,9439.0 -2013-06-03 09:00:00,10325.0 -2013-06-03 10:00:00,10845.0 -2013-06-03 11:00:00,11074.0 -2013-06-03 12:00:00,11362.0 -2013-06-03 13:00:00,11485.0 -2013-06-03 14:00:00,11547.0 -2013-06-03 15:00:00,11680.0 -2013-06-03 16:00:00,11643.0 -2013-06-03 17:00:00,11583.0 -2013-06-03 18:00:00,11476.0 -2013-06-03 19:00:00,11318.0 -2013-06-03 20:00:00,11005.0 -2013-06-03 21:00:00,10871.0 -2013-06-03 22:00:00,11070.0 -2013-06-03 23:00:00,10977.0 -2013-06-04 00:00:00,10207.0 -2013-06-02 01:00:00,9731.0 -2013-06-02 02:00:00,9078.0 -2013-06-02 03:00:00,8639.0 -2013-06-02 04:00:00,8329.0 -2013-06-02 05:00:00,8160.0 -2013-06-02 06:00:00,8103.0 -2013-06-02 07:00:00,7950.0 -2013-06-02 08:00:00,7967.0 -2013-06-02 09:00:00,8220.0 -2013-06-02 10:00:00,8569.0 -2013-06-02 11:00:00,8856.0 -2013-06-02 12:00:00,9075.0 -2013-06-02 13:00:00,9165.0 -2013-06-02 14:00:00,9185.0 -2013-06-02 15:00:00,9147.0 -2013-06-02 16:00:00,9109.0 -2013-06-02 17:00:00,9064.0 -2013-06-02 18:00:00,9102.0 -2013-06-02 19:00:00,9155.0 -2013-06-02 20:00:00,9197.0 -2013-06-02 21:00:00,9326.0 -2013-06-02 22:00:00,9672.0 -2013-06-02 23:00:00,9665.0 -2013-06-03 00:00:00,9173.0 -2013-06-01 01:00:00,11378.0 -2013-06-01 02:00:00,10584.0 -2013-06-01 03:00:00,9973.0 -2013-06-01 04:00:00,9451.0 -2013-06-01 05:00:00,9184.0 -2013-06-01 06:00:00,9043.0 -2013-06-01 07:00:00,9054.0 -2013-06-01 08:00:00,9290.0 -2013-06-01 09:00:00,9980.0 -2013-06-01 10:00:00,10590.0 -2013-06-01 11:00:00,11143.0 -2013-06-01 12:00:00,11598.0 -2013-06-01 13:00:00,11855.0 -2013-06-01 14:00:00,11909.0 -2013-06-01 15:00:00,11805.0 -2013-06-01 16:00:00,11751.0 -2013-06-01 17:00:00,11794.0 -2013-06-01 18:00:00,11760.0 -2013-06-01 19:00:00,11579.0 -2013-06-01 20:00:00,11278.0 -2013-06-01 21:00:00,11156.0 -2013-06-01 22:00:00,11137.0 -2013-06-01 23:00:00,11044.0 -2013-06-02 00:00:00,10395.0 -2013-05-31 01:00:00,11208.0 -2013-05-31 02:00:00,10317.0 -2013-05-31 03:00:00,9797.0 -2013-05-31 04:00:00,9503.0 -2013-05-31 05:00:00,9312.0 -2013-05-31 06:00:00,9446.0 -2013-05-31 07:00:00,9905.0 -2013-05-31 08:00:00,10872.0 -2013-05-31 09:00:00,11928.0 -2013-05-31 10:00:00,12755.0 -2013-05-31 11:00:00,13255.0 -2013-05-31 12:00:00,13845.0 -2013-05-31 13:00:00,14197.0 -2013-05-31 14:00:00,14289.0 -2013-05-31 15:00:00,14462.0 -2013-05-31 16:00:00,14456.0 -2013-05-31 17:00:00,14168.0 -2013-05-31 18:00:00,13916.0 -2013-05-31 19:00:00,13689.0 -2013-05-31 20:00:00,13392.0 -2013-05-31 21:00:00,13088.0 -2013-05-31 22:00:00,13059.0 -2013-05-31 23:00:00,12992.0 -2013-06-01 00:00:00,12220.0 -2013-05-30 01:00:00,11907.0 -2013-05-30 02:00:00,11005.0 -2013-05-30 03:00:00,10374.0 -2013-05-30 04:00:00,9976.0 -2013-05-30 05:00:00,9799.0 -2013-05-30 06:00:00,9897.0 -2013-05-30 07:00:00,10362.0 -2013-05-30 08:00:00,11432.0 -2013-05-30 09:00:00,12770.0 -2013-05-30 10:00:00,13696.0 -2013-05-30 11:00:00,14433.0 -2013-05-30 12:00:00,15085.0 -2013-05-30 13:00:00,15577.0 -2013-05-30 14:00:00,15899.0 -2013-05-30 15:00:00,16093.0 -2013-05-30 16:00:00,16018.0 -2013-05-30 17:00:00,15837.0 -2013-05-30 18:00:00,15532.0 -2013-05-30 19:00:00,15239.0 -2013-05-30 20:00:00,14903.0 -2013-05-30 21:00:00,14529.0 -2013-05-30 22:00:00,14447.0 -2013-05-30 23:00:00,13743.0 -2013-05-31 00:00:00,12451.0 -2013-05-29 01:00:00,10207.0 -2013-05-29 02:00:00,9575.0 -2013-05-29 03:00:00,9150.0 -2013-05-29 04:00:00,8899.0 -2013-05-29 05:00:00,8801.0 -2013-05-29 06:00:00,8948.0 -2013-05-29 07:00:00,9470.0 -2013-05-29 08:00:00,10451.0 -2013-05-29 09:00:00,11319.0 -2013-05-29 10:00:00,11906.0 -2013-05-29 11:00:00,12290.0 -2013-05-29 12:00:00,12668.0 -2013-05-29 13:00:00,13056.0 -2013-05-29 14:00:00,13461.0 -2013-05-29 15:00:00,13991.0 -2013-05-29 16:00:00,14340.0 -2013-05-29 17:00:00,14643.0 -2013-05-29 18:00:00,14840.0 -2013-05-29 19:00:00,14842.0 -2013-05-29 20:00:00,14599.0 -2013-05-29 21:00:00,14345.0 -2013-05-29 22:00:00,14409.0 -2013-05-29 23:00:00,14117.0 -2013-05-30 00:00:00,13111.0 -2013-05-28 01:00:00,8407.0 -2013-05-28 02:00:00,7977.0 -2013-05-28 03:00:00,7779.0 -2013-05-28 04:00:00,7660.0 -2013-05-28 05:00:00,7662.0 -2013-05-28 06:00:00,7936.0 -2013-05-28 07:00:00,8582.0 -2013-05-28 08:00:00,9642.0 -2013-05-28 09:00:00,10745.0 -2013-05-28 10:00:00,11332.0 -2013-05-28 11:00:00,11676.0 -2013-05-28 12:00:00,12049.0 -2013-05-28 13:00:00,12242.0 -2013-05-28 14:00:00,12417.0 -2013-05-28 15:00:00,12604.0 -2013-05-28 16:00:00,12585.0 -2013-05-28 17:00:00,12577.0 -2013-05-28 18:00:00,12557.0 -2013-05-28 19:00:00,12455.0 -2013-05-28 20:00:00,12283.0 -2013-05-28 21:00:00,12618.0 -2013-05-28 22:00:00,12760.0 -2013-05-28 23:00:00,12097.0 -2013-05-29 00:00:00,11156.0 -2013-05-27 01:00:00,8380.0 -2013-05-27 02:00:00,7966.0 -2013-05-27 03:00:00,7664.0 -2013-05-27 04:00:00,7492.0 -2013-05-27 05:00:00,7394.0 -2013-05-27 06:00:00,7480.0 -2013-05-27 07:00:00,7561.0 -2013-05-27 08:00:00,7654.0 -2013-05-27 09:00:00,7904.0 -2013-05-27 10:00:00,8239.0 -2013-05-27 11:00:00,8542.0 -2013-05-27 12:00:00,8835.0 -2013-05-27 13:00:00,8978.0 -2013-05-27 14:00:00,8997.0 -2013-05-27 15:00:00,8983.0 -2013-05-27 16:00:00,8946.0 -2013-05-27 17:00:00,8906.0 -2013-05-27 18:00:00,8962.0 -2013-05-27 19:00:00,9056.0 -2013-05-27 20:00:00,9066.0 -2013-05-27 21:00:00,9133.0 -2013-05-27 22:00:00,9546.0 -2013-05-27 23:00:00,9504.0 -2013-05-28 00:00:00,8988.0 -2013-05-26 01:00:00,8642.0 -2013-05-26 02:00:00,8173.0 -2013-05-26 03:00:00,7915.0 -2013-05-26 04:00:00,7727.0 -2013-05-26 05:00:00,7641.0 -2013-05-26 06:00:00,7643.0 -2013-05-26 07:00:00,7503.0 -2013-05-26 08:00:00,7506.0 -2013-05-26 09:00:00,7782.0 -2013-05-26 10:00:00,8110.0 -2013-05-26 11:00:00,8435.0 -2013-05-26 12:00:00,8661.0 -2013-05-26 13:00:00,8791.0 -2013-05-26 14:00:00,8788.0 -2013-05-26 15:00:00,8827.0 -2013-05-26 16:00:00,8784.0 -2013-05-26 17:00:00,8743.0 -2013-05-26 18:00:00,8718.0 -2013-05-26 19:00:00,8819.0 -2013-05-26 20:00:00,8852.0 -2013-05-26 21:00:00,9015.0 -2013-05-26 22:00:00,9331.0 -2013-05-26 23:00:00,9284.0 -2013-05-27 00:00:00,8889.0 -2013-05-25 01:00:00,9110.0 -2013-05-25 02:00:00,8591.0 -2013-05-25 03:00:00,8242.0 -2013-05-25 04:00:00,8076.0 -2013-05-25 05:00:00,7919.0 -2013-05-25 06:00:00,7986.0 -2013-05-25 07:00:00,7969.0 -2013-05-25 08:00:00,8100.0 -2013-05-25 09:00:00,8541.0 -2013-05-25 10:00:00,8982.0 -2013-05-25 11:00:00,9326.0 -2013-05-25 12:00:00,9474.0 -2013-05-25 13:00:00,9581.0 -2013-05-25 14:00:00,9488.0 -2013-05-25 15:00:00,9324.0 -2013-05-25 16:00:00,9196.0 -2013-05-25 17:00:00,9099.0 -2013-05-25 18:00:00,9096.0 -2013-05-25 19:00:00,9068.0 -2013-05-25 20:00:00,9141.0 -2013-05-25 21:00:00,9209.0 -2013-05-25 22:00:00,9631.0 -2013-05-25 23:00:00,9590.0 -2013-05-26 00:00:00,9140.0 -2013-05-24 01:00:00,9262.0 -2013-05-24 02:00:00,8763.0 -2013-05-24 03:00:00,8455.0 -2013-05-24 04:00:00,8297.0 -2013-05-24 05:00:00,8247.0 -2013-05-24 06:00:00,8431.0 -2013-05-24 07:00:00,8893.0 -2013-05-24 08:00:00,9679.0 -2013-05-24 09:00:00,10426.0 -2013-05-24 10:00:00,10791.0 -2013-05-24 11:00:00,10926.0 -2013-05-24 12:00:00,11058.0 -2013-05-24 13:00:00,11021.0 -2013-05-24 14:00:00,11001.0 -2013-05-24 15:00:00,11031.0 -2013-05-24 16:00:00,10888.0 -2013-05-24 17:00:00,10714.0 -2013-05-24 18:00:00,10542.0 -2013-05-24 19:00:00,10397.0 -2013-05-24 20:00:00,10177.0 -2013-05-24 21:00:00,10082.0 -2013-05-24 22:00:00,10450.0 -2013-05-24 23:00:00,10412.0 -2013-05-25 00:00:00,9801.0 -2013-05-23 01:00:00,9922.0 -2013-05-23 02:00:00,9243.0 -2013-05-23 03:00:00,8813.0 -2013-05-23 04:00:00,8515.0 -2013-05-23 05:00:00,8397.0 -2013-05-23 06:00:00,8527.0 -2013-05-23 07:00:00,9071.0 -2013-05-23 08:00:00,9914.0 -2013-05-23 09:00:00,10717.0 -2013-05-23 10:00:00,11134.0 -2013-05-23 11:00:00,11301.0 -2013-05-23 12:00:00,11410.0 -2013-05-23 13:00:00,11372.0 -2013-05-23 14:00:00,11336.0 -2013-05-23 15:00:00,11285.0 -2013-05-23 16:00:00,11110.0 -2013-05-23 17:00:00,10927.0 -2013-05-23 18:00:00,10782.0 -2013-05-23 19:00:00,10638.0 -2013-05-23 20:00:00,10423.0 -2013-05-23 21:00:00,10379.0 -2013-05-23 22:00:00,10842.0 -2013-05-23 23:00:00,10743.0 -2013-05-24 00:00:00,10015.0 -2013-05-22 01:00:00,11472.0 -2013-05-22 02:00:00,10625.0 -2013-05-22 03:00:00,10018.0 -2013-05-22 04:00:00,9616.0 -2013-05-22 05:00:00,9490.0 -2013-05-22 06:00:00,9620.0 -2013-05-22 07:00:00,10239.0 -2013-05-22 08:00:00,11298.0 -2013-05-22 09:00:00,12157.0 -2013-05-22 10:00:00,12596.0 -2013-05-22 11:00:00,12998.0 -2013-05-22 12:00:00,13497.0 -2013-05-22 13:00:00,13541.0 -2013-05-22 14:00:00,13290.0 -2013-05-22 15:00:00,13122.0 -2013-05-22 16:00:00,13158.0 -2013-05-22 17:00:00,13302.0 -2013-05-22 18:00:00,13286.0 -2013-05-22 19:00:00,12972.0 -2013-05-22 20:00:00,12484.0 -2013-05-22 21:00:00,12199.0 -2013-05-22 22:00:00,12342.0 -2013-05-22 23:00:00,11877.0 -2013-05-23 00:00:00,10883.0 -2013-05-21 01:00:00,12197.0 -2013-05-21 02:00:00,11155.0 -2013-05-21 03:00:00,10461.0 -2013-05-21 04:00:00,9979.0 -2013-05-21 05:00:00,9718.0 -2013-05-21 06:00:00,9758.0 -2013-05-21 07:00:00,10193.0 -2013-05-21 08:00:00,11081.0 -2013-05-21 09:00:00,12189.0 -2013-05-21 10:00:00,12926.0 -2013-05-21 11:00:00,13491.0 -2013-05-21 12:00:00,13906.0 -2013-05-21 13:00:00,14147.0 -2013-05-21 14:00:00,14585.0 -2013-05-21 15:00:00,15100.0 -2013-05-21 16:00:00,15329.0 -2013-05-21 17:00:00,15478.0 -2013-05-21 18:00:00,15539.0 -2013-05-21 19:00:00,15351.0 -2013-05-21 20:00:00,14837.0 -2013-05-21 21:00:00,14297.0 -2013-05-21 22:00:00,14249.0 -2013-05-21 23:00:00,13874.0 -2013-05-22 00:00:00,12666.0 -2013-05-20 01:00:00,11070.0 -2013-05-20 02:00:00,10542.0 -2013-05-20 03:00:00,10172.0 -2013-05-20 04:00:00,9918.0 -2013-05-20 05:00:00,9769.0 -2013-05-20 06:00:00,9936.0 -2013-05-20 07:00:00,10562.0 -2013-05-20 08:00:00,11703.0 -2013-05-20 09:00:00,13021.0 -2013-05-20 10:00:00,13520.0 -2013-05-20 11:00:00,13880.0 -2013-05-20 12:00:00,14379.0 -2013-05-20 13:00:00,14861.0 -2013-05-20 14:00:00,15291.0 -2013-05-20 15:00:00,15842.0 -2013-05-20 16:00:00,16260.0 -2013-05-20 17:00:00,16563.0 -2013-05-20 18:00:00,16629.0 -2013-05-20 19:00:00,16537.0 -2013-05-20 20:00:00,16161.0 -2013-05-20 21:00:00,15574.0 -2013-05-20 22:00:00,15492.0 -2013-05-20 23:00:00,15123.0 -2013-05-21 00:00:00,13871.0 -2013-05-19 01:00:00,9585.0 -2013-05-19 02:00:00,8950.0 -2013-05-19 03:00:00,8529.0 -2013-05-19 04:00:00,8219.0 -2013-05-19 05:00:00,8022.0 -2013-05-19 06:00:00,7977.0 -2013-05-19 07:00:00,7859.0 -2013-05-19 08:00:00,7915.0 -2013-05-19 09:00:00,8445.0 -2013-05-19 10:00:00,9096.0 -2013-05-19 11:00:00,9836.0 -2013-05-19 12:00:00,10553.0 -2013-05-19 13:00:00,11189.0 -2013-05-19 14:00:00,11618.0 -2013-05-19 15:00:00,11981.0 -2013-05-19 16:00:00,12281.0 -2013-05-19 17:00:00,12630.0 -2013-05-19 18:00:00,12897.0 -2013-05-19 19:00:00,13030.0 -2013-05-19 20:00:00,12860.0 -2013-05-19 21:00:00,12668.0 -2013-05-19 22:00:00,12824.0 -2013-05-19 23:00:00,12641.0 -2013-05-20 00:00:00,11935.0 -2013-05-18 01:00:00,9376.0 -2013-05-18 02:00:00,8799.0 -2013-05-18 03:00:00,8451.0 -2013-05-18 04:00:00,8222.0 -2013-05-18 05:00:00,8088.0 -2013-05-18 06:00:00,8044.0 -2013-05-18 07:00:00,8120.0 -2013-05-18 08:00:00,8323.0 -2013-05-18 09:00:00,8865.0 -2013-05-18 10:00:00,9579.0 -2013-05-18 11:00:00,10061.0 -2013-05-18 12:00:00,10556.0 -2013-05-18 13:00:00,10827.0 -2013-05-18 14:00:00,11027.0 -2013-05-18 15:00:00,11045.0 -2013-05-18 16:00:00,11061.0 -2013-05-18 17:00:00,11180.0 -2013-05-18 18:00:00,11320.0 -2013-05-18 19:00:00,11337.0 -2013-05-18 20:00:00,11114.0 -2013-05-18 21:00:00,10858.0 -2013-05-18 22:00:00,11109.0 -2013-05-18 23:00:00,10932.0 -2013-05-19 00:00:00,10286.0 -2013-05-17 01:00:00,9666.0 -2013-05-17 02:00:00,9024.0 -2013-05-17 03:00:00,8664.0 -2013-05-17 04:00:00,8418.0 -2013-05-17 05:00:00,8332.0 -2013-05-17 06:00:00,8440.0 -2013-05-17 07:00:00,8935.0 -2013-05-17 08:00:00,9713.0 -2013-05-17 09:00:00,10645.0 -2013-05-17 10:00:00,11218.0 -2013-05-17 11:00:00,11617.0 -2013-05-17 12:00:00,11938.0 -2013-05-17 13:00:00,12052.0 -2013-05-17 14:00:00,11975.0 -2013-05-17 15:00:00,11968.0 -2013-05-17 16:00:00,11904.0 -2013-05-17 17:00:00,11782.0 -2013-05-17 18:00:00,11600.0 -2013-05-17 19:00:00,11324.0 -2013-05-17 20:00:00,11063.0 -2013-05-17 21:00:00,10965.0 -2013-05-17 22:00:00,11157.0 -2013-05-17 23:00:00,10873.0 -2013-05-18 00:00:00,10137.0 -2013-05-16 01:00:00,10087.0 -2013-05-16 02:00:00,9344.0 -2013-05-16 03:00:00,8838.0 -2013-05-16 04:00:00,8587.0 -2013-05-16 05:00:00,8468.0 -2013-05-16 06:00:00,8567.0 -2013-05-16 07:00:00,9007.0 -2013-05-16 08:00:00,9873.0 -2013-05-16 09:00:00,10971.0 -2013-05-16 10:00:00,11655.0 -2013-05-16 11:00:00,12155.0 -2013-05-16 12:00:00,12603.0 -2013-05-16 13:00:00,12875.0 -2013-05-16 14:00:00,13203.0 -2013-05-16 15:00:00,13478.0 -2013-05-16 16:00:00,13683.0 -2013-05-16 17:00:00,13825.0 -2013-05-16 18:00:00,13808.0 -2013-05-16 19:00:00,13466.0 -2013-05-16 20:00:00,12866.0 -2013-05-16 21:00:00,12192.0 -2013-05-16 22:00:00,12180.0 -2013-05-16 23:00:00,11624.0 -2013-05-17 00:00:00,10603.0 -2013-05-15 01:00:00,10844.0 -2013-05-15 02:00:00,10161.0 -2013-05-15 03:00:00,9723.0 -2013-05-15 04:00:00,9384.0 -2013-05-15 05:00:00,9224.0 -2013-05-15 06:00:00,9292.0 -2013-05-15 07:00:00,9781.0 -2013-05-15 08:00:00,10621.0 -2013-05-15 09:00:00,11657.0 -2013-05-15 10:00:00,12272.0 -2013-05-15 11:00:00,12753.0 -2013-05-15 12:00:00,13164.0 -2013-05-15 13:00:00,13452.0 -2013-05-15 14:00:00,13627.0 -2013-05-15 15:00:00,13861.0 -2013-05-15 16:00:00,13898.0 -2013-05-15 17:00:00,13893.0 -2013-05-15 18:00:00,13774.0 -2013-05-15 19:00:00,13495.0 -2013-05-15 20:00:00,12919.0 -2013-05-15 21:00:00,12538.0 -2013-05-15 22:00:00,12629.0 -2013-05-15 23:00:00,12203.0 -2013-05-16 00:00:00,11128.0 -2013-05-14 01:00:00,9160.0 -2013-05-14 02:00:00,8636.0 -2013-05-14 03:00:00,8329.0 -2013-05-14 04:00:00,8142.0 -2013-05-14 05:00:00,8091.0 -2013-05-14 06:00:00,8281.0 -2013-05-14 07:00:00,8855.0 -2013-05-14 08:00:00,9700.0 -2013-05-14 09:00:00,10542.0 -2013-05-14 10:00:00,11038.0 -2013-05-14 11:00:00,11392.0 -2013-05-14 12:00:00,11697.0 -2013-05-14 13:00:00,11995.0 -2013-05-14 14:00:00,12288.0 -2013-05-14 15:00:00,12623.0 -2013-05-14 16:00:00,12914.0 -2013-05-14 17:00:00,13002.0 -2013-05-14 18:00:00,13085.0 -2013-05-14 19:00:00,13013.0 -2013-05-14 20:00:00,12759.0 -2013-05-14 21:00:00,12668.0 -2013-05-14 22:00:00,13024.0 -2013-05-14 23:00:00,12726.0 -2013-05-15 00:00:00,11783.0 -2013-05-13 01:00:00,8573.0 -2013-05-13 02:00:00,8196.0 -2013-05-13 03:00:00,8027.0 -2013-05-13 04:00:00,7951.0 -2013-05-13 05:00:00,8021.0 -2013-05-13 06:00:00,8313.0 -2013-05-13 07:00:00,9041.0 -2013-05-13 08:00:00,9864.0 -2013-05-13 09:00:00,10593.0 -2013-05-13 10:00:00,10858.0 -2013-05-13 11:00:00,11011.0 -2013-05-13 12:00:00,11159.0 -2013-05-13 13:00:00,11187.0 -2013-05-13 14:00:00,11219.0 -2013-05-13 15:00:00,11270.0 -2013-05-13 16:00:00,11164.0 -2013-05-13 17:00:00,11006.0 -2013-05-13 18:00:00,10879.0 -2013-05-13 19:00:00,10690.0 -2013-05-13 20:00:00,10548.0 -2013-05-13 21:00:00,10719.0 -2013-05-13 22:00:00,11147.0 -2013-05-13 23:00:00,10743.0 -2013-05-14 00:00:00,9922.0 -2013-05-12 01:00:00,8813.0 -2013-05-12 02:00:00,8373.0 -2013-05-12 03:00:00,8084.0 -2013-05-12 04:00:00,7929.0 -2013-05-12 05:00:00,7819.0 -2013-05-12 06:00:00,7865.0 -2013-05-12 07:00:00,7823.0 -2013-05-12 08:00:00,7829.0 -2013-05-12 09:00:00,8086.0 -2013-05-12 10:00:00,8430.0 -2013-05-12 11:00:00,8575.0 -2013-05-12 12:00:00,8743.0 -2013-05-12 13:00:00,8728.0 -2013-05-12 14:00:00,8695.0 -2013-05-12 15:00:00,8612.0 -2013-05-12 16:00:00,8556.0 -2013-05-12 17:00:00,8506.0 -2013-05-12 18:00:00,8529.0 -2013-05-12 19:00:00,8600.0 -2013-05-12 20:00:00,8606.0 -2013-05-12 21:00:00,8850.0 -2013-05-12 22:00:00,9535.0 -2013-05-12 23:00:00,9535.0 -2013-05-13 00:00:00,9038.0 -2013-05-11 01:00:00,9247.0 -2013-05-11 02:00:00,8729.0 -2013-05-11 03:00:00,8382.0 -2013-05-11 04:00:00,8189.0 -2013-05-11 05:00:00,8066.0 -2013-05-11 06:00:00,8127.0 -2013-05-11 07:00:00,8270.0 -2013-05-11 08:00:00,8424.0 -2013-05-11 09:00:00,8874.0 -2013-05-11 10:00:00,9277.0 -2013-05-11 11:00:00,9570.0 -2013-05-11 12:00:00,9788.0 -2013-05-11 13:00:00,9786.0 -2013-05-11 14:00:00,9648.0 -2013-05-11 15:00:00,9420.0 -2013-05-11 16:00:00,9260.0 -2013-05-11 17:00:00,9171.0 -2013-05-11 18:00:00,9146.0 -2013-05-11 19:00:00,9123.0 -2013-05-11 20:00:00,9079.0 -2013-05-11 21:00:00,9237.0 -2013-05-11 22:00:00,9778.0 -2013-05-11 23:00:00,9789.0 -2013-05-12 00:00:00,9321.0 -2013-05-10 01:00:00,9218.0 -2013-05-10 02:00:00,8672.0 -2013-05-10 03:00:00,8293.0 -2013-05-10 04:00:00,8097.0 -2013-05-10 05:00:00,8035.0 -2013-05-10 06:00:00,8219.0 -2013-05-10 07:00:00,8777.0 -2013-05-10 08:00:00,9690.0 -2013-05-10 09:00:00,10504.0 -2013-05-10 10:00:00,10870.0 -2013-05-10 11:00:00,11064.0 -2013-05-10 12:00:00,11190.0 -2013-05-10 13:00:00,11210.0 -2013-05-10 14:00:00,11171.0 -2013-05-10 15:00:00,11155.0 -2013-05-10 16:00:00,11016.0 -2013-05-10 17:00:00,10865.0 -2013-05-10 18:00:00,10748.0 -2013-05-10 19:00:00,10661.0 -2013-05-10 20:00:00,10553.0 -2013-05-10 21:00:00,10697.0 -2013-05-10 22:00:00,10972.0 -2013-05-10 23:00:00,10653.0 -2013-05-11 00:00:00,10007.0 -2013-05-09 01:00:00,9444.0 -2013-05-09 02:00:00,8847.0 -2013-05-09 03:00:00,8492.0 -2013-05-09 04:00:00,8246.0 -2013-05-09 05:00:00,8193.0 -2013-05-09 06:00:00,8316.0 -2013-05-09 07:00:00,8818.0 -2013-05-09 08:00:00,9638.0 -2013-05-09 09:00:00,10576.0 -2013-05-09 10:00:00,11089.0 -2013-05-09 11:00:00,11422.0 -2013-05-09 12:00:00,11605.0 -2013-05-09 13:00:00,11748.0 -2013-05-09 14:00:00,11873.0 -2013-05-09 15:00:00,11973.0 -2013-05-09 16:00:00,11917.0 -2013-05-09 17:00:00,11777.0 -2013-05-09 18:00:00,11648.0 -2013-05-09 19:00:00,11478.0 -2013-05-09 20:00:00,11240.0 -2013-05-09 21:00:00,11263.0 -2013-05-09 22:00:00,11460.0 -2013-05-09 23:00:00,10985.0 -2013-05-10 00:00:00,10113.0 -2013-05-08 01:00:00,9264.0 -2013-05-08 02:00:00,8695.0 -2013-05-08 03:00:00,8318.0 -2013-05-08 04:00:00,8134.0 -2013-05-08 05:00:00,8042.0 -2013-05-08 06:00:00,8227.0 -2013-05-08 07:00:00,8762.0 -2013-05-08 08:00:00,9534.0 -2013-05-08 09:00:00,10480.0 -2013-05-08 10:00:00,11053.0 -2013-05-08 11:00:00,11443.0 -2013-05-08 12:00:00,11816.0 -2013-05-08 13:00:00,11956.0 -2013-05-08 14:00:00,12118.0 -2013-05-08 15:00:00,12289.0 -2013-05-08 16:00:00,12358.0 -2013-05-08 17:00:00,12291.0 -2013-05-08 18:00:00,12193.0 -2013-05-08 19:00:00,11968.0 -2013-05-08 20:00:00,11539.0 -2013-05-08 21:00:00,11417.0 -2013-05-08 22:00:00,11742.0 -2013-05-08 23:00:00,11279.0 -2013-05-09 00:00:00,10349.0 -2013-05-07 01:00:00,9046.0 -2013-05-07 02:00:00,8529.0 -2013-05-07 03:00:00,8186.0 -2013-05-07 04:00:00,8027.0 -2013-05-07 05:00:00,7995.0 -2013-05-07 06:00:00,8177.0 -2013-05-07 07:00:00,8757.0 -2013-05-07 08:00:00,9569.0 -2013-05-07 09:00:00,10392.0 -2013-05-07 10:00:00,10846.0 -2013-05-07 11:00:00,11154.0 -2013-05-07 12:00:00,11436.0 -2013-05-07 13:00:00,11610.0 -2013-05-07 14:00:00,11733.0 -2013-05-07 15:00:00,11873.0 -2013-05-07 16:00:00,11931.0 -2013-05-07 17:00:00,11856.0 -2013-05-07 18:00:00,11767.0 -2013-05-07 19:00:00,11583.0 -2013-05-07 20:00:00,11231.0 -2013-05-07 21:00:00,11124.0 -2013-05-07 22:00:00,11523.0 -2013-05-07 23:00:00,11109.0 -2013-05-08 00:00:00,10174.0 -2013-05-06 01:00:00,8392.0 -2013-05-06 02:00:00,8022.0 -2013-05-06 03:00:00,7775.0 -2013-05-06 04:00:00,7683.0 -2013-05-06 05:00:00,7681.0 -2013-05-06 06:00:00,7909.0 -2013-05-06 07:00:00,8578.0 -2013-05-06 08:00:00,9497.0 -2013-05-06 09:00:00,10439.0 -2013-05-06 10:00:00,10766.0 -2013-05-06 11:00:00,10976.0 -2013-05-06 12:00:00,11209.0 -2013-05-06 13:00:00,11317.0 -2013-05-06 14:00:00,11356.0 -2013-05-06 15:00:00,11469.0 -2013-05-06 16:00:00,11410.0 -2013-05-06 17:00:00,11279.0 -2013-05-06 18:00:00,11202.0 -2013-05-06 19:00:00,11008.0 -2013-05-06 20:00:00,10723.0 -2013-05-06 21:00:00,10743.0 -2013-05-06 22:00:00,11234.0 -2013-05-06 23:00:00,10756.0 -2013-05-07 00:00:00,9887.0 -2013-05-05 01:00:00,8600.0 -2013-05-05 02:00:00,8167.0 -2013-05-05 03:00:00,7846.0 -2013-05-05 04:00:00,7656.0 -2013-05-05 05:00:00,7538.0 -2013-05-05 06:00:00,7578.0 -2013-05-05 07:00:00,7587.0 -2013-05-05 08:00:00,7512.0 -2013-05-05 09:00:00,7791.0 -2013-05-05 10:00:00,8202.0 -2013-05-05 11:00:00,8483.0 -2013-05-05 12:00:00,8739.0 -2013-05-05 13:00:00,8830.0 -2013-05-05 14:00:00,8939.0 -2013-05-05 15:00:00,8958.0 -2013-05-05 16:00:00,8963.0 -2013-05-05 17:00:00,8980.0 -2013-05-05 18:00:00,9021.0 -2013-05-05 19:00:00,9041.0 -2013-05-05 20:00:00,9066.0 -2013-05-05 21:00:00,9207.0 -2013-05-05 22:00:00,9788.0 -2013-05-05 23:00:00,9550.0 -2013-05-06 00:00:00,8973.0 -2013-05-04 01:00:00,9402.0 -2013-05-04 02:00:00,8804.0 -2013-05-04 03:00:00,8461.0 -2013-05-04 04:00:00,8249.0 -2013-05-04 05:00:00,8144.0 -2013-05-04 06:00:00,8165.0 -2013-05-04 07:00:00,8303.0 -2013-05-04 08:00:00,8476.0 -2013-05-04 09:00:00,8865.0 -2013-05-04 10:00:00,9322.0 -2013-05-04 11:00:00,9546.0 -2013-05-04 12:00:00,9672.0 -2013-05-04 13:00:00,9638.0 -2013-05-04 14:00:00,9545.0 -2013-05-04 15:00:00,9439.0 -2013-05-04 16:00:00,9345.0 -2013-05-04 17:00:00,9280.0 -2013-05-04 18:00:00,9257.0 -2013-05-04 19:00:00,9204.0 -2013-05-04 20:00:00,9197.0 -2013-05-04 21:00:00,9369.0 -2013-05-04 22:00:00,9835.0 -2013-05-04 23:00:00,9659.0 -2013-05-05 00:00:00,9155.0 -2013-05-03 01:00:00,9187.0 -2013-05-03 02:00:00,8691.0 -2013-05-03 03:00:00,8427.0 -2013-05-03 04:00:00,8256.0 -2013-05-03 05:00:00,8198.0 -2013-05-03 06:00:00,8416.0 -2013-05-03 07:00:00,9091.0 -2013-05-03 08:00:00,9986.0 -2013-05-03 09:00:00,10815.0 -2013-05-03 10:00:00,11199.0 -2013-05-03 11:00:00,11334.0 -2013-05-03 12:00:00,11425.0 -2013-05-03 13:00:00,11358.0 -2013-05-03 14:00:00,11264.0 -2013-05-03 15:00:00,11234.0 -2013-05-03 16:00:00,11166.0 -2013-05-03 17:00:00,11025.0 -2013-05-03 18:00:00,10860.0 -2013-05-03 19:00:00,10753.0 -2013-05-03 20:00:00,10690.0 -2013-05-03 21:00:00,10903.0 -2013-05-03 22:00:00,11121.0 -2013-05-03 23:00:00,10799.0 -2013-05-04 00:00:00,10119.0 -2013-05-02 01:00:00,10221.0 -2013-05-02 02:00:00,9439.0 -2013-05-02 03:00:00,8848.0 -2013-05-02 04:00:00,8478.0 -2013-05-02 05:00:00,8270.0 -2013-05-02 06:00:00,8344.0 -2013-05-02 07:00:00,8891.0 -2013-05-02 08:00:00,9564.0 -2013-05-02 09:00:00,10380.0 -2013-05-02 10:00:00,10704.0 -2013-05-02 11:00:00,10595.0 -2013-05-02 12:00:00,10958.0 -2013-05-02 13:00:00,11164.0 -2013-05-02 14:00:00,11075.0 -2013-05-02 15:00:00,11136.0 -2013-05-02 16:00:00,11021.0 -2013-05-02 17:00:00,10870.0 -2013-05-02 18:00:00,10752.0 -2013-05-02 19:00:00,10660.0 -2013-05-02 20:00:00,10595.0 -2013-05-02 21:00:00,10893.0 -2013-05-02 22:00:00,11124.0 -2013-05-02 23:00:00,10684.0 -2013-05-03 00:00:00,9974.0 -2013-05-01 01:00:00,10247.0 -2013-05-01 02:00:00,9575.0 -2013-05-01 03:00:00,9122.0 -2013-05-01 04:00:00,8795.0 -2013-05-01 05:00:00,8631.0 -2013-05-01 06:00:00,8729.0 -2013-05-01 07:00:00,9304.0 -2013-05-01 08:00:00,10121.0 -2013-05-01 09:00:00,11108.0 -2013-05-01 10:00:00,11741.0 -2013-05-01 11:00:00,12206.0 -2013-05-01 12:00:00,12699.0 -2013-05-01 13:00:00,12937.0 -2013-05-01 14:00:00,13133.0 -2013-05-01 15:00:00,13322.0 -2013-05-01 16:00:00,13421.0 -2013-05-01 17:00:00,13461.0 -2013-05-01 18:00:00,13409.0 -2013-05-01 19:00:00,13194.0 -2013-05-01 20:00:00,12763.0 -2013-05-01 21:00:00,12566.0 -2013-05-01 22:00:00,12859.0 -2013-05-01 23:00:00,12318.0 -2013-05-02 00:00:00,11269.0 -2013-04-30 01:00:00,9056.0 -2013-04-30 02:00:00,8534.0 -2013-04-30 03:00:00,8221.0 -2013-04-30 04:00:00,8184.0 -2013-04-30 05:00:00,8104.0 -2013-04-30 06:00:00,8282.0 -2013-04-30 07:00:00,8923.0 -2013-04-30 08:00:00,9737.0 -2013-04-30 09:00:00,10571.0 -2013-04-30 10:00:00,11015.0 -2013-04-30 11:00:00,11298.0 -2013-04-30 12:00:00,11622.0 -2013-04-30 13:00:00,11864.0 -2013-04-30 14:00:00,12064.0 -2013-04-30 15:00:00,12266.0 -2013-04-30 16:00:00,12416.0 -2013-04-30 17:00:00,12453.0 -2013-04-30 18:00:00,12445.0 -2013-04-30 19:00:00,12310.0 -2013-04-30 20:00:00,12014.0 -2013-04-30 21:00:00,12034.0 -2013-04-30 22:00:00,12474.0 -2013-04-30 23:00:00,12056.0 -2013-05-01 00:00:00,11139.0 -2013-04-29 01:00:00,8415.0 -2013-04-29 02:00:00,8061.0 -2013-04-29 03:00:00,7772.0 -2013-04-29 04:00:00,7678.0 -2013-04-29 05:00:00,7700.0 -2013-04-29 06:00:00,7931.0 -2013-04-29 07:00:00,8661.0 -2013-04-29 08:00:00,9447.0 -2013-04-29 09:00:00,10282.0 -2013-04-29 10:00:00,10733.0 -2013-04-29 11:00:00,10943.0 -2013-04-29 12:00:00,11193.0 -2013-04-29 13:00:00,11294.0 -2013-04-29 14:00:00,11382.0 -2013-04-29 15:00:00,11482.0 -2013-04-29 16:00:00,11432.0 -2013-04-29 17:00:00,11270.0 -2013-04-29 18:00:00,11141.0 -2013-04-29 19:00:00,11017.0 -2013-04-29 20:00:00,10784.0 -2013-04-29 21:00:00,10881.0 -2013-04-29 22:00:00,11370.0 -2013-04-29 23:00:00,10922.0 -2013-04-30 00:00:00,9868.0 -2013-04-28 01:00:00,8566.0 -2013-04-28 02:00:00,8140.0 -2013-04-28 03:00:00,7813.0 -2013-04-28 04:00:00,7638.0 -2013-04-28 05:00:00,7544.0 -2013-04-28 06:00:00,7535.0 -2013-04-28 07:00:00,7666.0 -2013-04-28 08:00:00,7656.0 -2013-04-28 09:00:00,7881.0 -2013-04-28 10:00:00,8235.0 -2013-04-28 11:00:00,8556.0 -2013-04-28 12:00:00,8789.0 -2013-04-28 13:00:00,8870.0 -2013-04-28 14:00:00,8878.0 -2013-04-28 15:00:00,8838.0 -2013-04-28 16:00:00,8820.0 -2013-04-28 17:00:00,8781.0 -2013-04-28 18:00:00,8836.0 -2013-04-28 19:00:00,8899.0 -2013-04-28 20:00:00,9036.0 -2013-04-28 21:00:00,9272.0 -2013-04-28 22:00:00,9806.0 -2013-04-28 23:00:00,9578.0 -2013-04-29 00:00:00,9001.0 -2013-04-27 01:00:00,9066.0 -2013-04-27 02:00:00,8537.0 -2013-04-27 03:00:00,8219.0 -2013-04-27 04:00:00,8010.0 -2013-04-27 05:00:00,7947.0 -2013-04-27 06:00:00,7995.0 -2013-04-27 07:00:00,8258.0 -2013-04-27 08:00:00,8354.0 -2013-04-27 09:00:00,8739.0 -2013-04-27 10:00:00,9102.0 -2013-04-27 11:00:00,9366.0 -2013-04-27 12:00:00,9505.0 -2013-04-27 13:00:00,9478.0 -2013-04-27 14:00:00,9439.0 -2013-04-27 15:00:00,9273.0 -2013-04-27 16:00:00,9187.0 -2013-04-27 17:00:00,9155.0 -2013-04-27 18:00:00,9093.0 -2013-04-27 19:00:00,9091.0 -2013-04-27 20:00:00,9076.0 -2013-04-27 21:00:00,9430.0 -2013-04-27 22:00:00,9762.0 -2013-04-27 23:00:00,9571.0 -2013-04-28 00:00:00,9075.0 -2013-04-26 01:00:00,9576.0 -2013-04-26 02:00:00,9113.0 -2013-04-26 03:00:00,8805.0 -2013-04-26 04:00:00,8661.0 -2013-04-26 05:00:00,8662.0 -2013-04-26 06:00:00,8898.0 -2013-04-26 07:00:00,9657.0 -2013-04-26 08:00:00,10404.0 -2013-04-26 09:00:00,10967.0 -2013-04-26 10:00:00,11067.0 -2013-04-26 11:00:00,11210.0 -2013-04-26 12:00:00,11275.0 -2013-04-26 13:00:00,11239.0 -2013-04-26 14:00:00,11201.0 -2013-04-26 15:00:00,11177.0 -2013-04-26 16:00:00,11012.0 -2013-04-26 17:00:00,10825.0 -2013-04-26 18:00:00,10649.0 -2013-04-26 19:00:00,10448.0 -2013-04-26 20:00:00,10228.0 -2013-04-26 21:00:00,10331.0 -2013-04-26 22:00:00,10744.0 -2013-04-26 23:00:00,10474.0 -2013-04-27 00:00:00,9861.0 -2013-04-25 01:00:00,9666.0 -2013-04-25 02:00:00,9194.0 -2013-04-25 03:00:00,8924.0 -2013-04-25 04:00:00,8755.0 -2013-04-25 05:00:00,8751.0 -2013-04-25 06:00:00,8994.0 -2013-04-25 07:00:00,9686.0 -2013-04-25 08:00:00,10515.0 -2013-04-25 09:00:00,11152.0 -2013-04-25 10:00:00,11383.0 -2013-04-25 11:00:00,11510.0 -2013-04-25 12:00:00,11585.0 -2013-04-25 13:00:00,11559.0 -2013-04-25 14:00:00,11454.0 -2013-04-25 15:00:00,11378.0 -2013-04-25 16:00:00,11238.0 -2013-04-25 17:00:00,11075.0 -2013-04-25 18:00:00,10904.0 -2013-04-25 19:00:00,10731.0 -2013-04-25 20:00:00,10592.0 -2013-04-25 21:00:00,10825.0 -2013-04-25 22:00:00,11376.0 -2013-04-25 23:00:00,11073.0 -2013-04-26 00:00:00,10328.0 -2013-04-24 01:00:00,9638.0 -2013-04-24 02:00:00,9177.0 -2013-04-24 03:00:00,8894.0 -2013-04-24 04:00:00,8735.0 -2013-04-24 05:00:00,8728.0 -2013-04-24 06:00:00,8948.0 -2013-04-24 07:00:00,9667.0 -2013-04-24 08:00:00,10653.0 -2013-04-24 09:00:00,11403.0 -2013-04-24 10:00:00,11751.0 -2013-04-24 11:00:00,11829.0 -2013-04-24 12:00:00,11747.0 -2013-04-24 13:00:00,11607.0 -2013-04-24 14:00:00,11484.0 -2013-04-24 15:00:00,11424.0 -2013-04-24 16:00:00,11268.0 -2013-04-24 17:00:00,11094.0 -2013-04-24 18:00:00,10955.0 -2013-04-24 19:00:00,10803.0 -2013-04-24 20:00:00,10595.0 -2013-04-24 21:00:00,10886.0 -2013-04-24 22:00:00,11415.0 -2013-04-24 23:00:00,11102.0 -2013-04-25 00:00:00,10372.0 -2013-04-23 01:00:00,9188.0 -2013-04-23 02:00:00,8680.0 -2013-04-23 03:00:00,8360.0 -2013-04-23 04:00:00,8190.0 -2013-04-23 05:00:00,8152.0 -2013-04-23 06:00:00,8356.0 -2013-04-23 07:00:00,9084.0 -2013-04-23 08:00:00,9988.0 -2013-04-23 09:00:00,10652.0 -2013-04-23 10:00:00,11000.0 -2013-04-23 11:00:00,11151.0 -2013-04-23 12:00:00,11313.0 -2013-04-23 13:00:00,11391.0 -2013-04-23 14:00:00,11450.0 -2013-04-23 15:00:00,11587.0 -2013-04-23 16:00:00,11478.0 -2013-04-23 17:00:00,11414.0 -2013-04-23 18:00:00,11488.0 -2013-04-23 19:00:00,11579.0 -2013-04-23 20:00:00,11573.0 -2013-04-23 21:00:00,11724.0 -2013-04-23 22:00:00,11691.0 -2013-04-23 23:00:00,11201.0 -2013-04-24 00:00:00,10356.0 -2013-04-22 01:00:00,8938.0 -2013-04-22 02:00:00,8613.0 -2013-04-22 03:00:00,8407.0 -2013-04-22 04:00:00,8350.0 -2013-04-22 05:00:00,8353.0 -2013-04-22 06:00:00,8677.0 -2013-04-22 07:00:00,9493.0 -2013-04-22 08:00:00,10332.0 -2013-04-22 09:00:00,10942.0 -2013-04-22 10:00:00,11117.0 -2013-04-22 11:00:00,11138.0 -2013-04-22 12:00:00,11200.0 -2013-04-22 13:00:00,11216.0 -2013-04-22 14:00:00,11139.0 -2013-04-22 15:00:00,11180.0 -2013-04-22 16:00:00,11100.0 -2013-04-22 17:00:00,10916.0 -2013-04-22 18:00:00,10769.0 -2013-04-22 19:00:00,10636.0 -2013-04-22 20:00:00,10454.0 -2013-04-22 21:00:00,10760.0 -2013-04-22 22:00:00,11179.0 -2013-04-22 23:00:00,10755.0 -2013-04-23 00:00:00,9955.0 -2013-04-21 01:00:00,9481.0 -2013-04-21 02:00:00,9126.0 -2013-04-21 03:00:00,8851.0 -2013-04-21 04:00:00,8697.0 -2013-04-21 05:00:00,8610.0 -2013-04-21 06:00:00,8683.0 -2013-04-21 07:00:00,8791.0 -2013-04-21 08:00:00,8764.0 -2013-04-21 09:00:00,8934.0 -2013-04-21 10:00:00,9213.0 -2013-04-21 11:00:00,9252.0 -2013-04-21 12:00:00,9366.0 -2013-04-21 13:00:00,9349.0 -2013-04-21 14:00:00,9284.0 -2013-04-21 15:00:00,9235.0 -2013-04-21 16:00:00,9126.0 -2013-04-21 17:00:00,9047.0 -2013-04-21 18:00:00,9058.0 -2013-04-21 19:00:00,9103.0 -2013-04-21 20:00:00,9249.0 -2013-04-21 21:00:00,9662.0 -2013-04-21 22:00:00,10228.0 -2013-04-21 23:00:00,10027.0 -2013-04-22 00:00:00,9502.0 -2013-04-20 01:00:00,10280.0 -2013-04-20 02:00:00,9729.0 -2013-04-20 03:00:00,9393.0 -2013-04-20 04:00:00,9181.0 -2013-04-20 05:00:00,9081.0 -2013-04-20 06:00:00,9146.0 -2013-04-20 07:00:00,9381.0 -2013-04-20 08:00:00,9561.0 -2013-04-20 09:00:00,9823.0 -2013-04-20 10:00:00,10133.0 -2013-04-20 11:00:00,10290.0 -2013-04-20 12:00:00,10349.0 -2013-04-20 13:00:00,10281.0 -2013-04-20 14:00:00,10118.0 -2013-04-20 15:00:00,9916.0 -2013-04-20 16:00:00,9689.0 -2013-04-20 17:00:00,9577.0 -2013-04-20 18:00:00,9514.0 -2013-04-20 19:00:00,9551.0 -2013-04-20 20:00:00,9581.0 -2013-04-20 21:00:00,9991.0 -2013-04-20 22:00:00,10576.0 -2013-04-20 23:00:00,10403.0 -2013-04-21 00:00:00,10006.0 -2013-04-19 01:00:00,9836.0 -2013-04-19 02:00:00,9304.0 -2013-04-19 03:00:00,9073.0 -2013-04-19 04:00:00,8938.0 -2013-04-19 05:00:00,8930.0 -2013-04-19 06:00:00,9161.0 -2013-04-19 07:00:00,9857.0 -2013-04-19 08:00:00,10879.0 -2013-04-19 09:00:00,11623.0 -2013-04-19 10:00:00,11949.0 -2013-04-19 11:00:00,12057.0 -2013-04-19 12:00:00,12261.0 -2013-04-19 13:00:00,12245.0 -2013-04-19 14:00:00,12186.0 -2013-04-19 15:00:00,12166.0 -2013-04-19 16:00:00,12068.0 -2013-04-19 17:00:00,11953.0 -2013-04-19 18:00:00,11915.0 -2013-04-19 19:00:00,11758.0 -2013-04-19 20:00:00,11695.0 -2013-04-19 21:00:00,11928.0 -2013-04-19 22:00:00,12012.0 -2013-04-19 23:00:00,11665.0 -2013-04-20 00:00:00,10939.0 -2013-04-18 01:00:00,9869.0 -2013-04-18 02:00:00,9372.0 -2013-04-18 03:00:00,9091.0 -2013-04-18 04:00:00,8916.0 -2013-04-18 05:00:00,8880.0 -2013-04-18 06:00:00,9118.0 -2013-04-18 07:00:00,9762.0 -2013-04-18 08:00:00,10783.0 -2013-04-18 09:00:00,11556.0 -2013-04-18 10:00:00,11694.0 -2013-04-18 11:00:00,11876.0 -2013-04-18 12:00:00,12003.0 -2013-04-18 13:00:00,11924.0 -2013-04-18 14:00:00,11924.0 -2013-04-18 15:00:00,11917.0 -2013-04-18 16:00:00,11778.0 -2013-04-18 17:00:00,11658.0 -2013-04-18 18:00:00,11612.0 -2013-04-18 19:00:00,11660.0 -2013-04-18 20:00:00,11601.0 -2013-04-18 21:00:00,11844.0 -2013-04-18 22:00:00,11847.0 -2013-04-18 23:00:00,11323.0 -2013-04-19 00:00:00,10542.0 -2013-04-17 01:00:00,9544.0 -2013-04-17 02:00:00,9050.0 -2013-04-17 03:00:00,8768.0 -2013-04-17 04:00:00,8600.0 -2013-04-17 05:00:00,8600.0 -2013-04-17 06:00:00,8826.0 -2013-04-17 07:00:00,9539.0 -2013-04-17 08:00:00,10470.0 -2013-04-17 09:00:00,11186.0 -2013-04-17 10:00:00,11566.0 -2013-04-17 11:00:00,11829.0 -2013-04-17 12:00:00,11950.0 -2013-04-17 13:00:00,12021.0 -2013-04-17 14:00:00,12070.0 -2013-04-17 15:00:00,12124.0 -2013-04-17 16:00:00,12097.0 -2013-04-17 17:00:00,12196.0 -2013-04-17 18:00:00,12255.0 -2013-04-17 19:00:00,12236.0 -2013-04-17 20:00:00,11978.0 -2013-04-17 21:00:00,12073.0 -2013-04-17 22:00:00,11901.0 -2013-04-17 23:00:00,11389.0 -2013-04-18 00:00:00,10584.0 -2013-04-16 01:00:00,9337.0 -2013-04-16 02:00:00,8816.0 -2013-04-16 03:00:00,8545.0 -2013-04-16 04:00:00,8416.0 -2013-04-16 05:00:00,8385.0 -2013-04-16 06:00:00,8593.0 -2013-04-16 07:00:00,9331.0 -2013-04-16 08:00:00,10287.0 -2013-04-16 09:00:00,10939.0 -2013-04-16 10:00:00,11073.0 -2013-04-16 11:00:00,11171.0 -2013-04-16 12:00:00,11241.0 -2013-04-16 13:00:00,11223.0 -2013-04-16 14:00:00,11192.0 -2013-04-16 15:00:00,11176.0 -2013-04-16 16:00:00,11091.0 -2013-04-16 17:00:00,10937.0 -2013-04-16 18:00:00,10848.0 -2013-04-16 19:00:00,10830.0 -2013-04-16 20:00:00,10805.0 -2013-04-16 21:00:00,11260.0 -2013-04-16 22:00:00,11472.0 -2013-04-16 23:00:00,11020.0 -2013-04-17 00:00:00,10287.0 -2013-04-15 01:00:00,8757.0 -2013-04-15 02:00:00,8397.0 -2013-04-15 03:00:00,8136.0 -2013-04-15 04:00:00,7992.0 -2013-04-15 05:00:00,7997.0 -2013-04-15 06:00:00,8148.0 -2013-04-15 07:00:00,8924.0 -2013-04-15 08:00:00,9982.0 -2013-04-15 09:00:00,10687.0 -2013-04-15 10:00:00,11062.0 -2013-04-15 11:00:00,11198.0 -2013-04-15 12:00:00,11311.0 -2013-04-15 13:00:00,11328.0 -2013-04-15 14:00:00,11329.0 -2013-04-15 15:00:00,11370.0 -2013-04-15 16:00:00,11264.0 -2013-04-15 17:00:00,11193.0 -2013-04-15 18:00:00,11252.0 -2013-04-15 19:00:00,11246.0 -2013-04-15 20:00:00,11230.0 -2013-04-15 21:00:00,11497.0 -2013-04-15 22:00:00,11400.0 -2013-04-15 23:00:00,10895.0 -2013-04-16 00:00:00,10063.0 -2013-04-14 01:00:00,9397.0 -2013-04-14 02:00:00,8930.0 -2013-04-14 03:00:00,8686.0 -2013-04-14 04:00:00,8460.0 -2013-04-14 05:00:00,8456.0 -2013-04-14 06:00:00,8466.0 -2013-04-14 07:00:00,8661.0 -2013-04-14 08:00:00,8756.0 -2013-04-14 09:00:00,8863.0 -2013-04-14 10:00:00,9146.0 -2013-04-14 11:00:00,9489.0 -2013-04-14 12:00:00,9537.0 -2013-04-14 13:00:00,9425.0 -2013-04-14 14:00:00,9329.0 -2013-04-14 15:00:00,9231.0 -2013-04-14 16:00:00,9128.0 -2013-04-14 17:00:00,9059.0 -2013-04-14 18:00:00,9074.0 -2013-04-14 19:00:00,9185.0 -2013-04-14 20:00:00,9266.0 -2013-04-14 21:00:00,9827.0 -2013-04-14 22:00:00,10213.0 -2013-04-14 23:00:00,9925.0 -2013-04-15 00:00:00,9331.0 -2013-04-13 01:00:00,10025.0 -2013-04-13 02:00:00,9471.0 -2013-04-13 03:00:00,9139.0 -2013-04-13 04:00:00,8948.0 -2013-04-13 05:00:00,8916.0 -2013-04-13 06:00:00,8927.0 -2013-04-13 07:00:00,9227.0 -2013-04-13 08:00:00,9592.0 -2013-04-13 09:00:00,9905.0 -2013-04-13 10:00:00,10392.0 -2013-04-13 11:00:00,10603.0 -2013-04-13 12:00:00,10699.0 -2013-04-13 13:00:00,10667.0 -2013-04-13 14:00:00,10504.0 -2013-04-13 15:00:00,10208.0 -2013-04-13 16:00:00,10026.0 -2013-04-13 17:00:00,9848.0 -2013-04-13 18:00:00,9720.0 -2013-04-13 19:00:00,9710.0 -2013-04-13 20:00:00,9800.0 -2013-04-13 21:00:00,10227.0 -2013-04-13 22:00:00,10608.0 -2013-04-13 23:00:00,10435.0 -2013-04-14 00:00:00,9905.0 -2013-04-12 01:00:00,9949.0 -2013-04-12 02:00:00,9407.0 -2013-04-12 03:00:00,9103.0 -2013-04-12 04:00:00,8931.0 -2013-04-12 05:00:00,8972.0 -2013-04-12 06:00:00,9241.0 -2013-04-12 07:00:00,9924.0 -2013-04-12 08:00:00,11012.0 -2013-04-12 09:00:00,11620.0 -2013-04-12 10:00:00,11945.0 -2013-04-12 11:00:00,12023.0 -2013-04-12 12:00:00,12057.0 -2013-04-12 13:00:00,11915.0 -2013-04-12 14:00:00,11799.0 -2013-04-12 15:00:00,11823.0 -2013-04-12 16:00:00,11658.0 -2013-04-12 17:00:00,11658.0 -2013-04-12 18:00:00,11675.0 -2013-04-12 19:00:00,11643.0 -2013-04-12 20:00:00,11663.0 -2013-04-12 21:00:00,11852.0 -2013-04-12 22:00:00,11794.0 -2013-04-12 23:00:00,11468.0 -2013-04-13 00:00:00,10734.0 -2013-04-11 01:00:00,10222.0 -2013-04-11 02:00:00,9787.0 -2013-04-11 03:00:00,9466.0 -2013-04-11 04:00:00,9229.0 -2013-04-11 05:00:00,9139.0 -2013-04-11 06:00:00,9315.0 -2013-04-11 07:00:00,10013.0 -2013-04-11 08:00:00,11122.0 -2013-04-11 09:00:00,11767.0 -2013-04-11 10:00:00,12001.0 -2013-04-11 11:00:00,12128.0 -2013-04-11 12:00:00,12204.0 -2013-04-11 13:00:00,12119.0 -2013-04-11 14:00:00,12058.0 -2013-04-11 15:00:00,11997.0 -2013-04-11 16:00:00,11856.0 -2013-04-11 17:00:00,11729.0 -2013-04-11 18:00:00,11724.0 -2013-04-11 19:00:00,11732.0 -2013-04-11 20:00:00,11742.0 -2013-04-11 21:00:00,11928.0 -2013-04-11 22:00:00,11908.0 -2013-04-11 23:00:00,11467.0 -2013-04-12 00:00:00,10687.0 -2013-04-10 01:00:00,9710.0 -2013-04-10 02:00:00,9233.0 -2013-04-10 03:00:00,8933.0 -2013-04-10 04:00:00,8743.0 -2013-04-10 05:00:00,8743.0 -2013-04-10 06:00:00,9011.0 -2013-04-10 07:00:00,9776.0 -2013-04-10 08:00:00,10985.0 -2013-04-10 09:00:00,11812.0 -2013-04-10 10:00:00,12083.0 -2013-04-10 11:00:00,11936.0 -2013-04-10 12:00:00,12113.0 -2013-04-10 13:00:00,12078.0 -2013-04-10 14:00:00,12060.0 -2013-04-10 15:00:00,12133.0 -2013-04-10 16:00:00,12016.0 -2013-04-10 17:00:00,11936.0 -2013-04-10 18:00:00,11923.0 -2013-04-10 19:00:00,11955.0 -2013-04-10 20:00:00,11874.0 -2013-04-10 21:00:00,12184.0 -2013-04-10 22:00:00,12153.0 -2013-04-10 23:00:00,11675.0 -2013-04-11 00:00:00,10911.0 -2013-04-09 01:00:00,9322.0 -2013-04-09 02:00:00,8838.0 -2013-04-09 03:00:00,8595.0 -2013-04-09 04:00:00,8462.0 -2013-04-09 05:00:00,8473.0 -2013-04-09 06:00:00,8701.0 -2013-04-09 07:00:00,9425.0 -2013-04-09 08:00:00,10477.0 -2013-04-09 09:00:00,11227.0 -2013-04-09 10:00:00,11576.0 -2013-04-09 11:00:00,11730.0 -2013-04-09 12:00:00,11808.0 -2013-04-09 13:00:00,11742.0 -2013-04-09 14:00:00,11571.0 -2013-04-09 15:00:00,11531.0 -2013-04-09 16:00:00,11364.0 -2013-04-09 17:00:00,11241.0 -2013-04-09 18:00:00,11178.0 -2013-04-09 19:00:00,11182.0 -2013-04-09 20:00:00,11219.0 -2013-04-09 21:00:00,11719.0 -2013-04-09 22:00:00,11801.0 -2013-04-09 23:00:00,11332.0 -2013-04-10 00:00:00,10504.0 -2013-04-08 01:00:00,8857.0 -2013-04-08 02:00:00,8448.0 -2013-04-08 03:00:00,8275.0 -2013-04-08 04:00:00,8172.0 -2013-04-08 05:00:00,8204.0 -2013-04-08 06:00:00,8457.0 -2013-04-08 07:00:00,9244.0 -2013-04-08 08:00:00,10483.0 -2013-04-08 09:00:00,11205.0 -2013-04-08 10:00:00,11391.0 -2013-04-08 11:00:00,11480.0 -2013-04-08 12:00:00,11482.0 -2013-04-08 13:00:00,11408.0 -2013-04-08 14:00:00,11328.0 -2013-04-08 15:00:00,11247.0 -2013-04-08 16:00:00,11090.0 -2013-04-08 17:00:00,10959.0 -2013-04-08 18:00:00,10829.0 -2013-04-08 19:00:00,10673.0 -2013-04-08 20:00:00,10551.0 -2013-04-08 21:00:00,11038.0 -2013-04-08 22:00:00,11225.0 -2013-04-08 23:00:00,10793.0 -2013-04-09 00:00:00,10038.0 -2013-04-07 01:00:00,8869.0 -2013-04-07 02:00:00,8452.0 -2013-04-07 03:00:00,8119.0 -2013-04-07 04:00:00,7969.0 -2013-04-07 05:00:00,7850.0 -2013-04-07 06:00:00,7891.0 -2013-04-07 07:00:00,7974.0 -2013-04-07 08:00:00,8145.0 -2013-04-07 09:00:00,8227.0 -2013-04-07 10:00:00,8495.0 -2013-04-07 11:00:00,8715.0 -2013-04-07 12:00:00,8830.0 -2013-04-07 13:00:00,8852.0 -2013-04-07 14:00:00,8890.0 -2013-04-07 15:00:00,8814.0 -2013-04-07 16:00:00,8792.0 -2013-04-07 17:00:00,8769.0 -2013-04-07 18:00:00,8819.0 -2013-04-07 19:00:00,8907.0 -2013-04-07 20:00:00,9097.0 -2013-04-07 21:00:00,9701.0 -2013-04-07 22:00:00,10148.0 -2013-04-07 23:00:00,9894.0 -2013-04-08 00:00:00,9369.0 -2013-04-06 01:00:00,9854.0 -2013-04-06 02:00:00,9408.0 -2013-04-06 03:00:00,9102.0 -2013-04-06 04:00:00,8945.0 -2013-04-06 05:00:00,8866.0 -2013-04-06 06:00:00,8880.0 -2013-04-06 07:00:00,9164.0 -2013-04-06 08:00:00,9521.0 -2013-04-06 09:00:00,9763.0 -2013-04-06 10:00:00,10151.0 -2013-04-06 11:00:00,10269.0 -2013-04-06 12:00:00,10354.0 -2013-04-06 13:00:00,10238.0 -2013-04-06 14:00:00,10020.0 -2013-04-06 15:00:00,9809.0 -2013-04-06 16:00:00,9635.0 -2013-04-06 17:00:00,9523.0 -2013-04-06 18:00:00,9597.0 -2013-04-06 19:00:00,9659.0 -2013-04-06 20:00:00,9737.0 -2013-04-06 21:00:00,10210.0 -2013-04-06 22:00:00,10226.0 -2013-04-06 23:00:00,9978.0 -2013-04-07 00:00:00,9454.0 -2013-04-05 01:00:00,9694.0 -2013-04-05 02:00:00,9167.0 -2013-04-05 03:00:00,8923.0 -2013-04-05 04:00:00,8719.0 -2013-04-05 05:00:00,8782.0 -2013-04-05 06:00:00,9034.0 -2013-04-05 07:00:00,9772.0 -2013-04-05 08:00:00,10730.0 -2013-04-05 09:00:00,11229.0 -2013-04-05 10:00:00,11433.0 -2013-04-05 11:00:00,11444.0 -2013-04-05 12:00:00,11463.0 -2013-04-05 13:00:00,11371.0 -2013-04-05 14:00:00,11317.0 -2013-04-05 15:00:00,11241.0 -2013-04-05 16:00:00,11055.0 -2013-04-05 17:00:00,10815.0 -2013-04-05 18:00:00,10685.0 -2013-04-05 19:00:00,10619.0 -2013-04-05 20:00:00,10783.0 -2013-04-05 21:00:00,11374.0 -2013-04-05 22:00:00,11539.0 -2013-04-05 23:00:00,11275.0 -2013-04-06 00:00:00,10613.0 -2013-04-04 01:00:00,10172.0 -2013-04-04 02:00:00,9691.0 -2013-04-04 03:00:00,9415.0 -2013-04-04 04:00:00,9292.0 -2013-04-04 05:00:00,9310.0 -2013-04-04 06:00:00,9569.0 -2013-04-04 07:00:00,10325.0 -2013-04-04 08:00:00,11330.0 -2013-04-04 09:00:00,11742.0 -2013-04-04 10:00:00,11836.0 -2013-04-04 11:00:00,11681.0 -2013-04-04 12:00:00,11732.0 -2013-04-04 13:00:00,11601.0 -2013-04-04 14:00:00,11434.0 -2013-04-04 15:00:00,11395.0 -2013-04-04 16:00:00,11205.0 -2013-04-04 17:00:00,11024.0 -2013-04-04 18:00:00,10847.0 -2013-04-04 19:00:00,10719.0 -2013-04-04 20:00:00,10753.0 -2013-04-04 21:00:00,11273.0 -2013-04-04 22:00:00,11427.0 -2013-04-04 23:00:00,11059.0 -2013-04-05 00:00:00,10389.0 -2013-04-03 01:00:00,10157.0 -2013-04-03 02:00:00,9689.0 -2013-04-03 03:00:00,9431.0 -2013-04-03 04:00:00,9312.0 -2013-04-03 05:00:00,9351.0 -2013-04-03 06:00:00,9608.0 -2013-04-03 07:00:00,10323.0 -2013-04-03 08:00:00,11373.0 -2013-04-03 09:00:00,11810.0 -2013-04-03 10:00:00,11859.0 -2013-04-03 11:00:00,11820.0 -2013-04-03 12:00:00,11805.0 -2013-04-03 13:00:00,11717.0 -2013-04-03 14:00:00,11620.0 -2013-04-03 15:00:00,11552.0 -2013-04-03 16:00:00,11407.0 -2013-04-03 17:00:00,11224.0 -2013-04-03 18:00:00,11073.0 -2013-04-03 19:00:00,11085.0 -2013-04-03 20:00:00,11128.0 -2013-04-03 21:00:00,11755.0 -2013-04-03 22:00:00,11973.0 -2013-04-03 23:00:00,11606.0 -2013-04-04 00:00:00,10884.0 -2013-04-02 01:00:00,10171.0 -2013-04-02 02:00:00,9729.0 -2013-04-02 03:00:00,9469.0 -2013-04-02 04:00:00,9366.0 -2013-04-02 05:00:00,9379.0 -2013-04-02 06:00:00,9648.0 -2013-04-02 07:00:00,10372.0 -2013-04-02 08:00:00,11449.0 -2013-04-02 09:00:00,11854.0 -2013-04-02 10:00:00,11954.0 -2013-04-02 11:00:00,11925.0 -2013-04-02 12:00:00,11901.0 -2013-04-02 13:00:00,11818.0 -2013-04-02 14:00:00,11732.0 -2013-04-02 15:00:00,11656.0 -2013-04-02 16:00:00,11486.0 -2013-04-02 17:00:00,11297.0 -2013-04-02 18:00:00,11217.0 -2013-04-02 19:00:00,11152.0 -2013-04-02 20:00:00,11128.0 -2013-04-02 21:00:00,11741.0 -2013-04-02 22:00:00,11956.0 -2013-04-02 23:00:00,11586.0 -2013-04-03 00:00:00,10869.0 -2013-04-01 01:00:00,8983.0 -2013-04-01 02:00:00,8663.0 -2013-04-01 03:00:00,8503.0 -2013-04-01 04:00:00,8451.0 -2013-04-01 05:00:00,8542.0 -2013-04-01 06:00:00,8861.0 -2013-04-01 07:00:00,9688.0 -2013-04-01 08:00:00,10744.0 -2013-04-01 09:00:00,11325.0 -2013-04-01 10:00:00,11601.0 -2013-04-01 11:00:00,11732.0 -2013-04-01 12:00:00,11854.0 -2013-04-01 13:00:00,11774.0 -2013-04-01 14:00:00,11702.0 -2013-04-01 15:00:00,11648.0 -2013-04-01 16:00:00,11482.0 -2013-04-01 17:00:00,11236.0 -2013-04-01 18:00:00,11153.0 -2013-04-01 19:00:00,11112.0 -2013-04-01 20:00:00,11136.0 -2013-04-01 21:00:00,11761.0 -2013-04-01 22:00:00,11992.0 -2013-04-01 23:00:00,11575.0 -2013-04-02 00:00:00,10865.0 -2013-03-31 01:00:00,8918.0 -2013-03-31 02:00:00,8477.0 -2013-03-31 03:00:00,8140.0 -2013-03-31 04:00:00,7990.0 -2013-03-31 05:00:00,7907.0 -2013-03-31 06:00:00,7923.0 -2013-03-31 07:00:00,8006.0 -2013-03-31 08:00:00,8235.0 -2013-03-31 09:00:00,8298.0 -2013-03-31 10:00:00,8596.0 -2013-03-31 11:00:00,8729.0 -2013-03-31 12:00:00,8787.0 -2013-03-31 13:00:00,8723.0 -2013-03-31 14:00:00,8707.0 -2013-03-31 15:00:00,8585.0 -2013-03-31 16:00:00,8517.0 -2013-03-31 17:00:00,8409.0 -2013-03-31 18:00:00,8437.0 -2013-03-31 19:00:00,8511.0 -2013-03-31 20:00:00,8759.0 -2013-03-31 21:00:00,9561.0 -2013-03-31 22:00:00,9923.0 -2013-03-31 23:00:00,9799.0 -2013-04-01 00:00:00,9403.0 -2013-03-30 01:00:00,9361.0 -2013-03-30 02:00:00,8931.0 -2013-03-30 03:00:00,8749.0 -2013-03-30 04:00:00,8557.0 -2013-03-30 05:00:00,8542.0 -2013-03-30 06:00:00,8591.0 -2013-03-30 07:00:00,8829.0 -2013-03-30 08:00:00,9223.0 -2013-03-30 09:00:00,9318.0 -2013-03-30 10:00:00,9636.0 -2013-03-30 11:00:00,9671.0 -2013-03-30 12:00:00,9700.0 -2013-03-30 13:00:00,9605.0 -2013-03-30 14:00:00,9477.0 -2013-03-30 15:00:00,9335.0 -2013-03-30 16:00:00,9242.0 -2013-03-30 17:00:00,9155.0 -2013-03-30 18:00:00,9170.0 -2013-03-30 19:00:00,9307.0 -2013-03-30 20:00:00,9516.0 -2013-03-30 21:00:00,10072.0 -2013-03-30 22:00:00,10049.0 -2013-03-30 23:00:00,9831.0 -2013-03-31 00:00:00,9447.0 -2013-03-29 01:00:00,9776.0 -2013-03-29 02:00:00,9282.0 -2013-03-29 03:00:00,8986.0 -2013-03-29 04:00:00,8800.0 -2013-03-29 05:00:00,8757.0 -2013-03-29 06:00:00,8956.0 -2013-03-29 07:00:00,9471.0 -2013-03-29 08:00:00,10121.0 -2013-03-29 09:00:00,10359.0 -2013-03-29 10:00:00,10522.0 -2013-03-29 11:00:00,10588.0 -2013-03-29 12:00:00,10569.0 -2013-03-29 13:00:00,10483.0 -2013-03-29 14:00:00,10367.0 -2013-03-29 15:00:00,10254.0 -2013-03-29 16:00:00,10082.0 -2013-03-29 17:00:00,9925.0 -2013-03-29 18:00:00,9840.0 -2013-03-29 19:00:00,9823.0 -2013-03-29 20:00:00,9854.0 -2013-03-29 21:00:00,10438.0 -2013-03-29 22:00:00,10629.0 -2013-03-29 23:00:00,10454.0 -2013-03-30 00:00:00,9949.0 -2013-03-28 01:00:00,10296.0 -2013-03-28 02:00:00,9818.0 -2013-03-28 03:00:00,9577.0 -2013-03-28 04:00:00,9447.0 -2013-03-28 05:00:00,9449.0 -2013-03-28 06:00:00,9702.0 -2013-03-28 07:00:00,10389.0 -2013-03-28 08:00:00,11258.0 -2013-03-28 09:00:00,11612.0 -2013-03-28 10:00:00,11735.0 -2013-03-28 11:00:00,11687.0 -2013-03-28 12:00:00,11613.0 -2013-03-28 13:00:00,11508.0 -2013-03-28 14:00:00,11407.0 -2013-03-28 15:00:00,11342.0 -2013-03-28 16:00:00,11109.0 -2013-03-28 17:00:00,10900.0 -2013-03-28 18:00:00,10753.0 -2013-03-28 19:00:00,10667.0 -2013-03-28 20:00:00,10630.0 -2013-03-28 21:00:00,11266.0 -2013-03-28 22:00:00,11360.0 -2013-03-28 23:00:00,11046.0 -2013-03-29 00:00:00,10412.0 -2013-03-27 01:00:00,10279.0 -2013-03-27 02:00:00,9803.0 -2013-03-27 03:00:00,9497.0 -2013-03-27 04:00:00,9348.0 -2013-03-27 05:00:00,9346.0 -2013-03-27 06:00:00,9583.0 -2013-03-27 07:00:00,10237.0 -2013-03-27 08:00:00,11198.0 -2013-03-27 09:00:00,11641.0 -2013-03-27 10:00:00,11926.0 -2013-03-27 11:00:00,11926.0 -2013-03-27 12:00:00,11944.0 -2013-03-27 13:00:00,11885.0 -2013-03-27 14:00:00,11897.0 -2013-03-27 15:00:00,11864.0 -2013-03-27 16:00:00,11740.0 -2013-03-27 17:00:00,11595.0 -2013-03-27 18:00:00,11527.0 -2013-03-27 19:00:00,11491.0 -2013-03-27 20:00:00,11512.0 -2013-03-27 21:00:00,12006.0 -2013-03-27 22:00:00,11971.0 -2013-03-27 23:00:00,11592.0 -2013-03-28 00:00:00,10944.0 -2013-03-26 01:00:00,10469.0 -2013-03-26 02:00:00,10002.0 -2013-03-26 03:00:00,9686.0 -2013-03-26 04:00:00,9528.0 -2013-03-26 05:00:00,9527.0 -2013-03-26 06:00:00,9751.0 -2013-03-26 07:00:00,10399.0 -2013-03-26 08:00:00,11396.0 -2013-03-26 09:00:00,11801.0 -2013-03-26 10:00:00,12074.0 -2013-03-26 11:00:00,12113.0 -2013-03-26 12:00:00,12034.0 -2013-03-26 13:00:00,11955.0 -2013-03-26 14:00:00,11925.0 -2013-03-26 15:00:00,11871.0 -2013-03-26 16:00:00,11773.0 -2013-03-26 17:00:00,11664.0 -2013-03-26 18:00:00,11637.0 -2013-03-26 19:00:00,11629.0 -2013-03-26 20:00:00,11600.0 -2013-03-26 21:00:00,12059.0 -2013-03-26 22:00:00,12007.0 -2013-03-26 23:00:00,11641.0 -2013-03-27 00:00:00,10934.0 -2013-03-25 01:00:00,10037.0 -2013-03-25 02:00:00,9682.0 -2013-03-25 03:00:00,9474.0 -2013-03-25 04:00:00,9398.0 -2013-03-25 05:00:00,9415.0 -2013-03-25 06:00:00,9713.0 -2013-03-25 07:00:00,10392.0 -2013-03-25 08:00:00,11478.0 -2013-03-25 09:00:00,11990.0 -2013-03-25 10:00:00,12369.0 -2013-03-25 11:00:00,12512.0 -2013-03-25 12:00:00,12554.0 -2013-03-25 13:00:00,12514.0 -2013-03-25 14:00:00,12431.0 -2013-03-25 15:00:00,12402.0 -2013-03-25 16:00:00,12310.0 -2013-03-25 17:00:00,12219.0 -2013-03-25 18:00:00,12176.0 -2013-03-25 19:00:00,12225.0 -2013-03-25 20:00:00,12222.0 -2013-03-25 21:00:00,12598.0 -2013-03-25 22:00:00,12388.0 -2013-03-25 23:00:00,11932.0 -2013-03-26 00:00:00,11190.0 -2013-03-24 01:00:00,9799.0 -2013-03-24 02:00:00,9293.0 -2013-03-24 03:00:00,9102.0 -2013-03-24 04:00:00,8916.0 -2013-03-24 05:00:00,8860.0 -2013-03-24 06:00:00,8874.0 -2013-03-24 07:00:00,9046.0 -2013-03-24 08:00:00,9298.0 -2013-03-24 09:00:00,9443.0 -2013-03-24 10:00:00,9740.0 -2013-03-24 11:00:00,9986.0 -2013-03-24 12:00:00,10033.0 -2013-03-24 13:00:00,10203.0 -2013-03-24 14:00:00,10152.0 -2013-03-24 15:00:00,10200.0 -2013-03-24 16:00:00,10254.0 -2013-03-24 17:00:00,10373.0 -2013-03-24 18:00:00,10476.0 -2013-03-24 19:00:00,10732.0 -2013-03-24 20:00:00,11062.0 -2013-03-24 21:00:00,11448.0 -2013-03-24 22:00:00,11369.0 -2013-03-24 23:00:00,11089.0 -2013-03-25 00:00:00,10584.0 -2013-03-23 01:00:00,10376.0 -2013-03-23 02:00:00,9850.0 -2013-03-23 03:00:00,9554.0 -2013-03-23 04:00:00,9357.0 -2013-03-23 05:00:00,9333.0 -2013-03-23 06:00:00,9385.0 -2013-03-23 07:00:00,9697.0 -2013-03-23 08:00:00,10153.0 -2013-03-23 09:00:00,10330.0 -2013-03-23 10:00:00,10538.0 -2013-03-23 11:00:00,10634.0 -2013-03-23 12:00:00,10599.0 -2013-03-23 13:00:00,10505.0 -2013-03-23 14:00:00,10314.0 -2013-03-23 15:00:00,10087.0 -2013-03-23 16:00:00,9859.0 -2013-03-23 17:00:00,9750.0 -2013-03-23 18:00:00,9681.0 -2013-03-23 19:00:00,9835.0 -2013-03-23 20:00:00,10094.0 -2013-03-23 21:00:00,10906.0 -2013-03-23 22:00:00,10981.0 -2013-03-23 23:00:00,10815.0 -2013-03-24 00:00:00,10324.0 -2013-03-22 01:00:00,10777.0 -2013-03-22 02:00:00,10305.0 -2013-03-22 03:00:00,10036.0 -2013-03-22 04:00:00,9900.0 -2013-03-22 05:00:00,9928.0 -2013-03-22 06:00:00,10191.0 -2013-03-22 07:00:00,10891.0 -2013-03-22 08:00:00,12081.0 -2013-03-22 09:00:00,12457.0 -2013-03-22 10:00:00,12459.0 -2013-03-22 11:00:00,12421.0 -2013-03-22 12:00:00,12344.0 -2013-03-22 13:00:00,12176.0 -2013-03-22 14:00:00,12003.0 -2013-03-22 15:00:00,11884.0 -2013-03-22 16:00:00,11603.0 -2013-03-22 17:00:00,11359.0 -2013-03-22 18:00:00,11189.0 -2013-03-22 19:00:00,11112.0 -2013-03-22 20:00:00,11184.0 -2013-03-22 21:00:00,11869.0 -2013-03-22 22:00:00,11899.0 -2013-03-22 23:00:00,11658.0 -2013-03-23 00:00:00,11042.0 -2013-03-21 01:00:00,11351.0 -2013-03-21 02:00:00,10797.0 -2013-03-21 03:00:00,10542.0 -2013-03-21 04:00:00,10395.0 -2013-03-21 05:00:00,10398.0 -2013-03-21 06:00:00,10649.0 -2013-03-21 07:00:00,11360.0 -2013-03-21 08:00:00,12559.0 -2013-03-21 09:00:00,12973.0 -2013-03-21 10:00:00,13002.0 -2013-03-21 11:00:00,12912.0 -2013-03-21 12:00:00,12866.0 -2013-03-21 13:00:00,12660.0 -2013-03-21 14:00:00,12498.0 -2013-03-21 15:00:00,12394.0 -2013-03-21 16:00:00,12152.0 -2013-03-21 17:00:00,11916.0 -2013-03-21 18:00:00,11786.0 -2013-03-21 19:00:00,11715.0 -2013-03-21 20:00:00,11790.0 -2013-03-21 21:00:00,12526.0 -2013-03-21 22:00:00,12616.0 -2013-03-21 23:00:00,12237.0 -2013-03-22 00:00:00,11501.0 -2013-03-20 01:00:00,11116.0 -2013-03-20 02:00:00,10633.0 -2013-03-20 03:00:00,10383.0 -2013-03-20 04:00:00,10295.0 -2013-03-20 05:00:00,10341.0 -2013-03-20 06:00:00,10631.0 -2013-03-20 07:00:00,11436.0 -2013-03-20 08:00:00,12681.0 -2013-03-20 09:00:00,13146.0 -2013-03-20 10:00:00,13230.0 -2013-03-20 11:00:00,13255.0 -2013-03-20 12:00:00,13249.0 -2013-03-20 13:00:00,13150.0 -2013-03-20 14:00:00,13077.0 -2013-03-20 15:00:00,12998.0 -2013-03-20 16:00:00,12838.0 -2013-03-20 17:00:00,12730.0 -2013-03-20 18:00:00,12708.0 -2013-03-20 19:00:00,12799.0 -2013-03-20 20:00:00,12997.0 -2013-03-20 21:00:00,13514.0 -2013-03-20 22:00:00,13353.0 -2013-03-20 23:00:00,12885.0 -2013-03-21 00:00:00,12085.0 -2013-03-19 01:00:00,11261.0 -2013-03-19 02:00:00,10780.0 -2013-03-19 03:00:00,10545.0 -2013-03-19 04:00:00,10406.0 -2013-03-19 05:00:00,10421.0 -2013-03-19 06:00:00,10699.0 -2013-03-19 07:00:00,11437.0 -2013-03-19 08:00:00,12684.0 -2013-03-19 09:00:00,13214.0 -2013-03-19 10:00:00,13276.0 -2013-03-19 11:00:00,13227.0 -2013-03-19 12:00:00,13209.0 -2013-03-19 13:00:00,13074.0 -2013-03-19 14:00:00,12960.0 -2013-03-19 15:00:00,12863.0 -2013-03-19 16:00:00,12626.0 -2013-03-19 17:00:00,12368.0 -2013-03-19 18:00:00,12239.0 -2013-03-19 19:00:00,12189.0 -2013-03-19 20:00:00,12276.0 -2013-03-19 21:00:00,13007.0 -2013-03-19 22:00:00,13074.0 -2013-03-19 23:00:00,12639.0 -2013-03-20 00:00:00,11841.0 -2013-03-18 01:00:00,10168.0 -2013-03-18 02:00:00,9774.0 -2013-03-18 03:00:00,9602.0 -2013-03-18 04:00:00,9480.0 -2013-03-18 05:00:00,9529.0 -2013-03-18 06:00:00,9811.0 -2013-03-18 07:00:00,10583.0 -2013-03-18 08:00:00,11827.0 -2013-03-18 09:00:00,12555.0 -2013-03-18 10:00:00,12661.0 -2013-03-18 11:00:00,12790.0 -2013-03-18 12:00:00,12908.0 -2013-03-18 13:00:00,12890.0 -2013-03-18 14:00:00,12763.0 -2013-03-18 15:00:00,12723.0 -2013-03-18 16:00:00,12628.0 -2013-03-18 17:00:00,12532.0 -2013-03-18 18:00:00,12518.0 -2013-03-18 19:00:00,12639.0 -2013-03-18 20:00:00,12874.0 -2013-03-18 21:00:00,13244.0 -2013-03-18 22:00:00,13118.0 -2013-03-18 23:00:00,12721.0 -2013-03-19 00:00:00,11997.0 -2013-03-17 01:00:00,10139.0 -2013-03-17 02:00:00,9736.0 -2013-03-17 03:00:00,9452.0 -2013-03-17 04:00:00,9334.0 -2013-03-17 05:00:00,9282.0 -2013-03-17 06:00:00,9328.0 -2013-03-17 07:00:00,9435.0 -2013-03-17 08:00:00,9728.0 -2013-03-17 09:00:00,9786.0 -2013-03-17 10:00:00,10062.0 -2013-03-17 11:00:00,10297.0 -2013-03-17 12:00:00,10409.0 -2013-03-17 13:00:00,10447.0 -2013-03-17 14:00:00,10392.0 -2013-03-17 15:00:00,10282.0 -2013-03-17 16:00:00,10164.0 -2013-03-17 17:00:00,10060.0 -2013-03-17 18:00:00,10128.0 -2013-03-17 19:00:00,10295.0 -2013-03-17 20:00:00,10691.0 -2013-03-17 21:00:00,11466.0 -2013-03-17 22:00:00,11510.0 -2013-03-17 23:00:00,11220.0 -2013-03-18 00:00:00,10724.0 -2013-03-16 01:00:00,10512.0 -2013-03-16 02:00:00,9993.0 -2013-03-16 03:00:00,9618.0 -2013-03-16 04:00:00,9447.0 -2013-03-16 05:00:00,9281.0 -2013-03-16 06:00:00,9372.0 -2013-03-16 07:00:00,9657.0 -2013-03-16 08:00:00,10161.0 -2013-03-16 09:00:00,10627.0 -2013-03-16 10:00:00,10982.0 -2013-03-16 11:00:00,11381.0 -2013-03-16 12:00:00,11545.0 -2013-03-16 13:00:00,11494.0 -2013-03-16 14:00:00,11278.0 -2013-03-16 15:00:00,11089.0 -2013-03-16 16:00:00,10917.0 -2013-03-16 17:00:00,10772.0 -2013-03-16 18:00:00,10722.0 -2013-03-16 19:00:00,10837.0 -2013-03-16 20:00:00,11041.0 -2013-03-16 21:00:00,11538.0 -2013-03-16 22:00:00,11408.0 -2013-03-16 23:00:00,11190.0 -2013-03-17 00:00:00,10715.0 -2013-03-15 01:00:00,10790.0 -2013-03-15 02:00:00,10248.0 -2013-03-15 03:00:00,9967.0 -2013-03-15 04:00:00,9798.0 -2013-03-15 05:00:00,9802.0 -2013-03-15 06:00:00,9976.0 -2013-03-15 07:00:00,10652.0 -2013-03-15 08:00:00,11858.0 -2013-03-15 09:00:00,12293.0 -2013-03-15 10:00:00,12319.0 -2013-03-15 11:00:00,12320.0 -2013-03-15 12:00:00,12210.0 -2013-03-15 13:00:00,12081.0 -2013-03-15 14:00:00,12020.0 -2013-03-15 15:00:00,12040.0 -2013-03-15 16:00:00,11946.0 -2013-03-15 17:00:00,11826.0 -2013-03-15 18:00:00,11860.0 -2013-03-15 19:00:00,11952.0 -2013-03-15 20:00:00,12128.0 -2013-03-15 21:00:00,12382.0 -2013-03-15 22:00:00,12175.0 -2013-03-15 23:00:00,11818.0 -2013-03-16 00:00:00,11223.0 -2013-03-14 01:00:00,10927.0 -2013-03-14 02:00:00,10442.0 -2013-03-14 03:00:00,10160.0 -2013-03-14 04:00:00,9994.0 -2013-03-14 05:00:00,10006.0 -2013-03-14 06:00:00,10266.0 -2013-03-14 07:00:00,10976.0 -2013-03-14 08:00:00,12220.0 -2013-03-14 09:00:00,12715.0 -2013-03-14 10:00:00,12666.0 -2013-03-14 11:00:00,12629.0 -2013-03-14 12:00:00,12619.0 -2013-03-14 13:00:00,12537.0 -2013-03-14 14:00:00,12475.0 -2013-03-14 15:00:00,12468.0 -2013-03-14 16:00:00,12386.0 -2013-03-14 17:00:00,12236.0 -2013-03-14 18:00:00,12014.0 -2013-03-14 19:00:00,11995.0 -2013-03-14 20:00:00,12348.0 -2013-03-14 21:00:00,12833.0 -2013-03-14 22:00:00,12682.0 -2013-03-14 23:00:00,12280.0 -2013-03-15 00:00:00,11525.0 -2013-03-13 01:00:00,11072.0 -2013-03-13 02:00:00,10538.0 -2013-03-13 03:00:00,10256.0 -2013-03-13 04:00:00,10068.0 -2013-03-13 05:00:00,10048.0 -2013-03-13 06:00:00,10302.0 -2013-03-13 07:00:00,11076.0 -2013-03-13 08:00:00,12334.0 -2013-03-13 09:00:00,12810.0 -2013-03-13 10:00:00,12864.0 -2013-03-13 11:00:00,12847.0 -2013-03-13 12:00:00,12908.0 -2013-03-13 13:00:00,12792.0 -2013-03-13 14:00:00,12693.0 -2013-03-13 15:00:00,12569.0 -2013-03-13 16:00:00,12370.0 -2013-03-13 17:00:00,12165.0 -2013-03-13 18:00:00,12018.0 -2013-03-13 19:00:00,12019.0 -2013-03-13 20:00:00,12178.0 -2013-03-13 21:00:00,12897.0 -2013-03-13 22:00:00,12878.0 -2013-03-13 23:00:00,12455.0 -2013-03-14 00:00:00,11704.0 -2013-03-12 01:00:00,10707.0 -2013-03-12 02:00:00,10196.0 -2013-03-12 03:00:00,9889.0 -2013-03-12 04:00:00,9733.0 -2013-03-12 05:00:00,9698.0 -2013-03-12 06:00:00,9963.0 -2013-03-12 07:00:00,10684.0 -2013-03-12 08:00:00,11934.0 -2013-03-12 09:00:00,12578.0 -2013-03-12 10:00:00,12640.0 -2013-03-12 11:00:00,12625.0 -2013-03-12 12:00:00,12523.0 -2013-03-12 13:00:00,12492.0 -2013-03-12 14:00:00,12413.0 -2013-03-12 15:00:00,12497.0 -2013-03-12 16:00:00,12451.0 -2013-03-12 17:00:00,12420.0 -2013-03-12 18:00:00,12498.0 -2013-03-12 19:00:00,12455.0 -2013-03-12 20:00:00,12689.0 -2013-03-12 21:00:00,13197.0 -2013-03-12 22:00:00,13091.0 -2013-03-12 23:00:00,12637.0 -2013-03-13 00:00:00,11819.0 -2013-03-11 01:00:00,9498.0 -2013-03-11 02:00:00,9092.0 -2013-03-11 03:00:00,8810.0 -2013-03-11 04:00:00,8736.0 -2013-03-11 05:00:00,8743.0 -2013-03-11 06:00:00,9013.0 -2013-03-11 07:00:00,9723.0 -2013-03-11 08:00:00,11062.0 -2013-03-11 09:00:00,11895.0 -2013-03-11 10:00:00,12067.0 -2013-03-11 11:00:00,12142.0 -2013-03-11 12:00:00,12278.0 -2013-03-11 13:00:00,12323.0 -2013-03-11 14:00:00,12435.0 -2013-03-11 15:00:00,12467.0 -2013-03-11 16:00:00,12390.0 -2013-03-11 17:00:00,12329.0 -2013-03-11 18:00:00,12372.0 -2013-03-11 19:00:00,12467.0 -2013-03-11 20:00:00,12677.0 -2013-03-11 21:00:00,12917.0 -2013-03-11 22:00:00,12707.0 -2013-03-11 23:00:00,12225.0 -2013-03-12 00:00:00,11446.0 -2013-03-10 01:00:00,9739.0 -2013-03-10 02:00:00,9334.0 -2013-03-10 04:00:00,9060.0 -2013-03-10 05:00:00,8992.0 -2013-03-10 06:00:00,8927.0 -2013-03-10 07:00:00,9042.0 -2013-03-10 08:00:00,9218.0 -2013-03-10 09:00:00,9374.0 -2013-03-10 10:00:00,9468.0 -2013-03-10 11:00:00,9786.0 -2013-03-10 12:00:00,9896.0 -2013-03-10 13:00:00,10004.0 -2013-03-10 14:00:00,10012.0 -2013-03-10 15:00:00,10125.0 -2013-03-10 16:00:00,10140.0 -2013-03-10 17:00:00,10245.0 -2013-03-10 18:00:00,10387.0 -2013-03-10 19:00:00,10601.0 -2013-03-10 20:00:00,10784.0 -2013-03-10 21:00:00,11162.0 -2013-03-10 22:00:00,10998.0 -2013-03-10 23:00:00,10644.0 -2013-03-11 00:00:00,10084.0 -2013-03-09 01:00:00,10589.0 -2013-03-09 02:00:00,10076.0 -2013-03-09 03:00:00,9745.0 -2013-03-09 04:00:00,9619.0 -2013-03-09 05:00:00,9502.0 -2013-03-09 06:00:00,9582.0 -2013-03-09 07:00:00,9867.0 -2013-03-09 08:00:00,10275.0 -2013-03-09 09:00:00,10564.0 -2013-03-09 10:00:00,10990.0 -2013-03-09 11:00:00,11164.0 -2013-03-09 12:00:00,11222.0 -2013-03-09 13:00:00,11137.0 -2013-03-09 14:00:00,10955.0 -2013-03-09 15:00:00,10723.0 -2013-03-09 16:00:00,10532.0 -2013-03-09 17:00:00,10472.0 -2013-03-09 18:00:00,10693.0 -2013-03-09 19:00:00,11022.0 -2013-03-09 20:00:00,11559.0 -2013-03-09 21:00:00,11427.0 -2013-03-09 22:00:00,11253.0 -2013-03-09 23:00:00,10837.0 -2013-03-10 00:00:00,10314.0 -2013-03-08 01:00:00,10722.0 -2013-03-08 02:00:00,10298.0 -2013-03-08 03:00:00,10065.0 -2013-03-08 04:00:00,9887.0 -2013-03-08 05:00:00,9930.0 -2013-03-08 06:00:00,10212.0 -2013-03-08 07:00:00,11013.0 -2013-03-08 08:00:00,11855.0 -2013-03-08 09:00:00,12214.0 -2013-03-08 10:00:00,12227.0 -2013-03-08 11:00:00,12228.0 -2013-03-08 12:00:00,12139.0 -2013-03-08 13:00:00,12063.0 -2013-03-08 14:00:00,11891.0 -2013-03-08 15:00:00,11817.0 -2013-03-08 16:00:00,11572.0 -2013-03-08 17:00:00,11461.0 -2013-03-08 18:00:00,11502.0 -2013-03-08 19:00:00,11806.0 -2013-03-08 20:00:00,12549.0 -2013-03-08 21:00:00,12535.0 -2013-03-08 22:00:00,12249.0 -2013-03-08 23:00:00,11859.0 -2013-03-09 00:00:00,11246.0 -2013-03-07 01:00:00,10729.0 -2013-03-07 02:00:00,10228.0 -2013-03-07 03:00:00,9977.0 -2013-03-07 04:00:00,9825.0 -2013-03-07 05:00:00,9826.0 -2013-03-07 06:00:00,10100.0 -2013-03-07 07:00:00,10817.0 -2013-03-07 08:00:00,11707.0 -2013-03-07 09:00:00,12178.0 -2013-03-07 10:00:00,12228.0 -2013-03-07 11:00:00,12222.0 -2013-03-07 12:00:00,12259.0 -2013-03-07 13:00:00,12174.0 -2013-03-07 14:00:00,12028.0 -2013-03-07 15:00:00,11982.0 -2013-03-07 16:00:00,11841.0 -2013-03-07 17:00:00,11759.0 -2013-03-07 18:00:00,11863.0 -2013-03-07 19:00:00,12194.0 -2013-03-07 20:00:00,13028.0 -2013-03-07 21:00:00,12967.0 -2013-03-07 22:00:00,12695.0 -2013-03-07 23:00:00,12230.0 -2013-03-08 00:00:00,11422.0 -2013-03-06 01:00:00,10737.0 -2013-03-06 02:00:00,10286.0 -2013-03-06 03:00:00,10032.0 -2013-03-06 04:00:00,9890.0 -2013-03-06 05:00:00,9937.0 -2013-03-06 06:00:00,10230.0 -2013-03-06 07:00:00,11005.0 -2013-03-06 08:00:00,11974.0 -2013-03-06 09:00:00,12452.0 -2013-03-06 10:00:00,12712.0 -2013-03-06 11:00:00,12796.0 -2013-03-06 12:00:00,12835.0 -2013-03-06 13:00:00,12638.0 -2013-03-06 14:00:00,12516.0 -2013-03-06 15:00:00,12538.0 -2013-03-06 16:00:00,12479.0 -2013-03-06 17:00:00,12446.0 -2013-03-06 18:00:00,12558.0 -2013-03-06 19:00:00,12869.0 -2013-03-06 20:00:00,13293.0 -2013-03-06 21:00:00,13136.0 -2013-03-06 22:00:00,12833.0 -2013-03-06 23:00:00,12290.0 -2013-03-07 00:00:00,11471.0 -2013-03-05 01:00:00,10762.0 -2013-03-05 02:00:00,10296.0 -2013-03-05 03:00:00,10035.0 -2013-03-05 04:00:00,9836.0 -2013-03-05 05:00:00,9846.0 -2013-03-05 06:00:00,10088.0 -2013-03-05 07:00:00,10786.0 -2013-03-05 08:00:00,11728.0 -2013-03-05 09:00:00,12323.0 -2013-03-05 10:00:00,12641.0 -2013-03-05 11:00:00,12822.0 -2013-03-05 12:00:00,12924.0 -2013-03-05 13:00:00,13052.0 -2013-03-05 14:00:00,13049.0 -2013-03-05 15:00:00,12980.0 -2013-03-05 16:00:00,12805.0 -2013-03-05 17:00:00,12687.0 -2013-03-05 18:00:00,12746.0 -2013-03-05 19:00:00,13124.0 -2013-03-05 20:00:00,13556.0 -2013-03-05 21:00:00,13344.0 -2013-03-05 22:00:00,12979.0 -2013-03-05 23:00:00,12398.0 -2013-03-06 00:00:00,11522.0 -2013-03-04 01:00:00,10425.0 -2013-03-04 02:00:00,10079.0 -2013-03-04 03:00:00,9958.0 -2013-03-04 04:00:00,9919.0 -2013-03-04 05:00:00,10039.0 -2013-03-04 06:00:00,10340.0 -2013-03-04 07:00:00,11133.0 -2013-03-04 08:00:00,12058.0 -2013-03-04 09:00:00,12584.0 -2013-03-04 10:00:00,12742.0 -2013-03-04 11:00:00,12813.0 -2013-03-04 12:00:00,12834.0 -2013-03-04 13:00:00,12763.0 -2013-03-04 14:00:00,12682.0 -2013-03-04 15:00:00,12729.0 -2013-03-04 16:00:00,12669.0 -2013-03-04 17:00:00,12603.0 -2013-03-04 18:00:00,12653.0 -2013-03-04 19:00:00,12984.0 -2013-03-04 20:00:00,13452.0 -2013-03-04 21:00:00,13257.0 -2013-03-04 22:00:00,12870.0 -2013-03-04 23:00:00,12319.0 -2013-03-05 00:00:00,11520.0 -2013-03-03 01:00:00,10702.0 -2013-03-03 02:00:00,10336.0 -2013-03-03 03:00:00,10064.0 -2013-03-03 04:00:00,9970.0 -2013-03-03 05:00:00,9906.0 -2013-03-03 06:00:00,9956.0 -2013-03-03 07:00:00,10125.0 -2013-03-03 08:00:00,10203.0 -2013-03-03 09:00:00,10168.0 -2013-03-03 10:00:00,10244.0 -2013-03-03 11:00:00,10301.0 -2013-03-03 12:00:00,10298.0 -2013-03-03 13:00:00,10284.0 -2013-03-03 14:00:00,10217.0 -2013-03-03 15:00:00,10171.0 -2013-03-03 16:00:00,10062.0 -2013-03-03 17:00:00,10081.0 -2013-03-03 18:00:00,10245.0 -2013-03-03 19:00:00,10888.0 -2013-03-03 20:00:00,11786.0 -2013-03-03 21:00:00,11897.0 -2013-03-03 22:00:00,11728.0 -2013-03-03 23:00:00,11451.0 -2013-03-04 00:00:00,10874.0 -2013-03-02 01:00:00,10992.0 -2013-03-02 02:00:00,10553.0 -2013-03-02 03:00:00,10209.0 -2013-03-02 04:00:00,10026.0 -2013-03-02 05:00:00,9970.0 -2013-03-02 06:00:00,10039.0 -2013-03-02 07:00:00,10285.0 -2013-03-02 08:00:00,10645.0 -2013-03-02 09:00:00,10900.0 -2013-03-02 10:00:00,11260.0 -2013-03-02 11:00:00,11327.0 -2013-03-02 12:00:00,11294.0 -2013-03-02 13:00:00,11196.0 -2013-03-02 14:00:00,11001.0 -2013-03-02 15:00:00,10776.0 -2013-03-02 16:00:00,10664.0 -2013-03-02 17:00:00,10609.0 -2013-03-02 18:00:00,10801.0 -2013-03-02 19:00:00,11228.0 -2013-03-02 20:00:00,12039.0 -2013-03-02 21:00:00,11998.0 -2013-03-02 22:00:00,11902.0 -2013-03-02 23:00:00,11608.0 -2013-03-03 00:00:00,11171.0 -2013-03-01 01:00:00,10818.0 -2013-03-01 02:00:00,10340.0 -2013-03-01 03:00:00,10056.0 -2013-03-01 04:00:00,9948.0 -2013-03-01 05:00:00,9932.0 -2013-03-01 06:00:00,10210.0 -2013-03-01 07:00:00,10930.0 -2013-03-01 08:00:00,11949.0 -2013-03-01 09:00:00,12479.0 -2013-03-01 10:00:00,12728.0 -2013-03-01 11:00:00,12830.0 -2013-03-01 12:00:00,12899.0 -2013-03-01 13:00:00,12884.0 -2013-03-01 14:00:00,12784.0 -2013-03-01 15:00:00,12795.0 -2013-03-01 16:00:00,12773.0 -2013-03-01 17:00:00,12745.0 -2013-03-01 18:00:00,12884.0 -2013-03-01 19:00:00,13215.0 -2013-03-01 20:00:00,13367.0 -2013-03-01 21:00:00,13131.0 -2013-03-01 22:00:00,12880.0 -2013-03-01 23:00:00,12453.0 -2013-03-02 00:00:00,11765.0 -2013-02-28 01:00:00,10794.0 -2013-02-28 02:00:00,10298.0 -2013-02-28 03:00:00,10017.0 -2013-02-28 04:00:00,9885.0 -2013-02-28 05:00:00,9892.0 -2013-02-28 06:00:00,10107.0 -2013-02-28 07:00:00,10837.0 -2013-02-28 08:00:00,11901.0 -2013-02-28 09:00:00,12395.0 -2013-02-28 10:00:00,12510.0 -2013-02-28 11:00:00,12620.0 -2013-02-28 12:00:00,12630.0 -2013-02-28 13:00:00,12607.0 -2013-02-28 14:00:00,12489.0 -2013-02-28 15:00:00,12547.0 -2013-02-28 16:00:00,12459.0 -2013-02-28 17:00:00,12505.0 -2013-02-28 18:00:00,12648.0 -2013-02-28 19:00:00,13021.0 -2013-02-28 20:00:00,13253.0 -2013-02-28 21:00:00,13053.0 -2013-02-28 22:00:00,12831.0 -2013-02-28 23:00:00,12331.0 -2013-03-01 00:00:00,11525.0 -2013-02-27 01:00:00,10811.0 -2013-02-27 02:00:00,10287.0 -2013-02-27 03:00:00,10026.0 -2013-02-27 04:00:00,9855.0 -2013-02-27 05:00:00,9814.0 -2013-02-27 06:00:00,10097.0 -2013-02-27 07:00:00,10854.0 -2013-02-27 08:00:00,11920.0 -2013-02-27 09:00:00,12431.0 -2013-02-27 10:00:00,12577.0 -2013-02-27 11:00:00,12662.0 -2013-02-27 12:00:00,12714.0 -2013-02-27 13:00:00,12662.0 -2013-02-27 14:00:00,12635.0 -2013-02-27 15:00:00,12644.0 -2013-02-27 16:00:00,12563.0 -2013-02-27 17:00:00,12507.0 -2013-02-27 18:00:00,12661.0 -2013-02-27 19:00:00,13114.0 -2013-02-27 20:00:00,13440.0 -2013-02-27 21:00:00,13232.0 -2013-02-27 22:00:00,12952.0 -2013-02-27 23:00:00,12427.0 -2013-02-28 00:00:00,11514.0 -2013-02-26 01:00:00,10539.0 -2013-02-26 02:00:00,10089.0 -2013-02-26 03:00:00,9832.0 -2013-02-26 04:00:00,9733.0 -2013-02-26 05:00:00,9758.0 -2013-02-26 06:00:00,10069.0 -2013-02-26 07:00:00,10857.0 -2013-02-26 08:00:00,11969.0 -2013-02-26 09:00:00,12519.0 -2013-02-26 10:00:00,12766.0 -2013-02-26 11:00:00,12884.0 -2013-02-26 12:00:00,13080.0 -2013-02-26 13:00:00,13205.0 -2013-02-26 14:00:00,13264.0 -2013-02-26 15:00:00,13261.0 -2013-02-26 16:00:00,13104.0 -2013-02-26 17:00:00,12942.0 -2013-02-26 18:00:00,13045.0 -2013-02-26 19:00:00,13532.0 -2013-02-26 20:00:00,13801.0 -2013-02-26 21:00:00,13520.0 -2013-02-26 22:00:00,13102.0 -2013-02-26 23:00:00,12460.0 -2013-02-27 00:00:00,11566.0 -2013-02-25 01:00:00,10100.0 -2013-02-25 02:00:00,9780.0 -2013-02-25 03:00:00,9667.0 -2013-02-25 04:00:00,9590.0 -2013-02-25 05:00:00,9693.0 -2013-02-25 06:00:00,10027.0 -2013-02-25 07:00:00,10864.0 -2013-02-25 08:00:00,11915.0 -2013-02-25 09:00:00,12322.0 -2013-02-25 10:00:00,12298.0 -2013-02-25 11:00:00,12245.0 -2013-02-25 12:00:00,12205.0 -2013-02-25 13:00:00,12065.0 -2013-02-25 14:00:00,11947.0 -2013-02-25 15:00:00,11863.0 -2013-02-25 16:00:00,11723.0 -2013-02-25 17:00:00,11638.0 -2013-02-25 18:00:00,11801.0 -2013-02-25 19:00:00,12492.0 -2013-02-25 20:00:00,12935.0 -2013-02-25 21:00:00,12849.0 -2013-02-25 22:00:00,12576.0 -2013-02-25 23:00:00,12095.0 -2013-02-26 00:00:00,11267.0 -2013-02-24 01:00:00,10603.0 -2013-02-24 02:00:00,10201.0 -2013-02-24 03:00:00,9951.0 -2013-02-24 04:00:00,9849.0 -2013-02-24 05:00:00,9763.0 -2013-02-24 06:00:00,9838.0 -2013-02-24 07:00:00,9985.0 -2013-02-24 08:00:00,10103.0 -2013-02-24 09:00:00,10095.0 -2013-02-24 10:00:00,10192.0 -2013-02-24 11:00:00,10219.0 -2013-02-24 12:00:00,10245.0 -2013-02-24 13:00:00,10175.0 -2013-02-24 14:00:00,10089.0 -2013-02-24 15:00:00,9979.0 -2013-02-24 16:00:00,9875.0 -2013-02-24 17:00:00,9838.0 -2013-02-24 18:00:00,10123.0 -2013-02-24 19:00:00,10812.0 -2013-02-24 20:00:00,11651.0 -2013-02-24 21:00:00,11603.0 -2013-02-24 22:00:00,11490.0 -2013-02-24 23:00:00,11156.0 -2013-02-25 00:00:00,10683.0 -2013-02-23 01:00:00,11017.0 -2013-02-23 02:00:00,10496.0 -2013-02-23 03:00:00,10170.0 -2013-02-23 04:00:00,10013.0 -2013-02-23 05:00:00,9938.0 -2013-02-23 06:00:00,9951.0 -2013-02-23 07:00:00,10288.0 -2013-02-23 08:00:00,10610.0 -2013-02-23 09:00:00,10834.0 -2013-02-23 10:00:00,11163.0 -2013-02-23 11:00:00,11310.0 -2013-02-23 12:00:00,11484.0 -2013-02-23 13:00:00,11411.0 -2013-02-23 14:00:00,11244.0 -2013-02-23 15:00:00,11042.0 -2013-02-23 16:00:00,10991.0 -2013-02-23 17:00:00,10987.0 -2013-02-23 18:00:00,11121.0 -2013-02-23 19:00:00,11664.0 -2013-02-23 20:00:00,12193.0 -2013-02-23 21:00:00,12080.0 -2013-02-23 22:00:00,11886.0 -2013-02-23 23:00:00,11620.0 -2013-02-24 00:00:00,11129.0 -2013-02-22 01:00:00,11515.0 -2013-02-22 02:00:00,10992.0 -2013-02-22 03:00:00,10708.0 -2013-02-22 04:00:00,10531.0 -2013-02-22 05:00:00,10566.0 -2013-02-22 06:00:00,10782.0 -2013-02-22 07:00:00,11423.0 -2013-02-22 08:00:00,12316.0 -2013-02-22 09:00:00,12681.0 -2013-02-22 10:00:00,12889.0 -2013-02-22 11:00:00,13048.0 -2013-02-22 12:00:00,13117.0 -2013-02-22 13:00:00,13065.0 -2013-02-22 14:00:00,12914.0 -2013-02-22 15:00:00,12849.0 -2013-02-22 16:00:00,12788.0 -2013-02-22 17:00:00,12773.0 -2013-02-22 18:00:00,12859.0 -2013-02-22 19:00:00,13259.0 -2013-02-22 20:00:00,13440.0 -2013-02-22 21:00:00,13191.0 -2013-02-22 22:00:00,12787.0 -2013-02-22 23:00:00,12392.0 -2013-02-23 00:00:00,11684.0 -2013-02-21 01:00:00,11566.0 -2013-02-21 02:00:00,11102.0 -2013-02-21 03:00:00,10831.0 -2013-02-21 04:00:00,10661.0 -2013-02-21 05:00:00,10622.0 -2013-02-21 06:00:00,10823.0 -2013-02-21 07:00:00,11523.0 -2013-02-21 08:00:00,12655.0 -2013-02-21 09:00:00,13156.0 -2013-02-21 10:00:00,13229.0 -2013-02-21 11:00:00,13312.0 -2013-02-21 12:00:00,13362.0 -2013-02-21 13:00:00,13326.0 -2013-02-21 14:00:00,13329.0 -2013-02-21 15:00:00,13345.0 -2013-02-21 16:00:00,13317.0 -2013-02-21 17:00:00,13302.0 -2013-02-21 18:00:00,13509.0 -2013-02-21 19:00:00,13956.0 -2013-02-21 20:00:00,14129.0 -2013-02-21 21:00:00,13924.0 -2013-02-21 22:00:00,13621.0 -2013-02-21 23:00:00,13095.0 -2013-02-22 00:00:00,12220.0 -2013-02-20 01:00:00,12086.0 -2013-02-20 02:00:00,11642.0 -2013-02-20 03:00:00,11407.0 -2013-02-20 04:00:00,11283.0 -2013-02-20 05:00:00,11251.0 -2013-02-20 06:00:00,11496.0 -2013-02-20 07:00:00,12265.0 -2013-02-20 08:00:00,13286.0 -2013-02-20 09:00:00,13596.0 -2013-02-20 10:00:00,13567.0 -2013-02-20 11:00:00,13528.0 -2013-02-20 12:00:00,13478.0 -2013-02-20 13:00:00,13343.0 -2013-02-20 14:00:00,13191.0 -2013-02-20 15:00:00,13090.0 -2013-02-20 16:00:00,12866.0 -2013-02-20 17:00:00,12786.0 -2013-02-20 18:00:00,12955.0 -2013-02-20 19:00:00,13614.0 -2013-02-20 20:00:00,14077.0 -2013-02-20 21:00:00,13942.0 -2013-02-20 22:00:00,13651.0 -2013-02-20 23:00:00,13159.0 -2013-02-21 00:00:00,12330.0 -2013-02-19 01:00:00,10578.0 -2013-02-19 02:00:00,10238.0 -2013-02-19 03:00:00,10124.0 -2013-02-19 04:00:00,10109.0 -2013-02-19 05:00:00,10243.0 -2013-02-19 06:00:00,10617.0 -2013-02-19 07:00:00,11447.0 -2013-02-19 08:00:00,12740.0 -2013-02-19 09:00:00,13295.0 -2013-02-19 10:00:00,13519.0 -2013-02-19 11:00:00,13644.0 -2013-02-19 12:00:00,13710.0 -2013-02-19 13:00:00,13782.0 -2013-02-19 14:00:00,13754.0 -2013-02-19 15:00:00,13830.0 -2013-02-19 16:00:00,13806.0 -2013-02-19 17:00:00,13788.0 -2013-02-19 18:00:00,13945.0 -2013-02-19 19:00:00,14472.0 -2013-02-19 20:00:00,14716.0 -2013-02-19 21:00:00,14549.0 -2013-02-19 22:00:00,14260.0 -2013-02-19 23:00:00,13723.0 -2013-02-20 00:00:00,12859.0 -2013-02-18 01:00:00,10620.0 -2013-02-18 02:00:00,10221.0 -2013-02-18 03:00:00,10052.0 -2013-02-18 04:00:00,9903.0 -2013-02-18 05:00:00,9955.0 -2013-02-18 06:00:00,10161.0 -2013-02-18 07:00:00,10867.0 -2013-02-18 08:00:00,11726.0 -2013-02-18 09:00:00,12026.0 -2013-02-18 10:00:00,12129.0 -2013-02-18 11:00:00,12217.0 -2013-02-18 12:00:00,12201.0 -2013-02-18 13:00:00,12054.0 -2013-02-18 14:00:00,11955.0 -2013-02-18 15:00:00,12108.0 -2013-02-18 16:00:00,12103.0 -2013-02-18 17:00:00,12110.0 -2013-02-18 18:00:00,12443.0 -2013-02-18 19:00:00,12920.0 -2013-02-18 20:00:00,12890.0 -2013-02-18 21:00:00,12758.0 -2013-02-18 22:00:00,12383.0 -2013-02-18 23:00:00,11842.0 -2013-02-19 00:00:00,11118.0 -2013-02-17 01:00:00,11221.0 -2013-02-17 02:00:00,10789.0 -2013-02-17 03:00:00,10563.0 -2013-02-17 04:00:00,10392.0 -2013-02-17 05:00:00,10387.0 -2013-02-17 06:00:00,10384.0 -2013-02-17 07:00:00,10559.0 -2013-02-17 08:00:00,10740.0 -2013-02-17 09:00:00,10691.0 -2013-02-17 10:00:00,10768.0 -2013-02-17 11:00:00,10785.0 -2013-02-17 12:00:00,10831.0 -2013-02-17 13:00:00,10798.0 -2013-02-17 14:00:00,10696.0 -2013-02-17 15:00:00,10563.0 -2013-02-17 16:00:00,10420.0 -2013-02-17 17:00:00,10455.0 -2013-02-17 18:00:00,10749.0 -2013-02-17 19:00:00,11600.0 -2013-02-17 20:00:00,12174.0 -2013-02-17 21:00:00,12189.0 -2013-02-17 22:00:00,11997.0 -2013-02-17 23:00:00,11710.0 -2013-02-18 00:00:00,11167.0 -2013-02-16 01:00:00,11105.0 -2013-02-16 02:00:00,10672.0 -2013-02-16 03:00:00,10416.0 -2013-02-16 04:00:00,10267.0 -2013-02-16 05:00:00,10257.0 -2013-02-16 06:00:00,10326.0 -2013-02-16 07:00:00,10653.0 -2013-02-16 08:00:00,11041.0 -2013-02-16 09:00:00,11139.0 -2013-02-16 10:00:00,11367.0 -2013-02-16 11:00:00,11547.0 -2013-02-16 12:00:00,11793.0 -2013-02-16 13:00:00,11862.0 -2013-02-16 14:00:00,11776.0 -2013-02-16 15:00:00,11627.0 -2013-02-16 16:00:00,11559.0 -2013-02-16 17:00:00,11531.0 -2013-02-16 18:00:00,11757.0 -2013-02-16 19:00:00,12310.0 -2013-02-16 20:00:00,12666.0 -2013-02-16 21:00:00,12586.0 -2013-02-16 22:00:00,12477.0 -2013-02-16 23:00:00,12224.0 -2013-02-17 00:00:00,11696.0 -2013-02-15 01:00:00,10677.0 -2013-02-15 02:00:00,10170.0 -2013-02-15 03:00:00,9879.0 -2013-02-15 04:00:00,9750.0 -2013-02-15 05:00:00,9740.0 -2013-02-15 06:00:00,10074.0 -2013-02-15 07:00:00,10822.0 -2013-02-15 08:00:00,11964.0 -2013-02-15 09:00:00,12436.0 -2013-02-15 10:00:00,12520.0 -2013-02-15 11:00:00,12497.0 -2013-02-15 12:00:00,12467.0 -2013-02-15 13:00:00,12360.0 -2013-02-15 14:00:00,12213.0 -2013-02-15 15:00:00,12157.0 -2013-02-15 16:00:00,11985.0 -2013-02-15 17:00:00,11925.0 -2013-02-15 18:00:00,12135.0 -2013-02-15 19:00:00,12802.0 -2013-02-15 20:00:00,13095.0 -2013-02-15 21:00:00,12930.0 -2013-02-15 22:00:00,12666.0 -2013-02-15 23:00:00,12331.0 -2013-02-16 00:00:00,11690.0 -2013-02-14 01:00:00,10457.0 -2013-02-14 02:00:00,9943.0 -2013-02-14 03:00:00,9682.0 -2013-02-14 04:00:00,9514.0 -2013-02-14 05:00:00,9524.0 -2013-02-14 06:00:00,9772.0 -2013-02-14 07:00:00,10529.0 -2013-02-14 08:00:00,11691.0 -2013-02-14 09:00:00,12081.0 -2013-02-14 10:00:00,12232.0 -2013-02-14 11:00:00,12435.0 -2013-02-14 12:00:00,12566.0 -2013-02-14 13:00:00,12579.0 -2013-02-14 14:00:00,12577.0 -2013-02-14 15:00:00,12582.0 -2013-02-14 16:00:00,12453.0 -2013-02-14 17:00:00,12390.0 -2013-02-14 18:00:00,12530.0 -2013-02-14 19:00:00,13074.0 -2013-02-14 20:00:00,13218.0 -2013-02-14 21:00:00,12988.0 -2013-02-14 22:00:00,12609.0 -2013-02-14 23:00:00,12136.0 -2013-02-15 00:00:00,11387.0 -2013-02-13 01:00:00,11046.0 -2013-02-13 02:00:00,10571.0 -2013-02-13 03:00:00,10276.0 -2013-02-13 04:00:00,10129.0 -2013-02-13 05:00:00,10142.0 -2013-02-13 06:00:00,10385.0 -2013-02-13 07:00:00,11143.0 -2013-02-13 08:00:00,12296.0 -2013-02-13 09:00:00,12581.0 -2013-02-13 10:00:00,12539.0 -2013-02-13 11:00:00,12435.0 -2013-02-13 12:00:00,12367.0 -2013-02-13 13:00:00,12208.0 -2013-02-13 14:00:00,12065.0 -2013-02-13 15:00:00,11979.0 -2013-02-13 16:00:00,11824.0 -2013-02-13 17:00:00,11748.0 -2013-02-13 18:00:00,11895.0 -2013-02-13 19:00:00,12523.0 -2013-02-13 20:00:00,12932.0 -2013-02-13 21:00:00,12795.0 -2013-02-13 22:00:00,12542.0 -2013-02-13 23:00:00,12051.0 -2013-02-14 00:00:00,11234.0 -2013-02-12 01:00:00,10864.0 -2013-02-12 02:00:00,10391.0 -2013-02-12 03:00:00,10123.0 -2013-02-12 04:00:00,10009.0 -2013-02-12 05:00:00,10049.0 -2013-02-12 06:00:00,10337.0 -2013-02-12 07:00:00,11083.0 -2013-02-12 08:00:00,12204.0 -2013-02-12 09:00:00,12675.0 -2013-02-12 10:00:00,12855.0 -2013-02-12 11:00:00,12954.0 -2013-02-12 12:00:00,13024.0 -2013-02-12 13:00:00,13015.0 -2013-02-12 14:00:00,12947.0 -2013-02-12 15:00:00,12948.0 -2013-02-12 16:00:00,12840.0 -2013-02-12 17:00:00,12741.0 -2013-02-12 18:00:00,12823.0 -2013-02-12 19:00:00,13393.0 -2013-02-12 20:00:00,13584.0 -2013-02-12 21:00:00,13438.0 -2013-02-12 22:00:00,13148.0 -2013-02-12 23:00:00,12653.0 -2013-02-13 00:00:00,11829.0 -2013-02-11 01:00:00,9747.0 -2013-02-11 02:00:00,9383.0 -2013-02-11 03:00:00,9264.0 -2013-02-11 04:00:00,9268.0 -2013-02-11 05:00:00,9402.0 -2013-02-11 06:00:00,9740.0 -2013-02-11 07:00:00,10671.0 -2013-02-11 08:00:00,11918.0 -2013-02-11 09:00:00,12622.0 -2013-02-11 10:00:00,12845.0 -2013-02-11 11:00:00,12917.0 -2013-02-11 12:00:00,13015.0 -2013-02-11 13:00:00,13051.0 -2013-02-11 14:00:00,13053.0 -2013-02-11 15:00:00,13099.0 -2013-02-11 16:00:00,12976.0 -2013-02-11 17:00:00,12937.0 -2013-02-11 18:00:00,13122.0 -2013-02-11 19:00:00,13648.0 -2013-02-11 20:00:00,13645.0 -2013-02-11 21:00:00,13419.0 -2013-02-11 22:00:00,13067.0 -2013-02-11 23:00:00,12469.0 -2013-02-12 00:00:00,11620.0 -2013-02-10 01:00:00,10410.0 -2013-02-10 02:00:00,9902.0 -2013-02-10 03:00:00,9619.0 -2013-02-10 04:00:00,9401.0 -2013-02-10 05:00:00,9368.0 -2013-02-10 06:00:00,9297.0 -2013-02-10 07:00:00,9434.0 -2013-02-10 08:00:00,9698.0 -2013-02-10 09:00:00,9671.0 -2013-02-10 10:00:00,10115.0 -2013-02-10 11:00:00,10441.0 -2013-02-10 12:00:00,10737.0 -2013-02-10 13:00:00,10844.0 -2013-02-10 14:00:00,10858.0 -2013-02-10 15:00:00,10797.0 -2013-02-10 16:00:00,10692.0 -2013-02-10 17:00:00,10806.0 -2013-02-10 18:00:00,11166.0 -2013-02-10 19:00:00,11671.0 -2013-02-10 20:00:00,11752.0 -2013-02-10 21:00:00,11578.0 -2013-02-10 22:00:00,11284.0 -2013-02-10 23:00:00,10884.0 -2013-02-11 00:00:00,10267.0 -2013-02-09 01:00:00,11084.0 -2013-02-09 02:00:00,10556.0 -2013-02-09 03:00:00,10234.0 -2013-02-09 04:00:00,10044.0 -2013-02-09 05:00:00,9951.0 -2013-02-09 06:00:00,10010.0 -2013-02-09 07:00:00,10263.0 -2013-02-09 08:00:00,10759.0 -2013-02-09 09:00:00,10927.0 -2013-02-09 10:00:00,11308.0 -2013-02-09 11:00:00,11510.0 -2013-02-09 12:00:00,11537.0 -2013-02-09 13:00:00,11416.0 -2013-02-09 14:00:00,11183.0 -2013-02-09 15:00:00,10945.0 -2013-02-09 16:00:00,10775.0 -2013-02-09 17:00:00,10666.0 -2013-02-09 18:00:00,10865.0 -2013-02-09 19:00:00,11696.0 -2013-02-09 20:00:00,12060.0 -2013-02-09 21:00:00,11961.0 -2013-02-09 22:00:00,11743.0 -2013-02-09 23:00:00,11497.0 -2013-02-10 00:00:00,10956.0 -2013-02-08 01:00:00,11015.0 -2013-02-08 02:00:00,10524.0 -2013-02-08 03:00:00,10290.0 -2013-02-08 04:00:00,10211.0 -2013-02-08 05:00:00,10245.0 -2013-02-08 06:00:00,10573.0 -2013-02-08 07:00:00,11246.0 -2013-02-08 08:00:00,12397.0 -2013-02-08 09:00:00,12853.0 -2013-02-08 10:00:00,13043.0 -2013-02-08 11:00:00,13107.0 -2013-02-08 12:00:00,13127.0 -2013-02-08 13:00:00,12989.0 -2013-02-08 14:00:00,12878.0 -2013-02-08 15:00:00,12781.0 -2013-02-08 16:00:00,12685.0 -2013-02-08 17:00:00,12593.0 -2013-02-08 18:00:00,12751.0 -2013-02-08 19:00:00,13390.0 -2013-02-08 20:00:00,13492.0 -2013-02-08 21:00:00,13229.0 -2013-02-08 22:00:00,12905.0 -2013-02-08 23:00:00,12499.0 -2013-02-09 00:00:00,11755.0 -2013-02-07 01:00:00,10846.0 -2013-02-07 02:00:00,10293.0 -2013-02-07 03:00:00,10022.0 -2013-02-07 04:00:00,9814.0 -2013-02-07 05:00:00,9835.0 -2013-02-07 06:00:00,10152.0 -2013-02-07 07:00:00,10880.0 -2013-02-07 08:00:00,12073.0 -2013-02-07 09:00:00,12640.0 -2013-02-07 10:00:00,12687.0 -2013-02-07 11:00:00,12848.0 -2013-02-07 12:00:00,12956.0 -2013-02-07 13:00:00,12994.0 -2013-02-07 14:00:00,12967.0 -2013-02-07 15:00:00,12963.0 -2013-02-07 16:00:00,12938.0 -2013-02-07 17:00:00,12912.0 -2013-02-07 18:00:00,13159.0 -2013-02-07 19:00:00,13614.0 -2013-02-07 20:00:00,13639.0 -2013-02-07 21:00:00,13441.0 -2013-02-07 22:00:00,13082.0 -2013-02-07 23:00:00,12548.0 -2013-02-08 00:00:00,11718.0 -2013-02-06 01:00:00,11113.0 -2013-02-06 02:00:00,10615.0 -2013-02-06 03:00:00,10342.0 -2013-02-06 04:00:00,10243.0 -2013-02-06 05:00:00,10283.0 -2013-02-06 06:00:00,10584.0 -2013-02-06 07:00:00,11375.0 -2013-02-06 08:00:00,12545.0 -2013-02-06 09:00:00,12899.0 -2013-02-06 10:00:00,12902.0 -2013-02-06 11:00:00,12787.0 -2013-02-06 12:00:00,12728.0 -2013-02-06 13:00:00,12592.0 -2013-02-06 14:00:00,12436.0 -2013-02-06 15:00:00,12419.0 -2013-02-06 16:00:00,12325.0 -2013-02-06 17:00:00,12309.0 -2013-02-06 18:00:00,12571.0 -2013-02-06 19:00:00,13273.0 -2013-02-06 20:00:00,13386.0 -2013-02-06 21:00:00,13224.0 -2013-02-06 22:00:00,12935.0 -2013-02-06 23:00:00,12405.0 -2013-02-07 00:00:00,11604.0 -2013-02-05 01:00:00,11494.0 -2013-02-05 02:00:00,11039.0 -2013-02-05 03:00:00,10814.0 -2013-02-05 04:00:00,10681.0 -2013-02-05 05:00:00,10689.0 -2013-02-05 06:00:00,10932.0 -2013-02-05 07:00:00,11623.0 -2013-02-05 08:00:00,12810.0 -2013-02-05 09:00:00,13196.0 -2013-02-05 10:00:00,13353.0 -2013-02-05 11:00:00,13308.0 -2013-02-05 12:00:00,13337.0 -2013-02-05 13:00:00,13287.0 -2013-02-05 14:00:00,13254.0 -2013-02-05 15:00:00,13307.0 -2013-02-05 16:00:00,13157.0 -2013-02-05 17:00:00,13028.0 -2013-02-05 18:00:00,13237.0 -2013-02-05 19:00:00,13868.0 -2013-02-05 20:00:00,13874.0 -2013-02-05 21:00:00,13638.0 -2013-02-05 22:00:00,13272.0 -2013-02-05 23:00:00,12711.0 -2013-02-06 00:00:00,11875.0 -2013-02-04 01:00:00,11078.0 -2013-02-04 02:00:00,10665.0 -2013-02-04 03:00:00,10412.0 -2013-02-04 04:00:00,10318.0 -2013-02-04 05:00:00,10356.0 -2013-02-04 06:00:00,10640.0 -2013-02-04 07:00:00,11294.0 -2013-02-04 08:00:00,12454.0 -2013-02-04 09:00:00,12941.0 -2013-02-04 10:00:00,13096.0 -2013-02-04 11:00:00,13238.0 -2013-02-04 12:00:00,13301.0 -2013-02-04 13:00:00,13243.0 -2013-02-04 14:00:00,13164.0 -2013-02-04 15:00:00,13173.0 -2013-02-04 16:00:00,13123.0 -2013-02-04 17:00:00,13056.0 -2013-02-04 18:00:00,13240.0 -2013-02-04 19:00:00,13919.0 -2013-02-04 20:00:00,14030.0 -2013-02-04 21:00:00,13830.0 -2013-02-04 22:00:00,13552.0 -2013-02-04 23:00:00,13045.0 -2013-02-05 00:00:00,12215.0 -2013-02-03 01:00:00,11609.0 -2013-02-03 02:00:00,11151.0 -2013-02-03 03:00:00,10777.0 -2013-02-03 04:00:00,10664.0 -2013-02-03 05:00:00,10582.0 -2013-02-03 06:00:00,10624.0 -2013-02-03 07:00:00,10716.0 -2013-02-03 08:00:00,11034.0 -2013-02-03 09:00:00,11067.0 -2013-02-03 10:00:00,11314.0 -2013-02-03 11:00:00,11558.0 -2013-02-03 12:00:00,11637.0 -2013-02-03 13:00:00,11518.0 -2013-02-03 14:00:00,11441.0 -2013-02-03 15:00:00,11352.0 -2013-02-03 16:00:00,11329.0 -2013-02-03 17:00:00,11412.0 -2013-02-03 18:00:00,11719.0 -2013-02-03 19:00:00,12535.0 -2013-02-03 20:00:00,12674.0 -2013-02-03 21:00:00,12545.0 -2013-02-03 22:00:00,12334.0 -2013-02-03 23:00:00,12050.0 -2013-02-04 00:00:00,11582.0 -2013-02-02 01:00:00,12204.0 -2013-02-02 02:00:00,11675.0 -2013-02-02 03:00:00,11299.0 -2013-02-02 04:00:00,11013.0 -2013-02-02 05:00:00,10837.0 -2013-02-02 06:00:00,10865.0 -2013-02-02 07:00:00,11061.0 -2013-02-02 08:00:00,11441.0 -2013-02-02 09:00:00,11675.0 -2013-02-02 10:00:00,11928.0 -2013-02-02 11:00:00,12203.0 -2013-02-02 12:00:00,12355.0 -2013-02-02 13:00:00,12419.0 -2013-02-02 14:00:00,12199.0 -2013-02-02 15:00:00,12074.0 -2013-02-02 16:00:00,11936.0 -2013-02-02 17:00:00,11967.0 -2013-02-02 18:00:00,12227.0 -2013-02-02 19:00:00,13065.0 -2013-02-02 20:00:00,13284.0 -2013-02-02 21:00:00,13173.0 -2013-02-02 22:00:00,13007.0 -2013-02-02 23:00:00,12681.0 -2013-02-03 00:00:00,12171.0 -2013-02-01 01:00:00,12514.0 -2013-02-01 02:00:00,12084.0 -2013-02-01 03:00:00,11823.0 -2013-02-01 04:00:00,11695.0 -2013-02-01 05:00:00,11713.0 -2013-02-01 06:00:00,11939.0 -2013-02-01 07:00:00,12596.0 -2013-02-01 08:00:00,13693.0 -2013-02-01 09:00:00,14098.0 -2013-02-01 10:00:00,14109.0 -2013-02-01 11:00:00,14158.0 -2013-02-01 12:00:00,14176.0 -2013-02-01 13:00:00,14053.0 -2013-02-01 14:00:00,13944.0 -2013-02-01 15:00:00,13841.0 -2013-02-01 16:00:00,13659.0 -2013-02-01 17:00:00,13525.0 -2013-02-01 18:00:00,13709.0 -2013-02-01 19:00:00,14555.0 -2013-02-01 20:00:00,14689.0 -2013-02-01 21:00:00,14444.0 -2013-02-01 22:00:00,14110.0 -2013-02-01 23:00:00,13692.0 -2013-02-02 00:00:00,12926.0 -2013-01-31 01:00:00,11346.0 -2013-01-31 02:00:00,10916.0 -2013-01-31 03:00:00,10630.0 -2013-01-31 04:00:00,10533.0 -2013-01-31 05:00:00,10590.0 -2013-01-31 06:00:00,10882.0 -2013-01-31 07:00:00,11608.0 -2013-01-31 08:00:00,12878.0 -2013-01-31 09:00:00,13421.0 -2013-01-31 10:00:00,13562.0 -2013-01-31 11:00:00,13615.0 -2013-01-31 12:00:00,13639.0 -2013-01-31 13:00:00,13641.0 -2013-01-31 14:00:00,13652.0 -2013-01-31 15:00:00,13746.0 -2013-01-31 16:00:00,13698.0 -2013-01-31 17:00:00,13732.0 -2013-01-31 18:00:00,14033.0 -2013-01-31 19:00:00,14753.0 -2013-01-31 20:00:00,14918.0 -2013-01-31 21:00:00,14748.0 -2013-01-31 22:00:00,14537.0 -2013-01-31 23:00:00,14065.0 -2013-02-01 00:00:00,13227.0 -2013-01-30 01:00:00,9826.0 -2013-01-30 02:00:00,9341.0 -2013-01-30 03:00:00,9084.0 -2013-01-30 04:00:00,8946.0 -2013-01-30 05:00:00,8956.0 -2013-01-30 06:00:00,9227.0 -2013-01-30 07:00:00,10001.0 -2013-01-30 08:00:00,11303.0 -2013-01-30 09:00:00,12098.0 -2013-01-30 10:00:00,12238.0 -2013-01-30 11:00:00,12376.0 -2013-01-30 12:00:00,12544.0 -2013-01-30 13:00:00,12534.0 -2013-01-30 14:00:00,12457.0 -2013-01-30 15:00:00,12571.0 -2013-01-30 16:00:00,12600.0 -2013-01-30 17:00:00,12705.0 -2013-01-30 18:00:00,13090.0 -2013-01-30 19:00:00,13652.0 -2013-01-30 20:00:00,13599.0 -2013-01-30 21:00:00,13429.0 -2013-01-30 22:00:00,13246.0 -2013-01-30 23:00:00,12779.0 -2013-01-31 00:00:00,12024.0 -2013-01-29 01:00:00,10329.0 -2013-01-29 02:00:00,9785.0 -2013-01-29 03:00:00,9414.0 -2013-01-29 04:00:00,9179.0 -2013-01-29 05:00:00,9048.0 -2013-01-29 06:00:00,9190.0 -2013-01-29 07:00:00,9816.0 -2013-01-29 08:00:00,11015.0 -2013-01-29 09:00:00,11686.0 -2013-01-29 10:00:00,11749.0 -2013-01-29 11:00:00,11796.0 -2013-01-29 12:00:00,11951.0 -2013-01-29 13:00:00,11931.0 -2013-01-29 14:00:00,11939.0 -2013-01-29 15:00:00,11883.0 -2013-01-29 16:00:00,11929.0 -2013-01-29 17:00:00,11932.0 -2013-01-29 18:00:00,12292.0 -2013-01-29 19:00:00,12691.0 -2013-01-29 20:00:00,12559.0 -2013-01-29 21:00:00,12257.0 -2013-01-29 22:00:00,11911.0 -2013-01-29 23:00:00,11370.0 -2013-01-30 00:00:00,10604.0 -2013-01-28 01:00:00,10369.0 -2013-01-28 02:00:00,9926.0 -2013-01-28 03:00:00,9581.0 -2013-01-28 04:00:00,9490.0 -2013-01-28 05:00:00,9450.0 -2013-01-28 06:00:00,9712.0 -2013-01-28 07:00:00,10351.0 -2013-01-28 08:00:00,11551.0 -2013-01-28 09:00:00,12167.0 -2013-01-28 10:00:00,12211.0 -2013-01-28 11:00:00,12260.0 -2013-01-28 12:00:00,12253.0 -2013-01-28 13:00:00,12178.0 -2013-01-28 14:00:00,12050.0 -2013-01-28 15:00:00,11990.0 -2013-01-28 16:00:00,11785.0 -2013-01-28 17:00:00,11704.0 -2013-01-28 18:00:00,12012.0 -2013-01-28 19:00:00,12830.0 -2013-01-28 20:00:00,12828.0 -2013-01-28 21:00:00,12651.0 -2013-01-28 22:00:00,12356.0 -2013-01-28 23:00:00,11834.0 -2013-01-29 00:00:00,11084.0 -2013-01-27 01:00:00,10753.0 -2013-01-27 02:00:00,10331.0 -2013-01-27 03:00:00,10081.0 -2013-01-27 04:00:00,9880.0 -2013-01-27 05:00:00,9852.0 -2013-01-27 06:00:00,9863.0 -2013-01-27 07:00:00,10028.0 -2013-01-27 08:00:00,10250.0 -2013-01-27 09:00:00,10364.0 -2013-01-27 10:00:00,10611.0 -2013-01-27 11:00:00,10927.0 -2013-01-27 12:00:00,11065.0 -2013-01-27 13:00:00,11203.0 -2013-01-27 14:00:00,11212.0 -2013-01-27 15:00:00,11322.0 -2013-01-27 16:00:00,11352.0 -2013-01-27 17:00:00,11614.0 -2013-01-27 18:00:00,12143.0 -2013-01-27 19:00:00,12706.0 -2013-01-27 20:00:00,12613.0 -2013-01-27 21:00:00,12443.0 -2013-01-27 22:00:00,12079.0 -2013-01-27 23:00:00,11634.0 -2013-01-28 00:00:00,10950.0 -2013-01-26 01:00:00,11497.0 -2013-01-26 02:00:00,11026.0 -2013-01-26 03:00:00,10731.0 -2013-01-26 04:00:00,10568.0 -2013-01-26 05:00:00,10551.0 -2013-01-26 06:00:00,10661.0 -2013-01-26 07:00:00,10978.0 -2013-01-26 08:00:00,11478.0 -2013-01-26 09:00:00,11684.0 -2013-01-26 10:00:00,11887.0 -2013-01-26 11:00:00,11952.0 -2013-01-26 12:00:00,11896.0 -2013-01-26 13:00:00,11742.0 -2013-01-26 14:00:00,11538.0 -2013-01-26 15:00:00,11270.0 -2013-01-26 16:00:00,11016.0 -2013-01-26 17:00:00,10997.0 -2013-01-26 18:00:00,11306.0 -2013-01-26 19:00:00,12327.0 -2013-01-26 20:00:00,12531.0 -2013-01-26 21:00:00,12378.0 -2013-01-26 22:00:00,12183.0 -2013-01-26 23:00:00,11828.0 -2013-01-27 00:00:00,11313.0 -2013-01-25 01:00:00,11850.0 -2013-01-25 02:00:00,11352.0 -2013-01-25 03:00:00,11065.0 -2013-01-25 04:00:00,10877.0 -2013-01-25 05:00:00,10842.0 -2013-01-25 06:00:00,11102.0 -2013-01-25 07:00:00,11767.0 -2013-01-25 08:00:00,12848.0 -2013-01-25 09:00:00,13324.0 -2013-01-25 10:00:00,13411.0 -2013-01-25 11:00:00,13591.0 -2013-01-25 12:00:00,13661.0 -2013-01-25 13:00:00,13577.0 -2013-01-25 14:00:00,13473.0 -2013-01-25 15:00:00,13425.0 -2013-01-25 16:00:00,13313.0 -2013-01-25 17:00:00,13213.0 -2013-01-25 18:00:00,13399.0 -2013-01-25 19:00:00,14005.0 -2013-01-25 20:00:00,13892.0 -2013-01-25 21:00:00,13699.0 -2013-01-25 22:00:00,13333.0 -2013-01-25 23:00:00,12933.0 -2013-01-26 00:00:00,12175.0 -2013-01-24 01:00:00,12231.0 -2013-01-24 02:00:00,11758.0 -2013-01-24 03:00:00,11513.0 -2013-01-24 04:00:00,11423.0 -2013-01-24 05:00:00,11436.0 -2013-01-24 06:00:00,11691.0 -2013-01-24 07:00:00,12382.0 -2013-01-24 08:00:00,13548.0 -2013-01-24 09:00:00,14077.0 -2013-01-24 10:00:00,14092.0 -2013-01-24 11:00:00,14104.0 -2013-01-24 12:00:00,14057.0 -2013-01-24 13:00:00,13934.0 -2013-01-24 14:00:00,13764.0 -2013-01-24 15:00:00,13637.0 -2013-01-24 16:00:00,13509.0 -2013-01-24 17:00:00,13482.0 -2013-01-24 18:00:00,13933.0 -2013-01-24 19:00:00,14623.0 -2013-01-24 20:00:00,14509.0 -2013-01-24 21:00:00,14337.0 -2013-01-24 22:00:00,14021.0 -2013-01-24 23:00:00,13508.0 -2013-01-25 00:00:00,12609.0 -2013-01-23 01:00:00,12327.0 -2013-01-23 02:00:00,11875.0 -2013-01-23 03:00:00,11572.0 -2013-01-23 04:00:00,11409.0 -2013-01-23 05:00:00,11399.0 -2013-01-23 06:00:00,11592.0 -2013-01-23 07:00:00,12200.0 -2013-01-23 08:00:00,13307.0 -2013-01-23 09:00:00,13933.0 -2013-01-23 10:00:00,13999.0 -2013-01-23 11:00:00,14074.0 -2013-01-23 12:00:00,14194.0 -2013-01-23 13:00:00,14138.0 -2013-01-23 14:00:00,14071.0 -2013-01-23 15:00:00,14001.0 -2013-01-23 16:00:00,13884.0 -2013-01-23 17:00:00,13844.0 -2013-01-23 18:00:00,14121.0 -2013-01-23 19:00:00,14777.0 -2013-01-23 20:00:00,14683.0 -2013-01-23 21:00:00,14543.0 -2013-01-23 22:00:00,14239.0 -2013-01-23 23:00:00,13800.0 -2013-01-24 00:00:00,12943.0 -2013-01-22 01:00:00,12472.0 -2013-01-22 02:00:00,11983.0 -2013-01-22 03:00:00,11744.0 -2013-01-22 04:00:00,11615.0 -2013-01-22 05:00:00,11629.0 -2013-01-22 06:00:00,11851.0 -2013-01-22 07:00:00,12557.0 -2013-01-22 08:00:00,13741.0 -2013-01-22 09:00:00,14229.0 -2013-01-22 10:00:00,14206.0 -2013-01-22 11:00:00,14274.0 -2013-01-22 12:00:00,14259.0 -2013-01-22 13:00:00,14169.0 -2013-01-22 14:00:00,14085.0 -2013-01-22 15:00:00,13951.0 -2013-01-22 16:00:00,13879.0 -2013-01-22 17:00:00,13901.0 -2013-01-22 18:00:00,14401.0 -2013-01-22 19:00:00,15139.0 -2013-01-22 20:00:00,15052.0 -2013-01-22 21:00:00,14849.0 -2013-01-22 22:00:00,14534.0 -2013-01-22 23:00:00,13977.0 -2013-01-23 00:00:00,13116.0 -2013-01-21 01:00:00,11200.0 -2013-01-21 02:00:00,10827.0 -2013-01-21 03:00:00,10670.0 -2013-01-21 04:00:00,10608.0 -2013-01-21 05:00:00,10673.0 -2013-01-21 06:00:00,10954.0 -2013-01-21 07:00:00,11581.0 -2013-01-21 08:00:00,12575.0 -2013-01-21 09:00:00,13122.0 -2013-01-21 10:00:00,13352.0 -2013-01-21 11:00:00,13544.0 -2013-01-21 12:00:00,13739.0 -2013-01-21 13:00:00,13776.0 -2013-01-21 14:00:00,13773.0 -2013-01-21 15:00:00,13697.0 -2013-01-21 16:00:00,13582.0 -2013-01-21 17:00:00,13558.0 -2013-01-21 18:00:00,13975.0 -2013-01-21 19:00:00,14914.0 -2013-01-21 20:00:00,14950.0 -2013-01-21 21:00:00,14789.0 -2013-01-21 22:00:00,14518.0 -2013-01-21 23:00:00,14025.0 -2013-01-22 00:00:00,13183.0 -2013-01-20 01:00:00,10358.0 -2013-01-20 02:00:00,10024.0 -2013-01-20 03:00:00,9926.0 -2013-01-20 04:00:00,9820.0 -2013-01-20 05:00:00,9854.0 -2013-01-20 06:00:00,9932.0 -2013-01-20 07:00:00,10148.0 -2013-01-20 08:00:00,10419.0 -2013-01-20 09:00:00,10539.0 -2013-01-20 10:00:00,10727.0 -2013-01-20 11:00:00,10876.0 -2013-01-20 12:00:00,10962.0 -2013-01-20 13:00:00,11023.0 -2013-01-20 14:00:00,11071.0 -2013-01-20 15:00:00,11054.0 -2013-01-20 16:00:00,11117.0 -2013-01-20 17:00:00,11217.0 -2013-01-20 18:00:00,11751.0 -2013-01-20 19:00:00,12641.0 -2013-01-20 20:00:00,12756.0 -2013-01-20 21:00:00,12658.0 -2013-01-20 22:00:00,12510.0 -2013-01-20 23:00:00,12207.0 -2013-01-21 00:00:00,11689.0 -2013-01-19 01:00:00,10622.0 -2013-01-19 02:00:00,10049.0 -2013-01-19 03:00:00,9705.0 -2013-01-19 04:00:00,9475.0 -2013-01-19 05:00:00,9415.0 -2013-01-19 06:00:00,9458.0 -2013-01-19 07:00:00,9743.0 -2013-01-19 08:00:00,10194.0 -2013-01-19 09:00:00,10513.0 -2013-01-19 10:00:00,10782.0 -2013-01-19 11:00:00,11063.0 -2013-01-19 12:00:00,11077.0 -2013-01-19 13:00:00,10896.0 -2013-01-19 14:00:00,10804.0 -2013-01-19 15:00:00,10715.0 -2013-01-19 16:00:00,10512.0 -2013-01-19 17:00:00,10381.0 -2013-01-19 18:00:00,10643.0 -2013-01-19 19:00:00,11509.0 -2013-01-19 20:00:00,11545.0 -2013-01-19 21:00:00,11452.0 -2013-01-19 22:00:00,11317.0 -2013-01-19 23:00:00,11099.0 -2013-01-20 00:00:00,10713.0 -2013-01-18 01:00:00,11199.0 -2013-01-18 02:00:00,10729.0 -2013-01-18 03:00:00,10444.0 -2013-01-18 04:00:00,10323.0 -2013-01-18 05:00:00,10297.0 -2013-01-18 06:00:00,10538.0 -2013-01-18 07:00:00,11254.0 -2013-01-18 08:00:00,12424.0 -2013-01-18 09:00:00,13064.0 -2013-01-18 10:00:00,13140.0 -2013-01-18 11:00:00,13165.0 -2013-01-18 12:00:00,13153.0 -2013-01-18 13:00:00,12952.0 -2013-01-18 14:00:00,12652.0 -2013-01-18 15:00:00,12498.0 -2013-01-18 16:00:00,12260.0 -2013-01-18 17:00:00,12162.0 -2013-01-18 18:00:00,12426.0 -2013-01-18 19:00:00,13240.0 -2013-01-18 20:00:00,13159.0 -2013-01-18 21:00:00,12889.0 -2013-01-18 22:00:00,12536.0 -2013-01-18 23:00:00,12063.0 -2013-01-19 00:00:00,11318.0 -2013-01-17 01:00:00,10738.0 -2013-01-17 02:00:00,10203.0 -2013-01-17 03:00:00,9910.0 -2013-01-17 04:00:00,9783.0 -2013-01-17 05:00:00,9804.0 -2013-01-17 06:00:00,10096.0 -2013-01-17 07:00:00,10910.0 -2013-01-17 08:00:00,12213.0 -2013-01-17 09:00:00,12809.0 -2013-01-17 10:00:00,12756.0 -2013-01-17 11:00:00,12742.0 -2013-01-17 12:00:00,12762.0 -2013-01-17 13:00:00,12709.0 -2013-01-17 14:00:00,12617.0 -2013-01-17 15:00:00,12571.0 -2013-01-17 16:00:00,12419.0 -2013-01-17 17:00:00,12414.0 -2013-01-17 18:00:00,12816.0 -2013-01-17 19:00:00,13724.0 -2013-01-17 20:00:00,13670.0 -2013-01-17 21:00:00,13486.0 -2013-01-17 22:00:00,13228.0 -2013-01-17 23:00:00,12717.0 -2013-01-18 00:00:00,11903.0 -2013-01-16 01:00:00,11160.0 -2013-01-16 02:00:00,10707.0 -2013-01-16 03:00:00,10466.0 -2013-01-16 04:00:00,10351.0 -2013-01-16 05:00:00,10376.0 -2013-01-16 06:00:00,10662.0 -2013-01-16 07:00:00,11414.0 -2013-01-16 08:00:00,12684.0 -2013-01-16 09:00:00,13210.0 -2013-01-16 10:00:00,13047.0 -2013-01-16 11:00:00,13054.0 -2013-01-16 12:00:00,13049.0 -2013-01-16 13:00:00,12916.0 -2013-01-16 14:00:00,12803.0 -2013-01-16 15:00:00,12809.0 -2013-01-16 16:00:00,12862.0 -2013-01-16 17:00:00,12947.0 -2013-01-16 18:00:00,13371.0 -2013-01-16 19:00:00,13873.0 -2013-01-16 20:00:00,13644.0 -2013-01-16 21:00:00,13364.0 -2013-01-16 22:00:00,12990.0 -2013-01-16 23:00:00,12401.0 -2013-01-17 00:00:00,11499.0 -2013-01-15 01:00:00,11382.0 -2013-01-15 02:00:00,10925.0 -2013-01-15 03:00:00,10633.0 -2013-01-15 04:00:00,10530.0 -2013-01-15 05:00:00,10547.0 -2013-01-15 06:00:00,10814.0 -2013-01-15 07:00:00,11546.0 -2013-01-15 08:00:00,12796.0 -2013-01-15 09:00:00,13323.0 -2013-01-15 10:00:00,13202.0 -2013-01-15 11:00:00,13096.0 -2013-01-15 12:00:00,12999.0 -2013-01-15 13:00:00,12908.0 -2013-01-15 14:00:00,12774.0 -2013-01-15 15:00:00,12768.0 -2013-01-15 16:00:00,12638.0 -2013-01-15 17:00:00,12589.0 -2013-01-15 18:00:00,12977.0 -2013-01-15 19:00:00,13851.0 -2013-01-15 20:00:00,13771.0 -2013-01-15 21:00:00,13592.0 -2013-01-15 22:00:00,13321.0 -2013-01-15 23:00:00,12765.0 -2013-01-16 00:00:00,11919.0 -2013-01-14 01:00:00,10871.0 -2013-01-14 02:00:00,10564.0 -2013-01-14 03:00:00,10337.0 -2013-01-14 04:00:00,10301.0 -2013-01-14 05:00:00,10363.0 -2013-01-14 06:00:00,10661.0 -2013-01-14 07:00:00,11432.0 -2013-01-14 08:00:00,12777.0 -2013-01-14 09:00:00,13402.0 -2013-01-14 10:00:00,13373.0 -2013-01-14 11:00:00,13299.0 -2013-01-14 12:00:00,13277.0 -2013-01-14 13:00:00,13252.0 -2013-01-14 14:00:00,13121.0 -2013-01-14 15:00:00,13041.0 -2013-01-14 16:00:00,12792.0 -2013-01-14 17:00:00,12712.0 -2013-01-14 18:00:00,13154.0 -2013-01-14 19:00:00,14041.0 -2013-01-14 20:00:00,14023.0 -2013-01-14 21:00:00,13857.0 -2013-01-14 22:00:00,13522.0 -2013-01-14 23:00:00,12993.0 -2013-01-15 00:00:00,12140.0 -2013-01-13 01:00:00,10076.0 -2013-01-13 02:00:00,9666.0 -2013-01-13 03:00:00,9312.0 -2013-01-13 04:00:00,9151.0 -2013-01-13 05:00:00,8997.0 -2013-01-13 06:00:00,9074.0 -2013-01-13 07:00:00,9197.0 -2013-01-13 08:00:00,9546.0 -2013-01-13 09:00:00,9805.0 -2013-01-13 10:00:00,10087.0 -2013-01-13 11:00:00,10367.0 -2013-01-13 12:00:00,10689.0 -2013-01-13 13:00:00,10826.0 -2013-01-13 14:00:00,10966.0 -2013-01-13 15:00:00,11011.0 -2013-01-13 16:00:00,11022.0 -2013-01-13 17:00:00,11185.0 -2013-01-13 18:00:00,11804.0 -2013-01-13 19:00:00,12507.0 -2013-01-13 20:00:00,12609.0 -2013-01-13 21:00:00,12504.0 -2013-01-13 22:00:00,12345.0 -2013-01-13 23:00:00,12005.0 -2013-01-14 00:00:00,11457.0 -2013-01-12 01:00:00,9962.0 -2013-01-12 02:00:00,9393.0 -2013-01-12 03:00:00,9031.0 -2013-01-12 04:00:00,8760.0 -2013-01-12 05:00:00,8659.0 -2013-01-12 06:00:00,8657.0 -2013-01-12 07:00:00,8897.0 -2013-01-12 08:00:00,9369.0 -2013-01-12 09:00:00,9676.0 -2013-01-12 10:00:00,9939.0 -2013-01-12 11:00:00,10227.0 -2013-01-12 12:00:00,10444.0 -2013-01-12 13:00:00,10480.0 -2013-01-12 14:00:00,10430.0 -2013-01-12 15:00:00,10328.0 -2013-01-12 16:00:00,10369.0 -2013-01-12 17:00:00,10511.0 -2013-01-12 18:00:00,11052.0 -2013-01-12 19:00:00,11628.0 -2013-01-12 20:00:00,11559.0 -2013-01-12 21:00:00,11427.0 -2013-01-12 22:00:00,11330.0 -2013-01-12 23:00:00,11069.0 -2013-01-13 00:00:00,10598.0 -2013-01-11 01:00:00,10377.0 -2013-01-11 02:00:00,9781.0 -2013-01-11 03:00:00,9452.0 -2013-01-11 04:00:00,9244.0 -2013-01-11 05:00:00,9162.0 -2013-01-11 06:00:00,9310.0 -2013-01-11 07:00:00,9931.0 -2013-01-11 08:00:00,11057.0 -2013-01-11 09:00:00,11711.0 -2013-01-11 10:00:00,11778.0 -2013-01-11 11:00:00,11856.0 -2013-01-11 12:00:00,11988.0 -2013-01-11 13:00:00,11952.0 -2013-01-11 14:00:00,11945.0 -2013-01-11 15:00:00,11900.0 -2013-01-11 16:00:00,11773.0 -2013-01-11 17:00:00,11700.0 -2013-01-11 18:00:00,12109.0 -2013-01-11 19:00:00,12559.0 -2013-01-11 20:00:00,12403.0 -2013-01-11 21:00:00,12116.0 -2013-01-11 22:00:00,11797.0 -2013-01-11 23:00:00,11384.0 -2013-01-12 00:00:00,10667.0 -2013-01-10 01:00:00,10495.0 -2013-01-10 02:00:00,10057.0 -2013-01-10 03:00:00,9792.0 -2013-01-10 04:00:00,9680.0 -2013-01-10 05:00:00,9692.0 -2013-01-10 06:00:00,9962.0 -2013-01-10 07:00:00,10707.0 -2013-01-10 08:00:00,11963.0 -2013-01-10 09:00:00,12658.0 -2013-01-10 10:00:00,12559.0 -2013-01-10 11:00:00,12494.0 -2013-01-10 12:00:00,12589.0 -2013-01-10 13:00:00,12510.0 -2013-01-10 14:00:00,12300.0 -2013-01-10 15:00:00,12297.0 -2013-01-10 16:00:00,12302.0 -2013-01-10 17:00:00,12414.0 -2013-01-10 18:00:00,12978.0 -2013-01-10 19:00:00,13430.0 -2013-01-10 20:00:00,13219.0 -2013-01-10 21:00:00,12967.0 -2013-01-10 22:00:00,12641.0 -2013-01-10 23:00:00,12084.0 -2013-01-11 00:00:00,11182.0 -2013-01-09 01:00:00,10617.0 -2013-01-09 02:00:00,10080.0 -2013-01-09 03:00:00,9749.0 -2013-01-09 04:00:00,9564.0 -2013-01-09 05:00:00,9587.0 -2013-01-09 06:00:00,9808.0 -2013-01-09 07:00:00,10562.0 -2013-01-09 08:00:00,11802.0 -2013-01-09 09:00:00,12376.0 -2013-01-09 10:00:00,12230.0 -2013-01-09 11:00:00,12187.0 -2013-01-09 12:00:00,12134.0 -2013-01-09 13:00:00,12078.0 -2013-01-09 14:00:00,12006.0 -2013-01-09 15:00:00,11950.0 -2013-01-09 16:00:00,11851.0 -2013-01-09 17:00:00,11799.0 -2013-01-09 18:00:00,12362.0 -2013-01-09 19:00:00,13144.0 -2013-01-09 20:00:00,13017.0 -2013-01-09 21:00:00,12858.0 -2013-01-09 22:00:00,12583.0 -2013-01-09 23:00:00,12068.0 -2013-01-10 00:00:00,11263.0 -2013-01-08 01:00:00,10806.0 -2013-01-08 02:00:00,10318.0 -2013-01-08 03:00:00,10031.0 -2013-01-08 04:00:00,9887.0 -2013-01-08 05:00:00,9877.0 -2013-01-08 06:00:00,10135.0 -2013-01-08 07:00:00,10855.0 -2013-01-08 08:00:00,12113.0 -2013-01-08 09:00:00,12684.0 -2013-01-08 10:00:00,12613.0 -2013-01-08 11:00:00,12590.0 -2013-01-08 12:00:00,12568.0 -2013-01-08 13:00:00,12374.0 -2013-01-08 14:00:00,12264.0 -2013-01-08 15:00:00,12244.0 -2013-01-08 16:00:00,12117.0 -2013-01-08 17:00:00,12085.0 -2013-01-08 18:00:00,12636.0 -2013-01-08 19:00:00,13467.0 -2013-01-08 20:00:00,13313.0 -2013-01-08 21:00:00,13119.0 -2013-01-08 22:00:00,12837.0 -2013-01-08 23:00:00,12286.0 -2013-01-09 00:00:00,11419.0 -2013-01-07 01:00:00,10612.0 -2013-01-07 02:00:00,10244.0 -2013-01-07 03:00:00,10072.0 -2013-01-07 04:00:00,9992.0 -2013-01-07 05:00:00,10095.0 -2013-01-07 06:00:00,10442.0 -2013-01-07 07:00:00,11264.0 -2013-01-07 08:00:00,12518.0 -2013-01-07 09:00:00,13104.0 -2013-01-07 10:00:00,12949.0 -2013-01-07 11:00:00,12937.0 -2013-01-07 12:00:00,12901.0 -2013-01-07 13:00:00,12751.0 -2013-01-07 14:00:00,12619.0 -2013-01-07 15:00:00,12546.0 -2013-01-07 16:00:00,12365.0 -2013-01-07 17:00:00,12348.0 -2013-01-07 18:00:00,12972.0 -2013-01-07 19:00:00,13747.0 -2013-01-07 20:00:00,13626.0 -2013-01-07 21:00:00,13382.0 -2013-01-07 22:00:00,13046.0 -2013-01-07 23:00:00,12452.0 -2013-01-08 00:00:00,11632.0 -2013-01-06 01:00:00,10533.0 -2013-01-06 02:00:00,9985.0 -2013-01-06 03:00:00,9639.0 -2013-01-06 04:00:00,9348.0 -2013-01-06 05:00:00,9275.0 -2013-01-06 06:00:00,9300.0 -2013-01-06 07:00:00,9547.0 -2013-01-06 08:00:00,9837.0 -2013-01-06 09:00:00,10050.0 -2013-01-06 10:00:00,10192.0 -2013-01-06 11:00:00,10452.0 -2013-01-06 12:00:00,10641.0 -2013-01-06 13:00:00,10677.0 -2013-01-06 14:00:00,10624.0 -2013-01-06 15:00:00,10514.0 -2013-01-06 16:00:00,10450.0 -2013-01-06 17:00:00,10582.0 -2013-01-06 18:00:00,11293.0 -2013-01-06 19:00:00,12373.0 -2013-01-06 20:00:00,12421.0 -2013-01-06 21:00:00,12396.0 -2013-01-06 22:00:00,12109.0 -2013-01-06 23:00:00,11775.0 -2013-01-07 00:00:00,11164.0 -2013-01-05 01:00:00,11261.0 -2013-01-05 02:00:00,10728.0 -2013-01-05 03:00:00,10307.0 -2013-01-05 04:00:00,10126.0 -2013-01-05 05:00:00,9997.0 -2013-01-05 06:00:00,10081.0 -2013-01-05 07:00:00,10348.0 -2013-01-05 08:00:00,10821.0 -2013-01-05 09:00:00,11064.0 -2013-01-05 10:00:00,11161.0 -2013-01-05 11:00:00,11275.0 -2013-01-05 12:00:00,11369.0 -2013-01-05 13:00:00,11346.0 -2013-01-05 14:00:00,11257.0 -2013-01-05 15:00:00,11289.0 -2013-01-05 16:00:00,11446.0 -2013-01-05 17:00:00,11459.0 -2013-01-05 18:00:00,12013.0 -2013-01-05 19:00:00,12557.0 -2013-01-05 20:00:00,12525.0 -2013-01-05 21:00:00,12330.0 -2013-01-05 22:00:00,12175.0 -2013-01-05 23:00:00,11806.0 -2013-01-06 00:00:00,11183.0 -2013-01-04 01:00:00,11528.0 -2013-01-04 02:00:00,11019.0 -2013-01-04 03:00:00,10708.0 -2013-01-04 04:00:00,10598.0 -2013-01-04 05:00:00,10648.0 -2013-01-04 06:00:00,10915.0 -2013-01-04 07:00:00,11604.0 -2013-01-04 08:00:00,12587.0 -2013-01-04 09:00:00,13111.0 -2013-01-04 10:00:00,13173.0 -2013-01-04 11:00:00,13165.0 -2013-01-04 12:00:00,13127.0 -2013-01-04 13:00:00,13000.0 -2013-01-04 14:00:00,12777.0 -2013-01-04 15:00:00,12650.0 -2013-01-04 16:00:00,12490.0 -2013-01-04 17:00:00,12391.0 -2013-01-04 18:00:00,12977.0 -2013-01-04 19:00:00,13784.0 -2013-01-04 20:00:00,13693.0 -2013-01-04 21:00:00,13383.0 -2013-01-04 22:00:00,13151.0 -2013-01-04 23:00:00,12716.0 -2013-01-05 00:00:00,12061.0 -2013-01-03 01:00:00,11443.0 -2013-01-03 02:00:00,10882.0 -2013-01-03 03:00:00,10526.0 -2013-01-03 04:00:00,10267.0 -2013-01-03 05:00:00,10213.0 -2013-01-03 06:00:00,10399.0 -2013-01-03 07:00:00,10982.0 -2013-01-03 08:00:00,11955.0 -2013-01-03 09:00:00,12607.0 -2013-01-03 10:00:00,12789.0 -2013-01-03 11:00:00,12968.0 -2013-01-03 12:00:00,13112.0 -2013-01-03 13:00:00,13178.0 -2013-01-03 14:00:00,13179.0 -2013-01-03 15:00:00,13182.0 -2013-01-03 16:00:00,13069.0 -2013-01-03 17:00:00,13001.0 -2013-01-03 18:00:00,13570.0 -2013-01-03 19:00:00,14201.0 -2013-01-03 20:00:00,14044.0 -2013-01-03 21:00:00,13833.0 -2013-01-03 22:00:00,13521.0 -2013-01-03 23:00:00,13076.0 -2013-01-04 00:00:00,12279.0 -2013-01-02 01:00:00,10780.0 -2013-01-02 02:00:00,10391.0 -2013-01-02 03:00:00,10157.0 -2013-01-02 04:00:00,10051.0 -2013-01-02 05:00:00,10087.0 -2013-01-02 06:00:00,10331.0 -2013-01-02 07:00:00,10980.0 -2013-01-02 08:00:00,12081.0 -2013-01-02 09:00:00,12656.0 -2013-01-02 10:00:00,12811.0 -2013-01-02 11:00:00,12887.0 -2013-01-02 12:00:00,12930.0 -2013-01-02 13:00:00,12900.0 -2013-01-02 14:00:00,12808.0 -2013-01-02 15:00:00,12705.0 -2013-01-02 16:00:00,12583.0 -2013-01-02 17:00:00,12562.0 -2013-01-02 18:00:00,13237.0 -2013-01-02 19:00:00,14138.0 -2013-01-02 20:00:00,14039.0 -2013-01-02 21:00:00,13857.0 -2013-01-02 22:00:00,13574.0 -2013-01-02 23:00:00,13098.0 -2013-01-03 00:00:00,12287.0 -2013-01-01 01:00:00,10581.0 -2013-01-01 02:00:00,10175.0 -2013-01-01 03:00:00,9866.0 -2013-01-01 04:00:00,9631.0 -2013-01-01 05:00:00,9536.0 -2013-01-01 06:00:00,9583.0 -2013-01-01 07:00:00,9778.0 -2013-01-01 08:00:00,10059.0 -2013-01-01 09:00:00,10047.0 -2013-01-01 10:00:00,9984.0 -2013-01-01 11:00:00,10102.0 -2013-01-01 12:00:00,10227.0 -2013-01-01 13:00:00,10262.0 -2013-01-01 14:00:00,10263.0 -2013-01-01 15:00:00,10216.0 -2013-01-01 16:00:00,10173.0 -2013-01-01 17:00:00,10339.0 -2013-01-01 18:00:00,11208.0 -2013-01-01 19:00:00,12355.0 -2013-01-01 20:00:00,12429.0 -2013-01-01 21:00:00,12364.0 -2013-01-01 22:00:00,12229.0 -2013-01-01 23:00:00,11909.0 -2013-01-02 00:00:00,11381.0 -2014-12-31 01:00:00,11633.0 -2014-12-31 02:00:00,11139.0 -2014-12-31 03:00:00,10871.0 -2014-12-31 04:00:00,10735.0 -2014-12-31 05:00:00,10714.0 -2014-12-31 06:00:00,10886.0 -2014-12-31 07:00:00,11404.0 -2014-12-31 08:00:00,12098.0 -2014-12-31 09:00:00,12409.0 -2014-12-31 10:00:00,12526.0 -2014-12-31 11:00:00,12620.0 -2014-12-31 12:00:00,12672.0 -2014-12-31 13:00:00,12608.0 -2014-12-31 14:00:00,12441.0 -2014-12-31 15:00:00,12281.0 -2014-12-31 16:00:00,12123.0 -2014-12-31 17:00:00,12081.0 -2014-12-31 18:00:00,12851.0 -2014-12-31 19:00:00,13663.0 -2014-12-31 20:00:00,13419.0 -2014-12-31 21:00:00,13067.0 -2014-12-31 22:00:00,12672.0 -2014-12-31 23:00:00,12244.0 -2015-01-01 00:00:00,11774.0 -2014-12-30 01:00:00,10988.0 -2014-12-30 02:00:00,10521.0 -2014-12-30 03:00:00,10264.0 -2014-12-30 04:00:00,10114.0 -2014-12-30 05:00:00,10150.0 -2014-12-30 06:00:00,10432.0 -2014-12-30 07:00:00,11105.0 -2014-12-30 08:00:00,12020.0 -2014-12-30 09:00:00,12496.0 -2014-12-30 10:00:00,12593.0 -2014-12-30 11:00:00,12687.0 -2014-12-30 12:00:00,12708.0 -2014-12-30 13:00:00,12651.0 -2014-12-30 14:00:00,12541.0 -2014-12-30 15:00:00,12490.0 -2014-12-30 16:00:00,12405.0 -2014-12-30 17:00:00,12460.0 -2014-12-30 18:00:00,13201.0 -2014-12-30 19:00:00,14012.0 -2014-12-30 20:00:00,13869.0 -2014-12-30 21:00:00,13705.0 -2014-12-30 22:00:00,13503.0 -2014-12-30 23:00:00,13083.0 -2014-12-31 00:00:00,12388.0 -2014-12-29 01:00:00,10290.0 -2014-12-29 02:00:00,9809.0 -2014-12-29 03:00:00,9606.0 -2014-12-29 04:00:00,9442.0 -2014-12-29 05:00:00,9491.0 -2014-12-29 06:00:00,9703.0 -2014-12-29 07:00:00,10364.0 -2014-12-29 08:00:00,11238.0 -2014-12-29 09:00:00,11754.0 -2014-12-29 10:00:00,11909.0 -2014-12-29 11:00:00,12058.0 -2014-12-29 12:00:00,12126.0 -2014-12-29 13:00:00,12088.0 -2014-12-29 14:00:00,12095.0 -2014-12-29 15:00:00,12114.0 -2014-12-29 16:00:00,12107.0 -2014-12-29 17:00:00,12162.0 -2014-12-29 18:00:00,12891.0 -2014-12-29 19:00:00,13514.0 -2014-12-29 20:00:00,13355.0 -2014-12-29 21:00:00,13105.0 -2014-12-29 22:00:00,12884.0 -2014-12-29 23:00:00,12414.0 -2014-12-30 00:00:00,11710.0 -2014-12-28 01:00:00,10030.0 -2014-12-28 02:00:00,9590.0 -2014-12-28 03:00:00,9225.0 -2014-12-28 04:00:00,9082.0 -2014-12-28 05:00:00,8985.0 -2014-12-28 06:00:00,9069.0 -2014-12-28 07:00:00,9246.0 -2014-12-28 08:00:00,9559.0 -2014-12-28 09:00:00,9707.0 -2014-12-28 10:00:00,9868.0 -2014-12-28 11:00:00,10041.0 -2014-12-28 12:00:00,10108.0 -2014-12-28 13:00:00,10165.0 -2014-12-28 14:00:00,10064.0 -2014-12-28 15:00:00,9957.0 -2014-12-28 16:00:00,9864.0 -2014-12-28 17:00:00,9984.0 -2014-12-28 18:00:00,10794.0 -2014-12-28 19:00:00,11746.0 -2014-12-28 20:00:00,11832.0 -2014-12-28 21:00:00,11807.0 -2014-12-28 22:00:00,11634.0 -2014-12-28 23:00:00,11395.0 -2014-12-29 00:00:00,10835.0 -2014-12-27 01:00:00,9464.0 -2014-12-27 02:00:00,8989.0 -2014-12-27 03:00:00,8591.0 -2014-12-27 04:00:00,8412.0 -2014-12-27 05:00:00,8301.0 -2014-12-27 06:00:00,8385.0 -2014-12-27 07:00:00,8587.0 -2014-12-27 08:00:00,9034.0 -2014-12-27 09:00:00,9334.0 -2014-12-27 10:00:00,9602.0 -2014-12-27 11:00:00,9948.0 -2014-12-27 12:00:00,10225.0 -2014-12-27 13:00:00,10397.0 -2014-12-27 14:00:00,10477.0 -2014-12-27 15:00:00,10354.0 -2014-12-27 16:00:00,10348.0 -2014-12-27 17:00:00,10522.0 -2014-12-27 18:00:00,11248.0 -2014-12-27 19:00:00,11691.0 -2014-12-27 20:00:00,11684.0 -2014-12-27 21:00:00,11523.0 -2014-12-27 22:00:00,11403.0 -2014-12-27 23:00:00,11107.0 -2014-12-28 00:00:00,10640.0 -2014-12-26 01:00:00,9388.0 -2014-12-26 02:00:00,8952.0 -2014-12-26 03:00:00,8688.0 -2014-12-26 04:00:00,8555.0 -2014-12-26 05:00:00,8566.0 -2014-12-26 06:00:00,8766.0 -2014-12-26 07:00:00,9258.0 -2014-12-26 08:00:00,9919.0 -2014-12-26 09:00:00,10180.0 -2014-12-26 10:00:00,10275.0 -2014-12-26 11:00:00,10399.0 -2014-12-26 12:00:00,10440.0 -2014-12-26 13:00:00,10394.0 -2014-12-26 14:00:00,10294.0 -2014-12-26 15:00:00,10187.0 -2014-12-26 16:00:00,10087.0 -2014-12-26 17:00:00,10114.0 -2014-12-26 18:00:00,10882.0 -2014-12-26 19:00:00,11661.0 -2014-12-26 20:00:00,11542.0 -2014-12-26 21:00:00,11346.0 -2014-12-26 22:00:00,11049.0 -2014-12-26 23:00:00,10730.0 -2014-12-27 00:00:00,10164.0 -2014-12-25 01:00:00,9681.0 -2014-12-25 02:00:00,9210.0 -2014-12-25 03:00:00,8851.0 -2014-12-25 04:00:00,8632.0 -2014-12-25 05:00:00,8526.0 -2014-12-25 06:00:00,8601.0 -2014-12-25 07:00:00,8798.0 -2014-12-25 08:00:00,9088.0 -2014-12-25 09:00:00,9251.0 -2014-12-25 10:00:00,9449.0 -2014-12-25 11:00:00,9696.0 -2014-12-25 12:00:00,9757.0 -2014-12-25 13:00:00,9640.0 -2014-12-25 14:00:00,9501.0 -2014-12-25 15:00:00,9310.0 -2014-12-25 16:00:00,9150.0 -2014-12-25 17:00:00,9218.0 -2014-12-25 18:00:00,9865.0 -2014-12-25 19:00:00,10499.0 -2014-12-25 20:00:00,10477.0 -2014-12-25 21:00:00,10491.0 -2014-12-25 22:00:00,10451.0 -2014-12-25 23:00:00,10272.0 -2014-12-26 00:00:00,9901.0 -2014-12-24 01:00:00,10159.0 -2014-12-24 02:00:00,9477.0 -2014-12-24 03:00:00,9055.0 -2014-12-24 04:00:00,8789.0 -2014-12-24 05:00:00,8735.0 -2014-12-24 06:00:00,8874.0 -2014-12-24 07:00:00,9265.0 -2014-12-24 08:00:00,9863.0 -2014-12-24 09:00:00,10291.0 -2014-12-24 10:00:00,10530.0 -2014-12-24 11:00:00,10824.0 -2014-12-24 12:00:00,10955.0 -2014-12-24 13:00:00,10948.0 -2014-12-24 14:00:00,10845.0 -2014-12-24 15:00:00,10790.0 -2014-12-24 16:00:00,10718.0 -2014-12-24 17:00:00,10835.0 -2014-12-24 18:00:00,11428.0 -2014-12-24 19:00:00,11655.0 -2014-12-24 20:00:00,11259.0 -2014-12-24 21:00:00,10963.0 -2014-12-24 22:00:00,10770.0 -2014-12-24 23:00:00,10549.0 -2014-12-25 00:00:00,10183.0 -2014-12-23 01:00:00,10667.0 -2014-12-23 02:00:00,10019.0 -2014-12-23 03:00:00,9584.0 -2014-12-23 04:00:00,9315.0 -2014-12-23 05:00:00,9215.0 -2014-12-23 06:00:00,9356.0 -2014-12-23 07:00:00,9926.0 -2014-12-23 08:00:00,10816.0 -2014-12-23 09:00:00,11427.0 -2014-12-23 10:00:00,11511.0 -2014-12-23 11:00:00,11566.0 -2014-12-23 12:00:00,11683.0 -2014-12-23 13:00:00,11589.0 -2014-12-23 14:00:00,11480.0 -2014-12-23 15:00:00,11448.0 -2014-12-23 16:00:00,11344.0 -2014-12-23 17:00:00,11303.0 -2014-12-23 18:00:00,12019.0 -2014-12-23 19:00:00,12710.0 -2014-12-23 20:00:00,12570.0 -2014-12-23 21:00:00,12353.0 -2014-12-23 22:00:00,12128.0 -2014-12-23 23:00:00,11727.0 -2014-12-24 00:00:00,10984.0 -2014-12-22 01:00:00,10467.0 -2014-12-22 02:00:00,10035.0 -2014-12-22 03:00:00,9735.0 -2014-12-22 04:00:00,9598.0 -2014-12-22 05:00:00,9617.0 -2014-12-22 06:00:00,9879.0 -2014-12-22 07:00:00,10577.0 -2014-12-22 08:00:00,11505.0 -2014-12-22 09:00:00,12200.0 -2014-12-22 10:00:00,12404.0 -2014-12-22 11:00:00,12544.0 -2014-12-22 12:00:00,12672.0 -2014-12-22 13:00:00,12631.0 -2014-12-22 14:00:00,12586.0 -2014-12-22 15:00:00,12578.0 -2014-12-22 16:00:00,12598.0 -2014-12-22 17:00:00,12731.0 -2014-12-22 18:00:00,13341.0 -2014-12-22 19:00:00,13590.0 -2014-12-22 20:00:00,13361.0 -2014-12-22 21:00:00,13088.0 -2014-12-22 22:00:00,12789.0 -2014-12-22 23:00:00,12323.0 -2014-12-23 00:00:00,11510.0 -2014-12-21 01:00:00,10568.0 -2014-12-21 02:00:00,9990.0 -2014-12-21 03:00:00,9606.0 -2014-12-21 04:00:00,9388.0 -2014-12-21 05:00:00,9319.0 -2014-12-21 06:00:00,9329.0 -2014-12-21 07:00:00,9521.0 -2014-12-21 08:00:00,9783.0 -2014-12-21 09:00:00,10085.0 -2014-12-21 10:00:00,10320.0 -2014-12-21 11:00:00,10600.0 -2014-12-21 12:00:00,10763.0 -2014-12-21 13:00:00,10869.0 -2014-12-21 14:00:00,10810.0 -2014-12-21 15:00:00,10729.0 -2014-12-21 16:00:00,10690.0 -2014-12-21 17:00:00,10792.0 -2014-12-21 18:00:00,11622.0 -2014-12-21 19:00:00,12251.0 -2014-12-21 20:00:00,12236.0 -2014-12-21 21:00:00,12193.0 -2014-12-21 22:00:00,12020.0 -2014-12-21 23:00:00,11733.0 -2014-12-22 00:00:00,11189.0 -2014-12-20 01:00:00,10977.0 -2014-12-20 02:00:00,10418.0 -2014-12-20 03:00:00,10012.0 -2014-12-20 04:00:00,9823.0 -2014-12-20 05:00:00,9677.0 -2014-12-20 06:00:00,9794.0 -2014-12-20 07:00:00,10039.0 -2014-12-20 08:00:00,10527.0 -2014-12-20 09:00:00,10887.0 -2014-12-20 10:00:00,11158.0 -2014-12-20 11:00:00,11480.0 -2014-12-20 12:00:00,11585.0 -2014-12-20 13:00:00,11567.0 -2014-12-20 14:00:00,11490.0 -2014-12-20 15:00:00,11263.0 -2014-12-20 16:00:00,11202.0 -2014-12-20 17:00:00,11265.0 -2014-12-20 18:00:00,12046.0 -2014-12-20 19:00:00,12439.0 -2014-12-20 20:00:00,12394.0 -2014-12-20 21:00:00,12269.0 -2014-12-20 22:00:00,12112.0 -2014-12-20 23:00:00,11745.0 -2014-12-21 00:00:00,11256.0 -2014-12-19 01:00:00,11185.0 -2014-12-19 02:00:00,10589.0 -2014-12-19 03:00:00,10237.0 -2014-12-19 04:00:00,10016.0 -2014-12-19 05:00:00,9998.0 -2014-12-19 06:00:00,10221.0 -2014-12-19 07:00:00,10933.0 -2014-12-19 08:00:00,12023.0 -2014-12-19 09:00:00,12665.0 -2014-12-19 10:00:00,12752.0 -2014-12-19 11:00:00,12837.0 -2014-12-19 12:00:00,12848.0 -2014-12-19 13:00:00,12802.0 -2014-12-19 14:00:00,12605.0 -2014-12-19 15:00:00,12561.0 -2014-12-19 16:00:00,12534.0 -2014-12-19 17:00:00,12690.0 -2014-12-19 18:00:00,13439.0 -2014-12-19 19:00:00,13775.0 -2014-12-19 20:00:00,13566.0 -2014-12-19 21:00:00,13325.0 -2014-12-19 22:00:00,12947.0 -2014-12-19 23:00:00,12560.0 -2014-12-20 00:00:00,11837.0 -2014-12-18 01:00:00,11283.0 -2014-12-18 02:00:00,10654.0 -2014-12-18 03:00:00,10355.0 -2014-12-18 04:00:00,10198.0 -2014-12-18 05:00:00,10170.0 -2014-12-18 06:00:00,10438.0 -2014-12-18 07:00:00,11228.0 -2014-12-18 08:00:00,12379.0 -2014-12-18 09:00:00,12865.0 -2014-12-18 10:00:00,12931.0 -2014-12-18 11:00:00,13040.0 -2014-12-18 12:00:00,13051.0 -2014-12-18 13:00:00,12966.0 -2014-12-18 14:00:00,12841.0 -2014-12-18 15:00:00,12829.0 -2014-12-18 16:00:00,12759.0 -2014-12-18 17:00:00,12840.0 -2014-12-18 18:00:00,13667.0 -2014-12-18 19:00:00,14095.0 -2014-12-18 20:00:00,13923.0 -2014-12-18 21:00:00,13716.0 -2014-12-18 22:00:00,13429.0 -2014-12-18 23:00:00,12907.0 -2014-12-19 00:00:00,12068.0 -2014-12-17 01:00:00,10849.0 -2014-12-17 02:00:00,10278.0 -2014-12-17 03:00:00,9950.0 -2014-12-17 04:00:00,9804.0 -2014-12-17 05:00:00,9827.0 -2014-12-17 06:00:00,10130.0 -2014-12-17 07:00:00,10919.0 -2014-12-17 08:00:00,12134.0 -2014-12-17 09:00:00,12784.0 -2014-12-17 10:00:00,12897.0 -2014-12-17 11:00:00,12974.0 -2014-12-17 12:00:00,13072.0 -2014-12-17 13:00:00,13040.0 -2014-12-17 14:00:00,12995.0 -2014-12-17 15:00:00,13053.0 -2014-12-17 16:00:00,13005.0 -2014-12-17 17:00:00,13117.0 -2014-12-17 18:00:00,13848.0 -2014-12-17 19:00:00,14310.0 -2014-12-17 20:00:00,14099.0 -2014-12-17 21:00:00,13856.0 -2014-12-17 22:00:00,13600.0 -2014-12-17 23:00:00,13054.0 -2014-12-18 00:00:00,12179.0 -2014-12-16 01:00:00,10292.0 -2014-12-16 02:00:00,9677.0 -2014-12-16 03:00:00,9295.0 -2014-12-16 04:00:00,9104.0 -2014-12-16 05:00:00,9071.0 -2014-12-16 06:00:00,9313.0 -2014-12-16 07:00:00,10013.0 -2014-12-16 08:00:00,11169.0 -2014-12-16 09:00:00,11849.0 -2014-12-16 10:00:00,12004.0 -2014-12-16 11:00:00,12091.0 -2014-12-16 12:00:00,12204.0 -2014-12-16 13:00:00,12230.0 -2014-12-16 14:00:00,12227.0 -2014-12-16 15:00:00,12323.0 -2014-12-16 16:00:00,12350.0 -2014-12-16 17:00:00,12556.0 -2014-12-16 18:00:00,13293.0 -2014-12-16 19:00:00,13705.0 -2014-12-16 20:00:00,13577.0 -2014-12-16 21:00:00,13388.0 -2014-12-16 22:00:00,13095.0 -2014-12-16 23:00:00,12580.0 -2014-12-17 00:00:00,11700.0 -2014-12-15 01:00:00,9637.0 -2014-12-15 02:00:00,9162.0 -2014-12-15 03:00:00,8892.0 -2014-12-15 04:00:00,8784.0 -2014-12-15 05:00:00,8800.0 -2014-12-15 06:00:00,9160.0 -2014-12-15 07:00:00,9926.0 -2014-12-15 08:00:00,11138.0 -2014-12-15 09:00:00,11787.0 -2014-12-15 10:00:00,11933.0 -2014-12-15 11:00:00,11966.0 -2014-12-15 12:00:00,12048.0 -2014-12-15 13:00:00,12060.0 -2014-12-15 14:00:00,12047.0 -2014-12-15 15:00:00,12059.0 -2014-12-15 16:00:00,11997.0 -2014-12-15 17:00:00,12156.0 -2014-12-15 18:00:00,12910.0 -2014-12-15 19:00:00,13317.0 -2014-12-15 20:00:00,13092.0 -2014-12-15 21:00:00,12928.0 -2014-12-15 22:00:00,12598.0 -2014-12-15 23:00:00,12050.0 -2014-12-16 00:00:00,11157.0 -2014-12-14 01:00:00,9871.0 -2014-12-14 02:00:00,9309.0 -2014-12-14 03:00:00,8941.0 -2014-12-14 04:00:00,8679.0 -2014-12-14 05:00:00,8546.0 -2014-12-14 06:00:00,8548.0 -2014-12-14 07:00:00,8662.0 -2014-12-14 08:00:00,8966.0 -2014-12-14 09:00:00,9147.0 -2014-12-14 10:00:00,9361.0 -2014-12-14 11:00:00,9640.0 -2014-12-14 12:00:00,9756.0 -2014-12-14 13:00:00,9824.0 -2014-12-14 14:00:00,9861.0 -2014-12-14 15:00:00,9878.0 -2014-12-14 16:00:00,9917.0 -2014-12-14 17:00:00,10101.0 -2014-12-14 18:00:00,10943.0 -2014-12-14 19:00:00,11478.0 -2014-12-14 20:00:00,11549.0 -2014-12-14 21:00:00,11519.0 -2014-12-14 22:00:00,11298.0 -2014-12-14 23:00:00,10940.0 -2014-12-15 00:00:00,10271.0 -2014-12-13 01:00:00,10850.0 -2014-12-13 02:00:00,10204.0 -2014-12-13 03:00:00,9774.0 -2014-12-13 04:00:00,9561.0 -2014-12-13 05:00:00,9444.0 -2014-12-13 06:00:00,9493.0 -2014-12-13 07:00:00,9785.0 -2014-12-13 08:00:00,10301.0 -2014-12-13 09:00:00,10627.0 -2014-12-13 10:00:00,10950.0 -2014-12-13 11:00:00,11142.0 -2014-12-13 12:00:00,11216.0 -2014-12-13 13:00:00,11126.0 -2014-12-13 14:00:00,11040.0 -2014-12-13 15:00:00,10850.0 -2014-12-13 16:00:00,10699.0 -2014-12-13 17:00:00,10761.0 -2014-12-13 18:00:00,11493.0 -2014-12-13 19:00:00,11930.0 -2014-12-13 20:00:00,11826.0 -2014-12-13 21:00:00,11653.0 -2014-12-13 22:00:00,11456.0 -2014-12-13 23:00:00,11104.0 -2014-12-14 00:00:00,10506.0 -2014-12-12 01:00:00,11201.0 -2014-12-12 02:00:00,10646.0 -2014-12-12 03:00:00,10351.0 -2014-12-12 04:00:00,10183.0 -2014-12-12 05:00:00,10217.0 -2014-12-12 06:00:00,10469.0 -2014-12-12 07:00:00,11195.0 -2014-12-12 08:00:00,12275.0 -2014-12-12 09:00:00,12839.0 -2014-12-12 10:00:00,12971.0 -2014-12-12 11:00:00,13012.0 -2014-12-12 12:00:00,13017.0 -2014-12-12 13:00:00,12947.0 -2014-12-12 14:00:00,12786.0 -2014-12-12 15:00:00,12696.0 -2014-12-12 16:00:00,12604.0 -2014-12-12 17:00:00,12670.0 -2014-12-12 18:00:00,13383.0 -2014-12-12 19:00:00,13693.0 -2014-12-12 20:00:00,13464.0 -2014-12-12 21:00:00,13168.0 -2014-12-12 22:00:00,12840.0 -2014-12-12 23:00:00,12428.0 -2014-12-13 00:00:00,11647.0 -2014-12-11 01:00:00,10971.0 -2014-12-11 02:00:00,10429.0 -2014-12-11 03:00:00,10125.0 -2014-12-11 04:00:00,9967.0 -2014-12-11 05:00:00,9939.0 -2014-12-11 06:00:00,10225.0 -2014-12-11 07:00:00,11011.0 -2014-12-11 08:00:00,12198.0 -2014-12-11 09:00:00,12762.0 -2014-12-11 10:00:00,12853.0 -2014-12-11 11:00:00,12845.0 -2014-12-11 12:00:00,12774.0 -2014-12-11 13:00:00,12665.0 -2014-12-11 14:00:00,12487.0 -2014-12-11 15:00:00,12421.0 -2014-12-11 16:00:00,12307.0 -2014-12-11 17:00:00,12373.0 -2014-12-11 18:00:00,13257.0 -2014-12-11 19:00:00,13900.0 -2014-12-11 20:00:00,13794.0 -2014-12-11 21:00:00,13600.0 -2014-12-11 22:00:00,13344.0 -2014-12-11 23:00:00,12872.0 -2014-12-12 00:00:00,12006.0 -2014-12-10 01:00:00,10838.0 -2014-12-10 02:00:00,10289.0 -2014-12-10 03:00:00,9969.0 -2014-12-10 04:00:00,9789.0 -2014-12-10 05:00:00,9810.0 -2014-12-10 06:00:00,10044.0 -2014-12-10 07:00:00,10811.0 -2014-12-10 08:00:00,11943.0 -2014-12-10 09:00:00,12548.0 -2014-12-10 10:00:00,12645.0 -2014-12-10 11:00:00,12740.0 -2014-12-10 12:00:00,12800.0 -2014-12-10 13:00:00,12747.0 -2014-12-10 14:00:00,12687.0 -2014-12-10 15:00:00,12695.0 -2014-12-10 16:00:00,12691.0 -2014-12-10 17:00:00,12842.0 -2014-12-10 18:00:00,13593.0 -2014-12-10 19:00:00,13963.0 -2014-12-10 20:00:00,13751.0 -2014-12-10 21:00:00,13530.0 -2014-12-10 22:00:00,13225.0 -2014-12-10 23:00:00,12705.0 -2014-12-11 00:00:00,11819.0 -2014-12-09 01:00:00,10740.0 -2014-12-09 02:00:00,10158.0 -2014-12-09 03:00:00,9819.0 -2014-12-09 04:00:00,9683.0 -2014-12-09 05:00:00,9675.0 -2014-12-09 06:00:00,9926.0 -2014-12-09 07:00:00,10651.0 -2014-12-09 08:00:00,11792.0 -2014-12-09 09:00:00,12465.0 -2014-12-09 10:00:00,12576.0 -2014-12-09 11:00:00,12591.0 -2014-12-09 12:00:00,12618.0 -2014-12-09 13:00:00,12567.0 -2014-12-09 14:00:00,12465.0 -2014-12-09 15:00:00,12505.0 -2014-12-09 16:00:00,12503.0 -2014-12-09 17:00:00,12637.0 -2014-12-09 18:00:00,13383.0 -2014-12-09 19:00:00,13747.0 -2014-12-09 20:00:00,13623.0 -2014-12-09 21:00:00,13441.0 -2014-12-09 22:00:00,13140.0 -2014-12-09 23:00:00,12573.0 -2014-12-10 00:00:00,11678.0 -2014-12-08 01:00:00,10345.0 -2014-12-08 02:00:00,9863.0 -2014-12-08 03:00:00,9641.0 -2014-12-08 04:00:00,9557.0 -2014-12-08 05:00:00,9579.0 -2014-12-08 06:00:00,9899.0 -2014-12-08 07:00:00,10734.0 -2014-12-08 08:00:00,11889.0 -2014-12-08 09:00:00,12623.0 -2014-12-08 10:00:00,12742.0 -2014-12-08 11:00:00,12859.0 -2014-12-08 12:00:00,12926.0 -2014-12-08 13:00:00,12891.0 -2014-12-08 14:00:00,12843.0 -2014-12-08 15:00:00,12809.0 -2014-12-08 16:00:00,12793.0 -2014-12-08 17:00:00,12963.0 -2014-12-08 18:00:00,13562.0 -2014-12-08 19:00:00,13812.0 -2014-12-08 20:00:00,13603.0 -2014-12-08 21:00:00,13360.0 -2014-12-08 22:00:00,13087.0 -2014-12-08 23:00:00,12522.0 -2014-12-09 00:00:00,11607.0 -2014-12-07 01:00:00,10463.0 -2014-12-07 02:00:00,9963.0 -2014-12-07 03:00:00,9581.0 -2014-12-07 04:00:00,9453.0 -2014-12-07 05:00:00,9372.0 -2014-12-07 06:00:00,9396.0 -2014-12-07 07:00:00,9604.0 -2014-12-07 08:00:00,9862.0 -2014-12-07 09:00:00,9976.0 -2014-12-07 10:00:00,10181.0 -2014-12-07 11:00:00,10432.0 -2014-12-07 12:00:00,10487.0 -2014-12-07 13:00:00,10473.0 -2014-12-07 14:00:00,10491.0 -2014-12-07 15:00:00,10507.0 -2014-12-07 16:00:00,10607.0 -2014-12-07 17:00:00,10861.0 -2014-12-07 18:00:00,11783.0 -2014-12-07 19:00:00,12348.0 -2014-12-07 20:00:00,12379.0 -2014-12-07 21:00:00,12351.0 -2014-12-07 22:00:00,12144.0 -2014-12-07 23:00:00,11733.0 -2014-12-08 00:00:00,11013.0 -2014-12-06 01:00:00,10541.0 -2014-12-06 02:00:00,9952.0 -2014-12-06 03:00:00,9587.0 -2014-12-06 04:00:00,9438.0 -2014-12-06 05:00:00,9383.0 -2014-12-06 06:00:00,9472.0 -2014-12-06 07:00:00,9783.0 -2014-12-06 08:00:00,10314.0 -2014-12-06 09:00:00,10756.0 -2014-12-06 10:00:00,11031.0 -2014-12-06 11:00:00,11251.0 -2014-12-06 12:00:00,11281.0 -2014-12-06 13:00:00,11125.0 -2014-12-06 14:00:00,10930.0 -2014-12-06 15:00:00,10719.0 -2014-12-06 16:00:00,10697.0 -2014-12-06 17:00:00,10752.0 -2014-12-06 18:00:00,11613.0 -2014-12-06 19:00:00,12233.0 -2014-12-06 20:00:00,12192.0 -2014-12-06 21:00:00,12063.0 -2014-12-06 22:00:00,11913.0 -2014-12-06 23:00:00,11619.0 -2014-12-07 00:00:00,11101.0 -2014-12-05 01:00:00,10859.0 -2014-12-05 02:00:00,10271.0 -2014-12-05 03:00:00,9934.0 -2014-12-05 04:00:00,9711.0 -2014-12-05 05:00:00,9676.0 -2014-12-05 06:00:00,9926.0 -2014-12-05 07:00:00,10619.0 -2014-12-05 08:00:00,11768.0 -2014-12-05 09:00:00,12346.0 -2014-12-05 10:00:00,12445.0 -2014-12-05 11:00:00,12579.0 -2014-12-05 12:00:00,12597.0 -2014-12-05 13:00:00,12525.0 -2014-12-05 14:00:00,12375.0 -2014-12-05 15:00:00,12218.0 -2014-12-05 16:00:00,12178.0 -2014-12-05 17:00:00,12149.0 -2014-12-05 18:00:00,12867.0 -2014-12-05 19:00:00,13207.0 -2014-12-05 20:00:00,13024.0 -2014-12-05 21:00:00,12801.0 -2014-12-05 22:00:00,12444.0 -2014-12-05 23:00:00,12077.0 -2014-12-06 00:00:00,11363.0 -2014-12-04 01:00:00,11043.0 -2014-12-04 02:00:00,10522.0 -2014-12-04 03:00:00,10226.0 -2014-12-04 04:00:00,10111.0 -2014-12-04 05:00:00,10118.0 -2014-12-04 06:00:00,10382.0 -2014-12-04 07:00:00,11142.0 -2014-12-04 08:00:00,12301.0 -2014-12-04 09:00:00,12756.0 -2014-12-04 10:00:00,12747.0 -2014-12-04 11:00:00,12617.0 -2014-12-04 12:00:00,12606.0 -2014-12-04 13:00:00,12539.0 -2014-12-04 14:00:00,12531.0 -2014-12-04 15:00:00,12612.0 -2014-12-04 16:00:00,12545.0 -2014-12-04 17:00:00,12661.0 -2014-12-04 18:00:00,13411.0 -2014-12-04 19:00:00,13770.0 -2014-12-04 20:00:00,13574.0 -2014-12-04 21:00:00,13367.0 -2014-12-04 22:00:00,13061.0 -2014-12-04 23:00:00,12504.0 -2014-12-05 00:00:00,11654.0 -2014-12-03 01:00:00,11000.0 -2014-12-03 02:00:00,10492.0 -2014-12-03 03:00:00,10205.0 -2014-12-03 04:00:00,10090.0 -2014-12-03 05:00:00,10106.0 -2014-12-03 06:00:00,10407.0 -2014-12-03 07:00:00,11168.0 -2014-12-03 08:00:00,12343.0 -2014-12-03 09:00:00,12778.0 -2014-12-03 10:00:00,12716.0 -2014-12-03 11:00:00,12670.0 -2014-12-03 12:00:00,12646.0 -2014-12-03 13:00:00,12504.0 -2014-12-03 14:00:00,12370.0 -2014-12-03 15:00:00,12323.0 -2014-12-03 16:00:00,12192.0 -2014-12-03 17:00:00,12242.0 -2014-12-03 18:00:00,13129.0 -2014-12-03 19:00:00,13763.0 -2014-12-03 20:00:00,13631.0 -2014-12-03 21:00:00,13469.0 -2014-12-03 22:00:00,13227.0 -2014-12-03 23:00:00,12657.0 -2014-12-04 00:00:00,11809.0 -2014-12-02 01:00:00,11384.0 -2014-12-02 02:00:00,10872.0 -2014-12-02 03:00:00,10546.0 -2014-12-02 04:00:00,10433.0 -2014-12-02 05:00:00,10418.0 -2014-12-02 06:00:00,10679.0 -2014-12-02 07:00:00,11449.0 -2014-12-02 08:00:00,12563.0 -2014-12-02 09:00:00,12991.0 -2014-12-02 10:00:00,12958.0 -2014-12-02 11:00:00,12836.0 -2014-12-02 12:00:00,12839.0 -2014-12-02 13:00:00,12730.0 -2014-12-02 14:00:00,12629.0 -2014-12-02 15:00:00,12660.0 -2014-12-02 16:00:00,12607.0 -2014-12-02 17:00:00,12770.0 -2014-12-02 18:00:00,13545.0 -2014-12-02 19:00:00,13958.0 -2014-12-02 20:00:00,13781.0 -2014-12-02 21:00:00,13560.0 -2014-12-02 22:00:00,13226.0 -2014-12-02 23:00:00,12704.0 -2014-12-03 00:00:00,11823.0 -2014-12-01 01:00:00,10074.0 -2014-12-01 02:00:00,9764.0 -2014-12-01 03:00:00,9646.0 -2014-12-01 04:00:00,9593.0 -2014-12-01 05:00:00,9689.0 -2014-12-01 06:00:00,10089.0 -2014-12-01 07:00:00,10905.0 -2014-12-01 08:00:00,12188.0 -2014-12-01 09:00:00,12824.0 -2014-12-01 10:00:00,12999.0 -2014-12-01 11:00:00,13032.0 -2014-12-01 12:00:00,13082.0 -2014-12-01 13:00:00,13097.0 -2014-12-01 14:00:00,13055.0 -2014-12-01 15:00:00,13168.0 -2014-12-01 16:00:00,13180.0 -2014-12-01 17:00:00,13289.0 -2014-12-01 18:00:00,14029.0 -2014-12-01 19:00:00,14494.0 -2014-12-01 20:00:00,14378.0 -2014-12-01 21:00:00,14155.0 -2014-12-01 22:00:00,13828.0 -2014-12-01 23:00:00,13199.0 -2014-12-02 00:00:00,12238.0 -2014-11-30 01:00:00,9460.0 -2014-11-30 02:00:00,8953.0 -2014-11-30 03:00:00,8641.0 -2014-11-30 04:00:00,8407.0 -2014-11-30 05:00:00,8310.0 -2014-11-30 06:00:00,8303.0 -2014-11-30 07:00:00,8508.0 -2014-11-30 08:00:00,8725.0 -2014-11-30 09:00:00,8725.0 -2014-11-30 10:00:00,8943.0 -2014-11-30 11:00:00,9188.0 -2014-11-30 12:00:00,9383.0 -2014-11-30 13:00:00,9544.0 -2014-11-30 14:00:00,9793.0 -2014-11-30 15:00:00,10044.0 -2014-11-30 16:00:00,10280.0 -2014-11-30 17:00:00,10570.0 -2014-11-30 18:00:00,11324.0 -2014-11-30 19:00:00,11788.0 -2014-11-30 20:00:00,11835.0 -2014-11-30 21:00:00,11737.0 -2014-11-30 22:00:00,11523.0 -2014-11-30 23:00:00,11176.0 -2014-12-01 00:00:00,10582.0 -2014-11-29 01:00:00,10104.0 -2014-11-29 02:00:00,9661.0 -2014-11-29 03:00:00,9382.0 -2014-11-29 04:00:00,9205.0 -2014-11-29 05:00:00,9123.0 -2014-11-29 06:00:00,9180.0 -2014-11-29 07:00:00,9425.0 -2014-11-29 08:00:00,9819.0 -2014-11-29 09:00:00,9964.0 -2014-11-29 10:00:00,10137.0 -2014-11-29 11:00:00,10259.0 -2014-11-29 12:00:00,10258.0 -2014-11-29 13:00:00,10210.0 -2014-11-29 14:00:00,10083.0 -2014-11-29 15:00:00,9868.0 -2014-11-29 16:00:00,9781.0 -2014-11-29 17:00:00,9896.0 -2014-11-29 18:00:00,10664.0 -2014-11-29 19:00:00,11211.0 -2014-11-29 20:00:00,11117.0 -2014-11-29 21:00:00,10965.0 -2014-11-29 22:00:00,10768.0 -2014-11-29 23:00:00,10486.0 -2014-11-30 00:00:00,10018.0 -2014-11-28 01:00:00,10309.0 -2014-11-28 02:00:00,10002.0 -2014-11-28 03:00:00,9778.0 -2014-11-28 04:00:00,9653.0 -2014-11-28 05:00:00,9653.0 -2014-11-28 06:00:00,9804.0 -2014-11-28 07:00:00,10215.0 -2014-11-28 08:00:00,10757.0 -2014-11-28 09:00:00,10984.0 -2014-11-28 10:00:00,11167.0 -2014-11-28 11:00:00,11373.0 -2014-11-28 12:00:00,11390.0 -2014-11-28 13:00:00,11290.0 -2014-11-28 14:00:00,11323.0 -2014-11-28 15:00:00,11240.0 -2014-11-28 16:00:00,11199.0 -2014-11-28 17:00:00,11347.0 -2014-11-28 18:00:00,12007.0 -2014-11-28 19:00:00,12366.0 -2014-11-28 20:00:00,12111.0 -2014-11-28 21:00:00,11884.0 -2014-11-28 22:00:00,11595.0 -2014-11-28 23:00:00,11266.0 -2014-11-29 00:00:00,10710.0 -2014-11-27 01:00:00,10691.0 -2014-11-27 02:00:00,10138.0 -2014-11-27 03:00:00,9845.0 -2014-11-27 04:00:00,9680.0 -2014-11-27 05:00:00,9579.0 -2014-11-27 06:00:00,9615.0 -2014-11-27 07:00:00,9789.0 -2014-11-27 08:00:00,10029.0 -2014-11-27 09:00:00,10059.0 -2014-11-27 10:00:00,10271.0 -2014-11-27 11:00:00,10435.0 -2014-11-27 12:00:00,10640.0 -2014-11-27 13:00:00,10747.0 -2014-11-27 14:00:00,10696.0 -2014-11-27 15:00:00,10623.0 -2014-11-27 16:00:00,10510.0 -2014-11-27 17:00:00,10476.0 -2014-11-27 18:00:00,10913.0 -2014-11-27 19:00:00,11178.0 -2014-11-27 20:00:00,11120.0 -2014-11-27 21:00:00,11078.0 -2014-11-27 22:00:00,11057.0 -2014-11-27 23:00:00,10972.0 -2014-11-28 00:00:00,10681.0 -2014-11-26 01:00:00,11240.0 -2014-11-26 02:00:00,10720.0 -2014-11-26 03:00:00,10449.0 -2014-11-26 04:00:00,10285.0 -2014-11-26 05:00:00,10285.0 -2014-11-26 06:00:00,10523.0 -2014-11-26 07:00:00,11164.0 -2014-11-26 08:00:00,12046.0 -2014-11-26 09:00:00,12480.0 -2014-11-26 10:00:00,12782.0 -2014-11-26 11:00:00,12910.0 -2014-11-26 12:00:00,13009.0 -2014-11-26 13:00:00,12937.0 -2014-11-26 14:00:00,12857.0 -2014-11-26 15:00:00,12831.0 -2014-11-26 16:00:00,12749.0 -2014-11-26 17:00:00,12680.0 -2014-11-26 18:00:00,13191.0 -2014-11-26 19:00:00,13455.0 -2014-11-26 20:00:00,13179.0 -2014-11-26 21:00:00,12904.0 -2014-11-26 22:00:00,12567.0 -2014-11-26 23:00:00,12120.0 -2014-11-27 00:00:00,11416.0 -2014-11-25 01:00:00,10967.0 -2014-11-25 02:00:00,10474.0 -2014-11-25 03:00:00,10204.0 -2014-11-25 04:00:00,10071.0 -2014-11-25 05:00:00,10093.0 -2014-11-25 06:00:00,10380.0 -2014-11-25 07:00:00,11092.0 -2014-11-25 08:00:00,12174.0 -2014-11-25 09:00:00,12641.0 -2014-11-25 10:00:00,12841.0 -2014-11-25 11:00:00,12807.0 -2014-11-25 12:00:00,12701.0 -2014-11-25 13:00:00,12590.0 -2014-11-25 14:00:00,12486.0 -2014-11-25 15:00:00,12487.0 -2014-11-25 16:00:00,12467.0 -2014-11-25 17:00:00,12569.0 -2014-11-25 18:00:00,13241.0 -2014-11-25 19:00:00,13790.0 -2014-11-25 20:00:00,13655.0 -2014-11-25 21:00:00,13449.0 -2014-11-25 22:00:00,13179.0 -2014-11-25 23:00:00,12683.0 -2014-11-26 00:00:00,11947.0 -2014-11-24 01:00:00,9230.0 -2014-11-24 02:00:00,8822.0 -2014-11-24 03:00:00,8645.0 -2014-11-24 04:00:00,8512.0 -2014-11-24 05:00:00,8581.0 -2014-11-24 06:00:00,8838.0 -2014-11-24 07:00:00,9618.0 -2014-11-24 08:00:00,10800.0 -2014-11-24 09:00:00,11578.0 -2014-11-24 10:00:00,11837.0 -2014-11-24 11:00:00,12095.0 -2014-11-24 12:00:00,12435.0 -2014-11-24 13:00:00,12633.0 -2014-11-24 14:00:00,12633.0 -2014-11-24 15:00:00,12685.0 -2014-11-24 16:00:00,12767.0 -2014-11-24 17:00:00,12856.0 -2014-11-24 18:00:00,13485.0 -2014-11-24 19:00:00,13818.0 -2014-11-24 20:00:00,13650.0 -2014-11-24 21:00:00,13446.0 -2014-11-24 22:00:00,13124.0 -2014-11-24 23:00:00,12597.0 -2014-11-25 00:00:00,11773.0 -2014-11-23 01:00:00,9513.0 -2014-11-23 02:00:00,9078.0 -2014-11-23 03:00:00,8748.0 -2014-11-23 04:00:00,8560.0 -2014-11-23 05:00:00,8442.0 -2014-11-23 06:00:00,8461.0 -2014-11-23 07:00:00,8591.0 -2014-11-23 08:00:00,8808.0 -2014-11-23 09:00:00,8961.0 -2014-11-23 10:00:00,9306.0 -2014-11-23 11:00:00,9558.0 -2014-11-23 12:00:00,9692.0 -2014-11-23 13:00:00,9753.0 -2014-11-23 14:00:00,9846.0 -2014-11-23 15:00:00,9899.0 -2014-11-23 16:00:00,10011.0 -2014-11-23 17:00:00,10231.0 -2014-11-23 18:00:00,10888.0 -2014-11-23 19:00:00,11174.0 -2014-11-23 20:00:00,11068.0 -2014-11-23 21:00:00,10961.0 -2014-11-23 22:00:00,10690.0 -2014-11-23 23:00:00,10366.0 -2014-11-24 00:00:00,9756.0 -2014-11-22 01:00:00,10973.0 -2014-11-22 02:00:00,10412.0 -2014-11-22 03:00:00,10131.0 -2014-11-22 04:00:00,9911.0 -2014-11-22 05:00:00,9845.0 -2014-11-22 06:00:00,9910.0 -2014-11-22 07:00:00,10161.0 -2014-11-22 08:00:00,10566.0 -2014-11-22 09:00:00,10851.0 -2014-11-22 10:00:00,11163.0 -2014-11-22 11:00:00,11389.0 -2014-11-22 12:00:00,11470.0 -2014-11-22 13:00:00,11408.0 -2014-11-22 14:00:00,11246.0 -2014-11-22 15:00:00,10954.0 -2014-11-22 16:00:00,10831.0 -2014-11-22 17:00:00,10863.0 -2014-11-22 18:00:00,11259.0 -2014-11-22 19:00:00,11554.0 -2014-11-22 20:00:00,11424.0 -2014-11-22 21:00:00,11202.0 -2014-11-22 22:00:00,10967.0 -2014-11-22 23:00:00,10607.0 -2014-11-23 00:00:00,10069.0 -2014-11-21 01:00:00,11469.0 -2014-11-21 02:00:00,11073.0 -2014-11-21 03:00:00,10819.0 -2014-11-21 04:00:00,10711.0 -2014-11-21 05:00:00,10755.0 -2014-11-21 06:00:00,11055.0 -2014-11-21 07:00:00,11818.0 -2014-11-21 08:00:00,12856.0 -2014-11-21 09:00:00,13116.0 -2014-11-21 10:00:00,13135.0 -2014-11-21 11:00:00,13009.0 -2014-11-21 12:00:00,12947.0 -2014-11-21 13:00:00,12790.0 -2014-11-21 14:00:00,12670.0 -2014-11-21 15:00:00,12518.0 -2014-11-21 16:00:00,12359.0 -2014-11-21 17:00:00,12335.0 -2014-11-21 18:00:00,13006.0 -2014-11-21 19:00:00,13525.0 -2014-11-21 20:00:00,13402.0 -2014-11-21 21:00:00,13149.0 -2014-11-21 22:00:00,12770.0 -2014-11-21 23:00:00,12378.0 -2014-11-22 00:00:00,11655.0 -2014-11-20 01:00:00,11345.0 -2014-11-20 02:00:00,10942.0 -2014-11-20 03:00:00,10700.0 -2014-11-20 04:00:00,10614.0 -2014-11-20 05:00:00,10628.0 -2014-11-20 06:00:00,10941.0 -2014-11-20 07:00:00,11664.0 -2014-11-20 08:00:00,12713.0 -2014-11-20 09:00:00,13019.0 -2014-11-20 10:00:00,13025.0 -2014-11-20 11:00:00,12999.0 -2014-11-20 12:00:00,13001.0 -2014-11-20 13:00:00,12933.0 -2014-11-20 14:00:00,12826.0 -2014-11-20 15:00:00,12793.0 -2014-11-20 16:00:00,12698.0 -2014-11-20 17:00:00,12739.0 -2014-11-20 18:00:00,13383.0 -2014-11-20 19:00:00,13997.0 -2014-11-20 20:00:00,13876.0 -2014-11-20 21:00:00,13695.0 -2014-11-20 22:00:00,13425.0 -2014-11-20 23:00:00,12946.0 -2014-11-21 00:00:00,12168.0 -2014-11-19 01:00:00,11668.0 -2014-11-19 02:00:00,11225.0 -2014-11-19 03:00:00,10974.0 -2014-11-19 04:00:00,10818.0 -2014-11-19 05:00:00,10808.0 -2014-11-19 06:00:00,11025.0 -2014-11-19 07:00:00,11706.0 -2014-11-19 08:00:00,12744.0 -2014-11-19 09:00:00,13146.0 -2014-11-19 10:00:00,13196.0 -2014-11-19 11:00:00,13080.0 -2014-11-19 12:00:00,13068.0 -2014-11-19 13:00:00,13111.0 -2014-11-19 14:00:00,13155.0 -2014-11-19 15:00:00,13314.0 -2014-11-19 16:00:00,13346.0 -2014-11-19 17:00:00,13384.0 -2014-11-19 18:00:00,13969.0 -2014-11-19 19:00:00,14310.0 -2014-11-19 20:00:00,14087.0 -2014-11-19 21:00:00,13855.0 -2014-11-19 22:00:00,13503.0 -2014-11-19 23:00:00,12856.0 -2014-11-20 00:00:00,12089.0 -2014-11-18 01:00:00,11859.0 -2014-11-18 02:00:00,11458.0 -2014-11-18 03:00:00,11205.0 -2014-11-18 04:00:00,11102.0 -2014-11-18 05:00:00,11115.0 -2014-11-18 06:00:00,11363.0 -2014-11-18 07:00:00,12098.0 -2014-11-18 08:00:00,13146.0 -2014-11-18 09:00:00,13527.0 -2014-11-18 10:00:00,13590.0 -2014-11-18 11:00:00,13621.0 -2014-11-18 12:00:00,13687.0 -2014-11-18 13:00:00,13654.0 -2014-11-18 14:00:00,13615.0 -2014-11-18 15:00:00,13633.0 -2014-11-18 16:00:00,13537.0 -2014-11-18 17:00:00,13699.0 -2014-11-18 18:00:00,14299.0 -2014-11-18 19:00:00,14653.0 -2014-11-18 20:00:00,14441.0 -2014-11-18 21:00:00,14204.0 -2014-11-18 22:00:00,13848.0 -2014-11-18 23:00:00,13246.0 -2014-11-19 00:00:00,12418.0 -2014-11-17 01:00:00,10263.0 -2014-11-17 02:00:00,10002.0 -2014-11-17 03:00:00,9922.0 -2014-11-17 04:00:00,9880.0 -2014-11-17 05:00:00,10080.0 -2014-11-17 06:00:00,10431.0 -2014-11-17 07:00:00,11302.0 -2014-11-17 08:00:00,12420.0 -2014-11-17 09:00:00,12958.0 -2014-11-17 10:00:00,13041.0 -2014-11-17 11:00:00,13071.0 -2014-11-17 12:00:00,13064.0 -2014-11-17 13:00:00,13125.0 -2014-11-17 14:00:00,13235.0 -2014-11-17 15:00:00,13399.0 -2014-11-17 16:00:00,13419.0 -2014-11-17 17:00:00,13548.0 -2014-11-17 18:00:00,14166.0 -2014-11-17 19:00:00,14623.0 -2014-11-17 20:00:00,14507.0 -2014-11-17 21:00:00,14339.0 -2014-11-17 22:00:00,14006.0 -2014-11-17 23:00:00,13429.0 -2014-11-18 00:00:00,12606.0 -2014-11-16 01:00:00,10153.0 -2014-11-16 02:00:00,9749.0 -2014-11-16 03:00:00,9497.0 -2014-11-16 04:00:00,9322.0 -2014-11-16 05:00:00,9275.0 -2014-11-16 06:00:00,9320.0 -2014-11-16 07:00:00,9467.0 -2014-11-16 08:00:00,9709.0 -2014-11-16 09:00:00,9767.0 -2014-11-16 10:00:00,10139.0 -2014-11-16 11:00:00,10408.0 -2014-11-16 12:00:00,10563.0 -2014-11-16 13:00:00,10645.0 -2014-11-16 14:00:00,10673.0 -2014-11-16 15:00:00,10714.0 -2014-11-16 16:00:00,10801.0 -2014-11-16 17:00:00,10985.0 -2014-11-16 18:00:00,11523.0 -2014-11-16 19:00:00,11938.0 -2014-11-16 20:00:00,11866.0 -2014-11-16 21:00:00,11782.0 -2014-11-16 22:00:00,11539.0 -2014-11-16 23:00:00,11223.0 -2014-11-17 00:00:00,10669.0 -2014-11-15 01:00:00,10650.0 -2014-11-15 02:00:00,10243.0 -2014-11-15 03:00:00,10036.0 -2014-11-15 04:00:00,9834.0 -2014-11-15 05:00:00,9794.0 -2014-11-15 06:00:00,9914.0 -2014-11-15 07:00:00,10255.0 -2014-11-15 08:00:00,10662.0 -2014-11-15 09:00:00,10860.0 -2014-11-15 10:00:00,11029.0 -2014-11-15 11:00:00,11125.0 -2014-11-15 12:00:00,11148.0 -2014-11-15 13:00:00,11128.0 -2014-11-15 14:00:00,10951.0 -2014-11-15 15:00:00,10847.0 -2014-11-15 16:00:00,10792.0 -2014-11-15 17:00:00,10981.0 -2014-11-15 18:00:00,11581.0 -2014-11-15 19:00:00,11975.0 -2014-11-15 20:00:00,11857.0 -2014-11-15 21:00:00,11779.0 -2014-11-15 22:00:00,11541.0 -2014-11-15 23:00:00,11228.0 -2014-11-16 00:00:00,10689.0 -2014-11-14 01:00:00,10712.0 -2014-11-14 02:00:00,10238.0 -2014-11-14 03:00:00,9953.0 -2014-11-14 04:00:00,9845.0 -2014-11-14 05:00:00,9814.0 -2014-11-14 06:00:00,10094.0 -2014-11-14 07:00:00,10838.0 -2014-11-14 08:00:00,11843.0 -2014-11-14 09:00:00,12284.0 -2014-11-14 10:00:00,12457.0 -2014-11-14 11:00:00,12449.0 -2014-11-14 12:00:00,12490.0 -2014-11-14 13:00:00,12551.0 -2014-11-14 14:00:00,12510.0 -2014-11-14 15:00:00,12519.0 -2014-11-14 16:00:00,12454.0 -2014-11-14 17:00:00,12440.0 -2014-11-14 18:00:00,12929.0 -2014-11-14 19:00:00,13257.0 -2014-11-14 20:00:00,13053.0 -2014-11-14 21:00:00,12780.0 -2014-11-14 22:00:00,12479.0 -2014-11-14 23:00:00,12050.0 -2014-11-15 00:00:00,11298.0 -2014-11-13 01:00:00,10551.0 -2014-11-13 02:00:00,10117.0 -2014-11-13 03:00:00,9838.0 -2014-11-13 04:00:00,9706.0 -2014-11-13 05:00:00,9696.0 -2014-11-13 06:00:00,9987.0 -2014-11-13 07:00:00,10775.0 -2014-11-13 08:00:00,11881.0 -2014-11-13 09:00:00,12287.0 -2014-11-13 10:00:00,12423.0 -2014-11-13 11:00:00,12498.0 -2014-11-13 12:00:00,12630.0 -2014-11-13 13:00:00,12694.0 -2014-11-13 14:00:00,12655.0 -2014-11-13 15:00:00,12664.0 -2014-11-13 16:00:00,12656.0 -2014-11-13 17:00:00,12602.0 -2014-11-13 18:00:00,13062.0 -2014-11-13 19:00:00,13464.0 -2014-11-13 20:00:00,13317.0 -2014-11-13 21:00:00,13087.0 -2014-11-13 22:00:00,12736.0 -2014-11-13 23:00:00,12184.0 -2014-11-14 00:00:00,11408.0 -2014-11-12 01:00:00,10207.0 -2014-11-12 02:00:00,9685.0 -2014-11-12 03:00:00,9439.0 -2014-11-12 04:00:00,9309.0 -2014-11-12 05:00:00,9335.0 -2014-11-12 06:00:00,9643.0 -2014-11-12 07:00:00,10396.0 -2014-11-12 08:00:00,11476.0 -2014-11-12 09:00:00,11860.0 -2014-11-12 10:00:00,12006.0 -2014-11-12 11:00:00,12216.0 -2014-11-12 12:00:00,12387.0 -2014-11-12 13:00:00,12316.0 -2014-11-12 14:00:00,12126.0 -2014-11-12 15:00:00,12188.0 -2014-11-12 16:00:00,12190.0 -2014-11-12 17:00:00,12335.0 -2014-11-12 18:00:00,12860.0 -2014-11-12 19:00:00,13380.0 -2014-11-12 20:00:00,13181.0 -2014-11-12 21:00:00,12936.0 -2014-11-12 22:00:00,12626.0 -2014-11-12 23:00:00,12014.0 -2014-11-13 00:00:00,11292.0 -2014-11-11 01:00:00,9376.0 -2014-11-11 02:00:00,8891.0 -2014-11-11 03:00:00,8578.0 -2014-11-11 04:00:00,8353.0 -2014-11-11 05:00:00,8342.0 -2014-11-11 06:00:00,8558.0 -2014-11-11 07:00:00,9213.0 -2014-11-11 08:00:00,10235.0 -2014-11-11 09:00:00,10854.0 -2014-11-11 10:00:00,11327.0 -2014-11-11 11:00:00,11709.0 -2014-11-11 12:00:00,11807.0 -2014-11-11 13:00:00,11808.0 -2014-11-11 14:00:00,11790.0 -2014-11-11 15:00:00,11847.0 -2014-11-11 16:00:00,11831.0 -2014-11-11 17:00:00,11936.0 -2014-11-11 18:00:00,12417.0 -2014-11-11 19:00:00,12810.0 -2014-11-11 20:00:00,12633.0 -2014-11-11 21:00:00,12428.0 -2014-11-11 22:00:00,12109.0 -2014-11-11 23:00:00,11563.0 -2014-11-12 00:00:00,10859.0 -2014-11-10 01:00:00,8898.0 -2014-11-10 02:00:00,8568.0 -2014-11-10 03:00:00,8359.0 -2014-11-10 04:00:00,8297.0 -2014-11-10 05:00:00,8303.0 -2014-11-10 06:00:00,8676.0 -2014-11-10 07:00:00,9482.0 -2014-11-10 08:00:00,10611.0 -2014-11-10 09:00:00,11098.0 -2014-11-10 10:00:00,11366.0 -2014-11-10 11:00:00,11441.0 -2014-11-10 12:00:00,11425.0 -2014-11-10 13:00:00,11358.0 -2014-11-10 14:00:00,11324.0 -2014-11-10 15:00:00,11305.0 -2014-11-10 16:00:00,11208.0 -2014-11-10 17:00:00,11121.0 -2014-11-10 18:00:00,11482.0 -2014-11-10 19:00:00,12159.0 -2014-11-10 20:00:00,11991.0 -2014-11-10 21:00:00,11728.0 -2014-11-10 22:00:00,11392.0 -2014-11-10 23:00:00,10814.0 -2014-11-11 00:00:00,10095.0 -2014-11-09 01:00:00,9279.0 -2014-11-09 02:00:00,8886.0 -2014-11-09 03:00:00,8627.0 -2014-11-09 04:00:00,8478.0 -2014-11-09 05:00:00,8465.0 -2014-11-09 06:00:00,8461.0 -2014-11-09 07:00:00,8662.0 -2014-11-09 08:00:00,8832.0 -2014-11-09 09:00:00,8943.0 -2014-11-09 10:00:00,9248.0 -2014-11-09 11:00:00,9504.0 -2014-11-09 12:00:00,9507.0 -2014-11-09 13:00:00,9538.0 -2014-11-09 14:00:00,9452.0 -2014-11-09 15:00:00,9414.0 -2014-11-09 16:00:00,9347.0 -2014-11-09 17:00:00,9349.0 -2014-11-09 18:00:00,10018.0 -2014-11-09 19:00:00,10717.0 -2014-11-09 20:00:00,10698.0 -2014-11-09 21:00:00,10539.0 -2014-11-09 22:00:00,10310.0 -2014-11-09 23:00:00,9914.0 -2014-11-10 00:00:00,9410.0 -2014-11-08 01:00:00,9790.0 -2014-11-08 02:00:00,9296.0 -2014-11-08 03:00:00,8978.0 -2014-11-08 04:00:00,8764.0 -2014-11-08 05:00:00,8679.0 -2014-11-08 06:00:00,8741.0 -2014-11-08 07:00:00,9044.0 -2014-11-08 08:00:00,9521.0 -2014-11-08 09:00:00,9873.0 -2014-11-08 10:00:00,10270.0 -2014-11-08 11:00:00,10455.0 -2014-11-08 12:00:00,10507.0 -2014-11-08 13:00:00,10462.0 -2014-11-08 14:00:00,10340.0 -2014-11-08 15:00:00,10122.0 -2014-11-08 16:00:00,9971.0 -2014-11-08 17:00:00,9875.0 -2014-11-08 18:00:00,10411.0 -2014-11-08 19:00:00,10997.0 -2014-11-08 20:00:00,10942.0 -2014-11-08 21:00:00,10700.0 -2014-11-08 22:00:00,10563.0 -2014-11-08 23:00:00,10252.0 -2014-11-09 00:00:00,9780.0 -2014-11-07 01:00:00,9717.0 -2014-11-07 02:00:00,9313.0 -2014-11-07 03:00:00,9069.0 -2014-11-07 04:00:00,8919.0 -2014-11-07 05:00:00,8922.0 -2014-11-07 06:00:00,9199.0 -2014-11-07 07:00:00,9956.0 -2014-11-07 08:00:00,10942.0 -2014-11-07 09:00:00,11348.0 -2014-11-07 10:00:00,11475.0 -2014-11-07 11:00:00,11473.0 -2014-11-07 12:00:00,11510.0 -2014-11-07 13:00:00,11417.0 -2014-11-07 14:00:00,11320.0 -2014-11-07 15:00:00,11328.0 -2014-11-07 16:00:00,11314.0 -2014-11-07 17:00:00,11324.0 -2014-11-07 18:00:00,11754.0 -2014-11-07 19:00:00,12263.0 -2014-11-07 20:00:00,12080.0 -2014-11-07 21:00:00,11849.0 -2014-11-07 22:00:00,11513.0 -2014-11-07 23:00:00,11083.0 -2014-11-08 00:00:00,10407.0 -2014-11-06 01:00:00,9376.0 -2014-11-06 02:00:00,8910.0 -2014-11-06 03:00:00,8631.0 -2014-11-06 04:00:00,8510.0 -2014-11-06 05:00:00,8481.0 -2014-11-06 06:00:00,8706.0 -2014-11-06 07:00:00,9464.0 -2014-11-06 08:00:00,10582.0 -2014-11-06 09:00:00,11179.0 -2014-11-06 10:00:00,11513.0 -2014-11-06 11:00:00,11685.0 -2014-11-06 12:00:00,11772.0 -2014-11-06 13:00:00,11752.0 -2014-11-06 14:00:00,11742.0 -2014-11-06 15:00:00,11830.0 -2014-11-06 16:00:00,11806.0 -2014-11-06 17:00:00,11886.0 -2014-11-06 18:00:00,12280.0 -2014-11-06 19:00:00,12639.0 -2014-11-06 20:00:00,12399.0 -2014-11-06 21:00:00,12154.0 -2014-11-06 22:00:00,11776.0 -2014-11-06 23:00:00,11207.0 -2014-11-07 00:00:00,10438.0 -2014-11-05 01:00:00,9517.0 -2014-11-05 02:00:00,9075.0 -2014-11-05 03:00:00,8836.0 -2014-11-05 04:00:00,8662.0 -2014-11-05 05:00:00,8653.0 -2014-11-05 06:00:00,8918.0 -2014-11-05 07:00:00,9707.0 -2014-11-05 08:00:00,10734.0 -2014-11-05 09:00:00,11117.0 -2014-11-05 10:00:00,11217.0 -2014-11-05 11:00:00,11244.0 -2014-11-05 12:00:00,11306.0 -2014-11-05 13:00:00,11287.0 -2014-11-05 14:00:00,11248.0 -2014-11-05 15:00:00,11272.0 -2014-11-05 16:00:00,11203.0 -2014-11-05 17:00:00,11179.0 -2014-11-05 18:00:00,11684.0 -2014-11-05 19:00:00,12177.0 -2014-11-05 20:00:00,11970.0 -2014-11-05 21:00:00,11732.0 -2014-11-05 22:00:00,11359.0 -2014-11-05 23:00:00,10799.0 -2014-11-06 00:00:00,10072.0 -2014-11-04 01:00:00,9324.0 -2014-11-04 02:00:00,8867.0 -2014-11-04 03:00:00,8564.0 -2014-11-04 04:00:00,8370.0 -2014-11-04 05:00:00,8407.0 -2014-11-04 06:00:00,8647.0 -2014-11-04 07:00:00,9362.0 -2014-11-04 08:00:00,10414.0 -2014-11-04 09:00:00,10966.0 -2014-11-04 10:00:00,11295.0 -2014-11-04 11:00:00,11384.0 -2014-11-04 12:00:00,11448.0 -2014-11-04 13:00:00,11383.0 -2014-11-04 14:00:00,11362.0 -2014-11-04 15:00:00,11351.0 -2014-11-04 16:00:00,11272.0 -2014-11-04 17:00:00,11272.0 -2014-11-04 18:00:00,11628.0 -2014-11-04 19:00:00,12209.0 -2014-11-04 20:00:00,12098.0 -2014-11-04 21:00:00,11883.0 -2014-11-04 22:00:00,11520.0 -2014-11-04 23:00:00,10952.0 -2014-11-05 00:00:00,10176.0 -2014-11-03 01:00:00,9009.0 -2014-11-03 02:00:00,8707.0 -2014-11-03 03:00:00,8510.0 -2014-11-03 04:00:00,8448.0 -2014-11-03 05:00:00,8560.0 -2014-11-03 06:00:00,8865.0 -2014-11-03 07:00:00,9760.0 -2014-11-03 08:00:00,10725.0 -2014-11-03 09:00:00,11259.0 -2014-11-03 10:00:00,11415.0 -2014-11-03 11:00:00,11466.0 -2014-11-03 12:00:00,11492.0 -2014-11-03 13:00:00,11478.0 -2014-11-03 14:00:00,11377.0 -2014-11-03 15:00:00,11374.0 -2014-11-03 16:00:00,11298.0 -2014-11-03 17:00:00,11323.0 -2014-11-03 18:00:00,11616.0 -2014-11-03 19:00:00,12221.0 -2014-11-03 20:00:00,12050.0 -2014-11-03 21:00:00,11812.0 -2014-11-03 22:00:00,11443.0 -2014-11-03 23:00:00,10824.0 -2014-11-04 00:00:00,10018.0 -2014-11-02 01:00:00,9573.0 -2014-11-02 02:00:00,8869.0 -2014-11-02 02:00:00,9184.0 -2014-11-02 03:00:00,8788.0 -2014-11-02 04:00:00,8678.0 -2014-11-02 05:00:00,8693.0 -2014-11-02 06:00:00,8725.0 -2014-11-02 07:00:00,8995.0 -2014-11-02 08:00:00,9110.0 -2014-11-02 09:00:00,9237.0 -2014-11-02 10:00:00,9445.0 -2014-11-02 11:00:00,9536.0 -2014-11-02 12:00:00,9501.0 -2014-11-02 13:00:00,9465.0 -2014-11-02 14:00:00,9417.0 -2014-11-02 15:00:00,9392.0 -2014-11-02 16:00:00,9365.0 -2014-11-02 17:00:00,9496.0 -2014-11-02 18:00:00,9927.0 -2014-11-02 19:00:00,10831.0 -2014-11-02 20:00:00,10868.0 -2014-11-02 21:00:00,10725.0 -2014-11-02 22:00:00,10441.0 -2014-11-02 23:00:00,10064.0 -2014-11-03 00:00:00,9472.0 -2014-11-01 01:00:00,9970.0 -2014-11-01 02:00:00,9413.0 -2014-11-01 03:00:00,9114.0 -2014-11-01 04:00:00,8907.0 -2014-11-01 05:00:00,8821.0 -2014-11-01 06:00:00,8852.0 -2014-11-01 07:00:00,9212.0 -2014-11-01 08:00:00,9694.0 -2014-11-01 09:00:00,10105.0 -2014-11-01 10:00:00,10312.0 -2014-11-01 11:00:00,10594.0 -2014-11-01 12:00:00,10614.0 -2014-11-01 13:00:00,10536.0 -2014-11-01 14:00:00,10349.0 -2014-11-01 15:00:00,10069.0 -2014-11-01 16:00:00,9878.0 -2014-11-01 17:00:00,9736.0 -2014-11-01 18:00:00,9767.0 -2014-11-01 19:00:00,10106.0 -2014-11-01 20:00:00,10900.0 -2014-11-01 21:00:00,10900.0 -2014-11-01 22:00:00,10797.0 -2014-11-01 23:00:00,10522.0 -2014-11-02 00:00:00,10105.0 -2014-10-31 01:00:00,9627.0 -2014-10-31 02:00:00,9049.0 -2014-10-31 03:00:00,8739.0 -2014-10-31 04:00:00,8607.0 -2014-10-31 05:00:00,8610.0 -2014-10-31 06:00:00,8831.0 -2014-10-31 07:00:00,9579.0 -2014-10-31 08:00:00,10826.0 -2014-10-31 09:00:00,11629.0 -2014-10-31 10:00:00,11717.0 -2014-10-31 11:00:00,11820.0 -2014-10-31 12:00:00,11928.0 -2014-10-31 13:00:00,11964.0 -2014-10-31 14:00:00,11892.0 -2014-10-31 15:00:00,11779.0 -2014-10-31 16:00:00,11672.0 -2014-10-31 17:00:00,11598.0 -2014-10-31 18:00:00,11603.0 -2014-10-31 19:00:00,11801.0 -2014-10-31 20:00:00,12211.0 -2014-10-31 21:00:00,12055.0 -2014-10-31 22:00:00,11735.0 -2014-10-31 23:00:00,11311.0 -2014-11-01 00:00:00,10676.0 -2014-10-30 01:00:00,9566.0 -2014-10-30 02:00:00,9099.0 -2014-10-30 03:00:00,8783.0 -2014-10-30 04:00:00,8659.0 -2014-10-30 05:00:00,8644.0 -2014-10-30 06:00:00,8859.0 -2014-10-30 07:00:00,9579.0 -2014-10-30 08:00:00,10823.0 -2014-10-30 09:00:00,11430.0 -2014-10-30 10:00:00,11400.0 -2014-10-30 11:00:00,11404.0 -2014-10-30 12:00:00,11392.0 -2014-10-30 13:00:00,11310.0 -2014-10-30 14:00:00,11275.0 -2014-10-30 15:00:00,11335.0 -2014-10-30 16:00:00,11210.0 -2014-10-30 17:00:00,11149.0 -2014-10-30 18:00:00,11201.0 -2014-10-30 19:00:00,11524.0 -2014-10-30 20:00:00,11968.0 -2014-10-30 21:00:00,11857.0 -2014-10-30 22:00:00,11586.0 -2014-10-30 23:00:00,11046.0 -2014-10-31 00:00:00,10297.0 -2014-10-29 01:00:00,9213.0 -2014-10-29 02:00:00,8728.0 -2014-10-29 03:00:00,8436.0 -2014-10-29 04:00:00,8319.0 -2014-10-29 05:00:00,8256.0 -2014-10-29 06:00:00,8489.0 -2014-10-29 07:00:00,9207.0 -2014-10-29 08:00:00,10403.0 -2014-10-29 09:00:00,11172.0 -2014-10-29 10:00:00,11254.0 -2014-10-29 11:00:00,11360.0 -2014-10-29 12:00:00,11462.0 -2014-10-29 13:00:00,11422.0 -2014-10-29 14:00:00,11365.0 -2014-10-29 15:00:00,11401.0 -2014-10-29 16:00:00,11279.0 -2014-10-29 17:00:00,11137.0 -2014-10-29 18:00:00,11090.0 -2014-10-29 19:00:00,11315.0 -2014-10-29 20:00:00,11871.0 -2014-10-29 21:00:00,11815.0 -2014-10-29 22:00:00,11547.0 -2014-10-29 23:00:00,11039.0 -2014-10-30 00:00:00,10304.0 -2014-10-28 01:00:00,9439.0 -2014-10-28 02:00:00,8887.0 -2014-10-28 03:00:00,8522.0 -2014-10-28 04:00:00,8315.0 -2014-10-28 05:00:00,8231.0 -2014-10-28 06:00:00,8349.0 -2014-10-28 07:00:00,8968.0 -2014-10-28 08:00:00,10073.0 -2014-10-28 09:00:00,10858.0 -2014-10-28 10:00:00,10975.0 -2014-10-28 11:00:00,11081.0 -2014-10-28 12:00:00,11194.0 -2014-10-28 13:00:00,11245.0 -2014-10-28 14:00:00,11216.0 -2014-10-28 15:00:00,11228.0 -2014-10-28 16:00:00,11134.0 -2014-10-28 17:00:00,11025.0 -2014-10-28 18:00:00,10947.0 -2014-10-28 19:00:00,11068.0 -2014-10-28 20:00:00,11584.0 -2014-10-28 21:00:00,11483.0 -2014-10-28 22:00:00,11182.0 -2014-10-28 23:00:00,10671.0 -2014-10-29 00:00:00,9952.0 -2014-10-27 01:00:00,8492.0 -2014-10-27 02:00:00,8101.0 -2014-10-27 03:00:00,7885.0 -2014-10-27 04:00:00,7777.0 -2014-10-27 05:00:00,7753.0 -2014-10-27 06:00:00,8030.0 -2014-10-27 07:00:00,8795.0 -2014-10-27 08:00:00,10055.0 -2014-10-27 09:00:00,10816.0 -2014-10-27 10:00:00,10929.0 -2014-10-27 11:00:00,11166.0 -2014-10-27 12:00:00,11502.0 -2014-10-27 13:00:00,11631.0 -2014-10-27 14:00:00,11715.0 -2014-10-27 15:00:00,11847.0 -2014-10-27 16:00:00,11899.0 -2014-10-27 17:00:00,11774.0 -2014-10-27 18:00:00,11631.0 -2014-10-27 19:00:00,11717.0 -2014-10-27 20:00:00,12201.0 -2014-10-27 21:00:00,12105.0 -2014-10-27 22:00:00,11741.0 -2014-10-27 23:00:00,11119.0 -2014-10-28 00:00:00,10237.0 -2014-10-26 01:00:00,8504.0 -2014-10-26 02:00:00,8027.0 -2014-10-26 03:00:00,7835.0 -2014-10-26 04:00:00,7644.0 -2014-10-26 05:00:00,7579.0 -2014-10-26 06:00:00,7578.0 -2014-10-26 07:00:00,7757.0 -2014-10-26 08:00:00,8006.0 -2014-10-26 09:00:00,8171.0 -2014-10-26 10:00:00,8368.0 -2014-10-26 11:00:00,8626.0 -2014-10-26 12:00:00,8754.0 -2014-10-26 13:00:00,8883.0 -2014-10-26 14:00:00,8889.0 -2014-10-26 15:00:00,8953.0 -2014-10-26 16:00:00,8899.0 -2014-10-26 17:00:00,8893.0 -2014-10-26 18:00:00,8925.0 -2014-10-26 19:00:00,9166.0 -2014-10-26 20:00:00,9953.0 -2014-10-26 21:00:00,10144.0 -2014-10-26 22:00:00,9933.0 -2014-10-26 23:00:00,9597.0 -2014-10-27 00:00:00,9074.0 -2014-10-25 01:00:00,9098.0 -2014-10-25 02:00:00,8608.0 -2014-10-25 03:00:00,8229.0 -2014-10-25 04:00:00,8041.0 -2014-10-25 05:00:00,7916.0 -2014-10-25 06:00:00,7980.0 -2014-10-25 07:00:00,8197.0 -2014-10-25 08:00:00,8698.0 -2014-10-25 09:00:00,8987.0 -2014-10-25 10:00:00,9308.0 -2014-10-25 11:00:00,9619.0 -2014-10-25 12:00:00,9818.0 -2014-10-25 13:00:00,9849.0 -2014-10-25 14:00:00,9835.0 -2014-10-25 15:00:00,9710.0 -2014-10-25 16:00:00,9608.0 -2014-10-25 17:00:00,9531.0 -2014-10-25 18:00:00,9483.0 -2014-10-25 19:00:00,9538.0 -2014-10-25 20:00:00,10079.0 -2014-10-25 21:00:00,10112.0 -2014-10-25 22:00:00,9900.0 -2014-10-25 23:00:00,9580.0 -2014-10-26 00:00:00,9019.0 -2014-10-24 01:00:00,9159.0 -2014-10-24 02:00:00,8684.0 -2014-10-24 03:00:00,8402.0 -2014-10-24 04:00:00,8186.0 -2014-10-24 05:00:00,8142.0 -2014-10-24 06:00:00,8337.0 -2014-10-24 07:00:00,9018.0 -2014-10-24 08:00:00,10152.0 -2014-10-24 09:00:00,10818.0 -2014-10-24 10:00:00,10947.0 -2014-10-24 11:00:00,10991.0 -2014-10-24 12:00:00,11180.0 -2014-10-24 13:00:00,11264.0 -2014-10-24 14:00:00,11167.0 -2014-10-24 15:00:00,11188.0 -2014-10-24 16:00:00,11145.0 -2014-10-24 17:00:00,11018.0 -2014-10-24 18:00:00,10844.0 -2014-10-24 19:00:00,10831.0 -2014-10-24 20:00:00,11324.0 -2014-10-24 21:00:00,11203.0 -2014-10-24 22:00:00,10847.0 -2014-10-24 23:00:00,10441.0 -2014-10-25 00:00:00,9822.0 -2014-10-23 01:00:00,9367.0 -2014-10-23 02:00:00,8905.0 -2014-10-23 03:00:00,8627.0 -2014-10-23 04:00:00,8463.0 -2014-10-23 05:00:00,8475.0 -2014-10-23 06:00:00,8698.0 -2014-10-23 07:00:00,9399.0 -2014-10-23 08:00:00,10625.0 -2014-10-23 09:00:00,11092.0 -2014-10-23 10:00:00,11162.0 -2014-10-23 11:00:00,11140.0 -2014-10-23 12:00:00,11202.0 -2014-10-23 13:00:00,10889.0 -2014-10-23 14:00:00,10916.0 -2014-10-23 15:00:00,11160.0 -2014-10-23 16:00:00,11066.0 -2014-10-23 17:00:00,10947.0 -2014-10-23 18:00:00,10917.0 -2014-10-23 19:00:00,11166.0 -2014-10-23 20:00:00,11611.0 -2014-10-23 21:00:00,11549.0 -2014-10-23 22:00:00,11251.0 -2014-10-23 23:00:00,10729.0 -2014-10-24 00:00:00,9944.0 -2014-10-22 01:00:00,9342.0 -2014-10-22 02:00:00,8854.0 -2014-10-22 03:00:00,8604.0 -2014-10-22 04:00:00,8445.0 -2014-10-22 05:00:00,8413.0 -2014-10-22 06:00:00,8650.0 -2014-10-22 07:00:00,9323.0 -2014-10-22 08:00:00,10530.0 -2014-10-22 09:00:00,11163.0 -2014-10-22 10:00:00,11187.0 -2014-10-22 11:00:00,11228.0 -2014-10-22 12:00:00,11217.0 -2014-10-22 13:00:00,11222.0 -2014-10-22 14:00:00,11166.0 -2014-10-22 15:00:00,11166.0 -2014-10-22 16:00:00,11075.0 -2014-10-22 17:00:00,10945.0 -2014-10-22 18:00:00,10859.0 -2014-10-22 19:00:00,10907.0 -2014-10-22 20:00:00,11519.0 -2014-10-22 21:00:00,11590.0 -2014-10-22 22:00:00,11328.0 -2014-10-22 23:00:00,10833.0 -2014-10-23 00:00:00,10093.0 -2014-10-21 01:00:00,9110.0 -2014-10-21 02:00:00,8618.0 -2014-10-21 03:00:00,8364.0 -2014-10-21 04:00:00,8194.0 -2014-10-21 05:00:00,8159.0 -2014-10-21 06:00:00,8374.0 -2014-10-21 07:00:00,9050.0 -2014-10-21 08:00:00,10256.0 -2014-10-21 09:00:00,10858.0 -2014-10-21 10:00:00,10997.0 -2014-10-21 11:00:00,11064.0 -2014-10-21 12:00:00,11174.0 -2014-10-21 13:00:00,11211.0 -2014-10-21 14:00:00,11177.0 -2014-10-21 15:00:00,11207.0 -2014-10-21 16:00:00,11147.0 -2014-10-21 17:00:00,11123.0 -2014-10-21 18:00:00,11148.0 -2014-10-21 19:00:00,11344.0 -2014-10-21 20:00:00,11766.0 -2014-10-21 21:00:00,11682.0 -2014-10-21 22:00:00,11424.0 -2014-10-21 23:00:00,10919.0 -2014-10-22 00:00:00,10114.0 -2014-10-20 01:00:00,8638.0 -2014-10-20 02:00:00,8332.0 -2014-10-20 03:00:00,8073.0 -2014-10-20 04:00:00,7997.0 -2014-10-20 05:00:00,7991.0 -2014-10-20 06:00:00,8272.0 -2014-10-20 07:00:00,9040.0 -2014-10-20 08:00:00,10335.0 -2014-10-20 09:00:00,10910.0 -2014-10-20 10:00:00,11040.0 -2014-10-20 11:00:00,11007.0 -2014-10-20 12:00:00,11110.0 -2014-10-20 13:00:00,11163.0 -2014-10-20 14:00:00,11204.0 -2014-10-20 15:00:00,11248.0 -2014-10-20 16:00:00,11179.0 -2014-10-20 17:00:00,11036.0 -2014-10-20 18:00:00,10955.0 -2014-10-20 19:00:00,11012.0 -2014-10-20 20:00:00,11461.0 -2014-10-20 21:00:00,11468.0 -2014-10-20 22:00:00,11164.0 -2014-10-20 23:00:00,10621.0 -2014-10-21 00:00:00,9847.0 -2014-10-19 01:00:00,8944.0 -2014-10-19 02:00:00,8513.0 -2014-10-19 03:00:00,8282.0 -2014-10-19 04:00:00,8080.0 -2014-10-19 05:00:00,8023.0 -2014-10-19 06:00:00,8033.0 -2014-10-19 07:00:00,8209.0 -2014-10-19 08:00:00,8453.0 -2014-10-19 09:00:00,8567.0 -2014-10-19 10:00:00,8765.0 -2014-10-19 11:00:00,8947.0 -2014-10-19 12:00:00,9007.0 -2014-10-19 13:00:00,9060.0 -2014-10-19 14:00:00,9026.0 -2014-10-19 15:00:00,8984.0 -2014-10-19 16:00:00,8902.0 -2014-10-19 17:00:00,8917.0 -2014-10-19 18:00:00,8972.0 -2014-10-19 19:00:00,9335.0 -2014-10-19 20:00:00,10084.0 -2014-10-19 21:00:00,10241.0 -2014-10-19 22:00:00,10018.0 -2014-10-19 23:00:00,9726.0 -2014-10-20 00:00:00,9184.0 -2014-10-18 01:00:00,9142.0 -2014-10-18 02:00:00,8648.0 -2014-10-18 03:00:00,8269.0 -2014-10-18 04:00:00,8120.0 -2014-10-18 05:00:00,8003.0 -2014-10-18 06:00:00,8054.0 -2014-10-18 07:00:00,8292.0 -2014-10-18 08:00:00,8807.0 -2014-10-18 09:00:00,9142.0 -2014-10-18 10:00:00,9505.0 -2014-10-18 11:00:00,9799.0 -2014-10-18 12:00:00,9934.0 -2014-10-18 13:00:00,9906.0 -2014-10-18 14:00:00,9842.0 -2014-10-18 15:00:00,9604.0 -2014-10-18 16:00:00,9455.0 -2014-10-18 17:00:00,9397.0 -2014-10-18 18:00:00,9479.0 -2014-10-18 19:00:00,9514.0 -2014-10-18 20:00:00,10091.0 -2014-10-18 21:00:00,10241.0 -2014-10-18 22:00:00,10095.0 -2014-10-18 23:00:00,9859.0 -2014-10-19 00:00:00,9409.0 -2014-10-17 01:00:00,9252.0 -2014-10-17 02:00:00,8769.0 -2014-10-17 03:00:00,8472.0 -2014-10-17 04:00:00,8274.0 -2014-10-17 05:00:00,8205.0 -2014-10-17 06:00:00,8386.0 -2014-10-17 07:00:00,9055.0 -2014-10-17 08:00:00,10142.0 -2014-10-17 09:00:00,10613.0 -2014-10-17 10:00:00,10781.0 -2014-10-17 11:00:00,10875.0 -2014-10-17 12:00:00,11038.0 -2014-10-17 13:00:00,11067.0 -2014-10-17 14:00:00,11020.0 -2014-10-17 15:00:00,11072.0 -2014-10-17 16:00:00,11013.0 -2014-10-17 17:00:00,10925.0 -2014-10-17 18:00:00,10978.0 -2014-10-17 19:00:00,11105.0 -2014-10-17 20:00:00,11311.0 -2014-10-17 21:00:00,11143.0 -2014-10-17 22:00:00,10874.0 -2014-10-17 23:00:00,10412.0 -2014-10-18 00:00:00,9821.0 -2014-10-16 01:00:00,9166.0 -2014-10-16 02:00:00,8654.0 -2014-10-16 03:00:00,8353.0 -2014-10-16 04:00:00,8172.0 -2014-10-16 05:00:00,8105.0 -2014-10-16 06:00:00,8303.0 -2014-10-16 07:00:00,8985.0 -2014-10-16 08:00:00,10176.0 -2014-10-16 09:00:00,10775.0 -2014-10-16 10:00:00,10888.0 -2014-10-16 11:00:00,11021.0 -2014-10-16 12:00:00,11141.0 -2014-10-16 13:00:00,11130.0 -2014-10-16 14:00:00,11078.0 -2014-10-16 15:00:00,11085.0 -2014-10-16 16:00:00,11079.0 -2014-10-16 17:00:00,11074.0 -2014-10-16 18:00:00,11199.0 -2014-10-16 19:00:00,11182.0 -2014-10-16 20:00:00,11403.0 -2014-10-16 21:00:00,11434.0 -2014-10-16 22:00:00,11194.0 -2014-10-16 23:00:00,10729.0 -2014-10-17 00:00:00,9982.0 -2014-10-15 01:00:00,9150.0 -2014-10-15 02:00:00,8658.0 -2014-10-15 03:00:00,8331.0 -2014-10-15 04:00:00,8129.0 -2014-10-15 05:00:00,8076.0 -2014-10-15 06:00:00,8312.0 -2014-10-15 07:00:00,8948.0 -2014-10-15 08:00:00,10114.0 -2014-10-15 09:00:00,10804.0 -2014-10-15 10:00:00,11010.0 -2014-10-15 11:00:00,11116.0 -2014-10-15 12:00:00,11260.0 -2014-10-15 13:00:00,11269.0 -2014-10-15 14:00:00,11206.0 -2014-10-15 15:00:00,11230.0 -2014-10-15 16:00:00,11148.0 -2014-10-15 17:00:00,11017.0 -2014-10-15 18:00:00,11006.0 -2014-10-15 19:00:00,11133.0 -2014-10-15 20:00:00,11513.0 -2014-10-15 21:00:00,11465.0 -2014-10-15 22:00:00,11152.0 -2014-10-15 23:00:00,10657.0 -2014-10-16 00:00:00,9902.0 -2014-10-14 01:00:00,9391.0 -2014-10-14 02:00:00,8837.0 -2014-10-14 03:00:00,8498.0 -2014-10-14 04:00:00,8269.0 -2014-10-14 05:00:00,8185.0 -2014-10-14 06:00:00,8337.0 -2014-10-14 07:00:00,9052.0 -2014-10-14 08:00:00,10223.0 -2014-10-14 09:00:00,11067.0 -2014-10-14 10:00:00,11364.0 -2014-10-14 11:00:00,11487.0 -2014-10-14 12:00:00,11572.0 -2014-10-14 13:00:00,11591.0 -2014-10-14 14:00:00,11551.0 -2014-10-14 15:00:00,11564.0 -2014-10-14 16:00:00,11474.0 -2014-10-14 17:00:00,11398.0 -2014-10-14 18:00:00,11425.0 -2014-10-14 19:00:00,11544.0 -2014-10-14 20:00:00,11735.0 -2014-10-14 21:00:00,11659.0 -2014-10-14 22:00:00,11297.0 -2014-10-14 23:00:00,10709.0 -2014-10-15 00:00:00,9889.0 -2014-10-13 01:00:00,8425.0 -2014-10-13 02:00:00,8054.0 -2014-10-13 03:00:00,7831.0 -2014-10-13 04:00:00,7694.0 -2014-10-13 05:00:00,7720.0 -2014-10-13 06:00:00,7927.0 -2014-10-13 07:00:00,8629.0 -2014-10-13 08:00:00,9599.0 -2014-10-13 09:00:00,10248.0 -2014-10-13 10:00:00,10584.0 -2014-10-13 11:00:00,10865.0 -2014-10-13 12:00:00,11129.0 -2014-10-13 13:00:00,11232.0 -2014-10-13 14:00:00,11275.0 -2014-10-13 15:00:00,11363.0 -2014-10-13 16:00:00,11394.0 -2014-10-13 17:00:00,11393.0 -2014-10-13 18:00:00,11504.0 -2014-10-13 19:00:00,11667.0 -2014-10-13 20:00:00,11916.0 -2014-10-13 21:00:00,11851.0 -2014-10-13 22:00:00,11504.0 -2014-10-13 23:00:00,10941.0 -2014-10-14 00:00:00,10170.0 -2014-10-12 01:00:00,8587.0 -2014-10-12 02:00:00,8210.0 -2014-10-12 03:00:00,7919.0 -2014-10-12 04:00:00,7747.0 -2014-10-12 05:00:00,7697.0 -2014-10-12 06:00:00,7718.0 -2014-10-12 07:00:00,7871.0 -2014-10-12 08:00:00,8145.0 -2014-10-12 09:00:00,8169.0 -2014-10-12 10:00:00,8392.0 -2014-10-12 11:00:00,8573.0 -2014-10-12 12:00:00,8706.0 -2014-10-12 13:00:00,8797.0 -2014-10-12 14:00:00,8813.0 -2014-10-12 15:00:00,8829.0 -2014-10-12 16:00:00,8773.0 -2014-10-12 17:00:00,8829.0 -2014-10-12 18:00:00,8898.0 -2014-10-12 19:00:00,9138.0 -2014-10-12 20:00:00,9713.0 -2014-10-12 21:00:00,9896.0 -2014-10-12 22:00:00,9628.0 -2014-10-12 23:00:00,9349.0 -2014-10-13 00:00:00,8880.0 -2014-10-11 01:00:00,9140.0 -2014-10-11 02:00:00,8642.0 -2014-10-11 03:00:00,8346.0 -2014-10-11 04:00:00,8160.0 -2014-10-11 05:00:00,8084.0 -2014-10-11 06:00:00,8158.0 -2014-10-11 07:00:00,8411.0 -2014-10-11 08:00:00,8926.0 -2014-10-11 09:00:00,9120.0 -2014-10-11 10:00:00,9432.0 -2014-10-11 11:00:00,9585.0 -2014-10-11 12:00:00,9654.0 -2014-10-11 13:00:00,9610.0 -2014-10-11 14:00:00,9470.0 -2014-10-11 15:00:00,9341.0 -2014-10-11 16:00:00,9201.0 -2014-10-11 17:00:00,9149.0 -2014-10-11 18:00:00,9096.0 -2014-10-11 19:00:00,9138.0 -2014-10-11 20:00:00,9640.0 -2014-10-11 21:00:00,9922.0 -2014-10-11 22:00:00,9758.0 -2014-10-11 23:00:00,9497.0 -2014-10-12 00:00:00,9116.0 -2014-10-10 01:00:00,9053.0 -2014-10-10 02:00:00,8602.0 -2014-10-10 03:00:00,8315.0 -2014-10-10 04:00:00,8125.0 -2014-10-10 05:00:00,8103.0 -2014-10-10 06:00:00,8327.0 -2014-10-10 07:00:00,8995.0 -2014-10-10 08:00:00,10119.0 -2014-10-10 09:00:00,10560.0 -2014-10-10 10:00:00,10761.0 -2014-10-10 11:00:00,10884.0 -2014-10-10 12:00:00,10991.0 -2014-10-10 13:00:00,10997.0 -2014-10-10 14:00:00,10915.0 -2014-10-10 15:00:00,10904.0 -2014-10-10 16:00:00,10799.0 -2014-10-10 17:00:00,10620.0 -2014-10-10 18:00:00,10504.0 -2014-10-10 19:00:00,10463.0 -2014-10-10 20:00:00,10931.0 -2014-10-10 21:00:00,11047.0 -2014-10-10 22:00:00,10744.0 -2014-10-10 23:00:00,10407.0 -2014-10-11 00:00:00,9738.0 -2014-10-09 01:00:00,9057.0 -2014-10-09 02:00:00,8565.0 -2014-10-09 03:00:00,8265.0 -2014-10-09 04:00:00,8083.0 -2014-10-09 05:00:00,8052.0 -2014-10-09 06:00:00,8233.0 -2014-10-09 07:00:00,8886.0 -2014-10-09 08:00:00,10070.0 -2014-10-09 09:00:00,10605.0 -2014-10-09 10:00:00,10769.0 -2014-10-09 11:00:00,10867.0 -2014-10-09 12:00:00,10996.0 -2014-10-09 13:00:00,11001.0 -2014-10-09 14:00:00,10996.0 -2014-10-09 15:00:00,11078.0 -2014-10-09 16:00:00,11052.0 -2014-10-09 17:00:00,10940.0 -2014-10-09 18:00:00,10806.0 -2014-10-09 19:00:00,10691.0 -2014-10-09 20:00:00,11025.0 -2014-10-09 21:00:00,11282.0 -2014-10-09 22:00:00,11011.0 -2014-10-09 23:00:00,10519.0 -2014-10-10 00:00:00,9826.0 -2014-10-08 01:00:00,9045.0 -2014-10-08 02:00:00,8560.0 -2014-10-08 03:00:00,8260.0 -2014-10-08 04:00:00,8081.0 -2014-10-08 05:00:00,8088.0 -2014-10-08 06:00:00,8282.0 -2014-10-08 07:00:00,8985.0 -2014-10-08 08:00:00,10153.0 -2014-10-08 09:00:00,10653.0 -2014-10-08 10:00:00,10803.0 -2014-10-08 11:00:00,10867.0 -2014-10-08 12:00:00,11133.0 -2014-10-08 13:00:00,11104.0 -2014-10-08 14:00:00,11122.0 -2014-10-08 15:00:00,11171.0 -2014-10-08 16:00:00,11121.0 -2014-10-08 17:00:00,11051.0 -2014-10-08 18:00:00,10937.0 -2014-10-08 19:00:00,10840.0 -2014-10-08 20:00:00,11154.0 -2014-10-08 21:00:00,11381.0 -2014-10-08 22:00:00,11101.0 -2014-10-08 23:00:00,10552.0 -2014-10-09 00:00:00,9780.0 -2014-10-07 01:00:00,9095.0 -2014-10-07 02:00:00,8621.0 -2014-10-07 03:00:00,8340.0 -2014-10-07 04:00:00,8165.0 -2014-10-07 05:00:00,8117.0 -2014-10-07 06:00:00,8337.0 -2014-10-07 07:00:00,9010.0 -2014-10-07 08:00:00,10155.0 -2014-10-07 09:00:00,10620.0 -2014-10-07 10:00:00,10840.0 -2014-10-07 11:00:00,10981.0 -2014-10-07 12:00:00,11120.0 -2014-10-07 13:00:00,11178.0 -2014-10-07 14:00:00,11219.0 -2014-10-07 15:00:00,11268.0 -2014-10-07 16:00:00,11186.0 -2014-10-07 17:00:00,11039.0 -2014-10-07 18:00:00,10959.0 -2014-10-07 19:00:00,10835.0 -2014-10-07 20:00:00,11128.0 -2014-10-07 21:00:00,11417.0 -2014-10-07 22:00:00,11129.0 -2014-10-07 23:00:00,10561.0 -2014-10-08 00:00:00,9807.0 -2014-10-06 01:00:00,8439.0 -2014-10-06 02:00:00,8143.0 -2014-10-06 03:00:00,7943.0 -2014-10-06 04:00:00,7867.0 -2014-10-06 05:00:00,7836.0 -2014-10-06 06:00:00,8187.0 -2014-10-06 07:00:00,8896.0 -2014-10-06 08:00:00,10154.0 -2014-10-06 09:00:00,10800.0 -2014-10-06 10:00:00,10969.0 -2014-10-06 11:00:00,11011.0 -2014-10-06 12:00:00,11119.0 -2014-10-06 13:00:00,11141.0 -2014-10-06 14:00:00,11083.0 -2014-10-06 15:00:00,11136.0 -2014-10-06 16:00:00,10997.0 -2014-10-06 17:00:00,10862.0 -2014-10-06 18:00:00,10778.0 -2014-10-06 19:00:00,10719.0 -2014-10-06 20:00:00,11092.0 -2014-10-06 21:00:00,11469.0 -2014-10-06 22:00:00,11171.0 -2014-10-06 23:00:00,10621.0 -2014-10-07 00:00:00,9864.0 -2014-10-05 01:00:00,8749.0 -2014-10-05 02:00:00,8334.0 -2014-10-05 03:00:00,8047.0 -2014-10-05 04:00:00,7920.0 -2014-10-05 05:00:00,7832.0 -2014-10-05 06:00:00,7834.0 -2014-10-05 07:00:00,7925.0 -2014-10-05 08:00:00,8138.0 -2014-10-05 09:00:00,8202.0 -2014-10-05 10:00:00,8454.0 -2014-10-05 11:00:00,8691.0 -2014-10-05 12:00:00,8790.0 -2014-10-05 13:00:00,8889.0 -2014-10-05 14:00:00,8869.0 -2014-10-05 15:00:00,8870.0 -2014-10-05 16:00:00,8774.0 -2014-10-05 17:00:00,8779.0 -2014-10-05 18:00:00,8873.0 -2014-10-05 19:00:00,9197.0 -2014-10-05 20:00:00,9755.0 -2014-10-05 21:00:00,10003.0 -2014-10-05 22:00:00,9882.0 -2014-10-05 23:00:00,9495.0 -2014-10-06 00:00:00,9007.0 -2014-10-04 01:00:00,9105.0 -2014-10-04 02:00:00,8575.0 -2014-10-04 03:00:00,8270.0 -2014-10-04 04:00:00,8101.0 -2014-10-04 05:00:00,7979.0 -2014-10-04 06:00:00,8052.0 -2014-10-04 07:00:00,8304.0 -2014-10-04 08:00:00,8769.0 -2014-10-04 09:00:00,9049.0 -2014-10-04 10:00:00,9564.0 -2014-10-04 11:00:00,9943.0 -2014-10-04 12:00:00,10141.0 -2014-10-04 13:00:00,10066.0 -2014-10-04 14:00:00,10010.0 -2014-10-04 15:00:00,9818.0 -2014-10-04 16:00:00,9701.0 -2014-10-04 17:00:00,9583.0 -2014-10-04 18:00:00,9583.0 -2014-10-04 19:00:00,9588.0 -2014-10-04 20:00:00,9949.0 -2014-10-04 21:00:00,10240.0 -2014-10-04 22:00:00,10060.0 -2014-10-04 23:00:00,9785.0 -2014-10-05 00:00:00,9341.0 -2014-10-03 01:00:00,9903.0 -2014-10-03 02:00:00,9336.0 -2014-10-03 03:00:00,8979.0 -2014-10-03 04:00:00,8684.0 -2014-10-03 05:00:00,8690.0 -2014-10-03 06:00:00,8869.0 -2014-10-03 07:00:00,9366.0 -2014-10-03 08:00:00,10300.0 -2014-10-03 09:00:00,10966.0 -2014-10-03 10:00:00,11124.0 -2014-10-03 11:00:00,11263.0 -2014-10-03 12:00:00,11316.0 -2014-10-03 13:00:00,11315.0 -2014-10-03 14:00:00,11252.0 -2014-10-03 15:00:00,11180.0 -2014-10-03 16:00:00,10989.0 -2014-10-03 17:00:00,10789.0 -2014-10-03 18:00:00,10697.0 -2014-10-03 19:00:00,10643.0 -2014-10-03 20:00:00,10929.0 -2014-10-03 21:00:00,11031.0 -2014-10-03 22:00:00,10790.0 -2014-10-03 23:00:00,10452.0 -2014-10-04 00:00:00,9783.0 -2014-10-02 01:00:00,9308.0 -2014-10-02 02:00:00,8776.0 -2014-10-02 03:00:00,8479.0 -2014-10-02 04:00:00,8260.0 -2014-10-02 05:00:00,8219.0 -2014-10-02 06:00:00,8424.0 -2014-10-02 07:00:00,9081.0 -2014-10-02 08:00:00,10328.0 -2014-10-02 09:00:00,11008.0 -2014-10-02 10:00:00,11369.0 -2014-10-02 11:00:00,11717.0 -2014-10-02 12:00:00,12023.0 -2014-10-02 13:00:00,12223.0 -2014-10-02 14:00:00,12342.0 -2014-10-02 15:00:00,12366.0 -2014-10-02 16:00:00,12318.0 -2014-10-02 17:00:00,12259.0 -2014-10-02 18:00:00,12195.0 -2014-10-02 19:00:00,12097.0 -2014-10-02 20:00:00,12375.0 -2014-10-02 21:00:00,12538.0 -2014-10-02 22:00:00,12185.0 -2014-10-02 23:00:00,11561.0 -2014-10-03 00:00:00,10701.0 -2014-10-01 01:00:00,9024.0 -2014-10-01 02:00:00,8517.0 -2014-10-01 03:00:00,8223.0 -2014-10-01 04:00:00,8039.0 -2014-10-01 05:00:00,7982.0 -2014-10-01 06:00:00,8172.0 -2014-10-01 07:00:00,8830.0 -2014-10-01 08:00:00,9930.0 -2014-10-01 09:00:00,10403.0 -2014-10-01 10:00:00,10688.0 -2014-10-01 11:00:00,10934.0 -2014-10-01 12:00:00,11106.0 -2014-10-01 13:00:00,11264.0 -2014-10-01 14:00:00,11412.0 -2014-10-01 15:00:00,11567.0 -2014-10-01 16:00:00,11578.0 -2014-10-01 17:00:00,11496.0 -2014-10-01 18:00:00,11452.0 -2014-10-01 19:00:00,11312.0 -2014-10-01 20:00:00,11442.0 -2014-10-01 21:00:00,11783.0 -2014-10-01 22:00:00,11496.0 -2014-10-01 23:00:00,10932.0 -2014-10-02 00:00:00,10117.0 -2014-09-30 01:00:00,9333.0 -2014-09-30 02:00:00,8736.0 -2014-09-30 03:00:00,8387.0 -2014-09-30 04:00:00,8167.0 -2014-09-30 05:00:00,8061.0 -2014-09-30 06:00:00,8221.0 -2014-09-30 07:00:00,8844.0 -2014-09-30 08:00:00,9951.0 -2014-09-30 09:00:00,10501.0 -2014-09-30 10:00:00,10750.0 -2014-09-30 11:00:00,10921.0 -2014-09-30 12:00:00,11040.0 -2014-09-30 13:00:00,11060.0 -2014-09-30 14:00:00,11041.0 -2014-09-30 15:00:00,11094.0 -2014-09-30 16:00:00,11014.0 -2014-09-30 17:00:00,10912.0 -2014-09-30 18:00:00,10802.0 -2014-09-30 19:00:00,10777.0 -2014-09-30 20:00:00,11084.0 -2014-09-30 21:00:00,11387.0 -2014-09-30 22:00:00,11105.0 -2014-09-30 23:00:00,10554.0 -2014-10-01 00:00:00,9762.0 -2014-09-29 01:00:00,8922.0 -2014-09-29 02:00:00,8460.0 -2014-09-29 03:00:00,8214.0 -2014-09-29 04:00:00,8034.0 -2014-09-29 05:00:00,8047.0 -2014-09-29 06:00:00,8227.0 -2014-09-29 07:00:00,8979.0 -2014-09-29 08:00:00,10098.0 -2014-09-29 09:00:00,10735.0 -2014-09-29 10:00:00,11204.0 -2014-09-29 11:00:00,11771.0 -2014-09-29 12:00:00,12261.0 -2014-09-29 13:00:00,12700.0 -2014-09-29 14:00:00,13076.0 -2014-09-29 15:00:00,13447.0 -2014-09-29 16:00:00,13644.0 -2014-09-29 17:00:00,13699.0 -2014-09-29 18:00:00,13600.0 -2014-09-29 19:00:00,13217.0 -2014-09-29 20:00:00,12691.0 -2014-09-29 21:00:00,12603.0 -2014-09-29 22:00:00,11968.0 -2014-09-29 23:00:00,11170.0 -2014-09-30 00:00:00,10210.0 -2014-09-28 01:00:00,9114.0 -2014-09-28 02:00:00,8623.0 -2014-09-28 03:00:00,8241.0 -2014-09-28 04:00:00,8023.0 -2014-09-28 05:00:00,7839.0 -2014-09-28 06:00:00,7839.0 -2014-09-28 07:00:00,7830.0 -2014-09-28 08:00:00,8026.0 -2014-09-28 09:00:00,8039.0 -2014-09-28 10:00:00,8625.0 -2014-09-28 11:00:00,9111.0 -2014-09-28 12:00:00,9603.0 -2014-09-28 13:00:00,10029.0 -2014-09-28 14:00:00,10310.0 -2014-09-28 15:00:00,10514.0 -2014-09-28 16:00:00,10727.0 -2014-09-28 17:00:00,10856.0 -2014-09-28 18:00:00,10826.0 -2014-09-28 19:00:00,10799.0 -2014-09-28 20:00:00,10792.0 -2014-09-28 21:00:00,11183.0 -2014-09-28 22:00:00,10862.0 -2014-09-28 23:00:00,10324.0 -2014-09-29 00:00:00,9595.0 -2014-09-27 01:00:00,9687.0 -2014-09-27 02:00:00,9036.0 -2014-09-27 03:00:00,8622.0 -2014-09-27 04:00:00,8277.0 -2014-09-27 05:00:00,8175.0 -2014-09-27 06:00:00,8172.0 -2014-09-27 07:00:00,8385.0 -2014-09-27 08:00:00,8707.0 -2014-09-27 09:00:00,8978.0 -2014-09-27 10:00:00,9628.0 -2014-09-27 11:00:00,10151.0 -2014-09-27 12:00:00,10580.0 -2014-09-27 13:00:00,10869.0 -2014-09-27 14:00:00,11068.0 -2014-09-27 15:00:00,11066.0 -2014-09-27 16:00:00,11151.0 -2014-09-27 17:00:00,11079.0 -2014-09-27 18:00:00,11012.0 -2014-09-27 19:00:00,10755.0 -2014-09-27 20:00:00,10749.0 -2014-09-27 21:00:00,11029.0 -2014-09-27 22:00:00,10810.0 -2014-09-27 23:00:00,10375.0 -2014-09-28 00:00:00,9793.0 -2014-09-26 01:00:00,9456.0 -2014-09-26 02:00:00,8881.0 -2014-09-26 03:00:00,8521.0 -2014-09-26 04:00:00,8290.0 -2014-09-26 05:00:00,8201.0 -2014-09-26 06:00:00,8361.0 -2014-09-26 07:00:00,8982.0 -2014-09-26 08:00:00,9957.0 -2014-09-26 09:00:00,10458.0 -2014-09-26 10:00:00,11023.0 -2014-09-26 11:00:00,11509.0 -2014-09-26 12:00:00,11959.0 -2014-09-26 13:00:00,12254.0 -2014-09-26 14:00:00,12458.0 -2014-09-26 15:00:00,12659.0 -2014-09-26 16:00:00,12750.0 -2014-09-26 17:00:00,12721.0 -2014-09-26 18:00:00,12580.0 -2014-09-26 19:00:00,12195.0 -2014-09-26 20:00:00,11912.0 -2014-09-26 21:00:00,12126.0 -2014-09-26 22:00:00,11749.0 -2014-09-26 23:00:00,11257.0 -2014-09-27 00:00:00,10460.0 -2014-09-25 01:00:00,9306.0 -2014-09-25 02:00:00,8749.0 -2014-09-25 03:00:00,8402.0 -2014-09-25 04:00:00,8213.0 -2014-09-25 05:00:00,8117.0 -2014-09-25 06:00:00,8279.0 -2014-09-25 07:00:00,8941.0 -2014-09-25 08:00:00,9999.0 -2014-09-25 09:00:00,10589.0 -2014-09-25 10:00:00,10977.0 -2014-09-25 11:00:00,11278.0 -2014-09-25 12:00:00,11547.0 -2014-09-25 13:00:00,11705.0 -2014-09-25 14:00:00,11848.0 -2014-09-25 15:00:00,12066.0 -2014-09-25 16:00:00,12135.0 -2014-09-25 17:00:00,12062.0 -2014-09-25 18:00:00,11962.0 -2014-09-25 19:00:00,11732.0 -2014-09-25 20:00:00,11622.0 -2014-09-25 21:00:00,12042.0 -2014-09-25 22:00:00,11745.0 -2014-09-25 23:00:00,11123.0 -2014-09-26 00:00:00,10294.0 -2014-09-24 01:00:00,9202.0 -2014-09-24 02:00:00,8681.0 -2014-09-24 03:00:00,8341.0 -2014-09-24 04:00:00,8143.0 -2014-09-24 05:00:00,8071.0 -2014-09-24 06:00:00,8247.0 -2014-09-24 07:00:00,8890.0 -2014-09-24 08:00:00,9888.0 -2014-09-24 09:00:00,10419.0 -2014-09-24 10:00:00,10866.0 -2014-09-24 11:00:00,11197.0 -2014-09-24 12:00:00,11523.0 -2014-09-24 13:00:00,11728.0 -2014-09-24 14:00:00,11887.0 -2014-09-24 15:00:00,12005.0 -2014-09-24 16:00:00,12023.0 -2014-09-24 17:00:00,11987.0 -2014-09-24 18:00:00,11924.0 -2014-09-24 19:00:00,11660.0 -2014-09-24 20:00:00,11560.0 -2014-09-24 21:00:00,11908.0 -2014-09-24 22:00:00,11588.0 -2014-09-24 23:00:00,10966.0 -2014-09-25 00:00:00,10124.0 -2014-09-23 01:00:00,8982.0 -2014-09-23 02:00:00,8463.0 -2014-09-23 03:00:00,8155.0 -2014-09-23 04:00:00,7978.0 -2014-09-23 05:00:00,7910.0 -2014-09-23 06:00:00,8110.0 -2014-09-23 07:00:00,8712.0 -2014-09-23 08:00:00,9768.0 -2014-09-23 09:00:00,10319.0 -2014-09-23 10:00:00,10675.0 -2014-09-23 11:00:00,10955.0 -2014-09-23 12:00:00,11302.0 -2014-09-23 13:00:00,11509.0 -2014-09-23 14:00:00,11643.0 -2014-09-23 15:00:00,11845.0 -2014-09-23 16:00:00,11896.0 -2014-09-23 17:00:00,11853.0 -2014-09-23 18:00:00,11820.0 -2014-09-23 19:00:00,11633.0 -2014-09-23 20:00:00,11473.0 -2014-09-23 21:00:00,11805.0 -2014-09-23 22:00:00,11502.0 -2014-09-23 23:00:00,10867.0 -2014-09-24 00:00:00,10020.0 -2014-09-22 01:00:00,8306.0 -2014-09-22 02:00:00,7942.0 -2014-09-22 03:00:00,7720.0 -2014-09-22 04:00:00,7549.0 -2014-09-22 05:00:00,7617.0 -2014-09-22 06:00:00,7861.0 -2014-09-22 07:00:00,8565.0 -2014-09-22 08:00:00,9689.0 -2014-09-22 09:00:00,10248.0 -2014-09-22 10:00:00,10565.0 -2014-09-22 11:00:00,10767.0 -2014-09-22 12:00:00,11014.0 -2014-09-22 13:00:00,11111.0 -2014-09-22 14:00:00,11175.0 -2014-09-22 15:00:00,11318.0 -2014-09-22 16:00:00,11315.0 -2014-09-22 17:00:00,11291.0 -2014-09-22 18:00:00,11225.0 -2014-09-22 19:00:00,11060.0 -2014-09-22 20:00:00,10972.0 -2014-09-22 21:00:00,11429.0 -2014-09-22 22:00:00,11153.0 -2014-09-22 23:00:00,10560.0 -2014-09-23 00:00:00,9816.0 -2014-09-21 01:00:00,9651.0 -2014-09-21 02:00:00,9102.0 -2014-09-21 03:00:00,8618.0 -2014-09-21 04:00:00,8301.0 -2014-09-21 05:00:00,7992.0 -2014-09-21 06:00:00,7858.0 -2014-09-21 07:00:00,7865.0 -2014-09-21 08:00:00,7910.0 -2014-09-21 09:00:00,7946.0 -2014-09-21 10:00:00,8409.0 -2014-09-21 11:00:00,8715.0 -2014-09-21 12:00:00,8971.0 -2014-09-21 13:00:00,9052.0 -2014-09-21 14:00:00,9175.0 -2014-09-21 15:00:00,9215.0 -2014-09-21 16:00:00,9235.0 -2014-09-21 17:00:00,9215.0 -2014-09-21 18:00:00,9288.0 -2014-09-21 19:00:00,9344.0 -2014-09-21 20:00:00,9467.0 -2014-09-21 21:00:00,10010.0 -2014-09-21 22:00:00,9863.0 -2014-09-21 23:00:00,9463.0 -2014-09-22 00:00:00,8914.0 -2014-09-20 01:00:00,9427.0 -2014-09-20 02:00:00,8761.0 -2014-09-20 03:00:00,8423.0 -2014-09-20 04:00:00,8130.0 -2014-09-20 05:00:00,8006.0 -2014-09-20 06:00:00,7986.0 -2014-09-20 07:00:00,8287.0 -2014-09-20 08:00:00,8659.0 -2014-09-20 09:00:00,8907.0 -2014-09-20 10:00:00,9442.0 -2014-09-20 11:00:00,9917.0 -2014-09-20 12:00:00,10358.0 -2014-09-20 13:00:00,10721.0 -2014-09-20 14:00:00,10931.0 -2014-09-20 15:00:00,10920.0 -2014-09-20 16:00:00,10918.0 -2014-09-20 17:00:00,10855.0 -2014-09-20 18:00:00,10863.0 -2014-09-20 19:00:00,10905.0 -2014-09-20 20:00:00,10847.0 -2014-09-20 21:00:00,11259.0 -2014-09-20 22:00:00,11112.0 -2014-09-20 23:00:00,10750.0 -2014-09-21 00:00:00,10231.0 -2014-09-19 01:00:00,9365.0 -2014-09-19 02:00:00,8837.0 -2014-09-19 03:00:00,8505.0 -2014-09-19 04:00:00,8331.0 -2014-09-19 05:00:00,8241.0 -2014-09-19 06:00:00,8396.0 -2014-09-19 07:00:00,8982.0 -2014-09-19 08:00:00,9900.0 -2014-09-19 09:00:00,10471.0 -2014-09-19 10:00:00,10873.0 -2014-09-19 11:00:00,11175.0 -2014-09-19 12:00:00,11499.0 -2014-09-19 13:00:00,11709.0 -2014-09-19 14:00:00,11787.0 -2014-09-19 15:00:00,11927.0 -2014-09-19 16:00:00,11932.0 -2014-09-19 17:00:00,11845.0 -2014-09-19 18:00:00,11721.0 -2014-09-19 19:00:00,11422.0 -2014-09-19 20:00:00,11150.0 -2014-09-19 21:00:00,11508.0 -2014-09-19 22:00:00,11288.0 -2014-09-19 23:00:00,10873.0 -2014-09-20 00:00:00,10112.0 -2014-09-18 01:00:00,9128.0 -2014-09-18 02:00:00,8616.0 -2014-09-18 03:00:00,8296.0 -2014-09-18 04:00:00,8092.0 -2014-09-18 05:00:00,8013.0 -2014-09-18 06:00:00,8208.0 -2014-09-18 07:00:00,8843.0 -2014-09-18 08:00:00,9850.0 -2014-09-18 09:00:00,10422.0 -2014-09-18 10:00:00,10755.0 -2014-09-18 11:00:00,11117.0 -2014-09-18 12:00:00,11437.0 -2014-09-18 13:00:00,11575.0 -2014-09-18 14:00:00,11657.0 -2014-09-18 15:00:00,11781.0 -2014-09-18 16:00:00,11773.0 -2014-09-18 17:00:00,11698.0 -2014-09-18 18:00:00,11588.0 -2014-09-18 19:00:00,11344.0 -2014-09-18 20:00:00,11153.0 -2014-09-18 21:00:00,11648.0 -2014-09-18 22:00:00,11443.0 -2014-09-18 23:00:00,10960.0 -2014-09-19 00:00:00,10153.0 -2014-09-17 01:00:00,9054.0 -2014-09-17 02:00:00,8540.0 -2014-09-17 03:00:00,8208.0 -2014-09-17 04:00:00,8032.0 -2014-09-17 05:00:00,7996.0 -2014-09-17 06:00:00,8184.0 -2014-09-17 07:00:00,8848.0 -2014-09-17 08:00:00,9829.0 -2014-09-17 09:00:00,10359.0 -2014-09-17 10:00:00,10702.0 -2014-09-17 11:00:00,10967.0 -2014-09-17 12:00:00,11220.0 -2014-09-17 13:00:00,11362.0 -2014-09-17 14:00:00,11458.0 -2014-09-17 15:00:00,11559.0 -2014-09-17 16:00:00,11583.0 -2014-09-17 17:00:00,11510.0 -2014-09-17 18:00:00,11421.0 -2014-09-17 19:00:00,11259.0 -2014-09-17 20:00:00,11058.0 -2014-09-17 21:00:00,11496.0 -2014-09-17 22:00:00,11315.0 -2014-09-17 23:00:00,10729.0 -2014-09-18 00:00:00,9941.0 -2014-09-16 01:00:00,9035.0 -2014-09-16 02:00:00,8538.0 -2014-09-16 03:00:00,8253.0 -2014-09-16 04:00:00,8101.0 -2014-09-16 05:00:00,8065.0 -2014-09-16 06:00:00,8261.0 -2014-09-16 07:00:00,8921.0 -2014-09-16 08:00:00,9934.0 -2014-09-16 09:00:00,10445.0 -2014-09-16 10:00:00,10719.0 -2014-09-16 11:00:00,10942.0 -2014-09-16 12:00:00,11160.0 -2014-09-16 13:00:00,11233.0 -2014-09-16 14:00:00,11231.0 -2014-09-16 15:00:00,11354.0 -2014-09-16 16:00:00,11341.0 -2014-09-16 17:00:00,11267.0 -2014-09-16 18:00:00,11186.0 -2014-09-16 19:00:00,11028.0 -2014-09-16 20:00:00,10844.0 -2014-09-16 21:00:00,11326.0 -2014-09-16 22:00:00,11185.0 -2014-09-16 23:00:00,10625.0 -2014-09-17 00:00:00,9809.0 -2014-09-15 01:00:00,8413.0 -2014-09-15 02:00:00,8021.0 -2014-09-15 03:00:00,7827.0 -2014-09-15 04:00:00,7691.0 -2014-09-15 05:00:00,7695.0 -2014-09-15 06:00:00,7948.0 -2014-09-15 07:00:00,8652.0 -2014-09-15 08:00:00,9752.0 -2014-09-15 09:00:00,10439.0 -2014-09-15 10:00:00,10704.0 -2014-09-15 11:00:00,10999.0 -2014-09-15 12:00:00,11253.0 -2014-09-15 13:00:00,11273.0 -2014-09-15 14:00:00,11246.0 -2014-09-15 15:00:00,11282.0 -2014-09-15 16:00:00,11208.0 -2014-09-15 17:00:00,11095.0 -2014-09-15 18:00:00,11031.0 -2014-09-15 19:00:00,10966.0 -2014-09-15 20:00:00,10982.0 -2014-09-15 21:00:00,11315.0 -2014-09-15 22:00:00,11100.0 -2014-09-15 23:00:00,10556.0 -2014-09-16 00:00:00,9781.0 -2014-09-14 01:00:00,8534.0 -2014-09-14 02:00:00,8157.0 -2014-09-14 03:00:00,7850.0 -2014-09-14 04:00:00,7690.0 -2014-09-14 05:00:00,7533.0 -2014-09-14 06:00:00,7583.0 -2014-09-14 07:00:00,7690.0 -2014-09-14 08:00:00,7824.0 -2014-09-14 09:00:00,7888.0 -2014-09-14 10:00:00,8267.0 -2014-09-14 11:00:00,8501.0 -2014-09-14 12:00:00,8762.0 -2014-09-14 13:00:00,8833.0 -2014-09-14 14:00:00,8926.0 -2014-09-14 15:00:00,8935.0 -2014-09-14 16:00:00,8977.0 -2014-09-14 17:00:00,8979.0 -2014-09-14 18:00:00,9080.0 -2014-09-14 19:00:00,9116.0 -2014-09-14 20:00:00,9248.0 -2014-09-14 21:00:00,9848.0 -2014-09-14 22:00:00,9857.0 -2014-09-14 23:00:00,9562.0 -2014-09-15 00:00:00,8991.0 -2014-09-13 01:00:00,9152.0 -2014-09-13 02:00:00,8620.0 -2014-09-13 03:00:00,8332.0 -2014-09-13 04:00:00,8041.0 -2014-09-13 05:00:00,8006.0 -2014-09-13 06:00:00,8037.0 -2014-09-13 07:00:00,8351.0 -2014-09-13 08:00:00,8637.0 -2014-09-13 09:00:00,8947.0 -2014-09-13 10:00:00,9311.0 -2014-09-13 11:00:00,9586.0 -2014-09-13 12:00:00,9740.0 -2014-09-13 13:00:00,9680.0 -2014-09-13 14:00:00,9540.0 -2014-09-13 15:00:00,9437.0 -2014-09-13 16:00:00,9366.0 -2014-09-13 17:00:00,9254.0 -2014-09-13 18:00:00,9228.0 -2014-09-13 19:00:00,9177.0 -2014-09-13 20:00:00,9235.0 -2014-09-13 21:00:00,9734.0 -2014-09-13 22:00:00,9843.0 -2014-09-13 23:00:00,9540.0 -2014-09-14 00:00:00,9102.0 -2014-09-12 01:00:00,9119.0 -2014-09-12 02:00:00,8605.0 -2014-09-12 03:00:00,8318.0 -2014-09-12 04:00:00,8146.0 -2014-09-12 05:00:00,8071.0 -2014-09-12 06:00:00,8279.0 -2014-09-12 07:00:00,8856.0 -2014-09-12 08:00:00,9897.0 -2014-09-12 09:00:00,10468.0 -2014-09-12 10:00:00,10800.0 -2014-09-12 11:00:00,10930.0 -2014-09-12 12:00:00,11058.0 -2014-09-12 13:00:00,11080.0 -2014-09-12 14:00:00,10979.0 -2014-09-12 15:00:00,11028.0 -2014-09-12 16:00:00,10955.0 -2014-09-12 17:00:00,10855.0 -2014-09-12 18:00:00,10802.0 -2014-09-12 19:00:00,10791.0 -2014-09-12 20:00:00,10790.0 -2014-09-12 21:00:00,11058.0 -2014-09-12 22:00:00,10908.0 -2014-09-12 23:00:00,10481.0 -2014-09-13 00:00:00,9808.0 -2014-09-11 01:00:00,9837.0 -2014-09-11 02:00:00,9178.0 -2014-09-11 03:00:00,8775.0 -2014-09-11 04:00:00,8519.0 -2014-09-11 05:00:00,8389.0 -2014-09-11 06:00:00,8474.0 -2014-09-11 07:00:00,9063.0 -2014-09-11 08:00:00,10014.0 -2014-09-11 09:00:00,10602.0 -2014-09-11 10:00:00,10882.0 -2014-09-11 11:00:00,11011.0 -2014-09-11 12:00:00,11100.0 -2014-09-11 13:00:00,11089.0 -2014-09-11 14:00:00,11043.0 -2014-09-11 15:00:00,11045.0 -2014-09-11 16:00:00,10965.0 -2014-09-11 17:00:00,10870.0 -2014-09-11 18:00:00,10824.0 -2014-09-11 19:00:00,10766.0 -2014-09-11 20:00:00,10839.0 -2014-09-11 21:00:00,11237.0 -2014-09-11 22:00:00,11099.0 -2014-09-11 23:00:00,10614.0 -2014-09-12 00:00:00,9882.0 -2014-09-10 01:00:00,11516.0 -2014-09-10 02:00:00,10768.0 -2014-09-10 03:00:00,10190.0 -2014-09-10 04:00:00,9813.0 -2014-09-10 05:00:00,9704.0 -2014-09-10 06:00:00,9869.0 -2014-09-10 07:00:00,10604.0 -2014-09-10 08:00:00,11824.0 -2014-09-10 09:00:00,12692.0 -2014-09-10 10:00:00,13214.0 -2014-09-10 11:00:00,13543.0 -2014-09-10 12:00:00,13829.0 -2014-09-10 13:00:00,14062.0 -2014-09-10 14:00:00,14350.0 -2014-09-10 15:00:00,14795.0 -2014-09-10 16:00:00,15094.0 -2014-09-10 17:00:00,15094.0 -2014-09-10 18:00:00,15013.0 -2014-09-10 19:00:00,14647.0 -2014-09-10 20:00:00,13967.0 -2014-09-10 21:00:00,13662.0 -2014-09-10 22:00:00,12975.0 -2014-09-10 23:00:00,11995.0 -2014-09-11 00:00:00,10870.0 -2014-09-09 01:00:00,10484.0 -2014-09-09 02:00:00,9806.0 -2014-09-09 03:00:00,9348.0 -2014-09-09 04:00:00,9077.0 -2014-09-09 05:00:00,8966.0 -2014-09-09 06:00:00,9151.0 -2014-09-09 07:00:00,9834.0 -2014-09-09 08:00:00,10952.0 -2014-09-09 09:00:00,11645.0 -2014-09-09 10:00:00,12127.0 -2014-09-09 11:00:00,12570.0 -2014-09-09 12:00:00,12947.0 -2014-09-09 13:00:00,13442.0 -2014-09-09 14:00:00,13984.0 -2014-09-09 15:00:00,14538.0 -2014-09-09 16:00:00,14750.0 -2014-09-09 17:00:00,15078.0 -2014-09-09 18:00:00,15181.0 -2014-09-09 19:00:00,14783.0 -2014-09-09 20:00:00,14391.0 -2014-09-09 21:00:00,14708.0 -2014-09-09 22:00:00,14524.0 -2014-09-09 23:00:00,13753.0 -2014-09-10 00:00:00,12658.0 -2014-09-08 01:00:00,9476.0 -2014-09-08 02:00:00,8918.0 -2014-09-08 03:00:00,8601.0 -2014-09-08 04:00:00,8476.0 -2014-09-08 05:00:00,8431.0 -2014-09-08 06:00:00,8586.0 -2014-09-08 07:00:00,9263.0 -2014-09-08 08:00:00,10251.0 -2014-09-08 09:00:00,11013.0 -2014-09-08 10:00:00,11624.0 -2014-09-08 11:00:00,12200.0 -2014-09-08 12:00:00,12710.0 -2014-09-08 13:00:00,13004.0 -2014-09-08 14:00:00,13247.0 -2014-09-08 15:00:00,13606.0 -2014-09-08 16:00:00,13789.0 -2014-09-08 17:00:00,13893.0 -2014-09-08 18:00:00,13930.0 -2014-09-08 19:00:00,13646.0 -2014-09-08 20:00:00,13132.0 -2014-09-08 21:00:00,13273.0 -2014-09-08 22:00:00,13136.0 -2014-09-08 23:00:00,12452.0 -2014-09-09 00:00:00,11442.0 -2014-09-07 01:00:00,9453.0 -2014-09-07 02:00:00,8879.0 -2014-09-07 03:00:00,8463.0 -2014-09-07 04:00:00,8204.0 -2014-09-07 05:00:00,8032.0 -2014-09-07 06:00:00,7963.0 -2014-09-07 07:00:00,8036.0 -2014-09-07 08:00:00,8000.0 -2014-09-07 09:00:00,8196.0 -2014-09-07 10:00:00,8843.0 -2014-09-07 11:00:00,9441.0 -2014-09-07 12:00:00,9976.0 -2014-09-07 13:00:00,10510.0 -2014-09-07 14:00:00,10868.0 -2014-09-07 15:00:00,11174.0 -2014-09-07 16:00:00,11415.0 -2014-09-07 17:00:00,11631.0 -2014-09-07 18:00:00,11758.0 -2014-09-07 19:00:00,11736.0 -2014-09-07 20:00:00,11448.0 -2014-09-07 21:00:00,11554.0 -2014-09-07 22:00:00,11539.0 -2014-09-07 23:00:00,10966.0 -2014-09-08 00:00:00,10220.0 -2014-09-06 01:00:00,10941.0 -2014-09-06 02:00:00,10168.0 -2014-09-06 03:00:00,9586.0 -2014-09-06 04:00:00,9175.0 -2014-09-06 05:00:00,8963.0 -2014-09-06 06:00:00,8912.0 -2014-09-06 07:00:00,9061.0 -2014-09-06 08:00:00,9223.0 -2014-09-06 09:00:00,9424.0 -2014-09-06 10:00:00,10030.0 -2014-09-06 11:00:00,10568.0 -2014-09-06 12:00:00,11041.0 -2014-09-06 13:00:00,11233.0 -2014-09-06 14:00:00,11312.0 -2014-09-06 15:00:00,11382.0 -2014-09-06 16:00:00,11499.0 -2014-09-06 17:00:00,11674.0 -2014-09-06 18:00:00,11741.0 -2014-09-06 19:00:00,11656.0 -2014-09-06 20:00:00,11300.0 -2014-09-06 21:00:00,11305.0 -2014-09-06 22:00:00,11280.0 -2014-09-06 23:00:00,10794.0 -2014-09-07 00:00:00,10157.0 -2014-09-05 01:00:00,13672.0 -2014-09-05 02:00:00,12826.0 -2014-09-05 03:00:00,12239.0 -2014-09-05 04:00:00,11831.0 -2014-09-05 05:00:00,11579.0 -2014-09-05 06:00:00,11706.0 -2014-09-05 07:00:00,12331.0 -2014-09-05 08:00:00,13461.0 -2014-09-05 09:00:00,14530.0 -2014-09-05 10:00:00,15700.0 -2014-09-05 11:00:00,16700.0 -2014-09-05 12:00:00,17624.0 -2014-09-05 13:00:00,18097.0 -2014-09-05 14:00:00,18434.0 -2014-09-05 15:00:00,19016.0 -2014-09-05 16:00:00,18974.0 -2014-09-05 17:00:00,17453.0 -2014-09-05 18:00:00,16558.0 -2014-09-05 19:00:00,15659.0 -2014-09-05 20:00:00,14528.0 -2014-09-05 21:00:00,13901.0 -2014-09-05 22:00:00,13434.0 -2014-09-05 23:00:00,12850.0 -2014-09-06 00:00:00,11915.0 -2014-09-04 01:00:00,12596.0 -2014-09-04 02:00:00,11660.0 -2014-09-04 03:00:00,11090.0 -2014-09-04 04:00:00,10777.0 -2014-09-04 05:00:00,10598.0 -2014-09-04 06:00:00,10738.0 -2014-09-04 07:00:00,11508.0 -2014-09-04 08:00:00,12745.0 -2014-09-04 09:00:00,13558.0 -2014-09-04 10:00:00,13896.0 -2014-09-04 11:00:00,14179.0 -2014-09-04 12:00:00,14315.0 -2014-09-04 13:00:00,14312.0 -2014-09-04 14:00:00,14203.0 -2014-09-04 15:00:00,14336.0 -2014-09-04 16:00:00,14764.0 -2014-09-04 17:00:00,15583.0 -2014-09-04 18:00:00,16440.0 -2014-09-04 19:00:00,16774.0 -2014-09-04 20:00:00,16614.0 -2014-09-04 21:00:00,16616.0 -2014-09-04 22:00:00,16626.0 -2014-09-04 23:00:00,16009.0 -2014-09-05 00:00:00,14885.0 -2014-09-03 01:00:00,11632.0 -2014-09-03 02:00:00,10764.0 -2014-09-03 03:00:00,10193.0 -2014-09-03 04:00:00,9810.0 -2014-09-03 05:00:00,9605.0 -2014-09-03 06:00:00,9711.0 -2014-09-03 07:00:00,10279.0 -2014-09-03 08:00:00,11211.0 -2014-09-03 09:00:00,12099.0 -2014-09-03 10:00:00,12994.0 -2014-09-03 11:00:00,13782.0 -2014-09-03 12:00:00,14555.0 -2014-09-03 13:00:00,15167.0 -2014-09-03 14:00:00,15723.0 -2014-09-03 15:00:00,16446.0 -2014-09-03 16:00:00,17003.0 -2014-09-03 17:00:00,17473.0 -2014-09-03 18:00:00,17697.0 -2014-09-03 19:00:00,17527.0 -2014-09-03 20:00:00,16896.0 -2014-09-03 21:00:00,16472.0 -2014-09-03 22:00:00,16169.0 -2014-09-03 23:00:00,15201.0 -2014-09-04 00:00:00,13827.0 -2014-09-02 01:00:00,11443.0 -2014-09-02 02:00:00,10721.0 -2014-09-02 03:00:00,10192.0 -2014-09-02 04:00:00,9816.0 -2014-09-02 05:00:00,9699.0 -2014-09-02 06:00:00,9892.0 -2014-09-02 07:00:00,10662.0 -2014-09-02 08:00:00,11741.0 -2014-09-02 09:00:00,12579.0 -2014-09-02 10:00:00,13411.0 -2014-09-02 11:00:00,14135.0 -2014-09-02 12:00:00,14899.0 -2014-09-02 13:00:00,15488.0 -2014-09-02 14:00:00,15958.0 -2014-09-02 15:00:00,16454.0 -2014-09-02 16:00:00,16765.0 -2014-09-02 17:00:00,16986.0 -2014-09-02 18:00:00,16962.0 -2014-09-02 19:00:00,16641.0 -2014-09-02 20:00:00,15827.0 -2014-09-02 21:00:00,15412.0 -2014-09-02 22:00:00,15197.0 -2014-09-02 23:00:00,14229.0 -2014-09-03 00:00:00,12900.0 -2014-09-01 01:00:00,12223.0 -2014-09-01 02:00:00,11421.0 -2014-09-01 03:00:00,10883.0 -2014-09-01 04:00:00,10455.0 -2014-09-01 05:00:00,10200.0 -2014-09-01 06:00:00,10100.0 -2014-09-01 07:00:00,10299.0 -2014-09-01 08:00:00,10408.0 -2014-09-01 09:00:00,10689.0 -2014-09-01 10:00:00,11343.0 -2014-09-01 11:00:00,12030.0 -2014-09-01 12:00:00,12492.0 -2014-09-01 13:00:00,12961.0 -2014-09-01 14:00:00,13622.0 -2014-09-01 15:00:00,14330.0 -2014-09-01 16:00:00,14869.0 -2014-09-01 17:00:00,15160.0 -2014-09-01 18:00:00,15270.0 -2014-09-01 19:00:00,15110.0 -2014-09-01 20:00:00,14639.0 -2014-09-01 21:00:00,14326.0 -2014-09-01 22:00:00,14236.0 -2014-09-01 23:00:00,13515.0 -2014-09-02 00:00:00,12511.0 -2014-08-31 01:00:00,11729.0 -2014-08-31 02:00:00,10952.0 -2014-08-31 03:00:00,10328.0 -2014-08-31 04:00:00,9900.0 -2014-08-31 05:00:00,9582.0 -2014-08-31 06:00:00,9486.0 -2014-08-31 07:00:00,9486.0 -2014-08-31 08:00:00,9476.0 -2014-08-31 09:00:00,9809.0 -2014-08-31 10:00:00,10735.0 -2014-08-31 11:00:00,11852.0 -2014-08-31 12:00:00,12905.0 -2014-08-31 13:00:00,13735.0 -2014-08-31 14:00:00,14393.0 -2014-08-31 15:00:00,14895.0 -2014-08-31 16:00:00,15363.0 -2014-08-31 17:00:00,15680.0 -2014-08-31 18:00:00,15774.0 -2014-08-31 19:00:00,15682.0 -2014-08-31 20:00:00,15154.0 -2014-08-31 21:00:00,14702.0 -2014-08-31 22:00:00,14516.0 -2014-08-31 23:00:00,13932.0 -2014-09-01 00:00:00,13051.0 -2014-08-30 01:00:00,13079.0 -2014-08-30 02:00:00,12107.0 -2014-08-30 03:00:00,11419.0 -2014-08-30 04:00:00,10912.0 -2014-08-30 05:00:00,10716.0 -2014-08-30 06:00:00,10548.0 -2014-08-30 07:00:00,10702.0 -2014-08-30 08:00:00,10835.0 -2014-08-30 09:00:00,11320.0 -2014-08-30 10:00:00,12084.0 -2014-08-30 11:00:00,12970.0 -2014-08-30 12:00:00,13522.0 -2014-08-30 13:00:00,13893.0 -2014-08-30 14:00:00,14359.0 -2014-08-30 15:00:00,14673.0 -2014-08-30 16:00:00,15004.0 -2014-08-30 17:00:00,15265.0 -2014-08-30 18:00:00,15323.0 -2014-08-30 19:00:00,15121.0 -2014-08-30 20:00:00,14622.0 -2014-08-30 21:00:00,14208.0 -2014-08-30 22:00:00,14057.0 -2014-08-30 23:00:00,13421.0 -2014-08-31 00:00:00,12616.0 -2014-08-29 01:00:00,11491.0 -2014-08-29 02:00:00,10778.0 -2014-08-29 03:00:00,10285.0 -2014-08-29 04:00:00,9965.0 -2014-08-29 05:00:00,9848.0 -2014-08-29 06:00:00,10007.0 -2014-08-29 07:00:00,10728.0 -2014-08-29 08:00:00,11803.0 -2014-08-29 09:00:00,12617.0 -2014-08-29 10:00:00,13266.0 -2014-08-29 11:00:00,14014.0 -2014-08-29 12:00:00,15063.0 -2014-08-29 13:00:00,15962.0 -2014-08-29 14:00:00,16606.0 -2014-08-29 15:00:00,17264.0 -2014-08-29 16:00:00,17629.0 -2014-08-29 17:00:00,17982.0 -2014-08-29 18:00:00,18139.0 -2014-08-29 19:00:00,17739.0 -2014-08-29 20:00:00,16957.0 -2014-08-29 21:00:00,16547.0 -2014-08-29 22:00:00,16153.0 -2014-08-29 23:00:00,15348.0 -2014-08-30 00:00:00,14213.0 -2014-08-28 01:00:00,11301.0 -2014-08-28 02:00:00,10511.0 -2014-08-28 03:00:00,9991.0 -2014-08-28 04:00:00,9673.0 -2014-08-28 05:00:00,9533.0 -2014-08-28 06:00:00,9646.0 -2014-08-28 07:00:00,10332.0 -2014-08-28 08:00:00,11216.0 -2014-08-28 09:00:00,11932.0 -2014-08-28 10:00:00,12639.0 -2014-08-28 11:00:00,13197.0 -2014-08-28 12:00:00,13750.0 -2014-08-28 13:00:00,14201.0 -2014-08-28 14:00:00,14550.0 -2014-08-28 15:00:00,15005.0 -2014-08-28 16:00:00,15231.0 -2014-08-28 17:00:00,15370.0 -2014-08-28 18:00:00,15247.0 -2014-08-28 19:00:00,14644.0 -2014-08-28 20:00:00,14024.0 -2014-08-28 21:00:00,14099.0 -2014-08-28 22:00:00,14072.0 -2014-08-28 23:00:00,13435.0 -2014-08-29 00:00:00,12478.0 -2014-08-27 01:00:00,12253.0 -2014-08-27 02:00:00,11365.0 -2014-08-27 03:00:00,10711.0 -2014-08-27 04:00:00,10315.0 -2014-08-27 05:00:00,10128.0 -2014-08-27 06:00:00,10249.0 -2014-08-27 07:00:00,10887.0 -2014-08-27 08:00:00,11818.0 -2014-08-27 09:00:00,12725.0 -2014-08-27 10:00:00,13600.0 -2014-08-27 11:00:00,14241.0 -2014-08-27 12:00:00,14906.0 -2014-08-27 13:00:00,15515.0 -2014-08-27 14:00:00,15993.0 -2014-08-27 15:00:00,16536.0 -2014-08-27 16:00:00,16837.0 -2014-08-27 17:00:00,16712.0 -2014-08-27 18:00:00,16279.0 -2014-08-27 19:00:00,15543.0 -2014-08-27 20:00:00,14695.0 -2014-08-27 21:00:00,14371.0 -2014-08-27 22:00:00,14309.0 -2014-08-27 23:00:00,13581.0 -2014-08-28 00:00:00,12427.0 -2014-08-26 01:00:00,12154.0 -2014-08-26 02:00:00,11317.0 -2014-08-26 03:00:00,10785.0 -2014-08-26 04:00:00,10400.0 -2014-08-26 05:00:00,10303.0 -2014-08-26 06:00:00,10455.0 -2014-08-26 07:00:00,11149.0 -2014-08-26 08:00:00,12207.0 -2014-08-26 09:00:00,13149.0 -2014-08-26 10:00:00,14022.0 -2014-08-26 11:00:00,14919.0 -2014-08-26 12:00:00,15347.0 -2014-08-26 13:00:00,15023.0 -2014-08-26 14:00:00,14954.0 -2014-08-26 15:00:00,15658.0 -2014-08-26 16:00:00,16398.0 -2014-08-26 17:00:00,17087.0 -2014-08-26 18:00:00,17515.0 -2014-08-26 19:00:00,17381.0 -2014-08-26 20:00:00,16782.0 -2014-08-26 21:00:00,16215.0 -2014-08-26 22:00:00,15959.0 -2014-08-26 23:00:00,14966.0 -2014-08-27 00:00:00,13604.0 -2014-08-25 01:00:00,13546.0 -2014-08-25 02:00:00,12667.0 -2014-08-25 03:00:00,12051.0 -2014-08-25 04:00:00,11774.0 -2014-08-25 05:00:00,11627.0 -2014-08-25 06:00:00,11845.0 -2014-08-25 07:00:00,12624.0 -2014-08-25 08:00:00,13750.0 -2014-08-25 09:00:00,14893.0 -2014-08-25 10:00:00,15974.0 -2014-08-25 11:00:00,17155.0 -2014-08-25 12:00:00,18276.0 -2014-08-25 13:00:00,18893.0 -2014-08-25 14:00:00,18476.0 -2014-08-25 15:00:00,17458.0 -2014-08-25 16:00:00,16526.0 -2014-08-25 17:00:00,16034.0 -2014-08-25 18:00:00,16040.0 -2014-08-25 19:00:00,15810.0 -2014-08-25 20:00:00,15413.0 -2014-08-25 21:00:00,15284.0 -2014-08-25 22:00:00,15247.0 -2014-08-25 23:00:00,14491.0 -2014-08-26 00:00:00,13360.0 -2014-08-24 01:00:00,11284.0 -2014-08-24 02:00:00,10606.0 -2014-08-24 03:00:00,10122.0 -2014-08-24 04:00:00,9794.0 -2014-08-24 05:00:00,9581.0 -2014-08-24 06:00:00,9545.0 -2014-08-24 07:00:00,9654.0 -2014-08-24 08:00:00,9659.0 -2014-08-24 09:00:00,9937.0 -2014-08-24 10:00:00,10795.0 -2014-08-24 11:00:00,11846.0 -2014-08-24 12:00:00,13042.0 -2014-08-24 13:00:00,14189.0 -2014-08-24 14:00:00,15108.0 -2014-08-24 15:00:00,15839.0 -2014-08-24 16:00:00,16502.0 -2014-08-24 17:00:00,16898.0 -2014-08-24 18:00:00,17129.0 -2014-08-24 19:00:00,17139.0 -2014-08-24 20:00:00,16767.0 -2014-08-24 21:00:00,16392.0 -2014-08-24 22:00:00,16362.0 -2014-08-24 23:00:00,15735.0 -2014-08-25 00:00:00,14626.0 -2014-08-23 01:00:00,13612.0 -2014-08-23 02:00:00,12593.0 -2014-08-23 03:00:00,11825.0 -2014-08-23 04:00:00,11331.0 -2014-08-23 05:00:00,10949.0 -2014-08-23 06:00:00,10824.0 -2014-08-23 07:00:00,10999.0 -2014-08-23 08:00:00,11209.0 -2014-08-23 09:00:00,11939.0 -2014-08-23 10:00:00,13124.0 -2014-08-23 11:00:00,14411.0 -2014-08-23 12:00:00,15389.0 -2014-08-23 13:00:00,16063.0 -2014-08-23 14:00:00,16148.0 -2014-08-23 15:00:00,15666.0 -2014-08-23 16:00:00,14642.0 -2014-08-23 17:00:00,13859.0 -2014-08-23 18:00:00,13389.0 -2014-08-23 19:00:00,13194.0 -2014-08-23 20:00:00,13004.0 -2014-08-23 21:00:00,12974.0 -2014-08-23 22:00:00,13203.0 -2014-08-23 23:00:00,12845.0 -2014-08-24 00:00:00,12099.0 -2014-08-22 01:00:00,12577.0 -2014-08-22 02:00:00,11745.0 -2014-08-22 03:00:00,11223.0 -2014-08-22 04:00:00,10848.0 -2014-08-22 05:00:00,10709.0 -2014-08-22 06:00:00,10840.0 -2014-08-22 07:00:00,11486.0 -2014-08-22 08:00:00,12440.0 -2014-08-22 09:00:00,13523.0 -2014-08-22 10:00:00,14468.0 -2014-08-22 11:00:00,15165.0 -2014-08-22 12:00:00,15747.0 -2014-08-22 13:00:00,15939.0 -2014-08-22 14:00:00,16391.0 -2014-08-22 15:00:00,17250.0 -2014-08-22 16:00:00,17732.0 -2014-08-22 17:00:00,18118.0 -2014-08-22 18:00:00,18253.0 -2014-08-22 19:00:00,17953.0 -2014-08-22 20:00:00,17290.0 -2014-08-22 21:00:00,16803.0 -2014-08-22 22:00:00,16603.0 -2014-08-22 23:00:00,15958.0 -2014-08-23 00:00:00,14843.0 -2014-08-21 01:00:00,13189.0 -2014-08-21 02:00:00,12169.0 -2014-08-21 03:00:00,11465.0 -2014-08-21 04:00:00,10958.0 -2014-08-21 05:00:00,10692.0 -2014-08-21 06:00:00,10852.0 -2014-08-21 07:00:00,11474.0 -2014-08-21 08:00:00,12576.0 -2014-08-21 09:00:00,13531.0 -2014-08-21 10:00:00,13913.0 -2014-08-21 11:00:00,14177.0 -2014-08-21 12:00:00,14619.0 -2014-08-21 13:00:00,15052.0 -2014-08-21 14:00:00,15264.0 -2014-08-21 15:00:00,15295.0 -2014-08-21 16:00:00,15437.0 -2014-08-21 17:00:00,15244.0 -2014-08-21 18:00:00,15437.0 -2014-08-21 19:00:00,15466.0 -2014-08-21 20:00:00,15258.0 -2014-08-21 21:00:00,15147.0 -2014-08-21 22:00:00,15284.0 -2014-08-21 23:00:00,14706.0 -2014-08-22 00:00:00,13736.0 -2014-08-20 01:00:00,12399.0 -2014-08-20 02:00:00,11390.0 -2014-08-20 03:00:00,10680.0 -2014-08-20 04:00:00,10228.0 -2014-08-20 05:00:00,9995.0 -2014-08-20 06:00:00,10067.0 -2014-08-20 07:00:00,10676.0 -2014-08-20 08:00:00,11495.0 -2014-08-20 09:00:00,12495.0 -2014-08-20 10:00:00,13452.0 -2014-08-20 11:00:00,14375.0 -2014-08-20 12:00:00,15378.0 -2014-08-20 13:00:00,16138.0 -2014-08-20 14:00:00,16729.0 -2014-08-20 15:00:00,17354.0 -2014-08-20 16:00:00,17759.0 -2014-08-20 17:00:00,18060.0 -2014-08-20 18:00:00,18150.0 -2014-08-20 19:00:00,17940.0 -2014-08-20 20:00:00,17337.0 -2014-08-20 21:00:00,16785.0 -2014-08-20 22:00:00,16731.0 -2014-08-20 23:00:00,15864.0 -2014-08-21 00:00:00,14496.0 -2014-08-19 01:00:00,12310.0 -2014-08-19 02:00:00,11404.0 -2014-08-19 03:00:00,10752.0 -2014-08-19 04:00:00,10363.0 -2014-08-19 05:00:00,10226.0 -2014-08-19 06:00:00,10374.0 -2014-08-19 07:00:00,11031.0 -2014-08-19 08:00:00,12055.0 -2014-08-19 09:00:00,12897.0 -2014-08-19 10:00:00,13809.0 -2014-08-19 11:00:00,14678.0 -2014-08-19 12:00:00,15387.0 -2014-08-19 13:00:00,15748.0 -2014-08-19 14:00:00,15791.0 -2014-08-19 15:00:00,16021.0 -2014-08-19 16:00:00,16456.0 -2014-08-19 17:00:00,16924.0 -2014-08-19 18:00:00,17227.0 -2014-08-19 19:00:00,17139.0 -2014-08-19 20:00:00,16528.0 -2014-08-19 21:00:00,15960.0 -2014-08-19 22:00:00,15920.0 -2014-08-19 23:00:00,15073.0 -2014-08-20 00:00:00,13754.0 -2014-08-18 01:00:00,9937.0 -2014-08-18 02:00:00,9435.0 -2014-08-18 03:00:00,9084.0 -2014-08-18 04:00:00,8909.0 -2014-08-18 05:00:00,8858.0 -2014-08-18 06:00:00,9109.0 -2014-08-18 07:00:00,9842.0 -2014-08-18 08:00:00,10797.0 -2014-08-18 09:00:00,11605.0 -2014-08-18 10:00:00,12192.0 -2014-08-18 11:00:00,12592.0 -2014-08-18 12:00:00,13027.0 -2014-08-18 13:00:00,13577.0 -2014-08-18 14:00:00,14357.0 -2014-08-18 15:00:00,15132.0 -2014-08-18 16:00:00,15731.0 -2014-08-18 17:00:00,16138.0 -2014-08-18 18:00:00,16344.0 -2014-08-18 19:00:00,16209.0 -2014-08-18 20:00:00,15745.0 -2014-08-18 21:00:00,15348.0 -2014-08-18 22:00:00,15392.0 -2014-08-18 23:00:00,14711.0 -2014-08-19 00:00:00,13542.0 -2014-08-17 01:00:00,10942.0 -2014-08-17 02:00:00,10200.0 -2014-08-17 03:00:00,9615.0 -2014-08-17 04:00:00,9201.0 -2014-08-17 05:00:00,8940.0 -2014-08-17 06:00:00,8814.0 -2014-08-17 07:00:00,8856.0 -2014-08-17 08:00:00,8792.0 -2014-08-17 09:00:00,9104.0 -2014-08-17 10:00:00,9659.0 -2014-08-17 11:00:00,10196.0 -2014-08-17 12:00:00,10627.0 -2014-08-17 13:00:00,10960.0 -2014-08-17 14:00:00,11253.0 -2014-08-17 15:00:00,11436.0 -2014-08-17 16:00:00,11534.0 -2014-08-17 17:00:00,11612.0 -2014-08-17 18:00:00,11697.0 -2014-08-17 19:00:00,11604.0 -2014-08-17 20:00:00,11435.0 -2014-08-17 21:00:00,11349.0 -2014-08-17 22:00:00,11595.0 -2014-08-17 23:00:00,11236.0 -2014-08-18 00:00:00,10616.0 -2014-08-16 01:00:00,10360.0 -2014-08-16 02:00:00,9726.0 -2014-08-16 03:00:00,9191.0 -2014-08-16 04:00:00,8860.0 -2014-08-16 05:00:00,8643.0 -2014-08-16 06:00:00,8626.0 -2014-08-16 07:00:00,8840.0 -2014-08-16 08:00:00,8941.0 -2014-08-16 09:00:00,9540.0 -2014-08-16 10:00:00,10378.0 -2014-08-16 11:00:00,11147.0 -2014-08-16 12:00:00,11827.0 -2014-08-16 13:00:00,12302.0 -2014-08-16 14:00:00,12662.0 -2014-08-16 15:00:00,12871.0 -2014-08-16 16:00:00,13167.0 -2014-08-16 17:00:00,13373.0 -2014-08-16 18:00:00,13511.0 -2014-08-16 19:00:00,13415.0 -2014-08-16 20:00:00,13061.0 -2014-08-16 21:00:00,12781.0 -2014-08-16 22:00:00,12916.0 -2014-08-16 23:00:00,12484.0 -2014-08-17 00:00:00,11738.0 -2014-08-15 01:00:00,9782.0 -2014-08-15 02:00:00,9186.0 -2014-08-15 03:00:00,8750.0 -2014-08-15 04:00:00,8475.0 -2014-08-15 05:00:00,8374.0 -2014-08-15 06:00:00,8500.0 -2014-08-15 07:00:00,9000.0 -2014-08-15 08:00:00,9546.0 -2014-08-15 09:00:00,10385.0 -2014-08-15 10:00:00,11072.0 -2014-08-15 11:00:00,11577.0 -2014-08-15 12:00:00,12001.0 -2014-08-15 13:00:00,12285.0 -2014-08-15 14:00:00,12541.0 -2014-08-15 15:00:00,12873.0 -2014-08-15 16:00:00,13084.0 -2014-08-15 17:00:00,13293.0 -2014-08-15 18:00:00,13404.0 -2014-08-15 19:00:00,13256.0 -2014-08-15 20:00:00,12830.0 -2014-08-15 21:00:00,12421.0 -2014-08-15 22:00:00,12520.0 -2014-08-15 23:00:00,12104.0 -2014-08-16 00:00:00,11267.0 -2014-08-14 01:00:00,10564.0 -2014-08-14 02:00:00,9855.0 -2014-08-14 03:00:00,9378.0 -2014-08-14 04:00:00,9049.0 -2014-08-14 05:00:00,8899.0 -2014-08-14 06:00:00,8988.0 -2014-08-14 07:00:00,9495.0 -2014-08-14 08:00:00,10123.0 -2014-08-14 09:00:00,10916.0 -2014-08-14 10:00:00,11579.0 -2014-08-14 11:00:00,12040.0 -2014-08-14 12:00:00,12456.0 -2014-08-14 13:00:00,12725.0 -2014-08-14 14:00:00,12882.0 -2014-08-14 15:00:00,13131.0 -2014-08-14 16:00:00,13241.0 -2014-08-14 17:00:00,13346.0 -2014-08-14 18:00:00,13268.0 -2014-08-14 19:00:00,12921.0 -2014-08-14 20:00:00,12295.0 -2014-08-14 21:00:00,11823.0 -2014-08-14 22:00:00,11974.0 -2014-08-14 23:00:00,11482.0 -2014-08-15 00:00:00,10679.0 -2014-08-13 01:00:00,9940.0 -2014-08-13 02:00:00,9318.0 -2014-08-13 03:00:00,8928.0 -2014-08-13 04:00:00,8639.0 -2014-08-13 05:00:00,8506.0 -2014-08-13 06:00:00,8640.0 -2014-08-13 07:00:00,9137.0 -2014-08-13 08:00:00,9791.0 -2014-08-13 09:00:00,10672.0 -2014-08-13 10:00:00,11393.0 -2014-08-13 11:00:00,11902.0 -2014-08-13 12:00:00,12414.0 -2014-08-13 13:00:00,12916.0 -2014-08-13 14:00:00,13359.0 -2014-08-13 15:00:00,13852.0 -2014-08-13 16:00:00,14102.0 -2014-08-13 17:00:00,14218.0 -2014-08-13 18:00:00,14194.0 -2014-08-13 19:00:00,13953.0 -2014-08-13 20:00:00,13423.0 -2014-08-13 21:00:00,12984.0 -2014-08-13 22:00:00,13056.0 -2014-08-13 23:00:00,12556.0 -2014-08-14 00:00:00,11559.0 -2014-08-12 01:00:00,11747.0 -2014-08-12 02:00:00,10829.0 -2014-08-12 03:00:00,10246.0 -2014-08-12 04:00:00,9858.0 -2014-08-12 05:00:00,9705.0 -2014-08-12 06:00:00,9756.0 -2014-08-12 07:00:00,10332.0 -2014-08-12 08:00:00,11105.0 -2014-08-12 09:00:00,11622.0 -2014-08-12 10:00:00,11949.0 -2014-08-12 11:00:00,12121.0 -2014-08-12 12:00:00,12271.0 -2014-08-12 13:00:00,12268.0 -2014-08-12 14:00:00,12259.0 -2014-08-12 15:00:00,12270.0 -2014-08-12 16:00:00,12158.0 -2014-08-12 17:00:00,12027.0 -2014-08-12 18:00:00,11948.0 -2014-08-12 19:00:00,11792.0 -2014-08-12 20:00:00,11627.0 -2014-08-12 21:00:00,11564.0 -2014-08-12 22:00:00,11869.0 -2014-08-12 23:00:00,11485.0 -2014-08-13 00:00:00,10753.0 -2014-08-11 01:00:00,11075.0 -2014-08-11 02:00:00,10450.0 -2014-08-11 03:00:00,9964.0 -2014-08-11 04:00:00,9685.0 -2014-08-11 05:00:00,9605.0 -2014-08-11 06:00:00,9864.0 -2014-08-11 07:00:00,10580.0 -2014-08-11 08:00:00,11469.0 -2014-08-11 09:00:00,12261.0 -2014-08-11 10:00:00,12899.0 -2014-08-11 11:00:00,13523.0 -2014-08-11 12:00:00,14158.0 -2014-08-11 13:00:00,14656.0 -2014-08-11 14:00:00,15064.0 -2014-08-11 15:00:00,15404.0 -2014-08-11 16:00:00,15733.0 -2014-08-11 17:00:00,16099.0 -2014-08-11 18:00:00,16294.0 -2014-08-11 19:00:00,16110.0 -2014-08-11 20:00:00,15398.0 -2014-08-11 21:00:00,14855.0 -2014-08-11 22:00:00,14771.0 -2014-08-11 23:00:00,14110.0 -2014-08-12 00:00:00,12966.0 -2014-08-10 01:00:00,10750.0 -2014-08-10 02:00:00,10139.0 -2014-08-10 03:00:00,9669.0 -2014-08-10 04:00:00,9312.0 -2014-08-10 05:00:00,9140.0 -2014-08-10 06:00:00,8975.0 -2014-08-10 07:00:00,8933.0 -2014-08-10 08:00:00,8860.0 -2014-08-10 09:00:00,9180.0 -2014-08-10 10:00:00,9910.0 -2014-08-10 11:00:00,10818.0 -2014-08-10 12:00:00,11605.0 -2014-08-10 13:00:00,12250.0 -2014-08-10 14:00:00,12741.0 -2014-08-10 15:00:00,13129.0 -2014-08-10 16:00:00,13340.0 -2014-08-10 17:00:00,13368.0 -2014-08-10 18:00:00,13287.0 -2014-08-10 19:00:00,13053.0 -2014-08-10 20:00:00,12754.0 -2014-08-10 21:00:00,12650.0 -2014-08-10 22:00:00,12889.0 -2014-08-10 23:00:00,12591.0 -2014-08-11 00:00:00,11903.0 -2014-08-09 01:00:00,11412.0 -2014-08-09 02:00:00,10628.0 -2014-08-09 03:00:00,10073.0 -2014-08-09 04:00:00,9579.0 -2014-08-09 05:00:00,9369.0 -2014-08-09 06:00:00,9209.0 -2014-08-09 07:00:00,9309.0 -2014-08-09 08:00:00,9396.0 -2014-08-09 09:00:00,10061.0 -2014-08-09 10:00:00,10971.0 -2014-08-09 11:00:00,11833.0 -2014-08-09 12:00:00,12612.0 -2014-08-09 13:00:00,13133.0 -2014-08-09 14:00:00,13450.0 -2014-08-09 15:00:00,13556.0 -2014-08-09 16:00:00,13699.0 -2014-08-09 17:00:00,13801.0 -2014-08-09 18:00:00,13852.0 -2014-08-09 19:00:00,13574.0 -2014-08-09 20:00:00,13116.0 -2014-08-09 21:00:00,12494.0 -2014-08-09 22:00:00,12533.0 -2014-08-09 23:00:00,12182.0 -2014-08-10 00:00:00,11475.0 -2014-08-08 01:00:00,11238.0 -2014-08-08 02:00:00,10441.0 -2014-08-08 03:00:00,9877.0 -2014-08-08 04:00:00,9514.0 -2014-08-08 05:00:00,9348.0 -2014-08-08 06:00:00,9456.0 -2014-08-08 07:00:00,10033.0 -2014-08-08 08:00:00,10803.0 -2014-08-08 09:00:00,11680.0 -2014-08-08 10:00:00,12512.0 -2014-08-08 11:00:00,13127.0 -2014-08-08 12:00:00,13728.0 -2014-08-08 13:00:00,14269.0 -2014-08-08 14:00:00,14679.0 -2014-08-08 15:00:00,15125.0 -2014-08-08 16:00:00,15441.0 -2014-08-08 17:00:00,15620.0 -2014-08-08 18:00:00,15541.0 -2014-08-08 19:00:00,15140.0 -2014-08-08 20:00:00,14405.0 -2014-08-08 21:00:00,13730.0 -2014-08-08 22:00:00,13684.0 -2014-08-08 23:00:00,13242.0 -2014-08-09 00:00:00,12376.0 -2014-08-07 01:00:00,10829.0 -2014-08-07 02:00:00,10056.0 -2014-08-07 03:00:00,9556.0 -2014-08-07 04:00:00,9244.0 -2014-08-07 05:00:00,9116.0 -2014-08-07 06:00:00,9273.0 -2014-08-07 07:00:00,9809.0 -2014-08-07 08:00:00,10517.0 -2014-08-07 09:00:00,11405.0 -2014-08-07 10:00:00,12199.0 -2014-08-07 11:00:00,12689.0 -2014-08-07 12:00:00,13367.0 -2014-08-07 13:00:00,13963.0 -2014-08-07 14:00:00,14376.0 -2014-08-07 15:00:00,14771.0 -2014-08-07 16:00:00,15120.0 -2014-08-07 17:00:00,15303.0 -2014-08-07 18:00:00,15256.0 -2014-08-07 19:00:00,15019.0 -2014-08-07 20:00:00,14427.0 -2014-08-07 21:00:00,13782.0 -2014-08-07 22:00:00,13697.0 -2014-08-07 23:00:00,13227.0 -2014-08-08 00:00:00,12237.0 -2014-08-06 01:00:00,11275.0 -2014-08-06 02:00:00,10447.0 -2014-08-06 03:00:00,9855.0 -2014-08-06 04:00:00,9495.0 -2014-08-06 05:00:00,9272.0 -2014-08-06 06:00:00,9402.0 -2014-08-06 07:00:00,9909.0 -2014-08-06 08:00:00,10615.0 -2014-08-06 09:00:00,11492.0 -2014-08-06 10:00:00,12274.0 -2014-08-06 11:00:00,12951.0 -2014-08-06 12:00:00,13687.0 -2014-08-06 13:00:00,14220.0 -2014-08-06 14:00:00,14676.0 -2014-08-06 15:00:00,15083.0 -2014-08-06 16:00:00,15291.0 -2014-08-06 17:00:00,15319.0 -2014-08-06 18:00:00,15059.0 -2014-08-06 19:00:00,14615.0 -2014-08-06 20:00:00,13819.0 -2014-08-06 21:00:00,13313.0 -2014-08-06 22:00:00,13317.0 -2014-08-06 23:00:00,12830.0 -2014-08-07 00:00:00,11849.0 -2014-08-05 01:00:00,11769.0 -2014-08-05 02:00:00,10900.0 -2014-08-05 03:00:00,10367.0 -2014-08-05 04:00:00,9957.0 -2014-08-05 05:00:00,9785.0 -2014-08-05 06:00:00,9904.0 -2014-08-05 07:00:00,10532.0 -2014-08-05 08:00:00,11322.0 -2014-08-05 09:00:00,12188.0 -2014-08-05 10:00:00,12788.0 -2014-08-05 11:00:00,13263.0 -2014-08-05 12:00:00,13744.0 -2014-08-05 13:00:00,14293.0 -2014-08-05 14:00:00,14826.0 -2014-08-05 15:00:00,15327.0 -2014-08-05 16:00:00,15689.0 -2014-08-05 17:00:00,15941.0 -2014-08-05 18:00:00,15913.0 -2014-08-05 19:00:00,15525.0 -2014-08-05 20:00:00,14845.0 -2014-08-05 21:00:00,14169.0 -2014-08-05 22:00:00,14052.0 -2014-08-05 23:00:00,13490.0 -2014-08-06 00:00:00,12423.0 -2014-08-04 01:00:00,11446.0 -2014-08-04 02:00:00,10595.0 -2014-08-04 03:00:00,10063.0 -2014-08-04 04:00:00,9710.0 -2014-08-04 05:00:00,9567.0 -2014-08-04 06:00:00,9703.0 -2014-08-04 07:00:00,10265.0 -2014-08-04 08:00:00,11051.0 -2014-08-04 09:00:00,12122.0 -2014-08-04 10:00:00,13015.0 -2014-08-04 11:00:00,13814.0 -2014-08-04 12:00:00,14774.0 -2014-08-04 13:00:00,15598.0 -2014-08-04 14:00:00,16237.0 -2014-08-04 15:00:00,16736.0 -2014-08-04 16:00:00,16994.0 -2014-08-04 17:00:00,17026.0 -2014-08-04 18:00:00,16792.0 -2014-08-04 19:00:00,16194.0 -2014-08-04 20:00:00,15418.0 -2014-08-04 21:00:00,14893.0 -2014-08-04 22:00:00,14698.0 -2014-08-04 23:00:00,14005.0 -2014-08-05 00:00:00,12876.0 -2014-08-03 01:00:00,10815.0 -2014-08-03 02:00:00,10007.0 -2014-08-03 03:00:00,9494.0 -2014-08-03 04:00:00,9056.0 -2014-08-03 05:00:00,8811.0 -2014-08-03 06:00:00,8640.0 -2014-08-03 07:00:00,8620.0 -2014-08-03 08:00:00,8495.0 -2014-08-03 09:00:00,9023.0 -2014-08-03 10:00:00,9891.0 -2014-08-03 11:00:00,10949.0 -2014-08-03 12:00:00,12014.0 -2014-08-03 13:00:00,12856.0 -2014-08-03 14:00:00,13474.0 -2014-08-03 15:00:00,13880.0 -2014-08-03 16:00:00,14206.0 -2014-08-03 17:00:00,14431.0 -2014-08-03 18:00:00,14589.0 -2014-08-03 19:00:00,14549.0 -2014-08-03 20:00:00,14256.0 -2014-08-03 21:00:00,13793.0 -2014-08-03 22:00:00,13781.0 -2014-08-03 23:00:00,13365.0 -2014-08-04 00:00:00,12437.0 -2014-08-02 01:00:00,10768.0 -2014-08-02 02:00:00,9992.0 -2014-08-02 03:00:00,9445.0 -2014-08-02 04:00:00,9063.0 -2014-08-02 05:00:00,8841.0 -2014-08-02 06:00:00,8768.0 -2014-08-02 07:00:00,8892.0 -2014-08-02 08:00:00,8964.0 -2014-08-02 09:00:00,9590.0 -2014-08-02 10:00:00,10328.0 -2014-08-02 11:00:00,11125.0 -2014-08-02 12:00:00,11786.0 -2014-08-02 13:00:00,12370.0 -2014-08-02 14:00:00,12766.0 -2014-08-02 15:00:00,13204.0 -2014-08-02 16:00:00,13467.0 -2014-08-02 17:00:00,13926.0 -2014-08-02 18:00:00,14174.0 -2014-08-02 19:00:00,14059.0 -2014-08-02 20:00:00,13645.0 -2014-08-02 21:00:00,12994.0 -2014-08-02 22:00:00,12790.0 -2014-08-02 23:00:00,12447.0 -2014-08-03 00:00:00,11624.0 -2014-08-01 01:00:00,11496.0 -2014-08-01 02:00:00,10634.0 -2014-08-01 03:00:00,10005.0 -2014-08-01 04:00:00,9620.0 -2014-08-01 05:00:00,9385.0 -2014-08-01 06:00:00,9439.0 -2014-08-01 07:00:00,9933.0 -2014-08-01 08:00:00,10578.0 -2014-08-01 09:00:00,11538.0 -2014-08-01 10:00:00,12335.0 -2014-08-01 11:00:00,13022.0 -2014-08-01 12:00:00,13875.0 -2014-08-01 13:00:00,14529.0 -2014-08-01 14:00:00,14889.0 -2014-08-01 15:00:00,15152.0 -2014-08-01 16:00:00,15230.0 -2014-08-01 17:00:00,15125.0 -2014-08-01 18:00:00,14836.0 -2014-08-01 19:00:00,14333.0 -2014-08-01 20:00:00,13732.0 -2014-08-01 21:00:00,13188.0 -2014-08-01 22:00:00,13035.0 -2014-08-01 23:00:00,12647.0 -2014-08-02 00:00:00,11696.0 -2014-07-31 01:00:00,10583.0 -2014-07-31 02:00:00,9832.0 -2014-07-31 03:00:00,9344.0 -2014-07-31 04:00:00,9014.0 -2014-07-31 05:00:00,8858.0 -2014-07-31 06:00:00,8988.0 -2014-07-31 07:00:00,9508.0 -2014-07-31 08:00:00,10150.0 -2014-07-31 09:00:00,10983.0 -2014-07-31 10:00:00,11729.0 -2014-07-31 11:00:00,12428.0 -2014-07-31 12:00:00,13149.0 -2014-07-31 13:00:00,13778.0 -2014-07-31 14:00:00,14339.0 -2014-07-31 15:00:00,14828.0 -2014-07-31 16:00:00,15095.0 -2014-07-31 17:00:00,15283.0 -2014-07-31 18:00:00,15322.0 -2014-07-31 19:00:00,15141.0 -2014-07-31 20:00:00,14632.0 -2014-07-31 21:00:00,14087.0 -2014-07-31 22:00:00,14048.0 -2014-07-31 23:00:00,13641.0 -2014-08-01 00:00:00,12625.0 -2014-07-30 01:00:00,10469.0 -2014-07-30 02:00:00,9747.0 -2014-07-30 03:00:00,9251.0 -2014-07-30 04:00:00,8927.0 -2014-07-30 05:00:00,8768.0 -2014-07-30 06:00:00,8890.0 -2014-07-30 07:00:00,9402.0 -2014-07-30 08:00:00,10079.0 -2014-07-30 09:00:00,10996.0 -2014-07-30 10:00:00,11714.0 -2014-07-30 11:00:00,12310.0 -2014-07-30 12:00:00,12905.0 -2014-07-30 13:00:00,13311.0 -2014-07-30 14:00:00,13677.0 -2014-07-30 15:00:00,14027.0 -2014-07-30 16:00:00,14231.0 -2014-07-30 17:00:00,14248.0 -2014-07-30 18:00:00,14143.0 -2014-07-30 19:00:00,13892.0 -2014-07-30 20:00:00,13347.0 -2014-07-30 21:00:00,12870.0 -2014-07-30 22:00:00,12817.0 -2014-07-30 23:00:00,12488.0 -2014-07-31 00:00:00,11575.0 -2014-07-29 01:00:00,9942.0 -2014-07-29 02:00:00,9331.0 -2014-07-29 03:00:00,8965.0 -2014-07-29 04:00:00,8710.0 -2014-07-29 05:00:00,8590.0 -2014-07-29 06:00:00,8759.0 -2014-07-29 07:00:00,9254.0 -2014-07-29 08:00:00,9910.0 -2014-07-29 09:00:00,10734.0 -2014-07-29 10:00:00,11450.0 -2014-07-29 11:00:00,11983.0 -2014-07-29 12:00:00,12585.0 -2014-07-29 13:00:00,12990.0 -2014-07-29 14:00:00,13356.0 -2014-07-29 15:00:00,13663.0 -2014-07-29 16:00:00,13806.0 -2014-07-29 17:00:00,13764.0 -2014-07-29 18:00:00,13644.0 -2014-07-29 19:00:00,13443.0 -2014-07-29 20:00:00,13057.0 -2014-07-29 21:00:00,12684.0 -2014-07-29 22:00:00,12783.0 -2014-07-29 23:00:00,12479.0 -2014-07-30 00:00:00,11525.0 -2014-07-28 01:00:00,10503.0 -2014-07-28 02:00:00,9798.0 -2014-07-28 03:00:00,9320.0 -2014-07-28 04:00:00,8949.0 -2014-07-28 05:00:00,8811.0 -2014-07-28 06:00:00,8945.0 -2014-07-28 07:00:00,9449.0 -2014-07-28 08:00:00,10015.0 -2014-07-28 09:00:00,10915.0 -2014-07-28 10:00:00,11473.0 -2014-07-28 11:00:00,11899.0 -2014-07-28 12:00:00,12245.0 -2014-07-28 13:00:00,12438.0 -2014-07-28 14:00:00,12545.0 -2014-07-28 15:00:00,12657.0 -2014-07-28 16:00:00,12716.0 -2014-07-28 17:00:00,12675.0 -2014-07-28 18:00:00,12567.0 -2014-07-28 19:00:00,12382.0 -2014-07-28 20:00:00,11997.0 -2014-07-28 21:00:00,11674.0 -2014-07-28 22:00:00,11840.0 -2014-07-28 23:00:00,11568.0 -2014-07-29 00:00:00,10830.0 -2014-07-27 01:00:00,12092.0 -2014-07-27 02:00:00,11332.0 -2014-07-27 03:00:00,10611.0 -2014-07-27 04:00:00,10141.0 -2014-07-27 05:00:00,9813.0 -2014-07-27 06:00:00,9680.0 -2014-07-27 07:00:00,9550.0 -2014-07-27 08:00:00,9578.0 -2014-07-27 09:00:00,10202.0 -2014-07-27 10:00:00,11360.0 -2014-07-27 11:00:00,12611.0 -2014-07-27 12:00:00,13779.0 -2014-07-27 13:00:00,14492.0 -2014-07-27 14:00:00,14763.0 -2014-07-27 15:00:00,14878.0 -2014-07-27 16:00:00,14808.0 -2014-07-27 17:00:00,14612.0 -2014-07-27 18:00:00,14283.0 -2014-07-27 19:00:00,13836.0 -2014-07-27 20:00:00,13284.0 -2014-07-27 21:00:00,12600.0 -2014-07-27 22:00:00,12421.0 -2014-07-27 23:00:00,12196.0 -2014-07-28 00:00:00,11434.0 -2014-07-26 01:00:00,10646.0 -2014-07-26 02:00:00,9925.0 -2014-07-26 03:00:00,9513.0 -2014-07-26 04:00:00,9176.0 -2014-07-26 05:00:00,9022.0 -2014-07-26 06:00:00,8978.0 -2014-07-26 07:00:00,9143.0 -2014-07-26 08:00:00,9339.0 -2014-07-26 09:00:00,9876.0 -2014-07-26 10:00:00,10642.0 -2014-07-26 11:00:00,11456.0 -2014-07-26 12:00:00,12311.0 -2014-07-26 13:00:00,12803.0 -2014-07-26 14:00:00,13344.0 -2014-07-26 15:00:00,13839.0 -2014-07-26 16:00:00,14116.0 -2014-07-26 17:00:00,14183.0 -2014-07-26 18:00:00,14274.0 -2014-07-26 19:00:00,14208.0 -2014-07-26 20:00:00,13946.0 -2014-07-26 21:00:00,13672.0 -2014-07-26 22:00:00,13814.0 -2014-07-26 23:00:00,13657.0 -2014-07-27 00:00:00,12997.0 -2014-07-25 01:00:00,10203.0 -2014-07-25 02:00:00,9576.0 -2014-07-25 03:00:00,9114.0 -2014-07-25 04:00:00,8813.0 -2014-07-25 05:00:00,8679.0 -2014-07-25 06:00:00,8802.0 -2014-07-25 07:00:00,9165.0 -2014-07-25 08:00:00,9839.0 -2014-07-25 09:00:00,10634.0 -2014-07-25 10:00:00,11260.0 -2014-07-25 11:00:00,11745.0 -2014-07-25 12:00:00,12050.0 -2014-07-25 13:00:00,12286.0 -2014-07-25 14:00:00,12441.0 -2014-07-25 15:00:00,12560.0 -2014-07-25 16:00:00,12664.0 -2014-07-25 17:00:00,12581.0 -2014-07-25 18:00:00,12491.0 -2014-07-25 19:00:00,12339.0 -2014-07-25 20:00:00,12248.0 -2014-07-25 21:00:00,11967.0 -2014-07-25 22:00:00,12126.0 -2014-07-25 23:00:00,12062.0 -2014-07-26 00:00:00,11404.0 -2014-07-24 01:00:00,10144.0 -2014-07-24 02:00:00,9509.0 -2014-07-24 03:00:00,9093.0 -2014-07-24 04:00:00,8814.0 -2014-07-24 05:00:00,8695.0 -2014-07-24 06:00:00,8807.0 -2014-07-24 07:00:00,9222.0 -2014-07-24 08:00:00,9920.0 -2014-07-24 09:00:00,10882.0 -2014-07-24 10:00:00,11608.0 -2014-07-24 11:00:00,12056.0 -2014-07-24 12:00:00,12468.0 -2014-07-24 13:00:00,12684.0 -2014-07-24 14:00:00,12911.0 -2014-07-24 15:00:00,13160.0 -2014-07-24 16:00:00,13318.0 -2014-07-24 17:00:00,13415.0 -2014-07-24 18:00:00,13463.0 -2014-07-24 19:00:00,13370.0 -2014-07-24 20:00:00,12910.0 -2014-07-24 21:00:00,12303.0 -2014-07-24 22:00:00,12259.0 -2014-07-24 23:00:00,11950.0 -2014-07-25 00:00:00,11116.0 -2014-07-23 01:00:00,15069.0 -2014-07-23 02:00:00,13577.0 -2014-07-23 03:00:00,12397.0 -2014-07-23 04:00:00,11391.0 -2014-07-23 05:00:00,10750.0 -2014-07-23 06:00:00,10575.0 -2014-07-23 07:00:00,10801.0 -2014-07-23 08:00:00,11342.0 -2014-07-23 09:00:00,12199.0 -2014-07-23 10:00:00,12875.0 -2014-07-23 11:00:00,13268.0 -2014-07-23 12:00:00,13629.0 -2014-07-23 13:00:00,13821.0 -2014-07-23 14:00:00,13899.0 -2014-07-23 15:00:00,14022.0 -2014-07-23 16:00:00,13998.0 -2014-07-23 17:00:00,14011.0 -2014-07-23 18:00:00,13915.0 -2014-07-23 19:00:00,13583.0 -2014-07-23 20:00:00,12919.0 -2014-07-23 21:00:00,12291.0 -2014-07-23 22:00:00,12120.0 -2014-07-23 23:00:00,11869.0 -2014-07-24 00:00:00,11051.0 -2014-07-22 01:00:00,13296.0 -2014-07-22 02:00:00,12233.0 -2014-07-22 03:00:00,11414.0 -2014-07-22 04:00:00,10850.0 -2014-07-22 05:00:00,10587.0 -2014-07-22 06:00:00,10628.0 -2014-07-22 07:00:00,11094.0 -2014-07-22 08:00:00,11921.0 -2014-07-22 09:00:00,13166.0 -2014-07-22 10:00:00,14300.0 -2014-07-22 11:00:00,15334.0 -2014-07-22 12:00:00,16404.0 -2014-07-22 13:00:00,17276.0 -2014-07-22 14:00:00,18019.0 -2014-07-22 15:00:00,18783.0 -2014-07-22 16:00:00,19359.0 -2014-07-22 17:00:00,19721.0 -2014-07-22 18:00:00,19673.0 -2014-07-22 19:00:00,19497.0 -2014-07-22 20:00:00,19145.0 -2014-07-22 21:00:00,18658.0 -2014-07-22 22:00:00,18486.0 -2014-07-22 23:00:00,17952.0 -2014-07-23 00:00:00,16665.0 -2014-07-21 01:00:00,11131.0 -2014-07-21 02:00:00,10343.0 -2014-07-21 03:00:00,9801.0 -2014-07-21 04:00:00,9464.0 -2014-07-21 05:00:00,9302.0 -2014-07-21 06:00:00,9508.0 -2014-07-21 07:00:00,10063.0 -2014-07-21 08:00:00,10948.0 -2014-07-21 09:00:00,12055.0 -2014-07-21 10:00:00,13068.0 -2014-07-21 11:00:00,13990.0 -2014-07-21 12:00:00,14857.0 -2014-07-21 13:00:00,15560.0 -2014-07-21 14:00:00,16254.0 -2014-07-21 15:00:00,16937.0 -2014-07-21 16:00:00,17468.0 -2014-07-21 17:00:00,17843.0 -2014-07-21 18:00:00,18066.0 -2014-07-21 19:00:00,18017.0 -2014-07-21 20:00:00,17570.0 -2014-07-21 21:00:00,16862.0 -2014-07-21 22:00:00,16426.0 -2014-07-21 23:00:00,15932.0 -2014-07-22 00:00:00,14677.0 -2014-07-20 01:00:00,10438.0 -2014-07-20 02:00:00,9711.0 -2014-07-20 03:00:00,9229.0 -2014-07-20 04:00:00,8842.0 -2014-07-20 05:00:00,8621.0 -2014-07-20 06:00:00,8516.0 -2014-07-20 07:00:00,8412.0 -2014-07-20 08:00:00,8342.0 -2014-07-20 09:00:00,8781.0 -2014-07-20 10:00:00,9500.0 -2014-07-20 11:00:00,10271.0 -2014-07-20 12:00:00,11134.0 -2014-07-20 13:00:00,11822.0 -2014-07-20 14:00:00,12308.0 -2014-07-20 15:00:00,12764.0 -2014-07-20 16:00:00,13072.0 -2014-07-20 17:00:00,13398.0 -2014-07-20 18:00:00,13611.0 -2014-07-20 19:00:00,13690.0 -2014-07-20 20:00:00,13478.0 -2014-07-20 21:00:00,13060.0 -2014-07-20 22:00:00,12968.0 -2014-07-20 23:00:00,12869.0 -2014-07-21 00:00:00,12082.0 -2014-07-19 01:00:00,10712.0 -2014-07-19 02:00:00,9964.0 -2014-07-19 03:00:00,9405.0 -2014-07-19 04:00:00,9025.0 -2014-07-19 05:00:00,8792.0 -2014-07-19 06:00:00,8715.0 -2014-07-19 07:00:00,8739.0 -2014-07-19 08:00:00,8854.0 -2014-07-19 09:00:00,9500.0 -2014-07-19 10:00:00,10325.0 -2014-07-19 11:00:00,11112.0 -2014-07-19 12:00:00,11732.0 -2014-07-19 13:00:00,12205.0 -2014-07-19 14:00:00,12469.0 -2014-07-19 15:00:00,12619.0 -2014-07-19 16:00:00,12777.0 -2014-07-19 17:00:00,12961.0 -2014-07-19 18:00:00,13023.0 -2014-07-19 19:00:00,12908.0 -2014-07-19 20:00:00,12484.0 -2014-07-19 21:00:00,12080.0 -2014-07-19 22:00:00,12004.0 -2014-07-19 23:00:00,11867.0 -2014-07-20 00:00:00,11195.0 -2014-07-18 01:00:00,10347.0 -2014-07-18 02:00:00,9684.0 -2014-07-18 03:00:00,9207.0 -2014-07-18 04:00:00,8951.0 -2014-07-18 05:00:00,8811.0 -2014-07-18 06:00:00,8936.0 -2014-07-18 07:00:00,9341.0 -2014-07-18 08:00:00,10010.0 -2014-07-18 09:00:00,10908.0 -2014-07-18 10:00:00,11680.0 -2014-07-18 11:00:00,12362.0 -2014-07-18 12:00:00,12968.0 -2014-07-18 13:00:00,13342.0 -2014-07-18 14:00:00,13601.0 -2014-07-18 15:00:00,13904.0 -2014-07-18 16:00:00,14080.0 -2014-07-18 17:00:00,14165.0 -2014-07-18 18:00:00,14128.0 -2014-07-18 19:00:00,13908.0 -2014-07-18 20:00:00,13387.0 -2014-07-18 21:00:00,12860.0 -2014-07-18 22:00:00,12703.0 -2014-07-18 23:00:00,12435.0 -2014-07-19 00:00:00,11623.0 -2014-07-17 01:00:00,9830.0 -2014-07-17 02:00:00,9185.0 -2014-07-17 03:00:00,8773.0 -2014-07-17 04:00:00,8499.0 -2014-07-17 05:00:00,8420.0 -2014-07-17 06:00:00,8538.0 -2014-07-17 07:00:00,8904.0 -2014-07-17 08:00:00,9671.0 -2014-07-17 09:00:00,10532.0 -2014-07-17 10:00:00,11227.0 -2014-07-17 11:00:00,11742.0 -2014-07-17 12:00:00,12205.0 -2014-07-17 13:00:00,12516.0 -2014-07-17 14:00:00,12765.0 -2014-07-17 15:00:00,13053.0 -2014-07-17 16:00:00,13151.0 -2014-07-17 17:00:00,13174.0 -2014-07-17 18:00:00,13221.0 -2014-07-17 19:00:00,13121.0 -2014-07-17 20:00:00,12787.0 -2014-07-17 21:00:00,12368.0 -2014-07-17 22:00:00,12398.0 -2014-07-17 23:00:00,12149.0 -2014-07-18 00:00:00,11299.0 -2014-07-16 01:00:00,9729.0 -2014-07-16 02:00:00,9163.0 -2014-07-16 03:00:00,8799.0 -2014-07-16 04:00:00,8545.0 -2014-07-16 05:00:00,8474.0 -2014-07-16 06:00:00,8602.0 -2014-07-16 07:00:00,8994.0 -2014-07-16 08:00:00,9692.0 -2014-07-16 09:00:00,10520.0 -2014-07-16 10:00:00,11184.0 -2014-07-16 11:00:00,11626.0 -2014-07-16 12:00:00,11991.0 -2014-07-16 13:00:00,12152.0 -2014-07-16 14:00:00,12258.0 -2014-07-16 15:00:00,12420.0 -2014-07-16 16:00:00,12438.0 -2014-07-16 17:00:00,12444.0 -2014-07-16 18:00:00,12412.0 -2014-07-16 19:00:00,12274.0 -2014-07-16 20:00:00,11896.0 -2014-07-16 21:00:00,11571.0 -2014-07-16 22:00:00,11598.0 -2014-07-16 23:00:00,11479.0 -2014-07-17 00:00:00,10710.0 -2014-07-15 01:00:00,10556.0 -2014-07-15 02:00:00,9790.0 -2014-07-15 03:00:00,9274.0 -2014-07-15 04:00:00,8909.0 -2014-07-15 05:00:00,8760.0 -2014-07-15 06:00:00,8868.0 -2014-07-15 07:00:00,9163.0 -2014-07-15 08:00:00,9808.0 -2014-07-15 09:00:00,10631.0 -2014-07-15 10:00:00,11202.0 -2014-07-15 11:00:00,11505.0 -2014-07-15 12:00:00,11842.0 -2014-07-15 13:00:00,12023.0 -2014-07-15 14:00:00,12136.0 -2014-07-15 15:00:00,12261.0 -2014-07-15 16:00:00,12263.0 -2014-07-15 17:00:00,12163.0 -2014-07-15 18:00:00,12061.0 -2014-07-15 19:00:00,11834.0 -2014-07-15 20:00:00,11491.0 -2014-07-15 21:00:00,11283.0 -2014-07-15 22:00:00,11439.0 -2014-07-15 23:00:00,11273.0 -2014-07-16 00:00:00,10569.0 -2014-07-14 01:00:00,11857.0 -2014-07-14 02:00:00,10809.0 -2014-07-14 03:00:00,10144.0 -2014-07-14 04:00:00,9683.0 -2014-07-14 05:00:00,9460.0 -2014-07-14 06:00:00,9552.0 -2014-07-14 07:00:00,10015.0 -2014-07-14 08:00:00,10828.0 -2014-07-14 09:00:00,11792.0 -2014-07-14 10:00:00,12437.0 -2014-07-14 11:00:00,13113.0 -2014-07-14 12:00:00,13622.0 -2014-07-14 13:00:00,14005.0 -2014-07-14 14:00:00,14268.0 -2014-07-14 15:00:00,14769.0 -2014-07-14 16:00:00,15097.0 -2014-07-14 17:00:00,15214.0 -2014-07-14 18:00:00,15161.0 -2014-07-14 19:00:00,14794.0 -2014-07-14 20:00:00,14030.0 -2014-07-14 21:00:00,13298.0 -2014-07-14 22:00:00,12933.0 -2014-07-14 23:00:00,12589.0 -2014-07-15 00:00:00,11572.0 -2014-07-13 01:00:00,12554.0 -2014-07-13 02:00:00,11423.0 -2014-07-13 03:00:00,10694.0 -2014-07-13 04:00:00,10151.0 -2014-07-13 05:00:00,9925.0 -2014-07-13 06:00:00,9687.0 -2014-07-13 07:00:00,9447.0 -2014-07-13 08:00:00,9401.0 -2014-07-13 09:00:00,9827.0 -2014-07-13 10:00:00,10406.0 -2014-07-13 11:00:00,11206.0 -2014-07-13 12:00:00,12113.0 -2014-07-13 13:00:00,12969.0 -2014-07-13 14:00:00,13587.0 -2014-07-13 15:00:00,14091.0 -2014-07-13 16:00:00,14573.0 -2014-07-13 17:00:00,15070.0 -2014-07-13 18:00:00,15316.0 -2014-07-13 19:00:00,15360.0 -2014-07-13 20:00:00,14974.0 -2014-07-13 21:00:00,14546.0 -2014-07-13 22:00:00,14413.0 -2014-07-13 23:00:00,14080.0 -2014-07-14 00:00:00,13095.0 -2014-07-12 01:00:00,11549.0 -2014-07-12 02:00:00,10810.0 -2014-07-12 03:00:00,10313.0 -2014-07-12 04:00:00,9945.0 -2014-07-12 05:00:00,9748.0 -2014-07-12 06:00:00,9691.0 -2014-07-12 07:00:00,9802.0 -2014-07-12 08:00:00,10061.0 -2014-07-12 09:00:00,10635.0 -2014-07-12 10:00:00,11381.0 -2014-07-12 11:00:00,12020.0 -2014-07-12 12:00:00,12426.0 -2014-07-12 13:00:00,12559.0 -2014-07-12 14:00:00,12910.0 -2014-07-12 15:00:00,13163.0 -2014-07-12 16:00:00,13258.0 -2014-07-12 17:00:00,13418.0 -2014-07-12 18:00:00,13639.0 -2014-07-12 19:00:00,13691.0 -2014-07-12 20:00:00,13563.0 -2014-07-12 21:00:00,13588.0 -2014-07-12 22:00:00,13837.0 -2014-07-12 23:00:00,13835.0 -2014-07-13 00:00:00,13386.0 -2014-07-11 01:00:00,10832.0 -2014-07-11 02:00:00,10051.0 -2014-07-11 03:00:00,9513.0 -2014-07-11 04:00:00,9171.0 -2014-07-11 05:00:00,9036.0 -2014-07-11 06:00:00,9099.0 -2014-07-11 07:00:00,9411.0 -2014-07-11 08:00:00,10215.0 -2014-07-11 09:00:00,11250.0 -2014-07-11 10:00:00,12159.0 -2014-07-11 11:00:00,12835.0 -2014-07-11 12:00:00,13495.0 -2014-07-11 13:00:00,13963.0 -2014-07-11 14:00:00,14353.0 -2014-07-11 15:00:00,14690.0 -2014-07-11 16:00:00,14712.0 -2014-07-11 17:00:00,14561.0 -2014-07-11 18:00:00,14233.0 -2014-07-11 19:00:00,13764.0 -2014-07-11 20:00:00,13389.0 -2014-07-11 21:00:00,13143.0 -2014-07-11 22:00:00,13262.0 -2014-07-11 23:00:00,13141.0 -2014-07-12 00:00:00,12400.0 -2014-07-10 01:00:00,10858.0 -2014-07-10 02:00:00,10063.0 -2014-07-10 03:00:00,9536.0 -2014-07-10 04:00:00,9155.0 -2014-07-10 05:00:00,9015.0 -2014-07-10 06:00:00,9115.0 -2014-07-10 07:00:00,9455.0 -2014-07-10 08:00:00,10317.0 -2014-07-10 09:00:00,11380.0 -2014-07-10 10:00:00,12183.0 -2014-07-10 11:00:00,12782.0 -2014-07-10 12:00:00,13326.0 -2014-07-10 13:00:00,13760.0 -2014-07-10 14:00:00,14099.0 -2014-07-10 15:00:00,14472.0 -2014-07-10 16:00:00,14715.0 -2014-07-10 17:00:00,14899.0 -2014-07-10 18:00:00,14942.0 -2014-07-10 19:00:00,14729.0 -2014-07-10 20:00:00,14197.0 -2014-07-10 21:00:00,13509.0 -2014-07-10 22:00:00,13172.0 -2014-07-10 23:00:00,12849.0 -2014-07-11 00:00:00,11894.0 -2014-07-09 01:00:00,11685.0 -2014-07-09 02:00:00,10753.0 -2014-07-09 03:00:00,10115.0 -2014-07-09 04:00:00,9698.0 -2014-07-09 05:00:00,9499.0 -2014-07-09 06:00:00,9566.0 -2014-07-09 07:00:00,9884.0 -2014-07-09 08:00:00,10678.0 -2014-07-09 09:00:00,11712.0 -2014-07-09 10:00:00,12561.0 -2014-07-09 11:00:00,13264.0 -2014-07-09 12:00:00,13869.0 -2014-07-09 13:00:00,14179.0 -2014-07-09 14:00:00,14410.0 -2014-07-09 15:00:00,14670.0 -2014-07-09 16:00:00,14839.0 -2014-07-09 17:00:00,14979.0 -2014-07-09 18:00:00,14999.0 -2014-07-09 19:00:00,14817.0 -2014-07-09 20:00:00,14263.0 -2014-07-09 21:00:00,13611.0 -2014-07-09 22:00:00,13184.0 -2014-07-09 23:00:00,12902.0 -2014-07-10 00:00:00,11927.0 -2014-07-08 01:00:00,13452.0 -2014-07-08 02:00:00,12421.0 -2014-07-08 03:00:00,11754.0 -2014-07-08 04:00:00,11227.0 -2014-07-08 05:00:00,11056.0 -2014-07-08 06:00:00,11133.0 -2014-07-08 07:00:00,11666.0 -2014-07-08 08:00:00,12341.0 -2014-07-08 09:00:00,13049.0 -2014-07-08 10:00:00,13820.0 -2014-07-08 11:00:00,14502.0 -2014-07-08 12:00:00,15219.0 -2014-07-08 13:00:00,15735.0 -2014-07-08 14:00:00,16003.0 -2014-07-08 15:00:00,16254.0 -2014-07-08 16:00:00,16413.0 -2014-07-08 17:00:00,16526.0 -2014-07-08 18:00:00,16502.0 -2014-07-08 19:00:00,16261.0 -2014-07-08 20:00:00,15631.0 -2014-07-08 21:00:00,14894.0 -2014-07-08 22:00:00,14349.0 -2014-07-08 23:00:00,13991.0 -2014-07-09 00:00:00,12895.0 -2014-07-07 01:00:00,12938.0 -2014-07-07 02:00:00,12128.0 -2014-07-07 03:00:00,11409.0 -2014-07-07 04:00:00,10815.0 -2014-07-07 05:00:00,10547.0 -2014-07-07 06:00:00,10583.0 -2014-07-07 07:00:00,11105.0 -2014-07-07 08:00:00,12125.0 -2014-07-07 09:00:00,13471.0 -2014-07-07 10:00:00,14494.0 -2014-07-07 11:00:00,15551.0 -2014-07-07 12:00:00,16483.0 -2014-07-07 13:00:00,17187.0 -2014-07-07 14:00:00,17695.0 -2014-07-07 15:00:00,18109.0 -2014-07-07 16:00:00,18342.0 -2014-07-07 17:00:00,18489.0 -2014-07-07 18:00:00,18544.0 -2014-07-07 19:00:00,18431.0 -2014-07-07 20:00:00,17887.0 -2014-07-07 21:00:00,17107.0 -2014-07-07 22:00:00,16563.0 -2014-07-07 23:00:00,16073.0 -2014-07-08 00:00:00,14783.0 -2014-07-06 01:00:00,9832.0 -2014-07-06 02:00:00,9225.0 -2014-07-06 03:00:00,8799.0 -2014-07-06 04:00:00,8490.0 -2014-07-06 05:00:00,8241.0 -2014-07-06 06:00:00,8214.0 -2014-07-06 07:00:00,8025.0 -2014-07-06 08:00:00,8042.0 -2014-07-06 09:00:00,8468.0 -2014-07-06 10:00:00,9002.0 -2014-07-06 11:00:00,9992.0 -2014-07-06 12:00:00,10948.0 -2014-07-06 13:00:00,11953.0 -2014-07-06 14:00:00,12550.0 -2014-07-06 15:00:00,13160.0 -2014-07-06 16:00:00,13943.0 -2014-07-06 17:00:00,14731.0 -2014-07-06 18:00:00,15167.0 -2014-07-06 19:00:00,15224.0 -2014-07-06 20:00:00,15006.0 -2014-07-06 21:00:00,14700.0 -2014-07-06 22:00:00,14598.0 -2014-07-06 23:00:00,14607.0 -2014-07-07 00:00:00,13860.0 -2014-07-05 01:00:00,9356.0 -2014-07-05 02:00:00,8816.0 -2014-07-05 03:00:00,8351.0 -2014-07-05 04:00:00,8067.0 -2014-07-05 05:00:00,7979.0 -2014-07-05 06:00:00,7918.0 -2014-07-05 07:00:00,7918.0 -2014-07-05 08:00:00,8038.0 -2014-07-05 09:00:00,8677.0 -2014-07-05 10:00:00,9373.0 -2014-07-05 11:00:00,10008.0 -2014-07-05 12:00:00,10608.0 -2014-07-05 13:00:00,10916.0 -2014-07-05 14:00:00,11147.0 -2014-07-05 15:00:00,11174.0 -2014-07-05 16:00:00,11240.0 -2014-07-05 17:00:00,11266.0 -2014-07-05 18:00:00,11314.0 -2014-07-05 19:00:00,11164.0 -2014-07-05 20:00:00,10904.0 -2014-07-05 21:00:00,10739.0 -2014-07-05 22:00:00,10938.0 -2014-07-05 23:00:00,10879.0 -2014-07-06 00:00:00,10441.0 -2014-07-04 01:00:00,9654.0 -2014-07-04 02:00:00,8972.0 -2014-07-04 03:00:00,8534.0 -2014-07-04 04:00:00,8232.0 -2014-07-04 05:00:00,8036.0 -2014-07-04 06:00:00,7992.0 -2014-07-04 07:00:00,7861.0 -2014-07-04 08:00:00,7925.0 -2014-07-04 09:00:00,8364.0 -2014-07-04 10:00:00,8906.0 -2014-07-04 11:00:00,9445.0 -2014-07-04 12:00:00,9927.0 -2014-07-04 13:00:00,10311.0 -2014-07-04 14:00:00,10606.0 -2014-07-04 15:00:00,10848.0 -2014-07-04 16:00:00,11040.0 -2014-07-04 17:00:00,11246.0 -2014-07-04 18:00:00,11364.0 -2014-07-04 19:00:00,11334.0 -2014-07-04 20:00:00,11103.0 -2014-07-04 21:00:00,10637.0 -2014-07-04 22:00:00,10262.0 -2014-07-04 23:00:00,10128.0 -2014-07-05 00:00:00,9767.0 -2014-07-03 01:00:00,9634.0 -2014-07-03 02:00:00,9086.0 -2014-07-03 03:00:00,8749.0 -2014-07-03 04:00:00,8523.0 -2014-07-03 05:00:00,8451.0 -2014-07-03 06:00:00,8542.0 -2014-07-03 07:00:00,8861.0 -2014-07-03 08:00:00,9460.0 -2014-07-03 09:00:00,10335.0 -2014-07-03 10:00:00,11057.0 -2014-07-03 11:00:00,11554.0 -2014-07-03 12:00:00,12001.0 -2014-07-03 13:00:00,12241.0 -2014-07-03 14:00:00,12293.0 -2014-07-03 15:00:00,12446.0 -2014-07-03 16:00:00,12493.0 -2014-07-03 17:00:00,12525.0 -2014-07-03 18:00:00,12501.0 -2014-07-03 19:00:00,12375.0 -2014-07-03 20:00:00,11983.0 -2014-07-03 21:00:00,11570.0 -2014-07-03 22:00:00,11321.0 -2014-07-03 23:00:00,11141.0 -2014-07-04 00:00:00,10443.0 -2014-07-02 01:00:00,11802.0 -2014-07-02 02:00:00,10843.0 -2014-07-02 03:00:00,10200.0 -2014-07-02 04:00:00,9682.0 -2014-07-02 05:00:00,9402.0 -2014-07-02 06:00:00,9383.0 -2014-07-02 07:00:00,9656.0 -2014-07-02 08:00:00,10400.0 -2014-07-02 09:00:00,11271.0 -2014-07-02 10:00:00,12007.0 -2014-07-02 11:00:00,12488.0 -2014-07-02 12:00:00,12863.0 -2014-07-02 13:00:00,13020.0 -2014-07-02 14:00:00,12939.0 -2014-07-02 15:00:00,12815.0 -2014-07-02 16:00:00,12486.0 -2014-07-02 17:00:00,12172.0 -2014-07-02 18:00:00,11887.0 -2014-07-02 19:00:00,11632.0 -2014-07-02 20:00:00,11281.0 -2014-07-02 21:00:00,11181.0 -2014-07-02 22:00:00,11346.0 -2014-07-02 23:00:00,11148.0 -2014-07-03 00:00:00,10453.0 -2014-07-01 01:00:00,11331.0 -2014-07-01 02:00:00,10522.0 -2014-07-01 03:00:00,10003.0 -2014-07-01 04:00:00,9670.0 -2014-07-01 05:00:00,9534.0 -2014-07-01 06:00:00,9609.0 -2014-07-01 07:00:00,10092.0 -2014-07-01 08:00:00,10921.0 -2014-07-01 09:00:00,11981.0 -2014-07-01 10:00:00,12967.0 -2014-07-01 11:00:00,13727.0 -2014-07-01 12:00:00,14367.0 -2014-07-01 13:00:00,14815.0 -2014-07-01 14:00:00,15187.0 -2014-07-01 15:00:00,15519.0 -2014-07-01 16:00:00,15584.0 -2014-07-01 17:00:00,15702.0 -2014-07-01 18:00:00,15686.0 -2014-07-01 19:00:00,15486.0 -2014-07-01 20:00:00,15028.0 -2014-07-01 21:00:00,14474.0 -2014-07-01 22:00:00,14115.0 -2014-07-01 23:00:00,13879.0 -2014-07-02 00:00:00,12931.0 -2014-06-30 01:00:00,13495.0 -2014-06-30 02:00:00,12544.0 -2014-06-30 03:00:00,11884.0 -2014-06-30 04:00:00,11405.0 -2014-06-30 05:00:00,11058.0 -2014-06-30 06:00:00,11123.0 -2014-06-30 07:00:00,11655.0 -2014-06-30 08:00:00,12498.0 -2014-06-30 09:00:00,13355.0 -2014-06-30 10:00:00,14256.0 -2014-06-30 11:00:00,15010.0 -2014-06-30 12:00:00,15593.0 -2014-06-30 13:00:00,15958.0 -2014-06-30 14:00:00,16591.0 -2014-06-30 15:00:00,17556.0 -2014-06-30 16:00:00,18422.0 -2014-06-30 17:00:00,18874.0 -2014-06-30 18:00:00,18483.0 -2014-06-30 19:00:00,18012.0 -2014-06-30 20:00:00,17542.0 -2014-06-30 21:00:00,16111.0 -2014-06-30 22:00:00,15059.0 -2014-06-30 23:00:00,14189.0 -2014-07-01 00:00:00,12568.0 -2014-06-29 01:00:00,12791.0 -2014-06-29 02:00:00,11989.0 -2014-06-29 03:00:00,11367.0 -2014-06-29 04:00:00,10897.0 -2014-06-29 05:00:00,10582.0 -2014-06-29 06:00:00,10261.0 -2014-06-29 07:00:00,9871.0 -2014-06-29 08:00:00,9814.0 -2014-06-29 09:00:00,10363.0 -2014-06-29 10:00:00,11095.0 -2014-06-29 11:00:00,12025.0 -2014-06-29 12:00:00,13015.0 -2014-06-29 13:00:00,13869.0 -2014-06-29 14:00:00,14463.0 -2014-06-29 15:00:00,15017.0 -2014-06-29 16:00:00,15447.0 -2014-06-29 17:00:00,15868.0 -2014-06-29 18:00:00,16230.0 -2014-06-29 19:00:00,16378.0 -2014-06-29 20:00:00,16231.0 -2014-06-29 21:00:00,15850.0 -2014-06-29 22:00:00,15497.0 -2014-06-29 23:00:00,15404.0 -2014-06-30 00:00:00,14515.0 -2014-06-28 01:00:00,13192.0 -2014-06-28 02:00:00,12136.0 -2014-06-28 03:00:00,11427.0 -2014-06-28 04:00:00,10780.0 -2014-06-28 05:00:00,10502.0 -2014-06-28 06:00:00,10339.0 -2014-06-28 07:00:00,10355.0 -2014-06-28 08:00:00,10691.0 -2014-06-28 09:00:00,11441.0 -2014-06-28 10:00:00,12491.0 -2014-06-28 11:00:00,13601.0 -2014-06-28 12:00:00,14514.0 -2014-06-28 13:00:00,15379.0 -2014-06-28 14:00:00,15935.0 -2014-06-28 15:00:00,16097.0 -2014-06-28 16:00:00,16357.0 -2014-06-28 17:00:00,16540.0 -2014-06-28 18:00:00,16687.0 -2014-06-28 19:00:00,16505.0 -2014-06-28 20:00:00,15902.0 -2014-06-28 21:00:00,15083.0 -2014-06-28 22:00:00,14614.0 -2014-06-28 23:00:00,14435.0 -2014-06-29 00:00:00,13655.0 -2014-06-27 01:00:00,10649.0 -2014-06-27 02:00:00,9886.0 -2014-06-27 03:00:00,9381.0 -2014-06-27 04:00:00,9125.0 -2014-06-27 05:00:00,8996.0 -2014-06-27 06:00:00,9137.0 -2014-06-27 07:00:00,9538.0 -2014-06-27 08:00:00,10320.0 -2014-06-27 09:00:00,11346.0 -2014-06-27 10:00:00,12345.0 -2014-06-27 11:00:00,13238.0 -2014-06-27 12:00:00,14211.0 -2014-06-27 13:00:00,15053.0 -2014-06-27 14:00:00,15760.0 -2014-06-27 15:00:00,16401.0 -2014-06-27 16:00:00,16888.0 -2014-06-27 17:00:00,17154.0 -2014-06-27 18:00:00,17251.0 -2014-06-27 19:00:00,17086.0 -2014-06-27 20:00:00,16507.0 -2014-06-27 21:00:00,15861.0 -2014-06-27 22:00:00,15567.0 -2014-06-27 23:00:00,15299.0 -2014-06-28 00:00:00,14323.0 -2014-06-26 01:00:00,10510.0 -2014-06-26 02:00:00,9817.0 -2014-06-26 03:00:00,9358.0 -2014-06-26 04:00:00,9106.0 -2014-06-26 05:00:00,8971.0 -2014-06-26 06:00:00,9088.0 -2014-06-26 07:00:00,9515.0 -2014-06-26 08:00:00,10249.0 -2014-06-26 09:00:00,11085.0 -2014-06-26 10:00:00,11578.0 -2014-06-26 11:00:00,11946.0 -2014-06-26 12:00:00,12386.0 -2014-06-26 13:00:00,12771.0 -2014-06-26 14:00:00,13199.0 -2014-06-26 15:00:00,13728.0 -2014-06-26 16:00:00,14150.0 -2014-06-26 17:00:00,14488.0 -2014-06-26 18:00:00,14658.0 -2014-06-26 19:00:00,14519.0 -2014-06-26 20:00:00,13925.0 -2014-06-26 21:00:00,13254.0 -2014-06-26 22:00:00,12925.0 -2014-06-26 23:00:00,12618.0 -2014-06-27 00:00:00,11644.0 -2014-06-25 01:00:00,12388.0 -2014-06-25 02:00:00,11385.0 -2014-06-25 03:00:00,10669.0 -2014-06-25 04:00:00,10189.0 -2014-06-25 05:00:00,9870.0 -2014-06-25 06:00:00,9909.0 -2014-06-25 07:00:00,10238.0 -2014-06-25 08:00:00,11000.0 -2014-06-25 09:00:00,11738.0 -2014-06-25 10:00:00,12398.0 -2014-06-25 11:00:00,12947.0 -2014-06-25 12:00:00,13778.0 -2014-06-25 13:00:00,14447.0 -2014-06-25 14:00:00,14970.0 -2014-06-25 15:00:00,15362.0 -2014-06-25 16:00:00,15130.0 -2014-06-25 17:00:00,14720.0 -2014-06-25 18:00:00,14271.0 -2014-06-25 19:00:00,13568.0 -2014-06-25 20:00:00,12895.0 -2014-06-25 21:00:00,12415.0 -2014-06-25 22:00:00,12199.0 -2014-06-25 23:00:00,12322.0 -2014-06-26 00:00:00,11434.0 -2014-06-24 01:00:00,12153.0 -2014-06-24 02:00:00,11247.0 -2014-06-24 03:00:00,10632.0 -2014-06-24 04:00:00,10263.0 -2014-06-24 05:00:00,10078.0 -2014-06-24 06:00:00,10224.0 -2014-06-24 07:00:00,10745.0 -2014-06-24 08:00:00,11683.0 -2014-06-24 09:00:00,12603.0 -2014-06-24 10:00:00,13325.0 -2014-06-24 11:00:00,14023.0 -2014-06-24 12:00:00,14951.0 -2014-06-24 13:00:00,15595.0 -2014-06-24 14:00:00,16245.0 -2014-06-24 15:00:00,16786.0 -2014-06-24 16:00:00,17241.0 -2014-06-24 17:00:00,17563.0 -2014-06-24 18:00:00,17684.0 -2014-06-24 19:00:00,17405.0 -2014-06-24 20:00:00,16585.0 -2014-06-24 21:00:00,15735.0 -2014-06-24 22:00:00,15262.0 -2014-06-24 23:00:00,14791.0 -2014-06-25 00:00:00,13646.0 -2014-06-23 01:00:00,10676.0 -2014-06-23 02:00:00,9951.0 -2014-06-23 03:00:00,9566.0 -2014-06-23 04:00:00,9267.0 -2014-06-23 05:00:00,9225.0 -2014-06-23 06:00:00,9470.0 -2014-06-23 07:00:00,10096.0 -2014-06-23 08:00:00,11072.0 -2014-06-23 09:00:00,12031.0 -2014-06-23 10:00:00,12643.0 -2014-06-23 11:00:00,13067.0 -2014-06-23 12:00:00,13519.0 -2014-06-23 13:00:00,13993.0 -2014-06-23 14:00:00,14316.0 -2014-06-23 15:00:00,14673.0 -2014-06-23 16:00:00,15165.0 -2014-06-23 17:00:00,15545.0 -2014-06-23 18:00:00,15754.0 -2014-06-23 19:00:00,15760.0 -2014-06-23 20:00:00,15387.0 -2014-06-23 21:00:00,14864.0 -2014-06-23 22:00:00,14636.0 -2014-06-23 23:00:00,14379.0 -2014-06-24 00:00:00,13362.0 -2014-06-22 01:00:00,10466.0 -2014-06-22 02:00:00,9845.0 -2014-06-22 03:00:00,9337.0 -2014-06-22 04:00:00,8977.0 -2014-06-22 05:00:00,8743.0 -2014-06-22 06:00:00,8654.0 -2014-06-22 07:00:00,8505.0 -2014-06-22 08:00:00,8566.0 -2014-06-22 09:00:00,8905.0 -2014-06-22 10:00:00,9554.0 -2014-06-22 11:00:00,10309.0 -2014-06-22 12:00:00,11246.0 -2014-06-22 13:00:00,11990.0 -2014-06-22 14:00:00,12690.0 -2014-06-22 15:00:00,13187.0 -2014-06-22 16:00:00,13583.0 -2014-06-22 17:00:00,13832.0 -2014-06-22 18:00:00,14060.0 -2014-06-22 19:00:00,13926.0 -2014-06-22 20:00:00,13457.0 -2014-06-22 21:00:00,12832.0 -2014-06-22 22:00:00,12687.0 -2014-06-22 23:00:00,12431.0 -2014-06-23 00:00:00,11619.0 -2014-06-21 01:00:00,11285.0 -2014-06-21 02:00:00,10430.0 -2014-06-21 03:00:00,9862.0 -2014-06-21 04:00:00,9449.0 -2014-06-21 05:00:00,9196.0 -2014-06-21 06:00:00,9120.0 -2014-06-21 07:00:00,9106.0 -2014-06-21 08:00:00,9248.0 -2014-06-21 09:00:00,9813.0 -2014-06-21 10:00:00,10361.0 -2014-06-21 11:00:00,11049.0 -2014-06-21 12:00:00,11714.0 -2014-06-21 13:00:00,12406.0 -2014-06-21 14:00:00,13124.0 -2014-06-21 15:00:00,13631.0 -2014-06-21 16:00:00,13847.0 -2014-06-21 17:00:00,14090.0 -2014-06-21 18:00:00,13909.0 -2014-06-21 19:00:00,13415.0 -2014-06-21 20:00:00,12933.0 -2014-06-21 21:00:00,12314.0 -2014-06-21 22:00:00,12073.0 -2014-06-21 23:00:00,11890.0 -2014-06-22 00:00:00,11263.0 -2014-06-20 01:00:00,11336.0 -2014-06-20 02:00:00,10639.0 -2014-06-20 03:00:00,10145.0 -2014-06-20 04:00:00,9832.0 -2014-06-20 05:00:00,9718.0 -2014-06-20 06:00:00,9869.0 -2014-06-20 07:00:00,10335.0 -2014-06-20 08:00:00,11246.0 -2014-06-20 09:00:00,12193.0 -2014-06-20 10:00:00,12996.0 -2014-06-20 11:00:00,13740.0 -2014-06-20 12:00:00,14511.0 -2014-06-20 13:00:00,15121.0 -2014-06-20 14:00:00,15590.0 -2014-06-20 15:00:00,16121.0 -2014-06-20 16:00:00,16487.0 -2014-06-20 17:00:00,16766.0 -2014-06-20 18:00:00,16775.0 -2014-06-20 19:00:00,16390.0 -2014-06-20 20:00:00,15590.0 -2014-06-20 21:00:00,14736.0 -2014-06-20 22:00:00,13918.0 -2014-06-20 23:00:00,13410.0 -2014-06-21 00:00:00,12333.0 -2014-06-19 01:00:00,10874.0 -2014-06-19 02:00:00,10162.0 -2014-06-19 03:00:00,9693.0 -2014-06-19 04:00:00,9361.0 -2014-06-19 05:00:00,9195.0 -2014-06-19 06:00:00,9290.0 -2014-06-19 07:00:00,9684.0 -2014-06-19 08:00:00,10466.0 -2014-06-19 09:00:00,11291.0 -2014-06-19 10:00:00,11909.0 -2014-06-19 11:00:00,12407.0 -2014-06-19 12:00:00,12847.0 -2014-06-19 13:00:00,13401.0 -2014-06-19 14:00:00,14069.0 -2014-06-19 15:00:00,14718.0 -2014-06-19 16:00:00,15164.0 -2014-06-19 17:00:00,15399.0 -2014-06-19 18:00:00,15282.0 -2014-06-19 19:00:00,14954.0 -2014-06-19 20:00:00,14254.0 -2014-06-19 21:00:00,13834.0 -2014-06-19 22:00:00,13848.0 -2014-06-19 23:00:00,13397.0 -2014-06-20 00:00:00,12470.0 -2014-06-18 01:00:00,14419.0 -2014-06-18 02:00:00,13445.0 -2014-06-18 03:00:00,12789.0 -2014-06-18 04:00:00,12346.0 -2014-06-18 05:00:00,12082.0 -2014-06-18 06:00:00,12098.0 -2014-06-18 07:00:00,12508.0 -2014-06-18 08:00:00,13404.0 -2014-06-18 09:00:00,14557.0 -2014-06-18 10:00:00,15428.0 -2014-06-18 11:00:00,15970.0 -2014-06-18 12:00:00,16361.0 -2014-06-18 13:00:00,15763.0 -2014-06-18 14:00:00,14905.0 -2014-06-18 15:00:00,15019.0 -2014-06-18 16:00:00,15348.0 -2014-06-18 17:00:00,15433.0 -2014-06-18 18:00:00,15440.0 -2014-06-18 19:00:00,15266.0 -2014-06-18 20:00:00,14651.0 -2014-06-18 21:00:00,14045.0 -2014-06-18 22:00:00,13528.0 -2014-06-18 23:00:00,12856.0 -2014-06-19 00:00:00,11893.0 -2014-06-17 01:00:00,13950.0 -2014-06-17 02:00:00,13012.0 -2014-06-17 03:00:00,12380.0 -2014-06-17 04:00:00,11882.0 -2014-06-17 05:00:00,11601.0 -2014-06-17 06:00:00,11641.0 -2014-06-17 07:00:00,12226.0 -2014-06-17 08:00:00,13152.0 -2014-06-17 09:00:00,14090.0 -2014-06-17 10:00:00,14702.0 -2014-06-17 11:00:00,15197.0 -2014-06-17 12:00:00,16061.0 -2014-06-17 13:00:00,16780.0 -2014-06-17 14:00:00,17387.0 -2014-06-17 15:00:00,18011.0 -2014-06-17 16:00:00,18592.0 -2014-06-17 17:00:00,19125.0 -2014-06-17 18:00:00,19412.0 -2014-06-17 19:00:00,19275.0 -2014-06-17 20:00:00,18758.0 -2014-06-17 21:00:00,18032.0 -2014-06-17 22:00:00,17563.0 -2014-06-17 23:00:00,17075.0 -2014-06-18 00:00:00,15732.0 -2014-06-16 01:00:00,11594.0 -2014-06-16 02:00:00,10759.0 -2014-06-16 03:00:00,10169.0 -2014-06-16 04:00:00,9752.0 -2014-06-16 05:00:00,9565.0 -2014-06-16 06:00:00,9598.0 -2014-06-16 07:00:00,10001.0 -2014-06-16 08:00:00,11092.0 -2014-06-16 09:00:00,12436.0 -2014-06-16 10:00:00,13642.0 -2014-06-16 11:00:00,14628.0 -2014-06-16 12:00:00,15505.0 -2014-06-16 13:00:00,16121.0 -2014-06-16 14:00:00,16650.0 -2014-06-16 15:00:00,17170.0 -2014-06-16 16:00:00,17621.0 -2014-06-16 17:00:00,17940.0 -2014-06-16 18:00:00,18122.0 -2014-06-16 19:00:00,17933.0 -2014-06-16 20:00:00,17159.0 -2014-06-16 21:00:00,16571.0 -2014-06-16 22:00:00,16499.0 -2014-06-16 23:00:00,16241.0 -2014-06-17 00:00:00,15188.0 -2014-06-15 01:00:00,9563.0 -2014-06-15 02:00:00,8943.0 -2014-06-15 03:00:00,8536.0 -2014-06-15 04:00:00,8223.0 -2014-06-15 05:00:00,8015.0 -2014-06-15 06:00:00,7934.0 -2014-06-15 07:00:00,7725.0 -2014-06-15 08:00:00,7861.0 -2014-06-15 09:00:00,8314.0 -2014-06-15 10:00:00,8967.0 -2014-06-15 11:00:00,9522.0 -2014-06-15 12:00:00,10078.0 -2014-06-15 13:00:00,10542.0 -2014-06-15 14:00:00,10937.0 -2014-06-15 15:00:00,11317.0 -2014-06-15 16:00:00,11784.0 -2014-06-15 17:00:00,12108.0 -2014-06-15 18:00:00,12461.0 -2014-06-15 19:00:00,12787.0 -2014-06-15 20:00:00,12966.0 -2014-06-15 21:00:00,12923.0 -2014-06-15 22:00:00,13049.0 -2014-06-15 23:00:00,13206.0 -2014-06-16 00:00:00,12570.0 -2014-06-14 01:00:00,9667.0 -2014-06-14 02:00:00,9013.0 -2014-06-14 03:00:00,8549.0 -2014-06-14 04:00:00,8287.0 -2014-06-14 05:00:00,8123.0 -2014-06-14 06:00:00,8127.0 -2014-06-14 07:00:00,8047.0 -2014-06-14 08:00:00,8385.0 -2014-06-14 09:00:00,8958.0 -2014-06-14 10:00:00,9596.0 -2014-06-14 11:00:00,10117.0 -2014-06-14 12:00:00,10564.0 -2014-06-14 13:00:00,10796.0 -2014-06-14 14:00:00,10941.0 -2014-06-14 15:00:00,10954.0 -2014-06-14 16:00:00,11068.0 -2014-06-14 17:00:00,11204.0 -2014-06-14 18:00:00,11362.0 -2014-06-14 19:00:00,11349.0 -2014-06-14 20:00:00,11152.0 -2014-06-14 21:00:00,10866.0 -2014-06-14 22:00:00,10780.0 -2014-06-14 23:00:00,10796.0 -2014-06-15 00:00:00,10234.0 -2014-06-13 01:00:00,10162.0 -2014-06-13 02:00:00,9404.0 -2014-06-13 03:00:00,8895.0 -2014-06-13 04:00:00,8588.0 -2014-06-13 05:00:00,8419.0 -2014-06-13 06:00:00,8515.0 -2014-06-13 07:00:00,8759.0 -2014-06-13 08:00:00,9527.0 -2014-06-13 09:00:00,10394.0 -2014-06-13 10:00:00,11009.0 -2014-06-13 11:00:00,11384.0 -2014-06-13 12:00:00,11742.0 -2014-06-13 13:00:00,11900.0 -2014-06-13 14:00:00,11995.0 -2014-06-13 15:00:00,12127.0 -2014-06-13 16:00:00,12128.0 -2014-06-13 17:00:00,12156.0 -2014-06-13 18:00:00,12148.0 -2014-06-13 19:00:00,12000.0 -2014-06-13 20:00:00,11687.0 -2014-06-13 21:00:00,11287.0 -2014-06-13 22:00:00,11238.0 -2014-06-13 23:00:00,11168.0 -2014-06-14 00:00:00,10416.0 -2014-06-12 01:00:00,9665.0 -2014-06-12 02:00:00,9116.0 -2014-06-12 03:00:00,8748.0 -2014-06-12 04:00:00,8510.0 -2014-06-12 05:00:00,8435.0 -2014-06-12 06:00:00,8609.0 -2014-06-12 07:00:00,9094.0 -2014-06-12 08:00:00,9937.0 -2014-06-12 09:00:00,10822.0 -2014-06-12 10:00:00,11544.0 -2014-06-12 11:00:00,12152.0 -2014-06-12 12:00:00,12760.0 -2014-06-12 13:00:00,13268.0 -2014-06-12 14:00:00,13625.0 -2014-06-12 15:00:00,13745.0 -2014-06-12 16:00:00,13656.0 -2014-06-12 17:00:00,13575.0 -2014-06-12 18:00:00,13507.0 -2014-06-12 19:00:00,13234.0 -2014-06-12 20:00:00,12717.0 -2014-06-12 21:00:00,12320.0 -2014-06-12 22:00:00,12270.0 -2014-06-12 23:00:00,12037.0 -2014-06-13 00:00:00,11146.0 -2014-06-11 01:00:00,9660.0 -2014-06-11 02:00:00,9133.0 -2014-06-11 03:00:00,8778.0 -2014-06-11 04:00:00,8570.0 -2014-06-11 05:00:00,8503.0 -2014-06-11 06:00:00,8645.0 -2014-06-11 07:00:00,9158.0 -2014-06-11 08:00:00,9934.0 -2014-06-11 09:00:00,10720.0 -2014-06-11 10:00:00,11204.0 -2014-06-11 11:00:00,11518.0 -2014-06-11 12:00:00,11761.0 -2014-06-11 13:00:00,11877.0 -2014-06-11 14:00:00,11922.0 -2014-06-11 15:00:00,11998.0 -2014-06-11 16:00:00,12008.0 -2014-06-11 17:00:00,11897.0 -2014-06-11 18:00:00,11759.0 -2014-06-11 19:00:00,11563.0 -2014-06-11 20:00:00,11276.0 -2014-06-11 21:00:00,11183.0 -2014-06-11 22:00:00,11484.0 -2014-06-11 23:00:00,11230.0 -2014-06-12 00:00:00,10491.0 -2014-06-10 01:00:00,9676.0 -2014-06-10 02:00:00,9071.0 -2014-06-10 03:00:00,8691.0 -2014-06-10 04:00:00,8482.0 -2014-06-10 05:00:00,8413.0 -2014-06-10 06:00:00,8562.0 -2014-06-10 07:00:00,9011.0 -2014-06-10 08:00:00,9845.0 -2014-06-10 09:00:00,10619.0 -2014-06-10 10:00:00,11075.0 -2014-06-10 11:00:00,11369.0 -2014-06-10 12:00:00,11672.0 -2014-06-10 13:00:00,11807.0 -2014-06-10 14:00:00,11873.0 -2014-06-10 15:00:00,11963.0 -2014-06-10 16:00:00,11728.0 -2014-06-10 17:00:00,11752.0 -2014-06-10 18:00:00,11630.0 -2014-06-10 19:00:00,11449.0 -2014-06-10 20:00:00,11375.0 -2014-06-10 21:00:00,11376.0 -2014-06-10 22:00:00,11477.0 -2014-06-10 23:00:00,11136.0 -2014-06-11 00:00:00,10460.0 -2014-06-09 01:00:00,8807.0 -2014-06-09 02:00:00,8365.0 -2014-06-09 03:00:00,8018.0 -2014-06-09 04:00:00,7848.0 -2014-06-09 05:00:00,7824.0 -2014-06-09 06:00:00,8028.0 -2014-06-09 07:00:00,8438.0 -2014-06-09 08:00:00,9375.0 -2014-06-09 09:00:00,10415.0 -2014-06-09 10:00:00,11126.0 -2014-06-09 11:00:00,11591.0 -2014-06-09 12:00:00,12077.0 -2014-06-09 13:00:00,12394.0 -2014-06-09 14:00:00,12701.0 -2014-06-09 15:00:00,13041.0 -2014-06-09 16:00:00,13217.0 -2014-06-09 17:00:00,13263.0 -2014-06-09 18:00:00,13153.0 -2014-06-09 19:00:00,12794.0 -2014-06-09 20:00:00,12237.0 -2014-06-09 21:00:00,11827.0 -2014-06-09 22:00:00,11809.0 -2014-06-09 23:00:00,11511.0 -2014-06-10 00:00:00,10601.0 -2014-06-08 01:00:00,9992.0 -2014-06-08 02:00:00,9294.0 -2014-06-08 03:00:00,8793.0 -2014-06-08 04:00:00,8343.0 -2014-06-08 05:00:00,8092.0 -2014-06-08 06:00:00,7971.0 -2014-06-08 07:00:00,7875.0 -2014-06-08 08:00:00,7758.0 -2014-06-08 09:00:00,8015.0 -2014-06-08 10:00:00,8538.0 -2014-06-08 11:00:00,9111.0 -2014-06-08 12:00:00,9462.0 -2014-06-08 13:00:00,9720.0 -2014-06-08 14:00:00,9795.0 -2014-06-08 15:00:00,9904.0 -2014-06-08 16:00:00,10036.0 -2014-06-08 17:00:00,10096.0 -2014-06-08 18:00:00,10102.0 -2014-06-08 19:00:00,10008.0 -2014-06-08 20:00:00,9763.0 -2014-06-08 21:00:00,9615.0 -2014-06-08 22:00:00,9897.0 -2014-06-08 23:00:00,9980.0 -2014-06-09 00:00:00,9496.0 -2014-06-07 01:00:00,10182.0 -2014-06-07 02:00:00,9442.0 -2014-06-07 03:00:00,8912.0 -2014-06-07 04:00:00,8556.0 -2014-06-07 05:00:00,8388.0 -2014-06-07 06:00:00,8368.0 -2014-06-07 07:00:00,8260.0 -2014-06-07 08:00:00,8670.0 -2014-06-07 09:00:00,9378.0 -2014-06-07 10:00:00,10271.0 -2014-06-07 11:00:00,11017.0 -2014-06-07 12:00:00,11639.0 -2014-06-07 13:00:00,12063.0 -2014-06-07 14:00:00,12456.0 -2014-06-07 15:00:00,12703.0 -2014-06-07 16:00:00,12863.0 -2014-06-07 17:00:00,12603.0 -2014-06-07 18:00:00,12306.0 -2014-06-07 19:00:00,11925.0 -2014-06-07 20:00:00,11643.0 -2014-06-07 21:00:00,11457.0 -2014-06-07 22:00:00,11609.0 -2014-06-07 23:00:00,11407.0 -2014-06-08 00:00:00,10775.0 -2014-06-06 01:00:00,9790.0 -2014-06-06 02:00:00,9174.0 -2014-06-06 03:00:00,8736.0 -2014-06-06 04:00:00,8501.0 -2014-06-06 05:00:00,8350.0 -2014-06-06 06:00:00,8514.0 -2014-06-06 07:00:00,8776.0 -2014-06-06 08:00:00,9638.0 -2014-06-06 09:00:00,10600.0 -2014-06-06 10:00:00,11303.0 -2014-06-06 11:00:00,11804.0 -2014-06-06 12:00:00,12325.0 -2014-06-06 13:00:00,12623.0 -2014-06-06 14:00:00,12911.0 -2014-06-06 15:00:00,13246.0 -2014-06-06 16:00:00,13494.0 -2014-06-06 17:00:00,13712.0 -2014-06-06 18:00:00,13816.0 -2014-06-06 19:00:00,13634.0 -2014-06-06 20:00:00,13146.0 -2014-06-06 21:00:00,12544.0 -2014-06-06 22:00:00,12173.0 -2014-06-06 23:00:00,11966.0 -2014-06-07 00:00:00,11120.0 -2014-06-05 01:00:00,9642.0 -2014-06-05 02:00:00,9038.0 -2014-06-05 03:00:00,8665.0 -2014-06-05 04:00:00,8392.0 -2014-06-05 05:00:00,8294.0 -2014-06-05 06:00:00,8421.0 -2014-06-05 07:00:00,8747.0 -2014-06-05 08:00:00,9648.0 -2014-06-05 09:00:00,10547.0 -2014-06-05 10:00:00,11119.0 -2014-06-05 11:00:00,11558.0 -2014-06-05 12:00:00,11964.0 -2014-06-05 13:00:00,12178.0 -2014-06-05 14:00:00,12289.0 -2014-06-05 15:00:00,12393.0 -2014-06-05 16:00:00,12574.0 -2014-06-05 17:00:00,12710.0 -2014-06-05 18:00:00,12758.0 -2014-06-05 19:00:00,12607.0 -2014-06-05 20:00:00,12213.0 -2014-06-05 21:00:00,11854.0 -2014-06-05 22:00:00,11785.0 -2014-06-05 23:00:00,11640.0 -2014-06-06 00:00:00,10782.0 -2014-06-04 01:00:00,11041.0 -2014-06-04 02:00:00,10273.0 -2014-06-04 03:00:00,9723.0 -2014-06-04 04:00:00,9412.0 -2014-06-04 05:00:00,9276.0 -2014-06-04 06:00:00,9349.0 -2014-06-04 07:00:00,9823.0 -2014-06-04 08:00:00,10610.0 -2014-06-04 09:00:00,11332.0 -2014-06-04 10:00:00,11808.0 -2014-06-04 11:00:00,12128.0 -2014-06-04 12:00:00,12231.0 -2014-06-04 13:00:00,12182.0 -2014-06-04 14:00:00,12096.0 -2014-06-04 15:00:00,12109.0 -2014-06-04 16:00:00,12055.0 -2014-06-04 17:00:00,11937.0 -2014-06-04 18:00:00,11786.0 -2014-06-04 19:00:00,11656.0 -2014-06-04 20:00:00,11458.0 -2014-06-04 21:00:00,11255.0 -2014-06-04 22:00:00,11366.0 -2014-06-04 23:00:00,11279.0 -2014-06-05 00:00:00,10497.0 -2014-06-03 01:00:00,12031.0 -2014-06-03 02:00:00,11100.0 -2014-06-03 03:00:00,10481.0 -2014-06-03 04:00:00,10064.0 -2014-06-03 05:00:00,9854.0 -2014-06-03 06:00:00,9896.0 -2014-06-03 07:00:00,10216.0 -2014-06-03 08:00:00,11252.0 -2014-06-03 09:00:00,12380.0 -2014-06-03 10:00:00,13118.0 -2014-06-03 11:00:00,13554.0 -2014-06-03 12:00:00,13964.0 -2014-06-03 13:00:00,14257.0 -2014-06-03 14:00:00,14532.0 -2014-06-03 15:00:00,14838.0 -2014-06-03 16:00:00,15127.0 -2014-06-03 17:00:00,15424.0 -2014-06-03 18:00:00,15563.0 -2014-06-03 19:00:00,15443.0 -2014-06-03 20:00:00,14912.0 -2014-06-03 21:00:00,14187.0 -2014-06-03 22:00:00,13797.0 -2014-06-03 23:00:00,13346.0 -2014-06-04 00:00:00,12221.0 -2014-06-02 01:00:00,11873.0 -2014-06-02 02:00:00,11082.0 -2014-06-02 03:00:00,10579.0 -2014-06-02 04:00:00,10202.0 -2014-06-02 05:00:00,10091.0 -2014-06-02 06:00:00,10305.0 -2014-06-02 07:00:00,10907.0 -2014-06-02 08:00:00,12168.0 -2014-06-02 09:00:00,13441.0 -2014-06-02 10:00:00,14274.0 -2014-06-02 11:00:00,14833.0 -2014-06-02 12:00:00,14938.0 -2014-06-02 13:00:00,15090.0 -2014-06-02 14:00:00,14828.0 -2014-06-02 15:00:00,15161.0 -2014-06-02 16:00:00,15585.0 -2014-06-02 17:00:00,15891.0 -2014-06-02 18:00:00,16121.0 -2014-06-02 19:00:00,16075.0 -2014-06-02 20:00:00,15635.0 -2014-06-02 21:00:00,15093.0 -2014-06-02 22:00:00,14760.0 -2014-06-02 23:00:00,14391.0 -2014-06-03 00:00:00,13293.0 -2014-06-01 01:00:00,10490.0 -2014-06-01 02:00:00,9741.0 -2014-06-01 03:00:00,9253.0 -2014-06-01 04:00:00,8880.0 -2014-06-01 05:00:00,8609.0 -2014-06-01 06:00:00,8513.0 -2014-06-01 07:00:00,8283.0 -2014-06-01 08:00:00,8413.0 -2014-06-01 09:00:00,9057.0 -2014-06-01 10:00:00,9938.0 -2014-06-01 11:00:00,10813.0 -2014-06-01 12:00:00,11739.0 -2014-06-01 13:00:00,12427.0 -2014-06-01 14:00:00,13310.0 -2014-06-01 15:00:00,13908.0 -2014-06-01 16:00:00,14345.0 -2014-06-01 17:00:00,14607.0 -2014-06-01 18:00:00,14858.0 -2014-06-01 19:00:00,15028.0 -2014-06-01 20:00:00,14665.0 -2014-06-01 21:00:00,14283.0 -2014-06-01 22:00:00,14041.0 -2014-06-01 23:00:00,13684.0 -2014-06-02 00:00:00,12867.0 -2014-05-31 01:00:00,10087.0 -2014-05-31 02:00:00,9316.0 -2014-05-31 03:00:00,8818.0 -2014-05-31 04:00:00,8506.0 -2014-05-31 05:00:00,8312.0 -2014-05-31 06:00:00,8307.0 -2014-05-31 07:00:00,8177.0 -2014-05-31 08:00:00,8479.0 -2014-05-31 09:00:00,9192.0 -2014-05-31 10:00:00,10007.0 -2014-05-31 11:00:00,10827.0 -2014-05-31 12:00:00,11511.0 -2014-05-31 13:00:00,12013.0 -2014-05-31 14:00:00,12549.0 -2014-05-31 15:00:00,12866.0 -2014-05-31 16:00:00,13214.0 -2014-05-31 17:00:00,13576.0 -2014-05-31 18:00:00,13752.0 -2014-05-31 19:00:00,13766.0 -2014-05-31 20:00:00,13426.0 -2014-05-31 21:00:00,12827.0 -2014-05-31 22:00:00,12522.0 -2014-05-31 23:00:00,12229.0 -2014-06-01 00:00:00,11425.0 -2014-05-30 01:00:00,9697.0 -2014-05-30 02:00:00,9054.0 -2014-05-30 03:00:00,8649.0 -2014-05-30 04:00:00,8357.0 -2014-05-30 05:00:00,8254.0 -2014-05-30 06:00:00,8403.0 -2014-05-30 07:00:00,8768.0 -2014-05-30 08:00:00,9696.0 -2014-05-30 09:00:00,10702.0 -2014-05-30 10:00:00,11362.0 -2014-05-30 11:00:00,11861.0 -2014-05-30 12:00:00,12349.0 -2014-05-30 13:00:00,12792.0 -2014-05-30 14:00:00,13071.0 -2014-05-30 15:00:00,13352.0 -2014-05-30 16:00:00,13559.0 -2014-05-30 17:00:00,13715.0 -2014-05-30 18:00:00,13757.0 -2014-05-30 19:00:00,13533.0 -2014-05-30 20:00:00,13066.0 -2014-05-30 21:00:00,12447.0 -2014-05-30 22:00:00,12211.0 -2014-05-30 23:00:00,11913.0 -2014-05-31 00:00:00,11015.0 -2014-05-29 01:00:00,9614.0 -2014-05-29 02:00:00,8962.0 -2014-05-29 03:00:00,8586.0 -2014-05-29 04:00:00,8356.0 -2014-05-29 05:00:00,8274.0 -2014-05-29 06:00:00,8435.0 -2014-05-29 07:00:00,8910.0 -2014-05-29 08:00:00,9789.0 -2014-05-29 09:00:00,10660.0 -2014-05-29 10:00:00,11272.0 -2014-05-29 11:00:00,11713.0 -2014-05-29 12:00:00,12191.0 -2014-05-29 13:00:00,12488.0 -2014-05-29 14:00:00,12755.0 -2014-05-29 15:00:00,13013.0 -2014-05-29 16:00:00,13066.0 -2014-05-29 17:00:00,13160.0 -2014-05-29 18:00:00,13231.0 -2014-05-29 19:00:00,13051.0 -2014-05-29 20:00:00,12595.0 -2014-05-29 21:00:00,12127.0 -2014-05-29 22:00:00,12009.0 -2014-05-29 23:00:00,11679.0 -2014-05-30 00:00:00,10705.0 -2014-05-28 01:00:00,10656.0 -2014-05-28 02:00:00,9877.0 -2014-05-28 03:00:00,9437.0 -2014-05-28 04:00:00,9189.0 -2014-05-28 05:00:00,9042.0 -2014-05-28 06:00:00,9131.0 -2014-05-28 07:00:00,9574.0 -2014-05-28 08:00:00,10351.0 -2014-05-28 09:00:00,11139.0 -2014-05-28 10:00:00,11606.0 -2014-05-28 11:00:00,11965.0 -2014-05-28 12:00:00,12331.0 -2014-05-28 13:00:00,12552.0 -2014-05-28 14:00:00,12757.0 -2014-05-28 15:00:00,13006.0 -2014-05-28 16:00:00,13113.0 -2014-05-28 17:00:00,13119.0 -2014-05-28 18:00:00,12999.0 -2014-05-28 19:00:00,12631.0 -2014-05-28 20:00:00,11998.0 -2014-05-28 21:00:00,11559.0 -2014-05-28 22:00:00,11610.0 -2014-05-28 23:00:00,11324.0 -2014-05-29 00:00:00,10488.0 -2014-05-27 01:00:00,10633.0 -2014-05-27 02:00:00,9994.0 -2014-05-27 03:00:00,9545.0 -2014-05-27 04:00:00,9253.0 -2014-05-27 05:00:00,9109.0 -2014-05-27 06:00:00,9307.0 -2014-05-27 07:00:00,9896.0 -2014-05-27 08:00:00,11027.0 -2014-05-27 09:00:00,12249.0 -2014-05-27 10:00:00,12982.0 -2014-05-27 11:00:00,13523.0 -2014-05-27 12:00:00,14264.0 -2014-05-27 13:00:00,14928.0 -2014-05-27 14:00:00,15475.0 -2014-05-27 15:00:00,15948.0 -2014-05-27 16:00:00,16141.0 -2014-05-27 17:00:00,16287.0 -2014-05-27 18:00:00,16122.0 -2014-05-27 19:00:00,15397.0 -2014-05-27 20:00:00,14514.0 -2014-05-27 21:00:00,13964.0 -2014-05-27 22:00:00,13809.0 -2014-05-27 23:00:00,13146.0 -2014-05-28 00:00:00,11891.0 -2014-05-26 01:00:00,9233.0 -2014-05-26 02:00:00,8690.0 -2014-05-26 03:00:00,8342.0 -2014-05-26 04:00:00,8081.0 -2014-05-26 05:00:00,7927.0 -2014-05-26 06:00:00,7912.0 -2014-05-26 07:00:00,7889.0 -2014-05-26 08:00:00,7981.0 -2014-05-26 09:00:00,8501.0 -2014-05-26 10:00:00,9135.0 -2014-05-26 11:00:00,9833.0 -2014-05-26 12:00:00,10600.0 -2014-05-26 13:00:00,11391.0 -2014-05-26 14:00:00,12104.0 -2014-05-26 15:00:00,12639.0 -2014-05-26 16:00:00,13032.0 -2014-05-26 17:00:00,13328.0 -2014-05-26 18:00:00,13371.0 -2014-05-26 19:00:00,13163.0 -2014-05-26 20:00:00,12797.0 -2014-05-26 21:00:00,12603.0 -2014-05-26 22:00:00,12726.0 -2014-05-26 23:00:00,12388.0 -2014-05-27 00:00:00,11530.0 -2014-05-25 01:00:00,8598.0 -2014-05-25 02:00:00,8119.0 -2014-05-25 03:00:00,7785.0 -2014-05-25 04:00:00,7600.0 -2014-05-25 05:00:00,7443.0 -2014-05-25 06:00:00,7447.0 -2014-05-25 07:00:00,7237.0 -2014-05-25 08:00:00,7269.0 -2014-05-25 09:00:00,7657.0 -2014-05-25 10:00:00,8239.0 -2014-05-25 11:00:00,8718.0 -2014-05-25 12:00:00,9203.0 -2014-05-25 13:00:00,9507.0 -2014-05-25 14:00:00,9775.0 -2014-05-25 15:00:00,9990.0 -2014-05-25 16:00:00,10260.0 -2014-05-25 17:00:00,10512.0 -2014-05-25 18:00:00,10728.0 -2014-05-25 19:00:00,10837.0 -2014-05-25 20:00:00,10600.0 -2014-05-25 21:00:00,10299.0 -2014-05-25 22:00:00,10421.0 -2014-05-25 23:00:00,10383.0 -2014-05-26 00:00:00,9816.0 -2014-05-24 01:00:00,9064.0 -2014-05-24 02:00:00,8532.0 -2014-05-24 03:00:00,8124.0 -2014-05-24 04:00:00,7951.0 -2014-05-24 05:00:00,7817.0 -2014-05-24 06:00:00,7815.0 -2014-05-24 07:00:00,7745.0 -2014-05-24 08:00:00,7940.0 -2014-05-24 09:00:00,8332.0 -2014-05-24 10:00:00,8894.0 -2014-05-24 11:00:00,9278.0 -2014-05-24 12:00:00,9567.0 -2014-05-24 13:00:00,9728.0 -2014-05-24 14:00:00,9799.0 -2014-05-24 15:00:00,9814.0 -2014-05-24 16:00:00,9812.0 -2014-05-24 17:00:00,9843.0 -2014-05-24 18:00:00,9785.0 -2014-05-24 19:00:00,9703.0 -2014-05-24 20:00:00,9525.0 -2014-05-24 21:00:00,9444.0 -2014-05-24 22:00:00,9764.0 -2014-05-24 23:00:00,9719.0 -2014-05-25 00:00:00,9215.0 -2014-05-23 01:00:00,9447.0 -2014-05-23 02:00:00,8880.0 -2014-05-23 03:00:00,8492.0 -2014-05-23 04:00:00,8282.0 -2014-05-23 05:00:00,8206.0 -2014-05-23 06:00:00,8347.0 -2014-05-23 07:00:00,8690.0 -2014-05-23 08:00:00,9512.0 -2014-05-23 09:00:00,10392.0 -2014-05-23 10:00:00,10915.0 -2014-05-23 11:00:00,11243.0 -2014-05-23 12:00:00,11497.0 -2014-05-23 13:00:00,11573.0 -2014-05-23 14:00:00,11601.0 -2014-05-23 15:00:00,11666.0 -2014-05-23 16:00:00,11644.0 -2014-05-23 17:00:00,11567.0 -2014-05-23 18:00:00,11460.0 -2014-05-23 19:00:00,11198.0 -2014-05-23 20:00:00,10852.0 -2014-05-23 21:00:00,10521.0 -2014-05-23 22:00:00,10696.0 -2014-05-23 23:00:00,10537.0 -2014-05-24 00:00:00,9847.0 -2014-05-22 01:00:00,11068.0 -2014-05-22 02:00:00,10127.0 -2014-05-22 03:00:00,9477.0 -2014-05-22 04:00:00,9080.0 -2014-05-22 05:00:00,8861.0 -2014-05-22 06:00:00,8926.0 -2014-05-22 07:00:00,9217.0 -2014-05-22 08:00:00,10065.0 -2014-05-22 09:00:00,10976.0 -2014-05-22 10:00:00,11590.0 -2014-05-22 11:00:00,11999.0 -2014-05-22 12:00:00,12282.0 -2014-05-22 13:00:00,12371.0 -2014-05-22 14:00:00,12421.0 -2014-05-22 15:00:00,12537.0 -2014-05-22 16:00:00,12501.0 -2014-05-22 17:00:00,12399.0 -2014-05-22 18:00:00,12172.0 -2014-05-22 19:00:00,11846.0 -2014-05-22 20:00:00,11501.0 -2014-05-22 21:00:00,11189.0 -2014-05-22 22:00:00,11357.0 -2014-05-22 23:00:00,11159.0 -2014-05-23 00:00:00,10301.0 -2014-05-21 01:00:00,11066.0 -2014-05-21 02:00:00,10344.0 -2014-05-21 03:00:00,9842.0 -2014-05-21 04:00:00,9465.0 -2014-05-21 05:00:00,9257.0 -2014-05-21 06:00:00,9351.0 -2014-05-21 07:00:00,9918.0 -2014-05-21 08:00:00,10884.0 -2014-05-21 09:00:00,11787.0 -2014-05-21 10:00:00,12357.0 -2014-05-21 11:00:00,12935.0 -2014-05-21 12:00:00,13556.0 -2014-05-21 13:00:00,14038.0 -2014-05-21 14:00:00,14435.0 -2014-05-21 15:00:00,14851.0 -2014-05-21 16:00:00,15107.0 -2014-05-21 17:00:00,15361.0 -2014-05-21 18:00:00,15512.0 -2014-05-21 19:00:00,15395.0 -2014-05-21 20:00:00,14993.0 -2014-05-21 21:00:00,14441.0 -2014-05-21 22:00:00,14299.0 -2014-05-21 23:00:00,13715.0 -2014-05-22 00:00:00,12376.0 -2014-05-20 01:00:00,9273.0 -2014-05-20 02:00:00,8759.0 -2014-05-20 03:00:00,8432.0 -2014-05-20 04:00:00,8237.0 -2014-05-20 05:00:00,8193.0 -2014-05-20 06:00:00,8360.0 -2014-05-20 07:00:00,8880.0 -2014-05-20 08:00:00,9775.0 -2014-05-20 09:00:00,10694.0 -2014-05-20 10:00:00,11314.0 -2014-05-20 11:00:00,11815.0 -2014-05-20 12:00:00,12289.0 -2014-05-20 13:00:00,12671.0 -2014-05-20 14:00:00,13041.0 -2014-05-20 15:00:00,13487.0 -2014-05-20 16:00:00,13753.0 -2014-05-20 17:00:00,14025.0 -2014-05-20 18:00:00,14202.0 -2014-05-20 19:00:00,14176.0 -2014-05-20 20:00:00,13903.0 -2014-05-20 21:00:00,14069.0 -2014-05-20 22:00:00,14008.0 -2014-05-20 23:00:00,13252.0 -2014-05-21 00:00:00,12137.0 -2014-05-19 01:00:00,8600.0 -2014-05-19 02:00:00,8141.0 -2014-05-19 03:00:00,7942.0 -2014-05-19 04:00:00,7857.0 -2014-05-19 05:00:00,7867.0 -2014-05-19 06:00:00,8119.0 -2014-05-19 07:00:00,8683.0 -2014-05-19 08:00:00,9569.0 -2014-05-19 09:00:00,10440.0 -2014-05-19 10:00:00,10853.0 -2014-05-19 11:00:00,11155.0 -2014-05-19 12:00:00,11412.0 -2014-05-19 13:00:00,11499.0 -2014-05-19 14:00:00,11504.0 -2014-05-19 15:00:00,11559.0 -2014-05-19 16:00:00,11495.0 -2014-05-19 17:00:00,11373.0 -2014-05-19 18:00:00,11197.0 -2014-05-19 19:00:00,11003.0 -2014-05-19 20:00:00,10864.0 -2014-05-19 21:00:00,11017.0 -2014-05-19 22:00:00,11205.0 -2014-05-19 23:00:00,10811.0 -2014-05-20 00:00:00,10005.0 -2014-05-18 01:00:00,8723.0 -2014-05-18 02:00:00,8309.0 -2014-05-18 03:00:00,8087.0 -2014-05-18 04:00:00,7904.0 -2014-05-18 05:00:00,7828.0 -2014-05-18 06:00:00,7821.0 -2014-05-18 07:00:00,7833.0 -2014-05-18 08:00:00,7760.0 -2014-05-18 09:00:00,8050.0 -2014-05-18 10:00:00,8306.0 -2014-05-18 11:00:00,8634.0 -2014-05-18 12:00:00,8792.0 -2014-05-18 13:00:00,8916.0 -2014-05-18 14:00:00,8957.0 -2014-05-18 15:00:00,8975.0 -2014-05-18 16:00:00,8979.0 -2014-05-18 17:00:00,9032.0 -2014-05-18 18:00:00,9048.0 -2014-05-18 19:00:00,9109.0 -2014-05-18 20:00:00,9135.0 -2014-05-18 21:00:00,9258.0 -2014-05-18 22:00:00,9766.0 -2014-05-18 23:00:00,9712.0 -2014-05-19 00:00:00,9188.0 -2014-05-17 01:00:00,9510.0 -2014-05-17 02:00:00,8952.0 -2014-05-17 03:00:00,8657.0 -2014-05-17 04:00:00,8476.0 -2014-05-17 05:00:00,8416.0 -2014-05-17 06:00:00,8490.0 -2014-05-17 07:00:00,8602.0 -2014-05-17 08:00:00,8834.0 -2014-05-17 09:00:00,9176.0 -2014-05-17 10:00:00,9500.0 -2014-05-17 11:00:00,9653.0 -2014-05-17 12:00:00,9787.0 -2014-05-17 13:00:00,9713.0 -2014-05-17 14:00:00,9592.0 -2014-05-17 15:00:00,9446.0 -2014-05-17 16:00:00,9315.0 -2014-05-17 17:00:00,9216.0 -2014-05-17 18:00:00,9177.0 -2014-05-17 19:00:00,9144.0 -2014-05-17 20:00:00,9089.0 -2014-05-17 21:00:00,9183.0 -2014-05-17 22:00:00,9681.0 -2014-05-17 23:00:00,9658.0 -2014-05-18 00:00:00,9207.0 -2014-05-16 01:00:00,9399.0 -2014-05-16 02:00:00,8909.0 -2014-05-16 03:00:00,8624.0 -2014-05-16 04:00:00,8454.0 -2014-05-16 05:00:00,8405.0 -2014-05-16 06:00:00,8639.0 -2014-05-16 07:00:00,9292.0 -2014-05-16 08:00:00,10227.0 -2014-05-16 09:00:00,10970.0 -2014-05-16 10:00:00,11354.0 -2014-05-16 11:00:00,11567.0 -2014-05-16 12:00:00,11616.0 -2014-05-16 13:00:00,11591.0 -2014-05-16 14:00:00,11454.0 -2014-05-16 15:00:00,11400.0 -2014-05-16 16:00:00,11285.0 -2014-05-16 17:00:00,11096.0 -2014-05-16 18:00:00,10981.0 -2014-05-16 19:00:00,10810.0 -2014-05-16 20:00:00,10737.0 -2014-05-16 21:00:00,10852.0 -2014-05-16 22:00:00,11114.0 -2014-05-16 23:00:00,10871.0 -2014-05-17 00:00:00,10208.0 -2014-05-15 01:00:00,9254.0 -2014-05-15 02:00:00,8781.0 -2014-05-15 03:00:00,8533.0 -2014-05-15 04:00:00,8321.0 -2014-05-15 05:00:00,8300.0 -2014-05-15 06:00:00,8511.0 -2014-05-15 07:00:00,9203.0 -2014-05-15 08:00:00,10144.0 -2014-05-15 09:00:00,10924.0 -2014-05-15 10:00:00,11342.0 -2014-05-15 11:00:00,11480.0 -2014-05-15 12:00:00,11497.0 -2014-05-15 13:00:00,11330.0 -2014-05-15 14:00:00,11167.0 -2014-05-15 15:00:00,11194.0 -2014-05-15 16:00:00,11100.0 -2014-05-15 17:00:00,10977.0 -2014-05-15 18:00:00,10851.0 -2014-05-15 19:00:00,10692.0 -2014-05-15 20:00:00,10563.0 -2014-05-15 21:00:00,10635.0 -2014-05-15 22:00:00,11096.0 -2014-05-15 23:00:00,10858.0 -2014-05-16 00:00:00,10116.0 -2014-05-14 01:00:00,9184.0 -2014-05-14 02:00:00,8651.0 -2014-05-14 03:00:00,8332.0 -2014-05-14 04:00:00,8147.0 -2014-05-14 05:00:00,8090.0 -2014-05-14 06:00:00,8257.0 -2014-05-14 07:00:00,8786.0 -2014-05-14 08:00:00,9576.0 -2014-05-14 09:00:00,10359.0 -2014-05-14 10:00:00,10700.0 -2014-05-14 11:00:00,10858.0 -2014-05-14 12:00:00,10994.0 -2014-05-14 13:00:00,11009.0 -2014-05-14 14:00:00,10997.0 -2014-05-14 15:00:00,11030.0 -2014-05-14 16:00:00,10914.0 -2014-05-14 17:00:00,10809.0 -2014-05-14 18:00:00,10723.0 -2014-05-14 19:00:00,10653.0 -2014-05-14 20:00:00,10614.0 -2014-05-14 21:00:00,10683.0 -2014-05-14 22:00:00,11029.0 -2014-05-14 23:00:00,10685.0 -2014-05-15 00:00:00,9919.0 -2014-05-13 01:00:00,11165.0 -2014-05-13 02:00:00,10160.0 -2014-05-13 03:00:00,9669.0 -2014-05-13 04:00:00,9360.0 -2014-05-13 05:00:00,9276.0 -2014-05-13 06:00:00,9368.0 -2014-05-13 07:00:00,9896.0 -2014-05-13 08:00:00,10741.0 -2014-05-13 09:00:00,11577.0 -2014-05-13 10:00:00,12002.0 -2014-05-13 11:00:00,12082.0 -2014-05-13 12:00:00,12132.0 -2014-05-13 13:00:00,12046.0 -2014-05-13 14:00:00,11988.0 -2014-05-13 15:00:00,11919.0 -2014-05-13 16:00:00,11682.0 -2014-05-13 17:00:00,11507.0 -2014-05-13 18:00:00,11282.0 -2014-05-13 19:00:00,11115.0 -2014-05-13 20:00:00,10923.0 -2014-05-13 21:00:00,10939.0 -2014-05-13 22:00:00,11168.0 -2014-05-13 23:00:00,10780.0 -2014-05-14 00:00:00,9999.0 -2014-05-12 01:00:00,9212.0 -2014-05-12 02:00:00,8708.0 -2014-05-12 03:00:00,8440.0 -2014-05-12 04:00:00,8248.0 -2014-05-12 05:00:00,8227.0 -2014-05-12 06:00:00,8500.0 -2014-05-12 07:00:00,9193.0 -2014-05-12 08:00:00,10247.0 -2014-05-12 09:00:00,11394.0 -2014-05-12 10:00:00,12154.0 -2014-05-12 11:00:00,12718.0 -2014-05-12 12:00:00,13281.0 -2014-05-12 13:00:00,13604.0 -2014-05-12 14:00:00,13843.0 -2014-05-12 15:00:00,14151.0 -2014-05-12 16:00:00,14299.0 -2014-05-12 17:00:00,14384.0 -2014-05-12 18:00:00,14494.0 -2014-05-12 19:00:00,14482.0 -2014-05-12 20:00:00,14172.0 -2014-05-12 21:00:00,14060.0 -2014-05-12 22:00:00,14135.0 -2014-05-12 23:00:00,13442.0 -2014-05-13 00:00:00,12363.0 -2014-05-11 01:00:00,8869.0 -2014-05-11 02:00:00,8353.0 -2014-05-11 03:00:00,8039.0 -2014-05-11 04:00:00,7787.0 -2014-05-11 05:00:00,7649.0 -2014-05-11 06:00:00,7583.0 -2014-05-11 07:00:00,7565.0 -2014-05-11 08:00:00,7506.0 -2014-05-11 09:00:00,8012.0 -2014-05-11 10:00:00,8411.0 -2014-05-11 11:00:00,8875.0 -2014-05-11 12:00:00,9245.0 -2014-05-11 13:00:00,9554.0 -2014-05-11 14:00:00,9841.0 -2014-05-11 15:00:00,10117.0 -2014-05-11 16:00:00,10401.0 -2014-05-11 17:00:00,10490.0 -2014-05-11 18:00:00,10516.0 -2014-05-11 19:00:00,10721.0 -2014-05-11 20:00:00,10721.0 -2014-05-11 21:00:00,10696.0 -2014-05-11 22:00:00,10768.0 -2014-05-11 23:00:00,10480.0 -2014-05-12 00:00:00,9860.0 -2014-05-10 01:00:00,9277.0 -2014-05-10 02:00:00,8713.0 -2014-05-10 03:00:00,8282.0 -2014-05-10 04:00:00,7980.0 -2014-05-10 05:00:00,7868.0 -2014-05-10 06:00:00,7906.0 -2014-05-10 07:00:00,7933.0 -2014-05-10 08:00:00,8133.0 -2014-05-10 09:00:00,8619.0 -2014-05-10 10:00:00,9172.0 -2014-05-10 11:00:00,9507.0 -2014-05-10 12:00:00,9801.0 -2014-05-10 13:00:00,9909.0 -2014-05-10 14:00:00,9951.0 -2014-05-10 15:00:00,9863.0 -2014-05-10 16:00:00,9907.0 -2014-05-10 17:00:00,9898.0 -2014-05-10 18:00:00,9955.0 -2014-05-10 19:00:00,9935.0 -2014-05-10 20:00:00,9832.0 -2014-05-10 21:00:00,9740.0 -2014-05-10 22:00:00,10064.0 -2014-05-10 23:00:00,9987.0 -2014-05-11 00:00:00,9449.0 -2014-05-09 01:00:00,10891.0 -2014-05-09 02:00:00,10097.0 -2014-05-09 03:00:00,9589.0 -2014-05-09 04:00:00,9240.0 -2014-05-09 05:00:00,9017.0 -2014-05-09 06:00:00,9141.0 -2014-05-09 07:00:00,9720.0 -2014-05-09 08:00:00,10663.0 -2014-05-09 09:00:00,11496.0 -2014-05-09 10:00:00,11914.0 -2014-05-09 11:00:00,12081.0 -2014-05-09 12:00:00,12300.0 -2014-05-09 13:00:00,12428.0 -2014-05-09 14:00:00,12505.0 -2014-05-09 15:00:00,12546.0 -2014-05-09 16:00:00,12397.0 -2014-05-09 17:00:00,12253.0 -2014-05-09 18:00:00,12080.0 -2014-05-09 19:00:00,11750.0 -2014-05-09 20:00:00,11315.0 -2014-05-09 21:00:00,10932.0 -2014-05-09 22:00:00,11112.0 -2014-05-09 23:00:00,10792.0 -2014-05-10 00:00:00,10085.0 -2014-05-08 01:00:00,9200.0 -2014-05-08 02:00:00,8733.0 -2014-05-08 03:00:00,8435.0 -2014-05-08 04:00:00,8219.0 -2014-05-08 05:00:00,8187.0 -2014-05-08 06:00:00,8379.0 -2014-05-08 07:00:00,9001.0 -2014-05-08 08:00:00,9820.0 -2014-05-08 09:00:00,10730.0 -2014-05-08 10:00:00,11290.0 -2014-05-08 11:00:00,11637.0 -2014-05-08 12:00:00,11947.0 -2014-05-08 13:00:00,12197.0 -2014-05-08 14:00:00,12455.0 -2014-05-08 15:00:00,12863.0 -2014-05-08 16:00:00,13090.0 -2014-05-08 17:00:00,13222.0 -2014-05-08 18:00:00,13366.0 -2014-05-08 19:00:00,13197.0 -2014-05-08 20:00:00,12876.0 -2014-05-08 21:00:00,12850.0 -2014-05-08 22:00:00,13223.0 -2014-05-08 23:00:00,12849.0 -2014-05-09 00:00:00,11929.0 -2014-05-07 01:00:00,9064.0 -2014-05-07 02:00:00,8564.0 -2014-05-07 03:00:00,8267.0 -2014-05-07 04:00:00,8106.0 -2014-05-07 05:00:00,8071.0 -2014-05-07 06:00:00,8282.0 -2014-05-07 07:00:00,8890.0 -2014-05-07 08:00:00,9629.0 -2014-05-07 09:00:00,10324.0 -2014-05-07 10:00:00,10649.0 -2014-05-07 11:00:00,10865.0 -2014-05-07 12:00:00,11091.0 -2014-05-07 13:00:00,11165.0 -2014-05-07 14:00:00,11254.0 -2014-05-07 15:00:00,11411.0 -2014-05-07 16:00:00,11413.0 -2014-05-07 17:00:00,11408.0 -2014-05-07 18:00:00,11098.0 -2014-05-07 19:00:00,10900.0 -2014-05-07 20:00:00,10622.0 -2014-05-07 21:00:00,10561.0 -2014-05-07 22:00:00,11068.0 -2014-05-07 23:00:00,10753.0 -2014-05-08 00:00:00,9997.0 -2014-05-06 01:00:00,9199.0 -2014-05-06 02:00:00,8768.0 -2014-05-06 03:00:00,8496.0 -2014-05-06 04:00:00,8342.0 -2014-05-06 05:00:00,8314.0 -2014-05-06 06:00:00,8531.0 -2014-05-06 07:00:00,9172.0 -2014-05-06 08:00:00,9919.0 -2014-05-06 09:00:00,10552.0 -2014-05-06 10:00:00,10774.0 -2014-05-06 11:00:00,10879.0 -2014-05-06 12:00:00,11011.0 -2014-05-06 13:00:00,11016.0 -2014-05-06 14:00:00,11002.0 -2014-05-06 15:00:00,11010.0 -2014-05-06 16:00:00,10906.0 -2014-05-06 17:00:00,10759.0 -2014-05-06 18:00:00,10627.0 -2014-05-06 19:00:00,10452.0 -2014-05-06 20:00:00,10299.0 -2014-05-06 21:00:00,10472.0 -2014-05-06 22:00:00,10942.0 -2014-05-06 23:00:00,10561.0 -2014-05-07 00:00:00,9816.0 -2014-05-05 01:00:00,8506.0 -2014-05-05 02:00:00,8169.0 -2014-05-05 03:00:00,8025.0 -2014-05-05 04:00:00,7934.0 -2014-05-05 05:00:00,7982.0 -2014-05-05 06:00:00,8239.0 -2014-05-05 07:00:00,9011.0 -2014-05-05 08:00:00,10021.0 -2014-05-05 09:00:00,10669.0 -2014-05-05 10:00:00,10893.0 -2014-05-05 11:00:00,11008.0 -2014-05-05 12:00:00,11041.0 -2014-05-05 13:00:00,10991.0 -2014-05-05 14:00:00,10920.0 -2014-05-05 15:00:00,10909.0 -2014-05-05 16:00:00,10783.0 -2014-05-05 17:00:00,10617.0 -2014-05-05 18:00:00,10502.0 -2014-05-05 19:00:00,10367.0 -2014-05-05 20:00:00,10225.0 -2014-05-05 21:00:00,10370.0 -2014-05-05 22:00:00,10953.0 -2014-05-05 23:00:00,10664.0 -2014-05-06 00:00:00,9934.0 -2014-05-04 01:00:00,8408.0 -2014-05-04 02:00:00,8009.0 -2014-05-04 03:00:00,7743.0 -2014-05-04 04:00:00,7560.0 -2014-05-04 05:00:00,7465.0 -2014-05-04 06:00:00,7483.0 -2014-05-04 07:00:00,7524.0 -2014-05-04 08:00:00,7469.0 -2014-05-04 09:00:00,7752.0 -2014-05-04 10:00:00,8112.0 -2014-05-04 11:00:00,8381.0 -2014-05-04 12:00:00,8582.0 -2014-05-04 13:00:00,8602.0 -2014-05-04 14:00:00,8635.0 -2014-05-04 15:00:00,8663.0 -2014-05-04 16:00:00,8623.0 -2014-05-04 17:00:00,8607.0 -2014-05-04 18:00:00,8603.0 -2014-05-04 19:00:00,8666.0 -2014-05-04 20:00:00,8710.0 -2014-05-04 21:00:00,9001.0 -2014-05-04 22:00:00,9639.0 -2014-05-04 23:00:00,9538.0 -2014-05-05 00:00:00,9042.0 -2014-05-03 01:00:00,9325.0 -2014-05-03 02:00:00,8747.0 -2014-05-03 03:00:00,8439.0 -2014-05-03 04:00:00,8207.0 -2014-05-03 05:00:00,8127.0 -2014-05-03 06:00:00,8153.0 -2014-05-03 07:00:00,8360.0 -2014-05-03 08:00:00,8528.0 -2014-05-03 09:00:00,8857.0 -2014-05-03 10:00:00,9159.0 -2014-05-03 11:00:00,9344.0 -2014-05-03 12:00:00,9482.0 -2014-05-03 13:00:00,9437.0 -2014-05-03 14:00:00,9390.0 -2014-05-03 15:00:00,9237.0 -2014-05-03 16:00:00,9107.0 -2014-05-03 17:00:00,9046.0 -2014-05-03 18:00:00,8995.0 -2014-05-03 19:00:00,8987.0 -2014-05-03 20:00:00,8950.0 -2014-05-03 21:00:00,9167.0 -2014-05-03 22:00:00,9623.0 -2014-05-03 23:00:00,9429.0 -2014-05-04 00:00:00,8931.0 -2014-05-02 01:00:00,9406.0 -2014-05-02 02:00:00,8909.0 -2014-05-02 03:00:00,8596.0 -2014-05-02 04:00:00,8394.0 -2014-05-02 05:00:00,8376.0 -2014-05-02 06:00:00,8588.0 -2014-05-02 07:00:00,9227.0 -2014-05-02 08:00:00,10038.0 -2014-05-02 09:00:00,10689.0 -2014-05-02 10:00:00,11008.0 -2014-05-02 11:00:00,11160.0 -2014-05-02 12:00:00,11233.0 -2014-05-02 13:00:00,11191.0 -2014-05-02 14:00:00,11170.0 -2014-05-02 15:00:00,11157.0 -2014-05-02 16:00:00,11046.0 -2014-05-02 17:00:00,10900.0 -2014-05-02 18:00:00,10803.0 -2014-05-02 19:00:00,10610.0 -2014-05-02 20:00:00,10514.0 -2014-05-02 21:00:00,10711.0 -2014-05-02 22:00:00,10890.0 -2014-05-02 23:00:00,10624.0 -2014-05-03 00:00:00,9992.0 -2014-05-01 01:00:00,9352.0 -2014-05-01 02:00:00,8880.0 -2014-05-01 03:00:00,8602.0 -2014-05-01 04:00:00,8468.0 -2014-05-01 05:00:00,8419.0 -2014-05-01 06:00:00,8660.0 -2014-05-01 07:00:00,9335.0 -2014-05-01 08:00:00,10308.0 -2014-05-01 09:00:00,10973.0 -2014-05-01 10:00:00,11273.0 -2014-05-01 11:00:00,11439.0 -2014-05-01 12:00:00,11543.0 -2014-05-01 13:00:00,11482.0 -2014-05-01 14:00:00,11375.0 -2014-05-01 15:00:00,11407.0 -2014-05-01 16:00:00,11291.0 -2014-05-01 17:00:00,11206.0 -2014-05-01 18:00:00,11192.0 -2014-05-01 19:00:00,11126.0 -2014-05-01 20:00:00,10964.0 -2014-05-01 21:00:00,11103.0 -2014-05-01 22:00:00,11343.0 -2014-05-01 23:00:00,10941.0 -2014-05-02 00:00:00,10190.0 -2014-04-30 01:00:00,9103.0 -2014-04-30 02:00:00,8621.0 -2014-04-30 03:00:00,8358.0 -2014-04-30 04:00:00,8171.0 -2014-04-30 05:00:00,8150.0 -2014-04-30 06:00:00,8318.0 -2014-04-30 07:00:00,9011.0 -2014-04-30 08:00:00,9868.0 -2014-04-30 09:00:00,10545.0 -2014-04-30 10:00:00,10877.0 -2014-04-30 11:00:00,11019.0 -2014-04-30 12:00:00,11154.0 -2014-04-30 13:00:00,11132.0 -2014-04-30 14:00:00,11080.0 -2014-04-30 15:00:00,11105.0 -2014-04-30 16:00:00,11020.0 -2014-04-30 17:00:00,10945.0 -2014-04-30 18:00:00,10927.0 -2014-04-30 19:00:00,10887.0 -2014-04-30 20:00:00,10777.0 -2014-04-30 21:00:00,10902.0 -2014-04-30 22:00:00,11199.0 -2014-04-30 23:00:00,10844.0 -2014-05-01 00:00:00,10097.0 -2014-04-29 01:00:00,9233.0 -2014-04-29 02:00:00,8772.0 -2014-04-29 03:00:00,8466.0 -2014-04-29 04:00:00,8317.0 -2014-04-29 05:00:00,8247.0 -2014-04-29 06:00:00,8432.0 -2014-04-29 07:00:00,9078.0 -2014-04-29 08:00:00,9949.0 -2014-04-29 09:00:00,10611.0 -2014-04-29 10:00:00,10887.0 -2014-04-29 11:00:00,11001.0 -2014-04-29 12:00:00,11105.0 -2014-04-29 13:00:00,11149.0 -2014-04-29 14:00:00,11184.0 -2014-04-29 15:00:00,11155.0 -2014-04-29 16:00:00,11047.0 -2014-04-29 17:00:00,10885.0 -2014-04-29 18:00:00,10792.0 -2014-04-29 19:00:00,10703.0 -2014-04-29 20:00:00,10548.0 -2014-04-29 21:00:00,10777.0 -2014-04-29 22:00:00,11011.0 -2014-04-29 23:00:00,10603.0 -2014-04-30 00:00:00,9832.0 -2014-04-28 01:00:00,8574.0 -2014-04-28 02:00:00,8287.0 -2014-04-28 03:00:00,8125.0 -2014-04-28 04:00:00,7949.0 -2014-04-28 05:00:00,8020.0 -2014-04-28 06:00:00,8255.0 -2014-04-28 07:00:00,9086.0 -2014-04-28 08:00:00,10200.0 -2014-04-28 09:00:00,10975.0 -2014-04-28 10:00:00,11137.0 -2014-04-28 11:00:00,11215.0 -2014-04-28 12:00:00,11390.0 -2014-04-28 13:00:00,11527.0 -2014-04-28 14:00:00,11412.0 -2014-04-28 15:00:00,11329.0 -2014-04-28 16:00:00,11176.0 -2014-04-28 17:00:00,11052.0 -2014-04-28 18:00:00,11018.0 -2014-04-28 19:00:00,11099.0 -2014-04-28 20:00:00,11052.0 -2014-04-28 21:00:00,11214.0 -2014-04-28 22:00:00,11269.0 -2014-04-28 23:00:00,10797.0 -2014-04-29 00:00:00,9972.0 -2014-04-27 01:00:00,8842.0 -2014-04-27 02:00:00,8390.0 -2014-04-27 03:00:00,8153.0 -2014-04-27 04:00:00,7970.0 -2014-04-27 05:00:00,7883.0 -2014-04-27 06:00:00,7846.0 -2014-04-27 07:00:00,8028.0 -2014-04-27 08:00:00,8060.0 -2014-04-27 09:00:00,8307.0 -2014-04-27 10:00:00,8667.0 -2014-04-27 11:00:00,8886.0 -2014-04-27 12:00:00,8911.0 -2014-04-27 13:00:00,8988.0 -2014-04-27 14:00:00,8946.0 -2014-04-27 15:00:00,8916.0 -2014-04-27 16:00:00,8869.0 -2014-04-27 17:00:00,8837.0 -2014-04-27 18:00:00,8882.0 -2014-04-27 19:00:00,9116.0 -2014-04-27 20:00:00,9391.0 -2014-04-27 21:00:00,9734.0 -2014-04-27 22:00:00,9821.0 -2014-04-27 23:00:00,9558.0 -2014-04-28 00:00:00,9050.0 -2014-04-26 01:00:00,9119.0 -2014-04-26 02:00:00,8584.0 -2014-04-26 03:00:00,8230.0 -2014-04-26 04:00:00,8036.0 -2014-04-26 05:00:00,7899.0 -2014-04-26 06:00:00,7962.0 -2014-04-26 07:00:00,8149.0 -2014-04-26 08:00:00,8280.0 -2014-04-26 09:00:00,8683.0 -2014-04-26 10:00:00,9106.0 -2014-04-26 11:00:00,9315.0 -2014-04-26 12:00:00,9465.0 -2014-04-26 13:00:00,9452.0 -2014-04-26 14:00:00,9355.0 -2014-04-26 15:00:00,9179.0 -2014-04-26 16:00:00,9089.0 -2014-04-26 17:00:00,9002.0 -2014-04-26 18:00:00,9005.0 -2014-04-26 19:00:00,9007.0 -2014-04-26 20:00:00,9088.0 -2014-04-26 21:00:00,9431.0 -2014-04-26 22:00:00,10004.0 -2014-04-26 23:00:00,9829.0 -2014-04-27 00:00:00,9391.0 -2014-04-25 01:00:00,9179.0 -2014-04-25 02:00:00,8688.0 -2014-04-25 03:00:00,8447.0 -2014-04-25 04:00:00,8300.0 -2014-04-25 05:00:00,8217.0 -2014-04-25 06:00:00,8407.0 -2014-04-25 07:00:00,9076.0 -2014-04-25 08:00:00,9996.0 -2014-04-25 09:00:00,10628.0 -2014-04-25 10:00:00,10850.0 -2014-04-25 11:00:00,10864.0 -2014-04-25 12:00:00,10946.0 -2014-04-25 13:00:00,10974.0 -2014-04-25 14:00:00,10978.0 -2014-04-25 15:00:00,11023.0 -2014-04-25 16:00:00,10975.0 -2014-04-25 17:00:00,10825.0 -2014-04-25 18:00:00,10678.0 -2014-04-25 19:00:00,10473.0 -2014-04-25 20:00:00,10242.0 -2014-04-25 21:00:00,10348.0 -2014-04-25 22:00:00,10704.0 -2014-04-25 23:00:00,10409.0 -2014-04-26 00:00:00,9752.0 -2014-04-24 01:00:00,9497.0 -2014-04-24 02:00:00,9000.0 -2014-04-24 03:00:00,8705.0 -2014-04-24 04:00:00,8545.0 -2014-04-24 05:00:00,8498.0 -2014-04-24 06:00:00,8763.0 -2014-04-24 07:00:00,9439.0 -2014-04-24 08:00:00,10288.0 -2014-04-24 09:00:00,10942.0 -2014-04-24 10:00:00,11196.0 -2014-04-24 11:00:00,11213.0 -2014-04-24 12:00:00,11278.0 -2014-04-24 13:00:00,11253.0 -2014-04-24 14:00:00,11199.0 -2014-04-24 15:00:00,11235.0 -2014-04-24 16:00:00,11107.0 -2014-04-24 17:00:00,10957.0 -2014-04-24 18:00:00,10856.0 -2014-04-24 19:00:00,10766.0 -2014-04-24 20:00:00,10704.0 -2014-04-24 21:00:00,11043.0 -2014-04-24 22:00:00,11145.0 -2014-04-24 23:00:00,10663.0 -2014-04-25 00:00:00,9886.0 -2014-04-23 01:00:00,9296.0 -2014-04-23 02:00:00,8853.0 -2014-04-23 03:00:00,8558.0 -2014-04-23 04:00:00,8436.0 -2014-04-23 05:00:00,8446.0 -2014-04-23 06:00:00,8722.0 -2014-04-23 07:00:00,9437.0 -2014-04-23 08:00:00,10247.0 -2014-04-23 09:00:00,10836.0 -2014-04-23 10:00:00,11108.0 -2014-04-23 11:00:00,11176.0 -2014-04-23 12:00:00,11210.0 -2014-04-23 13:00:00,11158.0 -2014-04-23 14:00:00,11091.0 -2014-04-23 15:00:00,11084.0 -2014-04-23 16:00:00,10973.0 -2014-04-23 17:00:00,10805.0 -2014-04-23 18:00:00,10696.0 -2014-04-23 19:00:00,10644.0 -2014-04-23 20:00:00,10656.0 -2014-04-23 21:00:00,11091.0 -2014-04-23 22:00:00,11317.0 -2014-04-23 23:00:00,10903.0 -2014-04-24 00:00:00,10191.0 -2014-04-22 01:00:00,9224.0 -2014-04-22 02:00:00,8687.0 -2014-04-22 03:00:00,8340.0 -2014-04-22 04:00:00,8098.0 -2014-04-22 05:00:00,8011.0 -2014-04-22 06:00:00,8211.0 -2014-04-22 07:00:00,8822.0 -2014-04-22 08:00:00,9615.0 -2014-04-22 09:00:00,10316.0 -2014-04-22 10:00:00,10646.0 -2014-04-22 11:00:00,10804.0 -2014-04-22 12:00:00,10903.0 -2014-04-22 13:00:00,10879.0 -2014-04-22 14:00:00,10853.0 -2014-04-22 15:00:00,10903.0 -2014-04-22 16:00:00,10799.0 -2014-04-22 17:00:00,10658.0 -2014-04-22 18:00:00,10537.0 -2014-04-22 19:00:00,10429.0 -2014-04-22 20:00:00,10273.0 -2014-04-22 21:00:00,10555.0 -2014-04-22 22:00:00,11041.0 -2014-04-22 23:00:00,10688.0 -2014-04-23 00:00:00,10006.0 -2014-04-21 01:00:00,8299.0 -2014-04-21 02:00:00,7913.0 -2014-04-21 03:00:00,7728.0 -2014-04-21 04:00:00,7554.0 -2014-04-21 05:00:00,7563.0 -2014-04-21 06:00:00,7777.0 -2014-04-21 07:00:00,8472.0 -2014-04-21 08:00:00,9313.0 -2014-04-21 09:00:00,10142.0 -2014-04-21 10:00:00,10638.0 -2014-04-21 11:00:00,10929.0 -2014-04-21 12:00:00,11170.0 -2014-04-21 13:00:00,11275.0 -2014-04-21 14:00:00,11387.0 -2014-04-21 15:00:00,11526.0 -2014-04-21 16:00:00,11511.0 -2014-04-21 17:00:00,11367.0 -2014-04-21 18:00:00,11280.0 -2014-04-21 19:00:00,11238.0 -2014-04-21 20:00:00,11136.0 -2014-04-21 21:00:00,11299.0 -2014-04-21 22:00:00,11352.0 -2014-04-21 23:00:00,10827.0 -2014-04-22 00:00:00,10019.0 -2014-04-20 01:00:00,8582.0 -2014-04-20 02:00:00,8148.0 -2014-04-20 03:00:00,7833.0 -2014-04-20 04:00:00,7678.0 -2014-04-20 05:00:00,7548.0 -2014-04-20 06:00:00,7610.0 -2014-04-20 07:00:00,7681.0 -2014-04-20 08:00:00,7705.0 -2014-04-20 09:00:00,7878.0 -2014-04-20 10:00:00,8174.0 -2014-04-20 11:00:00,8309.0 -2014-04-20 12:00:00,8464.0 -2014-04-20 13:00:00,8501.0 -2014-04-20 14:00:00,8547.0 -2014-04-20 15:00:00,8480.0 -2014-04-20 16:00:00,8474.0 -2014-04-20 17:00:00,8433.0 -2014-04-20 18:00:00,8479.0 -2014-04-20 19:00:00,8502.0 -2014-04-20 20:00:00,8557.0 -2014-04-20 21:00:00,8961.0 -2014-04-20 22:00:00,9530.0 -2014-04-20 23:00:00,9341.0 -2014-04-21 00:00:00,8838.0 -2014-04-19 01:00:00,9149.0 -2014-04-19 02:00:00,8726.0 -2014-04-19 03:00:00,8438.0 -2014-04-19 04:00:00,8282.0 -2014-04-19 05:00:00,8208.0 -2014-04-19 06:00:00,8280.0 -2014-04-19 07:00:00,8533.0 -2014-04-19 08:00:00,8677.0 -2014-04-19 09:00:00,8977.0 -2014-04-19 10:00:00,9291.0 -2014-04-19 11:00:00,9461.0 -2014-04-19 12:00:00,9502.0 -2014-04-19 13:00:00,9430.0 -2014-04-19 14:00:00,9334.0 -2014-04-19 15:00:00,9200.0 -2014-04-19 16:00:00,9069.0 -2014-04-19 17:00:00,9049.0 -2014-04-19 18:00:00,8983.0 -2014-04-19 19:00:00,8989.0 -2014-04-19 20:00:00,8982.0 -2014-04-19 21:00:00,9326.0 -2014-04-19 22:00:00,9790.0 -2014-04-19 23:00:00,9572.0 -2014-04-20 00:00:00,9156.0 -2014-04-18 01:00:00,9192.0 -2014-04-18 02:00:00,8633.0 -2014-04-18 03:00:00,8353.0 -2014-04-18 04:00:00,8179.0 -2014-04-18 05:00:00,8132.0 -2014-04-18 06:00:00,8310.0 -2014-04-18 07:00:00,8843.0 -2014-04-18 08:00:00,9318.0 -2014-04-18 09:00:00,9713.0 -2014-04-18 10:00:00,9986.0 -2014-04-18 11:00:00,10138.0 -2014-04-18 12:00:00,10251.0 -2014-04-18 13:00:00,10217.0 -2014-04-18 14:00:00,10152.0 -2014-04-18 15:00:00,10107.0 -2014-04-18 16:00:00,9962.0 -2014-04-18 17:00:00,9852.0 -2014-04-18 18:00:00,9755.0 -2014-04-18 19:00:00,9702.0 -2014-04-18 20:00:00,9641.0 -2014-04-18 21:00:00,9979.0 -2014-04-18 22:00:00,10419.0 -2014-04-18 23:00:00,10277.0 -2014-04-19 00:00:00,9729.0 -2014-04-17 01:00:00,9758.0 -2014-04-17 02:00:00,9319.0 -2014-04-17 03:00:00,9012.0 -2014-04-17 04:00:00,8834.0 -2014-04-17 05:00:00,8860.0 -2014-04-17 06:00:00,9098.0 -2014-04-17 07:00:00,9798.0 -2014-04-17 08:00:00,10645.0 -2014-04-17 09:00:00,11153.0 -2014-04-17 10:00:00,11297.0 -2014-04-17 11:00:00,11342.0 -2014-04-17 12:00:00,11476.0 -2014-04-17 13:00:00,11268.0 -2014-04-17 14:00:00,11171.0 -2014-04-17 15:00:00,11176.0 -2014-04-17 16:00:00,11027.0 -2014-04-17 17:00:00,10831.0 -2014-04-17 18:00:00,10674.0 -2014-04-17 19:00:00,10512.0 -2014-04-17 20:00:00,10383.0 -2014-04-17 21:00:00,10712.0 -2014-04-17 22:00:00,10983.0 -2014-04-17 23:00:00,10612.0 -2014-04-18 00:00:00,9904.0 -2014-04-16 01:00:00,10172.0 -2014-04-16 02:00:00,9704.0 -2014-04-16 03:00:00,9455.0 -2014-04-16 04:00:00,9313.0 -2014-04-16 05:00:00,9346.0 -2014-04-16 06:00:00,9575.0 -2014-04-16 07:00:00,10285.0 -2014-04-16 08:00:00,11132.0 -2014-04-16 09:00:00,11630.0 -2014-04-16 10:00:00,11796.0 -2014-04-16 11:00:00,11818.0 -2014-04-16 12:00:00,11853.0 -2014-04-16 13:00:00,11735.0 -2014-04-16 14:00:00,11606.0 -2014-04-16 15:00:00,11499.0 -2014-04-16 16:00:00,11362.0 -2014-04-16 17:00:00,11109.0 -2014-04-16 18:00:00,11012.0 -2014-04-16 19:00:00,10928.0 -2014-04-16 20:00:00,10837.0 -2014-04-16 21:00:00,11347.0 -2014-04-16 22:00:00,11634.0 -2014-04-16 23:00:00,11246.0 -2014-04-17 00:00:00,10487.0 -2014-04-15 01:00:00,10120.0 -2014-04-15 02:00:00,9673.0 -2014-04-15 03:00:00,9404.0 -2014-04-15 04:00:00,9268.0 -2014-04-15 05:00:00,9285.0 -2014-04-15 06:00:00,9535.0 -2014-04-15 07:00:00,10266.0 -2014-04-15 08:00:00,11136.0 -2014-04-15 09:00:00,11664.0 -2014-04-15 10:00:00,11843.0 -2014-04-15 11:00:00,11882.0 -2014-04-15 12:00:00,11946.0 -2014-04-15 13:00:00,11940.0 -2014-04-15 14:00:00,11872.0 -2014-04-15 15:00:00,11853.0 -2014-04-15 16:00:00,11681.0 -2014-04-15 17:00:00,11567.0 -2014-04-15 18:00:00,11437.0 -2014-04-15 19:00:00,11410.0 -2014-04-15 20:00:00,11299.0 -2014-04-15 21:00:00,11681.0 -2014-04-15 22:00:00,12034.0 -2014-04-15 23:00:00,11627.0 -2014-04-16 00:00:00,10915.0 -2014-04-14 01:00:00,8709.0 -2014-04-14 02:00:00,8306.0 -2014-04-14 03:00:00,8110.0 -2014-04-14 04:00:00,7990.0 -2014-04-14 05:00:00,8015.0 -2014-04-14 06:00:00,8210.0 -2014-04-14 07:00:00,8968.0 -2014-04-14 08:00:00,10099.0 -2014-04-14 09:00:00,10908.0 -2014-04-14 10:00:00,11278.0 -2014-04-14 11:00:00,11460.0 -2014-04-14 12:00:00,11605.0 -2014-04-14 13:00:00,11642.0 -2014-04-14 14:00:00,11615.0 -2014-04-14 15:00:00,11566.0 -2014-04-14 16:00:00,11515.0 -2014-04-14 17:00:00,11544.0 -2014-04-14 18:00:00,11606.0 -2014-04-14 19:00:00,11702.0 -2014-04-14 20:00:00,11734.0 -2014-04-14 21:00:00,12043.0 -2014-04-14 22:00:00,12103.0 -2014-04-14 23:00:00,11631.0 -2014-04-15 00:00:00,10854.0 -2014-04-13 01:00:00,8841.0 -2014-04-13 02:00:00,8357.0 -2014-04-13 03:00:00,8053.0 -2014-04-13 04:00:00,7829.0 -2014-04-13 05:00:00,7724.0 -2014-04-13 06:00:00,7652.0 -2014-04-13 07:00:00,7718.0 -2014-04-13 08:00:00,7754.0 -2014-04-13 09:00:00,7893.0 -2014-04-13 10:00:00,8314.0 -2014-04-13 11:00:00,8620.0 -2014-04-13 12:00:00,8970.0 -2014-04-13 13:00:00,9038.0 -2014-04-13 14:00:00,9037.0 -2014-04-13 15:00:00,9036.0 -2014-04-13 16:00:00,9083.0 -2014-04-13 17:00:00,9164.0 -2014-04-13 18:00:00,9234.0 -2014-04-13 19:00:00,9347.0 -2014-04-13 20:00:00,9681.0 -2014-04-13 21:00:00,10031.0 -2014-04-13 22:00:00,10063.0 -2014-04-13 23:00:00,9752.0 -2014-04-14 00:00:00,9214.0 -2014-04-12 01:00:00,9188.0 -2014-04-12 02:00:00,8663.0 -2014-04-12 03:00:00,8304.0 -2014-04-12 04:00:00,8123.0 -2014-04-12 05:00:00,8026.0 -2014-04-12 06:00:00,8089.0 -2014-04-12 07:00:00,8345.0 -2014-04-12 08:00:00,8613.0 -2014-04-12 09:00:00,8937.0 -2014-04-12 10:00:00,9357.0 -2014-04-12 11:00:00,9626.0 -2014-04-12 12:00:00,9918.0 -2014-04-12 13:00:00,9975.0 -2014-04-12 14:00:00,9866.0 -2014-04-12 15:00:00,9706.0 -2014-04-12 16:00:00,9675.0 -2014-04-12 17:00:00,9605.0 -2014-04-12 18:00:00,9591.0 -2014-04-12 19:00:00,9571.0 -2014-04-12 20:00:00,9588.0 -2014-04-12 21:00:00,9986.0 -2014-04-12 22:00:00,10210.0 -2014-04-12 23:00:00,9936.0 -2014-04-13 00:00:00,9408.0 -2014-04-11 01:00:00,9232.0 -2014-04-11 02:00:00,8765.0 -2014-04-11 03:00:00,8509.0 -2014-04-11 04:00:00,8395.0 -2014-04-11 05:00:00,8394.0 -2014-04-11 06:00:00,8595.0 -2014-04-11 07:00:00,9311.0 -2014-04-11 08:00:00,10217.0 -2014-04-11 09:00:00,10710.0 -2014-04-11 10:00:00,10920.0 -2014-04-11 11:00:00,10988.0 -2014-04-11 12:00:00,11099.0 -2014-04-11 13:00:00,11099.0 -2014-04-11 14:00:00,11053.0 -2014-04-11 15:00:00,11066.0 -2014-04-11 16:00:00,10950.0 -2014-04-11 17:00:00,10790.0 -2014-04-11 18:00:00,10641.0 -2014-04-11 19:00:00,10459.0 -2014-04-11 20:00:00,10294.0 -2014-04-11 21:00:00,10590.0 -2014-04-11 22:00:00,10817.0 -2014-04-11 23:00:00,10442.0 -2014-04-12 00:00:00,9872.0 -2014-04-10 01:00:00,9376.0 -2014-04-10 02:00:00,8896.0 -2014-04-10 03:00:00,8614.0 -2014-04-10 04:00:00,8464.0 -2014-04-10 05:00:00,8443.0 -2014-04-10 06:00:00,8638.0 -2014-04-10 07:00:00,9320.0 -2014-04-10 08:00:00,10200.0 -2014-04-10 09:00:00,10761.0 -2014-04-10 10:00:00,11022.0 -2014-04-10 11:00:00,11137.0 -2014-04-10 12:00:00,11240.0 -2014-04-10 13:00:00,11222.0 -2014-04-10 14:00:00,11178.0 -2014-04-10 15:00:00,11175.0 -2014-04-10 16:00:00,11075.0 -2014-04-10 17:00:00,10907.0 -2014-04-10 18:00:00,10816.0 -2014-04-10 19:00:00,10699.0 -2014-04-10 20:00:00,10635.0 -2014-04-10 21:00:00,10899.0 -2014-04-10 22:00:00,11127.0 -2014-04-10 23:00:00,10707.0 -2014-04-11 00:00:00,9934.0 -2014-04-09 01:00:00,9476.0 -2014-04-09 02:00:00,9068.0 -2014-04-09 03:00:00,8810.0 -2014-04-09 04:00:00,8669.0 -2014-04-09 05:00:00,8723.0 -2014-04-09 06:00:00,8934.0 -2014-04-09 07:00:00,9693.0 -2014-04-09 08:00:00,10665.0 -2014-04-09 09:00:00,11073.0 -2014-04-09 10:00:00,11217.0 -2014-04-09 11:00:00,11214.0 -2014-04-09 12:00:00,11247.0 -2014-04-09 13:00:00,11204.0 -2014-04-09 14:00:00,11162.0 -2014-04-09 15:00:00,11132.0 -2014-04-09 16:00:00,11003.0 -2014-04-09 17:00:00,10886.0 -2014-04-09 18:00:00,10759.0 -2014-04-09 19:00:00,10646.0 -2014-04-09 20:00:00,10538.0 -2014-04-09 21:00:00,11064.0 -2014-04-09 22:00:00,11290.0 -2014-04-09 23:00:00,10840.0 -2014-04-10 00:00:00,10093.0 -2014-04-08 01:00:00,9582.0 -2014-04-08 02:00:00,9111.0 -2014-04-08 03:00:00,8809.0 -2014-04-08 04:00:00,8696.0 -2014-04-08 05:00:00,8658.0 -2014-04-08 06:00:00,8886.0 -2014-04-08 07:00:00,9587.0 -2014-04-08 08:00:00,10600.0 -2014-04-08 09:00:00,11121.0 -2014-04-08 10:00:00,11329.0 -2014-04-08 11:00:00,11360.0 -2014-04-08 12:00:00,11395.0 -2014-04-08 13:00:00,11305.0 -2014-04-08 14:00:00,11230.0 -2014-04-08 15:00:00,11224.0 -2014-04-08 16:00:00,11081.0 -2014-04-08 17:00:00,10941.0 -2014-04-08 18:00:00,10866.0 -2014-04-08 19:00:00,10795.0 -2014-04-08 20:00:00,10665.0 -2014-04-08 21:00:00,11084.0 -2014-04-08 22:00:00,11342.0 -2014-04-08 23:00:00,10916.0 -2014-04-09 00:00:00,10188.0 -2014-04-07 01:00:00,8896.0 -2014-04-07 02:00:00,8575.0 -2014-04-07 03:00:00,8418.0 -2014-04-07 04:00:00,8389.0 -2014-04-07 05:00:00,8465.0 -2014-04-07 06:00:00,8782.0 -2014-04-07 07:00:00,9564.0 -2014-04-07 08:00:00,10609.0 -2014-04-07 09:00:00,11246.0 -2014-04-07 10:00:00,11512.0 -2014-04-07 11:00:00,11568.0 -2014-04-07 12:00:00,11549.0 -2014-04-07 13:00:00,11465.0 -2014-04-07 14:00:00,11374.0 -2014-04-07 15:00:00,11373.0 -2014-04-07 16:00:00,11245.0 -2014-04-07 17:00:00,11137.0 -2014-04-07 18:00:00,11077.0 -2014-04-07 19:00:00,11097.0 -2014-04-07 20:00:00,11074.0 -2014-04-07 21:00:00,11513.0 -2014-04-07 22:00:00,11538.0 -2014-04-07 23:00:00,11104.0 -2014-04-08 00:00:00,10362.0 -2014-04-06 01:00:00,9321.0 -2014-04-06 02:00:00,8926.0 -2014-04-06 03:00:00,8698.0 -2014-04-06 04:00:00,8572.0 -2014-04-06 05:00:00,8503.0 -2014-04-06 06:00:00,8569.0 -2014-04-06 07:00:00,8716.0 -2014-04-06 08:00:00,8838.0 -2014-04-06 09:00:00,8884.0 -2014-04-06 10:00:00,9015.0 -2014-04-06 11:00:00,9130.0 -2014-04-06 12:00:00,9148.0 -2014-04-06 13:00:00,9105.0 -2014-04-06 14:00:00,9108.0 -2014-04-06 15:00:00,9040.0 -2014-04-06 16:00:00,8942.0 -2014-04-06 17:00:00,8848.0 -2014-04-06 18:00:00,8960.0 -2014-04-06 19:00:00,9105.0 -2014-04-06 20:00:00,9293.0 -2014-04-06 21:00:00,9842.0 -2014-04-06 22:00:00,10131.0 -2014-04-06 23:00:00,9849.0 -2014-04-07 00:00:00,9399.0 -2014-04-05 01:00:00,10347.0 -2014-04-05 02:00:00,9759.0 -2014-04-05 03:00:00,9424.0 -2014-04-05 04:00:00,9194.0 -2014-04-05 05:00:00,9125.0 -2014-04-05 06:00:00,9176.0 -2014-04-05 07:00:00,9475.0 -2014-04-05 08:00:00,9828.0 -2014-04-05 09:00:00,9999.0 -2014-04-05 10:00:00,10228.0 -2014-04-05 11:00:00,10298.0 -2014-04-05 12:00:00,10259.0 -2014-04-05 13:00:00,10109.0 -2014-04-05 14:00:00,9938.0 -2014-04-05 15:00:00,9734.0 -2014-04-05 16:00:00,9564.0 -2014-04-05 17:00:00,9406.0 -2014-04-05 18:00:00,9349.0 -2014-04-05 19:00:00,9351.0 -2014-04-05 20:00:00,9451.0 -2014-04-05 21:00:00,9971.0 -2014-04-05 22:00:00,10355.0 -2014-04-05 23:00:00,10215.0 -2014-04-06 00:00:00,9801.0 -2014-04-04 01:00:00,10164.0 -2014-04-04 02:00:00,9662.0 -2014-04-04 03:00:00,9305.0 -2014-04-04 04:00:00,9164.0 -2014-04-04 05:00:00,9111.0 -2014-04-04 06:00:00,9330.0 -2014-04-04 07:00:00,9995.0 -2014-04-04 08:00:00,11075.0 -2014-04-04 09:00:00,11678.0 -2014-04-04 10:00:00,11833.0 -2014-04-04 11:00:00,11910.0 -2014-04-04 12:00:00,12086.0 -2014-04-04 13:00:00,12149.0 -2014-04-04 14:00:00,12121.0 -2014-04-04 15:00:00,12165.0 -2014-04-04 16:00:00,12053.0 -2014-04-04 17:00:00,11990.0 -2014-04-04 18:00:00,11939.0 -2014-04-04 19:00:00,11871.0 -2014-04-04 20:00:00,11857.0 -2014-04-04 21:00:00,12097.0 -2014-04-04 22:00:00,11988.0 -2014-04-04 23:00:00,11621.0 -2014-04-05 00:00:00,11004.0 -2014-04-03 01:00:00,10083.0 -2014-04-03 02:00:00,9599.0 -2014-04-03 03:00:00,9297.0 -2014-04-03 04:00:00,9142.0 -2014-04-03 05:00:00,9067.0 -2014-04-03 06:00:00,9309.0 -2014-04-03 07:00:00,10069.0 -2014-04-03 08:00:00,11245.0 -2014-04-03 09:00:00,12072.0 -2014-04-03 10:00:00,12389.0 -2014-04-03 11:00:00,12454.0 -2014-04-03 12:00:00,12436.0 -2014-04-03 13:00:00,12371.0 -2014-04-03 14:00:00,12297.0 -2014-04-03 15:00:00,12299.0 -2014-04-03 16:00:00,12213.0 -2014-04-03 17:00:00,12093.0 -2014-04-03 18:00:00,12086.0 -2014-04-03 19:00:00,12038.0 -2014-04-03 20:00:00,11987.0 -2014-04-03 21:00:00,12322.0 -2014-04-03 22:00:00,12191.0 -2014-04-03 23:00:00,11720.0 -2014-04-04 00:00:00,10895.0 -2014-04-02 01:00:00,9795.0 -2014-04-02 02:00:00,9308.0 -2014-04-02 03:00:00,9034.0 -2014-04-02 04:00:00,8858.0 -2014-04-02 05:00:00,8879.0 -2014-04-02 06:00:00,9153.0 -2014-04-02 07:00:00,9898.0 -2014-04-02 08:00:00,10966.0 -2014-04-02 09:00:00,11405.0 -2014-04-02 10:00:00,11598.0 -2014-04-02 11:00:00,11642.0 -2014-04-02 12:00:00,11636.0 -2014-04-02 13:00:00,11573.0 -2014-04-02 14:00:00,11472.0 -2014-04-02 15:00:00,11479.0 -2014-04-02 16:00:00,11342.0 -2014-04-02 17:00:00,11203.0 -2014-04-02 18:00:00,11215.0 -2014-04-02 19:00:00,11329.0 -2014-04-02 20:00:00,11500.0 -2014-04-02 21:00:00,12030.0 -2014-04-02 22:00:00,12002.0 -2014-04-02 23:00:00,11574.0 -2014-04-03 00:00:00,10801.0 -2014-04-01 01:00:00,9407.0 -2014-04-01 02:00:00,8906.0 -2014-04-01 03:00:00,8580.0 -2014-04-01 04:00:00,8468.0 -2014-04-01 05:00:00,8477.0 -2014-04-01 06:00:00,8718.0 -2014-04-01 07:00:00,9433.0 -2014-04-01 08:00:00,10727.0 -2014-04-01 09:00:00,11429.0 -2014-04-01 10:00:00,11804.0 -2014-04-01 11:00:00,11924.0 -2014-04-01 12:00:00,12088.0 -2014-04-01 13:00:00,12072.0 -2014-04-01 14:00:00,11956.0 -2014-04-01 15:00:00,11856.0 -2014-04-01 16:00:00,11637.0 -2014-04-01 17:00:00,11390.0 -2014-04-01 18:00:00,11166.0 -2014-04-01 19:00:00,11081.0 -2014-04-01 20:00:00,11103.0 -2014-04-01 21:00:00,11707.0 -2014-04-01 22:00:00,11722.0 -2014-04-01 23:00:00,11277.0 -2014-04-02 00:00:00,10535.0 -2014-03-31 01:00:00,9112.0 -2014-03-31 02:00:00,8791.0 -2014-03-31 03:00:00,8598.0 -2014-03-31 04:00:00,8532.0 -2014-03-31 05:00:00,8566.0 -2014-03-31 06:00:00,8860.0 -2014-03-31 07:00:00,9644.0 -2014-03-31 08:00:00,10647.0 -2014-03-31 09:00:00,11147.0 -2014-03-31 10:00:00,11354.0 -2014-03-31 11:00:00,11404.0 -2014-03-31 12:00:00,11412.0 -2014-03-31 13:00:00,11366.0 -2014-03-31 14:00:00,11327.0 -2014-03-31 15:00:00,11351.0 -2014-03-31 16:00:00,11196.0 -2014-03-31 17:00:00,11087.0 -2014-03-31 18:00:00,10991.0 -2014-03-31 19:00:00,10932.0 -2014-03-31 20:00:00,10839.0 -2014-03-31 21:00:00,11324.0 -2014-03-31 22:00:00,11355.0 -2014-03-31 23:00:00,10893.0 -2014-04-01 00:00:00,10104.0 -2014-03-30 01:00:00,9950.0 -2014-03-30 02:00:00,9585.0 -2014-03-30 03:00:00,9301.0 -2014-03-30 04:00:00,9172.0 -2014-03-30 05:00:00,9108.0 -2014-03-30 06:00:00,9170.0 -2014-03-30 07:00:00,9304.0 -2014-03-30 08:00:00,9481.0 -2014-03-30 09:00:00,9458.0 -2014-03-30 10:00:00,9604.0 -2014-03-30 11:00:00,9645.0 -2014-03-30 12:00:00,9641.0 -2014-03-30 13:00:00,9589.0 -2014-03-30 14:00:00,9495.0 -2014-03-30 15:00:00,9380.0 -2014-03-30 16:00:00,9258.0 -2014-03-30 17:00:00,9173.0 -2014-03-30 18:00:00,9147.0 -2014-03-30 19:00:00,9255.0 -2014-03-30 20:00:00,9430.0 -2014-03-30 21:00:00,10186.0 -2014-03-30 22:00:00,10353.0 -2014-03-30 23:00:00,10121.0 -2014-03-31 00:00:00,9627.0 -2014-03-29 01:00:00,10225.0 -2014-03-29 02:00:00,9737.0 -2014-03-29 03:00:00,9469.0 -2014-03-29 04:00:00,9261.0 -2014-03-29 05:00:00,9228.0 -2014-03-29 06:00:00,9220.0 -2014-03-29 07:00:00,9544.0 -2014-03-29 08:00:00,9985.0 -2014-03-29 09:00:00,10345.0 -2014-03-29 10:00:00,10749.0 -2014-03-29 11:00:00,11011.0 -2014-03-29 12:00:00,11162.0 -2014-03-29 13:00:00,11086.0 -2014-03-29 14:00:00,10914.0 -2014-03-29 15:00:00,10653.0 -2014-03-29 16:00:00,10425.0 -2014-03-29 17:00:00,10136.0 -2014-03-29 18:00:00,10035.0 -2014-03-29 19:00:00,9947.0 -2014-03-29 20:00:00,10082.0 -2014-03-29 21:00:00,10760.0 -2014-03-29 22:00:00,11011.0 -2014-03-29 23:00:00,10857.0 -2014-03-30 00:00:00,10425.0 -2014-03-28 01:00:00,9988.0 -2014-03-28 02:00:00,9489.0 -2014-03-28 03:00:00,9135.0 -2014-03-28 04:00:00,9006.0 -2014-03-28 05:00:00,9032.0 -2014-03-28 06:00:00,9254.0 -2014-03-28 07:00:00,9938.0 -2014-03-28 08:00:00,10993.0 -2014-03-28 09:00:00,11667.0 -2014-03-28 10:00:00,11885.0 -2014-03-28 11:00:00,12073.0 -2014-03-28 12:00:00,12159.0 -2014-03-28 13:00:00,12018.0 -2014-03-28 14:00:00,11905.0 -2014-03-28 15:00:00,11813.0 -2014-03-28 16:00:00,11716.0 -2014-03-28 17:00:00,11564.0 -2014-03-28 18:00:00,11516.0 -2014-03-28 19:00:00,11521.0 -2014-03-28 20:00:00,11623.0 -2014-03-28 21:00:00,12003.0 -2014-03-28 22:00:00,11867.0 -2014-03-28 23:00:00,11533.0 -2014-03-29 00:00:00,10944.0 -2014-03-27 01:00:00,10485.0 -2014-03-27 02:00:00,10000.0 -2014-03-27 03:00:00,9672.0 -2014-03-27 04:00:00,9490.0 -2014-03-27 05:00:00,9477.0 -2014-03-27 06:00:00,9747.0 -2014-03-27 07:00:00,10433.0 -2014-03-27 08:00:00,11501.0 -2014-03-27 09:00:00,12094.0 -2014-03-27 10:00:00,12398.0 -2014-03-27 11:00:00,12530.0 -2014-03-27 12:00:00,12657.0 -2014-03-27 13:00:00,12642.0 -2014-03-27 14:00:00,12564.0 -2014-03-27 15:00:00,12502.0 -2014-03-27 16:00:00,12316.0 -2014-03-27 17:00:00,12109.0 -2014-03-27 18:00:00,12066.0 -2014-03-27 19:00:00,12010.0 -2014-03-27 20:00:00,12089.0 -2014-03-27 21:00:00,12306.0 -2014-03-27 22:00:00,12082.0 -2014-03-27 23:00:00,11573.0 -2014-03-28 00:00:00,10772.0 -2014-03-26 01:00:00,11005.0 -2014-03-26 02:00:00,10542.0 -2014-03-26 03:00:00,10325.0 -2014-03-26 04:00:00,10227.0 -2014-03-26 05:00:00,10249.0 -2014-03-26 06:00:00,10505.0 -2014-03-26 07:00:00,11231.0 -2014-03-26 08:00:00,12223.0 -2014-03-26 09:00:00,12591.0 -2014-03-26 10:00:00,12632.0 -2014-03-26 11:00:00,12590.0 -2014-03-26 12:00:00,12562.0 -2014-03-26 13:00:00,12438.0 -2014-03-26 14:00:00,12260.0 -2014-03-26 15:00:00,12163.0 -2014-03-26 16:00:00,11966.0 -2014-03-26 17:00:00,11711.0 -2014-03-26 18:00:00,11602.0 -2014-03-26 19:00:00,11627.0 -2014-03-26 20:00:00,11814.0 -2014-03-26 21:00:00,12410.0 -2014-03-26 22:00:00,12369.0 -2014-03-26 23:00:00,11941.0 -2014-03-27 00:00:00,11201.0 -2014-03-25 01:00:00,10631.0 -2014-03-25 02:00:00,10150.0 -2014-03-25 03:00:00,9908.0 -2014-03-25 04:00:00,9781.0 -2014-03-25 05:00:00,9806.0 -2014-03-25 06:00:00,10102.0 -2014-03-25 07:00:00,10828.0 -2014-03-25 08:00:00,11923.0 -2014-03-25 09:00:00,12332.0 -2014-03-25 10:00:00,12461.0 -2014-03-25 11:00:00,12564.0 -2014-03-25 12:00:00,12676.0 -2014-03-25 13:00:00,12698.0 -2014-03-25 14:00:00,12577.0 -2014-03-25 15:00:00,12565.0 -2014-03-25 16:00:00,12393.0 -2014-03-25 17:00:00,12218.0 -2014-03-25 18:00:00,12137.0 -2014-03-25 19:00:00,12096.0 -2014-03-25 20:00:00,12143.0 -2014-03-25 21:00:00,12781.0 -2014-03-25 22:00:00,12821.0 -2014-03-25 23:00:00,12379.0 -2014-03-26 00:00:00,11719.0 -2014-03-24 01:00:00,10264.0 -2014-03-24 02:00:00,9981.0 -2014-03-24 03:00:00,9809.0 -2014-03-24 04:00:00,9753.0 -2014-03-24 05:00:00,9842.0 -2014-03-24 06:00:00,10140.0 -2014-03-24 07:00:00,10814.0 -2014-03-24 08:00:00,11954.0 -2014-03-24 09:00:00,12462.0 -2014-03-24 10:00:00,12571.0 -2014-03-24 11:00:00,12506.0 -2014-03-24 12:00:00,12430.0 -2014-03-24 13:00:00,12322.0 -2014-03-24 14:00:00,12176.0 -2014-03-24 15:00:00,12114.0 -2014-03-24 16:00:00,11904.0 -2014-03-24 17:00:00,11705.0 -2014-03-24 18:00:00,11572.0 -2014-03-24 19:00:00,11573.0 -2014-03-24 20:00:00,11649.0 -2014-03-24 21:00:00,12347.0 -2014-03-24 22:00:00,12404.0 -2014-03-24 23:00:00,11997.0 -2014-03-25 00:00:00,11336.0 -2014-03-23 01:00:00,9954.0 -2014-03-23 02:00:00,9575.0 -2014-03-23 03:00:00,9401.0 -2014-03-23 04:00:00,9280.0 -2014-03-23 05:00:00,9266.0 -2014-03-23 06:00:00,9311.0 -2014-03-23 07:00:00,9516.0 -2014-03-23 08:00:00,9790.0 -2014-03-23 09:00:00,9888.0 -2014-03-23 10:00:00,10139.0 -2014-03-23 11:00:00,10261.0 -2014-03-23 12:00:00,10254.0 -2014-03-23 13:00:00,10280.0 -2014-03-23 14:00:00,10254.0 -2014-03-23 15:00:00,10147.0 -2014-03-23 16:00:00,10042.0 -2014-03-23 17:00:00,10031.0 -2014-03-23 18:00:00,10009.0 -2014-03-23 19:00:00,10208.0 -2014-03-23 20:00:00,10508.0 -2014-03-23 21:00:00,11285.0 -2014-03-23 22:00:00,11425.0 -2014-03-23 23:00:00,11177.0 -2014-03-24 00:00:00,10754.0 -2014-03-22 01:00:00,9889.0 -2014-03-22 02:00:00,9439.0 -2014-03-22 03:00:00,9114.0 -2014-03-22 04:00:00,8961.0 -2014-03-22 05:00:00,8873.0 -2014-03-22 06:00:00,8946.0 -2014-03-22 07:00:00,9202.0 -2014-03-22 08:00:00,9710.0 -2014-03-22 09:00:00,9979.0 -2014-03-22 10:00:00,10384.0 -2014-03-22 11:00:00,10578.0 -2014-03-22 12:00:00,10705.0 -2014-03-22 13:00:00,10656.0 -2014-03-22 14:00:00,10539.0 -2014-03-22 15:00:00,10331.0 -2014-03-22 16:00:00,10133.0 -2014-03-22 17:00:00,10099.0 -2014-03-22 18:00:00,10041.0 -2014-03-22 19:00:00,10091.0 -2014-03-22 20:00:00,10295.0 -2014-03-22 21:00:00,10975.0 -2014-03-22 22:00:00,11051.0 -2014-03-22 23:00:00,10863.0 -2014-03-23 00:00:00,10426.0 -2014-03-21 01:00:00,10257.0 -2014-03-21 02:00:00,9754.0 -2014-03-21 03:00:00,9450.0 -2014-03-21 04:00:00,9331.0 -2014-03-21 05:00:00,9303.0 -2014-03-21 06:00:00,9543.0 -2014-03-21 07:00:00,10254.0 -2014-03-21 08:00:00,11399.0 -2014-03-21 09:00:00,11853.0 -2014-03-21 10:00:00,11926.0 -2014-03-21 11:00:00,11852.0 -2014-03-21 12:00:00,11767.0 -2014-03-21 13:00:00,11617.0 -2014-03-21 14:00:00,11446.0 -2014-03-21 15:00:00,11401.0 -2014-03-21 16:00:00,11197.0 -2014-03-21 17:00:00,11009.0 -2014-03-21 18:00:00,10975.0 -2014-03-21 19:00:00,11000.0 -2014-03-21 20:00:00,11138.0 -2014-03-21 21:00:00,11540.0 -2014-03-21 22:00:00,11370.0 -2014-03-21 23:00:00,11041.0 -2014-03-22 00:00:00,10487.0 -2014-03-20 01:00:00,10477.0 -2014-03-20 02:00:00,9955.0 -2014-03-20 03:00:00,9655.0 -2014-03-20 04:00:00,9490.0 -2014-03-20 05:00:00,9486.0 -2014-03-20 06:00:00,9741.0 -2014-03-20 07:00:00,10440.0 -2014-03-20 08:00:00,11641.0 -2014-03-20 09:00:00,12218.0 -2014-03-20 10:00:00,12333.0 -2014-03-20 11:00:00,12213.0 -2014-03-20 12:00:00,12134.0 -2014-03-20 13:00:00,11972.0 -2014-03-20 14:00:00,11806.0 -2014-03-20 15:00:00,11731.0 -2014-03-20 16:00:00,11538.0 -2014-03-20 17:00:00,11343.0 -2014-03-20 18:00:00,11230.0 -2014-03-20 19:00:00,11159.0 -2014-03-20 20:00:00,11214.0 -2014-03-20 21:00:00,11897.0 -2014-03-20 22:00:00,11982.0 -2014-03-20 23:00:00,11646.0 -2014-03-21 00:00:00,10960.0 -2014-03-19 01:00:00,10129.0 -2014-03-19 02:00:00,9633.0 -2014-03-19 03:00:00,9310.0 -2014-03-19 04:00:00,9159.0 -2014-03-19 05:00:00,9163.0 -2014-03-19 06:00:00,9380.0 -2014-03-19 07:00:00,10047.0 -2014-03-19 08:00:00,11274.0 -2014-03-19 09:00:00,12035.0 -2014-03-19 10:00:00,12169.0 -2014-03-19 11:00:00,12173.0 -2014-03-19 12:00:00,12272.0 -2014-03-19 13:00:00,12329.0 -2014-03-19 14:00:00,12307.0 -2014-03-19 15:00:00,12336.0 -2014-03-19 16:00:00,12253.0 -2014-03-19 17:00:00,12208.0 -2014-03-19 18:00:00,12227.0 -2014-03-19 19:00:00,12361.0 -2014-03-19 20:00:00,12453.0 -2014-03-19 21:00:00,12700.0 -2014-03-19 22:00:00,12503.0 -2014-03-19 23:00:00,11984.0 -2014-03-20 00:00:00,11230.0 -2014-03-18 01:00:00,10689.0 -2014-03-18 02:00:00,10218.0 -2014-03-18 03:00:00,9959.0 -2014-03-18 04:00:00,9869.0 -2014-03-18 05:00:00,9874.0 -2014-03-18 06:00:00,10122.0 -2014-03-18 07:00:00,10847.0 -2014-03-18 08:00:00,12012.0 -2014-03-18 09:00:00,12396.0 -2014-03-18 10:00:00,12438.0 -2014-03-18 11:00:00,12361.0 -2014-03-18 12:00:00,12257.0 -2014-03-18 13:00:00,12135.0 -2014-03-18 14:00:00,11980.0 -2014-03-18 15:00:00,11968.0 -2014-03-18 16:00:00,11760.0 -2014-03-18 17:00:00,11617.0 -2014-03-18 18:00:00,11488.0 -2014-03-18 19:00:00,11393.0 -2014-03-18 20:00:00,11547.0 -2014-03-18 21:00:00,12172.0 -2014-03-18 22:00:00,12136.0 -2014-03-18 23:00:00,11633.0 -2014-03-19 00:00:00,10911.0 -2014-03-17 01:00:00,10713.0 -2014-03-17 02:00:00,10411.0 -2014-03-17 03:00:00,10291.0 -2014-03-17 04:00:00,10213.0 -2014-03-17 05:00:00,10334.0 -2014-03-17 06:00:00,10583.0 -2014-03-17 07:00:00,11395.0 -2014-03-17 08:00:00,12572.0 -2014-03-17 09:00:00,13036.0 -2014-03-17 10:00:00,12994.0 -2014-03-17 11:00:00,12940.0 -2014-03-17 12:00:00,12845.0 -2014-03-17 13:00:00,12742.0 -2014-03-17 14:00:00,12572.0 -2014-03-17 15:00:00,12475.0 -2014-03-17 16:00:00,12209.0 -2014-03-17 17:00:00,11995.0 -2014-03-17 18:00:00,11868.0 -2014-03-17 19:00:00,11809.0 -2014-03-17 20:00:00,11908.0 -2014-03-17 21:00:00,12619.0 -2014-03-17 22:00:00,12601.0 -2014-03-17 23:00:00,12195.0 -2014-03-18 00:00:00,11448.0 -2014-03-16 01:00:00,10385.0 -2014-03-16 02:00:00,9958.0 -2014-03-16 03:00:00,9758.0 -2014-03-16 04:00:00,9620.0 -2014-03-16 05:00:00,9655.0 -2014-03-16 06:00:00,9752.0 -2014-03-16 07:00:00,9935.0 -2014-03-16 08:00:00,10304.0 -2014-03-16 09:00:00,10441.0 -2014-03-16 10:00:00,10734.0 -2014-03-16 11:00:00,10958.0 -2014-03-16 12:00:00,11047.0 -2014-03-16 13:00:00,11050.0 -2014-03-16 14:00:00,10983.0 -2014-03-16 15:00:00,10894.0 -2014-03-16 16:00:00,10791.0 -2014-03-16 17:00:00,10661.0 -2014-03-16 18:00:00,10764.0 -2014-03-16 19:00:00,10912.0 -2014-03-16 20:00:00,11230.0 -2014-03-16 21:00:00,12047.0 -2014-03-16 22:00:00,12166.0 -2014-03-16 23:00:00,11854.0 -2014-03-17 00:00:00,11292.0 -2014-03-15 01:00:00,10290.0 -2014-03-15 02:00:00,9799.0 -2014-03-15 03:00:00,9478.0 -2014-03-15 04:00:00,9297.0 -2014-03-15 05:00:00,9186.0 -2014-03-15 06:00:00,9263.0 -2014-03-15 07:00:00,9593.0 -2014-03-15 08:00:00,10130.0 -2014-03-15 09:00:00,10426.0 -2014-03-15 10:00:00,10715.0 -2014-03-15 11:00:00,10917.0 -2014-03-15 12:00:00,10886.0 -2014-03-15 13:00:00,10670.0 -2014-03-15 14:00:00,10592.0 -2014-03-15 15:00:00,10314.0 -2014-03-15 16:00:00,10181.0 -2014-03-15 17:00:00,10109.0 -2014-03-15 18:00:00,10155.0 -2014-03-15 19:00:00,10349.0 -2014-03-15 20:00:00,10686.0 -2014-03-15 21:00:00,11355.0 -2014-03-15 22:00:00,11388.0 -2014-03-15 23:00:00,11240.0 -2014-03-16 00:00:00,10831.0 -2014-03-14 01:00:00,10995.0 -2014-03-14 02:00:00,10461.0 -2014-03-14 03:00:00,10105.0 -2014-03-14 04:00:00,9954.0 -2014-03-14 05:00:00,9878.0 -2014-03-14 06:00:00,10047.0 -2014-03-14 07:00:00,10727.0 -2014-03-14 08:00:00,11868.0 -2014-03-14 09:00:00,12271.0 -2014-03-14 10:00:00,12201.0 -2014-03-14 11:00:00,12131.0 -2014-03-14 12:00:00,12061.0 -2014-03-14 13:00:00,11940.0 -2014-03-14 14:00:00,11743.0 -2014-03-14 15:00:00,11631.0 -2014-03-14 16:00:00,11430.0 -2014-03-14 17:00:00,11217.0 -2014-03-14 18:00:00,11103.0 -2014-03-14 19:00:00,11058.0 -2014-03-14 20:00:00,11256.0 -2014-03-14 21:00:00,11886.0 -2014-03-14 22:00:00,11837.0 -2014-03-14 23:00:00,11574.0 -2014-03-15 00:00:00,10941.0 -2014-03-13 01:00:00,11362.0 -2014-03-13 02:00:00,10874.0 -2014-03-13 03:00:00,10618.0 -2014-03-13 04:00:00,10545.0 -2014-03-13 05:00:00,10566.0 -2014-03-13 06:00:00,10822.0 -2014-03-13 07:00:00,11489.0 -2014-03-13 08:00:00,12672.0 -2014-03-13 09:00:00,13136.0 -2014-03-13 10:00:00,13042.0 -2014-03-13 11:00:00,12999.0 -2014-03-13 12:00:00,12967.0 -2014-03-13 13:00:00,12997.0 -2014-03-13 14:00:00,13006.0 -2014-03-13 15:00:00,12964.0 -2014-03-13 16:00:00,12789.0 -2014-03-13 17:00:00,12551.0 -2014-03-13 18:00:00,12315.0 -2014-03-13 19:00:00,12210.0 -2014-03-13 20:00:00,12373.0 -2014-03-13 21:00:00,13073.0 -2014-03-13 22:00:00,12970.0 -2014-03-13 23:00:00,12558.0 -2014-03-14 00:00:00,11789.0 -2014-03-12 01:00:00,10430.0 -2014-03-12 02:00:00,10095.0 -2014-03-12 03:00:00,9844.0 -2014-03-12 04:00:00,9742.0 -2014-03-12 05:00:00,9735.0 -2014-03-12 06:00:00,9972.0 -2014-03-12 07:00:00,10602.0 -2014-03-12 08:00:00,11716.0 -2014-03-12 09:00:00,12313.0 -2014-03-12 10:00:00,12522.0 -2014-03-12 11:00:00,12650.0 -2014-03-12 12:00:00,12639.0 -2014-03-12 13:00:00,12530.0 -2014-03-12 14:00:00,12468.0 -2014-03-12 15:00:00,12500.0 -2014-03-12 16:00:00,12355.0 -2014-03-12 17:00:00,12211.0 -2014-03-12 18:00:00,12181.0 -2014-03-12 19:00:00,12213.0 -2014-03-12 20:00:00,12411.0 -2014-03-12 21:00:00,13161.0 -2014-03-12 22:00:00,13174.0 -2014-03-12 23:00:00,12774.0 -2014-03-13 00:00:00,12079.0 -2014-03-11 01:00:00,9963.0 -2014-03-11 02:00:00,9517.0 -2014-03-11 03:00:00,9196.0 -2014-03-11 04:00:00,9059.0 -2014-03-11 05:00:00,9016.0 -2014-03-11 06:00:00,9205.0 -2014-03-11 07:00:00,9910.0 -2014-03-11 08:00:00,11147.0 -2014-03-11 09:00:00,11804.0 -2014-03-11 10:00:00,11877.0 -2014-03-11 11:00:00,11865.0 -2014-03-11 12:00:00,11881.0 -2014-03-11 13:00:00,11774.0 -2014-03-11 14:00:00,11688.0 -2014-03-11 15:00:00,11753.0 -2014-03-11 16:00:00,11705.0 -2014-03-11 17:00:00,11700.0 -2014-03-11 18:00:00,11761.0 -2014-03-11 19:00:00,11874.0 -2014-03-11 20:00:00,12070.0 -2014-03-11 21:00:00,12577.0 -2014-03-11 22:00:00,12426.0 -2014-03-11 23:00:00,12000.0 -2014-03-12 00:00:00,11305.0 -2014-03-10 01:00:00,10076.0 -2014-03-10 02:00:00,9655.0 -2014-03-10 03:00:00,9416.0 -2014-03-10 04:00:00,9261.0 -2014-03-10 05:00:00,9233.0 -2014-03-10 06:00:00,9496.0 -2014-03-10 07:00:00,10223.0 -2014-03-10 08:00:00,11397.0 -2014-03-10 09:00:00,11996.0 -2014-03-10 10:00:00,11928.0 -2014-03-10 11:00:00,11831.0 -2014-03-10 12:00:00,11798.0 -2014-03-10 13:00:00,11669.0 -2014-03-10 14:00:00,11515.0 -2014-03-10 15:00:00,11467.0 -2014-03-10 16:00:00,11284.0 -2014-03-10 17:00:00,11111.0 -2014-03-10 18:00:00,11034.0 -2014-03-10 19:00:00,11029.0 -2014-03-10 20:00:00,11236.0 -2014-03-10 21:00:00,11978.0 -2014-03-10 22:00:00,11850.0 -2014-03-10 23:00:00,11418.0 -2014-03-11 00:00:00,10709.0 -2014-03-09 01:00:00,10586.0 -2014-03-09 02:00:00,10268.0 -2014-03-09 04:00:00,10030.0 -2014-03-09 05:00:00,9854.0 -2014-03-09 06:00:00,9912.0 -2014-03-09 07:00:00,9976.0 -2014-03-09 08:00:00,10219.0 -2014-03-09 09:00:00,10237.0 -2014-03-09 10:00:00,10290.0 -2014-03-09 11:00:00,10327.0 -2014-03-09 12:00:00,10371.0 -2014-03-09 13:00:00,10332.0 -2014-03-09 14:00:00,10275.0 -2014-03-09 15:00:00,10171.0 -2014-03-09 16:00:00,10084.0 -2014-03-09 17:00:00,10052.0 -2014-03-09 18:00:00,10203.0 -2014-03-09 19:00:00,10422.0 -2014-03-09 20:00:00,10854.0 -2014-03-09 21:00:00,11575.0 -2014-03-09 22:00:00,11494.0 -2014-03-09 23:00:00,11220.0 -2014-03-10 00:00:00,10618.0 -2014-03-08 01:00:00,10359.0 -2014-03-08 02:00:00,9901.0 -2014-03-08 03:00:00,9701.0 -2014-03-08 04:00:00,9574.0 -2014-03-08 05:00:00,9555.0 -2014-03-08 06:00:00,9688.0 -2014-03-08 07:00:00,10028.0 -2014-03-08 08:00:00,10401.0 -2014-03-08 09:00:00,10775.0 -2014-03-08 10:00:00,11228.0 -2014-03-08 11:00:00,11516.0 -2014-03-08 12:00:00,11555.0 -2014-03-08 13:00:00,11443.0 -2014-03-08 14:00:00,11238.0 -2014-03-08 15:00:00,11068.0 -2014-03-08 16:00:00,10883.0 -2014-03-08 17:00:00,10842.0 -2014-03-08 18:00:00,10916.0 -2014-03-08 19:00:00,11240.0 -2014-03-08 20:00:00,11940.0 -2014-03-08 21:00:00,11980.0 -2014-03-08 22:00:00,11807.0 -2014-03-08 23:00:00,11590.0 -2014-03-09 00:00:00,11090.0 -2014-03-07 01:00:00,11360.0 -2014-03-07 02:00:00,11001.0 -2014-03-07 03:00:00,10755.0 -2014-03-07 04:00:00,10639.0 -2014-03-07 05:00:00,10594.0 -2014-03-07 06:00:00,10912.0 -2014-03-07 07:00:00,11572.0 -2014-03-07 08:00:00,12391.0 -2014-03-07 09:00:00,12671.0 -2014-03-07 10:00:00,12617.0 -2014-03-07 11:00:00,12469.0 -2014-03-07 12:00:00,12348.0 -2014-03-07 13:00:00,12184.0 -2014-03-07 14:00:00,12003.0 -2014-03-07 15:00:00,11895.0 -2014-03-07 16:00:00,11749.0 -2014-03-07 17:00:00,11615.0 -2014-03-07 18:00:00,11724.0 -2014-03-07 19:00:00,12022.0 -2014-03-07 20:00:00,12558.0 -2014-03-07 21:00:00,12377.0 -2014-03-07 22:00:00,12095.0 -2014-03-07 23:00:00,11687.0 -2014-03-08 00:00:00,10998.0 -2014-03-06 01:00:00,11665.0 -2014-03-06 02:00:00,11214.0 -2014-03-06 03:00:00,10989.0 -2014-03-06 04:00:00,10873.0 -2014-03-06 05:00:00,10903.0 -2014-03-06 06:00:00,11201.0 -2014-03-06 07:00:00,11911.0 -2014-03-06 08:00:00,12799.0 -2014-03-06 09:00:00,13112.0 -2014-03-06 10:00:00,13120.0 -2014-03-06 11:00:00,13026.0 -2014-03-06 12:00:00,12958.0 -2014-03-06 13:00:00,12808.0 -2014-03-06 14:00:00,12620.0 -2014-03-06 15:00:00,12536.0 -2014-03-06 16:00:00,12401.0 -2014-03-06 17:00:00,12305.0 -2014-03-06 18:00:00,12396.0 -2014-03-06 19:00:00,12732.0 -2014-03-06 20:00:00,13527.0 -2014-03-06 21:00:00,13550.0 -2014-03-06 22:00:00,13340.0 -2014-03-06 23:00:00,12885.0 -2014-03-07 00:00:00,12053.0 -2014-03-05 01:00:00,11523.0 -2014-03-05 02:00:00,11076.0 -2014-03-05 03:00:00,10839.0 -2014-03-05 04:00:00,10742.0 -2014-03-05 05:00:00,10793.0 -2014-03-05 06:00:00,11067.0 -2014-03-05 07:00:00,11798.0 -2014-03-05 08:00:00,12739.0 -2014-03-05 09:00:00,13232.0 -2014-03-05 10:00:00,13565.0 -2014-03-05 11:00:00,13441.0 -2014-03-05 12:00:00,13515.0 -2014-03-05 13:00:00,13419.0 -2014-03-05 14:00:00,13277.0 -2014-03-05 15:00:00,13199.0 -2014-03-05 16:00:00,13066.0 -2014-03-05 17:00:00,12995.0 -2014-03-05 18:00:00,13125.0 -2014-03-05 19:00:00,13557.0 -2014-03-05 20:00:00,14131.0 -2014-03-05 21:00:00,13991.0 -2014-03-05 22:00:00,13712.0 -2014-03-05 23:00:00,13176.0 -2014-03-06 00:00:00,12379.0 -2014-03-04 01:00:00,12006.0 -2014-03-04 02:00:00,11548.0 -2014-03-04 03:00:00,11308.0 -2014-03-04 04:00:00,11189.0 -2014-03-04 05:00:00,11183.0 -2014-03-04 06:00:00,11406.0 -2014-03-04 07:00:00,12090.0 -2014-03-04 08:00:00,13026.0 -2014-03-04 09:00:00,13457.0 -2014-03-04 10:00:00,13658.0 -2014-03-04 11:00:00,13625.0 -2014-03-04 12:00:00,13538.0 -2014-03-04 13:00:00,13293.0 -2014-03-04 14:00:00,13104.0 -2014-03-04 15:00:00,13010.0 -2014-03-04 16:00:00,12792.0 -2014-03-04 17:00:00,12683.0 -2014-03-04 18:00:00,12800.0 -2014-03-04 19:00:00,13203.0 -2014-03-04 20:00:00,13879.0 -2014-03-04 21:00:00,13825.0 -2014-03-04 22:00:00,13586.0 -2014-03-04 23:00:00,13023.0 -2014-03-05 00:00:00,12233.0 -2014-03-03 01:00:00,11911.0 -2014-03-03 02:00:00,11603.0 -2014-03-03 03:00:00,11453.0 -2014-03-03 04:00:00,11382.0 -2014-03-03 05:00:00,11464.0 -2014-03-03 06:00:00,11703.0 -2014-03-03 07:00:00,12494.0 -2014-03-03 08:00:00,13350.0 -2014-03-03 09:00:00,13847.0 -2014-03-03 10:00:00,13928.0 -2014-03-03 11:00:00,13945.0 -2014-03-03 12:00:00,13848.0 -2014-03-03 13:00:00,13729.0 -2014-03-03 14:00:00,13541.0 -2014-03-03 15:00:00,13380.0 -2014-03-03 16:00:00,13182.0 -2014-03-03 17:00:00,13026.0 -2014-03-03 18:00:00,13192.0 -2014-03-03 19:00:00,13717.0 -2014-03-03 20:00:00,14466.0 -2014-03-03 21:00:00,14387.0 -2014-03-03 22:00:00,14110.0 -2014-03-03 23:00:00,13557.0 -2014-03-04 00:00:00,12753.0 -2014-03-02 01:00:00,11503.0 -2014-03-02 02:00:00,11124.0 -2014-03-02 03:00:00,10934.0 -2014-03-02 04:00:00,10724.0 -2014-03-02 05:00:00,10681.0 -2014-03-02 06:00:00,10749.0 -2014-03-02 07:00:00,10920.0 -2014-03-02 08:00:00,11052.0 -2014-03-02 09:00:00,11172.0 -2014-03-02 10:00:00,11443.0 -2014-03-02 11:00:00,11626.0 -2014-03-02 12:00:00,11619.0 -2014-03-02 13:00:00,11525.0 -2014-03-02 14:00:00,11451.0 -2014-03-02 15:00:00,11449.0 -2014-03-02 16:00:00,11484.0 -2014-03-02 17:00:00,11636.0 -2014-03-02 18:00:00,11915.0 -2014-03-02 19:00:00,12530.0 -2014-03-02 20:00:00,13377.0 -2014-03-02 21:00:00,13386.0 -2014-03-02 22:00:00,13224.0 -2014-03-02 23:00:00,12883.0 -2014-03-03 00:00:00,12429.0 -2014-03-01 01:00:00,11406.0 -2014-03-01 02:00:00,10816.0 -2014-03-01 03:00:00,10484.0 -2014-03-01 04:00:00,10291.0 -2014-03-01 05:00:00,10202.0 -2014-03-01 06:00:00,10294.0 -2014-03-01 07:00:00,10577.0 -2014-03-01 08:00:00,11035.0 -2014-03-01 09:00:00,11371.0 -2014-03-01 10:00:00,11799.0 -2014-03-01 11:00:00,12003.0 -2014-03-01 12:00:00,12092.0 -2014-03-01 13:00:00,11990.0 -2014-03-01 14:00:00,11807.0 -2014-03-01 15:00:00,11671.0 -2014-03-01 16:00:00,11664.0 -2014-03-01 17:00:00,11757.0 -2014-03-01 18:00:00,12057.0 -2014-03-01 19:00:00,12584.0 -2014-03-01 20:00:00,13051.0 -2014-03-01 21:00:00,13005.0 -2014-03-01 22:00:00,12833.0 -2014-03-01 23:00:00,12521.0 -2014-03-02 00:00:00,12028.0 -2014-02-28 01:00:00,12586.0 -2014-02-28 02:00:00,12201.0 -2014-02-28 03:00:00,11983.0 -2014-02-28 04:00:00,11865.0 -2014-02-28 05:00:00,11885.0 -2014-02-28 06:00:00,12084.0 -2014-02-28 07:00:00,12705.0 -2014-02-28 08:00:00,13500.0 -2014-02-28 09:00:00,13918.0 -2014-02-28 10:00:00,14178.0 -2014-02-28 11:00:00,14147.0 -2014-02-28 12:00:00,14071.0 -2014-02-28 13:00:00,13783.0 -2014-02-28 14:00:00,13483.0 -2014-02-28 15:00:00,13366.0 -2014-02-28 16:00:00,13170.0 -2014-02-28 17:00:00,13017.0 -2014-02-28 18:00:00,12994.0 -2014-02-28 19:00:00,13348.0 -2014-02-28 20:00:00,13839.0 -2014-02-28 21:00:00,13632.0 -2014-02-28 22:00:00,13285.0 -2014-02-28 23:00:00,12884.0 -2014-03-01 00:00:00,12100.0 -2014-02-27 01:00:00,12421.0 -2014-02-27 02:00:00,11964.0 -2014-02-27 03:00:00,11739.0 -2014-02-27 04:00:00,11683.0 -2014-02-27 05:00:00,11725.0 -2014-02-27 06:00:00,12043.0 -2014-02-27 07:00:00,12733.0 -2014-02-27 08:00:00,13648.0 -2014-02-27 09:00:00,14017.0 -2014-02-27 10:00:00,14081.0 -2014-02-27 11:00:00,14098.0 -2014-02-27 12:00:00,14081.0 -2014-02-27 13:00:00,13952.0 -2014-02-27 14:00:00,13797.0 -2014-02-27 15:00:00,13668.0 -2014-02-27 16:00:00,13509.0 -2014-02-27 17:00:00,13367.0 -2014-02-27 18:00:00,13470.0 -2014-02-27 19:00:00,14022.0 -2014-02-27 20:00:00,14745.0 -2014-02-27 21:00:00,14735.0 -2014-02-27 22:00:00,14548.0 -2014-02-27 23:00:00,14087.0 -2014-02-28 00:00:00,13298.0 -2014-02-26 01:00:00,12446.0 -2014-02-26 02:00:00,12073.0 -2014-02-26 03:00:00,11875.0 -2014-02-26 04:00:00,11759.0 -2014-02-26 05:00:00,11783.0 -2014-02-26 06:00:00,12051.0 -2014-02-26 07:00:00,12735.0 -2014-02-26 08:00:00,13655.0 -2014-02-26 09:00:00,13985.0 -2014-02-26 10:00:00,14050.0 -2014-02-26 11:00:00,14057.0 -2014-02-26 12:00:00,14094.0 -2014-02-26 13:00:00,13982.0 -2014-02-26 14:00:00,13909.0 -2014-02-26 15:00:00,13874.0 -2014-02-26 16:00:00,13794.0 -2014-02-26 17:00:00,13745.0 -2014-02-26 18:00:00,13884.0 -2014-02-26 19:00:00,14451.0 -2014-02-26 20:00:00,15065.0 -2014-02-26 21:00:00,14928.0 -2014-02-26 22:00:00,14631.0 -2014-02-26 23:00:00,14071.0 -2014-02-27 00:00:00,13208.0 -2014-02-25 01:00:00,11545.0 -2014-02-25 02:00:00,11121.0 -2014-02-25 03:00:00,10867.0 -2014-02-25 04:00:00,10758.0 -2014-02-25 05:00:00,10757.0 -2014-02-25 06:00:00,11048.0 -2014-02-25 07:00:00,11825.0 -2014-02-25 08:00:00,12805.0 -2014-02-25 09:00:00,13161.0 -2014-02-25 10:00:00,13225.0 -2014-02-25 11:00:00,13196.0 -2014-02-25 12:00:00,13163.0 -2014-02-25 13:00:00,13084.0 -2014-02-25 14:00:00,12995.0 -2014-02-25 15:00:00,13036.0 -2014-02-25 16:00:00,13030.0 -2014-02-25 17:00:00,12999.0 -2014-02-25 18:00:00,13141.0 -2014-02-25 19:00:00,13736.0 -2014-02-25 20:00:00,14477.0 -2014-02-25 21:00:00,14501.0 -2014-02-25 22:00:00,14294.0 -2014-02-25 23:00:00,13897.0 -2014-02-26 00:00:00,13132.0 -2014-02-24 01:00:00,11045.0 -2014-02-24 02:00:00,10758.0 -2014-02-24 03:00:00,10679.0 -2014-02-24 04:00:00,10653.0 -2014-02-24 05:00:00,10701.0 -2014-02-24 06:00:00,11029.0 -2014-02-24 07:00:00,11765.0 -2014-02-24 08:00:00,12815.0 -2014-02-24 09:00:00,13187.0 -2014-02-24 10:00:00,13254.0 -2014-02-24 11:00:00,13202.0 -2014-02-24 12:00:00,13163.0 -2014-02-24 13:00:00,13085.0 -2014-02-24 14:00:00,12957.0 -2014-02-24 15:00:00,12900.0 -2014-02-24 16:00:00,12742.0 -2014-02-24 17:00:00,12708.0 -2014-02-24 18:00:00,12921.0 -2014-02-24 19:00:00,13548.0 -2014-02-24 20:00:00,14017.0 -2014-02-24 21:00:00,13898.0 -2014-02-24 22:00:00,13599.0 -2014-02-24 23:00:00,13106.0 -2014-02-25 00:00:00,12273.0 -2014-02-23 01:00:00,10730.0 -2014-02-23 02:00:00,10347.0 -2014-02-23 03:00:00,10168.0 -2014-02-23 04:00:00,10026.0 -2014-02-23 05:00:00,9992.0 -2014-02-23 06:00:00,10032.0 -2014-02-23 07:00:00,10231.0 -2014-02-23 08:00:00,10429.0 -2014-02-23 09:00:00,10531.0 -2014-02-23 10:00:00,10784.0 -2014-02-23 11:00:00,10856.0 -2014-02-23 12:00:00,10792.0 -2014-02-23 13:00:00,10686.0 -2014-02-23 14:00:00,10645.0 -2014-02-23 15:00:00,10587.0 -2014-02-23 16:00:00,10481.0 -2014-02-23 17:00:00,10527.0 -2014-02-23 18:00:00,10840.0 -2014-02-23 19:00:00,11618.0 -2014-02-23 20:00:00,12435.0 -2014-02-23 21:00:00,12545.0 -2014-02-23 22:00:00,12353.0 -2014-02-23 23:00:00,12071.0 -2014-02-24 00:00:00,11574.0 -2014-02-22 01:00:00,11124.0 -2014-02-22 02:00:00,10628.0 -2014-02-22 03:00:00,10338.0 -2014-02-22 04:00:00,10207.0 -2014-02-22 05:00:00,10157.0 -2014-02-22 06:00:00,10283.0 -2014-02-22 07:00:00,10615.0 -2014-02-22 08:00:00,10984.0 -2014-02-22 09:00:00,11080.0 -2014-02-22 10:00:00,11467.0 -2014-02-22 11:00:00,11628.0 -2014-02-22 12:00:00,11708.0 -2014-02-22 13:00:00,11535.0 -2014-02-22 14:00:00,11156.0 -2014-02-22 15:00:00,10907.0 -2014-02-22 16:00:00,10788.0 -2014-02-22 17:00:00,10755.0 -2014-02-22 18:00:00,10976.0 -2014-02-22 19:00:00,11518.0 -2014-02-22 20:00:00,12084.0 -2014-02-22 21:00:00,11994.0 -2014-02-22 22:00:00,11882.0 -2014-02-22 23:00:00,11580.0 -2014-02-23 00:00:00,11121.0 -2014-02-21 01:00:00,10832.0 -2014-02-21 02:00:00,10367.0 -2014-02-21 03:00:00,10072.0 -2014-02-21 04:00:00,9946.0 -2014-02-21 05:00:00,10010.0 -2014-02-21 06:00:00,10277.0 -2014-02-21 07:00:00,11069.0 -2014-02-21 08:00:00,12265.0 -2014-02-21 09:00:00,12708.0 -2014-02-21 10:00:00,12993.0 -2014-02-21 11:00:00,13039.0 -2014-02-21 12:00:00,13009.0 -2014-02-21 13:00:00,12938.0 -2014-02-21 14:00:00,12811.0 -2014-02-21 15:00:00,12683.0 -2014-02-21 16:00:00,12476.0 -2014-02-21 17:00:00,12313.0 -2014-02-21 18:00:00,12356.0 -2014-02-21 19:00:00,12869.0 -2014-02-21 20:00:00,13313.0 -2014-02-21 21:00:00,13104.0 -2014-02-21 22:00:00,12769.0 -2014-02-21 23:00:00,12396.0 -2014-02-22 00:00:00,11738.0 -2014-02-20 01:00:00,10493.0 -2014-02-20 02:00:00,10018.0 -2014-02-20 03:00:00,9757.0 -2014-02-20 04:00:00,9624.0 -2014-02-20 05:00:00,9654.0 -2014-02-20 06:00:00,9998.0 -2014-02-20 07:00:00,10802.0 -2014-02-20 08:00:00,11896.0 -2014-02-20 09:00:00,12425.0 -2014-02-20 10:00:00,12559.0 -2014-02-20 11:00:00,12619.0 -2014-02-20 12:00:00,12725.0 -2014-02-20 13:00:00,12728.0 -2014-02-20 14:00:00,12743.0 -2014-02-20 15:00:00,12742.0 -2014-02-20 16:00:00,12722.0 -2014-02-20 17:00:00,12754.0 -2014-02-20 18:00:00,12802.0 -2014-02-20 19:00:00,13122.0 -2014-02-20 20:00:00,13079.0 -2014-02-20 21:00:00,12862.0 -2014-02-20 22:00:00,12717.0 -2014-02-20 23:00:00,12269.0 -2014-02-21 00:00:00,11523.0 -2014-02-19 01:00:00,10615.0 -2014-02-19 02:00:00,10191.0 -2014-02-19 03:00:00,9902.0 -2014-02-19 04:00:00,9748.0 -2014-02-19 05:00:00,9760.0 -2014-02-19 06:00:00,10019.0 -2014-02-19 07:00:00,10864.0 -2014-02-19 08:00:00,11897.0 -2014-02-19 09:00:00,12200.0 -2014-02-19 10:00:00,12172.0 -2014-02-19 11:00:00,12104.0 -2014-02-19 12:00:00,12056.0 -2014-02-19 13:00:00,11975.0 -2014-02-19 14:00:00,11861.0 -2014-02-19 15:00:00,11786.0 -2014-02-19 16:00:00,11637.0 -2014-02-19 17:00:00,11562.0 -2014-02-19 18:00:00,11705.0 -2014-02-19 19:00:00,12357.0 -2014-02-19 20:00:00,12853.0 -2014-02-19 21:00:00,12728.0 -2014-02-19 22:00:00,12552.0 -2014-02-19 23:00:00,12030.0 -2014-02-20 00:00:00,11233.0 -2014-02-18 01:00:00,11271.0 -2014-02-18 02:00:00,10834.0 -2014-02-18 03:00:00,10529.0 -2014-02-18 04:00:00,10378.0 -2014-02-18 05:00:00,10352.0 -2014-02-18 06:00:00,10603.0 -2014-02-18 07:00:00,11321.0 -2014-02-18 08:00:00,12380.0 -2014-02-18 09:00:00,12731.0 -2014-02-18 10:00:00,12635.0 -2014-02-18 11:00:00,12540.0 -2014-02-18 12:00:00,12458.0 -2014-02-18 13:00:00,12321.0 -2014-02-18 14:00:00,12193.0 -2014-02-18 15:00:00,12112.0 -2014-02-18 16:00:00,12001.0 -2014-02-18 17:00:00,12072.0 -2014-02-18 18:00:00,12191.0 -2014-02-18 19:00:00,12753.0 -2014-02-18 20:00:00,13179.0 -2014-02-18 21:00:00,12967.0 -2014-02-18 22:00:00,12686.0 -2014-02-18 23:00:00,12100.0 -2014-02-19 00:00:00,11378.0 -2014-02-17 01:00:00,10825.0 -2014-02-17 02:00:00,10490.0 -2014-02-17 03:00:00,10315.0 -2014-02-17 04:00:00,10271.0 -2014-02-17 05:00:00,10287.0 -2014-02-17 06:00:00,10589.0 -2014-02-17 07:00:00,11236.0 -2014-02-17 08:00:00,12228.0 -2014-02-17 09:00:00,12720.0 -2014-02-17 10:00:00,13137.0 -2014-02-17 11:00:00,13421.0 -2014-02-17 12:00:00,13550.0 -2014-02-17 13:00:00,13629.0 -2014-02-17 14:00:00,13610.0 -2014-02-17 15:00:00,13620.0 -2014-02-17 16:00:00,13571.0 -2014-02-17 17:00:00,13241.0 -2014-02-17 18:00:00,13236.0 -2014-02-17 19:00:00,13776.0 -2014-02-17 20:00:00,13984.0 -2014-02-17 21:00:00,13815.0 -2014-02-17 22:00:00,13452.0 -2014-02-17 23:00:00,12859.0 -2014-02-18 00:00:00,12010.0 -2014-02-16 01:00:00,11314.0 -2014-02-16 02:00:00,10836.0 -2014-02-16 03:00:00,10544.0 -2014-02-16 04:00:00,10343.0 -2014-02-16 05:00:00,10303.0 -2014-02-16 06:00:00,10235.0 -2014-02-16 07:00:00,10353.0 -2014-02-16 08:00:00,10650.0 -2014-02-16 09:00:00,10673.0 -2014-02-16 10:00:00,10950.0 -2014-02-16 11:00:00,11031.0 -2014-02-16 12:00:00,10978.0 -2014-02-16 13:00:00,10849.0 -2014-02-16 14:00:00,10791.0 -2014-02-16 15:00:00,10699.0 -2014-02-16 16:00:00,10713.0 -2014-02-16 17:00:00,10836.0 -2014-02-16 18:00:00,11280.0 -2014-02-16 19:00:00,12067.0 -2014-02-16 20:00:00,12514.0 -2014-02-16 21:00:00,12444.0 -2014-02-16 22:00:00,12229.0 -2014-02-16 23:00:00,11889.0 -2014-02-17 00:00:00,11380.0 -2014-02-15 01:00:00,11840.0 -2014-02-15 02:00:00,11342.0 -2014-02-15 03:00:00,11155.0 -2014-02-15 04:00:00,11024.0 -2014-02-15 05:00:00,10937.0 -2014-02-15 06:00:00,11030.0 -2014-02-15 07:00:00,11318.0 -2014-02-15 08:00:00,11800.0 -2014-02-15 09:00:00,11918.0 -2014-02-15 10:00:00,12037.0 -2014-02-15 11:00:00,12064.0 -2014-02-15 12:00:00,12004.0 -2014-02-15 13:00:00,11815.0 -2014-02-15 14:00:00,11636.0 -2014-02-15 15:00:00,11520.0 -2014-02-15 16:00:00,11529.0 -2014-02-15 17:00:00,11653.0 -2014-02-15 18:00:00,11885.0 -2014-02-15 19:00:00,12571.0 -2014-02-15 20:00:00,12854.0 -2014-02-15 21:00:00,12797.0 -2014-02-15 22:00:00,12651.0 -2014-02-15 23:00:00,12373.0 -2014-02-16 00:00:00,11830.0 -2014-02-14 01:00:00,11593.0 -2014-02-14 02:00:00,11107.0 -2014-02-14 03:00:00,10868.0 -2014-02-14 04:00:00,10793.0 -2014-02-14 05:00:00,10772.0 -2014-02-14 06:00:00,11102.0 -2014-02-14 07:00:00,11791.0 -2014-02-14 08:00:00,12812.0 -2014-02-14 09:00:00,13204.0 -2014-02-14 10:00:00,13436.0 -2014-02-14 11:00:00,13532.0 -2014-02-14 12:00:00,13493.0 -2014-02-14 13:00:00,13361.0 -2014-02-14 14:00:00,13156.0 -2014-02-14 15:00:00,13113.0 -2014-02-14 16:00:00,12977.0 -2014-02-14 17:00:00,12847.0 -2014-02-14 18:00:00,12901.0 -2014-02-14 19:00:00,13484.0 -2014-02-14 20:00:00,13843.0 -2014-02-14 21:00:00,13666.0 -2014-02-14 22:00:00,13441.0 -2014-02-14 23:00:00,12998.0 -2014-02-15 00:00:00,12464.0 -2014-02-13 01:00:00,11776.0 -2014-02-13 02:00:00,11309.0 -2014-02-13 03:00:00,11125.0 -2014-02-13 04:00:00,10957.0 -2014-02-13 05:00:00,10964.0 -2014-02-13 06:00:00,11194.0 -2014-02-13 07:00:00,11883.0 -2014-02-13 08:00:00,12939.0 -2014-02-13 09:00:00,13272.0 -2014-02-13 10:00:00,13360.0 -2014-02-13 11:00:00,13361.0 -2014-02-13 12:00:00,13426.0 -2014-02-13 13:00:00,13472.0 -2014-02-13 14:00:00,13446.0 -2014-02-13 15:00:00,13259.0 -2014-02-13 16:00:00,12868.0 -2014-02-13 17:00:00,12758.0 -2014-02-13 18:00:00,13019.0 -2014-02-13 19:00:00,13688.0 -2014-02-13 20:00:00,13845.0 -2014-02-13 21:00:00,13660.0 -2014-02-13 22:00:00,13405.0 -2014-02-13 23:00:00,13039.0 -2014-02-14 00:00:00,12346.0 -2014-02-12 01:00:00,12605.0 -2014-02-12 02:00:00,12194.0 -2014-02-12 03:00:00,11942.0 -2014-02-12 04:00:00,11838.0 -2014-02-12 05:00:00,11881.0 -2014-02-12 06:00:00,12087.0 -2014-02-12 07:00:00,12687.0 -2014-02-12 08:00:00,13637.0 -2014-02-12 09:00:00,13969.0 -2014-02-12 10:00:00,14149.0 -2014-02-12 11:00:00,14167.0 -2014-02-12 12:00:00,14067.0 -2014-02-12 13:00:00,13870.0 -2014-02-12 14:00:00,13690.0 -2014-02-12 15:00:00,13497.0 -2014-02-12 16:00:00,13317.0 -2014-02-12 17:00:00,13147.0 -2014-02-12 18:00:00,13251.0 -2014-02-12 19:00:00,14025.0 -2014-02-12 20:00:00,14254.0 -2014-02-12 21:00:00,14090.0 -2014-02-12 22:00:00,13834.0 -2014-02-12 23:00:00,13328.0 -2014-02-13 00:00:00,12511.0 -2014-02-11 01:00:00,12557.0 -2014-02-11 02:00:00,12194.0 -2014-02-11 03:00:00,11988.0 -2014-02-11 04:00:00,11868.0 -2014-02-11 05:00:00,11889.0 -2014-02-11 06:00:00,12085.0 -2014-02-11 07:00:00,12783.0 -2014-02-11 08:00:00,13829.0 -2014-02-11 09:00:00,14231.0 -2014-02-11 10:00:00,14208.0 -2014-02-11 11:00:00,14126.0 -2014-02-11 12:00:00,14014.0 -2014-02-11 13:00:00,13901.0 -2014-02-11 14:00:00,13743.0 -2014-02-11 15:00:00,13646.0 -2014-02-11 16:00:00,13459.0 -2014-02-11 17:00:00,13387.0 -2014-02-11 18:00:00,13570.0 -2014-02-11 19:00:00,14366.0 -2014-02-11 20:00:00,14804.0 -2014-02-11 21:00:00,14717.0 -2014-02-11 22:00:00,14543.0 -2014-02-11 23:00:00,14063.0 -2014-02-12 00:00:00,13341.0 -2014-02-10 01:00:00,11752.0 -2014-02-10 02:00:00,11459.0 -2014-02-10 03:00:00,11323.0 -2014-02-10 04:00:00,11280.0 -2014-02-10 05:00:00,11393.0 -2014-02-10 06:00:00,11695.0 -2014-02-10 07:00:00,12502.0 -2014-02-10 08:00:00,13548.0 -2014-02-10 09:00:00,14019.0 -2014-02-10 10:00:00,14078.0 -2014-02-10 11:00:00,14030.0 -2014-02-10 12:00:00,13968.0 -2014-02-10 13:00:00,13840.0 -2014-02-10 14:00:00,13729.0 -2014-02-10 15:00:00,13619.0 -2014-02-10 16:00:00,13493.0 -2014-02-10 17:00:00,13418.0 -2014-02-10 18:00:00,13649.0 -2014-02-10 19:00:00,14479.0 -2014-02-10 20:00:00,14931.0 -2014-02-10 21:00:00,14855.0 -2014-02-10 22:00:00,14618.0 -2014-02-10 23:00:00,14125.0 -2014-02-11 00:00:00,13270.0 -2014-02-09 01:00:00,11551.0 -2014-02-09 02:00:00,11117.0 -2014-02-09 03:00:00,10864.0 -2014-02-09 04:00:00,10689.0 -2014-02-09 05:00:00,10659.0 -2014-02-09 06:00:00,10658.0 -2014-02-09 07:00:00,10846.0 -2014-02-09 08:00:00,11137.0 -2014-02-09 09:00:00,11192.0 -2014-02-09 10:00:00,11321.0 -2014-02-09 11:00:00,11421.0 -2014-02-09 12:00:00,11421.0 -2014-02-09 13:00:00,11408.0 -2014-02-09 14:00:00,11323.0 -2014-02-09 15:00:00,11301.0 -2014-02-09 16:00:00,11330.0 -2014-02-09 17:00:00,11506.0 -2014-02-09 18:00:00,11798.0 -2014-02-09 19:00:00,12822.0 -2014-02-09 20:00:00,13271.0 -2014-02-09 21:00:00,13249.0 -2014-02-09 22:00:00,13062.0 -2014-02-09 23:00:00,12811.0 -2014-02-10 00:00:00,12227.0 -2014-02-08 01:00:00,12448.0 -2014-02-08 02:00:00,12023.0 -2014-02-08 03:00:00,11706.0 -2014-02-08 04:00:00,11547.0 -2014-02-08 05:00:00,11394.0 -2014-02-08 06:00:00,11454.0 -2014-02-08 07:00:00,11654.0 -2014-02-08 08:00:00,12090.0 -2014-02-08 09:00:00,12237.0 -2014-02-08 10:00:00,12588.0 -2014-02-08 11:00:00,12737.0 -2014-02-08 12:00:00,12805.0 -2014-02-08 13:00:00,12720.0 -2014-02-08 14:00:00,12737.0 -2014-02-08 15:00:00,12595.0 -2014-02-08 16:00:00,12437.0 -2014-02-08 17:00:00,12374.0 -2014-02-08 18:00:00,12546.0 -2014-02-08 19:00:00,13202.0 -2014-02-08 20:00:00,13470.0 -2014-02-08 21:00:00,13283.0 -2014-02-08 22:00:00,13071.0 -2014-02-08 23:00:00,12711.0 -2014-02-09 00:00:00,12119.0 -2014-02-07 01:00:00,12946.0 -2014-02-07 02:00:00,12507.0 -2014-02-07 03:00:00,12258.0 -2014-02-07 04:00:00,12114.0 -2014-02-07 05:00:00,12104.0 -2014-02-07 06:00:00,12355.0 -2014-02-07 07:00:00,12960.0 -2014-02-07 08:00:00,14006.0 -2014-02-07 09:00:00,14320.0 -2014-02-07 10:00:00,14318.0 -2014-02-07 11:00:00,14229.0 -2014-02-07 12:00:00,14152.0 -2014-02-07 13:00:00,14047.0 -2014-02-07 14:00:00,13921.0 -2014-02-07 15:00:00,13893.0 -2014-02-07 16:00:00,13676.0 -2014-02-07 17:00:00,13580.0 -2014-02-07 18:00:00,13746.0 -2014-02-07 19:00:00,14478.0 -2014-02-07 20:00:00,14800.0 -2014-02-07 21:00:00,14646.0 -2014-02-07 22:00:00,14318.0 -2014-02-07 23:00:00,13947.0 -2014-02-08 00:00:00,13221.0 -2014-02-06 01:00:00,12121.0 -2014-02-06 02:00:00,11675.0 -2014-02-06 03:00:00,11477.0 -2014-02-06 04:00:00,11438.0 -2014-02-06 05:00:00,11473.0 -2014-02-06 06:00:00,11738.0 -2014-02-06 07:00:00,12460.0 -2014-02-06 08:00:00,13592.0 -2014-02-06 09:00:00,13993.0 -2014-02-06 10:00:00,14048.0 -2014-02-06 11:00:00,13999.0 -2014-02-06 12:00:00,14043.0 -2014-02-06 13:00:00,13989.0 -2014-02-06 14:00:00,13926.0 -2014-02-06 15:00:00,13859.0 -2014-02-06 16:00:00,13761.0 -2014-02-06 17:00:00,13754.0 -2014-02-06 18:00:00,14010.0 -2014-02-06 19:00:00,14856.0 -2014-02-06 20:00:00,15256.0 -2014-02-06 21:00:00,15155.0 -2014-02-06 22:00:00,14952.0 -2014-02-06 23:00:00,14458.0 -2014-02-07 00:00:00,13666.0 -2014-02-05 01:00:00,11776.0 -2014-02-05 02:00:00,11305.0 -2014-02-05 03:00:00,11031.0 -2014-02-05 04:00:00,10906.0 -2014-02-05 05:00:00,10930.0 -2014-02-05 06:00:00,11140.0 -2014-02-05 07:00:00,11808.0 -2014-02-05 08:00:00,12837.0 -2014-02-05 09:00:00,13206.0 -2014-02-05 10:00:00,13357.0 -2014-02-05 11:00:00,13448.0 -2014-02-05 12:00:00,13513.0 -2014-02-05 13:00:00,13467.0 -2014-02-05 14:00:00,13409.0 -2014-02-05 15:00:00,13432.0 -2014-02-05 16:00:00,13365.0 -2014-02-05 17:00:00,13348.0 -2014-02-05 18:00:00,13510.0 -2014-02-05 19:00:00,14223.0 -2014-02-05 20:00:00,14458.0 -2014-02-05 21:00:00,14340.0 -2014-02-05 22:00:00,14095.0 -2014-02-05 23:00:00,13588.0 -2014-02-06 00:00:00,12806.0 -2014-02-04 01:00:00,12030.0 -2014-02-04 02:00:00,11603.0 -2014-02-04 03:00:00,11337.0 -2014-02-04 04:00:00,11239.0 -2014-02-04 05:00:00,11224.0 -2014-02-04 06:00:00,11437.0 -2014-02-04 07:00:00,12078.0 -2014-02-04 08:00:00,13185.0 -2014-02-04 09:00:00,13689.0 -2014-02-04 10:00:00,13702.0 -2014-02-04 11:00:00,13715.0 -2014-02-04 12:00:00,13714.0 -2014-02-04 13:00:00,13649.0 -2014-02-04 14:00:00,13623.0 -2014-02-04 15:00:00,13594.0 -2014-02-04 16:00:00,13502.0 -2014-02-04 17:00:00,13480.0 -2014-02-04 18:00:00,13744.0 -2014-02-04 19:00:00,14431.0 -2014-02-04 20:00:00,14448.0 -2014-02-04 21:00:00,14273.0 -2014-02-04 22:00:00,13962.0 -2014-02-04 23:00:00,13374.0 -2014-02-05 00:00:00,12535.0 -2014-02-03 01:00:00,11416.0 -2014-02-03 02:00:00,11158.0 -2014-02-03 03:00:00,11020.0 -2014-02-03 04:00:00,11047.0 -2014-02-03 05:00:00,11102.0 -2014-02-03 06:00:00,11428.0 -2014-02-03 07:00:00,12188.0 -2014-02-03 08:00:00,13349.0 -2014-02-03 09:00:00,13753.0 -2014-02-03 10:00:00,13821.0 -2014-02-03 11:00:00,13654.0 -2014-02-03 12:00:00,13579.0 -2014-02-03 13:00:00,13455.0 -2014-02-03 14:00:00,13305.0 -2014-02-03 15:00:00,13231.0 -2014-02-03 16:00:00,13072.0 -2014-02-03 17:00:00,12938.0 -2014-02-03 18:00:00,13274.0 -2014-02-03 19:00:00,14135.0 -2014-02-03 20:00:00,14406.0 -2014-02-03 21:00:00,14294.0 -2014-02-03 22:00:00,14044.0 -2014-02-03 23:00:00,13574.0 -2014-02-04 00:00:00,12749.0 -2014-02-02 01:00:00,11024.0 -2014-02-02 02:00:00,10589.0 -2014-02-02 03:00:00,10333.0 -2014-02-02 04:00:00,10201.0 -2014-02-02 05:00:00,10087.0 -2014-02-02 06:00:00,10210.0 -2014-02-02 07:00:00,10418.0 -2014-02-02 08:00:00,10726.0 -2014-02-02 09:00:00,10791.0 -2014-02-02 10:00:00,10964.0 -2014-02-02 11:00:00,11085.0 -2014-02-02 12:00:00,11150.0 -2014-02-02 13:00:00,11209.0 -2014-02-02 14:00:00,11185.0 -2014-02-02 15:00:00,11208.0 -2014-02-02 16:00:00,11298.0 -2014-02-02 17:00:00,11378.0 -2014-02-02 18:00:00,11751.0 -2014-02-02 19:00:00,12526.0 -2014-02-02 20:00:00,12731.0 -2014-02-02 21:00:00,12677.0 -2014-02-02 22:00:00,12616.0 -2014-02-02 23:00:00,12386.0 -2014-02-03 00:00:00,11863.0 -2014-02-01 01:00:00,11639.0 -2014-02-01 02:00:00,11078.0 -2014-02-01 03:00:00,10766.0 -2014-02-01 04:00:00,10538.0 -2014-02-01 05:00:00,10467.0 -2014-02-01 06:00:00,10548.0 -2014-02-01 07:00:00,10838.0 -2014-02-01 08:00:00,11234.0 -2014-02-01 09:00:00,11426.0 -2014-02-01 10:00:00,11679.0 -2014-02-01 11:00:00,11860.0 -2014-02-01 12:00:00,12037.0 -2014-02-01 13:00:00,12023.0 -2014-02-01 14:00:00,11922.0 -2014-02-01 15:00:00,11739.0 -2014-02-01 16:00:00,11687.0 -2014-02-01 17:00:00,11710.0 -2014-02-01 18:00:00,11916.0 -2014-02-01 19:00:00,12704.0 -2014-02-01 20:00:00,12806.0 -2014-02-01 21:00:00,12666.0 -2014-02-01 22:00:00,12418.0 -2014-02-01 23:00:00,12058.0 -2014-02-02 00:00:00,11549.0 -2014-01-31 01:00:00,11551.0 -2014-01-31 02:00:00,11139.0 -2014-01-31 03:00:00,10938.0 -2014-01-31 04:00:00,10808.0 -2014-01-31 05:00:00,10798.0 -2014-01-31 06:00:00,11042.0 -2014-01-31 07:00:00,11724.0 -2014-01-31 08:00:00,12795.0 -2014-01-31 09:00:00,13301.0 -2014-01-31 10:00:00,13386.0 -2014-01-31 11:00:00,13430.0 -2014-01-31 12:00:00,13404.0 -2014-01-31 13:00:00,13388.0 -2014-01-31 14:00:00,13253.0 -2014-01-31 15:00:00,13180.0 -2014-01-31 16:00:00,13071.0 -2014-01-31 17:00:00,12997.0 -2014-01-31 18:00:00,13233.0 -2014-01-31 19:00:00,13869.0 -2014-01-31 20:00:00,13864.0 -2014-01-31 21:00:00,13687.0 -2014-01-31 22:00:00,13376.0 -2014-01-31 23:00:00,13013.0 -2014-02-01 00:00:00,12371.0 -2014-01-30 01:00:00,12423.0 -2014-01-30 02:00:00,11920.0 -2014-01-30 03:00:00,11628.0 -2014-01-30 04:00:00,11528.0 -2014-01-30 05:00:00,11499.0 -2014-01-30 06:00:00,11693.0 -2014-01-30 07:00:00,12329.0 -2014-01-30 08:00:00,13364.0 -2014-01-30 09:00:00,13826.0 -2014-01-30 10:00:00,13914.0 -2014-01-30 11:00:00,13981.0 -2014-01-30 12:00:00,14075.0 -2014-01-30 13:00:00,13920.0 -2014-01-30 14:00:00,13820.0 -2014-01-30 15:00:00,13860.0 -2014-01-30 16:00:00,13845.0 -2014-01-30 17:00:00,13809.0 -2014-01-30 18:00:00,14013.0 -2014-01-30 19:00:00,14533.0 -2014-01-30 20:00:00,14403.0 -2014-01-30 21:00:00,14122.0 -2014-01-30 22:00:00,13752.0 -2014-01-30 23:00:00,13190.0 -2014-01-31 00:00:00,12324.0 -2014-01-29 01:00:00,13295.0 -2014-01-29 02:00:00,12815.0 -2014-01-29 03:00:00,12590.0 -2014-01-29 04:00:00,12396.0 -2014-01-29 05:00:00,12415.0 -2014-01-29 06:00:00,12646.0 -2014-01-29 07:00:00,13266.0 -2014-01-29 08:00:00,14296.0 -2014-01-29 09:00:00,14640.0 -2014-01-29 10:00:00,14590.0 -2014-01-29 11:00:00,14448.0 -2014-01-29 12:00:00,14357.0 -2014-01-29 13:00:00,14188.0 -2014-01-29 14:00:00,14009.0 -2014-01-29 15:00:00,13912.0 -2014-01-29 16:00:00,13735.0 -2014-01-29 17:00:00,13621.0 -2014-01-29 18:00:00,13912.0 -2014-01-29 19:00:00,14759.0 -2014-01-29 20:00:00,14925.0 -2014-01-29 21:00:00,14758.0 -2014-01-29 22:00:00,14461.0 -2014-01-29 23:00:00,13959.0 -2014-01-30 00:00:00,13172.0 -2014-01-28 01:00:00,13525.0 -2014-01-28 02:00:00,13154.0 -2014-01-28 03:00:00,12901.0 -2014-01-28 04:00:00,12778.0 -2014-01-28 05:00:00,12723.0 -2014-01-28 06:00:00,12905.0 -2014-01-28 07:00:00,13491.0 -2014-01-28 08:00:00,14313.0 -2014-01-28 09:00:00,14670.0 -2014-01-28 10:00:00,14873.0 -2014-01-28 11:00:00,14932.0 -2014-01-28 12:00:00,15002.0 -2014-01-28 13:00:00,14950.0 -2014-01-28 14:00:00,14847.0 -2014-01-28 15:00:00,14783.0 -2014-01-28 16:00:00,14619.0 -2014-01-28 17:00:00,14531.0 -2014-01-28 18:00:00,14819.0 -2014-01-28 19:00:00,15720.0 -2014-01-28 20:00:00,15862.0 -2014-01-28 21:00:00,15651.0 -2014-01-28 22:00:00,15328.0 -2014-01-28 23:00:00,14749.0 -2014-01-29 00:00:00,13959.0 -2014-01-27 01:00:00,12203.0 -2014-01-27 02:00:00,11921.0 -2014-01-27 03:00:00,11831.0 -2014-01-27 04:00:00,11787.0 -2014-01-27 05:00:00,11880.0 -2014-01-27 06:00:00,12078.0 -2014-01-27 07:00:00,12769.0 -2014-01-27 08:00:00,13710.0 -2014-01-27 09:00:00,14162.0 -2014-01-27 10:00:00,14352.0 -2014-01-27 11:00:00,14578.0 -2014-01-27 12:00:00,14699.0 -2014-01-27 13:00:00,14738.0 -2014-01-27 14:00:00,14728.0 -2014-01-27 15:00:00,14754.0 -2014-01-27 16:00:00,14649.0 -2014-01-27 17:00:00,14634.0 -2014-01-27 18:00:00,14932.0 -2014-01-27 19:00:00,15872.0 -2014-01-27 20:00:00,16064.0 -2014-01-27 21:00:00,15875.0 -2014-01-27 22:00:00,15615.0 -2014-01-27 23:00:00,15068.0 -2014-01-28 00:00:00,14229.0 -2014-01-26 01:00:00,12082.0 -2014-01-26 02:00:00,11619.0 -2014-01-26 03:00:00,11391.0 -2014-01-26 04:00:00,11216.0 -2014-01-26 05:00:00,11196.0 -2014-01-26 06:00:00,11162.0 -2014-01-26 07:00:00,11360.0 -2014-01-26 08:00:00,11560.0 -2014-01-26 09:00:00,11684.0 -2014-01-26 10:00:00,11810.0 -2014-01-26 11:00:00,11966.0 -2014-01-26 12:00:00,11865.0 -2014-01-26 13:00:00,11950.0 -2014-01-26 14:00:00,11825.0 -2014-01-26 15:00:00,11658.0 -2014-01-26 16:00:00,11456.0 -2014-01-26 17:00:00,11567.0 -2014-01-26 18:00:00,12076.0 -2014-01-26 19:00:00,12987.0 -2014-01-26 20:00:00,13078.0 -2014-01-26 21:00:00,13114.0 -2014-01-26 22:00:00,13037.0 -2014-01-26 23:00:00,12837.0 -2014-01-27 00:00:00,12567.0 -2014-01-25 01:00:00,12269.0 -2014-01-25 02:00:00,11649.0 -2014-01-25 03:00:00,11193.0 -2014-01-25 04:00:00,10920.0 -2014-01-25 05:00:00,10788.0 -2014-01-25 06:00:00,10706.0 -2014-01-25 07:00:00,10992.0 -2014-01-25 08:00:00,11517.0 -2014-01-25 09:00:00,11730.0 -2014-01-25 10:00:00,12148.0 -2014-01-25 11:00:00,12381.0 -2014-01-25 12:00:00,12440.0 -2014-01-25 13:00:00,12408.0 -2014-01-25 14:00:00,12347.0 -2014-01-25 15:00:00,12195.0 -2014-01-25 16:00:00,12086.0 -2014-01-25 17:00:00,12175.0 -2014-01-25 18:00:00,12555.0 -2014-01-25 19:00:00,13614.0 -2014-01-25 20:00:00,13790.0 -2014-01-25 21:00:00,13762.0 -2014-01-25 22:00:00,13530.0 -2014-01-25 23:00:00,13249.0 -2014-01-26 00:00:00,12631.0 -2014-01-24 01:00:00,13337.0 -2014-01-24 02:00:00,12921.0 -2014-01-24 03:00:00,12637.0 -2014-01-24 04:00:00,12519.0 -2014-01-24 05:00:00,12528.0 -2014-01-24 06:00:00,12739.0 -2014-01-24 07:00:00,13255.0 -2014-01-24 08:00:00,14306.0 -2014-01-24 09:00:00,14802.0 -2014-01-24 10:00:00,15031.0 -2014-01-24 11:00:00,15082.0 -2014-01-24 12:00:00,14982.0 -2014-01-24 13:00:00,14760.0 -2014-01-24 14:00:00,14649.0 -2014-01-24 15:00:00,14623.0 -2014-01-24 16:00:00,14497.0 -2014-01-24 17:00:00,14410.0 -2014-01-24 18:00:00,14631.0 -2014-01-24 19:00:00,15142.0 -2014-01-24 20:00:00,15011.0 -2014-01-24 21:00:00,14687.0 -2014-01-24 22:00:00,14336.0 -2014-01-24 23:00:00,13827.0 -2014-01-25 00:00:00,13035.0 -2014-01-23 01:00:00,13119.0 -2014-01-23 02:00:00,12701.0 -2014-01-23 03:00:00,12509.0 -2014-01-23 04:00:00,12387.0 -2014-01-23 05:00:00,12381.0 -2014-01-23 06:00:00,12604.0 -2014-01-23 07:00:00,13255.0 -2014-01-23 08:00:00,14331.0 -2014-01-23 09:00:00,14703.0 -2014-01-23 10:00:00,14810.0 -2014-01-23 11:00:00,14770.0 -2014-01-23 12:00:00,14727.0 -2014-01-23 13:00:00,14582.0 -2014-01-23 14:00:00,14407.0 -2014-01-23 15:00:00,14313.0 -2014-01-23 16:00:00,14154.0 -2014-01-23 17:00:00,14165.0 -2014-01-23 18:00:00,14538.0 -2014-01-23 19:00:00,15469.0 -2014-01-23 20:00:00,15554.0 -2014-01-23 21:00:00,15476.0 -2014-01-23 22:00:00,15290.0 -2014-01-23 23:00:00,14813.0 -2014-01-24 00:00:00,14015.0 -2014-01-22 01:00:00,12664.0 -2014-01-22 02:00:00,12229.0 -2014-01-22 03:00:00,11935.0 -2014-01-22 04:00:00,11812.0 -2014-01-22 05:00:00,11831.0 -2014-01-22 06:00:00,11976.0 -2014-01-22 07:00:00,12684.0 -2014-01-22 08:00:00,13747.0 -2014-01-22 09:00:00,14304.0 -2014-01-22 10:00:00,14416.0 -2014-01-22 11:00:00,14440.0 -2014-01-22 12:00:00,14532.0 -2014-01-22 13:00:00,14409.0 -2014-01-22 14:00:00,14245.0 -2014-01-22 15:00:00,14250.0 -2014-01-22 16:00:00,14145.0 -2014-01-22 17:00:00,14214.0 -2014-01-22 18:00:00,14500.0 -2014-01-22 19:00:00,15226.0 -2014-01-22 20:00:00,15406.0 -2014-01-22 21:00:00,15233.0 -2014-01-22 22:00:00,15047.0 -2014-01-22 23:00:00,14552.0 -2014-01-23 00:00:00,13821.0 -2014-01-21 01:00:00,11786.0 -2014-01-21 02:00:00,11436.0 -2014-01-21 03:00:00,11305.0 -2014-01-21 04:00:00,11274.0 -2014-01-21 05:00:00,11388.0 -2014-01-21 06:00:00,11694.0 -2014-01-21 07:00:00,12430.0 -2014-01-21 08:00:00,13558.0 -2014-01-21 09:00:00,14016.0 -2014-01-21 10:00:00,14062.0 -2014-01-21 11:00:00,14045.0 -2014-01-21 12:00:00,14029.0 -2014-01-21 13:00:00,13915.0 -2014-01-21 14:00:00,13821.0 -2014-01-21 15:00:00,13715.0 -2014-01-21 16:00:00,13625.0 -2014-01-21 17:00:00,13613.0 -2014-01-21 18:00:00,14077.0 -2014-01-21 19:00:00,15013.0 -2014-01-21 20:00:00,15061.0 -2014-01-21 21:00:00,14903.0 -2014-01-21 22:00:00,14698.0 -2014-01-21 23:00:00,14205.0 -2014-01-22 00:00:00,13408.0 -2014-01-20 01:00:00,10566.0 -2014-01-20 02:00:00,10230.0 -2014-01-20 03:00:00,9993.0 -2014-01-20 04:00:00,9856.0 -2014-01-20 05:00:00,9830.0 -2014-01-20 06:00:00,10101.0 -2014-01-20 07:00:00,10762.0 -2014-01-20 08:00:00,11662.0 -2014-01-20 09:00:00,12193.0 -2014-01-20 10:00:00,12395.0 -2014-01-20 11:00:00,12507.0 -2014-01-20 12:00:00,12658.0 -2014-01-20 13:00:00,12696.0 -2014-01-20 14:00:00,12561.0 -2014-01-20 15:00:00,12527.0 -2014-01-20 16:00:00,12486.0 -2014-01-20 17:00:00,12619.0 -2014-01-20 18:00:00,13192.0 -2014-01-20 19:00:00,13868.0 -2014-01-20 20:00:00,13871.0 -2014-01-20 21:00:00,13794.0 -2014-01-20 22:00:00,13638.0 -2014-01-20 23:00:00,13129.0 -2014-01-21 00:00:00,12400.0 -2014-01-19 01:00:00,11453.0 -2014-01-19 02:00:00,11069.0 -2014-01-19 03:00:00,10888.0 -2014-01-19 04:00:00,10790.0 -2014-01-19 05:00:00,10766.0 -2014-01-19 06:00:00,10787.0 -2014-01-19 07:00:00,11002.0 -2014-01-19 08:00:00,11259.0 -2014-01-19 09:00:00,11351.0 -2014-01-19 10:00:00,11423.0 -2014-01-19 11:00:00,11512.0 -2014-01-19 12:00:00,11478.0 -2014-01-19 13:00:00,11390.0 -2014-01-19 14:00:00,11257.0 -2014-01-19 15:00:00,11187.0 -2014-01-19 16:00:00,11256.0 -2014-01-19 17:00:00,11311.0 -2014-01-19 18:00:00,11639.0 -2014-01-19 19:00:00,12572.0 -2014-01-19 20:00:00,12667.0 -2014-01-19 21:00:00,12422.0 -2014-01-19 22:00:00,12156.0 -2014-01-19 23:00:00,11720.0 -2014-01-20 00:00:00,11198.0 -2014-01-18 01:00:00,12116.0 -2014-01-18 02:00:00,11614.0 -2014-01-18 03:00:00,11322.0 -2014-01-18 04:00:00,11147.0 -2014-01-18 05:00:00,11092.0 -2014-01-18 06:00:00,11150.0 -2014-01-18 07:00:00,11440.0 -2014-01-18 08:00:00,11888.0 -2014-01-18 09:00:00,12153.0 -2014-01-18 10:00:00,12472.0 -2014-01-18 11:00:00,12800.0 -2014-01-18 12:00:00,12944.0 -2014-01-18 13:00:00,12890.0 -2014-01-18 14:00:00,12749.0 -2014-01-18 15:00:00,12578.0 -2014-01-18 16:00:00,12379.0 -2014-01-18 17:00:00,12347.0 -2014-01-18 18:00:00,12611.0 -2014-01-18 19:00:00,13358.0 -2014-01-18 20:00:00,13311.0 -2014-01-18 21:00:00,13157.0 -2014-01-18 22:00:00,12865.0 -2014-01-18 23:00:00,12546.0 -2014-01-19 00:00:00,11984.0 -2014-01-17 01:00:00,11677.0 -2014-01-17 02:00:00,11246.0 -2014-01-17 03:00:00,11027.0 -2014-01-17 04:00:00,10917.0 -2014-01-17 05:00:00,10935.0 -2014-01-17 06:00:00,11232.0 -2014-01-17 07:00:00,11906.0 -2014-01-17 08:00:00,13033.0 -2014-01-17 09:00:00,13644.0 -2014-01-17 10:00:00,13732.0 -2014-01-17 11:00:00,13843.0 -2014-01-17 12:00:00,13886.0 -2014-01-17 13:00:00,13812.0 -2014-01-17 14:00:00,13675.0 -2014-01-17 15:00:00,13662.0 -2014-01-17 16:00:00,13621.0 -2014-01-17 17:00:00,13628.0 -2014-01-17 18:00:00,14022.0 -2014-01-17 19:00:00,14528.0 -2014-01-17 20:00:00,14389.0 -2014-01-17 21:00:00,14166.0 -2014-01-17 22:00:00,13819.0 -2014-01-17 23:00:00,13473.0 -2014-01-18 00:00:00,12768.0 -2014-01-16 01:00:00,11846.0 -2014-01-16 02:00:00,11383.0 -2014-01-16 03:00:00,11065.0 -2014-01-16 04:00:00,10914.0 -2014-01-16 05:00:00,10855.0 -2014-01-16 06:00:00,11054.0 -2014-01-16 07:00:00,11711.0 -2014-01-16 08:00:00,12797.0 -2014-01-16 09:00:00,13462.0 -2014-01-16 10:00:00,13539.0 -2014-01-16 11:00:00,13539.0 -2014-01-16 12:00:00,13504.0 -2014-01-16 13:00:00,13433.0 -2014-01-16 14:00:00,13283.0 -2014-01-16 15:00:00,13289.0 -2014-01-16 16:00:00,13192.0 -2014-01-16 17:00:00,13144.0 -2014-01-16 18:00:00,13541.0 -2014-01-16 19:00:00,14083.0 -2014-01-16 20:00:00,13938.0 -2014-01-16 21:00:00,13822.0 -2014-01-16 22:00:00,13605.0 -2014-01-16 23:00:00,13152.0 -2014-01-17 00:00:00,12378.0 -2014-01-15 01:00:00,11502.0 -2014-01-15 02:00:00,10998.0 -2014-01-15 03:00:00,10763.0 -2014-01-15 04:00:00,10636.0 -2014-01-15 05:00:00,10671.0 -2014-01-15 06:00:00,10930.0 -2014-01-15 07:00:00,11643.0 -2014-01-15 08:00:00,12823.0 -2014-01-15 09:00:00,13443.0 -2014-01-15 10:00:00,13370.0 -2014-01-15 11:00:00,13269.0 -2014-01-15 12:00:00,13234.0 -2014-01-15 13:00:00,13204.0 -2014-01-15 14:00:00,13165.0 -2014-01-15 15:00:00,13267.0 -2014-01-15 16:00:00,13181.0 -2014-01-15 17:00:00,13135.0 -2014-01-15 18:00:00,13564.0 -2014-01-15 19:00:00,14473.0 -2014-01-15 20:00:00,14416.0 -2014-01-15 21:00:00,14203.0 -2014-01-15 22:00:00,13918.0 -2014-01-15 23:00:00,13404.0 -2014-01-16 00:00:00,12585.0 -2014-01-14 01:00:00,10825.0 -2014-01-14 02:00:00,10366.0 -2014-01-14 03:00:00,10101.0 -2014-01-14 04:00:00,9950.0 -2014-01-14 05:00:00,9989.0 -2014-01-14 06:00:00,10293.0 -2014-01-14 07:00:00,11036.0 -2014-01-14 08:00:00,12200.0 -2014-01-14 09:00:00,12646.0 -2014-01-14 10:00:00,12700.0 -2014-01-14 11:00:00,12831.0 -2014-01-14 12:00:00,13016.0 -2014-01-14 13:00:00,13036.0 -2014-01-14 14:00:00,13045.0 -2014-01-14 15:00:00,13122.0 -2014-01-14 16:00:00,13135.0 -2014-01-14 17:00:00,13219.0 -2014-01-14 18:00:00,13723.0 -2014-01-14 19:00:00,14269.0 -2014-01-14 20:00:00,14124.0 -2014-01-14 21:00:00,13904.0 -2014-01-14 22:00:00,13601.0 -2014-01-14 23:00:00,13075.0 -2014-01-15 00:00:00,12265.0 -2014-01-13 01:00:00,10229.0 -2014-01-13 02:00:00,9861.0 -2014-01-13 03:00:00,9600.0 -2014-01-13 04:00:00,9519.0 -2014-01-13 05:00:00,9497.0 -2014-01-13 06:00:00,9824.0 -2014-01-13 07:00:00,10558.0 -2014-01-13 08:00:00,11822.0 -2014-01-13 09:00:00,12421.0 -2014-01-13 10:00:00,12366.0 -2014-01-13 11:00:00,12350.0 -2014-01-13 12:00:00,12283.0 -2014-01-13 13:00:00,12164.0 -2014-01-13 14:00:00,12007.0 -2014-01-13 15:00:00,11938.0 -2014-01-13 16:00:00,11840.0 -2014-01-13 17:00:00,11844.0 -2014-01-13 18:00:00,12325.0 -2014-01-13 19:00:00,13210.0 -2014-01-13 20:00:00,13200.0 -2014-01-13 21:00:00,13013.0 -2014-01-13 22:00:00,12809.0 -2014-01-13 23:00:00,12323.0 -2014-01-14 00:00:00,11531.0 -2014-01-12 01:00:00,10713.0 -2014-01-12 02:00:00,10257.0 -2014-01-12 03:00:00,9967.0 -2014-01-12 04:00:00,9786.0 -2014-01-12 05:00:00,9748.0 -2014-01-12 06:00:00,9760.0 -2014-01-12 07:00:00,9934.0 -2014-01-12 08:00:00,10205.0 -2014-01-12 09:00:00,10353.0 -2014-01-12 10:00:00,10525.0 -2014-01-12 11:00:00,10694.0 -2014-01-12 12:00:00,10652.0 -2014-01-12 13:00:00,10773.0 -2014-01-12 14:00:00,10760.0 -2014-01-12 15:00:00,10716.0 -2014-01-12 16:00:00,10604.0 -2014-01-12 17:00:00,10725.0 -2014-01-12 18:00:00,11265.0 -2014-01-12 19:00:00,12126.0 -2014-01-12 20:00:00,12145.0 -2014-01-12 21:00:00,12062.0 -2014-01-12 22:00:00,11846.0 -2014-01-12 23:00:00,11389.0 -2014-01-13 00:00:00,10828.0 -2014-01-11 01:00:00,11084.0 -2014-01-11 02:00:00,10553.0 -2014-01-11 03:00:00,10120.0 -2014-01-11 04:00:00,9894.0 -2014-01-11 05:00:00,9784.0 -2014-01-11 06:00:00,9803.0 -2014-01-11 07:00:00,10095.0 -2014-01-11 08:00:00,10578.0 -2014-01-11 09:00:00,11048.0 -2014-01-11 10:00:00,11334.0 -2014-01-11 11:00:00,11759.0 -2014-01-11 12:00:00,11887.0 -2014-01-11 13:00:00,11902.0 -2014-01-11 14:00:00,11731.0 -2014-01-11 15:00:00,11549.0 -2014-01-11 16:00:00,11461.0 -2014-01-11 17:00:00,11480.0 -2014-01-11 18:00:00,11952.0 -2014-01-11 19:00:00,12556.0 -2014-01-11 20:00:00,12506.0 -2014-01-11 21:00:00,12384.0 -2014-01-11 22:00:00,12159.0 -2014-01-11 23:00:00,11810.0 -2014-01-12 00:00:00,11328.0 -2014-01-10 01:00:00,11881.0 -2014-01-10 02:00:00,11324.0 -2014-01-10 03:00:00,10994.0 -2014-01-10 04:00:00,10756.0 -2014-01-10 05:00:00,10678.0 -2014-01-10 06:00:00,10845.0 -2014-01-10 07:00:00,11457.0 -2014-01-10 08:00:00,12555.0 -2014-01-10 09:00:00,13142.0 -2014-01-10 10:00:00,13126.0 -2014-01-10 11:00:00,13174.0 -2014-01-10 12:00:00,13229.0 -2014-01-10 13:00:00,13206.0 -2014-01-10 14:00:00,13135.0 -2014-01-10 15:00:00,13178.0 -2014-01-10 16:00:00,13261.0 -2014-01-10 17:00:00,13243.0 -2014-01-10 18:00:00,13801.0 -2014-01-10 19:00:00,14075.0 -2014-01-10 20:00:00,13841.0 -2014-01-10 21:00:00,13525.0 -2014-01-10 22:00:00,13050.0 -2014-01-10 23:00:00,12585.0 -2014-01-11 00:00:00,11890.0 -2014-01-09 01:00:00,12871.0 -2014-01-09 02:00:00,12381.0 -2014-01-09 03:00:00,12158.0 -2014-01-09 04:00:00,11985.0 -2014-01-09 05:00:00,11981.0 -2014-01-09 06:00:00,12166.0 -2014-01-09 07:00:00,12812.0 -2014-01-09 08:00:00,13883.0 -2014-01-09 09:00:00,14373.0 -2014-01-09 10:00:00,14404.0 -2014-01-09 11:00:00,14407.0 -2014-01-09 12:00:00,14348.0 -2014-01-09 13:00:00,14245.0 -2014-01-09 14:00:00,14203.0 -2014-01-09 15:00:00,14218.0 -2014-01-09 16:00:00,14038.0 -2014-01-09 17:00:00,13985.0 -2014-01-09 18:00:00,14451.0 -2014-01-09 19:00:00,14945.0 -2014-01-09 20:00:00,14771.0 -2014-01-09 21:00:00,14604.0 -2014-01-09 22:00:00,14267.0 -2014-01-09 23:00:00,13612.0 -2014-01-10 00:00:00,12717.0 -2014-01-08 01:00:00,13204.0 -2014-01-08 02:00:00,12723.0 -2014-01-08 03:00:00,12467.0 -2014-01-08 04:00:00,12289.0 -2014-01-08 05:00:00,12269.0 -2014-01-08 06:00:00,12469.0 -2014-01-08 07:00:00,13040.0 -2014-01-08 08:00:00,14088.0 -2014-01-08 09:00:00,14580.0 -2014-01-08 10:00:00,14612.0 -2014-01-08 11:00:00,14599.0 -2014-01-08 12:00:00,14549.0 -2014-01-08 13:00:00,14377.0 -2014-01-08 14:00:00,14209.0 -2014-01-08 15:00:00,14092.0 -2014-01-08 16:00:00,14006.0 -2014-01-08 17:00:00,14028.0 -2014-01-08 18:00:00,14586.0 -2014-01-08 19:00:00,15356.0 -2014-01-08 20:00:00,15337.0 -2014-01-08 21:00:00,15199.0 -2014-01-08 22:00:00,14932.0 -2014-01-08 23:00:00,14439.0 -2014-01-09 00:00:00,13604.0 -2014-01-07 01:00:00,13829.0 -2014-01-07 02:00:00,13349.0 -2014-01-07 03:00:00,13034.0 -2014-01-07 04:00:00,12892.0 -2014-01-07 05:00:00,12866.0 -2014-01-07 06:00:00,13023.0 -2014-01-07 07:00:00,13513.0 -2014-01-07 08:00:00,14255.0 -2014-01-07 09:00:00,14604.0 -2014-01-07 10:00:00,14785.0 -2014-01-07 11:00:00,14990.0 -2014-01-07 12:00:00,15226.0 -2014-01-07 13:00:00,15276.0 -2014-01-07 14:00:00,15218.0 -2014-01-07 15:00:00,15180.0 -2014-01-07 16:00:00,14974.0 -2014-01-07 17:00:00,14831.0 -2014-01-07 18:00:00,15284.0 -2014-01-07 19:00:00,16099.0 -2014-01-07 20:00:00,16047.0 -2014-01-07 21:00:00,15830.0 -2014-01-07 22:00:00,15529.0 -2014-01-07 23:00:00,14826.0 -2014-01-08 00:00:00,13950.0 -2014-01-06 01:00:00,12633.0 -2014-01-06 02:00:00,12324.0 -2014-01-06 03:00:00,12127.0 -2014-01-06 04:00:00,12205.0 -2014-01-06 05:00:00,12165.0 -2014-01-06 06:00:00,12370.0 -2014-01-06 07:00:00,12893.0 -2014-01-06 08:00:00,13744.0 -2014-01-06 09:00:00,14177.0 -2014-01-06 10:00:00,14446.0 -2014-01-06 11:00:00,14598.0 -2014-01-06 12:00:00,14861.0 -2014-01-06 13:00:00,14961.0 -2014-01-06 14:00:00,14971.0 -2014-01-06 15:00:00,14956.0 -2014-01-06 16:00:00,15006.0 -2014-01-06 17:00:00,15059.0 -2014-01-06 18:00:00,15613.0 -2014-01-06 19:00:00,16514.0 -2014-01-06 20:00:00,16427.0 -2014-01-06 21:00:00,16143.0 -2014-01-06 22:00:00,15839.0 -2014-01-06 23:00:00,15335.0 -2014-01-07 00:00:00,14589.0 -2014-01-05 01:00:00,11272.0 -2014-01-05 02:00:00,10835.0 -2014-01-05 03:00:00,10509.0 -2014-01-05 04:00:00,10379.0 -2014-01-05 05:00:00,10308.0 -2014-01-05 06:00:00,10337.0 -2014-01-05 07:00:00,10496.0 -2014-01-05 08:00:00,10770.0 -2014-01-05 09:00:00,10930.0 -2014-01-05 10:00:00,11213.0 -2014-01-05 11:00:00,11559.0 -2014-01-05 12:00:00,11822.0 -2014-01-05 13:00:00,12053.0 -2014-01-05 14:00:00,12191.0 -2014-01-05 15:00:00,12303.0 -2014-01-05 16:00:00,12403.0 -2014-01-05 17:00:00,12592.0 -2014-01-05 18:00:00,13366.0 -2014-01-05 19:00:00,14250.0 -2014-01-05 20:00:00,14253.0 -2014-01-05 21:00:00,14200.0 -2014-01-05 22:00:00,13959.0 -2014-01-05 23:00:00,13678.0 -2014-01-06 00:00:00,13174.0 -2014-01-04 01:00:00,12566.0 -2014-01-04 02:00:00,11995.0 -2014-01-04 03:00:00,11593.0 -2014-01-04 04:00:00,11340.0 -2014-01-04 05:00:00,11163.0 -2014-01-04 06:00:00,11209.0 -2014-01-04 07:00:00,11339.0 -2014-01-04 08:00:00,11701.0 -2014-01-04 09:00:00,11826.0 -2014-01-04 10:00:00,12025.0 -2014-01-04 11:00:00,12197.0 -2014-01-04 12:00:00,12430.0 -2014-01-04 13:00:00,12444.0 -2014-01-04 14:00:00,12365.0 -2014-01-04 15:00:00,12157.0 -2014-01-04 16:00:00,12084.0 -2014-01-04 17:00:00,12092.0 -2014-01-04 18:00:00,12759.0 -2014-01-04 19:00:00,13288.0 -2014-01-04 20:00:00,13245.0 -2014-01-04 21:00:00,13097.0 -2014-01-04 22:00:00,12890.0 -2014-01-04 23:00:00,12493.0 -2014-01-05 00:00:00,11940.0 -2014-01-03 01:00:00,12353.0 -2014-01-03 02:00:00,11868.0 -2014-01-03 03:00:00,11606.0 -2014-01-03 04:00:00,11458.0 -2014-01-03 05:00:00,11468.0 -2014-01-03 06:00:00,11694.0 -2014-01-03 07:00:00,12267.0 -2014-01-03 08:00:00,13126.0 -2014-01-03 09:00:00,13567.0 -2014-01-03 10:00:00,13710.0 -2014-01-03 11:00:00,13775.0 -2014-01-03 12:00:00,13897.0 -2014-01-03 13:00:00,13946.0 -2014-01-03 14:00:00,13979.0 -2014-01-03 15:00:00,13971.0 -2014-01-03 16:00:00,13893.0 -2014-01-03 17:00:00,13940.0 -2014-01-03 18:00:00,14385.0 -2014-01-03 19:00:00,15100.0 -2014-01-03 20:00:00,14998.0 -2014-01-03 21:00:00,14784.0 -2014-01-03 22:00:00,14456.0 -2014-01-03 23:00:00,14023.0 -2014-01-04 00:00:00,13297.0 -2014-01-02 01:00:00,11004.0 -2014-01-02 02:00:00,10568.0 -2014-01-02 03:00:00,10345.0 -2014-01-02 04:00:00,10215.0 -2014-01-02 05:00:00,10255.0 -2014-01-02 06:00:00,10509.0 -2014-01-02 07:00:00,11167.0 -2014-01-02 08:00:00,12153.0 -2014-01-02 09:00:00,12777.0 -2014-01-02 10:00:00,13008.0 -2014-01-02 11:00:00,13292.0 -2014-01-02 12:00:00,13462.0 -2014-01-02 13:00:00,13492.0 -2014-01-02 14:00:00,13473.0 -2014-01-02 15:00:00,13474.0 -2014-01-02 16:00:00,13365.0 -2014-01-02 17:00:00,13276.0 -2014-01-02 18:00:00,13856.0 -2014-01-02 19:00:00,14744.0 -2014-01-02 20:00:00,14715.0 -2014-01-02 21:00:00,14567.0 -2014-01-02 22:00:00,14306.0 -2014-01-02 23:00:00,13830.0 -2014-01-03 00:00:00,13079.0 -2014-01-01 01:00:00,11562.0 -2014-01-01 02:00:00,11135.0 -2014-01-01 03:00:00,10766.0 -2014-01-01 04:00:00,10489.0 -2014-01-01 05:00:00,10326.0 -2014-01-01 06:00:00,10296.0 -2014-01-01 07:00:00,10433.0 -2014-01-01 08:00:00,10626.0 -2014-01-01 09:00:00,10640.0 -2014-01-01 10:00:00,10657.0 -2014-01-01 11:00:00,10927.0 -2014-01-01 12:00:00,11137.0 -2014-01-01 13:00:00,11292.0 -2014-01-01 14:00:00,11344.0 -2014-01-01 15:00:00,11371.0 -2014-01-01 16:00:00,11389.0 -2014-01-01 17:00:00,11501.0 -2014-01-01 18:00:00,12263.0 -2014-01-01 19:00:00,13062.0 -2014-01-01 20:00:00,13015.0 -2014-01-01 21:00:00,12831.0 -2014-01-01 22:00:00,12598.0 -2014-01-01 23:00:00,12231.0 -2014-01-02 00:00:00,11605.0 -2015-12-31 01:00:00,10419.0 -2015-12-31 02:00:00,9893.0 -2015-12-31 03:00:00,9544.0 -2015-12-31 04:00:00,9341.0 -2015-12-31 05:00:00,9318.0 -2015-12-31 06:00:00,9507.0 -2015-12-31 07:00:00,9992.0 -2015-12-31 08:00:00,10692.0 -2015-12-31 09:00:00,10984.0 -2015-12-31 10:00:00,11162.0 -2015-12-31 11:00:00,11289.0 -2015-12-31 12:00:00,11306.0 -2015-12-31 13:00:00,11273.0 -2015-12-31 14:00:00,11196.0 -2015-12-31 15:00:00,11229.0 -2015-12-31 16:00:00,11226.0 -2015-12-31 17:00:00,11286.0 -2015-12-31 18:00:00,11937.0 -2015-12-31 19:00:00,12564.0 -2015-12-31 20:00:00,12297.0 -2015-12-31 21:00:00,11944.0 -2015-12-31 22:00:00,11607.0 -2015-12-31 23:00:00,11277.0 -2016-01-01 00:00:00,10802.0 -2015-12-30 01:00:00,10309.0 -2015-12-30 02:00:00,9794.0 -2015-12-30 03:00:00,9477.0 -2015-12-30 04:00:00,9300.0 -2015-12-30 05:00:00,9260.0 -2015-12-30 06:00:00,9488.0 -2015-12-30 07:00:00,10061.0 -2015-12-30 08:00:00,10921.0 -2015-12-30 09:00:00,11486.0 -2015-12-30 10:00:00,11670.0 -2015-12-30 11:00:00,11771.0 -2015-12-30 12:00:00,11854.0 -2015-12-30 13:00:00,11865.0 -2015-12-30 14:00:00,11837.0 -2015-12-30 15:00:00,11897.0 -2015-12-30 16:00:00,11851.0 -2015-12-30 17:00:00,11870.0 -2015-12-30 18:00:00,12459.0 -2015-12-30 19:00:00,13039.0 -2015-12-30 20:00:00,12781.0 -2015-12-30 21:00:00,12562.0 -2015-12-30 22:00:00,12281.0 -2015-12-30 23:00:00,11843.0 -2015-12-31 00:00:00,11168.0 -2015-12-29 01:00:00,10199.0 -2015-12-29 02:00:00,9664.0 -2015-12-29 03:00:00,9309.0 -2015-12-29 04:00:00,9168.0 -2015-12-29 05:00:00,9174.0 -2015-12-29 06:00:00,9393.0 -2015-12-29 07:00:00,10002.0 -2015-12-29 08:00:00,10851.0 -2015-12-29 09:00:00,11338.0 -2015-12-29 10:00:00,11519.0 -2015-12-29 11:00:00,11691.0 -2015-12-29 12:00:00,11834.0 -2015-12-29 13:00:00,11843.0 -2015-12-29 14:00:00,11851.0 -2015-12-29 15:00:00,11873.0 -2015-12-29 16:00:00,11847.0 -2015-12-29 17:00:00,11902.0 -2015-12-29 18:00:00,12520.0 -2015-12-29 19:00:00,12973.0 -2015-12-29 20:00:00,12707.0 -2015-12-29 21:00:00,12464.0 -2015-12-29 22:00:00,12204.0 -2015-12-29 23:00:00,11773.0 -2015-12-30 00:00:00,11045.0 -2015-12-28 01:00:00,9714.0 -2015-12-28 02:00:00,9346.0 -2015-12-28 03:00:00,9077.0 -2015-12-28 04:00:00,9004.0 -2015-12-28 05:00:00,9041.0 -2015-12-28 06:00:00,9341.0 -2015-12-28 07:00:00,10054.0 -2015-12-28 08:00:00,11018.0 -2015-12-28 09:00:00,11720.0 -2015-12-28 10:00:00,12063.0 -2015-12-28 11:00:00,12280.0 -2015-12-28 12:00:00,12568.0 -2015-12-28 13:00:00,12678.0 -2015-12-28 14:00:00,12724.0 -2015-12-28 15:00:00,12727.0 -2015-12-28 16:00:00,12651.0 -2015-12-28 17:00:00,12720.0 -2015-12-28 18:00:00,13195.0 -2015-12-28 19:00:00,13398.0 -2015-12-28 20:00:00,13068.0 -2015-12-28 21:00:00,12738.0 -2015-12-28 22:00:00,12361.0 -2015-12-28 23:00:00,11797.0 -2015-12-29 00:00:00,10920.0 -2015-12-27 01:00:00,9371.0 -2015-12-27 02:00:00,8914.0 -2015-12-27 03:00:00,8674.0 -2015-12-27 04:00:00,8447.0 -2015-12-27 05:00:00,8403.0 -2015-12-27 06:00:00,8399.0 -2015-12-27 07:00:00,8542.0 -2015-12-27 08:00:00,8810.0 -2015-12-27 09:00:00,9017.0 -2015-12-27 10:00:00,9219.0 -2015-12-27 11:00:00,9471.0 -2015-12-27 12:00:00,9632.0 -2015-12-27 13:00:00,9710.0 -2015-12-27 14:00:00,9741.0 -2015-12-27 15:00:00,9786.0 -2015-12-27 16:00:00,9848.0 -2015-12-27 17:00:00,10035.0 -2015-12-27 18:00:00,10824.0 -2015-12-27 19:00:00,11169.0 -2015-12-27 20:00:00,11384.0 -2015-12-27 21:00:00,11271.0 -2015-12-27 22:00:00,11111.0 -2015-12-27 23:00:00,10783.0 -2015-12-28 00:00:00,10315.0 -2015-12-26 01:00:00,9017.0 -2015-12-26 02:00:00,8610.0 -2015-12-26 03:00:00,8344.0 -2015-12-26 04:00:00,8170.0 -2015-12-26 05:00:00,8105.0 -2015-12-26 06:00:00,8229.0 -2015-12-26 07:00:00,8474.0 -2015-12-26 08:00:00,8871.0 -2015-12-26 09:00:00,9195.0 -2015-12-26 10:00:00,9454.0 -2015-12-26 11:00:00,9785.0 -2015-12-26 12:00:00,10112.0 -2015-12-26 13:00:00,10214.0 -2015-12-26 14:00:00,10099.0 -2015-12-26 15:00:00,9943.0 -2015-12-26 16:00:00,9923.0 -2015-12-26 17:00:00,10039.0 -2015-12-26 18:00:00,10593.0 -2015-12-26 19:00:00,10879.0 -2015-12-26 20:00:00,10876.0 -2015-12-26 21:00:00,10770.0 -2015-12-26 22:00:00,10602.0 -2015-12-26 23:00:00,10372.0 -2015-12-27 00:00:00,9902.0 -2015-12-25 01:00:00,9099.0 -2015-12-25 02:00:00,8618.0 -2015-12-25 03:00:00,8322.0 -2015-12-25 04:00:00,8086.0 -2015-12-25 05:00:00,8036.0 -2015-12-25 06:00:00,8106.0 -2015-12-25 07:00:00,8323.0 -2015-12-25 08:00:00,8622.0 -2015-12-25 09:00:00,8685.0 -2015-12-25 10:00:00,8698.0 -2015-12-25 11:00:00,8770.0 -2015-12-25 12:00:00,8818.0 -2015-12-25 13:00:00,8821.0 -2015-12-25 14:00:00,8776.0 -2015-12-25 15:00:00,8716.0 -2015-12-25 16:00:00,8689.0 -2015-12-25 17:00:00,8824.0 -2015-12-25 18:00:00,9554.0 -2015-12-25 19:00:00,10062.0 -2015-12-25 20:00:00,10054.0 -2015-12-25 21:00:00,10017.0 -2015-12-25 22:00:00,9980.0 -2015-12-25 23:00:00,9858.0 -2015-12-26 00:00:00,9521.0 -2015-12-24 01:00:00,9620.0 -2015-12-24 02:00:00,9032.0 -2015-12-24 03:00:00,8687.0 -2015-12-24 04:00:00,8510.0 -2015-12-24 05:00:00,8459.0 -2015-12-24 06:00:00,8548.0 -2015-12-24 07:00:00,8936.0 -2015-12-24 08:00:00,9491.0 -2015-12-24 09:00:00,9721.0 -2015-12-24 10:00:00,9859.0 -2015-12-24 11:00:00,9976.0 -2015-12-24 12:00:00,10025.0 -2015-12-24 13:00:00,10039.0 -2015-12-24 14:00:00,9956.0 -2015-12-24 15:00:00,9987.0 -2015-12-24 16:00:00,9961.0 -2015-12-24 17:00:00,10111.0 -2015-12-24 18:00:00,10718.0 -2015-12-24 19:00:00,10992.0 -2015-12-24 20:00:00,10608.0 -2015-12-24 21:00:00,10325.0 -2015-12-24 22:00:00,10133.0 -2015-12-24 23:00:00,9907.0 -2015-12-25 00:00:00,9539.0 -2015-12-23 01:00:00,10200.0 -2015-12-23 02:00:00,9550.0 -2015-12-23 03:00:00,9141.0 -2015-12-23 04:00:00,8853.0 -2015-12-23 05:00:00,8758.0 -2015-12-23 06:00:00,8943.0 -2015-12-23 07:00:00,9493.0 -2015-12-23 08:00:00,10320.0 -2015-12-23 09:00:00,10886.0 -2015-12-23 10:00:00,11180.0 -2015-12-23 11:00:00,11491.0 -2015-12-23 12:00:00,11726.0 -2015-12-23 13:00:00,11733.0 -2015-12-23 14:00:00,11537.0 -2015-12-23 15:00:00,11325.0 -2015-12-23 16:00:00,11172.0 -2015-12-23 17:00:00,11200.0 -2015-12-23 18:00:00,11621.0 -2015-12-23 19:00:00,12146.0 -2015-12-23 20:00:00,11902.0 -2015-12-23 21:00:00,11704.0 -2015-12-23 22:00:00,11426.0 -2015-12-23 23:00:00,10982.0 -2015-12-24 00:00:00,10344.0 -2015-12-22 01:00:00,9863.0 -2015-12-22 02:00:00,9275.0 -2015-12-22 03:00:00,8967.0 -2015-12-22 04:00:00,8762.0 -2015-12-22 05:00:00,8734.0 -2015-12-22 06:00:00,8977.0 -2015-12-22 07:00:00,9658.0 -2015-12-22 08:00:00,10604.0 -2015-12-22 09:00:00,11264.0 -2015-12-22 10:00:00,11514.0 -2015-12-22 11:00:00,11684.0 -2015-12-22 12:00:00,11789.0 -2015-12-22 13:00:00,11836.0 -2015-12-22 14:00:00,11845.0 -2015-12-22 15:00:00,11894.0 -2015-12-22 16:00:00,11831.0 -2015-12-22 17:00:00,11858.0 -2015-12-22 18:00:00,12533.0 -2015-12-22 19:00:00,12969.0 -2015-12-22 20:00:00,12732.0 -2015-12-22 21:00:00,12541.0 -2015-12-22 22:00:00,12273.0 -2015-12-22 23:00:00,11809.0 -2015-12-23 00:00:00,11043.0 -2015-12-21 01:00:00,9670.0 -2015-12-21 02:00:00,9285.0 -2015-12-21 03:00:00,9084.0 -2015-12-21 04:00:00,8979.0 -2015-12-21 05:00:00,8984.0 -2015-12-21 06:00:00,9246.0 -2015-12-21 07:00:00,9891.0 -2015-12-21 08:00:00,10866.0 -2015-12-21 09:00:00,11580.0 -2015-12-21 10:00:00,11732.0 -2015-12-21 11:00:00,11908.0 -2015-12-21 12:00:00,11919.0 -2015-12-21 13:00:00,11939.0 -2015-12-21 14:00:00,11842.0 -2015-12-21 15:00:00,11709.0 -2015-12-21 16:00:00,11640.0 -2015-12-21 17:00:00,11690.0 -2015-12-21 18:00:00,12227.0 -2015-12-21 19:00:00,12574.0 -2015-12-21 20:00:00,12340.0 -2015-12-21 21:00:00,12137.0 -2015-12-21 22:00:00,11878.0 -2015-12-21 23:00:00,11428.0 -2015-12-22 00:00:00,10651.0 -2015-12-20 01:00:00,10631.0 -2015-12-20 02:00:00,10106.0 -2015-12-20 03:00:00,9748.0 -2015-12-20 04:00:00,9569.0 -2015-12-20 05:00:00,9426.0 -2015-12-20 06:00:00,9459.0 -2015-12-20 07:00:00,9523.0 -2015-12-20 08:00:00,9814.0 -2015-12-20 09:00:00,9915.0 -2015-12-20 10:00:00,9995.0 -2015-12-20 11:00:00,10094.0 -2015-12-20 12:00:00,10017.0 -2015-12-20 13:00:00,9951.0 -2015-12-20 14:00:00,9851.0 -2015-12-20 15:00:00,9779.0 -2015-12-20 16:00:00,9779.0 -2015-12-20 17:00:00,9986.0 -2015-12-20 18:00:00,10911.0 -2015-12-20 19:00:00,11437.0 -2015-12-20 20:00:00,11463.0 -2015-12-20 21:00:00,11379.0 -2015-12-20 22:00:00,11236.0 -2015-12-20 23:00:00,10871.0 -2015-12-21 00:00:00,10300.0 -2015-12-19 01:00:00,11050.0 -2015-12-19 02:00:00,10488.0 -2015-12-19 03:00:00,10244.0 -2015-12-19 04:00:00,10065.0 -2015-12-19 05:00:00,9998.0 -2015-12-19 06:00:00,10091.0 -2015-12-19 07:00:00,10457.0 -2015-12-19 08:00:00,10964.0 -2015-12-19 09:00:00,11206.0 -2015-12-19 10:00:00,11392.0 -2015-12-19 11:00:00,11476.0 -2015-12-19 12:00:00,11477.0 -2015-12-19 13:00:00,11333.0 -2015-12-19 14:00:00,11127.0 -2015-12-19 15:00:00,10868.0 -2015-12-19 16:00:00,10720.0 -2015-12-19 17:00:00,10775.0 -2015-12-19 18:00:00,11612.0 -2015-12-19 19:00:00,12319.0 -2015-12-19 20:00:00,12330.0 -2015-12-19 21:00:00,12191.0 -2015-12-19 22:00:00,12037.0 -2015-12-19 23:00:00,11780.0 -2015-12-20 00:00:00,11252.0 -2015-12-18 01:00:00,10770.0 -2015-12-18 02:00:00,10207.0 -2015-12-18 03:00:00,9893.0 -2015-12-18 04:00:00,9741.0 -2015-12-18 05:00:00,9733.0 -2015-12-18 06:00:00,10007.0 -2015-12-18 07:00:00,10725.0 -2015-12-18 08:00:00,11854.0 -2015-12-18 09:00:00,12452.0 -2015-12-18 10:00:00,12558.0 -2015-12-18 11:00:00,12535.0 -2015-12-18 12:00:00,12479.0 -2015-12-18 13:00:00,12395.0 -2015-12-18 14:00:00,12301.0 -2015-12-18 15:00:00,12255.0 -2015-12-18 16:00:00,12087.0 -2015-12-18 17:00:00,12210.0 -2015-12-18 18:00:00,13041.0 -2015-12-18 19:00:00,13553.0 -2015-12-18 20:00:00,13272.0 -2015-12-18 21:00:00,13070.0 -2015-12-18 22:00:00,12799.0 -2015-12-18 23:00:00,12421.0 -2015-12-19 00:00:00,11751.0 -2015-12-17 01:00:00,10421.0 -2015-12-17 02:00:00,9817.0 -2015-12-17 03:00:00,9492.0 -2015-12-17 04:00:00,9342.0 -2015-12-17 05:00:00,9349.0 -2015-12-17 06:00:00,9620.0 -2015-12-17 07:00:00,10385.0 -2015-12-17 08:00:00,11563.0 -2015-12-17 09:00:00,12202.0 -2015-12-17 10:00:00,12345.0 -2015-12-17 11:00:00,12389.0 -2015-12-17 12:00:00,12456.0 -2015-12-17 13:00:00,12360.0 -2015-12-17 14:00:00,12186.0 -2015-12-17 15:00:00,12122.0 -2015-12-17 16:00:00,12047.0 -2015-12-17 17:00:00,12134.0 -2015-12-17 18:00:00,12893.0 -2015-12-17 19:00:00,13458.0 -2015-12-17 20:00:00,13254.0 -2015-12-17 21:00:00,13117.0 -2015-12-17 22:00:00,12881.0 -2015-12-17 23:00:00,12399.0 -2015-12-18 00:00:00,11565.0 -2015-12-16 01:00:00,10182.0 -2015-12-16 02:00:00,9598.0 -2015-12-16 03:00:00,9272.0 -2015-12-16 04:00:00,9074.0 -2015-12-16 05:00:00,9045.0 -2015-12-16 06:00:00,9304.0 -2015-12-16 07:00:00,10014.0 -2015-12-16 08:00:00,11149.0 -2015-12-16 09:00:00,11755.0 -2015-12-16 10:00:00,11891.0 -2015-12-16 11:00:00,11980.0 -2015-12-16 12:00:00,12000.0 -2015-12-16 13:00:00,11936.0 -2015-12-16 14:00:00,11692.0 -2015-12-16 15:00:00,11571.0 -2015-12-16 16:00:00,11498.0 -2015-12-16 17:00:00,11480.0 -2015-12-16 18:00:00,12234.0 -2015-12-16 19:00:00,12960.0 -2015-12-16 20:00:00,12892.0 -2015-12-16 21:00:00,12735.0 -2015-12-16 22:00:00,12503.0 -2015-12-16 23:00:00,12023.0 -2015-12-17 00:00:00,11183.0 -2015-12-15 01:00:00,9860.0 -2015-12-15 02:00:00,9270.0 -2015-12-15 03:00:00,8966.0 -2015-12-15 04:00:00,8800.0 -2015-12-15 05:00:00,8765.0 -2015-12-15 06:00:00,9032.0 -2015-12-15 07:00:00,9753.0 -2015-12-15 08:00:00,10913.0 -2015-12-15 09:00:00,11575.0 -2015-12-15 10:00:00,11678.0 -2015-12-15 11:00:00,11746.0 -2015-12-15 12:00:00,11836.0 -2015-12-15 13:00:00,11797.0 -2015-12-15 14:00:00,11752.0 -2015-12-15 15:00:00,11792.0 -2015-12-15 16:00:00,11771.0 -2015-12-15 17:00:00,11849.0 -2015-12-15 18:00:00,12607.0 -2015-12-15 19:00:00,12986.0 -2015-12-15 20:00:00,12815.0 -2015-12-15 21:00:00,12675.0 -2015-12-15 22:00:00,12386.0 -2015-12-15 23:00:00,11879.0 -2015-12-16 00:00:00,11028.0 -2015-12-14 01:00:00,9083.0 -2015-12-14 02:00:00,8666.0 -2015-12-14 03:00:00,8443.0 -2015-12-14 04:00:00,8261.0 -2015-12-14 05:00:00,8282.0 -2015-12-14 06:00:00,8489.0 -2015-12-14 07:00:00,9187.0 -2015-12-14 08:00:00,10417.0 -2015-12-14 09:00:00,11083.0 -2015-12-14 10:00:00,11243.0 -2015-12-14 11:00:00,11342.0 -2015-12-14 12:00:00,11488.0 -2015-12-14 13:00:00,11523.0 -2015-12-14 14:00:00,11544.0 -2015-12-14 15:00:00,11527.0 -2015-12-14 16:00:00,11457.0 -2015-12-14 17:00:00,11599.0 -2015-12-14 18:00:00,12314.0 -2015-12-14 19:00:00,12725.0 -2015-12-14 20:00:00,12570.0 -2015-12-14 21:00:00,12378.0 -2015-12-14 22:00:00,12052.0 -2015-12-14 23:00:00,11534.0 -2015-12-15 00:00:00,10692.0 -2015-12-13 01:00:00,9109.0 -2015-12-13 02:00:00,8597.0 -2015-12-13 03:00:00,8252.0 -2015-12-13 04:00:00,8044.0 -2015-12-13 05:00:00,7927.0 -2015-12-13 06:00:00,7957.0 -2015-12-13 07:00:00,8067.0 -2015-12-13 08:00:00,8361.0 -2015-12-13 09:00:00,8601.0 -2015-12-13 10:00:00,8873.0 -2015-12-13 11:00:00,9134.0 -2015-12-13 12:00:00,9354.0 -2015-12-13 13:00:00,9461.0 -2015-12-13 14:00:00,9564.0 -2015-12-13 15:00:00,9555.0 -2015-12-13 16:00:00,9680.0 -2015-12-13 17:00:00,10053.0 -2015-12-13 18:00:00,10805.0 -2015-12-13 19:00:00,11124.0 -2015-12-13 20:00:00,11093.0 -2015-12-13 21:00:00,11015.0 -2015-12-13 22:00:00,10743.0 -2015-12-13 23:00:00,10342.0 -2015-12-14 00:00:00,9694.0 -2015-12-12 01:00:00,9545.0 -2015-12-12 02:00:00,8926.0 -2015-12-12 03:00:00,8606.0 -2015-12-12 04:00:00,8417.0 -2015-12-12 05:00:00,8305.0 -2015-12-12 06:00:00,8356.0 -2015-12-12 07:00:00,8657.0 -2015-12-12 08:00:00,9114.0 -2015-12-12 09:00:00,9445.0 -2015-12-12 10:00:00,9785.0 -2015-12-12 11:00:00,9992.0 -2015-12-12 12:00:00,10120.0 -2015-12-12 13:00:00,10087.0 -2015-12-12 14:00:00,9952.0 -2015-12-12 15:00:00,9850.0 -2015-12-12 16:00:00,9784.0 -2015-12-12 17:00:00,9939.0 -2015-12-12 18:00:00,10681.0 -2015-12-12 19:00:00,11023.0 -2015-12-12 20:00:00,10924.0 -2015-12-12 21:00:00,10785.0 -2015-12-12 22:00:00,10572.0 -2015-12-12 23:00:00,10294.0 -2015-12-13 00:00:00,9750.0 -2015-12-11 01:00:00,9809.0 -2015-12-11 02:00:00,9258.0 -2015-12-11 03:00:00,8932.0 -2015-12-11 04:00:00,8740.0 -2015-12-11 05:00:00,8723.0 -2015-12-11 06:00:00,8955.0 -2015-12-11 07:00:00,9682.0 -2015-12-11 08:00:00,10819.0 -2015-12-11 09:00:00,11274.0 -2015-12-11 10:00:00,11283.0 -2015-12-11 11:00:00,11356.0 -2015-12-11 12:00:00,11370.0 -2015-12-11 13:00:00,11264.0 -2015-12-11 14:00:00,11142.0 -2015-12-11 15:00:00,11088.0 -2015-12-11 16:00:00,10991.0 -2015-12-11 17:00:00,11044.0 -2015-12-11 18:00:00,11877.0 -2015-12-11 19:00:00,12308.0 -2015-12-11 20:00:00,12095.0 -2015-12-11 21:00:00,11841.0 -2015-12-11 22:00:00,11560.0 -2015-12-11 23:00:00,11113.0 -2015-12-12 00:00:00,10429.0 -2015-12-10 01:00:00,9854.0 -2015-12-10 02:00:00,9317.0 -2015-12-10 03:00:00,9006.0 -2015-12-10 04:00:00,8864.0 -2015-12-10 05:00:00,8828.0 -2015-12-10 06:00:00,9052.0 -2015-12-10 07:00:00,9761.0 -2015-12-10 08:00:00,10904.0 -2015-12-10 09:00:00,11470.0 -2015-12-10 10:00:00,11562.0 -2015-12-10 11:00:00,11611.0 -2015-12-10 12:00:00,11605.0 -2015-12-10 13:00:00,11521.0 -2015-12-10 14:00:00,11470.0 -2015-12-10 15:00:00,11535.0 -2015-12-10 16:00:00,11410.0 -2015-12-10 17:00:00,11441.0 -2015-12-10 18:00:00,12182.0 -2015-12-10 19:00:00,12616.0 -2015-12-10 20:00:00,12458.0 -2015-12-10 21:00:00,12282.0 -2015-12-10 22:00:00,12015.0 -2015-12-10 23:00:00,11520.0 -2015-12-11 00:00:00,10681.0 -2015-12-09 01:00:00,9990.0 -2015-12-09 02:00:00,9415.0 -2015-12-09 03:00:00,9087.0 -2015-12-09 04:00:00,8865.0 -2015-12-09 05:00:00,8828.0 -2015-12-09 06:00:00,9098.0 -2015-12-09 07:00:00,9820.0 -2015-12-09 08:00:00,10965.0 -2015-12-09 09:00:00,11442.0 -2015-12-09 10:00:00,11461.0 -2015-12-09 11:00:00,11443.0 -2015-12-09 12:00:00,11462.0 -2015-12-09 13:00:00,11370.0 -2015-12-09 14:00:00,11256.0 -2015-12-09 15:00:00,11233.0 -2015-12-09 16:00:00,11166.0 -2015-12-09 17:00:00,11230.0 -2015-12-09 18:00:00,12041.0 -2015-12-09 19:00:00,12616.0 -2015-12-09 20:00:00,12462.0 -2015-12-09 21:00:00,12266.0 -2015-12-09 22:00:00,12033.0 -2015-12-09 23:00:00,11477.0 -2015-12-10 00:00:00,10661.0 -2015-12-08 01:00:00,10282.0 -2015-12-08 02:00:00,9723.0 -2015-12-08 03:00:00,9436.0 -2015-12-08 04:00:00,9193.0 -2015-12-08 05:00:00,9198.0 -2015-12-08 06:00:00,9457.0 -2015-12-08 07:00:00,10173.0 -2015-12-08 08:00:00,11349.0 -2015-12-08 09:00:00,11838.0 -2015-12-08 10:00:00,11885.0 -2015-12-08 11:00:00,11920.0 -2015-12-08 12:00:00,11900.0 -2015-12-08 13:00:00,11830.0 -2015-12-08 14:00:00,11756.0 -2015-12-08 15:00:00,11709.0 -2015-12-08 16:00:00,11598.0 -2015-12-08 17:00:00,11690.0 -2015-12-08 18:00:00,12418.0 -2015-12-08 19:00:00,12939.0 -2015-12-08 20:00:00,12741.0 -2015-12-08 21:00:00,12522.0 -2015-12-08 22:00:00,12223.0 -2015-12-08 23:00:00,11697.0 -2015-12-09 00:00:00,10835.0 -2015-12-07 01:00:00,9548.0 -2015-12-07 02:00:00,9209.0 -2015-12-07 03:00:00,8995.0 -2015-12-07 04:00:00,8963.0 -2015-12-07 05:00:00,8976.0 -2015-12-07 06:00:00,9349.0 -2015-12-07 07:00:00,10165.0 -2015-12-07 08:00:00,11398.0 -2015-12-07 09:00:00,12030.0 -2015-12-07 10:00:00,12120.0 -2015-12-07 11:00:00,12192.0 -2015-12-07 12:00:00,12205.0 -2015-12-07 13:00:00,12170.0 -2015-12-07 14:00:00,12146.0 -2015-12-07 15:00:00,12156.0 -2015-12-07 16:00:00,12094.0 -2015-12-07 17:00:00,12176.0 -2015-12-07 18:00:00,12828.0 -2015-12-07 19:00:00,13220.0 -2015-12-07 20:00:00,13053.0 -2015-12-07 21:00:00,12839.0 -2015-12-07 22:00:00,12557.0 -2015-12-07 23:00:00,11968.0 -2015-12-08 00:00:00,11104.0 -2015-12-06 01:00:00,9851.0 -2015-12-06 02:00:00,9374.0 -2015-12-06 03:00:00,9023.0 -2015-12-06 04:00:00,8853.0 -2015-12-06 05:00:00,8772.0 -2015-12-06 06:00:00,8822.0 -2015-12-06 07:00:00,8962.0 -2015-12-06 08:00:00,9274.0 -2015-12-06 09:00:00,9322.0 -2015-12-06 10:00:00,9510.0 -2015-12-06 11:00:00,9572.0 -2015-12-06 12:00:00,9590.0 -2015-12-06 13:00:00,9505.0 -2015-12-06 14:00:00,9523.0 -2015-12-06 15:00:00,9504.0 -2015-12-06 16:00:00,9568.0 -2015-12-06 17:00:00,9725.0 -2015-12-06 18:00:00,10642.0 -2015-12-06 19:00:00,11226.0 -2015-12-06 20:00:00,11307.0 -2015-12-06 21:00:00,11250.0 -2015-12-06 22:00:00,11107.0 -2015-12-06 23:00:00,10745.0 -2015-12-07 00:00:00,10139.0 -2015-12-05 01:00:00,10180.0 -2015-12-05 02:00:00,9600.0 -2015-12-05 03:00:00,9294.0 -2015-12-05 04:00:00,9078.0 -2015-12-05 05:00:00,9076.0 -2015-12-05 06:00:00,9159.0 -2015-12-05 07:00:00,9492.0 -2015-12-05 08:00:00,10008.0 -2015-12-05 09:00:00,10298.0 -2015-12-05 10:00:00,10623.0 -2015-12-05 11:00:00,10730.0 -2015-12-05 12:00:00,10615.0 -2015-12-05 13:00:00,10469.0 -2015-12-05 14:00:00,10268.0 -2015-12-05 15:00:00,10042.0 -2015-12-05 16:00:00,9890.0 -2015-12-05 17:00:00,10009.0 -2015-12-05 18:00:00,10847.0 -2015-12-05 19:00:00,11524.0 -2015-12-05 20:00:00,11479.0 -2015-12-05 21:00:00,11395.0 -2015-12-05 22:00:00,11224.0 -2015-12-05 23:00:00,10987.0 -2015-12-06 00:00:00,10439.0 -2015-12-04 01:00:00,10413.0 -2015-12-04 02:00:00,9868.0 -2015-12-04 03:00:00,9569.0 -2015-12-04 04:00:00,9389.0 -2015-12-04 05:00:00,9393.0 -2015-12-04 06:00:00,9655.0 -2015-12-04 07:00:00,10360.0 -2015-12-04 08:00:00,11491.0 -2015-12-04 09:00:00,11959.0 -2015-12-04 10:00:00,12016.0 -2015-12-04 11:00:00,11959.0 -2015-12-04 12:00:00,11830.0 -2015-12-04 13:00:00,11668.0 -2015-12-04 14:00:00,11490.0 -2015-12-04 15:00:00,11407.0 -2015-12-04 16:00:00,11274.0 -2015-12-04 17:00:00,11249.0 -2015-12-04 18:00:00,11939.0 -2015-12-04 19:00:00,12516.0 -2015-12-04 20:00:00,12385.0 -2015-12-04 21:00:00,12184.0 -2015-12-04 22:00:00,11912.0 -2015-12-04 23:00:00,11551.0 -2015-12-05 00:00:00,10871.0 -2015-12-03 01:00:00,10426.0 -2015-12-03 02:00:00,9930.0 -2015-12-03 03:00:00,9642.0 -2015-12-03 04:00:00,9448.0 -2015-12-03 05:00:00,9402.0 -2015-12-03 06:00:00,9672.0 -2015-12-03 07:00:00,10387.0 -2015-12-03 08:00:00,11514.0 -2015-12-03 09:00:00,12036.0 -2015-12-03 10:00:00,12210.0 -2015-12-03 11:00:00,12296.0 -2015-12-03 12:00:00,12384.0 -2015-12-03 13:00:00,12355.0 -2015-12-03 14:00:00,12289.0 -2015-12-03 15:00:00,12328.0 -2015-12-03 16:00:00,12258.0 -2015-12-03 17:00:00,12346.0 -2015-12-03 18:00:00,12952.0 -2015-12-03 19:00:00,13256.0 -2015-12-03 20:00:00,13037.0 -2015-12-03 21:00:00,12813.0 -2015-12-03 22:00:00,12530.0 -2015-12-03 23:00:00,12036.0 -2015-12-04 00:00:00,11235.0 -2015-12-02 01:00:00,9989.0 -2015-12-02 02:00:00,9504.0 -2015-12-02 03:00:00,9226.0 -2015-12-02 04:00:00,9073.0 -2015-12-02 05:00:00,9067.0 -2015-12-02 06:00:00,9451.0 -2015-12-02 07:00:00,10220.0 -2015-12-02 08:00:00,11350.0 -2015-12-02 09:00:00,11941.0 -2015-12-02 10:00:00,12140.0 -2015-12-02 11:00:00,12180.0 -2015-12-02 12:00:00,12204.0 -2015-12-02 13:00:00,12137.0 -2015-12-02 14:00:00,12091.0 -2015-12-02 15:00:00,12102.0 -2015-12-02 16:00:00,12021.0 -2015-12-02 17:00:00,12140.0 -2015-12-02 18:00:00,12868.0 -2015-12-02 19:00:00,13263.0 -2015-12-02 20:00:00,13081.0 -2015-12-02 21:00:00,12894.0 -2015-12-02 22:00:00,12581.0 -2015-12-02 23:00:00,12048.0 -2015-12-03 00:00:00,11222.0 -2015-12-01 01:00:00,9843.0 -2015-12-01 02:00:00,9360.0 -2015-12-01 03:00:00,9075.0 -2015-12-01 04:00:00,8911.0 -2015-12-01 05:00:00,8840.0 -2015-12-01 06:00:00,9068.0 -2015-12-01 07:00:00,9770.0 -2015-12-01 08:00:00,10996.0 -2015-12-01 09:00:00,11570.0 -2015-12-01 10:00:00,11696.0 -2015-12-01 11:00:00,11766.0 -2015-12-01 12:00:00,11722.0 -2015-12-01 13:00:00,11573.0 -2015-12-01 14:00:00,11524.0 -2015-12-01 15:00:00,11577.0 -2015-12-01 16:00:00,11473.0 -2015-12-01 17:00:00,11501.0 -2015-12-01 18:00:00,12193.0 -2015-12-01 19:00:00,12709.0 -2015-12-01 20:00:00,12540.0 -2015-12-01 21:00:00,12367.0 -2015-12-01 22:00:00,12073.0 -2015-12-01 23:00:00,11539.0 -2015-12-02 00:00:00,10745.0 -2015-11-30 01:00:00,9332.0 -2015-11-30 02:00:00,8931.0 -2015-11-30 03:00:00,8743.0 -2015-11-30 04:00:00,8671.0 -2015-11-30 05:00:00,8694.0 -2015-11-30 06:00:00,8998.0 -2015-11-30 07:00:00,9825.0 -2015-11-30 08:00:00,11003.0 -2015-11-30 09:00:00,11599.0 -2015-11-30 10:00:00,11724.0 -2015-11-30 11:00:00,11822.0 -2015-11-30 12:00:00,11917.0 -2015-11-30 13:00:00,11947.0 -2015-11-30 14:00:00,11904.0 -2015-11-30 15:00:00,11871.0 -2015-11-30 16:00:00,11850.0 -2015-11-30 17:00:00,11908.0 -2015-11-30 18:00:00,12523.0 -2015-11-30 19:00:00,12842.0 -2015-11-30 20:00:00,12609.0 -2015-11-30 21:00:00,12408.0 -2015-11-30 22:00:00,12071.0 -2015-11-30 23:00:00,11514.0 -2015-12-01 00:00:00,10670.0 -2015-11-29 01:00:00,9528.0 -2015-11-29 02:00:00,9115.0 -2015-11-29 03:00:00,8863.0 -2015-11-29 04:00:00,8714.0 -2015-11-29 05:00:00,8692.0 -2015-11-29 06:00:00,8715.0 -2015-11-29 07:00:00,8903.0 -2015-11-29 08:00:00,9141.0 -2015-11-29 09:00:00,9226.0 -2015-11-29 10:00:00,9417.0 -2015-11-29 11:00:00,9542.0 -2015-11-29 12:00:00,9585.0 -2015-11-29 13:00:00,9614.0 -2015-11-29 14:00:00,9570.0 -2015-11-29 15:00:00,9557.0 -2015-11-29 16:00:00,9628.0 -2015-11-29 17:00:00,9852.0 -2015-11-29 18:00:00,10675.0 -2015-11-29 19:00:00,11270.0 -2015-11-29 20:00:00,11253.0 -2015-11-29 21:00:00,11180.0 -2015-11-29 22:00:00,10876.0 -2015-11-29 23:00:00,10498.0 -2015-11-30 00:00:00,9847.0 -2015-11-28 01:00:00,9302.0 -2015-11-28 02:00:00,8864.0 -2015-11-28 03:00:00,8605.0 -2015-11-28 04:00:00,8446.0 -2015-11-28 05:00:00,8382.0 -2015-11-28 06:00:00,8467.0 -2015-11-28 07:00:00,8711.0 -2015-11-28 08:00:00,9159.0 -2015-11-28 09:00:00,9381.0 -2015-11-28 10:00:00,9769.0 -2015-11-28 11:00:00,10000.0 -2015-11-28 12:00:00,10114.0 -2015-11-28 13:00:00,10063.0 -2015-11-28 14:00:00,10012.0 -2015-11-28 15:00:00,9879.0 -2015-11-28 16:00:00,9871.0 -2015-11-28 17:00:00,9965.0 -2015-11-28 18:00:00,10642.0 -2015-11-28 19:00:00,11071.0 -2015-11-28 20:00:00,10997.0 -2015-11-28 21:00:00,10862.0 -2015-11-28 22:00:00,10711.0 -2015-11-28 23:00:00,10486.0 -2015-11-29 00:00:00,9989.0 -2015-11-27 01:00:00,8284.0 -2015-11-27 02:00:00,7984.0 -2015-11-27 03:00:00,7812.0 -2015-11-27 04:00:00,7690.0 -2015-11-27 05:00:00,7691.0 -2015-11-27 06:00:00,7841.0 -2015-11-27 07:00:00,8294.0 -2015-11-27 08:00:00,8907.0 -2015-11-27 09:00:00,9213.0 -2015-11-27 10:00:00,9539.0 -2015-11-27 11:00:00,9829.0 -2015-11-27 12:00:00,10067.0 -2015-11-27 13:00:00,10164.0 -2015-11-27 14:00:00,10204.0 -2015-11-27 15:00:00,10282.0 -2015-11-27 16:00:00,10337.0 -2015-11-27 17:00:00,10526.0 -2015-11-27 18:00:00,11082.0 -2015-11-27 19:00:00,11269.0 -2015-11-27 20:00:00,11128.0 -2015-11-27 21:00:00,10917.0 -2015-11-27 22:00:00,10684.0 -2015-11-27 23:00:00,10368.0 -2015-11-28 00:00:00,9834.0 -2015-11-26 01:00:00,9230.0 -2015-11-26 02:00:00,8734.0 -2015-11-26 03:00:00,8406.0 -2015-11-26 04:00:00,8214.0 -2015-11-26 05:00:00,8069.0 -2015-11-26 06:00:00,8081.0 -2015-11-26 07:00:00,8251.0 -2015-11-26 08:00:00,8445.0 -2015-11-26 09:00:00,8442.0 -2015-11-26 10:00:00,8738.0 -2015-11-26 11:00:00,9066.0 -2015-11-26 12:00:00,9318.0 -2015-11-26 13:00:00,9448.0 -2015-11-26 14:00:00,9357.0 -2015-11-26 15:00:00,9223.0 -2015-11-26 16:00:00,9119.0 -2015-11-26 17:00:00,9021.0 -2015-11-26 18:00:00,9316.0 -2015-11-26 19:00:00,9399.0 -2015-11-26 20:00:00,9240.0 -2015-11-26 21:00:00,9149.0 -2015-11-26 22:00:00,9053.0 -2015-11-26 23:00:00,8895.0 -2015-11-27 00:00:00,8651.0 -2015-11-25 01:00:00,10075.0 -2015-11-25 02:00:00,9578.0 -2015-11-25 03:00:00,9295.0 -2015-11-25 04:00:00,9133.0 -2015-11-25 05:00:00,9126.0 -2015-11-25 06:00:00,9330.0 -2015-11-25 07:00:00,9954.0 -2015-11-25 08:00:00,10819.0 -2015-11-25 09:00:00,11155.0 -2015-11-25 10:00:00,11324.0 -2015-11-25 11:00:00,11385.0 -2015-11-25 12:00:00,11434.0 -2015-11-25 13:00:00,11422.0 -2015-11-25 14:00:00,11345.0 -2015-11-25 15:00:00,11438.0 -2015-11-25 16:00:00,11390.0 -2015-11-25 17:00:00,11395.0 -2015-11-25 18:00:00,11829.0 -2015-11-25 19:00:00,12040.0 -2015-11-25 20:00:00,11715.0 -2015-11-25 21:00:00,11440.0 -2015-11-25 22:00:00,11138.0 -2015-11-25 23:00:00,10678.0 -2015-11-26 00:00:00,9978.0 -2015-11-24 01:00:00,10219.0 -2015-11-24 02:00:00,9767.0 -2015-11-24 03:00:00,9557.0 -2015-11-24 04:00:00,9432.0 -2015-11-24 05:00:00,9442.0 -2015-11-24 06:00:00,9742.0 -2015-11-24 07:00:00,10430.0 -2015-11-24 08:00:00,11470.0 -2015-11-24 09:00:00,11837.0 -2015-11-24 10:00:00,11999.0 -2015-11-24 11:00:00,11949.0 -2015-11-24 12:00:00,11839.0 -2015-11-24 13:00:00,11861.0 -2015-11-24 14:00:00,11570.0 -2015-11-24 15:00:00,11503.0 -2015-11-24 16:00:00,11426.0 -2015-11-24 17:00:00,11452.0 -2015-11-24 18:00:00,12087.0 -2015-11-24 19:00:00,12603.0 -2015-11-24 20:00:00,12397.0 -2015-11-24 21:00:00,12222.0 -2015-11-24 22:00:00,11935.0 -2015-11-24 23:00:00,11500.0 -2015-11-25 00:00:00,10791.0 -2015-11-23 01:00:00,10111.0 -2015-11-23 02:00:00,9731.0 -2015-11-23 03:00:00,9523.0 -2015-11-23 04:00:00,9402.0 -2015-11-23 05:00:00,9454.0 -2015-11-23 06:00:00,9817.0 -2015-11-23 07:00:00,10494.0 -2015-11-23 08:00:00,11585.0 -2015-11-23 09:00:00,11916.0 -2015-11-23 10:00:00,12033.0 -2015-11-23 11:00:00,12068.0 -2015-11-23 12:00:00,12119.0 -2015-11-23 13:00:00,12111.0 -2015-11-23 14:00:00,12010.0 -2015-11-23 15:00:00,11860.0 -2015-11-23 16:00:00,11673.0 -2015-11-23 17:00:00,11623.0 -2015-11-23 18:00:00,12220.0 -2015-11-23 19:00:00,12878.0 -2015-11-23 20:00:00,12711.0 -2015-11-23 21:00:00,12499.0 -2015-11-23 22:00:00,12196.0 -2015-11-23 23:00:00,11661.0 -2015-11-24 00:00:00,10903.0 -2015-11-22 01:00:00,10216.0 -2015-11-22 02:00:00,9859.0 -2015-11-22 03:00:00,9608.0 -2015-11-22 04:00:00,9502.0 -2015-11-22 05:00:00,9462.0 -2015-11-22 06:00:00,9388.0 -2015-11-22 07:00:00,9585.0 -2015-11-22 08:00:00,9836.0 -2015-11-22 09:00:00,9929.0 -2015-11-22 10:00:00,10113.0 -2015-11-22 11:00:00,10176.0 -2015-11-22 12:00:00,10220.0 -2015-11-22 13:00:00,10212.0 -2015-11-22 14:00:00,10219.0 -2015-11-22 15:00:00,10191.0 -2015-11-22 16:00:00,10226.0 -2015-11-22 17:00:00,10479.0 -2015-11-22 18:00:00,11246.0 -2015-11-22 19:00:00,11925.0 -2015-11-22 20:00:00,11912.0 -2015-11-22 21:00:00,11830.0 -2015-11-22 22:00:00,11572.0 -2015-11-22 23:00:00,11236.0 -2015-11-23 00:00:00,10589.0 -2015-11-21 01:00:00,10087.0 -2015-11-21 02:00:00,9587.0 -2015-11-21 03:00:00,9294.0 -2015-11-21 04:00:00,9116.0 -2015-11-21 05:00:00,9079.0 -2015-11-21 06:00:00,9157.0 -2015-11-21 07:00:00,9412.0 -2015-11-21 08:00:00,9915.0 -2015-11-21 09:00:00,10159.0 -2015-11-21 10:00:00,10631.0 -2015-11-21 11:00:00,10929.0 -2015-11-21 12:00:00,11155.0 -2015-11-21 13:00:00,11179.0 -2015-11-21 14:00:00,11162.0 -2015-11-21 15:00:00,10995.0 -2015-11-21 16:00:00,10911.0 -2015-11-21 17:00:00,10916.0 -2015-11-21 18:00:00,11461.0 -2015-11-21 19:00:00,11919.0 -2015-11-21 20:00:00,11876.0 -2015-11-21 21:00:00,11698.0 -2015-11-21 22:00:00,11497.0 -2015-11-21 23:00:00,11161.0 -2015-11-22 00:00:00,10700.0 -2015-11-20 01:00:00,9890.0 -2015-11-20 02:00:00,9463.0 -2015-11-20 03:00:00,9182.0 -2015-11-20 04:00:00,9029.0 -2015-11-20 05:00:00,9020.0 -2015-11-20 06:00:00,9287.0 -2015-11-20 07:00:00,10009.0 -2015-11-20 08:00:00,11057.0 -2015-11-20 09:00:00,11399.0 -2015-11-20 10:00:00,11501.0 -2015-11-20 11:00:00,11543.0 -2015-11-20 12:00:00,11493.0 -2015-11-20 13:00:00,11468.0 -2015-11-20 14:00:00,11460.0 -2015-11-20 15:00:00,11455.0 -2015-11-20 16:00:00,11439.0 -2015-11-20 17:00:00,11474.0 -2015-11-20 18:00:00,12012.0 -2015-11-20 19:00:00,12287.0 -2015-11-20 20:00:00,12091.0 -2015-11-20 21:00:00,11917.0 -2015-11-20 22:00:00,11679.0 -2015-11-20 23:00:00,11328.0 -2015-11-21 00:00:00,10720.0 -2015-11-19 01:00:00,9234.0 -2015-11-19 02:00:00,8808.0 -2015-11-19 03:00:00,8546.0 -2015-11-19 04:00:00,8400.0 -2015-11-19 05:00:00,8388.0 -2015-11-19 06:00:00,8634.0 -2015-11-19 07:00:00,9368.0 -2015-11-19 08:00:00,10523.0 -2015-11-19 09:00:00,11024.0 -2015-11-19 10:00:00,11145.0 -2015-11-19 11:00:00,11256.0 -2015-11-19 12:00:00,11317.0 -2015-11-19 13:00:00,11319.0 -2015-11-19 14:00:00,11266.0 -2015-11-19 15:00:00,11285.0 -2015-11-19 16:00:00,11243.0 -2015-11-19 17:00:00,11243.0 -2015-11-19 18:00:00,11815.0 -2015-11-19 19:00:00,12379.0 -2015-11-19 20:00:00,12245.0 -2015-11-19 21:00:00,12009.0 -2015-11-19 22:00:00,11759.0 -2015-11-19 23:00:00,11305.0 -2015-11-20 00:00:00,10580.0 -2015-11-18 01:00:00,9216.0 -2015-11-18 02:00:00,8752.0 -2015-11-18 03:00:00,8493.0 -2015-11-18 04:00:00,8321.0 -2015-11-18 05:00:00,8310.0 -2015-11-18 06:00:00,8470.0 -2015-11-18 07:00:00,9089.0 -2015-11-18 08:00:00,10187.0 -2015-11-18 09:00:00,10793.0 -2015-11-18 10:00:00,11152.0 -2015-11-18 11:00:00,11334.0 -2015-11-18 12:00:00,11405.0 -2015-11-18 13:00:00,11349.0 -2015-11-18 14:00:00,11304.0 -2015-11-18 15:00:00,11298.0 -2015-11-18 16:00:00,11163.0 -2015-11-18 17:00:00,11050.0 -2015-11-18 18:00:00,11426.0 -2015-11-18 19:00:00,11914.0 -2015-11-18 20:00:00,11711.0 -2015-11-18 21:00:00,11497.0 -2015-11-18 22:00:00,11205.0 -2015-11-18 23:00:00,10696.0 -2015-11-19 00:00:00,9970.0 -2015-11-17 01:00:00,9234.0 -2015-11-17 02:00:00,8756.0 -2015-11-17 03:00:00,8482.0 -2015-11-17 04:00:00,8318.0 -2015-11-17 05:00:00,8293.0 -2015-11-17 06:00:00,8514.0 -2015-11-17 07:00:00,9254.0 -2015-11-17 08:00:00,10333.0 -2015-11-17 09:00:00,10887.0 -2015-11-17 10:00:00,11102.0 -2015-11-17 11:00:00,11224.0 -2015-11-17 12:00:00,11355.0 -2015-11-17 13:00:00,11414.0 -2015-11-17 14:00:00,11473.0 -2015-11-17 15:00:00,11611.0 -2015-11-17 16:00:00,11640.0 -2015-11-17 17:00:00,11604.0 -2015-11-17 18:00:00,11982.0 -2015-11-17 19:00:00,12122.0 -2015-11-17 20:00:00,11858.0 -2015-11-17 21:00:00,11548.0 -2015-11-17 22:00:00,11251.0 -2015-11-17 23:00:00,10688.0 -2015-11-18 00:00:00,9929.0 -2015-11-16 01:00:00,8404.0 -2015-11-16 02:00:00,8102.0 -2015-11-16 03:00:00,7957.0 -2015-11-16 04:00:00,7850.0 -2015-11-16 05:00:00,7907.0 -2015-11-16 06:00:00,8194.0 -2015-11-16 07:00:00,8990.0 -2015-11-16 08:00:00,10086.0 -2015-11-16 09:00:00,10608.0 -2015-11-16 10:00:00,10799.0 -2015-11-16 11:00:00,10929.0 -2015-11-16 12:00:00,11092.0 -2015-11-16 13:00:00,11118.0 -2015-11-16 14:00:00,11087.0 -2015-11-16 15:00:00,11168.0 -2015-11-16 16:00:00,11109.0 -2015-11-16 17:00:00,11131.0 -2015-11-16 18:00:00,11644.0 -2015-11-16 19:00:00,12007.0 -2015-11-16 20:00:00,11787.0 -2015-11-16 21:00:00,11538.0 -2015-11-16 22:00:00,11252.0 -2015-11-16 23:00:00,10695.0 -2015-11-17 00:00:00,9924.0 -2015-11-15 01:00:00,8834.0 -2015-11-15 02:00:00,8424.0 -2015-11-15 03:00:00,8163.0 -2015-11-15 04:00:00,8008.0 -2015-11-15 05:00:00,7968.0 -2015-11-15 06:00:00,8008.0 -2015-11-15 07:00:00,8118.0 -2015-11-15 08:00:00,8343.0 -2015-11-15 09:00:00,8310.0 -2015-11-15 10:00:00,8541.0 -2015-11-15 11:00:00,8659.0 -2015-11-15 12:00:00,8797.0 -2015-11-15 13:00:00,8875.0 -2015-11-15 14:00:00,8893.0 -2015-11-15 15:00:00,8856.0 -2015-11-15 16:00:00,8792.0 -2015-11-15 17:00:00,8827.0 -2015-11-15 18:00:00,9352.0 -2015-11-15 19:00:00,10120.0 -2015-11-15 20:00:00,10100.0 -2015-11-15 21:00:00,10014.0 -2015-11-15 22:00:00,9789.0 -2015-11-15 23:00:00,9435.0 -2015-11-16 00:00:00,8924.0 -2015-11-14 01:00:00,9683.0 -2015-11-14 02:00:00,9232.0 -2015-11-14 03:00:00,8960.0 -2015-11-14 04:00:00,8808.0 -2015-11-14 05:00:00,8799.0 -2015-11-14 06:00:00,8875.0 -2015-11-14 07:00:00,9215.0 -2015-11-14 08:00:00,9610.0 -2015-11-14 09:00:00,9739.0 -2015-11-14 10:00:00,9981.0 -2015-11-14 11:00:00,10059.0 -2015-11-14 12:00:00,10040.0 -2015-11-14 13:00:00,9921.0 -2015-11-14 14:00:00,9766.0 -2015-11-14 15:00:00,9524.0 -2015-11-14 16:00:00,9397.0 -2015-11-14 17:00:00,9370.0 -2015-11-14 18:00:00,9874.0 -2015-11-14 19:00:00,10492.0 -2015-11-14 20:00:00,10410.0 -2015-11-14 21:00:00,10242.0 -2015-11-14 22:00:00,10062.0 -2015-11-14 23:00:00,9782.0 -2015-11-15 00:00:00,9289.0 -2015-11-13 01:00:00,9595.0 -2015-11-13 02:00:00,9173.0 -2015-11-13 03:00:00,8912.0 -2015-11-13 04:00:00,8784.0 -2015-11-13 05:00:00,8745.0 -2015-11-13 06:00:00,9020.0 -2015-11-13 07:00:00,9760.0 -2015-11-13 08:00:00,10751.0 -2015-11-13 09:00:00,11164.0 -2015-11-13 10:00:00,11342.0 -2015-11-13 11:00:00,11404.0 -2015-11-13 12:00:00,11419.0 -2015-11-13 13:00:00,11383.0 -2015-11-13 14:00:00,11282.0 -2015-11-13 15:00:00,11237.0 -2015-11-13 16:00:00,11062.0 -2015-11-13 17:00:00,10999.0 -2015-11-13 18:00:00,11384.0 -2015-11-13 19:00:00,11954.0 -2015-11-13 20:00:00,11820.0 -2015-11-13 21:00:00,11597.0 -2015-11-13 22:00:00,11288.0 -2015-11-13 23:00:00,10908.0 -2015-11-14 00:00:00,10255.0 -2015-11-12 01:00:00,9123.0 -2015-11-12 02:00:00,8681.0 -2015-11-12 03:00:00,8451.0 -2015-11-12 04:00:00,8291.0 -2015-11-12 05:00:00,8343.0 -2015-11-12 06:00:00,8623.0 -2015-11-12 07:00:00,9352.0 -2015-11-12 08:00:00,10523.0 -2015-11-12 09:00:00,11175.0 -2015-11-12 10:00:00,11451.0 -2015-11-12 11:00:00,11576.0 -2015-11-12 12:00:00,11643.0 -2015-11-12 13:00:00,11679.0 -2015-11-12 14:00:00,11657.0 -2015-11-12 15:00:00,11673.0 -2015-11-12 16:00:00,11611.0 -2015-11-12 17:00:00,11517.0 -2015-11-12 18:00:00,11754.0 -2015-11-12 19:00:00,12254.0 -2015-11-12 20:00:00,12057.0 -2015-11-12 21:00:00,11855.0 -2015-11-12 22:00:00,11550.0 -2015-11-12 23:00:00,11014.0 -2015-11-13 00:00:00,10273.0 -2015-11-11 01:00:00,9283.0 -2015-11-11 02:00:00,8837.0 -2015-11-11 03:00:00,8587.0 -2015-11-11 04:00:00,8450.0 -2015-11-11 05:00:00,8444.0 -2015-11-11 06:00:00,8677.0 -2015-11-11 07:00:00,9344.0 -2015-11-11 08:00:00,10250.0 -2015-11-11 09:00:00,10611.0 -2015-11-11 10:00:00,10766.0 -2015-11-11 11:00:00,10814.0 -2015-11-11 12:00:00,10921.0 -2015-11-11 13:00:00,11006.0 -2015-11-11 14:00:00,11012.0 -2015-11-11 15:00:00,11019.0 -2015-11-11 16:00:00,10982.0 -2015-11-11 17:00:00,11058.0 -2015-11-11 18:00:00,11566.0 -2015-11-11 19:00:00,11855.0 -2015-11-11 20:00:00,11677.0 -2015-11-11 21:00:00,11421.0 -2015-11-11 22:00:00,11026.0 -2015-11-11 23:00:00,10491.0 -2015-11-12 00:00:00,9770.0 -2015-11-10 01:00:00,9229.0 -2015-11-10 02:00:00,8804.0 -2015-11-10 03:00:00,8580.0 -2015-11-10 04:00:00,8464.0 -2015-11-10 05:00:00,8461.0 -2015-11-10 06:00:00,8702.0 -2015-11-10 07:00:00,9469.0 -2015-11-10 08:00:00,10569.0 -2015-11-10 09:00:00,10982.0 -2015-11-10 10:00:00,11048.0 -2015-11-10 11:00:00,11028.0 -2015-11-10 12:00:00,11040.0 -2015-11-10 13:00:00,11027.0 -2015-11-10 14:00:00,10960.0 -2015-11-10 15:00:00,10962.0 -2015-11-10 16:00:00,10838.0 -2015-11-10 17:00:00,10732.0 -2015-11-10 18:00:00,11039.0 -2015-11-10 19:00:00,11708.0 -2015-11-10 20:00:00,11560.0 -2015-11-10 21:00:00,11387.0 -2015-11-10 22:00:00,11109.0 -2015-11-10 23:00:00,10597.0 -2015-11-11 00:00:00,9917.0 -2015-11-09 01:00:00,8665.0 -2015-11-09 02:00:00,8407.0 -2015-11-09 03:00:00,8254.0 -2015-11-09 04:00:00,8214.0 -2015-11-09 05:00:00,8283.0 -2015-11-09 06:00:00,8614.0 -2015-11-09 07:00:00,9424.0 -2015-11-09 08:00:00,10481.0 -2015-11-09 09:00:00,10923.0 -2015-11-09 10:00:00,11084.0 -2015-11-09 11:00:00,11010.0 -2015-11-09 12:00:00,11041.0 -2015-11-09 13:00:00,11005.0 -2015-11-09 14:00:00,10911.0 -2015-11-09 15:00:00,10936.0 -2015-11-09 16:00:00,10803.0 -2015-11-09 17:00:00,10711.0 -2015-11-09 18:00:00,11076.0 -2015-11-09 19:00:00,11742.0 -2015-11-09 20:00:00,11627.0 -2015-11-09 21:00:00,11428.0 -2015-11-09 22:00:00,11103.0 -2015-11-09 23:00:00,10600.0 -2015-11-10 00:00:00,9913.0 -2015-11-08 01:00:00,8846.0 -2015-11-08 02:00:00,8506.0 -2015-11-08 03:00:00,8263.0 -2015-11-08 04:00:00,8171.0 -2015-11-08 05:00:00,8107.0 -2015-11-08 06:00:00,8153.0 -2015-11-08 07:00:00,8319.0 -2015-11-08 08:00:00,8472.0 -2015-11-08 09:00:00,8511.0 -2015-11-08 10:00:00,8699.0 -2015-11-08 11:00:00,8836.0 -2015-11-08 12:00:00,8891.0 -2015-11-08 13:00:00,8930.0 -2015-11-08 14:00:00,8884.0 -2015-11-08 15:00:00,8841.0 -2015-11-08 16:00:00,8801.0 -2015-11-08 17:00:00,8867.0 -2015-11-08 18:00:00,9391.0 -2015-11-08 19:00:00,10201.0 -2015-11-08 20:00:00,10256.0 -2015-11-08 21:00:00,10199.0 -2015-11-08 22:00:00,9991.0 -2015-11-08 23:00:00,9662.0 -2015-11-09 00:00:00,9133.0 -2015-11-07 01:00:00,9128.0 -2015-11-07 02:00:00,8690.0 -2015-11-07 03:00:00,8389.0 -2015-11-07 04:00:00,8237.0 -2015-11-07 05:00:00,8171.0 -2015-11-07 06:00:00,8284.0 -2015-11-07 07:00:00,8564.0 -2015-11-07 08:00:00,8918.0 -2015-11-07 09:00:00,9117.0 -2015-11-07 10:00:00,9430.0 -2015-11-07 11:00:00,9560.0 -2015-11-07 12:00:00,9642.0 -2015-11-07 13:00:00,9592.0 -2015-11-07 14:00:00,9539.0 -2015-11-07 15:00:00,9341.0 -2015-11-07 16:00:00,9228.0 -2015-11-07 17:00:00,9200.0 -2015-11-07 18:00:00,9595.0 -2015-11-07 19:00:00,10292.0 -2015-11-07 20:00:00,10270.0 -2015-11-07 21:00:00,10185.0 -2015-11-07 22:00:00,10050.0 -2015-11-07 23:00:00,9775.0 -2015-11-08 00:00:00,9310.0 -2015-11-06 01:00:00,9268.0 -2015-11-06 02:00:00,8767.0 -2015-11-06 03:00:00,8488.0 -2015-11-06 04:00:00,8236.0 -2015-11-06 05:00:00,8101.0 -2015-11-06 06:00:00,8219.0 -2015-11-06 07:00:00,8770.0 -2015-11-06 08:00:00,9749.0 -2015-11-06 09:00:00,10249.0 -2015-11-06 10:00:00,10580.0 -2015-11-06 11:00:00,10790.0 -2015-11-06 12:00:00,10885.0 -2015-11-06 13:00:00,10855.0 -2015-11-06 14:00:00,10829.0 -2015-11-06 15:00:00,10843.0 -2015-11-06 16:00:00,10787.0 -2015-11-06 17:00:00,10753.0 -2015-11-06 18:00:00,11159.0 -2015-11-06 19:00:00,11534.0 -2015-11-06 20:00:00,11343.0 -2015-11-06 21:00:00,11082.0 -2015-11-06 22:00:00,10793.0 -2015-11-06 23:00:00,10410.0 -2015-11-07 00:00:00,9764.0 -2015-11-05 01:00:00,9184.0 -2015-11-05 02:00:00,8720.0 -2015-11-05 03:00:00,8427.0 -2015-11-05 04:00:00,8253.0 -2015-11-05 05:00:00,8228.0 -2015-11-05 06:00:00,8441.0 -2015-11-05 07:00:00,9114.0 -2015-11-05 08:00:00,10109.0 -2015-11-05 09:00:00,10727.0 -2015-11-05 10:00:00,11083.0 -2015-11-05 11:00:00,11256.0 -2015-11-05 12:00:00,11456.0 -2015-11-05 13:00:00,11619.0 -2015-11-05 14:00:00,11641.0 -2015-11-05 15:00:00,11605.0 -2015-11-05 16:00:00,11530.0 -2015-11-05 17:00:00,11447.0 -2015-11-05 18:00:00,11812.0 -2015-11-05 19:00:00,12200.0 -2015-11-05 20:00:00,11968.0 -2015-11-05 21:00:00,11661.0 -2015-11-05 22:00:00,11308.0 -2015-11-05 23:00:00,10729.0 -2015-11-06 00:00:00,9959.0 -2015-11-04 01:00:00,8981.0 -2015-11-04 02:00:00,8545.0 -2015-11-04 03:00:00,8274.0 -2015-11-04 04:00:00,8085.0 -2015-11-04 05:00:00,8016.0 -2015-11-04 06:00:00,8287.0 -2015-11-04 07:00:00,8941.0 -2015-11-04 08:00:00,9891.0 -2015-11-04 09:00:00,10475.0 -2015-11-04 10:00:00,10833.0 -2015-11-04 11:00:00,11031.0 -2015-11-04 12:00:00,11224.0 -2015-11-04 13:00:00,11389.0 -2015-11-04 14:00:00,11515.0 -2015-11-04 15:00:00,11633.0 -2015-11-04 16:00:00,11605.0 -2015-11-04 17:00:00,11495.0 -2015-11-04 18:00:00,11667.0 -2015-11-04 19:00:00,12156.0 -2015-11-04 20:00:00,11937.0 -2015-11-04 21:00:00,11641.0 -2015-11-04 22:00:00,11310.0 -2015-11-04 23:00:00,10715.0 -2015-11-05 00:00:00,9924.0 -2015-11-03 01:00:00,8908.0 -2015-11-03 02:00:00,8468.0 -2015-11-03 03:00:00,8187.0 -2015-11-03 04:00:00,8044.0 -2015-11-03 05:00:00,7996.0 -2015-11-03 06:00:00,8232.0 -2015-11-03 07:00:00,8901.0 -2015-11-03 08:00:00,9827.0 -2015-11-03 09:00:00,10344.0 -2015-11-03 10:00:00,10652.0 -2015-11-03 11:00:00,10933.0 -2015-11-03 12:00:00,11195.0 -2015-11-03 13:00:00,11341.0 -2015-11-03 14:00:00,11378.0 -2015-11-03 15:00:00,11378.0 -2015-11-03 16:00:00,11394.0 -2015-11-03 17:00:00,11269.0 -2015-11-03 18:00:00,11351.0 -2015-11-03 19:00:00,11945.0 -2015-11-03 20:00:00,11739.0 -2015-11-03 21:00:00,11449.0 -2015-11-03 22:00:00,11054.0 -2015-11-03 23:00:00,10454.0 -2015-11-04 00:00:00,9700.0 -2015-11-02 01:00:00,8354.0 -2015-11-02 02:00:00,8045.0 -2015-11-02 03:00:00,7915.0 -2015-11-02 04:00:00,7849.0 -2015-11-02 05:00:00,7844.0 -2015-11-02 06:00:00,8186.0 -2015-11-02 07:00:00,8955.0 -2015-11-02 08:00:00,9901.0 -2015-11-02 09:00:00,10442.0 -2015-11-02 10:00:00,10713.0 -2015-11-02 11:00:00,10888.0 -2015-11-02 12:00:00,11121.0 -2015-11-02 13:00:00,11209.0 -2015-11-02 14:00:00,11237.0 -2015-11-02 15:00:00,11300.0 -2015-11-02 16:00:00,11229.0 -2015-11-02 17:00:00,11130.0 -2015-11-02 18:00:00,11228.0 -2015-11-02 19:00:00,11813.0 -2015-11-02 20:00:00,11644.0 -2015-11-02 21:00:00,11382.0 -2015-11-02 22:00:00,10991.0 -2015-11-02 23:00:00,10386.0 -2015-11-03 00:00:00,9574.0 -2015-11-01 01:00:00,8663.0 -2015-11-01 02:00:00,8270.0 -2015-11-01 02:00:00,7923.0 -2015-11-01 03:00:00,7902.0 -2015-11-01 04:00:00,7761.0 -2015-11-01 05:00:00,7744.0 -2015-11-01 06:00:00,7792.0 -2015-11-01 07:00:00,7981.0 -2015-11-01 08:00:00,8149.0 -2015-11-01 09:00:00,8264.0 -2015-11-01 10:00:00,8509.0 -2015-11-01 11:00:00,8676.0 -2015-11-01 12:00:00,8762.0 -2015-11-01 13:00:00,8798.0 -2015-11-01 14:00:00,8851.0 -2015-11-01 15:00:00,8821.0 -2015-11-01 16:00:00,8812.0 -2015-11-01 17:00:00,8829.0 -2015-11-01 18:00:00,9181.0 -2015-11-01 19:00:00,10004.0 -2015-11-01 20:00:00,10086.0 -2015-11-01 21:00:00,9981.0 -2015-11-01 22:00:00,9686.0 -2015-11-01 23:00:00,9327.0 -2015-11-02 00:00:00,8782.0 -2015-10-31 01:00:00,9110.0 -2015-10-31 02:00:00,8587.0 -2015-10-31 03:00:00,8314.0 -2015-10-31 04:00:00,8085.0 -2015-10-31 05:00:00,8041.0 -2015-10-31 06:00:00,8068.0 -2015-10-31 07:00:00,8329.0 -2015-10-31 08:00:00,8772.0 -2015-10-31 09:00:00,9220.0 -2015-10-31 10:00:00,9558.0 -2015-10-31 11:00:00,9941.0 -2015-10-31 12:00:00,10156.0 -2015-10-31 13:00:00,10239.0 -2015-10-31 14:00:00,10147.0 -2015-10-31 15:00:00,10051.0 -2015-10-31 16:00:00,9899.0 -2015-10-31 17:00:00,9874.0 -2015-10-31 18:00:00,9876.0 -2015-10-31 19:00:00,10079.0 -2015-10-31 20:00:00,10241.0 -2015-10-31 21:00:00,10096.0 -2015-10-31 22:00:00,9876.0 -2015-10-31 23:00:00,9592.0 -2015-11-01 00:00:00,9158.0 -2015-10-30 01:00:00,9337.0 -2015-10-30 02:00:00,8855.0 -2015-10-30 03:00:00,8571.0 -2015-10-30 04:00:00,8452.0 -2015-10-30 05:00:00,8432.0 -2015-10-30 06:00:00,8624.0 -2015-10-30 07:00:00,9349.0 -2015-10-30 08:00:00,10544.0 -2015-10-30 09:00:00,11163.0 -2015-10-30 10:00:00,11137.0 -2015-10-30 11:00:00,11084.0 -2015-10-30 12:00:00,11064.0 -2015-10-30 13:00:00,10977.0 -2015-10-30 14:00:00,10868.0 -2015-10-30 15:00:00,10822.0 -2015-10-30 16:00:00,10660.0 -2015-10-30 17:00:00,10494.0 -2015-10-30 18:00:00,10393.0 -2015-10-30 19:00:00,10590.0 -2015-10-30 20:00:00,11106.0 -2015-10-30 21:00:00,11002.0 -2015-10-30 22:00:00,10737.0 -2015-10-30 23:00:00,10384.0 -2015-10-31 00:00:00,9726.0 -2015-10-29 01:00:00,9406.0 -2015-10-29 02:00:00,8982.0 -2015-10-29 03:00:00,8705.0 -2015-10-29 04:00:00,8539.0 -2015-10-29 05:00:00,8551.0 -2015-10-29 06:00:00,8834.0 -2015-10-29 07:00:00,9561.0 -2015-10-29 08:00:00,10751.0 -2015-10-29 09:00:00,11492.0 -2015-10-29 10:00:00,11581.0 -2015-10-29 11:00:00,11652.0 -2015-10-29 12:00:00,11660.0 -2015-10-29 13:00:00,11570.0 -2015-10-29 14:00:00,11452.0 -2015-10-29 15:00:00,11464.0 -2015-10-29 16:00:00,11315.0 -2015-10-29 17:00:00,11246.0 -2015-10-29 18:00:00,11294.0 -2015-10-29 19:00:00,11563.0 -2015-10-29 20:00:00,11740.0 -2015-10-29 21:00:00,11591.0 -2015-10-29 22:00:00,11349.0 -2015-10-29 23:00:00,10811.0 -2015-10-30 00:00:00,10048.0 -2015-10-28 01:00:00,9181.0 -2015-10-28 02:00:00,8701.0 -2015-10-28 03:00:00,8375.0 -2015-10-28 04:00:00,8200.0 -2015-10-28 05:00:00,8131.0 -2015-10-28 06:00:00,8330.0 -2015-10-28 07:00:00,8964.0 -2015-10-28 08:00:00,10139.0 -2015-10-28 09:00:00,10918.0 -2015-10-28 10:00:00,11079.0 -2015-10-28 11:00:00,11158.0 -2015-10-28 12:00:00,11299.0 -2015-10-28 13:00:00,11298.0 -2015-10-28 14:00:00,11249.0 -2015-10-28 15:00:00,11251.0 -2015-10-28 16:00:00,11182.0 -2015-10-28 17:00:00,11115.0 -2015-10-28 18:00:00,11189.0 -2015-10-28 19:00:00,11481.0 -2015-10-28 20:00:00,11662.0 -2015-10-28 21:00:00,11548.0 -2015-10-28 22:00:00,11303.0 -2015-10-28 23:00:00,10843.0 -2015-10-29 00:00:00,10086.0 -2015-10-27 01:00:00,8996.0 -2015-10-27 02:00:00,8552.0 -2015-10-27 03:00:00,8271.0 -2015-10-27 04:00:00,8074.0 -2015-10-27 05:00:00,8015.0 -2015-10-27 06:00:00,8221.0 -2015-10-27 07:00:00,8922.0 -2015-10-27 08:00:00,10119.0 -2015-10-27 09:00:00,10740.0 -2015-10-27 10:00:00,10816.0 -2015-10-27 11:00:00,10937.0 -2015-10-27 12:00:00,11063.0 -2015-10-27 13:00:00,11118.0 -2015-10-27 14:00:00,11098.0 -2015-10-27 15:00:00,11062.0 -2015-10-27 16:00:00,10985.0 -2015-10-27 17:00:00,10915.0 -2015-10-27 18:00:00,11019.0 -2015-10-27 19:00:00,11346.0 -2015-10-27 20:00:00,11587.0 -2015-10-27 21:00:00,11422.0 -2015-10-27 22:00:00,11178.0 -2015-10-27 23:00:00,10660.0 -2015-10-28 00:00:00,9893.0 -2015-10-26 01:00:00,8433.0 -2015-10-26 02:00:00,8116.0 -2015-10-26 03:00:00,7927.0 -2015-10-26 04:00:00,7859.0 -2015-10-26 05:00:00,7859.0 -2015-10-26 06:00:00,8141.0 -2015-10-26 07:00:00,8845.0 -2015-10-26 08:00:00,10113.0 -2015-10-26 09:00:00,10737.0 -2015-10-26 10:00:00,10804.0 -2015-10-26 11:00:00,10792.0 -2015-10-26 12:00:00,10943.0 -2015-10-26 13:00:00,10948.0 -2015-10-26 14:00:00,10949.0 -2015-10-26 15:00:00,10960.0 -2015-10-26 16:00:00,10860.0 -2015-10-26 17:00:00,10709.0 -2015-10-26 18:00:00,10628.0 -2015-10-26 19:00:00,10774.0 -2015-10-26 20:00:00,11304.0 -2015-10-26 21:00:00,11217.0 -2015-10-26 22:00:00,10981.0 -2015-10-26 23:00:00,10476.0 -2015-10-27 00:00:00,9758.0 -2015-10-25 01:00:00,8471.0 -2015-10-25 02:00:00,8055.0 -2015-10-25 03:00:00,7796.0 -2015-10-25 04:00:00,7597.0 -2015-10-25 05:00:00,7526.0 -2015-10-25 06:00:00,7504.0 -2015-10-25 07:00:00,7669.0 -2015-10-25 08:00:00,7968.0 -2015-10-25 09:00:00,8080.0 -2015-10-25 10:00:00,8286.0 -2015-10-25 11:00:00,8507.0 -2015-10-25 12:00:00,8647.0 -2015-10-25 13:00:00,8736.0 -2015-10-25 14:00:00,8748.0 -2015-10-25 15:00:00,8747.0 -2015-10-25 16:00:00,8712.0 -2015-10-25 17:00:00,8726.0 -2015-10-25 18:00:00,8769.0 -2015-10-25 19:00:00,8995.0 -2015-10-25 20:00:00,9722.0 -2015-10-25 21:00:00,9868.0 -2015-10-25 22:00:00,9684.0 -2015-10-25 23:00:00,9356.0 -2015-10-26 00:00:00,8918.0 -2015-10-24 01:00:00,9167.0 -2015-10-24 02:00:00,8643.0 -2015-10-24 03:00:00,8326.0 -2015-10-24 04:00:00,8087.0 -2015-10-24 05:00:00,8047.0 -2015-10-24 06:00:00,8077.0 -2015-10-24 07:00:00,8364.0 -2015-10-24 08:00:00,8817.0 -2015-10-24 09:00:00,9200.0 -2015-10-24 10:00:00,9631.0 -2015-10-24 11:00:00,10028.0 -2015-10-24 12:00:00,10191.0 -2015-10-24 13:00:00,10058.0 -2015-10-24 14:00:00,9895.0 -2015-10-24 15:00:00,9721.0 -2015-10-24 16:00:00,9564.0 -2015-10-24 17:00:00,9477.0 -2015-10-24 18:00:00,9623.0 -2015-10-24 19:00:00,9802.0 -2015-10-24 20:00:00,10109.0 -2015-10-24 21:00:00,9970.0 -2015-10-24 22:00:00,9808.0 -2015-10-24 23:00:00,9449.0 -2015-10-25 00:00:00,8999.0 -2015-10-23 01:00:00,8989.0 -2015-10-23 02:00:00,8509.0 -2015-10-23 03:00:00,8186.0 -2015-10-23 04:00:00,8041.0 -2015-10-23 05:00:00,7982.0 -2015-10-23 06:00:00,8159.0 -2015-10-23 07:00:00,8802.0 -2015-10-23 08:00:00,9910.0 -2015-10-23 09:00:00,10544.0 -2015-10-23 10:00:00,10715.0 -2015-10-23 11:00:00,10864.0 -2015-10-23 12:00:00,10921.0 -2015-10-23 13:00:00,10960.0 -2015-10-23 14:00:00,10953.0 -2015-10-23 15:00:00,10970.0 -2015-10-23 16:00:00,10880.0 -2015-10-23 17:00:00,10764.0 -2015-10-23 18:00:00,10788.0 -2015-10-23 19:00:00,11041.0 -2015-10-23 20:00:00,11260.0 -2015-10-23 21:00:00,11042.0 -2015-10-23 22:00:00,10773.0 -2015-10-23 23:00:00,10440.0 -2015-10-24 00:00:00,9806.0 -2015-10-22 01:00:00,9204.0 -2015-10-22 02:00:00,8722.0 -2015-10-22 03:00:00,8324.0 -2015-10-22 04:00:00,8085.0 -2015-10-22 05:00:00,8019.0 -2015-10-22 06:00:00,8184.0 -2015-10-22 07:00:00,8774.0 -2015-10-22 08:00:00,9866.0 -2015-10-22 09:00:00,10539.0 -2015-10-22 10:00:00,10699.0 -2015-10-22 11:00:00,10822.0 -2015-10-22 12:00:00,10927.0 -2015-10-22 13:00:00,10966.0 -2015-10-22 14:00:00,11017.0 -2015-10-22 15:00:00,11134.0 -2015-10-22 16:00:00,11074.0 -2015-10-22 17:00:00,10960.0 -2015-10-22 18:00:00,10806.0 -2015-10-22 19:00:00,10771.0 -2015-10-22 20:00:00,11234.0 -2015-10-22 21:00:00,11220.0 -2015-10-22 22:00:00,10905.0 -2015-10-22 23:00:00,10406.0 -2015-10-23 00:00:00,9682.0 -2015-10-21 01:00:00,9061.0 -2015-10-21 02:00:00,8534.0 -2015-10-21 03:00:00,8216.0 -2015-10-21 04:00:00,7993.0 -2015-10-21 05:00:00,7959.0 -2015-10-21 06:00:00,8134.0 -2015-10-21 07:00:00,8773.0 -2015-10-21 08:00:00,9915.0 -2015-10-21 09:00:00,10507.0 -2015-10-21 10:00:00,10748.0 -2015-10-21 11:00:00,10956.0 -2015-10-21 12:00:00,11191.0 -2015-10-21 13:00:00,11362.0 -2015-10-21 14:00:00,11504.0 -2015-10-21 15:00:00,11583.0 -2015-10-21 16:00:00,11574.0 -2015-10-21 17:00:00,11471.0 -2015-10-21 18:00:00,11393.0 -2015-10-21 19:00:00,11317.0 -2015-10-21 20:00:00,11764.0 -2015-10-21 21:00:00,11792.0 -2015-10-21 22:00:00,11413.0 -2015-10-21 23:00:00,10864.0 -2015-10-22 00:00:00,10038.0 -2015-10-20 01:00:00,9000.0 -2015-10-20 02:00:00,8522.0 -2015-10-20 03:00:00,8183.0 -2015-10-20 04:00:00,8001.0 -2015-10-20 05:00:00,7946.0 -2015-10-20 06:00:00,8148.0 -2015-10-20 07:00:00,8791.0 -2015-10-20 08:00:00,9928.0 -2015-10-20 09:00:00,10511.0 -2015-10-20 10:00:00,10675.0 -2015-10-20 11:00:00,10824.0 -2015-10-20 12:00:00,11037.0 -2015-10-20 13:00:00,11122.0 -2015-10-20 14:00:00,11134.0 -2015-10-20 15:00:00,11238.0 -2015-10-20 16:00:00,11226.0 -2015-10-20 17:00:00,11136.0 -2015-10-20 18:00:00,11066.0 -2015-10-20 19:00:00,11054.0 -2015-10-20 20:00:00,11525.0 -2015-10-20 21:00:00,11432.0 -2015-10-20 22:00:00,11123.0 -2015-10-20 23:00:00,10597.0 -2015-10-21 00:00:00,9818.0 -2015-10-19 01:00:00,8490.0 -2015-10-19 02:00:00,8141.0 -2015-10-19 03:00:00,7962.0 -2015-10-19 04:00:00,7878.0 -2015-10-19 05:00:00,7927.0 -2015-10-19 06:00:00,8175.0 -2015-10-19 07:00:00,8936.0 -2015-10-19 08:00:00,10113.0 -2015-10-19 09:00:00,10699.0 -2015-10-19 10:00:00,10829.0 -2015-10-19 11:00:00,10913.0 -2015-10-19 12:00:00,11051.0 -2015-10-19 13:00:00,11118.0 -2015-10-19 14:00:00,11168.0 -2015-10-19 15:00:00,11212.0 -2015-10-19 16:00:00,11164.0 -2015-10-19 17:00:00,11033.0 -2015-10-19 18:00:00,10988.0 -2015-10-19 19:00:00,10969.0 -2015-10-19 20:00:00,11414.0 -2015-10-19 21:00:00,11444.0 -2015-10-19 22:00:00,11118.0 -2015-10-19 23:00:00,10553.0 -2015-10-20 00:00:00,9740.0 -2015-10-18 01:00:00,8752.0 -2015-10-18 02:00:00,8396.0 -2015-10-18 03:00:00,8127.0 -2015-10-18 04:00:00,8031.0 -2015-10-18 05:00:00,7955.0 -2015-10-18 06:00:00,8000.0 -2015-10-18 07:00:00,8100.0 -2015-10-18 08:00:00,8391.0 -2015-10-18 09:00:00,8448.0 -2015-10-18 10:00:00,8670.0 -2015-10-18 11:00:00,8776.0 -2015-10-18 12:00:00,8897.0 -2015-10-18 13:00:00,8899.0 -2015-10-18 14:00:00,8920.0 -2015-10-18 15:00:00,8866.0 -2015-10-18 16:00:00,8780.0 -2015-10-18 17:00:00,8791.0 -2015-10-18 18:00:00,8845.0 -2015-10-18 19:00:00,9034.0 -2015-10-18 20:00:00,9718.0 -2015-10-18 21:00:00,9960.0 -2015-10-18 22:00:00,9779.0 -2015-10-18 23:00:00,9500.0 -2015-10-19 00:00:00,8969.0 -2015-10-17 01:00:00,9128.0 -2015-10-17 02:00:00,8639.0 -2015-10-17 03:00:00,8404.0 -2015-10-17 04:00:00,8226.0 -2015-10-17 05:00:00,8159.0 -2015-10-17 06:00:00,8246.0 -2015-10-17 07:00:00,8506.0 -2015-10-17 08:00:00,9046.0 -2015-10-17 09:00:00,9298.0 -2015-10-17 10:00:00,9586.0 -2015-10-17 11:00:00,9716.0 -2015-10-17 12:00:00,9755.0 -2015-10-17 13:00:00,9663.0 -2015-10-17 14:00:00,9528.0 -2015-10-17 15:00:00,9322.0 -2015-10-17 16:00:00,9197.0 -2015-10-17 17:00:00,9106.0 -2015-10-17 18:00:00,9108.0 -2015-10-17 19:00:00,9217.0 -2015-10-17 20:00:00,9792.0 -2015-10-17 21:00:00,10001.0 -2015-10-17 22:00:00,9882.0 -2015-10-17 23:00:00,9666.0 -2015-10-18 00:00:00,9222.0 -2015-10-16 01:00:00,8950.0 -2015-10-16 02:00:00,8499.0 -2015-10-16 03:00:00,8229.0 -2015-10-16 04:00:00,8070.0 -2015-10-16 05:00:00,8044.0 -2015-10-16 06:00:00,8285.0 -2015-10-16 07:00:00,8820.0 -2015-10-16 08:00:00,10053.0 -2015-10-16 09:00:00,10543.0 -2015-10-16 10:00:00,10728.0 -2015-10-16 11:00:00,10783.0 -2015-10-16 12:00:00,10875.0 -2015-10-16 13:00:00,10825.0 -2015-10-16 14:00:00,10763.0 -2015-10-16 15:00:00,10776.0 -2015-10-16 16:00:00,10624.0 -2015-10-16 17:00:00,10490.0 -2015-10-16 18:00:00,10384.0 -2015-10-16 19:00:00,10350.0 -2015-10-16 20:00:00,10833.0 -2015-10-16 21:00:00,10885.0 -2015-10-16 22:00:00,10660.0 -2015-10-16 23:00:00,10337.0 -2015-10-17 00:00:00,9754.0 -2015-10-15 01:00:00,8887.0 -2015-10-15 02:00:00,8471.0 -2015-10-15 03:00:00,8186.0 -2015-10-15 04:00:00,8007.0 -2015-10-15 05:00:00,7968.0 -2015-10-15 06:00:00,8151.0 -2015-10-15 07:00:00,8804.0 -2015-10-15 08:00:00,9973.0 -2015-10-15 09:00:00,10517.0 -2015-10-15 10:00:00,10763.0 -2015-10-15 11:00:00,10861.0 -2015-10-15 12:00:00,11025.0 -2015-10-15 13:00:00,11081.0 -2015-10-15 14:00:00,11049.0 -2015-10-15 15:00:00,11101.0 -2015-10-15 16:00:00,11089.0 -2015-10-15 17:00:00,11007.0 -2015-10-15 18:00:00,10896.0 -2015-10-15 19:00:00,10787.0 -2015-10-15 20:00:00,11142.0 -2015-10-15 21:00:00,11241.0 -2015-10-15 22:00:00,10938.0 -2015-10-15 23:00:00,10409.0 -2015-10-16 00:00:00,9665.0 -2015-10-14 01:00:00,8824.0 -2015-10-14 02:00:00,8360.0 -2015-10-14 03:00:00,8065.0 -2015-10-14 04:00:00,7912.0 -2015-10-14 05:00:00,7866.0 -2015-10-14 06:00:00,8062.0 -2015-10-14 07:00:00,8709.0 -2015-10-14 08:00:00,9881.0 -2015-10-14 09:00:00,10432.0 -2015-10-14 10:00:00,10627.0 -2015-10-14 11:00:00,10656.0 -2015-10-14 12:00:00,10828.0 -2015-10-14 13:00:00,10870.0 -2015-10-14 14:00:00,10867.0 -2015-10-14 15:00:00,10992.0 -2015-10-14 16:00:00,10925.0 -2015-10-14 17:00:00,10862.0 -2015-10-14 18:00:00,10774.0 -2015-10-14 19:00:00,10677.0 -2015-10-14 20:00:00,11048.0 -2015-10-14 21:00:00,11132.0 -2015-10-14 22:00:00,10869.0 -2015-10-14 23:00:00,10321.0 -2015-10-15 00:00:00,9604.0 -2015-10-13 01:00:00,8895.0 -2015-10-13 02:00:00,8373.0 -2015-10-13 03:00:00,8061.0 -2015-10-13 04:00:00,7861.0 -2015-10-13 05:00:00,7769.0 -2015-10-13 06:00:00,7963.0 -2015-10-13 07:00:00,8644.0 -2015-10-13 08:00:00,9799.0 -2015-10-13 09:00:00,10267.0 -2015-10-13 10:00:00,10532.0 -2015-10-13 11:00:00,10692.0 -2015-10-13 12:00:00,10831.0 -2015-10-13 13:00:00,10870.0 -2015-10-13 14:00:00,10858.0 -2015-10-13 15:00:00,10873.0 -2015-10-13 16:00:00,10836.0 -2015-10-13 17:00:00,10780.0 -2015-10-13 18:00:00,10710.0 -2015-10-13 19:00:00,10663.0 -2015-10-13 20:00:00,10993.0 -2015-10-13 21:00:00,11154.0 -2015-10-13 22:00:00,10820.0 -2015-10-13 23:00:00,10296.0 -2015-10-14 00:00:00,9541.0 -2015-10-12 01:00:00,8606.0 -2015-10-12 02:00:00,8172.0 -2015-10-12 03:00:00,7938.0 -2015-10-12 04:00:00,7768.0 -2015-10-12 05:00:00,7726.0 -2015-10-12 06:00:00,7962.0 -2015-10-12 07:00:00,8570.0 -2015-10-12 08:00:00,9569.0 -2015-10-12 09:00:00,10077.0 -2015-10-12 10:00:00,10631.0 -2015-10-12 11:00:00,11020.0 -2015-10-12 12:00:00,11294.0 -2015-10-12 13:00:00,11332.0 -2015-10-12 14:00:00,11299.0 -2015-10-12 15:00:00,11319.0 -2015-10-12 16:00:00,11402.0 -2015-10-12 17:00:00,11409.0 -2015-10-12 18:00:00,11293.0 -2015-10-12 19:00:00,11150.0 -2015-10-12 20:00:00,11383.0 -2015-10-12 21:00:00,11371.0 -2015-10-12 22:00:00,11003.0 -2015-10-12 23:00:00,10471.0 -2015-10-13 00:00:00,9678.0 -2015-10-11 01:00:00,8446.0 -2015-10-11 02:00:00,8029.0 -2015-10-11 03:00:00,7758.0 -2015-10-11 04:00:00,7556.0 -2015-10-11 05:00:00,7479.0 -2015-10-11 06:00:00,7470.0 -2015-10-11 07:00:00,7600.0 -2015-10-11 08:00:00,7728.0 -2015-10-11 09:00:00,7789.0 -2015-10-11 10:00:00,8135.0 -2015-10-11 11:00:00,8436.0 -2015-10-11 12:00:00,8710.0 -2015-10-11 13:00:00,8923.0 -2015-10-11 14:00:00,9127.0 -2015-10-11 15:00:00,9309.0 -2015-10-11 16:00:00,9436.0 -2015-10-11 17:00:00,9548.0 -2015-10-11 18:00:00,9674.0 -2015-10-11 19:00:00,9716.0 -2015-10-11 20:00:00,10074.0 -2015-10-11 21:00:00,10351.0 -2015-10-11 22:00:00,10072.0 -2015-10-11 23:00:00,9733.0 -2015-10-12 00:00:00,9154.0 -2015-10-10 01:00:00,8872.0 -2015-10-10 02:00:00,8370.0 -2015-10-10 03:00:00,8064.0 -2015-10-10 04:00:00,7891.0 -2015-10-10 05:00:00,7782.0 -2015-10-10 06:00:00,7861.0 -2015-10-10 07:00:00,8085.0 -2015-10-10 08:00:00,8563.0 -2015-10-10 09:00:00,8759.0 -2015-10-10 10:00:00,9124.0 -2015-10-10 11:00:00,9342.0 -2015-10-10 12:00:00,9516.0 -2015-10-10 13:00:00,9527.0 -2015-10-10 14:00:00,9485.0 -2015-10-10 15:00:00,9392.0 -2015-10-10 16:00:00,9344.0 -2015-10-10 17:00:00,9314.0 -2015-10-10 18:00:00,9286.0 -2015-10-10 19:00:00,9284.0 -2015-10-10 20:00:00,9704.0 -2015-10-10 21:00:00,9857.0 -2015-10-10 22:00:00,9756.0 -2015-10-10 23:00:00,9420.0 -2015-10-11 00:00:00,8976.0 -2015-10-09 01:00:00,9590.0 -2015-10-09 02:00:00,9013.0 -2015-10-09 03:00:00,8630.0 -2015-10-09 04:00:00,8351.0 -2015-10-09 05:00:00,8202.0 -2015-10-09 06:00:00,8311.0 -2015-10-09 07:00:00,8903.0 -2015-10-09 08:00:00,9935.0 -2015-10-09 09:00:00,10467.0 -2015-10-09 10:00:00,10712.0 -2015-10-09 11:00:00,10906.0 -2015-10-09 12:00:00,11045.0 -2015-10-09 13:00:00,11023.0 -2015-10-09 14:00:00,10994.0 -2015-10-09 15:00:00,11024.0 -2015-10-09 16:00:00,10897.0 -2015-10-09 17:00:00,10667.0 -2015-10-09 18:00:00,10518.0 -2015-10-09 19:00:00,10445.0 -2015-10-09 20:00:00,10735.0 -2015-10-09 21:00:00,10857.0 -2015-10-09 22:00:00,10583.0 -2015-10-09 23:00:00,10168.0 -2015-10-10 00:00:00,9531.0 -2015-10-08 01:00:00,9157.0 -2015-10-08 02:00:00,8611.0 -2015-10-08 03:00:00,8287.0 -2015-10-08 04:00:00,8096.0 -2015-10-08 05:00:00,7997.0 -2015-10-08 06:00:00,8172.0 -2015-10-08 07:00:00,8760.0 -2015-10-08 08:00:00,9859.0 -2015-10-08 09:00:00,10409.0 -2015-10-08 10:00:00,10796.0 -2015-10-08 11:00:00,11094.0 -2015-10-08 12:00:00,11474.0 -2015-10-08 13:00:00,11757.0 -2015-10-08 14:00:00,11952.0 -2015-10-08 15:00:00,12131.0 -2015-10-08 16:00:00,12161.0 -2015-10-08 17:00:00,11995.0 -2015-10-08 18:00:00,11873.0 -2015-10-08 19:00:00,11753.0 -2015-10-08 20:00:00,12019.0 -2015-10-08 21:00:00,12168.0 -2015-10-08 22:00:00,11829.0 -2015-10-08 23:00:00,11244.0 -2015-10-09 00:00:00,10375.0 -2015-10-07 01:00:00,9104.0 -2015-10-07 02:00:00,8606.0 -2015-10-07 03:00:00,8293.0 -2015-10-07 04:00:00,8099.0 -2015-10-07 05:00:00,8049.0 -2015-10-07 06:00:00,8258.0 -2015-10-07 07:00:00,8893.0 -2015-10-07 08:00:00,10020.0 -2015-10-07 09:00:00,10519.0 -2015-10-07 10:00:00,10875.0 -2015-10-07 11:00:00,11135.0 -2015-10-07 12:00:00,11445.0 -2015-10-07 13:00:00,11643.0 -2015-10-07 14:00:00,11764.0 -2015-10-07 15:00:00,11956.0 -2015-10-07 16:00:00,11906.0 -2015-10-07 17:00:00,11735.0 -2015-10-07 18:00:00,11530.0 -2015-10-07 19:00:00,11244.0 -2015-10-07 20:00:00,11417.0 -2015-10-07 21:00:00,11544.0 -2015-10-07 22:00:00,11197.0 -2015-10-07 23:00:00,10667.0 -2015-10-08 00:00:00,9913.0 -2015-10-06 01:00:00,9025.0 -2015-10-06 02:00:00,8564.0 -2015-10-06 03:00:00,8262.0 -2015-10-06 04:00:00,8079.0 -2015-10-06 05:00:00,7997.0 -2015-10-06 06:00:00,8185.0 -2015-10-06 07:00:00,8871.0 -2015-10-06 08:00:00,9977.0 -2015-10-06 09:00:00,10581.0 -2015-10-06 10:00:00,10812.0 -2015-10-06 11:00:00,10988.0 -2015-10-06 12:00:00,11164.0 -2015-10-06 13:00:00,11212.0 -2015-10-06 14:00:00,11235.0 -2015-10-06 15:00:00,11395.0 -2015-10-06 16:00:00,11358.0 -2015-10-06 17:00:00,11334.0 -2015-10-06 18:00:00,11255.0 -2015-10-06 19:00:00,11094.0 -2015-10-06 20:00:00,11235.0 -2015-10-06 21:00:00,11471.0 -2015-10-06 22:00:00,11158.0 -2015-10-06 23:00:00,10605.0 -2015-10-07 00:00:00,9827.0 -2015-10-05 01:00:00,8350.0 -2015-10-05 02:00:00,7968.0 -2015-10-05 03:00:00,7796.0 -2015-10-05 04:00:00,7656.0 -2015-10-05 05:00:00,7693.0 -2015-10-05 06:00:00,7900.0 -2015-10-05 07:00:00,8623.0 -2015-10-05 08:00:00,9766.0 -2015-10-05 09:00:00,10477.0 -2015-10-05 10:00:00,10735.0 -2015-10-05 11:00:00,10904.0 -2015-10-05 12:00:00,11063.0 -2015-10-05 13:00:00,11109.0 -2015-10-05 14:00:00,11113.0 -2015-10-05 15:00:00,11127.0 -2015-10-05 16:00:00,11025.0 -2015-10-05 17:00:00,10917.0 -2015-10-05 18:00:00,10861.0 -2015-10-05 19:00:00,10946.0 -2015-10-05 20:00:00,11259.0 -2015-10-05 21:00:00,11325.0 -2015-10-05 22:00:00,11022.0 -2015-10-05 23:00:00,10474.0 -2015-10-06 00:00:00,9715.0 -2015-10-04 01:00:00,8505.0 -2015-10-04 02:00:00,8155.0 -2015-10-04 03:00:00,7792.0 -2015-10-04 04:00:00,7619.0 -2015-10-04 05:00:00,7505.0 -2015-10-04 06:00:00,7522.0 -2015-10-04 07:00:00,7592.0 -2015-10-04 08:00:00,7865.0 -2015-10-04 09:00:00,7982.0 -2015-10-04 10:00:00,8345.0 -2015-10-04 11:00:00,8657.0 -2015-10-04 12:00:00,8846.0 -2015-10-04 13:00:00,8948.0 -2015-10-04 14:00:00,8970.0 -2015-10-04 15:00:00,8957.0 -2015-10-04 16:00:00,8945.0 -2015-10-04 17:00:00,8951.0 -2015-10-04 18:00:00,9071.0 -2015-10-04 19:00:00,9237.0 -2015-10-04 20:00:00,9696.0 -2015-10-04 21:00:00,9949.0 -2015-10-04 22:00:00,9768.0 -2015-10-04 23:00:00,9408.0 -2015-10-05 00:00:00,8860.0 -2015-10-03 01:00:00,8948.0 -2015-10-03 02:00:00,8490.0 -2015-10-03 03:00:00,8149.0 -2015-10-03 04:00:00,7954.0 -2015-10-03 05:00:00,7844.0 -2015-10-03 06:00:00,7864.0 -2015-10-03 07:00:00,8082.0 -2015-10-03 08:00:00,8518.0 -2015-10-03 09:00:00,8707.0 -2015-10-03 10:00:00,9139.0 -2015-10-03 11:00:00,9382.0 -2015-10-03 12:00:00,9537.0 -2015-10-03 13:00:00,9476.0 -2015-10-03 14:00:00,9446.0 -2015-10-03 15:00:00,9327.0 -2015-10-03 16:00:00,9261.0 -2015-10-03 17:00:00,9232.0 -2015-10-03 18:00:00,9284.0 -2015-10-03 19:00:00,9476.0 -2015-10-03 20:00:00,9855.0 -2015-10-03 21:00:00,10028.0 -2015-10-03 22:00:00,9873.0 -2015-10-03 23:00:00,9554.0 -2015-10-04 00:00:00,9052.0 -2015-10-02 01:00:00,8980.0 -2015-10-02 02:00:00,8496.0 -2015-10-02 03:00:00,8210.0 -2015-10-02 04:00:00,8002.0 -2015-10-02 05:00:00,7947.0 -2015-10-02 06:00:00,8140.0 -2015-10-02 07:00:00,8739.0 -2015-10-02 08:00:00,9832.0 -2015-10-02 09:00:00,10324.0 -2015-10-02 10:00:00,10543.0 -2015-10-02 11:00:00,10733.0 -2015-10-02 12:00:00,10886.0 -2015-10-02 13:00:00,10909.0 -2015-10-02 14:00:00,10890.0 -2015-10-02 15:00:00,10911.0 -2015-10-02 16:00:00,10811.0 -2015-10-02 17:00:00,10680.0 -2015-10-02 18:00:00,10555.0 -2015-10-02 19:00:00,10413.0 -2015-10-02 20:00:00,10564.0 -2015-10-02 21:00:00,10916.0 -2015-10-02 22:00:00,10645.0 -2015-10-02 23:00:00,10242.0 -2015-10-03 00:00:00,9562.0 -2015-10-01 01:00:00,8931.0 -2015-10-01 02:00:00,8425.0 -2015-10-01 03:00:00,8119.0 -2015-10-01 04:00:00,7926.0 -2015-10-01 05:00:00,7879.0 -2015-10-01 06:00:00,8053.0 -2015-10-01 07:00:00,8705.0 -2015-10-01 08:00:00,9779.0 -2015-10-01 09:00:00,10256.0 -2015-10-01 10:00:00,10587.0 -2015-10-01 11:00:00,10761.0 -2015-10-01 12:00:00,10894.0 -2015-10-01 13:00:00,10927.0 -2015-10-01 14:00:00,10891.0 -2015-10-01 15:00:00,10981.0 -2015-10-01 16:00:00,10883.0 -2015-10-01 17:00:00,10768.0 -2015-10-01 18:00:00,10677.0 -2015-10-01 19:00:00,10586.0 -2015-10-01 20:00:00,10728.0 -2015-10-01 21:00:00,11122.0 -2015-10-01 22:00:00,10951.0 -2015-10-01 23:00:00,10424.0 -2015-10-02 00:00:00,9681.0 -2015-09-30 01:00:00,9017.0 -2015-09-30 02:00:00,8512.0 -2015-09-30 03:00:00,8173.0 -2015-09-30 04:00:00,8003.0 -2015-09-30 05:00:00,7941.0 -2015-09-30 06:00:00,8121.0 -2015-09-30 07:00:00,8740.0 -2015-09-30 08:00:00,9824.0 -2015-09-30 09:00:00,10304.0 -2015-09-30 10:00:00,10570.0 -2015-09-30 11:00:00,10753.0 -2015-09-30 12:00:00,10956.0 -2015-09-30 13:00:00,11032.0 -2015-09-30 14:00:00,11063.0 -2015-09-30 15:00:00,11121.0 -2015-09-30 16:00:00,11047.0 -2015-09-30 17:00:00,10981.0 -2015-09-30 18:00:00,10849.0 -2015-09-30 19:00:00,10694.0 -2015-09-30 20:00:00,10730.0 -2015-09-30 21:00:00,11153.0 -2015-09-30 22:00:00,10917.0 -2015-09-30 23:00:00,10398.0 -2015-10-01 00:00:00,9674.0 -2015-09-29 01:00:00,10656.0 -2015-09-29 02:00:00,9920.0 -2015-09-29 03:00:00,9418.0 -2015-09-29 04:00:00,9113.0 -2015-09-29 05:00:00,8961.0 -2015-09-29 06:00:00,9107.0 -2015-09-29 07:00:00,9844.0 -2015-09-29 08:00:00,11079.0 -2015-09-29 09:00:00,11647.0 -2015-09-29 10:00:00,11814.0 -2015-09-29 11:00:00,11951.0 -2015-09-29 12:00:00,11992.0 -2015-09-29 13:00:00,11929.0 -2015-09-29 14:00:00,11828.0 -2015-09-29 15:00:00,11813.0 -2015-09-29 16:00:00,11679.0 -2015-09-29 17:00:00,11457.0 -2015-09-29 18:00:00,11316.0 -2015-09-29 19:00:00,11134.0 -2015-09-29 20:00:00,11111.0 -2015-09-29 21:00:00,11408.0 -2015-09-29 22:00:00,11134.0 -2015-09-29 23:00:00,10576.0 -2015-09-30 00:00:00,9784.0 -2015-09-28 01:00:00,9461.0 -2015-09-28 02:00:00,8973.0 -2015-09-28 03:00:00,8674.0 -2015-09-28 04:00:00,8508.0 -2015-09-28 05:00:00,8469.0 -2015-09-28 06:00:00,8711.0 -2015-09-28 07:00:00,9524.0 -2015-09-28 08:00:00,10767.0 -2015-09-28 09:00:00,11496.0 -2015-09-28 10:00:00,11932.0 -2015-09-28 11:00:00,12314.0 -2015-09-28 12:00:00,12705.0 -2015-09-28 13:00:00,13044.0 -2015-09-28 14:00:00,13360.0 -2015-09-28 15:00:00,13722.0 -2015-09-28 16:00:00,13881.0 -2015-09-28 17:00:00,13889.0 -2015-09-28 18:00:00,13794.0 -2015-09-28 19:00:00,13570.0 -2015-09-28 20:00:00,13371.0 -2015-09-28 21:00:00,13835.0 -2015-09-28 22:00:00,13461.0 -2015-09-28 23:00:00,12762.0 -2015-09-29 00:00:00,11705.0 -2015-09-27 01:00:00,9413.0 -2015-09-27 02:00:00,8905.0 -2015-09-27 03:00:00,8551.0 -2015-09-27 04:00:00,8260.0 -2015-09-27 05:00:00,8107.0 -2015-09-27 06:00:00,8048.0 -2015-09-27 07:00:00,8177.0 -2015-09-27 08:00:00,8330.0 -2015-09-27 09:00:00,8545.0 -2015-09-27 10:00:00,8908.0 -2015-09-27 11:00:00,9280.0 -2015-09-27 12:00:00,9616.0 -2015-09-27 13:00:00,9887.0 -2015-09-27 14:00:00,10103.0 -2015-09-27 15:00:00,10276.0 -2015-09-27 16:00:00,10483.0 -2015-09-27 17:00:00,10526.0 -2015-09-27 18:00:00,10705.0 -2015-09-27 19:00:00,10820.0 -2015-09-27 20:00:00,10932.0 -2015-09-27 21:00:00,11387.0 -2015-09-27 22:00:00,11195.0 -2015-09-27 23:00:00,10770.0 -2015-09-28 00:00:00,10132.0 -2015-09-26 01:00:00,9888.0 -2015-09-26 02:00:00,9202.0 -2015-09-26 03:00:00,8744.0 -2015-09-26 04:00:00,8454.0 -2015-09-26 05:00:00,8264.0 -2015-09-26 06:00:00,8204.0 -2015-09-26 07:00:00,8459.0 -2015-09-26 08:00:00,8819.0 -2015-09-26 09:00:00,9052.0 -2015-09-26 10:00:00,9682.0 -2015-09-26 11:00:00,10230.0 -2015-09-26 12:00:00,10680.0 -2015-09-26 13:00:00,10995.0 -2015-09-26 14:00:00,11221.0 -2015-09-26 15:00:00,11339.0 -2015-09-26 16:00:00,11494.0 -2015-09-26 17:00:00,11582.0 -2015-09-26 18:00:00,11602.0 -2015-09-26 19:00:00,11319.0 -2015-09-26 20:00:00,11068.0 -2015-09-26 21:00:00,11340.0 -2015-09-26 22:00:00,11086.0 -2015-09-26 23:00:00,10613.0 -2015-09-27 00:00:00,10029.0 -2015-09-25 01:00:00,9854.0 -2015-09-25 02:00:00,9273.0 -2015-09-25 03:00:00,8857.0 -2015-09-25 04:00:00,8597.0 -2015-09-25 05:00:00,8463.0 -2015-09-25 06:00:00,8603.0 -2015-09-25 07:00:00,9279.0 -2015-09-25 08:00:00,10296.0 -2015-09-25 09:00:00,10893.0 -2015-09-25 10:00:00,11485.0 -2015-09-25 11:00:00,11949.0 -2015-09-25 12:00:00,12338.0 -2015-09-25 13:00:00,12688.0 -2015-09-25 14:00:00,12868.0 -2015-09-25 15:00:00,13084.0 -2015-09-25 16:00:00,13153.0 -2015-09-25 17:00:00,13090.0 -2015-09-25 18:00:00,12942.0 -2015-09-25 19:00:00,12527.0 -2015-09-25 20:00:00,12114.0 -2015-09-25 21:00:00,12267.0 -2015-09-25 22:00:00,11922.0 -2015-09-25 23:00:00,11437.0 -2015-09-26 00:00:00,10659.0 -2015-09-24 01:00:00,9834.0 -2015-09-24 02:00:00,9227.0 -2015-09-24 03:00:00,8830.0 -2015-09-24 04:00:00,8561.0 -2015-09-24 05:00:00,8439.0 -2015-09-24 06:00:00,8558.0 -2015-09-24 07:00:00,9194.0 -2015-09-24 08:00:00,10237.0 -2015-09-24 09:00:00,10807.0 -2015-09-24 10:00:00,11315.0 -2015-09-24 11:00:00,11782.0 -2015-09-24 12:00:00,12261.0 -2015-09-24 13:00:00,12587.0 -2015-09-24 14:00:00,12842.0 -2015-09-24 15:00:00,13179.0 -2015-09-24 16:00:00,13325.0 -2015-09-24 17:00:00,13282.0 -2015-09-24 18:00:00,13142.0 -2015-09-24 19:00:00,12833.0 -2015-09-24 20:00:00,12442.0 -2015-09-24 21:00:00,12693.0 -2015-09-24 22:00:00,12292.0 -2015-09-24 23:00:00,11636.0 -2015-09-25 00:00:00,10724.0 -2015-09-23 01:00:00,9683.0 -2015-09-23 02:00:00,9083.0 -2015-09-23 03:00:00,8692.0 -2015-09-23 04:00:00,8429.0 -2015-09-23 05:00:00,8308.0 -2015-09-23 06:00:00,8474.0 -2015-09-23 07:00:00,9088.0 -2015-09-23 08:00:00,10084.0 -2015-09-23 09:00:00,10677.0 -2015-09-23 10:00:00,11311.0 -2015-09-23 11:00:00,11834.0 -2015-09-23 12:00:00,12322.0 -2015-09-23 13:00:00,12718.0 -2015-09-23 14:00:00,13028.0 -2015-09-23 15:00:00,13408.0 -2015-09-23 16:00:00,13607.0 -2015-09-23 17:00:00,13683.0 -2015-09-23 18:00:00,13562.0 -2015-09-23 19:00:00,13181.0 -2015-09-23 20:00:00,12694.0 -2015-09-23 21:00:00,12858.0 -2015-09-23 22:00:00,12421.0 -2015-09-23 23:00:00,11692.0 -2015-09-24 00:00:00,10739.0 -2015-09-22 01:00:00,9292.0 -2015-09-22 02:00:00,8749.0 -2015-09-22 03:00:00,8378.0 -2015-09-22 04:00:00,8168.0 -2015-09-22 05:00:00,8050.0 -2015-09-22 06:00:00,8208.0 -2015-09-22 07:00:00,8839.0 -2015-09-22 08:00:00,9831.0 -2015-09-22 09:00:00,10421.0 -2015-09-22 10:00:00,10969.0 -2015-09-22 11:00:00,11444.0 -2015-09-22 12:00:00,11913.0 -2015-09-22 13:00:00,12231.0 -2015-09-22 14:00:00,12524.0 -2015-09-22 15:00:00,12850.0 -2015-09-22 16:00:00,13054.0 -2015-09-22 17:00:00,13152.0 -2015-09-22 18:00:00,13078.0 -2015-09-22 19:00:00,12800.0 -2015-09-22 20:00:00,12443.0 -2015-09-22 21:00:00,12674.0 -2015-09-22 22:00:00,12287.0 -2015-09-22 23:00:00,11562.0 -2015-09-23 00:00:00,10568.0 -2015-09-21 01:00:00,8632.0 -2015-09-21 02:00:00,8200.0 -2015-09-21 03:00:00,7947.0 -2015-09-21 04:00:00,7800.0 -2015-09-21 05:00:00,7760.0 -2015-09-21 06:00:00,7961.0 -2015-09-21 07:00:00,8664.0 -2015-09-21 08:00:00,9689.0 -2015-09-21 09:00:00,10287.0 -2015-09-21 10:00:00,10835.0 -2015-09-21 11:00:00,11221.0 -2015-09-21 12:00:00,11595.0 -2015-09-21 13:00:00,11848.0 -2015-09-21 14:00:00,12037.0 -2015-09-21 15:00:00,12241.0 -2015-09-21 16:00:00,12331.0 -2015-09-21 17:00:00,12337.0 -2015-09-21 18:00:00,12308.0 -2015-09-21 19:00:00,12081.0 -2015-09-21 20:00:00,11695.0 -2015-09-21 21:00:00,11943.0 -2015-09-21 22:00:00,11654.0 -2015-09-21 23:00:00,11014.0 -2015-09-22 00:00:00,10101.0 -2015-09-20 01:00:00,8784.0 -2015-09-20 02:00:00,8289.0 -2015-09-20 03:00:00,7956.0 -2015-09-20 04:00:00,7709.0 -2015-09-20 05:00:00,7580.0 -2015-09-20 06:00:00,7503.0 -2015-09-20 07:00:00,7581.0 -2015-09-20 08:00:00,7683.0 -2015-09-20 09:00:00,7798.0 -2015-09-20 10:00:00,8254.0 -2015-09-20 11:00:00,8693.0 -2015-09-20 12:00:00,9059.0 -2015-09-20 13:00:00,9337.0 -2015-09-20 14:00:00,9504.0 -2015-09-20 15:00:00,9627.0 -2015-09-20 16:00:00,9766.0 -2015-09-20 17:00:00,9904.0 -2015-09-20 18:00:00,9994.0 -2015-09-20 19:00:00,10033.0 -2015-09-20 20:00:00,9960.0 -2015-09-20 21:00:00,10414.0 -2015-09-20 22:00:00,10233.0 -2015-09-20 23:00:00,9785.0 -2015-09-21 00:00:00,9194.0 -2015-09-19 01:00:00,10918.0 -2015-09-19 02:00:00,10145.0 -2015-09-19 03:00:00,9629.0 -2015-09-19 04:00:00,9233.0 -2015-09-19 05:00:00,8955.0 -2015-09-19 06:00:00,8796.0 -2015-09-19 07:00:00,8947.0 -2015-09-19 08:00:00,9175.0 -2015-09-19 09:00:00,9364.0 -2015-09-19 10:00:00,9791.0 -2015-09-19 11:00:00,10160.0 -2015-09-19 12:00:00,10374.0 -2015-09-19 13:00:00,10544.0 -2015-09-19 14:00:00,10625.0 -2015-09-19 15:00:00,10642.0 -2015-09-19 16:00:00,10588.0 -2015-09-19 17:00:00,10642.0 -2015-09-19 18:00:00,10611.0 -2015-09-19 19:00:00,10506.0 -2015-09-19 20:00:00,10252.0 -2015-09-19 21:00:00,10519.0 -2015-09-19 22:00:00,10320.0 -2015-09-19 23:00:00,9974.0 -2015-09-20 00:00:00,9358.0 -2015-09-18 01:00:00,11785.0 -2015-09-18 02:00:00,11022.0 -2015-09-18 03:00:00,10346.0 -2015-09-18 04:00:00,9923.0 -2015-09-18 05:00:00,9761.0 -2015-09-18 06:00:00,9878.0 -2015-09-18 07:00:00,10422.0 -2015-09-18 08:00:00,11527.0 -2015-09-18 09:00:00,12233.0 -2015-09-18 10:00:00,12779.0 -2015-09-18 11:00:00,13353.0 -2015-09-18 12:00:00,13840.0 -2015-09-18 13:00:00,13989.0 -2015-09-18 14:00:00,14082.0 -2015-09-18 15:00:00,14307.0 -2015-09-18 16:00:00,14338.0 -2015-09-18 17:00:00,14220.0 -2015-09-18 18:00:00,14201.0 -2015-09-18 19:00:00,14017.0 -2015-09-18 20:00:00,13752.0 -2015-09-18 21:00:00,13641.0 -2015-09-18 22:00:00,13316.0 -2015-09-18 23:00:00,12650.0 -2015-09-19 00:00:00,11793.0 -2015-09-17 01:00:00,10917.0 -2015-09-17 02:00:00,10131.0 -2015-09-17 03:00:00,9560.0 -2015-09-17 04:00:00,9220.0 -2015-09-17 05:00:00,9075.0 -2015-09-17 06:00:00,9172.0 -2015-09-17 07:00:00,9811.0 -2015-09-17 08:00:00,10854.0 -2015-09-17 09:00:00,11546.0 -2015-09-17 10:00:00,12165.0 -2015-09-17 11:00:00,12773.0 -2015-09-17 12:00:00,13541.0 -2015-09-17 13:00:00,14234.0 -2015-09-17 14:00:00,14970.0 -2015-09-17 15:00:00,15586.0 -2015-09-17 16:00:00,15972.0 -2015-09-17 17:00:00,16002.0 -2015-09-17 18:00:00,15559.0 -2015-09-17 19:00:00,14849.0 -2015-09-17 20:00:00,14516.0 -2015-09-17 21:00:00,14751.0 -2015-09-17 22:00:00,14529.0 -2015-09-17 23:00:00,13925.0 -2015-09-18 00:00:00,12809.0 -2015-09-16 01:00:00,10358.0 -2015-09-16 02:00:00,9617.0 -2015-09-16 03:00:00,9181.0 -2015-09-16 04:00:00,8879.0 -2015-09-16 05:00:00,8753.0 -2015-09-16 06:00:00,8876.0 -2015-09-16 07:00:00,9482.0 -2015-09-16 08:00:00,10467.0 -2015-09-16 09:00:00,11103.0 -2015-09-16 10:00:00,11767.0 -2015-09-16 11:00:00,12319.0 -2015-09-16 12:00:00,12967.0 -2015-09-16 13:00:00,13444.0 -2015-09-16 14:00:00,13934.0 -2015-09-16 15:00:00,14471.0 -2015-09-16 16:00:00,14870.0 -2015-09-16 17:00:00,15143.0 -2015-09-16 18:00:00,15219.0 -2015-09-16 19:00:00,14905.0 -2015-09-16 20:00:00,14290.0 -2015-09-16 21:00:00,14285.0 -2015-09-16 22:00:00,13916.0 -2015-09-16 23:00:00,13074.0 -2015-09-17 00:00:00,11994.0 -2015-09-15 01:00:00,10184.0 -2015-09-15 02:00:00,9529.0 -2015-09-15 03:00:00,9123.0 -2015-09-15 04:00:00,8812.0 -2015-09-15 05:00:00,8699.0 -2015-09-15 06:00:00,8825.0 -2015-09-15 07:00:00,9451.0 -2015-09-15 08:00:00,10471.0 -2015-09-15 09:00:00,11109.0 -2015-09-15 10:00:00,11650.0 -2015-09-15 11:00:00,12198.0 -2015-09-15 12:00:00,12730.0 -2015-09-15 13:00:00,13183.0 -2015-09-15 14:00:00,13610.0 -2015-09-15 15:00:00,14109.0 -2015-09-15 16:00:00,14430.0 -2015-09-15 17:00:00,14606.0 -2015-09-15 18:00:00,14627.0 -2015-09-15 19:00:00,14329.0 -2015-09-15 20:00:00,13661.0 -2015-09-15 21:00:00,13662.0 -2015-09-15 22:00:00,13330.0 -2015-09-15 23:00:00,12512.0 -2015-09-16 00:00:00,11417.0 -2015-09-14 01:00:00,8512.0 -2015-09-14 02:00:00,8109.0 -2015-09-14 03:00:00,7881.0 -2015-09-14 04:00:00,7727.0 -2015-09-14 05:00:00,7722.0 -2015-09-14 06:00:00,7953.0 -2015-09-14 07:00:00,8653.0 -2015-09-14 08:00:00,9638.0 -2015-09-14 09:00:00,10261.0 -2015-09-14 10:00:00,10764.0 -2015-09-14 11:00:00,11158.0 -2015-09-14 12:00:00,11551.0 -2015-09-14 13:00:00,11869.0 -2015-09-14 14:00:00,12148.0 -2015-09-14 15:00:00,12522.0 -2015-09-14 16:00:00,12788.0 -2015-09-14 17:00:00,13000.0 -2015-09-14 18:00:00,13133.0 -2015-09-14 19:00:00,13038.0 -2015-09-14 20:00:00,12716.0 -2015-09-14 21:00:00,13015.0 -2015-09-14 22:00:00,12835.0 -2015-09-14 23:00:00,12191.0 -2015-09-15 00:00:00,11195.0 -2015-09-13 01:00:00,8400.0 -2015-09-13 02:00:00,7981.0 -2015-09-13 03:00:00,7667.0 -2015-09-13 04:00:00,7517.0 -2015-09-13 05:00:00,7376.0 -2015-09-13 06:00:00,7396.0 -2015-09-13 07:00:00,7464.0 -2015-09-13 08:00:00,7570.0 -2015-09-13 09:00:00,7667.0 -2015-09-13 10:00:00,8137.0 -2015-09-13 11:00:00,8528.0 -2015-09-13 12:00:00,8877.0 -2015-09-13 13:00:00,9086.0 -2015-09-13 14:00:00,9218.0 -2015-09-13 15:00:00,9254.0 -2015-09-13 16:00:00,9337.0 -2015-09-13 17:00:00,9383.0 -2015-09-13 18:00:00,9531.0 -2015-09-13 19:00:00,9586.0 -2015-09-13 20:00:00,9545.0 -2015-09-13 21:00:00,9989.0 -2015-09-13 22:00:00,9980.0 -2015-09-13 23:00:00,9638.0 -2015-09-14 00:00:00,9048.0 -2015-09-12 01:00:00,9139.0 -2015-09-12 02:00:00,8537.0 -2015-09-12 03:00:00,8214.0 -2015-09-12 04:00:00,7945.0 -2015-09-12 05:00:00,7842.0 -2015-09-12 06:00:00,7839.0 -2015-09-12 07:00:00,8073.0 -2015-09-12 08:00:00,8354.0 -2015-09-12 09:00:00,8684.0 -2015-09-12 10:00:00,9072.0 -2015-09-12 11:00:00,9447.0 -2015-09-12 12:00:00,9662.0 -2015-09-12 13:00:00,9660.0 -2015-09-12 14:00:00,9615.0 -2015-09-12 15:00:00,9542.0 -2015-09-12 16:00:00,9472.0 -2015-09-12 17:00:00,9410.0 -2015-09-12 18:00:00,9407.0 -2015-09-12 19:00:00,9327.0 -2015-09-12 20:00:00,9292.0 -2015-09-12 21:00:00,9687.0 -2015-09-12 22:00:00,9754.0 -2015-09-12 23:00:00,9419.0 -2015-09-13 00:00:00,8945.0 -2015-09-11 01:00:00,9626.0 -2015-09-11 02:00:00,9074.0 -2015-09-11 03:00:00,8753.0 -2015-09-11 04:00:00,8547.0 -2015-09-11 05:00:00,8467.0 -2015-09-11 06:00:00,8638.0 -2015-09-11 07:00:00,9304.0 -2015-09-11 08:00:00,10379.0 -2015-09-11 09:00:00,11036.0 -2015-09-11 10:00:00,11323.0 -2015-09-11 11:00:00,11569.0 -2015-09-11 12:00:00,11817.0 -2015-09-11 13:00:00,11860.0 -2015-09-11 14:00:00,11881.0 -2015-09-11 15:00:00,11954.0 -2015-09-11 16:00:00,11883.0 -2015-09-11 17:00:00,11657.0 -2015-09-11 18:00:00,11378.0 -2015-09-11 19:00:00,11060.0 -2015-09-11 20:00:00,10785.0 -2015-09-11 21:00:00,11024.0 -2015-09-11 22:00:00,10959.0 -2015-09-11 23:00:00,10522.0 -2015-09-12 00:00:00,9822.0 -2015-09-10 01:00:00,10074.0 -2015-09-10 02:00:00,9440.0 -2015-09-10 03:00:00,8996.0 -2015-09-10 04:00:00,8703.0 -2015-09-10 05:00:00,8632.0 -2015-09-10 06:00:00,8766.0 -2015-09-10 07:00:00,9401.0 -2015-09-10 08:00:00,10349.0 -2015-09-10 09:00:00,11060.0 -2015-09-10 10:00:00,11645.0 -2015-09-10 11:00:00,11995.0 -2015-09-10 12:00:00,12269.0 -2015-09-10 13:00:00,12572.0 -2015-09-10 14:00:00,12654.0 -2015-09-10 15:00:00,12579.0 -2015-09-10 16:00:00,12290.0 -2015-09-10 17:00:00,12103.0 -2015-09-10 18:00:00,12094.0 -2015-09-10 19:00:00,11954.0 -2015-09-10 20:00:00,11740.0 -2015-09-10 21:00:00,11983.0 -2015-09-10 22:00:00,11939.0 -2015-09-10 23:00:00,11337.0 -2015-09-11 00:00:00,10460.0 -2015-09-09 01:00:00,11438.0 -2015-09-09 02:00:00,10611.0 -2015-09-09 03:00:00,10071.0 -2015-09-09 04:00:00,9748.0 -2015-09-09 05:00:00,9605.0 -2015-09-09 06:00:00,9744.0 -2015-09-09 07:00:00,10404.0 -2015-09-09 08:00:00,11516.0 -2015-09-09 09:00:00,12114.0 -2015-09-09 10:00:00,12496.0 -2015-09-09 11:00:00,12776.0 -2015-09-09 12:00:00,13101.0 -2015-09-09 13:00:00,13382.0 -2015-09-09 14:00:00,13684.0 -2015-09-09 15:00:00,14025.0 -2015-09-09 16:00:00,14244.0 -2015-09-09 17:00:00,14503.0 -2015-09-09 18:00:00,14600.0 -2015-09-09 19:00:00,14287.0 -2015-09-09 20:00:00,13607.0 -2015-09-09 21:00:00,13353.0 -2015-09-09 22:00:00,13023.0 -2015-09-09 23:00:00,12198.0 -2015-09-10 00:00:00,11085.0 -2015-09-08 01:00:00,12794.0 -2015-09-08 02:00:00,12014.0 -2015-09-08 03:00:00,11461.0 -2015-09-08 04:00:00,11056.0 -2015-09-08 05:00:00,10897.0 -2015-09-08 06:00:00,11082.0 -2015-09-08 07:00:00,11898.0 -2015-09-08 08:00:00,13165.0 -2015-09-08 09:00:00,14029.0 -2015-09-08 10:00:00,14638.0 -2015-09-08 11:00:00,15361.0 -2015-09-08 12:00:00,16061.0 -2015-09-08 13:00:00,16330.0 -2015-09-08 14:00:00,16215.0 -2015-09-08 15:00:00,15658.0 -2015-09-08 16:00:00,15272.0 -2015-09-08 17:00:00,15047.0 -2015-09-08 18:00:00,14978.0 -2015-09-08 19:00:00,14818.0 -2015-09-08 20:00:00,14582.0 -2015-09-08 21:00:00,14859.0 -2015-09-08 22:00:00,14521.0 -2015-09-08 23:00:00,13722.0 -2015-09-09 00:00:00,12553.0 -2015-09-07 01:00:00,13037.0 -2015-09-07 02:00:00,12235.0 -2015-09-07 03:00:00,11483.0 -2015-09-07 04:00:00,10992.0 -2015-09-07 05:00:00,10635.0 -2015-09-07 06:00:00,10505.0 -2015-09-07 07:00:00,10648.0 -2015-09-07 08:00:00,10725.0 -2015-09-07 09:00:00,10860.0 -2015-09-07 10:00:00,11455.0 -2015-09-07 11:00:00,12636.0 -2015-09-07 12:00:00,14113.0 -2015-09-07 13:00:00,15262.0 -2015-09-07 14:00:00,15865.0 -2015-09-07 15:00:00,16122.0 -2015-09-07 16:00:00,16484.0 -2015-09-07 17:00:00,16611.0 -2015-09-07 18:00:00,16455.0 -2015-09-07 19:00:00,16513.0 -2015-09-07 20:00:00,16108.0 -2015-09-07 21:00:00,16120.0 -2015-09-07 22:00:00,15874.0 -2015-09-07 23:00:00,15046.0 -2015-09-08 00:00:00,13855.0 -2015-09-06 01:00:00,11880.0 -2015-09-06 02:00:00,11023.0 -2015-09-06 03:00:00,10520.0 -2015-09-06 04:00:00,10082.0 -2015-09-06 05:00:00,9769.0 -2015-09-06 06:00:00,9619.0 -2015-09-06 07:00:00,9619.0 -2015-09-06 08:00:00,9568.0 -2015-09-06 09:00:00,9958.0 -2015-09-06 10:00:00,11067.0 -2015-09-06 11:00:00,12380.0 -2015-09-06 12:00:00,13816.0 -2015-09-06 13:00:00,15037.0 -2015-09-06 14:00:00,16022.0 -2015-09-06 15:00:00,16585.0 -2015-09-06 16:00:00,17105.0 -2015-09-06 17:00:00,17322.0 -2015-09-06 18:00:00,17341.0 -2015-09-06 19:00:00,17073.0 -2015-09-06 20:00:00,16372.0 -2015-09-06 21:00:00,15974.0 -2015-09-06 22:00:00,15628.0 -2015-09-06 23:00:00,14930.0 -2015-09-07 00:00:00,14008.0 -2015-09-05 01:00:00,12364.0 -2015-09-05 02:00:00,11472.0 -2015-09-05 03:00:00,10801.0 -2015-09-05 04:00:00,10320.0 -2015-09-05 05:00:00,10008.0 -2015-09-05 06:00:00,9934.0 -2015-09-05 07:00:00,10091.0 -2015-09-05 08:00:00,10264.0 -2015-09-05 09:00:00,10682.0 -2015-09-05 10:00:00,11407.0 -2015-09-05 11:00:00,12143.0 -2015-09-05 12:00:00,12745.0 -2015-09-05 13:00:00,13432.0 -2015-09-05 14:00:00,13958.0 -2015-09-05 15:00:00,14485.0 -2015-09-05 16:00:00,14871.0 -2015-09-05 17:00:00,15157.0 -2015-09-05 18:00:00,15266.0 -2015-09-05 19:00:00,15099.0 -2015-09-05 20:00:00,14578.0 -2015-09-05 21:00:00,14402.0 -2015-09-05 22:00:00,14166.0 -2015-09-05 23:00:00,13568.0 -2015-09-06 00:00:00,12739.0 -2015-09-04 01:00:00,13277.0 -2015-09-04 02:00:00,12222.0 -2015-09-04 03:00:00,11465.0 -2015-09-04 04:00:00,10946.0 -2015-09-04 05:00:00,10635.0 -2015-09-04 06:00:00,10652.0 -2015-09-04 07:00:00,11260.0 -2015-09-04 08:00:00,12167.0 -2015-09-04 09:00:00,13031.0 -2015-09-04 10:00:00,13893.0 -2015-09-04 11:00:00,14666.0 -2015-09-04 12:00:00,15554.0 -2015-09-04 13:00:00,16333.0 -2015-09-04 14:00:00,17062.0 -2015-09-04 15:00:00,17782.0 -2015-09-04 16:00:00,18180.0 -2015-09-04 17:00:00,18234.0 -2015-09-04 18:00:00,18114.0 -2015-09-04 19:00:00,17483.0 -2015-09-04 20:00:00,16441.0 -2015-09-04 21:00:00,15751.0 -2015-09-04 22:00:00,15336.0 -2015-09-04 23:00:00,14502.0 -2015-09-05 00:00:00,13439.0 -2015-09-03 01:00:00,13869.0 -2015-09-03 02:00:00,12823.0 -2015-09-03 03:00:00,12101.0 -2015-09-03 04:00:00,11538.0 -2015-09-03 05:00:00,11255.0 -2015-09-03 06:00:00,11326.0 -2015-09-03 07:00:00,11965.0 -2015-09-03 08:00:00,12897.0 -2015-09-03 09:00:00,13867.0 -2015-09-03 10:00:00,14958.0 -2015-09-03 11:00:00,16086.0 -2015-09-03 12:00:00,17254.0 -2015-09-03 13:00:00,18196.0 -2015-09-03 14:00:00,18917.0 -2015-09-03 15:00:00,19491.0 -2015-09-03 16:00:00,19847.0 -2015-09-03 17:00:00,20099.0 -2015-09-03 18:00:00,20162.0 -2015-09-03 19:00:00,19936.0 -2015-09-03 20:00:00,19033.0 -2015-09-03 21:00:00,18169.0 -2015-09-03 22:00:00,17436.0 -2015-09-03 23:00:00,16230.0 -2015-09-04 00:00:00,14712.0 -2015-09-02 01:00:00,14015.0 -2015-09-02 02:00:00,12825.0 -2015-09-02 03:00:00,11998.0 -2015-09-02 04:00:00,11420.0 -2015-09-02 05:00:00,11093.0 -2015-09-02 06:00:00,11146.0 -2015-09-02 07:00:00,11754.0 -2015-09-02 08:00:00,12731.0 -2015-09-02 09:00:00,13634.0 -2015-09-02 10:00:00,14591.0 -2015-09-02 11:00:00,15613.0 -2015-09-02 12:00:00,16813.0 -2015-09-02 13:00:00,17919.0 -2015-09-02 14:00:00,18759.0 -2015-09-02 15:00:00,19388.0 -2015-09-02 16:00:00,19715.0 -2015-09-02 17:00:00,19705.0 -2015-09-02 18:00:00,19507.0 -2015-09-02 19:00:00,19066.0 -2015-09-02 20:00:00,18259.0 -2015-09-02 21:00:00,17925.0 -2015-09-02 22:00:00,17645.0 -2015-09-02 23:00:00,16644.0 -2015-09-03 00:00:00,15142.0 -2015-09-01 01:00:00,12248.0 -2015-09-01 02:00:00,11352.0 -2015-09-01 03:00:00,10740.0 -2015-09-01 04:00:00,10334.0 -2015-09-01 05:00:00,10112.0 -2015-09-01 06:00:00,10298.0 -2015-09-01 07:00:00,11001.0 -2015-09-01 08:00:00,12053.0 -2015-09-01 09:00:00,13007.0 -2015-09-01 10:00:00,13986.0 -2015-09-01 11:00:00,15033.0 -2015-09-01 12:00:00,16178.0 -2015-09-01 13:00:00,17200.0 -2015-09-01 14:00:00,18060.0 -2015-09-01 15:00:00,18593.0 -2015-09-01 16:00:00,18983.0 -2015-09-01 17:00:00,19301.0 -2015-09-01 18:00:00,19424.0 -2015-09-01 19:00:00,19185.0 -2015-09-01 20:00:00,18560.0 -2015-09-01 21:00:00,18199.0 -2015-09-01 22:00:00,17888.0 -2015-09-01 23:00:00,16917.0 -2015-09-02 00:00:00,15499.0 -2015-08-31 01:00:00,9877.0 -2015-08-31 02:00:00,9319.0 -2015-08-31 03:00:00,8928.0 -2015-08-31 04:00:00,8688.0 -2015-08-31 05:00:00,8624.0 -2015-08-31 06:00:00,8858.0 -2015-08-31 07:00:00,9557.0 -2015-08-31 08:00:00,10550.0 -2015-08-31 09:00:00,11333.0 -2015-08-31 10:00:00,12057.0 -2015-08-31 11:00:00,12802.0 -2015-08-31 12:00:00,13654.0 -2015-08-31 13:00:00,14434.0 -2015-08-31 14:00:00,15184.0 -2015-08-31 15:00:00,15921.0 -2015-08-31 16:00:00,16454.0 -2015-08-31 17:00:00,16798.0 -2015-08-31 18:00:00,17001.0 -2015-08-31 19:00:00,16821.0 -2015-08-31 20:00:00,16236.0 -2015-08-31 21:00:00,15950.0 -2015-08-31 22:00:00,15727.0 -2015-08-31 23:00:00,14774.0 -2015-09-01 00:00:00,13495.0 -2015-08-30 01:00:00,9720.0 -2015-08-30 02:00:00,9102.0 -2015-08-30 03:00:00,8752.0 -2015-08-30 04:00:00,8478.0 -2015-08-30 05:00:00,8323.0 -2015-08-30 06:00:00,8258.0 -2015-08-30 07:00:00,8336.0 -2015-08-30 08:00:00,8355.0 -2015-08-30 09:00:00,8555.0 -2015-08-30 10:00:00,9049.0 -2015-08-30 11:00:00,9603.0 -2015-08-30 12:00:00,10090.0 -2015-08-30 13:00:00,10565.0 -2015-08-30 14:00:00,10827.0 -2015-08-30 15:00:00,11061.0 -2015-08-30 16:00:00,11326.0 -2015-08-30 17:00:00,11646.0 -2015-08-30 18:00:00,11935.0 -2015-08-30 19:00:00,11994.0 -2015-08-30 20:00:00,11813.0 -2015-08-30 21:00:00,11893.0 -2015-08-30 22:00:00,12010.0 -2015-08-30 23:00:00,11484.0 -2015-08-31 00:00:00,10700.0 -2015-08-29 01:00:00,10094.0 -2015-08-29 02:00:00,9471.0 -2015-08-29 03:00:00,9033.0 -2015-08-29 04:00:00,8718.0 -2015-08-29 05:00:00,8583.0 -2015-08-29 06:00:00,8556.0 -2015-08-29 07:00:00,8778.0 -2015-08-29 08:00:00,9152.0 -2015-08-29 09:00:00,9505.0 -2015-08-29 10:00:00,10016.0 -2015-08-29 11:00:00,10493.0 -2015-08-29 12:00:00,10790.0 -2015-08-29 13:00:00,10974.0 -2015-08-29 14:00:00,10980.0 -2015-08-29 15:00:00,10961.0 -2015-08-29 16:00:00,10919.0 -2015-08-29 17:00:00,10883.0 -2015-08-29 18:00:00,10865.0 -2015-08-29 19:00:00,10873.0 -2015-08-29 20:00:00,10899.0 -2015-08-29 21:00:00,11183.0 -2015-08-29 22:00:00,11219.0 -2015-08-29 23:00:00,10920.0 -2015-08-30 00:00:00,10303.0 -2015-08-28 01:00:00,9696.0 -2015-08-28 02:00:00,9121.0 -2015-08-28 03:00:00,8732.0 -2015-08-28 04:00:00,8493.0 -2015-08-28 05:00:00,8367.0 -2015-08-28 06:00:00,8550.0 -2015-08-28 07:00:00,9145.0 -2015-08-28 08:00:00,9975.0 -2015-08-28 09:00:00,10741.0 -2015-08-28 10:00:00,11337.0 -2015-08-28 11:00:00,11768.0 -2015-08-28 12:00:00,12148.0 -2015-08-28 13:00:00,12323.0 -2015-08-28 14:00:00,12333.0 -2015-08-28 15:00:00,12365.0 -2015-08-28 16:00:00,12275.0 -2015-08-28 17:00:00,12155.0 -2015-08-28 18:00:00,11998.0 -2015-08-28 19:00:00,11807.0 -2015-08-28 20:00:00,11661.0 -2015-08-28 21:00:00,11835.0 -2015-08-28 22:00:00,11849.0 -2015-08-28 23:00:00,11549.0 -2015-08-29 00:00:00,10840.0 -2015-08-27 01:00:00,9320.0 -2015-08-27 02:00:00,8765.0 -2015-08-27 03:00:00,8448.0 -2015-08-27 04:00:00,8219.0 -2015-08-27 05:00:00,8125.0 -2015-08-27 06:00:00,8277.0 -2015-08-27 07:00:00,8855.0 -2015-08-27 08:00:00,9698.0 -2015-08-27 09:00:00,10432.0 -2015-08-27 10:00:00,10942.0 -2015-08-27 11:00:00,11326.0 -2015-08-27 12:00:00,11609.0 -2015-08-27 13:00:00,11819.0 -2015-08-27 14:00:00,12005.0 -2015-08-27 15:00:00,12271.0 -2015-08-27 16:00:00,12434.0 -2015-08-27 17:00:00,12494.0 -2015-08-27 18:00:00,12474.0 -2015-08-27 19:00:00,12162.0 -2015-08-27 20:00:00,11747.0 -2015-08-27 21:00:00,11792.0 -2015-08-27 22:00:00,11966.0 -2015-08-27 23:00:00,11412.0 -2015-08-28 00:00:00,10545.0 -2015-08-26 01:00:00,9588.0 -2015-08-26 02:00:00,8988.0 -2015-08-26 03:00:00,8578.0 -2015-08-26 04:00:00,8336.0 -2015-08-26 05:00:00,8222.0 -2015-08-26 06:00:00,8386.0 -2015-08-26 07:00:00,9002.0 -2015-08-26 08:00:00,9879.0 -2015-08-26 09:00:00,10625.0 -2015-08-26 10:00:00,11062.0 -2015-08-26 11:00:00,11280.0 -2015-08-26 12:00:00,11454.0 -2015-08-26 13:00:00,11531.0 -2015-08-26 14:00:00,11533.0 -2015-08-26 15:00:00,11607.0 -2015-08-26 16:00:00,11590.0 -2015-08-26 17:00:00,11512.0 -2015-08-26 18:00:00,11384.0 -2015-08-26 19:00:00,11245.0 -2015-08-26 20:00:00,11065.0 -2015-08-26 21:00:00,11234.0 -2015-08-26 22:00:00,11419.0 -2015-08-26 23:00:00,10922.0 -2015-08-27 00:00:00,10104.0 -2015-08-25 01:00:00,9533.0 -2015-08-25 02:00:00,8996.0 -2015-08-25 03:00:00,8649.0 -2015-08-25 04:00:00,8431.0 -2015-08-25 05:00:00,8318.0 -2015-08-25 06:00:00,8508.0 -2015-08-25 07:00:00,9109.0 -2015-08-25 08:00:00,9940.0 -2015-08-25 09:00:00,10594.0 -2015-08-25 10:00:00,11065.0 -2015-08-25 11:00:00,11344.0 -2015-08-25 12:00:00,11557.0 -2015-08-25 13:00:00,11672.0 -2015-08-25 14:00:00,11770.0 -2015-08-25 15:00:00,11922.0 -2015-08-25 16:00:00,11756.0 -2015-08-25 17:00:00,11683.0 -2015-08-25 18:00:00,11796.0 -2015-08-25 19:00:00,11659.0 -2015-08-25 20:00:00,11401.0 -2015-08-25 21:00:00,11583.0 -2015-08-25 22:00:00,11704.0 -2015-08-25 23:00:00,11203.0 -2015-08-26 00:00:00,10389.0 -2015-08-24 01:00:00,9694.0 -2015-08-24 02:00:00,9123.0 -2015-08-24 03:00:00,8729.0 -2015-08-24 04:00:00,8490.0 -2015-08-24 05:00:00,8414.0 -2015-08-24 06:00:00,8591.0 -2015-08-24 07:00:00,9200.0 -2015-08-24 08:00:00,9884.0 -2015-08-24 09:00:00,10642.0 -2015-08-24 10:00:00,11211.0 -2015-08-24 11:00:00,11712.0 -2015-08-24 12:00:00,12166.0 -2015-08-24 13:00:00,12500.0 -2015-08-24 14:00:00,12599.0 -2015-08-24 15:00:00,12770.0 -2015-08-24 16:00:00,12817.0 -2015-08-24 17:00:00,12705.0 -2015-08-24 18:00:00,12513.0 -2015-08-24 19:00:00,12146.0 -2015-08-24 20:00:00,11745.0 -2015-08-24 21:00:00,11779.0 -2015-08-24 22:00:00,11777.0 -2015-08-24 23:00:00,11121.0 -2015-08-25 00:00:00,10337.0 -2015-08-23 01:00:00,10591.0 -2015-08-23 02:00:00,9984.0 -2015-08-23 03:00:00,9535.0 -2015-08-23 04:00:00,9215.0 -2015-08-23 05:00:00,9000.0 -2015-08-23 06:00:00,8910.0 -2015-08-23 07:00:00,8969.0 -2015-08-23 08:00:00,8872.0 -2015-08-23 09:00:00,9296.0 -2015-08-23 10:00:00,9839.0 -2015-08-23 11:00:00,10436.0 -2015-08-23 12:00:00,10880.0 -2015-08-23 13:00:00,11188.0 -2015-08-23 14:00:00,11476.0 -2015-08-23 15:00:00,11743.0 -2015-08-23 16:00:00,12050.0 -2015-08-23 17:00:00,12206.0 -2015-08-23 18:00:00,12192.0 -2015-08-23 19:00:00,12008.0 -2015-08-23 20:00:00,11664.0 -2015-08-23 21:00:00,11450.0 -2015-08-23 22:00:00,11607.0 -2015-08-23 23:00:00,11147.0 -2015-08-24 00:00:00,10391.0 -2015-08-22 01:00:00,11210.0 -2015-08-22 02:00:00,10414.0 -2015-08-22 03:00:00,9764.0 -2015-08-22 04:00:00,9370.0 -2015-08-22 05:00:00,9029.0 -2015-08-22 06:00:00,8999.0 -2015-08-22 07:00:00,9077.0 -2015-08-22 08:00:00,9215.0 -2015-08-22 09:00:00,9780.0 -2015-08-22 10:00:00,10663.0 -2015-08-22 11:00:00,11488.0 -2015-08-22 12:00:00,12207.0 -2015-08-22 13:00:00,12700.0 -2015-08-22 14:00:00,13003.0 -2015-08-22 15:00:00,13214.0 -2015-08-22 16:00:00,13338.0 -2015-08-22 17:00:00,13456.0 -2015-08-22 18:00:00,13555.0 -2015-08-22 19:00:00,13398.0 -2015-08-22 20:00:00,12890.0 -2015-08-22 21:00:00,12426.0 -2015-08-22 22:00:00,12427.0 -2015-08-22 23:00:00,12014.0 -2015-08-23 00:00:00,11276.0 -2015-08-21 01:00:00,9870.0 -2015-08-21 02:00:00,9256.0 -2015-08-21 03:00:00,8861.0 -2015-08-21 04:00:00,8583.0 -2015-08-21 05:00:00,8476.0 -2015-08-21 06:00:00,8611.0 -2015-08-21 07:00:00,9200.0 -2015-08-21 08:00:00,9917.0 -2015-08-21 09:00:00,10775.0 -2015-08-21 10:00:00,11551.0 -2015-08-21 11:00:00,12183.0 -2015-08-21 12:00:00,12720.0 -2015-08-21 13:00:00,13157.0 -2015-08-21 14:00:00,13585.0 -2015-08-21 15:00:00,14109.0 -2015-08-21 16:00:00,14520.0 -2015-08-21 17:00:00,14707.0 -2015-08-21 18:00:00,14749.0 -2015-08-21 19:00:00,14662.0 -2015-08-21 20:00:00,14185.0 -2015-08-21 21:00:00,13716.0 -2015-08-21 22:00:00,13689.0 -2015-08-21 23:00:00,13184.0 -2015-08-22 00:00:00,12239.0 -2015-08-20 01:00:00,10278.0 -2015-08-20 02:00:00,9646.0 -2015-08-20 03:00:00,9184.0 -2015-08-20 04:00:00,8885.0 -2015-08-20 05:00:00,8810.0 -2015-08-20 06:00:00,8963.0 -2015-08-20 07:00:00,9566.0 -2015-08-20 08:00:00,10304.0 -2015-08-20 09:00:00,10991.0 -2015-08-20 10:00:00,11458.0 -2015-08-20 11:00:00,11714.0 -2015-08-20 12:00:00,11992.0 -2015-08-20 13:00:00,12148.0 -2015-08-20 14:00:00,12213.0 -2015-08-20 15:00:00,12521.0 -2015-08-20 16:00:00,12646.0 -2015-08-20 17:00:00,12699.0 -2015-08-20 18:00:00,12703.0 -2015-08-20 19:00:00,12529.0 -2015-08-20 20:00:00,12143.0 -2015-08-20 21:00:00,11919.0 -2015-08-20 22:00:00,12121.0 -2015-08-20 23:00:00,11637.0 -2015-08-21 00:00:00,10774.0 -2015-08-19 01:00:00,12117.0 -2015-08-19 02:00:00,11308.0 -2015-08-19 03:00:00,10737.0 -2015-08-19 04:00:00,10394.0 -2015-08-19 05:00:00,10220.0 -2015-08-19 06:00:00,10361.0 -2015-08-19 07:00:00,11098.0 -2015-08-19 08:00:00,12094.0 -2015-08-19 09:00:00,12950.0 -2015-08-19 10:00:00,13526.0 -2015-08-19 11:00:00,13942.0 -2015-08-19 12:00:00,14207.0 -2015-08-19 13:00:00,14477.0 -2015-08-19 14:00:00,14667.0 -2015-08-19 15:00:00,14946.0 -2015-08-19 16:00:00,15068.0 -2015-08-19 17:00:00,15020.0 -2015-08-19 18:00:00,14430.0 -2015-08-19 19:00:00,13752.0 -2015-08-19 20:00:00,13035.0 -2015-08-19 21:00:00,12763.0 -2015-08-19 22:00:00,12726.0 -2015-08-19 23:00:00,12105.0 -2015-08-20 00:00:00,11206.0 -2015-08-18 01:00:00,12474.0 -2015-08-18 02:00:00,11605.0 -2015-08-18 03:00:00,10959.0 -2015-08-18 04:00:00,10568.0 -2015-08-18 05:00:00,10418.0 -2015-08-18 06:00:00,10491.0 -2015-08-18 07:00:00,11148.0 -2015-08-18 08:00:00,12027.0 -2015-08-18 09:00:00,12819.0 -2015-08-18 10:00:00,13674.0 -2015-08-18 11:00:00,14433.0 -2015-08-18 12:00:00,15146.0 -2015-08-18 13:00:00,15811.0 -2015-08-18 14:00:00,16448.0 -2015-08-18 15:00:00,16953.0 -2015-08-18 16:00:00,17243.0 -2015-08-18 17:00:00,17425.0 -2015-08-18 18:00:00,17359.0 -2015-08-18 19:00:00,16892.0 -2015-08-18 20:00:00,16366.0 -2015-08-18 21:00:00,16249.0 -2015-08-18 22:00:00,15554.0 -2015-08-18 23:00:00,14464.0 -2015-08-19 00:00:00,13274.0 -2015-08-17 01:00:00,13213.0 -2015-08-17 02:00:00,12248.0 -2015-08-17 03:00:00,11630.0 -2015-08-17 04:00:00,11131.0 -2015-08-17 05:00:00,10912.0 -2015-08-17 06:00:00,10978.0 -2015-08-17 07:00:00,11609.0 -2015-08-17 08:00:00,12461.0 -2015-08-17 09:00:00,13639.0 -2015-08-17 10:00:00,14984.0 -2015-08-17 11:00:00,16152.0 -2015-08-17 12:00:00,17339.0 -2015-08-17 13:00:00,18070.0 -2015-08-17 14:00:00,18435.0 -2015-08-17 15:00:00,18694.0 -2015-08-17 16:00:00,18667.0 -2015-08-17 17:00:00,18348.0 -2015-08-17 18:00:00,17865.0 -2015-08-17 19:00:00,17263.0 -2015-08-17 20:00:00,16649.0 -2015-08-17 21:00:00,16227.0 -2015-08-17 22:00:00,15944.0 -2015-08-17 23:00:00,14939.0 -2015-08-18 00:00:00,13667.0 -2015-08-16 01:00:00,12577.0 -2015-08-16 02:00:00,11748.0 -2015-08-16 03:00:00,11058.0 -2015-08-16 04:00:00,10564.0 -2015-08-16 05:00:00,10186.0 -2015-08-16 06:00:00,10015.0 -2015-08-16 07:00:00,9963.0 -2015-08-16 08:00:00,9917.0 -2015-08-16 09:00:00,10554.0 -2015-08-16 10:00:00,11865.0 -2015-08-16 11:00:00,13210.0 -2015-08-16 12:00:00,14490.0 -2015-08-16 13:00:00,15443.0 -2015-08-16 14:00:00,16154.0 -2015-08-16 15:00:00,16676.0 -2015-08-16 16:00:00,17094.0 -2015-08-16 17:00:00,17467.0 -2015-08-16 18:00:00,17583.0 -2015-08-16 19:00:00,17563.0 -2015-08-16 20:00:00,17100.0 -2015-08-16 21:00:00,16373.0 -2015-08-16 22:00:00,16212.0 -2015-08-16 23:00:00,15485.0 -2015-08-17 00:00:00,14362.0 -2015-08-15 01:00:00,12701.0 -2015-08-15 02:00:00,11659.0 -2015-08-15 03:00:00,10880.0 -2015-08-15 04:00:00,10369.0 -2015-08-15 05:00:00,10008.0 -2015-08-15 06:00:00,9939.0 -2015-08-15 07:00:00,10056.0 -2015-08-15 08:00:00,10216.0 -2015-08-15 09:00:00,10958.0 -2015-08-15 10:00:00,12064.0 -2015-08-15 11:00:00,13199.0 -2015-08-15 12:00:00,14387.0 -2015-08-15 13:00:00,15467.0 -2015-08-15 14:00:00,16404.0 -2015-08-15 15:00:00,16996.0 -2015-08-15 16:00:00,17323.0 -2015-08-15 17:00:00,17107.0 -2015-08-15 18:00:00,16481.0 -2015-08-15 19:00:00,15881.0 -2015-08-15 20:00:00,15569.0 -2015-08-15 21:00:00,15035.0 -2015-08-15 22:00:00,14934.0 -2015-08-15 23:00:00,14443.0 -2015-08-16 00:00:00,13617.0 -2015-08-14 01:00:00,12626.0 -2015-08-14 02:00:00,11787.0 -2015-08-14 03:00:00,11191.0 -2015-08-14 04:00:00,10731.0 -2015-08-14 05:00:00,10448.0 -2015-08-14 06:00:00,10634.0 -2015-08-14 07:00:00,11267.0 -2015-08-14 08:00:00,12050.0 -2015-08-14 09:00:00,12869.0 -2015-08-14 10:00:00,13618.0 -2015-08-14 11:00:00,14099.0 -2015-08-14 12:00:00,14737.0 -2015-08-14 13:00:00,15394.0 -2015-08-14 14:00:00,16240.0 -2015-08-14 15:00:00,17186.0 -2015-08-14 16:00:00,17914.0 -2015-08-14 17:00:00,18469.0 -2015-08-14 18:00:00,18741.0 -2015-08-14 19:00:00,18508.0 -2015-08-14 20:00:00,17873.0 -2015-08-14 21:00:00,17240.0 -2015-08-14 22:00:00,16991.0 -2015-08-14 23:00:00,15845.0 -2015-08-15 00:00:00,14098.0 -2015-08-13 01:00:00,11478.0 -2015-08-13 02:00:00,10630.0 -2015-08-13 03:00:00,10054.0 -2015-08-13 04:00:00,9651.0 -2015-08-13 05:00:00,9472.0 -2015-08-13 06:00:00,9539.0 -2015-08-13 07:00:00,10107.0 -2015-08-13 08:00:00,10883.0 -2015-08-13 09:00:00,11889.0 -2015-08-13 10:00:00,12917.0 -2015-08-13 11:00:00,13818.0 -2015-08-13 12:00:00,14738.0 -2015-08-13 13:00:00,15415.0 -2015-08-13 14:00:00,15999.0 -2015-08-13 15:00:00,16564.0 -2015-08-13 16:00:00,16951.0 -2015-08-13 17:00:00,17126.0 -2015-08-13 18:00:00,17216.0 -2015-08-13 19:00:00,16802.0 -2015-08-13 20:00:00,15897.0 -2015-08-13 21:00:00,15392.0 -2015-08-13 22:00:00,15384.0 -2015-08-13 23:00:00,14800.0 -2015-08-14 00:00:00,13779.0 -2015-08-12 01:00:00,11067.0 -2015-08-12 02:00:00,10270.0 -2015-08-12 03:00:00,9730.0 -2015-08-12 04:00:00,9382.0 -2015-08-12 05:00:00,9216.0 -2015-08-12 06:00:00,9333.0 -2015-08-12 07:00:00,9887.0 -2015-08-12 08:00:00,10633.0 -2015-08-12 09:00:00,11645.0 -2015-08-12 10:00:00,12549.0 -2015-08-12 11:00:00,13237.0 -2015-08-12 12:00:00,13851.0 -2015-08-12 13:00:00,14309.0 -2015-08-12 14:00:00,14584.0 -2015-08-12 15:00:00,15151.0 -2015-08-12 16:00:00,15563.0 -2015-08-12 17:00:00,15826.0 -2015-08-12 18:00:00,15950.0 -2015-08-12 19:00:00,15810.0 -2015-08-12 20:00:00,15240.0 -2015-08-12 21:00:00,14551.0 -2015-08-12 22:00:00,14369.0 -2015-08-12 23:00:00,13715.0 -2015-08-13 00:00:00,12631.0 -2015-08-11 01:00:00,11686.0 -2015-08-11 02:00:00,10823.0 -2015-08-11 03:00:00,10272.0 -2015-08-11 04:00:00,9849.0 -2015-08-11 05:00:00,9624.0 -2015-08-11 06:00:00,9727.0 -2015-08-11 07:00:00,10281.0 -2015-08-11 08:00:00,10990.0 -2015-08-11 09:00:00,12065.0 -2015-08-11 10:00:00,13042.0 -2015-08-11 11:00:00,13845.0 -2015-08-11 12:00:00,14672.0 -2015-08-11 13:00:00,15238.0 -2015-08-11 14:00:00,15618.0 -2015-08-11 15:00:00,15963.0 -2015-08-11 16:00:00,16172.0 -2015-08-11 17:00:00,16170.0 -2015-08-11 18:00:00,16031.0 -2015-08-11 19:00:00,15690.0 -2015-08-11 20:00:00,14967.0 -2015-08-11 21:00:00,14172.0 -2015-08-11 22:00:00,13896.0 -2015-08-11 23:00:00,13272.0 -2015-08-12 00:00:00,12175.0 -2015-08-10 01:00:00,11340.0 -2015-08-10 02:00:00,10702.0 -2015-08-10 03:00:00,10234.0 -2015-08-10 04:00:00,9970.0 -2015-08-10 05:00:00,9879.0 -2015-08-10 06:00:00,10146.0 -2015-08-10 07:00:00,10855.0 -2015-08-10 08:00:00,11756.0 -2015-08-10 09:00:00,12690.0 -2015-08-10 10:00:00,13701.0 -2015-08-10 11:00:00,14584.0 -2015-08-10 12:00:00,15550.0 -2015-08-10 13:00:00,16312.0 -2015-08-10 14:00:00,16690.0 -2015-08-10 15:00:00,16602.0 -2015-08-10 16:00:00,16551.0 -2015-08-10 17:00:00,16185.0 -2015-08-10 18:00:00,15649.0 -2015-08-10 19:00:00,15331.0 -2015-08-10 20:00:00,14966.0 -2015-08-10 21:00:00,14581.0 -2015-08-10 22:00:00,14497.0 -2015-08-10 23:00:00,13960.0 -2015-08-11 00:00:00,12856.0 -2015-08-09 01:00:00,11064.0 -2015-08-09 02:00:00,10342.0 -2015-08-09 03:00:00,9843.0 -2015-08-09 04:00:00,9419.0 -2015-08-09 05:00:00,9198.0 -2015-08-09 06:00:00,9116.0 -2015-08-09 07:00:00,9133.0 -2015-08-09 08:00:00,9114.0 -2015-08-09 09:00:00,9443.0 -2015-08-09 10:00:00,9942.0 -2015-08-09 11:00:00,10429.0 -2015-08-09 12:00:00,10888.0 -2015-08-09 13:00:00,11250.0 -2015-08-09 14:00:00,11633.0 -2015-08-09 15:00:00,12177.0 -2015-08-09 16:00:00,12779.0 -2015-08-09 17:00:00,12652.0 -2015-08-09 18:00:00,12617.0 -2015-08-09 19:00:00,12740.0 -2015-08-09 20:00:00,12716.0 -2015-08-09 21:00:00,12604.0 -2015-08-09 22:00:00,12977.0 -2015-08-09 23:00:00,12771.0 -2015-08-10 00:00:00,12149.0 -2015-08-08 01:00:00,12152.0 -2015-08-08 02:00:00,11310.0 -2015-08-08 03:00:00,10643.0 -2015-08-08 04:00:00,10209.0 -2015-08-08 05:00:00,9858.0 -2015-08-08 06:00:00,9773.0 -2015-08-08 07:00:00,9848.0 -2015-08-08 08:00:00,10012.0 -2015-08-08 09:00:00,10777.0 -2015-08-08 10:00:00,11902.0 -2015-08-08 11:00:00,13064.0 -2015-08-08 12:00:00,14090.0 -2015-08-08 13:00:00,14678.0 -2015-08-08 14:00:00,14660.0 -2015-08-08 15:00:00,14554.0 -2015-08-08 16:00:00,14280.0 -2015-08-08 17:00:00,14036.0 -2015-08-08 18:00:00,13767.0 -2015-08-08 19:00:00,13431.0 -2015-08-08 20:00:00,12982.0 -2015-08-08 21:00:00,12682.0 -2015-08-08 22:00:00,12817.0 -2015-08-08 23:00:00,12533.0 -2015-08-09 00:00:00,11819.0 -2015-08-07 01:00:00,11983.0 -2015-08-07 02:00:00,11084.0 -2015-08-07 03:00:00,10453.0 -2015-08-07 04:00:00,9991.0 -2015-08-07 05:00:00,9732.0 -2015-08-07 06:00:00,9788.0 -2015-08-07 07:00:00,10253.0 -2015-08-07 08:00:00,10853.0 -2015-08-07 09:00:00,11883.0 -2015-08-07 10:00:00,12813.0 -2015-08-07 11:00:00,13620.0 -2015-08-07 12:00:00,14397.0 -2015-08-07 13:00:00,14986.0 -2015-08-07 14:00:00,15427.0 -2015-08-07 15:00:00,15912.0 -2015-08-07 16:00:00,16226.0 -2015-08-07 17:00:00,16285.0 -2015-08-07 18:00:00,16065.0 -2015-08-07 19:00:00,15622.0 -2015-08-07 20:00:00,15026.0 -2015-08-07 21:00:00,14577.0 -2015-08-07 22:00:00,14504.0 -2015-08-07 23:00:00,14105.0 -2015-08-08 00:00:00,13175.0 -2015-08-06 01:00:00,11710.0 -2015-08-06 02:00:00,10859.0 -2015-08-06 03:00:00,10285.0 -2015-08-06 04:00:00,9907.0 -2015-08-06 05:00:00,9693.0 -2015-08-06 06:00:00,9827.0 -2015-08-06 07:00:00,10411.0 -2015-08-06 08:00:00,11086.0 -2015-08-06 09:00:00,12090.0 -2015-08-06 10:00:00,13018.0 -2015-08-06 11:00:00,13867.0 -2015-08-06 12:00:00,14640.0 -2015-08-06 13:00:00,15285.0 -2015-08-06 14:00:00,15751.0 -2015-08-06 15:00:00,16235.0 -2015-08-06 16:00:00,16453.0 -2015-08-06 17:00:00,16555.0 -2015-08-06 18:00:00,16548.0 -2015-08-06 19:00:00,16355.0 -2015-08-06 20:00:00,15668.0 -2015-08-06 21:00:00,14902.0 -2015-08-06 22:00:00,14651.0 -2015-08-06 23:00:00,14080.0 -2015-08-07 00:00:00,13058.0 -2015-08-05 01:00:00,11411.0 -2015-08-05 02:00:00,10574.0 -2015-08-05 03:00:00,9991.0 -2015-08-05 04:00:00,9597.0 -2015-08-05 05:00:00,9382.0 -2015-08-05 06:00:00,9483.0 -2015-08-05 07:00:00,9988.0 -2015-08-05 08:00:00,10650.0 -2015-08-05 09:00:00,11752.0 -2015-08-05 10:00:00,12676.0 -2015-08-05 11:00:00,13523.0 -2015-08-05 12:00:00,14269.0 -2015-08-05 13:00:00,14862.0 -2015-08-05 14:00:00,15409.0 -2015-08-05 15:00:00,15859.0 -2015-08-05 16:00:00,16125.0 -2015-08-05 17:00:00,16197.0 -2015-08-05 18:00:00,16020.0 -2015-08-05 19:00:00,15598.0 -2015-08-05 20:00:00,14853.0 -2015-08-05 21:00:00,14256.0 -2015-08-05 22:00:00,14226.0 -2015-08-05 23:00:00,13743.0 -2015-08-06 00:00:00,12745.0 -2015-08-04 01:00:00,11594.0 -2015-08-04 02:00:00,10692.0 -2015-08-04 03:00:00,10059.0 -2015-08-04 04:00:00,9609.0 -2015-08-04 05:00:00,9393.0 -2015-08-04 06:00:00,9474.0 -2015-08-04 07:00:00,9912.0 -2015-08-04 08:00:00,10629.0 -2015-08-04 09:00:00,11675.0 -2015-08-04 10:00:00,12620.0 -2015-08-04 11:00:00,13406.0 -2015-08-04 12:00:00,14131.0 -2015-08-04 13:00:00,14677.0 -2015-08-04 14:00:00,15103.0 -2015-08-04 15:00:00,15579.0 -2015-08-04 16:00:00,15956.0 -2015-08-04 17:00:00,16060.0 -2015-08-04 18:00:00,15939.0 -2015-08-04 19:00:00,15732.0 -2015-08-04 20:00:00,15154.0 -2015-08-04 21:00:00,14412.0 -2015-08-04 22:00:00,14210.0 -2015-08-04 23:00:00,13642.0 -2015-08-05 00:00:00,12564.0 -2015-08-03 01:00:00,13229.0 -2015-08-03 02:00:00,12031.0 -2015-08-03 03:00:00,11206.0 -2015-08-03 04:00:00,10693.0 -2015-08-03 05:00:00,10458.0 -2015-08-03 06:00:00,10457.0 -2015-08-03 07:00:00,10917.0 -2015-08-03 08:00:00,11663.0 -2015-08-03 09:00:00,12742.0 -2015-08-03 10:00:00,13687.0 -2015-08-03 11:00:00,14453.0 -2015-08-03 12:00:00,15170.0 -2015-08-03 13:00:00,15664.0 -2015-08-03 14:00:00,15964.0 -2015-08-03 15:00:00,16251.0 -2015-08-03 16:00:00,16482.0 -2015-08-03 17:00:00,16648.0 -2015-08-03 18:00:00,16691.0 -2015-08-03 19:00:00,16511.0 -2015-08-03 20:00:00,15858.0 -2015-08-03 21:00:00,15046.0 -2015-08-03 22:00:00,14590.0 -2015-08-03 23:00:00,13952.0 -2015-08-04 00:00:00,12801.0 -2015-08-02 01:00:00,12395.0 -2015-08-02 02:00:00,11573.0 -2015-08-02 03:00:00,10890.0 -2015-08-02 04:00:00,10401.0 -2015-08-02 05:00:00,10066.0 -2015-08-02 06:00:00,9842.0 -2015-08-02 07:00:00,9828.0 -2015-08-02 08:00:00,9790.0 -2015-08-02 09:00:00,10595.0 -2015-08-02 10:00:00,11813.0 -2015-08-02 11:00:00,13108.0 -2015-08-02 12:00:00,14273.0 -2015-08-02 13:00:00,15287.0 -2015-08-02 14:00:00,16038.0 -2015-08-02 15:00:00,16741.0 -2015-08-02 16:00:00,16890.0 -2015-08-02 17:00:00,16778.0 -2015-08-02 18:00:00,17461.0 -2015-08-02 19:00:00,17673.0 -2015-08-02 20:00:00,17449.0 -2015-08-02 21:00:00,16942.0 -2015-08-02 22:00:00,16841.0 -2015-08-02 23:00:00,16451.0 -2015-08-03 00:00:00,15181.0 -2015-08-01 01:00:00,11952.0 -2015-08-01 02:00:00,10993.0 -2015-08-01 03:00:00,10269.0 -2015-08-01 04:00:00,9714.0 -2015-08-01 05:00:00,9415.0 -2015-08-01 06:00:00,9261.0 -2015-08-01 07:00:00,9323.0 -2015-08-01 08:00:00,9413.0 -2015-08-01 09:00:00,10208.0 -2015-08-01 10:00:00,11211.0 -2015-08-01 11:00:00,12253.0 -2015-08-01 12:00:00,13142.0 -2015-08-01 13:00:00,13791.0 -2015-08-01 14:00:00,14334.0 -2015-08-01 15:00:00,14831.0 -2015-08-01 16:00:00,15316.0 -2015-08-01 17:00:00,15705.0 -2015-08-01 18:00:00,15863.0 -2015-08-01 19:00:00,15757.0 -2015-08-01 20:00:00,15294.0 -2015-08-01 21:00:00,14712.0 -2015-08-01 22:00:00,14497.0 -2015-08-01 23:00:00,14120.0 -2015-08-02 00:00:00,13280.0 -2015-07-31 01:00:00,13168.0 -2015-07-31 02:00:00,12077.0 -2015-07-31 03:00:00,11237.0 -2015-07-31 04:00:00,10653.0 -2015-07-31 05:00:00,10295.0 -2015-07-31 06:00:00,10301.0 -2015-07-31 07:00:00,10704.0 -2015-07-31 08:00:00,11421.0 -2015-07-31 09:00:00,12531.0 -2015-07-31 10:00:00,13362.0 -2015-07-31 11:00:00,14549.0 -2015-07-31 12:00:00,15764.0 -2015-07-31 13:00:00,16496.0 -2015-07-31 14:00:00,16933.0 -2015-07-31 15:00:00,17283.0 -2015-07-31 16:00:00,17475.0 -2015-07-31 17:00:00,17571.0 -2015-07-31 18:00:00,17549.0 -2015-07-31 19:00:00,17245.0 -2015-07-31 20:00:00,16531.0 -2015-07-31 21:00:00,15577.0 -2015-07-31 22:00:00,14863.0 -2015-07-31 23:00:00,14273.0 -2015-08-01 00:00:00,13096.0 -2015-07-30 01:00:00,12774.0 -2015-07-30 02:00:00,11744.0 -2015-07-30 03:00:00,10964.0 -2015-07-30 04:00:00,10441.0 -2015-07-30 05:00:00,10160.0 -2015-07-30 06:00:00,10224.0 -2015-07-30 07:00:00,10662.0 -2015-07-30 08:00:00,11379.0 -2015-07-30 09:00:00,12511.0 -2015-07-30 10:00:00,13553.0 -2015-07-30 11:00:00,14490.0 -2015-07-30 12:00:00,15454.0 -2015-07-30 13:00:00,16202.0 -2015-07-30 14:00:00,16781.0 -2015-07-30 15:00:00,17269.0 -2015-07-30 16:00:00,17665.0 -2015-07-30 17:00:00,18038.0 -2015-07-30 18:00:00,18218.0 -2015-07-30 19:00:00,18171.0 -2015-07-30 20:00:00,17621.0 -2015-07-30 21:00:00,16845.0 -2015-07-30 22:00:00,16334.0 -2015-07-30 23:00:00,15753.0 -2015-07-31 00:00:00,14512.0 -2015-07-29 01:00:00,14828.0 -2015-07-29 02:00:00,13829.0 -2015-07-29 03:00:00,13125.0 -2015-07-29 04:00:00,12546.0 -2015-07-29 05:00:00,12176.0 -2015-07-29 06:00:00,12178.0 -2015-07-29 07:00:00,12655.0 -2015-07-29 08:00:00,13466.0 -2015-07-29 09:00:00,14293.0 -2015-07-29 10:00:00,15079.0 -2015-07-29 11:00:00,15901.0 -2015-07-29 12:00:00,16849.0 -2015-07-29 13:00:00,17469.0 -2015-07-29 14:00:00,17938.0 -2015-07-29 15:00:00,18298.0 -2015-07-29 16:00:00,18519.0 -2015-07-29 17:00:00,18586.0 -2015-07-29 18:00:00,18564.0 -2015-07-29 19:00:00,18310.0 -2015-07-29 20:00:00,17662.0 -2015-07-29 21:00:00,16705.0 -2015-07-29 22:00:00,16072.0 -2015-07-29 23:00:00,15385.0 -2015-07-30 00:00:00,14081.0 -2015-07-28 01:00:00,13157.0 -2015-07-28 02:00:00,12160.0 -2015-07-28 03:00:00,11445.0 -2015-07-28 04:00:00,10891.0 -2015-07-28 05:00:00,10602.0 -2015-07-28 06:00:00,10643.0 -2015-07-28 07:00:00,11100.0 -2015-07-28 08:00:00,11974.0 -2015-07-28 09:00:00,13280.0 -2015-07-28 10:00:00,14623.0 -2015-07-28 11:00:00,15812.0 -2015-07-28 12:00:00,17023.0 -2015-07-28 13:00:00,17850.0 -2015-07-28 14:00:00,18510.0 -2015-07-28 15:00:00,19153.0 -2015-07-28 16:00:00,19547.0 -2015-07-28 17:00:00,19765.0 -2015-07-28 18:00:00,19707.0 -2015-07-28 19:00:00,19368.0 -2015-07-28 20:00:00,18728.0 -2015-07-28 21:00:00,18276.0 -2015-07-28 22:00:00,18092.0 -2015-07-28 23:00:00,17482.0 -2015-07-29 00:00:00,16157.0 -2015-07-27 01:00:00,11430.0 -2015-07-27 02:00:00,10722.0 -2015-07-27 03:00:00,10169.0 -2015-07-27 04:00:00,9927.0 -2015-07-27 05:00:00,9810.0 -2015-07-27 06:00:00,10014.0 -2015-07-27 07:00:00,10620.0 -2015-07-27 08:00:00,11560.0 -2015-07-27 09:00:00,12902.0 -2015-07-27 10:00:00,14202.0 -2015-07-27 11:00:00,15390.0 -2015-07-27 12:00:00,16415.0 -2015-07-27 13:00:00,17142.0 -2015-07-27 14:00:00,17666.0 -2015-07-27 15:00:00,18248.0 -2015-07-27 16:00:00,18615.0 -2015-07-27 17:00:00,18828.0 -2015-07-27 18:00:00,18829.0 -2015-07-27 19:00:00,18349.0 -2015-07-27 20:00:00,17489.0 -2015-07-27 21:00:00,16668.0 -2015-07-27 22:00:00,16329.0 -2015-07-27 23:00:00,15728.0 -2015-07-28 00:00:00,14481.0 -2015-07-26 01:00:00,12650.0 -2015-07-26 02:00:00,11668.0 -2015-07-26 03:00:00,10840.0 -2015-07-26 04:00:00,10259.0 -2015-07-26 05:00:00,9892.0 -2015-07-26 06:00:00,9589.0 -2015-07-26 07:00:00,9426.0 -2015-07-26 08:00:00,9456.0 -2015-07-26 09:00:00,10115.0 -2015-07-26 10:00:00,11031.0 -2015-07-26 11:00:00,11905.0 -2015-07-26 12:00:00,12423.0 -2015-07-26 13:00:00,12717.0 -2015-07-26 14:00:00,12874.0 -2015-07-26 15:00:00,13033.0 -2015-07-26 16:00:00,13240.0 -2015-07-26 17:00:00,13323.0 -2015-07-26 18:00:00,13437.0 -2015-07-26 19:00:00,13349.0 -2015-07-26 20:00:00,13240.0 -2015-07-26 21:00:00,13055.0 -2015-07-26 22:00:00,13195.0 -2015-07-26 23:00:00,12989.0 -2015-07-27 00:00:00,12301.0 -2015-07-25 01:00:00,12665.0 -2015-07-25 02:00:00,11789.0 -2015-07-25 03:00:00,11185.0 -2015-07-25 04:00:00,10703.0 -2015-07-25 05:00:00,10427.0 -2015-07-25 06:00:00,10285.0 -2015-07-25 07:00:00,10324.0 -2015-07-25 08:00:00,10576.0 -2015-07-25 09:00:00,11541.0 -2015-07-25 10:00:00,12821.0 -2015-07-25 11:00:00,14055.0 -2015-07-25 12:00:00,15292.0 -2015-07-25 13:00:00,16277.0 -2015-07-25 14:00:00,16859.0 -2015-07-25 15:00:00,17183.0 -2015-07-25 16:00:00,17518.0 -2015-07-25 17:00:00,17703.0 -2015-07-25 18:00:00,17653.0 -2015-07-25 19:00:00,17415.0 -2015-07-25 20:00:00,16770.0 -2015-07-25 21:00:00,15882.0 -2015-07-25 22:00:00,15331.0 -2015-07-25 23:00:00,14799.0 -2015-07-26 00:00:00,13777.0 -2015-07-24 01:00:00,12298.0 -2015-07-24 02:00:00,11333.0 -2015-07-24 03:00:00,10616.0 -2015-07-24 04:00:00,10150.0 -2015-07-24 05:00:00,9866.0 -2015-07-24 06:00:00,9913.0 -2015-07-24 07:00:00,10342.0 -2015-07-24 08:00:00,11155.0 -2015-07-24 09:00:00,12427.0 -2015-07-24 10:00:00,13642.0 -2015-07-24 11:00:00,14650.0 -2015-07-24 12:00:00,15516.0 -2015-07-24 13:00:00,16234.0 -2015-07-24 14:00:00,16785.0 -2015-07-24 15:00:00,17228.0 -2015-07-24 16:00:00,17238.0 -2015-07-24 17:00:00,17024.0 -2015-07-24 18:00:00,16776.0 -2015-07-24 19:00:00,16792.0 -2015-07-24 20:00:00,16443.0 -2015-07-24 21:00:00,15765.0 -2015-07-24 22:00:00,15270.0 -2015-07-24 23:00:00,14881.0 -2015-07-25 00:00:00,13786.0 -2015-07-23 01:00:00,11677.0 -2015-07-23 02:00:00,10761.0 -2015-07-23 03:00:00,10120.0 -2015-07-23 04:00:00,9685.0 -2015-07-23 05:00:00,9461.0 -2015-07-23 06:00:00,9547.0 -2015-07-23 07:00:00,9983.0 -2015-07-23 08:00:00,10803.0 -2015-07-23 09:00:00,12017.0 -2015-07-23 10:00:00,13096.0 -2015-07-23 11:00:00,14047.0 -2015-07-23 12:00:00,14920.0 -2015-07-23 13:00:00,15574.0 -2015-07-23 14:00:00,16069.0 -2015-07-23 15:00:00,16541.0 -2015-07-23 16:00:00,16845.0 -2015-07-23 17:00:00,17047.0 -2015-07-23 18:00:00,17132.0 -2015-07-23 19:00:00,16976.0 -2015-07-23 20:00:00,16413.0 -2015-07-23 21:00:00,15682.0 -2015-07-23 22:00:00,15294.0 -2015-07-23 23:00:00,14755.0 -2015-07-24 00:00:00,13546.0 -2015-07-22 01:00:00,11509.0 -2015-07-22 02:00:00,10630.0 -2015-07-22 03:00:00,10068.0 -2015-07-22 04:00:00,9683.0 -2015-07-22 05:00:00,9498.0 -2015-07-22 06:00:00,9564.0 -2015-07-22 07:00:00,9984.0 -2015-07-22 08:00:00,10798.0 -2015-07-22 09:00:00,11882.0 -2015-07-22 10:00:00,12740.0 -2015-07-22 11:00:00,13454.0 -2015-07-22 12:00:00,14198.0 -2015-07-22 13:00:00,14667.0 -2015-07-22 14:00:00,15157.0 -2015-07-22 15:00:00,15642.0 -2015-07-22 16:00:00,15903.0 -2015-07-22 17:00:00,16039.0 -2015-07-22 18:00:00,16028.0 -2015-07-22 19:00:00,15799.0 -2015-07-22 20:00:00,15227.0 -2015-07-22 21:00:00,14569.0 -2015-07-22 22:00:00,14264.0 -2015-07-22 23:00:00,13865.0 -2015-07-23 00:00:00,12852.0 -2015-07-21 01:00:00,12659.0 -2015-07-21 02:00:00,11668.0 -2015-07-21 03:00:00,10921.0 -2015-07-21 04:00:00,10371.0 -2015-07-21 05:00:00,10066.0 -2015-07-21 06:00:00,10033.0 -2015-07-21 07:00:00,10363.0 -2015-07-21 08:00:00,11208.0 -2015-07-21 09:00:00,12397.0 -2015-07-21 10:00:00,13266.0 -2015-07-21 11:00:00,13985.0 -2015-07-21 12:00:00,14542.0 -2015-07-21 13:00:00,14913.0 -2015-07-21 14:00:00,15217.0 -2015-07-21 15:00:00,15499.0 -2015-07-21 16:00:00,15762.0 -2015-07-21 17:00:00,15966.0 -2015-07-21 18:00:00,16157.0 -2015-07-21 19:00:00,16018.0 -2015-07-21 20:00:00,15482.0 -2015-07-21 21:00:00,14810.0 -2015-07-21 22:00:00,14319.0 -2015-07-21 23:00:00,13823.0 -2015-07-22 00:00:00,12705.0 -2015-07-20 01:00:00,11684.0 -2015-07-20 02:00:00,10865.0 -2015-07-20 03:00:00,10188.0 -2015-07-20 04:00:00,9822.0 -2015-07-20 05:00:00,9587.0 -2015-07-20 06:00:00,9747.0 -2015-07-20 07:00:00,10149.0 -2015-07-20 08:00:00,11047.0 -2015-07-20 09:00:00,12214.0 -2015-07-20 10:00:00,13272.0 -2015-07-20 11:00:00,14217.0 -2015-07-20 12:00:00,14926.0 -2015-07-20 13:00:00,15423.0 -2015-07-20 14:00:00,16198.0 -2015-07-20 15:00:00,16908.0 -2015-07-20 16:00:00,17350.0 -2015-07-20 17:00:00,17202.0 -2015-07-20 18:00:00,16855.0 -2015-07-20 19:00:00,16447.0 -2015-07-20 20:00:00,16191.0 -2015-07-20 21:00:00,15900.0 -2015-07-20 22:00:00,15542.0 -2015-07-20 23:00:00,15133.0 -2015-07-21 00:00:00,13964.0 -2015-07-19 01:00:00,13090.0 -2015-07-19 02:00:00,12312.0 -2015-07-19 03:00:00,11679.0 -2015-07-19 04:00:00,11149.0 -2015-07-19 05:00:00,10829.0 -2015-07-19 06:00:00,10602.0 -2015-07-19 07:00:00,10435.0 -2015-07-19 08:00:00,10483.0 -2015-07-19 09:00:00,11191.0 -2015-07-19 10:00:00,12324.0 -2015-07-19 11:00:00,13406.0 -2015-07-19 12:00:00,14492.0 -2015-07-19 13:00:00,15280.0 -2015-07-19 14:00:00,15676.0 -2015-07-19 15:00:00,15887.0 -2015-07-19 16:00:00,16051.0 -2015-07-19 17:00:00,16078.0 -2015-07-19 18:00:00,15914.0 -2015-07-19 19:00:00,15772.0 -2015-07-19 20:00:00,15250.0 -2015-07-19 21:00:00,14549.0 -2015-07-19 22:00:00,14079.0 -2015-07-19 23:00:00,13733.0 -2015-07-20 00:00:00,12766.0 -2015-07-18 01:00:00,13944.0 -2015-07-18 02:00:00,12967.0 -2015-07-18 03:00:00,12169.0 -2015-07-18 04:00:00,11571.0 -2015-07-18 05:00:00,11197.0 -2015-07-18 06:00:00,11022.0 -2015-07-18 07:00:00,10997.0 -2015-07-18 08:00:00,11368.0 -2015-07-18 09:00:00,12536.0 -2015-07-18 10:00:00,13926.0 -2015-07-18 11:00:00,14948.0 -2015-07-18 12:00:00,15509.0 -2015-07-18 13:00:00,16047.0 -2015-07-18 14:00:00,16506.0 -2015-07-18 15:00:00,17055.0 -2015-07-18 16:00:00,17377.0 -2015-07-18 17:00:00,16954.0 -2015-07-18 18:00:00,16560.0 -2015-07-18 19:00:00,16125.0 -2015-07-18 20:00:00,15829.0 -2015-07-18 21:00:00,15361.0 -2015-07-18 22:00:00,14970.0 -2015-07-18 23:00:00,14721.0 -2015-07-19 00:00:00,13937.0 -2015-07-17 01:00:00,10959.0 -2015-07-17 02:00:00,10353.0 -2015-07-17 03:00:00,9957.0 -2015-07-17 04:00:00,9691.0 -2015-07-17 05:00:00,9614.0 -2015-07-17 06:00:00,9778.0 -2015-07-17 07:00:00,10365.0 -2015-07-17 08:00:00,11306.0 -2015-07-17 09:00:00,12734.0 -2015-07-17 10:00:00,14159.0 -2015-07-17 11:00:00,15400.0 -2015-07-17 12:00:00,16561.0 -2015-07-17 13:00:00,17567.0 -2015-07-17 14:00:00,18221.0 -2015-07-17 15:00:00,18841.0 -2015-07-17 16:00:00,19302.0 -2015-07-17 17:00:00,19525.0 -2015-07-17 18:00:00,19398.0 -2015-07-17 19:00:00,18889.0 -2015-07-17 20:00:00,18153.0 -2015-07-17 21:00:00,17479.0 -2015-07-17 22:00:00,16804.0 -2015-07-17 23:00:00,16326.0 -2015-07-18 00:00:00,15196.0 -2015-07-16 01:00:00,9973.0 -2015-07-16 02:00:00,9290.0 -2015-07-16 03:00:00,8884.0 -2015-07-16 04:00:00,8612.0 -2015-07-16 05:00:00,8510.0 -2015-07-16 06:00:00,8677.0 -2015-07-16 07:00:00,9136.0 -2015-07-16 08:00:00,9942.0 -2015-07-16 09:00:00,10863.0 -2015-07-16 10:00:00,11505.0 -2015-07-16 11:00:00,11896.0 -2015-07-16 12:00:00,12223.0 -2015-07-16 13:00:00,12408.0 -2015-07-16 14:00:00,12524.0 -2015-07-16 15:00:00,12707.0 -2015-07-16 16:00:00,12823.0 -2015-07-16 17:00:00,12962.0 -2015-07-16 18:00:00,12944.0 -2015-07-16 19:00:00,12900.0 -2015-07-16 20:00:00,12682.0 -2015-07-16 21:00:00,12579.0 -2015-07-16 22:00:00,12689.0 -2015-07-16 23:00:00,12399.0 -2015-07-17 00:00:00,11727.0 -2015-07-15 01:00:00,10281.0 -2015-07-15 02:00:00,9517.0 -2015-07-15 03:00:00,9022.0 -2015-07-15 04:00:00,8712.0 -2015-07-15 05:00:00,8566.0 -2015-07-15 06:00:00,8667.0 -2015-07-15 07:00:00,9041.0 -2015-07-15 08:00:00,9784.0 -2015-07-15 09:00:00,10703.0 -2015-07-15 10:00:00,11451.0 -2015-07-15 11:00:00,12065.0 -2015-07-15 12:00:00,12599.0 -2015-07-15 13:00:00,12987.0 -2015-07-15 14:00:00,13274.0 -2015-07-15 15:00:00,13643.0 -2015-07-15 16:00:00,13914.0 -2015-07-15 17:00:00,14032.0 -2015-07-15 18:00:00,13844.0 -2015-07-15 19:00:00,13343.0 -2015-07-15 20:00:00,12630.0 -2015-07-15 21:00:00,12108.0 -2015-07-15 22:00:00,12017.0 -2015-07-15 23:00:00,11752.0 -2015-07-16 00:00:00,10894.0 -2015-07-14 01:00:00,12913.0 -2015-07-14 02:00:00,11877.0 -2015-07-14 03:00:00,11216.0 -2015-07-14 04:00:00,10740.0 -2015-07-14 05:00:00,10486.0 -2015-07-14 06:00:00,10530.0 -2015-07-14 07:00:00,10951.0 -2015-07-14 08:00:00,11959.0 -2015-07-14 09:00:00,13255.0 -2015-07-14 10:00:00,14307.0 -2015-07-14 11:00:00,15013.0 -2015-07-14 12:00:00,15455.0 -2015-07-14 13:00:00,15834.0 -2015-07-14 14:00:00,15989.0 -2015-07-14 15:00:00,16224.0 -2015-07-14 16:00:00,16419.0 -2015-07-14 17:00:00,16203.0 -2015-07-14 18:00:00,15766.0 -2015-07-14 19:00:00,15195.0 -2015-07-14 20:00:00,14462.0 -2015-07-14 21:00:00,13584.0 -2015-07-14 22:00:00,12944.0 -2015-07-14 23:00:00,12451.0 -2015-07-15 00:00:00,11365.0 -2015-07-13 01:00:00,11867.0 -2015-07-13 02:00:00,11091.0 -2015-07-13 03:00:00,10578.0 -2015-07-13 04:00:00,10194.0 -2015-07-13 05:00:00,10052.0 -2015-07-13 06:00:00,10185.0 -2015-07-13 07:00:00,10802.0 -2015-07-13 08:00:00,11729.0 -2015-07-13 09:00:00,12559.0 -2015-07-13 10:00:00,12874.0 -2015-07-13 11:00:00,13285.0 -2015-07-13 12:00:00,14119.0 -2015-07-13 13:00:00,14972.0 -2015-07-13 14:00:00,15749.0 -2015-07-13 15:00:00,16485.0 -2015-07-13 16:00:00,17319.0 -2015-07-13 17:00:00,17910.0 -2015-07-13 18:00:00,18135.0 -2015-07-13 19:00:00,18221.0 -2015-07-13 20:00:00,17956.0 -2015-07-13 21:00:00,17528.0 -2015-07-13 22:00:00,17071.0 -2015-07-13 23:00:00,16114.0 -2015-07-14 00:00:00,14405.0 -2015-07-12 01:00:00,10324.0 -2015-07-12 02:00:00,9732.0 -2015-07-12 03:00:00,9245.0 -2015-07-12 04:00:00,8888.0 -2015-07-12 05:00:00,8678.0 -2015-07-12 06:00:00,8654.0 -2015-07-12 07:00:00,8530.0 -2015-07-12 08:00:00,8488.0 -2015-07-12 09:00:00,8874.0 -2015-07-12 10:00:00,9415.0 -2015-07-12 11:00:00,10298.0 -2015-07-12 12:00:00,11231.0 -2015-07-12 13:00:00,12072.0 -2015-07-12 14:00:00,12625.0 -2015-07-12 15:00:00,13221.0 -2015-07-12 16:00:00,13681.0 -2015-07-12 17:00:00,14079.0 -2015-07-12 18:00:00,14434.0 -2015-07-12 19:00:00,14598.0 -2015-07-12 20:00:00,14471.0 -2015-07-12 21:00:00,14029.0 -2015-07-12 22:00:00,13760.0 -2015-07-12 23:00:00,13568.0 -2015-07-13 00:00:00,12761.0 -2015-07-11 01:00:00,10663.0 -2015-07-11 02:00:00,9835.0 -2015-07-11 03:00:00,9306.0 -2015-07-11 04:00:00,8860.0 -2015-07-11 05:00:00,8663.0 -2015-07-11 06:00:00,8559.0 -2015-07-11 07:00:00,8560.0 -2015-07-11 08:00:00,8733.0 -2015-07-11 09:00:00,9517.0 -2015-07-11 10:00:00,10226.0 -2015-07-11 11:00:00,11057.0 -2015-07-11 12:00:00,11643.0 -2015-07-11 13:00:00,12121.0 -2015-07-11 14:00:00,12388.0 -2015-07-11 15:00:00,12439.0 -2015-07-11 16:00:00,12257.0 -2015-07-11 17:00:00,12056.0 -2015-07-11 18:00:00,11789.0 -2015-07-11 19:00:00,11613.0 -2015-07-11 20:00:00,11402.0 -2015-07-11 21:00:00,11234.0 -2015-07-11 22:00:00,11486.0 -2015-07-11 23:00:00,11436.0 -2015-07-12 00:00:00,10944.0 -2015-07-10 01:00:00,9996.0 -2015-07-10 02:00:00,9302.0 -2015-07-10 03:00:00,8856.0 -2015-07-10 04:00:00,8614.0 -2015-07-10 05:00:00,8469.0 -2015-07-10 06:00:00,8614.0 -2015-07-10 07:00:00,9018.0 -2015-07-10 08:00:00,9719.0 -2015-07-10 09:00:00,10659.0 -2015-07-10 10:00:00,11296.0 -2015-07-10 11:00:00,11851.0 -2015-07-10 12:00:00,12427.0 -2015-07-10 13:00:00,12906.0 -2015-07-10 14:00:00,13245.0 -2015-07-10 15:00:00,13667.0 -2015-07-10 16:00:00,14062.0 -2015-07-10 17:00:00,14286.0 -2015-07-10 18:00:00,14435.0 -2015-07-10 19:00:00,14291.0 -2015-07-10 20:00:00,13783.0 -2015-07-10 21:00:00,13159.0 -2015-07-10 22:00:00,12873.0 -2015-07-10 23:00:00,12589.0 -2015-07-11 00:00:00,11642.0 -2015-07-09 01:00:00,9508.0 -2015-07-09 02:00:00,8967.0 -2015-07-09 03:00:00,8614.0 -2015-07-09 04:00:00,8396.0 -2015-07-09 05:00:00,8304.0 -2015-07-09 06:00:00,8429.0 -2015-07-09 07:00:00,8920.0 -2015-07-09 08:00:00,9677.0 -2015-07-09 09:00:00,10406.0 -2015-07-09 10:00:00,10957.0 -2015-07-09 11:00:00,11273.0 -2015-07-09 12:00:00,11550.0 -2015-07-09 13:00:00,11825.0 -2015-07-09 14:00:00,12050.0 -2015-07-09 15:00:00,12224.0 -2015-07-09 16:00:00,12372.0 -2015-07-09 17:00:00,12521.0 -2015-07-09 18:00:00,12595.0 -2015-07-09 19:00:00,12627.0 -2015-07-09 20:00:00,12222.0 -2015-07-09 21:00:00,11876.0 -2015-07-09 22:00:00,11799.0 -2015-07-09 23:00:00,11674.0 -2015-07-10 00:00:00,10873.0 -2015-07-08 01:00:00,9417.0 -2015-07-08 02:00:00,8841.0 -2015-07-08 03:00:00,8443.0 -2015-07-08 04:00:00,8255.0 -2015-07-08 05:00:00,8169.0 -2015-07-08 06:00:00,8308.0 -2015-07-08 07:00:00,8752.0 -2015-07-08 08:00:00,9414.0 -2015-07-08 09:00:00,10223.0 -2015-07-08 10:00:00,10819.0 -2015-07-08 11:00:00,11191.0 -2015-07-08 12:00:00,11499.0 -2015-07-08 13:00:00,11642.0 -2015-07-08 14:00:00,11699.0 -2015-07-08 15:00:00,11822.0 -2015-07-08 16:00:00,11789.0 -2015-07-08 17:00:00,11601.0 -2015-07-08 18:00:00,11432.0 -2015-07-08 19:00:00,11221.0 -2015-07-08 20:00:00,11010.0 -2015-07-08 21:00:00,11064.0 -2015-07-08 22:00:00,11236.0 -2015-07-08 23:00:00,10984.0 -2015-07-09 00:00:00,10232.0 -2015-07-07 01:00:00,12187.0 -2015-07-07 02:00:00,11294.0 -2015-07-07 03:00:00,10723.0 -2015-07-07 04:00:00,10354.0 -2015-07-07 05:00:00,10136.0 -2015-07-07 06:00:00,10052.0 -2015-07-07 07:00:00,10192.0 -2015-07-07 08:00:00,10757.0 -2015-07-07 09:00:00,11194.0 -2015-07-07 10:00:00,11540.0 -2015-07-07 11:00:00,11742.0 -2015-07-07 12:00:00,12110.0 -2015-07-07 13:00:00,12359.0 -2015-07-07 14:00:00,12489.0 -2015-07-07 15:00:00,12565.0 -2015-07-07 16:00:00,12543.0 -2015-07-07 17:00:00,12475.0 -2015-07-07 18:00:00,12329.0 -2015-07-07 19:00:00,12058.0 -2015-07-07 20:00:00,11615.0 -2015-07-07 21:00:00,11218.0 -2015-07-07 22:00:00,11153.0 -2015-07-07 23:00:00,11036.0 -2015-07-08 00:00:00,10271.0 -2015-07-06 01:00:00,11048.0 -2015-07-06 02:00:00,10258.0 -2015-07-06 03:00:00,9747.0 -2015-07-06 04:00:00,9411.0 -2015-07-06 05:00:00,9214.0 -2015-07-06 06:00:00,9375.0 -2015-07-06 07:00:00,9850.0 -2015-07-06 08:00:00,10775.0 -2015-07-06 09:00:00,11947.0 -2015-07-06 10:00:00,13009.0 -2015-07-06 11:00:00,13976.0 -2015-07-06 12:00:00,14966.0 -2015-07-06 13:00:00,15772.0 -2015-07-06 14:00:00,16505.0 -2015-07-06 15:00:00,16917.0 -2015-07-06 16:00:00,16346.0 -2015-07-06 17:00:00,15696.0 -2015-07-06 18:00:00,15808.0 -2015-07-06 19:00:00,16019.0 -2015-07-06 20:00:00,15630.0 -2015-07-06 21:00:00,15270.0 -2015-07-06 22:00:00,15197.0 -2015-07-06 23:00:00,14574.0 -2015-07-07 00:00:00,13386.0 -2015-07-05 01:00:00,9751.0 -2015-07-05 02:00:00,9216.0 -2015-07-05 03:00:00,8663.0 -2015-07-05 04:00:00,8316.0 -2015-07-05 05:00:00,8052.0 -2015-07-05 06:00:00,7988.0 -2015-07-05 07:00:00,7836.0 -2015-07-05 08:00:00,7845.0 -2015-07-05 09:00:00,8305.0 -2015-07-05 10:00:00,9110.0 -2015-07-05 11:00:00,9984.0 -2015-07-05 12:00:00,10921.0 -2015-07-05 13:00:00,11675.0 -2015-07-05 14:00:00,12288.0 -2015-07-05 15:00:00,12804.0 -2015-07-05 16:00:00,13176.0 -2015-07-05 17:00:00,13556.0 -2015-07-05 18:00:00,13723.0 -2015-07-05 19:00:00,13775.0 -2015-07-05 20:00:00,13463.0 -2015-07-05 21:00:00,13078.0 -2015-07-05 22:00:00,12913.0 -2015-07-05 23:00:00,12762.0 -2015-07-06 00:00:00,11954.0 -2015-07-04 01:00:00,9307.0 -2015-07-04 02:00:00,8677.0 -2015-07-04 03:00:00,8254.0 -2015-07-04 04:00:00,7944.0 -2015-07-04 05:00:00,7712.0 -2015-07-04 06:00:00,7655.0 -2015-07-04 07:00:00,7506.0 -2015-07-04 08:00:00,7535.0 -2015-07-04 09:00:00,7972.0 -2015-07-04 10:00:00,8591.0 -2015-07-04 11:00:00,9263.0 -2015-07-04 12:00:00,9904.0 -2015-07-04 13:00:00,10466.0 -2015-07-04 14:00:00,10968.0 -2015-07-04 15:00:00,11327.0 -2015-07-04 16:00:00,11638.0 -2015-07-04 17:00:00,11852.0 -2015-07-04 18:00:00,11951.0 -2015-07-04 19:00:00,11893.0 -2015-07-04 20:00:00,11533.0 -2015-07-04 21:00:00,11101.0 -2015-07-04 22:00:00,10827.0 -2015-07-04 23:00:00,10635.0 -2015-07-05 00:00:00,10279.0 -2015-07-03 01:00:00,9151.0 -2015-07-03 02:00:00,8580.0 -2015-07-03 03:00:00,8197.0 -2015-07-03 04:00:00,7971.0 -2015-07-03 05:00:00,7838.0 -2015-07-03 06:00:00,7874.0 -2015-07-03 07:00:00,7927.0 -2015-07-03 08:00:00,8060.0 -2015-07-03 09:00:00,8552.0 -2015-07-03 10:00:00,9059.0 -2015-07-03 11:00:00,9550.0 -2015-07-03 12:00:00,10006.0 -2015-07-03 13:00:00,10338.0 -2015-07-03 14:00:00,10630.0 -2015-07-03 15:00:00,10878.0 -2015-07-03 16:00:00,11090.0 -2015-07-03 17:00:00,11342.0 -2015-07-03 18:00:00,11511.0 -2015-07-03 19:00:00,11495.0 -2015-07-03 20:00:00,11209.0 -2015-07-03 21:00:00,10830.0 -2015-07-03 22:00:00,10688.0 -2015-07-03 23:00:00,10547.0 -2015-07-04 00:00:00,9958.0 -2015-07-02 01:00:00,9288.0 -2015-07-02 02:00:00,8742.0 -2015-07-02 03:00:00,8359.0 -2015-07-02 04:00:00,8127.0 -2015-07-02 05:00:00,8052.0 -2015-07-02 06:00:00,8169.0 -2015-07-02 07:00:00,8552.0 -2015-07-02 08:00:00,9218.0 -2015-07-02 09:00:00,10026.0 -2015-07-02 10:00:00,10600.0 -2015-07-02 11:00:00,10990.0 -2015-07-02 12:00:00,11306.0 -2015-07-02 13:00:00,11518.0 -2015-07-02 14:00:00,11621.0 -2015-07-02 15:00:00,11768.0 -2015-07-02 16:00:00,11728.0 -2015-07-02 17:00:00,11642.0 -2015-07-02 18:00:00,11661.0 -2015-07-02 19:00:00,11523.0 -2015-07-02 20:00:00,11063.0 -2015-07-02 21:00:00,10695.0 -2015-07-02 22:00:00,10742.0 -2015-07-02 23:00:00,10629.0 -2015-07-03 00:00:00,9953.0 -2015-07-01 01:00:00,9904.0 -2015-07-01 02:00:00,9236.0 -2015-07-01 03:00:00,8755.0 -2015-07-01 04:00:00,8491.0 -2015-07-01 05:00:00,8353.0 -2015-07-01 06:00:00,8463.0 -2015-07-01 07:00:00,8846.0 -2015-07-01 08:00:00,9546.0 -2015-07-01 09:00:00,10327.0 -2015-07-01 10:00:00,10867.0 -2015-07-01 11:00:00,11320.0 -2015-07-01 12:00:00,11719.0 -2015-07-01 13:00:00,11988.0 -2015-07-01 14:00:00,12125.0 -2015-07-01 15:00:00,12275.0 -2015-07-01 16:00:00,12276.0 -2015-07-01 17:00:00,12196.0 -2015-07-01 18:00:00,11958.0 -2015-07-01 19:00:00,11670.0 -2015-07-01 20:00:00,11156.0 -2015-07-01 21:00:00,10842.0 -2015-07-01 22:00:00,10959.0 -2015-07-01 23:00:00,10817.0 -2015-07-02 00:00:00,10077.0 -2015-06-30 01:00:00,10451.0 -2015-06-30 02:00:00,9758.0 -2015-06-30 03:00:00,9275.0 -2015-06-30 04:00:00,8972.0 -2015-06-30 05:00:00,8844.0 -2015-06-30 06:00:00,8967.0 -2015-06-30 07:00:00,9365.0 -2015-06-30 08:00:00,10156.0 -2015-06-30 09:00:00,11106.0 -2015-06-30 10:00:00,11826.0 -2015-06-30 11:00:00,12426.0 -2015-06-30 12:00:00,12994.0 -2015-06-30 13:00:00,13433.0 -2015-06-30 14:00:00,13787.0 -2015-06-30 15:00:00,14062.0 -2015-06-30 16:00:00,14192.0 -2015-06-30 17:00:00,14205.0 -2015-06-30 18:00:00,14024.0 -2015-06-30 19:00:00,13652.0 -2015-06-30 20:00:00,13016.0 -2015-06-30 21:00:00,12427.0 -2015-06-30 22:00:00,12131.0 -2015-06-30 23:00:00,11816.0 -2015-07-01 00:00:00,10899.0 -2015-06-29 01:00:00,9696.0 -2015-06-29 02:00:00,9185.0 -2015-06-29 03:00:00,8808.0 -2015-06-29 04:00:00,8634.0 -2015-06-29 05:00:00,8577.0 -2015-06-29 06:00:00,8821.0 -2015-06-29 07:00:00,9414.0 -2015-06-29 08:00:00,10278.0 -2015-06-29 09:00:00,11128.0 -2015-06-29 10:00:00,11686.0 -2015-06-29 11:00:00,12158.0 -2015-06-29 12:00:00,12664.0 -2015-06-29 13:00:00,13018.0 -2015-06-29 14:00:00,13236.0 -2015-06-29 15:00:00,13505.0 -2015-06-29 16:00:00,13519.0 -2015-06-29 17:00:00,13383.0 -2015-06-29 18:00:00,13145.0 -2015-06-29 19:00:00,12952.0 -2015-06-29 20:00:00,12710.0 -2015-06-29 21:00:00,12443.0 -2015-06-29 22:00:00,12415.0 -2015-06-29 23:00:00,12247.0 -2015-06-30 00:00:00,11413.0 -2015-06-28 01:00:00,9301.0 -2015-06-28 02:00:00,8686.0 -2015-06-28 03:00:00,8233.0 -2015-06-28 04:00:00,7972.0 -2015-06-28 05:00:00,7825.0 -2015-06-28 06:00:00,7739.0 -2015-06-28 07:00:00,7579.0 -2015-06-28 08:00:00,7647.0 -2015-06-28 09:00:00,8125.0 -2015-06-28 10:00:00,8755.0 -2015-06-28 11:00:00,9361.0 -2015-06-28 12:00:00,9954.0 -2015-06-28 13:00:00,10366.0 -2015-06-28 14:00:00,10820.0 -2015-06-28 15:00:00,11114.0 -2015-06-28 16:00:00,11260.0 -2015-06-28 17:00:00,11248.0 -2015-06-28 18:00:00,11267.0 -2015-06-28 19:00:00,11131.0 -2015-06-28 20:00:00,11028.0 -2015-06-28 21:00:00,10992.0 -2015-06-28 22:00:00,11111.0 -2015-06-28 23:00:00,10974.0 -2015-06-29 00:00:00,10366.0 -2015-06-27 01:00:00,9624.0 -2015-06-27 02:00:00,9011.0 -2015-06-27 03:00:00,8641.0 -2015-06-27 04:00:00,8322.0 -2015-06-27 05:00:00,8233.0 -2015-06-27 06:00:00,8146.0 -2015-06-27 07:00:00,8187.0 -2015-06-27 08:00:00,8362.0 -2015-06-27 09:00:00,8915.0 -2015-06-27 10:00:00,9530.0 -2015-06-27 11:00:00,10088.0 -2015-06-27 12:00:00,10403.0 -2015-06-27 13:00:00,10536.0 -2015-06-27 14:00:00,10659.0 -2015-06-27 15:00:00,10716.0 -2015-06-27 16:00:00,10789.0 -2015-06-27 17:00:00,10910.0 -2015-06-27 18:00:00,11060.0 -2015-06-27 19:00:00,11114.0 -2015-06-27 20:00:00,10968.0 -2015-06-27 21:00:00,10718.0 -2015-06-27 22:00:00,10563.0 -2015-06-27 23:00:00,10535.0 -2015-06-28 00:00:00,9955.0 -2015-06-26 01:00:00,10530.0 -2015-06-26 02:00:00,9758.0 -2015-06-26 03:00:00,9258.0 -2015-06-26 04:00:00,8923.0 -2015-06-26 05:00:00,8794.0 -2015-06-26 06:00:00,8881.0 -2015-06-26 07:00:00,9271.0 -2015-06-26 08:00:00,10051.0 -2015-06-26 09:00:00,10875.0 -2015-06-26 10:00:00,11457.0 -2015-06-26 11:00:00,11845.0 -2015-06-26 12:00:00,12173.0 -2015-06-26 13:00:00,12282.0 -2015-06-26 14:00:00,12297.0 -2015-06-26 15:00:00,12349.0 -2015-06-26 16:00:00,12197.0 -2015-06-26 17:00:00,11968.0 -2015-06-26 18:00:00,11754.0 -2015-06-26 19:00:00,11532.0 -2015-06-26 20:00:00,11277.0 -2015-06-26 21:00:00,11143.0 -2015-06-26 22:00:00,11156.0 -2015-06-26 23:00:00,10986.0 -2015-06-27 00:00:00,10355.0 -2015-06-25 01:00:00,11087.0 -2015-06-25 02:00:00,10321.0 -2015-06-25 03:00:00,9799.0 -2015-06-25 04:00:00,9522.0 -2015-06-25 05:00:00,9407.0 -2015-06-25 06:00:00,9519.0 -2015-06-25 07:00:00,10020.0 -2015-06-25 08:00:00,10889.0 -2015-06-25 09:00:00,11757.0 -2015-06-25 10:00:00,12510.0 -2015-06-25 11:00:00,13235.0 -2015-06-25 12:00:00,14160.0 -2015-06-25 13:00:00,14726.0 -2015-06-25 14:00:00,14905.0 -2015-06-25 15:00:00,14898.0 -2015-06-25 16:00:00,14714.0 -2015-06-25 17:00:00,14385.0 -2015-06-25 18:00:00,14064.0 -2015-06-25 19:00:00,13715.0 -2015-06-25 20:00:00,13214.0 -2015-06-25 21:00:00,12882.0 -2015-06-25 22:00:00,12828.0 -2015-06-25 23:00:00,12513.0 -2015-06-26 00:00:00,11585.0 -2015-06-24 01:00:00,11139.0 -2015-06-24 02:00:00,10282.0 -2015-06-24 03:00:00,9656.0 -2015-06-24 04:00:00,9248.0 -2015-06-24 05:00:00,9079.0 -2015-06-24 06:00:00,9181.0 -2015-06-24 07:00:00,9502.0 -2015-06-24 08:00:00,10460.0 -2015-06-24 09:00:00,11369.0 -2015-06-24 10:00:00,11982.0 -2015-06-24 11:00:00,12459.0 -2015-06-24 12:00:00,12995.0 -2015-06-24 13:00:00,13585.0 -2015-06-24 14:00:00,14043.0 -2015-06-24 15:00:00,14390.0 -2015-06-24 16:00:00,14607.0 -2015-06-24 17:00:00,14823.0 -2015-06-24 18:00:00,14755.0 -2015-06-24 19:00:00,14278.0 -2015-06-24 20:00:00,13744.0 -2015-06-24 21:00:00,13401.0 -2015-06-24 22:00:00,13320.0 -2015-06-24 23:00:00,13089.0 -2015-06-25 00:00:00,12127.0 -2015-06-23 01:00:00,12720.0 -2015-06-23 02:00:00,11790.0 -2015-06-23 03:00:00,11150.0 -2015-06-23 04:00:00,10733.0 -2015-06-23 05:00:00,10418.0 -2015-06-23 06:00:00,10332.0 -2015-06-23 07:00:00,10470.0 -2015-06-23 08:00:00,11333.0 -2015-06-23 09:00:00,12314.0 -2015-06-23 10:00:00,12996.0 -2015-06-23 11:00:00,13509.0 -2015-06-23 12:00:00,13993.0 -2015-06-23 13:00:00,14312.0 -2015-06-23 14:00:00,14593.0 -2015-06-23 15:00:00,14856.0 -2015-06-23 16:00:00,15092.0 -2015-06-23 17:00:00,15342.0 -2015-06-23 18:00:00,15493.0 -2015-06-23 19:00:00,15398.0 -2015-06-23 20:00:00,14914.0 -2015-06-23 21:00:00,14251.0 -2015-06-23 22:00:00,13695.0 -2015-06-23 23:00:00,13404.0 -2015-06-24 00:00:00,12342.0 -2015-06-22 01:00:00,11699.0 -2015-06-22 02:00:00,10862.0 -2015-06-22 03:00:00,10212.0 -2015-06-22 04:00:00,9827.0 -2015-06-22 05:00:00,9634.0 -2015-06-22 06:00:00,9852.0 -2015-06-22 07:00:00,10394.0 -2015-06-22 08:00:00,11618.0 -2015-06-22 09:00:00,12946.0 -2015-06-22 10:00:00,13754.0 -2015-06-22 11:00:00,14428.0 -2015-06-22 12:00:00,14739.0 -2015-06-22 13:00:00,14875.0 -2015-06-22 14:00:00,14768.0 -2015-06-22 15:00:00,14873.0 -2015-06-22 16:00:00,15422.0 -2015-06-22 17:00:00,15458.0 -2015-06-22 18:00:00,15692.0 -2015-06-22 19:00:00,15951.0 -2015-06-22 20:00:00,16023.0 -2015-06-22 21:00:00,15934.0 -2015-06-22 22:00:00,16055.0 -2015-06-22 23:00:00,15655.0 -2015-06-23 00:00:00,14181.0 -2015-06-21 01:00:00,10126.0 -2015-06-21 02:00:00,9483.0 -2015-06-21 03:00:00,9061.0 -2015-06-21 04:00:00,8720.0 -2015-06-21 05:00:00,8554.0 -2015-06-21 06:00:00,8494.0 -2015-06-21 07:00:00,8347.0 -2015-06-21 08:00:00,8363.0 -2015-06-21 09:00:00,8869.0 -2015-06-21 10:00:00,9718.0 -2015-06-21 11:00:00,10497.0 -2015-06-21 12:00:00,11488.0 -2015-06-21 13:00:00,12229.0 -2015-06-21 14:00:00,12754.0 -2015-06-21 15:00:00,13306.0 -2015-06-21 16:00:00,13778.0 -2015-06-21 17:00:00,14094.0 -2015-06-21 18:00:00,14011.0 -2015-06-21 19:00:00,13832.0 -2015-06-21 20:00:00,13712.0 -2015-06-21 21:00:00,13573.0 -2015-06-21 22:00:00,13495.0 -2015-06-21 23:00:00,13500.0 -2015-06-22 00:00:00,12814.0 -2015-06-20 01:00:00,9443.0 -2015-06-20 02:00:00,8818.0 -2015-06-20 03:00:00,8459.0 -2015-06-20 04:00:00,8195.0 -2015-06-20 05:00:00,8048.0 -2015-06-20 06:00:00,8080.0 -2015-06-20 07:00:00,8085.0 -2015-06-20 08:00:00,8311.0 -2015-06-20 09:00:00,8824.0 -2015-06-20 10:00:00,9472.0 -2015-06-20 11:00:00,9947.0 -2015-06-20 12:00:00,10349.0 -2015-06-20 13:00:00,10539.0 -2015-06-20 14:00:00,10742.0 -2015-06-20 15:00:00,10881.0 -2015-06-20 16:00:00,11171.0 -2015-06-20 17:00:00,11450.0 -2015-06-20 18:00:00,11698.0 -2015-06-20 19:00:00,11711.0 -2015-06-20 20:00:00,11611.0 -2015-06-20 21:00:00,11598.0 -2015-06-20 22:00:00,11750.0 -2015-06-20 23:00:00,11550.0 -2015-06-21 00:00:00,10861.0 -2015-06-19 01:00:00,10220.0 -2015-06-19 02:00:00,9414.0 -2015-06-19 03:00:00,8918.0 -2015-06-19 04:00:00,8565.0 -2015-06-19 05:00:00,8417.0 -2015-06-19 06:00:00,8547.0 -2015-06-19 07:00:00,8738.0 -2015-06-19 08:00:00,9478.0 -2015-06-19 09:00:00,10319.0 -2015-06-19 10:00:00,10931.0 -2015-06-19 11:00:00,11377.0 -2015-06-19 12:00:00,11708.0 -2015-06-19 13:00:00,11897.0 -2015-06-19 14:00:00,12045.0 -2015-06-19 15:00:00,12212.0 -2015-06-19 16:00:00,12209.0 -2015-06-19 17:00:00,12072.0 -2015-06-19 18:00:00,11913.0 -2015-06-19 19:00:00,11641.0 -2015-06-19 20:00:00,11240.0 -2015-06-19 21:00:00,10891.0 -2015-06-19 22:00:00,10850.0 -2015-06-19 23:00:00,10898.0 -2015-06-20 00:00:00,10216.0 -2015-06-18 01:00:00,10350.0 -2015-06-18 02:00:00,9708.0 -2015-06-18 03:00:00,9282.0 -2015-06-18 04:00:00,8999.0 -2015-06-18 05:00:00,8955.0 -2015-06-18 06:00:00,9138.0 -2015-06-18 07:00:00,9644.0 -2015-06-18 08:00:00,10794.0 -2015-06-18 09:00:00,11910.0 -2015-06-18 10:00:00,12646.0 -2015-06-18 11:00:00,13267.0 -2015-06-18 12:00:00,13998.0 -2015-06-18 13:00:00,14557.0 -2015-06-18 14:00:00,14939.0 -2015-06-18 15:00:00,15311.0 -2015-06-18 16:00:00,15420.0 -2015-06-18 17:00:00,15261.0 -2015-06-18 18:00:00,14957.0 -2015-06-18 19:00:00,14602.0 -2015-06-18 20:00:00,13916.0 -2015-06-18 21:00:00,13194.0 -2015-06-18 22:00:00,12812.0 -2015-06-18 23:00:00,12357.0 -2015-06-19 00:00:00,11302.0 -2015-06-17 01:00:00,9870.0 -2015-06-17 02:00:00,9221.0 -2015-06-17 03:00:00,8798.0 -2015-06-17 04:00:00,8517.0 -2015-06-17 05:00:00,8395.0 -2015-06-17 06:00:00,8559.0 -2015-06-17 07:00:00,8967.0 -2015-06-17 08:00:00,9758.0 -2015-06-17 09:00:00,10614.0 -2015-06-17 10:00:00,11099.0 -2015-06-17 11:00:00,11345.0 -2015-06-17 12:00:00,11614.0 -2015-06-17 13:00:00,11810.0 -2015-06-17 14:00:00,11990.0 -2015-06-17 15:00:00,12192.0 -2015-06-17 16:00:00,12259.0 -2015-06-17 17:00:00,12293.0 -2015-06-17 18:00:00,12299.0 -2015-06-17 19:00:00,12208.0 -2015-06-17 20:00:00,11931.0 -2015-06-17 21:00:00,11795.0 -2015-06-17 22:00:00,11992.0 -2015-06-17 23:00:00,11917.0 -2015-06-18 00:00:00,11192.0 -2015-06-16 01:00:00,11867.0 -2015-06-16 02:00:00,10937.0 -2015-06-16 03:00:00,10234.0 -2015-06-16 04:00:00,9781.0 -2015-06-16 05:00:00,9556.0 -2015-06-16 06:00:00,9606.0 -2015-06-16 07:00:00,9942.0 -2015-06-16 08:00:00,10843.0 -2015-06-16 09:00:00,11818.0 -2015-06-16 10:00:00,12477.0 -2015-06-16 11:00:00,12900.0 -2015-06-16 12:00:00,13282.0 -2015-06-16 13:00:00,13487.0 -2015-06-16 14:00:00,13610.0 -2015-06-16 15:00:00,13828.0 -2015-06-16 16:00:00,13933.0 -2015-06-16 17:00:00,14027.0 -2015-06-16 18:00:00,13924.0 -2015-06-16 19:00:00,13582.0 -2015-06-16 20:00:00,12886.0 -2015-06-16 21:00:00,12203.0 -2015-06-16 22:00:00,11918.0 -2015-06-16 23:00:00,11697.0 -2015-06-17 00:00:00,10836.0 -2015-06-15 01:00:00,11715.0 -2015-06-15 02:00:00,10918.0 -2015-06-15 03:00:00,10317.0 -2015-06-15 04:00:00,9990.0 -2015-06-15 05:00:00,9884.0 -2015-06-15 06:00:00,10049.0 -2015-06-15 07:00:00,10602.0 -2015-06-15 08:00:00,11736.0 -2015-06-15 09:00:00,12826.0 -2015-06-15 10:00:00,13629.0 -2015-06-15 11:00:00,14272.0 -2015-06-15 12:00:00,14821.0 -2015-06-15 13:00:00,14988.0 -2015-06-15 14:00:00,14973.0 -2015-06-15 15:00:00,15349.0 -2015-06-15 16:00:00,15831.0 -2015-06-15 17:00:00,16148.0 -2015-06-15 18:00:00,16009.0 -2015-06-15 19:00:00,15471.0 -2015-06-15 20:00:00,15052.0 -2015-06-15 21:00:00,14589.0 -2015-06-15 22:00:00,14274.0 -2015-06-15 23:00:00,13780.0 -2015-06-16 00:00:00,12882.0 -2015-06-14 01:00:00,10712.0 -2015-06-14 02:00:00,10093.0 -2015-06-14 03:00:00,9608.0 -2015-06-14 04:00:00,9214.0 -2015-06-14 05:00:00,8971.0 -2015-06-14 06:00:00,8858.0 -2015-06-14 07:00:00,8905.0 -2015-06-14 08:00:00,9020.0 -2015-06-14 09:00:00,9337.0 -2015-06-14 10:00:00,9919.0 -2015-06-14 11:00:00,10570.0 -2015-06-14 12:00:00,11292.0 -2015-06-14 13:00:00,11950.0 -2015-06-14 14:00:00,12608.0 -2015-06-14 15:00:00,13107.0 -2015-06-14 16:00:00,13434.0 -2015-06-14 17:00:00,13867.0 -2015-06-14 18:00:00,14160.0 -2015-06-14 19:00:00,14239.0 -2015-06-14 20:00:00,14042.0 -2015-06-14 21:00:00,13803.0 -2015-06-14 22:00:00,13658.0 -2015-06-14 23:00:00,13504.0 -2015-06-15 00:00:00,12664.0 -2015-06-13 01:00:00,9301.0 -2015-06-13 02:00:00,8749.0 -2015-06-13 03:00:00,8396.0 -2015-06-13 04:00:00,8149.0 -2015-06-13 05:00:00,8005.0 -2015-06-13 06:00:00,8103.0 -2015-06-13 07:00:00,8164.0 -2015-06-13 08:00:00,8354.0 -2015-06-13 09:00:00,8914.0 -2015-06-13 10:00:00,9554.0 -2015-06-13 11:00:00,10276.0 -2015-06-13 12:00:00,10867.0 -2015-06-13 13:00:00,11578.0 -2015-06-13 14:00:00,12159.0 -2015-06-13 15:00:00,12703.0 -2015-06-13 16:00:00,13144.0 -2015-06-13 17:00:00,13367.0 -2015-06-13 18:00:00,13594.0 -2015-06-13 19:00:00,13313.0 -2015-06-13 20:00:00,12873.0 -2015-06-13 21:00:00,12556.0 -2015-06-13 22:00:00,12471.0 -2015-06-13 23:00:00,12113.0 -2015-06-14 00:00:00,11507.0 -2015-06-12 01:00:00,10268.0 -2015-06-12 02:00:00,9640.0 -2015-06-12 03:00:00,9190.0 -2015-06-12 04:00:00,9005.0 -2015-06-12 05:00:00,8851.0 -2015-06-12 06:00:00,9011.0 -2015-06-12 07:00:00,9409.0 -2015-06-12 08:00:00,10372.0 -2015-06-12 09:00:00,11350.0 -2015-06-12 10:00:00,11895.0 -2015-06-12 11:00:00,12339.0 -2015-06-12 12:00:00,12560.0 -2015-06-12 13:00:00,12418.0 -2015-06-12 14:00:00,12217.0 -2015-06-12 15:00:00,12041.0 -2015-06-12 16:00:00,11794.0 -2015-06-12 17:00:00,11465.0 -2015-06-12 18:00:00,11255.0 -2015-06-12 19:00:00,10966.0 -2015-06-12 20:00:00,10748.0 -2015-06-12 21:00:00,10670.0 -2015-06-12 22:00:00,10828.0 -2015-06-12 23:00:00,10651.0 -2015-06-13 00:00:00,10009.0 -2015-06-11 01:00:00,12212.0 -2015-06-11 02:00:00,11199.0 -2015-06-11 03:00:00,10494.0 -2015-06-11 04:00:00,10017.0 -2015-06-11 05:00:00,9767.0 -2015-06-11 06:00:00,9757.0 -2015-06-11 07:00:00,10052.0 -2015-06-11 08:00:00,10883.0 -2015-06-11 09:00:00,11794.0 -2015-06-11 10:00:00,12456.0 -2015-06-11 11:00:00,12806.0 -2015-06-11 12:00:00,13163.0 -2015-06-11 13:00:00,13418.0 -2015-06-11 14:00:00,13848.0 -2015-06-11 15:00:00,13976.0 -2015-06-11 16:00:00,13889.0 -2015-06-11 17:00:00,13652.0 -2015-06-11 18:00:00,13286.0 -2015-06-11 19:00:00,12756.0 -2015-06-11 20:00:00,12281.0 -2015-06-11 21:00:00,12185.0 -2015-06-11 22:00:00,12347.0 -2015-06-11 23:00:00,12047.0 -2015-06-12 00:00:00,11194.0 -2015-06-10 01:00:00,12245.0 -2015-06-10 02:00:00,11325.0 -2015-06-10 03:00:00,10669.0 -2015-06-10 04:00:00,10198.0 -2015-06-10 05:00:00,10027.0 -2015-06-10 06:00:00,10116.0 -2015-06-10 07:00:00,10537.0 -2015-06-10 08:00:00,11741.0 -2015-06-10 09:00:00,13136.0 -2015-06-10 10:00:00,14345.0 -2015-06-10 11:00:00,15292.0 -2015-06-10 12:00:00,16165.0 -2015-06-10 13:00:00,16912.0 -2015-06-10 14:00:00,17534.0 -2015-06-10 15:00:00,18170.0 -2015-06-10 16:00:00,18489.0 -2015-06-10 17:00:00,18472.0 -2015-06-10 18:00:00,18222.0 -2015-06-10 19:00:00,17629.0 -2015-06-10 20:00:00,16655.0 -2015-06-10 21:00:00,15818.0 -2015-06-10 22:00:00,15266.0 -2015-06-10 23:00:00,14790.0 -2015-06-11 00:00:00,13589.0 -2015-06-09 01:00:00,10186.0 -2015-06-09 02:00:00,9483.0 -2015-06-09 03:00:00,9023.0 -2015-06-09 04:00:00,8714.0 -2015-06-09 05:00:00,8524.0 -2015-06-09 06:00:00,8674.0 -2015-06-09 07:00:00,9143.0 -2015-06-09 08:00:00,10022.0 -2015-06-09 09:00:00,10943.0 -2015-06-09 10:00:00,11583.0 -2015-06-09 11:00:00,12120.0 -2015-06-09 12:00:00,12737.0 -2015-06-09 13:00:00,13282.0 -2015-06-09 14:00:00,13793.0 -2015-06-09 15:00:00,14363.0 -2015-06-09 16:00:00,14892.0 -2015-06-09 17:00:00,15342.0 -2015-06-09 18:00:00,15664.0 -2015-06-09 19:00:00,15742.0 -2015-06-09 20:00:00,15430.0 -2015-06-09 21:00:00,15001.0 -2015-06-09 22:00:00,14771.0 -2015-06-09 23:00:00,14537.0 -2015-06-10 00:00:00,13478.0 -2015-06-08 01:00:00,10137.0 -2015-06-08 02:00:00,9488.0 -2015-06-08 03:00:00,9076.0 -2015-06-08 04:00:00,8780.0 -2015-06-08 05:00:00,8687.0 -2015-06-08 06:00:00,8881.0 -2015-06-08 07:00:00,9393.0 -2015-06-08 08:00:00,10422.0 -2015-06-08 09:00:00,11570.0 -2015-06-08 10:00:00,12424.0 -2015-06-08 11:00:00,13163.0 -2015-06-08 12:00:00,13875.0 -2015-06-08 13:00:00,14408.0 -2015-06-08 14:00:00,14772.0 -2015-06-08 15:00:00,15171.0 -2015-06-08 16:00:00,15346.0 -2015-06-08 17:00:00,15132.0 -2015-06-08 18:00:00,14432.0 -2015-06-08 19:00:00,13863.0 -2015-06-08 20:00:00,13511.0 -2015-06-08 21:00:00,13057.0 -2015-06-08 22:00:00,12715.0 -2015-06-08 23:00:00,12253.0 -2015-06-09 00:00:00,11297.0 -2015-06-07 01:00:00,8796.0 -2015-06-07 02:00:00,8289.0 -2015-06-07 03:00:00,7941.0 -2015-06-07 04:00:00,7716.0 -2015-06-07 05:00:00,7613.0 -2015-06-07 06:00:00,7589.0 -2015-06-07 07:00:00,7537.0 -2015-06-07 08:00:00,7615.0 -2015-06-07 09:00:00,8028.0 -2015-06-07 10:00:00,8658.0 -2015-06-07 11:00:00,9075.0 -2015-06-07 12:00:00,9432.0 -2015-06-07 13:00:00,9745.0 -2015-06-07 14:00:00,9910.0 -2015-06-07 15:00:00,10067.0 -2015-06-07 16:00:00,10368.0 -2015-06-07 17:00:00,10848.0 -2015-06-07 18:00:00,11275.0 -2015-06-07 19:00:00,11428.0 -2015-06-07 20:00:00,11423.0 -2015-06-07 21:00:00,11378.0 -2015-06-07 22:00:00,11651.0 -2015-06-07 23:00:00,11594.0 -2015-06-08 00:00:00,10909.0 -2015-06-06 01:00:00,9162.0 -2015-06-06 02:00:00,8574.0 -2015-06-06 03:00:00,8168.0 -2015-06-06 04:00:00,7913.0 -2015-06-06 05:00:00,7805.0 -2015-06-06 06:00:00,7809.0 -2015-06-06 07:00:00,7825.0 -2015-06-06 08:00:00,8030.0 -2015-06-06 09:00:00,8455.0 -2015-06-06 10:00:00,9057.0 -2015-06-06 11:00:00,9514.0 -2015-06-06 12:00:00,9872.0 -2015-06-06 13:00:00,10073.0 -2015-06-06 14:00:00,10119.0 -2015-06-06 15:00:00,10139.0 -2015-06-06 16:00:00,10202.0 -2015-06-06 17:00:00,10283.0 -2015-06-06 18:00:00,10389.0 -2015-06-06 19:00:00,10399.0 -2015-06-06 20:00:00,10140.0 -2015-06-06 21:00:00,9861.0 -2015-06-06 22:00:00,9909.0 -2015-06-06 23:00:00,9917.0 -2015-06-07 00:00:00,9395.0 -2015-06-05 01:00:00,11096.0 -2015-06-05 02:00:00,10249.0 -2015-06-05 03:00:00,9627.0 -2015-06-05 04:00:00,9184.0 -2015-06-05 05:00:00,8956.0 -2015-06-05 06:00:00,9002.0 -2015-06-05 07:00:00,9332.0 -2015-06-05 08:00:00,10045.0 -2015-06-05 09:00:00,10765.0 -2015-06-05 10:00:00,11161.0 -2015-06-05 11:00:00,11472.0 -2015-06-05 12:00:00,11678.0 -2015-06-05 13:00:00,11808.0 -2015-06-05 14:00:00,11914.0 -2015-06-05 15:00:00,12014.0 -2015-06-05 16:00:00,11960.0 -2015-06-05 17:00:00,11831.0 -2015-06-05 18:00:00,11613.0 -2015-06-05 19:00:00,11356.0 -2015-06-05 20:00:00,10929.0 -2015-06-05 21:00:00,10580.0 -2015-06-05 22:00:00,10627.0 -2015-06-05 23:00:00,10577.0 -2015-06-06 00:00:00,9920.0 -2015-06-04 01:00:00,9808.0 -2015-06-04 02:00:00,9109.0 -2015-06-04 03:00:00,8688.0 -2015-06-04 04:00:00,8413.0 -2015-06-04 05:00:00,8296.0 -2015-06-04 06:00:00,8460.0 -2015-06-04 07:00:00,8902.0 -2015-06-04 08:00:00,9863.0 -2015-06-04 09:00:00,10804.0 -2015-06-04 10:00:00,11494.0 -2015-06-04 11:00:00,12019.0 -2015-06-04 12:00:00,12513.0 -2015-06-04 13:00:00,12831.0 -2015-06-04 14:00:00,13096.0 -2015-06-04 15:00:00,13515.0 -2015-06-04 16:00:00,13879.0 -2015-06-04 17:00:00,14128.0 -2015-06-04 18:00:00,14233.0 -2015-06-04 19:00:00,14071.0 -2015-06-04 20:00:00,13737.0 -2015-06-04 21:00:00,13473.0 -2015-06-04 22:00:00,13483.0 -2015-06-04 23:00:00,13238.0 -2015-06-05 00:00:00,12220.0 -2015-06-03 01:00:00,9197.0 -2015-06-03 02:00:00,8614.0 -2015-06-03 03:00:00,8266.0 -2015-06-03 04:00:00,8086.0 -2015-06-03 05:00:00,7995.0 -2015-06-03 06:00:00,8173.0 -2015-06-03 07:00:00,8545.0 -2015-06-03 08:00:00,9411.0 -2015-06-03 09:00:00,10334.0 -2015-06-03 10:00:00,10930.0 -2015-06-03 11:00:00,11311.0 -2015-06-03 12:00:00,11691.0 -2015-06-03 13:00:00,11911.0 -2015-06-03 14:00:00,12072.0 -2015-06-03 15:00:00,12338.0 -2015-06-03 16:00:00,12493.0 -2015-06-03 17:00:00,12574.0 -2015-06-03 18:00:00,12608.0 -2015-06-03 19:00:00,12460.0 -2015-06-03 20:00:00,12040.0 -2015-06-03 21:00:00,11796.0 -2015-06-03 22:00:00,11819.0 -2015-06-03 23:00:00,11643.0 -2015-06-04 00:00:00,10786.0 -2015-06-02 01:00:00,8913.0 -2015-06-02 02:00:00,8429.0 -2015-06-02 03:00:00,8131.0 -2015-06-02 04:00:00,7948.0 -2015-06-02 05:00:00,7918.0 -2015-06-02 06:00:00,8130.0 -2015-06-02 07:00:00,8502.0 -2015-06-02 08:00:00,9343.0 -2015-06-02 09:00:00,10115.0 -2015-06-02 10:00:00,10587.0 -2015-06-02 11:00:00,10904.0 -2015-06-02 12:00:00,11181.0 -2015-06-02 13:00:00,11275.0 -2015-06-02 14:00:00,11362.0 -2015-06-02 15:00:00,11462.0 -2015-06-02 16:00:00,11484.0 -2015-06-02 17:00:00,11466.0 -2015-06-02 18:00:00,11427.0 -2015-06-02 19:00:00,11320.0 -2015-06-02 20:00:00,11013.0 -2015-06-02 21:00:00,10805.0 -2015-06-02 22:00:00,10961.0 -2015-06-02 23:00:00,10847.0 -2015-06-03 00:00:00,10027.0 -2015-06-01 01:00:00,8422.0 -2015-06-01 02:00:00,8042.0 -2015-06-01 03:00:00,7846.0 -2015-06-01 04:00:00,7711.0 -2015-06-01 05:00:00,7746.0 -2015-06-01 06:00:00,7922.0 -2015-06-01 07:00:00,8466.0 -2015-06-01 08:00:00,9371.0 -2015-06-01 09:00:00,10145.0 -2015-06-01 10:00:00,10505.0 -2015-06-01 11:00:00,10699.0 -2015-06-01 12:00:00,10855.0 -2015-06-01 13:00:00,10900.0 -2015-06-01 14:00:00,10914.0 -2015-06-01 15:00:00,10977.0 -2015-06-01 16:00:00,10950.0 -2015-06-01 17:00:00,10820.0 -2015-06-01 18:00:00,10723.0 -2015-06-01 19:00:00,10570.0 -2015-06-01 20:00:00,10323.0 -2015-06-01 21:00:00,10257.0 -2015-06-01 22:00:00,10504.0 -2015-06-01 23:00:00,10408.0 -2015-06-02 00:00:00,9683.0 -2015-05-31 01:00:00,8643.0 -2015-05-31 02:00:00,8168.0 -2015-05-31 03:00:00,7890.0 -2015-05-31 04:00:00,7657.0 -2015-05-31 05:00:00,7556.0 -2015-05-31 06:00:00,7536.0 -2015-05-31 07:00:00,7501.0 -2015-05-31 08:00:00,7545.0 -2015-05-31 09:00:00,7855.0 -2015-05-31 10:00:00,8247.0 -2015-05-31 11:00:00,8557.0 -2015-05-31 12:00:00,8733.0 -2015-05-31 13:00:00,8817.0 -2015-05-31 14:00:00,8830.0 -2015-05-31 15:00:00,8827.0 -2015-05-31 16:00:00,8785.0 -2015-05-31 17:00:00,8752.0 -2015-05-31 18:00:00,8782.0 -2015-05-31 19:00:00,8824.0 -2015-05-31 20:00:00,8882.0 -2015-05-31 21:00:00,9021.0 -2015-05-31 22:00:00,9409.0 -2015-05-31 23:00:00,9421.0 -2015-06-01 00:00:00,8948.0 -2015-05-30 01:00:00,11433.0 -2015-05-30 02:00:00,10573.0 -2015-05-30 03:00:00,9978.0 -2015-05-30 04:00:00,9572.0 -2015-05-30 05:00:00,9326.0 -2015-05-30 06:00:00,9302.0 -2015-05-30 07:00:00,9387.0 -2015-05-30 08:00:00,9560.0 -2015-05-30 09:00:00,10083.0 -2015-05-30 10:00:00,10657.0 -2015-05-30 11:00:00,11036.0 -2015-05-30 12:00:00,11130.0 -2015-05-30 13:00:00,10847.0 -2015-05-30 14:00:00,10517.0 -2015-05-30 15:00:00,10044.0 -2015-05-30 16:00:00,9756.0 -2015-05-30 17:00:00,9620.0 -2015-05-30 18:00:00,9555.0 -2015-05-30 19:00:00,9500.0 -2015-05-30 20:00:00,9422.0 -2015-05-30 21:00:00,9518.0 -2015-05-30 22:00:00,9745.0 -2015-05-30 23:00:00,9661.0 -2015-05-31 00:00:00,9200.0 -2015-05-29 01:00:00,10633.0 -2015-05-29 02:00:00,9839.0 -2015-05-29 03:00:00,9340.0 -2015-05-29 04:00:00,9093.0 -2015-05-29 05:00:00,8953.0 -2015-05-29 06:00:00,9140.0 -2015-05-29 07:00:00,9636.0 -2015-05-29 08:00:00,10636.0 -2015-05-29 09:00:00,11789.0 -2015-05-29 10:00:00,12767.0 -2015-05-29 11:00:00,13558.0 -2015-05-29 12:00:00,14093.0 -2015-05-29 13:00:00,14158.0 -2015-05-29 14:00:00,14321.0 -2015-05-29 15:00:00,14716.0 -2015-05-29 16:00:00,14800.0 -2015-05-29 17:00:00,14791.0 -2015-05-29 18:00:00,14659.0 -2015-05-29 19:00:00,14483.0 -2015-05-29 20:00:00,14150.0 -2015-05-29 21:00:00,13698.0 -2015-05-29 22:00:00,13705.0 -2015-05-29 23:00:00,13423.0 -2015-05-30 00:00:00,12487.0 -2015-05-28 01:00:00,9951.0 -2015-05-28 02:00:00,9257.0 -2015-05-28 03:00:00,8858.0 -2015-05-28 04:00:00,8591.0 -2015-05-28 05:00:00,8453.0 -2015-05-28 06:00:00,8608.0 -2015-05-28 07:00:00,8997.0 -2015-05-28 08:00:00,9916.0 -2015-05-28 09:00:00,11017.0 -2015-05-28 10:00:00,11709.0 -2015-05-28 11:00:00,12337.0 -2015-05-28 12:00:00,12927.0 -2015-05-28 13:00:00,13316.0 -2015-05-28 14:00:00,13649.0 -2015-05-28 15:00:00,14088.0 -2015-05-28 16:00:00,14343.0 -2015-05-28 17:00:00,14579.0 -2015-05-28 18:00:00,14644.0 -2015-05-28 19:00:00,14457.0 -2015-05-28 20:00:00,13976.0 -2015-05-28 21:00:00,13397.0 -2015-05-28 22:00:00,13196.0 -2015-05-28 23:00:00,12786.0 -2015-05-29 00:00:00,11747.0 -2015-05-27 01:00:00,10250.0 -2015-05-27 02:00:00,9618.0 -2015-05-27 03:00:00,9197.0 -2015-05-27 04:00:00,8899.0 -2015-05-27 05:00:00,8757.0 -2015-05-27 06:00:00,8911.0 -2015-05-27 07:00:00,9361.0 -2015-05-27 08:00:00,10398.0 -2015-05-27 09:00:00,11374.0 -2015-05-27 10:00:00,11955.0 -2015-05-27 11:00:00,12302.0 -2015-05-27 12:00:00,12650.0 -2015-05-27 13:00:00,12794.0 -2015-05-27 14:00:00,12833.0 -2015-05-27 15:00:00,12980.0 -2015-05-27 16:00:00,12953.0 -2015-05-27 17:00:00,12844.0 -2015-05-27 18:00:00,12905.0 -2015-05-27 19:00:00,12900.0 -2015-05-27 20:00:00,12587.0 -2015-05-27 21:00:00,12227.0 -2015-05-27 22:00:00,12264.0 -2015-05-27 23:00:00,11866.0 -2015-05-28 00:00:00,10944.0 -2015-05-26 01:00:00,9759.0 -2015-05-26 02:00:00,9131.0 -2015-05-26 03:00:00,8729.0 -2015-05-26 04:00:00,8456.0 -2015-05-26 05:00:00,8398.0 -2015-05-26 06:00:00,8622.0 -2015-05-26 07:00:00,9305.0 -2015-05-26 08:00:00,10424.0 -2015-05-26 09:00:00,11467.0 -2015-05-26 10:00:00,12073.0 -2015-05-26 11:00:00,12489.0 -2015-05-26 12:00:00,12813.0 -2015-05-26 13:00:00,12976.0 -2015-05-26 14:00:00,13002.0 -2015-05-26 15:00:00,13016.0 -2015-05-26 16:00:00,13155.0 -2015-05-26 17:00:00,13295.0 -2015-05-26 18:00:00,13319.0 -2015-05-26 19:00:00,13149.0 -2015-05-26 20:00:00,12710.0 -2015-05-26 21:00:00,12353.0 -2015-05-26 22:00:00,12363.0 -2015-05-26 23:00:00,12089.0 -2015-05-27 00:00:00,11177.0 -2015-05-25 01:00:00,8979.0 -2015-05-25 02:00:00,8506.0 -2015-05-25 03:00:00,8252.0 -2015-05-25 04:00:00,8053.0 -2015-05-25 05:00:00,7969.0 -2015-05-25 06:00:00,7940.0 -2015-05-25 07:00:00,8095.0 -2015-05-25 08:00:00,8129.0 -2015-05-25 09:00:00,8530.0 -2015-05-25 10:00:00,8974.0 -2015-05-25 11:00:00,9515.0 -2015-05-25 12:00:00,9841.0 -2015-05-25 13:00:00,10138.0 -2015-05-25 14:00:00,10334.0 -2015-05-25 15:00:00,10490.0 -2015-05-25 16:00:00,10687.0 -2015-05-25 17:00:00,10866.0 -2015-05-25 18:00:00,11041.0 -2015-05-25 19:00:00,11197.0 -2015-05-25 20:00:00,11023.0 -2015-05-25 21:00:00,10985.0 -2015-05-25 22:00:00,11318.0 -2015-05-25 23:00:00,11273.0 -2015-05-26 00:00:00,10563.0 -2015-05-24 01:00:00,8933.0 -2015-05-24 02:00:00,8407.0 -2015-05-24 03:00:00,8015.0 -2015-05-24 04:00:00,7761.0 -2015-05-24 05:00:00,7613.0 -2015-05-24 06:00:00,7561.0 -2015-05-24 07:00:00,7495.0 -2015-05-24 08:00:00,7484.0 -2015-05-24 09:00:00,7786.0 -2015-05-24 10:00:00,8259.0 -2015-05-24 11:00:00,8643.0 -2015-05-24 12:00:00,8989.0 -2015-05-24 13:00:00,9202.0 -2015-05-24 14:00:00,9289.0 -2015-05-24 15:00:00,9367.0 -2015-05-24 16:00:00,9405.0 -2015-05-24 17:00:00,9462.0 -2015-05-24 18:00:00,9462.0 -2015-05-24 19:00:00,9450.0 -2015-05-24 20:00:00,9430.0 -2015-05-24 21:00:00,9566.0 -2015-05-24 22:00:00,9830.0 -2015-05-24 23:00:00,9817.0 -2015-05-25 00:00:00,9430.0 -2015-05-23 01:00:00,8835.0 -2015-05-23 02:00:00,8331.0 -2015-05-23 03:00:00,7932.0 -2015-05-23 04:00:00,7797.0 -2015-05-23 05:00:00,7654.0 -2015-05-23 06:00:00,7697.0 -2015-05-23 07:00:00,7606.0 -2015-05-23 08:00:00,7788.0 -2015-05-23 09:00:00,8239.0 -2015-05-23 10:00:00,8790.0 -2015-05-23 11:00:00,9209.0 -2015-05-23 12:00:00,9481.0 -2015-05-23 13:00:00,9633.0 -2015-05-23 14:00:00,9763.0 -2015-05-23 15:00:00,9798.0 -2015-05-23 16:00:00,9871.0 -2015-05-23 17:00:00,10025.0 -2015-05-23 18:00:00,10138.0 -2015-05-23 19:00:00,10216.0 -2015-05-23 20:00:00,10141.0 -2015-05-23 21:00:00,9960.0 -2015-05-23 22:00:00,10176.0 -2015-05-23 23:00:00,10067.0 -2015-05-24 00:00:00,9531.0 -2015-05-22 01:00:00,9118.0 -2015-05-22 02:00:00,8625.0 -2015-05-22 03:00:00,8266.0 -2015-05-22 04:00:00,8061.0 -2015-05-22 05:00:00,7983.0 -2015-05-22 06:00:00,8139.0 -2015-05-22 07:00:00,8521.0 -2015-05-22 08:00:00,9331.0 -2015-05-22 09:00:00,10082.0 -2015-05-22 10:00:00,10532.0 -2015-05-22 11:00:00,10788.0 -2015-05-22 12:00:00,10978.0 -2015-05-22 13:00:00,11047.0 -2015-05-22 14:00:00,11063.0 -2015-05-22 15:00:00,11105.0 -2015-05-22 16:00:00,11059.0 -2015-05-22 17:00:00,10929.0 -2015-05-22 18:00:00,10727.0 -2015-05-22 19:00:00,10442.0 -2015-05-22 20:00:00,10129.0 -2015-05-22 21:00:00,10040.0 -2015-05-22 22:00:00,10359.0 -2015-05-22 23:00:00,10200.0 -2015-05-23 00:00:00,9550.0 -2015-05-21 01:00:00,9285.0 -2015-05-21 02:00:00,8817.0 -2015-05-21 03:00:00,8521.0 -2015-05-21 04:00:00,8321.0 -2015-05-21 05:00:00,8278.0 -2015-05-21 06:00:00,8493.0 -2015-05-21 07:00:00,9081.0 -2015-05-21 08:00:00,9944.0 -2015-05-21 09:00:00,10661.0 -2015-05-21 10:00:00,10929.0 -2015-05-21 11:00:00,10997.0 -2015-05-21 12:00:00,11082.0 -2015-05-21 13:00:00,11123.0 -2015-05-21 14:00:00,11153.0 -2015-05-21 15:00:00,11236.0 -2015-05-21 16:00:00,11141.0 -2015-05-21 17:00:00,11080.0 -2015-05-21 18:00:00,10989.0 -2015-05-21 19:00:00,10848.0 -2015-05-21 20:00:00,10588.0 -2015-05-21 21:00:00,10485.0 -2015-05-21 22:00:00,10836.0 -2015-05-21 23:00:00,10654.0 -2015-05-22 00:00:00,9928.0 -2015-05-20 01:00:00,9141.0 -2015-05-20 02:00:00,8685.0 -2015-05-20 03:00:00,8354.0 -2015-05-20 04:00:00,8190.0 -2015-05-20 05:00:00,8116.0 -2015-05-20 06:00:00,8347.0 -2015-05-20 07:00:00,8876.0 -2015-05-20 08:00:00,9751.0 -2015-05-20 09:00:00,10513.0 -2015-05-20 10:00:00,10773.0 -2015-05-20 11:00:00,10900.0 -2015-05-20 12:00:00,11020.0 -2015-05-20 13:00:00,11070.0 -2015-05-20 14:00:00,11058.0 -2015-05-20 15:00:00,11152.0 -2015-05-20 16:00:00,11062.0 -2015-05-20 17:00:00,10992.0 -2015-05-20 18:00:00,11003.0 -2015-05-20 19:00:00,10931.0 -2015-05-20 20:00:00,10783.0 -2015-05-20 21:00:00,10766.0 -2015-05-20 22:00:00,11084.0 -2015-05-20 23:00:00,10746.0 -2015-05-21 00:00:00,10011.0 -2015-05-19 01:00:00,9223.0 -2015-05-19 02:00:00,8672.0 -2015-05-19 03:00:00,8322.0 -2015-05-19 04:00:00,8083.0 -2015-05-19 05:00:00,8020.0 -2015-05-19 06:00:00,8205.0 -2015-05-19 07:00:00,8717.0 -2015-05-19 08:00:00,9555.0 -2015-05-19 09:00:00,10297.0 -2015-05-19 10:00:00,10640.0 -2015-05-19 11:00:00,10821.0 -2015-05-19 12:00:00,10936.0 -2015-05-19 13:00:00,10962.0 -2015-05-19 14:00:00,10974.0 -2015-05-19 15:00:00,10982.0 -2015-05-19 16:00:00,10867.0 -2015-05-19 17:00:00,10701.0 -2015-05-19 18:00:00,10587.0 -2015-05-19 19:00:00,10476.0 -2015-05-19 20:00:00,10221.0 -2015-05-19 21:00:00,10256.0 -2015-05-19 22:00:00,10773.0 -2015-05-19 23:00:00,10543.0 -2015-05-20 00:00:00,9883.0 -2015-05-18 01:00:00,10405.0 -2015-05-18 02:00:00,9742.0 -2015-05-18 03:00:00,9349.0 -2015-05-18 04:00:00,9056.0 -2015-05-18 05:00:00,8988.0 -2015-05-18 06:00:00,9143.0 -2015-05-18 07:00:00,9631.0 -2015-05-18 08:00:00,10723.0 -2015-05-18 09:00:00,11842.0 -2015-05-18 10:00:00,12447.0 -2015-05-18 11:00:00,12899.0 -2015-05-18 12:00:00,13384.0 -2015-05-18 13:00:00,13735.0 -2015-05-18 14:00:00,13847.0 -2015-05-18 15:00:00,13960.0 -2015-05-18 16:00:00,13852.0 -2015-05-18 17:00:00,13688.0 -2015-05-18 18:00:00,13384.0 -2015-05-18 19:00:00,12876.0 -2015-05-18 20:00:00,12077.0 -2015-05-18 21:00:00,11504.0 -2015-05-18 22:00:00,11436.0 -2015-05-18 23:00:00,10995.0 -2015-05-19 00:00:00,10065.0 -2015-05-17 01:00:00,10101.0 -2015-05-17 02:00:00,9440.0 -2015-05-17 03:00:00,9283.0 -2015-05-17 04:00:00,8980.0 -2015-05-17 05:00:00,8441.0 -2015-05-17 06:00:00,8408.0 -2015-05-17 07:00:00,8372.0 -2015-05-17 08:00:00,8441.0 -2015-05-17 09:00:00,8787.0 -2015-05-17 10:00:00,9322.0 -2015-05-17 11:00:00,9814.0 -2015-05-17 12:00:00,10303.0 -2015-05-17 13:00:00,10798.0 -2015-05-17 14:00:00,11175.0 -2015-05-17 15:00:00,11728.0 -2015-05-17 16:00:00,12152.0 -2015-05-17 17:00:00,12381.0 -2015-05-17 18:00:00,12175.0 -2015-05-17 19:00:00,12245.0 -2015-05-17 20:00:00,12263.0 -2015-05-17 21:00:00,12004.0 -2015-05-17 22:00:00,12518.0 -2015-05-17 23:00:00,12342.0 -2015-05-18 00:00:00,11214.0 -2015-05-16 01:00:00,9821.0 -2015-05-16 02:00:00,9141.0 -2015-05-16 03:00:00,8755.0 -2015-05-16 04:00:00,8457.0 -2015-05-16 05:00:00,8341.0 -2015-05-16 06:00:00,8271.0 -2015-05-16 07:00:00,8433.0 -2015-05-16 08:00:00,8635.0 -2015-05-16 09:00:00,9148.0 -2015-05-16 10:00:00,9759.0 -2015-05-16 11:00:00,10254.0 -2015-05-16 12:00:00,10590.0 -2015-05-16 13:00:00,10888.0 -2015-05-16 14:00:00,11002.0 -2015-05-16 15:00:00,11059.0 -2015-05-16 16:00:00,11244.0 -2015-05-16 17:00:00,11477.0 -2015-05-16 18:00:00,11695.0 -2015-05-16 19:00:00,11680.0 -2015-05-16 20:00:00,11522.0 -2015-05-16 21:00:00,11336.0 -2015-05-16 22:00:00,11646.0 -2015-05-16 23:00:00,11476.0 -2015-05-17 00:00:00,10862.0 -2015-05-15 01:00:00,9108.0 -2015-05-15 02:00:00,8598.0 -2015-05-15 03:00:00,8289.0 -2015-05-15 04:00:00,8094.0 -2015-05-15 05:00:00,8040.0 -2015-05-15 06:00:00,8243.0 -2015-05-15 07:00:00,8773.0 -2015-05-15 08:00:00,9615.0 -2015-05-15 09:00:00,10497.0 -2015-05-15 10:00:00,11002.0 -2015-05-15 11:00:00,11341.0 -2015-05-15 12:00:00,11609.0 -2015-05-15 13:00:00,11722.0 -2015-05-15 14:00:00,11839.0 -2015-05-15 15:00:00,11969.0 -2015-05-15 16:00:00,11963.0 -2015-05-15 17:00:00,11934.0 -2015-05-15 18:00:00,11891.0 -2015-05-15 19:00:00,11729.0 -2015-05-15 20:00:00,11408.0 -2015-05-15 21:00:00,11309.0 -2015-05-15 22:00:00,11613.0 -2015-05-15 23:00:00,11344.0 -2015-05-16 00:00:00,10593.0 -2015-05-14 01:00:00,9181.0 -2015-05-14 02:00:00,8722.0 -2015-05-14 03:00:00,8458.0 -2015-05-14 04:00:00,8269.0 -2015-05-14 05:00:00,8240.0 -2015-05-14 06:00:00,8431.0 -2015-05-14 07:00:00,8957.0 -2015-05-14 08:00:00,9776.0 -2015-05-14 09:00:00,10508.0 -2015-05-14 10:00:00,10819.0 -2015-05-14 11:00:00,10967.0 -2015-05-14 12:00:00,11151.0 -2015-05-14 13:00:00,11198.0 -2015-05-14 14:00:00,11195.0 -2015-05-14 15:00:00,11235.0 -2015-05-14 16:00:00,11175.0 -2015-05-14 17:00:00,11037.0 -2015-05-14 18:00:00,10912.0 -2015-05-14 19:00:00,10748.0 -2015-05-14 20:00:00,10698.0 -2015-05-14 21:00:00,10821.0 -2015-05-14 22:00:00,10991.0 -2015-05-14 23:00:00,10593.0 -2015-05-15 00:00:00,9847.0 -2015-05-13 01:00:00,9168.0 -2015-05-13 02:00:00,8701.0 -2015-05-13 03:00:00,8466.0 -2015-05-13 04:00:00,8309.0 -2015-05-13 05:00:00,8297.0 -2015-05-13 06:00:00,8539.0 -2015-05-13 07:00:00,9091.0 -2015-05-13 08:00:00,9936.0 -2015-05-13 09:00:00,10556.0 -2015-05-13 10:00:00,10828.0 -2015-05-13 11:00:00,10964.0 -2015-05-13 12:00:00,10979.0 -2015-05-13 13:00:00,10977.0 -2015-05-13 14:00:00,11004.0 -2015-05-13 15:00:00,10996.0 -2015-05-13 16:00:00,10909.0 -2015-05-13 17:00:00,10751.0 -2015-05-13 18:00:00,10639.0 -2015-05-13 19:00:00,10479.0 -2015-05-13 20:00:00,10300.0 -2015-05-13 21:00:00,10347.0 -2015-05-13 22:00:00,10880.0 -2015-05-13 23:00:00,10651.0 -2015-05-14 00:00:00,9888.0 -2015-05-12 01:00:00,9142.0 -2015-05-12 02:00:00,8654.0 -2015-05-12 03:00:00,8380.0 -2015-05-12 04:00:00,8230.0 -2015-05-12 05:00:00,8148.0 -2015-05-12 06:00:00,8383.0 -2015-05-12 07:00:00,8972.0 -2015-05-12 08:00:00,9809.0 -2015-05-12 09:00:00,10575.0 -2015-05-12 10:00:00,10922.0 -2015-05-12 11:00:00,11062.0 -2015-05-12 12:00:00,11165.0 -2015-05-12 13:00:00,11144.0 -2015-05-12 14:00:00,11110.0 -2015-05-12 15:00:00,11114.0 -2015-05-12 16:00:00,11016.0 -2015-05-12 17:00:00,10832.0 -2015-05-12 18:00:00,10703.0 -2015-05-12 19:00:00,10570.0 -2015-05-12 20:00:00,10390.0 -2015-05-12 21:00:00,10561.0 -2015-05-12 22:00:00,10986.0 -2015-05-12 23:00:00,10660.0 -2015-05-13 00:00:00,9989.0 -2015-05-11 01:00:00,8467.0 -2015-05-11 02:00:00,8123.0 -2015-05-11 03:00:00,7867.0 -2015-05-11 04:00:00,7840.0 -2015-05-11 05:00:00,7837.0 -2015-05-11 06:00:00,8112.0 -2015-05-11 07:00:00,8723.0 -2015-05-11 08:00:00,9729.0 -2015-05-11 09:00:00,10764.0 -2015-05-11 10:00:00,11243.0 -2015-05-11 11:00:00,11457.0 -2015-05-11 12:00:00,11638.0 -2015-05-11 13:00:00,11734.0 -2015-05-11 14:00:00,11767.0 -2015-05-11 15:00:00,11795.0 -2015-05-11 16:00:00,11679.0 -2015-05-11 17:00:00,11522.0 -2015-05-11 18:00:00,11425.0 -2015-05-11 19:00:00,11201.0 -2015-05-11 20:00:00,10854.0 -2015-05-11 21:00:00,10740.0 -2015-05-11 22:00:00,11055.0 -2015-05-11 23:00:00,10658.0 -2015-05-12 00:00:00,9875.0 -2015-05-10 01:00:00,8659.0 -2015-05-10 02:00:00,8165.0 -2015-05-10 03:00:00,7872.0 -2015-05-10 04:00:00,7669.0 -2015-05-10 05:00:00,7601.0 -2015-05-10 06:00:00,7583.0 -2015-05-10 07:00:00,7665.0 -2015-05-10 08:00:00,7688.0 -2015-05-10 09:00:00,7952.0 -2015-05-10 10:00:00,8426.0 -2015-05-10 11:00:00,8713.0 -2015-05-10 12:00:00,8910.0 -2015-05-10 13:00:00,8964.0 -2015-05-10 14:00:00,8921.0 -2015-05-10 15:00:00,8838.0 -2015-05-10 16:00:00,8791.0 -2015-05-10 17:00:00,8753.0 -2015-05-10 18:00:00,8782.0 -2015-05-10 19:00:00,8781.0 -2015-05-10 20:00:00,8883.0 -2015-05-10 21:00:00,9170.0 -2015-05-10 22:00:00,9682.0 -2015-05-10 23:00:00,9526.0 -2015-05-11 00:00:00,9051.0 -2015-05-09 01:00:00,10108.0 -2015-05-09 02:00:00,9451.0 -2015-05-09 03:00:00,9048.0 -2015-05-09 04:00:00,8763.0 -2015-05-09 05:00:00,8588.0 -2015-05-09 06:00:00,8576.0 -2015-05-09 07:00:00,8655.0 -2015-05-09 08:00:00,8728.0 -2015-05-09 09:00:00,9036.0 -2015-05-09 10:00:00,9483.0 -2015-05-09 11:00:00,9787.0 -2015-05-09 12:00:00,9850.0 -2015-05-09 13:00:00,9907.0 -2015-05-09 14:00:00,9970.0 -2015-05-09 15:00:00,9842.0 -2015-05-09 16:00:00,9759.0 -2015-05-09 17:00:00,9583.0 -2015-05-09 18:00:00,9517.0 -2015-05-09 19:00:00,9369.0 -2015-05-09 20:00:00,9319.0 -2015-05-09 21:00:00,9425.0 -2015-05-09 22:00:00,9866.0 -2015-05-09 23:00:00,9673.0 -2015-05-10 00:00:00,9191.0 -2015-05-08 01:00:00,10338.0 -2015-05-08 02:00:00,9655.0 -2015-05-08 03:00:00,9218.0 -2015-05-08 04:00:00,8924.0 -2015-05-08 05:00:00,8776.0 -2015-05-08 06:00:00,8908.0 -2015-05-08 07:00:00,9496.0 -2015-05-08 08:00:00,10308.0 -2015-05-08 09:00:00,11277.0 -2015-05-08 10:00:00,12006.0 -2015-05-08 11:00:00,12519.0 -2015-05-08 12:00:00,12913.0 -2015-05-08 13:00:00,13270.0 -2015-05-08 14:00:00,13611.0 -2015-05-08 15:00:00,13947.0 -2015-05-08 16:00:00,13979.0 -2015-05-08 17:00:00,13793.0 -2015-05-08 18:00:00,13310.0 -2015-05-08 19:00:00,12822.0 -2015-05-08 20:00:00,12339.0 -2015-05-08 21:00:00,12281.0 -2015-05-08 22:00:00,12258.0 -2015-05-08 23:00:00,11862.0 -2015-05-09 00:00:00,10998.0 -2015-05-07 01:00:00,9372.0 -2015-05-07 02:00:00,8756.0 -2015-05-07 03:00:00,8406.0 -2015-05-07 04:00:00,8223.0 -2015-05-07 05:00:00,8148.0 -2015-05-07 06:00:00,8342.0 -2015-05-07 07:00:00,8920.0 -2015-05-07 08:00:00,9779.0 -2015-05-07 09:00:00,10731.0 -2015-05-07 10:00:00,11371.0 -2015-05-07 11:00:00,11818.0 -2015-05-07 12:00:00,12309.0 -2015-05-07 13:00:00,12669.0 -2015-05-07 14:00:00,12800.0 -2015-05-07 15:00:00,13003.0 -2015-05-07 16:00:00,13143.0 -2015-05-07 17:00:00,13090.0 -2015-05-07 18:00:00,13087.0 -2015-05-07 19:00:00,12969.0 -2015-05-07 20:00:00,12623.0 -2015-05-07 21:00:00,12494.0 -2015-05-07 22:00:00,12692.0 -2015-05-07 23:00:00,12197.0 -2015-05-08 00:00:00,11269.0 -2015-05-06 01:00:00,9076.0 -2015-05-06 02:00:00,8606.0 -2015-05-06 03:00:00,8325.0 -2015-05-06 04:00:00,8119.0 -2015-05-06 05:00:00,8096.0 -2015-05-06 06:00:00,8240.0 -2015-05-06 07:00:00,8894.0 -2015-05-06 08:00:00,9756.0 -2015-05-06 09:00:00,10496.0 -2015-05-06 10:00:00,10869.0 -2015-05-06 11:00:00,11162.0 -2015-05-06 12:00:00,11428.0 -2015-05-06 13:00:00,11632.0 -2015-05-06 14:00:00,11811.0 -2015-05-06 15:00:00,12056.0 -2015-05-06 16:00:00,12118.0 -2015-05-06 17:00:00,12056.0 -2015-05-06 18:00:00,11923.0 -2015-05-06 19:00:00,11697.0 -2015-05-06 20:00:00,11306.0 -2015-05-06 21:00:00,11212.0 -2015-05-06 22:00:00,11542.0 -2015-05-06 23:00:00,11125.0 -2015-05-07 00:00:00,10250.0 -2015-05-05 01:00:00,9077.0 -2015-05-05 02:00:00,8552.0 -2015-05-05 03:00:00,8229.0 -2015-05-05 04:00:00,8031.0 -2015-05-05 05:00:00,8027.0 -2015-05-05 06:00:00,8250.0 -2015-05-05 07:00:00,8882.0 -2015-05-05 08:00:00,9745.0 -2015-05-05 09:00:00,10439.0 -2015-05-05 10:00:00,10780.0 -2015-05-05 11:00:00,11028.0 -2015-05-05 12:00:00,11192.0 -2015-05-05 13:00:00,11195.0 -2015-05-05 14:00:00,11175.0 -2015-05-05 15:00:00,11181.0 -2015-05-05 16:00:00,11071.0 -2015-05-05 17:00:00,10943.0 -2015-05-05 18:00:00,10842.0 -2015-05-05 19:00:00,10705.0 -2015-05-05 20:00:00,10564.0 -2015-05-05 21:00:00,10703.0 -2015-05-05 22:00:00,11026.0 -2015-05-05 23:00:00,10634.0 -2015-05-06 00:00:00,9852.0 -2015-05-04 01:00:00,8720.0 -2015-05-04 02:00:00,8319.0 -2015-05-04 03:00:00,8046.0 -2015-05-04 04:00:00,7936.0 -2015-05-04 05:00:00,7856.0 -2015-05-04 06:00:00,8087.0 -2015-05-04 07:00:00,8773.0 -2015-05-04 08:00:00,9743.0 -2015-05-04 09:00:00,10551.0 -2015-05-04 10:00:00,10991.0 -2015-05-04 11:00:00,11293.0 -2015-05-04 12:00:00,11613.0 -2015-05-04 13:00:00,11800.0 -2015-05-04 14:00:00,11903.0 -2015-05-04 15:00:00,11954.0 -2015-05-04 16:00:00,11885.0 -2015-05-04 17:00:00,11765.0 -2015-05-04 18:00:00,11578.0 -2015-05-04 19:00:00,11306.0 -2015-05-04 20:00:00,10982.0 -2015-05-04 21:00:00,10908.0 -2015-05-04 22:00:00,11165.0 -2015-05-04 23:00:00,10723.0 -2015-05-05 00:00:00,9886.0 -2015-05-03 01:00:00,8601.0 -2015-05-03 02:00:00,8198.0 -2015-05-03 03:00:00,7867.0 -2015-05-03 04:00:00,7631.0 -2015-05-03 05:00:00,7510.0 -2015-05-03 06:00:00,7473.0 -2015-05-03 07:00:00,7523.0 -2015-05-03 08:00:00,7433.0 -2015-05-03 09:00:00,7727.0 -2015-05-03 10:00:00,8175.0 -2015-05-03 11:00:00,8553.0 -2015-05-03 12:00:00,8881.0 -2015-05-03 13:00:00,9093.0 -2015-05-03 14:00:00,9287.0 -2015-05-03 15:00:00,9284.0 -2015-05-03 16:00:00,9336.0 -2015-05-03 17:00:00,9361.0 -2015-05-03 18:00:00,9483.0 -2015-05-03 19:00:00,9694.0 -2015-05-03 20:00:00,9931.0 -2015-05-03 21:00:00,10046.0 -2015-05-03 22:00:00,10175.0 -2015-05-03 23:00:00,9830.0 -2015-05-04 00:00:00,9280.0 -2015-05-02 01:00:00,9041.0 -2015-05-02 02:00:00,8455.0 -2015-05-02 03:00:00,8177.0 -2015-05-02 04:00:00,7920.0 -2015-05-02 05:00:00,7864.0 -2015-05-02 06:00:00,7881.0 -2015-05-02 07:00:00,8078.0 -2015-05-02 08:00:00,8189.0 -2015-05-02 09:00:00,8628.0 -2015-05-02 10:00:00,9093.0 -2015-05-02 11:00:00,9390.0 -2015-05-02 12:00:00,9656.0 -2015-05-02 13:00:00,9700.0 -2015-05-02 14:00:00,9726.0 -2015-05-02 15:00:00,9607.0 -2015-05-02 16:00:00,9568.0 -2015-05-02 17:00:00,9541.0 -2015-05-02 18:00:00,9555.0 -2015-05-02 19:00:00,9490.0 -2015-05-02 20:00:00,9444.0 -2015-05-02 21:00:00,9505.0 -2015-05-02 22:00:00,9923.0 -2015-05-02 23:00:00,9689.0 -2015-05-03 00:00:00,9167.0 -2015-05-01 01:00:00,9254.0 -2015-05-01 02:00:00,8753.0 -2015-05-01 03:00:00,8509.0 -2015-05-01 04:00:00,8385.0 -2015-05-01 05:00:00,8389.0 -2015-05-01 06:00:00,8634.0 -2015-05-01 07:00:00,9248.0 -2015-05-01 08:00:00,9961.0 -2015-05-01 09:00:00,10500.0 -2015-05-01 10:00:00,10678.0 -2015-05-01 11:00:00,10772.0 -2015-05-01 12:00:00,10925.0 -2015-05-01 13:00:00,10916.0 -2015-05-01 14:00:00,10914.0 -2015-05-01 15:00:00,10972.0 -2015-05-01 16:00:00,10846.0 -2015-05-01 17:00:00,10701.0 -2015-05-01 18:00:00,10549.0 -2015-05-01 19:00:00,10328.0 -2015-05-01 20:00:00,10136.0 -2015-05-01 21:00:00,10251.0 -2015-05-01 22:00:00,10611.0 -2015-05-01 23:00:00,10323.0 -2015-05-02 00:00:00,9670.0 -2015-04-30 01:00:00,9058.0 -2015-04-30 02:00:00,8593.0 -2015-04-30 03:00:00,8343.0 -2015-04-30 04:00:00,8209.0 -2015-04-30 05:00:00,8187.0 -2015-04-30 06:00:00,8425.0 -2015-04-30 07:00:00,9108.0 -2015-04-30 08:00:00,9962.0 -2015-04-30 09:00:00,10676.0 -2015-04-30 10:00:00,10919.0 -2015-04-30 11:00:00,10944.0 -2015-04-30 12:00:00,11004.0 -2015-04-30 13:00:00,10985.0 -2015-04-30 14:00:00,10887.0 -2015-04-30 15:00:00,10911.0 -2015-04-30 16:00:00,10804.0 -2015-04-30 17:00:00,10620.0 -2015-04-30 18:00:00,10478.0 -2015-04-30 19:00:00,10357.0 -2015-04-30 20:00:00,10138.0 -2015-04-30 21:00:00,10338.0 -2015-04-30 22:00:00,10899.0 -2015-04-30 23:00:00,10615.0 -2015-05-01 00:00:00,9914.0 -2015-04-29 01:00:00,9061.0 -2015-04-29 02:00:00,8639.0 -2015-04-29 03:00:00,8391.0 -2015-04-29 04:00:00,8259.0 -2015-04-29 05:00:00,8268.0 -2015-04-29 06:00:00,8517.0 -2015-04-29 07:00:00,9160.0 -2015-04-29 08:00:00,9921.0 -2015-04-29 09:00:00,10474.0 -2015-04-29 10:00:00,10664.0 -2015-04-29 11:00:00,10794.0 -2015-04-29 12:00:00,10901.0 -2015-04-29 13:00:00,10929.0 -2015-04-29 14:00:00,10903.0 -2015-04-29 15:00:00,10926.0 -2015-04-29 16:00:00,10823.0 -2015-04-29 17:00:00,10701.0 -2015-04-29 18:00:00,10552.0 -2015-04-29 19:00:00,10336.0 -2015-04-29 20:00:00,10290.0 -2015-04-29 21:00:00,10495.0 -2015-04-29 22:00:00,10862.0 -2015-04-29 23:00:00,10466.0 -2015-04-30 00:00:00,9766.0 -2015-04-28 01:00:00,9178.0 -2015-04-28 02:00:00,8759.0 -2015-04-28 03:00:00,8541.0 -2015-04-28 04:00:00,8416.0 -2015-04-28 05:00:00,8410.0 -2015-04-28 06:00:00,8670.0 -2015-04-28 07:00:00,9324.0 -2015-04-28 08:00:00,10122.0 -2015-04-28 09:00:00,10686.0 -2015-04-28 10:00:00,10841.0 -2015-04-28 11:00:00,10914.0 -2015-04-28 12:00:00,10984.0 -2015-04-28 13:00:00,10970.0 -2015-04-28 14:00:00,10954.0 -2015-04-28 15:00:00,10947.0 -2015-04-28 16:00:00,10851.0 -2015-04-28 17:00:00,10697.0 -2015-04-28 18:00:00,10589.0 -2015-04-28 19:00:00,10404.0 -2015-04-28 20:00:00,10227.0 -2015-04-28 21:00:00,10388.0 -2015-04-28 22:00:00,10887.0 -2015-04-28 23:00:00,10494.0 -2015-04-29 00:00:00,9791.0 -2015-04-27 01:00:00,8693.0 -2015-04-27 02:00:00,8353.0 -2015-04-27 03:00:00,8210.0 -2015-04-27 04:00:00,8132.0 -2015-04-27 05:00:00,8271.0 -2015-04-27 06:00:00,8578.0 -2015-04-27 07:00:00,9325.0 -2015-04-27 08:00:00,10117.0 -2015-04-27 09:00:00,10703.0 -2015-04-27 10:00:00,10802.0 -2015-04-27 11:00:00,10887.0 -2015-04-27 12:00:00,10974.0 -2015-04-27 13:00:00,10952.0 -2015-04-27 14:00:00,10904.0 -2015-04-27 15:00:00,10922.0 -2015-04-27 16:00:00,10793.0 -2015-04-27 17:00:00,10656.0 -2015-04-27 18:00:00,10539.0 -2015-04-27 19:00:00,10402.0 -2015-04-27 20:00:00,10281.0 -2015-04-27 21:00:00,10510.0 -2015-04-27 22:00:00,10994.0 -2015-04-27 23:00:00,10608.0 -2015-04-28 00:00:00,9886.0 -2015-04-26 01:00:00,9052.0 -2015-04-26 02:00:00,8683.0 -2015-04-26 03:00:00,8359.0 -2015-04-26 04:00:00,8269.0 -2015-04-26 05:00:00,8174.0 -2015-04-26 06:00:00,8221.0 -2015-04-26 07:00:00,8280.0 -2015-04-26 08:00:00,8273.0 -2015-04-26 09:00:00,8414.0 -2015-04-26 10:00:00,8654.0 -2015-04-26 11:00:00,8772.0 -2015-04-26 12:00:00,8859.0 -2015-04-26 13:00:00,8853.0 -2015-04-26 14:00:00,8836.0 -2015-04-26 15:00:00,8790.0 -2015-04-26 16:00:00,8711.0 -2015-04-26 17:00:00,8649.0 -2015-04-26 18:00:00,8645.0 -2015-04-26 19:00:00,8753.0 -2015-04-26 20:00:00,8872.0 -2015-04-26 21:00:00,9147.0 -2015-04-26 22:00:00,9775.0 -2015-04-26 23:00:00,9584.0 -2015-04-27 00:00:00,9141.0 -2015-04-25 01:00:00,9472.0 -2015-04-25 02:00:00,8998.0 -2015-04-25 03:00:00,8684.0 -2015-04-25 04:00:00,8499.0 -2015-04-25 05:00:00,8417.0 -2015-04-25 06:00:00,8435.0 -2015-04-25 07:00:00,8659.0 -2015-04-25 08:00:00,9008.0 -2015-04-25 09:00:00,9349.0 -2015-04-25 10:00:00,9854.0 -2015-04-25 11:00:00,10159.0 -2015-04-25 12:00:00,10339.0 -2015-04-25 13:00:00,10319.0 -2015-04-25 14:00:00,10239.0 -2015-04-25 15:00:00,10003.0 -2015-04-25 16:00:00,9779.0 -2015-04-25 17:00:00,9625.0 -2015-04-25 18:00:00,9556.0 -2015-04-25 19:00:00,9616.0 -2015-04-25 20:00:00,9726.0 -2015-04-25 21:00:00,10022.0 -2015-04-25 22:00:00,10285.0 -2015-04-25 23:00:00,10035.0 -2015-04-26 00:00:00,9614.0 -2015-04-24 01:00:00,9416.0 -2015-04-24 02:00:00,9018.0 -2015-04-24 03:00:00,8780.0 -2015-04-24 04:00:00,8665.0 -2015-04-24 05:00:00,8670.0 -2015-04-24 06:00:00,8923.0 -2015-04-24 07:00:00,9574.0 -2015-04-24 08:00:00,10347.0 -2015-04-24 09:00:00,10850.0 -2015-04-24 10:00:00,11021.0 -2015-04-24 11:00:00,11087.0 -2015-04-24 12:00:00,11103.0 -2015-04-24 13:00:00,11030.0 -2015-04-24 14:00:00,10972.0 -2015-04-24 15:00:00,10998.0 -2015-04-24 16:00:00,10838.0 -2015-04-24 17:00:00,10720.0 -2015-04-24 18:00:00,10636.0 -2015-04-24 19:00:00,10589.0 -2015-04-24 20:00:00,10613.0 -2015-04-24 21:00:00,10899.0 -2015-04-24 22:00:00,11003.0 -2015-04-24 23:00:00,10751.0 -2015-04-25 00:00:00,10098.0 -2015-04-23 01:00:00,9690.0 -2015-04-23 02:00:00,9234.0 -2015-04-23 03:00:00,8996.0 -2015-04-23 04:00:00,8861.0 -2015-04-23 05:00:00,8850.0 -2015-04-23 06:00:00,9142.0 -2015-04-23 07:00:00,9800.0 -2015-04-23 08:00:00,10579.0 -2015-04-23 09:00:00,11042.0 -2015-04-23 10:00:00,11200.0 -2015-04-23 11:00:00,11191.0 -2015-04-23 12:00:00,11239.0 -2015-04-23 13:00:00,11170.0 -2015-04-23 14:00:00,11073.0 -2015-04-23 15:00:00,11033.0 -2015-04-23 16:00:00,10924.0 -2015-04-23 17:00:00,10761.0 -2015-04-23 18:00:00,10628.0 -2015-04-23 19:00:00,10465.0 -2015-04-23 20:00:00,10337.0 -2015-04-23 21:00:00,10583.0 -2015-04-23 22:00:00,11101.0 -2015-04-23 23:00:00,10779.0 -2015-04-24 00:00:00,10119.0 -2015-04-22 01:00:00,9536.0 -2015-04-22 02:00:00,9148.0 -2015-04-22 03:00:00,8878.0 -2015-04-22 04:00:00,8686.0 -2015-04-22 05:00:00,8694.0 -2015-04-22 06:00:00,8959.0 -2015-04-22 07:00:00,9659.0 -2015-04-22 08:00:00,10589.0 -2015-04-22 09:00:00,11277.0 -2015-04-22 10:00:00,11583.0 -2015-04-22 11:00:00,11633.0 -2015-04-22 12:00:00,11721.0 -2015-04-22 13:00:00,11705.0 -2015-04-22 14:00:00,11657.0 -2015-04-22 15:00:00,11572.0 -2015-04-22 16:00:00,11456.0 -2015-04-22 17:00:00,11299.0 -2015-04-22 18:00:00,11165.0 -2015-04-22 19:00:00,11078.0 -2015-04-22 20:00:00,10954.0 -2015-04-22 21:00:00,11220.0 -2015-04-22 22:00:00,11481.0 -2015-04-22 23:00:00,11018.0 -2015-04-23 00:00:00,10330.0 -2015-04-21 01:00:00,9362.0 -2015-04-21 02:00:00,8919.0 -2015-04-21 03:00:00,8645.0 -2015-04-21 04:00:00,8535.0 -2015-04-21 05:00:00,8506.0 -2015-04-21 06:00:00,8791.0 -2015-04-21 07:00:00,9485.0 -2015-04-21 08:00:00,10338.0 -2015-04-21 09:00:00,10981.0 -2015-04-21 10:00:00,11217.0 -2015-04-21 11:00:00,11372.0 -2015-04-21 12:00:00,11429.0 -2015-04-21 13:00:00,11393.0 -2015-04-21 14:00:00,11302.0 -2015-04-21 15:00:00,11206.0 -2015-04-21 16:00:00,11053.0 -2015-04-21 17:00:00,10930.0 -2015-04-21 18:00:00,10760.0 -2015-04-21 19:00:00,10649.0 -2015-04-21 20:00:00,10590.0 -2015-04-21 21:00:00,10905.0 -2015-04-21 22:00:00,11332.0 -2015-04-21 23:00:00,10917.0 -2015-04-22 00:00:00,10194.0 -2015-04-20 01:00:00,8388.0 -2015-04-20 02:00:00,8054.0 -2015-04-20 03:00:00,7879.0 -2015-04-20 04:00:00,7789.0 -2015-04-20 05:00:00,7811.0 -2015-04-20 06:00:00,8100.0 -2015-04-20 07:00:00,8792.0 -2015-04-20 08:00:00,9876.0 -2015-04-20 09:00:00,10628.0 -2015-04-20 10:00:00,10953.0 -2015-04-20 11:00:00,11131.0 -2015-04-20 12:00:00,11297.0 -2015-04-20 13:00:00,11332.0 -2015-04-20 14:00:00,11308.0 -2015-04-20 15:00:00,11409.0 -2015-04-20 16:00:00,11263.0 -2015-04-20 17:00:00,11132.0 -2015-04-20 18:00:00,11095.0 -2015-04-20 19:00:00,11049.0 -2015-04-20 20:00:00,10937.0 -2015-04-20 21:00:00,11062.0 -2015-04-20 22:00:00,11317.0 -2015-04-20 23:00:00,10831.0 -2015-04-21 00:00:00,10087.0 -2015-04-19 01:00:00,8434.0 -2015-04-19 02:00:00,8027.0 -2015-04-19 03:00:00,7705.0 -2015-04-19 04:00:00,7495.0 -2015-04-19 05:00:00,7439.0 -2015-04-19 06:00:00,7432.0 -2015-04-19 07:00:00,7541.0 -2015-04-19 08:00:00,7505.0 -2015-04-19 09:00:00,7694.0 -2015-04-19 10:00:00,8052.0 -2015-04-19 11:00:00,8371.0 -2015-04-19 12:00:00,8594.0 -2015-04-19 13:00:00,8765.0 -2015-04-19 14:00:00,8782.0 -2015-04-19 15:00:00,8841.0 -2015-04-19 16:00:00,8794.0 -2015-04-19 17:00:00,8781.0 -2015-04-19 18:00:00,9025.0 -2015-04-19 19:00:00,9183.0 -2015-04-19 20:00:00,9352.0 -2015-04-19 21:00:00,9713.0 -2015-04-19 22:00:00,9795.0 -2015-04-19 23:00:00,9438.0 -2015-04-20 00:00:00,8928.0 -2015-04-18 01:00:00,9178.0 -2015-04-18 02:00:00,8612.0 -2015-04-18 03:00:00,8260.0 -2015-04-18 04:00:00,7978.0 -2015-04-18 05:00:00,7883.0 -2015-04-18 06:00:00,7891.0 -2015-04-18 07:00:00,8097.0 -2015-04-18 08:00:00,8174.0 -2015-04-18 09:00:00,8597.0 -2015-04-18 10:00:00,9074.0 -2015-04-18 11:00:00,9348.0 -2015-04-18 12:00:00,9482.0 -2015-04-18 13:00:00,9487.0 -2015-04-18 14:00:00,9449.0 -2015-04-18 15:00:00,9344.0 -2015-04-18 16:00:00,9195.0 -2015-04-18 17:00:00,9107.0 -2015-04-18 18:00:00,9017.0 -2015-04-18 19:00:00,9009.0 -2015-04-18 20:00:00,8932.0 -2015-04-18 21:00:00,9259.0 -2015-04-18 22:00:00,9613.0 -2015-04-18 23:00:00,9342.0 -2015-04-19 00:00:00,8947.0 -2015-04-17 01:00:00,8954.0 -2015-04-17 02:00:00,8442.0 -2015-04-17 03:00:00,8174.0 -2015-04-17 04:00:00,8034.0 -2015-04-17 05:00:00,8013.0 -2015-04-17 06:00:00,8202.0 -2015-04-17 07:00:00,8846.0 -2015-04-17 08:00:00,9625.0 -2015-04-17 09:00:00,10208.0 -2015-04-17 10:00:00,10585.0 -2015-04-17 11:00:00,10914.0 -2015-04-17 12:00:00,11145.0 -2015-04-17 13:00:00,11260.0 -2015-04-17 14:00:00,11303.0 -2015-04-17 15:00:00,11368.0 -2015-04-17 16:00:00,11296.0 -2015-04-17 17:00:00,11185.0 -2015-04-17 18:00:00,11017.0 -2015-04-17 19:00:00,10790.0 -2015-04-17 20:00:00,10595.0 -2015-04-17 21:00:00,10773.0 -2015-04-17 22:00:00,10927.0 -2015-04-17 23:00:00,10541.0 -2015-04-18 00:00:00,9870.0 -2015-04-16 01:00:00,9113.0 -2015-04-16 02:00:00,8606.0 -2015-04-16 03:00:00,8314.0 -2015-04-16 04:00:00,8114.0 -2015-04-16 05:00:00,8065.0 -2015-04-16 06:00:00,8287.0 -2015-04-16 07:00:00,8926.0 -2015-04-16 08:00:00,9930.0 -2015-04-16 09:00:00,10551.0 -2015-04-16 10:00:00,10793.0 -2015-04-16 11:00:00,10916.0 -2015-04-16 12:00:00,11008.0 -2015-04-16 13:00:00,10960.0 -2015-04-16 14:00:00,10898.0 -2015-04-16 15:00:00,10931.0 -2015-04-16 16:00:00,10872.0 -2015-04-16 17:00:00,10756.0 -2015-04-16 18:00:00,10668.0 -2015-04-16 19:00:00,10531.0 -2015-04-16 20:00:00,10322.0 -2015-04-16 21:00:00,10557.0 -2015-04-16 22:00:00,10856.0 -2015-04-16 23:00:00,10404.0 -2015-04-17 00:00:00,9674.0 -2015-04-15 01:00:00,9071.0 -2015-04-15 02:00:00,8602.0 -2015-04-15 03:00:00,8324.0 -2015-04-15 04:00:00,8183.0 -2015-04-15 05:00:00,8154.0 -2015-04-15 06:00:00,8409.0 -2015-04-15 07:00:00,9077.0 -2015-04-15 08:00:00,9955.0 -2015-04-15 09:00:00,10563.0 -2015-04-15 10:00:00,10799.0 -2015-04-15 11:00:00,10883.0 -2015-04-15 12:00:00,11059.0 -2015-04-15 13:00:00,11110.0 -2015-04-15 14:00:00,11089.0 -2015-04-15 15:00:00,11133.0 -2015-04-15 16:00:00,11046.0 -2015-04-15 17:00:00,10856.0 -2015-04-15 18:00:00,10744.0 -2015-04-15 19:00:00,10581.0 -2015-04-15 20:00:00,10399.0 -2015-04-15 21:00:00,10739.0 -2015-04-15 22:00:00,10973.0 -2015-04-15 23:00:00,10571.0 -2015-04-16 00:00:00,9842.0 -2015-04-14 01:00:00,9016.0 -2015-04-14 02:00:00,8538.0 -2015-04-14 03:00:00,8282.0 -2015-04-14 04:00:00,8133.0 -2015-04-14 05:00:00,8117.0 -2015-04-14 06:00:00,8338.0 -2015-04-14 07:00:00,9048.0 -2015-04-14 08:00:00,9892.0 -2015-04-14 09:00:00,10428.0 -2015-04-14 10:00:00,10668.0 -2015-04-14 11:00:00,10835.0 -2015-04-14 12:00:00,10979.0 -2015-04-14 13:00:00,11000.0 -2015-04-14 14:00:00,10994.0 -2015-04-14 15:00:00,11044.0 -2015-04-14 16:00:00,10974.0 -2015-04-14 17:00:00,10849.0 -2015-04-14 18:00:00,10747.0 -2015-04-14 19:00:00,10584.0 -2015-04-14 20:00:00,10379.0 -2015-04-14 21:00:00,10684.0 -2015-04-14 22:00:00,11002.0 -2015-04-14 23:00:00,10490.0 -2015-04-15 00:00:00,9661.0 -2015-04-13 01:00:00,8474.0 -2015-04-13 02:00:00,8154.0 -2015-04-13 03:00:00,7945.0 -2015-04-13 04:00:00,7828.0 -2015-04-13 05:00:00,7841.0 -2015-04-13 06:00:00,8119.0 -2015-04-13 07:00:00,8879.0 -2015-04-13 08:00:00,9966.0 -2015-04-13 09:00:00,10585.0 -2015-04-13 10:00:00,10799.0 -2015-04-13 11:00:00,11039.0 -2015-04-13 12:00:00,11173.0 -2015-04-13 13:00:00,11241.0 -2015-04-13 14:00:00,11210.0 -2015-04-13 15:00:00,11228.0 -2015-04-13 16:00:00,11128.0 -2015-04-13 17:00:00,10995.0 -2015-04-13 18:00:00,10827.0 -2015-04-13 19:00:00,10628.0 -2015-04-13 20:00:00,10443.0 -2015-04-13 21:00:00,10687.0 -2015-04-13 22:00:00,10934.0 -2015-04-13 23:00:00,10465.0 -2015-04-14 00:00:00,9722.0 -2015-04-12 01:00:00,8667.0 -2015-04-12 02:00:00,8326.0 -2015-04-12 03:00:00,8042.0 -2015-04-12 04:00:00,7892.0 -2015-04-12 05:00:00,7826.0 -2015-04-12 06:00:00,7845.0 -2015-04-12 07:00:00,7978.0 -2015-04-12 08:00:00,8026.0 -2015-04-12 09:00:00,8143.0 -2015-04-12 10:00:00,8451.0 -2015-04-12 11:00:00,8654.0 -2015-04-12 12:00:00,8833.0 -2015-04-12 13:00:00,8914.0 -2015-04-12 14:00:00,8944.0 -2015-04-12 15:00:00,8916.0 -2015-04-12 16:00:00,8923.0 -2015-04-12 17:00:00,8858.0 -2015-04-12 18:00:00,8982.0 -2015-04-12 19:00:00,9069.0 -2015-04-12 20:00:00,9222.0 -2015-04-12 21:00:00,9685.0 -2015-04-12 22:00:00,9817.0 -2015-04-12 23:00:00,9479.0 -2015-04-13 00:00:00,9002.0 -2015-04-11 01:00:00,9314.0 -2015-04-11 02:00:00,8902.0 -2015-04-11 03:00:00,8636.0 -2015-04-11 04:00:00,8534.0 -2015-04-11 05:00:00,8418.0 -2015-04-11 06:00:00,8554.0 -2015-04-11 07:00:00,8793.0 -2015-04-11 08:00:00,9054.0 -2015-04-11 09:00:00,9227.0 -2015-04-11 10:00:00,9469.0 -2015-04-11 11:00:00,9585.0 -2015-04-11 12:00:00,9668.0 -2015-04-11 13:00:00,9603.0 -2015-04-11 14:00:00,9505.0 -2015-04-11 15:00:00,9314.0 -2015-04-11 16:00:00,9203.0 -2015-04-11 17:00:00,9123.0 -2015-04-11 18:00:00,9060.0 -2015-04-11 19:00:00,9052.0 -2015-04-11 20:00:00,9031.0 -2015-04-11 21:00:00,9448.0 -2015-04-11 22:00:00,9855.0 -2015-04-11 23:00:00,9602.0 -2015-04-12 00:00:00,9205.0 -2015-04-10 01:00:00,9340.0 -2015-04-10 02:00:00,8816.0 -2015-04-10 03:00:00,8525.0 -2015-04-10 04:00:00,8396.0 -2015-04-10 05:00:00,8372.0 -2015-04-10 06:00:00,8635.0 -2015-04-10 07:00:00,9319.0 -2015-04-10 08:00:00,10284.0 -2015-04-10 09:00:00,10912.0 -2015-04-10 10:00:00,11156.0 -2015-04-10 11:00:00,11100.0 -2015-04-10 12:00:00,11125.0 -2015-04-10 13:00:00,11082.0 -2015-04-10 14:00:00,11024.0 -2015-04-10 15:00:00,11024.0 -2015-04-10 16:00:00,10908.0 -2015-04-10 17:00:00,10678.0 -2015-04-10 18:00:00,10486.0 -2015-04-10 19:00:00,10326.0 -2015-04-10 20:00:00,10283.0 -2015-04-10 21:00:00,10718.0 -2015-04-10 22:00:00,10809.0 -2015-04-10 23:00:00,10520.0 -2015-04-11 00:00:00,9952.0 -2015-04-09 01:00:00,9669.0 -2015-04-09 02:00:00,9176.0 -2015-04-09 03:00:00,8834.0 -2015-04-09 04:00:00,8658.0 -2015-04-09 05:00:00,8622.0 -2015-04-09 06:00:00,8853.0 -2015-04-09 07:00:00,9545.0 -2015-04-09 08:00:00,10555.0 -2015-04-09 09:00:00,11180.0 -2015-04-09 10:00:00,11372.0 -2015-04-09 11:00:00,11491.0 -2015-04-09 12:00:00,11721.0 -2015-04-09 13:00:00,11858.0 -2015-04-09 14:00:00,11777.0 -2015-04-09 15:00:00,11702.0 -2015-04-09 16:00:00,11627.0 -2015-04-09 17:00:00,11608.0 -2015-04-09 18:00:00,11375.0 -2015-04-09 19:00:00,11253.0 -2015-04-09 20:00:00,11245.0 -2015-04-09 21:00:00,11509.0 -2015-04-09 22:00:00,11475.0 -2015-04-09 23:00:00,11029.0 -2015-04-10 00:00:00,10166.0 -2015-04-08 01:00:00,9573.0 -2015-04-08 02:00:00,9086.0 -2015-04-08 03:00:00,8799.0 -2015-04-08 04:00:00,8582.0 -2015-04-08 05:00:00,8535.0 -2015-04-08 06:00:00,8764.0 -2015-04-08 07:00:00,9455.0 -2015-04-08 08:00:00,10452.0 -2015-04-08 09:00:00,10998.0 -2015-04-08 10:00:00,11295.0 -2015-04-08 11:00:00,11335.0 -2015-04-08 12:00:00,11428.0 -2015-04-08 13:00:00,11448.0 -2015-04-08 14:00:00,11326.0 -2015-04-08 15:00:00,11258.0 -2015-04-08 16:00:00,11138.0 -2015-04-08 17:00:00,11083.0 -2015-04-08 18:00:00,11049.0 -2015-04-08 19:00:00,11023.0 -2015-04-08 20:00:00,11044.0 -2015-04-08 21:00:00,11439.0 -2015-04-08 22:00:00,11503.0 -2015-04-08 23:00:00,11070.0 -2015-04-09 00:00:00,10392.0 -2015-04-07 01:00:00,9393.0 -2015-04-07 02:00:00,8969.0 -2015-04-07 03:00:00,8721.0 -2015-04-07 04:00:00,8548.0 -2015-04-07 05:00:00,8503.0 -2015-04-07 06:00:00,8751.0 -2015-04-07 07:00:00,9491.0 -2015-04-07 08:00:00,10498.0 -2015-04-07 09:00:00,11082.0 -2015-04-07 10:00:00,11385.0 -2015-04-07 11:00:00,11587.0 -2015-04-07 12:00:00,11725.0 -2015-04-07 13:00:00,11700.0 -2015-04-07 14:00:00,11633.0 -2015-04-07 15:00:00,11635.0 -2015-04-07 16:00:00,11515.0 -2015-04-07 17:00:00,11414.0 -2015-04-07 18:00:00,11343.0 -2015-04-07 19:00:00,11327.0 -2015-04-07 20:00:00,11298.0 -2015-04-07 21:00:00,11660.0 -2015-04-07 22:00:00,11529.0 -2015-04-07 23:00:00,11040.0 -2015-04-08 00:00:00,10287.0 -2015-04-06 01:00:00,8270.0 -2015-04-06 02:00:00,7982.0 -2015-04-06 03:00:00,7840.0 -2015-04-06 04:00:00,7780.0 -2015-04-06 05:00:00,7856.0 -2015-04-06 06:00:00,8126.0 -2015-04-06 07:00:00,8931.0 -2015-04-06 08:00:00,9832.0 -2015-04-06 09:00:00,10426.0 -2015-04-06 10:00:00,10753.0 -2015-04-06 11:00:00,10928.0 -2015-04-06 12:00:00,10998.0 -2015-04-06 13:00:00,11018.0 -2015-04-06 14:00:00,10918.0 -2015-04-06 15:00:00,10912.0 -2015-04-06 16:00:00,10867.0 -2015-04-06 17:00:00,10723.0 -2015-04-06 18:00:00,10603.0 -2015-04-06 19:00:00,10591.0 -2015-04-06 20:00:00,10593.0 -2015-04-06 21:00:00,11070.0 -2015-04-06 22:00:00,11078.0 -2015-04-06 23:00:00,10703.0 -2015-04-07 00:00:00,10071.0 -2015-04-05 01:00:00,8592.0 -2015-04-05 02:00:00,8142.0 -2015-04-05 03:00:00,7891.0 -2015-04-05 04:00:00,7703.0 -2015-04-05 05:00:00,7653.0 -2015-04-05 06:00:00,7603.0 -2015-04-05 07:00:00,7726.0 -2015-04-05 08:00:00,7829.0 -2015-04-05 09:00:00,7886.0 -2015-04-05 10:00:00,8053.0 -2015-04-05 11:00:00,8223.0 -2015-04-05 12:00:00,8257.0 -2015-04-05 13:00:00,8217.0 -2015-04-05 14:00:00,8256.0 -2015-04-05 15:00:00,8092.0 -2015-04-05 16:00:00,8014.0 -2015-04-05 17:00:00,7866.0 -2015-04-05 18:00:00,7881.0 -2015-04-05 19:00:00,7914.0 -2015-04-05 20:00:00,8038.0 -2015-04-05 21:00:00,8621.0 -2015-04-05 22:00:00,9017.0 -2015-04-05 23:00:00,8853.0 -2015-04-06 00:00:00,8500.0 -2015-04-04 01:00:00,9295.0 -2015-04-04 02:00:00,8828.0 -2015-04-04 03:00:00,8615.0 -2015-04-04 04:00:00,8456.0 -2015-04-04 05:00:00,8442.0 -2015-04-04 06:00:00,8493.0 -2015-04-04 07:00:00,8797.0 -2015-04-04 08:00:00,9069.0 -2015-04-04 09:00:00,9275.0 -2015-04-04 10:00:00,9499.0 -2015-04-04 11:00:00,9665.0 -2015-04-04 12:00:00,9645.0 -2015-04-04 13:00:00,9593.0 -2015-04-04 14:00:00,9459.0 -2015-04-04 15:00:00,9242.0 -2015-04-04 16:00:00,9030.0 -2015-04-04 17:00:00,8968.0 -2015-04-04 18:00:00,8926.0 -2015-04-04 19:00:00,8920.0 -2015-04-04 20:00:00,8960.0 -2015-04-04 21:00:00,9496.0 -2015-04-04 22:00:00,9796.0 -2015-04-04 23:00:00,9648.0 -2015-04-05 00:00:00,9243.0 -2015-04-03 01:00:00,8978.0 -2015-04-03 02:00:00,8458.0 -2015-04-03 03:00:00,8170.0 -2015-04-03 04:00:00,8002.0 -2015-04-03 05:00:00,7945.0 -2015-04-03 06:00:00,8069.0 -2015-04-03 07:00:00,8577.0 -2015-04-03 08:00:00,9210.0 -2015-04-03 09:00:00,9612.0 -2015-04-03 10:00:00,10000.0 -2015-04-03 11:00:00,10113.0 -2015-04-03 12:00:00,10200.0 -2015-04-03 13:00:00,10198.0 -2015-04-03 14:00:00,10080.0 -2015-04-03 15:00:00,10123.0 -2015-04-03 16:00:00,10064.0 -2015-04-03 17:00:00,9942.0 -2015-04-03 18:00:00,9904.0 -2015-04-03 19:00:00,9983.0 -2015-04-03 20:00:00,10054.0 -2015-04-03 21:00:00,10405.0 -2015-04-03 22:00:00,10558.0 -2015-04-03 23:00:00,10332.0 -2015-04-04 00:00:00,9827.0 -2015-04-02 01:00:00,9152.0 -2015-04-02 02:00:00,8644.0 -2015-04-02 03:00:00,8307.0 -2015-04-02 04:00:00,8147.0 -2015-04-02 05:00:00,8132.0 -2015-04-02 06:00:00,8296.0 -2015-04-02 07:00:00,8909.0 -2015-04-02 08:00:00,9887.0 -2015-04-02 09:00:00,10585.0 -2015-04-02 10:00:00,10904.0 -2015-04-02 11:00:00,11085.0 -2015-04-02 12:00:00,11208.0 -2015-04-02 13:00:00,11218.0 -2015-04-02 14:00:00,11228.0 -2015-04-02 15:00:00,11221.0 -2015-04-02 16:00:00,11019.0 -2015-04-02 17:00:00,10821.0 -2015-04-02 18:00:00,10682.0 -2015-04-02 19:00:00,10533.0 -2015-04-02 20:00:00,10308.0 -2015-04-02 21:00:00,10701.0 -2015-04-02 22:00:00,10730.0 -2015-04-02 23:00:00,10359.0 -2015-04-03 00:00:00,9666.0 -2015-04-01 01:00:00,9491.0 -2015-04-01 02:00:00,9056.0 -2015-04-01 03:00:00,8813.0 -2015-04-01 04:00:00,8666.0 -2015-04-01 05:00:00,8639.0 -2015-04-01 06:00:00,8891.0 -2015-04-01 07:00:00,9561.0 -2015-04-01 08:00:00,10533.0 -2015-04-01 09:00:00,10953.0 -2015-04-01 10:00:00,11088.0 -2015-04-01 11:00:00,11109.0 -2015-04-01 12:00:00,11115.0 -2015-04-01 13:00:00,11086.0 -2015-04-01 14:00:00,11072.0 -2015-04-01 15:00:00,11106.0 -2015-04-01 16:00:00,11035.0 -2015-04-01 17:00:00,10944.0 -2015-04-01 18:00:00,10833.0 -2015-04-01 19:00:00,10668.0 -2015-04-01 20:00:00,10489.0 -2015-04-01 21:00:00,10895.0 -2015-04-01 22:00:00,11060.0 -2015-04-01 23:00:00,10622.0 -2015-04-02 00:00:00,9892.0 -2015-03-31 01:00:00,9397.0 -2015-03-31 02:00:00,8945.0 -2015-03-31 03:00:00,8663.0 -2015-03-31 04:00:00,8490.0 -2015-03-31 05:00:00,8459.0 -2015-03-31 06:00:00,8690.0 -2015-03-31 07:00:00,9437.0 -2015-03-31 08:00:00,10515.0 -2015-03-31 09:00:00,11022.0 -2015-03-31 10:00:00,11315.0 -2015-03-31 11:00:00,11383.0 -2015-03-31 12:00:00,11353.0 -2015-03-31 13:00:00,11262.0 -2015-03-31 14:00:00,11165.0 -2015-03-31 15:00:00,11114.0 -2015-03-31 16:00:00,10951.0 -2015-03-31 17:00:00,10793.0 -2015-03-31 18:00:00,10663.0 -2015-03-31 19:00:00,10559.0 -2015-03-31 20:00:00,10495.0 -2015-03-31 21:00:00,10992.0 -2015-03-31 22:00:00,11147.0 -2015-03-31 23:00:00,10772.0 -2015-04-01 00:00:00,10138.0 -2015-03-30 01:00:00,9330.0 -2015-03-30 02:00:00,8983.0 -2015-03-30 03:00:00,8804.0 -2015-03-30 04:00:00,8801.0 -2015-03-30 05:00:00,8910.0 -2015-03-30 06:00:00,9244.0 -2015-03-30 07:00:00,10021.0 -2015-03-30 08:00:00,11037.0 -2015-03-30 09:00:00,11314.0 -2015-03-30 10:00:00,11438.0 -2015-03-30 11:00:00,11397.0 -2015-03-30 12:00:00,11379.0 -2015-03-30 13:00:00,11276.0 -2015-03-30 14:00:00,11174.0 -2015-03-30 15:00:00,11141.0 -2015-03-30 16:00:00,10956.0 -2015-03-30 17:00:00,10829.0 -2015-03-30 18:00:00,10775.0 -2015-03-30 19:00:00,10755.0 -2015-03-30 20:00:00,10799.0 -2015-03-30 21:00:00,11189.0 -2015-03-30 22:00:00,11181.0 -2015-03-30 23:00:00,10775.0 -2015-03-31 00:00:00,10081.0 -2015-03-29 01:00:00,9946.0 -2015-03-29 02:00:00,9579.0 -2015-03-29 03:00:00,9328.0 -2015-03-29 04:00:00,9239.0 -2015-03-29 05:00:00,9144.0 -2015-03-29 06:00:00,9201.0 -2015-03-29 07:00:00,9303.0 -2015-03-29 08:00:00,9557.0 -2015-03-29 09:00:00,9487.0 -2015-03-29 10:00:00,9731.0 -2015-03-29 11:00:00,9919.0 -2015-03-29 12:00:00,10204.0 -2015-03-29 13:00:00,10436.0 -2015-03-29 14:00:00,10550.0 -2015-03-29 15:00:00,10544.0 -2015-03-29 16:00:00,10452.0 -2015-03-29 17:00:00,10118.0 -2015-03-29 18:00:00,10006.0 -2015-03-29 19:00:00,10093.0 -2015-03-29 20:00:00,10389.0 -2015-03-29 21:00:00,10720.0 -2015-03-29 22:00:00,10643.0 -2015-03-29 23:00:00,10310.0 -2015-03-30 00:00:00,9848.0 -2015-03-28 01:00:00,10603.0 -2015-03-28 02:00:00,10135.0 -2015-03-28 03:00:00,9895.0 -2015-03-28 04:00:00,9761.0 -2015-03-28 05:00:00,9716.0 -2015-03-28 06:00:00,9829.0 -2015-03-28 07:00:00,10111.0 -2015-03-28 08:00:00,10545.0 -2015-03-28 09:00:00,10628.0 -2015-03-28 10:00:00,10830.0 -2015-03-28 11:00:00,10898.0 -2015-03-28 12:00:00,10885.0 -2015-03-28 13:00:00,10778.0 -2015-03-28 14:00:00,10603.0 -2015-03-28 15:00:00,10329.0 -2015-03-28 16:00:00,10125.0 -2015-03-28 17:00:00,9916.0 -2015-03-28 18:00:00,9857.0 -2015-03-28 19:00:00,9841.0 -2015-03-28 20:00:00,10025.0 -2015-03-28 21:00:00,10703.0 -2015-03-28 22:00:00,11000.0 -2015-03-28 23:00:00,10814.0 -2015-03-29 00:00:00,10428.0 -2015-03-27 01:00:00,10173.0 -2015-03-27 02:00:00,9721.0 -2015-03-27 03:00:00,9490.0 -2015-03-27 04:00:00,9416.0 -2015-03-27 05:00:00,9429.0 -2015-03-27 06:00:00,9743.0 -2015-03-27 07:00:00,10503.0 -2015-03-27 08:00:00,11576.0 -2015-03-27 09:00:00,12076.0 -2015-03-27 10:00:00,12210.0 -2015-03-27 11:00:00,12205.0 -2015-03-27 12:00:00,12247.0 -2015-03-27 13:00:00,12232.0 -2015-03-27 14:00:00,12031.0 -2015-03-27 15:00:00,11973.0 -2015-03-27 16:00:00,11750.0 -2015-03-27 17:00:00,11521.0 -2015-03-27 18:00:00,11382.0 -2015-03-27 19:00:00,11329.0 -2015-03-27 20:00:00,11415.0 -2015-03-27 21:00:00,12069.0 -2015-03-27 22:00:00,12154.0 -2015-03-27 23:00:00,11857.0 -2015-03-28 00:00:00,11261.0 -2015-03-26 01:00:00,10161.0 -2015-03-26 02:00:00,9678.0 -2015-03-26 03:00:00,9426.0 -2015-03-26 04:00:00,9274.0 -2015-03-26 05:00:00,9267.0 -2015-03-26 06:00:00,9532.0 -2015-03-26 07:00:00,10268.0 -2015-03-26 08:00:00,11363.0 -2015-03-26 09:00:00,11803.0 -2015-03-26 10:00:00,11925.0 -2015-03-26 11:00:00,11895.0 -2015-03-26 12:00:00,11839.0 -2015-03-26 13:00:00,11775.0 -2015-03-26 14:00:00,11659.0 -2015-03-26 15:00:00,11661.0 -2015-03-26 16:00:00,11530.0 -2015-03-26 17:00:00,11409.0 -2015-03-26 18:00:00,11396.0 -2015-03-26 19:00:00,11472.0 -2015-03-26 20:00:00,11524.0 -2015-03-26 21:00:00,12007.0 -2015-03-26 22:00:00,11941.0 -2015-03-26 23:00:00,11505.0 -2015-03-27 00:00:00,10822.0 -2015-03-25 01:00:00,10244.0 -2015-03-25 02:00:00,9814.0 -2015-03-25 03:00:00,9541.0 -2015-03-25 04:00:00,9375.0 -2015-03-25 05:00:00,9323.0 -2015-03-25 06:00:00,9514.0 -2015-03-25 07:00:00,10189.0 -2015-03-25 08:00:00,11259.0 -2015-03-25 09:00:00,11855.0 -2015-03-25 10:00:00,12049.0 -2015-03-25 11:00:00,12148.0 -2015-03-25 12:00:00,12160.0 -2015-03-25 13:00:00,12089.0 -2015-03-25 14:00:00,12096.0 -2015-03-25 15:00:00,12084.0 -2015-03-25 16:00:00,11950.0 -2015-03-25 17:00:00,11859.0 -2015-03-25 18:00:00,11791.0 -2015-03-25 19:00:00,11723.0 -2015-03-25 20:00:00,11631.0 -2015-03-25 21:00:00,12062.0 -2015-03-25 22:00:00,12022.0 -2015-03-25 23:00:00,11579.0 -2015-03-26 00:00:00,10836.0 -2015-03-24 01:00:00,10460.0 -2015-03-24 02:00:00,10018.0 -2015-03-24 03:00:00,9783.0 -2015-03-24 04:00:00,9650.0 -2015-03-24 05:00:00,9694.0 -2015-03-24 06:00:00,9958.0 -2015-03-24 07:00:00,10730.0 -2015-03-24 08:00:00,11788.0 -2015-03-24 09:00:00,12170.0 -2015-03-24 10:00:00,12261.0 -2015-03-24 11:00:00,12218.0 -2015-03-24 12:00:00,12140.0 -2015-03-24 13:00:00,12038.0 -2015-03-24 14:00:00,11907.0 -2015-03-24 15:00:00,11863.0 -2015-03-24 16:00:00,11676.0 -2015-03-24 17:00:00,11577.0 -2015-03-24 18:00:00,11585.0 -2015-03-24 19:00:00,11635.0 -2015-03-24 20:00:00,11699.0 -2015-03-24 21:00:00,12242.0 -2015-03-24 22:00:00,12127.0 -2015-03-24 23:00:00,11659.0 -2015-03-25 00:00:00,10961.0 -2015-03-23 01:00:00,9708.0 -2015-03-23 02:00:00,9375.0 -2015-03-23 03:00:00,9212.0 -2015-03-23 04:00:00,9172.0 -2015-03-23 05:00:00,9217.0 -2015-03-23 06:00:00,9522.0 -2015-03-23 07:00:00,10285.0 -2015-03-23 08:00:00,11455.0 -2015-03-23 09:00:00,12020.0 -2015-03-23 10:00:00,12322.0 -2015-03-23 11:00:00,12440.0 -2015-03-23 12:00:00,12543.0 -2015-03-23 13:00:00,12480.0 -2015-03-23 14:00:00,12351.0 -2015-03-23 15:00:00,12342.0 -2015-03-23 16:00:00,12164.0 -2015-03-23 17:00:00,12008.0 -2015-03-23 18:00:00,12032.0 -2015-03-23 19:00:00,12022.0 -2015-03-23 20:00:00,12039.0 -2015-03-23 21:00:00,12483.0 -2015-03-23 22:00:00,12363.0 -2015-03-23 23:00:00,11886.0 -2015-03-24 00:00:00,11158.0 -2015-03-22 01:00:00,9250.0 -2015-03-22 02:00:00,8917.0 -2015-03-22 03:00:00,8676.0 -2015-03-22 04:00:00,8561.0 -2015-03-22 05:00:00,8473.0 -2015-03-22 06:00:00,8541.0 -2015-03-22 07:00:00,8697.0 -2015-03-22 08:00:00,9003.0 -2015-03-22 09:00:00,9061.0 -2015-03-22 10:00:00,9371.0 -2015-03-22 11:00:00,9613.0 -2015-03-22 12:00:00,9740.0 -2015-03-22 13:00:00,9745.0 -2015-03-22 14:00:00,9742.0 -2015-03-22 15:00:00,9694.0 -2015-03-22 16:00:00,9723.0 -2015-03-22 17:00:00,9672.0 -2015-03-22 18:00:00,9789.0 -2015-03-22 19:00:00,10030.0 -2015-03-22 20:00:00,10402.0 -2015-03-22 21:00:00,11039.0 -2015-03-22 22:00:00,11025.0 -2015-03-22 23:00:00,10728.0 -2015-03-23 00:00:00,10201.0 -2015-03-21 01:00:00,9681.0 -2015-03-21 02:00:00,9154.0 -2015-03-21 03:00:00,8904.0 -2015-03-21 04:00:00,8681.0 -2015-03-21 05:00:00,8673.0 -2015-03-21 06:00:00,8699.0 -2015-03-21 07:00:00,9007.0 -2015-03-21 08:00:00,9451.0 -2015-03-21 09:00:00,9608.0 -2015-03-21 10:00:00,9784.0 -2015-03-21 11:00:00,9914.0 -2015-03-21 12:00:00,9921.0 -2015-03-21 13:00:00,9826.0 -2015-03-21 14:00:00,9680.0 -2015-03-21 15:00:00,9462.0 -2015-03-21 16:00:00,9312.0 -2015-03-21 17:00:00,9165.0 -2015-03-21 18:00:00,9162.0 -2015-03-21 19:00:00,9203.0 -2015-03-21 20:00:00,9414.0 -2015-03-21 21:00:00,10085.0 -2015-03-21 22:00:00,10280.0 -2015-03-21 23:00:00,10072.0 -2015-03-22 00:00:00,9734.0 -2015-03-20 01:00:00,9731.0 -2015-03-20 02:00:00,9263.0 -2015-03-20 03:00:00,8994.0 -2015-03-20 04:00:00,8836.0 -2015-03-20 05:00:00,8797.0 -2015-03-20 06:00:00,9001.0 -2015-03-20 07:00:00,9670.0 -2015-03-20 08:00:00,10784.0 -2015-03-20 09:00:00,11294.0 -2015-03-20 10:00:00,11376.0 -2015-03-20 11:00:00,11364.0 -2015-03-20 12:00:00,11280.0 -2015-03-20 13:00:00,11131.0 -2015-03-20 14:00:00,11029.0 -2015-03-20 15:00:00,11021.0 -2015-03-20 16:00:00,10895.0 -2015-03-20 17:00:00,10736.0 -2015-03-20 18:00:00,10658.0 -2015-03-20 19:00:00,10549.0 -2015-03-20 20:00:00,10607.0 -2015-03-20 21:00:00,11144.0 -2015-03-20 22:00:00,11117.0 -2015-03-20 23:00:00,10832.0 -2015-03-21 00:00:00,10270.0 -2015-03-19 01:00:00,9648.0 -2015-03-19 02:00:00,9197.0 -2015-03-19 03:00:00,8941.0 -2015-03-19 04:00:00,8793.0 -2015-03-19 05:00:00,8801.0 -2015-03-19 06:00:00,9024.0 -2015-03-19 07:00:00,9747.0 -2015-03-19 08:00:00,10926.0 -2015-03-19 09:00:00,11316.0 -2015-03-19 10:00:00,11479.0 -2015-03-19 11:00:00,11527.0 -2015-03-19 12:00:00,11523.0 -2015-03-19 13:00:00,11491.0 -2015-03-19 14:00:00,11390.0 -2015-03-19 15:00:00,11334.0 -2015-03-19 16:00:00,11196.0 -2015-03-19 17:00:00,11126.0 -2015-03-19 18:00:00,11112.0 -2015-03-19 19:00:00,11187.0 -2015-03-19 20:00:00,11341.0 -2015-03-19 21:00:00,11776.0 -2015-03-19 22:00:00,11624.0 -2015-03-19 23:00:00,11164.0 -2015-03-20 00:00:00,10414.0 -2015-03-18 01:00:00,9879.0 -2015-03-18 02:00:00,9454.0 -2015-03-18 03:00:00,9186.0 -2015-03-18 04:00:00,9062.0 -2015-03-18 05:00:00,9066.0 -2015-03-18 06:00:00,9313.0 -2015-03-18 07:00:00,10078.0 -2015-03-18 08:00:00,11224.0 -2015-03-18 09:00:00,11655.0 -2015-03-18 10:00:00,11668.0 -2015-03-18 11:00:00,11530.0 -2015-03-18 12:00:00,11481.0 -2015-03-18 13:00:00,11351.0 -2015-03-18 14:00:00,11215.0 -2015-03-18 15:00:00,11149.0 -2015-03-18 16:00:00,11024.0 -2015-03-18 17:00:00,10942.0 -2015-03-18 18:00:00,10925.0 -2015-03-18 19:00:00,10963.0 -2015-03-18 20:00:00,11187.0 -2015-03-18 21:00:00,11636.0 -2015-03-18 22:00:00,11495.0 -2015-03-18 23:00:00,11026.0 -2015-03-19 00:00:00,10309.0 -2015-03-17 01:00:00,9139.0 -2015-03-17 02:00:00,8737.0 -2015-03-17 03:00:00,8505.0 -2015-03-17 04:00:00,8401.0 -2015-03-17 05:00:00,8395.0 -2015-03-17 06:00:00,8722.0 -2015-03-17 07:00:00,9506.0 -2015-03-17 08:00:00,10766.0 -2015-03-17 09:00:00,11463.0 -2015-03-17 10:00:00,11573.0 -2015-03-17 11:00:00,11537.0 -2015-03-17 12:00:00,11550.0 -2015-03-17 13:00:00,11473.0 -2015-03-17 14:00:00,11399.0 -2015-03-17 15:00:00,11328.0 -2015-03-17 16:00:00,11172.0 -2015-03-17 17:00:00,10977.0 -2015-03-17 18:00:00,10879.0 -2015-03-17 19:00:00,10828.0 -2015-03-17 20:00:00,10927.0 -2015-03-17 21:00:00,11614.0 -2015-03-17 22:00:00,11588.0 -2015-03-17 23:00:00,11199.0 -2015-03-18 00:00:00,10541.0 -2015-03-16 01:00:00,8640.0 -2015-03-16 02:00:00,8355.0 -2015-03-16 03:00:00,8139.0 -2015-03-16 04:00:00,8076.0 -2015-03-16 05:00:00,8081.0 -2015-03-16 06:00:00,8346.0 -2015-03-16 07:00:00,9088.0 -2015-03-16 08:00:00,10356.0 -2015-03-16 09:00:00,10894.0 -2015-03-16 10:00:00,10958.0 -2015-03-16 11:00:00,11032.0 -2015-03-16 12:00:00,11158.0 -2015-03-16 13:00:00,11172.0 -2015-03-16 14:00:00,11113.0 -2015-03-16 15:00:00,11121.0 -2015-03-16 16:00:00,11039.0 -2015-03-16 17:00:00,10904.0 -2015-03-16 18:00:00,10773.0 -2015-03-16 19:00:00,10710.0 -2015-03-16 20:00:00,10656.0 -2015-03-16 21:00:00,11238.0 -2015-03-16 22:00:00,11132.0 -2015-03-16 23:00:00,10582.0 -2015-03-17 00:00:00,9842.0 -2015-03-15 01:00:00,8950.0 -2015-03-15 02:00:00,8576.0 -2015-03-15 03:00:00,8307.0 -2015-03-15 04:00:00,8170.0 -2015-03-15 05:00:00,8169.0 -2015-03-15 06:00:00,8183.0 -2015-03-15 07:00:00,8406.0 -2015-03-15 08:00:00,8684.0 -2015-03-15 09:00:00,8742.0 -2015-03-15 10:00:00,8876.0 -2015-03-15 11:00:00,9026.0 -2015-03-15 12:00:00,9056.0 -2015-03-15 13:00:00,9026.0 -2015-03-15 14:00:00,9009.0 -2015-03-15 15:00:00,8964.0 -2015-03-15 16:00:00,8887.0 -2015-03-15 17:00:00,8836.0 -2015-03-15 18:00:00,8899.0 -2015-03-15 19:00:00,9017.0 -2015-03-15 20:00:00,9449.0 -2015-03-15 21:00:00,10090.0 -2015-03-15 22:00:00,10031.0 -2015-03-15 23:00:00,9663.0 -2015-03-16 00:00:00,9194.0 -2015-03-14 01:00:00,9367.0 -2015-03-14 02:00:00,8852.0 -2015-03-14 03:00:00,8586.0 -2015-03-14 04:00:00,8427.0 -2015-03-14 05:00:00,8372.0 -2015-03-14 06:00:00,8435.0 -2015-03-14 07:00:00,8709.0 -2015-03-14 08:00:00,9165.0 -2015-03-14 09:00:00,9459.0 -2015-03-14 10:00:00,9741.0 -2015-03-14 11:00:00,9895.0 -2015-03-14 12:00:00,9886.0 -2015-03-14 13:00:00,9839.0 -2015-03-14 14:00:00,9672.0 -2015-03-14 15:00:00,9528.0 -2015-03-14 16:00:00,9381.0 -2015-03-14 17:00:00,9257.0 -2015-03-14 18:00:00,9210.0 -2015-03-14 19:00:00,9210.0 -2015-03-14 20:00:00,9348.0 -2015-03-14 21:00:00,9980.0 -2015-03-14 22:00:00,10027.0 -2015-03-14 23:00:00,9800.0 -2015-03-15 00:00:00,9434.0 -2015-03-13 01:00:00,9766.0 -2015-03-13 02:00:00,9270.0 -2015-03-13 03:00:00,8988.0 -2015-03-13 04:00:00,8827.0 -2015-03-13 05:00:00,8811.0 -2015-03-13 06:00:00,9046.0 -2015-03-13 07:00:00,9728.0 -2015-03-13 08:00:00,10860.0 -2015-03-13 09:00:00,11355.0 -2015-03-13 10:00:00,11377.0 -2015-03-13 11:00:00,11289.0 -2015-03-13 12:00:00,11265.0 -2015-03-13 13:00:00,11225.0 -2015-03-13 14:00:00,11125.0 -2015-03-13 15:00:00,11074.0 -2015-03-13 16:00:00,10914.0 -2015-03-13 17:00:00,10739.0 -2015-03-13 18:00:00,10590.0 -2015-03-13 19:00:00,10481.0 -2015-03-13 20:00:00,10479.0 -2015-03-13 21:00:00,11018.0 -2015-03-13 22:00:00,10865.0 -2015-03-13 23:00:00,10594.0 -2015-03-14 00:00:00,9959.0 -2015-03-12 01:00:00,9859.0 -2015-03-12 02:00:00,9483.0 -2015-03-12 03:00:00,9205.0 -2015-03-12 04:00:00,9111.0 -2015-03-12 05:00:00,9099.0 -2015-03-12 06:00:00,9364.0 -2015-03-12 07:00:00,10136.0 -2015-03-12 08:00:00,11363.0 -2015-03-12 09:00:00,11861.0 -2015-03-12 10:00:00,11776.0 -2015-03-12 11:00:00,11706.0 -2015-03-12 12:00:00,11658.0 -2015-03-12 13:00:00,11529.0 -2015-03-12 14:00:00,11432.0 -2015-03-12 15:00:00,11362.0 -2015-03-12 16:00:00,11175.0 -2015-03-12 17:00:00,11001.0 -2015-03-12 18:00:00,10844.0 -2015-03-12 19:00:00,10772.0 -2015-03-12 20:00:00,10840.0 -2015-03-12 21:00:00,11532.0 -2015-03-12 22:00:00,11509.0 -2015-03-12 23:00:00,11116.0 -2015-03-13 00:00:00,10423.0 -2015-03-11 01:00:00,9875.0 -2015-03-11 02:00:00,9408.0 -2015-03-11 03:00:00,9189.0 -2015-03-11 04:00:00,9048.0 -2015-03-11 05:00:00,9043.0 -2015-03-11 06:00:00,9306.0 -2015-03-11 07:00:00,10050.0 -2015-03-11 08:00:00,11191.0 -2015-03-11 09:00:00,11687.0 -2015-03-11 10:00:00,11637.0 -2015-03-11 11:00:00,11539.0 -2015-03-11 12:00:00,11457.0 -2015-03-11 13:00:00,11388.0 -2015-03-11 14:00:00,11311.0 -2015-03-11 15:00:00,11280.0 -2015-03-11 16:00:00,11134.0 -2015-03-11 17:00:00,10977.0 -2015-03-11 18:00:00,10852.0 -2015-03-11 19:00:00,10805.0 -2015-03-11 20:00:00,10911.0 -2015-03-11 21:00:00,11596.0 -2015-03-11 22:00:00,11583.0 -2015-03-11 23:00:00,11163.0 -2015-03-12 00:00:00,10537.0 -2015-03-10 01:00:00,9878.0 -2015-03-10 02:00:00,9439.0 -2015-03-10 03:00:00,9192.0 -2015-03-10 04:00:00,9029.0 -2015-03-10 05:00:00,8999.0 -2015-03-10 06:00:00,9226.0 -2015-03-10 07:00:00,9918.0 -2015-03-10 08:00:00,11080.0 -2015-03-10 09:00:00,11777.0 -2015-03-10 10:00:00,11803.0 -2015-03-10 11:00:00,11803.0 -2015-03-10 12:00:00,11796.0 -2015-03-10 13:00:00,11677.0 -2015-03-10 14:00:00,11501.0 -2015-03-10 15:00:00,11422.0 -2015-03-10 16:00:00,11200.0 -2015-03-10 17:00:00,11015.0 -2015-03-10 18:00:00,10904.0 -2015-03-10 19:00:00,10869.0 -2015-03-10 20:00:00,11009.0 -2015-03-10 21:00:00,11659.0 -2015-03-10 22:00:00,11624.0 -2015-03-10 23:00:00,11215.0 -2015-03-11 00:00:00,10556.0 -2015-03-09 01:00:00,9808.0 -2015-03-09 02:00:00,9457.0 -2015-03-09 03:00:00,9314.0 -2015-03-09 04:00:00,9243.0 -2015-03-09 05:00:00,9353.0 -2015-03-09 06:00:00,9652.0 -2015-03-09 07:00:00,10418.0 -2015-03-09 08:00:00,11590.0 -2015-03-09 09:00:00,12204.0 -2015-03-09 10:00:00,12063.0 -2015-03-09 11:00:00,11888.0 -2015-03-09 12:00:00,11768.0 -2015-03-09 13:00:00,11597.0 -2015-03-09 14:00:00,11462.0 -2015-03-09 15:00:00,11395.0 -2015-03-09 16:00:00,11192.0 -2015-03-09 17:00:00,11000.0 -2015-03-09 18:00:00,10883.0 -2015-03-09 19:00:00,10805.0 -2015-03-09 20:00:00,10973.0 -2015-03-09 21:00:00,11722.0 -2015-03-09 22:00:00,11673.0 -2015-03-09 23:00:00,11253.0 -2015-03-10 00:00:00,10564.0 -2015-03-08 01:00:00,9825.0 -2015-03-08 02:00:00,9433.0 -2015-03-08 04:00:00,9239.0 -2015-03-08 05:00:00,9082.0 -2015-03-08 06:00:00,9071.0 -2015-03-08 07:00:00,9205.0 -2015-03-08 08:00:00,9444.0 -2015-03-08 09:00:00,9495.0 -2015-03-08 10:00:00,9679.0 -2015-03-08 11:00:00,9849.0 -2015-03-08 12:00:00,9957.0 -2015-03-08 13:00:00,9954.0 -2015-03-08 14:00:00,9890.0 -2015-03-08 15:00:00,9767.0 -2015-03-08 16:00:00,9681.0 -2015-03-08 17:00:00,9750.0 -2015-03-08 18:00:00,9828.0 -2015-03-08 19:00:00,10056.0 -2015-03-08 20:00:00,10410.0 -2015-03-08 21:00:00,11061.0 -2015-03-08 22:00:00,10987.0 -2015-03-08 23:00:00,10746.0 -2015-03-09 00:00:00,10268.0 -2015-03-07 01:00:00,11182.0 -2015-03-07 02:00:00,10671.0 -2015-03-07 03:00:00,10439.0 -2015-03-07 04:00:00,10207.0 -2015-03-07 05:00:00,10184.0 -2015-03-07 06:00:00,10192.0 -2015-03-07 07:00:00,10428.0 -2015-03-07 08:00:00,10585.0 -2015-03-07 09:00:00,10795.0 -2015-03-07 10:00:00,10865.0 -2015-03-07 11:00:00,10910.0 -2015-03-07 12:00:00,10729.0 -2015-03-07 13:00:00,10609.0 -2015-03-07 14:00:00,10357.0 -2015-03-07 15:00:00,10128.0 -2015-03-07 16:00:00,9949.0 -2015-03-07 17:00:00,9877.0 -2015-03-07 18:00:00,9954.0 -2015-03-07 19:00:00,10340.0 -2015-03-07 20:00:00,11088.0 -2015-03-07 21:00:00,11141.0 -2015-03-07 22:00:00,10995.0 -2015-03-07 23:00:00,10763.0 -2015-03-08 00:00:00,10299.0 -2015-03-06 01:00:00,11983.0 -2015-03-06 02:00:00,11559.0 -2015-03-06 03:00:00,11360.0 -2015-03-06 04:00:00,11275.0 -2015-03-06 05:00:00,11314.0 -2015-03-06 06:00:00,11546.0 -2015-03-06 07:00:00,12237.0 -2015-03-06 08:00:00,13022.0 -2015-03-06 09:00:00,13361.0 -2015-03-06 10:00:00,13366.0 -2015-03-06 11:00:00,13332.0 -2015-03-06 12:00:00,13283.0 -2015-03-06 13:00:00,13125.0 -2015-03-06 14:00:00,12872.0 -2015-03-06 15:00:00,12780.0 -2015-03-06 16:00:00,12566.0 -2015-03-06 17:00:00,12395.0 -2015-03-06 18:00:00,12454.0 -2015-03-06 19:00:00,12736.0 -2015-03-06 20:00:00,13415.0 -2015-03-06 21:00:00,13275.0 -2015-03-06 22:00:00,12979.0 -2015-03-06 23:00:00,12548.0 -2015-03-07 00:00:00,11862.0 -2015-03-05 01:00:00,11761.0 -2015-03-05 02:00:00,11399.0 -2015-03-05 03:00:00,11187.0 -2015-03-05 04:00:00,11081.0 -2015-03-05 05:00:00,11108.0 -2015-03-05 06:00:00,11386.0 -2015-03-05 07:00:00,12090.0 -2015-03-05 08:00:00,13035.0 -2015-03-05 09:00:00,13331.0 -2015-03-05 10:00:00,13376.0 -2015-03-05 11:00:00,13273.0 -2015-03-05 12:00:00,13214.0 -2015-03-05 13:00:00,13136.0 -2015-03-05 14:00:00,13022.0 -2015-03-05 15:00:00,12952.0 -2015-03-05 16:00:00,12792.0 -2015-03-05 17:00:00,12686.0 -2015-03-05 18:00:00,12783.0 -2015-03-05 19:00:00,13200.0 -2015-03-05 20:00:00,14002.0 -2015-03-05 21:00:00,14032.0 -2015-03-05 22:00:00,13829.0 -2015-03-05 23:00:00,13353.0 -2015-03-06 00:00:00,12648.0 -2015-03-04 01:00:00,10813.0 -2015-03-04 02:00:00,10368.0 -2015-03-04 03:00:00,10144.0 -2015-03-04 04:00:00,10055.0 -2015-03-04 05:00:00,10146.0 -2015-03-04 06:00:00,10504.0 -2015-03-04 07:00:00,11314.0 -2015-03-04 08:00:00,12276.0 -2015-03-04 09:00:00,12738.0 -2015-03-04 10:00:00,12863.0 -2015-03-04 11:00:00,12969.0 -2015-03-04 12:00:00,12972.0 -2015-03-04 13:00:00,12879.0 -2015-03-04 14:00:00,12742.0 -2015-03-04 15:00:00,12760.0 -2015-03-04 16:00:00,12649.0 -2015-03-04 17:00:00,12600.0 -2015-03-04 18:00:00,12803.0 -2015-03-04 19:00:00,13215.0 -2015-03-04 20:00:00,13897.0 -2015-03-04 21:00:00,13835.0 -2015-03-04 22:00:00,13623.0 -2015-03-04 23:00:00,13124.0 -2015-03-05 00:00:00,12397.0 -2015-03-03 01:00:00,10940.0 -2015-03-03 02:00:00,10516.0 -2015-03-03 03:00:00,10286.0 -2015-03-03 04:00:00,10154.0 -2015-03-03 05:00:00,10190.0 -2015-03-03 06:00:00,10515.0 -2015-03-03 07:00:00,11261.0 -2015-03-03 08:00:00,12214.0 -2015-03-03 09:00:00,12689.0 -2015-03-03 10:00:00,12927.0 -2015-03-03 11:00:00,13057.0 -2015-03-03 12:00:00,13175.0 -2015-03-03 13:00:00,13105.0 -2015-03-03 14:00:00,12979.0 -2015-03-03 15:00:00,12944.0 -2015-03-03 16:00:00,12814.0 -2015-03-03 17:00:00,12704.0 -2015-03-03 18:00:00,12737.0 -2015-03-03 19:00:00,13092.0 -2015-03-03 20:00:00,13299.0 -2015-03-03 21:00:00,13159.0 -2015-03-03 22:00:00,12813.0 -2015-03-03 23:00:00,12269.0 -2015-03-04 00:00:00,11535.0 -2015-03-02 01:00:00,10979.0 -2015-03-02 02:00:00,10657.0 -2015-03-02 03:00:00,10562.0 -2015-03-02 04:00:00,10484.0 -2015-03-02 05:00:00,10511.0 -2015-03-02 06:00:00,10808.0 -2015-03-02 07:00:00,11575.0 -2015-03-02 08:00:00,12522.0 -2015-03-02 09:00:00,12873.0 -2015-03-02 10:00:00,12826.0 -2015-03-02 11:00:00,12735.0 -2015-03-02 12:00:00,12657.0 -2015-03-02 13:00:00,12536.0 -2015-03-02 14:00:00,12379.0 -2015-03-02 15:00:00,12287.0 -2015-03-02 16:00:00,12131.0 -2015-03-02 17:00:00,12033.0 -2015-03-02 18:00:00,12238.0 -2015-03-02 19:00:00,12769.0 -2015-03-02 20:00:00,13329.0 -2015-03-02 21:00:00,13214.0 -2015-03-02 22:00:00,12943.0 -2015-03-02 23:00:00,12426.0 -2015-03-03 00:00:00,11638.0 -2015-03-01 01:00:00,11149.0 -2015-03-01 02:00:00,10762.0 -2015-03-01 03:00:00,10530.0 -2015-03-01 04:00:00,10380.0 -2015-03-01 05:00:00,10347.0 -2015-03-01 06:00:00,10342.0 -2015-03-01 07:00:00,10513.0 -2015-03-01 08:00:00,10601.0 -2015-03-01 09:00:00,10619.0 -2015-03-01 10:00:00,10924.0 -2015-03-01 11:00:00,11110.0 -2015-03-01 12:00:00,11149.0 -2015-03-01 13:00:00,11120.0 -2015-03-01 14:00:00,10929.0 -2015-03-01 15:00:00,10873.0 -2015-03-01 16:00:00,10742.0 -2015-03-01 17:00:00,10742.0 -2015-03-01 18:00:00,10980.0 -2015-03-01 19:00:00,11632.0 -2015-03-01 20:00:00,12483.0 -2015-03-01 21:00:00,12495.0 -2015-03-01 22:00:00,12310.0 -2015-03-01 23:00:00,11981.0 -2015-03-02 00:00:00,11437.0 -2015-02-28 01:00:00,12256.0 -2015-02-28 02:00:00,11795.0 -2015-02-28 03:00:00,11632.0 -2015-02-28 04:00:00,11518.0 -2015-02-28 05:00:00,11502.0 -2015-02-28 06:00:00,11603.0 -2015-02-28 07:00:00,11805.0 -2015-02-28 08:00:00,12145.0 -2015-02-28 09:00:00,12286.0 -2015-02-28 10:00:00,12407.0 -2015-02-28 11:00:00,12414.0 -2015-02-28 12:00:00,12300.0 -2015-02-28 13:00:00,12150.0 -2015-02-28 14:00:00,11852.0 -2015-02-28 15:00:00,11592.0 -2015-02-28 16:00:00,11385.0 -2015-02-28 17:00:00,11374.0 -2015-02-28 18:00:00,11626.0 -2015-02-28 19:00:00,12195.0 -2015-02-28 20:00:00,12673.0 -2015-02-28 21:00:00,12630.0 -2015-02-28 22:00:00,12456.0 -2015-02-28 23:00:00,12168.0 -2015-03-01 00:00:00,11663.0 -2015-02-27 01:00:00,12236.0 -2015-02-27 02:00:00,11835.0 -2015-02-27 03:00:00,11637.0 -2015-02-27 04:00:00,11553.0 -2015-02-27 05:00:00,11582.0 -2015-02-27 06:00:00,11791.0 -2015-02-27 07:00:00,12427.0 -2015-02-27 08:00:00,13251.0 -2015-02-27 09:00:00,13560.0 -2015-02-27 10:00:00,13560.0 -2015-02-27 11:00:00,13567.0 -2015-02-27 12:00:00,13489.0 -2015-02-27 13:00:00,13416.0 -2015-02-27 14:00:00,13242.0 -2015-02-27 15:00:00,13138.0 -2015-02-27 16:00:00,12935.0 -2015-02-27 17:00:00,12792.0 -2015-02-27 18:00:00,12895.0 -2015-02-27 19:00:00,13368.0 -2015-02-27 20:00:00,14048.0 -2015-02-27 21:00:00,13976.0 -2015-02-27 22:00:00,13810.0 -2015-02-27 23:00:00,13472.0 -2015-02-28 00:00:00,12885.0 -2015-02-26 01:00:00,11738.0 -2015-02-26 02:00:00,11325.0 -2015-02-26 03:00:00,11083.0 -2015-02-26 04:00:00,10954.0 -2015-02-26 05:00:00,11006.0 -2015-02-26 06:00:00,11260.0 -2015-02-26 07:00:00,11916.0 -2015-02-26 08:00:00,12832.0 -2015-02-26 09:00:00,13262.0 -2015-02-26 10:00:00,13442.0 -2015-02-26 11:00:00,13518.0 -2015-02-26 12:00:00,13554.0 -2015-02-26 13:00:00,13418.0 -2015-02-26 14:00:00,13273.0 -2015-02-26 15:00:00,13259.0 -2015-02-26 16:00:00,13133.0 -2015-02-26 17:00:00,13038.0 -2015-02-26 18:00:00,13161.0 -2015-02-26 19:00:00,13664.0 -2015-02-26 20:00:00,14283.0 -2015-02-26 21:00:00,14229.0 -2015-02-26 22:00:00,14039.0 -2015-02-26 23:00:00,13598.0 -2015-02-27 00:00:00,12852.0 -2015-02-25 01:00:00,11570.0 -2015-02-25 02:00:00,11174.0 -2015-02-25 03:00:00,10955.0 -2015-02-25 04:00:00,10851.0 -2015-02-25 05:00:00,10906.0 -2015-02-25 06:00:00,11200.0 -2015-02-25 07:00:00,11916.0 -2015-02-25 08:00:00,12860.0 -2015-02-25 09:00:00,13133.0 -2015-02-25 10:00:00,13202.0 -2015-02-25 11:00:00,13187.0 -2015-02-25 12:00:00,13136.0 -2015-02-25 13:00:00,13011.0 -2015-02-25 14:00:00,12948.0 -2015-02-25 15:00:00,12966.0 -2015-02-25 16:00:00,12946.0 -2015-02-25 17:00:00,12995.0 -2015-02-25 18:00:00,13240.0 -2015-02-25 19:00:00,13702.0 -2015-02-25 20:00:00,14001.0 -2015-02-25 21:00:00,13883.0 -2015-02-25 22:00:00,13654.0 -2015-02-25 23:00:00,13175.0 -2015-02-26 00:00:00,12444.0 -2015-02-24 01:00:00,12473.0 -2015-02-24 02:00:00,12084.0 -2015-02-24 03:00:00,11876.0 -2015-02-24 04:00:00,11792.0 -2015-02-24 05:00:00,11798.0 -2015-02-24 06:00:00,12028.0 -2015-02-24 07:00:00,12682.0 -2015-02-24 08:00:00,13610.0 -2015-02-24 09:00:00,13974.0 -2015-02-24 10:00:00,14141.0 -2015-02-24 11:00:00,14234.0 -2015-02-24 12:00:00,14226.0 -2015-02-24 13:00:00,14134.0 -2015-02-24 14:00:00,13970.0 -2015-02-24 15:00:00,13915.0 -2015-02-24 16:00:00,13647.0 -2015-02-24 17:00:00,13344.0 -2015-02-24 18:00:00,13231.0 -2015-02-24 19:00:00,13645.0 -2015-02-24 20:00:00,14089.0 -2015-02-24 21:00:00,13936.0 -2015-02-24 22:00:00,13613.0 -2015-02-24 23:00:00,13119.0 -2015-02-25 00:00:00,12503.0 -2015-02-23 01:00:00,11790.0 -2015-02-23 02:00:00,11526.0 -2015-02-23 03:00:00,11400.0 -2015-02-23 04:00:00,11380.0 -2015-02-23 05:00:00,11475.0 -2015-02-23 06:00:00,11782.0 -2015-02-23 07:00:00,12543.0 -2015-02-23 08:00:00,13565.0 -2015-02-23 09:00:00,13948.0 -2015-02-23 10:00:00,13989.0 -2015-02-23 11:00:00,13920.0 -2015-02-23 12:00:00,13858.0 -2015-02-23 13:00:00,13741.0 -2015-02-23 14:00:00,13599.0 -2015-02-23 15:00:00,13487.0 -2015-02-23 16:00:00,13311.0 -2015-02-23 17:00:00,13206.0 -2015-02-23 18:00:00,13350.0 -2015-02-23 19:00:00,14009.0 -2015-02-23 20:00:00,14664.0 -2015-02-23 21:00:00,14612.0 -2015-02-23 22:00:00,14383.0 -2015-02-23 23:00:00,13907.0 -2015-02-24 00:00:00,13134.0 -2015-02-22 01:00:00,10687.0 -2015-02-22 02:00:00,10331.0 -2015-02-22 03:00:00,10094.0 -2015-02-22 04:00:00,9980.0 -2015-02-22 05:00:00,9991.0 -2015-02-22 06:00:00,10082.0 -2015-02-22 07:00:00,10308.0 -2015-02-22 08:00:00,10531.0 -2015-02-22 09:00:00,10593.0 -2015-02-22 10:00:00,10906.0 -2015-02-22 11:00:00,11153.0 -2015-02-22 12:00:00,11167.0 -2015-02-22 13:00:00,11138.0 -2015-02-22 14:00:00,11089.0 -2015-02-22 15:00:00,11064.0 -2015-02-22 16:00:00,10985.0 -2015-02-22 17:00:00,11080.0 -2015-02-22 18:00:00,11460.0 -2015-02-22 19:00:00,12290.0 -2015-02-22 20:00:00,13062.0 -2015-02-22 21:00:00,13094.0 -2015-02-22 22:00:00,12898.0 -2015-02-22 23:00:00,12680.0 -2015-02-23 00:00:00,12221.0 -2015-02-21 01:00:00,12125.0 -2015-02-21 02:00:00,11592.0 -2015-02-21 03:00:00,11261.0 -2015-02-21 04:00:00,11023.0 -2015-02-21 05:00:00,10923.0 -2015-02-21 06:00:00,10900.0 -2015-02-21 07:00:00,11181.0 -2015-02-21 08:00:00,11542.0 -2015-02-21 09:00:00,11672.0 -2015-02-21 10:00:00,11986.0 -2015-02-21 11:00:00,12157.0 -2015-02-21 12:00:00,12081.0 -2015-02-21 13:00:00,11826.0 -2015-02-21 14:00:00,11574.0 -2015-02-21 15:00:00,11371.0 -2015-02-21 16:00:00,11280.0 -2015-02-21 17:00:00,11227.0 -2015-02-21 18:00:00,11449.0 -2015-02-21 19:00:00,11993.0 -2015-02-21 20:00:00,12309.0 -2015-02-21 21:00:00,12172.0 -2015-02-21 22:00:00,11989.0 -2015-02-21 23:00:00,11687.0 -2015-02-22 00:00:00,11180.0 -2015-02-20 01:00:00,12920.0 -2015-02-20 02:00:00,12522.0 -2015-02-20 03:00:00,12317.0 -2015-02-20 04:00:00,12191.0 -2015-02-20 05:00:00,12193.0 -2015-02-20 06:00:00,12409.0 -2015-02-20 07:00:00,13020.0 -2015-02-20 08:00:00,13917.0 -2015-02-20 09:00:00,14332.0 -2015-02-20 10:00:00,14540.0 -2015-02-20 11:00:00,14600.0 -2015-02-20 12:00:00,14593.0 -2015-02-20 13:00:00,14506.0 -2015-02-20 14:00:00,14283.0 -2015-02-20 15:00:00,14237.0 -2015-02-20 16:00:00,14099.0 -2015-02-20 17:00:00,14021.0 -2015-02-20 18:00:00,14002.0 -2015-02-20 19:00:00,14342.0 -2015-02-20 20:00:00,14631.0 -2015-02-20 21:00:00,14393.0 -2015-02-20 22:00:00,14058.0 -2015-02-20 23:00:00,13612.0 -2015-02-21 00:00:00,12830.0 -2015-02-19 01:00:00,13080.0 -2015-02-19 02:00:00,12693.0 -2015-02-19 03:00:00,12425.0 -2015-02-19 04:00:00,12293.0 -2015-02-19 05:00:00,12280.0 -2015-02-19 06:00:00,12529.0 -2015-02-19 07:00:00,13097.0 -2015-02-19 08:00:00,13974.0 -2015-02-19 09:00:00,14332.0 -2015-02-19 10:00:00,14519.0 -2015-02-19 11:00:00,14570.0 -2015-02-19 12:00:00,14604.0 -2015-02-19 13:00:00,14511.0 -2015-02-19 14:00:00,14401.0 -2015-02-19 15:00:00,14272.0 -2015-02-19 16:00:00,14073.0 -2015-02-19 17:00:00,13920.0 -2015-02-19 18:00:00,14054.0 -2015-02-19 19:00:00,14712.0 -2015-02-19 20:00:00,15183.0 -2015-02-19 21:00:00,15106.0 -2015-02-19 22:00:00,14819.0 -2015-02-19 23:00:00,14361.0 -2015-02-20 00:00:00,13634.0 -2015-02-18 01:00:00,12177.0 -2015-02-18 02:00:00,11776.0 -2015-02-18 03:00:00,11570.0 -2015-02-18 04:00:00,11481.0 -2015-02-18 05:00:00,11530.0 -2015-02-18 06:00:00,11774.0 -2015-02-18 07:00:00,12492.0 -2015-02-18 08:00:00,13445.0 -2015-02-18 09:00:00,13884.0 -2015-02-18 10:00:00,13971.0 -2015-02-18 11:00:00,14010.0 -2015-02-18 12:00:00,14046.0 -2015-02-18 13:00:00,13994.0 -2015-02-18 14:00:00,13938.0 -2015-02-18 15:00:00,13990.0 -2015-02-18 16:00:00,13960.0 -2015-02-18 17:00:00,14003.0 -2015-02-18 18:00:00,14244.0 -2015-02-18 19:00:00,14882.0 -2015-02-18 20:00:00,15279.0 -2015-02-18 21:00:00,15135.0 -2015-02-18 22:00:00,14895.0 -2015-02-18 23:00:00,14433.0 -2015-02-19 00:00:00,13709.0 -2015-02-17 01:00:00,11808.0 -2015-02-17 02:00:00,11336.0 -2015-02-17 03:00:00,11104.0 -2015-02-17 04:00:00,10974.0 -2015-02-17 05:00:00,10973.0 -2015-02-17 06:00:00,11210.0 -2015-02-17 07:00:00,11884.0 -2015-02-17 08:00:00,12912.0 -2015-02-17 09:00:00,13328.0 -2015-02-17 10:00:00,13396.0 -2015-02-17 11:00:00,13476.0 -2015-02-17 12:00:00,13489.0 -2015-02-17 13:00:00,13406.0 -2015-02-17 14:00:00,13290.0 -2015-02-17 15:00:00,13301.0 -2015-02-17 16:00:00,13250.0 -2015-02-17 17:00:00,13215.0 -2015-02-17 18:00:00,13447.0 -2015-02-17 19:00:00,14032.0 -2015-02-17 20:00:00,14385.0 -2015-02-17 21:00:00,14276.0 -2015-02-17 22:00:00,14052.0 -2015-02-17 23:00:00,13510.0 -2015-02-18 00:00:00,12865.0 -2015-02-16 01:00:00,11434.0 -2015-02-16 02:00:00,11110.0 -2015-02-16 03:00:00,10925.0 -2015-02-16 04:00:00,10881.0 -2015-02-16 05:00:00,10910.0 -2015-02-16 06:00:00,11177.0 -2015-02-16 07:00:00,11804.0 -2015-02-16 08:00:00,12672.0 -2015-02-16 09:00:00,13105.0 -2015-02-16 10:00:00,13391.0 -2015-02-16 11:00:00,13517.0 -2015-02-16 12:00:00,13453.0 -2015-02-16 13:00:00,13274.0 -2015-02-16 14:00:00,13183.0 -2015-02-16 15:00:00,13146.0 -2015-02-16 16:00:00,13002.0 -2015-02-16 17:00:00,12959.0 -2015-02-16 18:00:00,13101.0 -2015-02-16 19:00:00,13832.0 -2015-02-16 20:00:00,14162.0 -2015-02-16 21:00:00,14041.0 -2015-02-16 22:00:00,13765.0 -2015-02-16 23:00:00,13230.0 -2015-02-17 00:00:00,12456.0 -2015-02-15 01:00:00,12028.0 -2015-02-15 02:00:00,11685.0 -2015-02-15 03:00:00,11477.0 -2015-02-15 04:00:00,11373.0 -2015-02-15 05:00:00,11312.0 -2015-02-15 06:00:00,11300.0 -2015-02-15 07:00:00,11415.0 -2015-02-15 08:00:00,11616.0 -2015-02-15 09:00:00,11610.0 -2015-02-15 10:00:00,11816.0 -2015-02-15 11:00:00,11915.0 -2015-02-15 12:00:00,11910.0 -2015-02-15 13:00:00,11839.0 -2015-02-15 14:00:00,11736.0 -2015-02-15 15:00:00,11603.0 -2015-02-15 16:00:00,11584.0 -2015-02-15 17:00:00,11648.0 -2015-02-15 18:00:00,11935.0 -2015-02-15 19:00:00,12653.0 -2015-02-15 20:00:00,13040.0 -2015-02-15 21:00:00,12956.0 -2015-02-15 22:00:00,12734.0 -2015-02-15 23:00:00,12429.0 -2015-02-16 00:00:00,11900.0 -2015-02-14 01:00:00,11353.0 -2015-02-14 02:00:00,10845.0 -2015-02-14 03:00:00,10526.0 -2015-02-14 04:00:00,10353.0 -2015-02-14 05:00:00,10313.0 -2015-02-14 06:00:00,10424.0 -2015-02-14 07:00:00,10732.0 -2015-02-14 08:00:00,11242.0 -2015-02-14 09:00:00,11600.0 -2015-02-14 10:00:00,12037.0 -2015-02-14 11:00:00,12319.0 -2015-02-14 12:00:00,12405.0 -2015-02-14 13:00:00,12398.0 -2015-02-14 14:00:00,12302.0 -2015-02-14 15:00:00,12191.0 -2015-02-14 16:00:00,12053.0 -2015-02-14 17:00:00,12042.0 -2015-02-14 18:00:00,12247.0 -2015-02-14 19:00:00,13009.0 -2015-02-14 20:00:00,13501.0 -2015-02-14 21:00:00,13392.0 -2015-02-14 22:00:00,13284.0 -2015-02-14 23:00:00,12980.0 -2015-02-15 00:00:00,12515.0 -2015-02-13 01:00:00,11911.0 -2015-02-13 02:00:00,11516.0 -2015-02-13 03:00:00,11334.0 -2015-02-13 04:00:00,11234.0 -2015-02-13 05:00:00,11276.0 -2015-02-13 06:00:00,11515.0 -2015-02-13 07:00:00,12082.0 -2015-02-13 08:00:00,13128.0 -2015-02-13 09:00:00,13463.0 -2015-02-13 10:00:00,13620.0 -2015-02-13 11:00:00,13701.0 -2015-02-13 12:00:00,13734.0 -2015-02-13 13:00:00,13690.0 -2015-02-13 14:00:00,13601.0 -2015-02-13 15:00:00,13538.0 -2015-02-13 16:00:00,13444.0 -2015-02-13 17:00:00,13371.0 -2015-02-13 18:00:00,13427.0 -2015-02-13 19:00:00,13837.0 -2015-02-13 20:00:00,13930.0 -2015-02-13 21:00:00,13656.0 -2015-02-13 22:00:00,13269.0 -2015-02-13 23:00:00,12870.0 -2015-02-14 00:00:00,12115.0 -2015-02-12 01:00:00,11480.0 -2015-02-12 02:00:00,11061.0 -2015-02-12 03:00:00,10825.0 -2015-02-12 04:00:00,10779.0 -2015-02-12 05:00:00,10850.0 -2015-02-12 06:00:00,11185.0 -2015-02-12 07:00:00,11955.0 -2015-02-12 08:00:00,13075.0 -2015-02-12 09:00:00,13461.0 -2015-02-12 10:00:00,13503.0 -2015-02-12 11:00:00,13497.0 -2015-02-12 12:00:00,13482.0 -2015-02-12 13:00:00,13334.0 -2015-02-12 14:00:00,13196.0 -2015-02-12 15:00:00,13086.0 -2015-02-12 16:00:00,12928.0 -2015-02-12 17:00:00,12839.0 -2015-02-12 18:00:00,13139.0 -2015-02-12 19:00:00,13863.0 -2015-02-12 20:00:00,14189.0 -2015-02-12 21:00:00,14028.0 -2015-02-12 22:00:00,13774.0 -2015-02-12 23:00:00,13295.0 -2015-02-13 00:00:00,12579.0 -2015-02-11 01:00:00,10754.0 -2015-02-11 02:00:00,10303.0 -2015-02-11 03:00:00,10061.0 -2015-02-11 04:00:00,9933.0 -2015-02-11 05:00:00,9945.0 -2015-02-11 06:00:00,10186.0 -2015-02-11 07:00:00,10941.0 -2015-02-11 08:00:00,12052.0 -2015-02-11 09:00:00,12516.0 -2015-02-11 10:00:00,12626.0 -2015-02-11 11:00:00,12695.0 -2015-02-11 12:00:00,12702.0 -2015-02-11 13:00:00,12647.0 -2015-02-11 14:00:00,12633.0 -2015-02-11 15:00:00,12689.0 -2015-02-11 16:00:00,12648.0 -2015-02-11 17:00:00,12622.0 -2015-02-11 18:00:00,12798.0 -2015-02-11 19:00:00,13388.0 -2015-02-11 20:00:00,13569.0 -2015-02-11 21:00:00,13438.0 -2015-02-11 22:00:00,13219.0 -2015-02-11 23:00:00,12782.0 -2015-02-12 00:00:00,12092.0 -2015-02-10 01:00:00,11057.0 -2015-02-10 02:00:00,10625.0 -2015-02-10 03:00:00,10383.0 -2015-02-10 04:00:00,10266.0 -2015-02-10 05:00:00,10278.0 -2015-02-10 06:00:00,10567.0 -2015-02-10 07:00:00,11302.0 -2015-02-10 08:00:00,12377.0 -2015-02-10 09:00:00,12700.0 -2015-02-10 10:00:00,12763.0 -2015-02-10 11:00:00,12741.0 -2015-02-10 12:00:00,12592.0 -2015-02-10 13:00:00,12388.0 -2015-02-10 14:00:00,12266.0 -2015-02-10 15:00:00,12162.0 -2015-02-10 16:00:00,12046.0 -2015-02-10 17:00:00,11969.0 -2015-02-10 18:00:00,12246.0 -2015-02-10 19:00:00,12994.0 -2015-02-10 20:00:00,13203.0 -2015-02-10 21:00:00,13073.0 -2015-02-10 22:00:00,12859.0 -2015-02-10 23:00:00,12328.0 -2015-02-11 00:00:00,11481.0 -2015-02-09 01:00:00,10136.0 -2015-02-09 02:00:00,9857.0 -2015-02-09 03:00:00,9711.0 -2015-02-09 04:00:00,9653.0 -2015-02-09 05:00:00,9720.0 -2015-02-09 06:00:00,10065.0 -2015-02-09 07:00:00,10860.0 -2015-02-09 08:00:00,12083.0 -2015-02-09 09:00:00,12696.0 -2015-02-09 10:00:00,12883.0 -2015-02-09 11:00:00,13009.0 -2015-02-09 12:00:00,13088.0 -2015-02-09 13:00:00,13076.0 -2015-02-09 14:00:00,12969.0 -2015-02-09 15:00:00,12934.0 -2015-02-09 16:00:00,12824.0 -2015-02-09 17:00:00,12746.0 -2015-02-09 18:00:00,12898.0 -2015-02-09 19:00:00,13457.0 -2015-02-09 20:00:00,13591.0 -2015-02-09 21:00:00,13389.0 -2015-02-09 22:00:00,13115.0 -2015-02-09 23:00:00,12568.0 -2015-02-10 00:00:00,11790.0 -2015-02-08 01:00:00,9928.0 -2015-02-08 02:00:00,9511.0 -2015-02-08 03:00:00,9261.0 -2015-02-08 04:00:00,9073.0 -2015-02-08 05:00:00,8988.0 -2015-02-08 06:00:00,8971.0 -2015-02-08 07:00:00,9108.0 -2015-02-08 08:00:00,9367.0 -2015-02-08 09:00:00,9373.0 -2015-02-08 10:00:00,9643.0 -2015-02-08 11:00:00,9904.0 -2015-02-08 12:00:00,10048.0 -2015-02-08 13:00:00,10134.0 -2015-02-08 14:00:00,10189.0 -2015-02-08 15:00:00,10224.0 -2015-02-08 16:00:00,10297.0 -2015-02-08 17:00:00,10513.0 -2015-02-08 18:00:00,10953.0 -2015-02-08 19:00:00,11663.0 -2015-02-08 20:00:00,11846.0 -2015-02-08 21:00:00,11741.0 -2015-02-08 22:00:00,11527.0 -2015-02-08 23:00:00,11193.0 -2015-02-09 00:00:00,10668.0 -2015-02-07 01:00:00,11102.0 -2015-02-07 02:00:00,10615.0 -2015-02-07 03:00:00,10334.0 -2015-02-07 04:00:00,10103.0 -2015-02-07 05:00:00,10047.0 -2015-02-07 06:00:00,10043.0 -2015-02-07 07:00:00,10402.0 -2015-02-07 08:00:00,10838.0 -2015-02-07 09:00:00,11002.0 -2015-02-07 10:00:00,11122.0 -2015-02-07 11:00:00,11131.0 -2015-02-07 12:00:00,11025.0 -2015-02-07 13:00:00,10872.0 -2015-02-07 14:00:00,10613.0 -2015-02-07 15:00:00,10396.0 -2015-02-07 16:00:00,10266.0 -2015-02-07 17:00:00,10210.0 -2015-02-07 18:00:00,10384.0 -2015-02-07 19:00:00,11249.0 -2015-02-07 20:00:00,11531.0 -2015-02-07 21:00:00,11409.0 -2015-02-07 22:00:00,11165.0 -2015-02-07 23:00:00,10900.0 -2015-02-08 00:00:00,10408.0 -2015-02-06 01:00:00,12104.0 -2015-02-06 02:00:00,11673.0 -2015-02-06 03:00:00,11390.0 -2015-02-06 04:00:00,11206.0 -2015-02-06 05:00:00,11129.0 -2015-02-06 06:00:00,11304.0 -2015-02-06 07:00:00,11926.0 -2015-02-06 08:00:00,12979.0 -2015-02-06 09:00:00,13333.0 -2015-02-06 10:00:00,13334.0 -2015-02-06 11:00:00,13397.0 -2015-02-06 12:00:00,13275.0 -2015-02-06 13:00:00,13082.0 -2015-02-06 14:00:00,12811.0 -2015-02-06 15:00:00,12662.0 -2015-02-06 16:00:00,12471.0 -2015-02-06 17:00:00,12242.0 -2015-02-06 18:00:00,12376.0 -2015-02-06 19:00:00,13105.0 -2015-02-06 20:00:00,13342.0 -2015-02-06 21:00:00,13101.0 -2015-02-06 22:00:00,12819.0 -2015-02-06 23:00:00,12470.0 -2015-02-07 00:00:00,11746.0 -2015-02-05 01:00:00,11856.0 -2015-02-05 02:00:00,11473.0 -2015-02-05 03:00:00,11238.0 -2015-02-05 04:00:00,11144.0 -2015-02-05 05:00:00,11213.0 -2015-02-05 06:00:00,11517.0 -2015-02-05 07:00:00,12239.0 -2015-02-05 08:00:00,13350.0 -2015-02-05 09:00:00,13693.0 -2015-02-05 10:00:00,13698.0 -2015-02-05 11:00:00,13624.0 -2015-02-05 12:00:00,13569.0 -2015-02-05 13:00:00,13417.0 -2015-02-05 14:00:00,13292.0 -2015-02-05 15:00:00,13214.0 -2015-02-05 16:00:00,13066.0 -2015-02-05 17:00:00,13085.0 -2015-02-05 18:00:00,13339.0 -2015-02-05 19:00:00,14189.0 -2015-02-05 20:00:00,14484.0 -2015-02-05 21:00:00,14365.0 -2015-02-05 22:00:00,14109.0 -2015-02-05 23:00:00,13581.0 -2015-02-06 00:00:00,12797.0 -2015-02-04 01:00:00,11025.0 -2015-02-04 02:00:00,10548.0 -2015-02-04 03:00:00,10278.0 -2015-02-04 04:00:00,10149.0 -2015-02-04 05:00:00,10186.0 -2015-02-04 06:00:00,10466.0 -2015-02-04 07:00:00,11182.0 -2015-02-04 08:00:00,12398.0 -2015-02-04 09:00:00,12881.0 -2015-02-04 10:00:00,13065.0 -2015-02-04 11:00:00,13198.0 -2015-02-04 12:00:00,13277.0 -2015-02-04 13:00:00,13242.0 -2015-02-04 14:00:00,13169.0 -2015-02-04 15:00:00,13264.0 -2015-02-04 16:00:00,13159.0 -2015-02-04 17:00:00,13072.0 -2015-02-04 18:00:00,13196.0 -2015-02-04 19:00:00,13927.0 -2015-02-04 20:00:00,14172.0 -2015-02-04 21:00:00,14043.0 -2015-02-04 22:00:00,13810.0 -2015-02-04 23:00:00,13310.0 -2015-02-05 00:00:00,12554.0 -2015-02-03 01:00:00,11620.0 -2015-02-03 02:00:00,11265.0 -2015-02-03 03:00:00,11058.0 -2015-02-03 04:00:00,10902.0 -2015-02-03 05:00:00,10935.0 -2015-02-03 06:00:00,11142.0 -2015-02-03 07:00:00,11806.0 -2015-02-03 08:00:00,12836.0 -2015-02-03 09:00:00,13201.0 -2015-02-03 10:00:00,13243.0 -2015-02-03 11:00:00,13160.0 -2015-02-03 12:00:00,13042.0 -2015-02-03 13:00:00,12900.0 -2015-02-03 14:00:00,12766.0 -2015-02-03 15:00:00,12780.0 -2015-02-03 16:00:00,12817.0 -2015-02-03 17:00:00,12925.0 -2015-02-03 18:00:00,13130.0 -2015-02-03 19:00:00,13727.0 -2015-02-03 20:00:00,13673.0 -2015-02-03 21:00:00,13495.0 -2015-02-03 22:00:00,13175.0 -2015-02-03 23:00:00,12581.0 -2015-02-04 00:00:00,11762.0 -2015-02-02 01:00:00,10811.0 -2015-02-02 02:00:00,10423.0 -2015-02-02 03:00:00,10254.0 -2015-02-02 04:00:00,10182.0 -2015-02-02 05:00:00,10252.0 -2015-02-02 06:00:00,10549.0 -2015-02-02 07:00:00,11125.0 -2015-02-02 08:00:00,12010.0 -2015-02-02 09:00:00,12388.0 -2015-02-02 10:00:00,12509.0 -2015-02-02 11:00:00,12613.0 -2015-02-02 12:00:00,12661.0 -2015-02-02 13:00:00,12623.0 -2015-02-02 14:00:00,12517.0 -2015-02-02 15:00:00,12463.0 -2015-02-02 16:00:00,12411.0 -2015-02-02 17:00:00,12417.0 -2015-02-02 18:00:00,12724.0 -2015-02-02 19:00:00,13732.0 -2015-02-02 20:00:00,13940.0 -2015-02-02 21:00:00,13780.0 -2015-02-02 22:00:00,13532.0 -2015-02-02 23:00:00,13038.0 -2015-02-03 00:00:00,12282.0 -2015-02-01 01:00:00,10403.0 -2015-02-01 02:00:00,9994.0 -2015-02-01 03:00:00,9765.0 -2015-02-01 04:00:00,9544.0 -2015-02-01 05:00:00,9488.0 -2015-02-01 06:00:00,9540.0 -2015-02-01 07:00:00,9717.0 -2015-02-01 08:00:00,9969.0 -2015-02-01 09:00:00,10094.0 -2015-02-01 10:00:00,10379.0 -2015-02-01 11:00:00,10677.0 -2015-02-01 12:00:00,10872.0 -2015-02-01 13:00:00,11029.0 -2015-02-01 14:00:00,11178.0 -2015-02-01 15:00:00,11258.0 -2015-02-01 16:00:00,11330.0 -2015-02-01 17:00:00,11429.0 -2015-02-01 18:00:00,11750.0 -2015-02-01 19:00:00,12453.0 -2015-02-01 20:00:00,12437.0 -2015-02-01 21:00:00,12288.0 -2015-02-01 22:00:00,12084.0 -2015-02-01 23:00:00,11860.0 -2015-02-02 00:00:00,11347.0 -2015-01-31 01:00:00,11125.0 -2015-01-31 02:00:00,10642.0 -2015-01-31 03:00:00,10316.0 -2015-01-31 04:00:00,10093.0 -2015-01-31 05:00:00,10018.0 -2015-01-31 06:00:00,10088.0 -2015-01-31 07:00:00,10386.0 -2015-01-31 08:00:00,10851.0 -2015-01-31 09:00:00,10981.0 -2015-01-31 10:00:00,11235.0 -2015-01-31 11:00:00,11225.0 -2015-01-31 12:00:00,11210.0 -2015-01-31 13:00:00,11131.0 -2015-01-31 14:00:00,10988.0 -2015-01-31 15:00:00,10799.0 -2015-01-31 16:00:00,10772.0 -2015-01-31 17:00:00,10758.0 -2015-01-31 18:00:00,11072.0 -2015-01-31 19:00:00,11706.0 -2015-01-31 20:00:00,11782.0 -2015-01-31 21:00:00,11656.0 -2015-01-31 22:00:00,11564.0 -2015-01-31 23:00:00,11321.0 -2015-02-01 00:00:00,10901.0 -2015-01-30 01:00:00,10775.0 -2015-01-30 02:00:00,10354.0 -2015-01-30 03:00:00,10130.0 -2015-01-30 04:00:00,10052.0 -2015-01-30 05:00:00,10072.0 -2015-01-30 06:00:00,10382.0 -2015-01-30 07:00:00,11142.0 -2015-01-30 08:00:00,12276.0 -2015-01-30 09:00:00,12687.0 -2015-01-30 10:00:00,12639.0 -2015-01-30 11:00:00,12573.0 -2015-01-30 12:00:00,12537.0 -2015-01-30 13:00:00,12415.0 -2015-01-30 14:00:00,12252.0 -2015-01-30 15:00:00,12185.0 -2015-01-30 16:00:00,12034.0 -2015-01-30 17:00:00,11913.0 -2015-01-30 18:00:00,12168.0 -2015-01-30 19:00:00,12943.0 -2015-01-30 20:00:00,13098.0 -2015-01-30 21:00:00,12928.0 -2015-01-30 22:00:00,12655.0 -2015-01-30 23:00:00,12349.0 -2015-01-31 00:00:00,11748.0 -2015-01-29 01:00:00,10833.0 -2015-01-29 02:00:00,10325.0 -2015-01-29 03:00:00,10000.0 -2015-01-29 04:00:00,9836.0 -2015-01-29 05:00:00,9786.0 -2015-01-29 06:00:00,10054.0 -2015-01-29 07:00:00,10751.0 -2015-01-29 08:00:00,11901.0 -2015-01-29 09:00:00,12506.0 -2015-01-29 10:00:00,12687.0 -2015-01-29 11:00:00,12758.0 -2015-01-29 12:00:00,12791.0 -2015-01-29 13:00:00,12828.0 -2015-01-29 14:00:00,12852.0 -2015-01-29 15:00:00,12850.0 -2015-01-29 16:00:00,12777.0 -2015-01-29 17:00:00,12772.0 -2015-01-29 18:00:00,13044.0 -2015-01-29 19:00:00,13448.0 -2015-01-29 20:00:00,13295.0 -2015-01-29 21:00:00,13059.0 -2015-01-29 22:00:00,12728.0 -2015-01-29 23:00:00,12163.0 -2015-01-30 00:00:00,11465.0 -2015-01-28 01:00:00,10964.0 -2015-01-28 02:00:00,10492.0 -2015-01-28 03:00:00,10246.0 -2015-01-28 04:00:00,10148.0 -2015-01-28 05:00:00,10140.0 -2015-01-28 06:00:00,10456.0 -2015-01-28 07:00:00,11212.0 -2015-01-28 08:00:00,12386.0 -2015-01-28 09:00:00,12806.0 -2015-01-28 10:00:00,12799.0 -2015-01-28 11:00:00,12724.0 -2015-01-28 12:00:00,12681.0 -2015-01-28 13:00:00,12555.0 -2015-01-28 14:00:00,12421.0 -2015-01-28 15:00:00,12401.0 -2015-01-28 16:00:00,12368.0 -2015-01-28 17:00:00,12376.0 -2015-01-28 18:00:00,12673.0 -2015-01-28 19:00:00,13454.0 -2015-01-28 20:00:00,13436.0 -2015-01-28 21:00:00,13222.0 -2015-01-28 22:00:00,12902.0 -2015-01-28 23:00:00,12347.0 -2015-01-29 00:00:00,11564.0 -2015-01-27 01:00:00,10952.0 -2015-01-27 02:00:00,10516.0 -2015-01-27 03:00:00,10184.0 -2015-01-27 04:00:00,10022.0 -2015-01-27 05:00:00,9968.0 -2015-01-27 06:00:00,10243.0 -2015-01-27 07:00:00,11041.0 -2015-01-27 08:00:00,12224.0 -2015-01-27 09:00:00,12755.0 -2015-01-27 10:00:00,12784.0 -2015-01-27 11:00:00,12908.0 -2015-01-27 12:00:00,12904.0 -2015-01-27 13:00:00,12725.0 -2015-01-27 14:00:00,12485.0 -2015-01-27 15:00:00,12385.0 -2015-01-27 16:00:00,12269.0 -2015-01-27 17:00:00,12216.0 -2015-01-27 18:00:00,12490.0 -2015-01-27 19:00:00,13333.0 -2015-01-27 20:00:00,13376.0 -2015-01-27 21:00:00,13197.0 -2015-01-27 22:00:00,12954.0 -2015-01-27 23:00:00,12439.0 -2015-01-28 00:00:00,11644.0 -2015-01-26 01:00:00,10570.0 -2015-01-26 02:00:00,10292.0 -2015-01-26 03:00:00,10131.0 -2015-01-26 04:00:00,10065.0 -2015-01-26 05:00:00,10145.0 -2015-01-26 06:00:00,10492.0 -2015-01-26 07:00:00,11320.0 -2015-01-26 08:00:00,12564.0 -2015-01-26 09:00:00,13046.0 -2015-01-26 10:00:00,12994.0 -2015-01-26 11:00:00,12949.0 -2015-01-26 12:00:00,12996.0 -2015-01-26 13:00:00,12930.0 -2015-01-26 14:00:00,12858.0 -2015-01-26 15:00:00,12837.0 -2015-01-26 16:00:00,12793.0 -2015-01-26 17:00:00,12816.0 -2015-01-26 18:00:00,13202.0 -2015-01-26 19:00:00,13781.0 -2015-01-26 20:00:00,13644.0 -2015-01-26 21:00:00,13406.0 -2015-01-26 22:00:00,13065.0 -2015-01-26 23:00:00,12467.0 -2015-01-27 00:00:00,11680.0 -2015-01-25 01:00:00,9917.0 -2015-01-25 02:00:00,9558.0 -2015-01-25 03:00:00,9284.0 -2015-01-25 04:00:00,9170.0 -2015-01-25 05:00:00,9127.0 -2015-01-25 06:00:00,9194.0 -2015-01-25 07:00:00,9321.0 -2015-01-25 08:00:00,9704.0 -2015-01-25 09:00:00,9954.0 -2015-01-25 10:00:00,10297.0 -2015-01-25 11:00:00,10570.0 -2015-01-25 12:00:00,10793.0 -2015-01-25 13:00:00,10866.0 -2015-01-25 14:00:00,10973.0 -2015-01-25 15:00:00,10993.0 -2015-01-25 16:00:00,11028.0 -2015-01-25 17:00:00,11126.0 -2015-01-25 18:00:00,11498.0 -2015-01-25 19:00:00,12275.0 -2015-01-25 20:00:00,12363.0 -2015-01-25 21:00:00,12209.0 -2015-01-25 22:00:00,12040.0 -2015-01-25 23:00:00,11649.0 -2015-01-26 00:00:00,11124.0 -2015-01-24 01:00:00,10798.0 -2015-01-24 02:00:00,10325.0 -2015-01-24 03:00:00,9983.0 -2015-01-24 04:00:00,9831.0 -2015-01-24 05:00:00,9734.0 -2015-01-24 06:00:00,9823.0 -2015-01-24 07:00:00,10029.0 -2015-01-24 08:00:00,10540.0 -2015-01-24 09:00:00,10711.0 -2015-01-24 10:00:00,10864.0 -2015-01-24 11:00:00,10968.0 -2015-01-24 12:00:00,10977.0 -2015-01-24 13:00:00,10822.0 -2015-01-24 14:00:00,10718.0 -2015-01-24 15:00:00,10496.0 -2015-01-24 16:00:00,10375.0 -2015-01-24 17:00:00,10415.0 -2015-01-24 18:00:00,10801.0 -2015-01-24 19:00:00,11483.0 -2015-01-24 20:00:00,11529.0 -2015-01-24 21:00:00,11382.0 -2015-01-24 22:00:00,11234.0 -2015-01-24 23:00:00,10900.0 -2015-01-25 00:00:00,10421.0 -2015-01-23 01:00:00,10792.0 -2015-01-23 02:00:00,10292.0 -2015-01-23 03:00:00,10045.0 -2015-01-23 04:00:00,9901.0 -2015-01-23 05:00:00,9875.0 -2015-01-23 06:00:00,10187.0 -2015-01-23 07:00:00,10868.0 -2015-01-23 08:00:00,11962.0 -2015-01-23 09:00:00,12616.0 -2015-01-23 10:00:00,12696.0 -2015-01-23 11:00:00,12750.0 -2015-01-23 12:00:00,12782.0 -2015-01-23 13:00:00,12691.0 -2015-01-23 14:00:00,12578.0 -2015-01-23 15:00:00,12513.0 -2015-01-23 16:00:00,12387.0 -2015-01-23 17:00:00,12290.0 -2015-01-23 18:00:00,12522.0 -2015-01-23 19:00:00,13181.0 -2015-01-23 20:00:00,13101.0 -2015-01-23 21:00:00,12894.0 -2015-01-23 22:00:00,12564.0 -2015-01-23 23:00:00,12201.0 -2015-01-24 00:00:00,11466.0 -2015-01-22 01:00:00,10668.0 -2015-01-22 02:00:00,10193.0 -2015-01-22 03:00:00,9944.0 -2015-01-22 04:00:00,9807.0 -2015-01-22 05:00:00,9840.0 -2015-01-22 06:00:00,10096.0 -2015-01-22 07:00:00,10803.0 -2015-01-22 08:00:00,11930.0 -2015-01-22 09:00:00,12537.0 -2015-01-22 10:00:00,12601.0 -2015-01-22 11:00:00,12631.0 -2015-01-22 12:00:00,12714.0 -2015-01-22 13:00:00,12707.0 -2015-01-22 14:00:00,12670.0 -2015-01-22 15:00:00,12679.0 -2015-01-22 16:00:00,12585.0 -2015-01-22 17:00:00,12595.0 -2015-01-22 18:00:00,12908.0 -2015-01-22 19:00:00,13404.0 -2015-01-22 20:00:00,13251.0 -2015-01-22 21:00:00,13038.0 -2015-01-22 22:00:00,12760.0 -2015-01-22 23:00:00,12218.0 -2015-01-23 00:00:00,11475.0 -2015-01-21 01:00:00,10546.0 -2015-01-21 02:00:00,10079.0 -2015-01-21 03:00:00,9814.0 -2015-01-21 04:00:00,9681.0 -2015-01-21 05:00:00,9672.0 -2015-01-21 06:00:00,9941.0 -2015-01-21 07:00:00,10670.0 -2015-01-21 08:00:00,11836.0 -2015-01-21 09:00:00,12495.0 -2015-01-21 10:00:00,12545.0 -2015-01-21 11:00:00,12627.0 -2015-01-21 12:00:00,12738.0 -2015-01-21 13:00:00,12700.0 -2015-01-21 14:00:00,12635.0 -2015-01-21 15:00:00,12666.0 -2015-01-21 16:00:00,12569.0 -2015-01-21 17:00:00,12576.0 -2015-01-21 18:00:00,12889.0 -2015-01-21 19:00:00,13385.0 -2015-01-21 20:00:00,13250.0 -2015-01-21 21:00:00,13032.0 -2015-01-21 22:00:00,12707.0 -2015-01-21 23:00:00,12167.0 -2015-01-22 00:00:00,11389.0 -2015-01-20 01:00:00,10345.0 -2015-01-20 02:00:00,9908.0 -2015-01-20 03:00:00,9653.0 -2015-01-20 04:00:00,9504.0 -2015-01-20 05:00:00,9523.0 -2015-01-20 06:00:00,9786.0 -2015-01-20 07:00:00,10559.0 -2015-01-20 08:00:00,11667.0 -2015-01-20 09:00:00,12236.0 -2015-01-20 10:00:00,12234.0 -2015-01-20 11:00:00,12262.0 -2015-01-20 12:00:00,12359.0 -2015-01-20 13:00:00,12362.0 -2015-01-20 14:00:00,12345.0 -2015-01-20 15:00:00,12356.0 -2015-01-20 16:00:00,12335.0 -2015-01-20 17:00:00,12435.0 -2015-01-20 18:00:00,12897.0 -2015-01-20 19:00:00,13258.0 -2015-01-20 20:00:00,13107.0 -2015-01-20 21:00:00,12901.0 -2015-01-20 22:00:00,12604.0 -2015-01-20 23:00:00,12065.0 -2015-01-21 00:00:00,11279.0 -2015-01-19 01:00:00,10009.0 -2015-01-19 02:00:00,9766.0 -2015-01-19 03:00:00,9555.0 -2015-01-19 04:00:00,9530.0 -2015-01-19 05:00:00,9560.0 -2015-01-19 06:00:00,9915.0 -2015-01-19 07:00:00,10548.0 -2015-01-19 08:00:00,11489.0 -2015-01-19 09:00:00,11903.0 -2015-01-19 10:00:00,11954.0 -2015-01-19 11:00:00,11889.0 -2015-01-19 12:00:00,11887.0 -2015-01-19 13:00:00,11823.0 -2015-01-19 14:00:00,11724.0 -2015-01-19 15:00:00,11733.0 -2015-01-19 16:00:00,11709.0 -2015-01-19 17:00:00,11828.0 -2015-01-19 18:00:00,12279.0 -2015-01-19 19:00:00,12889.0 -2015-01-19 20:00:00,12779.0 -2015-01-19 21:00:00,12555.0 -2015-01-19 22:00:00,12255.0 -2015-01-19 23:00:00,11744.0 -2015-01-20 00:00:00,11012.0 -2015-01-18 01:00:00,10110.0 -2015-01-18 02:00:00,9735.0 -2015-01-18 03:00:00,9421.0 -2015-01-18 04:00:00,9293.0 -2015-01-18 05:00:00,9195.0 -2015-01-18 06:00:00,9232.0 -2015-01-18 07:00:00,9378.0 -2015-01-18 08:00:00,9658.0 -2015-01-18 09:00:00,9806.0 -2015-01-18 10:00:00,10050.0 -2015-01-18 11:00:00,10189.0 -2015-01-18 12:00:00,10253.0 -2015-01-18 13:00:00,10240.0 -2015-01-18 14:00:00,10159.0 -2015-01-18 15:00:00,10009.0 -2015-01-18 16:00:00,9991.0 -2015-01-18 17:00:00,9980.0 -2015-01-18 18:00:00,10449.0 -2015-01-18 19:00:00,11313.0 -2015-01-18 20:00:00,11476.0 -2015-01-18 21:00:00,11384.0 -2015-01-18 22:00:00,11258.0 -2015-01-18 23:00:00,10923.0 -2015-01-19 00:00:00,10522.0 -2015-01-17 01:00:00,11096.0 -2015-01-17 02:00:00,10581.0 -2015-01-17 03:00:00,10301.0 -2015-01-17 04:00:00,10088.0 -2015-01-17 05:00:00,9995.0 -2015-01-17 06:00:00,9998.0 -2015-01-17 07:00:00,10281.0 -2015-01-17 08:00:00,10666.0 -2015-01-17 09:00:00,10899.0 -2015-01-17 10:00:00,11016.0 -2015-01-17 11:00:00,11060.0 -2015-01-17 12:00:00,11082.0 -2015-01-17 13:00:00,11015.0 -2015-01-17 14:00:00,10882.0 -2015-01-17 15:00:00,10664.0 -2015-01-17 16:00:00,10627.0 -2015-01-17 17:00:00,10731.0 -2015-01-17 18:00:00,11150.0 -2015-01-17 19:00:00,11804.0 -2015-01-17 20:00:00,11789.0 -2015-01-17 21:00:00,11606.0 -2015-01-17 22:00:00,11455.0 -2015-01-17 23:00:00,11102.0 -2015-01-18 00:00:00,10646.0 -2015-01-16 01:00:00,11197.0 -2015-01-16 02:00:00,10753.0 -2015-01-16 03:00:00,10518.0 -2015-01-16 04:00:00,10394.0 -2015-01-16 05:00:00,10404.0 -2015-01-16 06:00:00,10645.0 -2015-01-16 07:00:00,11324.0 -2015-01-16 08:00:00,12351.0 -2015-01-16 09:00:00,12839.0 -2015-01-16 10:00:00,12916.0 -2015-01-16 11:00:00,12890.0 -2015-01-16 12:00:00,12732.0 -2015-01-16 13:00:00,12540.0 -2015-01-16 14:00:00,12389.0 -2015-01-16 15:00:00,12341.0 -2015-01-16 16:00:00,12214.0 -2015-01-16 17:00:00,12147.0 -2015-01-16 18:00:00,12506.0 -2015-01-16 19:00:00,13314.0 -2015-01-16 20:00:00,13266.0 -2015-01-16 21:00:00,13099.0 -2015-01-16 22:00:00,12773.0 -2015-01-16 23:00:00,12442.0 -2015-01-17 00:00:00,11746.0 -2015-01-15 01:00:00,11938.0 -2015-01-15 02:00:00,11470.0 -2015-01-15 03:00:00,11191.0 -2015-01-15 04:00:00,11018.0 -2015-01-15 05:00:00,11043.0 -2015-01-15 06:00:00,11248.0 -2015-01-15 07:00:00,11908.0 -2015-01-15 08:00:00,13003.0 -2015-01-15 09:00:00,13472.0 -2015-01-15 10:00:00,13551.0 -2015-01-15 11:00:00,13527.0 -2015-01-15 12:00:00,13294.0 -2015-01-15 13:00:00,13027.0 -2015-01-15 14:00:00,12822.0 -2015-01-15 15:00:00,12732.0 -2015-01-15 16:00:00,12552.0 -2015-01-15 17:00:00,12461.0 -2015-01-15 18:00:00,12870.0 -2015-01-15 19:00:00,13742.0 -2015-01-15 20:00:00,13690.0 -2015-01-15 21:00:00,13521.0 -2015-01-15 22:00:00,13220.0 -2015-01-15 23:00:00,12674.0 -2015-01-16 00:00:00,11912.0 -2015-01-14 01:00:00,12123.0 -2015-01-14 02:00:00,11726.0 -2015-01-14 03:00:00,11513.0 -2015-01-14 04:00:00,11433.0 -2015-01-14 05:00:00,11459.0 -2015-01-14 06:00:00,11719.0 -2015-01-14 07:00:00,12417.0 -2015-01-14 08:00:00,13533.0 -2015-01-14 09:00:00,13994.0 -2015-01-14 10:00:00,14026.0 -2015-01-14 11:00:00,14054.0 -2015-01-14 12:00:00,13964.0 -2015-01-14 13:00:00,13737.0 -2015-01-14 14:00:00,13629.0 -2015-01-14 15:00:00,13544.0 -2015-01-14 16:00:00,13453.0 -2015-01-14 17:00:00,13360.0 -2015-01-14 18:00:00,13757.0 -2015-01-14 19:00:00,14551.0 -2015-01-14 20:00:00,14490.0 -2015-01-14 21:00:00,14341.0 -2015-01-14 22:00:00,14034.0 -2015-01-14 23:00:00,13496.0 -2015-01-15 00:00:00,12702.0 -2015-01-13 01:00:00,11905.0 -2015-01-13 02:00:00,11401.0 -2015-01-13 03:00:00,11136.0 -2015-01-13 04:00:00,10955.0 -2015-01-13 05:00:00,10973.0 -2015-01-13 06:00:00,11223.0 -2015-01-13 07:00:00,11934.0 -2015-01-13 08:00:00,13040.0 -2015-01-13 09:00:00,13584.0 -2015-01-13 10:00:00,13598.0 -2015-01-13 11:00:00,13627.0 -2015-01-13 12:00:00,13581.0 -2015-01-13 13:00:00,13432.0 -2015-01-13 14:00:00,13278.0 -2015-01-13 15:00:00,13203.0 -2015-01-13 16:00:00,13071.0 -2015-01-13 17:00:00,13041.0 -2015-01-13 18:00:00,13464.0 -2015-01-13 19:00:00,14360.0 -2015-01-13 20:00:00,14363.0 -2015-01-13 21:00:00,14213.0 -2015-01-13 22:00:00,14002.0 -2015-01-13 23:00:00,13512.0 -2015-01-14 00:00:00,12799.0 -2015-01-12 01:00:00,10837.0 -2015-01-12 02:00:00,10446.0 -2015-01-12 03:00:00,10254.0 -2015-01-12 04:00:00,10128.0 -2015-01-12 05:00:00,10201.0 -2015-01-12 06:00:00,10521.0 -2015-01-12 07:00:00,11310.0 -2015-01-12 08:00:00,12574.0 -2015-01-12 09:00:00,13137.0 -2015-01-12 10:00:00,13255.0 -2015-01-12 11:00:00,13249.0 -2015-01-12 12:00:00,13274.0 -2015-01-12 13:00:00,13206.0 -2015-01-12 14:00:00,13184.0 -2015-01-12 15:00:00,13200.0 -2015-01-12 16:00:00,13150.0 -2015-01-12 17:00:00,13199.0 -2015-01-12 18:00:00,13685.0 -2015-01-12 19:00:00,14456.0 -2015-01-12 20:00:00,14465.0 -2015-01-12 21:00:00,14280.0 -2015-01-12 22:00:00,13975.0 -2015-01-12 23:00:00,13444.0 -2015-01-13 00:00:00,12676.0 -2015-01-11 01:00:00,11634.0 -2015-01-11 02:00:00,11169.0 -2015-01-11 03:00:00,10882.0 -2015-01-11 04:00:00,10719.0 -2015-01-11 05:00:00,10574.0 -2015-01-11 06:00:00,10589.0 -2015-01-11 07:00:00,10695.0 -2015-01-11 08:00:00,10946.0 -2015-01-11 09:00:00,10981.0 -2015-01-11 10:00:00,11142.0 -2015-01-11 11:00:00,11241.0 -2015-01-11 12:00:00,11293.0 -2015-01-11 13:00:00,11220.0 -2015-01-11 14:00:00,11239.0 -2015-01-11 15:00:00,11166.0 -2015-01-11 16:00:00,11259.0 -2015-01-11 17:00:00,11483.0 -2015-01-11 18:00:00,12140.0 -2015-01-11 19:00:00,12788.0 -2015-01-11 20:00:00,12767.0 -2015-01-11 21:00:00,12610.0 -2015-01-11 22:00:00,12306.0 -2015-01-11 23:00:00,11920.0 -2015-01-12 00:00:00,11336.0 -2015-01-10 01:00:00,13132.0 -2015-01-10 02:00:00,12665.0 -2015-01-10 03:00:00,12315.0 -2015-01-10 04:00:00,12145.0 -2015-01-10 05:00:00,12110.0 -2015-01-10 06:00:00,12184.0 -2015-01-10 07:00:00,12442.0 -2015-01-10 08:00:00,12865.0 -2015-01-10 09:00:00,13056.0 -2015-01-10 10:00:00,13175.0 -2015-01-10 11:00:00,13250.0 -2015-01-10 12:00:00,13223.0 -2015-01-10 13:00:00,13110.0 -2015-01-10 14:00:00,12846.0 -2015-01-10 15:00:00,12615.0 -2015-01-10 16:00:00,12815.0 -2015-01-10 17:00:00,12993.0 -2015-01-10 18:00:00,13505.0 -2015-01-10 19:00:00,13924.0 -2015-01-10 20:00:00,13800.0 -2015-01-10 21:00:00,13568.0 -2015-01-10 22:00:00,13266.0 -2015-01-10 23:00:00,12890.0 -2015-01-11 00:00:00,12270.0 -2015-01-09 01:00:00,12880.0 -2015-01-09 02:00:00,12505.0 -2015-01-09 03:00:00,12247.0 -2015-01-09 04:00:00,12059.0 -2015-01-09 05:00:00,12025.0 -2015-01-09 06:00:00,12229.0 -2015-01-09 07:00:00,12814.0 -2015-01-09 08:00:00,13813.0 -2015-01-09 09:00:00,14298.0 -2015-01-09 10:00:00,14312.0 -2015-01-09 11:00:00,14382.0 -2015-01-09 12:00:00,14452.0 -2015-01-09 13:00:00,14474.0 -2015-01-09 14:00:00,14451.0 -2015-01-09 15:00:00,14455.0 -2015-01-09 16:00:00,14364.0 -2015-01-09 17:00:00,14358.0 -2015-01-09 18:00:00,14798.0 -2015-01-09 19:00:00,15509.0 -2015-01-09 20:00:00,15502.0 -2015-01-09 21:00:00,15316.0 -2015-01-09 22:00:00,14998.0 -2015-01-09 23:00:00,14593.0 -2015-01-10 00:00:00,13817.0 -2015-01-08 01:00:00,13356.0 -2015-01-08 02:00:00,12965.0 -2015-01-08 03:00:00,12730.0 -2015-01-08 04:00:00,12596.0 -2015-01-08 05:00:00,12590.0 -2015-01-08 06:00:00,12792.0 -2015-01-08 07:00:00,13293.0 -2015-01-08 08:00:00,14159.0 -2015-01-08 09:00:00,14662.0 -2015-01-08 10:00:00,14893.0 -2015-01-08 11:00:00,15113.0 -2015-01-08 12:00:00,15373.0 -2015-01-08 13:00:00,15398.0 -2015-01-08 14:00:00,15381.0 -2015-01-08 15:00:00,15412.0 -2015-01-08 16:00:00,15281.0 -2015-01-08 17:00:00,15145.0 -2015-01-08 18:00:00,15473.0 -2015-01-08 19:00:00,15951.0 -2015-01-08 20:00:00,15775.0 -2015-01-08 21:00:00,15494.0 -2015-01-08 22:00:00,15100.0 -2015-01-08 23:00:00,14470.0 -2015-01-09 00:00:00,13667.0 -2015-01-07 01:00:00,12832.0 -2015-01-07 02:00:00,12396.0 -2015-01-07 03:00:00,12113.0 -2015-01-07 04:00:00,12027.0 -2015-01-07 05:00:00,12006.0 -2015-01-07 06:00:00,12249.0 -2015-01-07 07:00:00,12866.0 -2015-01-07 08:00:00,13792.0 -2015-01-07 09:00:00,14244.0 -2015-01-07 10:00:00,14353.0 -2015-01-07 11:00:00,14518.0 -2015-01-07 12:00:00,14620.0 -2015-01-07 13:00:00,14643.0 -2015-01-07 14:00:00,14545.0 -2015-01-07 15:00:00,14540.0 -2015-01-07 16:00:00,14445.0 -2015-01-07 17:00:00,14450.0 -2015-01-07 18:00:00,14969.0 -2015-01-07 19:00:00,15816.0 -2015-01-07 20:00:00,15778.0 -2015-01-07 21:00:00,15578.0 -2015-01-07 22:00:00,15314.0 -2015-01-07 23:00:00,14824.0 -2015-01-08 00:00:00,14072.0 -2015-01-06 01:00:00,12414.0 -2015-01-06 02:00:00,11904.0 -2015-01-06 03:00:00,11598.0 -2015-01-06 04:00:00,11472.0 -2015-01-06 05:00:00,11492.0 -2015-01-06 06:00:00,11712.0 -2015-01-06 07:00:00,12421.0 -2015-01-06 08:00:00,13479.0 -2015-01-06 09:00:00,13897.0 -2015-01-06 10:00:00,13859.0 -2015-01-06 11:00:00,13838.0 -2015-01-06 12:00:00,13875.0 -2015-01-06 13:00:00,13748.0 -2015-01-06 14:00:00,13713.0 -2015-01-06 15:00:00,13785.0 -2015-01-06 16:00:00,13783.0 -2015-01-06 17:00:00,13920.0 -2015-01-06 18:00:00,14644.0 -2015-01-06 19:00:00,15347.0 -2015-01-06 20:00:00,15297.0 -2015-01-06 21:00:00,15099.0 -2015-01-06 22:00:00,14804.0 -2015-01-06 23:00:00,14301.0 -2015-01-07 00:00:00,13549.0 -2015-01-05 01:00:00,11601.0 -2015-01-05 02:00:00,11325.0 -2015-01-05 03:00:00,11209.0 -2015-01-05 04:00:00,11181.0 -2015-01-05 05:00:00,11217.0 -2015-01-05 06:00:00,11570.0 -2015-01-05 07:00:00,12312.0 -2015-01-05 08:00:00,13465.0 -2015-01-05 09:00:00,14010.0 -2015-01-05 10:00:00,14073.0 -2015-01-05 11:00:00,14079.0 -2015-01-05 12:00:00,14133.0 -2015-01-05 13:00:00,14176.0 -2015-01-05 14:00:00,14122.0 -2015-01-05 15:00:00,14234.0 -2015-01-05 16:00:00,14187.0 -2015-01-05 17:00:00,14248.0 -2015-01-05 18:00:00,14865.0 -2015-01-05 19:00:00,15447.0 -2015-01-05 20:00:00,15322.0 -2015-01-05 21:00:00,15122.0 -2015-01-05 22:00:00,14827.0 -2015-01-05 23:00:00,14198.0 -2015-01-06 00:00:00,13262.0 -2015-01-04 01:00:00,10217.0 -2015-01-04 02:00:00,9752.0 -2015-01-04 03:00:00,9384.0 -2015-01-04 04:00:00,9251.0 -2015-01-04 05:00:00,9197.0 -2015-01-04 06:00:00,9251.0 -2015-01-04 07:00:00,9404.0 -2015-01-04 08:00:00,9677.0 -2015-01-04 09:00:00,9805.0 -2015-01-04 10:00:00,10078.0 -2015-01-04 11:00:00,10324.0 -2015-01-04 12:00:00,10657.0 -2015-01-04 13:00:00,10858.0 -2015-01-04 14:00:00,11023.0 -2015-01-04 15:00:00,11060.0 -2015-01-04 16:00:00,11119.0 -2015-01-04 17:00:00,11253.0 -2015-01-04 18:00:00,12008.0 -2015-01-04 19:00:00,12975.0 -2015-01-04 20:00:00,13078.0 -2015-01-04 21:00:00,13106.0 -2015-01-04 22:00:00,12990.0 -2015-01-04 23:00:00,12627.0 -2015-01-05 00:00:00,12084.0 -2015-01-03 01:00:00,10520.0 -2015-01-03 02:00:00,9963.0 -2015-01-03 03:00:00,9622.0 -2015-01-03 04:00:00,9475.0 -2015-01-03 05:00:00,9423.0 -2015-01-03 06:00:00,9494.0 -2015-01-03 07:00:00,9733.0 -2015-01-03 08:00:00,10135.0 -2015-01-03 09:00:00,10432.0 -2015-01-03 10:00:00,10696.0 -2015-01-03 11:00:00,11014.0 -2015-01-03 12:00:00,11244.0 -2015-01-03 13:00:00,11249.0 -2015-01-03 14:00:00,11328.0 -2015-01-03 15:00:00,11200.0 -2015-01-03 16:00:00,11073.0 -2015-01-03 17:00:00,11033.0 -2015-01-03 18:00:00,11517.0 -2015-01-03 19:00:00,12009.0 -2015-01-03 20:00:00,12010.0 -2015-01-03 21:00:00,11840.0 -2015-01-03 22:00:00,11648.0 -2015-01-03 23:00:00,11302.0 -2015-01-04 00:00:00,10823.0 -2015-01-02 01:00:00,10304.0 -2015-01-02 02:00:00,9884.0 -2015-01-02 03:00:00,9620.0 -2015-01-02 04:00:00,9513.0 -2015-01-02 05:00:00,9533.0 -2015-01-02 06:00:00,9791.0 -2015-01-02 07:00:00,10346.0 -2015-01-02 08:00:00,11115.0 -2015-01-02 09:00:00,11543.0 -2015-01-02 10:00:00,11598.0 -2015-01-02 11:00:00,11661.0 -2015-01-02 12:00:00,11701.0 -2015-01-02 13:00:00,11628.0 -2015-01-02 14:00:00,11469.0 -2015-01-02 15:00:00,11445.0 -2015-01-02 16:00:00,11394.0 -2015-01-02 17:00:00,11478.0 -2015-01-02 18:00:00,12211.0 -2015-01-02 19:00:00,12804.0 -2015-01-02 20:00:00,12666.0 -2015-01-02 21:00:00,12436.0 -2015-01-02 22:00:00,12185.0 -2015-01-02 23:00:00,11823.0 -2015-01-03 00:00:00,11194.0 -2015-01-01 01:00:00,11341.0 -2015-01-01 02:00:00,10969.0 -2015-01-01 03:00:00,10651.0 -2015-01-01 04:00:00,10425.0 -2015-01-01 05:00:00,10299.0 -2015-01-01 06:00:00,10294.0 -2015-01-01 07:00:00,10443.0 -2015-01-01 08:00:00,10634.0 -2015-01-01 09:00:00,10532.0 -2015-01-01 10:00:00,10433.0 -2015-01-01 11:00:00,10543.0 -2015-01-01 12:00:00,10635.0 -2015-01-01 13:00:00,10623.0 -2015-01-01 14:00:00,10592.0 -2015-01-01 15:00:00,10511.0 -2015-01-01 16:00:00,10503.0 -2015-01-01 17:00:00,10585.0 -2015-01-01 18:00:00,11404.0 -2015-01-01 19:00:00,12140.0 -2015-01-01 20:00:00,12076.0 -2015-01-01 21:00:00,11919.0 -2015-01-01 22:00:00,11712.0 -2015-01-01 23:00:00,11397.0 -2015-01-02 00:00:00,10826.0 -2016-12-31 01:00:00,10419.0 -2016-12-31 02:00:00,9940.0 -2016-12-31 03:00:00,9604.0 -2016-12-31 04:00:00,9318.0 -2016-12-31 05:00:00,9224.0 -2016-12-31 06:00:00,9209.0 -2016-12-31 07:00:00,9343.0 -2016-12-31 08:00:00,9715.0 -2016-12-31 09:00:00,9809.0 -2016-12-31 10:00:00,9974.0 -2016-12-31 11:00:00,10087.0 -2016-12-31 12:00:00,10187.0 -2016-12-31 13:00:00,10299.0 -2016-12-31 14:00:00,10385.0 -2016-12-31 15:00:00,10261.0 -2016-12-31 16:00:00,10313.0 -2016-12-31 17:00:00,10416.0 -2016-12-31 18:00:00,11037.0 -2016-12-31 19:00:00,11587.0 -2016-12-31 20:00:00,11549.0 -2016-12-31 21:00:00,11273.0 -2016-12-31 22:00:00,11085.0 -2016-12-31 23:00:00,10801.0 -2017-01-01 00:00:00,10500.0 -2016-12-30 01:00:00,10596.0 -2016-12-30 02:00:00,10137.0 -2016-12-30 03:00:00,9838.0 -2016-12-30 04:00:00,9715.0 -2016-12-30 05:00:00,9657.0 -2016-12-30 06:00:00,9885.0 -2016-12-30 07:00:00,10392.0 -2016-12-30 08:00:00,11013.0 -2016-12-30 09:00:00,11286.0 -2016-12-30 10:00:00,11457.0 -2016-12-30 11:00:00,11507.0 -2016-12-30 12:00:00,11576.0 -2016-12-30 13:00:00,11506.0 -2016-12-30 14:00:00,11367.0 -2016-12-30 15:00:00,11331.0 -2016-12-30 16:00:00,11369.0 -2016-12-30 17:00:00,11363.0 -2016-12-30 18:00:00,11930.0 -2016-12-30 19:00:00,12533.0 -2016-12-30 20:00:00,12375.0 -2016-12-30 21:00:00,12229.0 -2016-12-30 22:00:00,11982.0 -2016-12-30 23:00:00,11613.0 -2016-12-31 00:00:00,11056.0 -2016-12-29 01:00:00,10387.0 -2016-12-29 02:00:00,9969.0 -2016-12-29 03:00:00,9694.0 -2016-12-29 04:00:00,9529.0 -2016-12-29 05:00:00,9567.0 -2016-12-29 06:00:00,9770.0 -2016-12-29 07:00:00,10340.0 -2016-12-29 08:00:00,11180.0 -2016-12-29 09:00:00,11573.0 -2016-12-29 10:00:00,11632.0 -2016-12-29 11:00:00,11755.0 -2016-12-29 12:00:00,11844.0 -2016-12-29 13:00:00,12104.0 -2016-12-29 14:00:00,12226.0 -2016-12-29 15:00:00,12241.0 -2016-12-29 16:00:00,12251.0 -2016-12-29 17:00:00,12198.0 -2016-12-29 18:00:00,12693.0 -2016-12-29 19:00:00,13087.0 -2016-12-29 20:00:00,12797.0 -2016-12-29 21:00:00,12599.0 -2016-12-29 22:00:00,12293.0 -2016-12-29 23:00:00,11900.0 -2016-12-30 00:00:00,11254.0 -2016-12-28 01:00:00,10693.0 -2016-12-28 02:00:00,10244.0 -2016-12-28 03:00:00,9967.0 -2016-12-28 04:00:00,9820.0 -2016-12-28 05:00:00,9836.0 -2016-12-28 06:00:00,10109.0 -2016-12-28 07:00:00,10653.0 -2016-12-28 08:00:00,11432.0 -2016-12-28 09:00:00,11805.0 -2016-12-28 10:00:00,11856.0 -2016-12-28 11:00:00,11878.0 -2016-12-28 12:00:00,11845.0 -2016-12-28 13:00:00,11758.0 -2016-12-28 14:00:00,11727.0 -2016-12-28 15:00:00,11703.0 -2016-12-28 16:00:00,11568.0 -2016-12-28 17:00:00,11644.0 -2016-12-28 18:00:00,12216.0 -2016-12-28 19:00:00,12699.0 -2016-12-28 20:00:00,12476.0 -2016-12-28 21:00:00,12292.0 -2016-12-28 22:00:00,11993.0 -2016-12-28 23:00:00,11596.0 -2016-12-29 00:00:00,10987.0 -2016-12-27 01:00:00,10095.0 -2016-12-27 02:00:00,9700.0 -2016-12-27 03:00:00,9524.0 -2016-12-27 04:00:00,9399.0 -2016-12-27 05:00:00,9432.0 -2016-12-27 06:00:00,9659.0 -2016-12-27 07:00:00,10313.0 -2016-12-27 08:00:00,11115.0 -2016-12-27 09:00:00,11560.0 -2016-12-27 10:00:00,11743.0 -2016-12-27 11:00:00,11900.0 -2016-12-27 12:00:00,12075.0 -2016-12-27 13:00:00,12146.0 -2016-12-27 14:00:00,12060.0 -2016-12-27 15:00:00,11995.0 -2016-12-27 16:00:00,11915.0 -2016-12-27 17:00:00,11929.0 -2016-12-27 18:00:00,12508.0 -2016-12-27 19:00:00,13086.0 -2016-12-27 20:00:00,12919.0 -2016-12-27 21:00:00,12724.0 -2016-12-27 22:00:00,12459.0 -2016-12-27 23:00:00,12038.0 -2016-12-28 00:00:00,11360.0 -2016-12-26 01:00:00,9677.0 -2016-12-26 02:00:00,9222.0 -2016-12-26 03:00:00,8820.0 -2016-12-26 04:00:00,8636.0 -2016-12-26 05:00:00,8498.0 -2016-12-26 06:00:00,8613.0 -2016-12-26 07:00:00,8848.0 -2016-12-26 08:00:00,9187.0 -2016-12-26 09:00:00,9326.0 -2016-12-26 10:00:00,9417.0 -2016-12-26 11:00:00,9519.0 -2016-12-26 12:00:00,9732.0 -2016-12-26 13:00:00,9709.0 -2016-12-26 14:00:00,9635.0 -2016-12-26 15:00:00,9576.0 -2016-12-26 16:00:00,9522.0 -2016-12-26 17:00:00,9611.0 -2016-12-26 18:00:00,10417.0 -2016-12-26 19:00:00,11279.0 -2016-12-26 20:00:00,11431.0 -2016-12-26 21:00:00,11436.0 -2016-12-26 22:00:00,11335.0 -2016-12-26 23:00:00,11144.0 -2016-12-27 00:00:00,10617.0 -2016-12-25 01:00:00,9755.0 -2016-12-25 02:00:00,9348.0 -2016-12-25 03:00:00,9079.0 -2016-12-25 04:00:00,8880.0 -2016-12-25 05:00:00,8782.0 -2016-12-25 06:00:00,8800.0 -2016-12-25 07:00:00,8912.0 -2016-12-25 08:00:00,9155.0 -2016-12-25 09:00:00,9384.0 -2016-12-25 10:00:00,9581.0 -2016-12-25 11:00:00,9870.0 -2016-12-25 12:00:00,9974.0 -2016-12-25 13:00:00,10052.0 -2016-12-25 14:00:00,10049.0 -2016-12-25 15:00:00,9992.0 -2016-12-25 16:00:00,9949.0 -2016-12-25 17:00:00,10086.0 -2016-12-25 18:00:00,10599.0 -2016-12-25 19:00:00,10890.0 -2016-12-25 20:00:00,10837.0 -2016-12-25 21:00:00,10769.0 -2016-12-25 22:00:00,10709.0 -2016-12-25 23:00:00,10518.0 -2016-12-26 00:00:00,10135.0 -2016-12-24 01:00:00,10318.0 -2016-12-24 02:00:00,9743.0 -2016-12-24 03:00:00,9377.0 -2016-12-24 04:00:00,9122.0 -2016-12-24 05:00:00,9025.0 -2016-12-24 06:00:00,9025.0 -2016-12-24 07:00:00,9205.0 -2016-12-24 08:00:00,9515.0 -2016-12-24 09:00:00,9756.0 -2016-12-24 10:00:00,9999.0 -2016-12-24 11:00:00,10214.0 -2016-12-24 12:00:00,10328.0 -2016-12-24 13:00:00,10340.0 -2016-12-24 14:00:00,10284.0 -2016-12-24 15:00:00,10190.0 -2016-12-24 16:00:00,10179.0 -2016-12-24 17:00:00,10280.0 -2016-12-24 18:00:00,10823.0 -2016-12-24 19:00:00,11107.0 -2016-12-24 20:00:00,10944.0 -2016-12-24 21:00:00,10766.0 -2016-12-24 22:00:00,10637.0 -2016-12-24 23:00:00,10489.0 -2016-12-25 00:00:00,10194.0 -2016-12-23 01:00:00,10985.0 -2016-12-23 02:00:00,10417.0 -2016-12-23 03:00:00,10070.0 -2016-12-23 04:00:00,9859.0 -2016-12-23 05:00:00,9792.0 -2016-12-23 06:00:00,9987.0 -2016-12-23 07:00:00,10461.0 -2016-12-23 08:00:00,11104.0 -2016-12-23 09:00:00,11515.0 -2016-12-23 10:00:00,11716.0 -2016-12-23 11:00:00,11812.0 -2016-12-23 12:00:00,11892.0 -2016-12-23 13:00:00,11772.0 -2016-12-23 14:00:00,11682.0 -2016-12-23 15:00:00,11638.0 -2016-12-23 16:00:00,11488.0 -2016-12-23 17:00:00,11615.0 -2016-12-23 18:00:00,12348.0 -2016-12-23 19:00:00,12712.0 -2016-12-23 20:00:00,12436.0 -2016-12-23 21:00:00,12188.0 -2016-12-23 22:00:00,12020.0 -2016-12-23 23:00:00,11683.0 -2016-12-24 00:00:00,11038.0 -2016-12-22 01:00:00,11280.0 -2016-12-22 02:00:00,10666.0 -2016-12-22 03:00:00,10329.0 -2016-12-22 04:00:00,10106.0 -2016-12-22 05:00:00,10026.0 -2016-12-22 06:00:00,10248.0 -2016-12-22 07:00:00,10849.0 -2016-12-22 08:00:00,11768.0 -2016-12-22 09:00:00,12189.0 -2016-12-22 10:00:00,12233.0 -2016-12-22 11:00:00,12168.0 -2016-12-22 12:00:00,12149.0 -2016-12-22 13:00:00,12142.0 -2016-12-22 14:00:00,11859.0 -2016-12-22 15:00:00,11823.0 -2016-12-22 16:00:00,11704.0 -2016-12-22 17:00:00,11803.0 -2016-12-22 18:00:00,12596.0 -2016-12-22 19:00:00,13255.0 -2016-12-22 20:00:00,13150.0 -2016-12-22 21:00:00,13033.0 -2016-12-22 22:00:00,12801.0 -2016-12-22 23:00:00,12416.0 -2016-12-23 00:00:00,11746.0 -2016-12-21 01:00:00,11725.0 -2016-12-21 02:00:00,11166.0 -2016-12-21 03:00:00,10877.0 -2016-12-21 04:00:00,10707.0 -2016-12-21 05:00:00,10694.0 -2016-12-21 06:00:00,10929.0 -2016-12-21 07:00:00,11574.0 -2016-12-21 08:00:00,12408.0 -2016-12-21 09:00:00,13001.0 -2016-12-21 10:00:00,13134.0 -2016-12-21 11:00:00,13110.0 -2016-12-21 12:00:00,13101.0 -2016-12-21 13:00:00,12972.0 -2016-12-21 14:00:00,12912.0 -2016-12-21 15:00:00,12895.0 -2016-12-21 16:00:00,12852.0 -2016-12-21 17:00:00,12908.0 -2016-12-21 18:00:00,13509.0 -2016-12-21 19:00:00,13905.0 -2016-12-21 20:00:00,13707.0 -2016-12-21 21:00:00,13494.0 -2016-12-21 22:00:00,13224.0 -2016-12-21 23:00:00,12772.0 -2016-12-22 00:00:00,11998.0 -2016-12-20 01:00:00,12723.0 -2016-12-20 02:00:00,12127.0 -2016-12-20 03:00:00,11823.0 -2016-12-20 04:00:00,11658.0 -2016-12-20 05:00:00,11656.0 -2016-12-20 06:00:00,11846.0 -2016-12-20 07:00:00,12348.0 -2016-12-20 08:00:00,13345.0 -2016-12-20 09:00:00,13806.0 -2016-12-20 10:00:00,13789.0 -2016-12-20 11:00:00,13662.0 -2016-12-20 12:00:00,13511.0 -2016-12-20 13:00:00,13327.0 -2016-12-20 14:00:00,13179.0 -2016-12-20 15:00:00,13193.0 -2016-12-20 16:00:00,13034.0 -2016-12-20 17:00:00,13047.0 -2016-12-20 18:00:00,13811.0 -2016-12-20 19:00:00,14411.0 -2016-12-20 20:00:00,14294.0 -2016-12-20 21:00:00,14099.0 -2016-12-20 22:00:00,13854.0 -2016-12-20 23:00:00,13373.0 -2016-12-21 00:00:00,12543.0 -2016-12-19 01:00:00,12603.0 -2016-12-19 02:00:00,12179.0 -2016-12-19 03:00:00,11956.0 -2016-12-19 04:00:00,11902.0 -2016-12-19 05:00:00,11964.0 -2016-12-19 06:00:00,12191.0 -2016-12-19 07:00:00,12890.0 -2016-12-19 08:00:00,13815.0 -2016-12-19 09:00:00,14345.0 -2016-12-19 10:00:00,14432.0 -2016-12-19 11:00:00,14363.0 -2016-12-19 12:00:00,14355.0 -2016-12-19 13:00:00,14224.0 -2016-12-19 14:00:00,14062.0 -2016-12-19 15:00:00,13968.0 -2016-12-19 16:00:00,13860.0 -2016-12-19 17:00:00,13927.0 -2016-12-19 18:00:00,14639.0 -2016-12-19 19:00:00,15316.0 -2016-12-19 20:00:00,15329.0 -2016-12-19 21:00:00,15139.0 -2016-12-19 22:00:00,14775.0 -2016-12-19 23:00:00,14276.0 -2016-12-20 00:00:00,13528.0 -2016-12-18 01:00:00,11646.0 -2016-12-18 02:00:00,11208.0 -2016-12-18 03:00:00,10912.0 -2016-12-18 04:00:00,10763.0 -2016-12-18 05:00:00,10691.0 -2016-12-18 06:00:00,10787.0 -2016-12-18 07:00:00,10987.0 -2016-12-18 08:00:00,11342.0 -2016-12-18 09:00:00,11447.0 -2016-12-18 10:00:00,11635.0 -2016-12-18 11:00:00,11784.0 -2016-12-18 12:00:00,11849.0 -2016-12-18 13:00:00,11902.0 -2016-12-18 14:00:00,11879.0 -2016-12-18 15:00:00,11871.0 -2016-12-18 16:00:00,11873.0 -2016-12-18 17:00:00,12138.0 -2016-12-18 18:00:00,13116.0 -2016-12-18 19:00:00,14079.0 -2016-12-18 20:00:00,14199.0 -2016-12-18 21:00:00,14230.0 -2016-12-18 22:00:00,14073.0 -2016-12-18 23:00:00,13736.0 -2016-12-19 00:00:00,13153.0 -2016-12-17 01:00:00,11983.0 -2016-12-17 02:00:00,11311.0 -2016-12-17 03:00:00,10956.0 -2016-12-17 04:00:00,10688.0 -2016-12-17 05:00:00,10589.0 -2016-12-17 06:00:00,10608.0 -2016-12-17 07:00:00,10862.0 -2016-12-17 08:00:00,11286.0 -2016-12-17 09:00:00,11554.0 -2016-12-17 10:00:00,11855.0 -2016-12-17 11:00:00,12152.0 -2016-12-17 12:00:00,12240.0 -2016-12-17 13:00:00,12223.0 -2016-12-17 14:00:00,12190.0 -2016-12-17 15:00:00,12032.0 -2016-12-17 16:00:00,12036.0 -2016-12-17 17:00:00,12167.0 -2016-12-17 18:00:00,12947.0 -2016-12-17 19:00:00,13393.0 -2016-12-17 20:00:00,13352.0 -2016-12-17 21:00:00,13144.0 -2016-12-17 22:00:00,12992.0 -2016-12-17 23:00:00,12723.0 -2016-12-18 00:00:00,12226.0 -2016-12-16 01:00:00,12615.0 -2016-12-16 02:00:00,12046.0 -2016-12-16 03:00:00,11631.0 -2016-12-16 04:00:00,11540.0 -2016-12-16 05:00:00,11515.0 -2016-12-16 06:00:00,11734.0 -2016-12-16 07:00:00,12343.0 -2016-12-16 08:00:00,13298.0 -2016-12-16 09:00:00,13808.0 -2016-12-16 10:00:00,13902.0 -2016-12-16 11:00:00,13973.0 -2016-12-16 12:00:00,14025.0 -2016-12-16 13:00:00,13938.0 -2016-12-16 14:00:00,13782.0 -2016-12-16 15:00:00,13743.0 -2016-12-16 16:00:00,13673.0 -2016-12-16 17:00:00,13704.0 -2016-12-16 18:00:00,14398.0 -2016-12-16 19:00:00,14672.0 -2016-12-16 20:00:00,14523.0 -2016-12-16 21:00:00,14261.0 -2016-12-16 22:00:00,13925.0 -2016-12-16 23:00:00,13484.0 -2016-12-17 00:00:00,12720.0 -2016-12-15 01:00:00,12828.0 -2016-12-15 02:00:00,12306.0 -2016-12-15 03:00:00,12039.0 -2016-12-15 04:00:00,11925.0 -2016-12-15 05:00:00,11908.0 -2016-12-15 06:00:00,12147.0 -2016-12-15 07:00:00,12834.0 -2016-12-15 08:00:00,13842.0 -2016-12-15 09:00:00,14276.0 -2016-12-15 10:00:00,14351.0 -2016-12-15 11:00:00,14303.0 -2016-12-15 12:00:00,14279.0 -2016-12-15 13:00:00,14130.0 -2016-12-15 14:00:00,14008.0 -2016-12-15 15:00:00,13939.0 -2016-12-15 16:00:00,13792.0 -2016-12-15 17:00:00,13802.0 -2016-12-15 18:00:00,14734.0 -2016-12-15 19:00:00,15385.0 -2016-12-15 20:00:00,15301.0 -2016-12-15 21:00:00,15119.0 -2016-12-15 22:00:00,14851.0 -2016-12-15 23:00:00,14299.0 -2016-12-16 00:00:00,13438.0 -2016-12-14 01:00:00,12287.0 -2016-12-14 02:00:00,11839.0 -2016-12-14 03:00:00,11590.0 -2016-12-14 04:00:00,11448.0 -2016-12-14 05:00:00,11446.0 -2016-12-14 06:00:00,11678.0 -2016-12-14 07:00:00,12286.0 -2016-12-14 08:00:00,13313.0 -2016-12-14 09:00:00,13693.0 -2016-12-14 10:00:00,13722.0 -2016-12-14 11:00:00,13809.0 -2016-12-14 12:00:00,13748.0 -2016-12-14 13:00:00,13581.0 -2016-12-14 14:00:00,13464.0 -2016-12-14 15:00:00,13409.0 -2016-12-14 16:00:00,13420.0 -2016-12-14 17:00:00,13560.0 -2016-12-14 18:00:00,14440.0 -2016-12-14 19:00:00,15122.0 -2016-12-14 20:00:00,15148.0 -2016-12-14 21:00:00,15069.0 -2016-12-14 22:00:00,14861.0 -2016-12-14 23:00:00,14391.0 -2016-12-15 00:00:00,13571.0 -2016-12-13 01:00:00,11355.0 -2016-12-13 02:00:00,11079.0 -2016-12-13 03:00:00,10737.0 -2016-12-13 04:00:00,10616.0 -2016-12-13 05:00:00,10559.0 -2016-12-13 06:00:00,10816.0 -2016-12-13 07:00:00,11470.0 -2016-12-13 08:00:00,12539.0 -2016-12-13 09:00:00,13094.0 -2016-12-13 10:00:00,13243.0 -2016-12-13 11:00:00,13282.0 -2016-12-13 12:00:00,13235.0 -2016-12-13 13:00:00,13042.0 -2016-12-13 14:00:00,12965.0 -2016-12-13 15:00:00,12960.0 -2016-12-13 16:00:00,12937.0 -2016-12-13 17:00:00,13037.0 -2016-12-13 18:00:00,13887.0 -2016-12-13 19:00:00,14650.0 -2016-12-13 20:00:00,14617.0 -2016-12-13 21:00:00,14525.0 -2016-12-13 22:00:00,14331.0 -2016-12-13 23:00:00,13831.0 -2016-12-14 00:00:00,13085.0 -2016-12-12 01:00:00,10354.0 -2016-12-12 02:00:00,10010.0 -2016-12-12 03:00:00,9800.0 -2016-12-12 04:00:00,9769.0 -2016-12-12 05:00:00,9843.0 -2016-12-12 06:00:00,10173.0 -2016-12-12 07:00:00,10995.0 -2016-12-12 08:00:00,12067.0 -2016-12-12 09:00:00,12660.0 -2016-12-12 10:00:00,12801.0 -2016-12-12 11:00:00,12931.0 -2016-12-12 12:00:00,12961.0 -2016-12-12 13:00:00,12863.0 -2016-12-12 14:00:00,12935.0 -2016-12-12 15:00:00,12733.0 -2016-12-12 16:00:00,12614.0 -2016-12-12 17:00:00,12719.0 -2016-12-12 18:00:00,13602.0 -2016-12-12 19:00:00,14312.0 -2016-12-12 20:00:00,14241.0 -2016-12-12 21:00:00,14087.0 -2016-12-12 22:00:00,13806.0 -2016-12-12 23:00:00,13290.0 -2016-12-13 00:00:00,12466.0 -2016-12-11 01:00:00,10998.0 -2016-12-11 02:00:00,10578.0 -2016-12-11 03:00:00,10209.0 -2016-12-11 04:00:00,10058.0 -2016-12-11 05:00:00,9894.0 -2016-12-11 06:00:00,9890.0 -2016-12-11 07:00:00,9999.0 -2016-12-11 08:00:00,10248.0 -2016-12-11 09:00:00,10396.0 -2016-12-11 10:00:00,10609.0 -2016-12-11 11:00:00,10899.0 -2016-12-11 12:00:00,11050.0 -2016-12-11 13:00:00,11081.0 -2016-12-11 14:00:00,11178.0 -2016-12-11 15:00:00,11132.0 -2016-12-11 16:00:00,11211.0 -2016-12-11 17:00:00,11347.0 -2016-12-11 18:00:00,12226.0 -2016-12-11 19:00:00,12634.0 -2016-12-11 20:00:00,12619.0 -2016-12-11 21:00:00,12494.0 -2016-12-11 22:00:00,12024.0 -2016-12-11 23:00:00,11657.0 -2016-12-12 00:00:00,11012.0 -2016-12-10 01:00:00,11424.0 -2016-12-10 02:00:00,10847.0 -2016-12-10 03:00:00,10537.0 -2016-12-10 04:00:00,10316.0 -2016-12-10 05:00:00,10265.0 -2016-12-10 06:00:00,10331.0 -2016-12-10 07:00:00,10657.0 -2016-12-10 08:00:00,11141.0 -2016-12-10 09:00:00,11427.0 -2016-12-10 10:00:00,11668.0 -2016-12-10 11:00:00,11734.0 -2016-12-10 12:00:00,11687.0 -2016-12-10 13:00:00,11673.0 -2016-12-10 14:00:00,11596.0 -2016-12-10 15:00:00,11508.0 -2016-12-10 16:00:00,11423.0 -2016-12-10 17:00:00,11663.0 -2016-12-10 18:00:00,12513.0 -2016-12-10 19:00:00,12966.0 -2016-12-10 20:00:00,12870.0 -2016-12-10 21:00:00,12787.0 -2016-12-10 22:00:00,12566.0 -2016-12-10 23:00:00,12217.0 -2016-12-11 00:00:00,11674.0 -2016-12-09 01:00:00,11457.0 -2016-12-09 02:00:00,10905.0 -2016-12-09 03:00:00,10593.0 -2016-12-09 04:00:00,10423.0 -2016-12-09 05:00:00,10417.0 -2016-12-09 06:00:00,10663.0 -2016-12-09 07:00:00,11311.0 -2016-12-09 08:00:00,12336.0 -2016-12-09 09:00:00,12807.0 -2016-12-09 10:00:00,12937.0 -2016-12-09 11:00:00,12976.0 -2016-12-09 12:00:00,12984.0 -2016-12-09 13:00:00,12909.0 -2016-12-09 14:00:00,12811.0 -2016-12-09 15:00:00,12735.0 -2016-12-09 16:00:00,12633.0 -2016-12-09 17:00:00,12670.0 -2016-12-09 18:00:00,13341.0 -2016-12-09 19:00:00,13854.0 -2016-12-09 20:00:00,13721.0 -2016-12-09 21:00:00,13502.0 -2016-12-09 22:00:00,13242.0 -2016-12-09 23:00:00,12869.0 -2016-12-10 00:00:00,12133.0 -2016-12-08 01:00:00,11286.0 -2016-12-08 02:00:00,10762.0 -2016-12-08 03:00:00,10468.0 -2016-12-08 04:00:00,10272.0 -2016-12-08 05:00:00,10326.0 -2016-12-08 06:00:00,10607.0 -2016-12-08 07:00:00,11332.0 -2016-12-08 08:00:00,12489.0 -2016-12-08 09:00:00,13097.0 -2016-12-08 10:00:00,13274.0 -2016-12-08 11:00:00,13362.0 -2016-12-08 12:00:00,13387.0 -2016-12-08 13:00:00,13321.0 -2016-12-08 14:00:00,13314.0 -2016-12-08 15:00:00,13328.0 -2016-12-08 16:00:00,13316.0 -2016-12-08 17:00:00,13362.0 -2016-12-08 18:00:00,14036.0 -2016-12-08 19:00:00,14310.0 -2016-12-08 20:00:00,14148.0 -2016-12-08 21:00:00,13890.0 -2016-12-08 22:00:00,13649.0 -2016-12-08 23:00:00,13104.0 -2016-12-09 00:00:00,12247.0 -2016-12-07 01:00:00,10868.0 -2016-12-07 02:00:00,10372.0 -2016-12-07 03:00:00,10085.0 -2016-12-07 04:00:00,9949.0 -2016-12-07 05:00:00,9945.0 -2016-12-07 06:00:00,10238.0 -2016-12-07 07:00:00,10953.0 -2016-12-07 08:00:00,12054.0 -2016-12-07 09:00:00,12559.0 -2016-12-07 10:00:00,12637.0 -2016-12-07 11:00:00,12511.0 -2016-12-07 12:00:00,12545.0 -2016-12-07 13:00:00,12383.0 -2016-12-07 14:00:00,12325.0 -2016-12-07 15:00:00,12426.0 -2016-12-07 16:00:00,12421.0 -2016-12-07 17:00:00,12609.0 -2016-12-07 18:00:00,13401.0 -2016-12-07 19:00:00,13927.0 -2016-12-07 20:00:00,13812.0 -2016-12-07 21:00:00,13676.0 -2016-12-07 22:00:00,13433.0 -2016-12-07 23:00:00,12891.0 -2016-12-08 00:00:00,12080.0 -2016-12-06 01:00:00,10453.0 -2016-12-06 02:00:00,9934.0 -2016-12-06 03:00:00,9604.0 -2016-12-06 04:00:00,9473.0 -2016-12-06 05:00:00,9470.0 -2016-12-06 06:00:00,9726.0 -2016-12-06 07:00:00,10461.0 -2016-12-06 08:00:00,11582.0 -2016-12-06 09:00:00,12026.0 -2016-12-06 10:00:00,12098.0 -2016-12-06 11:00:00,12116.0 -2016-12-06 12:00:00,12020.0 -2016-12-06 13:00:00,11891.0 -2016-12-06 14:00:00,11994.0 -2016-12-06 15:00:00,12174.0 -2016-12-06 16:00:00,12205.0 -2016-12-06 17:00:00,12179.0 -2016-12-06 18:00:00,12800.0 -2016-12-06 19:00:00,13444.0 -2016-12-06 20:00:00,13288.0 -2016-12-06 21:00:00,13145.0 -2016-12-06 22:00:00,12912.0 -2016-12-06 23:00:00,12402.0 -2016-12-07 00:00:00,11561.0 -2016-12-05 01:00:00,9928.0 -2016-12-05 02:00:00,9518.0 -2016-12-05 03:00:00,9301.0 -2016-12-05 04:00:00,9193.0 -2016-12-05 05:00:00,9200.0 -2016-12-05 06:00:00,9552.0 -2016-12-05 07:00:00,10307.0 -2016-12-05 08:00:00,11440.0 -2016-12-05 09:00:00,11989.0 -2016-12-05 10:00:00,12093.0 -2016-12-05 11:00:00,12225.0 -2016-12-05 12:00:00,12248.0 -2016-12-05 13:00:00,12327.0 -2016-12-05 14:00:00,12318.0 -2016-12-05 15:00:00,12300.0 -2016-12-05 16:00:00,12265.0 -2016-12-05 17:00:00,12256.0 -2016-12-05 18:00:00,12867.0 -2016-12-05 19:00:00,13311.0 -2016-12-05 20:00:00,13153.0 -2016-12-05 21:00:00,12960.0 -2016-12-05 22:00:00,12674.0 -2016-12-05 23:00:00,12123.0 -2016-12-06 00:00:00,11290.0 -2016-12-04 01:00:00,9894.0 -2016-12-04 02:00:00,9428.0 -2016-12-04 03:00:00,9098.0 -2016-12-04 04:00:00,8940.0 -2016-12-04 05:00:00,8841.0 -2016-12-04 06:00:00,8859.0 -2016-12-04 07:00:00,8991.0 -2016-12-04 08:00:00,9280.0 -2016-12-04 09:00:00,9440.0 -2016-12-04 10:00:00,9743.0 -2016-12-04 11:00:00,10029.0 -2016-12-04 12:00:00,10340.0 -2016-12-04 13:00:00,10555.0 -2016-12-04 14:00:00,10671.0 -2016-12-04 15:00:00,10676.0 -2016-12-04 16:00:00,10725.0 -2016-12-04 17:00:00,10863.0 -2016-12-04 18:00:00,11562.0 -2016-12-04 19:00:00,11987.0 -2016-12-04 20:00:00,11923.0 -2016-12-04 21:00:00,11723.0 -2016-12-04 22:00:00,11526.0 -2016-12-04 23:00:00,11039.0 -2016-12-05 00:00:00,10528.0 -2016-12-03 01:00:00,10121.0 -2016-12-03 02:00:00,9628.0 -2016-12-03 03:00:00,9311.0 -2016-12-03 04:00:00,9083.0 -2016-12-03 05:00:00,9044.0 -2016-12-03 06:00:00,9097.0 -2016-12-03 07:00:00,9418.0 -2016-12-03 08:00:00,9881.0 -2016-12-03 09:00:00,10194.0 -2016-12-03 10:00:00,10563.0 -2016-12-03 11:00:00,10707.0 -2016-12-03 12:00:00,10877.0 -2016-12-03 13:00:00,10777.0 -2016-12-03 14:00:00,10747.0 -2016-12-03 15:00:00,10594.0 -2016-12-03 16:00:00,10500.0 -2016-12-03 17:00:00,10526.0 -2016-12-03 18:00:00,11203.0 -2016-12-03 19:00:00,11643.0 -2016-12-03 20:00:00,11607.0 -2016-12-03 21:00:00,11438.0 -2016-12-03 22:00:00,11279.0 -2016-12-03 23:00:00,10970.0 -2016-12-04 00:00:00,10511.0 -2016-12-02 01:00:00,10060.0 -2016-12-02 02:00:00,9539.0 -2016-12-02 03:00:00,9276.0 -2016-12-02 04:00:00,9103.0 -2016-12-02 05:00:00,9071.0 -2016-12-02 06:00:00,9340.0 -2016-12-02 07:00:00,10015.0 -2016-12-02 08:00:00,11049.0 -2016-12-02 09:00:00,11560.0 -2016-12-02 10:00:00,11716.0 -2016-12-02 11:00:00,11816.0 -2016-12-02 12:00:00,11916.0 -2016-12-02 13:00:00,11877.0 -2016-12-02 14:00:00,11808.0 -2016-12-02 15:00:00,11827.0 -2016-12-02 16:00:00,11745.0 -2016-12-02 17:00:00,11750.0 -2016-12-02 18:00:00,12329.0 -2016-12-02 19:00:00,12642.0 -2016-12-02 20:00:00,12456.0 -2016-12-02 21:00:00,12207.0 -2016-12-02 22:00:00,11936.0 -2016-12-02 23:00:00,11566.0 -2016-12-03 00:00:00,10841.0 -2016-12-01 01:00:00,10147.0 -2016-12-01 02:00:00,9657.0 -2016-12-01 03:00:00,9365.0 -2016-12-01 04:00:00,9200.0 -2016-12-01 05:00:00,9198.0 -2016-12-01 06:00:00,9455.0 -2016-12-01 07:00:00,10128.0 -2016-12-01 08:00:00,11205.0 -2016-12-01 09:00:00,11740.0 -2016-12-01 10:00:00,11849.0 -2016-12-01 11:00:00,11915.0 -2016-12-01 12:00:00,11966.0 -2016-12-01 13:00:00,11937.0 -2016-12-01 14:00:00,11889.0 -2016-12-01 15:00:00,11906.0 -2016-12-01 16:00:00,11828.0 -2016-12-01 17:00:00,11910.0 -2016-12-01 18:00:00,12482.0 -2016-12-01 19:00:00,12748.0 -2016-12-01 20:00:00,12556.0 -2016-12-01 21:00:00,12354.0 -2016-12-01 22:00:00,12075.0 -2016-12-01 23:00:00,11560.0 -2016-12-02 00:00:00,10798.0 -2016-11-30 01:00:00,9598.0 -2016-11-30 02:00:00,9152.0 -2016-11-30 03:00:00,8870.0 -2016-11-30 04:00:00,8739.0 -2016-11-30 05:00:00,8744.0 -2016-11-30 06:00:00,9066.0 -2016-11-30 07:00:00,9869.0 -2016-11-30 08:00:00,10994.0 -2016-11-30 09:00:00,11413.0 -2016-11-30 10:00:00,11452.0 -2016-11-30 11:00:00,11412.0 -2016-11-30 12:00:00,11504.0 -2016-11-30 13:00:00,11663.0 -2016-11-30 14:00:00,11765.0 -2016-11-30 15:00:00,11908.0 -2016-11-30 16:00:00,11910.0 -2016-11-30 17:00:00,12011.0 -2016-11-30 18:00:00,12594.0 -2016-11-30 19:00:00,12882.0 -2016-11-30 20:00:00,12699.0 -2016-11-30 21:00:00,12499.0 -2016-11-30 22:00:00,12176.0 -2016-11-30 23:00:00,11662.0 -2016-12-01 00:00:00,10908.0 -2016-11-29 01:00:00,9705.0 -2016-11-29 02:00:00,9244.0 -2016-11-29 03:00:00,8947.0 -2016-11-29 04:00:00,8767.0 -2016-11-29 05:00:00,8747.0 -2016-11-29 06:00:00,8972.0 -2016-11-29 07:00:00,9743.0 -2016-11-29 08:00:00,10848.0 -2016-11-29 09:00:00,11298.0 -2016-11-29 10:00:00,11400.0 -2016-11-29 11:00:00,11344.0 -2016-11-29 12:00:00,11350.0 -2016-11-29 13:00:00,11267.0 -2016-11-29 14:00:00,11170.0 -2016-11-29 15:00:00,11169.0 -2016-11-29 16:00:00,11052.0 -2016-11-29 17:00:00,11045.0 -2016-11-29 18:00:00,11664.0 -2016-11-29 19:00:00,12165.0 -2016-11-29 20:00:00,12031.0 -2016-11-29 21:00:00,11856.0 -2016-11-29 22:00:00,11560.0 -2016-11-29 23:00:00,11067.0 -2016-11-30 00:00:00,10332.0 -2016-11-28 01:00:00,9336.0 -2016-11-28 02:00:00,8994.0 -2016-11-28 03:00:00,8794.0 -2016-11-28 04:00:00,8753.0 -2016-11-28 05:00:00,8725.0 -2016-11-28 06:00:00,9018.0 -2016-11-28 07:00:00,9815.0 -2016-11-28 08:00:00,10866.0 -2016-11-28 09:00:00,11461.0 -2016-11-28 10:00:00,11626.0 -2016-11-28 11:00:00,11769.0 -2016-11-28 12:00:00,11894.0 -2016-11-28 13:00:00,11948.0 -2016-11-28 14:00:00,12068.0 -2016-11-28 15:00:00,12035.0 -2016-11-28 16:00:00,11963.0 -2016-11-28 17:00:00,11989.0 -2016-11-28 18:00:00,12395.0 -2016-11-28 19:00:00,12527.0 -2016-11-28 20:00:00,12280.0 -2016-11-28 21:00:00,12049.0 -2016-11-28 22:00:00,11728.0 -2016-11-28 23:00:00,11227.0 -2016-11-29 00:00:00,10455.0 -2016-11-27 01:00:00,9202.0 -2016-11-27 02:00:00,8782.0 -2016-11-27 03:00:00,8553.0 -2016-11-27 04:00:00,8377.0 -2016-11-27 05:00:00,8350.0 -2016-11-27 06:00:00,8380.0 -2016-11-27 07:00:00,8596.0 -2016-11-27 08:00:00,8797.0 -2016-11-27 09:00:00,8845.0 -2016-11-27 10:00:00,9122.0 -2016-11-27 11:00:00,9363.0 -2016-11-27 12:00:00,9555.0 -2016-11-27 13:00:00,9646.0 -2016-11-27 14:00:00,9717.0 -2016-11-27 15:00:00,9777.0 -2016-11-27 16:00:00,9827.0 -2016-11-27 17:00:00,9944.0 -2016-11-27 18:00:00,10665.0 -2016-11-27 19:00:00,11084.0 -2016-11-27 20:00:00,11094.0 -2016-11-27 21:00:00,10994.0 -2016-11-27 22:00:00,10758.0 -2016-11-27 23:00:00,10334.0 -2016-11-28 00:00:00,9878.0 -2016-11-26 01:00:00,9283.0 -2016-11-26 02:00:00,8908.0 -2016-11-26 03:00:00,8646.0 -2016-11-26 04:00:00,8463.0 -2016-11-26 05:00:00,8417.0 -2016-11-26 06:00:00,8465.0 -2016-11-26 07:00:00,8731.0 -2016-11-26 08:00:00,9079.0 -2016-11-26 09:00:00,9268.0 -2016-11-26 10:00:00,9537.0 -2016-11-26 11:00:00,9644.0 -2016-11-26 12:00:00,9676.0 -2016-11-26 13:00:00,9646.0 -2016-11-26 14:00:00,9506.0 -2016-11-26 15:00:00,9407.0 -2016-11-26 16:00:00,9286.0 -2016-11-26 17:00:00,9430.0 -2016-11-26 18:00:00,10070.0 -2016-11-26 19:00:00,10602.0 -2016-11-26 20:00:00,10540.0 -2016-11-26 21:00:00,10438.0 -2016-11-26 22:00:00,10276.0 -2016-11-26 23:00:00,10090.0 -2016-11-27 00:00:00,9648.0 -2016-11-25 01:00:00,8720.0 -2016-11-25 02:00:00,8432.0 -2016-11-25 03:00:00,8234.0 -2016-11-25 04:00:00,8111.0 -2016-11-25 05:00:00,8084.0 -2016-11-25 06:00:00,8238.0 -2016-11-25 07:00:00,8636.0 -2016-11-25 08:00:00,9118.0 -2016-11-25 09:00:00,9307.0 -2016-11-25 10:00:00,9502.0 -2016-11-25 11:00:00,9685.0 -2016-11-25 12:00:00,9798.0 -2016-11-25 13:00:00,9872.0 -2016-11-25 14:00:00,9938.0 -2016-11-25 15:00:00,10012.0 -2016-11-25 16:00:00,10016.0 -2016-11-25 17:00:00,10208.0 -2016-11-25 18:00:00,10725.0 -2016-11-25 19:00:00,10930.0 -2016-11-25 20:00:00,10765.0 -2016-11-25 21:00:00,10600.0 -2016-11-25 22:00:00,10402.0 -2016-11-25 23:00:00,10176.0 -2016-11-26 00:00:00,9713.0 -2016-11-24 01:00:00,9277.0 -2016-11-24 02:00:00,8770.0 -2016-11-24 03:00:00,8468.0 -2016-11-24 04:00:00,8275.0 -2016-11-24 05:00:00,8192.0 -2016-11-24 06:00:00,8246.0 -2016-11-24 07:00:00,8472.0 -2016-11-24 08:00:00,8728.0 -2016-11-24 09:00:00,8765.0 -2016-11-24 10:00:00,9003.0 -2016-11-24 11:00:00,9283.0 -2016-11-24 12:00:00,9501.0 -2016-11-24 13:00:00,9623.0 -2016-11-24 14:00:00,9604.0 -2016-11-24 15:00:00,9563.0 -2016-11-24 16:00:00,9442.0 -2016-11-24 17:00:00,9461.0 -2016-11-24 18:00:00,9757.0 -2016-11-24 19:00:00,9757.0 -2016-11-24 20:00:00,9630.0 -2016-11-24 21:00:00,9574.0 -2016-11-24 22:00:00,9506.0 -2016-11-24 23:00:00,9369.0 -2016-11-25 00:00:00,9077.0 -2016-11-23 01:00:00,9921.0 -2016-11-23 02:00:00,9477.0 -2016-11-23 03:00:00,9210.0 -2016-11-23 04:00:00,9061.0 -2016-11-23 05:00:00,9007.0 -2016-11-23 06:00:00,9235.0 -2016-11-23 07:00:00,9871.0 -2016-11-23 08:00:00,10744.0 -2016-11-23 09:00:00,11309.0 -2016-11-23 10:00:00,11647.0 -2016-11-23 11:00:00,11841.0 -2016-11-23 12:00:00,11956.0 -2016-11-23 13:00:00,11962.0 -2016-11-23 14:00:00,11875.0 -2016-11-23 15:00:00,11802.0 -2016-11-23 16:00:00,11657.0 -2016-11-23 17:00:00,11549.0 -2016-11-23 18:00:00,11830.0 -2016-11-23 19:00:00,11950.0 -2016-11-23 20:00:00,11610.0 -2016-11-23 21:00:00,11340.0 -2016-11-23 22:00:00,11049.0 -2016-11-23 23:00:00,10600.0 -2016-11-24 00:00:00,9941.0 -2016-11-22 01:00:00,10076.0 -2016-11-22 02:00:00,9677.0 -2016-11-22 03:00:00,9439.0 -2016-11-22 04:00:00,9306.0 -2016-11-22 05:00:00,9296.0 -2016-11-22 06:00:00,9532.0 -2016-11-22 07:00:00,10234.0 -2016-11-22 08:00:00,11292.0 -2016-11-22 09:00:00,11742.0 -2016-11-22 10:00:00,11923.0 -2016-11-22 11:00:00,12062.0 -2016-11-22 12:00:00,12067.0 -2016-11-22 13:00:00,11994.0 -2016-11-22 14:00:00,11872.0 -2016-11-22 15:00:00,11837.0 -2016-11-22 16:00:00,11729.0 -2016-11-22 17:00:00,11775.0 -2016-11-22 18:00:00,12282.0 -2016-11-22 19:00:00,12509.0 -2016-11-22 20:00:00,12279.0 -2016-11-22 21:00:00,12070.0 -2016-11-22 22:00:00,11767.0 -2016-11-22 23:00:00,11289.0 -2016-11-23 00:00:00,10593.0 -2016-11-21 01:00:00,9600.0 -2016-11-21 02:00:00,9313.0 -2016-11-21 03:00:00,9184.0 -2016-11-21 04:00:00,9138.0 -2016-11-21 05:00:00,9216.0 -2016-11-21 06:00:00,9531.0 -2016-11-21 07:00:00,10315.0 -2016-11-21 08:00:00,11327.0 -2016-11-21 09:00:00,11749.0 -2016-11-21 10:00:00,11834.0 -2016-11-21 11:00:00,11694.0 -2016-11-21 12:00:00,11790.0 -2016-11-21 13:00:00,11690.0 -2016-11-21 14:00:00,11564.0 -2016-11-21 15:00:00,11511.0 -2016-11-21 16:00:00,11384.0 -2016-11-21 17:00:00,11391.0 -2016-11-21 18:00:00,12073.0 -2016-11-21 19:00:00,12580.0 -2016-11-21 20:00:00,12452.0 -2016-11-21 21:00:00,12240.0 -2016-11-21 22:00:00,11915.0 -2016-11-21 23:00:00,11449.0 -2016-11-22 00:00:00,10713.0 -2016-11-20 01:00:00,9571.0 -2016-11-20 02:00:00,9219.0 -2016-11-20 03:00:00,8973.0 -2016-11-20 04:00:00,8863.0 -2016-11-20 05:00:00,8734.0 -2016-11-20 06:00:00,8875.0 -2016-11-20 07:00:00,9041.0 -2016-11-20 08:00:00,9154.0 -2016-11-20 09:00:00,9255.0 -2016-11-20 10:00:00,9461.0 -2016-11-20 11:00:00,9770.0 -2016-11-20 12:00:00,9804.0 -2016-11-20 13:00:00,9710.0 -2016-11-20 14:00:00,9699.0 -2016-11-20 15:00:00,9630.0 -2016-11-20 16:00:00,9531.0 -2016-11-20 17:00:00,9654.0 -2016-11-20 18:00:00,10341.0 -2016-11-20 19:00:00,10886.0 -2016-11-20 20:00:00,11213.0 -2016-11-20 21:00:00,10992.0 -2016-11-20 22:00:00,10759.0 -2016-11-20 23:00:00,10600.0 -2016-11-21 00:00:00,10130.0 -2016-11-19 01:00:00,9249.0 -2016-11-19 02:00:00,8894.0 -2016-11-19 03:00:00,8664.0 -2016-11-19 04:00:00,8531.0 -2016-11-19 05:00:00,8519.0 -2016-11-19 06:00:00,8624.0 -2016-11-19 07:00:00,9009.0 -2016-11-19 08:00:00,9407.0 -2016-11-19 09:00:00,9644.0 -2016-11-19 10:00:00,10103.0 -2016-11-19 11:00:00,10434.0 -2016-11-19 12:00:00,10499.0 -2016-11-19 13:00:00,10555.0 -2016-11-19 14:00:00,10341.0 -2016-11-19 15:00:00,10247.0 -2016-11-19 16:00:00,10113.0 -2016-11-19 17:00:00,10171.0 -2016-11-19 18:00:00,10426.0 -2016-11-19 19:00:00,10816.0 -2016-11-19 20:00:00,11052.0 -2016-11-19 21:00:00,10872.0 -2016-11-19 22:00:00,10690.0 -2016-11-19 23:00:00,10479.0 -2016-11-20 00:00:00,10114.0 -2016-11-18 01:00:00,9036.0 -2016-11-18 02:00:00,8550.0 -2016-11-18 03:00:00,8242.0 -2016-11-18 04:00:00,8059.0 -2016-11-18 05:00:00,7980.0 -2016-11-18 06:00:00,8214.0 -2016-11-18 07:00:00,8835.0 -2016-11-18 08:00:00,9849.0 -2016-11-18 09:00:00,10379.0 -2016-11-18 10:00:00,10728.0 -2016-11-18 11:00:00,11407.0 -2016-11-18 12:00:00,11088.0 -2016-11-18 13:00:00,11190.0 -2016-11-18 14:00:00,11159.0 -2016-11-18 15:00:00,11517.0 -2016-11-18 16:00:00,11344.0 -2016-11-18 17:00:00,11347.0 -2016-11-18 18:00:00,11388.0 -2016-11-18 19:00:00,11586.0 -2016-11-18 20:00:00,11686.0 -2016-11-18 21:00:00,11281.0 -2016-11-18 22:00:00,10816.0 -2016-11-18 23:00:00,10631.0 -2016-11-19 00:00:00,10042.0 -2016-11-17 01:00:00,9126.0 -2016-11-17 02:00:00,8731.0 -2016-11-17 03:00:00,8467.0 -2016-11-17 04:00:00,8328.0 -2016-11-17 05:00:00,8277.0 -2016-11-17 06:00:00,8512.0 -2016-11-17 07:00:00,9163.0 -2016-11-17 08:00:00,10158.0 -2016-11-17 09:00:00,10591.0 -2016-11-17 10:00:00,10739.0 -2016-11-17 11:00:00,10871.0 -2016-11-17 12:00:00,11009.0 -2016-11-17 13:00:00,11108.0 -2016-11-17 14:00:00,11193.0 -2016-11-17 15:00:00,11212.0 -2016-11-17 16:00:00,11174.0 -2016-11-17 17:00:00,11044.0 -2016-11-17 18:00:00,11383.0 -2016-11-17 19:00:00,11827.0 -2016-11-17 20:00:00,11584.0 -2016-11-17 21:00:00,11311.0 -2016-11-17 22:00:00,10978.0 -2016-11-17 23:00:00,10477.0 -2016-11-18 00:00:00,9697.0 -2016-11-16 01:00:00,9111.0 -2016-11-16 02:00:00,8673.0 -2016-11-16 03:00:00,8420.0 -2016-11-16 04:00:00,8326.0 -2016-11-16 05:00:00,8324.0 -2016-11-16 06:00:00,8593.0 -2016-11-16 07:00:00,9306.0 -2016-11-16 08:00:00,10354.0 -2016-11-16 09:00:00,10726.0 -2016-11-16 10:00:00,10836.0 -2016-11-16 11:00:00,10877.0 -2016-11-16 12:00:00,10914.0 -2016-11-16 13:00:00,10952.0 -2016-11-16 14:00:00,10928.0 -2016-11-16 15:00:00,10960.0 -2016-11-16 16:00:00,10897.0 -2016-11-16 17:00:00,10824.0 -2016-11-16 18:00:00,11174.0 -2016-11-16 19:00:00,11647.0 -2016-11-16 20:00:00,11472.0 -2016-11-16 21:00:00,11290.0 -2016-11-16 22:00:00,10972.0 -2016-11-16 23:00:00,10507.0 -2016-11-17 00:00:00,9804.0 -2016-11-15 01:00:00,8963.0 -2016-11-15 02:00:00,8580.0 -2016-11-15 03:00:00,8361.0 -2016-11-15 04:00:00,8220.0 -2016-11-15 05:00:00,8238.0 -2016-11-15 06:00:00,8486.0 -2016-11-15 07:00:00,9212.0 -2016-11-15 08:00:00,10305.0 -2016-11-15 09:00:00,10754.0 -2016-11-15 10:00:00,10917.0 -2016-11-15 11:00:00,10956.0 -2016-11-15 12:00:00,11002.0 -2016-11-15 13:00:00,10983.0 -2016-11-15 14:00:00,10926.0 -2016-11-15 15:00:00,10872.0 -2016-11-15 16:00:00,10782.0 -2016-11-15 17:00:00,10723.0 -2016-11-15 18:00:00,11113.0 -2016-11-15 19:00:00,11636.0 -2016-11-15 20:00:00,11455.0 -2016-11-15 21:00:00,11262.0 -2016-11-15 22:00:00,10975.0 -2016-11-15 23:00:00,10454.0 -2016-11-16 00:00:00,9760.0 -2016-11-14 01:00:00,8616.0 -2016-11-14 02:00:00,8318.0 -2016-11-14 03:00:00,8186.0 -2016-11-14 04:00:00,8098.0 -2016-11-14 05:00:00,8118.0 -2016-11-14 06:00:00,8490.0 -2016-11-14 07:00:00,9330.0 -2016-11-14 08:00:00,10388.0 -2016-11-14 09:00:00,10864.0 -2016-11-14 10:00:00,10963.0 -2016-11-14 11:00:00,10910.0 -2016-11-14 12:00:00,10921.0 -2016-11-14 13:00:00,10889.0 -2016-11-14 14:00:00,10847.0 -2016-11-14 15:00:00,10886.0 -2016-11-14 16:00:00,10807.0 -2016-11-14 17:00:00,10708.0 -2016-11-14 18:00:00,11088.0 -2016-11-14 19:00:00,11570.0 -2016-11-14 20:00:00,11379.0 -2016-11-14 21:00:00,11132.0 -2016-11-14 22:00:00,10839.0 -2016-11-14 23:00:00,10277.0 -2016-11-15 00:00:00,9604.0 -2016-11-13 01:00:00,8832.0 -2016-11-13 02:00:00,8518.0 -2016-11-13 03:00:00,8297.0 -2016-11-13 04:00:00,8193.0 -2016-11-13 05:00:00,8057.0 -2016-11-13 06:00:00,8129.0 -2016-11-13 07:00:00,8274.0 -2016-11-13 08:00:00,8495.0 -2016-11-13 09:00:00,8539.0 -2016-11-13 10:00:00,8752.0 -2016-11-13 11:00:00,8849.0 -2016-11-13 12:00:00,8959.0 -2016-11-13 13:00:00,8924.0 -2016-11-13 14:00:00,8912.0 -2016-11-13 15:00:00,8877.0 -2016-11-13 16:00:00,8815.0 -2016-11-13 17:00:00,8895.0 -2016-11-13 18:00:00,9406.0 -2016-11-13 19:00:00,10141.0 -2016-11-13 20:00:00,10145.0 -2016-11-13 21:00:00,10082.0 -2016-11-13 22:00:00,9849.0 -2016-11-13 23:00:00,9551.0 -2016-11-14 00:00:00,9044.0 -2016-11-12 01:00:00,9245.0 -2016-11-12 02:00:00,8795.0 -2016-11-12 03:00:00,8570.0 -2016-11-12 04:00:00,8434.0 -2016-11-12 05:00:00,8401.0 -2016-11-12 06:00:00,8488.0 -2016-11-12 07:00:00,8796.0 -2016-11-12 08:00:00,9141.0 -2016-11-12 09:00:00,9336.0 -2016-11-12 10:00:00,9551.0 -2016-11-12 11:00:00,9703.0 -2016-11-12 12:00:00,9677.0 -2016-11-12 13:00:00,9634.0 -2016-11-12 14:00:00,9481.0 -2016-11-12 15:00:00,9289.0 -2016-11-12 16:00:00,9171.0 -2016-11-12 17:00:00,9161.0 -2016-11-12 18:00:00,9597.0 -2016-11-12 19:00:00,10250.0 -2016-11-12 20:00:00,10234.0 -2016-11-12 21:00:00,10055.0 -2016-11-12 22:00:00,9943.0 -2016-11-12 23:00:00,9663.0 -2016-11-13 00:00:00,9269.0 -2016-11-11 01:00:00,9063.0 -2016-11-11 02:00:00,8588.0 -2016-11-11 03:00:00,8348.0 -2016-11-11 04:00:00,8219.0 -2016-11-11 05:00:00,8218.0 -2016-11-11 06:00:00,8440.0 -2016-11-11 07:00:00,9064.0 -2016-11-11 08:00:00,9884.0 -2016-11-11 09:00:00,10374.0 -2016-11-11 10:00:00,10603.0 -2016-11-11 11:00:00,10752.0 -2016-11-11 12:00:00,10898.0 -2016-11-11 13:00:00,10802.0 -2016-11-11 14:00:00,10774.0 -2016-11-11 15:00:00,10774.0 -2016-11-11 16:00:00,10661.0 -2016-11-11 17:00:00,10633.0 -2016-11-11 18:00:00,10999.0 -2016-11-11 19:00:00,11467.0 -2016-11-11 20:00:00,11383.0 -2016-11-11 21:00:00,11113.0 -2016-11-11 22:00:00,10849.0 -2016-11-11 23:00:00,10426.0 -2016-11-12 00:00:00,9806.0 -2016-11-10 01:00:00,9100.0 -2016-11-10 02:00:00,8708.0 -2016-11-10 03:00:00,8502.0 -2016-11-10 04:00:00,8392.0 -2016-11-10 05:00:00,8369.0 -2016-11-10 06:00:00,8639.0 -2016-11-10 07:00:00,9343.0 -2016-11-10 08:00:00,10337.0 -2016-11-10 09:00:00,10754.0 -2016-11-10 10:00:00,10907.0 -2016-11-10 11:00:00,10993.0 -2016-11-10 12:00:00,11038.0 -2016-11-10 13:00:00,11048.0 -2016-11-10 14:00:00,11093.0 -2016-11-10 15:00:00,11049.0 -2016-11-10 16:00:00,10976.0 -2016-11-10 17:00:00,10885.0 -2016-11-10 18:00:00,11094.0 -2016-11-10 19:00:00,11685.0 -2016-11-10 20:00:00,11463.0 -2016-11-10 21:00:00,11224.0 -2016-11-10 22:00:00,10879.0 -2016-11-10 23:00:00,10377.0 -2016-11-11 00:00:00,9687.0 -2016-11-09 01:00:00,9123.0 -2016-11-09 02:00:00,8717.0 -2016-11-09 03:00:00,8418.0 -2016-11-09 04:00:00,8199.0 -2016-11-09 05:00:00,8191.0 -2016-11-09 06:00:00,8461.0 -2016-11-09 07:00:00,9189.0 -2016-11-09 08:00:00,10156.0 -2016-11-09 09:00:00,10594.0 -2016-11-09 10:00:00,10803.0 -2016-11-09 11:00:00,10883.0 -2016-11-09 12:00:00,10990.0 -2016-11-09 13:00:00,10962.0 -2016-11-09 14:00:00,10949.0 -2016-11-09 15:00:00,10956.0 -2016-11-09 16:00:00,10851.0 -2016-11-09 17:00:00,10768.0 -2016-11-09 18:00:00,10991.0 -2016-11-09 19:00:00,11527.0 -2016-11-09 20:00:00,11438.0 -2016-11-09 21:00:00,11252.0 -2016-11-09 22:00:00,10942.0 -2016-11-09 23:00:00,10430.0 -2016-11-10 00:00:00,9671.0 -2016-11-08 01:00:00,8809.0 -2016-11-08 02:00:00,8420.0 -2016-11-08 03:00:00,8135.0 -2016-11-08 04:00:00,7998.0 -2016-11-08 05:00:00,7988.0 -2016-11-08 06:00:00,8201.0 -2016-11-08 07:00:00,8871.0 -2016-11-08 08:00:00,9920.0 -2016-11-08 09:00:00,10487.0 -2016-11-08 10:00:00,10774.0 -2016-11-08 11:00:00,10852.0 -2016-11-08 12:00:00,10935.0 -2016-11-08 13:00:00,10969.0 -2016-11-08 14:00:00,11023.0 -2016-11-08 15:00:00,11071.0 -2016-11-08 16:00:00,10911.0 -2016-11-08 17:00:00,10751.0 -2016-11-08 18:00:00,11055.0 -2016-11-08 19:00:00,11594.0 -2016-11-08 20:00:00,11432.0 -2016-11-08 21:00:00,11220.0 -2016-11-08 22:00:00,10890.0 -2016-11-08 23:00:00,10352.0 -2016-11-09 00:00:00,9699.0 -2016-11-07 01:00:00,8128.0 -2016-11-07 02:00:00,7882.0 -2016-11-07 03:00:00,7737.0 -2016-11-07 04:00:00,7687.0 -2016-11-07 05:00:00,7733.0 -2016-11-07 06:00:00,8046.0 -2016-11-07 07:00:00,8688.0 -2016-11-07 08:00:00,9770.0 -2016-11-07 09:00:00,10338.0 -2016-11-07 10:00:00,10574.0 -2016-11-07 11:00:00,10698.0 -2016-11-07 12:00:00,10954.0 -2016-11-07 13:00:00,10989.0 -2016-11-07 14:00:00,11029.0 -2016-11-07 15:00:00,11077.0 -2016-11-07 16:00:00,10984.0 -2016-11-07 17:00:00,10856.0 -2016-11-07 18:00:00,11084.0 -2016-11-07 19:00:00,11540.0 -2016-11-07 20:00:00,11327.0 -2016-11-07 21:00:00,11062.0 -2016-11-07 22:00:00,10691.0 -2016-11-07 23:00:00,10127.0 -2016-11-08 00:00:00,9429.0 -2016-11-06 01:00:00,8475.0 -2016-11-06 02:00:00,7814.0 -2016-11-06 02:00:00,8028.0 -2016-11-06 03:00:00,7557.0 -2016-11-06 04:00:00,7543.0 -2016-11-06 05:00:00,7509.0 -2016-11-06 06:00:00,7509.0 -2016-11-06 07:00:00,7704.0 -2016-11-06 08:00:00,7900.0 -2016-11-06 09:00:00,8075.0 -2016-11-06 10:00:00,8390.0 -2016-11-06 11:00:00,8591.0 -2016-11-06 12:00:00,8771.0 -2016-11-06 13:00:00,8943.0 -2016-11-06 14:00:00,9001.0 -2016-11-06 15:00:00,9002.0 -2016-11-06 16:00:00,9027.0 -2016-11-06 17:00:00,9019.0 -2016-11-06 18:00:00,9348.0 -2016-11-06 19:00:00,9995.0 -2016-11-06 20:00:00,9963.0 -2016-11-06 21:00:00,9769.0 -2016-11-06 22:00:00,9482.0 -2016-11-06 23:00:00,9046.0 -2016-11-07 00:00:00,8595.0 -2016-11-05 01:00:00,8995.0 -2016-11-05 02:00:00,8484.0 -2016-11-05 03:00:00,8241.0 -2016-11-05 04:00:00,8027.0 -2016-11-05 05:00:00,7924.0 -2016-11-05 06:00:00,7964.0 -2016-11-05 07:00:00,8230.0 -2016-11-05 08:00:00,8658.0 -2016-11-05 09:00:00,8971.0 -2016-11-05 10:00:00,9148.0 -2016-11-05 11:00:00,9366.0 -2016-11-05 12:00:00,9484.0 -2016-11-05 13:00:00,9530.0 -2016-11-05 14:00:00,9500.0 -2016-11-05 15:00:00,9397.0 -2016-11-05 16:00:00,9307.0 -2016-11-05 17:00:00,9266.0 -2016-11-05 18:00:00,9252.0 -2016-11-05 19:00:00,9473.0 -2016-11-05 20:00:00,9954.0 -2016-11-05 21:00:00,9861.0 -2016-11-05 22:00:00,9674.0 -2016-11-05 23:00:00,9390.0 -2016-11-06 00:00:00,8936.0 -2016-11-04 01:00:00,8954.0 -2016-11-04 02:00:00,8489.0 -2016-11-04 03:00:00,8192.0 -2016-11-04 04:00:00,8043.0 -2016-11-04 05:00:00,7997.0 -2016-11-04 06:00:00,8213.0 -2016-11-04 07:00:00,8817.0 -2016-11-04 08:00:00,9899.0 -2016-11-04 09:00:00,10595.0 -2016-11-04 10:00:00,10669.0 -2016-11-04 11:00:00,10752.0 -2016-11-04 12:00:00,10890.0 -2016-11-04 13:00:00,10859.0 -2016-11-04 14:00:00,10823.0 -2016-11-04 15:00:00,10872.0 -2016-11-04 16:00:00,10787.0 -2016-11-04 17:00:00,10613.0 -2016-11-04 18:00:00,10510.0 -2016-11-04 19:00:00,10576.0 -2016-11-04 20:00:00,11006.0 -2016-11-04 21:00:00,10837.0 -2016-11-04 22:00:00,10547.0 -2016-11-04 23:00:00,10147.0 -2016-11-05 00:00:00,9573.0 -2016-11-03 01:00:00,9322.0 -2016-11-03 02:00:00,8832.0 -2016-11-03 03:00:00,8411.0 -2016-11-03 04:00:00,8168.0 -2016-11-03 05:00:00,8080.0 -2016-11-03 06:00:00,8170.0 -2016-11-03 07:00:00,8749.0 -2016-11-03 08:00:00,9822.0 -2016-11-03 09:00:00,10620.0 -2016-11-03 10:00:00,10663.0 -2016-11-03 11:00:00,10727.0 -2016-11-03 12:00:00,10829.0 -2016-11-03 13:00:00,10870.0 -2016-11-03 14:00:00,10939.0 -2016-11-03 15:00:00,11004.0 -2016-11-03 16:00:00,11013.0 -2016-11-03 17:00:00,10898.0 -2016-11-03 18:00:00,10807.0 -2016-11-03 19:00:00,10912.0 -2016-11-03 20:00:00,11213.0 -2016-11-03 21:00:00,11117.0 -2016-11-03 22:00:00,10800.0 -2016-11-03 23:00:00,10310.0 -2016-11-04 00:00:00,9621.0 -2016-11-02 01:00:00,9657.0 -2016-11-02 02:00:00,9137.0 -2016-11-02 03:00:00,8783.0 -2016-11-02 04:00:00,8607.0 -2016-11-02 05:00:00,8549.0 -2016-11-02 06:00:00,8651.0 -2016-11-02 07:00:00,9145.0 -2016-11-02 08:00:00,10353.0 -2016-11-02 09:00:00,11164.0 -2016-11-02 10:00:00,11262.0 -2016-11-02 11:00:00,11407.0 -2016-11-02 12:00:00,11674.0 -2016-11-02 13:00:00,11639.0 -2016-11-02 14:00:00,11634.0 -2016-11-02 15:00:00,11738.0 -2016-11-02 16:00:00,11676.0 -2016-11-02 17:00:00,11600.0 -2016-11-02 18:00:00,11651.0 -2016-11-02 19:00:00,11993.0 -2016-11-02 20:00:00,12038.0 -2016-11-02 21:00:00,11676.0 -2016-11-02 22:00:00,11235.0 -2016-11-02 23:00:00,10614.0 -2016-11-03 00:00:00,9898.0 -2016-11-01 01:00:00,9058.0 -2016-11-01 02:00:00,8618.0 -2016-11-01 03:00:00,8353.0 -2016-11-01 04:00:00,8217.0 -2016-11-01 05:00:00,8177.0 -2016-11-01 06:00:00,8340.0 -2016-11-01 07:00:00,9010.0 -2016-11-01 08:00:00,10190.0 -2016-11-01 09:00:00,10865.0 -2016-11-01 10:00:00,10973.0 -2016-11-01 11:00:00,11171.0 -2016-11-01 12:00:00,11419.0 -2016-11-01 13:00:00,11491.0 -2016-11-01 14:00:00,11641.0 -2016-11-01 15:00:00,11811.0 -2016-11-01 16:00:00,11760.0 -2016-11-01 17:00:00,11674.0 -2016-11-01 18:00:00,11568.0 -2016-11-01 19:00:00,11657.0 -2016-11-01 20:00:00,12028.0 -2016-11-01 21:00:00,11830.0 -2016-11-01 22:00:00,11406.0 -2016-11-01 23:00:00,10887.0 -2016-11-02 00:00:00,10211.0 -2016-10-31 01:00:00,8621.0 -2016-10-31 02:00:00,8226.0 -2016-10-31 03:00:00,8053.0 -2016-10-31 04:00:00,7939.0 -2016-10-31 05:00:00,7943.0 -2016-10-31 06:00:00,8240.0 -2016-10-31 07:00:00,8975.0 -2016-10-31 08:00:00,10254.0 -2016-10-31 09:00:00,10961.0 -2016-10-31 10:00:00,11004.0 -2016-10-31 11:00:00,10993.0 -2016-10-31 12:00:00,11058.0 -2016-10-31 13:00:00,10955.0 -2016-10-31 14:00:00,10874.0 -2016-10-31 15:00:00,10916.0 -2016-10-31 16:00:00,10809.0 -2016-10-31 17:00:00,10663.0 -2016-10-31 18:00:00,10535.0 -2016-10-31 19:00:00,10748.0 -2016-10-31 20:00:00,11115.0 -2016-10-31 21:00:00,11094.0 -2016-10-31 22:00:00,10859.0 -2016-10-31 23:00:00,10423.0 -2016-11-01 00:00:00,9737.0 -2016-10-30 01:00:00,8659.0 -2016-10-30 02:00:00,8252.0 -2016-10-30 03:00:00,7978.0 -2016-10-30 04:00:00,7787.0 -2016-10-30 05:00:00,7756.0 -2016-10-30 06:00:00,7820.0 -2016-10-30 07:00:00,7784.0 -2016-10-30 08:00:00,7960.0 -2016-10-30 09:00:00,8119.0 -2016-10-30 10:00:00,8278.0 -2016-10-30 11:00:00,8567.0 -2016-10-30 12:00:00,8904.0 -2016-10-30 13:00:00,9082.0 -2016-10-30 14:00:00,9197.0 -2016-10-30 15:00:00,9213.0 -2016-10-30 16:00:00,9204.0 -2016-10-30 17:00:00,9214.0 -2016-10-30 18:00:00,9340.0 -2016-10-30 19:00:00,9657.0 -2016-10-30 20:00:00,10191.0 -2016-10-30 21:00:00,10227.0 -2016-10-30 22:00:00,9950.0 -2016-10-30 23:00:00,9579.0 -2016-10-31 00:00:00,9172.0 -2016-10-29 01:00:00,9455.0 -2016-10-29 02:00:00,9039.0 -2016-10-29 03:00:00,8712.0 -2016-10-29 04:00:00,8418.0 -2016-10-29 05:00:00,8315.0 -2016-10-29 06:00:00,8274.0 -2016-10-29 07:00:00,8532.0 -2016-10-29 08:00:00,8924.0 -2016-10-29 09:00:00,9216.0 -2016-10-29 10:00:00,9577.0 -2016-10-29 11:00:00,9895.0 -2016-10-29 12:00:00,10150.0 -2016-10-29 13:00:00,10227.0 -2016-10-29 14:00:00,10274.0 -2016-10-29 15:00:00,10200.0 -2016-10-29 16:00:00,10142.0 -2016-10-29 17:00:00,10176.0 -2016-10-29 18:00:00,10209.0 -2016-10-29 19:00:00,10399.0 -2016-10-29 20:00:00,10625.0 -2016-10-29 21:00:00,10555.0 -2016-10-29 22:00:00,10232.0 -2016-10-29 23:00:00,9745.0 -2016-10-30 00:00:00,9183.0 -2016-10-28 01:00:00,9450.0 -2016-10-28 02:00:00,8945.0 -2016-10-28 03:00:00,8679.0 -2016-10-28 04:00:00,8582.0 -2016-10-28 05:00:00,8518.0 -2016-10-28 06:00:00,8692.0 -2016-10-28 07:00:00,9345.0 -2016-10-28 08:00:00,10561.0 -2016-10-28 09:00:00,11109.0 -2016-10-28 10:00:00,11153.0 -2016-10-28 11:00:00,11184.0 -2016-10-28 12:00:00,11278.0 -2016-10-28 13:00:00,11180.0 -2016-10-28 14:00:00,11204.0 -2016-10-28 15:00:00,11244.0 -2016-10-28 16:00:00,11180.0 -2016-10-28 17:00:00,11009.0 -2016-10-28 18:00:00,10950.0 -2016-10-28 19:00:00,11078.0 -2016-10-28 20:00:00,11572.0 -2016-10-28 21:00:00,11444.0 -2016-10-28 22:00:00,11154.0 -2016-10-28 23:00:00,10801.0 -2016-10-29 00:00:00,10147.0 -2016-10-27 01:00:00,9395.0 -2016-10-27 02:00:00,9005.0 -2016-10-27 03:00:00,8669.0 -2016-10-27 04:00:00,8451.0 -2016-10-27 05:00:00,8419.0 -2016-10-27 06:00:00,8520.0 -2016-10-27 07:00:00,9236.0 -2016-10-27 08:00:00,10455.0 -2016-10-27 09:00:00,11040.0 -2016-10-27 10:00:00,11180.0 -2016-10-27 11:00:00,11257.0 -2016-10-27 12:00:00,11393.0 -2016-10-27 13:00:00,11291.0 -2016-10-27 14:00:00,11308.0 -2016-10-27 15:00:00,11323.0 -2016-10-27 16:00:00,11233.0 -2016-10-27 17:00:00,11127.0 -2016-10-27 18:00:00,11135.0 -2016-10-27 19:00:00,11226.0 -2016-10-27 20:00:00,11539.0 -2016-10-27 21:00:00,11460.0 -2016-10-27 22:00:00,11138.0 -2016-10-27 23:00:00,10787.0 -2016-10-28 00:00:00,10130.0 -2016-10-26 01:00:00,9257.0 -2016-10-26 02:00:00,8852.0 -2016-10-26 03:00:00,8584.0 -2016-10-26 04:00:00,8477.0 -2016-10-26 05:00:00,8414.0 -2016-10-26 06:00:00,8603.0 -2016-10-26 07:00:00,9307.0 -2016-10-26 08:00:00,10380.0 -2016-10-26 09:00:00,11292.0 -2016-10-26 10:00:00,11514.0 -2016-10-26 11:00:00,11569.0 -2016-10-26 12:00:00,11790.0 -2016-10-26 13:00:00,11890.0 -2016-10-26 14:00:00,11792.0 -2016-10-26 15:00:00,11893.0 -2016-10-26 16:00:00,11851.0 -2016-10-26 17:00:00,11691.0 -2016-10-26 18:00:00,11800.0 -2016-10-26 19:00:00,11934.0 -2016-10-26 20:00:00,12022.0 -2016-10-26 21:00:00,11811.0 -2016-10-26 22:00:00,11550.0 -2016-10-26 23:00:00,11002.0 -2016-10-27 00:00:00,10128.0 -2016-10-25 01:00:00,9088.0 -2016-10-25 02:00:00,8707.0 -2016-10-25 03:00:00,8493.0 -2016-10-25 04:00:00,8340.0 -2016-10-25 05:00:00,8200.0 -2016-10-25 06:00:00,8315.0 -2016-10-25 07:00:00,8997.0 -2016-10-25 08:00:00,10197.0 -2016-10-25 09:00:00,10843.0 -2016-10-25 10:00:00,10895.0 -2016-10-25 11:00:00,11073.0 -2016-10-25 12:00:00,11139.0 -2016-10-25 13:00:00,11106.0 -2016-10-25 14:00:00,11050.0 -2016-10-25 15:00:00,11066.0 -2016-10-25 16:00:00,10959.0 -2016-10-25 17:00:00,10926.0 -2016-10-25 18:00:00,10963.0 -2016-10-25 19:00:00,11233.0 -2016-10-25 20:00:00,11647.0 -2016-10-25 21:00:00,11634.0 -2016-10-25 22:00:00,11335.0 -2016-10-25 23:00:00,10791.0 -2016-10-26 00:00:00,9933.0 -2016-10-24 01:00:00,8458.0 -2016-10-24 02:00:00,8129.0 -2016-10-24 03:00:00,7948.0 -2016-10-24 04:00:00,7749.0 -2016-10-24 05:00:00,7793.0 -2016-10-24 06:00:00,8062.0 -2016-10-24 07:00:00,8706.0 -2016-10-24 08:00:00,9964.0 -2016-10-24 09:00:00,10463.0 -2016-10-24 10:00:00,10497.0 -2016-10-24 11:00:00,10606.0 -2016-10-24 12:00:00,10731.0 -2016-10-24 13:00:00,10770.0 -2016-10-24 14:00:00,10770.0 -2016-10-24 15:00:00,10780.0 -2016-10-24 16:00:00,10750.0 -2016-10-24 17:00:00,10655.0 -2016-10-24 18:00:00,10586.0 -2016-10-24 19:00:00,10591.0 -2016-10-24 20:00:00,11170.0 -2016-10-24 21:00:00,11162.0 -2016-10-24 22:00:00,10872.0 -2016-10-24 23:00:00,10385.0 -2016-10-25 00:00:00,9668.0 -2016-10-23 01:00:00,8743.0 -2016-10-23 02:00:00,8415.0 -2016-10-23 03:00:00,8126.0 -2016-10-23 04:00:00,7954.0 -2016-10-23 05:00:00,7807.0 -2016-10-23 06:00:00,7822.0 -2016-10-23 07:00:00,7932.0 -2016-10-23 08:00:00,8163.0 -2016-10-23 09:00:00,8222.0 -2016-10-23 10:00:00,8372.0 -2016-10-23 11:00:00,8570.0 -2016-10-23 12:00:00,8712.0 -2016-10-23 13:00:00,8869.0 -2016-10-23 14:00:00,8965.0 -2016-10-23 15:00:00,9016.0 -2016-10-23 16:00:00,9142.0 -2016-10-23 17:00:00,9214.0 -2016-10-23 18:00:00,9233.0 -2016-10-23 19:00:00,9409.0 -2016-10-23 20:00:00,10128.0 -2016-10-23 21:00:00,10066.0 -2016-10-23 22:00:00,9836.0 -2016-10-23 23:00:00,9419.0 -2016-10-24 00:00:00,8920.0 -2016-10-22 01:00:00,9143.0 -2016-10-22 02:00:00,8678.0 -2016-10-22 03:00:00,8379.0 -2016-10-22 04:00:00,8180.0 -2016-10-22 05:00:00,8078.0 -2016-10-22 06:00:00,8070.0 -2016-10-22 07:00:00,8312.0 -2016-10-22 08:00:00,8826.0 -2016-10-22 09:00:00,9024.0 -2016-10-22 10:00:00,9363.0 -2016-10-22 11:00:00,9473.0 -2016-10-22 12:00:00,9522.0 -2016-10-22 13:00:00,9500.0 -2016-10-22 14:00:00,9405.0 -2016-10-22 15:00:00,9303.0 -2016-10-22 16:00:00,9204.0 -2016-10-22 17:00:00,9237.0 -2016-10-22 18:00:00,9188.0 -2016-10-22 19:00:00,9276.0 -2016-10-22 20:00:00,9919.0 -2016-10-22 21:00:00,9950.0 -2016-10-22 22:00:00,9811.0 -2016-10-22 23:00:00,9536.0 -2016-10-23 00:00:00,9164.0 -2016-10-21 01:00:00,9236.0 -2016-10-21 02:00:00,8809.0 -2016-10-21 03:00:00,8545.0 -2016-10-21 04:00:00,8389.0 -2016-10-21 05:00:00,8298.0 -2016-10-21 06:00:00,8465.0 -2016-10-21 07:00:00,9026.0 -2016-10-21 08:00:00,10136.0 -2016-10-21 09:00:00,10766.0 -2016-10-21 10:00:00,10850.0 -2016-10-21 11:00:00,10841.0 -2016-10-21 12:00:00,10911.0 -2016-10-21 13:00:00,10887.0 -2016-10-21 14:00:00,10818.0 -2016-10-21 15:00:00,10764.0 -2016-10-21 16:00:00,10588.0 -2016-10-21 17:00:00,10549.0 -2016-10-21 18:00:00,10444.0 -2016-10-21 19:00:00,10490.0 -2016-10-21 20:00:00,10958.0 -2016-10-21 21:00:00,10919.0 -2016-10-21 22:00:00,10654.0 -2016-10-21 23:00:00,10305.0 -2016-10-22 00:00:00,9719.0 -2016-10-20 01:00:00,9291.0 -2016-10-20 02:00:00,8762.0 -2016-10-20 03:00:00,8417.0 -2016-10-20 04:00:00,8170.0 -2016-10-20 05:00:00,8055.0 -2016-10-20 06:00:00,8227.0 -2016-10-20 07:00:00,8796.0 -2016-10-20 08:00:00,9849.0 -2016-10-20 09:00:00,10541.0 -2016-10-20 10:00:00,10829.0 -2016-10-20 11:00:00,11034.0 -2016-10-20 12:00:00,11157.0 -2016-10-20 13:00:00,11198.0 -2016-10-20 14:00:00,11126.0 -2016-10-20 15:00:00,11177.0 -2016-10-20 16:00:00,11073.0 -2016-10-20 17:00:00,11017.0 -2016-10-20 18:00:00,10958.0 -2016-10-20 19:00:00,11022.0 -2016-10-20 20:00:00,11322.0 -2016-10-20 21:00:00,11295.0 -2016-10-20 22:00:00,11015.0 -2016-10-20 23:00:00,10588.0 -2016-10-21 00:00:00,9878.0 -2016-10-19 01:00:00,9485.0 -2016-10-19 02:00:00,8914.0 -2016-10-19 03:00:00,8510.0 -2016-10-19 04:00:00,8238.0 -2016-10-19 05:00:00,8083.0 -2016-10-19 06:00:00,8259.0 -2016-10-19 07:00:00,8746.0 -2016-10-19 08:00:00,9745.0 -2016-10-19 09:00:00,10306.0 -2016-10-19 10:00:00,10590.0 -2016-10-19 11:00:00,11014.0 -2016-10-19 12:00:00,11348.0 -2016-10-19 13:00:00,11507.0 -2016-10-19 14:00:00,11642.0 -2016-10-19 15:00:00,11769.0 -2016-10-19 16:00:00,11725.0 -2016-10-19 17:00:00,11559.0 -2016-10-19 18:00:00,11447.0 -2016-10-19 19:00:00,11448.0 -2016-10-19 20:00:00,11804.0 -2016-10-19 21:00:00,11739.0 -2016-10-19 22:00:00,11292.0 -2016-10-19 23:00:00,10724.0 -2016-10-20 00:00:00,10018.0 -2016-10-18 01:00:00,10769.0 -2016-10-18 02:00:00,10179.0 -2016-10-18 03:00:00,9714.0 -2016-10-18 04:00:00,9404.0 -2016-10-18 05:00:00,9185.0 -2016-10-18 06:00:00,9221.0 -2016-10-18 07:00:00,9815.0 -2016-10-18 08:00:00,11006.0 -2016-10-18 09:00:00,11731.0 -2016-10-18 10:00:00,12074.0 -2016-10-18 11:00:00,12494.0 -2016-10-18 12:00:00,12872.0 -2016-10-18 13:00:00,13147.0 -2016-10-18 14:00:00,13264.0 -2016-10-18 15:00:00,13401.0 -2016-10-18 16:00:00,13422.0 -2016-10-18 17:00:00,13289.0 -2016-10-18 18:00:00,13096.0 -2016-10-18 19:00:00,12658.0 -2016-10-18 20:00:00,12671.0 -2016-10-18 21:00:00,12455.0 -2016-10-18 22:00:00,11968.0 -2016-10-18 23:00:00,11217.0 -2016-10-19 00:00:00,10287.0 -2016-10-17 01:00:00,9239.0 -2016-10-17 02:00:00,8929.0 -2016-10-17 03:00:00,8647.0 -2016-10-17 04:00:00,8505.0 -2016-10-17 05:00:00,8428.0 -2016-10-17 06:00:00,8617.0 -2016-10-17 07:00:00,9394.0 -2016-10-17 08:00:00,10496.0 -2016-10-17 09:00:00,11119.0 -2016-10-17 10:00:00,11548.0 -2016-10-17 11:00:00,11774.0 -2016-10-17 12:00:00,12180.0 -2016-10-17 13:00:00,12513.0 -2016-10-17 14:00:00,12748.0 -2016-10-17 15:00:00,13031.0 -2016-10-17 16:00:00,13425.0 -2016-10-17 17:00:00,13635.0 -2016-10-17 18:00:00,13629.0 -2016-10-17 19:00:00,13394.0 -2016-10-17 20:00:00,13634.0 -2016-10-17 21:00:00,13678.0 -2016-10-17 22:00:00,13285.0 -2016-10-17 23:00:00,12586.0 -2016-10-18 00:00:00,11738.0 -2016-10-16 01:00:00,8908.0 -2016-10-16 02:00:00,8521.0 -2016-10-16 03:00:00,8230.0 -2016-10-16 04:00:00,8031.0 -2016-10-16 05:00:00,7944.0 -2016-10-16 06:00:00,7885.0 -2016-10-16 07:00:00,7995.0 -2016-10-16 08:00:00,8167.0 -2016-10-16 09:00:00,8404.0 -2016-10-16 10:00:00,8660.0 -2016-10-16 11:00:00,8955.0 -2016-10-16 12:00:00,9188.0 -2016-10-16 13:00:00,9540.0 -2016-10-16 14:00:00,9684.0 -2016-10-16 15:00:00,9725.0 -2016-10-16 16:00:00,9744.0 -2016-10-16 17:00:00,9839.0 -2016-10-16 18:00:00,9941.0 -2016-10-16 19:00:00,10139.0 -2016-10-16 20:00:00,10658.0 -2016-10-16 21:00:00,10931.0 -2016-10-16 22:00:00,10711.0 -2016-10-16 23:00:00,10346.0 -2016-10-17 00:00:00,9798.0 -2016-10-15 01:00:00,9184.0 -2016-10-15 02:00:00,8619.0 -2016-10-15 03:00:00,8325.0 -2016-10-15 04:00:00,8075.0 -2016-10-15 05:00:00,7923.0 -2016-10-15 06:00:00,7959.0 -2016-10-15 07:00:00,8185.0 -2016-10-15 08:00:00,8667.0 -2016-10-15 09:00:00,8973.0 -2016-10-15 10:00:00,9334.0 -2016-10-15 11:00:00,9685.0 -2016-10-15 12:00:00,9843.0 -2016-10-15 13:00:00,9971.0 -2016-10-15 14:00:00,9921.0 -2016-10-15 15:00:00,9847.0 -2016-10-15 16:00:00,9771.0 -2016-10-15 17:00:00,9806.0 -2016-10-15 18:00:00,9748.0 -2016-10-15 19:00:00,9722.0 -2016-10-15 20:00:00,10216.0 -2016-10-15 21:00:00,10378.0 -2016-10-15 22:00:00,10213.0 -2016-10-15 23:00:00,9837.0 -2016-10-16 00:00:00,9312.0 -2016-10-14 01:00:00,8950.0 -2016-10-14 02:00:00,8489.0 -2016-10-14 03:00:00,8191.0 -2016-10-14 04:00:00,8044.0 -2016-10-14 05:00:00,8012.0 -2016-10-14 06:00:00,8210.0 -2016-10-14 07:00:00,8836.0 -2016-10-14 08:00:00,9942.0 -2016-10-14 09:00:00,10466.0 -2016-10-14 10:00:00,10612.0 -2016-10-14 11:00:00,10705.0 -2016-10-14 12:00:00,10823.0 -2016-10-14 13:00:00,10846.0 -2016-10-14 14:00:00,10834.0 -2016-10-14 15:00:00,10887.0 -2016-10-14 16:00:00,10815.0 -2016-10-14 17:00:00,10691.0 -2016-10-14 18:00:00,10595.0 -2016-10-14 19:00:00,10475.0 -2016-10-14 20:00:00,10805.0 -2016-10-14 21:00:00,10828.0 -2016-10-14 22:00:00,10668.0 -2016-10-14 23:00:00,10376.0 -2016-10-15 00:00:00,9772.0 -2016-10-13 01:00:00,8805.0 -2016-10-13 02:00:00,8352.0 -2016-10-13 03:00:00,8035.0 -2016-10-13 04:00:00,7871.0 -2016-10-13 05:00:00,7868.0 -2016-10-13 06:00:00,8083.0 -2016-10-13 07:00:00,8747.0 -2016-10-13 08:00:00,9890.0 -2016-10-13 09:00:00,10419.0 -2016-10-13 10:00:00,10615.0 -2016-10-13 11:00:00,10703.0 -2016-10-13 12:00:00,10755.0 -2016-10-13 13:00:00,10735.0 -2016-10-13 14:00:00,10700.0 -2016-10-13 15:00:00,10725.0 -2016-10-13 16:00:00,10650.0 -2016-10-13 17:00:00,10541.0 -2016-10-13 18:00:00,10486.0 -2016-10-13 19:00:00,10425.0 -2016-10-13 20:00:00,10861.0 -2016-10-13 21:00:00,11006.0 -2016-10-13 22:00:00,10764.0 -2016-10-13 23:00:00,10323.0 -2016-10-14 00:00:00,9607.0 -2016-10-12 01:00:00,9197.0 -2016-10-12 02:00:00,8652.0 -2016-10-12 03:00:00,8319.0 -2016-10-12 04:00:00,8146.0 -2016-10-12 05:00:00,8112.0 -2016-10-12 06:00:00,8314.0 -2016-10-12 07:00:00,8959.0 -2016-10-12 08:00:00,10083.0 -2016-10-12 09:00:00,10604.0 -2016-10-12 10:00:00,10959.0 -2016-10-12 11:00:00,11261.0 -2016-10-12 12:00:00,11524.0 -2016-10-12 13:00:00,11612.0 -2016-10-12 14:00:00,11665.0 -2016-10-12 15:00:00,11589.0 -2016-10-12 16:00:00,11482.0 -2016-10-12 17:00:00,11405.0 -2016-10-12 18:00:00,11222.0 -2016-10-12 19:00:00,11006.0 -2016-10-12 20:00:00,11125.0 -2016-10-12 21:00:00,11074.0 -2016-10-12 22:00:00,10782.0 -2016-10-12 23:00:00,10248.0 -2016-10-13 00:00:00,9489.0 -2016-10-11 01:00:00,9066.0 -2016-10-11 02:00:00,8608.0 -2016-10-11 03:00:00,8313.0 -2016-10-11 04:00:00,7953.0 -2016-10-11 05:00:00,7938.0 -2016-10-11 06:00:00,8140.0 -2016-10-11 07:00:00,8759.0 -2016-10-11 08:00:00,9855.0 -2016-10-11 09:00:00,10406.0 -2016-10-11 10:00:00,10731.0 -2016-10-11 11:00:00,11015.0 -2016-10-11 12:00:00,11227.0 -2016-10-11 13:00:00,11332.0 -2016-10-11 14:00:00,11427.0 -2016-10-11 15:00:00,11568.0 -2016-10-11 16:00:00,11606.0 -2016-10-11 17:00:00,11553.0 -2016-10-11 18:00:00,11495.0 -2016-10-11 19:00:00,11347.0 -2016-10-11 20:00:00,11552.0 -2016-10-11 21:00:00,11635.0 -2016-10-11 22:00:00,11272.0 -2016-10-11 23:00:00,10697.0 -2016-10-12 00:00:00,9938.0 -2016-10-10 01:00:00,8293.0 -2016-10-10 02:00:00,7938.0 -2016-10-10 03:00:00,7691.0 -2016-10-10 04:00:00,7646.0 -2016-10-10 05:00:00,7606.0 -2016-10-10 06:00:00,7832.0 -2016-10-10 07:00:00,8394.0 -2016-10-10 08:00:00,9388.0 -2016-10-10 09:00:00,9859.0 -2016-10-10 10:00:00,10162.0 -2016-10-10 11:00:00,10448.0 -2016-10-10 12:00:00,10724.0 -2016-10-10 13:00:00,10892.0 -2016-10-10 14:00:00,11005.0 -2016-10-10 15:00:00,11116.0 -2016-10-10 16:00:00,11130.0 -2016-10-10 17:00:00,11054.0 -2016-10-10 18:00:00,10994.0 -2016-10-10 19:00:00,10889.0 -2016-10-10 20:00:00,11246.0 -2016-10-10 21:00:00,11288.0 -2016-10-10 22:00:00,10969.0 -2016-10-10 23:00:00,10431.0 -2016-10-11 00:00:00,9747.0 -2016-10-09 01:00:00,8290.0 -2016-10-09 02:00:00,7904.0 -2016-10-09 03:00:00,7641.0 -2016-10-09 04:00:00,7458.0 -2016-10-09 05:00:00,7359.0 -2016-10-09 06:00:00,7333.0 -2016-10-09 07:00:00,7466.0 -2016-10-09 08:00:00,7662.0 -2016-10-09 09:00:00,7691.0 -2016-10-09 10:00:00,8006.0 -2016-10-09 11:00:00,8334.0 -2016-10-09 12:00:00,8525.0 -2016-10-09 13:00:00,8676.0 -2016-10-09 14:00:00,8758.0 -2016-10-09 15:00:00,8799.0 -2016-10-09 16:00:00,8819.0 -2016-10-09 17:00:00,8831.0 -2016-10-09 18:00:00,8871.0 -2016-10-09 19:00:00,8931.0 -2016-10-09 20:00:00,9376.0 -2016-10-09 21:00:00,9665.0 -2016-10-09 22:00:00,9453.0 -2016-10-09 23:00:00,9160.0 -2016-10-10 00:00:00,8789.0 -2016-10-08 01:00:00,8766.0 -2016-10-08 02:00:00,8225.0 -2016-10-08 03:00:00,7984.0 -2016-10-08 04:00:00,7694.0 -2016-10-08 05:00:00,7589.0 -2016-10-08 06:00:00,7653.0 -2016-10-08 07:00:00,7886.0 -2016-10-08 08:00:00,8274.0 -2016-10-08 09:00:00,8463.0 -2016-10-08 10:00:00,8828.0 -2016-10-08 11:00:00,9072.0 -2016-10-08 12:00:00,9200.0 -2016-10-08 13:00:00,9292.0 -2016-10-08 14:00:00,9266.0 -2016-10-08 15:00:00,9134.0 -2016-10-08 16:00:00,9138.0 -2016-10-08 17:00:00,9106.0 -2016-10-08 18:00:00,9070.0 -2016-10-08 19:00:00,9110.0 -2016-10-08 20:00:00,9441.0 -2016-10-08 21:00:00,9695.0 -2016-10-08 22:00:00,9505.0 -2016-10-08 23:00:00,9227.0 -2016-10-09 00:00:00,8797.0 -2016-10-07 01:00:00,9997.0 -2016-10-07 02:00:00,9411.0 -2016-10-07 03:00:00,9069.0 -2016-10-07 04:00:00,8690.0 -2016-10-07 05:00:00,8621.0 -2016-10-07 06:00:00,8789.0 -2016-10-07 07:00:00,9399.0 -2016-10-07 08:00:00,10473.0 -2016-10-07 09:00:00,11015.0 -2016-10-07 10:00:00,11541.0 -2016-10-07 11:00:00,11864.0 -2016-10-07 12:00:00,12188.0 -2016-10-07 13:00:00,11994.0 -2016-10-07 14:00:00,11722.0 -2016-10-07 15:00:00,11445.0 -2016-10-07 16:00:00,11245.0 -2016-10-07 17:00:00,11008.0 -2016-10-07 18:00:00,10791.0 -2016-10-07 19:00:00,10618.0 -2016-10-07 20:00:00,10732.0 -2016-10-07 21:00:00,10837.0 -2016-10-07 22:00:00,10527.0 -2016-10-07 23:00:00,10095.0 -2016-10-08 00:00:00,9489.0 -2016-10-06 01:00:00,9695.0 -2016-10-06 02:00:00,9067.0 -2016-10-06 03:00:00,8703.0 -2016-10-06 04:00:00,8498.0 -2016-10-06 05:00:00,8401.0 -2016-10-06 06:00:00,8601.0 -2016-10-06 07:00:00,9347.0 -2016-10-06 08:00:00,10526.0 -2016-10-06 09:00:00,11316.0 -2016-10-06 10:00:00,11678.0 -2016-10-06 11:00:00,11920.0 -2016-10-06 12:00:00,12105.0 -2016-10-06 13:00:00,12139.0 -2016-10-06 14:00:00,12348.0 -2016-10-06 15:00:00,12444.0 -2016-10-06 16:00:00,12345.0 -2016-10-06 17:00:00,12343.0 -2016-10-06 18:00:00,12308.0 -2016-10-06 19:00:00,12193.0 -2016-10-06 20:00:00,12451.0 -2016-10-06 21:00:00,12496.0 -2016-10-06 22:00:00,12196.0 -2016-10-06 23:00:00,11639.0 -2016-10-07 00:00:00,10825.0 -2016-10-05 01:00:00,9495.0 -2016-10-05 02:00:00,8953.0 -2016-10-05 03:00:00,8601.0 -2016-10-05 04:00:00,8432.0 -2016-10-05 05:00:00,8378.0 -2016-10-05 06:00:00,8550.0 -2016-10-05 07:00:00,9217.0 -2016-10-05 08:00:00,10384.0 -2016-10-05 09:00:00,10960.0 -2016-10-05 10:00:00,11430.0 -2016-10-05 11:00:00,11835.0 -2016-10-05 12:00:00,12289.0 -2016-10-05 13:00:00,12387.0 -2016-10-05 14:00:00,12510.0 -2016-10-05 15:00:00,12639.0 -2016-10-05 16:00:00,12970.0 -2016-10-05 17:00:00,13030.0 -2016-10-05 18:00:00,13006.0 -2016-10-05 19:00:00,12736.0 -2016-10-05 20:00:00,12650.0 -2016-10-05 21:00:00,12673.0 -2016-10-05 22:00:00,12234.0 -2016-10-05 23:00:00,11479.0 -2016-10-06 00:00:00,10562.0 -2016-10-04 01:00:00,9197.0 -2016-10-04 02:00:00,8701.0 -2016-10-04 03:00:00,8404.0 -2016-10-04 04:00:00,8227.0 -2016-10-04 05:00:00,8162.0 -2016-10-04 06:00:00,8363.0 -2016-10-04 07:00:00,9011.0 -2016-10-04 08:00:00,10128.0 -2016-10-04 09:00:00,10736.0 -2016-10-04 10:00:00,11050.0 -2016-10-04 11:00:00,11282.0 -2016-10-04 12:00:00,11583.0 -2016-10-04 13:00:00,11837.0 -2016-10-04 14:00:00,11905.0 -2016-10-04 15:00:00,12005.0 -2016-10-04 16:00:00,12015.0 -2016-10-04 17:00:00,12007.0 -2016-10-04 18:00:00,11951.0 -2016-10-04 19:00:00,11731.0 -2016-10-04 20:00:00,11747.0 -2016-10-04 21:00:00,12025.0 -2016-10-04 22:00:00,11731.0 -2016-10-04 23:00:00,11146.0 -2016-10-05 00:00:00,10298.0 -2016-10-03 01:00:00,8514.0 -2016-10-03 02:00:00,8181.0 -2016-10-03 03:00:00,7958.0 -2016-10-03 04:00:00,7869.0 -2016-10-03 05:00:00,7851.0 -2016-10-03 06:00:00,8097.0 -2016-10-03 07:00:00,8754.0 -2016-10-03 08:00:00,9912.0 -2016-10-03 09:00:00,10500.0 -2016-10-03 10:00:00,10885.0 -2016-10-03 11:00:00,11070.0 -2016-10-03 12:00:00,11341.0 -2016-10-03 13:00:00,11450.0 -2016-10-03 14:00:00,11480.0 -2016-10-03 15:00:00,11537.0 -2016-10-03 16:00:00,11434.0 -2016-10-03 17:00:00,11326.0 -2016-10-03 18:00:00,11249.0 -2016-10-03 19:00:00,11160.0 -2016-10-03 20:00:00,11389.0 -2016-10-03 21:00:00,11567.0 -2016-10-03 22:00:00,11255.0 -2016-10-03 23:00:00,10727.0 -2016-10-04 00:00:00,9955.0 -2016-10-02 01:00:00,8637.0 -2016-10-02 02:00:00,8213.0 -2016-10-02 03:00:00,7900.0 -2016-10-02 04:00:00,7734.0 -2016-10-02 05:00:00,7594.0 -2016-10-02 06:00:00,7595.0 -2016-10-02 07:00:00,7633.0 -2016-10-02 08:00:00,7861.0 -2016-10-02 09:00:00,7872.0 -2016-10-02 10:00:00,8278.0 -2016-10-02 11:00:00,8548.0 -2016-10-02 12:00:00,8849.0 -2016-10-02 13:00:00,9038.0 -2016-10-02 14:00:00,9106.0 -2016-10-02 15:00:00,9114.0 -2016-10-02 16:00:00,9101.0 -2016-10-02 17:00:00,9191.0 -2016-10-02 18:00:00,9261.0 -2016-10-02 19:00:00,9346.0 -2016-10-02 20:00:00,9735.0 -2016-10-02 21:00:00,10042.0 -2016-10-02 22:00:00,9835.0 -2016-10-02 23:00:00,9483.0 -2016-10-03 00:00:00,9043.0 -2016-10-01 01:00:00,9142.0 -2016-10-01 02:00:00,8616.0 -2016-10-01 03:00:00,8302.0 -2016-10-01 04:00:00,8102.0 -2016-10-01 05:00:00,7964.0 -2016-10-01 06:00:00,7979.0 -2016-10-01 07:00:00,8222.0 -2016-10-01 08:00:00,8603.0 -2016-10-01 09:00:00,8909.0 -2016-10-01 10:00:00,9375.0 -2016-10-01 11:00:00,9784.0 -2016-10-01 12:00:00,9969.0 -2016-10-01 13:00:00,10061.0 -2016-10-01 14:00:00,9950.0 -2016-10-01 15:00:00,9795.0 -2016-10-01 16:00:00,9698.0 -2016-10-01 17:00:00,9595.0 -2016-10-01 18:00:00,9573.0 -2016-10-01 19:00:00,9610.0 -2016-10-01 20:00:00,9864.0 -2016-10-01 21:00:00,10074.0 -2016-10-01 22:00:00,9950.0 -2016-10-01 23:00:00,9623.0 -2016-10-02 00:00:00,9182.0 -2016-09-30 01:00:00,9163.0 -2016-09-30 02:00:00,8694.0 -2016-09-30 03:00:00,8429.0 -2016-09-30 04:00:00,8234.0 -2016-09-30 05:00:00,8212.0 -2016-09-30 06:00:00,8339.0 -2016-09-30 07:00:00,8949.0 -2016-09-30 08:00:00,10036.0 -2016-09-30 09:00:00,10766.0 -2016-09-30 10:00:00,11079.0 -2016-09-30 11:00:00,11331.0 -2016-09-30 12:00:00,11503.0 -2016-09-30 13:00:00,11449.0 -2016-09-30 14:00:00,11399.0 -2016-09-30 15:00:00,11394.0 -2016-09-30 16:00:00,11267.0 -2016-09-30 17:00:00,11138.0 -2016-09-30 18:00:00,11042.0 -2016-09-30 19:00:00,10998.0 -2016-09-30 20:00:00,11152.0 -2016-09-30 21:00:00,11121.0 -2016-09-30 22:00:00,10832.0 -2016-09-30 23:00:00,10458.0 -2016-10-01 00:00:00,9808.0 -2016-09-29 01:00:00,9000.0 -2016-09-29 02:00:00,8586.0 -2016-09-29 03:00:00,8273.0 -2016-09-29 04:00:00,8091.0 -2016-09-29 05:00:00,8091.0 -2016-09-29 06:00:00,8253.0 -2016-09-29 07:00:00,8918.0 -2016-09-29 08:00:00,10056.0 -2016-09-29 09:00:00,10695.0 -2016-09-29 10:00:00,11026.0 -2016-09-29 11:00:00,11246.0 -2016-09-29 12:00:00,11410.0 -2016-09-29 13:00:00,11432.0 -2016-09-29 14:00:00,11462.0 -2016-09-29 15:00:00,11492.0 -2016-09-29 16:00:00,11408.0 -2016-09-29 17:00:00,11281.0 -2016-09-29 18:00:00,11204.0 -2016-09-29 19:00:00,11078.0 -2016-09-29 20:00:00,11225.0 -2016-09-29 21:00:00,11434.0 -2016-09-29 22:00:00,11146.0 -2016-09-29 23:00:00,10666.0 -2016-09-30 00:00:00,9902.0 -2016-09-28 01:00:00,8947.0 -2016-09-28 02:00:00,8459.0 -2016-09-28 03:00:00,8160.0 -2016-09-28 04:00:00,8019.0 -2016-09-28 05:00:00,7966.0 -2016-09-28 06:00:00,8151.0 -2016-09-28 07:00:00,8756.0 -2016-09-28 08:00:00,9778.0 -2016-09-28 09:00:00,10302.0 -2016-09-28 10:00:00,10562.0 -2016-09-28 11:00:00,10773.0 -2016-09-28 12:00:00,10946.0 -2016-09-28 13:00:00,10988.0 -2016-09-28 14:00:00,10973.0 -2016-09-28 15:00:00,10999.0 -2016-09-28 16:00:00,10956.0 -2016-09-28 17:00:00,10891.0 -2016-09-28 18:00:00,10864.0 -2016-09-28 19:00:00,10778.0 -2016-09-28 20:00:00,10925.0 -2016-09-28 21:00:00,11191.0 -2016-09-28 22:00:00,10922.0 -2016-09-28 23:00:00,10409.0 -2016-09-29 00:00:00,9640.0 -2016-09-27 01:00:00,9080.0 -2016-09-27 02:00:00,8583.0 -2016-09-27 03:00:00,8222.0 -2016-09-27 04:00:00,8048.0 -2016-09-27 05:00:00,7957.0 -2016-09-27 06:00:00,8136.0 -2016-09-27 07:00:00,8713.0 -2016-09-27 08:00:00,9712.0 -2016-09-27 09:00:00,10189.0 -2016-09-27 10:00:00,10509.0 -2016-09-27 11:00:00,10823.0 -2016-09-27 12:00:00,11116.0 -2016-09-27 13:00:00,11333.0 -2016-09-27 14:00:00,11416.0 -2016-09-27 15:00:00,11556.0 -2016-09-27 16:00:00,11542.0 -2016-09-27 17:00:00,11398.0 -2016-09-27 18:00:00,11292.0 -2016-09-27 19:00:00,11074.0 -2016-09-27 20:00:00,11085.0 -2016-09-27 21:00:00,11356.0 -2016-09-27 22:00:00,11087.0 -2016-09-27 23:00:00,10485.0 -2016-09-28 00:00:00,9679.0 -2016-09-26 01:00:00,10275.0 -2016-09-26 02:00:00,9453.0 -2016-09-26 03:00:00,8947.0 -2016-09-26 04:00:00,8659.0 -2016-09-26 05:00:00,8498.0 -2016-09-26 06:00:00,8636.0 -2016-09-26 07:00:00,9301.0 -2016-09-26 08:00:00,10304.0 -2016-09-26 09:00:00,10811.0 -2016-09-26 10:00:00,11085.0 -2016-09-26 11:00:00,11430.0 -2016-09-26 12:00:00,11834.0 -2016-09-26 13:00:00,11927.0 -2016-09-26 14:00:00,11968.0 -2016-09-26 15:00:00,12075.0 -2016-09-26 16:00:00,12104.0 -2016-09-26 17:00:00,12050.0 -2016-09-26 18:00:00,11903.0 -2016-09-26 19:00:00,11582.0 -2016-09-26 20:00:00,11359.0 -2016-09-26 21:00:00,11600.0 -2016-09-26 22:00:00,11217.0 -2016-09-26 23:00:00,10650.0 -2016-09-27 00:00:00,9879.0 -2016-09-25 01:00:00,10013.0 -2016-09-25 02:00:00,9362.0 -2016-09-25 03:00:00,8944.0 -2016-09-25 04:00:00,8633.0 -2016-09-25 05:00:00,8460.0 -2016-09-25 06:00:00,8354.0 -2016-09-25 07:00:00,8430.0 -2016-09-25 08:00:00,8499.0 -2016-09-25 09:00:00,8565.0 -2016-09-25 10:00:00,9146.0 -2016-09-25 11:00:00,9888.0 -2016-09-25 12:00:00,10681.0 -2016-09-25 13:00:00,11486.0 -2016-09-25 14:00:00,12179.0 -2016-09-25 15:00:00,13028.0 -2016-09-25 16:00:00,13554.0 -2016-09-25 17:00:00,13876.0 -2016-09-25 18:00:00,13965.0 -2016-09-25 19:00:00,13809.0 -2016-09-25 20:00:00,13567.0 -2016-09-25 21:00:00,13847.0 -2016-09-25 22:00:00,13510.0 -2016-09-25 23:00:00,12790.0 -2016-09-26 00:00:00,11502.0 -2016-09-24 01:00:00,10670.0 -2016-09-24 02:00:00,9942.0 -2016-09-24 03:00:00,9501.0 -2016-09-24 04:00:00,9156.0 -2016-09-24 05:00:00,8961.0 -2016-09-24 06:00:00,8946.0 -2016-09-24 07:00:00,9191.0 -2016-09-24 08:00:00,9579.0 -2016-09-24 09:00:00,9869.0 -2016-09-24 10:00:00,10363.0 -2016-09-24 11:00:00,10786.0 -2016-09-24 12:00:00,11071.0 -2016-09-24 13:00:00,11318.0 -2016-09-24 14:00:00,11555.0 -2016-09-24 15:00:00,11810.0 -2016-09-24 16:00:00,12096.0 -2016-09-24 17:00:00,12286.0 -2016-09-24 18:00:00,12219.0 -2016-09-24 19:00:00,11917.0 -2016-09-24 20:00:00,11647.0 -2016-09-24 21:00:00,11871.0 -2016-09-24 22:00:00,11645.0 -2016-09-24 23:00:00,11256.0 -2016-09-25 00:00:00,10623.0 -2016-09-23 01:00:00,11473.0 -2016-09-23 02:00:00,10653.0 -2016-09-23 03:00:00,10096.0 -2016-09-23 04:00:00,9707.0 -2016-09-23 05:00:00,9564.0 -2016-09-23 06:00:00,9719.0 -2016-09-23 07:00:00,10437.0 -2016-09-23 08:00:00,11616.0 -2016-09-23 09:00:00,12269.0 -2016-09-23 10:00:00,12699.0 -2016-09-23 11:00:00,13082.0 -2016-09-23 12:00:00,13300.0 -2016-09-23 13:00:00,13423.0 -2016-09-23 14:00:00,13593.0 -2016-09-23 15:00:00,13842.0 -2016-09-23 16:00:00,13900.0 -2016-09-23 17:00:00,13846.0 -2016-09-23 18:00:00,13664.0 -2016-09-23 19:00:00,13312.0 -2016-09-23 20:00:00,13039.0 -2016-09-23 21:00:00,13187.0 -2016-09-23 22:00:00,12849.0 -2016-09-23 23:00:00,12343.0 -2016-09-24 00:00:00,11533.0 -2016-09-22 01:00:00,10484.0 -2016-09-22 02:00:00,9818.0 -2016-09-22 03:00:00,9410.0 -2016-09-22 04:00:00,9181.0 -2016-09-22 05:00:00,9068.0 -2016-09-22 06:00:00,9298.0 -2016-09-22 07:00:00,9977.0 -2016-09-22 08:00:00,11058.0 -2016-09-22 09:00:00,11691.0 -2016-09-22 10:00:00,12296.0 -2016-09-22 11:00:00,12750.0 -2016-09-22 12:00:00,13229.0 -2016-09-22 13:00:00,13513.0 -2016-09-22 14:00:00,13744.0 -2016-09-22 15:00:00,14107.0 -2016-09-22 16:00:00,14443.0 -2016-09-22 17:00:00,14588.0 -2016-09-22 18:00:00,14705.0 -2016-09-22 19:00:00,14565.0 -2016-09-22 20:00:00,14499.0 -2016-09-22 21:00:00,14739.0 -2016-09-22 22:00:00,14362.0 -2016-09-22 23:00:00,13620.0 -2016-09-23 00:00:00,12519.0 -2016-09-21 01:00:00,10983.0 -2016-09-21 02:00:00,10315.0 -2016-09-21 03:00:00,9875.0 -2016-09-21 04:00:00,9610.0 -2016-09-21 05:00:00,9482.0 -2016-09-21 06:00:00,9686.0 -2016-09-21 07:00:00,10396.0 -2016-09-21 08:00:00,11581.0 -2016-09-21 09:00:00,12505.0 -2016-09-21 10:00:00,12791.0 -2016-09-21 11:00:00,12875.0 -2016-09-21 12:00:00,13413.0 -2016-09-21 13:00:00,13691.0 -2016-09-21 14:00:00,13685.0 -2016-09-21 15:00:00,13617.0 -2016-09-21 16:00:00,13444.0 -2016-09-21 17:00:00,13201.0 -2016-09-21 18:00:00,13191.0 -2016-09-21 19:00:00,13281.0 -2016-09-21 20:00:00,13155.0 -2016-09-21 21:00:00,13403.0 -2016-09-21 22:00:00,13036.0 -2016-09-21 23:00:00,12364.0 -2016-09-22 00:00:00,11415.0 -2016-09-20 01:00:00,11399.0 -2016-09-20 02:00:00,10608.0 -2016-09-20 03:00:00,10105.0 -2016-09-20 04:00:00,9762.0 -2016-09-20 05:00:00,9609.0 -2016-09-20 06:00:00,9726.0 -2016-09-20 07:00:00,10451.0 -2016-09-20 08:00:00,11556.0 -2016-09-20 09:00:00,12228.0 -2016-09-20 10:00:00,13004.0 -2016-09-20 11:00:00,13789.0 -2016-09-20 12:00:00,14519.0 -2016-09-20 13:00:00,15038.0 -2016-09-20 14:00:00,15445.0 -2016-09-20 15:00:00,15860.0 -2016-09-20 16:00:00,16127.0 -2016-09-20 17:00:00,16276.0 -2016-09-20 18:00:00,16189.0 -2016-09-20 19:00:00,15713.0 -2016-09-20 20:00:00,14787.0 -2016-09-20 21:00:00,14511.0 -2016-09-20 22:00:00,13912.0 -2016-09-20 23:00:00,12993.0 -2016-09-21 00:00:00,11955.0 -2016-09-19 01:00:00,10050.0 -2016-09-19 02:00:00,9409.0 -2016-09-19 03:00:00,9023.0 -2016-09-19 04:00:00,8756.0 -2016-09-19 05:00:00,8684.0 -2016-09-19 06:00:00,8853.0 -2016-09-19 07:00:00,9573.0 -2016-09-19 08:00:00,10512.0 -2016-09-19 09:00:00,11264.0 -2016-09-19 10:00:00,11990.0 -2016-09-19 11:00:00,12693.0 -2016-09-19 12:00:00,13457.0 -2016-09-19 13:00:00,14147.0 -2016-09-19 14:00:00,14747.0 -2016-09-19 15:00:00,15436.0 -2016-09-19 16:00:00,15930.0 -2016-09-19 17:00:00,16299.0 -2016-09-19 18:00:00,16430.0 -2016-09-19 19:00:00,15931.0 -2016-09-19 20:00:00,15346.0 -2016-09-19 21:00:00,15283.0 -2016-09-19 22:00:00,14697.0 -2016-09-19 23:00:00,13736.0 -2016-09-20 00:00:00,12559.0 -2016-09-18 01:00:00,10106.0 -2016-09-18 02:00:00,9388.0 -2016-09-18 03:00:00,8940.0 -2016-09-18 04:00:00,8578.0 -2016-09-18 05:00:00,8404.0 -2016-09-18 06:00:00,8268.0 -2016-09-18 07:00:00,8331.0 -2016-09-18 08:00:00,8368.0 -2016-09-18 09:00:00,8516.0 -2016-09-18 10:00:00,9155.0 -2016-09-18 11:00:00,9820.0 -2016-09-18 12:00:00,10450.0 -2016-09-18 13:00:00,10965.0 -2016-09-18 14:00:00,11467.0 -2016-09-18 15:00:00,11929.0 -2016-09-18 16:00:00,12428.0 -2016-09-18 17:00:00,12792.0 -2016-09-18 18:00:00,13078.0 -2016-09-18 19:00:00,13014.0 -2016-09-18 20:00:00,12644.0 -2016-09-18 21:00:00,12736.0 -2016-09-18 22:00:00,12387.0 -2016-09-18 23:00:00,11770.0 -2016-09-19 00:00:00,10909.0 -2016-09-17 01:00:00,11940.0 -2016-09-17 02:00:00,11000.0 -2016-09-17 03:00:00,10405.0 -2016-09-17 04:00:00,9919.0 -2016-09-17 05:00:00,9613.0 -2016-09-17 06:00:00,9506.0 -2016-09-17 07:00:00,9694.0 -2016-09-17 08:00:00,10046.0 -2016-09-17 09:00:00,10348.0 -2016-09-17 10:00:00,10991.0 -2016-09-17 11:00:00,11793.0 -2016-09-17 12:00:00,12425.0 -2016-09-17 13:00:00,12962.0 -2016-09-17 14:00:00,13378.0 -2016-09-17 15:00:00,13647.0 -2016-09-17 16:00:00,13849.0 -2016-09-17 17:00:00,13922.0 -2016-09-17 18:00:00,13874.0 -2016-09-17 19:00:00,13428.0 -2016-09-17 20:00:00,12751.0 -2016-09-17 21:00:00,12626.0 -2016-09-17 22:00:00,12184.0 -2016-09-17 23:00:00,11611.0 -2016-09-18 00:00:00,10841.0 -2016-09-16 01:00:00,10564.0 -2016-09-16 02:00:00,9852.0 -2016-09-16 03:00:00,9370.0 -2016-09-16 04:00:00,9073.0 -2016-09-16 05:00:00,8935.0 -2016-09-16 06:00:00,9024.0 -2016-09-16 07:00:00,9731.0 -2016-09-16 08:00:00,10800.0 -2016-09-16 09:00:00,11427.0 -2016-09-16 10:00:00,12022.0 -2016-09-16 11:00:00,12608.0 -2016-09-16 12:00:00,13476.0 -2016-09-16 13:00:00,14266.0 -2016-09-16 14:00:00,14500.0 -2016-09-16 15:00:00,14721.0 -2016-09-16 16:00:00,15273.0 -2016-09-16 17:00:00,15660.0 -2016-09-16 18:00:00,15647.0 -2016-09-16 19:00:00,15238.0 -2016-09-16 20:00:00,14729.0 -2016-09-16 21:00:00,14697.0 -2016-09-16 22:00:00,14335.0 -2016-09-16 23:00:00,13761.0 -2016-09-17 00:00:00,12866.0 -2016-09-15 01:00:00,10192.0 -2016-09-15 02:00:00,9537.0 -2016-09-15 03:00:00,9161.0 -2016-09-15 04:00:00,8908.0 -2016-09-15 05:00:00,8772.0 -2016-09-15 06:00:00,8964.0 -2016-09-15 07:00:00,9583.0 -2016-09-15 08:00:00,10523.0 -2016-09-15 09:00:00,11152.0 -2016-09-15 10:00:00,11750.0 -2016-09-15 11:00:00,12209.0 -2016-09-15 12:00:00,12638.0 -2016-09-15 13:00:00,12991.0 -2016-09-15 14:00:00,13396.0 -2016-09-15 15:00:00,13821.0 -2016-09-15 16:00:00,14096.0 -2016-09-15 17:00:00,14330.0 -2016-09-15 18:00:00,14388.0 -2016-09-15 19:00:00,14040.0 -2016-09-15 20:00:00,13398.0 -2016-09-15 21:00:00,13469.0 -2016-09-15 22:00:00,13168.0 -2016-09-15 23:00:00,12487.0 -2016-09-16 00:00:00,11476.0 -2016-09-14 01:00:00,11502.0 -2016-09-14 02:00:00,10685.0 -2016-09-14 03:00:00,10111.0 -2016-09-14 04:00:00,9747.0 -2016-09-14 05:00:00,9600.0 -2016-09-14 06:00:00,9672.0 -2016-09-14 07:00:00,10297.0 -2016-09-14 08:00:00,11358.0 -2016-09-14 09:00:00,11886.0 -2016-09-14 10:00:00,12258.0 -2016-09-14 11:00:00,12754.0 -2016-09-14 12:00:00,13177.0 -2016-09-14 13:00:00,13479.0 -2016-09-14 14:00:00,13689.0 -2016-09-14 15:00:00,13828.0 -2016-09-14 16:00:00,13844.0 -2016-09-14 17:00:00,13989.0 -2016-09-14 18:00:00,13900.0 -2016-09-14 19:00:00,13485.0 -2016-09-14 20:00:00,12842.0 -2016-09-14 21:00:00,12963.0 -2016-09-14 22:00:00,12698.0 -2016-09-14 23:00:00,11994.0 -2016-09-15 00:00:00,11087.0 -2016-09-13 01:00:00,10406.0 -2016-09-13 02:00:00,9787.0 -2016-09-13 03:00:00,9353.0 -2016-09-13 04:00:00,9060.0 -2016-09-13 05:00:00,8971.0 -2016-09-13 06:00:00,9091.0 -2016-09-13 07:00:00,9786.0 -2016-09-13 08:00:00,10724.0 -2016-09-13 09:00:00,11406.0 -2016-09-13 10:00:00,12171.0 -2016-09-13 11:00:00,12868.0 -2016-09-13 12:00:00,13556.0 -2016-09-13 13:00:00,14180.0 -2016-09-13 14:00:00,14873.0 -2016-09-13 15:00:00,15555.0 -2016-09-13 16:00:00,15941.0 -2016-09-13 17:00:00,16156.0 -2016-09-13 18:00:00,16143.0 -2016-09-13 19:00:00,15690.0 -2016-09-13 20:00:00,15044.0 -2016-09-13 21:00:00,15052.0 -2016-09-13 22:00:00,14679.0 -2016-09-13 23:00:00,13840.0 -2016-09-14 00:00:00,12677.0 -2016-09-12 01:00:00,9320.0 -2016-09-12 02:00:00,8847.0 -2016-09-12 03:00:00,8500.0 -2016-09-12 04:00:00,8383.0 -2016-09-12 05:00:00,8252.0 -2016-09-12 06:00:00,8513.0 -2016-09-12 07:00:00,9183.0 -2016-09-12 08:00:00,10128.0 -2016-09-12 09:00:00,10798.0 -2016-09-12 10:00:00,11494.0 -2016-09-12 11:00:00,12031.0 -2016-09-12 12:00:00,12581.0 -2016-09-12 13:00:00,12907.0 -2016-09-12 14:00:00,13254.0 -2016-09-12 15:00:00,13616.0 -2016-09-12 16:00:00,13899.0 -2016-09-12 17:00:00,14151.0 -2016-09-12 18:00:00,14227.0 -2016-09-12 19:00:00,13922.0 -2016-09-12 20:00:00,13347.0 -2016-09-12 21:00:00,13368.0 -2016-09-12 22:00:00,13071.0 -2016-09-12 23:00:00,12328.0 -2016-09-13 00:00:00,11360.0 -2016-09-11 01:00:00,9272.0 -2016-09-11 02:00:00,8723.0 -2016-09-11 03:00:00,8302.0 -2016-09-11 04:00:00,8115.0 -2016-09-11 05:00:00,7953.0 -2016-09-11 06:00:00,7850.0 -2016-09-11 07:00:00,7871.0 -2016-09-11 08:00:00,8009.0 -2016-09-11 09:00:00,8204.0 -2016-09-11 10:00:00,8707.0 -2016-09-11 11:00:00,9225.0 -2016-09-11 12:00:00,9787.0 -2016-09-11 13:00:00,10081.0 -2016-09-11 14:00:00,10414.0 -2016-09-11 15:00:00,10711.0 -2016-09-11 16:00:00,10965.0 -2016-09-11 17:00:00,11193.0 -2016-09-11 18:00:00,11341.0 -2016-09-11 19:00:00,11393.0 -2016-09-11 20:00:00,11170.0 -2016-09-11 21:00:00,11241.0 -2016-09-11 22:00:00,11140.0 -2016-09-11 23:00:00,10667.0 -2016-09-12 00:00:00,9900.0 -2016-09-10 01:00:00,11386.0 -2016-09-10 02:00:00,10654.0 -2016-09-10 03:00:00,10075.0 -2016-09-10 04:00:00,9700.0 -2016-09-10 05:00:00,9505.0 -2016-09-10 06:00:00,9467.0 -2016-09-10 07:00:00,9713.0 -2016-09-10 08:00:00,10068.0 -2016-09-10 09:00:00,10471.0 -2016-09-10 10:00:00,11081.0 -2016-09-10 11:00:00,11500.0 -2016-09-10 12:00:00,11714.0 -2016-09-10 13:00:00,11628.0 -2016-09-10 14:00:00,11636.0 -2016-09-10 15:00:00,11690.0 -2016-09-10 16:00:00,11685.0 -2016-09-10 17:00:00,11686.0 -2016-09-10 18:00:00,11712.0 -2016-09-10 19:00:00,11455.0 -2016-09-10 20:00:00,11100.0 -2016-09-10 21:00:00,11118.0 -2016-09-10 22:00:00,11039.0 -2016-09-10 23:00:00,10566.0 -2016-09-11 00:00:00,9974.0 -2016-09-09 01:00:00,12475.0 -2016-09-09 02:00:00,11505.0 -2016-09-09 03:00:00,10809.0 -2016-09-09 04:00:00,10355.0 -2016-09-09 05:00:00,10086.0 -2016-09-09 06:00:00,10169.0 -2016-09-09 07:00:00,10797.0 -2016-09-09 08:00:00,11728.0 -2016-09-09 09:00:00,12451.0 -2016-09-09 10:00:00,13125.0 -2016-09-09 11:00:00,13653.0 -2016-09-09 12:00:00,14144.0 -2016-09-09 13:00:00,14598.0 -2016-09-09 14:00:00,14945.0 -2016-09-09 15:00:00,15372.0 -2016-09-09 16:00:00,15725.0 -2016-09-09 17:00:00,15827.0 -2016-09-09 18:00:00,15571.0 -2016-09-09 19:00:00,15022.0 -2016-09-09 20:00:00,14283.0 -2016-09-09 21:00:00,14051.0 -2016-09-09 22:00:00,13757.0 -2016-09-09 23:00:00,13191.0 -2016-09-10 00:00:00,12354.0 -2016-09-08 01:00:00,13940.0 -2016-09-08 02:00:00,13007.0 -2016-09-08 03:00:00,12343.0 -2016-09-08 04:00:00,11903.0 -2016-09-08 05:00:00,11686.0 -2016-09-08 06:00:00,11778.0 -2016-09-08 07:00:00,12410.0 -2016-09-08 08:00:00,13376.0 -2016-09-08 09:00:00,13948.0 -2016-09-08 10:00:00,14290.0 -2016-09-08 11:00:00,14569.0 -2016-09-08 12:00:00,14808.0 -2016-09-08 13:00:00,15090.0 -2016-09-08 14:00:00,15532.0 -2016-09-08 15:00:00,16140.0 -2016-09-08 16:00:00,16453.0 -2016-09-08 17:00:00,16713.0 -2016-09-08 18:00:00,16996.0 -2016-09-08 19:00:00,17092.0 -2016-09-08 20:00:00,16544.0 -2016-09-08 21:00:00,16317.0 -2016-09-08 22:00:00,15992.0 -2016-09-08 23:00:00,15014.0 -2016-09-09 00:00:00,13763.0 -2016-09-07 01:00:00,15205.0 -2016-09-07 02:00:00,14212.0 -2016-09-07 03:00:00,13418.0 -2016-09-07 04:00:00,12871.0 -2016-09-07 05:00:00,12580.0 -2016-09-07 06:00:00,12597.0 -2016-09-07 07:00:00,13293.0 -2016-09-07 08:00:00,14314.0 -2016-09-07 09:00:00,15212.0 -2016-09-07 10:00:00,16061.0 -2016-09-07 11:00:00,16910.0 -2016-09-07 12:00:00,17731.0 -2016-09-07 13:00:00,18517.0 -2016-09-07 14:00:00,18693.0 -2016-09-07 15:00:00,18968.0 -2016-09-07 16:00:00,19653.0 -2016-09-07 17:00:00,20153.0 -2016-09-07 18:00:00,20014.0 -2016-09-07 19:00:00,19336.0 -2016-09-07 20:00:00,18812.0 -2016-09-07 21:00:00,18493.0 -2016-09-07 22:00:00,17688.0 -2016-09-07 23:00:00,16568.0 -2016-09-08 00:00:00,15227.0 -2016-09-06 01:00:00,12826.0 -2016-09-06 02:00:00,12023.0 -2016-09-06 03:00:00,11518.0 -2016-09-06 04:00:00,11118.0 -2016-09-06 05:00:00,10945.0 -2016-09-06 06:00:00,11133.0 -2016-09-06 07:00:00,11893.0 -2016-09-06 08:00:00,13070.0 -2016-09-06 09:00:00,14196.0 -2016-09-06 10:00:00,15374.0 -2016-09-06 11:00:00,16582.0 -2016-09-06 12:00:00,17838.0 -2016-09-06 13:00:00,18906.0 -2016-09-06 14:00:00,19808.0 -2016-09-06 15:00:00,20319.0 -2016-09-06 16:00:00,20612.0 -2016-09-06 17:00:00,20927.0 -2016-09-06 18:00:00,20949.0 -2016-09-06 19:00:00,20740.0 -2016-09-06 20:00:00,20061.0 -2016-09-06 21:00:00,19686.0 -2016-09-06 22:00:00,19193.0 -2016-09-06 23:00:00,18102.0 -2016-09-07 00:00:00,16651.0 -2016-09-05 01:00:00,9957.0 -2016-09-05 02:00:00,9353.0 -2016-09-05 03:00:00,8920.0 -2016-09-05 04:00:00,8617.0 -2016-09-05 05:00:00,8496.0 -2016-09-05 06:00:00,8432.0 -2016-09-05 07:00:00,8592.0 -2016-09-05 08:00:00,8619.0 -2016-09-05 09:00:00,8927.0 -2016-09-05 10:00:00,9647.0 -2016-09-05 11:00:00,10693.0 -2016-09-05 12:00:00,11930.0 -2016-09-05 13:00:00,13134.0 -2016-09-05 14:00:00,14166.0 -2016-09-05 15:00:00,14921.0 -2016-09-05 16:00:00,15535.0 -2016-09-05 17:00:00,15993.0 -2016-09-05 18:00:00,16295.0 -2016-09-05 19:00:00,16350.0 -2016-09-05 20:00:00,15958.0 -2016-09-05 21:00:00,15800.0 -2016-09-05 22:00:00,15643.0 -2016-09-05 23:00:00,14939.0 -2016-09-06 00:00:00,13829.0 -2016-09-04 01:00:00,9723.0 -2016-09-04 02:00:00,9151.0 -2016-09-04 03:00:00,8723.0 -2016-09-04 04:00:00,8459.0 -2016-09-04 05:00:00,8224.0 -2016-09-04 06:00:00,8160.0 -2016-09-04 07:00:00,8128.0 -2016-09-04 08:00:00,8074.0 -2016-09-04 09:00:00,8290.0 -2016-09-04 10:00:00,8935.0 -2016-09-04 11:00:00,9611.0 -2016-09-04 12:00:00,10388.0 -2016-09-04 13:00:00,10936.0 -2016-09-04 14:00:00,11172.0 -2016-09-04 15:00:00,11597.0 -2016-09-04 16:00:00,11959.0 -2016-09-04 17:00:00,12126.0 -2016-09-04 18:00:00,12017.0 -2016-09-04 19:00:00,11823.0 -2016-09-04 20:00:00,11493.0 -2016-09-04 21:00:00,11525.0 -2016-09-04 22:00:00,11536.0 -2016-09-04 23:00:00,11187.0 -2016-09-05 00:00:00,10597.0 -2016-09-03 01:00:00,10109.0 -2016-09-03 02:00:00,9454.0 -2016-09-03 03:00:00,9038.0 -2016-09-03 04:00:00,8718.0 -2016-09-03 05:00:00,8525.0 -2016-09-03 06:00:00,8470.0 -2016-09-03 07:00:00,8588.0 -2016-09-03 08:00:00,8646.0 -2016-09-03 09:00:00,8994.0 -2016-09-03 10:00:00,9665.0 -2016-09-03 11:00:00,10276.0 -2016-09-03 12:00:00,10734.0 -2016-09-03 13:00:00,11134.0 -2016-09-03 14:00:00,11401.0 -2016-09-03 15:00:00,11613.0 -2016-09-03 16:00:00,11834.0 -2016-09-03 17:00:00,12029.0 -2016-09-03 18:00:00,12109.0 -2016-09-03 19:00:00,11946.0 -2016-09-03 20:00:00,11466.0 -2016-09-03 21:00:00,11324.0 -2016-09-03 22:00:00,11312.0 -2016-09-03 23:00:00,10930.0 -2016-09-04 00:00:00,10349.0 -2016-09-02 01:00:00,10157.0 -2016-09-02 02:00:00,9541.0 -2016-09-02 03:00:00,9125.0 -2016-09-02 04:00:00,8883.0 -2016-09-02 05:00:00,8772.0 -2016-09-02 06:00:00,8887.0 -2016-09-02 07:00:00,9498.0 -2016-09-02 08:00:00,10254.0 -2016-09-02 09:00:00,11006.0 -2016-09-02 10:00:00,11674.0 -2016-09-02 11:00:00,12172.0 -2016-09-02 12:00:00,12544.0 -2016-09-02 13:00:00,12800.0 -2016-09-02 14:00:00,12977.0 -2016-09-02 15:00:00,13146.0 -2016-09-02 16:00:00,13281.0 -2016-09-02 17:00:00,13311.0 -2016-09-02 18:00:00,13292.0 -2016-09-02 19:00:00,13009.0 -2016-09-02 20:00:00,12411.0 -2016-09-02 21:00:00,12187.0 -2016-09-02 22:00:00,12074.0 -2016-09-02 23:00:00,11645.0 -2016-09-03 00:00:00,10883.0 -2016-09-01 01:00:00,10633.0 -2016-09-01 02:00:00,9937.0 -2016-09-01 03:00:00,9471.0 -2016-09-01 04:00:00,9158.0 -2016-09-01 05:00:00,9160.0 -2016-09-01 06:00:00,9295.0 -2016-09-01 07:00:00,9902.0 -2016-09-01 08:00:00,10831.0 -2016-09-01 09:00:00,11501.0 -2016-09-01 10:00:00,12071.0 -2016-09-01 11:00:00,12480.0 -2016-09-01 12:00:00,12877.0 -2016-09-01 13:00:00,13044.0 -2016-09-01 14:00:00,13096.0 -2016-09-01 15:00:00,13188.0 -2016-09-01 16:00:00,13256.0 -2016-09-01 17:00:00,13161.0 -2016-09-01 18:00:00,13087.0 -2016-09-01 19:00:00,12865.0 -2016-09-01 20:00:00,12361.0 -2016-09-01 21:00:00,12298.0 -2016-09-01 22:00:00,12340.0 -2016-09-01 23:00:00,11827.0 -2016-09-02 00:00:00,10977.0 -2016-08-31 01:00:00,12473.0 -2016-08-31 02:00:00,11616.0 -2016-08-31 03:00:00,10971.0 -2016-08-31 04:00:00,10593.0 -2016-08-31 05:00:00,10362.0 -2016-08-31 06:00:00,10475.0 -2016-08-31 07:00:00,11189.0 -2016-08-31 08:00:00,12109.0 -2016-08-31 09:00:00,12756.0 -2016-08-31 10:00:00,13373.0 -2016-08-31 11:00:00,14074.0 -2016-08-31 12:00:00,14761.0 -2016-08-31 13:00:00,15198.0 -2016-08-31 14:00:00,15416.0 -2016-08-31 15:00:00,15650.0 -2016-08-31 16:00:00,15769.0 -2016-08-31 17:00:00,15720.0 -2016-08-31 18:00:00,15481.0 -2016-08-31 19:00:00,14858.0 -2016-08-31 20:00:00,13944.0 -2016-08-31 21:00:00,13500.0 -2016-08-31 22:00:00,13311.0 -2016-08-31 23:00:00,12583.0 -2016-09-01 00:00:00,11593.0 -2016-08-30 01:00:00,12647.0 -2016-08-30 02:00:00,11815.0 -2016-08-30 03:00:00,11208.0 -2016-08-30 04:00:00,10805.0 -2016-08-30 05:00:00,10620.0 -2016-08-30 06:00:00,10795.0 -2016-08-30 07:00:00,11584.0 -2016-08-30 08:00:00,12812.0 -2016-08-30 09:00:00,13540.0 -2016-08-30 10:00:00,13879.0 -2016-08-30 11:00:00,14397.0 -2016-08-30 12:00:00,15188.0 -2016-08-30 13:00:00,15727.0 -2016-08-30 14:00:00,16486.0 -2016-08-30 15:00:00,17380.0 -2016-08-30 16:00:00,17923.0 -2016-08-30 17:00:00,18180.0 -2016-08-30 18:00:00,17992.0 -2016-08-30 19:00:00,17216.0 -2016-08-30 20:00:00,16438.0 -2016-08-30 21:00:00,16054.0 -2016-08-30 22:00:00,15833.0 -2016-08-30 23:00:00,14924.0 -2016-08-31 00:00:00,13660.0 -2016-08-29 01:00:00,11732.0 -2016-08-29 02:00:00,11015.0 -2016-08-29 03:00:00,10471.0 -2016-08-29 04:00:00,10151.0 -2016-08-29 05:00:00,9990.0 -2016-08-29 06:00:00,10233.0 -2016-08-29 07:00:00,11000.0 -2016-08-29 08:00:00,12012.0 -2016-08-29 09:00:00,13159.0 -2016-08-29 10:00:00,14267.0 -2016-08-29 11:00:00,15253.0 -2016-08-29 12:00:00,16328.0 -2016-08-29 13:00:00,17206.0 -2016-08-29 14:00:00,17945.0 -2016-08-29 15:00:00,18517.0 -2016-08-29 16:00:00,18466.0 -2016-08-29 17:00:00,17880.0 -2016-08-29 18:00:00,17259.0 -2016-08-29 19:00:00,16687.0 -2016-08-29 20:00:00,16271.0 -2016-08-29 21:00:00,16122.0 -2016-08-29 22:00:00,15814.0 -2016-08-29 23:00:00,15004.0 -2016-08-30 00:00:00,13786.0 -2016-08-28 01:00:00,12261.0 -2016-08-28 02:00:00,11464.0 -2016-08-28 03:00:00,10833.0 -2016-08-28 04:00:00,10368.0 -2016-08-28 05:00:00,10046.0 -2016-08-28 06:00:00,9876.0 -2016-08-28 07:00:00,9821.0 -2016-08-28 08:00:00,9798.0 -2016-08-28 09:00:00,10222.0 -2016-08-28 10:00:00,11320.0 -2016-08-28 11:00:00,12522.0 -2016-08-28 12:00:00,13669.0 -2016-08-28 13:00:00,14595.0 -2016-08-28 14:00:00,15231.0 -2016-08-28 15:00:00,15599.0 -2016-08-28 16:00:00,15464.0 -2016-08-28 17:00:00,15296.0 -2016-08-28 18:00:00,15161.0 -2016-08-28 19:00:00,14725.0 -2016-08-28 20:00:00,14290.0 -2016-08-28 21:00:00,14183.0 -2016-08-28 22:00:00,14153.0 -2016-08-28 23:00:00,13513.0 -2016-08-29 00:00:00,12674.0 -2016-08-27 01:00:00,11614.0 -2016-08-27 02:00:00,10904.0 -2016-08-27 03:00:00,10303.0 -2016-08-27 04:00:00,9915.0 -2016-08-27 05:00:00,9707.0 -2016-08-27 06:00:00,9702.0 -2016-08-27 07:00:00,9982.0 -2016-08-27 08:00:00,10421.0 -2016-08-27 09:00:00,10806.0 -2016-08-27 10:00:00,11371.0 -2016-08-27 11:00:00,11919.0 -2016-08-27 12:00:00,12633.0 -2016-08-27 13:00:00,13112.0 -2016-08-27 14:00:00,13692.0 -2016-08-27 15:00:00,14301.0 -2016-08-27 16:00:00,14900.0 -2016-08-27 17:00:00,15315.0 -2016-08-27 18:00:00,15468.0 -2016-08-27 19:00:00,15291.0 -2016-08-27 20:00:00,14742.0 -2016-08-27 21:00:00,14551.0 -2016-08-27 22:00:00,14504.0 -2016-08-27 23:00:00,13976.0 -2016-08-28 00:00:00,13158.0 -2016-08-26 01:00:00,11815.0 -2016-08-26 02:00:00,10937.0 -2016-08-26 03:00:00,10342.0 -2016-08-26 04:00:00,9923.0 -2016-08-26 05:00:00,9696.0 -2016-08-26 06:00:00,9807.0 -2016-08-26 07:00:00,10386.0 -2016-08-26 08:00:00,11218.0 -2016-08-26 09:00:00,12035.0 -2016-08-26 10:00:00,12817.0 -2016-08-26 11:00:00,13423.0 -2016-08-26 12:00:00,14191.0 -2016-08-26 13:00:00,14801.0 -2016-08-26 14:00:00,15142.0 -2016-08-26 15:00:00,15709.0 -2016-08-26 16:00:00,16072.0 -2016-08-26 17:00:00,16038.0 -2016-08-26 18:00:00,15948.0 -2016-08-26 19:00:00,15429.0 -2016-08-26 20:00:00,14667.0 -2016-08-26 21:00:00,14177.0 -2016-08-26 22:00:00,13974.0 -2016-08-26 23:00:00,13451.0 -2016-08-27 00:00:00,12618.0 -2016-08-25 01:00:00,13200.0 -2016-08-25 02:00:00,12278.0 -2016-08-25 03:00:00,11598.0 -2016-08-25 04:00:00,11131.0 -2016-08-25 05:00:00,10903.0 -2016-08-25 06:00:00,10946.0 -2016-08-25 07:00:00,11584.0 -2016-08-25 08:00:00,12563.0 -2016-08-25 09:00:00,13364.0 -2016-08-25 10:00:00,13915.0 -2016-08-25 11:00:00,14375.0 -2016-08-25 12:00:00,14930.0 -2016-08-25 13:00:00,15458.0 -2016-08-25 14:00:00,16133.0 -2016-08-25 15:00:00,16825.0 -2016-08-25 16:00:00,17308.0 -2016-08-25 17:00:00,17415.0 -2016-08-25 18:00:00,17036.0 -2016-08-25 19:00:00,16367.0 -2016-08-25 20:00:00,15740.0 -2016-08-25 21:00:00,15352.0 -2016-08-25 22:00:00,15171.0 -2016-08-25 23:00:00,14272.0 -2016-08-26 00:00:00,13059.0 -2016-08-24 01:00:00,12043.0 -2016-08-24 02:00:00,11286.0 -2016-08-24 03:00:00,10721.0 -2016-08-24 04:00:00,10386.0 -2016-08-24 05:00:00,10216.0 -2016-08-24 06:00:00,10344.0 -2016-08-24 07:00:00,11058.0 -2016-08-24 08:00:00,12034.0 -2016-08-24 09:00:00,13025.0 -2016-08-24 10:00:00,13624.0 -2016-08-24 11:00:00,13850.0 -2016-08-24 12:00:00,14262.0 -2016-08-24 13:00:00,14554.0 -2016-08-24 14:00:00,15161.0 -2016-08-24 15:00:00,15625.0 -2016-08-24 16:00:00,16209.0 -2016-08-24 17:00:00,16888.0 -2016-08-24 18:00:00,17458.0 -2016-08-24 19:00:00,17623.0 -2016-08-24 20:00:00,17194.0 -2016-08-24 21:00:00,16729.0 -2016-08-24 22:00:00,16585.0 -2016-08-24 23:00:00,15752.0 -2016-08-25 00:00:00,14444.0 -2016-08-23 01:00:00,10782.0 -2016-08-23 02:00:00,10088.0 -2016-08-23 03:00:00,9565.0 -2016-08-23 04:00:00,9218.0 -2016-08-23 05:00:00,9099.0 -2016-08-23 06:00:00,9242.0 -2016-08-23 07:00:00,9867.0 -2016-08-23 08:00:00,10618.0 -2016-08-23 09:00:00,11518.0 -2016-08-23 10:00:00,12328.0 -2016-08-23 11:00:00,13051.0 -2016-08-23 12:00:00,13812.0 -2016-08-23 13:00:00,14330.0 -2016-08-23 14:00:00,14784.0 -2016-08-23 15:00:00,15216.0 -2016-08-23 16:00:00,15595.0 -2016-08-23 17:00:00,15925.0 -2016-08-23 18:00:00,16005.0 -2016-08-23 19:00:00,15632.0 -2016-08-23 20:00:00,14992.0 -2016-08-23 21:00:00,14700.0 -2016-08-23 22:00:00,14694.0 -2016-08-23 23:00:00,14038.0 -2016-08-24 00:00:00,13071.0 -2016-08-22 01:00:00,9535.0 -2016-08-22 02:00:00,8997.0 -2016-08-22 03:00:00,8576.0 -2016-08-22 04:00:00,8421.0 -2016-08-22 05:00:00,8392.0 -2016-08-22 06:00:00,8586.0 -2016-08-22 07:00:00,9203.0 -2016-08-22 08:00:00,10004.0 -2016-08-22 09:00:00,10877.0 -2016-08-22 10:00:00,11636.0 -2016-08-22 11:00:00,12211.0 -2016-08-22 12:00:00,12710.0 -2016-08-22 13:00:00,13095.0 -2016-08-22 14:00:00,13401.0 -2016-08-22 15:00:00,13814.0 -2016-08-22 16:00:00,14145.0 -2016-08-22 17:00:00,14455.0 -2016-08-22 18:00:00,14625.0 -2016-08-22 19:00:00,14543.0 -2016-08-22 20:00:00,14062.0 -2016-08-22 21:00:00,13574.0 -2016-08-22 22:00:00,13510.0 -2016-08-22 23:00:00,12802.0 -2016-08-23 00:00:00,11768.0 -2016-08-21 01:00:00,10244.0 -2016-08-21 02:00:00,9561.0 -2016-08-21 03:00:00,9072.0 -2016-08-21 04:00:00,8708.0 -2016-08-21 05:00:00,8468.0 -2016-08-21 06:00:00,8377.0 -2016-08-21 07:00:00,8366.0 -2016-08-21 08:00:00,8300.0 -2016-08-21 09:00:00,8513.0 -2016-08-21 10:00:00,8889.0 -2016-08-21 11:00:00,9356.0 -2016-08-21 12:00:00,9815.0 -2016-08-21 13:00:00,10296.0 -2016-08-21 14:00:00,10628.0 -2016-08-21 15:00:00,10889.0 -2016-08-21 16:00:00,11152.0 -2016-08-21 17:00:00,11309.0 -2016-08-21 18:00:00,11426.0 -2016-08-21 19:00:00,11404.0 -2016-08-21 20:00:00,11298.0 -2016-08-21 21:00:00,11082.0 -2016-08-21 22:00:00,11282.0 -2016-08-21 23:00:00,10889.0 -2016-08-22 00:00:00,10178.0 -2016-08-20 01:00:00,13039.0 -2016-08-20 02:00:00,12096.0 -2016-08-20 03:00:00,11410.0 -2016-08-20 04:00:00,10937.0 -2016-08-20 05:00:00,10726.0 -2016-08-20 06:00:00,10662.0 -2016-08-20 07:00:00,10895.0 -2016-08-20 08:00:00,11144.0 -2016-08-20 09:00:00,11620.0 -2016-08-20 10:00:00,12260.0 -2016-08-20 11:00:00,12791.0 -2016-08-20 12:00:00,13237.0 -2016-08-20 13:00:00,13627.0 -2016-08-20 14:00:00,13981.0 -2016-08-20 15:00:00,14357.0 -2016-08-20 16:00:00,14471.0 -2016-08-20 17:00:00,14466.0 -2016-08-20 18:00:00,14126.0 -2016-08-20 19:00:00,13613.0 -2016-08-20 20:00:00,12968.0 -2016-08-20 21:00:00,12322.0 -2016-08-20 22:00:00,12256.0 -2016-08-20 23:00:00,11772.0 -2016-08-21 00:00:00,11079.0 -2016-08-19 01:00:00,13405.0 -2016-08-19 02:00:00,12369.0 -2016-08-19 03:00:00,11588.0 -2016-08-19 04:00:00,11060.0 -2016-08-19 05:00:00,10790.0 -2016-08-19 06:00:00,10825.0 -2016-08-19 07:00:00,11363.0 -2016-08-19 08:00:00,12176.0 -2016-08-19 09:00:00,12990.0 -2016-08-19 10:00:00,13782.0 -2016-08-19 11:00:00,14464.0 -2016-08-19 12:00:00,14588.0 -2016-08-19 13:00:00,14763.0 -2016-08-19 14:00:00,15393.0 -2016-08-19 15:00:00,16188.0 -2016-08-19 16:00:00,16946.0 -2016-08-19 17:00:00,17623.0 -2016-08-19 18:00:00,17925.0 -2016-08-19 19:00:00,17806.0 -2016-08-19 20:00:00,17106.0 -2016-08-19 21:00:00,16378.0 -2016-08-19 22:00:00,16110.0 -2016-08-19 23:00:00,15349.0 -2016-08-20 00:00:00,14180.0 -2016-08-18 01:00:00,13322.0 -2016-08-18 02:00:00,12308.0 -2016-08-18 03:00:00,11609.0 -2016-08-18 04:00:00,11143.0 -2016-08-18 05:00:00,10909.0 -2016-08-18 06:00:00,11040.0 -2016-08-18 07:00:00,11777.0 -2016-08-18 08:00:00,12771.0 -2016-08-18 09:00:00,13477.0 -2016-08-18 10:00:00,14058.0 -2016-08-18 11:00:00,15013.0 -2016-08-18 12:00:00,16106.0 -2016-08-18 13:00:00,17054.0 -2016-08-18 14:00:00,17943.0 -2016-08-18 15:00:00,18714.0 -2016-08-18 16:00:00,18902.0 -2016-08-18 17:00:00,19033.0 -2016-08-18 18:00:00,19170.0 -2016-08-18 19:00:00,18884.0 -2016-08-18 20:00:00,17984.0 -2016-08-18 21:00:00,17297.0 -2016-08-18 22:00:00,17043.0 -2016-08-18 23:00:00,16095.0 -2016-08-19 00:00:00,14720.0 -2016-08-17 01:00:00,13174.0 -2016-08-17 02:00:00,12156.0 -2016-08-17 03:00:00,11378.0 -2016-08-17 04:00:00,10880.0 -2016-08-17 05:00:00,10585.0 -2016-08-17 06:00:00,10710.0 -2016-08-17 07:00:00,11335.0 -2016-08-17 08:00:00,12194.0 -2016-08-17 09:00:00,13247.0 -2016-08-17 10:00:00,14227.0 -2016-08-17 11:00:00,15110.0 -2016-08-17 12:00:00,16170.0 -2016-08-17 13:00:00,17110.0 -2016-08-17 14:00:00,17883.0 -2016-08-17 15:00:00,18521.0 -2016-08-17 16:00:00,18871.0 -2016-08-17 17:00:00,19019.0 -2016-08-17 18:00:00,18899.0 -2016-08-17 19:00:00,18439.0 -2016-08-17 20:00:00,17746.0 -2016-08-17 21:00:00,16986.0 -2016-08-17 22:00:00,16853.0 -2016-08-17 23:00:00,15851.0 -2016-08-18 00:00:00,14679.0 -2016-08-16 01:00:00,12209.0 -2016-08-16 02:00:00,11476.0 -2016-08-16 03:00:00,10988.0 -2016-08-16 04:00:00,10656.0 -2016-08-16 05:00:00,10476.0 -2016-08-16 06:00:00,10652.0 -2016-08-16 07:00:00,11351.0 -2016-08-16 08:00:00,12165.0 -2016-08-16 09:00:00,13119.0 -2016-08-16 10:00:00,14014.0 -2016-08-16 11:00:00,14888.0 -2016-08-16 12:00:00,15919.0 -2016-08-16 13:00:00,16591.0 -2016-08-16 14:00:00,17215.0 -2016-08-16 15:00:00,17770.0 -2016-08-16 16:00:00,17919.0 -2016-08-16 17:00:00,18132.0 -2016-08-16 18:00:00,18413.0 -2016-08-16 19:00:00,18370.0 -2016-08-16 20:00:00,17696.0 -2016-08-16 21:00:00,16928.0 -2016-08-16 22:00:00,16613.0 -2016-08-16 23:00:00,15757.0 -2016-08-17 00:00:00,14478.0 -2016-08-15 01:00:00,12255.0 -2016-08-15 02:00:00,11431.0 -2016-08-15 03:00:00,10838.0 -2016-08-15 04:00:00,10474.0 -2016-08-15 05:00:00,10250.0 -2016-08-15 06:00:00,10452.0 -2016-08-15 07:00:00,11079.0 -2016-08-15 08:00:00,11869.0 -2016-08-15 09:00:00,12733.0 -2016-08-15 10:00:00,13542.0 -2016-08-15 11:00:00,14152.0 -2016-08-15 12:00:00,14761.0 -2016-08-15 13:00:00,15200.0 -2016-08-15 14:00:00,15483.0 -2016-08-15 15:00:00,15684.0 -2016-08-15 16:00:00,15734.0 -2016-08-15 17:00:00,15585.0 -2016-08-15 18:00:00,15405.0 -2016-08-15 19:00:00,15216.0 -2016-08-15 20:00:00,14838.0 -2016-08-15 21:00:00,14712.0 -2016-08-15 22:00:00,14726.0 -2016-08-15 23:00:00,14143.0 -2016-08-16 00:00:00,13231.0 -2016-08-14 01:00:00,12299.0 -2016-08-14 02:00:00,11422.0 -2016-08-14 03:00:00,10694.0 -2016-08-14 04:00:00,10176.0 -2016-08-14 05:00:00,9812.0 -2016-08-14 06:00:00,9642.0 -2016-08-14 07:00:00,9544.0 -2016-08-14 08:00:00,9484.0 -2016-08-14 09:00:00,10126.0 -2016-08-14 10:00:00,11206.0 -2016-08-14 11:00:00,12357.0 -2016-08-14 12:00:00,13366.0 -2016-08-14 13:00:00,14125.0 -2016-08-14 14:00:00,14725.0 -2016-08-14 15:00:00,15129.0 -2016-08-14 16:00:00,15542.0 -2016-08-14 17:00:00,15691.0 -2016-08-14 18:00:00,15852.0 -2016-08-14 19:00:00,15819.0 -2016-08-14 20:00:00,15514.0 -2016-08-14 21:00:00,14922.0 -2016-08-14 22:00:00,14829.0 -2016-08-14 23:00:00,14244.0 -2016-08-15 00:00:00,13331.0 -2016-08-13 01:00:00,13193.0 -2016-08-13 02:00:00,12319.0 -2016-08-13 03:00:00,11745.0 -2016-08-13 04:00:00,11430.0 -2016-08-13 05:00:00,11209.0 -2016-08-13 06:00:00,11197.0 -2016-08-13 07:00:00,11390.0 -2016-08-13 08:00:00,11652.0 -2016-08-13 09:00:00,12214.0 -2016-08-13 10:00:00,13111.0 -2016-08-13 11:00:00,14119.0 -2016-08-13 12:00:00,15035.0 -2016-08-13 13:00:00,15656.0 -2016-08-13 14:00:00,15947.0 -2016-08-13 15:00:00,15997.0 -2016-08-13 16:00:00,15941.0 -2016-08-13 17:00:00,15935.0 -2016-08-13 18:00:00,16162.0 -2016-08-13 19:00:00,16141.0 -2016-08-13 20:00:00,15702.0 -2016-08-13 21:00:00,15009.0 -2016-08-13 22:00:00,14826.0 -2016-08-13 23:00:00,14260.0 -2016-08-14 00:00:00,13340.0 -2016-08-12 01:00:00,15641.0 -2016-08-12 02:00:00,14665.0 -2016-08-12 03:00:00,13991.0 -2016-08-12 04:00:00,13521.0 -2016-08-12 05:00:00,13141.0 -2016-08-12 06:00:00,13060.0 -2016-08-12 07:00:00,13547.0 -2016-08-12 08:00:00,14194.0 -2016-08-12 09:00:00,14998.0 -2016-08-12 10:00:00,15704.0 -2016-08-12 11:00:00,16158.0 -2016-08-12 12:00:00,16509.0 -2016-08-12 13:00:00,16384.0 -2016-08-12 14:00:00,16343.0 -2016-08-12 15:00:00,16479.0 -2016-08-12 16:00:00,16443.0 -2016-08-12 17:00:00,16337.0 -2016-08-12 18:00:00,16399.0 -2016-08-12 19:00:00,16271.0 -2016-08-12 20:00:00,16097.0 -2016-08-12 21:00:00,15907.0 -2016-08-12 22:00:00,15635.0 -2016-08-12 23:00:00,15074.0 -2016-08-13 00:00:00,14151.0 -2016-08-11 01:00:00,14577.0 -2016-08-11 02:00:00,13478.0 -2016-08-11 03:00:00,12700.0 -2016-08-11 04:00:00,12146.0 -2016-08-11 05:00:00,11843.0 -2016-08-11 06:00:00,11867.0 -2016-08-11 07:00:00,12468.0 -2016-08-11 08:00:00,13256.0 -2016-08-11 09:00:00,14575.0 -2016-08-11 10:00:00,16078.0 -2016-08-11 11:00:00,17444.0 -2016-08-11 12:00:00,18812.0 -2016-08-11 13:00:00,19792.0 -2016-08-11 14:00:00,20430.0 -2016-08-11 15:00:00,20885.0 -2016-08-11 16:00:00,21175.0 -2016-08-11 17:00:00,21086.0 -2016-08-11 18:00:00,20292.0 -2016-08-11 19:00:00,19336.0 -2016-08-11 20:00:00,18909.0 -2016-08-11 21:00:00,18537.0 -2016-08-11 22:00:00,18484.0 -2016-08-11 23:00:00,17935.0 -2016-08-12 00:00:00,16817.0 -2016-08-10 01:00:00,13802.0 -2016-08-10 02:00:00,12712.0 -2016-08-10 03:00:00,11926.0 -2016-08-10 04:00:00,11368.0 -2016-08-10 05:00:00,11053.0 -2016-08-10 06:00:00,11122.0 -2016-08-10 07:00:00,11685.0 -2016-08-10 08:00:00,12461.0 -2016-08-10 09:00:00,13788.0 -2016-08-10 10:00:00,15113.0 -2016-08-10 11:00:00,16370.0 -2016-08-10 12:00:00,17515.0 -2016-08-10 13:00:00,18327.0 -2016-08-10 14:00:00,19051.0 -2016-08-10 15:00:00,19520.0 -2016-08-10 16:00:00,19829.0 -2016-08-10 17:00:00,20017.0 -2016-08-10 18:00:00,20082.0 -2016-08-10 19:00:00,19867.0 -2016-08-10 20:00:00,19266.0 -2016-08-10 21:00:00,18547.0 -2016-08-10 22:00:00,18226.0 -2016-08-10 23:00:00,17332.0 -2016-08-11 00:00:00,16017.0 -2016-08-09 01:00:00,12924.0 -2016-08-09 02:00:00,11969.0 -2016-08-09 03:00:00,11213.0 -2016-08-09 04:00:00,10680.0 -2016-08-09 05:00:00,10395.0 -2016-08-09 06:00:00,10470.0 -2016-08-09 07:00:00,11017.0 -2016-08-09 08:00:00,11717.0 -2016-08-09 09:00:00,12793.0 -2016-08-09 10:00:00,13839.0 -2016-08-09 11:00:00,14793.0 -2016-08-09 12:00:00,15864.0 -2016-08-09 13:00:00,16801.0 -2016-08-09 14:00:00,17603.0 -2016-08-09 15:00:00,18282.0 -2016-08-09 16:00:00,18635.0 -2016-08-09 17:00:00,18729.0 -2016-08-09 18:00:00,18880.0 -2016-08-09 19:00:00,18799.0 -2016-08-09 20:00:00,18114.0 -2016-08-09 21:00:00,17439.0 -2016-08-09 22:00:00,17157.0 -2016-08-09 23:00:00,16417.0 -2016-08-10 00:00:00,15147.0 -2016-08-08 01:00:00,11547.0 -2016-08-08 02:00:00,10838.0 -2016-08-08 03:00:00,10343.0 -2016-08-08 04:00:00,9999.0 -2016-08-08 05:00:00,9883.0 -2016-08-08 06:00:00,10112.0 -2016-08-08 07:00:00,10775.0 -2016-08-08 08:00:00,11604.0 -2016-08-08 09:00:00,12688.0 -2016-08-08 10:00:00,13865.0 -2016-08-08 11:00:00,14742.0 -2016-08-08 12:00:00,15663.0 -2016-08-08 13:00:00,16417.0 -2016-08-08 14:00:00,17005.0 -2016-08-08 15:00:00,17523.0 -2016-08-08 16:00:00,17857.0 -2016-08-08 17:00:00,18041.0 -2016-08-08 18:00:00,18070.0 -2016-08-08 19:00:00,17877.0 -2016-08-08 20:00:00,17228.0 -2016-08-08 21:00:00,16351.0 -2016-08-08 22:00:00,15995.0 -2016-08-08 23:00:00,15337.0 -2016-08-09 00:00:00,14144.0 -2016-08-07 01:00:00,11264.0 -2016-08-07 02:00:00,10493.0 -2016-08-07 03:00:00,9888.0 -2016-08-07 04:00:00,9466.0 -2016-08-07 05:00:00,9113.0 -2016-08-07 06:00:00,8972.0 -2016-08-07 07:00:00,8857.0 -2016-08-07 08:00:00,8828.0 -2016-08-07 09:00:00,9382.0 -2016-08-07 10:00:00,10264.0 -2016-08-07 11:00:00,11192.0 -2016-08-07 12:00:00,12158.0 -2016-08-07 13:00:00,12934.0 -2016-08-07 14:00:00,13528.0 -2016-08-07 15:00:00,13945.0 -2016-08-07 16:00:00,14193.0 -2016-08-07 17:00:00,14260.0 -2016-08-07 18:00:00,14248.0 -2016-08-07 19:00:00,13944.0 -2016-08-07 20:00:00,13555.0 -2016-08-07 21:00:00,13175.0 -2016-08-07 22:00:00,13279.0 -2016-08-07 23:00:00,13020.0 -2016-08-08 00:00:00,12311.0 -2016-08-06 01:00:00,12864.0 -2016-08-06 02:00:00,11763.0 -2016-08-06 03:00:00,11033.0 -2016-08-06 04:00:00,10442.0 -2016-08-06 05:00:00,10006.0 -2016-08-06 06:00:00,9860.0 -2016-08-06 07:00:00,9937.0 -2016-08-06 08:00:00,10019.0 -2016-08-06 09:00:00,10794.0 -2016-08-06 10:00:00,11839.0 -2016-08-06 11:00:00,12893.0 -2016-08-06 12:00:00,13727.0 -2016-08-06 13:00:00,14295.0 -2016-08-06 14:00:00,14600.0 -2016-08-06 15:00:00,14832.0 -2016-08-06 16:00:00,15026.0 -2016-08-06 17:00:00,15177.0 -2016-08-06 18:00:00,15213.0 -2016-08-06 19:00:00,15050.0 -2016-08-06 20:00:00,14550.0 -2016-08-06 21:00:00,13827.0 -2016-08-06 22:00:00,13538.0 -2016-08-06 23:00:00,13031.0 -2016-08-07 00:00:00,12209.0 -2016-08-05 01:00:00,15050.0 -2016-08-05 02:00:00,13731.0 -2016-08-05 03:00:00,12861.0 -2016-08-05 04:00:00,12277.0 -2016-08-05 05:00:00,11980.0 -2016-08-05 06:00:00,11976.0 -2016-08-05 07:00:00,12473.0 -2016-08-05 08:00:00,13110.0 -2016-08-05 09:00:00,13867.0 -2016-08-05 10:00:00,14504.0 -2016-08-05 11:00:00,14984.0 -2016-08-05 12:00:00,15557.0 -2016-08-05 13:00:00,15911.0 -2016-08-05 14:00:00,16395.0 -2016-08-05 15:00:00,17230.0 -2016-08-05 16:00:00,18056.0 -2016-08-05 17:00:00,18119.0 -2016-08-05 18:00:00,18169.0 -2016-08-05 19:00:00,17944.0 -2016-08-05 20:00:00,17295.0 -2016-08-05 21:00:00,16432.0 -2016-08-05 22:00:00,15881.0 -2016-08-05 23:00:00,15241.0 -2016-08-06 00:00:00,14040.0 -2016-08-04 01:00:00,14181.0 -2016-08-04 02:00:00,13090.0 -2016-08-04 03:00:00,12254.0 -2016-08-04 04:00:00,11599.0 -2016-08-04 05:00:00,11274.0 -2016-08-04 06:00:00,11285.0 -2016-08-04 07:00:00,11750.0 -2016-08-04 08:00:00,12515.0 -2016-08-04 09:00:00,13875.0 -2016-08-04 10:00:00,15264.0 -2016-08-04 11:00:00,16533.0 -2016-08-04 12:00:00,17686.0 -2016-08-04 13:00:00,18551.0 -2016-08-04 14:00:00,19257.0 -2016-08-04 15:00:00,19858.0 -2016-08-04 16:00:00,20175.0 -2016-08-04 17:00:00,20421.0 -2016-08-04 18:00:00,20501.0 -2016-08-04 19:00:00,20323.0 -2016-08-04 20:00:00,19797.0 -2016-08-04 21:00:00,18959.0 -2016-08-04 22:00:00,18636.0 -2016-08-04 23:00:00,17791.0 -2016-08-05 00:00:00,16507.0 -2016-08-03 01:00:00,13486.0 -2016-08-03 02:00:00,12449.0 -2016-08-03 03:00:00,11719.0 -2016-08-03 04:00:00,11193.0 -2016-08-03 05:00:00,10891.0 -2016-08-03 06:00:00,10928.0 -2016-08-03 07:00:00,11442.0 -2016-08-03 08:00:00,12255.0 -2016-08-03 09:00:00,13566.0 -2016-08-03 10:00:00,14929.0 -2016-08-03 11:00:00,16199.0 -2016-08-03 12:00:00,17388.0 -2016-08-03 13:00:00,18263.0 -2016-08-03 14:00:00,18998.0 -2016-08-03 15:00:00,19505.0 -2016-08-03 16:00:00,19840.0 -2016-08-03 17:00:00,20009.0 -2016-08-03 18:00:00,19983.0 -2016-08-03 19:00:00,19648.0 -2016-08-03 20:00:00,18932.0 -2016-08-03 21:00:00,18176.0 -2016-08-03 22:00:00,17740.0 -2016-08-03 23:00:00,16953.0 -2016-08-04 00:00:00,15570.0 -2016-08-02 01:00:00,12583.0 -2016-08-02 02:00:00,11611.0 -2016-08-02 03:00:00,10939.0 -2016-08-02 04:00:00,10514.0 -2016-08-02 05:00:00,10304.0 -2016-08-02 06:00:00,10421.0 -2016-08-02 07:00:00,10937.0 -2016-08-02 08:00:00,11772.0 -2016-08-02 09:00:00,12959.0 -2016-08-02 10:00:00,14102.0 -2016-08-02 11:00:00,15214.0 -2016-08-02 12:00:00,16170.0 -2016-08-02 13:00:00,16974.0 -2016-08-02 14:00:00,17745.0 -2016-08-02 15:00:00,18369.0 -2016-08-02 16:00:00,18695.0 -2016-08-02 17:00:00,18883.0 -2016-08-02 18:00:00,18823.0 -2016-08-02 19:00:00,18552.0 -2016-08-02 20:00:00,17920.0 -2016-08-02 21:00:00,17141.0 -2016-08-02 22:00:00,16744.0 -2016-08-02 23:00:00,16075.0 -2016-08-03 00:00:00,14846.0 -2016-08-01 01:00:00,11463.0 -2016-08-01 02:00:00,10684.0 -2016-08-01 03:00:00,10167.0 -2016-08-01 04:00:00,9787.0 -2016-08-01 05:00:00,9666.0 -2016-08-01 06:00:00,9836.0 -2016-08-01 07:00:00,10452.0 -2016-08-01 08:00:00,11275.0 -2016-08-01 09:00:00,12561.0 -2016-08-01 10:00:00,13640.0 -2016-08-01 11:00:00,14556.0 -2016-08-01 12:00:00,15533.0 -2016-08-01 13:00:00,16295.0 -2016-08-01 14:00:00,16936.0 -2016-08-01 15:00:00,17488.0 -2016-08-01 16:00:00,17803.0 -2016-08-01 17:00:00,17911.0 -2016-08-01 18:00:00,17893.0 -2016-08-01 19:00:00,17586.0 -2016-08-01 20:00:00,16980.0 -2016-08-01 21:00:00,16174.0 -2016-08-01 22:00:00,15674.0 -2016-08-01 23:00:00,14973.0 -2016-08-02 00:00:00,13865.0 -2016-07-31 01:00:00,10985.0 -2016-07-31 02:00:00,10278.0 -2016-07-31 03:00:00,9759.0 -2016-07-31 04:00:00,9325.0 -2016-07-31 05:00:00,9094.0 -2016-07-31 06:00:00,8926.0 -2016-07-31 07:00:00,8877.0 -2016-07-31 08:00:00,8809.0 -2016-07-31 09:00:00,9362.0 -2016-07-31 10:00:00,10175.0 -2016-07-31 11:00:00,11121.0 -2016-07-31 12:00:00,12075.0 -2016-07-31 13:00:00,12821.0 -2016-07-31 14:00:00,13462.0 -2016-07-31 15:00:00,13916.0 -2016-07-31 16:00:00,14332.0 -2016-07-31 17:00:00,14522.0 -2016-07-31 18:00:00,14584.0 -2016-07-31 19:00:00,14475.0 -2016-07-31 20:00:00,14139.0 -2016-07-31 21:00:00,13529.0 -2016-07-31 22:00:00,13414.0 -2016-07-31 23:00:00,13163.0 -2016-08-01 00:00:00,12385.0 -2016-07-30 01:00:00,11806.0 -2016-07-30 02:00:00,11046.0 -2016-07-30 03:00:00,10521.0 -2016-07-30 04:00:00,10090.0 -2016-07-30 05:00:00,9868.0 -2016-07-30 06:00:00,9784.0 -2016-07-30 07:00:00,9936.0 -2016-07-30 08:00:00,10102.0 -2016-07-30 09:00:00,10598.0 -2016-07-30 10:00:00,11442.0 -2016-07-30 11:00:00,12120.0 -2016-07-30 12:00:00,12564.0 -2016-07-30 13:00:00,12821.0 -2016-07-30 14:00:00,13138.0 -2016-07-30 15:00:00,13463.0 -2016-07-30 16:00:00,13730.0 -2016-07-30 17:00:00,13876.0 -2016-07-30 18:00:00,14011.0 -2016-07-30 19:00:00,13851.0 -2016-07-30 20:00:00,13300.0 -2016-07-30 21:00:00,12689.0 -2016-07-30 22:00:00,12551.0 -2016-07-30 23:00:00,12336.0 -2016-07-31 00:00:00,11665.0 -2016-07-29 01:00:00,12527.0 -2016-07-29 02:00:00,11681.0 -2016-07-29 03:00:00,11132.0 -2016-07-29 04:00:00,10708.0 -2016-07-29 05:00:00,10496.0 -2016-07-29 06:00:00,10650.0 -2016-07-29 07:00:00,11181.0 -2016-07-29 08:00:00,11966.0 -2016-07-29 09:00:00,13058.0 -2016-07-29 10:00:00,14064.0 -2016-07-29 11:00:00,14874.0 -2016-07-29 12:00:00,15670.0 -2016-07-29 13:00:00,16204.0 -2016-07-29 14:00:00,16590.0 -2016-07-29 15:00:00,16573.0 -2016-07-29 16:00:00,15950.0 -2016-07-29 17:00:00,15264.0 -2016-07-29 18:00:00,14891.0 -2016-07-29 19:00:00,14472.0 -2016-07-29 20:00:00,14051.0 -2016-07-29 21:00:00,13724.0 -2016-07-29 22:00:00,13746.0 -2016-07-29 23:00:00,13450.0 -2016-07-30 00:00:00,12632.0 -2016-07-28 01:00:00,14098.0 -2016-07-28 02:00:00,12982.0 -2016-07-28 03:00:00,12177.0 -2016-07-28 04:00:00,11596.0 -2016-07-28 05:00:00,11280.0 -2016-07-28 06:00:00,11292.0 -2016-07-28 07:00:00,11794.0 -2016-07-28 08:00:00,12601.0 -2016-07-28 09:00:00,13822.0 -2016-07-28 10:00:00,14960.0 -2016-07-28 11:00:00,15967.0 -2016-07-28 12:00:00,16993.0 -2016-07-28 13:00:00,17629.0 -2016-07-28 14:00:00,17990.0 -2016-07-28 15:00:00,17845.0 -2016-07-28 16:00:00,17198.0 -2016-07-28 17:00:00,16513.0 -2016-07-28 18:00:00,15930.0 -2016-07-28 19:00:00,15742.0 -2016-07-28 20:00:00,15388.0 -2016-07-28 21:00:00,14989.0 -2016-07-28 22:00:00,14868.0 -2016-07-28 23:00:00,14476.0 -2016-07-29 00:00:00,13582.0 -2016-07-27 01:00:00,13555.0 -2016-07-27 02:00:00,12464.0 -2016-07-27 03:00:00,11652.0 -2016-07-27 04:00:00,11147.0 -2016-07-27 05:00:00,10799.0 -2016-07-27 06:00:00,10862.0 -2016-07-27 07:00:00,11300.0 -2016-07-27 08:00:00,12153.0 -2016-07-27 09:00:00,13461.0 -2016-07-27 10:00:00,14744.0 -2016-07-27 11:00:00,15932.0 -2016-07-27 12:00:00,17000.0 -2016-07-27 13:00:00,17860.0 -2016-07-27 14:00:00,18432.0 -2016-07-27 15:00:00,18982.0 -2016-07-27 16:00:00,19370.0 -2016-07-27 17:00:00,19612.0 -2016-07-27 18:00:00,19627.0 -2016-07-27 19:00:00,19396.0 -2016-07-27 20:00:00,18725.0 -2016-07-27 21:00:00,17891.0 -2016-07-27 22:00:00,17521.0 -2016-07-27 23:00:00,16789.0 -2016-07-28 00:00:00,15453.0 -2016-07-26 01:00:00,13474.0 -2016-07-26 02:00:00,12383.0 -2016-07-26 03:00:00,11600.0 -2016-07-26 04:00:00,11070.0 -2016-07-26 05:00:00,10767.0 -2016-07-26 06:00:00,10809.0 -2016-07-26 07:00:00,11280.0 -2016-07-26 08:00:00,12127.0 -2016-07-26 09:00:00,13414.0 -2016-07-26 10:00:00,14626.0 -2016-07-26 11:00:00,15704.0 -2016-07-26 12:00:00,16688.0 -2016-07-26 13:00:00,17312.0 -2016-07-26 14:00:00,17860.0 -2016-07-26 15:00:00,18352.0 -2016-07-26 16:00:00,18657.0 -2016-07-26 17:00:00,18879.0 -2016-07-26 18:00:00,18982.0 -2016-07-26 19:00:00,18865.0 -2016-07-26 20:00:00,18369.0 -2016-07-26 21:00:00,17567.0 -2016-07-26 22:00:00,16972.0 -2016-07-26 23:00:00,16288.0 -2016-07-27 00:00:00,14933.0 -2016-07-25 01:00:00,13351.0 -2016-07-25 02:00:00,12512.0 -2016-07-25 03:00:00,11822.0 -2016-07-25 04:00:00,11384.0 -2016-07-25 05:00:00,11143.0 -2016-07-25 06:00:00,11256.0 -2016-07-25 07:00:00,11828.0 -2016-07-25 08:00:00,12643.0 -2016-07-25 09:00:00,13963.0 -2016-07-25 10:00:00,15088.0 -2016-07-25 11:00:00,15991.0 -2016-07-25 12:00:00,16861.0 -2016-07-25 13:00:00,17473.0 -2016-07-25 14:00:00,17985.0 -2016-07-25 15:00:00,18503.0 -2016-07-25 16:00:00,18829.0 -2016-07-25 17:00:00,19026.0 -2016-07-25 18:00:00,19043.0 -2016-07-25 19:00:00,18871.0 -2016-07-25 20:00:00,18383.0 -2016-07-25 21:00:00,17561.0 -2016-07-25 22:00:00,16926.0 -2016-07-25 23:00:00,16222.0 -2016-07-26 00:00:00,14852.0 -2016-07-24 01:00:00,12881.0 -2016-07-24 02:00:00,12038.0 -2016-07-24 03:00:00,11365.0 -2016-07-24 04:00:00,10945.0 -2016-07-24 05:00:00,10644.0 -2016-07-24 06:00:00,10431.0 -2016-07-24 07:00:00,10373.0 -2016-07-24 08:00:00,10399.0 -2016-07-24 09:00:00,11058.0 -2016-07-24 10:00:00,12079.0 -2016-07-24 11:00:00,13506.0 -2016-07-24 12:00:00,15072.0 -2016-07-24 13:00:00,16335.0 -2016-07-24 14:00:00,17318.0 -2016-07-24 15:00:00,18150.0 -2016-07-24 16:00:00,18755.0 -2016-07-24 17:00:00,18895.0 -2016-07-24 18:00:00,19004.0 -2016-07-24 19:00:00,18814.0 -2016-07-24 20:00:00,17779.0 -2016-07-24 21:00:00,16443.0 -2016-07-24 22:00:00,15896.0 -2016-07-24 23:00:00,15367.0 -2016-07-25 00:00:00,14434.0 -2016-07-23 01:00:00,15479.0 -2016-07-23 02:00:00,14489.0 -2016-07-23 03:00:00,13758.0 -2016-07-23 04:00:00,13152.0 -2016-07-23 05:00:00,12735.0 -2016-07-23 06:00:00,12495.0 -2016-07-23 07:00:00,12479.0 -2016-07-23 08:00:00,12662.0 -2016-07-23 09:00:00,13667.0 -2016-07-23 10:00:00,15214.0 -2016-07-23 11:00:00,16611.0 -2016-07-23 12:00:00,17725.0 -2016-07-23 13:00:00,18420.0 -2016-07-23 14:00:00,18656.0 -2016-07-23 15:00:00,18857.0 -2016-07-23 16:00:00,18877.0 -2016-07-23 17:00:00,18850.0 -2016-07-23 18:00:00,18554.0 -2016-07-23 19:00:00,17977.0 -2016-07-23 20:00:00,17058.0 -2016-07-23 21:00:00,15956.0 -2016-07-23 22:00:00,15375.0 -2016-07-23 23:00:00,14763.0 -2016-07-24 00:00:00,13822.0 -2016-07-22 01:00:00,14177.0 -2016-07-22 02:00:00,13089.0 -2016-07-22 03:00:00,12396.0 -2016-07-22 04:00:00,11945.0 -2016-07-22 05:00:00,11578.0 -2016-07-22 06:00:00,11575.0 -2016-07-22 07:00:00,11979.0 -2016-07-22 08:00:00,12657.0 -2016-07-22 09:00:00,13735.0 -2016-07-22 10:00:00,14632.0 -2016-07-22 11:00:00,15495.0 -2016-07-22 12:00:00,16511.0 -2016-07-22 13:00:00,17336.0 -2016-07-22 14:00:00,18004.0 -2016-07-22 15:00:00,18610.0 -2016-07-22 16:00:00,19134.0 -2016-07-22 17:00:00,19555.0 -2016-07-22 18:00:00,19929.0 -2016-07-22 19:00:00,19972.0 -2016-07-22 20:00:00,19641.0 -2016-07-22 21:00:00,18905.0 -2016-07-22 22:00:00,18423.0 -2016-07-22 23:00:00,17879.0 -2016-07-23 00:00:00,16710.0 -2016-07-21 01:00:00,14399.0 -2016-07-21 02:00:00,13340.0 -2016-07-21 03:00:00,12666.0 -2016-07-21 04:00:00,12122.0 -2016-07-21 05:00:00,11844.0 -2016-07-21 06:00:00,11929.0 -2016-07-21 07:00:00,12488.0 -2016-07-21 08:00:00,13454.0 -2016-07-21 09:00:00,14786.0 -2016-07-21 10:00:00,16209.0 -2016-07-21 11:00:00,17599.0 -2016-07-21 12:00:00,18768.0 -2016-07-21 13:00:00,19616.0 -2016-07-21 14:00:00,19788.0 -2016-07-21 15:00:00,19308.0 -2016-07-21 16:00:00,19100.0 -2016-07-21 17:00:00,19340.0 -2016-07-21 18:00:00,19871.0 -2016-07-21 19:00:00,20296.0 -2016-07-21 20:00:00,20104.0 -2016-07-21 21:00:00,19458.0 -2016-07-21 22:00:00,18828.0 -2016-07-21 23:00:00,17130.0 -2016-07-22 00:00:00,15578.0 -2016-07-20 01:00:00,13462.0 -2016-07-20 02:00:00,12389.0 -2016-07-20 03:00:00,11616.0 -2016-07-20 04:00:00,11093.0 -2016-07-20 05:00:00,10815.0 -2016-07-20 06:00:00,10863.0 -2016-07-20 07:00:00,11329.0 -2016-07-20 08:00:00,12281.0 -2016-07-20 09:00:00,13644.0 -2016-07-20 10:00:00,14996.0 -2016-07-20 11:00:00,16259.0 -2016-07-20 12:00:00,17401.0 -2016-07-20 13:00:00,18306.0 -2016-07-20 14:00:00,18914.0 -2016-07-20 15:00:00,18788.0 -2016-07-20 16:00:00,18303.0 -2016-07-20 17:00:00,18314.0 -2016-07-20 18:00:00,18595.0 -2016-07-20 19:00:00,18713.0 -2016-07-20 20:00:00,18574.0 -2016-07-20 21:00:00,17979.0 -2016-07-20 22:00:00,17402.0 -2016-07-20 23:00:00,16820.0 -2016-07-21 00:00:00,15657.0 -2016-07-19 01:00:00,12861.0 -2016-07-19 02:00:00,11815.0 -2016-07-19 03:00:00,11068.0 -2016-07-19 04:00:00,10585.0 -2016-07-19 05:00:00,10325.0 -2016-07-19 06:00:00,10376.0 -2016-07-19 07:00:00,10830.0 -2016-07-19 08:00:00,11779.0 -2016-07-19 09:00:00,13097.0 -2016-07-19 10:00:00,14303.0 -2016-07-19 11:00:00,15401.0 -2016-07-19 12:00:00,16453.0 -2016-07-19 13:00:00,17277.0 -2016-07-19 14:00:00,17964.0 -2016-07-19 15:00:00,18569.0 -2016-07-19 16:00:00,18904.0 -2016-07-19 17:00:00,19029.0 -2016-07-19 18:00:00,18873.0 -2016-07-19 19:00:00,18494.0 -2016-07-19 20:00:00,17811.0 -2016-07-19 21:00:00,17039.0 -2016-07-19 22:00:00,16584.0 -2016-07-19 23:00:00,16067.0 -2016-07-20 00:00:00,14856.0 -2016-07-18 01:00:00,12784.0 -2016-07-18 02:00:00,11922.0 -2016-07-18 03:00:00,11321.0 -2016-07-18 04:00:00,10810.0 -2016-07-18 05:00:00,10544.0 -2016-07-18 06:00:00,10600.0 -2016-07-18 07:00:00,11095.0 -2016-07-18 08:00:00,12057.0 -2016-07-18 09:00:00,13362.0 -2016-07-18 10:00:00,14420.0 -2016-07-18 11:00:00,15139.0 -2016-07-18 12:00:00,15618.0 -2016-07-18 13:00:00,15807.0 -2016-07-18 14:00:00,16220.0 -2016-07-18 15:00:00,16785.0 -2016-07-18 16:00:00,17261.0 -2016-07-18 17:00:00,17617.0 -2016-07-18 18:00:00,17934.0 -2016-07-18 19:00:00,17907.0 -2016-07-18 20:00:00,17572.0 -2016-07-18 21:00:00,16834.0 -2016-07-18 22:00:00,16135.0 -2016-07-18 23:00:00,15501.0 -2016-07-19 00:00:00,14194.0 -2016-07-17 01:00:00,10341.0 -2016-07-17 02:00:00,9660.0 -2016-07-17 03:00:00,9137.0 -2016-07-17 04:00:00,8748.0 -2016-07-17 05:00:00,8610.0 -2016-07-17 06:00:00,8501.0 -2016-07-17 07:00:00,8456.0 -2016-07-17 08:00:00,8473.0 -2016-07-17 09:00:00,8891.0 -2016-07-17 10:00:00,9466.0 -2016-07-17 11:00:00,9974.0 -2016-07-17 12:00:00,10447.0 -2016-07-17 13:00:00,11102.0 -2016-07-17 14:00:00,11859.0 -2016-07-17 15:00:00,12591.0 -2016-07-17 16:00:00,13260.0 -2016-07-17 17:00:00,13848.0 -2016-07-17 18:00:00,14247.0 -2016-07-17 19:00:00,14507.0 -2016-07-17 20:00:00,14732.0 -2016-07-17 21:00:00,14681.0 -2016-07-17 22:00:00,14715.0 -2016-07-17 23:00:00,14719.0 -2016-07-18 00:00:00,13982.0 -2016-07-16 01:00:00,10263.0 -2016-07-16 02:00:00,9644.0 -2016-07-16 03:00:00,9187.0 -2016-07-16 04:00:00,8911.0 -2016-07-16 05:00:00,8708.0 -2016-07-16 06:00:00,8703.0 -2016-07-16 07:00:00,8692.0 -2016-07-16 08:00:00,8885.0 -2016-07-16 09:00:00,9504.0 -2016-07-16 10:00:00,10282.0 -2016-07-16 11:00:00,10918.0 -2016-07-16 12:00:00,11424.0 -2016-07-16 13:00:00,11767.0 -2016-07-16 14:00:00,12029.0 -2016-07-16 15:00:00,12225.0 -2016-07-16 16:00:00,12438.0 -2016-07-16 17:00:00,12765.0 -2016-07-16 18:00:00,12955.0 -2016-07-16 19:00:00,13012.0 -2016-07-16 20:00:00,12740.0 -2016-07-16 21:00:00,12274.0 -2016-07-16 22:00:00,11958.0 -2016-07-16 23:00:00,11725.0 -2016-07-17 00:00:00,11082.0 -2016-07-15 01:00:00,12712.0 -2016-07-15 02:00:00,11686.0 -2016-07-15 03:00:00,10953.0 -2016-07-15 04:00:00,10421.0 -2016-07-15 05:00:00,10045.0 -2016-07-15 06:00:00,10047.0 -2016-07-15 07:00:00,10363.0 -2016-07-15 08:00:00,11095.0 -2016-07-15 09:00:00,12111.0 -2016-07-15 10:00:00,13012.0 -2016-07-15 11:00:00,13651.0 -2016-07-15 12:00:00,14027.0 -2016-07-15 13:00:00,14153.0 -2016-07-15 14:00:00,14188.0 -2016-07-15 15:00:00,14152.0 -2016-07-15 16:00:00,13923.0 -2016-07-15 17:00:00,13571.0 -2016-07-15 18:00:00,13211.0 -2016-07-15 19:00:00,12807.0 -2016-07-15 20:00:00,12350.0 -2016-07-15 21:00:00,12005.0 -2016-07-15 22:00:00,11975.0 -2016-07-15 23:00:00,11851.0 -2016-07-16 00:00:00,11107.0 -2016-07-14 01:00:00,12479.0 -2016-07-14 02:00:00,11550.0 -2016-07-14 03:00:00,10931.0 -2016-07-14 04:00:00,10480.0 -2016-07-14 05:00:00,10261.0 -2016-07-14 06:00:00,10391.0 -2016-07-14 07:00:00,10898.0 -2016-07-14 08:00:00,11818.0 -2016-07-14 09:00:00,13033.0 -2016-07-14 10:00:00,14191.0 -2016-07-14 11:00:00,15225.0 -2016-07-14 12:00:00,16216.0 -2016-07-14 13:00:00,16862.0 -2016-07-14 14:00:00,17417.0 -2016-07-14 15:00:00,17863.0 -2016-07-14 16:00:00,18153.0 -2016-07-14 17:00:00,18281.0 -2016-07-14 18:00:00,18244.0 -2016-07-14 19:00:00,17951.0 -2016-07-14 20:00:00,17285.0 -2016-07-14 21:00:00,16296.0 -2016-07-14 22:00:00,15724.0 -2016-07-14 23:00:00,15182.0 -2016-07-15 00:00:00,13996.0 -2016-07-13 01:00:00,13940.0 -2016-07-13 02:00:00,12811.0 -2016-07-13 03:00:00,12055.0 -2016-07-13 04:00:00,11577.0 -2016-07-13 05:00:00,11259.0 -2016-07-13 06:00:00,11202.0 -2016-07-13 07:00:00,11545.0 -2016-07-13 08:00:00,12550.0 -2016-07-13 09:00:00,13987.0 -2016-07-13 10:00:00,14878.0 -2016-07-13 11:00:00,15425.0 -2016-07-13 12:00:00,16249.0 -2016-07-13 13:00:00,16979.0 -2016-07-13 14:00:00,17371.0 -2016-07-13 15:00:00,18056.0 -2016-07-13 16:00:00,18376.0 -2016-07-13 17:00:00,18421.0 -2016-07-13 18:00:00,18331.0 -2016-07-13 19:00:00,17689.0 -2016-07-13 20:00:00,17114.0 -2016-07-13 21:00:00,16336.0 -2016-07-13 22:00:00,15387.0 -2016-07-13 23:00:00,14791.0 -2016-07-14 00:00:00,13667.0 -2016-07-12 01:00:00,14964.0 -2016-07-12 02:00:00,13833.0 -2016-07-12 03:00:00,12992.0 -2016-07-12 04:00:00,12348.0 -2016-07-12 05:00:00,11976.0 -2016-07-12 06:00:00,11996.0 -2016-07-12 07:00:00,12363.0 -2016-07-12 08:00:00,13341.0 -2016-07-12 09:00:00,14468.0 -2016-07-12 10:00:00,15447.0 -2016-07-12 11:00:00,16492.0 -2016-07-12 12:00:00,17661.0 -2016-07-12 13:00:00,18604.0 -2016-07-12 14:00:00,19286.0 -2016-07-12 15:00:00,19740.0 -2016-07-12 16:00:00,19841.0 -2016-07-12 17:00:00,19712.0 -2016-07-12 18:00:00,19480.0 -2016-07-12 19:00:00,19285.0 -2016-07-12 20:00:00,18731.0 -2016-07-12 21:00:00,17956.0 -2016-07-12 22:00:00,17231.0 -2016-07-12 23:00:00,16668.0 -2016-07-13 00:00:00,15364.0 -2016-07-11 01:00:00,11678.0 -2016-07-11 02:00:00,10920.0 -2016-07-11 03:00:00,10440.0 -2016-07-11 04:00:00,10049.0 -2016-07-11 05:00:00,9940.0 -2016-07-11 06:00:00,10094.0 -2016-07-11 07:00:00,10665.0 -2016-07-11 08:00:00,11715.0 -2016-07-11 09:00:00,13182.0 -2016-07-11 10:00:00,14538.0 -2016-07-11 11:00:00,15895.0 -2016-07-11 12:00:00,17162.0 -2016-07-11 13:00:00,18190.0 -2016-07-11 14:00:00,18964.0 -2016-07-11 15:00:00,19552.0 -2016-07-11 16:00:00,19960.0 -2016-07-11 17:00:00,20154.0 -2016-07-11 18:00:00,20268.0 -2016-07-11 19:00:00,20109.0 -2016-07-11 20:00:00,19554.0 -2016-07-11 21:00:00,18859.0 -2016-07-11 22:00:00,18201.0 -2016-07-11 23:00:00,17679.0 -2016-07-12 00:00:00,16360.0 -2016-07-10 01:00:00,10680.0 -2016-07-10 02:00:00,9923.0 -2016-07-10 03:00:00,9393.0 -2016-07-10 04:00:00,8979.0 -2016-07-10 05:00:00,8751.0 -2016-07-10 06:00:00,8600.0 -2016-07-10 07:00:00,8446.0 -2016-07-10 08:00:00,8483.0 -2016-07-10 09:00:00,9170.0 -2016-07-10 10:00:00,9983.0 -2016-07-10 11:00:00,10882.0 -2016-07-10 12:00:00,11692.0 -2016-07-10 13:00:00,12432.0 -2016-07-10 14:00:00,12935.0 -2016-07-10 15:00:00,13378.0 -2016-07-10 16:00:00,13743.0 -2016-07-10 17:00:00,13899.0 -2016-07-10 18:00:00,13846.0 -2016-07-10 19:00:00,13837.0 -2016-07-10 20:00:00,13547.0 -2016-07-10 21:00:00,13270.0 -2016-07-10 22:00:00,13174.0 -2016-07-10 23:00:00,13179.0 -2016-07-11 00:00:00,12502.0 -2016-07-09 01:00:00,12217.0 -2016-07-09 02:00:00,11213.0 -2016-07-09 03:00:00,10494.0 -2016-07-09 04:00:00,10008.0 -2016-07-09 05:00:00,9703.0 -2016-07-09 06:00:00,9558.0 -2016-07-09 07:00:00,9500.0 -2016-07-09 08:00:00,9791.0 -2016-07-09 09:00:00,10537.0 -2016-07-09 10:00:00,11354.0 -2016-07-09 11:00:00,12143.0 -2016-07-09 12:00:00,12773.0 -2016-07-09 13:00:00,13205.0 -2016-07-09 14:00:00,13517.0 -2016-07-09 15:00:00,13684.0 -2016-07-09 16:00:00,13878.0 -2016-07-09 17:00:00,14023.0 -2016-07-09 18:00:00,14128.0 -2016-07-09 19:00:00,14015.0 -2016-07-09 20:00:00,13621.0 -2016-07-09 21:00:00,12968.0 -2016-07-09 22:00:00,12415.0 -2016-07-09 23:00:00,12210.0 -2016-07-10 00:00:00,11485.0 -2016-07-08 01:00:00,12400.0 -2016-07-08 02:00:00,11415.0 -2016-07-08 03:00:00,10726.0 -2016-07-08 04:00:00,10286.0 -2016-07-08 05:00:00,10088.0 -2016-07-08 06:00:00,10248.0 -2016-07-08 07:00:00,10711.0 -2016-07-08 08:00:00,11617.0 -2016-07-08 09:00:00,12885.0 -2016-07-08 10:00:00,14158.0 -2016-07-08 11:00:00,15433.0 -2016-07-08 12:00:00,16525.0 -2016-07-08 13:00:00,17222.0 -2016-07-08 14:00:00,17680.0 -2016-07-08 15:00:00,17868.0 -2016-07-08 16:00:00,17869.0 -2016-07-08 17:00:00,17884.0 -2016-07-08 18:00:00,17834.0 -2016-07-08 19:00:00,17504.0 -2016-07-08 20:00:00,16692.0 -2016-07-08 21:00:00,15802.0 -2016-07-08 22:00:00,15028.0 -2016-07-08 23:00:00,14520.0 -2016-07-09 00:00:00,13396.0 -2016-07-07 01:00:00,13474.0 -2016-07-07 02:00:00,12437.0 -2016-07-07 03:00:00,11651.0 -2016-07-07 04:00:00,11070.0 -2016-07-07 05:00:00,10756.0 -2016-07-07 06:00:00,10778.0 -2016-07-07 07:00:00,11163.0 -2016-07-07 08:00:00,12043.0 -2016-07-07 09:00:00,13150.0 -2016-07-07 10:00:00,14337.0 -2016-07-07 11:00:00,15168.0 -2016-07-07 12:00:00,15731.0 -2016-07-07 13:00:00,15936.0 -2016-07-07 14:00:00,16073.0 -2016-07-07 15:00:00,16120.0 -2016-07-07 16:00:00,16082.0 -2016-07-07 17:00:00,16103.0 -2016-07-07 18:00:00,16434.0 -2016-07-07 19:00:00,16197.0 -2016-07-07 20:00:00,15740.0 -2016-07-07 21:00:00,15287.0 -2016-07-07 22:00:00,14974.0 -2016-07-07 23:00:00,14610.0 -2016-07-08 00:00:00,13554.0 -2016-07-06 01:00:00,13476.0 -2016-07-06 02:00:00,12459.0 -2016-07-06 03:00:00,11781.0 -2016-07-06 04:00:00,11239.0 -2016-07-06 05:00:00,10635.0 -2016-07-06 06:00:00,10524.0 -2016-07-06 07:00:00,11052.0 -2016-07-06 08:00:00,11881.0 -2016-07-06 09:00:00,12529.0 -2016-07-06 10:00:00,12935.0 -2016-07-06 11:00:00,13478.0 -2016-07-06 12:00:00,14718.0 -2016-07-06 13:00:00,15638.0 -2016-07-06 14:00:00,16442.0 -2016-07-06 15:00:00,17162.0 -2016-07-06 16:00:00,17842.0 -2016-07-06 17:00:00,18348.0 -2016-07-06 18:00:00,18416.0 -2016-07-06 19:00:00,18026.0 -2016-07-06 20:00:00,17399.0 -2016-07-06 21:00:00,16823.0 -2016-07-06 22:00:00,16423.0 -2016-07-06 23:00:00,16079.0 -2016-07-07 00:00:00,14820.0 -2016-07-05 01:00:00,10430.0 -2016-07-05 02:00:00,9742.0 -2016-07-05 03:00:00,9229.0 -2016-07-05 04:00:00,8899.0 -2016-07-05 05:00:00,8816.0 -2016-07-05 06:00:00,9018.0 -2016-07-05 07:00:00,9599.0 -2016-07-05 08:00:00,10531.0 -2016-07-05 09:00:00,11712.0 -2016-07-05 10:00:00,12650.0 -2016-07-05 11:00:00,13473.0 -2016-07-05 12:00:00,14410.0 -2016-07-05 13:00:00,15094.0 -2016-07-05 14:00:00,15748.0 -2016-07-05 15:00:00,16370.0 -2016-07-05 16:00:00,16897.0 -2016-07-05 17:00:00,17403.0 -2016-07-05 18:00:00,17799.0 -2016-07-05 19:00:00,17914.0 -2016-07-05 20:00:00,17621.0 -2016-07-05 21:00:00,17047.0 -2016-07-05 22:00:00,16437.0 -2016-07-05 23:00:00,16091.0 -2016-07-06 00:00:00,14810.0 -2016-07-04 01:00:00,9522.0 -2016-07-04 02:00:00,9046.0 -2016-07-04 03:00:00,8631.0 -2016-07-04 04:00:00,8369.0 -2016-07-04 05:00:00,8189.0 -2016-07-04 06:00:00,8174.0 -2016-07-04 07:00:00,8191.0 -2016-07-04 08:00:00,8236.0 -2016-07-04 09:00:00,8476.0 -2016-07-04 10:00:00,8823.0 -2016-07-04 11:00:00,9316.0 -2016-07-04 12:00:00,9787.0 -2016-07-04 13:00:00,10298.0 -2016-07-04 14:00:00,10863.0 -2016-07-04 15:00:00,11286.0 -2016-07-04 16:00:00,11679.0 -2016-07-04 17:00:00,11860.0 -2016-07-04 18:00:00,11844.0 -2016-07-04 19:00:00,11726.0 -2016-07-04 20:00:00,11477.0 -2016-07-04 21:00:00,11288.0 -2016-07-04 22:00:00,11355.0 -2016-07-04 23:00:00,11320.0 -2016-07-05 00:00:00,10991.0 -2016-07-03 01:00:00,9258.0 -2016-07-03 02:00:00,8714.0 -2016-07-03 03:00:00,8297.0 -2016-07-03 04:00:00,8004.0 -2016-07-03 05:00:00,7762.0 -2016-07-03 06:00:00,7769.0 -2016-07-03 07:00:00,7516.0 -2016-07-03 08:00:00,7589.0 -2016-07-03 09:00:00,7913.0 -2016-07-03 10:00:00,8437.0 -2016-07-03 11:00:00,8998.0 -2016-07-03 12:00:00,9397.0 -2016-07-03 13:00:00,9854.0 -2016-07-03 14:00:00,10184.0 -2016-07-03 15:00:00,10490.0 -2016-07-03 16:00:00,10712.0 -2016-07-03 17:00:00,10892.0 -2016-07-03 18:00:00,10933.0 -2016-07-03 19:00:00,10953.0 -2016-07-03 20:00:00,10737.0 -2016-07-03 21:00:00,10408.0 -2016-07-03 22:00:00,10285.0 -2016-07-03 23:00:00,10314.0 -2016-07-04 00:00:00,10002.0 -2016-07-02 01:00:00,9647.0 -2016-07-02 02:00:00,8979.0 -2016-07-02 03:00:00,8571.0 -2016-07-02 04:00:00,8273.0 -2016-07-02 05:00:00,8105.0 -2016-07-02 06:00:00,8040.0 -2016-07-02 07:00:00,7996.0 -2016-07-02 08:00:00,8124.0 -2016-07-02 09:00:00,8504.0 -2016-07-02 10:00:00,9015.0 -2016-07-02 11:00:00,9558.0 -2016-07-02 12:00:00,9997.0 -2016-07-02 13:00:00,10299.0 -2016-07-02 14:00:00,10437.0 -2016-07-02 15:00:00,10606.0 -2016-07-02 16:00:00,10661.0 -2016-07-02 17:00:00,10784.0 -2016-07-02 18:00:00,10720.0 -2016-07-02 19:00:00,10530.0 -2016-07-02 20:00:00,10294.0 -2016-07-02 21:00:00,10147.0 -2016-07-02 22:00:00,10226.0 -2016-07-02 23:00:00,10270.0 -2016-07-03 00:00:00,9762.0 -2016-07-01 01:00:00,11152.0 -2016-07-01 02:00:00,10447.0 -2016-07-01 03:00:00,9872.0 -2016-07-01 04:00:00,9432.0 -2016-07-01 05:00:00,9187.0 -2016-07-01 06:00:00,9244.0 -2016-07-01 07:00:00,9541.0 -2016-07-01 08:00:00,10220.0 -2016-07-01 09:00:00,10982.0 -2016-07-01 10:00:00,11603.0 -2016-07-01 11:00:00,12027.0 -2016-07-01 12:00:00,12403.0 -2016-07-01 13:00:00,12650.0 -2016-07-01 14:00:00,12807.0 -2016-07-01 15:00:00,12983.0 -2016-07-01 16:00:00,13022.0 -2016-07-01 17:00:00,12989.0 -2016-07-01 18:00:00,12918.0 -2016-07-01 19:00:00,12717.0 -2016-07-01 20:00:00,12247.0 -2016-07-01 21:00:00,11667.0 -2016-07-01 22:00:00,11290.0 -2016-07-01 23:00:00,11147.0 -2016-07-02 00:00:00,10384.0 -2016-06-30 01:00:00,10406.0 -2016-06-30 02:00:00,9681.0 -2016-06-30 03:00:00,9143.0 -2016-06-30 04:00:00,8854.0 -2016-06-30 05:00:00,8692.0 -2016-06-30 06:00:00,8773.0 -2016-06-30 07:00:00,9144.0 -2016-06-30 08:00:00,10016.0 -2016-06-30 09:00:00,11090.0 -2016-06-30 10:00:00,11916.0 -2016-06-30 11:00:00,12570.0 -2016-06-30 12:00:00,13091.0 -2016-06-30 13:00:00,13390.0 -2016-06-30 14:00:00,13645.0 -2016-06-30 15:00:00,13913.0 -2016-06-30 16:00:00,14092.0 -2016-06-30 17:00:00,14041.0 -2016-06-30 18:00:00,14045.0 -2016-06-30 19:00:00,13953.0 -2016-06-30 20:00:00,13641.0 -2016-06-30 21:00:00,13197.0 -2016-06-30 22:00:00,13074.0 -2016-06-30 23:00:00,12934.0 -2016-07-01 00:00:00,12135.0 -2016-06-29 01:00:00,9949.0 -2016-06-29 02:00:00,9304.0 -2016-06-29 03:00:00,8858.0 -2016-06-29 04:00:00,8597.0 -2016-06-29 05:00:00,8465.0 -2016-06-29 06:00:00,8587.0 -2016-06-29 07:00:00,8933.0 -2016-06-29 08:00:00,9791.0 -2016-06-29 09:00:00,10783.0 -2016-06-29 10:00:00,11541.0 -2016-06-29 11:00:00,12065.0 -2016-06-29 12:00:00,12536.0 -2016-06-29 13:00:00,12881.0 -2016-06-29 14:00:00,13181.0 -2016-06-29 15:00:00,13570.0 -2016-06-29 16:00:00,13866.0 -2016-06-29 17:00:00,14016.0 -2016-06-29 18:00:00,14068.0 -2016-06-29 19:00:00,13863.0 -2016-06-29 20:00:00,13281.0 -2016-06-29 21:00:00,12881.0 -2016-06-29 22:00:00,12554.0 -2016-06-29 23:00:00,12294.0 -2016-06-30 00:00:00,11414.0 -2016-06-28 01:00:00,12393.0 -2016-06-28 02:00:00,11379.0 -2016-06-28 03:00:00,10717.0 -2016-06-28 04:00:00,10161.0 -2016-06-28 05:00:00,9851.0 -2016-06-28 06:00:00,9829.0 -2016-06-28 07:00:00,10129.0 -2016-06-28 08:00:00,10818.0 -2016-06-28 09:00:00,11729.0 -2016-06-28 10:00:00,12252.0 -2016-06-28 11:00:00,12489.0 -2016-06-28 12:00:00,12711.0 -2016-06-28 13:00:00,12719.0 -2016-06-28 14:00:00,12753.0 -2016-06-28 15:00:00,12905.0 -2016-06-28 16:00:00,13001.0 -2016-06-28 17:00:00,13019.0 -2016-06-28 18:00:00,13023.0 -2016-06-28 19:00:00,12903.0 -2016-06-28 20:00:00,12506.0 -2016-06-28 21:00:00,12016.0 -2016-06-28 22:00:00,11766.0 -2016-06-28 23:00:00,11613.0 -2016-06-29 00:00:00,10836.0 -2016-06-27 01:00:00,12780.0 -2016-06-27 02:00:00,11813.0 -2016-06-27 03:00:00,11134.0 -2016-06-27 04:00:00,10648.0 -2016-06-27 05:00:00,10510.0 -2016-06-27 06:00:00,10624.0 -2016-06-27 07:00:00,11011.0 -2016-06-27 08:00:00,12199.0 -2016-06-27 09:00:00,13621.0 -2016-06-27 10:00:00,14864.0 -2016-06-27 11:00:00,15962.0 -2016-06-27 12:00:00,16931.0 -2016-06-27 13:00:00,17692.0 -2016-06-27 14:00:00,18251.0 -2016-06-27 15:00:00,18708.0 -2016-06-27 16:00:00,18971.0 -2016-06-27 17:00:00,19069.0 -2016-06-27 18:00:00,18926.0 -2016-06-27 19:00:00,18559.0 -2016-06-27 20:00:00,17737.0 -2016-06-27 21:00:00,16713.0 -2016-06-27 22:00:00,15724.0 -2016-06-27 23:00:00,15020.0 -2016-06-28 00:00:00,13749.0 -2016-06-26 01:00:00,14312.0 -2016-06-26 02:00:00,13346.0 -2016-06-26 03:00:00,12600.0 -2016-06-26 04:00:00,11929.0 -2016-06-26 05:00:00,11539.0 -2016-06-26 06:00:00,11247.0 -2016-06-26 07:00:00,11005.0 -2016-06-26 08:00:00,10998.0 -2016-06-26 09:00:00,11334.0 -2016-06-26 10:00:00,11904.0 -2016-06-26 11:00:00,12773.0 -2016-06-26 12:00:00,13875.0 -2016-06-26 13:00:00,14672.0 -2016-06-26 14:00:00,15291.0 -2016-06-26 15:00:00,15704.0 -2016-06-26 16:00:00,16263.0 -2016-06-26 17:00:00,16720.0 -2016-06-26 18:00:00,17096.0 -2016-06-26 19:00:00,17195.0 -2016-06-26 20:00:00,16935.0 -2016-06-26 21:00:00,16177.0 -2016-06-26 22:00:00,15382.0 -2016-06-26 23:00:00,14975.0 -2016-06-27 00:00:00,13926.0 -2016-06-25 01:00:00,11740.0 -2016-06-25 02:00:00,10824.0 -2016-06-25 03:00:00,10191.0 -2016-06-25 04:00:00,9770.0 -2016-06-25 05:00:00,9511.0 -2016-06-25 06:00:00,9378.0 -2016-06-25 07:00:00,9327.0 -2016-06-25 08:00:00,9804.0 -2016-06-25 09:00:00,10814.0 -2016-06-25 10:00:00,11979.0 -2016-06-25 11:00:00,13208.0 -2016-06-25 12:00:00,14326.0 -2016-06-25 13:00:00,15254.0 -2016-06-25 14:00:00,15972.0 -2016-06-25 15:00:00,16507.0 -2016-06-25 16:00:00,17040.0 -2016-06-25 17:00:00,17499.0 -2016-06-25 18:00:00,17794.0 -2016-06-25 19:00:00,17807.0 -2016-06-25 20:00:00,17539.0 -2016-06-25 21:00:00,16994.0 -2016-06-25 22:00:00,16493.0 -2016-06-25 23:00:00,16250.0 -2016-06-26 00:00:00,15365.0 -2016-06-24 01:00:00,10756.0 -2016-06-24 02:00:00,10005.0 -2016-06-24 03:00:00,9460.0 -2016-06-24 04:00:00,9144.0 -2016-06-24 05:00:00,9032.0 -2016-06-24 06:00:00,9109.0 -2016-06-24 07:00:00,9467.0 -2016-06-24 08:00:00,10403.0 -2016-06-24 09:00:00,11492.0 -2016-06-24 10:00:00,12469.0 -2016-06-24 11:00:00,13289.0 -2016-06-24 12:00:00,13987.0 -2016-06-24 13:00:00,14603.0 -2016-06-24 14:00:00,15073.0 -2016-06-24 15:00:00,15546.0 -2016-06-24 16:00:00,15904.0 -2016-06-24 17:00:00,16184.0 -2016-06-24 18:00:00,16335.0 -2016-06-24 19:00:00,16238.0 -2016-06-24 20:00:00,15697.0 -2016-06-24 21:00:00,14944.0 -2016-06-24 22:00:00,14233.0 -2016-06-24 23:00:00,13847.0 -2016-06-25 00:00:00,12846.0 -2016-06-23 01:00:00,11486.0 -2016-06-23 02:00:00,10759.0 -2016-06-23 03:00:00,10234.0 -2016-06-23 04:00:00,9944.0 -2016-06-23 05:00:00,9795.0 -2016-06-23 06:00:00,9876.0 -2016-06-23 07:00:00,10377.0 -2016-06-23 08:00:00,11270.0 -2016-06-23 09:00:00,12141.0 -2016-06-23 10:00:00,12732.0 -2016-06-23 11:00:00,13128.0 -2016-06-23 12:00:00,13449.0 -2016-06-23 13:00:00,13712.0 -2016-06-23 14:00:00,13915.0 -2016-06-23 15:00:00,14140.0 -2016-06-23 16:00:00,14762.0 -2016-06-23 17:00:00,15154.0 -2016-06-23 18:00:00,15312.0 -2016-06-23 19:00:00,15038.0 -2016-06-23 20:00:00,14351.0 -2016-06-23 21:00:00,13564.0 -2016-06-23 22:00:00,13061.0 -2016-06-23 23:00:00,12712.0 -2016-06-24 00:00:00,11794.0 -2016-06-22 01:00:00,11981.0 -2016-06-22 02:00:00,11025.0 -2016-06-22 03:00:00,10429.0 -2016-06-22 04:00:00,10085.0 -2016-06-22 05:00:00,9905.0 -2016-06-22 06:00:00,10047.0 -2016-06-22 07:00:00,10635.0 -2016-06-22 08:00:00,11480.0 -2016-06-22 09:00:00,12189.0 -2016-06-22 10:00:00,12722.0 -2016-06-22 11:00:00,13214.0 -2016-06-22 12:00:00,13575.0 -2016-06-22 13:00:00,13612.0 -2016-06-22 14:00:00,13856.0 -2016-06-22 15:00:00,14279.0 -2016-06-22 16:00:00,14501.0 -2016-06-22 17:00:00,14462.0 -2016-06-22 18:00:00,14420.0 -2016-06-22 19:00:00,14403.0 -2016-06-22 20:00:00,14184.0 -2016-06-22 21:00:00,13999.0 -2016-06-22 22:00:00,13920.0 -2016-06-22 23:00:00,13478.0 -2016-06-23 00:00:00,12497.0 -2016-06-21 01:00:00,13275.0 -2016-06-21 02:00:00,12050.0 -2016-06-21 03:00:00,11103.0 -2016-06-21 04:00:00,10524.0 -2016-06-21 05:00:00,10119.0 -2016-06-21 06:00:00,10081.0 -2016-06-21 07:00:00,10353.0 -2016-06-21 08:00:00,11337.0 -2016-06-21 09:00:00,12497.0 -2016-06-21 10:00:00,13454.0 -2016-06-21 11:00:00,14152.0 -2016-06-21 12:00:00,14777.0 -2016-06-21 13:00:00,15194.0 -2016-06-21 14:00:00,15544.0 -2016-06-21 15:00:00,16016.0 -2016-06-21 16:00:00,16376.0 -2016-06-21 17:00:00,16727.0 -2016-06-21 18:00:00,16919.0 -2016-06-21 19:00:00,16852.0 -2016-06-21 20:00:00,16354.0 -2016-06-21 21:00:00,15715.0 -2016-06-21 22:00:00,14993.0 -2016-06-21 23:00:00,14488.0 -2016-06-22 00:00:00,13291.0 -2016-06-20 01:00:00,13284.0 -2016-06-20 02:00:00,12458.0 -2016-06-20 03:00:00,11846.0 -2016-06-20 04:00:00,11518.0 -2016-06-20 05:00:00,11343.0 -2016-06-20 06:00:00,11506.0 -2016-06-20 07:00:00,12021.0 -2016-06-20 08:00:00,13181.0 -2016-06-20 09:00:00,14804.0 -2016-06-20 10:00:00,15976.0 -2016-06-20 11:00:00,16658.0 -2016-06-20 12:00:00,17820.0 -2016-06-20 13:00:00,18698.0 -2016-06-20 14:00:00,18976.0 -2016-06-20 15:00:00,19052.0 -2016-06-20 16:00:00,19488.0 -2016-06-20 17:00:00,19898.0 -2016-06-20 18:00:00,20189.0 -2016-06-20 19:00:00,20040.0 -2016-06-20 20:00:00,19429.0 -2016-06-20 21:00:00,18483.0 -2016-06-20 22:00:00,17367.0 -2016-06-20 23:00:00,16487.0 -2016-06-21 00:00:00,14921.0 -2016-06-19 01:00:00,11338.0 -2016-06-19 02:00:00,10495.0 -2016-06-19 03:00:00,9818.0 -2016-06-19 04:00:00,9352.0 -2016-06-19 05:00:00,9059.0 -2016-06-19 06:00:00,8862.0 -2016-06-19 07:00:00,8639.0 -2016-06-19 08:00:00,8886.0 -2016-06-19 09:00:00,9647.0 -2016-06-19 10:00:00,10791.0 -2016-06-19 11:00:00,12075.0 -2016-06-19 12:00:00,13287.0 -2016-06-19 13:00:00,14190.0 -2016-06-19 14:00:00,14864.0 -2016-06-19 15:00:00,15423.0 -2016-06-19 16:00:00,15923.0 -2016-06-19 17:00:00,16291.0 -2016-06-19 18:00:00,16565.0 -2016-06-19 19:00:00,16613.0 -2016-06-19 20:00:00,16412.0 -2016-06-19 21:00:00,16004.0 -2016-06-19 22:00:00,15477.0 -2016-06-19 23:00:00,15185.0 -2016-06-20 00:00:00,14341.0 -2016-06-18 01:00:00,11133.0 -2016-06-18 02:00:00,10200.0 -2016-06-18 03:00:00,9559.0 -2016-06-18 04:00:00,9106.0 -2016-06-18 05:00:00,8855.0 -2016-06-18 06:00:00,8760.0 -2016-06-18 07:00:00,8694.0 -2016-06-18 08:00:00,9020.0 -2016-06-18 09:00:00,9859.0 -2016-06-18 10:00:00,10791.0 -2016-06-18 11:00:00,11815.0 -2016-06-18 12:00:00,12726.0 -2016-06-18 13:00:00,13549.0 -2016-06-18 14:00:00,14127.0 -2016-06-18 15:00:00,14572.0 -2016-06-18 16:00:00,14877.0 -2016-06-18 17:00:00,15155.0 -2016-06-18 18:00:00,15186.0 -2016-06-18 19:00:00,14973.0 -2016-06-18 20:00:00,14679.0 -2016-06-18 21:00:00,14081.0 -2016-06-18 22:00:00,13483.0 -2016-06-18 23:00:00,13154.0 -2016-06-19 00:00:00,12326.0 -2016-06-17 01:00:00,10481.0 -2016-06-17 02:00:00,9779.0 -2016-06-17 03:00:00,9284.0 -2016-06-17 04:00:00,8994.0 -2016-06-17 05:00:00,8852.0 -2016-06-17 06:00:00,8988.0 -2016-06-17 07:00:00,9306.0 -2016-06-17 08:00:00,10245.0 -2016-06-17 09:00:00,11373.0 -2016-06-17 10:00:00,12303.0 -2016-06-17 11:00:00,13000.0 -2016-06-17 12:00:00,13644.0 -2016-06-17 13:00:00,14127.0 -2016-06-17 14:00:00,14570.0 -2016-06-17 15:00:00,15074.0 -2016-06-17 16:00:00,15494.0 -2016-06-17 17:00:00,15844.0 -2016-06-17 18:00:00,16045.0 -2016-06-17 19:00:00,15900.0 -2016-06-17 20:00:00,15368.0 -2016-06-17 21:00:00,14498.0 -2016-06-17 22:00:00,13787.0 -2016-06-17 23:00:00,13269.0 -2016-06-18 00:00:00,12221.0 -2016-06-16 01:00:00,14745.0 -2016-06-16 02:00:00,13392.0 -2016-06-16 03:00:00,12325.0 -2016-06-16 04:00:00,11553.0 -2016-06-16 05:00:00,11021.0 -2016-06-16 06:00:00,10921.0 -2016-06-16 07:00:00,11157.0 -2016-06-16 08:00:00,12155.0 -2016-06-16 09:00:00,13206.0 -2016-06-16 10:00:00,13611.0 -2016-06-16 11:00:00,13677.0 -2016-06-16 12:00:00,13747.0 -2016-06-16 13:00:00,13887.0 -2016-06-16 14:00:00,14072.0 -2016-06-16 15:00:00,14185.0 -2016-06-16 16:00:00,13920.0 -2016-06-16 17:00:00,13619.0 -2016-06-16 18:00:00,13513.0 -2016-06-16 19:00:00,13316.0 -2016-06-16 20:00:00,12822.0 -2016-06-16 21:00:00,12416.0 -2016-06-16 22:00:00,12380.0 -2016-06-16 23:00:00,12222.0 -2016-06-17 00:00:00,11400.0 -2016-06-15 01:00:00,12932.0 -2016-06-15 02:00:00,11974.0 -2016-06-15 03:00:00,11273.0 -2016-06-15 04:00:00,10815.0 -2016-06-15 05:00:00,10444.0 -2016-06-15 06:00:00,10433.0 -2016-06-15 07:00:00,10929.0 -2016-06-15 08:00:00,11845.0 -2016-06-15 09:00:00,13161.0 -2016-06-15 10:00:00,14445.0 -2016-06-15 11:00:00,15492.0 -2016-06-15 12:00:00,16573.0 -2016-06-15 13:00:00,17439.0 -2016-06-15 14:00:00,18078.0 -2016-06-15 15:00:00,18652.0 -2016-06-15 16:00:00,19025.0 -2016-06-15 17:00:00,19375.0 -2016-06-15 18:00:00,19520.0 -2016-06-15 19:00:00,19434.0 -2016-06-15 20:00:00,18995.0 -2016-06-15 21:00:00,18319.0 -2016-06-15 22:00:00,17774.0 -2016-06-15 23:00:00,17407.0 -2016-06-16 00:00:00,16217.0 -2016-06-14 01:00:00,11599.0 -2016-06-14 02:00:00,10672.0 -2016-06-14 03:00:00,9999.0 -2016-06-14 04:00:00,9609.0 -2016-06-14 05:00:00,9358.0 -2016-06-14 06:00:00,9462.0 -2016-06-14 07:00:00,9925.0 -2016-06-14 08:00:00,10792.0 -2016-06-14 09:00:00,11892.0 -2016-06-14 10:00:00,12896.0 -2016-06-14 11:00:00,13740.0 -2016-06-14 12:00:00,14700.0 -2016-06-14 13:00:00,15615.0 -2016-06-14 14:00:00,16328.0 -2016-06-14 15:00:00,17040.0 -2016-06-14 16:00:00,17424.0 -2016-06-14 17:00:00,17573.0 -2016-06-14 18:00:00,17424.0 -2016-06-14 19:00:00,16716.0 -2016-06-14 20:00:00,16032.0 -2016-06-14 21:00:00,15629.0 -2016-06-14 22:00:00,15479.0 -2016-06-14 23:00:00,15120.0 -2016-06-15 00:00:00,14103.0 -2016-06-13 01:00:00,9057.0 -2016-06-13 02:00:00,8587.0 -2016-06-13 03:00:00,8307.0 -2016-06-13 04:00:00,8160.0 -2016-06-13 05:00:00,8073.0 -2016-06-13 06:00:00,8252.0 -2016-06-13 07:00:00,8665.0 -2016-06-13 08:00:00,9662.0 -2016-06-13 09:00:00,10719.0 -2016-06-13 10:00:00,11567.0 -2016-06-13 11:00:00,12114.0 -2016-06-13 12:00:00,12730.0 -2016-06-13 13:00:00,13205.0 -2016-06-13 14:00:00,13754.0 -2016-06-13 15:00:00,14338.0 -2016-06-13 16:00:00,14808.0 -2016-06-13 17:00:00,15381.0 -2016-06-13 18:00:00,15937.0 -2016-06-13 19:00:00,16166.0 -2016-06-13 20:00:00,15790.0 -2016-06-13 21:00:00,15131.0 -2016-06-13 22:00:00,14653.0 -2016-06-13 23:00:00,14050.0 -2016-06-14 00:00:00,12816.0 -2016-06-12 01:00:00,13993.0 -2016-06-12 02:00:00,12932.0 -2016-06-12 03:00:00,11835.0 -2016-06-12 04:00:00,10767.0 -2016-06-12 05:00:00,10049.0 -2016-06-12 06:00:00,9537.0 -2016-06-12 07:00:00,9049.0 -2016-06-12 08:00:00,8973.0 -2016-06-12 09:00:00,9394.0 -2016-06-12 10:00:00,9888.0 -2016-06-12 11:00:00,10312.0 -2016-06-12 12:00:00,10417.0 -2016-06-12 13:00:00,10393.0 -2016-06-12 14:00:00,10394.0 -2016-06-12 15:00:00,10400.0 -2016-06-12 16:00:00,10419.0 -2016-06-12 17:00:00,10514.0 -2016-06-12 18:00:00,10675.0 -2016-06-12 19:00:00,10665.0 -2016-06-12 20:00:00,10468.0 -2016-06-12 21:00:00,10198.0 -2016-06-12 22:00:00,10134.0 -2016-06-12 23:00:00,10165.0 -2016-06-13 00:00:00,9635.0 -2016-06-11 01:00:00,13518.0 -2016-06-11 02:00:00,12318.0 -2016-06-11 03:00:00,11388.0 -2016-06-11 04:00:00,10752.0 -2016-06-11 05:00:00,10438.0 -2016-06-11 06:00:00,10247.0 -2016-06-11 07:00:00,10180.0 -2016-06-11 08:00:00,10612.0 -2016-06-11 09:00:00,11756.0 -2016-06-11 10:00:00,13077.0 -2016-06-11 11:00:00,14420.0 -2016-06-11 12:00:00,15559.0 -2016-06-11 13:00:00,16381.0 -2016-06-11 14:00:00,16885.0 -2016-06-11 15:00:00,17251.0 -2016-06-11 16:00:00,17496.0 -2016-06-11 17:00:00,17722.0 -2016-06-11 18:00:00,17861.0 -2016-06-11 19:00:00,17760.0 -2016-06-11 20:00:00,17445.0 -2016-06-11 21:00:00,16931.0 -2016-06-11 22:00:00,16456.0 -2016-06-11 23:00:00,16136.0 -2016-06-12 00:00:00,15129.0 -2016-06-10 01:00:00,9922.0 -2016-06-10 02:00:00,9340.0 -2016-06-10 03:00:00,8912.0 -2016-06-10 04:00:00,8676.0 -2016-06-10 05:00:00,8601.0 -2016-06-10 06:00:00,8747.0 -2016-06-10 07:00:00,9206.0 -2016-06-10 08:00:00,10068.0 -2016-06-10 09:00:00,11089.0 -2016-06-10 10:00:00,12139.0 -2016-06-10 11:00:00,13097.0 -2016-06-10 12:00:00,14071.0 -2016-06-10 13:00:00,14938.0 -2016-06-10 14:00:00,15693.0 -2016-06-10 15:00:00,16409.0 -2016-06-10 16:00:00,16977.0 -2016-06-10 17:00:00,17412.0 -2016-06-10 18:00:00,17744.0 -2016-06-10 19:00:00,17790.0 -2016-06-10 20:00:00,17437.0 -2016-06-10 21:00:00,16796.0 -2016-06-10 22:00:00,16281.0 -2016-06-10 23:00:00,15731.0 -2016-06-11 00:00:00,14690.0 -2016-06-09 01:00:00,9336.0 -2016-06-09 02:00:00,8734.0 -2016-06-09 03:00:00,8369.0 -2016-06-09 04:00:00,8126.0 -2016-06-09 05:00:00,8059.0 -2016-06-09 06:00:00,8213.0 -2016-06-09 07:00:00,8588.0 -2016-06-09 08:00:00,9432.0 -2016-06-09 09:00:00,10296.0 -2016-06-09 10:00:00,10870.0 -2016-06-09 11:00:00,11249.0 -2016-06-09 12:00:00,11564.0 -2016-06-09 13:00:00,11762.0 -2016-06-09 14:00:00,11862.0 -2016-06-09 15:00:00,11936.0 -2016-06-09 16:00:00,11868.0 -2016-06-09 17:00:00,11766.0 -2016-06-09 18:00:00,11704.0 -2016-06-09 19:00:00,11617.0 -2016-06-09 20:00:00,11454.0 -2016-06-09 21:00:00,11378.0 -2016-06-09 22:00:00,11621.0 -2016-06-09 23:00:00,11491.0 -2016-06-10 00:00:00,10761.0 -2016-06-08 01:00:00,9272.0 -2016-06-08 02:00:00,8680.0 -2016-06-08 03:00:00,8298.0 -2016-06-08 04:00:00,8077.0 -2016-06-08 05:00:00,7998.0 -2016-06-08 06:00:00,8147.0 -2016-06-08 07:00:00,8464.0 -2016-06-08 08:00:00,9343.0 -2016-06-08 09:00:00,10279.0 -2016-06-08 10:00:00,10908.0 -2016-06-08 11:00:00,11305.0 -2016-06-08 12:00:00,11649.0 -2016-06-08 13:00:00,11853.0 -2016-06-08 14:00:00,11994.0 -2016-06-08 15:00:00,12170.0 -2016-06-08 16:00:00,12207.0 -2016-06-08 17:00:00,12224.0 -2016-06-08 18:00:00,12214.0 -2016-06-08 19:00:00,12106.0 -2016-06-08 20:00:00,11703.0 -2016-06-08 21:00:00,11278.0 -2016-06-08 22:00:00,11157.0 -2016-06-08 23:00:00,10954.0 -2016-06-09 00:00:00,10146.0 -2016-06-07 01:00:00,10232.0 -2016-06-07 02:00:00,9446.0 -2016-06-07 03:00:00,8964.0 -2016-06-07 04:00:00,8642.0 -2016-06-07 05:00:00,8508.0 -2016-06-07 06:00:00,8618.0 -2016-06-07 07:00:00,8946.0 -2016-06-07 08:00:00,9765.0 -2016-06-07 09:00:00,10580.0 -2016-06-07 10:00:00,11104.0 -2016-06-07 11:00:00,11416.0 -2016-06-07 12:00:00,11668.0 -2016-06-07 13:00:00,11828.0 -2016-06-07 14:00:00,11943.0 -2016-06-07 15:00:00,12057.0 -2016-06-07 16:00:00,11998.0 -2016-06-07 17:00:00,11833.0 -2016-06-07 18:00:00,11670.0 -2016-06-07 19:00:00,11456.0 -2016-06-07 20:00:00,11097.0 -2016-06-07 21:00:00,10898.0 -2016-06-07 22:00:00,10953.0 -2016-06-07 23:00:00,10849.0 -2016-06-08 00:00:00,10076.0 -2016-06-06 01:00:00,10054.0 -2016-06-06 02:00:00,9391.0 -2016-06-06 03:00:00,8935.0 -2016-06-06 04:00:00,8650.0 -2016-06-06 05:00:00,8521.0 -2016-06-06 06:00:00,8683.0 -2016-06-06 07:00:00,9107.0 -2016-06-06 08:00:00,10201.0 -2016-06-06 09:00:00,11257.0 -2016-06-06 10:00:00,12193.0 -2016-06-06 11:00:00,12899.0 -2016-06-06 12:00:00,13517.0 -2016-06-06 13:00:00,13999.0 -2016-06-06 14:00:00,14216.0 -2016-06-06 15:00:00,14432.0 -2016-06-06 16:00:00,14523.0 -2016-06-06 17:00:00,14559.0 -2016-06-06 18:00:00,14474.0 -2016-06-06 19:00:00,14201.0 -2016-06-06 20:00:00,13584.0 -2016-06-06 21:00:00,12915.0 -2016-06-06 22:00:00,12611.0 -2016-06-06 23:00:00,12279.0 -2016-06-07 00:00:00,11343.0 -2016-06-05 01:00:00,9838.0 -2016-06-05 02:00:00,9254.0 -2016-06-05 03:00:00,8822.0 -2016-06-05 04:00:00,8538.0 -2016-06-05 05:00:00,8329.0 -2016-06-05 06:00:00,8262.0 -2016-06-05 07:00:00,8062.0 -2016-06-05 08:00:00,8177.0 -2016-06-05 09:00:00,8551.0 -2016-06-05 10:00:00,8998.0 -2016-06-05 11:00:00,9435.0 -2016-06-05 12:00:00,9911.0 -2016-06-05 13:00:00,10234.0 -2016-06-05 14:00:00,10544.0 -2016-06-05 15:00:00,10813.0 -2016-06-05 16:00:00,11116.0 -2016-06-05 17:00:00,11370.0 -2016-06-05 18:00:00,11697.0 -2016-06-05 19:00:00,11933.0 -2016-06-05 20:00:00,11889.0 -2016-06-05 21:00:00,11646.0 -2016-06-05 22:00:00,11700.0 -2016-06-05 23:00:00,11666.0 -2016-06-06 00:00:00,10923.0 -2016-06-04 01:00:00,11251.0 -2016-06-04 02:00:00,10355.0 -2016-06-04 03:00:00,9776.0 -2016-06-04 04:00:00,9298.0 -2016-06-04 05:00:00,9032.0 -2016-06-04 06:00:00,8932.0 -2016-06-04 07:00:00,8954.0 -2016-06-04 08:00:00,9202.0 -2016-06-04 09:00:00,9974.0 -2016-06-04 10:00:00,10634.0 -2016-06-04 11:00:00,11169.0 -2016-06-04 12:00:00,11378.0 -2016-06-04 13:00:00,11508.0 -2016-06-04 14:00:00,11419.0 -2016-06-04 15:00:00,11298.0 -2016-06-04 16:00:00,11114.0 -2016-06-04 17:00:00,11078.0 -2016-06-04 18:00:00,11203.0 -2016-06-04 19:00:00,11313.0 -2016-06-04 20:00:00,11235.0 -2016-06-04 21:00:00,11120.0 -2016-06-04 22:00:00,11177.0 -2016-06-04 23:00:00,11157.0 -2016-06-05 00:00:00,10598.0 -2016-06-03 01:00:00,10490.0 -2016-06-03 02:00:00,9656.0 -2016-06-03 03:00:00,9133.0 -2016-06-03 04:00:00,8786.0 -2016-06-03 05:00:00,8625.0 -2016-06-03 06:00:00,8747.0 -2016-06-03 07:00:00,9035.0 -2016-06-03 08:00:00,9992.0 -2016-06-03 09:00:00,11034.0 -2016-06-03 10:00:00,11852.0 -2016-06-03 11:00:00,12482.0 -2016-06-03 12:00:00,13027.0 -2016-06-03 13:00:00,13484.0 -2016-06-03 14:00:00,13894.0 -2016-06-03 15:00:00,14437.0 -2016-06-03 16:00:00,14818.0 -2016-06-03 17:00:00,15169.0 -2016-06-03 18:00:00,15395.0 -2016-06-03 19:00:00,15252.0 -2016-06-03 20:00:00,14670.0 -2016-06-03 21:00:00,13954.0 -2016-06-03 22:00:00,13642.0 -2016-06-03 23:00:00,13290.0 -2016-06-04 00:00:00,12300.0 -2016-06-02 01:00:00,10884.0 -2016-06-02 02:00:00,9999.0 -2016-06-02 03:00:00,9387.0 -2016-06-02 04:00:00,8970.0 -2016-06-02 05:00:00,8809.0 -2016-06-02 06:00:00,8913.0 -2016-06-02 07:00:00,9271.0 -2016-06-02 08:00:00,10212.0 -2016-06-02 09:00:00,11304.0 -2016-06-02 10:00:00,12103.0 -2016-06-02 11:00:00,12654.0 -2016-06-02 12:00:00,13121.0 -2016-06-02 13:00:00,13450.0 -2016-06-02 14:00:00,13720.0 -2016-06-02 15:00:00,14152.0 -2016-06-02 16:00:00,14438.0 -2016-06-02 17:00:00,14661.0 -2016-06-02 18:00:00,14826.0 -2016-06-02 19:00:00,14713.0 -2016-06-02 20:00:00,14228.0 -2016-06-02 21:00:00,13565.0 -2016-06-02 22:00:00,13109.0 -2016-06-02 23:00:00,12668.0 -2016-06-03 00:00:00,11553.0 -2016-06-01 01:00:00,10301.0 -2016-06-01 02:00:00,9651.0 -2016-06-01 03:00:00,9220.0 -2016-06-01 04:00:00,8981.0 -2016-06-01 05:00:00,8869.0 -2016-06-01 06:00:00,9042.0 -2016-06-01 07:00:00,9566.0 -2016-06-01 08:00:00,10527.0 -2016-06-01 09:00:00,11356.0 -2016-06-01 10:00:00,11825.0 -2016-06-01 11:00:00,12111.0 -2016-06-01 12:00:00,12451.0 -2016-06-01 13:00:00,12827.0 -2016-06-01 14:00:00,13341.0 -2016-06-01 15:00:00,13780.0 -2016-06-01 16:00:00,14164.0 -2016-06-01 17:00:00,14488.0 -2016-06-01 18:00:00,14766.0 -2016-06-01 19:00:00,14772.0 -2016-06-01 20:00:00,14423.0 -2016-06-01 21:00:00,13933.0 -2016-06-01 22:00:00,13553.0 -2016-06-01 23:00:00,13149.0 -2016-06-02 00:00:00,12094.0 -2016-05-31 01:00:00,10556.0 -2016-05-31 02:00:00,9739.0 -2016-05-31 03:00:00,9249.0 -2016-05-31 04:00:00,8900.0 -2016-05-31 05:00:00,8784.0 -2016-05-31 06:00:00,8950.0 -2016-05-31 07:00:00,9405.0 -2016-05-31 08:00:00,10511.0 -2016-05-31 09:00:00,11738.0 -2016-05-31 10:00:00,12605.0 -2016-05-31 11:00:00,13301.0 -2016-05-31 12:00:00,14143.0 -2016-05-31 13:00:00,14740.0 -2016-05-31 14:00:00,15244.0 -2016-05-31 15:00:00,15473.0 -2016-05-31 16:00:00,15205.0 -2016-05-31 17:00:00,14366.0 -2016-05-31 18:00:00,13636.0 -2016-05-31 19:00:00,13214.0 -2016-05-31 20:00:00,12694.0 -2016-05-31 21:00:00,12446.0 -2016-05-31 22:00:00,12508.0 -2016-05-31 23:00:00,12157.0 -2016-06-01 00:00:00,11259.0 -2016-05-30 01:00:00,10082.0 -2016-05-30 02:00:00,9374.0 -2016-05-30 03:00:00,8874.0 -2016-05-30 04:00:00,8514.0 -2016-05-30 05:00:00,8314.0 -2016-05-30 06:00:00,8263.0 -2016-05-30 07:00:00,8181.0 -2016-05-30 08:00:00,8424.0 -2016-05-30 09:00:00,9071.0 -2016-05-30 10:00:00,9902.0 -2016-05-30 11:00:00,10817.0 -2016-05-30 12:00:00,11699.0 -2016-05-30 13:00:00,12377.0 -2016-05-30 14:00:00,12875.0 -2016-05-30 15:00:00,13239.0 -2016-05-30 16:00:00,13544.0 -2016-05-30 17:00:00,13859.0 -2016-05-30 18:00:00,14061.0 -2016-05-30 19:00:00,14096.0 -2016-05-30 20:00:00,13796.0 -2016-05-30 21:00:00,13233.0 -2016-05-30 22:00:00,12980.0 -2016-05-30 23:00:00,12578.0 -2016-05-31 00:00:00,11582.0 -2016-05-29 01:00:00,10595.0 -2016-05-29 02:00:00,9835.0 -2016-05-29 03:00:00,9203.0 -2016-05-29 04:00:00,8804.0 -2016-05-29 05:00:00,8575.0 -2016-05-29 06:00:00,8418.0 -2016-05-29 07:00:00,8258.0 -2016-05-29 08:00:00,8415.0 -2016-05-29 09:00:00,9092.0 -2016-05-29 10:00:00,9921.0 -2016-05-29 11:00:00,10698.0 -2016-05-29 12:00:00,11238.0 -2016-05-29 13:00:00,11493.0 -2016-05-29 14:00:00,11808.0 -2016-05-29 15:00:00,12019.0 -2016-05-29 16:00:00,12298.0 -2016-05-29 17:00:00,12503.0 -2016-05-29 18:00:00,12648.0 -2016-05-29 19:00:00,12653.0 -2016-05-29 20:00:00,12421.0 -2016-05-29 21:00:00,11898.0 -2016-05-29 22:00:00,11676.0 -2016-05-29 23:00:00,11509.0 -2016-05-30 00:00:00,10868.0 -2016-05-28 01:00:00,11108.0 -2016-05-28 02:00:00,10268.0 -2016-05-28 03:00:00,9790.0 -2016-05-28 04:00:00,9390.0 -2016-05-28 05:00:00,9183.0 -2016-05-28 06:00:00,9139.0 -2016-05-28 07:00:00,9183.0 -2016-05-28 08:00:00,9369.0 -2016-05-28 09:00:00,9946.0 -2016-05-28 10:00:00,10700.0 -2016-05-28 11:00:00,11436.0 -2016-05-28 12:00:00,12049.0 -2016-05-28 13:00:00,12275.0 -2016-05-28 14:00:00,12309.0 -2016-05-28 15:00:00,12386.0 -2016-05-28 16:00:00,12751.0 -2016-05-28 17:00:00,13180.0 -2016-05-28 18:00:00,13534.0 -2016-05-28 19:00:00,13657.0 -2016-05-28 20:00:00,13440.0 -2016-05-28 21:00:00,12946.0 -2016-05-28 22:00:00,12786.0 -2016-05-28 23:00:00,12495.0 -2016-05-29 00:00:00,11591.0 -2016-05-27 01:00:00,12095.0 -2016-05-27 02:00:00,11213.0 -2016-05-27 03:00:00,10586.0 -2016-05-27 04:00:00,10134.0 -2016-05-27 05:00:00,9855.0 -2016-05-27 06:00:00,9879.0 -2016-05-27 07:00:00,10279.0 -2016-05-27 08:00:00,11178.0 -2016-05-27 09:00:00,12228.0 -2016-05-27 10:00:00,12956.0 -2016-05-27 11:00:00,13465.0 -2016-05-27 12:00:00,14085.0 -2016-05-27 13:00:00,14617.0 -2016-05-27 14:00:00,15179.0 -2016-05-27 15:00:00,15414.0 -2016-05-27 16:00:00,15353.0 -2016-05-27 17:00:00,14905.0 -2016-05-27 18:00:00,14726.0 -2016-05-27 19:00:00,14356.0 -2016-05-27 20:00:00,13924.0 -2016-05-27 21:00:00,13550.0 -2016-05-27 22:00:00,13484.0 -2016-05-27 23:00:00,13085.0 -2016-05-28 00:00:00,12162.0 -2016-05-26 01:00:00,11032.0 -2016-05-26 02:00:00,10236.0 -2016-05-26 03:00:00,9759.0 -2016-05-26 04:00:00,9423.0 -2016-05-26 05:00:00,9296.0 -2016-05-26 06:00:00,9470.0 -2016-05-26 07:00:00,9951.0 -2016-05-26 08:00:00,11033.0 -2016-05-26 09:00:00,12144.0 -2016-05-26 10:00:00,12811.0 -2016-05-26 11:00:00,13217.0 -2016-05-26 12:00:00,13528.0 -2016-05-26 13:00:00,13875.0 -2016-05-26 14:00:00,14355.0 -2016-05-26 15:00:00,14903.0 -2016-05-26 16:00:00,15278.0 -2016-05-26 17:00:00,15661.0 -2016-05-26 18:00:00,16013.0 -2016-05-26 19:00:00,16044.0 -2016-05-26 20:00:00,15669.0 -2016-05-26 21:00:00,15094.0 -2016-05-26 22:00:00,14930.0 -2016-05-26 23:00:00,14416.0 -2016-05-27 00:00:00,13271.0 -2016-05-25 01:00:00,11488.0 -2016-05-25 02:00:00,10673.0 -2016-05-25 03:00:00,10145.0 -2016-05-25 04:00:00,9732.0 -2016-05-25 05:00:00,9483.0 -2016-05-25 06:00:00,9543.0 -2016-05-25 07:00:00,9919.0 -2016-05-25 08:00:00,10954.0 -2016-05-25 09:00:00,12164.0 -2016-05-25 10:00:00,13068.0 -2016-05-25 11:00:00,13821.0 -2016-05-25 12:00:00,14500.0 -2016-05-25 13:00:00,15041.0 -2016-05-25 14:00:00,15467.0 -2016-05-25 15:00:00,15951.0 -2016-05-25 16:00:00,16228.0 -2016-05-25 17:00:00,16396.0 -2016-05-25 18:00:00,15960.0 -2016-05-25 19:00:00,15167.0 -2016-05-25 20:00:00,14468.0 -2016-05-25 21:00:00,13889.0 -2016-05-25 22:00:00,13685.0 -2016-05-25 23:00:00,13143.0 -2016-05-26 00:00:00,12138.0 -2016-05-24 01:00:00,9769.0 -2016-05-24 02:00:00,9101.0 -2016-05-24 03:00:00,8696.0 -2016-05-24 04:00:00,8413.0 -2016-05-24 05:00:00,8294.0 -2016-05-24 06:00:00,8442.0 -2016-05-24 07:00:00,8793.0 -2016-05-24 08:00:00,9738.0 -2016-05-24 09:00:00,10686.0 -2016-05-24 10:00:00,11433.0 -2016-05-24 11:00:00,12070.0 -2016-05-24 12:00:00,12664.0 -2016-05-24 13:00:00,13069.0 -2016-05-24 14:00:00,13415.0 -2016-05-24 15:00:00,13785.0 -2016-05-24 16:00:00,14051.0 -2016-05-24 17:00:00,14248.0 -2016-05-24 18:00:00,14457.0 -2016-05-24 19:00:00,14442.0 -2016-05-24 20:00:00,14107.0 -2016-05-24 21:00:00,13783.0 -2016-05-24 22:00:00,13853.0 -2016-05-24 23:00:00,13537.0 -2016-05-25 00:00:00,12558.0 -2016-05-23 01:00:00,8625.0 -2016-05-23 02:00:00,8116.0 -2016-05-23 03:00:00,7835.0 -2016-05-23 04:00:00,7644.0 -2016-05-23 05:00:00,7643.0 -2016-05-23 06:00:00,7845.0 -2016-05-23 07:00:00,8363.0 -2016-05-23 08:00:00,9266.0 -2016-05-23 09:00:00,10243.0 -2016-05-23 10:00:00,10805.0 -2016-05-23 11:00:00,11273.0 -2016-05-23 12:00:00,11672.0 -2016-05-23 13:00:00,11919.0 -2016-05-23 14:00:00,12142.0 -2016-05-23 15:00:00,12401.0 -2016-05-23 16:00:00,12553.0 -2016-05-23 17:00:00,12692.0 -2016-05-23 18:00:00,12763.0 -2016-05-23 19:00:00,12662.0 -2016-05-23 20:00:00,12278.0 -2016-05-23 21:00:00,11919.0 -2016-05-23 22:00:00,11953.0 -2016-05-23 23:00:00,11562.0 -2016-05-24 00:00:00,10687.0 -2016-05-22 01:00:00,8510.0 -2016-05-22 02:00:00,8041.0 -2016-05-22 03:00:00,7722.0 -2016-05-22 04:00:00,7534.0 -2016-05-22 05:00:00,7416.0 -2016-05-22 06:00:00,7415.0 -2016-05-22 07:00:00,7290.0 -2016-05-22 08:00:00,7304.0 -2016-05-22 09:00:00,7764.0 -2016-05-22 10:00:00,8262.0 -2016-05-22 11:00:00,8647.0 -2016-05-22 12:00:00,9050.0 -2016-05-22 13:00:00,9263.0 -2016-05-22 14:00:00,9449.0 -2016-05-22 15:00:00,9546.0 -2016-05-22 16:00:00,9674.0 -2016-05-22 17:00:00,9774.0 -2016-05-22 18:00:00,9881.0 -2016-05-22 19:00:00,9966.0 -2016-05-22 20:00:00,9876.0 -2016-05-22 21:00:00,9815.0 -2016-05-22 22:00:00,10040.0 -2016-05-22 23:00:00,9955.0 -2016-05-23 00:00:00,9288.0 -2016-05-21 01:00:00,8826.0 -2016-05-21 02:00:00,8312.0 -2016-05-21 03:00:00,8013.0 -2016-05-21 04:00:00,7796.0 -2016-05-21 05:00:00,7693.0 -2016-05-21 06:00:00,7749.0 -2016-05-21 07:00:00,7836.0 -2016-05-21 08:00:00,8014.0 -2016-05-21 09:00:00,8485.0 -2016-05-21 10:00:00,8989.0 -2016-05-21 11:00:00,9314.0 -2016-05-21 12:00:00,9546.0 -2016-05-21 13:00:00,9632.0 -2016-05-21 14:00:00,9629.0 -2016-05-21 15:00:00,9625.0 -2016-05-21 16:00:00,9593.0 -2016-05-21 17:00:00,9618.0 -2016-05-21 18:00:00,9607.0 -2016-05-21 19:00:00,9599.0 -2016-05-21 20:00:00,9468.0 -2016-05-21 21:00:00,9417.0 -2016-05-21 22:00:00,9661.0 -2016-05-21 23:00:00,9622.0 -2016-05-22 00:00:00,9105.0 -2016-05-20 01:00:00,8900.0 -2016-05-20 02:00:00,8410.0 -2016-05-20 03:00:00,8114.0 -2016-05-20 04:00:00,7942.0 -2016-05-20 05:00:00,7866.0 -2016-05-20 06:00:00,8051.0 -2016-05-20 07:00:00,8514.0 -2016-05-20 08:00:00,9321.0 -2016-05-20 09:00:00,10162.0 -2016-05-20 10:00:00,10567.0 -2016-05-20 11:00:00,10820.0 -2016-05-20 12:00:00,11034.0 -2016-05-20 13:00:00,11077.0 -2016-05-20 14:00:00,11077.0 -2016-05-20 15:00:00,11117.0 -2016-05-20 16:00:00,10973.0 -2016-05-20 17:00:00,10780.0 -2016-05-20 18:00:00,10611.0 -2016-05-20 19:00:00,10402.0 -2016-05-20 20:00:00,10185.0 -2016-05-20 21:00:00,10130.0 -2016-05-20 22:00:00,10412.0 -2016-05-20 23:00:00,10167.0 -2016-05-21 00:00:00,9513.0 -2016-05-19 01:00:00,8926.0 -2016-05-19 02:00:00,8441.0 -2016-05-19 03:00:00,8188.0 -2016-05-19 04:00:00,8026.0 -2016-05-19 05:00:00,8001.0 -2016-05-19 06:00:00,8247.0 -2016-05-19 07:00:00,8729.0 -2016-05-19 08:00:00,9569.0 -2016-05-19 09:00:00,10285.0 -2016-05-19 10:00:00,10636.0 -2016-05-19 11:00:00,10878.0 -2016-05-19 12:00:00,11085.0 -2016-05-19 13:00:00,11184.0 -2016-05-19 14:00:00,11210.0 -2016-05-19 15:00:00,11278.0 -2016-05-19 16:00:00,11233.0 -2016-05-19 17:00:00,11119.0 -2016-05-19 18:00:00,10936.0 -2016-05-19 19:00:00,10779.0 -2016-05-19 20:00:00,10554.0 -2016-05-19 21:00:00,10384.0 -2016-05-19 22:00:00,10643.0 -2016-05-19 23:00:00,10421.0 -2016-05-20 00:00:00,9655.0 -2016-05-18 01:00:00,8924.0 -2016-05-18 02:00:00,8484.0 -2016-05-18 03:00:00,8224.0 -2016-05-18 04:00:00,8074.0 -2016-05-18 05:00:00,8043.0 -2016-05-18 06:00:00,8254.0 -2016-05-18 07:00:00,8835.0 -2016-05-18 08:00:00,9657.0 -2016-05-18 09:00:00,10367.0 -2016-05-18 10:00:00,10623.0 -2016-05-18 11:00:00,10755.0 -2016-05-18 12:00:00,10869.0 -2016-05-18 13:00:00,10944.0 -2016-05-18 14:00:00,10985.0 -2016-05-18 15:00:00,11029.0 -2016-05-18 16:00:00,10904.0 -2016-05-18 17:00:00,10820.0 -2016-05-18 18:00:00,10668.0 -2016-05-18 19:00:00,10511.0 -2016-05-18 20:00:00,10271.0 -2016-05-18 21:00:00,10194.0 -2016-05-18 22:00:00,10546.0 -2016-05-18 23:00:00,10342.0 -2016-05-19 00:00:00,9639.0 -2016-05-17 01:00:00,8848.0 -2016-05-17 02:00:00,8385.0 -2016-05-17 03:00:00,8104.0 -2016-05-17 04:00:00,7918.0 -2016-05-17 05:00:00,7888.0 -2016-05-17 06:00:00,8067.0 -2016-05-17 07:00:00,8633.0 -2016-05-17 08:00:00,9529.0 -2016-05-17 09:00:00,10277.0 -2016-05-17 10:00:00,10622.0 -2016-05-17 11:00:00,10771.0 -2016-05-17 12:00:00,10910.0 -2016-05-17 13:00:00,10950.0 -2016-05-17 14:00:00,10948.0 -2016-05-17 15:00:00,10952.0 -2016-05-17 16:00:00,10840.0 -2016-05-17 17:00:00,10695.0 -2016-05-17 18:00:00,10580.0 -2016-05-17 19:00:00,10414.0 -2016-05-17 20:00:00,10219.0 -2016-05-17 21:00:00,10245.0 -2016-05-17 22:00:00,10629.0 -2016-05-17 23:00:00,10397.0 -2016-05-18 00:00:00,9633.0 -2016-05-16 01:00:00,8359.0 -2016-05-16 02:00:00,8067.0 -2016-05-16 03:00:00,7902.0 -2016-05-16 04:00:00,7879.0 -2016-05-16 05:00:00,7912.0 -2016-05-16 06:00:00,8153.0 -2016-05-16 07:00:00,8737.0 -2016-05-16 08:00:00,9653.0 -2016-05-16 09:00:00,10235.0 -2016-05-16 10:00:00,10552.0 -2016-05-16 11:00:00,10727.0 -2016-05-16 12:00:00,10948.0 -2016-05-16 13:00:00,11014.0 -2016-05-16 14:00:00,11058.0 -2016-05-16 15:00:00,11113.0 -2016-05-16 16:00:00,11042.0 -2016-05-16 17:00:00,10893.0 -2016-05-16 18:00:00,10763.0 -2016-05-16 19:00:00,10611.0 -2016-05-16 20:00:00,10393.0 -2016-05-16 21:00:00,10441.0 -2016-05-16 22:00:00,10791.0 -2016-05-16 23:00:00,10379.0 -2016-05-17 00:00:00,9604.0 -2016-05-15 01:00:00,8648.0 -2016-05-15 02:00:00,8223.0 -2016-05-15 03:00:00,8025.0 -2016-05-15 04:00:00,7840.0 -2016-05-15 05:00:00,7780.0 -2016-05-15 06:00:00,7842.0 -2016-05-15 07:00:00,7795.0 -2016-05-15 08:00:00,7851.0 -2016-05-15 09:00:00,8064.0 -2016-05-15 10:00:00,8355.0 -2016-05-15 11:00:00,8580.0 -2016-05-15 12:00:00,8709.0 -2016-05-15 13:00:00,8753.0 -2016-05-15 14:00:00,8709.0 -2016-05-15 15:00:00,8676.0 -2016-05-15 16:00:00,8589.0 -2016-05-15 17:00:00,8590.0 -2016-05-15 18:00:00,8603.0 -2016-05-15 19:00:00,8686.0 -2016-05-15 20:00:00,8708.0 -2016-05-15 21:00:00,8863.0 -2016-05-15 22:00:00,9383.0 -2016-05-15 23:00:00,9337.0 -2016-05-16 00:00:00,8808.0 -2016-05-14 01:00:00,9008.0 -2016-05-14 02:00:00,8461.0 -2016-05-14 03:00:00,8113.0 -2016-05-14 04:00:00,8010.0 -2016-05-14 05:00:00,7928.0 -2016-05-14 06:00:00,8006.0 -2016-05-14 07:00:00,8111.0 -2016-05-14 08:00:00,8343.0 -2016-05-14 09:00:00,8817.0 -2016-05-14 10:00:00,9218.0 -2016-05-14 11:00:00,9502.0 -2016-05-14 12:00:00,9581.0 -2016-05-14 13:00:00,9615.0 -2016-05-14 14:00:00,9584.0 -2016-05-14 15:00:00,9466.0 -2016-05-14 16:00:00,9364.0 -2016-05-14 17:00:00,9307.0 -2016-05-14 18:00:00,9257.0 -2016-05-14 19:00:00,9262.0 -2016-05-14 20:00:00,9212.0 -2016-05-14 21:00:00,9287.0 -2016-05-14 22:00:00,9675.0 -2016-05-14 23:00:00,9580.0 -2016-05-15 00:00:00,9115.0 -2016-05-13 01:00:00,8949.0 -2016-05-13 02:00:00,8427.0 -2016-05-13 03:00:00,8092.0 -2016-05-13 04:00:00,7927.0 -2016-05-13 05:00:00,7866.0 -2016-05-13 06:00:00,8059.0 -2016-05-13 07:00:00,8512.0 -2016-05-13 08:00:00,9339.0 -2016-05-13 09:00:00,10084.0 -2016-05-13 10:00:00,10497.0 -2016-05-13 11:00:00,10761.0 -2016-05-13 12:00:00,10993.0 -2016-05-13 13:00:00,11048.0 -2016-05-13 14:00:00,11041.0 -2016-05-13 15:00:00,11088.0 -2016-05-13 16:00:00,11019.0 -2016-05-13 17:00:00,10816.0 -2016-05-13 18:00:00,10677.0 -2016-05-13 19:00:00,10548.0 -2016-05-13 20:00:00,10334.0 -2016-05-13 21:00:00,10492.0 -2016-05-13 22:00:00,10660.0 -2016-05-13 23:00:00,10333.0 -2016-05-14 00:00:00,9680.0 -2016-05-12 01:00:00,9088.0 -2016-05-12 02:00:00,8596.0 -2016-05-12 03:00:00,8294.0 -2016-05-12 04:00:00,8155.0 -2016-05-12 05:00:00,8100.0 -2016-05-12 06:00:00,8296.0 -2016-05-12 07:00:00,8855.0 -2016-05-12 08:00:00,9741.0 -2016-05-12 09:00:00,10641.0 -2016-05-12 10:00:00,11137.0 -2016-05-12 11:00:00,11426.0 -2016-05-12 12:00:00,11623.0 -2016-05-12 13:00:00,11610.0 -2016-05-12 14:00:00,11589.0 -2016-05-12 15:00:00,11595.0 -2016-05-12 16:00:00,11500.0 -2016-05-12 17:00:00,11342.0 -2016-05-12 18:00:00,11282.0 -2016-05-12 19:00:00,11094.0 -2016-05-12 20:00:00,10708.0 -2016-05-12 21:00:00,10563.0 -2016-05-12 22:00:00,10856.0 -2016-05-12 23:00:00,10506.0 -2016-05-13 00:00:00,9732.0 -2016-05-11 01:00:00,9055.0 -2016-05-11 02:00:00,8537.0 -2016-05-11 03:00:00,8217.0 -2016-05-11 04:00:00,8051.0 -2016-05-11 05:00:00,7990.0 -2016-05-11 06:00:00,8166.0 -2016-05-11 07:00:00,8760.0 -2016-05-11 08:00:00,9594.0 -2016-05-11 09:00:00,10338.0 -2016-05-11 10:00:00,10692.0 -2016-05-11 11:00:00,10911.0 -2016-05-11 12:00:00,11121.0 -2016-05-11 13:00:00,11203.0 -2016-05-11 14:00:00,11263.0 -2016-05-11 15:00:00,11341.0 -2016-05-11 16:00:00,11333.0 -2016-05-11 17:00:00,11251.0 -2016-05-11 18:00:00,11128.0 -2016-05-11 19:00:00,11001.0 -2016-05-11 20:00:00,10717.0 -2016-05-11 21:00:00,10716.0 -2016-05-11 22:00:00,11058.0 -2016-05-11 23:00:00,10645.0 -2016-05-12 00:00:00,9844.0 -2016-05-10 01:00:00,8923.0 -2016-05-10 02:00:00,8477.0 -2016-05-10 03:00:00,8212.0 -2016-05-10 04:00:00,8045.0 -2016-05-10 05:00:00,7977.0 -2016-05-10 06:00:00,8187.0 -2016-05-10 07:00:00,8826.0 -2016-05-10 08:00:00,9787.0 -2016-05-10 09:00:00,10543.0 -2016-05-10 10:00:00,10886.0 -2016-05-10 11:00:00,11073.0 -2016-05-10 12:00:00,11258.0 -2016-05-10 13:00:00,11321.0 -2016-05-10 14:00:00,11271.0 -2016-05-10 15:00:00,11231.0 -2016-05-10 16:00:00,11160.0 -2016-05-10 17:00:00,11044.0 -2016-05-10 18:00:00,10991.0 -2016-05-10 19:00:00,10900.0 -2016-05-10 20:00:00,10655.0 -2016-05-10 21:00:00,10628.0 -2016-05-10 22:00:00,10928.0 -2016-05-10 23:00:00,10543.0 -2016-05-11 00:00:00,9785.0 -2016-05-09 01:00:00,8193.0 -2016-05-09 02:00:00,7851.0 -2016-05-09 03:00:00,7672.0 -2016-05-09 04:00:00,7584.0 -2016-05-09 05:00:00,7581.0 -2016-05-09 06:00:00,7825.0 -2016-05-09 07:00:00,8441.0 -2016-05-09 08:00:00,9350.0 -2016-05-09 09:00:00,10154.0 -2016-05-09 10:00:00,10558.0 -2016-05-09 11:00:00,10749.0 -2016-05-09 12:00:00,10935.0 -2016-05-09 13:00:00,10985.0 -2016-05-09 14:00:00,10946.0 -2016-05-09 15:00:00,10961.0 -2016-05-09 16:00:00,10833.0 -2016-05-09 17:00:00,10691.0 -2016-05-09 18:00:00,10644.0 -2016-05-09 19:00:00,10659.0 -2016-05-09 20:00:00,10668.0 -2016-05-09 21:00:00,10840.0 -2016-05-09 22:00:00,10854.0 -2016-05-09 23:00:00,10419.0 -2016-05-10 00:00:00,9659.0 -2016-05-08 01:00:00,8267.0 -2016-05-08 02:00:00,7872.0 -2016-05-08 03:00:00,7592.0 -2016-05-08 04:00:00,7392.0 -2016-05-08 05:00:00,7383.0 -2016-05-08 06:00:00,7366.0 -2016-05-08 07:00:00,7421.0 -2016-05-08 08:00:00,7372.0 -2016-05-08 09:00:00,7652.0 -2016-05-08 10:00:00,8026.0 -2016-05-08 11:00:00,8293.0 -2016-05-08 12:00:00,8476.0 -2016-05-08 13:00:00,8569.0 -2016-05-08 14:00:00,8619.0 -2016-05-08 15:00:00,8586.0 -2016-05-08 16:00:00,8613.0 -2016-05-08 17:00:00,8578.0 -2016-05-08 18:00:00,8648.0 -2016-05-08 19:00:00,8581.0 -2016-05-08 20:00:00,8617.0 -2016-05-08 21:00:00,8771.0 -2016-05-08 22:00:00,9296.0 -2016-05-08 23:00:00,9154.0 -2016-05-09 00:00:00,8718.0 -2016-05-07 01:00:00,9460.0 -2016-05-07 02:00:00,8856.0 -2016-05-07 03:00:00,8457.0 -2016-05-07 04:00:00,8259.0 -2016-05-07 05:00:00,8071.0 -2016-05-07 06:00:00,8102.0 -2016-05-07 07:00:00,8203.0 -2016-05-07 08:00:00,8357.0 -2016-05-07 09:00:00,8808.0 -2016-05-07 10:00:00,9223.0 -2016-05-07 11:00:00,9393.0 -2016-05-07 12:00:00,9472.0 -2016-05-07 13:00:00,9457.0 -2016-05-07 14:00:00,9319.0 -2016-05-07 15:00:00,9186.0 -2016-05-07 16:00:00,9078.0 -2016-05-07 17:00:00,9011.0 -2016-05-07 18:00:00,8926.0 -2016-05-07 19:00:00,8892.0 -2016-05-07 20:00:00,8816.0 -2016-05-07 21:00:00,8942.0 -2016-05-07 22:00:00,9364.0 -2016-05-07 23:00:00,9222.0 -2016-05-08 00:00:00,8808.0 -2016-05-06 01:00:00,8911.0 -2016-05-06 02:00:00,8472.0 -2016-05-06 03:00:00,8179.0 -2016-05-06 04:00:00,8045.0 -2016-05-06 05:00:00,8013.0 -2016-05-06 06:00:00,8214.0 -2016-05-06 07:00:00,8818.0 -2016-05-06 08:00:00,9587.0 -2016-05-06 09:00:00,10265.0 -2016-05-06 10:00:00,10571.0 -2016-05-06 11:00:00,10804.0 -2016-05-06 12:00:00,11031.0 -2016-05-06 13:00:00,11133.0 -2016-05-06 14:00:00,11217.0 -2016-05-06 15:00:00,11363.0 -2016-05-06 16:00:00,11412.0 -2016-05-06 17:00:00,11435.0 -2016-05-06 18:00:00,11451.0 -2016-05-06 19:00:00,11357.0 -2016-05-06 20:00:00,11180.0 -2016-05-06 21:00:00,11023.0 -2016-05-06 22:00:00,11290.0 -2016-05-06 23:00:00,10968.0 -2016-05-07 00:00:00,10222.0 -2016-05-05 01:00:00,9177.0 -2016-05-05 02:00:00,8748.0 -2016-05-05 03:00:00,8514.0 -2016-05-05 04:00:00,8342.0 -2016-05-05 05:00:00,8319.0 -2016-05-05 06:00:00,8542.0 -2016-05-05 07:00:00,9185.0 -2016-05-05 08:00:00,10048.0 -2016-05-05 09:00:00,10647.0 -2016-05-05 10:00:00,10832.0 -2016-05-05 11:00:00,10893.0 -2016-05-05 12:00:00,10963.0 -2016-05-05 13:00:00,10944.0 -2016-05-05 14:00:00,10943.0 -2016-05-05 15:00:00,10964.0 -2016-05-05 16:00:00,10856.0 -2016-05-05 17:00:00,10724.0 -2016-05-05 18:00:00,10575.0 -2016-05-05 19:00:00,10404.0 -2016-05-05 20:00:00,10134.0 -2016-05-05 21:00:00,10183.0 -2016-05-05 22:00:00,10627.0 -2016-05-05 23:00:00,10347.0 -2016-05-06 00:00:00,9621.0 -2016-05-04 01:00:00,8950.0 -2016-05-04 02:00:00,8449.0 -2016-05-04 03:00:00,8231.0 -2016-05-04 04:00:00,8053.0 -2016-05-04 05:00:00,8000.0 -2016-05-04 06:00:00,8212.0 -2016-05-04 07:00:00,8847.0 -2016-05-04 08:00:00,9685.0 -2016-05-04 09:00:00,10359.0 -2016-05-04 10:00:00,10656.0 -2016-05-04 11:00:00,10791.0 -2016-05-04 12:00:00,10961.0 -2016-05-04 13:00:00,10951.0 -2016-05-04 14:00:00,11006.0 -2016-05-04 15:00:00,11043.0 -2016-05-04 16:00:00,10927.0 -2016-05-04 17:00:00,10813.0 -2016-05-04 18:00:00,10683.0 -2016-05-04 19:00:00,10623.0 -2016-05-04 20:00:00,10509.0 -2016-05-04 21:00:00,10653.0 -2016-05-04 22:00:00,10933.0 -2016-05-04 23:00:00,10593.0 -2016-05-05 00:00:00,9863.0 -2016-05-03 01:00:00,8963.0 -2016-05-03 02:00:00,8525.0 -2016-05-03 03:00:00,8279.0 -2016-05-03 04:00:00,8179.0 -2016-05-03 05:00:00,8151.0 -2016-05-03 06:00:00,8427.0 -2016-05-03 07:00:00,9038.0 -2016-05-03 08:00:00,9859.0 -2016-05-03 09:00:00,10461.0 -2016-05-03 10:00:00,10652.0 -2016-05-03 11:00:00,10755.0 -2016-05-03 12:00:00,10927.0 -2016-05-03 13:00:00,10971.0 -2016-05-03 14:00:00,11023.0 -2016-05-03 15:00:00,11021.0 -2016-05-03 16:00:00,10920.0 -2016-05-03 17:00:00,10788.0 -2016-05-03 18:00:00,10672.0 -2016-05-03 19:00:00,10517.0 -2016-05-03 20:00:00,10334.0 -2016-05-03 21:00:00,10484.0 -2016-05-03 22:00:00,10833.0 -2016-05-03 23:00:00,10464.0 -2016-05-04 00:00:00,9695.0 -2016-05-02 01:00:00,8647.0 -2016-05-02 02:00:00,8305.0 -2016-05-02 03:00:00,8146.0 -2016-05-02 04:00:00,8029.0 -2016-05-02 05:00:00,8097.0 -2016-05-02 06:00:00,8318.0 -2016-05-02 07:00:00,9056.0 -2016-05-02 08:00:00,9897.0 -2016-05-02 09:00:00,10513.0 -2016-05-02 10:00:00,10693.0 -2016-05-02 11:00:00,10771.0 -2016-05-02 12:00:00,10850.0 -2016-05-02 13:00:00,10822.0 -2016-05-02 14:00:00,10774.0 -2016-05-02 15:00:00,10793.0 -2016-05-02 16:00:00,10687.0 -2016-05-02 17:00:00,10510.0 -2016-05-02 18:00:00,10379.0 -2016-05-02 19:00:00,10278.0 -2016-05-02 20:00:00,10156.0 -2016-05-02 21:00:00,10228.0 -2016-05-02 22:00:00,10705.0 -2016-05-02 23:00:00,10372.0 -2016-05-03 00:00:00,9679.0 -2016-05-01 01:00:00,8906.0 -2016-05-01 02:00:00,8473.0 -2016-05-01 03:00:00,8170.0 -2016-05-01 04:00:00,7999.0 -2016-05-01 05:00:00,7893.0 -2016-05-01 06:00:00,7902.0 -2016-05-01 07:00:00,8004.0 -2016-05-01 08:00:00,8070.0 -2016-05-01 09:00:00,8309.0 -2016-05-01 10:00:00,8667.0 -2016-05-01 11:00:00,8959.0 -2016-05-01 12:00:00,9087.0 -2016-05-01 13:00:00,9147.0 -2016-05-01 14:00:00,9081.0 -2016-05-01 15:00:00,9011.0 -2016-05-01 16:00:00,8923.0 -2016-05-01 17:00:00,8965.0 -2016-05-01 18:00:00,9029.0 -2016-05-01 19:00:00,9165.0 -2016-05-01 20:00:00,9368.0 -2016-05-01 21:00:00,9814.0 -2016-05-01 22:00:00,9925.0 -2016-05-01 23:00:00,9648.0 -2016-05-02 00:00:00,9157.0 -2016-04-30 01:00:00,9196.0 -2016-04-30 02:00:00,8627.0 -2016-04-30 03:00:00,8393.0 -2016-04-30 04:00:00,8197.0 -2016-04-30 05:00:00,8116.0 -2016-04-30 06:00:00,8160.0 -2016-04-30 07:00:00,8398.0 -2016-04-30 08:00:00,8628.0 -2016-04-30 09:00:00,8984.0 -2016-04-30 10:00:00,9455.0 -2016-04-30 11:00:00,9765.0 -2016-04-30 12:00:00,9931.0 -2016-04-30 13:00:00,9974.0 -2016-04-30 14:00:00,9965.0 -2016-04-30 15:00:00,9843.0 -2016-04-30 16:00:00,9798.0 -2016-04-30 17:00:00,9800.0 -2016-04-30 18:00:00,9774.0 -2016-04-30 19:00:00,9794.0 -2016-04-30 20:00:00,9857.0 -2016-04-30 21:00:00,10013.0 -2016-04-30 22:00:00,10174.0 -2016-04-30 23:00:00,9897.0 -2016-05-01 00:00:00,9429.0 -2016-04-29 01:00:00,9189.0 -2016-04-29 02:00:00,8706.0 -2016-04-29 03:00:00,8427.0 -2016-04-29 04:00:00,8267.0 -2016-04-29 05:00:00,8234.0 -2016-04-29 06:00:00,8457.0 -2016-04-29 07:00:00,9052.0 -2016-04-29 08:00:00,9929.0 -2016-04-29 09:00:00,10669.0 -2016-04-29 10:00:00,10965.0 -2016-04-29 11:00:00,11140.0 -2016-04-29 12:00:00,11180.0 -2016-04-29 13:00:00,11078.0 -2016-04-29 14:00:00,10967.0 -2016-04-29 15:00:00,10900.0 -2016-04-29 16:00:00,10720.0 -2016-04-29 17:00:00,10526.0 -2016-04-29 18:00:00,10342.0 -2016-04-29 19:00:00,10216.0 -2016-04-29 20:00:00,10180.0 -2016-04-29 21:00:00,10431.0 -2016-04-29 22:00:00,10683.0 -2016-04-29 23:00:00,10441.0 -2016-04-30 00:00:00,9802.0 -2016-04-28 01:00:00,9236.0 -2016-04-28 02:00:00,8788.0 -2016-04-28 03:00:00,8559.0 -2016-04-28 04:00:00,8404.0 -2016-04-28 05:00:00,8384.0 -2016-04-28 06:00:00,8641.0 -2016-04-28 07:00:00,9296.0 -2016-04-28 08:00:00,10160.0 -2016-04-28 09:00:00,10787.0 -2016-04-28 10:00:00,11095.0 -2016-04-28 11:00:00,11197.0 -2016-04-28 12:00:00,11249.0 -2016-04-28 13:00:00,11212.0 -2016-04-28 14:00:00,11110.0 -2016-04-28 15:00:00,11057.0 -2016-04-28 16:00:00,10941.0 -2016-04-28 17:00:00,10771.0 -2016-04-28 18:00:00,10687.0 -2016-04-28 19:00:00,10632.0 -2016-04-28 20:00:00,10566.0 -2016-04-28 21:00:00,10818.0 -2016-04-28 22:00:00,10985.0 -2016-04-28 23:00:00,10576.0 -2016-04-29 00:00:00,9874.0 -2016-04-27 01:00:00,9044.0 -2016-04-27 02:00:00,8604.0 -2016-04-27 03:00:00,8345.0 -2016-04-27 04:00:00,8182.0 -2016-04-27 05:00:00,8160.0 -2016-04-27 06:00:00,8392.0 -2016-04-27 07:00:00,9099.0 -2016-04-27 08:00:00,9989.0 -2016-04-27 09:00:00,10681.0 -2016-04-27 10:00:00,10959.0 -2016-04-27 11:00:00,11087.0 -2016-04-27 12:00:00,11196.0 -2016-04-27 13:00:00,11146.0 -2016-04-27 14:00:00,11099.0 -2016-04-27 15:00:00,11093.0 -2016-04-27 16:00:00,11053.0 -2016-04-27 17:00:00,11071.0 -2016-04-27 18:00:00,11046.0 -2016-04-27 19:00:00,11092.0 -2016-04-27 20:00:00,11050.0 -2016-04-27 21:00:00,11124.0 -2016-04-27 22:00:00,11187.0 -2016-04-27 23:00:00,10730.0 -2016-04-28 00:00:00,9944.0 -2016-04-26 01:00:00,9874.0 -2016-04-26 02:00:00,9160.0 -2016-04-26 03:00:00,8586.0 -2016-04-26 04:00:00,8261.0 -2016-04-26 05:00:00,8045.0 -2016-04-26 06:00:00,8167.0 -2016-04-26 07:00:00,8747.0 -2016-04-26 08:00:00,9538.0 -2016-04-26 09:00:00,10217.0 -2016-04-26 10:00:00,10584.0 -2016-04-26 11:00:00,10706.0 -2016-04-26 12:00:00,10805.0 -2016-04-26 13:00:00,10821.0 -2016-04-26 14:00:00,10784.0 -2016-04-26 15:00:00,10829.0 -2016-04-26 16:00:00,10737.0 -2016-04-26 17:00:00,10618.0 -2016-04-26 18:00:00,10552.0 -2016-04-26 19:00:00,10566.0 -2016-04-26 20:00:00,10503.0 -2016-04-26 21:00:00,10743.0 -2016-04-26 22:00:00,10932.0 -2016-04-26 23:00:00,10524.0 -2016-04-27 00:00:00,9735.0 -2016-04-25 01:00:00,8491.0 -2016-04-25 02:00:00,8106.0 -2016-04-25 03:00:00,7874.0 -2016-04-25 04:00:00,7694.0 -2016-04-25 05:00:00,7725.0 -2016-04-25 06:00:00,7910.0 -2016-04-25 07:00:00,8598.0 -2016-04-25 08:00:00,9416.0 -2016-04-25 09:00:00,10285.0 -2016-04-25 10:00:00,10812.0 -2016-04-25 11:00:00,11092.0 -2016-04-25 12:00:00,11466.0 -2016-04-25 13:00:00,11728.0 -2016-04-25 14:00:00,11927.0 -2016-04-25 15:00:00,12156.0 -2016-04-25 16:00:00,12232.0 -2016-04-25 17:00:00,12300.0 -2016-04-25 18:00:00,12298.0 -2016-04-25 19:00:00,12134.0 -2016-04-25 20:00:00,11822.0 -2016-04-25 21:00:00,11810.0 -2016-04-25 22:00:00,12088.0 -2016-04-25 23:00:00,11600.0 -2016-04-26 00:00:00,10747.0 -2016-04-24 01:00:00,8393.0 -2016-04-24 02:00:00,7996.0 -2016-04-24 03:00:00,7750.0 -2016-04-24 04:00:00,7601.0 -2016-04-24 05:00:00,7554.0 -2016-04-24 06:00:00,7519.0 -2016-04-24 07:00:00,7667.0 -2016-04-24 08:00:00,7562.0 -2016-04-24 09:00:00,7772.0 -2016-04-24 10:00:00,8133.0 -2016-04-24 11:00:00,8382.0 -2016-04-24 12:00:00,8584.0 -2016-04-24 13:00:00,8706.0 -2016-04-24 14:00:00,8831.0 -2016-04-24 15:00:00,8906.0 -2016-04-24 16:00:00,9053.0 -2016-04-24 17:00:00,9067.0 -2016-04-24 18:00:00,9116.0 -2016-04-24 19:00:00,9169.0 -2016-04-24 20:00:00,9183.0 -2016-04-24 21:00:00,9474.0 -2016-04-24 22:00:00,9868.0 -2016-04-24 23:00:00,9587.0 -2016-04-25 00:00:00,9091.0 -2016-04-23 01:00:00,9016.0 -2016-04-23 02:00:00,8509.0 -2016-04-23 03:00:00,8181.0 -2016-04-23 04:00:00,8008.0 -2016-04-23 05:00:00,7999.0 -2016-04-23 06:00:00,8043.0 -2016-04-23 07:00:00,8288.0 -2016-04-23 08:00:00,8449.0 -2016-04-23 09:00:00,8782.0 -2016-04-23 10:00:00,9076.0 -2016-04-23 11:00:00,9288.0 -2016-04-23 12:00:00,9349.0 -2016-04-23 13:00:00,9341.0 -2016-04-23 14:00:00,9256.0 -2016-04-23 15:00:00,9114.0 -2016-04-23 16:00:00,9005.0 -2016-04-23 17:00:00,8924.0 -2016-04-23 18:00:00,8911.0 -2016-04-23 19:00:00,8892.0 -2016-04-23 20:00:00,8872.0 -2016-04-23 21:00:00,9090.0 -2016-04-23 22:00:00,9504.0 -2016-04-23 23:00:00,9247.0 -2016-04-24 00:00:00,8852.0 -2016-04-22 01:00:00,8965.0 -2016-04-22 02:00:00,8454.0 -2016-04-22 03:00:00,8141.0 -2016-04-22 04:00:00,7952.0 -2016-04-22 05:00:00,7869.0 -2016-04-22 06:00:00,8026.0 -2016-04-22 07:00:00,8625.0 -2016-04-22 08:00:00,9444.0 -2016-04-22 09:00:00,10156.0 -2016-04-22 10:00:00,10578.0 -2016-04-22 11:00:00,10805.0 -2016-04-22 12:00:00,10895.0 -2016-04-22 13:00:00,10834.0 -2016-04-22 14:00:00,10754.0 -2016-04-22 15:00:00,10716.0 -2016-04-22 16:00:00,10598.0 -2016-04-22 17:00:00,10404.0 -2016-04-22 18:00:00,10259.0 -2016-04-22 19:00:00,10086.0 -2016-04-22 20:00:00,9924.0 -2016-04-22 21:00:00,10092.0 -2016-04-22 22:00:00,10492.0 -2016-04-22 23:00:00,10229.0 -2016-04-23 00:00:00,9627.0 -2016-04-21 01:00:00,9014.0 -2016-04-21 02:00:00,8514.0 -2016-04-21 03:00:00,8221.0 -2016-04-21 04:00:00,8036.0 -2016-04-21 05:00:00,7966.0 -2016-04-21 06:00:00,8167.0 -2016-04-21 07:00:00,8805.0 -2016-04-21 08:00:00,9688.0 -2016-04-21 09:00:00,10405.0 -2016-04-21 10:00:00,10795.0 -2016-04-21 11:00:00,11036.0 -2016-04-21 12:00:00,11264.0 -2016-04-21 13:00:00,11394.0 -2016-04-21 14:00:00,11405.0 -2016-04-21 15:00:00,11442.0 -2016-04-21 16:00:00,11359.0 -2016-04-21 17:00:00,11217.0 -2016-04-21 18:00:00,11113.0 -2016-04-21 19:00:00,10924.0 -2016-04-21 20:00:00,10647.0 -2016-04-21 21:00:00,10664.0 -2016-04-21 22:00:00,10848.0 -2016-04-21 23:00:00,10400.0 -2016-04-22 00:00:00,9696.0 -2016-04-20 01:00:00,8877.0 -2016-04-20 02:00:00,8423.0 -2016-04-20 03:00:00,8158.0 -2016-04-20 04:00:00,7992.0 -2016-04-20 05:00:00,7952.0 -2016-04-20 06:00:00,8209.0 -2016-04-20 07:00:00,8883.0 -2016-04-20 08:00:00,9619.0 -2016-04-20 09:00:00,10250.0 -2016-04-20 10:00:00,10609.0 -2016-04-20 11:00:00,10762.0 -2016-04-20 12:00:00,10934.0 -2016-04-20 13:00:00,11027.0 -2016-04-20 14:00:00,11082.0 -2016-04-20 15:00:00,11181.0 -2016-04-20 16:00:00,11128.0 -2016-04-20 17:00:00,10980.0 -2016-04-20 18:00:00,10823.0 -2016-04-20 19:00:00,10747.0 -2016-04-20 20:00:00,10624.0 -2016-04-20 21:00:00,10949.0 -2016-04-20 22:00:00,10971.0 -2016-04-20 23:00:00,10494.0 -2016-04-21 00:00:00,9707.0 -2016-04-19 01:00:00,9139.0 -2016-04-19 02:00:00,8559.0 -2016-04-19 03:00:00,8220.0 -2016-04-19 04:00:00,7999.0 -2016-04-19 05:00:00,7914.0 -2016-04-19 06:00:00,8072.0 -2016-04-19 07:00:00,8666.0 -2016-04-19 08:00:00,9476.0 -2016-04-19 09:00:00,10142.0 -2016-04-19 10:00:00,10506.0 -2016-04-19 11:00:00,10677.0 -2016-04-19 12:00:00,10798.0 -2016-04-19 13:00:00,10852.0 -2016-04-19 14:00:00,10842.0 -2016-04-19 15:00:00,10849.0 -2016-04-19 16:00:00,10784.0 -2016-04-19 17:00:00,10666.0 -2016-04-19 18:00:00,10556.0 -2016-04-19 19:00:00,10461.0 -2016-04-19 20:00:00,10444.0 -2016-04-19 21:00:00,10682.0 -2016-04-19 22:00:00,10765.0 -2016-04-19 23:00:00,10336.0 -2016-04-20 00:00:00,9606.0 -2016-04-18 01:00:00,8367.0 -2016-04-18 02:00:00,7956.0 -2016-04-18 03:00:00,7728.0 -2016-04-18 04:00:00,7578.0 -2016-04-18 05:00:00,7568.0 -2016-04-18 06:00:00,7797.0 -2016-04-18 07:00:00,8466.0 -2016-04-18 08:00:00,9278.0 -2016-04-18 09:00:00,10076.0 -2016-04-18 10:00:00,10581.0 -2016-04-18 11:00:00,10938.0 -2016-04-18 12:00:00,11336.0 -2016-04-18 13:00:00,11547.0 -2016-04-18 14:00:00,11746.0 -2016-04-18 15:00:00,11939.0 -2016-04-18 16:00:00,12003.0 -2016-04-18 17:00:00,11974.0 -2016-04-18 18:00:00,11871.0 -2016-04-18 19:00:00,11620.0 -2016-04-18 20:00:00,11228.0 -2016-04-18 21:00:00,11307.0 -2016-04-18 22:00:00,11403.0 -2016-04-18 23:00:00,10871.0 -2016-04-19 00:00:00,9987.0 -2016-04-17 01:00:00,8423.0 -2016-04-17 02:00:00,7962.0 -2016-04-17 03:00:00,7693.0 -2016-04-17 04:00:00,7558.0 -2016-04-17 05:00:00,7426.0 -2016-04-17 06:00:00,7436.0 -2016-04-17 07:00:00,7507.0 -2016-04-17 08:00:00,7502.0 -2016-04-17 09:00:00,7697.0 -2016-04-17 10:00:00,8098.0 -2016-04-17 11:00:00,8413.0 -2016-04-17 12:00:00,8696.0 -2016-04-17 13:00:00,8925.0 -2016-04-17 14:00:00,9053.0 -2016-04-17 15:00:00,9156.0 -2016-04-17 16:00:00,9247.0 -2016-04-17 17:00:00,9301.0 -2016-04-17 18:00:00,9378.0 -2016-04-17 19:00:00,9441.0 -2016-04-17 20:00:00,9368.0 -2016-04-17 21:00:00,9580.0 -2016-04-17 22:00:00,9895.0 -2016-04-17 23:00:00,9506.0 -2016-04-18 00:00:00,8960.0 -2016-04-16 01:00:00,8969.0 -2016-04-16 02:00:00,8472.0 -2016-04-16 03:00:00,8192.0 -2016-04-16 04:00:00,7966.0 -2016-04-16 05:00:00,7945.0 -2016-04-16 06:00:00,8003.0 -2016-04-16 07:00:00,8253.0 -2016-04-16 08:00:00,8414.0 -2016-04-16 09:00:00,8665.0 -2016-04-16 10:00:00,9001.0 -2016-04-16 11:00:00,9270.0 -2016-04-16 12:00:00,9442.0 -2016-04-16 13:00:00,9499.0 -2016-04-16 14:00:00,9501.0 -2016-04-16 15:00:00,9433.0 -2016-04-16 16:00:00,9348.0 -2016-04-16 17:00:00,9311.0 -2016-04-16 18:00:00,9297.0 -2016-04-16 19:00:00,9266.0 -2016-04-16 20:00:00,9159.0 -2016-04-16 21:00:00,9327.0 -2016-04-16 22:00:00,9623.0 -2016-04-16 23:00:00,9456.0 -2016-04-17 00:00:00,8945.0 -2016-04-15 01:00:00,9160.0 -2016-04-15 02:00:00,8708.0 -2016-04-15 03:00:00,8448.0 -2016-04-15 04:00:00,8315.0 -2016-04-15 05:00:00,8312.0 -2016-04-15 06:00:00,8557.0 -2016-04-15 07:00:00,9255.0 -2016-04-15 08:00:00,10028.0 -2016-04-15 09:00:00,10525.0 -2016-04-15 10:00:00,10724.0 -2016-04-15 11:00:00,10857.0 -2016-04-15 12:00:00,10931.0 -2016-04-15 13:00:00,10933.0 -2016-04-15 14:00:00,10939.0 -2016-04-15 15:00:00,10971.0 -2016-04-15 16:00:00,10879.0 -2016-04-15 17:00:00,10713.0 -2016-04-15 18:00:00,10549.0 -2016-04-15 19:00:00,10354.0 -2016-04-15 20:00:00,10149.0 -2016-04-15 21:00:00,10299.0 -2016-04-15 22:00:00,10549.0 -2016-04-15 23:00:00,10251.0 -2016-04-16 00:00:00,9615.0 -2016-04-14 01:00:00,9435.0 -2016-04-14 02:00:00,9002.0 -2016-04-14 03:00:00,8732.0 -2016-04-14 04:00:00,8587.0 -2016-04-14 05:00:00,8600.0 -2016-04-14 06:00:00,8850.0 -2016-04-14 07:00:00,9589.0 -2016-04-14 08:00:00,10435.0 -2016-04-14 09:00:00,10920.0 -2016-04-14 10:00:00,11062.0 -2016-04-14 11:00:00,11053.0 -2016-04-14 12:00:00,11088.0 -2016-04-14 13:00:00,11059.0 -2016-04-14 14:00:00,11009.0 -2016-04-14 15:00:00,11021.0 -2016-04-14 16:00:00,10874.0 -2016-04-14 17:00:00,10712.0 -2016-04-14 18:00:00,10582.0 -2016-04-14 19:00:00,10465.0 -2016-04-14 20:00:00,10285.0 -2016-04-14 21:00:00,10605.0 -2016-04-14 22:00:00,10909.0 -2016-04-14 23:00:00,10534.0 -2016-04-15 00:00:00,9849.0 -2016-04-13 01:00:00,9570.0 -2016-04-13 02:00:00,9148.0 -2016-04-13 03:00:00,8909.0 -2016-04-13 04:00:00,8776.0 -2016-04-13 05:00:00,8780.0 -2016-04-13 06:00:00,9037.0 -2016-04-13 07:00:00,9777.0 -2016-04-13 08:00:00,10698.0 -2016-04-13 09:00:00,11126.0 -2016-04-13 10:00:00,11282.0 -2016-04-13 11:00:00,11249.0 -2016-04-13 12:00:00,11218.0 -2016-04-13 13:00:00,11130.0 -2016-04-13 14:00:00,11036.0 -2016-04-13 15:00:00,11034.0 -2016-04-13 16:00:00,10949.0 -2016-04-13 17:00:00,10818.0 -2016-04-13 18:00:00,10742.0 -2016-04-13 19:00:00,10661.0 -2016-04-13 20:00:00,10538.0 -2016-04-13 21:00:00,10872.0 -2016-04-13 22:00:00,11179.0 -2016-04-13 23:00:00,10783.0 -2016-04-14 00:00:00,10093.0 -2016-04-12 01:00:00,9466.0 -2016-04-12 02:00:00,9087.0 -2016-04-12 03:00:00,8853.0 -2016-04-12 04:00:00,8727.0 -2016-04-12 05:00:00,8768.0 -2016-04-12 06:00:00,9061.0 -2016-04-12 07:00:00,9855.0 -2016-04-12 08:00:00,10788.0 -2016-04-12 09:00:00,11221.0 -2016-04-12 10:00:00,11339.0 -2016-04-12 11:00:00,11319.0 -2016-04-12 12:00:00,11331.0 -2016-04-12 13:00:00,11256.0 -2016-04-12 14:00:00,11127.0 -2016-04-12 15:00:00,11110.0 -2016-04-12 16:00:00,10962.0 -2016-04-12 17:00:00,10818.0 -2016-04-12 18:00:00,10681.0 -2016-04-12 19:00:00,10618.0 -2016-04-12 20:00:00,10470.0 -2016-04-12 21:00:00,10870.0 -2016-04-12 22:00:00,11269.0 -2016-04-12 23:00:00,10906.0 -2016-04-13 00:00:00,10241.0 -2016-04-11 01:00:00,8963.0 -2016-04-11 02:00:00,8566.0 -2016-04-11 03:00:00,8381.0 -2016-04-11 04:00:00,8274.0 -2016-04-11 05:00:00,8291.0 -2016-04-11 06:00:00,8599.0 -2016-04-11 07:00:00,9380.0 -2016-04-11 08:00:00,10320.0 -2016-04-11 09:00:00,10850.0 -2016-04-11 10:00:00,11112.0 -2016-04-11 11:00:00,11198.0 -2016-04-11 12:00:00,11241.0 -2016-04-11 13:00:00,11204.0 -2016-04-11 14:00:00,11108.0 -2016-04-11 15:00:00,11113.0 -2016-04-11 16:00:00,10944.0 -2016-04-11 17:00:00,10773.0 -2016-04-11 18:00:00,10639.0 -2016-04-11 19:00:00,10537.0 -2016-04-11 20:00:00,10439.0 -2016-04-11 21:00:00,10811.0 -2016-04-11 22:00:00,11134.0 -2016-04-11 23:00:00,10773.0 -2016-04-12 00:00:00,10092.0 -2016-04-10 01:00:00,9634.0 -2016-04-10 02:00:00,9283.0 -2016-04-10 03:00:00,9046.0 -2016-04-10 04:00:00,8888.0 -2016-04-10 05:00:00,8811.0 -2016-04-10 06:00:00,8817.0 -2016-04-10 07:00:00,8937.0 -2016-04-10 08:00:00,9112.0 -2016-04-10 09:00:00,9169.0 -2016-04-10 10:00:00,9474.0 -2016-04-10 11:00:00,9667.0 -2016-04-10 12:00:00,9899.0 -2016-04-10 13:00:00,9973.0 -2016-04-10 14:00:00,9961.0 -2016-04-10 15:00:00,9899.0 -2016-04-10 16:00:00,9898.0 -2016-04-10 17:00:00,9890.0 -2016-04-10 18:00:00,9862.0 -2016-04-10 19:00:00,9972.0 -2016-04-10 20:00:00,10126.0 -2016-04-10 21:00:00,10399.0 -2016-04-10 22:00:00,10347.0 -2016-04-10 23:00:00,9997.0 -2016-04-11 00:00:00,9481.0 -2016-04-09 01:00:00,10261.0 -2016-04-09 02:00:00,9875.0 -2016-04-09 03:00:00,9633.0 -2016-04-09 04:00:00,9431.0 -2016-04-09 05:00:00,9417.0 -2016-04-09 06:00:00,9491.0 -2016-04-09 07:00:00,9829.0 -2016-04-09 08:00:00,10080.0 -2016-04-09 09:00:00,10251.0 -2016-04-09 10:00:00,10449.0 -2016-04-09 11:00:00,10625.0 -2016-04-09 12:00:00,10616.0 -2016-04-09 13:00:00,10510.0 -2016-04-09 14:00:00,10355.0 -2016-04-09 15:00:00,10086.0 -2016-04-09 16:00:00,9910.0 -2016-04-09 17:00:00,9685.0 -2016-04-09 18:00:00,9636.0 -2016-04-09 19:00:00,9638.0 -2016-04-09 20:00:00,9726.0 -2016-04-09 21:00:00,10289.0 -2016-04-09 22:00:00,10698.0 -2016-04-09 23:00:00,10526.0 -2016-04-10 00:00:00,10154.0 -2016-04-08 01:00:00,9712.0 -2016-04-08 02:00:00,9286.0 -2016-04-08 03:00:00,9023.0 -2016-04-08 04:00:00,8878.0 -2016-04-08 05:00:00,8890.0 -2016-04-08 06:00:00,9160.0 -2016-04-08 07:00:00,9823.0 -2016-04-08 08:00:00,10814.0 -2016-04-08 09:00:00,11393.0 -2016-04-08 10:00:00,11750.0 -2016-04-08 11:00:00,11928.0 -2016-04-08 12:00:00,11958.0 -2016-04-08 13:00:00,11838.0 -2016-04-08 14:00:00,11650.0 -2016-04-08 15:00:00,11636.0 -2016-04-08 16:00:00,11538.0 -2016-04-08 17:00:00,11394.0 -2016-04-08 18:00:00,11217.0 -2016-04-08 19:00:00,11118.0 -2016-04-08 20:00:00,11105.0 -2016-04-08 21:00:00,11390.0 -2016-04-08 22:00:00,11617.0 -2016-04-08 23:00:00,11392.0 -2016-04-09 00:00:00,10777.0 -2016-04-07 01:00:00,9465.0 -2016-04-07 02:00:00,9028.0 -2016-04-07 03:00:00,8825.0 -2016-04-07 04:00:00,8669.0 -2016-04-07 05:00:00,8653.0 -2016-04-07 06:00:00,8899.0 -2016-04-07 07:00:00,9660.0 -2016-04-07 08:00:00,10796.0 -2016-04-07 09:00:00,11403.0 -2016-04-07 10:00:00,11665.0 -2016-04-07 11:00:00,11791.0 -2016-04-07 12:00:00,11835.0 -2016-04-07 13:00:00,11752.0 -2016-04-07 14:00:00,11713.0 -2016-04-07 15:00:00,11751.0 -2016-04-07 16:00:00,11639.0 -2016-04-07 17:00:00,11554.0 -2016-04-07 18:00:00,11483.0 -2016-04-07 19:00:00,11337.0 -2016-04-07 20:00:00,11242.0 -2016-04-07 21:00:00,11609.0 -2016-04-07 22:00:00,11598.0 -2016-04-07 23:00:00,11145.0 -2016-04-08 00:00:00,10414.0 -2016-04-06 01:00:00,9678.0 -2016-04-06 02:00:00,9184.0 -2016-04-06 03:00:00,8916.0 -2016-04-06 04:00:00,8775.0 -2016-04-06 05:00:00,8743.0 -2016-04-06 06:00:00,8954.0 -2016-04-06 07:00:00,9646.0 -2016-04-06 08:00:00,10661.0 -2016-04-06 09:00:00,11281.0 -2016-04-06 10:00:00,11548.0 -2016-04-06 11:00:00,11631.0 -2016-04-06 12:00:00,11784.0 -2016-04-06 13:00:00,11787.0 -2016-04-06 14:00:00,11751.0 -2016-04-06 15:00:00,11624.0 -2016-04-06 16:00:00,11519.0 -2016-04-06 17:00:00,11339.0 -2016-04-06 18:00:00,11332.0 -2016-04-06 19:00:00,11390.0 -2016-04-06 20:00:00,11311.0 -2016-04-06 21:00:00,11489.0 -2016-04-06 22:00:00,11350.0 -2016-04-06 23:00:00,10905.0 -2016-04-07 00:00:00,10141.0 -2016-04-05 01:00:00,10035.0 -2016-04-05 02:00:00,9622.0 -2016-04-05 03:00:00,9378.0 -2016-04-05 04:00:00,9287.0 -2016-04-05 05:00:00,9280.0 -2016-04-05 06:00:00,9597.0 -2016-04-05 07:00:00,10338.0 -2016-04-05 08:00:00,11325.0 -2016-04-05 09:00:00,11692.0 -2016-04-05 10:00:00,11754.0 -2016-04-05 11:00:00,11730.0 -2016-04-05 12:00:00,11674.0 -2016-04-05 13:00:00,11613.0 -2016-04-05 14:00:00,11486.0 -2016-04-05 15:00:00,11411.0 -2016-04-05 16:00:00,11201.0 -2016-04-05 17:00:00,11037.0 -2016-04-05 18:00:00,11004.0 -2016-04-05 19:00:00,11106.0 -2016-04-05 20:00:00,11220.0 -2016-04-05 21:00:00,11654.0 -2016-04-05 22:00:00,11609.0 -2016-04-05 23:00:00,11160.0 -2016-04-06 00:00:00,10417.0 -2016-04-04 01:00:00,8554.0 -2016-04-04 02:00:00,8288.0 -2016-04-04 03:00:00,8159.0 -2016-04-04 04:00:00,8138.0 -2016-04-04 05:00:00,8259.0 -2016-04-04 06:00:00,8629.0 -2016-04-04 07:00:00,9438.0 -2016-04-04 08:00:00,10617.0 -2016-04-04 09:00:00,11235.0 -2016-04-04 10:00:00,11540.0 -2016-04-04 11:00:00,11685.0 -2016-04-04 12:00:00,11839.0 -2016-04-04 13:00:00,11859.0 -2016-04-04 14:00:00,11824.0 -2016-04-04 15:00:00,11833.0 -2016-04-04 16:00:00,11729.0 -2016-04-04 17:00:00,11620.0 -2016-04-04 18:00:00,11578.0 -2016-04-04 19:00:00,11585.0 -2016-04-04 20:00:00,11530.0 -2016-04-04 21:00:00,11880.0 -2016-04-04 22:00:00,11916.0 -2016-04-04 23:00:00,11477.0 -2016-04-05 00:00:00,10777.0 -2016-04-03 01:00:00,9565.0 -2016-04-03 02:00:00,9176.0 -2016-04-03 03:00:00,8946.0 -2016-04-03 04:00:00,8752.0 -2016-04-03 05:00:00,8701.0 -2016-04-03 06:00:00,8720.0 -2016-04-03 07:00:00,8871.0 -2016-04-03 08:00:00,9043.0 -2016-04-03 09:00:00,9154.0 -2016-04-03 10:00:00,9325.0 -2016-04-03 11:00:00,9535.0 -2016-04-03 12:00:00,9501.0 -2016-04-03 13:00:00,9440.0 -2016-04-03 14:00:00,9265.0 -2016-04-03 15:00:00,9141.0 -2016-04-03 16:00:00,8968.0 -2016-04-03 17:00:00,8891.0 -2016-04-03 18:00:00,8852.0 -2016-04-03 19:00:00,8957.0 -2016-04-03 20:00:00,9073.0 -2016-04-03 21:00:00,9671.0 -2016-04-03 22:00:00,9765.0 -2016-04-03 23:00:00,9454.0 -2016-04-04 00:00:00,8993.0 -2016-04-02 01:00:00,9489.0 -2016-04-02 02:00:00,9040.0 -2016-04-02 03:00:00,8837.0 -2016-04-02 04:00:00,8714.0 -2016-04-02 05:00:00,8665.0 -2016-04-02 06:00:00,8779.0 -2016-04-02 07:00:00,9056.0 -2016-04-02 08:00:00,9492.0 -2016-04-02 09:00:00,9754.0 -2016-04-02 10:00:00,10230.0 -2016-04-02 11:00:00,10427.0 -2016-04-02 12:00:00,10517.0 -2016-04-02 13:00:00,10423.0 -2016-04-02 14:00:00,10359.0 -2016-04-02 15:00:00,10248.0 -2016-04-02 16:00:00,10139.0 -2016-04-02 17:00:00,9976.0 -2016-04-02 18:00:00,9960.0 -2016-04-02 19:00:00,9896.0 -2016-04-02 20:00:00,9922.0 -2016-04-02 21:00:00,10481.0 -2016-04-02 22:00:00,10707.0 -2016-04-02 23:00:00,10531.0 -2016-04-03 00:00:00,10067.0 -2016-04-01 01:00:00,9116.0 -2016-04-01 02:00:00,8655.0 -2016-04-01 03:00:00,8387.0 -2016-04-01 04:00:00,8236.0 -2016-04-01 05:00:00,8232.0 -2016-04-01 06:00:00,8489.0 -2016-04-01 07:00:00,9098.0 -2016-04-01 08:00:00,10063.0 -2016-04-01 09:00:00,10579.0 -2016-04-01 10:00:00,10901.0 -2016-04-01 11:00:00,11105.0 -2016-04-01 12:00:00,11188.0 -2016-04-01 13:00:00,11104.0 -2016-04-01 14:00:00,10917.0 -2016-04-01 15:00:00,10851.0 -2016-04-01 16:00:00,10704.0 -2016-04-01 17:00:00,10562.0 -2016-04-01 18:00:00,10491.0 -2016-04-01 19:00:00,10547.0 -2016-04-01 20:00:00,10514.0 -2016-04-01 21:00:00,10799.0 -2016-04-01 22:00:00,10942.0 -2016-04-01 23:00:00,10533.0 -2016-04-02 00:00:00,10042.0 -2016-03-31 01:00:00,9179.0 -2016-03-31 02:00:00,8711.0 -2016-03-31 03:00:00,8390.0 -2016-03-31 04:00:00,8230.0 -2016-03-31 05:00:00,8166.0 -2016-03-31 06:00:00,8341.0 -2016-03-31 07:00:00,8913.0 -2016-03-31 08:00:00,9919.0 -2016-03-31 09:00:00,10512.0 -2016-03-31 10:00:00,10915.0 -2016-03-31 11:00:00,11041.0 -2016-03-31 12:00:00,11142.0 -2016-03-31 13:00:00,11213.0 -2016-03-31 14:00:00,11181.0 -2016-03-31 15:00:00,11175.0 -2016-03-31 16:00:00,11012.0 -2016-03-31 17:00:00,10793.0 -2016-03-31 18:00:00,10595.0 -2016-03-31 19:00:00,10529.0 -2016-03-31 20:00:00,10527.0 -2016-03-31 21:00:00,10863.0 -2016-03-31 22:00:00,10786.0 -2016-03-31 23:00:00,10419.0 -2016-04-01 00:00:00,9799.0 -2016-03-30 01:00:00,9184.0 -2016-03-30 02:00:00,8748.0 -2016-03-30 03:00:00,8490.0 -2016-03-30 04:00:00,8334.0 -2016-03-30 05:00:00,8361.0 -2016-03-30 06:00:00,8583.0 -2016-03-30 07:00:00,9224.0 -2016-03-30 08:00:00,10231.0 -2016-03-30 09:00:00,10659.0 -2016-03-30 10:00:00,10960.0 -2016-03-30 11:00:00,11133.0 -2016-03-30 12:00:00,11240.0 -2016-03-30 13:00:00,11204.0 -2016-03-30 14:00:00,11062.0 -2016-03-30 15:00:00,11031.0 -2016-03-30 16:00:00,10897.0 -2016-03-30 17:00:00,10717.0 -2016-03-30 18:00:00,10699.0 -2016-03-30 19:00:00,10726.0 -2016-03-30 20:00:00,10846.0 -2016-03-30 21:00:00,11160.0 -2016-03-30 22:00:00,11001.0 -2016-03-30 23:00:00,10543.0 -2016-03-31 00:00:00,9856.0 -2016-03-29 01:00:00,9411.0 -2016-03-29 02:00:00,9000.0 -2016-03-29 03:00:00,8790.0 -2016-03-29 04:00:00,8661.0 -2016-03-29 05:00:00,8653.0 -2016-03-29 06:00:00,8915.0 -2016-03-29 07:00:00,9650.0 -2016-03-29 08:00:00,10619.0 -2016-03-29 09:00:00,10960.0 -2016-03-29 10:00:00,11021.0 -2016-03-29 11:00:00,10990.0 -2016-03-29 12:00:00,11005.0 -2016-03-29 13:00:00,10952.0 -2016-03-29 14:00:00,10865.0 -2016-03-29 15:00:00,10864.0 -2016-03-29 16:00:00,10737.0 -2016-03-29 17:00:00,10584.0 -2016-03-29 18:00:00,10443.0 -2016-03-29 19:00:00,10313.0 -2016-03-29 20:00:00,10269.0 -2016-03-29 21:00:00,10793.0 -2016-03-29 22:00:00,10870.0 -2016-03-29 23:00:00,10486.0 -2016-03-30 00:00:00,9828.0 -2016-03-28 01:00:00,8488.0 -2016-03-28 02:00:00,8225.0 -2016-03-28 03:00:00,8030.0 -2016-03-28 04:00:00,7979.0 -2016-03-28 05:00:00,8028.0 -2016-03-28 06:00:00,8326.0 -2016-03-28 07:00:00,9058.0 -2016-03-28 08:00:00,10108.0 -2016-03-28 09:00:00,10643.0 -2016-03-28 10:00:00,10861.0 -2016-03-28 11:00:00,11021.0 -2016-03-28 12:00:00,11091.0 -2016-03-28 13:00:00,11061.0 -2016-03-28 14:00:00,10925.0 -2016-03-28 15:00:00,10874.0 -2016-03-28 16:00:00,10728.0 -2016-03-28 17:00:00,10525.0 -2016-03-28 18:00:00,10398.0 -2016-03-28 19:00:00,10322.0 -2016-03-28 20:00:00,10239.0 -2016-03-28 21:00:00,10792.0 -2016-03-28 22:00:00,10971.0 -2016-03-28 23:00:00,10635.0 -2016-03-29 00:00:00,10024.0 -2016-03-27 01:00:00,8794.0 -2016-03-27 02:00:00,8371.0 -2016-03-27 03:00:00,8164.0 -2016-03-27 04:00:00,8009.0 -2016-03-27 05:00:00,7928.0 -2016-03-27 06:00:00,7914.0 -2016-03-27 07:00:00,8069.0 -2016-03-27 08:00:00,8233.0 -2016-03-27 09:00:00,8228.0 -2016-03-27 10:00:00,8423.0 -2016-03-27 11:00:00,8544.0 -2016-03-27 12:00:00,8548.0 -2016-03-27 13:00:00,8552.0 -2016-03-27 14:00:00,8502.0 -2016-03-27 15:00:00,8427.0 -2016-03-27 16:00:00,8378.0 -2016-03-27 17:00:00,8398.0 -2016-03-27 18:00:00,8581.0 -2016-03-27 19:00:00,8799.0 -2016-03-27 20:00:00,9053.0 -2016-03-27 21:00:00,9516.0 -2016-03-27 22:00:00,9592.0 -2016-03-27 23:00:00,9360.0 -2016-03-28 00:00:00,8949.0 -2016-03-26 01:00:00,9277.0 -2016-03-26 02:00:00,8802.0 -2016-03-26 03:00:00,8592.0 -2016-03-26 04:00:00,8442.0 -2016-03-26 05:00:00,8421.0 -2016-03-26 06:00:00,8466.0 -2016-03-26 07:00:00,8749.0 -2016-03-26 08:00:00,9068.0 -2016-03-26 09:00:00,9190.0 -2016-03-26 10:00:00,9448.0 -2016-03-26 11:00:00,9520.0 -2016-03-26 12:00:00,9521.0 -2016-03-26 13:00:00,9404.0 -2016-03-26 14:00:00,9300.0 -2016-03-26 15:00:00,9073.0 -2016-03-26 16:00:00,8948.0 -2016-03-26 17:00:00,8828.0 -2016-03-26 18:00:00,8885.0 -2016-03-26 19:00:00,8952.0 -2016-03-26 20:00:00,9133.0 -2016-03-26 21:00:00,9661.0 -2016-03-26 22:00:00,9830.0 -2016-03-26 23:00:00,9619.0 -2016-03-27 00:00:00,9232.0 -2016-03-25 01:00:00,9939.0 -2016-03-25 02:00:00,9453.0 -2016-03-25 03:00:00,9165.0 -2016-03-25 04:00:00,8978.0 -2016-03-25 05:00:00,8943.0 -2016-03-25 06:00:00,9097.0 -2016-03-25 07:00:00,9616.0 -2016-03-25 08:00:00,10292.0 -2016-03-25 09:00:00,10565.0 -2016-03-25 10:00:00,10756.0 -2016-03-25 11:00:00,10776.0 -2016-03-25 12:00:00,10687.0 -2016-03-25 13:00:00,10553.0 -2016-03-25 14:00:00,10369.0 -2016-03-25 15:00:00,10223.0 -2016-03-25 16:00:00,10049.0 -2016-03-25 17:00:00,9843.0 -2016-03-25 18:00:00,9775.0 -2016-03-25 19:00:00,9726.0 -2016-03-25 20:00:00,9736.0 -2016-03-25 21:00:00,10325.0 -2016-03-25 22:00:00,10455.0 -2016-03-25 23:00:00,10254.0 -2016-03-26 00:00:00,9775.0 -2016-03-24 01:00:00,9723.0 -2016-03-24 02:00:00,9241.0 -2016-03-24 03:00:00,8973.0 -2016-03-24 04:00:00,8802.0 -2016-03-24 05:00:00,8739.0 -2016-03-24 06:00:00,8912.0 -2016-03-24 07:00:00,9576.0 -2016-03-24 08:00:00,10695.0 -2016-03-24 09:00:00,11374.0 -2016-03-24 10:00:00,11576.0 -2016-03-24 11:00:00,11773.0 -2016-03-24 12:00:00,11990.0 -2016-03-24 13:00:00,12108.0 -2016-03-24 14:00:00,12088.0 -2016-03-24 15:00:00,11903.0 -2016-03-24 16:00:00,11629.0 -2016-03-24 17:00:00,11515.0 -2016-03-24 18:00:00,11686.0 -2016-03-24 19:00:00,11628.0 -2016-03-24 20:00:00,11542.0 -2016-03-24 21:00:00,11860.0 -2016-03-24 22:00:00,11777.0 -2016-03-24 23:00:00,11372.0 -2016-03-25 00:00:00,10655.0 -2016-03-23 01:00:00,9178.0 -2016-03-23 02:00:00,8768.0 -2016-03-23 03:00:00,8546.0 -2016-03-23 04:00:00,8419.0 -2016-03-23 05:00:00,8439.0 -2016-03-23 06:00:00,8705.0 -2016-03-23 07:00:00,9411.0 -2016-03-23 08:00:00,10571.0 -2016-03-23 09:00:00,11175.0 -2016-03-23 10:00:00,11361.0 -2016-03-23 11:00:00,11432.0 -2016-03-23 12:00:00,11526.0 -2016-03-23 13:00:00,11415.0 -2016-03-23 14:00:00,11276.0 -2016-03-23 15:00:00,11266.0 -2016-03-23 16:00:00,11282.0 -2016-03-23 17:00:00,11332.0 -2016-03-23 18:00:00,11455.0 -2016-03-23 19:00:00,11537.0 -2016-03-23 20:00:00,11563.0 -2016-03-23 21:00:00,11750.0 -2016-03-23 22:00:00,11501.0 -2016-03-23 23:00:00,11039.0 -2016-03-24 00:00:00,10366.0 -2016-03-22 01:00:00,9429.0 -2016-03-22 02:00:00,9028.0 -2016-03-22 03:00:00,8783.0 -2016-03-22 04:00:00,8642.0 -2016-03-22 05:00:00,8628.0 -2016-03-22 06:00:00,8889.0 -2016-03-22 07:00:00,9588.0 -2016-03-22 08:00:00,10682.0 -2016-03-22 09:00:00,11086.0 -2016-03-22 10:00:00,11261.0 -2016-03-22 11:00:00,11281.0 -2016-03-22 12:00:00,11313.0 -2016-03-22 13:00:00,11199.0 -2016-03-22 14:00:00,11078.0 -2016-03-22 15:00:00,11029.0 -2016-03-22 16:00:00,10855.0 -2016-03-22 17:00:00,10692.0 -2016-03-22 18:00:00,10596.0 -2016-03-22 19:00:00,10509.0 -2016-03-22 20:00:00,10452.0 -2016-03-22 21:00:00,10978.0 -2016-03-22 22:00:00,10905.0 -2016-03-22 23:00:00,10467.0 -2016-03-23 00:00:00,9814.0 -2016-03-21 01:00:00,9088.0 -2016-03-21 02:00:00,8808.0 -2016-03-21 03:00:00,8622.0 -2016-03-21 04:00:00,8588.0 -2016-03-21 05:00:00,8649.0 -2016-03-21 06:00:00,8994.0 -2016-03-21 07:00:00,9737.0 -2016-03-21 08:00:00,10929.0 -2016-03-21 09:00:00,11359.0 -2016-03-21 10:00:00,11443.0 -2016-03-21 11:00:00,11274.0 -2016-03-21 12:00:00,11286.0 -2016-03-21 13:00:00,11189.0 -2016-03-21 14:00:00,11071.0 -2016-03-21 15:00:00,11012.0 -2016-03-21 16:00:00,10834.0 -2016-03-21 17:00:00,10638.0 -2016-03-21 18:00:00,10540.0 -2016-03-21 19:00:00,10526.0 -2016-03-21 20:00:00,10603.0 -2016-03-21 21:00:00,11220.0 -2016-03-21 22:00:00,11202.0 -2016-03-21 23:00:00,10779.0 -2016-03-22 00:00:00,10076.0 -2016-03-20 01:00:00,9176.0 -2016-03-20 02:00:00,8787.0 -2016-03-20 03:00:00,8567.0 -2016-03-20 04:00:00,8394.0 -2016-03-20 05:00:00,8330.0 -2016-03-20 06:00:00,8326.0 -2016-03-20 07:00:00,8503.0 -2016-03-20 08:00:00,8764.0 -2016-03-20 09:00:00,8842.0 -2016-03-20 10:00:00,9024.0 -2016-03-20 11:00:00,9164.0 -2016-03-20 12:00:00,9192.0 -2016-03-20 13:00:00,9230.0 -2016-03-20 14:00:00,9142.0 -2016-03-20 15:00:00,9145.0 -2016-03-20 16:00:00,9069.0 -2016-03-20 17:00:00,9025.0 -2016-03-20 18:00:00,9100.0 -2016-03-20 19:00:00,9277.0 -2016-03-20 20:00:00,9448.0 -2016-03-20 21:00:00,10173.0 -2016-03-20 22:00:00,10264.0 -2016-03-20 23:00:00,9987.0 -2016-03-21 00:00:00,9538.0 -2016-03-19 01:00:00,9496.0 -2016-03-19 02:00:00,9008.0 -2016-03-19 03:00:00,8732.0 -2016-03-19 04:00:00,8554.0 -2016-03-19 05:00:00,8487.0 -2016-03-19 06:00:00,8542.0 -2016-03-19 07:00:00,8835.0 -2016-03-19 08:00:00,9277.0 -2016-03-19 09:00:00,9533.0 -2016-03-19 10:00:00,9822.0 -2016-03-19 11:00:00,9970.0 -2016-03-19 12:00:00,9975.0 -2016-03-19 13:00:00,9878.0 -2016-03-19 14:00:00,9752.0 -2016-03-19 15:00:00,9528.0 -2016-03-19 16:00:00,9441.0 -2016-03-19 17:00:00,9355.0 -2016-03-19 18:00:00,9383.0 -2016-03-19 19:00:00,9480.0 -2016-03-19 20:00:00,9735.0 -2016-03-19 21:00:00,10228.0 -2016-03-19 22:00:00,10286.0 -2016-03-19 23:00:00,10056.0 -2016-03-20 00:00:00,9680.0 -2016-03-18 01:00:00,9247.0 -2016-03-18 02:00:00,8815.0 -2016-03-18 03:00:00,8579.0 -2016-03-18 04:00:00,8467.0 -2016-03-18 05:00:00,8484.0 -2016-03-18 06:00:00,8762.0 -2016-03-18 07:00:00,9507.0 -2016-03-18 08:00:00,10644.0 -2016-03-18 09:00:00,11101.0 -2016-03-18 10:00:00,11201.0 -2016-03-18 11:00:00,11212.0 -2016-03-18 12:00:00,11213.0 -2016-03-18 13:00:00,11093.0 -2016-03-18 14:00:00,10960.0 -2016-03-18 15:00:00,10890.0 -2016-03-18 16:00:00,10721.0 -2016-03-18 17:00:00,10557.0 -2016-03-18 18:00:00,10538.0 -2016-03-18 19:00:00,10606.0 -2016-03-18 20:00:00,10650.0 -2016-03-18 21:00:00,11118.0 -2016-03-18 22:00:00,11010.0 -2016-03-18 23:00:00,10709.0 -2016-03-19 00:00:00,10128.0 -2016-03-17 01:00:00,9312.0 -2016-03-17 02:00:00,8843.0 -2016-03-17 03:00:00,8602.0 -2016-03-17 04:00:00,8448.0 -2016-03-17 05:00:00,8450.0 -2016-03-17 06:00:00,8730.0 -2016-03-17 07:00:00,9418.0 -2016-03-17 08:00:00,10584.0 -2016-03-17 09:00:00,11028.0 -2016-03-17 10:00:00,11066.0 -2016-03-17 11:00:00,11071.0 -2016-03-17 12:00:00,11112.0 -2016-03-17 13:00:00,11071.0 -2016-03-17 14:00:00,10989.0 -2016-03-17 15:00:00,10980.0 -2016-03-17 16:00:00,10801.0 -2016-03-17 17:00:00,10648.0 -2016-03-17 18:00:00,10533.0 -2016-03-17 19:00:00,10435.0 -2016-03-17 20:00:00,10411.0 -2016-03-17 21:00:00,10955.0 -2016-03-17 22:00:00,10964.0 -2016-03-17 23:00:00,10533.0 -2016-03-18 00:00:00,9893.0 -2016-03-16 01:00:00,9220.0 -2016-03-16 02:00:00,8718.0 -2016-03-16 03:00:00,8434.0 -2016-03-16 04:00:00,8291.0 -2016-03-16 05:00:00,8269.0 -2016-03-16 06:00:00,8491.0 -2016-03-16 07:00:00,9175.0 -2016-03-16 08:00:00,10297.0 -2016-03-16 09:00:00,10969.0 -2016-03-16 10:00:00,11097.0 -2016-03-16 11:00:00,11194.0 -2016-03-16 12:00:00,11178.0 -2016-03-16 13:00:00,11104.0 -2016-03-16 14:00:00,11045.0 -2016-03-16 15:00:00,11015.0 -2016-03-16 16:00:00,10873.0 -2016-03-16 17:00:00,10722.0 -2016-03-16 18:00:00,10627.0 -2016-03-16 19:00:00,10581.0 -2016-03-16 20:00:00,10630.0 -2016-03-16 21:00:00,11152.0 -2016-03-16 22:00:00,11083.0 -2016-03-16 23:00:00,10655.0 -2016-03-17 00:00:00,9986.0 -2016-03-15 01:00:00,9282.0 -2016-03-15 02:00:00,8791.0 -2016-03-15 03:00:00,8504.0 -2016-03-15 04:00:00,8339.0 -2016-03-15 05:00:00,8290.0 -2016-03-15 06:00:00,8504.0 -2016-03-15 07:00:00,9170.0 -2016-03-15 08:00:00,10271.0 -2016-03-15 09:00:00,10865.0 -2016-03-15 10:00:00,11011.0 -2016-03-15 11:00:00,11115.0 -2016-03-15 12:00:00,11210.0 -2016-03-15 13:00:00,11151.0 -2016-03-15 14:00:00,11075.0 -2016-03-15 15:00:00,11057.0 -2016-03-15 16:00:00,10924.0 -2016-03-15 17:00:00,10748.0 -2016-03-15 18:00:00,10662.0 -2016-03-15 19:00:00,10573.0 -2016-03-15 20:00:00,10606.0 -2016-03-15 21:00:00,11192.0 -2016-03-15 22:00:00,11062.0 -2016-03-15 23:00:00,10617.0 -2016-03-16 00:00:00,9958.0 -2016-03-14 01:00:00,8878.0 -2016-03-14 02:00:00,8535.0 -2016-03-14 03:00:00,8308.0 -2016-03-14 04:00:00,8184.0 -2016-03-14 05:00:00,8198.0 -2016-03-14 06:00:00,8432.0 -2016-03-14 07:00:00,9160.0 -2016-03-14 08:00:00,10342.0 -2016-03-14 09:00:00,11033.0 -2016-03-14 10:00:00,11167.0 -2016-03-14 11:00:00,11161.0 -2016-03-14 12:00:00,11219.0 -2016-03-14 13:00:00,11156.0 -2016-03-14 14:00:00,11101.0 -2016-03-14 15:00:00,11097.0 -2016-03-14 16:00:00,10959.0 -2016-03-14 17:00:00,10803.0 -2016-03-14 18:00:00,10693.0 -2016-03-14 19:00:00,10652.0 -2016-03-14 20:00:00,10771.0 -2016-03-14 21:00:00,11261.0 -2016-03-14 22:00:00,11079.0 -2016-03-14 23:00:00,10630.0 -2016-03-15 00:00:00,9962.0 -2016-03-13 01:00:00,8648.0 -2016-03-13 02:00:00,8325.0 -2016-03-13 04:00:00,8078.0 -2016-03-13 05:00:00,7969.0 -2016-03-13 06:00:00,7891.0 -2016-03-13 07:00:00,8011.0 -2016-03-13 08:00:00,8225.0 -2016-03-13 09:00:00,8393.0 -2016-03-13 10:00:00,8623.0 -2016-03-13 11:00:00,8909.0 -2016-03-13 12:00:00,9169.0 -2016-03-13 13:00:00,9334.0 -2016-03-13 14:00:00,9420.0 -2016-03-13 15:00:00,9445.0 -2016-03-13 16:00:00,9410.0 -2016-03-13 17:00:00,9405.0 -2016-03-13 18:00:00,9463.0 -2016-03-13 19:00:00,9643.0 -2016-03-13 20:00:00,10052.0 -2016-03-13 21:00:00,10409.0 -2016-03-13 22:00:00,10312.0 -2016-03-13 23:00:00,9970.0 -2016-03-14 00:00:00,9431.0 -2016-03-12 01:00:00,9455.0 -2016-03-12 02:00:00,8961.0 -2016-03-12 03:00:00,8712.0 -2016-03-12 04:00:00,8531.0 -2016-03-12 05:00:00,8482.0 -2016-03-12 06:00:00,8558.0 -2016-03-12 07:00:00,8856.0 -2016-03-12 08:00:00,9054.0 -2016-03-12 09:00:00,9392.0 -2016-03-12 10:00:00,9674.0 -2016-03-12 11:00:00,9847.0 -2016-03-12 12:00:00,9811.0 -2016-03-12 13:00:00,9697.0 -2016-03-12 14:00:00,9524.0 -2016-03-12 15:00:00,9351.0 -2016-03-12 16:00:00,9257.0 -2016-03-12 17:00:00,9227.0 -2016-03-12 18:00:00,9376.0 -2016-03-12 19:00:00,9687.0 -2016-03-12 20:00:00,10136.0 -2016-03-12 21:00:00,10077.0 -2016-03-12 22:00:00,9931.0 -2016-03-12 23:00:00,9586.0 -2016-03-13 00:00:00,9144.0 -2016-03-11 01:00:00,9330.0 -2016-03-11 02:00:00,8949.0 -2016-03-11 03:00:00,8715.0 -2016-03-11 04:00:00,8568.0 -2016-03-11 05:00:00,8590.0 -2016-03-11 06:00:00,8845.0 -2016-03-11 07:00:00,9597.0 -2016-03-11 08:00:00,10734.0 -2016-03-11 09:00:00,10919.0 -2016-03-11 10:00:00,10980.0 -2016-03-11 11:00:00,10970.0 -2016-03-11 12:00:00,10993.0 -2016-03-11 13:00:00,10941.0 -2016-03-11 14:00:00,10850.0 -2016-03-11 15:00:00,10829.0 -2016-03-11 16:00:00,10663.0 -2016-03-11 17:00:00,10529.0 -2016-03-11 18:00:00,10494.0 -2016-03-11 19:00:00,10590.0 -2016-03-11 20:00:00,11198.0 -2016-03-11 21:00:00,11178.0 -2016-03-11 22:00:00,10949.0 -2016-03-11 23:00:00,10649.0 -2016-03-12 00:00:00,10047.0 -2016-03-10 01:00:00,8984.0 -2016-03-10 02:00:00,8539.0 -2016-03-10 03:00:00,8251.0 -2016-03-10 04:00:00,8128.0 -2016-03-10 05:00:00,8116.0 -2016-03-10 06:00:00,8343.0 -2016-03-10 07:00:00,9091.0 -2016-03-10 08:00:00,10029.0 -2016-03-10 09:00:00,10655.0 -2016-03-10 10:00:00,10930.0 -2016-03-10 11:00:00,11054.0 -2016-03-10 12:00:00,11075.0 -2016-03-10 13:00:00,11072.0 -2016-03-10 14:00:00,11031.0 -2016-03-10 15:00:00,11036.0 -2016-03-10 16:00:00,10956.0 -2016-03-10 17:00:00,10864.0 -2016-03-10 18:00:00,10919.0 -2016-03-10 19:00:00,11112.0 -2016-03-10 20:00:00,11566.0 -2016-03-10 21:00:00,11489.0 -2016-03-10 22:00:00,11227.0 -2016-03-10 23:00:00,10747.0 -2016-03-11 00:00:00,9981.0 -2016-03-09 01:00:00,9043.0 -2016-03-09 02:00:00,8573.0 -2016-03-09 03:00:00,8293.0 -2016-03-09 04:00:00,8158.0 -2016-03-09 05:00:00,8119.0 -2016-03-09 06:00:00,8307.0 -2016-03-09 07:00:00,8964.0 -2016-03-09 08:00:00,9896.0 -2016-03-09 09:00:00,10467.0 -2016-03-09 10:00:00,10801.0 -2016-03-09 11:00:00,10926.0 -2016-03-09 12:00:00,11085.0 -2016-03-09 13:00:00,11101.0 -2016-03-09 14:00:00,11140.0 -2016-03-09 15:00:00,11150.0 -2016-03-09 16:00:00,11012.0 -2016-03-09 17:00:00,10860.0 -2016-03-09 18:00:00,10862.0 -2016-03-09 19:00:00,11034.0 -2016-03-09 20:00:00,11433.0 -2016-03-09 21:00:00,11251.0 -2016-03-09 22:00:00,10921.0 -2016-03-09 23:00:00,10425.0 -2016-03-10 00:00:00,9660.0 -2016-03-08 01:00:00,9127.0 -2016-03-08 02:00:00,8662.0 -2016-03-08 03:00:00,8399.0 -2016-03-08 04:00:00,8240.0 -2016-03-08 05:00:00,8212.0 -2016-03-08 06:00:00,8442.0 -2016-03-08 07:00:00,9106.0 -2016-03-08 08:00:00,9971.0 -2016-03-08 09:00:00,10578.0 -2016-03-08 10:00:00,10862.0 -2016-03-08 11:00:00,10947.0 -2016-03-08 12:00:00,11096.0 -2016-03-08 13:00:00,11149.0 -2016-03-08 14:00:00,11136.0 -2016-03-08 15:00:00,11168.0 -2016-03-08 16:00:00,11078.0 -2016-03-08 17:00:00,10954.0 -2016-03-08 18:00:00,10924.0 -2016-03-08 19:00:00,10968.0 -2016-03-08 20:00:00,11480.0 -2016-03-08 21:00:00,11386.0 -2016-03-08 22:00:00,11044.0 -2016-03-08 23:00:00,10496.0 -2016-03-09 00:00:00,9730.0 -2016-03-07 01:00:00,9113.0 -2016-03-07 02:00:00,8786.0 -2016-03-07 03:00:00,8608.0 -2016-03-07 04:00:00,8574.0 -2016-03-07 05:00:00,8561.0 -2016-03-07 06:00:00,8887.0 -2016-03-07 07:00:00,9587.0 -2016-03-07 08:00:00,10480.0 -2016-03-07 09:00:00,10864.0 -2016-03-07 10:00:00,11006.0 -2016-03-07 11:00:00,11017.0 -2016-03-07 12:00:00,11169.0 -2016-03-07 13:00:00,11155.0 -2016-03-07 14:00:00,11053.0 -2016-03-07 15:00:00,11049.0 -2016-03-07 16:00:00,10939.0 -2016-03-07 17:00:00,10788.0 -2016-03-07 18:00:00,10846.0 -2016-03-07 19:00:00,11138.0 -2016-03-07 20:00:00,11540.0 -2016-03-07 21:00:00,11369.0 -2016-03-07 22:00:00,11066.0 -2016-03-07 23:00:00,10570.0 -2016-03-08 00:00:00,9814.0 -2016-03-06 01:00:00,9599.0 -2016-03-06 02:00:00,9234.0 -2016-03-06 03:00:00,8974.0 -2016-03-06 04:00:00,8863.0 -2016-03-06 05:00:00,8758.0 -2016-03-06 06:00:00,8821.0 -2016-03-06 07:00:00,9001.0 -2016-03-06 08:00:00,9107.0 -2016-03-06 09:00:00,9237.0 -2016-03-06 10:00:00,9441.0 -2016-03-06 11:00:00,9575.0 -2016-03-06 12:00:00,9613.0 -2016-03-06 13:00:00,9534.0 -2016-03-06 14:00:00,9446.0 -2016-03-06 15:00:00,9318.0 -2016-03-06 16:00:00,9248.0 -2016-03-06 17:00:00,9220.0 -2016-03-06 18:00:00,9337.0 -2016-03-06 19:00:00,9782.0 -2016-03-06 20:00:00,10561.0 -2016-03-06 21:00:00,10588.0 -2016-03-06 22:00:00,10408.0 -2016-03-06 23:00:00,10072.0 -2016-03-07 00:00:00,9594.0 -2016-03-05 01:00:00,10345.0 -2016-03-05 02:00:00,9852.0 -2016-03-05 03:00:00,9600.0 -2016-03-05 04:00:00,9370.0 -2016-03-05 05:00:00,9347.0 -2016-03-05 06:00:00,9365.0 -2016-03-05 07:00:00,9672.0 -2016-03-05 08:00:00,9930.0 -2016-03-05 09:00:00,10207.0 -2016-03-05 10:00:00,10570.0 -2016-03-05 11:00:00,10813.0 -2016-03-05 12:00:00,10867.0 -2016-03-05 13:00:00,10812.0 -2016-03-05 14:00:00,10702.0 -2016-03-05 15:00:00,10564.0 -2016-03-05 16:00:00,10458.0 -2016-03-05 17:00:00,10412.0 -2016-03-05 18:00:00,10541.0 -2016-03-05 19:00:00,10852.0 -2016-03-05 20:00:00,11170.0 -2016-03-05 21:00:00,11031.0 -2016-03-05 22:00:00,10886.0 -2016-03-05 23:00:00,10600.0 -2016-03-06 00:00:00,10101.0 -2016-03-04 01:00:00,10480.0 -2016-03-04 02:00:00,10054.0 -2016-03-04 03:00:00,9835.0 -2016-03-04 04:00:00,9723.0 -2016-03-04 05:00:00,9795.0 -2016-03-04 06:00:00,10107.0 -2016-03-04 07:00:00,10848.0 -2016-03-04 08:00:00,11692.0 -2016-03-04 09:00:00,12011.0 -2016-03-04 10:00:00,12006.0 -2016-03-04 11:00:00,11927.0 -2016-03-04 12:00:00,11885.0 -2016-03-04 13:00:00,11769.0 -2016-03-04 14:00:00,11632.0 -2016-03-04 15:00:00,11558.0 -2016-03-04 16:00:00,11473.0 -2016-03-04 17:00:00,11316.0 -2016-03-04 18:00:00,11396.0 -2016-03-04 19:00:00,11776.0 -2016-03-04 20:00:00,12250.0 -2016-03-04 21:00:00,12114.0 -2016-03-04 22:00:00,11873.0 -2016-03-04 23:00:00,11560.0 -2016-03-05 00:00:00,10932.0 -2016-03-03 01:00:00,10457.0 -2016-03-03 02:00:00,10075.0 -2016-03-03 03:00:00,9821.0 -2016-03-03 04:00:00,9718.0 -2016-03-03 05:00:00,9697.0 -2016-03-03 06:00:00,9977.0 -2016-03-03 07:00:00,10703.0 -2016-03-03 08:00:00,11699.0 -2016-03-03 09:00:00,12131.0 -2016-03-03 10:00:00,12316.0 -2016-03-03 11:00:00,12404.0 -2016-03-03 12:00:00,12433.0 -2016-03-03 13:00:00,12401.0 -2016-03-03 14:00:00,12321.0 -2016-03-03 15:00:00,12313.0 -2016-03-03 16:00:00,12256.0 -2016-03-03 17:00:00,12178.0 -2016-03-03 18:00:00,12268.0 -2016-03-03 19:00:00,12598.0 -2016-03-03 20:00:00,12840.0 -2016-03-03 21:00:00,12647.0 -2016-03-03 22:00:00,12347.0 -2016-03-03 23:00:00,11864.0 -2016-03-04 00:00:00,11116.0 -2016-03-02 01:00:00,10934.0 -2016-03-02 02:00:00,10539.0 -2016-03-02 03:00:00,10336.0 -2016-03-02 04:00:00,10263.0 -2016-03-02 05:00:00,10279.0 -2016-03-02 06:00:00,10563.0 -2016-03-02 07:00:00,11307.0 -2016-03-02 08:00:00,12226.0 -2016-03-02 09:00:00,12569.0 -2016-03-02 10:00:00,12563.0 -2016-03-02 11:00:00,12460.0 -2016-03-02 12:00:00,12451.0 -2016-03-02 13:00:00,12318.0 -2016-03-02 14:00:00,12152.0 -2016-03-02 15:00:00,12081.0 -2016-03-02 16:00:00,11859.0 -2016-03-02 17:00:00,11713.0 -2016-03-02 18:00:00,11926.0 -2016-03-02 19:00:00,12383.0 -2016-03-02 20:00:00,12815.0 -2016-03-02 21:00:00,12645.0 -2016-03-02 22:00:00,12366.0 -2016-03-02 23:00:00,11845.0 -2016-03-03 00:00:00,11100.0 -2016-03-01 01:00:00,10152.0 -2016-03-01 02:00:00,9794.0 -2016-03-01 03:00:00,9614.0 -2016-03-01 04:00:00,9494.0 -2016-03-01 05:00:00,9501.0 -2016-03-01 06:00:00,9796.0 -2016-03-01 07:00:00,10534.0 -2016-03-01 08:00:00,11599.0 -2016-03-01 09:00:00,12125.0 -2016-03-01 10:00:00,12381.0 -2016-03-01 11:00:00,12541.0 -2016-03-01 12:00:00,12757.0 -2016-03-01 13:00:00,12837.0 -2016-03-01 14:00:00,12780.0 -2016-03-01 15:00:00,12755.0 -2016-03-01 16:00:00,12673.0 -2016-03-01 17:00:00,12570.0 -2016-03-01 18:00:00,12615.0 -2016-03-01 19:00:00,12928.0 -2016-03-01 20:00:00,13384.0 -2016-03-01 21:00:00,13233.0 -2016-03-01 22:00:00,12933.0 -2016-03-01 23:00:00,12382.0 -2016-03-02 00:00:00,11568.0 -2016-02-29 01:00:00,9084.0 -2016-02-29 02:00:00,8776.0 -2016-02-29 03:00:00,8635.0 -2016-02-29 04:00:00,8604.0 -2016-02-29 05:00:00,8700.0 -2016-02-29 06:00:00,9008.0 -2016-02-29 07:00:00,9868.0 -2016-02-29 08:00:00,10893.0 -2016-02-29 09:00:00,11372.0 -2016-02-29 10:00:00,11455.0 -2016-02-29 11:00:00,11486.0 -2016-02-29 12:00:00,11520.0 -2016-02-29 13:00:00,11408.0 -2016-02-29 14:00:00,11258.0 -2016-02-29 15:00:00,11171.0 -2016-02-29 16:00:00,11047.0 -2016-02-29 17:00:00,11054.0 -2016-02-29 18:00:00,11338.0 -2016-02-29 19:00:00,11889.0 -2016-02-29 20:00:00,12235.0 -2016-02-29 21:00:00,12133.0 -2016-02-29 22:00:00,11912.0 -2016-02-29 23:00:00,11460.0 -2016-03-01 00:00:00,10785.0 -2016-02-28 01:00:00,9210.0 -2016-02-28 02:00:00,8870.0 -2016-02-28 03:00:00,8600.0 -2016-02-28 04:00:00,8448.0 -2016-02-28 05:00:00,8342.0 -2016-02-28 06:00:00,8388.0 -2016-02-28 07:00:00,8524.0 -2016-02-28 08:00:00,8644.0 -2016-02-28 09:00:00,8628.0 -2016-02-28 10:00:00,8861.0 -2016-02-28 11:00:00,8985.0 -2016-02-28 12:00:00,9055.0 -2016-02-28 13:00:00,9061.0 -2016-02-28 14:00:00,9037.0 -2016-02-28 15:00:00,9018.0 -2016-02-28 16:00:00,9132.0 -2016-02-28 17:00:00,9265.0 -2016-02-28 18:00:00,9361.0 -2016-02-28 19:00:00,9750.0 -2016-02-28 20:00:00,10287.0 -2016-02-28 21:00:00,10240.0 -2016-02-28 22:00:00,10148.0 -2016-02-28 23:00:00,9894.0 -2016-02-29 00:00:00,9469.0 -2016-02-27 01:00:00,10451.0 -2016-02-27 02:00:00,9990.0 -2016-02-27 03:00:00,9727.0 -2016-02-27 04:00:00,9560.0 -2016-02-27 05:00:00,9544.0 -2016-02-27 06:00:00,9642.0 -2016-02-27 07:00:00,9918.0 -2016-02-27 08:00:00,10201.0 -2016-02-27 09:00:00,10310.0 -2016-02-27 10:00:00,10477.0 -2016-02-27 11:00:00,10528.0 -2016-02-27 12:00:00,10398.0 -2016-02-27 13:00:00,10257.0 -2016-02-27 14:00:00,10020.0 -2016-02-27 15:00:00,9785.0 -2016-02-27 16:00:00,9611.0 -2016-02-27 17:00:00,9504.0 -2016-02-27 18:00:00,9579.0 -2016-02-27 19:00:00,9971.0 -2016-02-27 20:00:00,10615.0 -2016-02-27 21:00:00,10577.0 -2016-02-27 22:00:00,10422.0 -2016-02-27 23:00:00,10178.0 -2016-02-28 00:00:00,9742.0 -2016-02-26 01:00:00,10326.0 -2016-02-26 02:00:00,9898.0 -2016-02-26 03:00:00,9654.0 -2016-02-26 04:00:00,9526.0 -2016-02-26 05:00:00,9528.0 -2016-02-26 06:00:00,9816.0 -2016-02-26 07:00:00,10597.0 -2016-02-26 08:00:00,11513.0 -2016-02-26 09:00:00,11808.0 -2016-02-26 10:00:00,11870.0 -2016-02-26 11:00:00,11962.0 -2016-02-26 12:00:00,12043.0 -2016-02-26 13:00:00,12016.0 -2016-02-26 14:00:00,11977.0 -2016-02-26 15:00:00,12003.0 -2016-02-26 16:00:00,11972.0 -2016-02-26 17:00:00,11945.0 -2016-02-26 18:00:00,12070.0 -2016-02-26 19:00:00,12396.0 -2016-02-26 20:00:00,12483.0 -2016-02-26 21:00:00,12262.0 -2016-02-26 22:00:00,12009.0 -2016-02-26 23:00:00,11677.0 -2016-02-27 00:00:00,11036.0 -2016-02-25 01:00:00,10401.0 -2016-02-25 02:00:00,9971.0 -2016-02-25 03:00:00,9717.0 -2016-02-25 04:00:00,9546.0 -2016-02-25 05:00:00,9595.0 -2016-02-25 06:00:00,9887.0 -2016-02-25 07:00:00,10625.0 -2016-02-25 08:00:00,11652.0 -2016-02-25 09:00:00,12073.0 -2016-02-25 10:00:00,12215.0 -2016-02-25 11:00:00,12241.0 -2016-02-25 12:00:00,12192.0 -2016-02-25 13:00:00,12047.0 -2016-02-25 14:00:00,11962.0 -2016-02-25 15:00:00,12010.0 -2016-02-25 16:00:00,12053.0 -2016-02-25 17:00:00,12059.0 -2016-02-25 18:00:00,12229.0 -2016-02-25 19:00:00,12630.0 -2016-02-25 20:00:00,12736.0 -2016-02-25 21:00:00,12523.0 -2016-02-25 22:00:00,12211.0 -2016-02-25 23:00:00,11726.0 -2016-02-26 00:00:00,10981.0 -2016-02-24 01:00:00,9982.0 -2016-02-24 02:00:00,9562.0 -2016-02-24 03:00:00,9369.0 -2016-02-24 04:00:00,9238.0 -2016-02-24 05:00:00,9260.0 -2016-02-24 06:00:00,9577.0 -2016-02-24 07:00:00,10347.0 -2016-02-24 08:00:00,11431.0 -2016-02-24 09:00:00,11967.0 -2016-02-24 10:00:00,12201.0 -2016-02-24 11:00:00,12357.0 -2016-02-24 12:00:00,12546.0 -2016-02-24 13:00:00,12590.0 -2016-02-24 14:00:00,12563.0 -2016-02-24 15:00:00,12604.0 -2016-02-24 16:00:00,12467.0 -2016-02-24 17:00:00,12440.0 -2016-02-24 18:00:00,12551.0 -2016-02-24 19:00:00,12945.0 -2016-02-24 20:00:00,13051.0 -2016-02-24 21:00:00,12802.0 -2016-02-24 22:00:00,12417.0 -2016-02-24 23:00:00,11873.0 -2016-02-25 00:00:00,11095.0 -2016-02-23 01:00:00,9849.0 -2016-02-23 02:00:00,9480.0 -2016-02-23 03:00:00,9286.0 -2016-02-23 04:00:00,9233.0 -2016-02-23 05:00:00,9244.0 -2016-02-23 06:00:00,9513.0 -2016-02-23 07:00:00,10314.0 -2016-02-23 08:00:00,11314.0 -2016-02-23 09:00:00,11736.0 -2016-02-23 10:00:00,11818.0 -2016-02-23 11:00:00,11722.0 -2016-02-23 12:00:00,11626.0 -2016-02-23 13:00:00,11507.0 -2016-02-23 14:00:00,11376.0 -2016-02-23 15:00:00,11302.0 -2016-02-23 16:00:00,11215.0 -2016-02-23 17:00:00,11098.0 -2016-02-23 18:00:00,11201.0 -2016-02-23 19:00:00,11758.0 -2016-02-23 20:00:00,12185.0 -2016-02-23 21:00:00,12074.0 -2016-02-23 22:00:00,11820.0 -2016-02-23 23:00:00,11342.0 -2016-02-24 00:00:00,10655.0 -2016-02-22 01:00:00,9271.0 -2016-02-22 02:00:00,9024.0 -2016-02-22 03:00:00,8924.0 -2016-02-22 04:00:00,8873.0 -2016-02-22 05:00:00,8980.0 -2016-02-22 06:00:00,9316.0 -2016-02-22 07:00:00,10123.0 -2016-02-22 08:00:00,11185.0 -2016-02-22 09:00:00,11630.0 -2016-02-22 10:00:00,11678.0 -2016-02-22 11:00:00,11580.0 -2016-02-22 12:00:00,11526.0 -2016-02-22 13:00:00,11457.0 -2016-02-22 14:00:00,11358.0 -2016-02-22 15:00:00,11332.0 -2016-02-22 16:00:00,11201.0 -2016-02-22 17:00:00,11137.0 -2016-02-22 18:00:00,11311.0 -2016-02-22 19:00:00,11889.0 -2016-02-22 20:00:00,12116.0 -2016-02-22 21:00:00,11941.0 -2016-02-22 22:00:00,11680.0 -2016-02-22 23:00:00,11212.0 -2016-02-23 00:00:00,10491.0 -2016-02-21 01:00:00,8929.0 -2016-02-21 02:00:00,8631.0 -2016-02-21 03:00:00,8388.0 -2016-02-21 04:00:00,8300.0 -2016-02-21 05:00:00,8262.0 -2016-02-21 06:00:00,8320.0 -2016-02-21 07:00:00,8475.0 -2016-02-21 08:00:00,8708.0 -2016-02-21 09:00:00,8744.0 -2016-02-21 10:00:00,9053.0 -2016-02-21 11:00:00,9301.0 -2016-02-21 12:00:00,9514.0 -2016-02-21 13:00:00,9550.0 -2016-02-21 14:00:00,9607.0 -2016-02-21 15:00:00,9518.0 -2016-02-21 16:00:00,9511.0 -2016-02-21 17:00:00,9536.0 -2016-02-21 18:00:00,9823.0 -2016-02-21 19:00:00,10513.0 -2016-02-21 20:00:00,10887.0 -2016-02-21 21:00:00,10818.0 -2016-02-21 22:00:00,10520.0 -2016-02-21 23:00:00,10215.0 -2016-02-22 00:00:00,9735.0 -2016-02-20 01:00:00,9552.0 -2016-02-20 02:00:00,9118.0 -2016-02-20 03:00:00,8860.0 -2016-02-20 04:00:00,8699.0 -2016-02-20 05:00:00,8630.0 -2016-02-20 06:00:00,8719.0 -2016-02-20 07:00:00,8992.0 -2016-02-20 08:00:00,9307.0 -2016-02-20 09:00:00,9404.0 -2016-02-20 10:00:00,9654.0 -2016-02-20 11:00:00,9766.0 -2016-02-20 12:00:00,9716.0 -2016-02-20 13:00:00,9618.0 -2016-02-20 14:00:00,9523.0 -2016-02-20 15:00:00,9348.0 -2016-02-20 16:00:00,9167.0 -2016-02-20 17:00:00,9071.0 -2016-02-20 18:00:00,9171.0 -2016-02-20 19:00:00,9694.0 -2016-02-20 20:00:00,10234.0 -2016-02-20 21:00:00,10146.0 -2016-02-20 22:00:00,10049.0 -2016-02-20 23:00:00,9807.0 -2016-02-21 00:00:00,9409.0 -2016-02-19 01:00:00,10270.0 -2016-02-19 02:00:00,9794.0 -2016-02-19 03:00:00,9551.0 -2016-02-19 04:00:00,9385.0 -2016-02-19 05:00:00,9330.0 -2016-02-19 06:00:00,9528.0 -2016-02-19 07:00:00,10155.0 -2016-02-19 08:00:00,11142.0 -2016-02-19 09:00:00,11434.0 -2016-02-19 10:00:00,11450.0 -2016-02-19 11:00:00,11403.0 -2016-02-19 12:00:00,11327.0 -2016-02-19 13:00:00,11221.0 -2016-02-19 14:00:00,11069.0 -2016-02-19 15:00:00,10920.0 -2016-02-19 16:00:00,10762.0 -2016-02-19 17:00:00,10726.0 -2016-02-19 18:00:00,10845.0 -2016-02-19 19:00:00,11160.0 -2016-02-19 20:00:00,11525.0 -2016-02-19 21:00:00,11396.0 -2016-02-19 22:00:00,11132.0 -2016-02-19 23:00:00,10753.0 -2016-02-20 00:00:00,10140.0 -2016-02-18 01:00:00,10759.0 -2016-02-18 02:00:00,10325.0 -2016-02-18 03:00:00,10107.0 -2016-02-18 04:00:00,10009.0 -2016-02-18 05:00:00,10022.0 -2016-02-18 06:00:00,10315.0 -2016-02-18 07:00:00,11059.0 -2016-02-18 08:00:00,12124.0 -2016-02-18 09:00:00,12446.0 -2016-02-18 10:00:00,12628.0 -2016-02-18 11:00:00,12674.0 -2016-02-18 12:00:00,12564.0 -2016-02-18 13:00:00,12482.0 -2016-02-18 14:00:00,12283.0 -2016-02-18 15:00:00,12154.0 -2016-02-18 16:00:00,12071.0 -2016-02-18 17:00:00,12081.0 -2016-02-18 18:00:00,12188.0 -2016-02-18 19:00:00,12687.0 -2016-02-18 20:00:00,12858.0 -2016-02-18 21:00:00,12652.0 -2016-02-18 22:00:00,12304.0 -2016-02-18 23:00:00,11778.0 -2016-02-19 00:00:00,11004.0 -2016-02-17 01:00:00,10662.0 -2016-02-17 02:00:00,10221.0 -2016-02-17 03:00:00,9958.0 -2016-02-17 04:00:00,9900.0 -2016-02-17 05:00:00,9887.0 -2016-02-17 06:00:00,10190.0 -2016-02-17 07:00:00,10948.0 -2016-02-17 08:00:00,12007.0 -2016-02-17 09:00:00,12322.0 -2016-02-17 10:00:00,12334.0 -2016-02-17 11:00:00,12233.0 -2016-02-17 12:00:00,12198.0 -2016-02-17 13:00:00,12133.0 -2016-02-17 14:00:00,12051.0 -2016-02-17 15:00:00,12014.0 -2016-02-17 16:00:00,11856.0 -2016-02-17 17:00:00,11782.0 -2016-02-17 18:00:00,11965.0 -2016-02-17 19:00:00,12603.0 -2016-02-17 20:00:00,13012.0 -2016-02-17 21:00:00,12900.0 -2016-02-17 22:00:00,12648.0 -2016-02-17 23:00:00,12156.0 -2016-02-18 00:00:00,11444.0 -2016-02-16 01:00:00,10853.0 -2016-02-16 02:00:00,10424.0 -2016-02-16 03:00:00,10190.0 -2016-02-16 04:00:00,10041.0 -2016-02-16 05:00:00,10060.0 -2016-02-16 06:00:00,10356.0 -2016-02-16 07:00:00,11074.0 -2016-02-16 08:00:00,12131.0 -2016-02-16 09:00:00,12468.0 -2016-02-16 10:00:00,12654.0 -2016-02-16 11:00:00,12671.0 -2016-02-16 12:00:00,12598.0 -2016-02-16 13:00:00,12434.0 -2016-02-16 14:00:00,12284.0 -2016-02-16 15:00:00,12326.0 -2016-02-16 16:00:00,12297.0 -2016-02-16 17:00:00,12365.0 -2016-02-16 18:00:00,12484.0 -2016-02-16 19:00:00,12981.0 -2016-02-16 20:00:00,13123.0 -2016-02-16 21:00:00,12962.0 -2016-02-16 22:00:00,12684.0 -2016-02-16 23:00:00,12092.0 -2016-02-17 00:00:00,11318.0 -2016-02-15 01:00:00,11061.0 -2016-02-15 02:00:00,10686.0 -2016-02-15 03:00:00,10533.0 -2016-02-15 04:00:00,10348.0 -2016-02-15 05:00:00,10413.0 -2016-02-15 06:00:00,10591.0 -2016-02-15 07:00:00,11204.0 -2016-02-15 08:00:00,12013.0 -2016-02-15 09:00:00,12379.0 -2016-02-15 10:00:00,12706.0 -2016-02-15 11:00:00,12906.0 -2016-02-15 12:00:00,12983.0 -2016-02-15 13:00:00,12904.0 -2016-02-15 14:00:00,12790.0 -2016-02-15 15:00:00,12755.0 -2016-02-15 16:00:00,12693.0 -2016-02-15 17:00:00,12648.0 -2016-02-15 18:00:00,12808.0 -2016-02-15 19:00:00,13294.0 -2016-02-15 20:00:00,13385.0 -2016-02-15 21:00:00,13164.0 -2016-02-15 22:00:00,12858.0 -2016-02-15 23:00:00,12358.0 -2016-02-16 00:00:00,11567.0 -2016-02-14 01:00:00,11428.0 -2016-02-14 02:00:00,11018.0 -2016-02-14 03:00:00,10820.0 -2016-02-14 04:00:00,10650.0 -2016-02-14 05:00:00,10571.0 -2016-02-14 06:00:00,10604.0 -2016-02-14 07:00:00,10741.0 -2016-02-14 08:00:00,11018.0 -2016-02-14 09:00:00,11019.0 -2016-02-14 10:00:00,11312.0 -2016-02-14 11:00:00,11565.0 -2016-02-14 12:00:00,11783.0 -2016-02-14 13:00:00,11939.0 -2016-02-14 14:00:00,11885.0 -2016-02-14 15:00:00,11845.0 -2016-02-14 16:00:00,11704.0 -2016-02-14 17:00:00,11751.0 -2016-02-14 18:00:00,11938.0 -2016-02-14 19:00:00,12590.0 -2016-02-14 20:00:00,12877.0 -2016-02-14 21:00:00,12751.0 -2016-02-14 22:00:00,12442.0 -2016-02-14 23:00:00,12113.0 -2016-02-15 00:00:00,11573.0 -2016-02-13 01:00:00,12079.0 -2016-02-13 02:00:00,11649.0 -2016-02-13 03:00:00,11416.0 -2016-02-13 04:00:00,11236.0 -2016-02-13 05:00:00,11232.0 -2016-02-13 06:00:00,11310.0 -2016-02-13 07:00:00,11578.0 -2016-02-13 08:00:00,11953.0 -2016-02-13 09:00:00,12066.0 -2016-02-13 10:00:00,12263.0 -2016-02-13 11:00:00,12348.0 -2016-02-13 12:00:00,12326.0 -2016-02-13 13:00:00,12215.0 -2016-02-13 14:00:00,11995.0 -2016-02-13 15:00:00,11751.0 -2016-02-13 16:00:00,11493.0 -2016-02-13 17:00:00,11416.0 -2016-02-13 18:00:00,11625.0 -2016-02-13 19:00:00,12471.0 -2016-02-13 20:00:00,12842.0 -2016-02-13 21:00:00,12869.0 -2016-02-13 22:00:00,12653.0 -2016-02-13 23:00:00,12394.0 -2016-02-14 00:00:00,11927.0 -2016-02-12 01:00:00,11415.0 -2016-02-12 02:00:00,10970.0 -2016-02-12 03:00:00,10697.0 -2016-02-12 04:00:00,10577.0 -2016-02-12 05:00:00,10590.0 -2016-02-12 06:00:00,10838.0 -2016-02-12 07:00:00,11536.0 -2016-02-12 08:00:00,12534.0 -2016-02-12 09:00:00,13038.0 -2016-02-12 10:00:00,13295.0 -2016-02-12 11:00:00,13387.0 -2016-02-12 12:00:00,13298.0 -2016-02-12 13:00:00,13199.0 -2016-02-12 14:00:00,13031.0 -2016-02-12 15:00:00,12941.0 -2016-02-12 16:00:00,12834.0 -2016-02-12 17:00:00,12771.0 -2016-02-12 18:00:00,12906.0 -2016-02-12 19:00:00,13558.0 -2016-02-12 20:00:00,13910.0 -2016-02-12 21:00:00,13755.0 -2016-02-12 22:00:00,13538.0 -2016-02-12 23:00:00,13241.0 -2016-02-13 00:00:00,12640.0 -2016-02-11 01:00:00,11756.0 -2016-02-11 02:00:00,11350.0 -2016-02-11 03:00:00,11127.0 -2016-02-11 04:00:00,11026.0 -2016-02-11 05:00:00,11072.0 -2016-02-11 06:00:00,11375.0 -2016-02-11 07:00:00,12094.0 -2016-02-11 08:00:00,13092.0 -2016-02-11 09:00:00,13446.0 -2016-02-11 10:00:00,13437.0 -2016-02-11 11:00:00,13353.0 -2016-02-11 12:00:00,13260.0 -2016-02-11 13:00:00,13107.0 -2016-02-11 14:00:00,12929.0 -2016-02-11 15:00:00,12843.0 -2016-02-11 16:00:00,12606.0 -2016-02-11 17:00:00,12512.0 -2016-02-11 18:00:00,12685.0 -2016-02-11 19:00:00,13386.0 -2016-02-11 20:00:00,13670.0 -2016-02-11 21:00:00,13566.0 -2016-02-11 22:00:00,13298.0 -2016-02-11 23:00:00,12830.0 -2016-02-12 00:00:00,12130.0 -2016-02-10 01:00:00,11644.0 -2016-02-10 02:00:00,11246.0 -2016-02-10 03:00:00,11046.0 -2016-02-10 04:00:00,10933.0 -2016-02-10 05:00:00,10957.0 -2016-02-10 06:00:00,11261.0 -2016-02-10 07:00:00,12008.0 -2016-02-10 08:00:00,13047.0 -2016-02-10 09:00:00,13387.0 -2016-02-10 10:00:00,13385.0 -2016-02-10 11:00:00,13384.0 -2016-02-10 12:00:00,13373.0 -2016-02-10 13:00:00,13235.0 -2016-02-10 14:00:00,13087.0 -2016-02-10 15:00:00,13040.0 -2016-02-10 16:00:00,12892.0 -2016-02-10 17:00:00,12820.0 -2016-02-10 18:00:00,13034.0 -2016-02-10 19:00:00,13754.0 -2016-02-10 20:00:00,13978.0 -2016-02-10 21:00:00,13835.0 -2016-02-10 22:00:00,13611.0 -2016-02-10 23:00:00,13149.0 -2016-02-11 00:00:00,12396.0 -2016-02-09 01:00:00,10941.0 -2016-02-09 02:00:00,10546.0 -2016-02-09 03:00:00,10393.0 -2016-02-09 04:00:00,10294.0 -2016-02-09 05:00:00,10352.0 -2016-02-09 06:00:00,10661.0 -2016-02-09 07:00:00,11412.0 -2016-02-09 08:00:00,12530.0 -2016-02-09 09:00:00,12949.0 -2016-02-09 10:00:00,13110.0 -2016-02-09 11:00:00,13155.0 -2016-02-09 12:00:00,13130.0 -2016-02-09 13:00:00,13014.0 -2016-02-09 14:00:00,13016.0 -2016-02-09 15:00:00,13141.0 -2016-02-09 16:00:00,13150.0 -2016-02-09 17:00:00,13153.0 -2016-02-09 18:00:00,13356.0 -2016-02-09 19:00:00,13941.0 -2016-02-09 20:00:00,14012.0 -2016-02-09 21:00:00,13817.0 -2016-02-09 22:00:00,13578.0 -2016-02-09 23:00:00,13071.0 -2016-02-10 00:00:00,12308.0 -2016-02-08 01:00:00,9627.0 -2016-02-08 02:00:00,9296.0 -2016-02-08 03:00:00,9168.0 -2016-02-08 04:00:00,9119.0 -2016-02-08 05:00:00,9211.0 -2016-02-08 06:00:00,9517.0 -2016-02-08 07:00:00,10307.0 -2016-02-08 08:00:00,11448.0 -2016-02-08 09:00:00,12076.0 -2016-02-08 10:00:00,12261.0 -2016-02-08 11:00:00,12370.0 -2016-02-08 12:00:00,12485.0 -2016-02-08 13:00:00,12552.0 -2016-02-08 14:00:00,12564.0 -2016-02-08 15:00:00,12581.0 -2016-02-08 16:00:00,12502.0 -2016-02-08 17:00:00,12502.0 -2016-02-08 18:00:00,12729.0 -2016-02-08 19:00:00,13289.0 -2016-02-08 20:00:00,13291.0 -2016-02-08 21:00:00,13106.0 -2016-02-08 22:00:00,12808.0 -2016-02-08 23:00:00,12323.0 -2016-02-09 00:00:00,11584.0 -2016-02-07 01:00:00,9870.0 -2016-02-07 02:00:00,9526.0 -2016-02-07 03:00:00,9248.0 -2016-02-07 04:00:00,9070.0 -2016-02-07 05:00:00,9020.0 -2016-02-07 06:00:00,8972.0 -2016-02-07 07:00:00,9116.0 -2016-02-07 08:00:00,9375.0 -2016-02-07 09:00:00,9400.0 -2016-02-07 10:00:00,9597.0 -2016-02-07 11:00:00,9727.0 -2016-02-07 12:00:00,9814.0 -2016-02-07 13:00:00,9887.0 -2016-02-07 14:00:00,9834.0 -2016-02-07 15:00:00,9901.0 -2016-02-07 16:00:00,9867.0 -2016-02-07 17:00:00,9833.0 -2016-02-07 18:00:00,10002.0 -2016-02-07 19:00:00,10702.0 -2016-02-07 20:00:00,10910.0 -2016-02-07 21:00:00,10847.0 -2016-02-07 22:00:00,10697.0 -2016-02-07 23:00:00,10508.0 -2016-02-08 00:00:00,10092.0 -2016-02-06 01:00:00,10530.0 -2016-02-06 02:00:00,10160.0 -2016-02-06 03:00:00,9827.0 -2016-02-06 04:00:00,9671.0 -2016-02-06 05:00:00,9632.0 -2016-02-06 06:00:00,9720.0 -2016-02-06 07:00:00,9996.0 -2016-02-06 08:00:00,10498.0 -2016-02-06 09:00:00,10675.0 -2016-02-06 10:00:00,10844.0 -2016-02-06 11:00:00,10915.0 -2016-02-06 12:00:00,10920.0 -2016-02-06 13:00:00,10886.0 -2016-02-06 14:00:00,10668.0 -2016-02-06 15:00:00,10397.0 -2016-02-06 16:00:00,10205.0 -2016-02-06 17:00:00,10143.0 -2016-02-06 18:00:00,10259.0 -2016-02-06 19:00:00,11048.0 -2016-02-06 20:00:00,11303.0 -2016-02-06 21:00:00,11234.0 -2016-02-06 22:00:00,11100.0 -2016-02-06 23:00:00,10826.0 -2016-02-07 00:00:00,10409.0 -2016-02-05 01:00:00,10616.0 -2016-02-05 02:00:00,10197.0 -2016-02-05 03:00:00,9926.0 -2016-02-05 04:00:00,9820.0 -2016-02-05 05:00:00,9800.0 -2016-02-05 06:00:00,10040.0 -2016-02-05 07:00:00,10761.0 -2016-02-05 08:00:00,11815.0 -2016-02-05 09:00:00,12330.0 -2016-02-05 10:00:00,12503.0 -2016-02-05 11:00:00,12553.0 -2016-02-05 12:00:00,12505.0 -2016-02-05 13:00:00,12356.0 -2016-02-05 14:00:00,12210.0 -2016-02-05 15:00:00,12133.0 -2016-02-05 16:00:00,11980.0 -2016-02-05 17:00:00,11983.0 -2016-02-05 18:00:00,12226.0 -2016-02-05 19:00:00,12746.0 -2016-02-05 20:00:00,12651.0 -2016-02-05 21:00:00,12409.0 -2016-02-05 22:00:00,12213.0 -2016-02-05 23:00:00,11842.0 -2016-02-06 00:00:00,11205.0 -2016-02-04 01:00:00,10634.0 -2016-02-04 02:00:00,10184.0 -2016-02-04 03:00:00,9944.0 -2016-02-04 04:00:00,9817.0 -2016-02-04 05:00:00,9838.0 -2016-02-04 06:00:00,10167.0 -2016-02-04 07:00:00,10956.0 -2016-02-04 08:00:00,12165.0 -2016-02-04 09:00:00,12647.0 -2016-02-04 10:00:00,12628.0 -2016-02-04 11:00:00,12545.0 -2016-02-04 12:00:00,12498.0 -2016-02-04 13:00:00,12393.0 -2016-02-04 14:00:00,12280.0 -2016-02-04 15:00:00,12140.0 -2016-02-04 16:00:00,11978.0 -2016-02-04 17:00:00,11939.0 -2016-02-04 18:00:00,12212.0 -2016-02-04 19:00:00,12910.0 -2016-02-04 20:00:00,13011.0 -2016-02-04 21:00:00,12826.0 -2016-02-04 22:00:00,12532.0 -2016-02-04 23:00:00,12080.0 -2016-02-05 00:00:00,11300.0 -2016-02-03 01:00:00,10117.0 -2016-02-03 02:00:00,9647.0 -2016-02-03 03:00:00,9393.0 -2016-02-03 04:00:00,9315.0 -2016-02-03 05:00:00,9306.0 -2016-02-03 06:00:00,9587.0 -2016-02-03 07:00:00,10338.0 -2016-02-03 08:00:00,11454.0 -2016-02-03 09:00:00,11989.0 -2016-02-03 10:00:00,12147.0 -2016-02-03 11:00:00,12235.0 -2016-02-03 12:00:00,12400.0 -2016-02-03 13:00:00,12396.0 -2016-02-03 14:00:00,12401.0 -2016-02-03 15:00:00,12440.0 -2016-02-03 16:00:00,12333.0 -2016-02-03 17:00:00,12363.0 -2016-02-03 18:00:00,12674.0 -2016-02-03 19:00:00,13131.0 -2016-02-03 20:00:00,13004.0 -2016-02-03 21:00:00,12791.0 -2016-02-03 22:00:00,12501.0 -2016-02-03 23:00:00,12008.0 -2016-02-04 00:00:00,11292.0 -2016-02-02 01:00:00,10112.0 -2016-02-02 02:00:00,9684.0 -2016-02-02 03:00:00,9423.0 -2016-02-02 04:00:00,9278.0 -2016-02-02 05:00:00,9300.0 -2016-02-02 06:00:00,9607.0 -2016-02-02 07:00:00,10385.0 -2016-02-02 08:00:00,11515.0 -2016-02-02 09:00:00,12105.0 -2016-02-02 10:00:00,12232.0 -2016-02-02 11:00:00,12354.0 -2016-02-02 12:00:00,12555.0 -2016-02-02 13:00:00,12680.0 -2016-02-02 14:00:00,12789.0 -2016-02-02 15:00:00,12852.0 -2016-02-02 16:00:00,12734.0 -2016-02-02 17:00:00,12789.0 -2016-02-02 18:00:00,12966.0 -2016-02-02 19:00:00,13173.0 -2016-02-02 20:00:00,12908.0 -2016-02-02 21:00:00,12604.0 -2016-02-02 22:00:00,12243.0 -2016-02-02 23:00:00,11621.0 -2016-02-03 00:00:00,10847.0 -2016-02-01 01:00:00,9487.0 -2016-02-01 02:00:00,9210.0 -2016-02-01 03:00:00,9074.0 -2016-02-01 04:00:00,9047.0 -2016-02-01 05:00:00,9070.0 -2016-02-01 06:00:00,9446.0 -2016-02-01 07:00:00,10219.0 -2016-02-01 08:00:00,11426.0 -2016-02-01 09:00:00,11864.0 -2016-02-01 10:00:00,11964.0 -2016-02-01 11:00:00,11931.0 -2016-02-01 12:00:00,11805.0 -2016-02-01 13:00:00,11672.0 -2016-02-01 14:00:00,11550.0 -2016-02-01 15:00:00,11485.0 -2016-02-01 16:00:00,11312.0 -2016-02-01 17:00:00,11234.0 -2016-02-01 18:00:00,11433.0 -2016-02-01 19:00:00,12251.0 -2016-02-01 20:00:00,12375.0 -2016-02-01 21:00:00,12230.0 -2016-02-01 22:00:00,11973.0 -2016-02-01 23:00:00,11544.0 -2016-02-02 00:00:00,10841.0 -2016-01-31 01:00:00,9323.0 -2016-01-31 02:00:00,8930.0 -2016-01-31 03:00:00,8688.0 -2016-01-31 04:00:00,8498.0 -2016-01-31 05:00:00,8443.0 -2016-01-31 06:00:00,8457.0 -2016-01-31 07:00:00,8616.0 -2016-01-31 08:00:00,8885.0 -2016-01-31 09:00:00,9036.0 -2016-01-31 10:00:00,9215.0 -2016-01-31 11:00:00,9448.0 -2016-01-31 12:00:00,9586.0 -2016-01-31 13:00:00,9773.0 -2016-01-31 14:00:00,9778.0 -2016-01-31 15:00:00,9745.0 -2016-01-31 16:00:00,9761.0 -2016-01-31 17:00:00,9922.0 -2016-01-31 18:00:00,10281.0 -2016-01-31 19:00:00,10951.0 -2016-01-31 20:00:00,11057.0 -2016-01-31 21:00:00,10988.0 -2016-01-31 22:00:00,10770.0 -2016-01-31 23:00:00,10464.0 -2016-02-01 00:00:00,9968.0 -2016-01-30 01:00:00,10479.0 -2016-01-30 02:00:00,10092.0 -2016-01-30 03:00:00,9647.0 -2016-01-30 04:00:00,9391.0 -2016-01-30 05:00:00,9367.0 -2016-01-30 06:00:00,9388.0 -2016-01-30 07:00:00,9643.0 -2016-01-30 08:00:00,10050.0 -2016-01-30 09:00:00,10189.0 -2016-01-30 10:00:00,10488.0 -2016-01-30 11:00:00,10697.0 -2016-01-30 12:00:00,10566.0 -2016-01-30 13:00:00,10401.0 -2016-01-30 14:00:00,10193.0 -2016-01-30 15:00:00,10021.0 -2016-01-30 16:00:00,9922.0 -2016-01-30 17:00:00,9906.0 -2016-01-30 18:00:00,10166.0 -2016-01-30 19:00:00,10872.0 -2016-01-30 20:00:00,10926.0 -2016-01-30 21:00:00,10811.0 -2016-01-30 22:00:00,10596.0 -2016-01-30 23:00:00,10320.0 -2016-01-31 00:00:00,9815.0 -2016-01-29 01:00:00,10471.0 -2016-01-29 02:00:00,10050.0 -2016-01-29 03:00:00,9836.0 -2016-01-29 04:00:00,9726.0 -2016-01-29 05:00:00,9759.0 -2016-01-29 06:00:00,10058.0 -2016-01-29 07:00:00,10803.0 -2016-01-29 08:00:00,11965.0 -2016-01-29 09:00:00,12401.0 -2016-01-29 10:00:00,12363.0 -2016-01-29 11:00:00,12315.0 -2016-01-29 12:00:00,12266.0 -2016-01-29 13:00:00,12148.0 -2016-01-29 14:00:00,12016.0 -2016-01-29 15:00:00,11936.0 -2016-01-29 16:00:00,11812.0 -2016-01-29 17:00:00,11769.0 -2016-01-29 18:00:00,11993.0 -2016-01-29 19:00:00,12671.0 -2016-01-29 20:00:00,12675.0 -2016-01-29 21:00:00,12460.0 -2016-01-29 22:00:00,12177.0 -2016-01-29 23:00:00,11801.0 -2016-01-30 00:00:00,11086.0 -2016-01-28 01:00:00,10628.0 -2016-01-28 02:00:00,10188.0 -2016-01-28 03:00:00,9922.0 -2016-01-28 04:00:00,9794.0 -2016-01-28 05:00:00,9792.0 -2016-01-28 06:00:00,10033.0 -2016-01-28 07:00:00,10777.0 -2016-01-28 08:00:00,11874.0 -2016-01-28 09:00:00,12264.0 -2016-01-28 10:00:00,12265.0 -2016-01-28 11:00:00,12319.0 -2016-01-28 12:00:00,12349.0 -2016-01-28 13:00:00,12352.0 -2016-01-28 14:00:00,12270.0 -2016-01-28 15:00:00,12250.0 -2016-01-28 16:00:00,12181.0 -2016-01-28 17:00:00,12298.0 -2016-01-28 18:00:00,12616.0 -2016-01-28 19:00:00,13043.0 -2016-01-28 20:00:00,12877.0 -2016-01-28 21:00:00,12674.0 -2016-01-28 22:00:00,12416.0 -2016-01-28 23:00:00,11924.0 -2016-01-29 00:00:00,11172.0 -2016-01-27 01:00:00,10816.0 -2016-01-27 02:00:00,10371.0 -2016-01-27 03:00:00,10117.0 -2016-01-27 04:00:00,9980.0 -2016-01-27 05:00:00,10006.0 -2016-01-27 06:00:00,10225.0 -2016-01-27 07:00:00,11015.0 -2016-01-27 08:00:00,12135.0 -2016-01-27 09:00:00,12691.0 -2016-01-27 10:00:00,12803.0 -2016-01-27 11:00:00,12861.0 -2016-01-27 12:00:00,12852.0 -2016-01-27 13:00:00,12621.0 -2016-01-27 14:00:00,12384.0 -2016-01-27 15:00:00,12265.0 -2016-01-27 16:00:00,12111.0 -2016-01-27 17:00:00,12051.0 -2016-01-27 18:00:00,12421.0 -2016-01-27 19:00:00,13209.0 -2016-01-27 20:00:00,13126.0 -2016-01-27 21:00:00,12954.0 -2016-01-27 22:00:00,12661.0 -2016-01-27 23:00:00,12119.0 -2016-01-28 00:00:00,11340.0 -2016-01-26 01:00:00,10314.0 -2016-01-26 02:00:00,9904.0 -2016-01-26 03:00:00,9719.0 -2016-01-26 04:00:00,9646.0 -2016-01-26 05:00:00,9716.0 -2016-01-26 06:00:00,10003.0 -2016-01-26 07:00:00,10777.0 -2016-01-26 08:00:00,11920.0 -2016-01-26 09:00:00,12566.0 -2016-01-26 10:00:00,12638.0 -2016-01-26 11:00:00,12677.0 -2016-01-26 12:00:00,12765.0 -2016-01-26 13:00:00,12798.0 -2016-01-26 14:00:00,12792.0 -2016-01-26 15:00:00,12815.0 -2016-01-26 16:00:00,12733.0 -2016-01-26 17:00:00,12746.0 -2016-01-26 18:00:00,13082.0 -2016-01-26 19:00:00,13547.0 -2016-01-26 20:00:00,13399.0 -2016-01-26 21:00:00,13193.0 -2016-01-26 22:00:00,12867.0 -2016-01-26 23:00:00,12273.0 -2016-01-27 00:00:00,11526.0 -2016-01-25 01:00:00,10053.0 -2016-01-25 02:00:00,9760.0 -2016-01-25 03:00:00,9639.0 -2016-01-25 04:00:00,9571.0 -2016-01-25 05:00:00,9619.0 -2016-01-25 06:00:00,9958.0 -2016-01-25 07:00:00,10728.0 -2016-01-25 08:00:00,11856.0 -2016-01-25 09:00:00,12469.0 -2016-01-25 10:00:00,12428.0 -2016-01-25 11:00:00,12381.0 -2016-01-25 12:00:00,12283.0 -2016-01-25 13:00:00,12164.0 -2016-01-25 14:00:00,12086.0 -2016-01-25 15:00:00,12171.0 -2016-01-25 16:00:00,12172.0 -2016-01-25 17:00:00,12298.0 -2016-01-25 18:00:00,12751.0 -2016-01-25 19:00:00,13165.0 -2016-01-25 20:00:00,12929.0 -2016-01-25 21:00:00,12674.0 -2016-01-25 22:00:00,12328.0 -2016-01-25 23:00:00,11754.0 -2016-01-26 00:00:00,10951.0 -2016-01-24 01:00:00,10461.0 -2016-01-24 02:00:00,10082.0 -2016-01-24 03:00:00,9894.0 -2016-01-24 04:00:00,9732.0 -2016-01-24 05:00:00,9699.0 -2016-01-24 06:00:00,9745.0 -2016-01-24 07:00:00,9900.0 -2016-01-24 08:00:00,10148.0 -2016-01-24 09:00:00,10247.0 -2016-01-24 10:00:00,10406.0 -2016-01-24 11:00:00,10692.0 -2016-01-24 12:00:00,10725.0 -2016-01-24 13:00:00,10672.0 -2016-01-24 14:00:00,10562.0 -2016-01-24 15:00:00,10473.0 -2016-01-24 16:00:00,10408.0 -2016-01-24 17:00:00,10485.0 -2016-01-24 18:00:00,10928.0 -2016-01-24 19:00:00,11718.0 -2016-01-24 20:00:00,11815.0 -2016-01-24 21:00:00,11714.0 -2016-01-24 22:00:00,11443.0 -2016-01-24 23:00:00,11076.0 -2016-01-25 00:00:00,10499.0 -2016-01-23 01:00:00,11039.0 -2016-01-23 02:00:00,10538.0 -2016-01-23 03:00:00,10259.0 -2016-01-23 04:00:00,10045.0 -2016-01-23 05:00:00,10006.0 -2016-01-23 06:00:00,10056.0 -2016-01-23 07:00:00,10331.0 -2016-01-23 08:00:00,10773.0 -2016-01-23 09:00:00,11017.0 -2016-01-23 10:00:00,11226.0 -2016-01-23 11:00:00,11457.0 -2016-01-23 12:00:00,11394.0 -2016-01-23 13:00:00,11288.0 -2016-01-23 14:00:00,11178.0 -2016-01-23 15:00:00,10996.0 -2016-01-23 16:00:00,10871.0 -2016-01-23 17:00:00,10813.0 -2016-01-23 18:00:00,11080.0 -2016-01-23 19:00:00,11874.0 -2016-01-23 20:00:00,11944.0 -2016-01-23 21:00:00,11804.0 -2016-01-23 22:00:00,11632.0 -2016-01-23 23:00:00,11402.0 -2016-01-24 00:00:00,10931.0 -2016-01-22 01:00:00,11155.0 -2016-01-22 02:00:00,10787.0 -2016-01-22 03:00:00,10536.0 -2016-01-22 04:00:00,10400.0 -2016-01-22 05:00:00,10407.0 -2016-01-22 06:00:00,10654.0 -2016-01-22 07:00:00,11285.0 -2016-01-22 08:00:00,12363.0 -2016-01-22 09:00:00,13023.0 -2016-01-22 10:00:00,13160.0 -2016-01-22 11:00:00,13217.0 -2016-01-22 12:00:00,13261.0 -2016-01-22 13:00:00,13191.0 -2016-01-22 14:00:00,13093.0 -2016-01-22 15:00:00,13023.0 -2016-01-22 16:00:00,12886.0 -2016-01-22 17:00:00,12669.0 -2016-01-22 18:00:00,12998.0 -2016-01-22 19:00:00,13567.0 -2016-01-22 20:00:00,13402.0 -2016-01-22 21:00:00,13115.0 -2016-01-22 22:00:00,12761.0 -2016-01-22 23:00:00,12348.0 -2016-01-23 00:00:00,11660.0 -2016-01-21 01:00:00,11561.0 -2016-01-21 02:00:00,11167.0 -2016-01-21 03:00:00,10930.0 -2016-01-21 04:00:00,10839.0 -2016-01-21 05:00:00,10813.0 -2016-01-21 06:00:00,11062.0 -2016-01-21 07:00:00,11781.0 -2016-01-21 08:00:00,12842.0 -2016-01-21 09:00:00,13363.0 -2016-01-21 10:00:00,13361.0 -2016-01-21 11:00:00,13214.0 -2016-01-21 12:00:00,13075.0 -2016-01-21 13:00:00,12914.0 -2016-01-21 14:00:00,12744.0 -2016-01-21 15:00:00,12684.0 -2016-01-21 16:00:00,12526.0 -2016-01-21 17:00:00,12478.0 -2016-01-21 18:00:00,12858.0 -2016-01-21 19:00:00,13641.0 -2016-01-21 20:00:00,13591.0 -2016-01-21 21:00:00,13434.0 -2016-01-21 22:00:00,13164.0 -2016-01-21 23:00:00,12632.0 -2016-01-22 00:00:00,11869.0 -2016-01-20 01:00:00,11930.0 -2016-01-20 02:00:00,11516.0 -2016-01-20 03:00:00,11292.0 -2016-01-20 04:00:00,11139.0 -2016-01-20 05:00:00,11135.0 -2016-01-20 06:00:00,11379.0 -2016-01-20 07:00:00,12016.0 -2016-01-20 08:00:00,13014.0 -2016-01-20 09:00:00,13479.0 -2016-01-20 10:00:00,13517.0 -2016-01-20 11:00:00,13580.0 -2016-01-20 12:00:00,13572.0 -2016-01-20 13:00:00,13423.0 -2016-01-20 14:00:00,13217.0 -2016-01-20 15:00:00,13148.0 -2016-01-20 16:00:00,12944.0 -2016-01-20 17:00:00,12858.0 -2016-01-20 18:00:00,13198.0 -2016-01-20 19:00:00,14018.0 -2016-01-20 20:00:00,13988.0 -2016-01-20 21:00:00,13845.0 -2016-01-20 22:00:00,13533.0 -2016-01-20 23:00:00,12971.0 -2016-01-21 00:00:00,12257.0 -2016-01-19 01:00:00,12518.0 -2016-01-19 02:00:00,12154.0 -2016-01-19 03:00:00,11968.0 -2016-01-19 04:00:00,11818.0 -2016-01-19 05:00:00,11812.0 -2016-01-19 06:00:00,12050.0 -2016-01-19 07:00:00,12689.0 -2016-01-19 08:00:00,13767.0 -2016-01-19 09:00:00,14253.0 -2016-01-19 10:00:00,14194.0 -2016-01-19 11:00:00,14117.0 -2016-01-19 12:00:00,14001.0 -2016-01-19 13:00:00,13837.0 -2016-01-19 14:00:00,13685.0 -2016-01-19 15:00:00,13541.0 -2016-01-19 16:00:00,13386.0 -2016-01-19 17:00:00,13398.0 -2016-01-19 18:00:00,13806.0 -2016-01-19 19:00:00,14508.0 -2016-01-19 20:00:00,14445.0 -2016-01-19 21:00:00,14275.0 -2016-01-19 22:00:00,13975.0 -2016-01-19 23:00:00,13398.0 -2016-01-20 00:00:00,12639.0 -2016-01-18 01:00:00,12191.0 -2016-01-18 02:00:00,11875.0 -2016-01-18 03:00:00,11727.0 -2016-01-18 04:00:00,11664.0 -2016-01-18 05:00:00,11657.0 -2016-01-18 06:00:00,11892.0 -2016-01-18 07:00:00,12435.0 -2016-01-18 08:00:00,13335.0 -2016-01-18 09:00:00,13816.0 -2016-01-18 10:00:00,13951.0 -2016-01-18 11:00:00,14063.0 -2016-01-18 12:00:00,14095.0 -2016-01-18 13:00:00,14072.0 -2016-01-18 14:00:00,13938.0 -2016-01-18 15:00:00,13857.0 -2016-01-18 16:00:00,13693.0 -2016-01-18 17:00:00,13676.0 -2016-01-18 18:00:00,14102.0 -2016-01-18 19:00:00,14956.0 -2016-01-18 20:00:00,14938.0 -2016-01-18 21:00:00,14782.0 -2016-01-18 22:00:00,14451.0 -2016-01-18 23:00:00,13947.0 -2016-01-19 00:00:00,13234.0 -2016-01-17 01:00:00,11117.0 -2016-01-17 02:00:00,10866.0 -2016-01-17 03:00:00,10618.0 -2016-01-17 04:00:00,10527.0 -2016-01-17 05:00:00,10481.0 -2016-01-17 06:00:00,10507.0 -2016-01-17 07:00:00,10712.0 -2016-01-17 08:00:00,11073.0 -2016-01-17 09:00:00,11281.0 -2016-01-17 10:00:00,11495.0 -2016-01-17 11:00:00,11759.0 -2016-01-17 12:00:00,11851.0 -2016-01-17 13:00:00,11975.0 -2016-01-17 14:00:00,11961.0 -2016-01-17 15:00:00,11957.0 -2016-01-17 16:00:00,11920.0 -2016-01-17 17:00:00,12101.0 -2016-01-17 18:00:00,12625.0 -2016-01-17 19:00:00,13626.0 -2016-01-17 20:00:00,13663.0 -2016-01-17 21:00:00,13595.0 -2016-01-17 22:00:00,13413.0 -2016-01-17 23:00:00,13145.0 -2016-01-18 00:00:00,12634.0 -2016-01-16 01:00:00,10540.0 -2016-01-16 02:00:00,10067.0 -2016-01-16 03:00:00,9776.0 -2016-01-16 04:00:00,9598.0 -2016-01-16 05:00:00,9590.0 -2016-01-16 06:00:00,9687.0 -2016-01-16 07:00:00,10009.0 -2016-01-16 08:00:00,10452.0 -2016-01-16 09:00:00,10709.0 -2016-01-16 10:00:00,10853.0 -2016-01-16 11:00:00,10990.0 -2016-01-16 12:00:00,11081.0 -2016-01-16 13:00:00,11114.0 -2016-01-16 14:00:00,10995.0 -2016-01-16 15:00:00,10810.0 -2016-01-16 16:00:00,10689.0 -2016-01-16 17:00:00,10764.0 -2016-01-16 18:00:00,11212.0 -2016-01-16 19:00:00,12114.0 -2016-01-16 20:00:00,12249.0 -2016-01-16 21:00:00,12147.0 -2016-01-16 22:00:00,12062.0 -2016-01-16 23:00:00,11816.0 -2016-01-17 00:00:00,11501.0 -2016-01-15 01:00:00,10284.0 -2016-01-15 02:00:00,9783.0 -2016-01-15 03:00:00,9561.0 -2016-01-15 04:00:00,9390.0 -2016-01-15 05:00:00,9369.0 -2016-01-15 06:00:00,9581.0 -2016-01-15 07:00:00,10278.0 -2016-01-15 08:00:00,11334.0 -2016-01-15 09:00:00,11949.0 -2016-01-15 10:00:00,12053.0 -2016-01-15 11:00:00,12146.0 -2016-01-15 12:00:00,12206.0 -2016-01-15 13:00:00,12235.0 -2016-01-15 14:00:00,12236.0 -2016-01-15 15:00:00,12242.0 -2016-01-15 16:00:00,12219.0 -2016-01-15 17:00:00,12274.0 -2016-01-15 18:00:00,12696.0 -2016-01-15 19:00:00,13035.0 -2016-01-15 20:00:00,12866.0 -2016-01-15 21:00:00,12605.0 -2016-01-15 22:00:00,12312.0 -2016-01-15 23:00:00,11928.0 -2016-01-16 00:00:00,11205.0 -2016-01-14 01:00:00,11577.0 -2016-01-14 02:00:00,11052.0 -2016-01-14 03:00:00,10782.0 -2016-01-14 04:00:00,10603.0 -2016-01-14 05:00:00,10575.0 -2016-01-14 06:00:00,10752.0 -2016-01-14 07:00:00,11378.0 -2016-01-14 08:00:00,12407.0 -2016-01-14 09:00:00,12857.0 -2016-01-14 10:00:00,12777.0 -2016-01-14 11:00:00,12657.0 -2016-01-14 12:00:00,12547.0 -2016-01-14 13:00:00,12358.0 -2016-01-14 14:00:00,12188.0 -2016-01-14 15:00:00,12120.0 -2016-01-14 16:00:00,11982.0 -2016-01-14 17:00:00,11893.0 -2016-01-14 18:00:00,12300.0 -2016-01-14 19:00:00,12985.0 -2016-01-14 20:00:00,12847.0 -2016-01-14 21:00:00,12641.0 -2016-01-14 22:00:00,12326.0 -2016-01-14 23:00:00,11764.0 -2016-01-15 00:00:00,11007.0 -2016-01-13 01:00:00,12378.0 -2016-01-13 02:00:00,11964.0 -2016-01-13 03:00:00,11748.0 -2016-01-13 04:00:00,11612.0 -2016-01-13 05:00:00,11618.0 -2016-01-13 06:00:00,11881.0 -2016-01-13 07:00:00,12498.0 -2016-01-13 08:00:00,13591.0 -2016-01-13 09:00:00,14090.0 -2016-01-13 10:00:00,14155.0 -2016-01-13 11:00:00,14351.0 -2016-01-13 12:00:00,14441.0 -2016-01-13 13:00:00,14426.0 -2016-01-13 14:00:00,14322.0 -2016-01-13 15:00:00,14166.0 -2016-01-13 16:00:00,14001.0 -2016-01-13 17:00:00,13890.0 -2016-01-13 18:00:00,14082.0 -2016-01-13 19:00:00,14622.0 -2016-01-13 20:00:00,14416.0 -2016-01-13 21:00:00,14148.0 -2016-01-13 22:00:00,13823.0 -2016-01-13 23:00:00,13224.0 -2016-01-14 00:00:00,12366.0 -2016-01-12 01:00:00,11752.0 -2016-01-12 02:00:00,11328.0 -2016-01-12 03:00:00,11087.0 -2016-01-12 04:00:00,10981.0 -2016-01-12 05:00:00,10967.0 -2016-01-12 06:00:00,11220.0 -2016-01-12 07:00:00,11956.0 -2016-01-12 08:00:00,13144.0 -2016-01-12 09:00:00,13686.0 -2016-01-12 10:00:00,13704.0 -2016-01-12 11:00:00,13738.0 -2016-01-12 12:00:00,13758.0 -2016-01-12 13:00:00,13720.0 -2016-01-12 14:00:00,13671.0 -2016-01-12 15:00:00,13632.0 -2016-01-12 16:00:00,13481.0 -2016-01-12 17:00:00,13534.0 -2016-01-12 18:00:00,14032.0 -2016-01-12 19:00:00,14852.0 -2016-01-12 20:00:00,14823.0 -2016-01-12 21:00:00,14657.0 -2016-01-12 22:00:00,14390.0 -2016-01-12 23:00:00,13889.0 -2016-01-13 00:00:00,13057.0 -2016-01-11 01:00:00,11513.0 -2016-01-11 02:00:00,11278.0 -2016-01-11 03:00:00,11090.0 -2016-01-11 04:00:00,11048.0 -2016-01-11 05:00:00,11097.0 -2016-01-11 06:00:00,11349.0 -2016-01-11 07:00:00,12058.0 -2016-01-11 08:00:00,13235.0 -2016-01-11 09:00:00,13758.0 -2016-01-11 10:00:00,13769.0 -2016-01-11 11:00:00,13830.0 -2016-01-11 12:00:00,13938.0 -2016-01-11 13:00:00,13887.0 -2016-01-11 14:00:00,13871.0 -2016-01-11 15:00:00,13867.0 -2016-01-11 16:00:00,13729.0 -2016-01-11 17:00:00,13681.0 -2016-01-11 18:00:00,14085.0 -2016-01-11 19:00:00,14565.0 -2016-01-11 20:00:00,14409.0 -2016-01-11 21:00:00,14129.0 -2016-01-11 22:00:00,13774.0 -2016-01-11 23:00:00,13223.0 -2016-01-12 00:00:00,12449.0 -2016-01-10 01:00:00,10429.0 -2016-01-10 02:00:00,10028.0 -2016-01-10 03:00:00,9839.0 -2016-01-10 04:00:00,9725.0 -2016-01-10 05:00:00,9662.0 -2016-01-10 06:00:00,9730.0 -2016-01-10 07:00:00,9895.0 -2016-01-10 08:00:00,10242.0 -2016-01-10 09:00:00,10334.0 -2016-01-10 10:00:00,10602.0 -2016-01-10 11:00:00,10799.0 -2016-01-10 12:00:00,10928.0 -2016-01-10 13:00:00,11008.0 -2016-01-10 14:00:00,11118.0 -2016-01-10 15:00:00,11114.0 -2016-01-10 16:00:00,11186.0 -2016-01-10 17:00:00,11350.0 -2016-01-10 18:00:00,12012.0 -2016-01-10 19:00:00,12998.0 -2016-01-10 20:00:00,13135.0 -2016-01-10 21:00:00,13044.0 -2016-01-10 22:00:00,12863.0 -2016-01-10 23:00:00,12540.0 -2016-01-11 00:00:00,11994.0 -2016-01-09 01:00:00,9982.0 -2016-01-09 02:00:00,9475.0 -2016-01-09 03:00:00,9167.0 -2016-01-09 04:00:00,8989.0 -2016-01-09 05:00:00,8906.0 -2016-01-09 06:00:00,8997.0 -2016-01-09 07:00:00,9243.0 -2016-01-09 08:00:00,9743.0 -2016-01-09 09:00:00,10091.0 -2016-01-09 10:00:00,10418.0 -2016-01-09 11:00:00,10732.0 -2016-01-09 12:00:00,10902.0 -2016-01-09 13:00:00,10902.0 -2016-01-09 14:00:00,10956.0 -2016-01-09 15:00:00,10919.0 -2016-01-09 16:00:00,11028.0 -2016-01-09 17:00:00,11189.0 -2016-01-09 18:00:00,11608.0 -2016-01-09 19:00:00,12019.0 -2016-01-09 20:00:00,11981.0 -2016-01-09 21:00:00,11789.0 -2016-01-09 22:00:00,11593.0 -2016-01-09 23:00:00,11299.0 -2016-01-10 00:00:00,10867.0 -2016-01-08 01:00:00,10087.0 -2016-01-08 02:00:00,9630.0 -2016-01-08 03:00:00,9331.0 -2016-01-08 04:00:00,9198.0 -2016-01-08 05:00:00,9161.0 -2016-01-08 06:00:00,9400.0 -2016-01-08 07:00:00,10086.0 -2016-01-08 08:00:00,11197.0 -2016-01-08 09:00:00,11878.0 -2016-01-08 10:00:00,11973.0 -2016-01-08 11:00:00,12049.0 -2016-01-08 12:00:00,12108.0 -2016-01-08 13:00:00,12088.0 -2016-01-08 14:00:00,11988.0 -2016-01-08 15:00:00,11972.0 -2016-01-08 16:00:00,11895.0 -2016-01-08 17:00:00,11907.0 -2016-01-08 18:00:00,12391.0 -2016-01-08 19:00:00,12643.0 -2016-01-08 20:00:00,12414.0 -2016-01-08 21:00:00,12100.0 -2016-01-08 22:00:00,11734.0 -2016-01-08 23:00:00,11340.0 -2016-01-09 00:00:00,10626.0 -2016-01-07 01:00:00,10445.0 -2016-01-07 02:00:00,9926.0 -2016-01-07 03:00:00,9664.0 -2016-01-07 04:00:00,9483.0 -2016-01-07 05:00:00,9458.0 -2016-01-07 06:00:00,9704.0 -2016-01-07 07:00:00,10408.0 -2016-01-07 08:00:00,11512.0 -2016-01-07 09:00:00,12081.0 -2016-01-07 10:00:00,12104.0 -2016-01-07 11:00:00,12004.0 -2016-01-07 12:00:00,12008.0 -2016-01-07 13:00:00,12015.0 -2016-01-07 14:00:00,12003.0 -2016-01-07 15:00:00,12073.0 -2016-01-07 16:00:00,11988.0 -2016-01-07 17:00:00,11949.0 -2016-01-07 18:00:00,12405.0 -2016-01-07 19:00:00,12872.0 -2016-01-07 20:00:00,12683.0 -2016-01-07 21:00:00,12450.0 -2016-01-07 22:00:00,12136.0 -2016-01-07 23:00:00,11594.0 -2016-01-08 00:00:00,10802.0 -2016-01-06 01:00:00,10835.0 -2016-01-06 02:00:00,10385.0 -2016-01-06 03:00:00,10108.0 -2016-01-06 04:00:00,9997.0 -2016-01-06 05:00:00,9974.0 -2016-01-06 06:00:00,10261.0 -2016-01-06 07:00:00,10977.0 -2016-01-06 08:00:00,12109.0 -2016-01-06 09:00:00,12590.0 -2016-01-06 10:00:00,12559.0 -2016-01-06 11:00:00,12410.0 -2016-01-06 12:00:00,12388.0 -2016-01-06 13:00:00,12278.0 -2016-01-06 14:00:00,12183.0 -2016-01-06 15:00:00,12220.0 -2016-01-06 16:00:00,12180.0 -2016-01-06 17:00:00,12179.0 -2016-01-06 18:00:00,12647.0 -2016-01-06 19:00:00,13221.0 -2016-01-06 20:00:00,13015.0 -2016-01-06 21:00:00,12775.0 -2016-01-06 22:00:00,12498.0 -2016-01-06 23:00:00,11979.0 -2016-01-07 00:00:00,11209.0 -2016-01-05 01:00:00,10371.0 -2016-01-05 02:00:00,10391.0 -2016-01-05 03:00:00,10158.0 -2016-01-05 04:00:00,10066.0 -2016-01-05 05:00:00,10101.0 -2016-01-05 06:00:00,10395.0 -2016-01-05 07:00:00,11138.0 -2016-01-05 08:00:00,12289.0 -2016-01-05 09:00:00,12796.0 -2016-01-05 10:00:00,12728.0 -2016-01-05 11:00:00,12668.0 -2016-01-05 12:00:00,12579.0 -2016-01-05 13:00:00,12459.0 -2016-01-05 14:00:00,12326.0 -2016-01-05 15:00:00,12298.0 -2016-01-05 16:00:00,12230.0 -2016-01-05 17:00:00,12264.0 -2016-01-05 18:00:00,12841.0 -2016-01-05 19:00:00,13521.0 -2016-01-05 20:00:00,13377.0 -2016-01-05 21:00:00,13214.0 -2016-01-05 22:00:00,12920.0 -2016-01-05 23:00:00,12360.0 -2016-01-06 00:00:00,11573.0 -2016-01-04 01:00:00,10150.0 -2016-01-04 02:00:00,9808.0 -2016-01-04 03:00:00,9625.0 -2016-01-04 04:00:00,9520.0 -2016-01-04 05:00:00,9603.0 -2016-01-04 06:00:00,9953.0 -2016-01-04 07:00:00,10652.0 -2016-01-04 08:00:00,11812.0 -2016-01-04 09:00:00,12346.0 -2016-01-04 10:00:00,12403.0 -2016-01-04 11:00:00,12415.0 -2016-01-04 12:00:00,12480.0 -2016-01-04 13:00:00,12480.0 -2016-01-04 14:00:00,12454.0 -2016-01-04 15:00:00,12437.0 -2016-01-04 16:00:00,12394.0 -2016-01-04 17:00:00,12428.0 -2016-01-04 18:00:00,12990.0 -2016-01-04 19:00:00,13560.0 -2016-01-04 20:00:00,13376.0 -2016-01-04 21:00:00,13155.0 -2016-01-04 22:00:00,12856.0 -2016-01-04 23:00:00,12306.0 -2016-01-05 00:00:00,11534.0 -2016-01-03 01:00:00,10373.0 -2016-01-03 02:00:00,9915.0 -2016-01-03 03:00:00,9638.0 -2016-01-03 04:00:00,9455.0 -2016-01-03 05:00:00,9395.0 -2016-01-03 06:00:00,9401.0 -2016-01-03 07:00:00,9587.0 -2016-01-03 08:00:00,9785.0 -2016-01-03 09:00:00,9910.0 -2016-01-03 10:00:00,10042.0 -2016-01-03 11:00:00,10312.0 -2016-01-03 12:00:00,10445.0 -2016-01-03 13:00:00,10566.0 -2016-01-03 14:00:00,10617.0 -2016-01-03 15:00:00,10667.0 -2016-01-03 16:00:00,10673.0 -2016-01-03 17:00:00,10899.0 -2016-01-03 18:00:00,11562.0 -2016-01-03 19:00:00,12138.0 -2016-01-03 20:00:00,12084.0 -2016-01-03 21:00:00,11982.0 -2016-01-03 22:00:00,11705.0 -2016-01-03 23:00:00,11399.0 -2016-01-04 00:00:00,10704.0 -2016-01-02 01:00:00,10170.0 -2016-01-02 02:00:00,9709.0 -2016-01-02 03:00:00,9464.0 -2016-01-02 04:00:00,9289.0 -2016-01-02 05:00:00,9266.0 -2016-01-02 06:00:00,9316.0 -2016-01-02 07:00:00,9608.0 -2016-01-02 08:00:00,10016.0 -2016-01-02 09:00:00,10228.0 -2016-01-02 10:00:00,10361.0 -2016-01-02 11:00:00,10506.0 -2016-01-02 12:00:00,10518.0 -2016-01-02 13:00:00,10530.0 -2016-01-02 14:00:00,10401.0 -2016-01-02 15:00:00,10247.0 -2016-01-02 16:00:00,10110.0 -2016-01-02 17:00:00,10252.0 -2016-01-02 18:00:00,10916.0 -2016-01-02 19:00:00,11846.0 -2016-01-02 20:00:00,11855.0 -2016-01-02 21:00:00,11718.0 -2016-01-02 22:00:00,11580.0 -2016-01-02 23:00:00,11389.0 -2016-01-03 00:00:00,10871.0 -2016-01-01 01:00:00,10407.0 -2016-01-01 02:00:00,10053.0 -2016-01-01 03:00:00,9791.0 -2016-01-01 04:00:00,9617.0 -2016-01-01 05:00:00,9563.0 -2016-01-01 06:00:00,9598.0 -2016-01-01 07:00:00,9766.0 -2016-01-01 08:00:00,9926.0 -2016-01-01 09:00:00,9903.0 -2016-01-01 10:00:00,9948.0 -2016-01-01 11:00:00,10203.0 -2016-01-01 12:00:00,10437.0 -2016-01-01 13:00:00,10557.0 -2016-01-01 14:00:00,10519.0 -2016-01-01 15:00:00,10446.0 -2016-01-01 16:00:00,10375.0 -2016-01-01 17:00:00,10430.0 -2016-01-01 18:00:00,11150.0 -2016-01-01 19:00:00,11849.0 -2016-01-01 20:00:00,11827.0 -2016-01-01 21:00:00,11678.0 -2016-01-01 22:00:00,11487.0 -2016-01-01 23:00:00,11084.0 -2016-01-02 00:00:00,10838.0 -2017-12-31 01:00:00,12132.0 -2017-12-31 02:00:00,11711.0 -2017-12-31 03:00:00,11405.0 -2017-12-31 04:00:00,11235.0 -2017-12-31 05:00:00,11145.0 -2017-12-31 06:00:00,11192.0 -2017-12-31 07:00:00,11288.0 -2017-12-31 08:00:00,11507.0 -2017-12-31 09:00:00,11554.0 -2017-12-31 10:00:00,11624.0 -2017-12-31 11:00:00,11673.0 -2017-12-31 12:00:00,11787.0 -2017-12-31 13:00:00,11883.0 -2017-12-31 14:00:00,11799.0 -2017-12-31 15:00:00,11704.0 -2017-12-31 16:00:00,11661.0 -2017-12-31 17:00:00,11840.0 -2017-12-31 18:00:00,12581.0 -2017-12-31 19:00:00,13452.0 -2017-12-31 20:00:00,13393.0 -2017-12-31 21:00:00,13192.0 -2017-12-31 22:00:00,13116.0 -2017-12-31 23:00:00,12809.0 -2018-01-01 00:00:00,12563.0 -2017-12-30 01:00:00,11644.0 -2017-12-30 02:00:00,11233.0 -2017-12-30 03:00:00,10947.0 -2017-12-30 04:00:00,10828.0 -2017-12-30 05:00:00,10782.0 -2017-12-30 06:00:00,10917.0 -2017-12-30 07:00:00,11169.0 -2017-12-30 08:00:00,11621.0 -2017-12-30 09:00:00,11832.0 -2017-12-30 10:00:00,12099.0 -2017-12-30 11:00:00,12146.0 -2017-12-30 12:00:00,12280.0 -2017-12-30 13:00:00,12261.0 -2017-12-30 14:00:00,12179.0 -2017-12-30 15:00:00,12059.0 -2017-12-30 16:00:00,11974.0 -2017-12-30 17:00:00,12154.0 -2017-12-30 18:00:00,12878.0 -2017-12-30 19:00:00,13673.0 -2017-12-30 20:00:00,13686.0 -2017-12-30 21:00:00,13632.0 -2017-12-30 22:00:00,13423.0 -2017-12-30 23:00:00,13191.0 -2017-12-31 00:00:00,12684.0 -2017-12-29 01:00:00,12009.0 -2017-12-29 02:00:00,11531.0 -2017-12-29 03:00:00,11222.0 -2017-12-29 04:00:00,11005.0 -2017-12-29 05:00:00,10961.0 -2017-12-29 06:00:00,11130.0 -2017-12-29 07:00:00,11552.0 -2017-12-29 08:00:00,12196.0 -2017-12-29 09:00:00,12542.0 -2017-12-29 10:00:00,12746.0 -2017-12-29 11:00:00,12906.0 -2017-12-29 12:00:00,12991.0 -2017-12-29 13:00:00,13023.0 -2017-12-29 14:00:00,12988.0 -2017-12-29 15:00:00,12999.0 -2017-12-29 16:00:00,12907.0 -2017-12-29 17:00:00,12917.0 -2017-12-29 18:00:00,13437.0 -2017-12-29 19:00:00,13862.0 -2017-12-29 20:00:00,13767.0 -2017-12-29 21:00:00,13572.0 -2017-12-29 22:00:00,13294.0 -2017-12-29 23:00:00,12917.0 -2017-12-30 00:00:00,12340.0 -2017-12-28 01:00:00,12475.0 -2017-12-28 02:00:00,12015.0 -2017-12-28 03:00:00,11751.0 -2017-12-28 04:00:00,11595.0 -2017-12-28 05:00:00,11578.0 -2017-12-28 06:00:00,11714.0 -2017-12-28 07:00:00,12108.0 -2017-12-28 08:00:00,12758.0 -2017-12-28 09:00:00,13145.0 -2017-12-28 10:00:00,13379.0 -2017-12-28 11:00:00,13386.0 -2017-12-28 12:00:00,13529.0 -2017-12-28 13:00:00,13608.0 -2017-12-28 14:00:00,13445.0 -2017-12-28 15:00:00,13504.0 -2017-12-28 16:00:00,13432.0 -2017-12-28 17:00:00,13409.0 -2017-12-28 18:00:00,13992.0 -2017-12-28 19:00:00,14435.0 -2017-12-28 20:00:00,14245.0 -2017-12-28 21:00:00,14055.0 -2017-12-28 22:00:00,13782.0 -2017-12-28 23:00:00,13353.0 -2017-12-29 00:00:00,12638.0 -2017-12-27 01:00:00,12171.0 -2017-12-27 02:00:00,11767.0 -2017-12-27 03:00:00,11473.0 -2017-12-27 04:00:00,11415.0 -2017-12-27 05:00:00,11408.0 -2017-12-27 06:00:00,11636.0 -2017-12-27 07:00:00,12163.0 -2017-12-27 08:00:00,12914.0 -2017-12-27 09:00:00,13317.0 -2017-12-27 10:00:00,13496.0 -2017-12-27 11:00:00,13574.0 -2017-12-27 12:00:00,13624.0 -2017-12-27 13:00:00,13540.0 -2017-12-27 14:00:00,13420.0 -2017-12-27 15:00:00,13354.0 -2017-12-27 16:00:00,13261.0 -2017-12-27 17:00:00,13315.0 -2017-12-27 18:00:00,14028.0 -2017-12-27 19:00:00,14694.0 -2017-12-27 20:00:00,14610.0 -2017-12-27 21:00:00,14446.0 -2017-12-27 22:00:00,14214.0 -2017-12-27 23:00:00,13757.0 -2017-12-28 00:00:00,13105.0 -2017-12-26 01:00:00,11091.0 -2017-12-26 02:00:00,10798.0 -2017-12-26 03:00:00,10642.0 -2017-12-26 04:00:00,10577.0 -2017-12-26 05:00:00,10587.0 -2017-12-26 06:00:00,10807.0 -2017-12-26 07:00:00,11283.0 -2017-12-26 08:00:00,11949.0 -2017-12-26 09:00:00,12326.0 -2017-12-26 10:00:00,12602.0 -2017-12-26 11:00:00,12788.0 -2017-12-26 12:00:00,12888.0 -2017-12-26 13:00:00,12869.0 -2017-12-26 14:00:00,12787.0 -2017-12-26 15:00:00,12726.0 -2017-12-26 16:00:00,12653.0 -2017-12-26 17:00:00,12708.0 -2017-12-26 18:00:00,13459.0 -2017-12-26 19:00:00,14251.0 -2017-12-26 20:00:00,14183.0 -2017-12-26 21:00:00,14019.0 -2017-12-26 22:00:00,13813.0 -2017-12-26 23:00:00,13438.0 -2017-12-27 00:00:00,12814.0 -2017-12-25 01:00:00,10596.0 -2017-12-25 02:00:00,10223.0 -2017-12-25 03:00:00,10004.0 -2017-12-25 04:00:00,9801.0 -2017-12-25 05:00:00,9754.0 -2017-12-25 06:00:00,9853.0 -2017-12-25 07:00:00,10060.0 -2017-12-25 08:00:00,10354.0 -2017-12-25 09:00:00,10412.0 -2017-12-25 10:00:00,10512.0 -2017-12-25 11:00:00,10577.0 -2017-12-25 12:00:00,10585.0 -2017-12-25 13:00:00,10601.0 -2017-12-25 14:00:00,10572.0 -2017-12-25 15:00:00,10571.0 -2017-12-25 16:00:00,10629.0 -2017-12-25 17:00:00,10830.0 -2017-12-25 18:00:00,11444.0 -2017-12-25 19:00:00,12013.0 -2017-12-25 20:00:00,11977.0 -2017-12-25 21:00:00,11983.0 -2017-12-25 22:00:00,11960.0 -2017-12-25 23:00:00,11847.0 -2017-12-26 00:00:00,11525.0 -2017-12-24 01:00:00,10490.0 -2017-12-24 02:00:00,9971.0 -2017-12-24 03:00:00,9723.0 -2017-12-24 04:00:00,9501.0 -2017-12-24 05:00:00,9451.0 -2017-12-24 06:00:00,9412.0 -2017-12-24 07:00:00,9561.0 -2017-12-24 08:00:00,9798.0 -2017-12-24 09:00:00,10060.0 -2017-12-24 10:00:00,10358.0 -2017-12-24 11:00:00,10616.0 -2017-12-24 12:00:00,10807.0 -2017-12-24 13:00:00,10834.0 -2017-12-24 14:00:00,10814.0 -2017-12-24 15:00:00,10816.0 -2017-12-24 16:00:00,10809.0 -2017-12-24 17:00:00,10870.0 -2017-12-24 18:00:00,11460.0 -2017-12-24 19:00:00,11871.0 -2017-12-24 20:00:00,11744.0 -2017-12-24 21:00:00,11608.0 -2017-12-24 22:00:00,11479.0 -2017-12-24 23:00:00,11375.0 -2017-12-25 00:00:00,10975.0 -2017-12-23 01:00:00,10222.0 -2017-12-23 02:00:00,9755.0 -2017-12-23 03:00:00,9402.0 -2017-12-23 04:00:00,9193.0 -2017-12-23 05:00:00,9112.0 -2017-12-23 06:00:00,9222.0 -2017-12-23 07:00:00,9515.0 -2017-12-23 08:00:00,9934.0 -2017-12-23 09:00:00,10247.0 -2017-12-23 10:00:00,10369.0 -2017-12-23 11:00:00,10443.0 -2017-12-23 12:00:00,10463.0 -2017-12-23 13:00:00,10452.0 -2017-12-23 14:00:00,10395.0 -2017-12-23 15:00:00,10294.0 -2017-12-23 16:00:00,10190.0 -2017-12-23 17:00:00,10329.0 -2017-12-23 18:00:00,11112.0 -2017-12-23 19:00:00,11785.0 -2017-12-23 20:00:00,11813.0 -2017-12-23 21:00:00,11764.0 -2017-12-23 22:00:00,11675.0 -2017-12-23 23:00:00,11453.0 -2017-12-24 00:00:00,10993.0 -2017-12-22 01:00:00,10307.0 -2017-12-22 02:00:00,9758.0 -2017-12-22 03:00:00,9428.0 -2017-12-22 04:00:00,9224.0 -2017-12-22 05:00:00,9201.0 -2017-12-22 06:00:00,9416.0 -2017-12-22 07:00:00,9980.0 -2017-12-22 08:00:00,10890.0 -2017-12-22 09:00:00,11440.0 -2017-12-22 10:00:00,11536.0 -2017-12-22 11:00:00,11595.0 -2017-12-22 12:00:00,11642.0 -2017-12-22 13:00:00,11630.0 -2017-12-22 14:00:00,11554.0 -2017-12-22 15:00:00,11536.0 -2017-12-22 16:00:00,11504.0 -2017-12-22 17:00:00,11595.0 -2017-12-22 18:00:00,12210.0 -2017-12-22 19:00:00,12504.0 -2017-12-22 20:00:00,12271.0 -2017-12-22 21:00:00,12032.0 -2017-12-22 22:00:00,11779.0 -2017-12-22 23:00:00,11457.0 -2017-12-23 00:00:00,10860.0 -2017-12-21 01:00:00,10574.0 -2017-12-21 02:00:00,10046.0 -2017-12-21 03:00:00,9702.0 -2017-12-21 04:00:00,9554.0 -2017-12-21 05:00:00,9541.0 -2017-12-21 06:00:00,9774.0 -2017-12-21 07:00:00,10472.0 -2017-12-21 08:00:00,11496.0 -2017-12-21 09:00:00,12092.0 -2017-12-21 10:00:00,12161.0 -2017-12-21 11:00:00,12178.0 -2017-12-21 12:00:00,12171.0 -2017-12-21 13:00:00,12076.0 -2017-12-21 14:00:00,12054.0 -2017-12-21 15:00:00,12101.0 -2017-12-21 16:00:00,12081.0 -2017-12-21 17:00:00,12090.0 -2017-12-21 18:00:00,12740.0 -2017-12-21 19:00:00,12990.0 -2017-12-21 20:00:00,12766.0 -2017-12-21 21:00:00,12548.0 -2017-12-21 22:00:00,12306.0 -2017-12-21 23:00:00,11829.0 -2017-12-22 00:00:00,11069.0 -2017-12-20 01:00:00,10256.0 -2017-12-20 02:00:00,9757.0 -2017-12-20 03:00:00,9468.0 -2017-12-20 04:00:00,9338.0 -2017-12-20 05:00:00,9362.0 -2017-12-20 06:00:00,9644.0 -2017-12-20 07:00:00,10387.0 -2017-12-20 08:00:00,11491.0 -2017-12-20 09:00:00,11991.0 -2017-12-20 10:00:00,12028.0 -2017-12-20 11:00:00,12008.0 -2017-12-20 12:00:00,11978.0 -2017-12-20 13:00:00,11882.0 -2017-12-20 14:00:00,11820.0 -2017-12-20 15:00:00,11851.0 -2017-12-20 16:00:00,11884.0 -2017-12-20 17:00:00,11995.0 -2017-12-20 18:00:00,12708.0 -2017-12-20 19:00:00,13266.0 -2017-12-20 20:00:00,13121.0 -2017-12-20 21:00:00,12935.0 -2017-12-20 22:00:00,12641.0 -2017-12-20 23:00:00,12171.0 -2017-12-21 00:00:00,11373.0 -2017-12-19 01:00:00,10256.0 -2017-12-19 02:00:00,9694.0 -2017-12-19 03:00:00,9389.0 -2017-12-19 04:00:00,9229.0 -2017-12-19 05:00:00,9220.0 -2017-12-19 06:00:00,9502.0 -2017-12-19 07:00:00,10205.0 -2017-12-19 08:00:00,11264.0 -2017-12-19 09:00:00,11732.0 -2017-12-19 10:00:00,11784.0 -2017-12-19 11:00:00,11745.0 -2017-12-19 12:00:00,11659.0 -2017-12-19 13:00:00,11506.0 -2017-12-19 14:00:00,11354.0 -2017-12-19 15:00:00,11333.0 -2017-12-19 16:00:00,11230.0 -2017-12-19 17:00:00,11294.0 -2017-12-19 18:00:00,12044.0 -2017-12-19 19:00:00,12606.0 -2017-12-19 20:00:00,12489.0 -2017-12-19 21:00:00,12350.0 -2017-12-19 22:00:00,12140.0 -2017-12-19 23:00:00,11698.0 -2017-12-20 00:00:00,10984.0 -2017-12-18 01:00:00,9560.0 -2017-12-18 02:00:00,9159.0 -2017-12-18 03:00:00,8890.0 -2017-12-18 04:00:00,8822.0 -2017-12-18 05:00:00,8828.0 -2017-12-18 06:00:00,9148.0 -2017-12-18 07:00:00,9887.0 -2017-12-18 08:00:00,10995.0 -2017-12-18 09:00:00,11710.0 -2017-12-18 10:00:00,11852.0 -2017-12-18 11:00:00,11843.0 -2017-12-18 12:00:00,11899.0 -2017-12-18 13:00:00,11851.0 -2017-12-18 14:00:00,11716.0 -2017-12-18 15:00:00,11578.0 -2017-12-18 16:00:00,11474.0 -2017-12-18 17:00:00,11460.0 -2017-12-18 18:00:00,12157.0 -2017-12-18 19:00:00,12798.0 -2017-12-18 20:00:00,12710.0 -2017-12-18 21:00:00,12567.0 -2017-12-18 22:00:00,12308.0 -2017-12-18 23:00:00,11800.0 -2017-12-19 00:00:00,11013.0 -2017-12-17 01:00:00,9964.0 -2017-12-17 02:00:00,9528.0 -2017-12-17 03:00:00,9198.0 -2017-12-17 04:00:00,9002.0 -2017-12-17 05:00:00,8914.0 -2017-12-17 06:00:00,8948.0 -2017-12-17 07:00:00,9094.0 -2017-12-17 08:00:00,9385.0 -2017-12-17 09:00:00,9559.0 -2017-12-17 10:00:00,9815.0 -2017-12-17 11:00:00,10005.0 -2017-12-17 12:00:00,10202.0 -2017-12-17 13:00:00,10148.0 -2017-12-17 14:00:00,10080.0 -2017-12-17 15:00:00,9943.0 -2017-12-17 16:00:00,9960.0 -2017-12-17 17:00:00,10130.0 -2017-12-17 18:00:00,10851.0 -2017-12-17 19:00:00,11362.0 -2017-12-17 20:00:00,11366.0 -2017-12-17 21:00:00,11330.0 -2017-12-17 22:00:00,11126.0 -2017-12-17 23:00:00,10746.0 -2017-12-18 00:00:00,10161.0 -2017-12-16 01:00:00,10781.0 -2017-12-16 02:00:00,10210.0 -2017-12-16 03:00:00,9863.0 -2017-12-16 04:00:00,9562.0 -2017-12-16 05:00:00,9453.0 -2017-12-16 06:00:00,9580.0 -2017-12-16 07:00:00,9867.0 -2017-12-16 08:00:00,10281.0 -2017-12-16 09:00:00,10562.0 -2017-12-16 10:00:00,10764.0 -2017-12-16 11:00:00,10765.0 -2017-12-16 12:00:00,10718.0 -2017-12-16 13:00:00,10507.0 -2017-12-16 14:00:00,10343.0 -2017-12-16 15:00:00,10070.0 -2017-12-16 16:00:00,9937.0 -2017-12-16 17:00:00,9983.0 -2017-12-16 18:00:00,10734.0 -2017-12-16 19:00:00,11433.0 -2017-12-16 20:00:00,11431.0 -2017-12-16 21:00:00,11369.0 -2017-12-16 22:00:00,11192.0 -2017-12-16 23:00:00,10970.0 -2017-12-17 00:00:00,10500.0 -2017-12-15 01:00:00,11220.0 -2017-12-15 02:00:00,10720.0 -2017-12-15 03:00:00,10425.0 -2017-12-15 04:00:00,10253.0 -2017-12-15 05:00:00,10249.0 -2017-12-15 06:00:00,10519.0 -2017-12-15 07:00:00,11208.0 -2017-12-15 08:00:00,12226.0 -2017-12-15 09:00:00,12856.0 -2017-12-15 10:00:00,12944.0 -2017-12-15 11:00:00,12929.0 -2017-12-15 12:00:00,12840.0 -2017-12-15 13:00:00,12598.0 -2017-12-15 14:00:00,12557.0 -2017-12-15 15:00:00,12522.0 -2017-12-15 16:00:00,12515.0 -2017-12-15 17:00:00,12568.0 -2017-12-15 18:00:00,13145.0 -2017-12-15 19:00:00,13379.0 -2017-12-15 20:00:00,13120.0 -2017-12-15 21:00:00,12886.0 -2017-12-15 22:00:00,12569.0 -2017-12-15 23:00:00,12197.0 -2017-12-16 00:00:00,11462.0 -2017-12-14 01:00:00,11014.0 -2017-12-14 02:00:00,10516.0 -2017-12-14 03:00:00,10245.0 -2017-12-14 04:00:00,10166.0 -2017-12-14 05:00:00,10194.0 -2017-12-14 06:00:00,10555.0 -2017-12-14 07:00:00,11281.0 -2017-12-14 08:00:00,12388.0 -2017-12-14 09:00:00,12902.0 -2017-12-14 10:00:00,12906.0 -2017-12-14 11:00:00,12860.0 -2017-12-14 12:00:00,12751.0 -2017-12-14 13:00:00,12571.0 -2017-12-14 14:00:00,12437.0 -2017-12-14 15:00:00,12438.0 -2017-12-14 16:00:00,12371.0 -2017-12-14 17:00:00,12503.0 -2017-12-14 18:00:00,13261.0 -2017-12-14 19:00:00,13746.0 -2017-12-14 20:00:00,13613.0 -2017-12-14 21:00:00,13442.0 -2017-12-14 22:00:00,13205.0 -2017-12-14 23:00:00,12758.0 -2017-12-15 00:00:00,11976.0 -2017-12-13 01:00:00,11335.0 -2017-12-13 02:00:00,10825.0 -2017-12-13 03:00:00,10522.0 -2017-12-13 04:00:00,10385.0 -2017-12-13 05:00:00,10371.0 -2017-12-13 06:00:00,10634.0 -2017-12-13 07:00:00,11335.0 -2017-12-13 08:00:00,12414.0 -2017-12-13 09:00:00,13003.0 -2017-12-13 10:00:00,13033.0 -2017-12-13 11:00:00,13017.0 -2017-12-13 12:00:00,12981.0 -2017-12-13 13:00:00,12851.0 -2017-12-13 14:00:00,12725.0 -2017-12-13 15:00:00,12639.0 -2017-12-13 16:00:00,12634.0 -2017-12-13 17:00:00,12819.0 -2017-12-13 18:00:00,13490.0 -2017-12-13 19:00:00,13803.0 -2017-12-13 20:00:00,13684.0 -2017-12-13 21:00:00,13529.0 -2017-12-13 22:00:00,13219.0 -2017-12-13 23:00:00,12681.0 -2017-12-14 00:00:00,11784.0 -2017-12-12 01:00:00,10817.0 -2017-12-12 02:00:00,10350.0 -2017-12-12 03:00:00,10173.0 -2017-12-12 04:00:00,10085.0 -2017-12-12 05:00:00,10169.0 -2017-12-12 06:00:00,10532.0 -2017-12-12 07:00:00,11338.0 -2017-12-12 08:00:00,12505.0 -2017-12-12 09:00:00,12924.0 -2017-12-12 10:00:00,12899.0 -2017-12-12 11:00:00,12864.0 -2017-12-12 12:00:00,12837.0 -2017-12-12 13:00:00,12724.0 -2017-12-12 14:00:00,12676.0 -2017-12-12 15:00:00,12645.0 -2017-12-12 16:00:00,12553.0 -2017-12-12 17:00:00,12750.0 -2017-12-12 18:00:00,13505.0 -2017-12-12 19:00:00,14111.0 -2017-12-12 20:00:00,13984.0 -2017-12-12 21:00:00,13803.0 -2017-12-12 22:00:00,13510.0 -2017-12-12 23:00:00,12952.0 -2017-12-13 00:00:00,12158.0 -2017-12-11 01:00:00,10198.0 -2017-12-11 02:00:00,9913.0 -2017-12-11 03:00:00,9694.0 -2017-12-11 04:00:00,9612.0 -2017-12-11 05:00:00,9693.0 -2017-12-11 06:00:00,10055.0 -2017-12-11 07:00:00,10824.0 -2017-12-11 08:00:00,12013.0 -2017-12-11 09:00:00,12536.0 -2017-12-11 10:00:00,12660.0 -2017-12-11 11:00:00,12627.0 -2017-12-11 12:00:00,12677.0 -2017-12-11 13:00:00,12664.0 -2017-12-11 14:00:00,12546.0 -2017-12-11 15:00:00,12450.0 -2017-12-11 16:00:00,12430.0 -2017-12-11 17:00:00,12465.0 -2017-12-11 18:00:00,13140.0 -2017-12-11 19:00:00,13568.0 -2017-12-11 20:00:00,13481.0 -2017-12-11 21:00:00,13267.0 -2017-12-11 22:00:00,13018.0 -2017-12-11 23:00:00,12546.0 -2017-12-12 00:00:00,11708.0 -2017-12-10 01:00:00,10717.0 -2017-12-10 02:00:00,10315.0 -2017-12-10 03:00:00,10020.0 -2017-12-10 04:00:00,9844.0 -2017-12-10 05:00:00,9710.0 -2017-12-10 06:00:00,9784.0 -2017-12-10 07:00:00,9935.0 -2017-12-10 08:00:00,10224.0 -2017-12-10 09:00:00,10271.0 -2017-12-10 10:00:00,10454.0 -2017-12-10 11:00:00,10426.0 -2017-12-10 12:00:00,10513.0 -2017-12-10 13:00:00,10462.0 -2017-12-10 14:00:00,10417.0 -2017-12-10 15:00:00,10408.0 -2017-12-10 16:00:00,10580.0 -2017-12-10 17:00:00,10723.0 -2017-12-10 18:00:00,11472.0 -2017-12-10 19:00:00,12017.0 -2017-12-10 20:00:00,12048.0 -2017-12-10 21:00:00,11998.0 -2017-12-10 22:00:00,11779.0 -2017-12-10 23:00:00,11388.0 -2017-12-11 00:00:00,10755.0 -2017-12-09 01:00:00,10743.0 -2017-12-09 02:00:00,10286.0 -2017-12-09 03:00:00,9943.0 -2017-12-09 04:00:00,9818.0 -2017-12-09 05:00:00,9668.0 -2017-12-09 06:00:00,9772.0 -2017-12-09 07:00:00,10058.0 -2017-12-09 08:00:00,10573.0 -2017-12-09 09:00:00,10873.0 -2017-12-09 10:00:00,11247.0 -2017-12-09 11:00:00,11520.0 -2017-12-09 12:00:00,11559.0 -2017-12-09 13:00:00,11575.0 -2017-12-09 14:00:00,11421.0 -2017-12-09 15:00:00,11304.0 -2017-12-09 16:00:00,11276.0 -2017-12-09 17:00:00,11313.0 -2017-12-09 18:00:00,11985.0 -2017-12-09 19:00:00,12422.0 -2017-12-09 20:00:00,12387.0 -2017-12-09 21:00:00,12232.0 -2017-12-09 22:00:00,12086.0 -2017-12-09 23:00:00,11806.0 -2017-12-10 00:00:00,11290.0 -2017-12-08 01:00:00,11183.0 -2017-12-08 02:00:00,10721.0 -2017-12-08 03:00:00,10445.0 -2017-12-08 04:00:00,10318.0 -2017-12-08 05:00:00,10338.0 -2017-12-08 06:00:00,10635.0 -2017-12-08 07:00:00,11294.0 -2017-12-08 08:00:00,12364.0 -2017-12-08 09:00:00,12763.0 -2017-12-08 10:00:00,12737.0 -2017-12-08 11:00:00,12603.0 -2017-12-08 12:00:00,12549.0 -2017-12-08 13:00:00,12369.0 -2017-12-08 14:00:00,12201.0 -2017-12-08 15:00:00,12042.0 -2017-12-08 16:00:00,11897.0 -2017-12-08 17:00:00,11878.0 -2017-12-08 18:00:00,12631.0 -2017-12-08 19:00:00,13191.0 -2017-12-08 20:00:00,13077.0 -2017-12-08 21:00:00,12885.0 -2017-12-08 22:00:00,12642.0 -2017-12-08 23:00:00,12224.0 -2017-12-09 00:00:00,11452.0 -2017-12-07 01:00:00,10738.0 -2017-12-07 02:00:00,10229.0 -2017-12-07 03:00:00,10016.0 -2017-12-07 04:00:00,9906.0 -2017-12-07 05:00:00,9975.0 -2017-12-07 06:00:00,10263.0 -2017-12-07 07:00:00,11025.0 -2017-12-07 08:00:00,12125.0 -2017-12-07 09:00:00,12571.0 -2017-12-07 10:00:00,12700.0 -2017-12-07 11:00:00,12831.0 -2017-12-07 12:00:00,12901.0 -2017-12-07 13:00:00,12734.0 -2017-12-07 14:00:00,12504.0 -2017-12-07 15:00:00,12404.0 -2017-12-07 16:00:00,12315.0 -2017-12-07 17:00:00,12328.0 -2017-12-07 18:00:00,13078.0 -2017-12-07 19:00:00,13645.0 -2017-12-07 20:00:00,13534.0 -2017-12-07 21:00:00,13382.0 -2017-12-07 22:00:00,13157.0 -2017-12-07 23:00:00,12678.0 -2017-12-08 00:00:00,11910.0 -2017-12-06 01:00:00,10651.0 -2017-12-06 02:00:00,10153.0 -2017-12-06 03:00:00,9888.0 -2017-12-06 04:00:00,9732.0 -2017-12-06 05:00:00,9708.0 -2017-12-06 06:00:00,9897.0 -2017-12-06 07:00:00,10629.0 -2017-12-06 08:00:00,11726.0 -2017-12-06 09:00:00,12152.0 -2017-12-06 10:00:00,12150.0 -2017-12-06 11:00:00,12146.0 -2017-12-06 12:00:00,12186.0 -2017-12-06 13:00:00,12131.0 -2017-12-06 14:00:00,12023.0 -2017-12-06 15:00:00,12006.0 -2017-12-06 16:00:00,11901.0 -2017-12-06 17:00:00,11980.0 -2017-12-06 18:00:00,12722.0 -2017-12-06 19:00:00,13306.0 -2017-12-06 20:00:00,13194.0 -2017-12-06 21:00:00,13021.0 -2017-12-06 22:00:00,12786.0 -2017-12-06 23:00:00,12265.0 -2017-12-07 00:00:00,11471.0 -2017-12-05 01:00:00,9390.0 -2017-12-05 02:00:00,8990.0 -2017-12-05 03:00:00,8802.0 -2017-12-05 04:00:00,8765.0 -2017-12-05 05:00:00,8823.0 -2017-12-05 06:00:00,9121.0 -2017-12-05 07:00:00,9935.0 -2017-12-05 08:00:00,11133.0 -2017-12-05 09:00:00,11727.0 -2017-12-05 10:00:00,11890.0 -2017-12-05 11:00:00,11944.0 -2017-12-05 12:00:00,11990.0 -2017-12-05 13:00:00,11983.0 -2017-12-05 14:00:00,11968.0 -2017-12-05 15:00:00,12035.0 -2017-12-05 16:00:00,12000.0 -2017-12-05 17:00:00,12019.0 -2017-12-05 18:00:00,12739.0 -2017-12-05 19:00:00,13296.0 -2017-12-05 20:00:00,13093.0 -2017-12-05 21:00:00,12911.0 -2017-12-05 22:00:00,12649.0 -2017-12-05 23:00:00,12127.0 -2017-12-06 00:00:00,11388.0 -2017-12-04 01:00:00,8993.0 -2017-12-04 02:00:00,8583.0 -2017-12-04 03:00:00,8358.0 -2017-12-04 04:00:00,8286.0 -2017-12-04 05:00:00,8296.0 -2017-12-04 06:00:00,8623.0 -2017-12-04 07:00:00,9336.0 -2017-12-04 08:00:00,10468.0 -2017-12-04 09:00:00,10989.0 -2017-12-04 10:00:00,11092.0 -2017-12-04 11:00:00,11145.0 -2017-12-04 12:00:00,11206.0 -2017-12-04 13:00:00,11158.0 -2017-12-04 14:00:00,11132.0 -2017-12-04 15:00:00,11136.0 -2017-12-04 16:00:00,11111.0 -2017-12-04 17:00:00,11158.0 -2017-12-04 18:00:00,11787.0 -2017-12-04 19:00:00,12084.0 -2017-12-04 20:00:00,11910.0 -2017-12-04 21:00:00,11704.0 -2017-12-04 22:00:00,11394.0 -2017-12-04 23:00:00,10855.0 -2017-12-05 00:00:00,10058.0 -2017-12-03 01:00:00,9285.0 -2017-12-03 02:00:00,8917.0 -2017-12-03 03:00:00,8636.0 -2017-12-03 04:00:00,8530.0 -2017-12-03 05:00:00,8457.0 -2017-12-03 06:00:00,8542.0 -2017-12-03 07:00:00,8728.0 -2017-12-03 08:00:00,9010.0 -2017-12-03 09:00:00,9024.0 -2017-12-03 10:00:00,9168.0 -2017-12-03 11:00:00,9199.0 -2017-12-03 12:00:00,9224.0 -2017-12-03 13:00:00,9211.0 -2017-12-03 14:00:00,9151.0 -2017-12-03 15:00:00,9121.0 -2017-12-03 16:00:00,9054.0 -2017-12-03 17:00:00,9189.0 -2017-12-03 18:00:00,9973.0 -2017-12-03 19:00:00,10603.0 -2017-12-03 20:00:00,10662.0 -2017-12-03 21:00:00,10619.0 -2017-12-03 22:00:00,10441.0 -2017-12-03 23:00:00,10050.0 -2017-12-04 00:00:00,9543.0 -2017-12-02 01:00:00,9816.0 -2017-12-02 02:00:00,9383.0 -2017-12-02 03:00:00,9051.0 -2017-12-02 04:00:00,8891.0 -2017-12-02 05:00:00,8785.0 -2017-12-02 06:00:00,8858.0 -2017-12-02 07:00:00,9132.0 -2017-12-02 08:00:00,9655.0 -2017-12-02 09:00:00,9794.0 -2017-12-02 10:00:00,10021.0 -2017-12-02 11:00:00,9995.0 -2017-12-02 12:00:00,9982.0 -2017-12-02 13:00:00,9886.0 -2017-12-02 14:00:00,9749.0 -2017-12-02 15:00:00,9584.0 -2017-12-02 16:00:00,9436.0 -2017-12-02 17:00:00,9512.0 -2017-12-02 18:00:00,10153.0 -2017-12-02 19:00:00,10718.0 -2017-12-02 20:00:00,10646.0 -2017-12-02 21:00:00,10578.0 -2017-12-02 22:00:00,10440.0 -2017-12-02 23:00:00,10210.0 -2017-12-03 00:00:00,9814.0 -2017-12-01 01:00:00,9791.0 -2017-12-01 02:00:00,9385.0 -2017-12-01 03:00:00,9100.0 -2017-12-01 04:00:00,9005.0 -2017-12-01 05:00:00,9036.0 -2017-12-01 06:00:00,9328.0 -2017-12-01 07:00:00,10042.0 -2017-12-01 08:00:00,11134.0 -2017-12-01 09:00:00,11521.0 -2017-12-01 10:00:00,11524.0 -2017-12-01 11:00:00,11404.0 -2017-12-01 12:00:00,11312.0 -2017-12-01 13:00:00,11195.0 -2017-12-01 14:00:00,11075.0 -2017-12-01 15:00:00,11032.0 -2017-12-01 16:00:00,10908.0 -2017-12-01 17:00:00,10848.0 -2017-12-01 18:00:00,11465.0 -2017-12-01 19:00:00,11953.0 -2017-12-01 20:00:00,11823.0 -2017-12-01 21:00:00,11655.0 -2017-12-01 22:00:00,11457.0 -2017-12-01 23:00:00,11119.0 -2017-12-02 00:00:00,10491.0 -2017-11-30 01:00:00,9701.0 -2017-11-30 02:00:00,9205.0 -2017-11-30 03:00:00,8909.0 -2017-11-30 04:00:00,8799.0 -2017-11-30 05:00:00,8745.0 -2017-11-30 06:00:00,9015.0 -2017-11-30 07:00:00,9767.0 -2017-11-30 08:00:00,10842.0 -2017-11-30 09:00:00,11342.0 -2017-11-30 10:00:00,11368.0 -2017-11-30 11:00:00,11333.0 -2017-11-30 12:00:00,11339.0 -2017-11-30 13:00:00,11243.0 -2017-11-30 14:00:00,11171.0 -2017-11-30 15:00:00,11174.0 -2017-11-30 16:00:00,11054.0 -2017-11-30 17:00:00,11035.0 -2017-11-30 18:00:00,11570.0 -2017-11-30 19:00:00,12100.0 -2017-11-30 20:00:00,11966.0 -2017-11-30 21:00:00,11838.0 -2017-11-30 22:00:00,11629.0 -2017-11-30 23:00:00,11139.0 -2017-12-01 00:00:00,10447.0 -2017-11-29 01:00:00,9541.0 -2017-11-29 02:00:00,9112.0 -2017-11-29 03:00:00,8905.0 -2017-11-29 04:00:00,8805.0 -2017-11-29 05:00:00,8812.0 -2017-11-29 06:00:00,9100.0 -2017-11-29 07:00:00,9912.0 -2017-11-29 08:00:00,10988.0 -2017-11-29 09:00:00,11412.0 -2017-11-29 10:00:00,11424.0 -2017-11-29 11:00:00,11395.0 -2017-11-29 12:00:00,11442.0 -2017-11-29 13:00:00,11332.0 -2017-11-29 14:00:00,11268.0 -2017-11-29 15:00:00,11302.0 -2017-11-29 16:00:00,11164.0 -2017-11-29 17:00:00,11173.0 -2017-11-29 18:00:00,11797.0 -2017-11-29 19:00:00,12318.0 -2017-11-29 20:00:00,12171.0 -2017-11-29 21:00:00,11998.0 -2017-11-29 22:00:00,11681.0 -2017-11-29 23:00:00,11148.0 -2017-11-30 00:00:00,10391.0 -2017-11-28 01:00:00,9554.0 -2017-11-28 02:00:00,9113.0 -2017-11-28 03:00:00,8804.0 -2017-11-28 04:00:00,8641.0 -2017-11-28 05:00:00,8621.0 -2017-11-28 06:00:00,8843.0 -2017-11-28 07:00:00,9542.0 -2017-11-28 08:00:00,10617.0 -2017-11-28 09:00:00,11035.0 -2017-11-28 10:00:00,11271.0 -2017-11-28 11:00:00,11353.0 -2017-11-28 12:00:00,11337.0 -2017-11-28 13:00:00,11314.0 -2017-11-28 14:00:00,11275.0 -2017-11-28 15:00:00,11321.0 -2017-11-28 16:00:00,11253.0 -2017-11-28 17:00:00,11350.0 -2017-11-28 18:00:00,11887.0 -2017-11-28 19:00:00,12054.0 -2017-11-28 20:00:00,11847.0 -2017-11-28 21:00:00,11642.0 -2017-11-28 22:00:00,11418.0 -2017-11-28 23:00:00,10888.0 -2017-11-29 00:00:00,10184.0 -2017-11-27 01:00:00,8900.0 -2017-11-27 02:00:00,8666.0 -2017-11-27 03:00:00,8535.0 -2017-11-27 04:00:00,8470.0 -2017-11-27 05:00:00,8574.0 -2017-11-27 06:00:00,8922.0 -2017-11-27 07:00:00,9741.0 -2017-11-27 08:00:00,10884.0 -2017-11-27 09:00:00,11338.0 -2017-11-27 10:00:00,11359.0 -2017-11-27 11:00:00,11255.0 -2017-11-27 12:00:00,11258.0 -2017-11-27 13:00:00,11167.0 -2017-11-27 14:00:00,11076.0 -2017-11-27 15:00:00,11100.0 -2017-11-27 16:00:00,11069.0 -2017-11-27 17:00:00,11067.0 -2017-11-27 18:00:00,11661.0 -2017-11-27 19:00:00,12097.0 -2017-11-27 20:00:00,11943.0 -2017-11-27 21:00:00,11757.0 -2017-11-27 22:00:00,11497.0 -2017-11-27 23:00:00,10962.0 -2017-11-28 00:00:00,10247.0 -2017-11-26 01:00:00,9130.0 -2017-11-26 02:00:00,8832.0 -2017-11-26 03:00:00,8631.0 -2017-11-26 04:00:00,8510.0 -2017-11-26 05:00:00,8478.0 -2017-11-26 06:00:00,8576.0 -2017-11-26 07:00:00,8738.0 -2017-11-26 08:00:00,9045.0 -2017-11-26 09:00:00,9070.0 -2017-11-26 10:00:00,9156.0 -2017-11-26 11:00:00,9278.0 -2017-11-26 12:00:00,9318.0 -2017-11-26 13:00:00,9329.0 -2017-11-26 14:00:00,9269.0 -2017-11-26 15:00:00,9154.0 -2017-11-26 16:00:00,9089.0 -2017-11-26 17:00:00,9229.0 -2017-11-26 18:00:00,9900.0 -2017-11-26 19:00:00,10605.0 -2017-11-26 20:00:00,10596.0 -2017-11-26 21:00:00,10458.0 -2017-11-26 22:00:00,10265.0 -2017-11-26 23:00:00,9891.0 -2017-11-27 00:00:00,9378.0 -2017-11-25 01:00:00,8733.0 -2017-11-25 02:00:00,8310.0 -2017-11-25 03:00:00,8076.0 -2017-11-25 04:00:00,7940.0 -2017-11-25 05:00:00,7910.0 -2017-11-25 06:00:00,8045.0 -2017-11-25 07:00:00,8327.0 -2017-11-25 08:00:00,8737.0 -2017-11-25 09:00:00,8913.0 -2017-11-25 10:00:00,9185.0 -2017-11-25 11:00:00,9370.0 -2017-11-25 12:00:00,9453.0 -2017-11-25 13:00:00,9444.0 -2017-11-25 14:00:00,9387.0 -2017-11-25 15:00:00,9226.0 -2017-11-25 16:00:00,9119.0 -2017-11-25 17:00:00,9211.0 -2017-11-25 18:00:00,9765.0 -2017-11-25 19:00:00,10334.0 -2017-11-25 20:00:00,10377.0 -2017-11-25 21:00:00,10279.0 -2017-11-25 22:00:00,10190.0 -2017-11-25 23:00:00,9960.0 -2017-11-26 00:00:00,9594.0 -2017-11-24 01:00:00,8860.0 -2017-11-24 02:00:00,8644.0 -2017-11-24 03:00:00,8477.0 -2017-11-24 04:00:00,8397.0 -2017-11-24 05:00:00,8405.0 -2017-11-24 06:00:00,8567.0 -2017-11-24 07:00:00,8975.0 -2017-11-24 08:00:00,9399.0 -2017-11-24 09:00:00,9446.0 -2017-11-24 10:00:00,9538.0 -2017-11-24 11:00:00,9564.0 -2017-11-24 12:00:00,9549.0 -2017-11-24 13:00:00,9532.0 -2017-11-24 14:00:00,9487.0 -2017-11-24 15:00:00,9419.0 -2017-11-24 16:00:00,9369.0 -2017-11-24 17:00:00,9386.0 -2017-11-24 18:00:00,9932.0 -2017-11-24 19:00:00,10420.0 -2017-11-24 20:00:00,10294.0 -2017-11-24 21:00:00,10169.0 -2017-11-24 22:00:00,9938.0 -2017-11-24 23:00:00,9656.0 -2017-11-25 00:00:00,9162.0 -2017-11-23 01:00:00,9998.0 -2017-11-23 02:00:00,9513.0 -2017-11-23 03:00:00,9187.0 -2017-11-23 04:00:00,8968.0 -2017-11-23 05:00:00,8891.0 -2017-11-23 06:00:00,8936.0 -2017-11-23 07:00:00,9081.0 -2017-11-23 08:00:00,9274.0 -2017-11-23 09:00:00,9240.0 -2017-11-23 10:00:00,9344.0 -2017-11-23 11:00:00,9388.0 -2017-11-23 12:00:00,9467.0 -2017-11-23 13:00:00,9470.0 -2017-11-23 14:00:00,9357.0 -2017-11-23 15:00:00,9151.0 -2017-11-23 16:00:00,8953.0 -2017-11-23 17:00:00,8901.0 -2017-11-23 18:00:00,9261.0 -2017-11-23 19:00:00,9609.0 -2017-11-23 20:00:00,9549.0 -2017-11-23 21:00:00,9504.0 -2017-11-23 22:00:00,9483.0 -2017-11-23 23:00:00,9378.0 -2017-11-24 00:00:00,9140.0 -2017-11-22 01:00:00,10412.0 -2017-11-22 02:00:00,9991.0 -2017-11-22 03:00:00,9785.0 -2017-11-22 04:00:00,9667.0 -2017-11-22 05:00:00,9670.0 -2017-11-22 06:00:00,9959.0 -2017-11-22 07:00:00,10639.0 -2017-11-22 08:00:00,11494.0 -2017-11-22 09:00:00,11814.0 -2017-11-22 10:00:00,11923.0 -2017-11-22 11:00:00,11976.0 -2017-11-22 12:00:00,11945.0 -2017-11-22 13:00:00,11840.0 -2017-11-22 14:00:00,11721.0 -2017-11-22 15:00:00,11611.0 -2017-11-22 16:00:00,11463.0 -2017-11-22 17:00:00,11536.0 -2017-11-22 18:00:00,12096.0 -2017-11-22 19:00:00,12445.0 -2017-11-22 20:00:00,12207.0 -2017-11-22 21:00:00,11968.0 -2017-11-22 22:00:00,11706.0 -2017-11-22 23:00:00,11290.0 -2017-11-23 00:00:00,10612.0 -2017-11-21 01:00:00,9888.0 -2017-11-21 02:00:00,9447.0 -2017-11-21 03:00:00,9189.0 -2017-11-21 04:00:00,9028.0 -2017-11-21 05:00:00,9025.0 -2017-11-21 06:00:00,9279.0 -2017-11-21 07:00:00,10012.0 -2017-11-21 08:00:00,10988.0 -2017-11-21 09:00:00,11405.0 -2017-11-21 10:00:00,11717.0 -2017-11-21 11:00:00,11850.0 -2017-11-21 12:00:00,11990.0 -2017-11-21 13:00:00,11948.0 -2017-11-21 14:00:00,11974.0 -2017-11-21 15:00:00,12018.0 -2017-11-21 16:00:00,11914.0 -2017-11-21 17:00:00,11812.0 -2017-11-21 18:00:00,12299.0 -2017-11-21 19:00:00,12643.0 -2017-11-21 20:00:00,12417.0 -2017-11-21 21:00:00,12254.0 -2017-11-21 22:00:00,12029.0 -2017-11-21 23:00:00,11565.0 -2017-11-22 00:00:00,11008.0 -2017-11-20 01:00:00,9858.0 -2017-11-20 02:00:00,9527.0 -2017-11-20 03:00:00,9405.0 -2017-11-20 04:00:00,9328.0 -2017-11-20 05:00:00,9382.0 -2017-11-20 06:00:00,9699.0 -2017-11-20 07:00:00,10414.0 -2017-11-20 08:00:00,11433.0 -2017-11-20 09:00:00,11797.0 -2017-11-20 10:00:00,11797.0 -2017-11-20 11:00:00,11776.0 -2017-11-20 12:00:00,11731.0 -2017-11-20 13:00:00,11616.0 -2017-11-20 14:00:00,11489.0 -2017-11-20 15:00:00,11418.0 -2017-11-20 16:00:00,11301.0 -2017-11-20 17:00:00,11257.0 -2017-11-20 18:00:00,11778.0 -2017-11-20 19:00:00,12240.0 -2017-11-20 20:00:00,12080.0 -2017-11-20 21:00:00,11891.0 -2017-11-20 22:00:00,11644.0 -2017-11-20 23:00:00,11173.0 -2017-11-21 00:00:00,10509.0 -2017-11-19 01:00:00,9595.0 -2017-11-19 02:00:00,9237.0 -2017-11-19 03:00:00,8985.0 -2017-11-19 04:00:00,8885.0 -2017-11-19 05:00:00,8852.0 -2017-11-19 06:00:00,8922.0 -2017-11-19 07:00:00,9110.0 -2017-11-19 08:00:00,9407.0 -2017-11-19 09:00:00,9463.0 -2017-11-19 10:00:00,9685.0 -2017-11-19 11:00:00,9800.0 -2017-11-19 12:00:00,9911.0 -2017-11-19 13:00:00,9904.0 -2017-11-19 14:00:00,9902.0 -2017-11-19 15:00:00,9847.0 -2017-11-19 16:00:00,9845.0 -2017-11-19 17:00:00,9969.0 -2017-11-19 18:00:00,10614.0 -2017-11-19 19:00:00,11191.0 -2017-11-19 20:00:00,11286.0 -2017-11-19 21:00:00,11262.0 -2017-11-19 22:00:00,11050.0 -2017-11-19 23:00:00,10783.0 -2017-11-20 00:00:00,10297.0 -2017-11-18 01:00:00,9645.0 -2017-11-18 02:00:00,9171.0 -2017-11-18 03:00:00,8829.0 -2017-11-18 04:00:00,8635.0 -2017-11-18 05:00:00,8553.0 -2017-11-18 06:00:00,8639.0 -2017-11-18 07:00:00,8990.0 -2017-11-18 08:00:00,9534.0 -2017-11-18 09:00:00,10027.0 -2017-11-18 10:00:00,10522.0 -2017-11-18 11:00:00,10928.0 -2017-11-18 12:00:00,11149.0 -2017-11-18 13:00:00,11147.0 -2017-11-18 14:00:00,11108.0 -2017-11-18 15:00:00,10995.0 -2017-11-18 16:00:00,10899.0 -2017-11-18 17:00:00,10909.0 -2017-11-18 18:00:00,11198.0 -2017-11-18 19:00:00,11394.0 -2017-11-18 20:00:00,11243.0 -2017-11-18 21:00:00,11080.0 -2017-11-18 22:00:00,10825.0 -2017-11-18 23:00:00,10563.0 -2017-11-19 00:00:00,10070.0 -2017-11-17 01:00:00,9993.0 -2017-11-17 02:00:00,9584.0 -2017-11-17 03:00:00,9307.0 -2017-11-17 04:00:00,9223.0 -2017-11-17 05:00:00,9244.0 -2017-11-17 06:00:00,9527.0 -2017-11-17 07:00:00,10256.0 -2017-11-17 08:00:00,11295.0 -2017-11-17 09:00:00,11681.0 -2017-11-17 10:00:00,11867.0 -2017-11-17 11:00:00,11930.0 -2017-11-17 12:00:00,11977.0 -2017-11-17 13:00:00,11897.0 -2017-11-17 14:00:00,11787.0 -2017-11-17 15:00:00,11821.0 -2017-11-17 16:00:00,11834.0 -2017-11-17 17:00:00,11828.0 -2017-11-17 18:00:00,12186.0 -2017-11-17 19:00:00,12199.0 -2017-11-17 20:00:00,11973.0 -2017-11-17 21:00:00,11714.0 -2017-11-17 22:00:00,11432.0 -2017-11-17 23:00:00,10925.0 -2017-11-18 00:00:00,10310.0 -2017-11-16 01:00:00,9993.0 -2017-11-16 02:00:00,9588.0 -2017-11-16 03:00:00,9325.0 -2017-11-16 04:00:00,9207.0 -2017-11-16 05:00:00,9190.0 -2017-11-16 06:00:00,9483.0 -2017-11-16 07:00:00,10234.0 -2017-11-16 08:00:00,11268.0 -2017-11-16 09:00:00,11741.0 -2017-11-16 10:00:00,11974.0 -2017-11-16 11:00:00,12008.0 -2017-11-16 12:00:00,12071.0 -2017-11-16 13:00:00,11980.0 -2017-11-16 14:00:00,11919.0 -2017-11-16 15:00:00,11936.0 -2017-11-16 16:00:00,11896.0 -2017-11-16 17:00:00,11976.0 -2017-11-16 18:00:00,12439.0 -2017-11-16 19:00:00,12634.0 -2017-11-16 20:00:00,12389.0 -2017-11-16 21:00:00,12133.0 -2017-11-16 22:00:00,11808.0 -2017-11-16 23:00:00,11360.0 -2017-11-17 00:00:00,10643.0 -2017-11-15 01:00:00,9634.0 -2017-11-15 02:00:00,9219.0 -2017-11-15 03:00:00,8943.0 -2017-11-15 04:00:00,8796.0 -2017-11-15 05:00:00,8813.0 -2017-11-15 06:00:00,9083.0 -2017-11-15 07:00:00,9816.0 -2017-11-15 08:00:00,10826.0 -2017-11-15 09:00:00,11499.0 -2017-11-15 10:00:00,11803.0 -2017-11-15 11:00:00,11858.0 -2017-11-15 12:00:00,11939.0 -2017-11-15 13:00:00,11890.0 -2017-11-15 14:00:00,11802.0 -2017-11-15 15:00:00,11741.0 -2017-11-15 16:00:00,11563.0 -2017-11-15 17:00:00,11429.0 -2017-11-15 18:00:00,11740.0 -2017-11-15 19:00:00,12213.0 -2017-11-15 20:00:00,12078.0 -2017-11-15 21:00:00,11967.0 -2017-11-15 22:00:00,11754.0 -2017-11-15 23:00:00,11329.0 -2017-11-16 00:00:00,10644.0 -2017-11-14 01:00:00,10043.0 -2017-11-14 02:00:00,9701.0 -2017-11-14 03:00:00,9459.0 -2017-11-14 04:00:00,9356.0 -2017-11-14 05:00:00,9344.0 -2017-11-14 06:00:00,9638.0 -2017-11-14 07:00:00,10409.0 -2017-11-14 08:00:00,11428.0 -2017-11-14 09:00:00,11718.0 -2017-11-14 10:00:00,11796.0 -2017-11-14 11:00:00,11782.0 -2017-11-14 12:00:00,11749.0 -2017-11-14 13:00:00,11719.0 -2017-11-14 14:00:00,11701.0 -2017-11-14 15:00:00,11725.0 -2017-11-14 16:00:00,11643.0 -2017-11-14 17:00:00,11668.0 -2017-11-14 18:00:00,12031.0 -2017-11-14 19:00:00,12285.0 -2017-11-14 20:00:00,12069.0 -2017-11-14 21:00:00,11824.0 -2017-11-14 22:00:00,11498.0 -2017-11-14 23:00:00,10983.0 -2017-11-15 00:00:00,10260.0 -2017-11-13 01:00:00,9221.0 -2017-11-13 02:00:00,8975.0 -2017-11-13 03:00:00,8870.0 -2017-11-13 04:00:00,8819.0 -2017-11-13 05:00:00,8991.0 -2017-11-13 06:00:00,9319.0 -2017-11-13 07:00:00,10149.0 -2017-11-13 08:00:00,11229.0 -2017-11-13 09:00:00,11705.0 -2017-11-13 10:00:00,11723.0 -2017-11-13 11:00:00,11745.0 -2017-11-13 12:00:00,11768.0 -2017-11-13 13:00:00,11747.0 -2017-11-13 14:00:00,11776.0 -2017-11-13 15:00:00,11822.0 -2017-11-13 16:00:00,11758.0 -2017-11-13 17:00:00,11764.0 -2017-11-13 18:00:00,12159.0 -2017-11-13 19:00:00,12516.0 -2017-11-13 20:00:00,12307.0 -2017-11-13 21:00:00,12103.0 -2017-11-13 22:00:00,11835.0 -2017-11-13 23:00:00,11331.0 -2017-11-14 00:00:00,10647.0 -2017-11-12 01:00:00,9524.0 -2017-11-12 02:00:00,9150.0 -2017-11-12 03:00:00,8934.0 -2017-11-12 04:00:00,8740.0 -2017-11-12 05:00:00,8725.0 -2017-11-12 06:00:00,8736.0 -2017-11-12 07:00:00,8881.0 -2017-11-12 08:00:00,9118.0 -2017-11-12 09:00:00,9196.0 -2017-11-12 10:00:00,9491.0 -2017-11-12 11:00:00,9795.0 -2017-11-12 12:00:00,9954.0 -2017-11-12 13:00:00,10061.0 -2017-11-12 14:00:00,10140.0 -2017-11-12 15:00:00,10097.0 -2017-11-12 16:00:00,10042.0 -2017-11-12 17:00:00,10060.0 -2017-11-12 18:00:00,10551.0 -2017-11-12 19:00:00,10888.0 -2017-11-12 20:00:00,10843.0 -2017-11-12 21:00:00,10729.0 -2017-11-12 22:00:00,10544.0 -2017-11-12 23:00:00,10162.0 -2017-11-13 00:00:00,9654.0 -2017-11-11 01:00:00,10605.0 -2017-11-11 02:00:00,10192.0 -2017-11-11 03:00:00,9942.0 -2017-11-11 04:00:00,9746.0 -2017-11-11 05:00:00,9668.0 -2017-11-11 06:00:00,9755.0 -2017-11-11 07:00:00,10075.0 -2017-11-11 08:00:00,10466.0 -2017-11-11 09:00:00,10763.0 -2017-11-11 10:00:00,11119.0 -2017-11-11 11:00:00,11362.0 -2017-11-11 12:00:00,11340.0 -2017-11-11 13:00:00,11217.0 -2017-11-11 14:00:00,11080.0 -2017-11-11 15:00:00,10916.0 -2017-11-11 16:00:00,10706.0 -2017-11-11 17:00:00,10753.0 -2017-11-11 18:00:00,11116.0 -2017-11-11 19:00:00,11415.0 -2017-11-11 20:00:00,11257.0 -2017-11-11 21:00:00,11063.0 -2017-11-11 22:00:00,10797.0 -2017-11-11 23:00:00,10495.0 -2017-11-12 00:00:00,9971.0 -2017-11-10 01:00:00,10424.0 -2017-11-10 02:00:00,10088.0 -2017-11-10 03:00:00,9878.0 -2017-11-10 04:00:00,9763.0 -2017-11-10 05:00:00,9811.0 -2017-11-10 06:00:00,10122.0 -2017-11-10 07:00:00,10806.0 -2017-11-10 08:00:00,11747.0 -2017-11-10 09:00:00,12214.0 -2017-11-10 10:00:00,12493.0 -2017-11-10 11:00:00,12524.0 -2017-11-10 12:00:00,12561.0 -2017-11-10 13:00:00,12526.0 -2017-11-10 14:00:00,12450.0 -2017-11-10 15:00:00,12428.0 -2017-11-10 16:00:00,12281.0 -2017-11-10 17:00:00,12178.0 -2017-11-10 18:00:00,12539.0 -2017-11-10 19:00:00,12856.0 -2017-11-10 20:00:00,12653.0 -2017-11-10 21:00:00,12486.0 -2017-11-10 22:00:00,12185.0 -2017-11-10 23:00:00,11786.0 -2017-11-11 00:00:00,11174.0 -2017-11-09 01:00:00,9814.0 -2017-11-09 02:00:00,9396.0 -2017-11-09 03:00:00,9207.0 -2017-11-09 04:00:00,9086.0 -2017-11-09 05:00:00,9092.0 -2017-11-09 06:00:00,9363.0 -2017-11-09 07:00:00,10034.0 -2017-11-09 08:00:00,11041.0 -2017-11-09 09:00:00,11506.0 -2017-11-09 10:00:00,11649.0 -2017-11-09 11:00:00,11583.0 -2017-11-09 12:00:00,11593.0 -2017-11-09 13:00:00,11580.0 -2017-11-09 14:00:00,11475.0 -2017-11-09 15:00:00,11437.0 -2017-11-09 16:00:00,11377.0 -2017-11-09 17:00:00,11395.0 -2017-11-09 18:00:00,11795.0 -2017-11-09 19:00:00,12411.0 -2017-11-09 20:00:00,12333.0 -2017-11-09 21:00:00,12227.0 -2017-11-09 22:00:00,12039.0 -2017-11-09 23:00:00,11591.0 -2017-11-10 00:00:00,10980.0 -2017-11-08 01:00:00,9751.0 -2017-11-08 02:00:00,9377.0 -2017-11-08 03:00:00,9184.0 -2017-11-08 04:00:00,9074.0 -2017-11-08 05:00:00,9097.0 -2017-11-08 06:00:00,9419.0 -2017-11-08 07:00:00,10161.0 -2017-11-08 08:00:00,11116.0 -2017-11-08 09:00:00,11448.0 -2017-11-08 10:00:00,11462.0 -2017-11-08 11:00:00,11426.0 -2017-11-08 12:00:00,11447.0 -2017-11-08 13:00:00,11344.0 -2017-11-08 14:00:00,11252.0 -2017-11-08 15:00:00,11204.0 -2017-11-08 16:00:00,11073.0 -2017-11-08 17:00:00,11034.0 -2017-11-08 18:00:00,11432.0 -2017-11-08 19:00:00,12035.0 -2017-11-08 20:00:00,11923.0 -2017-11-08 21:00:00,11777.0 -2017-11-08 22:00:00,11540.0 -2017-11-08 23:00:00,11068.0 -2017-11-09 00:00:00,10398.0 -2017-11-07 01:00:00,9454.0 -2017-11-07 02:00:00,9079.0 -2017-11-07 03:00:00,8835.0 -2017-11-07 04:00:00,8766.0 -2017-11-07 05:00:00,8781.0 -2017-11-07 06:00:00,9080.0 -2017-11-07 07:00:00,9844.0 -2017-11-07 08:00:00,10859.0 -2017-11-07 09:00:00,11242.0 -2017-11-07 10:00:00,11330.0 -2017-11-07 11:00:00,11378.0 -2017-11-07 12:00:00,11504.0 -2017-11-07 13:00:00,11454.0 -2017-11-07 14:00:00,11416.0 -2017-11-07 15:00:00,11431.0 -2017-11-07 16:00:00,11358.0 -2017-11-07 17:00:00,11380.0 -2017-11-07 18:00:00,11676.0 -2017-11-07 19:00:00,12137.0 -2017-11-07 20:00:00,11984.0 -2017-11-07 21:00:00,11803.0 -2017-11-07 22:00:00,11519.0 -2017-11-07 23:00:00,11018.0 -2017-11-08 00:00:00,10353.0 -2017-11-06 01:00:00,8530.0 -2017-11-06 02:00:00,8339.0 -2017-11-06 03:00:00,8207.0 -2017-11-06 04:00:00,8242.0 -2017-11-06 05:00:00,8343.0 -2017-11-06 06:00:00,8723.0 -2017-11-06 07:00:00,9647.0 -2017-11-06 08:00:00,10678.0 -2017-11-06 09:00:00,11160.0 -2017-11-06 10:00:00,11286.0 -2017-11-06 11:00:00,11272.0 -2017-11-06 12:00:00,11343.0 -2017-11-06 13:00:00,11363.0 -2017-11-06 14:00:00,11278.0 -2017-11-06 15:00:00,11290.0 -2017-11-06 16:00:00,11170.0 -2017-11-06 17:00:00,11152.0 -2017-11-06 18:00:00,11595.0 -2017-11-06 19:00:00,12124.0 -2017-11-06 20:00:00,11934.0 -2017-11-06 21:00:00,11700.0 -2017-11-06 22:00:00,11341.0 -2017-11-06 23:00:00,10795.0 -2017-11-07 00:00:00,10069.0 -2017-11-05 01:00:00,8576.0 -2017-11-05 02:00:00,8198.0 -2017-11-05 02:00:00,7878.0 -2017-11-05 03:00:00,7889.0 -2017-11-05 04:00:00,7636.0 -2017-11-05 05:00:00,7598.0 -2017-11-05 06:00:00,7642.0 -2017-11-05 07:00:00,7805.0 -2017-11-05 08:00:00,8130.0 -2017-11-05 09:00:00,8300.0 -2017-11-05 10:00:00,8658.0 -2017-11-05 11:00:00,8920.0 -2017-11-05 12:00:00,9067.0 -2017-11-05 13:00:00,9149.0 -2017-11-05 14:00:00,9225.0 -2017-11-05 15:00:00,9213.0 -2017-11-05 16:00:00,9234.0 -2017-11-05 17:00:00,9251.0 -2017-11-05 18:00:00,9740.0 -2017-11-05 19:00:00,10096.0 -2017-11-05 20:00:00,10002.0 -2017-11-05 21:00:00,9854.0 -2017-11-05 22:00:00,9663.0 -2017-11-05 23:00:00,9276.0 -2017-11-06 00:00:00,8818.0 -2017-11-04 01:00:00,9441.0 -2017-11-04 02:00:00,9024.0 -2017-11-04 03:00:00,8699.0 -2017-11-04 04:00:00,8555.0 -2017-11-04 05:00:00,8465.0 -2017-11-04 06:00:00,8529.0 -2017-11-04 07:00:00,8755.0 -2017-11-04 08:00:00,9177.0 -2017-11-04 09:00:00,9624.0 -2017-11-04 10:00:00,9973.0 -2017-11-04 11:00:00,10268.0 -2017-11-04 12:00:00,10370.0 -2017-11-04 13:00:00,10357.0 -2017-11-04 14:00:00,10234.0 -2017-11-04 15:00:00,10054.0 -2017-11-04 16:00:00,9879.0 -2017-11-04 17:00:00,9782.0 -2017-11-04 18:00:00,9784.0 -2017-11-04 19:00:00,10089.0 -2017-11-04 20:00:00,10268.0 -2017-11-04 21:00:00,10128.0 -2017-11-04 22:00:00,9947.0 -2017-11-04 23:00:00,9665.0 -2017-11-05 00:00:00,9242.0 -2017-11-03 01:00:00,9250.0 -2017-11-03 02:00:00,8832.0 -2017-11-03 03:00:00,8589.0 -2017-11-03 04:00:00,8462.0 -2017-11-03 05:00:00,8438.0 -2017-11-03 06:00:00,8698.0 -2017-11-03 07:00:00,9393.0 -2017-11-03 08:00:00,10432.0 -2017-11-03 09:00:00,11122.0 -2017-11-03 10:00:00,11213.0 -2017-11-03 11:00:00,11300.0 -2017-11-03 12:00:00,11342.0 -2017-11-03 13:00:00,11274.0 -2017-11-03 14:00:00,11150.0 -2017-11-03 15:00:00,10982.0 -2017-11-03 16:00:00,10845.0 -2017-11-03 17:00:00,10714.0 -2017-11-03 18:00:00,10681.0 -2017-11-03 19:00:00,10985.0 -2017-11-03 20:00:00,11259.0 -2017-11-03 21:00:00,11177.0 -2017-11-03 22:00:00,10982.0 -2017-11-03 23:00:00,10575.0 -2017-11-04 00:00:00,10043.0 -2017-11-02 01:00:00,9519.0 -2017-11-02 02:00:00,9062.0 -2017-11-02 03:00:00,8704.0 -2017-11-02 04:00:00,8492.0 -2017-11-02 05:00:00,8485.0 -2017-11-02 06:00:00,8655.0 -2017-11-02 07:00:00,9298.0 -2017-11-02 08:00:00,10410.0 -2017-11-02 09:00:00,11133.0 -2017-11-02 10:00:00,11243.0 -2017-11-02 11:00:00,11264.0 -2017-11-02 12:00:00,11280.0 -2017-11-02 13:00:00,11246.0 -2017-11-02 14:00:00,11262.0 -2017-11-02 15:00:00,11291.0 -2017-11-02 16:00:00,11212.0 -2017-11-02 17:00:00,11137.0 -2017-11-02 18:00:00,11182.0 -2017-11-02 19:00:00,11386.0 -2017-11-02 20:00:00,11439.0 -2017-11-02 21:00:00,11256.0 -2017-11-02 22:00:00,10990.0 -2017-11-02 23:00:00,10513.0 -2017-11-03 00:00:00,9858.0 -2017-11-01 01:00:00,9626.0 -2017-11-01 02:00:00,9242.0 -2017-11-01 03:00:00,8982.0 -2017-11-01 04:00:00,8830.0 -2017-11-01 05:00:00,8810.0 -2017-11-01 06:00:00,9086.0 -2017-11-01 07:00:00,9778.0 -2017-11-01 08:00:00,10884.0 -2017-11-01 09:00:00,11578.0 -2017-11-01 10:00:00,11596.0 -2017-11-01 11:00:00,11665.0 -2017-11-01 12:00:00,11653.0 -2017-11-01 13:00:00,11659.0 -2017-11-01 14:00:00,11559.0 -2017-11-01 15:00:00,11554.0 -2017-11-01 16:00:00,11494.0 -2017-11-01 17:00:00,11504.0 -2017-11-01 18:00:00,11644.0 -2017-11-01 19:00:00,11912.0 -2017-11-01 20:00:00,11964.0 -2017-11-01 21:00:00,11791.0 -2017-11-01 22:00:00,11447.0 -2017-11-01 23:00:00,10929.0 -2017-11-02 00:00:00,10166.0 -2017-10-31 01:00:00,9667.0 -2017-10-31 02:00:00,9291.0 -2017-10-31 03:00:00,9031.0 -2017-10-31 04:00:00,8922.0 -2017-10-31 05:00:00,8934.0 -2017-10-31 06:00:00,9158.0 -2017-10-31 07:00:00,9848.0 -2017-10-31 08:00:00,10963.0 -2017-10-31 09:00:00,11653.0 -2017-10-31 10:00:00,11734.0 -2017-10-31 11:00:00,11671.0 -2017-10-31 12:00:00,11655.0 -2017-10-31 13:00:00,11557.0 -2017-10-31 14:00:00,11418.0 -2017-10-31 15:00:00,11345.0 -2017-10-31 16:00:00,11178.0 -2017-10-31 17:00:00,11098.0 -2017-10-31 18:00:00,11124.0 -2017-10-31 19:00:00,11329.0 -2017-10-31 20:00:00,11667.0 -2017-10-31 21:00:00,11550.0 -2017-10-31 22:00:00,11337.0 -2017-10-31 23:00:00,10909.0 -2017-11-01 00:00:00,10258.0 -2017-10-30 01:00:00,8865.0 -2017-10-30 02:00:00,8557.0 -2017-10-30 03:00:00,8372.0 -2017-10-30 04:00:00,8279.0 -2017-10-30 05:00:00,8307.0 -2017-10-30 06:00:00,8571.0 -2017-10-30 07:00:00,9343.0 -2017-10-30 08:00:00,10549.0 -2017-10-30 09:00:00,11271.0 -2017-10-30 10:00:00,11373.0 -2017-10-30 11:00:00,11403.0 -2017-10-30 12:00:00,11384.0 -2017-10-30 13:00:00,11307.0 -2017-10-30 14:00:00,11218.0 -2017-10-30 15:00:00,11256.0 -2017-10-30 16:00:00,11213.0 -2017-10-30 17:00:00,11242.0 -2017-10-30 18:00:00,11350.0 -2017-10-30 19:00:00,11622.0 -2017-10-30 20:00:00,11867.0 -2017-10-30 21:00:00,11740.0 -2017-10-30 22:00:00,11467.0 -2017-10-30 23:00:00,10982.0 -2017-10-31 00:00:00,10287.0 -2017-10-29 01:00:00,9119.0 -2017-10-29 02:00:00,8748.0 -2017-10-29 03:00:00,8520.0 -2017-10-29 04:00:00,8346.0 -2017-10-29 05:00:00,8304.0 -2017-10-29 06:00:00,8296.0 -2017-10-29 07:00:00,8462.0 -2017-10-29 08:00:00,8740.0 -2017-10-29 09:00:00,8881.0 -2017-10-29 10:00:00,8970.0 -2017-10-29 11:00:00,9118.0 -2017-10-29 12:00:00,9152.0 -2017-10-29 13:00:00,9158.0 -2017-10-29 14:00:00,9127.0 -2017-10-29 15:00:00,9036.0 -2017-10-29 16:00:00,8963.0 -2017-10-29 17:00:00,8934.0 -2017-10-29 18:00:00,9049.0 -2017-10-29 19:00:00,9404.0 -2017-10-29 20:00:00,10121.0 -2017-10-29 21:00:00,10150.0 -2017-10-29 22:00:00,10017.0 -2017-10-29 23:00:00,9704.0 -2017-10-30 00:00:00,9287.0 -2017-10-28 01:00:00,9620.0 -2017-10-28 02:00:00,9170.0 -2017-10-28 03:00:00,8897.0 -2017-10-28 04:00:00,8659.0 -2017-10-28 05:00:00,8615.0 -2017-10-28 06:00:00,8657.0 -2017-10-28 07:00:00,8973.0 -2017-10-28 08:00:00,9441.0 -2017-10-28 09:00:00,9783.0 -2017-10-28 10:00:00,9980.0 -2017-10-28 11:00:00,10239.0 -2017-10-28 12:00:00,10258.0 -2017-10-28 13:00:00,10292.0 -2017-10-28 14:00:00,10249.0 -2017-10-28 15:00:00,10093.0 -2017-10-28 16:00:00,9948.0 -2017-10-28 17:00:00,9910.0 -2017-10-28 18:00:00,9964.0 -2017-10-28 19:00:00,10230.0 -2017-10-28 20:00:00,10542.0 -2017-10-28 21:00:00,10444.0 -2017-10-28 22:00:00,10253.0 -2017-10-28 23:00:00,9977.0 -2017-10-29 00:00:00,9556.0 -2017-10-27 01:00:00,9182.0 -2017-10-27 02:00:00,8740.0 -2017-10-27 03:00:00,8492.0 -2017-10-27 04:00:00,8375.0 -2017-10-27 05:00:00,8340.0 -2017-10-27 06:00:00,8583.0 -2017-10-27 07:00:00,9265.0 -2017-10-27 08:00:00,10390.0 -2017-10-27 09:00:00,11117.0 -2017-10-27 10:00:00,11325.0 -2017-10-27 11:00:00,11450.0 -2017-10-27 12:00:00,11608.0 -2017-10-27 13:00:00,11556.0 -2017-10-27 14:00:00,11419.0 -2017-10-27 15:00:00,11429.0 -2017-10-27 16:00:00,11355.0 -2017-10-27 17:00:00,11243.0 -2017-10-27 18:00:00,11259.0 -2017-10-27 19:00:00,11456.0 -2017-10-27 20:00:00,11617.0 -2017-10-27 21:00:00,11389.0 -2017-10-27 22:00:00,11217.0 -2017-10-27 23:00:00,10833.0 -2017-10-28 00:00:00,10238.0 -2017-10-26 01:00:00,9330.0 -2017-10-26 02:00:00,8915.0 -2017-10-26 03:00:00,8691.0 -2017-10-26 04:00:00,8549.0 -2017-10-26 05:00:00,8526.0 -2017-10-26 06:00:00,8735.0 -2017-10-26 07:00:00,9497.0 -2017-10-26 08:00:00,10652.0 -2017-10-26 09:00:00,11127.0 -2017-10-26 10:00:00,11137.0 -2017-10-26 11:00:00,11111.0 -2017-10-26 12:00:00,11094.0 -2017-10-26 13:00:00,11067.0 -2017-10-26 14:00:00,11049.0 -2017-10-26 15:00:00,11058.0 -2017-10-26 16:00:00,10983.0 -2017-10-26 17:00:00,10852.0 -2017-10-26 18:00:00,10781.0 -2017-10-26 19:00:00,10923.0 -2017-10-26 20:00:00,11360.0 -2017-10-26 21:00:00,11252.0 -2017-10-26 22:00:00,10998.0 -2017-10-26 23:00:00,10524.0 -2017-10-27 00:00:00,9829.0 -2017-10-25 01:00:00,9290.0 -2017-10-25 02:00:00,8858.0 -2017-10-25 03:00:00,8602.0 -2017-10-25 04:00:00,8474.0 -2017-10-25 05:00:00,8467.0 -2017-10-25 06:00:00,8683.0 -2017-10-25 07:00:00,9431.0 -2017-10-25 08:00:00,10564.0 -2017-10-25 09:00:00,11173.0 -2017-10-25 10:00:00,11214.0 -2017-10-25 11:00:00,11175.0 -2017-10-25 12:00:00,11091.0 -2017-10-25 13:00:00,11057.0 -2017-10-25 14:00:00,10944.0 -2017-10-25 15:00:00,10906.0 -2017-10-25 16:00:00,10795.0 -2017-10-25 17:00:00,10647.0 -2017-10-25 18:00:00,10537.0 -2017-10-25 19:00:00,10610.0 -2017-10-25 20:00:00,11158.0 -2017-10-25 21:00:00,11172.0 -2017-10-25 22:00:00,10972.0 -2017-10-25 23:00:00,10537.0 -2017-10-26 00:00:00,9907.0 -2017-10-24 01:00:00,8943.0 -2017-10-24 02:00:00,8540.0 -2017-10-24 03:00:00,8299.0 -2017-10-24 04:00:00,8090.0 -2017-10-24 05:00:00,8087.0 -2017-10-24 06:00:00,8287.0 -2017-10-24 07:00:00,8944.0 -2017-10-24 08:00:00,10093.0 -2017-10-24 09:00:00,10907.0 -2017-10-24 10:00:00,11139.0 -2017-10-24 11:00:00,11265.0 -2017-10-24 12:00:00,11401.0 -2017-10-24 13:00:00,11426.0 -2017-10-24 14:00:00,11376.0 -2017-10-24 15:00:00,11406.0 -2017-10-24 16:00:00,11277.0 -2017-10-24 17:00:00,11184.0 -2017-10-24 18:00:00,11221.0 -2017-10-24 19:00:00,11469.0 -2017-10-24 20:00:00,11613.0 -2017-10-24 21:00:00,11461.0 -2017-10-24 22:00:00,11134.0 -2017-10-24 23:00:00,10596.0 -2017-10-25 00:00:00,9911.0 -2017-10-23 01:00:00,8210.0 -2017-10-23 02:00:00,7939.0 -2017-10-23 03:00:00,7727.0 -2017-10-23 04:00:00,7629.0 -2017-10-23 05:00:00,7624.0 -2017-10-23 06:00:00,7910.0 -2017-10-23 07:00:00,8582.0 -2017-10-23 08:00:00,9695.0 -2017-10-23 09:00:00,10554.0 -2017-10-23 10:00:00,10737.0 -2017-10-23 11:00:00,10864.0 -2017-10-23 12:00:00,10999.0 -2017-10-23 13:00:00,11071.0 -2017-10-23 14:00:00,11015.0 -2017-10-23 15:00:00,11010.0 -2017-10-23 16:00:00,10870.0 -2017-10-23 17:00:00,10705.0 -2017-10-23 18:00:00,10705.0 -2017-10-23 19:00:00,10847.0 -2017-10-23 20:00:00,11108.0 -2017-10-23 21:00:00,10965.0 -2017-10-23 22:00:00,10638.0 -2017-10-23 23:00:00,10148.0 -2017-10-24 00:00:00,9532.0 -2017-10-22 01:00:00,8883.0 -2017-10-22 02:00:00,8397.0 -2017-10-22 03:00:00,8091.0 -2017-10-22 04:00:00,7834.0 -2017-10-22 05:00:00,7695.0 -2017-10-22 06:00:00,7686.0 -2017-10-22 07:00:00,7773.0 -2017-10-22 08:00:00,7995.0 -2017-10-22 09:00:00,8041.0 -2017-10-22 10:00:00,8421.0 -2017-10-22 11:00:00,8836.0 -2017-10-22 12:00:00,9146.0 -2017-10-22 13:00:00,9321.0 -2017-10-22 14:00:00,9456.0 -2017-10-22 15:00:00,9451.0 -2017-10-22 16:00:00,9375.0 -2017-10-22 17:00:00,9218.0 -2017-10-22 18:00:00,9178.0 -2017-10-22 19:00:00,9367.0 -2017-10-22 20:00:00,9676.0 -2017-10-22 21:00:00,9640.0 -2017-10-22 22:00:00,9419.0 -2017-10-22 23:00:00,9127.0 -2017-10-23 00:00:00,8674.0 -2017-10-21 01:00:00,9122.0 -2017-10-21 02:00:00,8631.0 -2017-10-21 03:00:00,8238.0 -2017-10-21 04:00:00,8021.0 -2017-10-21 05:00:00,7923.0 -2017-10-21 06:00:00,7941.0 -2017-10-21 07:00:00,8169.0 -2017-10-21 08:00:00,8600.0 -2017-10-21 09:00:00,8878.0 -2017-10-21 10:00:00,9194.0 -2017-10-21 11:00:00,9523.0 -2017-10-21 12:00:00,9696.0 -2017-10-21 13:00:00,9875.0 -2017-10-21 14:00:00,9977.0 -2017-10-21 15:00:00,10066.0 -2017-10-21 16:00:00,10130.0 -2017-10-21 17:00:00,10182.0 -2017-10-21 18:00:00,10117.0 -2017-10-21 19:00:00,10080.0 -2017-10-21 20:00:00,10507.0 -2017-10-21 21:00:00,10468.0 -2017-10-21 22:00:00,10261.0 -2017-10-21 23:00:00,9923.0 -2017-10-22 00:00:00,9420.0 -2017-10-20 01:00:00,8847.0 -2017-10-20 02:00:00,8388.0 -2017-10-20 03:00:00,8122.0 -2017-10-20 04:00:00,7935.0 -2017-10-20 05:00:00,7901.0 -2017-10-20 06:00:00,8095.0 -2017-10-20 07:00:00,8615.0 -2017-10-20 08:00:00,9671.0 -2017-10-20 09:00:00,10241.0 -2017-10-20 10:00:00,10511.0 -2017-10-20 11:00:00,10751.0 -2017-10-20 12:00:00,11067.0 -2017-10-20 13:00:00,11264.0 -2017-10-20 14:00:00,11408.0 -2017-10-20 15:00:00,11616.0 -2017-10-20 16:00:00,11646.0 -2017-10-20 17:00:00,11557.0 -2017-10-20 18:00:00,11401.0 -2017-10-20 19:00:00,11158.0 -2017-10-20 20:00:00,11316.0 -2017-10-20 21:00:00,11150.0 -2017-10-20 22:00:00,10816.0 -2017-10-20 23:00:00,10392.0 -2017-10-21 00:00:00,9763.0 -2017-10-19 01:00:00,8906.0 -2017-10-19 02:00:00,8413.0 -2017-10-19 03:00:00,8108.0 -2017-10-19 04:00:00,7945.0 -2017-10-19 05:00:00,7893.0 -2017-10-19 06:00:00,8064.0 -2017-10-19 07:00:00,8659.0 -2017-10-19 08:00:00,9713.0 -2017-10-19 09:00:00,10242.0 -2017-10-19 10:00:00,10460.0 -2017-10-19 11:00:00,10681.0 -2017-10-19 12:00:00,10929.0 -2017-10-19 13:00:00,11080.0 -2017-10-19 14:00:00,11065.0 -2017-10-19 15:00:00,11118.0 -2017-10-19 16:00:00,11183.0 -2017-10-19 17:00:00,11080.0 -2017-10-19 18:00:00,10901.0 -2017-10-19 19:00:00,10749.0 -2017-10-19 20:00:00,11052.0 -2017-10-19 21:00:00,10960.0 -2017-10-19 22:00:00,10671.0 -2017-10-19 23:00:00,10158.0 -2017-10-20 00:00:00,9476.0 -2017-10-18 01:00:00,8810.0 -2017-10-18 02:00:00,8381.0 -2017-10-18 03:00:00,8234.0 -2017-10-18 04:00:00,8053.0 -2017-10-18 05:00:00,7996.0 -2017-10-18 06:00:00,8197.0 -2017-10-18 07:00:00,8816.0 -2017-10-18 08:00:00,9911.0 -2017-10-18 09:00:00,10454.0 -2017-10-18 10:00:00,10619.0 -2017-10-18 11:00:00,10744.0 -2017-10-18 12:00:00,10925.0 -2017-10-18 13:00:00,11058.0 -2017-10-18 14:00:00,11121.0 -2017-10-18 15:00:00,11184.0 -2017-10-18 16:00:00,11146.0 -2017-10-18 17:00:00,11141.0 -2017-10-18 18:00:00,11041.0 -2017-10-18 19:00:00,10902.0 -2017-10-18 20:00:00,11185.0 -2017-10-18 21:00:00,11126.0 -2017-10-18 22:00:00,10819.0 -2017-10-18 23:00:00,10298.0 -2017-10-19 00:00:00,9562.0 -2017-10-17 01:00:00,8677.0 -2017-10-17 02:00:00,8231.0 -2017-10-17 03:00:00,7985.0 -2017-10-17 04:00:00,7828.0 -2017-10-17 05:00:00,7756.0 -2017-10-17 06:00:00,7993.0 -2017-10-17 07:00:00,8648.0 -2017-10-17 08:00:00,9719.0 -2017-10-17 09:00:00,10230.0 -2017-10-17 10:00:00,10429.0 -2017-10-17 11:00:00,10551.0 -2017-10-17 12:00:00,10763.0 -2017-10-17 13:00:00,10789.0 -2017-10-17 14:00:00,10846.0 -2017-10-17 15:00:00,11033.0 -2017-10-17 16:00:00,11005.0 -2017-10-17 17:00:00,10963.0 -2017-10-17 18:00:00,10887.0 -2017-10-17 19:00:00,10804.0 -2017-10-17 20:00:00,11065.0 -2017-10-17 21:00:00,11071.0 -2017-10-17 22:00:00,10696.0 -2017-10-17 23:00:00,10177.0 -2017-10-18 00:00:00,9458.0 -2017-10-16 01:00:00,8168.0 -2017-10-16 02:00:00,7850.0 -2017-10-16 03:00:00,7658.0 -2017-10-16 04:00:00,7581.0 -2017-10-16 05:00:00,7596.0 -2017-10-16 06:00:00,7809.0 -2017-10-16 07:00:00,8557.0 -2017-10-16 08:00:00,9674.0 -2017-10-16 09:00:00,10229.0 -2017-10-16 10:00:00,10424.0 -2017-10-16 11:00:00,10531.0 -2017-10-16 12:00:00,10623.0 -2017-10-16 13:00:00,10704.0 -2017-10-16 14:00:00,10758.0 -2017-10-16 15:00:00,10824.0 -2017-10-16 16:00:00,10780.0 -2017-10-16 17:00:00,10679.0 -2017-10-16 18:00:00,10576.0 -2017-10-16 19:00:00,10524.0 -2017-10-16 20:00:00,10868.0 -2017-10-16 21:00:00,10960.0 -2017-10-16 22:00:00,10659.0 -2017-10-16 23:00:00,10081.0 -2017-10-17 00:00:00,9384.0 -2017-10-15 01:00:00,9017.0 -2017-10-15 02:00:00,8586.0 -2017-10-15 03:00:00,8235.0 -2017-10-15 04:00:00,8029.0 -2017-10-15 05:00:00,7918.0 -2017-10-15 06:00:00,7909.0 -2017-10-15 07:00:00,7928.0 -2017-10-15 08:00:00,8066.0 -2017-10-15 09:00:00,8129.0 -2017-10-15 10:00:00,8284.0 -2017-10-15 11:00:00,8513.0 -2017-10-15 12:00:00,8710.0 -2017-10-15 13:00:00,8819.0 -2017-10-15 14:00:00,8846.0 -2017-10-15 15:00:00,8831.0 -2017-10-15 16:00:00,8767.0 -2017-10-15 17:00:00,8759.0 -2017-10-15 18:00:00,8788.0 -2017-10-15 19:00:00,8995.0 -2017-10-15 20:00:00,9524.0 -2017-10-15 21:00:00,9600.0 -2017-10-15 22:00:00,9422.0 -2017-10-15 23:00:00,9100.0 -2017-10-16 00:00:00,8631.0 -2017-10-14 01:00:00,9208.0 -2017-10-14 02:00:00,8693.0 -2017-10-14 03:00:00,8339.0 -2017-10-14 04:00:00,8149.0 -2017-10-14 05:00:00,8102.0 -2017-10-14 06:00:00,8102.0 -2017-10-14 07:00:00,8295.0 -2017-10-14 08:00:00,8688.0 -2017-10-14 09:00:00,9127.0 -2017-10-14 10:00:00,9555.0 -2017-10-14 11:00:00,9892.0 -2017-10-14 12:00:00,10044.0 -2017-10-14 13:00:00,10217.0 -2017-10-14 14:00:00,10283.0 -2017-10-14 15:00:00,10105.0 -2017-10-14 16:00:00,9947.0 -2017-10-14 17:00:00,9864.0 -2017-10-14 18:00:00,9957.0 -2017-10-14 19:00:00,10088.0 -2017-10-14 20:00:00,10357.0 -2017-10-14 21:00:00,10395.0 -2017-10-14 22:00:00,10279.0 -2017-10-14 23:00:00,9923.0 -2017-10-15 00:00:00,9474.0 -2017-10-13 01:00:00,9023.0 -2017-10-13 02:00:00,8488.0 -2017-10-13 03:00:00,8189.0 -2017-10-13 04:00:00,8009.0 -2017-10-13 05:00:00,7969.0 -2017-10-13 06:00:00,8102.0 -2017-10-13 07:00:00,8694.0 -2017-10-13 08:00:00,9714.0 -2017-10-13 09:00:00,10237.0 -2017-10-13 10:00:00,10481.0 -2017-10-13 11:00:00,10742.0 -2017-10-13 12:00:00,10900.0 -2017-10-13 13:00:00,11049.0 -2017-10-13 14:00:00,11137.0 -2017-10-13 15:00:00,11297.0 -2017-10-13 16:00:00,11388.0 -2017-10-13 17:00:00,11338.0 -2017-10-13 18:00:00,11223.0 -2017-10-13 19:00:00,11103.0 -2017-10-13 20:00:00,11314.0 -2017-10-13 21:00:00,11193.0 -2017-10-13 22:00:00,10939.0 -2017-10-13 23:00:00,10513.0 -2017-10-14 00:00:00,9897.0 -2017-10-12 01:00:00,8921.0 -2017-10-12 02:00:00,8428.0 -2017-10-12 03:00:00,8128.0 -2017-10-12 04:00:00,7958.0 -2017-10-12 05:00:00,7916.0 -2017-10-12 06:00:00,8080.0 -2017-10-12 07:00:00,8710.0 -2017-10-12 08:00:00,9768.0 -2017-10-12 09:00:00,10412.0 -2017-10-12 10:00:00,10671.0 -2017-10-12 11:00:00,10834.0 -2017-10-12 12:00:00,10976.0 -2017-10-12 13:00:00,10993.0 -2017-10-12 14:00:00,10961.0 -2017-10-12 15:00:00,10996.0 -2017-10-12 16:00:00,10878.0 -2017-10-12 17:00:00,10842.0 -2017-10-12 18:00:00,10762.0 -2017-10-12 19:00:00,10787.0 -2017-10-12 20:00:00,11027.0 -2017-10-12 21:00:00,11057.0 -2017-10-12 22:00:00,10770.0 -2017-10-12 23:00:00,10240.0 -2017-10-13 00:00:00,9600.0 -2017-10-11 01:00:00,9142.0 -2017-10-11 02:00:00,8714.0 -2017-10-11 03:00:00,8497.0 -2017-10-11 04:00:00,8301.0 -2017-10-11 05:00:00,8162.0 -2017-10-11 06:00:00,8350.0 -2017-10-11 07:00:00,8960.0 -2017-10-11 08:00:00,9987.0 -2017-10-11 09:00:00,10596.0 -2017-10-11 10:00:00,10857.0 -2017-10-11 11:00:00,11002.0 -2017-10-11 12:00:00,11105.0 -2017-10-11 13:00:00,11116.0 -2017-10-11 14:00:00,11128.0 -2017-10-11 15:00:00,11153.0 -2017-10-11 16:00:00,11132.0 -2017-10-11 17:00:00,10970.0 -2017-10-11 18:00:00,10975.0 -2017-10-11 19:00:00,11005.0 -2017-10-11 20:00:00,11215.0 -2017-10-11 21:00:00,11154.0 -2017-10-11 22:00:00,10879.0 -2017-10-11 23:00:00,10273.0 -2017-10-12 00:00:00,9593.0 -2017-10-10 01:00:00,9516.0 -2017-10-10 02:00:00,8907.0 -2017-10-10 03:00:00,8563.0 -2017-10-10 04:00:00,8357.0 -2017-10-10 05:00:00,8296.0 -2017-10-10 06:00:00,8400.0 -2017-10-10 07:00:00,9036.0 -2017-10-10 08:00:00,10090.0 -2017-10-10 09:00:00,10632.0 -2017-10-10 10:00:00,10958.0 -2017-10-10 11:00:00,11185.0 -2017-10-10 12:00:00,11355.0 -2017-10-10 13:00:00,11429.0 -2017-10-10 14:00:00,11419.0 -2017-10-10 15:00:00,11415.0 -2017-10-10 16:00:00,11287.0 -2017-10-10 17:00:00,11148.0 -2017-10-10 18:00:00,11102.0 -2017-10-10 19:00:00,11250.0 -2017-10-10 20:00:00,11360.0 -2017-10-10 21:00:00,11301.0 -2017-10-10 22:00:00,11044.0 -2017-10-10 23:00:00,10471.0 -2017-10-11 00:00:00,9788.0 -2017-10-09 01:00:00,8643.0 -2017-10-09 02:00:00,8265.0 -2017-10-09 03:00:00,8020.0 -2017-10-09 04:00:00,7818.0 -2017-10-09 05:00:00,7770.0 -2017-10-09 06:00:00,7984.0 -2017-10-09 07:00:00,8560.0 -2017-10-09 08:00:00,9334.0 -2017-10-09 09:00:00,9966.0 -2017-10-09 10:00:00,10517.0 -2017-10-09 11:00:00,11097.0 -2017-10-09 12:00:00,11525.0 -2017-10-09 13:00:00,11933.0 -2017-10-09 14:00:00,12247.0 -2017-10-09 15:00:00,12646.0 -2017-10-09 16:00:00,12795.0 -2017-10-09 17:00:00,12865.0 -2017-10-09 18:00:00,12788.0 -2017-10-09 19:00:00,12475.0 -2017-10-09 20:00:00,12435.0 -2017-10-09 21:00:00,12330.0 -2017-10-09 22:00:00,11714.0 -2017-10-09 23:00:00,11071.0 -2017-10-10 00:00:00,10257.0 -2017-10-08 01:00:00,8699.0 -2017-10-08 02:00:00,8209.0 -2017-10-08 03:00:00,7901.0 -2017-10-08 04:00:00,7621.0 -2017-10-08 05:00:00,7501.0 -2017-10-08 06:00:00,7421.0 -2017-10-08 07:00:00,7567.0 -2017-10-08 08:00:00,7716.0 -2017-10-08 09:00:00,7778.0 -2017-10-08 10:00:00,8121.0 -2017-10-08 11:00:00,8614.0 -2017-10-08 12:00:00,9013.0 -2017-10-08 13:00:00,9420.0 -2017-10-08 14:00:00,9705.0 -2017-10-08 15:00:00,9919.0 -2017-10-08 16:00:00,10118.0 -2017-10-08 17:00:00,10342.0 -2017-10-08 18:00:00,10417.0 -2017-10-08 19:00:00,10363.0 -2017-10-08 20:00:00,10435.0 -2017-10-08 21:00:00,10529.0 -2017-10-08 22:00:00,10249.0 -2017-10-08 23:00:00,9787.0 -2017-10-09 00:00:00,9252.0 -2017-10-07 01:00:00,9673.0 -2017-10-07 02:00:00,9106.0 -2017-10-07 03:00:00,8768.0 -2017-10-07 04:00:00,8501.0 -2017-10-07 05:00:00,8376.0 -2017-10-07 06:00:00,8322.0 -2017-10-07 07:00:00,8616.0 -2017-10-07 08:00:00,9019.0 -2017-10-07 09:00:00,9310.0 -2017-10-07 10:00:00,9823.0 -2017-10-07 11:00:00,10300.0 -2017-10-07 12:00:00,10736.0 -2017-10-07 13:00:00,11072.0 -2017-10-07 14:00:00,11186.0 -2017-10-07 15:00:00,11061.0 -2017-10-07 16:00:00,11037.0 -2017-10-07 17:00:00,10882.0 -2017-10-07 18:00:00,10440.0 -2017-10-07 19:00:00,10117.0 -2017-10-07 20:00:00,10134.0 -2017-10-07 21:00:00,10266.0 -2017-10-07 22:00:00,10058.0 -2017-10-07 23:00:00,9779.0 -2017-10-08 00:00:00,9226.0 -2017-10-06 01:00:00,9636.0 -2017-10-06 02:00:00,9044.0 -2017-10-06 03:00:00,8715.0 -2017-10-06 04:00:00,8491.0 -2017-10-06 05:00:00,8421.0 -2017-10-06 06:00:00,8561.0 -2017-10-06 07:00:00,9176.0 -2017-10-06 08:00:00,10251.0 -2017-10-06 09:00:00,10900.0 -2017-10-06 10:00:00,11236.0 -2017-10-06 11:00:00,11333.0 -2017-10-06 12:00:00,11691.0 -2017-10-06 13:00:00,11753.0 -2017-10-06 14:00:00,11775.0 -2017-10-06 15:00:00,11817.0 -2017-10-06 16:00:00,11819.0 -2017-10-06 17:00:00,11698.0 -2017-10-06 18:00:00,11673.0 -2017-10-06 19:00:00,11689.0 -2017-10-06 20:00:00,11808.0 -2017-10-06 21:00:00,11701.0 -2017-10-06 22:00:00,11361.0 -2017-10-06 23:00:00,10990.0 -2017-10-07 00:00:00,10307.0 -2017-10-05 01:00:00,9512.0 -2017-10-05 02:00:00,8912.0 -2017-10-05 03:00:00,8568.0 -2017-10-05 04:00:00,8314.0 -2017-10-05 05:00:00,8243.0 -2017-10-05 06:00:00,8382.0 -2017-10-05 07:00:00,8985.0 -2017-10-05 08:00:00,9936.0 -2017-10-05 09:00:00,10387.0 -2017-10-05 10:00:00,10799.0 -2017-10-05 11:00:00,11207.0 -2017-10-05 12:00:00,11569.0 -2017-10-05 13:00:00,11829.0 -2017-10-05 14:00:00,12064.0 -2017-10-05 15:00:00,12324.0 -2017-10-05 16:00:00,12403.0 -2017-10-05 17:00:00,12428.0 -2017-10-05 18:00:00,12312.0 -2017-10-05 19:00:00,11994.0 -2017-10-05 20:00:00,12056.0 -2017-10-05 21:00:00,12137.0 -2017-10-05 22:00:00,11771.0 -2017-10-05 23:00:00,11203.0 -2017-10-06 00:00:00,10392.0 -2017-10-04 01:00:00,10621.0 -2017-10-04 02:00:00,9993.0 -2017-10-04 03:00:00,9631.0 -2017-10-04 04:00:00,9369.0 -2017-10-04 05:00:00,9252.0 -2017-10-04 06:00:00,9500.0 -2017-10-04 07:00:00,10269.0 -2017-10-04 08:00:00,11474.0 -2017-10-04 09:00:00,12198.0 -2017-10-04 10:00:00,12515.0 -2017-10-04 11:00:00,12591.0 -2017-10-04 12:00:00,12628.0 -2017-10-04 13:00:00,12550.0 -2017-10-04 14:00:00,12493.0 -2017-10-04 15:00:00,12592.0 -2017-10-04 16:00:00,12732.0 -2017-10-04 17:00:00,12828.0 -2017-10-04 18:00:00,12678.0 -2017-10-04 19:00:00,12306.0 -2017-10-04 20:00:00,12190.0 -2017-10-04 21:00:00,12186.0 -2017-10-04 22:00:00,11809.0 -2017-10-04 23:00:00,11162.0 -2017-10-05 00:00:00,10304.0 -2017-10-03 01:00:00,9966.0 -2017-10-03 02:00:00,9374.0 -2017-10-03 03:00:00,8985.0 -2017-10-03 04:00:00,8761.0 -2017-10-03 05:00:00,8676.0 -2017-10-03 06:00:00,8844.0 -2017-10-03 07:00:00,9553.0 -2017-10-03 08:00:00,10738.0 -2017-10-03 09:00:00,11287.0 -2017-10-03 10:00:00,11767.0 -2017-10-03 11:00:00,12123.0 -2017-10-03 12:00:00,12531.0 -2017-10-03 13:00:00,12742.0 -2017-10-03 14:00:00,12900.0 -2017-10-03 15:00:00,13087.0 -2017-10-03 16:00:00,13284.0 -2017-10-03 17:00:00,13322.0 -2017-10-03 18:00:00,13172.0 -2017-10-03 19:00:00,13112.0 -2017-10-03 20:00:00,13132.0 -2017-10-03 21:00:00,13357.0 -2017-10-03 22:00:00,13004.0 -2017-10-03 23:00:00,12383.0 -2017-10-04 00:00:00,11453.0 -2017-10-02 01:00:00,8370.0 -2017-10-02 02:00:00,8049.0 -2017-10-02 03:00:00,7798.0 -2017-10-02 04:00:00,7687.0 -2017-10-02 05:00:00,7698.0 -2017-10-02 06:00:00,7955.0 -2017-10-02 07:00:00,8679.0 -2017-10-02 08:00:00,9802.0 -2017-10-02 09:00:00,10412.0 -2017-10-02 10:00:00,10864.0 -2017-10-02 11:00:00,11302.0 -2017-10-02 12:00:00,11677.0 -2017-10-02 13:00:00,11985.0 -2017-10-02 14:00:00,12277.0 -2017-10-02 15:00:00,12657.0 -2017-10-02 16:00:00,12790.0 -2017-10-02 17:00:00,12780.0 -2017-10-02 18:00:00,12651.0 -2017-10-02 19:00:00,12477.0 -2017-10-02 20:00:00,12471.0 -2017-10-02 21:00:00,12660.0 -2017-10-02 22:00:00,12391.0 -2017-10-02 23:00:00,11699.0 -2017-10-03 00:00:00,10804.0 -2017-10-01 01:00:00,8467.0 -2017-10-01 02:00:00,8027.0 -2017-10-01 03:00:00,7765.0 -2017-10-01 04:00:00,7563.0 -2017-10-01 05:00:00,7458.0 -2017-10-01 06:00:00,7409.0 -2017-10-01 07:00:00,7539.0 -2017-10-01 08:00:00,7668.0 -2017-10-01 09:00:00,7755.0 -2017-10-01 10:00:00,8072.0 -2017-10-01 11:00:00,8370.0 -2017-10-01 12:00:00,8646.0 -2017-10-01 13:00:00,8867.0 -2017-10-01 14:00:00,8981.0 -2017-10-01 15:00:00,9071.0 -2017-10-01 16:00:00,9133.0 -2017-10-01 17:00:00,9167.0 -2017-10-01 18:00:00,9237.0 -2017-10-01 19:00:00,9262.0 -2017-10-01 20:00:00,9643.0 -2017-10-01 21:00:00,9901.0 -2017-10-01 22:00:00,9707.0 -2017-10-01 23:00:00,9420.0 -2017-10-02 00:00:00,8948.0 -2017-09-30 01:00:00,8902.0 -2017-09-30 02:00:00,8478.0 -2017-09-30 03:00:00,8176.0 -2017-09-30 04:00:00,7952.0 -2017-09-30 05:00:00,7837.0 -2017-09-30 06:00:00,7855.0 -2017-09-30 07:00:00,8067.0 -2017-09-30 08:00:00,8416.0 -2017-09-30 09:00:00,8578.0 -2017-09-30 10:00:00,9058.0 -2017-09-30 11:00:00,9382.0 -2017-09-30 12:00:00,9655.0 -2017-09-30 13:00:00,9748.0 -2017-09-30 14:00:00,9802.0 -2017-09-30 15:00:00,9767.0 -2017-09-30 16:00:00,9754.0 -2017-09-30 17:00:00,9796.0 -2017-09-30 18:00:00,9774.0 -2017-09-30 19:00:00,9584.0 -2017-09-30 20:00:00,9563.0 -2017-09-30 21:00:00,9881.0 -2017-09-30 22:00:00,9698.0 -2017-09-30 23:00:00,9413.0 -2017-10-01 00:00:00,8974.0 -2017-09-29 01:00:00,9174.0 -2017-09-29 02:00:00,8705.0 -2017-09-29 03:00:00,8382.0 -2017-09-29 04:00:00,8212.0 -2017-09-29 05:00:00,8153.0 -2017-09-29 06:00:00,8327.0 -2017-09-29 07:00:00,8877.0 -2017-09-29 08:00:00,9834.0 -2017-09-29 09:00:00,10290.0 -2017-09-29 10:00:00,10759.0 -2017-09-29 11:00:00,11072.0 -2017-09-29 12:00:00,11358.0 -2017-09-29 13:00:00,11467.0 -2017-09-29 14:00:00,11560.0 -2017-09-29 15:00:00,11651.0 -2017-09-29 16:00:00,11609.0 -2017-09-29 17:00:00,11495.0 -2017-09-29 18:00:00,11288.0 -2017-09-29 19:00:00,10956.0 -2017-09-29 20:00:00,10811.0 -2017-09-29 21:00:00,10958.0 -2017-09-29 22:00:00,10651.0 -2017-09-29 23:00:00,10238.0 -2017-09-30 00:00:00,9536.0 -2017-09-28 01:00:00,9188.0 -2017-09-28 02:00:00,8668.0 -2017-09-28 03:00:00,8326.0 -2017-09-28 04:00:00,8112.0 -2017-09-28 05:00:00,8034.0 -2017-09-28 06:00:00,8193.0 -2017-09-28 07:00:00,8784.0 -2017-09-28 08:00:00,9747.0 -2017-09-28 09:00:00,10225.0 -2017-09-28 10:00:00,10625.0 -2017-09-28 11:00:00,11035.0 -2017-09-28 12:00:00,11357.0 -2017-09-28 13:00:00,11520.0 -2017-09-28 14:00:00,11642.0 -2017-09-28 15:00:00,11857.0 -2017-09-28 16:00:00,11986.0 -2017-09-28 17:00:00,12042.0 -2017-09-28 18:00:00,12026.0 -2017-09-28 19:00:00,11767.0 -2017-09-28 20:00:00,11532.0 -2017-09-28 21:00:00,11661.0 -2017-09-28 22:00:00,11307.0 -2017-09-28 23:00:00,10685.0 -2017-09-29 00:00:00,9901.0 -2017-09-27 01:00:00,12455.0 -2017-09-27 02:00:00,11472.0 -2017-09-27 03:00:00,10760.0 -2017-09-27 04:00:00,10219.0 -2017-09-27 05:00:00,9843.0 -2017-09-27 06:00:00,9790.0 -2017-09-27 07:00:00,10266.0 -2017-09-27 08:00:00,11166.0 -2017-09-27 09:00:00,11512.0 -2017-09-27 10:00:00,11811.0 -2017-09-27 11:00:00,11922.0 -2017-09-27 12:00:00,12155.0 -2017-09-27 13:00:00,12472.0 -2017-09-27 14:00:00,12847.0 -2017-09-27 15:00:00,13075.0 -2017-09-27 16:00:00,13073.0 -2017-09-27 17:00:00,12982.0 -2017-09-27 18:00:00,12660.0 -2017-09-27 19:00:00,12125.0 -2017-09-27 20:00:00,11943.0 -2017-09-27 21:00:00,12051.0 -2017-09-27 22:00:00,11546.0 -2017-09-27 23:00:00,10899.0 -2017-09-28 00:00:00,10038.0 -2017-09-26 01:00:00,12406.0 -2017-09-26 02:00:00,11476.0 -2017-09-26 03:00:00,10843.0 -2017-09-26 04:00:00,10394.0 -2017-09-26 05:00:00,10169.0 -2017-09-26 06:00:00,10241.0 -2017-09-26 07:00:00,10911.0 -2017-09-26 08:00:00,12025.0 -2017-09-26 09:00:00,12651.0 -2017-09-26 10:00:00,13452.0 -2017-09-26 11:00:00,14383.0 -2017-09-26 12:00:00,15564.0 -2017-09-26 13:00:00,16745.0 -2017-09-26 14:00:00,17736.0 -2017-09-26 15:00:00,18358.0 -2017-09-26 16:00:00,18608.0 -2017-09-26 17:00:00,18734.0 -2017-09-26 18:00:00,18647.0 -2017-09-26 19:00:00,18033.0 -2017-09-26 20:00:00,17352.0 -2017-09-26 21:00:00,17071.0 -2017-09-26 22:00:00,16318.0 -2017-09-26 23:00:00,15208.0 -2017-09-27 00:00:00,13786.0 -2017-09-25 01:00:00,11965.0 -2017-09-25 02:00:00,11149.0 -2017-09-25 03:00:00,10541.0 -2017-09-25 04:00:00,10181.0 -2017-09-25 05:00:00,9946.0 -2017-09-25 06:00:00,10077.0 -2017-09-25 07:00:00,10760.0 -2017-09-25 08:00:00,11794.0 -2017-09-25 09:00:00,12437.0 -2017-09-25 10:00:00,13358.0 -2017-09-25 11:00:00,14463.0 -2017-09-25 12:00:00,15649.0 -2017-09-25 13:00:00,16822.0 -2017-09-25 14:00:00,17688.0 -2017-09-25 15:00:00,18373.0 -2017-09-25 16:00:00,18684.0 -2017-09-25 17:00:00,18852.0 -2017-09-25 18:00:00,18735.0 -2017-09-25 19:00:00,18213.0 -2017-09-25 20:00:00,17386.0 -2017-09-25 21:00:00,16994.0 -2017-09-25 22:00:00,16131.0 -2017-09-25 23:00:00,15005.0 -2017-09-26 00:00:00,13706.0 -2017-09-24 01:00:00,12818.0 -2017-09-24 02:00:00,11883.0 -2017-09-24 03:00:00,11175.0 -2017-09-24 04:00:00,10580.0 -2017-09-24 05:00:00,10196.0 -2017-09-24 06:00:00,9980.0 -2017-09-24 07:00:00,9934.0 -2017-09-24 08:00:00,10018.0 -2017-09-24 09:00:00,10194.0 -2017-09-24 10:00:00,11272.0 -2017-09-24 11:00:00,12622.0 -2017-09-24 12:00:00,13971.0 -2017-09-24 13:00:00,15088.0 -2017-09-24 14:00:00,15979.0 -2017-09-24 15:00:00,16559.0 -2017-09-24 16:00:00,16986.0 -2017-09-24 17:00:00,17165.0 -2017-09-24 18:00:00,17149.0 -2017-09-24 19:00:00,16705.0 -2017-09-24 20:00:00,16009.0 -2017-09-24 21:00:00,15757.0 -2017-09-24 22:00:00,15016.0 -2017-09-24 23:00:00,14142.0 -2017-09-25 00:00:00,13025.0 -2017-09-23 01:00:00,13675.0 -2017-09-23 02:00:00,12653.0 -2017-09-23 03:00:00,11882.0 -2017-09-23 04:00:00,11303.0 -2017-09-23 05:00:00,10899.0 -2017-09-23 06:00:00,10730.0 -2017-09-23 07:00:00,10855.0 -2017-09-23 08:00:00,11134.0 -2017-09-23 09:00:00,11567.0 -2017-09-23 10:00:00,12764.0 -2017-09-23 11:00:00,14190.0 -2017-09-23 12:00:00,15593.0 -2017-09-23 13:00:00,16823.0 -2017-09-23 14:00:00,17582.0 -2017-09-23 15:00:00,18056.0 -2017-09-23 16:00:00,18364.0 -2017-09-23 17:00:00,18510.0 -2017-09-23 18:00:00,18373.0 -2017-09-23 19:00:00,17816.0 -2017-09-23 20:00:00,16973.0 -2017-09-23 21:00:00,16530.0 -2017-09-23 22:00:00,15793.0 -2017-09-23 23:00:00,14901.0 -2017-09-24 00:00:00,13868.0 -2017-09-22 01:00:00,13860.0 -2017-09-22 02:00:00,12838.0 -2017-09-22 03:00:00,12104.0 -2017-09-22 04:00:00,11536.0 -2017-09-22 05:00:00,11230.0 -2017-09-22 06:00:00,11227.0 -2017-09-22 07:00:00,11802.0 -2017-09-22 08:00:00,12747.0 -2017-09-22 09:00:00,13516.0 -2017-09-22 10:00:00,14580.0 -2017-09-22 11:00:00,15859.0 -2017-09-22 12:00:00,17237.0 -2017-09-22 13:00:00,18190.0 -2017-09-22 14:00:00,18944.0 -2017-09-22 15:00:00,19533.0 -2017-09-22 16:00:00,19889.0 -2017-09-22 17:00:00,20040.0 -2017-09-22 18:00:00,19979.0 -2017-09-22 19:00:00,19391.0 -2017-09-22 20:00:00,18477.0 -2017-09-22 21:00:00,17950.0 -2017-09-22 22:00:00,17070.0 -2017-09-22 23:00:00,16145.0 -2017-09-23 00:00:00,14893.0 -2017-09-21 01:00:00,13235.0 -2017-09-21 02:00:00,12307.0 -2017-09-21 03:00:00,11632.0 -2017-09-21 04:00:00,11097.0 -2017-09-21 05:00:00,10797.0 -2017-09-21 06:00:00,10877.0 -2017-09-21 07:00:00,11491.0 -2017-09-21 08:00:00,12542.0 -2017-09-21 09:00:00,13289.0 -2017-09-21 10:00:00,14312.0 -2017-09-21 11:00:00,15376.0 -2017-09-21 12:00:00,16513.0 -2017-09-21 13:00:00,17512.0 -2017-09-21 14:00:00,18347.0 -2017-09-21 15:00:00,19003.0 -2017-09-21 16:00:00,19333.0 -2017-09-21 17:00:00,19518.0 -2017-09-21 18:00:00,19443.0 -2017-09-21 19:00:00,18836.0 -2017-09-21 20:00:00,18130.0 -2017-09-21 21:00:00,17932.0 -2017-09-21 22:00:00,17301.0 -2017-09-21 23:00:00,16403.0 -2017-09-22 00:00:00,15091.0 -2017-09-20 01:00:00,10413.0 -2017-09-20 02:00:00,9721.0 -2017-09-20 03:00:00,9284.0 -2017-09-20 04:00:00,9018.0 -2017-09-20 05:00:00,8885.0 -2017-09-20 06:00:00,9043.0 -2017-09-20 07:00:00,9729.0 -2017-09-20 08:00:00,10827.0 -2017-09-20 09:00:00,11505.0 -2017-09-20 10:00:00,12304.0 -2017-09-20 11:00:00,13167.0 -2017-09-20 12:00:00,14141.0 -2017-09-20 13:00:00,15128.0 -2017-09-20 14:00:00,16112.0 -2017-09-20 15:00:00,17084.0 -2017-09-20 16:00:00,17800.0 -2017-09-20 17:00:00,18347.0 -2017-09-20 18:00:00,18585.0 -2017-09-20 19:00:00,18356.0 -2017-09-20 20:00:00,17711.0 -2017-09-20 21:00:00,17494.0 -2017-09-20 22:00:00,16839.0 -2017-09-20 23:00:00,15803.0 -2017-09-21 00:00:00,14456.0 -2017-09-19 01:00:00,10127.0 -2017-09-19 02:00:00,9614.0 -2017-09-19 03:00:00,9231.0 -2017-09-19 04:00:00,8999.0 -2017-09-19 05:00:00,8855.0 -2017-09-19 06:00:00,9039.0 -2017-09-19 07:00:00,9730.0 -2017-09-19 08:00:00,10919.0 -2017-09-19 09:00:00,11629.0 -2017-09-19 10:00:00,12067.0 -2017-09-19 11:00:00,12451.0 -2017-09-19 12:00:00,12865.0 -2017-09-19 13:00:00,13341.0 -2017-09-19 14:00:00,13938.0 -2017-09-19 15:00:00,14426.0 -2017-09-19 16:00:00,14598.0 -2017-09-19 17:00:00,14719.0 -2017-09-19 18:00:00,14773.0 -2017-09-19 19:00:00,14415.0 -2017-09-19 20:00:00,13775.0 -2017-09-19 21:00:00,13721.0 -2017-09-19 22:00:00,13185.0 -2017-09-19 23:00:00,12359.0 -2017-09-20 00:00:00,11291.0 -2017-09-18 01:00:00,9987.0 -2017-09-18 02:00:00,9277.0 -2017-09-18 03:00:00,8777.0 -2017-09-18 04:00:00,8487.0 -2017-09-18 05:00:00,8380.0 -2017-09-18 06:00:00,8546.0 -2017-09-18 07:00:00,9205.0 -2017-09-18 08:00:00,10158.0 -2017-09-18 09:00:00,10791.0 -2017-09-18 10:00:00,11503.0 -2017-09-18 11:00:00,12048.0 -2017-09-18 12:00:00,12604.0 -2017-09-18 13:00:00,13018.0 -2017-09-18 14:00:00,13370.0 -2017-09-18 15:00:00,13766.0 -2017-09-18 16:00:00,14024.0 -2017-09-18 17:00:00,14176.0 -2017-09-18 18:00:00,13970.0 -2017-09-18 19:00:00,13293.0 -2017-09-18 20:00:00,12757.0 -2017-09-18 21:00:00,12882.0 -2017-09-18 22:00:00,12538.0 -2017-09-18 23:00:00,11900.0 -2017-09-19 00:00:00,11031.0 -2017-09-17 01:00:00,11269.0 -2017-09-17 02:00:00,10482.0 -2017-09-17 03:00:00,9958.0 -2017-09-17 04:00:00,9508.0 -2017-09-17 05:00:00,9204.0 -2017-09-17 06:00:00,9106.0 -2017-09-17 07:00:00,9111.0 -2017-09-17 08:00:00,9181.0 -2017-09-17 09:00:00,9341.0 -2017-09-17 10:00:00,10102.0 -2017-09-17 11:00:00,11083.0 -2017-09-17 12:00:00,12273.0 -2017-09-17 13:00:00,13321.0 -2017-09-17 14:00:00,14091.0 -2017-09-17 15:00:00,14260.0 -2017-09-17 16:00:00,14102.0 -2017-09-17 17:00:00,14048.0 -2017-09-17 18:00:00,14064.0 -2017-09-17 19:00:00,13756.0 -2017-09-17 20:00:00,13257.0 -2017-09-17 21:00:00,13252.0 -2017-09-17 22:00:00,12780.0 -2017-09-17 23:00:00,11971.0 -2017-09-18 00:00:00,11000.0 -2017-09-16 01:00:00,11474.0 -2017-09-16 02:00:00,10678.0 -2017-09-16 03:00:00,10002.0 -2017-09-16 04:00:00,9542.0 -2017-09-16 05:00:00,9313.0 -2017-09-16 06:00:00,9187.0 -2017-09-16 07:00:00,9319.0 -2017-09-16 08:00:00,9540.0 -2017-09-16 09:00:00,9929.0 -2017-09-16 10:00:00,10937.0 -2017-09-16 11:00:00,11925.0 -2017-09-16 12:00:00,12927.0 -2017-09-16 13:00:00,13852.0 -2017-09-16 14:00:00,14427.0 -2017-09-16 15:00:00,14709.0 -2017-09-16 16:00:00,15013.0 -2017-09-16 17:00:00,15226.0 -2017-09-16 18:00:00,15216.0 -2017-09-16 19:00:00,14855.0 -2017-09-16 20:00:00,14234.0 -2017-09-16 21:00:00,14020.0 -2017-09-16 22:00:00,13527.0 -2017-09-16 23:00:00,12963.0 -2017-09-17 00:00:00,12105.0 -2017-09-15 01:00:00,10357.0 -2017-09-15 02:00:00,9665.0 -2017-09-15 03:00:00,9194.0 -2017-09-15 04:00:00,8868.0 -2017-09-15 05:00:00,8695.0 -2017-09-15 06:00:00,8821.0 -2017-09-15 07:00:00,9412.0 -2017-09-15 08:00:00,10375.0 -2017-09-15 09:00:00,11057.0 -2017-09-15 10:00:00,11742.0 -2017-09-15 11:00:00,12436.0 -2017-09-15 12:00:00,13200.0 -2017-09-15 13:00:00,13890.0 -2017-09-15 14:00:00,14479.0 -2017-09-15 15:00:00,15074.0 -2017-09-15 16:00:00,15479.0 -2017-09-15 17:00:00,15796.0 -2017-09-15 18:00:00,15944.0 -2017-09-15 19:00:00,15644.0 -2017-09-15 20:00:00,14958.0 -2017-09-15 21:00:00,14707.0 -2017-09-15 22:00:00,14148.0 -2017-09-15 23:00:00,13499.0 -2017-09-16 00:00:00,12544.0 -2017-09-14 01:00:00,9690.0 -2017-09-14 02:00:00,9072.0 -2017-09-14 03:00:00,8688.0 -2017-09-14 04:00:00,8421.0 -2017-09-14 05:00:00,8300.0 -2017-09-14 06:00:00,8478.0 -2017-09-14 07:00:00,9113.0 -2017-09-14 08:00:00,10078.0 -2017-09-14 09:00:00,10720.0 -2017-09-14 10:00:00,11172.0 -2017-09-14 11:00:00,11561.0 -2017-09-14 12:00:00,11983.0 -2017-09-14 13:00:00,12400.0 -2017-09-14 14:00:00,12797.0 -2017-09-14 15:00:00,13247.0 -2017-09-14 16:00:00,13568.0 -2017-09-14 17:00:00,13850.0 -2017-09-14 18:00:00,13976.0 -2017-09-14 19:00:00,13801.0 -2017-09-14 20:00:00,13309.0 -2017-09-14 21:00:00,13353.0 -2017-09-14 22:00:00,13066.0 -2017-09-14 23:00:00,12341.0 -2017-09-15 00:00:00,11305.0 -2017-09-13 01:00:00,9366.0 -2017-09-13 02:00:00,8876.0 -2017-09-13 03:00:00,8543.0 -2017-09-13 04:00:00,8307.0 -2017-09-13 05:00:00,8270.0 -2017-09-13 06:00:00,8428.0 -2017-09-13 07:00:00,9072.0 -2017-09-13 08:00:00,10128.0 -2017-09-13 09:00:00,10764.0 -2017-09-13 10:00:00,11150.0 -2017-09-13 11:00:00,11368.0 -2017-09-13 12:00:00,11733.0 -2017-09-13 13:00:00,12018.0 -2017-09-13 14:00:00,12200.0 -2017-09-13 15:00:00,12427.0 -2017-09-13 16:00:00,12440.0 -2017-09-13 17:00:00,12326.0 -2017-09-13 18:00:00,12235.0 -2017-09-13 19:00:00,12096.0 -2017-09-13 20:00:00,11860.0 -2017-09-13 21:00:00,12120.0 -2017-09-13 22:00:00,11940.0 -2017-09-13 23:00:00,11347.0 -2017-09-14 00:00:00,10515.0 -2017-09-12 01:00:00,9021.0 -2017-09-12 02:00:00,8568.0 -2017-09-12 03:00:00,8263.0 -2017-09-12 04:00:00,8099.0 -2017-09-12 05:00:00,8047.0 -2017-09-12 06:00:00,8210.0 -2017-09-12 07:00:00,8847.0 -2017-09-12 08:00:00,9771.0 -2017-09-12 09:00:00,10378.0 -2017-09-12 10:00:00,10931.0 -2017-09-12 11:00:00,11310.0 -2017-09-12 12:00:00,11637.0 -2017-09-12 13:00:00,11837.0 -2017-09-12 14:00:00,12011.0 -2017-09-12 15:00:00,12207.0 -2017-09-12 16:00:00,12310.0 -2017-09-12 17:00:00,12272.0 -2017-09-12 18:00:00,12220.0 -2017-09-12 19:00:00,11886.0 -2017-09-12 20:00:00,11506.0 -2017-09-12 21:00:00,11678.0 -2017-09-12 22:00:00,11508.0 -2017-09-12 23:00:00,10902.0 -2017-09-13 00:00:00,10116.0 -2017-09-11 01:00:00,8416.0 -2017-09-11 02:00:00,8053.0 -2017-09-11 03:00:00,7799.0 -2017-09-11 04:00:00,7677.0 -2017-09-11 05:00:00,7601.0 -2017-09-11 06:00:00,7800.0 -2017-09-11 07:00:00,8449.0 -2017-09-11 08:00:00,9351.0 -2017-09-11 09:00:00,9960.0 -2017-09-11 10:00:00,10369.0 -2017-09-11 11:00:00,10738.0 -2017-09-11 12:00:00,11105.0 -2017-09-11 13:00:00,11308.0 -2017-09-11 14:00:00,11468.0 -2017-09-11 15:00:00,11655.0 -2017-09-11 16:00:00,11731.0 -2017-09-11 17:00:00,11699.0 -2017-09-11 18:00:00,11547.0 -2017-09-11 19:00:00,11280.0 -2017-09-11 20:00:00,11004.0 -2017-09-11 21:00:00,11216.0 -2017-09-11 22:00:00,11081.0 -2017-09-11 23:00:00,10499.0 -2017-09-12 00:00:00,9774.0 -2017-09-10 01:00:00,8528.0 -2017-09-10 02:00:00,8083.0 -2017-09-10 03:00:00,7730.0 -2017-09-10 04:00:00,7468.0 -2017-09-10 05:00:00,7314.0 -2017-09-10 06:00:00,7263.0 -2017-09-10 07:00:00,7351.0 -2017-09-10 08:00:00,7402.0 -2017-09-10 09:00:00,7520.0 -2017-09-10 10:00:00,7934.0 -2017-09-10 11:00:00,8390.0 -2017-09-10 12:00:00,8669.0 -2017-09-10 13:00:00,8896.0 -2017-09-10 14:00:00,9033.0 -2017-09-10 15:00:00,9128.0 -2017-09-10 16:00:00,9270.0 -2017-09-10 17:00:00,9363.0 -2017-09-10 18:00:00,9470.0 -2017-09-10 19:00:00,9481.0 -2017-09-10 20:00:00,9439.0 -2017-09-10 21:00:00,9799.0 -2017-09-10 22:00:00,9847.0 -2017-09-10 23:00:00,9464.0 -2017-09-11 00:00:00,8964.0 -2017-09-09 01:00:00,8909.0 -2017-09-09 02:00:00,8494.0 -2017-09-09 03:00:00,8140.0 -2017-09-09 04:00:00,7949.0 -2017-09-09 05:00:00,7823.0 -2017-09-09 06:00:00,7840.0 -2017-09-09 07:00:00,8100.0 -2017-09-09 08:00:00,8318.0 -2017-09-09 09:00:00,8622.0 -2017-09-09 10:00:00,9128.0 -2017-09-09 11:00:00,9519.0 -2017-09-09 12:00:00,9774.0 -2017-09-09 13:00:00,9868.0 -2017-09-09 14:00:00,9929.0 -2017-09-09 15:00:00,9904.0 -2017-09-09 16:00:00,9913.0 -2017-09-09 17:00:00,9931.0 -2017-09-09 18:00:00,9950.0 -2017-09-09 19:00:00,9847.0 -2017-09-09 20:00:00,9636.0 -2017-09-09 21:00:00,9875.0 -2017-09-09 22:00:00,9833.0 -2017-09-09 23:00:00,9552.0 -2017-09-10 00:00:00,9055.0 -2017-09-08 01:00:00,8892.0 -2017-09-08 02:00:00,8427.0 -2017-09-08 03:00:00,8122.0 -2017-09-08 04:00:00,7938.0 -2017-09-08 05:00:00,7862.0 -2017-09-08 06:00:00,8007.0 -2017-09-08 07:00:00,8598.0 -2017-09-08 08:00:00,9607.0 -2017-09-08 09:00:00,10207.0 -2017-09-08 10:00:00,10648.0 -2017-09-08 11:00:00,10972.0 -2017-09-08 12:00:00,11263.0 -2017-09-08 13:00:00,11379.0 -2017-09-08 14:00:00,11382.0 -2017-09-08 15:00:00,11423.0 -2017-09-08 16:00:00,11345.0 -2017-09-08 17:00:00,11211.0 -2017-09-08 18:00:00,10987.0 -2017-09-08 19:00:00,10707.0 -2017-09-08 20:00:00,10491.0 -2017-09-08 21:00:00,10659.0 -2017-09-08 22:00:00,10643.0 -2017-09-08 23:00:00,10231.0 -2017-09-09 00:00:00,9637.0 -2017-09-07 01:00:00,8740.0 -2017-09-07 02:00:00,8285.0 -2017-09-07 03:00:00,7989.0 -2017-09-07 04:00:00,7831.0 -2017-09-07 05:00:00,7809.0 -2017-09-07 06:00:00,7961.0 -2017-09-07 07:00:00,8559.0 -2017-09-07 08:00:00,9475.0 -2017-09-07 09:00:00,10031.0 -2017-09-07 10:00:00,10418.0 -2017-09-07 11:00:00,10724.0 -2017-09-07 12:00:00,11012.0 -2017-09-07 13:00:00,11096.0 -2017-09-07 14:00:00,11147.0 -2017-09-07 15:00:00,11200.0 -2017-09-07 16:00:00,11118.0 -2017-09-07 17:00:00,11032.0 -2017-09-07 18:00:00,10905.0 -2017-09-07 19:00:00,10738.0 -2017-09-07 20:00:00,10510.0 -2017-09-07 21:00:00,10829.0 -2017-09-07 22:00:00,10827.0 -2017-09-07 23:00:00,10313.0 -2017-09-08 00:00:00,9577.0 -2017-09-06 01:00:00,8866.0 -2017-09-06 02:00:00,8381.0 -2017-09-06 03:00:00,8091.0 -2017-09-06 04:00:00,7888.0 -2017-09-06 05:00:00,7836.0 -2017-09-06 06:00:00,8008.0 -2017-09-06 07:00:00,8575.0 -2017-09-06 08:00:00,9433.0 -2017-09-06 09:00:00,10007.0 -2017-09-06 10:00:00,10350.0 -2017-09-06 11:00:00,10612.0 -2017-09-06 12:00:00,10875.0 -2017-09-06 13:00:00,11040.0 -2017-09-06 14:00:00,11086.0 -2017-09-06 15:00:00,11178.0 -2017-09-06 16:00:00,11077.0 -2017-09-06 17:00:00,10913.0 -2017-09-06 18:00:00,10805.0 -2017-09-06 19:00:00,10649.0 -2017-09-06 20:00:00,10488.0 -2017-09-06 21:00:00,10741.0 -2017-09-06 22:00:00,10668.0 -2017-09-06 23:00:00,10154.0 -2017-09-07 00:00:00,9448.0 -2017-09-05 01:00:00,8889.0 -2017-09-05 02:00:00,8376.0 -2017-09-05 03:00:00,8065.0 -2017-09-05 04:00:00,7853.0 -2017-09-05 05:00:00,7725.0 -2017-09-05 06:00:00,7889.0 -2017-09-05 07:00:00,8534.0 -2017-09-05 08:00:00,9417.0 -2017-09-05 09:00:00,10076.0 -2017-09-05 10:00:00,10546.0 -2017-09-05 11:00:00,10888.0 -2017-09-05 12:00:00,11298.0 -2017-09-05 13:00:00,11536.0 -2017-09-05 14:00:00,11650.0 -2017-09-05 15:00:00,11801.0 -2017-09-05 16:00:00,11763.0 -2017-09-05 17:00:00,11625.0 -2017-09-05 18:00:00,11367.0 -2017-09-05 19:00:00,11007.0 -2017-09-05 20:00:00,10711.0 -2017-09-05 21:00:00,10905.0 -2017-09-05 22:00:00,10838.0 -2017-09-05 23:00:00,10301.0 -2017-09-06 00:00:00,9557.0 -2017-09-04 01:00:00,9306.0 -2017-09-04 02:00:00,8786.0 -2017-09-04 03:00:00,8370.0 -2017-09-04 04:00:00,8107.0 -2017-09-04 05:00:00,7928.0 -2017-09-04 06:00:00,7932.0 -2017-09-04 07:00:00,8062.0 -2017-09-04 08:00:00,8202.0 -2017-09-04 09:00:00,8294.0 -2017-09-04 10:00:00,8814.0 -2017-09-04 11:00:00,9592.0 -2017-09-04 12:00:00,10478.0 -2017-09-04 13:00:00,11352.0 -2017-09-04 14:00:00,11898.0 -2017-09-04 15:00:00,11937.0 -2017-09-04 16:00:00,11678.0 -2017-09-04 17:00:00,11540.0 -2017-09-04 18:00:00,11455.0 -2017-09-04 19:00:00,11322.0 -2017-09-04 20:00:00,11026.0 -2017-09-04 21:00:00,11163.0 -2017-09-04 22:00:00,10979.0 -2017-09-04 23:00:00,10341.0 -2017-09-05 00:00:00,9592.0 -2017-09-03 01:00:00,8573.0 -2017-09-03 02:00:00,8179.0 -2017-09-03 03:00:00,7796.0 -2017-09-03 04:00:00,7591.0 -2017-09-03 05:00:00,7467.0 -2017-09-03 06:00:00,7443.0 -2017-09-03 07:00:00,7513.0 -2017-09-03 08:00:00,7512.0 -2017-09-03 09:00:00,7748.0 -2017-09-03 10:00:00,8266.0 -2017-09-03 11:00:00,8853.0 -2017-09-03 12:00:00,9378.0 -2017-09-03 13:00:00,9877.0 -2017-09-03 14:00:00,10186.0 -2017-09-03 15:00:00,10509.0 -2017-09-03 16:00:00,10906.0 -2017-09-03 17:00:00,11197.0 -2017-09-03 18:00:00,11404.0 -2017-09-03 19:00:00,11392.0 -2017-09-03 20:00:00,11061.0 -2017-09-03 21:00:00,10971.0 -2017-09-03 22:00:00,10932.0 -2017-09-03 23:00:00,10540.0 -2017-09-04 00:00:00,9925.0 -2017-09-02 01:00:00,8989.0 -2017-09-02 02:00:00,8411.0 -2017-09-02 03:00:00,8084.0 -2017-09-02 04:00:00,7837.0 -2017-09-02 05:00:00,7688.0 -2017-09-02 06:00:00,7692.0 -2017-09-02 07:00:00,7820.0 -2017-09-02 08:00:00,7919.0 -2017-09-02 09:00:00,8115.0 -2017-09-02 10:00:00,8680.0 -2017-09-02 11:00:00,9115.0 -2017-09-02 12:00:00,9406.0 -2017-09-02 13:00:00,9559.0 -2017-09-02 14:00:00,9674.0 -2017-09-02 15:00:00,9708.0 -2017-09-02 16:00:00,9639.0 -2017-09-02 17:00:00,9712.0 -2017-09-02 18:00:00,9753.0 -2017-09-02 19:00:00,9713.0 -2017-09-02 20:00:00,9639.0 -2017-09-02 21:00:00,9887.0 -2017-09-02 22:00:00,9862.0 -2017-09-02 23:00:00,9581.0 -2017-09-03 00:00:00,9149.0 -2017-09-01 01:00:00,9543.0 -2017-09-01 02:00:00,9011.0 -2017-09-01 03:00:00,8620.0 -2017-09-01 04:00:00,8322.0 -2017-09-01 05:00:00,8193.0 -2017-09-01 06:00:00,8309.0 -2017-09-01 07:00:00,8813.0 -2017-09-01 08:00:00,9543.0 -2017-09-01 09:00:00,10119.0 -2017-09-01 10:00:00,10604.0 -2017-09-01 11:00:00,10877.0 -2017-09-01 12:00:00,11169.0 -2017-09-01 13:00:00,11304.0 -2017-09-01 14:00:00,11427.0 -2017-09-01 15:00:00,11554.0 -2017-09-01 16:00:00,11578.0 -2017-09-01 17:00:00,11503.0 -2017-09-01 18:00:00,11404.0 -2017-09-01 19:00:00,11187.0 -2017-09-01 20:00:00,10775.0 -2017-09-01 21:00:00,10709.0 -2017-09-01 22:00:00,10734.0 -2017-09-01 23:00:00,10319.0 -2017-09-02 00:00:00,9660.0 -2017-08-31 01:00:00,10500.0 -2017-08-31 02:00:00,9809.0 -2017-08-31 03:00:00,9342.0 -2017-08-31 04:00:00,9079.0 -2017-08-31 05:00:00,8944.0 -2017-08-31 06:00:00,9099.0 -2017-08-31 07:00:00,9694.0 -2017-08-31 08:00:00,10572.0 -2017-08-31 09:00:00,11238.0 -2017-08-31 10:00:00,11741.0 -2017-08-31 11:00:00,12159.0 -2017-08-31 12:00:00,12585.0 -2017-08-31 13:00:00,12794.0 -2017-08-31 14:00:00,12930.0 -2017-08-31 15:00:00,13044.0 -2017-08-31 16:00:00,13006.0 -2017-08-31 17:00:00,12906.0 -2017-08-31 18:00:00,12697.0 -2017-08-31 19:00:00,12248.0 -2017-08-31 20:00:00,11666.0 -2017-08-31 21:00:00,11668.0 -2017-08-31 22:00:00,11674.0 -2017-08-31 23:00:00,11152.0 -2017-09-01 00:00:00,10388.0 -2017-08-30 01:00:00,10053.0 -2017-08-30 02:00:00,9407.0 -2017-08-30 03:00:00,8986.0 -2017-08-30 04:00:00,8696.0 -2017-08-30 05:00:00,8608.0 -2017-08-30 06:00:00,8755.0 -2017-08-30 07:00:00,9344.0 -2017-08-30 08:00:00,10164.0 -2017-08-30 09:00:00,10923.0 -2017-08-30 10:00:00,11590.0 -2017-08-30 11:00:00,12166.0 -2017-08-30 12:00:00,12778.0 -2017-08-30 13:00:00,13154.0 -2017-08-30 14:00:00,13547.0 -2017-08-30 15:00:00,13922.0 -2017-08-30 16:00:00,14196.0 -2017-08-30 17:00:00,14367.0 -2017-08-30 18:00:00,14382.0 -2017-08-30 19:00:00,14208.0 -2017-08-30 20:00:00,13664.0 -2017-08-30 21:00:00,13408.0 -2017-08-30 22:00:00,13265.0 -2017-08-30 23:00:00,12523.0 -2017-08-31 00:00:00,11458.0 -2017-08-29 01:00:00,10081.0 -2017-08-29 02:00:00,9453.0 -2017-08-29 03:00:00,9028.0 -2017-08-29 04:00:00,8797.0 -2017-08-29 05:00:00,8686.0 -2017-08-29 06:00:00,8828.0 -2017-08-29 07:00:00,9427.0 -2017-08-29 08:00:00,10310.0 -2017-08-29 09:00:00,11081.0 -2017-08-29 10:00:00,11770.0 -2017-08-29 11:00:00,12352.0 -2017-08-29 12:00:00,12813.0 -2017-08-29 13:00:00,13013.0 -2017-08-29 14:00:00,12996.0 -2017-08-29 15:00:00,13033.0 -2017-08-29 16:00:00,12921.0 -2017-08-29 17:00:00,12659.0 -2017-08-29 18:00:00,12522.0 -2017-08-29 19:00:00,12608.0 -2017-08-29 20:00:00,12371.0 -2017-08-29 21:00:00,12377.0 -2017-08-29 22:00:00,12372.0 -2017-08-29 23:00:00,11817.0 -2017-08-30 00:00:00,10921.0 -2017-08-28 01:00:00,9423.0 -2017-08-28 02:00:00,8980.0 -2017-08-28 03:00:00,8689.0 -2017-08-28 04:00:00,8527.0 -2017-08-28 05:00:00,8477.0 -2017-08-28 06:00:00,8673.0 -2017-08-28 07:00:00,9469.0 -2017-08-28 08:00:00,10413.0 -2017-08-28 09:00:00,11099.0 -2017-08-28 10:00:00,11646.0 -2017-08-28 11:00:00,12122.0 -2017-08-28 12:00:00,12677.0 -2017-08-28 13:00:00,13126.0 -2017-08-28 14:00:00,13516.0 -2017-08-28 15:00:00,13898.0 -2017-08-28 16:00:00,14069.0 -2017-08-28 17:00:00,13860.0 -2017-08-28 18:00:00,13456.0 -2017-08-28 19:00:00,13026.0 -2017-08-28 20:00:00,12586.0 -2017-08-28 21:00:00,12515.0 -2017-08-28 22:00:00,12444.0 -2017-08-28 23:00:00,11848.0 -2017-08-29 00:00:00,10983.0 -2017-08-27 01:00:00,9314.0 -2017-08-27 02:00:00,8868.0 -2017-08-27 03:00:00,8509.0 -2017-08-27 04:00:00,8213.0 -2017-08-27 05:00:00,8031.0 -2017-08-27 06:00:00,7952.0 -2017-08-27 07:00:00,7944.0 -2017-08-27 08:00:00,7838.0 -2017-08-27 09:00:00,7993.0 -2017-08-27 10:00:00,8499.0 -2017-08-27 11:00:00,8904.0 -2017-08-27 12:00:00,9197.0 -2017-08-27 13:00:00,9428.0 -2017-08-27 14:00:00,9608.0 -2017-08-27 15:00:00,9898.0 -2017-08-27 16:00:00,10290.0 -2017-08-27 17:00:00,10484.0 -2017-08-27 18:00:00,10474.0 -2017-08-27 19:00:00,10500.0 -2017-08-27 20:00:00,10550.0 -2017-08-27 21:00:00,10860.0 -2017-08-27 22:00:00,11007.0 -2017-08-27 23:00:00,10673.0 -2017-08-28 00:00:00,10147.0 -2017-08-26 01:00:00,9519.0 -2017-08-26 02:00:00,8949.0 -2017-08-26 03:00:00,8593.0 -2017-08-26 04:00:00,8288.0 -2017-08-26 05:00:00,8146.0 -2017-08-26 06:00:00,8132.0 -2017-08-26 07:00:00,8357.0 -2017-08-26 08:00:00,8563.0 -2017-08-26 09:00:00,8940.0 -2017-08-26 10:00:00,9486.0 -2017-08-26 11:00:00,10002.0 -2017-08-26 12:00:00,10449.0 -2017-08-26 13:00:00,10765.0 -2017-08-26 14:00:00,10967.0 -2017-08-26 15:00:00,11113.0 -2017-08-26 16:00:00,11122.0 -2017-08-26 17:00:00,11008.0 -2017-08-26 18:00:00,10834.0 -2017-08-26 19:00:00,10683.0 -2017-08-26 20:00:00,10548.0 -2017-08-26 21:00:00,10720.0 -2017-08-26 22:00:00,10728.0 -2017-08-26 23:00:00,10424.0 -2017-08-27 00:00:00,9901.0 -2017-08-25 01:00:00,9393.0 -2017-08-25 02:00:00,8853.0 -2017-08-25 03:00:00,8503.0 -2017-08-25 04:00:00,8230.0 -2017-08-25 05:00:00,8112.0 -2017-08-25 06:00:00,8281.0 -2017-08-25 07:00:00,8780.0 -2017-08-25 08:00:00,9486.0 -2017-08-25 09:00:00,10171.0 -2017-08-25 10:00:00,10745.0 -2017-08-25 11:00:00,11261.0 -2017-08-25 12:00:00,11624.0 -2017-08-25 13:00:00,11853.0 -2017-08-25 14:00:00,12006.0 -2017-08-25 15:00:00,12219.0 -2017-08-25 16:00:00,12342.0 -2017-08-25 17:00:00,12399.0 -2017-08-25 18:00:00,12384.0 -2017-08-25 19:00:00,12165.0 -2017-08-25 20:00:00,11713.0 -2017-08-25 21:00:00,11393.0 -2017-08-25 22:00:00,11427.0 -2017-08-25 23:00:00,10945.0 -2017-08-26 00:00:00,10273.0 -2017-08-24 01:00:00,10073.0 -2017-08-24 02:00:00,9431.0 -2017-08-24 03:00:00,9010.0 -2017-08-24 04:00:00,8706.0 -2017-08-24 05:00:00,8581.0 -2017-08-24 06:00:00,8733.0 -2017-08-24 07:00:00,9262.0 -2017-08-24 08:00:00,10080.0 -2017-08-24 09:00:00,10760.0 -2017-08-24 10:00:00,11336.0 -2017-08-24 11:00:00,11885.0 -2017-08-24 12:00:00,12347.0 -2017-08-24 13:00:00,12595.0 -2017-08-24 14:00:00,12802.0 -2017-08-24 15:00:00,12891.0 -2017-08-24 16:00:00,12834.0 -2017-08-24 17:00:00,12704.0 -2017-08-24 18:00:00,12604.0 -2017-08-24 19:00:00,12278.0 -2017-08-24 20:00:00,11763.0 -2017-08-24 21:00:00,11539.0 -2017-08-24 22:00:00,11592.0 -2017-08-24 23:00:00,11029.0 -2017-08-25 00:00:00,10202.0 -2017-08-23 01:00:00,10416.0 -2017-08-23 02:00:00,9703.0 -2017-08-23 03:00:00,9217.0 -2017-08-23 04:00:00,8907.0 -2017-08-23 05:00:00,8734.0 -2017-08-23 06:00:00,8840.0 -2017-08-23 07:00:00,9381.0 -2017-08-23 08:00:00,10123.0 -2017-08-23 09:00:00,10924.0 -2017-08-23 10:00:00,11608.0 -2017-08-23 11:00:00,12089.0 -2017-08-23 12:00:00,12594.0 -2017-08-23 13:00:00,12967.0 -2017-08-23 14:00:00,13254.0 -2017-08-23 15:00:00,13539.0 -2017-08-23 16:00:00,13736.0 -2017-08-23 17:00:00,13831.0 -2017-08-23 18:00:00,13782.0 -2017-08-23 19:00:00,13521.0 -2017-08-23 20:00:00,12959.0 -2017-08-23 21:00:00,12558.0 -2017-08-23 22:00:00,12593.0 -2017-08-23 23:00:00,11923.0 -2017-08-24 00:00:00,10960.0 -2017-08-22 01:00:00,12788.0 -2017-08-22 02:00:00,11767.0 -2017-08-22 03:00:00,11236.0 -2017-08-22 04:00:00,10930.0 -2017-08-22 05:00:00,10746.0 -2017-08-22 06:00:00,10899.0 -2017-08-22 07:00:00,11465.0 -2017-08-22 08:00:00,12308.0 -2017-08-22 09:00:00,12881.0 -2017-08-22 10:00:00,13386.0 -2017-08-22 11:00:00,13734.0 -2017-08-22 12:00:00,14114.0 -2017-08-22 13:00:00,14356.0 -2017-08-22 14:00:00,14789.0 -2017-08-22 15:00:00,15312.0 -2017-08-22 16:00:00,15536.0 -2017-08-22 17:00:00,15641.0 -2017-08-22 18:00:00,15548.0 -2017-08-22 19:00:00,15192.0 -2017-08-22 20:00:00,14382.0 -2017-08-22 21:00:00,13607.0 -2017-08-22 22:00:00,13358.0 -2017-08-22 23:00:00,12553.0 -2017-08-23 00:00:00,11478.0 -2017-08-21 01:00:00,12211.0 -2017-08-21 02:00:00,11444.0 -2017-08-21 03:00:00,10871.0 -2017-08-21 04:00:00,10479.0 -2017-08-21 05:00:00,10340.0 -2017-08-21 06:00:00,10478.0 -2017-08-21 07:00:00,11216.0 -2017-08-21 08:00:00,12095.0 -2017-08-21 09:00:00,13079.0 -2017-08-21 10:00:00,13913.0 -2017-08-21 11:00:00,14800.0 -2017-08-21 12:00:00,15724.0 -2017-08-21 13:00:00,16140.0 -2017-08-21 14:00:00,16319.0 -2017-08-21 15:00:00,15908.0 -2017-08-21 16:00:00,16202.0 -2017-08-21 17:00:00,16750.0 -2017-08-21 18:00:00,17313.0 -2017-08-21 19:00:00,17064.0 -2017-08-21 20:00:00,16384.0 -2017-08-21 21:00:00,15938.0 -2017-08-21 22:00:00,15778.0 -2017-08-21 23:00:00,15017.0 -2017-08-22 00:00:00,13930.0 -2017-08-20 01:00:00,10816.0 -2017-08-20 02:00:00,10076.0 -2017-08-20 03:00:00,9551.0 -2017-08-20 04:00:00,9088.0 -2017-08-20 05:00:00,8831.0 -2017-08-20 06:00:00,8677.0 -2017-08-20 07:00:00,8725.0 -2017-08-20 08:00:00,8638.0 -2017-08-20 09:00:00,9094.0 -2017-08-20 10:00:00,9935.0 -2017-08-20 11:00:00,10918.0 -2017-08-20 12:00:00,11924.0 -2017-08-20 13:00:00,12980.0 -2017-08-20 14:00:00,13953.0 -2017-08-20 15:00:00,14708.0 -2017-08-20 16:00:00,15112.0 -2017-08-20 17:00:00,14964.0 -2017-08-20 18:00:00,14848.0 -2017-08-20 19:00:00,14590.0 -2017-08-20 20:00:00,14424.0 -2017-08-20 21:00:00,14422.0 -2017-08-20 22:00:00,14444.0 -2017-08-20 23:00:00,13940.0 -2017-08-21 00:00:00,13176.0 -2017-08-19 01:00:00,11690.0 -2017-08-19 02:00:00,10835.0 -2017-08-19 03:00:00,10197.0 -2017-08-19 04:00:00,9738.0 -2017-08-19 05:00:00,9760.0 -2017-08-19 06:00:00,9573.0 -2017-08-19 07:00:00,9697.0 -2017-08-19 08:00:00,9765.0 -2017-08-19 09:00:00,10227.0 -2017-08-19 10:00:00,11202.0 -2017-08-19 11:00:00,12146.0 -2017-08-19 12:00:00,13000.0 -2017-08-19 13:00:00,13743.0 -2017-08-19 14:00:00,14374.0 -2017-08-19 15:00:00,14814.0 -2017-08-19 16:00:00,15242.0 -2017-08-19 17:00:00,15585.0 -2017-08-19 18:00:00,15692.0 -2017-08-19 19:00:00,15564.0 -2017-08-19 20:00:00,14919.0 -2017-08-19 21:00:00,13968.0 -2017-08-19 22:00:00,13508.0 -2017-08-19 23:00:00,12850.0 -2017-08-20 00:00:00,11957.0 -2017-08-18 01:00:00,12036.0 -2017-08-18 02:00:00,11176.0 -2017-08-18 03:00:00,10524.0 -2017-08-18 04:00:00,10060.0 -2017-08-18 05:00:00,9834.0 -2017-08-18 06:00:00,9864.0 -2017-08-18 07:00:00,10401.0 -2017-08-18 08:00:00,11120.0 -2017-08-18 09:00:00,12021.0 -2017-08-18 10:00:00,12710.0 -2017-08-18 11:00:00,13190.0 -2017-08-18 12:00:00,13587.0 -2017-08-18 13:00:00,14063.0 -2017-08-18 14:00:00,14650.0 -2017-08-18 15:00:00,15240.0 -2017-08-18 16:00:00,15708.0 -2017-08-18 17:00:00,16105.0 -2017-08-18 18:00:00,16229.0 -2017-08-18 19:00:00,15941.0 -2017-08-18 20:00:00,15232.0 -2017-08-18 21:00:00,14489.0 -2017-08-18 22:00:00,14307.0 -2017-08-18 23:00:00,13670.0 -2017-08-19 00:00:00,12724.0 -2017-08-17 01:00:00,13266.0 -2017-08-17 02:00:00,12442.0 -2017-08-17 03:00:00,11819.0 -2017-08-17 04:00:00,11312.0 -2017-08-17 05:00:00,11011.0 -2017-08-17 06:00:00,11019.0 -2017-08-17 07:00:00,11656.0 -2017-08-17 08:00:00,12611.0 -2017-08-17 09:00:00,13344.0 -2017-08-17 10:00:00,13788.0 -2017-08-17 11:00:00,14251.0 -2017-08-17 12:00:00,14575.0 -2017-08-17 13:00:00,14982.0 -2017-08-17 14:00:00,15687.0 -2017-08-17 15:00:00,16265.0 -2017-08-17 16:00:00,16501.0 -2017-08-17 17:00:00,16662.0 -2017-08-17 18:00:00,16676.0 -2017-08-17 19:00:00,16422.0 -2017-08-17 20:00:00,15742.0 -2017-08-17 21:00:00,15161.0 -2017-08-17 22:00:00,15050.0 -2017-08-17 23:00:00,14344.0 -2017-08-18 00:00:00,13169.0 -2017-08-16 01:00:00,11418.0 -2017-08-16 02:00:00,10626.0 -2017-08-16 03:00:00,10074.0 -2017-08-16 04:00:00,9732.0 -2017-08-16 05:00:00,9532.0 -2017-08-16 06:00:00,9635.0 -2017-08-16 07:00:00,10237.0 -2017-08-16 08:00:00,11101.0 -2017-08-16 09:00:00,12116.0 -2017-08-16 10:00:00,13105.0 -2017-08-16 11:00:00,14056.0 -2017-08-16 12:00:00,15092.0 -2017-08-16 13:00:00,16106.0 -2017-08-16 14:00:00,16994.0 -2017-08-16 15:00:00,17802.0 -2017-08-16 16:00:00,18237.0 -2017-08-16 17:00:00,18282.0 -2017-08-16 18:00:00,17547.0 -2017-08-16 19:00:00,16633.0 -2017-08-16 20:00:00,16042.0 -2017-08-16 21:00:00,15727.0 -2017-08-16 22:00:00,15768.0 -2017-08-16 23:00:00,15274.0 -2017-08-17 00:00:00,14254.0 -2017-08-15 01:00:00,11878.0 -2017-08-15 02:00:00,11038.0 -2017-08-15 03:00:00,10419.0 -2017-08-15 04:00:00,10056.0 -2017-08-15 05:00:00,9789.0 -2017-08-15 06:00:00,9909.0 -2017-08-15 07:00:00,10486.0 -2017-08-15 08:00:00,11253.0 -2017-08-15 09:00:00,12275.0 -2017-08-15 10:00:00,13299.0 -2017-08-15 11:00:00,14164.0 -2017-08-15 12:00:00,15038.0 -2017-08-15 13:00:00,15687.0 -2017-08-15 14:00:00,16249.0 -2017-08-15 15:00:00,16784.0 -2017-08-15 16:00:00,16993.0 -2017-08-15 17:00:00,16984.0 -2017-08-15 18:00:00,16777.0 -2017-08-15 19:00:00,16252.0 -2017-08-15 20:00:00,15236.0 -2017-08-15 21:00:00,14498.0 -2017-08-15 22:00:00,14272.0 -2017-08-15 23:00:00,13553.0 -2017-08-16 00:00:00,12514.0 -2017-08-14 01:00:00,9869.0 -2017-08-14 02:00:00,9357.0 -2017-08-14 03:00:00,8970.0 -2017-08-14 04:00:00,8743.0 -2017-08-14 05:00:00,8616.0 -2017-08-14 06:00:00,8830.0 -2017-08-14 07:00:00,9466.0 -2017-08-14 08:00:00,10192.0 -2017-08-14 09:00:00,11121.0 -2017-08-14 10:00:00,11998.0 -2017-08-14 11:00:00,12803.0 -2017-08-14 12:00:00,13428.0 -2017-08-14 13:00:00,13760.0 -2017-08-14 14:00:00,14066.0 -2017-08-14 15:00:00,14471.0 -2017-08-14 16:00:00,14942.0 -2017-08-14 17:00:00,15328.0 -2017-08-14 18:00:00,15433.0 -2017-08-14 19:00:00,15235.0 -2017-08-14 20:00:00,14731.0 -2017-08-14 21:00:00,14473.0 -2017-08-14 22:00:00,14535.0 -2017-08-14 23:00:00,13990.0 -2017-08-15 00:00:00,12969.0 -2017-08-13 01:00:00,9528.0 -2017-08-13 02:00:00,8985.0 -2017-08-13 03:00:00,8565.0 -2017-08-13 04:00:00,8234.0 -2017-08-13 05:00:00,8038.0 -2017-08-13 06:00:00,7980.0 -2017-08-13 07:00:00,7982.0 -2017-08-13 08:00:00,7866.0 -2017-08-13 09:00:00,8272.0 -2017-08-13 10:00:00,8883.0 -2017-08-13 11:00:00,9542.0 -2017-08-13 12:00:00,10066.0 -2017-08-13 13:00:00,10492.0 -2017-08-13 14:00:00,10899.0 -2017-08-13 15:00:00,11289.0 -2017-08-13 16:00:00,11493.0 -2017-08-13 17:00:00,11611.0 -2017-08-13 18:00:00,11623.0 -2017-08-13 19:00:00,11526.0 -2017-08-13 20:00:00,11273.0 -2017-08-13 21:00:00,11144.0 -2017-08-13 22:00:00,11384.0 -2017-08-13 23:00:00,11044.0 -2017-08-14 00:00:00,10562.0 -2017-08-12 01:00:00,10513.0 -2017-08-12 02:00:00,9799.0 -2017-08-12 03:00:00,9222.0 -2017-08-12 04:00:00,8923.0 -2017-08-12 05:00:00,8695.0 -2017-08-12 06:00:00,8642.0 -2017-08-12 07:00:00,8699.0 -2017-08-12 08:00:00,8822.0 -2017-08-12 09:00:00,9306.0 -2017-08-12 10:00:00,10073.0 -2017-08-12 11:00:00,10543.0 -2017-08-12 12:00:00,10973.0 -2017-08-12 13:00:00,11151.0 -2017-08-12 14:00:00,11279.0 -2017-08-12 15:00:00,11334.0 -2017-08-12 16:00:00,11492.0 -2017-08-12 17:00:00,11671.0 -2017-08-12 18:00:00,11816.0 -2017-08-12 19:00:00,11794.0 -2017-08-12 20:00:00,11540.0 -2017-08-12 21:00:00,11096.0 -2017-08-12 22:00:00,11101.0 -2017-08-12 23:00:00,10821.0 -2017-08-13 00:00:00,10151.0 -2017-08-11 01:00:00,11421.0 -2017-08-11 02:00:00,10598.0 -2017-08-11 03:00:00,10007.0 -2017-08-11 04:00:00,9631.0 -2017-08-11 05:00:00,9477.0 -2017-08-11 06:00:00,9551.0 -2017-08-11 07:00:00,10070.0 -2017-08-11 08:00:00,10747.0 -2017-08-11 09:00:00,11498.0 -2017-08-11 10:00:00,12342.0 -2017-08-11 11:00:00,13155.0 -2017-08-11 12:00:00,13690.0 -2017-08-11 13:00:00,14054.0 -2017-08-11 14:00:00,14302.0 -2017-08-11 15:00:00,14533.0 -2017-08-11 16:00:00,14599.0 -2017-08-11 17:00:00,14486.0 -2017-08-11 18:00:00,14260.0 -2017-08-11 19:00:00,13949.0 -2017-08-11 20:00:00,13391.0 -2017-08-11 21:00:00,12790.0 -2017-08-11 22:00:00,12751.0 -2017-08-11 23:00:00,12259.0 -2017-08-12 00:00:00,11457.0 -2017-08-10 01:00:00,11216.0 -2017-08-10 02:00:00,10374.0 -2017-08-10 03:00:00,9813.0 -2017-08-10 04:00:00,9393.0 -2017-08-10 05:00:00,9197.0 -2017-08-10 06:00:00,9259.0 -2017-08-10 07:00:00,9822.0 -2017-08-10 08:00:00,10461.0 -2017-08-10 09:00:00,11336.0 -2017-08-10 10:00:00,12136.0 -2017-08-10 11:00:00,12890.0 -2017-08-10 12:00:00,13564.0 -2017-08-10 13:00:00,14219.0 -2017-08-10 14:00:00,15077.0 -2017-08-10 15:00:00,15777.0 -2017-08-10 16:00:00,16176.0 -2017-08-10 17:00:00,16306.0 -2017-08-10 18:00:00,15942.0 -2017-08-10 19:00:00,15669.0 -2017-08-10 20:00:00,14884.0 -2017-08-10 21:00:00,14300.0 -2017-08-10 22:00:00,14224.0 -2017-08-10 23:00:00,13627.0 -2017-08-11 00:00:00,12595.0 -2017-08-09 01:00:00,10853.0 -2017-08-09 02:00:00,10083.0 -2017-08-09 03:00:00,9508.0 -2017-08-09 04:00:00,9142.0 -2017-08-09 05:00:00,8982.0 -2017-08-09 06:00:00,9050.0 -2017-08-09 07:00:00,9550.0 -2017-08-09 08:00:00,10179.0 -2017-08-09 09:00:00,11081.0 -2017-08-09 10:00:00,12020.0 -2017-08-09 11:00:00,12796.0 -2017-08-09 12:00:00,13516.0 -2017-08-09 13:00:00,14049.0 -2017-08-09 14:00:00,14485.0 -2017-08-09 15:00:00,14932.0 -2017-08-09 16:00:00,15153.0 -2017-08-09 17:00:00,15308.0 -2017-08-09 18:00:00,15491.0 -2017-08-09 19:00:00,15298.0 -2017-08-09 20:00:00,14778.0 -2017-08-09 21:00:00,14182.0 -2017-08-09 22:00:00,13955.0 -2017-08-09 23:00:00,13315.0 -2017-08-10 00:00:00,12202.0 -2017-08-08 01:00:00,9809.0 -2017-08-08 02:00:00,9181.0 -2017-08-08 03:00:00,8774.0 -2017-08-08 04:00:00,8495.0 -2017-08-08 05:00:00,8377.0 -2017-08-08 06:00:00,8479.0 -2017-08-08 07:00:00,8995.0 -2017-08-08 08:00:00,9633.0 -2017-08-08 09:00:00,10536.0 -2017-08-08 10:00:00,11253.0 -2017-08-08 11:00:00,11873.0 -2017-08-08 12:00:00,12510.0 -2017-08-08 13:00:00,13025.0 -2017-08-08 14:00:00,13488.0 -2017-08-08 15:00:00,14024.0 -2017-08-08 16:00:00,14426.0 -2017-08-08 17:00:00,14706.0 -2017-08-08 18:00:00,14847.0 -2017-08-08 19:00:00,14716.0 -2017-08-08 20:00:00,14306.0 -2017-08-08 21:00:00,13701.0 -2017-08-08 22:00:00,13555.0 -2017-08-08 23:00:00,12940.0 -2017-08-09 00:00:00,11918.0 -2017-08-07 01:00:00,9595.0 -2017-08-07 02:00:00,9110.0 -2017-08-07 03:00:00,8698.0 -2017-08-07 04:00:00,8501.0 -2017-08-07 05:00:00,8384.0 -2017-08-07 06:00:00,8618.0 -2017-08-07 07:00:00,9270.0 -2017-08-07 08:00:00,9985.0 -2017-08-07 09:00:00,10893.0 -2017-08-07 10:00:00,11619.0 -2017-08-07 11:00:00,12180.0 -2017-08-07 12:00:00,12675.0 -2017-08-07 13:00:00,12961.0 -2017-08-07 14:00:00,13225.0 -2017-08-07 15:00:00,13449.0 -2017-08-07 16:00:00,13535.0 -2017-08-07 17:00:00,13642.0 -2017-08-07 18:00:00,13683.0 -2017-08-07 19:00:00,13483.0 -2017-08-07 20:00:00,12962.0 -2017-08-07 21:00:00,12346.0 -2017-08-07 22:00:00,12128.0 -2017-08-07 23:00:00,11634.0 -2017-08-08 00:00:00,10747.0 -2017-08-06 01:00:00,9695.0 -2017-08-06 02:00:00,9138.0 -2017-08-06 03:00:00,8738.0 -2017-08-06 04:00:00,8444.0 -2017-08-06 05:00:00,8247.0 -2017-08-06 06:00:00,8164.0 -2017-08-06 07:00:00,8180.0 -2017-08-06 08:00:00,8175.0 -2017-08-06 09:00:00,8405.0 -2017-08-06 10:00:00,8879.0 -2017-08-06 11:00:00,9396.0 -2017-08-06 12:00:00,9893.0 -2017-08-06 13:00:00,10407.0 -2017-08-06 14:00:00,10635.0 -2017-08-06 15:00:00,10799.0 -2017-08-06 16:00:00,10890.0 -2017-08-06 17:00:00,11188.0 -2017-08-06 18:00:00,11434.0 -2017-08-06 19:00:00,11542.0 -2017-08-06 20:00:00,11359.0 -2017-08-06 21:00:00,11077.0 -2017-08-06 22:00:00,11202.0 -2017-08-06 23:00:00,10937.0 -2017-08-07 00:00:00,10353.0 -2017-08-05 01:00:00,9227.0 -2017-08-05 02:00:00,8747.0 -2017-08-05 03:00:00,8385.0 -2017-08-05 04:00:00,8197.0 -2017-08-05 05:00:00,8047.0 -2017-08-05 06:00:00,8076.0 -2017-08-05 07:00:00,8216.0 -2017-08-05 08:00:00,8307.0 -2017-08-05 09:00:00,8834.0 -2017-08-05 10:00:00,9451.0 -2017-08-05 11:00:00,10048.0 -2017-08-05 12:00:00,10547.0 -2017-08-05 13:00:00,10928.0 -2017-08-05 14:00:00,11240.0 -2017-08-05 15:00:00,11394.0 -2017-08-05 16:00:00,11542.0 -2017-08-05 17:00:00,11671.0 -2017-08-05 18:00:00,11748.0 -2017-08-05 19:00:00,11681.0 -2017-08-05 20:00:00,11322.0 -2017-08-05 21:00:00,11040.0 -2017-08-05 22:00:00,11246.0 -2017-08-05 23:00:00,10956.0 -2017-08-06 00:00:00,10338.0 -2017-08-04 01:00:00,11363.0 -2017-08-04 02:00:00,10344.0 -2017-08-04 03:00:00,9612.0 -2017-08-04 04:00:00,9175.0 -2017-08-04 05:00:00,8941.0 -2017-08-04 06:00:00,8976.0 -2017-08-04 07:00:00,9373.0 -2017-08-04 08:00:00,9867.0 -2017-08-04 09:00:00,10428.0 -2017-08-04 10:00:00,10871.0 -2017-08-04 11:00:00,11084.0 -2017-08-04 12:00:00,11271.0 -2017-08-04 13:00:00,11332.0 -2017-08-04 14:00:00,11324.0 -2017-08-04 15:00:00,11315.0 -2017-08-04 16:00:00,11207.0 -2017-08-04 17:00:00,11049.0 -2017-08-04 18:00:00,10934.0 -2017-08-04 19:00:00,10779.0 -2017-08-04 20:00:00,10608.0 -2017-08-04 21:00:00,10450.0 -2017-08-04 22:00:00,10639.0 -2017-08-04 23:00:00,10446.0 -2017-08-05 00:00:00,9859.0 -2017-08-03 01:00:00,12560.0 -2017-08-03 02:00:00,11671.0 -2017-08-03 03:00:00,11010.0 -2017-08-03 04:00:00,10567.0 -2017-08-03 05:00:00,10298.0 -2017-08-03 06:00:00,10387.0 -2017-08-03 07:00:00,10881.0 -2017-08-03 08:00:00,11680.0 -2017-08-03 09:00:00,12660.0 -2017-08-03 10:00:00,13595.0 -2017-08-03 11:00:00,14666.0 -2017-08-03 12:00:00,15761.0 -2017-08-03 13:00:00,16514.0 -2017-08-03 14:00:00,16740.0 -2017-08-03 15:00:00,16781.0 -2017-08-03 16:00:00,16421.0 -2017-08-03 17:00:00,16122.0 -2017-08-03 18:00:00,16282.0 -2017-08-03 19:00:00,16086.0 -2017-08-03 20:00:00,15203.0 -2017-08-03 21:00:00,14462.0 -2017-08-03 22:00:00,14259.0 -2017-08-03 23:00:00,13553.0 -2017-08-04 00:00:00,12464.0 -2017-08-02 01:00:00,12508.0 -2017-08-02 02:00:00,11504.0 -2017-08-02 03:00:00,10811.0 -2017-08-02 04:00:00,10340.0 -2017-08-02 05:00:00,10068.0 -2017-08-02 06:00:00,10148.0 -2017-08-02 07:00:00,10632.0 -2017-08-02 08:00:00,11364.0 -2017-08-02 09:00:00,12492.0 -2017-08-02 10:00:00,13560.0 -2017-08-02 11:00:00,14553.0 -2017-08-02 12:00:00,15527.0 -2017-08-02 13:00:00,16112.0 -2017-08-02 14:00:00,16902.0 -2017-08-02 15:00:00,17489.0 -2017-08-02 16:00:00,17846.0 -2017-08-02 17:00:00,17997.0 -2017-08-02 18:00:00,17899.0 -2017-08-02 19:00:00,17440.0 -2017-08-02 20:00:00,16602.0 -2017-08-02 21:00:00,15905.0 -2017-08-02 22:00:00,15581.0 -2017-08-02 23:00:00,14886.0 -2017-08-03 00:00:00,13733.0 -2017-08-01 01:00:00,12008.0 -2017-08-01 02:00:00,11152.0 -2017-08-01 03:00:00,10564.0 -2017-08-01 04:00:00,10136.0 -2017-08-01 05:00:00,9906.0 -2017-08-01 06:00:00,9981.0 -2017-08-01 07:00:00,10525.0 -2017-08-01 08:00:00,11289.0 -2017-08-01 09:00:00,12293.0 -2017-08-01 10:00:00,13278.0 -2017-08-01 11:00:00,14288.0 -2017-08-01 12:00:00,15299.0 -2017-08-01 13:00:00,15950.0 -2017-08-01 14:00:00,16425.0 -2017-08-01 15:00:00,16939.0 -2017-08-01 16:00:00,17096.0 -2017-08-01 17:00:00,17017.0 -2017-08-01 18:00:00,17146.0 -2017-08-01 19:00:00,17219.0 -2017-08-01 20:00:00,16692.0 -2017-08-01 21:00:00,16037.0 -2017-08-01 22:00:00,15637.0 -2017-08-01 23:00:00,15025.0 -2017-08-02 00:00:00,13765.0 -2017-07-31 01:00:00,10973.0 -2017-07-31 02:00:00,10256.0 -2017-07-31 03:00:00,9695.0 -2017-07-31 04:00:00,9403.0 -2017-07-31 05:00:00,9230.0 -2017-07-31 06:00:00,9380.0 -2017-07-31 07:00:00,9869.0 -2017-07-31 08:00:00,10614.0 -2017-07-31 09:00:00,11653.0 -2017-07-31 10:00:00,12691.0 -2017-07-31 11:00:00,13614.0 -2017-07-31 12:00:00,14495.0 -2017-07-31 13:00:00,15111.0 -2017-07-31 14:00:00,15671.0 -2017-07-31 15:00:00,16200.0 -2017-07-31 16:00:00,16604.0 -2017-07-31 17:00:00,16873.0 -2017-07-31 18:00:00,17005.0 -2017-07-31 19:00:00,16885.0 -2017-07-31 20:00:00,16304.0 -2017-07-31 21:00:00,15519.0 -2017-07-31 22:00:00,15088.0 -2017-07-31 23:00:00,14419.0 -2017-08-01 00:00:00,13238.0 -2017-07-30 01:00:00,10136.0 -2017-07-30 02:00:00,9526.0 -2017-07-30 03:00:00,9039.0 -2017-07-30 04:00:00,8701.0 -2017-07-30 05:00:00,8465.0 -2017-07-30 06:00:00,8375.0 -2017-07-30 07:00:00,8314.0 -2017-07-30 08:00:00,8277.0 -2017-07-30 09:00:00,8804.0 -2017-07-30 10:00:00,9637.0 -2017-07-30 11:00:00,10492.0 -2017-07-30 12:00:00,11374.0 -2017-07-30 13:00:00,12011.0 -2017-07-30 14:00:00,12508.0 -2017-07-30 15:00:00,12947.0 -2017-07-30 16:00:00,13332.0 -2017-07-30 17:00:00,13742.0 -2017-07-30 18:00:00,14013.0 -2017-07-30 19:00:00,14111.0 -2017-07-30 20:00:00,13858.0 -2017-07-30 21:00:00,13327.0 -2017-07-30 22:00:00,13009.0 -2017-07-30 23:00:00,12711.0 -2017-07-31 00:00:00,11891.0 -2017-07-29 01:00:00,11086.0 -2017-07-29 02:00:00,10320.0 -2017-07-29 03:00:00,9789.0 -2017-07-29 04:00:00,9401.0 -2017-07-29 05:00:00,9103.0 -2017-07-29 06:00:00,8899.0 -2017-07-29 07:00:00,8929.0 -2017-07-29 08:00:00,9110.0 -2017-07-29 09:00:00,9808.0 -2017-07-29 10:00:00,10561.0 -2017-07-29 11:00:00,11164.0 -2017-07-29 12:00:00,11676.0 -2017-07-29 13:00:00,11966.0 -2017-07-29 14:00:00,12202.0 -2017-07-29 15:00:00,12376.0 -2017-07-29 16:00:00,12579.0 -2017-07-29 17:00:00,12858.0 -2017-07-29 18:00:00,13010.0 -2017-07-29 19:00:00,13007.0 -2017-07-29 20:00:00,12695.0 -2017-07-29 21:00:00,12130.0 -2017-07-29 22:00:00,11755.0 -2017-07-29 23:00:00,11508.0 -2017-07-30 00:00:00,10845.0 -2017-07-28 01:00:00,11617.0 -2017-07-28 02:00:00,10857.0 -2017-07-28 03:00:00,10322.0 -2017-07-28 04:00:00,9983.0 -2017-07-28 05:00:00,9771.0 -2017-07-28 06:00:00,9904.0 -2017-07-28 07:00:00,10372.0 -2017-07-28 08:00:00,11133.0 -2017-07-28 09:00:00,12129.0 -2017-07-28 10:00:00,12950.0 -2017-07-28 11:00:00,13642.0 -2017-07-28 12:00:00,14222.0 -2017-07-28 13:00:00,14643.0 -2017-07-28 14:00:00,14986.0 -2017-07-28 15:00:00,15298.0 -2017-07-28 16:00:00,15498.0 -2017-07-28 17:00:00,15581.0 -2017-07-28 18:00:00,15559.0 -2017-07-28 19:00:00,15298.0 -2017-07-28 20:00:00,14671.0 -2017-07-28 21:00:00,13760.0 -2017-07-28 22:00:00,13232.0 -2017-07-28 23:00:00,12783.0 -2017-07-29 00:00:00,11929.0 -2017-07-27 01:00:00,13324.0 -2017-07-27 02:00:00,12382.0 -2017-07-27 03:00:00,11696.0 -2017-07-27 04:00:00,11208.0 -2017-07-27 05:00:00,10940.0 -2017-07-27 06:00:00,11027.0 -2017-07-27 07:00:00,11584.0 -2017-07-27 08:00:00,12364.0 -2017-07-27 09:00:00,13467.0 -2017-07-27 10:00:00,14398.0 -2017-07-27 11:00:00,15174.0 -2017-07-27 12:00:00,15899.0 -2017-07-27 13:00:00,16500.0 -2017-07-27 14:00:00,16962.0 -2017-07-27 15:00:00,17277.0 -2017-07-27 16:00:00,17429.0 -2017-07-27 17:00:00,17484.0 -2017-07-27 18:00:00,17395.0 -2017-07-27 19:00:00,17043.0 -2017-07-27 20:00:00,16193.0 -2017-07-27 21:00:00,15082.0 -2017-07-27 22:00:00,14322.0 -2017-07-27 23:00:00,13733.0 -2017-07-28 00:00:00,12663.0 -2017-07-26 01:00:00,11421.0 -2017-07-26 02:00:00,10659.0 -2017-07-26 03:00:00,10059.0 -2017-07-26 04:00:00,9703.0 -2017-07-26 05:00:00,9475.0 -2017-07-26 06:00:00,9620.0 -2017-07-26 07:00:00,10114.0 -2017-07-26 08:00:00,10855.0 -2017-07-26 09:00:00,11827.0 -2017-07-26 10:00:00,12692.0 -2017-07-26 11:00:00,13603.0 -2017-07-26 12:00:00,14591.0 -2017-07-26 13:00:00,15550.0 -2017-07-26 14:00:00,16527.0 -2017-07-26 15:00:00,17377.0 -2017-07-26 16:00:00,17628.0 -2017-07-26 17:00:00,17450.0 -2017-07-26 18:00:00,17115.0 -2017-07-26 19:00:00,16749.0 -2017-07-26 20:00:00,16342.0 -2017-07-26 21:00:00,16071.0 -2017-07-26 22:00:00,16014.0 -2017-07-26 23:00:00,15515.0 -2017-07-27 00:00:00,14501.0 -2017-07-25 01:00:00,10709.0 -2017-07-25 02:00:00,10051.0 -2017-07-25 03:00:00,9555.0 -2017-07-25 04:00:00,9256.0 -2017-07-25 05:00:00,9108.0 -2017-07-25 06:00:00,9240.0 -2017-07-25 07:00:00,9656.0 -2017-07-25 08:00:00,10479.0 -2017-07-25 09:00:00,11536.0 -2017-07-25 10:00:00,12348.0 -2017-07-25 11:00:00,12928.0 -2017-07-25 12:00:00,13506.0 -2017-07-25 13:00:00,13958.0 -2017-07-25 14:00:00,14388.0 -2017-07-25 15:00:00,14946.0 -2017-07-25 16:00:00,15395.0 -2017-07-25 17:00:00,15720.0 -2017-07-25 18:00:00,15797.0 -2017-07-25 19:00:00,15455.0 -2017-07-25 20:00:00,14857.0 -2017-07-25 21:00:00,14286.0 -2017-07-25 22:00:00,13958.0 -2017-07-25 23:00:00,13479.0 -2017-07-26 00:00:00,12459.0 -2017-07-24 01:00:00,11513.0 -2017-07-24 02:00:00,10749.0 -2017-07-24 03:00:00,10209.0 -2017-07-24 04:00:00,9862.0 -2017-07-24 05:00:00,9690.0 -2017-07-24 06:00:00,9790.0 -2017-07-24 07:00:00,10325.0 -2017-07-24 08:00:00,10979.0 -2017-07-24 09:00:00,11902.0 -2017-07-24 10:00:00,12530.0 -2017-07-24 11:00:00,13002.0 -2017-07-24 12:00:00,13339.0 -2017-07-24 13:00:00,13552.0 -2017-07-24 14:00:00,13731.0 -2017-07-24 15:00:00,14005.0 -2017-07-24 16:00:00,14214.0 -2017-07-24 17:00:00,14411.0 -2017-07-24 18:00:00,14485.0 -2017-07-24 19:00:00,14386.0 -2017-07-24 20:00:00,13902.0 -2017-07-24 21:00:00,13271.0 -2017-07-24 22:00:00,12973.0 -2017-07-24 23:00:00,12597.0 -2017-07-25 00:00:00,11690.0 -2017-07-23 01:00:00,12618.0 -2017-07-23 02:00:00,11727.0 -2017-07-23 03:00:00,11058.0 -2017-07-23 04:00:00,10607.0 -2017-07-23 05:00:00,10288.0 -2017-07-23 06:00:00,10116.0 -2017-07-23 07:00:00,9982.0 -2017-07-23 08:00:00,10100.0 -2017-07-23 09:00:00,10827.0 -2017-07-23 10:00:00,11985.0 -2017-07-23 11:00:00,13313.0 -2017-07-23 12:00:00,14555.0 -2017-07-23 13:00:00,15500.0 -2017-07-23 14:00:00,16122.0 -2017-07-23 15:00:00,16632.0 -2017-07-23 16:00:00,16837.0 -2017-07-23 17:00:00,16796.0 -2017-07-23 18:00:00,16207.0 -2017-07-23 19:00:00,15026.0 -2017-07-23 20:00:00,14318.0 -2017-07-23 21:00:00,13765.0 -2017-07-23 22:00:00,13477.0 -2017-07-23 23:00:00,13102.0 -2017-07-24 00:00:00,12413.0 -2017-07-22 01:00:00,13141.0 -2017-07-22 02:00:00,12146.0 -2017-07-22 03:00:00,11437.0 -2017-07-22 04:00:00,11009.0 -2017-07-22 05:00:00,10693.0 -2017-07-22 06:00:00,10612.0 -2017-07-22 07:00:00,10714.0 -2017-07-22 08:00:00,10981.0 -2017-07-22 09:00:00,11264.0 -2017-07-22 10:00:00,11776.0 -2017-07-22 11:00:00,12550.0 -2017-07-22 12:00:00,13403.0 -2017-07-22 13:00:00,14200.0 -2017-07-22 14:00:00,14900.0 -2017-07-22 15:00:00,15465.0 -2017-07-22 16:00:00,15880.0 -2017-07-22 17:00:00,16112.0 -2017-07-22 18:00:00,16411.0 -2017-07-22 19:00:00,16397.0 -2017-07-22 20:00:00,16074.0 -2017-07-22 21:00:00,15400.0 -2017-07-22 22:00:00,14981.0 -2017-07-22 23:00:00,14516.0 -2017-07-23 00:00:00,13624.0 -2017-07-21 01:00:00,13408.0 -2017-07-21 02:00:00,12371.0 -2017-07-21 03:00:00,11674.0 -2017-07-21 04:00:00,11241.0 -2017-07-21 05:00:00,10935.0 -2017-07-21 06:00:00,10979.0 -2017-07-21 07:00:00,11426.0 -2017-07-21 08:00:00,12215.0 -2017-07-21 09:00:00,13405.0 -2017-07-21 10:00:00,14596.0 -2017-07-21 11:00:00,15581.0 -2017-07-21 12:00:00,16459.0 -2017-07-21 13:00:00,17122.0 -2017-07-21 14:00:00,17679.0 -2017-07-21 15:00:00,18309.0 -2017-07-21 16:00:00,18626.0 -2017-07-21 17:00:00,18506.0 -2017-07-21 18:00:00,17952.0 -2017-07-21 19:00:00,17237.0 -2017-07-21 20:00:00,16656.0 -2017-07-21 21:00:00,16227.0 -2017-07-21 22:00:00,15986.0 -2017-07-21 23:00:00,15187.0 -2017-07-22 00:00:00,14179.0 -2017-07-20 01:00:00,12910.0 -2017-07-20 02:00:00,11914.0 -2017-07-20 03:00:00,11268.0 -2017-07-20 04:00:00,10823.0 -2017-07-20 05:00:00,10582.0 -2017-07-20 06:00:00,10685.0 -2017-07-20 07:00:00,11306.0 -2017-07-20 08:00:00,12145.0 -2017-07-20 09:00:00,12827.0 -2017-07-20 10:00:00,13270.0 -2017-07-20 11:00:00,13725.0 -2017-07-20 12:00:00,14662.0 -2017-07-20 13:00:00,15840.0 -2017-07-20 14:00:00,16936.0 -2017-07-20 15:00:00,17936.0 -2017-07-20 16:00:00,18287.0 -2017-07-20 17:00:00,18023.0 -2017-07-20 18:00:00,17857.0 -2017-07-20 19:00:00,17713.0 -2017-07-20 20:00:00,17209.0 -2017-07-20 21:00:00,16694.0 -2017-07-20 22:00:00,16341.0 -2017-07-20 23:00:00,15811.0 -2017-07-21 00:00:00,14627.0 -2017-07-19 01:00:00,13511.0 -2017-07-19 02:00:00,12507.0 -2017-07-19 03:00:00,11708.0 -2017-07-19 04:00:00,11056.0 -2017-07-19 05:00:00,10726.0 -2017-07-19 06:00:00,10777.0 -2017-07-19 07:00:00,11168.0 -2017-07-19 08:00:00,12099.0 -2017-07-19 09:00:00,13403.0 -2017-07-19 10:00:00,14618.0 -2017-07-19 11:00:00,15613.0 -2017-07-19 12:00:00,16660.0 -2017-07-19 13:00:00,17533.0 -2017-07-19 14:00:00,18175.0 -2017-07-19 15:00:00,18650.0 -2017-07-19 16:00:00,18840.0 -2017-07-19 17:00:00,18836.0 -2017-07-19 18:00:00,18842.0 -2017-07-19 19:00:00,18521.0 -2017-07-19 20:00:00,17976.0 -2017-07-19 21:00:00,17209.0 -2017-07-19 22:00:00,16660.0 -2017-07-19 23:00:00,15843.0 -2017-07-20 00:00:00,14197.0 -2017-07-18 01:00:00,10855.0 -2017-07-18 02:00:00,10054.0 -2017-07-18 03:00:00,9527.0 -2017-07-18 04:00:00,9190.0 -2017-07-18 05:00:00,9013.0 -2017-07-18 06:00:00,9100.0 -2017-07-18 07:00:00,9529.0 -2017-07-18 08:00:00,10349.0 -2017-07-18 09:00:00,11450.0 -2017-07-18 10:00:00,12359.0 -2017-07-18 11:00:00,13119.0 -2017-07-18 12:00:00,13911.0 -2017-07-18 13:00:00,14605.0 -2017-07-18 14:00:00,15243.0 -2017-07-18 15:00:00,15967.0 -2017-07-18 16:00:00,16653.0 -2017-07-18 17:00:00,17233.0 -2017-07-18 18:00:00,17649.0 -2017-07-18 19:00:00,17604.0 -2017-07-18 20:00:00,17259.0 -2017-07-18 21:00:00,16790.0 -2017-07-18 22:00:00,16448.0 -2017-07-18 23:00:00,15973.0 -2017-07-19 00:00:00,14761.0 -2017-07-17 01:00:00,9602.0 -2017-07-17 02:00:00,8982.0 -2017-07-17 03:00:00,8607.0 -2017-07-17 04:00:00,8387.0 -2017-07-17 05:00:00,8325.0 -2017-07-17 06:00:00,8518.0 -2017-07-17 07:00:00,8926.0 -2017-07-17 08:00:00,9741.0 -2017-07-17 09:00:00,10728.0 -2017-07-17 10:00:00,11548.0 -2017-07-17 11:00:00,12181.0 -2017-07-17 12:00:00,12715.0 -2017-07-17 13:00:00,13136.0 -2017-07-17 14:00:00,13509.0 -2017-07-17 15:00:00,13950.0 -2017-07-17 16:00:00,14319.0 -2017-07-17 17:00:00,14616.0 -2017-07-17 18:00:00,14821.0 -2017-07-17 19:00:00,14822.0 -2017-07-17 20:00:00,14381.0 -2017-07-17 21:00:00,13766.0 -2017-07-17 22:00:00,13320.0 -2017-07-17 23:00:00,12914.0 -2017-07-18 00:00:00,11931.0 -2017-07-16 01:00:00,11042.0 -2017-07-16 02:00:00,10399.0 -2017-07-16 03:00:00,9816.0 -2017-07-16 04:00:00,9441.0 -2017-07-16 05:00:00,9267.0 -2017-07-16 06:00:00,9178.0 -2017-07-16 07:00:00,9028.0 -2017-07-16 08:00:00,8993.0 -2017-07-16 09:00:00,9372.0 -2017-07-16 10:00:00,9839.0 -2017-07-16 11:00:00,10297.0 -2017-07-16 12:00:00,10777.0 -2017-07-16 13:00:00,11051.0 -2017-07-16 14:00:00,11147.0 -2017-07-16 15:00:00,11286.0 -2017-07-16 16:00:00,11402.0 -2017-07-16 17:00:00,11567.0 -2017-07-16 18:00:00,11818.0 -2017-07-16 19:00:00,11874.0 -2017-07-16 20:00:00,11662.0 -2017-07-16 21:00:00,11219.0 -2017-07-16 22:00:00,10982.0 -2017-07-16 23:00:00,10858.0 -2017-07-17 00:00:00,10316.0 -2017-07-15 01:00:00,10296.0 -2017-07-15 02:00:00,9631.0 -2017-07-15 03:00:00,9136.0 -2017-07-15 04:00:00,8802.0 -2017-07-15 05:00:00,8634.0 -2017-07-15 06:00:00,8565.0 -2017-07-15 07:00:00,8611.0 -2017-07-15 08:00:00,8850.0 -2017-07-15 09:00:00,9505.0 -2017-07-15 10:00:00,10291.0 -2017-07-15 11:00:00,11009.0 -2017-07-15 12:00:00,11647.0 -2017-07-15 13:00:00,12090.0 -2017-07-15 14:00:00,12504.0 -2017-07-15 15:00:00,12833.0 -2017-07-15 16:00:00,13208.0 -2017-07-15 17:00:00,13624.0 -2017-07-15 18:00:00,13767.0 -2017-07-15 19:00:00,13614.0 -2017-07-15 20:00:00,13348.0 -2017-07-15 21:00:00,13030.0 -2017-07-15 22:00:00,12722.0 -2017-07-15 23:00:00,12483.0 -2017-07-16 00:00:00,11818.0 -2017-07-14 01:00:00,12371.0 -2017-07-14 02:00:00,11408.0 -2017-07-14 03:00:00,10689.0 -2017-07-14 04:00:00,10197.0 -2017-07-14 05:00:00,9933.0 -2017-07-14 06:00:00,9976.0 -2017-07-14 07:00:00,10296.0 -2017-07-14 08:00:00,10950.0 -2017-07-14 09:00:00,11718.0 -2017-07-14 10:00:00,12322.0 -2017-07-14 11:00:00,12660.0 -2017-07-14 12:00:00,12866.0 -2017-07-14 13:00:00,13043.0 -2017-07-14 14:00:00,13194.0 -2017-07-14 15:00:00,13391.0 -2017-07-14 16:00:00,13394.0 -2017-07-14 17:00:00,13210.0 -2017-07-14 18:00:00,13094.0 -2017-07-14 19:00:00,12731.0 -2017-07-14 20:00:00,12244.0 -2017-07-14 21:00:00,11935.0 -2017-07-14 22:00:00,11890.0 -2017-07-14 23:00:00,11778.0 -2017-07-15 00:00:00,11055.0 -2017-07-13 01:00:00,12960.0 -2017-07-13 02:00:00,12172.0 -2017-07-13 03:00:00,11476.0 -2017-07-13 04:00:00,10968.0 -2017-07-13 05:00:00,10697.0 -2017-07-13 06:00:00,10806.0 -2017-07-13 07:00:00,11341.0 -2017-07-13 08:00:00,12160.0 -2017-07-13 09:00:00,13089.0 -2017-07-13 10:00:00,13742.0 -2017-07-13 11:00:00,14172.0 -2017-07-13 12:00:00,14590.0 -2017-07-13 13:00:00,15093.0 -2017-07-13 14:00:00,15535.0 -2017-07-13 15:00:00,16056.0 -2017-07-13 16:00:00,16526.0 -2017-07-13 17:00:00,16790.0 -2017-07-13 18:00:00,16963.0 -2017-07-13 19:00:00,16864.0 -2017-07-13 20:00:00,16550.0 -2017-07-13 21:00:00,15864.0 -2017-07-13 22:00:00,15207.0 -2017-07-13 23:00:00,14724.0 -2017-07-14 00:00:00,13590.0 -2017-07-12 01:00:00,13155.0 -2017-07-12 02:00:00,12256.0 -2017-07-12 03:00:00,11743.0 -2017-07-12 04:00:00,11326.0 -2017-07-12 05:00:00,11120.0 -2017-07-12 06:00:00,11081.0 -2017-07-12 07:00:00,11568.0 -2017-07-12 08:00:00,12346.0 -2017-07-12 09:00:00,13382.0 -2017-07-12 10:00:00,14046.0 -2017-07-12 11:00:00,14311.0 -2017-07-12 12:00:00,14355.0 -2017-07-12 13:00:00,14353.0 -2017-07-12 14:00:00,14258.0 -2017-07-12 15:00:00,14163.0 -2017-07-12 16:00:00,14284.0 -2017-07-12 17:00:00,14609.0 -2017-07-12 18:00:00,14839.0 -2017-07-12 19:00:00,14908.0 -2017-07-12 20:00:00,14941.0 -2017-07-12 21:00:00,14981.0 -2017-07-12 22:00:00,14993.0 -2017-07-12 23:00:00,14739.0 -2017-07-13 00:00:00,13906.0 -2017-07-11 01:00:00,12362.0 -2017-07-11 02:00:00,11437.0 -2017-07-11 03:00:00,10800.0 -2017-07-11 04:00:00,10394.0 -2017-07-11 05:00:00,10172.0 -2017-07-11 06:00:00,10296.0 -2017-07-11 07:00:00,10739.0 -2017-07-11 08:00:00,11712.0 -2017-07-11 09:00:00,12885.0 -2017-07-11 10:00:00,13975.0 -2017-07-11 11:00:00,14796.0 -2017-07-11 12:00:00,15456.0 -2017-07-11 13:00:00,16049.0 -2017-07-11 14:00:00,16772.0 -2017-07-11 15:00:00,17401.0 -2017-07-11 16:00:00,17535.0 -2017-07-11 17:00:00,17114.0 -2017-07-11 18:00:00,16628.0 -2017-07-11 19:00:00,16378.0 -2017-07-11 20:00:00,16089.0 -2017-07-11 21:00:00,15750.0 -2017-07-11 22:00:00,15564.0 -2017-07-11 23:00:00,15224.0 -2017-07-12 00:00:00,14296.0 -2017-07-10 01:00:00,12523.0 -2017-07-10 02:00:00,11705.0 -2017-07-10 03:00:00,11136.0 -2017-07-10 04:00:00,10782.0 -2017-07-10 05:00:00,10660.0 -2017-07-10 06:00:00,10790.0 -2017-07-10 07:00:00,11243.0 -2017-07-10 08:00:00,11847.0 -2017-07-10 09:00:00,12408.0 -2017-07-10 10:00:00,12836.0 -2017-07-10 11:00:00,13402.0 -2017-07-10 12:00:00,14194.0 -2017-07-10 13:00:00,14705.0 -2017-07-10 14:00:00,15056.0 -2017-07-10 15:00:00,15662.0 -2017-07-10 16:00:00,16147.0 -2017-07-10 17:00:00,15987.0 -2017-07-10 18:00:00,15924.0 -2017-07-10 19:00:00,16101.0 -2017-07-10 20:00:00,15862.0 -2017-07-10 21:00:00,15439.0 -2017-07-10 22:00:00,15028.0 -2017-07-10 23:00:00,14764.0 -2017-07-11 00:00:00,13570.0 -2017-07-09 01:00:00,10044.0 -2017-07-09 02:00:00,9394.0 -2017-07-09 03:00:00,8855.0 -2017-07-09 04:00:00,8537.0 -2017-07-09 05:00:00,8276.0 -2017-07-09 06:00:00,8168.0 -2017-07-09 07:00:00,7949.0 -2017-07-09 08:00:00,8091.0 -2017-07-09 09:00:00,8615.0 -2017-07-09 10:00:00,9404.0 -2017-07-09 11:00:00,10362.0 -2017-07-09 12:00:00,11197.0 -2017-07-09 13:00:00,11990.0 -2017-07-09 14:00:00,12641.0 -2017-07-09 15:00:00,13319.0 -2017-07-09 16:00:00,13861.0 -2017-07-09 17:00:00,14219.0 -2017-07-09 18:00:00,14631.0 -2017-07-09 19:00:00,14716.0 -2017-07-09 20:00:00,14550.0 -2017-07-09 21:00:00,14257.0 -2017-07-09 22:00:00,14220.0 -2017-07-09 23:00:00,14133.0 -2017-07-10 00:00:00,13452.0 -2017-07-08 01:00:00,11519.0 -2017-07-08 02:00:00,10625.0 -2017-07-08 03:00:00,9953.0 -2017-07-08 04:00:00,9503.0 -2017-07-08 05:00:00,9224.0 -2017-07-08 06:00:00,9108.0 -2017-07-08 07:00:00,9016.0 -2017-07-08 08:00:00,9328.0 -2017-07-08 09:00:00,9999.0 -2017-07-08 10:00:00,10749.0 -2017-07-08 11:00:00,11433.0 -2017-07-08 12:00:00,11973.0 -2017-07-08 13:00:00,12357.0 -2017-07-08 14:00:00,12567.0 -2017-07-08 15:00:00,12730.0 -2017-07-08 16:00:00,12908.0 -2017-07-08 17:00:00,13149.0 -2017-07-08 18:00:00,13256.0 -2017-07-08 19:00:00,13179.0 -2017-07-08 20:00:00,12901.0 -2017-07-08 21:00:00,12321.0 -2017-07-08 22:00:00,11790.0 -2017-07-08 23:00:00,11515.0 -2017-07-09 00:00:00,10830.0 -2017-07-07 01:00:00,14605.0 -2017-07-07 02:00:00,13542.0 -2017-07-07 03:00:00,12736.0 -2017-07-07 04:00:00,12047.0 -2017-07-07 05:00:00,11575.0 -2017-07-07 06:00:00,11603.0 -2017-07-07 07:00:00,12012.0 -2017-07-07 08:00:00,12857.0 -2017-07-07 09:00:00,13850.0 -2017-07-07 10:00:00,14686.0 -2017-07-07 11:00:00,15479.0 -2017-07-07 12:00:00,16434.0 -2017-07-07 13:00:00,17034.0 -2017-07-07 14:00:00,17336.0 -2017-07-07 15:00:00,17648.0 -2017-07-07 16:00:00,17730.0 -2017-07-07 17:00:00,17586.0 -2017-07-07 18:00:00,17371.0 -2017-07-07 19:00:00,16943.0 -2017-07-07 20:00:00,16099.0 -2017-07-07 21:00:00,15090.0 -2017-07-07 22:00:00,14375.0 -2017-07-07 23:00:00,13895.0 -2017-07-08 00:00:00,12760.0 -2017-07-06 01:00:00,12322.0 -2017-07-06 02:00:00,11422.0 -2017-07-06 03:00:00,10809.0 -2017-07-06 04:00:00,10340.0 -2017-07-06 05:00:00,10095.0 -2017-07-06 06:00:00,10149.0 -2017-07-06 07:00:00,10531.0 -2017-07-06 08:00:00,11523.0 -2017-07-06 09:00:00,12904.0 -2017-07-06 10:00:00,14201.0 -2017-07-06 11:00:00,15371.0 -2017-07-06 12:00:00,16453.0 -2017-07-06 13:00:00,17311.0 -2017-07-06 14:00:00,18052.0 -2017-07-06 15:00:00,18687.0 -2017-07-06 16:00:00,19054.0 -2017-07-06 17:00:00,19269.0 -2017-07-06 18:00:00,19408.0 -2017-07-06 19:00:00,19354.0 -2017-07-06 20:00:00,19005.0 -2017-07-06 21:00:00,18389.0 -2017-07-06 22:00:00,17753.0 -2017-07-06 23:00:00,17205.0 -2017-07-07 00:00:00,15918.0 -2017-07-05 01:00:00,10778.0 -2017-07-05 02:00:00,10057.0 -2017-07-05 03:00:00,9530.0 -2017-07-05 04:00:00,9165.0 -2017-07-05 05:00:00,8984.0 -2017-07-05 06:00:00,9122.0 -2017-07-05 07:00:00,9573.0 -2017-07-05 08:00:00,10500.0 -2017-07-05 09:00:00,11680.0 -2017-07-05 10:00:00,12759.0 -2017-07-05 11:00:00,13781.0 -2017-07-05 12:00:00,14845.0 -2017-07-05 13:00:00,15680.0 -2017-07-05 14:00:00,16392.0 -2017-07-05 15:00:00,17044.0 -2017-07-05 16:00:00,17404.0 -2017-07-05 17:00:00,17531.0 -2017-07-05 18:00:00,17527.0 -2017-07-05 19:00:00,17020.0 -2017-07-05 20:00:00,16187.0 -2017-07-05 21:00:00,15415.0 -2017-07-05 22:00:00,15061.0 -2017-07-05 23:00:00,14533.0 -2017-07-06 00:00:00,13470.0 -2017-07-04 01:00:00,10085.0 -2017-07-04 02:00:00,9421.0 -2017-07-04 03:00:00,8945.0 -2017-07-04 04:00:00,8604.0 -2017-07-04 05:00:00,8420.0 -2017-07-04 06:00:00,8384.0 -2017-07-04 07:00:00,8306.0 -2017-07-04 08:00:00,8469.0 -2017-07-04 09:00:00,8993.0 -2017-07-04 10:00:00,9856.0 -2017-07-04 11:00:00,10827.0 -2017-07-04 12:00:00,11836.0 -2017-07-04 13:00:00,12708.0 -2017-07-04 14:00:00,13450.0 -2017-07-04 15:00:00,13906.0 -2017-07-04 16:00:00,14159.0 -2017-07-04 17:00:00,14202.0 -2017-07-04 18:00:00,14150.0 -2017-07-04 19:00:00,13960.0 -2017-07-04 20:00:00,13443.0 -2017-07-04 21:00:00,12768.0 -2017-07-04 22:00:00,12282.0 -2017-07-04 23:00:00,11939.0 -2017-07-05 00:00:00,11463.0 -2017-07-03 01:00:00,11112.0 -2017-07-03 02:00:00,10347.0 -2017-07-03 03:00:00,9819.0 -2017-07-03 04:00:00,9438.0 -2017-07-03 05:00:00,9250.0 -2017-07-03 06:00:00,9267.0 -2017-07-03 07:00:00,9503.0 -2017-07-03 08:00:00,10034.0 -2017-07-03 09:00:00,10853.0 -2017-07-03 10:00:00,11700.0 -2017-07-03 11:00:00,12530.0 -2017-07-03 12:00:00,13310.0 -2017-07-03 13:00:00,13901.0 -2017-07-03 14:00:00,14348.0 -2017-07-03 15:00:00,14725.0 -2017-07-03 16:00:00,14912.0 -2017-07-03 17:00:00,14981.0 -2017-07-03 18:00:00,14588.0 -2017-07-03 19:00:00,13889.0 -2017-07-03 20:00:00,13028.0 -2017-07-03 21:00:00,12300.0 -2017-07-03 22:00:00,11857.0 -2017-07-03 23:00:00,11544.0 -2017-07-04 00:00:00,10858.0 -2017-07-02 01:00:00,10487.0 -2017-07-02 02:00:00,9710.0 -2017-07-02 03:00:00,9153.0 -2017-07-02 04:00:00,8712.0 -2017-07-02 05:00:00,8472.0 -2017-07-02 06:00:00,8301.0 -2017-07-02 07:00:00,8132.0 -2017-07-02 08:00:00,8244.0 -2017-07-02 09:00:00,8847.0 -2017-07-02 10:00:00,9603.0 -2017-07-02 11:00:00,10424.0 -2017-07-02 12:00:00,11161.0 -2017-07-02 13:00:00,11944.0 -2017-07-02 14:00:00,12601.0 -2017-07-02 15:00:00,13334.0 -2017-07-02 16:00:00,14119.0 -2017-07-02 17:00:00,14698.0 -2017-07-02 18:00:00,14893.0 -2017-07-02 19:00:00,14799.0 -2017-07-02 20:00:00,14167.0 -2017-07-02 21:00:00,13395.0 -2017-07-02 22:00:00,12910.0 -2017-07-02 23:00:00,12657.0 -2017-07-03 00:00:00,11943.0 -2017-07-01 01:00:00,11781.0 -2017-07-01 02:00:00,10873.0 -2017-07-01 03:00:00,10203.0 -2017-07-01 04:00:00,9625.0 -2017-07-01 05:00:00,9335.0 -2017-07-01 06:00:00,9235.0 -2017-07-01 07:00:00,9108.0 -2017-07-01 08:00:00,9400.0 -2017-07-01 09:00:00,10117.0 -2017-07-01 10:00:00,11067.0 -2017-07-01 11:00:00,12045.0 -2017-07-01 12:00:00,12835.0 -2017-07-01 13:00:00,13351.0 -2017-07-01 14:00:00,13771.0 -2017-07-01 15:00:00,13933.0 -2017-07-01 16:00:00,14019.0 -2017-07-01 17:00:00,14034.0 -2017-07-01 18:00:00,14108.0 -2017-07-01 19:00:00,13955.0 -2017-07-01 20:00:00,13530.0 -2017-07-01 21:00:00,12817.0 -2017-07-01 22:00:00,12235.0 -2017-07-01 23:00:00,11967.0 -2017-07-02 00:00:00,11228.0 -2017-06-30 01:00:00,11161.0 -2017-06-30 02:00:00,10392.0 -2017-06-30 03:00:00,9852.0 -2017-06-30 04:00:00,9449.0 -2017-06-30 05:00:00,9275.0 -2017-06-30 06:00:00,9427.0 -2017-06-30 07:00:00,9807.0 -2017-06-30 08:00:00,10683.0 -2017-06-30 09:00:00,11717.0 -2017-06-30 10:00:00,12632.0 -2017-06-30 11:00:00,13259.0 -2017-06-30 12:00:00,14024.0 -2017-06-30 13:00:00,14695.0 -2017-06-30 14:00:00,15177.0 -2017-06-30 15:00:00,15505.0 -2017-06-30 16:00:00,15623.0 -2017-06-30 17:00:00,15495.0 -2017-06-30 18:00:00,15354.0 -2017-06-30 19:00:00,15369.0 -2017-06-30 20:00:00,14990.0 -2017-06-30 21:00:00,14478.0 -2017-06-30 22:00:00,14053.0 -2017-06-30 23:00:00,13763.0 -2017-07-01 00:00:00,12837.0 -2017-06-29 01:00:00,10527.0 -2017-06-29 02:00:00,9821.0 -2017-06-29 03:00:00,9392.0 -2017-06-29 04:00:00,9099.0 -2017-06-29 05:00:00,9010.0 -2017-06-29 06:00:00,9193.0 -2017-06-29 07:00:00,9693.0 -2017-06-29 08:00:00,10595.0 -2017-06-29 09:00:00,11607.0 -2017-06-29 10:00:00,12433.0 -2017-06-29 11:00:00,13124.0 -2017-06-29 12:00:00,13793.0 -2017-06-29 13:00:00,14429.0 -2017-06-29 14:00:00,15032.0 -2017-06-29 15:00:00,15372.0 -2017-06-29 16:00:00,15613.0 -2017-06-29 17:00:00,15522.0 -2017-06-29 18:00:00,15252.0 -2017-06-29 19:00:00,14687.0 -2017-06-29 20:00:00,14131.0 -2017-06-29 21:00:00,13768.0 -2017-06-29 22:00:00,13393.0 -2017-06-29 23:00:00,13049.0 -2017-06-30 00:00:00,12160.0 -2017-06-28 01:00:00,9796.0 -2017-06-28 02:00:00,9164.0 -2017-06-28 03:00:00,8751.0 -2017-06-28 04:00:00,8492.0 -2017-06-28 05:00:00,8356.0 -2017-06-28 06:00:00,8526.0 -2017-06-28 07:00:00,8867.0 -2017-06-28 08:00:00,9679.0 -2017-06-28 09:00:00,10516.0 -2017-06-28 10:00:00,11056.0 -2017-06-28 11:00:00,11366.0 -2017-06-28 12:00:00,11651.0 -2017-06-28 13:00:00,11762.0 -2017-06-28 14:00:00,11708.0 -2017-06-28 15:00:00,11930.0 -2017-06-28 16:00:00,12160.0 -2017-06-28 17:00:00,12371.0 -2017-06-28 18:00:00,12507.0 -2017-06-28 19:00:00,12364.0 -2017-06-28 20:00:00,12174.0 -2017-06-28 21:00:00,12140.0 -2017-06-28 22:00:00,12333.0 -2017-06-28 23:00:00,12148.0 -2017-06-29 00:00:00,11367.0 -2017-06-27 01:00:00,9153.0 -2017-06-27 02:00:00,8616.0 -2017-06-27 03:00:00,8264.0 -2017-06-27 04:00:00,8052.0 -2017-06-27 05:00:00,7931.0 -2017-06-27 06:00:00,8057.0 -2017-06-27 07:00:00,8444.0 -2017-06-27 08:00:00,9170.0 -2017-06-27 09:00:00,10089.0 -2017-06-27 10:00:00,10751.0 -2017-06-27 11:00:00,11184.0 -2017-06-27 12:00:00,11562.0 -2017-06-27 13:00:00,11801.0 -2017-06-27 14:00:00,11954.0 -2017-06-27 15:00:00,12212.0 -2017-06-27 16:00:00,12342.0 -2017-06-27 17:00:00,12451.0 -2017-06-27 18:00:00,12539.0 -2017-06-27 19:00:00,12498.0 -2017-06-27 20:00:00,12228.0 -2017-06-27 21:00:00,11875.0 -2017-06-27 22:00:00,11624.0 -2017-06-27 23:00:00,11449.0 -2017-06-28 00:00:00,10677.0 -2017-06-26 01:00:00,8682.0 -2017-06-26 02:00:00,8241.0 -2017-06-26 03:00:00,7946.0 -2017-06-26 04:00:00,7739.0 -2017-06-26 05:00:00,7699.0 -2017-06-26 06:00:00,7921.0 -2017-06-26 07:00:00,8322.0 -2017-06-26 08:00:00,9137.0 -2017-06-26 09:00:00,10080.0 -2017-06-26 10:00:00,10694.0 -2017-06-26 11:00:00,11101.0 -2017-06-26 12:00:00,11424.0 -2017-06-26 13:00:00,11614.0 -2017-06-26 14:00:00,11717.0 -2017-06-26 15:00:00,11833.0 -2017-06-26 16:00:00,11842.0 -2017-06-26 17:00:00,11800.0 -2017-06-26 18:00:00,11711.0 -2017-06-26 19:00:00,11471.0 -2017-06-26 20:00:00,11024.0 -2017-06-26 21:00:00,10749.0 -2017-06-26 22:00:00,10712.0 -2017-06-26 23:00:00,10613.0 -2017-06-27 00:00:00,9909.0 -2017-06-25 01:00:00,9036.0 -2017-06-25 02:00:00,8532.0 -2017-06-25 03:00:00,8182.0 -2017-06-25 04:00:00,7899.0 -2017-06-25 05:00:00,7728.0 -2017-06-25 06:00:00,7626.0 -2017-06-25 07:00:00,7481.0 -2017-06-25 08:00:00,7605.0 -2017-06-25 09:00:00,7984.0 -2017-06-25 10:00:00,8494.0 -2017-06-25 11:00:00,8951.0 -2017-06-25 12:00:00,9305.0 -2017-06-25 13:00:00,9502.0 -2017-06-25 14:00:00,9576.0 -2017-06-25 15:00:00,9716.0 -2017-06-25 16:00:00,9758.0 -2017-06-25 17:00:00,9803.0 -2017-06-25 18:00:00,9903.0 -2017-06-25 19:00:00,9968.0 -2017-06-25 20:00:00,9834.0 -2017-06-25 21:00:00,9622.0 -2017-06-25 22:00:00,9639.0 -2017-06-25 23:00:00,9712.0 -2017-06-26 00:00:00,9272.0 -2017-06-24 01:00:00,10871.0 -2017-06-24 02:00:00,10049.0 -2017-06-24 03:00:00,9462.0 -2017-06-24 04:00:00,9092.0 -2017-06-24 05:00:00,8782.0 -2017-06-24 06:00:00,8727.0 -2017-06-24 07:00:00,8670.0 -2017-06-24 08:00:00,9065.0 -2017-06-24 09:00:00,9697.0 -2017-06-24 10:00:00,10357.0 -2017-06-24 11:00:00,10971.0 -2017-06-24 12:00:00,11333.0 -2017-06-24 13:00:00,11461.0 -2017-06-24 14:00:00,11462.0 -2017-06-24 15:00:00,11409.0 -2017-06-24 16:00:00,11344.0 -2017-06-24 17:00:00,11370.0 -2017-06-24 18:00:00,11378.0 -2017-06-24 19:00:00,11242.0 -2017-06-24 20:00:00,10964.0 -2017-06-24 21:00:00,10479.0 -2017-06-24 22:00:00,10254.0 -2017-06-24 23:00:00,10118.0 -2017-06-25 00:00:00,9612.0 -2017-06-23 01:00:00,13586.0 -2017-06-23 02:00:00,12627.0 -2017-06-23 03:00:00,11966.0 -2017-06-23 04:00:00,11525.0 -2017-06-23 05:00:00,11315.0 -2017-06-23 06:00:00,11343.0 -2017-06-23 07:00:00,11475.0 -2017-06-23 08:00:00,11899.0 -2017-06-23 09:00:00,12470.0 -2017-06-23 10:00:00,12905.0 -2017-06-23 11:00:00,13236.0 -2017-06-23 12:00:00,13820.0 -2017-06-23 13:00:00,14243.0 -2017-06-23 14:00:00,14670.0 -2017-06-23 15:00:00,15045.0 -2017-06-23 16:00:00,15277.0 -2017-06-23 17:00:00,15507.0 -2017-06-23 18:00:00,15623.0 -2017-06-23 19:00:00,15313.0 -2017-06-23 20:00:00,14622.0 -2017-06-23 21:00:00,13880.0 -2017-06-23 22:00:00,13228.0 -2017-06-23 23:00:00,12819.0 -2017-06-24 00:00:00,11911.0 -2017-06-22 01:00:00,11059.0 -2017-06-22 02:00:00,10365.0 -2017-06-22 03:00:00,9893.0 -2017-06-22 04:00:00,9662.0 -2017-06-22 05:00:00,9748.0 -2017-06-22 06:00:00,10077.0 -2017-06-22 07:00:00,10695.0 -2017-06-22 08:00:00,11711.0 -2017-06-22 09:00:00,12721.0 -2017-06-22 10:00:00,13676.0 -2017-06-22 11:00:00,14766.0 -2017-06-22 12:00:00,15791.0 -2017-06-22 13:00:00,16620.0 -2017-06-22 14:00:00,17047.0 -2017-06-22 15:00:00,17249.0 -2017-06-22 16:00:00,17891.0 -2017-06-22 17:00:00,18189.0 -2017-06-22 18:00:00,18002.0 -2017-06-22 19:00:00,17805.0 -2017-06-22 20:00:00,17469.0 -2017-06-22 21:00:00,16894.0 -2017-06-22 22:00:00,16490.0 -2017-06-22 23:00:00,16008.0 -2017-06-23 00:00:00,14844.0 -2017-06-21 01:00:00,9798.0 -2017-06-21 02:00:00,9188.0 -2017-06-21 03:00:00,8776.0 -2017-06-21 04:00:00,8524.0 -2017-06-21 05:00:00,8419.0 -2017-06-21 06:00:00,8583.0 -2017-06-21 07:00:00,8917.0 -2017-06-21 08:00:00,9851.0 -2017-06-21 09:00:00,10822.0 -2017-06-21 10:00:00,11554.0 -2017-06-21 11:00:00,12049.0 -2017-06-21 12:00:00,12470.0 -2017-06-21 13:00:00,12819.0 -2017-06-21 14:00:00,13126.0 -2017-06-21 15:00:00,13550.0 -2017-06-21 16:00:00,13884.0 -2017-06-21 17:00:00,13775.0 -2017-06-21 18:00:00,13706.0 -2017-06-21 19:00:00,13625.0 -2017-06-21 20:00:00,13276.0 -2017-06-21 21:00:00,13005.0 -2017-06-21 22:00:00,12938.0 -2017-06-21 23:00:00,12768.0 -2017-06-22 00:00:00,11953.0 -2017-06-20 01:00:00,9942.0 -2017-06-20 02:00:00,9331.0 -2017-06-20 03:00:00,8912.0 -2017-06-20 04:00:00,8675.0 -2017-06-20 05:00:00,8557.0 -2017-06-20 06:00:00,8661.0 -2017-06-20 07:00:00,9043.0 -2017-06-20 08:00:00,9997.0 -2017-06-20 09:00:00,10953.0 -2017-06-20 10:00:00,11708.0 -2017-06-20 11:00:00,12283.0 -2017-06-20 12:00:00,12777.0 -2017-06-20 13:00:00,13173.0 -2017-06-20 14:00:00,13350.0 -2017-06-20 15:00:00,13432.0 -2017-06-20 16:00:00,13364.0 -2017-06-20 17:00:00,12939.0 -2017-06-20 18:00:00,12458.0 -2017-06-20 19:00:00,12258.0 -2017-06-20 20:00:00,12072.0 -2017-06-20 21:00:00,11773.0 -2017-06-20 22:00:00,11583.0 -2017-06-20 23:00:00,11436.0 -2017-06-21 00:00:00,10669.0 -2017-06-19 01:00:00,10495.0 -2017-06-19 02:00:00,9817.0 -2017-06-19 03:00:00,9355.0 -2017-06-19 04:00:00,9089.0 -2017-06-19 05:00:00,8922.0 -2017-06-19 06:00:00,9107.0 -2017-06-19 07:00:00,9480.0 -2017-06-19 08:00:00,10452.0 -2017-06-19 09:00:00,11399.0 -2017-06-19 10:00:00,12065.0 -2017-06-19 11:00:00,12789.0 -2017-06-19 12:00:00,13390.0 -2017-06-19 13:00:00,13722.0 -2017-06-19 14:00:00,14046.0 -2017-06-19 15:00:00,14313.0 -2017-06-19 16:00:00,14265.0 -2017-06-19 17:00:00,13844.0 -2017-06-19 18:00:00,13315.0 -2017-06-19 19:00:00,12921.0 -2017-06-19 20:00:00,12442.0 -2017-06-19 21:00:00,12059.0 -2017-06-19 22:00:00,11870.0 -2017-06-19 23:00:00,11668.0 -2017-06-20 00:00:00,10853.0 -2017-06-18 01:00:00,11910.0 -2017-06-18 02:00:00,11101.0 -2017-06-18 03:00:00,10557.0 -2017-06-18 04:00:00,10151.0 -2017-06-18 05:00:00,9959.0 -2017-06-18 06:00:00,9808.0 -2017-06-18 07:00:00,9705.0 -2017-06-18 08:00:00,9701.0 -2017-06-18 09:00:00,10189.0 -2017-06-18 10:00:00,11027.0 -2017-06-18 11:00:00,11706.0 -2017-06-18 12:00:00,12251.0 -2017-06-18 13:00:00,12488.0 -2017-06-18 14:00:00,12598.0 -2017-06-18 15:00:00,12687.0 -2017-06-18 16:00:00,12738.0 -2017-06-18 17:00:00,12765.0 -2017-06-18 18:00:00,12838.0 -2017-06-18 19:00:00,12731.0 -2017-06-18 20:00:00,12344.0 -2017-06-18 21:00:00,11906.0 -2017-06-18 22:00:00,11840.0 -2017-06-18 23:00:00,11856.0 -2017-06-19 00:00:00,11293.0 -2017-06-17 01:00:00,13116.0 -2017-06-17 02:00:00,12217.0 -2017-06-17 03:00:00,11550.0 -2017-06-17 04:00:00,11103.0 -2017-06-17 05:00:00,10771.0 -2017-06-17 06:00:00,10603.0 -2017-06-17 07:00:00,10529.0 -2017-06-17 08:00:00,10570.0 -2017-06-17 09:00:00,11119.0 -2017-06-17 10:00:00,12010.0 -2017-06-17 11:00:00,13197.0 -2017-06-17 12:00:00,13923.0 -2017-06-17 13:00:00,14534.0 -2017-06-17 14:00:00,15078.0 -2017-06-17 15:00:00,15504.0 -2017-06-17 16:00:00,15967.0 -2017-06-17 17:00:00,16061.0 -2017-06-17 18:00:00,15758.0 -2017-06-17 19:00:00,15059.0 -2017-06-17 20:00:00,14529.0 -2017-06-17 21:00:00,14101.0 -2017-06-17 22:00:00,14063.0 -2017-06-17 23:00:00,13592.0 -2017-06-18 00:00:00,12796.0 -2017-06-16 01:00:00,13283.0 -2017-06-16 02:00:00,12288.0 -2017-06-16 03:00:00,11413.0 -2017-06-16 04:00:00,10694.0 -2017-06-16 05:00:00,10379.0 -2017-06-16 06:00:00,10367.0 -2017-06-16 07:00:00,10788.0 -2017-06-16 08:00:00,11404.0 -2017-06-16 09:00:00,11984.0 -2017-06-16 10:00:00,12602.0 -2017-06-16 11:00:00,13105.0 -2017-06-16 12:00:00,13497.0 -2017-06-16 13:00:00,13918.0 -2017-06-16 14:00:00,14680.0 -2017-06-16 15:00:00,15609.0 -2017-06-16 16:00:00,16301.0 -2017-06-16 17:00:00,16907.0 -2017-06-16 18:00:00,17309.0 -2017-06-16 19:00:00,17398.0 -2017-06-16 20:00:00,17083.0 -2017-06-16 21:00:00,16339.0 -2017-06-16 22:00:00,15726.0 -2017-06-16 23:00:00,15213.0 -2017-06-17 00:00:00,14226.0 -2017-06-15 01:00:00,11655.0 -2017-06-15 02:00:00,10965.0 -2017-06-15 03:00:00,10447.0 -2017-06-15 04:00:00,10094.0 -2017-06-15 05:00:00,9960.0 -2017-06-15 06:00:00,10118.0 -2017-06-15 07:00:00,10519.0 -2017-06-15 08:00:00,11489.0 -2017-06-15 09:00:00,12454.0 -2017-06-15 10:00:00,13345.0 -2017-06-15 11:00:00,14253.0 -2017-06-15 12:00:00,14989.0 -2017-06-15 13:00:00,15729.0 -2017-06-15 14:00:00,16568.0 -2017-06-15 15:00:00,17354.0 -2017-06-15 16:00:00,17845.0 -2017-06-15 17:00:00,18254.0 -2017-06-15 18:00:00,18466.0 -2017-06-15 19:00:00,18413.0 -2017-06-15 20:00:00,18121.0 -2017-06-15 21:00:00,17408.0 -2017-06-15 22:00:00,16571.0 -2017-06-15 23:00:00,15831.0 -2017-06-16 00:00:00,14607.0 -2017-06-14 01:00:00,12354.0 -2017-06-14 02:00:00,11488.0 -2017-06-14 03:00:00,10902.0 -2017-06-14 04:00:00,10483.0 -2017-06-14 05:00:00,10283.0 -2017-06-14 06:00:00,10390.0 -2017-06-14 07:00:00,10742.0 -2017-06-14 08:00:00,11867.0 -2017-06-14 09:00:00,13254.0 -2017-06-14 10:00:00,14622.0 -2017-06-14 11:00:00,15904.0 -2017-06-14 12:00:00,17137.0 -2017-06-14 13:00:00,18067.0 -2017-06-14 14:00:00,18627.0 -2017-06-14 15:00:00,18973.0 -2017-06-14 16:00:00,18349.0 -2017-06-14 17:00:00,17435.0 -2017-06-14 18:00:00,16789.0 -2017-06-14 19:00:00,15715.0 -2017-06-14 20:00:00,14811.0 -2017-06-14 21:00:00,14316.0 -2017-06-14 22:00:00,14019.0 -2017-06-14 23:00:00,13566.0 -2017-06-15 00:00:00,12682.0 -2017-06-13 01:00:00,14384.0 -2017-06-13 02:00:00,13160.0 -2017-06-13 03:00:00,12262.0 -2017-06-13 04:00:00,11657.0 -2017-06-13 05:00:00,11256.0 -2017-06-13 06:00:00,11256.0 -2017-06-13 07:00:00,11561.0 -2017-06-13 08:00:00,12555.0 -2017-06-13 09:00:00,13821.0 -2017-06-13 10:00:00,15159.0 -2017-06-13 11:00:00,15972.0 -2017-06-13 12:00:00,16840.0 -2017-06-13 13:00:00,17616.0 -2017-06-13 14:00:00,18180.0 -2017-06-13 15:00:00,18577.0 -2017-06-13 16:00:00,18784.0 -2017-06-13 17:00:00,18502.0 -2017-06-13 18:00:00,17600.0 -2017-06-13 19:00:00,16740.0 -2017-06-13 20:00:00,15984.0 -2017-06-13 21:00:00,15231.0 -2017-06-13 22:00:00,14792.0 -2017-06-13 23:00:00,14478.0 -2017-06-14 00:00:00,13510.0 -2017-06-12 01:00:00,13461.0 -2017-06-12 02:00:00,12469.0 -2017-06-12 03:00:00,11712.0 -2017-06-12 04:00:00,11244.0 -2017-06-12 05:00:00,10944.0 -2017-06-12 06:00:00,10982.0 -2017-06-12 07:00:00,11352.0 -2017-06-12 08:00:00,12498.0 -2017-06-12 09:00:00,14046.0 -2017-06-12 10:00:00,15274.0 -2017-06-12 11:00:00,16387.0 -2017-06-12 12:00:00,17435.0 -2017-06-12 13:00:00,18267.0 -2017-06-12 14:00:00,18944.0 -2017-06-12 15:00:00,19527.0 -2017-06-12 16:00:00,19864.0 -2017-06-12 17:00:00,20166.0 -2017-06-12 18:00:00,20351.0 -2017-06-12 19:00:00,20266.0 -2017-06-12 20:00:00,19851.0 -2017-06-12 21:00:00,19063.0 -2017-06-12 22:00:00,18628.0 -2017-06-12 23:00:00,17764.0 -2017-06-13 00:00:00,15996.0 -2017-06-11 01:00:00,11907.0 -2017-06-11 02:00:00,11087.0 -2017-06-11 03:00:00,10457.0 -2017-06-11 04:00:00,10008.0 -2017-06-11 05:00:00,9627.0 -2017-06-11 06:00:00,9418.0 -2017-06-11 07:00:00,9199.0 -2017-06-11 08:00:00,9421.0 -2017-06-11 09:00:00,10311.0 -2017-06-11 10:00:00,11331.0 -2017-06-11 11:00:00,12381.0 -2017-06-11 12:00:00,13330.0 -2017-06-11 13:00:00,14291.0 -2017-06-11 14:00:00,15119.0 -2017-06-11 15:00:00,15801.0 -2017-06-11 16:00:00,16402.0 -2017-06-11 17:00:00,16828.0 -2017-06-11 18:00:00,17098.0 -2017-06-11 19:00:00,17124.0 -2017-06-11 20:00:00,16865.0 -2017-06-11 21:00:00,16390.0 -2017-06-11 22:00:00,15894.0 -2017-06-11 23:00:00,15614.0 -2017-06-12 00:00:00,14625.0 -2017-06-10 01:00:00,10968.0 -2017-06-10 02:00:00,10227.0 -2017-06-10 03:00:00,9635.0 -2017-06-10 04:00:00,9273.0 -2017-06-10 05:00:00,9044.0 -2017-06-10 06:00:00,9014.0 -2017-06-10 07:00:00,8996.0 -2017-06-10 08:00:00,9303.0 -2017-06-10 09:00:00,10056.0 -2017-06-10 10:00:00,10958.0 -2017-06-10 11:00:00,11879.0 -2017-06-10 12:00:00,12750.0 -2017-06-10 13:00:00,13466.0 -2017-06-10 14:00:00,13922.0 -2017-06-10 15:00:00,14382.0 -2017-06-10 16:00:00,14841.0 -2017-06-10 17:00:00,15245.0 -2017-06-10 18:00:00,15499.0 -2017-06-10 19:00:00,15466.0 -2017-06-10 20:00:00,15156.0 -2017-06-10 21:00:00,14510.0 -2017-06-10 22:00:00,13959.0 -2017-06-10 23:00:00,13620.0 -2017-06-11 00:00:00,12832.0 -2017-06-09 01:00:00,10543.0 -2017-06-09 02:00:00,9810.0 -2017-06-09 03:00:00,9262.0 -2017-06-09 04:00:00,8931.0 -2017-06-09 05:00:00,8799.0 -2017-06-09 06:00:00,8894.0 -2017-06-09 07:00:00,9195.0 -2017-06-09 08:00:00,10083.0 -2017-06-09 09:00:00,11127.0 -2017-06-09 10:00:00,12110.0 -2017-06-09 11:00:00,12912.0 -2017-06-09 12:00:00,13740.0 -2017-06-09 13:00:00,14382.0 -2017-06-09 14:00:00,14903.0 -2017-06-09 15:00:00,15441.0 -2017-06-09 16:00:00,15701.0 -2017-06-09 17:00:00,15805.0 -2017-06-09 18:00:00,15746.0 -2017-06-09 19:00:00,15289.0 -2017-06-09 20:00:00,14496.0 -2017-06-09 21:00:00,13572.0 -2017-06-09 22:00:00,13251.0 -2017-06-09 23:00:00,12850.0 -2017-06-10 00:00:00,12002.0 -2017-06-08 01:00:00,9682.0 -2017-06-08 02:00:00,9017.0 -2017-06-08 03:00:00,8569.0 -2017-06-08 04:00:00,8332.0 -2017-06-08 05:00:00,8181.0 -2017-06-08 06:00:00,8284.0 -2017-06-08 07:00:00,8634.0 -2017-06-08 08:00:00,9545.0 -2017-06-08 09:00:00,10551.0 -2017-06-08 10:00:00,11245.0 -2017-06-08 11:00:00,11759.0 -2017-06-08 12:00:00,12243.0 -2017-06-08 13:00:00,12613.0 -2017-06-08 14:00:00,12928.0 -2017-06-08 15:00:00,13294.0 -2017-06-08 16:00:00,13586.0 -2017-06-08 17:00:00,13794.0 -2017-06-08 18:00:00,13810.0 -2017-06-08 19:00:00,13506.0 -2017-06-08 20:00:00,13066.0 -2017-06-08 21:00:00,12738.0 -2017-06-08 22:00:00,12598.0 -2017-06-08 23:00:00,12351.0 -2017-06-09 00:00:00,11483.0 -2017-06-07 01:00:00,9416.0 -2017-06-07 02:00:00,8863.0 -2017-06-07 03:00:00,8484.0 -2017-06-07 04:00:00,8211.0 -2017-06-07 05:00:00,8097.0 -2017-06-07 06:00:00,8250.0 -2017-06-07 07:00:00,8548.0 -2017-06-07 08:00:00,9471.0 -2017-06-07 09:00:00,10435.0 -2017-06-07 10:00:00,11036.0 -2017-06-07 11:00:00,11462.0 -2017-06-07 12:00:00,11807.0 -2017-06-07 13:00:00,12025.0 -2017-06-07 14:00:00,12172.0 -2017-06-07 15:00:00,12363.0 -2017-06-07 16:00:00,12465.0 -2017-06-07 17:00:00,12523.0 -2017-06-07 18:00:00,12578.0 -2017-06-07 19:00:00,12511.0 -2017-06-07 20:00:00,12190.0 -2017-06-07 21:00:00,11810.0 -2017-06-07 22:00:00,11623.0 -2017-06-07 23:00:00,11374.0 -2017-06-08 00:00:00,10559.0 -2017-06-06 01:00:00,9510.0 -2017-06-06 02:00:00,8917.0 -2017-06-06 03:00:00,8495.0 -2017-06-06 04:00:00,8258.0 -2017-06-06 05:00:00,8139.0 -2017-06-06 06:00:00,8254.0 -2017-06-06 07:00:00,8582.0 -2017-06-06 08:00:00,9463.0 -2017-06-06 09:00:00,10371.0 -2017-06-06 10:00:00,10956.0 -2017-06-06 11:00:00,11327.0 -2017-06-06 12:00:00,11659.0 -2017-06-06 13:00:00,11868.0 -2017-06-06 14:00:00,12023.0 -2017-06-06 15:00:00,12214.0 -2017-06-06 16:00:00,12321.0 -2017-06-06 17:00:00,12330.0 -2017-06-06 18:00:00,12354.0 -2017-06-06 19:00:00,12181.0 -2017-06-06 20:00:00,11796.0 -2017-06-06 21:00:00,11413.0 -2017-06-06 22:00:00,11266.0 -2017-06-06 23:00:00,11028.0 -2017-06-07 00:00:00,10270.0 -2017-06-05 01:00:00,11140.0 -2017-06-05 02:00:00,10283.0 -2017-06-05 03:00:00,9698.0 -2017-06-05 04:00:00,9253.0 -2017-06-05 05:00:00,9036.0 -2017-06-05 06:00:00,9139.0 -2017-06-05 07:00:00,9534.0 -2017-06-05 08:00:00,10545.0 -2017-06-05 09:00:00,11695.0 -2017-06-05 10:00:00,12516.0 -2017-06-05 11:00:00,12989.0 -2017-06-05 12:00:00,13257.0 -2017-06-05 13:00:00,13275.0 -2017-06-05 14:00:00,13293.0 -2017-06-05 15:00:00,13302.0 -2017-06-05 16:00:00,13263.0 -2017-06-05 17:00:00,13170.0 -2017-06-05 18:00:00,13060.0 -2017-06-05 19:00:00,12776.0 -2017-06-05 20:00:00,12292.0 -2017-06-05 21:00:00,11748.0 -2017-06-05 22:00:00,11465.0 -2017-06-05 23:00:00,11186.0 -2017-06-06 00:00:00,10353.0 -2017-06-04 01:00:00,11081.0 -2017-06-04 02:00:00,10467.0 -2017-06-04 03:00:00,9889.0 -2017-06-04 04:00:00,9497.0 -2017-06-04 05:00:00,9177.0 -2017-06-04 06:00:00,9027.0 -2017-06-04 07:00:00,8867.0 -2017-06-04 08:00:00,8959.0 -2017-06-04 09:00:00,9404.0 -2017-06-04 10:00:00,10077.0 -2017-06-04 11:00:00,10996.0 -2017-06-04 12:00:00,12058.0 -2017-06-04 13:00:00,13073.0 -2017-06-04 14:00:00,13759.0 -2017-06-04 15:00:00,14301.0 -2017-06-04 16:00:00,14651.0 -2017-06-04 17:00:00,15002.0 -2017-06-04 18:00:00,15110.0 -2017-06-04 19:00:00,15126.0 -2017-06-04 20:00:00,14848.0 -2017-06-04 21:00:00,14396.0 -2017-06-04 22:00:00,13799.0 -2017-06-04 23:00:00,13379.0 -2017-06-05 00:00:00,12284.0 -2017-06-03 01:00:00,10333.0 -2017-06-03 02:00:00,9566.0 -2017-06-03 03:00:00,9018.0 -2017-06-03 04:00:00,8610.0 -2017-06-03 05:00:00,8382.0 -2017-06-03 06:00:00,8321.0 -2017-06-03 07:00:00,8238.0 -2017-06-03 08:00:00,8574.0 -2017-06-03 09:00:00,9323.0 -2017-06-03 10:00:00,10158.0 -2017-06-03 11:00:00,10867.0 -2017-06-03 12:00:00,11442.0 -2017-06-03 13:00:00,12021.0 -2017-06-03 14:00:00,12358.0 -2017-06-03 15:00:00,12671.0 -2017-06-03 16:00:00,12975.0 -2017-06-03 17:00:00,13300.0 -2017-06-03 18:00:00,13396.0 -2017-06-03 19:00:00,13401.0 -2017-06-03 20:00:00,13080.0 -2017-06-03 21:00:00,12756.0 -2017-06-03 22:00:00,12661.0 -2017-06-03 23:00:00,12512.0 -2017-06-04 00:00:00,11853.0 -2017-06-02 01:00:00,9505.0 -2017-06-02 02:00:00,8898.0 -2017-06-02 03:00:00,8479.0 -2017-06-02 04:00:00,8221.0 -2017-06-02 05:00:00,8103.0 -2017-06-02 06:00:00,8204.0 -2017-06-02 07:00:00,8570.0 -2017-06-02 08:00:00,9452.0 -2017-06-02 09:00:00,10422.0 -2017-06-02 10:00:00,11132.0 -2017-06-02 11:00:00,11726.0 -2017-06-02 12:00:00,12219.0 -2017-06-02 13:00:00,12626.0 -2017-06-02 14:00:00,12898.0 -2017-06-02 15:00:00,13227.0 -2017-06-02 16:00:00,13490.0 -2017-06-02 17:00:00,13690.0 -2017-06-02 18:00:00,13843.0 -2017-06-02 19:00:00,13784.0 -2017-06-02 20:00:00,13433.0 -2017-06-02 21:00:00,12911.0 -2017-06-02 22:00:00,12486.0 -2017-06-02 23:00:00,12177.0 -2017-06-03 00:00:00,11316.0 -2017-06-01 01:00:00,9307.0 -2017-06-01 02:00:00,8736.0 -2017-06-01 03:00:00,8374.0 -2017-06-01 04:00:00,8104.0 -2017-06-01 05:00:00,8013.0 -2017-06-01 06:00:00,8139.0 -2017-06-01 07:00:00,8487.0 -2017-06-01 08:00:00,9369.0 -2017-06-01 09:00:00,10237.0 -2017-06-01 10:00:00,10847.0 -2017-06-01 11:00:00,11269.0 -2017-06-01 12:00:00,11682.0 -2017-06-01 13:00:00,11904.0 -2017-06-01 14:00:00,12033.0 -2017-06-01 15:00:00,12260.0 -2017-06-01 16:00:00,12385.0 -2017-06-01 17:00:00,12474.0 -2017-06-01 18:00:00,12555.0 -2017-06-01 19:00:00,12447.0 -2017-06-01 20:00:00,12076.0 -2017-06-01 21:00:00,11658.0 -2017-06-01 22:00:00,11537.0 -2017-06-01 23:00:00,11247.0 -2017-06-02 00:00:00,10404.0 -2017-05-31 01:00:00,9224.0 -2017-05-31 02:00:00,8676.0 -2017-05-31 03:00:00,8315.0 -2017-05-31 04:00:00,8068.0 -2017-05-31 05:00:00,7979.0 -2017-05-31 06:00:00,8117.0 -2017-05-31 07:00:00,8500.0 -2017-05-31 08:00:00,9342.0 -2017-05-31 09:00:00,10172.0 -2017-05-31 10:00:00,10728.0 -2017-05-31 11:00:00,11065.0 -2017-05-31 12:00:00,11344.0 -2017-05-31 13:00:00,11485.0 -2017-05-31 14:00:00,11587.0 -2017-05-31 15:00:00,11726.0 -2017-05-31 16:00:00,11758.0 -2017-05-31 17:00:00,11790.0 -2017-05-31 18:00:00,11792.0 -2017-05-31 19:00:00,11674.0 -2017-05-31 20:00:00,11396.0 -2017-05-31 21:00:00,11141.0 -2017-05-31 22:00:00,11129.0 -2017-05-31 23:00:00,10916.0 -2017-06-01 00:00:00,10125.0 -2017-05-30 01:00:00,8417.0 -2017-05-30 02:00:00,7973.0 -2017-05-30 03:00:00,7679.0 -2017-05-30 04:00:00,7513.0 -2017-05-30 05:00:00,7469.0 -2017-05-30 06:00:00,7646.0 -2017-05-30 07:00:00,8141.0 -2017-05-30 08:00:00,9095.0 -2017-05-30 09:00:00,10050.0 -2017-05-30 10:00:00,10691.0 -2017-05-30 11:00:00,11116.0 -2017-05-30 12:00:00,11450.0 -2017-05-30 13:00:00,11642.0 -2017-05-30 14:00:00,11718.0 -2017-05-30 15:00:00,11678.0 -2017-05-30 16:00:00,11642.0 -2017-05-30 17:00:00,11630.0 -2017-05-30 18:00:00,11541.0 -2017-05-30 19:00:00,11389.0 -2017-05-30 20:00:00,11038.0 -2017-05-30 21:00:00,10826.0 -2017-05-30 22:00:00,10932.0 -2017-05-30 23:00:00,10718.0 -2017-05-31 00:00:00,9991.0 -2017-05-29 01:00:00,8575.0 -2017-05-29 02:00:00,8094.0 -2017-05-29 03:00:00,7776.0 -2017-05-29 04:00:00,7587.0 -2017-05-29 05:00:00,7480.0 -2017-05-29 06:00:00,7463.0 -2017-05-29 07:00:00,7442.0 -2017-05-29 08:00:00,7543.0 -2017-05-29 09:00:00,7950.0 -2017-05-29 10:00:00,8448.0 -2017-05-29 11:00:00,8954.0 -2017-05-29 12:00:00,9319.0 -2017-05-29 13:00:00,9593.0 -2017-05-29 14:00:00,9732.0 -2017-05-29 15:00:00,9804.0 -2017-05-29 16:00:00,9793.0 -2017-05-29 17:00:00,9856.0 -2017-05-29 18:00:00,9913.0 -2017-05-29 19:00:00,9905.0 -2017-05-29 20:00:00,9761.0 -2017-05-29 21:00:00,9571.0 -2017-05-29 22:00:00,9711.0 -2017-05-29 23:00:00,9657.0 -2017-05-30 00:00:00,9026.0 -2017-05-28 01:00:00,8696.0 -2017-05-28 02:00:00,8220.0 -2017-05-28 03:00:00,7886.0 -2017-05-28 04:00:00,7650.0 -2017-05-28 05:00:00,7515.0 -2017-05-28 06:00:00,7425.0 -2017-05-28 07:00:00,7318.0 -2017-05-28 08:00:00,7331.0 -2017-05-28 09:00:00,7714.0 -2017-05-28 10:00:00,8243.0 -2017-05-28 11:00:00,8697.0 -2017-05-28 12:00:00,9081.0 -2017-05-28 13:00:00,9320.0 -2017-05-28 14:00:00,9474.0 -2017-05-28 15:00:00,9540.0 -2017-05-28 16:00:00,9644.0 -2017-05-28 17:00:00,9719.0 -2017-05-28 18:00:00,9886.0 -2017-05-28 19:00:00,9937.0 -2017-05-28 20:00:00,9748.0 -2017-05-28 21:00:00,9601.0 -2017-05-28 22:00:00,9663.0 -2017-05-28 23:00:00,9632.0 -2017-05-29 00:00:00,9127.0 -2017-05-27 01:00:00,8789.0 -2017-05-27 02:00:00,8289.0 -2017-05-27 03:00:00,7986.0 -2017-05-27 04:00:00,7730.0 -2017-05-27 05:00:00,7637.0 -2017-05-27 06:00:00,7653.0 -2017-05-27 07:00:00,7669.0 -2017-05-27 08:00:00,7789.0 -2017-05-27 09:00:00,8208.0 -2017-05-27 10:00:00,8740.0 -2017-05-27 11:00:00,9156.0 -2017-05-27 12:00:00,9466.0 -2017-05-27 13:00:00,9611.0 -2017-05-27 14:00:00,9726.0 -2017-05-27 15:00:00,9825.0 -2017-05-27 16:00:00,9907.0 -2017-05-27 17:00:00,10024.0 -2017-05-27 18:00:00,10124.0 -2017-05-27 19:00:00,10153.0 -2017-05-27 20:00:00,10025.0 -2017-05-27 21:00:00,9786.0 -2017-05-27 22:00:00,9905.0 -2017-05-27 23:00:00,9692.0 -2017-05-28 00:00:00,9161.0 -2017-05-26 01:00:00,9012.0 -2017-05-26 02:00:00,8533.0 -2017-05-26 03:00:00,8192.0 -2017-05-26 04:00:00,7987.0 -2017-05-26 05:00:00,7888.0 -2017-05-26 06:00:00,8048.0 -2017-05-26 07:00:00,8462.0 -2017-05-26 08:00:00,9247.0 -2017-05-26 09:00:00,10071.0 -2017-05-26 10:00:00,10589.0 -2017-05-26 11:00:00,10904.0 -2017-05-26 12:00:00,11107.0 -2017-05-26 13:00:00,11151.0 -2017-05-26 14:00:00,11125.0 -2017-05-26 15:00:00,11118.0 -2017-05-26 16:00:00,11089.0 -2017-05-26 17:00:00,10912.0 -2017-05-26 18:00:00,10762.0 -2017-05-26 19:00:00,10542.0 -2017-05-26 20:00:00,10354.0 -2017-05-26 21:00:00,10257.0 -2017-05-26 22:00:00,10259.0 -2017-05-26 23:00:00,10026.0 -2017-05-27 00:00:00,9362.0 -2017-05-25 01:00:00,8998.0 -2017-05-25 02:00:00,8553.0 -2017-05-25 03:00:00,8243.0 -2017-05-25 04:00:00,8062.0 -2017-05-25 05:00:00,7984.0 -2017-05-25 06:00:00,8188.0 -2017-05-25 07:00:00,8695.0 -2017-05-25 08:00:00,9535.0 -2017-05-25 09:00:00,10317.0 -2017-05-25 10:00:00,10744.0 -2017-05-25 11:00:00,10909.0 -2017-05-25 12:00:00,11014.0 -2017-05-25 13:00:00,11096.0 -2017-05-25 14:00:00,11139.0 -2017-05-25 15:00:00,11189.0 -2017-05-25 16:00:00,11143.0 -2017-05-25 17:00:00,11061.0 -2017-05-25 18:00:00,10916.0 -2017-05-25 19:00:00,10745.0 -2017-05-25 20:00:00,10461.0 -2017-05-25 21:00:00,10333.0 -2017-05-25 22:00:00,10583.0 -2017-05-25 23:00:00,10414.0 -2017-05-26 00:00:00,9751.0 -2017-05-24 01:00:00,9013.0 -2017-05-24 02:00:00,8583.0 -2017-05-24 03:00:00,8276.0 -2017-05-24 04:00:00,8109.0 -2017-05-24 05:00:00,8050.0 -2017-05-24 06:00:00,8244.0 -2017-05-24 07:00:00,8785.0 -2017-05-24 08:00:00,9608.0 -2017-05-24 09:00:00,10313.0 -2017-05-24 10:00:00,10675.0 -2017-05-24 11:00:00,10863.0 -2017-05-24 12:00:00,10987.0 -2017-05-24 13:00:00,10994.0 -2017-05-24 14:00:00,10952.0 -2017-05-24 15:00:00,11002.0 -2017-05-24 16:00:00,10874.0 -2017-05-24 17:00:00,10766.0 -2017-05-24 18:00:00,10667.0 -2017-05-24 19:00:00,10542.0 -2017-05-24 20:00:00,10389.0 -2017-05-24 21:00:00,10405.0 -2017-05-24 22:00:00,10632.0 -2017-05-24 23:00:00,10363.0 -2017-05-25 00:00:00,9660.0 -2017-05-23 01:00:00,8997.0 -2017-05-23 02:00:00,8517.0 -2017-05-23 03:00:00,8193.0 -2017-05-23 04:00:00,8020.0 -2017-05-23 05:00:00,7949.0 -2017-05-23 06:00:00,8149.0 -2017-05-23 07:00:00,8651.0 -2017-05-23 08:00:00,9526.0 -2017-05-23 09:00:00,10325.0 -2017-05-23 10:00:00,10775.0 -2017-05-23 11:00:00,10976.0 -2017-05-23 12:00:00,11126.0 -2017-05-23 13:00:00,11182.0 -2017-05-23 14:00:00,11157.0 -2017-05-23 15:00:00,11171.0 -2017-05-23 16:00:00,11095.0 -2017-05-23 17:00:00,10983.0 -2017-05-23 18:00:00,10943.0 -2017-05-23 19:00:00,10833.0 -2017-05-23 20:00:00,10690.0 -2017-05-23 21:00:00,10655.0 -2017-05-23 22:00:00,10730.0 -2017-05-23 23:00:00,10371.0 -2017-05-24 00:00:00,9712.0 -2017-05-22 01:00:00,8291.0 -2017-05-22 02:00:00,7924.0 -2017-05-22 03:00:00,7761.0 -2017-05-22 04:00:00,7680.0 -2017-05-22 05:00:00,7712.0 -2017-05-22 06:00:00,7947.0 -2017-05-22 07:00:00,8471.0 -2017-05-22 08:00:00,9377.0 -2017-05-22 09:00:00,10123.0 -2017-05-22 10:00:00,10443.0 -2017-05-22 11:00:00,10679.0 -2017-05-22 12:00:00,10882.0 -2017-05-22 13:00:00,11024.0 -2017-05-22 14:00:00,11099.0 -2017-05-22 15:00:00,11256.0 -2017-05-22 16:00:00,11244.0 -2017-05-22 17:00:00,11208.0 -2017-05-22 18:00:00,11090.0 -2017-05-22 19:00:00,10926.0 -2017-05-22 20:00:00,10670.0 -2017-05-22 21:00:00,10623.0 -2017-05-22 22:00:00,10820.0 -2017-05-22 23:00:00,10449.0 -2017-05-23 00:00:00,9707.0 -2017-05-21 01:00:00,8566.0 -2017-05-21 02:00:00,8180.0 -2017-05-21 03:00:00,7869.0 -2017-05-21 04:00:00,7736.0 -2017-05-21 05:00:00,7654.0 -2017-05-21 06:00:00,7641.0 -2017-05-21 07:00:00,7623.0 -2017-05-21 08:00:00,7548.0 -2017-05-21 09:00:00,7805.0 -2017-05-21 10:00:00,8214.0 -2017-05-21 11:00:00,8543.0 -2017-05-21 12:00:00,8748.0 -2017-05-21 13:00:00,8801.0 -2017-05-21 14:00:00,8890.0 -2017-05-21 15:00:00,8854.0 -2017-05-21 16:00:00,8808.0 -2017-05-21 17:00:00,8781.0 -2017-05-21 18:00:00,8830.0 -2017-05-21 19:00:00,8930.0 -2017-05-21 20:00:00,9006.0 -2017-05-21 21:00:00,9042.0 -2017-05-21 22:00:00,9346.0 -2017-05-21 23:00:00,9179.0 -2017-05-22 00:00:00,8754.0 -2017-05-20 01:00:00,9000.0 -2017-05-20 02:00:00,8505.0 -2017-05-20 03:00:00,8204.0 -2017-05-20 04:00:00,8010.0 -2017-05-20 05:00:00,7942.0 -2017-05-20 06:00:00,7970.0 -2017-05-20 07:00:00,8143.0 -2017-05-20 08:00:00,8331.0 -2017-05-20 09:00:00,8745.0 -2017-05-20 10:00:00,9254.0 -2017-05-20 11:00:00,9614.0 -2017-05-20 12:00:00,9993.0 -2017-05-20 13:00:00,9917.0 -2017-05-20 14:00:00,9765.0 -2017-05-20 15:00:00,9544.0 -2017-05-20 16:00:00,9434.0 -2017-05-20 17:00:00,9342.0 -2017-05-20 18:00:00,9284.0 -2017-05-20 19:00:00,9260.0 -2017-05-20 20:00:00,9203.0 -2017-05-20 21:00:00,9242.0 -2017-05-20 22:00:00,9565.0 -2017-05-20 23:00:00,9517.0 -2017-05-21 00:00:00,9092.0 -2017-05-19 01:00:00,9238.0 -2017-05-19 02:00:00,8659.0 -2017-05-19 03:00:00,8293.0 -2017-05-19 04:00:00,8048.0 -2017-05-19 05:00:00,7961.0 -2017-05-19 06:00:00,8133.0 -2017-05-19 07:00:00,8629.0 -2017-05-19 08:00:00,9433.0 -2017-05-19 09:00:00,10225.0 -2017-05-19 10:00:00,10646.0 -2017-05-19 11:00:00,10917.0 -2017-05-19 12:00:00,10963.0 -2017-05-19 13:00:00,10923.0 -2017-05-19 14:00:00,10869.0 -2017-05-19 15:00:00,10909.0 -2017-05-19 16:00:00,10807.0 -2017-05-19 17:00:00,10685.0 -2017-05-19 18:00:00,10536.0 -2017-05-19 19:00:00,10465.0 -2017-05-19 20:00:00,10284.0 -2017-05-19 21:00:00,10237.0 -2017-05-19 22:00:00,10482.0 -2017-05-19 23:00:00,10279.0 -2017-05-20 00:00:00,9642.0 -2017-05-18 01:00:00,11320.0 -2017-05-18 02:00:00,10274.0 -2017-05-18 03:00:00,9664.0 -2017-05-18 04:00:00,9347.0 -2017-05-18 05:00:00,9269.0 -2017-05-18 06:00:00,9432.0 -2017-05-18 07:00:00,9921.0 -2017-05-18 08:00:00,10968.0 -2017-05-18 09:00:00,12122.0 -2017-05-18 10:00:00,12718.0 -2017-05-18 11:00:00,13016.0 -2017-05-18 12:00:00,13311.0 -2017-05-18 13:00:00,13457.0 -2017-05-18 14:00:00,13608.0 -2017-05-18 15:00:00,13857.0 -2017-05-18 16:00:00,13993.0 -2017-05-18 17:00:00,14162.0 -2017-05-18 18:00:00,14260.0 -2017-05-18 19:00:00,14082.0 -2017-05-18 20:00:00,13423.0 -2017-05-18 21:00:00,12564.0 -2017-05-18 22:00:00,11963.0 -2017-05-18 23:00:00,11189.0 -2017-05-19 00:00:00,10157.0 -2017-05-17 01:00:00,11516.0 -2017-05-17 02:00:00,10721.0 -2017-05-17 03:00:00,10190.0 -2017-05-17 04:00:00,9781.0 -2017-05-17 05:00:00,9580.0 -2017-05-17 06:00:00,9646.0 -2017-05-17 07:00:00,10104.0 -2017-05-17 08:00:00,11056.0 -2017-05-17 09:00:00,12147.0 -2017-05-17 10:00:00,13045.0 -2017-05-17 11:00:00,13647.0 -2017-05-17 12:00:00,14193.0 -2017-05-17 13:00:00,14535.0 -2017-05-17 14:00:00,14623.0 -2017-05-17 15:00:00,14886.0 -2017-05-17 16:00:00,14981.0 -2017-05-17 17:00:00,14983.0 -2017-05-17 18:00:00,15057.0 -2017-05-17 19:00:00,14722.0 -2017-05-17 20:00:00,14196.0 -2017-05-17 21:00:00,13788.0 -2017-05-17 22:00:00,13812.0 -2017-05-17 23:00:00,13386.0 -2017-05-18 00:00:00,12381.0 -2017-05-16 01:00:00,9889.0 -2017-05-16 02:00:00,9308.0 -2017-05-16 03:00:00,8854.0 -2017-05-16 04:00:00,8624.0 -2017-05-16 05:00:00,8485.0 -2017-05-16 06:00:00,8663.0 -2017-05-16 07:00:00,9141.0 -2017-05-16 08:00:00,10104.0 -2017-05-16 09:00:00,11120.0 -2017-05-16 10:00:00,11827.0 -2017-05-16 11:00:00,12407.0 -2017-05-16 12:00:00,12997.0 -2017-05-16 13:00:00,13492.0 -2017-05-16 14:00:00,13984.0 -2017-05-16 15:00:00,14422.0 -2017-05-16 16:00:00,14650.0 -2017-05-16 17:00:00,14750.0 -2017-05-16 18:00:00,14862.0 -2017-05-16 19:00:00,14753.0 -2017-05-16 20:00:00,14332.0 -2017-05-16 21:00:00,13941.0 -2017-05-16 22:00:00,13970.0 -2017-05-16 23:00:00,13564.0 -2017-05-17 00:00:00,12568.0 -2017-05-15 01:00:00,8120.0 -2017-05-15 02:00:00,7760.0 -2017-05-15 03:00:00,7567.0 -2017-05-15 04:00:00,7468.0 -2017-05-15 05:00:00,7468.0 -2017-05-15 06:00:00,7712.0 -2017-05-15 07:00:00,8254.0 -2017-05-15 08:00:00,9074.0 -2017-05-15 09:00:00,9967.0 -2017-05-15 10:00:00,10441.0 -2017-05-15 11:00:00,10836.0 -2017-05-15 12:00:00,11201.0 -2017-05-15 13:00:00,11446.0 -2017-05-15 14:00:00,11684.0 -2017-05-15 15:00:00,11926.0 -2017-05-15 16:00:00,12059.0 -2017-05-15 17:00:00,12091.0 -2017-05-15 18:00:00,12089.0 -2017-05-15 19:00:00,12030.0 -2017-05-15 20:00:00,11801.0 -2017-05-15 21:00:00,11761.0 -2017-05-15 22:00:00,12054.0 -2017-05-15 23:00:00,11605.0 -2017-05-16 00:00:00,10726.0 -2017-05-14 01:00:00,8674.0 -2017-05-14 02:00:00,8135.0 -2017-05-14 03:00:00,7797.0 -2017-05-14 04:00:00,7509.0 -2017-05-14 05:00:00,7422.0 -2017-05-14 06:00:00,7313.0 -2017-05-14 07:00:00,7308.0 -2017-05-14 08:00:00,7289.0 -2017-05-14 09:00:00,7640.0 -2017-05-14 10:00:00,8129.0 -2017-05-14 11:00:00,8509.0 -2017-05-14 12:00:00,8829.0 -2017-05-14 13:00:00,9053.0 -2017-05-14 14:00:00,9173.0 -2017-05-14 15:00:00,9166.0 -2017-05-14 16:00:00,9137.0 -2017-05-14 17:00:00,9049.0 -2017-05-14 18:00:00,8983.0 -2017-05-14 19:00:00,8893.0 -2017-05-14 20:00:00,8827.0 -2017-05-14 21:00:00,8742.0 -2017-05-14 22:00:00,9179.0 -2017-05-14 23:00:00,9069.0 -2017-05-15 00:00:00,8614.0 -2017-05-13 01:00:00,8851.0 -2017-05-13 02:00:00,8303.0 -2017-05-13 03:00:00,8033.0 -2017-05-13 04:00:00,7802.0 -2017-05-13 05:00:00,7691.0 -2017-05-13 06:00:00,7719.0 -2017-05-13 07:00:00,7834.0 -2017-05-13 08:00:00,8018.0 -2017-05-13 09:00:00,8481.0 -2017-05-13 10:00:00,8941.0 -2017-05-13 11:00:00,9277.0 -2017-05-13 12:00:00,9511.0 -2017-05-13 13:00:00,9602.0 -2017-05-13 14:00:00,9613.0 -2017-05-13 15:00:00,9558.0 -2017-05-13 16:00:00,9580.0 -2017-05-13 17:00:00,9627.0 -2017-05-13 18:00:00,9764.0 -2017-05-13 19:00:00,9827.0 -2017-05-13 20:00:00,9795.0 -2017-05-13 21:00:00,9740.0 -2017-05-13 22:00:00,9963.0 -2017-05-13 23:00:00,9813.0 -2017-05-14 00:00:00,9265.0 -2017-05-12 01:00:00,8861.0 -2017-05-12 02:00:00,8399.0 -2017-05-12 03:00:00,8103.0 -2017-05-12 04:00:00,7945.0 -2017-05-12 05:00:00,7864.0 -2017-05-12 06:00:00,8092.0 -2017-05-12 07:00:00,8603.0 -2017-05-12 08:00:00,9379.0 -2017-05-12 09:00:00,10056.0 -2017-05-12 10:00:00,10425.0 -2017-05-12 11:00:00,10593.0 -2017-05-12 12:00:00,10801.0 -2017-05-12 13:00:00,10938.0 -2017-05-12 14:00:00,10967.0 -2017-05-12 15:00:00,11039.0 -2017-05-12 16:00:00,10889.0 -2017-05-12 17:00:00,10762.0 -2017-05-12 18:00:00,10560.0 -2017-05-12 19:00:00,10354.0 -2017-05-12 20:00:00,10108.0 -2017-05-12 21:00:00,9984.0 -2017-05-12 22:00:00,10243.0 -2017-05-12 23:00:00,10038.0 -2017-05-13 00:00:00,9459.0 -2017-05-11 01:00:00,8898.0 -2017-05-11 02:00:00,8501.0 -2017-05-11 03:00:00,8240.0 -2017-05-11 04:00:00,8086.0 -2017-05-11 05:00:00,8070.0 -2017-05-11 06:00:00,8273.0 -2017-05-11 07:00:00,8880.0 -2017-05-11 08:00:00,9670.0 -2017-05-11 09:00:00,10352.0 -2017-05-11 10:00:00,10630.0 -2017-05-11 11:00:00,10751.0 -2017-05-11 12:00:00,10848.0 -2017-05-11 13:00:00,10870.0 -2017-05-11 14:00:00,10854.0 -2017-05-11 15:00:00,10878.0 -2017-05-11 16:00:00,10797.0 -2017-05-11 17:00:00,10729.0 -2017-05-11 18:00:00,10679.0 -2017-05-11 19:00:00,10557.0 -2017-05-11 20:00:00,10304.0 -2017-05-11 21:00:00,10214.0 -2017-05-11 22:00:00,10562.0 -2017-05-11 23:00:00,10280.0 -2017-05-12 00:00:00,9545.0 -2017-05-10 01:00:00,8935.0 -2017-05-10 02:00:00,8459.0 -2017-05-10 03:00:00,8193.0 -2017-05-10 04:00:00,8060.0 -2017-05-10 05:00:00,8029.0 -2017-05-10 06:00:00,8258.0 -2017-05-10 07:00:00,8836.0 -2017-05-10 08:00:00,9617.0 -2017-05-10 09:00:00,10289.0 -2017-05-10 10:00:00,10488.0 -2017-05-10 11:00:00,10653.0 -2017-05-10 12:00:00,10856.0 -2017-05-10 13:00:00,10972.0 -2017-05-10 14:00:00,10980.0 -2017-05-10 15:00:00,11025.0 -2017-05-10 16:00:00,10832.0 -2017-05-10 17:00:00,10711.0 -2017-05-10 18:00:00,10644.0 -2017-05-10 19:00:00,10648.0 -2017-05-10 20:00:00,10614.0 -2017-05-10 21:00:00,10817.0 -2017-05-10 22:00:00,10863.0 -2017-05-10 23:00:00,10346.0 -2017-05-11 00:00:00,9587.0 -2017-05-09 01:00:00,9024.0 -2017-05-09 02:00:00,8620.0 -2017-05-09 03:00:00,8414.0 -2017-05-09 04:00:00,8245.0 -2017-05-09 05:00:00,8285.0 -2017-05-09 06:00:00,8515.0 -2017-05-09 07:00:00,9198.0 -2017-05-09 08:00:00,10110.0 -2017-05-09 09:00:00,10725.0 -2017-05-09 10:00:00,11019.0 -2017-05-09 11:00:00,11132.0 -2017-05-09 12:00:00,11180.0 -2017-05-09 13:00:00,11113.0 -2017-05-09 14:00:00,10928.0 -2017-05-09 15:00:00,10874.0 -2017-05-09 16:00:00,10718.0 -2017-05-09 17:00:00,10525.0 -2017-05-09 18:00:00,10457.0 -2017-05-09 19:00:00,10358.0 -2017-05-09 20:00:00,10184.0 -2017-05-09 21:00:00,10320.0 -2017-05-09 22:00:00,10648.0 -2017-05-09 23:00:00,10261.0 -2017-05-10 00:00:00,9577.0 -2017-05-08 01:00:00,8353.0 -2017-05-08 02:00:00,8094.0 -2017-05-08 03:00:00,7944.0 -2017-05-08 04:00:00,7888.0 -2017-05-08 05:00:00,7954.0 -2017-05-08 06:00:00,8272.0 -2017-05-08 07:00:00,8947.0 -2017-05-08 08:00:00,9777.0 -2017-05-08 09:00:00,10396.0 -2017-05-08 10:00:00,10610.0 -2017-05-08 11:00:00,10735.0 -2017-05-08 12:00:00,10806.0 -2017-05-08 13:00:00,10791.0 -2017-05-08 14:00:00,10719.0 -2017-05-08 15:00:00,10743.0 -2017-05-08 16:00:00,10625.0 -2017-05-08 17:00:00,10492.0 -2017-05-08 18:00:00,10346.0 -2017-05-08 19:00:00,10284.0 -2017-05-08 20:00:00,10123.0 -2017-05-08 21:00:00,10307.0 -2017-05-08 22:00:00,10693.0 -2017-05-08 23:00:00,10308.0 -2017-05-09 00:00:00,9655.0 -2017-05-07 01:00:00,8314.0 -2017-05-07 02:00:00,7998.0 -2017-05-07 03:00:00,7793.0 -2017-05-07 04:00:00,7663.0 -2017-05-07 05:00:00,7606.0 -2017-05-07 06:00:00,7664.0 -2017-05-07 07:00:00,7730.0 -2017-05-07 08:00:00,7745.0 -2017-05-07 09:00:00,7914.0 -2017-05-07 10:00:00,8150.0 -2017-05-07 11:00:00,8331.0 -2017-05-07 12:00:00,8442.0 -2017-05-07 13:00:00,8459.0 -2017-05-07 14:00:00,8472.0 -2017-05-07 15:00:00,8436.0 -2017-05-07 16:00:00,8381.0 -2017-05-07 17:00:00,8349.0 -2017-05-07 18:00:00,8396.0 -2017-05-07 19:00:00,8495.0 -2017-05-07 20:00:00,8556.0 -2017-05-07 21:00:00,8744.0 -2017-05-07 22:00:00,9311.0 -2017-05-07 23:00:00,9228.0 -2017-05-08 00:00:00,8817.0 -2017-05-06 01:00:00,8954.0 -2017-05-06 02:00:00,8416.0 -2017-05-06 03:00:00,8222.0 -2017-05-06 04:00:00,8077.0 -2017-05-06 05:00:00,7997.0 -2017-05-06 06:00:00,8061.0 -2017-05-06 07:00:00,8196.0 -2017-05-06 08:00:00,8399.0 -2017-05-06 09:00:00,8752.0 -2017-05-06 10:00:00,9047.0 -2017-05-06 11:00:00,9202.0 -2017-05-06 12:00:00,9289.0 -2017-05-06 13:00:00,9308.0 -2017-05-06 14:00:00,9195.0 -2017-05-06 15:00:00,9048.0 -2017-05-06 16:00:00,8895.0 -2017-05-06 17:00:00,8805.0 -2017-05-06 18:00:00,8753.0 -2017-05-06 19:00:00,8709.0 -2017-05-06 20:00:00,8715.0 -2017-05-06 21:00:00,8762.0 -2017-05-06 22:00:00,9193.0 -2017-05-06 23:00:00,9099.0 -2017-05-07 00:00:00,8739.0 -2017-05-05 01:00:00,9067.0 -2017-05-05 02:00:00,8680.0 -2017-05-05 03:00:00,8434.0 -2017-05-05 04:00:00,8318.0 -2017-05-05 05:00:00,8305.0 -2017-05-05 06:00:00,8540.0 -2017-05-05 07:00:00,9162.0 -2017-05-05 08:00:00,9942.0 -2017-05-05 09:00:00,10593.0 -2017-05-05 10:00:00,10807.0 -2017-05-05 11:00:00,10854.0 -2017-05-05 12:00:00,10904.0 -2017-05-05 13:00:00,10881.0 -2017-05-05 14:00:00,10799.0 -2017-05-05 15:00:00,10776.0 -2017-05-05 16:00:00,10609.0 -2017-05-05 17:00:00,10417.0 -2017-05-05 18:00:00,10246.0 -2017-05-05 19:00:00,10058.0 -2017-05-05 20:00:00,9887.0 -2017-05-05 21:00:00,9906.0 -2017-05-05 22:00:00,10271.0 -2017-05-05 23:00:00,10067.0 -2017-05-06 00:00:00,9565.0 -2017-05-04 01:00:00,9020.0 -2017-05-04 02:00:00,8613.0 -2017-05-04 03:00:00,8344.0 -2017-05-04 04:00:00,8219.0 -2017-05-04 05:00:00,8183.0 -2017-05-04 06:00:00,8401.0 -2017-05-04 07:00:00,9053.0 -2017-05-04 08:00:00,9960.0 -2017-05-04 09:00:00,10624.0 -2017-05-04 10:00:00,10873.0 -2017-05-04 11:00:00,11007.0 -2017-05-04 12:00:00,11098.0 -2017-05-04 13:00:00,11033.0 -2017-05-04 14:00:00,10992.0 -2017-05-04 15:00:00,10959.0 -2017-05-04 16:00:00,10796.0 -2017-05-04 17:00:00,10648.0 -2017-05-04 18:00:00,10555.0 -2017-05-04 19:00:00,10475.0 -2017-05-04 20:00:00,10272.0 -2017-05-04 21:00:00,10238.0 -2017-05-04 22:00:00,10663.0 -2017-05-04 23:00:00,10392.0 -2017-05-05 00:00:00,9703.0 -2017-05-03 01:00:00,9281.0 -2017-05-03 02:00:00,8843.0 -2017-05-03 03:00:00,8632.0 -2017-05-03 04:00:00,8497.0 -2017-05-03 05:00:00,8488.0 -2017-05-03 06:00:00,8761.0 -2017-05-03 07:00:00,9456.0 -2017-05-03 08:00:00,10241.0 -2017-05-03 09:00:00,10758.0 -2017-05-03 10:00:00,10902.0 -2017-05-03 11:00:00,10903.0 -2017-05-03 12:00:00,10900.0 -2017-05-03 13:00:00,10886.0 -2017-05-03 14:00:00,10795.0 -2017-05-03 15:00:00,10777.0 -2017-05-03 16:00:00,10593.0 -2017-05-03 17:00:00,10428.0 -2017-05-03 18:00:00,10360.0 -2017-05-03 19:00:00,10265.0 -2017-05-03 20:00:00,10178.0 -2017-05-03 21:00:00,10388.0 -2017-05-03 22:00:00,10693.0 -2017-05-03 23:00:00,10351.0 -2017-05-04 00:00:00,9673.0 -2017-05-02 01:00:00,9306.0 -2017-05-02 02:00:00,8883.0 -2017-05-02 03:00:00,8618.0 -2017-05-02 04:00:00,8494.0 -2017-05-02 05:00:00,8504.0 -2017-05-02 06:00:00,8746.0 -2017-05-02 07:00:00,9426.0 -2017-05-02 08:00:00,10317.0 -2017-05-02 09:00:00,10979.0 -2017-05-02 10:00:00,11208.0 -2017-05-02 11:00:00,11271.0 -2017-05-02 12:00:00,11340.0 -2017-05-02 13:00:00,11309.0 -2017-05-02 14:00:00,11231.0 -2017-05-02 15:00:00,11161.0 -2017-05-02 16:00:00,10998.0 -2017-05-02 17:00:00,10895.0 -2017-05-02 18:00:00,10828.0 -2017-05-02 19:00:00,10828.0 -2017-05-02 20:00:00,10651.0 -2017-05-02 21:00:00,10750.0 -2017-05-02 22:00:00,10971.0 -2017-05-02 23:00:00,10578.0 -2017-05-03 00:00:00,9908.0 -2017-05-01 01:00:00,8858.0 -2017-05-01 02:00:00,8483.0 -2017-05-01 03:00:00,8275.0 -2017-05-01 04:00:00,8141.0 -2017-05-01 05:00:00,8071.0 -2017-05-01 06:00:00,8291.0 -2017-05-01 07:00:00,8940.0 -2017-05-01 08:00:00,9752.0 -2017-05-01 09:00:00,10441.0 -2017-05-01 10:00:00,10620.0 -2017-05-01 11:00:00,10759.0 -2017-05-01 12:00:00,10885.0 -2017-05-01 13:00:00,10915.0 -2017-05-01 14:00:00,10906.0 -2017-05-01 15:00:00,10895.0 -2017-05-01 16:00:00,10777.0 -2017-05-01 17:00:00,10646.0 -2017-05-01 18:00:00,10578.0 -2017-05-01 19:00:00,10563.0 -2017-05-01 20:00:00,10628.0 -2017-05-01 21:00:00,10814.0 -2017-05-01 22:00:00,10982.0 -2017-05-01 23:00:00,10564.0 -2017-05-02 00:00:00,9916.0 -2017-04-30 01:00:00,8988.0 -2017-04-30 02:00:00,8598.0 -2017-04-30 03:00:00,8260.0 -2017-04-30 04:00:00,8119.0 -2017-04-30 05:00:00,8012.0 -2017-04-30 06:00:00,8051.0 -2017-04-30 07:00:00,8185.0 -2017-04-30 08:00:00,8285.0 -2017-04-30 09:00:00,8517.0 -2017-04-30 10:00:00,8886.0 -2017-04-30 11:00:00,9105.0 -2017-04-30 12:00:00,9276.0 -2017-04-30 13:00:00,9392.0 -2017-04-30 14:00:00,9316.0 -2017-04-30 15:00:00,9277.0 -2017-04-30 16:00:00,9233.0 -2017-04-30 17:00:00,9259.0 -2017-04-30 18:00:00,9330.0 -2017-04-30 19:00:00,9540.0 -2017-04-30 20:00:00,9722.0 -2017-04-30 21:00:00,10029.0 -2017-04-30 22:00:00,9955.0 -2017-04-30 23:00:00,9665.0 -2017-05-01 00:00:00,9192.0 -2017-04-29 01:00:00,8802.0 -2017-04-29 02:00:00,8387.0 -2017-04-29 03:00:00,8035.0 -2017-04-29 04:00:00,7941.0 -2017-04-29 05:00:00,7908.0 -2017-04-29 06:00:00,7977.0 -2017-04-29 07:00:00,8259.0 -2017-04-29 08:00:00,8503.0 -2017-04-29 09:00:00,8889.0 -2017-04-29 10:00:00,9288.0 -2017-04-29 11:00:00,9527.0 -2017-04-29 12:00:00,9638.0 -2017-04-29 13:00:00,9783.0 -2017-04-29 14:00:00,9804.0 -2017-04-29 15:00:00,9769.0 -2017-04-29 16:00:00,9769.0 -2017-04-29 17:00:00,9792.0 -2017-04-29 18:00:00,9924.0 -2017-04-29 19:00:00,10040.0 -2017-04-29 20:00:00,10186.0 -2017-04-29 21:00:00,10300.0 -2017-04-29 22:00:00,10387.0 -2017-04-29 23:00:00,10096.0 -2017-04-30 00:00:00,9549.0 -2017-04-28 01:00:00,8957.0 -2017-04-28 02:00:00,8509.0 -2017-04-28 03:00:00,8263.0 -2017-04-28 04:00:00,8114.0 -2017-04-28 05:00:00,8061.0 -2017-04-28 06:00:00,8283.0 -2017-04-28 07:00:00,8948.0 -2017-04-28 08:00:00,9689.0 -2017-04-28 09:00:00,10273.0 -2017-04-28 10:00:00,10473.0 -2017-04-28 11:00:00,10575.0 -2017-04-28 12:00:00,10640.0 -2017-04-28 13:00:00,10662.0 -2017-04-28 14:00:00,10608.0 -2017-04-28 15:00:00,10621.0 -2017-04-28 16:00:00,10467.0 -2017-04-28 17:00:00,10282.0 -2017-04-28 18:00:00,10143.0 -2017-04-28 19:00:00,10061.0 -2017-04-28 20:00:00,9971.0 -2017-04-28 21:00:00,10138.0 -2017-04-28 22:00:00,10272.0 -2017-04-28 23:00:00,9996.0 -2017-04-29 00:00:00,9411.0 -2017-04-27 01:00:00,9248.0 -2017-04-27 02:00:00,8705.0 -2017-04-27 03:00:00,8386.0 -2017-04-27 04:00:00,8178.0 -2017-04-27 05:00:00,8135.0 -2017-04-27 06:00:00,8323.0 -2017-04-27 07:00:00,8883.0 -2017-04-27 08:00:00,9651.0 -2017-04-27 09:00:00,10223.0 -2017-04-27 10:00:00,10511.0 -2017-04-27 11:00:00,10629.0 -2017-04-27 12:00:00,10710.0 -2017-04-27 13:00:00,10766.0 -2017-04-27 14:00:00,10784.0 -2017-04-27 15:00:00,10792.0 -2017-04-27 16:00:00,10735.0 -2017-04-27 17:00:00,10642.0 -2017-04-27 18:00:00,10538.0 -2017-04-27 19:00:00,10483.0 -2017-04-27 20:00:00,10373.0 -2017-04-27 21:00:00,10556.0 -2017-04-27 22:00:00,10692.0 -2017-04-27 23:00:00,10225.0 -2017-04-28 00:00:00,9592.0 -2017-04-26 01:00:00,9273.0 -2017-04-26 02:00:00,8746.0 -2017-04-26 03:00:00,8405.0 -2017-04-26 04:00:00,8218.0 -2017-04-26 05:00:00,8133.0 -2017-04-26 06:00:00,8306.0 -2017-04-26 07:00:00,8937.0 -2017-04-26 08:00:00,9726.0 -2017-04-26 09:00:00,10519.0 -2017-04-26 10:00:00,10931.0 -2017-04-26 11:00:00,11239.0 -2017-04-26 12:00:00,11515.0 -2017-04-26 13:00:00,11605.0 -2017-04-26 14:00:00,11713.0 -2017-04-26 15:00:00,11813.0 -2017-04-26 16:00:00,11750.0 -2017-04-26 17:00:00,11628.0 -2017-04-26 18:00:00,11500.0 -2017-04-26 19:00:00,11291.0 -2017-04-26 20:00:00,10963.0 -2017-04-26 21:00:00,11031.0 -2017-04-26 22:00:00,11165.0 -2017-04-26 23:00:00,10741.0 -2017-04-27 00:00:00,9964.0 -2017-04-25 01:00:00,8779.0 -2017-04-25 02:00:00,8353.0 -2017-04-25 03:00:00,8055.0 -2017-04-25 04:00:00,7895.0 -2017-04-25 05:00:00,7829.0 -2017-04-25 06:00:00,8030.0 -2017-04-25 07:00:00,8657.0 -2017-04-25 08:00:00,9369.0 -2017-04-25 09:00:00,10129.0 -2017-04-25 10:00:00,10525.0 -2017-04-25 11:00:00,10811.0 -2017-04-25 12:00:00,11049.0 -2017-04-25 13:00:00,11205.0 -2017-04-25 14:00:00,11327.0 -2017-04-25 15:00:00,11445.0 -2017-04-25 16:00:00,11398.0 -2017-04-25 17:00:00,11290.0 -2017-04-25 18:00:00,11280.0 -2017-04-25 19:00:00,11183.0 -2017-04-25 20:00:00,10927.0 -2017-04-25 21:00:00,10990.0 -2017-04-25 22:00:00,11249.0 -2017-04-25 23:00:00,10839.0 -2017-04-26 00:00:00,10043.0 -2017-04-24 01:00:00,8135.0 -2017-04-24 02:00:00,7851.0 -2017-04-24 03:00:00,7685.0 -2017-04-24 04:00:00,7633.0 -2017-04-24 05:00:00,7702.0 -2017-04-24 06:00:00,8006.0 -2017-04-24 07:00:00,8679.0 -2017-04-24 08:00:00,9487.0 -2017-04-24 09:00:00,10128.0 -2017-04-24 10:00:00,10436.0 -2017-04-24 11:00:00,10648.0 -2017-04-24 12:00:00,10872.0 -2017-04-24 13:00:00,10951.0 -2017-04-24 14:00:00,10976.0 -2017-04-24 15:00:00,11060.0 -2017-04-24 16:00:00,11028.0 -2017-04-24 17:00:00,10959.0 -2017-04-24 18:00:00,10879.0 -2017-04-24 19:00:00,10697.0 -2017-04-24 20:00:00,10420.0 -2017-04-24 21:00:00,10445.0 -2017-04-24 22:00:00,10727.0 -2017-04-24 23:00:00,10269.0 -2017-04-25 00:00:00,9555.0 -2017-04-23 01:00:00,8334.0 -2017-04-23 02:00:00,7982.0 -2017-04-23 03:00:00,7785.0 -2017-04-23 04:00:00,7638.0 -2017-04-23 05:00:00,7613.0 -2017-04-23 06:00:00,7633.0 -2017-04-23 07:00:00,7787.0 -2017-04-23 08:00:00,7734.0 -2017-04-23 09:00:00,7946.0 -2017-04-23 10:00:00,8117.0 -2017-04-23 11:00:00,8342.0 -2017-04-23 12:00:00,8498.0 -2017-04-23 13:00:00,8565.0 -2017-04-23 14:00:00,8581.0 -2017-04-23 15:00:00,8654.0 -2017-04-23 16:00:00,8624.0 -2017-04-23 17:00:00,8712.0 -2017-04-23 18:00:00,8767.0 -2017-04-23 19:00:00,8863.0 -2017-04-23 20:00:00,8845.0 -2017-04-23 21:00:00,9038.0 -2017-04-23 22:00:00,9395.0 -2017-04-23 23:00:00,9133.0 -2017-04-24 00:00:00,8640.0 -2017-04-22 01:00:00,8940.0 -2017-04-22 02:00:00,8474.0 -2017-04-22 03:00:00,8157.0 -2017-04-22 04:00:00,7980.0 -2017-04-22 05:00:00,7943.0 -2017-04-22 06:00:00,8004.0 -2017-04-22 07:00:00,8288.0 -2017-04-22 08:00:00,8475.0 -2017-04-22 09:00:00,8816.0 -2017-04-22 10:00:00,9098.0 -2017-04-22 11:00:00,9249.0 -2017-04-22 12:00:00,9296.0 -2017-04-22 13:00:00,9252.0 -2017-04-22 14:00:00,9150.0 -2017-04-22 15:00:00,9019.0 -2017-04-22 16:00:00,8880.0 -2017-04-22 17:00:00,8773.0 -2017-04-22 18:00:00,8758.0 -2017-04-22 19:00:00,8750.0 -2017-04-22 20:00:00,8752.0 -2017-04-22 21:00:00,8924.0 -2017-04-22 22:00:00,9348.0 -2017-04-22 23:00:00,9181.0 -2017-04-23 00:00:00,8761.0 -2017-04-21 01:00:00,8865.0 -2017-04-21 02:00:00,8407.0 -2017-04-21 03:00:00,8139.0 -2017-04-21 04:00:00,7974.0 -2017-04-21 05:00:00,7932.0 -2017-04-21 06:00:00,8138.0 -2017-04-21 07:00:00,8760.0 -2017-04-21 08:00:00,9512.0 -2017-04-21 09:00:00,10135.0 -2017-04-21 10:00:00,10425.0 -2017-04-21 11:00:00,10527.0 -2017-04-21 12:00:00,10613.0 -2017-04-21 13:00:00,10556.0 -2017-04-21 14:00:00,10504.0 -2017-04-21 15:00:00,10465.0 -2017-04-21 16:00:00,10321.0 -2017-04-21 17:00:00,10153.0 -2017-04-21 18:00:00,10064.0 -2017-04-21 19:00:00,9917.0 -2017-04-21 20:00:00,9859.0 -2017-04-21 21:00:00,10126.0 -2017-04-21 22:00:00,10400.0 -2017-04-21 23:00:00,10122.0 -2017-04-22 00:00:00,9543.0 -2017-04-20 01:00:00,8935.0 -2017-04-20 02:00:00,8465.0 -2017-04-20 03:00:00,8214.0 -2017-04-20 04:00:00,8078.0 -2017-04-20 05:00:00,8047.0 -2017-04-20 06:00:00,8232.0 -2017-04-20 07:00:00,8882.0 -2017-04-20 08:00:00,9619.0 -2017-04-20 09:00:00,10307.0 -2017-04-20 10:00:00,10704.0 -2017-04-20 11:00:00,11046.0 -2017-04-20 12:00:00,11373.0 -2017-04-20 13:00:00,11432.0 -2017-04-20 14:00:00,11314.0 -2017-04-20 15:00:00,11274.0 -2017-04-20 16:00:00,11155.0 -2017-04-20 17:00:00,10951.0 -2017-04-20 18:00:00,10783.0 -2017-04-20 19:00:00,10544.0 -2017-04-20 20:00:00,10300.0 -2017-04-20 21:00:00,10431.0 -2017-04-20 22:00:00,10640.0 -2017-04-20 23:00:00,10234.0 -2017-04-21 00:00:00,9511.0 -2017-04-19 01:00:00,9176.0 -2017-04-19 02:00:00,8694.0 -2017-04-19 03:00:00,8371.0 -2017-04-19 04:00:00,8143.0 -2017-04-19 05:00:00,8102.0 -2017-04-19 06:00:00,8301.0 -2017-04-19 07:00:00,8919.0 -2017-04-19 08:00:00,9718.0 -2017-04-19 09:00:00,10443.0 -2017-04-19 10:00:00,10882.0 -2017-04-19 11:00:00,11137.0 -2017-04-19 12:00:00,11314.0 -2017-04-19 13:00:00,11315.0 -2017-04-19 14:00:00,11200.0 -2017-04-19 15:00:00,11082.0 -2017-04-19 16:00:00,10691.0 -2017-04-19 17:00:00,10617.0 -2017-04-19 18:00:00,10449.0 -2017-04-19 19:00:00,10388.0 -2017-04-19 20:00:00,10216.0 -2017-04-19 21:00:00,10429.0 -2017-04-19 22:00:00,10692.0 -2017-04-19 23:00:00,10277.0 -2017-04-20 00:00:00,9588.0 -2017-04-18 01:00:00,8847.0 -2017-04-18 02:00:00,8432.0 -2017-04-18 03:00:00,8165.0 -2017-04-18 04:00:00,7994.0 -2017-04-18 05:00:00,7959.0 -2017-04-18 06:00:00,8225.0 -2017-04-18 07:00:00,8851.0 -2017-04-18 08:00:00,9672.0 -2017-04-18 09:00:00,10232.0 -2017-04-18 10:00:00,10508.0 -2017-04-18 11:00:00,10694.0 -2017-04-18 12:00:00,10923.0 -2017-04-18 13:00:00,11036.0 -2017-04-18 14:00:00,11091.0 -2017-04-18 15:00:00,11175.0 -2017-04-18 16:00:00,11150.0 -2017-04-18 17:00:00,11044.0 -2017-04-18 18:00:00,10921.0 -2017-04-18 19:00:00,10877.0 -2017-04-18 20:00:00,10676.0 -2017-04-18 21:00:00,10862.0 -2017-04-18 22:00:00,11068.0 -2017-04-18 23:00:00,10626.0 -2017-04-19 00:00:00,9921.0 -2017-04-17 01:00:00,7942.0 -2017-04-17 02:00:00,7594.0 -2017-04-17 03:00:00,7385.0 -2017-04-17 04:00:00,7275.0 -2017-04-17 05:00:00,7286.0 -2017-04-17 06:00:00,7546.0 -2017-04-17 07:00:00,8211.0 -2017-04-17 08:00:00,8979.0 -2017-04-17 09:00:00,9724.0 -2017-04-17 10:00:00,10217.0 -2017-04-17 11:00:00,10525.0 -2017-04-17 12:00:00,10789.0 -2017-04-17 13:00:00,10854.0 -2017-04-17 14:00:00,10874.0 -2017-04-17 15:00:00,10910.0 -2017-04-17 16:00:00,10865.0 -2017-04-17 17:00:00,10756.0 -2017-04-17 18:00:00,10602.0 -2017-04-17 19:00:00,10392.0 -2017-04-17 20:00:00,10160.0 -2017-04-17 21:00:00,10302.0 -2017-04-17 22:00:00,10576.0 -2017-04-17 23:00:00,10170.0 -2017-04-18 00:00:00,9492.0 -2017-04-16 01:00:00,8857.0 -2017-04-16 02:00:00,8359.0 -2017-04-16 03:00:00,7956.0 -2017-04-16 04:00:00,7698.0 -2017-04-16 05:00:00,7525.0 -2017-04-16 06:00:00,7465.0 -2017-04-16 07:00:00,7488.0 -2017-04-16 08:00:00,7453.0 -2017-04-16 09:00:00,7651.0 -2017-04-16 10:00:00,8040.0 -2017-04-16 11:00:00,8367.0 -2017-04-16 12:00:00,8573.0 -2017-04-16 13:00:00,8724.0 -2017-04-16 14:00:00,8770.0 -2017-04-16 15:00:00,8794.0 -2017-04-16 16:00:00,8783.0 -2017-04-16 17:00:00,8694.0 -2017-04-16 18:00:00,8648.0 -2017-04-16 19:00:00,8632.0 -2017-04-16 20:00:00,8561.0 -2017-04-16 21:00:00,8761.0 -2017-04-16 22:00:00,9126.0 -2017-04-16 23:00:00,8915.0 -2017-04-17 00:00:00,8413.0 -2017-04-15 01:00:00,8531.0 -2017-04-15 02:00:00,8106.0 -2017-04-15 03:00:00,7810.0 -2017-04-15 04:00:00,7612.0 -2017-04-15 05:00:00,7522.0 -2017-04-15 06:00:00,7563.0 -2017-04-15 07:00:00,7765.0 -2017-04-15 08:00:00,7897.0 -2017-04-15 09:00:00,8226.0 -2017-04-15 10:00:00,8715.0 -2017-04-15 11:00:00,9165.0 -2017-04-15 12:00:00,9462.0 -2017-04-15 13:00:00,9622.0 -2017-04-15 14:00:00,9674.0 -2017-04-15 15:00:00,9634.0 -2017-04-15 16:00:00,9663.0 -2017-04-15 17:00:00,9661.0 -2017-04-15 18:00:00,9692.0 -2017-04-15 19:00:00,9665.0 -2017-04-15 20:00:00,9592.0 -2017-04-15 21:00:00,9895.0 -2017-04-15 22:00:00,10119.0 -2017-04-15 23:00:00,9897.0 -2017-04-16 00:00:00,9436.0 -2017-04-14 01:00:00,8934.0 -2017-04-14 02:00:00,8453.0 -2017-04-14 03:00:00,8202.0 -2017-04-14 04:00:00,8047.0 -2017-04-14 05:00:00,7993.0 -2017-04-14 06:00:00,8119.0 -2017-04-14 07:00:00,8595.0 -2017-04-14 08:00:00,9071.0 -2017-04-14 09:00:00,9421.0 -2017-04-14 10:00:00,9660.0 -2017-04-14 11:00:00,9830.0 -2017-04-14 12:00:00,9890.0 -2017-04-14 13:00:00,9890.0 -2017-04-14 14:00:00,9846.0 -2017-04-14 15:00:00,9869.0 -2017-04-14 16:00:00,9797.0 -2017-04-14 17:00:00,9697.0 -2017-04-14 18:00:00,9659.0 -2017-04-14 19:00:00,9615.0 -2017-04-14 20:00:00,9539.0 -2017-04-14 21:00:00,9882.0 -2017-04-14 22:00:00,9878.0 -2017-04-14 23:00:00,9602.0 -2017-04-15 00:00:00,9096.0 -2017-04-13 01:00:00,8940.0 -2017-04-13 02:00:00,8555.0 -2017-04-13 03:00:00,8303.0 -2017-04-13 04:00:00,8121.0 -2017-04-13 05:00:00,8106.0 -2017-04-13 06:00:00,8332.0 -2017-04-13 07:00:00,9033.0 -2017-04-13 08:00:00,10039.0 -2017-04-13 09:00:00,10726.0 -2017-04-13 10:00:00,11006.0 -2017-04-13 11:00:00,11118.0 -2017-04-13 12:00:00,11159.0 -2017-04-13 13:00:00,11050.0 -2017-04-13 14:00:00,10919.0 -2017-04-13 15:00:00,10841.0 -2017-04-13 16:00:00,10675.0 -2017-04-13 17:00:00,10519.0 -2017-04-13 18:00:00,10390.0 -2017-04-13 19:00:00,10229.0 -2017-04-13 20:00:00,10072.0 -2017-04-13 21:00:00,10347.0 -2017-04-13 22:00:00,10573.0 -2017-04-13 23:00:00,10216.0 -2017-04-14 00:00:00,9558.0 -2017-04-12 01:00:00,9100.0 -2017-04-12 02:00:00,8698.0 -2017-04-12 03:00:00,8478.0 -2017-04-12 04:00:00,8344.0 -2017-04-12 05:00:00,8322.0 -2017-04-12 06:00:00,8592.0 -2017-04-12 07:00:00,9319.0 -2017-04-12 08:00:00,10122.0 -2017-04-12 09:00:00,10587.0 -2017-04-12 10:00:00,10698.0 -2017-04-12 11:00:00,10687.0 -2017-04-12 12:00:00,10758.0 -2017-04-12 13:00:00,10708.0 -2017-04-12 14:00:00,10666.0 -2017-04-12 15:00:00,10671.0 -2017-04-12 16:00:00,10536.0 -2017-04-12 17:00:00,10376.0 -2017-04-12 18:00:00,10285.0 -2017-04-12 19:00:00,10217.0 -2017-04-12 20:00:00,10207.0 -2017-04-12 21:00:00,10618.0 -2017-04-12 22:00:00,10694.0 -2017-04-12 23:00:00,10265.0 -2017-04-13 00:00:00,9551.0 -2017-04-11 01:00:00,8882.0 -2017-04-11 02:00:00,8474.0 -2017-04-11 03:00:00,8191.0 -2017-04-11 04:00:00,8070.0 -2017-04-11 05:00:00,8033.0 -2017-04-11 06:00:00,8290.0 -2017-04-11 07:00:00,8946.0 -2017-04-11 08:00:00,9895.0 -2017-04-11 09:00:00,10476.0 -2017-04-11 10:00:00,10762.0 -2017-04-11 11:00:00,10882.0 -2017-04-11 12:00:00,10981.0 -2017-04-11 13:00:00,10910.0 -2017-04-11 14:00:00,10818.0 -2017-04-11 15:00:00,10766.0 -2017-04-11 16:00:00,10655.0 -2017-04-11 17:00:00,10521.0 -2017-04-11 18:00:00,10419.0 -2017-04-11 19:00:00,10349.0 -2017-04-11 20:00:00,10257.0 -2017-04-11 21:00:00,10591.0 -2017-04-11 22:00:00,10778.0 -2017-04-11 23:00:00,10352.0 -2017-04-12 00:00:00,9726.0 -2017-04-10 01:00:00,8483.0 -2017-04-10 02:00:00,8157.0 -2017-04-10 03:00:00,7911.0 -2017-04-10 04:00:00,7751.0 -2017-04-10 05:00:00,7733.0 -2017-04-10 06:00:00,7981.0 -2017-04-10 07:00:00,8678.0 -2017-04-10 08:00:00,9596.0 -2017-04-10 09:00:00,10274.0 -2017-04-10 10:00:00,10608.0 -2017-04-10 11:00:00,10777.0 -2017-04-10 12:00:00,11084.0 -2017-04-10 13:00:00,11300.0 -2017-04-10 14:00:00,11255.0 -2017-04-10 15:00:00,11298.0 -2017-04-10 16:00:00,11162.0 -2017-04-10 17:00:00,11008.0 -2017-04-10 18:00:00,10998.0 -2017-04-10 19:00:00,10933.0 -2017-04-10 20:00:00,10594.0 -2017-04-10 21:00:00,10628.0 -2017-04-10 22:00:00,10603.0 -2017-04-10 23:00:00,10160.0 -2017-04-11 00:00:00,9511.0 -2017-04-09 01:00:00,8468.0 -2017-04-09 02:00:00,8061.0 -2017-04-09 03:00:00,7837.0 -2017-04-09 04:00:00,7635.0 -2017-04-09 05:00:00,7590.0 -2017-04-09 06:00:00,7570.0 -2017-04-09 07:00:00,7732.0 -2017-04-09 08:00:00,7830.0 -2017-04-09 09:00:00,7946.0 -2017-04-09 10:00:00,8236.0 -2017-04-09 11:00:00,8413.0 -2017-04-09 12:00:00,8632.0 -2017-04-09 13:00:00,8710.0 -2017-04-09 14:00:00,8760.0 -2017-04-09 15:00:00,8795.0 -2017-04-09 16:00:00,8817.0 -2017-04-09 17:00:00,8879.0 -2017-04-09 18:00:00,8928.0 -2017-04-09 19:00:00,8973.0 -2017-04-09 20:00:00,8990.0 -2017-04-09 21:00:00,9544.0 -2017-04-09 22:00:00,9768.0 -2017-04-09 23:00:00,9483.0 -2017-04-10 00:00:00,9024.0 -2017-04-08 01:00:00,9309.0 -2017-04-08 02:00:00,8857.0 -2017-04-08 03:00:00,8614.0 -2017-04-08 04:00:00,8444.0 -2017-04-08 05:00:00,8449.0 -2017-04-08 06:00:00,8515.0 -2017-04-08 07:00:00,8815.0 -2017-04-08 08:00:00,9030.0 -2017-04-08 09:00:00,9240.0 -2017-04-08 10:00:00,9433.0 -2017-04-08 11:00:00,9535.0 -2017-04-08 12:00:00,9571.0 -2017-04-08 13:00:00,9492.0 -2017-04-08 14:00:00,9367.0 -2017-04-08 15:00:00,9215.0 -2017-04-08 16:00:00,9086.0 -2017-04-08 17:00:00,9021.0 -2017-04-08 18:00:00,9002.0 -2017-04-08 19:00:00,8963.0 -2017-04-08 20:00:00,8994.0 -2017-04-08 21:00:00,9374.0 -2017-04-08 22:00:00,9619.0 -2017-04-08 23:00:00,9361.0 -2017-04-09 00:00:00,8948.0 -2017-04-07 01:00:00,9678.0 -2017-04-07 02:00:00,9258.0 -2017-04-07 03:00:00,9005.0 -2017-04-07 04:00:00,8879.0 -2017-04-07 05:00:00,8888.0 -2017-04-07 06:00:00,9152.0 -2017-04-07 07:00:00,9846.0 -2017-04-07 08:00:00,10745.0 -2017-04-07 09:00:00,11138.0 -2017-04-07 10:00:00,11273.0 -2017-04-07 11:00:00,11229.0 -2017-04-07 12:00:00,11213.0 -2017-04-07 13:00:00,11105.0 -2017-04-07 14:00:00,10979.0 -2017-04-07 15:00:00,10899.0 -2017-04-07 16:00:00,10710.0 -2017-04-07 17:00:00,10468.0 -2017-04-07 18:00:00,10280.0 -2017-04-07 19:00:00,10138.0 -2017-04-07 20:00:00,10057.0 -2017-04-07 21:00:00,10463.0 -2017-04-07 22:00:00,10637.0 -2017-04-07 23:00:00,10391.0 -2017-04-08 00:00:00,9855.0 -2017-04-06 01:00:00,9985.0 -2017-04-06 02:00:00,9538.0 -2017-04-06 03:00:00,9284.0 -2017-04-06 04:00:00,9117.0 -2017-04-06 05:00:00,9140.0 -2017-04-06 06:00:00,9389.0 -2017-04-06 07:00:00,10094.0 -2017-04-06 08:00:00,11060.0 -2017-04-06 09:00:00,11559.0 -2017-04-06 10:00:00,11575.0 -2017-04-06 11:00:00,11587.0 -2017-04-06 12:00:00,11548.0 -2017-04-06 13:00:00,11428.0 -2017-04-06 14:00:00,11299.0 -2017-04-06 15:00:00,11294.0 -2017-04-06 16:00:00,11152.0 -2017-04-06 17:00:00,11005.0 -2017-04-06 18:00:00,10903.0 -2017-04-06 19:00:00,10779.0 -2017-04-06 20:00:00,10732.0 -2017-04-06 21:00:00,11124.0 -2017-04-06 22:00:00,11294.0 -2017-04-06 23:00:00,10963.0 -2017-04-07 00:00:00,10300.0 -2017-04-05 01:00:00,9256.0 -2017-04-05 02:00:00,8823.0 -2017-04-05 03:00:00,8553.0 -2017-04-05 04:00:00,8426.0 -2017-04-05 05:00:00,8412.0 -2017-04-05 06:00:00,8699.0 -2017-04-05 07:00:00,9449.0 -2017-04-05 08:00:00,10526.0 -2017-04-05 09:00:00,11215.0 -2017-04-05 10:00:00,11497.0 -2017-04-05 11:00:00,11631.0 -2017-04-05 12:00:00,11734.0 -2017-04-05 13:00:00,11718.0 -2017-04-05 14:00:00,11670.0 -2017-04-05 15:00:00,11787.0 -2017-04-05 16:00:00,11756.0 -2017-04-05 17:00:00,11728.0 -2017-04-05 18:00:00,11824.0 -2017-04-05 19:00:00,11835.0 -2017-04-05 20:00:00,11737.0 -2017-04-05 21:00:00,11960.0 -2017-04-05 22:00:00,11790.0 -2017-04-05 23:00:00,11318.0 -2017-04-06 00:00:00,10640.0 -2017-04-04 01:00:00,9268.0 -2017-04-04 02:00:00,8859.0 -2017-04-04 03:00:00,8602.0 -2017-04-04 04:00:00,8451.0 -2017-04-04 05:00:00,8440.0 -2017-04-04 06:00:00,8676.0 -2017-04-04 07:00:00,9357.0 -2017-04-04 08:00:00,10396.0 -2017-04-04 09:00:00,10943.0 -2017-04-04 10:00:00,11159.0 -2017-04-04 11:00:00,11249.0 -2017-04-04 12:00:00,11287.0 -2017-04-04 13:00:00,11200.0 -2017-04-04 14:00:00,11094.0 -2017-04-04 15:00:00,11013.0 -2017-04-04 16:00:00,10821.0 -2017-04-04 17:00:00,10727.0 -2017-04-04 18:00:00,10678.0 -2017-04-04 19:00:00,10627.0 -2017-04-04 20:00:00,10575.0 -2017-04-04 21:00:00,10923.0 -2017-04-04 22:00:00,10966.0 -2017-04-04 23:00:00,10560.0 -2017-04-05 00:00:00,9906.0 -2017-04-03 01:00:00,8596.0 -2017-04-03 02:00:00,8271.0 -2017-04-03 03:00:00,8113.0 -2017-04-03 04:00:00,8017.0 -2017-04-03 05:00:00,7932.0 -2017-04-03 06:00:00,8238.0 -2017-04-03 07:00:00,8898.0 -2017-04-03 08:00:00,10133.0 -2017-04-03 09:00:00,10588.0 -2017-04-03 10:00:00,10895.0 -2017-04-03 11:00:00,11007.0 -2017-04-03 12:00:00,11134.0 -2017-04-03 13:00:00,11112.0 -2017-04-03 14:00:00,11046.0 -2017-04-03 15:00:00,11088.0 -2017-04-03 16:00:00,11080.0 -2017-04-03 17:00:00,11030.0 -2017-04-03 18:00:00,11013.0 -2017-04-03 19:00:00,11033.0 -2017-04-03 20:00:00,11016.0 -2017-04-03 21:00:00,11176.0 -2017-04-03 22:00:00,11029.0 -2017-04-03 23:00:00,10603.0 -2017-04-04 00:00:00,9955.0 -2017-04-02 01:00:00,8804.0 -2017-04-02 02:00:00,8458.0 -2017-04-02 03:00:00,8196.0 -2017-04-02 04:00:00,8073.0 -2017-04-02 05:00:00,8013.0 -2017-04-02 06:00:00,8032.0 -2017-04-02 07:00:00,8128.0 -2017-04-02 08:00:00,8413.0 -2017-04-02 09:00:00,8411.0 -2017-04-02 10:00:00,8694.0 -2017-04-02 11:00:00,8854.0 -2017-04-02 12:00:00,8847.0 -2017-04-02 13:00:00,8883.0 -2017-04-02 14:00:00,8845.0 -2017-04-02 15:00:00,8890.0 -2017-04-02 16:00:00,8866.0 -2017-04-02 17:00:00,8904.0 -2017-04-02 18:00:00,9086.0 -2017-04-02 19:00:00,9202.0 -2017-04-02 20:00:00,9373.0 -2017-04-02 21:00:00,9788.0 -2017-04-02 22:00:00,9793.0 -2017-04-02 23:00:00,9460.0 -2017-04-03 00:00:00,9069.0 -2017-04-01 01:00:00,9743.0 -2017-04-01 02:00:00,9292.0 -2017-04-01 03:00:00,9014.0 -2017-04-01 04:00:00,8850.0 -2017-04-01 05:00:00,8812.0 -2017-04-01 06:00:00,8887.0 -2017-04-01 07:00:00,9149.0 -2017-04-01 08:00:00,9492.0 -2017-04-01 09:00:00,9629.0 -2017-04-01 10:00:00,9806.0 -2017-04-01 11:00:00,9760.0 -2017-04-01 12:00:00,9779.0 -2017-04-01 13:00:00,9650.0 -2017-04-01 14:00:00,9469.0 -2017-04-01 15:00:00,9265.0 -2017-04-01 16:00:00,9152.0 -2017-04-01 17:00:00,9058.0 -2017-04-01 18:00:00,8986.0 -2017-04-01 19:00:00,9005.0 -2017-04-01 20:00:00,9137.0 -2017-04-01 21:00:00,9661.0 -2017-04-01 22:00:00,9784.0 -2017-04-01 23:00:00,9592.0 -2017-04-02 00:00:00,9204.0 -2017-03-31 01:00:00,9770.0 -2017-03-31 02:00:00,9381.0 -2017-03-31 03:00:00,9118.0 -2017-03-31 04:00:00,8947.0 -2017-03-31 05:00:00,8927.0 -2017-03-31 06:00:00,9173.0 -2017-03-31 07:00:00,9832.0 -2017-03-31 08:00:00,10759.0 -2017-03-31 09:00:00,11320.0 -2017-03-31 10:00:00,11562.0 -2017-03-31 11:00:00,11717.0 -2017-03-31 12:00:00,11811.0 -2017-03-31 13:00:00,11758.0 -2017-03-31 14:00:00,11664.0 -2017-03-31 15:00:00,11615.0 -2017-03-31 16:00:00,11442.0 -2017-03-31 17:00:00,11301.0 -2017-03-31 18:00:00,11170.0 -2017-03-31 19:00:00,11013.0 -2017-03-31 20:00:00,10927.0 -2017-03-31 21:00:00,11255.0 -2017-03-31 22:00:00,11185.0 -2017-03-31 23:00:00,10878.0 -2017-04-01 00:00:00,10302.0 -2017-03-30 01:00:00,9663.0 -2017-03-30 02:00:00,9289.0 -2017-03-30 03:00:00,9094.0 -2017-03-30 04:00:00,8978.0 -2017-03-30 05:00:00,8994.0 -2017-03-30 06:00:00,9182.0 -2017-03-30 07:00:00,9869.0 -2017-03-30 08:00:00,10918.0 -2017-03-30 09:00:00,11405.0 -2017-03-30 10:00:00,11584.0 -2017-03-30 11:00:00,11719.0 -2017-03-30 12:00:00,11884.0 -2017-03-30 13:00:00,12020.0 -2017-03-30 14:00:00,12010.0 -2017-03-30 15:00:00,12053.0 -2017-03-30 16:00:00,11890.0 -2017-03-30 17:00:00,11540.0 -2017-03-30 18:00:00,11318.0 -2017-03-30 19:00:00,11316.0 -2017-03-30 20:00:00,11310.0 -2017-03-30 21:00:00,11584.0 -2017-03-30 22:00:00,11417.0 -2017-03-30 23:00:00,10992.0 -2017-03-31 00:00:00,10364.0 -2017-03-29 01:00:00,9504.0 -2017-03-29 02:00:00,9064.0 -2017-03-29 03:00:00,8898.0 -2017-03-29 04:00:00,8713.0 -2017-03-29 05:00:00,8727.0 -2017-03-29 06:00:00,9054.0 -2017-03-29 07:00:00,9788.0 -2017-03-29 08:00:00,10766.0 -2017-03-29 09:00:00,11189.0 -2017-03-29 10:00:00,11264.0 -2017-03-29 11:00:00,11228.0 -2017-03-29 12:00:00,11160.0 -2017-03-29 13:00:00,11167.0 -2017-03-29 14:00:00,11158.0 -2017-03-29 15:00:00,11201.0 -2017-03-29 16:00:00,11225.0 -2017-03-29 17:00:00,11140.0 -2017-03-29 18:00:00,11136.0 -2017-03-29 19:00:00,11193.0 -2017-03-29 20:00:00,11254.0 -2017-03-29 21:00:00,11340.0 -2017-03-29 22:00:00,11138.0 -2017-03-29 23:00:00,10826.0 -2017-03-30 00:00:00,10249.0 -2017-03-28 01:00:00,9293.0 -2017-03-28 02:00:00,8859.0 -2017-03-28 03:00:00,8595.0 -2017-03-28 04:00:00,8514.0 -2017-03-28 05:00:00,8518.0 -2017-03-28 06:00:00,8835.0 -2017-03-28 07:00:00,9543.0 -2017-03-28 08:00:00,10544.0 -2017-03-28 09:00:00,10926.0 -2017-03-28 10:00:00,11187.0 -2017-03-28 11:00:00,11276.0 -2017-03-28 12:00:00,11292.0 -2017-03-28 13:00:00,11235.0 -2017-03-28 14:00:00,11147.0 -2017-03-28 15:00:00,11044.0 -2017-03-28 16:00:00,10912.0 -2017-03-28 17:00:00,10775.0 -2017-03-28 18:00:00,10748.0 -2017-03-28 19:00:00,10619.0 -2017-03-28 20:00:00,10640.0 -2017-03-28 21:00:00,11067.0 -2017-03-28 22:00:00,11114.0 -2017-03-28 23:00:00,10659.0 -2017-03-29 00:00:00,9981.0 -2017-03-27 01:00:00,8475.0 -2017-03-27 02:00:00,8165.0 -2017-03-27 03:00:00,7998.0 -2017-03-27 04:00:00,7814.0 -2017-03-27 05:00:00,7841.0 -2017-03-27 06:00:00,8111.0 -2017-03-27 07:00:00,8794.0 -2017-03-27 08:00:00,9838.0 -2017-03-27 09:00:00,10434.0 -2017-03-27 10:00:00,10753.0 -2017-03-27 11:00:00,10899.0 -2017-03-27 12:00:00,11015.0 -2017-03-27 13:00:00,11003.0 -2017-03-27 14:00:00,10927.0 -2017-03-27 15:00:00,10928.0 -2017-03-27 16:00:00,10836.0 -2017-03-27 17:00:00,10724.0 -2017-03-27 18:00:00,10656.0 -2017-03-27 19:00:00,10660.0 -2017-03-27 20:00:00,10650.0 -2017-03-27 21:00:00,10985.0 -2017-03-27 22:00:00,10897.0 -2017-03-27 23:00:00,10526.0 -2017-03-28 00:00:00,9919.0 -2017-03-26 01:00:00,8880.0 -2017-03-26 02:00:00,8457.0 -2017-03-26 03:00:00,8205.0 -2017-03-26 04:00:00,8017.0 -2017-03-26 05:00:00,7937.0 -2017-03-26 06:00:00,7952.0 -2017-03-26 07:00:00,8069.0 -2017-03-26 08:00:00,8362.0 -2017-03-26 09:00:00,8482.0 -2017-03-26 10:00:00,8752.0 -2017-03-26 11:00:00,8918.0 -2017-03-26 12:00:00,9033.0 -2017-03-26 13:00:00,9053.0 -2017-03-26 14:00:00,9027.0 -2017-03-26 15:00:00,8955.0 -2017-03-26 16:00:00,8882.0 -2017-03-26 17:00:00,8788.0 -2017-03-26 18:00:00,8886.0 -2017-03-26 19:00:00,8999.0 -2017-03-26 20:00:00,9191.0 -2017-03-26 21:00:00,9615.0 -2017-03-26 22:00:00,9538.0 -2017-03-26 23:00:00,9290.0 -2017-03-27 00:00:00,8878.0 -2017-03-25 01:00:00,9098.0 -2017-03-25 02:00:00,8649.0 -2017-03-25 03:00:00,8427.0 -2017-03-25 04:00:00,8247.0 -2017-03-25 05:00:00,8219.0 -2017-03-25 06:00:00,8288.0 -2017-03-25 07:00:00,8646.0 -2017-03-25 08:00:00,9103.0 -2017-03-25 09:00:00,9480.0 -2017-03-25 10:00:00,9872.0 -2017-03-25 11:00:00,10242.0 -2017-03-25 12:00:00,10413.0 -2017-03-25 13:00:00,10383.0 -2017-03-25 14:00:00,10189.0 -2017-03-25 15:00:00,9949.0 -2017-03-25 16:00:00,9799.0 -2017-03-25 17:00:00,9734.0 -2017-03-25 18:00:00,9769.0 -2017-03-25 19:00:00,9769.0 -2017-03-25 20:00:00,9857.0 -2017-03-25 21:00:00,10149.0 -2017-03-25 22:00:00,10035.0 -2017-03-25 23:00:00,9765.0 -2017-03-26 00:00:00,9345.0 -2017-03-24 01:00:00,9520.0 -2017-03-24 02:00:00,9123.0 -2017-03-24 03:00:00,8846.0 -2017-03-24 04:00:00,8662.0 -2017-03-24 05:00:00,8623.0 -2017-03-24 06:00:00,8807.0 -2017-03-24 07:00:00,9420.0 -2017-03-24 08:00:00,10439.0 -2017-03-24 09:00:00,10785.0 -2017-03-24 10:00:00,10901.0 -2017-03-24 11:00:00,10874.0 -2017-03-24 12:00:00,10961.0 -2017-03-24 13:00:00,11039.0 -2017-03-24 14:00:00,10995.0 -2017-03-24 15:00:00,11064.0 -2017-03-24 16:00:00,11021.0 -2017-03-24 17:00:00,10831.0 -2017-03-24 18:00:00,10632.0 -2017-03-24 19:00:00,10400.0 -2017-03-24 20:00:00,10313.0 -2017-03-24 21:00:00,10661.0 -2017-03-24 22:00:00,10470.0 -2017-03-24 23:00:00,10172.0 -2017-03-25 00:00:00,9613.0 -2017-03-23 01:00:00,10143.0 -2017-03-23 02:00:00,9676.0 -2017-03-23 03:00:00,9427.0 -2017-03-23 04:00:00,9342.0 -2017-03-23 05:00:00,9368.0 -2017-03-23 06:00:00,9553.0 -2017-03-23 07:00:00,10408.0 -2017-03-23 08:00:00,11488.0 -2017-03-23 09:00:00,11892.0 -2017-03-23 10:00:00,11966.0 -2017-03-23 11:00:00,12033.0 -2017-03-23 12:00:00,11927.0 -2017-03-23 13:00:00,11601.0 -2017-03-23 14:00:00,11523.0 -2017-03-23 15:00:00,11469.0 -2017-03-23 16:00:00,11366.0 -2017-03-23 17:00:00,11246.0 -2017-03-23 18:00:00,11088.0 -2017-03-23 19:00:00,11174.0 -2017-03-23 20:00:00,11287.0 -2017-03-23 21:00:00,11537.0 -2017-03-23 22:00:00,11356.0 -2017-03-23 23:00:00,10896.0 -2017-03-24 00:00:00,10105.0 -2017-03-22 01:00:00,9947.0 -2017-03-22 02:00:00,9570.0 -2017-03-22 03:00:00,9387.0 -2017-03-22 04:00:00,9273.0 -2017-03-22 05:00:00,9293.0 -2017-03-22 06:00:00,9593.0 -2017-03-22 07:00:00,10355.0 -2017-03-22 08:00:00,11451.0 -2017-03-22 09:00:00,11888.0 -2017-03-22 10:00:00,12011.0 -2017-03-22 11:00:00,12057.0 -2017-03-22 12:00:00,11940.0 -2017-03-22 13:00:00,11743.0 -2017-03-22 14:00:00,11605.0 -2017-03-22 15:00:00,11523.0 -2017-03-22 16:00:00,11329.0 -2017-03-22 17:00:00,11157.0 -2017-03-22 18:00:00,11082.0 -2017-03-22 19:00:00,11085.0 -2017-03-22 20:00:00,11158.0 -2017-03-22 21:00:00,11786.0 -2017-03-22 22:00:00,11807.0 -2017-03-22 23:00:00,11421.0 -2017-03-23 00:00:00,10761.0 -2017-03-21 01:00:00,9453.0 -2017-03-21 02:00:00,9079.0 -2017-03-21 03:00:00,8851.0 -2017-03-21 04:00:00,8742.0 -2017-03-21 05:00:00,8763.0 -2017-03-21 06:00:00,9052.0 -2017-03-21 07:00:00,9771.0 -2017-03-21 08:00:00,10913.0 -2017-03-21 09:00:00,11318.0 -2017-03-21 10:00:00,11312.0 -2017-03-21 11:00:00,11289.0 -2017-03-21 12:00:00,11255.0 -2017-03-21 13:00:00,11187.0 -2017-03-21 14:00:00,11136.0 -2017-03-21 15:00:00,11215.0 -2017-03-21 16:00:00,11070.0 -2017-03-21 17:00:00,10890.0 -2017-03-21 18:00:00,10772.0 -2017-03-21 19:00:00,10711.0 -2017-03-21 20:00:00,10780.0 -2017-03-21 21:00:00,11388.0 -2017-03-21 22:00:00,11471.0 -2017-03-21 23:00:00,11132.0 -2017-03-22 00:00:00,10546.0 -2017-03-20 01:00:00,8929.0 -2017-03-20 02:00:00,8598.0 -2017-03-20 03:00:00,8398.0 -2017-03-20 04:00:00,8256.0 -2017-03-20 05:00:00,8375.0 -2017-03-20 06:00:00,8689.0 -2017-03-20 07:00:00,9480.0 -2017-03-20 08:00:00,10590.0 -2017-03-20 09:00:00,11224.0 -2017-03-20 10:00:00,11289.0 -2017-03-20 11:00:00,11330.0 -2017-03-20 12:00:00,11364.0 -2017-03-20 13:00:00,11235.0 -2017-03-20 14:00:00,11173.0 -2017-03-20 15:00:00,11116.0 -2017-03-20 16:00:00,10985.0 -2017-03-20 17:00:00,10916.0 -2017-03-20 18:00:00,10864.0 -2017-03-20 19:00:00,10858.0 -2017-03-20 20:00:00,10834.0 -2017-03-20 21:00:00,11308.0 -2017-03-20 22:00:00,11194.0 -2017-03-20 23:00:00,10744.0 -2017-03-21 00:00:00,10106.0 -2017-03-19 01:00:00,9425.0 -2017-03-19 02:00:00,9090.0 -2017-03-19 03:00:00,8902.0 -2017-03-19 04:00:00,8802.0 -2017-03-19 05:00:00,8736.0 -2017-03-19 06:00:00,8818.0 -2017-03-19 07:00:00,8941.0 -2017-03-19 08:00:00,9246.0 -2017-03-19 09:00:00,9250.0 -2017-03-19 10:00:00,9362.0 -2017-03-19 11:00:00,9446.0 -2017-03-19 12:00:00,9482.0 -2017-03-19 13:00:00,9428.0 -2017-03-19 14:00:00,9391.0 -2017-03-19 15:00:00,9257.0 -2017-03-19 16:00:00,9145.0 -2017-03-19 17:00:00,9051.0 -2017-03-19 18:00:00,9082.0 -2017-03-19 19:00:00,9145.0 -2017-03-19 20:00:00,9384.0 -2017-03-19 21:00:00,10052.0 -2017-03-19 22:00:00,10107.0 -2017-03-19 23:00:00,9834.0 -2017-03-20 00:00:00,9412.0 -2017-03-18 01:00:00,9914.0 -2017-03-18 02:00:00,9399.0 -2017-03-18 03:00:00,9195.0 -2017-03-18 04:00:00,9046.0 -2017-03-18 05:00:00,8976.0 -2017-03-18 06:00:00,9000.0 -2017-03-18 07:00:00,9251.0 -2017-03-18 08:00:00,9756.0 -2017-03-18 09:00:00,9999.0 -2017-03-18 10:00:00,10345.0 -2017-03-18 11:00:00,10587.0 -2017-03-18 12:00:00,10713.0 -2017-03-18 13:00:00,10630.0 -2017-03-18 14:00:00,10431.0 -2017-03-18 15:00:00,10152.0 -2017-03-18 16:00:00,10014.0 -2017-03-18 17:00:00,9925.0 -2017-03-18 18:00:00,10025.0 -2017-03-18 19:00:00,10076.0 -2017-03-18 20:00:00,10282.0 -2017-03-18 21:00:00,10594.0 -2017-03-18 22:00:00,10543.0 -2017-03-18 23:00:00,10297.0 -2017-03-19 00:00:00,9863.0 -2017-03-17 01:00:00,10113.0 -2017-03-17 02:00:00,9661.0 -2017-03-17 03:00:00,9381.0 -2017-03-17 04:00:00,9269.0 -2017-03-17 05:00:00,9301.0 -2017-03-17 06:00:00,9507.0 -2017-03-17 07:00:00,10200.0 -2017-03-17 08:00:00,11259.0 -2017-03-17 09:00:00,11913.0 -2017-03-17 10:00:00,12095.0 -2017-03-17 11:00:00,12169.0 -2017-03-17 12:00:00,12257.0 -2017-03-17 13:00:00,12173.0 -2017-03-17 14:00:00,12021.0 -2017-03-17 15:00:00,11902.0 -2017-03-17 16:00:00,11724.0 -2017-03-17 17:00:00,11498.0 -2017-03-17 18:00:00,11339.0 -2017-03-17 19:00:00,11114.0 -2017-03-17 20:00:00,10996.0 -2017-03-17 21:00:00,11465.0 -2017-03-17 22:00:00,11367.0 -2017-03-17 23:00:00,11064.0 -2017-03-18 00:00:00,10510.0 -2017-03-16 01:00:00,10678.0 -2017-03-16 02:00:00,10293.0 -2017-03-16 03:00:00,10047.0 -2017-03-16 04:00:00,9947.0 -2017-03-16 05:00:00,9968.0 -2017-03-16 06:00:00,10237.0 -2017-03-16 07:00:00,10957.0 -2017-03-16 08:00:00,12057.0 -2017-03-16 09:00:00,12490.0 -2017-03-16 10:00:00,12441.0 -2017-03-16 11:00:00,12274.0 -2017-03-16 12:00:00,12205.0 -2017-03-16 13:00:00,12057.0 -2017-03-16 14:00:00,11887.0 -2017-03-16 15:00:00,11773.0 -2017-03-16 16:00:00,11573.0 -2017-03-16 17:00:00,11326.0 -2017-03-16 18:00:00,11195.0 -2017-03-16 19:00:00,11169.0 -2017-03-16 20:00:00,11231.0 -2017-03-16 21:00:00,11866.0 -2017-03-16 22:00:00,11843.0 -2017-03-16 23:00:00,11465.0 -2017-03-17 00:00:00,10784.0 -2017-03-15 01:00:00,11044.0 -2017-03-15 02:00:00,10629.0 -2017-03-15 03:00:00,10381.0 -2017-03-15 04:00:00,10304.0 -2017-03-15 05:00:00,10295.0 -2017-03-15 06:00:00,10566.0 -2017-03-15 07:00:00,11255.0 -2017-03-15 08:00:00,12384.0 -2017-03-15 09:00:00,12856.0 -2017-03-15 10:00:00,12815.0 -2017-03-15 11:00:00,12704.0 -2017-03-15 12:00:00,12622.0 -2017-03-15 13:00:00,12483.0 -2017-03-15 14:00:00,12336.0 -2017-03-15 15:00:00,12218.0 -2017-03-15 16:00:00,12028.0 -2017-03-15 17:00:00,11831.0 -2017-03-15 18:00:00,11686.0 -2017-03-15 19:00:00,11677.0 -2017-03-15 20:00:00,11749.0 -2017-03-15 21:00:00,12414.0 -2017-03-15 22:00:00,12415.0 -2017-03-15 23:00:00,12023.0 -2017-03-16 00:00:00,11344.0 -2017-03-14 01:00:00,10659.0 -2017-03-14 02:00:00,10248.0 -2017-03-14 03:00:00,9989.0 -2017-03-14 04:00:00,9868.0 -2017-03-14 05:00:00,9909.0 -2017-03-14 06:00:00,10188.0 -2017-03-14 07:00:00,10895.0 -2017-03-14 08:00:00,12015.0 -2017-03-14 09:00:00,12547.0 -2017-03-14 10:00:00,12653.0 -2017-03-14 11:00:00,12627.0 -2017-03-14 12:00:00,12592.0 -2017-03-14 13:00:00,12513.0 -2017-03-14 14:00:00,12421.0 -2017-03-14 15:00:00,12362.0 -2017-03-14 16:00:00,12239.0 -2017-03-14 17:00:00,12152.0 -2017-03-14 18:00:00,12112.0 -2017-03-14 19:00:00,12099.0 -2017-03-14 20:00:00,12210.0 -2017-03-14 21:00:00,12831.0 -2017-03-14 22:00:00,12803.0 -2017-03-14 23:00:00,12373.0 -2017-03-15 00:00:00,11669.0 -2017-03-13 01:00:00,9872.0 -2017-03-13 02:00:00,9554.0 -2017-03-13 03:00:00,9408.0 -2017-03-13 04:00:00,9354.0 -2017-03-13 05:00:00,9414.0 -2017-03-13 06:00:00,9746.0 -2017-03-13 07:00:00,10475.0 -2017-03-13 08:00:00,11619.0 -2017-03-13 09:00:00,12166.0 -2017-03-13 10:00:00,12289.0 -2017-03-13 11:00:00,12336.0 -2017-03-13 12:00:00,12414.0 -2017-03-13 13:00:00,12378.0 -2017-03-13 14:00:00,12324.0 -2017-03-13 15:00:00,12290.0 -2017-03-13 16:00:00,12168.0 -2017-03-13 17:00:00,12047.0 -2017-03-13 18:00:00,12062.0 -2017-03-13 19:00:00,12183.0 -2017-03-13 20:00:00,12327.0 -2017-03-13 21:00:00,12643.0 -2017-03-13 22:00:00,12476.0 -2017-03-13 23:00:00,12038.0 -2017-03-14 00:00:00,11335.0 -2017-03-12 01:00:00,9870.0 -2017-03-12 02:00:00,9582.0 -2017-03-12 04:00:00,9464.0 -2017-03-12 05:00:00,9373.0 -2017-03-12 06:00:00,9405.0 -2017-03-12 07:00:00,9537.0 -2017-03-12 08:00:00,9799.0 -2017-03-12 09:00:00,9797.0 -2017-03-12 10:00:00,9876.0 -2017-03-12 11:00:00,9912.0 -2017-03-12 12:00:00,9985.0 -2017-03-12 13:00:00,9925.0 -2017-03-12 14:00:00,9897.0 -2017-03-12 15:00:00,9778.0 -2017-03-12 16:00:00,9683.0 -2017-03-12 17:00:00,9596.0 -2017-03-12 18:00:00,9667.0 -2017-03-12 19:00:00,9907.0 -2017-03-12 20:00:00,10355.0 -2017-03-12 21:00:00,11001.0 -2017-03-12 22:00:00,10948.0 -2017-03-12 23:00:00,10666.0 -2017-03-13 00:00:00,10304.0 -2017-03-11 01:00:00,10473.0 -2017-03-11 02:00:00,10122.0 -2017-03-11 03:00:00,9871.0 -2017-03-11 04:00:00,9775.0 -2017-03-11 05:00:00,9728.0 -2017-03-11 06:00:00,9875.0 -2017-03-11 07:00:00,10179.0 -2017-03-11 08:00:00,10419.0 -2017-03-11 09:00:00,10579.0 -2017-03-11 10:00:00,10861.0 -2017-03-11 11:00:00,10926.0 -2017-03-11 12:00:00,10892.0 -2017-03-11 13:00:00,10825.0 -2017-03-11 14:00:00,10584.0 -2017-03-11 15:00:00,10329.0 -2017-03-11 16:00:00,10122.0 -2017-03-11 17:00:00,10076.0 -2017-03-11 18:00:00,10188.0 -2017-03-11 19:00:00,10598.0 -2017-03-11 20:00:00,11220.0 -2017-03-11 21:00:00,11198.0 -2017-03-11 22:00:00,11093.0 -2017-03-11 23:00:00,10795.0 -2017-03-12 00:00:00,10385.0 -2017-03-10 01:00:00,9853.0 -2017-03-10 02:00:00,9514.0 -2017-03-10 03:00:00,9345.0 -2017-03-10 04:00:00,9286.0 -2017-03-10 05:00:00,9337.0 -2017-03-10 06:00:00,9646.0 -2017-03-10 07:00:00,10401.0 -2017-03-10 08:00:00,11203.0 -2017-03-10 09:00:00,11581.0 -2017-03-10 10:00:00,11771.0 -2017-03-10 11:00:00,11785.0 -2017-03-10 12:00:00,11832.0 -2017-03-10 13:00:00,11768.0 -2017-03-10 14:00:00,11668.0 -2017-03-10 15:00:00,11629.0 -2017-03-10 16:00:00,11445.0 -2017-03-10 17:00:00,11319.0 -2017-03-10 18:00:00,11378.0 -2017-03-10 19:00:00,11691.0 -2017-03-10 20:00:00,12258.0 -2017-03-10 21:00:00,12207.0 -2017-03-10 22:00:00,12000.0 -2017-03-10 23:00:00,11665.0 -2017-03-11 00:00:00,11076.0 -2017-03-09 01:00:00,9516.0 -2017-03-09 02:00:00,9155.0 -2017-03-09 03:00:00,8924.0 -2017-03-09 04:00:00,8849.0 -2017-03-09 05:00:00,8878.0 -2017-03-09 06:00:00,9167.0 -2017-03-09 07:00:00,9905.0 -2017-03-09 08:00:00,10779.0 -2017-03-09 09:00:00,11270.0 -2017-03-09 10:00:00,11377.0 -2017-03-09 11:00:00,11412.0 -2017-03-09 12:00:00,11429.0 -2017-03-09 13:00:00,11366.0 -2017-03-09 14:00:00,11235.0 -2017-03-09 15:00:00,11250.0 -2017-03-09 16:00:00,11187.0 -2017-03-09 17:00:00,11181.0 -2017-03-09 18:00:00,11389.0 -2017-03-09 19:00:00,11716.0 -2017-03-09 20:00:00,11990.0 -2017-03-09 21:00:00,11855.0 -2017-03-09 22:00:00,11607.0 -2017-03-09 23:00:00,11142.0 -2017-03-10 00:00:00,10467.0 -2017-03-08 01:00:00,9410.0 -2017-03-08 02:00:00,9012.0 -2017-03-08 03:00:00,8801.0 -2017-03-08 04:00:00,8704.0 -2017-03-08 05:00:00,8718.0 -2017-03-08 06:00:00,9043.0 -2017-03-08 07:00:00,9809.0 -2017-03-08 08:00:00,10662.0 -2017-03-08 09:00:00,11087.0 -2017-03-08 10:00:00,11215.0 -2017-03-08 11:00:00,11251.0 -2017-03-08 12:00:00,11214.0 -2017-03-08 13:00:00,11253.0 -2017-03-08 14:00:00,11118.0 -2017-03-08 15:00:00,11061.0 -2017-03-08 16:00:00,10871.0 -2017-03-08 17:00:00,10747.0 -2017-03-08 18:00:00,10748.0 -2017-03-08 19:00:00,10951.0 -2017-03-08 20:00:00,11502.0 -2017-03-08 21:00:00,11507.0 -2017-03-08 22:00:00,11235.0 -2017-03-08 23:00:00,10786.0 -2017-03-09 00:00:00,10134.0 -2017-03-07 01:00:00,9124.0 -2017-03-07 02:00:00,8665.0 -2017-03-07 03:00:00,8417.0 -2017-03-07 04:00:00,8285.0 -2017-03-07 05:00:00,8219.0 -2017-03-07 06:00:00,8448.0 -2017-03-07 07:00:00,9188.0 -2017-03-07 08:00:00,10144.0 -2017-03-07 09:00:00,10696.0 -2017-03-07 10:00:00,10938.0 -2017-03-07 11:00:00,11001.0 -2017-03-07 12:00:00,11062.0 -2017-03-07 13:00:00,11035.0 -2017-03-07 14:00:00,10954.0 -2017-03-07 15:00:00,10903.0 -2017-03-07 16:00:00,10756.0 -2017-03-07 17:00:00,10633.0 -2017-03-07 18:00:00,10652.0 -2017-03-07 19:00:00,10911.0 -2017-03-07 20:00:00,11416.0 -2017-03-07 21:00:00,11386.0 -2017-03-07 22:00:00,11145.0 -2017-03-07 23:00:00,10698.0 -2017-03-08 00:00:00,10033.0 -2017-03-06 01:00:00,8817.0 -2017-03-06 02:00:00,8515.0 -2017-03-06 03:00:00,8351.0 -2017-03-06 04:00:00,8275.0 -2017-03-06 05:00:00,8319.0 -2017-03-06 06:00:00,8621.0 -2017-03-06 07:00:00,9367.0 -2017-03-06 08:00:00,10276.0 -2017-03-06 09:00:00,10757.0 -2017-03-06 10:00:00,11077.0 -2017-03-06 11:00:00,11135.0 -2017-03-06 12:00:00,11182.0 -2017-03-06 13:00:00,11193.0 -2017-03-06 14:00:00,11152.0 -2017-03-06 15:00:00,11170.0 -2017-03-06 16:00:00,11101.0 -2017-03-06 17:00:00,11020.0 -2017-03-06 18:00:00,11079.0 -2017-03-06 19:00:00,11378.0 -2017-03-06 20:00:00,11544.0 -2017-03-06 21:00:00,11334.0 -2017-03-06 22:00:00,10997.0 -2017-03-06 23:00:00,10509.0 -2017-03-07 00:00:00,9771.0 -2017-03-05 01:00:00,9794.0 -2017-03-05 02:00:00,9425.0 -2017-03-05 03:00:00,9183.0 -2017-03-05 04:00:00,9056.0 -2017-03-05 05:00:00,8979.0 -2017-03-05 06:00:00,8999.0 -2017-03-05 07:00:00,9172.0 -2017-03-05 08:00:00,9188.0 -2017-03-05 09:00:00,9245.0 -2017-03-05 10:00:00,9419.0 -2017-03-05 11:00:00,9565.0 -2017-03-05 12:00:00,9521.0 -2017-03-05 13:00:00,9458.0 -2017-03-05 14:00:00,9408.0 -2017-03-05 15:00:00,9318.0 -2017-03-05 16:00:00,9282.0 -2017-03-05 17:00:00,9278.0 -2017-03-05 18:00:00,9458.0 -2017-03-05 19:00:00,9742.0 -2017-03-05 20:00:00,10315.0 -2017-03-05 21:00:00,10251.0 -2017-03-05 22:00:00,10063.0 -2017-03-05 23:00:00,9698.0 -2017-03-06 00:00:00,9259.0 -2017-03-04 01:00:00,10174.0 -2017-03-04 02:00:00,9761.0 -2017-03-04 03:00:00,9563.0 -2017-03-04 04:00:00,9405.0 -2017-03-04 05:00:00,9377.0 -2017-03-04 06:00:00,9476.0 -2017-03-04 07:00:00,9794.0 -2017-03-04 08:00:00,10099.0 -2017-03-04 09:00:00,10290.0 -2017-03-04 10:00:00,10550.0 -2017-03-04 11:00:00,10632.0 -2017-03-04 12:00:00,10597.0 -2017-03-04 13:00:00,10401.0 -2017-03-04 14:00:00,10209.0 -2017-03-04 15:00:00,10013.0 -2017-03-04 16:00:00,9867.0 -2017-03-04 17:00:00,9794.0 -2017-03-04 18:00:00,9877.0 -2017-03-04 19:00:00,10299.0 -2017-03-04 20:00:00,11020.0 -2017-03-04 21:00:00,11003.0 -2017-03-04 22:00:00,10926.0 -2017-03-04 23:00:00,10672.0 -2017-03-05 00:00:00,10237.0 -2017-03-03 01:00:00,10366.0 -2017-03-03 02:00:00,9984.0 -2017-03-03 03:00:00,9805.0 -2017-03-03 04:00:00,9720.0 -2017-03-03 05:00:00,9723.0 -2017-03-03 06:00:00,10007.0 -2017-03-03 07:00:00,10763.0 -2017-03-03 08:00:00,11630.0 -2017-03-03 09:00:00,11991.0 -2017-03-03 10:00:00,12101.0 -2017-03-03 11:00:00,12115.0 -2017-03-03 12:00:00,12079.0 -2017-03-03 13:00:00,11967.0 -2017-03-03 14:00:00,11814.0 -2017-03-03 15:00:00,11706.0 -2017-03-03 16:00:00,11485.0 -2017-03-03 17:00:00,11315.0 -2017-03-03 18:00:00,11324.0 -2017-03-03 19:00:00,11725.0 -2017-03-03 20:00:00,12220.0 -2017-03-03 21:00:00,12052.0 -2017-03-03 22:00:00,11772.0 -2017-03-03 23:00:00,11395.0 -2017-03-04 00:00:00,10775.0 -2017-03-02 01:00:00,10061.0 -2017-03-02 02:00:00,9641.0 -2017-03-02 03:00:00,9423.0 -2017-03-02 04:00:00,9308.0 -2017-03-02 05:00:00,9316.0 -2017-03-02 06:00:00,9593.0 -2017-03-02 07:00:00,10287.0 -2017-03-02 08:00:00,11240.0 -2017-03-02 09:00:00,11690.0 -2017-03-02 10:00:00,11855.0 -2017-03-02 11:00:00,11791.0 -2017-03-02 12:00:00,11744.0 -2017-03-02 13:00:00,11746.0 -2017-03-02 14:00:00,11816.0 -2017-03-02 15:00:00,11831.0 -2017-03-02 16:00:00,11799.0 -2017-03-02 17:00:00,11820.0 -2017-03-02 18:00:00,11878.0 -2017-03-02 19:00:00,12170.0 -2017-03-02 20:00:00,12487.0 -2017-03-02 21:00:00,12366.0 -2017-03-02 22:00:00,12106.0 -2017-03-02 23:00:00,11651.0 -2017-03-03 00:00:00,10946.0 -2017-03-01 01:00:00,9285.0 -2017-03-01 02:00:00,8782.0 -2017-03-01 03:00:00,8517.0 -2017-03-01 04:00:00,8381.0 -2017-03-01 05:00:00,8335.0 -2017-03-01 06:00:00,8615.0 -2017-03-01 07:00:00,9343.0 -2017-03-01 08:00:00,10378.0 -2017-03-01 09:00:00,10996.0 -2017-03-01 10:00:00,11279.0 -2017-03-01 11:00:00,11428.0 -2017-03-01 12:00:00,11579.0 -2017-03-01 13:00:00,11597.0 -2017-03-01 14:00:00,11593.0 -2017-03-01 15:00:00,11680.0 -2017-03-01 16:00:00,11650.0 -2017-03-01 17:00:00,11700.0 -2017-03-01 18:00:00,11860.0 -2017-03-01 19:00:00,12204.0 -2017-03-01 20:00:00,12437.0 -2017-03-01 21:00:00,12264.0 -2017-03-01 22:00:00,11959.0 -2017-03-01 23:00:00,11459.0 -2017-03-02 00:00:00,10700.0 -2017-02-28 01:00:00,9356.0 -2017-02-28 02:00:00,8960.0 -2017-02-28 03:00:00,8723.0 -2017-02-28 04:00:00,8622.0 -2017-02-28 05:00:00,8575.0 -2017-02-28 06:00:00,8763.0 -2017-02-28 07:00:00,9462.0 -2017-02-28 08:00:00,10354.0 -2017-02-28 09:00:00,10796.0 -2017-02-28 10:00:00,11065.0 -2017-02-28 11:00:00,11131.0 -2017-02-28 12:00:00,11216.0 -2017-02-28 13:00:00,11186.0 -2017-02-28 14:00:00,11120.0 -2017-02-28 15:00:00,11121.0 -2017-02-28 16:00:00,11017.0 -2017-02-28 17:00:00,11007.0 -2017-02-28 18:00:00,11182.0 -2017-02-28 19:00:00,11639.0 -2017-02-28 20:00:00,11595.0 -2017-02-28 21:00:00,11328.0 -2017-02-28 22:00:00,10994.0 -2017-02-28 23:00:00,10552.0 -2017-03-01 00:00:00,9869.0 -2017-02-27 01:00:00,9218.0 -2017-02-27 02:00:00,8884.0 -2017-02-27 03:00:00,8783.0 -2017-02-27 04:00:00,8664.0 -2017-02-27 05:00:00,8718.0 -2017-02-27 06:00:00,9036.0 -2017-02-27 07:00:00,9864.0 -2017-02-27 08:00:00,10862.0 -2017-02-27 09:00:00,11237.0 -2017-02-27 10:00:00,11299.0 -2017-02-27 11:00:00,11234.0 -2017-02-27 12:00:00,11182.0 -2017-02-27 13:00:00,11079.0 -2017-02-27 14:00:00,11012.0 -2017-02-27 15:00:00,10938.0 -2017-02-27 16:00:00,10788.0 -2017-02-27 17:00:00,10705.0 -2017-02-27 18:00:00,10779.0 -2017-02-27 19:00:00,11174.0 -2017-02-27 20:00:00,11534.0 -2017-02-27 21:00:00,11410.0 -2017-02-27 22:00:00,11162.0 -2017-02-27 23:00:00,10692.0 -2017-02-28 00:00:00,9967.0 -2017-02-26 01:00:00,10032.0 -2017-02-26 02:00:00,9684.0 -2017-02-26 03:00:00,9418.0 -2017-02-26 04:00:00,9357.0 -2017-02-26 05:00:00,9241.0 -2017-02-26 06:00:00,9288.0 -2017-02-26 07:00:00,9361.0 -2017-02-26 08:00:00,9620.0 -2017-02-26 09:00:00,9600.0 -2017-02-26 10:00:00,9846.0 -2017-02-26 11:00:00,10023.0 -2017-02-26 12:00:00,9932.0 -2017-02-26 13:00:00,9932.0 -2017-02-26 14:00:00,9673.0 -2017-02-26 15:00:00,9534.0 -2017-02-26 16:00:00,9373.0 -2017-02-26 17:00:00,9347.0 -2017-02-26 18:00:00,9461.0 -2017-02-26 19:00:00,10044.0 -2017-02-26 20:00:00,10641.0 -2017-02-26 21:00:00,10530.0 -2017-02-26 22:00:00,10373.0 -2017-02-26 23:00:00,10049.0 -2017-02-27 00:00:00,9640.0 -2017-02-25 01:00:00,9781.0 -2017-02-25 02:00:00,9420.0 -2017-02-25 03:00:00,9195.0 -2017-02-25 04:00:00,9025.0 -2017-02-25 05:00:00,9039.0 -2017-02-25 06:00:00,9173.0 -2017-02-25 07:00:00,9564.0 -2017-02-25 08:00:00,10031.0 -2017-02-25 09:00:00,10280.0 -2017-02-25 10:00:00,10791.0 -2017-02-25 11:00:00,11081.0 -2017-02-25 12:00:00,11211.0 -2017-02-25 13:00:00,11119.0 -2017-02-25 14:00:00,10989.0 -2017-02-25 15:00:00,10802.0 -2017-02-25 16:00:00,10676.0 -2017-02-25 17:00:00,10709.0 -2017-02-25 18:00:00,10713.0 -2017-02-25 19:00:00,11133.0 -2017-02-25 20:00:00,11509.0 -2017-02-25 21:00:00,11441.0 -2017-02-25 22:00:00,11230.0 -2017-02-25 23:00:00,10982.0 -2017-02-26 00:00:00,10542.0 -2017-02-24 01:00:00,9599.0 -2017-02-24 02:00:00,9201.0 -2017-02-24 03:00:00,8965.0 -2017-02-24 04:00:00,8755.0 -2017-02-24 05:00:00,8768.0 -2017-02-24 06:00:00,8946.0 -2017-02-24 07:00:00,9629.0 -2017-02-24 08:00:00,10637.0 -2017-02-24 09:00:00,11124.0 -2017-02-24 10:00:00,11364.0 -2017-02-24 11:00:00,11553.0 -2017-02-24 12:00:00,11679.0 -2017-02-24 13:00:00,11626.0 -2017-02-24 14:00:00,11613.0 -2017-02-24 15:00:00,11463.0 -2017-02-24 16:00:00,11373.0 -2017-02-24 17:00:00,11294.0 -2017-02-24 18:00:00,11382.0 -2017-02-24 19:00:00,11649.0 -2017-02-24 20:00:00,11741.0 -2017-02-24 21:00:00,11463.0 -2017-02-24 22:00:00,11276.0 -2017-02-24 23:00:00,10893.0 -2017-02-25 00:00:00,10320.0 -2017-02-23 01:00:00,8943.0 -2017-02-23 02:00:00,8502.0 -2017-02-23 03:00:00,8236.0 -2017-02-23 04:00:00,8093.0 -2017-02-23 05:00:00,8067.0 -2017-02-23 06:00:00,8231.0 -2017-02-23 07:00:00,8902.0 -2017-02-23 08:00:00,9841.0 -2017-02-23 09:00:00,10268.0 -2017-02-23 10:00:00,10470.0 -2017-02-23 11:00:00,10655.0 -2017-02-23 12:00:00,10819.0 -2017-02-23 13:00:00,10916.0 -2017-02-23 14:00:00,10962.0 -2017-02-23 15:00:00,11091.0 -2017-02-23 16:00:00,11035.0 -2017-02-23 17:00:00,11096.0 -2017-02-23 18:00:00,11356.0 -2017-02-23 19:00:00,11749.0 -2017-02-23 20:00:00,11852.0 -2017-02-23 21:00:00,11691.0 -2017-02-23 22:00:00,11423.0 -2017-02-23 23:00:00,10917.0 -2017-02-24 00:00:00,10196.0 -2017-02-22 01:00:00,9018.0 -2017-02-22 02:00:00,8654.0 -2017-02-22 03:00:00,8374.0 -2017-02-22 04:00:00,8257.0 -2017-02-22 05:00:00,8225.0 -2017-02-22 06:00:00,8442.0 -2017-02-22 07:00:00,9020.0 -2017-02-22 08:00:00,10036.0 -2017-02-22 09:00:00,10477.0 -2017-02-22 10:00:00,10723.0 -2017-02-22 11:00:00,10826.0 -2017-02-22 12:00:00,10900.0 -2017-02-22 13:00:00,10867.0 -2017-02-22 14:00:00,10855.0 -2017-02-22 15:00:00,10858.0 -2017-02-22 16:00:00,10795.0 -2017-02-22 17:00:00,10748.0 -2017-02-22 18:00:00,10686.0 -2017-02-22 19:00:00,10927.0 -2017-02-22 20:00:00,11331.0 -2017-02-22 21:00:00,11133.0 -2017-02-22 22:00:00,10825.0 -2017-02-22 23:00:00,10318.0 -2017-02-23 00:00:00,9566.0 -2017-02-21 01:00:00,8858.0 -2017-02-21 02:00:00,8403.0 -2017-02-21 03:00:00,8122.0 -2017-02-21 04:00:00,7995.0 -2017-02-21 05:00:00,8008.0 -2017-02-21 06:00:00,8251.0 -2017-02-21 07:00:00,8903.0 -2017-02-21 08:00:00,9923.0 -2017-02-21 09:00:00,10435.0 -2017-02-21 10:00:00,10686.0 -2017-02-21 11:00:00,10797.0 -2017-02-21 12:00:00,10878.0 -2017-02-21 13:00:00,10896.0 -2017-02-21 14:00:00,10895.0 -2017-02-21 15:00:00,10975.0 -2017-02-21 16:00:00,10890.0 -2017-02-21 17:00:00,10757.0 -2017-02-21 18:00:00,10720.0 -2017-02-21 19:00:00,10982.0 -2017-02-21 20:00:00,11321.0 -2017-02-21 21:00:00,11121.0 -2017-02-21 22:00:00,10774.0 -2017-02-21 23:00:00,10267.0 -2017-02-22 00:00:00,9630.0 -2017-02-20 01:00:00,8529.0 -2017-02-20 02:00:00,8244.0 -2017-02-20 03:00:00,8065.0 -2017-02-20 04:00:00,7991.0 -2017-02-20 05:00:00,8033.0 -2017-02-20 06:00:00,8302.0 -2017-02-20 07:00:00,8917.0 -2017-02-20 08:00:00,9734.0 -2017-02-20 09:00:00,10112.0 -2017-02-20 10:00:00,10350.0 -2017-02-20 11:00:00,10485.0 -2017-02-20 12:00:00,10606.0 -2017-02-20 13:00:00,10665.0 -2017-02-20 14:00:00,10609.0 -2017-02-20 15:00:00,10645.0 -2017-02-20 16:00:00,10554.0 -2017-02-20 17:00:00,10494.0 -2017-02-20 18:00:00,10523.0 -2017-02-20 19:00:00,11023.0 -2017-02-20 20:00:00,11235.0 -2017-02-20 21:00:00,11046.0 -2017-02-20 22:00:00,10681.0 -2017-02-20 23:00:00,10165.0 -2017-02-21 00:00:00,9472.0 -2017-02-19 01:00:00,8474.0 -2017-02-19 02:00:00,8192.0 -2017-02-19 03:00:00,7974.0 -2017-02-19 04:00:00,7886.0 -2017-02-19 05:00:00,7783.0 -2017-02-19 06:00:00,7904.0 -2017-02-19 07:00:00,8060.0 -2017-02-19 08:00:00,8272.0 -2017-02-19 09:00:00,8311.0 -2017-02-19 10:00:00,8460.0 -2017-02-19 11:00:00,8608.0 -2017-02-19 12:00:00,8689.0 -2017-02-19 13:00:00,8728.0 -2017-02-19 14:00:00,8693.0 -2017-02-19 15:00:00,8649.0 -2017-02-19 16:00:00,8612.0 -2017-02-19 17:00:00,8592.0 -2017-02-19 18:00:00,8742.0 -2017-02-19 19:00:00,9278.0 -2017-02-19 20:00:00,9754.0 -2017-02-19 21:00:00,9683.0 -2017-02-19 22:00:00,9547.0 -2017-02-19 23:00:00,9297.0 -2017-02-20 00:00:00,8914.0 -2017-02-18 01:00:00,9146.0 -2017-02-18 02:00:00,8699.0 -2017-02-18 03:00:00,8454.0 -2017-02-18 04:00:00,8299.0 -2017-02-18 05:00:00,8289.0 -2017-02-18 06:00:00,8299.0 -2017-02-18 07:00:00,8672.0 -2017-02-18 08:00:00,9005.0 -2017-02-18 09:00:00,9105.0 -2017-02-18 10:00:00,9335.0 -2017-02-18 11:00:00,9448.0 -2017-02-18 12:00:00,9490.0 -2017-02-18 13:00:00,9421.0 -2017-02-18 14:00:00,9354.0 -2017-02-18 15:00:00,9192.0 -2017-02-18 16:00:00,9078.0 -2017-02-18 17:00:00,8994.0 -2017-02-18 18:00:00,9030.0 -2017-02-18 19:00:00,9398.0 -2017-02-18 20:00:00,9806.0 -2017-02-18 21:00:00,9677.0 -2017-02-18 22:00:00,9546.0 -2017-02-18 23:00:00,9281.0 -2017-02-19 00:00:00,8871.0 -2017-02-17 01:00:00,9893.0 -2017-02-17 02:00:00,9543.0 -2017-02-17 03:00:00,9298.0 -2017-02-17 04:00:00,9178.0 -2017-02-17 05:00:00,9171.0 -2017-02-17 06:00:00,9461.0 -2017-02-17 07:00:00,10175.0 -2017-02-17 08:00:00,11126.0 -2017-02-17 09:00:00,11376.0 -2017-02-17 10:00:00,11388.0 -2017-02-17 11:00:00,11283.0 -2017-02-17 12:00:00,11179.0 -2017-02-17 13:00:00,11110.0 -2017-02-17 14:00:00,10920.0 -2017-02-17 15:00:00,10855.0 -2017-02-17 16:00:00,10739.0 -2017-02-17 17:00:00,10613.0 -2017-02-17 18:00:00,10554.0 -2017-02-17 19:00:00,10874.0 -2017-02-17 20:00:00,11146.0 -2017-02-17 21:00:00,10950.0 -2017-02-17 22:00:00,10679.0 -2017-02-17 23:00:00,10266.0 -2017-02-18 00:00:00,9753.0 -2017-02-16 01:00:00,10129.0 -2017-02-16 02:00:00,9711.0 -2017-02-16 03:00:00,9514.0 -2017-02-16 04:00:00,9398.0 -2017-02-16 05:00:00,9383.0 -2017-02-16 06:00:00,9655.0 -2017-02-16 07:00:00,10324.0 -2017-02-16 08:00:00,11374.0 -2017-02-16 09:00:00,11784.0 -2017-02-16 10:00:00,11969.0 -2017-02-16 11:00:00,11987.0 -2017-02-16 12:00:00,11902.0 -2017-02-16 13:00:00,11773.0 -2017-02-16 14:00:00,11648.0 -2017-02-16 15:00:00,11603.0 -2017-02-16 16:00:00,11419.0 -2017-02-16 17:00:00,11459.0 -2017-02-16 18:00:00,11623.0 -2017-02-16 19:00:00,12122.0 -2017-02-16 20:00:00,12167.0 -2017-02-16 21:00:00,11963.0 -2017-02-16 22:00:00,11721.0 -2017-02-16 23:00:00,11218.0 -2017-02-17 00:00:00,10523.0 -2017-02-15 01:00:00,9848.0 -2017-02-15 02:00:00,9513.0 -2017-02-15 03:00:00,9351.0 -2017-02-15 04:00:00,9218.0 -2017-02-15 05:00:00,9292.0 -2017-02-15 06:00:00,9628.0 -2017-02-15 07:00:00,10440.0 -2017-02-15 08:00:00,11425.0 -2017-02-15 09:00:00,11836.0 -2017-02-15 10:00:00,11854.0 -2017-02-15 11:00:00,11873.0 -2017-02-15 12:00:00,11885.0 -2017-02-15 13:00:00,11840.0 -2017-02-15 14:00:00,11770.0 -2017-02-15 15:00:00,11697.0 -2017-02-15 16:00:00,11540.0 -2017-02-15 17:00:00,11534.0 -2017-02-15 18:00:00,11607.0 -2017-02-15 19:00:00,12227.0 -2017-02-15 20:00:00,12438.0 -2017-02-15 21:00:00,12255.0 -2017-02-15 22:00:00,11971.0 -2017-02-15 23:00:00,11427.0 -2017-02-16 00:00:00,10762.0 -2017-02-14 01:00:00,9992.0 -2017-02-14 02:00:00,9594.0 -2017-02-14 03:00:00,9370.0 -2017-02-14 04:00:00,9264.0 -2017-02-14 05:00:00,9273.0 -2017-02-14 06:00:00,9569.0 -2017-02-14 07:00:00,10296.0 -2017-02-14 08:00:00,11299.0 -2017-02-14 09:00:00,11640.0 -2017-02-14 10:00:00,11645.0 -2017-02-14 11:00:00,11562.0 -2017-02-14 12:00:00,11517.0 -2017-02-14 13:00:00,11387.0 -2017-02-14 14:00:00,11244.0 -2017-02-14 15:00:00,11190.0 -2017-02-14 16:00:00,11023.0 -2017-02-14 17:00:00,10917.0 -2017-02-14 18:00:00,11016.0 -2017-02-14 19:00:00,11609.0 -2017-02-14 20:00:00,11853.0 -2017-02-14 21:00:00,11769.0 -2017-02-14 22:00:00,11514.0 -2017-02-14 23:00:00,11106.0 -2017-02-15 00:00:00,10474.0 -2017-02-13 01:00:00,9572.0 -2017-02-13 02:00:00,9305.0 -2017-02-13 03:00:00,9181.0 -2017-02-13 04:00:00,9066.0 -2017-02-13 05:00:00,9172.0 -2017-02-13 06:00:00,9512.0 -2017-02-13 07:00:00,10343.0 -2017-02-13 08:00:00,11453.0 -2017-02-13 09:00:00,11832.0 -2017-02-13 10:00:00,11818.0 -2017-02-13 11:00:00,11738.0 -2017-02-13 12:00:00,11672.0 -2017-02-13 13:00:00,11580.0 -2017-02-13 14:00:00,11460.0 -2017-02-13 15:00:00,11369.0 -2017-02-13 16:00:00,11226.0 -2017-02-13 17:00:00,11088.0 -2017-02-13 18:00:00,11212.0 -2017-02-13 19:00:00,11836.0 -2017-02-13 20:00:00,12155.0 -2017-02-13 21:00:00,12012.0 -2017-02-13 22:00:00,11773.0 -2017-02-13 23:00:00,11296.0 -2017-02-14 00:00:00,10661.0 -2017-02-12 01:00:00,9431.0 -2017-02-12 02:00:00,9089.0 -2017-02-12 03:00:00,8849.0 -2017-02-12 04:00:00,8729.0 -2017-02-12 05:00:00,8665.0 -2017-02-12 06:00:00,8666.0 -2017-02-12 07:00:00,8836.0 -2017-02-12 08:00:00,9105.0 -2017-02-12 09:00:00,9160.0 -2017-02-12 10:00:00,9319.0 -2017-02-12 11:00:00,9403.0 -2017-02-12 12:00:00,9478.0 -2017-02-12 13:00:00,9559.0 -2017-02-12 14:00:00,9526.0 -2017-02-12 15:00:00,9476.0 -2017-02-12 16:00:00,9444.0 -2017-02-12 17:00:00,9469.0 -2017-02-12 18:00:00,9680.0 -2017-02-12 19:00:00,10460.0 -2017-02-12 20:00:00,10942.0 -2017-02-12 21:00:00,10890.0 -2017-02-12 22:00:00,10744.0 -2017-02-12 23:00:00,10429.0 -2017-02-13 00:00:00,9976.0 -2017-02-11 01:00:00,10065.0 -2017-02-11 02:00:00,9573.0 -2017-02-11 03:00:00,9311.0 -2017-02-11 04:00:00,9134.0 -2017-02-11 05:00:00,9067.0 -2017-02-11 06:00:00,9161.0 -2017-02-11 07:00:00,9365.0 -2017-02-11 08:00:00,9878.0 -2017-02-11 09:00:00,10064.0 -2017-02-11 10:00:00,10378.0 -2017-02-11 11:00:00,10421.0 -2017-02-11 12:00:00,10399.0 -2017-02-11 13:00:00,10312.0 -2017-02-11 14:00:00,10136.0 -2017-02-11 15:00:00,9940.0 -2017-02-11 16:00:00,9875.0 -2017-02-11 17:00:00,9895.0 -2017-02-11 18:00:00,10059.0 -2017-02-11 19:00:00,10647.0 -2017-02-11 20:00:00,10804.0 -2017-02-11 21:00:00,10725.0 -2017-02-11 22:00:00,10544.0 -2017-02-11 23:00:00,10327.0 -2017-02-12 00:00:00,9899.0 -2017-02-10 01:00:00,11200.0 -2017-02-10 02:00:00,10806.0 -2017-02-10 03:00:00,10587.0 -2017-02-10 04:00:00,10455.0 -2017-02-10 05:00:00,10402.0 -2017-02-10 06:00:00,10694.0 -2017-02-10 07:00:00,11349.0 -2017-02-10 08:00:00,12386.0 -2017-02-10 09:00:00,12834.0 -2017-02-10 10:00:00,12854.0 -2017-02-10 11:00:00,12761.0 -2017-02-10 12:00:00,12719.0 -2017-02-10 13:00:00,12522.0 -2017-02-10 14:00:00,12324.0 -2017-02-10 15:00:00,12109.0 -2017-02-10 16:00:00,11853.0 -2017-02-10 17:00:00,11636.0 -2017-02-10 18:00:00,11746.0 -2017-02-10 19:00:00,12302.0 -2017-02-10 20:00:00,12366.0 -2017-02-10 21:00:00,12136.0 -2017-02-10 22:00:00,11827.0 -2017-02-10 23:00:00,11390.0 -2017-02-11 00:00:00,10740.0 -2017-02-09 01:00:00,10965.0 -2017-02-09 02:00:00,10650.0 -2017-02-09 03:00:00,10499.0 -2017-02-09 04:00:00,10415.0 -2017-02-09 05:00:00,10453.0 -2017-02-09 06:00:00,10772.0 -2017-02-09 07:00:00,11557.0 -2017-02-09 08:00:00,12572.0 -2017-02-09 09:00:00,12947.0 -2017-02-09 10:00:00,12970.0 -2017-02-09 11:00:00,12937.0 -2017-02-09 12:00:00,12870.0 -2017-02-09 13:00:00,12771.0 -2017-02-09 14:00:00,12657.0 -2017-02-09 15:00:00,12578.0 -2017-02-09 16:00:00,12400.0 -2017-02-09 17:00:00,12300.0 -2017-02-09 18:00:00,12459.0 -2017-02-09 19:00:00,13145.0 -2017-02-09 20:00:00,13371.0 -2017-02-09 21:00:00,13265.0 -2017-02-09 22:00:00,13043.0 -2017-02-09 23:00:00,12562.0 -2017-02-10 00:00:00,11841.0 -2017-02-08 01:00:00,10052.0 -2017-02-08 02:00:00,9655.0 -2017-02-08 03:00:00,9454.0 -2017-02-08 04:00:00,9337.0 -2017-02-08 05:00:00,9328.0 -2017-02-08 06:00:00,9631.0 -2017-02-08 07:00:00,10418.0 -2017-02-08 08:00:00,11595.0 -2017-02-08 09:00:00,12071.0 -2017-02-08 10:00:00,12233.0 -2017-02-08 11:00:00,12323.0 -2017-02-08 12:00:00,12448.0 -2017-02-08 13:00:00,12502.0 -2017-02-08 14:00:00,12528.0 -2017-02-08 15:00:00,12526.0 -2017-02-08 16:00:00,12405.0 -2017-02-08 17:00:00,12327.0 -2017-02-08 18:00:00,12392.0 -2017-02-08 19:00:00,12879.0 -2017-02-08 20:00:00,13053.0 -2017-02-08 21:00:00,12910.0 -2017-02-08 22:00:00,12663.0 -2017-02-08 23:00:00,12234.0 -2017-02-09 00:00:00,11551.0 -2017-02-07 01:00:00,9561.0 -2017-02-07 02:00:00,9150.0 -2017-02-07 03:00:00,8951.0 -2017-02-07 04:00:00,8825.0 -2017-02-07 05:00:00,8806.0 -2017-02-07 06:00:00,9047.0 -2017-02-07 07:00:00,9781.0 -2017-02-07 08:00:00,10832.0 -2017-02-07 09:00:00,11401.0 -2017-02-07 10:00:00,11484.0 -2017-02-07 11:00:00,11637.0 -2017-02-07 12:00:00,11705.0 -2017-02-07 13:00:00,11607.0 -2017-02-07 14:00:00,11572.0 -2017-02-07 15:00:00,11592.0 -2017-02-07 16:00:00,11571.0 -2017-02-07 17:00:00,11644.0 -2017-02-07 18:00:00,11839.0 -2017-02-07 19:00:00,12311.0 -2017-02-07 20:00:00,12258.0 -2017-02-07 21:00:00,12085.0 -2017-02-07 22:00:00,11864.0 -2017-02-07 23:00:00,11380.0 -2017-02-08 00:00:00,10716.0 -2017-02-06 01:00:00,9754.0 -2017-02-06 02:00:00,9482.0 -2017-02-06 03:00:00,9328.0 -2017-02-06 04:00:00,9298.0 -2017-02-06 05:00:00,9419.0 -2017-02-06 06:00:00,9724.0 -2017-02-06 07:00:00,10501.0 -2017-02-06 08:00:00,11565.0 -2017-02-06 09:00:00,11963.0 -2017-02-06 10:00:00,11963.0 -2017-02-06 11:00:00,11831.0 -2017-02-06 12:00:00,11698.0 -2017-02-06 13:00:00,11512.0 -2017-02-06 14:00:00,11373.0 -2017-02-06 15:00:00,11263.0 -2017-02-06 16:00:00,11112.0 -2017-02-06 17:00:00,11079.0 -2017-02-06 18:00:00,11413.0 -2017-02-06 19:00:00,11995.0 -2017-02-06 20:00:00,11970.0 -2017-02-06 21:00:00,11765.0 -2017-02-06 22:00:00,11449.0 -2017-02-06 23:00:00,10924.0 -2017-02-07 00:00:00,10229.0 -2017-02-05 01:00:00,10256.0 -2017-02-05 02:00:00,9883.0 -2017-02-05 03:00:00,9652.0 -2017-02-05 04:00:00,9422.0 -2017-02-05 05:00:00,9394.0 -2017-02-05 06:00:00,9423.0 -2017-02-05 07:00:00,9555.0 -2017-02-05 08:00:00,9827.0 -2017-02-05 09:00:00,9829.0 -2017-02-05 10:00:00,9904.0 -2017-02-05 11:00:00,9969.0 -2017-02-05 12:00:00,9994.0 -2017-02-05 13:00:00,10016.0 -2017-02-05 14:00:00,9996.0 -2017-02-05 15:00:00,9944.0 -2017-02-05 16:00:00,9835.0 -2017-02-05 17:00:00,9843.0 -2017-02-05 18:00:00,10114.0 -2017-02-05 19:00:00,10829.0 -2017-02-05 20:00:00,11036.0 -2017-02-05 21:00:00,10925.0 -2017-02-05 22:00:00,10821.0 -2017-02-05 23:00:00,10602.0 -2017-02-06 00:00:00,10238.0 -2017-02-04 01:00:00,11167.0 -2017-02-04 02:00:00,10735.0 -2017-02-04 03:00:00,10549.0 -2017-02-04 04:00:00,10364.0 -2017-02-04 05:00:00,10344.0 -2017-02-04 06:00:00,10393.0 -2017-02-04 07:00:00,10753.0 -2017-02-04 08:00:00,11170.0 -2017-02-04 09:00:00,11322.0 -2017-02-04 10:00:00,11535.0 -2017-02-04 11:00:00,11687.0 -2017-02-04 12:00:00,11767.0 -2017-02-04 13:00:00,11726.0 -2017-02-04 14:00:00,11626.0 -2017-02-04 15:00:00,11477.0 -2017-02-04 16:00:00,11344.0 -2017-02-04 17:00:00,11299.0 -2017-02-04 18:00:00,11507.0 -2017-02-04 19:00:00,12003.0 -2017-02-04 20:00:00,11959.0 -2017-02-04 21:00:00,11796.0 -2017-02-04 22:00:00,11543.0 -2017-02-04 23:00:00,11269.0 -2017-02-05 00:00:00,10726.0 -2017-02-03 01:00:00,11305.0 -2017-02-03 02:00:00,10909.0 -2017-02-03 03:00:00,10684.0 -2017-02-03 04:00:00,10575.0 -2017-02-03 05:00:00,10613.0 -2017-02-03 06:00:00,10844.0 -2017-02-03 07:00:00,11552.0 -2017-02-03 08:00:00,12530.0 -2017-02-03 09:00:00,12987.0 -2017-02-03 10:00:00,13018.0 -2017-02-03 11:00:00,12882.0 -2017-02-03 12:00:00,12817.0 -2017-02-03 13:00:00,12658.0 -2017-02-03 14:00:00,12490.0 -2017-02-03 15:00:00,12410.0 -2017-02-03 16:00:00,12227.0 -2017-02-03 17:00:00,12098.0 -2017-02-03 18:00:00,12267.0 -2017-02-03 19:00:00,12926.0 -2017-02-03 20:00:00,13034.0 -2017-02-03 21:00:00,12847.0 -2017-02-03 22:00:00,12640.0 -2017-02-03 23:00:00,12314.0 -2017-02-04 00:00:00,11732.0 -2017-02-02 01:00:00,10880.0 -2017-02-02 02:00:00,10551.0 -2017-02-02 03:00:00,10404.0 -2017-02-02 04:00:00,10355.0 -2017-02-02 05:00:00,10396.0 -2017-02-02 06:00:00,10696.0 -2017-02-02 07:00:00,11401.0 -2017-02-02 08:00:00,12497.0 -2017-02-02 09:00:00,12868.0 -2017-02-02 10:00:00,12884.0 -2017-02-02 11:00:00,12862.0 -2017-02-02 12:00:00,12810.0 -2017-02-02 13:00:00,12707.0 -2017-02-02 14:00:00,12594.0 -2017-02-02 15:00:00,12518.0 -2017-02-02 16:00:00,12460.0 -2017-02-02 17:00:00,12520.0 -2017-02-02 18:00:00,12683.0 -2017-02-02 19:00:00,13361.0 -2017-02-02 20:00:00,13427.0 -2017-02-02 21:00:00,13275.0 -2017-02-02 22:00:00,13065.0 -2017-02-02 23:00:00,12585.0 -2017-02-03 00:00:00,11869.0 -2017-02-01 01:00:00,10366.0 -2017-02-01 02:00:00,9909.0 -2017-02-01 03:00:00,9669.0 -2017-02-01 04:00:00,9510.0 -2017-02-01 05:00:00,9514.0 -2017-02-01 06:00:00,9796.0 -2017-02-01 07:00:00,10513.0 -2017-02-01 08:00:00,11592.0 -2017-02-01 09:00:00,12014.0 -2017-02-01 10:00:00,12092.0 -2017-02-01 11:00:00,12140.0 -2017-02-01 12:00:00,12132.0 -2017-02-01 13:00:00,11985.0 -2017-02-01 14:00:00,11971.0 -2017-02-01 15:00:00,12013.0 -2017-02-01 16:00:00,11990.0 -2017-02-01 17:00:00,11920.0 -2017-02-01 18:00:00,12114.0 -2017-02-01 19:00:00,12760.0 -2017-02-01 20:00:00,12894.0 -2017-02-01 21:00:00,12768.0 -2017-02-01 22:00:00,12499.0 -2017-02-01 23:00:00,12087.0 -2017-02-02 00:00:00,11453.0 -2017-01-31 01:00:00,10529.0 -2017-01-31 02:00:00,10109.0 -2017-01-31 03:00:00,9815.0 -2017-01-31 04:00:00,9748.0 -2017-01-31 05:00:00,9735.0 -2017-01-31 06:00:00,9958.0 -2017-01-31 07:00:00,10645.0 -2017-01-31 08:00:00,11719.0 -2017-01-31 09:00:00,12298.0 -2017-01-31 10:00:00,12382.0 -2017-01-31 11:00:00,12440.0 -2017-01-31 12:00:00,12486.0 -2017-01-31 13:00:00,12490.0 -2017-01-31 14:00:00,12389.0 -2017-01-31 15:00:00,12343.0 -2017-01-31 16:00:00,12086.0 -2017-01-31 17:00:00,12130.0 -2017-01-31 18:00:00,12308.0 -2017-01-31 19:00:00,12785.0 -2017-01-31 20:00:00,12735.0 -2017-01-31 21:00:00,12497.0 -2017-01-31 22:00:00,12224.0 -2017-01-31 23:00:00,11691.0 -2017-02-01 00:00:00,10996.0 -2017-01-30 01:00:00,10393.0 -2017-01-30 02:00:00,10145.0 -2017-01-30 03:00:00,10031.0 -2017-01-30 04:00:00,10003.0 -2017-01-30 05:00:00,10079.0 -2017-01-30 06:00:00,10506.0 -2017-01-30 07:00:00,11263.0 -2017-01-30 08:00:00,12420.0 -2017-01-30 09:00:00,12850.0 -2017-01-30 10:00:00,12889.0 -2017-01-30 11:00:00,12728.0 -2017-01-30 12:00:00,12706.0 -2017-01-30 13:00:00,12640.0 -2017-01-30 14:00:00,12617.0 -2017-01-30 15:00:00,12710.0 -2017-01-30 16:00:00,12678.0 -2017-01-30 17:00:00,12601.0 -2017-01-30 18:00:00,12959.0 -2017-01-30 19:00:00,13457.0 -2017-01-30 20:00:00,13267.0 -2017-01-30 21:00:00,12973.0 -2017-01-30 22:00:00,12631.0 -2017-01-30 23:00:00,12072.0 -2017-01-31 00:00:00,11272.0 -2017-01-29 01:00:00,10278.0 -2017-01-29 02:00:00,9955.0 -2017-01-29 03:00:00,9629.0 -2017-01-29 04:00:00,9460.0 -2017-01-29 05:00:00,9395.0 -2017-01-29 06:00:00,9445.0 -2017-01-29 07:00:00,9527.0 -2017-01-29 08:00:00,9863.0 -2017-01-29 09:00:00,10015.0 -2017-01-29 10:00:00,10276.0 -2017-01-29 11:00:00,10511.0 -2017-01-29 12:00:00,10712.0 -2017-01-29 13:00:00,10774.0 -2017-01-29 14:00:00,10850.0 -2017-01-29 15:00:00,10827.0 -2017-01-29 16:00:00,10906.0 -2017-01-29 17:00:00,10998.0 -2017-01-29 18:00:00,11424.0 -2017-01-29 19:00:00,11967.0 -2017-01-29 20:00:00,11977.0 -2017-01-29 21:00:00,11908.0 -2017-01-29 22:00:00,11678.0 -2017-01-29 23:00:00,11328.0 -2017-01-30 00:00:00,10890.0 -2017-01-28 01:00:00,10962.0 -2017-01-28 02:00:00,10510.0 -2017-01-28 03:00:00,10265.0 -2017-01-28 04:00:00,10058.0 -2017-01-28 05:00:00,9984.0 -2017-01-28 06:00:00,10020.0 -2017-01-28 07:00:00,10248.0 -2017-01-28 08:00:00,10637.0 -2017-01-28 09:00:00,10973.0 -2017-01-28 10:00:00,11264.0 -2017-01-28 11:00:00,11501.0 -2017-01-28 12:00:00,11644.0 -2017-01-28 13:00:00,11638.0 -2017-01-28 14:00:00,11580.0 -2017-01-28 15:00:00,11389.0 -2017-01-28 16:00:00,11300.0 -2017-01-28 17:00:00,11239.0 -2017-01-28 18:00:00,11573.0 -2017-01-28 19:00:00,11958.0 -2017-01-28 20:00:00,11932.0 -2017-01-28 21:00:00,11784.0 -2017-01-28 22:00:00,11585.0 -2017-01-28 23:00:00,11238.0 -2017-01-29 00:00:00,10798.0 -2017-01-27 01:00:00,10437.0 -2017-01-27 02:00:00,10076.0 -2017-01-27 03:00:00,9847.0 -2017-01-27 04:00:00,9766.0 -2017-01-27 05:00:00,9790.0 -2017-01-27 06:00:00,10045.0 -2017-01-27 07:00:00,10740.0 -2017-01-27 08:00:00,11812.0 -2017-01-27 09:00:00,12469.0 -2017-01-27 10:00:00,12610.0 -2017-01-27 11:00:00,12777.0 -2017-01-27 12:00:00,12873.0 -2017-01-27 13:00:00,12851.0 -2017-01-27 14:00:00,12773.0 -2017-01-27 15:00:00,12765.0 -2017-01-27 16:00:00,12749.0 -2017-01-27 17:00:00,12752.0 -2017-01-27 18:00:00,12954.0 -2017-01-27 19:00:00,13351.0 -2017-01-27 20:00:00,13160.0 -2017-01-27 21:00:00,12905.0 -2017-01-27 22:00:00,12627.0 -2017-01-27 23:00:00,12218.0 -2017-01-28 00:00:00,11554.0 -2017-01-26 01:00:00,10307.0 -2017-01-26 02:00:00,9869.0 -2017-01-26 03:00:00,9608.0 -2017-01-26 04:00:00,9505.0 -2017-01-26 05:00:00,9457.0 -2017-01-26 06:00:00,9775.0 -2017-01-26 07:00:00,10536.0 -2017-01-26 08:00:00,11625.0 -2017-01-26 09:00:00,12158.0 -2017-01-26 10:00:00,12290.0 -2017-01-26 11:00:00,12343.0 -2017-01-26 12:00:00,12443.0 -2017-01-26 13:00:00,12451.0 -2017-01-26 14:00:00,12388.0 -2017-01-26 15:00:00,12360.0 -2017-01-26 16:00:00,12287.0 -2017-01-26 17:00:00,12290.0 -2017-01-26 18:00:00,12533.0 -2017-01-26 19:00:00,12973.0 -2017-01-26 20:00:00,12870.0 -2017-01-26 21:00:00,12632.0 -2017-01-26 22:00:00,12356.0 -2017-01-26 23:00:00,11821.0 -2017-01-27 00:00:00,11097.0 -2017-01-25 01:00:00,10165.0 -2017-01-25 02:00:00,9755.0 -2017-01-25 03:00:00,9514.0 -2017-01-25 04:00:00,9441.0 -2017-01-25 05:00:00,9414.0 -2017-01-25 06:00:00,9685.0 -2017-01-25 07:00:00,10381.0 -2017-01-25 08:00:00,11397.0 -2017-01-25 09:00:00,11901.0 -2017-01-25 10:00:00,11961.0 -2017-01-25 11:00:00,12003.0 -2017-01-25 12:00:00,12064.0 -2017-01-25 13:00:00,12060.0 -2017-01-25 14:00:00,12116.0 -2017-01-25 15:00:00,12169.0 -2017-01-25 16:00:00,12172.0 -2017-01-25 17:00:00,12175.0 -2017-01-25 18:00:00,12481.0 -2017-01-25 19:00:00,12794.0 -2017-01-25 20:00:00,12624.0 -2017-01-25 21:00:00,12425.0 -2017-01-25 22:00:00,12131.0 -2017-01-25 23:00:00,11592.0 -2017-01-26 00:00:00,10928.0 -2017-01-24 01:00:00,9845.0 -2017-01-24 02:00:00,9427.0 -2017-01-24 03:00:00,9159.0 -2017-01-24 04:00:00,9058.0 -2017-01-24 05:00:00,9076.0 -2017-01-24 06:00:00,9368.0 -2017-01-24 07:00:00,10043.0 -2017-01-24 08:00:00,11089.0 -2017-01-24 09:00:00,11785.0 -2017-01-24 10:00:00,11928.0 -2017-01-24 11:00:00,11994.0 -2017-01-24 12:00:00,12062.0 -2017-01-24 13:00:00,12090.0 -2017-01-24 14:00:00,12009.0 -2017-01-24 15:00:00,11999.0 -2017-01-24 16:00:00,11954.0 -2017-01-24 17:00:00,11905.0 -2017-01-24 18:00:00,12156.0 -2017-01-24 19:00:00,12640.0 -2017-01-24 20:00:00,12532.0 -2017-01-24 21:00:00,12346.0 -2017-01-24 22:00:00,12023.0 -2017-01-24 23:00:00,11532.0 -2017-01-25 00:00:00,10834.0 -2017-01-23 01:00:00,9561.0 -2017-01-23 02:00:00,8914.0 -2017-01-23 03:00:00,8781.0 -2017-01-23 04:00:00,8687.0 -2017-01-23 05:00:00,8771.0 -2017-01-23 06:00:00,9052.0 -2017-01-23 07:00:00,9872.0 -2017-01-23 08:00:00,11056.0 -2017-01-23 09:00:00,11711.0 -2017-01-23 10:00:00,11737.0 -2017-01-23 11:00:00,11866.0 -2017-01-23 12:00:00,11949.0 -2017-01-23 13:00:00,11891.0 -2017-01-23 14:00:00,11870.0 -2017-01-23 15:00:00,11839.0 -2017-01-23 16:00:00,11691.0 -2017-01-23 17:00:00,11608.0 -2017-01-23 18:00:00,11886.0 -2017-01-23 19:00:00,12371.0 -2017-01-23 20:00:00,12239.0 -2017-01-23 21:00:00,12013.0 -2017-01-23 22:00:00,11720.0 -2017-01-23 23:00:00,11213.0 -2017-01-24 00:00:00,10516.0 -2017-01-22 01:00:00,9009.0 -2017-01-22 02:00:00,8673.0 -2017-01-22 03:00:00,8416.0 -2017-01-22 04:00:00,8272.0 -2017-01-22 05:00:00,8218.0 -2017-01-22 06:00:00,8289.0 -2017-01-22 07:00:00,8401.0 -2017-01-22 08:00:00,8720.0 -2017-01-22 09:00:00,8813.0 -2017-01-22 10:00:00,9079.0 -2017-01-22 11:00:00,9300.0 -2017-01-22 12:00:00,9440.0 -2017-01-22 13:00:00,9610.0 -2017-01-22 14:00:00,9668.0 -2017-01-22 15:00:00,9654.0 -2017-01-22 16:00:00,9678.0 -2017-01-22 17:00:00,9744.0 -2017-01-22 18:00:00,10187.0 -2017-01-22 19:00:00,10840.0 -2017-01-22 20:00:00,10890.0 -2017-01-22 21:00:00,10739.0 -2017-01-22 22:00:00,10567.0 -2017-01-22 23:00:00,10172.0 -2017-01-23 00:00:00,9708.0 -2017-01-21 01:00:00,9855.0 -2017-01-21 02:00:00,9405.0 -2017-01-21 03:00:00,9063.0 -2017-01-21 04:00:00,8905.0 -2017-01-21 05:00:00,8765.0 -2017-01-21 06:00:00,8865.0 -2017-01-21 07:00:00,9080.0 -2017-01-21 08:00:00,9496.0 -2017-01-21 09:00:00,9754.0 -2017-01-21 10:00:00,9869.0 -2017-01-21 11:00:00,10007.0 -2017-01-21 12:00:00,10016.0 -2017-01-21 13:00:00,9909.0 -2017-01-21 14:00:00,9812.0 -2017-01-21 15:00:00,9635.0 -2017-01-21 16:00:00,9483.0 -2017-01-21 17:00:00,9414.0 -2017-01-21 18:00:00,9613.0 -2017-01-21 19:00:00,10302.0 -2017-01-21 20:00:00,10347.0 -2017-01-21 21:00:00,10246.0 -2017-01-21 22:00:00,10098.0 -2017-01-21 23:00:00,9858.0 -2017-01-22 00:00:00,9477.0 -2017-01-20 01:00:00,10184.0 -2017-01-20 02:00:00,9768.0 -2017-01-20 03:00:00,9509.0 -2017-01-20 04:00:00,9378.0 -2017-01-20 05:00:00,9351.0 -2017-01-20 06:00:00,9593.0 -2017-01-20 07:00:00,10230.0 -2017-01-20 08:00:00,11236.0 -2017-01-20 09:00:00,11862.0 -2017-01-20 10:00:00,11950.0 -2017-01-20 11:00:00,12040.0 -2017-01-20 12:00:00,12035.0 -2017-01-20 13:00:00,11980.0 -2017-01-20 14:00:00,11909.0 -2017-01-20 15:00:00,11831.0 -2017-01-20 16:00:00,11765.0 -2017-01-20 17:00:00,11739.0 -2017-01-20 18:00:00,12033.0 -2017-01-20 19:00:00,12342.0 -2017-01-20 20:00:00,12133.0 -2017-01-20 21:00:00,11904.0 -2017-01-20 22:00:00,11508.0 -2017-01-20 23:00:00,11168.0 -2017-01-21 00:00:00,10509.0 -2017-01-19 01:00:00,10470.0 -2017-01-19 02:00:00,10030.0 -2017-01-19 03:00:00,9780.0 -2017-01-19 04:00:00,9599.0 -2017-01-19 05:00:00,9562.0 -2017-01-19 06:00:00,9780.0 -2017-01-19 07:00:00,10479.0 -2017-01-19 08:00:00,11525.0 -2017-01-19 09:00:00,12060.0 -2017-01-19 10:00:00,12089.0 -2017-01-19 11:00:00,12105.0 -2017-01-19 12:00:00,12145.0 -2017-01-19 13:00:00,12083.0 -2017-01-19 14:00:00,11966.0 -2017-01-19 15:00:00,11976.0 -2017-01-19 16:00:00,11917.0 -2017-01-19 17:00:00,12011.0 -2017-01-19 18:00:00,12393.0 -2017-01-19 19:00:00,12756.0 -2017-01-19 20:00:00,12559.0 -2017-01-19 21:00:00,12337.0 -2017-01-19 22:00:00,12115.0 -2017-01-19 23:00:00,11580.0 -2017-01-20 00:00:00,10827.0 -2017-01-18 01:00:00,10305.0 -2017-01-18 02:00:00,9865.0 -2017-01-18 03:00:00,9649.0 -2017-01-18 04:00:00,9480.0 -2017-01-18 05:00:00,9498.0 -2017-01-18 06:00:00,9774.0 -2017-01-18 07:00:00,10467.0 -2017-01-18 08:00:00,11521.0 -2017-01-18 09:00:00,12156.0 -2017-01-18 10:00:00,12199.0 -2017-01-18 11:00:00,12243.0 -2017-01-18 12:00:00,12285.0 -2017-01-18 13:00:00,12330.0 -2017-01-18 14:00:00,12339.0 -2017-01-18 15:00:00,12341.0 -2017-01-18 16:00:00,12159.0 -2017-01-18 17:00:00,12045.0 -2017-01-18 18:00:00,12391.0 -2017-01-18 19:00:00,12910.0 -2017-01-18 20:00:00,12731.0 -2017-01-18 21:00:00,12571.0 -2017-01-18 22:00:00,12273.0 -2017-01-18 23:00:00,11775.0 -2017-01-19 00:00:00,11087.0 -2017-01-17 01:00:00,10210.0 -2017-01-17 02:00:00,9802.0 -2017-01-17 03:00:00,9587.0 -2017-01-17 04:00:00,9394.0 -2017-01-17 05:00:00,9281.0 -2017-01-17 06:00:00,9548.0 -2017-01-17 07:00:00,10273.0 -2017-01-17 08:00:00,11376.0 -2017-01-17 09:00:00,12004.0 -2017-01-17 10:00:00,12021.0 -2017-01-17 11:00:00,12110.0 -2017-01-17 12:00:00,12193.0 -2017-01-17 13:00:00,12156.0 -2017-01-17 14:00:00,12161.0 -2017-01-17 15:00:00,12151.0 -2017-01-17 16:00:00,12159.0 -2017-01-17 17:00:00,12094.0 -2017-01-17 18:00:00,12526.0 -2017-01-17 19:00:00,12920.0 -2017-01-17 20:00:00,12684.0 -2017-01-17 21:00:00,12505.0 -2017-01-17 22:00:00,12180.0 -2017-01-17 23:00:00,11697.0 -2017-01-18 00:00:00,10990.0 -2017-01-16 01:00:00,10021.0 -2017-01-16 02:00:00,9678.0 -2017-01-16 03:00:00,9486.0 -2017-01-16 04:00:00,9408.0 -2017-01-16 05:00:00,9421.0 -2017-01-16 06:00:00,9619.0 -2017-01-16 07:00:00,10236.0 -2017-01-16 08:00:00,11083.0 -2017-01-16 09:00:00,11646.0 -2017-01-16 10:00:00,11891.0 -2017-01-16 11:00:00,12113.0 -2017-01-16 12:00:00,12316.0 -2017-01-16 13:00:00,12417.0 -2017-01-16 14:00:00,12399.0 -2017-01-16 15:00:00,12479.0 -2017-01-16 16:00:00,12487.0 -2017-01-16 17:00:00,12417.0 -2017-01-16 18:00:00,12774.0 -2017-01-16 19:00:00,12984.0 -2017-01-16 20:00:00,12738.0 -2017-01-16 21:00:00,12507.0 -2017-01-16 22:00:00,12160.0 -2017-01-16 23:00:00,11572.0 -2017-01-17 00:00:00,10888.0 -2017-01-15 01:00:00,10395.0 -2017-01-15 02:00:00,10046.0 -2017-01-15 03:00:00,9849.0 -2017-01-15 04:00:00,9709.0 -2017-01-15 05:00:00,9670.0 -2017-01-15 06:00:00,9713.0 -2017-01-15 07:00:00,9865.0 -2017-01-15 08:00:00,10168.0 -2017-01-15 09:00:00,10271.0 -2017-01-15 10:00:00,10407.0 -2017-01-15 11:00:00,10672.0 -2017-01-15 12:00:00,10700.0 -2017-01-15 13:00:00,10614.0 -2017-01-15 14:00:00,10520.0 -2017-01-15 15:00:00,10363.0 -2017-01-15 16:00:00,10312.0 -2017-01-15 17:00:00,10404.0 -2017-01-15 18:00:00,10905.0 -2017-01-15 19:00:00,11554.0 -2017-01-15 20:00:00,11526.0 -2017-01-15 21:00:00,11418.0 -2017-01-15 22:00:00,11212.0 -2017-01-15 23:00:00,10930.0 -2017-01-16 00:00:00,10492.0 -2017-01-14 01:00:00,11074.0 -2017-01-14 02:00:00,10558.0 -2017-01-14 03:00:00,10296.0 -2017-01-14 04:00:00,10081.0 -2017-01-14 05:00:00,9986.0 -2017-01-14 06:00:00,10059.0 -2017-01-14 07:00:00,10302.0 -2017-01-14 08:00:00,10748.0 -2017-01-14 09:00:00,11014.0 -2017-01-14 10:00:00,11229.0 -2017-01-14 11:00:00,11439.0 -2017-01-14 12:00:00,11563.0 -2017-01-14 13:00:00,11472.0 -2017-01-14 14:00:00,11304.0 -2017-01-14 15:00:00,11049.0 -2017-01-14 16:00:00,10929.0 -2017-01-14 17:00:00,10851.0 -2017-01-14 18:00:00,11127.0 -2017-01-14 19:00:00,11825.0 -2017-01-14 20:00:00,11791.0 -2017-01-14 21:00:00,11711.0 -2017-01-14 22:00:00,11532.0 -2017-01-14 23:00:00,11328.0 -2017-01-15 00:00:00,10863.0 -2017-01-13 01:00:00,11321.0 -2017-01-13 02:00:00,10937.0 -2017-01-13 03:00:00,10715.0 -2017-01-13 04:00:00,10576.0 -2017-01-13 05:00:00,10571.0 -2017-01-13 06:00:00,10842.0 -2017-01-13 07:00:00,11506.0 -2017-01-13 08:00:00,12537.0 -2017-01-13 09:00:00,13074.0 -2017-01-13 10:00:00,13123.0 -2017-01-13 11:00:00,13179.0 -2017-01-13 12:00:00,13243.0 -2017-01-13 13:00:00,13175.0 -2017-01-13 14:00:00,13118.0 -2017-01-13 15:00:00,13077.0 -2017-01-13 16:00:00,12977.0 -2017-01-13 17:00:00,12916.0 -2017-01-13 18:00:00,13278.0 -2017-01-13 19:00:00,13613.0 -2017-01-13 20:00:00,13402.0 -2017-01-13 21:00:00,13127.0 -2017-01-13 22:00:00,12805.0 -2017-01-13 23:00:00,12350.0 -2017-01-14 00:00:00,11693.0 -2017-01-12 01:00:00,10671.0 -2017-01-12 02:00:00,10285.0 -2017-01-12 03:00:00,10092.0 -2017-01-12 04:00:00,9997.0 -2017-01-12 05:00:00,10025.0 -2017-01-12 06:00:00,10327.0 -2017-01-12 07:00:00,11149.0 -2017-01-12 08:00:00,12266.0 -2017-01-12 09:00:00,12968.0 -2017-01-12 10:00:00,13124.0 -2017-01-12 11:00:00,13185.0 -2017-01-12 12:00:00,13288.0 -2017-01-12 13:00:00,13232.0 -2017-01-12 14:00:00,13219.0 -2017-01-12 15:00:00,13204.0 -2017-01-12 16:00:00,13109.0 -2017-01-12 17:00:00,13001.0 -2017-01-12 18:00:00,13288.0 -2017-01-12 19:00:00,13859.0 -2017-01-12 20:00:00,13726.0 -2017-01-12 21:00:00,13497.0 -2017-01-12 22:00:00,13248.0 -2017-01-12 23:00:00,12767.0 -2017-01-13 00:00:00,12049.0 -2017-01-11 01:00:00,11036.0 -2017-01-11 02:00:00,10570.0 -2017-01-11 03:00:00,10322.0 -2017-01-11 04:00:00,10178.0 -2017-01-11 05:00:00,10128.0 -2017-01-11 06:00:00,10353.0 -2017-01-11 07:00:00,11075.0 -2017-01-11 08:00:00,12157.0 -2017-01-11 09:00:00,12769.0 -2017-01-11 10:00:00,12793.0 -2017-01-11 11:00:00,12735.0 -2017-01-11 12:00:00,12772.0 -2017-01-11 13:00:00,12734.0 -2017-01-11 14:00:00,12620.0 -2017-01-11 15:00:00,12585.0 -2017-01-11 16:00:00,12410.0 -2017-01-11 17:00:00,12399.0 -2017-01-11 18:00:00,12789.0 -2017-01-11 19:00:00,13012.0 -2017-01-11 20:00:00,12946.0 -2017-01-11 21:00:00,12807.0 -2017-01-11 22:00:00,12540.0 -2017-01-11 23:00:00,12002.0 -2017-01-12 00:00:00,11348.0 -2017-01-10 01:00:00,11132.0 -2017-01-10 02:00:00,10601.0 -2017-01-10 03:00:00,10310.0 -2017-01-10 04:00:00,10134.0 -2017-01-10 05:00:00,10113.0 -2017-01-10 06:00:00,10274.0 -2017-01-10 07:00:00,10973.0 -2017-01-10 08:00:00,11939.0 -2017-01-10 09:00:00,12682.0 -2017-01-10 10:00:00,12794.0 -2017-01-10 11:00:00,12836.0 -2017-01-10 12:00:00,12934.0 -2017-01-10 13:00:00,12844.0 -2017-01-10 14:00:00,12770.0 -2017-01-10 15:00:00,12700.0 -2017-01-10 16:00:00,12563.0 -2017-01-10 17:00:00,12483.0 -2017-01-10 18:00:00,12927.0 -2017-01-10 19:00:00,13310.0 -2017-01-10 20:00:00,13204.0 -2017-01-10 21:00:00,13148.0 -2017-01-10 22:00:00,12885.0 -2017-01-10 23:00:00,12445.0 -2017-01-11 00:00:00,11776.0 -2017-01-09 01:00:00,11518.0 -2017-01-09 02:00:00,11116.0 -2017-01-09 03:00:00,10919.0 -2017-01-09 04:00:00,10798.0 -2017-01-09 05:00:00,10807.0 -2017-01-09 06:00:00,10991.0 -2017-01-09 07:00:00,11609.0 -2017-01-09 08:00:00,12668.0 -2017-01-09 09:00:00,13214.0 -2017-01-09 10:00:00,13247.0 -2017-01-09 11:00:00,13248.0 -2017-01-09 12:00:00,13255.0 -2017-01-09 13:00:00,13219.0 -2017-01-09 14:00:00,13205.0 -2017-01-09 15:00:00,13090.0 -2017-01-09 16:00:00,13061.0 -2017-01-09 17:00:00,13144.0 -2017-01-09 18:00:00,13638.0 -2017-01-09 19:00:00,13973.0 -2017-01-09 20:00:00,13804.0 -2017-01-09 21:00:00,13622.0 -2017-01-09 22:00:00,13216.0 -2017-01-09 23:00:00,12602.0 -2017-01-10 00:00:00,11888.0 -2017-01-08 01:00:00,12029.0 -2017-01-08 02:00:00,11636.0 -2017-01-08 03:00:00,11406.0 -2017-01-08 04:00:00,11233.0 -2017-01-08 05:00:00,11190.0 -2017-01-08 06:00:00,11215.0 -2017-01-08 07:00:00,11396.0 -2017-01-08 08:00:00,11683.0 -2017-01-08 09:00:00,11763.0 -2017-01-08 10:00:00,11804.0 -2017-01-08 11:00:00,11879.0 -2017-01-08 12:00:00,11871.0 -2017-01-08 13:00:00,11834.0 -2017-01-08 14:00:00,11777.0 -2017-01-08 15:00:00,11664.0 -2017-01-08 16:00:00,11690.0 -2017-01-08 17:00:00,11829.0 -2017-01-08 18:00:00,12509.0 -2017-01-08 19:00:00,13243.0 -2017-01-08 20:00:00,13335.0 -2017-01-08 21:00:00,13181.0 -2017-01-08 22:00:00,13051.0 -2017-01-08 23:00:00,12651.0 -2017-01-09 00:00:00,12044.0 -2017-01-07 01:00:00,12667.0 -2017-01-07 02:00:00,12204.0 -2017-01-07 03:00:00,11951.0 -2017-01-07 04:00:00,11724.0 -2017-01-07 05:00:00,11679.0 -2017-01-07 06:00:00,11716.0 -2017-01-07 07:00:00,12017.0 -2017-01-07 08:00:00,12348.0 -2017-01-07 09:00:00,12573.0 -2017-01-07 10:00:00,12664.0 -2017-01-07 11:00:00,12742.0 -2017-01-07 12:00:00,12755.0 -2017-01-07 13:00:00,12618.0 -2017-01-07 14:00:00,12501.0 -2017-01-07 15:00:00,12304.0 -2017-01-07 16:00:00,12147.0 -2017-01-07 17:00:00,12100.0 -2017-01-07 18:00:00,12615.0 -2017-01-07 19:00:00,13377.0 -2017-01-07 20:00:00,13458.0 -2017-01-07 21:00:00,13358.0 -2017-01-07 22:00:00,13187.0 -2017-01-07 23:00:00,12970.0 -2017-01-08 00:00:00,12551.0 -2017-01-06 01:00:00,12485.0 -2017-01-06 02:00:00,12072.0 -2017-01-06 03:00:00,11820.0 -2017-01-06 04:00:00,11666.0 -2017-01-06 05:00:00,11646.0 -2017-01-06 06:00:00,11903.0 -2017-01-06 07:00:00,12518.0 -2017-01-06 08:00:00,13386.0 -2017-01-06 09:00:00,13946.0 -2017-01-06 10:00:00,13981.0 -2017-01-06 11:00:00,13997.0 -2017-01-06 12:00:00,14022.0 -2017-01-06 13:00:00,13967.0 -2017-01-06 14:00:00,13894.0 -2017-01-06 15:00:00,13851.0 -2017-01-06 16:00:00,13729.0 -2017-01-06 17:00:00,13643.0 -2017-01-06 18:00:00,14064.0 -2017-01-06 19:00:00,14730.0 -2017-01-06 20:00:00,14675.0 -2017-01-06 21:00:00,14490.0 -2017-01-06 22:00:00,14224.0 -2017-01-06 23:00:00,13866.0 -2017-01-07 00:00:00,13231.0 -2017-01-05 01:00:00,12018.0 -2017-01-05 02:00:00,11622.0 -2017-01-05 03:00:00,11363.0 -2017-01-05 04:00:00,11281.0 -2017-01-05 05:00:00,11264.0 -2017-01-05 06:00:00,11527.0 -2017-01-05 07:00:00,12109.0 -2017-01-05 08:00:00,12983.0 -2017-01-05 09:00:00,13550.0 -2017-01-05 10:00:00,13682.0 -2017-01-05 11:00:00,13650.0 -2017-01-05 12:00:00,13683.0 -2017-01-05 13:00:00,13707.0 -2017-01-05 14:00:00,13777.0 -2017-01-05 15:00:00,13772.0 -2017-01-05 16:00:00,13636.0 -2017-01-05 17:00:00,13597.0 -2017-01-05 18:00:00,14110.0 -2017-01-05 19:00:00,14681.0 -2017-01-05 20:00:00,14580.0 -2017-01-05 21:00:00,14455.0 -2017-01-05 22:00:00,14209.0 -2017-01-05 23:00:00,13787.0 -2017-01-06 00:00:00,13111.0 -2017-01-04 01:00:00,11005.0 -2017-01-04 02:00:00,10672.0 -2017-01-04 03:00:00,10492.0 -2017-01-04 04:00:00,10502.0 -2017-01-04 05:00:00,10568.0 -2017-01-04 06:00:00,10854.0 -2017-01-04 07:00:00,11517.0 -2017-01-04 08:00:00,12547.0 -2017-01-04 09:00:00,13031.0 -2017-01-04 10:00:00,13065.0 -2017-01-04 11:00:00,13163.0 -2017-01-04 12:00:00,13207.0 -2017-01-04 13:00:00,13161.0 -2017-01-04 14:00:00,13105.0 -2017-01-04 15:00:00,13046.0 -2017-01-04 16:00:00,12954.0 -2017-01-04 17:00:00,12966.0 -2017-01-04 18:00:00,13631.0 -2017-01-04 19:00:00,14300.0 -2017-01-04 20:00:00,14197.0 -2017-01-04 21:00:00,14028.0 -2017-01-04 22:00:00,13801.0 -2017-01-04 23:00:00,13330.0 -2017-01-05 00:00:00,12635.0 -2017-01-03 01:00:00,9519.0 -2017-01-03 02:00:00,9133.0 -2017-01-03 03:00:00,8918.0 -2017-01-03 04:00:00,8810.0 -2017-01-03 05:00:00,8832.0 -2017-01-03 06:00:00,9080.0 -2017-01-03 07:00:00,9796.0 -2017-01-03 08:00:00,10746.0 -2017-01-03 09:00:00,11421.0 -2017-01-03 10:00:00,11591.0 -2017-01-03 11:00:00,11744.0 -2017-01-03 12:00:00,11957.0 -2017-01-03 13:00:00,12074.0 -2017-01-03 14:00:00,12203.0 -2017-01-03 15:00:00,12206.0 -2017-01-03 16:00:00,12182.0 -2017-01-03 17:00:00,12143.0 -2017-01-03 18:00:00,12640.0 -2017-01-03 19:00:00,13032.0 -2017-01-03 20:00:00,12830.0 -2017-01-03 21:00:00,12672.0 -2017-01-03 22:00:00,12413.0 -2017-01-03 23:00:00,12071.0 -2017-01-04 00:00:00,11484.0 -2017-01-02 01:00:00,9678.0 -2017-01-02 02:00:00,9296.0 -2017-01-02 03:00:00,9076.0 -2017-01-02 04:00:00,8995.0 -2017-01-02 05:00:00,8960.0 -2017-01-02 06:00:00,9119.0 -2017-01-02 07:00:00,9399.0 -2017-01-02 08:00:00,9747.0 -2017-01-02 09:00:00,9866.0 -2017-01-02 10:00:00,9978.0 -2017-01-02 11:00:00,10222.0 -2017-01-02 12:00:00,10367.0 -2017-01-02 13:00:00,10458.0 -2017-01-02 14:00:00,10503.0 -2017-01-02 15:00:00,10629.0 -2017-01-02 16:00:00,10674.0 -2017-01-02 17:00:00,10878.0 -2017-01-02 18:00:00,11381.0 -2017-01-02 19:00:00,11686.0 -2017-01-02 20:00:00,11508.0 -2017-01-02 21:00:00,11355.0 -2017-01-02 22:00:00,11070.0 -2017-01-02 23:00:00,10666.0 -2017-01-03 00:00:00,10106.0 -2017-01-01 01:00:00,10197.0 -2017-01-01 02:00:00,9893.0 -2017-01-01 03:00:00,9654.0 -2017-01-01 04:00:00,9437.0 -2017-01-01 05:00:00,9370.0 -2017-01-01 06:00:00,9322.0 -2017-01-01 07:00:00,9459.0 -2017-01-01 08:00:00,9602.0 -2017-01-01 09:00:00,9587.0 -2017-01-01 10:00:00,9530.0 -2017-01-01 11:00:00,9513.0 -2017-01-01 12:00:00,9477.0 -2017-01-01 13:00:00,9511.0 -2017-01-01 14:00:00,9459.0 -2017-01-01 15:00:00,9367.0 -2017-01-01 16:00:00,9328.0 -2017-01-01 17:00:00,9428.0 -2017-01-01 18:00:00,10134.0 -2017-01-01 19:00:00,10851.0 -2017-01-01 20:00:00,10882.0 -2017-01-01 21:00:00,10831.0 -2017-01-01 22:00:00,10691.0 -2017-01-01 23:00:00,10484.0 -2017-01-02 00:00:00,10114.0 -2018-08-02 01:00:00,11916.0 -2018-08-02 02:00:00,11095.0 -2018-08-02 03:00:00,10530.0 -2018-08-02 04:00:00,10165.0 -2018-08-02 05:00:00,9931.0 -2018-08-02 06:00:00,9996.0 -2018-08-02 07:00:00,10482.0 -2018-08-02 08:00:00,11200.0 -2018-08-02 09:00:00,12179.0 -2018-08-02 10:00:00,13042.0 -2018-08-02 11:00:00,13828.0 -2018-08-02 12:00:00,14790.0 -2018-08-02 13:00:00,15527.0 -2018-08-02 14:00:00,16074.0 -2018-08-02 15:00:00,16584.0 -2018-08-02 16:00:00,16869.0 -2018-08-02 17:00:00,17015.0 -2018-08-02 18:00:00,17068.0 -2018-08-02 19:00:00,16897.0 -2018-08-02 20:00:00,16437.0 -2018-08-02 21:00:00,15590.0 -2018-08-02 22:00:00,15086.0 -2018-08-02 23:00:00,14448.0 -2018-08-03 00:00:00,13335.0 -2018-08-01 01:00:00,10975.0 -2018-08-01 02:00:00,10233.0 -2018-08-01 03:00:00,9734.0 -2018-08-01 04:00:00,9374.0 -2018-08-01 05:00:00,9156.0 -2018-08-01 06:00:00,9270.0 -2018-08-01 07:00:00,9753.0 -2018-08-01 08:00:00,10466.0 -2018-08-01 09:00:00,11424.0 -2018-08-01 10:00:00,12294.0 -2018-08-01 11:00:00,13025.0 -2018-08-01 12:00:00,13814.0 -2018-08-01 13:00:00,14516.0 -2018-08-01 14:00:00,15203.0 -2018-08-01 15:00:00,15881.0 -2018-08-01 16:00:00,16123.0 -2018-08-01 17:00:00,15766.0 -2018-08-01 18:00:00,15574.0 -2018-08-01 19:00:00,15569.0 -2018-08-01 20:00:00,15122.0 -2018-08-01 21:00:00,14449.0 -2018-08-01 22:00:00,14246.0 -2018-08-01 23:00:00,13778.0 -2018-08-02 00:00:00,12892.0 -2018-07-31 01:00:00,11206.0 -2018-07-31 02:00:00,10442.0 -2018-07-31 03:00:00,9918.0 -2018-07-31 04:00:00,9548.0 -2018-07-31 05:00:00,9391.0 -2018-07-31 06:00:00,9511.0 -2018-07-31 07:00:00,10119.0 -2018-07-31 08:00:00,10896.0 -2018-07-31 09:00:00,11737.0 -2018-07-31 10:00:00,12272.0 -2018-07-31 11:00:00,12716.0 -2018-07-31 12:00:00,13366.0 -2018-07-31 13:00:00,13826.0 -2018-07-31 14:00:00,14103.0 -2018-07-31 15:00:00,14435.0 -2018-07-31 16:00:00,14484.0 -2018-07-31 17:00:00,14501.0 -2018-07-31 18:00:00,14242.0 -2018-07-31 19:00:00,13921.0 -2018-07-31 20:00:00,13532.0 -2018-07-31 21:00:00,13300.0 -2018-07-31 22:00:00,13346.0 -2018-07-31 23:00:00,12898.0 -2018-08-01 00:00:00,11945.0 -2018-07-30 01:00:00,10223.0 -2018-07-30 02:00:00,9632.0 -2018-07-30 03:00:00,9140.0 -2018-07-30 04:00:00,8872.0 -2018-07-30 05:00:00,8700.0 -2018-07-30 06:00:00,8921.0 -2018-07-30 07:00:00,9472.0 -2018-07-30 08:00:00,10201.0 -2018-07-30 09:00:00,11180.0 -2018-07-30 10:00:00,12085.0 -2018-07-30 11:00:00,12792.0 -2018-07-30 12:00:00,13417.0 -2018-07-30 13:00:00,13960.0 -2018-07-30 14:00:00,14386.0 -2018-07-30 15:00:00,14900.0 -2018-07-30 16:00:00,15242.0 -2018-07-30 17:00:00,15414.0 -2018-07-30 18:00:00,15532.0 -2018-07-30 19:00:00,15434.0 -2018-07-30 20:00:00,14930.0 -2018-07-30 21:00:00,14168.0 -2018-07-30 22:00:00,13735.0 -2018-07-30 23:00:00,13194.0 -2018-07-31 00:00:00,12223.0 -2018-07-29 01:00:00,10031.0 -2018-07-29 02:00:00,9400.0 -2018-07-29 03:00:00,8950.0 -2018-07-29 04:00:00,8587.0 -2018-07-29 05:00:00,8325.0 -2018-07-29 06:00:00,8255.0 -2018-07-29 07:00:00,8205.0 -2018-07-29 08:00:00,8172.0 -2018-07-29 09:00:00,8629.0 -2018-07-29 10:00:00,9353.0 -2018-07-29 11:00:00,10143.0 -2018-07-29 12:00:00,10853.0 -2018-07-29 13:00:00,11423.0 -2018-07-29 14:00:00,11856.0 -2018-07-29 15:00:00,12252.0 -2018-07-29 16:00:00,12384.0 -2018-07-29 17:00:00,12520.0 -2018-07-29 18:00:00,12455.0 -2018-07-29 19:00:00,12448.0 -2018-07-29 20:00:00,12186.0 -2018-07-29 21:00:00,11845.0 -2018-07-29 22:00:00,11809.0 -2018-07-29 23:00:00,11569.0 -2018-07-30 00:00:00,10989.0 -2018-07-28 01:00:00,10501.0 -2018-07-28 02:00:00,9727.0 -2018-07-28 03:00:00,9251.0 -2018-07-28 04:00:00,8865.0 -2018-07-28 05:00:00,8644.0 -2018-07-28 06:00:00,8606.0 -2018-07-28 07:00:00,8672.0 -2018-07-28 08:00:00,8834.0 -2018-07-28 09:00:00,9375.0 -2018-07-28 10:00:00,10152.0 -2018-07-28 11:00:00,10872.0 -2018-07-28 12:00:00,11418.0 -2018-07-28 13:00:00,11796.0 -2018-07-28 14:00:00,12111.0 -2018-07-28 15:00:00,12382.0 -2018-07-28 16:00:00,12520.0 -2018-07-28 17:00:00,12665.0 -2018-07-28 18:00:00,12546.0 -2018-07-28 19:00:00,12369.0 -2018-07-28 20:00:00,12035.0 -2018-07-28 21:00:00,11714.0 -2018-07-28 22:00:00,11591.0 -2018-07-28 23:00:00,11384.0 -2018-07-29 00:00:00,10728.0 -2018-07-27 01:00:00,11143.0 -2018-07-27 02:00:00,10307.0 -2018-07-27 03:00:00,9719.0 -2018-07-27 04:00:00,9325.0 -2018-07-27 05:00:00,9142.0 -2018-07-27 06:00:00,9167.0 -2018-07-27 07:00:00,9610.0 -2018-07-27 08:00:00,10224.0 -2018-07-27 09:00:00,11121.0 -2018-07-27 10:00:00,11791.0 -2018-07-27 11:00:00,12345.0 -2018-07-27 12:00:00,12845.0 -2018-07-27 13:00:00,13219.0 -2018-07-27 14:00:00,13483.0 -2018-07-27 15:00:00,13674.0 -2018-07-27 16:00:00,13781.0 -2018-07-27 17:00:00,13827.0 -2018-07-27 18:00:00,13799.0 -2018-07-27 19:00:00,13586.0 -2018-07-27 20:00:00,13179.0 -2018-07-27 21:00:00,12672.0 -2018-07-27 22:00:00,12502.0 -2018-07-27 23:00:00,12155.0 -2018-07-28 00:00:00,11383.0 -2018-07-26 01:00:00,13054.0 -2018-07-26 02:00:00,12096.0 -2018-07-26 03:00:00,11432.0 -2018-07-26 04:00:00,10929.0 -2018-07-26 05:00:00,10600.0 -2018-07-26 06:00:00,10645.0 -2018-07-26 07:00:00,11085.0 -2018-07-26 08:00:00,11769.0 -2018-07-26 09:00:00,12686.0 -2018-07-26 10:00:00,13498.0 -2018-07-26 11:00:00,14258.0 -2018-07-26 12:00:00,14902.0 -2018-07-26 13:00:00,15314.0 -2018-07-26 14:00:00,15585.0 -2018-07-26 15:00:00,15810.0 -2018-07-26 16:00:00,15942.0 -2018-07-26 17:00:00,15924.0 -2018-07-26 18:00:00,15715.0 -2018-07-26 19:00:00,15244.0 -2018-07-26 20:00:00,14579.0 -2018-07-26 21:00:00,13945.0 -2018-07-26 22:00:00,13626.0 -2018-07-26 23:00:00,13174.0 -2018-07-27 00:00:00,12206.0 -2018-07-25 01:00:00,12425.0 -2018-07-25 02:00:00,11506.0 -2018-07-25 03:00:00,10763.0 -2018-07-25 04:00:00,10251.0 -2018-07-25 05:00:00,9907.0 -2018-07-25 06:00:00,9939.0 -2018-07-25 07:00:00,10312.0 -2018-07-25 08:00:00,11117.0 -2018-07-25 09:00:00,12191.0 -2018-07-25 10:00:00,13179.0 -2018-07-25 11:00:00,14081.0 -2018-07-25 12:00:00,15008.0 -2018-07-25 13:00:00,15677.0 -2018-07-25 14:00:00,16225.0 -2018-07-25 15:00:00,16943.0 -2018-07-25 16:00:00,17465.0 -2018-07-25 17:00:00,17846.0 -2018-07-25 18:00:00,18050.0 -2018-07-25 19:00:00,17927.0 -2018-07-25 20:00:00,17313.0 -2018-07-25 21:00:00,16532.0 -2018-07-25 22:00:00,15997.0 -2018-07-25 23:00:00,15321.0 -2018-07-26 00:00:00,14185.0 -2018-07-24 01:00:00,11311.0 -2018-07-24 02:00:00,10550.0 -2018-07-24 03:00:00,9981.0 -2018-07-24 04:00:00,9576.0 -2018-07-24 05:00:00,9428.0 -2018-07-24 06:00:00,9538.0 -2018-07-24 07:00:00,10024.0 -2018-07-24 08:00:00,10798.0 -2018-07-24 09:00:00,11869.0 -2018-07-24 10:00:00,12803.0 -2018-07-24 11:00:00,13679.0 -2018-07-24 12:00:00,14676.0 -2018-07-24 13:00:00,15478.0 -2018-07-24 14:00:00,16100.0 -2018-07-24 15:00:00,16671.0 -2018-07-24 16:00:00,17067.0 -2018-07-24 17:00:00,17317.0 -2018-07-24 18:00:00,17371.0 -2018-07-24 19:00:00,17194.0 -2018-07-24 20:00:00,16678.0 -2018-07-24 21:00:00,15973.0 -2018-07-24 22:00:00,15443.0 -2018-07-24 23:00:00,14873.0 -2018-07-25 00:00:00,13701.0 -2018-07-23 01:00:00,10092.0 -2018-07-23 02:00:00,9581.0 -2018-07-23 03:00:00,9196.0 -2018-07-23 04:00:00,8962.0 -2018-07-23 05:00:00,8901.0 -2018-07-23 06:00:00,9085.0 -2018-07-23 07:00:00,9635.0 -2018-07-23 08:00:00,10393.0 -2018-07-23 09:00:00,11480.0 -2018-07-23 10:00:00,12309.0 -2018-07-23 11:00:00,13075.0 -2018-07-23 12:00:00,13802.0 -2018-07-23 13:00:00,14405.0 -2018-07-23 14:00:00,15004.0 -2018-07-23 15:00:00,15405.0 -2018-07-23 16:00:00,15542.0 -2018-07-23 17:00:00,15439.0 -2018-07-23 18:00:00,15217.0 -2018-07-23 19:00:00,14927.0 -2018-07-23 20:00:00,14490.0 -2018-07-23 21:00:00,13991.0 -2018-07-23 22:00:00,13725.0 -2018-07-23 23:00:00,13347.0 -2018-07-24 00:00:00,12381.0 -2018-07-22 01:00:00,10520.0 -2018-07-22 02:00:00,9947.0 -2018-07-22 03:00:00,9501.0 -2018-07-22 04:00:00,9236.0 -2018-07-22 05:00:00,9034.0 -2018-07-22 06:00:00,9028.0 -2018-07-22 07:00:00,9003.0 -2018-07-22 08:00:00,9022.0 -2018-07-22 09:00:00,9283.0 -2018-07-22 10:00:00,9727.0 -2018-07-22 11:00:00,10121.0 -2018-07-22 12:00:00,10485.0 -2018-07-22 13:00:00,10747.0 -2018-07-22 14:00:00,11045.0 -2018-07-22 15:00:00,11193.0 -2018-07-22 16:00:00,11379.0 -2018-07-22 17:00:00,11611.0 -2018-07-22 18:00:00,11776.0 -2018-07-22 19:00:00,11797.0 -2018-07-22 20:00:00,11722.0 -2018-07-22 21:00:00,11513.0 -2018-07-22 22:00:00,11451.0 -2018-07-22 23:00:00,11357.0 -2018-07-23 00:00:00,10824.0 -2018-07-21 01:00:00,11256.0 -2018-07-21 02:00:00,10581.0 -2018-07-21 03:00:00,10058.0 -2018-07-21 04:00:00,9708.0 -2018-07-21 05:00:00,9558.0 -2018-07-21 06:00:00,9504.0 -2018-07-21 07:00:00,9666.0 -2018-07-21 08:00:00,9915.0 -2018-07-21 09:00:00,10364.0 -2018-07-21 10:00:00,10912.0 -2018-07-21 11:00:00,11395.0 -2018-07-21 12:00:00,11717.0 -2018-07-21 13:00:00,12027.0 -2018-07-21 14:00:00,12205.0 -2018-07-21 15:00:00,12395.0 -2018-07-21 16:00:00,12447.0 -2018-07-21 17:00:00,12571.0 -2018-07-21 18:00:00,12589.0 -2018-07-21 19:00:00,12460.0 -2018-07-21 20:00:00,12184.0 -2018-07-21 21:00:00,11836.0 -2018-07-21 22:00:00,11783.0 -2018-07-21 23:00:00,11645.0 -2018-07-22 00:00:00,11107.0 -2018-07-20 01:00:00,12682.0 -2018-07-20 02:00:00,11929.0 -2018-07-20 03:00:00,11327.0 -2018-07-20 04:00:00,10895.0 -2018-07-20 05:00:00,10676.0 -2018-07-20 06:00:00,10784.0 -2018-07-20 07:00:00,11178.0 -2018-07-20 08:00:00,11930.0 -2018-07-20 09:00:00,13035.0 -2018-07-20 10:00:00,13687.0 -2018-07-20 11:00:00,14158.0 -2018-07-20 12:00:00,14361.0 -2018-07-20 13:00:00,14404.0 -2018-07-20 14:00:00,14491.0 -2018-07-20 15:00:00,14456.0 -2018-07-20 16:00:00,14420.0 -2018-07-20 17:00:00,14242.0 -2018-07-20 18:00:00,14080.0 -2018-07-20 19:00:00,13773.0 -2018-07-20 20:00:00,13333.0 -2018-07-20 21:00:00,12964.0 -2018-07-20 22:00:00,12896.0 -2018-07-20 23:00:00,12687.0 -2018-07-21 00:00:00,12004.0 -2018-07-19 01:00:00,11089.0 -2018-07-19 02:00:00,10331.0 -2018-07-19 03:00:00,9791.0 -2018-07-19 04:00:00,9422.0 -2018-07-19 05:00:00,9209.0 -2018-07-19 06:00:00,9287.0 -2018-07-19 07:00:00,9746.0 -2018-07-19 08:00:00,10560.0 -2018-07-19 09:00:00,11705.0 -2018-07-19 10:00:00,12536.0 -2018-07-19 11:00:00,13275.0 -2018-07-19 12:00:00,14133.0 -2018-07-19 13:00:00,14881.0 -2018-07-19 14:00:00,15420.0 -2018-07-19 15:00:00,15988.0 -2018-07-19 16:00:00,16323.0 -2018-07-19 17:00:00,16236.0 -2018-07-19 18:00:00,15929.0 -2018-07-19 19:00:00,15534.0 -2018-07-19 20:00:00,15171.0 -2018-07-19 21:00:00,14879.0 -2018-07-19 22:00:00,14729.0 -2018-07-19 23:00:00,14495.0 -2018-07-20 00:00:00,13645.0 -2018-07-18 01:00:00,11393.0 -2018-07-18 02:00:00,10572.0 -2018-07-18 03:00:00,9976.0 -2018-07-18 04:00:00,9556.0 -2018-07-18 05:00:00,9338.0 -2018-07-18 06:00:00,9380.0 -2018-07-18 07:00:00,9755.0 -2018-07-18 08:00:00,10616.0 -2018-07-18 09:00:00,11699.0 -2018-07-18 10:00:00,12532.0 -2018-07-18 11:00:00,13118.0 -2018-07-18 12:00:00,13684.0 -2018-07-18 13:00:00,14098.0 -2018-07-18 14:00:00,14512.0 -2018-07-18 15:00:00,14891.0 -2018-07-18 16:00:00,15188.0 -2018-07-18 17:00:00,15387.0 -2018-07-18 18:00:00,15486.0 -2018-07-18 19:00:00,15311.0 -2018-07-18 20:00:00,14717.0 -2018-07-18 21:00:00,13832.0 -2018-07-18 22:00:00,13416.0 -2018-07-18 23:00:00,12990.0 -2018-07-19 00:00:00,12111.0 -2018-07-17 01:00:00,13173.0 -2018-07-17 02:00:00,12050.0 -2018-07-17 03:00:00,11198.0 -2018-07-17 04:00:00,10633.0 -2018-07-17 05:00:00,10296.0 -2018-07-17 06:00:00,10340.0 -2018-07-17 07:00:00,10731.0 -2018-07-17 08:00:00,11660.0 -2018-07-17 09:00:00,12802.0 -2018-07-17 10:00:00,13830.0 -2018-07-17 11:00:00,14677.0 -2018-07-17 12:00:00,15477.0 -2018-07-17 13:00:00,16073.0 -2018-07-17 14:00:00,16426.0 -2018-07-17 15:00:00,16643.0 -2018-07-17 16:00:00,16780.0 -2018-07-17 17:00:00,16879.0 -2018-07-17 18:00:00,16834.0 -2018-07-17 19:00:00,16534.0 -2018-07-17 20:00:00,15873.0 -2018-07-17 21:00:00,14938.0 -2018-07-17 22:00:00,14160.0 -2018-07-17 23:00:00,13569.0 -2018-07-18 00:00:00,12504.0 -2018-07-16 01:00:00,13757.0 -2018-07-16 02:00:00,12869.0 -2018-07-16 03:00:00,12186.0 -2018-07-16 04:00:00,11721.0 -2018-07-16 05:00:00,11532.0 -2018-07-16 06:00:00,11621.0 -2018-07-16 07:00:00,12155.0 -2018-07-16 08:00:00,13133.0 -2018-07-16 09:00:00,14504.0 -2018-07-16 10:00:00,15832.0 -2018-07-16 11:00:00,16988.0 -2018-07-16 12:00:00,17964.0 -2018-07-16 13:00:00,18857.0 -2018-07-16 14:00:00,19167.0 -2018-07-16 15:00:00,19266.0 -2018-07-16 16:00:00,19005.0 -2018-07-16 17:00:00,18960.0 -2018-07-16 18:00:00,19032.0 -2018-07-16 19:00:00,18990.0 -2018-07-16 20:00:00,18568.0 -2018-07-16 21:00:00,17710.0 -2018-07-16 22:00:00,16736.0 -2018-07-16 23:00:00,15925.0 -2018-07-17 00:00:00,14540.0 -2018-07-15 01:00:00,12381.0 -2018-07-15 02:00:00,11527.0 -2018-07-15 03:00:00,10870.0 -2018-07-15 04:00:00,10413.0 -2018-07-15 05:00:00,10183.0 -2018-07-15 06:00:00,9966.0 -2018-07-15 07:00:00,9875.0 -2018-07-15 08:00:00,10025.0 -2018-07-15 09:00:00,10940.0 -2018-07-15 10:00:00,12192.0 -2018-07-15 11:00:00,13439.0 -2018-07-15 12:00:00,14594.0 -2018-07-15 13:00:00,15497.0 -2018-07-15 14:00:00,16147.0 -2018-07-15 15:00:00,16558.0 -2018-07-15 16:00:00,16976.0 -2018-07-15 17:00:00,17224.0 -2018-07-15 18:00:00,17320.0 -2018-07-15 19:00:00,17307.0 -2018-07-15 20:00:00,17092.0 -2018-07-15 21:00:00,16663.0 -2018-07-15 22:00:00,16199.0 -2018-07-15 23:00:00,15806.0 -2018-07-16 00:00:00,14879.0 -2018-07-14 01:00:00,14362.0 -2018-07-14 02:00:00,13304.0 -2018-07-14 03:00:00,12420.0 -2018-07-14 04:00:00,11830.0 -2018-07-14 05:00:00,11500.0 -2018-07-14 06:00:00,11260.0 -2018-07-14 07:00:00,11353.0 -2018-07-14 08:00:00,11505.0 -2018-07-14 09:00:00,11851.0 -2018-07-14 10:00:00,12354.0 -2018-07-14 11:00:00,12940.0 -2018-07-14 12:00:00,13414.0 -2018-07-14 13:00:00,13515.0 -2018-07-14 14:00:00,13508.0 -2018-07-14 15:00:00,13642.0 -2018-07-14 16:00:00,13788.0 -2018-07-14 17:00:00,14180.0 -2018-07-14 18:00:00,14653.0 -2018-07-14 19:00:00,14905.0 -2018-07-14 20:00:00,14664.0 -2018-07-14 21:00:00,14315.0 -2018-07-14 22:00:00,14013.0 -2018-07-14 23:00:00,13841.0 -2018-07-15 00:00:00,13186.0 -2018-07-13 01:00:00,13357.0 -2018-07-13 02:00:00,12296.0 -2018-07-13 03:00:00,11618.0 -2018-07-13 04:00:00,11076.0 -2018-07-13 05:00:00,10709.0 -2018-07-13 06:00:00,10699.0 -2018-07-13 07:00:00,11080.0 -2018-07-13 08:00:00,11937.0 -2018-07-13 09:00:00,13237.0 -2018-07-13 10:00:00,14347.0 -2018-07-13 11:00:00,15396.0 -2018-07-13 12:00:00,16430.0 -2018-07-13 13:00:00,17257.0 -2018-07-13 14:00:00,18027.0 -2018-07-13 15:00:00,18756.0 -2018-07-13 16:00:00,19279.0 -2018-07-13 17:00:00,19599.0 -2018-07-13 18:00:00,19773.0 -2018-07-13 19:00:00,19682.0 -2018-07-13 20:00:00,19208.0 -2018-07-13 21:00:00,18332.0 -2018-07-13 22:00:00,17525.0 -2018-07-13 23:00:00,16735.0 -2018-07-14 00:00:00,15608.0 -2018-07-12 01:00:00,12715.0 -2018-07-12 02:00:00,11750.0 -2018-07-12 03:00:00,11046.0 -2018-07-12 04:00:00,10513.0 -2018-07-12 05:00:00,10231.0 -2018-07-12 06:00:00,10299.0 -2018-07-12 07:00:00,10760.0 -2018-07-12 08:00:00,11643.0 -2018-07-12 09:00:00,12805.0 -2018-07-12 10:00:00,13811.0 -2018-07-12 11:00:00,14568.0 -2018-07-12 12:00:00,15421.0 -2018-07-12 13:00:00,16236.0 -2018-07-12 14:00:00,16855.0 -2018-07-12 15:00:00,17366.0 -2018-07-12 16:00:00,17581.0 -2018-07-12 17:00:00,17713.0 -2018-07-12 18:00:00,17751.0 -2018-07-12 19:00:00,17622.0 -2018-07-12 20:00:00,17173.0 -2018-07-12 21:00:00,16594.0 -2018-07-12 22:00:00,16067.0 -2018-07-12 23:00:00,15557.0 -2018-07-13 00:00:00,14475.0 -2018-07-11 01:00:00,12391.0 -2018-07-11 02:00:00,11420.0 -2018-07-11 03:00:00,10721.0 -2018-07-11 04:00:00,10242.0 -2018-07-11 05:00:00,9981.0 -2018-07-11 06:00:00,10003.0 -2018-07-11 07:00:00,10420.0 -2018-07-11 08:00:00,11328.0 -2018-07-11 09:00:00,12551.0 -2018-07-11 10:00:00,13566.0 -2018-07-11 11:00:00,14482.0 -2018-07-11 12:00:00,15374.0 -2018-07-11 13:00:00,16066.0 -2018-07-11 14:00:00,16712.0 -2018-07-11 15:00:00,17347.0 -2018-07-11 16:00:00,17782.0 -2018-07-11 17:00:00,18084.0 -2018-07-11 18:00:00,18135.0 -2018-07-11 19:00:00,17944.0 -2018-07-11 20:00:00,17327.0 -2018-07-11 21:00:00,16450.0 -2018-07-11 22:00:00,15621.0 -2018-07-11 23:00:00,15012.0 -2018-07-12 00:00:00,13895.0 -2018-07-10 01:00:00,14232.0 -2018-07-10 02:00:00,13214.0 -2018-07-10 03:00:00,12485.0 -2018-07-10 04:00:00,11964.0 -2018-07-10 05:00:00,11681.0 -2018-07-10 06:00:00,11702.0 -2018-07-10 07:00:00,12098.0 -2018-07-10 08:00:00,13077.0 -2018-07-10 09:00:00,14477.0 -2018-07-10 10:00:00,15589.0 -2018-07-10 11:00:00,16496.0 -2018-07-10 12:00:00,17310.0 -2018-07-10 13:00:00,17793.0 -2018-07-10 14:00:00,18121.0 -2018-07-10 15:00:00,18420.0 -2018-07-10 16:00:00,18515.0 -2018-07-10 17:00:00,18514.0 -2018-07-10 18:00:00,18318.0 -2018-07-10 19:00:00,17850.0 -2018-07-10 20:00:00,16923.0 -2018-07-10 21:00:00,15969.0 -2018-07-10 22:00:00,15217.0 -2018-07-10 23:00:00,14704.0 -2018-07-11 00:00:00,13596.0 -2018-07-09 01:00:00,11660.0 -2018-07-09 02:00:00,10817.0 -2018-07-09 03:00:00,10213.0 -2018-07-09 04:00:00,9759.0 -2018-07-09 05:00:00,9588.0 -2018-07-09 06:00:00,9715.0 -2018-07-09 07:00:00,10140.0 -2018-07-09 08:00:00,11073.0 -2018-07-09 09:00:00,12453.0 -2018-07-09 10:00:00,13593.0 -2018-07-09 11:00:00,14683.0 -2018-07-09 12:00:00,15689.0 -2018-07-09 13:00:00,16510.0 -2018-07-09 14:00:00,17248.0 -2018-07-09 15:00:00,17951.0 -2018-07-09 16:00:00,18528.0 -2018-07-09 17:00:00,19037.0 -2018-07-09 18:00:00,19325.0 -2018-07-09 19:00:00,19096.0 -2018-07-09 20:00:00,18642.0 -2018-07-09 21:00:00,18103.0 -2018-07-09 22:00:00,17393.0 -2018-07-09 23:00:00,16777.0 -2018-07-10 00:00:00,15533.0 -2018-07-08 01:00:00,10198.0 -2018-07-08 02:00:00,9532.0 -2018-07-08 03:00:00,9072.0 -2018-07-08 04:00:00,8691.0 -2018-07-08 05:00:00,8466.0 -2018-07-08 06:00:00,8339.0 -2018-07-08 07:00:00,8228.0 -2018-07-08 08:00:00,8334.0 -2018-07-08 09:00:00,8914.0 -2018-07-08 10:00:00,9667.0 -2018-07-08 11:00:00,10550.0 -2018-07-08 12:00:00,11297.0 -2018-07-08 13:00:00,12028.0 -2018-07-08 14:00:00,12625.0 -2018-07-08 15:00:00,13232.0 -2018-07-08 16:00:00,13855.0 -2018-07-08 17:00:00,14422.0 -2018-07-08 18:00:00,14933.0 -2018-07-08 19:00:00,15116.0 -2018-07-08 20:00:00,14988.0 -2018-07-08 21:00:00,14546.0 -2018-07-08 22:00:00,13953.0 -2018-07-08 23:00:00,13635.0 -2018-07-09 00:00:00,12699.0 -2018-07-07 01:00:00,10248.0 -2018-07-07 02:00:00,9600.0 -2018-07-07 03:00:00,9144.0 -2018-07-07 04:00:00,8806.0 -2018-07-07 05:00:00,8626.0 -2018-07-07 06:00:00,8599.0 -2018-07-07 07:00:00,8517.0 -2018-07-07 08:00:00,8858.0 -2018-07-07 09:00:00,9497.0 -2018-07-07 10:00:00,10171.0 -2018-07-07 11:00:00,10731.0 -2018-07-07 12:00:00,11283.0 -2018-07-07 13:00:00,11668.0 -2018-07-07 14:00:00,12011.0 -2018-07-07 15:00:00,12201.0 -2018-07-07 16:00:00,12601.0 -2018-07-07 17:00:00,12934.0 -2018-07-07 18:00:00,13277.0 -2018-07-07 19:00:00,13302.0 -2018-07-07 20:00:00,13043.0 -2018-07-07 21:00:00,12489.0 -2018-07-07 22:00:00,11924.0 -2018-07-07 23:00:00,11603.0 -2018-07-08 00:00:00,10929.0 -2018-07-06 01:00:00,13178.0 -2018-07-06 02:00:00,12136.0 -2018-07-06 03:00:00,11303.0 -2018-07-06 04:00:00,10677.0 -2018-07-06 05:00:00,10317.0 -2018-07-06 06:00:00,10275.0 -2018-07-06 07:00:00,10455.0 -2018-07-06 08:00:00,11248.0 -2018-07-06 09:00:00,12290.0 -2018-07-06 10:00:00,13028.0 -2018-07-06 11:00:00,13513.0 -2018-07-06 12:00:00,13875.0 -2018-07-06 13:00:00,13973.0 -2018-07-06 14:00:00,14025.0 -2018-07-06 15:00:00,14200.0 -2018-07-06 16:00:00,14286.0 -2018-07-06 17:00:00,14381.0 -2018-07-06 18:00:00,14375.0 -2018-07-06 19:00:00,14183.0 -2018-07-06 20:00:00,13590.0 -2018-07-06 21:00:00,12901.0 -2018-07-06 22:00:00,12191.0 -2018-07-06 23:00:00,11826.0 -2018-07-07 00:00:00,11082.0 -2018-07-05 01:00:00,13451.0 -2018-07-05 02:00:00,12563.0 -2018-07-05 03:00:00,11826.0 -2018-07-05 04:00:00,11305.0 -2018-07-05 05:00:00,11086.0 -2018-07-05 06:00:00,11140.0 -2018-07-05 07:00:00,11533.0 -2018-07-05 08:00:00,12568.0 -2018-07-05 09:00:00,14000.0 -2018-07-05 10:00:00,15428.0 -2018-07-05 11:00:00,16799.0 -2018-07-05 12:00:00,18001.0 -2018-07-05 13:00:00,18945.0 -2018-07-05 14:00:00,19530.0 -2018-07-05 15:00:00,19488.0 -2018-07-05 16:00:00,18228.0 -2018-07-05 17:00:00,17390.0 -2018-07-05 18:00:00,17069.0 -2018-07-05 19:00:00,16812.0 -2018-07-05 20:00:00,16374.0 -2018-07-05 21:00:00,16103.0 -2018-07-05 22:00:00,15679.0 -2018-07-05 23:00:00,15347.0 -2018-07-06 00:00:00,14329.0 -2018-07-04 01:00:00,14123.0 -2018-07-04 02:00:00,13088.0 -2018-07-04 03:00:00,12394.0 -2018-07-04 04:00:00,11815.0 -2018-07-04 05:00:00,11435.0 -2018-07-04 06:00:00,11185.0 -2018-07-04 07:00:00,11026.0 -2018-07-04 08:00:00,11339.0 -2018-07-04 09:00:00,12266.0 -2018-07-04 10:00:00,13470.0 -2018-07-04 11:00:00,14834.0 -2018-07-04 12:00:00,16089.0 -2018-07-04 13:00:00,16913.0 -2018-07-04 14:00:00,17377.0 -2018-07-04 15:00:00,17818.0 -2018-07-04 16:00:00,17984.0 -2018-07-04 17:00:00,18021.0 -2018-07-04 18:00:00,17834.0 -2018-07-04 19:00:00,17700.0 -2018-07-04 20:00:00,17134.0 -2018-07-04 21:00:00,16365.0 -2018-07-04 22:00:00,15949.0 -2018-07-04 23:00:00,15347.0 -2018-07-05 00:00:00,14424.0 -2018-07-03 01:00:00,11743.0 -2018-07-03 02:00:00,10726.0 -2018-07-03 03:00:00,10103.0 -2018-07-03 04:00:00,9653.0 -2018-07-03 05:00:00,9461.0 -2018-07-03 06:00:00,9557.0 -2018-07-03 07:00:00,9977.0 -2018-07-03 08:00:00,10947.0 -2018-07-03 09:00:00,12122.0 -2018-07-03 10:00:00,13286.0 -2018-07-03 11:00:00,14391.0 -2018-07-03 12:00:00,15475.0 -2018-07-03 13:00:00,16418.0 -2018-07-03 14:00:00,17310.0 -2018-07-03 15:00:00,18058.0 -2018-07-03 16:00:00,18492.0 -2018-07-03 17:00:00,18765.0 -2018-07-03 18:00:00,19005.0 -2018-07-03 19:00:00,18939.0 -2018-07-03 20:00:00,18328.0 -2018-07-03 21:00:00,17483.0 -2018-07-03 22:00:00,16798.0 -2018-07-03 23:00:00,16263.0 -2018-07-04 00:00:00,15221.0 -2018-07-02 01:00:00,13134.0 -2018-07-02 02:00:00,12278.0 -2018-07-02 03:00:00,11615.0 -2018-07-02 04:00:00,11131.0 -2018-07-02 05:00:00,10787.0 -2018-07-02 06:00:00,10806.0 -2018-07-02 07:00:00,11065.0 -2018-07-02 08:00:00,11845.0 -2018-07-02 09:00:00,13042.0 -2018-07-02 10:00:00,13947.0 -2018-07-02 11:00:00,14665.0 -2018-07-02 12:00:00,15322.0 -2018-07-02 13:00:00,15786.0 -2018-07-02 14:00:00,16229.0 -2018-07-02 15:00:00,16688.0 -2018-07-02 16:00:00,17072.0 -2018-07-02 17:00:00,17356.0 -2018-07-02 18:00:00,17486.0 -2018-07-02 19:00:00,17413.0 -2018-07-02 20:00:00,16871.0 -2018-07-02 21:00:00,15962.0 -2018-07-02 22:00:00,14964.0 -2018-07-02 23:00:00,14266.0 -2018-07-03 00:00:00,13017.0 -2018-07-01 01:00:00,15466.0 -2018-07-01 02:00:00,14452.0 -2018-07-01 03:00:00,13586.0 -2018-07-01 04:00:00,12906.0 -2018-07-01 05:00:00,12416.0 -2018-07-01 06:00:00,12099.0 -2018-07-01 07:00:00,11849.0 -2018-07-01 08:00:00,11960.0 -2018-07-01 09:00:00,12831.0 -2018-07-01 10:00:00,14181.0 -2018-07-01 11:00:00,15384.0 -2018-07-01 12:00:00,16494.0 -2018-07-01 13:00:00,17474.0 -2018-07-01 14:00:00,18150.0 -2018-07-01 15:00:00,18634.0 -2018-07-01 16:00:00,18932.0 -2018-07-01 17:00:00,18909.0 -2018-07-01 18:00:00,18056.0 -2018-07-01 19:00:00,16710.0 -2018-07-01 20:00:00,15828.0 -2018-07-01 21:00:00,15320.0 -2018-07-01 22:00:00,14968.0 -2018-07-01 23:00:00,14812.0 -2018-07-02 00:00:00,14068.0 -2018-06-30 01:00:00,16412.0 -2018-06-30 02:00:00,15313.0 -2018-06-30 03:00:00,14478.0 -2018-06-30 04:00:00,13758.0 -2018-06-30 05:00:00,13263.0 -2018-06-30 06:00:00,12937.0 -2018-06-30 07:00:00,12778.0 -2018-06-30 08:00:00,13219.0 -2018-06-30 09:00:00,14318.0 -2018-06-30 10:00:00,15662.0 -2018-06-30 11:00:00,17009.0 -2018-06-30 12:00:00,18114.0 -2018-06-30 13:00:00,18832.0 -2018-06-30 14:00:00,19302.0 -2018-06-30 15:00:00,19605.0 -2018-06-30 16:00:00,19821.0 -2018-06-30 17:00:00,19937.0 -2018-06-30 18:00:00,19954.0 -2018-06-30 19:00:00,19790.0 -2018-06-30 20:00:00,19376.0 -2018-06-30 21:00:00,18709.0 -2018-06-30 22:00:00,18086.0 -2018-06-30 23:00:00,17611.0 -2018-07-01 00:00:00,16600.0 -2018-06-29 01:00:00,13028.0 -2018-06-29 02:00:00,11948.0 -2018-06-29 03:00:00,11243.0 -2018-06-29 04:00:00,10764.0 -2018-06-29 05:00:00,10529.0 -2018-06-29 06:00:00,10599.0 -2018-06-29 07:00:00,11035.0 -2018-06-29 08:00:00,12137.0 -2018-06-29 09:00:00,13605.0 -2018-06-29 10:00:00,14950.0 -2018-06-29 11:00:00,16120.0 -2018-06-29 12:00:00,17214.0 -2018-06-29 13:00:00,18179.0 -2018-06-29 14:00:00,18953.0 -2018-06-29 15:00:00,19756.0 -2018-06-29 16:00:00,20375.0 -2018-06-29 17:00:00,20745.0 -2018-06-29 18:00:00,20996.0 -2018-06-29 19:00:00,20915.0 -2018-06-29 20:00:00,20530.0 -2018-06-29 21:00:00,19965.0 -2018-06-29 22:00:00,19389.0 -2018-06-29 23:00:00,18848.0 -2018-06-30 00:00:00,17633.0 -2018-06-28 01:00:00,11297.0 -2018-06-28 02:00:00,10486.0 -2018-06-28 03:00:00,9927.0 -2018-06-28 04:00:00,9542.0 -2018-06-28 05:00:00,9374.0 -2018-06-28 06:00:00,9474.0 -2018-06-28 07:00:00,9888.0 -2018-06-28 08:00:00,10956.0 -2018-06-28 09:00:00,12254.0 -2018-06-28 10:00:00,13390.0 -2018-06-28 11:00:00,14381.0 -2018-06-28 12:00:00,15291.0 -2018-06-28 13:00:00,16059.0 -2018-06-28 14:00:00,16715.0 -2018-06-28 15:00:00,17322.0 -2018-06-28 16:00:00,17736.0 -2018-06-28 17:00:00,17998.0 -2018-06-28 18:00:00,18075.0 -2018-06-28 19:00:00,17677.0 -2018-06-28 20:00:00,16999.0 -2018-06-28 21:00:00,16355.0 -2018-06-28 22:00:00,15863.0 -2018-06-28 23:00:00,15400.0 -2018-06-29 00:00:00,14306.0 -2018-06-27 01:00:00,11521.0 -2018-06-27 02:00:00,10748.0 -2018-06-27 03:00:00,10212.0 -2018-06-27 04:00:00,9850.0 -2018-06-27 05:00:00,9697.0 -2018-06-27 06:00:00,9790.0 -2018-06-27 07:00:00,10334.0 -2018-06-27 08:00:00,11146.0 -2018-06-27 09:00:00,11983.0 -2018-06-27 10:00:00,12537.0 -2018-06-27 11:00:00,12968.0 -2018-06-27 12:00:00,13463.0 -2018-06-27 13:00:00,13743.0 -2018-06-27 14:00:00,14092.0 -2018-06-27 15:00:00,14671.0 -2018-06-27 16:00:00,15095.0 -2018-06-27 17:00:00,15163.0 -2018-06-27 18:00:00,15069.0 -2018-06-27 19:00:00,15012.0 -2018-06-27 20:00:00,14652.0 -2018-06-27 21:00:00,14115.0 -2018-06-27 22:00:00,13663.0 -2018-06-27 23:00:00,13308.0 -2018-06-28 00:00:00,12389.0 -2018-06-26 01:00:00,11152.0 -2018-06-26 02:00:00,10402.0 -2018-06-26 03:00:00,9867.0 -2018-06-26 04:00:00,9491.0 -2018-06-26 05:00:00,9269.0 -2018-06-26 06:00:00,9378.0 -2018-06-26 07:00:00,9769.0 -2018-06-26 08:00:00,10528.0 -2018-06-26 09:00:00,11358.0 -2018-06-26 10:00:00,11865.0 -2018-06-26 11:00:00,12237.0 -2018-06-26 12:00:00,12833.0 -2018-06-26 13:00:00,13211.0 -2018-06-26 14:00:00,13702.0 -2018-06-26 15:00:00,14279.0 -2018-06-26 16:00:00,15081.0 -2018-06-26 17:00:00,15453.0 -2018-06-26 18:00:00,15369.0 -2018-06-26 19:00:00,15177.0 -2018-06-26 20:00:00,14546.0 -2018-06-26 21:00:00,14039.0 -2018-06-26 22:00:00,13750.0 -2018-06-26 23:00:00,13373.0 -2018-06-27 00:00:00,12504.0 -2018-06-25 01:00:00,9864.0 -2018-06-25 02:00:00,9280.0 -2018-06-25 03:00:00,8841.0 -2018-06-25 04:00:00,8543.0 -2018-06-25 05:00:00,8460.0 -2018-06-25 06:00:00,8664.0 -2018-06-25 07:00:00,9136.0 -2018-06-25 08:00:00,10081.0 -2018-06-25 09:00:00,11174.0 -2018-06-25 10:00:00,11918.0 -2018-06-25 11:00:00,12556.0 -2018-06-25 12:00:00,13146.0 -2018-06-25 13:00:00,13595.0 -2018-06-25 14:00:00,13949.0 -2018-06-25 15:00:00,14293.0 -2018-06-25 16:00:00,14543.0 -2018-06-25 17:00:00,14746.0 -2018-06-25 18:00:00,14845.0 -2018-06-25 19:00:00,14688.0 -2018-06-25 20:00:00,14147.0 -2018-06-25 21:00:00,13468.0 -2018-06-25 22:00:00,13192.0 -2018-06-25 23:00:00,12935.0 -2018-06-26 00:00:00,12110.0 -2018-06-24 01:00:00,9944.0 -2018-06-24 02:00:00,9379.0 -2018-06-24 03:00:00,8941.0 -2018-06-24 04:00:00,8592.0 -2018-06-24 05:00:00,8363.0 -2018-06-24 06:00:00,8259.0 -2018-06-24 07:00:00,8108.0 -2018-06-24 08:00:00,8275.0 -2018-06-24 09:00:00,8900.0 -2018-06-24 10:00:00,9743.0 -2018-06-24 11:00:00,10592.0 -2018-06-24 12:00:00,11376.0 -2018-06-24 13:00:00,12005.0 -2018-06-24 14:00:00,12548.0 -2018-06-24 15:00:00,12928.0 -2018-06-24 16:00:00,13256.0 -2018-06-24 17:00:00,13346.0 -2018-06-24 18:00:00,13215.0 -2018-06-24 19:00:00,13103.0 -2018-06-24 20:00:00,12696.0 -2018-06-24 21:00:00,12002.0 -2018-06-24 22:00:00,11547.0 -2018-06-24 23:00:00,11301.0 -2018-06-25 00:00:00,10623.0 -2018-06-23 01:00:00,9391.0 -2018-06-23 02:00:00,8899.0 -2018-06-23 03:00:00,8572.0 -2018-06-23 04:00:00,8332.0 -2018-06-23 05:00:00,8210.0 -2018-06-23 06:00:00,8207.0 -2018-06-23 07:00:00,8354.0 -2018-06-23 08:00:00,8577.0 -2018-06-23 09:00:00,9054.0 -2018-06-23 10:00:00,9648.0 -2018-06-23 11:00:00,10159.0 -2018-06-23 12:00:00,10541.0 -2018-06-23 13:00:00,10928.0 -2018-06-23 14:00:00,11238.0 -2018-06-23 15:00:00,11486.0 -2018-06-23 16:00:00,11706.0 -2018-06-23 17:00:00,11846.0 -2018-06-23 18:00:00,11959.0 -2018-06-23 19:00:00,11929.0 -2018-06-23 20:00:00,11610.0 -2018-06-23 21:00:00,11244.0 -2018-06-23 22:00:00,11221.0 -2018-06-23 23:00:00,11131.0 -2018-06-24 00:00:00,10572.0 -2018-06-22 01:00:00,9914.0 -2018-06-22 02:00:00,9378.0 -2018-06-22 03:00:00,9012.0 -2018-06-22 04:00:00,8822.0 -2018-06-22 05:00:00,8687.0 -2018-06-22 06:00:00,8843.0 -2018-06-22 07:00:00,9253.0 -2018-06-22 08:00:00,10029.0 -2018-06-22 09:00:00,10700.0 -2018-06-22 10:00:00,11146.0 -2018-06-22 11:00:00,11371.0 -2018-06-22 12:00:00,11508.0 -2018-06-22 13:00:00,11529.0 -2018-06-22 14:00:00,11472.0 -2018-06-22 15:00:00,11485.0 -2018-06-22 16:00:00,11385.0 -2018-06-22 17:00:00,11195.0 -2018-06-22 18:00:00,11037.0 -2018-06-22 19:00:00,10861.0 -2018-06-22 20:00:00,10663.0 -2018-06-22 21:00:00,10541.0 -2018-06-22 22:00:00,10631.0 -2018-06-22 23:00:00,10543.0 -2018-06-23 00:00:00,9989.0 -2018-06-21 01:00:00,10648.0 -2018-06-21 02:00:00,10016.0 -2018-06-21 03:00:00,9573.0 -2018-06-21 04:00:00,9231.0 -2018-06-21 05:00:00,9143.0 -2018-06-21 06:00:00,9289.0 -2018-06-21 07:00:00,9818.0 -2018-06-21 08:00:00,10657.0 -2018-06-21 09:00:00,11481.0 -2018-06-21 10:00:00,11963.0 -2018-06-21 11:00:00,12251.0 -2018-06-21 12:00:00,12485.0 -2018-06-21 13:00:00,12497.0 -2018-06-21 14:00:00,12455.0 -2018-06-21 15:00:00,12426.0 -2018-06-21 16:00:00,12266.0 -2018-06-21 17:00:00,12064.0 -2018-06-21 18:00:00,11920.0 -2018-06-21 19:00:00,11782.0 -2018-06-21 20:00:00,11615.0 -2018-06-21 21:00:00,11528.0 -2018-06-21 22:00:00,11505.0 -2018-06-21 23:00:00,11258.0 -2018-06-22 00:00:00,10622.0 -2018-06-20 01:00:00,11069.0 -2018-06-20 02:00:00,10334.0 -2018-06-20 03:00:00,9832.0 -2018-06-20 04:00:00,9510.0 -2018-06-20 05:00:00,9381.0 -2018-06-20 06:00:00,9469.0 -2018-06-20 07:00:00,9949.0 -2018-06-20 08:00:00,10810.0 -2018-06-20 09:00:00,11640.0 -2018-06-20 10:00:00,12103.0 -2018-06-20 11:00:00,12611.0 -2018-06-20 12:00:00,13265.0 -2018-06-20 13:00:00,13722.0 -2018-06-20 14:00:00,14063.0 -2018-06-20 15:00:00,14514.0 -2018-06-20 16:00:00,14786.0 -2018-06-20 17:00:00,14741.0 -2018-06-20 18:00:00,14671.0 -2018-06-20 19:00:00,14418.0 -2018-06-20 20:00:00,13817.0 -2018-06-20 21:00:00,13058.0 -2018-06-20 22:00:00,12712.0 -2018-06-20 23:00:00,12310.0 -2018-06-21 00:00:00,11492.0 -2018-06-19 01:00:00,13394.0 -2018-06-19 02:00:00,12273.0 -2018-06-19 03:00:00,11426.0 -2018-06-19 04:00:00,10841.0 -2018-06-19 05:00:00,10524.0 -2018-06-19 06:00:00,10509.0 -2018-06-19 07:00:00,10855.0 -2018-06-19 08:00:00,11523.0 -2018-06-19 09:00:00,12339.0 -2018-06-19 10:00:00,13025.0 -2018-06-19 11:00:00,13651.0 -2018-06-19 12:00:00,14293.0 -2018-06-19 13:00:00,15008.0 -2018-06-19 14:00:00,15630.0 -2018-06-19 15:00:00,16160.0 -2018-06-19 16:00:00,16387.0 -2018-06-19 17:00:00,16030.0 -2018-06-19 18:00:00,15315.0 -2018-06-19 19:00:00,14758.0 -2018-06-19 20:00:00,14127.0 -2018-06-19 21:00:00,13556.0 -2018-06-19 22:00:00,13296.0 -2018-06-19 23:00:00,12891.0 -2018-06-20 00:00:00,12016.0 -2018-06-18 01:00:00,14753.0 -2018-06-18 02:00:00,13812.0 -2018-06-18 03:00:00,13239.0 -2018-06-18 04:00:00,12770.0 -2018-06-18 05:00:00,12495.0 -2018-06-18 06:00:00,12578.0 -2018-06-18 07:00:00,13042.0 -2018-06-18 08:00:00,14139.0 -2018-06-18 09:00:00,15689.0 -2018-06-18 10:00:00,16983.0 -2018-06-18 11:00:00,18085.0 -2018-06-18 12:00:00,19090.0 -2018-06-18 13:00:00,19926.0 -2018-06-18 14:00:00,20498.0 -2018-06-18 15:00:00,20941.0 -2018-06-18 16:00:00,21209.0 -2018-06-18 17:00:00,21349.0 -2018-06-18 18:00:00,21344.0 -2018-06-18 19:00:00,20727.0 -2018-06-18 20:00:00,19188.0 -2018-06-18 21:00:00,18050.0 -2018-06-18 22:00:00,17122.0 -2018-06-18 23:00:00,16209.0 -2018-06-19 00:00:00,14819.0 -2018-06-17 01:00:00,14147.0 -2018-06-17 02:00:00,13278.0 -2018-06-17 03:00:00,12563.0 -2018-06-17 04:00:00,11966.0 -2018-06-17 05:00:00,11474.0 -2018-06-17 06:00:00,11226.0 -2018-06-17 07:00:00,10926.0 -2018-06-17 08:00:00,11219.0 -2018-06-17 09:00:00,12275.0 -2018-06-17 10:00:00,13646.0 -2018-06-17 11:00:00,15034.0 -2018-06-17 12:00:00,16198.0 -2018-06-17 13:00:00,17102.0 -2018-06-17 14:00:00,17760.0 -2018-06-17 15:00:00,18241.0 -2018-06-17 16:00:00,18449.0 -2018-06-17 17:00:00,18390.0 -2018-06-17 18:00:00,18215.0 -2018-06-17 19:00:00,17974.0 -2018-06-17 20:00:00,17599.0 -2018-06-17 21:00:00,17213.0 -2018-06-17 22:00:00,16949.0 -2018-06-17 23:00:00,16789.0 -2018-06-18 00:00:00,15823.0 -2018-06-16 01:00:00,13472.0 -2018-06-16 02:00:00,12619.0 -2018-06-16 03:00:00,11781.0 -2018-06-16 04:00:00,10973.0 -2018-06-16 05:00:00,10456.0 -2018-06-16 06:00:00,10275.0 -2018-06-16 07:00:00,10211.0 -2018-06-16 08:00:00,10410.0 -2018-06-16 09:00:00,11334.0 -2018-06-16 10:00:00,12681.0 -2018-06-16 11:00:00,14007.0 -2018-06-16 12:00:00,15050.0 -2018-06-16 13:00:00,15834.0 -2018-06-16 14:00:00,16284.0 -2018-06-16 15:00:00,16632.0 -2018-06-16 16:00:00,17057.0 -2018-06-16 17:00:00,17496.0 -2018-06-16 18:00:00,17801.0 -2018-06-16 19:00:00,17910.0 -2018-06-16 20:00:00,17464.0 -2018-06-16 21:00:00,16834.0 -2018-06-16 22:00:00,16384.0 -2018-06-16 23:00:00,16000.0 -2018-06-17 00:00:00,15129.0 -2018-06-15 01:00:00,10585.0 -2018-06-15 02:00:00,9850.0 -2018-06-15 03:00:00,9382.0 -2018-06-15 04:00:00,8982.0 -2018-06-15 05:00:00,8848.0 -2018-06-15 06:00:00,8954.0 -2018-06-15 07:00:00,9326.0 -2018-06-15 08:00:00,10283.0 -2018-06-15 09:00:00,11329.0 -2018-06-15 10:00:00,12082.0 -2018-06-15 11:00:00,12501.0 -2018-06-15 12:00:00,12818.0 -2018-06-15 13:00:00,13178.0 -2018-06-15 14:00:00,13851.0 -2018-06-15 15:00:00,14763.0 -2018-06-15 16:00:00,15616.0 -2018-06-15 17:00:00,16326.0 -2018-06-15 18:00:00,16833.0 -2018-06-15 19:00:00,16994.0 -2018-06-15 20:00:00,16684.0 -2018-06-15 21:00:00,16062.0 -2018-06-15 22:00:00,15713.0 -2018-06-15 23:00:00,15411.0 -2018-06-16 00:00:00,14555.0 -2018-06-14 01:00:00,10507.0 -2018-06-14 02:00:00,9709.0 -2018-06-14 03:00:00,9180.0 -2018-06-14 04:00:00,8904.0 -2018-06-14 05:00:00,8766.0 -2018-06-14 06:00:00,8882.0 -2018-06-14 07:00:00,9260.0 -2018-06-14 08:00:00,10199.0 -2018-06-14 09:00:00,11085.0 -2018-06-14 10:00:00,12004.0 -2018-06-14 11:00:00,12748.0 -2018-06-14 12:00:00,13344.0 -2018-06-14 13:00:00,13829.0 -2018-06-14 14:00:00,14156.0 -2018-06-14 15:00:00,14394.0 -2018-06-14 16:00:00,14347.0 -2018-06-14 17:00:00,14149.0 -2018-06-14 18:00:00,13910.0 -2018-06-14 19:00:00,13617.0 -2018-06-14 20:00:00,13147.0 -2018-06-14 21:00:00,12760.0 -2018-06-14 22:00:00,12630.0 -2018-06-14 23:00:00,12451.0 -2018-06-15 00:00:00,11567.0 -2018-06-13 01:00:00,10997.0 -2018-06-13 02:00:00,10254.0 -2018-06-13 03:00:00,9767.0 -2018-06-13 04:00:00,9463.0 -2018-06-13 05:00:00,9358.0 -2018-06-13 06:00:00,9460.0 -2018-06-13 07:00:00,9917.0 -2018-06-13 08:00:00,10939.0 -2018-06-13 09:00:00,11978.0 -2018-06-13 10:00:00,12823.0 -2018-06-13 11:00:00,13454.0 -2018-06-13 12:00:00,13981.0 -2018-06-13 13:00:00,14261.0 -2018-06-13 14:00:00,14467.0 -2018-06-13 15:00:00,14749.0 -2018-06-13 16:00:00,14970.0 -2018-06-13 17:00:00,15134.0 -2018-06-13 18:00:00,15236.0 -2018-06-13 19:00:00,15119.0 -2018-06-13 20:00:00,14622.0 -2018-06-13 21:00:00,13925.0 -2018-06-13 22:00:00,13228.0 -2018-06-13 23:00:00,12672.0 -2018-06-14 00:00:00,11591.0 -2018-06-12 01:00:00,9669.0 -2018-06-12 02:00:00,9117.0 -2018-06-12 03:00:00,8789.0 -2018-06-12 04:00:00,8583.0 -2018-06-12 05:00:00,8559.0 -2018-06-12 06:00:00,8730.0 -2018-06-12 07:00:00,9215.0 -2018-06-12 08:00:00,10120.0 -2018-06-12 09:00:00,11002.0 -2018-06-12 10:00:00,11576.0 -2018-06-12 11:00:00,12006.0 -2018-06-12 12:00:00,12449.0 -2018-06-12 13:00:00,12711.0 -2018-06-12 14:00:00,12948.0 -2018-06-12 15:00:00,13340.0 -2018-06-12 16:00:00,13674.0 -2018-06-12 17:00:00,13879.0 -2018-06-12 18:00:00,13856.0 -2018-06-12 19:00:00,13718.0 -2018-06-12 20:00:00,13389.0 -2018-06-12 21:00:00,13190.0 -2018-06-12 22:00:00,13194.0 -2018-06-12 23:00:00,12955.0 -2018-06-13 00:00:00,12019.0 -2018-06-11 01:00:00,8940.0 -2018-06-11 02:00:00,8590.0 -2018-06-11 03:00:00,8338.0 -2018-06-11 04:00:00,8232.0 -2018-06-11 05:00:00,8153.0 -2018-06-11 06:00:00,8404.0 -2018-06-11 07:00:00,8848.0 -2018-06-11 08:00:00,9679.0 -2018-06-11 09:00:00,10501.0 -2018-06-11 10:00:00,10976.0 -2018-06-11 11:00:00,11284.0 -2018-06-11 12:00:00,11566.0 -2018-06-11 13:00:00,11704.0 -2018-06-11 14:00:00,11765.0 -2018-06-11 15:00:00,11852.0 -2018-06-11 16:00:00,11854.0 -2018-06-11 17:00:00,11714.0 -2018-06-11 18:00:00,11629.0 -2018-06-11 19:00:00,11514.0 -2018-06-11 20:00:00,11383.0 -2018-06-11 21:00:00,11243.0 -2018-06-11 22:00:00,11361.0 -2018-06-11 23:00:00,11113.0 -2018-06-12 00:00:00,10432.0 -2018-06-10 01:00:00,9447.0 -2018-06-10 02:00:00,8863.0 -2018-06-10 03:00:00,8478.0 -2018-06-10 04:00:00,8207.0 -2018-06-10 05:00:00,8165.0 -2018-06-10 06:00:00,8148.0 -2018-06-10 07:00:00,8169.0 -2018-06-10 08:00:00,8121.0 -2018-06-10 09:00:00,8431.0 -2018-06-10 10:00:00,8876.0 -2018-06-10 11:00:00,9307.0 -2018-06-10 12:00:00,9654.0 -2018-06-10 13:00:00,9900.0 -2018-06-10 14:00:00,9973.0 -2018-06-10 15:00:00,9967.0 -2018-06-10 16:00:00,9895.0 -2018-06-10 17:00:00,9832.0 -2018-06-10 18:00:00,9740.0 -2018-06-10 19:00:00,9808.0 -2018-06-10 20:00:00,9748.0 -2018-06-10 21:00:00,9717.0 -2018-06-10 22:00:00,9953.0 -2018-06-10 23:00:00,9900.0 -2018-06-11 00:00:00,9494.0 -2018-06-09 01:00:00,9552.0 -2018-06-09 02:00:00,8960.0 -2018-06-09 03:00:00,8538.0 -2018-06-09 04:00:00,8283.0 -2018-06-09 05:00:00,8103.0 -2018-06-09 06:00:00,8111.0 -2018-06-09 07:00:00,8379.0 -2018-06-09 08:00:00,8701.0 -2018-06-09 09:00:00,9148.0 -2018-06-09 10:00:00,9664.0 -2018-06-09 11:00:00,10038.0 -2018-06-09 12:00:00,10292.0 -2018-06-09 13:00:00,10492.0 -2018-06-09 14:00:00,10745.0 -2018-06-09 15:00:00,10991.0 -2018-06-09 16:00:00,11332.0 -2018-06-09 17:00:00,11722.0 -2018-06-09 18:00:00,12046.0 -2018-06-09 19:00:00,12152.0 -2018-06-09 20:00:00,11850.0 -2018-06-09 21:00:00,11282.0 -2018-06-09 22:00:00,11043.0 -2018-06-09 23:00:00,10743.0 -2018-06-10 00:00:00,10072.0 -2018-06-08 01:00:00,10020.0 -2018-06-08 02:00:00,9316.0 -2018-06-08 03:00:00,8893.0 -2018-06-08 04:00:00,8578.0 -2018-06-08 05:00:00,8458.0 -2018-06-08 06:00:00,8577.0 -2018-06-08 07:00:00,8975.0 -2018-06-08 08:00:00,9642.0 -2018-06-08 09:00:00,10363.0 -2018-06-08 10:00:00,10905.0 -2018-06-08 11:00:00,11192.0 -2018-06-08 12:00:00,11428.0 -2018-06-08 13:00:00,11533.0 -2018-06-08 14:00:00,11661.0 -2018-06-08 15:00:00,11893.0 -2018-06-08 16:00:00,11970.0 -2018-06-08 17:00:00,11899.0 -2018-06-08 18:00:00,11908.0 -2018-06-08 19:00:00,11850.0 -2018-06-08 20:00:00,11637.0 -2018-06-08 21:00:00,11283.0 -2018-06-08 22:00:00,11250.0 -2018-06-08 23:00:00,11021.0 -2018-06-09 00:00:00,10343.0 -2018-06-07 01:00:00,10099.0 -2018-06-07 02:00:00,9475.0 -2018-06-07 03:00:00,9034.0 -2018-06-07 04:00:00,8694.0 -2018-06-07 05:00:00,8622.0 -2018-06-07 06:00:00,8717.0 -2018-06-07 07:00:00,9154.0 -2018-06-07 08:00:00,9951.0 -2018-06-07 09:00:00,10855.0 -2018-06-07 10:00:00,11627.0 -2018-06-07 11:00:00,12357.0 -2018-06-07 12:00:00,13000.0 -2018-06-07 13:00:00,13495.0 -2018-06-07 14:00:00,13896.0 -2018-06-07 15:00:00,14357.0 -2018-06-07 16:00:00,14782.0 -2018-06-07 17:00:00,15001.0 -2018-06-07 18:00:00,15029.0 -2018-06-07 19:00:00,14674.0 -2018-06-07 20:00:00,13865.0 -2018-06-07 21:00:00,12944.0 -2018-06-07 22:00:00,12448.0 -2018-06-07 23:00:00,11903.0 -2018-06-08 00:00:00,10977.0 -2018-06-06 01:00:00,8997.0 -2018-06-06 02:00:00,8508.0 -2018-06-06 03:00:00,8222.0 -2018-06-06 04:00:00,8020.0 -2018-06-06 05:00:00,7922.0 -2018-06-06 06:00:00,8062.0 -2018-06-06 07:00:00,8394.0 -2018-06-06 08:00:00,9190.0 -2018-06-06 09:00:00,10026.0 -2018-06-06 10:00:00,10630.0 -2018-06-06 11:00:00,11055.0 -2018-06-06 12:00:00,11439.0 -2018-06-06 13:00:00,11619.0 -2018-06-06 14:00:00,11672.0 -2018-06-06 15:00:00,11684.0 -2018-06-06 16:00:00,11927.0 -2018-06-06 17:00:00,12127.0 -2018-06-06 18:00:00,12137.0 -2018-06-06 19:00:00,12190.0 -2018-06-06 20:00:00,12145.0 -2018-06-06 21:00:00,11921.0 -2018-06-06 22:00:00,11976.0 -2018-06-06 23:00:00,11770.0 -2018-06-07 00:00:00,10987.0 -2018-06-05 01:00:00,10466.0 -2018-06-05 02:00:00,9801.0 -2018-06-05 03:00:00,9317.0 -2018-06-05 04:00:00,8985.0 -2018-06-05 05:00:00,8818.0 -2018-06-05 06:00:00,8927.0 -2018-06-05 07:00:00,9367.0 -2018-06-05 08:00:00,10177.0 -2018-06-05 09:00:00,10955.0 -2018-06-05 10:00:00,11494.0 -2018-06-05 11:00:00,11863.0 -2018-06-05 12:00:00,12116.0 -2018-06-05 13:00:00,12188.0 -2018-06-05 14:00:00,12186.0 -2018-06-05 15:00:00,12185.0 -2018-06-05 16:00:00,12053.0 -2018-06-05 17:00:00,11854.0 -2018-06-05 18:00:00,11599.0 -2018-06-05 19:00:00,11377.0 -2018-06-05 20:00:00,10941.0 -2018-06-05 21:00:00,10607.0 -2018-06-05 22:00:00,10529.0 -2018-06-05 23:00:00,10382.0 -2018-06-06 00:00:00,9720.0 -2018-06-04 01:00:00,8984.0 -2018-06-04 02:00:00,8474.0 -2018-06-04 03:00:00,8154.0 -2018-06-04 04:00:00,7968.0 -2018-06-04 05:00:00,7928.0 -2018-06-04 06:00:00,8126.0 -2018-06-04 07:00:00,8547.0 -2018-06-04 08:00:00,9435.0 -2018-06-04 09:00:00,10372.0 -2018-06-04 10:00:00,11007.0 -2018-06-04 11:00:00,11408.0 -2018-06-04 12:00:00,11773.0 -2018-06-04 13:00:00,12034.0 -2018-06-04 14:00:00,12264.0 -2018-06-04 15:00:00,12579.0 -2018-06-04 16:00:00,12870.0 -2018-06-04 17:00:00,13177.0 -2018-06-04 18:00:00,13399.0 -2018-06-04 19:00:00,13449.0 -2018-06-04 20:00:00,13244.0 -2018-06-04 21:00:00,12872.0 -2018-06-04 22:00:00,12588.0 -2018-06-04 23:00:00,12285.0 -2018-06-05 00:00:00,11407.0 -2018-06-03 01:00:00,9184.0 -2018-06-03 02:00:00,8687.0 -2018-06-03 03:00:00,8321.0 -2018-06-03 04:00:00,8104.0 -2018-06-03 05:00:00,7953.0 -2018-06-03 06:00:00,7927.0 -2018-06-03 07:00:00,7785.0 -2018-06-03 08:00:00,7930.0 -2018-06-03 09:00:00,8315.0 -2018-06-03 10:00:00,8870.0 -2018-06-03 11:00:00,9361.0 -2018-06-03 12:00:00,9786.0 -2018-06-03 13:00:00,10136.0 -2018-06-03 14:00:00,10415.0 -2018-06-03 15:00:00,10602.0 -2018-06-03 16:00:00,10766.0 -2018-06-03 17:00:00,10848.0 -2018-06-03 18:00:00,10931.0 -2018-06-03 19:00:00,10864.0 -2018-06-03 20:00:00,10680.0 -2018-06-03 21:00:00,10372.0 -2018-06-03 22:00:00,10265.0 -2018-06-03 23:00:00,10175.0 -2018-06-04 00:00:00,9624.0 -2018-06-02 01:00:00,9357.0 -2018-06-02 02:00:00,8799.0 -2018-06-02 03:00:00,8388.0 -2018-06-02 04:00:00,8153.0 -2018-06-02 05:00:00,8014.0 -2018-06-02 06:00:00,8003.0 -2018-06-02 07:00:00,8030.0 -2018-06-02 08:00:00,8275.0 -2018-06-02 09:00:00,8761.0 -2018-06-02 10:00:00,9253.0 -2018-06-02 11:00:00,9671.0 -2018-06-02 12:00:00,9893.0 -2018-06-02 13:00:00,10082.0 -2018-06-02 14:00:00,10112.0 -2018-06-02 15:00:00,10169.0 -2018-06-02 16:00:00,10282.0 -2018-06-02 17:00:00,10469.0 -2018-06-02 18:00:00,10665.0 -2018-06-02 19:00:00,10645.0 -2018-06-02 20:00:00,10495.0 -2018-06-02 21:00:00,10336.0 -2018-06-02 22:00:00,10457.0 -2018-06-02 23:00:00,10311.0 -2018-06-03 00:00:00,9808.0 -2018-06-01 01:00:00,13078.0 -2018-06-01 02:00:00,11985.0 -2018-06-01 03:00:00,11243.0 -2018-06-01 04:00:00,10649.0 -2018-06-01 05:00:00,10360.0 -2018-06-01 06:00:00,10355.0 -2018-06-01 07:00:00,10790.0 -2018-06-01 08:00:00,11856.0 -2018-06-01 09:00:00,13120.0 -2018-06-01 10:00:00,13593.0 -2018-06-01 11:00:00,13743.0 -2018-06-01 12:00:00,13736.0 -2018-06-01 13:00:00,13724.0 -2018-06-01 14:00:00,13640.0 -2018-06-01 15:00:00,13648.0 -2018-06-01 16:00:00,13623.0 -2018-06-01 17:00:00,13536.0 -2018-06-01 18:00:00,13278.0 -2018-06-01 19:00:00,12835.0 -2018-06-01 20:00:00,12161.0 -2018-06-01 21:00:00,11488.0 -2018-06-01 22:00:00,11168.0 -2018-06-01 23:00:00,10852.0 -2018-06-02 00:00:00,10148.0 -2018-05-31 01:00:00,11765.0 -2018-05-31 02:00:00,11010.0 -2018-05-31 03:00:00,10433.0 -2018-05-31 04:00:00,10054.0 -2018-05-31 05:00:00,9885.0 -2018-05-31 06:00:00,9985.0 -2018-05-31 07:00:00,10460.0 -2018-05-31 08:00:00,11579.0 -2018-05-31 09:00:00,12863.0 -2018-05-31 10:00:00,13880.0 -2018-05-31 11:00:00,14915.0 -2018-05-31 12:00:00,15973.0 -2018-05-31 13:00:00,16686.0 -2018-05-31 14:00:00,17306.0 -2018-05-31 15:00:00,17842.0 -2018-05-31 16:00:00,18048.0 -2018-05-31 17:00:00,18151.0 -2018-05-31 18:00:00,18397.0 -2018-05-31 19:00:00,18352.0 -2018-05-31 20:00:00,17781.0 -2018-05-31 21:00:00,17018.0 -2018-05-31 22:00:00,16370.0 -2018-05-31 23:00:00,15752.0 -2018-06-01 00:00:00,14417.0 -2018-05-30 01:00:00,11916.0 -2018-05-30 02:00:00,11025.0 -2018-05-30 03:00:00,10443.0 -2018-05-30 04:00:00,10052.0 -2018-05-30 05:00:00,9886.0 -2018-05-30 06:00:00,10020.0 -2018-05-30 07:00:00,10579.0 -2018-05-30 08:00:00,11524.0 -2018-05-30 09:00:00,12495.0 -2018-05-30 10:00:00,13202.0 -2018-05-30 11:00:00,13857.0 -2018-05-30 12:00:00,14567.0 -2018-05-30 13:00:00,15382.0 -2018-05-30 14:00:00,16066.0 -2018-05-30 15:00:00,16592.0 -2018-05-30 16:00:00,16763.0 -2018-05-30 17:00:00,16345.0 -2018-05-30 18:00:00,15872.0 -2018-05-30 19:00:00,15462.0 -2018-05-30 20:00:00,14964.0 -2018-05-30 21:00:00,14455.0 -2018-05-30 22:00:00,14250.0 -2018-05-30 23:00:00,13820.0 -2018-05-31 00:00:00,12863.0 -2018-05-29 01:00:00,12167.0 -2018-05-29 02:00:00,11210.0 -2018-05-29 03:00:00,10513.0 -2018-05-29 04:00:00,10037.0 -2018-05-29 05:00:00,9805.0 -2018-05-29 06:00:00,9846.0 -2018-05-29 07:00:00,10298.0 -2018-05-29 08:00:00,11363.0 -2018-05-29 09:00:00,12590.0 -2018-05-29 10:00:00,13642.0 -2018-05-29 11:00:00,14471.0 -2018-05-29 12:00:00,15241.0 -2018-05-29 13:00:00,15769.0 -2018-05-29 14:00:00,16374.0 -2018-05-29 15:00:00,16891.0 -2018-05-29 16:00:00,17081.0 -2018-05-29 17:00:00,17065.0 -2018-05-29 18:00:00,16896.0 -2018-05-29 19:00:00,16619.0 -2018-05-29 20:00:00,15783.0 -2018-05-29 21:00:00,14962.0 -2018-05-29 22:00:00,14669.0 -2018-05-29 23:00:00,14183.0 -2018-05-30 00:00:00,13069.0 -2018-05-28 01:00:00,12791.0 -2018-05-28 02:00:00,11788.0 -2018-05-28 03:00:00,11015.0 -2018-05-28 04:00:00,10532.0 -2018-05-28 05:00:00,10164.0 -2018-05-28 06:00:00,9924.0 -2018-05-28 07:00:00,9845.0 -2018-05-28 08:00:00,10260.0 -2018-05-28 09:00:00,11213.0 -2018-05-28 10:00:00,12510.0 -2018-05-28 11:00:00,13794.0 -2018-05-28 12:00:00,14627.0 -2018-05-28 13:00:00,14768.0 -2018-05-28 14:00:00,14962.0 -2018-05-28 15:00:00,15255.0 -2018-05-28 16:00:00,15741.0 -2018-05-28 17:00:00,16394.0 -2018-05-28 18:00:00,17002.0 -2018-05-28 19:00:00,17254.0 -2018-05-28 20:00:00,16910.0 -2018-05-28 21:00:00,16147.0 -2018-05-28 22:00:00,15485.0 -2018-05-28 23:00:00,14768.0 -2018-05-29 00:00:00,13487.0 -2018-05-27 01:00:00,11795.0 -2018-05-27 02:00:00,10951.0 -2018-05-27 03:00:00,10262.0 -2018-05-27 04:00:00,9781.0 -2018-05-27 05:00:00,9406.0 -2018-05-27 06:00:00,9243.0 -2018-05-27 07:00:00,9028.0 -2018-05-27 08:00:00,9350.0 -2018-05-27 09:00:00,10276.0 -2018-05-27 10:00:00,11589.0 -2018-05-27 11:00:00,12974.0 -2018-05-27 12:00:00,14161.0 -2018-05-27 13:00:00,15126.0 -2018-05-27 14:00:00,15768.0 -2018-05-27 15:00:00,16225.0 -2018-05-27 16:00:00,16602.0 -2018-05-27 17:00:00,16888.0 -2018-05-27 18:00:00,17001.0 -2018-05-27 19:00:00,16883.0 -2018-05-27 20:00:00,16525.0 -2018-05-27 21:00:00,15825.0 -2018-05-27 22:00:00,15320.0 -2018-05-27 23:00:00,14761.0 -2018-05-28 00:00:00,13811.0 -2018-05-26 01:00:00,11981.0 -2018-05-26 02:00:00,11112.0 -2018-05-26 03:00:00,10430.0 -2018-05-26 04:00:00,9919.0 -2018-05-26 05:00:00,9633.0 -2018-05-26 06:00:00,9477.0 -2018-05-26 07:00:00,9395.0 -2018-05-26 08:00:00,9617.0 -2018-05-26 09:00:00,10373.0 -2018-05-26 10:00:00,11357.0 -2018-05-26 11:00:00,12278.0 -2018-05-26 12:00:00,13214.0 -2018-05-26 13:00:00,13941.0 -2018-05-26 14:00:00,14463.0 -2018-05-26 15:00:00,14890.0 -2018-05-26 16:00:00,15247.0 -2018-05-26 17:00:00,15577.0 -2018-05-26 18:00:00,15768.0 -2018-05-26 19:00:00,15755.0 -2018-05-26 20:00:00,15410.0 -2018-05-26 21:00:00,14819.0 -2018-05-26 22:00:00,14221.0 -2018-05-26 23:00:00,13717.0 -2018-05-27 00:00:00,12798.0 -2018-05-25 01:00:00,10915.0 -2018-05-25 02:00:00,10062.0 -2018-05-25 03:00:00,9480.0 -2018-05-25 04:00:00,9089.0 -2018-05-25 05:00:00,8862.0 -2018-05-25 06:00:00,8940.0 -2018-05-25 07:00:00,9309.0 -2018-05-25 08:00:00,10284.0 -2018-05-25 09:00:00,11435.0 -2018-05-25 10:00:00,12444.0 -2018-05-25 11:00:00,13288.0 -2018-05-25 12:00:00,14122.0 -2018-05-25 13:00:00,14820.0 -2018-05-25 14:00:00,15351.0 -2018-05-25 15:00:00,15854.0 -2018-05-25 16:00:00,16168.0 -2018-05-25 17:00:00,16159.0 -2018-05-25 18:00:00,15925.0 -2018-05-25 19:00:00,15412.0 -2018-05-25 20:00:00,14785.0 -2018-05-25 21:00:00,14342.0 -2018-05-25 22:00:00,14172.0 -2018-05-25 23:00:00,13789.0 -2018-05-26 00:00:00,12979.0 -2018-05-24 01:00:00,9660.0 -2018-05-24 02:00:00,9019.0 -2018-05-24 03:00:00,8575.0 -2018-05-24 04:00:00,8343.0 -2018-05-24 05:00:00,8246.0 -2018-05-24 06:00:00,8366.0 -2018-05-24 07:00:00,8792.0 -2018-05-24 08:00:00,9686.0 -2018-05-24 09:00:00,10730.0 -2018-05-24 10:00:00,11437.0 -2018-05-24 11:00:00,11984.0 -2018-05-24 12:00:00,12533.0 -2018-05-24 13:00:00,12987.0 -2018-05-24 14:00:00,13381.0 -2018-05-24 15:00:00,13864.0 -2018-05-24 16:00:00,14216.0 -2018-05-24 17:00:00,14517.0 -2018-05-24 18:00:00,14775.0 -2018-05-24 19:00:00,14710.0 -2018-05-24 20:00:00,14354.0 -2018-05-24 21:00:00,13868.0 -2018-05-24 22:00:00,13583.0 -2018-05-24 23:00:00,13066.0 -2018-05-25 00:00:00,12043.0 -2018-05-23 01:00:00,9079.0 -2018-05-23 02:00:00,8593.0 -2018-05-23 03:00:00,8319.0 -2018-05-23 04:00:00,8104.0 -2018-05-23 05:00:00,8079.0 -2018-05-23 06:00:00,8253.0 -2018-05-23 07:00:00,8688.0 -2018-05-23 08:00:00,9518.0 -2018-05-23 09:00:00,10359.0 -2018-05-23 10:00:00,10859.0 -2018-05-23 11:00:00,11239.0 -2018-05-23 12:00:00,11583.0 -2018-05-23 13:00:00,11808.0 -2018-05-23 14:00:00,12003.0 -2018-05-23 15:00:00,12312.0 -2018-05-23 16:00:00,12478.0 -2018-05-23 17:00:00,12587.0 -2018-05-23 18:00:00,12673.0 -2018-05-23 19:00:00,12612.0 -2018-05-23 20:00:00,12244.0 -2018-05-23 21:00:00,11906.0 -2018-05-23 22:00:00,11823.0 -2018-05-23 23:00:00,11445.0 -2018-05-24 00:00:00,10567.0 -2018-05-22 01:00:00,9065.0 -2018-05-22 02:00:00,8618.0 -2018-05-22 03:00:00,8333.0 -2018-05-22 04:00:00,8145.0 -2018-05-22 05:00:00,8110.0 -2018-05-22 06:00:00,8278.0 -2018-05-22 07:00:00,8838.0 -2018-05-22 08:00:00,9669.0 -2018-05-22 09:00:00,10371.0 -2018-05-22 10:00:00,10753.0 -2018-05-22 11:00:00,10976.0 -2018-05-22 12:00:00,11145.0 -2018-05-22 13:00:00,11289.0 -2018-05-22 14:00:00,11322.0 -2018-05-22 15:00:00,11381.0 -2018-05-22 16:00:00,11292.0 -2018-05-22 17:00:00,11196.0 -2018-05-22 18:00:00,11091.0 -2018-05-22 19:00:00,10956.0 -2018-05-22 20:00:00,10739.0 -2018-05-22 21:00:00,10616.0 -2018-05-22 22:00:00,10776.0 -2018-05-22 23:00:00,10517.0 -2018-05-23 00:00:00,9819.0 -2018-05-21 01:00:00,8317.0 -2018-05-21 02:00:00,8003.0 -2018-05-21 03:00:00,7811.0 -2018-05-21 04:00:00,7723.0 -2018-05-21 05:00:00,7728.0 -2018-05-21 06:00:00,7960.0 -2018-05-21 07:00:00,8666.0 -2018-05-21 08:00:00,9631.0 -2018-05-21 09:00:00,10464.0 -2018-05-21 10:00:00,10752.0 -2018-05-21 11:00:00,10929.0 -2018-05-21 12:00:00,11211.0 -2018-05-21 13:00:00,11333.0 -2018-05-21 14:00:00,11276.0 -2018-05-21 15:00:00,11298.0 -2018-05-21 16:00:00,11170.0 -2018-05-21 17:00:00,11076.0 -2018-05-21 18:00:00,11033.0 -2018-05-21 19:00:00,10948.0 -2018-05-21 20:00:00,10756.0 -2018-05-21 21:00:00,10649.0 -2018-05-21 22:00:00,10808.0 -2018-05-21 23:00:00,10479.0 -2018-05-22 00:00:00,9763.0 -2018-05-20 01:00:00,8650.0 -2018-05-20 02:00:00,8233.0 -2018-05-20 03:00:00,7878.0 -2018-05-20 04:00:00,7670.0 -2018-05-20 05:00:00,7547.0 -2018-05-20 06:00:00,7510.0 -2018-05-20 07:00:00,7488.0 -2018-05-20 08:00:00,7552.0 -2018-05-20 09:00:00,7775.0 -2018-05-20 10:00:00,8141.0 -2018-05-20 11:00:00,8510.0 -2018-05-20 12:00:00,8784.0 -2018-05-20 13:00:00,8931.0 -2018-05-20 14:00:00,8927.0 -2018-05-20 15:00:00,8809.0 -2018-05-20 16:00:00,8820.0 -2018-05-20 17:00:00,8763.0 -2018-05-20 18:00:00,8767.0 -2018-05-20 19:00:00,8895.0 -2018-05-20 20:00:00,8953.0 -2018-05-20 21:00:00,9082.0 -2018-05-20 22:00:00,9385.0 -2018-05-20 23:00:00,9266.0 -2018-05-21 00:00:00,8791.0 -2018-05-19 01:00:00,9018.0 -2018-05-19 02:00:00,8535.0 -2018-05-19 03:00:00,8236.0 -2018-05-19 04:00:00,8006.0 -2018-05-19 05:00:00,7873.0 -2018-05-19 06:00:00,7951.0 -2018-05-19 07:00:00,8107.0 -2018-05-19 08:00:00,8344.0 -2018-05-19 09:00:00,8764.0 -2018-05-19 10:00:00,9207.0 -2018-05-19 11:00:00,9565.0 -2018-05-19 12:00:00,9752.0 -2018-05-19 13:00:00,9874.0 -2018-05-19 14:00:00,9789.0 -2018-05-19 15:00:00,9733.0 -2018-05-19 16:00:00,9634.0 -2018-05-19 17:00:00,9607.0 -2018-05-19 18:00:00,9663.0 -2018-05-19 19:00:00,9643.0 -2018-05-19 20:00:00,9623.0 -2018-05-19 21:00:00,9618.0 -2018-05-19 22:00:00,9826.0 -2018-05-19 23:00:00,9639.0 -2018-05-20 00:00:00,9198.0 -2018-05-18 01:00:00,8968.0 -2018-05-18 02:00:00,8482.0 -2018-05-18 03:00:00,8191.0 -2018-05-18 04:00:00,8004.0 -2018-05-18 05:00:00,7944.0 -2018-05-18 06:00:00,8145.0 -2018-05-18 07:00:00,8585.0 -2018-05-18 08:00:00,9282.0 -2018-05-18 09:00:00,10030.0 -2018-05-18 10:00:00,10505.0 -2018-05-18 11:00:00,10763.0 -2018-05-18 12:00:00,10982.0 -2018-05-18 13:00:00,11149.0 -2018-05-18 14:00:00,11178.0 -2018-05-18 15:00:00,11257.0 -2018-05-18 16:00:00,11151.0 -2018-05-18 17:00:00,10914.0 -2018-05-18 18:00:00,10725.0 -2018-05-18 19:00:00,10503.0 -2018-05-18 20:00:00,10356.0 -2018-05-18 21:00:00,10373.0 -2018-05-18 22:00:00,10502.0 -2018-05-18 23:00:00,10254.0 -2018-05-19 00:00:00,9625.0 -2018-05-17 01:00:00,9478.0 -2018-05-17 02:00:00,8879.0 -2018-05-17 03:00:00,8506.0 -2018-05-17 04:00:00,8287.0 -2018-05-17 05:00:00,8162.0 -2018-05-17 06:00:00,8275.0 -2018-05-17 07:00:00,8752.0 -2018-05-17 08:00:00,9591.0 -2018-05-17 09:00:00,10490.0 -2018-05-17 10:00:00,10995.0 -2018-05-17 11:00:00,11298.0 -2018-05-17 12:00:00,11554.0 -2018-05-17 13:00:00,11756.0 -2018-05-17 14:00:00,11885.0 -2018-05-17 15:00:00,12082.0 -2018-05-17 16:00:00,12103.0 -2018-05-17 17:00:00,11997.0 -2018-05-17 18:00:00,11817.0 -2018-05-17 19:00:00,11503.0 -2018-05-17 20:00:00,10981.0 -2018-05-17 21:00:00,10673.0 -2018-05-17 22:00:00,10817.0 -2018-05-17 23:00:00,10474.0 -2018-05-18 00:00:00,9725.0 -2018-05-16 01:00:00,8917.0 -2018-05-16 02:00:00,8467.0 -2018-05-16 03:00:00,8165.0 -2018-05-16 04:00:00,7984.0 -2018-05-16 05:00:00,7934.0 -2018-05-16 06:00:00,8093.0 -2018-05-16 07:00:00,8584.0 -2018-05-16 08:00:00,9405.0 -2018-05-16 09:00:00,10183.0 -2018-05-16 10:00:00,10647.0 -2018-05-16 11:00:00,10991.0 -2018-05-16 12:00:00,11366.0 -2018-05-16 13:00:00,11601.0 -2018-05-16 14:00:00,11788.0 -2018-05-16 15:00:00,12038.0 -2018-05-16 16:00:00,12221.0 -2018-05-16 17:00:00,12346.0 -2018-05-16 18:00:00,12436.0 -2018-05-16 19:00:00,12378.0 -2018-05-16 20:00:00,12049.0 -2018-05-16 21:00:00,11734.0 -2018-05-16 22:00:00,11682.0 -2018-05-16 23:00:00,11254.0 -2018-05-17 00:00:00,10338.0 -2018-05-15 01:00:00,9551.0 -2018-05-15 02:00:00,9026.0 -2018-05-15 03:00:00,8645.0 -2018-05-15 04:00:00,8412.0 -2018-05-15 05:00:00,8341.0 -2018-05-15 06:00:00,8528.0 -2018-05-15 07:00:00,9085.0 -2018-05-15 08:00:00,10030.0 -2018-05-15 09:00:00,10766.0 -2018-05-15 10:00:00,10897.0 -2018-05-15 11:00:00,11276.0 -2018-05-15 12:00:00,11583.0 -2018-05-15 13:00:00,11692.0 -2018-05-15 14:00:00,11786.0 -2018-05-15 15:00:00,11957.0 -2018-05-15 16:00:00,11868.0 -2018-05-15 17:00:00,11547.0 -2018-05-15 18:00:00,11319.0 -2018-05-15 19:00:00,11130.0 -2018-05-15 20:00:00,10805.0 -2018-05-15 21:00:00,10567.0 -2018-05-15 22:00:00,10707.0 -2018-05-15 23:00:00,10416.0 -2018-05-16 00:00:00,9634.0 -2018-05-14 01:00:00,8201.0 -2018-05-14 02:00:00,7882.0 -2018-05-14 03:00:00,7684.0 -2018-05-14 04:00:00,7620.0 -2018-05-14 05:00:00,7632.0 -2018-05-14 06:00:00,7871.0 -2018-05-14 07:00:00,8504.0 -2018-05-14 08:00:00,9600.0 -2018-05-14 09:00:00,10455.0 -2018-05-14 10:00:00,10684.0 -2018-05-14 11:00:00,10998.0 -2018-05-14 12:00:00,11206.0 -2018-05-14 13:00:00,11304.0 -2018-05-14 14:00:00,11552.0 -2018-05-14 15:00:00,11913.0 -2018-05-14 16:00:00,12098.0 -2018-05-14 17:00:00,12229.0 -2018-05-14 18:00:00,12242.0 -2018-05-14 19:00:00,12104.0 -2018-05-14 20:00:00,11793.0 -2018-05-14 21:00:00,11464.0 -2018-05-14 22:00:00,11519.0 -2018-05-14 23:00:00,11044.0 -2018-05-15 00:00:00,10361.0 -2018-05-13 01:00:00,8478.0 -2018-05-13 02:00:00,8122.0 -2018-05-13 03:00:00,7884.0 -2018-05-13 04:00:00,7668.0 -2018-05-13 05:00:00,7580.0 -2018-05-13 06:00:00,7542.0 -2018-05-13 07:00:00,7621.0 -2018-05-13 08:00:00,7662.0 -2018-05-13 09:00:00,7917.0 -2018-05-13 10:00:00,8287.0 -2018-05-13 11:00:00,8543.0 -2018-05-13 12:00:00,8611.0 -2018-05-13 13:00:00,8637.0 -2018-05-13 14:00:00,8686.0 -2018-05-13 15:00:00,8658.0 -2018-05-13 16:00:00,8659.0 -2018-05-13 17:00:00,8667.0 -2018-05-13 18:00:00,8654.0 -2018-05-13 19:00:00,8712.0 -2018-05-13 20:00:00,8810.0 -2018-05-13 21:00:00,8879.0 -2018-05-13 22:00:00,9202.0 -2018-05-13 23:00:00,9084.0 -2018-05-14 00:00:00,8677.0 -2018-05-12 01:00:00,8872.0 -2018-05-12 02:00:00,8391.0 -2018-05-12 03:00:00,8145.0 -2018-05-12 04:00:00,7920.0 -2018-05-12 05:00:00,7861.0 -2018-05-12 06:00:00,7920.0 -2018-05-12 07:00:00,8118.0 -2018-05-12 08:00:00,8392.0 -2018-05-12 09:00:00,8880.0 -2018-05-12 10:00:00,9271.0 -2018-05-12 11:00:00,9462.0 -2018-05-12 12:00:00,9605.0 -2018-05-12 13:00:00,9506.0 -2018-05-12 14:00:00,9345.0 -2018-05-12 15:00:00,9269.0 -2018-05-12 16:00:00,9302.0 -2018-05-12 17:00:00,9164.0 -2018-05-12 18:00:00,9060.0 -2018-05-12 19:00:00,9080.0 -2018-05-12 20:00:00,9080.0 -2018-05-12 21:00:00,9181.0 -2018-05-12 22:00:00,9472.0 -2018-05-12 23:00:00,9349.0 -2018-05-13 00:00:00,8946.0 -2018-05-11 01:00:00,9118.0 -2018-05-11 02:00:00,8542.0 -2018-05-11 03:00:00,8192.0 -2018-05-11 04:00:00,7959.0 -2018-05-11 05:00:00,7895.0 -2018-05-11 06:00:00,8072.0 -2018-05-11 07:00:00,8594.0 -2018-05-11 08:00:00,9338.0 -2018-05-11 09:00:00,9999.0 -2018-05-11 10:00:00,10362.0 -2018-05-11 11:00:00,10607.0 -2018-05-11 12:00:00,10720.0 -2018-05-11 13:00:00,10775.0 -2018-05-11 14:00:00,10757.0 -2018-05-11 15:00:00,10742.0 -2018-05-11 16:00:00,10631.0 -2018-05-11 17:00:00,10420.0 -2018-05-11 18:00:00,10297.0 -2018-05-11 19:00:00,10125.0 -2018-05-11 20:00:00,10011.0 -2018-05-11 21:00:00,10039.0 -2018-05-11 22:00:00,10334.0 -2018-05-11 23:00:00,10080.0 -2018-05-12 00:00:00,9470.0 -2018-05-10 01:00:00,9804.0 -2018-05-10 02:00:00,9093.0 -2018-05-10 03:00:00,8671.0 -2018-05-10 04:00:00,8397.0 -2018-05-10 05:00:00,8267.0 -2018-05-10 06:00:00,8394.0 -2018-05-10 07:00:00,8877.0 -2018-05-10 08:00:00,9642.0 -2018-05-10 09:00:00,10509.0 -2018-05-10 10:00:00,10944.0 -2018-05-10 11:00:00,11116.0 -2018-05-10 12:00:00,11394.0 -2018-05-10 13:00:00,11654.0 -2018-05-10 14:00:00,11865.0 -2018-05-10 15:00:00,12098.0 -2018-05-10 16:00:00,12212.0 -2018-05-10 17:00:00,12025.0 -2018-05-10 18:00:00,11823.0 -2018-05-10 19:00:00,11562.0 -2018-05-10 20:00:00,11228.0 -2018-05-10 21:00:00,10978.0 -2018-05-10 22:00:00,11125.0 -2018-05-10 23:00:00,10709.0 -2018-05-11 00:00:00,9897.0 -2018-05-09 01:00:00,9696.0 -2018-05-09 02:00:00,9061.0 -2018-05-09 03:00:00,8665.0 -2018-05-09 04:00:00,8405.0 -2018-05-09 05:00:00,8296.0 -2018-05-09 06:00:00,8449.0 -2018-05-09 07:00:00,8979.0 -2018-05-09 08:00:00,9902.0 -2018-05-09 09:00:00,10725.0 -2018-05-09 10:00:00,10926.0 -2018-05-09 11:00:00,11171.0 -2018-05-09 12:00:00,11487.0 -2018-05-09 13:00:00,11747.0 -2018-05-09 14:00:00,12062.0 -2018-05-09 15:00:00,12456.0 -2018-05-09 16:00:00,12526.0 -2018-05-09 17:00:00,12697.0 -2018-05-09 18:00:00,12943.0 -2018-05-09 19:00:00,12760.0 -2018-05-09 20:00:00,12495.0 -2018-05-09 21:00:00,12201.0 -2018-05-09 22:00:00,12171.0 -2018-05-09 23:00:00,11650.0 -2018-05-10 00:00:00,10707.0 -2018-05-08 01:00:00,9176.0 -2018-05-08 02:00:00,8666.0 -2018-05-08 03:00:00,8331.0 -2018-05-08 04:00:00,8111.0 -2018-05-08 05:00:00,8048.0 -2018-05-08 06:00:00,8191.0 -2018-05-08 07:00:00,8684.0 -2018-05-08 08:00:00,9504.0 -2018-05-08 09:00:00,10370.0 -2018-05-08 10:00:00,10914.0 -2018-05-08 11:00:00,11337.0 -2018-05-08 12:00:00,11712.0 -2018-05-08 13:00:00,12002.0 -2018-05-08 14:00:00,12308.0 -2018-05-08 15:00:00,12627.0 -2018-05-08 16:00:00,12868.0 -2018-05-08 17:00:00,13018.0 -2018-05-08 18:00:00,13116.0 -2018-05-08 19:00:00,12907.0 -2018-05-08 20:00:00,12336.0 -2018-05-08 21:00:00,11973.0 -2018-05-08 22:00:00,12018.0 -2018-05-08 23:00:00,11506.0 -2018-05-09 00:00:00,10583.0 -2018-05-07 01:00:00,8245.0 -2018-05-07 02:00:00,7880.0 -2018-05-07 03:00:00,7627.0 -2018-05-07 04:00:00,7509.0 -2018-05-07 05:00:00,7532.0 -2018-05-07 06:00:00,7809.0 -2018-05-07 07:00:00,8344.0 -2018-05-07 08:00:00,9219.0 -2018-05-07 09:00:00,10054.0 -2018-05-07 10:00:00,10485.0 -2018-05-07 11:00:00,10803.0 -2018-05-07 12:00:00,11114.0 -2018-05-07 13:00:00,11286.0 -2018-05-07 14:00:00,11388.0 -2018-05-07 15:00:00,11552.0 -2018-05-07 16:00:00,11626.0 -2018-05-07 17:00:00,11665.0 -2018-05-07 18:00:00,11675.0 -2018-05-07 19:00:00,11595.0 -2018-05-07 20:00:00,11310.0 -2018-05-07 21:00:00,11061.0 -2018-05-07 22:00:00,11211.0 -2018-05-07 23:00:00,10785.0 -2018-05-08 00:00:00,9985.0 -2018-05-06 01:00:00,8777.0 -2018-05-06 02:00:00,8259.0 -2018-05-06 03:00:00,7900.0 -2018-05-06 04:00:00,7665.0 -2018-05-06 05:00:00,7472.0 -2018-05-06 06:00:00,7418.0 -2018-05-06 07:00:00,7438.0 -2018-05-06 08:00:00,7425.0 -2018-05-06 09:00:00,7683.0 -2018-05-06 10:00:00,8081.0 -2018-05-06 11:00:00,8461.0 -2018-05-06 12:00:00,8787.0 -2018-05-06 13:00:00,8992.0 -2018-05-06 14:00:00,9199.0 -2018-05-06 15:00:00,9280.0 -2018-05-06 16:00:00,9393.0 -2018-05-06 17:00:00,9484.0 -2018-05-06 18:00:00,9520.0 -2018-05-06 19:00:00,9529.0 -2018-05-06 20:00:00,9382.0 -2018-05-06 21:00:00,9289.0 -2018-05-06 22:00:00,9531.0 -2018-05-06 23:00:00,9321.0 -2018-05-07 00:00:00,8763.0 -2018-05-05 01:00:00,9356.0 -2018-05-05 02:00:00,8770.0 -2018-05-05 03:00:00,8365.0 -2018-05-05 04:00:00,8072.0 -2018-05-05 05:00:00,7901.0 -2018-05-05 06:00:00,7939.0 -2018-05-05 07:00:00,8036.0 -2018-05-05 08:00:00,8188.0 -2018-05-05 09:00:00,8700.0 -2018-05-05 10:00:00,9297.0 -2018-05-05 11:00:00,9744.0 -2018-05-05 12:00:00,10154.0 -2018-05-05 13:00:00,10374.0 -2018-05-05 14:00:00,10537.0 -2018-05-05 15:00:00,10655.0 -2018-05-05 16:00:00,10887.0 -2018-05-05 17:00:00,11085.0 -2018-05-05 18:00:00,11305.0 -2018-05-05 19:00:00,11313.0 -2018-05-05 20:00:00,11063.0 -2018-05-05 21:00:00,10679.0 -2018-05-05 22:00:00,10648.0 -2018-05-05 23:00:00,10156.0 -2018-05-06 00:00:00,9446.0 -2018-05-04 01:00:00,9408.0 -2018-05-04 02:00:00,8913.0 -2018-05-04 03:00:00,8616.0 -2018-05-04 04:00:00,8440.0 -2018-05-04 05:00:00,8361.0 -2018-05-04 06:00:00,8534.0 -2018-05-04 07:00:00,9079.0 -2018-05-04 08:00:00,9883.0 -2018-05-04 09:00:00,10615.0 -2018-05-04 10:00:00,10961.0 -2018-05-04 11:00:00,11134.0 -2018-05-04 12:00:00,11230.0 -2018-05-04 13:00:00,11338.0 -2018-05-04 14:00:00,11465.0 -2018-05-04 15:00:00,11642.0 -2018-05-04 16:00:00,11713.0 -2018-05-04 17:00:00,11725.0 -2018-05-04 18:00:00,11773.0 -2018-05-04 19:00:00,11674.0 -2018-05-04 20:00:00,11386.0 -2018-05-04 21:00:00,11071.0 -2018-05-04 22:00:00,11129.0 -2018-05-04 23:00:00,10798.0 -2018-05-05 00:00:00,10065.0 -2018-05-03 01:00:00,10307.0 -2018-05-03 02:00:00,9648.0 -2018-05-03 03:00:00,9160.0 -2018-05-03 04:00:00,8862.0 -2018-05-03 05:00:00,8776.0 -2018-05-03 06:00:00,8917.0 -2018-05-03 07:00:00,9406.0 -2018-05-03 08:00:00,10244.0 -2018-05-03 09:00:00,10986.0 -2018-05-03 10:00:00,11306.0 -2018-05-03 11:00:00,11612.0 -2018-05-03 12:00:00,11883.0 -2018-05-03 13:00:00,11881.0 -2018-05-03 14:00:00,11937.0 -2018-05-03 15:00:00,12025.0 -2018-05-03 16:00:00,11846.0 -2018-05-03 17:00:00,11547.0 -2018-05-03 18:00:00,11441.0 -2018-05-03 19:00:00,11298.0 -2018-05-03 20:00:00,11080.0 -2018-05-03 21:00:00,11055.0 -2018-05-03 22:00:00,11177.0 -2018-05-03 23:00:00,10788.0 -2018-05-04 00:00:00,10083.0 -2018-05-02 01:00:00,9874.0 -2018-05-02 02:00:00,9313.0 -2018-05-02 03:00:00,8893.0 -2018-05-02 04:00:00,8591.0 -2018-05-02 05:00:00,8459.0 -2018-05-02 06:00:00,8577.0 -2018-05-02 07:00:00,9164.0 -2018-05-02 08:00:00,9990.0 -2018-05-02 09:00:00,10840.0 -2018-05-02 10:00:00,11373.0 -2018-05-02 11:00:00,11752.0 -2018-05-02 12:00:00,12070.0 -2018-05-02 13:00:00,12418.0 -2018-05-02 14:00:00,12731.0 -2018-05-02 15:00:00,13164.0 -2018-05-02 16:00:00,13417.0 -2018-05-02 17:00:00,13545.0 -2018-05-02 18:00:00,13370.0 -2018-05-02 19:00:00,13106.0 -2018-05-02 20:00:00,12898.0 -2018-05-02 21:00:00,12914.0 -2018-05-02 22:00:00,12670.0 -2018-05-02 23:00:00,12055.0 -2018-05-03 00:00:00,11160.0 -2018-05-01 01:00:00,8937.0 -2018-05-01 02:00:00,8411.0 -2018-05-01 03:00:00,8110.0 -2018-05-01 04:00:00,7917.0 -2018-05-01 05:00:00,7844.0 -2018-05-01 06:00:00,8020.0 -2018-05-01 07:00:00,8519.0 -2018-05-01 08:00:00,9293.0 -2018-05-01 09:00:00,10048.0 -2018-05-01 10:00:00,10537.0 -2018-05-01 11:00:00,10896.0 -2018-05-01 12:00:00,11235.0 -2018-05-01 13:00:00,11482.0 -2018-05-01 14:00:00,11667.0 -2018-05-01 15:00:00,11909.0 -2018-05-01 16:00:00,11998.0 -2018-05-01 17:00:00,12011.0 -2018-05-01 18:00:00,11971.0 -2018-05-01 19:00:00,11851.0 -2018-05-01 20:00:00,11609.0 -2018-05-01 21:00:00,11592.0 -2018-05-01 22:00:00,11870.0 -2018-05-01 23:00:00,11448.0 -2018-05-02 00:00:00,10647.0 -2018-04-30 01:00:00,8381.0 -2018-04-30 02:00:00,8098.0 -2018-04-30 03:00:00,7916.0 -2018-04-30 04:00:00,7813.0 -2018-04-30 05:00:00,7975.0 -2018-04-30 06:00:00,8300.0 -2018-04-30 07:00:00,9010.0 -2018-04-30 08:00:00,9799.0 -2018-04-30 09:00:00,10368.0 -2018-04-30 10:00:00,10514.0 -2018-04-30 11:00:00,10676.0 -2018-04-30 12:00:00,10815.0 -2018-04-30 13:00:00,10858.0 -2018-04-30 14:00:00,10905.0 -2018-04-30 15:00:00,10977.0 -2018-04-30 16:00:00,10963.0 -2018-04-30 17:00:00,10867.0 -2018-04-30 18:00:00,10788.0 -2018-04-30 19:00:00,10707.0 -2018-04-30 20:00:00,10491.0 -2018-04-30 21:00:00,10461.0 -2018-04-30 22:00:00,10755.0 -2018-04-30 23:00:00,10358.0 -2018-05-01 00:00:00,9658.0 -2018-04-29 01:00:00,8752.0 -2018-04-29 02:00:00,8403.0 -2018-04-29 03:00:00,8243.0 -2018-04-29 04:00:00,8098.0 -2018-04-29 05:00:00,8087.0 -2018-04-29 06:00:00,8113.0 -2018-04-29 07:00:00,8254.0 -2018-04-29 08:00:00,8231.0 -2018-04-29 09:00:00,8415.0 -2018-04-29 10:00:00,8560.0 -2018-04-29 11:00:00,8705.0 -2018-04-29 12:00:00,8747.0 -2018-04-29 13:00:00,8733.0 -2018-04-29 14:00:00,8713.0 -2018-04-29 15:00:00,8645.0 -2018-04-29 16:00:00,8605.0 -2018-04-29 17:00:00,8547.0 -2018-04-29 18:00:00,8581.0 -2018-04-29 19:00:00,8664.0 -2018-04-29 20:00:00,8696.0 -2018-04-29 21:00:00,8928.0 -2018-04-29 22:00:00,9391.0 -2018-04-29 23:00:00,9228.0 -2018-04-30 00:00:00,8808.0 -2018-04-28 01:00:00,9127.0 -2018-04-28 02:00:00,8698.0 -2018-04-28 03:00:00,8478.0 -2018-04-28 04:00:00,8289.0 -2018-04-28 05:00:00,8281.0 -2018-04-28 06:00:00,8376.0 -2018-04-28 07:00:00,8624.0 -2018-04-28 08:00:00,8806.0 -2018-04-28 09:00:00,9061.0 -2018-04-28 10:00:00,9317.0 -2018-04-28 11:00:00,9466.0 -2018-04-28 12:00:00,9544.0 -2018-04-28 13:00:00,9455.0 -2018-04-28 14:00:00,9331.0 -2018-04-28 15:00:00,9164.0 -2018-04-28 16:00:00,9039.0 -2018-04-28 17:00:00,8901.0 -2018-04-28 18:00:00,8896.0 -2018-04-28 19:00:00,8858.0 -2018-04-28 20:00:00,8908.0 -2018-04-28 21:00:00,9110.0 -2018-04-28 22:00:00,9618.0 -2018-04-28 23:00:00,9491.0 -2018-04-29 00:00:00,9139.0 -2018-04-27 01:00:00,8832.0 -2018-04-27 02:00:00,8399.0 -2018-04-27 03:00:00,8146.0 -2018-04-27 04:00:00,8021.0 -2018-04-27 05:00:00,8015.0 -2018-04-27 06:00:00,8270.0 -2018-04-27 07:00:00,8900.0 -2018-04-27 08:00:00,9617.0 -2018-04-27 09:00:00,10161.0 -2018-04-27 10:00:00,10395.0 -2018-04-27 11:00:00,10491.0 -2018-04-27 12:00:00,10590.0 -2018-04-27 13:00:00,10582.0 -2018-04-27 14:00:00,10581.0 -2018-04-27 15:00:00,10605.0 -2018-04-27 16:00:00,10463.0 -2018-04-27 17:00:00,10312.0 -2018-04-27 18:00:00,10209.0 -2018-04-27 19:00:00,10160.0 -2018-04-27 20:00:00,10232.0 -2018-04-27 21:00:00,10362.0 -2018-04-27 22:00:00,10518.0 -2018-04-27 23:00:00,10251.0 -2018-04-28 00:00:00,9685.0 -2018-04-26 01:00:00,9056.0 -2018-04-26 02:00:00,8720.0 -2018-04-26 03:00:00,8516.0 -2018-04-26 04:00:00,8389.0 -2018-04-26 05:00:00,8435.0 -2018-04-26 06:00:00,8692.0 -2018-04-26 07:00:00,9363.0 -2018-04-26 08:00:00,10095.0 -2018-04-26 09:00:00,10596.0 -2018-04-26 10:00:00,10727.0 -2018-04-26 11:00:00,10693.0 -2018-04-26 12:00:00,10767.0 -2018-04-26 13:00:00,10719.0 -2018-04-26 14:00:00,10722.0 -2018-04-26 15:00:00,10739.0 -2018-04-26 16:00:00,10659.0 -2018-04-26 17:00:00,10542.0 -2018-04-26 18:00:00,10409.0 -2018-04-26 19:00:00,10263.0 -2018-04-26 20:00:00,10077.0 -2018-04-26 21:00:00,10159.0 -2018-04-26 22:00:00,10454.0 -2018-04-26 23:00:00,10082.0 -2018-04-27 00:00:00,9430.0 -2018-04-25 01:00:00,8876.0 -2018-04-25 02:00:00,8474.0 -2018-04-25 03:00:00,8251.0 -2018-04-25 04:00:00,8156.0 -2018-04-25 05:00:00,8156.0 -2018-04-25 06:00:00,8432.0 -2018-04-25 07:00:00,9091.0 -2018-04-25 08:00:00,9872.0 -2018-04-25 09:00:00,10456.0 -2018-04-25 10:00:00,10629.0 -2018-04-25 11:00:00,10708.0 -2018-04-25 12:00:00,10764.0 -2018-04-25 13:00:00,10713.0 -2018-04-25 14:00:00,10643.0 -2018-04-25 15:00:00,10666.0 -2018-04-25 16:00:00,10556.0 -2018-04-25 17:00:00,10437.0 -2018-04-25 18:00:00,10286.0 -2018-04-25 19:00:00,10184.0 -2018-04-25 20:00:00,10037.0 -2018-04-25 21:00:00,10223.0 -2018-04-25 22:00:00,10592.0 -2018-04-25 23:00:00,10251.0 -2018-04-26 00:00:00,9662.0 -2018-04-24 01:00:00,8927.0 -2018-04-24 02:00:00,8519.0 -2018-04-24 03:00:00,8288.0 -2018-04-24 04:00:00,8131.0 -2018-04-24 05:00:00,8104.0 -2018-04-24 06:00:00,8324.0 -2018-04-24 07:00:00,9007.0 -2018-04-24 08:00:00,9785.0 -2018-04-24 09:00:00,10395.0 -2018-04-24 10:00:00,10565.0 -2018-04-24 11:00:00,10621.0 -2018-04-24 12:00:00,10729.0 -2018-04-24 13:00:00,10770.0 -2018-04-24 14:00:00,10811.0 -2018-04-24 15:00:00,10854.0 -2018-04-24 16:00:00,10776.0 -2018-04-24 17:00:00,10689.0 -2018-04-24 18:00:00,10558.0 -2018-04-24 19:00:00,10446.0 -2018-04-24 20:00:00,10207.0 -2018-04-24 21:00:00,10307.0 -2018-04-24 22:00:00,10603.0 -2018-04-24 23:00:00,10176.0 -2018-04-25 00:00:00,9499.0 -2018-04-23 01:00:00,8403.0 -2018-04-23 02:00:00,8158.0 -2018-04-23 03:00:00,8046.0 -2018-04-23 04:00:00,7993.0 -2018-04-23 05:00:00,8108.0 -2018-04-23 06:00:00,8397.0 -2018-04-23 07:00:00,9159.0 -2018-04-23 08:00:00,9916.0 -2018-04-23 09:00:00,10598.0 -2018-04-23 10:00:00,10716.0 -2018-04-23 11:00:00,10763.0 -2018-04-23 12:00:00,10755.0 -2018-04-23 13:00:00,10733.0 -2018-04-23 14:00:00,10702.0 -2018-04-23 15:00:00,10714.0 -2018-04-23 16:00:00,10615.0 -2018-04-23 17:00:00,10506.0 -2018-04-23 18:00:00,10377.0 -2018-04-23 19:00:00,10287.0 -2018-04-23 20:00:00,10198.0 -2018-04-23 21:00:00,10402.0 -2018-04-23 22:00:00,10601.0 -2018-04-23 23:00:00,10194.0 -2018-04-24 00:00:00,9519.0 -2018-04-22 01:00:00,8801.0 -2018-04-22 02:00:00,8429.0 -2018-04-22 03:00:00,8188.0 -2018-04-22 04:00:00,8024.0 -2018-04-22 05:00:00,7973.0 -2018-04-22 06:00:00,7996.0 -2018-04-22 07:00:00,8148.0 -2018-04-22 08:00:00,8204.0 -2018-04-22 09:00:00,8429.0 -2018-04-22 10:00:00,8625.0 -2018-04-22 11:00:00,8727.0 -2018-04-22 12:00:00,8808.0 -2018-04-22 13:00:00,8817.0 -2018-04-22 14:00:00,8773.0 -2018-04-22 15:00:00,8754.0 -2018-04-22 16:00:00,8661.0 -2018-04-22 17:00:00,8627.0 -2018-04-22 18:00:00,8690.0 -2018-04-22 19:00:00,8747.0 -2018-04-22 20:00:00,8792.0 -2018-04-22 21:00:00,9049.0 -2018-04-22 22:00:00,9499.0 -2018-04-22 23:00:00,9305.0 -2018-04-23 00:00:00,8891.0 -2018-04-21 01:00:00,9142.0 -2018-04-21 02:00:00,8801.0 -2018-04-21 03:00:00,8545.0 -2018-04-21 04:00:00,8426.0 -2018-04-21 05:00:00,8357.0 -2018-04-21 06:00:00,8454.0 -2018-04-21 07:00:00,8716.0 -2018-04-21 08:00:00,9022.0 -2018-04-21 09:00:00,9359.0 -2018-04-21 10:00:00,9722.0 -2018-04-21 11:00:00,9866.0 -2018-04-21 12:00:00,9968.0 -2018-04-21 13:00:00,9857.0 -2018-04-21 14:00:00,9733.0 -2018-04-21 15:00:00,9509.0 -2018-04-21 16:00:00,9365.0 -2018-04-21 17:00:00,9230.0 -2018-04-21 18:00:00,9191.0 -2018-04-21 19:00:00,9152.0 -2018-04-21 20:00:00,9268.0 -2018-04-21 21:00:00,9600.0 -2018-04-21 22:00:00,9820.0 -2018-04-21 23:00:00,9649.0 -2018-04-22 00:00:00,9199.0 -2018-04-20 01:00:00,9584.0 -2018-04-20 02:00:00,9240.0 -2018-04-20 03:00:00,9053.0 -2018-04-20 04:00:00,8930.0 -2018-04-20 05:00:00,8959.0 -2018-04-20 06:00:00,9236.0 -2018-04-20 07:00:00,9932.0 -2018-04-20 08:00:00,10661.0 -2018-04-20 09:00:00,11084.0 -2018-04-20 10:00:00,11112.0 -2018-04-20 11:00:00,11016.0 -2018-04-20 12:00:00,10964.0 -2018-04-20 13:00:00,10851.0 -2018-04-20 14:00:00,10775.0 -2018-04-20 15:00:00,10744.0 -2018-04-20 16:00:00,10618.0 -2018-04-20 17:00:00,10420.0 -2018-04-20 18:00:00,10275.0 -2018-04-20 19:00:00,10109.0 -2018-04-20 20:00:00,9984.0 -2018-04-20 21:00:00,10159.0 -2018-04-20 22:00:00,10474.0 -2018-04-20 23:00:00,10199.0 -2018-04-21 00:00:00,9695.0 -2018-04-19 01:00:00,10210.0 -2018-04-19 02:00:00,9845.0 -2018-04-19 03:00:00,9597.0 -2018-04-19 04:00:00,9447.0 -2018-04-19 05:00:00,9376.0 -2018-04-19 06:00:00,9613.0 -2018-04-19 07:00:00,10296.0 -2018-04-19 08:00:00,11058.0 -2018-04-19 09:00:00,11436.0 -2018-04-19 10:00:00,11531.0 -2018-04-19 11:00:00,11492.0 -2018-04-19 12:00:00,11447.0 -2018-04-19 13:00:00,11335.0 -2018-04-19 14:00:00,11194.0 -2018-04-19 15:00:00,11107.0 -2018-04-19 16:00:00,10926.0 -2018-04-19 17:00:00,10726.0 -2018-04-19 18:00:00,10548.0 -2018-04-19 19:00:00,10450.0 -2018-04-19 20:00:00,10372.0 -2018-04-19 21:00:00,10609.0 -2018-04-19 22:00:00,10949.0 -2018-04-19 23:00:00,10686.0 -2018-04-20 00:00:00,10111.0 -2018-04-18 01:00:00,9935.0 -2018-04-18 02:00:00,9624.0 -2018-04-18 03:00:00,9451.0 -2018-04-18 04:00:00,9315.0 -2018-04-18 05:00:00,9301.0 -2018-04-18 06:00:00,9596.0 -2018-04-18 07:00:00,10323.0 -2018-04-18 08:00:00,11130.0 -2018-04-18 09:00:00,11684.0 -2018-04-18 10:00:00,11896.0 -2018-04-18 11:00:00,12044.0 -2018-04-18 12:00:00,12111.0 -2018-04-18 13:00:00,12160.0 -2018-04-18 14:00:00,12137.0 -2018-04-18 15:00:00,12164.0 -2018-04-18 16:00:00,12042.0 -2018-04-18 17:00:00,11952.0 -2018-04-18 18:00:00,11909.0 -2018-04-18 19:00:00,11942.0 -2018-04-18 20:00:00,11870.0 -2018-04-18 21:00:00,12009.0 -2018-04-18 22:00:00,11923.0 -2018-04-18 23:00:00,11455.0 -2018-04-19 00:00:00,10805.0 -2018-04-17 01:00:00,10266.0 -2018-04-17 02:00:00,9843.0 -2018-04-17 03:00:00,9677.0 -2018-04-17 04:00:00,9537.0 -2018-04-17 05:00:00,9582.0 -2018-04-17 06:00:00,9857.0 -2018-04-17 07:00:00,10605.0 -2018-04-17 08:00:00,11386.0 -2018-04-17 09:00:00,11792.0 -2018-04-17 10:00:00,11889.0 -2018-04-17 11:00:00,11859.0 -2018-04-17 12:00:00,11846.0 -2018-04-17 13:00:00,11705.0 -2018-04-17 14:00:00,11556.0 -2018-04-17 15:00:00,11437.0 -2018-04-17 16:00:00,11262.0 -2018-04-17 17:00:00,11035.0 -2018-04-17 18:00:00,10931.0 -2018-04-17 19:00:00,10848.0 -2018-04-17 20:00:00,10758.0 -2018-04-17 21:00:00,11112.0 -2018-04-17 22:00:00,11462.0 -2018-04-17 23:00:00,11163.0 -2018-04-18 00:00:00,10576.0 -2018-04-16 01:00:00,9691.0 -2018-04-16 02:00:00,9436.0 -2018-04-16 03:00:00,9293.0 -2018-04-16 04:00:00,9241.0 -2018-04-16 05:00:00,9360.0 -2018-04-16 06:00:00,9709.0 -2018-04-16 07:00:00,10482.0 -2018-04-16 08:00:00,11432.0 -2018-04-16 09:00:00,12079.0 -2018-04-16 10:00:00,12327.0 -2018-04-16 11:00:00,12355.0 -2018-04-16 12:00:00,12378.0 -2018-04-16 13:00:00,12305.0 -2018-04-16 14:00:00,12277.0 -2018-04-16 15:00:00,12272.0 -2018-04-16 16:00:00,12174.0 -2018-04-16 17:00:00,12073.0 -2018-04-16 18:00:00,12028.0 -2018-04-16 19:00:00,12009.0 -2018-04-16 20:00:00,11873.0 -2018-04-16 21:00:00,12005.0 -2018-04-16 22:00:00,12019.0 -2018-04-16 23:00:00,11552.0 -2018-04-17 00:00:00,10874.0 -2018-04-15 01:00:00,9604.0 -2018-04-15 02:00:00,9193.0 -2018-04-15 03:00:00,9016.0 -2018-04-15 04:00:00,8823.0 -2018-04-15 05:00:00,8796.0 -2018-04-15 06:00:00,8819.0 -2018-04-15 07:00:00,9013.0 -2018-04-15 08:00:00,9223.0 -2018-04-15 09:00:00,9414.0 -2018-04-15 10:00:00,9863.0 -2018-04-15 11:00:00,10136.0 -2018-04-15 12:00:00,10326.0 -2018-04-15 13:00:00,10319.0 -2018-04-15 14:00:00,10181.0 -2018-04-15 15:00:00,10121.0 -2018-04-15 16:00:00,10044.0 -2018-04-15 17:00:00,10076.0 -2018-04-15 18:00:00,10134.0 -2018-04-15 19:00:00,10292.0 -2018-04-15 20:00:00,10391.0 -2018-04-15 21:00:00,10716.0 -2018-04-15 22:00:00,10800.0 -2018-04-15 23:00:00,10544.0 -2018-04-16 00:00:00,10117.0 -2018-04-14 01:00:00,9498.0 -2018-04-14 02:00:00,9161.0 -2018-04-14 03:00:00,8948.0 -2018-04-14 04:00:00,8839.0 -2018-04-14 05:00:00,8712.0 -2018-04-14 06:00:00,8838.0 -2018-04-14 07:00:00,9098.0 -2018-04-14 08:00:00,9537.0 -2018-04-14 09:00:00,9880.0 -2018-04-14 10:00:00,10282.0 -2018-04-14 11:00:00,10612.0 -2018-04-14 12:00:00,10709.0 -2018-04-14 13:00:00,10673.0 -2018-04-14 14:00:00,10529.0 -2018-04-14 15:00:00,10377.0 -2018-04-14 16:00:00,10352.0 -2018-04-14 17:00:00,10330.0 -2018-04-14 18:00:00,10450.0 -2018-04-14 19:00:00,10494.0 -2018-04-14 20:00:00,10595.0 -2018-04-14 21:00:00,10772.0 -2018-04-14 22:00:00,10688.0 -2018-04-14 23:00:00,10473.0 -2018-04-15 00:00:00,10034.0 -2018-04-13 01:00:00,9067.0 -2018-04-13 02:00:00,8703.0 -2018-04-13 03:00:00,8472.0 -2018-04-13 04:00:00,8363.0 -2018-04-13 05:00:00,8364.0 -2018-04-13 06:00:00,8575.0 -2018-04-13 07:00:00,9260.0 -2018-04-13 08:00:00,10101.0 -2018-04-13 09:00:00,10576.0 -2018-04-13 10:00:00,10811.0 -2018-04-13 11:00:00,10913.0 -2018-04-13 12:00:00,11081.0 -2018-04-13 13:00:00,11136.0 -2018-04-13 14:00:00,11149.0 -2018-04-13 15:00:00,11207.0 -2018-04-13 16:00:00,11169.0 -2018-04-13 17:00:00,10954.0 -2018-04-13 18:00:00,10929.0 -2018-04-13 19:00:00,10792.0 -2018-04-13 20:00:00,10780.0 -2018-04-13 21:00:00,11004.0 -2018-04-13 22:00:00,10961.0 -2018-04-13 23:00:00,10632.0 -2018-04-14 00:00:00,10033.0 -2018-04-12 01:00:00,9111.0 -2018-04-12 02:00:00,8749.0 -2018-04-12 03:00:00,8502.0 -2018-04-12 04:00:00,8366.0 -2018-04-12 05:00:00,8333.0 -2018-04-12 06:00:00,8536.0 -2018-04-12 07:00:00,9228.0 -2018-04-12 08:00:00,10053.0 -2018-04-12 09:00:00,10523.0 -2018-04-12 10:00:00,10808.0 -2018-04-12 11:00:00,10781.0 -2018-04-12 12:00:00,10867.0 -2018-04-12 13:00:00,10882.0 -2018-04-12 14:00:00,10877.0 -2018-04-12 15:00:00,10924.0 -2018-04-12 16:00:00,10798.0 -2018-04-12 17:00:00,10579.0 -2018-04-12 18:00:00,10404.0 -2018-04-12 19:00:00,10215.0 -2018-04-12 20:00:00,10065.0 -2018-04-12 21:00:00,10464.0 -2018-04-12 22:00:00,10604.0 -2018-04-12 23:00:00,10223.0 -2018-04-13 00:00:00,9645.0 -2018-04-11 01:00:00,9619.0 -2018-04-11 02:00:00,9245.0 -2018-04-11 03:00:00,9004.0 -2018-04-11 04:00:00,8853.0 -2018-04-11 05:00:00,8890.0 -2018-04-11 06:00:00,9129.0 -2018-04-11 07:00:00,9916.0 -2018-04-11 08:00:00,10760.0 -2018-04-11 09:00:00,11171.0 -2018-04-11 10:00:00,11210.0 -2018-04-11 11:00:00,11139.0 -2018-04-11 12:00:00,11084.0 -2018-04-11 13:00:00,10997.0 -2018-04-11 14:00:00,10883.0 -2018-04-11 15:00:00,10875.0 -2018-04-11 16:00:00,10769.0 -2018-04-11 17:00:00,10636.0 -2018-04-11 18:00:00,10525.0 -2018-04-11 19:00:00,10466.0 -2018-04-11 20:00:00,10446.0 -2018-04-11 21:00:00,10784.0 -2018-04-11 22:00:00,10779.0 -2018-04-11 23:00:00,10361.0 -2018-04-12 00:00:00,9774.0 -2018-04-10 01:00:00,9974.0 -2018-04-10 02:00:00,9589.0 -2018-04-10 03:00:00,9430.0 -2018-04-10 04:00:00,9337.0 -2018-04-10 05:00:00,9348.0 -2018-04-10 06:00:00,9650.0 -2018-04-10 07:00:00,10388.0 -2018-04-10 08:00:00,11255.0 -2018-04-10 09:00:00,11657.0 -2018-04-10 10:00:00,11650.0 -2018-04-10 11:00:00,11552.0 -2018-04-10 12:00:00,11492.0 -2018-04-10 13:00:00,11360.0 -2018-04-10 14:00:00,11228.0 -2018-04-10 15:00:00,11221.0 -2018-04-10 16:00:00,11080.0 -2018-04-10 17:00:00,10977.0 -2018-04-10 18:00:00,10906.0 -2018-04-10 19:00:00,10849.0 -2018-04-10 20:00:00,10831.0 -2018-04-10 21:00:00,11166.0 -2018-04-10 22:00:00,11257.0 -2018-04-10 23:00:00,10820.0 -2018-04-11 00:00:00,10204.0 -2018-04-09 01:00:00,9427.0 -2018-04-09 02:00:00,9184.0 -2018-04-09 03:00:00,9047.0 -2018-04-09 04:00:00,9006.0 -2018-04-09 05:00:00,9067.0 -2018-04-09 06:00:00,9352.0 -2018-04-09 07:00:00,10139.0 -2018-04-09 08:00:00,11058.0 -2018-04-09 09:00:00,11612.0 -2018-04-09 10:00:00,11824.0 -2018-04-09 11:00:00,11858.0 -2018-04-09 12:00:00,11855.0 -2018-04-09 13:00:00,11822.0 -2018-04-09 14:00:00,11802.0 -2018-04-09 15:00:00,11766.0 -2018-04-09 16:00:00,11641.0 -2018-04-09 17:00:00,11579.0 -2018-04-09 18:00:00,11505.0 -2018-04-09 19:00:00,11465.0 -2018-04-09 20:00:00,11399.0 -2018-04-09 21:00:00,11634.0 -2018-04-09 22:00:00,11637.0 -2018-04-09 23:00:00,11192.0 -2018-04-10 00:00:00,10510.0 -2018-04-08 01:00:00,9691.0 -2018-04-08 02:00:00,9396.0 -2018-04-08 03:00:00,9210.0 -2018-04-08 04:00:00,9116.0 -2018-04-08 05:00:00,9073.0 -2018-04-08 06:00:00,9148.0 -2018-04-08 07:00:00,9324.0 -2018-04-08 08:00:00,9471.0 -2018-04-08 09:00:00,9461.0 -2018-04-08 10:00:00,9569.0 -2018-04-08 11:00:00,9657.0 -2018-04-08 12:00:00,9599.0 -2018-04-08 13:00:00,9612.0 -2018-04-08 14:00:00,9529.0 -2018-04-08 15:00:00,9483.0 -2018-04-08 16:00:00,9399.0 -2018-04-08 17:00:00,9327.0 -2018-04-08 18:00:00,9482.0 -2018-04-08 19:00:00,9649.0 -2018-04-08 20:00:00,9934.0 -2018-04-08 21:00:00,10397.0 -2018-04-08 22:00:00,10516.0 -2018-04-08 23:00:00,10212.0 -2018-04-09 00:00:00,9774.0 -2018-04-07 01:00:00,10379.0 -2018-04-07 02:00:00,9992.0 -2018-04-07 03:00:00,9774.0 -2018-04-07 04:00:00,9625.0 -2018-04-07 05:00:00,9602.0 -2018-04-07 06:00:00,9723.0 -2018-04-07 07:00:00,10042.0 -2018-04-07 08:00:00,10355.0 -2018-04-07 09:00:00,10570.0 -2018-04-07 10:00:00,10742.0 -2018-04-07 11:00:00,10811.0 -2018-04-07 12:00:00,10786.0 -2018-04-07 13:00:00,10675.0 -2018-04-07 14:00:00,10502.0 -2018-04-07 15:00:00,10205.0 -2018-04-07 16:00:00,10019.0 -2018-04-07 17:00:00,9812.0 -2018-04-07 18:00:00,9730.0 -2018-04-07 19:00:00,9706.0 -2018-04-07 20:00:00,9787.0 -2018-04-07 21:00:00,10293.0 -2018-04-07 22:00:00,10609.0 -2018-04-07 23:00:00,10416.0 -2018-04-08 00:00:00,10036.0 -2018-04-06 01:00:00,9943.0 -2018-04-06 02:00:00,9565.0 -2018-04-06 03:00:00,9334.0 -2018-04-06 04:00:00,9180.0 -2018-04-06 05:00:00,9177.0 -2018-04-06 06:00:00,9411.0 -2018-04-06 07:00:00,10073.0 -2018-04-06 08:00:00,10993.0 -2018-04-06 09:00:00,11501.0 -2018-04-06 10:00:00,11787.0 -2018-04-06 11:00:00,11913.0 -2018-04-06 12:00:00,11887.0 -2018-04-06 13:00:00,11785.0 -2018-04-06 14:00:00,11855.0 -2018-04-06 15:00:00,11899.0 -2018-04-06 16:00:00,11767.0 -2018-04-06 17:00:00,11512.0 -2018-04-06 18:00:00,11371.0 -2018-04-06 19:00:00,11315.0 -2018-04-06 20:00:00,11322.0 -2018-04-06 21:00:00,11646.0 -2018-04-06 22:00:00,11719.0 -2018-04-06 23:00:00,11446.0 -2018-04-07 00:00:00,10889.0 -2018-04-05 01:00:00,10243.0 -2018-04-05 02:00:00,9865.0 -2018-04-05 03:00:00,9660.0 -2018-04-05 04:00:00,9541.0 -2018-04-05 05:00:00,9562.0 -2018-04-05 06:00:00,9777.0 -2018-04-05 07:00:00,10505.0 -2018-04-05 08:00:00,11493.0 -2018-04-05 09:00:00,11831.0 -2018-04-05 10:00:00,11856.0 -2018-04-05 11:00:00,11725.0 -2018-04-05 12:00:00,11649.0 -2018-04-05 13:00:00,11540.0 -2018-04-05 14:00:00,11376.0 -2018-04-05 15:00:00,11353.0 -2018-04-05 16:00:00,11295.0 -2018-04-05 17:00:00,11272.0 -2018-04-05 18:00:00,11313.0 -2018-04-05 19:00:00,11408.0 -2018-04-05 20:00:00,11381.0 -2018-04-05 21:00:00,11680.0 -2018-04-05 22:00:00,11620.0 -2018-04-05 23:00:00,11223.0 -2018-04-06 00:00:00,10556.0 -2018-04-04 01:00:00,10014.0 -2018-04-04 02:00:00,9669.0 -2018-04-04 03:00:00,9463.0 -2018-04-04 04:00:00,9415.0 -2018-04-04 05:00:00,9471.0 -2018-04-04 06:00:00,9811.0 -2018-04-04 07:00:00,10528.0 -2018-04-04 08:00:00,11462.0 -2018-04-04 09:00:00,11957.0 -2018-04-04 10:00:00,12104.0 -2018-04-04 11:00:00,12013.0 -2018-04-04 12:00:00,12020.0 -2018-04-04 13:00:00,11975.0 -2018-04-04 14:00:00,11900.0 -2018-04-04 15:00:00,11922.0 -2018-04-04 16:00:00,11829.0 -2018-04-04 17:00:00,11658.0 -2018-04-04 18:00:00,11568.0 -2018-04-04 19:00:00,11499.0 -2018-04-04 20:00:00,11428.0 -2018-04-04 21:00:00,11796.0 -2018-04-04 22:00:00,11919.0 -2018-04-04 23:00:00,11501.0 -2018-04-05 00:00:00,10826.0 -2018-04-03 01:00:00,9577.0 -2018-04-03 02:00:00,9171.0 -2018-04-03 03:00:00,8956.0 -2018-04-03 04:00:00,8841.0 -2018-04-03 05:00:00,8800.0 -2018-04-03 06:00:00,9042.0 -2018-04-03 07:00:00,9745.0 -2018-04-03 08:00:00,10768.0 -2018-04-03 09:00:00,11354.0 -2018-04-03 10:00:00,11628.0 -2018-04-03 11:00:00,11712.0 -2018-04-03 12:00:00,11832.0 -2018-04-03 13:00:00,11859.0 -2018-04-03 14:00:00,11782.0 -2018-04-03 15:00:00,11806.0 -2018-04-03 16:00:00,11763.0 -2018-04-03 17:00:00,11667.0 -2018-04-03 18:00:00,11642.0 -2018-04-03 19:00:00,11656.0 -2018-04-03 20:00:00,11631.0 -2018-04-03 21:00:00,11820.0 -2018-04-03 22:00:00,11676.0 -2018-04-03 23:00:00,11185.0 -2018-04-04 00:00:00,10577.0 -2018-04-02 01:00:00,9259.0 -2018-04-02 02:00:00,9073.0 -2018-04-02 03:00:00,8960.0 -2018-04-02 04:00:00,8889.0 -2018-04-02 05:00:00,9056.0 -2018-04-02 06:00:00,9383.0 -2018-04-02 07:00:00,10172.0 -2018-04-02 08:00:00,11112.0 -2018-04-02 09:00:00,11557.0 -2018-04-02 10:00:00,11656.0 -2018-04-02 11:00:00,11504.0 -2018-04-02 12:00:00,11478.0 -2018-04-02 13:00:00,11431.0 -2018-04-02 14:00:00,11345.0 -2018-04-02 15:00:00,11293.0 -2018-04-02 16:00:00,11211.0 -2018-04-02 17:00:00,11031.0 -2018-04-02 18:00:00,10900.0 -2018-04-02 19:00:00,10828.0 -2018-04-02 20:00:00,10885.0 -2018-04-02 21:00:00,11345.0 -2018-04-02 22:00:00,11300.0 -2018-04-02 23:00:00,10903.0 -2018-04-03 00:00:00,10225.0 -2018-04-01 01:00:00,9127.0 -2018-04-01 02:00:00,8829.0 -2018-04-01 03:00:00,8632.0 -2018-04-01 04:00:00,8571.0 -2018-04-01 05:00:00,8543.0 -2018-04-01 06:00:00,8691.0 -2018-04-01 07:00:00,8881.0 -2018-04-01 08:00:00,9055.0 -2018-04-01 09:00:00,9050.0 -2018-04-01 10:00:00,9201.0 -2018-04-01 11:00:00,9348.0 -2018-04-01 12:00:00,9390.0 -2018-04-01 13:00:00,9338.0 -2018-04-01 14:00:00,9194.0 -2018-04-01 15:00:00,9111.0 -2018-04-01 16:00:00,9049.0 -2018-04-01 17:00:00,9027.0 -2018-04-01 18:00:00,9056.0 -2018-04-01 19:00:00,9192.0 -2018-04-01 20:00:00,9454.0 -2018-04-01 21:00:00,9995.0 -2018-04-01 22:00:00,10172.0 -2018-04-01 23:00:00,9983.0 -2018-04-02 00:00:00,9660.0 -2018-03-31 01:00:00,9096.0 -2018-03-31 02:00:00,8768.0 -2018-03-31 03:00:00,8551.0 -2018-03-31 04:00:00,8467.0 -2018-03-31 05:00:00,8382.0 -2018-03-31 06:00:00,8468.0 -2018-03-31 07:00:00,8666.0 -2018-03-31 08:00:00,9106.0 -2018-03-31 09:00:00,9383.0 -2018-03-31 10:00:00,9918.0 -2018-03-31 11:00:00,10099.0 -2018-03-31 12:00:00,10193.0 -2018-03-31 13:00:00,10095.0 -2018-03-31 14:00:00,9817.0 -2018-03-31 15:00:00,9583.0 -2018-03-31 16:00:00,9394.0 -2018-03-31 17:00:00,9228.0 -2018-03-31 18:00:00,9152.0 -2018-03-31 19:00:00,9214.0 -2018-03-31 20:00:00,9325.0 -2018-03-31 21:00:00,9850.0 -2018-03-31 22:00:00,10016.0 -2018-03-31 23:00:00,9857.0 -2018-04-01 00:00:00,9556.0 -2018-03-30 01:00:00,9512.0 -2018-03-30 02:00:00,9118.0 -2018-03-30 03:00:00,8835.0 -2018-03-30 04:00:00,8705.0 -2018-03-30 05:00:00,8693.0 -2018-03-30 06:00:00,8883.0 -2018-03-30 07:00:00,9384.0 -2018-03-30 08:00:00,10022.0 -2018-03-30 09:00:00,10298.0 -2018-03-30 10:00:00,10516.0 -2018-03-30 11:00:00,10521.0 -2018-03-30 12:00:00,10434.0 -2018-03-30 13:00:00,10282.0 -2018-03-30 14:00:00,10104.0 -2018-03-30 15:00:00,9991.0 -2018-03-30 16:00:00,9852.0 -2018-03-30 17:00:00,9727.0 -2018-03-30 18:00:00,9670.0 -2018-03-30 19:00:00,9733.0 -2018-03-30 20:00:00,9822.0 -2018-03-30 21:00:00,10154.0 -2018-03-30 22:00:00,10193.0 -2018-03-30 23:00:00,9973.0 -2018-03-31 00:00:00,9545.0 -2018-03-29 01:00:00,9299.0 -2018-03-29 02:00:00,8897.0 -2018-03-29 03:00:00,8664.0 -2018-03-29 04:00:00,8522.0 -2018-03-29 05:00:00,8513.0 -2018-03-29 06:00:00,8769.0 -2018-03-29 07:00:00,9317.0 -2018-03-29 08:00:00,10247.0 -2018-03-29 09:00:00,10702.0 -2018-03-29 10:00:00,10991.0 -2018-03-29 11:00:00,11067.0 -2018-03-29 12:00:00,11078.0 -2018-03-29 13:00:00,11030.0 -2018-03-29 14:00:00,10994.0 -2018-03-29 15:00:00,10994.0 -2018-03-29 16:00:00,10936.0 -2018-03-29 17:00:00,10866.0 -2018-03-29 18:00:00,10845.0 -2018-03-29 19:00:00,10804.0 -2018-03-29 20:00:00,10707.0 -2018-03-29 21:00:00,11048.0 -2018-03-29 22:00:00,11009.0 -2018-03-29 23:00:00,10674.0 -2018-03-30 00:00:00,10095.0 -2018-03-28 01:00:00,9485.0 -2018-03-28 02:00:00,9094.0 -2018-03-28 03:00:00,8894.0 -2018-03-28 04:00:00,8723.0 -2018-03-28 05:00:00,8699.0 -2018-03-28 06:00:00,8953.0 -2018-03-28 07:00:00,9583.0 -2018-03-28 08:00:00,10483.0 -2018-03-28 09:00:00,10905.0 -2018-03-28 10:00:00,11129.0 -2018-03-28 11:00:00,11133.0 -2018-03-28 12:00:00,11099.0 -2018-03-28 13:00:00,10976.0 -2018-03-28 14:00:00,10911.0 -2018-03-28 15:00:00,10847.0 -2018-03-28 16:00:00,10710.0 -2018-03-28 17:00:00,10567.0 -2018-03-28 18:00:00,10473.0 -2018-03-28 19:00:00,10490.0 -2018-03-28 20:00:00,10478.0 -2018-03-28 21:00:00,10890.0 -2018-03-28 22:00:00,10823.0 -2018-03-28 23:00:00,10435.0 -2018-03-29 00:00:00,9866.0 -2018-03-27 01:00:00,9560.0 -2018-03-27 02:00:00,9152.0 -2018-03-27 03:00:00,8890.0 -2018-03-27 04:00:00,8743.0 -2018-03-27 05:00:00,8650.0 -2018-03-27 06:00:00,8878.0 -2018-03-27 07:00:00,9469.0 -2018-03-27 08:00:00,10402.0 -2018-03-27 09:00:00,10904.0 -2018-03-27 10:00:00,11133.0 -2018-03-27 11:00:00,11260.0 -2018-03-27 12:00:00,11356.0 -2018-03-27 13:00:00,11297.0 -2018-03-27 14:00:00,11193.0 -2018-03-27 15:00:00,11174.0 -2018-03-27 16:00:00,11099.0 -2018-03-27 17:00:00,10990.0 -2018-03-27 18:00:00,10909.0 -2018-03-27 19:00:00,10860.0 -2018-03-27 20:00:00,10801.0 -2018-03-27 21:00:00,11085.0 -2018-03-27 22:00:00,10973.0 -2018-03-27 23:00:00,10604.0 -2018-03-28 00:00:00,10034.0 -2018-03-26 01:00:00,9554.0 -2018-03-26 02:00:00,9289.0 -2018-03-26 03:00:00,9157.0 -2018-03-26 04:00:00,9128.0 -2018-03-26 05:00:00,9168.0 -2018-03-26 06:00:00,9462.0 -2018-03-26 07:00:00,10198.0 -2018-03-26 08:00:00,11165.0 -2018-03-26 09:00:00,11549.0 -2018-03-26 10:00:00,11606.0 -2018-03-26 11:00:00,11470.0 -2018-03-26 12:00:00,11410.0 -2018-03-26 13:00:00,11365.0 -2018-03-26 14:00:00,11271.0 -2018-03-26 15:00:00,11212.0 -2018-03-26 16:00:00,11112.0 -2018-03-26 17:00:00,11023.0 -2018-03-26 18:00:00,10964.0 -2018-03-26 19:00:00,10937.0 -2018-03-26 20:00:00,10944.0 -2018-03-26 21:00:00,11234.0 -2018-03-26 22:00:00,11061.0 -2018-03-26 23:00:00,10669.0 -2018-03-27 00:00:00,10111.0 -2018-03-25 01:00:00,9590.0 -2018-03-25 02:00:00,9321.0 -2018-03-25 03:00:00,9096.0 -2018-03-25 04:00:00,8993.0 -2018-03-25 05:00:00,9047.0 -2018-03-25 06:00:00,9113.0 -2018-03-25 07:00:00,9289.0 -2018-03-25 08:00:00,9569.0 -2018-03-25 09:00:00,9535.0 -2018-03-25 10:00:00,9687.0 -2018-03-25 11:00:00,9716.0 -2018-03-25 12:00:00,9715.0 -2018-03-25 13:00:00,9699.0 -2018-03-25 14:00:00,9624.0 -2018-03-25 15:00:00,9501.0 -2018-03-25 16:00:00,9373.0 -2018-03-25 17:00:00,9354.0 -2018-03-25 18:00:00,9394.0 -2018-03-25 19:00:00,9527.0 -2018-03-25 20:00:00,9678.0 -2018-03-25 21:00:00,10322.0 -2018-03-25 22:00:00,10488.0 -2018-03-25 23:00:00,10265.0 -2018-03-26 00:00:00,9908.0 -2018-03-24 01:00:00,9954.0 -2018-03-24 02:00:00,9545.0 -2018-03-24 03:00:00,9230.0 -2018-03-24 04:00:00,9084.0 -2018-03-24 05:00:00,9007.0 -2018-03-24 06:00:00,9157.0 -2018-03-24 07:00:00,9363.0 -2018-03-24 08:00:00,9912.0 -2018-03-24 09:00:00,10132.0 -2018-03-24 10:00:00,10549.0 -2018-03-24 11:00:00,10782.0 -2018-03-24 12:00:00,10881.0 -2018-03-24 13:00:00,10902.0 -2018-03-24 14:00:00,10737.0 -2018-03-24 15:00:00,10544.0 -2018-03-24 16:00:00,10411.0 -2018-03-24 17:00:00,10252.0 -2018-03-24 18:00:00,10221.0 -2018-03-24 19:00:00,10266.0 -2018-03-24 20:00:00,10301.0 -2018-03-24 21:00:00,10657.0 -2018-03-24 22:00:00,10684.0 -2018-03-24 23:00:00,10459.0 -2018-03-25 00:00:00,10085.0 -2018-03-23 01:00:00,9774.0 -2018-03-23 02:00:00,9374.0 -2018-03-23 03:00:00,9148.0 -2018-03-23 04:00:00,9073.0 -2018-03-23 05:00:00,9089.0 -2018-03-23 06:00:00,9410.0 -2018-03-23 07:00:00,10116.0 -2018-03-23 08:00:00,11147.0 -2018-03-23 09:00:00,11521.0 -2018-03-23 10:00:00,11544.0 -2018-03-23 11:00:00,11464.0 -2018-03-23 12:00:00,11412.0 -2018-03-23 13:00:00,11272.0 -2018-03-23 14:00:00,11170.0 -2018-03-23 15:00:00,11065.0 -2018-03-23 16:00:00,10921.0 -2018-03-23 17:00:00,10733.0 -2018-03-23 18:00:00,10722.0 -2018-03-23 19:00:00,10768.0 -2018-03-23 20:00:00,10886.0 -2018-03-23 21:00:00,11381.0 -2018-03-23 22:00:00,11357.0 -2018-03-23 23:00:00,11120.0 -2018-03-24 00:00:00,10551.0 -2018-03-22 01:00:00,10005.0 -2018-03-22 02:00:00,9628.0 -2018-03-22 03:00:00,9432.0 -2018-03-22 04:00:00,9351.0 -2018-03-22 05:00:00,9378.0 -2018-03-22 06:00:00,9685.0 -2018-03-22 07:00:00,10391.0 -2018-03-22 08:00:00,11494.0 -2018-03-22 09:00:00,11846.0 -2018-03-22 10:00:00,11797.0 -2018-03-22 11:00:00,11652.0 -2018-03-22 12:00:00,11552.0 -2018-03-22 13:00:00,11418.0 -2018-03-22 14:00:00,11242.0 -2018-03-22 15:00:00,11177.0 -2018-03-22 16:00:00,10994.0 -2018-03-22 17:00:00,10783.0 -2018-03-22 18:00:00,10667.0 -2018-03-22 19:00:00,10610.0 -2018-03-22 20:00:00,10663.0 -2018-03-22 21:00:00,11217.0 -2018-03-22 22:00:00,11248.0 -2018-03-22 23:00:00,10913.0 -2018-03-23 00:00:00,10345.0 -2018-03-21 01:00:00,10141.0 -2018-03-21 02:00:00,9756.0 -2018-03-21 03:00:00,9485.0 -2018-03-21 04:00:00,9378.0 -2018-03-21 05:00:00,9369.0 -2018-03-21 06:00:00,9618.0 -2018-03-21 07:00:00,10349.0 -2018-03-21 08:00:00,11461.0 -2018-03-21 09:00:00,11845.0 -2018-03-21 10:00:00,11923.0 -2018-03-21 11:00:00,11837.0 -2018-03-21 12:00:00,11749.0 -2018-03-21 13:00:00,11615.0 -2018-03-21 14:00:00,11470.0 -2018-03-21 15:00:00,11414.0 -2018-03-21 16:00:00,11214.0 -2018-03-21 17:00:00,11032.0 -2018-03-21 18:00:00,10954.0 -2018-03-21 19:00:00,10966.0 -2018-03-21 20:00:00,10968.0 -2018-03-21 21:00:00,11552.0 -2018-03-21 22:00:00,11583.0 -2018-03-21 23:00:00,11207.0 -2018-03-22 00:00:00,10616.0 -2018-03-20 01:00:00,9919.0 -2018-03-20 02:00:00,9564.0 -2018-03-20 03:00:00,9354.0 -2018-03-20 04:00:00,9269.0 -2018-03-20 05:00:00,9283.0 -2018-03-20 06:00:00,9599.0 -2018-03-20 07:00:00,10354.0 -2018-03-20 08:00:00,11447.0 -2018-03-20 09:00:00,11821.0 -2018-03-20 10:00:00,11954.0 -2018-03-20 11:00:00,11916.0 -2018-03-20 12:00:00,11828.0 -2018-03-20 13:00:00,11737.0 -2018-03-20 14:00:00,11609.0 -2018-03-20 15:00:00,11563.0 -2018-03-20 16:00:00,11422.0 -2018-03-20 17:00:00,11315.0 -2018-03-20 18:00:00,11308.0 -2018-03-20 19:00:00,11387.0 -2018-03-20 20:00:00,11487.0 -2018-03-20 21:00:00,11948.0 -2018-03-20 22:00:00,11839.0 -2018-03-20 23:00:00,11409.0 -2018-03-21 00:00:00,10791.0 -2018-03-19 01:00:00,8915.0 -2018-03-19 02:00:00,8630.0 -2018-03-19 03:00:00,8491.0 -2018-03-19 04:00:00,8450.0 -2018-03-19 05:00:00,8503.0 -2018-03-19 06:00:00,8841.0 -2018-03-19 07:00:00,9652.0 -2018-03-19 08:00:00,10871.0 -2018-03-19 09:00:00,11410.0 -2018-03-19 10:00:00,11466.0 -2018-03-19 11:00:00,11337.0 -2018-03-19 12:00:00,11319.0 -2018-03-19 13:00:00,11245.0 -2018-03-19 14:00:00,11171.0 -2018-03-19 15:00:00,11135.0 -2018-03-19 16:00:00,11021.0 -2018-03-19 17:00:00,10913.0 -2018-03-19 18:00:00,10904.0 -2018-03-19 19:00:00,10957.0 -2018-03-19 20:00:00,11043.0 -2018-03-19 21:00:00,11612.0 -2018-03-19 22:00:00,11571.0 -2018-03-19 23:00:00,11203.0 -2018-03-20 00:00:00,10524.0 -2018-03-18 01:00:00,9518.0 -2018-03-18 02:00:00,9171.0 -2018-03-18 03:00:00,8994.0 -2018-03-18 04:00:00,8857.0 -2018-03-18 05:00:00,8862.0 -2018-03-18 06:00:00,8815.0 -2018-03-18 07:00:00,9029.0 -2018-03-18 08:00:00,9285.0 -2018-03-18 09:00:00,9305.0 -2018-03-18 10:00:00,9408.0 -2018-03-18 11:00:00,9406.0 -2018-03-18 12:00:00,9378.0 -2018-03-18 13:00:00,9279.0 -2018-03-18 14:00:00,9192.0 -2018-03-18 15:00:00,9076.0 -2018-03-18 16:00:00,8971.0 -2018-03-18 17:00:00,8922.0 -2018-03-18 18:00:00,8907.0 -2018-03-18 19:00:00,9017.0 -2018-03-18 20:00:00,9199.0 -2018-03-18 21:00:00,9830.0 -2018-03-18 22:00:00,9886.0 -2018-03-18 23:00:00,9664.0 -2018-03-19 00:00:00,9302.0 -2018-03-17 01:00:00,10118.0 -2018-03-17 02:00:00,9767.0 -2018-03-17 03:00:00,9484.0 -2018-03-17 04:00:00,9291.0 -2018-03-17 05:00:00,9316.0 -2018-03-17 06:00:00,9361.0 -2018-03-17 07:00:00,9672.0 -2018-03-17 08:00:00,10090.0 -2018-03-17 09:00:00,10379.0 -2018-03-17 10:00:00,10694.0 -2018-03-17 11:00:00,10835.0 -2018-03-17 12:00:00,10801.0 -2018-03-17 13:00:00,10659.0 -2018-03-17 14:00:00,10464.0 -2018-03-17 15:00:00,10097.0 -2018-03-17 16:00:00,9899.0 -2018-03-17 17:00:00,9642.0 -2018-03-17 18:00:00,9574.0 -2018-03-17 19:00:00,9508.0 -2018-03-17 20:00:00,9692.0 -2018-03-17 21:00:00,10272.0 -2018-03-17 22:00:00,10372.0 -2018-03-17 23:00:00,10251.0 -2018-03-18 00:00:00,9888.0 -2018-03-16 01:00:00,10100.0 -2018-03-16 02:00:00,9700.0 -2018-03-16 03:00:00,9510.0 -2018-03-16 04:00:00,9414.0 -2018-03-16 05:00:00,9429.0 -2018-03-16 06:00:00,9714.0 -2018-03-16 07:00:00,10386.0 -2018-03-16 08:00:00,11480.0 -2018-03-16 09:00:00,12015.0 -2018-03-16 10:00:00,12131.0 -2018-03-16 11:00:00,12142.0 -2018-03-16 12:00:00,12000.0 -2018-03-16 13:00:00,11851.0 -2018-03-16 14:00:00,11712.0 -2018-03-16 15:00:00,11586.0 -2018-03-16 16:00:00,11416.0 -2018-03-16 17:00:00,11304.0 -2018-03-16 18:00:00,11257.0 -2018-03-16 19:00:00,11322.0 -2018-03-16 20:00:00,11394.0 -2018-03-16 21:00:00,11855.0 -2018-03-16 22:00:00,11711.0 -2018-03-16 23:00:00,11322.0 -2018-03-17 00:00:00,10710.0 -2018-03-15 01:00:00,9896.0 -2018-03-15 02:00:00,9499.0 -2018-03-15 03:00:00,9246.0 -2018-03-15 04:00:00,9167.0 -2018-03-15 05:00:00,9212.0 -2018-03-15 06:00:00,9482.0 -2018-03-15 07:00:00,10226.0 -2018-03-15 08:00:00,11364.0 -2018-03-15 09:00:00,11785.0 -2018-03-15 10:00:00,11759.0 -2018-03-15 11:00:00,11634.0 -2018-03-15 12:00:00,11510.0 -2018-03-15 13:00:00,11413.0 -2018-03-15 14:00:00,11269.0 -2018-03-15 15:00:00,11220.0 -2018-03-15 16:00:00,11045.0 -2018-03-15 17:00:00,10853.0 -2018-03-15 18:00:00,10812.0 -2018-03-15 19:00:00,10816.0 -2018-03-15 20:00:00,10927.0 -2018-03-15 21:00:00,11571.0 -2018-03-15 22:00:00,11574.0 -2018-03-15 23:00:00,11257.0 -2018-03-16 00:00:00,10665.0 -2018-03-14 01:00:00,10599.0 -2018-03-14 02:00:00,10213.0 -2018-03-14 03:00:00,9994.0 -2018-03-14 04:00:00,9913.0 -2018-03-14 05:00:00,9939.0 -2018-03-14 06:00:00,10164.0 -2018-03-14 07:00:00,10892.0 -2018-03-14 08:00:00,11951.0 -2018-03-14 09:00:00,12388.0 -2018-03-14 10:00:00,12347.0 -2018-03-14 11:00:00,12152.0 -2018-03-14 12:00:00,12059.0 -2018-03-14 13:00:00,11965.0 -2018-03-14 14:00:00,11832.0 -2018-03-14 15:00:00,11646.0 -2018-03-14 16:00:00,11459.0 -2018-03-14 17:00:00,11211.0 -2018-03-14 18:00:00,11092.0 -2018-03-14 19:00:00,11041.0 -2018-03-14 20:00:00,11107.0 -2018-03-14 21:00:00,11616.0 -2018-03-14 22:00:00,11549.0 -2018-03-14 23:00:00,11159.0 -2018-03-15 00:00:00,10513.0 -2018-03-13 01:00:00,10104.0 -2018-03-13 02:00:00,9746.0 -2018-03-13 03:00:00,9535.0 -2018-03-13 04:00:00,9407.0 -2018-03-13 05:00:00,9464.0 -2018-03-13 06:00:00,9755.0 -2018-03-13 07:00:00,10463.0 -2018-03-13 08:00:00,11581.0 -2018-03-13 09:00:00,12044.0 -2018-03-13 10:00:00,12030.0 -2018-03-13 11:00:00,11935.0 -2018-03-13 12:00:00,11922.0 -2018-03-13 13:00:00,11958.0 -2018-03-13 14:00:00,11919.0 -2018-03-13 15:00:00,11984.0 -2018-03-13 16:00:00,11937.0 -2018-03-13 17:00:00,11950.0 -2018-03-13 18:00:00,11926.0 -2018-03-13 19:00:00,11900.0 -2018-03-13 20:00:00,11996.0 -2018-03-13 21:00:00,12411.0 -2018-03-13 22:00:00,12312.0 -2018-03-13 23:00:00,11883.0 -2018-03-14 00:00:00,11227.0 -2018-03-12 01:00:00,9629.0 -2018-03-12 02:00:00,9249.0 -2018-03-12 03:00:00,9071.0 -2018-03-12 04:00:00,8970.0 -2018-03-12 05:00:00,9100.0 -2018-03-12 06:00:00,9255.0 -2018-03-12 07:00:00,10031.0 -2018-03-12 08:00:00,11161.0 -2018-03-12 09:00:00,11826.0 -2018-03-12 10:00:00,11923.0 -2018-03-12 11:00:00,11973.0 -2018-03-12 12:00:00,11960.0 -2018-03-12 13:00:00,11914.0 -2018-03-12 14:00:00,11725.0 -2018-03-12 15:00:00,11599.0 -2018-03-12 16:00:00,11449.0 -2018-03-12 17:00:00,11304.0 -2018-03-12 18:00:00,11276.0 -2018-03-12 19:00:00,11321.0 -2018-03-12 20:00:00,11468.0 -2018-03-12 21:00:00,11865.0 -2018-03-12 22:00:00,11750.0 -2018-03-12 23:00:00,11362.0 -2018-03-13 00:00:00,10740.0 -2018-03-11 01:00:00,9632.0 -2018-03-11 02:00:00,9360.0 -2018-03-11 04:00:00,9126.0 -2018-03-11 05:00:00,9031.0 -2018-03-11 06:00:00,8966.0 -2018-03-11 07:00:00,9127.0 -2018-03-11 08:00:00,9384.0 -2018-03-11 09:00:00,9431.0 -2018-03-11 10:00:00,9485.0 -2018-03-11 11:00:00,9568.0 -2018-03-11 12:00:00,9628.0 -2018-03-11 13:00:00,9638.0 -2018-03-11 14:00:00,9609.0 -2018-03-11 15:00:00,9562.0 -2018-03-11 16:00:00,9460.0 -2018-03-11 17:00:00,9440.0 -2018-03-11 18:00:00,9441.0 -2018-03-11 19:00:00,9523.0 -2018-03-11 20:00:00,9877.0 -2018-03-11 21:00:00,10574.0 -2018-03-11 22:00:00,10647.0 -2018-03-11 23:00:00,10463.0 -2018-03-12 00:00:00,10052.0 -2018-03-10 01:00:00,10230.0 -2018-03-10 02:00:00,9851.0 -2018-03-10 03:00:00,9638.0 -2018-03-10 04:00:00,9545.0 -2018-03-10 05:00:00,9473.0 -2018-03-10 06:00:00,9621.0 -2018-03-10 07:00:00,9890.0 -2018-03-10 08:00:00,10109.0 -2018-03-10 09:00:00,10263.0 -2018-03-10 10:00:00,10443.0 -2018-03-10 11:00:00,10523.0 -2018-03-10 12:00:00,10483.0 -2018-03-10 13:00:00,10378.0 -2018-03-10 14:00:00,10182.0 -2018-03-10 15:00:00,9978.0 -2018-03-10 16:00:00,9807.0 -2018-03-10 17:00:00,9745.0 -2018-03-10 18:00:00,9871.0 -2018-03-10 19:00:00,10177.0 -2018-03-10 20:00:00,10783.0 -2018-03-10 21:00:00,10860.0 -2018-03-10 22:00:00,10743.0 -2018-03-10 23:00:00,10489.0 -2018-03-11 00:00:00,10056.0 -2018-03-09 01:00:00,10381.0 -2018-03-09 02:00:00,10037.0 -2018-03-09 03:00:00,9882.0 -2018-03-09 04:00:00,9762.0 -2018-03-09 05:00:00,9772.0 -2018-03-09 06:00:00,10087.0 -2018-03-09 07:00:00,10793.0 -2018-03-09 08:00:00,11543.0 -2018-03-09 09:00:00,11860.0 -2018-03-09 10:00:00,11875.0 -2018-03-09 11:00:00,11839.0 -2018-03-09 12:00:00,11792.0 -2018-03-09 13:00:00,11701.0 -2018-03-09 14:00:00,11510.0 -2018-03-09 15:00:00,11405.0 -2018-03-09 16:00:00,11200.0 -2018-03-09 17:00:00,11067.0 -2018-03-09 18:00:00,11103.0 -2018-03-09 19:00:00,11414.0 -2018-03-09 20:00:00,11984.0 -2018-03-09 21:00:00,11853.0 -2018-03-09 22:00:00,11621.0 -2018-03-09 23:00:00,11318.0 -2018-03-10 00:00:00,10750.0 -2018-03-08 01:00:00,10464.0 -2018-03-08 02:00:00,10101.0 -2018-03-08 03:00:00,9924.0 -2018-03-08 04:00:00,9822.0 -2018-03-08 05:00:00,9834.0 -2018-03-08 06:00:00,10163.0 -2018-03-08 07:00:00,10857.0 -2018-03-08 08:00:00,11710.0 -2018-03-08 09:00:00,12068.0 -2018-03-08 10:00:00,12120.0 -2018-03-08 11:00:00,12072.0 -2018-03-08 12:00:00,12062.0 -2018-03-08 13:00:00,11981.0 -2018-03-08 14:00:00,11894.0 -2018-03-08 15:00:00,11826.0 -2018-03-08 16:00:00,11735.0 -2018-03-08 17:00:00,11639.0 -2018-03-08 18:00:00,11707.0 -2018-03-08 19:00:00,11930.0 -2018-03-08 20:00:00,12442.0 -2018-03-08 21:00:00,12354.0 -2018-03-08 22:00:00,12119.0 -2018-03-08 23:00:00,11633.0 -2018-03-09 00:00:00,10952.0 -2018-03-07 01:00:00,10244.0 -2018-03-07 02:00:00,9854.0 -2018-03-07 03:00:00,9682.0 -2018-03-07 04:00:00,9613.0 -2018-03-07 05:00:00,9672.0 -2018-03-07 06:00:00,9984.0 -2018-03-07 07:00:00,10704.0 -2018-03-07 08:00:00,11611.0 -2018-03-07 09:00:00,11994.0 -2018-03-07 10:00:00,12017.0 -2018-03-07 11:00:00,11952.0 -2018-03-07 12:00:00,11958.0 -2018-03-07 13:00:00,11882.0 -2018-03-07 14:00:00,11818.0 -2018-03-07 15:00:00,11810.0 -2018-03-07 16:00:00,11711.0 -2018-03-07 17:00:00,11726.0 -2018-03-07 18:00:00,11878.0 -2018-03-07 19:00:00,12247.0 -2018-03-07 20:00:00,12535.0 -2018-03-07 21:00:00,12401.0 -2018-03-07 22:00:00,12203.0 -2018-03-07 23:00:00,11703.0 -2018-03-08 00:00:00,11031.0 -2018-03-06 01:00:00,10138.0 -2018-03-06 02:00:00,9699.0 -2018-03-06 03:00:00,9420.0 -2018-03-06 04:00:00,9329.0 -2018-03-06 05:00:00,9330.0 -2018-03-06 06:00:00,9567.0 -2018-03-06 07:00:00,10268.0 -2018-03-06 08:00:00,11185.0 -2018-03-06 09:00:00,11674.0 -2018-03-06 10:00:00,11840.0 -2018-03-06 11:00:00,11904.0 -2018-03-06 12:00:00,11893.0 -2018-03-06 13:00:00,11769.0 -2018-03-06 14:00:00,11799.0 -2018-03-06 15:00:00,11815.0 -2018-03-06 16:00:00,11706.0 -2018-03-06 17:00:00,11609.0 -2018-03-06 18:00:00,11711.0 -2018-03-06 19:00:00,11990.0 -2018-03-06 20:00:00,12274.0 -2018-03-06 21:00:00,12146.0 -2018-03-06 22:00:00,11891.0 -2018-03-06 23:00:00,11432.0 -2018-03-07 00:00:00,10776.0 -2018-03-05 01:00:00,9379.0 -2018-03-05 02:00:00,9124.0 -2018-03-05 03:00:00,9030.0 -2018-03-05 04:00:00,8977.0 -2018-03-05 05:00:00,9021.0 -2018-03-05 06:00:00,9335.0 -2018-03-05 07:00:00,10129.0 -2018-03-05 08:00:00,11029.0 -2018-03-05 09:00:00,11666.0 -2018-03-05 10:00:00,11897.0 -2018-03-05 11:00:00,12058.0 -2018-03-05 12:00:00,12037.0 -2018-03-05 13:00:00,12011.0 -2018-03-05 14:00:00,11981.0 -2018-03-05 15:00:00,11900.0 -2018-03-05 16:00:00,11826.0 -2018-03-05 17:00:00,11816.0 -2018-03-05 18:00:00,12004.0 -2018-03-05 19:00:00,12362.0 -2018-03-05 20:00:00,12446.0 -2018-03-05 21:00:00,12261.0 -2018-03-05 22:00:00,11956.0 -2018-03-05 23:00:00,11448.0 -2018-03-06 00:00:00,10750.0 -2018-03-04 01:00:00,9450.0 -2018-03-04 02:00:00,9128.0 -2018-03-04 03:00:00,8968.0 -2018-03-04 04:00:00,8831.0 -2018-03-04 05:00:00,8769.0 -2018-03-04 06:00:00,8854.0 -2018-03-04 07:00:00,8999.0 -2018-03-04 08:00:00,9087.0 -2018-03-04 09:00:00,9139.0 -2018-03-04 10:00:00,9279.0 -2018-03-04 11:00:00,9373.0 -2018-03-04 12:00:00,9383.0 -2018-03-04 13:00:00,9357.0 -2018-03-04 14:00:00,9336.0 -2018-03-04 15:00:00,9152.0 -2018-03-04 16:00:00,9092.0 -2018-03-04 17:00:00,9153.0 -2018-03-04 18:00:00,9338.0 -2018-03-04 19:00:00,9820.0 -2018-03-04 20:00:00,10486.0 -2018-03-04 21:00:00,10554.0 -2018-03-04 22:00:00,10384.0 -2018-03-04 23:00:00,10167.0 -2018-03-05 00:00:00,9700.0 -2018-03-03 01:00:00,9943.0 -2018-03-03 02:00:00,9526.0 -2018-03-03 03:00:00,9281.0 -2018-03-03 04:00:00,9102.0 -2018-03-03 05:00:00,9041.0 -2018-03-03 06:00:00,9159.0 -2018-03-03 07:00:00,9434.0 -2018-03-03 08:00:00,9744.0 -2018-03-03 09:00:00,9919.0 -2018-03-03 10:00:00,10083.0 -2018-03-03 11:00:00,10148.0 -2018-03-03 12:00:00,10156.0 -2018-03-03 13:00:00,10085.0 -2018-03-03 14:00:00,9915.0 -2018-03-03 15:00:00,9679.0 -2018-03-03 16:00:00,9518.0 -2018-03-03 17:00:00,9472.0 -2018-03-03 18:00:00,9586.0 -2018-03-03 19:00:00,9938.0 -2018-03-03 20:00:00,10568.0 -2018-03-03 21:00:00,10575.0 -2018-03-03 22:00:00,10480.0 -2018-03-03 23:00:00,10280.0 -2018-03-04 00:00:00,9863.0 -2018-03-02 01:00:00,10035.0 -2018-03-02 02:00:00,9635.0 -2018-03-02 03:00:00,9480.0 -2018-03-02 04:00:00,9346.0 -2018-03-02 05:00:00,9350.0 -2018-03-02 06:00:00,9639.0 -2018-03-02 07:00:00,10347.0 -2018-03-02 08:00:00,11147.0 -2018-03-02 09:00:00,11409.0 -2018-03-02 10:00:00,11403.0 -2018-03-02 11:00:00,11403.0 -2018-03-02 12:00:00,11420.0 -2018-03-02 13:00:00,11299.0 -2018-03-02 14:00:00,11162.0 -2018-03-02 15:00:00,11079.0 -2018-03-02 16:00:00,10908.0 -2018-03-02 17:00:00,10738.0 -2018-03-02 18:00:00,10742.0 -2018-03-02 19:00:00,11021.0 -2018-03-02 20:00:00,11530.0 -2018-03-02 21:00:00,11446.0 -2018-03-02 22:00:00,11258.0 -2018-03-02 23:00:00,11033.0 -2018-03-03 00:00:00,10475.0 -2018-03-01 01:00:00,9616.0 -2018-03-01 02:00:00,9217.0 -2018-03-01 03:00:00,9008.0 -2018-03-01 04:00:00,8887.0 -2018-03-01 05:00:00,8929.0 -2018-03-01 06:00:00,9226.0 -2018-03-01 07:00:00,10012.0 -2018-03-01 08:00:00,11129.0 -2018-03-01 09:00:00,11753.0 -2018-03-01 10:00:00,11945.0 -2018-03-01 11:00:00,11935.0 -2018-03-01 12:00:00,11984.0 -2018-03-01 13:00:00,11917.0 -2018-03-01 14:00:00,11713.0 -2018-03-01 15:00:00,11596.0 -2018-03-01 16:00:00,11334.0 -2018-03-01 17:00:00,11247.0 -2018-03-01 18:00:00,11250.0 -2018-03-01 19:00:00,11488.0 -2018-03-01 20:00:00,12004.0 -2018-03-01 21:00:00,11872.0 -2018-03-01 22:00:00,11640.0 -2018-03-01 23:00:00,11251.0 -2018-03-02 00:00:00,10558.0 -2018-02-28 01:00:00,9282.0 -2018-02-28 02:00:00,8881.0 -2018-02-28 03:00:00,8654.0 -2018-02-28 04:00:00,8510.0 -2018-02-28 05:00:00,8467.0 -2018-02-28 06:00:00,8697.0 -2018-02-28 07:00:00,9365.0 -2018-02-28 08:00:00,10280.0 -2018-02-28 09:00:00,10662.0 -2018-02-28 10:00:00,10734.0 -2018-02-28 11:00:00,10781.0 -2018-02-28 12:00:00,10836.0 -2018-02-28 13:00:00,10807.0 -2018-02-28 14:00:00,10740.0 -2018-02-28 15:00:00,10788.0 -2018-02-28 16:00:00,10807.0 -2018-02-28 17:00:00,10789.0 -2018-02-28 18:00:00,11030.0 -2018-02-28 19:00:00,11406.0 -2018-02-28 20:00:00,11604.0 -2018-02-28 21:00:00,11494.0 -2018-02-28 22:00:00,11278.0 -2018-02-28 23:00:00,10833.0 -2018-03-01 00:00:00,10185.0 -2018-02-27 01:00:00,9695.0 -2018-02-27 02:00:00,9337.0 -2018-02-27 03:00:00,9113.0 -2018-02-27 04:00:00,9009.0 -2018-02-27 05:00:00,8994.0 -2018-02-27 06:00:00,9224.0 -2018-02-27 07:00:00,9929.0 -2018-02-27 08:00:00,10873.0 -2018-02-27 09:00:00,11225.0 -2018-02-27 10:00:00,11360.0 -2018-02-27 11:00:00,11299.0 -2018-02-27 12:00:00,11183.0 -2018-02-27 13:00:00,11105.0 -2018-02-27 14:00:00,10988.0 -2018-02-27 15:00:00,10955.0 -2018-02-27 16:00:00,10812.0 -2018-02-27 17:00:00,10625.0 -2018-02-27 18:00:00,10654.0 -2018-02-27 19:00:00,10925.0 -2018-02-27 20:00:00,11345.0 -2018-02-27 21:00:00,11176.0 -2018-02-27 22:00:00,10943.0 -2018-02-27 23:00:00,10488.0 -2018-02-28 00:00:00,9857.0 -2018-02-26 01:00:00,9414.0 -2018-02-26 02:00:00,9123.0 -2018-02-26 03:00:00,9060.0 -2018-02-26 04:00:00,9040.0 -2018-02-26 05:00:00,9113.0 -2018-02-26 06:00:00,9442.0 -2018-02-26 07:00:00,10275.0 -2018-02-26 08:00:00,11223.0 -2018-02-26 09:00:00,11573.0 -2018-02-26 10:00:00,11579.0 -2018-02-26 11:00:00,11441.0 -2018-02-26 12:00:00,11382.0 -2018-02-26 13:00:00,11288.0 -2018-02-26 14:00:00,11162.0 -2018-02-26 15:00:00,11095.0 -2018-02-26 16:00:00,10940.0 -2018-02-26 17:00:00,10789.0 -2018-02-26 18:00:00,10800.0 -2018-02-26 19:00:00,11096.0 -2018-02-26 20:00:00,11545.0 -2018-02-26 21:00:00,11444.0 -2018-02-26 22:00:00,11221.0 -2018-02-26 23:00:00,10773.0 -2018-02-27 00:00:00,10210.0 -2018-02-25 01:00:00,9383.0 -2018-02-25 02:00:00,9053.0 -2018-02-25 03:00:00,8896.0 -2018-02-25 04:00:00,8886.0 -2018-02-25 05:00:00,8902.0 -2018-02-25 06:00:00,8990.0 -2018-02-25 07:00:00,9131.0 -2018-02-25 08:00:00,9327.0 -2018-02-25 09:00:00,9409.0 -2018-02-25 10:00:00,9566.0 -2018-02-25 11:00:00,9648.0 -2018-02-25 12:00:00,9674.0 -2018-02-25 13:00:00,9642.0 -2018-02-25 14:00:00,9575.0 -2018-02-25 15:00:00,9502.0 -2018-02-25 16:00:00,9408.0 -2018-02-25 17:00:00,9426.0 -2018-02-25 18:00:00,9591.0 -2018-02-25 19:00:00,10113.0 -2018-02-25 20:00:00,10683.0 -2018-02-25 21:00:00,10668.0 -2018-02-25 22:00:00,10561.0 -2018-02-25 23:00:00,10224.0 -2018-02-26 00:00:00,9810.0 -2018-02-24 01:00:00,10072.0 -2018-02-24 02:00:00,9631.0 -2018-02-24 03:00:00,9429.0 -2018-02-24 04:00:00,9267.0 -2018-02-24 05:00:00,9209.0 -2018-02-24 06:00:00,9316.0 -2018-02-24 07:00:00,9606.0 -2018-02-24 08:00:00,10052.0 -2018-02-24 09:00:00,10304.0 -2018-02-24 10:00:00,10639.0 -2018-02-24 11:00:00,10787.0 -2018-02-24 12:00:00,10859.0 -2018-02-24 13:00:00,10682.0 -2018-02-24 14:00:00,10576.0 -2018-02-24 15:00:00,10406.0 -2018-02-24 16:00:00,10300.0 -2018-02-24 17:00:00,10360.0 -2018-02-24 18:00:00,10545.0 -2018-02-24 19:00:00,10927.0 -2018-02-24 20:00:00,10989.0 -2018-02-24 21:00:00,10859.0 -2018-02-24 22:00:00,10658.0 -2018-02-24 23:00:00,10243.0 -2018-02-25 00:00:00,9771.0 -2018-02-23 01:00:00,10068.0 -2018-02-23 02:00:00,9651.0 -2018-02-23 03:00:00,9417.0 -2018-02-23 04:00:00,9251.0 -2018-02-23 05:00:00,9277.0 -2018-02-23 06:00:00,9500.0 -2018-02-23 07:00:00,10150.0 -2018-02-23 08:00:00,11070.0 -2018-02-23 09:00:00,11481.0 -2018-02-23 10:00:00,11632.0 -2018-02-23 11:00:00,11790.0 -2018-02-23 12:00:00,11866.0 -2018-02-23 13:00:00,11862.0 -2018-02-23 14:00:00,11796.0 -2018-02-23 15:00:00,11749.0 -2018-02-23 16:00:00,11647.0 -2018-02-23 17:00:00,11530.0 -2018-02-23 18:00:00,11599.0 -2018-02-23 19:00:00,11862.0 -2018-02-23 20:00:00,11954.0 -2018-02-23 21:00:00,11786.0 -2018-02-23 22:00:00,11508.0 -2018-02-23 23:00:00,11183.0 -2018-02-24 00:00:00,10584.0 -2018-02-22 01:00:00,10582.0 -2018-02-22 02:00:00,10211.0 -2018-02-22 03:00:00,9953.0 -2018-02-22 04:00:00,9807.0 -2018-02-22 05:00:00,9773.0 -2018-02-22 06:00:00,10046.0 -2018-02-22 07:00:00,10699.0 -2018-02-22 08:00:00,11715.0 -2018-02-22 09:00:00,12125.0 -2018-02-22 10:00:00,12212.0 -2018-02-22 11:00:00,12168.0 -2018-02-22 12:00:00,12138.0 -2018-02-22 13:00:00,11947.0 -2018-02-22 14:00:00,11796.0 -2018-02-22 15:00:00,11796.0 -2018-02-22 16:00:00,11764.0 -2018-02-22 17:00:00,11766.0 -2018-02-22 18:00:00,11831.0 -2018-02-22 19:00:00,12192.0 -2018-02-22 20:00:00,12300.0 -2018-02-22 21:00:00,12101.0 -2018-02-22 22:00:00,11821.0 -2018-02-22 23:00:00,11350.0 -2018-02-23 00:00:00,10681.0 -2018-02-21 01:00:00,10102.0 -2018-02-21 02:00:00,9775.0 -2018-02-21 03:00:00,9599.0 -2018-02-21 04:00:00,9537.0 -2018-02-21 05:00:00,9519.0 -2018-02-21 06:00:00,9821.0 -2018-02-21 07:00:00,10558.0 -2018-02-21 08:00:00,11601.0 -2018-02-21 09:00:00,12059.0 -2018-02-21 10:00:00,12231.0 -2018-02-21 11:00:00,12279.0 -2018-02-21 12:00:00,12242.0 -2018-02-21 13:00:00,12199.0 -2018-02-21 14:00:00,12091.0 -2018-02-21 15:00:00,12106.0 -2018-02-21 16:00:00,12032.0 -2018-02-21 17:00:00,12045.0 -2018-02-21 18:00:00,12195.0 -2018-02-21 19:00:00,12585.0 -2018-02-21 20:00:00,12739.0 -2018-02-21 21:00:00,12611.0 -2018-02-21 22:00:00,12346.0 -2018-02-21 23:00:00,11870.0 -2018-02-22 00:00:00,11153.0 -2018-02-20 01:00:00,9614.0 -2018-02-20 02:00:00,9158.0 -2018-02-20 03:00:00,8836.0 -2018-02-20 04:00:00,8726.0 -2018-02-20 05:00:00,8709.0 -2018-02-20 06:00:00,8953.0 -2018-02-20 07:00:00,9578.0 -2018-02-20 08:00:00,10555.0 -2018-02-20 09:00:00,11017.0 -2018-02-20 10:00:00,11157.0 -2018-02-20 11:00:00,11200.0 -2018-02-20 12:00:00,11329.0 -2018-02-20 13:00:00,11349.0 -2018-02-20 14:00:00,11413.0 -2018-02-20 15:00:00,11516.0 -2018-02-20 16:00:00,11375.0 -2018-02-20 17:00:00,11302.0 -2018-02-20 18:00:00,11426.0 -2018-02-20 19:00:00,11809.0 -2018-02-20 20:00:00,11927.0 -2018-02-20 21:00:00,11803.0 -2018-02-20 22:00:00,11596.0 -2018-02-20 23:00:00,11213.0 -2018-02-21 00:00:00,10610.0 -2018-02-19 01:00:00,9668.0 -2018-02-19 02:00:00,9361.0 -2018-02-19 03:00:00,9183.0 -2018-02-19 04:00:00,9055.0 -2018-02-19 05:00:00,9074.0 -2018-02-19 06:00:00,9339.0 -2018-02-19 07:00:00,9918.0 -2018-02-19 08:00:00,10793.0 -2018-02-19 09:00:00,11150.0 -2018-02-19 10:00:00,11455.0 -2018-02-19 11:00:00,11665.0 -2018-02-19 12:00:00,11765.0 -2018-02-19 13:00:00,11756.0 -2018-02-19 14:00:00,11645.0 -2018-02-19 15:00:00,11576.0 -2018-02-19 16:00:00,11441.0 -2018-02-19 17:00:00,11365.0 -2018-02-19 18:00:00,11466.0 -2018-02-19 19:00:00,11789.0 -2018-02-19 20:00:00,11752.0 -2018-02-19 21:00:00,11514.0 -2018-02-19 22:00:00,11249.0 -2018-02-19 23:00:00,10834.0 -2018-02-20 00:00:00,10186.0 -2018-02-18 01:00:00,10152.0 -2018-02-18 02:00:00,9824.0 -2018-02-18 03:00:00,9634.0 -2018-02-18 04:00:00,9544.0 -2018-02-18 05:00:00,9557.0 -2018-02-18 06:00:00,9655.0 -2018-02-18 07:00:00,9847.0 -2018-02-18 08:00:00,10061.0 -2018-02-18 09:00:00,10127.0 -2018-02-18 10:00:00,10236.0 -2018-02-18 11:00:00,10307.0 -2018-02-18 12:00:00,10370.0 -2018-02-18 13:00:00,10242.0 -2018-02-18 14:00:00,10135.0 -2018-02-18 15:00:00,9986.0 -2018-02-18 16:00:00,9924.0 -2018-02-18 17:00:00,9928.0 -2018-02-18 18:00:00,10144.0 -2018-02-18 19:00:00,10750.0 -2018-02-18 20:00:00,11218.0 -2018-02-18 21:00:00,11141.0 -2018-02-18 22:00:00,10945.0 -2018-02-18 23:00:00,10628.0 -2018-02-19 00:00:00,10135.0 -2018-02-17 01:00:00,10823.0 -2018-02-17 02:00:00,10421.0 -2018-02-17 03:00:00,10280.0 -2018-02-17 04:00:00,10122.0 -2018-02-17 05:00:00,10123.0 -2018-02-17 06:00:00,10176.0 -2018-02-17 07:00:00,10503.0 -2018-02-17 08:00:00,10882.0 -2018-02-17 09:00:00,11078.0 -2018-02-17 10:00:00,11339.0 -2018-02-17 11:00:00,11503.0 -2018-02-17 12:00:00,11390.0 -2018-02-17 13:00:00,11164.0 -2018-02-17 14:00:00,10986.0 -2018-02-17 15:00:00,10825.0 -2018-02-17 16:00:00,10812.0 -2018-02-17 17:00:00,10906.0 -2018-02-17 18:00:00,11057.0 -2018-02-17 19:00:00,11382.0 -2018-02-17 20:00:00,11609.0 -2018-02-17 21:00:00,11502.0 -2018-02-17 22:00:00,11357.0 -2018-02-17 23:00:00,11051.0 -2018-02-18 00:00:00,10603.0 -2018-02-16 01:00:00,9995.0 -2018-02-16 02:00:00,9673.0 -2018-02-16 03:00:00,9478.0 -2018-02-16 04:00:00,9367.0 -2018-02-16 05:00:00,9360.0 -2018-02-16 06:00:00,9641.0 -2018-02-16 07:00:00,10323.0 -2018-02-16 08:00:00,11334.0 -2018-02-16 09:00:00,11788.0 -2018-02-16 10:00:00,12005.0 -2018-02-16 11:00:00,12135.0 -2018-02-16 12:00:00,12101.0 -2018-02-16 13:00:00,11884.0 -2018-02-16 14:00:00,11719.0 -2018-02-16 15:00:00,11621.0 -2018-02-16 16:00:00,11474.0 -2018-02-16 17:00:00,11329.0 -2018-02-16 18:00:00,11430.0 -2018-02-16 19:00:00,11975.0 -2018-02-16 20:00:00,12368.0 -2018-02-16 21:00:00,12287.0 -2018-02-16 22:00:00,12095.0 -2018-02-16 23:00:00,11829.0 -2018-02-17 00:00:00,11282.0 -2018-02-15 01:00:00,10106.0 -2018-02-15 02:00:00,9686.0 -2018-02-15 03:00:00,9428.0 -2018-02-15 04:00:00,9287.0 -2018-02-15 05:00:00,9266.0 -2018-02-15 06:00:00,9486.0 -2018-02-15 07:00:00,10182.0 -2018-02-15 08:00:00,11219.0 -2018-02-15 09:00:00,11621.0 -2018-02-15 10:00:00,11818.0 -2018-02-15 11:00:00,11872.0 -2018-02-15 12:00:00,11871.0 -2018-02-15 13:00:00,11822.0 -2018-02-15 14:00:00,11665.0 -2018-02-15 15:00:00,11634.0 -2018-02-15 16:00:00,11480.0 -2018-02-15 17:00:00,11414.0 -2018-02-15 18:00:00,11533.0 -2018-02-15 19:00:00,12002.0 -2018-02-15 20:00:00,12082.0 -2018-02-15 21:00:00,11904.0 -2018-02-15 22:00:00,11659.0 -2018-02-15 23:00:00,11220.0 -2018-02-16 00:00:00,10538.0 -2018-02-14 01:00:00,10896.0 -2018-02-14 02:00:00,10512.0 -2018-02-14 03:00:00,10284.0 -2018-02-14 04:00:00,10145.0 -2018-02-14 05:00:00,10125.0 -2018-02-14 06:00:00,10391.0 -2018-02-14 07:00:00,11081.0 -2018-02-14 08:00:00,12054.0 -2018-02-14 09:00:00,12352.0 -2018-02-14 10:00:00,12276.0 -2018-02-14 11:00:00,12103.0 -2018-02-14 12:00:00,11997.0 -2018-02-14 13:00:00,11905.0 -2018-02-14 14:00:00,11746.0 -2018-02-14 15:00:00,11674.0 -2018-02-14 16:00:00,11585.0 -2018-02-14 17:00:00,11613.0 -2018-02-14 18:00:00,11818.0 -2018-02-14 19:00:00,12307.0 -2018-02-14 20:00:00,12386.0 -2018-02-14 21:00:00,12197.0 -2018-02-14 22:00:00,11908.0 -2018-02-14 23:00:00,11428.0 -2018-02-15 00:00:00,10725.0 -2018-02-13 01:00:00,11347.0 -2018-02-13 02:00:00,11011.0 -2018-02-13 03:00:00,10852.0 -2018-02-13 04:00:00,10736.0 -2018-02-13 05:00:00,10747.0 -2018-02-13 06:00:00,10989.0 -2018-02-13 07:00:00,11636.0 -2018-02-13 08:00:00,12590.0 -2018-02-13 09:00:00,12935.0 -2018-02-13 10:00:00,13070.0 -2018-02-13 11:00:00,13079.0 -2018-02-13 12:00:00,13008.0 -2018-02-13 13:00:00,12808.0 -2018-02-13 14:00:00,12617.0 -2018-02-13 15:00:00,12451.0 -2018-02-13 16:00:00,12339.0 -2018-02-13 17:00:00,12250.0 -2018-02-13 18:00:00,12293.0 -2018-02-13 19:00:00,12735.0 -2018-02-13 20:00:00,13003.0 -2018-02-13 21:00:00,12870.0 -2018-02-13 22:00:00,12653.0 -2018-02-13 23:00:00,12185.0 -2018-02-14 00:00:00,11516.0 -2018-02-12 01:00:00,11045.0 -2018-02-12 02:00:00,10827.0 -2018-02-12 03:00:00,10643.0 -2018-02-12 04:00:00,10623.0 -2018-02-12 05:00:00,10707.0 -2018-02-12 06:00:00,10993.0 -2018-02-12 07:00:00,11719.0 -2018-02-12 08:00:00,12739.0 -2018-02-12 09:00:00,13059.0 -2018-02-12 10:00:00,13053.0 -2018-02-12 11:00:00,12894.0 -2018-02-12 12:00:00,12789.0 -2018-02-12 13:00:00,12663.0 -2018-02-12 14:00:00,12586.0 -2018-02-12 15:00:00,12495.0 -2018-02-12 16:00:00,12385.0 -2018-02-12 17:00:00,12394.0 -2018-02-12 18:00:00,12611.0 -2018-02-12 19:00:00,13184.0 -2018-02-12 20:00:00,13436.0 -2018-02-12 21:00:00,13300.0 -2018-02-12 22:00:00,13090.0 -2018-02-12 23:00:00,12634.0 -2018-02-13 00:00:00,11966.0 -2018-02-11 01:00:00,10794.0 -2018-02-11 02:00:00,10443.0 -2018-02-11 03:00:00,10244.0 -2018-02-11 04:00:00,10099.0 -2018-02-11 05:00:00,10051.0 -2018-02-11 06:00:00,10082.0 -2018-02-11 07:00:00,10252.0 -2018-02-11 08:00:00,10451.0 -2018-02-11 09:00:00,10489.0 -2018-02-11 10:00:00,10749.0 -2018-02-11 11:00:00,10969.0 -2018-02-11 12:00:00,11056.0 -2018-02-11 13:00:00,11041.0 -2018-02-11 14:00:00,11072.0 -2018-02-11 15:00:00,11028.0 -2018-02-11 16:00:00,10924.0 -2018-02-11 17:00:00,10847.0 -2018-02-11 18:00:00,11217.0 -2018-02-11 19:00:00,11953.0 -2018-02-11 20:00:00,12468.0 -2018-02-11 21:00:00,12327.0 -2018-02-11 22:00:00,12249.0 -2018-02-11 23:00:00,11940.0 -2018-02-12 00:00:00,11453.0 -2018-02-10 01:00:00,11062.0 -2018-02-10 02:00:00,10683.0 -2018-02-10 03:00:00,10479.0 -2018-02-10 04:00:00,10305.0 -2018-02-10 05:00:00,10294.0 -2018-02-10 06:00:00,10433.0 -2018-02-10 07:00:00,10740.0 -2018-02-10 08:00:00,11125.0 -2018-02-10 09:00:00,11300.0 -2018-02-10 10:00:00,11671.0 -2018-02-10 11:00:00,11887.0 -2018-02-10 12:00:00,11906.0 -2018-02-10 13:00:00,11862.0 -2018-02-10 14:00:00,11712.0 -2018-02-10 15:00:00,11555.0 -2018-02-10 16:00:00,11428.0 -2018-02-10 17:00:00,11424.0 -2018-02-10 18:00:00,11589.0 -2018-02-10 19:00:00,12123.0 -2018-02-10 20:00:00,12328.0 -2018-02-10 21:00:00,12198.0 -2018-02-10 22:00:00,11978.0 -2018-02-10 23:00:00,11669.0 -2018-02-11 00:00:00,11140.0 -2018-02-09 01:00:00,11362.0 -2018-02-09 02:00:00,10973.0 -2018-02-09 03:00:00,10686.0 -2018-02-09 04:00:00,10534.0 -2018-02-09 05:00:00,10502.0 -2018-02-09 06:00:00,10670.0 -2018-02-09 07:00:00,11136.0 -2018-02-09 08:00:00,11797.0 -2018-02-09 09:00:00,12074.0 -2018-02-09 10:00:00,12436.0 -2018-02-09 11:00:00,12622.0 -2018-02-09 12:00:00,12758.0 -2018-02-09 13:00:00,12804.0 -2018-02-09 14:00:00,12796.0 -2018-02-09 15:00:00,12733.0 -2018-02-09 16:00:00,12596.0 -2018-02-09 17:00:00,12522.0 -2018-02-09 18:00:00,12595.0 -2018-02-09 19:00:00,13138.0 -2018-02-09 20:00:00,13139.0 -2018-02-09 21:00:00,12884.0 -2018-02-09 22:00:00,12596.0 -2018-02-09 23:00:00,12228.0 -2018-02-10 00:00:00,11611.0 -2018-02-08 01:00:00,11729.0 -2018-02-08 02:00:00,11412.0 -2018-02-08 03:00:00,11223.0 -2018-02-08 04:00:00,11128.0 -2018-02-08 05:00:00,11156.0 -2018-02-08 06:00:00,11378.0 -2018-02-08 07:00:00,12014.0 -2018-02-08 08:00:00,12988.0 -2018-02-08 09:00:00,13400.0 -2018-02-08 10:00:00,13492.0 -2018-02-08 11:00:00,13485.0 -2018-02-08 12:00:00,13427.0 -2018-02-08 13:00:00,13168.0 -2018-02-08 14:00:00,12967.0 -2018-02-08 15:00:00,12912.0 -2018-02-08 16:00:00,12866.0 -2018-02-08 17:00:00,12813.0 -2018-02-08 18:00:00,13010.0 -2018-02-08 19:00:00,13544.0 -2018-02-08 20:00:00,13643.0 -2018-02-08 21:00:00,13464.0 -2018-02-08 22:00:00,13180.0 -2018-02-08 23:00:00,12670.0 -2018-02-09 00:00:00,11974.0 -2018-02-07 01:00:00,11402.0 -2018-02-07 02:00:00,11013.0 -2018-02-07 03:00:00,10805.0 -2018-02-07 04:00:00,10756.0 -2018-02-07 05:00:00,10770.0 -2018-02-07 06:00:00,11040.0 -2018-02-07 07:00:00,11705.0 -2018-02-07 08:00:00,12700.0 -2018-02-07 09:00:00,13080.0 -2018-02-07 10:00:00,13186.0 -2018-02-07 11:00:00,13132.0 -2018-02-07 12:00:00,13155.0 -2018-02-07 13:00:00,13071.0 -2018-02-07 14:00:00,13022.0 -2018-02-07 15:00:00,12918.0 -2018-02-07 16:00:00,12884.0 -2018-02-07 17:00:00,12976.0 -2018-02-07 18:00:00,13251.0 -2018-02-07 19:00:00,13778.0 -2018-02-07 20:00:00,13877.0 -2018-02-07 21:00:00,13710.0 -2018-02-07 22:00:00,13471.0 -2018-02-07 23:00:00,12966.0 -2018-02-08 00:00:00,12298.0 -2018-02-06 01:00:00,11845.0 -2018-02-06 02:00:00,11498.0 -2018-02-06 03:00:00,11288.0 -2018-02-06 04:00:00,11248.0 -2018-02-06 05:00:00,11227.0 -2018-02-06 06:00:00,11523.0 -2018-02-06 07:00:00,12155.0 -2018-02-06 08:00:00,13108.0 -2018-02-06 09:00:00,13445.0 -2018-02-06 10:00:00,13374.0 -2018-02-06 11:00:00,13253.0 -2018-02-06 12:00:00,13158.0 -2018-02-06 13:00:00,13001.0 -2018-02-06 14:00:00,12828.0 -2018-02-06 15:00:00,12801.0 -2018-02-06 16:00:00,12742.0 -2018-02-06 17:00:00,12800.0 -2018-02-06 18:00:00,13037.0 -2018-02-06 19:00:00,13595.0 -2018-02-06 20:00:00,13675.0 -2018-02-06 21:00:00,13475.0 -2018-02-06 22:00:00,13177.0 -2018-02-06 23:00:00,12670.0 -2018-02-07 00:00:00,11988.0 -2018-02-05 01:00:00,11318.0 -2018-02-05 02:00:00,11098.0 -2018-02-05 03:00:00,10995.0 -2018-02-05 04:00:00,10974.0 -2018-02-05 05:00:00,11108.0 -2018-02-05 06:00:00,11349.0 -2018-02-05 07:00:00,12178.0 -2018-02-05 08:00:00,13223.0 -2018-02-05 09:00:00,13649.0 -2018-02-05 10:00:00,13775.0 -2018-02-05 11:00:00,13739.0 -2018-02-05 12:00:00,13599.0 -2018-02-05 13:00:00,13517.0 -2018-02-05 14:00:00,13612.0 -2018-02-05 15:00:00,13676.0 -2018-02-05 16:00:00,13598.0 -2018-02-05 17:00:00,13573.0 -2018-02-05 18:00:00,13772.0 -2018-02-05 19:00:00,14292.0 -2018-02-05 20:00:00,14292.0 -2018-02-05 21:00:00,14103.0 -2018-02-05 22:00:00,13781.0 -2018-02-05 23:00:00,13236.0 -2018-02-06 00:00:00,12478.0 -2018-02-04 01:00:00,10330.0 -2018-02-04 02:00:00,9937.0 -2018-02-04 03:00:00,9638.0 -2018-02-04 04:00:00,9484.0 -2018-02-04 05:00:00,9418.0 -2018-02-04 06:00:00,9519.0 -2018-02-04 07:00:00,9684.0 -2018-02-04 08:00:00,10091.0 -2018-02-04 09:00:00,10275.0 -2018-02-04 10:00:00,10643.0 -2018-02-04 11:00:00,11021.0 -2018-02-04 12:00:00,11310.0 -2018-02-04 13:00:00,11459.0 -2018-02-04 14:00:00,11598.0 -2018-02-04 15:00:00,11636.0 -2018-02-04 16:00:00,11610.0 -2018-02-04 17:00:00,11610.0 -2018-02-04 18:00:00,11750.0 -2018-02-04 19:00:00,12356.0 -2018-02-04 20:00:00,12541.0 -2018-02-04 21:00:00,12444.0 -2018-02-04 22:00:00,12323.0 -2018-02-04 23:00:00,12110.0 -2018-02-05 00:00:00,11715.0 -2018-02-03 01:00:00,11501.0 -2018-02-03 02:00:00,11038.0 -2018-02-03 03:00:00,10763.0 -2018-02-03 04:00:00,10509.0 -2018-02-03 05:00:00,10451.0 -2018-02-03 06:00:00,10492.0 -2018-02-03 07:00:00,10775.0 -2018-02-03 08:00:00,11188.0 -2018-02-03 09:00:00,11427.0 -2018-02-03 10:00:00,11689.0 -2018-02-03 11:00:00,11831.0 -2018-02-03 12:00:00,11881.0 -2018-02-03 13:00:00,11765.0 -2018-02-03 14:00:00,11643.0 -2018-02-03 15:00:00,11414.0 -2018-02-03 16:00:00,11253.0 -2018-02-03 17:00:00,11122.0 -2018-02-03 18:00:00,11254.0 -2018-02-03 19:00:00,11768.0 -2018-02-03 20:00:00,11845.0 -2018-02-03 21:00:00,11705.0 -2018-02-03 22:00:00,11522.0 -2018-02-03 23:00:00,11280.0 -2018-02-04 00:00:00,10833.0 -2018-02-02 01:00:00,11743.0 -2018-02-02 02:00:00,11444.0 -2018-02-02 03:00:00,11265.0 -2018-02-02 04:00:00,11140.0 -2018-02-02 05:00:00,11173.0 -2018-02-02 06:00:00,11408.0 -2018-02-02 07:00:00,12073.0 -2018-02-02 08:00:00,13050.0 -2018-02-02 09:00:00,13456.0 -2018-02-02 10:00:00,13509.0 -2018-02-02 11:00:00,13435.0 -2018-02-02 12:00:00,13414.0 -2018-02-02 13:00:00,13263.0 -2018-02-02 14:00:00,13099.0 -2018-02-02 15:00:00,12895.0 -2018-02-02 16:00:00,12719.0 -2018-02-02 17:00:00,12613.0 -2018-02-02 18:00:00,12852.0 -2018-02-02 19:00:00,13495.0 -2018-02-02 20:00:00,13527.0 -2018-02-02 21:00:00,13341.0 -2018-02-02 22:00:00,13102.0 -2018-02-02 23:00:00,12708.0 -2018-02-03 00:00:00,12069.0 -2018-02-01 01:00:00,10355.0 -2018-02-01 02:00:00,9978.0 -2018-02-01 03:00:00,9803.0 -2018-02-01 04:00:00,9686.0 -2018-02-01 05:00:00,9720.0 -2018-02-01 06:00:00,10014.0 -2018-02-01 07:00:00,10772.0 -2018-02-01 08:00:00,11874.0 -2018-02-01 09:00:00,12451.0 -2018-02-01 10:00:00,12719.0 -2018-02-01 11:00:00,12582.0 -2018-02-01 12:00:00,12664.0 -2018-02-01 13:00:00,12623.0 -2018-02-01 14:00:00,12603.0 -2018-02-01 15:00:00,12553.0 -2018-02-01 16:00:00,12522.0 -2018-02-01 17:00:00,12554.0 -2018-02-01 18:00:00,12927.0 -2018-02-01 19:00:00,13665.0 -2018-02-01 20:00:00,13820.0 -2018-02-01 21:00:00,13700.0 -2018-02-01 22:00:00,13436.0 -2018-02-01 23:00:00,12994.0 -2018-02-02 00:00:00,12286.0 -2018-01-31 01:00:00,10986.0 -2018-01-31 02:00:00,10603.0 -2018-01-31 03:00:00,10346.0 -2018-01-31 04:00:00,10183.0 -2018-01-31 05:00:00,10141.0 -2018-01-31 06:00:00,10400.0 -2018-01-31 07:00:00,11098.0 -2018-01-31 08:00:00,12151.0 -2018-01-31 09:00:00,12519.0 -2018-01-31 10:00:00,12494.0 -2018-01-31 11:00:00,12293.0 -2018-01-31 12:00:00,12322.0 -2018-01-31 13:00:00,12256.0 -2018-01-31 14:00:00,12132.0 -2018-01-31 15:00:00,12062.0 -2018-01-31 16:00:00,11975.0 -2018-01-31 17:00:00,11923.0 -2018-01-31 18:00:00,12144.0 -2018-01-31 19:00:00,12665.0 -2018-01-31 20:00:00,12597.0 -2018-01-31 21:00:00,12379.0 -2018-01-31 22:00:00,12114.0 -2018-01-31 23:00:00,11614.0 -2018-02-01 00:00:00,10975.0 -2018-01-30 01:00:00,11111.0 -2018-01-30 02:00:00,10802.0 -2018-01-30 03:00:00,10624.0 -2018-01-30 04:00:00,10509.0 -2018-01-30 05:00:00,10536.0 -2018-01-30 06:00:00,10833.0 -2018-01-30 07:00:00,11577.0 -2018-01-30 08:00:00,12643.0 -2018-01-30 09:00:00,13066.0 -2018-01-30 10:00:00,12949.0 -2018-01-30 11:00:00,12809.0 -2018-01-30 12:00:00,12749.0 -2018-01-30 13:00:00,12650.0 -2018-01-30 14:00:00,12445.0 -2018-01-30 15:00:00,12375.0 -2018-01-30 16:00:00,12399.0 -2018-01-30 17:00:00,12466.0 -2018-01-30 18:00:00,12814.0 -2018-01-30 19:00:00,13373.0 -2018-01-30 20:00:00,13268.0 -2018-01-30 21:00:00,13110.0 -2018-01-30 22:00:00,12771.0 -2018-01-30 23:00:00,12282.0 -2018-01-31 00:00:00,11646.0 -2018-01-29 01:00:00,9728.0 -2018-01-29 02:00:00,9499.0 -2018-01-29 03:00:00,9425.0 -2018-01-29 04:00:00,9377.0 -2018-01-29 05:00:00,9474.0 -2018-01-29 06:00:00,9785.0 -2018-01-29 07:00:00,10648.0 -2018-01-29 08:00:00,11785.0 -2018-01-29 09:00:00,12434.0 -2018-01-29 10:00:00,12545.0 -2018-01-29 11:00:00,12684.0 -2018-01-29 12:00:00,12780.0 -2018-01-29 13:00:00,12742.0 -2018-01-29 14:00:00,12670.0 -2018-01-29 15:00:00,12642.0 -2018-01-29 16:00:00,12606.0 -2018-01-29 17:00:00,12591.0 -2018-01-29 18:00:00,12795.0 -2018-01-29 19:00:00,13321.0 -2018-01-29 20:00:00,13247.0 -2018-01-29 21:00:00,13058.0 -2018-01-29 22:00:00,12794.0 -2018-01-29 23:00:00,12341.0 -2018-01-30 00:00:00,11699.0 -2018-01-28 01:00:00,9405.0 -2018-01-28 02:00:00,9027.0 -2018-01-28 03:00:00,8857.0 -2018-01-28 04:00:00,8796.0 -2018-01-28 05:00:00,8730.0 -2018-01-28 06:00:00,8838.0 -2018-01-28 07:00:00,8982.0 -2018-01-28 08:00:00,9331.0 -2018-01-28 09:00:00,9359.0 -2018-01-28 10:00:00,9504.0 -2018-01-28 11:00:00,9562.0 -2018-01-28 12:00:00,9619.0 -2018-01-28 13:00:00,9648.0 -2018-01-28 14:00:00,9693.0 -2018-01-28 15:00:00,9765.0 -2018-01-28 16:00:00,9938.0 -2018-01-28 17:00:00,10139.0 -2018-01-28 18:00:00,10555.0 -2018-01-28 19:00:00,11119.0 -2018-01-28 20:00:00,11175.0 -2018-01-28 21:00:00,11113.0 -2018-01-28 22:00:00,10938.0 -2018-01-28 23:00:00,10605.0 -2018-01-29 00:00:00,10201.0 -2018-01-27 01:00:00,9836.0 -2018-01-27 02:00:00,9417.0 -2018-01-27 03:00:00,9124.0 -2018-01-27 04:00:00,8901.0 -2018-01-27 05:00:00,8757.0 -2018-01-27 06:00:00,8852.0 -2018-01-27 07:00:00,9070.0 -2018-01-27 08:00:00,9549.0 -2018-01-27 09:00:00,9825.0 -2018-01-27 10:00:00,10054.0 -2018-01-27 11:00:00,10130.0 -2018-01-27 12:00:00,10180.0 -2018-01-27 13:00:00,10025.0 -2018-01-27 14:00:00,9907.0 -2018-01-27 15:00:00,9671.0 -2018-01-27 16:00:00,9576.0 -2018-01-27 17:00:00,9532.0 -2018-01-27 18:00:00,9770.0 -2018-01-27 19:00:00,10489.0 -2018-01-27 20:00:00,10619.0 -2018-01-27 21:00:00,10540.0 -2018-01-27 22:00:00,10401.0 -2018-01-27 23:00:00,10191.0 -2018-01-28 00:00:00,9798.0 -2018-01-26 01:00:00,10334.0 -2018-01-26 02:00:00,9956.0 -2018-01-26 03:00:00,9721.0 -2018-01-26 04:00:00,9587.0 -2018-01-26 05:00:00,9562.0 -2018-01-26 06:00:00,9810.0 -2018-01-26 07:00:00,10480.0 -2018-01-26 08:00:00,11508.0 -2018-01-26 09:00:00,11868.0 -2018-01-26 10:00:00,11846.0 -2018-01-26 11:00:00,11777.0 -2018-01-26 12:00:00,11746.0 -2018-01-26 13:00:00,11624.0 -2018-01-26 14:00:00,11550.0 -2018-01-26 15:00:00,11544.0 -2018-01-26 16:00:00,11510.0 -2018-01-26 17:00:00,11392.0 -2018-01-26 18:00:00,11621.0 -2018-01-26 19:00:00,12087.0 -2018-01-26 20:00:00,11923.0 -2018-01-26 21:00:00,11711.0 -2018-01-26 22:00:00,11425.0 -2018-01-26 23:00:00,11000.0 -2018-01-27 00:00:00,10428.0 -2018-01-25 01:00:00,10742.0 -2018-01-25 02:00:00,10359.0 -2018-01-25 03:00:00,10161.0 -2018-01-25 04:00:00,10057.0 -2018-01-25 05:00:00,10036.0 -2018-01-25 06:00:00,10310.0 -2018-01-25 07:00:00,11010.0 -2018-01-25 08:00:00,12064.0 -2018-01-25 09:00:00,12515.0 -2018-01-25 10:00:00,12491.0 -2018-01-25 11:00:00,12371.0 -2018-01-25 12:00:00,12243.0 -2018-01-25 13:00:00,12086.0 -2018-01-25 14:00:00,11904.0 -2018-01-25 15:00:00,11814.0 -2018-01-25 16:00:00,11660.0 -2018-01-25 17:00:00,11591.0 -2018-01-25 18:00:00,11812.0 -2018-01-25 19:00:00,12472.0 -2018-01-25 20:00:00,12459.0 -2018-01-25 21:00:00,12332.0 -2018-01-25 22:00:00,12061.0 -2018-01-25 23:00:00,11622.0 -2018-01-26 00:00:00,10935.0 -2018-01-24 01:00:00,10523.0 -2018-01-24 02:00:00,10156.0 -2018-01-24 03:00:00,9945.0 -2018-01-24 04:00:00,9804.0 -2018-01-24 05:00:00,9787.0 -2018-01-24 06:00:00,10080.0 -2018-01-24 07:00:00,10806.0 -2018-01-24 08:00:00,11844.0 -2018-01-24 09:00:00,12397.0 -2018-01-24 10:00:00,12467.0 -2018-01-24 11:00:00,12522.0 -2018-01-24 12:00:00,12620.0 -2018-01-24 13:00:00,12631.0 -2018-01-24 14:00:00,12617.0 -2018-01-24 15:00:00,12675.0 -2018-01-24 16:00:00,12545.0 -2018-01-24 17:00:00,12470.0 -2018-01-24 18:00:00,12775.0 -2018-01-24 19:00:00,13194.0 -2018-01-24 20:00:00,13037.0 -2018-01-24 21:00:00,12869.0 -2018-01-24 22:00:00,12542.0 -2018-01-24 23:00:00,12072.0 -2018-01-25 00:00:00,11315.0 -2018-01-23 01:00:00,9848.0 -2018-01-23 02:00:00,9512.0 -2018-01-23 03:00:00,9321.0 -2018-01-23 04:00:00,9226.0 -2018-01-23 05:00:00,9263.0 -2018-01-23 06:00:00,9596.0 -2018-01-23 07:00:00,10350.0 -2018-01-23 08:00:00,11495.0 -2018-01-23 09:00:00,12108.0 -2018-01-23 10:00:00,12210.0 -2018-01-23 11:00:00,12337.0 -2018-01-23 12:00:00,12491.0 -2018-01-23 13:00:00,12499.0 -2018-01-23 14:00:00,12485.0 -2018-01-23 15:00:00,12517.0 -2018-01-23 16:00:00,12445.0 -2018-01-23 17:00:00,12450.0 -2018-01-23 18:00:00,12698.0 -2018-01-23 19:00:00,13078.0 -2018-01-23 20:00:00,12911.0 -2018-01-23 21:00:00,12638.0 -2018-01-23 22:00:00,12358.0 -2018-01-23 23:00:00,11845.0 -2018-01-24 00:00:00,11177.0 -2018-01-22 01:00:00,9313.0 -2018-01-22 02:00:00,9022.0 -2018-01-22 03:00:00,8900.0 -2018-01-22 04:00:00,8788.0 -2018-01-22 05:00:00,8904.0 -2018-01-22 06:00:00,9173.0 -2018-01-22 07:00:00,9979.0 -2018-01-22 08:00:00,10990.0 -2018-01-22 09:00:00,11673.0 -2018-01-22 10:00:00,11697.0 -2018-01-22 11:00:00,11711.0 -2018-01-22 12:00:00,11779.0 -2018-01-22 13:00:00,11802.0 -2018-01-22 14:00:00,11751.0 -2018-01-22 15:00:00,11699.0 -2018-01-22 16:00:00,11449.0 -2018-01-22 17:00:00,11288.0 -2018-01-22 18:00:00,11536.0 -2018-01-22 19:00:00,12058.0 -2018-01-22 20:00:00,11968.0 -2018-01-22 21:00:00,11792.0 -2018-01-22 22:00:00,11555.0 -2018-01-22 23:00:00,11050.0 -2018-01-23 00:00:00,10434.0 -2018-01-21 01:00:00,9446.0 -2018-01-21 02:00:00,9108.0 -2018-01-21 03:00:00,8875.0 -2018-01-21 04:00:00,8706.0 -2018-01-21 05:00:00,8674.0 -2018-01-21 06:00:00,8669.0 -2018-01-21 07:00:00,8876.0 -2018-01-21 08:00:00,9117.0 -2018-01-21 09:00:00,9283.0 -2018-01-21 10:00:00,9494.0 -2018-01-21 11:00:00,9741.0 -2018-01-21 12:00:00,9916.0 -2018-01-21 13:00:00,10005.0 -2018-01-21 14:00:00,10044.0 -2018-01-21 15:00:00,10007.0 -2018-01-21 16:00:00,10087.0 -2018-01-21 17:00:00,10172.0 -2018-01-21 18:00:00,10550.0 -2018-01-21 19:00:00,11060.0 -2018-01-21 20:00:00,11011.0 -2018-01-21 21:00:00,10910.0 -2018-01-21 22:00:00,10621.0 -2018-01-21 23:00:00,10263.0 -2018-01-22 00:00:00,9769.0 -2018-01-20 01:00:00,10423.0 -2018-01-20 02:00:00,10067.0 -2018-01-20 03:00:00,9756.0 -2018-01-20 04:00:00,9533.0 -2018-01-20 05:00:00,9498.0 -2018-01-20 06:00:00,9527.0 -2018-01-20 07:00:00,9895.0 -2018-01-20 08:00:00,10316.0 -2018-01-20 09:00:00,10565.0 -2018-01-20 10:00:00,10681.0 -2018-01-20 11:00:00,10698.0 -2018-01-20 12:00:00,10682.0 -2018-01-20 13:00:00,10505.0 -2018-01-20 14:00:00,10357.0 -2018-01-20 15:00:00,10077.0 -2018-01-20 16:00:00,9976.0 -2018-01-20 17:00:00,9917.0 -2018-01-20 18:00:00,10296.0 -2018-01-20 19:00:00,10962.0 -2018-01-20 20:00:00,10997.0 -2018-01-20 21:00:00,10849.0 -2018-01-20 22:00:00,10657.0 -2018-01-20 23:00:00,10377.0 -2018-01-21 00:00:00,9920.0 -2018-01-19 01:00:00,11099.0 -2018-01-19 02:00:00,10726.0 -2018-01-19 03:00:00,10482.0 -2018-01-19 04:00:00,10362.0 -2018-01-19 05:00:00,10375.0 -2018-01-19 06:00:00,10604.0 -2018-01-19 07:00:00,11245.0 -2018-01-19 08:00:00,12284.0 -2018-01-19 09:00:00,12667.0 -2018-01-19 10:00:00,12586.0 -2018-01-19 11:00:00,12502.0 -2018-01-19 12:00:00,12432.0 -2018-01-19 13:00:00,12291.0 -2018-01-19 14:00:00,12148.0 -2018-01-19 15:00:00,12111.0 -2018-01-19 16:00:00,11961.0 -2018-01-19 17:00:00,11851.0 -2018-01-19 18:00:00,12244.0 -2018-01-19 19:00:00,12851.0 -2018-01-19 20:00:00,12729.0 -2018-01-19 21:00:00,12444.0 -2018-01-19 22:00:00,12132.0 -2018-01-19 23:00:00,11692.0 -2018-01-20 00:00:00,11073.0 -2018-01-18 01:00:00,11968.0 -2018-01-18 02:00:00,11601.0 -2018-01-18 03:00:00,11363.0 -2018-01-18 04:00:00,11239.0 -2018-01-18 05:00:00,11195.0 -2018-01-18 06:00:00,11476.0 -2018-01-18 07:00:00,12074.0 -2018-01-18 08:00:00,13134.0 -2018-01-18 09:00:00,13549.0 -2018-01-18 10:00:00,13483.0 -2018-01-18 11:00:00,13353.0 -2018-01-18 12:00:00,13253.0 -2018-01-18 13:00:00,13018.0 -2018-01-18 14:00:00,12850.0 -2018-01-18 15:00:00,12734.0 -2018-01-18 16:00:00,12588.0 -2018-01-18 17:00:00,12527.0 -2018-01-18 18:00:00,12846.0 -2018-01-18 19:00:00,13572.0 -2018-01-18 20:00:00,13496.0 -2018-01-18 21:00:00,13306.0 -2018-01-18 22:00:00,13001.0 -2018-01-18 23:00:00,12454.0 -2018-01-19 00:00:00,11745.0 -2018-01-17 01:00:00,11905.0 -2018-01-17 02:00:00,11578.0 -2018-01-17 03:00:00,11368.0 -2018-01-17 04:00:00,11326.0 -2018-01-17 05:00:00,11340.0 -2018-01-17 06:00:00,11593.0 -2018-01-17 07:00:00,12262.0 -2018-01-17 08:00:00,13351.0 -2018-01-17 09:00:00,13864.0 -2018-01-17 10:00:00,13798.0 -2018-01-17 11:00:00,13648.0 -2018-01-17 12:00:00,13572.0 -2018-01-17 13:00:00,13410.0 -2018-01-17 14:00:00,13279.0 -2018-01-17 15:00:00,13218.0 -2018-01-17 16:00:00,13098.0 -2018-01-17 17:00:00,13085.0 -2018-01-17 18:00:00,13484.0 -2018-01-17 19:00:00,14257.0 -2018-01-17 20:00:00,14244.0 -2018-01-17 21:00:00,14128.0 -2018-01-17 22:00:00,13848.0 -2018-01-17 23:00:00,13298.0 -2018-01-18 00:00:00,12590.0 -2018-01-16 01:00:00,12073.0 -2018-01-16 02:00:00,11735.0 -2018-01-16 03:00:00,11559.0 -2018-01-16 04:00:00,11475.0 -2018-01-16 05:00:00,11506.0 -2018-01-16 06:00:00,11684.0 -2018-01-16 07:00:00,12285.0 -2018-01-16 08:00:00,13219.0 -2018-01-16 09:00:00,13690.0 -2018-01-16 10:00:00,13687.0 -2018-01-16 11:00:00,13603.0 -2018-01-16 12:00:00,13511.0 -2018-01-16 13:00:00,13355.0 -2018-01-16 14:00:00,13110.0 -2018-01-16 15:00:00,13080.0 -2018-01-16 16:00:00,12958.0 -2018-01-16 17:00:00,12918.0 -2018-01-16 18:00:00,13319.0 -2018-01-16 19:00:00,14023.0 -2018-01-16 20:00:00,13992.0 -2018-01-16 21:00:00,13848.0 -2018-01-16 22:00:00,13586.0 -2018-01-16 23:00:00,13124.0 -2018-01-17 00:00:00,12487.0 -2018-01-15 01:00:00,11433.0 -2018-01-15 02:00:00,11170.0 -2018-01-15 03:00:00,10984.0 -2018-01-15 04:00:00,10789.0 -2018-01-15 05:00:00,10837.0 -2018-01-15 06:00:00,11060.0 -2018-01-15 07:00:00,11497.0 -2018-01-15 08:00:00,12318.0 -2018-01-15 09:00:00,12763.0 -2018-01-15 10:00:00,12967.0 -2018-01-15 11:00:00,13168.0 -2018-01-15 12:00:00,13137.0 -2018-01-15 13:00:00,13149.0 -2018-01-15 14:00:00,13190.0 -2018-01-15 15:00:00,13298.0 -2018-01-15 16:00:00,13310.0 -2018-01-15 17:00:00,13342.0 -2018-01-15 18:00:00,13667.0 -2018-01-15 19:00:00,14304.0 -2018-01-15 20:00:00,14191.0 -2018-01-15 21:00:00,14006.0 -2018-01-15 22:00:00,13756.0 -2018-01-15 23:00:00,13260.0 -2018-01-16 00:00:00,12642.0 -2018-01-14 01:00:00,11673.0 -2018-01-14 02:00:00,11383.0 -2018-01-14 03:00:00,11200.0 -2018-01-14 04:00:00,11063.0 -2018-01-14 05:00:00,11014.0 -2018-01-14 06:00:00,11049.0 -2018-01-14 07:00:00,11291.0 -2018-01-14 08:00:00,11567.0 -2018-01-14 09:00:00,11671.0 -2018-01-14 10:00:00,11656.0 -2018-01-14 11:00:00,11727.0 -2018-01-14 12:00:00,11812.0 -2018-01-14 13:00:00,11762.0 -2018-01-14 14:00:00,11746.0 -2018-01-14 15:00:00,11699.0 -2018-01-14 16:00:00,11778.0 -2018-01-14 17:00:00,11918.0 -2018-01-14 18:00:00,12467.0 -2018-01-14 19:00:00,12986.0 -2018-01-14 20:00:00,13004.0 -2018-01-14 21:00:00,12966.0 -2018-01-14 22:00:00,12738.0 -2018-01-14 23:00:00,12423.0 -2018-01-15 00:00:00,11942.0 -2018-01-13 01:00:00,11805.0 -2018-01-13 02:00:00,11415.0 -2018-01-13 03:00:00,11171.0 -2018-01-13 04:00:00,11024.0 -2018-01-13 05:00:00,10992.0 -2018-01-13 06:00:00,11143.0 -2018-01-13 07:00:00,11467.0 -2018-01-13 08:00:00,11884.0 -2018-01-13 09:00:00,12150.0 -2018-01-13 10:00:00,12231.0 -2018-01-13 11:00:00,12245.0 -2018-01-13 12:00:00,12191.0 -2018-01-13 13:00:00,12210.0 -2018-01-13 14:00:00,12077.0 -2018-01-13 15:00:00,11848.0 -2018-01-13 16:00:00,11714.0 -2018-01-13 17:00:00,11765.0 -2018-01-13 18:00:00,12172.0 -2018-01-13 19:00:00,12938.0 -2018-01-13 20:00:00,12928.0 -2018-01-13 21:00:00,12830.0 -2018-01-13 22:00:00,12706.0 -2018-01-13 23:00:00,12483.0 -2018-01-14 00:00:00,12113.0 -2018-01-12 01:00:00,10608.0 -2018-01-12 02:00:00,10278.0 -2018-01-12 03:00:00,10093.0 -2018-01-12 04:00:00,10058.0 -2018-01-12 05:00:00,10071.0 -2018-01-12 06:00:00,10345.0 -2018-01-12 07:00:00,11055.0 -2018-01-12 08:00:00,12101.0 -2018-01-12 09:00:00,12762.0 -2018-01-12 10:00:00,12967.0 -2018-01-12 11:00:00,13109.0 -2018-01-12 12:00:00,13232.0 -2018-01-12 13:00:00,13223.0 -2018-01-12 14:00:00,13221.0 -2018-01-12 15:00:00,13264.0 -2018-01-12 16:00:00,13175.0 -2018-01-12 17:00:00,13115.0 -2018-01-12 18:00:00,13425.0 -2018-01-12 19:00:00,13868.0 -2018-01-12 20:00:00,13749.0 -2018-01-12 21:00:00,13550.0 -2018-01-12 22:00:00,13321.0 -2018-01-12 23:00:00,12985.0 -2018-01-13 00:00:00,12391.0 -2018-01-11 01:00:00,9886.0 -2018-01-11 02:00:00,9427.0 -2018-01-11 03:00:00,9110.0 -2018-01-11 04:00:00,8947.0 -2018-01-11 05:00:00,8919.0 -2018-01-11 06:00:00,9125.0 -2018-01-11 07:00:00,9760.0 -2018-01-11 08:00:00,10694.0 -2018-01-11 09:00:00,11334.0 -2018-01-11 10:00:00,11396.0 -2018-01-11 11:00:00,11479.0 -2018-01-11 12:00:00,11528.0 -2018-01-11 13:00:00,11528.0 -2018-01-11 14:00:00,11527.0 -2018-01-11 15:00:00,11555.0 -2018-01-11 16:00:00,11470.0 -2018-01-11 17:00:00,11521.0 -2018-01-11 18:00:00,11955.0 -2018-01-11 19:00:00,12264.0 -2018-01-11 20:00:00,12242.0 -2018-01-11 21:00:00,12173.0 -2018-01-11 22:00:00,12074.0 -2018-01-11 23:00:00,11750.0 -2018-01-12 00:00:00,11155.0 -2018-01-10 01:00:00,10814.0 -2018-01-10 02:00:00,10372.0 -2018-01-10 03:00:00,10134.0 -2018-01-10 04:00:00,9995.0 -2018-01-10 05:00:00,9945.0 -2018-01-10 06:00:00,10193.0 -2018-01-10 07:00:00,10793.0 -2018-01-10 08:00:00,11782.0 -2018-01-10 09:00:00,12305.0 -2018-01-10 10:00:00,12317.0 -2018-01-10 11:00:00,12357.0 -2018-01-10 12:00:00,12408.0 -2018-01-10 13:00:00,12350.0 -2018-01-10 14:00:00,12278.0 -2018-01-10 15:00:00,12219.0 -2018-01-10 16:00:00,12134.0 -2018-01-10 17:00:00,12086.0 -2018-01-10 18:00:00,12429.0 -2018-01-10 19:00:00,12673.0 -2018-01-10 20:00:00,12405.0 -2018-01-10 21:00:00,12141.0 -2018-01-10 22:00:00,11797.0 -2018-01-10 23:00:00,11256.0 -2018-01-11 00:00:00,10546.0 -2018-01-09 01:00:00,10891.0 -2018-01-09 02:00:00,10452.0 -2018-01-09 03:00:00,10180.0 -2018-01-09 04:00:00,10039.0 -2018-01-09 05:00:00,10075.0 -2018-01-09 06:00:00,10362.0 -2018-01-09 07:00:00,11023.0 -2018-01-09 08:00:00,12075.0 -2018-01-09 09:00:00,12602.0 -2018-01-09 10:00:00,12597.0 -2018-01-09 11:00:00,12636.0 -2018-01-09 12:00:00,12602.0 -2018-01-09 13:00:00,12450.0 -2018-01-09 14:00:00,12302.0 -2018-01-09 15:00:00,12236.0 -2018-01-09 16:00:00,12138.0 -2018-01-09 17:00:00,12162.0 -2018-01-09 18:00:00,12663.0 -2018-01-09 19:00:00,13189.0 -2018-01-09 20:00:00,13064.0 -2018-01-09 21:00:00,12876.0 -2018-01-09 22:00:00,12610.0 -2018-01-09 23:00:00,12137.0 -2018-01-10 00:00:00,11442.0 -2018-01-08 01:00:00,10866.0 -2018-01-08 02:00:00,10525.0 -2018-01-08 03:00:00,10254.0 -2018-01-08 04:00:00,10060.0 -2018-01-08 05:00:00,10099.0 -2018-01-08 06:00:00,10312.0 -2018-01-08 07:00:00,10977.0 -2018-01-08 08:00:00,11998.0 -2018-01-08 09:00:00,12515.0 -2018-01-08 10:00:00,12514.0 -2018-01-08 11:00:00,12522.0 -2018-01-08 12:00:00,12445.0 -2018-01-08 13:00:00,12315.0 -2018-01-08 14:00:00,12221.0 -2018-01-08 15:00:00,12175.0 -2018-01-08 16:00:00,12032.0 -2018-01-08 17:00:00,11992.0 -2018-01-08 18:00:00,12469.0 -2018-01-08 19:00:00,13199.0 -2018-01-08 20:00:00,13105.0 -2018-01-08 21:00:00,12981.0 -2018-01-08 22:00:00,12733.0 -2018-01-08 23:00:00,12240.0 -2018-01-09 00:00:00,11540.0 -2018-01-07 01:00:00,12231.0 -2018-01-07 02:00:00,11820.0 -2018-01-07 03:00:00,11560.0 -2018-01-07 04:00:00,11310.0 -2018-01-07 05:00:00,11230.0 -2018-01-07 06:00:00,11187.0 -2018-01-07 07:00:00,11306.0 -2018-01-07 08:00:00,11417.0 -2018-01-07 09:00:00,11507.0 -2018-01-07 10:00:00,11727.0 -2018-01-07 11:00:00,11890.0 -2018-01-07 12:00:00,12053.0 -2018-01-07 13:00:00,12111.0 -2018-01-07 14:00:00,12066.0 -2018-01-07 15:00:00,12002.0 -2018-01-07 16:00:00,11988.0 -2018-01-07 17:00:00,11992.0 -2018-01-07 18:00:00,12483.0 -2018-01-07 19:00:00,12924.0 -2018-01-07 20:00:00,12828.0 -2018-01-07 21:00:00,12607.0 -2018-01-07 22:00:00,12361.0 -2018-01-07 23:00:00,11982.0 -2018-01-08 00:00:00,11409.0 -2018-01-06 01:00:00,12846.0 -2018-01-06 02:00:00,12423.0 -2018-01-06 03:00:00,12167.0 -2018-01-06 04:00:00,12013.0 -2018-01-06 05:00:00,11970.0 -2018-01-06 06:00:00,12032.0 -2018-01-06 07:00:00,12306.0 -2018-01-06 08:00:00,12691.0 -2018-01-06 09:00:00,12900.0 -2018-01-06 10:00:00,13008.0 -2018-01-06 11:00:00,13060.0 -2018-01-06 12:00:00,12967.0 -2018-01-06 13:00:00,12878.0 -2018-01-06 14:00:00,12630.0 -2018-01-06 15:00:00,12437.0 -2018-01-06 16:00:00,12235.0 -2018-01-06 17:00:00,12289.0 -2018-01-06 18:00:00,12814.0 -2018-01-06 19:00:00,13630.0 -2018-01-06 20:00:00,13666.0 -2018-01-06 21:00:00,13612.0 -2018-01-06 22:00:00,13461.0 -2018-01-06 23:00:00,13217.0 -2018-01-07 00:00:00,12725.0 -2018-01-05 01:00:00,12806.0 -2018-01-05 02:00:00,12388.0 -2018-01-05 03:00:00,12167.0 -2018-01-05 04:00:00,12032.0 -2018-01-05 05:00:00,12057.0 -2018-01-05 06:00:00,12243.0 -2018-01-05 07:00:00,12795.0 -2018-01-05 08:00:00,13647.0 -2018-01-05 09:00:00,14065.0 -2018-01-05 10:00:00,14152.0 -2018-01-05 11:00:00,14135.0 -2018-01-05 12:00:00,14123.0 -2018-01-05 13:00:00,13998.0 -2018-01-05 14:00:00,13844.0 -2018-01-05 15:00:00,13765.0 -2018-01-05 16:00:00,13640.0 -2018-01-05 17:00:00,13628.0 -2018-01-05 18:00:00,14143.0 -2018-01-05 19:00:00,14785.0 -2018-01-05 20:00:00,14783.0 -2018-01-05 21:00:00,14628.0 -2018-01-05 22:00:00,14418.0 -2018-01-05 23:00:00,14072.0 -2018-01-06 00:00:00,13426.0 -2018-01-04 01:00:00,12649.0 -2018-01-04 02:00:00,12250.0 -2018-01-04 03:00:00,12010.0 -2018-01-04 04:00:00,11867.0 -2018-01-04 05:00:00,11890.0 -2018-01-04 06:00:00,12099.0 -2018-01-04 07:00:00,12669.0 -2018-01-04 08:00:00,13507.0 -2018-01-04 09:00:00,13939.0 -2018-01-04 10:00:00,14054.0 -2018-01-04 11:00:00,14022.0 -2018-01-04 12:00:00,14000.0 -2018-01-04 13:00:00,13915.0 -2018-01-04 14:00:00,13794.0 -2018-01-04 15:00:00,13721.0 -2018-01-04 16:00:00,13624.0 -2018-01-04 17:00:00,13607.0 -2018-01-04 18:00:00,14149.0 -2018-01-04 19:00:00,14906.0 -2018-01-04 20:00:00,14875.0 -2018-01-04 21:00:00,14735.0 -2018-01-04 22:00:00,14518.0 -2018-01-04 23:00:00,14027.0 -2018-01-05 00:00:00,13453.0 -2018-01-03 01:00:00,12995.0 -2018-01-03 02:00:00,12494.0 -2018-01-03 03:00:00,12135.0 -2018-01-03 04:00:00,11901.0 -2018-01-03 05:00:00,11787.0 -2018-01-03 06:00:00,11923.0 -2018-01-03 07:00:00,12395.0 -2018-01-03 08:00:00,13135.0 -2018-01-03 09:00:00,13575.0 -2018-01-03 10:00:00,13753.0 -2018-01-03 11:00:00,13858.0 -2018-01-03 12:00:00,13913.0 -2018-01-03 13:00:00,13847.0 -2018-01-03 14:00:00,13802.0 -2018-01-03 15:00:00,13739.0 -2018-01-03 16:00:00,13672.0 -2018-01-03 17:00:00,13744.0 -2018-01-03 18:00:00,14294.0 -2018-01-03 19:00:00,14887.0 -2018-01-03 20:00:00,14866.0 -2018-01-03 21:00:00,14595.0 -2018-01-03 22:00:00,14314.0 -2018-01-03 23:00:00,13880.0 -2018-01-04 00:00:00,13234.0 -2018-01-02 01:00:00,12315.0 -2018-01-02 02:00:00,11981.0 -2018-01-02 03:00:00,11805.0 -2018-01-02 04:00:00,11684.0 -2018-01-02 05:00:00,11728.0 -2018-01-02 06:00:00,11991.0 -2018-01-02 07:00:00,12559.0 -2018-01-02 08:00:00,13379.0 -2018-01-02 09:00:00,13854.0 -2018-01-02 10:00:00,14040.0 -2018-01-02 11:00:00,14179.0 -2018-01-02 12:00:00,14241.0 -2018-01-02 13:00:00,14204.0 -2018-01-02 14:00:00,14138.0 -2018-01-02 15:00:00,14078.0 -2018-01-02 16:00:00,13984.0 -2018-01-02 17:00:00,14011.0 -2018-01-02 18:00:00,14614.0 -2018-01-02 19:00:00,15405.0 -2018-01-02 20:00:00,15408.0 -2018-01-02 21:00:00,15246.0 -2018-01-02 22:00:00,14933.0 -2018-01-02 23:00:00,14437.0 -2018-01-03 00:00:00,13704.0 -2018-01-01 01:00:00,12263.0 -2018-01-01 02:00:00,11982.0 -2018-01-01 03:00:00,11747.0 -2018-01-01 04:00:00,11581.0 -2018-01-01 05:00:00,11565.0 -2018-01-01 06:00:00,11585.0 -2018-01-01 07:00:00,11773.0 -2018-01-01 08:00:00,11949.0 -2018-01-01 09:00:00,11940.0 -2018-01-01 10:00:00,11891.0 -2018-01-01 11:00:00,11979.0 -2018-01-01 12:00:00,12096.0 -2018-01-01 13:00:00,12155.0 -2018-01-01 14:00:00,12131.0 -2018-01-01 15:00:00,12064.0 -2018-01-01 16:00:00,12053.0 -2018-01-01 17:00:00,12183.0 -2018-01-01 18:00:00,12926.0 -2018-01-01 19:00:00,13809.0 -2018-01-01 20:00:00,13858.0 -2018-01-01 21:00:00,13758.0 -2018-01-01 22:00:00,13627.0 -2018-01-01 23:00:00,13336.0 -2018-01-02 00:00:00,12816.0 diff --git a/docker/images/pinot-thirdeye/config/pinot-quickstart/data/pageviews.csv b/docker/images/pinot-thirdeye/config/pinot-quickstart/data/pageviews.csv deleted file mode 100644 index ccba33d4efe8..000000000000 --- a/docker/images/pinot-thirdeye/config/pinot-quickstart/data/pageviews.csv +++ /dev/null @@ -1,67 +0,0 @@ -timestamp,country,device,pageviews -2011-01-01,us,android,1985 -2011-01-01,us,ios,985 -2011-01-01,in,android,2000 -2011-01-01,in,ios,909 -2011-01-01,cn,android,1983 -2011-01-01,cn,ios,1200 -2011-01-02,us,android,1785 -2011-01-02,us,ios,1085 -2011-01-02,in,android,2100 -2011-01-02,in,ios,919 -2011-01-02,cn,android,1883 -2011-01-02,cn,ios,1100 -2011-01-03,us,android,1885 -2011-01-03,us,ios,985 -2011-01-03,in,android,1990 -2011-01-03,in,ios,920 -2011-01-03,cn,android,1893 -2011-01-03,cn,ios,1200 -2011-01-04,us,android,1985 -2011-01-04,us,ios,885 -2011-01-04,in,android,1890 -2011-01-04,in,ios,820 -2011-01-04,cn,android,1993 -2011-01-04,cn,ios,1280 -2011-01-05,us,android,1890 -2011-01-05,us,ios,905 -2011-01-05,in,android,1890 -2011-01-05,in,ios,870 -2011-01-05,cn,android,1693 -2011-01-05,cn,ios,900 -2011-01-06,us,android,1690 -2011-01-06,us,ios,995 -2011-01-06,in,android,1790 -2011-01-06,in,ios,890 -2011-01-06,cn,android,1613 -2011-01-06,cn,ios,880 -2011-01-07,us,android,1785 -2011-01-07,us,ios,1085 -2011-01-07,in,android,2100 -2011-01-07,in,ios,919 -2011-01-07,cn,android,1883 -2011-01-07,cn,ios,1100 -2011-01-08,us,android,1995 -2011-01-08,us,ios,1085 -2011-01-08,in,android,950 -2011-01-08,in,ios,999 -2011-01-08,cn,android,1183 -2011-01-08,cn,ios,800 -2011-01-09,us,android,1990 -2011-01-09,us,ios,995 -2011-01-09,in,android,1790 -2011-01-09,in,ios,890 -2011-01-09,cn,android,1613 -2011-01-09,cn,ios,980 -2011-01-10,us,android,1690 -2011-01-10,us,ios,1095 -2011-01-10,in,android,1590 -2011-01-10,in,ios,790 -2011-01-10,cn,android,1713 -2011-01-10,cn,ios,999 -2011-01-11,us,android,1590 -2011-01-11,us,ios,1195 -2011-01-11,in,android,1890 -2011-01-11,in,ios,990 -2011-01-11,cn,android,1513 -2011-01-11,cn,ios,1300 \ No newline at end of file diff --git a/docker/images/pinot-thirdeye/config/pinot-quickstart/detector-config/anomaly-functions/alertFilter.properties b/docker/images/pinot-thirdeye/config/pinot-quickstart/detector-config/anomaly-functions/alertFilter.properties deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/docker/images/pinot-thirdeye/config/pinot-quickstart/detector-config/anomaly-functions/alertFilterAutotune.properties b/docker/images/pinot-thirdeye/config/pinot-quickstart/detector-config/anomaly-functions/alertFilterAutotune.properties deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/docker/images/pinot-thirdeye/config/pinot-quickstart/detector-config/anomaly-functions/anomalyClassifier.properties b/docker/images/pinot-thirdeye/config/pinot-quickstart/detector-config/anomaly-functions/anomalyClassifier.properties deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/docker/images/pinot-thirdeye/config/pinot-quickstart/detector-config/anomaly-functions/functions.properties b/docker/images/pinot-thirdeye/config/pinot-quickstart/detector-config/anomaly-functions/functions.properties deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/docker/images/pinot-thirdeye/config/pinot-quickstart/detector.yml b/docker/images/pinot-thirdeye/config/pinot-quickstart/detector.yml deleted file mode 100644 index 1ad3312dbf3d..000000000000 --- a/docker/images/pinot-thirdeye/config/pinot-quickstart/detector.yml +++ /dev/null @@ -1,127 +0,0 @@ -logging: - type: external -server: - requestLog: - type: external - type: default - rootPath: '/api/*' - applicationContextPath: / - adminContextPath: /admin - applicationConnectors: - - type: http - port: 1867 - adminConnectors: - - type: http - port: 1868 -alert: false -autoload: true -classifier: false -holidayEventsLoader: false -mockEventsLoader: true -monitor: true -pinotProxy: false -scheduler: true -worker: true -detectionPipeline: true -detectionAlert: true -dashboardHost: "http://localhost:1426" -id: 0 -alerterConfiguration: - smtpConfiguration: - smtpHost: "localhost" - smtpPort: 25 -# jiraConfiguration: -# jiraUser: -# jiraPassword: -# jiraUrl: -# jiraDefaultProject: -# jiraIssueTypeId: 19 -failureFromAddress: "thirdeye@localhost" -failureToAddress: "thirdeye@localhost" -phantomJsPath: "/usr/local/bin/jstf" -swagger: - resourcePackage: "org.apache.pinot.thirdeye.dashboard.resources,org.apache.pinot.thirdeye.dashboard.resources.v2,org.apache.pinot.thirdeye.anomaly.onboard" -holidayEventsLoaderConfiguration: - calendars: - - "en.australian#holiday@group.v.calendar.google.com" - - "en.austrian#holiday@group.v.calendar.google.com" - - "en.brazilian#holiday@group.v.calendar.google.com" - - "en.canadian#holiday@group.v.calendar.google.com" - - "en.china#holiday@group.v.calendar.google.com" - - "en.christian#holiday@group.v.calendar.google.com" - - "en.danish#holiday@group.v.calendar.google.com" - - "en.dutch#holiday@group.v.calendar.google.com" - - "en.finnish#holiday@group.v.calendar.google.com" - - "en.french#holiday@group.v.calendar.google.com" - - "en.german#holiday@group.v.calendar.google.com" - - "en.greek#holiday@group.v.calendar.google.com" - - "en.hong_kong#holiday@group.v.calendar.google.com" - - "en.indian#holiday@group.v.calendar.google.com" - - "en.indonesian#holiday@group.v.calendar.google.com" - - "en.irish#holiday@group.v.calendar.google.com" - - "en.islamic#holiday@group.v.calendar.google.com" - - "en.italian#holiday@group.v.calendar.google.com" - - "en.japanese#holiday@group.v.calendar.google.com" - - "en.jewish#holiday@group.v.calendar.google.com" - - "en.malaysia#holiday@group.v.calendar.google.com" - - "en.mexican#holiday@group.v.calendar.google.com" - - "en.new_zealand#holiday@group.v.calendar.google.com" - - "en.norwegian#holiday@group.v.calendar.google.com" - - "en.philippines#holiday@group.v.calendar.google.com" - - "en.polish#holiday@group.v.calendar.google.com" - - "en.portuguese#holiday@group.v.calendar.google.com" - - "en.russian#holiday@group.v.calendar.google.com" - - "en.singapore#holiday@group.v.calendar.google.com" - - "en.sa#holiday@group.v.calendar.google.com" - - "en.south_korea#holiday@group.v.calendar.google.com" - - "en.spain#holiday@group.v.calendar.google.com" - - "en.swedish#holiday@group.v.calendar.google.com" - - "en.taiwan#holiday@group.v.calendar.google.com" - - "en.thai#holiday@group.v.calendar.google.com" - - "en.uk#holiday@group.v.calendar.google.com" - - "en.usa#holiday@group.v.calendar.google.com" - - "en.vietnamese#holiday@group.v.calendar.google.com" - holidayLoadRange: 2592000000 - runFrequency: 7 -mockEventsLoaderConfiguration: - generators: - - type: HOLIDAY - arrivalType: exponential - arrivalMean: 86400000 - durationType: fixed - durationMean: 86400000 - seed: 0 - namePrefixes: [First, Second, Third, Last, Funky, Happy, Sad, Glorious, Jolly, Unity, Pinot's] - nameSuffixes: [day, day, days, celebration, rememberance, occurrence, moment] - - type: INFORMED - arrivalType: exponential - arrivalMean: 43200000 - durationType: exponential - durationMean: 3600000 - seed: 1 - namePrefixes: [Login, Web, Search, Catalog, Integration, Network, Backup, Ingress, Proxy, Failure, Pinot, ThirdEye] - nameSuffixes: [backend, frontend, v1.1, v1.2, v1.3, v2.0, v3, v4, v5, storage, topic, container, database] - - type: CM - arrivalType: exponential - arrivalMean: 21600000 - durationType: fixed - durationMean: 1800000 - seed: 2 - namePrefixes: [Database, Web, Search, Catalog, Integration, Network, Backup, Ingress, Proxy, Failure, Pinot, ThirdEye] - - type: CUSTOM - arrivalType: exponential - arrivalMean: 432000000 - durationType: exponential - durationMean: 86400000 - seed: 3 - namePrefixes: [Marketing, Onboarding, Vaction, Outreach, InDay] - nameSuffixes: [integration, campaign, meeting] - - type: LIX - arrivalType: exponential - arrivalMean: 259200000 - durationType: exponential - durationMean: 604800000 - seed: 4 - namePrefixes: [System, Model, Campaign, Welcome, Pinot, ThirdEye] - nameSuffixes: [tuning, bugfix, rollout, test] - diff --git a/docker/images/pinot-thirdeye/config/pinot-quickstart/persistence.yml b/docker/images/pinot-thirdeye/config/pinot-quickstart/persistence.yml deleted file mode 100644 index 991d18dcf07e..000000000000 --- a/docker/images/pinot-thirdeye/config/pinot-quickstart/persistence.yml +++ /dev/null @@ -1,5 +0,0 @@ -databaseConfiguration: - url: jdbc:h2:tcp://localhost/h2db - user: sa - password: sa - driver: org.h2.Driver diff --git a/docker/images/pinot-thirdeye/config/pinot-quickstart/rca.yml b/docker/images/pinot-thirdeye/config/pinot-quickstart/rca.yml deleted file mode 100644 index aecfd76a85fe..000000000000 --- a/docker/images/pinot-thirdeye/config/pinot-quickstart/rca.yml +++ /dev/null @@ -1,98 +0,0 @@ -frameworks: - identity: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.NullPipeline - - metricRelated: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.MetricMappingPipeline - - metricAnalysis: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.MetricAnalysisPipeline - - metricBreakdown: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.MetricBreakdownPipeline - - metricComponentAnalysis: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.MetricComponentAnalysisPipeline - properties: - excludeDimensions: ["environment", "continent"] - parallelism: 5 - k: 5 - - eventExperiment: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.ThirdEyeEventsPipeline - properties: - strategy: COMPOUND - k: 500 - eventType: LIX - - eventHoliday: - - outputName: METRIC_RELATED - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.MetricAnalysisPipeline - - - outputName: OUTPUT - inputNames: [INPUT, METRIC_RELATED] - className: org.apache.pinot.thirdeye.rootcause.impl.ThirdEyeEventsPipeline - properties: - strategy: COMPOUND - k: 500 - eventType: HOLIDAY - - eventCustom: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.ThirdEyeEventsPipeline - properties: - strategy: COMPOUND - k: 500 - eventType: CUSTOM - - eventAnomaly: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.AnomalyEventsPipeline - properties: - strategyClass: org.apache.pinot.thirdeye.rootcause.impl.AnomalyEventsPipeline$TimeRangeScoreStrategy - strategyProperties: - type: HYPERBOLA - k: 500 - - eventIssue: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.EmptyPipeline - - eventChange: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.ThirdEyeEventsPipeline - properties: - strategy: COMPOUND - k: 500 - eventType: CM - - eventDeployment: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.ThirdEyeEventsPipeline - properties: - strategy: COMPOUND - k: 500 - eventType: INFORMED - - eventAC: - - outputName: OUTPUT - inputNames: [INPUT] - className: org.apache.pinot.thirdeye.rootcause.impl.EmptyPipeline diff --git a/docker/images/pinot-thirdeye/docker-build-and-push.sh b/docker/images/pinot-thirdeye/docker-build-and-push.sh deleted file mode 100755 index 6b3895245faf..000000000000 --- a/docker/images/pinot-thirdeye/docker-build-and-push.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - - -sh docker-build.sh $@ -if [[ "$#" -gt 0 ]] -then - DOCKER_TAG=$1 -else - DOCKER_TAG="apachepinot/thirdeye:latest" -fi -echo "Trying to push docker image to ${DOCKER_TAG}" -docker push ${DOCKER_TAG} diff --git a/docker/images/pinot-thirdeye/docker-build.sh b/docker/images/pinot-thirdeye/docker-build.sh deleted file mode 100755 index 6e94608da5ca..000000000000 --- a/docker/images/pinot-thirdeye/docker-build.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - - -if [[ "$#" -gt 0 ]] -then - DOCKER_TAG=$1 -else - DOCKER_TAG="thirdeye:latest" - echo "Not specified a Docker Tag, using default tag: ${DOCKER_TAG}." -fi - -if [[ "$#" -gt 1 ]] -then - PINOT_BRANCH=$2 -else - PINOT_BRANCH=master - echo "Not specified a Pinot branch to build, using default branch: ${PINOT_BRANCH}." -fi - -if [[ "$#" -gt 2 ]] -then - PINOT_GIT_URL=$3 -else - PINOT_GIT_URL="https://github.com/apache/pinot.git" -fi - -echo "Trying to build Thirdeye docker image from Git URL: [ ${PINOT_GIT_URL} ] on branch: [ ${PINOT_BRANCH} ] and tag it as: [ ${DOCKER_TAG} ]." - -docker build -t ${DOCKER_TAG} --build-arg PINOT_BRANCH=${PINOT_BRANCH} --build-arg PINOT_GIT_URL=${PINOT_GIT_URL} -f Dockerfile . diff --git a/docker/images/pinot-thirdeye/docker-push.sh b/docker/images/pinot-thirdeye/docker-push.sh deleted file mode 100755 index a6baa9aca5b7..000000000000 --- a/docker/images/pinot-thirdeye/docker-push.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - - -if [[ "$#" -gt 0 ]] -then - DOCKER_TAG=$1 -else - DOCKER_TAG="apachepinot/thirdeye:latest" -fi -echo "Trying to push docker image to ${DOCKER_TAG}" -docker push ${DOCKER_TAG} diff --git a/docker/images/pinot/Dockerfile b/docker/images/pinot/Dockerfile index 499060ed7c7d..3378b6f6454e 100644 --- a/docker/images/pinot/Dockerfile +++ b/docker/images/pinot/Dockerfile @@ -16,16 +16,20 @@ # specific language governing permissions and limitations # under the License. # -ARG PINOT_BASE_IMAGE_TAG=openjdk11 +ARG PINOT_BASE_IMAGE_TAG=11-amazoncorretto FROM apachepinot/pinot-base-build:${PINOT_BASE_IMAGE_TAG} AS pinot_build_env LABEL MAINTAINER=dev@pinot.apache.org ARG PINOT_BRANCH=master -ARG KAFKA_VERSION=2.0 ARG JDK_VERSION=11 ARG PINOT_GIT_URL="https://github.com/apache/pinot.git" -RUN echo "Trying to build Pinot from [ ${PINOT_GIT_URL} ] on branch [ ${PINOT_BRANCH} ] with Kafka version [ ${KAFKA_VERSION} ]" +ARG CI=true + +RUN echo "Build Pinot based on image: apachepinot/pinot-base-build:${PINOT_BASE_IMAGE_TAG}" +RUN echo "Current build system CPU arch is [ $(uname -m) ]" + +RUN echo "Trying to build Pinot from [ ${PINOT_GIT_URL} ] on branch [ ${PINOT_BRANCH} ] and CI [ ${CI} ]" ENV PINOT_HOME=/opt/pinot ENV PINOT_BUILD_DIR=/opt/pinot-build ENV MAVEN_HOME /usr/share/maven @@ -35,6 +39,7 @@ RUN git clone ${PINOT_GIT_URL} ${PINOT_BUILD_DIR} && \ cd ${PINOT_BUILD_DIR} && \ git checkout ${PINOT_BRANCH} && \ mvn install package -DskipTests -Pbin-dist -Pbuild-shaded-jar -Djdk.version=${JDK_VERSION} -T1C && \ + rm -rf /root/.m2 && \ mkdir -p ${PINOT_HOME}/configs && \ mkdir -p ${PINOT_HOME}/data && \ cp -r build/* ${PINOT_HOME}/. && \ @@ -54,9 +59,8 @@ COPY bin ${PINOT_HOME}/bin COPY etc ${PINOT_HOME}/etc COPY examples ${PINOT_HOME}/examples -RUN wget -O ${PINOT_HOME}/etc/jmx_prometheus_javaagent/jmx_prometheus_javaagent-0.12.0.jar https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.12.0/jmx_prometheus_javaagent-0.12.0.jar -RUN wget -O ${PINOT_HOME}/etc/jmx_prometheus_javaagent/jmx_prometheus_javaagent-0.16.1.jar https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.16.1/jmx_prometheus_javaagent-0.16.1.jar && \ - ln -s ${PINOT_HOME}/etc/jmx_prometheus_javaagent/jmx_prometheus_javaagent-0.16.1.jar ${PINOT_HOME}/etc/jmx_prometheus_javaagent/jmx_prometheus_javaagent.jar +RUN wget -O ${PINOT_HOME}/etc/jmx_prometheus_javaagent/jmx_prometheus_javaagent-0.18.0.jar https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.18.0/jmx_prometheus_javaagent-0.18.0.jar && \ + ln -s ${PINOT_HOME}/etc/jmx_prometheus_javaagent/jmx_prometheus_javaagent-0.18.0.jar ${PINOT_HOME}/etc/jmx_prometheus_javaagent/jmx_prometheus_javaagent.jar # expose ports for controller/broker/server/admin EXPOSE 9000 8099 8098 8097 8096 diff --git a/docker/images/pinot/Dockerfile.build b/docker/images/pinot/Dockerfile.build new file mode 100644 index 000000000000..9da9ebbf6fa2 --- /dev/null +++ b/docker/images/pinot/Dockerfile.build @@ -0,0 +1,59 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +ARG PINOT_BASE_IMAGE_TAG=11-amazoncorretto +FROM apachepinot/pinot-base-build:${PINOT_BASE_IMAGE_TAG} AS pinot_build_env + +LABEL MAINTAINER=dev@pinot.apache.org + +ARG PINOT_BRANCH=master +ARG JDK_VERSION=11 +ARG PINOT_GIT_URL="https://github.com/apache/pinot.git" +ARG CI=true + +RUN echo "Build Pinot based on image: apachepinot/pinot-base-build:${PINOT_BASE_IMAGE_TAG}" +RUN echo "Current build system CPU arch is [ $(uname -m) ]" + +RUN echo "Trying to build Pinot from [ ${PINOT_GIT_URL} ] on branch [ ${PINOT_BRANCH} ] and CI [ ${CI} ]" +ENV PINOT_HOME=/opt/pinot +ENV PINOT_BUILD_DIR=/opt/pinot-build +ENV MAVEN_HOME /usr/share/maven +ENV MAVEN_CONFIG /opt/.m2 + +RUN git clone ${PINOT_GIT_URL} ${PINOT_BUILD_DIR} && \ + cd ${PINOT_BUILD_DIR} && \ + git checkout ${PINOT_BRANCH} && \ + mvn install package -DskipTests -Pbin-dist -Pbuild-shaded-jar -Djdk.version=${JDK_VERSION} -T1C --batch-mode -pl \!:pinot-connectors,\!:pinot-spark-common,\!:pinot-spark-common,\!:pinot-spark-2-connector,\!:pinot-spark-3-connector,\!:pinot-flink-connector,\!:pinot-compatibility-verifier,\!:pinot-perf,\!:pinot-integration-tests,\!:pinot-integration-test-base && \ + rm -rf /root/.m2 && \ + mkdir -p ${PINOT_HOME}/configs && \ + mkdir -p ${PINOT_HOME}/data && \ + cp -r build/* ${PINOT_HOME}/. && \ + chmod +x ${PINOT_HOME}/bin/*.sh + +ENV JAVA_OPTS="-Xms4G -Xmx4G -Dpinot.admin.system.exit=false" + +VOLUME ["${PINOT_HOME}/configs", "${PINOT_HOME}/data"] + +# expose ports for controller/broker/server/admin +EXPOSE 9000 8099 8098 8097 8096 + +WORKDIR ${PINOT_HOME} + +ENTRYPOINT ["./bin/pinot-admin.sh"] + +CMD ["-help"] diff --git a/docker/images/pinot/Dockerfile.package b/docker/images/pinot/Dockerfile.package new file mode 100644 index 000000000000..6e10fdc161ac --- /dev/null +++ b/docker/images/pinot/Dockerfile.package @@ -0,0 +1,45 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +ARG PINOT_BUILD_IMAGE_TAG=11-amazoncorretto +ARG PINOT_RUNTIME_IMAGE_TAG=${PINOT_BUILD_IMAGE_TAG} +FROM pinot-build:${PINOT_BUILD_IMAGE_TAG} AS pinot_build_dist +FROM apachepinot/pinot-base-runtime:${PINOT_RUNTIME_IMAGE_TAG} +LABEL MAINTAINER=dev@pinot.apache.org + +ENV PINOT_HOME=/opt/pinot +ENV JAVA_OPTS="-Xms4G -Xmx4G -Dpinot.admin.system.exit=false" + +VOLUME ["${PINOT_HOME}/configs", "${PINOT_HOME}/data"] + +COPY --from=pinot_build_dist ${PINOT_HOME} ${PINOT_HOME} +COPY bin ${PINOT_HOME}/bin +COPY etc ${PINOT_HOME}/etc +COPY examples ${PINOT_HOME}/examples + +RUN wget -O ${PINOT_HOME}/etc/jmx_prometheus_javaagent/jmx_prometheus_javaagent-0.18.0.jar https://repo1.maven.org/maven2/io/prometheus/jmx/jmx_prometheus_javaagent/0.18.0/jmx_prometheus_javaagent-0.18.0.jar && \ + ln -s ${PINOT_HOME}/etc/jmx_prometheus_javaagent/jmx_prometheus_javaagent-0.18.0.jar ${PINOT_HOME}/etc/jmx_prometheus_javaagent/jmx_prometheus_javaagent.jar + +# expose ports for controller/broker/server/admin +EXPOSE 9000 8099 8098 8097 8096 + +WORKDIR ${PINOT_HOME} + +ENTRYPOINT ["./bin/pinot-admin.sh"] + +CMD ["-help"] diff --git a/docker/images/pinot/etc/conf/pinot-minion-log4j2.xml b/docker/images/pinot/etc/conf/pinot-minion-log4j2.xml new file mode 100644 index 000000000000..6907f5e9f9d9 --- /dev/null +++ b/docker/images/pinot/etc/conf/pinot-minion-log4j2.xml @@ -0,0 +1,80 @@ + + + + + logs/pinotMinion + + + + + %d{yyyy/MM/dd HH:mm:ss.SSS} %p [%c{1}] [%t] %m%n + + + + + %d{yyyy/MM/dd HH:mm:ss.SSS} %p [%c{1}] [%t] %m%n + + + + + + + + + + + %d{yyyy/MM/dd HH:mm:ss.SSS} %p [%c{1}] [%t] %m%n + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docker/images/pinot/etc/jmx_prometheus_javaagent/README.md b/docker/images/pinot/etc/jmx_prometheus_javaagent/README.md index bcb6b348019c..e1d1f059e11e 100644 --- a/docker/images/pinot/etc/jmx_prometheus_javaagent/README.md +++ b/docker/images/pinot/etc/jmx_prometheus_javaagent/README.md @@ -6,17 +6,27 @@ Prometheus is not a full-fledged dashboarding solution and needs to be hooked up #### How do Pinot metrics end up in Prometheus? -Currently, Pinot metrics are exposed as JMX mbeans through the PinotJmxReporter. These JMX mbeans are consumed by Prometheus using the [Prometheus JMX Exporter](https://github.com/prometheus/jmx_exporter). A fairly comprehensive Prometheus JMX Exporter config can be found under `docker/images/pinot/etc/jmx_prometheus_javaagent/configs/pinot.yml`. +Currently, Pinot metrics are exposed as JMX mbeans through the PinotJmxReporter. +These JMX mbeans are consumed by Prometheus using the [Prometheus JMX Exporter](https://github.com/prometheus/jmx_exporter). +Fairly comprehensive Prometheus JMX Exporter config files can be found under +[docker/images/pinot/etc/jmx_prometheus_javaagent/configs/](docker/images/pinot/etc/jmx_prometheus_javaagent/configs/). See the [Pinot docs](https://docs.pinot.apache.org/operators/operating-pinot/monitoring) for more info. #### How can I view and test metrics? -First, you need to make sure the metrics are exposed though JMX. Note, that if no metric has been published (e.g. no values recorded), the metric will not show up in JMX or Prometheus server. -With a local Pinot deployment, you can launch `jconsole`, select your local deployment and view all the metrics exposed as jmx mbeans. To see if the metrics are being consumed with the Prometheus JMX Exporter and your config file, you can set the JAVA_OPTS env variable before running Pinot locally. +First, you need to make sure the metrics are exposed though JMX. +Note, that if no metric has been published (e.g. no values recorded), the metric will not show up in JMX or Prometheus server. +With a local Pinot deployment, you can launch `jconsole`, select your local deployment and view all the metrics exposed as jmx mbeans. +Alternative, you can use [jmxterm](https://docs.cyclopsgroup.org/jmxterm) in order to read them using a CLI. +To see if the metrics are being consumed with the Prometheus JMX Exporter and your config file, +you can set the JAVA_OPTS env variable before running Pinot locally. `export JAVA_OPTS="-javaagent:jmx_prometheus_javaagent.jar=8080:pinot.yml -Xms4G -Xmx4G -XX:MaxDirectMemorySize=30g -Dlog4j2.configurationFile=conf/pinot-admin-log4j2.xml -Dplugins.dir=$BASEDIR/plugins" bin/pinot-admin.sh .... ` +Remember that it is recommended to use service specific prometheus configurations (like `broker.yml`, `server.yml`, etc) +in production instead of `pinot.xml`. + This will expose a port at 8080 to dump metrics as Prometheus format for Prometheus scraper to fetch. diff --git a/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/broker.yml b/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/broker.yml index 450de902c694..3e74ae244ebc 100644 --- a/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/broker.yml +++ b/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/broker.yml @@ -1,151 +1,241 @@ rules: -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_authorization_$2" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_authorization_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_documentsScanned_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_documentsScanned_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_entriesScannedInFilter_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_entriesScannedInFilter_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_entriesScannedPostFilter_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_entriesScannedPostFilter_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_freshnessLagMs_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_freshnessLagMs_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_queries_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_queries_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_queryExecution_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_queryExecution_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_queryRouting_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_queryRouting_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_reduce_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_reduce_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_requestCompilation_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_requestCompilation_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_scatterGather_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_requestSize_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_totalServerResponseSize_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_scatterGather_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_groupBySize_$3" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_totalServerResponseSize_$4" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_noServingHostForSegment_$3" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_groupBySize_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_noServingHostForSegment_$5" + cache: true + labels: + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_broker_healthcheck_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_broker_helix_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_broker_helix_zookeeper_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_broker_nettyConnection_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_broker_unhealthyServers_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_broker_clusterChangeCheck_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_broker_proactiveClusterChangeCheck_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_broker_exceptions_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_broker_routingTableUpdateTime_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_brokerResponsesWithPartialServersResponded_$2" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_adaptiveServerSelectorType_$1" + cache: true +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_adaptiveServerSelectorType_$1_$2" + cache: true +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_brokerResponsesWithPartialServersResponded_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_noServerFoundExceptions_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_brokerResponsesWithTimeouts_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_brokerResponsesWithProcessingExceptions_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_noServerFoundExceptions_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_brokerResponsesWithNumGroupsLimitReached_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_brokerResponsesWithProcessingExceptions_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_queryQuotaExceeded_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_brokerResponsesWithNumGroupsLimitReached_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_queryTotalTimeMs_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_queryQuotaExceeded_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_serverMissingForRouting_$3" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_queryTotalTimeMs_$4" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_deserialization_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_serverMissingForRouting_$5" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_requestConnectionWait_$2" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_deserialization_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_version" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_requestConnectionWait_$4" cache: true labels: - version: "$1" + database: "$2" + table: "$1$3" +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_version" + cache: true + labels: + version: "$2" + + ## Metrics that fit the catch-all patterns above should not be added to this file. + ## In case a metric does not fit the catch-all patterns, add them before this comment +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$2_$8" + cache: true + labels: + database: "$4" + table: "$3$5" + tableType: "$6" + partition: "$7" + # This is a catch-all pattern for pinot table metrics with offline/realtime suffix without kafka topic + # Patterns after this line may be skipped. +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$6_$7" + cache: true + labels: + database: "$3" + table: "$2$4" + tableType: "$5" + #when there is no partition in the metric +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$2_$7" + cache: true + labels: + database: "$4" + table: "$3$5" + tableType: "$6" + #This is a catch-all pattern for pinot table metrics with offline/realtime suffix that also contain kafka topic +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$2_$9" + cache: true + labels: + database: "$4" + table: "$3$5" + tableType: "$6" + topic: "$7" + partition: "$8" + # This is a catch-all pattern for pinot table metrics. Patterns after this line may be skipped. +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$5_$6" + cache: true + labels: + database: "$3" + table: "$2$4" + # This is a catch-all pattern for pinot controller metrics not related to tables. Patterns after this line may be skipped. +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$2_$3" + cache: true diff --git a/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/controller.yml b/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/controller.yml index d258161b4213..81bb7b0e5b84 100644 --- a/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/controller.yml +++ b/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/controller.yml @@ -1,188 +1,291 @@ rules: -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_helix_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_helix_ZookeeperReconnects_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_idealstateZnodeSize_$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_idealstateZnodeSize_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_idealstateZnodeByteSize_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_idealstateZnodeByteSize_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_replicationFromConfig_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_replicationFromConfig_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_numberOfReplicas_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_numberOfReplicas_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_percentOfReplicas_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_percentOfReplicas_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_percentSegmentsAvailable_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_percentSegmentsAvailable_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_segmentCount_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_segmentCount_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_segmentsInErrorState_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_segmentsInErrorState_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_dataDir_$1_$2" + cache: true +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_numberSegmentUploadTimeoutExceeded_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_numberTimesScheduleTasksCalled_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_periodicTaskNumTablesProcessed_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_pinotControllerLeader_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_partitionLeader_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_realtimeTableCount_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_offlineTableCount_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_validateion_$2_$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_tierBackendTableCount_$1_$2" + cache: true +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_validateion_$4_$5" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_cronSchedulerJobScheduled_$3" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_cronSchedulerJobScheduled_$5" cache: true labels: - table: "$1" - taskType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_cronSchedulerTriggered_$3" + database: "$2" + table: "$1$3" + taskType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_cronSchedulerJobTriggered_$5" cache: true labels: - table: "$1" - taskType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_cronSchedulerSkipped_$3" + database: "$2" + table: "$1$3" + taskType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_cronSchedulerJobSkipped_$5" cache: true labels: - table: "$1" - taskType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_cronSchedulerJobExecutionTimeMs_$3" + database: "$2" + table: "$1$3" + taskType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_cronSchedulerJobExecutionTimeMs_$5" cache: true labels: - table: "$1" - taskType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" + database: "$2" + table: "$1$3" + taskType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_tableRebalanceExecutionTimeMs_$5" + cache: true + labels: + database: "$2" + table: "$1$3" + result: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_taskStatus_$3" cache: true labels: taskType: "$1" status: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_timeMsSinceLastMinionTaskMetadataUpdate_$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_timeMsSinceLastMinionTaskMetadataUpdate_$6" + cache: true + labels: + database: "$2" + table: "$1$3" + tableType: "$4" + taskType: "$5" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_$1_$7" cache: true labels: - table: "$1" - tableType: "$2" - taskType: "$3" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_timeMsSinceLastSuccessfulMinionTaskGeneration_$4" + database: "$3" + table: "$2$4" + tableType: "$5" + taskType: "$6" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_$1_$3" + cache: true + labels: + taskType: "$2" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_timeMsSinceLastSuccessfulMinionTaskGeneration_$6" cache: true labels: - table: "$1" - tableType: "$2" - taskType: "$3" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_lastMinionTaskGenerationEncountersError_$4" + database: "$2" + table: "$1$3" + tableType: "$4" + taskType: "$5" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_lastMinionTaskGenerationEncountersError_$6" cache: true labels: - table: "$1" - tableType: "$2" - taskType: "$3" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" + database: "$2" + table: "$1$3" + tableType: "$4" + taskType: "$5" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_pinotLeadControllerResourceEnabled_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_offlineTableEstimatedSize_$2" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_offlineTableEstimatedSize_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_tableQuota_$3" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_tableQuota_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_periodicTaskError_$4" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_periodicTaskError_$6" cache: true labels: - table: "$1" - tableType: "$2" - periodicTask: "$3" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_tableStorageQuotaUtilization_$3" + database: "$2" + table: "$1$3" + tableType: "$4" + periodicTask: "$5" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_tableStorageQuotaUtilization_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_tableStorageEstMissingSegmentPercent_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_tableStorageEstMissingSegmentPercent_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_tableTotalSizeOnServer_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_tableTotalSizeOnServer_$5" labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_tableSizePerReplicaOnServer_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_tableSizePerReplicaOnServer_$5" labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_tableCompressedSize_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_tableCompressedSize_$5" labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_version" + database: "$2" + table: "$1$3" + tableType: "$4" +# Controller periodic task metrics +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_periodicTaskRun_$1_$2" + cache: true +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_version" + cache: true + labels: + version: "$2" + + ## Metrics that fit the catch-all patterns above should not be added to this file. + ## In case a metric does not fit the catch-all patterns, add them before this comment + # This is a catch-all pattern for pinot table metrics with offline/realtime suffix without kafka topic + # Patterns after this line may be skipped. +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$2_$8" + cache: true + labels: + database: "$4" + table: "$3$5" + tableType: "$6" + partition: "$7" +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$6_$7" + cache: true + labels: + database: "$3" + table: "$2$4" + tableType: "$5" + #This is a catch-all pattern for pinot table metrics with offline/realtime suffix that also contain kafka topic +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$2_$9" + cache: true + labels: + database: "$4" + table: "$3$5" + tableType: "$6" + topic: "$7" + partition: "$8" + #when there is no partition in the metric +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$2_$7" cache: true labels: - version: "$1" + database: "$4" + table: "$3$5" + tableType: "$6" + # This is a catch-all pattern for pinot table metrics. Patterns after this line may be skipped. +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$5_$6" + cache: true + labels: + database: "$3" + table: "$2$4" + # This is a catch-all pattern for pinot controller metrics not related to tables. Patterns after this line may be skipped. +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$2_$3" + cache: true diff --git a/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/minion.yml b/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/minion.yml index 622da8861a53..4d21107a1b9f 100644 --- a/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/minion.yml +++ b/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/minion.yml @@ -1,27 +1,58 @@ rules: -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_minion_version" cache: true labels: version: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_minion_numberOfTasks_$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_minion_numberOfTasks_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_minion_$4_$5" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_minion_$6_$7" cache: true labels: - table: "$1" - tableType: "$2" - taskType: "$3" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" + database: "$2" + table: "$1$3" + tableType: "$4" + taskType: "$5" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_minion_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_minion_$2_$3" cache: true labels: id: "$1" +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_version" + cache: true + labels: + version: "$2" + + ## Metrics that fit the catch-all patterns above should not be added to this file. + ## In case a metric does not fit the catch-all patterns, add them before this comment + + # This is a catch-all pattern for pinot table metrics with offline/realtime suffix. + # Patterns after this line may be skipped. +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$6_$7" + cache: true + labels: + database: "$3" + table: "$2$4" + tableType: "$5" + # This is a catch-all pattern for pinot table metrics. Patterns after this line may be skipped. +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$5_$6" + cache: true + labels: + database: "$3" + table: "$2$4" + # This is a catch-all pattern for pinot controller metrics not related to tables. Patterns after this line may be skipped. +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$2_$3" + cache: true diff --git a/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/pinot.yml b/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/pinot.yml index af618eeb567c..b530a79b363b 100644 --- a/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/pinot.yml +++ b/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/pinot.yml @@ -1,456 +1,681 @@ +# It is recommended to use the specific service yml file (like broker.yml, controller.yml, etc) +# instead of this file. +# Prometheus executes rules in a linear time, so the more rules your configuration has, the more CPU will be spent by +# Prometheus Java agent. + rules: # Pinot Controller -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_helix_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_helix_ZookeeperReconnects_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_idealstateZnodeSize_$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_idealstateZnodeSize_$5" + cache: true + labels: + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_idealstateZnodeByteSize_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_idealstateZnodeByteSize_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_replicationFromConfig_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_numberOfReplicas_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_numberOfReplicas_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_percentOfReplicas_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_percentOfReplicas_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_percentSegmentsAvailable_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_percentSegmentsAvailable_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_segmentCount_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_segmentCount_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_segmentsInErrorState_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_segmentsInErrorState_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_numberSegmentUploadTimeoutExceeded_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_numberTimesScheduleTasksCalled_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_periodicTaskNumTablesProcessed_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_pinotControllerLeader_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_partitionLeader_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_realtimeTableCount_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_offlineTableCount_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_validateion_$2_$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_validateion_$4_$5" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_cronSchedulerJobScheduled_$3" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_cronSchedulerJobScheduled_$5" cache: true labels: - table: "$1" - taskType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_cronSchedulerTriggered_$3" + database: "$2" + table: "$1$3" + taskType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_cronSchedulerJobTriggered_$5" cache: true labels: - table: "$1" - taskType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_cronSchedulerJobExecutionTimeMs_$3" + database: "$2" + table: "$1$3" + taskType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_cronSchedulerJobSkipped_$5" cache: true labels: - table: "$1" - taskType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" + database: "$2" + table: "$1$3" + taskType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_cronSchedulerJobExecutionTimeMs_$5" + cache: true + labels: + database: "$2" + table: "$1$3" + taskType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_tableRebalanceExecutionTimeMs_$5" + cache: true + labels: + database: "$2" + table: "$1$3" + result: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_taskStatus_$3" cache: true labels: taskType: "$1" status: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_timeMsSinceLastMinionTaskMetadataUpdate_$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_timeMsSinceLastMinionTaskMetadataUpdate_$6" cache: true labels: - table: "$1" - tableType: "$2" - taskType: "$3" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_timeMsSinceLastSuccessfulMinionTaskGeneration_$4" + database: "$2" + table: "$1$3" + tableType: "$4" + taskType: "$5" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_$1_$7" cache: true labels: - table: "$1" - tableType: "$2" - taskType: "$3" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_lastMinionTaskGenerationEncountersError_$4" + database: "$3" + table: "$2$4" + tableType: "$5" + taskType: "$6" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_$1_$3" cache: true labels: - table: "$1" - tableType: "$2" - taskType: "$3" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" + taskType: "$2" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_timeMsSinceLastSuccessfulMinionTaskGeneration_$6" + cache: true + labels: + database: "$2" + table: "$1$3" + tableType: "$4" + taskType: "$5" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_lastMinionTaskGenerationEncountersError_$6" + cache: true + labels: + database: "$2" + table: "$1$3" + tableType: "$4" + taskType: "$5" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_controller_pinotLeadControllerResourceEnabled_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_offlineTableEstimatedSize_$2" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_offlineTableEstimatedSize_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_largestSegmentSizeOnServer_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_largestSegmentSizeOnServer_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_tableTotalSizeOnServer_$3" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_tableTotalSizeOnServer_$5" labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_tableSizePerReplicaOnServer_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_tableSizePerReplicaOnServer_$5" labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_tableCompressedSize_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_tableCompressedSize_$5" labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_tableQuota_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_tableQuota_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_tableStorageQuotaUtilization_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_periodicTaskError_$6" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_controller_tableStorageEstMissingSegmentPercent_$3" + database: "$2" + table: "$1$3" + tableType: "$4" + periodicTask: "$5" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_tableStorageQuotaUtilization_$5" cache: true labels: - table: "$1" - tableType: "$2" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_controller_tableStorageEstMissingSegmentPercent_$5" + cache: true + labels: + database: "$2" + table: "$1$3" + tableType: "$4" # Pinot Broker -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_authorization_$2" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_authorization_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_documentsScanned_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_documentsScanned_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_entriesScannedInFilter_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_entriesScannedInFilter_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_entriesScannedPostFilter_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_entriesScannedPostFilter_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_freshnessLagMs_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_freshnessLagMs_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_queries_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_queries_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_queryExecution_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_queryExecution_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_queryRouting_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_queryRouting_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_queryTotalTimeMs_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_queryTotalTimeMs_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_reduce_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_reduce_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_requestCompilation_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_requestCompilation_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_scatterGather_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_scatterGather_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_totalServerResponseSize_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_totalServerResponseSize_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_groupBySize_$3" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_groupBySize_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_noServingHostForSegment_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_noServingHostForSegment_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_broker_healthcheck_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_broker_helix_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_broker_helix_zookeeper_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_broker_nettyConnection_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_broker_unhealthyServers_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_broker_clusterChangeCheck_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_broker_proactiveClusterChangeCheck_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_broker_exceptions_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_broker_routingTableUpdateTime_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_brokerResponsesWithPartialServersResponded_$2" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_brokerResponsesWithPartialServersResponded_$4" + cache: true + labels: + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_brokerResponsesWithProcessingExceptions_$4" + cache: true + labels: + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_brokerResponsesWithNumGroupsLimitReached_$4" + cache: true + labels: + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_queryQuotaExceeded_$4" + cache: true + labels: + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_serverMissingForRouting_$5" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_brokerResponsesWithProcessingExceptions_$2" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_deserialization_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_brokerResponsesWithNumGroupsLimitReached_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_requestConnectionWait_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_queryQuotaExceeded_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_brokerResponsesWithTimeouts_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_serverMissingForRouting_$3" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_noServerFoundExceptions_$4" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_deserialization_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_brokerResponsesWithProcessingExceptions_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_broker_requestConnectionWait_$2" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_broker_queryTotalTimeMs_$4" cache: true labels: - table: "$1" + database: "$2" + table: "$1$3" # Pinot Server -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_documentCount_$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_documentCount_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_segmentCount_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_segmentCount_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_$3_$4" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_$5_$6" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_realtimeRowsConsumed_$5" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_$7_$8" cache: true labels: - table: "$1" - tableType: "$2" - topic: "$3" - partition: "$4" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" + database: "$2" + table: "$1$3" + tableType: "$4" + topic: "$5" + partition: "$6" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_server_helix_connected_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_server_helix_zookeeperReconnects_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_highestKafkaOffsetConsumed_$5" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_highestKafkaOffsetConsumed_$7" cache: true labels: - table: "$1" - tableType: "$2" - topic: "$3" - partition: "$4" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_highestStreamOffsetConsumed_$5" + database: "$2" + table: "$1$3" + tableType: "$4" + topic: "$5" + partition: "$6" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_highestStreamOffsetConsumed_$7" cache: true labels: - table: "$1" - tableType: "$2" - topic: "$3" - partition: "$4" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_lastRealtimeSegment$1Seconds_$6" + database: "$2" + table: "$1$3" + tableType: "$4" + topic: "$5" + partition: "$6" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_lastRealtimeSegment$1Seconds_$8" cache: true labels: - table: "$2" - tableType: "$3" - topic: "$4" - partition: "$5" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" + database: "$3" + table: "$2$4" + tableType: "$5" + topic: "$6" + partition: "$7" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_server_llcControllerResponse_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_llcPartitionConsuming_$5" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_llcPartitionConsuming_$7" cache: true labels: - table: "$1" - tableType: "$2" - topic: "$3" - partition: "$4" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" + database: "$2" + table: "$1$3" + tableType: "$4" + topic: "$5" + partition: "$6" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_server_llcSimultaneousSegmentBuilds_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_server_memory_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_server_queries_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_server_realtime_consumptionExceptions_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_realtime_offheapMemoryUsed_$2" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_realtime_offheapMemoryUsed_$4" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_server_realtime_offsetCommits_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_server_realtime_rowsConsumed_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_realtime_rowsErrored_$1" + cache: true +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_server_realtime_exceptions_$1_$2" cache: true -- pattern: "\"org.apache.pinot.transport.netty.NettyTCPServer_(\\w+)_\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.transport\\.netty\\.NettyTCPServer_(\\w+)_\"<>(\\w+)" name: "pinot_server_netty_tcp_$2_$3" cache: true labels: id: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_server_nettyConnection_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_realtimeSegmentNumPartitions_$2" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_realtimeSegmentNumPartitions_$4" + cache: true + labels: + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_resizeTimeMs_$5" + cache: true + labels: + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_numResizes_$5" + cache: true + labels: + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_upsertPrimaryKeysCount_$6" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_resizeTimeMs_$3" + database: "$2" + table: "$1$3" + tableType: "$4" + partition: "$5" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_realtimeIngestionDelayMs_$6" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_upsertPrimaryKeysCount_$4" + database: "$2" + table: "$1$3" + tableType: "$4" + partition: "$5" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_upsertValidDocSnapshotCount_$6" cache: true labels: - table: "$1" - tableType: "$2" - partition: "$3" + database: "$2" + table: "$1$3" + tableType: "$4" + partition: "$5" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_upsertPrimaryKeysInSnapshotCount_$6" + cache: true + labels: + database: "$2" + table: "$1$3" + tableType: "$4" + partition: "$5" + #grpc related metrics +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_grpc$1_$2" + cache: true # Pinot Minions -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_minion_numberOfTasks_$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_minion_numberOfTasks_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_minion_$4_$5" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_minion_$6_$7" cache: true labels: - table: "$1" - tableType: "$2" - taskType: "$3" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" + database: "$2" + table: "$1$3" + tableType: "$4" + taskType: "$5" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_minion_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_minion_$2_$3" cache: true labels: id: "$1" +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_version" + cache: true + labels: + version: "$2" + + + ## Metrics that fit the catch-all patterns above should not be added to this file. + ## In case a metric does not fit the catch-all patterns, add them before this comment + + # This is a catch-all pattern for pinot table metrics with offline/realtime suffix without topic but containing partition + # Patterns after this line may be skipped. +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$2_$8" + cache: true + labels: + database: "$4" + table: "$3$5" + tableType: "$6" + partition: "$7" + # This is a catch-all pattern for pinot table metrics with offline/realtime suffix without topic or partition info + # Patterns after this line may be skipped. +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$6_$7" + cache: true + labels: + database: "$3" + table: "$2$4" + tableType: "$5" + # This is a catch-all pattern for pinot table metrics with offline/realtime suffix with topic and partition + # Patterns after this line may be skipped. +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$2_$9" + cache: true + labels: + database: "$4" + table: "$3$5" + tableType: "$6" + topic: "$7" + partition: "$8" +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$2_$7" + cache: true + labels: + database: "$4" + table: "$2$5" + tableType: "$6" + # This is a catch-all pattern for pinot table metrics. Patterns after this line may be skipped. +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$5_$6" + cache: true + labels: + database: "$3" + table: "$2$4" + # This is a catch-all pattern for pinot controller metrics not related to tables. Patterns after this line may be skipped. +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$2_$3" + cache: true diff --git a/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/server.yml b/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/server.yml index 211c859f390b..c711d3cfd514 100644 --- a/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/server.yml +++ b/docker/images/pinot/etc/jmx_prometheus_javaagent/configs/server.yml @@ -1,125 +1,232 @@ rules: -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_documentCount_$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_documentCount_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_segmentCount_$3" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_segmentCount_$5" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_$3_$4" - cache: true - labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_realtimeRowsConsumed_$5" - cache: true - labels: - table: "$1" - tableType: "$2" - topic: "$3" - partition: "$4" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_server_helix_connected_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_server_helix_zookeeperReconnects_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_highestKafkaOffsetConsumed_$5" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_highestKafkaOffsetConsumed_$7" cache: true labels: - table: "$1" - tableType: "$2" - topic: "$3" - partition: "$4" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_highestStreamOffsetConsumed_$5" + database: "$2" + table: "$1$3" + tableType: "$4" + topic: "$5" + partition: "$6" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_highestStreamOffsetConsumed_$7" cache: true labels: - table: "$1" - tableType: "$2" - topic: "$3" - partition: "$4" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_lastRealtimeSegment$1Seconds_$6" + database: "$2" + table: "$1$3" + tableType: "$4" + topic: "$5" + partition: "$6" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_lastRealtimeSegment$1Seconds_$8" cache: true labels: - table: "$2" - tableType: "$3" - topic: "$4" - partition: "$5" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" + database: "$3" + table: "$2$4" + tableType: "$5" + topic: "$6" + partition: "$7" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_server_llcControllerResponse_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_llcPartitionConsuming_$5" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_llcPartitionConsuming_$7" + cache: true + labels: + database: "$2" + table: "$1$3" + tableType: "$4" + topic: "$5" + partition: "$6" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_realtimeIngestionDelayMs_$6" + cache: true + labels: + database: "$2" + table: "$1$3" + tableType: "$4" + partition: "$5" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_endToEndRealtimeIngestionDelayMs_$6" cache: true labels: - table: "$1" - tableType: "$2" - topic: "$3" - partition: "$4" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" + database: "$2" + table: "$1$3" + tableType: "$4" + partition: "$5" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_server_llcSimultaneousSegmentBuilds_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_server_memory_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_server_queries_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_server_realtime_consumptionExceptions_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_realtime_offheapMemoryUsed_$2" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_$7_$8" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" + database: "$2" + table: "$1$3" + tableType: "$4" + topic: "$5" + partition: "$6" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_realtime_offheapMemoryUsed_$4" + cache: true + labels: + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_server_realtime_offsetCommits_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_server_realtime_rowsConsumed_$1" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_server_realtime_exceptions_$1_$2" cache: true -- pattern: "\"org.apache.pinot.transport.netty.NettyTCPServer_(\\w+)_\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.transport\\.netty\\.NettyTCPServer_(\\w+)_\"<>(\\w+)" name: "pinot_server_netty_tcp_$2_$3" cache: true labels: id: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" name: "pinot_server_nettyConnection_$1_$2" cache: true -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_realtimeSegmentNumPartitions_$2" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_realtimeSegmentNumPartitions_$4" + cache: true + labels: + database: "$2" + table: "$1$3" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_numResizes_$5" + cache: true + labels: + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_resizeTimeMs_$5" + cache: true + labels: + database: "$2" + table: "$1$3" + tableType: "$4" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_upsertPrimaryKeysCount_$6" + cache: true + labels: + database: "$2" + table: "$1$3" + tableType: "$4" + partition: "$5" +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_version" + cache: true + labels: + version: "$2" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_upsertValidDocSnapshotCount_$6" + cache: true + labels: + database: "$2" + table: "$1$3" + tableType: "$4" + partition: "$5" +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_upsertPrimaryKeysInSnapshotCount_$6" cache: true labels: - table: "$1" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_resizeTimeMs_$3" + database: "$2" + table: "$1$3" + tableType: "$4" + partition: "$5" +#grpc related metrics +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_grpc$1_$2" + cache: true + +- pattern: "\"org\\.apache\\.pinot\\.common\\.metrics\"<>(\\w+)" + name: "pinot_server_$5_$6" cache: true labels: - table: "$1" - tableType: "$2" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_upsertPrimaryKeysCount_$4" + database: "$2" + table: "$1$3" + tableType: "$4" + + ## Metrics that fit the catch-all patterns above should not be added to this file. + ## In case a metric does not fit the catch-all patterns, add them before this comment + # when there is partition but no topic in the metric +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$2_$8" cache: true labels: - table: "$1" - tableType: "$2" - partition: "$3" -- pattern: "\"org.apache.pinot.common.metrics\"<>(\\w+)" - name: "pinot_server_version" + database: "$4" + table: "$3$5" + tableType: "$6" + partition: "$7" + # This is a catch-all pattern for pinot table metrics with offline/realtime suffix without the topic + # Patterns after this line may be skipped. +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$6_$7" cache: true labels: - version: "$1" + database: "$3" + table: "$2$4" + tableType: "$5" +#when there is partition and topic in the metric +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$2_$9" + cache: true + labels: + database: "$4" + table: "$3$5" + tableType: "$6" + topic: "$7" + partition: "$8" +#when there is no partition in the metric +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$2_$7" + cache: true + labels: + database: "$4" + table: "$3$5" + tableType: "$6" + # This is a catch-all pattern for pinot table metrics. Patterns after this line may be skipped. +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$5_$6" + cache: true + labels: + database: "$3" + table: "$2$4" + # This is a catch-all pattern for pinot controller metrics not related to tables. Patterns after this line may be skipped. +- pattern: "\"?org\\.apache\\.pinot\\.common\\.metrics\"?<>(\\w+)" + name: "pinot_$1_$2_$3" + cache: true + diff --git a/kubernetes/helm/README-dev.md b/helm/README-dev.md similarity index 97% rename from kubernetes/helm/README-dev.md rename to helm/README-dev.md index d763421ff81a..ab1c168cb3dd 100644 --- a/kubernetes/helm/README-dev.md +++ b/helm/README-dev.md @@ -42,4 +42,4 @@ This step will generate an `index.yaml` file which contains all the Charts infor Update generated `index.yaml` accordingly: - Revert the changes for all previous Charts; -- Change `entries.pinot.source` to `https://github.com/apache/pinot/tree/master/kubernetes/helm`. +- Change `entries.pinot.source` to `https://github.com/apache/pinot/tree/master/helm`. diff --git a/kubernetes/helm/README.md b/helm/README.md similarity index 100% rename from kubernetes/helm/README.md rename to helm/README.md diff --git a/kubernetes/helm/gke-pd.yaml b/helm/gke-pd.yaml similarity index 100% rename from kubernetes/helm/gke-pd.yaml rename to helm/gke-pd.yaml diff --git a/kubernetes/helm/gke-ssd.yaml b/helm/gke-ssd.yaml similarity index 100% rename from kubernetes/helm/gke-ssd.yaml rename to helm/gke-ssd.yaml diff --git a/kubernetes/helm/helm-rbac.yaml b/helm/helm-rbac.yaml similarity index 100% rename from kubernetes/helm/helm-rbac.yaml rename to helm/helm-rbac.yaml diff --git a/helm/index.yaml b/helm/index.yaml new file mode 100644 index 000000000000..f08bd52f4c48 --- /dev/null +++ b/helm/index.yaml @@ -0,0 +1,327 @@ +apiVersion: v1 +entries: + pinot: + - apiVersion: v1 + appVersion: 1.0.0 + created: "2024-05-30T09:29:30.175007-04:00" + dependencies: + - condition: pinot.zookeeper.enabled,zookeeper.enabled + name: zookeeper + repository: https://charts.bitnami.com/bitnami + version: 13.x.x + description: Apache Pinot is a realtime distributed OLAP datastore, which is used + to deliver scalable real time analytics with low latency. It can ingest data + from offline data sources (such as Hadoop and flat files) as well as online + sources (such as Kafka). Pinot is designed to scale horizontally. + digest: a39ab0af4e8b3c179516b071a79232d52d987b547cfb358bd46140dd27ce2530 + home: https://pinot.apache.org/ + keywords: + - olap + - analytics + - database + - pinot + maintainers: + - email: dev@pinot.apache.org + name: pinot-dev + name: pinot + sources: + - https://github.com/apache/pinot/tree/master/helm + urls: + - pinot-0.2.9.tgz + version: 0.2.9 + - apiVersion: v1 + appVersion: 1.0.0 + created: "2024-02-22T11:12:34.734773+08:00" + dependencies: + - condition: pinot.zookeeper.enabled,zookeeper.enabled + name: zookeeper + repository: https://charts.bitnami.com/bitnami + version: 9.x.x + description: Apache Pinot is a realtime distributed OLAP datastore, which is used + to deliver scalable real time analytics with low latency. It can ingest data + from offline data sources (such as Hadoop and flat files) as well as online + sources (such as Kafka). Pinot is designed to scale horizontally. + digest: c027e580faecc7aa934a10cf1b5d324cc3faafa58c21531c0fa006458f60547a + home: https://pinot.apache.org/ + keywords: + - olap + - analytics + - database + - pinot + maintainers: + - email: dev@pinot.apache.org + name: pinot-dev + name: pinot + sources: + - https://github.com/apache/pinot/tree/master/helm + urls: + - pinot-0.2.8.tgz + version: 0.2.8 + - apiVersion: v1 + appVersion: 0.2.7 + created: "2023-05-04T20:38:46.922122-04:00" + dependencies: + - condition: pinot.zookeeper.enabled,zookeeper.enabled + name: zookeeper + repository: https://charts.bitnami.com/bitnami + version: 9.x.x + description: Apache Pinot is a realtime distributed OLAP datastore, which is used + to deliver scalable real time analytics with low latency. It can ingest data + from offline data sources (such as Hadoop and flat files) as well as online + sources (such as Kafka). Pinot is designed to scale horizontally. + digest: f466ab18582850c589c0a6fc264e12ee236ab9cb3b540b653822296e2f65a323 + home: https://pinot.apache.org/ + keywords: + - olap + - analytics + - database + - pinot + maintainers: + - email: dev@pinot.apache.org + name: pinot-dev + name: pinot + sources: + - https://github.com/apache/pinot/tree/master/helm + urls: + - pinot-0.2.7.tgz + version: 0.2.7 + - apiVersion: v1 + appVersion: 0.2.6 + created: "2022-11-23T13:05:57.685715-08:00" + dependencies: + - condition: pinot.zookeeper.enabled,zookeeper.enabled + name: zookeeper + repository: https://charts.bitnami.com/bitnami + version: 9.x.x + description: Apache Pinot is a realtime distributed OLAP datastore, which is used + to deliver scalable real time analytics with low latency. It can ingest data + from offline data sources (such as Hadoop and flat files) as well as online + sources (such as Kafka). Pinot is designed to scale horizontally. + digest: a02cf25577d5cfe6a78c82dbb987e1817fe059d276168933635876467071d402 + home: https://pinot.apache.org/ + keywords: + - olap + - analytics + - database + - pinot + maintainers: + - email: dev@pinot.apache.org + name: pinot-dev + name: pinot + sources: + - https://github.com/apache/pinot/tree/master/helm + urls: + - pinot-0.2.6.tgz + version: 0.2.6 + - apiVersion: v1 + appVersion: 0.2.5 + created: "2022-03-29T13:13:13.151614-07:00" + dependencies: + - condition: pinot.zookeeper.enabled,zookeeper.enabled + name: zookeeper + repository: https://charts.bitnami.com/bitnami + version: 7.0.0 + description: Apache Pinot is a realtime distributed OLAP datastore, which is used + to deliver scalable real time analytics with low latency. It can ingest data + from offline data sources (such as Hadoop and flat files) as well as online + sources (such as Kafka). Pinot is designed to scale horizontally. + digest: adf2e1134745cafd3330cfc9272e1ed61586fe3bc59e2440c7d54db5ec5fb261 + home: https://pinot.apache.org/ + keywords: + - olap + - analytics + - database + - pinot + maintainers: + - email: dev@pinot.apache.org + name: pinot-dev + name: pinot + sources: + - https://github.com/apache/pinot/tree/master/helm + urls: + - pinot-0.2.5.tgz + version: 0.2.5 + - apiVersion: v1 + appVersion: 0.2.4 + created: "2021-07-07T15:53:54.287681-07:00" + dependencies: + - condition: pinot.zookeeper.enabled,zookeeper.enabled + name: zookeeper + repository: https://charts.helm.sh/incubator + version: 2.1.6 + description: Apache Pinot is a realtime distributed OLAP datastore, which is used + to deliver scalable real time analytics with low latency. It can ingest data + from offline data sources (such as Hadoop and flat files) as well as online + sources (such as Kafka). Pinot is designed to scale horizontally. + digest: d3d662baa8ec714ac099cd48ea6acf0158b09c1bbd34175d889ed3475159f808 + home: https://pinot.apache.org/ + keywords: + - olap + - analytics + - database + - pinot + maintainers: + - email: dev@pinot.apache.org + name: pinot-dev + name: pinot + sources: + - https://github.com/apache/pinot/tree/master/helm + urls: + - pinot-0.2.4.tgz + version: 0.2.4 + - apiVersion: v1 + appVersion: 0.2.3 + created: "2021-04-22T16:13:57.610533-07:00" + dependencies: + - condition: pinot.zookeeper.enabled,zookeeper.enabled + name: zookeeper + repository: https://charts.helm.sh/incubator + version: 2.1.3 + description: Apache Pinot is a realtime distributed OLAP datastore, which is used + to deliver scalable real time analytics with low latency. It can ingest data + from offline data sources (such as Hadoop and flat files) as well as online + sources (such as Kafka). Pinot is designed to scale horizontally. + digest: 6361ac8a90ad895e2ee4372b11eb52c80107ba6e3e5926b5790bf509ab8a8a7d + home: https://pinot.apache.org/ + keywords: + - olap + - analytics + - database + - pinot + maintainers: + - email: dev@pinot.apache.org + name: pinot-dev + name: pinot + sources: + - https://github.com/apache/pinot/tree/master/helm + urls: + - pinot-0.2.3.tgz + version: 0.2.3 + - apiVersion: v1 + appVersion: 0.2.2 + created: "2021-04-22T16:13:57.604713-07:00" + dependencies: + - condition: pinot.zookeeper.enabled,zookeeper.enabled + name: zookeeper + repository: https://charts.helm.sh/incubator + version: 2.1.3 + description: Apache Pinot is a realtime distributed OLAP datastore, which is used + to deliver scalable real time analytics with low latency. It can ingest data + from offline data sources (such as Hadoop and flat files) as well as online + sources (such as Kafka). Pinot is designed to scale horizontally. + digest: 7b571d881df299a83d5d351df562e81ec5e9d0ab0d2d4f7c2fd8622a997abde7 + home: https://pinot.apache.org/ + keywords: + - olap + - analytics + - database + - pinot + maintainers: + - email: dev@pinot.apache.org + name: pinot-dev + name: pinot + sources: + - https://github.com/apache/pinot/tree/master/helm + urls: + - pinot-0.2.2.tgz + version: 0.2.2 + - apiVersion: v1 + appVersion: 0.2.1 + created: "2021-04-22T16:13:57.600004-07:00" + dependencies: + - condition: pinot.zookeeper.enabled,zookeeper.enabled + name: zookeeper + repository: https://charts.helm.sh/incubator + version: 2.1.3 + description: Apache Pinot is a realtime distributed OLAP datastore, which is used + to deliver scalable real time analytics with low latency. It can ingest data + from offline data sources (such as Hadoop and flat files) as well as online + sources (such as Kafka). Pinot is designed to scale horizontally. + digest: 605b74fb6203f62500638fd679859cc771ca37ed420f89e02a0c82c3ede92690 + home: https://pinot.apache.org/ + keywords: + - olap + - analytics + - database + - pinot + maintainers: + - email: dev@pinot.apache.org + name: pinot-dev + name: pinot + sources: + - https://github.com/apache/pinot/tree/master/helm + urls: + - pinot-0.2.1.tgz + version: 0.2.1 + - apiVersion: v1 + appVersion: 0.2.0 + created: "2021-04-22T16:13:57.592641-07:00" + dependencies: + - condition: pinot.zookeeper.enabled,zookeeper.enabled + name: zookeeper + repository: https://charts.helm.sh/incubator + version: 2.1.3 + description: Apache Pinot is a realtime distributed OLAP datastore, which is used + to deliver scalable real time analytics with low latency. It can ingest data + from offline data sources (such as Hadoop and flat files) as well as online + sources (such as Kafka). Pinot is designed to scale horizontally. + digest: a15cb08e2077d6e2893ea46212a5b769f303af83904681a95e550879fa22f870 + home: https://pinot.apache.org/ + keywords: + - olap + - analytics + - database + - pinot + maintainers: + - email: dev@pinot.apache.org + name: pinot-dev + name: pinot + sources: + - https://github.com/apache/pinot/tree/master/helm + urls: + - pinot-0.2.0.tgz + version: 0.2.0 + presto: + - apiVersion: v1 + appVersion: 0.2.1 + created: "2021-04-22T16:13:57.616017-07:00" + description: Presto is an open source distributed SQL query engine for running + interactive analytic queries against data sources of all sizes ranging from + gigabytes to petabytes. + digest: 370757c5a5c4fdb2e9e2e0e219400719b572e76c1c800e9c3abb29b6e2e5743a + home: https://github.com/prestodb/presto + keywords: + - analytics + - database + - presto + maintainers: + - email: dev@pinot.apache.org + name: pinot-dev + name: presto + sources: + - https://github.com/apache/pinot/tree/master/helm + urls: + - presto-0.2.1.tgz + version: 0.2.1 + - apiVersion: v1 + appVersion: 0.2.0 + created: "2021-04-22T16:13:57.612839-07:00" + description: Presto is an open source distributed SQL query engine for running + interactive analytic queries against data sources of all sizes ranging from + gigabytes to petabytes. + digest: d17eb6e0b1b3ba26e013325d7fb8da1c7a25fad35f37f955f4fbe402b5363b15 + home: https://github.com/prestodb/presto + keywords: + - analytics + - database + - presto + maintainers: + - email: dev@pinot.apache.org + name: pinot-dev + name: presto + sources: + - https://github.com/apache/pinot/tree/master/helm + urls: + - presto-0.2.0.tgz + version: 0.2.0 +generated: "2021-04-22T16:13:57.586041-07:00" diff --git a/kubernetes/helm/open-superset-ui.sh b/helm/open-superset-ui.sh similarity index 100% rename from kubernetes/helm/open-superset-ui.sh rename to helm/open-superset-ui.sh diff --git a/kubernetes/helm/pinot-0.2.0.tgz b/helm/pinot-0.2.0.tgz similarity index 100% rename from kubernetes/helm/pinot-0.2.0.tgz rename to helm/pinot-0.2.0.tgz diff --git a/kubernetes/helm/pinot-0.2.1.tgz b/helm/pinot-0.2.1.tgz similarity index 100% rename from kubernetes/helm/pinot-0.2.1.tgz rename to helm/pinot-0.2.1.tgz diff --git a/kubernetes/helm/pinot-0.2.2.tgz b/helm/pinot-0.2.2.tgz similarity index 100% rename from kubernetes/helm/pinot-0.2.2.tgz rename to helm/pinot-0.2.2.tgz diff --git a/kubernetes/helm/pinot-0.2.3.tgz b/helm/pinot-0.2.3.tgz similarity index 100% rename from kubernetes/helm/pinot-0.2.3.tgz rename to helm/pinot-0.2.3.tgz diff --git a/kubernetes/helm/pinot-0.2.4.tgz b/helm/pinot-0.2.4.tgz similarity index 100% rename from kubernetes/helm/pinot-0.2.4.tgz rename to helm/pinot-0.2.4.tgz diff --git a/kubernetes/helm/pinot-0.2.5.tgz b/helm/pinot-0.2.5.tgz similarity index 100% rename from kubernetes/helm/pinot-0.2.5.tgz rename to helm/pinot-0.2.5.tgz diff --git a/kubernetes/helm/pinot-0.2.6.tgz b/helm/pinot-0.2.6.tgz similarity index 100% rename from kubernetes/helm/pinot-0.2.6.tgz rename to helm/pinot-0.2.6.tgz diff --git a/helm/pinot-0.2.7.tgz b/helm/pinot-0.2.7.tgz new file mode 100644 index 000000000000..0bffc948defb Binary files /dev/null and b/helm/pinot-0.2.7.tgz differ diff --git a/helm/pinot-0.2.8.tgz b/helm/pinot-0.2.8.tgz new file mode 100644 index 000000000000..d189c0689270 Binary files /dev/null and b/helm/pinot-0.2.8.tgz differ diff --git a/helm/pinot-0.2.9.tgz b/helm/pinot-0.2.9.tgz new file mode 100644 index 000000000000..20c345d6247f Binary files /dev/null and b/helm/pinot-0.2.9.tgz differ diff --git a/helm/pinot/Chart.yaml b/helm/pinot/Chart.yaml new file mode 100644 index 000000000000..8f3518e86313 --- /dev/null +++ b/helm/pinot/Chart.yaml @@ -0,0 +1,35 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +apiVersion: v1 +appVersion: 1.0.0 +name: pinot +description: Apache Pinot is a realtime distributed OLAP datastore, which is used to deliver scalable real time analytics with low latency. It can ingest data from offline data sources (such as Hadoop and flat files) as well as online sources (such as Kafka). Pinot is designed to scale horizontally. +version: 0.3.0-SNAPSHOT +keywords: + - olap + - analytics + - database + - pinot +home: https://pinot.apache.org/ +sources: + - https://github.com/apache/pinot/tree/master/helm +maintainers: + - name: pinot-dev + email: dev@pinot.apache.org diff --git a/helm/pinot/README.md b/helm/pinot/README.md new file mode 100644 index 000000000000..93339cf4bfb6 --- /dev/null +++ b/helm/pinot/README.md @@ -0,0 +1,457 @@ + + +# Pinot Quickstart on Kubernetes with Helm + +## Prerequisite + +- kubectl () +- Helm () +- Configure kubectl to connect to the Kubernetes cluster. + - Skip to [Section: How to setup a Pinot cluster for demo](#How to setup a Pinot cluster for demo) if a k8s cluster is already setup. + + +## (Optional) Setup a Kubernetes cluster on Amazon Elastic Kubernetes Service (Amazon EKS) + +### (Optional) Create a new k8s cluster on AWS EKS + +- Install AWS CLI () +- Install AWS-IAM-AUTHENTICATOR () +- Install eksctl () + +- Login to your AWS account. + +```bash +aws configure +``` + +Note that environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` will override the aws configuration in file `~/.aws/credentials`. + +- Create an EKS cluster + +Please modify the parameters in the example command below: + +```bash +eksctl create cluster \ +--name pinot-quickstart \ +--version 1.14 \ +--region us-west-2 \ +--nodegroup-name standard-workers \ +--node-type t3.small \ +--nodes 3 \ +--nodes-min 3 \ +--nodes-max 4 \ +--node-ami auto +``` + +For k8s 1.23+ we need to run the following commands to allow the containers to provision their storage +``` +eksctl utils associate-iam-oidc-provider --region=us-east-2 --cluster=pinot-quickstart --approve + +eksctl create iamserviceaccount \ + --name ebs-csi-controller-sa \ + --namespace kube-system \ + --cluster pinot-quickstart \ + --attach-policy-arn arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy \ + --approve \ + --role-only \ + --role-name AmazonEKS_EBS_CSI_DriverRole + +eksctl create addon --name aws-ebs-csi-driver --cluster pinot-quickstart --service-account-role-arn arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):role/AmazonEKS_EBS_CSI_DriverRole --force +``` + +You can monitor cluster status by command: + +```bash +EKS_CLUSTER_NAME=pinot-quickstart +aws eks describe-cluster --name ${EKS_CLUSTER_NAME} +``` + +Once the cluster is in `ACTIVE` status, it's ready to be used. + +### (Optional) How to connect to an existing cluster + +Simply run below command to get the credential for the cluster you just created or your existing cluster. + +```bash +EKS_CLUSTER_NAME=pinot-quickstart +aws eks update-kubeconfig --name ${EKS_CLUSTER_NAME} +``` + +To verify the connection, you can run + +```bash +kubectl get nodes +``` + +## (Optional) Setup a Kubernetes cluster on Google Kubernetes Engine(GKE) + +### (Optional) Create a new k8s cluster on GKE + +- Google Cloud SDK () +- Enable Google Cloud Account and create a project, e.g. `pinot-demo`. + - `pinot-demo` will be used as example value for `${GCLOUD_PROJECT}` variable in script example. + - `pinot-demo@example.com` will be used as example value for `${GCLOUD_EMAIL}`. + +Below script will: + +- Create a gCloud cluster `pinot-quickstart` +- Request 2 servers of type `n1-standard-8` for demo. + +Please fill both environment variables: `${GCLOUD_PROJECT}` and `${GCLOUD_EMAIL}` with your gcloud project and gcloud account email in below script. + +```bash +GCLOUD_PROJECT=[your gcloud project name] +GCLOUD_EMAIL=[Your gcloud account email] +./setup_gke.sh +``` + +E.g. + +```bash +GCLOUD_PROJECT=pinot-demo +GCLOUD_EMAIL=pinot-demo@example.com +./setup_gke.sh +``` + +### (Optional) How to connect to an existing GKE cluster + +Simply run below command to get the credential for the cluster you just created or your existing cluster. +Please modify the Env variables `${GCLOUD_PROJECT}`, `${GCLOUD_ZONE}`, `${GCLOUD_CLUSTER}` accordingly in below script. + +```bash +GCLOUD_PROJECT=pinot-demo +GCLOUD_ZONE=us-west1-b +GCLOUD_CLUSTER=pinot-quickstart +gcloud container clusters get-credentials ${GCLOUD_CLUSTER} --zone ${GCLOUD_ZONE} --project ${GCLOUD_PROJECT} +``` + +## (Optional) Setup a Kubernetes cluster on Microsoft Azure + +### (Optional) Create a new k8s cluster on Azure + +- Install Azure CLI () +- Login to your Azure account. + +```bash +az login +``` + +- Create Resource Group + +```bash +AKS_RESOURCE_GROUP=pinot-demo +AKS_RESOURCE_GROUP_LOCATION=eastus +az group create --name ${AKS_RESOURCE_GROUP} --location ${AKS_RESOURCE_GROUP_LOCATION} +``` + +- Create an AKS cluster + +```bash +AKS_RESOURCE_GROUP=pinot-demo +AKS_CLUSTER_NAME=pinot-quickstart +az aks create --resource-group ${AKS_RESOURCE_GROUP} --name ${AKS_CLUSTER_NAME} --node-count 3 +``` + +(Optional) Please register default provider if above command failed for error: `MissingSubscriptionRegistration` + +```bash +az provider register --namespace Microsoft.Network +``` + +### (Optional) How to connect to an existing AKS cluster + +Simply run below command to get the credential for the cluster you just created or your existing cluster. + +```bash +AKS_RESOURCE_GROUP=pinot-demo +AKS_CLUSTER_NAME=pinot-quickstart +az aks get-credentials --resource-group ${AKS_RESOURCE_GROUP} --name ${AKS_CLUSTER_NAME} +``` + +To verify the connection, you can run + +```bash +kubectl get nodes +``` + +## How to setup a Pinot cluster for demo + +### Update helm dependency + +```bash +helm dependency update +``` + +### Start Pinot with Helm + +- For helm v3.X.X + +```bash +kubectl create ns pinot-quickstart +helm install pinot -n pinot-quickstart . +``` + +- For helm v3.X.X + +```bash +kubectl create ns pinot-quickstart + +# First run dry-run with debug to verify: +helm install -n pinot-quickstart pinot . --dry-run --debug + +# Install the Helm chart with: +helm install -n pinot-quickstart pinot . +``` + +- For helm v2.12.1 + +If cluster is just initialized, ensure helm is initialized by running: + +```bash +helm init --service-account tiller +``` + +Then deploy pinot cluster by: + +```bash +helm install --namespace "pinot-quickstart" --name "pinot" . +``` + +### Troubleshooting (For helm v2.12.1) + +- Error: Please run below command if encountering issue: + +``` +Error: could not find tiller". +``` + +- Resolution: + +```bash +kubectl -n kube-system delete deployment tiller-deploy +kubectl -n kube-system delete service/tiller-deploy +helm init --service-account tiller +``` + +- Error: Please run below command if encountering permission issue: + +```Error: release pinot failed: namespaces "pinot-quickstart" is forbidden: User "system:serviceaccount:kube-system:default" cannot get resource "namespaces" in API group "" in the namespace "pinot-quickstart"``` + +- Resolution: + +```bash +kubectl apply -f helm-rbac.yaml +``` + +#### To check deployment status + +```bash +kubectl get all -n pinot-quickstart +``` + +### Pinot Realtime QuickStart + +#### Bring up a Kafka Cluster for realtime data ingestion + +- For helm v3.X.X + +```bash +helm repo add incubator https://charts.helm.sh/incubator +helm install -n pinot-quickstart kafka incubator/kafka --set replicas=1 +``` + +- For helm v2.12.1 + +```bash +helm repo add incubator https://charts.helm.sh/incubator +helm install --namespace "pinot-quickstart" --name kafka incubator/kafka --set replicas=1 +``` + +#### Create Kafka topic + +```bash +kubectl -n pinot-quickstart exec kafka-0 -- kafka-topics.sh --bootstrap-server kafka-0:9092 --topic flights-realtime --create --partitions 1 --replication-factor 1 +kubectl -n pinot-quickstart exec kafka-0 -- kafka-topics.sh --bootstrap-server kafka-0:9092 --topic flights-realtime-avro --create --partitions 1 --replication-factor 1 +``` + +#### Load data into Kafka and create Pinot schema/table + +```bash +kubectl apply -f pinot-realtime-quickstart.yml +``` + +### How to query pinot data + +Please use below script to do local port-forwarding and open Pinot query console on your web browser. + +```bash +./query-pinot-data.sh +``` + +## Configuring the Chart + +This chart includes a ZooKeeper chart as a dependency to the Pinot +cluster in its `requirement.yaml` by default. The chart can be customized using the +following configurable parameters: + +| Parameter | Description | Default | +|------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| +| `image.repository` | Pinot Container image repo | `apachepinot/pinot` | +| `image.tag` | Pinot Container image tag | `release-0.7.1` | +| `image.pullPolicy` | Pinot Container image pull policy | `IfNotPresent` | +| `cluster.name` | Pinot Cluster name | `pinot-quickstart` | +|------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| +| `controller.name` | Name of Pinot Controller | `controller` | +| `controller.port` | Pinot controller port | `9000` | +| `controller.replicaCount` | Pinot controller replicas | `1` | +| `controller.data.dir` | Pinot controller data directory, should be same as `controller.persistence.mountPath` or a sub directory of it | `/var/pinot/controller/data` | +| `controller.vip.enabled` | Enable Pinot controller Vip host | `false` | +| `controller.vip.host` | Pinot controller Vip host | `pinot-controller` | +| `controller.vip.port` | Pinot controller Vip port | `9000` | +| `controller.persistence.enabled` | Use a PVC to persist Pinot Controller data | `true` | +| `controller.persistence.accessMode` | Access mode of data volume | `ReadWriteOnce` | +| `controller.persistence.size` | Size of data volume | `1G` | +| `controller.persistence.mountPath` | Mount path of controller data volume | `/var/pinot/controller/data` | +| `controller.persistence.storageClass` | Storage class of backing PVC | `""` | +| `controller.jvmOpts` | Pinot Controller JVM Options | `-Xms256M -Xmx1G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime -Xloggc:/opt/pinot/gc-pinot-controller.log` | +| `controller.log4j2ConfFile` | Pinot Controller log4j2 configuration file | `/opt/pinot/conf/log4j2.xml` | +| `controller.pluginsDir` | Pinot Controller plugins directory | `/opt/pinot/plugins` | +| `controller.service.port` | Service Port | `9000` | +| `controller.external.enabled` | If True, exposes Pinot Controller externally | `true` | +| `controller.external.type` | Service Type | `LoadBalancer` | +| `controller.external.port` | Service Port | `9000` | +| `controller.resources` | Pinot Controller resource requests and limits | `{}` | +| `controller.nodeSelector` | Node labels for controller pod assignment | `{}` | +| `controller.affinity` | Defines affinities and anti-affinities for pods as defined in: preferences | `{}` | +| `controller.tolerations` | List of node tolerations for the pods. | `[]` | +| `controller.podAnnotations` | Annotations to be added to controller pod | `{}` | +| `controller.updateStrategy.type` | StatefulSet update strategy to use. | `RollingUpdate` | +| `controller.extra.configs` | Extra configs append to 'pinot-controller.conf' file to start Pinot Controller | `pinot.set.instance.id.to.hostname=true` | +|------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| +| `broker.name` | Name of Pinot Broker | `broker` | +| `broker.port` | Pinot broker port | `8099` | +| `broker.replicaCount` | Pinot broker replicas | `1` | +| `broker.jvmOpts` | Pinot Broker JVM Options | `-Xms256M -Xmx1G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime -Xloggc:/opt/pinot/gc-pinot-broker.log` | +| `broker.log4j2ConfFile` | Pinot Broker log4j2 configuration file | `/opt/pinot/conf/log4j2.xml` | +| `broker.pluginsDir` | Pinot Broker plugins directory | `/opt/pinot/plugins` | +| `broker.service.port` | Service Port | `8099` | +| `broker.external.enabled` | If True, exposes Pinot Broker externally | `true` | +| `broker.external.type` | External service Type | `LoadBalancer` | +| `broker.external.port` | External service Port | `8099` | +| `broker.routingTable.builderClass` | Routing Table Builder Class | `random` | +| `broker.resources` | Pinot Broker resource requests and limits | `{}` | +| `broker.nodeSelector` | Node labels for broker pod assignment | `{}` | +| `broker.affinity` | Defines affinities and anti-affinities for pods as defined in: preferences | `{}` | +| `broker.tolerations` | List of node tolerations for the pods. | `[]` | +| `broker.podAnnotations` | Annotations to be added to broker pod | `{}` | +| `broker.updateStrategy.type` | StatefulSet update strategy to use. | `RollingUpdate` | +| `broker.extra.configs` | Extra configs append to 'pinot-broker.conf' file to start Pinot Broker | `pinot.set.instance.id.to.hostname=true` | +|------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| +| `server.name` | Name of Pinot Server | `server` | +| `server.port.netty` | Pinot server netty port | `8098` | +| `server.port.admin` | Pinot server admin port | `8097` | +| `server.replicaCount` | Pinot server replicas | `1` | +| `server.dataDir` | Pinot server data directory, should be same as `server.persistence.mountPath` or a sub directory of it | `/var/pinot/server/data/index` | +| `server.segmentTarDir` | Pinot server segment directory, should be same as `server.persistence.mountPath` or a sub directory of it | `/var/pinot/server/data/segments` | +| `server.persistence.enabled` | Use a PVC to persist Pinot Server data | `true` | +| `server.persistence.accessMode` | Access mode of data volume | `ReadWriteOnce` | +| `server.persistence.size` | Size of data volume | `4G` | +| `server.persistence.mountPath` | Mount path of server data volume | `/var/pinot/server/data` | +| `server.persistence.storageClass` | Storage class of backing PVC | `""` | +| `server.probes.endpoint` | Pinot server liveness and readiness probes endpoint | `"/health"` | +| `server.probes.livenessEnabled` | Whether to enable Pinot server liveness probe | `false` | +| `server.probes.livenessProbe.endpoint` | Optional parameter. Specify a specific Pinot server liveness probe endpoint instead of the shared `server.probes.endpoint`, You should use `"/health?checkType=liveness"` | Optional param, no default value | +| `server.probes.readinessEnabled` | Whether to enable Pinot server readiness probe | `false` | +| `server.probes.readinessProbe.endpoint` | Optional parameter. Specify a specific Pinot server readiness probe endpoint instead of the shared `server.probes.endpoint`, You should use `"/health?checkType=readiness"` | Optional param, no default value | +| `server.jvmOpts` | Pinot Server JVM Options | `-Xms512M -Xmx1G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime -Xloggc:/opt/pinot/gc-pinot-server.log` | +| `server.log4j2ConfFile` | Pinot Server log4j2 configuration file | `/opt/pinot/conf/log4j2.xml` | +| `server.pluginsDir` | Pinot Server plugins directory | `/opt/pinot/plugins` | +| `server.service.port` | Service Port | `8098` | +| `server.resources` | Pinot Server resource requests and limits | `{}` | +| `server.nodeSelector` | Node labels for server pod assignment | `{}` | +| `server.affinity` | Defines affinities and anti-affinities for pods as defined in: preferences | `{}` | +| `server.tolerations` | List of node tolerations for the pods. | `[]` | +| `server.podAnnotations` | Annotations to be added to server pod | `{}` | +| `server.updateStrategy.type` | StatefulSet update strategy to use. | `RollingUpdate` | +| `server.extra.configs` | Extra configs append to 'pinot-server.conf' file to start Pinot Server | `pinot.set.instance.id.to.hostname=true` | +|------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| +| `minion.name` | Name of Pinot Minion | `minion` | +| `minion.port` | Pinot minion netty port | `9514` | +| `minion.replicaCount` | Pinot minion replicas | `1` | +| `minion.dataDir` | Pinot minion data directory, should be same as `minion.persistence.mountPath` or a sub directory of it | `/var/pinot/minion/data` | +| `minion.persistence.enabled` | Use a PVC to persist Pinot minion data | `true` | +| `minion.persistence.accessMode` | Access mode of data volume | `ReadWriteOnce` | +| `minion.persistence.size` | Size of data volume | `4G` | +| `minion.persistence.mountPath` | Mount path of minion data volume | `/var/pinot/minion/data` | +| `minion.persistence.storageClass` | Storage class of backing PVC | `""` | +| `minion.jvmOpts` | Pinot minion JVM Options | `-Xms512M -Xmx1G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime -Xloggc:/opt/pinot/gc-pinot-minion.log` | +| `minion.log4j2ConfFile` | Pinot minion log4j2 configuration file | `/opt/pinot/conf/log4j2.xml` | +| `minion.pluginsDir` | Pinot minion plugins directory | `/opt/pinot/plugins` | +| `minion.service.port` | Service Port | `9514` | +| `minion.resources` | Pinot minion resource requests and limits | `{}` | +| `minion.nodeSelector` | Node labels for minion pod assignment | `{}` | +| `minion.affinity` | Defines affinities and anti-affinities for pods as defined in: preferences | `{}` | +| `minion.tolerations` | List of node tolerations for the pods. | `[]` | +| `minion.podAnnotations` | Annotations to be added to minion pod | `{}` | +| `minion.updateStrategy.type` | StatefulSet update strategy to use. | `RollingUpdate` | +| `minion.extra.configs` | Extra configs append to 'pinot-minion.conf' file to start Pinot Minion | `pinot.set.instance.id.to.hostname=true` | +|------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| +| `zookeeper.enabled` | If True, installs Zookeeper Chart | `true` | +| `zookeeper.resources` | Zookeeper resource requests and limits | `{}` | +| `zookeeper.env` | Environmental variables provided to Zookeeper Zookeeper | `{ZK_HEAP_SIZE: "256M"}` | +| `zookeeper.storage` | Zookeeper Persistent volume size | `2Gi` | +| `zookeeper.image.tag` | Zookeeper Image Version +| `zookeeper.image.PullPolicy` | Zookeeper Container pull policy | `IfNotPresent` | +| `zookeeper.url` | URL of Zookeeper Cluster (unneeded if installing Zookeeper Chart) | `""` | +| `zookeeper.port` | Port of Zookeeper Cluster | `2181` | +| `zookeeper.affinity` | Defines affinities and anti-affinities for pods as defined in: preferences | `{}` | +|------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| + +Specify parameters using `--set key=value[,key=value]` argument to `helm install` + +```bash +helm install --name pinot -f values.yaml . --set server.replicaCount=2 +``` + +Alternatively a YAML file that specifies the values for the parameters can be provided like this: + +```bash +helm install --name pinot -f values.yaml . +``` + +If you are using GKE, Create a storageClass: + +``` +kubectl apply -f gke-ssd.yaml +``` + +or If you want to use pd-standard storageClass: + +```bash +kubectl apply -f gke-pd.yaml +``` + +## How to clean up Pinot deployment + +```bash +kubectl delete ns pinot-quickstart +``` diff --git a/helm/pinot/charts/zookeeper-13.2.0.tgz b/helm/pinot/charts/zookeeper-13.2.0.tgz new file mode 100644 index 000000000000..d044e68b8f0e Binary files /dev/null and b/helm/pinot/charts/zookeeper-13.2.0.tgz differ diff --git a/kubernetes/helm/pinot/pinot-github-events-setup.yml b/helm/pinot/pinot-github-events-setup.yml similarity index 100% rename from kubernetes/helm/pinot/pinot-github-events-setup.yml rename to helm/pinot/pinot-github-events-setup.yml diff --git a/kubernetes/helm/pinot/pinot-realtime-quickstart.yml b/helm/pinot/pinot-realtime-quickstart.yml similarity index 100% rename from kubernetes/helm/pinot/pinot-realtime-quickstart.yml rename to helm/pinot/pinot-realtime-quickstart.yml diff --git a/kubernetes/helm/pinot/query-pinot-data.sh b/helm/pinot/query-pinot-data.sh similarity index 100% rename from kubernetes/helm/pinot/query-pinot-data.sh rename to helm/pinot/query-pinot-data.sh diff --git a/kubernetes/helm/pinot/requirements.lock b/helm/pinot/requirements.lock similarity index 85% rename from kubernetes/helm/pinot/requirements.lock rename to helm/pinot/requirements.lock index 4a3dfaaea73e..3269c660f02d 100644 --- a/kubernetes/helm/pinot/requirements.lock +++ b/helm/pinot/requirements.lock @@ -20,6 +20,6 @@ dependencies: - name: zookeeper repository: https://charts.bitnami.com/bitnami - version: 9.2.7 -digest: sha256:a5da7ddd352d63b0a0e1e5bd85e90e304ae5d0fa7759d5cb7ffb39f61adef1e9 -generated: "2022-06-20T14:57:34.981883-07:00" + version: 13.2.0 +digest: sha256:d9252aa40c5511fa1124c0db142b3222f8aff999ee0add757bbb6f19b6f45826 +generated: "2024-05-05T19:02:58.334678-04:00" diff --git a/kubernetes/helm/pinot/requirements.yaml b/helm/pinot/requirements.yaml similarity index 97% rename from kubernetes/helm/pinot/requirements.yaml rename to helm/pinot/requirements.yaml index ece3625593cb..23448f616cbb 100644 --- a/kubernetes/helm/pinot/requirements.yaml +++ b/helm/pinot/requirements.yaml @@ -19,6 +19,6 @@ dependencies: - name: zookeeper - version: 9.x.x + version: 13.x.x repository: https://charts.bitnami.com/bitnami condition: pinot.zookeeper.enabled,zookeeper.enabled diff --git a/helm/pinot/templates/_helpers.tpl b/helm/pinot/templates/_helpers.tpl new file mode 100644 index 000000000000..436cf45fd71e --- /dev/null +++ b/helm/pinot/templates/_helpers.tpl @@ -0,0 +1,332 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +{{/* +Expand the name of the chart. +*/}} +{{- define "pinot.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "pinot.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "pinot.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Match Selector labels +*/}} +{{- define "pinot.matchLabels" -}} +app: {{ include "pinot.name" . }} +release: {{ .Release.Name }} +{{- range $key, $value := .Values.additionalMatchLabels }} +{{ $key }}: {{ $value }} +{{- end }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "pinot.labels" -}} +helm.sh/chart: {{ include "pinot.chart" . }} +{{ include "pinot.matchLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +heritage: {{ .Release.Service }} +{{- end }} + +{{/* +Broker labels +*/}} +{{- define "pinot.brokerLabels" -}} +{{- include "pinot.labels" . }} +component: {{ .Values.broker.name }} +{{- end }} + + +{{/* +Controller labels +*/}} +{{- define "pinot.controllerLabels" -}} +{{- include "pinot.labels" . }} +component: {{ .Values.controller.name }} +{{- end }} + +{{/* +Minion labels +*/}} +{{- define "pinot.minionLabels" -}} +{{- include "pinot.labels" . }} +component: {{ .Values.minion.name }} +{{- end }} + +{{/* +minionStateless labels +*/}} +{{- define "pinot.minionStatelessLabels" -}} +{{- include "pinot.labels" . }} +component: {{ .Values.minionStateless.name }} +{{- end }} + +{{/* +Server labels +*/}} +{{- define "pinot.serverLabels" -}} +{{- include "pinot.labels" . }} +component: {{ .Values.server.name }} +{{- end }} + + + +{{/* +Broker Match Selector labels +*/}} +{{- define "pinot.brokerMatchLabels" -}} +{{- include "pinot.matchLabels" . }} +component: {{ .Values.broker.name }} +{{- end }} + +{{/* +Controller Match Selector labels +*/}} +{{- define "pinot.controllerMatchLabels" -}} +{{- include "pinot.matchLabels" . }} +component: {{ .Values.controller.name }} +{{- end }} + + +{{/* +Minion Match Selector labels +*/}} +{{- define "pinot.minionMatchLabels" -}} +{{- include "pinot.matchLabels" . }} +component: {{ .Values.minion.name }} +{{- end }} + + +{{/* +MinionStateless Match Selector labels +*/}} +{{- define "pinot.minionStatelessMatchLabels" -}} +{{- include "pinot.matchLabels" . }} +component: {{ .Values.minionStateless.name }} +{{- end }} + + +{{/* +Server Match Selector labels +*/}} +{{- define "pinot.serverMatchLabels" -}} +{{- include "pinot.matchLabels" . }} +component: {{ .Values.server.name }} +{{- end }} + + +{{/* +Create the name of the service account to use for pinot components +*/}} +{{- define "pinot.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "pinot.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + + +{{/* +Create a default fully qualified zookeeper name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "pinot.zookeeper.fullname" -}} +{{- if .Values.zookeeper.fullnameOverride -}} +{{- .Values.zookeeper.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default "zookeeper" .Values.zookeeper.nameOverride -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} + +{{/* +Form the Zookeeper URL. If zookeeper is installed as part of this chart, use k8s service discovery, +else use user-provided URL +*/}} +{{- define "zookeeper.url" }} +{{- $port := .Values.zookeeper.port | toString }} +{{- if .Values.zookeeper.enabled -}} +{{- printf "%s:%s" (include "pinot.zookeeper.fullname" .) $port }} +{{- else -}} +{{- required "Missing 'zookeeper.urlOverride' entry zookeeper is disabled!" .Values.zookeeper.urlOverride }} +{{- end -}} +{{- end -}} + +{{/* +Create a default fully qualified pinot controller name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "pinot.controller.fullname" -}} +{{ template "pinot.fullname" . }}-{{ .Values.controller.name }} +{{- end -}} + + +{{/* +Create a default fully qualified pinot broker name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "pinot.broker.fullname" -}} +{{ template "pinot.fullname" . }}-{{ .Values.broker.name }} +{{- end -}} + + +{{/* +Create a default fully qualified pinot server name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "pinot.server.fullname" -}} +{{ template "pinot.fullname" . }}-{{ .Values.server.name }} +{{- end -}} + + +{{/* +Create a default fully qualified pinot minion name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "pinot.minion.fullname" -}} +{{ template "pinot.fullname" . }}-{{ .Values.minion.name }} +{{- end -}} + + +{{/* +Create a default fully qualified pinot minion stateless name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +*/}} +{{- define "pinot.minionStateless.fullname" -}} +{{ template "pinot.fullname" . }}-{{ .Values.minionStateless.name }} +{{- end -}} + +{{/* +The name of the pinot controller headless service. +*/}} +{{- define "pinot.controller.headless" -}} +{{- printf "%s-headless" (include "pinot.controller.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +The name of the pinot broker headless service. +*/}} +{{- define "pinot.broker.headless" -}} +{{- printf "%s-headless" (include "pinot.broker.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +The name of the pinot server headless service. +*/}} +{{- define "pinot.server.headless" -}} +{{- printf "%s-headless" (include "pinot.server.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +The name of the pinot minion headless service. +*/}} +{{- define "pinot.minion.headless" -}} +{{- printf "%s-headless" (include "pinot.minion.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +The name of the pinot controller external service. +*/}} +{{- define "pinot.controller.external" -}} +{{- printf "%s-external" (include "pinot.controller.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +The name of the pinot broker external service. +*/}} +{{- define "pinot.broker.external" -}} +{{- printf "%s-external" (include "pinot.broker.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +The name of the pinot controller config. +*/}} +{{- define "pinot.controller.config" -}} +{{- printf "%s-config" (include "pinot.controller.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +The name of the pinot broker config. +*/}} +{{- define "pinot.broker.config" -}} +{{- printf "%s-config" (include "pinot.broker.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +The name of the pinot server config. +*/}} +{{- define "pinot.server.config" -}} +{{- printf "%s-config" (include "pinot.server.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +The name of the pinot minion config. +*/}} +{{- define "pinot.minion.config" -}} +{{- printf "%s-config" (include "pinot.minion.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +The name of the pinot minion stateless config. +*/}} +{{- define "pinot.minionStateless.config" -}} +{{- printf "%s-config" (include "pinot.minionStateless.fullname" .) | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Return pinot namespace to use +*/}} +{{- define "pinot.namespace" -}} +{{- if .Values.namespaceOverride -}} + {{- .Values.namespaceOverride -}} +{{- else -}} + {{- .Release.Namespace -}} +{{- end -}} +{{- end -}} diff --git a/helm/pinot/templates/broker/configmap.yaml b/helm/pinot/templates/broker/configmap.yaml new file mode 100644 index 000000000000..6a4447f707c6 --- /dev/null +++ b/helm/pinot/templates/broker/configmap.yaml @@ -0,0 +1,35 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "pinot.broker.config" . }} + namespace: {{ include "pinot.namespace" . }} +data: + pinot-broker.conf: |- + pinot.broker.client.queryPort={{ .Values.broker.service.port }} + pinot.broker.routing.table.builder.class={{ .Values.broker.routingTable.builderClass }} +{{ .Values.broker.extra.configs | indent 4 }} +{{- if .Values.pinotAuth.enabled}} + pinot.broker.access.control.class={{ .Values.pinotAuth.brokerFactoryClass }} +{{- range $config := .Values.pinotAuth.configs}} +{{ printf "pinot.broker.%s" $config | indent 4 -}} +{{- end }} +{{- end }} diff --git a/helm/pinot/templates/broker/ingress-v1.yaml b/helm/pinot/templates/broker/ingress-v1.yaml new file mode 100644 index 000000000000..5ed2ce14b714 --- /dev/null +++ b/helm/pinot/templates/broker/ingress-v1.yaml @@ -0,0 +1,56 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +{{- if .Values.broker.ingress.v1.enabled }} +{{- $ingressPath := .Values.broker.ingress.v1.path }} +{{- $serviceName := include "pinot.broker.fullname" . }} +{{- $servicePort := .Values.broker.service.port }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $serviceName }} + namespace: {{ include "pinot.namespace" . }} +{{- if .Values.broker.ingress.v1.annotations }} + annotations: +{{ toYaml .Values.broker.ingress.v1.annotations | indent 4 }} +{{- end }} + labels: +{{- include "pinot.brokerLabels" . | nindent 4 }} +spec: +{{- if .Values.broker.ingress.v1.ingressClassName }} + ingressClassName: {{ .Values.broker.ingress.v1.ingressClassName }} +{{- end }} +{{- if .Values.broker.ingress.v1.tls }} + tls: +{{ toYaml .Values.broker.ingress.v1.tls | indent 4 }} +{{- end }} + rules: + {{- range .Values.broker.ingress.v1.hosts }} + - host: {{ . }} + http: + paths: + - path: {{ $ingressPath }} + pathType: Prefix + backend: + service: + name: {{ $serviceName }} + port: + number: {{ $servicePort }} + {{- end }} +{{- end }} diff --git a/helm/pinot/templates/broker/ingress-v1beta1.yaml b/helm/pinot/templates/broker/ingress-v1beta1.yaml new file mode 100644 index 000000000000..bb6467e84b8f --- /dev/null +++ b/helm/pinot/templates/broker/ingress-v1beta1.yaml @@ -0,0 +1,50 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +{{- if .Values.broker.ingress.v1beta1.enabled }} +{{- $ingressPath := .Values.broker.ingress.v1beta1.path }} +{{- $serviceName := include "pinot.broker.fullname" . }} +{{- $servicePort := .Values.broker.service.port }} +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: {{ $serviceName }} + namespace: {{ include "pinot.namespace" . }} +{{- if .Values.broker.ingress.v1beta1.annotations }} + annotations: +{{ toYaml .Values.broker.ingress.v1beta1.annotations | indent 4 }} +{{- end }} + labels: +{{- include "pinot.brokerLabels" . | nindent 4 }} +spec: +{{- if .Values.broker.ingress.v1beta1.tls }} + tls: +{{ toYaml .Values.broker.ingress.v1beta1.tls | indent 4 }} +{{- end }} + rules: + {{- range .Values.broker.ingress.v1beta1.hosts }} + - host: {{ . }} + http: + paths: + - path: {{ $ingressPath }} + backend: + serviceName: {{ $serviceName }} + servicePort: {{ $servicePort }} + {{- end }} +{{- end }} diff --git a/helm/pinot/templates/broker/poddisruptionbudget.yaml b/helm/pinot/templates/broker/poddisruptionbudget.yaml new file mode 100644 index 000000000000..0be27bd7123e --- /dev/null +++ b/helm/pinot/templates/broker/poddisruptionbudget.yaml @@ -0,0 +1,36 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +{{- if .Values.broker.pdb.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "pinot.broker.fullname" . }} + namespace: {{ include "pinot.namespace" . }} +spec: + {{- if .Values.broker.pdb.minAvailable }} + minAvailable: {{ .Values.broker.pdb.minAvailable }} + {{- end }} + {{- if .Values.broker.pdb.maxUnavailable }} + maxUnavailable: {{ .Values.broker.pdb.maxUnavailable }} + {{- end }} + selector: + matchLabels: + {{- include "pinot.brokerMatchLabels" . | nindent 6 }} +{{- end }} diff --git a/helm/pinot/templates/broker/service-external.yaml b/helm/pinot/templates/broker/service-external.yaml new file mode 100644 index 000000000000..b340ef97dc72 --- /dev/null +++ b/helm/pinot/templates/broker/service-external.yaml @@ -0,0 +1,41 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +{{- if .Values.broker.external.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "pinot.broker.external" . }} + namespace: {{ include "pinot.namespace" . }} + annotations: +{{ toYaml .Values.broker.external.annotations | indent 4 }} + labels: + {{- include "pinot.brokerLabels" . | nindent 4 }} +spec: + type: {{ .Values.broker.external.type }} + ports: + - name: external-broker + port: {{ .Values.broker.external.port }} + selector: + {{- include "pinot.brokerMatchLabels" . | nindent 4 }} +{{- with .Values.broker.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: +{{ toYaml . | indent 4 }} +{{- end }} +{{- end }} diff --git a/helm/pinot/templates/broker/service-headless.yaml b/helm/pinot/templates/broker/service-headless.yaml new file mode 100644 index 000000000000..1fef96eb7928 --- /dev/null +++ b/helm/pinot/templates/broker/service-headless.yaml @@ -0,0 +1,40 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +apiVersion: v1 +kind: Service +metadata: + name: {{ template "pinot.broker.headless" . }} + namespace: {{ include "pinot.namespace" . }} + labels: + {{- include "pinot.brokerLabels" . | nindent 4 }} +spec: + clusterIP: None + ports: + # [pod_name].[service_name].[namespace].svc.cluster.local + - name: {{ .Values.broker.service.name }} + port: {{ .Values.broker.service.port }} + {{- if .Values.broker.service.extraPorts }} + {{- range .Values.broker.service.extraPorts }} + - name: {{ .name }} + port: {{ .containerPort }} + {{- end }} + {{- end }} + selector: + {{- include "pinot.brokerMatchLabels" . | nindent 4 }} diff --git a/helm/pinot/templates/broker/service.yaml b/helm/pinot/templates/broker/service.yaml new file mode 100644 index 000000000000..0f6dae17c3be --- /dev/null +++ b/helm/pinot/templates/broker/service.yaml @@ -0,0 +1,42 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +apiVersion: v1 +kind: Service +metadata: + name: {{ include "pinot.broker.fullname" . }} + namespace: {{ include "pinot.namespace" . }} + annotations: +{{ toYaml .Values.broker.service.annotations | indent 4 }} + labels: + {{- include "pinot.brokerLabels" . | nindent 4 }} +spec: + type: {{ .Values.broker.service.type }} + ports: + # [pod_name].[service_name].[namespace].svc.cluster.local + - name: {{ .Values.broker.service.name }} + port: {{ .Values.broker.service.port }} + {{- if .Values.broker.service.extraPorts }} + {{- range .Values.broker.service.extraPorts }} + - name: {{ .name }} + port: {{ .containerPort }} + {{- end }} + {{- end }} + selector: + {{- include "pinot.brokerMatchLabels" . | nindent 4 }} diff --git a/helm/pinot/templates/broker/statefulset.yaml b/helm/pinot/templates/broker/statefulset.yaml new file mode 100644 index 000000000000..42267918314b --- /dev/null +++ b/helm/pinot/templates/broker/statefulset.yaml @@ -0,0 +1,137 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "pinot.broker.fullname" . }} + namespace: {{ include "pinot.namespace" . }} + labels: + {{- include "pinot.brokerLabels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "pinot.brokerMatchLabels" . | nindent 6 }} + serviceName: {{ template "pinot.broker.headless" . }} + replicas: {{ .Values.broker.replicaCount }} + updateStrategy: + type: {{ .Values.broker.updateStrategy.type }} + podManagementPolicy: {{ .Values.broker.podManagementPolicy }} + template: + metadata: + labels: + {{- include "pinot.brokerLabels" . | nindent 8 }} + annotations: + {{- if .Values.broker.automaticReload.enabled }} + checksum/config: {{ include (print $.Template.BasePath "/broker/configmap.yaml") . | sha256sum }} + {{- end }} + {{- with .Values.broker.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + serviceAccountName: {{ include "pinot.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.broker.podSecurityContext | nindent 8 }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + nodeSelector: +{{ toYaml .Values.broker.nodeSelector | indent 8 }} + affinity: +{{ toYaml .Values.broker.affinity | indent 8 }} + tolerations: +{{ toYaml .Values.broker.tolerations | indent 8 }} + containers: + - name: broker + securityContext: + {{- toYaml .Values.broker.securityContext | nindent 10 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + args: [ + "{{ .Values.broker.startCommand }}", + "-clusterName", "{{ .Values.cluster.name }}", + "-zkAddress", {{ include "zookeeper.url" . | quote }}, + "-configFileName", "/var/pinot/broker/config/pinot-broker.conf" + ] + env: + - name: JAVA_OPTS + value: "{{ .Values.broker.jvmOpts }} -Dlog4j2.configurationFile={{ .Values.broker.log4j2ConfFile }} -Dplugins.dir={{ .Values.broker.pluginsDir }}" +{{- if .Values.broker.extraEnv }} +{{ toYaml .Values.broker.extraEnv | indent 10 }} +{{- end }} + envFrom: +{{ toYaml .Values.broker.envFrom | indent 10 }} + ports: + - containerPort: {{ .Values.broker.service.port }} + protocol: {{ .Values.broker.service.protocol }} + name: {{ .Values.broker.service.name }} +{{- if .Values.broker.service.extraPorts }} +{{ toYaml .Values.broker.service.extraPorts | indent 10 }} +{{- end }} + volumeMounts: + - name: config + mountPath: /var/pinot/broker/config + {{- if ne (len .Values.broker.persistence.extraVolumeMounts) 0 }} +{{ toYaml .Values.broker.persistence.extraVolumeMounts | indent 10 }} + {{- end }} + {{- if .Values.broker.probes.livenessEnabled }} + livenessProbe: + initialDelaySeconds: {{ .Values.broker.probes.liveness.initialDelaySeconds | default .Values.probes.initialDelaySeconds }} + periodSeconds: {{ .Values.broker.probes.liveness.periodSeconds | default .Values.probes.periodSeconds }} + failureThreshold: {{ .Values.broker.probes.liveness.failureThreshold | default .Values.probes.failureThreshold }} + successThreshold: {{ .Values.broker.probes.liveness.successThreshold | default .Values.probes.successThreshold }} + timeoutSeconds: {{ .Values.broker.probes.liveness.timeoutSeconds | default .Values.probes.timeoutSeconds }} + httpGet: + path: {{ .Values.broker.probes.endpoint }} + port: {{ .Values.broker.service.port }} + {{- end }} + {{- if .Values.broker.probes.readinessEnabled }} + readinessProbe: + initialDelaySeconds: {{ .Values.broker.probes.readiness.initialDelaySeconds | default .Values.probes.initialDelaySeconds }} + periodSeconds: {{ .Values.broker.probes.readiness.periodSeconds | default .Values.probes.periodSeconds }} + failureThreshold: {{ .Values.broker.probes.readiness.failureThreshold | default .Values.probes.failureThreshold }} + successThreshold: {{ .Values.broker.probes.readiness.successThreshold | default .Values.probes.successThreshold }} + timeoutSeconds: {{ .Values.broker.probes.readiness.timeoutSeconds | default .Values.probes.timeoutSeconds }} + httpGet: + path: {{ .Values.broker.probes.endpoint }} + port: {{ .Values.broker.service.port }} + {{- end }} + {{- if .Values.broker.probes.startupEnabled }} + startupProbe: + initialDelaySeconds: {{ .Values.broker.probes.startup.initialDelaySeconds | default .Values.probes.initialDelaySeconds }} + periodSeconds: {{ .Values.broker.probes.startup.periodSeconds | default .Values.probes.periodSeconds }} + failureThreshold: {{ .Values.broker.probes.startup.failureThreshold | default .Values.probes.failureThreshold }} + successThreshold: {{ .Values.broker.probes.startup.successThreshold | default .Values.probes.successThreshold }} + timeoutSeconds: {{ .Values.broker.probes.startup.timeoutSeconds | default .Values.probes.timeoutSeconds }} + httpGet: + path: {{ .Values.broker.probes.endpoint }} + port: {{ .Values.broker.service.port }} + {{- end }} + resources: +{{ toYaml .Values.broker.resources | indent 12 }} + restartPolicy: Always + volumes: + - name: config + configMap: + name: {{ include "pinot.broker.config" . }} + {{- if ne (len .Values.broker.persistence.extraVolumes) 0 }} +{{ toYaml .Values.broker.persistence.extraVolumes | indent 8 }} + {{- end }} diff --git a/helm/pinot/templates/controller/configmap.yaml b/helm/pinot/templates/controller/configmap.yaml new file mode 100644 index 000000000000..d3bc483e32cd --- /dev/null +++ b/helm/pinot/templates/controller/configmap.yaml @@ -0,0 +1,41 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "pinot.controller.config" . }} + namespace: {{ include "pinot.namespace" . }} +data: + pinot-controller.conf: |- + controller.helix.cluster.name={{ .Values.cluster.name }} + controller.port={{ .Values.controller.service.port }} +{{- if .Values.controller.vip.enabled }} + controller.vip.host={{ .Values.controller.vip.host }} + controller.vip.port={{ .Values.controller.vip.port }} +{{- end }} + controller.data.dir={{ .Values.controller.data.dir }} + controller.zk.str={{ include "zookeeper.url" . }} +{{ .Values.controller.extra.configs | indent 4 }} +{{- if .Values.pinotAuth.enabled}} + controller.admin.access.control.factory.class={{ .Values.pinotAuth.controllerFactoryClass }} +{{- range $config := .Values.pinotAuth.configs}} +{{ printf "controller.admin.%s" $config | indent 4 -}} +{{- end }} +{{- end }} diff --git a/helm/pinot/templates/controller/ingress-v1.yaml b/helm/pinot/templates/controller/ingress-v1.yaml new file mode 100644 index 000000000000..893256c20c48 --- /dev/null +++ b/helm/pinot/templates/controller/ingress-v1.yaml @@ -0,0 +1,56 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +{{- if .Values.controller.ingress.v1.enabled }} +{{- $ingressPath := .Values.controller.ingress.v1.path }} +{{- $serviceName := include "pinot.controller.fullname" . }} +{{- $servicePort := .Values.controller.service.port }} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ $serviceName }} + namespace: {{ include "pinot.namespace" . }} +{{- if .Values.controller.ingress.v1.annotations }} + annotations: +{{ toYaml .Values.controller.ingress.v1.annotations | indent 4 }} +{{- end }} + labels: +{{- include "pinot.controllerLabels" . | nindent 4 }} +spec: +{{- if .Values.controller.ingress.v1.ingressClassName }} + ingressClassName: {{ .Values.controller.ingress.v1.ingressClassName }} +{{- end }} +{{- if .Values.controller.ingress.v1.tls }} + tls: +{{ toYaml .Values.controller.ingress.v1.tls | indent 4 }} +{{- end }} + rules: + {{- range .Values.controller.ingress.v1.hosts }} + - host: {{ . }} + http: + paths: + - path: {{ $ingressPath }} + pathType: Prefix + backend: + service: + name: {{ $serviceName }} + port: + number: {{ $servicePort }} + {{- end }} +{{- end }} diff --git a/helm/pinot/templates/controller/ingress-v1beta1.yaml b/helm/pinot/templates/controller/ingress-v1beta1.yaml new file mode 100644 index 000000000000..d598ddf6f69c --- /dev/null +++ b/helm/pinot/templates/controller/ingress-v1beta1.yaml @@ -0,0 +1,50 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +{{- if .Values.controller.ingress.v1beta1.enabled }} +{{- $ingressPath := .Values.controller.ingress.v1beta1.path }} +{{- $serviceName := include "pinot.controller.fullname" . }} +{{- $servicePort := .Values.controller.service.port }} +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: {{ $serviceName }} + namespace: {{ include "pinot.namespace" . }} +{{- if .Values.controller.ingress.v1beta1.annotations }} + annotations: +{{ toYaml .Values.controller.ingress.v1beta1.annotations | indent 4 }} +{{- end }} + labels: +{{- include "pinot.controllerLabels" . | nindent 4 }} +spec: +{{- if .Values.controller.ingress.v1beta1.tls }} + tls: +{{ toYaml .Values.controller.ingress.v1beta1.tls | indent 4 }} +{{- end }} + rules: + {{- range .Values.controller.ingress.v1beta1.hosts }} + - host: {{ . }} + http: + paths: + - path: {{ $ingressPath }} + backend: + serviceName: {{ $serviceName }} + servicePort: {{ $servicePort }} + {{- end }} +{{- end }} diff --git a/helm/pinot/templates/controller/poddisruptionbudget.yaml b/helm/pinot/templates/controller/poddisruptionbudget.yaml new file mode 100644 index 000000000000..f1c0a1da8237 --- /dev/null +++ b/helm/pinot/templates/controller/poddisruptionbudget.yaml @@ -0,0 +1,36 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +{{- if .Values.controller.pdb.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "pinot.controller.fullname" . }} + namespace: {{ include "pinot.namespace" . }} +spec: + {{- if .Values.controller.pdb.minAvailable }} + minAvailable: {{ .Values.controller.pdb.minAvailable }} + {{- end }} + {{- if .Values.controller.pdb.maxUnavailable }} + maxUnavailable: {{ .Values.controller.pdb.maxUnavailable }} + {{- end }} + selector: + matchLabels: + {{- include "pinot.controllerMatchLabels" . | nindent 6 }} +{{- end }} diff --git a/helm/pinot/templates/controller/service-external.yaml b/helm/pinot/templates/controller/service-external.yaml new file mode 100644 index 000000000000..ec2f0fc68c2a --- /dev/null +++ b/helm/pinot/templates/controller/service-external.yaml @@ -0,0 +1,41 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +{{- if .Values.controller.external.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "pinot.controller.external" . }} + namespace: {{ include "pinot.namespace" . }} + annotations: +{{ toYaml .Values.controller.external.annotations | indent 4 }} + labels: + {{- include "pinot.controllerLabels" . | nindent 4 }} +spec: + type: {{ .Values.controller.external.type }} + ports: + - name: external-controller + port: {{ .Values.controller.external.port }} + selector: + {{- include "pinot.controllerMatchLabels" . | nindent 4 }} +{{- with .Values.controller.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: +{{ toYaml . | indent 4 }} +{{- end }} +{{- end }} diff --git a/helm/pinot/templates/controller/service-headless.yaml b/helm/pinot/templates/controller/service-headless.yaml new file mode 100644 index 000000000000..d7ff22ae16c3 --- /dev/null +++ b/helm/pinot/templates/controller/service-headless.yaml @@ -0,0 +1,40 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +apiVersion: v1 +kind: Service +metadata: + name: {{ template "pinot.controller.headless" . }} + namespace: {{ include "pinot.namespace" . }} + labels: + {{- include "pinot.controllerLabels" . | nindent 4 }} +spec: + clusterIP: None + ports: + # [pod_name].[service_name].[namespace].svc.cluster.local + - name: {{ .Values.controller.service.name }} + port: {{ .Values.controller.service.port }} + {{- if .Values.controller.service.extraPorts }} + {{- range .Values.controller.service.extraPorts }} + - name: {{ .name }} + port: {{ .containerPort }} + {{- end }} + {{- end }} + selector: + {{- include "pinot.controllerMatchLabels" . | nindent 4 }} diff --git a/helm/pinot/templates/controller/service.yaml b/helm/pinot/templates/controller/service.yaml new file mode 100644 index 000000000000..2e77c1359bd6 --- /dev/null +++ b/helm/pinot/templates/controller/service.yaml @@ -0,0 +1,42 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +apiVersion: v1 +kind: Service +metadata: + name: {{ include "pinot.controller.fullname" . }} + namespace: {{ include "pinot.namespace" . }} + annotations: +{{ toYaml .Values.controller.service.annotations | indent 4 }} + labels: + {{- include "pinot.controllerLabels" . | nindent 4 }} +spec: + type: {{ .Values.controller.service.type }} + ports: + # [pod_name].[service_name].[namespace].svc.cluster.local + - name: {{ .Values.controller.service.name }} + port: {{ .Values.controller.service.port }} + {{- if .Values.controller.service.extraPorts }} + {{- range .Values.controller.service.extraPorts }} + - name: {{ .name }} + port: {{ .containerPort }} + {{- end }} + {{- end }} + selector: + {{- include "pinot.controllerMatchLabels" . | nindent 4 }} diff --git a/helm/pinot/templates/controller/statefulset.yaml b/helm/pinot/templates/controller/statefulset.yaml new file mode 100644 index 000000000000..f0b6a51c292c --- /dev/null +++ b/helm/pinot/templates/controller/statefulset.yaml @@ -0,0 +1,156 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "pinot.controller.fullname" . }} + namespace: {{ include "pinot.namespace" . }} + labels: + {{- include "pinot.controllerLabels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "pinot.controllerMatchLabels" . | nindent 6 }} + serviceName: {{ template "pinot.controller.headless" . }} + replicas: {{ .Values.controller.replicaCount }} + updateStrategy: + type: {{ .Values.controller.updateStrategy.type }} + podManagementPolicy: {{ .Values.controller.podManagementPolicy }} + template: + metadata: + labels: + {{- include "pinot.controllerLabels" . | nindent 8 }} + annotations: + {{- if .Values.controller.automaticReload.enabled }} + checksum/config: {{ include (print $.Template.BasePath "/controller/configmap.yaml") . | sha256sum }} + {{- end }} + {{- with .Values.controller.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + serviceAccountName: {{ include "pinot.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.controller.podSecurityContext | nindent 8 }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + nodeSelector: +{{ toYaml .Values.controller.nodeSelector | indent 8 }} + affinity: +{{ toYaml .Values.controller.affinity | indent 8 }} + tolerations: +{{ toYaml .Values.controller.tolerations | indent 8 }} + containers: + - name: controller + securityContext: + {{- toYaml .Values.controller.securityContext | nindent 10 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + args: [ "{{ .Values.controller.startCommand }}", "-configFileName", "/var/pinot/controller/config/pinot-controller.conf" ] + env: + - name: JAVA_OPTS + value: "{{ .Values.controller.jvmOpts }} -Dlog4j2.configurationFile={{ .Values.controller.log4j2ConfFile }} -Dplugins.dir={{ .Values.controller.pluginsDir }}" +{{- if .Values.controller.extraEnv }} +{{ toYaml .Values.controller.extraEnv | indent 10 }} +{{- end }} + envFrom: +{{ toYaml .Values.controller.envFrom | indent 10 }} + ports: + - containerPort: {{ .Values.controller.service.port }} + protocol: {{ .Values.controller.service.protocol }} + name: {{ .Values.controller.service.name }} +{{- if .Values.controller.service.extraPorts }} +{{ toYaml .Values.controller.service.extraPorts | indent 10 }} +{{- end }} + {{- if .Values.controller.probes.livenessEnabled }} + livenessProbe: + initialDelaySeconds: {{ .Values.controller.probes.liveness.initialDelaySeconds | default .Values.probes.initialDelaySeconds }} + periodSeconds: {{ .Values.controller.probes.liveness.periodSeconds | default .Values.probes.periodSeconds }} + failureThreshold: {{ .Values.controller.probes.liveness.failureThreshold | default .Values.probes.failureThreshold }} + successThreshold: {{ .Values.controller.probes.liveness.successThreshold | default .Values.probes.successThreshold }} + timeoutSeconds: {{ .Values.controller.probes.liveness.timeoutSeconds | default .Values.probes.timeoutSeconds }} + httpGet: + path: {{ .Values.controller.probes.endpoint }} + port: {{ .Values.controller.service.port }} + {{- end }} + {{- if .Values.controller.probes.readinessEnabled }} + readinessProbe: + initialDelaySeconds: {{ .Values.controller.probes.readiness.initialDelaySeconds | default .Values.probes.initialDelaySeconds }} + periodSeconds: {{ .Values.controller.probes.readiness.periodSeconds | default .Values.probes.periodSeconds }} + failureThreshold: {{ .Values.controller.probes.readiness.failureThreshold | default .Values.probes.failureThreshold }} + successThreshold: {{ .Values.controller.probes.readiness.successThreshold | default .Values.probes.successThreshold }} + timeoutSeconds: {{ .Values.controller.probes.readiness.timeoutSeconds | default .Values.probes.timeoutSeconds }} + httpGet: + path: {{ .Values.controller.probes.endpoint }} + port: {{ .Values.controller.service.port }} + {{- end }} + {{- if .Values.controller.probes.startupEnabled }} + startupProbe: + initialDelaySeconds: {{ .Values.controller.probes.startup.initialDelaySeconds | default .Values.probes.initialDelaySeconds }} + periodSeconds: {{ .Values.controller.probes.startup.periodSeconds | default .Values.probes.periodSeconds }} + failureThreshold: {{ .Values.controller.probes.startup.failureThreshold | default .Values.probes.failureThreshold }} + successThreshold: {{ .Values.controller.probes.startup.successThreshold | default .Values.probes.successThreshold }} + timeoutSeconds: {{ .Values.controller.probes.startup.timeoutSeconds | default .Values.probes.timeoutSeconds }} + httpGet: + path: {{ .Values.controller.probes.endpoint }} + port: {{ .Values.controller.service.port }} + {{- end }} + volumeMounts: + - name: config + mountPath: /var/pinot/controller/config + - name: data + mountPath: "{{ .Values.controller.persistence.mountPath }}" + {{- if ne (len .Values.controller.persistence.extraVolumeMounts) 0 }} +{{ toYaml .Values.controller.persistence.extraVolumeMounts | indent 10 }} + {{- end }} + resources: +{{ toYaml .Values.controller.resources | indent 12 }} + restartPolicy: Always + volumes: + - name: config + configMap: + name: {{ include "pinot.controller.config" . }} +{{- if not .Values.controller.persistence.enabled }} + - name: data + emptyDir: {} +{{- end }} + {{- if ne (len .Values.controller.persistence.extraVolumes) 0 }} +{{ toYaml .Values.controller.persistence.extraVolumes | indent 6 }} + {{- end }} +{{- if .Values.controller.persistence.enabled }} + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: + - {{ .Values.controller.persistence.accessMode | quote }} + {{- if .Values.controller.persistence.storageClass }} + {{- if (eq "-" .Values.controller.persistence.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: {{ .Values.controller.persistence.storageClass }} + {{- end }} + {{- end }} + resources: + requests: + storage: {{ .Values.controller.persistence.size | quote}} +{{ end }} diff --git a/helm/pinot/templates/minion-stateless/configmap.yaml b/helm/pinot/templates/minion-stateless/configmap.yaml new file mode 100644 index 000000000000..71131e9db1ee --- /dev/null +++ b/helm/pinot/templates/minion-stateless/configmap.yaml @@ -0,0 +1,33 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +{{- if .Values.minionStateless.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "pinot.minionStateless.config" . }} + namespace: {{ include "pinot.namespace" . }} +data: + pinot-minion-stateless.conf: |- + pinot.minion.port={{ .Values.minionStateless.service.port }} + {{- if .Values.minionStateless.dataDir }} + dataDir={{ .Values.minionStateless.dataDir }} + {{- end }} +{{ .Values.minionStateless.extra.configs | indent 4 }} +{{- end }} diff --git a/helm/pinot/templates/minion-stateless/deployment.yaml b/helm/pinot/templates/minion-stateless/deployment.yaml new file mode 100644 index 000000000000..f52a5dd1da99 --- /dev/null +++ b/helm/pinot/templates/minion-stateless/deployment.yaml @@ -0,0 +1,143 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +{{- if .Values.minionStateless.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "pinot.minionStateless.fullname" . }} + namespace: {{ include "pinot.namespace" . }} + labels: + {{- include "pinot.minionStatelessLabels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "pinot.minionStatelessMatchLabels" . | nindent 6 }} + replicas: {{ .Values.minionStateless.replicaCount }} + template: + metadata: + labels: + {{- include "pinot.minionStatelessLabels" . | nindent 8 }} + annotations: +{{ toYaml .Values.minionStateless.podAnnotations | indent 8 }} + spec: + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + serviceAccountName: {{ include "pinot.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.minionStateless.podSecurityContext | nindent 8 }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + nodeSelector: +{{ toYaml .Values.minionStateless.nodeSelector | indent 8 }} + affinity: +{{ toYaml .Values.minionStateless.affinity | indent 8 }} + tolerations: +{{ toYaml .Values.minionStateless.tolerations | indent 8 }} + containers: + - name: minion-stateless + securityContext: + {{- toYaml .Values.minionStateless.securityContext | nindent 10 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + args: [ + "{{ .Values.minionStateless.startCommand }}", + "-clusterName", "{{ .Values.cluster.name }}", + "-zkAddress", {{ include "zookeeper.url" . | quote }}, + "-configFileName", "/var/pinot/minion/config/pinot-minion-stateless.conf" + ] + env: + - name: JAVA_OPTS + value: "{{ .Values.minionStateless.jvmOpts }} -Dlog4j2.configurationFile={{ .Values.minionStateless.log4j2ConfFile }} -Dplugins.dir={{ .Values.minionStateless.pluginsDir }}" +{{- if .Values.minionStateless.extraEnv }} +{{ toYaml .Values.minionStateless.extraEnv | indent 10 }} +{{- end }} + envFrom: +{{ toYaml .Values.minionStateless.envFrom | indent 10 }} + ports: + - containerPort: {{ .Values.minionStateless.service.port }} + protocol: {{ .Values.minionStateless.service.protocol }} + name: {{ .Values.minionStateless.service.name }} +{{- if .Values.minionStateless.service.extraPorts }} +{{ toYaml .Values.minionStateless.service.extraPorts | indent 10 }} +{{- end }} + {{- if .Values.minionStateless.probes.livenessEnabled }} + livenessProbe: + initialDelaySeconds: {{ .Values.minionStateless.probes.liveness.initialDelaySeconds | default .Values.probes.initialDelaySeconds }} + periodSeconds: {{ .Values.minionStateless.probes.liveness.periodSeconds | default .Values.probes.periodSeconds }} + failureThreshold: {{ .Values.minionStateless.probes.liveness.failureThreshold | default .Values.probes.failureThreshold }} + successThreshold: {{ .Values.minionStateless.probes.liveness.successThreshold | default .Values.probes.successThreshold }} + timeoutSeconds: {{ .Values.minionStateless.probes.liveness.timeoutSeconds | default .Values.probes.timeoutSeconds }} + httpGet: + path: {{ .Values.minionStateless.probes.endpoint }} + port: {{ .Values.minionStateless.service.port }} + {{- end }} + {{- if .Values.minionStateless.probes.readinessEnabled }} + readinessProbe: + initialDelaySeconds: {{ .Values.minionStateless.probes.readiness.initialDelaySeconds | default .Values.probes.initialDelaySeconds }} + periodSeconds: {{ .Values.minionStateless.probes.readiness.periodSeconds | default .Values.probes.periodSeconds }} + failureThreshold: {{ .Values.minionStateless.probes.readiness.failureThreshold | default .Values.probes.failureThreshold }} + successThreshold: {{ .Values.minionStateless.probes.readiness.successThreshold | default .Values.probes.successThreshold }} + timeoutSeconds: {{ .Values.minionStateless.probes.readiness.timeoutSeconds | default .Values.probes.timeoutSeconds }} + httpGet: + path: {{ .Values.minionStateless.probes.endpoint }} + port: {{ .Values.minionStateless.service.port }} + {{- end }} + {{- if .Values.minionStateless.probes.startupEnabled }} + startupProbe: + initialDelaySeconds: {{ .Values.minionStateless.probes.startup.initialDelaySeconds | default .Values.probes.initialDelaySeconds }} + periodSeconds: {{ .Values.minionStateless.probes.startup.periodSeconds | default .Values.probes.periodSeconds }} + failureThreshold: {{ .Values.minionStateless.probes.startup.failureThreshold | default .Values.probes.failureThreshold }} + successThreshold: {{ .Values.minionStateless.probes.startup.successThreshold | default .Values.probes.successThreshold }} + timeoutSeconds: {{ .Values.minionStateless.probes.startup.timeoutSeconds | default .Values.probes.timeoutSeconds }} + httpGet: + path: {{ .Values.minionStateless.probes.endpoint }} + port: {{ .Values.minionStateless.service.port }} + {{- end }} + volumeMounts: + - name: config + mountPath: /var/pinot/minion/config + {{- if .Values.minionStateless.persistence.enabled }} + - name: data + mountPath: "{{ .Values.minionStateless.persistence.mountPath }}" + {{- end }} + {{- if ne (len .Values.minionStateless.persistence.extraVolumeMounts) 0 }} +{{ toYaml .Values.minionStateless.persistence.extraVolumeMounts | indent 10 }} + {{- end }} + resources: +{{ toYaml .Values.minionStateless.resources | indent 12 }} + restartPolicy: Always + volumes: + - name: config + configMap: + name: {{ include "pinot.minionStateless.config" . }} + {{- if not .Values.minionStateless.persistence.enabled }} + - name: data + emptyDir: {} + {{- end }} + {{- if .Values.minionStateless.persistence.enabled }} + - name: data + persistentVolumeClaim: + claimName: {{ .Values.minionStateless.persistence.pvcName }} + {{- end }} + {{- if ne (len .Values.minionStateless.persistence.extraVolumes) 0 }} +{{ toYaml .Values.minionStateless.persistence.extraVolumes | indent 8 }} + {{- end }} +{{- end }} diff --git a/kubernetes/helm/pinot/templates/minion-stateless/pvc.yaml b/helm/pinot/templates/minion-stateless/pvc.yaml similarity index 96% rename from kubernetes/helm/pinot/templates/minion-stateless/pvc.yaml rename to helm/pinot/templates/minion-stateless/pvc.yaml index 3db9473f2528..d3641bd8ee72 100644 --- a/kubernetes/helm/pinot/templates/minion-stateless/pvc.yaml +++ b/helm/pinot/templates/minion-stateless/pvc.yaml @@ -23,6 +23,7 @@ kind: PersistentVolumeClaim apiVersion: v1 metadata: name: {{ .Values.minionStateless.persistence.pvcName }} + namespace: {{ include "pinot.namespace" . }} spec: accessModes: - {{ .Values.minionStateless.persistence.accessMode | quote }} diff --git a/helm/pinot/templates/minion/configmap.yaml b/helm/pinot/templates/minion/configmap.yaml new file mode 100644 index 000000000000..3a7f30170d5f --- /dev/null +++ b/helm/pinot/templates/minion/configmap.yaml @@ -0,0 +1,33 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +{{- if .Values.minion.enabled }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "pinot.minion.config" . }} + namespace: {{ include "pinot.namespace" . }} +data: + pinot-minion.conf: |- + pinot.minion.port={{ .Values.minion.service.port }} + {{- if .Values.minion.dataDir }} + dataDir={{ .Values.minion.dataDir }} + {{- end }} +{{ .Values.minion.extra.configs | indent 4 }} +{{- end }} diff --git a/helm/pinot/templates/minion/service-headless.yaml b/helm/pinot/templates/minion/service-headless.yaml new file mode 100644 index 000000000000..6e0c4decfd2f --- /dev/null +++ b/helm/pinot/templates/minion/service-headless.yaml @@ -0,0 +1,42 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +{{- if .Values.minion.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "pinot.minion.headless" . }} + namespace: {{ include "pinot.namespace" . }} + labels: + {{- include "pinot.minionLabels" . | nindent 4 }} +spec: + clusterIP: None + ports: + # [pod_name].[service_name].[namespace].svc.cluster.local + - name: {{ .Values.minion.service.name }} + port: {{ .Values.minion.service.port }} + {{- if .Values.minion.service.extraPorts }} + {{- range .Values.minion.service.extraPorts }} + - name: {{ .name }} + port: {{ .containerPort }} + {{- end }} + {{- end }} + selector: + {{- include "pinot.minionMatchLabels" . | nindent 4 }} +{{- end }} diff --git a/helm/pinot/templates/minion/service.yaml b/helm/pinot/templates/minion/service.yaml new file mode 100644 index 000000000000..6b80a6222814 --- /dev/null +++ b/helm/pinot/templates/minion/service.yaml @@ -0,0 +1,44 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +{{- if .Values.minion.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "pinot.minion.fullname" . }} + namespace: {{ include "pinot.namespace" . }} + annotations: +{{ toYaml .Values.minion.service.annotations | indent 4 }} + labels: + {{- include "pinot.minionLabels" . | nindent 4 }} +spec: + type: {{ .Values.minion.service.type }} + ports: + # [pod_name].[service_name].[namespace].svc.cluster.local + - name: {{ .Values.minion.service.name }} + port: {{ .Values.minion.service.port }} + {{- if .Values.minion.service.extraPorts }} + {{- range .Values.minion.service.extraPorts }} + - name: {{ .name }} + port: {{ .containerPort }} + {{- end }} + {{- end }} + selector: + {{- include "pinot.minionMatchLabels" . | nindent 4 }} +{{- end }} diff --git a/helm/pinot/templates/minion/statefulset.yaml b/helm/pinot/templates/minion/statefulset.yaml new file mode 100644 index 000000000000..99c3a2f6cc47 --- /dev/null +++ b/helm/pinot/templates/minion/statefulset.yaml @@ -0,0 +1,165 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +{{- if .Values.minion.enabled }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "pinot.minion.fullname" . }} + namespace: {{ include "pinot.namespace" . }} + labels: + {{- include "pinot.minionLabels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "pinot.minionMatchLabels" . | nindent 6 }} + serviceName: {{ template "pinot.minion.headless" . }} + replicas: {{ .Values.minion.replicaCount }} + updateStrategy: + type: {{ .Values.minion.updateStrategy.type }} + podManagementPolicy: {{ .Values.minion.podManagementPolicy }} + template: + metadata: + labels: + {{- include "pinot.minionLabels" . | nindent 8 }} + annotations: + {{- if .Values.minion.automaticReload.enabled }} + checksum/config: {{ include (print $.Template.BasePath "/minion/configmap.yaml") . | sha256sum}} + {{- end }} + {{- with .Values.minion.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + serviceAccountName: {{ include "pinot.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.minion.podSecurityContext | nindent 8 }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + nodeSelector: +{{ toYaml .Values.minion.nodeSelector | indent 8 }} + affinity: +{{ toYaml .Values.minion.affinity | indent 8 }} + tolerations: +{{ toYaml .Values.minion.tolerations | indent 8 }} + containers: + - name: minion + securityContext: + {{- toYaml .Values.minion.securityContext | nindent 10 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + args: [ + "{{ .Values.minion.startCommand }}", + "-clusterName", "{{ .Values.cluster.name }}", + "-zkAddress", {{ include "zookeeper.url" . | quote }}, + "-configFileName", "/var/pinot/minion/config/pinot-minion.conf" + ] + env: + - name: JAVA_OPTS + value: "{{ .Values.minion.jvmOpts }} -Dlog4j2.configurationFile={{ .Values.minion.log4j2ConfFile }} -Dplugins.dir={{ .Values.minion.pluginsDir }}" +{{- if .Values.minion.extraEnv }} +{{ toYaml .Values.minion.extraEnv | indent 10 }} +{{- end }} + envFrom: +{{ toYaml .Values.minion.envFrom | indent 10 }} + ports: + - containerPort: {{ .Values.minion.service.port }} + protocol: {{ .Values.minion.service.protocol }} + name: {{ .Values.minion.service.name }} +{{- if .Values.minion.service.extraPorts }} +{{ toYaml .Values.minion.service.extraPorts | indent 10 }} +{{- end }} + {{- if .Values.minion.probes.livenessEnabled }} + livenessProbe: + initialDelaySeconds: {{ .Values.minion.probes.liveness.initialDelaySeconds | default .Values.probes.initialDelaySeconds }} + periodSeconds: {{ .Values.minion.probes.liveness.periodSeconds | default .Values.probes.periodSeconds }} + failureThreshold: {{ .Values.minion.probes.liveness.failureThreshold | default .Values.probes.failureThreshold }} + successThreshold: {{ .Values.minion.probes.liveness.successThreshold | default .Values.probes.successThreshold }} + timeoutSeconds: {{ .Values.minion.probes.liveness.timeoutSeconds | default .Values.probes.timeoutSeconds }} + httpGet: + path: {{ .Values.minion.probes.endpoint }} + port: {{ .Values.minion.service.port }} + {{- end }} + {{- if .Values.minion.probes.readinessEnabled }} + readinessProbe: + initialDelaySeconds: {{ .Values.minion.probes.readiness.initialDelaySeconds | default .Values.probes.initialDelaySeconds }} + periodSeconds: {{ .Values.minion.probes.readiness.periodSeconds | default .Values.probes.periodSeconds }} + failureThreshold: {{ .Values.minion.probes.readiness.failureThreshold | default .Values.probes.failureThreshold }} + successThreshold: {{ .Values.minion.probes.readiness.successThreshold | default .Values.probes.successThreshold }} + timeoutSeconds: {{ .Values.minion.probes.readiness.timeoutSeconds | default .Values.probes.timeoutSeconds }} + httpGet: + path: {{ .Values.minion.probes.endpoint }} + port: {{ .Values.minion.service.port }} + {{- end }} + {{- if .Values.minion.probes.startupEnabled }} + startupProbe: + initialDelaySeconds: {{ .Values.minion.probes.startup.initialDelaySeconds | default .Values.probes.initialDelaySeconds }} + periodSeconds: {{ .Values.minion.probes.startup.periodSeconds | default .Values.probes.periodSeconds }} + failureThreshold: {{ .Values.minion.probes.startup.failureThreshold | default .Values.probes.failureThreshold }} + successThreshold: {{ .Values.minion.probes.startup.successThreshold | default .Values.probes.successThreshold }} + timeoutSeconds: {{ .Values.minion.probes.startup.timeoutSeconds | default .Values.probes.timeoutSeconds }} + httpGet: + path: {{ .Values.minion.probes.endpoint }} + port: {{ .Values.minion.service.port }} + {{- end }} + volumeMounts: + - name: config + mountPath: /var/pinot/minion/config + {{- if .Values.minion.persistence.enabled }} + - name: data + mountPath: "{{ .Values.minion.persistence.mountPath }}" + {{- end }} + {{- if ne (len .Values.minion.persistence.extraVolumeMounts) 0 }} +{{ toYaml .Values.minion.persistence.extraVolumeMounts | indent 10 }} + {{- end }} + resources: +{{ toYaml .Values.minion.resources | indent 12 }} + restartPolicy: Always + volumes: + - name: config + configMap: + name: {{ include "pinot.minion.config" . }} + {{- if not .Values.minion.persistence.enabled }} + - name: data + emptyDir: {} + {{- end }} + {{- if ne (len .Values.minion.persistence.extraVolumes) 0 }} +{{ toYaml .Values.minion.persistence.extraVolumes | indent 8 }} + {{- end }} + {{- if .Values.minion.persistence.enabled }} + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: + - {{ .Values.minion.persistence.accessMode | quote }} + {{- if .Values.minion.persistence.storageClass }} + {{- if (eq "-" .Values.minion.persistence.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: {{ .Values.minion.persistence.storageClass }} + {{- end }} + {{- end }} + resources: + requests: + storage: {{ .Values.minion.persistence.size }} + {{ end }} +{{- end }} diff --git a/helm/pinot/templates/namespace.yaml b/helm/pinot/templates/namespace.yaml new file mode 100644 index 000000000000..9a5b72e1d66d --- /dev/null +++ b/helm/pinot/templates/namespace.yaml @@ -0,0 +1,29 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +apiVersion: v1 +kind: Namespace +metadata: + name: {{ include "pinot.namespace" . }} + labels: + {{- include "pinot.labels" . | nindent 4 }} + {{- with .Values.namespaceAnnotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} diff --git a/helm/pinot/templates/server/configmap.yaml b/helm/pinot/templates/server/configmap.yaml new file mode 100644 index 000000000000..fe3a253b61db --- /dev/null +++ b/helm/pinot/templates/server/configmap.yaml @@ -0,0 +1,31 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "pinot.server.config" . }} + namespace: {{ include "pinot.namespace" . }} +data: + pinot-server.conf: |- + pinot.server.netty.port={{ .Values.server.service.nettyPort }} + pinot.server.adminapi.port={{ .Values.server.service.adminPort }} + pinot.server.instance.dataDir={{ .Values.server.dataDir }} + pinot.server.instance.segmentTarDir={{ .Values.server.segmentTarDir }} +{{ .Values.server.extra.configs | indent 4 }} diff --git a/helm/pinot/templates/server/poddisruptionbudget.yaml b/helm/pinot/templates/server/poddisruptionbudget.yaml new file mode 100644 index 000000000000..c5bc727cc358 --- /dev/null +++ b/helm/pinot/templates/server/poddisruptionbudget.yaml @@ -0,0 +1,36 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +{{- if .Values.server.pdb.enabled }} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + name: {{ include "pinot.server.fullname" . }} + namespace: {{ include "pinot.namespace" . }} +spec: + {{- if .Values.server.pdb.minAvailable }} + minAvailable: {{ .Values.server.pdb.minAvailable }} + {{- end }} + {{- if .Values.server.pdb.maxUnavailable }} + maxUnavailable: {{ .Values.server.pdb.maxUnavailable }} + {{- end }} + selector: + matchLabels: + {{- include "pinot.serverMatchLabels" . | nindent 6 }} +{{- end }} diff --git a/helm/pinot/templates/server/service-headless.yaml b/helm/pinot/templates/server/service-headless.yaml new file mode 100644 index 000000000000..c6e219c5ac1d --- /dev/null +++ b/helm/pinot/templates/server/service-headless.yaml @@ -0,0 +1,46 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +apiVersion: v1 +kind: Service +metadata: + name: {{ template "pinot.server.headless" . }} + namespace: {{ include "pinot.namespace" . }} + labels: + {{- include "pinot.serverLabels" . | nindent 4 }} +spec: + clusterIP: None + ports: + # [pod_name].[service_name].[namespace].svc.cluster.local + - name: {{ .Values.server.service.nettyPortName }} + port: {{ .Values.server.service.nettyPort }} + protocol: {{ .Values.server.service.protocol }} + - name: {{ .Values.server.service.adminPortName }} + port: {{ .Values.server.service.adminExposePort }} + targetPort: {{ .Values.server.service.adminPort }} + protocol: {{ .Values.server.service.protocol }} + {{- if .Values.server.service.extraPorts }} + {{- range .Values.server.service.extraPorts }} + - name: {{ .name }} + port: {{ .containerPort }} + protocol: {{ .protocol }} + {{- end }} + {{- end }} + selector: + {{- include "pinot.serverMatchLabels" . | nindent 4 }} diff --git a/helm/pinot/templates/server/service.yaml b/helm/pinot/templates/server/service.yaml new file mode 100644 index 000000000000..d8f1cfa095ed --- /dev/null +++ b/helm/pinot/templates/server/service.yaml @@ -0,0 +1,47 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +apiVersion: v1 +kind: Service +metadata: + name: {{ include "pinot.server.fullname" . }} + namespace: {{ include "pinot.namespace" . }} + annotations: +{{ toYaml .Values.server.service.annotations | indent 4 }} + labels: + {{- include "pinot.serverLabels" . | nindent 4 }} +spec: + type: {{ .Values.server.service.type }} + ports: + # [pod_name].[service_name].[namespace].svc.cluster.local + - name: {{ .Values.server.service.nettyPortName }} + port: {{ .Values.server.service.nettyPort }} + protocol: {{ .Values.server.service.protocol }} + - name: {{ .Values.server.service.adminPortName }} + port: {{ .Values.server.service.adminExposePort }} + targetPort: {{ .Values.server.service.adminPort }} + protocol: {{ .Values.server.service.protocol }} + {{- if .Values.server.service.extraPorts }} + {{- range .Values.server.service.extraPorts }} + - name: {{ .name }} + port: {{ .containerPort }} + {{- end }} + {{- end }} + selector: + {{- include "pinot.serverMatchLabels" . | nindent 4 }} diff --git a/helm/pinot/templates/server/statefulset.yaml b/helm/pinot/templates/server/statefulset.yaml new file mode 100644 index 000000000000..ab5ec1c49301 --- /dev/null +++ b/helm/pinot/templates/server/statefulset.yaml @@ -0,0 +1,164 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "pinot.server.fullname" . }} + namespace: {{ include "pinot.namespace" . }} + labels: + {{- include "pinot.serverLabels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "pinot.serverMatchLabels" . | nindent 6 }} + serviceName: {{ template "pinot.server.headless" . }} + replicas: {{ .Values.server.replicaCount }} + updateStrategy: + type: {{ .Values.server.updateStrategy.type }} + podManagementPolicy: {{ .Values.server.podManagementPolicy }} + template: + metadata: + labels: + {{- include "pinot.serverLabels" . | nindent 8 }} + annotations: + {{- if .Values.server.automaticReload.enabled }} + checksum/config: {{ include (print $.Template.BasePath "/server/configmap.yaml") . | sha256sum }} + {{- end }} + {{- with .Values.server.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + serviceAccountName: {{ include "pinot.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.server.podSecurityContext | nindent 8 }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + nodeSelector: +{{ toYaml .Values.server.nodeSelector | indent 8 }} + affinity: +{{ toYaml .Values.server.affinity | indent 8 }} + tolerations: +{{ toYaml .Values.server.tolerations | indent 8 }} + containers: + - name: server + securityContext: + {{- toYaml .Values.server.securityContext | nindent 10 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + args: [ + "{{ .Values.server.startCommand }}", + "-clusterName", "{{ .Values.cluster.name }}", + "-zkAddress", {{ include "zookeeper.url" . | quote }}, + "-configFileName", "/var/pinot/server/config/pinot-server.conf" + ] + env: + - name: JAVA_OPTS + value: "{{ .Values.server.jvmOpts }} -Dlog4j2.configurationFile={{ .Values.server.log4j2ConfFile }} -Dplugins.dir={{ .Values.server.pluginsDir }}" +{{- if .Values.server.extraEnv }} +{{ toYaml .Values.server.extraEnv | indent 10 }} +{{- end}} + envFrom: +{{ toYaml .Values.server.envFrom | indent 10 }} + ports: + - containerPort: {{ .Values.server.service.nettyPort }} + protocol: {{ .Values.server.service.protocol }} + name: {{ .Values.server.service.nettyPortName }} + - containerPort: {{ .Values.server.service.adminPort }} + protocol: {{ .Values.server.service.protocol }} + name: {{ .Values.server.service.adminPortName }} +{{- if .Values.server.service.extraPorts }} +{{ toYaml .Values.server.service.extraPorts | indent 10 }} +{{- end }} + {{- if .Values.server.probes.livenessEnabled }} + livenessProbe: + initialDelaySeconds: {{ .Values.server.probes.liveness.initialDelaySeconds | default .Values.probes.initialDelaySeconds }} + periodSeconds: {{ .Values.server.probes.liveness.periodSeconds | default .Values.probes.periodSeconds }} + failureThreshold: {{ .Values.server.probes.liveness.failureThreshold | default .Values.probes.failureThreshold }} + successThreshold: {{ .Values.server.probes.liveness.successThreshold | default .Values.probes.successThreshold }} + timeoutSeconds: {{ .Values.server.probes.liveness.timeoutSeconds | default .Values.probes.timeoutSeconds }} + httpGet: + path: {{ .Values.server.probes.liveness.endpoint | default .Values.server.probes.endpoint }} + port: {{ .Values.server.service.adminPort }} + {{- end }} + {{- if .Values.server.probes.readinessEnabled }} + readinessProbe: + initialDelaySeconds: {{ .Values.server.probes.readiness.initialDelaySeconds | default .Values.probes.initialDelaySeconds }} + periodSeconds: {{ .Values.server.probes.readiness.periodSeconds | default .Values.probes.periodSeconds }} + failureThreshold: {{ .Values.server.probes.readiness.failureThreshold | default .Values.probes.failureThreshold }} + successThreshold: {{ .Values.server.probes.readiness.successThreshold | default .Values.probes.successThreshold }} + timeoutSeconds: {{ .Values.server.probes.readiness.timeoutSeconds | default .Values.probes.timeoutSeconds }} + httpGet: + path: {{ .Values.server.probes.readiness.endpoint | default .Values.server.probes.endpoint }} + port: {{ .Values.server.service.adminPort }} + {{- end }} + {{- if .Values.server.probes.startupEnabled }} + startupProbe: + initialDelaySeconds: {{ .Values.server.probes.startup.initialDelaySeconds | default .Values.probes.initialDelaySeconds }} + periodSeconds: {{ .Values.server.probes.startup.periodSeconds | default .Values.probes.periodSeconds }} + failureThreshold: {{ .Values.server.probes.startup.failureThreshold | default .Values.probes.failureThreshold }} + successThreshold: {{ .Values.server.probes.startup.successThreshold | default .Values.probes.successThreshold }} + timeoutSeconds: {{ .Values.server.probes.startup.timeoutSeconds | default .Values.probes.timeoutSeconds }} + httpGet: + path: {{ .Values.server.probes.liveness.endpoint | default .Values.server.probes.endpoint }} + port: {{ .Values.server.service.adminPort }} + {{- end }} + volumeMounts: + - name: config + mountPath: /var/pinot/server/config + - name: data + mountPath: "{{ .Values.server.persistence.mountPath }}" + {{- if ne (len .Values.server.persistence.extraVolumeMounts) 0 }} +{{ toYaml .Values.server.persistence.extraVolumeMounts | indent 10 }} + {{- end }} + resources: +{{ toYaml .Values.server.resources | indent 12 }} + restartPolicy: Always + volumes: + - name: config + configMap: + name: {{ include "pinot.server.config" . }} + {{- if not .Values.server.persistence.enabled }} + - name: data + emptyDir: {} + {{- end }} + {{- if ne (len .Values.server.persistence.extraVolumes) 0 }} +{{ toYaml .Values.server.persistence.extraVolumes | indent 8 }} + {{- end }} + {{- if .Values.server.persistence.enabled }} + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: + - {{ .Values.server.persistence.accessMode | quote }} + {{- if .Values.server.persistence.storageClass }} + {{- if (eq "-" .Values.server.persistence.storageClass) }} + storageClassName: "" + {{- else }} + storageClassName: {{ .Values.server.persistence.storageClass }} + {{- end }} + {{- end }} + resources: + requests: + storage: {{ .Values.server.persistence.size }} + {{ end }} diff --git a/helm/pinot/templates/serviceaccount.yaml b/helm/pinot/templates/serviceaccount.yaml new file mode 100644 index 000000000000..905c8f03f5a5 --- /dev/null +++ b/helm/pinot/templates/serviceaccount.yaml @@ -0,0 +1,31 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "pinot.serviceAccountName" . }} + namespace: {{ include "pinot.namespace" . }} + labels: + {{- include "pinot.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm/pinot/values.yaml b/helm/pinot/values.yaml new file mode 100644 index 000000000000..db48b53c4171 --- /dev/null +++ b/helm/pinot/values.yaml @@ -0,0 +1,764 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Default values for Pinot. + +namespaceOverride: +namespaceAnnotations: {} + +image: + repository: apachepinot/pinot + # Pinot docker images are available at https://hub.docker.com/r/apachepinot/pinot/tags + # - `latest` tag is always available and points to the nightly pinot master branch build + # - `release-x.y.z` or `x.y.z` tags are available for each release, e.g. release-1.0.0, release-0.12.1, 1.0.0, 0.12.1, etc. + # + # Default JDK comes with Amazon Corretto 11, here are also images with different JDKs: + # - Amazon Corretto 11, e.g. `latest-11`, `1.0.0-11`, `latest-11-amazoncorretto`, `1.0.0-11-amazoncorretto` + # - Amazon Corretto 17, e.g. `latest-17-amazoncorretto`, `1.0.0-17-amazoncorretto` + # - MS OpenJDK 11, e.g. `latest-11-ms-openjdk`, `1.0.0-11-ms-openjdk` + # - MS OpenJDK 17, e.g. `latest-17-ms-openjdk`, `1.0.0-17-ms-openjdk` + # - OpenJDK 21, e.g. `latest-21-openjdk`, `1.0.0-21-openjdk` + tag: latest # 1.0.0, 0.12.1, latest + pullPolicy: Always # Use IfNotPresent when you pinged a version of image tag + +cluster: + name: pinot-quickstart + +imagePullSecrets: [] + +terminationGracePeriodSeconds: 30 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +# default values of the probes i.e. liveness and readiness. +# customization of values is present at the component level. +probes: + initialDelaySeconds: 60 + periodSeconds: 10 + failureThreshold: 10 + # should be 1 for liveness and startup probe, as per K8s doc. + successThreshold: 1 + timeoutSeconds: 10 + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +additionalMatchLabels: {} + + +pinotAuth: + enabled: false + controllerFactoryClass: org.apache.pinot.controller.api.access.BasicAuthAccessControlFactory + brokerFactoryClass: org.apache.pinot.broker.broker.BasicAuthAccessControlFactory + configs: + # - access.control.principals=admin,user + # - access.control.principals.admin.password=verysecret + # - access.control.principals.user.password=secret + # - access.control.principals.user.tables=baseballStats,otherstuff + # - access.control.principals.user.permissions=READ + +# ------------------------------------------------------------------------------ +# Pinot Controller: +# ------------------------------------------------------------------------------ +controller: + name: controller + replicaCount: 1 + podManagementPolicy: Parallel + podSecurityContext: {} + # fsGroup: 2000 + securityContext: {} + startCommand: "StartController" + + probes: + endpoint: "/health" + livenessEnabled: false + readinessEnabled: false + startupEnabled: false + liveness: + initialDelaySeconds: 60 + failureThreshold: 10 + timeoutSeconds: 10 + successThreshold: 1 + periodSeconds: 10 + + readiness: + initialDelaySeconds: 60 + failureThreshold: 10 + timeoutSeconds: 10 + successThreshold: 1 + periodSeconds: 10 + + startup: + initialDelaySeconds: 60 + failureThreshold: 10 + timeoutSeconds: 10 + successThreshold: 1 + periodSeconds: 10 + + persistence: + enabled: true + accessMode: ReadWriteOnce + size: 1G + mountPath: /var/pinot/controller/data + storageClass: "" + extraVolumes: [] + extraVolumeMounts: [] + + data: + dir: /var/pinot/controller/data + + vip: + enabled: false + host: pinot-controller + port: 9000 + + jvmOpts: "-XX:ActiveProcessorCount=2 -Xms256M -Xmx1G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Xlog:gc*:file=/opt/pinot/gc-pinot-controller.log" + + log4j2ConfFile: /opt/pinot/etc/conf/pinot-controller-log4j2.xml + pluginsDir: /opt/pinot/plugins + + pdb: + enabled: false + minAvailable: "" + maxUnavailable: 50% + + service: + annotations: {} + clusterIP: "None" + externalIPs: [] + loadBalancerIP: "" + loadBalancerSourceRanges: [] + type: ClusterIP + port: 9000 + nodePort: "" + protocol: TCP + name: controller + extraPorts: [] + # - containerPort: 1234 + # protocol: PROTOCOL + # name: extra-port + + external: + enabled: true + type: LoadBalancer + port: 9000 + annotations: {} + + ingress: + v1beta1: + enabled: false + annotations: { } + tls: { } + path: / + hosts: [ ] + v1: + enabled: false + ingressClassName: "" + annotations: {} + tls: [] + path: / + hosts: [] + + resources: + requests: + memory: "1.25Gi" + + nodeSelector: {} + + tolerations: [] + + affinity: {} + + podAnnotations: {} + + # set enabled as true, to automatically roll controller stateful set for configmap change + automaticReload: + enabled: false + + updateStrategy: + type: RollingUpdate + + # Use envFrom to define all of the ConfigMap or Secret data as container environment variables. + # ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#configure-all-key-value-pairs-in-a-configmap-as-container-environment-variables + # ref: https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/#configure-all-key-value-pairs-in-a-secret-as-container-environment-variables + envFrom: [] + # - configMapRef: + # name: special-config + # - secretRef: + # name: test-secret + + # Use extraEnv to add individual key value pairs as container environment variables. + # ref: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/ + extraEnv: + - name: LOG4J_CONSOLE_LEVEL + value: info + # - name: PINOT_CUSTOM_ENV + # value: custom-value + + # Extra configs will be appended to pinot-controller.conf file + extra: + configs: |- + pinot.set.instance.id.to.hostname=true + controller.task.scheduler.enabled=true + +# ------------------------------------------------------------------------------ +# Pinot Broker: +# ------------------------------------------------------------------------------ +broker: + name: broker + replicaCount: 1 + podManagementPolicy: Parallel + podSecurityContext: {} + # fsGroup: 2000 + securityContext: {} + startCommand: "StartBroker" + + jvmOpts: "-XX:ActiveProcessorCount=2 -Xms256M -Xmx1G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Xlog:gc*:file=/opt/pinot/gc-pinot-broker.log" + + log4j2ConfFile: /opt/pinot/etc/conf/pinot-broker-log4j2.xml + pluginsDir: /opt/pinot/plugins + + routingTable: + builderClass: random + + probes: + endpoint: "/health" + livenessEnabled: true + readinessEnabled: true + startupEnabled: false + liveness: + initialDelaySeconds: 60 + failureThreshold: 10 + timeoutSeconds: 10 + successThreshold: 1 + periodSeconds: 10 + + readiness: + initialDelaySeconds: 60 + failureThreshold: 10 + timeoutSeconds: 10 + successThreshold: 1 + periodSeconds: 10 + + startup: + initialDelaySeconds: 60 + failureThreshold: 10 + timeoutSeconds: 10 + successThreshold: 1 + periodSeconds: 10 + + persistence: + extraVolumes: [] + extraVolumeMounts: [] + + pdb: + enabled: false + minAvailable: "" + maxUnavailable: 50% + + service: + annotations: {} + clusterIP: "None" + externalIPs: [] + loadBalancerIP: "" + loadBalancerSourceRanges: [] + type: ClusterIP + protocol: TCP + port: 8099 + name: broker + nodePort: "" + extraPorts: [] + # - containerPort: 1234 + # protocol: PROTOCOL + # name: extra-port + + external: + enabled: true + type: LoadBalancer + port: 8099 + # For example, in private GKE cluster, you might add cloud.google.com/load-balancer-type: Internal + annotations: {} + + ingress: + v1beta1: + enabled: false + annotations: {} + tls: {} + path: / + hosts: [] + v1: + enabled: false + ingressClassName: "" + annotations: {} + tls: [] + path: / + hosts: [] + + resources: + requests: + memory: "1.25Gi" + + nodeSelector: {} + + affinity: {} + + tolerations: [] + + podAnnotations: {} + + # set enabled as true, to automatically roll broker stateful set for configmap change + automaticReload: + enabled: false + + updateStrategy: + type: RollingUpdate + + # Use envFrom to define all of the ConfigMap or Secret data as container environment variables. + # ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#configure-all-key-value-pairs-in-a-configmap-as-container-environment-variables + # ref: https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/#configure-all-key-value-pairs-in-a-secret-as-container-environment-variables + envFrom: [] + # - configMapRef: + # name: special-config + # - secretRef: + # name: test-secret + + # Use extraEnv to add individual key value pairs as container environment variables. + # ref: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/ + extraEnv: + - name: LOG4J_CONSOLE_LEVEL + value: info + # - name: PINOT_CUSTOM_ENV + # value: custom-value + + # Extra configs will be appended to pinot-broker.conf file + extra: + configs: |- + pinot.set.instance.id.to.hostname=true + pinot.query.server.port=7321 + pinot.query.runner.port=7732 + +# ------------------------------------------------------------------------------ +# Pinot Server: +# ------------------------------------------------------------------------------ +server: + name: server + replicaCount: 1 + podManagementPolicy: Parallel + podSecurityContext: {} + # fsGroup: 2000 + securityContext: {} + startCommand: "StartServer" + + probes: + endpoint: "/health" + livenessEnabled: false + readinessEnabled: false + startupEnabled: false + liveness: + endpoint: "/health/liveness" + initialDelaySeconds: 60 + failureThreshold: 10 + timeoutSeconds: 10 + successThreshold: 1 + periodSeconds: 10 + + readiness: + endpoint: "/health/readiness" + initialDelaySeconds: 60 + failureThreshold: 10 + timeoutSeconds: 10 + successThreshold: 1 + periodSeconds: 10 + + startup: + endpoint: "/health/liveness" + initialDelaySeconds: 60 + failureThreshold: 10 + timeoutSeconds: 10 + successThreshold: 1 + periodSeconds: 10 + + dataDir: /var/pinot/server/data/index + segmentTarDir: /var/pinot/server/data/segment + + persistence: + enabled: true + accessMode: ReadWriteOnce + size: 4G + mountPath: /var/pinot/server/data + storageClass: "" + #storageClass: "ssd" + extraVolumes: [] + extraVolumeMounts: [] + + jvmOpts: "-Xms512M -Xmx1G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Xlog:gc*:file=/opt/pinot/gc-pinot-server.log" + + log4j2ConfFile: /opt/pinot/etc/conf/pinot-server-log4j2.xml + pluginsDir: /opt/pinot/plugins + + pdb: + enabled: false + minAvailable: "" + maxUnavailable: 1 + + service: + annotations: {} + clusterIP: "" + externalIPs: [] + loadBalancerIP: "" + loadBalancerSourceRanges: [] + type: ClusterIP + nettyPort: 8098 + nettyPortName: netty + adminPort: 8097 + adminExposePort: 80 + adminPortName: admin + nodePort: "" + protocol: TCP + extraPorts: [] + # - containerPort: 1234 + # protocol: PROTOCOL + # name: extra-port + + resources: + requests: + memory: "1.25Gi" + + nodeSelector: {} + + affinity: {} + + tolerations: [] + + podAnnotations: {} + + # set enabled as true, to automatically roll server stateful set for configmap change + automaticReload: + enabled: false + + updateStrategy: + type: RollingUpdate + + # Use envFrom to define all of the ConfigMap or Secret data as container environment variables. + # ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#configure-all-key-value-pairs-in-a-configmap-as-container-environment-variables + # ref: https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/#configure-all-key-value-pairs-in-a-secret-as-container-environment-variables + envFrom: [] + # - configMapRef: + # name: special-config + # - secretRef: + # name: test-secret + + # Use extraEnv to add individual key value pairs as container environment variables. + # ref: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/ + extraEnv: + - name: LOG4J_CONSOLE_LEVEL + value: info + # - name: PINOT_CUSTOM_ENV + # value: custom-value + + # Extra configs will be appended to pinot-server.conf file + extra: + configs: |- + pinot.set.instance.id.to.hostname=true + pinot.server.instance.realtime.alloc.offheap=true + pinot.query.server.port=7321 + pinot.query.runner.port=7732 + +# ------------------------------------------------------------------------------ +# Pinot Minion: +# ------------------------------------------------------------------------------ +minion: + enabled: false + name: minion + replicaCount: 0 + podManagementPolicy: Parallel + podSecurityContext: {} + # fsGroup: 2000 + securityContext: {} + startCommand: "StartMinion" + + probes: + endpoint: "/health" + livenessEnabled: true + readinessEnabled: true + startupEnabled: false + liveness: + initialDelaySeconds: 60 + failureThreshold: 10 + timeoutSeconds: 10 + successThreshold: 1 + periodSeconds: 10 + + readiness: + initialDelaySeconds: 60 + failureThreshold: 10 + timeoutSeconds: 10 + successThreshold: 1 + periodSeconds: 10 + + startup: + initialDelaySeconds: 60 + failureThreshold: 10 + timeoutSeconds: 10 + successThreshold: 1 + periodSeconds: 10 + + dataDir: /var/pinot/minion/data + jvmOpts: "-XX:ActiveProcessorCount=2 -Xms256M -Xmx1G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Xlog:gc*:file=/opt/pinot/gc-pinot-minion.log" + + log4j2ConfFile: /opt/pinot/etc/conf/pinot-minion-log4j2.xml + pluginsDir: /opt/pinot/plugins + + persistence: + enabled: true + accessMode: ReadWriteOnce + size: 4G + mountPath: /var/pinot/minion/data + storageClass: "" + #storageClass: "ssd" + extraVolumes: [] + extraVolumeMounts: [] + + service: + annotations: {} + clusterIP: "" + externalIPs: [] + loadBalancerIP: "" + loadBalancerSourceRanges: [] + type: ClusterIP + port: 9514 + nodePort: "" + protocol: TCP + name: minion + extraPorts: [] + # - containerPort: 1234 + # protocol: PROTOCOL + # name: extra-port + + resources: + requests: + memory: "1.25Gi" + + nodeSelector: {} + + affinity: {} + + tolerations: [] + + podAnnotations: {} + + automaticReload: + enabled: false + + updateStrategy: + type: RollingUpdate + + # Use envFrom to define all of the ConfigMap or Secret data as container environment variables. + # ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#configure-all-key-value-pairs-in-a-configmap-as-container-environment-variables + # ref: https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/#configure-all-key-value-pairs-in-a-secret-as-container-environment-variables + envFrom: [] + # - configMapRef: + # name: special-config + # - secretRef: + # name: test-secret + + # Use extraEnv to add individual key value pairs as container environment variables. + # ref: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/ + extraEnv: + - name: LOG4J_CONSOLE_LEVEL + value: info + # - name: PINOT_CUSTOM_ENV + # value: custom-value + + # Extra configs will be appended to pinot-minion.conf file + extra: + configs: |- + pinot.set.instance.id.to.hostname=true + + +# ------------------------------------------------------------------------------ +# Pinot Minion Stateless: +# ------------------------------------------------------------------------------ +minionStateless: + enabled: true + name: minion-stateless + replicaCount: 1 + podSecurityContext: {} + # fsGroup: 2000 + securityContext: {} + startCommand: "StartMinion" + + probes: + endpoint: "/health" + livenessEnabled: true + readinessEnabled: true + startupEnabled: true + liveness: + initialDelaySeconds: 60 + failureThreshold: 10 + timeoutSeconds: 10 + successThreshold: 1 + periodSeconds: 10 + + readiness: + initialDelaySeconds: 60 + failureThreshold: 10 + timeoutSeconds: 10 + successThreshold: 1 + periodSeconds: 10 + + startup: + initialDelaySeconds: 60 + failureThreshold: 10 + timeoutSeconds: 10 + successThreshold: 1 + periodSeconds: 10 + + dataDir: /var/pinot/minion/data + jvmOpts: "-XX:ActiveProcessorCount=2 -Xms256M -Xmx1G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Xlog:gc*:file=/opt/pinot/gc-pinot-minion.log" + + log4j2ConfFile: /opt/pinot/etc/conf/pinot-minion-log4j2.xml + pluginsDir: /opt/pinot/plugins + + persistence: + enabled: false + pvcName: minion-data-vol + accessMode: ReadWriteOnce + size: 4G + mountPath: /var/pinot/minion/data + storageClass: "" + #storageClass: "ssd" + extraVolumes: [] + extraVolumeMounts: [] + + service: + port: 9514 + protocol: TCP + name: minion + extraPorts: [] + # - containerPort: 1234 + # protocol: PROTOCOL + # name: extra-port + + resources: + requests: + memory: "1.25Gi" + + nodeSelector: {} + + affinity: {} + + tolerations: [] + + podAnnotations: {} + + # Use envFrom to define all of the ConfigMap or Secret data as container environment variables. + # ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#configure-all-key-value-pairs-in-a-configmap-as-container-environment-variables + # ref: https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/#configure-all-key-value-pairs-in-a-secret-as-container-environment-variables + envFrom: [] + # - configMapRef: + # name: special-config + # - secretRef: + # name: test-secret + + # Use extraEnv to add individual key value pairs as container environment variables. + # ref: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/ + extraEnv: + - name: LOG4J_CONSOLE_LEVEL + value: info + # - name: PINOT_CUSTOM_ENV + # value: custom-value + + # Extra configs will be appended to pinot-minion.conf file + extra: + configs: |- + pinot.set.instance.id.to.hostname=true + +# ------------------------------------------------------------------------------ +# Zookeeper: +# NOTE: IN PRODUCTION USE CASES, IT's BEST TO USE ZOOKEEPER K8S OPERATOR +# ref: https://github.com/pravega/zookeeper-operator#install-the-operator +# ------------------------------------------------------------------------------ + +zookeeper: + ## If true, install the Zookeeper chart alongside Pinot + ## ref: https://github.com/bitnami/charts/tree/master/bitnami/zookeeper + enabled: true + + ## If the Zookeeper Chart is disabled a URL override is required to connect + urlOverride: "my-zookeeper:2181/my-pinot" + + ## Zookeeper port + port: 2181 + + ## Configure Zookeeper resource requests and limits + ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ + resources: + requests: + memory: "1.25Gi" + + ## Replicas + replicaCount: 1 + + ## Ongoing data directory cleanup configuration + autopurge: + + ## The time interval (in hours) for which the purge task has to be triggered + ## Set to a positive integer to enable the auto purging + purgeInterval: 1 + + ## The most recent snapshots amount (and corresponding transaction logs) to retain + snapRetainCount: 5 + + ## Size (in MB) for the Java Heap options (Xmx and Xms) + ## This env var is ignored if Xmx an Xms are configured via `zookeeper.jvmFlags` + heapSize: "1024" + + persistence: + enabled: true + ## The amount of PV storage allocated to each Zookeeper pod in the statefulset + # size: "2Gi" + + ## Specify a Zookeeper imagePullPolicy + ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images + image: + PullPolicy: "IfNotPresent" + + ## Pod scheduling preferences (by default keep pods within a release on separate nodes). + ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity + ## By default we don't set affinity: + affinity: {} # Criteria by which pod label-values influence scheduling for zookeeper pods. + # podAntiAffinity: + # requiredDuringSchedulingIgnoredDuringExecution: + # - topologyKey: "kubernetes.io/hostname" + # labelSelector: + # matchLabels: + # release: zookeeper diff --git a/kubernetes/helm/presto-0.2.0.tgz b/helm/presto-0.2.0.tgz similarity index 100% rename from kubernetes/helm/presto-0.2.0.tgz rename to helm/presto-0.2.0.tgz diff --git a/kubernetes/helm/presto-0.2.1.tgz b/helm/presto-0.2.1.tgz similarity index 100% rename from kubernetes/helm/presto-0.2.1.tgz rename to helm/presto-0.2.1.tgz diff --git a/kubernetes/helm/presto/Chart.yaml b/helm/presto/Chart.yaml similarity index 100% rename from kubernetes/helm/presto/Chart.yaml rename to helm/presto/Chart.yaml diff --git a/kubernetes/helm/presto/README.md b/helm/presto/README.md similarity index 100% rename from kubernetes/helm/presto/README.md rename to helm/presto/README.md diff --git a/kubernetes/helm/presto/launch-presto-ui.sh b/helm/presto/launch-presto-ui.sh similarity index 100% rename from kubernetes/helm/presto/launch-presto-ui.sh rename to helm/presto/launch-presto-ui.sh diff --git a/kubernetes/helm/presto/pinot-presto-cli.sh b/helm/presto/pinot-presto-cli.sh similarity index 100% rename from kubernetes/helm/presto/pinot-presto-cli.sh rename to helm/presto/pinot-presto-cli.sh diff --git a/kubernetes/helm/presto/presto-cli.sh b/helm/presto/presto-cli.sh similarity index 100% rename from kubernetes/helm/presto/presto-cli.sh rename to helm/presto/presto-cli.sh diff --git a/kubernetes/helm/presto/presto-coordinator.yaml b/helm/presto/presto-coordinator.yaml similarity index 100% rename from kubernetes/helm/presto/presto-coordinator.yaml rename to helm/presto/presto-coordinator.yaml diff --git a/kubernetes/helm/presto/presto-worker.yaml b/helm/presto/presto-worker.yaml similarity index 100% rename from kubernetes/helm/presto/presto-worker.yaml rename to helm/presto/presto-worker.yaml diff --git a/kubernetes/helm/presto/templates/_helpers.tpl b/helm/presto/templates/_helpers.tpl similarity index 100% rename from kubernetes/helm/presto/templates/_helpers.tpl rename to helm/presto/templates/_helpers.tpl diff --git a/kubernetes/helm/presto/templates/coordinator/configmap.yaml b/helm/presto/templates/coordinator/configmap.yaml similarity index 100% rename from kubernetes/helm/presto/templates/coordinator/configmap.yaml rename to helm/presto/templates/coordinator/configmap.yaml diff --git a/kubernetes/helm/presto/templates/coordinator/service-external.yaml b/helm/presto/templates/coordinator/service-external.yaml similarity index 100% rename from kubernetes/helm/presto/templates/coordinator/service-external.yaml rename to helm/presto/templates/coordinator/service-external.yaml diff --git a/kubernetes/helm/presto/templates/coordinator/service-headless.yaml b/helm/presto/templates/coordinator/service-headless.yaml similarity index 100% rename from kubernetes/helm/presto/templates/coordinator/service-headless.yaml rename to helm/presto/templates/coordinator/service-headless.yaml diff --git a/kubernetes/helm/presto/templates/coordinator/service.yaml b/helm/presto/templates/coordinator/service.yaml similarity index 100% rename from kubernetes/helm/presto/templates/coordinator/service.yaml rename to helm/presto/templates/coordinator/service.yaml diff --git a/kubernetes/helm/presto/templates/coordinator/statefulset.yml b/helm/presto/templates/coordinator/statefulset.yml similarity index 100% rename from kubernetes/helm/presto/templates/coordinator/statefulset.yml rename to helm/presto/templates/coordinator/statefulset.yml diff --git a/kubernetes/helm/presto/templates/worker/configmap.yaml b/helm/presto/templates/worker/configmap.yaml similarity index 100% rename from kubernetes/helm/presto/templates/worker/configmap.yaml rename to helm/presto/templates/worker/configmap.yaml diff --git a/kubernetes/helm/presto/templates/worker/service-headless.yaml b/helm/presto/templates/worker/service-headless.yaml similarity index 100% rename from kubernetes/helm/presto/templates/worker/service-headless.yaml rename to helm/presto/templates/worker/service-headless.yaml diff --git a/kubernetes/helm/presto/templates/worker/service.yaml b/helm/presto/templates/worker/service.yaml similarity index 100% rename from kubernetes/helm/presto/templates/worker/service.yaml rename to helm/presto/templates/worker/service.yaml diff --git a/kubernetes/helm/presto/templates/worker/statefulset.yml b/helm/presto/templates/worker/statefulset.yml similarity index 100% rename from kubernetes/helm/presto/templates/worker/statefulset.yml rename to helm/presto/templates/worker/statefulset.yml diff --git a/kubernetes/helm/presto/values.yaml b/helm/presto/values.yaml similarity index 100% rename from kubernetes/helm/presto/values.yaml rename to helm/presto/values.yaml diff --git a/kubernetes/helm/setup_gke.sh b/helm/setup_gke.sh similarity index 100% rename from kubernetes/helm/setup_gke.sh rename to helm/setup_gke.sh diff --git a/kubernetes/helm/superset.yaml b/helm/superset.yaml similarity index 100% rename from kubernetes/helm/superset.yaml rename to helm/superset.yaml diff --git a/kubernetes/README.md b/kubernetes/README.md deleted file mode 100644 index 52f3789131d3..000000000000 --- a/kubernetes/README.md +++ /dev/null @@ -1,31 +0,0 @@ - -# Pinot Quickstart on Kubernetes - -Below are the examples of Pinot Kubernetes deployments on different Cloud environments. - -## Deploy Pinot Cluster using [Helm]() - -Please go to directory [helm](helm) for the detailed example. - -## Deploy Pinot Cluster using [Skaffold]() on [Google Kubernetes Engine(GKE)](https://cloud.google.com/kubernetes-engine/) - -Please go to directory [skaffold/gke](skaffold/gke) for the detailed example. diff --git a/kubernetes/helm/index.yaml b/kubernetes/helm/index.yaml deleted file mode 100644 index 5f69405a1146..000000000000 --- a/kubernetes/helm/index.yaml +++ /dev/null @@ -1,243 +0,0 @@ -apiVersion: v1 -entries: - pinot: - - apiVersion: v1 - appVersion: 0.2.6 - created: "2022-11-23T13:05:57.685715-08:00" - dependencies: - - condition: pinot.zookeeper.enabled,zookeeper.enabled - name: zookeeper - repository: https://charts.bitnami.com/bitnami - version: 9.x.x - description: Apache Pinot is a realtime distributed OLAP datastore, which is used - to deliver scalable real time analytics with low latency. It can ingest data - from offline data sources (such as Hadoop and flat files) as well as online - sources (such as Kafka). Pinot is designed to scale horizontally. - digest: a02cf25577d5cfe6a78c82dbb987e1817fe059d276168933635876467071d402 - home: https://pinot.apache.org/ - keywords: - - olap - - analytics - - database - - pinot - maintainers: - - email: dev@pinot.apache.org - name: pinot-dev - name: pinot - sources: - - https://github.com/apache/pinot/tree/master/kubernetes/helm - urls: - - pinot-0.2.6.tgz - version: 0.2.6 - - apiVersion: v1 - appVersion: 0.2.5 - created: "2022-03-29T13:13:13.151614-07:00" - dependencies: - - condition: pinot.zookeeper.enabled,zookeeper.enabled - name: zookeeper - repository: https://charts.bitnami.com/bitnami - version: 7.0.0 - description: Apache Pinot is a realtime distributed OLAP datastore, which is used - to deliver scalable real time analytics with low latency. It can ingest data - from offline data sources (such as Hadoop and flat files) as well as online - sources (such as Kafka). Pinot is designed to scale horizontally. - digest: adf2e1134745cafd3330cfc9272e1ed61586fe3bc59e2440c7d54db5ec5fb261 - home: https://pinot.apache.org/ - keywords: - - olap - - analytics - - database - - pinot - maintainers: - - email: dev@pinot.apache.org - name: pinot-dev - name: pinot - sources: - - https://github.com/apache/pinot/tree/master/kubernetes/helm - urls: - - pinot-0.2.5.tgz - version: 0.2.5 - - apiVersion: v1 - appVersion: 0.2.4 - created: "2021-07-07T15:53:54.287681-07:00" - dependencies: - - condition: pinot.zookeeper.enabled,zookeeper.enabled - name: zookeeper - repository: https://charts.helm.sh/incubator - version: 2.1.6 - description: Apache Pinot is a realtime distributed OLAP datastore, which is used - to deliver scalable real time analytics with low latency. It can ingest data - from offline data sources (such as Hadoop and flat files) as well as online - sources (such as Kafka). Pinot is designed to scale horizontally. - digest: d3d662baa8ec714ac099cd48ea6acf0158b09c1bbd34175d889ed3475159f808 - home: https://pinot.apache.org/ - keywords: - - olap - - analytics - - database - - pinot - maintainers: - - email: dev@pinot.apache.org - name: pinot-dev - name: pinot - sources: - - https://github.com/apache/pinot/tree/master/kubernetes/helm - urls: - - pinot-0.2.4.tgz - version: 0.2.4 - - apiVersion: v1 - appVersion: 0.2.3 - created: "2021-04-22T16:13:57.610533-07:00" - dependencies: - - condition: pinot.zookeeper.enabled,zookeeper.enabled - name: zookeeper - repository: https://charts.helm.sh/incubator - version: 2.1.3 - description: Apache Pinot is a realtime distributed OLAP datastore, which is used - to deliver scalable real time analytics with low latency. It can ingest data - from offline data sources (such as Hadoop and flat files) as well as online - sources (such as Kafka). Pinot is designed to scale horizontally. - digest: 6361ac8a90ad895e2ee4372b11eb52c80107ba6e3e5926b5790bf509ab8a8a7d - home: https://pinot.apache.org/ - keywords: - - olap - - analytics - - database - - pinot - maintainers: - - email: dev@pinot.apache.org - name: pinot-dev - name: pinot - sources: - - https://github.com/apache/pinot/tree/master/kubernetes/helm - urls: - - pinot-0.2.3.tgz - version: 0.2.3 - - apiVersion: v1 - appVersion: 0.2.2 - created: "2021-04-22T16:13:57.604713-07:00" - dependencies: - - condition: pinot.zookeeper.enabled,zookeeper.enabled - name: zookeeper - repository: https://charts.helm.sh/incubator - version: 2.1.3 - description: Apache Pinot is a realtime distributed OLAP datastore, which is used - to deliver scalable real time analytics with low latency. It can ingest data - from offline data sources (such as Hadoop and flat files) as well as online - sources (such as Kafka). Pinot is designed to scale horizontally. - digest: 7b571d881df299a83d5d351df562e81ec5e9d0ab0d2d4f7c2fd8622a997abde7 - home: https://pinot.apache.org/ - keywords: - - olap - - analytics - - database - - pinot - maintainers: - - email: dev@pinot.apache.org - name: pinot-dev - name: pinot - sources: - - https://github.com/apache/pinot/tree/master/kubernetes/helm - urls: - - pinot-0.2.2.tgz - version: 0.2.2 - - apiVersion: v1 - appVersion: 0.2.1 - created: "2021-04-22T16:13:57.600004-07:00" - dependencies: - - condition: pinot.zookeeper.enabled,zookeeper.enabled - name: zookeeper - repository: https://charts.helm.sh/incubator - version: 2.1.3 - description: Apache Pinot is a realtime distributed OLAP datastore, which is used - to deliver scalable real time analytics with low latency. It can ingest data - from offline data sources (such as Hadoop and flat files) as well as online - sources (such as Kafka). Pinot is designed to scale horizontally. - digest: 605b74fb6203f62500638fd679859cc771ca37ed420f89e02a0c82c3ede92690 - home: https://pinot.apache.org/ - keywords: - - olap - - analytics - - database - - pinot - maintainers: - - email: dev@pinot.apache.org - name: pinot-dev - name: pinot - sources: - - https://github.com/apache/pinot/tree/master/kubernetes/helm - urls: - - pinot-0.2.1.tgz - version: 0.2.1 - - apiVersion: v1 - appVersion: 0.2.0 - created: "2021-04-22T16:13:57.592641-07:00" - dependencies: - - condition: pinot.zookeeper.enabled,zookeeper.enabled - name: zookeeper - repository: https://charts.helm.sh/incubator - version: 2.1.3 - description: Apache Pinot is a realtime distributed OLAP datastore, which is used - to deliver scalable real time analytics with low latency. It can ingest data - from offline data sources (such as Hadoop and flat files) as well as online - sources (such as Kafka). Pinot is designed to scale horizontally. - digest: a15cb08e2077d6e2893ea46212a5b769f303af83904681a95e550879fa22f870 - home: https://pinot.apache.org/ - keywords: - - olap - - analytics - - database - - pinot - maintainers: - - email: dev@pinot.apache.org - name: pinot-dev - name: pinot - sources: - - https://github.com/apache/pinot/tree/master/kubernetes/helm - urls: - - pinot-0.2.0.tgz - version: 0.2.0 - presto: - - apiVersion: v1 - appVersion: 0.2.1 - created: "2021-04-22T16:13:57.616017-07:00" - description: Presto is an open source distributed SQL query engine for running - interactive analytic queries against data sources of all sizes ranging from - gigabytes to petabytes. - digest: 370757c5a5c4fdb2e9e2e0e219400719b572e76c1c800e9c3abb29b6e2e5743a - home: https://github.com/prestodb/presto - keywords: - - analytics - - database - - presto - maintainers: - - email: dev@pinot.apache.org - name: pinot-dev - name: presto - sources: - - https://github.com/apache/pinot/tree/master/kubernetes/helm - urls: - - presto-0.2.1.tgz - version: 0.2.1 - - apiVersion: v1 - appVersion: 0.2.0 - created: "2021-04-22T16:13:57.612839-07:00" - description: Presto is an open source distributed SQL query engine for running - interactive analytic queries against data sources of all sizes ranging from - gigabytes to petabytes. - digest: d17eb6e0b1b3ba26e013325d7fb8da1c7a25fad35f37f955f4fbe402b5363b15 - home: https://github.com/prestodb/presto - keywords: - - analytics - - database - - presto - maintainers: - - email: dev@pinot.apache.org - name: pinot-dev - name: presto - sources: - - https://github.com/apache/pinot/tree/master/kubernetes/helm - urls: - - presto-0.2.0.tgz - version: 0.2.0 -generated: "2021-04-22T16:13:57.586041-07:00" diff --git a/kubernetes/helm/pinot/Chart.yaml b/kubernetes/helm/pinot/Chart.yaml deleted file mode 100644 index 8d60c1236650..000000000000 --- a/kubernetes/helm/pinot/Chart.yaml +++ /dev/null @@ -1,35 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: v1 -appVersion: 0.2.7-SNAPSHOT -name: pinot -description: Apache Pinot is a realtime distributed OLAP datastore, which is used to deliver scalable real time analytics with low latency. It can ingest data from offline data sources (such as Hadoop and flat files) as well as online sources (such as Kafka). Pinot is designed to scale horizontally. -version: 0.2.7-SNAPSHOT -keywords: - - olap - - analytics - - database - - pinot -home: https://pinot.apache.org/ -sources: - - https://github.com/apache/pinot/tree/master/kubernetes/helm -maintainers: - - name: pinot-dev - email: dev@pinot.apache.org diff --git a/kubernetes/helm/pinot/README.md b/kubernetes/helm/pinot/README.md deleted file mode 100644 index cf5d086f8cb9..000000000000 --- a/kubernetes/helm/pinot/README.md +++ /dev/null @@ -1,436 +0,0 @@ - - -# Pinot Quickstart on Kubernetes with Helm - -## Prerequisite - -- kubectl () -- Helm () -- Configure kubectl to connect to the Kubernetes cluster. - - Skip to [Section: How to setup a Pinot cluster for demo](#How to setup a Pinot cluster for demo) if a k8s cluster is already setup. - - -## (Optional) Setup a Kubernetes cluster on Amazon Elastic Kubernetes Service (Amazon EKS) - -### (Optional) Create a new k8s cluster on AWS EKS - -- Install AWS CLI () -- Install AWS-IAM-AUTHENTICATOR () -- Install eksctl () - -- Login to your AWS account. - -```bash -aws configure -``` - -Note that environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` will override the aws configuration in file `~/.aws/credentials`. - -- Create an EKS cluster - -Please modify the parameters in the example command below: - -```bash -eksctl create cluster \ ---name pinot-quickstart \ ---version 1.14 \ ---region us-west-2 \ ---nodegroup-name standard-workers \ ---node-type t3.small \ ---nodes 3 \ ---nodes-min 3 \ ---nodes-max 4 \ ---node-ami auto -``` - -You can monitor cluster status by command: - -```bash -EKS_CLUSTER_NAME=pinot-quickstart -aws eks describe-cluster --name ${EKS_CLUSTER_NAME} -``` - -Once the cluster is in `ACTIVE` status, it's ready to be used. - -### (Optional) How to connect to an existing cluster - -Simply run below command to get the credential for the cluster you just created or your existing cluster. - -```bash -EKS_CLUSTER_NAME=pinot-quickstart -aws eks update-kubeconfig --name ${EKS_CLUSTER_NAME} -``` - -To verify the connection, you can run - -```bash -kubectl get nodes -``` - -## (Optional) Setup a Kubernetes cluster on Google Kubernetes Engine(GKE) - -### (Optional) Create a new k8s cluster on GKE - -- Google Cloud SDK () -- Enable Google Cloud Account and create a project, e.g. `pinot-demo`. - - `pinot-demo` will be used as example value for `${GCLOUD_PROJECT}` variable in script example. - - `pinot-demo@example.com` will be used as example value for `${GCLOUD_EMAIL}`. - -Below script will: - -- Create a gCloud cluster `pinot-quickstart` -- Request 2 servers of type `n1-standard-8` for demo. - -Please fill both environment variables: `${GCLOUD_PROJECT}` and `${GCLOUD_EMAIL}` with your gcloud project and gcloud account email in below script. - -```bash -GCLOUD_PROJECT=[your gcloud project name] -GCLOUD_EMAIL=[Your gcloud account email] -./setup_gke.sh -``` - -E.g. - -```bash -GCLOUD_PROJECT=pinot-demo -GCLOUD_EMAIL=pinot-demo@example.com -./setup_gke.sh -``` - -### (Optional) How to connect to an existing GKE cluster - -Simply run below command to get the credential for the cluster you just created or your existing cluster. -Please modify the Env variables `${GCLOUD_PROJECT}`, `${GCLOUD_ZONE}`, `${GCLOUD_CLUSTER}` accordingly in below script. - -```bash -GCLOUD_PROJECT=pinot-demo -GCLOUD_ZONE=us-west1-b -GCLOUD_CLUSTER=pinot-quickstart -gcloud container clusters get-credentials ${GCLOUD_CLUSTER} --zone ${GCLOUD_ZONE} --project ${GCLOUD_PROJECT} -``` - -## (Optional) Setup a Kubernetes cluster on Microsoft Azure - -### (Optional) Create a new k8s cluster on Azure - -- Install Azure CLI () -- Login to your Azure account. - -```bash -az login -``` - -- Create Resource Group - -```bash -AKS_RESOURCE_GROUP=pinot-demo -AKS_RESOURCE_GROUP_LOCATION=eastus -az group create --name ${AKS_RESOURCE_GROUP} --location ${AKS_RESOURCE_GROUP_LOCATION} -``` - -- Create an AKS cluster - -```bash -AKS_RESOURCE_GROUP=pinot-demo -AKS_CLUSTER_NAME=pinot-quickstart -az aks create --resource-group ${AKS_RESOURCE_GROUP} --name ${AKS_CLUSTER_NAME} --node-count 3 -``` - -(Optional) Please register default provider if above command failed for error: `MissingSubscriptionRegistration` - -```bash -az provider register --namespace Microsoft.Network -``` - -### (Optional) How to connect to an existing AKS cluster - -Simply run below command to get the credential for the cluster you just created or your existing cluster. - -```bash -AKS_RESOURCE_GROUP=pinot-demo -AKS_CLUSTER_NAME=pinot-quickstart -az aks get-credentials --resource-group ${AKS_RESOURCE_GROUP} --name ${AKS_CLUSTER_NAME} -``` - -To verify the connection, you can run - -```bash -kubectl get nodes -``` - -## How to setup a Pinot cluster for demo - -### Update helm dependency - -```bash -helm dependency update -``` - -### Start Pinot with Helm - -- For helm v3.X.X - -```bash -kubectl create ns pinot-quickstart -helm install pinot -n pinot-quickstart . -``` - -- For helm v3.X.X - -```bash -kubectl create ns pinot-quickstart - -# First run dry-run with debug to verify: -helm install -n pinot-quickstart pinot . --dry-run --debug - -# Install the Helm chart with: -helm install -n pinot-quickstart pinot . -``` - -- For helm v2.12.1 - -If cluster is just initialized, ensure helm is initialized by running: - -```bash -helm init --service-account tiller -``` - -Then deploy pinot cluster by: - -```bash -helm install --namespace "pinot-quickstart" --name "pinot" . -``` - -### Troubleshooting (For helm v2.12.1) - -- Error: Please run below command if encountering issue: - -``` -Error: could not find tiller". -``` - -- Resolution: - -```bash -kubectl -n kube-system delete deployment tiller-deploy -kubectl -n kube-system delete service/tiller-deploy -helm init --service-account tiller -``` - -- Error: Please run below command if encountering permission issue: - -```Error: release pinot failed: namespaces "pinot-quickstart" is forbidden: User "system:serviceaccount:kube-system:default" cannot get resource "namespaces" in API group "" in the namespace "pinot-quickstart"``` - -- Resolution: - -```bash -kubectl apply -f helm-rbac.yaml -``` - -#### To check deployment status - -```bash -kubectl get all -n pinot-quickstart -``` - -### Pinot Realtime QuickStart - -#### Bring up a Kafka Cluster for realtime data ingestion - -- For helm v3.X.X - -```bash -helm repo add incubator https://charts.helm.sh/incubator -helm install -n pinot-quickstart kafka incubator/kafka --set replicas=1 -``` - -- For helm v2.12.1 - -```bash -helm repo add incubator https://charts.helm.sh/incubator -helm install --namespace "pinot-quickstart" --name kafka incubator/kafka --set replicas=1 -``` - -#### Create Kafka topic - -```bash -kubectl -n pinot-quickstart exec kafka-0 -- kafka-topics.sh --bootstrap-server kafka-0:9092 --topic flights-realtime --create --partitions 1 --replication-factor 1 -kubectl -n pinot-quickstart exec kafka-0 -- kafka-topics.sh --bootstrap-server kafka-0:9092 --topic flights-realtime-avro --create --partitions 1 --replication-factor 1 -``` - -#### Load data into Kafka and create Pinot schema/table - -```bash -kubectl apply -f pinot-realtime-quickstart.yml -``` - -### How to query pinot data - -Please use below script to do local port-forwarding and open Pinot query console on your web browser. - -```bash -./query-pinot-data.sh -``` - -## Configuring the Chart - -This chart includes a ZooKeeper chart as a dependency to the Pinot -cluster in its `requirement.yaml` by default. The chart can be customized using the -following configurable parameters: - -| Parameter | Description | Default | -|------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| -| `image.repository` | Pinot Container image repo | `apachepinot/pinot` | -| `image.tag` | Pinot Container image tag | `release-0.7.1` | -| `image.pullPolicy` | Pinot Container image pull policy | `IfNotPresent` | -| `cluster.name` | Pinot Cluster name | `pinot-quickstart` | -|------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| -| `controller.name` | Name of Pinot Controller | `controller` | -| `controller.port` | Pinot controller port | `9000` | -| `controller.replicaCount` | Pinot controller replicas | `1` | -| `controller.data.dir` | Pinot controller data directory, should be same as `controller.persistence.mountPath` or a sub directory of it | `/var/pinot/controller/data` | -| `controller.vip.enabled` | Enable Pinot controller Vip host | `false` | -| `controller.vip.host` | Pinot controller Vip host | `pinot-controller` | -| `controller.vip.port` | Pinot controller Vip port | `9000` | -| `controller.persistence.enabled` | Use a PVC to persist Pinot Controller data | `true` | -| `controller.persistence.accessMode` | Access mode of data volume | `ReadWriteOnce` | -| `controller.persistence.size` | Size of data volume | `1G` | -| `controller.persistence.mountPath` | Mount path of controller data volume | `/var/pinot/controller/data` | -| `controller.persistence.storageClass` | Storage class of backing PVC | `""` | -| `controller.jvmOpts` | Pinot Controller JVM Options | `-Xms256M -Xmx1G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime -Xloggc:/opt/pinot/gc-pinot-controller.log` | -| `controller.log4j2ConfFile` | Pinot Controller log4j2 configuration file | `/opt/pinot/conf/log4j2.xml` | -| `controller.pluginsDir` | Pinot Controller plugins directory | `/opt/pinot/plugins` | -| `controller.service.port` | Service Port | `9000` | -| `controller.external.enabled` | If True, exposes Pinot Controller externally | `true` | -| `controller.external.type` | Service Type | `LoadBalancer` | -| `controller.external.port` | Service Port | `9000` | -| `controller.resources` | Pinot Controller resource requests and limits | `{}` | -| `controller.nodeSelector` | Node labels for controller pod assignment | `{}` | -| `controller.affinity` | Defines affinities and anti-affinities for pods as defined in: preferences | `{}` | -| `controller.tolerations` | List of node tolerations for the pods. | `[]` | -| `controller.podAnnotations` | Annotations to be added to controller pod | `{}` | -| `controller.updateStrategy.type` | StatefulSet update strategy to use. | `RollingUpdate` | -| `controller.extra.configs` | Extra configs append to 'pinot-controller.conf' file to start Pinot Controller | `pinot.set.instance.id.to.hostname=true` | -|------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| -| `broker.name` | Name of Pinot Broker | `broker` | -| `broker.port` | Pinot broker port | `8099` | -| `broker.replicaCount` | Pinot broker replicas | `1` | -| `broker.jvmOpts` | Pinot Broker JVM Options | `-Xms256M -Xmx1G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime -Xloggc:/opt/pinot/gc-pinot-broker.log` | -| `broker.log4j2ConfFile` | Pinot Broker log4j2 configuration file | `/opt/pinot/conf/log4j2.xml` | -| `broker.pluginsDir` | Pinot Broker plugins directory | `/opt/pinot/plugins` | -| `broker.service.port` | Service Port | `8099` | -| `broker.external.enabled` | If True, exposes Pinot Broker externally | `true` | -| `broker.external.type` | External service Type | `LoadBalancer` | -| `broker.external.port` | External service Port | `8099` | -| `broker.routingTable.builderClass` | Routing Table Builder Class | `random` | -| `broker.resources` | Pinot Broker resource requests and limits | `{}` | -| `broker.nodeSelector` | Node labels for broker pod assignment | `{}` | -| `broker.affinity` | Defines affinities and anti-affinities for pods as defined in: preferences | `{}` | -| `broker.tolerations` | List of node tolerations for the pods. | `[]` | -| `broker.podAnnotations` | Annotations to be added to broker pod | `{}` | -| `broker.updateStrategy.type` | StatefulSet update strategy to use. | `RollingUpdate` | -| `broker.extra.configs` | Extra configs append to 'pinot-broker.conf' file to start Pinot Broker | `pinot.set.instance.id.to.hostname=true` | -|------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| -| `server.name` | Name of Pinot Server | `server` | -| `server.port.netty` | Pinot server netty port | `8098` | -| `server.port.admin` | Pinot server admin port | `8097` | -| `server.replicaCount` | Pinot server replicas | `1` | -| `server.dataDir` | Pinot server data directory, should be same as `server.persistence.mountPath` or a sub directory of it | `/var/pinot/server/data/index` | -| `server.segmentTarDir` | Pinot server segment directory, should be same as `server.persistence.mountPath` or a sub directory of it | `/var/pinot/server/data/segments` | -| `server.persistence.enabled` | Use a PVC to persist Pinot Server data | `true` | -| `server.persistence.accessMode` | Access mode of data volume | `ReadWriteOnce` | -| `server.persistence.size` | Size of data volume | `4G` | -| `server.persistence.mountPath` | Mount path of server data volume | `/var/pinot/server/data` | -| `server.persistence.storageClass` | Storage class of backing PVC | `""` | -| `server.jvmOpts` | Pinot Server JVM Options | `-Xms512M -Xmx1G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime -Xloggc:/opt/pinot/gc-pinot-server.log` | -| `server.log4j2ConfFile` | Pinot Server log4j2 configuration file | `/opt/pinot/conf/log4j2.xml` | -| `server.pluginsDir` | Pinot Server plugins directory | `/opt/pinot/plugins` | -| `server.service.port` | Service Port | `8098` | -| `server.resources` | Pinot Server resource requests and limits | `{}` | -| `server.nodeSelector` | Node labels for server pod assignment | `{}` | -| `server.affinity` | Defines affinities and anti-affinities for pods as defined in: preferences | `{}` | -| `server.tolerations` | List of node tolerations for the pods. | `[]` | -| `server.podAnnotations` | Annotations to be added to server pod | `{}` | -| `server.updateStrategy.type` | StatefulSet update strategy to use. | `RollingUpdate` | -| `server.extra.configs` | Extra configs append to 'pinot-server.conf' file to start Pinot Server | `pinot.set.instance.id.to.hostname=true` | -|------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| -| `minion.name` | Name of Pinot Minion | `minion` | -| `minion.port` | Pinot minion netty port | `9514` | -| `minion.replicaCount` | Pinot minion replicas | `1` | -| `minion.dataDir` | Pinot minion data directory, should be same as `minion.persistence.mountPath` or a sub directory of it | `/var/pinot/minion/data` | -| `minion.persistence.enabled` | Use a PVC to persist Pinot minion data | `true` | -| `minion.persistence.accessMode` | Access mode of data volume | `ReadWriteOnce` | -| `minion.persistence.size` | Size of data volume | `4G` | -| `minion.persistence.mountPath` | Mount path of minion data volume | `/var/pinot/minion/data` | -| `minion.persistence.storageClass` | Storage class of backing PVC | `""` | -| `minion.jvmOpts` | Pinot minion JVM Options | `-Xms512M -Xmx1G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintGCApplicationConcurrentTime -Xloggc:/opt/pinot/gc-pinot-minion.log` | -| `minion.log4j2ConfFile` | Pinot minion log4j2 configuration file | `/opt/pinot/conf/log4j2.xml` | -| `minion.pluginsDir` | Pinot minion plugins directory | `/opt/pinot/plugins` | -| `minion.service.port` | Service Port | `9514` | -| `minion.resources` | Pinot minion resource requests and limits | `{}` | -| `minion.nodeSelector` | Node labels for minion pod assignment | `{}` | -| `minion.affinity` | Defines affinities and anti-affinities for pods as defined in: preferences | `{}` | -| `minion.tolerations` | List of node tolerations for the pods. | `[]` | -| `minion.podAnnotations` | Annotations to be added to minion pod | `{}` | -| `minion.updateStrategy.type` | StatefulSet update strategy to use. | `RollingUpdate` | -| `minion.extra.configs` | Extra configs append to 'pinot-minion.conf' file to start Pinot Minion | `pinot.set.instance.id.to.hostname=true` | -|------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| -| `zookeeper.enabled` | If True, installs Zookeeper Chart | `true` | -| `zookeeper.resources` | Zookeeper resource requests and limits | `{}` | -| `zookeeper.env` | Environmental variables provided to Zookeeper Zookeeper | `{ZK_HEAP_SIZE: "256M"}` | -| `zookeeper.storage` | Zookeeper Persistent volume size | `2Gi` | -| `zookeeper.image.tag` | Zookeeper Image Version -| `zookeeper.image.PullPolicy` | Zookeeper Container pull policy | `IfNotPresent` | -| `zookeeper.url` | URL of Zookeeper Cluster (unneeded if installing Zookeeper Chart) | `""` | -| `zookeeper.port` | Port of Zookeeper Cluster | `2181` | -| `zookeeper.affinity` | Defines affinities and anti-affinities for pods as defined in: preferences | `{}` | -|------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| - -Specify parameters using `--set key=value[,key=value]` argument to `helm install` - -```bash -helm install --name pinot -f values.yaml . --set server.replicaCount=2 -``` - -Alternatively a YAML file that specifies the values for the parameters can be provided like this: - -```bash -helm install --name pinot -f values.yaml . -``` - -If you are using GKE, Create a storageClass: - -``` -kubectl apply -f gke-ssd.yaml -``` - -or If you want to use pd-standard storageClass: - -```bash -kubectl apply -f gke-pd.yaml -``` - -## How to clean up Pinot deployment - -```bash -kubectl delete ns pinot-quickstart -``` diff --git a/kubernetes/helm/pinot/templates/_helpers.tpl b/kubernetes/helm/pinot/templates/_helpers.tpl deleted file mode 100644 index fa30dcfc8153..000000000000 --- a/kubernetes/helm/pinot/templates/_helpers.tpl +++ /dev/null @@ -1,321 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -{{/* -Expand the name of the chart. -*/}} -{{- define "pinot.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "pinot.fullname" -}} -{{- if .Values.fullnameOverride }} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- $name := default .Chart.Name .Values.nameOverride }} -{{- if contains $name .Release.Name }} -{{- .Release.Name | trunc 63 | trimSuffix "-" }} -{{- else }} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} -{{- end }} -{{- end }} -{{- end }} - - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "pinot.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{/* -Match Selector labels -*/}} -{{- define "pinot.matchLabels" -}} -app: {{ include "pinot.name" . }} -release: {{ .Release.Name }} -{{- range $key, $value := .Values.additionalMatchLabels }} -{{ $key }}: {{ $value }} -{{- end }} -{{- end }} - -{{/* -Common labels -*/}} -{{- define "pinot.labels" -}} -helm.sh/chart: {{ include "pinot.chart" . }} -{{ include "pinot.matchLabels" . }} -{{- if .Chart.AppVersion }} -app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} -{{- end }} -app.kubernetes.io/managed-by: {{ .Release.Service }} -heritage: {{ .Release.Service }} -{{- end }} - -{{/* -Broker labels -*/}} -{{- define "pinot.brokerLabels" -}} -{{- include "pinot.labels" . }} -component: {{ .Values.broker.name }} -{{- end }} - - -{{/* -Controller labels -*/}} -{{- define "pinot.controllerLabels" -}} -{{- include "pinot.labels" . }} -component: {{ .Values.controller.name }} -{{- end }} - -{{/* -Minion labels -*/}} -{{- define "pinot.minionLabels" -}} -{{- include "pinot.labels" . }} -component: {{ .Values.minion.name }} -{{- end }} - -{{/* -minionStateless labels -*/}} -{{- define "pinot.minionStatelessLabels" -}} -{{- include "pinot.labels" . }} -component: {{ .Values.minionStateless.name }} -{{- end }} - -{{/* -Server labels -*/}} -{{- define "pinot.serverLabels" -}} -{{- include "pinot.labels" . }} -component: {{ .Values.server.name }} -{{- end }} - - - -{{/* -Broker Match Selector labels -*/}} -{{- define "pinot.brokerMatchLabels" -}} -{{- include "pinot.matchLabels" . }} -component: {{ .Values.broker.name }} -{{- end }} - -{{/* -Controller Match Selector labels -*/}} -{{- define "pinot.controllerMatchLabels" -}} -{{- include "pinot.matchLabels" . }} -component: {{ .Values.controller.name }} -{{- end }} - - -{{/* -Minion Match Selector labels -*/}} -{{- define "pinot.minionMatchLabels" -}} -{{- include "pinot.matchLabels" . }} -component: {{ .Values.minion.name }} -{{- end }} - - -{{/* -MinionStateless Match Selector labels -*/}} -{{- define "pinot.minionStatelessMatchLabels" -}} -{{- include "pinot.matchLabels" . }} -component: {{ .Values.minionStateless.name }} -{{- end }} - - -{{/* -Server Match Selector labels -*/}} -{{- define "pinot.serverMatchLabels" -}} -{{- include "pinot.matchLabels" . }} -component: {{ .Values.server.name }} -{{- end }} - - -{{/* -Create the name of the service account to use for pinot components -*/}} -{{- define "pinot.serviceAccountName" -}} -{{- if .Values.serviceAccount.create }} -{{- default (include "pinot.fullname" .) .Values.serviceAccount.name }} -{{- else }} -{{- default "default" .Values.serviceAccount.name }} -{{- end }} -{{- end }} - - -{{/* -Create a default fully qualified zookeeper name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "pinot.zookeeper.fullname" -}} -{{- if .Values.zookeeper.fullnameOverride -}} -{{- .Values.zookeeper.fullnameOverride | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- $name := default "zookeeper" .Values.zookeeper.nameOverride -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} - -{{/* -Form the Zookeeper URL. If zookeeper is installed as part of this chart, use k8s service discovery, -else use user-provided URL -*/}} -{{- define "zookeeper.url" }} -{{- $port := .Values.zookeeper.port | toString }} -{{- if .Values.zookeeper.enabled -}} -{{- printf "%s:%s" (include "pinot.zookeeper.fullname" .) $port }} -{{- else -}} -{{- required "Missing 'zookeeper.urlOverride' entry zookeeper is disabled!" .Values.zookeeper.urlOverride }} -{{- end -}} -{{- end -}} - -{{/* -Create a default fully qualified pinot controller name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "pinot.controller.fullname" -}} -{{ template "pinot.fullname" . }}-{{ .Values.controller.name }} -{{- end -}} - - -{{/* -Create a default fully qualified pinot broker name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "pinot.broker.fullname" -}} -{{ template "pinot.fullname" . }}-{{ .Values.broker.name }} -{{- end -}} - - -{{/* -Create a default fully qualified pinot server name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "pinot.server.fullname" -}} -{{ template "pinot.fullname" . }}-{{ .Values.server.name }} -{{- end -}} - - -{{/* -Create a default fully qualified pinot minion name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "pinot.minion.fullname" -}} -{{ template "pinot.fullname" . }}-{{ .Values.minion.name }} -{{- end -}} - - -{{/* -Create a default fully qualified pinot minion stateless name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "pinot.minionStateless.fullname" -}} -{{ template "pinot.fullname" . }}-{{ .Values.minionStateless.name }} -{{- end -}} - -{{/* -The name of the pinot controller headless service. -*/}} -{{- define "pinot.controller.headless" -}} -{{- printf "%s-headless" (include "pinot.controller.fullname" .) | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -The name of the pinot broker headless service. -*/}} -{{- define "pinot.broker.headless" -}} -{{- printf "%s-headless" (include "pinot.broker.fullname" .) | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -The name of the pinot server headless service. -*/}} -{{- define "pinot.server.headless" -}} -{{- printf "%s-headless" (include "pinot.server.fullname" .) | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -The name of the pinot minion headless service. -*/}} -{{- define "pinot.minion.headless" -}} -{{- printf "%s-headless" (include "pinot.minion.fullname" .) | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -The name of the pinot controller external service. -*/}} -{{- define "pinot.controller.external" -}} -{{- printf "%s-external" (include "pinot.controller.fullname" .) | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -The name of the pinot broker external service. -*/}} -{{- define "pinot.broker.external" -}} -{{- printf "%s-external" (include "pinot.broker.fullname" .) | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -The name of the pinot controller config. -*/}} -{{- define "pinot.controller.config" -}} -{{- printf "%s-config" (include "pinot.controller.fullname" .) | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -The name of the pinot broker config. -*/}} -{{- define "pinot.broker.config" -}} -{{- printf "%s-config" (include "pinot.broker.fullname" .) | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -The name of the pinot server config. -*/}} -{{- define "pinot.server.config" -}} -{{- printf "%s-config" (include "pinot.server.fullname" .) | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -The name of the pinot minion config. -*/}} -{{- define "pinot.minion.config" -}} -{{- printf "%s-config" (include "pinot.minion.fullname" .) | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -The name of the pinot minion stateless config. -*/}} -{{- define "pinot.minionStateless.config" -}} -{{- printf "%s-config" (include "pinot.minionStateless.fullname" .) | trunc 63 | trimSuffix "-" -}} -{{- end -}} diff --git a/kubernetes/helm/pinot/templates/broker/configmap.yaml b/kubernetes/helm/pinot/templates/broker/configmap.yaml deleted file mode 100644 index 37161272c25c..000000000000 --- a/kubernetes/helm/pinot/templates/broker/configmap.yaml +++ /dev/null @@ -1,34 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "pinot.broker.config" . }} -data: - pinot-broker.conf: |- - pinot.broker.client.queryPort={{ .Values.broker.service.port }} - pinot.broker.routing.table.builder.class={{ .Values.broker.routingTable.builderClass }} -{{ .Values.broker.extra.configs | indent 4 }} -{{- if .Values.pinotAuth.enabled}} - pinot.broker.access.control.class={{ .Values.pinotAuth.brokerFactoryClass }} -{{- range $config := .Values.pinotAuth.configs}} -{{ printf "pinot.broker.%s" $config | indent 4 -}} -{{- end }} -{{- end }} diff --git a/kubernetes/helm/pinot/templates/broker/ingress.yaml b/kubernetes/helm/pinot/templates/broker/ingress.yaml deleted file mode 100644 index 80aa8fe42662..000000000000 --- a/kubernetes/helm/pinot/templates/broker/ingress.yaml +++ /dev/null @@ -1,67 +0,0 @@ -{{- if .Values.broker.ingress.v1beta1.enabled -}} -{{- $ingressPath := .Values.broker.ingress.v1beta1.path -}} -{{- $serviceName := include "pinot.broker.fullname" . -}} -{{- $servicePort := .Values.broker.service.port -}} -apiVersion: extensions/v1beta1 -kind: Ingress -metadata: - name: {{ $serviceName }} -{{- if .Values.broker.ingress.v1beta1.annotations }} - annotations: -{{ toYaml .Values.broker.ingress.v1beta1.annotations | indent 4 }} -{{- end }} - labels: -{{- include "pinot.brokerLabels" . | nindent 4 }} -spec: -{{- if .Values.broker.ingress.v1beta1.tls }} - tls: -{{ toYaml .Values.broker.ingress.v1beta1.tls | indent 4 }} -{{- end }} - rules: - {{- range .Values.broker.ingress.v1beta1.hosts }} - - host: {{ . }} - http: - paths: - - path: {{ $ingressPath }} - backend: - serviceName: {{ $serviceName }} - servicePort: {{ $servicePort }} - {{- end }} -{{- end }} - -{{- if .Values.broker.ingress.v1.enabled -}} -{{- $ingressPath := .Values.broker.ingress.v1.path -}} -{{- $serviceName := include "pinot.broker.fullname" . -}} -{{- $servicePort := .Values.broker.service.port -}} -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: {{ $serviceName }} -{{- if .Values.broker.ingress.v1.annotations }} - annotations: -{{ toYaml .Values.broker.ingress.v1.annotations | indent 4 }} -{{- end }} - labels: -{{- include "pinot.brokerLabels" . | nindent 4 }} -spec: -{{- if .Values.broker.ingress.v1.ingressClassName }} - ingressClassName: {{ .Values.broker.ingress.v1.ingressClassName }} -{{- end }} -{{- if .Values.broker.ingress.v1.tls }} - tls: -{{ toYaml .Values.broker.ingress.v1.tls | indent 4 }} -{{- end }} - rules: - {{- range .Values.broker.ingress.v1.hosts }} - - host: {{ . }} - http: - paths: - - path: {{ $ingressPath }} - pathType: Prefix - backend: - service: - name: {{ $serviceName }} - port: - number: {{ $servicePort }} - {{- end }} -{{- end }} diff --git a/kubernetes/helm/pinot/templates/broker/service-external.yaml b/kubernetes/helm/pinot/templates/broker/service-external.yaml deleted file mode 100644 index 075bd0f160db..000000000000 --- a/kubernetes/helm/pinot/templates/broker/service-external.yaml +++ /dev/null @@ -1,41 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -{{- if .Values.broker.external.enabled }} ---- -apiVersion: v1 -kind: Service -metadata: - name: {{ template "pinot.broker.external" . }} - annotations: -{{ toYaml .Values.broker.external.annotations | indent 4 }} - labels: - {{- include "pinot.brokerLabels" . | nindent 4 }} -spec: - type: {{ .Values.broker.external.type }} - ports: - - name: external-broker - port: {{ .Values.broker.external.port }} - selector: - {{- include "pinot.brokerMatchLabels" . | nindent 4 }} -{{- with .Values.broker.service.loadBalancerSourceRanges }} - loadBalancerSourceRanges: -{{ toYaml . | indent 4 }} -{{- end }} -{{- end }} diff --git a/kubernetes/helm/pinot/templates/broker/service-headless.yaml b/kubernetes/helm/pinot/templates/broker/service-headless.yaml deleted file mode 100644 index 1c8ad71e7208..000000000000 --- a/kubernetes/helm/pinot/templates/broker/service-headless.yaml +++ /dev/null @@ -1,33 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: v1 -kind: Service -metadata: - name: {{ template "pinot.broker.headless" . }} - labels: - {{- include "pinot.brokerLabels" . | nindent 4 }} -spec: - clusterIP: None - ports: - # [pod_name].[service_name].[namespace].svc.cluster.local - - name: {{ .Values.broker.service.name }} - port: {{ .Values.broker.service.port }} - selector: - {{- include "pinot.brokerMatchLabels" . | nindent 4 }} diff --git a/kubernetes/helm/pinot/templates/broker/service.yaml b/kubernetes/helm/pinot/templates/broker/service.yaml deleted file mode 100644 index bd0f4a5db067..000000000000 --- a/kubernetes/helm/pinot/templates/broker/service.yaml +++ /dev/null @@ -1,35 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: v1 -kind: Service -metadata: - name: {{ include "pinot.broker.fullname" . }} - annotations: -{{ toYaml .Values.broker.service.annotations | indent 4 }} - labels: - {{- include "pinot.brokerLabels" . | nindent 4 }} -spec: - type: {{ .Values.broker.service.type }} - ports: - # [pod_name].[service_name].[namespace].svc.cluster.local - - name: {{ .Values.broker.service.name }} - port: {{ .Values.broker.service.port }} - selector: - {{- include "pinot.brokerMatchLabels" . | nindent 4 }} diff --git a/kubernetes/helm/pinot/templates/broker/statefulset.yaml b/kubernetes/helm/pinot/templates/broker/statefulset.yaml deleted file mode 100644 index f53ce57d3549..000000000000 --- a/kubernetes/helm/pinot/templates/broker/statefulset.yaml +++ /dev/null @@ -1,111 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: {{ include "pinot.broker.fullname" . }} - labels: - {{- include "pinot.brokerLabels" . | nindent 4 }} -spec: - selector: - matchLabels: - {{- include "pinot.brokerMatchLabels" . | nindent 6 }} - serviceName: {{ template "pinot.broker.headless" . }} - replicas: {{ .Values.broker.replicaCount }} - updateStrategy: - type: {{ .Values.broker.updateStrategy.type }} - podManagementPolicy: {{ .Values.broker.podManagementPolicy }} - template: - metadata: - labels: - {{- include "pinot.brokerLabels" . | nindent 8 }} - annotations: -{{ toYaml .Values.broker.podAnnotations | indent 8 }} - spec: - terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} - serviceAccountName: {{ include "pinot.serviceAccountName" . }} - securityContext: - {{- toYaml .Values.broker.podSecurityContext | nindent 8 }} - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - nodeSelector: -{{ toYaml .Values.broker.nodeSelector | indent 8 }} - affinity: -{{ toYaml .Values.broker.affinity | indent 8 }} - tolerations: -{{ toYaml .Values.broker.tolerations | indent 8 }} - containers: - - name: broker - securityContext: - {{- toYaml .Values.broker.securityContext | nindent 10 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - args: [ - "StartBroker", - "-clusterName", "{{ .Values.cluster.name }}", - "-zkAddress", {{ include "zookeeper.url" . | quote }}, - "-configFileName", "/var/pinot/broker/config/pinot-broker.conf" - ] - env: - - name: JAVA_OPTS - value: "{{ .Values.broker.jvmOpts }} -Dlog4j2.configurationFile={{ .Values.broker.log4j2ConfFile }} -Dplugins.dir={{ .Values.broker.pluginsDir }}" -{{- if .Values.broker.extraEnv }} -{{ toYaml .Values.broker.extraEnv | indent 10 }} -{{- end }} - envFrom: -{{ toYaml .Values.broker.envFrom | indent 10 }} - ports: - - containerPort: {{ .Values.broker.service.port }} - protocol: {{ .Values.broker.service.protocol }} - name: {{ .Values.broker.service.name }} - volumeMounts: - - name: config - mountPath: /var/pinot/broker/config - {{- if ne (len .Values.broker.persistence.extraVolumeMounts) 0 }} -{{ toYaml .Values.broker.persistence.extraVolumeMounts | indent 10 }} - {{- end }} - {{- if .Values.broker.probes.livenessEnabled }} - livenessProbe: - initialDelaySeconds: {{ .Values.probes.initialDelaySeconds }} - periodSeconds: {{ .Values.probes.periodSeconds }} - httpGet: - path: {{ .Values.broker.probes.endpoint }} - port: {{ .Values.broker.service.port }} - {{- end }} - {{- if .Values.broker.probes.readinessEnabled }} - readinessProbe: - initialDelaySeconds: {{ .Values.probes.initialDelaySeconds }} - periodSeconds: {{ .Values.probes.periodSeconds }} - httpGet: - path: {{ .Values.broker.probes.endpoint }} - port: {{ .Values.broker.service.port }} - {{- end }} - resources: -{{ toYaml .Values.broker.resources | indent 12 }} - restartPolicy: Always - volumes: - - name: config - configMap: - name: {{ include "pinot.broker.config" . }} - {{- if ne (len .Values.broker.persistence.extraVolumes) 0 }} -{{ toYaml .Values.broker.persistence.extraVolumes | indent 8 }} - {{- end }} diff --git a/kubernetes/helm/pinot/templates/controller/configmap.yaml b/kubernetes/helm/pinot/templates/controller/configmap.yaml deleted file mode 100644 index 2db39593bb90..000000000000 --- a/kubernetes/helm/pinot/templates/controller/configmap.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "pinot.controller.config" . }} -data: - pinot-controller.conf: |- - controller.helix.cluster.name={{ .Values.cluster.name }} - controller.port={{ .Values.controller.service.port }} -{{- if .Values.controller.vip.enabled }} - controller.vip.host={{ .Values.controller.vip.host }} - controller.vip.port={{ .Values.controller.vip.port }} -{{- end }} - controller.data.dir={{ .Values.controller.data.dir }} - controller.zk.str={{ include "zookeeper.url" . }} -{{ .Values.controller.extra.configs | indent 4 }} -{{- if .Values.pinotAuth.enabled}} - controller.admin.access.control.factory.class={{ .Values.pinotAuth.controllerFactoryClass }} -{{- range $config := .Values.pinotAuth.configs}} -{{ printf "controller.admin.%s" $config | indent 4 -}} -{{- end }} -{{- end }} diff --git a/kubernetes/helm/pinot/templates/controller/ingress.yaml b/kubernetes/helm/pinot/templates/controller/ingress.yaml deleted file mode 100644 index 449a84e67860..000000000000 --- a/kubernetes/helm/pinot/templates/controller/ingress.yaml +++ /dev/null @@ -1,67 +0,0 @@ -{{- if .Values.controller.ingress.v1beta1.enabled -}} -{{- $ingressPath := .Values.controller.ingress.v1beta1.path -}} -{{- $serviceName := include "pinot.controller.fullname" . -}} -{{- $servicePort := .Values.controller.service.port -}} -apiVersion: extensions/v1beta1 -kind: Ingress -metadata: - name: {{ $serviceName }} -{{- if .Values.controller.ingress.v1beta1.annotations }} - annotations: -{{ toYaml .Values.controller.ingress.v1beta1.annotations | indent 4 }} -{{- end }} - labels: -{{- include "pinot.controllerLabels" . | nindent 4 }} -spec: -{{- if .Values.controller.ingress.v1beta1.tls }} - tls: -{{ toYaml .Values.controller.ingress.v1beta1.tls | indent 4 }} -{{- end }} - rules: - {{- range .Values.controller.ingress.v1beta1.hosts }} - - host: {{ . }} - http: - paths: - - path: {{ $ingressPath }} - backend: - serviceName: {{ $serviceName }} - servicePort: {{ $servicePort }} - {{- end }} -{{- end }} - -{{- if .Values.controller.ingress.v1.enabled -}} -{{- $ingressPath := .Values.controller.ingress.v1.path -}} -{{- $serviceName := include "pinot.controller.fullname" . -}} -{{- $servicePort := .Values.controller.service.port -}} -apiVersion: networking.k8s.io/v1 -kind: Ingress -metadata: - name: {{ $serviceName }} -{{- if .Values.controller.ingress.v1.annotations }} - annotations: -{{ toYaml .Values.controller.ingress.v1.annotations | indent 4 }} -{{- end }} - labels: -{{- include "pinot.controllerLabels" . | nindent 4 }} -spec: -{{- if .Values.controller.ingress.v1.ingressClassName }} - ingressClassName: {{ .Values.controller.ingress.v1.ingressClassName }} -{{- end }} -{{- if .Values.controller.ingress.v1.tls }} - tls: -{{ toYaml .Values.controller.ingress.v1.tls | indent 4 }} -{{- end }} - rules: - {{- range .Values.controller.ingress.v1.hosts }} - - host: {{ . }} - http: - paths: - - path: {{ $ingressPath }} - pathType: Prefix - backend: - service: - name: {{ $serviceName }} - port: - number: {{ $servicePort }} - {{- end }} -{{- end }} diff --git a/kubernetes/helm/pinot/templates/controller/service-external.yaml b/kubernetes/helm/pinot/templates/controller/service-external.yaml deleted file mode 100644 index 6fefc22813ef..000000000000 --- a/kubernetes/helm/pinot/templates/controller/service-external.yaml +++ /dev/null @@ -1,41 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -{{- if .Values.controller.external.enabled }} ---- -apiVersion: v1 -kind: Service -metadata: - name: {{ template "pinot.controller.external" . }} - annotations: -{{ toYaml .Values.controller.external.annotations | indent 4 }} - labels: - {{- include "pinot.controllerLabels" . | nindent 4 }} -spec: - type: {{ .Values.controller.external.type }} - ports: - - name: external-controller - port: {{ .Values.controller.external.port }} - selector: - {{- include "pinot.controllerMatchLabels" . | nindent 4 }} -{{- with .Values.controller.service.loadBalancerSourceRanges }} - loadBalancerSourceRanges: -{{ toYaml . | indent 4 }} -{{- end }} -{{- end }} diff --git a/kubernetes/helm/pinot/templates/controller/service-headless.yaml b/kubernetes/helm/pinot/templates/controller/service-headless.yaml deleted file mode 100644 index bb64490c3c2c..000000000000 --- a/kubernetes/helm/pinot/templates/controller/service-headless.yaml +++ /dev/null @@ -1,33 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: v1 -kind: Service -metadata: - name: {{ template "pinot.controller.headless" . }} - labels: - {{- include "pinot.controllerLabels" . | nindent 4 }} -spec: - clusterIP: None - ports: - # [pod_name].[service_name].[namespace].svc.cluster.local - - name: {{ .Values.controller.service.name }} - port: {{ .Values.controller.service.port }} - selector: - {{- include "pinot.controllerMatchLabels" . | nindent 4 }} diff --git a/kubernetes/helm/pinot/templates/controller/service.yaml b/kubernetes/helm/pinot/templates/controller/service.yaml deleted file mode 100644 index 8e0cba9bf231..000000000000 --- a/kubernetes/helm/pinot/templates/controller/service.yaml +++ /dev/null @@ -1,35 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: v1 -kind: Service -metadata: - name: {{ include "pinot.controller.fullname" . }} - annotations: -{{ toYaml .Values.controller.service.annotations | indent 4 }} - labels: - {{- include "pinot.controllerLabels" . | nindent 4 }} -spec: - type: {{ .Values.controller.service.type }} - ports: - # [pod_name].[service_name].[namespace].svc.cluster.local - - name: {{ .Values.controller.service.name }} - port: {{ .Values.controller.service.port }} - selector: - {{- include "pinot.controllerMatchLabels" . | nindent 4 }} diff --git a/kubernetes/helm/pinot/templates/controller/statefulset.yaml b/kubernetes/helm/pinot/templates/controller/statefulset.yaml deleted file mode 100644 index baf6229b5de1..000000000000 --- a/kubernetes/helm/pinot/templates/controller/statefulset.yaml +++ /dev/null @@ -1,130 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: {{ include "pinot.controller.fullname" . }} - labels: - {{- include "pinot.controllerLabels" . | nindent 4 }} -spec: - selector: - matchLabels: - {{- include "pinot.controllerMatchLabels" . | nindent 6 }} - serviceName: {{ template "pinot.controller.headless" . }} - replicas: {{ .Values.controller.replicaCount }} - updateStrategy: - type: {{ .Values.controller.updateStrategy.type }} - podManagementPolicy: {{ .Values.controller.podManagementPolicy }} - template: - metadata: - labels: - {{- include "pinot.controllerLabels" . | nindent 8 }} - annotations: -{{ toYaml .Values.controller.podAnnotations | indent 8 }} - spec: - terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} - serviceAccountName: {{ include "pinot.serviceAccountName" . }} - securityContext: - {{- toYaml .Values.controller.podSecurityContext | nindent 8 }} - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - nodeSelector: -{{ toYaml .Values.controller.nodeSelector | indent 8 }} - affinity: -{{ toYaml .Values.controller.affinity | indent 8 }} - tolerations: -{{ toYaml .Values.controller.tolerations | indent 8 }} - containers: - - name: controller - securityContext: - {{- toYaml .Values.controller.securityContext | nindent 10 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - args: [ "StartController", "-configFileName", "/var/pinot/controller/config/pinot-controller.conf" ] - env: - - name: JAVA_OPTS - value: "{{ .Values.controller.jvmOpts }} -Dlog4j2.configurationFile={{ .Values.controller.log4j2ConfFile }} -Dplugins.dir={{ .Values.controller.pluginsDir }}" -{{- if .Values.controller.extraEnv }} -{{ toYaml .Values.controller.extraEnv | indent 10 }} -{{- end }} - envFrom: -{{ toYaml .Values.controller.envFrom | indent 10 }} - ports: - - containerPort: {{ .Values.controller.service.port }} - protocol: {{ .Values.controller.service.protocol }} - name: {{ .Values.controller.service.name }} - {{- if .Values.controller.probes.livenessEnabled }} - livenessProbe: - initialDelaySeconds: {{ .Values.probes.initialDelaySeconds }} - periodSeconds: {{ .Values.probes.periodSeconds }} - httpGet: - path: {{ .Values.controller.probes.endpoint }} - port: {{ .Values.controller.service.port }} - {{- end }} - {{- if .Values.controller.probes.readinessEnabled }} - readinessProbe: - initialDelaySeconds: {{ .Values.probes.initialDelaySeconds }} - periodSeconds: {{ .Values.probes.periodSeconds }} - httpGet: - path: {{ .Values.controller.probes.endpoint }} - port: {{ .Values.controller.service.port }} - {{- end }} - volumeMounts: - - name: config - mountPath: /var/pinot/controller/config - - name: data - mountPath: "{{ .Values.controller.persistence.mountPath }}" - {{- if ne (len .Values.controller.persistence.extraVolumeMounts) 0 }} -{{ toYaml .Values.controller.persistence.extraVolumeMounts | indent 10 }} - {{- end }} - resources: -{{ toYaml .Values.controller.resources | indent 12 }} - restartPolicy: Always - volumes: - - name: config - configMap: - name: {{ include "pinot.controller.config" . }} -{{- if not .Values.controller.persistence.enabled }} - - name: data - emptyDir: {} -{{- end }} - {{- if ne (len .Values.controller.persistence.extraVolumes) 0 }} -{{ toYaml .Values.controller.persistence.extraVolumes | indent 6 }} - {{- end }} -{{- if .Values.controller.persistence.enabled }} - volumeClaimTemplates: - - metadata: - name: data - spec: - accessModes: - - {{ .Values.controller.persistence.accessMode | quote }} - {{- if .Values.controller.persistence.storageClass }} - {{- if (eq "-" .Values.controller.persistence.storageClass) }} - storageClassName: "" - {{- else }} - storageClassName: {{ .Values.controller.persistence.storageClass }} - {{- end }} - {{- end }} - resources: - requests: - storage: {{ .Values.controller.persistence.size | quote}} -{{ end }} diff --git a/kubernetes/helm/pinot/templates/minion-stateless/configmap.yaml b/kubernetes/helm/pinot/templates/minion-stateless/configmap.yaml deleted file mode 100644 index f61c597faded..000000000000 --- a/kubernetes/helm/pinot/templates/minion-stateless/configmap.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -{{- if .Values.minionStateless.enabled }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "pinot.minionStateless.config" . }} -data: - pinot-minion-stateless.conf: |- - pinot.minion.port={{ .Values.minionStateless.service.port }} - {{- if .Values.minionStateless.dataDir }} - dataDir={{ .Values.minionStateless.dataDir }} - {{- end }} -{{ .Values.minionStateless.extra.configs | indent 4 }} -{{- end }} diff --git a/kubernetes/helm/pinot/templates/minion-stateless/deployment.yaml b/kubernetes/helm/pinot/templates/minion-stateless/deployment.yaml deleted file mode 100644 index 1f286f7a80dd..000000000000 --- a/kubernetes/helm/pinot/templates/minion-stateless/deployment.yaml +++ /dev/null @@ -1,121 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -{{- if .Values.minionStateless.enabled }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "pinot.minionStateless.fullname" . }} - labels: - {{- include "pinot.minionStatelessLabels" . | nindent 4 }} -spec: - selector: - matchLabels: - {{- include "pinot.minionStatelessMatchLabels" . | nindent 6 }} - replicas: {{ .Values.minionStateless.replicaCount }} - template: - metadata: - labels: - {{- include "pinot.minionStatelessLabels" . | nindent 8 }} - annotations: -{{ toYaml .Values.minionStateless.podAnnotations | indent 8 }} - spec: - terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} - securityContext: - {{- toYaml .Values.minionStateless.podSecurityContext | nindent 8 }} - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - nodeSelector: -{{ toYaml .Values.minionStateless.nodeSelector | indent 8 }} - affinity: -{{ toYaml .Values.minionStateless.affinity | indent 8 }} - tolerations: -{{ toYaml .Values.minionStateless.tolerations | indent 8 }} - containers: - - name: minion-stateless - securityContext: - {{- toYaml .Values.minionStateless.securityContext | nindent 10 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - args: [ - "StartMinion", - "-clusterName", "{{ .Values.cluster.name }}", - "-zkAddress", {{ include "zookeeper.url" . | quote }}, - "-configFileName", "/var/pinot/minion/config/pinot-minion-stateless.conf" - ] - env: - - name: JAVA_OPTS - value: "{{ .Values.minionStateless.jvmOpts }} -Dlog4j2.configurationFile={{ .Values.minionStateless.log4j2ConfFile }} -Dplugins.dir={{ .Values.minionStateless.pluginsDir }}" -{{- if .Values.minionStateless.extraEnv }} -{{ toYaml .Values.minionStateless.extraEnv | indent 10 }} -{{- end }} - envFrom: -{{ toYaml .Values.minionStateless.envFrom | indent 10 }} - ports: - - containerPort: {{ .Values.minionStateless.service.port }} - protocol: {{ .Values.minionStateless.service.protocol }} - name: {{ .Values.minionStateless.service.name }} - {{- if .Values.minionStateless.probes.livenessEnabled }} - livenessProbe: - initialDelaySeconds: {{ .Values.probes.initialDelaySeconds }} - periodSeconds: {{ .Values.probes.periodSeconds }} - httpGet: - path: {{ .Values.minionStateless.probes.endpoint }} - port: {{ .Values.minionStateless.service.port }} - {{- end }} - {{- if .Values.minionStateless.probes.readinessEnabled }} - readinessProbe: - initialDelaySeconds: {{ .Values.probes.initialDelaySeconds }} - periodSeconds: {{ .Values.probes.periodSeconds }} - httpGet: - path: {{ .Values.minionStateless.probes.endpoint }} - port: {{ .Values.minionStateless.service.port }} - {{- end }} - volumeMounts: - - name: config - mountPath: /var/pinot/minion/config - {{- if .Values.minionStateless.persistence.enabled }} - - name: data - mountPath: "{{ .Values.minionStateless.persistence.mountPath }}" - {{- end }} - {{- if ne (len .Values.minionStateless.persistence.extraVolumeMounts) 0 }} -{{ toYaml .Values.minionStateless.persistence.extraVolumeMounts | indent 10 }} - {{- end }} - resources: -{{ toYaml .Values.minionStateless.resources | indent 12 }} - restartPolicy: Always - volumes: - - name: config - configMap: - name: {{ include "pinot.minionStateless.config" . }} - {{- if not .Values.minionStateless.persistence.enabled }} - - name: data - emptyDir: {} - {{- end }} - {{- if .Values.minionStateless.persistence.enabled }} - - name: data - persistentVolumeClaim: - claimName: {{ .Values.minionStateless.persistence.pvcName }} - {{- end }} - {{- if ne (len .Values.minionStateless.persistence.extraVolumes) 0 }} -{{ toYaml .Values.minionStateless.persistence.extraVolumes | indent 8 }} - {{- end }} -{{- end }} diff --git a/kubernetes/helm/pinot/templates/minion/configmap.yaml b/kubernetes/helm/pinot/templates/minion/configmap.yaml deleted file mode 100644 index 59ed6005027f..000000000000 --- a/kubernetes/helm/pinot/templates/minion/configmap.yaml +++ /dev/null @@ -1,32 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -{{- if .Values.minion.enabled }} -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "pinot.minion.config" . }} -data: - pinot-minion.conf: |- - pinot.minion.port={{ .Values.minion.service.port }} - {{- if .Values.minion.dataDir }} - dataDir={{ .Values.minion.dataDir }} - {{- end }} -{{ .Values.minion.extra.configs | indent 4 }} -{{- end }} diff --git a/kubernetes/helm/pinot/templates/minion/service-headless.yaml b/kubernetes/helm/pinot/templates/minion/service-headless.yaml deleted file mode 100644 index 120042b8b162..000000000000 --- a/kubernetes/helm/pinot/templates/minion/service-headless.yaml +++ /dev/null @@ -1,35 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -{{- if .Values.minion.enabled }} -apiVersion: v1 -kind: Service -metadata: - name: {{ template "pinot.minion.headless" . }} - labels: - {{- include "pinot.minionLabels" . | nindent 4 }} -spec: - clusterIP: None - ports: - # [pod_name].[service_name].[namespace].svc.cluster.local - - name: {{ .Values.minion.service.name }} - port: {{ .Values.minion.service.port }} - selector: - {{- include "pinot.minionMatchLabels" . | nindent 4 }} -{{- end }} diff --git a/kubernetes/helm/pinot/templates/minion/service.yaml b/kubernetes/helm/pinot/templates/minion/service.yaml deleted file mode 100644 index d4f61d982d2d..000000000000 --- a/kubernetes/helm/pinot/templates/minion/service.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -{{- if .Values.minion.enabled }} -apiVersion: v1 -kind: Service -metadata: - name: {{ include "pinot.minion.fullname" . }} - annotations: -{{ toYaml .Values.minion.service.annotations | indent 4 }} - labels: - {{- include "pinot.minionLabels" . | nindent 4 }} -spec: - type: {{ .Values.minion.service.type }} - ports: - # [pod_name].[service_name].[namespace].svc.cluster.local - - name: {{ .Values.minion.service.name }} - port: {{ .Values.minion.service.port }} - selector: - {{- include "pinot.minionMatchLabels" . | nindent 4 }} -{{- end }} diff --git a/kubernetes/helm/pinot/templates/minion/statefulset.yaml b/kubernetes/helm/pinot/templates/minion/statefulset.yaml deleted file mode 100644 index db43b87ded81..000000000000 --- a/kubernetes/helm/pinot/templates/minion/statefulset.yaml +++ /dev/null @@ -1,139 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -{{- if .Values.minion.enabled }} -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: {{ include "pinot.minion.fullname" . }} - labels: - {{- include "pinot.minionLabels" . | nindent 4 }} -spec: - selector: - matchLabels: - {{- include "pinot.minionMatchLabels" . | nindent 6 }} - serviceName: {{ template "pinot.minion.headless" . }} - replicas: {{ .Values.minion.replicaCount }} - updateStrategy: - type: {{ .Values.minion.updateStrategy.type }} - podManagementPolicy: {{ .Values.minion.podManagementPolicy }} - template: - metadata: - labels: - {{- include "pinot.minionLabels" . | nindent 8 }} - annotations: -{{ toYaml .Values.minion.podAnnotations | indent 8 }} - spec: - terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} - serviceAccountName: {{ include "pinot.serviceAccountName" . }} - securityContext: - {{- toYaml .Values.minion.podSecurityContext | nindent 8 }} - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - nodeSelector: -{{ toYaml .Values.minion.nodeSelector | indent 8 }} - affinity: -{{ toYaml .Values.minion.affinity | indent 8 }} - tolerations: -{{ toYaml .Values.minion.tolerations | indent 8 }} - containers: - - name: minion - securityContext: - {{- toYaml .Values.minion.securityContext | nindent 10 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - args: [ - "StartMinion", - "-clusterName", "{{ .Values.cluster.name }}", - "-zkAddress", {{ include "zookeeper.url" . | quote }}, - "-configFileName", "/var/pinot/minion/config/pinot-minion.conf" - ] - env: - - name: JAVA_OPTS - value: "{{ .Values.minion.jvmOpts }} -Dlog4j2.configurationFile={{ .Values.minion.log4j2ConfFile }} -Dplugins.dir={{ .Values.minion.pluginsDir }}" -{{- if .Values.minion.extraEnv }} -{{ toYaml .Values.minion.extraEnv | indent 10 }} -{{- end }} - envFrom: -{{ toYaml .Values.minion.envFrom | indent 10 }} - ports: - - containerPort: {{ .Values.minion.service.port }} - protocol: {{ .Values.minion.service.protocol }} - name: {{ .Values.minion.service.name }} - {{- if .Values.minion.probes.livenessEnabled }} - livenessProbe: - initialDelaySeconds: {{ .Values.probes.initialDelaySeconds }} - periodSeconds: {{ .Values.probes.periodSeconds }} - httpGet: - path: {{ .Values.minion.probes.endpoint }} - port: {{ .Values.minion.service.port }} - {{- end }} - {{- if .Values.minion.probes.readinessEnabled }} - readinessProbe: - initialDelaySeconds: {{ .Values.probes.initialDelaySeconds }} - periodSeconds: {{ .Values.probes.periodSeconds }} - httpGet: - path: {{ .Values.minion.probes.endpoint }} - port: {{ .Values.minion.service.port }} - {{- end }} - volumeMounts: - - name: config - mountPath: /var/pinot/minion/config - {{- if .Values.minion.persistence.enabled }} - - name: data - mountPath: "{{ .Values.minion.persistence.mountPath }}" - {{- end }} - {{- if ne (len .Values.minion.persistence.extraVolumeMounts) 0 }} -{{ toYaml .Values.minion.persistence.extraVolumeMounts | indent 10 }} - {{- end }} - resources: -{{ toYaml .Values.minion.resources | indent 12 }} - restartPolicy: Always - volumes: - - name: config - configMap: - name: {{ include "pinot.minion.config" . }} - {{- if not .Values.minion.persistence.enabled }} - - name: data - emptyDir: {} - {{- end }} - {{- if ne (len .Values.minion.persistence.extraVolumes) 0 }} -{{ toYaml .Values.minion.persistence.extraVolumes | indent 8 }} - {{- end }} - {{- if .Values.minion.persistence.enabled }} - volumeClaimTemplates: - - metadata: - name: data - spec: - accessModes: - - {{ .Values.minion.persistence.accessMode | quote }} - {{- if .Values.minion.persistence.storageClass }} - {{- if (eq "-" .Values.minion.persistence.storageClass) }} - storageClassName: "" - {{- else }} - storageClassName: {{ .Values.minion.persistence.storageClass }} - {{- end }} - {{- end }} - resources: - requests: - storage: {{ .Values.minion.persistence.size }} - {{ end }} -{{- end }} diff --git a/kubernetes/helm/pinot/templates/server/configmap.yaml b/kubernetes/helm/pinot/templates/server/configmap.yaml deleted file mode 100644 index 5259d3b8a7fa..000000000000 --- a/kubernetes/helm/pinot/templates/server/configmap.yaml +++ /dev/null @@ -1,30 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "pinot.server.config" . }} -data: - pinot-server.conf: |- - pinot.server.netty.port={{ .Values.server.service.nettyPort }} - pinot.server.adminapi.port={{ .Values.server.service.adminPort }} - pinot.server.instance.dataDir={{ .Values.server.dataDir }} - pinot.server.instance.segmentTarDir={{ .Values.server.segmentTarDir }} -{{ .Values.server.extra.configs | indent 4 }} diff --git a/kubernetes/helm/pinot/templates/server/service-headless.yaml b/kubernetes/helm/pinot/templates/server/service-headless.yaml deleted file mode 100644 index 5ccfcd56b1b9..000000000000 --- a/kubernetes/helm/pinot/templates/server/service-headless.yaml +++ /dev/null @@ -1,38 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: v1 -kind: Service -metadata: - name: {{ template "pinot.server.headless" . }} - labels: - {{- include "pinot.serverLabels" . | nindent 4 }} -spec: - clusterIP: None - ports: - # [pod_name].[service_name].[namespace].svc.cluster.local - - name: {{ .Values.server.service.nettyPortName }} - port: {{ .Values.server.service.nettyPort }} - protocol: {{ .Values.server.service.protocol }} - - name: {{ .Values.server.service.adminPortName }} - port: {{ .Values.server.service.adminExposePort }} - targetPort: {{ .Values.server.service.adminPort }} - protocol: {{ .Values.server.service.protocol }} - selector: - {{- include "pinot.serverMatchLabels" . | nindent 4 }} diff --git a/kubernetes/helm/pinot/templates/server/service.yaml b/kubernetes/helm/pinot/templates/server/service.yaml deleted file mode 100644 index c93dfd01fac7..000000000000 --- a/kubernetes/helm/pinot/templates/server/service.yaml +++ /dev/null @@ -1,40 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: v1 -kind: Service -metadata: - name: {{ include "pinot.server.fullname" . }} - annotations: -{{ toYaml .Values.server.service.annotations | indent 4 }} - labels: - {{- include "pinot.serverLabels" . | nindent 4 }} -spec: - type: {{ .Values.server.service.type }} - ports: - # [pod_name].[service_name].[namespace].svc.cluster.local - - name: {{ .Values.server.service.nettyPortName }} - port: {{ .Values.server.service.nettyPort }} - protocol: {{ .Values.server.service.protocol }} - - name: {{ .Values.server.service.adminPortName }} - port: {{ .Values.server.service.adminExposePort }} - targetPort: {{ .Values.server.service.adminPort }} - protocol: {{ .Values.server.service.protocol }} - selector: - {{- include "pinot.serverMatchLabels" . | nindent 4 }} diff --git a/kubernetes/helm/pinot/templates/server/statefulset.yaml b/kubernetes/helm/pinot/templates/server/statefulset.yaml deleted file mode 100644 index 1d80e9bdd061..000000000000 --- a/kubernetes/helm/pinot/templates/server/statefulset.yaml +++ /dev/null @@ -1,138 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: {{ include "pinot.server.fullname" . }} - labels: - {{- include "pinot.serverLabels" . | nindent 4 }} -spec: - selector: - matchLabels: - {{- include "pinot.serverMatchLabels" . | nindent 6 }} - serviceName: {{ template "pinot.server.headless" . }} - replicas: {{ .Values.server.replicaCount }} - updateStrategy: - type: {{ .Values.server.updateStrategy.type }} - podManagementPolicy: {{ .Values.server.podManagementPolicy }} - template: - metadata: - labels: - {{- include "pinot.serverLabels" . | nindent 8 }} - annotations: -{{ toYaml .Values.server.podAnnotations | indent 8 }} - spec: - terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} - serviceAccountName: {{ include "pinot.serviceAccountName" . }} - securityContext: - {{- toYaml .Values.server.podSecurityContext | nindent 8 }} - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - nodeSelector: -{{ toYaml .Values.server.nodeSelector | indent 8 }} - affinity: -{{ toYaml .Values.server.affinity | indent 8 }} - tolerations: -{{ toYaml .Values.server.tolerations | indent 8 }} - containers: - - name: server - securityContext: - {{- toYaml .Values.server.securityContext | nindent 10 }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - args: [ - "StartServer", - "-clusterName", "{{ .Values.cluster.name }}", - "-zkAddress", {{ include "zookeeper.url" . | quote }}, - "-configFileName", "/var/pinot/server/config/pinot-server.conf" - ] - env: - - name: JAVA_OPTS - value: "{{ .Values.server.jvmOpts }} -Dlog4j2.configurationFile={{ .Values.server.log4j2ConfFile }} -Dplugins.dir={{ .Values.server.pluginsDir }}" -{{- if .Values.server.extraEnv }} -{{ toYaml .Values.server.extraEnv | indent 10 }} -{{- end}} - envFrom: -{{ toYaml .Values.server.envFrom | indent 10 }} - ports: - - containerPort: {{ .Values.server.service.nettyPort }} - protocol: {{ .Values.server.service.protocol }} - name: {{ .Values.server.service.nettyPortName }} - - containerPort: {{ .Values.server.service.adminPort }} - protocol: {{ .Values.server.service.protocol }} - name: {{ .Values.server.service.adminPortName }} - {{- if .Values.server.probes.livenessEnabled }} - livenessProbe: - initialDelaySeconds: {{ .Values.probes.initialDelaySeconds }} - periodSeconds: {{ .Values.probes.periodSeconds }} - httpGet: - path: {{ .Values.server.probes.endpoint }} - port: {{ .Values.server.service.adminPort }} - {{- end }} - {{- if .Values.server.probes.readinessEnabled }} - readinessProbe: - initialDelaySeconds: {{ .Values.probes.initialDelaySeconds }} - periodSeconds: {{ .Values.probes.periodSeconds }} - httpGet: - path: {{ .Values.server.probes.endpoint }} - port: {{ .Values.server.service.adminPort }} - {{- end }} - volumeMounts: - - name: config - mountPath: /var/pinot/server/config - - name: data - mountPath: "{{ .Values.server.persistence.mountPath }}" - {{- if ne (len .Values.server.persistence.extraVolumeMounts) 0 }} -{{ toYaml .Values.server.persistence.extraVolumeMounts | indent 10 }} - {{- end }} - resources: -{{ toYaml .Values.server.resources | indent 12 }} - restartPolicy: Always - volumes: - - name: config - configMap: - name: {{ include "pinot.server.config" . }} - {{- if not .Values.server.persistence.enabled }} - - name: data - emptyDir: {} - {{- end }} - {{- if ne (len .Values.server.persistence.extraVolumes) 0 }} -{{ toYaml .Values.server.persistence.extraVolumes | indent 8 }} - {{- end }} - {{- if .Values.server.persistence.enabled }} - volumeClaimTemplates: - - metadata: - name: data - spec: - accessModes: - - {{ .Values.server.persistence.accessMode | quote }} - {{- if .Values.server.persistence.storageClass }} - {{- if (eq "-" .Values.server.persistence.storageClass) }} - storageClassName: "" - {{- else }} - storageClassName: {{ .Values.server.persistence.storageClass }} - {{- end }} - {{- end }} - resources: - requests: - storage: {{ .Values.server.persistence.size }} - {{ end }} diff --git a/kubernetes/helm/pinot/templates/serviceaccount.yaml b/kubernetes/helm/pinot/templates/serviceaccount.yaml deleted file mode 100644 index b5373baf1458..000000000000 --- a/kubernetes/helm/pinot/templates/serviceaccount.yaml +++ /dev/null @@ -1,12 +0,0 @@ -{{- if .Values.serviceAccount.create -}} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ include "pinot.serviceAccountName" . }} - labels: - {{- include "pinot.labels" . | nindent 4 }} - {{- with .Values.serviceAccount.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -{{- end }} diff --git a/kubernetes/helm/pinot/values.yaml b/kubernetes/helm/pinot/values.yaml deleted file mode 100644 index 54aaa638d14a..000000000000 --- a/kubernetes/helm/pinot/values.yaml +++ /dev/null @@ -1,578 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -# Default values for Pinot. - -image: - repository: apachepinot/pinot - tag: latest # release-0.11.0 - pullPolicy: Always # Use IfNotPresent when you pinged a version of image tag - -cluster: - name: pinot-quickstart - -imagePullSecrets: [] - -terminationGracePeriodSeconds: 30 - -securityContext: {} - # capabilities: - # drop: - # - ALL - # readOnlyRootFilesystem: true - # runAsNonRoot: true - # runAsUser: 1000 - -probes: - initialDelaySeconds: 60 - periodSeconds: 10 - -serviceAccount: - # Specifies whether a service account should be created - create: true - # Annotations to add to the service account - annotations: {} - # The name of the service account to use. - # If not set and create is true, a name is generated using the fullname template - name: "" - -additionalMatchLabels: {} - - -pinotAuth: - enabled: false - controllerFactoryClass: org.apache.pinot.controller.api.access.BasicAuthAccessControlFactory - brokerFactoryClass: org.apache.pinot.broker.broker.BasicAuthAccessControlFactory - configs: - # - access.control.principals=admin,user - # - access.control.principals.admin.password=verysecret - # - access.control.principals.user.password=secret - # - access.control.principals.user.tables=baseballStats,otherstuff - # - access.control.principals.user.permissions=READ - -# ------------------------------------------------------------------------------ -# Pinot Controller: -# ------------------------------------------------------------------------------ -controller: - name: controller - replicaCount: 1 - podManagementPolicy: Parallel - podSecurityContext: {} - # fsGroup: 2000 - securityContext: {} - - probes: - endpoint: "/health" - livenessEnabled: false - readinessEnabled: false - - persistence: - enabled: true - accessMode: ReadWriteOnce - size: 1G - mountPath: /var/pinot/controller/data - storageClass: "" - extraVolumes: [] - extraVolumeMounts: [] - - data: - dir: /var/pinot/controller/data - - vip: - enabled: false - host: pinot-controller - port: 9000 - - jvmOpts: "-XX:ActiveProcessorCount=2 -Xms256M -Xmx1G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Xlog:gc*:file=/opt/pinot/gc-pinot-controller.log" - - log4j2ConfFile: /opt/pinot/conf/log4j2.xml - pluginsDir: /opt/pinot/plugins - - service: - annotations: {} - clusterIP: "None" - externalIPs: [] - loadBalancerIP: "" - loadBalancerSourceRanges: [] - type: ClusterIP - port: 9000 - nodePort: "" - protocol: TCP - name: controller - - external: - enabled: true - type: LoadBalancer - port: 9000 - annotations: {} - - ingress: - v1beta1: - enabled: false - annotations: { } - tls: { } - path: / - hosts: [ ] - v1: - enabled: false - ingressClassName: "" - annotations: {} - tls: [] - path: / - hosts: [] - - resources: - requests: - memory: "1.25Gi" - - nodeSelector: {} - - tolerations: [] - - affinity: {} - - podAnnotations: {} - - updateStrategy: - type: RollingUpdate - - # Use envFrom to define all of the ConfigMap or Secret data as container environment variables. - # ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#configure-all-key-value-pairs-in-a-configmap-as-container-environment-variables - # ref: https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/#configure-all-key-value-pairs-in-a-secret-as-container-environment-variables - envFrom: [] - # - configMapRef: - # name: special-config - # - secretRef: - # name: test-secret - - # Use extraEnv to add individual key value pairs as container environment variables. - # ref: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/ - extraEnv: - - name: LOG4J_CONSOLE_LEVEL - value: info - # - name: PINOT_CUSTOM_ENV - # value: custom-value - - # Extra configs will be appended to pinot-controller.conf file - extra: - configs: |- - pinot.set.instance.id.to.hostname=true - controller.task.scheduler.enabled=true - -# ------------------------------------------------------------------------------ -# Pinot Broker: -# ------------------------------------------------------------------------------ -broker: - name: broker - replicaCount: 1 - podManagementPolicy: Parallel - podSecurityContext: {} - # fsGroup: 2000 - securityContext: {} - - jvmOpts: "-XX:ActiveProcessorCount=2 -Xms256M -Xmx1G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Xlog:gc*:file=/opt/pinot/gc-pinot-broker.log" - - log4j2ConfFile: /opt/pinot/conf/log4j2.xml - pluginsDir: /opt/pinot/plugins - - routingTable: - builderClass: random - - probes: - endpoint: "/health" - livenessEnabled: true - readinessEnabled: true - - persistence: - extraVolumes: [] - extraVolumeMounts: [] - - service: - annotations: {} - clusterIP: "None" - externalIPs: [] - loadBalancerIP: "" - loadBalancerSourceRanges: [] - type: ClusterIP - protocol: TCP - port: 8099 - name: broker - nodePort: "" - - external: - enabled: true - type: LoadBalancer - port: 8099 - # For example, in private GKE cluster, you might add cloud.google.com/load-balancer-type: Internal - annotations: {} - - ingress: - v1beta1: - enabled: false - annotations: {} - tls: {} - path: / - hosts: [] - v1: - enabled: false - ingressClassName: "" - annotations: {} - tls: [] - path: / - hosts: [] - - resources: - requests: - memory: "1.25Gi" - - nodeSelector: {} - - affinity: {} - - tolerations: [] - - podAnnotations: {} - - updateStrategy: - type: RollingUpdate - - # Use envFrom to define all of the ConfigMap or Secret data as container environment variables. - # ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#configure-all-key-value-pairs-in-a-configmap-as-container-environment-variables - # ref: https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/#configure-all-key-value-pairs-in-a-secret-as-container-environment-variables - envFrom: [] - # - configMapRef: - # name: special-config - # - secretRef: - # name: test-secret - - # Use extraEnv to add individual key value pairs as container environment variables. - # ref: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/ - extraEnv: - - name: LOG4J_CONSOLE_LEVEL - value: info - # - name: PINOT_CUSTOM_ENV - # value: custom-value - - # Extra configs will be appended to pinot-broker.conf file - extra: - configs: |- - pinot.set.instance.id.to.hostname=true - -# ------------------------------------------------------------------------------ -# Pinot Server: -# ------------------------------------------------------------------------------ -server: - name: server - replicaCount: 1 - podManagementPolicy: Parallel - podSecurityContext: {} - # fsGroup: 2000 - securityContext: {} - - probes: - endpoint: "/health" - livenessEnabled: false - readinessEnabled: false - - dataDir: /var/pinot/server/data/index - segmentTarDir: /var/pinot/server/data/segment - - persistence: - enabled: true - accessMode: ReadWriteOnce - size: 4G - mountPath: /var/pinot/server/data - storageClass: "" - #storageClass: "ssd" - extraVolumes: [] - extraVolumeMounts: [] - - jvmOpts: "-Xms512M -Xmx1G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Xlog:gc*:file=/opt/pinot/gc-pinot-server.log" - - log4j2ConfFile: /opt/pinot/conf/log4j2.xml - pluginsDir: /opt/pinot/plugins - - service: - annotations: {} - clusterIP: "" - externalIPs: [] - loadBalancerIP: "" - loadBalancerSourceRanges: [] - type: ClusterIP - nettyPort: 8098 - nettyPortName: netty - adminPort: 8097 - adminExposePort: 80 - adminPortName: admin - nodePort: "" - protocol: TCP - - resources: - requests: - memory: "1.25Gi" - - nodeSelector: {} - - affinity: {} - - tolerations: [] - - podAnnotations: {} - - updateStrategy: - type: RollingUpdate - - # Use envFrom to define all of the ConfigMap or Secret data as container environment variables. - # ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#configure-all-key-value-pairs-in-a-configmap-as-container-environment-variables - # ref: https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/#configure-all-key-value-pairs-in-a-secret-as-container-environment-variables - envFrom: [] - # - configMapRef: - # name: special-config - # - secretRef: - # name: test-secret - - # Use extraEnv to add individual key value pairs as container environment variables. - # ref: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/ - extraEnv: - - name: LOG4J_CONSOLE_LEVEL - value: info - # - name: PINOT_CUSTOM_ENV - # value: custom-value - - # Extra configs will be appended to pinot-server.conf file - extra: - configs: |- - pinot.set.instance.id.to.hostname=true - pinot.server.instance.realtime.alloc.offheap=true - -# ------------------------------------------------------------------------------ -# Pinot Minion: -# ------------------------------------------------------------------------------ -minion: - enabled: false - name: minion - replicaCount: 0 - podManagementPolicy: Parallel - podSecurityContext: {} - # fsGroup: 2000 - securityContext: {} - - probes: - endpoint: "/health" - livenessEnabled: true - readinessEnabled: true - - dataDir: /var/pinot/minion/data - jvmOpts: "-XX:ActiveProcessorCount=2 -Xms256M -Xmx1G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Xlog:gc*:file=/opt/pinot/gc-pinot-minion.log" - - log4j2ConfFile: /opt/pinot/conf/log4j2.xml - pluginsDir: /opt/pinot/plugins - - persistence: - enabled: true - accessMode: ReadWriteOnce - size: 4G - mountPath: /var/pinot/minion/data - storageClass: "" - #storageClass: "ssd" - extraVolumes: [] - extraVolumeMounts: [] - - service: - annotations: {} - clusterIP: "" - externalIPs: [] - loadBalancerIP: "" - loadBalancerSourceRanges: [] - type: ClusterIP - port: 9514 - nodePort: "" - protocol: TCP - name: minion - - resources: - requests: - memory: "1.25Gi" - - nodeSelector: {} - - affinity: {} - - tolerations: [] - - podAnnotations: {} - - updateStrategy: - type: RollingUpdate - - # Use envFrom to define all of the ConfigMap or Secret data as container environment variables. - # ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#configure-all-key-value-pairs-in-a-configmap-as-container-environment-variables - # ref: https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/#configure-all-key-value-pairs-in-a-secret-as-container-environment-variables - envFrom: [] - # - configMapRef: - # name: special-config - # - secretRef: - # name: test-secret - - # Use extraEnv to add individual key value pairs as container environment variables. - # ref: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/ - extraEnv: - - name: LOG4J_CONSOLE_LEVEL - value: info - # - name: PINOT_CUSTOM_ENV - # value: custom-value - - # Extra configs will be appended to pinot-minion.conf file - extra: - configs: |- - pinot.set.instance.id.to.hostname=true - - -# ------------------------------------------------------------------------------ -# Pinot Minion Stateless: -# ------------------------------------------------------------------------------ -minionStateless: - enabled: true - name: minion-stateless - replicaCount: 1 - podSecurityContext: {} - # fsGroup: 2000 - securityContext: {} - - probes: - endpoint: "/health" - livenessEnabled: true - readinessEnabled: true - - dataDir: /var/pinot/minion/data - jvmOpts: "-XX:ActiveProcessorCount=2 -Xms256M -Xmx1G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Xlog:gc*:file=/opt/pinot/gc-pinot-minion.log" - - log4j2ConfFile: /opt/pinot/conf/log4j2.xml - pluginsDir: /opt/pinot/plugins - - persistence: - enabled: false - pvcName: minion-data-vol - accessMode: ReadWriteOnce - size: 4G - mountPath: /var/pinot/minion/data - storageClass: "" - #storageClass: "ssd" - extraVolumes: [] - extraVolumeMounts: [] - - service: - port: 9514 - protocol: TCP - name: minion - - resources: - requests: - memory: "1.25Gi" - - nodeSelector: {} - - affinity: {} - - tolerations: [] - - podAnnotations: {} - - # Use envFrom to define all of the ConfigMap or Secret data as container environment variables. - # ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/#configure-all-key-value-pairs-in-a-configmap-as-container-environment-variables - # ref: https://kubernetes.io/docs/tasks/inject-data-application/distribute-credentials-secure/#configure-all-key-value-pairs-in-a-secret-as-container-environment-variables - envFrom: [] - # - configMapRef: - # name: special-config - # - secretRef: - # name: test-secret - - # Use extraEnv to add individual key value pairs as container environment variables. - # ref: https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/ - extraEnv: - - name: LOG4J_CONSOLE_LEVEL - value: info - # - name: PINOT_CUSTOM_ENV - # value: custom-value - - # Extra configs will be appended to pinot-minion.conf file - extra: - configs: |- - pinot.set.instance.id.to.hostname=true - -# ------------------------------------------------------------------------------ -# Zookeeper: -# NOTE: IN PRODUCTION USE CASES, IT's BEST TO USE ZOOKEEPER K8S OPERATOR -# ref: https://github.com/pravega/zookeeper-operator#install-the-operator -# ------------------------------------------------------------------------------ - -zookeeper: - ## If true, install the Zookeeper chart alongside Pinot - ## ref: https://github.com/bitnami/charts/tree/master/bitnami/zookeeper - enabled: true - - ## If the Zookeeper Chart is disabled a URL override is required to connect - urlOverride: "my-zookeeper:2181/my-pinot" - - ## Zookeeper port - port: 2181 - - ## Configure Zookeeper resource requests and limits - ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ - resources: - requests: - memory: "1.25Gi" - - ## Replicas - replicaCount: 1 - - ## Ongoing data directory cleanup configuration - autopurge: - - ## The time interval (in hours) for which the purge task has to be triggered - ## Set to a positive integer to enable the auto purging - purgeInterval: 1 - - ## The most recent snapshots amount (and corresponding transaction logs) to retain - snapRetainCount: 5 - - ## Size (in MB) for the Java Heap options (Xmx and Xms) - ## This env var is ignored if Xmx an Xms are configured via `zookeeper.jvmFlags` - heapSize: "1024" - - persistence: - enabled: true - ## The amount of PV storage allocated to each Zookeeper pod in the statefulset - # size: "2Gi" - - ## Specify a Zookeeper imagePullPolicy - ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images - image: - PullPolicy: "IfNotPresent" - - ## Pod scheduling preferences (by default keep pods within a release on separate nodes). - ## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity - ## By default we don't set affinity: - affinity: {} # Criteria by which pod label-values influence scheduling for zookeeper pods. - # podAntiAffinity: - # requiredDuringSchedulingIgnoredDuringExecution: - # - topologyKey: "kubernetes.io/hostname" - # labelSelector: - # matchLabels: - # release: zookeeper diff --git a/kubernetes/helm/thirdeye/Chart.yaml b/kubernetes/helm/thirdeye/Chart.yaml deleted file mode 100644 index 0cb2d3873fb1..000000000000 --- a/kubernetes/helm/thirdeye/Chart.yaml +++ /dev/null @@ -1,23 +0,0 @@ -apiVersion: v2 -appVersion: 0.5.0-SNAPSHOT # Should be maintained automatically by a pipeline -name: thirdeye -description: One Stop Shop For Anomaly Detection. Built on Apache Pinot -version: 0.1.0 -keywords: -- olap -- analytics -- pinot -- anomaly detection -- rootcause analysis -home: https://github.com/apache/pinot -sources: -- https://github.com/apache/pinot -maintainers: -- name: Alexander Pucher - email: alex@cortexdata.io -- name: Suvodeep Pyne - email: pyne.suvodeep@gmail.com -dependencies: -- name: mysql - version: 1.6.6 - repository: https://charts.helm.sh/stable diff --git a/kubernetes/helm/thirdeye/README.md b/kubernetes/helm/thirdeye/README.md deleted file mode 100644 index 700276e74463..000000000000 --- a/kubernetes/helm/thirdeye/README.md +++ /dev/null @@ -1,82 +0,0 @@ - - -# ThirdEye (Alpha) - - -This helm chart installs ThirdEye with a Pinot Quickstart Cluster. -It sets up the following components. -- MySQL 5.7 server -- ThirdEye Frontend server -- ThirdEye Backend server - - -#### Prerequisites - -- kubectl () -- Helm () -- Configure kubectl to connect to the Kubernetes cluster. -- An already Setup Pinot quickstart cluster. - -## Installing ThirdEye - -This installs thirdeye in the default namespace. -```bash -./install.sh -``` - -All arguments passed to this script are forwarded to helm. So to install in namespace `te`, -you can simply pass on the helm arguments directly. - -```bash -./install.sh --namespace te -``` - -## Uninstalling ThirdEye - -Simply run one of the commands below depending on whether you are using a namespace or not. - -```bash -helm uninstall thirdeye -helm uninstall thirdeye --namespace te -``` - - -## Configuration - -> Warning: The initdb.sql used for setting up the db may be out of date. Please note that this chart is currently a work in progress. - -Please see `values.yaml` for configurable parameters. Specify parameters using `--set key=value[,key=value]` argument to `helm install` - -Alternatively a YAML file that specifies the values for the parameters can be provided like this: - -```bash -./install.sh --name thirdeye -f values.yaml . -``` - -## Holiday Events - -ThirdEye allows you to display events from external Google Calendars. To enable this feature, -simply provide a JSON key. Check https://docs.simplecalendar.io/google-api-key/ - -```bash -./install.sh --set-file backend.holidayLoaderKey="/path/to/holiday-loader-key.json" -``` diff --git a/kubernetes/helm/thirdeye/config/data-sources/data-sources-config.yml b/kubernetes/helm/thirdeye/config/data-sources/data-sources-config.yml deleted file mode 100644 index c68f0ec75778..000000000000 --- a/kubernetes/helm/thirdeye/config/data-sources/data-sources-config.yml +++ /dev/null @@ -1,12 +0,0 @@ -dataSourceConfigs: - # Default option is to run thirdeye with a pinot-quickstart cluster - - className: org.apache.pinot.thirdeye.datasource.pinot.PinotThirdEyeDataSource - properties: - zookeeperUrl: 'pinot-zookeeper:2181' - clusterName: 'pinot-quickstart' - controllerConnectionScheme: 'http' - controllerHost: 'pinot-controller' - controllerPort: 9000 - cacheLoaderClassName: org.apache.pinot.thirdeye.datasource.pinot.PinotControllerResponseCacheLoader - metadataSourceConfigs: - - className: org.apache.pinot.thirdeye.auto.onboard.AutoOnboardPinotMetadataSource \ No newline at end of file diff --git a/kubernetes/helm/thirdeye/config/initdb.sql b/kubernetes/helm/thirdeye/config/initdb.sql deleted file mode 100644 index fd1c217e5dec..000000000000 --- a/kubernetes/helm/thirdeye/config/initdb.sql +++ /dev/null @@ -1,460 +0,0 @@ -create table if not exists generic_json_entity ( - id bigint(20) primary key auto_increment, - json_val text, - beanClass varchar(200), - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -create index generic_json_entity_beanclass_idx on generic_json_entity(beanClass); - -create table if not exists anomaly_function_index ( - function_name varchar(200) not null, - active boolean, - metric_id bigint(20) not null, - collection varchar(200), - metric varchar(200), - base_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10), - CONSTRAINT uc_functionName unique(function_name) -) ENGINE=InnoDB; -create index anomaly_function_name_idx on anomaly_function_index(function_name); -create index anomaly_function_base_id_idx ON anomaly_function_index(base_id); - -create table if not exists job_index ( - name varchar(200) not null, - status varchar(100) not null, - type varchar(100) not null, - config_id bigint(20), - schedule_start_time bigint(20) not null, - schedule_end_time bigint(20) not null, - base_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -create index job_status_idx on job_index(status); -create index job_type_idx on job_index(type); -create index job_config_id_idx on job_index(config_id); -create index job_base_id_idx ON job_index(base_id); - -create table if not exists task_index ( - name varchar(200) not null, - status varchar(100) not null, - type varchar(100) not null, - start_time bigint(20) not null, - end_time bigint(20) not null, - job_id bigint(20), - worker_id bigint(20), - base_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -create index task_status_idx on task_index(status); -create index task_type_idx on task_index(type); -create index task_job_idx on task_index(job_id); -create index task_base_id_idx ON task_index(base_id); -create index task_name_idx ON task_index(name); -create index task_start_time_idx on task_index(start_time); -create index task_create_time_idx on task_index(create_time); -create index task_status_start_time_idx on task_index(status, start_time); - -create table if not exists anomaly_feedback_index ( - type varchar(100) not null, - base_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -create index anomaly_feedback_base_id_idx ON anomaly_feedback_index(base_id); - -create table if not exists raw_anomaly_result_index ( - function_id bigint(20), - job_id bigint(20), - anomaly_feedback_id bigint(20), - start_time bigint(20) not null, - end_time bigint(20) not null, - data_missing boolean default false not null, - merged boolean default false, - dimensions varchar(1023), - base_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -create index raw_anomaly_result_function_idx on raw_anomaly_result_index(function_id); -create index raw_anomaly_result_feedback_idx on raw_anomaly_result_index(anomaly_feedback_id); -create index raw_anomaly_result_job_idx on raw_anomaly_result_index(job_id); -create index raw_anomaly_result_merged_idx on raw_anomaly_result_index(merged); -create index raw_anomaly_result_data_missing_idx on raw_anomaly_result_index(data_missing); -create index raw_anomaly_result_start_time_idx on raw_anomaly_result_index(start_time); -create index raw_anomaly_result_base_id_idx on raw_anomaly_result_index(base_id); - -create table if not exists merged_anomaly_result_index ( - function_id bigint(20), - detection_config_id bigint(20), - anomaly_feedback_id bigint(20), - metric_id bigint(20), - start_time bigint(20) not null, - end_time bigint(20) not null, - collection varchar(200), - metric varchar(200), - dimensions varchar(1023), - notified boolean default false, - base_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - child boolean, - version int(10) -) ENGINE=InnoDB; -create index merged_anomaly_result_function_idx on merged_anomaly_result_index(function_id); -create index merged_anomaly_result_feedback_idx on merged_anomaly_result_index(anomaly_feedback_id); -create index merged_anomaly_result_metric_idx on merged_anomaly_result_index(metric_id); -create index merged_anomaly_result_start_time_idx on merged_anomaly_result_index(start_time); -create index merged_anomaly_result_base_id_idx on merged_anomaly_result_index(base_id); -create index merged_anomaly_result_detection_config_id_idx on merged_anomaly_result_index(detection_config_id); - -create table if not exists dataset_config_index ( - dataset varchar(200) not null, - display_name varchar(200), - active boolean, - requires_completeness_check boolean, - last_refresh_time bigint(20) default 0, - base_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10), - CONSTRAINT uc_dataset unique(dataset) -) ENGINE=InnoDB; -create index dataset_config_dataset_idx on dataset_config_index(dataset); -create index dataset_config_active_idx on dataset_config_index(active); -create index dataset_config_requires_completeness_check_idx on dataset_config_index(requires_completeness_check); -create index dataset_config_base_id_idx ON dataset_config_index(base_id); -create index dataset_config_display_name_idx on dataset_config_index(display_name); -create index dataset_config_last_refresh_time_idx on dataset_config_index(last_refresh_time); - -create table if not exists metric_config_index ( - name varchar(200) not null, - dataset varchar(200) not null, - alias varchar(400) not null, - active boolean, - base_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -ALTER TABLE `metric_config_index` ADD UNIQUE `unique_metric_index`(`name`, `dataset`); -create index metric_config_name_idx on metric_config_index(name); -create index metric_config_dataset_idx on metric_config_index(dataset); -create index metric_config_alias_idx on metric_config_index(alias); -create index metric_config_active_idx on metric_config_index(active); -create index metric_config_base_id_idx ON metric_config_index(base_id); - -create table if not exists override_config_index ( - start_time bigint(20) NOT NULL, - end_time bigint(20) NOT NULL, - target_entity varchar(100) NOT NULL, - active boolean default false, - base_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -create index override_config_target_entity_idx on override_config_index(target_entity); -create index override_config_target_start_time_idx on override_config_index(start_time); -create index override_config_base_id_idx ON override_config_index(base_id); - -create table if not exists alert_config_index ( - active boolean, - name varchar(500) not null, - application VARCHAR(500) not null, - base_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10), - CONSTRAINT uc_alert_name UNIQUE (name) -) ENGINE=InnoDB; -create index alert_config_application_idx on alert_config_index(application); -create index alert_config_name_idx on alert_config_index(name); -create index alert_config_base_id_idx ON alert_config_index(base_id); - -create table if not exists data_completeness_config_index ( - dataset varchar(200) not null, - date_to_check_in_ms bigint(20) not null, - date_to_check_in_sdf varchar(20), - data_complete boolean, - percent_complete double, - base_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -ALTER TABLE `data_completeness_config_index` ADD UNIQUE `data_completeness_config_unique_index`(`dataset`, `date_to_check_in_ms`); -create index data_completeness_config_dataset_idx on data_completeness_config_index(dataset); -create index data_completeness_config_date_idx on data_completeness_config_index(date_to_check_in_ms); -create index data_completeness_config_sdf_idx on data_completeness_config_index(date_to_check_in_sdf); -create index data_completeness_config_complete_idx on data_completeness_config_index(data_complete); -create index data_completeness_config_percent_idx on data_completeness_config_index(percent_complete); -create index data_completeness_config_base_id_idx ON data_completeness_config_index(base_id); - -create table if not exists event_index ( - name VARCHAR (100), - event_type VARCHAR (100), - start_time bigint(20) not null, - end_time bigint(20) not null, - metric_name VARCHAR(200), - service_name VARCHAR (200), - base_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -create index event_event_type_idx on event_index(event_type); -create index event_start_time_idx on event_index(start_time); -create index event_end_time_idx on event_index(end_time); -create index event_base_id_idx ON event_index(base_id); - -create table if not exists detection_status_index ( - function_id bigint(20), - dataset varchar(200) not null, - date_to_check_in_ms bigint(20) not null, - date_to_check_in_sdf varchar(20), - detection_run boolean, - base_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -ALTER TABLE `detection_status_index` ADD UNIQUE `detection_status_unique_index`(`function_id`, `date_to_check_in_sdf`); -create index detection_status_dataset_idx on detection_status_index(dataset); -create index detection_status_date_idx on detection_status_index(date_to_check_in_ms); -create index detection_status_sdf_idx on detection_status_index(date_to_check_in_sdf); -create index detection_status_run_idx on detection_status_index(detection_run); -create index detection_status_function_idx on detection_status_index(function_id); -create index detection_status_base_id_idx ON detection_status_index(base_id); - -create table if not exists autotune_config_index ( - function_id bigint(20), - start_time bigint(20) not null, - end_time bigint(20) not null, - performance_evaluation_method varchar(200), - autotune_method varchar(200), - base_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -create index autotune_config_function_idx on autotune_config_index(function_id); -create index autotune_config_autoTuneMethod_idx on autotune_config_index(autotune_method); -create index autotune_config_performanceEval_idx on autotune_config_index(performance_evaluation_method); -create index autotune_config_start_time_idx on autotune_config_index(start_time); -create index autotune_config_base_id_idx ON autotune_config_index(base_id); - -create table if not exists classification_config_index ( - name varchar(200) not null, - active boolean, - base_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -ALTER TABLE `classification_config_index` ADD UNIQUE `classification_config_unique_index`(`name`); -create index classification_config_name_index on classification_config_index(name); -create index classification_config_base_id_idx ON classification_config_index(base_id); - -create table if not exists entity_to_entity_mapping_index ( - from_urn varchar(400) not null, - to_urn varchar(400) not null, - mapping_type varchar(500) not null, - base_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -ALTER TABLE `entity_to_entity_mapping_index` ADD UNIQUE `entity_mapping_unique_index`(`from_urn`, `to_urn`); -create index entity_mapping_from_urn_idx on entity_to_entity_mapping_index(from_urn); -create index entity_mapping_to_urn_idx on entity_to_entity_mapping_index(to_urn); -create index entity_mapping_type_idx on entity_to_entity_mapping_index(mapping_type); -create index entity_to_entity_mapping_base_id_idx ON entity_to_entity_mapping_index(base_id); - -create table if not exists grouped_anomaly_results_index ( - alert_config_id bigint(20) not null, - dimensions varchar(1023), - end_time bigint(20) not null, - base_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -create index grouped_anomaly_results_alert_config_id on grouped_anomaly_results_index(alert_config_id); -create index grouped_anomaly_results_base_id_idx ON grouped_anomaly_results_index(base_id); - -create table if not exists onboard_dataset_metric_index ( - dataset_name varchar(200) not null, - metric_name varchar(500), - data_source varchar(500) not null, - onboarded boolean, - base_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -create index onboard_dataset_idx on onboard_dataset_metric_index(dataset_name); -create index onboard_metric_idx on onboard_dataset_metric_index(metric_name); -create index onboard_datasource_idx on onboard_dataset_metric_index(data_source); -create index onboard_onboarded_idx on onboard_dataset_metric_index(onboarded); -create index onboard_dataset_metric_base_id_idx ON onboard_dataset_metric_index(base_id); - -create table if not exists config_index ( - namespace varchar(64) not null, - name varchar(128) not null, - base_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -ALTER TABLE `config_index` ADD UNIQUE `config_unique_index`(`namespace`, `name`); -create index config_namespace_idx on config_index(namespace); -create index config_name_idx on config_index(name); -create index config_base_id_idx ON config_index(base_id); - -create table application_index ( - base_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - application VARCHAR (200) not null, - recipients VARCHAR(1000) NOT NULL -); -create index application_application_idx on application_index(application); - -create table if not exists alert_snapshot_index ( - base_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -create index alert_snapshot_base_id_idx ON alert_snapshot_index(base_id); - -create table if not exists rootcause_session_index ( - base_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - name varchar(256), - owner varchar(32), - previousId bigint(20), - anomalyId bigint(20), - anomaly_range_start bigint(8), - anomaly_range_end bigint(8), - created bigint(8), - updated bigint(8), - version int(10) -) ENGINE=InnoDB; -create index rootcause_session_name_idx on rootcause_session_index(name); -create index rootcause_session_owner_idx on rootcause_session_index(owner); -create index rootcause_session_previousId_idx on rootcause_session_index(previousId); -create index rootcause_session_anomalyId_idx on rootcause_session_index(anomalyId); -create index rootcause_session_anomaly_range_start_idx on rootcause_session_index(anomaly_range_start); -create index rootcause_session_anomaly_range_end_idx on rootcause_session_index(anomaly_range_end); -create index rootcause_session_created_idx on rootcause_session_index(created); -create index rootcause_session_updated_idx on rootcause_session_index(updated); -create index rootcause_session_base_id_idx ON rootcause_session_index(base_id); - -create table if not exists session_index ( - base_id bigint(20) not null, - session_key CHAR(64) not null, - principal_type VARCHAR(32), - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -ALTER TABLE `session_index` ADD UNIQUE `session_unique_index`(session_key); -create index session_base_id_idx ON session_index(base_id); -create index session_key_idx ON session_index(session_key); -create index session_principal_type_idx ON session_index(principal_type); - -create table if not exists detection_config_index ( - base_id bigint(20) not null, - `name` VARCHAR(256) not null, - active BOOLEAN, - created_by VARCHAR(256), - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -ALTER TABLE `detection_config_index` ADD UNIQUE `detection_config_unique_index`(`name`); -create index detection_config_base_id_idx ON detection_config_index(base_id); -create index detection_config_name_idx ON detection_config_index(`name`); -create index detection_config_active_idx ON detection_config_index(active); -create index detection_config_created_by_index ON detection_config_index(created_by); - -create table if not exists detection_alert_config_index ( - base_id bigint(20) not null, - application VARCHAR(128), - `name` VARCHAR(256) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -ALTER TABLE `detection_alert_config_index` ADD UNIQUE `detection_alert_config_unique_index`(`name`); -create index detection_alert_config_base_id_idx ON detection_alert_config_index(base_id); -create index detection_alert_config_name_idx ON detection_alert_config_index(`name`); -create index detection_alert_config_application_idx ON detection_alert_config_index(`application`); - -create table if not exists evaluation_index ( - base_id bigint(20) not null, - detection_config_id bigint(20) not null, - start_time bigint(20) not null, - end_time bigint(20) not null, - detectorName VARCHAR(128), - mape double, - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -ALTER TABLE `evaluation_index` ADD UNIQUE `evaluation_index`(`detection_config_id`, `start_time`, `end_time`); -create index evaluation_base_id_idx ON evaluation_index(base_id); -create index evaluation_detection_config_id_idx ON evaluation_index(detection_config_id); -create index evaluation_detection_start_time_idx on evaluation_index(start_time); - -create table if not exists rootcause_template_index ( - base_id bigint(20) not null, - `name` VARCHAR(256) not null, - application VARCHAR(128), - owner varchar(32) not null, - metric_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -ALTER TABLE `rootcause_template_index` ADD UNIQUE `rootcause_template_index`(`name`); -create index rootcause_template_id_idx ON rootcause_template_index(base_id); -create index rootcause_template_owner_idx ON rootcause_template_index(owner); -create index rootcause_template_metric_idx on rootcause_template_index(metric_id); -create index rootcause_template_config_application_idx ON rootcause_template_index(`application`); - -create table if not exists online_detection_data_index ( - base_id bigint(20) not null, - dataset varchar(200), - metric varchar(200), - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -create index online_detection_data_id_idx ON online_detection_data_index(base_id); -create index online_detection_data_dataset_idx ON online_detection_data_index(dataset); -create index online_detection_data_metric_idx ON online_detection_data_index(metric); - -create table if not exists anomaly_subscription_group_notification_index ( - base_id bigint(20) not null, - anomaly_id bigint(20) not null, - detection_config_id bigint(20) not null, - create_time timestamp, - update_time timestamp default current_timestamp, - version int(10) -) ENGINE=InnoDB; -ALTER TABLE `anomaly_subscription_group_notification_index` ADD UNIQUE `anomaly_subscription_group_notification_index`(anomaly_id); -create index anomaly_subscription_group_anomaly_idx ON anomaly_subscription_group_notification_index(anomaly_id); -create index anomaly_subscription_group_detection_config_idx ON anomaly_subscription_group_notification_index(anomaly_id) diff --git a/kubernetes/helm/thirdeye/install.sh b/kubernetes/helm/thirdeye/install.sh deleted file mode 100755 index 8489c7779325..000000000000 --- a/kubernetes/helm/thirdeye/install.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash - -base_dir=$(dirname "$0") -cd ${base_dir}; - -# fetch dependencies. example: mysql. See Chart.yaml -helm dependency update - -# Note: -# - initdb files must end with .sql -# - When injecting yaml config via terminal, the period ('.') must be escaped and quoted -helm install thirdeye . \ - --set-file mysql.initializationFiles."initdb\.sql"="./config/initdb.sql" \ - --set-file thirdeye.config.dataSources="./config/data-sources/data-sources-config.yml" \ - $@ diff --git a/kubernetes/helm/thirdeye/templates/_helpers.tpl b/kubernetes/helm/thirdeye/templates/_helpers.tpl deleted file mode 100644 index d821fb7c9d53..000000000000 --- a/kubernetes/helm/thirdeye/templates/_helpers.tpl +++ /dev/null @@ -1,131 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -{{/* vim: set filetype=mustache: */}} -{{/* -Expand the name of the chart. -*/}} -{{- define "thirdeye.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified app name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -If release name contains chart name it will be used as a full name. -*/}} -{{- define "thirdeye.fullname" -}} -{{- if .Values.fullnameOverride -}} -{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- $name := default .Chart.Name .Values.nameOverride -}} -{{- if contains $name .Release.Name -}} -{{- .Release.Name | trunc 63 | trimSuffix "-" -}} -{{- else -}} -{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} -{{- end -}} - -{{/* -Create chart name and version as used by the chart label. -*/}} -{{- define "thirdeye.chart" -}} -{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -Create a default fully qualified thirdeye frontend name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "thirdeye.frontend.fullname" -}} -{{ template "thirdeye.fullname" . }}-{{ .Values.frontend.name }} -{{- end -}} - -{{/* -Create a default fully qualified thirdeye backend name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "thirdeye.backend.fullname" -}} -{{ template "thirdeye.fullname" . }}-{{ .Values.backend.name }} -{{- end -}} - -{{/* -Create a default fully qualified thirdeye scheduler (backend with special detector.yml) name. -We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "thirdeye.scheduler.fullname" -}} -{{ template "thirdeye.fullname" . }}-{{ .Values.scheduler.name }} -{{- end -}} - -{{/* -The name of the thirdeye config. -*/}} -{{- define "thirdeye.config" -}} -{{- printf "%s-config" (include "thirdeye.fullname" .) | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -The name of the thirdeye scheduler (backend with special detector.yml) config. -*/}} -{{- define "thirdeye.scheduler.config" -}} -{{- printf "%s-scheduler-config" (include "thirdeye.fullname" .) | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -The name of the thirdeye frontend external service. -*/}} -{{- define "thirdeye.frontend.external" -}} -{{- printf "%s-external" (include "thirdeye.frontend.fullname" .) | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -The name of the thirdeye frontend headless service. -*/}} -{{- define "thirdeye.frontend.headless" -}} -{{- printf "%s-headless" (include "thirdeye.frontend.fullname" .) | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -The name of the thirdeye backend headless service. -*/}} -{{- define "thirdeye.backend.headless" -}} -{{- printf "%s-headless" (include "thirdeye.backend.fullname" .) | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* -The name of the thirdeye scheduler (backend with special detector.yml) headless service. -*/}} -{{- define "thirdeye.scheduler.headless" -}} -{{- printf "%s-headless" (include "thirdeye.scheduler.fullname" .) | trunc 63 | trimSuffix "-" -}} -{{- end -}} - -{{/* - Create a default fully qualified traefik name. - We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). -*/}} -{{- define "thirdeye.traefik.fullname" -}} -{{- if .Values.traefik.fullnameOverride -}} -{{- .Values.traefik.fullnameOverride | trunc -63 | trimSuffix "-" -}} -{{- else -}} -{{- $name := default "traefik" .Values.traefik.nameOverride -}} -{{- printf "%s-%s" .Release.Name $name | trunc -63 | trimSuffix "-" -}} -{{- end -}} -{{- end -}} - diff --git a/kubernetes/helm/thirdeye/templates/backend/deployment.yaml b/kubernetes/helm/thirdeye/templates/backend/deployment.yaml deleted file mode 100644 index de09771a62d8..000000000000 --- a/kubernetes/helm/thirdeye/templates/backend/deployment.yaml +++ /dev/null @@ -1,102 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "thirdeye.backend.fullname" . }} - labels: - app: {{ include "thirdeye.name" . }} - chart: {{ include "thirdeye.chart" . }} - component: {{ .Values.backend.name }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -spec: - selector: - matchLabels: - app: {{ include "thirdeye.name" . }} - release: {{ .Release.Name }} - component: {{ .Values.backend.name }} - replicas: {{ .Values.backend.replicaCount }} - template: - metadata: - labels: - app: {{ include "thirdeye.name" . }} - release: {{ .Release.Name }} - component: {{ .Values.backend.name }} - annotations: -{{ toYaml .Values.backend.podAnnotations | indent 8 }} - spec: - nodeSelector: -{{ toYaml .Values.backend.nodeSelector | indent 8 }} - affinity: -{{ toYaml .Values.backend.affinity | indent 8 }} - tolerations: -{{ toYaml .Values.backend.tolerations | indent 8 }} - securityContext: - runAsGroup: 1000 - fsGroup: 1000 - runAsUser: 1000 - containers: - - name: backend - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - args: [ - "pinot-quickstart", - "backend" - ] - ports: - - containerPort: {{ .Values.backend.port }} - protocol: TCP - - containerPort: {{ .Values.backend.adminport }} - protocol: TCP - volumeMounts: - - name: thirdeye-config - mountPath: "/opt/thirdeye/config/pinot-quickstart/data-sources/data-sources-config.yml" - subPath: "data-sources-config.yml" - readOnly: true - - name: thirdeye-config - mountPath: "/opt/thirdeye/config/pinot-quickstart/data-sources/cache-config.yml" - subPath: "cache-config.yml" - readOnly: true - - name: thirdeye-config - mountPath: "/opt/thirdeye/config/pinot-quickstart/detector.yml" - subPath: "detector.yml" - readOnly: true - - name: thirdeye-config - mountPath: "/opt/thirdeye/config/pinot-quickstart/dashboard.yml" - subPath: "dashboard.yml" - readOnly: true - - name: thirdeye-config - mountPath: "/opt/thirdeye/config/pinot-quickstart/persistence.yml" - subPath: "persistence.yml" - readOnly: true - {{- if .Values.backend.holidayLoaderKey }} - - name: thirdeye-config - mountPath: "/opt/thirdeye/config/pinot-quickstart/holiday-loader-key.json" - subPath: "holiday-loader-key.json" - readOnly: true - {{- end }} - resources: -{{ toYaml .Values.frontend.resources | indent 12 }} - restartPolicy: Always - volumes: - - name: thirdeye-config - configMap: - name: thirdeye-config diff --git a/kubernetes/helm/thirdeye/templates/backend/service-headless.yaml b/kubernetes/helm/thirdeye/templates/backend/service-headless.yaml deleted file mode 100644 index a6703beaae8b..000000000000 --- a/kubernetes/helm/thirdeye/templates/backend/service-headless.yaml +++ /dev/null @@ -1,38 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: v1 -kind: Service -metadata: - name: {{ template "thirdeye.backend.headless" . }} - labels: - app: {{ include "thirdeye.name" . }} - chart: {{ include "thirdeye.chart" . }} - component: {{ .Values.backend.name }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -spec: - clusterIP: None - ports: - # [pod_name].[service_name].[namespace].svc.cluster.local - - port: {{ .Values.backend.port }} - selector: - app: {{ include "thirdeye.name" . }} - release: {{ .Release.Name }} - component: {{ .Values.backend.name }} diff --git a/kubernetes/helm/thirdeye/templates/backend/service.yaml b/kubernetes/helm/thirdeye/templates/backend/service.yaml deleted file mode 100644 index f94721b5f6ac..000000000000 --- a/kubernetes/helm/thirdeye/templates/backend/service.yaml +++ /dev/null @@ -1,38 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: v1 -kind: Service -metadata: - name: {{ include "thirdeye.backend.fullname" . }} - labels: - app: {{ include "thirdeye.name" . }} - chart: {{ include "thirdeye.chart" . }} - component: {{ .Values.backend.name }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -spec: - type: {{ .Values.backend.serviceType }} - ports: - # [pod_name].[service_name].[namespace].svc.cluster.local - - port: {{ .Values.backend.port }} - selector: - app: {{ include "thirdeye.name" . }} - release: {{ .Release.Name }} - component: {{ .Values.backend.name }} diff --git a/kubernetes/helm/thirdeye/templates/common/configmap.yaml b/kubernetes/helm/thirdeye/templates/common/configmap.yaml deleted file mode 100644 index 7d7746599dbf..000000000000 --- a/kubernetes/helm/thirdeye/templates/common/configmap.yaml +++ /dev/null @@ -1,246 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: v1 -kind: ConfigMap -metadata: - name: thirdeye-config -data: - persistence.yml: |- - databaseConfiguration: - url: jdbc:mysql://{{ .Release.Name }}-mysql/thirdeye - user: {{ .Values.mysql.mysqlUser }} - password: {{ .Values.mysql.mysqlPassword }} - driver: com.mysql.jdbc.Driver - dashboard.yml: |- - authConfig: - authEnabled: false - authKey: "" - ldapUrl: "" - domainSuffix: [] - cacheTTL: 3600 - cookieTTL: 604800 - resourceConfig: [] - rootCause: - definitionsPath: rca.yml - parallelism: 5 - formatters: - - org.apache.pinot.thirdeye.dashboard.resources.v2.rootcause.AnomalyEventFormatter - - org.apache.pinot.thirdeye.dashboard.resources.v2.rootcause.ThirdEyeEventFormatter - - org.apache.pinot.thirdeye.dashboard.resources.v2.rootcause.MetricEntityFormatter - - org.apache.pinot.thirdeye.dashboard.resources.v2.rootcause.DimensionEntityFormatter - - org.apache.pinot.thirdeye.dashboard.resources.v2.rootcause.ServiceEntityFormatter - - org.apache.pinot.thirdeye.dashboard.resources.v2.rootcause.DefaultEventEntityFormatter - dashboardHost: "http://localhost:1426" - failureFromAddress: thirdeye@localhost - failureToAddress: user@localhost - alerterConfiguration: - smtpConfiguration: - smtpHost: localhost - smtpPort: 25 - logging: - level: INFO - loggers: - org.apache.pinot: INFO - - server: - type: default - applicationConnectors: - - type: http - port: 1426 - adminConnectors: - - type: http - port: 1427 - requestLog: - type: external - whitelistDatasets: [] - swagger: - resourcePackage: "org.apache.pinot.thirdeye.dashboard.resources,org.apache.pinot.thirdeye.dashboard.resources.v2,org.apache.pinot.thirdeye.anomaly.onboard,org.apache.pinot.thirdeye.detection,org.apache.pinot.thirdeye.detection.yaml" - - detector.yml: |- - logging: - level: INFO - loggers: - org.hibernate.engine.internal: WARN - server: - type: default - rootPath: '/api/*' - applicationContextPath: / - adminContextPath: /admin - applicationConnectors: - - type: http - port: 1867 - adminConnectors: - - type: http - port: 1868 - alert: true - autoload: true - # autoOnboardConfiguration: - # runFrequency: 10s - - classifier: true - holidayEventsLoader: {{ not .Values.backend.holidayLoaderKey | ternary "false" "true" }} - monitor: true - pinotProxy: false - scheduler: true - worker: true - detectionPipeline: true - detectionAlert: true - dashboardHost: "http://localhost:1426" - id: 0 - alerterConfiguration: - smtpConfiguration: - smtpHost: "localhost" - smtpPort: 25 - # jiraConfiguration: - # jiraUser: - # jiraPassword: - # jiraUrl: - # jiraDefaultProject: - # jiraIssueTypeId: 19 - failureFromAddress: "thirdeye@localhost" - failureToAddress: "thirdeye@localhost" - phantomJsPath: "/usr/local/bin/jstf" - swagger: - resourcePackage: "org.apache.pinot.thirdeye.dashboard.resources,org.apache.pinot.thirdeye.dashboard.resources.v2,org.apache.pinot.thirdeye.anomaly.onboard" - holidayEventsLoaderConfiguration: - calendars: - - "en.australian#holiday@group.v.calendar.google.com" - - "en.austrian#holiday@group.v.calendar.google.com" - - "en.brazilian#holiday@group.v.calendar.google.com" - - "en.canadian#holiday@group.v.calendar.google.com" - - "en.china#holiday@group.v.calendar.google.com" - - "en.christian#holiday@group.v.calendar.google.com" - - "en.danish#holiday@group.v.calendar.google.com" - - "en.dutch#holiday@group.v.calendar.google.com" - - "en.finnish#holiday@group.v.calendar.google.com" - - "en.french#holiday@group.v.calendar.google.com" - - "en.german#holiday@group.v.calendar.google.com" - - "en.greek#holiday@group.v.calendar.google.com" - - "en.hong_kong#holiday@group.v.calendar.google.com" - - "en.indian#holiday@group.v.calendar.google.com" - - "en.indonesian#holiday@group.v.calendar.google.com" - - "en.irish#holiday@group.v.calendar.google.com" - - "en.islamic#holiday@group.v.calendar.google.com" - - "en.italian#holiday@group.v.calendar.google.com" - - "en.japanese#holiday@group.v.calendar.google.com" - - "en.jewish#holiday@group.v.calendar.google.com" - - "en.malaysia#holiday@group.v.calendar.google.com" - - "en.mexican#holiday@group.v.calendar.google.com" - - "en.new_zealand#holiday@group.v.calendar.google.com" - - "en.norwegian#holiday@group.v.calendar.google.com" - - "en.philippines#holiday@group.v.calendar.google.com" - - "en.polish#holiday@group.v.calendar.google.com" - - "en.portuguese#holiday@group.v.calendar.google.com" - - "en.russian#holiday@group.v.calendar.google.com" - - "en.singapore#holiday@group.v.calendar.google.com" - - "en.sa#holiday@group.v.calendar.google.com" - - "en.south_korea#holiday@group.v.calendar.google.com" - - "en.spain#holiday@group.v.calendar.google.com" - - "en.swedish#holiday@group.v.calendar.google.com" - - "en.taiwan#holiday@group.v.calendar.google.com" - - "en.uk#holiday@group.v.calendar.google.com" - - "en.usa#holiday@group.v.calendar.google.com" - - "en.vietnamese#holiday@group.v.calendar.google.com" - holidayLoadRange: 2592000000 - runFrequency: 1 - mockEventsLoader: true - mockEventsLoaderConfiguration: - generators: - {{- if not .Values.backend.holidayLoaderKey }} - - type: HOLIDAY - arrivalType: exponential - arrivalMean: 86400000 - durationType: fixed - durationMean: 86400000 - seed: 0 - namePrefixes: [First, Second, Third, Last, Funky, Happy, Sad, Glorious, Jolly, Unity, Pinot's] - nameSuffixes: [day, day, days, celebration, rememberance, occurrence, moment] - {{- end }} - - type: INFORMED - arrivalType: exponential - arrivalMean: 43200000 - durationType: exponential - durationMean: 3600000 - seed: 1 - namePrefixes: [Login, Web, Search, Catalog, Integration, Network, Backup, Ingress, Proxy, Failure, Pinot, ThirdEye] - nameSuffixes: [backend, frontend, v1.1, v1.2, v1.3, v2.0, v3, v4, v5, storage, topic, container, database] - - type: CM - arrivalType: exponential - arrivalMean: 21600000 - durationType: fixed - durationMean: 1800000 - seed: 2 - namePrefixes: [Database, Web, Search, Catalog, Integration, Network, Backup, Ingress, Proxy, Failure, Pinot, ThirdEye] - - type: CUSTOM - arrivalType: exponential - arrivalMean: 432000000 - durationType: exponential - durationMean: 86400000 - seed: 3 - namePrefixes: [Marketing, Onboarding, Vaction, Outreach, InDay] - nameSuffixes: [integration, campaign, meeting] - - type: LIX - arrivalType: exponential - arrivalMean: 259200000 - durationType: exponential - durationMean: 604800000 - seed: 4 - namePrefixes: [System, Model, Campaign, Welcome, Pinot, ThirdEye] - - data-sources-config.yml: {{ toYaml .Values.thirdeye.config.dataSources | indent 4 }} - - cache-config.yml: |- - useInMemoryCache: true - useCentralizedCache: false - - centralizedCacheSettings: - # TTL (time-to-live) for documents in seconds - ttl: 3600 - # if inserting data points individually, max number of threads to spawn to parallel insert at a time - maxParallelInserts: 10 - # which store to use - cacheDataStoreName: 'couchbase' - cacheDataSources: - couchbase: - className: org.apache.pinot.thirdeye.detection.cache.CouchbaseCacheDAO - config: - useCertificateBasedAuthentication: false - # at least 1 host needed - hosts: - - 'host1' # ex. http://localhost:8091 - - 'host2' # ex. http://localhost:8092 - - 'host3' # ex. http://localhost:8093 - - 'host4' # and so on... - bucketName: 'your_bucket_name' - # if using certificate-based authentication, authUsername and authPassword values don't matter and won't be used - authUsername: 'your_bucket_user_username' - authPassword: 'your_bucket_user_password' - enableDnsSrv: false - # certificate based authentication is only available in Couchbase enterprise edition. - keyStoreFilePath: 'key/store/path/keystore_file' # e.g. '/var/identity.p12' - # if your keystore has a password, enter it here. by default, Java uses 'work_around_jdk-6879539' to enable empty passwords for certificates. - keyStorePassword: 'work_around_jdk-6879539' - trustStoreFilePath: 'trust/store/path/truststore_file' # e.g. '/etc/riddler/cacerts' - trustStorePassword: '' - # add your store of choice here - - {{- if .Values.backend.holidayLoaderKey }} - holiday-loader-key.json: {{ .Values.backend.holidayLoaderKey | toPrettyJson }} - {{- end }} diff --git a/kubernetes/helm/thirdeye/templates/frontend/deployment.yaml b/kubernetes/helm/thirdeye/templates/frontend/deployment.yaml deleted file mode 100644 index f8ac8b66c67a..000000000000 --- a/kubernetes/helm/thirdeye/templates/frontend/deployment.yaml +++ /dev/null @@ -1,96 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "thirdeye.frontend.fullname" . }} - labels: - app: {{ include "thirdeye.name" . }} - chart: {{ include "thirdeye.chart" . }} - component: {{ .Values.frontend.name }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -spec: - selector: - matchLabels: - app: {{ include "thirdeye.name" . }} - release: {{ .Release.Name }} - component: {{ .Values.frontend.name }} - replicas: {{ .Values.frontend.replicaCount }} - template: - metadata: - labels: - app: {{ include "thirdeye.name" . }} - release: {{ .Release.Name }} - component: {{ .Values.frontend.name }} - annotations: -{{ toYaml .Values.frontend.podAnnotations | indent 8 }} - spec: - nodeSelector: -{{ toYaml .Values.frontend.nodeSelector | indent 8 }} - affinity: -{{ toYaml .Values.frontend.affinity | indent 8 }} - tolerations: -{{ toYaml .Values.frontend.tolerations | indent 8 }} - securityContext: - runAsGroup: 1000 - fsGroup: 1000 - runAsUser: 1000 - containers: - - name: frontend - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} - args: [ - "pinot-quickstart", - "frontend" - ] - ports: - - containerPort: {{ .Values.frontend.port }} - protocol: TCP - - containerPort: {{ .Values.frontend.adminport }} - protocol: TCP - volumeMounts: - - name: thirdeye-config - mountPath: "/opt/thirdeye/config/pinot-quickstart/data-sources/data-sources-config.yml" - subPath: "data-sources-config.yml" - readOnly: true - - name: thirdeye-config - mountPath: "/opt/thirdeye/config/pinot-quickstart/data-sources/cache-config.yml" - subPath: "cache-config.yml" - readOnly: true - - name: thirdeye-config - mountPath: "/opt/thirdeye/config/pinot-quickstart/detector.yml" - subPath: "detector.yml" - readOnly: true - - name: thirdeye-config - mountPath: "/opt/thirdeye/config/pinot-quickstart/dashboard.yml" - subPath: "dashboard.yml" - readOnly: true - - name: thirdeye-config - mountPath: "/opt/thirdeye/config/pinot-quickstart/persistence.yml" - subPath: "persistence.yml" - readOnly: true - resources: -{{ toYaml .Values.frontend.resources | indent 12 }} - restartPolicy: Always - volumes: - - name: thirdeye-config - configMap: - name: thirdeye-config diff --git a/kubernetes/helm/thirdeye/templates/frontend/ingress.yaml b/kubernetes/helm/thirdeye/templates/frontend/ingress.yaml deleted file mode 100644 index 462bb297ae84..000000000000 --- a/kubernetes/helm/thirdeye/templates/frontend/ingress.yaml +++ /dev/null @@ -1,14 +0,0 @@ -{{- if .Values.traefik.enabled -}} -kind: Ingress -apiVersion: extensions/v1beta1 -metadata: - name: {{ printf "%s-%s" (include "thirdeye.traefik.fullname" . ) "thirdeye" | trunc -63 }} -spec: - rules: - - host: {{ .Release.Name }}.{{ .Release.Namespace }}.{{ required "domain is required." .Values.domain }} - http: - paths: - - backend: - serviceName: {{ include "thirdeye.frontend.headless" . }} - servicePort: {{ .Values.frontend.port }} -{{- end }} diff --git a/kubernetes/helm/thirdeye/templates/frontend/service-headless.yaml b/kubernetes/helm/thirdeye/templates/frontend/service-headless.yaml deleted file mode 100644 index 7fd328bd79fe..000000000000 --- a/kubernetes/helm/thirdeye/templates/frontend/service-headless.yaml +++ /dev/null @@ -1,38 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: v1 -kind: Service -metadata: - name: {{ template "thirdeye.frontend.headless" . }} - labels: - app: {{ include "thirdeye.name" . }} - chart: {{ include "thirdeye.chart" . }} - component: {{ .Values.frontend.name }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -spec: - clusterIP: None - ports: - # [pod_name].[service_name].[namespace].svc.cluster.local - - port: {{ .Values.frontend.port }} - selector: - app: {{ include "thirdeye.name" . }} - release: {{ .Release.Name }} - component: {{ .Values.frontend.name }} diff --git a/kubernetes/helm/thirdeye/templates/frontend/service.yaml b/kubernetes/helm/thirdeye/templates/frontend/service.yaml deleted file mode 100644 index 1c1c78d82a71..000000000000 --- a/kubernetes/helm/thirdeye/templates/frontend/service.yaml +++ /dev/null @@ -1,38 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: v1 -kind: Service -metadata: - name: {{ include "thirdeye.frontend.fullname" . }} - labels: - app: {{ include "thirdeye.name" . }} - chart: {{ include "thirdeye.chart" . }} - component: {{ .Values.frontend.name }} - release: {{ .Release.Name }} - heritage: {{ .Release.Service }} -spec: - type: {{ .Values.frontend.serviceType }} - ports: - # [pod_name].[service_name].[namespace].svc.cluster.local - - port: {{ .Values.frontend.port }} - selector: - app: {{ include "thirdeye.name" . }} - release: {{ .Release.Name }} - component: {{ .Values.frontend.name }} diff --git a/kubernetes/helm/thirdeye/values.yaml b/kubernetes/helm/thirdeye/values.yaml deleted file mode 100644 index 1cf59718bfa6..000000000000 --- a/kubernetes/helm/thirdeye/values.yaml +++ /dev/null @@ -1,74 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -# Default values for ThirdEye Deployment - -image: - repository: apachepinot/thirdeye - tag: latest - pullPolicy: IfNotPresent - -frontend: - enabled: true - name: frontend - replicaCount: 1 - port: 1426 - adminport: 1427 - serviceType: LoadBalancer - - resources: {} - nodeSelector: {} - tolerations: [] - affinity: {} - podAnnotations: {} - - -backend: - enabled: true - name: backend - replicaCount: 1 - port: 1867 - adminport: 1868 - serviceType: LoadBalancer - - resources: {} - nodeSelector: {} - tolerations: [] - affinity: {} - podAnnotations: {} - -# Persistence layer configuration -mysql: - mysqlRootPassword: password - mysqlUser: uthirdeye - mysqlPassword: pass - mysqlDatabase: thirdeye - - # Note: initializationFiles parameter needs to be injected via command line to - # complete the DB setup. - -persistence: - enabled: true - accessMode: ReadWriteOnce - size: 4G - storageClass: "" - -# Optional: Enable for ingress -traefik: - enabled: false diff --git a/kubernetes/skaffold/gke/README.md b/kubernetes/skaffold/gke/README.md deleted file mode 100644 index 7cec40aea965..000000000000 --- a/kubernetes/skaffold/gke/README.md +++ /dev/null @@ -1,119 +0,0 @@ - -# Pinot Quickstart on Kubernetes on Google Kubernetes Engine(GKE) - -## Prerequisite - -- kubectl (https://kubernetes.io/docs/tasks/tools/install-kubectl/) -- Google Cloud SDK (https://cloud.google.com/sdk/install) -- Skaffold (https://skaffold.dev/docs/getting-started/#installing-skaffold) -- Enable Google Cloud Account and create a project, e.g. `pinot-demo`. - - `pinot-demo` will be used as example value for `${GCLOUD_PROJECT}` variable in script example. - - `pinot-demo@example.com` will be used as example value for `${GCLOUD_EMAIL}`. -- Configure kubectl to connect to the Kubernetes cluster. - -## Create a cluster on GKE - -Below script will: -- Create a gCloud cluster `pinot-quickstart` -- Request 1 server of type `n1-standard-2` for zookeeper, kafka, pinot controller, pinot broker. -- Request 1 server of type `n1-standard-8` for Pinot server. - -Please fill both environment variables: `${GCLOUD_PROJECT}` and `${GCLOUD_EMAIL}` with your gcloud project and gcloud account email in below script. -``` -GCLOUD_PROJECT=[your gcloud project name] -GCLOUD_EMAIL=[Your gcloud account email] -./setup.sh -``` - -E.g. -``` -GCLOUD_PROJECT=pinot-demo -GCLOUD_EMAIL=pinot-demo@example.com -./setup.sh -``` - -Feel free to modify the script to pick your preferred sku, e.g. `n1-highmem-32` for Pinot server. - - -## How to connect to an existing cluster -Simply run below command to get the credential for the cluster you just created or your existing cluster. -Please modify the Env variables `${GCLOUD_PROJECT}`, `${GCLOUD_ZONE}`, `${GCLOUD_CLUSTER}` accordingly in below script. -``` -GCLOUD_PROJECT=pinot-demo -GCLOUD_ZONE=us-west1-b -GCLOUD_CLUSTER=pinot-quickstart -gcloud container clusters get-credentials ${GCLOUD_CLUSTER} --zone ${GCLOUD_ZONE} --project ${GCLOUD_PROJECT} -``` - -Look for cluster status -``` -kubectl get all -n pinot-quickstart -o wide -``` - -## How to setup a Pinot cluster for demo - -The script requests: - - Create persistent disk for deep storage and mount it. - - Zookeeper - - Kafka - - Pinot Controller - - Pinot Server - - Create Pods for - - Zookeeper - - Kafka - - Pinot Controller - - Pinot Broker - - Pinot Server - - Pinot Example Loader - - -``` -skaffold run -f skaffold.yaml -``` - -## How to load sample data - -Below command will -- Upload sample table schema -- Create sample table -- Publish sample data to a Kafka topic, which the example table would consume from. - -``` -kubectl apply -f pinot-realtime-quickstart.yml -``` - - -## How to query pinot data - -Please use below script to do local port-forwarding and open Pinot query console on your web browser. -``` -./query-pinot-data.sh -``` - -## How to delete a cluster -Below script will delete the pinot perf cluster and delete the pvc disks. - -Note that you need to replace the gcloud project name if you are using another one. -``` -GCLOUD_PROJECT=[your gcloud project name] -./cleanup.sh -``` diff --git a/kubernetes/skaffold/gke/cleanup.sh b/kubernetes/skaffold/gke/cleanup.sh deleted file mode 100755 index d6a961afad79..000000000000 --- a/kubernetes/skaffold/gke/cleanup.sh +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/env bash -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -set -e - -if [[ -z "${GCLOUD_PROJECT}" ]] -then - echo "Please set \$GCLOUD_PROJECT variable. E.g. GCLOUD_PROJECT=pinot-demo ./cleanup.sh" - exit 1 -fi - -GCLOUD_ZONE=us-west1-b -GCLOUD_CLUSTER=pinot-quickstart - -gcloud container clusters delete ${GCLOUD_CLUSTER} --zone=${GCLOUD_ZONE} --project=${GCLOUD_PROJECT} -q - -for diskname in `gcloud compute disks list --zones=${GCLOUD_ZONE} --project ${GCLOUD_PROJECT} |grep gke-${GCLOUD_CLUSTER}|awk -F ' ' '{print $1}'`; -do - echo $diskname; - gcloud compute disks delete $diskname --zone=${GCLOUD_ZONE} --project ${GCLOUD_PROJECT} -q -done - diff --git a/kubernetes/skaffold/gke/gke-storageclass-kafka-pd.yml b/kubernetes/skaffold/gke/gke-storageclass-kafka-pd.yml deleted file mode 100644 index cf05d18f52e4..000000000000 --- a/kubernetes/skaffold/gke/gke-storageclass-kafka-pd.yml +++ /dev/null @@ -1,29 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: kafka - namespace: pinot-quickstart -provisioner: kubernetes.io/gce-pd -allowVolumeExpansion: true -reclaimPolicy: Retain -parameters: - type: pd-ssd diff --git a/kubernetes/skaffold/gke/gke-storageclass-pinot-controller-pd.yml b/kubernetes/skaffold/gke/gke-storageclass-pinot-controller-pd.yml deleted file mode 100644 index 063a94223680..000000000000 --- a/kubernetes/skaffold/gke/gke-storageclass-pinot-controller-pd.yml +++ /dev/null @@ -1,29 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: pinot-controller - namespace: pinot-quickstart -provisioner: kubernetes.io/gce-pd -allowVolumeExpansion: true -reclaimPolicy: Retain -parameters: - type: pd-standard diff --git a/kubernetes/skaffold/gke/gke-storageclass-pinot-server-pd.yml b/kubernetes/skaffold/gke/gke-storageclass-pinot-server-pd.yml deleted file mode 100644 index 634803383aab..000000000000 --- a/kubernetes/skaffold/gke/gke-storageclass-pinot-server-pd.yml +++ /dev/null @@ -1,29 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: pinot-server - namespace: pinot-quickstart -provisioner: kubernetes.io/gce-pd -allowVolumeExpansion: true -reclaimPolicy: Retain -parameters: - type: pd-ssd diff --git a/kubernetes/skaffold/gke/gke-storageclass-zk-pd.yml b/kubernetes/skaffold/gke/gke-storageclass-zk-pd.yml deleted file mode 100644 index aa42b42f719c..000000000000 --- a/kubernetes/skaffold/gke/gke-storageclass-zk-pd.yml +++ /dev/null @@ -1,29 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -kind: StorageClass -apiVersion: storage.k8s.io/v1 -metadata: - name: zookeeper - namespace: pinot-quickstart -provisioner: kubernetes.io/gce-pd -allowVolumeExpansion: true -reclaimPolicy: Retain -parameters: - type: pd-ssd diff --git a/kubernetes/skaffold/gke/kafka.yml b/kubernetes/skaffold/gke/kafka.yml deleted file mode 100644 index b40023ff0efc..000000000000 --- a/kubernetes/skaffold/gke/kafka.yml +++ /dev/null @@ -1,408 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -kind: ConfigMap -metadata: - name: broker-config - namespace: pinot-quickstart -apiVersion: v1 -data: - init.sh: |- - #!/bin/bash - set -e - set -x - cp /etc/kafka-configmap/log4j.properties /etc/kafka/ - - KAFKA_BROKER_ID=${HOSTNAME##*-} - SEDS=("s/#init#broker.id=#init#/broker.id=$KAFKA_BROKER_ID/") - LABELS="kafka-broker-id=$KAFKA_BROKER_ID" - ANNOTATIONS="" - - hash kubectl 2>/dev/null || { - SEDS+=("s/#init#broker.rack=#init#/#init#broker.rack=# kubectl not found in path/") - } && { - ZONE=$(kubectl get node "$NODE_NAME" -o=go-template='{{index .metadata.labels "failure-domain.beta.kubernetes.io/zone"}}') - if [ $? -ne 0 ]; then - SEDS+=("s/#init#broker.rack=#init#/#init#broker.rack=# zone lookup failed, see -c init-config logs/") - elif [ "x$ZONE" == "x" ]; then - SEDS+=("s/#init#broker.rack=#init#/#init#broker.rack=# zone label not found for node $NODE_NAME/") - else - SEDS+=("s/#init#broker.rack=#init#/broker.rack=$ZONE/") - LABELS="$LABELS kafka-broker-rack=$ZONE" - fi - - OUTSIDE_HOST=$(kubectl get node "$NODE_NAME" -o jsonpath='{.status.addresses[?(@.type=="InternalIP")].address}') - if [ $? -ne 0 ]; then - echo "Outside (i.e. cluster-external access) host lookup command failed" - else - OUTSIDE_PORT=3240${KAFKA_BROKER_ID} - SEDS+=("s|#init#advertised.listeners=OUTSIDE://#init#|advertised.listeners=OUTSIDE://${OUTSIDE_HOST}:${OUTSIDE_PORT}|") - ANNOTATIONS="$ANNOTATIONS kafka-listener-outside-host=$OUTSIDE_HOST kafka-listener-outside-port=$OUTSIDE_PORT" - fi - - if [ ! -z "$LABELS" ]; then - kubectl -n $POD_NAMESPACE label pod $POD_NAME $LABELS || echo "Failed to label $POD_NAMESPACE.$POD_NAME - RBAC issue?" - fi - if [ ! -z "$ANNOTATIONS" ]; then - kubectl -n $POD_NAMESPACE annotate pod $POD_NAME $ANNOTATIONS || echo "Failed to annotate $POD_NAMESPACE.$POD_NAME - RBAC issue?" - fi - } - printf '%s\n' "${SEDS[@]}" | sed -f - /etc/kafka-configmap/server.properties > /etc/kafka/server.properties.tmp - [ $? -eq 0 ] && mv /etc/kafka/server.properties.tmp /etc/kafka/server.properties - - server.properties: |- - ############################# Log Basics ############################# - - # A comma seperated list of directories under which to store log files - # Overrides log.dir - log.dirs=/var/lib/kafka/data/topics - - # The default number of log partitions per topic. More partitions allow greater - # parallelism for consumption, but this will also result in more files across - # the brokers. - num.partitions=12 - - default.replication.factor=1 - - min.insync.replicas=1 - - auto.create.topics.enable=true - - # The number of threads per data directory to be used for log recovery at startup and flushing at shutdown. - # This value is recommended to be increased for installations with data dirs located in RAID array. - #num.recovery.threads.per.data.dir=1 - - ############################# Server Basics ############################# - - # The id of the broker. This must be set to a unique integer for each broker. - #init#broker.id=#init# - - #init#broker.rack=#init# - - ############################# Socket Server Settings ############################# - - # The address the socket server listens on. It will get the value returned from - # java.net.InetAddress.getCanonicalHostName() if not configured. - # FORMAT: - # listeners = listener_name://host_name:port - # EXAMPLE: - # listeners = PLAINTEXT://your.host.name:9092 - #listeners=PLAINTEXT://:9092 - listeners=OUTSIDE://:9094,PLAINTEXT://:9092 - - # Hostname and port the broker will advertise to producers and consumers. If not set, - # it uses the value for "listeners" if configured. Otherwise, it will use the value - # returned from java.net.InetAddress.getCanonicalHostName(). - #advertised.listeners=PLAINTEXT://your.host.name:9092 - #init#advertised.listeners=OUTSIDE://#init#,PLAINTEXT://:9092 - - # Maps listener names to security protocols, the default is for them to be the same. See the config documentation for more details - #listener.security.protocol.map=PLAINTEXT:PLAINTEXT,SSL:SSL,SASL_PLAINTEXT:SASL_PLAINTEXT,SASL_SSL:SASL_SSL - listener.security.protocol.map=PLAINTEXT:PLAINTEXT,SSL:SSL,SASL_PLAINTEXT:SASL_PLAINTEXT,SASL_SSL:SASL_SSL,OUTSIDE:PLAINTEXT - inter.broker.listener.name=PLAINTEXT - - compression.type=gzip - inter.broker.protocol.version=0.10.2.0 - log.message.format.version=0.10.2.0 - - # The number of threads that the server uses for receiving requests from the network and sending responses to the network - #num.network.threads=3 - - # The number of threads that the server uses for processing requests, which may include disk I/O - #num.io.threads=8 - - # The send buffer (SO_SNDBUF) used by the socket server - #socket.send.buffer.bytes=102400 - - # The receive buffer (SO_RCVBUF) used by the socket server - #socket.receive.buffer.bytes=102400 - - # The maximum size of a request that the socket server will accept (protection against OOM) - #socket.request.max.bytes=104857600 - - ############################# Internal Topic Settings ############################# - # The replication factor for the group metadata internal topics "__consumer_offsets" and "__transaction_state" - # For anything other than development testing, a value greater than 1 is recommended for to ensure availability such as 3. - offsets.topic.replication.factor=1 - #transaction.state.log.replication.factor=1 - #transaction.state.log.min.isr=1 - - ############################# Log Flush Policy ############################# - - # Messages are immediately written to the filesystem but by default we only fsync() to sync - # the OS cache lazily. The following configurations control the flush of data to disk. - # There are a few important trade-offs here: - # 1. Durability: Unflushed data may be lost if you are not using replication. - # 2. Latency: Very large flush intervals may lead to latency spikes when the flush does occur as there will be a lot of data to flush. - # 3. Throughput: The flush is generally the most expensive operation, and a small flush interval may lead to excessive seeks. - # The settings below allow one to configure the flush policy to flush data after a period of time or - # every N messages (or both). This can be done globally and overridden on a per-topic basis. - - # The number of messages to accept before forcing a flush of data to disk - #log.flush.interval.messages=10000 - - # The maximum amount of time a message can sit in a log before we force a flush - #log.flush.interval.ms=1000 - - ############################# Log Retention Policy ############################# - - # The following configurations control the disposal of log segments. The policy can - # be set to delete segments after a period of time, or after a given size has accumulated. - # A segment will be deleted whenever *either* of these criteria are met. Deletion always happens - # from the end of the log. - - # https://cwiki.apache.org/confluence/display/KAFKA/KIP-186%3A+Increase+offsets+retention+default+to+7+days - offsets.retention.minutes=10080 - - # The minimum age of a log file to be eligible for deletion due to age - log.retention.hours=-1 - - # A size-based retention policy for logs. Segments are pruned from the log unless the remaining - # segments drop below log.retention.bytes. Functions independently of log.retention.hours. - #log.retention.bytes=1073741824 - - # The maximum size of a log segment file. When this size is reached a new log segment will be created. - #log.segment.bytes=1073741824 - - # The interval at which log segments are checked to see if they can be deleted according - # to the retention policies - #log.retention.check.interval.ms=300000 - - ############################# Zookeeper ############################# - - # Zookeeper connection string (see zookeeper docs for details). - # This is a comma separated host:port pairs, each corresponding to a zk - # server. e.g. "127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002". - # You can also append an optional chroot string to the urls to specify the - # root directory for all kafka znodes. - zookeeper.connect=zookeeper:2181 - - # Timeout in ms for connecting to zookeeper - #zookeeper.connection.timeout.ms=6000 - - - ############################# Group Coordinator Settings ############################# - - # The following configuration specifies the time, in milliseconds, that the GroupCoordinator will delay the initial consumer rebalance. - # The rebalance will be further delayed by the value of group.initial.rebalance.delay.ms as new members join the group, up to a maximum of max.poll.interval.ms. - # The default value for this is 3 seconds. - # We override this to 0 here as it makes for a better out-of-the-box experience for development and testing. - # However, in production environments the default value of 3 seconds is more suitable as this will help to avoid unnecessary, and potentially expensive, rebalances during application startup. - #group.initial.rebalance.delay.ms=0 - - log4j.properties: |- - # Unspecified loggers and loggers with additivity=true output to server.log and stdout - # Note that INFO only applies to unspecified loggers, the log level of the child logger is used otherwise - log4j.rootLogger=INFO, stdout - - log4j.appender.stdout=org.apache.log4j.ConsoleAppender - log4j.appender.stdout.layout=org.apache.log4j.PatternLayout - log4j.appender.stdout.layout.ConversionPattern=[%d] %p %m (%c)%n - - log4j.appender.kafkaAppender=org.apache.log4j.DailyRollingFileAppender - log4j.appender.kafkaAppender.DatePattern='.'yyyy-MM-dd-HH - log4j.appender.kafkaAppender.File=${kafka.logs.dir}/server.log - log4j.appender.kafkaAppender.layout=org.apache.log4j.PatternLayout - log4j.appender.kafkaAppender.layout.ConversionPattern=[%d] %p %m (%c)%n - - log4j.appender.stateChangeAppender=org.apache.log4j.DailyRollingFileAppender - log4j.appender.stateChangeAppender.DatePattern='.'yyyy-MM-dd-HH - log4j.appender.stateChangeAppender.File=${kafka.logs.dir}/state-change.log - log4j.appender.stateChangeAppender.layout=org.apache.log4j.PatternLayout - log4j.appender.stateChangeAppender.layout.ConversionPattern=[%d] %p %m (%c)%n - - log4j.appender.requestAppender=org.apache.log4j.DailyRollingFileAppender - log4j.appender.requestAppender.DatePattern='.'yyyy-MM-dd-HH - log4j.appender.requestAppender.File=${kafka.logs.dir}/kafka-request.log - log4j.appender.requestAppender.layout=org.apache.log4j.PatternLayout - log4j.appender.requestAppender.layout.ConversionPattern=[%d] %p %m (%c)%n - - log4j.appender.cleanerAppender=org.apache.log4j.DailyRollingFileAppender - log4j.appender.cleanerAppender.DatePattern='.'yyyy-MM-dd-HH - log4j.appender.cleanerAppender.File=${kafka.logs.dir}/log-cleaner.log - log4j.appender.cleanerAppender.layout=org.apache.log4j.PatternLayout - log4j.appender.cleanerAppender.layout.ConversionPattern=[%d] %p %m (%c)%n - - log4j.appender.controllerAppender=org.apache.log4j.DailyRollingFileAppender - log4j.appender.controllerAppender.DatePattern='.'yyyy-MM-dd-HH - log4j.appender.controllerAppender.File=${kafka.logs.dir}/controller.log - log4j.appender.controllerAppender.layout=org.apache.log4j.PatternLayout - log4j.appender.controllerAppender.layout.ConversionPattern=[%d] %p %m (%c)%n - - log4j.appender.authorizerAppender=org.apache.log4j.DailyRollingFileAppender - log4j.appender.authorizerAppender.DatePattern='.'yyyy-MM-dd-HH - log4j.appender.authorizerAppender.File=${kafka.logs.dir}/kafka-authorizer.log - log4j.appender.authorizerAppender.layout=org.apache.log4j.PatternLayout - log4j.appender.authorizerAppender.layout.ConversionPattern=[%d] %p %m (%c)%n - - # Change the two lines below to adjust ZK client logging - log4j.logger.org.I0Itec.zkclient.ZkClient=INFO - log4j.logger.org.apache.zookeeper=INFO - - # Change the two lines below to adjust the general broker logging level (output to server.log and stdout) - log4j.logger.kafka=INFO - log4j.logger.org.apache.kafka=INFO - - # Change to DEBUG or TRACE to enable request logging - log4j.logger.kafka.request.logger=WARN, requestAppender - log4j.additivity.kafka.request.logger=false - - # Uncomment the lines below and change log4j.logger.kafka.network.RequestChannel$ to TRACE for additional output - # related to the handling of requests - #log4j.logger.kafka.network.Processor=TRACE, requestAppender - #log4j.logger.kafka.server.KafkaApis=TRACE, requestAppender - #log4j.additivity.kafka.server.KafkaApis=false - log4j.logger.kafka.network.RequestChannel$=WARN, requestAppender - log4j.additivity.kafka.network.RequestChannel$=false - - log4j.logger.kafka.controller=TRACE, controllerAppender - log4j.additivity.kafka.controller=false - - log4j.logger.kafka.log.LogCleaner=INFO, cleanerAppender - log4j.additivity.kafka.log.LogCleaner=false - - log4j.logger.state.change.logger=TRACE, stateChangeAppender - log4j.additivity.state.change.logger=false - - # Change to DEBUG to enable audit log for the authorizer - log4j.logger.kafka.authorizer.logger=WARN, authorizerAppender - log4j.additivity.kafka.authorizer.logger=false - ---- -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: kafka - namespace: pinot-quickstart -spec: - selector: - matchLabels: - app: kafka - serviceName: kafka - replicas: 1 - updateStrategy: - type: RollingUpdate - podManagementPolicy: Parallel - template: - metadata: - labels: - app: kafka - annotations: - spec: - terminationGracePeriodSeconds: 30 - initContainers: - - name: init-config - image: solsson/kafka-initutils@sha256:2cdb90ea514194d541c7b869ac15d2d530ca64889f56e270161fe4e5c3d076ea - env: - - name: NODE_NAME - valueFrom: - fieldRef: - fieldPath: spec.nodeName - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - command: ['/bin/bash', '/etc/kafka-configmap/init.sh'] - volumeMounts: - - name: configmap - mountPath: /etc/kafka-configmap - - name: config - mountPath: /etc/kafka - - name: extensions - mountPath: /opt/kafka/libs/extensions - containers: - - name: kafka - image: solsson/kafka:2.1.0@sha256:ac3f06d87d45c7be727863f31e79fbfdcb9c610b51ba9cf03c75a95d602f15e1 - env: - - name: CLASSPATH - value: /opt/kafka/libs/extensions/* - - name: KAFKA_LOG4J_OPTS - value: -Dlog4j.configuration=file:/etc/kafka/log4j.properties - - name: JMX_PORT - value: "5555" - ports: - - name: inside - containerPort: 9092 - - name: outside - containerPort: 9094 - - name: jmx - containerPort: 5555 - command: - - ./bin/kafka-server-start.sh - - /etc/kafka/server.properties - lifecycle: - preStop: - exec: - command: ["sh", "-ce", "kill -s TERM 1; while $(kill -0 1 2>/dev/null); do sleep 1; done"] - resources: - requests: - cpu: 400m - memory: 2048Mi - limits: - # This limit was intentionally set low as a reminder that - # the entire Yolean/kubernetes-kafka is meant to be tweaked - # before you run production workloads - memory: 4096Mi - readinessProbe: - tcpSocket: - port: 9092 - timeoutSeconds: 1 - volumeMounts: - - name: config - mountPath: /etc/kafka - - name: data - mountPath: /var/lib/kafka/data - - name: extensions - mountPath: /opt/kafka/libs/extensions - volumes: - - name: configmap - configMap: - name: broker-config - - name: config - emptyDir: {} - - name: extensions - emptyDir: {} - nodeSelector: - cloud.google.com/gke-nodepool: default-pool - volumeClaimTemplates: - - metadata: - name: data - spec: - accessModes: [ "ReadWriteOnce" ] - storageClassName: kafka - resources: - requests: - storage: 5Gi ---- -apiVersion: v1 -kind: Service -metadata: - name: kafka - namespace: pinot-quickstart -spec: - ports: - # [podname].kafka.pinot-quickstart.svc.cluster.local - - port: 9092 - clusterIP: None - selector: - app: kafka diff --git a/kubernetes/skaffold/gke/pinot-broker.yml b/kubernetes/skaffold/gke/pinot-broker.yml deleted file mode 100644 index f3dea9544803..000000000000 --- a/kubernetes/skaffold/gke/pinot-broker.yml +++ /dev/null @@ -1,88 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: v1 -kind: List -items: - - apiVersion: v1 - kind: ConfigMap - metadata: - name: pinot-broker-config - namespace: pinot-quickstart - data: - pinot-broker.conf: |- - pinot.broker.client.queryPort=8099 - pinot.broker.routing.table.builder.class=random - pinot.set.instance.id.to.hostname=true - - - apiVersion: apps/v1 - kind: StatefulSet - metadata: - name: pinot-broker - namespace: pinot-quickstart - spec: - selector: - matchLabels: - app: pinot-broker - serviceName: pinot-broker - replicas: 3 - updateStrategy: - type: RollingUpdate - podManagementPolicy: Parallel - template: - metadata: - labels: - app: pinot-broker - spec: - terminationGracePeriodSeconds: 30 - containers: - - image: apachepinot/pinot:release-0.7.1 - imagePullPolicy: Always - name: pinot-broker - args: [ - "StartBroker", - "-clusterName", "pinot-quickstart", - "-zkAddress", "zookeeper:2181/pinot", - "-configFileName", "/var/pinot/broker/config/pinot-broker.conf" - ] - ports: - - containerPort: 8099 - protocol: TCP - volumeMounts: - - name: config - mountPath: /var/pinot/broker/config - restartPolicy: Always - volumes: - - name: config - configMap: - name: pinot-broker-config - nodeSelector: - cloud.google.com/gke-nodepool: default-pool - - apiVersion: v1 - kind: Service - metadata: - name: pinot-broker - namespace: pinot-quickstart - spec: - ports: - # [podname].pinot-broker.pinot-quickstart.svc.cluster.local - - port: 8099 - clusterIP: None - selector: - app: pinot-broker diff --git a/kubernetes/skaffold/gke/pinot-controller.yml b/kubernetes/skaffold/gke/pinot-controller.yml deleted file mode 100644 index 8ae6478823c4..000000000000 --- a/kubernetes/skaffold/gke/pinot-controller.yml +++ /dev/null @@ -1,105 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: v1 -kind: List -items: - - apiVersion: v1 - kind: ConfigMap - metadata: - name: pinot-controller-config - namespace: pinot-quickstart - data: - pinot-controller.conf: |- - controller.helix.cluster.name=pinot-quickstart - controller.port=9000 - controller.vip.host=pinot-controller - controller.vip.port=9000 - controller.data.dir=/var/pinot/controller/data - controller.zk.str=zookeeper:2181/pinot - pinot.set.instance.id.to.hostname=true - - apiVersion: apps/v1 - kind: StatefulSet - metadata: - name: pinot-controller - namespace: pinot-quickstart - spec: - selector: - matchLabels: - app: pinot-controller - serviceName: pinot-controller - replicas: 1 - updateStrategy: - type: RollingUpdate - podManagementPolicy: Parallel - template: - metadata: - labels: - app: pinot-controller - spec: - terminationGracePeriodSeconds: 30 - containers: - - image: apachepinot/pinot:release-0.7.1 - imagePullPolicy: Always - name: pinot-controller - args: [ - "StartController", - "-configFileName", "/var/pinot/controller/config/pinot-controller.conf" - ] - ports: - - containerPort: 9000 - protocol: TCP - volumeMounts: - - name: config - mountPath: /var/pinot/controller/config - - name: pinot-controller-storage - mountPath: /var/pinot/controller/data - initContainers: - - name: create-zk-root-path - image: solsson/kafka:2.1.0@sha256:ac3f06d87d45c7be727863f31e79fbfdcb9c610b51ba9cf03c75a95d602f15e1 - command: ["bin/zookeeper-shell.sh", "ZooKeeper", "-server", "zookeeper:2181", "create", "/pinot", "\"\""] - restartPolicy: Always - volumes: - - name: config - configMap: - name: pinot-controller-config - nodeSelector: - cloud.google.com/gke-nodepool: default-pool - volumeClaimTemplates: - - metadata: - name: pinot-controller-storage - spec: - accessModes: - - ReadWriteOnce - storageClassName: "pinot-controller" - resources: - requests: - storage: 10G - - apiVersion: v1 - kind: Service - metadata: - name: pinot-controller - namespace: pinot-quickstart - spec: - ports: - # [podname].pinot-controller.pinot-quickstart.svc.cluster.local - - port: 9000 - clusterIP: None - selector: - app: pinot-controller diff --git a/kubernetes/skaffold/gke/pinot-realtime-quickstart.yml b/kubernetes/skaffold/gke/pinot-realtime-quickstart.yml deleted file mode 100644 index 37fc57eac759..000000000000 --- a/kubernetes/skaffold/gke/pinot-realtime-quickstart.yml +++ /dev/null @@ -1,40 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: batch/v1 -kind: Job -metadata: - name: pinot-example-loader - namespace: pinot-quickstart -spec: - template: - spec: - containers: - - name: loading-data-to-kafka - image: apachepinot/pinot:release-0.7.1 - args: [ "StreamAvroIntoKafka", "-avroFile", "examples/stream/airlineStats/rawdata/airlineStats_data.avro", "-kafkaTopic", "flights-realtime", "-kafkaBrokerList", "kafka:9092", "-zkAddress", "zookeeper:2181" ] - - name: pinot-add-example-realtime-table - image: apachepinot/pinot:release-0.7.1 - args: [ "AddTable", "-schemaFile", "examples/stream/airlineStats/airlineStats_schema.json", "-tableConfigFile", "examples/stream/airlineStats/docker/airlineStats_realtime_table_config.json", "-controllerHost", "pinot-controller", "-controllerPort", "9000", "-exec" ] - restartPolicy: OnFailure - nodeSelector: - cloud.google.com/gke-nodepool: default-pool - backoffLimit: 3 - - diff --git a/kubernetes/skaffold/gke/pinot-server.yml b/kubernetes/skaffold/gke/pinot-server.yml deleted file mode 100644 index f1705a6ede26..000000000000 --- a/kubernetes/skaffold/gke/pinot-server.yml +++ /dev/null @@ -1,101 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: v1 -kind: List -items: - - apiVersion: v1 - kind: ConfigMap - metadata: - name: pinot-server-config - namespace: pinot-quickstart - data: - pinot-server.conf: |- - pinot.server.netty.port=8098 - pinot.server.adminapi.port=8097 - pinot.server.instance.dataDir=/var/pinot/server/data/index - pinot.server.instance.segmentTarDir=/var/pinot/server/data/segmentTar - pinot.set.instance.id.to.hostname=true - - apiVersion: apps/v1 - kind: StatefulSet - metadata: - name: pinot-server - namespace: pinot-quickstart - spec: - selector: - matchLabels: - app: pinot-server - serviceName: pinot-server - replicas: 1 - updateStrategy: - type: RollingUpdate - podManagementPolicy: Parallel - template: - metadata: - labels: - app: pinot-server - spec: - terminationGracePeriodSeconds: 30 - containers: - - image: apachepinot/pinot:release-0.7.1 - imagePullPolicy: Always - name: pinot-server - args: [ - "StartServer", - "-clusterName", "pinot-quickstart", - "-zkAddress", "zookeeper:2181/pinot", - "-configFileName", "/var/pinot/server/config/pinot-server.conf" - ] - ports: - - containerPort: 8098 - protocol: TCP - volumeMounts: - - name: config - mountPath: /var/pinot/server/config - - name: pinot-server-storage - mountPath: /var/pinot/server/data - restartPolicy: Always - volumes: - - name: config - configMap: - name: pinot-server-config - nodeSelector: - cloud.google.com/gke-nodepool: pinot-server-pool - volumeClaimTemplates: - - metadata: - name: pinot-server-storage - spec: - accessModes: - - ReadWriteOnce - storageClassName: "pinot-server" - resources: - requests: - storage: 10G - - apiVersion: v1 - kind: Service - metadata: - name: pinot-server - namespace: pinot-quickstart - spec: - ports: - # [podname].pinot-server.pinot-quickstart.svc.cluster.local - - port: 8098 - clusterIP: None - selector: - app: pinot-server diff --git a/kubernetes/skaffold/gke/query-pinot-data.sh b/kubernetes/skaffold/gke/query-pinot-data.sh deleted file mode 100755 index ec3082660bf9..000000000000 --- a/kubernetes/skaffold/gke/query-pinot-data.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -if [[ $(nc -z localhost 9000) != 0 ]]; then - kubectl port-forward service/pinot-controller 9000:9000 -n pinot-quickstart > /dev/null & -fi -sleep 2 -open http://localhost:9000 -# Just for blocking -tail -f /dev/null -pkill -f "kubectl port-forward service/pinot-controller 9000:9000 -n pinot-quickstart" diff --git a/kubernetes/skaffold/gke/setup.sh b/kubernetes/skaffold/gke/setup.sh deleted file mode 100755 index 8f13c60d2ae2..000000000000 --- a/kubernetes/skaffold/gke/setup.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env bash -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -set -e -if [[ -z "${GCLOUD_EMAIL}" ]] || [[ -z "${GCLOUD_PROJECT}" ]] -then - echo "Please set both \$GCLOUD_EMAIL and \$GCLOUD_PROJECT variables. E.g. GCLOUD_PROJECT=pinot-demo GCLOUD_EMAIL=pinot-demo@example.com ./setup.sh" - exit 1 -fi - -GCLOUD_ZONE=us-west1-b -GCLOUD_CLUSTER=pinot-quickstart -GCLOUD_MACHINE_TYPE=n1-standard-2 -GCLOUD_NUM_NODES=1 -gcloud container clusters create ${GCLOUD_CLUSTER} \ - --num-nodes=${GCLOUD_NUM_NODES} \ - --machine-type=${GCLOUD_MACHINE_TYPE} \ - --zone=${GCLOUD_ZONE} \ - --project=${GCLOUD_PROJECT} -gcloud container clusters get-credentials ${GCLOUD_CLUSTER} --zone ${GCLOUD_ZONE} --project ${GCLOUD_PROJECT} - - -GCLOUD_MACHINE_TYPE=n1-standar-8 -gcloud container node-pools create pinot-server-pool \ - --cluster=${GCLOUD_CLUSTER} \ - --machine-type=${GCLOUD_MACHINE_TYPE} \ - --num-nodes=${GCLOUD_NUM_NODES} \ - --zone=${GCLOUD_ZONE} \ - --project=${GCLOUD_PROJECT} - -kubectl create namespace pinot-quickstart - -kubectl create clusterrolebinding cluster-admin-binding --clusterrole cluster-admin --user ${GCLOUD_EMAIL} -kubectl create clusterrolebinding add-on-cluster-admin --clusterrole=cluster-admin --serviceaccount=pinot-quickstart:default diff --git a/kubernetes/skaffold/gke/skaffold.yaml b/kubernetes/skaffold/gke/skaffold.yaml deleted file mode 100644 index d50f6e40a372..000000000000 --- a/kubernetes/skaffold/gke/skaffold.yaml +++ /dev/null @@ -1,35 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: skaffold/v2beta14 -kind: Config -build: - artifacts: -deploy: - kubectl: - manifests: - - ./gke-storageclass-zk-pd.yml - - ./gke-storageclass-kafka-pd.yml - - ./gke-storageclass-pinot-controller-pd.yml - - ./gke-storageclass-pinot-server-pd.yml - - ./zookeeper.yml - - ./kafka.yml - - ./pinot-controller.yml - - ./pinot-broker.yml - - ./pinot-server.yml diff --git a/kubernetes/skaffold/gke/zookeeper.yml b/kubernetes/skaffold/gke/zookeeper.yml deleted file mode 100644 index a841a3c0f7a2..000000000000 --- a/kubernetes/skaffold/gke/zookeeper.yml +++ /dev/null @@ -1,80 +0,0 @@ -# -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. -# - -apiVersion: v1 -kind: Service -metadata: - name: zookeeper - namespace: pinot-quickstart -spec: - ports: - - name: zookeeper - port: 2181 - selector: - app: zookeeper ---- -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: zookeeper - namespace: pinot-quickstart -spec: - selector: - matchLabels: - app: zookeeper - serviceName: "zookeeper" - template: - metadata: - labels: - app: zookeeper - spec: - terminationGracePeriodSeconds: 10 - containers: - - name: zookeeper - image: solsson/kafka:2.1.0@sha256:ac3f06d87d45c7be727863f31e79fbfdcb9c610b51ba9cf03c75a95d602f15e1 - env: - - name: JMX_PORT - value: "5555" - ports: - - name: zookeeper - containerPort: 2181 - - name: jmx - containerPort: 5555 - volumeMounts: - - name: zookeeper-storage - mountPath: /tmp/zookeeper - command: - - ./bin/zookeeper-server-start.sh - - config/zookeeper.properties - readinessProbe: - tcpSocket: - port: 2181 - timeoutSeconds: 1 - nodeSelector: - cloud.google.com/gke-nodepool: default-pool - volumeClaimTemplates: - - metadata: - name: zookeeper-storage - spec: - accessModes: - - ReadWriteOnce - storageClassName: "zookeeper" - resources: - requests: - storage: 10G diff --git a/licenses-binary/LICENSE-azure-msal4j-persistence-extension.txt b/licenses-binary/LICENSE-azure-msal4j-persistence-extension.txt new file mode 100644 index 000000000000..d1ca00f20a89 --- /dev/null +++ b/licenses-binary/LICENSE-azure-msal4j-persistence-extension.txt @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE \ No newline at end of file diff --git a/licenses-binary/LICENSE-bouncycastle.txt b/licenses-binary/LICENSE-bouncycastle.txt index 10a3dd4a6e4b..a6bff8bd6d5d 100644 --- a/licenses-binary/LICENSE-bouncycastle.txt +++ b/licenses-binary/LICENSE-bouncycastle.txt @@ -1,4 +1,4 @@ -Copyright (c) 2000 - 2020 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) +Copyright (c) 2000 - 2023 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/licenses-binary/LICENSE-go.txt b/licenses-binary/LICENSE-go.txt new file mode 100644 index 000000000000..6a66aea5eafe --- /dev/null +++ b/licenses-binary/LICENSE-go.txt @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licenses-binary/LICENSE-jcabi-log.txt b/licenses-binary/LICENSE-jcabi-log.txt new file mode 100644 index 000000000000..2b53ab7c66c0 --- /dev/null +++ b/licenses-binary/LICENSE-jcabi-log.txt @@ -0,0 +1,27 @@ +Copyright (c) 2012-2022, jcabi.com +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: 1) Redistributions of source code must retain the above +copyright notice, this list of conditions and the following +disclaimer. 2) Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following +disclaimer in the documentation and/or other materials provided +with the distribution. 3) Neither the name of the jcabi.com nor +the names of its contributors may be used to endorse or promote +products derived from this software without specific prior written +permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT +NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/licenses-binary/LICENSE-jnr.txt b/licenses-binary/LICENSE-jnr.txt new file mode 100644 index 000000000000..40d516c11e66 --- /dev/null +++ b/licenses-binary/LICENSE-jnr.txt @@ -0,0 +1,23 @@ + Copyright (C) 2010 Wayne Meissner + Copyright (c) 2008-2009, Petr Kobalicek + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/licenses-binary/LICENSE-jsch.txt b/licenses-binary/LICENSE-jsch.txt new file mode 100644 index 000000000000..303096bf3529 --- /dev/null +++ b/licenses-binary/LICENSE-jsch.txt @@ -0,0 +1,30 @@ +JSch 0.0.* was released under the GNU LGPL license. Later, we have switched +over to a BSD-style license. + +------------------------------------------------------------------------------ +Copyright (c) 2002-2015 Atsuhiko Yamanaka, JCraft,Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the distribution. + + 3. The names of the authors may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT, +INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, +EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pinot-broker/pom.xml b/pinot-broker/pom.xml index c90e920d0a7d..161efef94e3c 100644 --- a/pinot-broker/pom.xml +++ b/pinot-broker/pom.xml @@ -19,13 +19,12 @@ under the License. --> - + 4.0.0 pinot org.apache.pinot - 0.13.0-SNAPSHOT + 1.2.0 .. pinot-broker @@ -34,24 +33,6 @@ ${basedir}/.. - - - - maven-jar-plugin - - - - test-jar - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - - - org.apache.pinot @@ -66,41 +47,6 @@ pinot-query-runtime - - - org.glassfish.jersey.containers - jersey-container-grizzly2-http - - - org.glassfish.jersey.inject - jersey-hk2 - - - org.glassfish.jersey.media - jersey-media-json-jackson - - - io.swagger - swagger-jaxrs - - - javax.ws.rs - jsr311-api - - - - - io.swagger - swagger-jersey2-jaxrs - - - com.jcabi - jcabi-log - - - org.glassfish.hk2 - hk2-locator - com.fasterxml.jackson.core jackson-databind diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/api/AccessControl.java b/pinot-broker/src/main/java/org/apache/pinot/broker/api/AccessControl.java index c8e252ee2721..f18fcc7f4129 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/api/AccessControl.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/api/AccessControl.java @@ -18,33 +18,105 @@ */ package org.apache.pinot.broker.api; +import java.util.Set; import org.apache.pinot.common.request.BrokerRequest; +import org.apache.pinot.core.auth.FineGrainedAccessControl; import org.apache.pinot.spi.annotations.InterfaceAudience; import org.apache.pinot.spi.annotations.InterfaceStability; +import org.apache.pinot.spi.auth.AuthorizationResult; +import org.apache.pinot.spi.auth.BasicAuthorizationResultImpl; +import org.apache.pinot.spi.auth.TableAuthorizationResult; @InterfaceAudience.Public @InterfaceStability.Stable -public interface AccessControl { +public interface AccessControl extends FineGrainedAccessControl { /** * First-step access control when processing broker requests. Decides whether request is allowed to acquire resources * for further processing. Request may still be rejected at table-level later on. - * + * The default implementation is kept to have backward compatibility with the existing implementations * @param requesterIdentity requester identity * * @return {@code true} if authorized, {@code false} otherwise */ + @Deprecated default boolean hasAccess(RequesterIdentity requesterIdentity) { return true; } + /** + * First-step access control when processing broker requests. Decides whether request is allowed to acquire resources + * for further processing. Request may still be rejected at table-level later on. + * The default implementation returns a {@link BasicAuthorizationResultImpl} with the result of the hasAccess() of + * the implementation + * + * @param requesterIdentity requester identity + * + * @return {@code AuthorizationResult} with the result of the access control check + */ + default AuthorizationResult authorize(RequesterIdentity requesterIdentity) { + return new BasicAuthorizationResultImpl(hasAccess(requesterIdentity)); + } + /** * Fine-grained access control on parsed broker request. May check table, column, permissions, etc. + * The default implementation is kept to have backward compatibility with the existing implementations + * @param requesterIdentity requester identity + * @param brokerRequest broker request (incl query) + * + * @return {@code true} if authorized, {@code false} otherwise + */ + @Deprecated + default boolean hasAccess(RequesterIdentity requesterIdentity, BrokerRequest brokerRequest) { + throw new UnsupportedOperationException( + "Both hasAccess() and authorize() are not implemented . Do implement authorize() method for new " + + "implementations."); + } + + /** + * Verify access control on parsed broker request. May check table, column, permissions, etc. + * The default implementation returns a {@link BasicAuthorizationResultImpl} with the result of the hasAccess() of + * the implementation * * @param requesterIdentity requester identity * @param brokerRequest broker request (incl query) * + * @return {@code AuthorizationResult} with the result of the access control check + */ + default AuthorizationResult authorize(RequesterIdentity requesterIdentity, BrokerRequest brokerRequest) { + return new BasicAuthorizationResultImpl(hasAccess(requesterIdentity, brokerRequest)); + } + + /** + * Fine-grained access control on pinot tables. + * The default implementation is kept to have backward compatibility with the existing implementations + * + * @param requesterIdentity requester identity + * @param tables Set of pinot tables used in the query. Table name can be with or without tableType. + * * @return {@code true} if authorized, {@code false} otherwise */ - boolean hasAccess(RequesterIdentity requesterIdentity, BrokerRequest brokerRequest); + @Deprecated + default boolean hasAccess(RequesterIdentity requesterIdentity, Set tables) { + throw new UnsupportedOperationException( + "Both hasAccess() and authorize() are not implemented . Do implement authorize() method for new " + + "implementations."); + } + + /** + * Verify access control on pinot tables. + * The default implementation returns a {@link TableAuthorizationResult} with the result of the hasAccess() of the + * implementation + * + * @param requesterIdentity requester identity + * @param tables Set of pinot tables used in the query. Table name can be with or without tableType. + * + * @return {@code TableAuthorizationResult} with the result of the access control check + */ + default TableAuthorizationResult authorize(RequesterIdentity requesterIdentity, Set tables) { + // Taking all tables when hasAccess Failed , to not break existing implementations + // It will say all tables names failed AuthZ even only some failed AuthZ - which is same as just boolean output + return hasAccess(requesterIdentity, tables) ? TableAuthorizationResult.success() + : new TableAuthorizationResult(tables); + } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/BrokerCommonExceptionMapper.java b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/BrokerCommonExceptionMapper.java new file mode 100644 index 000000000000..9fee30848557 --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/BrokerCommonExceptionMapper.java @@ -0,0 +1,47 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.api.resources; + +import javax.inject.Singleton; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; +import org.apache.pinot.common.utils.SimpleHttpErrorInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +@Provider +@Singleton +public class BrokerCommonExceptionMapper implements ExceptionMapper { + private static final Logger LOGGER = LoggerFactory.getLogger(BrokerCommonExceptionMapper.class); + private static final int DEFAULT_STATUS = Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(); + + @Override + public Response toResponse(Throwable t) { + int status = DEFAULT_STATUS; + if (t instanceof WebApplicationException) { + status = ((WebApplicationException) t).getResponse().getStatus(); + } + SimpleHttpErrorInfo errorInfo = new SimpleHttpErrorInfo(status, t.getMessage()); + return Response.status(status).entity(errorInfo).type(MediaType.APPLICATION_JSON).build(); + } +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/InstanceResource.java b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/InstanceResource.java new file mode 100644 index 000000000000..750e523d5008 --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/InstanceResource.java @@ -0,0 +1,81 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.api.resources; + +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiKeyAuthDefinition; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import io.swagger.annotations.Authorization; +import io.swagger.annotations.SecurityDefinition; +import io.swagger.annotations.SwaggerDefinition; +import java.util.Collections; +import java.util.List; +import javax.inject.Inject; +import javax.inject.Named; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import org.apache.helix.HelixManager; +import org.apache.helix.model.InstanceConfig; +import org.apache.pinot.broker.broker.BrokerAdminApiApplication; +import org.apache.pinot.common.utils.helix.HelixHelper; +import org.apache.pinot.core.auth.Actions; +import org.apache.pinot.core.auth.Authorize; +import org.apache.pinot.core.auth.TargetType; + +import static org.apache.pinot.spi.utils.CommonConstants.SWAGGER_AUTHORIZATION_KEY; + + +/** + * This resource API can be used to retrieve instance level information like instance tags. + */ +@Api(tags = "Instance", authorizations = {@Authorization(value = SWAGGER_AUTHORIZATION_KEY)}) +@SwaggerDefinition(securityDefinition = @SecurityDefinition(apiKeyAuthDefinitions = @ApiKeyAuthDefinition(name = + HttpHeaders.AUTHORIZATION, in = ApiKeyAuthDefinition.ApiKeyLocation.HEADER, key = SWAGGER_AUTHORIZATION_KEY))) +@Path("/") +public class InstanceResource { + + @Inject + @Named(BrokerAdminApiApplication.BROKER_INSTANCE_ID) + private String _instanceId; + + @Inject + private HelixManager _helixManager; + + @GET + @Path("/instance/tags") + @Authorize(targetType = TargetType.CLUSTER, action = Actions.Cluster.GET_INSTANCE) + @ApiOperation(value = "Tenant tags for current instance") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Success"), + @ApiResponse(code = 500, message = "Internal server error") + }) + @Produces(MediaType.APPLICATION_JSON) + public List getInstanceTags() { + InstanceConfig config = HelixHelper.getInstanceConfig(_helixManager, _instanceId); + if (config != null && config.getTags() != null) { + return config.getTags(); + } + return Collections.emptyList(); + } +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerAppConfigs.java b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerAppConfigs.java index cc3eeec0f886..0dacae0412d0 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerAppConfigs.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerAppConfigs.java @@ -32,6 +32,9 @@ import javax.ws.rs.core.MediaType; import org.apache.pinot.broker.broker.BrokerAdminApiApplication; import org.apache.pinot.common.utils.PinotAppConfigs; +import org.apache.pinot.core.auth.Actions; +import org.apache.pinot.core.auth.Authorize; +import org.apache.pinot.core.auth.TargetType; import org.apache.pinot.spi.env.PinotConfiguration; import static org.apache.pinot.spi.utils.CommonConstants.SWAGGER_AUTHORIZATION_KEY; @@ -52,6 +55,7 @@ public class PinotBrokerAppConfigs { @GET @Path("/appconfigs") + @Authorize(targetType = TargetType.CLUSTER, action = Actions.Cluster.GET_APP_CONFIG) @Produces(MediaType.APPLICATION_JSON) public String getAppConfigs() { PinotConfiguration pinotConfiguration = diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerDebug.java b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerDebug.java index f9eb55b5352f..8f079ca351b2 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerDebug.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerDebug.java @@ -27,10 +27,12 @@ import io.swagger.annotations.Authorization; import io.swagger.annotations.SecurityDefinition; import io.swagger.annotations.SwaggerDefinition; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.BiConsumer; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; @@ -38,10 +40,19 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.pinot.broker.broker.AccessControlFactory; import org.apache.pinot.broker.routing.BrokerRoutingManager; +import org.apache.pinot.common.request.BrokerRequest; +import org.apache.pinot.common.utils.DatabaseUtils; +import org.apache.pinot.core.auth.Actions; +import org.apache.pinot.core.auth.Authorize; +import org.apache.pinot.core.auth.ManualAuthorization; +import org.apache.pinot.core.auth.TargetType; import org.apache.pinot.core.routing.RoutingTable; import org.apache.pinot.core.routing.TimeBoundaryInfo; import org.apache.pinot.core.transport.ServerInstance; @@ -50,12 +61,18 @@ import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.apache.pinot.sql.parsers.CalciteSqlCompiler; +import static org.apache.pinot.spi.utils.CommonConstants.DATABASE; import static org.apache.pinot.spi.utils.CommonConstants.SWAGGER_AUTHORIZATION_KEY; -@Api(tags = "Debug", authorizations = {@Authorization(value = SWAGGER_AUTHORIZATION_KEY)}) -@SwaggerDefinition(securityDefinition = @SecurityDefinition(apiKeyAuthDefinitions = @ApiKeyAuthDefinition(name = - HttpHeaders.AUTHORIZATION, in = ApiKeyAuthDefinition.ApiKeyLocation.HEADER, key = SWAGGER_AUTHORIZATION_KEY))) +@Api(tags = "Debug", authorizations = {@Authorization(value = SWAGGER_AUTHORIZATION_KEY), + @Authorization(value = DATABASE)}) +@SwaggerDefinition(securityDefinition = @SecurityDefinition(apiKeyAuthDefinitions = { + @ApiKeyAuthDefinition(name = HttpHeaders.AUTHORIZATION, in = ApiKeyAuthDefinition.ApiKeyLocation.HEADER, + key = SWAGGER_AUTHORIZATION_KEY), + @ApiKeyAuthDefinition(name = DATABASE, in = ApiKeyAuthDefinition.ApiKeyLocation.HEADER, key = DATABASE, + description = "Database context passed through http header. If no context is provided 'default' database " + + "context will be considered.")})) @Path("/") // TODO: Add APIs to return the RoutingTable (with unavailable segments) public class PinotBrokerDebug { @@ -69,9 +86,13 @@ public class PinotBrokerDebug { @Inject private ServerRoutingStatsManager _serverRoutingStatsManager; + @Inject + AccessControlFactory _accessControlFactory; + @GET @Produces(MediaType.APPLICATION_JSON) @Path("/debug/timeBoundary/{tableName}") + @Authorize(targetType = TargetType.TABLE, paramName = "tableName", action = Actions.Table.GET_TIME_BOUNDARY) @ApiOperation(value = "Get the time boundary information for a table") @ApiResponses(value = { @ApiResponse(code = 200, message = "Time boundary information for a table"), @@ -79,7 +100,9 @@ public class PinotBrokerDebug { @ApiResponse(code = 500, message = "Internal server error") }) public TimeBoundaryInfo getTimeBoundary( - @ApiParam(value = "Name of the table") @PathParam("tableName") String tableName) { + @ApiParam(value = "Name of the table") @PathParam("tableName") String tableName, + @Context HttpHeaders headers) { + tableName = DatabaseUtils.translateTableName(tableName, headers); String offlineTableName = TableNameBuilder.OFFLINE.tableNameWithType(TableNameBuilder.extractRawTableName(tableName)); TimeBoundaryInfo timeBoundaryInfo = _routingManager.getTimeBoundaryInfo(offlineTableName); @@ -93,6 +116,7 @@ public TimeBoundaryInfo getTimeBoundary( @GET @Produces(MediaType.APPLICATION_JSON) @Path("/debug/routingTable/{tableName}") + @Authorize(targetType = TargetType.TABLE, paramName = "tableName", action = Actions.Table.GET_ROUTING_TABLE) @ApiOperation(value = "Get the routing table for a table") @ApiResponses(value = { @ApiResponse(code = 200, message = "Routing table"), @@ -100,15 +124,51 @@ public TimeBoundaryInfo getTimeBoundary( @ApiResponse(code = 500, message = "Internal server error") }) public Map>> getRoutingTable( - @ApiParam(value = "Name of the table") @PathParam("tableName") String tableName) { + @ApiParam(value = "Name of the table") @PathParam("tableName") String tableName, + @Context HttpHeaders headers) { + tableName = DatabaseUtils.translateTableName(tableName, headers); Map>> result = new TreeMap<>(); + getRoutingTable(tableName, (tableNameWithType, routingTable) -> result.put(tableNameWithType, + removeOptionalSegments(routingTable.getServerInstanceToSegmentsMap()))); + if (!result.isEmpty()) { + return result; + } else { + throw new WebApplicationException("Cannot find routing for table: " + tableName, Response.Status.NOT_FOUND); + } + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/debug/routingTableWithOptionalSegments/{tableName}") + @Authorize(targetType = TargetType.TABLE, paramName = "tableName", action = Actions.Table.GET_ROUTING_TABLE) + @ApiOperation(value = "Get the routing table for a table, including optional segments") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Routing table"), + @ApiResponse(code = 404, message = "Routing not found"), + @ApiResponse(code = 500, message = "Internal server error") + }) + public Map, List>>> getRoutingTableWithOptionalSegments( + @ApiParam(value = "Name of the table") @PathParam("tableName") String tableName, + @Context HttpHeaders headers) { + tableName = DatabaseUtils.translateTableName(tableName, headers); + Map, List>>> result = new TreeMap<>(); + getRoutingTable(tableName, (tableNameWithType, routingTable) -> result.put(tableNameWithType, + routingTable.getServerInstanceToSegmentsMap())); + if (!result.isEmpty()) { + return result; + } else { + throw new WebApplicationException("Cannot find routing for table: " + tableName, Response.Status.NOT_FOUND); + } + } + + private void getRoutingTable(String tableName, BiConsumer consumer) { TableType tableType = TableNameBuilder.getTableTypeFromTableName(tableName); if (tableType != TableType.REALTIME) { String offlineTableName = TableNameBuilder.OFFLINE.tableNameWithType(tableName); RoutingTable routingTable = _routingManager.getRoutingTable( CalciteSqlCompiler.compileToBrokerRequest("SELECT * FROM " + offlineTableName), getRequestId()); if (routingTable != null) { - result.put(offlineTableName, routingTable.getServerInstanceToSegmentsMap()); + consumer.accept(offlineTableName, routingTable); } } if (tableType != TableType.OFFLINE) { @@ -116,19 +176,22 @@ public Map>> getRoutingTable( RoutingTable routingTable = _routingManager.getRoutingTable( CalciteSqlCompiler.compileToBrokerRequest("SELECT * FROM " + realtimeTableName), getRequestId()); if (routingTable != null) { - result.put(realtimeTableName, routingTable.getServerInstanceToSegmentsMap()); + consumer.accept(realtimeTableName, routingTable); } } - if (!result.isEmpty()) { - return result; - } else { - throw new WebApplicationException("Cannot find routing for table: " + tableName, Response.Status.NOT_FOUND); - } + } + + private static Map> removeOptionalSegments( + Map, List>> serverInstanceToSegmentsMap) { + Map> ret = new HashMap<>(); + serverInstanceToSegmentsMap.forEach((k, v) -> ret.put(k, v.getLeft())); + return ret; } @GET @Produces(MediaType.APPLICATION_JSON) @Path("/debug/routingTable/sql") + @ManualAuthorization @ApiOperation(value = "Get the routing table for a query") @ApiResponses(value = { @ApiResponse(code = 200, message = "Routing table"), @@ -136,9 +199,34 @@ public Map>> getRoutingTable( @ApiResponse(code = 500, message = "Internal server error") }) public Map> getRoutingTableForQuery( - @ApiParam(value = "SQL query (table name should have type suffix)") @QueryParam("query") String query) { - RoutingTable routingTable = _routingManager.getRoutingTable(CalciteSqlCompiler.compileToBrokerRequest(query), - getRequestId()); + @ApiParam(value = "SQL query (table name should have type suffix)") @QueryParam("query") String query, + @Context HttpHeaders httpHeaders) { + BrokerRequest brokerRequest = CalciteSqlCompiler.compileToBrokerRequest(query); + checkAccessControl(brokerRequest, httpHeaders); + RoutingTable routingTable = _routingManager.getRoutingTable(brokerRequest, getRequestId()); + if (routingTable != null) { + return removeOptionalSegments(routingTable.getServerInstanceToSegmentsMap()); + } else { + throw new WebApplicationException("Cannot find routing for query: " + query, Response.Status.NOT_FOUND); + } + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/debug/routingTableWithOptionalSegments/sql") + @ManualAuthorization + @ApiOperation(value = "Get the routing table for a query, including optional segments") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Routing table"), + @ApiResponse(code = 404, message = "Routing not found"), + @ApiResponse(code = 500, message = "Internal server error") + }) + public Map, List>> getRoutingTableForQueryWithOptionalSegments( + @ApiParam(value = "SQL query (table name should have type suffix)") @QueryParam("query") String query, + @Context HttpHeaders httpHeaders) { + BrokerRequest brokerRequest = CalciteSqlCompiler.compileToBrokerRequest(query); + checkAccessControl(brokerRequest, httpHeaders); + RoutingTable routingTable = _routingManager.getRoutingTable(brokerRequest, getRequestId()); if (routingTable != null) { return routingTable.getServerInstanceToSegmentsMap(); } else { @@ -146,6 +234,19 @@ public Map> getRoutingTableForQuery( } } + private void checkAccessControl(BrokerRequest brokerRequest, HttpHeaders httpHeaders) { + // TODO: Handle nested queries + if (brokerRequest.isSetQuerySource() && brokerRequest.getQuerySource().isSetTableName()) { + if (!_accessControlFactory.create() + .hasAccess(httpHeaders, TargetType.TABLE, brokerRequest.getQuerySource().getTableName(), + Actions.Table.GET_ROUTING_TABLE)) { + throw new WebApplicationException("Permission denied", Response.Status.FORBIDDEN); + } + } else { + throw new WebApplicationException("Table name is not set in the query", Response.Status.BAD_REQUEST); + } + } + /** * API to get a snapshot of ServerRoutingStatsEntry for all the servers. * @return String containing server name and the associated routing stats. @@ -153,6 +254,7 @@ public Map> getRoutingTableForQuery( @GET @Produces(MediaType.APPLICATION_JSON) @Path("/debug/serverRoutingStats") + @Authorize(targetType = TargetType.CLUSTER, action = Actions.Cluster.GET_SERVER_ROUTING_STATS) @ApiOperation(value = "Get the routing stats for all the servers") @ApiResponses(value = { @ApiResponse(code = 200, message = "Server routing Stats"), diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerHealthCheck.java b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerHealthCheck.java index ac0c16b7a008..03069f45361a 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerHealthCheck.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerHealthCheck.java @@ -26,6 +26,9 @@ import io.swagger.annotations.Authorization; import io.swagger.annotations.SecurityDefinition; import io.swagger.annotations.SwaggerDefinition; +import java.time.Duration; +import java.time.Instant; +import java.time.format.DateTimeFormatter; import javax.inject.Inject; import javax.inject.Named; import javax.ws.rs.GET; @@ -39,6 +42,9 @@ import org.apache.pinot.common.metrics.BrokerMeter; import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.utils.ServiceStatus; +import org.apache.pinot.core.auth.Actions; +import org.apache.pinot.core.auth.Authorize; +import org.apache.pinot.core.auth.TargetType; import static org.apache.pinot.spi.utils.CommonConstants.SWAGGER_AUTHORIZATION_KEY; @@ -55,9 +61,14 @@ public class PinotBrokerHealthCheck { @Inject private BrokerMetrics _brokerMetrics; + @Inject + @Named(BrokerAdminApiApplication.START_TIME) + private Instant _startTime; + @GET @Produces(MediaType.TEXT_PLAIN) @Path("health") + @Authorize(targetType = TargetType.CLUSTER, action = Actions.Cluster.GET_HEALTH) @ApiOperation(value = "Checking broker health") @ApiResponses(value = { @ApiResponse(code = 200, message = "Broker is healthy"), @@ -75,4 +86,28 @@ public String getBrokerHealth() { Response.status(Response.Status.SERVICE_UNAVAILABLE).entity(errMessage).build(); throw new WebApplicationException(errMessage, response); } + + @GET + @Produces(MediaType.TEXT_PLAIN) + @Path("uptime") + @Authorize(targetType = TargetType.CLUSTER, action = Actions.Cluster.GET_HEALTH) + @ApiOperation(value = "Get broker uptime") + public long getUptime() { + if (_startTime == null) { + return 0; + } + Instant now = Instant.now(); + Duration uptime = Duration.between(_startTime, now); + return uptime.getSeconds(); + } + + @GET + @Path("start-time") + @Authorize(targetType = TargetType.CLUSTER, action = Actions.Cluster.GET_HEALTH) + @ApiOperation(value = "Get broker start time") + @Produces(MediaType.TEXT_PLAIN) + public String getStartTime() { + DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT; + return _startTime != null ? formatter.format(_startTime) : ""; + } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerLogger.java b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerLogger.java index 849807f42d6f..593e05d8daca 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerLogger.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerLogger.java @@ -40,8 +40,12 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import org.apache.pinot.common.utils.LoggerFileServer; import org.apache.pinot.common.utils.LoggerUtils; +import org.apache.pinot.common.utils.log.DummyLogFileServer; +import org.apache.pinot.common.utils.log.LogFileServer; +import org.apache.pinot.core.auth.Actions; +import org.apache.pinot.core.auth.Authorize; +import org.apache.pinot.core.auth.TargetType; import static org.apache.pinot.spi.utils.CommonConstants.SWAGGER_AUTHORIZATION_KEY; @@ -56,18 +60,20 @@ public class PinotBrokerLogger { @Inject - private LoggerFileServer _loggerFileServer; + private LogFileServer _logFileServer; @GET @Path("/loggers") + @Authorize(targetType = TargetType.CLUSTER, action = Actions.Cluster.GET_LOGGER) @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Get all the loggers", notes = "Return all the logger names") public List getLoggers() { - return LoggerUtils.getAllLoggers(); + return LoggerUtils.getAllConfiguredLoggers(); } @GET @Path("/loggers/{loggerName}") + @Authorize(targetType = TargetType.CLUSTER, action = Actions.Cluster.GET_LOGGER) @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Get logger configs", notes = "Return logger info") public Map getLogger( @@ -81,6 +87,7 @@ public Map getLogger( @PUT @Path("/loggers/{loggerName}") + @Authorize(targetType = TargetType.CLUSTER, action = Actions.Cluster.UPDATE_LOGGER) @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Set logger level", notes = "Set logger level for a given logger") public Map setLoggerLevel(@ApiParam(value = "Logger name") @PathParam("loggerName") String loggerName, @@ -90,14 +97,15 @@ public Map setLoggerLevel(@ApiParam(value = "Logger name") @Path @GET @Path("/loggers/files") + @Authorize(targetType = TargetType.CLUSTER, action = Actions.Cluster.GET_LOG_FILE) @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Get all local log files") public Set getLocalLogFiles() { try { - if (_loggerFileServer == null) { + if (_logFileServer == null || _logFileServer instanceof DummyLogFileServer) { throw new WebApplicationException("Root log directory doesn't exist", Response.Status.INTERNAL_SERVER_ERROR); } - return _loggerFileServer.getAllPaths(); + return _logFileServer.getAllLogFilePaths(); } catch (IOException e) { throw new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR); } @@ -105,14 +113,15 @@ public Set getLocalLogFiles() { @GET @Path("/loggers/download") + @Authorize(targetType = TargetType.CLUSTER, action = Actions.Cluster.GET_LOG_FILE) @Produces(MediaType.APPLICATION_OCTET_STREAM) @ApiOperation(value = "Download a log file") public Response downloadLogFile( @ApiParam(value = "Log file path", required = true) @QueryParam("filePath") String filePath) { - if (_loggerFileServer == null) { + if (_logFileServer == null || _logFileServer instanceof DummyLogFileServer) { throw new WebApplicationException("Root log directory is not configured", Response.Status.INTERNAL_SERVER_ERROR); } - return _loggerFileServer.downloadLogFile(filePath); + return _logFileServer.downloadLogFile(filePath); } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerRouting.java b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerRouting.java index e5179b82ea70..99b978d1165f 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerRouting.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotBrokerRouting.java @@ -33,16 +33,27 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import org.apache.pinot.broker.routing.BrokerRoutingManager; +import org.apache.pinot.common.utils.DatabaseUtils; +import org.apache.pinot.core.auth.Actions; +import org.apache.pinot.core.auth.Authorize; +import org.apache.pinot.core.auth.TargetType; +import static org.apache.pinot.spi.utils.CommonConstants.DATABASE; import static org.apache.pinot.spi.utils.CommonConstants.SWAGGER_AUTHORIZATION_KEY; -@Api(tags = "Routing", authorizations = {@Authorization(value = SWAGGER_AUTHORIZATION_KEY)}) -@SwaggerDefinition(securityDefinition = @SecurityDefinition(apiKeyAuthDefinitions = @ApiKeyAuthDefinition(name = - HttpHeaders.AUTHORIZATION, in = ApiKeyAuthDefinition.ApiKeyLocation.HEADER, key = SWAGGER_AUTHORIZATION_KEY))) +@Api(tags = "Routing", authorizations = {@Authorization(value = SWAGGER_AUTHORIZATION_KEY), + @Authorization(value = DATABASE)}) +@SwaggerDefinition(securityDefinition = @SecurityDefinition(apiKeyAuthDefinitions = { + @ApiKeyAuthDefinition(name = HttpHeaders.AUTHORIZATION, in = ApiKeyAuthDefinition.ApiKeyLocation.HEADER, + key = SWAGGER_AUTHORIZATION_KEY), + @ApiKeyAuthDefinition(name = DATABASE, in = ApiKeyAuthDefinition.ApiKeyLocation.HEADER, key = DATABASE, + description = "Database context passed through http header. If no context is provided 'default' database " + + "context will be considered.")})) @Path("/") public class PinotBrokerRouting { @@ -52,20 +63,23 @@ public class PinotBrokerRouting { @PUT @Produces(MediaType.TEXT_PLAIN) @Path("/routing/{tableName}") + @Authorize(targetType = TargetType.TABLE, paramName = "tableName", action = Actions.Table.BUILD_ROUTING) @ApiOperation(value = "Build/rebuild the routing for a table", notes = "Build/rebuild the routing for a table") @ApiResponses(value = { @ApiResponse(code = 200, message = "Success"), @ApiResponse(code = 500, message = "Internal server error") }) public String buildRouting( - @ApiParam(value = "Table name (with type)") @PathParam("tableName") String tableNameWithType) { - _routingManager.buildRouting(tableNameWithType); + @ApiParam(value = "Table name (with type)") @PathParam("tableName") String tableNameWithType, + @Context HttpHeaders headers) { + _routingManager.buildRouting(DatabaseUtils.translateTableName(tableNameWithType, headers)); return "Success"; } @PUT @Produces(MediaType.TEXT_PLAIN) @Path("/routing/refresh/{tableName}/{segmentName}") + @Authorize(targetType = TargetType.TABLE, paramName = "tableName", action = Actions.Table.REFRESH_ROUTING) @ApiOperation(value = "Refresh the routing for a segment", notes = "Refresh the routing for a segment") @ApiResponses(value = { @ApiResponse(code = 200, message = "Success"), @@ -73,22 +87,25 @@ public String buildRouting( }) public String refreshRouting( @ApiParam(value = "Table name (with type)") @PathParam("tableName") String tableNameWithType, - @ApiParam(value = "Segment name") @PathParam("segmentName") String segmentName) { - _routingManager.refreshSegment(tableNameWithType, segmentName); + @ApiParam(value = "Segment name") @PathParam("segmentName") String segmentName, + @Context HttpHeaders headers) { + _routingManager.refreshSegment(DatabaseUtils.translateTableName(tableNameWithType, headers), segmentName); return "Success"; } @DELETE @Produces(MediaType.TEXT_PLAIN) @Path("/routing/{tableName}") + @Authorize(targetType = TargetType.TABLE, paramName = "tableName", action = Actions.Table.DELETE_ROUTING) @ApiOperation(value = "Remove the routing for a table", notes = "Remove the routing for a table") @ApiResponses(value = { @ApiResponse(code = 200, message = "Success"), @ApiResponse(code = 500, message = "Internal server error") }) public String removeRouting( - @ApiParam(value = "Table name (with type)") @PathParam("tableName") String tableNameWithType) { - _routingManager.removeRouting(tableNameWithType); + @ApiParam(value = "Table name (with type)") @PathParam("tableName") String tableNameWithType, + @Context HttpHeaders headers) { + _routingManager.removeRouting(DatabaseUtils.translateTableName(tableNameWithType, headers)); return "Success"; } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotClientRequest.java b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotClientRequest.java index 30c8cb6d7b85..ccd26688bcb3 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotClientRequest.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/api/resources/PinotClientRequest.java @@ -20,7 +20,9 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.Multimap; import io.swagger.annotations.Api; import io.swagger.annotations.ApiKeyAuthDefinition; @@ -32,6 +34,7 @@ import io.swagger.annotations.SecurityDefinition; import io.swagger.annotations.SwaggerDefinition; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.Executor; import javax.inject.Inject; @@ -50,7 +53,8 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import org.apache.commons.httpclient.HttpConnectionManager; +import javax.ws.rs.core.StreamingOutput; +import org.apache.http.conn.HttpClientConnectionManager; import org.apache.pinot.broker.api.HttpRequesterIdentity; import org.apache.pinot.broker.requesthandler.BrokerRequestHandler; import org.apache.pinot.common.exception.QueryException; @@ -58,8 +62,12 @@ import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.response.BrokerResponse; import org.apache.pinot.common.response.broker.BrokerResponseNative; +import org.apache.pinot.common.response.broker.QueryProcessingException; import org.apache.pinot.common.utils.request.RequestUtils; +import org.apache.pinot.core.auth.Actions; +import org.apache.pinot.core.auth.Authorize; import org.apache.pinot.core.auth.ManualAuthorization; +import org.apache.pinot.core.auth.TargetType; import org.apache.pinot.core.query.executor.sql.SqlQueryExecutor; import org.apache.pinot.spi.trace.RequestScope; import org.apache.pinot.spi.trace.Tracing; @@ -71,6 +79,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.apache.pinot.spi.utils.CommonConstants.Controller.PINOT_QUERY_ERROR_CODE_HEADER; import static org.apache.pinot.spi.utils.CommonConstants.SWAGGER_AUTHORIZATION_KEY; @@ -94,7 +103,7 @@ public class PinotClientRequest { private Executor _executor; @Inject - private HttpConnectionManager _httpConnMgr; + private HttpClientConnectionManager _httpConnMgr; @GET @ManagedAsync @@ -108,19 +117,18 @@ public class PinotClientRequest { @ManualAuthorization public void processSqlQueryGet(@ApiParam(value = "Query", required = true) @QueryParam("sql") String query, @ApiParam(value = "Trace enabled") @QueryParam(Request.TRACE) String traceEnabled, - @ApiParam(value = "Debug options") @QueryParam(Request.DEBUG_OPTIONS) String debugOptions, - @Suspended AsyncResponse asyncResponse, @Context org.glassfish.grizzly.http.server.Request requestContext) { + @Suspended AsyncResponse asyncResponse, @Context org.glassfish.grizzly.http.server.Request requestContext, + @Context HttpHeaders httpHeaders) { try { ObjectNode requestJson = JsonUtils.newObjectNode(); requestJson.put(Request.SQL, query); if (traceEnabled != null) { requestJson.put(Request.TRACE, traceEnabled); } - if (debugOptions != null) { - requestJson.put(Request.DEBUG_OPTIONS, debugOptions); - } - BrokerResponse brokerResponse = executeSqlQuery(requestJson, makeHttpIdentity(requestContext), true); - asyncResponse.resume(brokerResponse.toJsonString()); + BrokerResponse brokerResponse = executeSqlQuery(requestJson, makeHttpIdentity(requestContext), true, httpHeaders); + asyncResponse.resume(getPinotQueryResponse(brokerResponse)); + } catch (WebApplicationException wae) { + asyncResponse.resume(wae); } catch (Exception e) { LOGGER.error("Caught exception while processing GET request", e); _brokerMetrics.addMeteredGlobalValue(BrokerMeter.UNCAUGHT_GET_EXCEPTIONS, 1L); @@ -139,15 +147,82 @@ public void processSqlQueryGet(@ApiParam(value = "Query", required = true) @Quer }) @ManualAuthorization public void processSqlQueryPost(String query, @Suspended AsyncResponse asyncResponse, - @Context org.glassfish.grizzly.http.server.Request requestContext) { + @Context org.glassfish.grizzly.http.server.Request requestContext, + @Context HttpHeaders httpHeaders) { try { JsonNode requestJson = JsonUtils.stringToJsonNode(query); if (!requestJson.has(Request.SQL)) { throw new IllegalStateException("Payload is missing the query string field 'sql'"); } BrokerResponse brokerResponse = - executeSqlQuery((ObjectNode) requestJson, makeHttpIdentity(requestContext), false); - asyncResponse.resume(brokerResponse.toJsonString()); + executeSqlQuery((ObjectNode) requestJson, makeHttpIdentity(requestContext), false, httpHeaders); + asyncResponse.resume(getPinotQueryResponse(brokerResponse)); + } catch (WebApplicationException wae) { + asyncResponse.resume(wae); + } catch (Exception e) { + LOGGER.error("Caught exception while processing POST request", e); + _brokerMetrics.addMeteredGlobalValue(BrokerMeter.UNCAUGHT_POST_EXCEPTIONS, 1L); + asyncResponse.resume( + new WebApplicationException(e, + Response + .status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(e.getMessage()) + .build())); + } + } + + @GET + @ManagedAsync + @Produces(MediaType.APPLICATION_JSON) + @Path("query") + @ApiOperation(value = "Querying pinot using MultiStage Query Engine") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Query response"), + @ApiResponse(code = 500, message = "Internal Server Error") + }) + @ManualAuthorization + public void processSqlWithMultiStageQueryEngineGet( + @ApiParam(value = "Query", required = true) @QueryParam("sql") String query, + @Suspended AsyncResponse asyncResponse, @Context org.glassfish.grizzly.http.server.Request requestContext, + @Context HttpHeaders httpHeaders) { + try { + ObjectNode requestJson = JsonUtils.newObjectNode(); + requestJson.put(Request.SQL, query); + BrokerResponse brokerResponse = + executeSqlQuery(requestJson, makeHttpIdentity(requestContext), true, httpHeaders, true); + asyncResponse.resume(getPinotQueryResponse(brokerResponse)); + } catch (WebApplicationException wae) { + asyncResponse.resume(wae); + } catch (Exception e) { + LOGGER.error("Caught exception while processing GET request", e); + _brokerMetrics.addMeteredGlobalValue(BrokerMeter.UNCAUGHT_GET_EXCEPTIONS, 1L); + asyncResponse.resume(new WebApplicationException(e, Response.Status.INTERNAL_SERVER_ERROR)); + } + } + + @POST + @ManagedAsync + @Produces(MediaType.APPLICATION_JSON) + @Path("query") + @ApiOperation(value = "Querying pinot using MultiStage Query Engine") + @ApiResponses(value = { + @ApiResponse(code = 200, message = "Query response"), + @ApiResponse(code = 500, message = "Internal Server Error") + }) + @ManualAuthorization + public void processSqlWithMultiStageQueryEnginePost(String query, @Suspended AsyncResponse asyncResponse, + @Context org.glassfish.grizzly.http.server.Request requestContext, + @Context HttpHeaders httpHeaders) { + try { + JsonNode requestJson = JsonUtils.stringToJsonNode(query); + if (!requestJson.has(Request.SQL)) { + throw new IllegalStateException("Payload is missing the query string field 'sql'"); + } + BrokerResponse brokerResponse = + executeSqlQuery((ObjectNode) requestJson, makeHttpIdentity(requestContext), false, httpHeaders, true); + asyncResponse.resume(getPinotQueryResponse(brokerResponse)); + } catch (WebApplicationException wae) { + asyncResponse.resume(wae); } catch (Exception e) { LOGGER.error("Caught exception while processing POST request", e); _brokerMetrics.addMeteredGlobalValue(BrokerMeter.UNCAUGHT_POST_EXCEPTIONS, 1L); @@ -162,6 +237,7 @@ public void processSqlQueryPost(String query, @Suspended AsyncResponse asyncResp @DELETE @Path("query/{queryId}") + @Authorize(targetType = TargetType.CLUSTER, action = Actions.Cluster.CANCEL_QUERY) @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Cancel a query as identified by the queryId", notes = "No effect if no query exists for the " + "given queryId on the requested broker. Query may continue to run for a short while after calling cancel as " @@ -175,7 +251,7 @@ public String cancelQuery( @ApiParam(value = "Timeout for servers to respond the cancel request") @QueryParam("timeoutMs") @DefaultValue("3000") int timeoutMs, @ApiParam(value = "Return server responses for troubleshooting") @QueryParam("verbose") @DefaultValue("false") - boolean verbose) { + boolean verbose) { try { Map serverResponses = verbose ? new HashMap<>() : null; if (_requestHandler.cancelQuery(queryId, timeoutMs, _executor, _httpConnMgr, serverResponses)) { @@ -197,6 +273,7 @@ public String cancelQuery( @GET @Path("queries") + @Authorize(targetType = TargetType.CLUSTER, action = Actions.Cluster.GET_RUNNING_QUERY) @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "Get running queries submitted via the requested broker", notes = "The id is assigned by the " + "requested broker and only unique at the scope of this broker") @@ -213,14 +290,24 @@ public Map getRunningQueries() { } private BrokerResponse executeSqlQuery(ObjectNode sqlRequestJson, HttpRequesterIdentity httpRequesterIdentity, - boolean onlyDql) + boolean onlyDql, HttpHeaders httpHeaders) + throws Exception { + return executeSqlQuery(sqlRequestJson, httpRequesterIdentity, onlyDql, httpHeaders, false); + } + + private BrokerResponse executeSqlQuery(ObjectNode sqlRequestJson, HttpRequesterIdentity httpRequesterIdentity, + boolean onlyDql, HttpHeaders httpHeaders, boolean forceUseMultiStage) throws Exception { + long requestArrivalTimeMs = System.currentTimeMillis(); SqlNodeAndOptions sqlNodeAndOptions; try { sqlNodeAndOptions = RequestUtils.parseQuery(sqlRequestJson.get(Request.SQL).asText(), sqlRequestJson); } catch (Exception e) { return new BrokerResponseNative(QueryException.getException(QueryException.SQL_PARSING_ERROR, e)); } + if (forceUseMultiStage) { + sqlNodeAndOptions.setExtraOptions(ImmutableMap.of(Request.QueryOptionKey.USE_MULTISTAGE_ENGINE, "true")); + } PinotSqlType sqlType = sqlNodeAndOptions.getSqlType(); if (onlyDql && sqlType != PinotSqlType.DQL) { return new BrokerResponseNative(QueryException.getException(QueryException.SQL_PARSING_ERROR, @@ -228,15 +315,26 @@ private BrokerResponse executeSqlQuery(ObjectNode sqlRequestJson, HttpRequesterI } switch (sqlType) { case DQL: - try (RequestScope requestStatistics = Tracing.getTracer().createRequestScope()) { - return _requestHandler.handleRequest(sqlRequestJson, sqlNodeAndOptions, httpRequesterIdentity, - requestStatistics); + try (RequestScope requestContext = Tracing.getTracer().createRequestScope()) { + requestContext.setRequestArrivalTimeMillis(requestArrivalTimeMs); + return _requestHandler.handleRequest(sqlRequestJson, sqlNodeAndOptions, httpRequesterIdentity, requestContext, + httpHeaders); + } catch (Exception e) { + LOGGER.error("Error handling DQL request:\n{}\nException: {}", sqlRequestJson, + QueryException.getTruncatedStackTrace(e)); + throw e; } case DML: - Map headers = new HashMap<>(); - httpRequesterIdentity.getHttpHeaders().entries() - .forEach(entry -> headers.put(entry.getKey(), entry.getValue())); - return _sqlQueryExecutor.executeDMLStatement(sqlNodeAndOptions, headers); + try { + Map headers = new HashMap<>(); + httpRequesterIdentity.getHttpHeaders().entries() + .forEach(entry -> headers.put(entry.getKey(), entry.getValue())); + return _sqlQueryExecutor.executeDMLStatement(sqlNodeAndOptions, headers); + } catch (Exception e) { + LOGGER.error("Error handling DML request:\n{}\nException: {}", sqlRequestJson, + QueryException.getTruncatedStackTrace(e)); + throw e; + } default: return new BrokerResponseNative(QueryException.getException(QueryException.SQL_PARSING_ERROR, new UnsupportedOperationException("Unsupported SQL type - " + sqlType))); @@ -253,4 +351,31 @@ private static HttpRequesterIdentity makeHttpIdentity(org.glassfish.grizzly.http return identity; } + + /** + * Generate Response object from the BrokerResponse object with 'X-Pinot-Error-Code' header value + * + * If the query is successful the 'X-Pinot-Error-Code' header value is set to -1 + * otherwise, the first error code of the broker response exception array will become the header value + * + * @param brokerResponse + * @return Response + * @throws Exception + */ + @VisibleForTesting + static Response getPinotQueryResponse(BrokerResponse brokerResponse) + throws Exception { + int queryErrorCodeHeaderValue = -1; // default value of the header. + List exceptions = brokerResponse.getExceptions(); + if (!exceptions.isEmpty()) { + // set the header value as first exception error code value. + queryErrorCodeHeaderValue = exceptions.get(0).getErrorCode(); + } + + // returning the Response with OK status and header value. + return Response.ok() + .header(PINOT_QUERY_ERROR_CODE_HEADER, queryErrorCodeHeaderValue) + .entity((StreamingOutput) brokerResponse::toOutputStream).type(MediaType.APPLICATION_JSON) + .build(); + } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/AllowAllAccessControlFactory.java b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/AllowAllAccessControlFactory.java index e5d96a424fe7..d0eb2ed65c1c 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/AllowAllAccessControlFactory.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/AllowAllAccessControlFactory.java @@ -18,9 +18,13 @@ */ package org.apache.pinot.broker.broker; +import java.util.Set; import org.apache.pinot.broker.api.AccessControl; import org.apache.pinot.broker.api.RequesterIdentity; import org.apache.pinot.common.request.BrokerRequest; +import org.apache.pinot.spi.auth.AuthorizationResult; +import org.apache.pinot.spi.auth.BasicAuthorizationResultImpl; +import org.apache.pinot.spi.auth.TableAuthorizationResult; import org.apache.pinot.spi.env.PinotConfiguration; @@ -40,8 +44,13 @@ public AccessControl create() { private static class AllowAllAccessControl implements AccessControl { @Override - public boolean hasAccess(RequesterIdentity requesterIdentity, BrokerRequest brokerRequest) { - return true; + public AuthorizationResult authorize(RequesterIdentity requesterIdentity, BrokerRequest brokerRequest) { + return BasicAuthorizationResultImpl.success(); + } + + @Override + public TableAuthorizationResult authorize(RequesterIdentity requesterIdentity, Set tables) { + return TableAuthorizationResult.success(); } } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/AuthenticationFilter.java b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/AuthenticationFilter.java index 644f449742f6..87bd5578fdb6 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/AuthenticationFilter.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/AuthenticationFilter.java @@ -34,9 +34,12 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; +import org.apache.commons.lang3.StringUtils; import org.apache.pinot.broker.api.AccessControl; import org.apache.pinot.broker.api.HttpRequesterIdentity; +import org.apache.pinot.core.auth.FineGrainedAuthUtils; import org.apache.pinot.core.auth.ManualAuthorization; +import org.apache.pinot.spi.auth.AuthorizationResult; import org.glassfish.grizzly.http.server.Request; /** @@ -81,10 +84,19 @@ public void filter(ContainerRequestContext requestContext) HttpRequesterIdentity httpRequestIdentity = HttpRequesterIdentity.fromRequest(request); - if (!accessControl.hasAccess(httpRequestIdentity)) { - throw new WebApplicationException("Failed access check for " + httpRequestIdentity.getEndpointUrl(), + AuthorizationResult authorizationResult = accessControl.authorize(httpRequestIdentity); + + if (!authorizationResult.hasAccess()) { + String failureMessage = authorizationResult.getFailureMessage(); + if (StringUtils.isNotBlank(failureMessage)) { + failureMessage = "Reason: " + failureMessage; + } + throw new WebApplicationException( + "Failed access check for " + httpRequestIdentity.getEndpointUrl() + "." + failureMessage, Response.Status.FORBIDDEN); } + + FineGrainedAuthUtils.validateFineGrainedAuth(endpointMethod, uriInfo, _httpHeaders, accessControl); } private static boolean isBaseFile(String path) { diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BasicAuthAccessControlFactory.java b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BasicAuthAccessControlFactory.java index 91ae183e8c27..18c1932a521e 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BasicAuthAccessControlFactory.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BasicAuthAccessControlFactory.java @@ -20,16 +20,21 @@ import com.google.common.base.Preconditions; import java.util.Collection; +import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; +import javax.ws.rs.NotAuthorizedException; import org.apache.pinot.broker.api.AccessControl; import org.apache.pinot.broker.api.HttpRequesterIdentity; import org.apache.pinot.broker.api.RequesterIdentity; import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.core.auth.BasicAuthPrincipal; import org.apache.pinot.core.auth.BasicAuthUtils; +import org.apache.pinot.spi.auth.AuthorizationResult; +import org.apache.pinot.spi.auth.TableAuthorizationResult; import org.apache.pinot.spi.env.PinotConfiguration; @@ -76,33 +81,70 @@ public BasicAuthAccessControl(Collection principals) { } @Override - public boolean hasAccess(RequesterIdentity requesterIdentity) { - return hasAccess(requesterIdentity, null); + public AuthorizationResult authorize(RequesterIdentity requesterIdentity) { + return authorize(requesterIdentity, (BrokerRequest) null); } @Override - public boolean hasAccess(RequesterIdentity requesterIdentity, BrokerRequest brokerRequest) { - Preconditions.checkArgument(requesterIdentity instanceof HttpRequesterIdentity, "HttpRequesterIdentity required"); - HttpRequesterIdentity identity = (HttpRequesterIdentity) requesterIdentity; - - Collection tokens = identity.getHttpHeaders().get(HEADER_AUTHORIZATION); - Optional principalOpt = - tokens.stream().map(BasicAuthUtils::normalizeBase64Token).map(_token2principal::get).filter(Objects::nonNull) - .findFirst(); + public AuthorizationResult authorize(RequesterIdentity requesterIdentity, BrokerRequest brokerRequest) { + Optional principalOpt = getPrincipalOpt(requesterIdentity); if (!principalOpt.isPresent()) { - // no matching token? reject - return false; + throw new NotAuthorizedException("Basic"); } BasicAuthPrincipal principal = principalOpt.get(); if (brokerRequest == null || !brokerRequest.isSetQuerySource() || !brokerRequest.getQuerySource() .isSetTableName()) { // no table restrictions? accept - return true; + return TableAuthorizationResult.success(); + } + + Set failedTables = new HashSet<>(); + + if (!principal.hasTable(brokerRequest.getQuerySource().getTableName())) { + failedTables.add(brokerRequest.getQuerySource().getTableName()); + } + if (failedTables.isEmpty()) { + return TableAuthorizationResult.success(); + } + return new TableAuthorizationResult(failedTables); + } + + @Override + public TableAuthorizationResult authorize(RequesterIdentity requesterIdentity, Set tables) { + Optional principalOpt = getPrincipalOpt(requesterIdentity); + + if (!principalOpt.isPresent()) { + throw new NotAuthorizedException("Basic"); + } + + if (tables == null || tables.isEmpty()) { + return TableAuthorizationResult.success(); + } + BasicAuthPrincipal principal = principalOpt.get(); + Set failedTables = new HashSet<>(); + for (String table : tables) { + if (!principal.hasTable(table)) { + failedTables.add(table); + } + } + if (failedTables.isEmpty()) { + return TableAuthorizationResult.success(); } + return new TableAuthorizationResult(failedTables); + } + + private Optional getPrincipalOpt(RequesterIdentity requesterIdentity) { + Preconditions.checkArgument(requesterIdentity instanceof HttpRequesterIdentity, "HttpRequesterIdentity required"); + HttpRequesterIdentity identity = (HttpRequesterIdentity) requesterIdentity; - return principal.hasTable(brokerRequest.getQuerySource().getTableName()); + Collection tokens = identity.getHttpHeaders().get(HEADER_AUTHORIZATION); + Optional principalOpt = + tokens.stream().map(org.apache.pinot.common.auth.BasicAuthUtils::normalizeBase64Token) + .map(_token2principal::get).filter(Objects::nonNull) + .findFirst(); + return principalOpt; } } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BrokerAdminApiApplication.java b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BrokerAdminApiApplication.java index 607ed4ab6d2d..b204dcd4350e 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BrokerAdminApiApplication.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BrokerAdminApiApplication.java @@ -19,20 +19,25 @@ package org.apache.pinot.broker.broker; import com.google.common.util.concurrent.ThreadFactoryBuilder; -import io.swagger.jaxrs.config.BeanConfig; +import io.swagger.jaxrs.listing.SwaggerSerializers; import java.io.IOException; -import java.net.URL; -import java.net.URLClassLoader; +import java.time.Instant; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import org.apache.commons.httpclient.HttpConnectionManager; -import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; +import org.apache.helix.HelixManager; +import org.apache.http.config.SocketConfig; +import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.pinot.broker.requesthandler.BrokerRequestHandler; import org.apache.pinot.broker.routing.BrokerRoutingManager; import org.apache.pinot.common.metrics.BrokerMetrics; -import org.apache.pinot.common.utils.LoggerFileServer; +import org.apache.pinot.common.swagger.SwaggerApiListingResource; +import org.apache.pinot.common.swagger.SwaggerSetupUtils; +import org.apache.pinot.common.utils.log.DummyLogFileServer; +import org.apache.pinot.common.utils.log.LocalLogFileServer; +import org.apache.pinot.common.utils.log.LogFileServer; import org.apache.pinot.core.api.ServiceAutoDiscoveryFeature; import org.apache.pinot.core.query.executor.sql.SqlQueryExecutor; import org.apache.pinot.core.transport.ListenerConfig; @@ -41,8 +46,6 @@ import org.apache.pinot.spi.env.PinotConfiguration; import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.PinotReflectionUtils; -import org.glassfish.grizzly.http.server.CLStaticHttpHandler; -import org.glassfish.grizzly.http.server.HttpHandler; import org.glassfish.grizzly.http.server.HttpServer; import org.glassfish.hk2.utilities.binding.AbstractBinder; import org.glassfish.jersey.jackson.JacksonFeature; @@ -53,10 +56,12 @@ public class BrokerAdminApiApplication extends ResourceConfig { private static final Logger LOGGER = LoggerFactory.getLogger(BrokerAdminApiApplication.class); - private static final String RESOURCE_PACKAGE = "org.apache.pinot.broker.api.resources"; public static final String PINOT_CONFIGURATION = "pinotConfiguration"; public static final String BROKER_INSTANCE_ID = "brokerInstanceId"; + public static final String START_TIME = "brokerStartTime"; + + private final String _brokerResourcePackages; private final boolean _useHttps; private final boolean _swaggerBrokerEnabled; private final ExecutorService _executorService; @@ -65,8 +70,12 @@ public class BrokerAdminApiApplication extends ResourceConfig { public BrokerAdminApiApplication(BrokerRoutingManager routingManager, BrokerRequestHandler brokerRequestHandler, BrokerMetrics brokerMetrics, PinotConfiguration brokerConf, SqlQueryExecutor sqlQueryExecutor, - ServerRoutingStatsManager serverRoutingStatsManager, AccessControlFactory accessFactory) { - packages(RESOURCE_PACKAGE); + ServerRoutingStatsManager serverRoutingStatsManager, AccessControlFactory accessFactory, + HelixManager helixManager) { + _brokerResourcePackages = brokerConf.getProperty(CommonConstants.Broker.BROKER_RESOURCE_PACKAGES, + CommonConstants.Broker.DEFAULT_BROKER_RESOURCE_PACKAGES); + String[] pkgs = _brokerResourcePackages.split(","); + packages(pkgs); property(PINOT_CONFIGURATION, brokerConf); _useHttps = Boolean.parseBoolean(brokerConf.getProperty(CommonConstants.Broker.CONFIG_OF_SWAGGER_USE_HTTPS)); _swaggerBrokerEnabled = brokerConf.getProperty(CommonConstants.Broker.CONFIG_OF_SWAGGER_BROKER_ENABLED, @@ -76,31 +85,42 @@ public BrokerAdminApiApplication(BrokerRoutingManager routingManager, BrokerRequ } _executorService = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setNameFormat("async-task-thread-%d").build()); - MultiThreadedHttpConnectionManager connMgr = new MultiThreadedHttpConnectionManager(); - connMgr.getParams().setConnectionTimeout((int) brokerConf - .getProperty(CommonConstants.Broker.CONFIG_OF_BROKER_TIMEOUT_MS, - CommonConstants.Broker.DEFAULT_BROKER_TIMEOUT_MS)); + PoolingHttpClientConnectionManager connMgr = new PoolingHttpClientConnectionManager(); + int timeoutMs = (int) brokerConf.getProperty(CommonConstants.Broker.CONFIG_OF_BROKER_TIMEOUT_MS, + CommonConstants.Broker.DEFAULT_BROKER_TIMEOUT_MS); + connMgr.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(timeoutMs).build()); + Instant startTime = Instant.now(); register(new AbstractBinder() { @Override protected void configure() { - bind(connMgr).to(HttpConnectionManager.class); + bind(connMgr).to(HttpClientConnectionManager.class); bind(_executorService).to(Executor.class); + bind(helixManager).to(HelixManager.class); bind(sqlQueryExecutor).to(SqlQueryExecutor.class); bind(routingManager).to(BrokerRoutingManager.class); bind(brokerRequestHandler).to(BrokerRequestHandler.class); bind(brokerMetrics).to(BrokerMetrics.class); String loggerRootDir = brokerConf.getProperty(CommonConstants.Broker.CONFIG_OF_LOGGER_ROOT_DIR); if (loggerRootDir != null) { - bind(new LoggerFileServer(loggerRootDir)).to(LoggerFileServer.class); + bind(new LocalLogFileServer(loggerRootDir)).to(LogFileServer.class); + } else { + bind(new DummyLogFileServer()).to(LogFileServer.class); } bind(brokerConf.getProperty(CommonConstants.Broker.CONFIG_OF_BROKER_ID)).named(BROKER_INSTANCE_ID); bind(serverRoutingStatsManager).to(ServerRoutingStatsManager.class); bind(accessFactory).to(AccessControlFactory.class); + bind(startTime).named(BrokerAdminApiApplication.START_TIME); } }); + boolean enableBoundedJerseyThreadPoolExecutor = + brokerConf.getProperty(CommonConstants.Broker.CONFIG_OF_ENABLE_BOUNDED_JERSEY_THREADPOOL_EXECUTOR, + CommonConstants.Broker.DEFAULT_ENABLE_BOUNDED_JERSEY_THREADPOOL_EXECUTOR); + if (enableBoundedJerseyThreadPoolExecutor) { + register(buildBrokerManagedAsyncExecutorProvider(brokerConf, brokerMetrics)); + } register(JacksonFeature.class); - registerClasses(io.swagger.jaxrs.listing.ApiListingResource.class); - registerClasses(io.swagger.jaxrs.listing.SwaggerSerializers.class); + register(SwaggerApiListingResource.class); + register(SwaggerSerializers.class); register(AuthenticationFilter.class); } @@ -114,36 +134,24 @@ public void start(List listenerConfigs) { } if (_swaggerBrokerEnabled) { - PinotReflectionUtils.runWithLock(this::setupSwagger); + PinotReflectionUtils.runWithLock(() -> + SwaggerSetupUtils.setupSwagger("Broker", _brokerResourcePackages, _useHttps, "/", _httpServer)); } else { LOGGER.info("Hiding Swagger UI for Broker, by {}", CommonConstants.Broker.CONFIG_OF_SWAGGER_BROKER_ENABLED); } } - private void setupSwagger() { - BeanConfig beanConfig = new BeanConfig(); - beanConfig.setTitle("Pinot Broker API"); - beanConfig.setDescription("APIs for accessing Pinot broker information"); - beanConfig.setContact("https://github.com/apache/pinot"); - beanConfig.setVersion("1.0"); - beanConfig.setExpandSuperTypes(false); - if (_useHttps) { - beanConfig.setSchemes(new String[]{CommonConstants.HTTPS_PROTOCOL}); - } else { - beanConfig.setSchemes(new String[]{CommonConstants.HTTP_PROTOCOL, CommonConstants.HTTPS_PROTOCOL}); - } - beanConfig.setBasePath("/"); - beanConfig.setResourcePackage(RESOURCE_PACKAGE); - beanConfig.setScan(true); - - HttpHandler httpHandler = new CLStaticHttpHandler(BrokerAdminApiApplication.class.getClassLoader(), "/api/"); - // map both /api and /help to swagger docs. /api because it looks nice. /help for backward compatibility - _httpServer.getServerConfiguration().addHttpHandler(httpHandler, "/api/", "/help/"); - - URL swaggerDistLocation = - BrokerAdminApiApplication.class.getClassLoader().getResource("META-INF/resources/webjars/swagger-ui/3.23.11/"); - CLStaticHttpHandler swaggerDist = new CLStaticHttpHandler(new URLClassLoader(new URL[]{swaggerDistLocation})); - _httpServer.getServerConfiguration().addHttpHandler(swaggerDist, "/swaggerui-dist/"); + private BrokerManagedAsyncExecutorProvider buildBrokerManagedAsyncExecutorProvider(PinotConfiguration brokerConf, + BrokerMetrics brokerMetrics) { + int corePoolSize = + brokerConf.getProperty(CommonConstants.Broker.CONFIG_OF_JERSEY_THREADPOOL_EXECUTOR_CORE_POOL_SIZE, + CommonConstants.Broker.DEFAULT_JERSEY_THREADPOOL_EXECUTOR_CORE_POOL_SIZE); + int maximumPoolSize = + brokerConf.getProperty(CommonConstants.Broker.CONFIG_OF_JERSEY_THREADPOOL_EXECUTOR_MAX_POOL_SIZE, + CommonConstants.Broker.DEFAULT_JERSEY_THREADPOOL_EXECUTOR_MAX_POOL_SIZE); + int queueSize = brokerConf.getProperty(CommonConstants.Broker.CONFIG_OF_JERSEY_THREADPOOL_EXECUTOR_QUEUE_SIZE, + CommonConstants.Broker.DEFAULT_JERSEY_THREADPOOL_EXECUTOR_QUEUE_SIZE); + return new BrokerManagedAsyncExecutorProvider(corePoolSize, maximumPoolSize, queueSize, brokerMetrics); } public void stop() { @@ -154,4 +162,8 @@ public void stop() { LOGGER.info("Shutting down executor service"); _executorService.shutdownNow(); } + + public HttpServer getHttpServer() { + return _httpServer; + } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BrokerManagedAsyncExecutorProvider.java b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BrokerManagedAsyncExecutorProvider.java new file mode 100644 index 000000000000..8d6823be84e5 --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/BrokerManagedAsyncExecutorProvider.java @@ -0,0 +1,108 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.broker; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadPoolExecutor; +import javax.ws.rs.ServiceUnavailableException; +import javax.ws.rs.core.Response; +import org.apache.pinot.common.metrics.BrokerMeter; +import org.apache.pinot.common.metrics.BrokerMetrics; +import org.glassfish.jersey.server.ManagedAsyncExecutor; +import org.glassfish.jersey.spi.ThreadPoolExecutorProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * BrokerManagedAsyncExecutorProvider provides a bounded thread pool. + */ +@ManagedAsyncExecutor +public class BrokerManagedAsyncExecutorProvider extends ThreadPoolExecutorProvider { + private static final Logger LOGGER = LoggerFactory.getLogger(BrokerManagedAsyncExecutorProvider.class); + + private static final String NAME = "broker-managed-async-executor"; + + private final BrokerMetrics _brokerMetrics; + + private final int _maximumPoolSize; + private final int _corePoolSize; + private final int _queueSize; + + public BrokerManagedAsyncExecutorProvider(int corePoolSize, int maximumPoolSize, int queueSize, + BrokerMetrics brokerMetrics) { + super(NAME); + _corePoolSize = corePoolSize; + _maximumPoolSize = maximumPoolSize; + _queueSize = queueSize; + _brokerMetrics = brokerMetrics; + } + + @Override + protected int getMaximumPoolSize() { + return _maximumPoolSize; + } + + @Override + protected int getCorePoolSize() { + return _corePoolSize; + } + + @Override + protected BlockingQueue getWorkQueue() { + if (_queueSize == Integer.MAX_VALUE) { + return new LinkedBlockingQueue(); + } + return new ArrayBlockingQueue(_queueSize); + } + + @Override + protected RejectedExecutionHandler getRejectedExecutionHandler() { + return new BrokerThreadPoolRejectExecutionHandler(_brokerMetrics); + } + + static class BrokerThreadPoolRejectExecutionHandler implements RejectedExecutionHandler { + private final BrokerMetrics _brokerMetrics; + + public BrokerThreadPoolRejectExecutionHandler(BrokerMetrics brokerMetrics) { + _brokerMetrics = brokerMetrics; + } + + /** + * Reject the runnable if it can’t be accommodated by the thread pool. + * + *

Response returned will have SERVICE_UNAVAILABLE(503) error code with error msg. + * + * @param r Runnable + * @param executor ThreadPoolExecutor + */ + @Override + public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { + _brokerMetrics.addMeteredGlobalValue(BrokerMeter.QUERY_REJECTED_EXCEPTIONS, 1L); + LOGGER.error("Task {} rejected from {}", r, executor); + + throw new ServiceUnavailableException(Response.status(Response.Status.SERVICE_UNAVAILABLE).entity( + "Pinot Broker thread pool can not accommodate more requests now. " + "Request is rejected from " + executor) + .build()); + } + } +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/ZkBasicAuthAccessControlFactory.java b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/ZkBasicAuthAccessControlFactory.java index a127f1a40aa1..d8434a23a4bb 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/ZkBasicAuthAccessControlFactory.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/ZkBasicAuthAccessControlFactory.java @@ -20,10 +20,14 @@ import com.google.common.base.Preconditions; import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; +import javax.ws.rs.NotAuthorizedException; import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.broker.api.AccessControl; @@ -35,7 +39,10 @@ import org.apache.pinot.core.auth.BasicAuthPrincipal; import org.apache.pinot.core.auth.BasicAuthUtils; import org.apache.pinot.core.auth.ZkBasicAuthPrincipal; +import org.apache.pinot.spi.auth.AuthorizationResult; +import org.apache.pinot.spi.auth.TableAuthorizationResult; import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; /** @@ -48,75 +55,93 @@ * */ public class ZkBasicAuthAccessControlFactory extends AccessControlFactory { - private static final String HEADER_AUTHORIZATION = "authorization"; + private static final String HEADER_AUTHORIZATION = "authorization"; - private AccessControl _accessControl; + private AccessControl _accessControl; - public ZkBasicAuthAccessControlFactory() { - // left blank + public ZkBasicAuthAccessControlFactory() { + // left blank + } + + @Override + public void init(PinotConfiguration configuration, ZkHelixPropertyStore propertyStore) { + _accessControl = new BasicAuthAccessControl(new AccessControlUserCache(propertyStore)); + } + + @Override + public AccessControl create() { + return _accessControl; + } + + /** + * Access Control using header-based basic http authentication + */ + private static class BasicAuthAccessControl implements AccessControl { + private Map _name2principal; + private final AccessControlUserCache _userCache; + + public BasicAuthAccessControl(AccessControlUserCache userCache) { + _userCache = userCache; } @Override - public void init(PinotConfiguration configuration, ZkHelixPropertyStore propertyStore) { - _accessControl = new BasicAuthAccessControl(new AccessControlUserCache(propertyStore)); + public AuthorizationResult authorize(RequesterIdentity requesterIdentity) { + return authorize(requesterIdentity, (BrokerRequest) null); } @Override - public AccessControl create() { - return _accessControl; + public AuthorizationResult authorize(RequesterIdentity requesterIdentity, BrokerRequest brokerRequest) { + if (brokerRequest == null || !brokerRequest.isSetQuerySource() || !brokerRequest.getQuerySource() + .isSetTableName()) { + // no table restrictions? accept + return TableAuthorizationResult.success(); + } + + return authorize(requesterIdentity, Collections.singleton(brokerRequest.getQuerySource().getTableName())); } - /** - * Access Control using header-based basic http authentication - */ - private static class BasicAuthAccessControl implements AccessControl { - private Map _name2principal; - private final AccessControlUserCache _userCache; - - public BasicAuthAccessControl(AccessControlUserCache userCache) { - _userCache = userCache; - } - - @Override - public boolean hasAccess(RequesterIdentity requesterIdentity) { - return hasAccess(requesterIdentity, null); + @Override + public TableAuthorizationResult authorize(RequesterIdentity requesterIdentity, Set tables) { + Optional principalOpt = getPrincipalAuth(requesterIdentity); + if (!principalOpt.isPresent()) { + throw new NotAuthorizedException("Basic"); + } + if (tables == null || tables.isEmpty()) { + return TableAuthorizationResult.success(); + } + + ZkBasicAuthPrincipal principal = principalOpt.get(); + Set failedTables = new HashSet<>(); + for (String table : tables) { + if (!principal.hasTable(TableNameBuilder.extractRawTableName(table))) { + failedTables.add(table); } + } + if (failedTables.isEmpty()) { + return TableAuthorizationResult.success(); + } + return new TableAuthorizationResult(failedTables); + } - @Override - public boolean hasAccess(RequesterIdentity requesterIdentity, BrokerRequest brokerRequest) { - Preconditions.checkArgument(requesterIdentity instanceof HttpRequesterIdentity, - "HttpRequesterIdentity required"); - HttpRequesterIdentity identity = (HttpRequesterIdentity) requesterIdentity; - - Collection tokens = identity.getHttpHeaders().get(HEADER_AUTHORIZATION); - - _name2principal = BasicAuthUtils.extractBasicAuthPrincipals(_userCache.getAllBrokerUserConfig()) - .stream().collect(Collectors.toMap(BasicAuthPrincipal::getName, p -> p)); - - - Map name2password = tokens.stream().collect(Collectors - .toMap(BasicAuthUtils::extractUsername, BasicAuthUtils::extractPassword)); - Map password2principal = name2password.keySet().stream() - .collect(Collectors.toMap(name2password::get, _name2principal::get)); + private Optional getPrincipalAuth(RequesterIdentity requesterIdentity) { + Preconditions.checkArgument(requesterIdentity instanceof HttpRequesterIdentity, "HttpRequesterIdentity required"); + HttpRequesterIdentity identity = (HttpRequesterIdentity) requesterIdentity; - Optional principalOpt = - password2principal.entrySet().stream() - .filter(entry -> BcryptUtils.checkpw(entry.getKey(), entry.getValue().getPassword())) - .map(u -> u.getValue()).filter(Objects::nonNull).findFirst(); + Collection tokens = identity.getHttpHeaders().get(HEADER_AUTHORIZATION); - if (!principalOpt.isPresent()) { - // no matching token? reject - return false; - } + _name2principal = BasicAuthUtils.extractBasicAuthPrincipals(_userCache.getAllBrokerUserConfig()).stream() + .collect(Collectors.toMap(BasicAuthPrincipal::getName, p -> p)); - ZkBasicAuthPrincipal principal = principalOpt.get(); - if (brokerRequest == null || !brokerRequest.isSetQuerySource() || !brokerRequest.getQuerySource() - .isSetTableName()) { - // no table restrictions? accept - return true; - } + Map name2password = tokens.stream().collect( + Collectors.toMap(org.apache.pinot.common.auth.BasicAuthUtils::extractUsername, + org.apache.pinot.common.auth.BasicAuthUtils::extractPassword)); + Map password2principal = + name2password.keySet().stream().collect(Collectors.toMap(name2password::get, _name2principal::get)); - return principal.hasTable(brokerRequest.getQuerySource().getTableName()); - } + Optional principalOpt = password2principal.entrySet().stream().filter( + entry -> BcryptUtils.checkpwWithCache(entry.getKey(), entry.getValue().getPassword(), + _userCache.getUserPasswordAuthCache())).map(u -> u.getValue()).filter(Objects::nonNull).findFirst(); + return principalOpt; } + } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/helix/BaseBrokerStarter.java b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/helix/BaseBrokerStarter.java index 6407fd0b296b..04bf6ce921de 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/helix/BaseBrokerStarter.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/helix/BaseBrokerStarter.java @@ -26,6 +26,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.commons.lang3.StringUtils; import org.apache.helix.HelixAdmin; import org.apache.helix.HelixConstants.ChangeType; import org.apache.helix.HelixDataAccessor; @@ -41,6 +42,7 @@ import org.apache.pinot.broker.broker.AccessControlFactory; import org.apache.pinot.broker.broker.BrokerAdminApiApplication; import org.apache.pinot.broker.queryquota.HelixExternalViewBasedQueryQuotaManager; +import org.apache.pinot.broker.requesthandler.BaseSingleStageBrokerRequestHandler; import org.apache.pinot.broker.requesthandler.BrokerRequestHandler; import org.apache.pinot.broker.requesthandler.BrokerRequestHandlerDelegate; import org.apache.pinot.broker.requesthandler.GrpcBrokerRequestHandler; @@ -56,23 +58,31 @@ import org.apache.pinot.common.metrics.BrokerGauge; import org.apache.pinot.common.metrics.BrokerMeter; import org.apache.pinot.common.metrics.BrokerMetrics; +import org.apache.pinot.common.utils.PinotAppConfigs; import org.apache.pinot.common.utils.ServiceStartableUtils; import org.apache.pinot.common.utils.ServiceStatus; -import org.apache.pinot.common.utils.TlsUtils; import org.apache.pinot.common.utils.config.TagNameUtils; import org.apache.pinot.common.utils.helix.HelixHelper; +import org.apache.pinot.common.utils.tls.PinotInsecureMode; +import org.apache.pinot.common.utils.tls.TlsUtils; import org.apache.pinot.common.version.PinotVersion; import org.apache.pinot.core.query.executor.sql.SqlQueryExecutor; +import org.apache.pinot.core.query.utils.rewriter.ResultRewriterFactory; import org.apache.pinot.core.transport.ListenerConfig; import org.apache.pinot.core.transport.server.routing.stats.ServerRoutingStatsManager; import org.apache.pinot.core.util.ListenerConfigUtil; +import org.apache.pinot.spi.accounting.ThreadResourceUsageProvider; import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.eventlistener.query.BrokerQueryEventListenerFactory; import org.apache.pinot.spi.metrics.PinotMetricUtils; import org.apache.pinot.spi.metrics.PinotMetricsRegistry; import org.apache.pinot.spi.services.ServiceRole; import org.apache.pinot.spi.services.ServiceStartable; +import org.apache.pinot.spi.trace.Tracing; +import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.CommonConstants.Broker; import org.apache.pinot.spi.utils.CommonConstants.Helix; +import org.apache.pinot.spi.utils.CommonConstants.MultiStageQueryRunner; import org.apache.pinot.spi.utils.InstanceTypeUtils; import org.apache.pinot.spi.utils.NetUtils; import org.apache.pinot.sql.parsers.rewriter.QueryRewriterFactory; @@ -128,6 +138,14 @@ public void init(PinotConfiguration brokerConf) _clusterName = brokerConf.getProperty(Helix.CONFIG_OF_CLUSTER_NAME); ServiceStartableUtils.applyClusterConfig(_brokerConf, _zkServers, _clusterName, ServiceRole.BROKER); + PinotInsecureMode.setPinotInInsecureMode(Boolean.valueOf( + _brokerConf.getProperty(CommonConstants.CONFIG_OF_PINOT_INSECURE_MODE, + CommonConstants.DEFAULT_PINOT_INSECURE_MODE))); + + if (_brokerConf.getProperty(MultiStageQueryRunner.KEY_OF_QUERY_RUNNER_PORT, + MultiStageQueryRunner.DEFAULT_QUERY_RUNNER_PORT) == 0) { + _brokerConf.setProperty(MultiStageQueryRunner.KEY_OF_QUERY_RUNNER_PORT, NetUtils.findOpenPort()); + } setupHelixSystemProperties(); _listenerConfigs = ListenerConfigUtil.buildBrokerConfigs(brokerConf); _hostname = brokerConf.getProperty(Broker.CONFIG_OF_BROKER_HOSTNAME); @@ -136,6 +154,10 @@ public void init(PinotConfiguration brokerConf) _brokerConf.getProperty(Helix.SET_INSTANCE_ID_TO_HOSTNAME_KEY, false) ? NetUtils.getHostnameOrAddress() : NetUtils.getHostAddress(); } + // Override multi-stage query runner hostname if not set explicitly + if (!_brokerConf.containsKey(MultiStageQueryRunner.KEY_OF_QUERY_RUNNER_HOSTNAME)) { + _brokerConf.setProperty(MultiStageQueryRunner.KEY_OF_QUERY_RUNNER_HOSTNAME, _hostname); + } _port = _listenerConfigs.get(0).getPort(); _tlsPort = ListenerConfigUtil.findLastTlsPort(_listenerConfigs, -1); @@ -220,6 +242,7 @@ public PinotConfiguration getConfig() { public void start() throws Exception { LOGGER.info("Starting Pinot broker (Version: {})", PinotVersion.VERSION); + LOGGER.info("Broker configs: {}", new PinotAppConfigs(getConfig()).toJSONString()); _isStarting = true; Utils.logVersions(); @@ -241,63 +264,81 @@ public void start() _brokerConf.getProperty(Broker.CONFIG_OF_ALLOWED_TABLES_FOR_EMITTING_METRICS, Collections.emptyList())); _brokerMetrics.initializeGlobalMeters(); _brokerMetrics.setValueOfGlobalGauge(BrokerGauge.VERSION, PinotVersion.VERSION_METRIC_NAME, 1); + _brokerMetrics.setValueOfGlobalGauge(BrokerGauge.ADAPTIVE_SERVER_SELECTOR_TYPE, + _brokerConf.getProperty(Broker.AdaptiveServerSelector.CONFIG_OF_TYPE, + Broker.AdaptiveServerSelector.DEFAULT_TYPE), 1); + BrokerMetrics.register(_brokerMetrics); // Set up request handling classes - _serverRoutingStatsManager = new ServerRoutingStatsManager(_brokerConf); + _serverRoutingStatsManager = new ServerRoutingStatsManager(_brokerConf, _brokerMetrics); _serverRoutingStatsManager.init(); _routingManager = new BrokerRoutingManager(_brokerMetrics, _serverRoutingStatsManager, _brokerConf); _routingManager.init(_spectatorHelixManager); - _accessControlFactory = - AccessControlFactory.loadFactory(_brokerConf.subset(Broker.ACCESS_CONTROL_CONFIG_PREFIX), _propertyStore); + final PinotConfiguration factoryConf = _brokerConf.subset(Broker.ACCESS_CONTROL_CONFIG_PREFIX); + // Adding cluster name to the config so that it can be used by the AccessControlFactory + factoryConf.setProperty(Helix.CONFIG_OF_CLUSTER_NAME, _brokerConf.getProperty(Helix.CONFIG_OF_CLUSTER_NAME)); + _accessControlFactory = AccessControlFactory.loadFactory(factoryConf, _propertyStore); HelixExternalViewBasedQueryQuotaManager queryQuotaManager = new HelixExternalViewBasedQueryQuotaManager(_brokerMetrics, _instanceId); queryQuotaManager.init(_spectatorHelixManager); // Initialize QueryRewriterFactory LOGGER.info("Initializing QueryRewriterFactory"); QueryRewriterFactory.init(_brokerConf.getProperty(Broker.CONFIG_OF_BROKER_QUERY_REWRITER_CLASS_NAMES)); + LOGGER.info("Initializing ResultRewriterFactory"); + ResultRewriterFactory.init(_brokerConf.getProperty(Broker.CONFIG_OF_BROKER_RESULT_REWRITER_CLASS_NAMES)); // Initialize FunctionRegistry before starting the broker request handler FunctionRegistry.init(); boolean caseInsensitive = - _brokerConf.getProperty(Helix.ENABLE_CASE_INSENSITIVE_KEY, false) || _brokerConf.getProperty( - Helix.DEPRECATED_ENABLE_CASE_INSENSITIVE_KEY, false); + _brokerConf.getProperty(Helix.ENABLE_CASE_INSENSITIVE_KEY, Helix.DEFAULT_ENABLE_CASE_INSENSITIVE); TableCache tableCache = new TableCache(_propertyStore, caseInsensitive); - // Configure TLS for netty connection to server - TlsConfig tlsDefaults = TlsUtils.extractTlsConfig(_brokerConf, Broker.BROKER_TLS_PREFIX); - NettyConfig nettyDefaults = NettyConfig.extractNettyConfig(_brokerConf, Broker.BROKER_NETTY_PREFIX); + + LOGGER.info("Initializing Broker Event Listener Factory"); + BrokerQueryEventListenerFactory.init(_brokerConf.subset(Broker.EVENT_LISTENER_CONFIG_PREFIX)); // Create Broker request handler. String brokerId = _brokerConf.getProperty(Broker.CONFIG_OF_BROKER_ID, getDefaultBrokerId()); String brokerRequestHandlerType = _brokerConf.getProperty(Broker.BROKER_REQUEST_HANDLER_TYPE, Broker.DEFAULT_BROKER_REQUEST_HANDLER_TYPE); - BrokerRequestHandler singleStageBrokerRequestHandler = null; + BaseSingleStageBrokerRequestHandler singleStageBrokerRequestHandler; if (brokerRequestHandlerType.equalsIgnoreCase(Broker.GRPC_BROKER_REQUEST_HANDLER_TYPE)) { singleStageBrokerRequestHandler = new GrpcBrokerRequestHandler(_brokerConf, brokerId, _routingManager, _accessControlFactory, queryQuotaManager, - tableCache, _brokerMetrics, null); - } else { // default request handler type, e.g. netty + tableCache); + } else { + // Default request handler type, i.e. netty + NettyConfig nettyDefaults = NettyConfig.extractNettyConfig(_brokerConf, Broker.BROKER_NETTY_PREFIX); + // Configure TLS for netty connection to server + TlsConfig tlsDefaults = null; if (_brokerConf.getProperty(Broker.BROKER_NETTYTLS_ENABLED, false)) { - singleStageBrokerRequestHandler = - new SingleConnectionBrokerRequestHandler(_brokerConf, brokerId, _routingManager, _accessControlFactory, - queryQuotaManager, tableCache, _brokerMetrics, nettyDefaults, tlsDefaults, _serverRoutingStatsManager); - } else { - singleStageBrokerRequestHandler = - new SingleConnectionBrokerRequestHandler(_brokerConf, brokerId, _routingManager, _accessControlFactory, - queryQuotaManager, tableCache, _brokerMetrics, nettyDefaults, null, _serverRoutingStatsManager); + tlsDefaults = TlsUtils.extractTlsConfig(_brokerConf, Broker.BROKER_TLS_PREFIX); } + singleStageBrokerRequestHandler = + new SingleConnectionBrokerRequestHandler(_brokerConf, brokerId, _routingManager, _accessControlFactory, + queryQuotaManager, tableCache, nettyDefaults, tlsDefaults, _serverRoutingStatsManager); } - - BrokerRequestHandler multiStageBrokerRequestHandler = null; + MultiStageBrokerRequestHandler multiStageBrokerRequestHandler = null; if (_brokerConf.getProperty(Helix.CONFIG_OF_MULTI_STAGE_ENGINE_ENABLED, Helix.DEFAULT_MULTI_STAGE_ENGINE_ENABLED)) { // multi-stage request handler uses both Netty and GRPC ports. // worker requires both the "Netty port" for protocol transport; and "GRPC port" for mailbox transport. // TODO: decouple protocol and engine selection. multiStageBrokerRequestHandler = new MultiStageBrokerRequestHandler(_brokerConf, brokerId, _routingManager, _accessControlFactory, - queryQuotaManager, tableCache, _brokerMetrics); + queryQuotaManager, tableCache); } - - _brokerRequestHandler = new BrokerRequestHandlerDelegate(brokerId, singleStageBrokerRequestHandler, - multiStageBrokerRequestHandler, _brokerMetrics); + _brokerRequestHandler = + new BrokerRequestHandlerDelegate(singleStageBrokerRequestHandler, multiStageBrokerRequestHandler); _brokerRequestHandler.start(); + + // Enable/disable thread CPU time measurement through instance config. + ThreadResourceUsageProvider.setThreadCpuTimeMeasurementEnabled( + _brokerConf.getProperty(CommonConstants.Broker.CONFIG_OF_ENABLE_THREAD_CPU_TIME_MEASUREMENT, + CommonConstants.Broker.DEFAULT_ENABLE_THREAD_CPU_TIME_MEASUREMENT)); + // Enable/disable thread memory allocation tracking through instance config + ThreadResourceUsageProvider.setThreadMemoryMeasurementEnabled( + _brokerConf.getProperty(CommonConstants.Broker.CONFIG_OF_ENABLE_THREAD_ALLOCATED_BYTES_MEASUREMENT, + CommonConstants.Broker.DEFAULT_THREAD_ALLOCATED_BYTES_MEASUREMENT)); + Tracing.ThreadAccountantOps.initializeThreadAccountant( + _brokerConf.subset(CommonConstants.PINOT_QUERY_SCHEDULER_PREFIX), _instanceId); + String controllerUrl = _brokerConf.getProperty(Broker.CONTROLLER_URL); if (controllerUrl != null) { _sqlQueryExecutor = new SqlQueryExecutor(controllerUrl); @@ -305,9 +346,7 @@ public void start() _sqlQueryExecutor = new SqlQueryExecutor(_spectatorHelixManager); } LOGGER.info("Starting broker admin application on: {}", ListenerConfigUtil.toString(_listenerConfigs)); - _brokerAdminApplication = - new BrokerAdminApiApplication(_routingManager, _brokerRequestHandler, _brokerMetrics, _brokerConf, - _sqlQueryExecutor, _serverRoutingStatsManager, _accessControlFactory); + _brokerAdminApplication = createBrokerAdminApp(); _brokerAdminApplication.start(_listenerConfigs); LOGGER.info("Initializing cluster change mediator"); @@ -370,25 +409,54 @@ public void start() LOGGER.info("Finish starting Pinot broker"); } + /** + * @deprecated Use {@link #createBrokerAdminApp()} instead. + * This method is called after initialization of BrokerAdminApiApplication object + * and before calling start to allow custom broker starters to register additional + * components. + * @param brokerAdminApplication is the application + */ + protected void registerExtraComponents(BrokerAdminApiApplication brokerAdminApplication) { + } + private void updateInstanceConfigAndBrokerResourceIfNeeded() { InstanceConfig instanceConfig = HelixHelper.getInstanceConfig(_participantHelixManager, _instanceId); boolean updated = HelixHelper.updateHostnamePort(instanceConfig, _hostname, _port); + + ZNRecord znRecord = instanceConfig.getRecord(); + Map simpleFields = znRecord.getSimpleFields(); if (_tlsPort > 0) { HelixHelper.updateTlsPort(instanceConfig, _tlsPort); } + // Update multi-stage query engine ports + if (_brokerConf.getProperty(Helix.CONFIG_OF_MULTI_STAGE_ENGINE_ENABLED, Helix.DEFAULT_MULTI_STAGE_ENGINE_ENABLED)) { + updated |= updatePortIfNeeded(simpleFields, Helix.Instance.MULTI_STAGE_QUERY_ENGINE_MAILBOX_PORT_KEY, + Integer.parseInt(_brokerConf.getProperty(MultiStageQueryRunner.KEY_OF_QUERY_RUNNER_PORT))); + } else { + updated |= updatePortIfNeeded(simpleFields, Helix.Instance.MULTI_STAGE_QUERY_ENGINE_MAILBOX_PORT_KEY, -1); + } updated |= HelixHelper.removeDisabledPartitions(instanceConfig); boolean shouldUpdateBrokerResource = false; - String brokerTag = null; List instanceTags = instanceConfig.getTags(); if (instanceTags.isEmpty()) { // This is a new broker (first time joining the cluster) if (ZKMetadataProvider.getClusterTenantIsolationEnabled(_propertyStore)) { - brokerTag = TagNameUtils.getBrokerTagForTenant(null); + instanceConfig.addTag(TagNameUtils.getBrokerTagForTenant(null)); shouldUpdateBrokerResource = true; } else { - brokerTag = Helix.UNTAGGED_BROKER_INSTANCE; + String instanceTagsConfig = _brokerConf.getProperty(Broker.CONFIG_OF_BROKER_INSTANCE_TAGS); + if (StringUtils.isNotEmpty(instanceTagsConfig)) { + for (String instanceTag : StringUtils.split(instanceTagsConfig, ',')) { + Preconditions.checkArgument(TagNameUtils.isBrokerTag(instanceTag), "Illegal broker instance tag: %s", + instanceTag); + instanceConfig.addTag(instanceTag); + } + shouldUpdateBrokerResource = true; + } else { + instanceConfig.addTag(Helix.UNTAGGED_BROKER_INSTANCE); + } } - instanceConfig.addTag(brokerTag); + instanceTags = instanceConfig.getTags(); updated = true; } if (updated) { @@ -398,10 +466,9 @@ private void updateInstanceConfigAndBrokerResourceIfNeeded() { // Update broker resource to include the new broker long startTimeMs = System.currentTimeMillis(); List tablesAdded = new ArrayList<>(); - HelixHelper.updateBrokerResource(_participantHelixManager, _instanceId, Collections.singletonList(brokerTag), - tablesAdded, null); - LOGGER.info("Updated broker resource for new joining broker: {} in {}ms, tables added: {}", _instanceId, - System.currentTimeMillis() - startTimeMs, tablesAdded); + HelixHelper.updateBrokerResource(_participantHelixManager, _instanceId, instanceTags, tablesAdded, null); + LOGGER.info("Updated broker resource for new joining broker: {} with instance tags: {} in {}ms, tables added: {}", + _instanceId, instanceTags, System.currentTimeMillis() - startTimeMs, tablesAdded); } } @@ -444,6 +511,25 @@ private String getDefaultBrokerId() { } } + private boolean updatePortIfNeeded(Map instanceConfigSimpleFields, String key, int port) { + String existingPortStr = instanceConfigSimpleFields.get(key); + if (port > 0) { + String portStr = Integer.toString(port); + if (!portStr.equals(existingPortStr)) { + LOGGER.info("Updating '{}' for instance: {} to: {}", key, _instanceId, port); + instanceConfigSimpleFields.put(key, portStr); + return true; + } + } else { + if (existingPortStr != null) { + LOGGER.info("Removing '{}' from instance: {}", key, _instanceId); + instanceConfigSimpleFields.remove(key); + return true; + } + } + return false; + } + @Override public void stop() { LOGGER.info("Shutting down Pinot broker"); @@ -512,4 +598,12 @@ public AccessControlFactory getAccessControlFactory() { public BrokerRequestHandler getBrokerRequestHandler() { return _brokerRequestHandler; } + + protected BrokerAdminApiApplication createBrokerAdminApp() { + BrokerAdminApiApplication brokerAdminApiApplication = + new BrokerAdminApiApplication(_routingManager, _brokerRequestHandler, _brokerMetrics, _brokerConf, + _sqlQueryExecutor, _serverRoutingStatsManager, _accessControlFactory, _spectatorHelixManager); + registerExtraComponents(brokerAdminApiApplication); + return brokerAdminApiApplication; + } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/helix/ClusterChangeMediator.java b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/helix/ClusterChangeMediator.java index 83beeebcd001..202f1a3f8e19 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/broker/helix/ClusterChangeMediator.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/broker/helix/ClusterChangeMediator.java @@ -129,14 +129,14 @@ private synchronized void processClusterChange(ChangeType changeType, List 0 - || params._timeUsedMs > TimeUnit.SECONDS.toMillis(1); + return params._response.isPartialResult() || params._response.getTimeUsedMs() > TimeUnit.SECONDS.toMillis(1); } public static class QueryLogParams { - final long _requestId; - final String _query; - final RequestContext _requestContext; - final String _table; - final int _numUnavailableSegments; - final BaseBrokerRequestHandler.ServerStats _serverStats; - final BrokerResponse _response; - final long _timeUsedMs; + private final RequestContext _requestContext; + private final String _table; + private final BrokerResponse _response; @Nullable - final RequesterIdentity _requester; + private final RequesterIdentity _identity; + @Nullable + private final ServerStats _serverStats; - public QueryLogParams(long requestId, String query, RequestContext requestContext, String table, - int numUnavailableSegments, BaseBrokerRequestHandler.ServerStats serverStats, BrokerResponse response, - long timeUsedMs, @Nullable RequesterIdentity requester) { - _requestId = requestId; - _query = query; - _table = table; - _timeUsedMs = timeUsedMs; + public QueryLogParams(RequestContext requestContext, String table, BrokerResponse response, + @Nullable RequesterIdentity identity, @Nullable ServerStats serverStats) { _requestContext = requestContext; - _requester = requester; + // NOTE: Passing table name separately because table name within request context is always raw table name. + _table = table; _response = response; + _identity = identity; _serverStats = serverStats; - _numUnavailableSegments = numUnavailableSegments; } } @@ -150,7 +144,8 @@ private enum QueryLogEntry { REQUEST_ID("requestId") { @Override void doFormat(StringBuilder builder, QueryLogger logger, QueryLogParams params) { - builder.append(params._requestId); + // NOTE: At this moment, request ID is not available at response yet. + builder.append(params._requestContext.getRequestId()); } }, TABLE("table") { @@ -162,7 +157,7 @@ void doFormat(StringBuilder builder, QueryLogger logger, QueryLogParams params) TIME_MS("timeMs") { @Override void doFormat(StringBuilder builder, QueryLogger logger, QueryLogParams params) { - builder.append(params._timeUsedMs); + builder.append(params._response.getTimeUsedMs()); } }, DOCS("docs") { @@ -188,7 +183,8 @@ void doFormat(StringBuilder builder, QueryLogger logger, QueryLogParams params) .append(params._response.getNumConsumingSegmentsQueried()).append('/') .append(params._response.getNumConsumingSegmentsProcessed()).append('/') .append(params._response.getNumConsumingSegmentsMatched()).append('/') - .append(params._numUnavailableSegments); + // TODO: Consider adding the number of unavailable segments to the response + .append(params._requestContext.getNumUnavailableSegments()); } }, CONSUMING_FRESHNESS_MS("consumingFreshnessTimeMs") { @@ -213,7 +209,7 @@ void doFormat(StringBuilder builder, QueryLogger logger, QueryLogParams params) BROKER_REDUCE_TIME_MS("brokerReduceTimeMs") { @Override void doFormat(StringBuilder builder, QueryLogger logger, QueryLogParams params) { - builder.append(params._requestContext.getReduceTimeMillis()); + builder.append(params._response.getBrokerReduceTimeMs()); } }, EXCEPTIONS("exceptions") { @@ -225,7 +221,11 @@ void doFormat(StringBuilder builder, QueryLogger logger, QueryLogParams params) SERVER_STATS("serverStats") { @Override void doFormat(StringBuilder builder, QueryLogger logger, QueryLogParams params) { - builder.append(params._serverStats.getServerStats()); + if (params._serverStats != null) { + builder.append(params._serverStats.getServerStats()); + } else { + builder.append(CommonConstants.UNKNOWN); + } } }, OFFLINE_THREAD_CPU_TIME("offlineThreadCpuTimeNs(total/thread/sysActivity/resSer)", ':') { @@ -249,8 +249,8 @@ void doFormat(StringBuilder builder, QueryLogger logger, QueryLogParams params) CLIENT_IP("clientIp") { @Override void doFormat(StringBuilder builder, QueryLogger logger, QueryLogParams params) { - if (logger._enableIpLogging && params._requester != null) { - builder.append(params._requester.getClientIp()); + if (logger._enableIpLogging && params._identity != null) { + builder.append(params._identity.getClientIp()); } else { builder.append(CommonConstants.UNKNOWN); } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/HelixExternalViewBasedQueryQuotaManager.java b/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/HelixExternalViewBasedQueryQuotaManager.java index 04db0f6a4255..dabb95867b9b 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/HelixExternalViewBasedQueryQuotaManager.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/HelixExternalViewBasedQueryQuotaManager.java @@ -224,6 +224,7 @@ private void createOrUpdateRateLimiter(String tableNameWithType, ExternalView br tableNameWithType, overallRate, previousRate, perBrokerRate, onlineCount, stat.getVersion()); } addMaxBurstQPSCallbackTableGaugeIfNeeded(tableNameWithType, queryQuotaEntity); + addQueryQuotaCapacityUtilizationRateTableGaugeIfNeeded(tableNameWithType, queryQuotaEntity); if (isQueryRateLimitDisabled()) { LOGGER.info("Query rate limiting is currently disabled for this broker. So it won't take effect immediately."); } @@ -245,6 +246,7 @@ private void buildEmptyOrResetRateLimiterInQueryQuotaEntity(String tableNameWith queryQuotaEntity.setRateLimiter(null); } addMaxBurstQPSCallbackTableGaugeIfNeeded(tableNameWithType, queryQuotaEntity); + addQueryQuotaCapacityUtilizationRateTableGaugeIfNeeded(tableNameWithType, queryQuotaEntity); } /** @@ -256,6 +258,27 @@ private void addMaxBurstQPSCallbackTableGaugeIfNeeded(String tableNameWithType, () -> (long) finalQueryQuotaEntity.getMaxQpsTracker().getMaxCountPerBucket()); } + /** + * Add the query quota capacity utilization rate table gauge to the metric system if the qps quota is specified. + */ + private void addQueryQuotaCapacityUtilizationRateTableGaugeIfNeeded(String tableNameWithType, + QueryQuotaEntity queryQuotaEntity) { + if (queryQuotaEntity.getRateLimiter() != null) { + final QueryQuotaEntity finalQueryQuotaEntity = queryQuotaEntity; + _brokerMetrics.setOrUpdateTableGauge(tableNameWithType, BrokerGauge.QUERY_QUOTA_CAPACITY_UTILIZATION_RATE, () -> { + double perBrokerRate = finalQueryQuotaEntity.getRateLimiter().getRate(); + int actualHitCountWithinTimeRange = finalQueryQuotaEntity.getMaxQpsTracker().getHitCount(); + long hitCountAllowedWithinTimeRage = + (long) (perBrokerRate * finalQueryQuotaEntity.getMaxQpsTracker().getDefaultTimeRangeMs() / 1000L); + // Since the MaxQpsTracker specifies 1-min window as valid time range, we can get the query quota capacity + // utilization by using the actual hit count within 1 min divided by the expected hit count within 1 min. + long percentageOfCapacityUtilization = actualHitCountWithinTimeRange * 100L / hitCountAllowedWithinTimeRage; + LOGGER.debug("The percentage of rate limit capacity utilization is {}", percentageOfCapacityUtilization); + return percentageOfCapacityUtilization; + }); + } + } + /** * {@inheritDoc} *

Acquires a token from rate limiter based on the table name. @@ -316,13 +339,6 @@ private boolean tryAcquireToken(String tableNameWithType, QueryQuotaEntity query // Emit the qps capacity utilization rate. int numHits = queryQuotaEntity.getQpsTracker().getHitCount(); - if (_brokerMetrics != null) { - int percentageOfCapacityUtilization = (int) (numHits * 100 / perBrokerRate); - LOGGER.debug("The percentage of rate limit capacity utilization is {}", percentageOfCapacityUtilization); - _brokerMetrics.setValueOfTableGauge(tableNameWithType, BrokerGauge.QUERY_QUOTA_CAPACITY_UTILIZATION_RATE, - percentageOfCapacityUtilization); - } - if (!rateLimiter.tryAcquire()) { LOGGER.info("Quota is exceeded for table: {}. Per-broker rate: {}. Current qps: {}", tableNameWithType, perBrokerRate, numHits); diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/HitCounter.java b/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/HitCounter.java index eedc53903d1f..b656c0234449 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/HitCounter.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/HitCounter.java @@ -83,10 +83,20 @@ public int getHitCount() { @VisibleForTesting int getHitCount(long timestamp) { + return getHitCount(timestamp, _bucketCount); + } + + /** + * Get the hit count within the valid number of buckets. + * @param timestamp the current timestamp + * @param validBucketCount the valid number of buckets + * @return the number of hits within the valid bucket count + */ + int getHitCount(long timestamp, int validBucketCount) { long numTimeUnits = timestamp / _timeBucketWidthMs; int count = 0; for (int i = 0; i < _bucketCount; i++) { - if (numTimeUnits - _bucketStartTime.get(i) < _bucketCount) { + if (numTimeUnits - _bucketStartTime.get(i) < validBucketCount) { count += _bucketHitCount.get(i); } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/MaxHitRateTracker.java b/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/MaxHitRateTracker.java index bdb8dbc2149f..b0cbd88b0bee 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/MaxHitRateTracker.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/queryquota/MaxHitRateTracker.java @@ -34,6 +34,7 @@ public class MaxHitRateTracker extends HitCounter { private static final int ONE_SECOND_BUCKET_WIDTH_MS = 1000; private static final int MAX_TIME_RANGE_FACTOR = 2; + private final int _validBucketCount; private final long _maxTimeRangeMs; private final long _defaultTimeRangeMs; private volatile long _lastAccessTimestamp; @@ -44,6 +45,7 @@ public MaxHitRateTracker(int timeRangeInSeconds) { private MaxHitRateTracker(int defaultTimeRangeInSeconds, int maxTimeRangeInSeconds) { super(maxTimeRangeInSeconds, (int) (maxTimeRangeInSeconds * 1000L / ONE_SECOND_BUCKET_WIDTH_MS)); + _validBucketCount = (int) (defaultTimeRangeInSeconds * 1000L / ONE_SECOND_BUCKET_WIDTH_MS); _defaultTimeRangeMs = defaultTimeRangeInSeconds * 1000L; _maxTimeRangeMs = maxTimeRangeInSeconds * 1000L; } @@ -80,4 +82,14 @@ int getMaxCountPerBucket(long now) { _lastAccessTimestamp = now; return maxCount; } + + @VisibleForTesting + @Override + int getHitCount(long now) { + return super.getHitCount(now, _validBucketCount); + } + + public long getDefaultTimeRangeMs() { + return _defaultTimeRangeMs; + } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandler.java b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandler.java index c9277395a4d7..b43dcd9763aa 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandler.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandler.java @@ -19,30 +19,19 @@ package org.apache.pinot.broker.requesthandler; import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; +import com.google.common.collect.Maps; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.CompletionService; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicLong; -import java.util.stream.Collectors; import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.httpclient.HttpConnectionManager; -import org.apache.commons.httpclient.URI; -import org.apache.commons.httpclient.methods.DeleteMethod; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; +import org.apache.pinot.broker.api.AccessControl; import org.apache.pinot.broker.api.RequesterIdentity; import org.apache.pinot.broker.broker.AccessControlFactory; import org.apache.pinot.broker.querylog.QueryLogger; @@ -50,1562 +39,143 @@ import org.apache.pinot.broker.routing.BrokerRoutingManager; import org.apache.pinot.common.config.provider.TableCache; import org.apache.pinot.common.exception.QueryException; -import org.apache.pinot.common.http.MultiHttpRequest; -import org.apache.pinot.common.metrics.BrokerGauge; import org.apache.pinot.common.metrics.BrokerMeter; import org.apache.pinot.common.metrics.BrokerMetrics; -import org.apache.pinot.common.metrics.BrokerQueryPhase; -import org.apache.pinot.common.metrics.BrokerTimer; -import org.apache.pinot.common.request.BrokerRequest; -import org.apache.pinot.common.request.Expression; -import org.apache.pinot.common.request.ExpressionType; -import org.apache.pinot.common.request.Function; -import org.apache.pinot.common.request.Identifier; -import org.apache.pinot.common.request.Literal; -import org.apache.pinot.common.request.PinotQuery; import org.apache.pinot.common.response.BrokerResponse; -import org.apache.pinot.common.response.ProcessingException; -import org.apache.pinot.common.response.broker.BrokerResponseNative; -import org.apache.pinot.common.response.broker.ResultTable; -import org.apache.pinot.common.utils.DataSchema; -import org.apache.pinot.common.utils.config.QueryOptionsUtils; -import org.apache.pinot.common.utils.request.RequestUtils; -import org.apache.pinot.core.query.optimizer.QueryOptimizer; -import org.apache.pinot.core.routing.RoutingTable; -import org.apache.pinot.core.routing.TimeBoundaryInfo; -import org.apache.pinot.core.transport.ServerInstance; -import org.apache.pinot.core.util.GapfillUtils; -import org.apache.pinot.spi.config.table.FieldConfig; -import org.apache.pinot.spi.config.table.QueryConfig; -import org.apache.pinot.spi.config.table.TableConfig; -import org.apache.pinot.spi.config.table.TableType; -import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.common.response.broker.QueryProcessingException; +import org.apache.pinot.spi.auth.AuthorizationResult; import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.eventlistener.query.BrokerQueryEventListener; +import org.apache.pinot.spi.eventlistener.query.BrokerQueryEventListenerFactory; import org.apache.pinot.spi.exception.BadQueryRequestException; import org.apache.pinot.spi.trace.RequestContext; -import org.apache.pinot.spi.utils.BytesUtils; -import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.CommonConstants.Broker; -import org.apache.pinot.spi.utils.TimestampIndexUtils; -import org.apache.pinot.spi.utils.builder.TableNameBuilder; -import org.apache.pinot.sql.FilterKind; -import org.apache.pinot.sql.parsers.CalciteSqlCompiler; -import org.apache.pinot.sql.parsers.CalciteSqlParser; import org.apache.pinot.sql.parsers.SqlNodeAndOptions; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; @ThreadSafe public abstract class BaseBrokerRequestHandler implements BrokerRequestHandler { - private static final Logger LOGGER = LoggerFactory.getLogger(BaseBrokerRequestHandler.class); - private static final String IN_SUBQUERY = "insubquery"; - private static final String IN_ID_SET = "inidset"; - private static final Expression FALSE = RequestUtils.getLiteralExpression(false); - private static final Expression TRUE = RequestUtils.getLiteralExpression(true); - private static final Expression STAR = RequestUtils.getIdentifierExpression("*"); - private static final int MAX_UNAVAILABLE_SEGMENTS_TO_PRINT_IN_QUERY_EXCEPTION = 10; - protected final PinotConfiguration _config; + protected final String _brokerId; protected final BrokerRoutingManager _routingManager; protected final AccessControlFactory _accessControlFactory; protected final QueryQuotaManager _queryQuotaManager; protected final TableCache _tableCache; protected final BrokerMetrics _brokerMetrics; - - protected final AtomicLong _requestIdGenerator = new AtomicLong(); - protected final QueryOptimizer _queryOptimizer = new QueryOptimizer(); - - protected final String _brokerId; + protected final BrokerQueryEventListener _brokerQueryEventListener; + protected final Set _trackedHeaders; + protected final BrokerRequestIdGenerator _requestIdGenerator; protected final long _brokerTimeoutMs; - protected final int _queryResponseLimit; - - private final QueryLogger _queryLogger; - private final boolean _disableGroovy; - private final boolean _useApproximateFunction; - private final int _defaultHllLog2m; - private final boolean _enableQueryLimitOverride; - private final boolean _enableDistinctCountBitmapOverride; - private final Map _queriesById; + protected final QueryLogger _queryLogger; public BaseBrokerRequestHandler(PinotConfiguration config, String brokerId, BrokerRoutingManager routingManager, - AccessControlFactory accessControlFactory, QueryQuotaManager queryQuotaManager, TableCache tableCache, - BrokerMetrics brokerMetrics) { - _brokerId = brokerId; + AccessControlFactory accessControlFactory, QueryQuotaManager queryQuotaManager, TableCache tableCache) { _config = config; + _brokerId = brokerId; _routingManager = routingManager; _accessControlFactory = accessControlFactory; _queryQuotaManager = queryQuotaManager; _tableCache = tableCache; - _brokerMetrics = brokerMetrics; - _disableGroovy = _config.getProperty(Broker.DISABLE_GROOVY, Broker.DEFAULT_DISABLE_GROOVY); - _useApproximateFunction = _config.getProperty(Broker.USE_APPROXIMATE_FUNCTION, false); - _defaultHllLog2m = _config.getProperty(CommonConstants.Helix.DEFAULT_HYPERLOGLOG_LOG2M_KEY, - CommonConstants.Helix.DEFAULT_HYPERLOGLOG_LOG2M); - _enableQueryLimitOverride = _config.getProperty(Broker.CONFIG_OF_ENABLE_QUERY_LIMIT_OVERRIDE, false); - _enableDistinctCountBitmapOverride = - _config.getProperty(CommonConstants.Helix.ENABLE_DISTINCT_COUNT_BITMAP_OVERRIDE_KEY, false); - + _brokerMetrics = BrokerMetrics.get(); + _brokerQueryEventListener = BrokerQueryEventListenerFactory.getBrokerQueryEventListener(); + _trackedHeaders = BrokerQueryEventListenerFactory.getTrackedHeaders(); + _requestIdGenerator = new BrokerRequestIdGenerator(brokerId); _brokerTimeoutMs = config.getProperty(Broker.CONFIG_OF_BROKER_TIMEOUT_MS, Broker.DEFAULT_BROKER_TIMEOUT_MS); - _queryResponseLimit = - config.getProperty(Broker.CONFIG_OF_BROKER_QUERY_RESPONSE_LIMIT, Broker.DEFAULT_BROKER_QUERY_RESPONSE_LIMIT); _queryLogger = new QueryLogger(config); - boolean enableQueryCancellation = - Boolean.parseBoolean(config.getProperty(Broker.CONFIG_OF_BROKER_ENABLE_QUERY_CANCELLATION)); - _queriesById = enableQueryCancellation ? new ConcurrentHashMap<>() : null; - LOGGER.info( - "Broker Id: {}, timeout: {}ms, query response limit: {}, query log length: {}, query log max rate: {}qps, " - + "enabling query cancellation: {}", _brokerId, _brokerTimeoutMs, _queryResponseLimit, - _queryLogger.getMaxQueryLengthToLog(), _queryLogger.getLogRateLimit(), enableQueryCancellation); } @Override - public Map getRunningQueries() { - Preconditions.checkState(_queriesById != null, "Query cancellation is not enabled on broker"); - return _queriesById.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue()._query)); - } - - @VisibleForTesting - Set getRunningServers(long requestId) { - Preconditions.checkState(_queriesById != null, "Query cancellation is not enabled on broker"); - QueryServers queryServers = _queriesById.get(requestId); - return queryServers != null ? queryServers._servers : Collections.emptySet(); - } - - @Override - public boolean cancelQuery(long requestId, int timeoutMs, Executor executor, HttpConnectionManager connMgr, - Map serverResponses) + public BrokerResponse handleRequest(JsonNode request, @Nullable SqlNodeAndOptions sqlNodeAndOptions, + @Nullable RequesterIdentity requesterIdentity, RequestContext requestContext, @Nullable HttpHeaders httpHeaders) throws Exception { - Preconditions.checkState(_queriesById != null, "Query cancellation is not enabled on broker"); - QueryServers queryServers = _queriesById.get(requestId); - if (queryServers == null) { - return false; - } - // TODO: Use different global query id for OFFLINE and REALTIME table after releasing 0.12.0. See QueryIdUtils for - // details - String globalQueryId = getGlobalQueryId(requestId); - List serverUrls = new ArrayList<>(); - for (ServerInstance serverInstance : queryServers._servers) { - serverUrls.add(String.format("%s/query/%s", serverInstance.getAdminEndpoint(), globalQueryId)); - } - LOGGER.debug("Cancelling the query: {} via server urls: {}", queryServers._query, serverUrls); - CompletionService completionService = - new MultiHttpRequest(executor, connMgr).execute(serverUrls, null, timeoutMs, "DELETE", DeleteMethod::new); - List errMsgs = new ArrayList<>(serverUrls.size()); - for (int i = 0; i < serverUrls.size(); i++) { - DeleteMethod deleteMethod = null; - try { - // Wait for all requests to respond before returning to be sure that the servers have handled the cancel - // requests. The completion order is different from serverUrls, thus use uri in the response. - deleteMethod = completionService.take().get(); - URI uri = deleteMethod.getURI(); - int status = deleteMethod.getStatusCode(); - // Unexpected server responses are collected and returned as exception. - if (status != 200 && status != 404) { - throw new Exception(String.format("Unexpected status=%d and response='%s' from uri='%s'", status, - deleteMethod.getResponseBodyAsString(), uri)); - } - if (serverResponses != null) { - serverResponses.put(uri.getHost() + ":" + uri.getPort(), status); - } - } catch (Exception e) { - LOGGER.error("Failed to cancel query: {}", queryServers._query, e); - // Can't just throw exception from here as there is a need to release the other connections. - // So just collect the error msg to throw them together after the for-loop. - errMsgs.add(e.getMessage()); - } finally { - if (deleteMethod != null) { - deleteMethod.releaseConnection(); + requestContext.setBrokerId(_brokerId); + long requestId = _requestIdGenerator.get(); + requestContext.setRequestId(requestId); + + if (httpHeaders != null && !_trackedHeaders.isEmpty()) { + MultivaluedMap requestHeaders = httpHeaders.getRequestHeaders(); + Map> trackedHeadersMap = Maps.newHashMapWithExpectedSize(_trackedHeaders.size()); + for (Map.Entry> entry : requestHeaders.entrySet()) { + String key = entry.getKey().toLowerCase(); + if (_trackedHeaders.contains(key)) { + trackedHeadersMap.put(key, entry.getValue()); } } + requestContext.setRequestHttpHeaders(trackedHeadersMap); } - if (errMsgs.size() > 0) { - throw new Exception("Unexpected responses from servers: " + StringUtils.join(errMsgs, ",")); - } - return true; - } - - @Override - public BrokerResponse handleRequest(JsonNode request, @Nullable SqlNodeAndOptions sqlNodeAndOptions, - @Nullable RequesterIdentity requesterIdentity, RequestContext requestContext) - throws Exception { - long requestId = _requestIdGenerator.incrementAndGet(); - requestContext.setRequestId(requestId); - requestContext.setRequestArrivalTimeMillis(System.currentTimeMillis()); // First-stage access control to prevent unauthenticated requests from using up resources. Secondary table-level // check comes later. - boolean hasAccess = _accessControlFactory.create().hasAccess(requesterIdentity); - if (!hasAccess) { + AccessControl accessControl = _accessControlFactory.create(); + AuthorizationResult authorizationResult = accessControl.authorize(requesterIdentity); + if (!authorizationResult.hasAccess()) { _brokerMetrics.addMeteredGlobalValue(BrokerMeter.REQUEST_DROPPED_DUE_TO_ACCESS_ERROR, 1); - LOGGER.info("Access denied for requestId {}", requestId); requestContext.setErrorCode(QueryException.ACCESS_DENIED_ERROR_CODE); - return new BrokerResponseNative(QueryException.ACCESS_DENIED_ERROR); + _brokerQueryEventListener.onQueryCompletion(requestContext); + String failureMessage = authorizationResult.getFailureMessage(); + if (StringUtils.isNotBlank(failureMessage)) { + failureMessage = "Reason: " + failureMessage; + } + throw new WebApplicationException("Permission denied." + failureMessage, + Response.Status.FORBIDDEN); } JsonNode sql = request.get(Broker.Request.SQL); - if (sql == null) { - throw new BadQueryRequestException("Failed to find 'sql' in the request: " + request); - } - String query = sql.asText(); - requestContext.setQuery(query); - return handleRequest(requestId, query, sqlNodeAndOptions, request, requesterIdentity, requestContext); - } - - private BrokerResponseNative handleRequest(long requestId, String query, - @Nullable SqlNodeAndOptions sqlNodeAndOptions, JsonNode request, @Nullable RequesterIdentity requesterIdentity, - RequestContext requestContext) - throws Exception { - LOGGER.debug("SQL query for request {}: {}", requestId, query); - - long compilationStartTimeNs; - PinotQuery pinotQuery; - try { - // Parse the request - sqlNodeAndOptions = sqlNodeAndOptions != null ? sqlNodeAndOptions : RequestUtils.parseQuery(query, request); - // Compile the request into PinotQuery - compilationStartTimeNs = System.nanoTime(); - pinotQuery = CalciteSqlParser.compileToPinotQuery(sqlNodeAndOptions); - } catch (Exception e) { - LOGGER.info("Caught exception while compiling SQL request {}: {}, {}", requestId, query, e.getMessage()); - _brokerMetrics.addMeteredGlobalValue(BrokerMeter.REQUEST_COMPILATION_EXCEPTIONS, 1); + if (sql == null || !sql.isTextual()) { requestContext.setErrorCode(QueryException.SQL_PARSING_ERROR_CODE); - return new BrokerResponseNative(QueryException.getException(QueryException.SQL_PARSING_ERROR, e)); - } - - if (isLiteralOnlyQuery(pinotQuery)) { - LOGGER.debug("Request {} contains only Literal, skipping server query: {}", requestId, query); - try { - if (pinotQuery.isExplain()) { - // EXPLAIN PLAN results to show that query is evaluated exclusively by Broker. - return BrokerResponseNative.BROKER_ONLY_EXPLAIN_PLAN_OUTPUT; - } - return processLiteralOnlyQuery(pinotQuery, compilationStartTimeNs, requestContext); - } catch (Exception e) { - // TODO: refine the exceptions here to early termination the queries won't requires to send to servers. - LOGGER.warn("Unable to execute literal request {}: {} at broker, fallback to server query. {}", requestId, - query, e.getMessage()); - } - } - - PinotQuery serverPinotQuery = GapfillUtils.stripGapfill(pinotQuery); - if (serverPinotQuery.getDataSource() == null) { - LOGGER.info("Data source (FROM clause) not found in request {}: {}", request, query); - requestContext.setErrorCode(QueryException.QUERY_VALIDATION_ERROR_CODE); - return new BrokerResponseNative( - QueryException.getException(QueryException.QUERY_VALIDATION_ERROR, "Data source (FROM clause) not found")); - } - - try { - handleSubquery(serverPinotQuery, requestId, request, requesterIdentity, requestContext); - } catch (Exception e) { - LOGGER.info("Caught exception while handling the subquery in request {}: {}, {}", requestId, query, - e.getMessage()); - requestContext.setErrorCode(QueryException.QUERY_EXECUTION_ERROR_CODE); - return new BrokerResponseNative(QueryException.getException(QueryException.QUERY_EXECUTION_ERROR, e)); - } - - String tableName = getActualTableName(serverPinotQuery.getDataSource().getTableName(), _tableCache); - serverPinotQuery.getDataSource().setTableName(tableName); - String rawTableName = TableNameBuilder.extractRawTableName(tableName); - requestContext.setTableName(rawTableName); - - try { - boolean isCaseInsensitive = _tableCache.isIgnoreCase(); - Map columnNameMap = _tableCache.getColumnNameMap(rawTableName); - if (columnNameMap != null) { - updateColumnNames(rawTableName, serverPinotQuery, isCaseInsensitive, columnNameMap); - } - } catch (Exception e) { - // Throw exceptions with column in-existence error. - if (e instanceof BadQueryRequestException) { - LOGGER.info("Caught exception while checking column names in request {}: {}, {}", requestId, query, - e.getMessage()); - requestContext.setErrorCode(QueryException.UNKNOWN_COLUMN_ERROR_CODE); - _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.UNKNOWN_COLUMN_EXCEPTIONS, 1); - return new BrokerResponseNative(QueryException.getException(QueryException.UNKNOWN_COLUMN_ERROR, e)); - } - LOGGER.warn("Caught exception while updating column names in request {}: {}, {}", requestId, query, - e.getMessage()); - } - if (_defaultHllLog2m > 0) { - handleHLLLog2mOverride(serverPinotQuery, _defaultHllLog2m); - } - if (_enableQueryLimitOverride) { - handleQueryLimitOverride(serverPinotQuery, _queryResponseLimit); - } - handleSegmentPartitionedDistinctCountOverride(serverPinotQuery, - getSegmentPartitionedColumns(_tableCache, tableName)); - if (_enableDistinctCountBitmapOverride) { - handleDistinctCountBitmapOverride(serverPinotQuery); - } - - long compilationEndTimeNs = System.nanoTime(); - // full request compile time = compilationTimeNs + parserTimeNs - _brokerMetrics.addPhaseTiming(rawTableName, BrokerQueryPhase.REQUEST_COMPILATION, - (compilationEndTimeNs - compilationStartTimeNs) + sqlNodeAndOptions.getParseTimeNs()); - - // Second-stage table-level access control - // TODO: Modify AccessControl interface to directly take PinotQuery - BrokerRequest brokerRequest = CalciteSqlCompiler.convertToBrokerRequest(pinotQuery); - BrokerRequest serverBrokerRequest = - serverPinotQuery == pinotQuery ? brokerRequest : CalciteSqlCompiler.convertToBrokerRequest(serverPinotQuery); - boolean hasTableAccess = _accessControlFactory.create().hasAccess(requesterIdentity, serverBrokerRequest); - if (!hasTableAccess) { - _brokerMetrics.addMeteredTableValue(tableName, BrokerMeter.REQUEST_DROPPED_DUE_TO_ACCESS_ERROR, 1); - LOGGER.info("Access denied for request {}: {}, table: {}", requestId, query, tableName); - requestContext.setErrorCode(QueryException.ACCESS_DENIED_ERROR_CODE); - return new BrokerResponseNative(QueryException.ACCESS_DENIED_ERROR); - } - _brokerMetrics.addPhaseTiming(rawTableName, BrokerQueryPhase.AUTHORIZATION, - System.nanoTime() - compilationEndTimeNs); - - // Get the tables hit by the request - String offlineTableName = null; - String realtimeTableName = null; - TableType tableType = TableNameBuilder.getTableTypeFromTableName(tableName); - if (tableType == TableType.OFFLINE) { - // Offline table - if (_routingManager.routingExists(tableName)) { - offlineTableName = tableName; - } - } else if (tableType == TableType.REALTIME) { - // Realtime table - if (_routingManager.routingExists(tableName)) { - realtimeTableName = tableName; - } - } else { - // Hybrid table (check both OFFLINE and REALTIME) - String offlineTableNameToCheck = TableNameBuilder.OFFLINE.tableNameWithType(tableName); - if (_routingManager.routingExists(offlineTableNameToCheck)) { - offlineTableName = offlineTableNameToCheck; - } - String realtimeTableNameToCheck = TableNameBuilder.REALTIME.tableNameWithType(tableName); - if (_routingManager.routingExists(realtimeTableNameToCheck)) { - realtimeTableName = realtimeTableNameToCheck; - } - } - - TableConfig offlineTableConfig = - _tableCache.getTableConfig(TableNameBuilder.OFFLINE.tableNameWithType(rawTableName)); - TableConfig realtimeTableConfig = - _tableCache.getTableConfig(TableNameBuilder.REALTIME.tableNameWithType(rawTableName)); - - if (offlineTableName == null && realtimeTableName == null) { - // No table matches the request - if (realtimeTableConfig == null && offlineTableConfig == null) { - LOGGER.info("Table not found for request {}: {}", requestId, query); - requestContext.setErrorCode(QueryException.TABLE_DOES_NOT_EXIST_ERROR_CODE); - return BrokerResponseNative.TABLE_DOES_NOT_EXIST; - } - LOGGER.info("No table matches for request {}: {}", requestId, query); - requestContext.setErrorCode(QueryException.BROKER_RESOURCE_MISSING_ERROR_CODE); - _brokerMetrics.addMeteredGlobalValue(BrokerMeter.RESOURCE_MISSING_EXCEPTIONS, 1); - return BrokerResponseNative.NO_TABLE_RESULT; - } - - // Handle query rewrite that can be overridden by the table configs - if (offlineTableName == null) { - offlineTableConfig = null; - } - if (realtimeTableName == null) { - realtimeTableConfig = null; - } - HandlerContext handlerContext = getHandlerContext(offlineTableConfig, realtimeTableConfig); - if (handlerContext._disableGroovy) { - rejectGroovyQuery(serverPinotQuery); - } - if (handlerContext._useApproximateFunction) { - handleApproximateFunctionOverride(serverPinotQuery); - } - - // Validate QPS quota - if (!_queryQuotaManager.acquire(tableName)) { - String errorMessage = - String.format("Request %d: %s exceeds query quota for table: %s", requestId, query, tableName); - LOGGER.info(errorMessage); - requestContext.setErrorCode(QueryException.TOO_MANY_REQUESTS_ERROR_CODE); - _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.QUERY_QUOTA_EXCEEDED, 1); - return new BrokerResponseNative(QueryException.getException(QueryException.QUOTA_EXCEEDED_ERROR, errorMessage)); - } - - // Validate the request - try { - validateRequest(serverPinotQuery, _queryResponseLimit); - } catch (Exception e) { - LOGGER.info("Caught exception while validating request {}: {}, {}", requestId, query, e.getMessage()); - requestContext.setErrorCode(QueryException.QUERY_VALIDATION_ERROR_CODE); - _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.QUERY_VALIDATION_EXCEPTIONS, 1); - return new BrokerResponseNative(QueryException.getException(QueryException.QUERY_VALIDATION_ERROR, e)); - } - - _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.QUERIES, 1); - _brokerMetrics.addValueToTableGauge(rawTableName, BrokerGauge.REQUEST_SIZE, query.length()); - - // Prepare OFFLINE and REALTIME requests - BrokerRequest offlineBrokerRequest = null; - BrokerRequest realtimeBrokerRequest = null; - TimeBoundaryInfo timeBoundaryInfo = null; - Schema schema = _tableCache.getSchema(rawTableName); - if (offlineTableName != null && realtimeTableName != null) { - // Time boundary info might be null when there is no segment in the offline table, query real-time side only - timeBoundaryInfo = _routingManager.getTimeBoundaryInfo(offlineTableName); - if (timeBoundaryInfo == null) { - LOGGER.debug("No time boundary info found for hybrid table: {}", rawTableName); - offlineTableName = null; - } - } - if (offlineTableName != null && realtimeTableName != null) { - // Hybrid - PinotQuery offlinePinotQuery = serverPinotQuery.deepCopy(); - offlinePinotQuery.getDataSource().setTableName(offlineTableName); - attachTimeBoundary(offlinePinotQuery, timeBoundaryInfo, true); - handleExpressionOverride(offlinePinotQuery, _tableCache.getExpressionOverrideMap(offlineTableName)); - handleTimestampIndexOverride(offlinePinotQuery, offlineTableConfig); - _queryOptimizer.optimize(offlinePinotQuery, offlineTableConfig, schema); - offlineBrokerRequest = CalciteSqlCompiler.convertToBrokerRequest(offlinePinotQuery); - - PinotQuery realtimePinotQuery = serverPinotQuery.deepCopy(); - realtimePinotQuery.getDataSource().setTableName(realtimeTableName); - attachTimeBoundary(realtimePinotQuery, timeBoundaryInfo, false); - handleExpressionOverride(realtimePinotQuery, _tableCache.getExpressionOverrideMap(realtimeTableName)); - handleTimestampIndexOverride(realtimePinotQuery, realtimeTableConfig); - _queryOptimizer.optimize(realtimePinotQuery, realtimeTableConfig, schema); - realtimeBrokerRequest = CalciteSqlCompiler.convertToBrokerRequest(realtimePinotQuery); - - requestContext.setFanoutType(RequestContext.FanoutType.HYBRID); - requestContext.setOfflineServerTenant(getServerTenant(offlineTableName)); - requestContext.setRealtimeServerTenant(getServerTenant(realtimeTableName)); - } else if (offlineTableName != null) { - // OFFLINE only - setTableName(serverBrokerRequest, offlineTableName); - handleExpressionOverride(serverPinotQuery, _tableCache.getExpressionOverrideMap(offlineTableName)); - handleTimestampIndexOverride(serverPinotQuery, offlineTableConfig); - _queryOptimizer.optimize(serverPinotQuery, offlineTableConfig, schema); - offlineBrokerRequest = serverBrokerRequest; - - requestContext.setFanoutType(RequestContext.FanoutType.OFFLINE); - requestContext.setOfflineServerTenant(getServerTenant(offlineTableName)); - } else { - // REALTIME only - setTableName(serverBrokerRequest, realtimeTableName); - handleExpressionOverride(serverPinotQuery, _tableCache.getExpressionOverrideMap(realtimeTableName)); - handleTimestampIndexOverride(serverPinotQuery, realtimeTableConfig); - _queryOptimizer.optimize(serverPinotQuery, realtimeTableConfig, schema); - realtimeBrokerRequest = serverBrokerRequest; - - requestContext.setFanoutType(RequestContext.FanoutType.REALTIME); - requestContext.setRealtimeServerTenant(getServerTenant(realtimeTableName)); - } - - // Check if response can be sent without server query evaluation. - if (offlineBrokerRequest != null && isFilterAlwaysFalse(offlineBrokerRequest.getPinotQuery())) { - // We don't need to evaluate offline request - offlineBrokerRequest = null; - } - if (realtimeBrokerRequest != null && isFilterAlwaysFalse(realtimeBrokerRequest.getPinotQuery())) { - // We don't need to evaluate realtime request - realtimeBrokerRequest = null; - } - - if (offlineBrokerRequest == null && realtimeBrokerRequest == null) { - if (pinotQuery.isExplain()) { - // EXPLAIN PLAN results to show that query is evaluated exclusively by Broker. - return BrokerResponseNative.BROKER_ONLY_EXPLAIN_PLAN_OUTPUT; - } - - // Send empty response since we don't need to evaluate either offline or realtime request. - BrokerResponseNative brokerResponse = BrokerResponseNative.empty(); - // Extract source info from incoming request - _queryLogger.log(new QueryLogger.QueryLogParams(requestId, query, requestContext, tableName, 0, new ServerStats(), - brokerResponse, System.nanoTime(), requesterIdentity)); - return brokerResponse; - } - - if (offlineBrokerRequest != null && isFilterAlwaysTrue(offlineBrokerRequest.getPinotQuery())) { - // Drop offline request filter since it is always true - offlineBrokerRequest.getPinotQuery().setFilterExpression(null); - } - if (realtimeBrokerRequest != null && isFilterAlwaysTrue(realtimeBrokerRequest.getPinotQuery())) { - // Drop realtime request filter since it is always true - realtimeBrokerRequest.getPinotQuery().setFilterExpression(null); - } - - // Calculate routing table for the query - // TODO: Modify RoutingManager interface to directly take PinotQuery - long routingStartTimeNs = System.nanoTime(); - Map> offlineRoutingTable = null; - Map> realtimeRoutingTable = null; - List unavailableSegments = new ArrayList<>(); - int numPrunedSegmentsTotal = 0; - if (offlineBrokerRequest != null) { - // NOTE: Routing table might be null if table is just removed - RoutingTable routingTable = _routingManager.getRoutingTable(offlineBrokerRequest, requestId); - if (routingTable != null) { - unavailableSegments.addAll(routingTable.getUnavailableSegments()); - Map> serverInstanceToSegmentsMap = routingTable.getServerInstanceToSegmentsMap(); - if (!serverInstanceToSegmentsMap.isEmpty()) { - offlineRoutingTable = serverInstanceToSegmentsMap; - } else { - offlineBrokerRequest = null; - } - numPrunedSegmentsTotal += routingTable.getNumPrunedSegments(); - } else { - offlineBrokerRequest = null; - } - } - if (realtimeBrokerRequest != null) { - // NOTE: Routing table might be null if table is just removed - RoutingTable routingTable = _routingManager.getRoutingTable(realtimeBrokerRequest, requestId); - if (routingTable != null) { - unavailableSegments.addAll(routingTable.getUnavailableSegments()); - Map> serverInstanceToSegmentsMap = routingTable.getServerInstanceToSegmentsMap(); - if (!serverInstanceToSegmentsMap.isEmpty()) { - realtimeRoutingTable = serverInstanceToSegmentsMap; - } else { - realtimeBrokerRequest = null; - } - numPrunedSegmentsTotal += routingTable.getNumPrunedSegments(); - } else { - realtimeBrokerRequest = null; - } - } - int numUnavailableSegments = unavailableSegments.size(); - requestContext.setNumUnavailableSegments(numUnavailableSegments); - - List exceptions = new ArrayList<>(); - if (numUnavailableSegments > 0) { - String errorMessage; - if (numUnavailableSegments > MAX_UNAVAILABLE_SEGMENTS_TO_PRINT_IN_QUERY_EXCEPTION) { - errorMessage = String.format("%d segments unavailable, sampling %d: %s", numUnavailableSegments, - MAX_UNAVAILABLE_SEGMENTS_TO_PRINT_IN_QUERY_EXCEPTION, - unavailableSegments.subList(0, MAX_UNAVAILABLE_SEGMENTS_TO_PRINT_IN_QUERY_EXCEPTION)); - } else { - errorMessage = String.format("%d segments unavailable: %s", numUnavailableSegments, unavailableSegments); - } - exceptions.add(QueryException.getException(QueryException.BROKER_SEGMENT_UNAVAILABLE_ERROR, errorMessage)); - } - - if (offlineBrokerRequest == null && realtimeBrokerRequest == null) { - LOGGER.info("No server found for request {}: {}", requestId, query); - _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.NO_SERVER_FOUND_EXCEPTIONS, 1); - return new BrokerResponseNative(exceptions); - } - long routingEndTimeNs = System.nanoTime(); - _brokerMetrics.addPhaseTiming(rawTableName, BrokerQueryPhase.QUERY_ROUTING, routingEndTimeNs - routingStartTimeNs); - - // Set timeout in the requests - long timeSpentMs = TimeUnit.NANOSECONDS.toMillis(routingEndTimeNs - compilationStartTimeNs); - // Remaining time in milliseconds for the server query execution - // NOTE: For hybrid use case, in most cases offline table and real-time table should have the same query timeout - // configured, but if necessary, we also allow different timeout for them. - // If the timeout is not the same for offline table and real-time table, use the max of offline table - // remaining time and realtime table remaining time. Server side will have different remaining time set for - // each table type, and broker should wait for both types to return. - long remainingTimeMs = 0; - try { - if (offlineBrokerRequest != null) { - remainingTimeMs = - setQueryTimeout(offlineTableName, offlineBrokerRequest.getPinotQuery().getQueryOptions(), timeSpentMs); - } - if (realtimeBrokerRequest != null) { - remainingTimeMs = Math.max(remainingTimeMs, - setQueryTimeout(realtimeTableName, realtimeBrokerRequest.getPinotQuery().getQueryOptions(), timeSpentMs)); - } - } catch (TimeoutException e) { - String errorMessage = e.getMessage(); - LOGGER.info("{} {}: {}", errorMessage, requestId, query); - _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.REQUEST_TIMEOUT_BEFORE_SCATTERED_EXCEPTIONS, 1); - exceptions.add(QueryException.getException(QueryException.BROKER_TIMEOUT_ERROR, errorMessage)); - return new BrokerResponseNative(exceptions); - } - - // Execute the query - // TODO: Replace ServerStats with ServerRoutingStatsEntry. - ServerStats serverStats = new ServerStats(); - // TODO: Handle broker specific operations for explain plan queries such as: - // - Alias handling - // - Compile time function invocation - // - Literal only queries - // - Any rewrites - if (pinotQuery.isExplain()) { - // Update routing tables to only send request to offline servers for OFFLINE and HYBRID tables. - // TODO: Assess if the Explain Plan Query should also be routed to REALTIME servers for HYBRID tables - if (offlineRoutingTable != null) { - // For OFFLINE and HYBRID tables, don't send EXPLAIN query to realtime servers. - realtimeBrokerRequest = null; - realtimeRoutingTable = null; - } - } - BrokerResponseNative brokerResponse; - if (_queriesById != null) { - // Start to track the running query for cancellation just before sending it out to servers to avoid any potential - // failures that could happen before sending it out, like failures to calculate the routing table etc. - // TODO: Even tracking the query as late as here, a potential race condition between calling cancel API and - // query being sent out to servers can still happen. If cancel request arrives earlier than query being - // sent out to servers, the servers miss the cancel request and continue to run the queries. The users - // can always list the running queries and cancel query again until it ends. Just that such race - // condition makes cancel API less reliable. This should be rare as it assumes sending queries out to - // servers takes time, but will address later if needed. - _queriesById.put(requestId, new QueryServers(query, offlineRoutingTable, realtimeRoutingTable)); - LOGGER.debug("Keep track of running query: {}", requestId); - try { - brokerResponse = processBrokerRequest(requestId, brokerRequest, serverBrokerRequest, offlineBrokerRequest, - offlineRoutingTable, realtimeBrokerRequest, realtimeRoutingTable, remainingTimeMs, serverStats, - requestContext); - } finally { - _queriesById.remove(requestId); - LOGGER.debug("Remove track of running query: {}", requestId); - } - } else { - brokerResponse = - processBrokerRequest(requestId, brokerRequest, serverBrokerRequest, offlineBrokerRequest, offlineRoutingTable, - realtimeBrokerRequest, realtimeRoutingTable, remainingTimeMs, serverStats, requestContext); - } - - brokerResponse.setExceptions(exceptions); - brokerResponse.setNumSegmentsPrunedByBroker(numPrunedSegmentsTotal); - long executionEndTimeNs = System.nanoTime(); - _brokerMetrics.addPhaseTiming(rawTableName, BrokerQueryPhase.QUERY_EXECUTION, - executionEndTimeNs - routingEndTimeNs); - - // Track number of queries with number of groups limit reached - if (brokerResponse.isNumGroupsLimitReached()) { - _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.BROKER_RESPONSES_WITH_NUM_GROUPS_LIMIT_REACHED, 1); - } - - // Set total query processing time - long totalTimeMs = TimeUnit.NANOSECONDS.toMillis(executionEndTimeNs - compilationStartTimeNs); - brokerResponse.setTimeUsedMs(totalTimeMs); - requestContext.setQueryProcessingTime(totalTimeMs); - augmentStatistics(requestContext, brokerResponse); - _brokerMetrics.addTimedTableValue(rawTableName, BrokerTimer.QUERY_TOTAL_TIME_MS, totalTimeMs, - TimeUnit.MILLISECONDS); - - // Extract source info from incoming request - _queryLogger.log( - new QueryLogger.QueryLogParams(requestId, query, requestContext, tableName, numUnavailableSegments, serverStats, - brokerResponse, totalTimeMs, requesterIdentity)); - return brokerResponse; - } - - private void handleTimestampIndexOverride(PinotQuery pinotQuery, @Nullable TableConfig tableConfig) { - if (tableConfig == null || tableConfig.getFieldConfigList() == null) { - return; - } - - Set timestampIndexColumns = _tableCache.getTimestampIndexColumns(tableConfig.getTableName()); - if (CollectionUtils.isEmpty(timestampIndexColumns)) { - return; - } - for (Expression expression : pinotQuery.getSelectList()) { - setTimestampIndexExpressionOverrideHints(expression, timestampIndexColumns, pinotQuery); - } - setTimestampIndexExpressionOverrideHints(pinotQuery.getFilterExpression(), timestampIndexColumns, pinotQuery); - setTimestampIndexExpressionOverrideHints(pinotQuery.getHavingExpression(), timestampIndexColumns, pinotQuery); - List groupByList = pinotQuery.getGroupByList(); - if (CollectionUtils.isNotEmpty(groupByList)) { - groupByList.forEach( - expression -> setTimestampIndexExpressionOverrideHints(expression, timestampIndexColumns, pinotQuery)); - } - List orderByList = pinotQuery.getOrderByList(); - if (CollectionUtils.isNotEmpty(orderByList)) { - orderByList.forEach( - expression -> setTimestampIndexExpressionOverrideHints(expression, timestampIndexColumns, pinotQuery)); - } - } - - private void setTimestampIndexExpressionOverrideHints(@Nullable Expression expression, - Set timestampIndexColumns, PinotQuery pinotQuery) { - if (expression == null || expression.getFunctionCall() == null) { - return; - } - Function function = expression.getFunctionCall(); - switch (function.getOperator()) { - case "datetrunc": - String granularString = function.getOperands().get(0).getLiteral().getStringValue().toUpperCase(); - Expression timeExpression = function.getOperands().get(1); - if (((function.getOperandsSize() == 2) || (function.getOperandsSize() == 3 && "MILLISECONDS".equalsIgnoreCase( - function.getOperands().get(2).getLiteral().getStringValue()))) && TimestampIndexUtils.isValidGranularity( - granularString) && timeExpression.getIdentifier() != null) { - String timeColumn = timeExpression.getIdentifier().getName(); - String timeColumnWithGranularity = TimestampIndexUtils.getColumnWithGranularity(timeColumn, granularString); - if (timestampIndexColumns.contains(timeColumnWithGranularity)) { - pinotQuery.putToExpressionOverrideHints(expression, - RequestUtils.getIdentifierExpression(timeColumnWithGranularity)); - } - } - break; - default: - break; - } - function.getOperands() - .forEach(operand -> setTimestampIndexExpressionOverrideHints(operand, timestampIndexColumns, pinotQuery)); - } - - /** Given a {@link PinotQuery}, check if the WHERE clause will always evaluate to false. */ - private boolean isFilterAlwaysFalse(PinotQuery pinotQuery) { - return FALSE.equals(pinotQuery.getFilterExpression()); - } - - /** Given a {@link PinotQuery}, check if the WHERE clause will always evaluate to true. */ - private boolean isFilterAlwaysTrue(PinotQuery pinotQuery) { - return TRUE.equals(pinotQuery.getFilterExpression()); - } - - private String getServerTenant(String tableNameWithType) { - TableConfig tableConfig = _tableCache.getTableConfig(tableNameWithType); - if (tableConfig == null) { - LOGGER.debug("Table config is not available for table {}", tableNameWithType); - return "unknownTenant"; - } - return tableConfig.getTenantConfig().getServer(); - } - - /** - * Handles the subquery in the given query. - *

Currently only supports subquery within the filter. - */ - private void handleSubquery(PinotQuery pinotQuery, long requestId, JsonNode jsonRequest, - @Nullable RequesterIdentity requesterIdentity, RequestContext requestContext) - throws Exception { - Expression filterExpression = pinotQuery.getFilterExpression(); - if (filterExpression != null) { - handleSubquery(filterExpression, requestId, jsonRequest, requesterIdentity, requestContext); - } - } - - /** - * Handles the subquery in the given expression. - *

When subquery is detected, first executes the subquery and gets the response, then rewrites the expression with - * the subquery response. - *

Currently only supports ID_SET subquery within the IN_SUBQUERY transform function, which will be rewritten to an - * IN_ID_SET transform function. - */ - private void handleSubquery(Expression expression, long requestId, JsonNode jsonRequest, - @Nullable RequesterIdentity requesterIdentity, RequestContext requestContext) - throws Exception { - Function function = expression.getFunctionCall(); - if (function == null) { - return; - } - List operands = function.getOperands(); - if (function.getOperator().equals(IN_SUBQUERY)) { - Preconditions.checkState(operands.size() == 2, "IN_SUBQUERY requires 2 arguments: expression, subquery"); - Literal subqueryLiteral = operands.get(1).getLiteral(); - Preconditions.checkState(subqueryLiteral != null, "Second argument of IN_SUBQUERY must be a literal (subquery)"); - String subquery = subqueryLiteral.getStringValue(); - BrokerResponseNative response = - handleRequest(requestId, subquery, null, jsonRequest, requesterIdentity, requestContext); - if (response.getExceptionsSize() != 0) { - throw new RuntimeException("Caught exception while executing subquery: " + subquery); - } - String serializedIdSet = (String) response.getResultTable().getRows().get(0)[0]; - function.setOperator(IN_ID_SET); - operands.set(1, RequestUtils.getLiteralExpression(serializedIdSet)); - } else { - for (Expression operand : operands) { - handleSubquery(operand, requestId, jsonRequest, requesterIdentity, requestContext); - } - } - } - - /** - * Resolves the actual table name for: - * - Case-insensitive cluster - * - Table name in the format of [database_name].[table_name] - * - * @param tableName the table name in the query - * @param tableCache the table case-sensitive cache - * @return table name if the table name is found in Pinot registry, drop the database_name in the format - * of [database_name].[table_name] if only [table_name] is found in Pinot registry. - */ - @VisibleForTesting - static String getActualTableName(String tableName, TableCache tableCache) { - String actualTableName = tableCache.getActualTableName(tableName); - if (actualTableName != null) { - return actualTableName; - } - - // Check if table is in the format of [database_name].[table_name] - String[] tableNameSplits = StringUtils.split(tableName, ".", 2); - if (tableNameSplits.length == 2) { - actualTableName = tableCache.getActualTableName(tableNameSplits[1]); - if (actualTableName != null) { - return actualTableName; - } - } - return tableName; - } - - /** - * Retrieve segment partitioned columns for a table. - * For a hybrid table, a segment partitioned column has to be the intersection of both offline and realtime tables. - * - * @param tableCache - * @param tableName - * @return segment partitioned columns belong to both offline and realtime tables. - */ - private static Set getSegmentPartitionedColumns(TableCache tableCache, String tableName) { - final TableConfig offlineTableConfig = - tableCache.getTableConfig(TableNameBuilder.OFFLINE.tableNameWithType(tableName)); - final TableConfig realtimeTableConfig = - tableCache.getTableConfig(TableNameBuilder.REALTIME.tableNameWithType(tableName)); - if (offlineTableConfig == null) { - return getSegmentPartitionedColumns(realtimeTableConfig); - } - if (realtimeTableConfig == null) { - return getSegmentPartitionedColumns(offlineTableConfig); - } - Set segmentPartitionedColumns = getSegmentPartitionedColumns(offlineTableConfig); - segmentPartitionedColumns.retainAll(getSegmentPartitionedColumns(realtimeTableConfig)); - return segmentPartitionedColumns; - } - - private static Set getSegmentPartitionedColumns(@Nullable TableConfig tableConfig) { - Set segmentPartitionedColumns = new HashSet<>(); - if (tableConfig == null) { - return segmentPartitionedColumns; - } - List fieldConfigs = tableConfig.getFieldConfigList(); - if (fieldConfigs != null) { - for (FieldConfig fieldConfig : fieldConfigs) { - if (fieldConfig.getProperties() != null && Boolean.parseBoolean( - fieldConfig.getProperties().get(FieldConfig.IS_SEGMENT_PARTITIONED_COLUMN_KEY))) { - segmentPartitionedColumns.add(fieldConfig.getName()); - } - } - } - return segmentPartitionedColumns; - } - - /** - * Sets the table name in the given broker request. - * NOTE: Set table name in broker request because it is used for access control, query routing etc. - */ - private void setTableName(BrokerRequest brokerRequest, String tableName) { - brokerRequest.getQuerySource().setTableName(tableName); - brokerRequest.getPinotQuery().getDataSource().setTableName(tableName); - } - - /** - * Sets HyperLogLog log2m for DistinctCountHLL functions if not explicitly set for the given query. - */ - private static void handleHLLLog2mOverride(PinotQuery pinotQuery, int hllLog2mOverride) { - List selectList = pinotQuery.getSelectList(); - for (Expression expression : selectList) { - handleHLLLog2mOverride(expression, hllLog2mOverride); - } - List orderByList = pinotQuery.getOrderByList(); - if (orderByList != null) { - for (Expression expression : orderByList) { - // NOTE: Order-by is always a Function with the ordering of the Expression - handleHLLLog2mOverride(expression.getFunctionCall().getOperands().get(0), hllLog2mOverride); - } - } - Expression havingExpression = pinotQuery.getHavingExpression(); - if (havingExpression != null) { - handleHLLLog2mOverride(havingExpression, hllLog2mOverride); - } - } - - /** - * Sets HyperLogLog log2m for DistinctCountHLL functions if not explicitly set for the given expression. - */ - private static void handleHLLLog2mOverride(Expression expression, int hllLog2mOverride) { - Function function = expression.getFunctionCall(); - if (function == null) { - return; - } - switch (function.getOperator()) { - case "distinctcounthll": - case "distinctcounthllmv": - case "distinctcountrawhll": - case "distinctcountrawhllmv": - if (function.getOperandsSize() == 1) { - function.addToOperands(RequestUtils.getLiteralExpression(hllLog2mOverride)); - } - return; - default: - break; - } - for (Expression operand : function.getOperands()) { - handleHLLLog2mOverride(operand, hllLog2mOverride); - } - } - - /** - * Overrides the LIMIT of the given query if it exceeds the query limit. - */ - @VisibleForTesting - static void handleQueryLimitOverride(PinotQuery pinotQuery, int queryLimit) { - if (queryLimit > 0 && pinotQuery.getLimit() > queryLimit) { - pinotQuery.setLimit(queryLimit); - } - } - - /** - * Rewrites 'DistinctCount' to 'SegmentPartitionDistinctCount' for the given query. - */ - @VisibleForTesting - static void handleSegmentPartitionedDistinctCountOverride(PinotQuery pinotQuery, - Set segmentPartitionedColumns) { - if (segmentPartitionedColumns.isEmpty()) { - return; - } - for (Expression expression : pinotQuery.getSelectList()) { - handleSegmentPartitionedDistinctCountOverride(expression, segmentPartitionedColumns); - } - List orderByExpressions = pinotQuery.getOrderByList(); - if (orderByExpressions != null) { - for (Expression expression : orderByExpressions) { - // NOTE: Order-by is always a Function with the ordering of the Expression - handleSegmentPartitionedDistinctCountOverride(expression.getFunctionCall().getOperands().get(0), - segmentPartitionedColumns); - } - } - Expression havingExpression = pinotQuery.getHavingExpression(); - if (havingExpression != null) { - handleSegmentPartitionedDistinctCountOverride(havingExpression, segmentPartitionedColumns); - } - } - - /** - * Rewrites 'DistinctCount' to 'SegmentPartitionDistinctCount' for the given expression. - */ - private static void handleSegmentPartitionedDistinctCountOverride(Expression expression, - Set segmentPartitionedColumns) { - Function function = expression.getFunctionCall(); - if (function == null) { - return; - } - if (function.getOperator().equals("distinctcount")) { - List operands = function.getOperands(); - if (operands.size() == 1 && operands.get(0).isSetIdentifier() && segmentPartitionedColumns.contains( - operands.get(0).getIdentifier().getName())) { - function.setOperator("segmentpartitioneddistinctcount"); - } - } else { - for (Expression operand : function.getOperands()) { - handleSegmentPartitionedDistinctCountOverride(operand, segmentPartitionedColumns); - } - } - } - - /** - * Rewrites 'DistinctCount' to 'DistinctCountBitmap' for the given query. - */ - private static void handleDistinctCountBitmapOverride(PinotQuery pinotQuery) { - for (Expression expression : pinotQuery.getSelectList()) { - handleDistinctCountBitmapOverride(expression); - } - List orderByExpressions = pinotQuery.getOrderByList(); - if (orderByExpressions != null) { - for (Expression expression : orderByExpressions) { - // NOTE: Order-by is always a Function with the ordering of the Expression - handleDistinctCountBitmapOverride(expression.getFunctionCall().getOperands().get(0)); - } - } - Expression havingExpression = pinotQuery.getHavingExpression(); - if (havingExpression != null) { - handleDistinctCountBitmapOverride(havingExpression); - } - } - - /** - * Rewrites 'DistinctCount' to 'DistinctCountBitmap' for the given expression. - */ - private static void handleDistinctCountBitmapOverride(Expression expression) { - Function function = expression.getFunctionCall(); - if (function == null) { - return; - } - if (function.getOperator().equals("distinctcount")) { - function.setOperator("distinctcountbitmap"); - } else { - for (Expression operand : function.getOperands()) { - handleDistinctCountBitmapOverride(operand); - } - } - } - - private HandlerContext getHandlerContext(@Nullable TableConfig offlineTableConfig, - @Nullable TableConfig realtimeTableConfig) { - Boolean disableGroovyOverride = null; - Boolean useApproximateFunctionOverride = null; - if (offlineTableConfig != null && offlineTableConfig.getQueryConfig() != null) { - QueryConfig offlineTableQueryConfig = offlineTableConfig.getQueryConfig(); - Boolean disableGroovyOfflineTableOverride = offlineTableQueryConfig.getDisableGroovy(); - if (disableGroovyOfflineTableOverride != null) { - disableGroovyOverride = disableGroovyOfflineTableOverride; - } - Boolean useApproximateFunctionOfflineTableOverride = offlineTableQueryConfig.getUseApproximateFunction(); - if (useApproximateFunctionOfflineTableOverride != null) { - useApproximateFunctionOverride = useApproximateFunctionOfflineTableOverride; - } - } - if (realtimeTableConfig != null && realtimeTableConfig.getQueryConfig() != null) { - QueryConfig realtimeTableQueryConfig = realtimeTableConfig.getQueryConfig(); - Boolean disableGroovyRealtimeTableOverride = realtimeTableQueryConfig.getDisableGroovy(); - if (disableGroovyRealtimeTableOverride != null) { - if (disableGroovyOverride == null) { - disableGroovyOverride = disableGroovyRealtimeTableOverride; - } else { - // Disable Groovy if either offline or realtime table config disables Groovy - disableGroovyOverride |= disableGroovyRealtimeTableOverride; - } - } - Boolean useApproximateFunctionRealtimeTableOverride = realtimeTableQueryConfig.getUseApproximateFunction(); - if (useApproximateFunctionRealtimeTableOverride != null) { - if (useApproximateFunctionOverride == null) { - useApproximateFunctionOverride = useApproximateFunctionRealtimeTableOverride; - } else { - // Use approximate function if both offline and realtime table config uses approximate function - useApproximateFunctionOverride &= useApproximateFunctionRealtimeTableOverride; - } - } - } - - boolean disableGroovy = disableGroovyOverride != null ? disableGroovyOverride : _disableGroovy; - boolean useApproximateFunction = - useApproximateFunctionOverride != null ? useApproximateFunctionOverride : _useApproximateFunction; - return new HandlerContext(disableGroovy, useApproximateFunction); - } - - private static class HandlerContext { - final boolean _disableGroovy; - final boolean _useApproximateFunction; - - HandlerContext(boolean disableGroovy, boolean useApproximateFunction) { - _disableGroovy = disableGroovy; - _useApproximateFunction = useApproximateFunction; - } - } - - /** - * Verifies that no groovy is present in the PinotQuery when disabled. - */ - @VisibleForTesting - static void rejectGroovyQuery(PinotQuery pinotQuery) { - List selectList = pinotQuery.getSelectList(); - for (Expression expression : selectList) { - rejectGroovyQuery(expression); - } - List orderByList = pinotQuery.getOrderByList(); - if (orderByList != null) { - for (Expression expression : orderByList) { - // NOTE: Order-by is always a Function with the ordering of the Expression - rejectGroovyQuery(expression.getFunctionCall().getOperands().get(0)); - } - } - Expression havingExpression = pinotQuery.getHavingExpression(); - if (havingExpression != null) { - rejectGroovyQuery(havingExpression); - } - Expression filterExpression = pinotQuery.getFilterExpression(); - if (filterExpression != null) { - rejectGroovyQuery(filterExpression); - } - List groupByList = pinotQuery.getGroupByList(); - if (groupByList != null) { - for (Expression expression : groupByList) { - rejectGroovyQuery(expression); - } - } - } - - private static void rejectGroovyQuery(Expression expression) { - Function function = expression.getFunctionCall(); - if (function == null) { - return; - } - if (function.getOperator().equals("groovy")) { - throw new BadQueryRequestException("Groovy transform functions are disabled for queries"); - } - for (Expression operandExpression : function.getOperands()) { - rejectGroovyQuery(operandExpression); - } - } - - /** - * Rewrites potential expensive functions to their approximation counterparts. - * - DISTINCT_COUNT -> DISTINCT_COUNT_SMART_HLL - * - PERCENTILE -> PERCENTILE_SMART_TDIGEST - */ - @VisibleForTesting - static void handleApproximateFunctionOverride(PinotQuery pinotQuery) { - for (Expression expression : pinotQuery.getSelectList()) { - handleApproximateFunctionOverride(expression); - } - List orderByExpressions = pinotQuery.getOrderByList(); - if (orderByExpressions != null) { - for (Expression expression : orderByExpressions) { - // NOTE: Order-by is always a Function with the ordering of the Expression - handleApproximateFunctionOverride(expression.getFunctionCall().getOperands().get(0)); - } - } - Expression havingExpression = pinotQuery.getHavingExpression(); - if (havingExpression != null) { - handleApproximateFunctionOverride(havingExpression); - } - } - - private static void handleApproximateFunctionOverride(Expression expression) { - Function function = expression.getFunctionCall(); - if (function == null) { - return; - } - String functionName = function.getOperator(); - if (functionName.equals("distinctcount") || functionName.equals("distinctcountmv")) { - function.setOperator("distinctcountsmarthll"); - } else if (functionName.startsWith("percentile")) { - String remainingFunctionName = functionName.substring(10); - if (remainingFunctionName.isEmpty() || remainingFunctionName.equals("mv")) { - function.setOperator("percentilesmarttdigest"); - } else if (remainingFunctionName.matches("\\d+")) { - try { - int percentile = Integer.parseInt(remainingFunctionName); - function.setOperator("percentilesmarttdigest"); - function.setOperands( - Arrays.asList(function.getOperands().get(0), RequestUtils.getLiteralExpression(percentile))); - } catch (Exception e) { - throw new BadQueryRequestException("Illegal function name: " + functionName); - } - } else if (remainingFunctionName.matches("\\d+mv")) { - try { - int percentile = Integer.parseInt(remainingFunctionName.substring(0, remainingFunctionName.length() - 2)); - function.setOperator("percentilesmarttdigest"); - function.setOperands( - Arrays.asList(function.getOperands().get(0), RequestUtils.getLiteralExpression(percentile))); - } catch (Exception e) { - throw new BadQueryRequestException("Illegal function name: " + functionName); - } - } - } else { - for (Expression operand : function.getOperands()) { - handleApproximateFunctionOverride(operand); - } - } - } - - private static void handleExpressionOverride(PinotQuery pinotQuery, - @Nullable Map expressionOverrideMap) { - if (expressionOverrideMap == null) { - return; - } - pinotQuery.getSelectList().replaceAll(o -> handleExpressionOverride(o, expressionOverrideMap)); - Expression filterExpression = pinotQuery.getFilterExpression(); - if (filterExpression != null) { - pinotQuery.setFilterExpression(handleExpressionOverride(filterExpression, expressionOverrideMap)); - } - List groupByList = pinotQuery.getGroupByList(); - if (groupByList != null) { - groupByList.replaceAll(o -> handleExpressionOverride(o, expressionOverrideMap)); - } - List orderByList = pinotQuery.getOrderByList(); - if (orderByList != null) { - for (Expression expression : orderByList) { - // NOTE: Order-by is always a Function with the ordering of the Expression - expression.getFunctionCall().getOperands().replaceAll(o -> handleExpressionOverride(o, expressionOverrideMap)); - } - } - Expression havingExpression = pinotQuery.getHavingExpression(); - if (havingExpression != null) { - pinotQuery.setHavingExpression(handleExpressionOverride(havingExpression, expressionOverrideMap)); - } - } - - private static Expression handleExpressionOverride(Expression expression, - Map expressionOverrideMap) { - Expression override = expressionOverrideMap.get(expression); - if (override != null) { - return new Expression(override); - } - Function function = expression.getFunctionCall(); - if (function != null) { - function.getOperands().replaceAll(o -> handleExpressionOverride(o, expressionOverrideMap)); - } - return expression; - } - - /** - * Returns {@code true} if the given query only contains literals, {@code false} otherwise. - */ - @VisibleForTesting - static boolean isLiteralOnlyQuery(PinotQuery pinotQuery) { - for (Expression expression : pinotQuery.getSelectList()) { - if (!CalciteSqlParser.isLiteralOnlyExpression(expression)) { - return false; - } + _brokerQueryEventListener.onQueryCompletion(requestContext); + throw new BadQueryRequestException("Failed to find 'sql' in the request: " + request); } - return true; - } - /** - * Processes the literal only query. - */ - private BrokerResponseNative processLiteralOnlyQuery(PinotQuery pinotQuery, long compilationStartTimeNs, - RequestContext requestContext) { - BrokerResponseNative brokerResponse = new BrokerResponseNative(); - List columnNames = new ArrayList<>(); - List columnTypes = new ArrayList<>(); - List row = new ArrayList<>(); - for (Expression expression : pinotQuery.getSelectList()) { - computeResultsForExpression(expression, columnNames, columnTypes, row); - } - DataSchema dataSchema = - new DataSchema(columnNames.toArray(new String[0]), columnTypes.toArray(new DataSchema.ColumnDataType[0])); - List rows = new ArrayList<>(); - rows.add(row.toArray()); - ResultTable resultTable = new ResultTable(dataSchema, rows); - brokerResponse.setResultTable(resultTable); + String query = sql.textValue(); + requestContext.setQuery(query); + BrokerResponse brokerResponse = + handleRequest(requestId, query, sqlNodeAndOptions, request, requesterIdentity, requestContext, httpHeaders, + accessControl); + brokerResponse.setBrokerId(_brokerId); + brokerResponse.setRequestId(Long.toString(requestId)); + _brokerQueryEventListener.onQueryCompletion(requestContext); - long totalTimeMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - compilationStartTimeNs); - brokerResponse.setTimeUsedMs(totalTimeMs); - requestContext.setQueryProcessingTime(totalTimeMs); - augmentStatistics(requestContext, brokerResponse); return brokerResponse; } - // TODO(xiangfu): Move Literal function computation here from Calcite Parser. - private void computeResultsForExpression(Expression e, List columnNames, - List columnTypes, List row) { - if (e.getType() == ExpressionType.LITERAL) { - computeResultsForLiteral(e.getLiteral(), columnNames, columnTypes, row); - } - if (e.getType() == ExpressionType.FUNCTION) { - if (e.getFunctionCall().getOperator().equals("as")) { - String columnName = e.getFunctionCall().getOperands().get(1).getIdentifier().getName(); - computeResultsForExpression(e.getFunctionCall().getOperands().get(0), columnNames, columnTypes, row); - columnNames.set(columnNames.size() - 1, columnName); - } else { - throw new IllegalStateException( - "No able to compute results for function - " + e.getFunctionCall().getOperator()); - } - } - } - - private void computeResultsForLiteral(Literal literal, List columnNames, - List columnTypes, List row) { - Object fieldValue = literal.getFieldValue(); - columnNames.add(fieldValue.toString()); - switch (literal.getSetField()) { - case BOOL_VALUE: - columnTypes.add(DataSchema.ColumnDataType.BOOLEAN); - row.add(literal.getBoolValue()); - break; - case BYTE_VALUE: - columnTypes.add(DataSchema.ColumnDataType.INT); - row.add((int) literal.getByteValue()); - break; - case SHORT_VALUE: - columnTypes.add(DataSchema.ColumnDataType.INT); - row.add((int) literal.getShortValue()); - break; - case INT_VALUE: - columnTypes.add(DataSchema.ColumnDataType.INT); - row.add(literal.getIntValue()); - break; - case LONG_VALUE: - columnTypes.add(DataSchema.ColumnDataType.LONG); - row.add(literal.getLongValue()); - break; - case DOUBLE_VALUE: - columnTypes.add(DataSchema.ColumnDataType.DOUBLE); - row.add(literal.getDoubleValue()); - break; - case STRING_VALUE: - columnTypes.add(DataSchema.ColumnDataType.STRING); - row.add(literal.getStringValue()); - break; - case BINARY_VALUE: - columnTypes.add(DataSchema.ColumnDataType.BYTES); - row.add(BytesUtils.toHexString(literal.getBinaryValue())); - break; - default: - break; - } - } - - /** - * Fixes the column names to the actual column names in the given query. - */ - @VisibleForTesting - static void updateColumnNames(String rawTableName, PinotQuery pinotQuery, boolean isCaseInsensitive, - Map columnNameMap) { - Map aliasMap = new HashMap<>(); - if (pinotQuery != null) { - boolean hasStar = false; - for (Expression expression : pinotQuery.getSelectList()) { - fixColumnName(rawTableName, expression, columnNameMap, aliasMap, isCaseInsensitive); - //check if the select expression is '*' - if (!hasStar && expression.equals(STAR)) { - hasStar = true; - } - } - //if query has a '*' selection along with other columns - if (hasStar) { - expandStarExpressionsToActualColumns(pinotQuery, columnNameMap); - } - Expression filterExpression = pinotQuery.getFilterExpression(); - if (filterExpression != null) { - fixColumnName(rawTableName, filterExpression, columnNameMap, aliasMap, isCaseInsensitive); - } - List groupByList = pinotQuery.getGroupByList(); - if (groupByList != null) { - for (Expression expression : groupByList) { - fixColumnName(rawTableName, expression, columnNameMap, aliasMap, isCaseInsensitive); - } - } - List orderByList = pinotQuery.getOrderByList(); - if (orderByList != null) { - for (Expression expression : orderByList) { - // NOTE: Order-by is always a Function with the ordering of the Expression - fixColumnName(rawTableName, expression.getFunctionCall().getOperands().get(0), columnNameMap, aliasMap, - isCaseInsensitive); - } - } - Expression havingExpression = pinotQuery.getHavingExpression(); - if (havingExpression != null) { - fixColumnName(rawTableName, havingExpression, columnNameMap, aliasMap, isCaseInsensitive); - } - } - } - - private static void expandStarExpressionsToActualColumns(PinotQuery pinotQuery, Map columnNameMap) { - List originalSelections = pinotQuery.getSelectList(); - //expand '*' - List expandedSelections = new ArrayList<>(); - for (String tableCol : columnNameMap.values()) { - Expression newSelection = RequestUtils.getIdentifierExpression(tableCol); - //we exclude default virtual columns - if (tableCol.charAt(0) != '$') { - expandedSelections.add(newSelection); - } - } - //sort naturally - expandedSelections.sort(null); - List newSelections = new ArrayList<>(); - for (Expression originalSelection : originalSelections) { - if (originalSelection.equals(STAR)) { - newSelections.addAll(expandedSelections); - } else { - newSelections.add(originalSelection); - } - } - pinotQuery.setSelectList(newSelections); - } - - /** - * Fixes the column names to the actual column names in the given expression. - */ - private static void fixColumnName(String rawTableName, Expression expression, Map columnNameMap, - Map aliasMap, boolean ignoreCase) { - ExpressionType expressionType = expression.getType(); - if (expressionType == ExpressionType.IDENTIFIER) { - Identifier identifier = expression.getIdentifier(); - identifier.setName(getActualColumnName(rawTableName, identifier.getName(), columnNameMap, aliasMap, ignoreCase)); - } else if (expressionType == ExpressionType.FUNCTION) { - final Function functionCall = expression.getFunctionCall(); - switch (functionCall.getOperator()) { - case "as": - fixColumnName(rawTableName, functionCall.getOperands().get(0), columnNameMap, aliasMap, ignoreCase); - final Expression rightAsExpr = functionCall.getOperands().get(1); - if (rightAsExpr.isSetIdentifier()) { - String rightColumn = rightAsExpr.getIdentifier().getName(); - if (ignoreCase) { - aliasMap.put(rightColumn.toLowerCase(), rightColumn); - } else { - aliasMap.put(rightColumn, rightColumn); - } - } - break; - case "lookup": - // LOOKUP function looks up another table's schema, skip the check for now. - break; - default: - for (Expression operand : functionCall.getOperands()) { - fixColumnName(rawTableName, operand, columnNameMap, aliasMap, ignoreCase); - } - break; - } - } - } - - /** - * Returns the actual column name for the given column name for: - * - Case-insensitive cluster - * - Column name in the format of [table_name].[column_name] - */ - @VisibleForTesting - static String getActualColumnName(String rawTableName, String columnName, @Nullable Map columnNameMap, - @Nullable Map aliasMap, boolean ignoreCase) { - if ("*".equals(columnName)) { - return columnName; - } - String columnNameToCheck; - if (columnName.regionMatches(ignoreCase, 0, rawTableName, 0, rawTableName.length()) - && columnName.length() > rawTableName.length() && columnName.charAt(rawTableName.length()) == '.') { - columnNameToCheck = ignoreCase ? columnName.substring(rawTableName.length() + 1).toLowerCase() - : columnName.substring(rawTableName.length() + 1); - } else { - columnNameToCheck = ignoreCase ? columnName.toLowerCase() : columnName; - } - if (columnNameMap != null) { - String actualColumnName = columnNameMap.get(columnNameToCheck); - if (actualColumnName != null) { - return actualColumnName; - } - } - if (aliasMap != null) { - String actualAlias = aliasMap.get(columnNameToCheck); - if (actualAlias != null) { - return actualAlias; - } - } - if (columnName.charAt(0) == '$') { - return columnName; - } - throw new BadQueryRequestException("Unknown columnName '" + columnName + "' found in the query"); - } - - /** - * Helper function to decide whether to force the log - * - * TODO: come up with other criteria for forcing a log and come up with better numbers - */ - private boolean forceLog(BrokerResponse brokerResponse, long totalTimeMs) { - if (brokerResponse.isNumGroupsLimitReached()) { - return true; - } - - if (brokerResponse.getExceptionsSize() > 0) { - return true; - } - - // If response time is more than 1 sec, force the log - return totalTimeMs > 1000L; - } - - /** - * Sets the query timeout (remaining time in milliseconds) into the query options, and returns the remaining time in - * milliseconds. - *

For the overall query timeout, use query-level timeout (in the query options) if exists, or use table-level - * timeout (in the table config) if exists, or use instance-level timeout (in the broker config). - */ - private long setQueryTimeout(String tableNameWithType, Map queryOptions, long timeSpentMs) - throws TimeoutException { - long queryTimeoutMs; - Long queryLevelTimeoutMs = QueryOptionsUtils.getTimeoutMs(queryOptions); - if (queryLevelTimeoutMs != null) { - // Use query-level timeout if exists - queryTimeoutMs = queryLevelTimeoutMs; - } else { - Long tableLevelTimeoutMs = _routingManager.getQueryTimeoutMs(tableNameWithType); - if (tableLevelTimeoutMs != null) { - // Use table-level timeout if exists - queryTimeoutMs = tableLevelTimeoutMs; - } else { - // Use instance-level timeout - queryTimeoutMs = _brokerTimeoutMs; - } - } - - long remainingTimeMs = queryTimeoutMs - timeSpentMs; - if (remainingTimeMs <= 0) { - String errorMessage = - String.format("Query timed out (time spent: %dms, timeout: %dms) for table: %s before scattering the request", - timeSpentMs, queryTimeoutMs, tableNameWithType); - throw new TimeoutException(errorMessage); - } - queryOptions.put(Broker.Request.QueryOptionKey.TIMEOUT_MS, Long.toString(remainingTimeMs)); - return remainingTimeMs; - } - - /** - * Broker side validation on the query. - *

Throw exception if query does not pass validation. - *

Current validations are: - *

    - *
  • Value for 'LIMIT' <= configured value
  • - *
  • Query options must be set to SQL mode
  • - *
  • Check if numReplicaGroupsToQuery option provided is valid
  • - *
- */ - @VisibleForTesting - static void validateRequest(PinotQuery pinotQuery, int queryResponseLimit) { - // Verify LIMIT - int limit = pinotQuery.getLimit(); - if (limit > queryResponseLimit) { - throw new IllegalStateException( - "Value for 'LIMIT' (" + limit + ") exceeds maximum allowed value of " + queryResponseLimit); - } - - Map queryOptions = pinotQuery.getQueryOptions(); - try { - // throw errors if options is less than 1 or invalid - Integer numReplicaGroupsToQuery = QueryOptionsUtils.getNumReplicaGroupsToQuery(queryOptions); - if (numReplicaGroupsToQuery != null) { - Preconditions.checkState(numReplicaGroupsToQuery > 0, "numReplicaGroups must be " + "positive number, got: %d", - numReplicaGroupsToQuery); - } - } catch (NumberFormatException e) { - String numReplicaGroupsToQuery = queryOptions.get(Broker.Request.QueryOptionKey.NUM_REPLICA_GROUPS_TO_QUERY); - throw new IllegalStateException( - String.format("numReplicaGroups must be a positive number, got: %s", numReplicaGroupsToQuery)); - } - - if (pinotQuery.getDataSource().getSubquery() != null) { - validateRequest(pinotQuery.getDataSource().getSubquery(), queryResponseLimit); - } - } - - /** - * Helper method to attach the time boundary to the given PinotQuery. - */ - private static void attachTimeBoundary(PinotQuery pinotQuery, TimeBoundaryInfo timeBoundaryInfo, - boolean isOfflineRequest) { - String timeColumn = timeBoundaryInfo.getTimeColumn(); - String timeValue = timeBoundaryInfo.getTimeValue(); - Expression timeFilterExpression = RequestUtils.getFunctionExpression( - isOfflineRequest ? FilterKind.LESS_THAN_OR_EQUAL.name() : FilterKind.GREATER_THAN.name()); - timeFilterExpression.getFunctionCall().setOperands( - Arrays.asList(RequestUtils.getIdentifierExpression(timeColumn), RequestUtils.getLiteralExpression(timeValue))); - - Expression filterExpression = pinotQuery.getFilterExpression(); - if (filterExpression != null) { - Expression andFilterExpression = RequestUtils.getFunctionExpression(FilterKind.AND.name()); - andFilterExpression.getFunctionCall().setOperands(Arrays.asList(filterExpression, timeFilterExpression)); - pinotQuery.setFilterExpression(andFilterExpression); - } else { - pinotQuery.setFilterExpression(timeFilterExpression); - } - } - - /** - * Processes the optimized broker requests for both OFFLINE and REALTIME table. - * TODO: Directly take PinotQuery - */ - protected abstract BrokerResponseNative processBrokerRequest(long requestId, BrokerRequest originalBrokerRequest, - BrokerRequest serverBrokerRequest, @Nullable BrokerRequest offlineBrokerRequest, - @Nullable Map> offlineRoutingTable, @Nullable BrokerRequest realtimeBrokerRequest, - @Nullable Map> realtimeRoutingTable, long timeoutMs, ServerStats serverStats, - RequestContext requestContext) + protected abstract BrokerResponse handleRequest(long requestId, String query, + @Nullable SqlNodeAndOptions sqlNodeAndOptions, JsonNode request, @Nullable RequesterIdentity requesterIdentity, + RequestContext requestContext, @Nullable HttpHeaders httpHeaders, AccessControl accessControl) throws Exception; protected static void augmentStatistics(RequestContext statistics, BrokerResponse response) { - statistics.setTotalDocs(response.getTotalDocs()); + statistics.setNumRowsResultSet(response.getNumRowsResultSet()); + // TODO: Add partial result flag to RequestContext + List exceptions = response.getExceptions(); + int numExceptions = exceptions.size(); + List processingExceptions = new ArrayList<>(numExceptions); + for (QueryProcessingException exception : exceptions) { + processingExceptions.add(exception.toString()); + } + statistics.setProcessingExceptions(processingExceptions); + statistics.setNumExceptions(numExceptions); + statistics.setNumGroupsLimitReached(response.isNumGroupsLimitReached()); + statistics.setProcessingTimeMillis(response.getTimeUsedMs()); statistics.setNumDocsScanned(response.getNumDocsScanned()); + statistics.setTotalDocs(response.getTotalDocs()); statistics.setNumEntriesScannedInFilter(response.getNumEntriesScannedInFilter()); statistics.setNumEntriesScannedPostFilter(response.getNumEntriesScannedPostFilter()); + statistics.setNumServersQueried(response.getNumServersQueried()); + statistics.setNumServersResponded(response.getNumServersResponded()); statistics.setNumSegmentsQueried(response.getNumSegmentsQueried()); statistics.setNumSegmentsProcessed(response.getNumSegmentsProcessed()); statistics.setNumSegmentsMatched(response.getNumSegmentsMatched()); - statistics.setNumServersQueried(response.getNumServersQueried()); - statistics.setNumSegmentsProcessed(response.getNumSegmentsProcessed()); - statistics.setNumServersResponded(response.getNumServersResponded()); - statistics.setNumGroupsLimitReached(response.isNumGroupsLimitReached()); - statistics.setNumExceptions(response.getExceptionsSize()); + statistics.setNumConsumingSegmentsQueried(response.getNumConsumingSegmentsQueried()); + statistics.setNumConsumingSegmentsProcessed(response.getNumConsumingSegmentsProcessed()); + statistics.setNumConsumingSegmentsMatched(response.getNumConsumingSegmentsMatched()); + statistics.setMinConsumingFreshnessTimeMs(response.getMinConsumingFreshnessTimeMs()); + statistics.setNumSegmentsPrunedByBroker(response.getNumSegmentsPrunedByBroker()); + statistics.setNumSegmentsPrunedByServer(response.getNumSegmentsPrunedByServer()); + statistics.setNumSegmentsPrunedInvalid(response.getNumSegmentsPrunedInvalid()); + statistics.setNumSegmentsPrunedByLimit(response.getNumSegmentsPrunedByLimit()); + statistics.setNumSegmentsPrunedByValue(response.getNumSegmentsPrunedByValue()); + statistics.setReduceTimeMillis(response.getBrokerReduceTimeMs()); statistics.setOfflineThreadCpuTimeNs(response.getOfflineThreadCpuTimeNs()); statistics.setRealtimeThreadCpuTimeNs(response.getRealtimeThreadCpuTimeNs()); statistics.setOfflineSystemActivitiesCpuTimeNs(response.getOfflineSystemActivitiesCpuTimeNs()); @@ -1614,44 +184,8 @@ protected static void augmentStatistics(RequestContext statistics, BrokerRespons statistics.setRealtimeResponseSerializationCpuTimeNs(response.getRealtimeResponseSerializationCpuTimeNs()); statistics.setOfflineTotalCpuTimeNs(response.getOfflineTotalCpuTimeNs()); statistics.setRealtimeTotalCpuTimeNs(response.getRealtimeTotalCpuTimeNs()); - statistics.setNumRowsResultSet(response.getNumRowsResultSet()); - } - - private String getGlobalQueryId(long requestId) { - return _brokerId + "_" + requestId; - } - - /** - * Helper class to pass the per server statistics. - */ - public static class ServerStats { - private String _serverStats; - - public String getServerStats() { - return _serverStats; - } - - public void setServerStats(String serverStats) { - _serverStats = serverStats; - } - } - - /** - * Helper class to track the query plaintext and the requested servers. - */ - private static class QueryServers { - final String _query; - final Set _servers = new HashSet<>(); - - QueryServers(String query, @Nullable Map> offlineRoutingTable, - @Nullable Map> realtimeRoutingTable) { - _query = query; - if (offlineRoutingTable != null) { - _servers.addAll(offlineRoutingTable.keySet()); - } - if (realtimeRoutingTable != null) { - _servers.addAll(realtimeRoutingTable.keySet()); - } - } + statistics.setExplainPlanNumEmptyFilterSegments(response.getExplainPlanNumEmptyFilterSegments()); + statistics.setExplainPlanNumMatchAllFilterSegments(response.getExplainPlanNumMatchAllFilterSegments()); + statistics.setTraceInfo(response.getTraceInfo()); } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseSingleStageBrokerRequestHandler.java b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseSingleStageBrokerRequestHandler.java new file mode 100644 index 000000000000..f2aa21c64512 --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BaseSingleStageBrokerRequestHandler.java @@ -0,0 +1,1850 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.requesthandler; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableMap; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletionService; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import javax.annotation.concurrent.ThreadSafe; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.util.EntityUtils; +import org.apache.pinot.broker.api.AccessControl; +import org.apache.pinot.broker.api.RequesterIdentity; +import org.apache.pinot.broker.broker.AccessControlFactory; +import org.apache.pinot.broker.querylog.QueryLogger; +import org.apache.pinot.broker.queryquota.QueryQuotaManager; +import org.apache.pinot.broker.routing.BrokerRoutingManager; +import org.apache.pinot.common.config.provider.TableCache; +import org.apache.pinot.common.exception.QueryException; +import org.apache.pinot.common.http.MultiHttpRequest; +import org.apache.pinot.common.http.MultiHttpRequestResponse; +import org.apache.pinot.common.metrics.BrokerGauge; +import org.apache.pinot.common.metrics.BrokerMeter; +import org.apache.pinot.common.metrics.BrokerQueryPhase; +import org.apache.pinot.common.metrics.BrokerTimer; +import org.apache.pinot.common.request.BrokerRequest; +import org.apache.pinot.common.request.DataSource; +import org.apache.pinot.common.request.Expression; +import org.apache.pinot.common.request.ExpressionType; +import org.apache.pinot.common.request.Function; +import org.apache.pinot.common.request.Identifier; +import org.apache.pinot.common.request.Literal; +import org.apache.pinot.common.request.PinotQuery; +import org.apache.pinot.common.response.BrokerResponse; +import org.apache.pinot.common.response.ProcessingException; +import org.apache.pinot.common.response.broker.BrokerResponseNative; +import org.apache.pinot.common.response.broker.ResultTable; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.common.utils.DatabaseUtils; +import org.apache.pinot.common.utils.config.QueryOptionsUtils; +import org.apache.pinot.common.utils.request.RequestUtils; +import org.apache.pinot.core.auth.Actions; +import org.apache.pinot.core.auth.TargetType; +import org.apache.pinot.core.query.optimizer.QueryOptimizer; +import org.apache.pinot.core.routing.RoutingTable; +import org.apache.pinot.core.routing.TimeBoundaryInfo; +import org.apache.pinot.core.transport.ServerInstance; +import org.apache.pinot.core.util.GapfillUtils; +import org.apache.pinot.spi.auth.AuthorizationResult; +import org.apache.pinot.spi.config.table.FieldConfig; +import org.apache.pinot.spi.config.table.QueryConfig; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.data.DimensionFieldSpec; +import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.exception.BadQueryRequestException; +import org.apache.pinot.spi.exception.DatabaseConflictException; +import org.apache.pinot.spi.trace.RequestContext; +import org.apache.pinot.spi.trace.Tracing; +import org.apache.pinot.spi.utils.BigDecimalUtils; +import org.apache.pinot.spi.utils.BytesUtils; +import org.apache.pinot.spi.utils.CommonConstants; +import org.apache.pinot.spi.utils.CommonConstants.Broker; +import org.apache.pinot.spi.utils.CommonConstants.Broker.Request.QueryOptionKey; +import org.apache.pinot.spi.utils.DataSizeUtils; +import org.apache.pinot.spi.utils.TimestampIndexUtils; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; +import org.apache.pinot.sql.FilterKind; +import org.apache.pinot.sql.parsers.CalciteSqlCompiler; +import org.apache.pinot.sql.parsers.CalciteSqlParser; +import org.apache.pinot.sql.parsers.SqlNodeAndOptions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +@ThreadSafe +public abstract class BaseSingleStageBrokerRequestHandler extends BaseBrokerRequestHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(BaseSingleStageBrokerRequestHandler.class); + private static final String IN_SUBQUERY = "insubquery"; + private static final String IN_ID_SET = "inidset"; + private static final Expression FALSE = RequestUtils.getLiteralExpression(false); + private static final Expression TRUE = RequestUtils.getLiteralExpression(true); + private static final Expression STAR = RequestUtils.getIdentifierExpression("*"); + private static final int MAX_UNAVAILABLE_SEGMENTS_TO_PRINT_IN_QUERY_EXCEPTION = 10; + private static final Map DISTINCT_MV_COL_FUNCTION_OVERRIDE_MAP = + ImmutableMap.builder().put("distinctcount", "distinctcountmv") + .put("distinctcountbitmap", "distinctcountbitmapmv").put("distinctcounthll", "distinctcounthllmv") + .put("distinctcountrawhll", "distinctcountrawhllmv").put("distinctsum", "distinctsummv") + .put("distinctavg", "distinctavgmv").put("count", "countmv").put("min", "minmv").put("max", "maxmv") + .put("avg", "avgmv").put("sum", "summv").put("minmaxrange", "minmaxrangemv") + .put("distinctcounthllplus", "distinctcounthllplusmv") + .put("distinctcountrawhllplus", "distinctcountrawhllplusmv").build(); + + protected final QueryOptimizer _queryOptimizer = new QueryOptimizer(); + protected final boolean _disableGroovy; + protected final boolean _useApproximateFunction; + protected final int _defaultHllLog2m; + protected final boolean _enableQueryLimitOverride; + protected final boolean _enableDistinctCountBitmapOverride; + protected final int _queryResponseLimit; + protected final Map _queriesById; + + public BaseSingleStageBrokerRequestHandler(PinotConfiguration config, String brokerId, + BrokerRoutingManager routingManager, AccessControlFactory accessControlFactory, + QueryQuotaManager queryQuotaManager, TableCache tableCache) { + super(config, brokerId, routingManager, accessControlFactory, queryQuotaManager, tableCache); + _disableGroovy = _config.getProperty(Broker.DISABLE_GROOVY, Broker.DEFAULT_DISABLE_GROOVY); + _useApproximateFunction = _config.getProperty(Broker.USE_APPROXIMATE_FUNCTION, false); + _defaultHllLog2m = _config.getProperty(CommonConstants.Helix.DEFAULT_HYPERLOGLOG_LOG2M_KEY, + CommonConstants.Helix.DEFAULT_HYPERLOGLOG_LOG2M); + _enableQueryLimitOverride = _config.getProperty(Broker.CONFIG_OF_ENABLE_QUERY_LIMIT_OVERRIDE, false); + _enableDistinctCountBitmapOverride = + _config.getProperty(CommonConstants.Helix.ENABLE_DISTINCT_COUNT_BITMAP_OVERRIDE_KEY, false); + _queryResponseLimit = + config.getProperty(Broker.CONFIG_OF_BROKER_QUERY_RESPONSE_LIMIT, Broker.DEFAULT_BROKER_QUERY_RESPONSE_LIMIT); + boolean enableQueryCancellation = + Boolean.parseBoolean(config.getProperty(Broker.CONFIG_OF_BROKER_ENABLE_QUERY_CANCELLATION)); + _queriesById = enableQueryCancellation ? new ConcurrentHashMap<>() : null; + LOGGER.info("Initialized {} with broker id: {}, timeout: {}ms, query response limit: {}, query log max length: {}, " + + "query log max rate: {}, query cancellation enabled: {}", getClass().getSimpleName(), _brokerId, + _brokerTimeoutMs, _queryResponseLimit, _queryLogger.getMaxQueryLengthToLog(), _queryLogger.getLogRateLimit(), + enableQueryCancellation); + } + + @Override + public Map getRunningQueries() { + Preconditions.checkState(_queriesById != null, "Query cancellation is not enabled on broker"); + return _queriesById.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue()._query)); + } + + @VisibleForTesting + Set getRunningServers(long requestId) { + Preconditions.checkState(_queriesById != null, "Query cancellation is not enabled on broker"); + QueryServers queryServers = _queriesById.get(requestId); + return queryServers != null ? queryServers._servers : Collections.emptySet(); + } + + @Override + public boolean cancelQuery(long requestId, int timeoutMs, Executor executor, HttpClientConnectionManager connMgr, + Map serverResponses) + throws Exception { + Preconditions.checkState(_queriesById != null, "Query cancellation is not enabled on broker"); + QueryServers queryServers = _queriesById.get(requestId); + if (queryServers == null) { + return false; + } + // TODO: Use different global query id for OFFLINE and REALTIME table after releasing 0.12.0. See QueryIdUtils for + // details + String globalQueryId = getGlobalQueryId(requestId); + List> serverUrls = new ArrayList<>(); + for (ServerInstance serverInstance : queryServers._servers) { + serverUrls.add(Pair.of(String.format("%s/query/%s", serverInstance.getAdminEndpoint(), globalQueryId), null)); + } + LOGGER.debug("Cancelling the query: {} via server urls: {}", queryServers._query, serverUrls); + CompletionService completionService = + new MultiHttpRequest(executor, connMgr).execute(serverUrls, null, timeoutMs, "DELETE", HttpDelete::new); + List errMsgs = new ArrayList<>(serverUrls.size()); + for (int i = 0; i < serverUrls.size(); i++) { + MultiHttpRequestResponse httpRequestResponse = null; + try { + // Wait for all requests to respond before returning to be sure that the servers have handled the cancel + // requests. The completion order is different from serverUrls, thus use uri in the response. + httpRequestResponse = completionService.take().get(); + URI uri = httpRequestResponse.getURI(); + int status = httpRequestResponse.getResponse().getStatusLine().getStatusCode(); + // Unexpected server responses are collected and returned as exception. + if (status != 200 && status != 404) { + String responseString = EntityUtils.toString(httpRequestResponse.getResponse().getEntity()); + throw new Exception( + String.format("Unexpected status=%d and response='%s' from uri='%s'", status, responseString, uri)); + } + if (serverResponses != null) { + serverResponses.put(uri.getHost() + ":" + uri.getPort(), status); + } + } catch (Exception e) { + LOGGER.error("Failed to cancel query: {}", queryServers._query, e); + // Can't just throw exception from here as there is a need to release the other connections. + // So just collect the error msg to throw them together after the for-loop. + errMsgs.add(e.getMessage()); + } finally { + if (httpRequestResponse != null) { + httpRequestResponse.close(); + } + } + } + if (errMsgs.size() > 0) { + throw new Exception("Unexpected responses from servers: " + StringUtils.join(errMsgs, ",")); + } + return true; + } + + @Override + protected BrokerResponse handleRequest(long requestId, String query, @Nullable SqlNodeAndOptions sqlNodeAndOptions, + JsonNode request, @Nullable RequesterIdentity requesterIdentity, RequestContext requestContext, + @Nullable HttpHeaders httpHeaders, AccessControl accessControl) + throws Exception { + LOGGER.debug("SQL query for request {}: {}", requestId, query); + + //Start instrumentation context. This must not be moved further below interspersed into the code. + Tracing.ThreadAccountantOps.setupRunner(String.valueOf(requestId)); + + try { + // Parse the query if needed + if (sqlNodeAndOptions == null) { + try { + sqlNodeAndOptions = RequestUtils.parseQuery(query, request); + } catch (Exception e) { + // Do not log or emit metric here because it is pure user error + requestContext.setErrorCode(QueryException.SQL_PARSING_ERROR_CODE); + return new BrokerResponseNative(QueryException.getException(QueryException.SQL_PARSING_ERROR, e)); + } + } + + // Compile the request into PinotQuery + long compilationStartTimeNs = System.nanoTime(); + PinotQuery pinotQuery; + try { + pinotQuery = CalciteSqlParser.compileToPinotQuery(sqlNodeAndOptions); + } catch (Exception e) { + LOGGER.info("Caught exception while compiling SQL request {}: {}, {}", requestId, query, e.getMessage()); + _brokerMetrics.addMeteredGlobalValue(BrokerMeter.REQUEST_COMPILATION_EXCEPTIONS, 1); + requestContext.setErrorCode(QueryException.SQL_PARSING_ERROR_CODE); + return new BrokerResponseNative(QueryException.getException(QueryException.SQL_PARSING_ERROR, e)); + } + + if (isLiteralOnlyQuery(pinotQuery)) { + LOGGER.debug("Request {} contains only Literal, skipping server query: {}", requestId, query); + try { + if (pinotQuery.isExplain()) { + // EXPLAIN PLAN results to show that query is evaluated exclusively by Broker. + return BrokerResponseNative.BROKER_ONLY_EXPLAIN_PLAN_OUTPUT; + } + return processLiteralOnlyQuery(requestId, pinotQuery, requestContext); + } catch (Exception e) { + // TODO: refine the exceptions here to early termination the queries won't requires to send to servers. + LOGGER.warn("Unable to execute literal request {}: {} at broker, fallback to server query. {}", requestId, + query, e.getMessage()); + } + } + + PinotQuery serverPinotQuery = GapfillUtils.stripGapfill(pinotQuery); + DataSource dataSource = serverPinotQuery.getDataSource(); + if (dataSource == null) { + LOGGER.info("Data source (FROM clause) not found in request {}: {}", requestId, query); + requestContext.setErrorCode(QueryException.QUERY_VALIDATION_ERROR_CODE); + return new BrokerResponseNative( + QueryException.getException(QueryException.QUERY_VALIDATION_ERROR, "Data source (FROM clause) not found")); + } + if (dataSource.getJoin() != null) { + LOGGER.info("JOIN is not supported in request {}: {}", requestId, query); + requestContext.setErrorCode(QueryException.QUERY_VALIDATION_ERROR_CODE); + return new BrokerResponseNative( + QueryException.getException(QueryException.QUERY_VALIDATION_ERROR, "JOIN is not supported")); + } + if (dataSource.getTableName() == null) { + LOGGER.info("Table name not found in request {}: {}", requestId, query); + requestContext.setErrorCode(QueryException.QUERY_VALIDATION_ERROR_CODE); + return new BrokerResponseNative( + QueryException.getException(QueryException.QUERY_VALIDATION_ERROR, "Table name not found")); + } + + try { + handleSubquery(serverPinotQuery, requestId, request, requesterIdentity, requestContext, httpHeaders, + accessControl); + } catch (Exception e) { + LOGGER.info("Caught exception while handling the subquery in request {}: {}, {}", requestId, query, + e.getMessage()); + requestContext.setErrorCode(QueryException.QUERY_EXECUTION_ERROR_CODE); + return new BrokerResponseNative(QueryException.getException(QueryException.QUERY_EXECUTION_ERROR, e)); + } + + boolean ignoreCase = _tableCache.isIgnoreCase(); + String tableName; + try { + tableName = + getActualTableName(DatabaseUtils.translateTableName(dataSource.getTableName(), httpHeaders, ignoreCase), + _tableCache); + } catch (DatabaseConflictException e) { + LOGGER.info("{}. Request {}: {}", e.getMessage(), requestId, query); + _brokerMetrics.addMeteredGlobalValue(BrokerMeter.QUERY_VALIDATION_EXCEPTIONS, 1); + requestContext.setErrorCode(QueryException.QUERY_VALIDATION_ERROR_CODE); + return new BrokerResponseNative(QueryException.getException(QueryException.QUERY_VALIDATION_ERROR, e)); + } + dataSource.setTableName(tableName); + String rawTableName = TableNameBuilder.extractRawTableName(tableName); + requestContext.setTableName(rawTableName); + + try { + Map columnNameMap = _tableCache.getColumnNameMap(rawTableName); + if (columnNameMap != null) { + updateColumnNames(rawTableName, serverPinotQuery, ignoreCase, columnNameMap); + } + } catch (Exception e) { + // Throw exceptions with column in-existence error. + if (e instanceof BadQueryRequestException) { + LOGGER.info("Caught exception while checking column names in request {}: {}, {}", requestId, query, + e.getMessage()); + requestContext.setErrorCode(QueryException.UNKNOWN_COLUMN_ERROR_CODE); + _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.UNKNOWN_COLUMN_EXCEPTIONS, 1); + return new BrokerResponseNative(QueryException.getException(QueryException.UNKNOWN_COLUMN_ERROR, e)); + } + LOGGER.warn("Caught exception while updating column names in request {}: {}, {}", requestId, query, + e.getMessage()); + } + if (_defaultHllLog2m > 0) { + handleHLLLog2mOverride(serverPinotQuery, _defaultHllLog2m); + } + if (_enableQueryLimitOverride) { + handleQueryLimitOverride(serverPinotQuery, _queryResponseLimit); + } + handleSegmentPartitionedDistinctCountOverride(serverPinotQuery, + getSegmentPartitionedColumns(_tableCache, tableName)); + if (_enableDistinctCountBitmapOverride) { + handleDistinctCountBitmapOverride(serverPinotQuery); + } + + Schema schema = _tableCache.getSchema(rawTableName); + if (schema != null) { + handleDistinctMultiValuedOverride(serverPinotQuery, schema); + } + + long compilationEndTimeNs = System.nanoTime(); + // full request compile time = compilationTimeNs + parserTimeNs + _brokerMetrics.addPhaseTiming(rawTableName, BrokerQueryPhase.REQUEST_COMPILATION, + (compilationEndTimeNs - compilationStartTimeNs) + sqlNodeAndOptions.getParseTimeNs()); + + // Second-stage table-level access control + // TODO: Modify AccessControl interface to directly take PinotQuery + BrokerRequest brokerRequest = CalciteSqlCompiler.convertToBrokerRequest(pinotQuery); + BrokerRequest serverBrokerRequest = + serverPinotQuery == pinotQuery ? brokerRequest : CalciteSqlCompiler.convertToBrokerRequest(serverPinotQuery); + AuthorizationResult authorizationResult = accessControl.authorize(requesterIdentity, serverBrokerRequest); + if (authorizationResult.hasAccess()) { + authorizationResult = accessControl.authorize(httpHeaders, TargetType.TABLE, tableName, Actions.Table.QUERY); + } + + _brokerMetrics.addPhaseTiming(rawTableName, BrokerQueryPhase.AUTHORIZATION, + System.nanoTime() - compilationEndTimeNs); + + if (!authorizationResult.hasAccess()) { + _brokerMetrics.addMeteredTableValue(tableName, BrokerMeter.REQUEST_DROPPED_DUE_TO_ACCESS_ERROR, 1); + LOGGER.info("Access denied for request {}: {}, table: {}, reason :{}", requestId, query, tableName, + authorizationResult.getFailureMessage()); + requestContext.setErrorCode(QueryException.ACCESS_DENIED_ERROR_CODE); + String failureMessage = authorizationResult.getFailureMessage(); + if (StringUtils.isNotBlank(failureMessage)) { + failureMessage = "Reason: " + failureMessage; + } + throw new WebApplicationException("Permission denied." + failureMessage, + Response.Status.FORBIDDEN); + } + + // Get the tables hit by the request + String offlineTableName = null; + String realtimeTableName = null; + TableType tableType = TableNameBuilder.getTableTypeFromTableName(tableName); + if (tableType == TableType.OFFLINE) { + // Offline table + if (_routingManager.routingExists(tableName)) { + offlineTableName = tableName; + } + } else if (tableType == TableType.REALTIME) { + // Realtime table + if (_routingManager.routingExists(tableName)) { + realtimeTableName = tableName; + } + } else { + // Hybrid table (check both OFFLINE and REALTIME) + String offlineTableNameToCheck = TableNameBuilder.OFFLINE.tableNameWithType(tableName); + if (_routingManager.routingExists(offlineTableNameToCheck)) { + offlineTableName = offlineTableNameToCheck; + } + String realtimeTableNameToCheck = TableNameBuilder.REALTIME.tableNameWithType(tableName); + if (_routingManager.routingExists(realtimeTableNameToCheck)) { + realtimeTableName = realtimeTableNameToCheck; + } + } + + TableConfig offlineTableConfig = + _tableCache.getTableConfig(TableNameBuilder.OFFLINE.tableNameWithType(rawTableName)); + TableConfig realtimeTableConfig = + _tableCache.getTableConfig(TableNameBuilder.REALTIME.tableNameWithType(rawTableName)); + + if (offlineTableName == null && realtimeTableName == null) { + // No table matches the request + if (realtimeTableConfig == null && offlineTableConfig == null) { + LOGGER.info("Table not found for request {}: {}", requestId, query); + requestContext.setErrorCode(QueryException.TABLE_DOES_NOT_EXIST_ERROR_CODE); + return BrokerResponseNative.TABLE_DOES_NOT_EXIST; + } + LOGGER.info("No table matches for request {}: {}", requestId, query); + requestContext.setErrorCode(QueryException.BROKER_RESOURCE_MISSING_ERROR_CODE); + _brokerMetrics.addMeteredGlobalValue(BrokerMeter.RESOURCE_MISSING_EXCEPTIONS, 1); + return BrokerResponseNative.NO_TABLE_RESULT; + } + + // Handle query rewrite that can be overridden by the table configs + if (offlineTableName == null) { + offlineTableConfig = null; + } + if (realtimeTableName == null) { + realtimeTableConfig = null; + } + HandlerContext handlerContext = getHandlerContext(offlineTableConfig, realtimeTableConfig); + if (handlerContext._disableGroovy) { + rejectGroovyQuery(serverPinotQuery); + } + if (handlerContext._useApproximateFunction) { + handleApproximateFunctionOverride(serverPinotQuery); + } + + // Validate QPS quota + if (!_queryQuotaManager.acquire(tableName)) { + String errorMessage = + String.format("Request %d: %s exceeds query quota for table: %s", requestId, query, tableName); + LOGGER.info(errorMessage); + requestContext.setErrorCode(QueryException.TOO_MANY_REQUESTS_ERROR_CODE); + _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.QUERY_QUOTA_EXCEEDED, 1); + return new BrokerResponseNative(QueryException.getException(QueryException.QUOTA_EXCEEDED_ERROR, errorMessage)); + } + + // Validate the request + try { + validateRequest(serverPinotQuery, _queryResponseLimit); + } catch (Exception e) { + LOGGER.info("Caught exception while validating request {}: {}, {}", requestId, query, e.getMessage()); + requestContext.setErrorCode(QueryException.QUERY_VALIDATION_ERROR_CODE); + _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.QUERY_VALIDATION_EXCEPTIONS, 1); + return new BrokerResponseNative(QueryException.getException(QueryException.QUERY_VALIDATION_ERROR, e)); + } + + _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.QUERIES, 1); + _brokerMetrics.addValueToTableGauge(rawTableName, BrokerGauge.REQUEST_SIZE, query.length()); + + // Prepare OFFLINE and REALTIME requests + BrokerRequest offlineBrokerRequest = null; + BrokerRequest realtimeBrokerRequest = null; + TimeBoundaryInfo timeBoundaryInfo = null; + if (offlineTableName != null && realtimeTableName != null) { + // Time boundary info might be null when there is no segment in the offline table, query real-time side only + timeBoundaryInfo = _routingManager.getTimeBoundaryInfo(offlineTableName); + if (timeBoundaryInfo == null) { + LOGGER.debug("No time boundary info found for hybrid table: {}", rawTableName); + offlineTableName = null; + } + } + if (offlineTableName != null && realtimeTableName != null) { + // Hybrid + PinotQuery offlinePinotQuery = serverPinotQuery.deepCopy(); + offlinePinotQuery.getDataSource().setTableName(offlineTableName); + attachTimeBoundary(offlinePinotQuery, timeBoundaryInfo, true); + handleExpressionOverride(offlinePinotQuery, _tableCache.getExpressionOverrideMap(offlineTableName)); + handleTimestampIndexOverride(offlinePinotQuery, offlineTableConfig); + _queryOptimizer.optimize(offlinePinotQuery, offlineTableConfig, schema); + offlineBrokerRequest = CalciteSqlCompiler.convertToBrokerRequest(offlinePinotQuery); + + PinotQuery realtimePinotQuery = serverPinotQuery.deepCopy(); + realtimePinotQuery.getDataSource().setTableName(realtimeTableName); + attachTimeBoundary(realtimePinotQuery, timeBoundaryInfo, false); + handleExpressionOverride(realtimePinotQuery, _tableCache.getExpressionOverrideMap(realtimeTableName)); + handleTimestampIndexOverride(realtimePinotQuery, realtimeTableConfig); + _queryOptimizer.optimize(realtimePinotQuery, realtimeTableConfig, schema); + realtimeBrokerRequest = CalciteSqlCompiler.convertToBrokerRequest(realtimePinotQuery); + + requestContext.setFanoutType(RequestContext.FanoutType.HYBRID); + requestContext.setOfflineServerTenant(getServerTenant(offlineTableName)); + requestContext.setRealtimeServerTenant(getServerTenant(realtimeTableName)); + } else if (offlineTableName != null) { + // OFFLINE only + setTableName(serverBrokerRequest, offlineTableName); + handleExpressionOverride(serverPinotQuery, _tableCache.getExpressionOverrideMap(offlineTableName)); + handleTimestampIndexOverride(serverPinotQuery, offlineTableConfig); + _queryOptimizer.optimize(serverPinotQuery, offlineTableConfig, schema); + offlineBrokerRequest = serverBrokerRequest; + + requestContext.setFanoutType(RequestContext.FanoutType.OFFLINE); + requestContext.setOfflineServerTenant(getServerTenant(offlineTableName)); + } else { + // REALTIME only + setTableName(serverBrokerRequest, realtimeTableName); + handleExpressionOverride(serverPinotQuery, _tableCache.getExpressionOverrideMap(realtimeTableName)); + handleTimestampIndexOverride(serverPinotQuery, realtimeTableConfig); + _queryOptimizer.optimize(serverPinotQuery, realtimeTableConfig, schema); + realtimeBrokerRequest = serverBrokerRequest; + + requestContext.setFanoutType(RequestContext.FanoutType.REALTIME); + requestContext.setRealtimeServerTenant(getServerTenant(realtimeTableName)); + } + + // Check if response can be sent without server query evaluation. + if (offlineBrokerRequest != null && isFilterAlwaysFalse(offlineBrokerRequest.getPinotQuery())) { + // We don't need to evaluate offline request + offlineBrokerRequest = null; + } + if (realtimeBrokerRequest != null && isFilterAlwaysFalse(realtimeBrokerRequest.getPinotQuery())) { + // We don't need to evaluate realtime request + realtimeBrokerRequest = null; + } + + if (offlineBrokerRequest == null && realtimeBrokerRequest == null) { + return getEmptyBrokerOnlyResponse(pinotQuery, requestContext, tableName, requesterIdentity); + } + + if (offlineBrokerRequest != null && isFilterAlwaysTrue(offlineBrokerRequest.getPinotQuery())) { + // Drop offline request filter since it is always true + offlineBrokerRequest.getPinotQuery().setFilterExpression(null); + } + if (realtimeBrokerRequest != null && isFilterAlwaysTrue(realtimeBrokerRequest.getPinotQuery())) { + // Drop realtime request filter since it is always true + realtimeBrokerRequest.getPinotQuery().setFilterExpression(null); + } + + // Calculate routing table for the query + // TODO: Modify RoutingManager interface to directly take PinotQuery + long routingStartTimeNs = System.nanoTime(); + Map, List>> offlineRoutingTable = null; + Map, List>> realtimeRoutingTable = null; + List unavailableSegments = new ArrayList<>(); + int numPrunedSegmentsTotal = 0; + if (offlineBrokerRequest != null) { + // NOTE: Routing table might be null if table is just removed + RoutingTable routingTable = _routingManager.getRoutingTable(offlineBrokerRequest, requestId); + if (routingTable != null) { + unavailableSegments.addAll(routingTable.getUnavailableSegments()); + Map, List>> serverInstanceToSegmentsMap = + routingTable.getServerInstanceToSegmentsMap(); + if (!serverInstanceToSegmentsMap.isEmpty()) { + offlineRoutingTable = serverInstanceToSegmentsMap; + } else { + offlineBrokerRequest = null; + } + numPrunedSegmentsTotal += routingTable.getNumPrunedSegments(); + } else { + offlineBrokerRequest = null; + } + } + if (realtimeBrokerRequest != null) { + // NOTE: Routing table might be null if table is just removed + RoutingTable routingTable = _routingManager.getRoutingTable(realtimeBrokerRequest, requestId); + if (routingTable != null) { + unavailableSegments.addAll(routingTable.getUnavailableSegments()); + Map, List>> serverInstanceToSegmentsMap = + routingTable.getServerInstanceToSegmentsMap(); + if (!serverInstanceToSegmentsMap.isEmpty()) { + realtimeRoutingTable = serverInstanceToSegmentsMap; + } else { + realtimeBrokerRequest = null; + } + numPrunedSegmentsTotal += routingTable.getNumPrunedSegments(); + } else { + realtimeBrokerRequest = null; + } + } + int numUnavailableSegments = unavailableSegments.size(); + requestContext.setNumUnavailableSegments(numUnavailableSegments); + + List exceptions = new ArrayList<>(); + if (numUnavailableSegments > 0) { + String errorMessage; + if (numUnavailableSegments > MAX_UNAVAILABLE_SEGMENTS_TO_PRINT_IN_QUERY_EXCEPTION) { + errorMessage = String.format("%d segments unavailable, sampling %d: %s", numUnavailableSegments, + MAX_UNAVAILABLE_SEGMENTS_TO_PRINT_IN_QUERY_EXCEPTION, + unavailableSegments.subList(0, MAX_UNAVAILABLE_SEGMENTS_TO_PRINT_IN_QUERY_EXCEPTION)); + } else { + errorMessage = String.format("%d segments unavailable: %s", numUnavailableSegments, unavailableSegments); + } + exceptions.add(QueryException.getException(QueryException.BROKER_SEGMENT_UNAVAILABLE_ERROR, errorMessage)); + _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.BROKER_RESPONSES_WITH_UNAVAILABLE_SEGMENTS, 1); + } + + if (offlineBrokerRequest == null && realtimeBrokerRequest == null) { + if (!exceptions.isEmpty()) { + LOGGER.info("No server found for request {}: {}", requestId, query); + _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.NO_SERVER_FOUND_EXCEPTIONS, 1); + return new BrokerResponseNative(exceptions); + } else { + // When all segments have been pruned, we can just return an empty response. + return getEmptyBrokerOnlyResponse(pinotQuery, requestContext, tableName, requesterIdentity); + } + } + long routingEndTimeNs = System.nanoTime(); + _brokerMetrics.addPhaseTiming(rawTableName, BrokerQueryPhase.QUERY_ROUTING, + routingEndTimeNs - routingStartTimeNs); + + // Set timeout in the requests + long timeSpentMs = TimeUnit.NANOSECONDS.toMillis(routingEndTimeNs - compilationStartTimeNs); + // Remaining time in milliseconds for the server query execution + // NOTE: For hybrid use case, in most cases offline table and real-time table should have the same query timeout + // configured, but if necessary, we also allow different timeout for them. + // If the timeout is not the same for offline table and real-time table, use the max of offline table + // remaining time and realtime table remaining time. Server side will have different remaining time set for + // each table type, and broker should wait for both types to return. + long remainingTimeMs = 0; + try { + if (offlineBrokerRequest != null) { + remainingTimeMs = + setQueryTimeout(offlineTableName, offlineBrokerRequest.getPinotQuery().getQueryOptions(), timeSpentMs); + } + if (realtimeBrokerRequest != null) { + remainingTimeMs = Math.max(remainingTimeMs, + setQueryTimeout(realtimeTableName, realtimeBrokerRequest.getPinotQuery().getQueryOptions(), timeSpentMs)); + } + } catch (TimeoutException e) { + String errorMessage = e.getMessage(); + LOGGER.info("{} {}: {}", errorMessage, requestId, query); + _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.REQUEST_TIMEOUT_BEFORE_SCATTERED_EXCEPTIONS, 1); + exceptions.add(QueryException.getException(QueryException.BROKER_TIMEOUT_ERROR, errorMessage)); + return new BrokerResponseNative(exceptions); + } + + // Set the maximum serialized response size per server, and ask server to directly return final response when only + // one server is queried + int numServers = 0; + if (offlineRoutingTable != null) { + numServers += offlineRoutingTable.size(); + } + if (realtimeRoutingTable != null) { + numServers += realtimeRoutingTable.size(); + } + if (offlineBrokerRequest != null) { + Map queryOptions = offlineBrokerRequest.getPinotQuery().getQueryOptions(); + setMaxServerResponseSizeBytes(numServers, queryOptions, offlineTableConfig); + // Set the query option to directly return final result for single server query unless it is explicitly disabled + if (numServers == 1) { + // Set the same flag in the original server request to be used in the reduce phase for hybrid table + if (queryOptions.putIfAbsent(QueryOptionKey.SERVER_RETURN_FINAL_RESULT, "true") == null + && offlineBrokerRequest != serverBrokerRequest) { + serverBrokerRequest.getPinotQuery().getQueryOptions() + .put(QueryOptionKey.SERVER_RETURN_FINAL_RESULT, "true"); + } + } + } + if (realtimeBrokerRequest != null) { + Map queryOptions = realtimeBrokerRequest.getPinotQuery().getQueryOptions(); + setMaxServerResponseSizeBytes(numServers, queryOptions, realtimeTableConfig); + // Set the query option to directly return final result for single server query unless it is explicitly disabled + if (numServers == 1) { + // Set the same flag in the original server request to be used in the reduce phase for hybrid table + if (queryOptions.putIfAbsent(QueryOptionKey.SERVER_RETURN_FINAL_RESULT, "true") == null + && realtimeBrokerRequest != serverBrokerRequest) { + serverBrokerRequest.getPinotQuery().getQueryOptions() + .put(QueryOptionKey.SERVER_RETURN_FINAL_RESULT, "true"); + } + } + } + + // Execute the query + // TODO: Replace ServerStats with ServerRoutingStatsEntry. + ServerStats serverStats = new ServerStats(); + // TODO: Handle broker specific operations for explain plan queries such as: + // - Alias handling + // - Compile time function invocation + // - Literal only queries + // - Any rewrites + if (pinotQuery.isExplain()) { + // Update routing tables to only send request to offline servers for OFFLINE and HYBRID tables. + // TODO: Assess if the Explain Plan Query should also be routed to REALTIME servers for HYBRID tables + if (offlineRoutingTable != null) { + // For OFFLINE and HYBRID tables, don't send EXPLAIN query to realtime servers. + realtimeBrokerRequest = null; + realtimeRoutingTable = null; + } + } + BrokerResponseNative brokerResponse; + if (_queriesById != null) { + // Start to track the running query for cancellation just before sending it out to servers to avoid any + // potential failures that could happen before sending it out, like failures to calculate the routing table etc. + // TODO: Even tracking the query as late as here, a potential race condition between calling cancel API and + // query being sent out to servers can still happen. If cancel request arrives earlier than query being + // sent out to servers, the servers miss the cancel request and continue to run the queries. The users + // can always list the running queries and cancel query again until it ends. Just that such race + // condition makes cancel API less reliable. This should be rare as it assumes sending queries out to + // servers takes time, but will address later if needed. + _queriesById.put(requestId, new QueryServers(query, offlineRoutingTable, realtimeRoutingTable)); + LOGGER.debug("Keep track of running query: {}", requestId); + try { + brokerResponse = processBrokerRequest(requestId, brokerRequest, serverBrokerRequest, offlineBrokerRequest, + offlineRoutingTable, realtimeBrokerRequest, realtimeRoutingTable, remainingTimeMs, serverStats, + requestContext); + } finally { + _queriesById.remove(requestId); + LOGGER.debug("Remove track of running query: {}", requestId); + } + } else { + brokerResponse = processBrokerRequest(requestId, brokerRequest, serverBrokerRequest, offlineBrokerRequest, + offlineRoutingTable, realtimeBrokerRequest, realtimeRoutingTable, remainingTimeMs, serverStats, + requestContext); + } + + for (ProcessingException exception : exceptions) { + brokerResponse.addException(exception); + } + brokerResponse.setNumSegmentsPrunedByBroker(numPrunedSegmentsTotal); + long executionEndTimeNs = System.nanoTime(); + _brokerMetrics.addPhaseTiming(rawTableName, BrokerQueryPhase.QUERY_EXECUTION, + executionEndTimeNs - routingEndTimeNs); + + // Track number of queries with number of groups limit reached + if (brokerResponse.isNumGroupsLimitReached()) { + _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.BROKER_RESPONSES_WITH_NUM_GROUPS_LIMIT_REACHED, + 1); + } + + // Set total query processing time + long totalTimeMs = System.currentTimeMillis() - requestContext.getRequestArrivalTimeMillis(); + brokerResponse.setTimeUsedMs(totalTimeMs); + augmentStatistics(requestContext, brokerResponse); + if (QueryOptionsUtils.shouldDropResults(pinotQuery.getQueryOptions())) { + brokerResponse.setResultTable(null); + } + _brokerMetrics.addTimedTableValue(rawTableName, BrokerTimer.QUERY_TOTAL_TIME_MS, totalTimeMs, + TimeUnit.MILLISECONDS); + + // Log query and stats + _queryLogger.log( + new QueryLogger.QueryLogParams(requestContext, tableName, brokerResponse, requesterIdentity, serverStats)); + + return brokerResponse; + } finally { + Tracing.ThreadAccountantOps.clear(); + } + } + + private BrokerResponseNative getEmptyBrokerOnlyResponse(PinotQuery pinotQuery, RequestContext requestContext, + String tableName, @Nullable RequesterIdentity requesterIdentity) { + if (pinotQuery.isExplain()) { + // EXPLAIN PLAN results to show that query is evaluated exclusively by Broker. + return BrokerResponseNative.BROKER_ONLY_EXPLAIN_PLAN_OUTPUT; + } + + // Send empty response since we don't need to evaluate either offline or realtime request. + BrokerResponseNative brokerResponse = BrokerResponseNative.empty(); + brokerResponse.setTimeUsedMs(System.currentTimeMillis() - requestContext.getRequestArrivalTimeMillis()); + _queryLogger.log( + new QueryLogger.QueryLogParams(requestContext, tableName, brokerResponse, requesterIdentity, null)); + return brokerResponse; + } + + private void handleTimestampIndexOverride(PinotQuery pinotQuery, @Nullable TableConfig tableConfig) { + if (tableConfig == null || tableConfig.getFieldConfigList() == null) { + return; + } + + Set timestampIndexColumns = _tableCache.getTimestampIndexColumns(tableConfig.getTableName()); + if (CollectionUtils.isEmpty(timestampIndexColumns)) { + return; + } + for (Expression expression : pinotQuery.getSelectList()) { + setTimestampIndexExpressionOverrideHints(expression, timestampIndexColumns, pinotQuery); + } + setTimestampIndexExpressionOverrideHints(pinotQuery.getFilterExpression(), timestampIndexColumns, pinotQuery); + setTimestampIndexExpressionOverrideHints(pinotQuery.getHavingExpression(), timestampIndexColumns, pinotQuery); + List groupByList = pinotQuery.getGroupByList(); + if (CollectionUtils.isNotEmpty(groupByList)) { + groupByList.forEach( + expression -> setTimestampIndexExpressionOverrideHints(expression, timestampIndexColumns, pinotQuery)); + } + List orderByList = pinotQuery.getOrderByList(); + if (CollectionUtils.isNotEmpty(orderByList)) { + orderByList.forEach( + expression -> setTimestampIndexExpressionOverrideHints(expression, timestampIndexColumns, pinotQuery)); + } + } + + private void setTimestampIndexExpressionOverrideHints(@Nullable Expression expression, + Set timestampIndexColumns, PinotQuery pinotQuery) { + if (expression == null || expression.getFunctionCall() == null) { + return; + } + Function function = expression.getFunctionCall(); + switch (function.getOperator()) { + case "datetrunc": + String granularString = function.getOperands().get(0).getLiteral().getStringValue().toUpperCase(); + Expression timeExpression = function.getOperands().get(1); + if (((function.getOperandsSize() == 2) || (function.getOperandsSize() == 3 && "MILLISECONDS".equalsIgnoreCase( + function.getOperands().get(2).getLiteral().getStringValue()))) && TimestampIndexUtils.isValidGranularity( + granularString) && timeExpression.getIdentifier() != null) { + String timeColumn = timeExpression.getIdentifier().getName(); + String timeColumnWithGranularity = TimestampIndexUtils.getColumnWithGranularity(timeColumn, granularString); + if (timestampIndexColumns.contains(timeColumnWithGranularity)) { + pinotQuery.putToExpressionOverrideHints(expression, + RequestUtils.getIdentifierExpression(timeColumnWithGranularity)); + } + } + break; + default: + break; + } + function.getOperands() + .forEach(operand -> setTimestampIndexExpressionOverrideHints(operand, timestampIndexColumns, pinotQuery)); + } + + /** Given a {@link PinotQuery}, check if the WHERE clause will always evaluate to false. */ + private boolean isFilterAlwaysFalse(PinotQuery pinotQuery) { + return FALSE.equals(pinotQuery.getFilterExpression()); + } + + /** Given a {@link PinotQuery}, check if the WHERE clause will always evaluate to true. */ + private boolean isFilterAlwaysTrue(PinotQuery pinotQuery) { + return TRUE.equals(pinotQuery.getFilterExpression()); + } + + private String getServerTenant(String tableNameWithType) { + TableConfig tableConfig = _tableCache.getTableConfig(tableNameWithType); + if (tableConfig == null) { + LOGGER.debug("Table config is not available for table {}", tableNameWithType); + return "unknownTenant"; + } + return tableConfig.getTenantConfig().getServer(); + } + + /** + * Handles the subquery in the given query. + *

Currently only supports subquery within the filter. + */ + private void handleSubquery(PinotQuery pinotQuery, long requestId, JsonNode jsonRequest, + @Nullable RequesterIdentity requesterIdentity, RequestContext requestContext, @Nullable HttpHeaders httpHeaders, + AccessControl accessControl) + throws Exception { + Expression filterExpression = pinotQuery.getFilterExpression(); + if (filterExpression != null) { + handleSubquery(filterExpression, requestId, jsonRequest, requesterIdentity, requestContext, httpHeaders, + accessControl); + } + } + + /** + * Handles the subquery in the given expression. + *

When subquery is detected, first executes the subquery and gets the response, then rewrites the expression with + * the subquery response. + *

Currently only supports ID_SET subquery within the IN_SUBQUERY transform function, which will be rewritten to an + * IN_ID_SET transform function. + */ + private void handleSubquery(Expression expression, long requestId, JsonNode jsonRequest, + @Nullable RequesterIdentity requesterIdentity, RequestContext requestContext, @Nullable HttpHeaders httpHeaders, + AccessControl accessControl) + throws Exception { + Function function = expression.getFunctionCall(); + if (function == null) { + return; + } + List operands = function.getOperands(); + if (function.getOperator().equals(IN_SUBQUERY)) { + Preconditions.checkState(operands.size() == 2, "IN_SUBQUERY requires 2 arguments: expression, subquery"); + Literal subqueryLiteral = operands.get(1).getLiteral(); + Preconditions.checkState(subqueryLiteral != null, "Second argument of IN_SUBQUERY must be a literal (subquery)"); + String subquery = subqueryLiteral.getStringValue(); + BrokerResponse response = + handleRequest(requestId, subquery, null, jsonRequest, requesterIdentity, requestContext, httpHeaders, + accessControl); + if (response.getExceptionsSize() != 0) { + throw new RuntimeException("Caught exception while executing subquery: " + subquery); + } + String serializedIdSet = (String) response.getResultTable().getRows().get(0)[0]; + function.setOperator(IN_ID_SET); + operands.set(1, RequestUtils.getLiteralExpression(serializedIdSet)); + } else { + for (Expression operand : operands) { + handleSubquery(operand, requestId, jsonRequest, requesterIdentity, requestContext, httpHeaders, accessControl); + } + } + } + + /** + * Resolves the actual table name for: + * - Case-insensitive cluster + * + * @param tableName the table name in the query + * @param tableCache the table case-sensitive cache + * @return table name if the table name is found in Pinot registry. + */ + @VisibleForTesting + static String getActualTableName(String tableName, TableCache tableCache) { + String actualTableName = tableCache.getActualTableName(tableName); + if (actualTableName != null) { + return actualTableName; + } + return tableName; + } + + /** + * Retrieve segment partitioned columns for a table. + * For a hybrid table, a segment partitioned column has to be the intersection of both offline and realtime tables. + * + * @param tableCache + * @param tableName + * @return segment partitioned columns belong to both offline and realtime tables. + */ + private static Set getSegmentPartitionedColumns(TableCache tableCache, String tableName) { + final TableConfig offlineTableConfig = + tableCache.getTableConfig(TableNameBuilder.OFFLINE.tableNameWithType(tableName)); + final TableConfig realtimeTableConfig = + tableCache.getTableConfig(TableNameBuilder.REALTIME.tableNameWithType(tableName)); + if (offlineTableConfig == null) { + return getSegmentPartitionedColumns(realtimeTableConfig); + } + if (realtimeTableConfig == null) { + return getSegmentPartitionedColumns(offlineTableConfig); + } + Set segmentPartitionedColumns = getSegmentPartitionedColumns(offlineTableConfig); + segmentPartitionedColumns.retainAll(getSegmentPartitionedColumns(realtimeTableConfig)); + return segmentPartitionedColumns; + } + + private static Set getSegmentPartitionedColumns(@Nullable TableConfig tableConfig) { + Set segmentPartitionedColumns = new HashSet<>(); + if (tableConfig == null) { + return segmentPartitionedColumns; + } + List fieldConfigs = tableConfig.getFieldConfigList(); + if (fieldConfigs != null) { + for (FieldConfig fieldConfig : fieldConfigs) { + if (fieldConfig.getProperties() != null && Boolean.parseBoolean( + fieldConfig.getProperties().get(FieldConfig.IS_SEGMENT_PARTITIONED_COLUMN_KEY))) { + segmentPartitionedColumns.add(fieldConfig.getName()); + } + } + } + return segmentPartitionedColumns; + } + + /** + * Retrieve multivalued columns for a table. + * From the table Schema , we get the multi valued columns of dimension fields. + * + * @param tableSchema + * @param columnName + * @return multivalued columns of the table . + */ + private static boolean isMultiValueColumn(Schema tableSchema, String columnName) { + + DimensionFieldSpec dimensionFieldSpec = tableSchema.getDimensionSpec(columnName); + return dimensionFieldSpec != null && !dimensionFieldSpec.isSingleValueField(); + } + + /** + * Sets the table name in the given broker request. + * NOTE: Set table name in broker request because it is used for access control, query routing etc. + */ + private void setTableName(BrokerRequest brokerRequest, String tableName) { + brokerRequest.getQuerySource().setTableName(tableName); + brokerRequest.getPinotQuery().getDataSource().setTableName(tableName); + } + + /** + * Sets HyperLogLog log2m for DistinctCountHLL functions if not explicitly set for the given query. + */ + private static void handleHLLLog2mOverride(PinotQuery pinotQuery, int hllLog2mOverride) { + List selectList = pinotQuery.getSelectList(); + for (Expression expression : selectList) { + handleHLLLog2mOverride(expression, hllLog2mOverride); + } + List orderByList = pinotQuery.getOrderByList(); + if (orderByList != null) { + for (Expression expression : orderByList) { + // NOTE: Order-by is always a Function with the ordering of the Expression + handleHLLLog2mOverride(expression.getFunctionCall().getOperands().get(0), hllLog2mOverride); + } + } + Expression havingExpression = pinotQuery.getHavingExpression(); + if (havingExpression != null) { + handleHLLLog2mOverride(havingExpression, hllLog2mOverride); + } + } + + /** + * Sets HyperLogLog log2m for DistinctCountHLL functions if not explicitly set for the given expression. + */ + private static void handleHLLLog2mOverride(Expression expression, int hllLog2mOverride) { + Function function = expression.getFunctionCall(); + if (function == null) { + return; + } + switch (function.getOperator()) { + case "distinctcounthll": + case "distinctcounthllmv": + case "distinctcountrawhll": + case "distinctcountrawhllmv": + if (function.getOperandsSize() == 1) { + function.addToOperands(RequestUtils.getLiteralExpression(hllLog2mOverride)); + } + return; + default: + break; + } + for (Expression operand : function.getOperands()) { + handleHLLLog2mOverride(operand, hllLog2mOverride); + } + } + + /** + * Overrides the LIMIT of the given query if it exceeds the query limit. + */ + @VisibleForTesting + static void handleQueryLimitOverride(PinotQuery pinotQuery, int queryLimit) { + if (queryLimit > 0 && pinotQuery.getLimit() > queryLimit) { + pinotQuery.setLimit(queryLimit); + } + } + + /** + * Rewrites 'DistinctCount' to 'SegmentPartitionDistinctCount' for the given query. + */ + @VisibleForTesting + static void handleSegmentPartitionedDistinctCountOverride(PinotQuery pinotQuery, + Set segmentPartitionedColumns) { + if (segmentPartitionedColumns.isEmpty()) { + return; + } + for (Expression expression : pinotQuery.getSelectList()) { + handleSegmentPartitionedDistinctCountOverride(expression, segmentPartitionedColumns); + } + List orderByExpressions = pinotQuery.getOrderByList(); + if (orderByExpressions != null) { + for (Expression expression : orderByExpressions) { + // NOTE: Order-by is always a Function with the ordering of the Expression + handleSegmentPartitionedDistinctCountOverride(expression.getFunctionCall().getOperands().get(0), + segmentPartitionedColumns); + } + } + Expression havingExpression = pinotQuery.getHavingExpression(); + if (havingExpression != null) { + handleSegmentPartitionedDistinctCountOverride(havingExpression, segmentPartitionedColumns); + } + } + + /** + * Rewrites 'DistinctCount' to 'SegmentPartitionDistinctCount' for the given expression. + */ + private static void handleSegmentPartitionedDistinctCountOverride(Expression expression, + Set segmentPartitionedColumns) { + Function function = expression.getFunctionCall(); + if (function == null) { + return; + } + if (function.getOperator().equals("distinctcount")) { + List operands = function.getOperands(); + if (operands.size() == 1 && operands.get(0).isSetIdentifier() && segmentPartitionedColumns.contains( + operands.get(0).getIdentifier().getName())) { + function.setOperator("segmentpartitioneddistinctcount"); + } + } else { + for (Expression operand : function.getOperands()) { + handleSegmentPartitionedDistinctCountOverride(operand, segmentPartitionedColumns); + } + } + } + + /** + * Rewrites 'DistinctCount' to 'DistinctCountBitmap' for the given query. + */ + private static void handleDistinctCountBitmapOverride(PinotQuery pinotQuery) { + for (Expression expression : pinotQuery.getSelectList()) { + handleDistinctCountBitmapOverride(expression); + } + List orderByExpressions = pinotQuery.getOrderByList(); + if (orderByExpressions != null) { + for (Expression expression : orderByExpressions) { + // NOTE: Order-by is always a Function with the ordering of the Expression + handleDistinctCountBitmapOverride(expression.getFunctionCall().getOperands().get(0)); + } + } + Expression havingExpression = pinotQuery.getHavingExpression(); + if (havingExpression != null) { + handleDistinctCountBitmapOverride(havingExpression); + } + } + + /** + * Rewrites selected 'Distinct' prefixed function to 'Distinct----MV' function for the field of multivalued type. + */ + @VisibleForTesting + static void handleDistinctMultiValuedOverride(PinotQuery pinotQuery, Schema tableSchema) { + for (Expression expression : pinotQuery.getSelectList()) { + handleDistinctMultiValuedOverride(expression, tableSchema); + } + List orderByExpressions = pinotQuery.getOrderByList(); + if (orderByExpressions != null) { + for (Expression expression : orderByExpressions) { + // NOTE: Order-by is always a Function with the ordering of the Expression + handleDistinctMultiValuedOverride(expression.getFunctionCall().getOperands().get(0), tableSchema); + } + } + Expression havingExpression = pinotQuery.getHavingExpression(); + if (havingExpression != null) { + handleDistinctMultiValuedOverride(havingExpression, tableSchema); + } + } + + /** + * Rewrites selected 'Distinct' prefixed function to 'Distinct----MV' function for the field of multivalued type. + */ + private static void handleDistinctMultiValuedOverride(Expression expression, Schema tableSchema) { + Function function = expression.getFunctionCall(); + if (function == null) { + return; + } + + String overrideOperator = DISTINCT_MV_COL_FUNCTION_OVERRIDE_MAP.get(function.getOperator()); + if (overrideOperator != null) { + List operands = function.getOperands(); + if (operands.size() >= 1 && operands.get(0).isSetIdentifier() && isMultiValueColumn(tableSchema, + operands.get(0).getIdentifier().getName())) { + // we are only checking the first operand that if its a MV column as all the overriding agg. fn.'s have + // first operator is column name + function.setOperator(overrideOperator); + } + } else { + for (Expression operand : function.getOperands()) { + handleDistinctMultiValuedOverride(operand, tableSchema); + } + } + } + + /** + * Rewrites 'DistinctCount' to 'DistinctCountBitmap' for the given expression. + */ + private static void handleDistinctCountBitmapOverride(Expression expression) { + Function function = expression.getFunctionCall(); + if (function == null) { + return; + } + if (function.getOperator().equals("distinctcount")) { + function.setOperator("distinctcountbitmap"); + } else { + for (Expression operand : function.getOperands()) { + handleDistinctCountBitmapOverride(operand); + } + } + } + + private HandlerContext getHandlerContext(@Nullable TableConfig offlineTableConfig, + @Nullable TableConfig realtimeTableConfig) { + Boolean disableGroovyOverride = null; + Boolean useApproximateFunctionOverride = null; + if (offlineTableConfig != null && offlineTableConfig.getQueryConfig() != null) { + QueryConfig offlineTableQueryConfig = offlineTableConfig.getQueryConfig(); + Boolean disableGroovyOfflineTableOverride = offlineTableQueryConfig.getDisableGroovy(); + if (disableGroovyOfflineTableOverride != null) { + disableGroovyOverride = disableGroovyOfflineTableOverride; + } + Boolean useApproximateFunctionOfflineTableOverride = offlineTableQueryConfig.getUseApproximateFunction(); + if (useApproximateFunctionOfflineTableOverride != null) { + useApproximateFunctionOverride = useApproximateFunctionOfflineTableOverride; + } + } + if (realtimeTableConfig != null && realtimeTableConfig.getQueryConfig() != null) { + QueryConfig realtimeTableQueryConfig = realtimeTableConfig.getQueryConfig(); + Boolean disableGroovyRealtimeTableOverride = realtimeTableQueryConfig.getDisableGroovy(); + if (disableGroovyRealtimeTableOverride != null) { + if (disableGroovyOverride == null) { + disableGroovyOverride = disableGroovyRealtimeTableOverride; + } else { + // Disable Groovy if either offline or realtime table config disables Groovy + disableGroovyOverride |= disableGroovyRealtimeTableOverride; + } + } + Boolean useApproximateFunctionRealtimeTableOverride = realtimeTableQueryConfig.getUseApproximateFunction(); + if (useApproximateFunctionRealtimeTableOverride != null) { + if (useApproximateFunctionOverride == null) { + useApproximateFunctionOverride = useApproximateFunctionRealtimeTableOverride; + } else { + // Use approximate function if both offline and realtime table config uses approximate function + useApproximateFunctionOverride &= useApproximateFunctionRealtimeTableOverride; + } + } + } + + boolean disableGroovy = disableGroovyOverride != null ? disableGroovyOverride : _disableGroovy; + boolean useApproximateFunction = + useApproximateFunctionOverride != null ? useApproximateFunctionOverride : _useApproximateFunction; + return new HandlerContext(disableGroovy, useApproximateFunction); + } + + private static class HandlerContext { + final boolean _disableGroovy; + final boolean _useApproximateFunction; + + HandlerContext(boolean disableGroovy, boolean useApproximateFunction) { + _disableGroovy = disableGroovy; + _useApproximateFunction = useApproximateFunction; + } + } + + /** + * Verifies that no groovy is present in the PinotQuery when disabled. + */ + @VisibleForTesting + static void rejectGroovyQuery(PinotQuery pinotQuery) { + List selectList = pinotQuery.getSelectList(); + for (Expression expression : selectList) { + rejectGroovyQuery(expression); + } + List orderByList = pinotQuery.getOrderByList(); + if (orderByList != null) { + for (Expression expression : orderByList) { + // NOTE: Order-by is always a Function with the ordering of the Expression + rejectGroovyQuery(expression.getFunctionCall().getOperands().get(0)); + } + } + Expression havingExpression = pinotQuery.getHavingExpression(); + if (havingExpression != null) { + rejectGroovyQuery(havingExpression); + } + Expression filterExpression = pinotQuery.getFilterExpression(); + if (filterExpression != null) { + rejectGroovyQuery(filterExpression); + } + List groupByList = pinotQuery.getGroupByList(); + if (groupByList != null) { + for (Expression expression : groupByList) { + rejectGroovyQuery(expression); + } + } + } + + private static void rejectGroovyQuery(Expression expression) { + Function function = expression.getFunctionCall(); + if (function == null) { + return; + } + if (function.getOperator().equals("groovy")) { + throw new BadQueryRequestException("Groovy transform functions are disabled for queries"); + } + for (Expression operandExpression : function.getOperands()) { + rejectGroovyQuery(operandExpression); + } + } + + /** + * Rewrites potential expensive functions to their approximation counterparts. + * - DISTINCT_COUNT -> DISTINCT_COUNT_SMART_HLL + * - PERCENTILE -> PERCENTILE_SMART_TDIGEST + */ + @VisibleForTesting + static void handleApproximateFunctionOverride(PinotQuery pinotQuery) { + for (Expression expression : pinotQuery.getSelectList()) { + handleApproximateFunctionOverride(expression); + } + List orderByExpressions = pinotQuery.getOrderByList(); + if (orderByExpressions != null) { + for (Expression expression : orderByExpressions) { + // NOTE: Order-by is always a Function with the ordering of the Expression + handleApproximateFunctionOverride(expression.getFunctionCall().getOperands().get(0)); + } + } + Expression havingExpression = pinotQuery.getHavingExpression(); + if (havingExpression != null) { + handleApproximateFunctionOverride(havingExpression); + } + } + + private static void handleApproximateFunctionOverride(Expression expression) { + Function function = expression.getFunctionCall(); + if (function == null) { + return; + } + String functionName = function.getOperator(); + if (functionName.equals("distinctcount") || functionName.equals("distinctcountmv")) { + function.setOperator("distinctcountsmarthll"); + } else if (functionName.startsWith("percentile")) { + String remainingFunctionName = functionName.substring(10); + if (remainingFunctionName.isEmpty() || remainingFunctionName.equals("mv")) { + function.setOperator("percentilesmarttdigest"); + } else if (remainingFunctionName.matches("\\d+")) { + try { + int percentile = Integer.parseInt(remainingFunctionName); + function.setOperator("percentilesmarttdigest"); + function.addToOperands(RequestUtils.getLiteralExpression(percentile)); + } catch (Exception e) { + throw new BadQueryRequestException("Illegal function name: " + functionName); + } + } else if (remainingFunctionName.matches("\\d+mv")) { + try { + int percentile = Integer.parseInt(remainingFunctionName.substring(0, remainingFunctionName.length() - 2)); + function.setOperator("percentilesmarttdigest"); + function.addToOperands(RequestUtils.getLiteralExpression(percentile)); + } catch (Exception e) { + throw new BadQueryRequestException("Illegal function name: " + functionName); + } + } + } else { + for (Expression operand : function.getOperands()) { + handleApproximateFunctionOverride(operand); + } + } + } + + private static void handleExpressionOverride(PinotQuery pinotQuery, + @Nullable Map expressionOverrideMap) { + if (expressionOverrideMap == null) { + return; + } + pinotQuery.getSelectList().replaceAll(o -> handleExpressionOverride(o, expressionOverrideMap)); + Expression filterExpression = pinotQuery.getFilterExpression(); + if (filterExpression != null) { + pinotQuery.setFilterExpression(handleExpressionOverride(filterExpression, expressionOverrideMap)); + } + List groupByList = pinotQuery.getGroupByList(); + if (groupByList != null) { + groupByList.replaceAll(o -> handleExpressionOverride(o, expressionOverrideMap)); + } + List orderByList = pinotQuery.getOrderByList(); + if (orderByList != null) { + for (Expression expression : orderByList) { + // NOTE: Order-by is always a Function with the ordering of the Expression + expression.getFunctionCall().getOperands().replaceAll(o -> handleExpressionOverride(o, expressionOverrideMap)); + } + } + Expression havingExpression = pinotQuery.getHavingExpression(); + if (havingExpression != null) { + pinotQuery.setHavingExpression(handleExpressionOverride(havingExpression, expressionOverrideMap)); + } + } + + private static Expression handleExpressionOverride(Expression expression, + Map expressionOverrideMap) { + Expression override = expressionOverrideMap.get(expression); + if (override != null) { + return new Expression(override); + } + Function function = expression.getFunctionCall(); + if (function != null) { + function.getOperands().replaceAll(o -> handleExpressionOverride(o, expressionOverrideMap)); + } + return expression; + } + + /** + * Returns {@code true} if the given query only contains literals, {@code false} otherwise. + */ + @VisibleForTesting + static boolean isLiteralOnlyQuery(PinotQuery pinotQuery) { + for (Expression expression : pinotQuery.getSelectList()) { + if (!CalciteSqlParser.isLiteralOnlyExpression(expression)) { + return false; + } + } + return true; + } + + /** + * Processes the literal only query. + */ + private BrokerResponseNative processLiteralOnlyQuery(long requestId, PinotQuery pinotQuery, + RequestContext requestContext) { + BrokerResponseNative brokerResponse = new BrokerResponseNative(); + List columnNames = new ArrayList<>(); + List columnTypes = new ArrayList<>(); + List row = new ArrayList<>(); + for (Expression expression : pinotQuery.getSelectList()) { + computeResultsForExpression(expression, columnNames, columnTypes, row); + } + DataSchema dataSchema = + new DataSchema(columnNames.toArray(new String[0]), columnTypes.toArray(new DataSchema.ColumnDataType[0])); + List rows = new ArrayList<>(); + rows.add(row.toArray()); + ResultTable resultTable = new ResultTable(dataSchema, rows); + brokerResponse.setResultTable(resultTable); + brokerResponse.setTimeUsedMs(System.currentTimeMillis() - requestContext.getRequestArrivalTimeMillis()); + augmentStatistics(requestContext, brokerResponse); + if (QueryOptionsUtils.shouldDropResults(pinotQuery.getQueryOptions())) { + brokerResponse.setResultTable(null); + } + return brokerResponse; + } + + // TODO(xiangfu): Move Literal function computation here from Calcite Parser. + private void computeResultsForExpression(Expression e, List columnNames, + List columnTypes, List row) { + if (e.getType() == ExpressionType.LITERAL) { + computeResultsForLiteral(e.getLiteral(), columnNames, columnTypes, row); + } + if (e.getType() == ExpressionType.FUNCTION) { + if (e.getFunctionCall().getOperator().equals("as")) { + String columnName = e.getFunctionCall().getOperands().get(1).getIdentifier().getName(); + computeResultsForExpression(e.getFunctionCall().getOperands().get(0), columnNames, columnTypes, row); + columnNames.set(columnNames.size() - 1, columnName); + } else { + throw new IllegalStateException( + "No able to compute results for function - " + e.getFunctionCall().getOperator()); + } + } + } + + private void computeResultsForLiteral(Literal literal, List columnNames, + List columnTypes, List row) { + columnNames.add(RequestUtils.prettyPrint(literal)); + switch (literal.getSetField()) { + case NULL_VALUE: + columnTypes.add(DataSchema.ColumnDataType.UNKNOWN); + row.add(null); + break; + case BOOL_VALUE: + columnTypes.add(DataSchema.ColumnDataType.BOOLEAN); + row.add(literal.getBoolValue()); + break; + case INT_VALUE: + columnTypes.add(DataSchema.ColumnDataType.INT); + row.add(literal.getIntValue()); + break; + case LONG_VALUE: + columnTypes.add(DataSchema.ColumnDataType.LONG); + row.add(literal.getLongValue()); + break; + case FLOAT_VALUE: + columnTypes.add(DataSchema.ColumnDataType.FLOAT); + row.add(Float.intBitsToFloat(literal.getFloatValue())); + break; + case DOUBLE_VALUE: + columnTypes.add(DataSchema.ColumnDataType.DOUBLE); + row.add(literal.getDoubleValue()); + break; + case BIG_DECIMAL_VALUE: + columnTypes.add(DataSchema.ColumnDataType.BIG_DECIMAL); + row.add(BigDecimalUtils.deserialize(literal.getBigDecimalValue())); + break; + case STRING_VALUE: + columnTypes.add(DataSchema.ColumnDataType.STRING); + row.add(literal.getStringValue()); + break; + case BINARY_VALUE: + columnTypes.add(DataSchema.ColumnDataType.BYTES); + row.add(BytesUtils.toHexString(literal.getBinaryValue())); + break; + // TODO: Revisit the array handling. Currently we are setting List into the row. + case INT_ARRAY_VALUE: + columnTypes.add(DataSchema.ColumnDataType.INT_ARRAY); + row.add(literal.getIntArrayValue()); + break; + case LONG_ARRAY_VALUE: + columnTypes.add(DataSchema.ColumnDataType.LONG_ARRAY); + row.add(literal.getLongArrayValue()); + break; + case FLOAT_ARRAY_VALUE: + columnTypes.add(DataSchema.ColumnDataType.FLOAT_ARRAY); + row.add(literal.getFloatArrayValue().stream().map(Float::intBitsToFloat).collect(Collectors.toList())); + break; + case DOUBLE_ARRAY_VALUE: + columnTypes.add(DataSchema.ColumnDataType.DOUBLE_ARRAY); + row.add(literal.getDoubleArrayValue()); + break; + case STRING_ARRAY_VALUE: + columnTypes.add(DataSchema.ColumnDataType.STRING_ARRAY); + row.add(literal.getStringArrayValue()); + break; + default: + throw new IllegalStateException("Unsupported literal: " + literal); + } + } + + /** + * Fixes the column names to the actual column names in the given query. + */ + @VisibleForTesting + static void updateColumnNames(String rawTableName, PinotQuery pinotQuery, boolean isCaseInsensitive, + Map columnNameMap) { + if (pinotQuery != null) { + boolean hasStar = false; + for (Expression expression : pinotQuery.getSelectList()) { + fixColumnName(rawTableName, expression, columnNameMap, isCaseInsensitive); + //check if the select expression is '*' + if (!hasStar && expression.equals(STAR)) { + hasStar = true; + } + } + //if query has a '*' selection along with other columns + if (hasStar) { + expandStarExpressionsToActualColumns(pinotQuery, columnNameMap); + } + Expression filterExpression = pinotQuery.getFilterExpression(); + if (filterExpression != null) { + // We don't support alias in filter expression, so we don't need to pass aliasMap + fixColumnName(rawTableName, filterExpression, columnNameMap, isCaseInsensitive); + } + List groupByList = pinotQuery.getGroupByList(); + if (groupByList != null) { + for (Expression expression : groupByList) { + fixColumnName(rawTableName, expression, columnNameMap, isCaseInsensitive); + } + } + List orderByList = pinotQuery.getOrderByList(); + if (orderByList != null) { + for (Expression expression : orderByList) { + // NOTE: Order-by is always a Function with the ordering of the Expression + fixColumnName(rawTableName, expression.getFunctionCall().getOperands().get(0), columnNameMap, + isCaseInsensitive); + } + } + Expression havingExpression = pinotQuery.getHavingExpression(); + if (havingExpression != null) { + fixColumnName(rawTableName, havingExpression, columnNameMap, isCaseInsensitive); + } + } + } + + private static void expandStarExpressionsToActualColumns(PinotQuery pinotQuery, Map columnNameMap) { + List originalSelections = pinotQuery.getSelectList(); + //expand '*' + List expandedSelections = new ArrayList<>(); + for (String tableCol : columnNameMap.values()) { + Expression newSelection = RequestUtils.getIdentifierExpression(tableCol); + //we exclude default virtual columns + if (tableCol.charAt(0) != '$') { + expandedSelections.add(newSelection); + } + } + //sort naturally + expandedSelections.sort(null); + List newSelections = new ArrayList<>(); + for (Expression originalSelection : originalSelections) { + if (originalSelection.equals(STAR)) { + newSelections.addAll(expandedSelections); + } else { + newSelections.add(originalSelection); + } + } + pinotQuery.setSelectList(newSelections); + } + + /** + * Fixes the column names to the actual column names in the given expression. + */ + private static void fixColumnName(String rawTableName, Expression expression, Map columnNameMap, + boolean ignoreCase) { + ExpressionType expressionType = expression.getType(); + if (expressionType == ExpressionType.IDENTIFIER) { + Identifier identifier = expression.getIdentifier(); + identifier.setName(getActualColumnName(rawTableName, identifier.getName(), columnNameMap, ignoreCase)); + } else if (expressionType == ExpressionType.FUNCTION) { + final Function functionCall = expression.getFunctionCall(); + switch (functionCall.getOperator()) { + case "as": + fixColumnName(rawTableName, functionCall.getOperands().get(0), columnNameMap, ignoreCase); + break; + case "lookup": + // LOOKUP function looks up another table's schema, skip the check for now. + break; + default: + for (Expression operand : functionCall.getOperands()) { + fixColumnName(rawTableName, operand, columnNameMap, ignoreCase); + } + break; + } + } + } + + /** + * Returns the actual column name for the given column name for: + * - Case-insensitive cluster + * - Column name in the format of [{@code rawTableName}].[column_name] + * - Column name in the format of [logical_table_name].[column_name] while {@code rawTableName} is a translated name + */ + @VisibleForTesting + static String getActualColumnName(String rawTableName, String columnName, @Nullable Map columnNameMap, + boolean ignoreCase) { + if ("*".equals(columnName)) { + return columnName; + } + String columnNameToCheck = trimTableName(rawTableName, columnName, ignoreCase); + if (ignoreCase) { + columnNameToCheck = columnNameToCheck.toLowerCase(); + } + if (columnNameMap != null) { + String actualColumnName = columnNameMap.get(columnNameToCheck); + if (actualColumnName != null) { + return actualColumnName; + } + } + if (columnName.charAt(0) == '$') { + return columnName; + } + throw new BadQueryRequestException("Unknown columnName '" + columnName + "' found in the query"); + } + + private static String trimTableName(String rawTableName, String columnName, boolean ignoreCase) { + int columnNameLength = columnName.length(); + int rawTableNameLength = rawTableName.length(); + if (columnNameLength > rawTableNameLength && columnName.charAt(rawTableNameLength) == '.' + && columnName.regionMatches(ignoreCase, 0, rawTableName, 0, rawTableNameLength)) { + return columnName.substring(rawTableNameLength + 1); + } + // Check if raw table name is translated name ([database_name].[logical_table_name]]) + String[] split = StringUtils.split(rawTableName, '.'); + if (split.length == 2) { + String logicalTableName = split[1]; + int logicalTableNameLength = logicalTableName.length(); + if (columnNameLength > logicalTableNameLength && columnName.charAt(logicalTableNameLength) == '.' + && columnName.regionMatches(ignoreCase, 0, logicalTableName, 0, logicalTableNameLength)) { + return columnName.substring(logicalTableNameLength + 1); + } + } + return columnName; + } + + /** + * Helper function to decide whether to force the log + * + * TODO: come up with other criteria for forcing a log and come up with better numbers + */ + private boolean forceLog(BrokerResponse brokerResponse, long totalTimeMs) { + if (brokerResponse.isNumGroupsLimitReached()) { + return true; + } + + if (brokerResponse.getExceptionsSize() > 0) { + return true; + } + + // If response time is more than 1 sec, force the log + return totalTimeMs > 1000L; + } + + /** + * Sets the query timeout (remaining time in milliseconds) into the query options, and returns the remaining time in + * milliseconds. + *

For the overall query timeout, use query-level timeout (in the query options) if exists, or use table-level + * timeout (in the table config) if exists, or use instance-level timeout (in the broker config). + */ + private long setQueryTimeout(String tableNameWithType, Map queryOptions, long timeSpentMs) + throws TimeoutException { + long queryTimeoutMs; + Long queryLevelTimeoutMs = QueryOptionsUtils.getTimeoutMs(queryOptions); + if (queryLevelTimeoutMs != null) { + // Use query-level timeout if exists + queryTimeoutMs = queryLevelTimeoutMs; + } else { + Long tableLevelTimeoutMs = _routingManager.getQueryTimeoutMs(tableNameWithType); + if (tableLevelTimeoutMs != null) { + // Use table-level timeout if exists + queryTimeoutMs = tableLevelTimeoutMs; + } else { + // Use instance-level timeout + queryTimeoutMs = _brokerTimeoutMs; + } + } + + long remainingTimeMs = queryTimeoutMs - timeSpentMs; + if (remainingTimeMs <= 0) { + String errorMessage = + String.format("Query timed out (time spent: %dms, timeout: %dms) for table: %s before scattering the request", + timeSpentMs, queryTimeoutMs, tableNameWithType); + throw new TimeoutException(errorMessage); + } + queryOptions.put(QueryOptionKey.TIMEOUT_MS, Long.toString(remainingTimeMs)); + return remainingTimeMs; + } + + /** + * Sets a query option indicating the maximum response size that can be sent from a server to the broker. This size + * is measured for the serialized response. + * + * The overriding order of priority is: + * 1. QueryOption -> maxServerResponseSizeBytes + * 2. QueryOption -> maxQueryResponseSizeBytes + * 3. TableConfig -> maxServerResponseSizeBytes + * 4. TableConfig -> maxQueryResponseSizeBytes + * 5. BrokerConfig -> maxServerResponseSizeBytes + * 6. BrokerConfig -> maxServerResponseSizeBytes + */ + private void setMaxServerResponseSizeBytes(int numServers, Map queryOptions, + @Nullable TableConfig tableConfig) { + // QueryOption + if (QueryOptionsUtils.getMaxServerResponseSizeBytes(queryOptions) != null) { + return; + } + Long maxQueryResponseSizeQueryOption = QueryOptionsUtils.getMaxQueryResponseSizeBytes(queryOptions); + if (maxQueryResponseSizeQueryOption != null) { + queryOptions.put(QueryOptionKey.MAX_SERVER_RESPONSE_SIZE_BYTES, + Long.toString(maxQueryResponseSizeQueryOption / numServers)); + return; + } + + // TableConfig + if (tableConfig != null && tableConfig.getQueryConfig() != null) { + QueryConfig queryConfig = tableConfig.getQueryConfig(); + if (queryConfig.getMaxServerResponseSizeBytes() != null) { + queryOptions.put(QueryOptionKey.MAX_SERVER_RESPONSE_SIZE_BYTES, + Long.toString(queryConfig.getMaxServerResponseSizeBytes())); + return; + } + if (queryConfig.getMaxQueryResponseSizeBytes() != null) { + queryOptions.put(QueryOptionKey.MAX_SERVER_RESPONSE_SIZE_BYTES, + Long.toString(queryConfig.getMaxQueryResponseSizeBytes() / numServers)); + return; + } + } + + // BrokerConfig + String maxServerResponseSizeBrokerConfig = _config.getProperty(Broker.CONFIG_OF_MAX_SERVER_RESPONSE_SIZE_BYTES); + if (maxServerResponseSizeBrokerConfig != null) { + queryOptions.put(QueryOptionKey.MAX_SERVER_RESPONSE_SIZE_BYTES, + Long.toString(DataSizeUtils.toBytes(maxServerResponseSizeBrokerConfig))); + return; + } + + String maxQueryResponseSizeBrokerConfig = _config.getProperty(Broker.CONFIG_OF_MAX_QUERY_RESPONSE_SIZE_BYTES); + if (maxQueryResponseSizeBrokerConfig != null) { + queryOptions.put(QueryOptionKey.MAX_SERVER_RESPONSE_SIZE_BYTES, + Long.toString(DataSizeUtils.toBytes(maxQueryResponseSizeBrokerConfig) / numServers)); + } + } + + /** + * Broker side validation on the query. + *

Throw exception if query does not pass validation. + *

Current validations are: + *

    + *
  • Value for 'LIMIT' <= configured value
  • + *
  • Query options must be set to SQL mode
  • + *
  • Check if numReplicaGroupsToQuery option provided is valid
  • + *
+ */ + @VisibleForTesting + static void validateRequest(PinotQuery pinotQuery, int queryResponseLimit) { + // Verify LIMIT + int limit = pinotQuery.getLimit(); + if (limit > queryResponseLimit) { + throw new IllegalStateException( + "Value for 'LIMIT' (" + limit + ") exceeds maximum allowed value of " + queryResponseLimit); + } + + Map queryOptions = pinotQuery.getQueryOptions(); + try { + // throw errors if options is less than 1 or invalid + Integer numReplicaGroupsToQuery = QueryOptionsUtils.getNumReplicaGroupsToQuery(queryOptions); + if (numReplicaGroupsToQuery != null) { + Preconditions.checkState(numReplicaGroupsToQuery > 0, "numReplicaGroups must be " + "positive number, got: %d", + numReplicaGroupsToQuery); + } + } catch (NumberFormatException e) { + String numReplicaGroupsToQuery = queryOptions.get(QueryOptionKey.NUM_REPLICA_GROUPS_TO_QUERY); + throw new IllegalStateException( + String.format("numReplicaGroups must be a positive number, got: %s", numReplicaGroupsToQuery)); + } + + if (pinotQuery.getDataSource().getSubquery() != null) { + validateRequest(pinotQuery.getDataSource().getSubquery(), queryResponseLimit); + } + } + + /** + * Helper method to attach the time boundary to the given PinotQuery. + */ + private static void attachTimeBoundary(PinotQuery pinotQuery, TimeBoundaryInfo timeBoundaryInfo, + boolean isOfflineRequest) { + String functionName = isOfflineRequest ? FilterKind.LESS_THAN_OR_EQUAL.name() : FilterKind.GREATER_THAN.name(); + String timeColumn = timeBoundaryInfo.getTimeColumn(); + String timeValue = timeBoundaryInfo.getTimeValue(); + Expression timeFilterExpression = + RequestUtils.getFunctionExpression(functionName, RequestUtils.getIdentifierExpression(timeColumn), + RequestUtils.getLiteralExpression(timeValue)); + + Expression filterExpression = pinotQuery.getFilterExpression(); + if (filterExpression != null) { + pinotQuery.setFilterExpression( + RequestUtils.getFunctionExpression(FilterKind.AND.name(), filterExpression, timeFilterExpression)); + } else { + pinotQuery.setFilterExpression(timeFilterExpression); + } + } + + /** + * Processes the optimized broker requests for both OFFLINE and REALTIME table. + * TODO: Directly take PinotQuery + */ + protected abstract BrokerResponseNative processBrokerRequest(long requestId, BrokerRequest originalBrokerRequest, + BrokerRequest serverBrokerRequest, @Nullable BrokerRequest offlineBrokerRequest, + @Nullable Map, List>> offlineRoutingTable, + @Nullable BrokerRequest realtimeBrokerRequest, + @Nullable Map, List>> realtimeRoutingTable, long timeoutMs, + ServerStats serverStats, RequestContext requestContext) + throws Exception; + + private String getGlobalQueryId(long requestId) { + return _brokerId + "_" + requestId; + } + + /** + * Helper class to pass the per server statistics. + */ + public static class ServerStats { + private String _serverStats; + + public String getServerStats() { + return _serverStats; + } + + public void setServerStats(String serverStats) { + _serverStats = serverStats; + } + } + + /** + * Helper class to track the query plaintext and the requested servers. + */ + private static class QueryServers { + final String _query; + final Set _servers = new HashSet<>(); + + QueryServers(String query, @Nullable Map, List>> offlineRoutingTable, + @Nullable Map, List>> realtimeRoutingTable) { + _query = query; + if (offlineRoutingTable != null) { + _servers.addAll(offlineRoutingTable.keySet()); + } + if (realtimeRoutingTable != null) { + _servers.addAll(realtimeRoutingTable.keySet()); + } + } + } +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BrokerRequestHandler.java b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BrokerRequestHandler.java index 873cb9e0a10a..9744a1cd5ec0 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BrokerRequestHandler.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BrokerRequestHandler.java @@ -19,14 +19,21 @@ package org.apache.pinot.broker.requesthandler; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.annotations.VisibleForTesting; import java.util.Map; import java.util.concurrent.Executor; import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; -import org.apache.commons.httpclient.HttpConnectionManager; +import javax.ws.rs.core.HttpHeaders; +import org.apache.http.conn.HttpClientConnectionManager; import org.apache.pinot.broker.api.RequesterIdentity; import org.apache.pinot.common.response.BrokerResponse; import org.apache.pinot.spi.trace.RequestContext; +import org.apache.pinot.spi.trace.RequestScope; +import org.apache.pinot.spi.trace.Tracing; +import org.apache.pinot.spi.utils.CommonConstants.Broker.Request; +import org.apache.pinot.spi.utils.JsonUtils; import org.apache.pinot.sql.parsers.SqlNodeAndOptions; @@ -38,13 +45,18 @@ public interface BrokerRequestHandler { void shutDown(); BrokerResponse handleRequest(JsonNode request, @Nullable SqlNodeAndOptions sqlNodeAndOptions, - @Nullable RequesterIdentity requesterIdentity, RequestContext requestContext) + @Nullable RequesterIdentity requesterIdentity, RequestContext requestContext, @Nullable HttpHeaders httpHeaders) throws Exception; - default BrokerResponse handleRequest(JsonNode request, @Nullable RequesterIdentity requesterIdentity, - RequestContext requestContext) + @VisibleForTesting + default BrokerResponse handleRequest(String sql) throws Exception { - return handleRequest(request, null, requesterIdentity, requestContext); + ObjectNode request = JsonUtils.newObjectNode(); + request.put(Request.SQL, sql); + try (RequestScope requestContext = Tracing.getTracer().createRequestScope()) { + requestContext.setRequestArrivalTimeMillis(System.currentTimeMillis()); + return handleRequest(request, null, null, requestContext, null); + } } Map getRunningQueries(); @@ -59,7 +71,7 @@ default BrokerResponse handleRequest(JsonNode request, @Nullable RequesterIdenti * @param serverResponses to collect cancel responses from all servers if a map is provided * @return true if there is a running query for the given queryId. */ - boolean cancelQuery(long queryId, int timeoutMs, Executor executor, HttpConnectionManager connMgr, + boolean cancelQuery(long queryId, int timeoutMs, Executor executor, HttpClientConnectionManager connMgr, Map serverResponses) throws Exception; } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BrokerRequestHandlerDelegate.java b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BrokerRequestHandlerDelegate.java index 3e6a0598bea9..cdbf64e3bc72 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BrokerRequestHandlerDelegate.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BrokerRequestHandlerDelegate.java @@ -22,19 +22,17 @@ import java.util.Map; import java.util.concurrent.Executor; import javax.annotation.Nullable; -import org.apache.commons.httpclient.HttpConnectionManager; +import javax.ws.rs.core.HttpHeaders; +import org.apache.http.conn.HttpClientConnectionManager; import org.apache.pinot.broker.api.RequesterIdentity; import org.apache.pinot.common.exception.QueryException; -import org.apache.pinot.common.metrics.BrokerMeter; -import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.response.BrokerResponse; import org.apache.pinot.common.response.broker.BrokerResponseNative; +import org.apache.pinot.common.utils.config.QueryOptionsUtils; import org.apache.pinot.common.utils.request.RequestUtils; import org.apache.pinot.spi.trace.RequestContext; -import org.apache.pinot.spi.utils.CommonConstants; +import org.apache.pinot.spi.utils.CommonConstants.Broker.Request; import org.apache.pinot.sql.parsers.SqlNodeAndOptions; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** @@ -44,67 +42,52 @@ * {@see: @CommonConstant */ public class BrokerRequestHandlerDelegate implements BrokerRequestHandler { - private static final Logger LOGGER = LoggerFactory.getLogger(BrokerRequestHandlerDelegate.class); + private final BaseSingleStageBrokerRequestHandler _singleStageBrokerRequestHandler; + private final MultiStageBrokerRequestHandler _multiStageBrokerRequestHandler; - private final BrokerRequestHandler _singleStageBrokerRequestHandler; - private final BrokerRequestHandler _multiStageWorkerRequestHandler; - private final BrokerMetrics _brokerMetrics; - private final String _brokerId; - - public BrokerRequestHandlerDelegate(String brokerId, BrokerRequestHandler singleStageBrokerRequestHandler, - @Nullable BrokerRequestHandler multiStageWorkerRequestHandler, BrokerMetrics brokerMetrics) { - _brokerId = brokerId; + public BrokerRequestHandlerDelegate(BaseSingleStageBrokerRequestHandler singleStageBrokerRequestHandler, + @Nullable MultiStageBrokerRequestHandler multiStageBrokerRequestHandler) { _singleStageBrokerRequestHandler = singleStageBrokerRequestHandler; - _multiStageWorkerRequestHandler = multiStageWorkerRequestHandler; - _brokerMetrics = brokerMetrics; + _multiStageBrokerRequestHandler = multiStageBrokerRequestHandler; } @Override public void start() { - if (_singleStageBrokerRequestHandler != null) { - _singleStageBrokerRequestHandler.start(); - } - if (_multiStageWorkerRequestHandler != null) { - _multiStageWorkerRequestHandler.start(); + _singleStageBrokerRequestHandler.start(); + if (_multiStageBrokerRequestHandler != null) { + _multiStageBrokerRequestHandler.start(); } } @Override public void shutDown() { - if (_singleStageBrokerRequestHandler != null) { - _singleStageBrokerRequestHandler.shutDown(); - } - if (_multiStageWorkerRequestHandler != null) { - _multiStageWorkerRequestHandler.shutDown(); + _singleStageBrokerRequestHandler.shutDown(); + if (_multiStageBrokerRequestHandler != null) { + _multiStageBrokerRequestHandler.shutDown(); } } @Override public BrokerResponse handleRequest(JsonNode request, @Nullable SqlNodeAndOptions sqlNodeAndOptions, - @Nullable RequesterIdentity requesterIdentity, RequestContext requestContext) + @Nullable RequesterIdentity requesterIdentity, RequestContext requestContext, @Nullable HttpHeaders httpHeaders) throws Exception { - requestContext.setBrokerId(_brokerId); + // Parse the query if needed if (sqlNodeAndOptions == null) { try { - sqlNodeAndOptions = RequestUtils.parseQuery(request.get(CommonConstants.Broker.Request.SQL).asText(), request); + sqlNodeAndOptions = RequestUtils.parseQuery(request.get(Request.SQL).asText(), request); } catch (Exception e) { - LOGGER.info("Caught exception while compiling SQL: {}, {}", request, e.getMessage()); - _brokerMetrics.addMeteredGlobalValue(BrokerMeter.REQUEST_COMPILATION_EXCEPTIONS, 1); + // Do not log or emit metric here because it is pure user error requestContext.setErrorCode(QueryException.SQL_PARSING_ERROR_CODE); return new BrokerResponseNative(QueryException.getException(QueryException.SQL_PARSING_ERROR, e)); } } - if (request.has(CommonConstants.Broker.Request.QUERY_OPTIONS)) { - sqlNodeAndOptions.setExtraOptions(RequestUtils.getOptionsFromJson(request, - CommonConstants.Broker.Request.QUERY_OPTIONS)); - } - - if (_multiStageWorkerRequestHandler != null && Boolean.parseBoolean(sqlNodeAndOptions.getOptions().get( - CommonConstants.Broker.Request.QueryOptionKey.USE_MULTISTAGE_ENGINE))) { - return _multiStageWorkerRequestHandler.handleRequest(request, requesterIdentity, requestContext); + if (_multiStageBrokerRequestHandler != null && QueryOptionsUtils.isUseMultistageEngine( + sqlNodeAndOptions.getOptions())) { + return _multiStageBrokerRequestHandler.handleRequest(request, sqlNodeAndOptions, requesterIdentity, + requestContext, httpHeaders); } else { return _singleStageBrokerRequestHandler.handleRequest(request, sqlNodeAndOptions, requesterIdentity, - requestContext); + requestContext, httpHeaders); } } @@ -117,7 +100,7 @@ public Map getRunningQueries() { } @Override - public boolean cancelQuery(long queryId, int timeoutMs, Executor executor, HttpConnectionManager connMgr, + public boolean cancelQuery(long queryId, int timeoutMs, Executor executor, HttpClientConnectionManager connMgr, Map serverResponses) throws Exception { // TODO: add support for multiStaged engine, basically try to cancel the query on multiStaged engine firstly; if diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BrokerRequestIdGenerator.java b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BrokerRequestIdGenerator.java new file mode 100644 index 000000000000..c97ee44a0af4 --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/BrokerRequestIdGenerator.java @@ -0,0 +1,49 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.requesthandler; + +import java.util.concurrent.atomic.AtomicLong; + +/** + * An ID generator to produce a global unique identifier for each query, used in v1/v2 engine for tracking and + * inter-stage communication(v2 only). It's guaranteed by: + *
    + *
  1. + * Using a mask computed using the hash-code of the broker-id to ensure two brokers don't arrive at the same + * requestId. This mask becomes the most significant 9 digits (in base-10). + *
  2. + *
  3. + * Using a auto-incrementing counter for the least significant 9 digits (in base-10). + *
  4. + *
+ */ +public class BrokerRequestIdGenerator { + private static final long OFFSET = 1_000_000_000L; + private final long _mask; + private final AtomicLong _incrementingId = new AtomicLong(0); + + public BrokerRequestIdGenerator(String brokerId) { + _mask = ((long) (brokerId.hashCode() & Integer.MAX_VALUE)) * OFFSET; + } + + public long get() { + long normalized = (_incrementingId.getAndIncrement() & Long.MAX_VALUE) % OFFSET; + return _mask + normalized; + } +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/GrpcBrokerRequestHandler.java b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/GrpcBrokerRequestHandler.java index 95d17c955b61..96e06ffec5a9 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/GrpcBrokerRequestHandler.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/GrpcBrokerRequestHandler.java @@ -23,15 +23,15 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; +import org.apache.commons.lang3.tuple.Pair; import org.apache.pinot.broker.broker.AccessControlFactory; import org.apache.pinot.broker.queryquota.QueryQuotaManager; import org.apache.pinot.broker.routing.BrokerRoutingManager; import org.apache.pinot.common.config.GrpcConfig; -import org.apache.pinot.common.config.TlsConfig; import org.apache.pinot.common.config.provider.TableCache; -import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.proto.Server; import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.common.response.broker.BrokerResponseNative; @@ -43,34 +43,22 @@ import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.env.PinotConfiguration; import org.apache.pinot.spi.trace.RequestContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * The GrpcBrokerRequestHandler class communicates query request via GRPC. */ @ThreadSafe -public class GrpcBrokerRequestHandler extends BaseBrokerRequestHandler { - private static final Logger LOGGER = LoggerFactory.getLogger(GrpcBrokerRequestHandler.class); - - private final GrpcConfig _grpcConfig; +public class GrpcBrokerRequestHandler extends BaseSingleStageBrokerRequestHandler { private final StreamingReduceService _streamingReduceService; private final PinotStreamingQueryClient _streamingQueryClient; // TODO: Support TLS public GrpcBrokerRequestHandler(PinotConfiguration config, String brokerId, BrokerRoutingManager routingManager, - AccessControlFactory accessControlFactory, QueryQuotaManager queryQuotaManager, TableCache tableCache, - BrokerMetrics brokerMetrics, TlsConfig tlsConfig) { - super(config, brokerId, routingManager, accessControlFactory, queryQuotaManager, tableCache, brokerMetrics); - LOGGER.info("Using Grpc BrokerRequestHandler."); - _grpcConfig = GrpcConfig.buildGrpcQueryConfig(config); - - // create streaming query client - _streamingQueryClient = new PinotStreamingQueryClient(_grpcConfig); - - // create streaming reduce service + AccessControlFactory accessControlFactory, QueryQuotaManager queryQuotaManager, TableCache tableCache) { + super(config, brokerId, routingManager, accessControlFactory, queryQuotaManager, tableCache); _streamingReduceService = new StreamingReduceService(config); + _streamingQueryClient = new PinotStreamingQueryClient(GrpcConfig.buildGrpcQueryConfig(config)); } @Override @@ -78,7 +66,7 @@ public void start() { } @Override - public synchronized void shutDown() { + public void shutDown() { _streamingQueryClient.shutdown(); _streamingReduceService.shutDown(); } @@ -86,11 +74,13 @@ public synchronized void shutDown() { @Override protected BrokerResponseNative processBrokerRequest(long requestId, BrokerRequest originalBrokerRequest, BrokerRequest serverBrokerRequest, @Nullable BrokerRequest offlineBrokerRequest, - @Nullable Map> offlineRoutingTable, @Nullable BrokerRequest realtimeBrokerRequest, - @Nullable Map> realtimeRoutingTable, long timeoutMs, ServerStats serverStats, - RequestContext requestContext) + @Nullable Map, List>> offlineRoutingTable, + @Nullable BrokerRequest realtimeBrokerRequest, + @Nullable Map, List>> realtimeRoutingTable, long timeoutMs, + ServerStats serverStats, RequestContext requestContext) throws Exception { // TODO: Support failure detection + // TODO: Add servers queried/responded stats assert offlineBrokerRequest != null || realtimeBrokerRequest != null; Map> responseMap = new HashMap<>(); if (offlineBrokerRequest != null) { @@ -103,19 +93,23 @@ protected BrokerResponseNative processBrokerRequest(long requestId, BrokerReques sendRequest(requestId, TableType.REALTIME, realtimeBrokerRequest, realtimeRoutingTable, responseMap, requestContext.isSampledRequest()); } - return _streamingReduceService.reduceOnStreamResponse(originalBrokerRequest, responseMap, timeoutMs, - _brokerMetrics); + long reduceStartTimeNs = System.nanoTime(); + BrokerResponseNative brokerResponse = + _streamingReduceService.reduceOnStreamResponse(originalBrokerRequest, responseMap, timeoutMs, _brokerMetrics); + brokerResponse.setBrokerReduceTimeMs(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - reduceStartTimeNs)); + return brokerResponse; } /** * Query pinot server for data table. */ private void sendRequest(long requestId, TableType tableType, BrokerRequest brokerRequest, - Map> routingTable, + Map, List>> routingTable, Map> responseMap, boolean trace) { - for (Map.Entry> routingEntry : routingTable.entrySet()) { + for (Map.Entry, List>> routingEntry : routingTable.entrySet()) { ServerInstance serverInstance = routingEntry.getKey(); - List segments = routingEntry.getValue(); + // TODO: support optional segments for GrpcQueryServer. + List segments = routingEntry.getValue().getLeft(); String serverHost = serverInstance.getHostname(); int port = serverInstance.getGrpcPort(); // TODO: enable throttling on per host bases. diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java index e480e8f5d6ea..34355c06c7a8 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/MultiStageBrokerRequestHandler.java @@ -19,44 +19,60 @@ package org.apache.pinot.broker.requesthandler; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.concurrent.TimeUnit; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; import javax.annotation.Nullable; -import org.apache.calcite.jdbc.CalciteSchemaBuilder; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; +import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.pinot.broker.api.AccessControl; import org.apache.pinot.broker.api.RequesterIdentity; import org.apache.pinot.broker.broker.AccessControlFactory; +import org.apache.pinot.broker.querylog.QueryLogger; import org.apache.pinot.broker.queryquota.QueryQuotaManager; import org.apache.pinot.broker.routing.BrokerRoutingManager; +import org.apache.pinot.calcite.jdbc.CalciteSchemaBuilder; import org.apache.pinot.common.config.provider.TableCache; import org.apache.pinot.common.exception.QueryException; import org.apache.pinot.common.metrics.BrokerMeter; -import org.apache.pinot.common.metrics.BrokerMetrics; -import org.apache.pinot.common.request.BrokerRequest; +import org.apache.pinot.common.metrics.BrokerQueryPhase; import org.apache.pinot.common.response.BrokerResponse; import org.apache.pinot.common.response.broker.BrokerResponseNative; +import org.apache.pinot.common.response.broker.BrokerResponseNativeV2; import org.apache.pinot.common.response.broker.ResultTable; import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.common.utils.DatabaseUtils; +import org.apache.pinot.common.utils.ExceptionUtils; import org.apache.pinot.common.utils.config.QueryOptionsUtils; import org.apache.pinot.common.utils.request.RequestUtils; -import org.apache.pinot.core.transport.ServerInstance; +import org.apache.pinot.core.auth.Actions; +import org.apache.pinot.core.auth.TargetType; import org.apache.pinot.query.QueryEnvironment; import org.apache.pinot.query.catalog.PinotCatalog; import org.apache.pinot.query.mailbox.MailboxService; -import org.apache.pinot.query.mailbox.MultiplexingMailboxService; -import org.apache.pinot.query.planner.QueryPlan; +import org.apache.pinot.query.planner.physical.DispatchablePlanFragment; +import org.apache.pinot.query.planner.physical.DispatchableSubPlan; +import org.apache.pinot.query.planner.plannode.PlanNode; import org.apache.pinot.query.routing.WorkerManager; -import org.apache.pinot.query.runtime.blocks.TransferableBlock; -import org.apache.pinot.query.service.QueryConfig; -import org.apache.pinot.query.service.QueryDispatcher; +import org.apache.pinot.query.runtime.MultiStageStatsTreeBuilder; +import org.apache.pinot.query.runtime.plan.MultiStageQueryStats; +import org.apache.pinot.query.service.dispatch.QueryDispatcher; import org.apache.pinot.query.type.TypeFactory; import org.apache.pinot.query.type.TypeSystem; +import org.apache.pinot.spi.auth.TableAuthorizationResult; import org.apache.pinot.spi.env.PinotConfiguration; -import org.apache.pinot.spi.exception.BadQueryRequestException; +import org.apache.pinot.spi.exception.DatabaseConflictException; import org.apache.pinot.spi.trace.RequestContext; import org.apache.pinot.spi.utils.CommonConstants; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.apache.pinot.sql.parsers.SqlNodeAndOptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,127 +80,277 @@ public class MultiStageBrokerRequestHandler extends BaseBrokerRequestHandler { private static final Logger LOGGER = LoggerFactory.getLogger(MultiStageBrokerRequestHandler.class); - private final String _reducerHostname; - private final int _reducerPort; - private final long _defaultBrokerTimeoutMs; - private final MailboxService _mailboxService; - private final QueryEnvironment _queryEnvironment; + private static final int NUM_UNAVAILABLE_SEGMENTS_TO_LOG = 10; + + private final WorkerManager _workerManager; private final QueryDispatcher _queryDispatcher; - public MultiStageBrokerRequestHandler(PinotConfiguration config, String brokerIdFromConfig, - BrokerRoutingManager routingManager, AccessControlFactory accessControlFactory, - QueryQuotaManager queryQuotaManager, TableCache tableCache, BrokerMetrics brokerMetrics) { - super(config, brokerIdFromConfig, routingManager, accessControlFactory, queryQuotaManager, tableCache, - brokerMetrics); - LOGGER.info("Using Multi-stage BrokerRequestHandler."); - String reducerHostname = config.getProperty(QueryConfig.KEY_OF_QUERY_RUNNER_HOSTNAME); - if (reducerHostname == null) { - // use broker ID as host name, but remove the - String brokerId = brokerIdFromConfig; - brokerId = brokerId.startsWith(CommonConstants.Helix.PREFIX_OF_BROKER_INSTANCE) ? brokerId.substring( - CommonConstants.Helix.SERVER_INSTANCE_PREFIX_LENGTH) : brokerId; - brokerId = StringUtils.split(brokerId, "_").length > 1 ? StringUtils.split(brokerId, "_")[0] : brokerId; - reducerHostname = brokerId; - } - _reducerHostname = reducerHostname; - _reducerPort = config.getProperty(QueryConfig.KEY_OF_QUERY_RUNNER_PORT, QueryConfig.DEFAULT_QUERY_RUNNER_PORT); - _defaultBrokerTimeoutMs = config.getProperty(CommonConstants.Broker.CONFIG_OF_BROKER_TIMEOUT_MS, - CommonConstants.Broker.DEFAULT_BROKER_TIMEOUT_MS); - _queryEnvironment = new QueryEnvironment(new TypeFactory(new TypeSystem()), - CalciteSchemaBuilder.asRootSchema(new PinotCatalog(tableCache)), - new WorkerManager(_reducerHostname, _reducerPort, routingManager)); - _queryDispatcher = new QueryDispatcher(); - - // it is OK to ignore the onDataAvailable callback because the broker top-level operators - // always run in-line (they don't have any scheduler) - _mailboxService = MultiplexingMailboxService.newInstance(_reducerHostname, _reducerPort, config, ignored -> { }); - - // TODO: move this to a startUp() function. - _mailboxService.start(); + public MultiStageBrokerRequestHandler(PinotConfiguration config, String brokerId, BrokerRoutingManager routingManager, + AccessControlFactory accessControlFactory, QueryQuotaManager queryQuotaManager, TableCache tableCache) { + super(config, brokerId, routingManager, accessControlFactory, queryQuotaManager, tableCache); + String hostname = config.getProperty(CommonConstants.MultiStageQueryRunner.KEY_OF_QUERY_RUNNER_HOSTNAME); + int port = Integer.parseInt(config.getProperty(CommonConstants.MultiStageQueryRunner.KEY_OF_QUERY_RUNNER_PORT)); + _workerManager = new WorkerManager(hostname, port, _routingManager); + _queryDispatcher = new QueryDispatcher(new MailboxService(hostname, port, config)); + LOGGER.info("Initialized MultiStageBrokerRequestHandler on host: {}, port: {} with broker id: {}, timeout: {}ms, " + + "query log max length: {}, query log max rate: {}", hostname, port, _brokerId, _brokerTimeoutMs, + _queryLogger.getMaxQueryLengthToLog(), _queryLogger.getLogRateLimit()); } @Override - public BrokerResponse handleRequest(JsonNode request, @Nullable SqlNodeAndOptions sqlNodeAndOptions, - @Nullable RequesterIdentity requesterIdentity, RequestContext requestContext) - throws Exception { - long requestId = _requestIdGenerator.incrementAndGet(); - requestContext.setRequestId(requestId); - requestContext.setRequestArrivalTimeMillis(System.currentTimeMillis()); - - // First-stage access control to prevent unauthenticated requests from using up resources. Secondary table-level - // check comes later. - boolean hasAccess = _accessControlFactory.create().hasAccess(requesterIdentity); - if (!hasAccess) { - _brokerMetrics.addMeteredGlobalValue(BrokerMeter.REQUEST_DROPPED_DUE_TO_ACCESS_ERROR, 1); - LOGGER.info("Access denied for requestId {}", requestId); - requestContext.setErrorCode(QueryException.ACCESS_DENIED_ERROR_CODE); - return new BrokerResponseNative(QueryException.ACCESS_DENIED_ERROR); - } + public void start() { + _queryDispatcher.start(); + } - JsonNode sql = request.get(CommonConstants.Broker.Request.SQL); - if (sql == null) { - throw new BadQueryRequestException("Failed to find 'sql' in the request: " + request); - } - String query = sql.asText(); - requestContext.setQuery(query); - return handleRequest(requestId, query, sqlNodeAndOptions, request, requesterIdentity, requestContext); + @Override + public void shutDown() { + _queryDispatcher.shutdown(); } - private BrokerResponseNative handleRequest(long requestId, String query, - @Nullable SqlNodeAndOptions sqlNodeAndOptions, JsonNode request, @Nullable RequesterIdentity requesterIdentity, - RequestContext requestContext) - throws Exception { + @Override + protected BrokerResponse handleRequest(long requestId, String query, @Nullable SqlNodeAndOptions sqlNodeAndOptions, + JsonNode request, @Nullable RequesterIdentity requesterIdentity, RequestContext requestContext, + HttpHeaders httpHeaders, AccessControl accessControl) { LOGGER.debug("SQL query for request {}: {}", requestId, query); - long compilationStartTimeNs; + // Parse the query if needed + if (sqlNodeAndOptions == null) { + try { + sqlNodeAndOptions = RequestUtils.parseQuery(query, request); + } catch (Exception e) { + // Do not log or emit metric here because it is pure user error + requestContext.setErrorCode(QueryException.SQL_PARSING_ERROR_CODE); + return new BrokerResponseNative(QueryException.getException(QueryException.SQL_PARSING_ERROR, e)); + } + } + + // Compile the request + Map queryOptions = sqlNodeAndOptions.getOptions(); + long compilationStartTimeNs = System.nanoTime(); long queryTimeoutMs; - QueryPlan queryPlan; + QueryEnvironment.QueryPlannerResult queryPlanResult; try { - // Parse the request - sqlNodeAndOptions = sqlNodeAndOptions != null ? sqlNodeAndOptions : RequestUtils.parseQuery(query, request); - Long timeoutMsFromQueryOption = QueryOptionsUtils.getTimeoutMs(sqlNodeAndOptions.getOptions()); - queryTimeoutMs = timeoutMsFromQueryOption == null ? _defaultBrokerTimeoutMs : timeoutMsFromQueryOption; - // Compile the request - compilationStartTimeNs = System.nanoTime(); + Long timeoutMsFromQueryOption = QueryOptionsUtils.getTimeoutMs(queryOptions); + queryTimeoutMs = timeoutMsFromQueryOption != null ? timeoutMsFromQueryOption : _brokerTimeoutMs; + String database = DatabaseUtils.extractDatabaseFromQueryRequest(queryOptions, httpHeaders); + QueryEnvironment queryEnvironment = new QueryEnvironment(new TypeFactory(new TypeSystem()), + CalciteSchemaBuilder.asRootSchema(new PinotCatalog(database, _tableCache), database), _workerManager, + _tableCache); switch (sqlNodeAndOptions.getSqlNode().getKind()) { case EXPLAIN: - String plan = _queryEnvironment.explainQuery(query, sqlNodeAndOptions); + queryPlanResult = queryEnvironment.explainQuery(query, sqlNodeAndOptions, requestId); + String plan = queryPlanResult.getExplainPlan(); + Set tableNames = queryPlanResult.getTableNames(); + TableAuthorizationResult tableAuthorizationResult = + hasTableAccess(requesterIdentity, tableNames, requestContext, httpHeaders); + if (!tableAuthorizationResult.hasAccess()) { + String failureMessage = tableAuthorizationResult.getFailureMessage(); + if (StringUtils.isNotBlank(failureMessage)) { + failureMessage = "Reason: " + failureMessage; + } + throw new WebApplicationException("Permission denied. " + failureMessage, + Response.Status.FORBIDDEN); + } return constructMultistageExplainPlan(query, plan); case SELECT: default: - queryPlan = _queryEnvironment.planQuery(query, sqlNodeAndOptions, requestId); + queryPlanResult = queryEnvironment.planQuery(query, sqlNodeAndOptions, requestId); break; } - } catch (Exception e) { - LOGGER.info("Caught exception while compiling SQL request {}: {}, {}", requestId, query, e.getMessage()); + } catch (DatabaseConflictException e) { + LOGGER.info("{}. Request {}: {}", e.getMessage(), requestId, query); + _brokerMetrics.addMeteredGlobalValue(BrokerMeter.QUERY_VALIDATION_EXCEPTIONS, 1); + requestContext.setErrorCode(QueryException.QUERY_VALIDATION_ERROR_CODE); + return new BrokerResponseNative(QueryException.getException(QueryException.QUERY_VALIDATION_ERROR, e)); + } catch (WebApplicationException e) { + throw e; + } catch (RuntimeException e) { + String consolidatedMessage = ExceptionUtils.consolidateExceptionMessages(e); + LOGGER.warn("Caught exception planning request {}: {}, {}", requestId, query, consolidatedMessage); _brokerMetrics.addMeteredGlobalValue(BrokerMeter.REQUEST_COMPILATION_EXCEPTIONS, 1); - requestContext.setErrorCode(QueryException.SQL_PARSING_ERROR_CODE); - return new BrokerResponseNative(QueryException.getException(QueryException.SQL_PARSING_ERROR, e)); + if (e.getMessage().matches(".* Column .* not found in any table'")) { + requestContext.setErrorCode(QueryException.UNKNOWN_COLUMN_ERROR_CODE); + return new BrokerResponseNative( + QueryException.getException(QueryException.UNKNOWN_COLUMN_ERROR, consolidatedMessage)); + } + requestContext.setErrorCode(QueryException.QUERY_PLANNING_ERROR_CODE); + return new BrokerResponseNative( + QueryException.getException(QueryException.QUERY_PLANNING_ERROR, consolidatedMessage)); } - ResultTable queryResults; - try { - queryResults = _queryDispatcher.submitAndReduce(requestId, queryPlan, _mailboxService, queryTimeoutMs); - } catch (Exception e) { - LOGGER.info("query execution failed", e); - return new BrokerResponseNative(QueryException.getException(QueryException.QUERY_EXECUTION_ERROR, e)); + DispatchableSubPlan dispatchableSubPlan = queryPlanResult.getQueryPlan(); + Set tableNames = queryPlanResult.getTableNames(); + + _brokerMetrics.addMeteredGlobalValue(BrokerMeter.MULTI_STAGE_QUERIES_GLOBAL, 1); + for (String tableName : tableNames) { + _brokerMetrics.addMeteredTableValue(tableName, BrokerMeter.MULTI_STAGE_QUERIES, 1); + } + + requestContext.setTableNames(List.copyOf(tableNames)); + + // Compilation Time. This includes the time taken for parsing, compiling, create stage plans and assigning workers. + long compilationEndTimeNs = System.nanoTime(); + long compilationTimeNs = (compilationEndTimeNs - compilationStartTimeNs) + sqlNodeAndOptions.getParseTimeNs(); + updatePhaseTimingForTables(tableNames, BrokerQueryPhase.REQUEST_COMPILATION, compilationTimeNs); + + // Validate table access. + TableAuthorizationResult tableAuthorizationResult = + hasTableAccess(requesterIdentity, tableNames, requestContext, httpHeaders); + if (!tableAuthorizationResult.hasAccess()) { + String failureMessage = tableAuthorizationResult.getFailureMessage(); + if (StringUtils.isNotBlank(failureMessage)) { + failureMessage = "Reason: " + failureMessage; + } + throw new WebApplicationException("Permission denied." + failureMessage, + Response.Status.FORBIDDEN); + } + + // Validate QPS quota + if (hasExceededQPSQuota(tableNames, requestContext)) { + String errorMessage = String.format("Request %d: %s exceeds query quota.", requestId, query); + return new BrokerResponseNative(QueryException.getException(QueryException.QUOTA_EXCEEDED_ERROR, errorMessage)); } - BrokerResponseNative brokerResponse = new BrokerResponseNative(); + long executionStartTimeNs = System.nanoTime(); + QueryDispatcher.QueryResult queryResults; + try { + queryResults = + _queryDispatcher.submitAndReduce(requestContext, dispatchableSubPlan, queryTimeoutMs, queryOptions); + } catch (TimeoutException e) { + for (String table : tableNames) { + _brokerMetrics.addMeteredTableValue(table, BrokerMeter.BROKER_RESPONSES_WITH_TIMEOUTS, 1); + } + LOGGER.warn("Timed out executing request {}: {}", requestId, query); + requestContext.setErrorCode(QueryException.EXECUTION_TIMEOUT_ERROR_CODE); + return new BrokerResponseNative(QueryException.EXECUTION_TIMEOUT_ERROR); + } catch (Throwable t) { + String consolidatedMessage = ExceptionUtils.consolidateExceptionMessages(t); + LOGGER.error("Caught exception executing request {}: {}, {}", requestId, query, consolidatedMessage); + requestContext.setErrorCode(QueryException.QUERY_EXECUTION_ERROR_CODE); + return new BrokerResponseNative( + QueryException.getException(QueryException.QUERY_EXECUTION_ERROR, consolidatedMessage)); + } long executionEndTimeNs = System.nanoTime(); + updatePhaseTimingForTables(tableNames, BrokerQueryPhase.QUERY_EXECUTION, executionEndTimeNs - executionStartTimeNs); + + BrokerResponseNativeV2 brokerResponse = new BrokerResponseNativeV2(); + brokerResponse.setResultTable(queryResults.getResultTable()); + // TODO: Add servers queried/responded stats + brokerResponse.setBrokerReduceTimeMs(queryResults.getBrokerReduceTimeMs()); + + // Attach unavailable segments + int numUnavailableSegments = 0; + for (Map.Entry> entry : dispatchableSubPlan.getTableToUnavailableSegmentsMap().entrySet()) { + String tableName = entry.getKey(); + Set unavailableSegments = entry.getValue(); + int unavailableSegmentsInSubPlan = unavailableSegments.size(); + numUnavailableSegments += unavailableSegmentsInSubPlan; + brokerResponse.addException(QueryException.getException(QueryException.SERVER_SEGMENT_MISSING_ERROR, + String.format("Found %d unavailable segments for table %s: %s", unavailableSegmentsInSubPlan, tableName, + toSizeLimitedString(unavailableSegments, NUM_UNAVAILABLE_SEGMENTS_TO_LOG)))); + } + requestContext.setNumUnavailableSegments(numUnavailableSegments); + + fillOldBrokerResponseStats(brokerResponse, queryResults.getQueryStats(), dispatchableSubPlan); // Set total query processing time - long totalTimeMs = TimeUnit.NANOSECONDS.toMillis(sqlNodeAndOptions.getParseTimeNs() - + (executionEndTimeNs - compilationStartTimeNs)); + // TODO: Currently we don't emit metric for QUERY_TOTAL_TIME_MS + long totalTimeMs = System.currentTimeMillis() - requestContext.getRequestArrivalTimeMillis(); brokerResponse.setTimeUsedMs(totalTimeMs); - brokerResponse.setResultTable(queryResults); - requestContext.setQueryProcessingTime(totalTimeMs); augmentStatistics(requestContext, brokerResponse); + if (QueryOptionsUtils.shouldDropResults(queryOptions)) { + brokerResponse.setResultTable(null); + } + + // Log query and stats + _queryLogger.log( + new QueryLogger.QueryLogParams(requestContext, tableNames.toString(), brokerResponse, requesterIdentity, null)); + return brokerResponse; } - private BrokerResponseNative constructMultistageExplainPlan(String sql, String plan) { + private void fillOldBrokerResponseStats(BrokerResponseNativeV2 brokerResponse, + List queryStats, DispatchableSubPlan dispatchableSubPlan) { + try { + List stagePlans = dispatchableSubPlan.getQueryStageList(); + List planNodes = new ArrayList<>(stagePlans.size()); + for (DispatchablePlanFragment stagePlan : stagePlans) { + planNodes.add(stagePlan.getPlanFragment().getFragmentRoot()); + } + MultiStageStatsTreeBuilder treeBuilder = new MultiStageStatsTreeBuilder(planNodes, queryStats); + brokerResponse.setStageStats(treeBuilder.jsonStatsByStage(0)); + for (MultiStageQueryStats.StageStats.Closed stageStats : queryStats) { + if (stageStats != null) { // for example pipeline breaker may not have stats + stageStats.forEach((type, stats) -> type.mergeInto(brokerResponse, stats)); + } + } + } catch (Exception e) { + LOGGER.warn("Error encountered while collecting multi-stage stats", e); + brokerResponse.setStageStats(JsonNodeFactory.instance.objectNode().put( + "error", + "Error encountered while collecting multi-stage stats - " + e) + ); + } + } + + /** + * Validates whether the requester has access to all the tables. + */ + private TableAuthorizationResult hasTableAccess(RequesterIdentity requesterIdentity, Set tableNames, + RequestContext requestContext, HttpHeaders httpHeaders) { + final long startTimeNs = System.nanoTime(); + AccessControl accessControl = _accessControlFactory.create(); + + TableAuthorizationResult tableAuthorizationResult = accessControl.authorize(requesterIdentity, tableNames); + + Set failedTables = tableNames.stream() + .filter(table -> !accessControl.hasAccess(httpHeaders, TargetType.TABLE, table, Actions.Table.QUERY)) + .collect(Collectors.toSet()); + + failedTables.addAll(tableAuthorizationResult.getFailedTables()); + + if (!failedTables.isEmpty()) { + tableAuthorizationResult = new TableAuthorizationResult(failedTables); + } else { + tableAuthorizationResult = TableAuthorizationResult.success(); + } + + if (!tableAuthorizationResult.hasAccess()) { + _brokerMetrics.addMeteredGlobalValue(BrokerMeter.REQUEST_DROPPED_DUE_TO_ACCESS_ERROR, 1); + LOGGER.warn("Access denied for requestId {}", requestContext.getRequestId()); + requestContext.setErrorCode(QueryException.ACCESS_DENIED_ERROR_CODE); + } + + updatePhaseTimingForTables(tableNames, BrokerQueryPhase.AUTHORIZATION, System.nanoTime() - startTimeNs); + + return tableAuthorizationResult; + } + + /** + * Returns true if the QPS quota of the tables has exceeded. + */ + private boolean hasExceededQPSQuota(Set tableNames, RequestContext requestContext) { + for (String tableName : tableNames) { + if (!_queryQuotaManager.acquire(tableName)) { + LOGGER.warn("Request {}: query exceeds quota for table: {}", requestContext.getRequestId(), tableName); + requestContext.setErrorCode(QueryException.TOO_MANY_REQUESTS_ERROR_CODE); + String rawTableName = TableNameBuilder.extractRawTableName(tableName); + _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.QUERY_QUOTA_EXCEEDED, 1); + return true; + } + } + return false; + } + + private void updatePhaseTimingForTables(Set tableNames, BrokerQueryPhase phase, long time) { + for (String tableName : tableNames) { + String rawTableName = TableNameBuilder.extractRawTableName(tableName); + _brokerMetrics.addPhaseTiming(rawTableName, phase, time); + } + } + + private BrokerResponse constructMultistageExplainPlan(String sql, String plan) { BrokerResponseNative brokerResponse = BrokerResponseNative.empty(); List rows = new ArrayList<>(); rows.add(new Object[]{sql, plan}); @@ -195,23 +361,26 @@ private BrokerResponseNative constructMultistageExplainPlan(String sql, String p } @Override - protected BrokerResponseNative processBrokerRequest(long requestId, BrokerRequest originalBrokerRequest, - BrokerRequest serverBrokerRequest, @Nullable BrokerRequest offlineBrokerRequest, - @Nullable Map> offlineRoutingTable, @Nullable BrokerRequest realtimeBrokerRequest, - @Nullable Map> realtimeRoutingTable, long timeoutMs, ServerStats serverStats, - RequestContext requestContext) - throws Exception { + public Map getRunningQueries() { + // TODO: Support running query tracking for multi-stage engine throw new UnsupportedOperationException(); } @Override - public void start() { - // no-op + public boolean cancelQuery(long queryId, int timeoutMs, Executor executor, HttpClientConnectionManager connMgr, + Map serverResponses) { + // TODO: Support query cancellation for multi-stage engine + throw new UnsupportedOperationException(); } - @Override - public void shutDown() { - _queryDispatcher.shutdown(); - _mailboxService.shutdown(); + /** + * Returns the string representation of the Set of Strings with a limit on the number of elements. + * @param setOfStrings Set of strings + * @param limit Limit on the number of elements + * @return String representation of the set of the form [a,b,c...]. + */ + private static String toSizeLimitedString(Set setOfStrings, int limit) { + return setOfStrings.stream().limit(limit) + .collect(Collectors.joining(", ", "[", setOfStrings.size() > limit ? "...]" : "]")); } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/SingleConnectionBrokerRequestHandler.java b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/SingleConnectionBrokerRequestHandler.java index d78aa5ffe871..a51634c983f8 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/SingleConnectionBrokerRequestHandler.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/requesthandler/SingleConnectionBrokerRequestHandler.java @@ -18,13 +18,14 @@ */ package org.apache.pinot.broker.requesthandler; +import com.google.common.collect.Maps; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; +import org.apache.commons.lang3.tuple.Pair; import org.apache.pinot.broker.broker.AccessControlFactory; import org.apache.pinot.broker.failuredetector.FailureDetector; import org.apache.pinot.broker.failuredetector.FailureDetectorFactory; @@ -36,14 +37,13 @@ import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.exception.QueryException; import org.apache.pinot.common.metrics.BrokerMeter; -import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.metrics.BrokerQueryPhase; import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.common.response.broker.BrokerResponseNative; import org.apache.pinot.common.response.broker.QueryProcessingException; -import org.apache.pinot.common.utils.HashUtil; import org.apache.pinot.core.query.reduce.BrokerReduceService; import org.apache.pinot.core.transport.AsyncQueryResponse; +import org.apache.pinot.core.transport.QueryResponse; import org.apache.pinot.core.transport.QueryRouter; import org.apache.pinot.core.transport.ServerInstance; import org.apache.pinot.core.transport.ServerResponse; @@ -62,7 +62,8 @@ * connection per server to route the queries. */ @ThreadSafe -public class SingleConnectionBrokerRequestHandler extends BaseBrokerRequestHandler implements FailureDetector.Listener { +public class SingleConnectionBrokerRequestHandler extends BaseSingleStageBrokerRequestHandler + implements FailureDetector.Listener { private static final Logger LOGGER = LoggerFactory.getLogger(SingleConnectionBrokerRequestHandler.class); private final BrokerReduceService _brokerReduceService; @@ -71,14 +72,12 @@ public class SingleConnectionBrokerRequestHandler extends BaseBrokerRequestHandl public SingleConnectionBrokerRequestHandler(PinotConfiguration config, String brokerId, BrokerRoutingManager routingManager, AccessControlFactory accessControlFactory, - QueryQuotaManager queryQuotaManager, TableCache tableCache, BrokerMetrics brokerMetrics, NettyConfig nettyConfig, - TlsConfig tlsConfig, ServerRoutingStatsManager serverRoutingStatsManager) { - super(config, brokerId, routingManager, accessControlFactory, queryQuotaManager, tableCache, brokerMetrics); - LOGGER.info("Using Netty BrokerRequestHandler."); - + QueryQuotaManager queryQuotaManager, TableCache tableCache, NettyConfig nettyConfig, TlsConfig tlsConfig, + ServerRoutingStatsManager serverRoutingStatsManager) { + super(config, brokerId, routingManager, accessControlFactory, queryQuotaManager, tableCache); _brokerReduceService = new BrokerReduceService(_config); - _queryRouter = new QueryRouter(_brokerId, brokerMetrics, nettyConfig, tlsConfig, serverRoutingStatsManager); - _failureDetector = FailureDetectorFactory.getFailureDetector(config, brokerMetrics); + _queryRouter = new QueryRouter(_brokerId, _brokerMetrics, nettyConfig, tlsConfig, serverRoutingStatsManager); + _failureDetector = FailureDetectorFactory.getFailureDetector(config, _brokerMetrics); } @Override @@ -88,7 +87,7 @@ public void start() { } @Override - public synchronized void shutDown() { + public void shutDown() { _failureDetector.stop(); _queryRouter.shutDown(); _brokerReduceService.shutDown(); @@ -97,9 +96,10 @@ public synchronized void shutDown() { @Override protected BrokerResponseNative processBrokerRequest(long requestId, BrokerRequest originalBrokerRequest, BrokerRequest serverBrokerRequest, @Nullable BrokerRequest offlineBrokerRequest, - @Nullable Map> offlineRoutingTable, @Nullable BrokerRequest realtimeBrokerRequest, - @Nullable Map> realtimeRoutingTable, long timeoutMs, ServerStats serverStats, - RequestContext requestContext) + @Nullable Map, List>> offlineRoutingTable, + @Nullable BrokerRequest realtimeBrokerRequest, + @Nullable Map, List>> realtimeRoutingTable, long timeoutMs, + ServerStats serverStats, RequestContext requestContext) throws Exception { assert offlineBrokerRequest != null || realtimeBrokerRequest != null; if (requestContext.isSampledRequest()) { @@ -113,6 +113,9 @@ protected BrokerResponseNative processBrokerRequest(long requestId, BrokerReques realtimeBrokerRequest, realtimeRoutingTable, timeoutMs); _failureDetector.notifyQuerySubmitted(asyncQueryResponse); Map finalResponses = asyncQueryResponse.getFinalResponses(); + if (asyncQueryResponse.getStatus() == QueryResponse.Status.TIMED_OUT) { + _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.BROKER_RESPONSES_WITH_TIMEOUTS, 1); + } _failureDetector.notifyQueryFinished(asyncQueryResponse); _brokerMetrics.addPhaseTiming(rawTableName, BrokerQueryPhase.SCATTER_GATHER, System.nanoTime() - scatterGatherStartTimeNs); @@ -121,7 +124,7 @@ protected BrokerResponseNative processBrokerRequest(long requestId, BrokerReques int numServersQueried = finalResponses.size(); long totalResponseSize = 0; - Map dataTableMap = new HashMap<>(HashUtil.getHashMapCapacity(numServersQueried)); + Map dataTableMap = Maps.newHashMapWithExpectedSize(numServersQueried); List serversNotResponded = new ArrayList<>(); for (Map.Entry entry : finalResponses.entrySet()) { ServerResponse serverResponse = entry.getValue(); @@ -136,26 +139,26 @@ protected BrokerResponseNative processBrokerRequest(long requestId, BrokerReques int numServersResponded = dataTableMap.size(); long reduceStartTimeNs = System.nanoTime(); - long reduceTimeOutMs = timeoutMs - TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - scatterGatherStartTimeNs); + long reduceTimeoutMs = timeoutMs - TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - scatterGatherStartTimeNs); BrokerResponseNative brokerResponse = _brokerReduceService.reduceOnDataTable(originalBrokerRequest, serverBrokerRequest, dataTableMap, - reduceTimeOutMs, _brokerMetrics); - final long reduceTimeNanos = System.nanoTime() - reduceStartTimeNs; - requestContext.setReduceTimeNanos(reduceTimeNanos); + reduceTimeoutMs, _brokerMetrics); + long reduceTimeNanos = System.nanoTime() - reduceStartTimeNs; _brokerMetrics.addPhaseTiming(rawTableName, BrokerQueryPhase.REDUCE, reduceTimeNanos); brokerResponse.setNumServersQueried(numServersQueried); brokerResponse.setNumServersResponded(numServersResponded); + brokerResponse.setBrokerReduceTimeMs(TimeUnit.NANOSECONDS.toMillis(reduceTimeNanos)); Exception brokerRequestSendException = asyncQueryResponse.getException(); if (brokerRequestSendException != null) { String errorMsg = QueryException.getTruncatedStackTrace(brokerRequestSendException); - brokerResponse.addToExceptions( + brokerResponse.addException( new QueryProcessingException(QueryException.BROKER_REQUEST_SEND_ERROR_CODE, errorMsg)); } int numServersNotResponded = serversNotResponded.size(); if (numServersNotResponded != 0) { - brokerResponse.addToExceptions(new QueryProcessingException(QueryException.SERVER_NOT_RESPONDING_ERROR_CODE, + brokerResponse.addException(new QueryProcessingException(QueryException.SERVER_NOT_RESPONDING_ERROR_CODE, String.format("%d servers %s not responded", numServersNotResponded, serversNotResponded))); _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.BROKER_RESPONSES_WITH_PARTIAL_SERVERS_RESPONDED, 1); } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/BrokerRoutingManager.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/BrokerRoutingManager.java index 883e9cfb0269..f2f65c91e800 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/BrokerRoutingManager.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/BrokerRoutingManager.java @@ -28,6 +28,7 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.Pair; import org.apache.helix.AccessOption; import org.apache.helix.BaseDataAccessor; import org.apache.helix.HelixConstants.ChangeType; @@ -43,6 +44,9 @@ import org.apache.pinot.broker.routing.adaptiveserverselector.AdaptiveServerSelectorFactory; import org.apache.pinot.broker.routing.instanceselector.InstanceSelector; import org.apache.pinot.broker.routing.instanceselector.InstanceSelectorFactory; +import org.apache.pinot.broker.routing.segmentmetadata.SegmentZkMetadataFetchListener; +import org.apache.pinot.broker.routing.segmentmetadata.SegmentZkMetadataFetcher; +import org.apache.pinot.broker.routing.segmentpartition.SegmentPartitionMetadataManager; import org.apache.pinot.broker.routing.segmentpreselector.SegmentPreSelector; import org.apache.pinot.broker.routing.segmentpreselector.SegmentPreSelectorFactory; import org.apache.pinot.broker.routing.segmentpruner.SegmentPruner; @@ -57,12 +61,16 @@ import org.apache.pinot.common.utils.HashUtil; import org.apache.pinot.core.routing.RoutingManager; import org.apache.pinot.core.routing.RoutingTable; +import org.apache.pinot.core.routing.TablePartitionInfo; import org.apache.pinot.core.routing.TimeBoundaryInfo; import org.apache.pinot.core.transport.ServerInstance; import org.apache.pinot.core.transport.server.routing.stats.ServerRoutingStatsManager; +import org.apache.pinot.spi.config.table.ColumnPartitionConfig; import org.apache.pinot.spi.config.table.QueryConfig; +import org.apache.pinot.spi.config.table.SegmentPartitionConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.CommonConstants.Helix; import org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel; import org.apache.pinot.spi.utils.InstanceTypeUtils; @@ -237,20 +245,27 @@ private void processInstanceConfigChange() { Set enabledServers = new HashSet<>(); List newEnabledServers = new ArrayList<>(); for (ZNRecord instanceConfigZNRecord : instanceConfigZNRecords) { + // Put instance initialization logics into try-catch block to prevent bad server configs affecting the entire + // cluster String instanceId = instanceConfigZNRecord.getId(); - if (isEnabledServer(instanceConfigZNRecord)) { - enabledServers.add(instanceId); - - // Always refresh the server instance with the latest instance config in case it changes - ServerInstance serverInstance = new ServerInstance(new InstanceConfig(instanceConfigZNRecord)); - if (_enabledServerInstanceMap.put(instanceId, serverInstance) == null) { - newEnabledServers.add(instanceId); - - // NOTE: Remove new enabled server from excluded servers because the server is likely being restarted - if (_excludedServers.remove(instanceId)) { - LOGGER.info("Got excluded server: {} re-enabled, including it into the routing", instanceId); + try { + if (isEnabledServer(instanceConfigZNRecord)) { + enabledServers.add(instanceId); + + // Always refresh the server instance with the latest instance config in case it changes + InstanceConfig instanceConfig = new InstanceConfig(instanceConfigZNRecord); + ServerInstance serverInstance = new ServerInstance(instanceConfig); + if (_enabledServerInstanceMap.put(instanceId, serverInstance) == null) { + newEnabledServers.add(instanceId); + + // NOTE: Remove new enabled server from excluded servers because the server is likely being restarted + if (_excludedServers.remove(instanceId)) { + LOGGER.info("Got excluded server: {} re-enabled, including it into the routing", instanceId); + } } } + } catch (Exception e) { + LOGGER.error("Caught exception while adding instance: {}, ignoring it", instanceId, e); } } List newDisabledServers = new ArrayList<>(); @@ -263,12 +278,10 @@ private void processInstanceConfigChange() { // Calculate the routable servers and the changed routable servers List changedServers = new ArrayList<>(newEnabledServers.size() + newDisabledServers.size()); if (_excludedServers.isEmpty()) { - _routableServers = enabledServers; changedServers.addAll(newEnabledServers); changedServers.addAll(newDisabledServers); } else { enabledServers.removeAll(_excludedServers); - _routableServers = enabledServers; // NOTE: All new enabled servers are routable changedServers.addAll(newEnabledServers); for (String newDisabledServer : newDisabledServers) { @@ -277,6 +290,7 @@ private void processInstanceConfigChange() { } } } + _routableServers = enabledServers; long calculateChangedServersEndTimeMs = System.currentTimeMillis(); // Early terminate if there is no changed servers @@ -430,15 +444,15 @@ public synchronized void buildRouting(String tableNameWithType) { Set preSelectedOnlineSegments = segmentPreSelector.preSelect(onlineSegments); SegmentSelector segmentSelector = SegmentSelectorFactory.getSegmentSelector(tableConfig); segmentSelector.init(idealState, externalView, preSelectedOnlineSegments); + + // Register segment pruners and initialize segment zk metadata fetcher. List segmentPruners = SegmentPrunerFactory.getSegmentPruners(tableConfig, _propertyStore); - for (SegmentPruner segmentPruner : segmentPruners) { - segmentPruner.init(idealState, externalView, preSelectedOnlineSegments); - } + AdaptiveServerSelector adaptiveServerSelector = AdaptiveServerSelectorFactory.getAdaptiveServerSelector(_serverRoutingStatsManager, _pinotConfig); InstanceSelector instanceSelector = InstanceSelectorFactory.getInstanceSelector(tableConfig, _propertyStore, _brokerMetrics, - adaptiveServerSelector); + adaptiveServerSelector, _pinotConfig); instanceSelector.init(_routableServers, idealState, externalView, preSelectedOnlineSegments); // Add time boundary manager if both offline and real-time part exist for a hybrid table @@ -485,13 +499,41 @@ public synchronized void buildRouting(String tableNameWithType) { } } + SegmentPartitionMetadataManager partitionMetadataManager = null; + // TODO: Support multiple partition columns + // TODO: Make partition pruner on top of the partition metadata manager to avoid keeping 2 copies of the metadata + if (_pinotConfig.getProperty(CommonConstants.Broker.CONFIG_OF_ENABLE_PARTITION_METADATA_MANAGER, + CommonConstants.Broker.DEFAULT_ENABLE_PARTITION_METADATA_MANAGER)) { + SegmentPartitionConfig segmentPartitionConfig = tableConfig.getIndexingConfig().getSegmentPartitionConfig(); + if (segmentPartitionConfig == null || segmentPartitionConfig.getColumnPartitionMap().size() != 1) { + LOGGER.warn("Cannot enable SegmentPartitionMetadataManager. " + + "Expecting SegmentPartitionConfig with exact 1 partition column"); + } else { + Map.Entry partitionConfig = + segmentPartitionConfig.getColumnPartitionMap().entrySet().iterator().next(); + LOGGER.info("Enabling SegmentPartitionMetadataManager for table: {} on partition column: {}", tableNameWithType, + partitionConfig.getKey()); + partitionMetadataManager = new SegmentPartitionMetadataManager(tableNameWithType, partitionConfig.getKey(), + partitionConfig.getValue().getFunctionName(), partitionConfig.getValue().getNumPartitions()); + } + } + QueryConfig queryConfig = tableConfig.getQueryConfig(); Long queryTimeoutMs = queryConfig != null ? queryConfig.getTimeoutMs() : null; + SegmentZkMetadataFetcher segmentZkMetadataFetcher = new SegmentZkMetadataFetcher(tableNameWithType, _propertyStore); + for (SegmentZkMetadataFetchListener listener : segmentPruners) { + segmentZkMetadataFetcher.register(listener); + } + if (partitionMetadataManager != null) { + segmentZkMetadataFetcher.register(partitionMetadataManager); + } + segmentZkMetadataFetcher.init(idealState, externalView, preSelectedOnlineSegments); + RoutingEntry routingEntry = new RoutingEntry(tableNameWithType, idealStatePath, externalViewPath, segmentPreSelector, segmentSelector, - segmentPruners, instanceSelector, idealStateVersion, externalViewVersion, timeBoundaryManager, - queryTimeoutMs); + segmentPruners, instanceSelector, idealStateVersion, externalViewVersion, segmentZkMetadataFetcher, + timeBoundaryManager, partitionMetadataManager, queryTimeoutMs); if (_routingEntryMap.put(tableNameWithType, routingEntry) == null) { LOGGER.info("Built routing for table: {}", tableNameWithType); } else { @@ -575,19 +617,38 @@ public RoutingTable getRoutingTable(BrokerRequest brokerRequest, long requestId) return null; } InstanceSelector.SelectionResult selectionResult = routingEntry.calculateRouting(brokerRequest, requestId); - Map segmentToInstanceMap = selectionResult.getSegmentToInstanceMap(); - Map> serverInstanceToSegmentsMap = new HashMap<>(); - for (Map.Entry entry : segmentToInstanceMap.entrySet()) { + return new RoutingTable(getServerInstanceToSegmentsMap(tableNameWithType, selectionResult), + selectionResult.getUnavailableSegments(), selectionResult.getNumPrunedSegments()); + } + + private Map, List>> getServerInstanceToSegmentsMap(String tableNameWithType, + InstanceSelector.SelectionResult selectionResult) { + Map, List>> merged = new HashMap<>(); + for (Map.Entry entry : selectionResult.getSegmentToInstanceMap().entrySet()) { ServerInstance serverInstance = _enabledServerInstanceMap.get(entry.getValue()); if (serverInstance != null) { - serverInstanceToSegmentsMap.computeIfAbsent(serverInstance, k -> new ArrayList<>()).add(entry.getKey()); + Pair, List> pair = + merged.computeIfAbsent(serverInstance, k -> Pair.of(new ArrayList<>(), new ArrayList<>())); + pair.getLeft().add(entry.getKey()); } else { // Should not happen in normal case unless encountered unexpected exception when updating routing entries _brokerMetrics.addMeteredTableValue(tableNameWithType, BrokerMeter.SERVER_MISSING_FOR_ROUTING, 1L); } } - return new RoutingTable(serverInstanceToSegmentsMap, selectionResult.getUnavailableSegments(), - selectionResult.getNumPrunedSegments()); + for (Map.Entry entry : selectionResult.getOptionalSegmentToInstanceMap().entrySet()) { + ServerInstance serverInstance = _enabledServerInstanceMap.get(entry.getValue()); + if (serverInstance != null) { + Pair, List> pair = merged.get(serverInstance); + // Skip servers that don't have non-optional segments, so that servers always get some non-optional segments + // to process, to be backward compatible. + // TODO: allow servers only with optional segments + if (pair != null) { + pair.getRight().add(entry.getKey()); + } + } + // TODO: Report missing server metrics when we allow servers only with optional segments. + } + return merged; } @Override @@ -619,6 +680,27 @@ public TimeBoundaryInfo getTimeBoundaryInfo(String offlineTableName) { return timeBoundaryManager != null ? timeBoundaryManager.getTimeBoundaryInfo() : null; } + @Nullable + @Override + public TablePartitionInfo getTablePartitionInfo(String tableNameWithType) { + RoutingEntry routingEntry = _routingEntryMap.get(tableNameWithType); + if (routingEntry == null) { + return null; + } + SegmentPartitionMetadataManager partitionMetadataManager = routingEntry.getPartitionMetadataManager(); + return partitionMetadataManager != null ? partitionMetadataManager.getTablePartitionInfo() : null; + } + + @Nullable + @Override + public Set getServingInstances(String tableNameWithType) { + RoutingEntry routingEntry = _routingEntryMap.get(tableNameWithType); + if (routingEntry == null) { + return null; + } + return routingEntry._instanceSelector.getServingInstances(); + } + /** * Returns the table-level query timeout in milliseconds for the given table, or {@code null} if the timeout is not * configured in the table config. @@ -636,8 +718,10 @@ private static class RoutingEntry { final SegmentPreSelector _segmentPreSelector; final SegmentSelector _segmentSelector; final List _segmentPruners; + final SegmentPartitionMetadataManager _partitionMetadataManager; final InstanceSelector _instanceSelector; final Long _queryTimeoutMs; + final SegmentZkMetadataFetcher _segmentZkMetadataFetcher; // Cache IdealState and ExternalView version for the last update transient int _lastUpdateIdealStateVersion; @@ -648,7 +732,8 @@ private static class RoutingEntry { RoutingEntry(String tableNameWithType, String idealStatePath, String externalViewPath, SegmentPreSelector segmentPreSelector, SegmentSelector segmentSelector, List segmentPruners, InstanceSelector instanceSelector, int lastUpdateIdealStateVersion, int lastUpdateExternalViewVersion, - @Nullable TimeBoundaryManager timeBoundaryManager, @Nullable Long queryTimeoutMs) { + SegmentZkMetadataFetcher segmentZkMetadataFetcher, @Nullable TimeBoundaryManager timeBoundaryManager, + @Nullable SegmentPartitionMetadataManager partitionMetadataManager, @Nullable Long queryTimeoutMs) { _tableNameWithType = tableNameWithType; _idealStatePath = idealStatePath; _externalViewPath = externalViewPath; @@ -659,7 +744,9 @@ private static class RoutingEntry { _lastUpdateIdealStateVersion = lastUpdateIdealStateVersion; _lastUpdateExternalViewVersion = lastUpdateExternalViewVersion; _timeBoundaryManager = timeBoundaryManager; + _partitionMetadataManager = partitionMetadataManager; _queryTimeoutMs = queryTimeoutMs; + _segmentZkMetadataFetcher = segmentZkMetadataFetcher; } String getTableNameWithType() { @@ -683,6 +770,11 @@ TimeBoundaryManager getTimeBoundaryManager() { return _timeBoundaryManager; } + @Nullable + SegmentPartitionMetadataManager getPartitionMetadataManager() { + return _partitionMetadataManager; + } + Long getQueryTimeoutMs() { return _queryTimeoutMs; } @@ -693,10 +785,8 @@ Long getQueryTimeoutMs() { void onAssignmentChange(IdealState idealState, ExternalView externalView) { Set onlineSegments = getOnlineSegments(idealState); Set preSelectedOnlineSegments = _segmentPreSelector.preSelect(onlineSegments); + _segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, preSelectedOnlineSegments); _segmentSelector.onAssignmentChange(idealState, externalView, preSelectedOnlineSegments); - for (SegmentPruner segmentPruner : _segmentPruners) { - segmentPruner.onAssignmentChange(idealState, externalView, preSelectedOnlineSegments); - } _instanceSelector.onAssignmentChange(idealState, externalView, preSelectedOnlineSegments); if (_timeBoundaryManager != null) { _timeBoundaryManager.onAssignmentChange(idealState, externalView, preSelectedOnlineSegments); @@ -710,9 +800,7 @@ void onInstancesChange(Set enabledInstances, List changedInstanc } void refreshSegment(String segment) { - for (SegmentPruner segmentPruner : _segmentPruners) { - segmentPruner.refreshSegment(segment); - } + _segmentZkMetadataFetcher.refreshSegment(segment); if (_timeBoundaryManager != null) { _timeBoundaryManager.refreshSegment(segment); } @@ -728,12 +816,13 @@ InstanceSelector.SelectionResult calculateRouting(BrokerRequest brokerRequest, l } int numPrunedSegments = numTotalSelectedSegments - selectedSegments.size(); if (!selectedSegments.isEmpty()) { - InstanceSelector.SelectionResult selectionResult = _instanceSelector.select(brokerRequest, - new ArrayList<>(selectedSegments), requestId); + InstanceSelector.SelectionResult selectionResult = + _instanceSelector.select(brokerRequest, new ArrayList<>(selectedSegments), requestId); selectionResult.setNumPrunedSegments(numPrunedSegments); return selectionResult; } else { - return new InstanceSelector.SelectionResult(Collections.emptyMap(), Collections.emptyList(), numPrunedSegments); + return new InstanceSelector.SelectionResult(Pair.of(Collections.emptyMap(), Collections.emptyMap()), + Collections.emptyList(), numPrunedSegments); } } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/adaptiveserverselector/AdaptiveServerSelectorFactory.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/adaptiveserverselector/AdaptiveServerSelectorFactory.java index a97c562abf3a..ae83cfca695a 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/adaptiveserverselector/AdaptiveServerSelectorFactory.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/adaptiveserverselector/AdaptiveServerSelectorFactory.java @@ -19,6 +19,7 @@ package org.apache.pinot.broker.routing.adaptiveserverselector; import com.google.common.base.Preconditions; +import javax.annotation.Nullable; import org.apache.pinot.core.transport.server.routing.stats.ServerRoutingStatsManager; import org.apache.pinot.spi.env.PinotConfiguration; import org.apache.pinot.spi.utils.CommonConstants.Broker; @@ -35,6 +36,7 @@ public class AdaptiveServerSelectorFactory { private AdaptiveServerSelectorFactory() { } + @Nullable public static AdaptiveServerSelector getAdaptiveServerSelector(ServerRoutingStatsManager serverRoutingStatsManager, PinotConfiguration pinotConfig) { boolean enableStatsCollection = diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/BalancedInstanceSelector.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/BalancedInstanceSelector.java index de5eb9f53004..c827369907c3 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/BalancedInstanceSelector.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/BalancedInstanceSelector.java @@ -18,15 +18,19 @@ */ package org.apache.pinot.broker.routing.instanceselector; +import java.time.Clock; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.helix.store.zk.ZkHelixPropertyStore; +import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.broker.routing.adaptiveserverselector.AdaptiveServerSelector; import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.utils.HashUtil; - /** * Instance selector to balance the number of segments served by each selected server instance. *

If AdaptiveServerSelection is enabled, the request is routed to the best available server for a segment @@ -37,40 +41,71 @@ * Step1: Process seg1. Fetch server rankings. Pick the best server. * Step2: Process seg2. Fetch server rankings (could have changed or not since Step 1). Pick the best server. * Step3: Process seg3. Fetch server rankings (could have changed or not since Step 2). Pick the best server. - + * *

If AdaptiveServerSelection is disabled, the selection algorithm will always evenly distribute the traffic to all * replicas of each segment, and will try to select different replica id for each segment. The algorithm is very * light-weight and will do best effort to balance the number of segments served by each selected server instance. */ public class BalancedInstanceSelector extends BaseInstanceSelector { - public BalancedInstanceSelector(String tableNameWithType, BrokerMetrics brokerMetrics, - @Nullable AdaptiveServerSelector adaptiveServerSelector) { - super(tableNameWithType, brokerMetrics, adaptiveServerSelector); + public BalancedInstanceSelector(String tableNameWithType, ZkHelixPropertyStore propertyStore, + BrokerMetrics brokerMetrics, @Nullable AdaptiveServerSelector adaptiveServerSelector, Clock clock, + boolean useFixedReplica) { + super(tableNameWithType, propertyStore, brokerMetrics, adaptiveServerSelector, clock, useFixedReplica); } @Override - Map select(List segments, int requestId, - Map> segmentToEnabledInstancesMap, Map queryOptions) { + Pair, Map> select(List segments, int requestId, + SegmentStates segmentStates, Map queryOptions) { Map segmentToSelectedInstanceMap = new HashMap<>(HashUtil.getHashMapCapacity(segments.size())); - for (String segment : segments) { - List enabledInstances = segmentToEnabledInstancesMap.get(segment); - // NOTE: enabledInstances can be null when there is no enabled instances for the segment, or the instance selector - // has not been updated (we update all components for routing in sequence) - if (enabledInstances == null) { - continue; + // No need to adjust this map per total segment numbers, as optional segments should be empty most of the time. + Map optionalSegmentToInstanceMap = new HashMap<>(); + if (_adaptiveServerSelector != null) { + for (String segment : segments) { + List candidates = segmentStates.getCandidates(segment); + // NOTE: candidates can be null when there is no enabled instances for the segment, or the instance selector has + // not been updated (we update all components for routing in sequence) + if (candidates == null) { + continue; + } + List candidateInstances = new ArrayList<>(candidates.size()); + for (SegmentInstanceCandidate candidate : candidates) { + candidateInstances.add(candidate.getInstance()); + } + String selectedInstance = _adaptiveServerSelector.select(candidateInstances); + // This can only be offline when it is a new segment. And such segment is marked as optional segment so that + // broker or server can skip it upon any issue to process it. + if (candidates.get(candidateInstances.indexOf(selectedInstance)).isOnline()) { + segmentToSelectedInstanceMap.put(segment, selectedInstance); + } else { + optionalSegmentToInstanceMap.put(segment, selectedInstance); + } } - - String selectedServer; - if (_adaptiveServerSelector != null) { - selectedServer = _adaptiveServerSelector.select(enabledInstances); - } else { - selectedServer = enabledInstances.get(requestId++ % enabledInstances.size()); + } else { + for (String segment : segments) { + List candidates = segmentStates.getCandidates(segment); + // NOTE: candidates can be null when there is no enabled instances for the segment, or the instance selector has + // not been updated (we update all components for routing in sequence) + if (candidates == null) { + continue; + } + int selectedIdx; + if (isUseFixedReplica(queryOptions)) { + // candidates array is always sorted + selectedIdx = _tableNameHashForFixedReplicaRouting % candidates.size(); + } else { + selectedIdx = requestId++ % candidates.size(); + } + SegmentInstanceCandidate selectedCandidate = candidates.get(selectedIdx); + // This can only be offline when it is a new segment. And such segment is marked as optional segment so that + // broker or server can skip it upon any issue to process it. + if (selectedCandidate.isOnline()) { + segmentToSelectedInstanceMap.put(segment, selectedCandidate.getInstance()); + } else { + optionalSegmentToInstanceMap.put(segment, selectedCandidate.getInstance()); + } } - - segmentToSelectedInstanceMap.put(segment, selectedServer); } - - return segmentToSelectedInstanceMap; + return Pair.of(segmentToSelectedInstanceMap, optionalSegmentToInstanceMap); } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/BaseInstanceSelector.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/BaseInstanceSelector.java index 9325035aca15..3cef77fac425 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/BaseInstanceSelector.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/BaseInstanceSelector.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.broker.routing.instanceselector; +import java.time.Clock; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -26,256 +27,413 @@ import java.util.Map; import java.util.Set; import java.util.SortedMap; +import java.util.TreeMap; +import java.util.TreeSet; import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.helix.AccessOption; import org.apache.helix.model.ExternalView; import org.apache.helix.model.IdealState; +import org.apache.helix.store.zk.ZkHelixPropertyStore; +import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.broker.routing.adaptiveserverselector.AdaptiveServerSelector; import org.apache.pinot.broker.routing.segmentpreselector.SegmentPreSelector; +import org.apache.pinot.common.metadata.ZKMetadataProvider; +import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; import org.apache.pinot.common.metrics.BrokerMeter; import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.common.utils.HashUtil; +import org.apache.pinot.common.utils.SegmentUtils; +import org.apache.pinot.common.utils.config.QueryOptionsUtils; import org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Base implementation of instance selector which maintains a map from segment to enabled ONLINE/CONSUMING server + * Base implementation of instance selector. Selector maintains a map from segment to enabled ONLINE/CONSUMING server * instances that serves the segment and a set of unavailable segments (no enabled instance or all enabled instances are - * in ERROR state). + * in OFFLINE/ERROR state). + *

+ * Special handling of new segment: It is common for new segment to be partially available or not available at all in + * all instances. + * 1) We don't report new segment as unavailable segments. + * 2) To avoid creating hotspot instances, unavailable instances for new segment won't be excluded for instance + * selection. When it is selected, we don't serve the new segment. + *

+ * Definition of new segment: + * 1) Segment created more than 5 minutes ago. + * - If we first see a segment via initialization, we look up segment creation time from zookeeper. + * - If we first see a segment via onAssignmentChange initialization, we use the calling time of onAssignmentChange + * as approximation. + * 2) We retire new segment as old when: + * - The creation time is more than 5 minutes ago + * - Any instance for new segment is in ERROR state + * - External view for segment converges with ideal state + * + * Note that this implementation means: + * 1) Inconsistent selection of new segments across queries (some queries will serve new segments and others won't). + * 2) When there is no state update from helix, new segments won't be retired because of the time passing (those with + * creation time more than 5 minutes ago). + * TODO: refresh new/old segment state where there is no update from helix for long time. */ abstract class BaseInstanceSelector implements InstanceSelector { private static final Logger LOGGER = LoggerFactory.getLogger(BaseInstanceSelector.class); - // To prevent int overflow, reset the request id once it reaches this value private static final long MAX_REQUEST_ID = 1_000_000_000; - private final String _tableNameWithType; - private final BrokerMetrics _brokerMetrics; - protected final AdaptiveServerSelector _adaptiveServerSelector; + final String _tableNameWithType; + final ZkHelixPropertyStore _propertyStore; + final BrokerMetrics _brokerMetrics; + final AdaptiveServerSelector _adaptiveServerSelector; + final Clock _clock; + final boolean _useFixedReplica; + final int _tableNameHashForFixedReplicaRouting; - // These 4 variables are the cached states to help accelerate the change processing - private Set _enabledInstances; - private Map> _segmentToOnlineInstancesMap; - private Map> _segmentToOfflineInstancesMap; - private Map> _instanceToSegmentsMap; + // These 3 variables are the cached states to help accelerate the change processing + Set _enabledInstances; + // For old segments, all candidates are online + // Reduce this map to reduce garbage + final Map> _oldSegmentCandidatesMap = new HashMap<>(); + Map _newSegmentStateMap; - // These 2 variables are needed for instance selection (multi-threaded), so make them volatile - private volatile Map> _segmentToEnabledInstancesMap; - private volatile Set _unavailableSegments; + // _segmentStates is needed for instance selection (multi-threaded), so it is made volatile. + private volatile SegmentStates _segmentStates; - BaseInstanceSelector(String tableNameWithType, BrokerMetrics brokerMetrics, - @Nullable AdaptiveServerSelector adaptiveServerSelector) { + BaseInstanceSelector(String tableNameWithType, ZkHelixPropertyStore propertyStore, + BrokerMetrics brokerMetrics, @Nullable AdaptiveServerSelector adaptiveServerSelector, Clock clock, + boolean useFixedReplica) { _tableNameWithType = tableNameWithType; + _propertyStore = propertyStore; _brokerMetrics = brokerMetrics; _adaptiveServerSelector = adaptiveServerSelector; + _clock = clock; + _useFixedReplica = useFixedReplica; + // Using raw table name to ensure queries spanning across REALTIME and OFFLINE tables are routed to the same + // instance + // Math.abs(Integer.MIN_VALUE) = Integer.MIN_VALUE, so we use & 0x7FFFFFFF to get a positive value + _tableNameHashForFixedReplicaRouting = + TableNameBuilder.extractRawTableName(tableNameWithType).hashCode() & 0x7FFFFFFF; + + if (_adaptiveServerSelector != null && _useFixedReplica) { + throw new IllegalArgumentException( + "AdaptiveServerSelector and consistent routing cannot be enabled at the same time"); + } } @Override public void init(Set enabledInstances, IdealState idealState, ExternalView externalView, Set onlineSegments) { _enabledInstances = enabledInstances; - int segmentMapCapacity = HashUtil.getHashMapCapacity(onlineSegments.size()); - _segmentToOnlineInstancesMap = new HashMap<>(segmentMapCapacity); - _segmentToOfflineInstancesMap = new HashMap<>(segmentMapCapacity); - _instanceToSegmentsMap = new HashMap<>(); - onAssignmentChange(idealState, externalView, onlineSegments); + Map newSegmentCreationTimeMap = + getNewSegmentCreationTimeMapFromZK(idealState, externalView, onlineSegments); + updateSegmentMaps(idealState, externalView, onlineSegments, newSegmentCreationTimeMap); + refreshSegmentStates(); } /** - * {@inheritDoc} - * - *

Updates the cached enabled instances and re-calculates {@code segmentToEnabledInstancesMap} and - * {@code unavailableSegments} based on the cached states. + * Returns whether the instance state is online for routing purpose (ONLINE/CONSUMING). */ - @Override - public void onInstancesChange(Set enabledInstances, List changedInstances) { - _enabledInstances = enabledInstances; + static boolean isOnlineForRouting(@Nullable String state) { + return SegmentStateModel.ONLINE.equals(state) || SegmentStateModel.CONSUMING.equals(state); + } - // Update all segments served by the changed instances - Set segmentsToUpdate = new HashSet<>(); - for (String instance : changedInstances) { - List segments = _instanceToSegmentsMap.get(instance); - if (segments != null) { - segmentsToUpdate.addAll(segments); + /** + * Returns a map from new segment to their creation time based on the ZK metadata. + */ + Map getNewSegmentCreationTimeMapFromZK(IdealState idealState, ExternalView externalView, + Set onlineSegments) { + List potentialNewSegments = new ArrayList<>(); + Map> idealStateAssignment = idealState.getRecord().getMapFields(); + Map> externalViewAssignment = externalView.getRecord().getMapFields(); + for (String segment : onlineSegments) { + assert idealStateAssignment.containsKey(segment); + if (isPotentialNewSegment(idealStateAssignment.get(segment), externalViewAssignment.get(segment))) { + potentialNewSegments.add(segment); } } - // Directly return if no segment needs to be updated - if (segmentsToUpdate.isEmpty()) { - return; + Map newSegmentCreationTimeMap = new HashMap<>(); + long currentTimeMs = _clock.millis(); + String segmentZKMetadataPathPrefix = + ZKMetadataProvider.constructPropertyStorePathForResource(_tableNameWithType) + "/"; + List segmentZKMetadataPaths = new ArrayList<>(potentialNewSegments.size()); + for (String segment : potentialNewSegments) { + segmentZKMetadataPaths.add(segmentZKMetadataPathPrefix + segment); + } + List znRecords = _propertyStore.get(segmentZKMetadataPaths, null, AccessOption.PERSISTENT, false); + for (ZNRecord record : znRecords) { + if (record == null) { + continue; + } + SegmentZKMetadata segmentZKMetadata = new SegmentZKMetadata(record); + long creationTimeMs = SegmentUtils.getSegmentCreationTimeMs(segmentZKMetadata); + if (InstanceSelector.isNewSegment(creationTimeMs, currentTimeMs)) { + newSegmentCreationTimeMap.put(segmentZKMetadata.getSegmentName(), creationTimeMs); + } } + LOGGER.info("Got {} new segments: {} for table: {} by reading ZK metadata, current time: {}", + newSegmentCreationTimeMap.size(), newSegmentCreationTimeMap, _tableNameWithType, currentTimeMs); + return newSegmentCreationTimeMap; + } - // Update the map from segment to enabled ONLINE/CONSUMING instances and set of unavailable segments (no enabled - // instance or all enabled instances are in ERROR state) - // NOTE: We can directly modify the map because we will only update the values without changing the map entries. - // Because the map is marked as volatile, the running queries (already accessed the map) might use the enabled - // instances either before or after the change, which is okay; the following queries (not yet accessed the map) will - // get the updated value. - Map> segmentToEnabledInstancesMap = _segmentToEnabledInstancesMap; - Set currentUnavailableSegments = _unavailableSegments; - Set newUnavailableSegments = new HashSet<>(); - for (Map.Entry> entry : segmentToEnabledInstancesMap.entrySet()) { - String segment = entry.getKey(); - if (segmentsToUpdate.contains(segment)) { - List enabledInstancesForSegment = - calculateEnabledInstancesForSegment(segment, _segmentToOnlineInstancesMap.get(segment), - newUnavailableSegments); - entry.setValue(enabledInstancesForSegment); - } else { - if (currentUnavailableSegments.contains(segment)) { - newUnavailableSegments.add(segment); + /** + * Returns whether a segment is qualified as a new segment. + * A segment is count as old when: + * - Any instance for the segment is in ERROR state + * - External view for the segment converges with ideal state + */ + static boolean isPotentialNewSegment(Map idealStateInstanceStateMap, + @Nullable Map externalViewInstanceStateMap) { + if (externalViewInstanceStateMap == null) { + return true; + } + boolean hasConverged = true; + // Only track ONLINE/CONSUMING instances within the ideal state + for (Map.Entry entry : idealStateInstanceStateMap.entrySet()) { + if (isOnlineForRouting(entry.getValue())) { + String externalViewState = externalViewInstanceStateMap.get(entry.getKey()); + if (externalViewState == null || externalViewState.equals(SegmentStateModel.OFFLINE)) { + hasConverged = false; + } else if (externalViewState.equals(SegmentStateModel.ERROR)) { + return false; } } } - _unavailableSegments = newUnavailableSegments; + return !hasConverged; } /** - * {@inheritDoc} - * - *

Updates the cached maps ({@code segmentToOnlineInstancesMap}, {@code segmentToOfflineInstancesMap} and - * {@code instanceToSegmentsMap}) and re-calculates {@code segmentToEnabledInstancesMap} and - * {@code unavailableSegments} based on the cached states. + * Returns the online instances for routing purpose. */ - @Override - public void onAssignmentChange(IdealState idealState, ExternalView externalView, Set onlineSegments) { - _segmentToOnlineInstancesMap.clear(); - _segmentToOfflineInstancesMap.clear(); - _instanceToSegmentsMap.clear(); - - // Update the cached maps - updateSegmentMaps(idealState, externalView, onlineSegments, _segmentToOnlineInstancesMap, - _segmentToOfflineInstancesMap, _instanceToSegmentsMap); - - // Generate a new map from segment to enabled ONLINE/CONSUMING instances and a new set of unavailable segments (no - // enabled instance or all enabled instances are in ERROR state) - Map> segmentToEnabledInstancesMap = - new HashMap<>(HashUtil.getHashMapCapacity(_segmentToOnlineInstancesMap.size())); - Set unavailableSegments = new HashSet<>(); - // NOTE: Put null as the value when there is no enabled instances for a segment so that segmentToEnabledInstancesMap - // always contains all segments. With this, in onInstancesChange() we can directly iterate over - // segmentToEnabledInstancesMap.entrySet() and modify the value without changing the map entries. - for (Map.Entry> entry : _segmentToOnlineInstancesMap.entrySet()) { - String segment = entry.getKey(); - List enabledInstancesForSegment = - calculateEnabledInstancesForSegment(segment, entry.getValue(), unavailableSegments); - segmentToEnabledInstancesMap.put(segment, enabledInstancesForSegment); + static TreeSet getOnlineInstances(Map idealStateInstanceStateMap, + Map externalViewInstanceStateMap) { + TreeSet onlineInstances = new TreeSet<>(); + // Only track ONLINE/CONSUMING instances within the ideal state + for (Map.Entry entry : idealStateInstanceStateMap.entrySet()) { + String instance = entry.getKey(); + // NOTE: DO NOT check if EV matches IS because it is a valid state when EV is CONSUMING while IS is ONLINE + if (isOnlineForRouting(entry.getValue()) && isOnlineForRouting(externalViewInstanceStateMap.get(instance))) { + onlineInstances.add(instance); + } } + return onlineInstances; + } - _segmentToEnabledInstancesMap = segmentToEnabledInstancesMap; - _unavailableSegments = unavailableSegments; + /** + * Converts the given map into a sorted map if needed. + */ + static SortedMap convertToSortedMap(Map map) { + if (map instanceof SortedMap) { + return (SortedMap) map; + } else { + return new TreeMap<>(map); + } } /** - * Updates the segment maps based on the given ideal state, external view and online segments (segments with - * ONLINE/CONSUMING instances in the ideal state and pre-selected by the {@link SegmentPreSelector}). + * Updates the segment maps based on the given ideal state, external view, online segments (segments with + * ONLINE/CONSUMING instances in the ideal state and pre-selected by the {@link SegmentPreSelector}) and new segments. + * After this update: + * - Old segments' online instances should be tracked in _oldSegmentCandidatesMap + * - New segments' state (creation time and candidate instances) should be tracked in _newSegmentStateMap */ void updateSegmentMaps(IdealState idealState, ExternalView externalView, Set onlineSegments, - Map> segmentToOnlineInstancesMap, Map> segmentToOfflineInstancesMap, - Map> instanceToSegmentsMap) { - // Iterate over the external view instead of the online segments so that the map lookups are performed on the - // HashSet instead of the TreeSet for performance - // NOTE: Do not track segments not in the external view because it is a valid state when the segment is new added - Map> idealStateAssignment = idealState.getRecord().getMapFields(); - for (Map.Entry> entry : externalView.getRecord().getMapFields().entrySet()) { - String segment = entry.getKey(); + Map newSegmentCreationTimeMap) { + _oldSegmentCandidatesMap.clear(); + _newSegmentStateMap = new HashMap<>(HashUtil.getHashMapCapacity(newSegmentCreationTimeMap.size())); - // Only track online segments - if (!onlineSegments.contains(segment)) { - continue; + Map> idealStateAssignment = idealState.getRecord().getMapFields(); + Map> externalViewAssignment = externalView.getRecord().getMapFields(); + for (String segment : onlineSegments) { + Map idealStateInstanceStateMap = idealStateAssignment.get(segment); + Long newSegmentCreationTimeMs = newSegmentCreationTimeMap.get(segment); + Map externalViewInstanceStateMap = externalViewAssignment.get(segment); + if (externalViewInstanceStateMap == null) { + if (newSegmentCreationTimeMs != null) { + // New segment + List candidates = new ArrayList<>(idealStateInstanceStateMap.size()); + for (Map.Entry entry : convertToSortedMap(idealStateInstanceStateMap).entrySet()) { + if (isOnlineForRouting(entry.getValue())) { + candidates.add(new SegmentInstanceCandidate(entry.getKey(), false)); + } + } + _newSegmentStateMap.put(segment, new NewSegmentState(newSegmentCreationTimeMs, candidates)); + } else { + // Old segment + _oldSegmentCandidatesMap.put(segment, Collections.emptyList()); + } + } else { + TreeSet onlineInstances = getOnlineInstances(idealStateInstanceStateMap, externalViewInstanceStateMap); + if (newSegmentCreationTimeMs != null) { + // New segment + List candidates = new ArrayList<>(idealStateInstanceStateMap.size()); + for (Map.Entry entry : convertToSortedMap(idealStateInstanceStateMap).entrySet()) { + if (isOnlineForRouting(entry.getValue())) { + String instance = entry.getKey(); + candidates.add(new SegmentInstanceCandidate(instance, onlineInstances.contains(instance))); + } + } + _newSegmentStateMap.put(segment, new NewSegmentState(newSegmentCreationTimeMs, candidates)); + } else { + // Old segment + List candidates = new ArrayList<>(onlineInstances.size()); + for (String instance : onlineInstances) { + candidates.add(new SegmentInstanceCandidate(instance, true)); + } + _oldSegmentCandidatesMap.put(segment, candidates); + } } + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Got _newSegmentStateMap: {}, _oldSegmentCandidatesMap: {}", _newSegmentStateMap.keySet(), + _oldSegmentCandidatesMap.keySet()); + } + } - Map externalViewInstanceStateMap = entry.getValue(); - Map idealStateInstanceStateMap = idealStateAssignment.get(segment); - List onlineInstances = new ArrayList<>(externalViewInstanceStateMap.size()); - List offlineInstances = new ArrayList<>(); - segmentToOnlineInstancesMap.put(segment, onlineInstances); - segmentToOfflineInstancesMap.put(segment, offlineInstances); - for (Map.Entry instanceStateEntry : externalViewInstanceStateMap.entrySet()) { - String instance = instanceStateEntry.getKey(); + /** + * Refreshes the _segmentStates based on the in-memory states. + * Note that the whole _segmentStates has to be updated together to avoid partial state update. + **/ + void refreshSegmentStates() { + Map> instanceCandidatesMap = + new HashMap<>(HashUtil.getHashMapCapacity(_oldSegmentCandidatesMap.size() + _newSegmentStateMap.size())); + Set servingInstances = new HashSet<>(); + Set unavailableSegments = new HashSet<>(); - // Only track instances within the ideal state - // NOTE: When an instance is not in the ideal state, the instance will drop the segment soon, and it is not safe - // to query this instance for the segment. This could happen when a segment is moved from one instance to - // another instance. - if (!idealStateInstanceStateMap.containsKey(instance)) { - continue; + for (Map.Entry> entry : _oldSegmentCandidatesMap.entrySet()) { + String segment = entry.getKey(); + List candidates = entry.getValue(); + List enabledCandidates = + getEnabledCandidatesAndAddToServingInstances(candidates, servingInstances); + if (!enabledCandidates.isEmpty()) { + instanceCandidatesMap.put(segment, enabledCandidates); + } else { + List candidateInstances = new ArrayList<>(candidates.size()); + for (SegmentInstanceCandidate candidate : candidates) { + candidateInstances.add(candidate.getInstance()); } + LOGGER.warn("Failed to find servers hosting old segment: {} for table: {} " + + "(all candidate instances: {} are disabled, counting segment as unavailable)", segment, + _tableNameWithType, candidateInstances); + unavailableSegments.add(segment); + _brokerMetrics.addMeteredTableValue(_tableNameWithType, BrokerMeter.NO_SERVING_HOST_FOR_SEGMENT, 1); + } + } - String externalViewState = instanceStateEntry.getValue(); - // Do not track instances in ERROR state - if (!externalViewState.equals(SegmentStateModel.ERROR)) { - instanceToSegmentsMap.computeIfAbsent(instance, k -> new ArrayList<>()).add(segment); - if (externalViewState.equals(SegmentStateModel.OFFLINE)) { - offlineInstances.add(instance); - } else { - onlineInstances.add(instance); - } + for (Map.Entry entry : _newSegmentStateMap.entrySet()) { + String segment = entry.getKey(); + NewSegmentState newSegmentState = entry.getValue(); + List candidates = newSegmentState.getCandidates(); + List enabledCandidates = + getEnabledCandidatesAndAddToServingInstances(candidates, servingInstances); + if (!enabledCandidates.isEmpty()) { + instanceCandidatesMap.put(segment, enabledCandidates); + } else { + // Do not count new segment as unavailable + List candidateInstances = new ArrayList<>(candidates.size()); + for (SegmentInstanceCandidate candidate : candidates) { + candidateInstances.add(candidate.getInstance()); } + LOGGER.info("Failed to find servers hosting new segment: {} for table: {} " + + "(all candidate instances: {} are disabled, but not counting new segment as unavailable)", segment, + _tableNameWithType, candidateInstances); } + } - // Sort the online instances for replica-group routing to work. For multiple segments with the same online - // instances, if the list is sorted, the same index in the list will always point to the same instance. - if (!(externalViewInstanceStateMap instanceof SortedMap)) { - onlineInstances.sort(null); - offlineInstances.sort(null); + _segmentStates = new SegmentStates(instanceCandidatesMap, servingInstances, unavailableSegments); + } + + private List getEnabledCandidatesAndAddToServingInstances( + List candidates, Set servingInstances) { + List enabledCandidates = new ArrayList<>(candidates.size()); + for (SegmentInstanceCandidate candidate : candidates) { + String instance = candidate.getInstance(); + if (_enabledInstances.contains(instance)) { + enabledCandidates.add(candidate); + servingInstances.add(instance); } } + return enabledCandidates; } /** - * Calculates the enabled ONLINE/CONSUMING instances for the given segment, and updates the unavailable segments (no - * enabled instance or all enabled instances are in ERROR state). + * {@inheritDoc} + * + *

Updates the cached enabled instances and re-calculates {@code segmentToEnabledInstancesMap} and + * {@code unavailableSegments} based on the cached states. */ - @Nullable - private List calculateEnabledInstancesForSegment(String segment, List onlineInstancesForSegment, - Set unavailableSegments) { - List enabledInstancesForSegment = new ArrayList<>(onlineInstancesForSegment.size()); - for (String onlineInstance : onlineInstancesForSegment) { - if (_enabledInstances.contains(onlineInstance)) { - enabledInstancesForSegment.add(onlineInstance); + @Override + public void onInstancesChange(Set enabledInstances, List changedInstances) { + _enabledInstances = enabledInstances; + refreshSegmentStates(); + } + + /** + * {@inheritDoc} + * + *

Updates the cached maps ({@code segmentToOnlineInstancesMap}, {@code segmentToOfflineInstancesMap} and + * {@code instanceToSegmentsMap}) and re-calculates {@code segmentToEnabledInstancesMap} and + * {@code unavailableSegments} based on the cached states. + */ + @Override + public void onAssignmentChange(IdealState idealState, ExternalView externalView, Set onlineSegments) { + Map newSegmentCreationTimeMap = + getNewSegmentCreationTimeMapFromExistingStates(idealState, externalView, onlineSegments); + updateSegmentMaps(idealState, externalView, onlineSegments, newSegmentCreationTimeMap); + refreshSegmentStates(); + } + + /** + * Returns a map from new segment to their creation time based on the existing in-memory states. + */ + Map getNewSegmentCreationTimeMapFromExistingStates(IdealState idealState, ExternalView externalView, + Set onlineSegments) { + Map newSegmentCreationTimeMap = new HashMap<>(); + long currentTimeMs = _clock.millis(); + Map> idealStateAssignment = idealState.getRecord().getMapFields(); + Map> externalViewAssignment = externalView.getRecord().getMapFields(); + for (String segment : onlineSegments) { + NewSegmentState newSegmentState = _newSegmentStateMap.get(segment); + long creationTimeMs = 0; + if (newSegmentState != null) { + // It was a new segment before, check the creation time and segment state to see if it is still a new segment + if (InstanceSelector.isNewSegment(newSegmentState.getCreationTimeMs(), currentTimeMs)) { + creationTimeMs = newSegmentState.getCreationTimeMs(); + } + } else if (!_oldSegmentCandidatesMap.containsKey(segment)) { + // This is the first time we see this segment, use the current time as the creation time + creationTimeMs = currentTimeMs; } - } - if (!enabledInstancesForSegment.isEmpty()) { - return enabledInstancesForSegment; - } else { - // NOTE: When there are enabled instances in OFFLINE state, we don't count the segment as unavailable because it - // is a valid state when the segment is new added. - List offlineInstancesForSegment = _segmentToOfflineInstancesMap.get(segment); - for (String offlineInstance : offlineInstancesForSegment) { - if (_enabledInstances.contains(offlineInstance)) { - LOGGER.info( - "Failed to find servers hosting segment: {} for table: {} (all ONLINE/CONSUMING instances: {} are " - + "disabled, but find enabled OFFLINE instance: {} from OFFLINE instances: {}, not counting the " - + "segment as unavailable)", segment, _tableNameWithType, onlineInstancesForSegment, offlineInstance, - offlineInstancesForSegment); - return null; + // For recently created segment, check if it is qualified as new segment + if (creationTimeMs > 0) { + assert idealStateAssignment.containsKey(segment); + if (isPotentialNewSegment(idealStateAssignment.get(segment), externalViewAssignment.get(segment))) { + newSegmentCreationTimeMap.put(segment, creationTimeMs); } } - LOGGER.warn( - "Failed to find servers hosting segment: {} for table: {} (all ONLINE/CONSUMING instances: {} and OFFLINE " - + "instances: {} are disabled, counting segment as unavailable)", segment, _tableNameWithType, - onlineInstancesForSegment, offlineInstancesForSegment); - unavailableSegments.add(segment); - _brokerMetrics.addMeteredTableValue(_tableNameWithType, BrokerMeter.NO_SERVING_HOST_FOR_SEGMENT, 1); - return null; } + LOGGER.info("Got {} new segments: {} for table: {} by processing existing states, current time: {}", + newSegmentCreationTimeMap.size(), newSegmentCreationTimeMap, _tableNameWithType, currentTimeMs); + return newSegmentCreationTimeMap; } @Override public SelectionResult select(BrokerRequest brokerRequest, List segments, long requestId) { - Map queryOptions = (brokerRequest.getPinotQuery() != null - && brokerRequest.getPinotQuery().getQueryOptions() != null) - ? brokerRequest.getPinotQuery().getQueryOptions() - : Collections.emptyMap(); + Map queryOptions = + (brokerRequest.getPinotQuery() != null && brokerRequest.getPinotQuery().getQueryOptions() != null) + ? brokerRequest.getPinotQuery().getQueryOptions() : Collections.emptyMap(); int requestIdInt = (int) (requestId % MAX_REQUEST_ID); - Map segmentToInstanceMap = select(segments, requestIdInt, _segmentToEnabledInstancesMap, - queryOptions); - Set unavailableSegments = _unavailableSegments; + // Copy the volatile reference so that segmentToInstanceMap and unavailableSegments can have a consistent view of + // the state. + SegmentStates segmentStates = _segmentStates; + Pair, Map> segmentToInstanceMap = + select(segments, requestIdInt, segmentStates, queryOptions); + Set unavailableSegments = segmentStates.getUnavailableSegments(); if (unavailableSegments.isEmpty()) { - return new SelectionResult(segmentToInstanceMap, Collections.emptyList()); + return new SelectionResult(segmentToInstanceMap, Collections.emptyList(), 0); } else { List unavailableSegmentsForRequest = new ArrayList<>(); for (String segment : segments) { @@ -283,16 +441,26 @@ public SelectionResult select(BrokerRequest brokerRequest, List segments unavailableSegmentsForRequest.add(segment); } } - return new SelectionResult(segmentToInstanceMap, unavailableSegmentsForRequest); + return new SelectionResult(segmentToInstanceMap, unavailableSegmentsForRequest, 0); } } + protected boolean isUseFixedReplica(Map queryOptions) { + Boolean queryOption = QueryOptionsUtils.isUseFixedReplica(queryOptions); + return queryOption != null ? queryOption : _useFixedReplica; + } + + @Override + public Set getServingInstances() { + return _segmentStates.getServingInstances(); + } + /** - * Selects the server instances for the given segments based on the request id and segment to enabled ONLINE/CONSUMING - * instances map, returns a map from segment to selected server instance hosting the segment. - *

NOTE: {@code segmentToEnabledInstancesMap} might contain {@code null} values (segment with no enabled - * ONLINE/CONSUMING instances). If enabled instances are not {@code null}, they are sorted in alphabetical order. + * Selects the server instances for the given segments based on the request id and segment states. Returns two maps + * from segment to selected server instance hosting the segment. The 2nd map is for optional segments. The optional + * segments are used to get the new segments that is not online yet. Instead of simply skipping them by broker at + * routing time, we can send them to servers and let servers decide how to handle them. */ - abstract Map select(List segments, int requestId, - Map> segmentToEnabledInstancesMap, Map queryOptions); + abstract Pair, Map/*optional segments*/> select(List segments, + int requestId, SegmentStates segmentStates, Map queryOptions); } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelector.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelector.java index 4c96007fd627..d003723c5b17 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelector.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelector.java @@ -21,6 +21,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; +import org.apache.commons.lang3.tuple.Pair; import org.apache.helix.model.ExternalView; import org.apache.helix.model.IdealState; import org.apache.pinot.broker.routing.segmentpreselector.SegmentPreSelector; @@ -31,6 +33,11 @@ * The instance selector selects server instances to serve the query based on the selected segments. */ public interface InstanceSelector { + long NEW_SEGMENT_EXPIRATION_MILLIS = TimeUnit.MINUTES.toMillis(5); + + static boolean isNewSegment(long creationTimeMs, long currentTimeMs) { + return creationTimeMs > 0 && currentTimeMs - creationTimeMs <= NEW_SEGMENT_EXPIRATION_MILLIS; + } /** * Initializes the instance selector with the enabled instances, ideal state, external view and online segments @@ -63,17 +70,18 @@ public interface InstanceSelector { */ SelectionResult select(BrokerRequest brokerRequest, List segments, long requestId); + /** + * Returns the enabled server instances currently serving the table. + */ + Set getServingInstances(); + class SelectionResult { - private final Map _segmentToInstanceMap; + private final Pair, Map/*optional segments*/> _segmentToInstanceMap; private final List _unavailableSegments; private int _numPrunedSegments; - public SelectionResult(Map segmentToInstanceMap, List unavailableSegments) { - this(segmentToInstanceMap, unavailableSegments, 0); - } - - public SelectionResult(Map segmentToInstanceMap, List unavailableSegments, - int numPrunedSegments) { + public SelectionResult(Pair, Map> segmentToInstanceMap, + List unavailableSegments, int numPrunedSegments) { _segmentToInstanceMap = segmentToInstanceMap; _unavailableSegments = unavailableSegments; _numPrunedSegments = numPrunedSegments; @@ -83,7 +91,15 @@ public SelectionResult(Map segmentToInstanceMap, List un * Returns the map from segment to selected server instance hosting the segment. */ public Map getSegmentToInstanceMap() { - return _segmentToInstanceMap; + return _segmentToInstanceMap.getLeft(); + } + + /** + * Returns the map from optional segment to selected server instance hosting the optional segment. + * Optional segments can be skipped by broker or server upon any issue w/o failing the query. + */ + public Map getOptionalSegmentToInstanceMap() { + return _segmentToInstanceMap.getRight(); } /** diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelectorFactory.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelectorFactory.java index 8cc9f260f88d..2ccb1a9ac1cd 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelectorFactory.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelectorFactory.java @@ -18,6 +18,8 @@ */ package org.apache.pinot.broker.routing.instanceselector; +import com.google.common.annotations.VisibleForTesting; +import java.time.Clock; import javax.annotation.Nullable; import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; @@ -26,6 +28,8 @@ import org.apache.pinot.spi.config.table.RoutingConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.utils.CommonConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,32 +43,53 @@ private InstanceSelectorFactory() { public static final String LEGACY_REPLICA_GROUP_OFFLINE_ROUTING = "PartitionAwareOffline"; public static final String LEGACY_REPLICA_GROUP_REALTIME_ROUTING = "PartitionAwareRealtime"; + @VisibleForTesting + public static InstanceSelector getInstanceSelector(TableConfig tableConfig, + ZkHelixPropertyStore propertyStore, BrokerMetrics brokerMetrics, PinotConfiguration brokerConfig) { + return getInstanceSelector(tableConfig, propertyStore, brokerMetrics, null, Clock.systemUTC(), brokerConfig); + } + public static InstanceSelector getInstanceSelector(TableConfig tableConfig, ZkHelixPropertyStore propertyStore, BrokerMetrics brokerMetrics, - @Nullable AdaptiveServerSelector adaptiveServerSelector) { + @Nullable AdaptiveServerSelector adaptiveServerSelector, PinotConfiguration brokerConfig) { + return getInstanceSelector(tableConfig, propertyStore, brokerMetrics, adaptiveServerSelector, Clock.systemUTC(), + brokerConfig); + } + + public static InstanceSelector getInstanceSelector(TableConfig tableConfig, + ZkHelixPropertyStore propertyStore, BrokerMetrics brokerMetrics, + @Nullable AdaptiveServerSelector adaptiveServerSelector, Clock clock, PinotConfiguration brokerConfig) { String tableNameWithType = tableConfig.getTableName(); RoutingConfig routingConfig = tableConfig.getRoutingConfig(); + boolean useFixedReplica = brokerConfig.getProperty(CommonConstants.Broker.CONFIG_OF_USE_FIXED_REPLICA, + CommonConstants.Broker.DEFAULT_USE_FIXED_REPLICA); if (routingConfig != null) { + if (routingConfig.getUseFixedReplica() != null) { + // table config overrides broker config + useFixedReplica = routingConfig.getUseFixedReplica(); + } if (RoutingConfig.REPLICA_GROUP_INSTANCE_SELECTOR_TYPE.equalsIgnoreCase(routingConfig.getInstanceSelectorType()) - || (tableConfig.getTableType() == TableType.OFFLINE && LEGACY_REPLICA_GROUP_OFFLINE_ROUTING - .equalsIgnoreCase(routingConfig.getRoutingTableBuilderName())) || ( - tableConfig.getTableType() == TableType.REALTIME && LEGACY_REPLICA_GROUP_REALTIME_ROUTING - .equalsIgnoreCase(routingConfig.getRoutingTableBuilderName()))) { + || (tableConfig.getTableType() == TableType.OFFLINE && LEGACY_REPLICA_GROUP_OFFLINE_ROUTING.equalsIgnoreCase( + routingConfig.getRoutingTableBuilderName())) || (tableConfig.getTableType() == TableType.REALTIME + && LEGACY_REPLICA_GROUP_REALTIME_ROUTING.equalsIgnoreCase(routingConfig.getRoutingTableBuilderName()))) { LOGGER.info("Using ReplicaGroupInstanceSelector for table: {}", tableNameWithType); - return new ReplicaGroupInstanceSelector(tableNameWithType, brokerMetrics, adaptiveServerSelector); + return new ReplicaGroupInstanceSelector(tableNameWithType, propertyStore, brokerMetrics, adaptiveServerSelector, + clock, useFixedReplica); } - if (RoutingConfig.STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE - .equalsIgnoreCase(routingConfig.getInstanceSelectorType())) { + if (RoutingConfig.STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE.equalsIgnoreCase( + routingConfig.getInstanceSelectorType())) { LOGGER.info("Using StrictReplicaGroupInstanceSelector for table: {}", tableNameWithType); - return new StrictReplicaGroupInstanceSelector(tableNameWithType, brokerMetrics, adaptiveServerSelector); + return new StrictReplicaGroupInstanceSelector(tableNameWithType, propertyStore, brokerMetrics, + adaptiveServerSelector, clock, useFixedReplica); } if (RoutingConfig.MULTI_STAGE_REPLICA_GROUP_SELECTOR_TYPE.equalsIgnoreCase( routingConfig.getInstanceSelectorType())) { LOGGER.info("Using {} for table: {}", routingConfig.getInstanceSelectorType(), tableNameWithType); return new MultiStageReplicaGroupSelector(tableNameWithType, propertyStore, brokerMetrics, - adaptiveServerSelector); + adaptiveServerSelector, clock, useFixedReplica); } } - return new BalancedInstanceSelector(tableNameWithType, brokerMetrics, adaptiveServerSelector); + return new BalancedInstanceSelector(tableNameWithType, propertyStore, brokerMetrics, adaptiveServerSelector, clock, + useFixedReplica); } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/MultiStageReplicaGroupSelector.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/MultiStageReplicaGroupSelector.java index 0a6d66510cc6..b27450426dbf 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/MultiStageReplicaGroupSelector.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/MultiStageReplicaGroupSelector.java @@ -20,12 +20,14 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import java.time.Clock; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.Pair; import org.apache.helix.model.ExternalView; import org.apache.helix.model.IdealState; import org.apache.helix.store.zk.ZkHelixPropertyStore; @@ -53,15 +55,12 @@ public class MultiStageReplicaGroupSelector extends BaseInstanceSelector { private static final Logger LOGGER = LoggerFactory.getLogger(MultiStageReplicaGroupSelector.class); - private final String _tableNameWithType; - private final ZkHelixPropertyStore _propertyStore; - private InstancePartitions _instancePartitions; + private volatile InstancePartitions _instancePartitions; public MultiStageReplicaGroupSelector(String tableNameWithType, ZkHelixPropertyStore propertyStore, - BrokerMetrics brokerMetrics, @Nullable AdaptiveServerSelector adaptiveServerSelector) { - super(tableNameWithType, brokerMetrics, adaptiveServerSelector); - _tableNameWithType = tableNameWithType; - _propertyStore = propertyStore; + BrokerMetrics brokerMetrics, @Nullable AdaptiveServerSelector adaptiveServerSelector, Clock clock, + boolean useFixedReplica) { + super(tableNameWithType, propertyStore, brokerMetrics, adaptiveServerSelector, clock, useFixedReplica); } @Override @@ -84,50 +83,71 @@ public void onAssignmentChange(IdealState idealState, ExternalView externalView, } @Override - Map select(List segments, int requestId, - Map> segmentToEnabledInstancesMap, Map queryOptions) { + Pair, Map> select(List segments, int requestId, + SegmentStates segmentStates, Map queryOptions) { // Create a copy of InstancePartitions to avoid race-condition with event-listeners above. InstancePartitions instancePartitions = _instancePartitions; - int replicaGroupSelected = requestId % instancePartitions.getNumReplicaGroups(); + int replicaGroupSelected; + if (isUseFixedReplica(queryOptions)) { + // When using sticky routing, we want to iterate over the instancePartitions in order to ensure deterministic + // selection of replica group across queries i.e. same instance replica group id is picked each time. + // Since the instances within a selected replica group are iterated in order, the assignment within a selected + // replica group is guaranteed to be deterministic. + // Note: This can cause major hotspots in the cluster. + replicaGroupSelected = 0; + } else { + replicaGroupSelected = requestId % instancePartitions.getNumReplicaGroups(); + } for (int iteration = 0; iteration < instancePartitions.getNumReplicaGroups(); iteration++) { int replicaGroup = (replicaGroupSelected + iteration) % instancePartitions.getNumReplicaGroups(); try { - return tryAssigning(segmentToEnabledInstancesMap, instancePartitions, replicaGroup); + return tryAssigning(segments, segmentStates, instancePartitions, replicaGroup); } catch (Exception e) { LOGGER.warn("Unable to select replica-group {} for table: {}", replicaGroup, _tableNameWithType, e); } } - throw new RuntimeException(String.format("Unable to find any replica-group to serve table: %s", - _tableNameWithType)); + throw new RuntimeException( + String.format("Unable to find any replica-group to serve table: %s", _tableNameWithType)); } /** * Returns a map from the segmentName to the corresponding server in the given replica-group. If the is not enabled, * we throw an exception. */ - private Map tryAssigning(Map> segmentToEnabledInstancesMap, - InstancePartitions instancePartitions, int replicaId) { + private Pair, Map> tryAssigning(List segments, + SegmentStates segmentStates, InstancePartitions instancePartitions, int replicaId) { Set instanceLookUpSet = new HashSet<>(); for (int partition = 0; partition < instancePartitions.getNumPartitions(); partition++) { List instances = instancePartitions.getInstances(partition, replicaId); instanceLookUpSet.addAll(instances); } - Map result = new HashMap<>(); - for (Map.Entry> entry : segmentToEnabledInstancesMap.entrySet()) { - String segmentName = entry.getKey(); + Map segmentToSelectedInstanceMap = new HashMap<>(); + Map optionalSegmentToInstanceMap = new HashMap<>(); + for (String segment : segments) { + List candidates = segmentStates.getCandidates(segment); + // If candidates are null, we will throw an exception and log a warning. + Preconditions.checkState(candidates != null, "Failed to find servers for segment: %s", segment); boolean found = false; - for (String enabledInstanceForSegment : entry.getValue()) { - if (instanceLookUpSet.contains(enabledInstanceForSegment)) { + // candidates array is always sorted + for (SegmentInstanceCandidate candidate : candidates) { + String instance = candidate.getInstance(); + if (instanceLookUpSet.contains(instance)) { found = true; - result.put(segmentName, enabledInstanceForSegment); + // This can only be offline when it is a new segment. And such segment is marked as optional segment so that + // broker or server can skip it upon any issue to process it. + if (candidate.isOnline()) { + segmentToSelectedInstanceMap.put(segment, instance); + } else { + optionalSegmentToInstanceMap.put(segment, instance); + } break; } } if (!found) { - throw new RuntimeException(String.format("Unable to find an enabled instance for segment: %s", segmentName)); + throw new RuntimeException(String.format("Unable to find an enabled instance for segment: %s", segment)); } } - return result; + return Pair.of(segmentToSelectedInstanceMap, optionalSegmentToInstanceMap); } @VisibleForTesting @@ -135,7 +155,7 @@ protected InstancePartitions getInstancePartitions() { // TODO: Evaluate whether we need to provide support for COMPLETE partitions. TableType tableType = TableNameBuilder.getTableTypeFromTableName(_tableNameWithType); Preconditions.checkNotNull(tableType); - InstancePartitions instancePartitions = null; + InstancePartitions instancePartitions; if (tableType.equals(TableType.OFFLINE)) { instancePartitions = InstancePartitionsUtils.fetchInstancePartitions(_propertyStore, InstancePartitionsUtils.getInstancePartitionsName(_tableNameWithType, tableType.name())); diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/NewSegmentState.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/NewSegmentState.java new file mode 100644 index 000000000000..6cc186e7b877 --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/NewSegmentState.java @@ -0,0 +1,51 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.routing.instanceselector; + +import java.util.List; +import javax.annotation.concurrent.Immutable; + + +/** + * Contains the push time and candidate instances for a new segment. + */ +@Immutable +public class NewSegmentState { + // Segment creation time. This could be + // 1) From ZK if we first see this segment via init call. + // 2) Use wall time if we first see this segment from onAssignmentChange call. + private final long _creationTimeMs; + + // List of SegmentInstanceCandidate: which contains instance name and online flags. + // The candidates have to be in instance sorted order. + private final List _candidates; + + public NewSegmentState(long creationTimeMs, List candidates) { + _creationTimeMs = creationTimeMs; + _candidates = candidates; + } + + public long getCreationTimeMs() { + return _creationTimeMs; + } + + public List getCandidates() { + return _candidates; + } +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/ReplicaGroupInstanceSelector.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/ReplicaGroupInstanceSelector.java index 957e140c8e66..0e9bd52d425f 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/ReplicaGroupInstanceSelector.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/ReplicaGroupInstanceSelector.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.broker.routing.instanceselector; +import java.time.Clock; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -26,6 +27,8 @@ import java.util.Set; import javax.annotation.Nullable; import org.apache.commons.lang3.tuple.Pair; +import org.apache.helix.store.zk.ZkHelixPropertyStore; +import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.broker.routing.adaptiveserverselector.AdaptiveServerSelector; import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.utils.HashUtil; @@ -60,20 +63,19 @@ */ public class ReplicaGroupInstanceSelector extends BaseInstanceSelector { - public ReplicaGroupInstanceSelector(String tableNameWithType, BrokerMetrics brokerMetrics, - @Nullable AdaptiveServerSelector adaptiveServerSelector) { - super(tableNameWithType, brokerMetrics, adaptiveServerSelector); + public ReplicaGroupInstanceSelector(String tableNameWithType, ZkHelixPropertyStore propertyStore, + BrokerMetrics brokerMetrics, @Nullable AdaptiveServerSelector adaptiveServerSelector, Clock clock, + boolean useFixedReplica) { + super(tableNameWithType, propertyStore, brokerMetrics, adaptiveServerSelector, clock, useFixedReplica); } @Override - Map select(List segments, int requestId, - Map> segmentToEnabledInstancesMap, Map queryOptions) { - Map segmentToSelectedInstanceMap = new HashMap<>(HashUtil.getHashMapCapacity(segments.size())); - + Pair, Map> select(List segments, int requestId, + SegmentStates segmentStates, Map queryOptions) { if (_adaptiveServerSelector != null) { // Adaptive Server Selection is enabled. List serverRankList = new ArrayList<>(); - List candidateServers = fetchCandidateServersForQuery(segments, segmentToEnabledInstancesMap); + List candidateServers = fetchCandidateServersForQuery(segments, segmentStates); // Fetch serverRankList before looping through all the segments. This is important to make sure that we pick // the least amount of instances for a query by referring to a single snapshot of the rankings. @@ -82,97 +84,110 @@ Map select(List segments, int requestId, for (Pair entry : serverRankListWithScores) { serverRankList.add(entry.getLeft()); } - - selectServersUsingAdaptiverServerSelector(segments, requestId, segmentToSelectedInstanceMap, - segmentToEnabledInstancesMap, queryOptions, serverRankList); + return selectServersUsingAdaptiveServerSelector(segments, requestId, segmentStates, serverRankList); } else { // Adaptive Server Selection is NOT enabled. - selectServersUsingRoundRobin(segments, requestId, segmentToSelectedInstanceMap, segmentToEnabledInstancesMap, - queryOptions); + return selectServersUsingRoundRobin(segments, requestId, segmentStates, queryOptions); } - - return segmentToSelectedInstanceMap; } - private void selectServersUsingRoundRobin(List segments, int requestId, - Map segmentToSelectedInstanceMap, Map> segmentToEnabledInstancesMap, - Map queryOptions) { + private Pair, Map> selectServersUsingRoundRobin(List segments, + int requestId, SegmentStates segmentStates, Map queryOptions) { + Map segmentToSelectedInstanceMap = new HashMap<>(HashUtil.getHashMapCapacity(segments.size())); + // No need to adjust this map per total segment numbers, as optional segments should be empty most of the time. + Map optionalSegmentToInstanceMap = new HashMap<>(); + Integer numReplicaGroupsToQuery = QueryOptionsUtils.getNumReplicaGroupsToQuery(queryOptions); + int numReplicaGroups = numReplicaGroupsToQuery == null ? 1 : numReplicaGroupsToQuery; int replicaOffset = 0; - Integer replicaGroup = QueryOptionsUtils.getNumReplicaGroupsToQuery(queryOptions); - int numReplicaGroupsToQuery = replicaGroup == null ? 1 : replicaGroup; - for (String segment : segments) { - // NOTE: enabledInstances can be null when there is no enabled instances for the segment, or the instance selector - // has not been updated (we update all components for routing in sequence) - List enabledInstances = segmentToEnabledInstancesMap.get(segment); - if (enabledInstances == null) { + List candidates = segmentStates.getCandidates(segment); + // NOTE: candidates can be null when there is no enabled instances for the segment, or the instance selector has + // not been updated (we update all components for routing in sequence) + if (candidates == null) { continue; } - // Round robin selection. - int numEnabledInstances = enabledInstances.size(); - int instanceIdx = (requestId + replicaOffset) % numEnabledInstances; - String selectedInstance = enabledInstances.get(instanceIdx); + int numCandidates = candidates.size(); + int instanceIdx; + + if (isUseFixedReplica(queryOptions)) { + // candidates array is always sorted + instanceIdx = _tableNameHashForFixedReplicaRouting % numCandidates; + } else { + instanceIdx = (requestId + replicaOffset) % numCandidates; + } - if (numReplicaGroupsToQuery > numEnabledInstances) { - numReplicaGroupsToQuery = numEnabledInstances; + SegmentInstanceCandidate selectedInstance = candidates.get(instanceIdx); + // This can only be offline when it is a new segment. And such segment is marked as optional segment so that + // broker or server can skip it upon any issue to process it. + if (selectedInstance.isOnline()) { + segmentToSelectedInstanceMap.put(segment, selectedInstance.getInstance()); + } else { + optionalSegmentToInstanceMap.put(segment, selectedInstance.getInstance()); + } + if (numReplicaGroups > numCandidates) { + numReplicaGroups = numCandidates; } - segmentToSelectedInstanceMap.put(segment, selectedInstance); - replicaOffset = (replicaOffset + 1) % numReplicaGroupsToQuery; + replicaOffset = (replicaOffset + 1) % numReplicaGroups; } + return Pair.of(segmentToSelectedInstanceMap, optionalSegmentToInstanceMap); } - private void selectServersUsingAdaptiverServerSelector(List segments, int requestId, - Map segmentToSelectedInstanceMap, Map> segmentToEnabledInstancesMap, - Map queryOptions, List serverRankList) { + private Pair, Map> selectServersUsingAdaptiveServerSelector(List segments, + int requestId, SegmentStates segmentStates, List serverRankList) { + Map segmentToSelectedInstanceMap = new HashMap<>(HashUtil.getHashMapCapacity(segments.size())); + // No need to adjust this map per total segment numbers, as optional segments should be empty most of the time. + Map optionalSegmentToInstanceMap = new HashMap<>(); for (String segment : segments) { - // NOTE: enabledInstances can be null when there is no enabled instances for the segment, or the instance selector - // has not been updated (we update all components for routing in sequence) - List enabledInstances = segmentToEnabledInstancesMap.get(segment); - if (enabledInstances == null) { + // NOTE: candidates can be null when there is no enabled instances for the segment, or the instance selector has + // not been updated (we update all components for routing in sequence) + List candidates = segmentStates.getCandidates(segment); + if (candidates == null) { continue; } - // Round Robin. - int numEnabledInstances = enabledInstances.size(); - int instanceIdx = requestId % numEnabledInstances; - String selectedInstance = enabledInstances.get(instanceIdx); - + int numCandidates = candidates.size(); + int instanceIdx = requestId % numCandidates; + SegmentInstanceCandidate selectedInstance = candidates.get(instanceIdx); // Adaptive Server Selection // TODO: Support numReplicaGroupsToQuery with Adaptive Server Selection. - if (serverRankList.size() > 0) { + if (!serverRankList.isEmpty()) { int minIdx = Integer.MAX_VALUE; - for (int i = 0; i < numEnabledInstances; i++) { - int idx = serverRankList.indexOf(enabledInstances.get(i)); + for (SegmentInstanceCandidate candidate : candidates) { + int idx = serverRankList.indexOf(candidate.getInstance()); if (idx == -1) { // Let's use the round-robin approach until stats for all servers are populated. - selectedInstance = enabledInstances.get(instanceIdx); + selectedInstance = candidates.get(instanceIdx); break; } if (idx < minIdx) { minIdx = idx; - selectedInstance = enabledInstances.get(i); + selectedInstance = candidate; } } } - - segmentToSelectedInstanceMap.put(segment, selectedInstance); + // This can only be offline when it is a new segment. And such segment is marked as optional segment so that + // broker or server can skip it upon any issue to process it. + if (selectedInstance.isOnline()) { + segmentToSelectedInstanceMap.put(segment, selectedInstance.getInstance()); + } else { + optionalSegmentToInstanceMap.put(segment, selectedInstance.getInstance()); + } } + return Pair.of(segmentToSelectedInstanceMap, optionalSegmentToInstanceMap); } - private List fetchCandidateServersForQuery(List segments, - Map> segmentToEnabledInstancesMap) { - List serversList = new ArrayList<>(); - - Set tempServerSet = new HashSet<>(); + private List fetchCandidateServersForQuery(List segments, SegmentStates segmentStates) { + Set candidateServers = new HashSet<>(); for (String segment : segments) { - List enabledInstances = segmentToEnabledInstancesMap.get(segment); - if (enabledInstances != null) { - tempServerSet.addAll(enabledInstances); + List candidates = segmentStates.getCandidates(segment); + if (candidates == null) { + continue; + } + for (SegmentInstanceCandidate candidate : candidates) { + candidateServers.add(candidate.getInstance()); } } - - serversList.addAll(tempServerSet); - return serversList; + return new ArrayList<>(candidateServers); } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/SegmentInstanceCandidate.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/SegmentInstanceCandidate.java new file mode 100644 index 000000000000..97c8f08f6b17 --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/SegmentInstanceCandidate.java @@ -0,0 +1,44 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.routing.instanceselector; + +import javax.annotation.concurrent.Immutable; + + +/** + * Represents an instance candidate for a segment. + */ +@Immutable +public class SegmentInstanceCandidate { + private final String _instance; + private final boolean _online; + + public SegmentInstanceCandidate(String instance, boolean online) { + _instance = instance; + _online = online; + } + + public String getInstance() { + return _instance; + } + + public boolean isOnline() { + return _online; + } +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/SegmentStates.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/SegmentStates.java new file mode 100644 index 000000000000..c8809549b715 --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/SegmentStates.java @@ -0,0 +1,63 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.routing.instanceselector; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; + + +/** + * The {@code SegmentStates} contains the candidate instances for each segment, and the unavailable segments for routing + * purpose. + * + * For old segments, the instance candidates should always have online flag set to true. + * For old segments without any enabled instance candidates, we report them as unavailable segments. + * + * For new segments, the online flag within the instance candidates indicates whether the instance is online or not. + * We don't report new segments as unavailable segments because it is valid for new segments to be offline. + */ +@Immutable +public class SegmentStates { + private final Map> _instanceCandidatesMap; + private final Set _servingInstances; + private final Set _unavailableSegments; + + public SegmentStates(Map> instanceCandidatesMap, Set servingInstances, + Set unavailableSegments) { + _instanceCandidatesMap = instanceCandidatesMap; + _servingInstances = servingInstances; + _unavailableSegments = unavailableSegments; + } + + @Nullable + public List getCandidates(String segment) { + return _instanceCandidatesMap.get(segment); + } + + public Set getServingInstances() { + return _servingInstances; + } + + public Set getUnavailableSegments() { + return _unavailableSegments; + } +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/StrictReplicaGroupInstanceSelector.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/StrictReplicaGroupInstanceSelector.java index 248cff0d1c6f..95e83ea31c5a 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/StrictReplicaGroupInstanceSelector.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/instanceselector/StrictReplicaGroupInstanceSelector.java @@ -18,19 +18,24 @@ */ package org.apache.pinot.broker.routing.instanceselector; +import java.time.Clock; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.TreeSet; import javax.annotation.Nullable; import org.apache.helix.model.ExternalView; import org.apache.helix.model.IdealState; +import org.apache.helix.store.zk.ZkHelixPropertyStore; +import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.broker.routing.adaptiveserverselector.AdaptiveServerSelector; import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.utils.HashUtil; -import org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** @@ -58,128 +63,119 @@ * transitioning/error scenario (external view does not match ideal state), if a segment is down on S1, we mark all * segments with the same assignment ([S1, S2, S3]) down on S1 to ensure that we always route the segments to the same * replica-group. + * + * Note that new segments won't be used to exclude instances from serving when the segment is unavailable. * */ public class StrictReplicaGroupInstanceSelector extends ReplicaGroupInstanceSelector { + private static final Logger LOGGER = LoggerFactory.getLogger(StrictReplicaGroupInstanceSelector.class); - public StrictReplicaGroupInstanceSelector(String tableNameWithType, BrokerMetrics brokerMetrics, @Nullable - AdaptiveServerSelector adaptiveServerSelector) { - super(tableNameWithType, brokerMetrics, adaptiveServerSelector); + public StrictReplicaGroupInstanceSelector(String tableNameWithType, ZkHelixPropertyStore propertyStore, + BrokerMetrics brokerMetrics, @Nullable AdaptiveServerSelector adaptiveServerSelector, Clock clock, + boolean useFixedReplica) { + super(tableNameWithType, propertyStore, brokerMetrics, adaptiveServerSelector, clock, useFixedReplica); } /** * {@inheritDoc} * *

+   * Instances unavailable for any old segment should not exist in _oldSegmentCandidatesMap or _newSegmentStateMap for
+   * segments with the same instances in ideal state.
+   *
    * The maps are calculated in the following steps to meet the strict replica-group guarantee:
-   *   1. Create a map from online segment to set of instances hosting the segment based on the ideal state
-   *   2. Gather the online and offline instances for each online segment from the external view
-   *   3. Compare the instances from the ideal state and the external view and gather the unavailable instances for each
-   *      set of instances
-   *   4. Exclude the unavailable instances from the online instances map
+   *   1. Compute the online instances for both old and new segments
+   *   2. Compare online instances for old segments with instances in ideal state and gather the unavailable instances
+   *   for each set of instances
+   *   3. Exclude the unavailable instances from the online instances map for both old and new segment map
    * 
*/ @Override void updateSegmentMaps(IdealState idealState, ExternalView externalView, Set onlineSegments, - Map> segmentToOnlineInstancesMap, Map> segmentToOfflineInstancesMap, - Map> instanceToSegmentsMap) { - // TODO: Add support for AdaptiveServerSelection. - // Iterate over the ideal state to fill up 'idealStateSegmentToInstancesMap' which is a map from segment to set of - // instances hosting the segment in the ideal state - int segmentMapCapacity = HashUtil.getHashMapCapacity(onlineSegments.size()); - Map> idealStateSegmentToInstancesMap = new HashMap<>(segmentMapCapacity); - for (Map.Entry> entry : idealState.getRecord().getMapFields().entrySet()) { - String segment = entry.getKey(); - // Only track online segments - if (!onlineSegments.contains(segment)) { - continue; - } - idealStateSegmentToInstancesMap.put(segment, entry.getValue().keySet()); - } + Map newSegmentCreationTimeMap) { + _oldSegmentCandidatesMap.clear(); + int newSegmentMapCapacity = HashUtil.getHashMapCapacity(newSegmentCreationTimeMap.size()); + _newSegmentStateMap = new HashMap<>(newSegmentMapCapacity); - // Iterate over the external view to fill up 'tempSegmentToOnlineInstancesMap' and 'segmentToOfflineInstancesMap'. - // 'tempSegmentToOnlineInstancesMap' is a temporary map from segment to set of instances that are in the ideal state - // and also ONLINE/CONSUMING in the external view. This map does not have the strict replica-group guarantee, and - // will be used to calculate the final 'segmentToOnlineInstancesMap'. - Map> tempSegmentToOnlineInstancesMap = new HashMap<>(segmentMapCapacity); - for (Map.Entry> entry : externalView.getRecord().getMapFields().entrySet()) { - String segment = entry.getKey(); - Set instancesInIdealState = idealStateSegmentToInstancesMap.get(segment); - // Only track online segments - if (instancesInIdealState == null) { - continue; + Map> idealStateAssignment = idealState.getRecord().getMapFields(); + Map> externalViewAssignment = externalView.getRecord().getMapFields(); + + // Get the online instances for the segments + Map> oldSegmentToOnlineInstancesMap = + new HashMap<>(HashUtil.getHashMapCapacity(onlineSegments.size())); + Map> newSegmentToOnlineInstancesMap = new HashMap<>(newSegmentMapCapacity); + for (String segment : onlineSegments) { + Map idealStateInstanceStateMap = idealStateAssignment.get(segment); + assert idealStateInstanceStateMap != null; + Map externalViewInstanceStateMap = externalViewAssignment.get(segment); + Set onlineInstances; + if (externalViewInstanceStateMap == null) { + onlineInstances = Collections.emptySet(); + } else { + onlineInstances = getOnlineInstances(idealStateInstanceStateMap, externalViewInstanceStateMap); } - Map instanceStateMap = entry.getValue(); - Set tempOnlineInstances = new TreeSet<>(); - List offlineInstances = new ArrayList<>(); - tempSegmentToOnlineInstancesMap.put(segment, tempOnlineInstances); - segmentToOfflineInstancesMap.put(segment, offlineInstances); - for (Map.Entry instanceStateEntry : instanceStateMap.entrySet()) { - String instance = instanceStateEntry.getKey(); - // Only track instances within the ideal state - if (!instancesInIdealState.contains(instance)) { - continue; - } - String state = instanceStateEntry.getValue(); - if (state.equals(SegmentStateModel.ONLINE) || state.equals(SegmentStateModel.CONSUMING)) { - tempOnlineInstances.add(instance); - } else if (state.equals(SegmentStateModel.OFFLINE)) { - offlineInstances.add(instance); - instanceToSegmentsMap.computeIfAbsent(instance, k -> new ArrayList<>()).add(segment); - } + if (newSegmentCreationTimeMap.containsKey(segment)) { + newSegmentToOnlineInstancesMap.put(segment, onlineInstances); + } else { + oldSegmentToOnlineInstancesMap.put(segment, onlineInstances); } } - // Iterate over the 'tempSegmentToOnlineInstancesMap' to gather the unavailable instances for each set of instances + // Calculate the unavailable instances based on the old segments' online instances for each combination of instances + // in the ideal state Map, Set> unavailableInstancesMap = new HashMap<>(); - for (Map.Entry> entry : tempSegmentToOnlineInstancesMap.entrySet()) { + for (Map.Entry> entry : oldSegmentToOnlineInstancesMap.entrySet()) { String segment = entry.getKey(); - Set tempOnlineInstances = entry.getValue(); - Set instancesInIdealState = idealStateSegmentToInstancesMap.get(segment); - // NOTE: When a segment is unavailable on all the instances, do not count all the instances as unavailable because - // this segment is unavailable and won't be included in the routing table, thus not breaking the requirement - // of routing to the same replica-group. This is normal for new added segments, and we don't want to mark - // all instances down on all segments with the same assignment. - if (tempOnlineInstances.size() == instancesInIdealState.size() || tempOnlineInstances.isEmpty()) { - continue; - } + Set onlineInstances = entry.getValue(); + Map idealStateInstanceStateMap = idealStateAssignment.get(segment); + Set instancesInIdealState = idealStateInstanceStateMap.keySet(); Set unavailableInstances = - unavailableInstancesMap.computeIfAbsent(instancesInIdealState, k -> new TreeSet<>()); + unavailableInstancesMap.computeIfAbsent(instancesInIdealState, k -> new HashSet<>()); for (String instance : instancesInIdealState) { - if (!tempOnlineInstances.contains(instance)) { - unavailableInstances.add(instance); + if (!onlineInstances.contains(instance)) { + if (unavailableInstances.add(instance)) { + LOGGER.warn( + "Found unavailable instance: {} in instance group: {} for segment: {}, table: {} (IS: {}, EV: {})", + instance, instancesInIdealState, segment, _tableNameWithType, idealStateInstanceStateMap, + externalViewAssignment.get(segment)); + } } } } - // Iterate over the 'tempSegmentToOnlineInstancesMap' again to fill up the 'segmentToOnlineInstancesMap' which has - // the strict replica-group guarantee - for (Map.Entry> entry : tempSegmentToOnlineInstancesMap.entrySet()) { + // Iterate over the maps and exclude the unavailable instances + for (Map.Entry> entry : oldSegmentToOnlineInstancesMap.entrySet()) { String segment = entry.getKey(); - Set tempOnlineInstances = entry.getValue(); - // NOTE: Instances will be sorted here because 'tempOnlineInstances' is a TreeSet. We need the online instances to - // be sorted for replica-group routing to work. For multiple segments with the same online instances, if the - // list is sorted, the same index in the list will always point to the same instance. - List onlineInstances = new ArrayList<>(tempOnlineInstances.size()); - segmentToOnlineInstancesMap.put(segment, onlineInstances); - - Set instancesInIdealState = idealStateSegmentToInstancesMap.get(segment); - Set unavailableInstances = unavailableInstancesMap.get(instancesInIdealState); - if (unavailableInstances == null) { - // No unavailable instance, add all instances as online instance - for (String instance : tempOnlineInstances) { - onlineInstances.add(instance); - instanceToSegmentsMap.computeIfAbsent(instance, k -> new ArrayList<>()).add(segment); + // NOTE: onlineInstances is either a TreeSet or an EmptySet (sorted) + Set onlineInstances = entry.getValue(); + Map idealStateInstanceStateMap = idealStateAssignment.get(segment); + Set unavailableInstances = unavailableInstancesMap.get(idealStateInstanceStateMap.keySet()); + List candidates = new ArrayList<>(onlineInstances.size()); + for (String instance : onlineInstances) { + if (!unavailableInstances.contains(instance)) { + candidates.add(new SegmentInstanceCandidate(instance, true)); } - } else { - // Some instances are unavailable, add the remaining instances as online instance - for (String instance : tempOnlineInstances) { - if (!unavailableInstances.contains(instance)) { - onlineInstances.add(instance); - instanceToSegmentsMap.computeIfAbsent(instance, k -> new ArrayList<>()).add(segment); - } + } + _oldSegmentCandidatesMap.put(segment, candidates); + } + + for (Map.Entry> entry : newSegmentToOnlineInstancesMap.entrySet()) { + String segment = entry.getKey(); + Set onlineInstances = entry.getValue(); + Map idealStateInstanceStateMap = idealStateAssignment.get(segment); + Set unavailableInstances = + unavailableInstancesMap.getOrDefault(idealStateInstanceStateMap.keySet(), Collections.emptySet()); + List candidates = new ArrayList<>(idealStateInstanceStateMap.size()); + for (String instance : convertToSortedMap(idealStateInstanceStateMap).keySet()) { + if (!unavailableInstances.contains(instance)) { + candidates.add(new SegmentInstanceCandidate(instance, onlineInstances.contains(instance))); } } + _newSegmentStateMap.put(segment, new NewSegmentState(newSegmentCreationTimeMap.get(segment), candidates)); + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Got _newSegmentStateMap: {}, _oldSegmentCandidatesMap: {}", _newSegmentStateMap.keySet(), + _oldSegmentCandidatesMap.keySet()); } } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentmetadata/SegmentZkMetadataFetchListener.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentmetadata/SegmentZkMetadataFetchListener.java new file mode 100644 index 000000000000..138291089da2 --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentmetadata/SegmentZkMetadataFetchListener.java @@ -0,0 +1,56 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.routing.segmentmetadata; + +import java.util.List; +import java.util.Set; +import javax.annotation.Nullable; +import org.apache.helix.model.ExternalView; +import org.apache.helix.model.IdealState; +import org.apache.helix.zookeeper.datamodel.ZNRecord; +import org.apache.pinot.broker.routing.segmentpreselector.SegmentPreSelector; + + +/** + * Interface to register with {@link SegmentZkMetadataFetcher}. + * + *

When registered, SegmentZKMetadataFetcher will fetch {@link ZNRecord} for associated {@code onlineSegments} list + * or refreshed {@code segment}. Thus batch up all ZK access for segment metadata. + */ +public interface SegmentZkMetadataFetchListener { + + /** + * Initializes the segment pruner with the ideal state, external view and online segments (segments with + * ONLINE/CONSUMING instances in the ideal state and pre-selected by the {@link SegmentPreSelector}). Should be called + * only once before calling other methods. + */ + void init(IdealState idealState, ExternalView externalView, List onlineSegments, List znRecords); + + /** + * Processes the segment assignment (ideal state or external view) change based on the given online segments (segments + * with ONLINE/CONSUMING instances in the ideal state and pre-selected by the {@link SegmentPreSelector}). + */ + void onAssignmentChange(IdealState idealState, ExternalView externalView, Set onlineSegments, + List pulledSegments, List znRecords); + + /** + * Refreshes the metadata for the given segment (called when segment is getting refreshed). + */ + void refreshSegment(String segment, @Nullable ZNRecord znRecord); +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentmetadata/SegmentZkMetadataFetcher.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentmetadata/SegmentZkMetadataFetcher.java new file mode 100644 index 000000000000..b6d996e8f45f --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentmetadata/SegmentZkMetadataFetcher.java @@ -0,0 +1,133 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.routing.segmentmetadata; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.apache.helix.AccessOption; +import org.apache.helix.model.ExternalView; +import org.apache.helix.model.IdealState; +import org.apache.helix.store.zk.ZkHelixPropertyStore; +import org.apache.helix.zookeeper.datamodel.ZNRecord; +import org.apache.pinot.common.metadata.ZKMetadataProvider; + + +/** + * {@code SegmentZkMetadataFetcher} is used to cache {@link ZNRecord} stored in {@link ZkHelixPropertyStore} for + * segments. + */ +public class SegmentZkMetadataFetcher { + private final String _tableNameWithType; + private final ZkHelixPropertyStore _propertyStore; + private final String _segmentZKMetadataPathPrefix; + private final List _listeners; + private final Set _onlineSegmentsCached; + + private boolean _initialized; + + public SegmentZkMetadataFetcher(String tableNameWithType, ZkHelixPropertyStore propertyStore) { + _tableNameWithType = tableNameWithType; + _propertyStore = propertyStore; + _segmentZKMetadataPathPrefix = ZKMetadataProvider.constructPropertyStorePathForResource(tableNameWithType) + "/"; + _listeners = new ArrayList<>(); + _onlineSegmentsCached = new HashSet<>(); + _initialized = false; + } + + public void register(SegmentZkMetadataFetchListener listener) { + if (!_initialized) { + _listeners.add(listener); + } else { + throw new RuntimeException( + "Segment ZK metadata fetcher has already been initialized! Unable to register more listeners."); + } + } + + public List getListeners() { + return _listeners; + } + + public void init(IdealState idealState, ExternalView externalView, Set onlineSegments) { + if (!_initialized) { + _initialized = true; + if (!_listeners.isEmpty()) { + // Bulk load partition info for all online segments + int numSegments = onlineSegments.size(); + List segments = new ArrayList<>(numSegments); + List segmentZKMetadataPaths = new ArrayList<>(numSegments); + for (String segment : onlineSegments) { + segments.add(segment); + segmentZKMetadataPaths.add(_segmentZKMetadataPathPrefix + segment); + } + List znRecords = _propertyStore.get(segmentZKMetadataPaths, null, AccessOption.PERSISTENT, false); + for (SegmentZkMetadataFetchListener listener : _listeners) { + listener.init(idealState, externalView, segments, znRecords); + } + for (int i = 0; i < numSegments; i++) { + if (znRecords.get(i) != null) { + _onlineSegmentsCached.add(segments.get(i)); + } + } + } + } else { + throw new RuntimeException("Segment ZK metadata fetcher has already been initialized!"); + } + } + + public synchronized void onAssignmentChange(IdealState idealState, ExternalView externalView, + Set onlineSegments) { + if (!_listeners.isEmpty()) { + List segments = new ArrayList<>(); + List segmentZKMetadataPaths = new ArrayList<>(); + for (String segment : onlineSegments) { + if (!_onlineSegmentsCached.contains(segment)) { + segments.add(segment); + segmentZKMetadataPaths.add(_segmentZKMetadataPathPrefix + segment); + } + } + List znRecords = _propertyStore.get(segmentZKMetadataPaths, null, AccessOption.PERSISTENT, false); + for (SegmentZkMetadataFetchListener listener : _listeners) { + listener.onAssignmentChange(idealState, externalView, onlineSegments, segments, znRecords); + } + int numSegments = segments.size(); + for (int i = 0; i < numSegments; i++) { + if (znRecords.get(i) != null) { + _onlineSegmentsCached.add(segments.get(i)); + } + } + _onlineSegmentsCached.retainAll(onlineSegments); + } + } + + public synchronized void refreshSegment(String segment) { + if (!_listeners.isEmpty()) { + ZNRecord znRecord = _propertyStore.get(_segmentZKMetadataPathPrefix + segment, null, AccessOption.PERSISTENT); + for (SegmentZkMetadataFetchListener listener : _listeners) { + listener.refreshSegment(segment, znRecord); + } + if (znRecord != null) { + _onlineSegmentsCached.add(segment); + } else { + _onlineSegmentsCached.remove(segment); + } + } + } +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionInfo.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionInfo.java new file mode 100644 index 000000000000..667c1f1c5339 --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionInfo.java @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.routing.segmentpartition; + +import java.util.Set; +import org.apache.pinot.segment.spi.partition.PartitionFunction; + + +public class SegmentPartitionInfo { + private final String _partitionColumn; + private final PartitionFunction _partitionFunction; + private final Set _partitions; + + public SegmentPartitionInfo(String partitionColumn, PartitionFunction partitionFunction, + Set partitions) { + _partitionColumn = partitionColumn; + _partitionFunction = partitionFunction; + _partitions = partitions; + } + + public String getPartitionColumn() { + return _partitionColumn; + } + + public PartitionFunction getPartitionFunction() { + return _partitionFunction; + } + + public Set getPartitions() { + return _partitions; + } +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionMetadataManager.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionMetadataManager.java new file mode 100644 index 000000000000..36239555911c --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionMetadataManager.java @@ -0,0 +1,299 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.routing.segmentpartition; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nullable; +import org.apache.helix.model.ExternalView; +import org.apache.helix.model.IdealState; +import org.apache.helix.zookeeper.datamodel.ZNRecord; +import org.apache.pinot.broker.routing.instanceselector.InstanceSelector; +import org.apache.pinot.broker.routing.segmentmetadata.SegmentZkMetadataFetchListener; +import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; +import org.apache.pinot.common.utils.SegmentUtils; +import org.apache.pinot.core.routing.TablePartitionInfo; +import org.apache.pinot.core.routing.TablePartitionInfo.PartitionInfo; +import org.apache.pinot.segment.spi.partition.PartitionFunction; +import org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * The {@code PartitionDataManager} manages partitions of a table. It manages + * 1. all the online segments associated with the partition and their allocated servers + * 2. all the replica of a specific segment. + * It provides API to query + * 1. For each partition ID, what are the servers that contains ALL segments belong to this partition ID. + * 2. For each server, what are all the partition IDs and list of segments of those partition IDs on this server. + */ +public class SegmentPartitionMetadataManager implements SegmentZkMetadataFetchListener { + private static final Logger LOGGER = LoggerFactory.getLogger(SegmentPartitionMetadataManager.class); + private static final int INVALID_PARTITION_ID = -1; + private static final long INVALID_CREATION_TIME_MS = -1L; + + private final String _tableNameWithType; + + // static content, if anything changes for the following. a rebuild of routing table is needed. + private final String _partitionColumn; + private final String _partitionFunctionName; + private final int _numPartitions; + + // cache-able content, only follow changes if onlineSegments list (of ideal-state) is changed. + private final Map _segmentInfoMap = new HashMap<>(); + + // computed value based on status change. + private transient TablePartitionInfo _tablePartitionInfo; + + public SegmentPartitionMetadataManager(String tableNameWithType, String partitionColumn, String partitionFunctionName, + int numPartitions) { + _tableNameWithType = tableNameWithType; + _partitionColumn = partitionColumn; + _partitionFunctionName = partitionFunctionName; + _numPartitions = numPartitions; + } + + @Override + public void init(IdealState idealState, ExternalView externalView, List onlineSegments, + List znRecords) { + int numSegments = onlineSegments.size(); + for (int i = 0; i < numSegments; i++) { + String segment = onlineSegments.get(i); + ZNRecord znRecord = znRecords.get(i); + SegmentInfo segmentInfo = new SegmentInfo(getPartitionId(segment, znRecord), getCreationTimeMs(znRecord), + getOnlineServers(externalView, segment)); + _segmentInfoMap.put(segment, segmentInfo); + } + computeTablePartitionInfo(); + } + + private int getPartitionId(String segment, @Nullable ZNRecord znRecord) { + SegmentPartitionInfo segmentPartitionInfo = + SegmentPartitionUtils.extractPartitionInfo(_tableNameWithType, _partitionColumn, segment, znRecord); + if (segmentPartitionInfo == null || segmentPartitionInfo == SegmentPartitionUtils.INVALID_PARTITION_INFO) { + return INVALID_PARTITION_ID; + } + if (!_partitionColumn.equals(segmentPartitionInfo.getPartitionColumn())) { + return INVALID_PARTITION_ID; + } + PartitionFunction partitionFunction = segmentPartitionInfo.getPartitionFunction(); + if (!_partitionFunctionName.equalsIgnoreCase(partitionFunction.getName())) { + return INVALID_PARTITION_ID; + } + if (_numPartitions != partitionFunction.getNumPartitions()) { + return INVALID_PARTITION_ID; + } + Set partitions = segmentPartitionInfo.getPartitions(); + if (partitions.size() != 1) { + return INVALID_PARTITION_ID; + } + return partitions.iterator().next(); + } + + private static long getCreationTimeMs(@Nullable ZNRecord znRecord) { + if (znRecord == null) { + return INVALID_CREATION_TIME_MS; + } + return SegmentUtils.getSegmentCreationTimeMs(new SegmentZKMetadata(znRecord)); + } + + private static List getOnlineServers(ExternalView externalView, String segment) { + Map instanceStateMap = externalView.getStateMap(segment); + if (instanceStateMap == null) { + return Collections.emptyList(); + } + List onlineServers = new ArrayList<>(instanceStateMap.size()); + for (Map.Entry entry : instanceStateMap.entrySet()) { + String instanceState = entry.getValue(); + if (instanceState.equals(SegmentStateModel.ONLINE) || instanceState.equals(SegmentStateModel.CONSUMING)) { + onlineServers.add(entry.getKey()); + } + } + return onlineServers; + } + + private void computeTablePartitionInfo() { + PartitionInfo[] partitionInfoMap = new PartitionInfo[_numPartitions]; + List segmentsWithInvalidPartition = new ArrayList<>(); + List> newSegmentInfoEntries = new ArrayList<>(); + long currentTimeMs = System.currentTimeMillis(); + for (Map.Entry entry : _segmentInfoMap.entrySet()) { + String segment = entry.getKey(); + SegmentInfo segmentInfo = entry.getValue(); + int partitionId = segmentInfo._partitionId; + if (partitionId == INVALID_PARTITION_ID) { + segmentsWithInvalidPartition.add(segment); + continue; + } + // Process new segments in the end + if (InstanceSelector.isNewSegment(segmentInfo._creationTimeMs, currentTimeMs)) { + newSegmentInfoEntries.add(entry); + continue; + } + List onlineServers = segmentInfo._onlineServers; + PartitionInfo partitionInfo = partitionInfoMap[partitionId]; + if (partitionInfo == null) { + Set fullyReplicatedServers = new HashSet<>(onlineServers); + List segments = new ArrayList<>(); + segments.add(segment); + partitionInfo = new PartitionInfo(fullyReplicatedServers, segments); + partitionInfoMap[partitionId] = partitionInfo; + if (onlineServers.isEmpty()) { + LOGGER.warn("Found segment: {} without any available replica in table: {}, partition: {}", segment, + _tableNameWithType, partitionId); + } + } else { + if (partitionInfo._fullyReplicatedServers.retainAll(onlineServers)) { + LOGGER.warn("Found segment: {} with online servers: {} that reduces the fully replicated servers to: {} " + + "in table: {}, partition: {}", segment, onlineServers, partitionInfo._fullyReplicatedServers, + _tableNameWithType, partitionId); + } + partitionInfo._segments.add(segment); + } + } + if (!segmentsWithInvalidPartition.isEmpty()) { + int numSegmentsWithInvalidPartition = segmentsWithInvalidPartition.size(); + if (numSegmentsWithInvalidPartition <= 10) { + LOGGER.warn("Found {} segments: {} with invalid partition in table: {}", numSegmentsWithInvalidPartition, + segmentsWithInvalidPartition, _tableNameWithType); + } else { + LOGGER.warn("Found {} segments: {}... with invalid partition in table: {}", numSegmentsWithInvalidPartition, + segmentsWithInvalidPartition.subList(0, 10), _tableNameWithType); + } + } + // Process new segments + if (!newSegmentInfoEntries.isEmpty()) { + List excludedNewSegments = new ArrayList<>(); + for (Map.Entry entry : newSegmentInfoEntries) { + String segment = entry.getKey(); + SegmentInfo segmentInfo = entry.getValue(); + int partitionId = segmentInfo._partitionId; + List onlineServers = segmentInfo._onlineServers; + PartitionInfo partitionInfo = partitionInfoMap[partitionId]; + if (partitionInfo == null) { + // If the new segment is the first segment of a partition, treat it as regular segment if it has available + // replicas + if (!onlineServers.isEmpty()) { + Set fullyReplicatedServers = new HashSet<>(onlineServers); + List segments = new ArrayList<>(); + segments.add(segment); + partitionInfo = new PartitionInfo(fullyReplicatedServers, segments); + partitionInfoMap[partitionId] = partitionInfo; + } else { + excludedNewSegments.add(segment); + } + } else { + // If the new segment is not the first segment of a partition, add it only if it won't reduce the fully + // replicated servers. It is common that a new created segment (newly pushed, or a new consuming segment) + // doesn't have all the replicas available yet, and we want to exclude it from the partition info until all + // the replicas are available. + //noinspection SlowListContainsAll + if (onlineServers.containsAll(partitionInfo._fullyReplicatedServers)) { + partitionInfo._segments.add(segment); + } else { + excludedNewSegments.add(segment); + } + } + } + if (!excludedNewSegments.isEmpty()) { + int numExcludedNewSegments = excludedNewSegments.size(); + if (numExcludedNewSegments <= 10) { + LOGGER.info("Excluded {} new segments: {} without all replicas available in table: {}", + numExcludedNewSegments, excludedNewSegments, _tableNameWithType); + } else { + LOGGER.info("Excluded {} new segments: {}... without all replicas available in table: {}", + numExcludedNewSegments, excludedNewSegments.subList(0, 10), _tableNameWithType); + } + } + } + _tablePartitionInfo = + new TablePartitionInfo(_tableNameWithType, _partitionColumn, _partitionFunctionName, _numPartitions, + partitionInfoMap, segmentsWithInvalidPartition); + } + + @Override + public synchronized void onAssignmentChange(IdealState idealState, ExternalView externalView, + Set onlineSegments, List pulledSegments, List znRecords) { + // Update segment partition id for the pulled segments + int numSegments = pulledSegments.size(); + for (int i = 0; i < numSegments; i++) { + String segment = pulledSegments.get(i); + ZNRecord znRecord = znRecords.get(i); + SegmentInfo segmentInfo = new SegmentInfo(getPartitionId(segment, znRecord), getCreationTimeMs(znRecord), + getOnlineServers(externalView, segment)); + _segmentInfoMap.put(segment, segmentInfo); + } + // Update online servers for all online segments + for (String segment : onlineSegments) { + SegmentInfo segmentInfo = _segmentInfoMap.get(segment); + if (segmentInfo == null) { + // NOTE: This should not happen, but we still handle it gracefully by adding an invalid SegmentInfo + LOGGER.error("Failed to find segment info for segment: {} in table: {} while handling assignment change", + segment, _tableNameWithType); + segmentInfo = + new SegmentInfo(INVALID_PARTITION_ID, INVALID_CREATION_TIME_MS, getOnlineServers(externalView, segment)); + _segmentInfoMap.put(segment, segmentInfo); + } else { + segmentInfo._onlineServers = getOnlineServers(externalView, segment); + } + } + _segmentInfoMap.keySet().retainAll(onlineSegments); + computeTablePartitionInfo(); + } + + @Override + public synchronized void refreshSegment(String segment, @Nullable ZNRecord znRecord) { + int partitionId = getPartitionId(segment, znRecord); + long pushTimeMs = getCreationTimeMs(znRecord); + SegmentInfo segmentInfo = _segmentInfoMap.get(segment); + if (segmentInfo == null) { + // NOTE: This should not happen, but we still handle it gracefully by adding an invalid SegmentInfo + LOGGER.error("Failed to find segment info for segment: {} in table: {} while handling segment refresh", segment, + _tableNameWithType); + segmentInfo = new SegmentInfo(partitionId, pushTimeMs, Collections.emptyList()); + _segmentInfoMap.put(segment, segmentInfo); + } else { + segmentInfo._partitionId = partitionId; + segmentInfo._creationTimeMs = pushTimeMs; + } + computeTablePartitionInfo(); + } + + public TablePartitionInfo getTablePartitionInfo() { + return _tablePartitionInfo; + } + + private static class SegmentInfo { + int _partitionId; + long _creationTimeMs; + List _onlineServers; + + SegmentInfo(int partitionId, long creationTimeMs, List onlineServers) { + _partitionId = partitionId; + _creationTimeMs = creationTimeMs; + _onlineServers = onlineServers; + } + } +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionUtils.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionUtils.java new file mode 100644 index 000000000000..ee3dac042b66 --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionUtils.java @@ -0,0 +1,139 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.routing.segmentpartition; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nullable; +import org.apache.helix.zookeeper.datamodel.ZNRecord; +import org.apache.pinot.common.metadata.segment.SegmentPartitionMetadata; +import org.apache.pinot.segment.spi.partition.PartitionFunctionFactory; +import org.apache.pinot.segment.spi.partition.metadata.ColumnPartitionMetadata; +import org.apache.pinot.spi.utils.CommonConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class SegmentPartitionUtils { + private SegmentPartitionUtils() { + } + + public static final SegmentPartitionInfo INVALID_PARTITION_INFO = new SegmentPartitionInfo(null, null, null); + public static final Map INVALID_COLUMN_PARTITION_INFO_MAP = Collections.emptyMap(); + + private static final Logger LOGGER = LoggerFactory.getLogger(SegmentPartitionUtils.class); + + /** + * Returns the partition info for a given segment with single partition column. + * + * NOTE: Returns {@code null} when the ZNRecord is missing (could be transient Helix issue). Returns + * {@link #INVALID_PARTITION_INFO} when the segment does not have valid partition metadata in its ZK metadata, + * in which case we won't retry later. + */ + @Nullable + public static SegmentPartitionInfo extractPartitionInfo(String tableNameWithType, String partitionColumn, + String segment, @Nullable ZNRecord znRecord) { + if (znRecord == null) { + LOGGER.warn("Failed to find segment ZK metadata for segment: {}, table: {}", segment, tableNameWithType); + return null; + } + + String partitionMetadataJson = znRecord.getSimpleField(CommonConstants.Segment.PARTITION_METADATA); + if (partitionMetadataJson == null) { + LOGGER.warn("Failed to find segment partition metadata for segment: {}, table: {}", segment, tableNameWithType); + return INVALID_PARTITION_INFO; + } + + SegmentPartitionMetadata segmentPartitionMetadata; + try { + segmentPartitionMetadata = SegmentPartitionMetadata.fromJsonString(partitionMetadataJson); + } catch (Exception e) { + LOGGER.warn("Caught exception while extracting segment partition metadata for segment: {}, table: {}", segment, + tableNameWithType, e); + return INVALID_PARTITION_INFO; + } + + ColumnPartitionMetadata columnPartitionMetadata = + segmentPartitionMetadata.getColumnPartitionMap().get(partitionColumn); + if (columnPartitionMetadata == null) { + LOGGER.warn("Failed to find column partition metadata for column: {}, segment: {}, table: {}", partitionColumn, + segment, tableNameWithType); + return INVALID_PARTITION_INFO; + } + + return new SegmentPartitionInfo(partitionColumn, + PartitionFunctionFactory.getPartitionFunction(columnPartitionMetadata.getFunctionName(), + columnPartitionMetadata.getNumPartitions(), columnPartitionMetadata.getFunctionConfig()), + columnPartitionMetadata.getPartitions()); + } + + /** + * Returns a map from partition column name to partition info for a given segment with multiple partition columns. + * + * NOTE: Returns {@code null} when the ZNRecord is missing (could be transient Helix issue). Returns + * {@link #INVALID_COLUMN_PARTITION_INFO_MAP} when the segment does not have valid partition metadata in its ZK + * metadata, in which case we won't retry later. + */ + @Nullable + public static Map extractPartitionInfoMap(String tableNameWithType, + Set partitionColumns, String segment, @Nullable ZNRecord znRecord) { + if (znRecord == null) { + LOGGER.warn("Failed to find segment ZK metadata for segment: {}, table: {}", segment, tableNameWithType); + return null; + } + + String partitionMetadataJson = znRecord.getSimpleField(CommonConstants.Segment.PARTITION_METADATA); + if (partitionMetadataJson == null) { + LOGGER.warn("Failed to find segment partition metadata for segment: {}, table: {}", segment, tableNameWithType); + return INVALID_COLUMN_PARTITION_INFO_MAP; + } + + SegmentPartitionMetadata segmentPartitionMetadata; + try { + segmentPartitionMetadata = SegmentPartitionMetadata.fromJsonString(partitionMetadataJson); + } catch (Exception e) { + LOGGER.warn("Caught exception while extracting segment partition metadata for segment: {}, table: {}", segment, + tableNameWithType, e); + return INVALID_COLUMN_PARTITION_INFO_MAP; + } + + Map columnSegmentPartitionInfoMap = new HashMap<>(); + for (String partitionColumn : partitionColumns) { + ColumnPartitionMetadata columnPartitionMetadata = + segmentPartitionMetadata.getColumnPartitionMap().get(partitionColumn); + if (columnPartitionMetadata == null) { + LOGGER.warn("Failed to find column partition metadata for column: {}, segment: {}, table: {}", partitionColumn, + segment, tableNameWithType); + continue; + } + SegmentPartitionInfo segmentPartitionInfo = new SegmentPartitionInfo(partitionColumn, + PartitionFunctionFactory.getPartitionFunction(columnPartitionMetadata.getFunctionName(), + columnPartitionMetadata.getNumPartitions(), columnPartitionMetadata.getFunctionConfig()), + columnPartitionMetadata.getPartitions()); + columnSegmentPartitionInfoMap.put(partitionColumn, segmentPartitionInfo); + } + if (columnSegmentPartitionInfoMap.size() == 1) { + String partitionColumn = columnSegmentPartitionInfoMap.keySet().iterator().next(); + return Collections.singletonMap(partitionColumn, columnSegmentPartitionInfoMap.get(partitionColumn)); + } + return columnSegmentPartitionInfoMap.isEmpty() ? INVALID_COLUMN_PARTITION_INFO_MAP : columnSegmentPartitionInfoMap; + } +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/EmptySegmentPruner.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/EmptySegmentPruner.java index 7a7b66b0869b..aeb6a01f2e94 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/EmptySegmentPruner.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/EmptySegmentPruner.java @@ -18,18 +18,14 @@ */ package org.apache.pinot.broker.routing.segmentpruner; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; -import org.apache.helix.AccessOption; import org.apache.helix.model.ExternalView; import org.apache.helix.model.IdealState; -import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; -import org.apache.pinot.common.metadata.ZKMetadataProvider; import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.utils.CommonConstants; @@ -45,35 +41,23 @@ public class EmptySegmentPruner implements SegmentPruner { private static final Logger LOGGER = LoggerFactory.getLogger(EmptySegmentPruner.class); private final String _tableNameWithType; - private final ZkHelixPropertyStore _propertyStore; - private final String _segmentZKMetadataPathPrefix; private final Set _segmentsLoaded = new HashSet<>(); private final Set _emptySegments = ConcurrentHashMap.newKeySet(); private volatile ResultCache _resultCache; - public EmptySegmentPruner(TableConfig tableConfig, ZkHelixPropertyStore propertyStore) { + public EmptySegmentPruner(TableConfig tableConfig) { _tableNameWithType = tableConfig.getTableName(); - _propertyStore = propertyStore; - _segmentZKMetadataPathPrefix = ZKMetadataProvider.constructPropertyStorePathForResource(_tableNameWithType) + "/"; } @Override - public void init(IdealState idealState, ExternalView externalView, Set onlineSegments) { + public void init(IdealState idealState, ExternalView externalView, List onlineSegments, + List znRecords) { // Bulk load info for all online segments - int numSegments = onlineSegments.size(); - List segments = new ArrayList<>(numSegments); - List segmentZKMetadataPaths = new ArrayList<>(numSegments); - for (String segment : onlineSegments) { - segments.add(segment); - segmentZKMetadataPaths.add(_segmentZKMetadataPathPrefix + segment); - } - _segmentsLoaded.addAll(segments); - List znRecords = _propertyStore.get(segmentZKMetadataPaths, null, AccessOption.PERSISTENT, false); - for (int i = 0; i < numSegments; i++) { - String segment = segments.get(i); - if (isEmpty(segment, znRecords.get(i))) { + for (int idx = 0; idx < onlineSegments.size(); idx++) { + String segment = onlineSegments.get(idx); + if (isEmpty(segment, znRecords.get(idx))) { _emptySegments.add(segment); } } @@ -81,14 +65,14 @@ public void init(IdealState idealState, ExternalView externalView, Set o @Override public synchronized void onAssignmentChange(IdealState idealState, ExternalView externalView, - Set onlineSegments) { + Set onlineSegments, List pulledSegments, List znRecords) { // NOTE: We don't update all the segment ZK metadata for every external view change, but only the new added/removed // ones. The refreshed segment ZK metadata change won't be picked up. boolean emptySegmentsChanged = false; - for (String segment : onlineSegments) { + for (int idx = 0; idx < pulledSegments.size(); idx++) { + String segment = pulledSegments.get(idx); if (_segmentsLoaded.add(segment)) { - if (isEmpty(segment, - _propertyStore.get(_segmentZKMetadataPathPrefix + segment, null, AccessOption.PERSISTENT))) { + if (isEmpty(segment, znRecords.get(idx))) { emptySegmentsChanged |= _emptySegments.add(segment); } } @@ -103,9 +87,9 @@ public synchronized void onAssignmentChange(IdealState idealState, ExternalView } @Override - public synchronized void refreshSegment(String segment) { + public synchronized void refreshSegment(String segment, @Nullable ZNRecord znRecord) { _segmentsLoaded.add(segment); - if (isEmpty(segment, _propertyStore.get(_segmentZKMetadataPathPrefix + segment, null, AccessOption.PERSISTENT))) { + if (isEmpty(segment, znRecord)) { if (_emptySegments.add(segment)) { // Reset the result cache when empty segments changed _resultCache = null; diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/MultiPartitionColumnsSegmentPruner.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/MultiPartitionColumnsSegmentPruner.java index dd4edb686b80..6970d3f54ea7 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/MultiPartitionColumnsSegmentPruner.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/MultiPartitionColumnsSegmentPruner.java @@ -19,33 +19,23 @@ package org.apache.pinot.broker.routing.segmentpruner; import com.google.common.annotations.VisibleForTesting; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; -import org.apache.helix.AccessOption; import org.apache.helix.model.ExternalView; import org.apache.helix.model.IdealState; -import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; -import org.apache.pinot.common.metadata.ZKMetadataProvider; -import org.apache.pinot.common.metadata.segment.SegmentPartitionMetadata; +import org.apache.pinot.broker.routing.segmentpartition.SegmentPartitionInfo; +import org.apache.pinot.broker.routing.segmentpartition.SegmentPartitionUtils; import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.common.request.Expression; import org.apache.pinot.common.request.Function; import org.apache.pinot.common.request.Identifier; -import org.apache.pinot.segment.spi.partition.PartitionFunction; -import org.apache.pinot.segment.spi.partition.PartitionFunctionFactory; -import org.apache.pinot.segment.spi.partition.metadata.ColumnPartitionMetadata; -import org.apache.pinot.spi.utils.CommonConstants.Segment; +import org.apache.pinot.common.request.context.RequestContextUtils; import org.apache.pinot.sql.FilterKind; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** @@ -53,111 +43,49 @@ * pruner supports queries with filter (or nested filter) of EQUALITY and IN predicates. */ public class MultiPartitionColumnsSegmentPruner implements SegmentPruner { - private static final Logger LOGGER = LoggerFactory.getLogger(MultiPartitionColumnsSegmentPruner.class); - private static final Map INVALID_COLUMN_PARTITION_INFO_MAP = Collections.emptyMap(); - private final String _tableNameWithType; private final Set _partitionColumns; - private final ZkHelixPropertyStore _propertyStore; - private final String _segmentZKMetadataPathPrefix; - private final Map> _segmentColumnPartitionInfoMap = new ConcurrentHashMap<>(); + private final Map> _segmentColumnPartitionInfoMap = + new ConcurrentHashMap<>(); - public MultiPartitionColumnsSegmentPruner(String tableNameWithType, Set partitionColumns, - ZkHelixPropertyStore propertyStore) { + public MultiPartitionColumnsSegmentPruner(String tableNameWithType, Set partitionColumns) { _tableNameWithType = tableNameWithType; _partitionColumns = partitionColumns; - _propertyStore = propertyStore; - _segmentZKMetadataPathPrefix = ZKMetadataProvider.constructPropertyStorePathForResource(tableNameWithType) + "/"; } @Override - public void init(IdealState idealState, ExternalView externalView, Set onlineSegments) { + public void init(IdealState idealState, ExternalView externalView, List onlineSegments, + List znRecords) { // Bulk load partition info for all online segments - int numSegments = onlineSegments.size(); - List segments = new ArrayList<>(numSegments); - List segmentZKMetadataPaths = new ArrayList<>(numSegments); - for (String segment : onlineSegments) { - segments.add(segment); - segmentZKMetadataPaths.add(_segmentZKMetadataPathPrefix + segment); - } - List znRecords = _propertyStore.get(segmentZKMetadataPaths, null, AccessOption.PERSISTENT, false); - for (int i = 0; i < numSegments; i++) { - String segment = segments.get(i); - Map columnPartitionInfoMap = - extractColumnPartitionInfoMapFromSegmentZKMetadataZNRecord(segment, znRecords.get(i)); + for (int idx = 0; idx < onlineSegments.size(); idx++) { + String segment = onlineSegments.get(idx); + Map columnPartitionInfoMap = + SegmentPartitionUtils.extractPartitionInfoMap(_tableNameWithType, _partitionColumns, segment, + znRecords.get(idx)); if (columnPartitionInfoMap != null) { _segmentColumnPartitionInfoMap.put(segment, columnPartitionInfoMap); } } } - /** - * NOTE: Returns {@code null} when the ZNRecord is missing (could be transient Helix issue). Returns - * {@link #INVALID_COLUMN_PARTITION_INFO_MAP} when the segment does not have valid partition metadata in its ZK - * metadata, in which case we won't retry later. - */ - @Nullable - private Map extractColumnPartitionInfoMapFromSegmentZKMetadataZNRecord(String segment, - @Nullable ZNRecord znRecord) { - if (znRecord == null) { - LOGGER.warn("Failed to find segment ZK metadata for segment: {}, table: {}", segment, _tableNameWithType); - return null; - } - - String partitionMetadataJson = znRecord.getSimpleField(Segment.PARTITION_METADATA); - if (partitionMetadataJson == null) { - LOGGER.warn("Failed to find segment partition metadata for segment: {}, table: {}", segment, _tableNameWithType); - return INVALID_COLUMN_PARTITION_INFO_MAP; - } - - SegmentPartitionMetadata segmentPartitionMetadata; - try { - segmentPartitionMetadata = SegmentPartitionMetadata.fromJsonString(partitionMetadataJson); - } catch (Exception e) { - LOGGER.warn("Caught exception while extracting segment partition metadata for segment: {}, table: {}", segment, - _tableNameWithType, e); - return INVALID_COLUMN_PARTITION_INFO_MAP; - } - - Map columnPartitionInfoMap = new HashMap<>(); - for (String partitionColumn : _partitionColumns) { - ColumnPartitionMetadata columnPartitionMetadata = - segmentPartitionMetadata.getColumnPartitionMap().get(partitionColumn); - if (columnPartitionMetadata == null) { - LOGGER.warn("Failed to find column partition metadata for column: {}, segment: {}, table: {}", partitionColumn, - segment, _tableNameWithType); - continue; - } - PartitionInfo partitionInfo = new PartitionInfo( - PartitionFunctionFactory.getPartitionFunction(columnPartitionMetadata.getFunctionName(), - columnPartitionMetadata.getNumPartitions(), columnPartitionMetadata.getFunctionConfig()), - columnPartitionMetadata.getPartitions()); - columnPartitionInfoMap.put(partitionColumn, partitionInfo); - } - if (columnPartitionInfoMap.size() == 1) { - String partitionColumn = columnPartitionInfoMap.keySet().iterator().next(); - return Collections.singletonMap(partitionColumn, columnPartitionInfoMap.get(partitionColumn)); - } - return columnPartitionInfoMap.isEmpty() ? INVALID_COLUMN_PARTITION_INFO_MAP : columnPartitionInfoMap; - } - @Override public synchronized void onAssignmentChange(IdealState idealState, ExternalView externalView, - Set onlineSegments) { + Set onlineSegments, List pulledSegments, List znRecords) { // NOTE: We don't update all the segment ZK metadata for every external view change, but only the new added/removed // ones. The refreshed segment ZK metadata change won't be picked up. - for (String segment : onlineSegments) { + for (int idx = 0; idx < pulledSegments.size(); idx++) { + String segment = pulledSegments.get(idx); + ZNRecord znRecord = znRecords.get(idx); _segmentColumnPartitionInfoMap.computeIfAbsent(segment, - k -> extractColumnPartitionInfoMapFromSegmentZKMetadataZNRecord(k, - _propertyStore.get(_segmentZKMetadataPathPrefix + k, null, AccessOption.PERSISTENT))); + k -> SegmentPartitionUtils.extractPartitionInfoMap(_tableNameWithType, _partitionColumns, k, znRecord)); } _segmentColumnPartitionInfoMap.keySet().retainAll(onlineSegments); } @Override - public synchronized void refreshSegment(String segment) { - Map columnPartitionInfo = extractColumnPartitionInfoMapFromSegmentZKMetadataZNRecord(segment, - _propertyStore.get(_segmentZKMetadataPathPrefix + segment, null, AccessOption.PERSISTENT)); + public synchronized void refreshSegment(String segment, @Nullable ZNRecord znRecord) { + Map columnPartitionInfo = + SegmentPartitionUtils.extractPartitionInfoMap(_tableNameWithType, _partitionColumns, segment, znRecord); if (columnPartitionInfo != null) { _segmentColumnPartitionInfoMap.put(segment, columnPartitionInfo); } else { @@ -173,9 +101,10 @@ public Set prune(BrokerRequest brokerRequest, Set segments) { } Set selectedSegments = new HashSet<>(); for (String segment : segments) { - Map columnPartitionInfoMap = _segmentColumnPartitionInfoMap.get(segment); - if (columnPartitionInfoMap == null || columnPartitionInfoMap == INVALID_COLUMN_PARTITION_INFO_MAP - || isPartitionMatch(filterExpression, columnPartitionInfoMap)) { + Map columnPartitionInfoMap = _segmentColumnPartitionInfoMap.get(segment); + if (columnPartitionInfoMap == null + || columnPartitionInfoMap == SegmentPartitionUtils.INVALID_COLUMN_PARTITION_INFO_MAP || isPartitionMatch( + filterExpression, columnPartitionInfoMap)) { selectedSegments.add(segment); } } @@ -187,7 +116,8 @@ public Set getPartitionColumns() { return _partitionColumns; } - private boolean isPartitionMatch(Expression filterExpression, Map columnPartitionInfoMap) { + private boolean isPartitionMatch(Expression filterExpression, + Map columnPartitionInfoMap) { Function function = filterExpression.getFunctionCall(); FilterKind filterKind = FilterKind.valueOf(function.getOperator()); List operands = function.getOperands(); @@ -209,9 +139,9 @@ private boolean isPartitionMatch(Expression filterExpression, Map _partitions; - - PartitionInfo(PartitionFunction partitionFunction, Set partitions) { - _partitionFunction = partitionFunction; - _partitions = partitions; - } - } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPruner.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPruner.java index 5893e6bd9233..17e92e5c1587 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPruner.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPruner.java @@ -19,34 +19,14 @@ package org.apache.pinot.broker.routing.segmentpruner; import java.util.Set; -import org.apache.helix.model.ExternalView; -import org.apache.helix.model.IdealState; -import org.apache.pinot.broker.routing.segmentpreselector.SegmentPreSelector; +import org.apache.pinot.broker.routing.segmentmetadata.SegmentZkMetadataFetchListener; import org.apache.pinot.common.request.BrokerRequest; /** * The segment pruner prunes the selected segments based on the query. */ -public interface SegmentPruner { - - /** - * Initializes the segment pruner with the ideal state, external view and online segments (segments with - * ONLINE/CONSUMING instances in the ideal state and pre-selected by the {@link SegmentPreSelector}). Should be called - * only once before calling other methods. - */ - void init(IdealState idealState, ExternalView externalView, Set onlineSegments); - - /** - * Processes the segment assignment (ideal state or external view) change based on the given online segments (segments - * with ONLINE/CONSUMING instances in the ideal state and pre-selected by the {@link SegmentPreSelector}). - */ - void onAssignmentChange(IdealState idealState, ExternalView externalView, Set onlineSegments); - - /** - * Refreshes the metadata for the given segment (called when segment is getting refreshed). - */ - void refreshSegment(String segment); +public interface SegmentPruner extends SegmentZkMetadataFetchListener { /** * Prunes the segments queried by the given broker request, returns the selected segments to be queried. diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPrunerFactory.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPrunerFactory.java index f34f55f3c3d2..9efd86764e8b 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPrunerFactory.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPrunerFactory.java @@ -23,9 +23,10 @@ import java.util.Map; import java.util.Set; import javax.annotation.Nullable; -import org.apache.commons.collections.MapUtils; +import org.apache.commons.collections4.MapUtils; import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; +import org.apache.pinot.common.metadata.ZKMetadataProvider; import org.apache.pinot.segment.local.utils.TableConfigUtils; import org.apache.pinot.spi.config.table.ColumnPartitionConfig; import org.apache.pinot.spi.config.table.RoutingConfig; @@ -33,6 +34,8 @@ import org.apache.pinot.spi.config.table.SegmentsValidationAndRetentionConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.data.DateTimeFieldSpec; +import org.apache.pinot.spi.data.Schema; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,7 +55,7 @@ public static List getSegmentPruners(TableConfig tableConfig, boolean needsEmptySegment = TableConfigUtils.needsEmptySegmentPruner(tableConfig); if (needsEmptySegment) { // Add EmptySegmentPruner if needed - segmentPruners.add(new EmptySegmentPruner(tableConfig, propertyStore)); + segmentPruners.add(new EmptySegmentPruner(tableConfig)); } RoutingConfig routingConfig = tableConfig.getRoutingConfig(); @@ -62,7 +65,7 @@ public static List getSegmentPruners(TableConfig tableConfig, List configuredSegmentPruners = new ArrayList<>(segmentPrunerTypes.size()); for (String segmentPrunerType : segmentPrunerTypes) { if (RoutingConfig.PARTITION_SEGMENT_PRUNER_TYPE.equalsIgnoreCase(segmentPrunerType)) { - SegmentPruner partitionSegmentPruner = getPartitionSegmentPruner(tableConfig, propertyStore); + SegmentPruner partitionSegmentPruner = getPartitionSegmentPruner(tableConfig); if (partitionSegmentPruner != null) { configuredSegmentPruners.add(partitionSegmentPruner); } @@ -85,7 +88,7 @@ public static List getSegmentPruners(TableConfig tableConfig, if ((tableType == TableType.OFFLINE && LEGACY_PARTITION_AWARE_OFFLINE_ROUTING.equalsIgnoreCase( routingTableBuilderName)) || (tableType == TableType.REALTIME && LEGACY_PARTITION_AWARE_REALTIME_ROUTING.equalsIgnoreCase(routingTableBuilderName))) { - SegmentPruner partitionSegmentPruner = getPartitionSegmentPruner(tableConfig, propertyStore); + SegmentPruner partitionSegmentPruner = getPartitionSegmentPruner(tableConfig); if (partitionSegmentPruner != null) { segmentPruners.add(partitionSegmentPruner); } @@ -96,8 +99,7 @@ public static List getSegmentPruners(TableConfig tableConfig, } @Nullable - private static SegmentPruner getPartitionSegmentPruner(TableConfig tableConfig, - ZkHelixPropertyStore propertyStore) { + private static SegmentPruner getPartitionSegmentPruner(TableConfig tableConfig) { String tableNameWithType = tableConfig.getTableName(); SegmentPartitionConfig segmentPartitionConfig = tableConfig.getIndexingConfig().getSegmentPartitionConfig(); if (segmentPartitionConfig == null) { @@ -113,8 +115,8 @@ private static SegmentPruner getPartitionSegmentPruner(TableConfig tableConfig, LOGGER.info("Using PartitionSegmentPruner on partition columns: {} for table: {}", partitionColumns, tableNameWithType); return partitionColumns.size() == 1 ? new SinglePartitionColumnSegmentPruner(tableNameWithType, - partitionColumns.iterator().next(), propertyStore) - : new MultiPartitionColumnsSegmentPruner(tableNameWithType, partitionColumns, propertyStore); + partitionColumns.iterator().next()) + : new MultiPartitionColumnsSegmentPruner(tableNameWithType, partitionColumns); } @Nullable @@ -131,9 +133,20 @@ private static TimeSegmentPruner getTimeSegmentPruner(TableConfig tableConfig, LOGGER.warn("Cannot enable time range pruning without time column for table: {}", tableNameWithType); return null; } - - LOGGER.info("Using TimeRangePruner on time column: {} for table: {}", timeColumn, tableNameWithType); - return new TimeSegmentPruner(tableConfig, propertyStore); + Schema schema = ZKMetadataProvider.getTableSchema(propertyStore, tableConfig); + if (schema == null) { + LOGGER.warn("Cannot enable time range pruning without schema for table: {}", tableNameWithType); + return null; + } + DateTimeFieldSpec timeFieldSpec = schema.getSpecForTimeColumn(timeColumn); + if (timeFieldSpec == null) { + LOGGER.warn("Cannot enable time range pruning without field spec for table: {}, time column: {}", + tableNameWithType, timeColumn); + return null; + } + LOGGER.info("Using TimeRangePruner on time column: {} for table: {} with DateTimeFieldSpec: {}", timeColumn, + tableNameWithType, timeFieldSpec); + return new TimeSegmentPruner(tableConfig, timeFieldSpec); } private static List sortSegmentPruners(List pruners) { diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SinglePartitionColumnSegmentPruner.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SinglePartitionColumnSegmentPruner.java index 2c75782ef3aa..7016484e48c4 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SinglePartitionColumnSegmentPruner.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/SinglePartitionColumnSegmentPruner.java @@ -18,31 +18,23 @@ */ package org.apache.pinot.broker.routing.segmentpruner; -import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; -import org.apache.helix.AccessOption; import org.apache.helix.model.ExternalView; import org.apache.helix.model.IdealState; -import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; -import org.apache.pinot.common.metadata.ZKMetadataProvider; -import org.apache.pinot.common.metadata.segment.SegmentPartitionMetadata; +import org.apache.pinot.broker.routing.segmentpartition.SegmentPartitionInfo; +import org.apache.pinot.broker.routing.segmentpartition.SegmentPartitionUtils; import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.common.request.Expression; import org.apache.pinot.common.request.Function; import org.apache.pinot.common.request.Identifier; -import org.apache.pinot.segment.spi.partition.PartitionFunction; -import org.apache.pinot.segment.spi.partition.PartitionFunctionFactory; -import org.apache.pinot.segment.spi.partition.metadata.ColumnPartitionMetadata; -import org.apache.pinot.spi.utils.CommonConstants.Segment; +import org.apache.pinot.common.request.context.RequestContextUtils; import org.apache.pinot.sql.FilterKind; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** @@ -50,99 +42,47 @@ * pruner supports queries with filter (or nested filter) of EQUALITY and IN predicates. */ public class SinglePartitionColumnSegmentPruner implements SegmentPruner { - private static final Logger LOGGER = LoggerFactory.getLogger(SinglePartitionColumnSegmentPruner.class); - private static final PartitionInfo INVALID_PARTITION_INFO = new PartitionInfo(null, null); - private final String _tableNameWithType; private final String _partitionColumn; - private final ZkHelixPropertyStore _propertyStore; - private final String _segmentZKMetadataPathPrefix; - private final Map _partitionInfoMap = new ConcurrentHashMap<>(); + private final Map _partitionInfoMap = new ConcurrentHashMap<>(); - public SinglePartitionColumnSegmentPruner(String tableNameWithType, String partitionColumn, - ZkHelixPropertyStore propertyStore) { + public SinglePartitionColumnSegmentPruner(String tableNameWithType, String partitionColumn) { _tableNameWithType = tableNameWithType; _partitionColumn = partitionColumn; - _propertyStore = propertyStore; - _segmentZKMetadataPathPrefix = ZKMetadataProvider.constructPropertyStorePathForResource(tableNameWithType) + "/"; } @Override - public void init(IdealState idealState, ExternalView externalView, Set onlineSegments) { + public void init(IdealState idealState, ExternalView externalView, List onlineSegments, + List znRecords) { // Bulk load partition info for all online segments - int numSegments = onlineSegments.size(); - List segments = new ArrayList<>(numSegments); - List segmentZKMetadataPaths = new ArrayList<>(numSegments); - for (String segment : onlineSegments) { - segments.add(segment); - segmentZKMetadataPaths.add(_segmentZKMetadataPathPrefix + segment); - } - List znRecords = _propertyStore.get(segmentZKMetadataPaths, null, AccessOption.PERSISTENT, false); - for (int i = 0; i < numSegments; i++) { - String segment = segments.get(i); - PartitionInfo partitionInfo = extractPartitionInfoFromSegmentZKMetadataZNRecord(segment, znRecords.get(i)); + for (int idx = 0; idx < onlineSegments.size(); idx++) { + String segment = onlineSegments.get(idx); + SegmentPartitionInfo partitionInfo = + SegmentPartitionUtils.extractPartitionInfo(_tableNameWithType, _partitionColumn, segment, znRecords.get(idx)); if (partitionInfo != null) { _partitionInfoMap.put(segment, partitionInfo); } } } - /** - * NOTE: Returns {@code null} when the ZNRecord is missing (could be transient Helix issue). Returns - * {@link #INVALID_PARTITION_INFO} when the segment does not have valid partition metadata in its ZK metadata, - * in which case we won't retry later. - */ - @Nullable - private PartitionInfo extractPartitionInfoFromSegmentZKMetadataZNRecord(String segment, @Nullable ZNRecord znRecord) { - if (znRecord == null) { - LOGGER.warn("Failed to find segment ZK metadata for segment: {}, table: {}", segment, _tableNameWithType); - return null; - } - - String partitionMetadataJson = znRecord.getSimpleField(Segment.PARTITION_METADATA); - if (partitionMetadataJson == null) { - LOGGER.warn("Failed to find segment partition metadata for segment: {}, table: {}", segment, _tableNameWithType); - return INVALID_PARTITION_INFO; - } - - SegmentPartitionMetadata segmentPartitionMetadata; - try { - segmentPartitionMetadata = SegmentPartitionMetadata.fromJsonString(partitionMetadataJson); - } catch (Exception e) { - LOGGER.warn("Caught exception while extracting segment partition metadata for segment: {}, table: {}", segment, - _tableNameWithType, e); - return INVALID_PARTITION_INFO; - } - - ColumnPartitionMetadata columnPartitionMetadata = - segmentPartitionMetadata.getColumnPartitionMap().get(_partitionColumn); - if (columnPartitionMetadata == null) { - LOGGER.warn("Failed to find column partition metadata for column: {}, segment: {}, table: {}", _partitionColumn, - segment, _tableNameWithType); - return INVALID_PARTITION_INFO; - } - - return new PartitionInfo(PartitionFunctionFactory.getPartitionFunction(columnPartitionMetadata.getFunctionName(), - columnPartitionMetadata.getNumPartitions(), columnPartitionMetadata.getFunctionConfig()), - columnPartitionMetadata.getPartitions()); - } - @Override public synchronized void onAssignmentChange(IdealState idealState, ExternalView externalView, - Set onlineSegments) { + Set onlineSegments, List pulledSegments, List znRecords) { // NOTE: We don't update all the segment ZK metadata for every external view change, but only the new added/removed // ones. The refreshed segment ZK metadata change won't be picked up. - for (String segment : onlineSegments) { - _partitionInfoMap.computeIfAbsent(segment, k -> extractPartitionInfoFromSegmentZKMetadataZNRecord(k, - _propertyStore.get(_segmentZKMetadataPathPrefix + k, null, AccessOption.PERSISTENT))); + for (int idx = 0; idx < pulledSegments.size(); idx++) { + String segment = pulledSegments.get(idx); + ZNRecord znRecord = znRecords.get(idx); + _partitionInfoMap.computeIfAbsent(segment, + k -> SegmentPartitionUtils.extractPartitionInfo(_tableNameWithType, _partitionColumn, k, znRecord)); } _partitionInfoMap.keySet().retainAll(onlineSegments); } @Override - public synchronized void refreshSegment(String segment) { - PartitionInfo partitionInfo = extractPartitionInfoFromSegmentZKMetadataZNRecord(segment, - _propertyStore.get(_segmentZKMetadataPathPrefix + segment, null, AccessOption.PERSISTENT)); + public synchronized void refreshSegment(String segment, @Nullable ZNRecord znRecord) { + SegmentPartitionInfo partitionInfo = + SegmentPartitionUtils.extractPartitionInfo(_tableNameWithType, _partitionColumn, segment, znRecord); if (partitionInfo != null) { _partitionInfoMap.put(segment, partitionInfo); } else { @@ -158,16 +98,16 @@ public Set prune(BrokerRequest brokerRequest, Set segments) { } Set selectedSegments = new HashSet<>(); for (String segment : segments) { - PartitionInfo partitionInfo = _partitionInfoMap.get(segment); - if (partitionInfo == null || partitionInfo == INVALID_PARTITION_INFO || isPartitionMatch(filterExpression, - partitionInfo)) { + SegmentPartitionInfo partitionInfo = _partitionInfoMap.get(segment); + if (partitionInfo == null || partitionInfo == SegmentPartitionUtils.INVALID_PARTITION_INFO || isPartitionMatch( + filterExpression, partitionInfo)) { selectedSegments.add(segment); } } return selectedSegments; } - private boolean isPartitionMatch(Expression filterExpression, PartitionInfo partitionInfo) { + private boolean isPartitionMatch(Expression filterExpression, SegmentPartitionInfo partitionInfo) { Function function = filterExpression.getFunctionCall(); FilterKind filterKind = FilterKind.valueOf(function.getOperator()); List operands = function.getOperands(); @@ -189,8 +129,8 @@ private boolean isPartitionMatch(Expression filterExpression, PartitionInfo part case EQUALS: { Identifier identifier = operands.get(0).getIdentifier(); if (identifier != null && identifier.getName().equals(_partitionColumn)) { - return partitionInfo._partitions.contains( - partitionInfo._partitionFunction.getPartition(operands.get(1).getLiteral().getFieldValue().toString())); + return partitionInfo.getPartitions().contains(partitionInfo.getPartitionFunction() + .getPartition(RequestContextUtils.getStringValue(operands.get(1)))); } else { return true; } @@ -200,8 +140,8 @@ private boolean isPartitionMatch(Expression filterExpression, PartitionInfo part if (identifier != null && identifier.getName().equals(_partitionColumn)) { int numOperands = operands.size(); for (int i = 1; i < numOperands; i++) { - if (partitionInfo._partitions.contains(partitionInfo._partitionFunction.getPartition( - operands.get(i).getLiteral().getFieldValue().toString()))) { + if (partitionInfo.getPartitions().contains(partitionInfo.getPartitionFunction() + .getPartition(RequestContextUtils.getStringValue(operands.get(i))))) { return true; } } @@ -214,14 +154,4 @@ private boolean isPartitionMatch(Expression filterExpression, PartitionInfo part return true; } } - - private static class PartitionInfo { - final PartitionFunction _partitionFunction; - final Set _partitions; - - PartitionInfo(PartitionFunction partitionFunction, Set partitions) { - _partitionFunction = partitionFunction; - _partitions = partitions; - } - } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/TimeSegmentPruner.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/TimeSegmentPruner.java index c123c65a6755..59aa65406da8 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/TimeSegmentPruner.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentpruner/TimeSegmentPruner.java @@ -29,22 +29,19 @@ import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import org.apache.commons.lang3.StringUtils; -import org.apache.helix.AccessOption; import org.apache.helix.model.ExternalView; import org.apache.helix.model.IdealState; -import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.broker.routing.segmentpruner.interval.Interval; import org.apache.pinot.broker.routing.segmentpruner.interval.IntervalTree; -import org.apache.pinot.common.metadata.ZKMetadataProvider; import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.common.request.Expression; import org.apache.pinot.common.request.Function; import org.apache.pinot.common.request.Identifier; +import org.apache.pinot.common.request.Literal; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.data.DateTimeFieldSpec; import org.apache.pinot.spi.data.DateTimeFormatSpec; -import org.apache.pinot.spi.data.Schema; import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.CommonConstants.Query.Range; import org.apache.pinot.sql.FilterKind; @@ -64,44 +61,25 @@ public class TimeSegmentPruner implements SegmentPruner { private static final Interval DEFAULT_INTERVAL = new Interval(MIN_START_TIME, MAX_END_TIME); private final String _tableNameWithType; - private final ZkHelixPropertyStore _propertyStore; - private final String _segmentZKMetadataPathPrefix; private final String _timeColumn; private final DateTimeFormatSpec _timeFormatSpec; private volatile IntervalTree _intervalTree; private final Map _intervalMap = new HashMap<>(); - public TimeSegmentPruner(TableConfig tableConfig, ZkHelixPropertyStore propertyStore) { + public TimeSegmentPruner(TableConfig tableConfig, DateTimeFieldSpec timeFieldSpec) { _tableNameWithType = tableConfig.getTableName(); - _propertyStore = propertyStore; - _segmentZKMetadataPathPrefix = ZKMetadataProvider.constructPropertyStorePathForResource(_tableNameWithType) + "/"; - _timeColumn = tableConfig.getValidationConfig().getTimeColumnName(); - Preconditions.checkNotNull(_timeColumn, "Time column must be configured in table config for table: %s", - _tableNameWithType); - - Schema schema = ZKMetadataProvider.getTableSchema(_propertyStore, _tableNameWithType); - Preconditions.checkNotNull(schema, "Failed to find schema for table: %s", _tableNameWithType); - DateTimeFieldSpec dateTimeSpec = schema.getSpecForTimeColumn(_timeColumn); - Preconditions.checkNotNull(dateTimeSpec, "Field spec must be specified in schema for time column: %s of table: %s", - _timeColumn, _tableNameWithType); - _timeFormatSpec = dateTimeSpec.getFormatSpec(); + _timeColumn = timeFieldSpec.getName(); + _timeFormatSpec = timeFieldSpec.getFormatSpec(); } @Override - public void init(IdealState idealState, ExternalView externalView, Set onlineSegments) { + public void init(IdealState idealState, ExternalView externalView, List onlineSegments, + List znRecords) { // Bulk load time info for all online segments - int numSegments = onlineSegments.size(); - List segments = new ArrayList<>(numSegments); - List segmentZKMetadataPaths = new ArrayList<>(numSegments); - for (String segment : onlineSegments) { - segments.add(segment); - segmentZKMetadataPaths.add(_segmentZKMetadataPathPrefix + segment); - } - List znRecords = _propertyStore.get(segmentZKMetadataPaths, null, AccessOption.PERSISTENT, false); - for (int i = 0; i < numSegments; i++) { - String segment = segments.get(i); - Interval interval = extractIntervalFromSegmentZKMetaZNRecord(segment, znRecords.get(i)); + for (int idx = 0; idx < onlineSegments.size(); idx++) { + String segment = onlineSegments.get(idx); + Interval interval = extractIntervalFromSegmentZKMetaZNRecord(segment, znRecords.get(idx)); _intervalMap.put(segment, interval); } _intervalTree = new IntervalTree<>(_intervalMap); @@ -128,21 +106,21 @@ private Interval extractIntervalFromSegmentZKMetaZNRecord(String segment, @Nulla @Override public synchronized void onAssignmentChange(IdealState idealState, ExternalView externalView, - Set onlineSegments) { + Set onlineSegments, List pulledSegments, List znRecords) { // NOTE: We don't update all the segment ZK metadata for every external view change, but only the new added/removed // ones. The refreshed segment ZK metadata change won't be picked up. - for (String segment : onlineSegments) { - _intervalMap.computeIfAbsent(segment, k -> extractIntervalFromSegmentZKMetaZNRecord(k, - _propertyStore.get(_segmentZKMetadataPathPrefix + k, null, AccessOption.PERSISTENT))); + for (int idx = 0; idx < pulledSegments.size(); idx++) { + String segment = pulledSegments.get(idx); + ZNRecord zNrecord = znRecords.get(idx); + _intervalMap.computeIfAbsent(segment, k -> extractIntervalFromSegmentZKMetaZNRecord(k, zNrecord)); } _intervalMap.keySet().retainAll(onlineSegments); _intervalTree = new IntervalTree<>(_intervalMap); } @Override - public synchronized void refreshSegment(String segment) { - Interval interval = extractIntervalFromSegmentZKMetaZNRecord(segment, - _propertyStore.get(_segmentZKMetadataPathPrefix + segment, null, AccessOption.PERSISTENT)); + public synchronized void refreshSegment(String segment, @Nullable ZNRecord znRecord) { + Interval interval = extractIntervalFromSegmentZKMetaZNRecord(segment, znRecord); _intervalMap.put(segment, interval); _intervalTree = new IntervalTree<>(_intervalMap); } @@ -231,97 +209,53 @@ private List getFilterTimeIntervals(Expression filterExpression) { } else { return getComplementSortedIntervals(childIntervals); } - case EQUALS: { - Identifier identifier = operands.get(0).getIdentifier(); - if (identifier != null && identifier.getName().equals(_timeColumn)) { - long timeStamp = _timeFormatSpec.fromFormatToMillis(operands.get(1).getLiteral().getFieldValue().toString()); - return Collections.singletonList(new Interval(timeStamp, timeStamp)); - } else { - return null; + case EQUALS: + if (isTimeColumn(operands.get(0))) { + long timestamp = toMillisSinceEpoch(operands.get(1)); + return List.of(new Interval(timestamp, timestamp)); } - } - case IN: { - Identifier identifier = operands.get(0).getIdentifier(); - if (identifier != null && identifier.getName().equals(_timeColumn)) { + return null; + case IN: + if (isTimeColumn(operands.get(0))) { int numOperands = operands.size(); List intervals = new ArrayList<>(numOperands - 1); for (int i = 1; i < numOperands; i++) { - long timeStamp = - _timeFormatSpec.fromFormatToMillis(operands.get(i).getLiteral().getFieldValue().toString()); - intervals.add(new Interval(timeStamp, timeStamp)); + long timestamp = toMillisSinceEpoch(operands.get(i)); + intervals.add(new Interval(timestamp, timestamp)); } return intervals; - } else { - return null; } - } - case GREATER_THAN: { - Identifier identifier = operands.get(0).getIdentifier(); - if (identifier != null && identifier.getName().equals(_timeColumn)) { - long timeStamp = _timeFormatSpec.fromFormatToMillis(operands.get(1).getLiteral().getFieldValue().toString()); - return Collections.singletonList(new Interval(timeStamp + 1, MAX_END_TIME)); - } else { - return null; + return null; + case GREATER_THAN: + if (isTimeColumn(operands.get(0))) { + return getInterval(toMillisSinceEpoch(operands.get(1)) + 1, MAX_END_TIME); } - } - case GREATER_THAN_OR_EQUAL: { - Identifier identifier = operands.get(0).getIdentifier(); - if (identifier != null && identifier.getName().equals(_timeColumn)) { - long timeStamp = _timeFormatSpec.fromFormatToMillis(operands.get(1).getLiteral().getFieldValue().toString()); - return Collections.singletonList(new Interval(timeStamp, MAX_END_TIME)); - } else { - return null; + return null; + case GREATER_THAN_OR_EQUAL: + if (isTimeColumn(operands.get(0))) { + return getInterval(toMillisSinceEpoch(operands.get(1)), MAX_END_TIME); } - } - case LESS_THAN: { - Identifier identifier = operands.get(0).getIdentifier(); - if (identifier != null && identifier.getName().equals(_timeColumn)) { - long timeStamp = _timeFormatSpec.fromFormatToMillis(operands.get(1).getLiteral().getFieldValue().toString()); - if (timeStamp > MIN_START_TIME) { - return Collections.singletonList(new Interval(MIN_START_TIME, timeStamp - 1)); - } else { - return Collections.emptyList(); - } - } else { - return null; + return null; + case LESS_THAN: + if (isTimeColumn(operands.get(0))) { + return getInterval(MIN_START_TIME, toMillisSinceEpoch(operands.get(1)) - 1); } - } - case LESS_THAN_OR_EQUAL: { - Identifier identifier = operands.get(0).getIdentifier(); - if (identifier != null && identifier.getName().equals(_timeColumn)) { - long timeStamp = _timeFormatSpec.fromFormatToMillis(operands.get(1).getLiteral().getFieldValue().toString()); - if (timeStamp >= MIN_START_TIME) { - return Collections.singletonList(new Interval(MIN_START_TIME, timeStamp)); - } else { - return Collections.emptyList(); - } - } else { - return null; + return null; + case LESS_THAN_OR_EQUAL: + if (isTimeColumn(operands.get(0))) { + return getInterval(MIN_START_TIME, toMillisSinceEpoch(operands.get(1))); } - } - case BETWEEN: { - Identifier identifier = operands.get(0).getIdentifier(); - if (identifier != null && identifier.getName().equals(_timeColumn)) { - long startTimestamp = - _timeFormatSpec.fromFormatToMillis(operands.get(1).getLiteral().getFieldValue().toString()); - long endTimestamp = - _timeFormatSpec.fromFormatToMillis(operands.get(2).getLiteral().getFieldValue().toString()); - if (endTimestamp >= startTimestamp) { - return Collections.singletonList(new Interval(startTimestamp, endTimestamp)); - } else { - return Collections.emptyList(); - } - } else { - return null; + return null; + case BETWEEN: + if (isTimeColumn(operands.get(0))) { + return getInterval(toMillisSinceEpoch(operands.get(1)), toMillisSinceEpoch(operands.get(2))); } - } - case RANGE: { - Identifier identifier = operands.get(0).getIdentifier(); - if (identifier != null && identifier.getName().equals(_timeColumn)) { - return parseInterval(operands.get(1).getLiteral().getFieldValue().toString()); + return null; + case RANGE: + if (isTimeColumn(operands.get(0))) { + return parseInterval(operands.get(1).getLiteral().getStringValue()); } return null; - } default: return null; } @@ -433,6 +367,32 @@ private List getComplementSortedIntervals(List intervals) { return res; } + private boolean isTimeColumn(Expression expression) { + Identifier identifier = expression.getIdentifier(); + return identifier != null && identifier.getName().equals(_timeColumn); + } + + private long toMillisSinceEpoch(Expression expression) { + Literal literal = expression.getLiteral(); + Preconditions.checkArgument(literal != null, "Literal is required for time column filter, got: %s", expression); + String value; + Literal._Fields type = literal.getSetField(); + switch (type) { + case INT_VALUE: + value = Integer.toString(literal.getIntValue()); + break; + case LONG_VALUE: + value = Long.toString(literal.getLongValue()); + break; + case STRING_VALUE: + value = literal.getStringValue(); + break; + default: + throw new IllegalStateException("Unsupported literal type: " + type + " as time column filter"); + } + return _timeFormatSpec.fromFormatToMillis(value); + } + /** * Parse interval to millisecond as [min, max] with both sides included. * E.g. '(* 16311]' is parsed as [0, 16311], '(1455 16311)' is parsed as [1456, 16310] @@ -457,10 +417,10 @@ private List parseInterval(String rangeString) { endTime--; } } + return getInterval(startTime, endTime); + } - if (startTime > endTime) { - return Collections.emptyList(); - } - return Collections.singletonList(new Interval(startTime, endTime)); + private static List getInterval(long inclusiveStart, long inclusiveEnd) { + return inclusiveStart <= inclusiveEnd ? List.of(new Interval(inclusiveStart, inclusiveEnd)) : List.of(); } } diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentselector/DefaultSegmentSelector.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentselector/DefaultSegmentSelector.java new file mode 100644 index 000000000000..069ce53da375 --- /dev/null +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentselector/DefaultSegmentSelector.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.routing.segmentselector; + +import java.util.Collections; +import java.util.Set; +import org.apache.helix.model.ExternalView; +import org.apache.helix.model.IdealState; +import org.apache.pinot.common.request.BrokerRequest; + + +public class DefaultSegmentSelector implements SegmentSelector { + private volatile Set _segments; + + @Override + public void init(IdealState idealState, ExternalView externalView, Set onlineSegments) { + _segments = Collections.unmodifiableSet(onlineSegments); + } + + @Override + public void onAssignmentChange(IdealState idealState, ExternalView externalView, Set onlineSegments) { + _segments = Collections.unmodifiableSet(onlineSegments); + } + + @Override + public Set select(BrokerRequest brokerRequest) { + return _segments; + } +} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentselector/OfflineSegmentSelector.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentselector/OfflineSegmentSelector.java deleted file mode 100644 index 32f1cd963d57..000000000000 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentselector/OfflineSegmentSelector.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.broker.routing.segmentselector; - -import java.util.Collections; -import java.util.Set; -import org.apache.helix.model.ExternalView; -import org.apache.helix.model.IdealState; -import org.apache.pinot.common.request.BrokerRequest; - - -/** - * Segment selector for offline table. - */ -public class OfflineSegmentSelector implements SegmentSelector { - private volatile Set _segments; - - @Override - public void init(IdealState idealState, ExternalView externalView, Set onlineSegments) { - onAssignmentChange(idealState, externalView, onlineSegments); - } - - @Override - public void onAssignmentChange(IdealState idealState, ExternalView externalView, Set onlineSegments) { - // TODO: for new added segments, before all replicas are up, consider not selecting them to avoid causing - // hotspot servers - - _segments = Collections.unmodifiableSet(onlineSegments); - } - - @Override - public Set select(BrokerRequest brokerRequest) { - return _segments; - } -} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentselector/RealtimeSegmentSelector.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentselector/RealtimeSegmentSelector.java deleted file mode 100644 index 72d44ffd8109..000000000000 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentselector/RealtimeSegmentSelector.java +++ /dev/null @@ -1,163 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.broker.routing.segmentselector; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.concurrent.atomic.AtomicLong; -import org.apache.helix.model.ExternalView; -import org.apache.helix.model.IdealState; -import org.apache.pinot.common.request.BrokerRequest; -import org.apache.pinot.common.utils.HLCSegmentName; -import org.apache.pinot.common.utils.LLCSegmentName; -import org.apache.pinot.common.utils.SegmentName; -import org.apache.pinot.common.utils.config.QueryOptionsUtils; -import org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel; - - -/** - * Segment selector for real-time table which handles the following scenarios: - *

    - *
  • When HLC and LLC segments coexist (during LLC migration), select only HLC segments or LLC segments
  • - *
  • For HLC segments, only select segments in one group
  • - *
  • - * For LLC segments, only select the first CONSUMING segment for each partition to avoid duplicate data because in - * certain unlikely degenerate scenarios, we can consume overlapping data until segments are flushed (at which point - * the overlapping data is discarded during the reconciliation process with the controller). - *
  • - *
- */ -public class RealtimeSegmentSelector implements SegmentSelector { - private final AtomicLong _requestId = new AtomicLong(); - private volatile List> _hlcSegments; - private volatile Set _llcSegments; - - @Override - public void init(IdealState idealState, ExternalView externalView, Set onlineSegments) { - onAssignmentChange(idealState, externalView, onlineSegments); - } - - @Override - public void onAssignmentChange(IdealState idealState, ExternalView externalView, Set onlineSegments) { - // Group HLC segments by their group id - // NOTE: Use TreeMap so that group ids are sorted and the result is deterministic - Map> groupIdToHLCSegmentsMap = new TreeMap<>(); - - List completedLLCSegments = new ArrayList<>(); - // Store the first CONSUMING segment for each partition - Map partitionIdToFirstConsumingLLCSegmentMap = new HashMap<>(); - - // Iterate over the external view instead of the online segments so that the map lookups are performed on the - // HashSet instead of the TreeSet for performance. For LLC segments, we need the external view to figure out whether - // the segments are in CONSUMING state. For the goal of segment selector, we should not exclude segments not in the - // external view, but it is okay to exclude them as there is no way to route them without instance states in - // external view. - // - New added segment might only exist in ideal state - // - New removed segment might only exist in external view - for (Map.Entry> entry : externalView.getRecord().getMapFields().entrySet()) { - String segment = entry.getKey(); - if (!onlineSegments.contains(segment)) { - continue; - } - - // TODO: for new added segments, before all replicas are up, consider not selecting them to avoid causing - // hotspot servers - - Map instanceStateMap = entry.getValue(); - if (SegmentName.isHighLevelConsumerSegmentName(segment)) { - HLCSegmentName hlcSegmentName = new HLCSegmentName(segment); - groupIdToHLCSegmentsMap.computeIfAbsent(hlcSegmentName.getGroupId(), k -> new HashSet<>()).add(segment); - } else { - if (instanceStateMap.containsValue(SegmentStateModel.CONSUMING)) { - // Keep the first CONSUMING segment for each partition - LLCSegmentName llcSegmentName = new LLCSegmentName(segment); - partitionIdToFirstConsumingLLCSegmentMap - .compute(llcSegmentName.getPartitionGroupId(), (k, consumingSegment) -> { - if (consumingSegment == null) { - return llcSegmentName; - } else { - if (llcSegmentName.getSequenceNumber() < consumingSegment.getSequenceNumber()) { - return llcSegmentName; - } else { - return consumingSegment; - } - } - }); - } else { - completedLLCSegments.add(segment); - } - } - } - - int numHLCGroups = groupIdToHLCSegmentsMap.size(); - if (numHLCGroups != 0) { - List> hlcSegments = new ArrayList<>(numHLCGroups); - for (Set hlcSegmentsForGroup : groupIdToHLCSegmentsMap.values()) { - hlcSegments.add(Collections.unmodifiableSet(hlcSegmentsForGroup)); - } - _hlcSegments = hlcSegments; - } else { - _hlcSegments = null; - } - - if (!completedLLCSegments.isEmpty() || !partitionIdToFirstConsumingLLCSegmentMap.isEmpty()) { - Set llcSegments = - new HashSet<>(completedLLCSegments.size() + partitionIdToFirstConsumingLLCSegmentMap.size()); - llcSegments.addAll(completedLLCSegments); - for (LLCSegmentName llcSegmentName : partitionIdToFirstConsumingLLCSegmentMap.values()) { - llcSegments.add(llcSegmentName.getSegmentName()); - } - _llcSegments = Collections.unmodifiableSet(llcSegments); - } else { - _llcSegments = null; - } - } - - @Override - public Set select(BrokerRequest brokerRequest) { - if (_hlcSegments == null && _llcSegments == null) { - return Collections.emptySet(); - } - if (_hlcSegments == null) { - return selectLLCSegments(); - } - if (_llcSegments == null) { - return selectHLCSegments(); - } - - // Handle HLC and LLC coexisting scenario, select HLC segments only if it is forced in the routing options - return QueryOptionsUtils.isRoutingForceHLC(brokerRequest.getPinotQuery().getQueryOptions()) ? selectHLCSegments() - : selectLLCSegments(); - } - - private Set selectHLCSegments() { - List> hlcSegments = _hlcSegments; - return hlcSegments.get((int) (_requestId.getAndIncrement() % hlcSegments.size())); - } - - private Set selectLLCSegments() { - return _llcSegments; - } -} diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentselector/SegmentSelector.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentselector/SegmentSelector.java index 5c12bb961fad..76c0ba3b378f 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentselector/SegmentSelector.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentselector/SegmentSelector.java @@ -28,18 +28,6 @@ /** * The segment selector selects the segments for the query. The segments selected should cover the whole dataset (table) * without overlap. - *

Segment selector examples: - *

    - *
  • - * For real-time table, when HLC and LLC segments coexist (during LLC migration), select only HLC segments or LLC - * segments - *
  • - *
  • For HLC real-time table, select segments in one group
  • - *
  • - * For table with segment merge/rollup enabled, select the merged segments over the original segments with the same - * data - *
  • - *
*/ public interface SegmentSelector { diff --git a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentselector/SegmentSelectorFactory.java b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentselector/SegmentSelectorFactory.java index 4085a54715cc..f6bd0ce24bef 100644 --- a/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentselector/SegmentSelectorFactory.java +++ b/pinot-broker/src/main/java/org/apache/pinot/broker/routing/segmentselector/SegmentSelectorFactory.java @@ -19,7 +19,6 @@ package org.apache.pinot.broker.routing.segmentselector; import org.apache.pinot.spi.config.table.TableConfig; -import org.apache.pinot.spi.config.table.TableType; public class SegmentSelectorFactory { @@ -27,10 +26,6 @@ private SegmentSelectorFactory() { } public static SegmentSelector getSegmentSelector(TableConfig tableConfig) { - if (tableConfig.getTableType() == TableType.OFFLINE) { - return new OfflineSegmentSelector(); - } else { - return new RealtimeSegmentSelector(); - } + return new DefaultSegmentSelector(); } } diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/api/AccessControlBackwardCompatibleTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/api/AccessControlBackwardCompatibleTest.java new file mode 100644 index 000000000000..ad781c9f885c --- /dev/null +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/api/AccessControlBackwardCompatibleTest.java @@ -0,0 +1,97 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.api; + +import java.util.Set; +import org.apache.pinot.common.request.BrokerRequest; +import org.apache.pinot.spi.auth.AuthorizationResult; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; + + +public class AccessControlBackwardCompatibleTest { + + @Test + public void testBackwardCompatibleHasAccessBrokerRequest() { + AccessControl accessControl = new AllFalseAccessControlImpl(); + HttpRequesterIdentity identity = new HttpRequesterIdentity(); + BrokerRequest request = new BrokerRequest(); + assertFalse(accessControl.authorize(identity, request).hasAccess()); + } + + @Test + public void testBackwardCompatibleHasAccessMutliTable() { + AccessControl accessControl = new AllFalseAccessControlImpl(); + HttpRequesterIdentity identity = new HttpRequesterIdentity(); + Set tables = Set.of("table1", "table2"); + AuthorizationResult result = accessControl.authorize(identity, tables); + assertFalse(result.hasAccess()); + assertEquals(result.getFailureMessage(), "Authorization Failed for tables: [table1, table2]"); + } + + @Test(expectedExceptions = UnsupportedOperationException.class) + public void testExceptionForNoImplAccessControlMultiTable() { + AccessControl accessControl = new NoImplAccessControl(); + HttpRequesterIdentity identity = new HttpRequesterIdentity(); + Set tables = Set.of("table1", "table2"); + accessControl.hasAccess(identity, tables); + } + + @Test(expectedExceptions = UnsupportedOperationException.class) + public void testExceptionForNoImplAccessControlBrokerRequest() { + AccessControl accessControl = new NoImplAccessControl(); + HttpRequesterIdentity identity = new HttpRequesterIdentity(); + BrokerRequest request = new BrokerRequest(); + accessControl.hasAccess(identity, request); + } + + @Test(expectedExceptions = UnsupportedOperationException.class) + public void testExceptionForNoImplAccessControlAuthorizeMultiTable() { + AccessControl accessControl = new NoImplAccessControl(); + HttpRequesterIdentity identity = new HttpRequesterIdentity(); + Set tables = Set.of("table1", "table2"); + accessControl.authorize(identity, tables); + } + + @Test(expectedExceptions = UnsupportedOperationException.class) + public void testExceptionForNoImplAccessControlAuthorizeBrokerRequest() { + AccessControl accessControl = new NoImplAccessControl(); + HttpRequesterIdentity identity = new HttpRequesterIdentity(); + BrokerRequest request = new BrokerRequest(); + accessControl.authorize(identity, request); + } + + class AllFalseAccessControlImpl implements AccessControl { + + @Override + public boolean hasAccess(RequesterIdentity requesterIdentity, BrokerRequest brokerRequest) { + return false; + } + + @Override + public boolean hasAccess(RequesterIdentity requesterIdentity, Set tables) { + return false; + } + } + + class NoImplAccessControl implements AccessControl { + } +} diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/api/resources/BrokerCommonExceptionMapperTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/api/resources/BrokerCommonExceptionMapperTest.java new file mode 100644 index 000000000000..f379f6361f6e --- /dev/null +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/api/resources/BrokerCommonExceptionMapperTest.java @@ -0,0 +1,77 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.api.resources; + +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.Response; +import org.apache.pinot.common.utils.SimpleHttpErrorInfo; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; + + +public class BrokerCommonExceptionMapperTest { + + private BrokerCommonExceptionMapper _exceptionMapper; + + @Mock + private WebApplicationException _webApplicationException; + + @BeforeMethod + public void setUp() { + MockitoAnnotations.openMocks(this); + _exceptionMapper = new BrokerCommonExceptionMapper(); + } + + @Test + public void testToResponseWithWebApplicationException() { + // Arrange + int status = 404; + when(_webApplicationException.getResponse()).thenReturn(Response.status(status).build()); + when(_webApplicationException.getMessage()).thenReturn("Not Found"); + + // Act + Response response = _exceptionMapper.toResponse(_webApplicationException); + + // Assert + assertEquals(response.getStatus(), status); + SimpleHttpErrorInfo errorInfo = (SimpleHttpErrorInfo) response.getEntity(); + assertEquals(errorInfo.getCode(), status); + assertEquals(errorInfo.getError(), "Not Found"); + } + + @Test + public void testToResponseWithGenericException() { + // Arrange + Throwable throwable = new RuntimeException("Internal Server Error"); + + // Act + Response response = _exceptionMapper.toResponse(throwable); + + // Assert + assertEquals(response.getStatus(), 500); + SimpleHttpErrorInfo errorInfo = (SimpleHttpErrorInfo) response.getEntity(); + assertEquals(errorInfo.getCode(), 500); + assertEquals(errorInfo.getError(), "Internal Server Error"); + } +} diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/api/resources/PinotClientRequestTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/api/resources/PinotClientRequestTest.java new file mode 100644 index 000000000000..e79b20c57b71 --- /dev/null +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/api/resources/PinotClientRequestTest.java @@ -0,0 +1,54 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.api.resources; + +import javax.ws.rs.core.Response; +import org.apache.pinot.common.response.BrokerResponse; +import org.apache.pinot.common.response.broker.BrokerResponseNative; +import org.testng.Assert; +import org.testng.annotations.Test; + +import static org.apache.pinot.common.exception.QueryException.TABLE_DOES_NOT_EXIST_ERROR_CODE; +import static org.apache.pinot.spi.utils.CommonConstants.Controller.PINOT_QUERY_ERROR_CODE_HEADER; + + +public class PinotClientRequestTest { + + @Test + public void testGetPinotQueryResponse() + throws Exception { + + // for successful query result the 'X-Pinot-Error-Code' should be -1 + BrokerResponse emptyResultBrokerResponse = BrokerResponseNative.EMPTY_RESULT; + Response successfulResponse = PinotClientRequest.getPinotQueryResponse(emptyResultBrokerResponse); + Assert.assertEquals(successfulResponse.getStatus(), Response.Status.OK.getStatusCode()); + Assert.assertTrue(successfulResponse.getHeaders().containsKey(PINOT_QUERY_ERROR_CODE_HEADER)); + Assert.assertEquals(successfulResponse.getHeaders().get(PINOT_QUERY_ERROR_CODE_HEADER).size(), 1); + Assert.assertEquals(successfulResponse.getHeaders().get(PINOT_QUERY_ERROR_CODE_HEADER).get(0), -1); + + // for failed query result the 'X-Pinot-Error-Code' should be Error code fo exception. + BrokerResponse tableDoesNotExistBrokerResponse = BrokerResponseNative.TABLE_DOES_NOT_EXIST; + Response tableDoesNotExistResponse = PinotClientRequest.getPinotQueryResponse(tableDoesNotExistBrokerResponse); + Assert.assertEquals(tableDoesNotExistResponse.getStatus(), Response.Status.OK.getStatusCode()); + Assert.assertTrue(tableDoesNotExistResponse.getHeaders().containsKey(PINOT_QUERY_ERROR_CODE_HEADER)); + Assert.assertEquals(tableDoesNotExistResponse.getHeaders().get(PINOT_QUERY_ERROR_CODE_HEADER).size(), 1); + Assert.assertEquals(tableDoesNotExistResponse.getHeaders().get(PINOT_QUERY_ERROR_CODE_HEADER).get(0), + TABLE_DOES_NOT_EXIST_ERROR_CODE); + } +} diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/broker/BasicAuthAccessControlTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/broker/BasicAuthAccessControlTest.java index f9e60f479192..5eb444431aaa 100644 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/broker/BasicAuthAccessControlTest.java +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/broker/BasicAuthAccessControlTest.java @@ -21,11 +21,15 @@ import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; +import javax.ws.rs.WebApplicationException; import org.apache.pinot.broker.api.AccessControl; import org.apache.pinot.broker.api.HttpRequesterIdentity; import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.common.request.QuerySource; +import org.apache.pinot.spi.auth.AuthorizationResult; import org.apache.pinot.spi.env.PinotConfiguration; import org.testng.Assert; import org.testng.annotations.BeforeClass; @@ -40,13 +44,20 @@ public class BasicAuthAccessControlTest { private AccessControl _accessControl; + Set _tableNames; + @BeforeClass public void setup() { Map config = new HashMap<>(); config.put("principals", "admin,user"); config.put("principals.admin.password", "verysecret"); config.put("principals.user.password", "secret"); - config.put("principals.user.tables", "lessImportantStuff"); + config.put("principals.user.tables", "lessImportantStuff,lesserImportantStuff,leastImportantStuff"); + + _tableNames = new HashSet<>(); + _tableNames.add("lessImportantStuff"); + _tableNames.add("lesserImportantStuff"); + _tableNames.add("leastImportantStuff"); AccessControlFactory factory = new BasicAuthAccessControlFactory(); factory.init(new PinotConfiguration(config)); @@ -56,7 +67,7 @@ public void setup() { @Test(expectedExceptions = IllegalArgumentException.class) public void testNullEntity() { - _accessControl.hasAccess(null, null); + _accessControl.authorize(null, (BrokerRequest) null); } @Test @@ -66,7 +77,11 @@ public void testNullToken() { HttpRequesterIdentity identity = new HttpRequesterIdentity(); identity.setHttpHeaders(headers); - Assert.assertFalse(_accessControl.hasAccess(identity, null)); + try { + _accessControl.authorize(identity, (BrokerRequest) null); + } catch (WebApplicationException e) { + Assert.assertEquals(e.getResponse().getStatus(), 401, "must return 401"); + } } @Test @@ -83,7 +98,8 @@ public void testAllow() { BrokerRequest request = new BrokerRequest(); request.setQuerySource(source); - Assert.assertTrue(_accessControl.hasAccess(identity, request)); + Assert.assertTrue(_accessControl.authorize(identity, request).hasAccess()); + Assert.assertTrue(_accessControl.authorize(identity, _tableNames).hasAccess()); } @Test @@ -99,8 +115,27 @@ public void testDeny() { BrokerRequest request = new BrokerRequest(); request.setQuerySource(source); - - Assert.assertFalse(_accessControl.hasAccess(identity, request)); + AuthorizationResult authorizationResult = _accessControl.authorize(identity, request); + Assert.assertFalse(authorizationResult.hasAccess()); + Assert.assertEquals(authorizationResult.getFailureMessage(), + "Authorization Failed for tables: [veryImportantStuff]"); + + Set tableNames = new HashSet<>(); + tableNames.add("veryImportantStuff"); + authorizationResult = _accessControl.authorize(identity, tableNames); + Assert.assertFalse(authorizationResult.hasAccess()); + Assert.assertEquals(authorizationResult.getFailureMessage(), + "Authorization Failed for tables: [veryImportantStuff]"); + tableNames.add("lessImportantStuff"); + authorizationResult = _accessControl.authorize(identity, tableNames); + Assert.assertFalse(authorizationResult.hasAccess()); + Assert.assertEquals(authorizationResult.getFailureMessage(), + "Authorization Failed for tables: [veryImportantStuff]"); + tableNames.add("lesserImportantStuff"); + authorizationResult = _accessControl.authorize(identity, tableNames); + Assert.assertFalse(authorizationResult.hasAccess()); + Assert.assertEquals(authorizationResult.getFailureMessage(), + "Authorization Failed for tables: [veryImportantStuff]"); } @Test @@ -116,8 +151,18 @@ public void testAllowAll() { BrokerRequest request = new BrokerRequest(); request.setQuerySource(source); - - Assert.assertTrue(_accessControl.hasAccess(identity, request)); + AuthorizationResult authorizationResult = _accessControl.authorize(identity, request); + Assert.assertTrue(authorizationResult.hasAccess()); + Assert.assertEquals(authorizationResult.getFailureMessage(), ""); + + Set tableNames = new HashSet<>(); + tableNames.add("lessImportantStuff"); + tableNames.add("veryImportantStuff"); + tableNames.add("lesserImportantStuff"); + + authorizationResult = _accessControl.authorize(identity, tableNames); + Assert.assertTrue(authorizationResult.hasAccess()); + Assert.assertEquals(authorizationResult.getFailureMessage(), ""); } @Test @@ -129,8 +174,12 @@ public void testAllowNonTable() { identity.setHttpHeaders(headers); BrokerRequest request = new BrokerRequest(); + AuthorizationResult authorizationResult = _accessControl.authorize(identity, request); + Assert.assertTrue(authorizationResult.hasAccess()); - Assert.assertTrue(_accessControl.hasAccess(identity, request)); + Set tableNames = new HashSet<>(); + authorizationResult = _accessControl.authorize(identity, tableNames); + Assert.assertTrue(authorizationResult.hasAccess()); } @Test @@ -147,6 +196,7 @@ public void testNormalizeToken() { BrokerRequest request = new BrokerRequest(); request.setQuerySource(source); - Assert.assertTrue(_accessControl.hasAccess(identity, request)); + Assert.assertTrue(_accessControl.authorize(identity, request).hasAccess()); + Assert.assertTrue(_accessControl.authorize(identity, _tableNames).hasAccess()); } } diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/broker/BrokerManagedAsyncExecutorProviderTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/broker/BrokerManagedAsyncExecutorProviderTest.java new file mode 100644 index 000000000000..2cab3985d5ea --- /dev/null +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/broker/BrokerManagedAsyncExecutorProviderTest.java @@ -0,0 +1,154 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.broker; + +import java.util.Collections; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import javax.ws.rs.ServiceUnavailableException; +import org.apache.pinot.common.metrics.BrokerMetrics; +import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.metrics.PinotMetricUtils; +import org.apache.pinot.spi.utils.CommonConstants; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + + +public class BrokerManagedAsyncExecutorProviderTest { + + public static BrokerMetrics _brokerMetrics; + + @BeforeClass + public void setUp() { + _brokerMetrics = new BrokerMetrics(CommonConstants.Broker.DEFAULT_METRICS_NAME_PREFIX, + PinotMetricUtils.getPinotMetricsRegistry(new PinotConfiguration()), + CommonConstants.Broker.DEFAULT_ENABLE_TABLE_LEVEL_METRICS, Collections.emptyList()); + } + + @Test + public void testExecutorService() + throws InterruptedException, ExecutionException { + // create a new instance of the executor provider + BrokerManagedAsyncExecutorProvider provider = new BrokerManagedAsyncExecutorProvider(2, 2, 2, _brokerMetrics); + + // get the executor service + ThreadPoolExecutor executor = (ThreadPoolExecutor) provider.getExecutorService(); + + // submit a task to the executor service and wait for it to complete + Future futureResult = executor.submit(() -> 1 + 1); + Integer result = futureResult.get(); + + // verify that the task was executed and returned the expected result + assertNotNull(result); + assertEquals((int) result, 2); + + // wait for the executor service to shutdown + executor.shutdown(); + executor.awaitTermination(1, TimeUnit.SECONDS); + } + + @Test + public void testGet() + throws InterruptedException { + + // verify executor has correct properties when queue size is Integer.MAX_VALUE + BrokerManagedAsyncExecutorProvider provider = + new BrokerManagedAsyncExecutorProvider(1, 1, Integer.MAX_VALUE, _brokerMetrics); + ExecutorService executorService = provider.getExecutorService(); + assertNotNull(executorService); + assertTrue(executorService instanceof ThreadPoolExecutor); + + ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executorService; + + assertEquals(threadPoolExecutor.getCorePoolSize(), 1); + assertEquals(threadPoolExecutor.getMaximumPoolSize(), 1); + + BlockingQueue blockingQueue = threadPoolExecutor.getQueue(); + assertNotNull(blockingQueue); + assertTrue(blockingQueue instanceof LinkedBlockingQueue); + assertEquals(blockingQueue.size(), 0); + assertEquals(blockingQueue.remainingCapacity(), Integer.MAX_VALUE); + + + // verify that the executor has the expected properties when queue size is 1 + provider = new BrokerManagedAsyncExecutorProvider(1, 1, 1, _brokerMetrics); + executorService = provider.getExecutorService(); + assertNotNull(executorService); + assertTrue(executorService instanceof ThreadPoolExecutor); + + threadPoolExecutor = (ThreadPoolExecutor) executorService; + + assertEquals(threadPoolExecutor.getCorePoolSize(), 1); + assertEquals(threadPoolExecutor.getMaximumPoolSize(), 1); + + blockingQueue = threadPoolExecutor.getQueue(); + assertNotNull(blockingQueue); + assertTrue(blockingQueue instanceof ArrayBlockingQueue); + assertEquals(blockingQueue.size(), 0); + assertEquals(blockingQueue.remainingCapacity(), 1); + + RejectedExecutionHandler rejectedExecutionHandler = threadPoolExecutor.getRejectedExecutionHandler(); + assertNotNull(rejectedExecutionHandler); + assertTrue( + rejectedExecutionHandler instanceof BrokerManagedAsyncExecutorProvider.BrokerThreadPoolRejectExecutionHandler); + + // test that the executor actually executes tasks + AtomicInteger counter = new AtomicInteger(); + CountDownLatch latch = new CountDownLatch(1); + for (int i = 0; i < 1; i++) { + threadPoolExecutor.execute(() -> { + counter.incrementAndGet(); + latch.countDown(); + }); + } + latch.await(); + assertEquals(counter.get(), 1); + } + + @Test(expectedExceptions = ServiceUnavailableException.class) + public void testRejectHandler() + throws InterruptedException { + BrokerManagedAsyncExecutorProvider provider = new BrokerManagedAsyncExecutorProvider(1, 1, 1, _brokerMetrics); + ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) provider.getExecutorService(); + + // test the rejection policy + AtomicInteger counter = new AtomicInteger(); + CountDownLatch latch = new CountDownLatch(10); + for (int i = 0; i < 10; i++) { + threadPoolExecutor.execute(() -> { + counter.incrementAndGet(); + latch.countDown(); + }); + } + latch.await(); + } +} diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/broker/HelixBrokerStarterTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/broker/HelixBrokerStarterTest.java index 53e75d055712..b08feaf87822 100644 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/broker/HelixBrokerStarterTest.java +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/broker/HelixBrokerStarterTest.java @@ -95,7 +95,8 @@ public void setUp() _helixResourceManager.addTable(offlineTableConfig); TableConfig realtimeTimeConfig = new TableConfigBuilder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setTimeColumnName(TIME_COLUMN_NAME) - .setTimeType(TimeUnit.DAYS.name()).setStreamConfigs(getStreamConfigs()).build(); + .setTimeType(TimeUnit.DAYS.name()).setStreamConfigs(getStreamConfigs()).setNumReplicas(1) + .build(); _helixResourceManager.addTable(realtimeTimeConfig); for (int i = 0; i < NUM_OFFLINE_SEGMENTS; i++) { @@ -104,20 +105,26 @@ public void setUp() } TestUtils.waitForCondition(aVoid -> { + // should wait for both realtime and offline table external view to be live. ExternalView offlineTableExternalView = _helixAdmin.getResourceExternalView(getHelixClusterName(), OFFLINE_TABLE_NAME); + ExternalView realtimeTableExternalView = + _helixAdmin.getResourceExternalView(getHelixClusterName(), REALTIME_TABLE_NAME); return offlineTableExternalView != null - && offlineTableExternalView.getPartitionSet().size() == NUM_OFFLINE_SEGMENTS; + && offlineTableExternalView.getPartitionSet().size() == NUM_OFFLINE_SEGMENTS + && realtimeTableExternalView != null; }, 30_000L, "Failed to find all OFFLINE segments in the ExternalView"); } private Map getStreamConfigs() { Map streamConfigs = new HashMap<>(); streamConfigs.put("streamType", "kafka"); - streamConfigs.put("stream.kafka.consumer.type", "highLevel"); + streamConfigs.put("stream.kafka.consumer.type", "lowlevel"); streamConfigs.put("stream.kafka.topic.name", "kafkaTopic"); streamConfigs.put("stream.kafka.decoder.class.name", "org.apache.pinot.plugin.stream.kafka.KafkaAvroMessageDecoder"); + streamConfigs.put("stream.kafka.consumer.factory.class.name", + "org.apache.pinot.core.realtime.impl.fakestream.FakeStreamConsumerFactory"); return streamConfigs; } @@ -156,7 +163,8 @@ public void testResourceAndTagAssignment() RoutingTable routingTable = routingManager.getRoutingTable(brokerRequest, 0); assertNotNull(routingTable); assertEquals(routingTable.getServerInstanceToSegmentsMap().size(), NUM_SERVERS); - assertEquals(routingTable.getServerInstanceToSegmentsMap().values().iterator().next().size(), NUM_OFFLINE_SEGMENTS); + assertEquals(routingTable.getServerInstanceToSegmentsMap().values().iterator().next().getLeft().size(), + NUM_OFFLINE_SEGMENTS); assertTrue(routingTable.getUnavailableSegments().isEmpty()); // Add a new segment into the OFFLINE table @@ -164,9 +172,9 @@ public void testResourceAndTagAssignment() SegmentMetadataMockUtils.mockSegmentMetadata(RAW_TABLE_NAME), "downloadUrl"); TestUtils.waitForCondition(aVoid -> - routingManager.getRoutingTable(brokerRequest, 0).getServerInstanceToSegmentsMap() - .values().iterator().next().size() == NUM_OFFLINE_SEGMENTS + 1, 30_000L, "Failed to add the new segment " - + "into the routing table"); + routingManager.getRoutingTable(brokerRequest, 0).getServerInstanceToSegmentsMap().values().iterator().next() + .getLeft().size() == NUM_OFFLINE_SEGMENTS + 1, 30_000L, + "Failed to add the new segment " + "into the routing table"); // Add a new table with different broker tenant String newRawTableName = "newTable"; @@ -181,6 +189,7 @@ public void testResourceAndTagAssignment() } // Add a new table with same broker tenant + addDummySchema(newRawTableName); newTableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(newRawTableName) .setServerTenant(TagNameUtils.DEFAULT_TENANT_NAME).build(); _helixResourceManager.addTable(newTableConfig); diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/failuredetector/ConnectionFailureDetectorTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/failuredetector/ConnectionFailureDetectorTest.java index 52c00884d5f0..859b834f0ce6 100644 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/failuredetector/ConnectionFailureDetectorTest.java +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/failuredetector/ConnectionFailureDetectorTest.java @@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicInteger; import org.apache.pinot.common.metrics.BrokerGauge; import org.apache.pinot.common.metrics.BrokerMetrics; +import org.apache.pinot.common.metrics.MetricValueUtils; import org.apache.pinot.core.transport.QueryResponse; import org.apache.pinot.core.transport.ServerRoutingInstance; import org.apache.pinot.spi.config.table.TableType; @@ -96,14 +97,14 @@ public void testRetry() { int numRetries = _listener._retryUnhealthyServerCalled.get(); if (numRetries < Broker.FailureDetector.DEFAULT_MAX_RETIRES) { assertEquals(_failureDetector.getUnhealthyServers(), Collections.singleton(INSTANCE_ID)); - assertEquals(_brokerMetrics.getValueOfGlobalGauge(BrokerGauge.UNHEALTHY_SERVERS), 1); + assertEquals(MetricValueUtils.getGlobalGaugeValue(_brokerMetrics, BrokerGauge.UNHEALTHY_SERVERS), 1); return false; } assertEquals(numRetries, Broker.FailureDetector.DEFAULT_MAX_RETIRES); // There might be a small delay between the last retry and removing failed server from the unhealthy servers. // Perform a check instead of an assertion. return _failureDetector.getUnhealthyServers().isEmpty() - && _brokerMetrics.getValueOfGlobalGauge(BrokerGauge.UNHEALTHY_SERVERS) == 0 + && MetricValueUtils.getGaugeValue(_brokerMetrics, BrokerGauge.UNHEALTHY_SERVERS.getGaugeName()) == 0 && _listener._notifyUnhealthyServerCalled.get() == 1 && _listener._notifyHealthyServerCalled.get() == 1; }, 5_000L, "Failed to get 10 retires"); @@ -113,7 +114,8 @@ public void testRetry() { private void verify(Set expectedUnhealthyServers, int expectedNotifyUnhealthyServerCalled, int expectedNotifyHealthyServerCalled) { assertEquals(_failureDetector.getUnhealthyServers(), expectedUnhealthyServers); - assertEquals(_brokerMetrics.getValueOfGlobalGauge(BrokerGauge.UNHEALTHY_SERVERS), expectedUnhealthyServers.size()); + assertEquals(MetricValueUtils.getGlobalGaugeValue(_brokerMetrics, BrokerGauge.UNHEALTHY_SERVERS), + expectedUnhealthyServers.size()); assertEquals(_listener._notifyUnhealthyServerCalled.get(), expectedNotifyUnhealthyServerCalled); assertEquals(_listener._notifyHealthyServerCalled.get(), expectedNotifyHealthyServerCalled); } diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/querylog/QueryLoggerTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/querylog/QueryLoggerTest.java index bf173f615b63..1e2d909a45cc 100644 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/querylog/QueryLoggerTest.java +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/querylog/QueryLoggerTest.java @@ -25,10 +25,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; -import java.util.stream.IntStream; import org.apache.pinot.broker.api.RequesterIdentity; -import org.apache.pinot.broker.requesthandler.BaseBrokerRequestHandler; +import org.apache.pinot.broker.requesthandler.BaseSingleStageBrokerRequestHandler.ServerStats; import org.apache.pinot.common.response.ProcessingException; import org.apache.pinot.common.response.broker.BrokerResponseNative; import org.apache.pinot.spi.trace.DefaultRequestContext; @@ -78,7 +76,8 @@ public void setUp() { } @AfterMethod - public void tearDown() throws Exception { + public void tearDown() + throws Exception { _closeMocks.close(); } @@ -94,23 +93,25 @@ public void shouldFormatLogLineProperly() { // Then: Assert.assertEquals(_infoLog.size(), 1); + //@formatter:off Assert.assertEquals(_infoLog.get(0), "requestId=123," + "table=table," + "timeMs=456," + "docs=1/2," + "entries=3/4," + "segments(queried/processed/matched/consumingQueried/consumingProcessed/consumingMatched/unavailable)" - + ":5/6/7/8/9/10/24," + + ":5/6/7/8/9/10/21," + "consumingFreshnessTimeMs=11," + "servers=12/13," + "groupLimitReached=false," - + "brokerReduceTimeMs=22," + + "brokerReduceTimeMs=20," + "exceptions=0," + "serverStats=serverStats," - + "offlineThreadCpuTimeNs(total/thread/sysActivity/resSer):14/15/16/17," - + "realtimeThreadCpuTimeNs(total/thread/sysActivity/resSer):18/19/20/21," + + "offlineThreadCpuTimeNs(total/thread/sysActivity/resSer):45/14/15/16," + + "realtimeThreadCpuTimeNs(total/thread/sysActivity/resSer):54/17/18/19," + "clientIp=ip," + "query=SELECT * FROM foo"); + //@formatter:on } @Test @@ -125,8 +126,7 @@ public void shouldOmitClientId() { // Then: Assert.assertEquals(_infoLog.size(), 1); - Assert.assertFalse( - _infoLog.get(0).contains("clientId"), + Assert.assertFalse(_infoLog.get(0).contains("clientId"), "did not expect to see clientId Logs. Got: " + _infoLog.get(0)); } @@ -191,8 +191,7 @@ public void shouldHandleRaceConditionsWithDroppedQueries() throws InterruptedException { // Given: final CountDownLatch logLatch = new CountDownLatch(1); - Mockito.when(_logRateLimiter.tryAcquire()) - .thenReturn(false) + Mockito.when(_logRateLimiter.tryAcquire()).thenReturn(false) .thenReturn(true) // this one will block when it hits tryAcquire() .thenReturn(false) // this one just increments the dropped logs .thenAnswer(invocation -> { @@ -205,12 +204,11 @@ public void shouldHandleRaceConditionsWithDroppedQueries() // ensure that the tryAcquire only succeeds after three other // logs have went through (see logAndDecrement) final CountDownLatch dropLogLatch = new CountDownLatch(3); - Mockito.when(_droppedRateLimiter.tryAcquire()) - .thenAnswer(invocation -> { - logLatch.countDown(); - dropLogLatch.await(); - return true; - }).thenReturn(true); + Mockito.when(_droppedRateLimiter.tryAcquire()).thenAnswer(invocation -> { + logLatch.countDown(); + dropLogLatch.await(); + return true; + }).thenReturn(true); QueryLogger.QueryLogParams params = generateParams(false, 0, 456); QueryLogger queryLogger = new QueryLogger(_logRateLimiter, 100, true, _logger, _droppedRateLimiter); @@ -240,8 +238,18 @@ public void shouldHandleRaceConditionsWithDroppedQueries() Assert.assertEquals((long) _numDropped.get(0), 2L); } - private QueryLogger.QueryLogParams generateParams(boolean isGroupLimitHit, int numExceptions, long timeUsed) { + private QueryLogger.QueryLogParams generateParams(boolean numGroupsLimitReached, int numExceptions, long timeUsedMs) { + RequestContext requestContext = new DefaultRequestContext(); + requestContext.setRequestId(123); + requestContext.setQuery("SELECT * FROM foo"); + requestContext.setNumUnavailableSegments(21); + BrokerResponseNative response = new BrokerResponseNative(); + response.setNumGroupsLimitReached(numGroupsLimitReached); + for (int i = 0; i < numExceptions; i++) { + response.addException(new ProcessingException()); + } + response.setTimeUsedMs(timeUsedMs); response.setNumDocsScanned(1); response.setTotalDocs(2); response.setNumEntriesScannedInFilter(3); @@ -255,40 +263,24 @@ private QueryLogger.QueryLogParams generateParams(boolean isGroupLimitHit, int n response.setMinConsumingFreshnessTimeMs(11); response.setNumServersResponded(12); response.setNumServersQueried(13); - response.setNumGroupsLimitReached(isGroupLimitHit); - response.setExceptions( - IntStream.range(0, numExceptions) - .mapToObj(i -> new ProcessingException()).collect(Collectors.toList())); - response.setOfflineTotalCpuTimeNs(14); - response.setOfflineThreadCpuTimeNs(15); - response.setOfflineSystemActivitiesCpuTimeNs(16); - response.setOfflineResponseSerializationCpuTimeNs(17); - response.setRealtimeTotalCpuTimeNs(18); - response.setRealtimeThreadCpuTimeNs(19); - response.setRealtimeSystemActivitiesCpuTimeNs(20); - response.setRealtimeResponseSerializationCpuTimeNs(21); - - RequestContext request = new DefaultRequestContext(); - request.setReduceTimeMillis(22); - - BaseBrokerRequestHandler.ServerStats serverStats = new BaseBrokerRequestHandler.ServerStats(); - serverStats.setServerStats("serverStats"); + response.setOfflineThreadCpuTimeNs(14); + response.setOfflineSystemActivitiesCpuTimeNs(15); + response.setOfflineResponseSerializationCpuTimeNs(16); + response.setRealtimeThreadCpuTimeNs(17); + response.setRealtimeSystemActivitiesCpuTimeNs(18); + response.setRealtimeResponseSerializationCpuTimeNs(19); + response.setBrokerReduceTimeMs(20); + RequesterIdentity identity = new RequesterIdentity() { - @Override public String getClientIp() { + @Override + public String getClientIp() { return "ip"; } }; - return new QueryLogger.QueryLogParams( - 123, - "SELECT * FROM foo", - request, - "table", - 24, - serverStats, - response, - timeUsed, - identity - ); + ServerStats serverStats = new ServerStats(); + serverStats.setServerStats("serverStats"); + + return new QueryLogger.QueryLogParams(requestContext, "table", response, identity, serverStats); } } diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/queryquota/HelixExternalViewBasedQueryQuotaManagerTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/queryquota/HelixExternalViewBasedQueryQuotaManagerTest.java index 80021f691c52..a22f8bdb5782 100644 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/queryquota/HelixExternalViewBasedQueryQuotaManagerTest.java +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/queryquota/HelixExternalViewBasedQueryQuotaManagerTest.java @@ -21,7 +21,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.helix.HelixAdmin; import org.apache.helix.HelixManager; import org.apache.helix.InstanceType; @@ -38,7 +38,6 @@ import org.apache.pinot.common.metadata.ZKMetadataProvider; import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.utils.ZkStarter; -import org.apache.pinot.common.utils.config.TableConfigUtils; import org.apache.pinot.spi.config.table.QuotaConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; @@ -147,8 +146,7 @@ public void testOfflineTableNotnullQuota() throws Exception { ExternalView brokerResource = generateBrokerResource(OFFLINE_TABLE_NAME); TableConfig tableConfig = generateDefaultTableConfig(OFFLINE_TABLE_NAME); - ZKMetadataProvider - .setOfflineTableConfig(_testPropertyStore, OFFLINE_TABLE_NAME, TableConfigUtils.toZNRecord(tableConfig)); + ZKMetadataProvider.setTableConfig(_testPropertyStore, tableConfig); setQps(tableConfig); _queryQuotaManager.initOrUpdateTableQueryQuota(tableConfig, brokerResource); Assert.assertEquals(_queryQuotaManager.getRateLimiterMapSize(), 1); @@ -179,8 +177,7 @@ public void testOfflineTableWithNullQuotaButWithRealtimeTableConfigNullQpsConfig new TableConfigBuilder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setQuotaConfig(quotaConfig) .setRetentionTimeUnit("DAYS").setRetentionTimeValue("1").setSegmentPushType("APPEND") .setBrokerTenant("testBroker").setServerTenant("testServer").build(); - ZKMetadataProvider.setRealtimeTableConfig(_testPropertyStore, REALTIME_TABLE_NAME, - TableConfigUtils.toZNRecord(realtimeTableConfig)); + ZKMetadataProvider.setTableConfig(_testPropertyStore, realtimeTableConfig); ExternalView brokerResource = generateBrokerResource(OFFLINE_TABLE_NAME); TableConfig tableConfig = generateDefaultTableConfig(OFFLINE_TABLE_NAME); @@ -202,8 +199,7 @@ public void testOfflineTableWithNullQuotaButWithRealtimeTableConfigNotNullQpsCon new TableConfigBuilder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setQuotaConfig(quotaConfig) .setRetentionTimeUnit("DAYS").setRetentionTimeValue("1").setSegmentPushType("APPEND") .setBrokerTenant("testBroker").setServerTenant("testServer").build(); - ZKMetadataProvider.setRealtimeTableConfig(_testPropertyStore, REALTIME_TABLE_NAME, - TableConfigUtils.toZNRecord(realtimeTableConfig)); + ZKMetadataProvider.setTableConfig(_testPropertyStore, realtimeTableConfig); ExternalView brokerResource = generateBrokerResource(REALTIME_TABLE_NAME); TableConfig tableConfig = generateDefaultTableConfig(OFFLINE_TABLE_NAME); @@ -234,10 +230,8 @@ public void testBothTableHaveQpsQuotaConfig() .setRetentionTimeUnit("DAYS").setRetentionTimeValue("1").setSegmentPushType("APPEND") .setBrokerTenant("testBroker").setServerTenant("testServer").build(); - ZKMetadataProvider.setRealtimeTableConfig(_testPropertyStore, REALTIME_TABLE_NAME, - TableConfigUtils.toZNRecord(realtimeTableConfig)); - ZKMetadataProvider - .setOfflineTableConfig(_testPropertyStore, OFFLINE_TABLE_NAME, TableConfigUtils.toZNRecord(offlineTableConfig)); + ZKMetadataProvider.setTableConfig(_testPropertyStore, realtimeTableConfig); + ZKMetadataProvider.setTableConfig(_testPropertyStore, offlineTableConfig); // Since each table has 2 online brokers, per broker rate becomes 100.0 / 2 = 50.0 _queryQuotaManager.initOrUpdateTableQueryQuota(offlineTableConfig, brokerResource); @@ -263,8 +257,7 @@ public void testRealtimeTableNotnullQuota() throws Exception { ExternalView brokerResource = generateBrokerResource(REALTIME_TABLE_NAME); TableConfig tableConfig = generateDefaultTableConfig(REALTIME_TABLE_NAME); - ZKMetadataProvider - .setRealtimeTableConfig(_testPropertyStore, REALTIME_TABLE_NAME, TableConfigUtils.toZNRecord(tableConfig)); + ZKMetadataProvider.setTableConfig(_testPropertyStore, tableConfig); setQps(tableConfig); _queryQuotaManager.initOrUpdateTableQueryQuota(tableConfig, brokerResource); Assert.assertEquals(_queryQuotaManager.getRateLimiterMapSize(), 1); @@ -280,8 +273,7 @@ public void testRealtimeTableNotnullQuotaWhileTableConfigGetsDeleted() throws Exception { ExternalView brokerResource = generateBrokerResource(REALTIME_TABLE_NAME); TableConfig tableConfig = generateDefaultTableConfig(REALTIME_TABLE_NAME); - ZKMetadataProvider - .setRealtimeTableConfig(_testPropertyStore, REALTIME_TABLE_NAME, TableConfigUtils.toZNRecord(tableConfig)); + ZKMetadataProvider.setTableConfig(_testPropertyStore, tableConfig); setQps(tableConfig); _queryQuotaManager.initOrUpdateTableQueryQuota(tableConfig, brokerResource); Assert.assertEquals(_queryQuotaManager.getRateLimiterMapSize(), 1); @@ -312,8 +304,7 @@ public void testRealtimeTableWithNullQuotaButWithOfflineTableConfigNullQpsConfig new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setQuotaConfig(quotaConfig) .setRetentionTimeUnit("DAYS").setRetentionTimeValue("1").setSegmentPushType("APPEND") .setBrokerTenant("testBroker").setServerTenant("testServer").build(); - ZKMetadataProvider - .setOfflineTableConfig(_testPropertyStore, OFFLINE_TABLE_NAME, TableConfigUtils.toZNRecord(offlineTableConfig)); + ZKMetadataProvider.setTableConfig(_testPropertyStore, offlineTableConfig); ExternalView brokerResource = generateBrokerResource(REALTIME_TABLE_NAME); TableConfig tableConfig = generateDefaultTableConfig(REALTIME_TABLE_NAME); @@ -331,8 +322,7 @@ public void testRealtimeTableWithNullQuotaButWithOfflineTableConfigNotNullQpsCon new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setQuotaConfig(quotaConfig) .setRetentionTimeUnit("DAYS").setRetentionTimeValue("1").setSegmentPushType("APPEND") .setBrokerTenant("testBroker").setServerTenant("testServer").build(); - ZKMetadataProvider - .setOfflineTableConfig(_testPropertyStore, OFFLINE_TABLE_NAME, TableConfigUtils.toZNRecord(offlineTableConfig)); + ZKMetadataProvider.setTableConfig(_testPropertyStore, offlineTableConfig); ExternalView brokerResource = generateBrokerResource(OFFLINE_TABLE_NAME); TableConfig tableConfig = generateDefaultTableConfig(REALTIME_TABLE_NAME); @@ -358,8 +348,7 @@ public void testNoBrokerServiceOnBrokerResource() throws Exception { ExternalView brokerResource = new ExternalView(CommonConstants.Helix.BROKER_RESOURCE_INSTANCE); TableConfig tableConfig = generateDefaultTableConfig(OFFLINE_TABLE_NAME); - ZKMetadataProvider - .setOfflineTableConfig(_testPropertyStore, OFFLINE_TABLE_NAME, TableConfigUtils.toZNRecord(tableConfig)); + ZKMetadataProvider.setTableConfig(_testPropertyStore, tableConfig); setQps(tableConfig); _queryQuotaManager.initOrUpdateTableQueryQuota(tableConfig, brokerResource); Assert.assertEquals(_queryQuotaManager.getRateLimiterMapSize(), 1); @@ -371,8 +360,7 @@ public void testNoOnlineBrokerServiceOnBrokerResource() ExternalView brokerResource = new ExternalView(CommonConstants.Helix.BROKER_RESOURCE_INSTANCE); brokerResource.setState(OFFLINE_TABLE_NAME, "broker_instance_2", "OFFLINE"); TableConfig tableConfig = generateDefaultTableConfig(OFFLINE_TABLE_NAME); - ZKMetadataProvider - .setOfflineTableConfig(_testPropertyStore, OFFLINE_TABLE_NAME, TableConfigUtils.toZNRecord(tableConfig)); + ZKMetadataProvider.setTableConfig(_testPropertyStore, tableConfig); setQps(tableConfig); _queryQuotaManager.initOrUpdateTableQueryQuota(tableConfig, brokerResource); diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/queryquota/MaxHitRateTrackerTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/queryquota/MaxHitRateTrackerTest.java index b4d9cc5f936e..f19c55504dbf 100644 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/queryquota/MaxHitRateTrackerTest.java +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/queryquota/MaxHitRateTrackerTest.java @@ -37,24 +37,29 @@ public void testMaxHitRateTracker() { long latestTimeStamp = currentTimestamp + (timeInSec - 1) * 1000; Assert.assertNotNull(hitCounter); Assert.assertEquals(5, hitCounter.getMaxCountPerBucket(latestTimeStamp)); + Assert.assertEquals(5 * 60, hitCounter.getHitCount(latestTimeStamp)); // 2 seconds have passed, the hit counter should return 5 as well since the count in the last bucket could increase. latestTimeStamp = latestTimeStamp + 2000L; Assert.assertEquals(5, hitCounter.getMaxCountPerBucket(latestTimeStamp)); + Assert.assertEquals(5 * (60 - 2), hitCounter.getHitCount(latestTimeStamp)); // This time it should return 0 as the internal lastAccessTimestamp has already been updated and there is no more // hits between the gap. latestTimeStamp = latestTimeStamp + 2000L; Assert.assertEquals(0, hitCounter.getMaxCountPerBucket(latestTimeStamp)); + Assert.assertEquals(5 * (60 - 4), hitCounter.getHitCount(latestTimeStamp)); // Increment the hit in this second and we should see the result becomes 1. hitCounter.hit(latestTimeStamp); latestTimeStamp = latestTimeStamp + 2000L; Assert.assertEquals(1, hitCounter.getMaxCountPerBucket(latestTimeStamp)); + Assert.assertEquals(5 * (60 - 6) + 1, hitCounter.getHitCount(latestTimeStamp)); // More than a time range period has passed and the hit counter should return 0 as there is no hits. hitCounter.hit(latestTimeStamp); latestTimeStamp = latestTimeStamp + timeInSec * 2 * 1000L + 2000L; Assert.assertEquals(0, hitCounter.getMaxCountPerBucket(latestTimeStamp)); + Assert.assertEquals(0, hitCounter.getHitCount(latestTimeStamp)); } } diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandlerTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandlerTest.java deleted file mode 100644 index d2ea5c9b7aa3..000000000000 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/BaseBrokerRequestHandlerTest.java +++ /dev/null @@ -1,255 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.broker.requesthandler; - -import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.collect.ImmutableMap; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CountDownLatch; -import javax.annotation.Nullable; -import org.apache.helix.model.InstanceConfig; -import org.apache.pinot.broker.broker.AllowAllAccessControlFactory; -import org.apache.pinot.broker.queryquota.QueryQuotaManager; -import org.apache.pinot.broker.routing.BrokerRoutingManager; -import org.apache.pinot.common.config.provider.TableCache; -import org.apache.pinot.common.metrics.BrokerMetrics; -import org.apache.pinot.common.request.BrokerRequest; -import org.apache.pinot.common.request.Expression; -import org.apache.pinot.common.request.PinotQuery; -import org.apache.pinot.common.response.broker.BrokerResponseNative; -import org.apache.pinot.core.routing.RoutingTable; -import org.apache.pinot.core.transport.ServerInstance; -import org.apache.pinot.spi.config.table.TableConfig; -import org.apache.pinot.spi.config.table.TenantConfig; -import org.apache.pinot.spi.env.PinotConfiguration; -import org.apache.pinot.spi.exception.BadQueryRequestException; -import org.apache.pinot.spi.metrics.PinotMetricUtils; -import org.apache.pinot.spi.trace.RequestContext; -import org.apache.pinot.spi.trace.Tracing; -import org.apache.pinot.spi.utils.CommonConstants; -import org.apache.pinot.spi.utils.JsonUtils; -import org.apache.pinot.sql.parsers.CalciteSqlParser; -import org.apache.pinot.util.TestUtils; -import org.mockito.Mockito; -import org.testng.Assert; -import org.testng.annotations.Test; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - - -public class BaseBrokerRequestHandlerTest { - - @Test - public void testUpdateColumnNames() { - String query = "SELECT database.my_table.column_name_1st, column_name_2nd from database.my_table"; - PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(query); - Map columnNameMap = - ImmutableMap.of("column_name_1st", "column_name_1st", "column_name_2nd", "column_name_2nd"); - BaseBrokerRequestHandler.updateColumnNames("database.my_table", pinotQuery, false, columnNameMap); - Assert.assertEquals(pinotQuery.getSelectList().size(), 2); - for (Expression expression : pinotQuery.getSelectList()) { - String columnName = expression.getIdentifier().getName(); - if (columnName.endsWith("column_name_1st")) { - Assert.assertEquals(columnName, "column_name_1st"); - } else if (columnName.endsWith("column_name_2nd")) { - Assert.assertEquals(columnName, "column_name_2nd"); - } else { - Assert.fail("rewritten column name should be column_name_1st or column_name_1st, but is " + columnName); - } - } - } - - @Test - public void testGetActualColumnNameCaseSensitive() { - Map columnNameMap = new HashMap<>(); - columnNameMap.put("student_name", "student_name"); - String actualColumnName = - BaseBrokerRequestHandler.getActualColumnName("mytable", "mytable.student_name", columnNameMap, null, false); - Assert.assertEquals(actualColumnName, "student_name"); - boolean exceptionThrown = false; - try { - BaseBrokerRequestHandler.getActualColumnName("mytable", "mytable2.student_name", columnNameMap, null, false); - Assert.fail("should throw exception if column is not known"); - } catch (BadQueryRequestException ex) { - exceptionThrown = true; - } - Assert.assertTrue(exceptionThrown, "should throw exception if column is not known"); - exceptionThrown = false; - try { - BaseBrokerRequestHandler.getActualColumnName("mytable", "MYTABLE.student_name", columnNameMap, null, false); - Assert.fail("should throw exception if case sensitive and table name different"); - } catch (BadQueryRequestException ex) { - exceptionThrown = true; - } - Assert.assertTrue(exceptionThrown, "should throw exception if column is not known"); - columnNameMap.put("mytable_student_name", "mytable_student_name"); - String wrongColumnName2 = - BaseBrokerRequestHandler.getActualColumnName("mytable", "mytable_student_name", columnNameMap, null, false); - Assert.assertEquals(wrongColumnName2, "mytable_student_name"); - - columnNameMap.put("mytable", "mytable"); - String wrongColumnName3 = - BaseBrokerRequestHandler.getActualColumnName("mytable", "mytable", columnNameMap, null, false); - Assert.assertEquals(wrongColumnName3, "mytable"); - } - - @Test - public void testGetActualColumnNameCaseInSensitive() { - Map columnNameMap = new HashMap<>(); - columnNameMap.put("student_name", "student_name"); - String actualColumnName = - BaseBrokerRequestHandler.getActualColumnName("mytable", "MYTABLE.student_name", columnNameMap, null, true); - Assert.assertEquals(actualColumnName, "student_name"); - boolean exceptionThrown = false; - try { - BaseBrokerRequestHandler.getActualColumnName("student", "MYTABLE2.student_name", columnNameMap, null, true); - Assert.fail("should throw exception if column is not known"); - } catch (BadQueryRequestException ex) { - exceptionThrown = true; - } - Assert.assertTrue(exceptionThrown, "should throw exception if column is not known"); - columnNameMap.put("mytable_student_name", "mytable_student_name"); - String wrongColumnName2 = - BaseBrokerRequestHandler.getActualColumnName("mytable", "MYTABLE_student_name", columnNameMap, null, true); - Assert.assertEquals(wrongColumnName2, "mytable_student_name"); - - columnNameMap.put("mytable", "mytable"); - String wrongColumnName3 = - BaseBrokerRequestHandler.getActualColumnName("MYTABLE", "mytable", columnNameMap, null, true); - Assert.assertEquals(wrongColumnName3, "mytable"); - } - - @Test - public void testGetActualTableNameBanningDots() { - // not allowing dots - PinotConfiguration configuration = new PinotConfiguration(); - configuration.setProperty(CommonConstants.Helix.ALLOW_TABLE_NAME_WITH_DATABASE, false); - - TableCache tableCache = Mockito.mock(TableCache.class); - when(tableCache.isIgnoreCase()).thenReturn(true); - when(tableCache.getActualTableName("mytable")).thenReturn("mytable"); - Assert.assertEquals(BaseBrokerRequestHandler.getActualTableName("mytable", tableCache), "mytable"); - when(tableCache.getActualTableName("db.mytable")).thenReturn(null); - Assert.assertEquals(BaseBrokerRequestHandler.getActualTableName("db.mytable", tableCache), "mytable"); - - when(tableCache.isIgnoreCase()).thenReturn(false); - Assert.assertEquals(BaseBrokerRequestHandler.getActualTableName("db.mytable", tableCache), "mytable"); - } - - @Test - public void testGetActualTableNameAllowingDots() { - - TableCache tableCache = Mockito.mock(TableCache.class); - when(tableCache.isIgnoreCase()).thenReturn(true); - // the tableCache should have only "db.mytable" in it since this is the only table - when(tableCache.getActualTableName("mytable")).thenReturn(null); - when(tableCache.getActualTableName("db.mytable")).thenReturn("db.mytable"); - when(tableCache.getActualTableName("other.mytable")).thenReturn(null); - when(tableCache.getActualTableName("test_table")).thenReturn(null); - - Assert.assertEquals(BaseBrokerRequestHandler.getActualTableName("test_table", tableCache), "test_table"); - Assert.assertEquals(BaseBrokerRequestHandler.getActualTableName("mytable", tableCache), "mytable"); - - Assert.assertEquals(BaseBrokerRequestHandler.getActualTableName("db.mytable", tableCache), "db.mytable"); - Assert.assertEquals(BaseBrokerRequestHandler.getActualTableName("other.mytable", tableCache), "other.mytable"); - - when(tableCache.isIgnoreCase()).thenReturn(false); - Assert.assertEquals(BaseBrokerRequestHandler.getActualTableName("db.mytable", tableCache), "db.mytable"); - Assert.assertEquals(BaseBrokerRequestHandler.getActualTableName("db.namespace.mytable", tableCache), - "db.namespace.mytable"); - } - - @Test - public void testCancelQuery() - throws Exception { - String tableName = "myTable_OFFLINE"; - // Mock pretty much everything until the query can be submitted. - TableCache tableCache = mock(TableCache.class); - TableConfig tableCfg = mock(TableConfig.class); - when(tableCache.getActualTableName(anyString())).thenReturn(tableName); - TenantConfig tenant = new TenantConfig("tier_BROKER", "tier_SERVER", null); - when(tableCfg.getTenantConfig()).thenReturn(tenant); - when(tableCache.getTableConfig(anyString())).thenReturn(tableCfg); - BrokerRoutingManager routingManager = mock(BrokerRoutingManager.class); - when(routingManager.routingExists(anyString())).thenReturn(true); - RoutingTable rt = mock(RoutingTable.class); - when(rt.getServerInstanceToSegmentsMap()).thenReturn(Collections - .singletonMap(new ServerInstance(new InstanceConfig("server01_9000")), Collections.singletonList("segment01"))); - when(routingManager.getRoutingTable(any(), Mockito.anyLong())).thenReturn(rt); - QueryQuotaManager queryQuotaManager = mock(QueryQuotaManager.class); - when(queryQuotaManager.acquire(anyString())).thenReturn(true); - CountDownLatch latch = new CountDownLatch(1); - PinotConfiguration config = - new PinotConfiguration(Collections.singletonMap("pinot.broker.enable.query.cancellation", "true")); - BaseBrokerRequestHandler requestHandler = - new BaseBrokerRequestHandler(config, null, routingManager, new AllowAllAccessControlFactory(), - queryQuotaManager, tableCache, - new BrokerMetrics("", PinotMetricUtils.getPinotMetricsRegistry(), true, Collections.emptySet())) { - @Override - public void start() { - } - - @Override - public void shutDown() { - } - - @Override - protected BrokerResponseNative processBrokerRequest(long requestId, BrokerRequest originalBrokerRequest, - BrokerRequest serverBrokerRequest, @Nullable BrokerRequest offlineBrokerRequest, - @Nullable Map> offlineRoutingTable, - @Nullable BrokerRequest realtimeBrokerRequest, - @Nullable Map> realtimeRoutingTable, long timeoutMs, ServerStats serverStats, - RequestContext requestContext) - throws Exception { - latch.await(); - return null; - } - }; - CompletableFuture.runAsync(() -> { - try { - JsonNode request = JsonUtils.stringToJsonNode( - String.format("{\"sql\":\"select * from %s limit 10\",\"queryOptions\":\"timeoutMs=10000\"}", tableName)); - RequestContext requestStats = Tracing.getTracer().createRequestScope(); - requestHandler.handleRequest(request, null, requestStats); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - TestUtils.waitForCondition((aVoid) -> requestHandler.getRunningServers(1).size() == 1, 500, 5000, - "Failed to submit query"); - Map.Entry entry = requestHandler.getRunningQueries().entrySet().iterator().next(); - Assert.assertEquals(entry.getKey().longValue(), 1); - Assert.assertTrue(entry.getValue().contains("select * from myTable_OFFLINE limit 10")); - Set servers = requestHandler.getRunningServers(1); - Assert.assertEquals(servers.size(), 1); - Assert.assertEquals(servers.iterator().next().getHostname(), "server01"); - Assert.assertEquals(servers.iterator().next().getPort(), 9000); - Assert.assertEquals(servers.iterator().next().getInstanceId(), "server01_9000"); - Assert.assertEquals(servers.iterator().next().getAdminEndpoint(), "http://server01:8097"); - latch.countDown(); - } -} diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/BaseSingleStageBrokerRequestHandlerTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/BaseSingleStageBrokerRequestHandlerTest.java new file mode 100644 index 000000000000..f1a6dfe33f11 --- /dev/null +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/BaseSingleStageBrokerRequestHandlerTest.java @@ -0,0 +1,225 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.requesthandler; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.helix.model.InstanceConfig; +import org.apache.pinot.broker.broker.AllowAllAccessControlFactory; +import org.apache.pinot.broker.queryquota.QueryQuotaManager; +import org.apache.pinot.broker.routing.BrokerRoutingManager; +import org.apache.pinot.common.config.provider.TableCache; +import org.apache.pinot.common.metrics.BrokerMetrics; +import org.apache.pinot.common.request.BrokerRequest; +import org.apache.pinot.common.request.Expression; +import org.apache.pinot.common.request.PinotQuery; +import org.apache.pinot.common.response.broker.BrokerResponseNative; +import org.apache.pinot.core.routing.RoutingTable; +import org.apache.pinot.core.transport.ServerInstance; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TenantConfig; +import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.eventlistener.query.BrokerQueryEventListenerFactory; +import org.apache.pinot.spi.exception.BadQueryRequestException; +import org.apache.pinot.spi.trace.RequestContext; +import org.apache.pinot.spi.utils.CommonConstants.Broker; +import org.apache.pinot.sql.parsers.CalciteSqlParser; +import org.apache.pinot.util.TestUtils; +import org.mockito.Mockito; +import org.testng.Assert; +import org.testng.annotations.Test; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +public class BaseSingleStageBrokerRequestHandlerTest { + + @Test + public void testUpdateColumnNames() { + String query = "SELECT database.my_table.column_name_1st, column_name_2nd from database.my_table"; + PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(query); + Map columnNameMap = + Map.of("column_name_1st", "column_name_1st", "column_name_2nd", "column_name_2nd"); + BaseSingleStageBrokerRequestHandler.updateColumnNames("database.my_table", pinotQuery, false, columnNameMap); + Assert.assertEquals(pinotQuery.getSelectList().size(), 2); + for (Expression expression : pinotQuery.getSelectList()) { + String columnName = expression.getIdentifier().getName(); + if (columnName.endsWith("column_name_1st")) { + Assert.assertEquals(columnName, "column_name_1st"); + } else if (columnName.endsWith("column_name_2nd")) { + Assert.assertEquals(columnName, "column_name_2nd"); + } else { + Assert.fail("rewritten column name should be column_name_1st or column_name_1st, but is " + columnName); + } + } + } + + @Test + public void testGetActualColumnNameCaseSensitive() { + Map columnNameMap = new HashMap<>(); + columnNameMap.put("student_name", "student_name"); + String actualColumnName = + BaseSingleStageBrokerRequestHandler.getActualColumnName("mytable", "mytable.student_name", columnNameMap, + false); + Assert.assertEquals(actualColumnName, "student_name"); + Assert.assertEquals( + BaseSingleStageBrokerRequestHandler.getActualColumnName("db1.mytable", "db1.mytable.student_name", + columnNameMap, false), "student_name"); + Assert.assertEquals( + BaseSingleStageBrokerRequestHandler.getActualColumnName("db1.mytable", "mytable.student_name", columnNameMap, + false), "student_name"); + boolean exceptionThrown = false; + try { + BaseSingleStageBrokerRequestHandler.getActualColumnName("mytable", "mytable2.student_name", columnNameMap, false); + Assert.fail("should throw exception if column is not known"); + } catch (BadQueryRequestException ex) { + exceptionThrown = true; + } + Assert.assertTrue(exceptionThrown, "should throw exception if column is not known"); + exceptionThrown = false; + try { + BaseSingleStageBrokerRequestHandler.getActualColumnName("mytable", "MYTABLE.student_name", columnNameMap, false); + Assert.fail("should throw exception if case sensitive and table name different"); + } catch (BadQueryRequestException ex) { + exceptionThrown = true; + } + Assert.assertTrue(exceptionThrown, "should throw exception if column is not known"); + columnNameMap.put("mytable_student_name", "mytable_student_name"); + String wrongColumnName2 = + BaseSingleStageBrokerRequestHandler.getActualColumnName("mytable", "mytable_student_name", columnNameMap, + false); + Assert.assertEquals(wrongColumnName2, "mytable_student_name"); + + columnNameMap.put("mytable", "mytable"); + String wrongColumnName3 = + BaseSingleStageBrokerRequestHandler.getActualColumnName("mytable", "mytable", columnNameMap, false); + Assert.assertEquals(wrongColumnName3, "mytable"); + } + + @Test + public void testGetActualColumnNameCaseInSensitive() { + Map columnNameMap = new HashMap<>(); + columnNameMap.put("student_name", "student_name"); + String actualColumnName = + BaseSingleStageBrokerRequestHandler.getActualColumnName("mytable", "MYTABLE.student_name", columnNameMap, true); + Assert.assertEquals(actualColumnName, "student_name"); + Assert.assertEquals( + BaseSingleStageBrokerRequestHandler.getActualColumnName("db1.MYTABLE", "DB1.mytable.student_name", + columnNameMap, true), "student_name"); + Assert.assertEquals( + BaseSingleStageBrokerRequestHandler.getActualColumnName("db1.mytable", "MYTABLE.student_name", columnNameMap, + true), "student_name"); + boolean exceptionThrown = false; + try { + BaseSingleStageBrokerRequestHandler.getActualColumnName("student", "MYTABLE2.student_name", columnNameMap, true); + Assert.fail("should throw exception if column is not known"); + } catch (BadQueryRequestException ex) { + exceptionThrown = true; + } + Assert.assertTrue(exceptionThrown, "should throw exception if column is not known"); + columnNameMap.put("mytable_student_name", "mytable_student_name"); + String wrongColumnName2 = + BaseSingleStageBrokerRequestHandler.getActualColumnName("mytable", "MYTABLE_student_name", columnNameMap, true); + Assert.assertEquals(wrongColumnName2, "mytable_student_name"); + + columnNameMap.put("mytable", "mytable"); + String wrongColumnName3 = + BaseSingleStageBrokerRequestHandler.getActualColumnName("MYTABLE", "mytable", columnNameMap, true); + Assert.assertEquals(wrongColumnName3, "mytable"); + } + + @Test + public void testCancelQuery() { + String tableName = "myTable_OFFLINE"; + // Mock pretty much everything until the query can be submitted. + TableCache tableCache = mock(TableCache.class); + TableConfig tableCfg = mock(TableConfig.class); + when(tableCache.getActualTableName(anyString())).thenReturn(tableName); + TenantConfig tenant = new TenantConfig("tier_BROKER", "tier_SERVER", null); + when(tableCfg.getTenantConfig()).thenReturn(tenant); + when(tableCache.getTableConfig(tableName)).thenReturn(tableCfg); + BrokerRoutingManager routingManager = mock(BrokerRoutingManager.class); + when(routingManager.routingExists(tableName)).thenReturn(true); + when(routingManager.getQueryTimeoutMs(tableName)).thenReturn(10000L); + RoutingTable rt = mock(RoutingTable.class); + when(rt.getServerInstanceToSegmentsMap()).thenReturn( + Map.of(new ServerInstance(new InstanceConfig("server01_9000")), Pair.of(List.of("segment01"), List.of()))); + when(routingManager.getRoutingTable(any(), Mockito.anyLong())).thenReturn(rt); + QueryQuotaManager queryQuotaManager = mock(QueryQuotaManager.class); + when(queryQuotaManager.acquire(anyString())).thenReturn(true); + CountDownLatch latch = new CountDownLatch(1); + long[] testRequestId = {-1}; + BrokerMetrics.register(mock(BrokerMetrics.class)); + PinotConfiguration config = + new PinotConfiguration(Map.of(Broker.CONFIG_OF_BROKER_ENABLE_QUERY_CANCELLATION, "true")); + BrokerQueryEventListenerFactory.init(config); + BaseSingleStageBrokerRequestHandler requestHandler = + new BaseSingleStageBrokerRequestHandler(config, "testBrokerId", routingManager, + new AllowAllAccessControlFactory(), queryQuotaManager, tableCache) { + @Override + public void start() { + } + + @Override + public void shutDown() { + } + + @Override + protected BrokerResponseNative processBrokerRequest(long requestId, BrokerRequest originalBrokerRequest, + BrokerRequest serverBrokerRequest, @Nullable BrokerRequest offlineBrokerRequest, + @Nullable Map, List>> offlineRoutingTable, + @Nullable BrokerRequest realtimeBrokerRequest, + @Nullable Map, List>> realtimeRoutingTable, long timeoutMs, + ServerStats serverStats, RequestContext requestContext) + throws Exception { + testRequestId[0] = requestId; + latch.await(); + return null; + } + }; + CompletableFuture.runAsync(() -> { + try { + requestHandler.handleRequest(String.format("select * from %s limit 10", tableName)); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + TestUtils.waitForCondition((aVoid) -> requestHandler.getRunningServers(testRequestId[0]).size() == 1, 500, 5000, + "Failed to submit query"); + Map.Entry entry = requestHandler.getRunningQueries().entrySet().iterator().next(); + Assert.assertEquals(entry.getKey().longValue(), testRequestId[0]); + Assert.assertTrue(entry.getValue().contains("select * from myTable_OFFLINE limit 10")); + Set servers = requestHandler.getRunningServers(testRequestId[0]); + Assert.assertEquals(servers.size(), 1); + Assert.assertEquals(servers.iterator().next().getHostname(), "server01"); + Assert.assertEquals(servers.iterator().next().getPort(), 9000); + Assert.assertEquals(servers.iterator().next().getInstanceId(), "server01_9000"); + Assert.assertEquals(servers.iterator().next().getAdminEndpoint(), "http://server01:8097"); + latch.countDown(); + } +} diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/BrokerRequestOptionsTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/BrokerRequestOptionsTest.java index 14d1d39bdba4..e6ebccd16641 100644 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/BrokerRequestOptionsTest.java +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/BrokerRequestOptionsTest.java @@ -26,68 +26,40 @@ import org.apache.pinot.spi.utils.JsonUtils; import org.apache.pinot.sql.parsers.CalciteSqlParser; import org.apache.pinot.sql.parsers.SqlNodeAndOptions; -import org.testng.Assert; import org.testng.annotations.Test; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + /** * Tests the various options set in the broker request */ public class BrokerRequestOptionsTest { - // TODO: remove this legacy option size checker after 0.11 release cut. - private static final int LEGACY_PQL_QUERY_OPTION_SIZE = 2; @Test public void testSetOptions() { - long requestId = 1; String query = "select * from testTable"; // None of the options ObjectNode jsonRequest = JsonUtils.newObjectNode(); - SqlNodeAndOptions sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(query);; + SqlNodeAndOptions sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(query); RequestUtils.setOptions(sqlNodeAndOptions, jsonRequest); - Assert.assertEquals(sqlNodeAndOptions.getOptions().size(), 0 + LEGACY_PQL_QUERY_OPTION_SIZE); + assertTrue(sqlNodeAndOptions.getOptions().isEmpty()); // TRACE // Has trace false jsonRequest.put(Request.TRACE, false); sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(query); RequestUtils.setOptions(sqlNodeAndOptions, jsonRequest); - Assert.assertEquals(sqlNodeAndOptions.getOptions().size(), 0 + LEGACY_PQL_QUERY_OPTION_SIZE); + assertTrue(sqlNodeAndOptions.getOptions().isEmpty()); // Has trace true jsonRequest.put(Request.TRACE, true); sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(query); RequestUtils.setOptions(sqlNodeAndOptions, jsonRequest); - Assert.assertEquals(sqlNodeAndOptions.getOptions().size(), 1 + LEGACY_PQL_QUERY_OPTION_SIZE); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get(Request.TRACE), "true"); - - // DEBUG_OPTIONS (debug options will also be included as query options) - // Has debugOptions - jsonRequest = JsonUtils.newObjectNode(); - jsonRequest.put(Request.DEBUG_OPTIONS, "debugOption1=foo"); - sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(query); - RequestUtils.setOptions(sqlNodeAndOptions, jsonRequest); - Assert.assertEquals(sqlNodeAndOptions.getOptions().size(), 1 + LEGACY_PQL_QUERY_OPTION_SIZE); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get("debugOption1"), "foo"); - - // Has multiple debugOptions - jsonRequest.put(Request.DEBUG_OPTIONS, "debugOption1=foo;debugOption2=bar"); - sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(query); - RequestUtils.setOptions(sqlNodeAndOptions, jsonRequest); - Assert.assertEquals(sqlNodeAndOptions.getOptions().size(), 2 + LEGACY_PQL_QUERY_OPTION_SIZE); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get("debugOption1"), "foo"); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get("debugOption2"), "bar"); - - // Invalid debug options - jsonRequest.put(Request.DEBUG_OPTIONS, "debugOption1"); - sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(query); - try { - RequestUtils.setOptions(sqlNodeAndOptions, jsonRequest); - Assert.fail(); - } catch (Exception e) { - // Expected - } + assertEquals(sqlNodeAndOptions.getOptions().size(), 1); + assertEquals(sqlNodeAndOptions.getOptions().get(Request.TRACE), "true"); // QUERY_OPTIONS jsonRequest = JsonUtils.newObjectNode(); @@ -97,41 +69,28 @@ public void testSetOptions() { queryOptions.put("queryOption1", "foo"); sqlNodeAndOptions.getOptions().putAll(queryOptions); RequestUtils.setOptions(sqlNodeAndOptions, jsonRequest); - Assert.assertEquals(sqlNodeAndOptions.getOptions().size(), 1 + LEGACY_PQL_QUERY_OPTION_SIZE); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get("queryOption1"), "foo"); + assertEquals(sqlNodeAndOptions.getOptions().size(), 1); + assertEquals(sqlNodeAndOptions.getOptions().get("queryOption1"), "foo"); // Has queryOptions in query sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions("SET queryOption1='foo'; select * from testTable"); RequestUtils.setOptions(sqlNodeAndOptions, jsonRequest); - Assert.assertEquals(sqlNodeAndOptions.getOptions().size(), 1 + LEGACY_PQL_QUERY_OPTION_SIZE); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get("queryOption1"), "foo"); + assertEquals(sqlNodeAndOptions.getOptions().size(), 1); + assertEquals(sqlNodeAndOptions.getOptions().get("queryOption1"), "foo"); // Has query options in json payload jsonRequest.put(Request.QUERY_OPTIONS, "queryOption1=foo"); sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(query); RequestUtils.setOptions(sqlNodeAndOptions, jsonRequest); - Assert.assertEquals(sqlNodeAndOptions.getOptions().size(), 1 + LEGACY_PQL_QUERY_OPTION_SIZE); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get("queryOption1"), "foo"); + assertEquals(sqlNodeAndOptions.getOptions().size(), 1); + assertEquals(sqlNodeAndOptions.getOptions().get("queryOption1"), "foo"); // Has query options in both json payload and sqlNodeAndOptions, sqlNodeAndOptions takes priority jsonRequest.put(Request.QUERY_OPTIONS, "queryOption1=bar;queryOption2=moo"); sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions("SET queryOption1='foo'; select * from testTable;"); RequestUtils.setOptions(sqlNodeAndOptions, jsonRequest); - Assert.assertEquals(sqlNodeAndOptions.getOptions().size(), 2 + LEGACY_PQL_QUERY_OPTION_SIZE); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get("queryOption1"), "foo"); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get("queryOption2"), "moo"); - - // Has all 3 - jsonRequest = JsonUtils.newObjectNode(); - jsonRequest.put(Request.TRACE, true); - jsonRequest.put(Request.DEBUG_OPTIONS, "debugOption1=foo"); - jsonRequest.put(Request.QUERY_OPTIONS, "queryOption1=bar;queryOption2=moo"); - sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(query); - RequestUtils.setOptions(sqlNodeAndOptions, jsonRequest); - Assert.assertEquals(sqlNodeAndOptions.getOptions().size(), 4 + LEGACY_PQL_QUERY_OPTION_SIZE); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get("queryOption1"), "bar"); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get("queryOption2"), "moo"); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get(Request.TRACE), "true"); - Assert.assertEquals(sqlNodeAndOptions.getOptions().get("debugOption1"), "foo"); + assertEquals(sqlNodeAndOptions.getOptions().size(), 2); + assertEquals(sqlNodeAndOptions.getOptions().get("queryOption1"), "foo"); + assertEquals(sqlNodeAndOptions.getOptions().get("queryOption2"), "moo"); } } diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/LiteralOnlyBrokerRequestTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/LiteralOnlyBrokerRequestTest.java index a9ee2a5deefb..0b68d2b843ba 100644 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/LiteralOnlyBrokerRequestTest.java +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/LiteralOnlyBrokerRequestTest.java @@ -18,8 +18,6 @@ */ package org.apache.pinot.broker.requesthandler; -import com.fasterxml.jackson.databind.JsonNode; -import java.util.Collections; import java.util.List; import java.util.Random; import java.util.concurrent.TimeUnit; @@ -31,16 +29,17 @@ import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.transport.server.routing.stats.ServerRoutingStatsManager; import org.apache.pinot.spi.env.PinotConfiguration; -import org.apache.pinot.spi.metrics.PinotMetricUtils; -import org.apache.pinot.spi.trace.RequestContext; -import org.apache.pinot.spi.trace.Tracing; +import org.apache.pinot.spi.eventlistener.query.BrokerQueryEventListenerFactory; import org.apache.pinot.spi.utils.BytesUtils; -import org.apache.pinot.spi.utils.JsonUtils; import org.apache.pinot.sql.parsers.CalciteSqlParser; -import org.testng.Assert; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; +import static org.apache.pinot.broker.requesthandler.BaseSingleStageBrokerRequestHandler.isLiteralOnlyQuery; import static org.mockito.Mockito.mock; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; public class LiteralOnlyBrokerRequestTest { @@ -48,132 +47,120 @@ public class LiteralOnlyBrokerRequestTest { private static final Random RANDOM = new Random(System.currentTimeMillis()); private static final long ONE_HOUR_IN_MS = TimeUnit.HOURS.toMillis(1); + @BeforeClass + public void setUp() { + BrokerMetrics.register(mock(BrokerMetrics.class)); + BrokerQueryEventListenerFactory.init(new PinotConfiguration()); + } + @Test public void testStringLiteralBrokerRequestFromSQL() { - Assert.assertTrue(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT 'a'"))); - Assert.assertTrue( - BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT 'a', 'b'"))); - Assert.assertTrue( - BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT 'a' FROM myTable"))); - Assert.assertTrue(BaseBrokerRequestHandler - .isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT 'a', 'b' FROM myTable"))); + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT 'a'"))); + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT 'a', 'b'"))); + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT 'a' FROM myTable"))); + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT 'a', 'b' FROM myTable"))); } @Test public void testSelectStarBrokerRequestFromSQL() { - Assert.assertTrue(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT '*'"))); - Assert.assertTrue( - BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT '*' FROM myTable"))); - Assert.assertFalse(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT *"))); - Assert.assertFalse( - BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT * FROM myTable"))); + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT '*'"))); + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT '*' FROM myTable"))); + assertFalse(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT *"))); + assertFalse(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT * FROM myTable"))); } @Test public void testNumberLiteralBrokerRequestFromSQL() { - Assert.assertTrue(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT 1"))); - Assert.assertTrue( - BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT 1, '2', 3"))); - Assert.assertTrue( - BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT 1 FROM myTable"))); - Assert.assertTrue(BaseBrokerRequestHandler - .isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT 1, '2', 3 FROM myTable"))); + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT 1"))); + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT 1, '2', 3"))); + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT 1 FROM myTable"))); + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT 1, '2', 3 FROM myTable"))); } @Test public void testLiteralOnlyTransformBrokerRequestFromSQL() { - Assert - .assertTrue(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT now()"))); - Assert.assertTrue( - BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT ago('PT1H')"))); - Assert.assertTrue(BaseBrokerRequestHandler.isLiteralOnlyQuery( + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT now()"))); + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT ago('PT1H')"))); + assertTrue(isLiteralOnlyQuery( CalciteSqlParser.compileToPinotQuery("SELECT now(), fromDateTime('2020-01-01 UTC', 'yyyy-MM-dd z')"))); - Assert.assertTrue(BaseBrokerRequestHandler.isLiteralOnlyQuery( + assertTrue(isLiteralOnlyQuery( CalciteSqlParser.compileToPinotQuery("SELECT ago('PT1H'), fromDateTime('2020-01-01 UTC', 'yyyy-MM-dd z')"))); - Assert.assertTrue( - BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT now() FROM myTable"))); - Assert.assertTrue(BaseBrokerRequestHandler - .isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT ago('PT1H') FROM myTable"))); - Assert.assertTrue(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser - .compileToPinotQuery("SELECT now(), fromDateTime('2020-01-01 UTC', 'yyyy-MM-dd z') FROM myTable"))); - Assert.assertTrue(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser - .compileToPinotQuery("SELECT ago('PT1H'), fromDateTime('2020-01-01 UTC', 'yyyy-MM-dd z') FROM myTable"))); - Assert.assertFalse(BaseBrokerRequestHandler - .isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT count(*) from foo where bar > ago('PT1H')"))); - Assert.assertTrue(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser - .compileToPinotQuery("SELECT encodeUrl('key1=value 1&key2=value@!$2&key3=value%3')," + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT now() FROM myTable"))); + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT ago('PT1H') FROM myTable"))); + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( + "SELECT now(), fromDateTime('2020-01-01 UTC', 'yyyy-MM-dd z') FROM myTable"))); + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( + "SELECT ago('PT1H'), fromDateTime('2020-01-01 UTC', 'yyyy-MM-dd z') FROM myTable"))); + assertFalse( + isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT count(*) from foo where bar > ago('PT1H')"))); + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( + "SELECT encodeUrl('key1=value 1&key2=value@!$2&key3=value%3')," + " decodeUrl('key1%3Dvalue+1%26key2%3Dvalue%40%21%242%26key3%3Dvalue%253') FROM myTable"))); - Assert.assertFalse(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser - .compileToPinotQuery("SELECT count(*) from foo " - + "where bar = encodeUrl('key1=value 1&key2=value@!$2&key3=value%3')"))); - Assert.assertFalse(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser - .compileToPinotQuery("SELECT count(*) from foo " - + "where bar = decodeUrl('key1%3Dvalue+1%26key2%3Dvalue%40%21%242%26key3%3Dvalue%253')"))); - Assert.assertTrue(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( - "SELECT toUtf8('hello!')," + " fromUtf8(toUtf8('hello!')) FROM myTable"))); - Assert.assertFalse(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( - "SELECT reverse(fromUtf8(foo))," + " toUtf8('hello!') FROM myTable"))); - Assert.assertTrue(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( + assertFalse(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( + "SELECT count(*) from foo " + "where bar = encodeUrl('key1=value 1&key2=value@!$2&key3=value%3')"))); + assertFalse(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("SELECT count(*) from foo " + + "where bar = decodeUrl('key1%3Dvalue+1%26key2%3Dvalue%40%21%242%26key3%3Dvalue%253')"))); + assertTrue(isLiteralOnlyQuery( + CalciteSqlParser.compileToPinotQuery("SELECT toUtf8('hello!')," + " fromUtf8(toUtf8('hello!')) FROM myTable"))); + assertFalse(isLiteralOnlyQuery( + CalciteSqlParser.compileToPinotQuery("SELECT reverse(fromUtf8(foo))," + " toUtf8('hello!') FROM myTable"))); + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( "SELECT toBase64(toUtf8('hello!'))," + " fromBase64('aGVsbG8h') FROM myTable"))); - Assert.assertFalse(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( + assertFalse(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( "SELECT reverse(toBase64(foo))," + " toBase64(fromBase64('aGVsbG8h')) FROM myTable"))); - Assert.assertFalse(BaseBrokerRequestHandler.isLiteralOnlyQuery( + assertFalse(isLiteralOnlyQuery( CalciteSqlParser.compileToPinotQuery("SELECT fromBase64(toBase64(to_utf8(foo))) FROM myTable"))); - Assert.assertFalse(BaseBrokerRequestHandler.isLiteralOnlyQuery( + assertFalse(isLiteralOnlyQuery( CalciteSqlParser.compileToPinotQuery("SELECT count(*) from foo " + "where bar = toBase64(toASCII('hello!'))"))); - Assert.assertFalse(BaseBrokerRequestHandler.isLiteralOnlyQuery( + assertFalse(isLiteralOnlyQuery( CalciteSqlParser.compileToPinotQuery("SELECT count(*) from foo " + "where bar = fromBase64('aGVsbG8h')"))); - Assert.assertFalse(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( + assertFalse(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( "SELECT count(*) from foo " + "where bar = fromUtf8(fromBase64('aGVsbG8h'))"))); - Assert.assertFalse(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser - .compileToPinotQuery("SELECT count(*) from myTable where regexpReplace(col1, \"b(..)\", \"X$1Y\") = " - + "\"fooXarYXazY\""))); - Assert.assertFalse(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser - .compileToPinotQuery("SELECT count(*) from myTable where regexpReplace(col1, \"b(..)\", \"X$1Y\", 10) = " - + "\"fooXarYXazY\""))); - Assert.assertFalse(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser - .compileToPinotQuery("SELECT count(*) from myTable where regexpReplace(col1, \"b(..)\", \"X$1Y\", 10 , 1) = " + assertFalse(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( + "SELECT count(*) from myTable where regexpReplace(col1, \"b(..)\", \"X$1Y\") = " + "\"fooXarYXazY\""))); + assertFalse(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( + "SELECT count(*) from myTable where regexpReplace(col1, \"b(..)\", \"X$1Y\", 10) = " + "\"fooXarYXazY\""))); + assertFalse(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( + "SELECT count(*) from myTable where regexpReplace(col1, \"b(..)\", \"X$1Y\", 10 , 1) = " + "\"fooXarYXazY\""))); - Assert.assertFalse(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser - .compileToPinotQuery("SELECT count(*) from myTable where regexpReplace(col1, \"b(..)\", \"X$1Y\", 10 , 1, " - + "\"i\") = " + assertFalse(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( + "SELECT count(*) from myTable where regexpReplace(col1, \"b(..)\", \"X$1Y\", 10 , 1, " + "\"i\") = " + "\"fooXarYXazY\""))); - Assert.assertFalse(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser - .compileToPinotQuery("SELECT count(*) from myTable where regexpReplace(col1, \"b(..)\", \"X$1Y\", 10 , 1, " - + "\"m\") = " + assertFalse(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( + "SELECT count(*) from myTable where regexpReplace(col1, \"b(..)\", \"X$1Y\", 10 , 1, " + "\"m\") = " + "\"fooXarYXazY\""))); - Assert.assertTrue(BaseBrokerRequestHandler.isLiteralOnlyQuery( + assertTrue(isLiteralOnlyQuery( CalciteSqlParser.compileToPinotQuery("select isSubnetOf('1.2.3.128/0', '192.168.5.1') from mytable"))); - Assert.assertTrue(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( "select isSubnetOf('1.2.3.128/0', rtrim('192.168.5.1 ')) from mytable"))); - Assert.assertTrue(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( "select isSubnetOf('123:db8:85a3::8a2e:370:7334/72', '124:db8:85a3::8a2e:370:7334') from mytable"))); - Assert.assertFalse(BaseBrokerRequestHandler.isLiteralOnlyQuery( - CalciteSqlParser.compileToPinotQuery("select isSubnetOf('1.2.3.128/0', foo) from mytable"))); - Assert.assertFalse(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( + assertFalse( + isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery("select isSubnetOf('1.2.3.128/0', foo) from mytable"))); + assertFalse(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( "select count(*) from mytable where isSubnetOf('7890:db8:113::8a2e:370:7334/127', ltrim(' " + "7890:db8:113::8a2e:370:7336'))"))); - Assert.assertFalse(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( + assertFalse(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( "select count(*) from mytable where isSubnetOf('7890:db8:113::8a2e:370:7334/127', " + "'7890:db8:113::8a2e:370:7336')"))); - Assert.assertFalse(BaseBrokerRequestHandler.isLiteralOnlyQuery( + assertFalse(isLiteralOnlyQuery( CalciteSqlParser.compileToPinotQuery("select count(*) from mytable where isSubnetOf(foo, bar)"))); } @Test public void testLiteralOnlyWithAsBrokerRequestFromSQL() { - Assert.assertTrue(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( "SELECT now() AS currentTs, fromDateTime('2020-01-01 UTC', 'yyyy-MM-dd z') AS firstDayOf2020"))); - Assert.assertTrue(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( "SELECT ago('PT1H') AS currentTs, fromDateTime('2020-01-01 UTC', 'yyyy-MM-dd z') AS firstDayOf2020"))); - Assert.assertTrue(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( "SELECT encodeUrl('key1=value 1&key2=value@!$2&key3=value%3') AS encoded, " + "decodeUrl('key1%3Dvalue+1%26key2%3Dvalue%40%21%242%26key3%3Dvalue%253') AS decoded"))); - Assert.assertTrue(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( "SELECT toUtf8('hello!') AS encoded, " + "fromUtf8(toUtf8('hello!')) AS decoded"))); - Assert.assertTrue(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( "SELECT toBase64(toUtf8('hello!')) AS encoded, " + "fromBase64('aGVsbG8h') AS decoded"))); - Assert.assertTrue(BaseBrokerRequestHandler.isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( + assertTrue(isLiteralOnlyQuery(CalciteSqlParser.compileToPinotQuery( "select isSubnetOf('1.2.3.128/0', '192.168.5.1') AS booleanCol from mytable"))); } @@ -181,234 +168,177 @@ public void testLiteralOnlyWithAsBrokerRequestFromSQL() { public void testBrokerRequestHandler() throws Exception { SingleConnectionBrokerRequestHandler requestHandler = - new SingleConnectionBrokerRequestHandler(new PinotConfiguration(), null, null, ACCESS_CONTROL_FACTORY, null, - null, new BrokerMetrics("", PinotMetricUtils.getPinotMetricsRegistry(), true, Collections.emptySet()), null, - null, mock(ServerRoutingStatsManager.class)); + new SingleConnectionBrokerRequestHandler(new PinotConfiguration(), "testBrokerId", null, ACCESS_CONTROL_FACTORY, + null, null, null, null, mock(ServerRoutingStatsManager.class)); long randNum = RANDOM.nextLong(); byte[] randBytes = new byte[12]; RANDOM.nextBytes(randBytes); String ranStr = BytesUtils.toHexString(randBytes); - JsonNode request = JsonUtils.stringToJsonNode(String.format("{\"sql\":\"SELECT %d, '%s'\"}", randNum, ranStr)); - RequestContext requestStats = Tracing.getTracer().createRequestScope(); - BrokerResponse brokerResponse = requestHandler.handleRequest(request, null, requestStats); - Assert.assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnName(0), String.format("%d", randNum)); - Assert.assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnDataType(0), - DataSchema.ColumnDataType.LONG); - Assert.assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnName(1), ranStr); - Assert.assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnDataType(1), + BrokerResponse brokerResponse = requestHandler.handleRequest(String.format("SELECT %d, '%s'", randNum, ranStr)); + assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnName(0), String.format("%d", randNum)); + assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnDataType(0), DataSchema.ColumnDataType.LONG); + assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnName(1), String.format("'%s'", ranStr)); + assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnDataType(1), DataSchema.ColumnDataType.STRING); - Assert.assertEquals(brokerResponse.getResultTable().getRows().size(), 1); - Assert.assertEquals(brokerResponse.getResultTable().getRows().get(0).length, 2); - Assert.assertEquals(brokerResponse.getResultTable().getRows().get(0)[0], randNum); - Assert.assertEquals(brokerResponse.getResultTable().getRows().get(0)[1], ranStr); - Assert.assertEquals(brokerResponse.getTotalDocs(), 0); + assertEquals(brokerResponse.getResultTable().getRows().size(), 1); + assertEquals(brokerResponse.getResultTable().getRows().get(0).length, 2); + assertEquals(brokerResponse.getResultTable().getRows().get(0)[0], randNum); + assertEquals(brokerResponse.getResultTable().getRows().get(0)[1], ranStr); + assertEquals(brokerResponse.getTotalDocs(), 0); } @Test public void testBrokerRequestHandlerWithAsFunction() throws Exception { SingleConnectionBrokerRequestHandler requestHandler = - new SingleConnectionBrokerRequestHandler(new PinotConfiguration(), null, null, ACCESS_CONTROL_FACTORY, null, - null, new BrokerMetrics("", PinotMetricUtils.getPinotMetricsRegistry(), true, Collections.emptySet()), null, - null, mock(ServerRoutingStatsManager.class)); + new SingleConnectionBrokerRequestHandler(new PinotConfiguration(), "testBrokerId", null, ACCESS_CONTROL_FACTORY, + null, null, null, null, mock(ServerRoutingStatsManager.class)); long currentTsMin = System.currentTimeMillis(); - JsonNode request = JsonUtils.stringToJsonNode( - "{\"sql\":\"SELECT now() as currentTs, fromDateTime('2020-01-01 UTC', 'yyyy-MM-dd z') as firstDayOf2020\"}"); - RequestContext requestStats = Tracing.getTracer().createRequestScope(); - BrokerResponse brokerResponse = requestHandler.handleRequest(request, null, requestStats); + BrokerResponse brokerResponse = requestHandler.handleRequest( + "SELECT now() AS currentTs, fromDateTime('2020-01-01 UTC', 'yyyy-MM-dd z') AS firstDayOf2020"); long currentTsMax = System.currentTimeMillis(); - Assert.assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnName(0), "currentTs"); - Assert.assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnDataType(0), - DataSchema.ColumnDataType.LONG); - Assert.assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnName(1), "firstDayOf2020"); - Assert.assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnDataType(1), - DataSchema.ColumnDataType.LONG); - Assert.assertEquals(brokerResponse.getResultTable().getRows().size(), 1); - Assert.assertEquals(brokerResponse.getResultTable().getRows().get(0).length, 2); - Assert.assertTrue(Long.parseLong(brokerResponse.getResultTable().getRows().get(0)[0].toString()) > currentTsMin); - Assert.assertTrue(Long.parseLong(brokerResponse.getResultTable().getRows().get(0)[0].toString()) < currentTsMax); - Assert.assertEquals(brokerResponse.getResultTable().getRows().get(0)[1], 1577836800000L); - Assert.assertEquals(brokerResponse.getTotalDocs(), 0); + assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnName(0), "currentTs"); + assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnDataType(0), DataSchema.ColumnDataType.LONG); + assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnName(1), "firstDayOf2020"); + assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnDataType(1), DataSchema.ColumnDataType.LONG); + assertEquals(brokerResponse.getResultTable().getRows().size(), 1); + assertEquals(brokerResponse.getResultTable().getRows().get(0).length, 2); + assertTrue(Long.parseLong(brokerResponse.getResultTable().getRows().get(0)[0].toString()) > currentTsMin); + assertTrue(Long.parseLong(brokerResponse.getResultTable().getRows().get(0)[0].toString()) < currentTsMax); + assertEquals(brokerResponse.getResultTable().getRows().get(0)[1], 1577836800000L); + assertEquals(brokerResponse.getTotalDocs(), 0); long oneHourAgoTsMin = System.currentTimeMillis() - ONE_HOUR_IN_MS; - request = JsonUtils.stringToJsonNode( - "{\"sql\":\"SELECT ago('PT1H') as oneHourAgoTs, fromDateTime('2020-01-01 UTC', 'yyyy-MM-dd z') as " - + "firstDayOf2020\"}"); - requestStats = Tracing.getTracer().createRequestScope(); - brokerResponse = requestHandler.handleRequest(request, null, requestStats); + brokerResponse = requestHandler.handleRequest( + "SELECT ago('PT1H') AS oneHourAgoTs, fromDateTime('2020-01-01 UTC', 'yyyy-MM-dd z') AS firstDayOf2020"); long oneHourAgoTsMax = System.currentTimeMillis() - ONE_HOUR_IN_MS; - Assert.assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnName(0), "oneHourAgoTs"); - Assert.assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnDataType(0), - DataSchema.ColumnDataType.LONG); - Assert.assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnName(1), "firstDayOf2020"); - Assert.assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnDataType(1), - DataSchema.ColumnDataType.LONG); - Assert.assertEquals(brokerResponse.getResultTable().getRows().size(), 1); - Assert.assertEquals(brokerResponse.getResultTable().getRows().get(0).length, 2); - Assert - .assertTrue(Long.parseLong(brokerResponse.getResultTable().getRows().get(0)[0].toString()) >= oneHourAgoTsMin); - Assert - .assertTrue(Long.parseLong(brokerResponse.getResultTable().getRows().get(0)[0].toString()) <= oneHourAgoTsMax); - Assert.assertEquals(brokerResponse.getResultTable().getRows().get(0)[1], 1577836800000L); - Assert.assertEquals(brokerResponse.getTotalDocs(), 0); - - request = JsonUtils.stringToJsonNode( - "{\"sql\":\"SELECT encodeUrl('key1=value 1&key2=value@!$2&key3=value%3') AS encoded, " - + "decodeUrl('key1%3Dvalue+1%26key2%3Dvalue%40%21%242%26key3%3Dvalue%253') AS decoded\"}"); - requestStats = Tracing.getTracer().createRequestScope(); - brokerResponse = requestHandler.handleRequest(request, null, requestStats); - System.out.println(brokerResponse.getResultTable()); - Assert.assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnName(0), "encoded"); - Assert.assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnDataType(0), + assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnName(0), "oneHourAgoTs"); + assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnDataType(0), DataSchema.ColumnDataType.LONG); + assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnName(1), "firstDayOf2020"); + assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnDataType(1), DataSchema.ColumnDataType.LONG); + assertEquals(brokerResponse.getResultTable().getRows().size(), 1); + assertEquals(brokerResponse.getResultTable().getRows().get(0).length, 2); + assertTrue(Long.parseLong(brokerResponse.getResultTable().getRows().get(0)[0].toString()) >= oneHourAgoTsMin); + assertTrue(Long.parseLong(brokerResponse.getResultTable().getRows().get(0)[0].toString()) <= oneHourAgoTsMax); + assertEquals(brokerResponse.getResultTable().getRows().get(0)[1], 1577836800000L); + assertEquals(brokerResponse.getTotalDocs(), 0); + + brokerResponse = requestHandler.handleRequest( + "SELECT encodeUrl('key1=value 1&key2=value@!$2&key3=value%3') AS encoded, " + + "decodeUrl('key1%3Dvalue+1%26key2%3Dvalue%40%21%242%26key3%3Dvalue%253') AS decoded"); + assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnName(0), "encoded"); + assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnDataType(0), DataSchema.ColumnDataType.STRING); - Assert.assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnName(1), "decoded"); - Assert.assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnDataType(1), + assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnName(1), "decoded"); + assertEquals(brokerResponse.getResultTable().getDataSchema().getColumnDataType(1), DataSchema.ColumnDataType.STRING); - Assert.assertEquals(brokerResponse.getResultTable().getRows().size(), 1); - Assert.assertEquals(brokerResponse.getResultTable().getRows().get(0).length, 2); - Assert.assertEquals(brokerResponse.getResultTable().getRows().get(0)[0].toString(), + assertEquals(brokerResponse.getResultTable().getRows().size(), 1); + assertEquals(brokerResponse.getResultTable().getRows().get(0).length, 2); + assertEquals(brokerResponse.getResultTable().getRows().get(0)[0].toString(), "key1%3Dvalue+1%26key2%3Dvalue%40%21%242%26key3%3Dvalue%253"); - Assert.assertEquals(brokerResponse.getResultTable().getRows().get(0)[1].toString(), + assertEquals(brokerResponse.getResultTable().getRows().get(0)[1].toString(), "key1=value 1&key2=value@!$2&key3=value%3"); - Assert.assertEquals(brokerResponse.getTotalDocs(), 0); + assertEquals(brokerResponse.getTotalDocs(), 0); - request = JsonUtils.stringToJsonNode( - "{\"sql\":\"SELECT toBase64(toUtf8('hello!')) AS encoded, " + "fromUtf8(fromBase64('aGVsbG8h')) AS decoded\"}"); - requestStats = Tracing.getTracer().createRequestScope(); - brokerResponse = requestHandler.handleRequest(request, null, requestStats); + brokerResponse = requestHandler.handleRequest( + "SELECT toBase64(toUtf8('hello!')) AS encoded, fromUtf8(fromBase64('aGVsbG8h')) AS decoded"); ResultTable resultTable = brokerResponse.getResultTable(); DataSchema dataSchema = resultTable.getDataSchema(); List rows = resultTable.getRows(); - Assert.assertEquals(dataSchema.getColumnName(0), "encoded"); - Assert.assertEquals(dataSchema.getColumnDataType(0), DataSchema.ColumnDataType.STRING); - Assert.assertEquals(dataSchema.getColumnName(1), "decoded"); - Assert.assertEquals(dataSchema.getColumnDataType(1), DataSchema.ColumnDataType.STRING); - Assert.assertEquals(rows.size(), 1); - Assert.assertEquals(rows.get(0).length, 2); - Assert.assertEquals(rows.get(0)[0].toString(), "aGVsbG8h"); - Assert.assertEquals(rows.get(0)[1].toString(), "hello!"); - Assert.assertEquals(brokerResponse.getTotalDocs(), 0); - - request = JsonUtils.stringToJsonNode( - "{\"sql\":\"SELECT fromUtf8(fromBase64(toBase64(toUtf8('nested')))) AS output\"}"); - requestStats = Tracing.getTracer().createRequestScope(); - brokerResponse = requestHandler.handleRequest(request, null, requestStats); + assertEquals(dataSchema.getColumnName(0), "encoded"); + assertEquals(dataSchema.getColumnDataType(0), DataSchema.ColumnDataType.STRING); + assertEquals(dataSchema.getColumnName(1), "decoded"); + assertEquals(dataSchema.getColumnDataType(1), DataSchema.ColumnDataType.STRING); + assertEquals(rows.size(), 1); + assertEquals(rows.get(0).length, 2); + assertEquals(rows.get(0)[0].toString(), "aGVsbG8h"); + assertEquals(rows.get(0)[1].toString(), "hello!"); + assertEquals(brokerResponse.getTotalDocs(), 0); + + brokerResponse = requestHandler.handleRequest("SELECT fromUtf8(fromBase64(toBase64(toUtf8('nested')))) AS output"); resultTable = brokerResponse.getResultTable(); dataSchema = resultTable.getDataSchema(); rows = resultTable.getRows(); - Assert.assertEquals(dataSchema.getColumnName(0), "output"); - Assert.assertEquals(dataSchema.getColumnDataType(0), DataSchema.ColumnDataType.STRING); - Assert.assertEquals(rows.size(), 1); - Assert.assertEquals(rows.get(0).length, 1); - Assert.assertEquals(rows.get(0)[0].toString(), "nested"); - Assert.assertEquals(brokerResponse.getTotalDocs(), 0); - - request = JsonUtils.stringToJsonNode( - "{\"sql\":\"SELECT toBase64(toUtf8('this is a long string that will encode to more than 76 characters using " - + "base64'))" - + " AS encoded\"}"); - requestStats = Tracing.getTracer().createRequestScope(); - brokerResponse = requestHandler.handleRequest(request, null, requestStats); + assertEquals(dataSchema.getColumnName(0), "output"); + assertEquals(dataSchema.getColumnDataType(0), DataSchema.ColumnDataType.STRING); + assertEquals(rows.size(), 1); + assertEquals(rows.get(0).length, 1); + assertEquals(rows.get(0)[0].toString(), "nested"); + assertEquals(brokerResponse.getTotalDocs(), 0); + + brokerResponse = requestHandler.handleRequest( + "SELECT toBase64(toUtf8('this is a long string that will encode to more than 76 characters using base64')) " + + "AS encoded"); resultTable = brokerResponse.getResultTable(); dataSchema = resultTable.getDataSchema(); rows = resultTable.getRows(); - Assert.assertEquals(dataSchema.getColumnName(0), "encoded"); - Assert.assertEquals(dataSchema.getColumnDataType(0), DataSchema.ColumnDataType.STRING); - Assert.assertEquals(rows.size(), 1); - Assert.assertEquals(rows.get(0).length, 1); - Assert.assertEquals(rows.get(0)[0].toString(), + assertEquals(dataSchema.getColumnName(0), "encoded"); + assertEquals(dataSchema.getColumnDataType(0), DataSchema.ColumnDataType.STRING); + assertEquals(rows.size(), 1); + assertEquals(rows.get(0).length, 1); + assertEquals(rows.get(0)[0].toString(), "dGhpcyBpcyBhIGxvbmcgc3RyaW5nIHRoYXQgd2lsbCBlbmNvZGUgdG8gbW9yZSB0aGFuIDc2IGNoYXJhY3RlcnMgdXNpbmcgYmFzZTY0"); - Assert.assertEquals(brokerResponse.getTotalDocs(), 0); + assertEquals(brokerResponse.getTotalDocs(), 0); - request = JsonUtils.stringToJsonNode("{\"sql\":\"SELECT fromUtf8(fromBase64" - + "('dGhpcyBpcyBhIGxvbmcgc3RyaW5nIHRoYXQgd2lsbCBlbmNvZGUgdG8gbW9yZSB0aGFuIDc2IGNoYXJhY3RlcnMgdXNpbmcgYmFzZTY0" - + "')) AS decoded\"}"); - requestStats = Tracing.getTracer().createRequestScope(); - brokerResponse = requestHandler.handleRequest(request, null, requestStats); + brokerResponse = requestHandler.handleRequest("SELECT fromUtf8(fromBase64(" + + "'dGhpcyBpcyBhIGxvbmcgc3RyaW5nIHRoYXQgd2lsbCBlbmNvZGUgdG8gbW9yZSB0aGFuIDc2IGNoYXJhY3RlcnMgdXNpbmcgYmFzZTY0'" + + ")) AS decoded"); resultTable = brokerResponse.getResultTable(); dataSchema = resultTable.getDataSchema(); rows = resultTable.getRows(); - Assert.assertEquals(dataSchema.getColumnName(0), "decoded"); - Assert.assertEquals(dataSchema.getColumnDataType(0), DataSchema.ColumnDataType.STRING); - Assert.assertEquals(rows.size(), 1); - Assert.assertEquals(rows.get(0).length, 1); - Assert.assertEquals(rows.get(0)[0].toString(), + assertEquals(dataSchema.getColumnName(0), "decoded"); + assertEquals(dataSchema.getColumnDataType(0), DataSchema.ColumnDataType.STRING); + assertEquals(rows.size(), 1); + assertEquals(rows.get(0).length, 1); + assertEquals(rows.get(0)[0].toString(), "this is a long string that will encode to more than 76 characters using base64"); - Assert.assertEquals(brokerResponse.getTotalDocs(), 0); - - request = JsonUtils.stringToJsonNode("{\"sql\":\"SELECT fromBase64" + "(0) AS decoded\"}"); - requestStats = Tracing.getTracer().createRequestScope(); - brokerResponse = requestHandler.handleRequest(request, null, requestStats); - Assert.assertTrue( - brokerResponse.getProcessingExceptions().get(0).getMessage().contains("IllegalArgumentException")); - - request = JsonUtils.stringToJsonNode( - "{\"sql\":\"SELECT isSubnetOf('2001:db8:85a3::8a2e:370:7334/62', '2001:0db8:85a3:0003:ffff:ffff:ffff:ffff')" - + " as booleanCol\"}"); - requestStats = Tracing.getTracer().createRequestScope(); - brokerResponse = requestHandler.handleRequest(request, null, requestStats); + assertEquals(brokerResponse.getTotalDocs(), 0); + + brokerResponse = requestHandler.handleRequest("SELECT fromBase64(0) AS decoded"); + assertTrue(brokerResponse.getExceptions().get(0).getMessage().contains("IllegalArgumentException")); + + brokerResponse = requestHandler.handleRequest( + "SELECT isSubnetOf('2001:db8:85a3::8a2e:370:7334/62', '2001:0db8:85a3:0003:ffff:ffff:ffff:ffff') " + + "AS booleanCol"); resultTable = brokerResponse.getResultTable(); dataSchema = resultTable.getDataSchema(); rows = resultTable.getRows(); - Assert.assertEquals(dataSchema.getColumnName(0), "booleanCol"); - Assert.assertEquals(dataSchema.getColumnDataType(0), DataSchema.ColumnDataType.BOOLEAN); - Assert.assertEquals(rows.size(), 1); - Assert.assertEquals(rows.get(0).length, 1); - Assert.assertTrue((boolean) rows.get(0)[0]); - Assert.assertEquals(brokerResponse.getTotalDocs(), 0); + assertEquals(dataSchema.getColumnName(0), "booleanCol"); + assertEquals(dataSchema.getColumnDataType(0), DataSchema.ColumnDataType.BOOLEAN); + assertEquals(rows.size(), 1); + assertEquals(rows.get(0).length, 1); + assertTrue((boolean) rows.get(0)[0]); + assertEquals(brokerResponse.getTotalDocs(), 0); // first argument must be in prefix format - request = JsonUtils.stringToJsonNode( - "{\"sql\":\"SELECT isSubnetOf('2001:db8:85a3::8a2e:370:7334', '2001:0db8:85a3:0003:ffff:ffff:ffff:ffff') as" - + " booleanCol\"}"); - requestStats = Tracing.getTracer().createRequestScope(); - brokerResponse = requestHandler.handleRequest(request, null, requestStats); - Assert.assertTrue( - brokerResponse.getProcessingExceptions().get(0).getMessage().contains("IllegalArgumentException")); + brokerResponse = requestHandler.handleRequest( + "SELECT isSubnetOf('2001:db8:85a3::8a2e:370:7334', '2001:0db8:85a3:0003:ffff:ffff:ffff:ffff') AS booleanCol"); + assertTrue(brokerResponse.getExceptions().get(0).getMessage().contains("IllegalArgumentException")); // first argument must be in prefix format - request = JsonUtils.stringToJsonNode( - "{\"sql\":\"SELECT isSubnetOf('105.25.245.115', '105.25.245.115') as" + " booleanCol\"}"); - requestStats = Tracing.getTracer().createRequestScope(); - brokerResponse = requestHandler.handleRequest(request, null, requestStats); - Assert.assertTrue( - brokerResponse.getProcessingExceptions().get(0).getMessage().contains("IllegalArgumentException")); + brokerResponse = + requestHandler.handleRequest("SELECT isSubnetOf('105.25.245.115', '105.25.245.115') AS booleanCol"); + assertTrue(brokerResponse.getExceptions().get(0).getMessage().contains("IllegalArgumentException")); // second argument should not be a prefix - request = JsonUtils.stringToJsonNode( - "{\"sql\":\"SELECT isSubnetOf('1.2.3.128/26', '3.175.47.239/26') as" + " booleanCol\"}"); - requestStats = Tracing.getTracer().createRequestScope(); - brokerResponse = requestHandler.handleRequest(request, null, requestStats); - Assert.assertTrue( - brokerResponse.getProcessingExceptions().get(0).getMessage().contains("IllegalArgumentException")); + brokerResponse = requestHandler.handleRequest("SELECT isSubnetOf('1.2.3.128/26', '3.175.47.239/26') AS booleanCol"); + assertTrue(brokerResponse.getExceptions().get(0).getMessage().contains("IllegalArgumentException")); // second argument should not be a prefix - request = JsonUtils.stringToJsonNode( - "{\"sql\":\"SELECT isSubnetOf('5f3f:bfdb:1bbe:a824:6bf9:0fbb:d358:1889/64', " - + "'4275:386f:b2b5:0664:04aa:d7bd:0589:6909/64') as" - + " booleanCol\"}"); - requestStats = Tracing.getTracer().createRequestScope(); - brokerResponse = requestHandler.handleRequest(request, null, requestStats); - Assert.assertTrue( - brokerResponse.getProcessingExceptions().get(0).getMessage().contains("IllegalArgumentException")); + brokerResponse = requestHandler.handleRequest("SELECT isSubnetOf('5f3f:bfdb:1bbe:a824:6bf9:0fbb:d358:1889/64', " + + "'4275:386f:b2b5:0664:04aa:d7bd:0589:6909/64') AS booleanCol"); + assertTrue(brokerResponse.getExceptions().get(0).getMessage().contains("IllegalArgumentException")); // invalid prefix length - request = JsonUtils.stringToJsonNode( - "{\"sql\":\"SELECT isSubnetOf('2001:4801:7825:103:be76:4eff::/129', '2001:4801:7825:103:be76:4eff::') as" - + " booleanCol\"}"); - requestStats = Tracing.getTracer().createRequestScope(); - brokerResponse = requestHandler.handleRequest(request, null, requestStats); - Assert.assertTrue( - brokerResponse.getProcessingExceptions().get(0).getMessage().contains("IllegalArgumentException")); + brokerResponse = requestHandler.handleRequest( + "SELECT isSubnetOf('2001:4801:7825:103:be76:4eff::/129', '2001:4801:7825:103:be76:4eff::') AS booleanCol"); + assertTrue(brokerResponse.getExceptions().get(0).getMessage().contains("IllegalArgumentException")); // invalid prefix length - request = JsonUtils.stringToJsonNode( - "{\"sql\":\"SELECT isSubnetOf('170.189.0.175/33', '170.189.0.175') as" + " booleanCol\"}"); - requestStats = Tracing.getTracer().createRequestScope(); - brokerResponse = requestHandler.handleRequest(request, null, requestStats); - Assert.assertTrue( - brokerResponse.getProcessingExceptions().get(0).getMessage().contains("IllegalArgumentException")); + brokerResponse = + requestHandler.handleRequest("SELECT isSubnetOf('170.189.0.175/33', '170.189.0.175') AS booleanCol"); + assertTrue(brokerResponse.getExceptions().get(0).getMessage().contains("IllegalArgumentException")); } /** Tests for EXPLAIN PLAN for literal only queries. */ @@ -416,54 +346,41 @@ null, new BrokerMetrics("", PinotMetricUtils.getPinotMetricsRegistry(), true, Co public void testExplainPlanLiteralOnly() throws Exception { SingleConnectionBrokerRequestHandler requestHandler = - new SingleConnectionBrokerRequestHandler(new PinotConfiguration(), null, null, ACCESS_CONTROL_FACTORY, null, - null, new BrokerMetrics("", PinotMetricUtils.getPinotMetricsRegistry(), true, Collections.emptySet()), null, - null, mock(ServerRoutingStatsManager.class)); + new SingleConnectionBrokerRequestHandler(new PinotConfiguration(), "testBrokerId", null, ACCESS_CONTROL_FACTORY, + null, null, null, null, mock(ServerRoutingStatsManager.class)); // Test 1: select constant - JsonNode request = JsonUtils.stringToJsonNode("{\"sql\":\"EXPLAIN PLAN FOR SELECT 1.5, 'test'\"}"); - RequestContext requestStats = Tracing.getTracer().createRequestScope(); - BrokerResponse brokerResponse = requestHandler.handleRequest(request, null, requestStats); + BrokerResponse brokerResponse = requestHandler.handleRequest("EXPLAIN PLAN FOR SELECT 1.5, 'test'"); checkExplainResultSchema(brokerResponse.getResultTable().getDataSchema(), - new String[]{"Operator", "Operator_Id", "Parent_Id"}, - new DataSchema.ColumnDataType[]{ - DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT, - DataSchema.ColumnDataType.INT + new String[]{"Operator", "Operator_Id", "Parent_Id"}, new DataSchema.ColumnDataType[]{ + DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.INT }); - Assert.assertEquals(brokerResponse.getResultTable().getRows().size(), 1); - Assert.assertEquals(brokerResponse.getResultTable().getRows().get(0), - new Object[]{"BROKER_EVALUATE", 0, -1}); - Assert.assertEquals(brokerResponse.getTotalDocs(), 0); + assertEquals(brokerResponse.getResultTable().getRows().size(), 1); + assertEquals(brokerResponse.getResultTable().getRows().get(0), new Object[]{"BROKER_EVALUATE", 0, -1}); + assertEquals(brokerResponse.getTotalDocs(), 0); // Test 2: invoke compile time function -> literal only - long currentTsMin = System.currentTimeMillis(); - request = JsonUtils.stringToJsonNode( - "{\"sql\":\"EXPLAIN PLAN FOR SELECT 6+8 as addition, fromDateTime('2020-01-01 UTC', 'yyyy-MM-dd z') as " - + "firstDayOf2020\"}"); - requestStats = Tracing.getTracer().createRequestScope(); - brokerResponse = requestHandler.handleRequest(request, null, requestStats); + requestHandler.handleRequest( + "EXPLAIN PLAN FOR SELECT 6+8 AS addition, fromDateTime('2020-01-01 UTC', 'yyyy-MM-dd z') AS firstDayOf2020"); checkExplainResultSchema(brokerResponse.getResultTable().getDataSchema(), - new String[]{"Operator", "Operator_Id", "Parent_Id"}, - new DataSchema.ColumnDataType[]{ - DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT, - DataSchema.ColumnDataType.INT + new String[]{"Operator", "Operator_Id", "Parent_Id"}, new DataSchema.ColumnDataType[]{ + DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.INT }); - Assert.assertEquals(brokerResponse.getResultTable().getRows().size(), 1); - Assert.assertEquals(brokerResponse.getResultTable().getRows().get(0), - new Object[]{"BROKER_EVALUATE", 0, -1}); + assertEquals(brokerResponse.getResultTable().getRows().size(), 1); + assertEquals(brokerResponse.getResultTable().getRows().get(0), new Object[]{"BROKER_EVALUATE", 0, -1}); - Assert.assertEquals(brokerResponse.getTotalDocs(), 0); + assertEquals(brokerResponse.getTotalDocs(), 0); } private void checkExplainResultSchema(DataSchema schema, String[] columnNames, DataSchema.ColumnDataType[] columnTypes) { for (int i = 0; i < columnNames.length; i++) { - Assert.assertEquals(schema.getColumnName(i), columnNames[i]); - Assert.assertEquals(schema.getColumnDataType(i), columnTypes[i]); + assertEquals(schema.getColumnName(i), columnNames[i]); + assertEquals(schema.getColumnDataType(i), columnTypes[i]); } } } diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/QueryOverrideTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/QueryOverrideTest.java index 8ed7ceb3b070..22137c137bb6 100644 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/QueryOverrideTest.java +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/QueryOverrideTest.java @@ -19,9 +19,11 @@ package org.apache.pinot.broker.requesthandler; import com.google.common.collect.ImmutableSet; +import java.io.IOException; import java.util.Arrays; import org.apache.pinot.common.request.PinotQuery; import org.apache.pinot.common.utils.request.RequestUtils; +import org.apache.pinot.spi.data.Schema; import org.apache.pinot.sql.parsers.CalciteSqlParser; import org.testng.annotations.Test; @@ -48,7 +50,7 @@ public void testLimitOverride() { private void testLimitOverride(String query, int expectedLimit) { PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(query); - BaseBrokerRequestHandler.handleQueryLimitOverride(pinotQuery, QUERY_LIMIT); + BaseSingleStageBrokerRequestHandler.handleQueryLimitOverride(pinotQuery, QUERY_LIMIT); assertEquals(pinotQuery.getLimit(), expectedLimit); } @@ -56,20 +58,39 @@ private void testLimitOverride(String query, int expectedLimit) { public void testDistinctCountOverride() { String query = "SELECT DISTINCT_COUNT(col1) FROM myTable"; PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(query); - BaseBrokerRequestHandler.handleSegmentPartitionedDistinctCountOverride(pinotQuery, ImmutableSet.of("col2", "col3")); + BaseSingleStageBrokerRequestHandler.handleSegmentPartitionedDistinctCountOverride(pinotQuery, + ImmutableSet.of("col2", "col3")); assertEquals(pinotQuery.getSelectList().get(0).getFunctionCall().getOperator(), "distinctcount"); - BaseBrokerRequestHandler.handleSegmentPartitionedDistinctCountOverride(pinotQuery, + BaseSingleStageBrokerRequestHandler.handleSegmentPartitionedDistinctCountOverride(pinotQuery, ImmutableSet.of("col1", "col2", "col3")); assertEquals(pinotQuery.getSelectList().get(0).getFunctionCall().getOperator(), "segmentpartitioneddistinctcount"); } + @Test + public void testDistinctMultiValuedOverride() + throws IOException { + String query1 = "SELECT DISTINCT_COUNT(col1) FROM myTable"; + PinotQuery pinotQuery1 = CalciteSqlParser.compileToPinotQuery(query1); + String query2 = "SELECT DISTINCT_COUNT(col2) FROM myTable"; + PinotQuery pinotQuery2 = CalciteSqlParser.compileToPinotQuery(query2); + Schema tableSchema = Schema.fromString("{\"schemaName\":\"testSchema\"," + + "\"dimensionFieldSpecs\":[ {\"name\":\"col2\",\"dataType\":\"LONG\",\"singleValueField\":\"false\"}," + + "{\"name\":\"col3\",\"dataType\":\"LONG\",\"singleValueField\":\"false\"}]," + + "\"dateTimeFieldSpecs\":[{\"name\":\"dt1\",\"dataType\":\"INT\",\"format\":\"x:HOURS:EPOCH\"," + + "\"granularity\":\"1:HOURS\"}]}"); + BaseSingleStageBrokerRequestHandler.handleDistinctMultiValuedOverride(pinotQuery1, tableSchema); + assertEquals(pinotQuery1.getSelectList().get(0).getFunctionCall().getOperator(), "distinctcount"); + BaseSingleStageBrokerRequestHandler.handleDistinctMultiValuedOverride(pinotQuery2, tableSchema); + assertEquals(pinotQuery2.getSelectList().get(0).getFunctionCall().getOperator(), "distinctcountmv"); + } + @Test public void testApproximateFunctionOverride() { { String query = "SELECT DISTINCT_COUNT(col1) FROM myTable GROUP BY col2 HAVING DISTINCT_COUNT(col1) > 10 " + "ORDER BY DISTINCT_COUNT(col1) DESC"; PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(query); - BaseBrokerRequestHandler.handleApproximateFunctionOverride(pinotQuery); + BaseSingleStageBrokerRequestHandler.handleApproximateFunctionOverride(pinotQuery); assertEquals(pinotQuery.getSelectList().get(0).getFunctionCall().getOperator(), "distinctcountsmarthll"); assertEquals( pinotQuery.getOrderByList().get(0).getFunctionCall().getOperands().get(0).getFunctionCall().getOperator(), @@ -80,22 +101,22 @@ public void testApproximateFunctionOverride() { query = "SELECT DISTINCT_COUNT_MV(col1) FROM myTable"; pinotQuery = CalciteSqlParser.compileToPinotQuery(query); - BaseBrokerRequestHandler.handleApproximateFunctionOverride(pinotQuery); + BaseSingleStageBrokerRequestHandler.handleApproximateFunctionOverride(pinotQuery); assertEquals(pinotQuery.getSelectList().get(0).getFunctionCall().getOperator(), "distinctcountsmarthll"); query = "SELECT DISTINCT col1 FROM myTable"; pinotQuery = CalciteSqlParser.compileToPinotQuery(query); - BaseBrokerRequestHandler.handleApproximateFunctionOverride(pinotQuery); + BaseSingleStageBrokerRequestHandler.handleApproximateFunctionOverride(pinotQuery); assertEquals(pinotQuery.getSelectList().get(0).getFunctionCall().getOperator(), "distinct"); query = "SELECT DISTINCT_COUNT_HLL(col1) FROM myTable"; pinotQuery = CalciteSqlParser.compileToPinotQuery(query); - BaseBrokerRequestHandler.handleApproximateFunctionOverride(pinotQuery); + BaseSingleStageBrokerRequestHandler.handleApproximateFunctionOverride(pinotQuery); assertEquals(pinotQuery.getSelectList().get(0).getFunctionCall().getOperator(), "distinctcounthll"); query = "SELECT DISTINCT_COUNT_BITMAP(col1) FROM myTable"; pinotQuery = CalciteSqlParser.compileToPinotQuery(query); - BaseBrokerRequestHandler.handleApproximateFunctionOverride(pinotQuery); + BaseSingleStageBrokerRequestHandler.handleApproximateFunctionOverride(pinotQuery); assertEquals(pinotQuery.getSelectList().get(0).getFunctionCall().getOperator(), "distinctcountbitmap"); } @@ -103,7 +124,7 @@ public void testApproximateFunctionOverride() { "SELECT PERCENTILE_MV(col1, 95) FROM myTable", "SELECT PERCENTILE95(col1) FROM myTable", "SELECT PERCENTILE95MV(col1) FROM myTable")) { PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(query); - BaseBrokerRequestHandler.handleApproximateFunctionOverride(pinotQuery); + BaseSingleStageBrokerRequestHandler.handleApproximateFunctionOverride(pinotQuery); assertEquals(pinotQuery.getSelectList().get(0).getFunctionCall().getOperator(), "percentilesmarttdigest"); assertEquals(pinotQuery.getSelectList().get(0).getFunctionCall().getOperands().get(1), RequestUtils.getLiteralExpression(95)); @@ -111,12 +132,12 @@ public void testApproximateFunctionOverride() { { String query = "SELECT PERCENTILE_TDIGEST(col1, 95) FROM myTable"; PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(query); - BaseBrokerRequestHandler.handleApproximateFunctionOverride(pinotQuery); + BaseSingleStageBrokerRequestHandler.handleApproximateFunctionOverride(pinotQuery); assertEquals(pinotQuery.getSelectList().get(0).getFunctionCall().getOperator(), "percentiletdigest"); query = "SELECT PERCENTILE_EST(col1, 95) FROM myTable"; pinotQuery = CalciteSqlParser.compileToPinotQuery(query); - BaseBrokerRequestHandler.handleApproximateFunctionOverride(pinotQuery); + BaseSingleStageBrokerRequestHandler.handleApproximateFunctionOverride(pinotQuery); assertEquals(pinotQuery.getSelectList().get(0).getFunctionCall().getOperator(), "percentileest"); } } diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/QueryValidationTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/QueryValidationTest.java index 4772c0ddacb3..f46833b0783b 100644 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/QueryValidationTest.java +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/QueryValidationTest.java @@ -115,16 +115,17 @@ public void testRejectGroovyQuery() { @Test public void testReplicaGroupToQueryInvalidQuery() { - PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery( - "SET numReplicaGroupsToQuery='illegal'; SELECT COUNT(*) FROM MY_TABLE"); - Assert.assertThrows(IllegalStateException.class, () -> BaseBrokerRequestHandler.validateRequest(pinotQuery, 10)); + PinotQuery pinotQuery = + CalciteSqlParser.compileToPinotQuery("SET numReplicaGroupsToQuery='illegal'; SELECT COUNT(*) FROM MY_TABLE"); + Assert.assertThrows(IllegalStateException.class, + () -> BaseSingleStageBrokerRequestHandler.validateRequest(pinotQuery, 10)); } private void testRejectGroovyQuery(String query, boolean queryContainsGroovy) { PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(query); try { - BaseBrokerRequestHandler.rejectGroovyQuery(pinotQuery); + BaseSingleStageBrokerRequestHandler.rejectGroovyQuery(pinotQuery); if (queryContainsGroovy) { Assert.fail("Query should have failed since groovy was found in query: " + pinotQuery); } @@ -136,7 +137,7 @@ private void testRejectGroovyQuery(String query, boolean queryContainsGroovy) { private void testUnsupportedQuery(String query, String errorMessage) { try { PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(query); - BaseBrokerRequestHandler.validateRequest(pinotQuery, 1000); + BaseSingleStageBrokerRequestHandler.validateRequest(pinotQuery, 1000); Assert.fail("Query should have failed"); } catch (Exception e) { Assert.assertEquals(e.getMessage(), errorMessage); @@ -147,7 +148,7 @@ private void testNonExistingColumns(String rawTableName, boolean isCaseInsensiti String query, String errorMessage) { try { PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(query); - BaseBrokerRequestHandler.updateColumnNames(rawTableName, pinotQuery, isCaseInsensitive, columnNameMap); + BaseSingleStageBrokerRequestHandler.updateColumnNames(rawTableName, pinotQuery, isCaseInsensitive, columnNameMap); Assert.fail("Query should have failed"); } catch (Exception e) { Assert.assertEquals(errorMessage, e.getMessage()); @@ -158,7 +159,7 @@ private void testExistingColumns(String rawTableName, boolean isCaseInsensitive, String query) { try { PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(query); - BaseBrokerRequestHandler.updateColumnNames(rawTableName, pinotQuery, isCaseInsensitive, columnNameMap); + BaseSingleStageBrokerRequestHandler.updateColumnNames(rawTableName, pinotQuery, isCaseInsensitive, columnNameMap); } catch (Exception e) { Assert.fail("Query should have succeeded"); } diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/SelectStarWithOtherColsRewriteTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/SelectStarWithOtherColsRewriteTest.java index 4c490b94ac31..3b8c6ac7cd2b 100644 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/SelectStarWithOtherColsRewriteTest.java +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/requesthandler/SelectStarWithOtherColsRewriteTest.java @@ -57,7 +57,7 @@ public class SelectStarWithOtherColsRewriteTest { public void testShouldExpandWhenOnlyStarIsSelected() { String sql = "SELECT * FROM baseballStats"; PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(sql); - BaseBrokerRequestHandler.updateColumnNames("baseballStats", pinotQuery, false, COL_MAP); + BaseSingleStageBrokerRequestHandler.updateColumnNames("baseballStats", pinotQuery, false, COL_MAP); List newSelections = pinotQuery.getSelectList(); Map countMap = new HashMap<>(); for (Expression selection : newSelections) { @@ -79,7 +79,7 @@ public void testShouldExpandWhenOnlyStarIsSelected() { public void testShouldNotReturnExtraDefaultColumns() { String sql = "SELECT $docId,*,$segmentName FROM baseballStats"; PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(sql); - BaseBrokerRequestHandler.updateColumnNames("baseballStats", pinotQuery, false, COL_MAP); + BaseSingleStageBrokerRequestHandler.updateColumnNames("baseballStats", pinotQuery, false, COL_MAP); List newSelections = pinotQuery.getSelectList(); int docIdCnt = 0; int segmentNameCnt = 0; @@ -108,7 +108,7 @@ public void testShouldNotReturnExtraDefaultColumns() { public void testShouldNotDedupMultipleRequestedColumns() { String sql = "SELECT playerID,*,G_old FROM baseballStats"; PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(sql); - BaseBrokerRequestHandler.updateColumnNames("baseballStats", pinotQuery, false, COL_MAP); + BaseSingleStageBrokerRequestHandler.updateColumnNames("baseballStats", pinotQuery, false, COL_MAP); List newSelections = pinotQuery.getSelectList(); int playerIdCnt = 0; int goldCount = 0; @@ -135,7 +135,7 @@ public void testShouldNotDedupMultipleRequestedColumns() { public void testSelectionOrder() { String sql = "SELECT playerID,*,G_old FROM baseballStats"; PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(sql); - BaseBrokerRequestHandler.updateColumnNames("baseballStats", pinotQuery, false, COL_MAP); + BaseSingleStageBrokerRequestHandler.updateColumnNames("baseballStats", pinotQuery, false, COL_MAP); List newSelections = pinotQuery.getSelectList(); Assert.assertEquals(newSelections.get(0).getIdentifier().getName(), "playerID"); Assert.assertEquals(newSelections.get(newSelections.size() - 1).getIdentifier().getName(), "G_old"); @@ -154,7 +154,7 @@ public void testSelectionOrder() { public void testAliasing() { String sql = "SELECT playerID as pid,* FROM baseballStats"; PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(sql); - BaseBrokerRequestHandler.updateColumnNames("baseballStats", pinotQuery, false, COL_MAP); + BaseSingleStageBrokerRequestHandler.updateColumnNames("baseballStats", pinotQuery, false, COL_MAP); List newSelections = pinotQuery.getSelectList(); Assert.assertTrue(newSelections.get(0).isSetFunctionCall()); Assert.assertEquals(newSelections.get(0).getFunctionCall().getOperator(), "as"); @@ -178,7 +178,7 @@ public void testAliasing() { public void testFuncOnColumns1() { String sql = "SELECT sqrt(homeRuns),* FROM baseballStats"; PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(sql); - BaseBrokerRequestHandler.updateColumnNames("baseballStats", pinotQuery, false, COL_MAP); + BaseSingleStageBrokerRequestHandler.updateColumnNames("baseballStats", pinotQuery, false, COL_MAP); List newSelections = pinotQuery.getSelectList(); Assert.assertTrue(newSelections.get(0).isSetFunctionCall()); Assert.assertEquals(newSelections.get(0).getFunctionCall().getOperator(), "sqrt"); @@ -201,7 +201,7 @@ public void testFuncOnColumns1() { public void testFuncOnColumns2() { String sql = "SELECT add(homeRuns,groundedIntoDoublePlays),* FROM baseballStats"; PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(sql); - BaseBrokerRequestHandler.updateColumnNames("baseballStats", pinotQuery, false, COL_MAP); + BaseSingleStageBrokerRequestHandler.updateColumnNames("baseballStats", pinotQuery, false, COL_MAP); List newSelections = pinotQuery.getSelectList(); Assert.assertTrue(newSelections.get(0).isSetFunctionCall()); Assert.assertEquals(newSelections.get(0).getFunctionCall().getOperator(), "add"); @@ -230,7 +230,7 @@ public void testFuncOnColumns2() { public void testMultipleUnqualifiedStars() { String sql = "SELECT *,* FROM baseballStats"; PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(sql); - BaseBrokerRequestHandler.updateColumnNames("baseballStats", pinotQuery, false, COL_MAP); + BaseSingleStageBrokerRequestHandler.updateColumnNames("baseballStats", pinotQuery, false, COL_MAP); List newSelections = pinotQuery.getSelectList(); Assert.assertEquals(newSelections.get(0).getIdentifier().getName(), "G_old"); Assert.assertEquals(newSelections.get(1).getIdentifier().getName(), "groundedIntoDoublePlays"); @@ -250,7 +250,7 @@ public void testAll() { "SELECT abs(homeRuns),sqrt(groundedIntoDoublePlays),*,$segmentName,$hostName,playerStint as pstint,playerID " + "FROM baseballStats"; PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(sql); - BaseBrokerRequestHandler.updateColumnNames("baseballStats", pinotQuery, false, COL_MAP); + BaseSingleStageBrokerRequestHandler.updateColumnNames("baseballStats", pinotQuery, false, COL_MAP); List newSelections = pinotQuery.getSelectList(); Assert.assertTrue(newSelections.get(0).isSetFunctionCall()); Assert.assertEquals(newSelections.get(0).getFunctionCall().getOperator(), "abs"); diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/routing/adaptiveserverselector/AdaptiveServerSelectorTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/routing/adaptiveserverselector/AdaptiveServerSelectorTest.java index ed976b3b03fe..427065b76eec 100644 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/routing/adaptiveserverselector/AdaptiveServerSelectorTest.java +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/routing/adaptiveserverselector/AdaptiveServerSelectorTest.java @@ -20,16 +20,21 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import org.apache.commons.lang3.tuple.Pair; +import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.utils.ExponentialMovingAverage; import org.apache.pinot.core.transport.server.routing.stats.ServerRoutingStatsManager; import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.metrics.PinotMetricUtils; +import org.apache.pinot.spi.metrics.PinotMetricsRegistry; import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.util.TestUtils; +import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; @@ -39,16 +44,39 @@ public class AdaptiveServerSelectorTest { + private BrokerMetrics _brokerMetrics; + List _servers = Arrays.asList("server1", "server2", "server3", "server4"); Map _properties = new HashMap<>(); + @BeforeTest + public void initBrokerMetrics() { + // Set up metric registry and broker metrics + PinotConfiguration brokerConfig = new PinotConfiguration(); + PinotMetricsRegistry metricsRegistry = PinotMetricUtils.getPinotMetricsRegistry( + brokerConfig.subset(CommonConstants.Broker.METRICS_CONFIG_PREFIX)); + _brokerMetrics = new BrokerMetrics( + brokerConfig.getProperty( + CommonConstants.Broker.CONFIG_OF_METRICS_NAME_PREFIX, + CommonConstants.Broker.DEFAULT_METRICS_NAME_PREFIX), + metricsRegistry, + brokerConfig.getProperty( + CommonConstants.Broker.CONFIG_OF_ENABLE_TABLE_LEVEL_METRICS, + CommonConstants.Broker.DEFAULT_ENABLE_TABLE_LEVEL_METRICS), + brokerConfig.getProperty( + CommonConstants.Broker.CONFIG_OF_ALLOWED_TABLES_FOR_EMITTING_METRICS, + Collections.emptyList())); + _brokerMetrics.initializeGlobalMeters(); + BrokerMetrics.register(_brokerMetrics); + } + @Test public void testAdaptiveServerSelectorFactory() { // Test 1: Test disabling Adaptive Server Selection . _properties.put(CommonConstants.Broker.AdaptiveServerSelector.CONFIG_OF_TYPE, CommonConstants.Broker.AdaptiveServerSelector.Type.NO_OP.name()); PinotConfiguration cfg = new PinotConfiguration(_properties); - ServerRoutingStatsManager serverRoutingStatsManager = new ServerRoutingStatsManager(cfg); + ServerRoutingStatsManager serverRoutingStatsManager = new ServerRoutingStatsManager(cfg, _brokerMetrics); assertNull(AdaptiveServerSelectorFactory.getAdaptiveServerSelector(serverRoutingStatsManager, cfg)); // Enable stats collection. Without this, AdaptiveServerSelectors cannot be used. @@ -58,7 +86,7 @@ public void testAdaptiveServerSelectorFactory() { _properties.put(CommonConstants.Broker.AdaptiveServerSelector.CONFIG_OF_TYPE, CommonConstants.Broker.AdaptiveServerSelector.Type.NUM_INFLIGHT_REQ.name()); cfg = new PinotConfiguration(_properties); - serverRoutingStatsManager = new ServerRoutingStatsManager(cfg); + serverRoutingStatsManager = new ServerRoutingStatsManager(cfg, _brokerMetrics); assertTrue(AdaptiveServerSelectorFactory.getAdaptiveServerSelector(serverRoutingStatsManager, cfg) instanceof NumInFlightReqSelector); @@ -66,7 +94,7 @@ public void testAdaptiveServerSelectorFactory() { _properties.put(CommonConstants.Broker.AdaptiveServerSelector.CONFIG_OF_TYPE, CommonConstants.Broker.AdaptiveServerSelector.Type.LATENCY.name()); cfg = new PinotConfiguration(_properties); - serverRoutingStatsManager = new ServerRoutingStatsManager(cfg); + serverRoutingStatsManager = new ServerRoutingStatsManager(cfg, _brokerMetrics); assertTrue(AdaptiveServerSelectorFactory.getAdaptiveServerSelector(serverRoutingStatsManager, cfg) instanceof LatencySelector); @@ -74,7 +102,7 @@ public void testAdaptiveServerSelectorFactory() { _properties.put(CommonConstants.Broker.AdaptiveServerSelector.CONFIG_OF_TYPE, CommonConstants.Broker.AdaptiveServerSelector.Type.HYBRID.name()); cfg = new PinotConfiguration(_properties); - serverRoutingStatsManager = new ServerRoutingStatsManager(cfg); + serverRoutingStatsManager = new ServerRoutingStatsManager(cfg, _brokerMetrics); assertTrue(AdaptiveServerSelectorFactory.getAdaptiveServerSelector(serverRoutingStatsManager, cfg) instanceof HybridSelector); @@ -82,7 +110,7 @@ public void testAdaptiveServerSelectorFactory() { assertThrows(IllegalArgumentException.class, () -> { _properties.put(CommonConstants.Broker.AdaptiveServerSelector.CONFIG_OF_TYPE, "Dummy"); PinotConfiguration config = new PinotConfiguration(_properties); - ServerRoutingStatsManager manager = new ServerRoutingStatsManager(config); + ServerRoutingStatsManager manager = new ServerRoutingStatsManager(config, _brokerMetrics); AdaptiveServerSelectorFactory.getAdaptiveServerSelector(manager, config); }); } @@ -91,7 +119,7 @@ public void testAdaptiveServerSelectorFactory() { public void testNumInFlightReqSelector() { _properties.put(CommonConstants.Broker.AdaptiveServerSelector.CONFIG_OF_ENABLE_STATS_COLLECTION, true); PinotConfiguration cfg = new PinotConfiguration(_properties); - ServerRoutingStatsManager serverRoutingStatsManager = new ServerRoutingStatsManager(cfg); + ServerRoutingStatsManager serverRoutingStatsManager = new ServerRoutingStatsManager(cfg, _brokerMetrics); serverRoutingStatsManager.init(); assertTrue(serverRoutingStatsManager.isEnabled()); long taskCount = 0; @@ -125,7 +153,7 @@ public void testNumInFlightReqSelector() { } for (int ii = 0; ii < 10; ii++) { for (String server : _servers) { - serverRoutingStatsManager.recordStatsAfterQuerySubmission(-1, server); + serverRoutingStatsManager.recordStatsForQuerySubmission(-1, server); waitForStatsUpdate(serverRoutingStatsManager, ++taskCount); } } @@ -159,7 +187,7 @@ public void testNumInFlightReqSelector() { for (int ii = 0; ii < _servers.size(); ii++) { for (int jj = 0; jj < ii; jj++) { - serverRoutingStatsManager.recordStatsAfterQuerySubmission(-1, _servers.get(ii)); + serverRoutingStatsManager.recordStatsForQuerySubmission(-1, _servers.get(ii)); waitForStatsUpdate(serverRoutingStatsManager, ++taskCount); } } @@ -204,15 +232,15 @@ public void testNumInFlightReqSelector() { numInflightReqMap.put("server2", 11); numInflightReqMap.put("server3", 15); numInflightReqMap.put("server4", 13); - serverRoutingStatsManager.recordStatsAfterQuerySubmission(-1, _servers.get(0)); + serverRoutingStatsManager.recordStatsForQuerySubmission(-1, _servers.get(0)); waitForStatsUpdate(serverRoutingStatsManager, ++taskCount); - serverRoutingStatsManager.recordStatsAfterQuerySubmission(-1, _servers.get(0)); + serverRoutingStatsManager.recordStatsForQuerySubmission(-1, _servers.get(0)); waitForStatsUpdate(serverRoutingStatsManager, ++taskCount); - serverRoutingStatsManager.recordStatsAfterQuerySubmission(-1, _servers.get(2)); + serverRoutingStatsManager.recordStatsForQuerySubmission(-1, _servers.get(2)); waitForStatsUpdate(serverRoutingStatsManager, ++taskCount); - serverRoutingStatsManager.recordStatsAfterQuerySubmission(-1, _servers.get(2)); + serverRoutingStatsManager.recordStatsForQuerySubmission(-1, _servers.get(2)); waitForStatsUpdate(serverRoutingStatsManager, ++taskCount); - serverRoutingStatsManager.recordStatsAfterQuerySubmission(-1, _servers.get(2)); + serverRoutingStatsManager.recordStatsForQuerySubmission(-1, _servers.get(2)); waitForStatsUpdate(serverRoutingStatsManager, ++taskCount); serverRankingWithVal = selector.fetchAllServerRankingsWithScores(); @@ -262,7 +290,7 @@ public void testNumInFlightReqSelector() { // Route the request to the best server. selectedServer = serverRankingWithVal.get(0).getLeft(); - serverRoutingStatsManager.recordStatsAfterQuerySubmission(-1, selectedServer); + serverRoutingStatsManager.recordStatsForQuerySubmission(-1, selectedServer); waitForStatsUpdate(serverRoutingStatsManager, ++taskCount); int numReq = numInflightReqMap.get(selectedServer) + 1; numInflightReqMap.put(selectedServer, numReq); @@ -290,7 +318,7 @@ public void testLatencySelector() { _properties.put(CommonConstants.Broker.AdaptiveServerSelector.CONFIG_OF_AVG_INITIALIZATION_VAL, avgInitializationVal); PinotConfiguration cfg = new PinotConfiguration(_properties); - ServerRoutingStatsManager serverRoutingStatsManager = new ServerRoutingStatsManager(cfg); + ServerRoutingStatsManager serverRoutingStatsManager = new ServerRoutingStatsManager(cfg, _brokerMetrics); serverRoutingStatsManager.init(); assertTrue(serverRoutingStatsManager.isEnabled()); long taskCount = 0; @@ -430,7 +458,7 @@ public void testHybridSelector() { hybridSelectorExponent); PinotConfiguration cfg = new PinotConfiguration(_properties); - ServerRoutingStatsManager serverRoutingStatsManager = new ServerRoutingStatsManager(cfg); + ServerRoutingStatsManager serverRoutingStatsManager = new ServerRoutingStatsManager(cfg, _brokerMetrics); serverRoutingStatsManager.init(); assertTrue(serverRoutingStatsManager.isEnabled()); @@ -456,7 +484,7 @@ public void testHybridSelector() { // TEST 2: Populate all servers with equal numInFlightRequests and latencies. for (int ii = 0; ii < 10; ii++) { for (String server : _servers) { - serverRoutingStatsManager.recordStatsAfterQuerySubmission(-1, server); + serverRoutingStatsManager.recordStatsForQuerySubmission(-1, server); waitForStatsUpdate(serverRoutingStatsManager, ++taskCount); } } @@ -543,7 +571,7 @@ public void testHybridSelector() { // Route the request to the best server. selectedServer = serverRankingWithVal.get(0).getLeft(); - serverRoutingStatsManager.recordStatsAfterQuerySubmission(-1, selectedServer); + serverRoutingStatsManager.recordStatsForQuerySubmission(-1, selectedServer); waitForStatsUpdate(serverRoutingStatsManager, ++taskCount); if (rand.nextBoolean()) { diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelectorTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelectorTest.java index 07efbe101abc..f4edc28e67d3 100644 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelectorTest.java +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/routing/instanceselector/InstanceSelectorTest.java @@ -20,6 +20,12 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -29,63 +35,184 @@ import java.util.Map; import java.util.Set; import java.util.TreeMap; +import java.util.concurrent.TimeUnit; +import org.apache.commons.lang3.tuple.Pair; import org.apache.helix.model.ExternalView; import org.apache.helix.model.IdealState; import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; -import org.apache.pinot.broker.routing.adaptiveserverselector.AdaptiveServerSelector; import org.apache.pinot.common.assignment.InstancePartitions; +import org.apache.pinot.common.metadata.ZKMetadataProvider; +import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.common.request.PinotQuery; +import org.apache.pinot.common.utils.TestClock; import org.apache.pinot.spi.config.table.RoutingConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.env.PinotConfiguration; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import static org.apache.pinot.broker.routing.instanceselector.InstanceSelector.NEW_SEGMENT_EXPIRATION_MILLIS; +import static org.apache.pinot.spi.config.table.RoutingConfig.REPLICA_GROUP_INSTANCE_SELECTOR_TYPE; +import static org.apache.pinot.spi.config.table.RoutingConfig.STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE; import static org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel.CONSUMING; import static org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel.ERROR; import static org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel.OFFLINE; import static org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel.ONLINE; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; +@SuppressWarnings("unchecked") public class InstanceSelectorTest { - private final static List SEGMENTS = Arrays.asList("segment0", "segment1", "segment2", "segment3", "segment4", - "segment5", "segment6", "segment7", "segment8", "segment9", "segment10", "segment11"); + private AutoCloseable _mocks; + + @Mock + private ZkHelixPropertyStore _propertyStore; + + @Mock + private BrokerMetrics _brokerMetrics; + + @Mock + private BrokerRequest _brokerRequest; + + @Mock + private PinotQuery _pinotQuery; + + @Mock + private TableConfig _tableConfig; + + private TestClock _mutableClock; + + private static final String TABLE_NAME = "testTable_OFFLINE"; + + private static final String BALANCED_INSTANCE_SELECTOR = "balanced"; + + private static List getSegments() { + return SEGMENTS; + } + + private final static List SEGMENTS = + Arrays.asList("segment0", "segment1", "segment2", "segment3", "segment4", "segment5", "segment6", "segment7", + "segment8", "segment9", "segment10", "segment11"); + + private void createSegments(List> segmentCreationTimeMsPairs) { + List segmentZKMetadataPaths = new ArrayList<>(); + List zkRecords = new ArrayList<>(); + for (Pair segmentCreationTimeMsPair : segmentCreationTimeMsPairs) { + String segment = segmentCreationTimeMsPair.getLeft(); + long creationTimeMs = segmentCreationTimeMsPair.getRight(); + SegmentZKMetadata offlineSegmentZKMetadata0 = new SegmentZKMetadata(segment); + offlineSegmentZKMetadata0.setCreationTime(creationTimeMs); + offlineSegmentZKMetadata0.setTimeUnit(TimeUnit.MILLISECONDS); + ZNRecord record = offlineSegmentZKMetadata0.toZNRecord(); + segmentZKMetadataPaths.add(ZKMetadataProvider.constructPropertyStorePathForSegment(TABLE_NAME, segment)); + zkRecords.add(record); + } + when(_propertyStore.get(eq(segmentZKMetadataPaths), any(), anyInt(), anyBoolean())).thenReturn(zkRecords); + } + + private IdealState createIdealState(Map>> segmentState) { + IdealState idealState = new IdealState(TABLE_NAME); + Map> idealStateSegmentAssignment = idealState.getRecord().getMapFields(); + for (Map.Entry>> entry : segmentState.entrySet()) { + Map externalViewInstanceStateMap = new TreeMap<>(); + for (Pair instanceState : entry.getValue()) { + externalViewInstanceStateMap.put(instanceState.getLeft(), instanceState.getRight()); + } + idealStateSegmentAssignment.put(entry.getKey(), externalViewInstanceStateMap); + } + return idealState; + } + + private ExternalView createExternalView(Map>> segmentState) { + ExternalView externalView = new ExternalView(TABLE_NAME); + Map> externalViewSegmentAssignment = externalView.getRecord().getMapFields(); + for (Map.Entry>> entry : segmentState.entrySet()) { + Map externalViewInstanceStateMap = new TreeMap<>(); + for (Pair instanceState : entry.getValue()) { + externalViewInstanceStateMap.put(instanceState.getLeft(), instanceState.getRight()); + } + externalViewSegmentAssignment.put(entry.getKey(), externalViewInstanceStateMap); + } + return externalView; + } + + private static boolean isReplicaGroupType(String selectorType) { + return selectorType.equals(REPLICA_GROUP_INSTANCE_SELECTOR_TYPE) || selectorType.equals( + STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE); + } + + private InstanceSelector createTestInstanceSelector(String selectorType) { + RoutingConfig config = new RoutingConfig(null, null, selectorType, false); + when(_tableConfig.getRoutingConfig()).thenReturn(config); + return InstanceSelectorFactory.getInstanceSelector(_tableConfig, _propertyStore, _brokerMetrics, null, + _mutableClock, new PinotConfiguration()); + } + + @DataProvider(name = "selectorType") + public Object[] getSelectorType() { + return new Object[]{ + REPLICA_GROUP_INSTANCE_SELECTOR_TYPE, STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE, + BALANCED_INSTANCE_SELECTOR + }; + } + + @BeforeMethod + public void setUp() { + _mutableClock = new TestClock(Instant.now(), ZoneId.systemDefault()); + _mocks = MockitoAnnotations.openMocks(this); + when(_brokerRequest.getPinotQuery()).thenReturn(_pinotQuery); + when(_pinotQuery.getQueryOptions()).thenReturn(null); + when(_tableConfig.getTableName()).thenReturn(TABLE_NAME); + } + + @AfterMethod + public void tearDown() + throws Exception { + clearInvocations(_tableConfig); + _mocks.close(); + } @Test public void testInstanceSelectorFactory() { TableConfig tableConfig = mock(TableConfig.class); - BrokerMetrics brokerMetrics = mock(BrokerMetrics.class); ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); - AdaptiveServerSelector adaptiveServerSelector = null; + BrokerMetrics brokerMetrics = mock(BrokerMetrics.class); + when(tableConfig.getTableName()).thenReturn("testTable_OFFLINE"); // Routing config is missing assertTrue(InstanceSelectorFactory.getInstanceSelector(tableConfig, propertyStore, brokerMetrics, - adaptiveServerSelector) instanceof BalancedInstanceSelector); + new PinotConfiguration()) instanceof BalancedInstanceSelector); // Instance selector type is not configured RoutingConfig routingConfig = mock(RoutingConfig.class); when(tableConfig.getRoutingConfig()).thenReturn(routingConfig); assertTrue(InstanceSelectorFactory.getInstanceSelector(tableConfig, propertyStore, brokerMetrics, - adaptiveServerSelector) instanceof BalancedInstanceSelector); + new PinotConfiguration()) instanceof BalancedInstanceSelector); // Replica-group instance selector should be returned - when(routingConfig.getInstanceSelectorType()).thenReturn(RoutingConfig.REPLICA_GROUP_INSTANCE_SELECTOR_TYPE); + when(routingConfig.getInstanceSelectorType()).thenReturn(REPLICA_GROUP_INSTANCE_SELECTOR_TYPE); assertTrue(InstanceSelectorFactory.getInstanceSelector(tableConfig, propertyStore, brokerMetrics, - adaptiveServerSelector) instanceof ReplicaGroupInstanceSelector); + new PinotConfiguration()) instanceof ReplicaGroupInstanceSelector); // Strict replica-group instance selector should be returned - when(routingConfig.getInstanceSelectorType()).thenReturn(RoutingConfig.STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE); + when(routingConfig.getInstanceSelectorType()).thenReturn(STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE); assertTrue(InstanceSelectorFactory.getInstanceSelector(tableConfig, propertyStore, brokerMetrics, - adaptiveServerSelector) instanceof StrictReplicaGroupInstanceSelector); + new PinotConfiguration()) instanceof StrictReplicaGroupInstanceSelector); // Should be backward-compatible with legacy config when(routingConfig.getInstanceSelectorType()).thenReturn(null); @@ -93,25 +220,27 @@ public void testInstanceSelectorFactory() { when(routingConfig.getRoutingTableBuilderName()).thenReturn( InstanceSelectorFactory.LEGACY_REPLICA_GROUP_OFFLINE_ROUTING); assertTrue(InstanceSelectorFactory.getInstanceSelector(tableConfig, propertyStore, brokerMetrics, - adaptiveServerSelector) instanceof ReplicaGroupInstanceSelector); + new PinotConfiguration()) instanceof ReplicaGroupInstanceSelector); when(tableConfig.getTableType()).thenReturn(TableType.REALTIME); when(routingConfig.getRoutingTableBuilderName()).thenReturn( InstanceSelectorFactory.LEGACY_REPLICA_GROUP_REALTIME_ROUTING); assertTrue(InstanceSelectorFactory.getInstanceSelector(tableConfig, propertyStore, brokerMetrics, - adaptiveServerSelector) instanceof ReplicaGroupInstanceSelector); + new PinotConfiguration()) instanceof ReplicaGroupInstanceSelector); } @Test public void testInstanceSelector() { String offlineTableName = "testTable_OFFLINE"; + ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); BrokerMetrics brokerMetrics = mock(BrokerMetrics.class); - AdaptiveServerSelector adaptiveServerSelector = null; - BalancedInstanceSelector balancedInstanceSelector = new BalancedInstanceSelector(offlineTableName, brokerMetrics, - adaptiveServerSelector); + BalancedInstanceSelector balancedInstanceSelector = + new BalancedInstanceSelector(offlineTableName, propertyStore, brokerMetrics, null, Clock.systemUTC(), false); ReplicaGroupInstanceSelector replicaGroupInstanceSelector = - new ReplicaGroupInstanceSelector(offlineTableName, brokerMetrics, adaptiveServerSelector); + new ReplicaGroupInstanceSelector(offlineTableName, propertyStore, brokerMetrics, null, Clock.systemUTC(), + false); StrictReplicaGroupInstanceSelector strictReplicaGroupInstanceSelector = - new StrictReplicaGroupInstanceSelector(offlineTableName, brokerMetrics, adaptiveServerSelector); + new StrictReplicaGroupInstanceSelector(offlineTableName, propertyStore, brokerMetrics, null, Clock.systemUTC(), + false); Set enabledInstances = new HashSet<>(); IdealState idealState = new IdealState(offlineTableName); @@ -201,8 +330,8 @@ public void testInstanceSelector() { expectedBalancedInstanceSelectorResult.put(segment1, instance2); expectedBalancedInstanceSelectorResult.put(segment2, instance1); expectedBalancedInstanceSelectorResult.put(segment3, instance3); - InstanceSelector.SelectionResult selectionResult = balancedInstanceSelector.select(brokerRequest, segments, - requestId); + InstanceSelector.SelectionResult selectionResult = + balancedInstanceSelector.select(brokerRequest, segments, requestId); assertEquals(selectionResult.getSegmentToInstanceMap(), expectedBalancedInstanceSelectorResult); assertTrue(selectionResult.getUnavailableSegments().isEmpty()); Map expectedReplicaGroupInstanceSelectorResult = new HashMap<>(); @@ -392,6 +521,7 @@ public void testInstanceSelector() { assertTrue(selectionResult.getUnavailableSegments().isEmpty()); // Process the changes + // segment4 is a newly added segment with 3 online instances in idealStage and 2 online instances in externalView. balancedInstanceSelector.onAssignmentChange(idealState, externalView, onlineSegments); replicaGroupInstanceSelector.onAssignmentChange(idealState, externalView, onlineSegments); strictReplicaGroupInstanceSelector.onAssignmentChange(idealState, externalView, onlineSegments); @@ -551,6 +681,7 @@ public void testInstanceSelector() { // segment2 -> instance1 // segment3 -> instance1 // segment4 -> instance0 + // instance0 is excluded from serving segment4 because instance0 has error for serving segment1 // StrictReplicaGroupInstanceSelector: // segment1 -> instance2 // segment2 -> instance1 @@ -618,6 +749,7 @@ public void testInstanceSelector() { @Test public void testReplicaGroupInstanceSelectorNumReplicaGroupsToQuery() { String offlineTableName = "testTable_OFFLINE"; + ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); BrokerMetrics brokerMetrics = mock(BrokerMetrics.class); BrokerRequest brokerRequest = mock(BrokerRequest.class); PinotQuery pinotQuery = mock(PinotQuery.class); @@ -626,10 +758,10 @@ public void testReplicaGroupInstanceSelectorNumReplicaGroupsToQuery() { queryOptions.put("numReplicaGroupsToQuery", "2"); when(brokerRequest.getPinotQuery()).thenReturn(pinotQuery); when(pinotQuery.getQueryOptions()).thenReturn(queryOptions); - AdaptiveServerSelector adaptiveServerSelector = null; ReplicaGroupInstanceSelector replicaGroupInstanceSelector = - new ReplicaGroupInstanceSelector(offlineTableName, brokerMetrics, adaptiveServerSelector); + new ReplicaGroupInstanceSelector(offlineTableName, propertyStore, brokerMetrics, null, Clock.systemUTC(), + false); Set enabledInstances = new HashSet<>(); IdealState idealState = new IdealState(offlineTableName); @@ -658,7 +790,7 @@ public void testReplicaGroupInstanceSelectorNumReplicaGroupsToQuery() { List segments = getSegments(); // add all segments to both idealStateSegmentAssignment and externalViewSegmentAssignment maps and also to online // segments - for (String segment: segments) { + for (String segment : segments) { idealStateSegmentAssignment.put(segment, idealStateInstanceStateMap0); externalViewSegmentAssignment.put(segment, externalViewInstanceStateMap0); onlineSegments.add(segment); @@ -700,18 +832,19 @@ public void testReplicaGroupInstanceSelectorNumReplicaGroupsToQuery() { @Test public void testReplicaGroupInstanceSelectorNumReplicaGroupsToQueryGreaterThanReplicas() { String offlineTableName = "testTable_OFFLINE"; + ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); BrokerMetrics brokerMetrics = mock(BrokerMetrics.class); BrokerRequest brokerRequest = mock(BrokerRequest.class); PinotQuery pinotQuery = mock(PinotQuery.class); Map queryOptions = new HashMap<>(); queryOptions.put("numReplicaGroupsToQuery", "4"); - AdaptiveServerSelector adaptiveServerSelector = null; when(brokerRequest.getPinotQuery()).thenReturn(pinotQuery); when(pinotQuery.getQueryOptions()).thenReturn(queryOptions); ReplicaGroupInstanceSelector replicaGroupInstanceSelector = - new ReplicaGroupInstanceSelector(offlineTableName, brokerMetrics, adaptiveServerSelector); + new ReplicaGroupInstanceSelector(offlineTableName, propertyStore, brokerMetrics, null, Clock.systemUTC(), + false); Set enabledInstances = new HashSet<>(); IdealState idealState = new IdealState(offlineTableName); @@ -741,7 +874,7 @@ public void testReplicaGroupInstanceSelectorNumReplicaGroupsToQueryGreaterThanRe // add all segments to both idealStateSegmentAssignment and externalViewSegmentAssignment maps and also to online // segments - for (String segment: segments) { + for (String segment : segments) { idealStateSegmentAssignment.put(segment, idealStateInstanceStateMap0); externalViewSegmentAssignment.put(segment, externalViewInstanceStateMap0); onlineSegments.add(segment); @@ -783,17 +916,18 @@ public void testReplicaGroupInstanceSelectorNumReplicaGroupsToQueryGreaterThanRe @Test public void testReplicaGroupInstanceSelectorNumReplicaGroupsNotSet() { String offlineTableName = "testTable_OFFLINE"; + ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); BrokerMetrics brokerMetrics = mock(BrokerMetrics.class); BrokerRequest brokerRequest = mock(BrokerRequest.class); PinotQuery pinotQuery = mock(PinotQuery.class); Map queryOptions = new HashMap<>(); - AdaptiveServerSelector adaptiveServerSelector = null; when(brokerRequest.getPinotQuery()).thenReturn(pinotQuery); when(pinotQuery.getQueryOptions()).thenReturn(queryOptions); ReplicaGroupInstanceSelector replicaGroupInstanceSelector = - new ReplicaGroupInstanceSelector(offlineTableName, brokerMetrics, adaptiveServerSelector); + new ReplicaGroupInstanceSelector(offlineTableName, propertyStore, brokerMetrics, null, Clock.systemUTC(), + false); Set enabledInstances = new HashSet<>(); IdealState idealState = new IdealState(offlineTableName); @@ -823,7 +957,7 @@ public void testReplicaGroupInstanceSelectorNumReplicaGroupsNotSet() { // add all segments to both idealStateSegmentAssignment and externalViewSegmentAssignment maps and also to online // segments - for (String segment: segments) { + for (String segment : segments) { idealStateSegmentAssignment.put(segment, idealStateInstanceStateMap0); externalViewSegmentAssignment.put(segment, externalViewInstanceStateMap0); onlineSegments.add(segment); @@ -834,13 +968,13 @@ public void testReplicaGroupInstanceSelectorNumReplicaGroupsNotSet() { // 2nd query should go to next replica group Map expectedReplicaGroupInstanceSelectorResult = new HashMap<>(); - for (String segment: segments) { + for (String segment : segments) { expectedReplicaGroupInstanceSelectorResult.put(segment, instance0); } InstanceSelector.SelectionResult selectionResult = replicaGroupInstanceSelector.select(brokerRequest, segments, 0); assertEquals(selectionResult.getSegmentToInstanceMap(), expectedReplicaGroupInstanceSelectorResult); - for (String segment: segments) { + for (String segment : segments) { expectedReplicaGroupInstanceSelectorResult.put(segment, instance1); } selectionResult = replicaGroupInstanceSelector.select(brokerRequest, segments, 1); @@ -850,27 +984,24 @@ public void testReplicaGroupInstanceSelectorNumReplicaGroupsNotSet() { @Test public void testMultiStageStrictReplicaGroupSelector() { String offlineTableName = "testTable_OFFLINE"; + ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); + BrokerMetrics brokerMetrics = mock(BrokerMetrics.class); // Create instance-partitions with two replica-groups and 1 partition. Each replica-group has 2 instances. List replicaGroup0 = ImmutableList.of("instance-0", "instance-1"); List replicaGroup1 = ImmutableList.of("instance-2", "instance-3"); - Map> partitionToInstances = ImmutableMap.of( - "0_0", replicaGroup0, - "0_1", replicaGroup1); + Map> partitionToInstances = ImmutableMap.of("0_0", replicaGroup0, "0_1", replicaGroup1); InstancePartitions instancePartitions = new InstancePartitions(offlineTableName); instancePartitions.setInstances(0, 0, partitionToInstances.get("0_0")); instancePartitions.setInstances(0, 1, partitionToInstances.get("0_1")); - BrokerMetrics brokerMetrics = mock(BrokerMetrics.class); BrokerRequest brokerRequest = mock(BrokerRequest.class); PinotQuery pinotQuery = mock(PinotQuery.class); Map queryOptions = new HashMap<>(); - when(brokerRequest.getPinotQuery()).thenReturn(pinotQuery); when(pinotQuery.getQueryOptions()).thenReturn(queryOptions); - ZkHelixPropertyStore propertyStore = (ZkHelixPropertyStore) mock(ZkHelixPropertyStore.class); - MultiStageReplicaGroupSelector multiStageSelector = - new MultiStageReplicaGroupSelector(offlineTableName, propertyStore, brokerMetrics, null); + new MultiStageReplicaGroupSelector(offlineTableName, propertyStore, brokerMetrics, null, Clock.systemUTC(), + false); multiStageSelector = spy(multiStageSelector); doReturn(instancePartitions).when(multiStageSelector).getInstancePartitions(); @@ -962,13 +1093,14 @@ public void testMultiStageStrictReplicaGroupSelector() { @Test public void testUnavailableSegments() { String offlineTableName = "testTable_OFFLINE"; + ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); BrokerMetrics brokerMetrics = mock(BrokerMetrics.class); - AdaptiveServerSelector adaptiveServerSelector = null; - BalancedInstanceSelector balancedInstanceSelector = new BalancedInstanceSelector(offlineTableName, brokerMetrics, - adaptiveServerSelector); + BalancedInstanceSelector balancedInstanceSelector = + new BalancedInstanceSelector(offlineTableName, propertyStore, brokerMetrics, null, Clock.systemUTC(), false); // ReplicaGroupInstanceSelector has the same behavior as BalancedInstanceSelector for the unavailable segments StrictReplicaGroupInstanceSelector strictReplicaGroupInstanceSelector = - new StrictReplicaGroupInstanceSelector(offlineTableName, brokerMetrics, adaptiveServerSelector); + new StrictReplicaGroupInstanceSelector(offlineTableName, propertyStore, brokerMetrics, null, Clock.systemUTC(), + false); Set enabledInstances = new HashSet<>(); IdealState idealState = new IdealState(offlineTableName); @@ -1154,10 +1286,11 @@ public void testUnavailableSegments() { strictReplicaGroupInstanceSelector.onAssignmentChange(idealState, externalView, onlineSegments); selectionResult = balancedInstanceSelector.select(brokerRequest, segments, 0); assertTrue(selectionResult.getSegmentToInstanceMap().isEmpty()); - assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + // segment1 and segment0 are considered old therefore we should report it as unavailable. + assertEquals(selectionResult.getUnavailableSegments(), Arrays.asList(segment0, segment1)); selectionResult = strictReplicaGroupInstanceSelector.select(brokerRequest, segments, 0); assertTrue(selectionResult.getSegmentToInstanceMap().isEmpty()); - assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + assertEquals(selectionResult.getUnavailableSegments(), Arrays.asList(segment0, segment1)); // Disable the OFFLINE instance, both segments should be unavailable // { @@ -1181,8 +1314,8 @@ public void testUnavailableSegments() { assertEquals(selectionResult.getUnavailableSegments(), Arrays.asList(segment0, segment1)); // Change the ERROR instance of segment0 to ONLINE, segment0 should be available - // (Note that for StrictReplicaGroupInstanceSelector, segment1 does not have any ONLINE/CONSUMING instance, so it - // won't mark instance down for segment0) + // (Note that for StrictReplicaGroupInstanceSelector, segment1 but it is old so it will mark instance down for + // segment0) // { // segment0: { // (disabled) instance: OFFLINE, @@ -1200,8 +1333,8 @@ public void testUnavailableSegments() { assertEquals(selectionResult.getSegmentToInstanceMap().size(), 1); assertEquals(selectionResult.getUnavailableSegments(), Collections.singletonList(segment1)); selectionResult = strictReplicaGroupInstanceSelector.select(brokerRequest, segments, 0); - assertEquals(selectionResult.getSegmentToInstanceMap().size(), 1); - assertEquals(selectionResult.getUnavailableSegments(), Collections.singletonList(segment1)); + assertTrue(selectionResult.getSegmentToInstanceMap().isEmpty()); + assertEquals(selectionResult.getUnavailableSegments(), Arrays.asList(segment0, segment1)); // Disable the ONLINE instance, both segments should be unavailable // { @@ -1250,7 +1383,576 @@ public void testUnavailableSegments() { } } - private static List getSegments() { - return SEGMENTS; + @Test(dataProvider = "selectorType") + public void testNewSegmentFromZKMetadataSelection(String selectorType) { + String oldSeg = "segment0"; + String newSeg = "segment1"; + List> segmentCreationTimeMsPairs = + ImmutableList.of(Pair.of(newSeg, _mutableClock.millis() - 100)); + createSegments(segmentCreationTimeMsPairs); + Set onlineSegments = ImmutableSet.of(oldSeg, newSeg); + + // Set up instances + String instance0 = "instance0"; + String instance1 = "instance1"; + Set enabledInstances = ImmutableSet.of(instance0, instance1); + // Set up ideal state: + // Ideal states for two segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [instance0:online, instance1:online] + Map>> idealSateMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + IdealState idealState = createIdealState(idealSateMap); + + // Set up external view: + // External view for two segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [instance1:online] + Map>> externalViewMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance1, ONLINE))); + + ExternalView externalView = createExternalView(externalViewMap); + InstanceSelector selector = createTestInstanceSelector(selectorType); + + selector.init(enabledInstances, idealState, externalView, onlineSegments); + + { + int requestId = 0; + // First selection, we select instance0 for oldSeg and instance1 for newSeg in balance selector + // For replica group, we select instance0 for oldSeg and newSeg. Because newSeg is not online in instance0, so + // we exclude it from selection result. + InstanceSelector.SelectionResult selectionResult = + selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + if (isReplicaGroupType(selectorType)) { + assertEquals(selectionResult.getSegmentToInstanceMap(), ImmutableMap.of(oldSeg, instance0)); + assertEquals(selectionResult.getOptionalSegmentToInstanceMap(), ImmutableMap.of(newSeg, instance0)); + } else { + assertEquals(selectionResult.getSegmentToInstanceMap(), ImmutableMap.of(oldSeg, instance0, newSeg, instance1)); + assertTrue(selectionResult.getOptionalSegmentToInstanceMap().isEmpty()); + } + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + } + { + int requestId = 1; + // Second selection, we select instance1 for oldSeg and instance0 for newSeg in balance selector + // Because newSeg is not online in instance0, so we exclude it from selection result. + // For replica group, we select instance1 for oldSeg and newSeg. + InstanceSelector.SelectionResult selectionResult = + selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + switch (selectorType) { + case BALANCED_INSTANCE_SELECTOR: + assertEquals(selectionResult.getSegmentToInstanceMap(), ImmutableMap.of(oldSeg, instance1)); + assertEquals(selectionResult.getOptionalSegmentToInstanceMap(), ImmutableMap.of(newSeg, instance0)); + break; + case STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE: // fall through + case REPLICA_GROUP_INSTANCE_SELECTOR_TYPE: + assertEquals(selectionResult.getSegmentToInstanceMap(), + ImmutableMap.of(oldSeg, instance1, newSeg, instance1)); + assertTrue(selectionResult.getOptionalSegmentToInstanceMap().isEmpty()); + break; + default: + throw new RuntimeException("unsupported selector type:" + selectorType); + } + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + } + // Advance the clock to make newSeg to old segment. + _mutableClock.fastForward(Duration.ofMillis(NEW_SEGMENT_EXPIRATION_MILLIS + 10)); + // Upon re-initialization, newly old segments can only be served from online instances: instance1 + selector.init(enabledInstances, idealState, externalView, onlineSegments); + { + int requestId = 0; + InstanceSelector.SelectionResult selectionResult = + selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + switch (selectorType) { + case BALANCED_INSTANCE_SELECTOR: // fall through + case REPLICA_GROUP_INSTANCE_SELECTOR_TYPE: + assertEquals(selectionResult.getSegmentToInstanceMap(), + ImmutableMap.of(oldSeg, instance0, newSeg, instance1)); + break; + case STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE: + assertEquals(selectionResult.getSegmentToInstanceMap(), + ImmutableMap.of(oldSeg, instance1, newSeg, instance1)); + break; + default: + throw new RuntimeException("unsupported selector type:" + selectorType); + } + assertTrue(selectionResult.getOptionalSegmentToInstanceMap().isEmpty()); + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + } + { + int requestId = 1; + Map expectedSelectionResult = ImmutableMap.of(oldSeg, instance1, newSeg, instance1); + InstanceSelector.SelectionResult selectionResult = + selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedSelectionResult); + assertTrue(selectionResult.getOptionalSegmentToInstanceMap().isEmpty()); + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + } + } + + // Test that we don't report new segment as unavailable till it gets old. + @Test(dataProvider = "selectorType") + public void testNewSegmentFromZKMetadataReportingUnavailable(String selectorType) { + // Set segment0 as new segment + String newSeg = "segment0"; + String oldSeg = "segment1"; + List> segmentCreationTimeMsPairs = + ImmutableList.of(Pair.of(newSeg, _mutableClock.millis() - 100), + Pair.of(oldSeg, _mutableClock.millis() - NEW_SEGMENT_EXPIRATION_MILLIS - 100)); + createSegments(segmentCreationTimeMsPairs); + Set onlineSegments = ImmutableSet.of(newSeg, oldSeg); + + // Set up instances + String instance0 = "instance0"; + String instance1 = "instance1"; + Set enabledInstances = ImmutableSet.of(instance0, instance1); + // Set up ideal state: + // Ideal states for new segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [instance0:online, instance1:online] + Map>> idealSateMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + + IdealState idealState = createIdealState(idealSateMap); + + // Set up external view: + // External view for new segments + // [segment0] -> [] + // [segment1] -> [instance0: online] + Map>> externalViewMap = + ImmutableMap.of(newSeg, ImmutableList.of(), oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE))); + + ExternalView externalView = createExternalView(externalViewMap); + + InstanceSelector selector = createTestInstanceSelector(selectorType); + selector.init(enabledInstances, idealState, externalView, onlineSegments); + // We don't mark segment as unavailable. + int requestId = 0; + Map expectedResult = ImmutableMap.of(oldSeg, instance0); + InstanceSelector.SelectionResult selectionResult = + selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedResult); + assertEquals(selectionResult.getOptionalSegmentToInstanceMap(), ImmutableMap.of(newSeg, instance0)); + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + + // Advance the clock to make newSeg to old segment and we see newSeg is reported as unavailable segment. + _mutableClock.fastForward(Duration.ofMillis(NEW_SEGMENT_EXPIRATION_MILLIS + 10)); + selector.init(enabledInstances, idealState, externalView, onlineSegments); + selectionResult = selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + if (STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE.equals(selectorType)) { + expectedResult = ImmutableMap.of(); + assertEquals(selectionResult.getUnavailableSegments(), ImmutableList.of(newSeg, oldSeg)); + } else { + assertEquals(selectionResult.getUnavailableSegments(), ImmutableList.of(newSeg)); + } + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedResult); + assertTrue(selectionResult.getOptionalSegmentToInstanceMap().isEmpty()); + } + + @Test(dataProvider = "selectorType") + public void testNewSegmentGetsOldWithErrorState(String selectorType) { + // Set segment0 as old segment + String oldSeg = "segment0"; + // Set segment1 as new segment + String newSeg = "segment1"; + List> segmentCreationTimeMsPairs = + ImmutableList.of(Pair.of(newSeg, _mutableClock.millis() - 100)); + createSegments(segmentCreationTimeMsPairs); + Set onlineSegments = ImmutableSet.of(oldSeg, newSeg); + + // Set up instances + String instance0 = "instance0"; + String instance1 = "instance1"; + Set enabledInstances = ImmutableSet.of(instance0, instance1); + // Set up ideal state: + // Ideal states for two segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [instance0:online, instance1:online] + Map>> idealSateMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + + IdealState idealState = createIdealState(idealSateMap); + + // Set up external view: + // External view for two segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [] + Map>> externalViewMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of()); + + ExternalView externalView = createExternalView(externalViewMap); + + InstanceSelector selector = createTestInstanceSelector(selectorType); + selector.init(enabledInstances, idealState, externalView, onlineSegments); + + // We don't mark segment as unavailable. + int requestId = 0; + Map expectedResult = ImmutableMap.of(oldSeg, instance0); + + InstanceSelector.SelectionResult selectionResult = + selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedResult); + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + + // Report error instance for segment1 since segment1 becomes old and we should report it as unavailable. + externalViewMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance0, ERROR))); + + externalView = createExternalView(externalViewMap); + selector.onAssignmentChange(idealState, externalView, onlineSegments); + selectionResult = selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + if (selectorType == STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE) { + expectedResult = ImmutableMap.of(); + assertEquals(selectionResult.getUnavailableSegments(), ImmutableList.of(oldSeg, newSeg)); + } else { + assertEquals(selectionResult.getUnavailableSegments(), ImmutableList.of(newSeg)); + } + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedResult); + + // Get segment1 back online in instance1 + externalViewMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance0, ERROR), Pair.of(instance1, ONLINE))); + + externalView = createExternalView(externalViewMap); + selector.onAssignmentChange(idealState, externalView, onlineSegments); + if (selectorType == STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE) { + expectedResult = ImmutableMap.of(oldSeg, instance1, newSeg, instance1); + } else { + expectedResult = ImmutableMap.of(oldSeg, instance0, newSeg, instance1); + } + selectionResult = selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedResult); + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + } + + // Test that we mark new segment as old when external view state converges with ideal state. + @Test(dataProvider = "selectorType") + public void testNewSegmentGetsOldWithStateConverge(String selectorType) { + // Set segment0 as old segment + String oldSeg = "segment0"; + // Set segment1 as new segment + String newSeg = "segment1"; + List> segmentCreationTimeMsPairs = + ImmutableList.of(Pair.of(newSeg, _mutableClock.millis() - 100)); + createSegments(segmentCreationTimeMsPairs); + Set onlineSegments = ImmutableSet.of(oldSeg, newSeg); + + // Set up instances + String instance0 = "instance0"; + String instance1 = "instance1"; + Set enabledInstances = ImmutableSet.of(instance0, instance1); + // Set up ideal state: + // Ideal states for two segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [instance0:online, instance1:online] + Map>> idealSateMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + + IdealState idealState = createIdealState(idealSateMap); + + // Set up external view: + // External view for two segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [] + Map>> externalViewMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of()); + + ExternalView externalView = createExternalView(externalViewMap); + + InstanceSelector selector = createTestInstanceSelector(selectorType); + selector.init(enabledInstances, idealState, externalView, onlineSegments); + + // We don't mark segment as unavailable. + int requestId = 0; + Map expectedBalancedInstanceSelectorResult = ImmutableMap.of(oldSeg, instance0); + + InstanceSelector.SelectionResult selectionResult = + selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedBalancedInstanceSelectorResult); + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + + // Segment1 is not old anymore with state converge. + externalViewMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + + externalView = createExternalView(externalViewMap); + selector.onAssignmentChange(idealState, externalView, onlineSegments); + + // Segment1 becomes unavailable. + externalViewMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of()); + + externalView = createExternalView(externalViewMap); + selector.onAssignmentChange(idealState, externalView, onlineSegments); + + selector.onAssignmentChange(idealState, externalView, onlineSegments); + selectionResult = selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + if (selectorType == STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE) { + expectedBalancedInstanceSelectorResult = ImmutableMap.of(); + assertEquals(selectionResult.getUnavailableSegments(), ImmutableList.of(oldSeg, newSeg)); + } else { + assertEquals(selectionResult.getUnavailableSegments(), ImmutableList.of(newSeg)); + } + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedBalancedInstanceSelectorResult); + } + + @Test(dataProvider = "selectorType") + public void testNewSegmentsFromIDWithMissingEV(String selectorType) { + String oldSeg0 = "segment0"; + String oldSeg1 = "segment1"; + Set onlineSegments = ImmutableSet.of(oldSeg0, oldSeg1); + + // Set up ideal state: + // Ideal states for two segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [instance0:online, instance1:online] + String instance0 = "instance0"; + String instance1 = "instance1"; + Set enabledInstances = ImmutableSet.of(instance0, instance1); + + Map>> idealSateMap = + ImmutableMap.of(oldSeg0, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), oldSeg1, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + + IdealState idealState = createIdealState(idealSateMap); + + // Set up external view: + // External view for two segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [instance0:online, instance1:online] + Map>> externalViewMap = + ImmutableMap.of(oldSeg0, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), oldSeg1, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + + ExternalView externalView = createExternalView(externalViewMap); + + InstanceSelector selector = createTestInstanceSelector(selectorType); + selector.init(enabledInstances, idealState, externalView, onlineSegments); + + // Add a new segment to ideal state with missing external view. + String newSeg = "segment2"; + onlineSegments = ImmutableSet.of(oldSeg0, oldSeg1, newSeg); + idealSateMap = + ImmutableMap.of(oldSeg0, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), oldSeg1, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + + idealState = createIdealState(idealSateMap); + externalViewMap = + ImmutableMap.of(oldSeg0, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), oldSeg1, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + + externalView = createExternalView(externalViewMap); + selector.onAssignmentChange(idealState, externalView, onlineSegments); + + int requestId = 0; + Map expectedResult; + switch (selectorType) { + case BALANCED_INSTANCE_SELECTOR: + expectedResult = ImmutableMap.of(oldSeg0, instance0, oldSeg1, instance1); + break; + case REPLICA_GROUP_INSTANCE_SELECTOR_TYPE: // fall through + case STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE: + expectedResult = ImmutableMap.of(oldSeg0, instance0, oldSeg1, instance0); + break; + default: + throw new RuntimeException("unsupported type:" + selectorType); + } + + InstanceSelector.SelectionResult selectionResult = + selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedResult); + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + + // Advance the clock to make newSeg to old segment. + // On state update, all segments become unavailable. + _mutableClock.fastForward(Duration.ofMillis(NEW_SEGMENT_EXPIRATION_MILLIS + 10)); + selector.onAssignmentChange(idealState, externalView, onlineSegments); + selectionResult = selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + if (selectorType == STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE) { + assertEquals(selectionResult.getUnavailableSegments(), ImmutableList.of(oldSeg0, oldSeg1, newSeg)); + } else { + assertEquals(selectionResult.getUnavailableSegments(), ImmutableList.of(newSeg)); + } + } + + // Test that on instance change, we exclude not enabled instance from serving for new segments. + @Test(dataProvider = "selectorType") + public void testExcludeNotEnabledInstanceForNewSegment(String selectorType) { + // Set segment0 as old segment + String oldSeg = "segment0"; + // Set segment1 as new segment + String newSeg = "segment1"; + List> segmentCreationTimeMsPairs = + ImmutableList.of(Pair.of(newSeg, _mutableClock.millis() - 100)); + createSegments(segmentCreationTimeMsPairs); + Set onlineSegments = ImmutableSet.of(oldSeg, newSeg); + + // Set up instances + String instance0 = "instance0"; + String instance1 = "instance1"; + Set enabledInstances = ImmutableSet.of(instance0, instance1); + // Set up ideal state: + // Ideal states for two segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [instance0:online, instance1:online] + Map>> idealSateMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + + IdealState idealState = createIdealState(idealSateMap); + + // Set up external view: + // External view for two segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [instance0:online] + Map>> externalViewMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance0, ONLINE))); + + ExternalView externalView = createExternalView(externalViewMap); + + InstanceSelector selector = createTestInstanceSelector(selectorType); + selector.init(enabledInstances, idealState, externalView, onlineSegments); + + // First selection, we select instance1 for newSeg. + int requestId = 0; + Map expectedResult; + switch (selectorType) { + case BALANCED_INSTANCE_SELECTOR: + expectedResult = ImmutableMap.of(oldSeg, instance0); + break; + case REPLICA_GROUP_INSTANCE_SELECTOR_TYPE: + case STRICT_REPLICA_GROUP_INSTANCE_SELECTOR_TYPE: + expectedResult = ImmutableMap.of(oldSeg, instance0, newSeg, instance0); + break; + default: + throw new RuntimeException("Unsupported type:" + selectorType); + } + + InstanceSelector.SelectionResult selectionResult = + selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedResult); + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + + // Remove instance0 from enabledInstances. + enabledInstances = ImmutableSet.of(instance1); + List changeInstance = ImmutableList.of(instance0); + selector.onInstancesChange(enabledInstances, changeInstance); + selectionResult = selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + // We don't include instance0 in selection anymore. + expectedResult = ImmutableMap.of(oldSeg, instance1); + + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedResult); + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); + } + + @Test(dataProvider = "selectorType") + public void testExcludeInstanceNotInIdealState(String selectorType) { + // Set segment0 as old segment + String oldSeg = "segment0"; + // Set segment1 as new segment + String newSeg = "segment1"; + List> segmentCreationTimeMsPairs = + ImmutableList.of(Pair.of(oldSeg, _mutableClock.millis() - NEW_SEGMENT_EXPIRATION_MILLIS - 100), + Pair.of(newSeg, _mutableClock.millis() - 100)); + createSegments(segmentCreationTimeMsPairs); + Set onlineSegments = ImmutableSet.of(oldSeg, newSeg); + + // Set up instances + String instance0 = "instance0"; + String instance1 = "instance1"; + String instance2 = "instance2"; + Set enabledInstances = ImmutableSet.of(instance0, instance1, instance2); + // Set up ideal state: + // Ideal states for two segments + // [segment0] -> [instance0:online, instance1:online] + // [segment1] -> [instance0:online, instance1:online] + Map>> idealSateMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + + IdealState idealState = createIdealState(idealSateMap); + + // Set up external view: + // External view for two segments + // [segment0] -> [instance2: online] + // [segment1] -> [instance2: online] + Map>> externalViewMap = + ImmutableMap.of(oldSeg, ImmutableList.of(Pair.of(instance2, ONLINE)), newSeg, + ImmutableList.of(Pair.of(instance2, ONLINE))); + + ExternalView externalView = createExternalView(externalViewMap); + + InstanceSelector selector = createTestInstanceSelector(selectorType); + selector.init(enabledInstances, idealState, externalView, onlineSegments); + + // No selection because the external view is not in ideal state. + int requestId = 0; + Map expectedBalancedInstanceSelectorResult = ImmutableMap.of(); + InstanceSelector.SelectionResult selectionResult = + selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedBalancedInstanceSelectorResult); + assertEquals(selectionResult.getUnavailableSegments(), ImmutableList.of(oldSeg)); + } + + @Test(dataProvider = "selectorType") + public void testExcludeIdealStateOffline(String selectorType) { + // Set segment0 as old segment + String newSeg = "segment0"; + String oldSeg = "segment1"; + + List> segmentCreationTimeMsPairs = + ImmutableList.of(Pair.of(newSeg, _mutableClock.millis() - 100), + Pair.of(oldSeg, _mutableClock.millis() - NEW_SEGMENT_EXPIRATION_MILLIS - 100)); + createSegments(segmentCreationTimeMsPairs); + Set onlineSegments = ImmutableSet.of(newSeg, oldSeg); + + // Set up instances + String instance0 = "instance0"; + String instance1 = "instance1"; + Set enabledInstances = ImmutableSet.of(instance0, instance1); + // Set up ideal state: + // Ideal states for two segments + // [segment0] -> [instance0:online, instance1:offline] + Map>> idealSateMap = + ImmutableMap.of( + newSeg, ImmutableList.of(Pair.of(instance0, OFFLINE), Pair.of(instance1, ONLINE)), + oldSeg, ImmutableList.of(Pair.of(instance0, OFFLINE), Pair.of(instance1, ONLINE))); + + IdealState idealState = createIdealState(idealSateMap); + + // Set up external view: + // External view for two segments + // [segment0] -> [instance0:offline, instance1:online] + Map>> externalViewMap = + ImmutableMap.of( + newSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE)), + oldSeg, ImmutableList.of(Pair.of(instance0, ONLINE), Pair.of(instance1, ONLINE))); + + ExternalView externalView = createExternalView(externalViewMap); + + InstanceSelector selector = createTestInstanceSelector(selectorType); + selector.init(enabledInstances, idealState, externalView, onlineSegments); + + // We don't mark segment as unavailable. + int requestId = 0; + Map expectedBalancedInstanceSelectorResult = ImmutableMap.of(oldSeg, instance1, newSeg, instance1); + + InstanceSelector.SelectionResult selectionResult = + selector.select(_brokerRequest, Lists.newArrayList(onlineSegments), requestId); + assertEquals(selectionResult.getSegmentToInstanceMap(), expectedBalancedInstanceSelectorResult); + assertTrue(selectionResult.getUnavailableSegments().isEmpty()); } } diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/routing/segmentmetadata/SegmentZkMetadataFetcherTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/routing/segmentmetadata/SegmentZkMetadataFetcherTest.java new file mode 100644 index 000000000000..151b9f4689b0 --- /dev/null +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/routing/segmentmetadata/SegmentZkMetadataFetcherTest.java @@ -0,0 +1,175 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.routing.segmentmetadata; + +import com.google.common.collect.ImmutableSet; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.apache.helix.model.ExternalView; +import org.apache.helix.model.IdealState; +import org.apache.helix.store.zk.ZkHelixPropertyStore; +import org.apache.helix.zookeeper.datamodel.ZNRecord; +import org.apache.pinot.broker.routing.segmentpruner.SegmentPruner; +import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; +import org.apache.pinot.controller.helix.ControllerTest; +import org.mockito.ArgumentMatcher; +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; +import static org.testng.AssertJUnit.fail; + + +public class SegmentZkMetadataFetcherTest extends ControllerTest { + private static final String OFFLINE_TABLE_NAME = "testTable_OFFLINE"; + + @Test + public void testSegmentZkMetadataFetcherShouldNotAllowIncorrectRegisterOrInitBehavior() { + ZkHelixPropertyStore mockPropertyStore = Mockito.mock(ZkHelixPropertyStore.class); + IdealState idealState = Mockito.mock(IdealState.class); + ExternalView externalView = Mockito.mock(ExternalView.class); + + // empty listener at beginning + SegmentZkMetadataFetcher segmentZkMetadataFetcher = + new SegmentZkMetadataFetcher(OFFLINE_TABLE_NAME, mockPropertyStore); + assertEquals(segmentZkMetadataFetcher.getListeners().size(), 0); + + // should allow register new listener + segmentZkMetadataFetcher.register(mock(SegmentZkMetadataFetchListener.class)); + assertEquals(segmentZkMetadataFetcher.getListeners().size(), 1); + + // should not allow register new listener once initialized + segmentZkMetadataFetcher.init(idealState, externalView, Collections.emptySet()); + try { + segmentZkMetadataFetcher.register(mock(SegmentZkMetadataFetchListener.class)); + fail(); + } catch (RuntimeException rte) { + assertTrue(rte.getMessage().contains("has already been initialized")); + } + + // should not allow duplicate init either + try { + segmentZkMetadataFetcher.init(idealState, externalView, Collections.emptySet()); + fail(); + } catch (RuntimeException rte) { + assertTrue(rte.getMessage().contains("has already been initialized")); + } + } + + @Test + public void testSegmentZkMetadataFetcherShouldNotPullZkWhenNoPrunerRegistered() { + ZkHelixPropertyStore mockPropertyStore = Mockito.mock(ZkHelixPropertyStore.class); + SegmentZkMetadataFetcher segmentZkMetadataFetcher = + new SegmentZkMetadataFetcher(OFFLINE_TABLE_NAME, mockPropertyStore); + // NOTE: Ideal state and external view are not used in the current implementation + IdealState idealState = Mockito.mock(IdealState.class); + ExternalView externalView = Mockito.mock(ExternalView.class); + + assertEquals(segmentZkMetadataFetcher.getListeners().size(), 0); + segmentZkMetadataFetcher.init(idealState, externalView, Collections.singleton("foo")); + Mockito.verify(mockPropertyStore, times(0)).get(any(), any(), anyInt(), anyBoolean()); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, Collections.singleton("foo")); + Mockito.verify(mockPropertyStore, times(0)).get(any(), any(), anyInt(), anyBoolean()); + segmentZkMetadataFetcher.refreshSegment("foo"); + Mockito.verify(mockPropertyStore, times(0)).get(any(), any(), anyInt(), anyBoolean()); + } + + @Test + public void testSegmentZkMetadataFetcherShouldPullZkOnlyOncePerSegmentWhenMultiplePrunersRegistered() { + ZkHelixPropertyStore mockPropertyStore = mock(ZkHelixPropertyStore.class); + when(mockPropertyStore.get(any(), any(), anyInt(), anyBoolean())).thenAnswer(inv -> { + List pathList = inv.getArgument(0); + List result = new ArrayList<>(pathList.size()); + for (String path : pathList) { + String[] pathParts = path.split("/"); + String segmentName = pathParts[pathParts.length - 1]; + SegmentZKMetadata fakeSegmentZkMetadata = new SegmentZKMetadata(segmentName); + result.add(fakeSegmentZkMetadata.toZNRecord()); + } + return result; + }); + SegmentPruner pruner1 = mock(SegmentPruner.class); + SegmentPruner pruner2 = mock(SegmentPruner.class); + SegmentZkMetadataFetcher segmentZkMetadataFetcher = + new SegmentZkMetadataFetcher(OFFLINE_TABLE_NAME, mockPropertyStore); + segmentZkMetadataFetcher.register(pruner1); + segmentZkMetadataFetcher.register(pruner2); + // NOTE: Ideal state and external view are not used in the current implementation + IdealState idealState = mock(IdealState.class); + ExternalView externalView = mock(ExternalView.class); + + assertEquals(segmentZkMetadataFetcher.getListeners().size(), 2); + // should call property store once for "foo" and "bar" as a batch + segmentZkMetadataFetcher.init(idealState, externalView, ImmutableSet.of("foo", "bar")); + verify(mockPropertyStore, times(1)).get(argThat(new ListMatcher("foo", "bar")), any(), anyInt(), anyBoolean()); + verify(pruner1, times(1)).init(any(), any(), argThat(new ListMatcher("foo", "bar")), any()); + verify(pruner2, times(1)).init(any(), any(), argThat(new ListMatcher("foo", "bar")), any()); + + // should call property store only once b/c "alice" was missing + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, ImmutableSet.of("bar", "alice")); + verify(mockPropertyStore, times(1)).get(argThat(new ListMatcher("alice")), any(), anyInt(), anyBoolean()); + verify(pruner1, times(1)).onAssignmentChange(any(), any(), any(), argThat(new ListMatcher("alice")), any()); + verify(pruner2, times(1)).onAssignmentChange(any(), any(), any(), argThat(new ListMatcher("alice")), any()); + + // should call property store once more b/c "foo" was cleared when onAssignmentChange called with "bar" and "alice" + segmentZkMetadataFetcher.refreshSegment("foo"); + verify(mockPropertyStore, times(1)).get(endsWith("foo"), any(), anyInt()); + verify(pruner1, times(1)).refreshSegment(eq("foo"), any()); + verify(pruner2, times(1)).refreshSegment(eq("foo"), any()); + clearInvocations(mockPropertyStore, pruner1, pruner2); + + // update with all existing segments will call into property store and pruner with empty list + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, ImmutableSet.of("bar", "alice")); + verify(mockPropertyStore, times(1)).get(argThat(new ListMatcher()), any(), anyInt(), anyBoolean()); + verify(pruner1, times(1)).onAssignmentChange(any(), any(), any(), argThat(new ListMatcher()), any()); + verify(pruner2, times(1)).onAssignmentChange(any(), any(), any(), argThat(new ListMatcher()), any()); + + // calling refresh will still force pull from property store + segmentZkMetadataFetcher.refreshSegment("foo"); + verify(mockPropertyStore, times(1)).get(endsWith("foo"), any(), anyInt()); + verify(pruner1, times(1)).refreshSegment(eq("foo"), any()); + verify(pruner2, times(1)).refreshSegment(eq("foo"), any()); + } + + private static class ListMatcher implements ArgumentMatcher> { + private final List _valueToMatch; + + private ListMatcher(String... values) { + _valueToMatch = Arrays.asList(values); + } + + @Override + public boolean matches(List arg) { + if (arg.size() != _valueToMatch.size()) { + return false; + } + for (int i = 0; i < arg.size(); i++) { + if (!arg.get(i).endsWith(_valueToMatch.get(i))) { + return false; + } + } + return true; + } + } +} diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionMetadataManagerTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionMetadataManagerTest.java new file mode 100644 index 000000000000..083a1a85485f --- /dev/null +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/routing/segmentpartition/SegmentPartitionMetadataManagerTest.java @@ -0,0 +1,275 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.broker.routing.segmentpartition; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.apache.helix.manager.zk.ZkBaseDataAccessor; +import org.apache.helix.model.ExternalView; +import org.apache.helix.model.IdealState; +import org.apache.helix.store.zk.ZkHelixPropertyStore; +import org.apache.helix.zookeeper.datamodel.ZNRecord; +import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer; +import org.apache.helix.zookeeper.impl.client.ZkClient; +import org.apache.pinot.broker.routing.segmentmetadata.SegmentZkMetadataFetcher; +import org.apache.pinot.common.metadata.ZKMetadataProvider; +import org.apache.pinot.common.metadata.segment.SegmentPartitionMetadata; +import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; +import org.apache.pinot.controller.helix.ControllerTest; +import org.apache.pinot.core.routing.TablePartitionInfo; +import org.apache.pinot.segment.spi.partition.metadata.ColumnPartitionMetadata; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel.ONLINE; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertEqualsNoOrder; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; + + +public class SegmentPartitionMetadataManagerTest extends ControllerTest { + private static final String OFFLINE_TABLE_NAME = "testTable_OFFLINE"; + private static final String PARTITION_COLUMN = "memberId"; + private static final String PARTITION_COLUMN_FUNC = "Murmur"; + private static final int NUM_PARTITIONS = 2; + private static final String PARTITION_COLUMN_FUNC_ALT = "Modulo"; + private static final int NUM_PARTITIONS_ALT = 4; + private static final String SERVER_0 = "server0"; + private static final String SERVER_1 = "server1"; + + private ZkClient _zkClient; + private ZkHelixPropertyStore _propertyStore; + + @BeforeClass + public void setUp() { + startZk(); + _zkClient = new ZkClient(getZkUrl(), ZkClient.DEFAULT_SESSION_TIMEOUT, ZkClient.DEFAULT_CONNECTION_TIMEOUT, + new ZNRecordSerializer()); + _propertyStore = + new ZkHelixPropertyStore<>(new ZkBaseDataAccessor<>(_zkClient), "/TimeBoundaryManagerTest/PROPERTYSTORE", null); + } + + @AfterClass + public void tearDown() { + _zkClient.close(); + stopZk(); + } + + @Test + public void testPartitionMetadataManagerProcessingThroughSegmentChangesSinglePartitionTable() { + ExternalView externalView = new ExternalView(OFFLINE_TABLE_NAME); + Map> segmentAssignment = externalView.getRecord().getMapFields(); + Map onlineInstanceStateMap = ImmutableMap.of(SERVER_0, ONLINE, SERVER_1, ONLINE); + Set onlineSegments = new HashSet<>(); + // NOTE: Ideal state is not used in the current implementation. + IdealState idealState = new IdealState(OFFLINE_TABLE_NAME); + + SegmentPartitionMetadataManager partitionMetadataManager = + new SegmentPartitionMetadataManager(OFFLINE_TABLE_NAME, PARTITION_COLUMN, PARTITION_COLUMN_FUNC, + NUM_PARTITIONS); + SegmentZkMetadataFetcher segmentZkMetadataFetcher = + new SegmentZkMetadataFetcher(OFFLINE_TABLE_NAME, _propertyStore); + segmentZkMetadataFetcher.register(partitionMetadataManager); + + // Initial state should be all empty + segmentZkMetadataFetcher.init(idealState, externalView, onlineSegments); + TablePartitionInfo tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + assertEquals(tablePartitionInfo.getPartitionInfoMap(), new TablePartitionInfo.PartitionInfo[NUM_PARTITIONS]); + assertTrue(tablePartitionInfo.getSegmentsWithInvalidPartition().isEmpty()); + + // Adding segment without partition metadata should be recorded in the invalid segments + String segmentWithoutPartitionMetadata = "segmentWithoutPartitionMetadata"; + onlineSegments.add(segmentWithoutPartitionMetadata); + segmentAssignment.put(segmentWithoutPartitionMetadata, onlineInstanceStateMap); + SegmentZKMetadata segmentZKMetadataWithoutPartitionMetadata = + new SegmentZKMetadata(segmentWithoutPartitionMetadata); + ZKMetadataProvider.setSegmentZKMetadata(_propertyStore, OFFLINE_TABLE_NAME, + segmentZKMetadataWithoutPartitionMetadata); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + assertEquals(tablePartitionInfo.getPartitionInfoMap(), new TablePartitionInfo.PartitionInfo[NUM_PARTITIONS]); + assertEquals(tablePartitionInfo.getSegmentsWithInvalidPartition(), + Collections.singletonList(segmentWithoutPartitionMetadata)); + + // Removing segment without partition metadata should remove it from the invalid segments + onlineSegments.remove(segmentWithoutPartitionMetadata); + segmentAssignment.remove(segmentWithoutPartitionMetadata); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + assertEquals(tablePartitionInfo.getPartitionInfoMap(), new TablePartitionInfo.PartitionInfo[NUM_PARTITIONS]); + assertTrue(tablePartitionInfo.getSegmentsWithInvalidPartition().isEmpty()); + + // Same logic applies to the new segment + onlineSegments.add(segmentWithoutPartitionMetadata); + segmentAssignment.put(segmentWithoutPartitionMetadata, onlineInstanceStateMap); + segmentZKMetadataWithoutPartitionMetadata = new SegmentZKMetadata(segmentWithoutPartitionMetadata); + segmentZKMetadataWithoutPartitionMetadata.setPushTime(System.currentTimeMillis()); + ZKMetadataProvider.setSegmentZKMetadata(_propertyStore, OFFLINE_TABLE_NAME, + segmentZKMetadataWithoutPartitionMetadata); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + assertEquals(tablePartitionInfo.getPartitionInfoMap(), new TablePartitionInfo.PartitionInfo[NUM_PARTITIONS]); + assertEquals(tablePartitionInfo.getSegmentsWithInvalidPartition(), + Collections.singletonList(segmentWithoutPartitionMetadata)); + onlineSegments.remove(segmentWithoutPartitionMetadata); + segmentAssignment.remove(segmentWithoutPartitionMetadata); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + assertEquals(tablePartitionInfo.getPartitionInfoMap(), new TablePartitionInfo.PartitionInfo[NUM_PARTITIONS]); + assertTrue(tablePartitionInfo.getSegmentsWithInvalidPartition().isEmpty()); + + // Adding segments inline with the partition column config should yield correct partition results + String segment0 = "segment0"; + onlineSegments.add(segment0); + segmentAssignment.put(segment0, Collections.singletonMap(SERVER_0, ONLINE)); + setSegmentZKMetadata(segment0, PARTITION_COLUMN_FUNC, NUM_PARTITIONS, 0, 0L); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + TablePartitionInfo.PartitionInfo[] partitionInfoMap = tablePartitionInfo.getPartitionInfoMap(); + assertEquals(partitionInfoMap[0]._fullyReplicatedServers, Collections.singleton(SERVER_0)); + assertEquals(partitionInfoMap[0]._segments, Collections.singleton(segment0)); + assertNull(partitionInfoMap[1]); + assertTrue(tablePartitionInfo.getSegmentsWithInvalidPartition().isEmpty()); + + // Adding one more segments + String segment1 = "segment1"; + onlineSegments.add(segment1); + segmentAssignment.put(segment1, Collections.singletonMap(SERVER_1, ONLINE)); + setSegmentZKMetadata(segment1, PARTITION_COLUMN_FUNC, NUM_PARTITIONS, 1, 0L); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + partitionInfoMap = tablePartitionInfo.getPartitionInfoMap(); + assertEquals(partitionInfoMap[0]._fullyReplicatedServers, Collections.singleton(SERVER_0)); + assertEquals(partitionInfoMap[0]._segments, Collections.singleton(segment0)); + assertEquals(partitionInfoMap[1]._fullyReplicatedServers, Collections.singleton(SERVER_1)); + assertEquals(partitionInfoMap[1]._segments, Collections.singleton(segment1)); + assertTrue(tablePartitionInfo.getSegmentsWithInvalidPartition().isEmpty()); + + // Updating partition metadata without refreshing should have no effect + setSegmentZKMetadata(segment0, PARTITION_COLUMN_FUNC_ALT, NUM_PARTITIONS_ALT, 0, 0L); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + partitionInfoMap = tablePartitionInfo.getPartitionInfoMap(); + assertEquals(partitionInfoMap[0]._fullyReplicatedServers, Collections.singleton(SERVER_0)); + assertEquals(partitionInfoMap[0]._segments, Collections.singleton(segment0)); + assertEquals(partitionInfoMap[1]._fullyReplicatedServers, Collections.singleton(SERVER_1)); + assertEquals(partitionInfoMap[1]._segments, Collections.singleton(segment1)); + assertTrue(tablePartitionInfo.getSegmentsWithInvalidPartition().isEmpty()); + + // Refreshing the changed segment should update the partition info + segmentZkMetadataFetcher.refreshSegment(segment0); + // segment0 is no longer inline with the table config, and it should be recorded in the invalid segments + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + partitionInfoMap = tablePartitionInfo.getPartitionInfoMap(); + assertNull(partitionInfoMap[0]); + assertEquals(partitionInfoMap[1]._fullyReplicatedServers, Collections.singleton(SERVER_1)); + assertEquals(partitionInfoMap[1]._segments, Collections.singleton(segment1)); + assertEquals(tablePartitionInfo.getSegmentsWithInvalidPartition(), Collections.singletonList(segment0)); + + // Refresh the changed segment back to inline, and both segments should now be back on the partition list + setSegmentZKMetadata(segment0, PARTITION_COLUMN_FUNC, NUM_PARTITIONS, 0, 0L); + segmentZkMetadataFetcher.refreshSegment(segment0); + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + partitionInfoMap = tablePartitionInfo.getPartitionInfoMap(); + assertEquals(partitionInfoMap[0]._fullyReplicatedServers, Collections.singleton(SERVER_0)); + assertEquals(partitionInfoMap[0]._segments, Collections.singleton(segment0)); + assertEquals(partitionInfoMap[1]._fullyReplicatedServers, Collections.singleton(SERVER_1)); + assertEquals(partitionInfoMap[1]._segments, Collections.singleton(segment1)); + assertTrue(tablePartitionInfo.getSegmentsWithInvalidPartition().isEmpty()); + + // Changing one of the segments to be on a different server should update the fully replicated servers + segmentAssignment.put(segment1, Collections.singletonMap(SERVER_0, ONLINE)); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + partitionInfoMap = tablePartitionInfo.getPartitionInfoMap(); + assertEquals(partitionInfoMap[0]._fullyReplicatedServers, Collections.singleton(SERVER_0)); + assertEquals(partitionInfoMap[0]._segments, Collections.singleton(segment0)); + assertEquals(partitionInfoMap[1]._fullyReplicatedServers, Collections.singleton(SERVER_0)); + assertEquals(partitionInfoMap[1]._segments, Collections.singleton(segment1)); + assertTrue(tablePartitionInfo.getSegmentsWithInvalidPartition().isEmpty()); + + // Adding one more segment to partition-1 but located on a different server will update the partition map, but + // remove the fully replicated server because it is no longer having full replica on a single server + String segment2 = "segment2"; + onlineSegments.add(segment2); + segmentAssignment.put(segment2, Collections.singletonMap(SERVER_1, ONLINE)); + setSegmentZKMetadata(segment2, PARTITION_COLUMN_FUNC, NUM_PARTITIONS, 1, 0L); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + partitionInfoMap = tablePartitionInfo.getPartitionInfoMap(); + assertEquals(partitionInfoMap[0]._fullyReplicatedServers, Collections.singleton(SERVER_0)); + assertEquals(partitionInfoMap[0]._segments, Collections.singleton(segment0)); + assertTrue(partitionInfoMap[1]._fullyReplicatedServers.isEmpty()); + assertEqualsNoOrder(partitionInfoMap[1]._segments.toArray(), new String[]{segment1, segment2}); + assertTrue(tablePartitionInfo.getSegmentsWithInvalidPartition().isEmpty()); + + // Updating the segment to be replicated on 2 servers should add the fully replicated server back + segmentAssignment.put(segment2, ImmutableMap.of(SERVER_0, ONLINE, SERVER_1, ONLINE)); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + partitionInfoMap = tablePartitionInfo.getPartitionInfoMap(); + assertEquals(partitionInfoMap[0]._fullyReplicatedServers, Collections.singleton(SERVER_0)); + assertEquals(partitionInfoMap[0]._segments, Collections.singleton(segment0)); + assertEquals(partitionInfoMap[1]._fullyReplicatedServers, Collections.singleton(SERVER_0)); + assertEqualsNoOrder(partitionInfoMap[1]._segments.toArray(), new String[]{segment1, segment2}); + assertTrue(tablePartitionInfo.getSegmentsWithInvalidPartition().isEmpty()); + + // Adding a newly created segment without available replica should not update the partition map + String newSegment = "newSegment"; + onlineSegments.add(newSegment); + setSegmentZKMetadata(newSegment, PARTITION_COLUMN_FUNC, NUM_PARTITIONS, 0, System.currentTimeMillis()); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + partitionInfoMap = tablePartitionInfo.getPartitionInfoMap(); + assertEquals(partitionInfoMap[0]._fullyReplicatedServers, Collections.singleton(SERVER_0)); + assertEquals(partitionInfoMap[0]._segments, Collections.singleton(segment0)); + assertEquals(partitionInfoMap[1]._fullyReplicatedServers, Collections.singleton(SERVER_0)); + assertEqualsNoOrder(partitionInfoMap[1]._segments.toArray(), new String[]{segment1, segment2}); + assertTrue(tablePartitionInfo.getSegmentsWithInvalidPartition().isEmpty()); + + // Making all of them replicated will show full list, even for the new segment + segmentAssignment.put(segment0, ImmutableMap.of(SERVER_0, ONLINE, SERVER_1, ONLINE)); + segmentAssignment.put(segment1, ImmutableMap.of(SERVER_0, ONLINE, SERVER_1, ONLINE)); + segmentAssignment.put(segment2, ImmutableMap.of(SERVER_0, ONLINE, SERVER_1, ONLINE)); + segmentAssignment.put(newSegment, ImmutableMap.of(SERVER_0, ONLINE, SERVER_1, ONLINE)); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + tablePartitionInfo = partitionMetadataManager.getTablePartitionInfo(); + partitionInfoMap = tablePartitionInfo.getPartitionInfoMap(); + assertEquals(partitionInfoMap[0]._fullyReplicatedServers, ImmutableSet.of(SERVER_0, SERVER_1)); + assertEqualsNoOrder(partitionInfoMap[0]._segments.toArray(), new String[]{segment0, newSegment}); + assertEquals(partitionInfoMap[1]._fullyReplicatedServers, ImmutableSet.of(SERVER_0, SERVER_1)); + assertEqualsNoOrder(partitionInfoMap[1]._segments.toArray(), new String[]{segment1, segment2}); + assertTrue(tablePartitionInfo.getSegmentsWithInvalidPartition().isEmpty()); + } + + private void setSegmentZKMetadata(String segment, String partitionFunction, int numPartitions, int partitionId, + long creationTimeMs) { + SegmentZKMetadata segmentZKMetadata = new SegmentZKMetadata(segment); + segmentZKMetadata.setPartitionMetadata(new SegmentPartitionMetadata(Collections.singletonMap(PARTITION_COLUMN, + new ColumnPartitionMetadata(partitionFunction, numPartitions, Collections.singleton(partitionId), null)))); + segmentZKMetadata.setCreationTime(creationTimeMs); + ZKMetadataProvider.setSegmentZKMetadata(_propertyStore, OFFLINE_TABLE_NAME, segmentZKMetadata); + } +} diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPrunerTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPrunerTest.java index cc60739d3695..5e48a981ccc4 100644 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPrunerTest.java +++ b/pinot-broker/src/test/java/org/apache/pinot/broker/routing/segmentpruner/SegmentPrunerTest.java @@ -18,8 +18,7 @@ */ package org.apache.pinot.broker.routing.segmentpruner; -import java.util.Arrays; -import java.util.Collections; +import java.sql.Timestamp; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -35,6 +34,7 @@ import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer; import org.apache.helix.zookeeper.impl.client.ZkClient; +import org.apache.pinot.broker.routing.segmentmetadata.SegmentZkMetadataFetcher; import org.apache.pinot.common.metadata.ZKMetadataProvider; import org.apache.pinot.common.metadata.segment.SegmentPartitionMetadata; import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; @@ -49,11 +49,11 @@ import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.config.table.ingestion.StreamIngestionConfig; +import org.apache.pinot.spi.data.DateTimeFieldSpec; import org.apache.pinot.spi.data.DateTimeFormatSpec; -import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.data.Schema; import org.apache.pinot.spi.stream.StreamConfigProperties; -import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.apache.pinot.sql.parsers.CalciteSqlCompiler; import org.mockito.Mockito; @@ -77,29 +77,45 @@ public class SegmentPrunerTest extends ControllerTest { private static final String SDF_PATTERN = "yyyyMMdd"; private static final String QUERY_1 = "SELECT * FROM testTable"; - private static final String QUERY_2 = "SELECT * FROM testTable where memberId = 0"; - private static final String QUERY_3 = "SELECT * FROM testTable where memberId IN (1, 2)"; - private static final String QUERY_4 = "SELECT * FROM testTable where memberId = 0 AND memberName='xyz'"; - - private static final String TIME_QUERY_1 = "SELECT * FROM testTable where timeColumn = 40"; - private static final String TIME_QUERY_2 = "SELECT * FROM testTable where timeColumn BETWEEN 20 AND 30"; - private static final String TIME_QUERY_3 = "SELECT * FROM testTable where 30 < timeColumn AND timeColumn <= 50"; - private static final String TIME_QUERY_4 = "SELECT * FROM testTable where timeColumn < 15 OR timeColumn > 45"; + private static final String QUERY_2 = "SELECT * FROM testTable WHERE memberId = 0"; + private static final String QUERY_3 = "SELECT * FROM testTable WHERE memberId IN (1, 2)"; + private static final String QUERY_4 = "SELECT * FROM testTable WHERE memberId = 0 AND memberName = 'xyz'"; + + private static final String TIME_QUERY_1 = "SELECT * FROM testTable WHERE timeColumn = 40"; + private static final String TIME_QUERY_2 = "SELECT * FROM testTable WHERE timeColumn BETWEEN 20 AND 30"; + private static final String TIME_QUERY_3 = "SELECT * FROM testTable WHERE 30 < timeColumn AND timeColumn <= 50"; + private static final String TIME_QUERY_4 = "SELECT * FROM testTable WHERE timeColumn < 15 OR timeColumn > 45"; private static final String TIME_QUERY_5 = - "SELECT * FROM testTable where timeColumn < 15 OR (60 < timeColumn AND timeColumn < 70)"; - private static final String TIME_QUERY_6 = "SELECT * FROM testTable where timeColumn < 0 AND timeColumn > 0"; + "SELECT * FROM testTable WHERE timeColumn < 15 OR (60 < timeColumn AND timeColumn < 70)"; + private static final String TIME_QUERY_6 = "SELECT * FROM testTable WHERE timeColumn NOT BETWEEN 20 AND 30"; + private static final String TIME_QUERY_7 = "SELECT * FROM testTable WHERE NOT timeColumn > 30"; + private static final String TIME_QUERY_8 = "SELECT * FROM testTable WHERE timeColumn < 0 AND timeColumn > 0"; - private static final String SDF_QUERY_1 = "SELECT * FROM testTable where timeColumn = 20200131"; - private static final String SDF_QUERY_2 = "SELECT * FROM testTable where timeColumn BETWEEN 20200101 AND 20200331"; + private static final String SDF_QUERY_1 = "SELECT * FROM testTable WHERE timeColumn = 20200131"; + private static final String SDF_QUERY_2 = "SELECT * FROM testTable WHERE timeColumn BETWEEN 20200101 AND 20200331"; private static final String SDF_QUERY_3 = - "SELECT * FROM testTable where 20200430 < timeColumn AND timeColumn < 20200630"; + "SELECT * FROM testTable WHERE 20200430 < timeColumn AND timeColumn < 20200630"; private static final String SDF_QUERY_4 = - "SELECT * FROM testTable where timeColumn <= 20200101 OR timeColumn in (20200201, 20200401)"; + "SELECT * FROM testTable WHERE timeColumn <= 20200101 OR timeColumn IN (20200201, 20200401)"; private static final String SDF_QUERY_5 = - "SELECT * FROM testTable where timeColumn in (20200101, 20200102) AND timeColumn >= 20200530"; - - private static final String SQL_TIME_QUERY_1 = "SELECT * FROM testTable WHERE timeColumn NOT BETWEEN 20 AND 30"; - private static final String SQL_TIME_QUERY_2 = "SELECT * FROM testTable WHERE NOT timeColumn > 30"; + "SELECT * FROM testTable WHERE timeColumn IN (20200101, 20200102) AND timeColumn >= 20200530"; + + // Timestamp can be passed as string or long + private static final String TIMESTAMP_QUERY_1 = "SELECT * FROM testTable WHERE timeColumn = '2020-01-31 00:00:00'"; + private static final String TIMESTAMP_QUERY_2 = String.format("SELECT * FROM testTable WHERE timeColumn = %d", + Timestamp.valueOf("2020-01-31 00:00:00").getTime()); + private static final String TIMESTAMP_QUERY_3 = + "SELECT * FROM testTable WHERE timeColumn BETWEEN '2020-01-01 00:00:00' AND '2020-03-31 00:00:00'"; + private static final String TIMESTAMP_QUERY_4 = + String.format("SELECT * FROM testTable WHERE timeColumn BETWEEN %d AND %d", + Timestamp.valueOf("2020-01-01 00:00:00").getTime(), Timestamp.valueOf("2020-03-31 00:00:00").getTime()); + private static final String TIMESTAMP_QUERY_5 = + "SELECT * FROM testTable WHERE timeColumn <= '2020-01-01 00:00:00' OR timeColumn IN ('2020-02-01 00:00:00', " + + "'2020-04-01 00:00:00')"; + private static final String TIMESTAMP_QUERY_6 = + String.format("SELECT * FROM testTable WHERE timeColumn <= %d OR timeColumn IN (%d, %d)", + Timestamp.valueOf("2020-01-01 00:00:00").getTime(), Timestamp.valueOf("2020-02-01 00:00:00").getTime(), + Timestamp.valueOf("2020-04-01 00:00:00").getTime()); // this is duplicate with KinesisConfig.STREAM_TYPE, while instead of use KinesisConfig.STREAM_TYPE directly, we // hardcode the value here to avoid pulling the entire pinot-kinesis module as dependency. @@ -126,6 +142,7 @@ public void tearDown() { @Test public void testSegmentPrunerFactoryForPartitionPruner() { TableConfig tableConfig = mock(TableConfig.class); + when(tableConfig.getTableName()).thenReturn(OFFLINE_TABLE_NAME); IndexingConfig indexingConfig = mock(IndexingConfig.class); when(tableConfig.getIndexingConfig()).thenReturn(indexingConfig); @@ -140,8 +157,7 @@ public void testSegmentPrunerFactoryForPartitionPruner() { assertEquals(segmentPruners.size(), 0); // Segment partition config is missing - when(routingConfig.getSegmentPrunerTypes()).thenReturn( - Collections.singletonList(RoutingConfig.PARTITION_SEGMENT_PRUNER_TYPE)); + when(routingConfig.getSegmentPrunerTypes()).thenReturn(List.of(RoutingConfig.PARTITION_SEGMENT_PRUNER_TYPE)); segmentPruners = SegmentPrunerFactory.getSegmentPruners(tableConfig, _propertyStore); assertEquals(segmentPruners.size(), 0); @@ -188,8 +204,7 @@ public void testSegmentPrunerFactoryForPartitionPruner() { @Test public void testSegmentPrunerFactoryForTimeRangePruner() { TableConfig tableConfig = mock(TableConfig.class); - when(tableConfig.getTableName()).thenReturn(RAW_TABLE_NAME); - setSchemaDateTimeFieldSpec(RAW_TABLE_NAME, TimeUnit.HOURS); + when(tableConfig.getTableName()).thenReturn(OFFLINE_TABLE_NAME); // Routing config is missing List segmentPruners = SegmentPrunerFactory.getSegmentPruners(tableConfig, _propertyStore); @@ -202,8 +217,7 @@ public void testSegmentPrunerFactoryForTimeRangePruner() { assertEquals(segmentPruners.size(), 0); // Validation config is missing - when(routingConfig.getSegmentPrunerTypes()).thenReturn( - Collections.singletonList(RoutingConfig.TIME_SEGMENT_PRUNER_TYPE)); + when(routingConfig.getSegmentPrunerTypes()).thenReturn(List.of(RoutingConfig.TIME_SEGMENT_PRUNER_TYPE)); segmentPruners = SegmentPrunerFactory.getSegmentPruners(tableConfig, _propertyStore); assertEquals(segmentPruners.size(), 0); @@ -213,41 +227,54 @@ public void testSegmentPrunerFactoryForTimeRangePruner() { segmentPruners = SegmentPrunerFactory.getSegmentPruners(tableConfig, _propertyStore); assertEquals(segmentPruners.size(), 0); - // Time range pruner should be returned + // Schema is missing when(validationConfig.getTimeColumnName()).thenReturn(TIME_COLUMN); segmentPruners = SegmentPrunerFactory.getSegmentPruners(tableConfig, _propertyStore); + assertEquals(segmentPruners.size(), 0); + + // Field spec is missing + Schema schema = new Schema.SchemaBuilder().setSchemaName(RAW_TABLE_NAME).build(); + ZKMetadataProvider.setSchema(_propertyStore, schema); + segmentPruners = SegmentPrunerFactory.getSegmentPruners(tableConfig, _propertyStore); + assertEquals(segmentPruners.size(), 0); + + // Time range pruner should be returned + schema = new Schema.SchemaBuilder().setSchemaName(RAW_TABLE_NAME) + .addDateTimeField(TIME_COLUMN, DataType.TIMESTAMP, "TIMESTAMP", "1:MILLISECONDS").build(); + ZKMetadataProvider.setSchema(_propertyStore, schema); + segmentPruners = SegmentPrunerFactory.getSegmentPruners(tableConfig, _propertyStore); assertEquals(segmentPruners.size(), 1); assertTrue(segmentPruners.get(0) instanceof TimeSegmentPruner); } @Test - public void testEnablingEmptySegmentPruner() { + public void testSegmentPrunerFactoryForEmptySegmentPruner() { TableConfig tableConfig = mock(TableConfig.class); + when(tableConfig.getTableName()).thenReturn(REALTIME_TABLE_NAME); IndexingConfig indexingConfig = mock(IndexingConfig.class); + when(tableConfig.getIndexingConfig()).thenReturn(indexingConfig); RoutingConfig routingConfig = mock(RoutingConfig.class); - StreamIngestionConfig streamIngestionConfig = mock(StreamIngestionConfig.class); + when(tableConfig.getRoutingConfig()).thenReturn(routingConfig); + ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); // When routingConfig is configured with EmptySegmentPruner, EmptySegmentPruner should be returned. - when(tableConfig.getRoutingConfig()).thenReturn(routingConfig); - when(routingConfig.getSegmentPrunerTypes()).thenReturn( - Collections.singletonList(RoutingConfig.EMPTY_SEGMENT_PRUNER_TYPE)); - List segmentPruners = SegmentPrunerFactory.getSegmentPruners(tableConfig, _propertyStore); + when(routingConfig.getSegmentPrunerTypes()).thenReturn(List.of(RoutingConfig.EMPTY_SEGMENT_PRUNER_TYPE)); + List segmentPruners = SegmentPrunerFactory.getSegmentPruners(tableConfig, propertyStore); assertEquals(segmentPruners.size(), 1); assertTrue(segmentPruners.get(0) instanceof EmptySegmentPruner); // When indexingConfig is configured with Kinesis streaming, EmptySegmentPruner should be returned. - when(indexingConfig.getStreamConfigs()).thenReturn( - Collections.singletonMap(StreamConfigProperties.STREAM_TYPE, KINESIS_STREAM_TYPE)); - segmentPruners = SegmentPrunerFactory.getSegmentPruners(tableConfig, _propertyStore); + when(indexingConfig.getStreamConfigs()).thenReturn(Map.of(StreamConfigProperties.STREAM_TYPE, KINESIS_STREAM_TYPE)); + segmentPruners = SegmentPrunerFactory.getSegmentPruners(tableConfig, propertyStore); assertEquals(segmentPruners.size(), 1); assertTrue(segmentPruners.get(0) instanceof EmptySegmentPruner); // When streamIngestionConfig is configured with Kinesis streaming, EmptySegmentPruner should be returned. + StreamIngestionConfig streamIngestionConfig = mock(StreamIngestionConfig.class); when(streamIngestionConfig.getStreamConfigMaps()).thenReturn( - Collections.singletonList(Collections.singletonMap(StreamConfigProperties.STREAM_TYPE, KINESIS_STREAM_TYPE))); - when(indexingConfig.getStreamConfigs()).thenReturn( - Collections.singletonMap(StreamConfigProperties.STREAM_TYPE, KINESIS_STREAM_TYPE)); - segmentPruners = SegmentPrunerFactory.getSegmentPruners(tableConfig, _propertyStore); + List.of(Map.of(StreamConfigProperties.STREAM_TYPE, KINESIS_STREAM_TYPE))); + when(indexingConfig.getStreamConfigs()).thenReturn(Map.of(StreamConfigProperties.STREAM_TYPE, KINESIS_STREAM_TYPE)); + segmentPruners = SegmentPrunerFactory.getSegmentPruners(tableConfig, propertyStore); assertEquals(segmentPruners.size(), 1); assertTrue(segmentPruners.get(0) instanceof EmptySegmentPruner); } @@ -258,130 +285,103 @@ public void testPartitionAwareSegmentPruner() { BrokerRequest brokerRequest2 = CalciteSqlCompiler.compileToBrokerRequest(QUERY_2); BrokerRequest brokerRequest3 = CalciteSqlCompiler.compileToBrokerRequest(QUERY_3); BrokerRequest brokerRequest4 = CalciteSqlCompiler.compileToBrokerRequest(QUERY_4); + // NOTE: Ideal state and external view are not used in the current implementation IdealState idealState = Mockito.mock(IdealState.class); ExternalView externalView = Mockito.mock(ExternalView.class); SinglePartitionColumnSegmentPruner singlePartitionColumnSegmentPruner = - new SinglePartitionColumnSegmentPruner(OFFLINE_TABLE_NAME, PARTITION_COLUMN_1, _propertyStore); + new SinglePartitionColumnSegmentPruner(OFFLINE_TABLE_NAME, PARTITION_COLUMN_1); + SegmentZkMetadataFetcher segmentZkMetadataFetcher = + new SegmentZkMetadataFetcher(OFFLINE_TABLE_NAME, _propertyStore); + segmentZkMetadataFetcher.register(singlePartitionColumnSegmentPruner); Set onlineSegments = new HashSet<>(); - singlePartitionColumnSegmentPruner.init(idealState, externalView, onlineSegments); - assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest1, Collections.emptySet()), - Collections.emptySet()); - assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest2, Collections.emptySet()), - Collections.emptySet()); - assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest3, Collections.emptySet()), - Collections.emptySet()); + segmentZkMetadataFetcher.init(idealState, externalView, onlineSegments); + + Set input = Set.of(); + assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest1, input), input); + assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest2, input), input); + assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest3, input), input); + assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest4, input), input); // Segments without metadata (not updated yet) should not be pruned String newSegment = "newSegment"; - assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest1, Collections.singleton(newSegment)), - Collections.singletonList(newSegment)); - assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest2, Collections.singleton(newSegment)), - Collections.singletonList(newSegment)); - assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest3, Collections.singleton(newSegment)), - Collections.singletonList(newSegment)); + onlineSegments.add(newSegment); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + input = Set.of(newSegment); + assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest1, input), input); + assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest2, input), input); + assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest3, input), input); + assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest4, input), input); // Segments without partition metadata should not be pruned String segmentWithoutPartitionMetadata = "segmentWithoutPartitionMetadata"; - onlineSegments.add(segmentWithoutPartitionMetadata); - SegmentZKMetadata segmentZKMetadataWithoutPartitionMetadata = - new SegmentZKMetadata(segmentWithoutPartitionMetadata); ZKMetadataProvider.setSegmentZKMetadata(_propertyStore, OFFLINE_TABLE_NAME, - segmentZKMetadataWithoutPartitionMetadata); - singlePartitionColumnSegmentPruner.onAssignmentChange(idealState, externalView, onlineSegments); - assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest1, - new HashSet<>(Collections.singletonList(segmentWithoutPartitionMetadata))), - Collections.singletonList(segmentWithoutPartitionMetadata)); - assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest2, - new HashSet<>(Collections.singletonList(segmentWithoutPartitionMetadata))), - Collections.singletonList(segmentWithoutPartitionMetadata)); - assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest3, - new HashSet<>(Collections.singletonList(segmentWithoutPartitionMetadata))), - Collections.singletonList(segmentWithoutPartitionMetadata)); + new SegmentZKMetadata(segmentWithoutPartitionMetadata)); + onlineSegments.add(segmentWithoutPartitionMetadata); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + input = Set.of(segmentWithoutPartitionMetadata); + assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest1, input), input); + assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest2, input), input); + assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest3, input), input); + assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest4, input), input); // Test different partition functions and number of partitions // 0 % 5 = 0; 1 % 5 = 1; 2 % 5 = 2 String segment0 = "segment0"; - onlineSegments.add(segment0); setSegmentZKPartitionMetadata(OFFLINE_TABLE_NAME, segment0, "Modulo", 5, 0); + onlineSegments.add(segment0); // Murmur(0) % 4 = 0; Murmur(1) % 4 = 3; Murmur(2) % 4 = 0 String segment1 = "segment1"; - onlineSegments.add(segment1); setSegmentZKPartitionMetadata(OFFLINE_TABLE_NAME, segment1, "Murmur", 4, 0); - singlePartitionColumnSegmentPruner.onAssignmentChange(idealState, externalView, onlineSegments); - assertEquals( - singlePartitionColumnSegmentPruner.prune(brokerRequest1, new HashSet<>(Arrays.asList(segment0, segment1))), - new HashSet<>(Arrays.asList(segment0, segment1))); - assertEquals( - singlePartitionColumnSegmentPruner.prune(brokerRequest2, new HashSet<>(Arrays.asList(segment0, segment1))), - new HashSet<>(Arrays.asList(segment0, segment1))); - assertEquals( - singlePartitionColumnSegmentPruner.prune(brokerRequest3, new HashSet<>(Arrays.asList(segment0, segment1))), - new HashSet<>(Collections.singletonList(segment1))); + onlineSegments.add(segment1); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + input = Set.of(segment0, segment1); + assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest1, input), input); + assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest2, input), input); + assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest3, input), Set.of(segment1)); // Update partition metadata without refreshing should have no effect setSegmentZKPartitionMetadata(OFFLINE_TABLE_NAME, segment0, "Modulo", 4, 1); - singlePartitionColumnSegmentPruner.onAssignmentChange(idealState, externalView, onlineSegments); - assertEquals( - singlePartitionColumnSegmentPruner.prune(brokerRequest1, new HashSet<>(Arrays.asList(segment0, segment1))), - new HashSet<>(Arrays.asList(segment0, segment1))); - assertEquals( - singlePartitionColumnSegmentPruner.prune(brokerRequest2, new HashSet<>(Arrays.asList(segment0, segment1))), - new HashSet<>(Arrays.asList(segment0, segment1))); - assertEquals( - singlePartitionColumnSegmentPruner.prune(brokerRequest3, new HashSet<>(Arrays.asList(segment0, segment1))), - new HashSet<>(Collections.singletonList(segment1))); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest1, input), input); + assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest2, input), input); + assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest3, input), Set.of(segment1)); + assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest4, input), input); // Refresh the changed segment should update the segment pruner - singlePartitionColumnSegmentPruner.refreshSegment(segment0); - assertEquals( - singlePartitionColumnSegmentPruner.prune(brokerRequest1, new HashSet<>(Arrays.asList(segment0, segment1))), - new HashSet<>(Arrays.asList(segment0, segment1))); - assertEquals( - singlePartitionColumnSegmentPruner.prune(brokerRequest2, new HashSet<>(Arrays.asList(segment0, segment1))), - new HashSet<>(Collections.singletonList(segment1))); - assertEquals( - singlePartitionColumnSegmentPruner.prune(brokerRequest3, new HashSet<>(Arrays.asList(segment0, segment1))), - new HashSet<>(Arrays.asList(segment0, segment1))); + segmentZkMetadataFetcher.refreshSegment(segment0); + assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest1, input), input); + assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest2, input), Set.of(segment1)); + assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest3, input), input); + assertEquals(singlePartitionColumnSegmentPruner.prune(brokerRequest4, input), Set.of(segment1)); // Multi-column partitioned segment. MultiPartitionColumnsSegmentPruner multiPartitionColumnsSegmentPruner = new MultiPartitionColumnsSegmentPruner(OFFLINE_TABLE_NAME, - Stream.of(PARTITION_COLUMN_1, PARTITION_COLUMN_2).collect(Collectors.toSet()), _propertyStore); - multiPartitionColumnsSegmentPruner.init(idealState, externalView, onlineSegments); - assertEquals(multiPartitionColumnsSegmentPruner.prune(brokerRequest1, Collections.emptySet()), - Collections.emptySet()); - assertEquals(multiPartitionColumnsSegmentPruner.prune(brokerRequest2, Collections.emptySet()), - Collections.emptySet()); - assertEquals(multiPartitionColumnsSegmentPruner.prune(brokerRequest3, Collections.emptySet()), - Collections.emptySet()); - assertEquals(multiPartitionColumnsSegmentPruner.prune(brokerRequest4, Collections.emptySet()), - Collections.emptySet()); + Stream.of(PARTITION_COLUMN_1, PARTITION_COLUMN_2).collect(Collectors.toSet())); + segmentZkMetadataFetcher = new SegmentZkMetadataFetcher(OFFLINE_TABLE_NAME, _propertyStore); + segmentZkMetadataFetcher.register(multiPartitionColumnsSegmentPruner); + segmentZkMetadataFetcher.init(idealState, externalView, onlineSegments); + + assertEquals(multiPartitionColumnsSegmentPruner.prune(brokerRequest1, input), input); + assertEquals(multiPartitionColumnsSegmentPruner.prune(brokerRequest2, input), Set.of(segment1)); + assertEquals(multiPartitionColumnsSegmentPruner.prune(brokerRequest3, input), input); + assertEquals(multiPartitionColumnsSegmentPruner.prune(brokerRequest4, input), Set.of(segment1)); String segment2 = "segment2"; - onlineSegments.add(segment2); Map columnPartitionMetadataMap = new HashMap<>(); - columnPartitionMetadataMap.put(PARTITION_COLUMN_1, - new ColumnPartitionMetadata("Modulo", 4, Collections.singleton(0), null)); - Map partitionColumn2FunctionConfig = new HashMap<>(); - partitionColumn2FunctionConfig.put("columnValues", "xyz|abc"); - partitionColumn2FunctionConfig.put("columnValuesDelimiter", "|"); - columnPartitionMetadataMap.put(PARTITION_COLUMN_2, - new ColumnPartitionMetadata("BoundedColumnValue", 3, Collections.singleton(1), partitionColumn2FunctionConfig)); + columnPartitionMetadataMap.put(PARTITION_COLUMN_1, new ColumnPartitionMetadata("Modulo", 4, Set.of(0), null)); + columnPartitionMetadataMap.put(PARTITION_COLUMN_2, new ColumnPartitionMetadata("BoundedColumnValue", 3, Set.of(1), + Map.of("columnValues", "xyz|abc", "columnValuesDelimiter", "|"))); setSegmentZKPartitionMetadata(OFFLINE_TABLE_NAME, segment2, columnPartitionMetadataMap); - multiPartitionColumnsSegmentPruner.onAssignmentChange(idealState, externalView, onlineSegments); - assertEquals( - multiPartitionColumnsSegmentPruner.prune(brokerRequest1, new HashSet<>(Arrays.asList(segment0, segment1))), - new HashSet<>(Arrays.asList(segment0, segment1))); - assertEquals( - multiPartitionColumnsSegmentPruner.prune(brokerRequest2, new HashSet<>(Arrays.asList(segment0, segment1))), - new HashSet<>(Collections.singletonList(segment1))); - assertEquals( - multiPartitionColumnsSegmentPruner.prune(brokerRequest3, new HashSet<>(Arrays.asList(segment0, segment1))), - new HashSet<>(Arrays.asList(segment0, segment1))); - assertEquals(multiPartitionColumnsSegmentPruner.prune(brokerRequest4, - new HashSet<>(Arrays.asList(segment0, segment1, segment2))), new HashSet<>(Arrays.asList(segment1, segment2))); + onlineSegments.add(segment2); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + input = Set.of(segment0, segment1, segment2); + assertEquals(multiPartitionColumnsSegmentPruner.prune(brokerRequest1, input), input); + assertEquals(multiPartitionColumnsSegmentPruner.prune(brokerRequest2, input), Set.of(segment1, segment2)); + assertEquals(multiPartitionColumnsSegmentPruner.prune(brokerRequest3, input), Set.of(segment0, segment1)); + assertEquals(multiPartitionColumnsSegmentPruner.prune(brokerRequest4, input), Set.of(segment1, segment2)); } @Test @@ -393,138 +393,112 @@ public void testTimeSegmentPruner() { BrokerRequest brokerRequest5 = CalciteSqlCompiler.compileToBrokerRequest(TIME_QUERY_4); BrokerRequest brokerRequest6 = CalciteSqlCompiler.compileToBrokerRequest(TIME_QUERY_5); BrokerRequest brokerRequest7 = CalciteSqlCompiler.compileToBrokerRequest(TIME_QUERY_6); + BrokerRequest brokerRequest8 = CalciteSqlCompiler.compileToBrokerRequest(TIME_QUERY_7); + BrokerRequest brokerRequest9 = CalciteSqlCompiler.compileToBrokerRequest(TIME_QUERY_8); + // NOTE: Ideal state and external view are not used in the current implementation IdealState idealState = Mockito.mock(IdealState.class); ExternalView externalView = Mockito.mock(ExternalView.class); - TableConfig tableConfig = getTableConfig(RAW_TABLE_NAME, TableType.REALTIME); - setSchemaDateTimeFieldSpec(RAW_TABLE_NAME, TimeUnit.DAYS); - - TimeSegmentPruner segmentPruner = new TimeSegmentPruner(tableConfig, _propertyStore); + TableConfig tableConfig = + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setTimeColumnName(TIME_COLUMN).build(); + DateTimeFieldSpec timeFieldSpec = new DateTimeFieldSpec(TIME_COLUMN, DataType.INT, "EPOCH|DAYS", "1:DAYS"); + TimeSegmentPruner segmentPruner = new TimeSegmentPruner(tableConfig, timeFieldSpec); + SegmentZkMetadataFetcher segmentZkMetadataFetcher = + new SegmentZkMetadataFetcher(REALTIME_TABLE_NAME, _propertyStore); + segmentZkMetadataFetcher.register(segmentPruner); Set onlineSegments = new HashSet<>(); - segmentPruner.init(idealState, externalView, onlineSegments); - assertEquals(segmentPruner.prune(brokerRequest1, Collections.emptySet()), Collections.emptySet()); - assertEquals(segmentPruner.prune(brokerRequest2, Collections.emptySet()), Collections.emptySet()); - assertEquals(segmentPruner.prune(brokerRequest3, Collections.emptySet()), Collections.emptySet()); - assertEquals(segmentPruner.prune(brokerRequest4, Collections.emptySet()), Collections.emptySet()); - assertEquals(segmentPruner.prune(brokerRequest5, Collections.emptySet()), Collections.emptySet()); - assertEquals(segmentPruner.prune(brokerRequest6, Collections.emptySet()), Collections.emptySet()); - assertEquals(segmentPruner.prune(brokerRequest7, Collections.emptySet()), Collections.emptySet()); - - // Initialize with non-empty onlineSegments + segmentZkMetadataFetcher.init(idealState, externalView, onlineSegments); + + Set input = Set.of(); + assertEquals(segmentPruner.prune(brokerRequest1, input), input); + assertEquals(segmentPruner.prune(brokerRequest2, input), input); + assertEquals(segmentPruner.prune(brokerRequest3, input), input); + assertEquals(segmentPruner.prune(brokerRequest4, input), input); + assertEquals(segmentPruner.prune(brokerRequest5, input), input); + assertEquals(segmentPruner.prune(brokerRequest6, input), input); + assertEquals(segmentPruner.prune(brokerRequest7, input), input); + assertEquals(segmentPruner.prune(brokerRequest8, input), input); + assertEquals(segmentPruner.prune(brokerRequest9, input), input); + // Segments without metadata (not updated yet) should not be pruned - segmentPruner = new TimeSegmentPruner(tableConfig, _propertyStore); String newSegment = "newSegment"; onlineSegments.add(newSegment); - segmentPruner.init(idealState, externalView, onlineSegments); - assertEquals(segmentPruner.prune(brokerRequest1, Collections.singleton(newSegment)), - Collections.singletonList(newSegment)); - assertEquals(segmentPruner.prune(brokerRequest2, Collections.singleton(newSegment)), - Collections.singletonList(newSegment)); - assertEquals(segmentPruner.prune(brokerRequest3, Collections.singleton(newSegment)), - Collections.singletonList(newSegment)); - assertEquals(segmentPruner.prune(brokerRequest4, Collections.singleton(newSegment)), - Collections.singletonList(newSegment)); - assertEquals(segmentPruner.prune(brokerRequest5, Collections.singleton(newSegment)), - Collections.singletonList(newSegment)); - assertEquals(segmentPruner.prune(brokerRequest6, Collections.singleton(newSegment)), - Collections.singletonList(newSegment)); - assertEquals(segmentPruner.prune(brokerRequest7, Collections.singleton(newSegment)), - Collections.emptySet()); // query with invalid range will always have empty filtered result + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + input = Set.of(newSegment); + assertEquals(segmentPruner.prune(brokerRequest1, input), input); + assertEquals(segmentPruner.prune(brokerRequest2, input), input); + assertEquals(segmentPruner.prune(brokerRequest3, input), input); + assertEquals(segmentPruner.prune(brokerRequest4, input), input); + assertEquals(segmentPruner.prune(brokerRequest5, input), input); + assertEquals(segmentPruner.prune(brokerRequest6, input), input); + assertEquals(segmentPruner.prune(brokerRequest7, input), input); + assertEquals(segmentPruner.prune(brokerRequest8, input), input); + assertEquals(segmentPruner.prune(brokerRequest9, input), Set.of()); // Query with invalid range // Segments without time range metadata should not be pruned String segmentWithoutTimeRangeMetadata = "segmentWithoutTimeRangeMetadata"; - onlineSegments.add(segmentWithoutTimeRangeMetadata); SegmentZKMetadata segmentZKMetadataWithoutTimeRangeMetadata = new SegmentZKMetadata(segmentWithoutTimeRangeMetadata); - segmentZKMetadataWithoutTimeRangeMetadata.setStatus(CommonConstants.Segment.Realtime.Status.DONE); ZKMetadataProvider.setSegmentZKMetadata(_propertyStore, REALTIME_TABLE_NAME, segmentZKMetadataWithoutTimeRangeMetadata); - segmentPruner.onAssignmentChange(idealState, externalView, onlineSegments); - assertEquals( - segmentPruner.prune(brokerRequest1, new HashSet<>(Collections.singletonList(segmentWithoutTimeRangeMetadata))), - Collections.singletonList(segmentWithoutTimeRangeMetadata)); - assertEquals( - segmentPruner.prune(brokerRequest2, new HashSet<>(Collections.singletonList(segmentWithoutTimeRangeMetadata))), - Collections.singletonList(segmentWithoutTimeRangeMetadata)); - assertEquals( - segmentPruner.prune(brokerRequest3, new HashSet<>(Collections.singletonList(segmentWithoutTimeRangeMetadata))), - Collections.singletonList(segmentWithoutTimeRangeMetadata)); - assertEquals( - segmentPruner.prune(brokerRequest4, new HashSet<>(Collections.singletonList(segmentWithoutTimeRangeMetadata))), - Collections.singletonList(segmentWithoutTimeRangeMetadata)); - assertEquals( - segmentPruner.prune(brokerRequest5, new HashSet<>(Collections.singletonList(segmentWithoutTimeRangeMetadata))), - Collections.singletonList(segmentWithoutTimeRangeMetadata)); - assertEquals( - segmentPruner.prune(brokerRequest6, new HashSet<>(Collections.singletonList(segmentWithoutTimeRangeMetadata))), - Collections.singletonList(segmentWithoutTimeRangeMetadata)); - assertEquals( - segmentPruner.prune(brokerRequest7, new HashSet<>(Collections.singletonList(segmentWithoutTimeRangeMetadata))), - Collections.emptySet()); + onlineSegments.add(segmentWithoutTimeRangeMetadata); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + assertEquals(segmentPruner.prune(brokerRequest1, input), input); + assertEquals(segmentPruner.prune(brokerRequest2, input), input); + assertEquals(segmentPruner.prune(brokerRequest3, input), input); + assertEquals(segmentPruner.prune(brokerRequest4, input), input); + assertEquals(segmentPruner.prune(brokerRequest5, input), input); + assertEquals(segmentPruner.prune(brokerRequest6, input), input); + assertEquals(segmentPruner.prune(brokerRequest7, input), input); + assertEquals(segmentPruner.prune(brokerRequest8, input), input); + assertEquals(segmentPruner.prune(brokerRequest9, input), Set.of()); // Query with invalid range // Test different time range String segment0 = "segment0"; - onlineSegments.add(segment0); setSegmentZKTimeRangeMetadata(REALTIME_TABLE_NAME, segment0, 10, 60, TimeUnit.DAYS); - + onlineSegments.add(segment0); String segment1 = "segment1"; - onlineSegments.add(segment1); setSegmentZKTimeRangeMetadata(REALTIME_TABLE_NAME, segment1, 20, 30, TimeUnit.DAYS); - + onlineSegments.add(segment1); String segment2 = "segment2"; - onlineSegments.add(segment2); setSegmentZKTimeRangeMetadata(REALTIME_TABLE_NAME, segment2, 50, 65, TimeUnit.DAYS); - - segmentPruner.onAssignmentChange(idealState, externalView, onlineSegments); - assertEquals(segmentPruner.prune(brokerRequest1, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - new HashSet<>(Arrays.asList(segment0, segment1, segment2))); - assertEquals(segmentPruner.prune(brokerRequest2, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - Collections.singleton(segment0)); - assertEquals(segmentPruner.prune(brokerRequest3, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - new HashSet<>(Arrays.asList(segment0, segment1))); - assertEquals(segmentPruner.prune(brokerRequest4, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - new HashSet<>(Arrays.asList(segment0, segment2))); - assertEquals(segmentPruner.prune(brokerRequest5, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - new HashSet<>(Arrays.asList(segment0, segment2))); - assertEquals(segmentPruner.prune(brokerRequest6, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - new HashSet<>(Arrays.asList(segment0, segment2))); - assertEquals(segmentPruner.prune(brokerRequest7, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - Collections.emptySet()); + onlineSegments.add(segment2); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + input = Set.of(segment0, segment1, segment2); + assertEquals(segmentPruner.prune(brokerRequest1, input), input); + assertEquals(segmentPruner.prune(brokerRequest2, input), Set.of(segment0)); + assertEquals(segmentPruner.prune(brokerRequest3, input), Set.of(segment0, segment1)); + assertEquals(segmentPruner.prune(brokerRequest4, input), Set.of(segment0, segment2)); + assertEquals(segmentPruner.prune(brokerRequest5, input), Set.of(segment0, segment2)); + assertEquals(segmentPruner.prune(brokerRequest6, input), Set.of(segment0, segment2)); + assertEquals(segmentPruner.prune(brokerRequest7, input), Set.of(segment0, segment2)); + assertEquals(segmentPruner.prune(brokerRequest8, input), Set.of(segment0, segment1)); + assertEquals(segmentPruner.prune(brokerRequest9, input), Set.of()); // Query with invalid range // Update metadata without external view change or refreshing should have no effect setSegmentZKTimeRangeMetadata(REALTIME_TABLE_NAME, segment2, 20, 30, TimeUnit.DAYS); - assertEquals(segmentPruner.prune(brokerRequest1, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - new HashSet<>(Arrays.asList(segment0, segment1, segment2))); - assertEquals(segmentPruner.prune(brokerRequest2, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - Collections.singleton(segment0)); - assertEquals(segmentPruner.prune(brokerRequest3, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - new HashSet<>(Arrays.asList(segment0, segment1))); - assertEquals(segmentPruner.prune(brokerRequest4, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - new HashSet<>(Arrays.asList(segment0, segment2))); - assertEquals(segmentPruner.prune(brokerRequest5, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - new HashSet<>(Arrays.asList(segment0, segment2))); - assertEquals(segmentPruner.prune(brokerRequest6, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - new HashSet<>(Arrays.asList(segment0, segment2))); - assertEquals(segmentPruner.prune(brokerRequest7, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - Collections.emptySet()); + assertEquals(segmentPruner.prune(brokerRequest1, input), input); + assertEquals(segmentPruner.prune(brokerRequest2, input), Set.of(segment0)); + assertEquals(segmentPruner.prune(brokerRequest3, input), Set.of(segment0, segment1)); + assertEquals(segmentPruner.prune(brokerRequest4, input), Set.of(segment0, segment2)); + assertEquals(segmentPruner.prune(brokerRequest5, input), Set.of(segment0, segment2)); + assertEquals(segmentPruner.prune(brokerRequest6, input), Set.of(segment0, segment2)); + assertEquals(segmentPruner.prune(brokerRequest7, input), Set.of(segment0, segment2)); + assertEquals(segmentPruner.prune(brokerRequest8, input), Set.of(segment0, segment1)); + assertEquals(segmentPruner.prune(brokerRequest9, input), Set.of()); // Query with invalid range // Refresh the changed segment should update the segment pruner - segmentPruner.refreshSegment(segment2); - assertEquals(segmentPruner.prune(brokerRequest1, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - new HashSet<>(Arrays.asList(segment0, segment1, segment2))); - assertEquals(segmentPruner.prune(brokerRequest2, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - Collections.singleton(segment0)); - assertEquals(segmentPruner.prune(brokerRequest3, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - new HashSet<>(Arrays.asList(segment0, segment1, segment2))); - assertEquals(segmentPruner.prune(brokerRequest4, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - Collections.singleton(segment0)); - assertEquals(segmentPruner.prune(brokerRequest5, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - Collections.singleton(segment0)); - assertEquals(segmentPruner.prune(brokerRequest6, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - Collections.singleton(segment0)); - assertEquals(segmentPruner.prune(brokerRequest7, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - Collections.emptySet()); + segmentZkMetadataFetcher.refreshSegment(segment2); + assertEquals(segmentPruner.prune(brokerRequest1, input), input); + assertEquals(segmentPruner.prune(brokerRequest2, input), Set.of(segment0)); + assertEquals(segmentPruner.prune(brokerRequest3, input), input); + assertEquals(segmentPruner.prune(brokerRequest4, input), Set.of(segment0)); + assertEquals(segmentPruner.prune(brokerRequest5, input), Set.of(segment0)); + assertEquals(segmentPruner.prune(brokerRequest6, input), Set.of(segment0)); + assertEquals(segmentPruner.prune(brokerRequest7, input), Set.of(segment0)); + assertEquals(segmentPruner.prune(brokerRequest8, input), input); + assertEquals(segmentPruner.prune(brokerRequest9, input), Set.of()); // Query with invalid range } @Test @@ -534,204 +508,175 @@ public void testTimeSegmentPrunerSimpleDateFormat() { BrokerRequest brokerRequest3 = CalciteSqlCompiler.compileToBrokerRequest(SDF_QUERY_3); BrokerRequest brokerRequest4 = CalciteSqlCompiler.compileToBrokerRequest(SDF_QUERY_4); BrokerRequest brokerRequest5 = CalciteSqlCompiler.compileToBrokerRequest(SDF_QUERY_5); + // NOTE: Ideal state and external view are not used in the current implementation IdealState idealState = Mockito.mock(IdealState.class); ExternalView externalView = Mockito.mock(ExternalView.class); - TableConfig tableConfig = getTableConfig(RAW_TABLE_NAME, TableType.REALTIME); - setSchemaDateTimeFieldSpecSDF(RAW_TABLE_NAME, SDF_PATTERN); - - TimeSegmentPruner segmentPruner = new TimeSegmentPruner(tableConfig, _propertyStore); - Schema schema = ZKMetadataProvider.getTableSchema(_propertyStore, RAW_TABLE_NAME); - DateTimeFormatSpec dateTimeFormatSpec = schema.getSpecForTimeColumn(TIME_COLUMN).getFormatSpec(); - + TableConfig tableConfig = + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setTimeColumnName(TIME_COLUMN).build(); + DateTimeFieldSpec timeFieldSpec = + new DateTimeFieldSpec(TIME_COLUMN, DataType.STRING, "SIMPLE_DATE_FORMAT|" + SDF_PATTERN, "1:DAYS"); + TimeSegmentPruner segmentPruner = new TimeSegmentPruner(tableConfig, timeFieldSpec); + SegmentZkMetadataFetcher segmentZkMetadataFetcher = + new SegmentZkMetadataFetcher(REALTIME_TABLE_NAME, _propertyStore); + segmentZkMetadataFetcher.register(segmentPruner); + DateTimeFormatSpec timeFormatSpec = timeFieldSpec.getFormatSpec(); Set onlineSegments = new HashSet<>(); String segment0 = "segment0"; + setSegmentZKTimeRangeMetadata(REALTIME_TABLE_NAME, segment0, timeFormatSpec.fromFormatToMillis("20200101"), + timeFormatSpec.fromFormatToMillis("20200228"), TimeUnit.MILLISECONDS); onlineSegments.add(segment0); - setSegmentZKTimeRangeMetadata(REALTIME_TABLE_NAME, segment0, dateTimeFormatSpec.fromFormatToMillis("20200101"), - dateTimeFormatSpec.fromFormatToMillis("20200228"), TimeUnit.MILLISECONDS); - String segment1 = "segment1"; + setSegmentZKTimeRangeMetadata(REALTIME_TABLE_NAME, segment1, timeFormatSpec.fromFormatToMillis("20200201"), + timeFormatSpec.fromFormatToMillis("20200530"), TimeUnit.MILLISECONDS); onlineSegments.add(segment1); - setSegmentZKTimeRangeMetadata(REALTIME_TABLE_NAME, segment1, dateTimeFormatSpec.fromFormatToMillis("20200201"), - dateTimeFormatSpec.fromFormatToMillis("20200530"), TimeUnit.MILLISECONDS); - String segment2 = "segment2"; + setSegmentZKTimeRangeMetadata(REALTIME_TABLE_NAME, segment2, timeFormatSpec.fromFormatToMillis("20200401"), + timeFormatSpec.fromFormatToMillis("20200430"), TimeUnit.MILLISECONDS); onlineSegments.add(segment2); - setSegmentZKTimeRangeMetadata(REALTIME_TABLE_NAME, segment2, dateTimeFormatSpec.fromFormatToMillis("20200401"), - dateTimeFormatSpec.fromFormatToMillis("20200430"), TimeUnit.MILLISECONDS); - - segmentPruner.init(idealState, externalView, onlineSegments); - assertEquals(segmentPruner.prune(brokerRequest1, onlineSegments), Collections.singleton(segment0)); - assertEquals(segmentPruner.prune(brokerRequest2, onlineSegments), new HashSet<>(Arrays.asList(segment0, segment1))); - assertEquals(segmentPruner.prune(brokerRequest3, onlineSegments), Collections.singleton(segment1)); - assertEquals(segmentPruner.prune(brokerRequest4, onlineSegments), - new HashSet<>(Arrays.asList(segment0, segment1, segment2))); - assertEquals(segmentPruner.prune(brokerRequest5, onlineSegments), Collections.emptySet()); + segmentZkMetadataFetcher.init(idealState, externalView, onlineSegments); + + Set input = Set.of(segment0, segment1, segment2); + assertEquals(segmentPruner.prune(brokerRequest1, input), Set.of(segment0)); + assertEquals(segmentPruner.prune(brokerRequest2, input), Set.of(segment0, segment1)); + assertEquals(segmentPruner.prune(brokerRequest3, input), Set.of(segment1)); + assertEquals(segmentPruner.prune(brokerRequest4, input), input); + assertEquals(segmentPruner.prune(brokerRequest5, input), Set.of()); } @Test - public void testTimeSegmentPrunerSql() { - BrokerRequest brokerRequest1 = CalciteSqlCompiler.compileToBrokerRequest(SQL_TIME_QUERY_1); - BrokerRequest brokerRequest2 = CalciteSqlCompiler.compileToBrokerRequest(SQL_TIME_QUERY_2); + public void testTimeSegmentPrunerTimestampFormat() { + BrokerRequest brokerRequest1 = CalciteSqlCompiler.compileToBrokerRequest(TIMESTAMP_QUERY_1); + BrokerRequest brokerRequest2 = CalciteSqlCompiler.compileToBrokerRequest(TIMESTAMP_QUERY_2); + BrokerRequest brokerRequest3 = CalciteSqlCompiler.compileToBrokerRequest(TIMESTAMP_QUERY_3); + BrokerRequest brokerRequest4 = CalciteSqlCompiler.compileToBrokerRequest(TIMESTAMP_QUERY_4); + BrokerRequest brokerRequest5 = CalciteSqlCompiler.compileToBrokerRequest(TIMESTAMP_QUERY_5); + BrokerRequest brokerRequest6 = CalciteSqlCompiler.compileToBrokerRequest(TIMESTAMP_QUERY_6); + // NOTE: Ideal state and external view are not used in the current implementation IdealState idealState = Mockito.mock(IdealState.class); ExternalView externalView = Mockito.mock(ExternalView.class); - TableConfig tableConfig = getTableConfig(RAW_TABLE_NAME, TableType.REALTIME); - setSchemaDateTimeFieldSpec(RAW_TABLE_NAME, TimeUnit.DAYS); - - TimeSegmentPruner segmentPruner = new TimeSegmentPruner(tableConfig, _propertyStore); + TableConfig tableConfig = + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setTimeColumnName(TIME_COLUMN).build(); + // Intentionally put EPOCH as the format which Pinot should handle + DateTimeFieldSpec timeFieldSpec = + new DateTimeFieldSpec(TIME_COLUMN, DataType.TIMESTAMP, "EPOCH|MILLISECONDS", "1:DAYS"); + TimeSegmentPruner segmentPruner = new TimeSegmentPruner(tableConfig, timeFieldSpec); + SegmentZkMetadataFetcher segmentZkMetadataFetcher = + new SegmentZkMetadataFetcher(REALTIME_TABLE_NAME, _propertyStore); + segmentZkMetadataFetcher.register(segmentPruner); + DateTimeFormatSpec timeFormatSpec = timeFieldSpec.getFormatSpec(); Set onlineSegments = new HashSet<>(); String segment0 = "segment0"; + setSegmentZKTimeRangeMetadata(REALTIME_TABLE_NAME, segment0, + timeFormatSpec.fromFormatToMillis("2020-01-01 00:00:00"), + timeFormatSpec.fromFormatToMillis("2020-02-28 00:00:00"), TimeUnit.MILLISECONDS); onlineSegments.add(segment0); - setSegmentZKTimeRangeMetadata(REALTIME_TABLE_NAME, segment0, 10, 60, TimeUnit.DAYS); String segment1 = "segment1"; + setSegmentZKTimeRangeMetadata(REALTIME_TABLE_NAME, segment1, + timeFormatSpec.fromFormatToMillis("2020-02-01 00:00:00"), + timeFormatSpec.fromFormatToMillis("2020-05-30 00:00:00"), TimeUnit.MILLISECONDS); onlineSegments.add(segment1); - setSegmentZKTimeRangeMetadata(REALTIME_TABLE_NAME, segment1, 20, 30, TimeUnit.DAYS); String segment2 = "segment2"; + setSegmentZKTimeRangeMetadata(REALTIME_TABLE_NAME, segment2, + timeFormatSpec.fromFormatToMillis("2020-04-01 00:00:00"), + timeFormatSpec.fromFormatToMillis("2020-04-30 00:00:00"), TimeUnit.MILLISECONDS); onlineSegments.add(segment2); - setSegmentZKTimeRangeMetadata(REALTIME_TABLE_NAME, segment2, 50, 65, TimeUnit.DAYS); - segmentPruner.init(idealState, externalView, onlineSegments); - - assertEquals(segmentPruner.prune(brokerRequest1, onlineSegments), new HashSet<>(Arrays.asList(segment0, segment2))); - assertEquals(segmentPruner.prune(brokerRequest2, onlineSegments), new HashSet<>(Arrays.asList(segment0, segment1))); + segmentZkMetadataFetcher.init(idealState, externalView, onlineSegments); + + Set input = Set.of(segment0, segment1, segment2); + assertEquals(segmentPruner.prune(brokerRequest1, input), Set.of(segment0)); + assertEquals(segmentPruner.prune(brokerRequest2, input), Set.of(segment0)); + assertEquals(segmentPruner.prune(brokerRequest3, input), Set.of(segment0, segment1)); + assertEquals(segmentPruner.prune(brokerRequest4, input), Set.of(segment0, segment1)); + assertEquals(segmentPruner.prune(brokerRequest5, input), input); + assertEquals(segmentPruner.prune(brokerRequest6, input), input); } @Test public void testEmptySegmentPruner() { BrokerRequest brokerRequest1 = CalciteSqlCompiler.compileToBrokerRequest(QUERY_1); - BrokerRequest brokerRequest2 = CalciteSqlCompiler.compileToBrokerRequest(QUERY_2); - BrokerRequest brokerRequest3 = CalciteSqlCompiler.compileToBrokerRequest(QUERY_3); + // NOTE: Ideal state and external view are not used in the current implementation IdealState idealState = Mockito.mock(IdealState.class); ExternalView externalView = Mockito.mock(ExternalView.class); - TableConfig tableConfig = getTableConfig(RAW_TABLE_NAME, TableType.REALTIME); + TableConfig tableConfig = new TableConfigBuilder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).build(); - // init with list of segments - EmptySegmentPruner segmentPruner = new EmptySegmentPruner(tableConfig, _propertyStore); + // Init with a list of segments + EmptySegmentPruner segmentPruner = new EmptySegmentPruner(tableConfig); + SegmentZkMetadataFetcher segmentZkMetadataFetcher = + new SegmentZkMetadataFetcher(REALTIME_TABLE_NAME, _propertyStore); + segmentZkMetadataFetcher.register(segmentPruner); Set onlineSegments = new HashSet<>(); String segment0 = "segment0"; - onlineSegments.add(segment0); setSegmentZKTotalDocsMetadata(REALTIME_TABLE_NAME, segment0, 10); + onlineSegments.add(segment0); String segment1 = "segment1"; - onlineSegments.add(segment1); setSegmentZKTotalDocsMetadata(REALTIME_TABLE_NAME, segment1, 0); - segmentPruner.init(idealState, externalView, onlineSegments); - assertEquals(segmentPruner.prune(brokerRequest1, new HashSet<>(Arrays.asList(segment0, segment1))), - new HashSet<>(Collections.singletonList(segment0))); - assertEquals(segmentPruner.prune(brokerRequest2, new HashSet<>(Arrays.asList(segment0, segment1))), - new HashSet<>(Collections.singletonList(segment0))); - assertEquals(segmentPruner.prune(brokerRequest3, new HashSet<>(Arrays.asList(segment0, segment1))), - new HashSet<>(Collections.singletonList(segment0))); - - // init with empty list of segments - segmentPruner = new EmptySegmentPruner(tableConfig, _propertyStore); + onlineSegments.add(segment1); + segmentZkMetadataFetcher.init(idealState, externalView, onlineSegments); + assertEquals(segmentPruner.prune(brokerRequest1, onlineSegments), Set.of(segment0)); + + // Init with no segment + segmentPruner = new EmptySegmentPruner(tableConfig); + segmentZkMetadataFetcher = new SegmentZkMetadataFetcher(REALTIME_TABLE_NAME, _propertyStore); + segmentZkMetadataFetcher.register(segmentPruner); onlineSegments.clear(); - segmentPruner.init(idealState, externalView, onlineSegments); - assertEquals(segmentPruner.prune(brokerRequest1, Collections.emptySet()), Collections.emptySet()); - assertEquals(segmentPruner.prune(brokerRequest2, Collections.emptySet()), Collections.emptySet()); - assertEquals(segmentPruner.prune(brokerRequest3, Collections.emptySet()), Collections.emptySet()); + segmentZkMetadataFetcher.init(idealState, externalView, onlineSegments); + assertEquals(segmentPruner.prune(brokerRequest1, onlineSegments), onlineSegments); // Segments without metadata (not updated yet) should not be pruned String newSegment = "newSegment"; onlineSegments.add(newSegment); - segmentPruner.onAssignmentChange(idealState, externalView, onlineSegments); - assertEquals(segmentPruner.prune(brokerRequest1, Collections.singleton(newSegment)), - Collections.singleton(newSegment)); - assertEquals(segmentPruner.prune(brokerRequest2, Collections.singleton(newSegment)), - Collections.singleton(newSegment)); - assertEquals(segmentPruner.prune(brokerRequest3, Collections.singleton(newSegment)), - Collections.singleton(newSegment)); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + assertEquals(segmentPruner.prune(brokerRequest1, onlineSegments), onlineSegments); // Segments without totalDocs metadata should not be pruned - onlineSegments.clear(); String segmentWithoutTotalDocsMetadata = "segmentWithoutTotalDocsMetadata"; - onlineSegments.add(segmentWithoutTotalDocsMetadata); SegmentZKMetadata segmentZKMetadataWithoutTotalDocsMetadata = new SegmentZKMetadata(segmentWithoutTotalDocsMetadata); - segmentZKMetadataWithoutTotalDocsMetadata.setStatus(CommonConstants.Segment.Realtime.Status.IN_PROGRESS); ZKMetadataProvider.setSegmentZKMetadata(_propertyStore, REALTIME_TABLE_NAME, segmentZKMetadataWithoutTotalDocsMetadata); - segmentPruner.onAssignmentChange(idealState, externalView, onlineSegments); - assertEquals(segmentPruner.prune(brokerRequest1, Collections.singleton(segmentWithoutTotalDocsMetadata)), - Collections.singleton(segmentWithoutTotalDocsMetadata)); - assertEquals(segmentPruner.prune(brokerRequest2, Collections.singleton(segmentWithoutTotalDocsMetadata)), - Collections.singleton(segmentWithoutTotalDocsMetadata)); - assertEquals(segmentPruner.prune(brokerRequest3, Collections.singleton(segmentWithoutTotalDocsMetadata)), - Collections.singleton(segmentWithoutTotalDocsMetadata)); + onlineSegments.add(segmentWithoutTotalDocsMetadata); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + assertEquals(segmentPruner.prune(brokerRequest1, onlineSegments), onlineSegments); // Segments with -1 totalDocs should not be pruned - onlineSegments.clear(); String segmentWithNegativeTotalDocsMetadata = "segmentWithNegativeTotalDocsMetadata"; - onlineSegments.add(segmentWithNegativeTotalDocsMetadata); setSegmentZKTotalDocsMetadata(REALTIME_TABLE_NAME, segmentWithNegativeTotalDocsMetadata, -1); - segmentPruner.onAssignmentChange(idealState, externalView, onlineSegments); - assertEquals(segmentPruner.prune(brokerRequest1, Collections.singleton(segmentWithNegativeTotalDocsMetadata)), - Collections.singleton(segmentWithNegativeTotalDocsMetadata)); - assertEquals(segmentPruner.prune(brokerRequest2, Collections.singleton(segmentWithNegativeTotalDocsMetadata)), - Collections.singleton(segmentWithNegativeTotalDocsMetadata)); - assertEquals(segmentPruner.prune(brokerRequest3, Collections.singleton(segmentWithNegativeTotalDocsMetadata)), - Collections.singleton(segmentWithNegativeTotalDocsMetadata)); + onlineSegments.add(segmentWithNegativeTotalDocsMetadata); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + assertEquals(segmentPruner.prune(brokerRequest1, onlineSegments), onlineSegments); // Prune segments with 0 total docs onlineSegments.clear(); - onlineSegments.add(segment0); setSegmentZKTotalDocsMetadata(REALTIME_TABLE_NAME, segment0, 10); - onlineSegments.add(segment1); + onlineSegments.add(segment0); setSegmentZKTotalDocsMetadata(REALTIME_TABLE_NAME, segment1, 0); + onlineSegments.add(segment1); String segment2 = "segment2"; - onlineSegments.add(segment2); setSegmentZKTotalDocsMetadata(REALTIME_TABLE_NAME, segment2, -1); - - segmentPruner.onAssignmentChange(idealState, externalView, onlineSegments); - assertEquals(segmentPruner.prune(brokerRequest1, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - new HashSet<>(Arrays.asList(segment0, segment2))); - assertEquals(segmentPruner.prune(brokerRequest2, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - new HashSet<>(Arrays.asList(segment0, segment2))); - assertEquals(segmentPruner.prune(brokerRequest3, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - new HashSet<>(Arrays.asList(segment0, segment2))); + onlineSegments.add(segment2); + segmentZkMetadataFetcher.onAssignmentChange(idealState, externalView, onlineSegments); + assertEquals(segmentPruner.prune(brokerRequest1, onlineSegments), Set.of(segment0, segment2)); // Update metadata without external view change or refreshing should have no effect - setSegmentZKTimeRangeMetadata(REALTIME_TABLE_NAME, segment2, 20, 30, TimeUnit.DAYS); setSegmentZKTotalDocsMetadata(REALTIME_TABLE_NAME, segment2, 0); - assertEquals(segmentPruner.prune(brokerRequest1, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - new HashSet<>(Arrays.asList(segment0, segment2))); - assertEquals(segmentPruner.prune(brokerRequest2, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - new HashSet<>(Arrays.asList(segment0, segment2))); - assertEquals(segmentPruner.prune(brokerRequest3, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - new HashSet<>(Arrays.asList(segment0, segment2))); + assertEquals(segmentPruner.prune(brokerRequest1, onlineSegments), Set.of(segment0, segment2)); // Refresh the changed segment should update the segment pruner - segmentPruner.refreshSegment(segment2); - assertEquals(segmentPruner.prune(brokerRequest1, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - new HashSet<>(Collections.singletonList(segment0))); - assertEquals(segmentPruner.prune(brokerRequest2, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - new HashSet<>(Collections.singletonList(segment0))); - assertEquals(segmentPruner.prune(brokerRequest3, new HashSet<>(Arrays.asList(segment0, segment1, segment2))), - new HashSet<>(Collections.singletonList(segment0))); - } - - private TableConfig getTableConfig(String rawTableName, TableType type) { - return new TableConfigBuilder(type).setTableName(rawTableName).setTimeColumnName(TIME_COLUMN).build(); - } - - private void setSchemaDateTimeFieldSpec(String rawTableName, TimeUnit timeUnit) { - ZKMetadataProvider.setSchema(_propertyStore, new Schema.SchemaBuilder().setSchemaName(rawTableName) - .addDateTime(TIME_COLUMN, FieldSpec.DataType.LONG, "1:" + timeUnit + ":EPOCH", "1:" + timeUnit).build()); - } - - private void setSchemaDateTimeFieldSpecSDF(String rawTableName, String format) { - ZKMetadataProvider.setSchema(_propertyStore, new Schema.SchemaBuilder().setSchemaName(rawTableName) - .addDateTime(TIME_COLUMN, FieldSpec.DataType.STRING, "1:DAYS:SIMPLE_DATE_FORMAT:" + format, "1:DAYS").build()); + segmentZkMetadataFetcher.refreshSegment(segment2); + assertEquals(segmentPruner.prune(brokerRequest1, onlineSegments), Set.of(segment0)); } private void setSegmentZKPartitionMetadata(String tableNameWithType, String segment, String partitionFunction, int numPartitions, int partitionId) { SegmentZKMetadata segmentZKMetadata = new SegmentZKMetadata(segment); - segmentZKMetadata.setPartitionMetadata(new SegmentPartitionMetadata(Collections.singletonMap(PARTITION_COLUMN_1, - new ColumnPartitionMetadata(partitionFunction, numPartitions, Collections.singleton(partitionId), null)))); + segmentZKMetadata.setPartitionMetadata(new SegmentPartitionMetadata(Map.of(PARTITION_COLUMN_1, + new ColumnPartitionMetadata(partitionFunction, numPartitions, Set.of(partitionId), null)))); ZKMetadataProvider.setSegmentZKMetadata(_propertyStore, tableNameWithType, segmentZKMetadata); } diff --git a/pinot-broker/src/test/java/org/apache/pinot/broker/routing/segmentselector/SegmentSelectorTest.java b/pinot-broker/src/test/java/org/apache/pinot/broker/routing/segmentselector/SegmentSelectorTest.java deleted file mode 100644 index e3ec929d5912..000000000000 --- a/pinot-broker/src/test/java/org/apache/pinot/broker/routing/segmentselector/SegmentSelectorTest.java +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.broker.routing.segmentselector; - -import java.util.Collections; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import org.apache.helix.model.ExternalView; -import org.apache.helix.model.IdealState; -import org.apache.pinot.common.request.BrokerRequest; -import org.apache.pinot.common.request.PinotQuery; -import org.apache.pinot.common.utils.HLCSegmentName; -import org.apache.pinot.common.utils.LLCSegmentName; -import org.apache.pinot.spi.config.table.TableConfig; -import org.apache.pinot.spi.config.table.TableType; -import org.apache.pinot.spi.utils.CommonConstants.Broker.Request; -import org.testng.annotations.Test; - -import static org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel.CONSUMING; -import static org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel.ONLINE; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.testng.Assert.assertEqualsNoOrder; -import static org.testng.Assert.assertTrue; - - -public class SegmentSelectorTest { - - @Test - public void testSegmentSelectorFactory() { - TableConfig tableConfig = mock(TableConfig.class); - - when(tableConfig.getTableType()).thenReturn(TableType.OFFLINE); - assertTrue(SegmentSelectorFactory.getSegmentSelector(tableConfig) instanceof OfflineSegmentSelector); - - when(tableConfig.getTableType()).thenReturn(TableType.REALTIME); - assertTrue(SegmentSelectorFactory.getSegmentSelector(tableConfig) instanceof RealtimeSegmentSelector); - } - - @Test - public void testRealtimeSegmentSelector() { - String realtimeTableName = "testTable_REALTIME"; - ExternalView externalView = new ExternalView(realtimeTableName); - Map> segmentAssignment = externalView.getRecord().getMapFields(); - Map onlineInstanceStateMap = Collections.singletonMap("server", ONLINE); - Map consumingInstanceStateMap = Collections.singletonMap("server", CONSUMING); - Set onlineSegments = new HashSet<>(); - // NOTE: Ideal state is not used in the current implementation - IdealState idealState = mock(IdealState.class); - - // Should return an empty list when there is no segment - RealtimeSegmentSelector segmentSelector = new RealtimeSegmentSelector(); - segmentSelector.init(idealState, externalView, onlineSegments); - BrokerRequest brokerRequest = mock(BrokerRequest.class); - PinotQuery pinotQuery = mock(PinotQuery.class); - when(brokerRequest.getPinotQuery()).thenReturn(pinotQuery); - assertTrue(segmentSelector.select(brokerRequest).isEmpty()); - - // For HLC segments, only one group of segments should be selected - int numHLCGroups = 3; - int numHLCSegmentsPerGroup = 5; - String[][] hlcSegments = new String[numHLCGroups][]; - for (int i = 0; i < numHLCGroups; i++) { - String groupId = "testTable_REALTIME_" + i; - String[] hlcSegmentsForGroup = new String[numHLCSegmentsPerGroup]; - for (int j = 0; j < numHLCSegmentsPerGroup; j++) { - String hlcSegment = new HLCSegmentName(groupId, "0", Integer.toString(j)).getSegmentName(); - segmentAssignment.put(hlcSegment, onlineInstanceStateMap); - onlineSegments.add(hlcSegment); - hlcSegmentsForGroup[j] = hlcSegment; - } - hlcSegments[i] = hlcSegmentsForGroup; - } - segmentSelector.onAssignmentChange(idealState, externalView, onlineSegments); - - // Only HLC segments exist, should select the HLC segments from the first group - assertEqualsNoOrder(segmentSelector.select(brokerRequest).toArray(), hlcSegments[0]); - - // For LLC segments, only the first CONSUMING segment for each partition should be selected - int numLLCPartitions = 3; - int numLLCSegmentsPerPartition = 5; - int numOnlineLLCSegmentsPerPartition = 3; - String[] expectedSelectedLLCSegments = new String[numLLCPartitions * (numLLCSegmentsPerPartition - 1)]; - for (int i = 0; i < numLLCPartitions; i++) { - for (int j = 0; j < numLLCSegmentsPerPartition; j++) { - String llcSegment = new LLCSegmentName(realtimeTableName, i, j, 0).getSegmentName(); - if (j < numOnlineLLCSegmentsPerPartition) { - externalView.setStateMap(llcSegment, onlineInstanceStateMap); - } else { - externalView.setStateMap(llcSegment, consumingInstanceStateMap); - } - onlineSegments.add(llcSegment); - if (j < numLLCSegmentsPerPartition - 1) { - expectedSelectedLLCSegments[i * (numLLCSegmentsPerPartition - 1) + j] = llcSegment; - } - } - } - segmentSelector.onAssignmentChange(idealState, externalView, onlineSegments); - - // Both HLC and LLC segments exist, should select the LLC segments - assertEqualsNoOrder(segmentSelector.select(brokerRequest).toArray(), expectedSelectedLLCSegments); - - // When HLC is forced, should select the HLC segments from the second group - when(pinotQuery.getQueryOptions()).thenReturn( - Collections.singletonMap(Request.QueryOptionKey.ROUTING_OPTIONS, Request.QueryOptionValue.ROUTING_FORCE_HLC)); - assertEqualsNoOrder(segmentSelector.select(brokerRequest).toArray(), hlcSegments[1]); - - // Remove all the HLC segments from ideal state, should select the LLC segments even when HLC is forced - for (String[] hlcSegmentsForGroup : hlcSegments) { - for (String hlcSegment : hlcSegmentsForGroup) { - onlineSegments.remove(hlcSegment); - } - } - segmentSelector.onAssignmentChange(idealState, externalView, onlineSegments); - assertEqualsNoOrder(segmentSelector.select(brokerRequest).toArray(), expectedSelectedLLCSegments); - } -} diff --git a/pinot-clients/pinot-java-client/pom.xml b/pinot-clients/pinot-java-client/pom.xml index df7f8f3d9d1b..69e7e8223f3c 100644 --- a/pinot-clients/pinot-java-client/pom.xml +++ b/pinot-clients/pinot-java-client/pom.xml @@ -19,13 +19,12 @@ under the License. --> - + 4.0.0 pinot-clients org.apache.pinot - 0.13.0-SNAPSHOT + 1.2.0 .. pinot-java-client @@ -33,22 +32,9 @@ https://pinot.apache.org/ ${basedir}/../.. + package - - - org.apache.maven.plugins - maven-surefire-plugin - - 1 - true - - - - org.apache.maven.plugins - maven-enforcer-plugin - - src/main/resources @@ -74,39 +60,15 @@ org.asynchttpclient async-http-client - - - io.netty - netty - - - io.netty - netty-transport-native-kqueue - - - io.netty - netty-transport-native-unix-common - - com.101tec zkclient - - - io.netty - netty - - org.slf4j slf4j-api - - com.google.code.findbugs - jsr305 - org.mockito mockito-core diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerCache.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerCache.java index 7bc2bc9e6f7d..6742174582dd 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerCache.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerCache.java @@ -22,18 +22,20 @@ import com.fasterxml.jackson.core.type.TypeReference; import io.netty.handler.ssl.ClientAuth; import io.netty.handler.ssl.JdkSslContext; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Properties; import java.util.Random; import java.util.Set; import java.util.concurrent.Future; import java.util.stream.Collectors; import javax.net.ssl.SSLContext; +import org.apache.pinot.client.utils.BrokerSelectorUtils; import org.apache.pinot.client.utils.ConnectionUtils; import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.JsonUtils; @@ -134,8 +136,7 @@ private Map> getTableToBrokersData() throws Excepti Future responseFuture = getRequest.addHeader("accept", "application/json").execute(); Response response = responseFuture.get(); - String responseBody = response.getResponseBody(StandardCharsets.UTF_8); - return JsonUtils.stringToObject(responseBody, RESPONSE_TYPE_REF); + return JsonUtils.inputStreamToObject(response.getResponseBodyAsStream(), RESPONSE_TYPE_REF); } private BrokerData getBrokerData(Map> responses) { @@ -188,9 +189,20 @@ protected void updateBrokerData() _brokerData = getBrokerData(responses); } - public String getBroker(String tableName) { - List brokers = - (tableName == null) ? _brokerData.getBrokers() : _brokerData.getTableToBrokerMap().get(tableName); + public String getBroker(String... tableNames) { + List brokers = null; + // If tableNames is not-null, filter out nulls + tableNames = + tableNames == null ? tableNames : Arrays.stream(tableNames).filter(Objects::nonNull).toArray(String[]::new); + if (!(tableNames == null || tableNames.length == 0)) { + // returning list of common brokers hosting all the tables. + brokers = BrokerSelectorUtils.getTablesCommonBrokers(Arrays.asList(tableNames), + _brokerData.getTableToBrokerMap()); + } + + if (brokers == null || brokers.isEmpty()) { + brokers = _brokerData.getBrokers(); + } return brokers.get(_random.nextInt(brokers.size())); } diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerCacheUpdaterPeriodic.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerCacheUpdaterPeriodic.java index 54d88ba8cd00..c8d8b580496a 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerCacheUpdaterPeriodic.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerCacheUpdaterPeriodic.java @@ -31,17 +31,15 @@ */ public class BrokerCacheUpdaterPeriodic implements UpdatableBrokerCache { public static final String BROKER_UPDATE_FREQUENCY_MILLIS = "brokerUpdateFrequencyInMillis"; - public static final String DEFAULT_BROKER_UPDATE_FREQUENCY_MILLIS = "defaultBrokerUpdateFrequencyInMillis"; + public static final String DEFAULT_BROKER_UPDATE_FREQUENCY_MILLIS = "300000"; // 5 minutes + + private static final Logger LOGGER = LoggerFactory.getLogger(BrokerCacheUpdaterPeriodic.class); private final BrokerCache _brokerCache; private final ScheduledExecutorService _scheduledExecutorService; private final long _brokerUpdateFreqInMillis; - private final Properties _properties; - - private static final Logger LOGGER = LoggerFactory.getLogger(BrokerCacheUpdaterPeriodic.class); public BrokerCacheUpdaterPeriodic(Properties properties, String controllerUrl) { - _properties = properties; _brokerCache = new BrokerCache(properties, controllerUrl); _scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); _brokerUpdateFreqInMillis = Long.parseLong( @@ -65,7 +63,7 @@ public void run() { } } - public String getBroker(String tableName) { + public String getBroker(String... tableName) { return _brokerCache.getBroker(tableName); } diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerResponse.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerResponse.java index 9422061395c7..a2c4d757bc7e 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerResponse.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerResponse.java @@ -24,7 +24,9 @@ /** * Reimplementation of BrokerResponse from pinot-common, so that pinot-api does not depend on pinot-common. */ -class BrokerResponse { +public class BrokerResponse { + private String _requestId; + private String _brokerId; private JsonNode _aggregationResults; private JsonNode _selectionResults; private JsonNode _resultTable; @@ -35,6 +37,8 @@ private BrokerResponse() { } private BrokerResponse(JsonNode brokerResponse) { + _requestId = brokerResponse.get("requestId") != null ? brokerResponse.get("requestId").asText() : "unknown"; + _brokerId = brokerResponse.get("brokerId") != null ? brokerResponse.get("brokerId").asText() : "unknown"; _aggregationResults = brokerResponse.get("aggregationResults"); _exceptions = brokerResponse.get("exceptions"); _selectionResults = brokerResponse.get("selectionResults"); @@ -42,27 +46,27 @@ private BrokerResponse(JsonNode brokerResponse) { _executionStats = ExecutionStats.fromJson(brokerResponse); } - boolean hasExceptions() { + public boolean hasExceptions() { return _exceptions != null && !_exceptions.isEmpty(); } - JsonNode getExceptions() { + public JsonNode getExceptions() { return _exceptions; } - JsonNode getAggregationResults() { + public JsonNode getAggregationResults() { return _aggregationResults; } - JsonNode getSelectionResults() { + public JsonNode getSelectionResults() { return _selectionResults; } - JsonNode getResultTable() { + public JsonNode getResultTable() { return _resultTable; } - int getAggregationResultsSize() { + public int getAggregationResultsSize() { if (_aggregationResults == null) { return 0; } else { @@ -70,15 +74,23 @@ int getAggregationResultsSize() { } } - ExecutionStats getExecutionStats() { + public ExecutionStats getExecutionStats() { return _executionStats; } - static BrokerResponse fromJson(JsonNode json) { + public static BrokerResponse fromJson(JsonNode json) { return new BrokerResponse(json); } - static BrokerResponse empty() { + public static BrokerResponse empty() { return new BrokerResponse(); } + + public String getRequestId() { + return _requestId; + } + + public String getBrokerId() { + return _brokerId; + } } diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerSelector.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerSelector.java index 7da5948ea0b2..6b43da27cd6c 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerSelector.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/BrokerSelector.java @@ -23,10 +23,10 @@ public interface BrokerSelector { /** * Returns the broker address in the form host:port - * @param table + * @param tableNames * @return */ - String selectBroker(String table); + String selectBroker(String... tableNames); /** * Returns list of all brokers. diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/Connection.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/Connection.java index c236446d3d37..99c2121e96a7 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/Connection.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/Connection.java @@ -18,14 +18,14 @@ */ package org.apache.pinot.client; +import java.util.Arrays; import java.util.List; import java.util.Properties; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.CompletableFuture; import javax.annotation.Nullable; -import org.apache.pinot.sql.parsers.CalciteSqlCompiler; +import org.apache.pinot.common.utils.request.RequestUtils; +import org.apache.pinot.sql.parsers.CalciteSqlParser; +import org.apache.pinot.sql.parsers.SqlNodeAndOptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,24 +37,24 @@ public class Connection { public static final String FAIL_ON_EXCEPTIONS = "failOnExceptions"; private static final Logger LOGGER = LoggerFactory.getLogger(Connection.class); - private final PinotClientTransport _transport; + private final PinotClientTransport _transport; private final BrokerSelector _brokerSelector; private final boolean _failOnExceptions; - Connection(List brokerList, PinotClientTransport transport) { + Connection(List brokerList, PinotClientTransport transport) { this(new Properties(), new SimpleBrokerSelector(brokerList), transport); } - Connection(Properties properties, List brokerList, PinotClientTransport transport) { + Connection(Properties properties, List brokerList, PinotClientTransport transport) { this(properties, new SimpleBrokerSelector(brokerList), transport); LOGGER.info("Created connection to broker list {}", brokerList); } - Connection(BrokerSelector brokerSelector, PinotClientTransport transport) { + Connection(BrokerSelector brokerSelector, PinotClientTransport transport) { this(new Properties(), brokerSelector, transport); } - Connection(Properties properties, BrokerSelector brokerSelector, PinotClientTransport transport) { + Connection(Properties properties, BrokerSelector brokerSelector, PinotClientTransport transport) { _brokerSelector = brokerSelector; _transport = transport; @@ -72,17 +72,6 @@ public PreparedStatement prepareStatement(String query) { return new PreparedStatement(this, query); } - /** - * Creates a prepared statement, to escape query parameters. - * - * @param request The request for which to create a prepared statement. - * @return A prepared statement for this connection. - */ - @Deprecated - public PreparedStatement prepareStatement(Request request) { - return new PreparedStatement(this, request); - } - /** * Executes a query. * @@ -94,18 +83,6 @@ public ResultSetGroup execute(String query) { return execute(null, query); } - /** - * Executes a Pinot Request. - * @param request The request to execute - * @return The result of the query - * @throws PinotClientException If an exception occurs while processing the query - */ - @Deprecated - public ResultSetGroup execute(Request request) - throws PinotClientException { - return execute(null, request); - } - /** * Executes a query. * @@ -116,10 +93,10 @@ public ResultSetGroup execute(Request request) */ public ResultSetGroup execute(@Nullable String tableName, String query) throws PinotClientException { - tableName = tableName == null ? resolveTableName(query) : tableName; - String brokerHostPort = _brokerSelector.selectBroker(tableName); + String[] tableNames = (tableName == null) ? resolveTableName(query) : new String[]{tableName}; + String brokerHostPort = _brokerSelector.selectBroker(tableNames); if (brokerHostPort == null) { - throw new PinotClientException("Could not find broker to query for table: " + tableName); + throw new PinotClientException("Could not find broker to query for table(s): " + Arrays.asList(tableNames)); } BrokerResponse response = _transport.executeQuery(brokerHostPort, query); if (response.hasExceptions() && _failOnExceptions) { @@ -128,19 +105,6 @@ public ResultSetGroup execute(@Nullable String tableName, String query) return new ResultSetGroup(response); } - /** - * Executes a Pinot Request. - * - * @param request The request to execute - * @return The result of the query - * @throws PinotClientException If an exception occurs while processing the query - */ - @Deprecated - public ResultSetGroup execute(@Nullable String tableName, Request request) - throws PinotClientException { - return execute(tableName, request.getQuery()); - } - /** * Executes a query asynchronously. * @@ -148,24 +112,11 @@ public ResultSetGroup execute(@Nullable String tableName, Request request) * @return A future containing the result of the query * @throws PinotClientException If an exception occurs while processing the query */ - public Future executeAsync(String query) + public CompletableFuture executeAsync(String query) throws PinotClientException { return executeAsync(null, query); } - /** - * Executes a Pinot Request asynchronously. - * - * @param request The request to execute - * @return A future containing the result of the query - * @throws PinotClientException If an exception occurs while processing the query - */ - @Deprecated - public Future executeAsync(Request request) - throws PinotClientException { - return executeAsync(null, request.getQuery()); - } - /** * Executes a query asynchronously. * @@ -173,24 +124,31 @@ public Future executeAsync(Request request) * @return A future containing the result of the query * @throws PinotClientException If an exception occurs while processing the query */ - public Future executeAsync(@Nullable String tableName, String query) + public CompletableFuture executeAsync(@Nullable String tableName, String query) throws PinotClientException { - tableName = tableName == null ? resolveTableName(query) : tableName; - String brokerHostPort = _brokerSelector.selectBroker(tableName); + String[] tableNames = (tableName == null) ? resolveTableName(query) : new String[]{tableName}; + String brokerHostPort = _brokerSelector.selectBroker(tableNames); if (brokerHostPort == null) { throw new PinotClientException("Could not find broker to query for statement: " + query); } - return new ResultSetGroupFuture(_transport.executeQueryAsync(brokerHostPort, query)); + return _transport.executeQueryAsync(brokerHostPort, query).thenApply(ResultSetGroup::new); } + /** + * Returns the name of all the tables used in a sql query. + * + * @return name of all the tables used in a sql query. + */ @Nullable - private static String resolveTableName(String query) { + private static String[] resolveTableName(String query) { try { - return CalciteSqlCompiler.compileToBrokerRequest(query).querySource.tableName; + SqlNodeAndOptions sqlNodeAndOptions = CalciteSqlParser.compileToSqlNodeAndOptions(query); + return RequestUtils.getTableNames(CalciteSqlParser.compileSqlNodeToPinotQuery(sqlNodeAndOptions.getSqlNode())) + .toArray(new String[0]); } catch (Exception e) { - LOGGER.error("Cannot parse table name from query: {}", query, e); - return null; + LOGGER.error("Cannot parse table name from query: {}. Fallback to broker selector default.", query, e); } + return null; } /** @@ -213,43 +171,13 @@ public void close() _brokerSelector.close(); } - private static class ResultSetGroupFuture implements Future { - private final Future _responseFuture; - - public ResultSetGroupFuture(Future responseFuture) { - _responseFuture = responseFuture; - } - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - return _responseFuture.cancel(mayInterruptIfRunning); - } - - @Override - public boolean isCancelled() { - return _responseFuture.isCancelled(); - } - - @Override - public boolean isDone() { - return _responseFuture.isDone(); - } - - @Override - public ResultSetGroup get() - throws InterruptedException, ExecutionException { - try { - return get(60000L, TimeUnit.MILLISECONDS); - } catch (TimeoutException e) { - throw new ExecutionException(e); - } - } - - @Override - public ResultSetGroup get(long timeout, TimeUnit unit) - throws InterruptedException, ExecutionException, TimeoutException { - BrokerResponse response = _responseFuture.get(timeout, unit); - return new ResultSetGroup(response); - } + /** + * Provides access to the underlying transport mechanism for this connection. + * There may be client metrics useful for monitoring and other observability goals. + * + * @return pinot client transport. + */ + public PinotClientTransport getTransport() { + return _transport; } } diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/ConnectionFactory.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/ConnectionFactory.java index dbb798fbd456..45a92be48b60 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/ConnectionFactory.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/ConnectionFactory.java @@ -28,7 +28,7 @@ * Creates connections to Pinot, given various initialization methods. */ public class ConnectionFactory { - private static PinotClientTransport _defaultTransport; + private static volatile PinotClientTransport _defaultTransport; private ConnectionFactory() { } @@ -126,6 +126,23 @@ public static Connection fromController(Properties properties, String controller throw new PinotClientException(e); } } + + /** + * @param properties + * @param controllerUrl url host:port of the controller + * @param transport pinot transport + * @return A connection that connects to brokers as per the given controller + */ + public static Connection fromController(Properties properties, String controllerUrl, PinotClientTransport transport) { + try { + return new Connection(properties, + new ControllerBasedBrokerSelector(properties, controllerUrl), + transport); + } catch (Exception e) { + throw new PinotClientException(e); + } + } + /** * Creates a connection to a Pinot cluster, given its Zookeeper URL * @@ -209,6 +226,7 @@ public static Connection fromHostList(Properties properties, List broker } private static PinotClientTransport getDefault(Properties connectionProperties) { + // TODO: This code incorrectly assumes that connection properties are always the same if (_defaultTransport == null) { synchronized (ConnectionFactory.class) { if (_defaultTransport == null) { diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/ControllerBasedBrokerSelector.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/ControllerBasedBrokerSelector.java index d49ed48dec06..cdc2190454e8 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/ControllerBasedBrokerSelector.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/ControllerBasedBrokerSelector.java @@ -55,8 +55,8 @@ public ControllerBasedBrokerSelector(Properties properties, String controllerUrl @Override - public String selectBroker(String table) { - return _brokerCache.getBroker(table); + public String selectBroker(String... tableNames) { + return _brokerCache.getBroker(tableNames); } @Override diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/DynamicBrokerSelector.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/DynamicBrokerSelector.java index 7c7fcfc8b631..84b37a418f96 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/DynamicBrokerSelector.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/DynamicBrokerSelector.java @@ -21,6 +21,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -32,6 +33,7 @@ import org.I0Itec.zkclient.IZkDataListener; import org.I0Itec.zkclient.ZkClient; import org.I0Itec.zkclient.serialize.BytesPushThroughSerializer; +import org.apache.pinot.client.utils.BrokerSelectorUtils; /** @@ -86,24 +88,16 @@ private void refresh() { @Nullable @Override - public String selectBroker(String table) { - if (table != null) { - String tableName = - table.replace(ExternalViewReader.OFFLINE_SUFFIX, "").replace(ExternalViewReader.REALTIME_SUFFIX, ""); - List list = _tableToBrokerListMapRef.get().get(tableName); - if (list != null && !list.isEmpty()) { - return list.get(RANDOM.nextInt(list.size())); - } - // In case tableName is formatted as . - int idx = tableName.indexOf('.'); - if (idx > 0) { - tableName = tableName.substring(idx + 1); - } - list = _tableToBrokerListMapRef.get().get(tableName); + public String selectBroker(String... tableNames) { + if (!(tableNames == null || tableNames.length == 0 || tableNames[0] == null)) { + // getting list of brokers hosting all the tables. + List list = BrokerSelectorUtils.getTablesCommonBrokers(Arrays.asList(tableNames), + _tableToBrokerListMapRef.get()); if (list != null && !list.isEmpty()) { return list.get(RANDOM.nextInt(list.size())); } } + // Return a broker randomly if table is null or no broker is found for the specified table. List list = _allBrokerListRef.get(); if (list != null && !list.isEmpty()) { diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/ExecutionStats.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/ExecutionStats.java index c66ca39b9813..29c9239f2287 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/ExecutionStats.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/ExecutionStats.java @@ -44,7 +44,9 @@ public class ExecutionStats { private static final String MIN_CONSUMING_FRESHNESS_TIME_MS = "minConsumingFreshnessTimeMs"; private static final String TOTAL_DOCS = "totalDocs"; private static final String NUM_GROUPS_LIMIT_REACHED = "numGroupsLimitReached"; + private static final String BROKER_REDUCE_TIME_MS = "brokerReduceTimeMs"; private static final String TIME_USED_MS = "timeUsedMs"; + private static final String PARTIAL_RESULT = "partialResult"; private final JsonNode _brokerResponse; @@ -109,10 +111,18 @@ public boolean isNumGroupsLimitReached() { return _brokerResponse.has(NUM_GROUPS_LIMIT_REACHED) && _brokerResponse.get(NUM_GROUPS_LIMIT_REACHED).asBoolean(); } + public boolean isPartialResult() { + return _brokerResponse.has(PARTIAL_RESULT) && _brokerResponse.get(PARTIAL_RESULT).asBoolean(); + } + public long getTimeUsedMs() { return _brokerResponse.has(TIME_USED_MS) ? _brokerResponse.get(TIME_USED_MS).asLong() : -1L; } + public long getBrokerReduceTimeMs() { + return _brokerResponse.has(BROKER_REDUCE_TIME_MS) ? _brokerResponse.get(BROKER_REDUCE_TIME_MS).asLong() : -1L; + } + @Override public String toString() { Map map = new HashMap<>(); @@ -128,7 +138,9 @@ public String toString() { map.put(MIN_CONSUMING_FRESHNESS_TIME_MS, getMinConsumingFreshnessTimeMs() + "ms"); map.put(TOTAL_DOCS, getTotalDocs()); map.put(NUM_GROUPS_LIMIT_REACHED, isNumGroupsLimitReached()); + map.put(BROKER_REDUCE_TIME_MS, getBrokerReduceTimeMs() + "ms"); map.put(TIME_USED_MS, getTimeUsedMs() + "ms"); + map.put(PARTIAL_RESULT, isPartialResult()); return map.toString(); } } diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/JsonAsyncHttpPinotClientTransport.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/JsonAsyncHttpPinotClientTransport.java index cd0e49139ad6..ec10d44c6991 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/JsonAsyncHttpPinotClientTransport.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/JsonAsyncHttpPinotClientTransport.java @@ -23,13 +23,11 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import io.netty.handler.ssl.ClientAuth; import io.netty.handler.ssl.JdkSslContext; -import io.netty.handler.ssl.SslContext; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; import javax.net.ssl.SSLContext; @@ -39,9 +37,9 @@ import org.apache.pinot.spi.utils.JsonUtils; import org.asynchttpclient.AsyncHttpClient; import org.asynchttpclient.BoundRequestBuilder; +import org.asynchttpclient.ClientStats; import org.asynchttpclient.DefaultAsyncHttpClientConfig.Builder; import org.asynchttpclient.Dsl; -import org.asynchttpclient.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,7 +47,7 @@ /** * JSON encoded Pinot client transport over AsyncHttpClient. */ -public class JsonAsyncHttpPinotClientTransport implements PinotClientTransport { +public class JsonAsyncHttpPinotClientTransport implements PinotClientTransport { private static final Logger LOGGER = LoggerFactory.getLogger(JsonAsyncHttpPinotClientTransport.class); private static final ObjectReader OBJECT_READER = JsonUtils.DEFAULT_READER; private static final String DEFAULT_EXTRA_QUERY_OPTION_STRING = "groupByMode=sql;responseFormat=sql"; @@ -60,50 +58,33 @@ public class JsonAsyncHttpPinotClientTransport implements PinotClientTransport { private final int _brokerReadTimeout; private final AsyncHttpClient _httpClient; private final String _extraOptionStr; + private final boolean _useMultistageEngine; public JsonAsyncHttpPinotClientTransport() { _brokerReadTimeout = 60000; _headers = new HashMap<>(); _scheme = CommonConstants.HTTP_PROTOCOL; _extraOptionStr = DEFAULT_EXTRA_QUERY_OPTION_STRING; - _httpClient = Dsl.asyncHttpClient(); + _httpClient = Dsl.asyncHttpClient(Dsl.config().setRequestTimeout(_brokerReadTimeout)); + _useMultistageEngine = false; } public JsonAsyncHttpPinotClientTransport(Map headers, String scheme, String extraOptionString, - @Nullable SSLContext sslContext, ConnectionTimeouts connectionTimeouts, TlsProtocols tlsProtocols, - @Nullable String appId) { + boolean useMultistageEngine, @Nullable SSLContext sslContext, ConnectionTimeouts connectionTimeouts, + TlsProtocols tlsProtocols, @Nullable String appId) { _brokerReadTimeout = connectionTimeouts.getReadTimeoutMs(); _headers = headers; _scheme = scheme; _extraOptionStr = StringUtils.isEmpty(extraOptionString) ? DEFAULT_EXTRA_QUERY_OPTION_STRING : extraOptionString; + _useMultistageEngine = useMultistageEngine; Builder builder = Dsl.config(); if (sslContext != null) { builder.setSslContext(new JdkSslContext(sslContext, true, ClientAuth.OPTIONAL)); } - builder.setReadTimeout(connectionTimeouts.getReadTimeoutMs()) - .setConnectTimeout(connectionTimeouts.getConnectTimeoutMs()) - .setHandshakeTimeout(connectionTimeouts.getHandshakeTimeoutMs()) - .setUserAgent(ConnectionUtils.getUserAgentVersionFromClassPath("ua", appId)) - .setEnabledProtocols(tlsProtocols.getEnabledProtocols().toArray(new String[0])); - _httpClient = Dsl.asyncHttpClient(builder.build()); - } - - public JsonAsyncHttpPinotClientTransport(Map headers, String scheme, String extraOptionStr, - @Nullable SslContext sslContext, ConnectionTimeouts connectionTimeouts, TlsProtocols tlsProtocols, - @Nullable String appId) { - _brokerReadTimeout = connectionTimeouts.getReadTimeoutMs(); - _headers = headers; - _scheme = scheme; - _extraOptionStr = StringUtils.isEmpty(extraOptionStr) ? DEFAULT_EXTRA_QUERY_OPTION_STRING : extraOptionStr; - - Builder builder = Dsl.config(); - if (sslContext != null) { - builder.setSslContext(sslContext); - } - - builder.setReadTimeout(connectionTimeouts.getReadTimeoutMs()) + builder.setRequestTimeout(_brokerReadTimeout) + .setReadTimeout(connectionTimeouts.getReadTimeoutMs()) .setConnectTimeout(connectionTimeouts.getConnectTimeoutMs()) .setHandshakeTimeout(connectionTimeouts.getHandshakeTimeoutMs()) .setUserAgent(ConnectionUtils.getUserAgentVersionFromClassPath("ua", appId)) @@ -122,44 +103,41 @@ public BrokerResponse executeQuery(String brokerAddress, String query) } @Override - public Future executeQueryAsync(String brokerAddress, String query) { + public CompletableFuture executeQueryAsync(String brokerAddress, String query) { try { ObjectNode json = JsonNodeFactory.instance.objectNode(); json.put("sql", query); json.put("queryOptions", _extraOptionStr); - String url = _scheme + "://" + brokerAddress + "/query/sql"; + LOGGER.debug("Query will use Multistage Engine = {}", _useMultistageEngine); + + String url = String.format("%s://%s%s", _scheme, brokerAddress, _useMultistageEngine ? "/query" : "/query/sql"); BoundRequestBuilder requestBuilder = _httpClient.preparePost(url); if (_headers != null) { _headers.forEach((k, v) -> requestBuilder.addHeader(k, v)); } - - Future response = - requestBuilder.addHeader("Content-Type", "application/json; charset=utf-8").setBody(json.toString()) - .execute(); - return new BrokerResponseFuture(response, query, url, _brokerReadTimeout); - } catch (Exception e) { - throw new PinotClientException(e); - } - } - - @Override - public BrokerResponse executeQuery(String brokerAddress, Request request) - throws PinotClientException { - try { - return executeQueryAsync(brokerAddress, request).get(_brokerReadTimeout, TimeUnit.MILLISECONDS); + LOGGER.debug("Sending query {} to {}", query, url); + return requestBuilder.addHeader("Content-Type", "application/json; charset=utf-8").setBody(json.toString()) + .execute().toCompletableFuture().thenApply(httpResponse -> { + LOGGER.debug("Completed query, HTTP status is {}", httpResponse.getStatusCode()); + + if (httpResponse.getStatusCode() != 200) { + throw new PinotClientException( + "Pinot returned HTTP status " + httpResponse.getStatusCode() + ", expected 200"); + } + + try { + return BrokerResponse.fromJson(OBJECT_READER.readTree(httpResponse.getResponseBodyAsStream())); + } catch (IOException e) { + throw new CompletionException(e); + } + }); } catch (Exception e) { throw new PinotClientException(e); } } - @Override - public Future executeQueryAsync(String brokerAddress, Request request) - throws PinotClientException { - return executeQueryAsync(brokerAddress, request.getQuery()); - } - @Override public void close() throws PinotClientException { @@ -173,60 +151,8 @@ public void close() } } - private static class BrokerResponseFuture implements Future { - private final Future _response; - private final String _query; - private final String _url; - private final long _brokerReadTimeout; - - public BrokerResponseFuture(Future response, String query, String url, long brokerReadTimeout) { - _response = response; - _query = query; - _url = url; - _brokerReadTimeout = brokerReadTimeout; - } - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - return _response.cancel(mayInterruptIfRunning); - } - - @Override - public boolean isCancelled() { - return _response.isCancelled(); - } - - @Override - public boolean isDone() { - return _response.isDone(); - } - - @Override - public BrokerResponse get() - throws ExecutionException { - return get(_brokerReadTimeout, TimeUnit.MILLISECONDS); - } - - @Override - public BrokerResponse get(long timeout, TimeUnit unit) - throws ExecutionException { - try { - LOGGER.debug("Sending query {} to {}", _query, _url); - - Response httpResponse = _response.get(timeout, unit); - - LOGGER.debug("Completed query, HTTP status is {}", httpResponse.getStatusCode()); - - if (httpResponse.getStatusCode() != 200) { - throw new PinotClientException( - "Pinot returned HTTP status " + httpResponse.getStatusCode() + ", expected 200"); - } - - String responseBody = httpResponse.getResponseBody(StandardCharsets.UTF_8); - return BrokerResponse.fromJson(OBJECT_READER.readTree(responseBody)); - } catch (Exception e) { - throw new ExecutionException(e); - } - } + @Override + public ClientStats getClientMetrics() { + return _httpClient.getClientStats(); } } diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/JsonAsyncHttpPinotClientTransportFactory.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/JsonAsyncHttpPinotClientTransportFactory.java index 0adb4e68d328..3471796d2846 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/JsonAsyncHttpPinotClientTransportFactory.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/JsonAsyncHttpPinotClientTransportFactory.java @@ -45,14 +45,15 @@ public class JsonAsyncHttpPinotClientTransportFactory implements PinotClientTran private int _handshakeTimeoutMs = Integer.parseInt(DEFAULT_BROKER_HANDSHAKE_TIMEOUT_MS); private String _appId = null; private String _extraOptionString; + private boolean _useMultistageEngine; @Override public PinotClientTransport buildTransport() { ConnectionTimeouts connectionTimeouts = ConnectionTimeouts.create(_readTimeoutMs, _connectTimeoutMs, _handshakeTimeoutMs); TlsProtocols tlsProtocols = TlsProtocols.defaultProtocols(_tlsV10Enabled); - return new JsonAsyncHttpPinotClientTransport(_headers, _scheme, _extraOptionString, _sslContext, connectionTimeouts, - tlsProtocols, _appId); + return new JsonAsyncHttpPinotClientTransport(_headers, _scheme, _extraOptionString, _useMultistageEngine, + _sslContext, connectionTimeouts, tlsProtocols, _appId); } public Map getHeaders() { @@ -84,8 +85,9 @@ public JsonAsyncHttpPinotClientTransportFactory withConnectionProperties(Propert _headers = ConnectionUtils.getHeadersFromProperties(properties); } - if (_scheme == null) { - _scheme = properties.getProperty("scheme", CommonConstants.HTTP_PROTOCOL); + String scheme = properties.getProperty("scheme", CommonConstants.HTTP_PROTOCOL); + if (_scheme == null || !_scheme.contentEquals(scheme)) { + _scheme = scheme; } if (_sslContext == null && _scheme.contentEquals(CommonConstants.HTTPS_PROTOCOL)) { @@ -103,6 +105,7 @@ public JsonAsyncHttpPinotClientTransportFactory withConnectionProperties(Propert System.getProperties().getProperty("broker.tlsV10Enabled", DEFAULT_BROKER_TLS_V10_ENABLED)); _extraOptionString = properties.getProperty("queryOptions", ""); + _useMultistageEngine = Boolean.parseBoolean(properties.getProperty("useMultistageEngine", "false")); return this; } } diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/PinotClientTransport.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/PinotClientTransport.java index fbd398906ad0..8d9b1dee41ca 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/PinotClientTransport.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/PinotClientTransport.java @@ -18,28 +18,30 @@ */ package org.apache.pinot.client; -import java.util.concurrent.Future; +import java.util.concurrent.CompletableFuture; /** * Interface for plugging different client transports. */ -public interface PinotClientTransport { +public interface PinotClientTransport { BrokerResponse executeQuery(String brokerAddress, String query) throws PinotClientException; - Future executeQueryAsync(String brokerAddress, String query) - throws PinotClientException; - - @Deprecated - BrokerResponse executeQuery(String brokerAddress, Request request) - throws PinotClientException; - - @Deprecated - Future executeQueryAsync(String brokerAddress, Request request) + CompletableFuture executeQueryAsync(String brokerAddress, String query) throws PinotClientException; void close() throws PinotClientException; + + /** + * Access to the client metrics implementation if any. + * This may be useful for observability into the client implementation. + * + * @return underlying client metrics if any + */ + default METRICS getClientMetrics() { + throw new UnsupportedOperationException("No useful client metrics available"); + } } diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/PreparedStatement.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/PreparedStatement.java index 314c94989bae..6d1a43b9e6b8 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/PreparedStatement.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/PreparedStatement.java @@ -18,7 +18,7 @@ */ package org.apache.pinot.client; -import java.util.concurrent.Future; +import java.util.concurrent.CompletableFuture; /** @@ -35,11 +35,6 @@ public class PreparedStatement { _parameters = new String[getQuestionMarkCount(query)]; } - @Deprecated - PreparedStatement(Connection connection, Request request) { - this(connection, request.getQuery()); - } - private int getQuestionMarkCount(String query) { int questionMarkCount = 0; int index = query.indexOf('?'); @@ -72,7 +67,7 @@ public ResultSetGroup execute() { * * @return The query results */ - public Future executeAsync() { + public CompletableFuture executeAsync() { return _connection.executeAsync(fillStatementWithParameters()); } diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/Request.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/Request.java deleted file mode 100644 index f267267c65ac..000000000000 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/Request.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.client; - -/** - * Request is used in server request to host multiple pinot query types, like PQL, SQL. - */ -@Deprecated -public class Request { - - private String _queryFormat; - private String _query; - - public Request(String queryFormat, String query) { - _queryFormat = queryFormat; - _query = query; - } - - public String getQueryFormat() { - return _queryFormat; - } - - public void setQueryFormat(String queryType) { - _queryFormat = queryType; - } - - public String getQuery() { - return _query; - } - - public void setQuery(String query) { - _query = query; - } -} diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/SimpleBrokerSelector.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/SimpleBrokerSelector.java index 51f55cfd1fea..547ba94ed316 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/SimpleBrokerSelector.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/SimpleBrokerSelector.java @@ -37,7 +37,7 @@ public SimpleBrokerSelector(List brokerList) { } @Override - public String selectBroker(String table) { + public String selectBroker(String... tableNames) { return _brokerList.get(_random.nextInt(_brokerList.size())); } diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/UpdatableBrokerCache.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/UpdatableBrokerCache.java index 1e19dff7891e..b3db7fff4d65 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/UpdatableBrokerCache.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/UpdatableBrokerCache.java @@ -38,7 +38,7 @@ void init() * @param tableName * @return Broker address corresponding to the table */ - String getBroker(String tableName); + String getBroker(String... tableName); /** * Returns all the brokers currently in the cache diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/utils/BrokerSelectorUtils.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/utils/BrokerSelectorUtils.java new file mode 100644 index 000000000000..1ca15255cdc8 --- /dev/null +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/utils/BrokerSelectorUtils.java @@ -0,0 +1,72 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.client.utils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.apache.pinot.client.ExternalViewReader; + + +public class BrokerSelectorUtils { + + private BrokerSelectorUtils() { + } + + /** + * + * @param tableNames: List of table names. + * @param brokerData: map holding data for table hosting on brokers. + * @return list of common brokers hosting all the tables. + */ + public static List getTablesCommonBrokers(List tableNames, Map> brokerData) { + List> tablesBrokersList = new ArrayList<>(); + for (String name: tableNames) { + String tableName = getTableNameWithoutSuffix(name); + int idx = tableName.indexOf('.'); + + if (brokerData.containsKey(tableName)) { + tablesBrokersList.add(brokerData.get(tableName)); + } else if (idx > 0) { + // In case tableName is formatted as .
+ tableName = tableName.substring(idx + 1); + tablesBrokersList.add(brokerData.get(tableName)); + } + } + + // return null if tablesBrokersList is empty or contains null + if (tablesBrokersList.isEmpty() + || tablesBrokersList.stream().anyMatch(Objects::isNull)) { + return null; + } + + List commonBrokers = tablesBrokersList.get(0); + for (int i = 1; i < tablesBrokersList.size(); i++) { + commonBrokers.retainAll(tablesBrokersList.get(i)); + } + return commonBrokers; + } + + private static String getTableNameWithoutSuffix(String tableName) { + return + tableName.replace(ExternalViewReader.OFFLINE_SUFFIX, ""). + replace(ExternalViewReader.REALTIME_SUFFIX, ""); + } +} diff --git a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/utils/ConnectionUtils.java b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/utils/ConnectionUtils.java index 51b8d5792068..1bdc1516202f 100644 --- a/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/utils/ConnectionUtils.java +++ b/pinot-clients/pinot-java-client/src/main/java/org/apache/pinot/client/utils/ConnectionUtils.java @@ -24,11 +24,11 @@ import java.util.stream.Collectors; import javax.annotation.Nullable; import javax.net.ssl.SSLContext; -import org.apache.commons.configuration.MapConfiguration; +import org.apache.commons.configuration2.MapConfiguration; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.pinot.common.config.TlsConfig; -import org.apache.pinot.common.utils.TlsUtils; +import org.apache.pinot.common.utils.tls.TlsUtils; import org.apache.pinot.spi.env.PinotConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/BrokerResponseTest.java b/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/BrokerResponseTest.java new file mode 100644 index 000000000000..219c7a09480d --- /dev/null +++ b/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/BrokerResponseTest.java @@ -0,0 +1,52 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.client; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectReader; +import org.apache.pinot.spi.utils.JsonUtils; +import org.testng.Assert; +import org.testng.annotations.Test; + + +public class BrokerResponseTest { + private static final ObjectReader OBJECT_READER = JsonUtils.DEFAULT_READER; + + @Test + public void parseResultWithRequestId() + throws JsonProcessingException { + String responseJson = "{\"requestId\":\"1\",\"traceInfo\":{},\"numDocsScanned\":36542," + + "\"aggregationResults\":[{\"function\":\"count_star\",\"value\":\"36542\"}],\"timeUsedMs\":30," + + "\"segmentStatistics\":[],\"exceptions\":[],\"totalDocs\":115545}"; + BrokerResponse brokerResponse = BrokerResponse.fromJson(OBJECT_READER.readTree(responseJson)); + Assert.assertEquals("1", brokerResponse.getRequestId()); + Assert.assertTrue(!brokerResponse.hasExceptions()); + } + + @Test + public void parseResultWithoutRequestId() + throws JsonProcessingException { + String responseJson = "{\"traceInfo\":{},\"numDocsScanned\":36542," + + "\"aggregationResults\":[{\"function\":\"count_star\",\"value\":\"36542\"}],\"timeUsedMs\":30," + + "\"segmentStatistics\":[],\"exceptions\":[],\"totalDocs\":115545}"; + BrokerResponse brokerResponse = BrokerResponse.fromJson(OBJECT_READER.readTree(responseJson)); + Assert.assertEquals("unknown", brokerResponse.getRequestId()); + Assert.assertTrue(!brokerResponse.hasExceptions()); + } +} diff --git a/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/ConnectionFactoryTest.java b/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/ConnectionFactoryTest.java index 037e8bc9af17..5a755afc5584 100644 --- a/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/ConnectionFactoryTest.java +++ b/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/ConnectionFactoryTest.java @@ -101,6 +101,19 @@ public void testBrokerListWithHeaders() { Assert.assertEquals(connection.getBrokerList(), brokers); } + @Test + public void testConnectionTransport() { + // Create properties + Properties properties = new Properties(); + properties.setProperty("brokerList", "127.0.0.1:1234,localhost:2345"); + + // Create the connection + Connection connection = ConnectionFactory.fromProperties(properties); + + Assert.assertNotNull(connection.getTransport()); + Assert.assertNotNull(connection.getTransport().getClientMetrics()); + } + // For testing DynamicBrokerSelector /** diff --git a/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/JsonAsyncHttpPinotClientTransportTest.java b/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/JsonAsyncHttpPinotClientTransportTest.java new file mode 100644 index 000000000000..4f2b2cbf816a --- /dev/null +++ b/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/JsonAsyncHttpPinotClientTransportTest.java @@ -0,0 +1,137 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.client; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.InetSocketAddress; +import java.util.Properties; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.fail; + + +public class JsonAsyncHttpPinotClientTransportTest implements HttpHandler { + private static final String _VALID_RESPONSE_JSON = "{\"requestId\":\"4567\",\"traceInfo\":{}," + + "\"numDocsScanned\":36542," + + "\"aggregationResults\":[{\"function\":\"count_star\",\"value\":\"36542\"}],\"timeUsedMs\":30," + + "\"segmentStatistics\":[],\"exceptions\":[],\"totalDocs\":115545,\"numServersResponded\":99}"; + + private HttpServer _dummyServer; + private String _responseJson = _VALID_RESPONSE_JSON; + private long _responseDelayMs = 0; + + @BeforeClass + public void setUp() + throws Exception { + _dummyServer = HttpServer.create(); + _dummyServer.bind(new InetSocketAddress("localhost", 0), 0); + _dummyServer.start(); + _dummyServer.createContext("/", this); + } + + @BeforeMethod + public void setUpTestCase() { + _responseJson = _VALID_RESPONSE_JSON; + _responseDelayMs = 0L; + } + + @AfterClass + public void tearDown() { + if (_dummyServer != null) { + _dummyServer.stop(0); + } + } + + @Test + public void validJsonResponse() { + _responseJson = _VALID_RESPONSE_JSON; + JsonAsyncHttpPinotClientTransportFactory factory = new JsonAsyncHttpPinotClientTransportFactory(); + JsonAsyncHttpPinotClientTransport transport = (JsonAsyncHttpPinotClientTransport) factory.buildTransport(); + BrokerResponse response = + transport.executeQuery("localhost:" + _dummyServer.getAddress().getPort(), "select * from planets"); + assertFalse(response.hasExceptions()); + assertEquals(response.getRequestId(), "4567"); + ExecutionStats stats = response.getExecutionStats(); + assertEquals(stats.getTotalDocs(), 115545); + assertEquals(stats.getNumServersResponded(), 99); + } + + @Test + public void invalidJsonResponseTriggersPinotClientException() { + _responseJson = "{"; + JsonAsyncHttpPinotClientTransportFactory factory = new JsonAsyncHttpPinotClientTransportFactory(); + JsonAsyncHttpPinotClientTransport transport = (JsonAsyncHttpPinotClientTransport) factory.buildTransport(); + try { + transport.executeQuery("localhost:" + _dummyServer.getAddress().getPort(), "select * from planets"); + fail("expected exception was not thrown"); + } catch (PinotClientException exception) { + Throwable cause = ExceptionUtils.getRootCause(exception); + assertEquals(cause.getClass().getName(), "com.fasterxml.jackson.core.io.JsonEOFException"); + } + } + + @Test + public void serverResponseExceedsBrokerReadTimeoutThreshold() { + long brokerReadTimeoutMs = 100; + _responseJson = _VALID_RESPONSE_JSON; + _responseDelayMs = brokerReadTimeoutMs + 50; + JsonAsyncHttpPinotClientTransportFactory factory = new JsonAsyncHttpPinotClientTransportFactory(); + Properties connectionProps = new Properties(); + connectionProps.put("brokerReadTimeoutMs", String.valueOf(brokerReadTimeoutMs)); + factory.withConnectionProperties(connectionProps); + JsonAsyncHttpPinotClientTransport transport = (JsonAsyncHttpPinotClientTransport) factory.buildTransport(); + try { + transport.executeQuery("localhost:" + _dummyServer.getAddress().getPort(), "select * from planets"); + fail("expected exception was not thrown"); + } catch (PinotClientException exception) { + Throwable cause = ExceptionUtils.getRootCause(exception); + assertEquals(cause.getClass().getName(), "java.util.concurrent.TimeoutException"); + } + } + + @Override + public void handle(HttpExchange exchange) + throws IOException { + if (_responseDelayMs > 0) { + try { + Thread.sleep(_responseDelayMs); + } catch (InterruptedException e) { + throw new IOException(e); + } + } + exchange.sendResponseHeaders(200, 0); + OutputStream out = exchange.getResponseBody(); + OutputStreamWriter writer = new OutputStreamWriter(out); + writer.append(_responseJson); + writer.flush(); + out.flush(); + out.close(); + } +} diff --git a/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/PreparedStatementTest.java b/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/PreparedStatementTest.java index 404b1b279ab6..97dee69ce1b8 100644 --- a/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/PreparedStatementTest.java +++ b/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/PreparedStatementTest.java @@ -19,7 +19,7 @@ package org.apache.pinot.client; import java.util.Collections; -import java.util.concurrent.Future; +import java.util.concurrent.CompletableFuture; import org.mockito.Mockito; import org.testng.Assert; import org.testng.annotations.Test; @@ -36,7 +36,8 @@ public class PreparedStatementTest { public void testPreparedStatementWithDynamicBroker() { // Create a connection with dynamic broker selector. BrokerSelector mockBrokerSelector = Mockito.mock(BrokerSelector.class); - Mockito.when(mockBrokerSelector.selectBroker(Mockito.anyString())).thenAnswer(i -> i.getArgument(0)); + Mockito.when(mockBrokerSelector.selectBroker(Mockito.anyString())) + .thenAnswer(i -> i.getArgument(0)); Connection connection = new Connection(mockBrokerSelector, _dummyPinotClientTransport); PreparedStatement preparedStatement = connection.prepareStatement("SELECT foo FROM bar WHERE baz = ?"); @@ -78,27 +79,9 @@ public BrokerResponse executeQuery(String brokerAddress, String query) } @Override - public Future executeQueryAsync(String brokerAddress, String query) + public CompletableFuture executeQueryAsync(String brokerAddress, String query) throws PinotClientException { - _lastBrokerAddress = brokerAddress; - _lastQuery = query; - return null; - } - - @Override - public BrokerResponse executeQuery(String brokerAddress, Request request) - throws PinotClientException { - _lastBrokerAddress = brokerAddress; - _lastQuery = request.getQuery(); - return BrokerResponse.empty(); - } - - @Override - public Future executeQueryAsync(String brokerAddress, Request request) - throws PinotClientException { - _lastBrokerAddress = brokerAddress; - _lastQuery = request.getQuery(); - return null; + return CompletableFuture.completedFuture(executeQuery(brokerAddress, query)); } public String getLastQuery() { diff --git a/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/ResultSetGroupTest.java b/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/ResultSetGroupTest.java index c51358769623..e9688b1905bf 100644 --- a/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/ResultSetGroupTest.java +++ b/pinot-clients/pinot-java-client/src/test/java/org/apache/pinot/client/ResultSetGroupTest.java @@ -21,7 +21,7 @@ import java.io.InputStream; import java.util.Collections; import java.util.Properties; -import java.util.concurrent.Future; +import java.util.concurrent.CompletableFuture; import org.apache.pinot.spi.utils.JsonUtils; import org.testng.Assert; import org.testng.annotations.Test; @@ -165,19 +165,7 @@ public BrokerResponse executeQuery(String brokerAddress, String query) } @Override - public Future executeQueryAsync(String brokerAddress, String query) - throws PinotClientException { - return null; - } - - @Override - public BrokerResponse executeQuery(String brokerAddress, Request request) - throws PinotClientException { - return executeQuery(brokerAddress, request.getQuery()); - } - - @Override - public Future executeQueryAsync(String brokerAddress, Request request) + public CompletableFuture executeQueryAsync(String brokerAddress, String query) throws PinotClientException { return null; } diff --git a/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/aggregation.json b/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/aggregation.json index ff9a775e7497..511b70404976 100644 --- a/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/aggregation.json +++ b/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/aggregation.json @@ -1,4 +1,5 @@ { + "requestId": "1", "traceInfo": {}, "numDocsScanned": 36542, "aggregationResults": [ diff --git a/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/aggregationGroupBy.json b/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/aggregationGroupBy.json index bfe0b26c2598..b589cef4c011 100644 --- a/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/aggregationGroupBy.json +++ b/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/aggregationGroupBy.json @@ -1,4 +1,5 @@ { + "requestId": "1", "traceInfo": {}, "numDocsScanned": 22598, "aggregationResults": [ diff --git a/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/exception.json b/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/exception.json index bc7c99ccb538..7a4e7e4fd5ab 100644 --- a/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/exception.json +++ b/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/exception.json @@ -1,4 +1,5 @@ { + "requestId": "1", "traceInfo": {}, "numDocsScanned": 0, "aggregationResults": [], diff --git a/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/selection.json b/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/selection.json index e3068dc263ce..7825e8762098 100644 --- a/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/selection.json +++ b/pinot-clients/pinot-java-client/src/test/resources/org/apache/pinot/client/selection.json @@ -1,4 +1,5 @@ { + "requestId": "1", "selectionResults": { "columns": [ "ActualElapsedTime", diff --git a/pinot-clients/pinot-jdbc-client/pom.xml b/pinot-clients/pinot-jdbc-client/pom.xml index d4b2dd34d2b6..4f781cc0c422 100644 --- a/pinot-clients/pinot-jdbc-client/pom.xml +++ b/pinot-clients/pinot-jdbc-client/pom.xml @@ -19,13 +19,12 @@ under the License. --> - + 4.0.0 pinot-clients org.apache.pinot - 0.13.0-SNAPSHOT + 1.2.0 .. pinot-jdbc-client @@ -35,20 +34,6 @@ ${basedir}/../.. - - - org.apache.maven.plugins - maven-surefire-plugin - - 1 - true - - - - org.apache.maven.plugins - maven-enforcer-plugin - - src/main/resources @@ -69,11 +54,6 @@ pinot-java-client ${project.version} - - org.apache.pinot - pinot-core - ${project.version} - org.testng testng @@ -86,37 +66,16 @@ org.asynchttpclient async-http-client - - - io.netty - netty - - - io.netty - netty-transport-native-unix-common - - com.101tec zkclient - - - io.netty - netty - - org.slf4j slf4j-api - - com.google.code.findbugs - jsr305 - - build-shaded-jar @@ -126,28 +85,9 @@ !true - - - - maven-shade-plugin - 3.2.1 - - - package - - shade - - - - - - - - - - - + + package + diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotConnection.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotConnection.java index 68471226054b..1e6a667ce773 100644 --- a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotConnection.java +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotConnection.java @@ -18,29 +18,34 @@ */ package org.apache.pinot.client; -import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Properties; import org.apache.pinot.client.base.AbstractBaseConnection; import org.apache.pinot.client.controller.PinotControllerTransport; import org.apache.pinot.client.controller.PinotControllerTransportFactory; import org.apache.pinot.client.controller.response.ControllerTenantBrokerResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.pinot.spi.utils.CommonConstants.Broker.Request.QueryOptionKey; public class PinotConnection extends AbstractBaseConnection { - private static final Logger LOGGER = LoggerFactory.getLogger(Connection.class); + protected static final String[] POSSIBLE_QUERY_OPTIONS = { + QueryOptionKey.ENABLE_NULL_HANDLING, + QueryOptionKey.USE_MULTISTAGE_ENGINE + }; private org.apache.pinot.client.Connection _session; private boolean _closed; private String _controllerURL; private PinotControllerTransport _controllerTransport; + private final Map _queryOptions = new HashMap(); + public static final String BROKER_LIST = "brokers"; PinotConnection(String controllerURL, PinotClientTransport transport, String tenant, @@ -64,12 +69,52 @@ public class PinotConnection extends AbstractBaseConnection { brokers = getBrokerList(controllerURL, tenant); } _session = new org.apache.pinot.client.Connection(properties, brokers, transport); + + for (String possibleQueryOption: POSSIBLE_QUERY_OPTIONS) { + Object property = properties.getProperty(possibleQueryOption); + if (property != null) { + _queryOptions.put(possibleQueryOption, parseOptionValue(property)); + } + } + } + + private Object parseOptionValue(Object value) { + if (value instanceof String) { + String str = (String) value; + + try { + Long numVal = Long.valueOf(str); + if (numVal != null) { + return numVal; + } + } catch (NumberFormatException e) { + } + + try { + Double numVal = Double.valueOf(str); + if (numVal != null) { + return numVal; + } + } catch (NumberFormatException e) { + } + + Boolean boolVal = Boolean.valueOf(str.toLowerCase()); + if (boolVal != null) { + return boolVal; + } + } + + return value; } public org.apache.pinot.client.Connection getSession() { return _session; } + public Map getQueryOptions() { + return _queryOptions; + } + private List getBrokerList(String controllerURL, String tenant) { ControllerTenantBrokerResponse controllerTenantBrokerResponse = _controllerTransport.getBrokersFromController(controllerURL, tenant); diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotConnectionMetaData.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotConnectionMetaData.java index 30f13d2b0c30..94e680f9cac0 100644 --- a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotConnectionMetaData.java +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotConnectionMetaData.java @@ -150,30 +150,34 @@ public ResultSet getTableTypes() public ResultSet getColumns(String catalog, String schemaPattern, String tableNamePattern, String columnNamePattern) throws SQLException { + if (tableNamePattern != null && tableNamePattern.equals("%")) { + LOGGER.warn("driver does not support pattern [{}] for table name", tableNamePattern); + return PinotResultSet.empty(); + } + SchemaResponse schemaResponse = _controllerTransport.getTableSchema(tableNamePattern, _controllerURL); PinotMeta pinotMeta = new PinotMeta(); pinotMeta.setColumnNames(TABLE_SCHEMA_COLUMNS); pinotMeta.setColumnDataTypes(TABLE_SCHEMA_COLUMNS_DTYPES); - String tableName = schemaResponse.getSchemaName(); int ordinalPosition = 1; if (schemaResponse.getDimensions() != null) { for (JsonNode columns : schemaResponse.getDimensions()) { - appendColumnMeta(pinotMeta, tableName, ordinalPosition, columns); + appendColumnMeta(pinotMeta, tableNamePattern, ordinalPosition, columns); ordinalPosition++; } } if (schemaResponse.getMetrics() != null) { for (JsonNode columns : schemaResponse.getMetrics()) { - appendColumnMeta(pinotMeta, tableName, ordinalPosition, columns); + appendColumnMeta(pinotMeta, tableNamePattern, ordinalPosition, columns); ordinalPosition++; } } if (schemaResponse.getDateTimeFieldSpecs() != null) { for (JsonNode columns : schemaResponse.getDateTimeFieldSpecs()) { - appendColumnMeta(pinotMeta, tableName, ordinalPosition, columns); + appendColumnMeta(pinotMeta, tableNamePattern, ordinalPosition, columns); ordinalPosition++; } } diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotDriver.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotDriver.java index 2e44fadeb5f0..93ba1ab551d1 100644 --- a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotDriver.java +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotDriver.java @@ -38,7 +38,7 @@ import org.apache.pinot.client.controller.PinotControllerTransport; import org.apache.pinot.client.controller.PinotControllerTransportFactory; import org.apache.pinot.client.utils.DriverUtils; -import org.apache.pinot.common.utils.TlsUtils; +import org.apache.pinot.common.utils.tls.TlsUtils; import org.apache.pinot.spi.utils.CommonConstants; import org.slf4j.LoggerFactory; @@ -80,8 +80,14 @@ public PinotDriver(SSLContext sslContext) { @Override public Connection connect(String url, Properties info) throws SQLException { + + PinotClientTransport pinotClientTransport = null; + PinotControllerTransport pinotControllerTransport = null; try { - LOGGER.info("Initiating connection to database for url: " + url); + if (!this.acceptsURL(url)) { + return null; + } + LOGGER.info("Initiating connection to database for url: {}", url); Map urlParams = DriverUtils.getURLParams(url); info.putAll(urlParams); @@ -112,18 +118,43 @@ public Connection connect(String url, Properties info) pinotControllerTransportFactory.setHeaders(headers); } - PinotClientTransport pinotClientTransport = factory.withConnectionProperties(info).buildTransport(); - PinotControllerTransport pinotControllerTransport = pinotControllerTransportFactory + pinotClientTransport = factory.withConnectionProperties(info).buildTransport(); + pinotControllerTransport = pinotControllerTransportFactory .withConnectionProperties(info) .buildTransport(); String controllerUrl = DriverUtils.getControllerFromURL(url); String tenant = info.getProperty(INFO_TENANT, DEFAULT_TENANT); return new PinotConnection(info, controllerUrl, pinotClientTransport, tenant, pinotControllerTransport); } catch (Exception e) { + closeResourcesSafely(pinotClientTransport, pinotControllerTransport); throw new SQLException(String.format("Failed to connect to url : %s", url), e); } } + /** + * If something goes wrong generating the connection, the transports need to be safely closed to prevent leaks. + * @param pinotClientTransport connection client transport + * @param pinotControllerTransport connection controller transport + */ + private static void closeResourcesSafely(PinotClientTransport pinotClientTransport, + PinotControllerTransport pinotControllerTransport) { + try { + if (pinotClientTransport != null) { + pinotClientTransport.close(); + } + } catch (PinotClientException ignored) { + // ignored + } + + try { + if (pinotControllerTransport != null) { + pinotControllerTransport.close(); + } + } catch (PinotClientException ignored) { + // ignored + } + } + private Map getHeadersFromProperties(Properties info) { return info.entrySet().stream().filter(entry -> entry.getKey().toString().startsWith(INFO_HEADERS + ".")).map( entry -> Pair.of(entry.getKey().toString().substring(INFO_HEADERS.length() + 1), diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotPreparedStatement.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotPreparedStatement.java index e3b601859f5a..06ada14ac8a7 100644 --- a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotPreparedStatement.java +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotPreparedStatement.java @@ -34,7 +34,7 @@ public class PinotPreparedStatement extends AbstractBasePreparedStatement { private static final String LIMIT_STATEMENT = "LIMIT"; - private Connection _connection; + private PinotConnection _connection; private org.apache.pinot.client.Connection _session; private ResultSetGroup _resultSetGroup; private PreparedStatement _preparedStatement; @@ -51,6 +51,7 @@ public PinotPreparedStatement(PinotConnection connection, String query) { if (!DriverUtils.queryContainsLimitStatement(_query)) { _query += " " + LIMIT_STATEMENT + " " + _maxRows; } + _query = DriverUtils.enableQueryOptions(_query, _connection.getQueryOptions()); _preparedStatement = new PreparedStatement(_session, _query); } @@ -167,7 +168,6 @@ public boolean execute() _resultSet.beforeFirst(); return true; } else { - _resultSet = null; return false; } } @@ -177,7 +177,7 @@ public ResultSet executeQuery(String sql) throws SQLException { validateState(); try { - _resultSetGroup = _session.execute(sql); + _resultSetGroup = _session.execute(DriverUtils.enableQueryOptions(sql, _connection.getQueryOptions())); if (_resultSetGroup.getResultSetCount() == 0) { _resultSet = PinotResultSet.empty(); return _resultSet; @@ -203,7 +203,7 @@ public ResultSet executeQuery() } return _resultSet; } catch (PinotClientException e) { - throw new SQLException("Failed to execute query : {}", _query, e); + throw new SQLException(String.format("Failed to execute query : %s", _query), e); } } @@ -211,13 +211,7 @@ public ResultSet executeQuery() public boolean execute(String sql) throws SQLException { _resultSet = executeQuery(sql); - if (_resultSet.next()) { - _resultSet.beforeFirst(); - return true; - } else { - _resultSet = null; - return false; - } + return _resultSet != null; } @Override diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotResultSet.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotResultSet.java index 3e900c64eabc..838fbf37e218 100644 --- a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotResultSet.java +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotResultSet.java @@ -193,19 +193,25 @@ public InputStream getAsciiStream(int columnIndex) public BigDecimal getBigDecimal(int columnIndex, int scale) throws SQLException { try { - String value = getString(columnIndex); - BigDecimal bigDecimal = new BigDecimal(value).setScale(scale); - return bigDecimal; + String value = this.getString(columnIndex); + int calculatedScale = getCalculatedScale(value); + return value == null ? null : new BigDecimal(value).setScale(calculatedScale); } catch (Exception e) { throw new SQLException("Unable to fetch BigDecimal value", e); } } + int getCalculatedScale(String value) { + int index = value.indexOf("."); + return index == -1 ? 0 : value.length() - index - 1; + } + @Override public boolean getBoolean(int columnIndex) throws SQLException { validateColumn(columnIndex); - return Boolean.parseBoolean(_resultSet.getString(_currentRow, columnIndex - 1)); + String value = getString(columnIndex); + return value == null ? false : Boolean.parseBoolean(value); } @Override @@ -213,7 +219,7 @@ public byte[] getBytes(int columnIndex) throws SQLException { try { String value = getString(columnIndex); - return Hex.decodeHex(value.toCharArray()); + return value == null ? null : Hex.decodeHex(value.toCharArray()); } catch (Exception e) { throw new SQLException(String.format("Unable to fetch value for column %d", columnIndex), e); } @@ -232,7 +238,7 @@ public Date getDate(int columnIndex, Calendar cal) throws SQLException { try { String value = getString(columnIndex); - return DateTimeUtils.getDateFromString(value, cal); + return value == null ? null : DateTimeUtils.getDateFromString(value, cal); } catch (Exception e) { throw new SQLException("Unable to fetch date", e); } @@ -242,32 +248,32 @@ public Date getDate(int columnIndex, Calendar cal) public double getDouble(int columnIndex) throws SQLException { validateColumn(columnIndex); - - return _resultSet.getDouble(_currentRow, columnIndex - 1); + String value = getString(columnIndex); + return value == null ? 0.0 : Double.parseDouble(value); } @Override public float getFloat(int columnIndex) throws SQLException { validateColumn(columnIndex); - - return _resultSet.getFloat(_currentRow, columnIndex - 1); + String value = getString(columnIndex); + return value == null ? 0.0f : Float.parseFloat(value); } @Override public int getInt(int columnIndex) throws SQLException { validateColumn(columnIndex); - - return _resultSet.getInt(_currentRow, columnIndex - 1); + String value = getString(columnIndex); + return value == null ? 0 : Integer.parseInt(value); } @Override public long getLong(int columnIndex) throws SQLException { validateColumn(columnIndex); - - return _resultSet.getLong(_currentRow, columnIndex - 1); + String value = getString(columnIndex); + return value == null ? 0 : Long.parseLong(value); } @Override @@ -282,7 +288,7 @@ public int getRow() public short getShort(int columnIndex) throws SQLException { Integer value = getInt(columnIndex); - return value.shortValue(); + return value == null ? null : value.shortValue(); } @Override @@ -359,7 +365,7 @@ public Time getTime(int columnIndex, Calendar cal) throws SQLException { try { String value = getString(columnIndex); - return DateTimeUtils.getTimeFromString(value, cal); + return value == null ? null : DateTimeUtils.getTimeFromString(value, cal); } catch (Exception e) { throw new SQLException("Unable to fetch date", e); } @@ -370,7 +376,7 @@ public Timestamp getTimestamp(int columnIndex, Calendar cal) throws SQLException { try { String value = getString(columnIndex); - return DateTimeUtils.getTimestampFromString(value, cal); + return value == null ? null : DateTimeUtils.getTimestampFromString(value, cal); } catch (Exception e) { throw new SQLException("Unable to fetch date", e); } diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotStatement.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotStatement.java index dcedad18a931..cdfd155d0471 100644 --- a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotStatement.java +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/PinotStatement.java @@ -28,7 +28,7 @@ public class PinotStatement extends AbstractBaseStatement { private static final String LIMIT_STATEMENT = "LIMIT"; - private final Connection _connection; + private final PinotConnection _connection; private final org.apache.pinot.client.Connection _session; private boolean _closed; private ResultSet _resultSet; @@ -63,7 +63,8 @@ public ResultSet executeQuery(String sql) if (!DriverUtils.queryContainsLimitStatement(sql)) { sql += " " + LIMIT_STATEMENT + " " + _maxRows; } - ResultSetGroup resultSetGroup = _session.execute(sql); + String enabledSql = DriverUtils.enableQueryOptions(sql, _connection.getQueryOptions()); + ResultSetGroup resultSetGroup = _session.execute(enabledSql); if (resultSetGroup.getResultSetCount() == 0) { _resultSet = PinotResultSet.empty(); return _resultSet; @@ -79,13 +80,7 @@ public ResultSet executeQuery(String sql) public boolean execute(String sql) throws SQLException { _resultSet = executeQuery(sql); - if (_resultSet.next()) { - _resultSet.beforeFirst(); - return true; - } else { - _resultSet = null; - return false; - } + return _resultSet != null; } @Override diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/base/AbstractBaseConnection.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/base/AbstractBaseConnection.java index 73a1d19433e4..eebf0367b501 100644 --- a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/base/AbstractBaseConnection.java +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/base/AbstractBaseConnection.java @@ -25,9 +25,7 @@ import java.sql.Connection; import java.sql.NClob; import java.sql.PreparedStatement; -import java.sql.SQLClientInfoException; import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; import java.sql.SQLWarning; import java.sql.SQLXML; import java.sql.Savepoint; @@ -43,182 +41,153 @@ protected abstract void validateState() throws SQLException; @Override - public void abort(Executor executor) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public void abort(Executor executor) { + // no-op } @Override - public void clearWarnings() - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public void clearWarnings() { + // no-op } @Override - public void commit() - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public void commit() { + // no-op } @Override - public Array createArrayOf(String typeName, Object[] elements) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public Array createArrayOf(String typeName, Object[] elements) { + return null; } @Override - public Blob createBlob() - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public Blob createBlob() { + return null; } @Override - public Clob createClob() - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public Clob createClob() { + return null; } @Override - public NClob createNClob() - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public NClob createNClob() { + return null; } @Override - public SQLXML createSQLXML() - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public SQLXML createSQLXML() { + return null; } @Override - public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) { + return null; } @Override - public Statement createStatement(int resultSetType, int resultSetConcurrency) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public Statement createStatement(int resultSetType, int resultSetConcurrency) { + return null; } @Override - public Struct createStruct(String typeName, Object[] attributes) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public Struct createStruct(String typeName, Object[] attributes) { + return null; } @Override - public boolean getAutoCommit() - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public boolean getAutoCommit() { + return false; } @Override - public void setAutoCommit(boolean autoCommit) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public void setAutoCommit(boolean autoCommit) { + // no-op } @Override - public String getCatalog() - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public String getCatalog() { + return null; } @Override - public void setCatalog(String catalog) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public void setCatalog(String catalog) { + // no-op } @Override - public String getClientInfo(String name) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public String getClientInfo(String name) { + return null; } @Override - public Properties getClientInfo() - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public Properties getClientInfo() { + return null; } @Override - public void setClientInfo(Properties properties) - throws SQLClientInfoException { + public void setClientInfo(Properties properties) { + // no-op } @Override - public int getHoldability() - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public int getHoldability() { + return 0; } @Override - public void setHoldability(int holdability) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public void setHoldability(int holdability) { + // no-op } @Override - public int getNetworkTimeout() - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public int getNetworkTimeout() { + return 0; } @Override - public String getSchema() - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public String getSchema() { + return null; } @Override - public void setSchema(String schema) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public void setSchema(String schema) { + // no-op } @Override - public int getTransactionIsolation() - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public int getTransactionIsolation() { + return Connection.TRANSACTION_NONE; } @Override - public void setTransactionIsolation(int level) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public void setTransactionIsolation(int level) { + // no-op } @Override - public Map> getTypeMap() - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public Map> getTypeMap() { + return null; } @Override - public void setTypeMap(Map> map) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public void setTypeMap(Map> map) { + // no-op } @Override - public SQLWarning getWarnings() - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public SQLWarning getWarnings() { + return null; } @Override - public boolean isReadOnly() - throws SQLException { + public boolean isReadOnly() { return false; } @Override - public void setReadOnly(boolean readOnly) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public void setReadOnly(boolean readOnly) { + // no-op } @Override @@ -228,111 +197,94 @@ public boolean isValid(int timeout) } @Override - public boolean isWrapperFor(Class iface) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public boolean isWrapperFor(Class iface) { + return false; } @Override - public String nativeSQL(String sql) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public String nativeSQL(String sql) { + return null; } @Override - public CallableStatement prepareCall(String sql) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public CallableStatement prepareCall(String sql) { + return null; } @Override - public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) { + return null; } @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, - int resultSetHoldability) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + int resultSetHoldability) { + return null; } @Override - public java.sql.PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public java.sql.PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) { + return null; } @Override public java.sql.PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, - int resultSetHoldability) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + int resultSetHoldability) { + return null; } @Override - public java.sql.PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public java.sql.PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) { + return null; } @Override - public java.sql.PreparedStatement prepareStatement(String sql, int[] columnIndexes) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public java.sql.PreparedStatement prepareStatement(String sql, int[] columnIndexes) { + return null; } @Override - public PreparedStatement prepareStatement(String sql, String[] columnNames) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public PreparedStatement prepareStatement(String sql, String[] columnNames) { + return null; } @Override - public void releaseSavepoint(Savepoint savepoint) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public void releaseSavepoint(Savepoint savepoint) { + // no-op } @Override - public void rollback() - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public void rollback() { + // no-op } @Override - public void rollback(Savepoint savepoint) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public void rollback(Savepoint savepoint) { + // no-op } @Override - public void setClientInfo(String name, String value) - throws SQLClientInfoException { + public void setClientInfo(String name, String value) { + // no-op } @Override - public void setNetworkTimeout(Executor executor, int milliseconds) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public void setNetworkTimeout(Executor executor, int milliseconds) { + // no-op } @Override - public Savepoint setSavepoint() - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public Savepoint setSavepoint() { + return null; } @Override - public Savepoint setSavepoint(String name) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public Savepoint setSavepoint(String name) { + return null; } @Override - public T unwrap(Class iface) - throws SQLException { - throw new SQLFeatureNotSupportedException(); + public T unwrap(Class iface) { + return null; } } diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/base/AbstractBaseStatement.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/base/AbstractBaseStatement.java index a061a3557ebd..454db760931d 100644 --- a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/base/AbstractBaseStatement.java +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/base/AbstractBaseStatement.java @@ -50,7 +50,7 @@ public void clearBatch() @Override public void clearWarnings() throws SQLException { - throw new SQLFeatureNotSupportedException(); + // no-op } @Override @@ -164,7 +164,7 @@ public int getQueryTimeout() @Override public void setQueryTimeout(int seconds) throws SQLException { - throw new SQLFeatureNotSupportedException(); + // no-op } @Override @@ -194,7 +194,7 @@ public int getUpdateCount() @Override public SQLWarning getWarnings() throws SQLException { - throw new SQLFeatureNotSupportedException(); + return null; } @Override @@ -212,7 +212,7 @@ public boolean isPoolable() @Override public void setPoolable(boolean poolable) throws SQLException { - throw new SQLFeatureNotSupportedException(); + // no-op; } @Override @@ -230,7 +230,7 @@ public void setCursorName(String cursorName) @Override public void setEscapeProcessing(boolean enable) throws SQLException { - throw new SQLFeatureNotSupportedException(); + // no-op } @Override diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/PinotControllerTransport.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/PinotControllerTransport.java index 6fc4679267de..4216b0445061 100644 --- a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/PinotControllerTransport.java +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/PinotControllerTransport.java @@ -76,7 +76,7 @@ private String getUserAgentVersionFromClassPath(@Nullable String appId) { userAgentProperties.load( PinotControllerTransport.class.getClassLoader().getResourceAsStream("version.properties")); } catch (IOException e) { - LOGGER.warn("Unable to set user agent version"); + LOGGER.warn("Unable to set user agent version", e); } String userAgentFromProperties = userAgentProperties.getProperty("ua", "unknown"); if (StringUtils.isNotEmpty(appId)) { @@ -131,6 +131,7 @@ public ControllerTenantBrokerResponse getBrokersFromController(String controller _headers.forEach((k, v) -> requestBuilder.addHeader(k, v)); } + final Future response = requestBuilder.addHeader("Content-Type", "application/json; charset=utf-8").execute(); diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/response/ControllerResponseFuture.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/response/ControllerResponseFuture.java index 1bc0470d75de..9899eeb2d568 100644 --- a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/response/ControllerResponseFuture.java +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/response/ControllerResponseFuture.java @@ -18,10 +18,11 @@ */ package org.apache.pinot.client.controller.response; -import java.nio.charset.StandardCharsets; +import java.io.InputStream; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import org.apache.pinot.client.PinotClientException; import org.asynchttpclient.Response; import org.slf4j.Logger; @@ -62,7 +63,7 @@ public T get() abstract public T get(long timeout, TimeUnit unit) throws ExecutionException; - public String getStringResponse(long timeout, TimeUnit unit) + public InputStream getStreamResponse(long timeout, TimeUnit unit) throws ExecutionException { try { LOGGER.debug("Sending request to {}", _url); @@ -75,10 +76,8 @@ public String getStringResponse(long timeout, TimeUnit unit) throw new PinotClientException("Pinot returned HTTP status " + httpResponse.getStatusCode() + ", expected 200"); } - String responseBody = httpResponse.getResponseBody(StandardCharsets.UTF_8); - - return responseBody; - } catch (Exception e) { + return httpResponse.getResponseBodyAsStream(); + } catch (TimeoutException | InterruptedException e) { throw new ExecutionException(e); } } diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/response/ControllerTenantBrokerResponse.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/response/ControllerTenantBrokerResponse.java index 68d212aade05..1b537aad42a8 100644 --- a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/response/ControllerTenantBrokerResponse.java +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/response/ControllerTenantBrokerResponse.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.JsonNode; import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; @@ -74,16 +75,13 @@ public ControllerTenantBrokerResponseFuture(Future response, String ur @Override public ControllerTenantBrokerResponse get(long timeout, TimeUnit unit) throws ExecutionException { - String response = getStringResponse(timeout, unit); try { - JsonNode jsonResponse = JsonUtils.stringToJsonNode(response); - ControllerTenantBrokerResponse tableResponse = ControllerTenantBrokerResponse.fromJson(jsonResponse); - return tableResponse; + InputStream response = getStreamResponse(timeout, unit); + JsonNode jsonResponse = JsonUtils.inputStreamToJsonNode(response); + return ControllerTenantBrokerResponse.fromJson(jsonResponse); } catch (IOException e) { - new ExecutionException(e); + throw new ExecutionException(e); } - - return null; } } } diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/response/SchemaResponse.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/response/SchemaResponse.java index 27116a634a11..5cdbe49050f4 100644 --- a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/response/SchemaResponse.java +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/response/SchemaResponse.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.JsonNode; import java.io.IOException; +import java.io.InputStream; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -75,15 +76,13 @@ public SchemaResponseFuture(Future response, String url) { @Override public SchemaResponse get(long timeout, TimeUnit unit) throws ExecutionException { - String response = getStringResponse(timeout, unit); try { - JsonNode jsonResponse = JsonUtils.stringToJsonNode(response); + InputStream response = getStreamResponse(timeout, unit); + JsonNode jsonResponse = JsonUtils.inputStreamToJsonNode(response); return SchemaResponse.fromJson(jsonResponse); } catch (IOException e) { - new ExecutionException(e); + throw new ExecutionException(e); } - - return null; } } } diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/response/TableResponse.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/response/TableResponse.java index 7e4a1c35fc72..56ea973e9cb7 100644 --- a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/response/TableResponse.java +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/controller/response/TableResponse.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.JsonNode; import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; @@ -78,16 +79,13 @@ public TableResponseFuture(Future response, String url) { @Override public TableResponse get(long timeout, TimeUnit unit) throws ExecutionException { - String response = getStringResponse(timeout, unit); try { - JsonNode jsonResponse = JsonUtils.stringToJsonNode(response); - TableResponse tableResponse = TableResponse.fromJson(jsonResponse); - return tableResponse; + InputStream response = getStreamResponse(timeout, unit); + JsonNode jsonResponse = JsonUtils.inputStreamToJsonNode(response); + return TableResponse.fromJson(jsonResponse); } catch (IOException e) { - new ExecutionException(e); + throw new ExecutionException(e); } - - return null; } } } diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/utils/Constants.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/utils/Constants.java index c26579702299..773d9a8ff210 100644 --- a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/utils/Constants.java +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/utils/Constants.java @@ -34,7 +34,7 @@ private Constants() { public static final String[] SCHEMA_COLUMNS_DTYPES = {"STRING", "STRING"}; public static final String[] TABLE_COLUMNS = { - "TABLE_SCHEM", "TABLE_CATALOG", "TABLE_NAME", "TABLE_TYPE", "REMARKS", "TYPE_CAT", "TYPE_SCHEM", "TYPE_NAME", + "TABLE_CAT", "TABLE_SCHEM", "TABLE_NAME", "TABLE_TYPE", "REMARKS", "TYPE_CAT", "TYPE_SCHEM", "TYPE_NAME", "SELF_REFERENCING_COL_NAME", "REF_GENERATION" }; public static final String[] TABLE_COLUMNS_DTYPES = diff --git a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/utils/DriverUtils.java b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/utils/DriverUtils.java index d995342be62a..a55c4d1a3299 100644 --- a/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/utils/DriverUtils.java +++ b/pinot-clients/pinot-jdbc-client/src/main/java/org/apache/pinot/client/utils/DriverUtils.java @@ -29,16 +29,15 @@ import java.util.List; import java.util.Map; import java.util.Properties; -import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.net.ssl.SSLContext; -import org.apache.commons.configuration.MapConfiguration; +import org.apache.commons.configuration2.MapConfiguration; import org.apache.commons.lang3.StringUtils; import org.apache.http.NameValuePair; import org.apache.http.client.utils.URLEncodedUtils; +import org.apache.pinot.common.auth.BasicAuthUtils; import org.apache.pinot.common.config.TlsConfig; -import org.apache.pinot.common.utils.TlsUtils; -import org.apache.pinot.core.auth.BasicAuthUtils; +import org.apache.pinot.common.utils.tls.TlsUtils; import org.apache.pinot.spi.env.PinotConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,6 +48,8 @@ public class DriverUtils { public static final String DRIVER = "pinot"; public static final Logger LOG = LoggerFactory.getLogger(DriverUtils.class); private static final String LIMIT_STATEMENT_REGEX = "\\s(limit)\\s"; + private static final Pattern LIMIT_STATEMENT_REGEX_PATTERN = + Pattern.compile(LIMIT_STATEMENT_REGEX, Pattern.CASE_INSENSITIVE); // SSL Properties public static final String PINOT_JDBC_TLS_PREFIX = "pinot.jdbc.tls"; @@ -62,8 +63,8 @@ private DriverUtils() { } public static SSLContext getSSLContextFromJDBCProps(Properties properties) { - TlsConfig tlsConfig = TlsUtils.extractTlsConfig( - new PinotConfiguration(new MapConfiguration(properties)), PINOT_JDBC_TLS_PREFIX); + TlsConfig tlsConfig = + TlsUtils.extractTlsConfig(new PinotConfiguration(new MapConfiguration(properties)), PINOT_JDBC_TLS_PREFIX); TlsUtils.installDefaultSSLSocketFactory(tlsConfig); return TlsUtils.getSslContext(); } @@ -71,7 +72,7 @@ public static SSLContext getSSLContextFromJDBCProps(Properties properties) { public static void handleAuth(Properties info, Map headers) throws SQLException { - if (info.contains(USER_PROPERTY) && !headers.containsKey(AUTH_HEADER)) { + if (info.containsKey(USER_PROPERTY) && !headers.containsKey(AUTH_HEADER)) { String username = info.getProperty(USER_PROPERTY); String password = info.getProperty(PASSWORD_PROPERTY, ""); if (StringUtils.isAnyEmpty(username, password)) { @@ -125,7 +126,7 @@ public static Map getURLParams(String url) { List params = URLEncodedUtils.parse(uri, StandardCharsets.UTF_8); Map paramsMap = new HashMap<>(); - for (NameValuePair param: params) { + for (NameValuePair param : params) { paramsMap.put(param.getName(), param.getValue()); } @@ -213,8 +214,40 @@ public static String getJavaClassName(String columnDataType) { } public static boolean queryContainsLimitStatement(String query) { - Pattern pattern = Pattern.compile(LIMIT_STATEMENT_REGEX, Pattern.CASE_INSENSITIVE); - Matcher matcher = pattern.matcher(query); - return matcher.find(); + return LIMIT_STATEMENT_REGEX_PATTERN.matcher(query).find(); + } + + public static String enableQueryOptions(String sql, Map options) { + StringBuilder optionsBuilder = new StringBuilder(); + for (Map.Entry optionEntry : options.entrySet()) { + if (!sql.contains(optionEntry.getKey())) { + optionsBuilder.append(DriverUtils.createSetQueryOptionString(optionEntry.getKey(), optionEntry.getValue())); + } + } + optionsBuilder.append(sql); + return optionsBuilder.toString(); + } + + public static String createSetQueryOptionString(String optionKey, Object optionValue) { + StringBuilder optionBuilder = new StringBuilder(); + optionBuilder.append("SET ").append(optionKey); + + if (optionValue != null) { + optionBuilder.append('='); + + if (optionValue instanceof Boolean) { + optionBuilder.append(((Boolean) optionValue).booleanValue()); + } else if (optionValue instanceof Integer || optionValue instanceof Long) { + optionBuilder.append(((Number) optionValue).longValue()); + } else if (optionValue instanceof Float || optionValue instanceof Double) { + optionBuilder.append(((Number) optionValue).doubleValue()); + } else { + throw new IllegalArgumentException( + "Option Type " + optionValue.getClass().getSimpleName() + " is not supported."); + } + } + + optionBuilder.append(";\n"); + return optionBuilder.toString(); } } diff --git a/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/DummyPinotClientTransport.java b/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/DummyPinotClientTransport.java index 86ad814ffab5..dc2040066baa 100644 --- a/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/DummyPinotClientTransport.java +++ b/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/DummyPinotClientTransport.java @@ -18,7 +18,7 @@ */ package org.apache.pinot.client; -import java.util.concurrent.Future; +import java.util.concurrent.CompletableFuture; public class DummyPinotClientTransport implements PinotClientTransport { @@ -32,26 +32,12 @@ public BrokerResponse executeQuery(String brokerAddress, String query) } @Override - public Future executeQueryAsync(String brokerAddress, String query) + public CompletableFuture executeQueryAsync(String brokerAddress, String query) throws PinotClientException { _lastQuery = query; return null; } - @Override - public BrokerResponse executeQuery(String brokerAddress, Request request) - throws PinotClientException { - _lastQuery = request.getQuery(); - return BrokerResponse.empty(); - } - - @Override - public Future executeQueryAsync(String brokerAddress, Request request) - throws PinotClientException { - _lastQuery = request.getQuery(); - return null; - } - public String getLastQuery() { return _lastQuery; } diff --git a/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotConnectionTest.java b/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotConnectionTest.java index ac3a418ccd10..5fd2b3ab6240 100644 --- a/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotConnectionTest.java +++ b/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotConnectionTest.java @@ -18,14 +18,22 @@ */ package org.apache.pinot.client; +import java.sql.DatabaseMetaData; import java.sql.Statement; import org.testng.Assert; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; public class PinotConnectionTest { - private DummyPinotClientTransport _dummyPinotClientTransport = new DummyPinotClientTransport(); - private DummyPinotControllerTransport _dummyPinotControllerTransport = DummyPinotControllerTransport.create(); + private DummyPinotClientTransport _dummyPinotClientTransport; + private DummyPinotControllerTransport _dummyPinotControllerTransport; + + @BeforeMethod + public void beforeTestMethod() { + _dummyPinotClientTransport = new DummyPinotClientTransport(); + _dummyPinotControllerTransport = DummyPinotControllerTransport.create(); + } @Test public void createStatementTest() @@ -36,6 +44,31 @@ public void createStatementTest() Assert.assertNotNull(statement); } + @Test + public void getMetaDataTest() + throws Exception { + try (PinotConnection pinotConnection = + new PinotConnection("dummy", _dummyPinotClientTransport, "dummy", _dummyPinotControllerTransport)) { + DatabaseMetaData metaData = pinotConnection.getMetaData(); + Assert.assertNotNull(metaData); + Assert.assertTrue(metaData.getDatabaseMajorVersion() > 0); + Assert.assertTrue(metaData.getDatabaseMinorVersion() >= 0); + Assert.assertEquals(metaData.getDatabaseProductName(), "APACHE_PINOT"); + Assert.assertEquals(metaData.getDriverName(), "APACHE_PINOT_DRIVER"); + Assert.assertNotNull(metaData.getConnection()); + } + } + + @Test + public void isClosedTest() + throws Exception { + PinotConnection pinotConnection = + new PinotConnection("dummy", _dummyPinotClientTransport, "dummy", _dummyPinotControllerTransport); + Assert.assertFalse(pinotConnection.isClosed()); + pinotConnection.close(); + Assert.assertTrue(pinotConnection.isClosed()); + } + @Test public void setUserAgentTest() throws Exception { diff --git a/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotDriverTest.java b/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotDriverTest.java index 36dccaa5e937..05691534c0b7 100644 --- a/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotDriverTest.java +++ b/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotDriverTest.java @@ -30,6 +30,8 @@ public class PinotDriverTest { static final String DB_URL = "jdbc:pinot://localhost:8000?controller=localhost:9000"; + static final String BAD_URL = "jdbc:someOtherDB://localhost:8000?controller=localhost:9000"; + static final String GOOD_URL_NO_CONNECTION = "jdbc:pinot://localhost:1111?controller=localhost:2222"; @Test(enabled = false) public void testDriver() @@ -59,4 +61,22 @@ public void testDriver() ; conn.close(); } + + @Test + public void testDriverBadURL() { + try { + DriverManager.getConnection(BAD_URL); + } catch (Exception e) { + Assert.assertTrue(e.getMessage().contains("No suitable driver found")); + } + } + + @Test + public void testDriverGoodURLNoConnection() { + try { + DriverManager.getConnection(GOOD_URL_NO_CONNECTION); + } catch (Exception e) { + Assert.assertTrue(e.getMessage().contains("Failed to connect to url")); + } + } } diff --git a/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotPreparedStatementTest.java b/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotPreparedStatementTest.java index 0cc203a99439..2b77206c550d 100644 --- a/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotPreparedStatementTest.java +++ b/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotPreparedStatementTest.java @@ -26,6 +26,8 @@ import java.util.Properties; import org.apache.commons.codec.binary.Hex; import org.apache.pinot.client.utils.DateTimeUtils; +import org.apache.pinot.client.utils.DriverUtils; +import org.apache.pinot.spi.utils.CommonConstants.Broker.Request.QueryOptionKey; import org.testng.Assert; import org.testng.annotations.Test; @@ -35,6 +37,7 @@ public class PinotPreparedStatementTest { "SELECT * FROM dummy WHERE name = ? and age = ? and score = ? and ts = ? and eligible = ? and sub_score = ?"; public static final String DATE_QUERY = "SELECT * FROM dummy WHERE date = ? and updated_at = ? and created_at = ?"; public static final String SINGLE_STRING_QUERY = "SELECT * FROM dummy WHERE value = ?"; + private static final String BASIC_TEST_QUERY = "SELECT * FROM dummy"; private DummyPinotClientTransport _dummyPinotClientTransport = new DummyPinotClientTransport(); private DummyPinotControllerTransport _dummyPinotControllerTransport = DummyPinotControllerTransport.create(); @@ -120,4 +123,32 @@ public void testSetAdditionalDataTypes() Assert.assertEquals(lastExecutedQuery.substring(0, lastExecutedQuery.indexOf("LIMIT")).trim(), String.format("SELECT * FROM dummy WHERE value = '%s'", Hex.encodeHexString(value.getBytes()))); } + + @Test + public void testSetEnableNullHandling() + throws Exception { + Properties props = new Properties(); + props.put(QueryOptionKey.ENABLE_NULL_HANDLING, "true"); + PinotConnection pinotConnection = + new PinotConnection(props, "dummy", _dummyPinotClientTransport, "dummy", _dummyPinotControllerTransport); + PreparedStatement preparedStatement = pinotConnection.prepareStatement(BASIC_TEST_QUERY); + preparedStatement.executeQuery(); + String expectedSql = + DriverUtils.createSetQueryOptionString(QueryOptionKey.ENABLE_NULL_HANDLING, true) + BASIC_TEST_QUERY; + Assert.assertEquals(_dummyPinotClientTransport.getLastQuery().substring(0, expectedSql.length()), expectedSql); + } + + @Test + public void testSetEnableNullHandling2() + throws Exception { + Properties props = new Properties(); + props.put(QueryOptionKey.ENABLE_NULL_HANDLING, "true"); + PinotConnection pinotConnection = + new PinotConnection(props, "dummy", _dummyPinotClientTransport, "dummy", _dummyPinotControllerTransport); + PreparedStatement preparedStatement = pinotConnection.prepareStatement(""); + preparedStatement.executeQuery(BASIC_TEST_QUERY); + String expectedSql = + DriverUtils.createSetQueryOptionString(QueryOptionKey.ENABLE_NULL_HANDLING, true) + BASIC_TEST_QUERY; + Assert.assertEquals(_dummyPinotClientTransport.getLastQuery().substring(0, expectedSql.length()), expectedSql); + } } diff --git a/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotResultSetTest.java b/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotResultSetTest.java index 7e92cc10400b..255d14d47087 100644 --- a/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotResultSetTest.java +++ b/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotResultSetTest.java @@ -19,11 +19,13 @@ package org.apache.pinot.client; import java.io.InputStream; +import java.math.BigDecimal; import java.sql.ResultSetMetaData; +import java.sql.SQLException; import java.util.Calendar; import java.util.Collections; import java.util.Date; -import java.util.concurrent.Future; +import java.util.concurrent.CompletableFuture; import org.apache.commons.io.IOUtils; import org.apache.pinot.client.utils.DateTimeUtils; import org.apache.pinot.spi.utils.JsonUtils; @@ -135,6 +137,27 @@ public void testFetchDates() } } + @Test + public void testFetchBigDecimals() + throws Exception { + ResultSetGroup resultSetGroup = getResultSet(TEST_RESULT_SET_RESOURCE); + ResultSet resultSet = resultSetGroup.getResultSet(0); + PinotResultSet pinotResultSet = new PinotResultSet(resultSet); + + int currentRow = 0; + while (pinotResultSet.next()) { + Assert.assertEquals(pinotResultSet.getBigDecimal(7), new BigDecimal(resultSet.getString(currentRow, 6))); + currentRow++; + } + + // test bad values and make sure exception is thrown + try { + pinotResultSet.getBigDecimal(6); + } catch (SQLException e) { + Assert.assertTrue(e.getMessage().contains("Unable to fetch BigDecimal value")); + } + } + @Test public void testFindColumn() throws Exception { @@ -160,6 +183,30 @@ public void testGetResultMetadata() } } + @Test + public void testGetCalculatedScale() { + PinotResultSet pinotResultSet = new PinotResultSet(); + int calculatedResult; + + calculatedResult = pinotResultSet.getCalculatedScale("1"); + Assert.assertEquals(calculatedResult, 0); + + calculatedResult = pinotResultSet.getCalculatedScale("1.0"); + Assert.assertEquals(calculatedResult, 1); + + calculatedResult = pinotResultSet.getCalculatedScale("1.2"); + Assert.assertEquals(calculatedResult, 1); + + calculatedResult = pinotResultSet.getCalculatedScale("1.23"); + Assert.assertEquals(calculatedResult, 2); + + calculatedResult = pinotResultSet.getCalculatedScale("1.234"); + Assert.assertEquals(calculatedResult, 3); + + calculatedResult = pinotResultSet.getCalculatedScale("-1.234"); + Assert.assertEquals(calculatedResult, 3); + } + private ResultSetGroup getResultSet(String resourceName) { _dummyJsonTransport._resource = resourceName; Connection connection = ConnectionFactory.fromHostList(Collections.singletonList("dummy"), _dummyJsonTransport); @@ -189,19 +236,7 @@ public BrokerResponse executeQuery(String brokerAddress, String query) } @Override - public Future executeQueryAsync(String brokerAddress, String query) - throws PinotClientException { - return null; - } - - @Override - public BrokerResponse executeQuery(String brokerAddress, Request request) - throws PinotClientException { - return executeQuery(brokerAddress, request.getQuery()); - } - - @Override - public Future executeQueryAsync(String brokerAddress, Request request) + public CompletableFuture executeQueryAsync(String brokerAddress, String query) throws PinotClientException { return null; } diff --git a/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotStatementTest.java b/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotStatementTest.java index db4e1dfbb0d5..fd47c6db9003 100644 --- a/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotStatementTest.java +++ b/pinot-clients/pinot-jdbc-client/src/test/java/org/apache/pinot/client/PinotStatementTest.java @@ -20,11 +20,15 @@ import java.sql.ResultSet; import java.sql.Statement; +import java.util.Properties; +import org.apache.pinot.client.utils.DriverUtils; +import org.apache.pinot.spi.utils.CommonConstants.Broker.Request.QueryOptionKey; import org.testng.Assert; import org.testng.annotations.Test; public class PinotStatementTest { + private static final String BASIC_TEST_QUERY = "SELECT * FROM dummy"; private DummyPinotClientTransport _dummyPinotClientTransport = new DummyPinotClientTransport(); private DummyPinotControllerTransport _dummyPinotControllerTransport = DummyPinotControllerTransport.create(); @@ -34,8 +38,116 @@ public void testExecuteQuery() PinotConnection connection = new PinotConnection("dummy", _dummyPinotClientTransport, "dummy", _dummyPinotControllerTransport); Statement statement = new PinotStatement(connection); - ResultSet resultSet = statement.executeQuery("select * from dummy"); + ResultSet resultSet = statement.executeQuery(BASIC_TEST_QUERY); Assert.assertNotNull(resultSet); Assert.assertEquals(statement.getConnection(), connection); } + + @Test + public void testSetEnableNullHandling() + throws Exception { + Properties props = new Properties(); + props.put(QueryOptionKey.ENABLE_NULL_HANDLING, "true"); + PinotConnection pinotConnection = + new PinotConnection(props, "dummy", _dummyPinotClientTransport, "dummy", _dummyPinotControllerTransport); + Statement statement = pinotConnection.createStatement(); + Assert.assertNotNull(statement); + statement.executeQuery(BASIC_TEST_QUERY); + String expectedSql = + DriverUtils.createSetQueryOptionString(QueryOptionKey.ENABLE_NULL_HANDLING, true) + BASIC_TEST_QUERY; + Assert.assertEquals(_dummyPinotClientTransport.getLastQuery().substring(0, expectedSql.length()), expectedSql); + } + + @Test + public void testSetDisableNullHandling() + throws Exception { + Properties props = new Properties(); + props.put(QueryOptionKey.ENABLE_NULL_HANDLING, "false"); + PinotConnection pinotConnection = + new PinotConnection(props, "dummy", _dummyPinotClientTransport, "dummy", _dummyPinotControllerTransport); + Statement statement = pinotConnection.createStatement(); + Assert.assertNotNull(statement); + statement.executeQuery(BASIC_TEST_QUERY); + String expectedSql = + DriverUtils.createSetQueryOptionString(QueryOptionKey.ENABLE_NULL_HANDLING, false) + BASIC_TEST_QUERY; + Assert.assertEquals(_dummyPinotClientTransport.getLastQuery().substring(0, expectedSql.length()), expectedSql); + } + + @Test + public void testPresetEnableNullHandling() + throws Exception { + Properties props = new Properties(); + props.put(QueryOptionKey.ENABLE_NULL_HANDLING, "true"); + PinotConnection pinotConnection = + new PinotConnection(props, "dummy", _dummyPinotClientTransport, "dummy", _dummyPinotControllerTransport); + Statement statement = pinotConnection.createStatement(); + Assert.assertNotNull(statement); + String presetSql = + DriverUtils.createSetQueryOptionString(QueryOptionKey.ENABLE_NULL_HANDLING, true) + BASIC_TEST_QUERY; + statement.executeQuery(presetSql); + Assert.assertEquals(_dummyPinotClientTransport.getLastQuery().substring(0, presetSql.length()), presetSql); + } + + @Test + public void testSetUseMultistageEngine() + throws Exception { + Properties props = new Properties(); + props.put(QueryOptionKey.USE_MULTISTAGE_ENGINE, "true"); + PinotConnection pinotConnection = + new PinotConnection(props, "dummy", _dummyPinotClientTransport, "dummy", _dummyPinotControllerTransport); + Statement statement = pinotConnection.createStatement(); + Assert.assertNotNull(statement); + statement.executeQuery(BASIC_TEST_QUERY); + String expectedSql = + DriverUtils.createSetQueryOptionString(QueryOptionKey.USE_MULTISTAGE_ENGINE, true) + BASIC_TEST_QUERY; + Assert.assertEquals(_dummyPinotClientTransport.getLastQuery().substring(0, expectedSql.length()), expectedSql); + } + + @Test + public void testSetMultipleQueryOptions() + throws Exception { + Properties props = new Properties(); + props.put(QueryOptionKey.ENABLE_NULL_HANDLING, "true"); + props.put(QueryOptionKey.USE_MULTISTAGE_ENGINE, "true"); + PinotConnection pinotConnection = + new PinotConnection(props, "dummy", _dummyPinotClientTransport, "dummy", _dummyPinotControllerTransport); + Statement statement = pinotConnection.createStatement(); + Assert.assertNotNull(statement); + statement.executeQuery(BASIC_TEST_QUERY); + String resultingQuery = _dummyPinotClientTransport.getLastQuery(); + Assert.assertTrue( + resultingQuery.contains(DriverUtils.createSetQueryOptionString(QueryOptionKey.ENABLE_NULL_HANDLING, true))); + Assert.assertTrue( + resultingQuery.contains(DriverUtils.createSetQueryOptionString(QueryOptionKey.USE_MULTISTAGE_ENGINE, true))); + } + + @Test + public void testSetOptionAsInteger() + throws Exception { + Properties props = new Properties(); + props.put(QueryOptionKey.USE_MULTISTAGE_ENGINE, "2"); + PinotConnection pinotConnection = + new PinotConnection(props, "dummy", _dummyPinotClientTransport, "dummy", _dummyPinotControllerTransport); + Statement statement = pinotConnection.createStatement(); + Assert.assertNotNull(statement); + statement.executeQuery(BASIC_TEST_QUERY); + String expectedSql = + DriverUtils.createSetQueryOptionString(QueryOptionKey.USE_MULTISTAGE_ENGINE, 2) + BASIC_TEST_QUERY; + Assert.assertEquals(_dummyPinotClientTransport.getLastQuery().substring(0, expectedSql.length()), expectedSql); + } + + @Test + public void testSetOptionAsFloat() + throws Exception { + Properties props = new Properties(); + props.put(QueryOptionKey.USE_MULTISTAGE_ENGINE, "2.5"); + PinotConnection pinotConnection = + new PinotConnection(props, "dummy", _dummyPinotClientTransport, "dummy", _dummyPinotControllerTransport); + Statement statement = pinotConnection.createStatement(); + Assert.assertNotNull(statement); + statement.executeQuery(BASIC_TEST_QUERY); + String expectedSql = + DriverUtils.createSetQueryOptionString(QueryOptionKey.USE_MULTISTAGE_ENGINE, 2.5) + BASIC_TEST_QUERY; + Assert.assertEquals(_dummyPinotClientTransport.getLastQuery().substring(0, expectedSql.length()), expectedSql); + } } diff --git a/pinot-clients/pinot-jdbc-client/src/test/resources/result_table.json b/pinot-clients/pinot-jdbc-client/src/test/resources/result_table.json index 0eedc13ec031..c158c0191b24 100644 --- a/pinot-clients/pinot-jdbc-client/src/test/resources/result_table.json +++ b/pinot-clients/pinot-jdbc-client/src/test/resources/result_table.json @@ -7,7 +7,8 @@ "columnC", "columnD", "columnE", - "columnF" + "columnF", + "columnG" ], "columnDataTypes": [ "INT", @@ -15,7 +16,8 @@ "FLOAT", "DOUBLE", "STRING", - "STRING" + "STRING", + "BIG_DECIMAL" ] }, "rows": [ @@ -25,7 +27,8 @@ 3432.3443, 2.2, "test", - "2020-01-02" + "2020-01-02", + 123 ], [ 2, @@ -33,7 +36,8 @@ 34323.342, 3743724.1, "jbdasnfb", - "2020-07-17" + "2020-07-17", + 123.4 ], [ 3, @@ -41,7 +45,8 @@ 4432.342, 27834.2121, "JHBNNB", - "2020-08-31" + "2020-08-31", + 123.45 ], [ 4, @@ -49,7 +54,8 @@ 653.342, 25452.5434432, "HHJkjkh", - "1999-06-30" + "1999-06-30", + 123.456 ], [ 5, @@ -57,7 +63,8 @@ 5412.1, 235235.23425, "ABCBD", - "2004-06-29" + "2004-06-29", + 123.4567 ], [ 6, @@ -65,7 +72,8 @@ 54543.3453, 6757.26775, "fuygad32748", - "2018-02-13" + "2018-02-13", + 123.45678 ], [ 7, @@ -73,7 +81,8 @@ 265.534, 345467.5667, "dyhudfa$%^&!#@^", - "2014-11-04" + "2014-11-04", + 123.456789 ], [ 8, @@ -81,7 +90,8 @@ 54.98, 87663.23456, "KHFGHJSF", - "2007-12-31" + "2007-12-31", + 123.4567891 ] ] }, diff --git a/pinot-clients/pom.xml b/pinot-clients/pom.xml index e591d0c25c6a..f8cb87c767b9 100644 --- a/pinot-clients/pom.xml +++ b/pinot-clients/pom.xml @@ -19,14 +19,12 @@ under the License. --> - + 4.0.0 pinot org.apache.pinot - 0.13.0-SNAPSHOT + 1.2.0 .. pinot-clients diff --git a/pinot-common/pom.xml b/pinot-common/pom.xml index 4315529ef98b..b9397cf847b8 100644 --- a/pinot-common/pom.xml +++ b/pinot-common/pom.xml @@ -19,13 +19,12 @@ under the License. --> - + 4.0.0 pinot org.apache.pinot - 0.13.0-SNAPSHOT + 1.2.0 .. pinot-common @@ -42,99 +41,67 @@ true - + - - maven-jar-plugin - - - - test-jar - - - - - - org.antlr - antlr4-maven-plugin - ${antlr.version} - - - antlr - - antlr4 - - - - org.apache.maven.plugins - maven-enforcer-plugin + maven-surefire-plugin + + + + usedefaultlisteners + false + + + + org.xolstice.maven.plugins protobuf-maven-plugin + com.diffplug.spotless spotless-maven-plugin - 2.28.0 - - - verify - - check - - - - - src/main/java/**/*.java - src/test/java/**/*.java - src/main/java/org/apache/pinot/common/request/*.java src/main/java/org/apache/pinot/common/response/ProcessingException.java - - ,\# - - + + + - - org.apache.maven.plugins - maven-dependency-plugin + maven-resources-plugin - unpack-parser-template - initialize + copy-fmpp-resources + generate-sources - unpack + copy-resources - - - org.apache.calcite - calcite-core - jar - true - ${project.build.directory}/ - **/Parser.jj,**/default_config.fmpp - - + ${project.build.directory}/codegen + + + src/main/codegen + false + + + - org.apache.pinot - pinot-fmpp-maven-plugin - ${project.version} + com.googlecode.fmpp-maven-plugin + fmpp-maven-plugin generate-fmpp-sources @@ -143,32 +110,14 @@ generate - ${project.basedir}/src/main/codegen/config.fmpp - ${project.build.directory}/generated-sources/fmpp - ${project.build.directory}/codegen/templates - tdd(${project.basedir}/src/main/codegen/config.fmpp), default:tdd(${project.build.directory}/codegen/default_config.fmpp) - - - - - - org.codehaus.mojo - build-helper-maven-plugin - - - add-generated-sources - process-sources - - add-source - - - - ${project.build.directory}/generated-sources/javacc - + ${project.build.directory}/codegen/config.fmpp + ${project.build.directory}/generated-sources + ${project.build.directory}/codegen/templates + org.codehaus.mojo javacc-maven-plugin @@ -180,13 +129,13 @@ javacc - ${project.build.directory}/generated-sources/fmpp + ${project.build.directory}/generated-sources/javacc - **/Parser.jj + Parser.jj 2 false - ${project.build.directory}/generated-sources/javacc + ${project.build.directory}/generated-sources @@ -214,46 +163,58 @@ org.apache.httpcomponents httpclient - - - commons-logging - commons-logging - - org.apache.httpcomponents httpcore - - org.antlr - antlr4-runtime - org.apache.calcite calcite-core - - - com.google.protobuf - protobuf-java - - org.apache.calcite calcite-babel - - - com.google.protobuf - protobuf-java - - + + - org.testng - testng - test + org.glassfish.jersey.core + jersey-server + + + org.glassfish.jersey.containers + jersey-container-grizzly2-http + + + org.glassfish.jersey.media + jersey-media-multipart + + + org.glassfish.jersey.media + jersey-media-json-jackson + + + org.glassfish.jersey.inject + jersey-hk2 + + org.glassfish.hk2 + hk2-metadata-generator + + + io.swagger + swagger-jersey2-jaxrs + + + org.webjars + swagger-ui + + + jakarta.servlet + jakarta.servlet-api + + com.google.guava guava @@ -276,7 +237,7 @@ org.apache.logging.log4j - log4j-slf4j-impl + log4j-slf4j2-impl org.apache.logging.log4j @@ -290,34 +251,6 @@ joda-time joda-time - - commons-configuration - commons-configuration - - - commons-logging - commons-logging - - - commons-lang - commons-lang - - - - - commons-httpclient - commons-httpclient - - - commons-logging - commons-logging - - - - - commons-io - commons-io - org.apache.commons commons-compress @@ -325,16 +258,6 @@ org.apache.helix helix-core - - - io.netty - netty - - - javax.xml.bind - jaxb-api - - it.unimi.dsi @@ -344,19 +267,6 @@ net.sf.jopt-simple jopt-simple - - nl.jqno.equalsverifier - equalsverifier - test - - - org.webjars - swagger-ui - - - com.google.code.findbugs - jsr305 - com.fasterxml.jackson.core jackson-annotations @@ -370,47 +280,13 @@ json-path - org.mockito - - mockito-inline - test - - - org.apache.commons - commons-math3 - provided - - - commons-logging - commons-logging - - - commons-lang - commons-lang + org.slf4j + jcl-over-slf4j org.apache.zookeeper zookeeper - - javax.servlet - javax.servlet-api - compile - - - org.glassfish.jersey.core - jersey-server - org.reflections reflections @@ -426,12 +302,6 @@ io.grpc grpc-protobuf - - - com.google.protobuf - protobuf-java - - io.grpc @@ -450,45 +320,37 @@ com.github.seancfoley ipaddress + + com.yscope.clp + clp-ffi + + + io.github.hakky54 + sslcontext-kickstart-for-netty + + + com.google.re2j + re2j + 1.7 + + + + org.testng + testng + test + + + org.mockito + mockito-core + test + + + nl.jqno.equalsverifier + equalsverifier + test + - - build-shaded-jar - - true - - - - - maven-shade-plugin - - - package - - shade - - - - - com.google.common.base - ${shade.prefix}.com.google.common.base - - - com.google.common.cache - ${shade.prefix}.com.google.common.cache - - - org.apache.http - ${shade.prefix}.org.apache.http - - - - - - - - - profile-buildthrift @@ -509,34 +371,35 @@ generate-sources generate-sources - - - + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - + + + + + run @@ -547,5 +410,15 @@ + + + build-shaded-jar + + true + + + package + + diff --git a/pinot-common/src/main/codegen/config.fmpp b/pinot-common/src/main/codegen/config.fmpp index e6955766bc70..aebbab93975e 100644 --- a/pinot-common/src/main/codegen/config.fmpp +++ b/pinot-common/src/main/codegen/config.fmpp @@ -17,7 +17,11 @@ # under the License. # +# Copied from Calcite 1.37.0 babel and modified for Pinot syntax. Update this file when upgrading Calcite version. + data: { + default: tdd("../default_config.fmpp") + # Data declarations for this parser. # # Default declarations are in default_config.fmpp; if you do not include a @@ -28,24 +32,28 @@ data: { package: "org.apache.pinot.sql.parsers.parser", class: "SqlParserImpl", - # List of import statements. + # List of additional classes and packages to import. + # Example: "org.apache.calcite.sql.*", "java.util.List". imports: [ - "com.google.common.collect.*" "org.apache.pinot.sql.parsers.parser.*" - "java.util.*" ] - # List of new keywords to add + # List of new keywords. Example: "DATABASES", "TABLES". If the keyword is + # not a reserved keyword, add it to the 'nonReservedKeywords' section. keywords: [ "FILE" "ARCHIVE" ] - # List of non-reserved keywords to add + # List of non-reserved keywords to add; + # items in this list become non-reserved nonReservedKeywordsToAdd: [ - # customized for Pinot "FILE" "ARCHIVE" + # Pinot allows using DEFAULT as the catalog name + "DEFAULT_" + # Pinot allows using DATETIME as column name + "DATETIME" # The following keywords are reserved in core Calcite, # are reserved in some version of SQL, @@ -132,6 +140,7 @@ data: { "CONSTRAINTS" "CONSTRUCTOR" "CONTAINS" + "CONTAINS_SUBSTR" "CONTINUE" "CONVERT" "CORR" @@ -159,12 +168,13 @@ data: { "CYCLE" "DATA" # "DATE" + "DATETIME_DIFF" "DAY" "DEALLOCATE" "DEC" "DECIMAL" "DECLARE" - "DEFAULT_" +# "DEFAULT" "DEFERRABLE" "DEFERRED" # "DEFINE" @@ -239,7 +249,6 @@ data: { "HOLD" "HOUR" "IDENTITY" -# "IF" # not a keyword in Calcite "ILIKE" "IMMEDIATE" "IMMEDIATELY" @@ -466,7 +475,11 @@ data: { "TEMPORARY" # "THEN" # "TIME" + "TIME_DIFF" + "TIME_TRUNC" # "TIMESTAMP" + "TIMESTAMP_DIFF" + "TIMESTAMP_TRUNC" "TIMEZONE_HOUR" "TIMEZONE_MINUTE" "TINYINT" @@ -523,20 +536,99 @@ data: { "ZONE" ] - # List of extended statement syntax to add + # List of non-reserved keywords to remove; + # items in this list become reserved. + nonReservedKeywordsToRemove: [ + ] + + # List of additional join types. Each is a method with no arguments. + # Example: "LeftSemiJoin". + joinTypes: [ + ] + + # List of methods for parsing custom SQL statements. + # Return type of method implementation should be 'SqlNode'. + # Example: "SqlShowDatabases()", "SqlShowTables()". statementParserMethods: [ "SqlInsertFromFile()" + "SqlPhysicalExplain()" + ] + + # List of methods for parsing custom literals. + # Return type of method implementation should be "SqlNode". + # Example: ParseJsonLiteral(). + literalParserMethods: [ + ] + + # List of methods for parsing custom data types. + # Return type of method implementation should be "SqlTypeNameSpec". + # Example: SqlParseTimeStampZ(). + dataTypeParserMethods: [ + ] + + # List of methods for parsing builtin function calls. + # Return type of method implementation should be "SqlNode". + # Example: "DateTimeConstructorCall()". + builtinFunctionCallMethods: [ ] - # List of custom function syntax to add + # List of methods for parsing extensions to "ALTER " calls. + # Each must accept arguments "(SqlParserPos pos, String scope)". + # Example: "SqlAlterTable". + alterStatementParserMethods: [ + ] + + # List of methods for parsing extensions to "CREATE [OR REPLACE]" calls. + # Each must accept arguments "(SqlParserPos pos, boolean replace)". + # Example: "SqlCreateForeignSchema". + createStatementParserMethods: [ + ] + + # List of methods for parsing extensions to "DROP" calls. + # Each must accept arguments "(SqlParserPos pos)". + # Example: "SqlDropSchema". + dropStatementParserMethods: [ + ] + + # List of methods for parsing extensions to "TRUNCATE" calls. + # Each must accept arguments "(SqlParserPos pos)". + # Example: "SqlTruncate". + truncateStatementParserMethods: [ + ] + + # Binary operators tokens. + # Example: "< INFIX_CAST: \"::\" >". + binaryOperatorsTokens: [ + ] + + # Binary operators initialization. + # Example: "InfixCast". extraBinaryExpressions: [ "SqlAtTimeZone" ] # List of files in @includes directory that have parser method + # implementations for parsing custom SQL statements, literals or types + # given as part of "statementParserMethods", "literalParserMethods" or + # "dataTypeParserMethods". + # Example: "parserImpls.ftl". implementationFiles: [ "parserImpls.ftl" - ], + ] + + # Custom identifier token. + # + # PostgreSQL allows letters with diacritical marks and non-Latin letters + # in the beginning of identifier and additionally dollar sign in the rest of identifier. + # Letters with diacritical marks and non-Latin letters + # are represented by character codes 128 to 255 (or in octal \200 to \377). + # See https://learn.microsoft.com/en-gb/office/vba/language/reference/user-interface-help/character-set-128255 + # See https://github.com/postgres/postgres/blob/master/src/backend/parser/scan.l + # + # MySQL allows digit in the beginning of identifier + customIdentifierToken: "< IDENTIFIER: (||[\"\\200\"-\"\\377\"]) (|||[\"\\200\"-\"\\377\"])* >" + + includeParsingStringLiteralAsArrayLiteral: true } } diff --git a/pinot-common/src/main/codegen/default_config.fmpp b/pinot-common/src/main/codegen/default_config.fmpp new file mode 100644 index 000000000000..31092dce926c --- /dev/null +++ b/pinot-common/src/main/codegen/default_config.fmpp @@ -0,0 +1,465 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +# Copied from Calcite 1.37.0 and modified for Pinot syntax. Update this file when upgrading Calcite version. + +# Default data declarations for parsers. +# Each of these may be overridden in a parser's config.fmpp file. +# In addition, each parser must define "package" and "class". +parser: { + # List of additional classes and packages to import. + # Example: "org.apache.calcite.sql.*", "java.util.List". + imports: [ + ] + + # List of new keywords. Example: "DATABASES", "TABLES". If the keyword is + # not a reserved keyword, add it to the 'nonReservedKeywords' section. + keywords: [ + ] + + # List of keywords from "keywords" section that are not reserved. + nonReservedKeywords: [ + "A" + "ABSENT" + "ABSOLUTE" + "ACTION" + "ADA" + "ADD" + "ADMIN" + "AFTER" + "ALWAYS" + "APPLY" + "ARRAY_AGG" + "ARRAY_CONCAT_AGG" + "ASC" + "ASSERTION" + "ASSIGNMENT" + "ATTRIBUTE" + "ATTRIBUTES" + "BEFORE" + "BERNOULLI" + "BREADTH" + "C" + "CASCADE" + "CATALOG" + "CATALOG_NAME" + "CENTURY" + "CHAIN" + "CHARACTERISTICS" + "CHARACTERS" + "CHARACTER_SET_CATALOG" + "CHARACTER_SET_NAME" + "CHARACTER_SET_SCHEMA" + "CLASS_ORIGIN" + "COBOL" + "COLLATION" + "COLLATION_CATALOG" + "COLLATION_NAME" + "COLLATION_SCHEMA" + "COLUMN_NAME" + "COMMAND_FUNCTION" + "COMMAND_FUNCTION_CODE" + "COMMITTED" + "CONDITIONAL" + "CONDITION_NUMBER" + "CONNECTION" + "CONNECTION_NAME" + "CONSTRAINT_CATALOG" + "CONSTRAINT_NAME" + "CONSTRAINTS" + "CONSTRAINT_SCHEMA" + "CONSTRUCTOR" + "CONTAINS_SUBSTR" + "CONTINUE" + "CURSOR_NAME" + "DATA" + "DATABASE" + "DATE_DIFF" + "DATE_TRUNC" + "DATETIME_DIFF" + "DATETIME_INTERVAL_CODE" + "DATETIME_INTERVAL_PRECISION" + "DATETIME_TRUNC" + "DAYOFWEEK" + "DAYOFYEAR" + "DAYS" + "DECADE" + "DEFAULTS" + "DEFERRABLE" + "DEFERRED" + "DEFINED" + "DEFINER" + "DEGREE" + "DEPTH" + "DERIVED" + "DESC" + "DESCRIPTION" + "DESCRIPTOR" + "DIAGNOSTICS" + "DISPATCH" + "DOMAIN" + "DOW" + "DOY" + "DOT_FORMAT" + "DYNAMIC_FUNCTION" + "DYNAMIC_FUNCTION_CODE" + "ENCODING" + "EPOCH" + "ERROR" + "EXCEPTION" + "EXCLUDE" + "EXCLUDING" + "FINAL" + "FIRST" + "FOLLOWING" + "FORMAT" + "FORTRAN" + "FOUND" + "FRAC_SECOND" + "G" + "GENERAL" + "GENERATED" + "GEOMETRY" + "GO" + "GOTO" + "GRANTED" + "GROUP_CONCAT" + "HIERARCHY" + "HOP" + "HOURS" + "IGNORE" + "ILIKE" + "IMMEDIATE" + "IMMEDIATELY" + "IMPLEMENTATION" + "INCLUDE" + "INCLUDING" + "INCREMENT" + "INITIALLY" + "INPUT" + "INSTANCE" + "INSTANTIABLE" + "INVOKER" + "ISODOW" + "ISOLATION" + "ISOYEAR" + "JAVA" + "JSON" + "K" + "KEY" + "KEY_MEMBER" + "KEY_TYPE" + "LABEL" + "LAST" + "LENGTH" + "LEVEL" + "LIBRARY" + "LOCATOR" + "M" + "MAP" + "MATCHED" + "MAXVALUE" + "MESSAGE_LENGTH" + "MESSAGE_OCTET_LENGTH" + "MESSAGE_TEXT" + "MICROSECOND" + "MILLENNIUM" + "MILLISECOND" + "MINUTES" + "MINVALUE" + "MONTHS" + "MORE_" + "MUMPS" + "NAME" + "NAMES" + "NANOSECOND" + "NESTING" + "NORMALIZED" + "NULLABLE" + "NULLS" + "NUMBER" + "OBJECT" + "OCTETS" + "OPTION" + "OPTIONS" + "ORDERING" + "ORDINALITY" + "OTHERS" + "OUTPUT" + "OVERRIDING" + "PAD" + "PARAMETER_MODE" + "PARAMETER_NAME" + "PARAMETER_ORDINAL_POSITION" + "PARAMETER_SPECIFIC_CATALOG" + "PARAMETER_SPECIFIC_NAME" + "PARAMETER_SPECIFIC_SCHEMA" + "PARTIAL" + "PASCAL" + "PASSING" + "PASSTHROUGH" + "PAST" + "PATH" + "PIVOT" + "PLACING" + "PLAN" + "PLI" + "PRECEDING" + "PRESERVE" + "PRIOR" + "PRIVILEGES" + "PUBLIC" + "QUARTER" + "QUARTERS" + "READ" + "RELATIVE" + "REPEATABLE" + "REPLACE" + "RESPECT" + "RESTART" + "RESTRICT" + "RETURNED_CARDINALITY" + "RETURNED_LENGTH" + "RETURNED_OCTET_LENGTH" + "RETURNED_SQLSTATE" + "RETURNING" + "RLIKE" + "ROLE" + "ROUTINE" + "ROUTINE_CATALOG" + "ROUTINE_NAME" + "ROUTINE_SCHEMA" + "ROW_COUNT" + "SCALAR" + "SCALE" + "SCHEMA" + "SCHEMA_NAME" + "SCOPE_CATALOGS" + "SCOPE_NAME" + "SCOPE_SCHEMA" + "SECONDS" + "SECTION" + "SECURITY" + "SELF" + "SEPARATOR" + "SEQUENCE" + "SERIALIZABLE" + "SERVER" + "SERVER_NAME" + "SESSION" + "SETS" + "SIMPLE" + "SIZE" + "SOURCE" + "SPACE" + "SPECIFIC_NAME" + "SQL_BIGINT" + "SQL_BINARY" + "SQL_BIT" + "SQL_BLOB" + "SQL_BOOLEAN" + "SQL_CHAR" + "SQL_CLOB" + "SQL_DATE" + "SQL_DECIMAL" + "SQL_DOUBLE" + "SQL_FLOAT" + "SQL_INTEGER" + "SQL_INTERVAL_DAY" + "SQL_INTERVAL_DAY_TO_HOUR" + "SQL_INTERVAL_DAY_TO_MINUTE" + "SQL_INTERVAL_DAY_TO_SECOND" + "SQL_INTERVAL_HOUR" + "SQL_INTERVAL_HOUR_TO_MINUTE" + "SQL_INTERVAL_HOUR_TO_SECOND" + "SQL_INTERVAL_MINUTE" + "SQL_INTERVAL_MINUTE_TO_SECOND" + "SQL_INTERVAL_MONTH" + "SQL_INTERVAL_SECOND" + "SQL_INTERVAL_YEAR" + "SQL_INTERVAL_YEAR_TO_MONTH" + "SQL_LONGVARBINARY" + "SQL_LONGVARCHAR" + "SQL_LONGVARNCHAR" + "SQL_NCHAR" + "SQL_NCLOB" + "SQL_NUMERIC" + "SQL_NVARCHAR" + "SQL_REAL" + "SQL_SMALLINT" + "SQL_TIME" + "SQL_TIMESTAMP" + "SQL_TINYINT" + "SQL_TSI_DAY" + "SQL_TSI_FRAC_SECOND" + "SQL_TSI_HOUR" + "SQL_TSI_MICROSECOND" + "SQL_TSI_MINUTE" + "SQL_TSI_MONTH" + "SQL_TSI_QUARTER" + "SQL_TSI_SECOND" + "SQL_TSI_WEEK" + "SQL_TSI_YEAR" + "SQL_VARBINARY" + "SQL_VARCHAR" + "STATE" + "STATEMENT" + "STRING_AGG" + "STRUCTURE" + "STYLE" + "SUBCLASS_ORIGIN" + "SUBSTITUTE" + "TABLE_NAME" + "TEMPORARY" + "TIES" + "TIME_DIFF" + "TIME_TRUNC" + "TIMESTAMPADD" + "TIMESTAMPDIFF" + "TIMESTAMP_DIFF" + "TIMESTAMP_TRUNC" + "TOP_LEVEL_COUNT" + "TRANSACTION" + "TRANSACTIONS_ACTIVE" + "TRANSACTIONS_COMMITTED" + "TRANSACTIONS_ROLLED_BACK" + "TRANSFORM" + "TRANSFORMS" + "TRIGGER_CATALOG" + "TRIGGER_NAME" + "TRIGGER_SCHEMA" + "TUMBLE" + "TYPE" + "UNBOUNDED" + "UNCOMMITTED" + "UNCONDITIONAL" + "UNDER" + "UNPIVOT" + "UNNAMED" + "USAGE" + "USER_DEFINED_TYPE_CATALOG" + "USER_DEFINED_TYPE_CODE" + "USER_DEFINED_TYPE_NAME" + "USER_DEFINED_TYPE_SCHEMA" + "UTF16" + "UTF32" + "UTF8" + "VERSION" + "VIEW" + "WEEK" + "WEEKS" + "WORK" + "WRAPPER" + "WRITE" + "XML" + "YEARS" + "ZONE" + ] + + # List of non-reserved keywords to add; + # items in this list become non-reserved. + nonReservedKeywordsToAdd: [ + ] + + # List of non-reserved keywords to remove; + # items in this list become reserved. + nonReservedKeywordsToRemove: [ + ] + + # List of additional join types. Each is a method with no arguments. + # Example: "LeftSemiJoin". + joinTypes: [ + ] + + # List of methods for parsing custom SQL statements. + # Return type of method implementation should be 'SqlNode'. + # Example: "SqlShowDatabases()", "SqlShowTables()". + statementParserMethods: [ + ] + + # List of methods for parsing custom literals. + # Return type of method implementation should be "SqlNode". + # Example: ParseJsonLiteral(). + literalParserMethods: [ + ] + + # List of methods for parsing custom data types. + # Return type of method implementation should be "SqlTypeNameSpec". + # Example: SqlParseTimeStampZ(). + dataTypeParserMethods: [ + ] + + # List of methods for parsing builtin function calls. + # Return type of method implementation should be "SqlNode". + # Example: "DateTimeConstructorCall()". + builtinFunctionCallMethods: [ + ] + + # List of methods for parsing extensions to "ALTER " calls. + # Each must accept arguments "(SqlParserPos pos, String scope)". + # Example: "SqlAlterTable". + alterStatementParserMethods: [ + ] + + # List of methods for parsing extensions to "CREATE [OR REPLACE]" calls. + # Each must accept arguments "(SqlParserPos pos, boolean replace)". + # Example: "SqlCreateForeignSchema". + createStatementParserMethods: [ + ] + + # List of methods for parsing extensions to "DROP" calls. + # Each must accept arguments "(SqlParserPos pos)". + # Example: "SqlDropSchema". + dropStatementParserMethods: [ + ] + + # List of methods for parsing extensions to "TRUNCATE" calls. + # Each must accept arguments "(SqlParserPos pos)". + # Example: "SqlTruncate". + truncateStatementParserMethods: [ + ] + + # Binary operators tokens. + # Example: "< INFIX_CAST: \"::\" >". + binaryOperatorsTokens: [ + ] + + # Binary operators initialization. + # Example: "InfixCast". + extraBinaryExpressions: [ + ] + + # List of files in @includes directory that have parser method + # implementations for parsing custom SQL statements, literals or types + # given as part of "statementParserMethods", "literalParserMethods" or + # "dataTypeParserMethods". + # Example: "parserImpls.ftl". + implementationFiles: [ + ] + + # Custom identifier token. + # Example: "< IDENTIFIER: (|)+ >". + customIdentifierToken: "" + + includePosixOperators: false + includeCompoundIdentifier: true + includeBraces: true + includeAdditionalDeclarations: false + includeParsingStringLiteralAsArrayLiteral: false +} diff --git a/pinot-common/src/main/codegen/includes/parserImpls.ftl b/pinot-common/src/main/codegen/includes/parserImpls.ftl index 449d8ab3b9a2..6e1283b075ba 100644 --- a/pinot-common/src/main/codegen/includes/parserImpls.ftl +++ b/pinot-common/src/main/codegen/includes/parserImpls.ftl @@ -33,7 +33,7 @@ private void DataFileDef(List list) : SqlNodeList DataFileDefList() : { SqlParserPos pos; - List list = Lists.newArrayList(); + List list = new ArrayList(); } { { pos = getPos(); } @@ -73,36 +73,6 @@ SqlInsertFromFile SqlInsertFromFile() : } } -/** - * define the rest of the sql into SqlStmtList - */ -private void SqlStatementList(SqlNodeList list) : -{ -} -{ - { - list.add(SqlStmt()); - } -} - -SqlNodeList SqlStmtsEof() : -{ - SqlParserPos pos; - SqlNodeList stmts; -} -{ - { - pos = getPos(); - stmts = new SqlNodeList(pos); - stmts.add(SqlStmt()); - } - ( LOOKAHEAD(2, SqlStmt()) SqlStatementList(stmts) )* - [ ] - { - return stmts; - } -} - void SqlAtTimeZone(List list, ExprContext exprContext, Span s) : { List list2; @@ -119,3 +89,23 @@ void SqlAtTimeZone(List list, ExprContext exprContext, Span s) : list.addAll(list2); } } + +SqlNode SqlPhysicalExplain() : +{ + SqlNode stmt; + SqlExplainLevel detailLevel = SqlExplainLevel.EXPPLAN_ATTRIBUTES; + SqlExplain.Depth depth = SqlExplain.Depth.PHYSICAL; + final SqlExplainFormat format = SqlExplainFormat.TEXT; +} +{ + + [ detailLevel = ExplainDetailLevel() ] + stmt = SqlQueryOrDml() { + return new SqlPhysicalExplain(getPos(), + stmt, + detailLevel.symbol(SqlParserPos.ZERO), + depth.symbol(SqlParserPos.ZERO), + format.symbol(SqlParserPos.ZERO), + nDynamicParams); + } +} diff --git a/pinot-common/src/main/codegen/templates/Parser.jj b/pinot-common/src/main/codegen/templates/Parser.jj new file mode 100644 index 000000000000..965a87b21db4 --- /dev/null +++ b/pinot-common/src/main/codegen/templates/Parser.jj @@ -0,0 +1,9219 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// Copied from Calcite 1.37.0 and modified for Pinot syntax. Update this file when upgrading Calcite version. +// Modified parts are marked with "PINOT CUSTOMIZATION START/END". + +<@pp.dropOutputFile /> + +<@pp.changeOutputFile name="javacc/Parser.jj" /> + +options { + STATIC = false; + IGNORE_CASE = true; + UNICODE_INPUT = true; +} + + +PARSER_BEGIN(${parser.class}) + +package ${parser.package}; + +<#list (parser.imports!default.parser.imports) as importStr> +import ${importStr}; + + +import org.apache.calcite.avatica.util.Casing; +import org.apache.calcite.avatica.util.TimeUnit; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.runtime.CalciteContextException; +import org.apache.calcite.sql.JoinConditionType; +import org.apache.calcite.sql.JoinType; +import org.apache.calcite.sql.SqlAlter; +import org.apache.calcite.sql.SqlBasicTypeNameSpec; +import org.apache.calcite.sql.SqlBinaryOperator; +import org.apache.calcite.sql.SqlCall; +import org.apache.calcite.sql.SqlCharStringLiteral; +import org.apache.calcite.sql.SqlCollation; +import org.apache.calcite.sql.SqlCollectionTypeNameSpec; +import org.apache.calcite.sql.SqlDataTypeSpec; +import org.apache.calcite.sql.SqlDelete; +import org.apache.calcite.sql.SqlDescribeSchema; +import org.apache.calcite.sql.SqlDescribeTable; +import org.apache.calcite.sql.SqlDynamicParam; +import org.apache.calcite.sql.SqlExplain; +import org.apache.calcite.sql.SqlExplainFormat; +import org.apache.calcite.sql.SqlExplainLevel; +import org.apache.calcite.sql.SqlFunction; +import org.apache.calcite.sql.SqlFunctionCategory; +import org.apache.calcite.sql.SqlHint; +import org.apache.calcite.sql.SqlIdentifier; +import org.apache.calcite.sql.SqlInsert; +import org.apache.calcite.sql.SqlInsertKeyword; +import org.apache.calcite.sql.SqlIntervalQualifier; +import org.apache.calcite.sql.SqlJdbcDataTypeName; +import org.apache.calcite.sql.SqlJdbcFunctionCall; +import org.apache.calcite.sql.SqlJoin; +import org.apache.calcite.sql.SqlJsonConstructorNullClause; +import org.apache.calcite.sql.SqlJsonEncoding; +import org.apache.calcite.sql.SqlJsonExistsErrorBehavior; +import org.apache.calcite.sql.SqlJsonEmptyOrError; +import org.apache.calcite.sql.SqlJsonQueryEmptyOrErrorBehavior; +import org.apache.calcite.sql.SqlJsonQueryWrapperBehavior; +import org.apache.calcite.sql.SqlJsonValueEmptyOrErrorBehavior; +import org.apache.calcite.sql.SqlJsonValueReturning; +import org.apache.calcite.sql.SqlKind; +import org.apache.calcite.sql.SqlLambda; +import org.apache.calcite.sql.SqlLiteral; +import org.apache.calcite.sql.SqlMatchRecognize; +import org.apache.calcite.sql.SqlMerge; +import org.apache.calcite.sql.SqlMapTypeNameSpec; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.SqlNodeList; +import org.apache.calcite.sql.SqlNumericLiteral; +import org.apache.calcite.sql.SqlOperator; +import org.apache.calcite.sql.SqlOrderBy; +import org.apache.calcite.sql.SqlPivot; +import org.apache.calcite.sql.SqlPostfixOperator; +import org.apache.calcite.sql.SqlPrefixOperator; +import org.apache.calcite.sql.SqlRowTypeNameSpec; +import org.apache.calcite.sql.SqlSampleSpec; +import org.apache.calcite.sql.SqlSelect; +import org.apache.calcite.sql.SqlSelectKeyword; +import org.apache.calcite.sql.SqlSetOption; +import org.apache.calcite.sql.SqlSnapshot; +import org.apache.calcite.sql.SqlTableRef; +import org.apache.calcite.sql.SqlTypeNameSpec; +import org.apache.calcite.sql.SqlUnnestOperator; +import org.apache.calcite.sql.SqlUnpivot; +import org.apache.calcite.sql.SqlUpdate; +import org.apache.calcite.sql.SqlUserDefinedTypeNameSpec; +import org.apache.calcite.sql.SqlUtil; +import org.apache.calcite.sql.SqlWindow; +import org.apache.calcite.sql.SqlWith; +import org.apache.calcite.sql.SqlWithItem; +import org.apache.calcite.sql.fun.SqlCase; +import org.apache.calcite.sql.fun.SqlInternalOperators; +import org.apache.calcite.sql.fun.SqlLibraryOperators; +import org.apache.calcite.sql.fun.SqlStdOperatorTable; +import org.apache.calcite.sql.fun.SqlTrimFunction; +import org.apache.calcite.sql.parser.Span; +import org.apache.calcite.sql.parser.SqlAbstractParserImpl; +import org.apache.calcite.sql.parser.SqlParseException; +import org.apache.calcite.sql.parser.SqlParser; +import org.apache.calcite.sql.parser.SqlParserImplFactory; +import org.apache.calcite.sql.parser.SqlParserPos; +import org.apache.calcite.sql.parser.SqlParserUtil; +import org.apache.calcite.sql.type.SqlTypeName; +import org.apache.calcite.sql.validate.SqlConformance; +import org.apache.calcite.sql.validate.SqlConformanceEnum; +import org.apache.calcite.util.Glossary; +import org.apache.calcite.util.Pair; +import org.apache.calcite.util.SourceStringReader; +import org.apache.calcite.util.Util; +import org.apache.calcite.util.trace.CalciteTrace; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import org.slf4j.Logger; + +import java.io.Reader; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import static org.apache.calcite.util.Static.RESOURCE; + +/** + * SQL parser, generated from Parser.jj by JavaCC. + * + *

The public wrapper for this parser is {@link SqlParser}. + */ +public class ${parser.class} extends SqlAbstractParserImpl +{ + private static final Logger LOGGER = CalciteTrace.getParserTracer(); + + // Can't use quoted literal because of a bug in how JavaCC translates + // backslash-backslash. + private static final char BACKSLASH = 0x5c; + private static final char DOUBLE_QUOTE = 0x22; + private static final String DQ = DOUBLE_QUOTE + ""; + private static final String DQDQ = DQ + DQ; + private static final SqlLiteral LITERAL_ZERO = + SqlLiteral.createExactNumeric("0", SqlParserPos.ZERO); + private static final SqlLiteral LITERAL_ONE = + SqlLiteral.createExactNumeric("1", SqlParserPos.ZERO); + private static final SqlLiteral LITERAL_MINUS_ONE = + SqlLiteral.createExactNumeric("-1", SqlParserPos.ZERO); + private static final BigDecimal ONE_HUNDRED = BigDecimal.valueOf(100L); + + private static Metadata metadata; + + private Casing unquotedCasing; + private Casing quotedCasing; + private int identifierMaxLength; + private SqlConformance conformance; + + /** + * {@link SqlParserImplFactory} implementation for creating parser. + */ + public static final SqlParserImplFactory FACTORY = new SqlParserImplFactory() { + public SqlAbstractParserImpl getParser(Reader reader) { + final ${parser.class} parser = new ${parser.class}(reader); + if (reader instanceof SourceStringReader) { + final String sql = + ((SourceStringReader) reader).getSourceString(); + parser.setOriginalSql(sql); + } + return parser; + } + }; + + public SqlParseException normalizeException(Throwable ex) { + try { + if (ex instanceof ParseException) { + ex = cleanupParseException((ParseException) ex); + } + return convertException(ex); + } catch (ParseException e) { + throw new AssertionError(e); + } + } + + public Metadata getMetadata() { + synchronized (${parser.class}.class) { + if (metadata == null) { + metadata = new MetadataImpl( + new ${parser.class}(new java.io.StringReader(""))); + } + return metadata; + } + } + + public void setTabSize(int tabSize) { + jj_input_stream.setTabSize(tabSize); + } + + public void switchTo(SqlAbstractParserImpl.LexicalState state) { + final int stateOrdinal = + Arrays.asList(${parser.class}TokenManager.lexStateNames) + .indexOf(state.name()); + token_source.SwitchTo(stateOrdinal); + } + + public void setQuotedCasing(Casing quotedCasing) { + this.quotedCasing = quotedCasing; + } + + public void setUnquotedCasing(Casing unquotedCasing) { + this.unquotedCasing = unquotedCasing; + } + + public void setIdentifierMaxLength(int identifierMaxLength) { + this.identifierMaxLength = identifierMaxLength; + } + + public void setConformance(SqlConformance conformance) { + this.conformance = conformance; + } + + public SqlNode parseSqlExpressionEof() throws Exception { + return SqlExpressionEof(); + } + + public SqlNode parseSqlStmtEof() throws Exception { + return SqlStmtEof(); + } + + public SqlNodeList parseSqlStmtList() throws Exception { + return SqlStmtList(); + } + + public SqlNode parseArray() throws SqlParseException { + switchTo(LexicalState.BQID); + try { + return ArrayLiteral(); + } catch (ParseException ex) { + throw normalizeException(ex); + } catch (TokenMgrError ex) { + throw normalizeException(ex); + } + } + + private SqlNode extend(SqlNode table, SqlNodeList extendList) { + return SqlStdOperatorTable.EXTEND.createCall( + Span.of(table, extendList).pos(), table, extendList); + } + + /** Adds a warning that a token such as "HOURS" was used, + * whereas the SQL standard only allows "HOUR". + * + *

Currently, we silently add an exception to a list of warnings. In + * future, we may have better compliance checking, for example a strict + * compliance mode that throws if any non-standard features are used. */ + private TimeUnit warn(TimeUnit timeUnit) throws ParseException { + final String token = getToken(0).image.toUpperCase(Locale.ROOT); + warnings.add( + SqlUtil.newContextException(getPos(), + RESOURCE.nonStandardFeatureUsed(token))); + return timeUnit; + } +} + +PARSER_END(${parser.class}) + + +/*************************************** + * Utility Codes for Semantic Analysis * + ***************************************/ + +/* For Debug */ +JAVACODE +void debug_message1() { + LOGGER.info("{} , {}", getToken(0).image, getToken(1).image); +} + +JAVACODE String unquotedIdentifier() { + return SqlParserUtil.toCase(getToken(0).image, unquotedCasing); +} + +/** + * Allows parser to be extended with new types of table references. The + * default implementation of this production is empty. + */ +SqlNode ExtendedTableRef() : +{ +} +{ + UnusedExtension() + { + return null; + } +} + +/** + * Allows an OVER clause following a table expression as an extension to + * standard SQL syntax. The default implementation of this production is empty. + */ +SqlNode TableOverOpt() : +{ +} +{ + { + return null; + } +} + +/* + * Parses dialect-specific keywords immediately following the SELECT keyword. + */ +void SqlSelectKeywords(List keywords) : +{} +{ + E() +} + +/* + * Parses dialect-specific keywords immediately following the INSERT keyword. + */ +void SqlInsertKeywords(List keywords) : +{} +{ + E() +} + +/* +* Parse Floor/Ceil function parameters +*/ +SqlNode FloorCeilOptions(Span s, boolean floorFlag) : +{ + SqlNode node; +} +{ + node = StandardFloorCeilOptions(s, floorFlag) { + return node; + } +} + +/* +// This file contains the heart of a parser for SQL SELECT statements. +// code can be shared between various parsers (for example, a DDL parser and a +// DML parser) but is not a standalone JavaCC file. You need to prepend a +// parser declaration (such as that in Parser.jj). +*/ + +/* Epsilon */ +JAVACODE +void E() {} + +/** @Deprecated */ +JAVACODE List startList(Object o) +{ + List list = new ArrayList(); + list.add(o); + return list; +} + +/* + * NOTE jvs 6-Feb-2004: The straightforward way to implement the SQL grammar is + * to keep query expressions (SELECT, UNION, etc) separate from row expressions + * (+, LIKE, etc). However, this is not possible with an LL(k) parser, because + * both kinds of expressions allow parenthesization, so no fixed amount of left + * context is ever good enough. A sub-query can be a leaf in a row expression, + * and can include operators like UNION, so it's not even possible to use a + * syntactic lookahead rule like "look past an indefinite number of parentheses + * until you see SELECT, VALUES, or TABLE" (since at that point we still + * don't know whether we're parsing a sub-query like ((select ...) + x) + * vs. (select ... union select ...). + * + * The somewhat messy solution is to unify the two kinds of expression, + * and to enforce syntax rules using parameterized context. This + * is the purpose of the ExprContext parameter. It is passed to + * most expression productions, which check the expressions encountered + * against the context for correctness. When a query + * element like SELECT is encountered, the production calls + * checkQueryExpression, which will throw an exception if + * a row expression was expected instead. When a row expression like + * IN is encountered, the production calls checkNonQueryExpression + * instead. It is very important to understand how this works + * when modifying the grammar. + * + * The commingling of expressions results in some bogus ambiguities which are + * resolved with LOOKAHEAD hints. The worst example is comma. SQL allows both + * (WHERE x IN (1,2)) and (WHERE x IN (select ...)). This means when we parse + * the right-hand-side of an IN, we have to allow any kind of expression inside + * the parentheses. Now consider the expression "WHERE x IN(SELECT a FROM b + * GROUP BY c,d)". When the parser gets to "c,d" it doesn't know whether the + * comma indicates the end of the GROUP BY or the end of one item in an IN + * list. Luckily, we know that select and comma-list are mutually exclusive + * within IN, so we use maximal munch for the GROUP BY comma. However, this + * usage of hints could easily mask unintended ambiguities resulting from + * future changes to the grammar, making it very brittle. + */ + +JAVACODE protected SqlParserPos getPos() +{ + return new SqlParserPos( + token.beginLine, + token.beginColumn, + token.endLine, + token.endColumn); +} + +/** Starts a span at the current position. */ +JAVACODE Span span() +{ + return Span.of(getPos()); +} + +JAVACODE void checkQueryExpression(ExprContext exprContext) +{ + switch (exprContext) { + case ACCEPT_NON_QUERY: + case ACCEPT_SUB_QUERY: + case ACCEPT_CURSOR: + throw SqlUtil.newContextException(getPos(), + RESOURCE.illegalQueryExpression()); + } +} + +JAVACODE void checkNonQueryExpression(ExprContext exprContext) +{ + switch (exprContext) { + case ACCEPT_QUERY: + throw SqlUtil.newContextException(getPos(), + RESOURCE.illegalNonQueryExpression()); + } +} + +JAVACODE SqlNode checkNotJoin(SqlNode e) +{ + if (e instanceof SqlJoin) { + throw SqlUtil.newContextException(e.getParserPosition(), + RESOURCE.illegalJoinExpression()); + } + return e; +} + +/** + * Converts a ParseException (local to this particular instantiation + * of the parser) into a SqlParseException (common to all parsers). + */ +JAVACODE SqlParseException convertException(Throwable ex) +{ + if (ex instanceof SqlParseException) { + return (SqlParseException) ex; + } + SqlParserPos pos = null; + int[][] expectedTokenSequences = null; + String[] tokenImage = null; + if (ex instanceof ParseException) { + ParseException pex = (ParseException) ex; + expectedTokenSequences = pex.expectedTokenSequences; + tokenImage = pex.tokenImage; + if (pex.currentToken != null) { + final Token token = pex.currentToken.next; + // Checks token.image.equals("1") to avoid recursive call. + // The SqlAbstractParserImpl#MetadataImpl constructor uses constant "1" to + // throw intentionally to collect the expected tokens. + if (!token.image.equals("1") + && getMetadata().isKeyword(token.image) + && SqlParserUtil.allowsIdentifier(tokenImage, expectedTokenSequences)) { + // If the next token is a keyword, reformat the error message as: + + // Incorrect syntax near the keyword '{keyword}' at line {line_number}, + // column {column_number}. + final String expecting = ex.getMessage() + .substring(ex.getMessage().indexOf("Was expecting")); + final String errorMsg = String.format("Incorrect syntax near the keyword '%s' " + + "at line %d, column %d.\n%s", + token.image, + token.beginLine, + token.beginColumn, + expecting); + // Replace the ParseException with explicit error message. + ex = new ParseException(errorMsg); + } + pos = new SqlParserPos( + token.beginLine, + token.beginColumn, + token.endLine, + token.endColumn); + } + } else if (ex instanceof TokenMgrError) { + expectedTokenSequences = null; + tokenImage = null; + // Example: + // Lexical error at line 3, column 24. Encountered "#" after "a". + final java.util.regex.Pattern pattern = java.util.regex.Pattern.compile( + "(?s)Lexical error at line ([0-9]+), column ([0-9]+).*"); + java.util.regex.Matcher matcher = pattern.matcher(ex.getMessage()); + if (matcher.matches()) { + int line = Integer.parseInt(matcher.group(1)); + int column = Integer.parseInt(matcher.group(2)); + pos = new SqlParserPos(line, column, line, column); + } + } else if (ex instanceof CalciteContextException) { + // CalciteContextException is the standard wrapper for exceptions + // produced by the validator, but in the parser, the standard is + // SqlParseException; so, strip it away. In case you were wondering, + // the CalciteContextException appears because the parser + // occasionally calls into validator-style code such as + // SqlSpecialOperator.reduceExpr. + CalciteContextException ece = + (CalciteContextException) ex; + pos = new SqlParserPos( + ece.getPosLine(), + ece.getPosColumn(), + ece.getEndPosLine(), + ece.getEndPosColumn()); + ex = ece.getCause(); + } + + return new SqlParseException( + ex.getMessage(), pos, expectedTokenSequences, tokenImage, ex); +} + +/** + * Removes or transforms misleading information from a parse exception. + * + * @param e dirty excn + * + * @return clean excn + */ +JAVACODE ParseException cleanupParseException(ParseException ex) +{ + if (ex.expectedTokenSequences == null) { + return ex; + } + int iIdentifier = Arrays.asList(ex.tokenImage).indexOf(""); + + // Find all sequences in the error which contain identifier. For + // example, + // {} + // {A} + // {B, C} + // {D, } + // {D, A} + // {D, B} + // + // would yield + // {} + // {D} + final List prefixList = new ArrayList(); + for (int i = 0; i < ex.expectedTokenSequences.length; ++i) { + int[] seq = ex.expectedTokenSequences[i]; + int j = seq.length - 1; + int i1 = seq[j]; + if (i1 == iIdentifier) { + int[] prefix = new int[j]; + System.arraycopy(seq, 0, prefix, 0, j); + prefixList.add(prefix); + } + } + + if (prefixList.isEmpty()) { + return ex; + } + + int[][] prefixes = (int[][]) + prefixList.toArray(new int[prefixList.size()][]); + + // Since was one of the possible productions, + // we know that the parser will also have included all + // of the non-reserved keywords (which are treated as + // identifiers in non-keyword contexts). So, now we need + // to clean those out, since they're totally irrelevant. + + final List list = new ArrayList(); + Metadata metadata = getMetadata(); + for (int i = 0; i < ex.expectedTokenSequences.length; ++i) { + int [] seq = ex.expectedTokenSequences[i]; + String tokenImage = ex.tokenImage[seq[seq.length - 1]]; + String token = SqlParserUtil.getTokenVal(tokenImage); + if (token == null || !metadata.isNonReservedKeyword(token)) { + list.add(seq); + continue; + } + boolean match = matchesPrefix(seq, prefixes); + if (!match) { + list.add(seq); + } + } + + ex.expectedTokenSequences = + (int [][]) list.toArray(new int [list.size()][]); + return ex; +} + +JAVACODE boolean matchesPrefix(int[] seq, int[][] prefixes) +{ + nextPrefix: + for (int[] prefix : prefixes) { + if (seq.length == prefix.length + 1) { + for (int k = 0; k < prefix.length; k++) { + if (prefix[k] != seq[k]) { + continue nextPrefix; + } + } + return true; + } + } + return false; +} + +/***************************************** + * Syntactical Descriptions * + *****************************************/ + +SqlNode ExprOrJoinOrOrderedQuery(ExprContext exprContext) : +{ + SqlNode e; + final List list = new ArrayList(); +} +{ + // Lookhead to distinguish between "TABLE emp" (which will be + // matched by ExplicitTable() via Query()) + // and "TABLE fun(args)" (which will be matched by TableRef()) + ( + LOOKAHEAD(2) + e = Query(exprContext) + e = OrderByLimitOpt(e) + { return e; } + | + e = TableRef1(ExprContext.ACCEPT_QUERY_OR_JOIN) + ( e = JoinTable(e) )* + { list.add(e); } + ( AddSetOpQuery(list, exprContext) )* + { return SqlParserUtil.toTree(list); } + ) +} + +/** + * Parses either a row expression or a query expression with an optional + * ORDER BY. + * + *

Postgres syntax for limit: + * + *

+ *    [ LIMIT { count | ALL } ]
+ *    [ OFFSET start ]
+ *
+ * + *

Trino syntax for limit: + * + *

+ *    [ OFFSET start ]
+ *    [ LIMIT { count | ALL } ]
+ *
+ * + *

MySQL syntax for limit: + * + *

+ *    [ LIMIT { count | start, count } ]
+ *
+ * + *

SQL:2008 syntax for limit: + * + *

+ *    [ OFFSET start { ROW | ROWS } ]
+ *    [ FETCH { FIRST | NEXT } [ count ] { ROW | ROWS } ONLY ]
+ *
+ */ +SqlNode OrderedQueryOrExpr(ExprContext exprContext) : +{ + SqlNode e; +} +{ + e = QueryOrExpr(exprContext) + e = OrderByLimitOpt(e) + { return e; } +} + +/** Reads optional "ORDER BY", "LIMIT", "OFFSET", "FETCH" following a query, + * {@code e}. If any of them are present, adds them to the query; + * otherwise returns the query unchanged. + * Throws if they are present and {@code e} is not a query. */ +SqlNode OrderByLimitOpt(SqlNode e) : +{ + final SqlNodeList orderBy; + final Span s = Span.of(); + SqlNode[] offsetFetch = {null, null}; +} +{ + ( + // use the syntactic type of the expression we just parsed + // to decide whether ORDER BY makes sense + orderBy = OrderBy(e.isA(SqlKind.QUERY)) + | { orderBy = null; } + ) + [ + LimitClause(s, offsetFetch) + [ OffsetClause(s, offsetFetch) ] + | + OffsetClause(s, offsetFetch) + [ + LimitClause(s, offsetFetch) { + if (!this.conformance.isOffsetLimitAllowed()) { + throw SqlUtil.newContextException(s.end(this), + RESOURCE.offsetLimitNotAllowed()); + } + } + | + FetchClause(offsetFetch) + ] + | + FetchClause(offsetFetch) + ] + { + if (orderBy != null || offsetFetch[0] != null || offsetFetch[1] != null) { + return new SqlOrderBy(getPos(), e, + Util.first(orderBy, SqlNodeList.EMPTY), + offsetFetch[0], offsetFetch[1]); + } + return e; + } +} + +/** + * Parses an OFFSET clause in an ORDER BY expression. + */ +void OffsetClause(Span s, SqlNode[] offsetFetch) : +{ +} +{ + // ROW or ROWS is required in SQL:2008 but we make it optional + // because it is not present in Postgres-style syntax. + { s.add(this); } + offsetFetch[0] = UnsignedNumericLiteralOrParam() + [ | ] +} + +/** + * Parses a FETCH clause in an ORDER BY expression. + */ +void FetchClause(SqlNode[] offsetFetch) : +{ +} +{ + // SQL:2008-style syntax. "OFFSET ... FETCH ...". + // If you specify both LIMIT and FETCH, FETCH wins. + ( | ) offsetFetch[1] = UnsignedNumericLiteralOrParam() + ( | ) +} + +/** + * Parses a LIMIT clause in an ORDER BY expression. + */ +void LimitClause(Span s, SqlNode[] offsetFetch) : +{ + final String error; +} +{ + // Postgres-style syntax. "LIMIT ... OFFSET ..." + { s.add(this); } + ( + // MySQL-style syntax. "LIMIT start, count" or "LIMIT start, ALL" + LOOKAHEAD(2) + offsetFetch[0] = UnsignedNumericLiteralOrParam() + + ( + offsetFetch[1] = UnsignedNumericLiteralOrParam() { + error = "count"; + } + | + { + error = "ALL"; + } + ) { + if (!this.conformance.isLimitStartCountAllowed()) { + throw SqlUtil.newContextException(s.end(this), + RESOURCE.limitStartCountOrAllNotAllowed(error)); + } + } + | + offsetFetch[1] = UnsignedNumericLiteralOrParam() + | + + ) +} + +/** + * Parses a leaf in a query expression (SELECT, VALUES or TABLE). + */ +SqlNode LeafQuery(ExprContext exprContext) : +{ + SqlNode e; +} +{ + { + // ensure a query is legal in this context + checkQueryExpression(exprContext); + } + e = SqlSelect() { return e; } +| + e = TableConstructor() { return e; } +| + e = ExplicitTable(getPos()) { return e; } +} + +/** + * Parses a parenthesized query or single row expression. + * Depending on {@code exprContext}, may also accept a join. + */ +SqlNode ParenthesizedExpression(ExprContext exprContext) : +{ + SqlNode e; +} +{ + + { + // we've now seen left paren, so queries inside should + // be allowed as sub-queries + switch (exprContext) { + case ACCEPT_SUB_QUERY: + exprContext = ExprContext.ACCEPT_NONCURSOR; + break; + case ACCEPT_CURSOR: + exprContext = ExprContext.ACCEPT_ALL; + break; + } + } + e = ExprOrJoinOrOrderedQuery(exprContext) + + { + exprContext.throwIfNotCompatible(e); + return e; + } +} + +/** + * Parses a parenthesized query or comma-list of row expressions. + * + *

REVIEW jvs 8-Feb-2004: There's a small hole in this production. It can be + * used to construct something like + * + *

+ * WHERE x IN (select count(*) from t where c=d,5)
+ *
+ * + *

which should be illegal. The above is interpreted as equivalent to + * + *

+ * WHERE x IN ((select count(*) from t where c=d),5)
+ *
+ * + *

which is a legal use of a sub-query. The only way to fix the hole is to + * be able to remember whether a subexpression was parenthesized or not, which + * means preserving parentheses in the SqlNode tree. This is probably + * desirable anyway for use in purely syntactic parsing applications (e.g. SQL + * pretty-printer). However, if this is done, it's important to also make + * isA() on the paren node call down to its operand so that we can + * always correctly discriminate a query from a row expression. + */ +SqlNodeList ParenthesizedQueryOrCommaList( + ExprContext exprContext) : +{ + SqlNode e; + final List list = new ArrayList(); + ExprContext firstExprContext = exprContext; + final Span s; +} +{ + + { + // we've now seen left paren, so a query by itself should + // be interpreted as a sub-query + s = span(); + switch (exprContext) { + case ACCEPT_SUB_QUERY: + firstExprContext = ExprContext.ACCEPT_NONCURSOR; + break; + case ACCEPT_CURSOR: + firstExprContext = ExprContext.ACCEPT_ALL; + break; + } + } + e = OrderedQueryOrExpr(firstExprContext) { list.add(e); } + ( + + { + // a comma-list can't appear where only a query is expected + checkNonQueryExpression(exprContext); + } + AddExpression(list, exprContext) + )* + + { + return new SqlNodeList(list, s.end(this)); + } +} + +/** As ParenthesizedQueryOrCommaList, but allows DEFAULT + * in place of any of the expressions. For example, + * {@code (x, DEFAULT, null, DEFAULT)}. */ +SqlNodeList ParenthesizedQueryOrCommaListWithDefault( + ExprContext exprContext) : +{ + SqlNode e; + final List list = new ArrayList(); + ExprContext firstExprContext = exprContext; + final Span s; +} +{ + + { + // we've now seen left paren, so a query by itself should + // be interpreted as a sub-query + s = span(); + switch (exprContext) { + case ACCEPT_SUB_QUERY: + firstExprContext = ExprContext.ACCEPT_NONCURSOR; + break; + case ACCEPT_CURSOR: + firstExprContext = ExprContext.ACCEPT_ALL; + break; + } + } + ( + e = OrderedQueryOrExpr(firstExprContext) { list.add(e); } + | + e = Default() { list.add(e); } + ) + ( + + { + // a comma-list can't appear where only a query is expected + checkNonQueryExpression(exprContext); + } + ( + e = Expression(exprContext) { list.add(e); } + | + e = Default() { list.add(e); } + ) + )* + + { + return new SqlNodeList(list, s.end(this)); + } +} + +/** + * Parses function parameter lists. + * If the list starts with DISTINCT or ALL, it is discarded. + */ +List UnquantifiedFunctionParameterList(ExprContext exprContext) : +{ + final List args; +} +{ + args = FunctionParameterList(exprContext) { + args.remove(0); // remove DISTINCT or ALL, if present + return args; + } +} + +/** + * Parses function parameter lists including DISTINCT keyword recognition, + * DEFAULT, and named argument assignment. + */ +List FunctionParameterList(ExprContext exprContext) : +{ + final SqlLiteral qualifier; + final List list = new ArrayList(); +} +{ + + ( + qualifier = AllOrDistinct() { list.add(qualifier); } + | + { list.add(null); } + ) + AddArg0(list, exprContext) + ( + { + // a comma-list can't appear where only a query is expected + checkNonQueryExpression(exprContext); + } + AddArg(list, exprContext) + )* + + { + return list; + } +} + +SqlLiteral AllOrDistinct() : +{ +} +{ + { return SqlSelectKeyword.DISTINCT.symbol(getPos()); } +| + { return SqlSelectKeyword.ALL.symbol(getPos()); } +} + +void AddArg0(List list, ExprContext exprContext) : +{ + final SqlIdentifier name; + SqlNode e; + final ExprContext firstExprContext; + { + // we've now seen left paren, so queries inside should + // be allowed as sub-queries + switch (exprContext) { + case ACCEPT_SUB_QUERY: + firstExprContext = ExprContext.ACCEPT_NONCURSOR; + break; + case ACCEPT_CURSOR: + firstExprContext = ExprContext.ACCEPT_ALL; + break; + default: + firstExprContext = exprContext; + break; + } + } +} +{ + ( + LOOKAHEAD(2) name = SimpleIdentifier() + | { name = null; } + ) + ( + e = Default() + | + LOOKAHEAD((SimpleIdentifierOrList() | ) ) + e = LambdaExpression() + | + LOOKAHEAD(3) + e = TableParam() + | + e = PartitionedQueryOrQueryOrExpr(firstExprContext) + ) + { + if (name != null) { + e = SqlStdOperatorTable.ARGUMENT_ASSIGNMENT.createCall( + Span.of(name, e).pos(), e, name); + } + list.add(e); + } +} + +void AddArg(List list, ExprContext exprContext) : +{ + final SqlIdentifier name; + SqlNode e; +} +{ + ( + LOOKAHEAD(2) name = SimpleIdentifier() + | { name = null; } + ) + ( + e = Default() + | + LOOKAHEAD((SimpleIdentifierOrList() | ) ) + e = LambdaExpression() + | + e = Expression(exprContext) + | + e = TableParam() + ) + { + if (name != null) { + e = SqlStdOperatorTable.ARGUMENT_ASSIGNMENT.createCall( + Span.of(name, e).pos(), e, name); + } + list.add(e); + } +} + +SqlNode Default() : {} +{ + { + return SqlStdOperatorTable.DEFAULT.createCall(getPos()); + } +} + +/** + * Parses a query (SELECT, UNION, INTERSECT, EXCEPT, VALUES, TABLE) followed by + * the end-of-file symbol. + */ +SqlNode SqlQueryEof() : +{ + SqlNode query; +} +{ + query = OrderedQueryOrExpr(ExprContext.ACCEPT_QUERY) + + { return query; } +} + +/** + * Parses a list of SQL statements separated by semicolon. + * The semicolon is required between statements, but is + * optional at the end. + */ +SqlNodeList SqlStmtList() : +{ + final List stmtList = new ArrayList(); + SqlNode stmt; +} +{ + stmt = SqlStmt() { + stmtList.add(stmt); + } + ( + + [ + stmt = SqlStmt() { + stmtList.add(stmt); + } + ] + )* + + { + return new SqlNodeList(stmtList, Span.of(stmtList).pos()); + } +} + +/** + * Parses an SQL statement. + */ +SqlNode SqlStmt() : +{ + SqlNode stmt; +} +{ + ( +<#-- Add methods to parse additional statements here --> +<#list (parser.statementParserMethods!default.parser.statementParserMethods) as method> + LOOKAHEAD(2) stmt = ${method} + | + + stmt = SqlSetOption(Span.of(), null) + | + stmt = SqlAlter() + | +<#if (parser.createStatementParserMethods!default.parser.createStatementParserMethods)?size != 0> + stmt = SqlCreate() + | + +<#if (parser.dropStatementParserMethods!default.parser.dropStatementParserMethods)?size != 0> + stmt = SqlDrop() + | + +<#if (parser.truncateStatementParserMethods!default.parser.truncateStatementParserMethods)?size != 0> + LOOKAHEAD(2) + stmt = SqlTruncate() + | + + stmt = OrderedQueryOrExpr(ExprContext.ACCEPT_QUERY) + | + stmt = SqlExplain() + | + stmt = SqlDescribe() + | + stmt = SqlInsert() + | + stmt = SqlDelete() + | + stmt = SqlUpdate() + | + stmt = SqlMerge() + | + stmt = SqlProcedureCall() + ) + { + return stmt; + } +} + +/** + * Parses an SQL statement followed by the end-of-file symbol. + */ +SqlNode SqlStmtEof() : +{ + SqlNode stmt; +} +{ + stmt = SqlStmt() + { + return stmt; + } +} + +<#-- Add implementations of additional parser statement calls here --> +<#list (parser.implementationFiles!default.parser.implementationFiles) as file> + <#include "/@includes/"+file /> + + +SqlNodeList ParenthesizedKeyValueOptionCommaList() : +{ + final Span s; + final List list = new ArrayList(); +} +{ + { s = span(); } + + AddKeyValueOption(list) + ( + + AddKeyValueOption(list) + )* + { + return new SqlNodeList(list, s.end(this)); + } +} + +/** +* Parses an option with format key=val whose key is a simple identifier or string literal +* and value is a string literal. +*/ +void AddKeyValueOption(List list) : +{ + final SqlNode key; + final SqlNode value; +} +{ + ( + key = SimpleIdentifier() + | + key = StringLiteral() + ) + + value = StringLiteral() { + list.add(key); + list.add(value); + } +} + +/** Parses an option value (either a string or a numeric) and adds to a list. */ +void AddOptionValue(List list) : +{ + final SqlNode value; +} +{ + ( + value = NumericLiteral() { list.add(value); } + | + value = StringLiteral() { list.add(value); } + ) +} + +/** + * Parses a literal list separated by comma. The literal is either a string or a numeric. + */ +SqlNodeList ParenthesizedLiteralOptionCommaList() : +{ + final Span s; + final List list = new ArrayList(); +} +{ + { s = span(); } + + AddOptionValue(list) ( AddOptionValue(list) )* + { + return new SqlNodeList(list, s.end(this)); + } +} + +void AddHint(List hints) : +{ + final SqlIdentifier hintName; + final SqlNodeList hintOptions; + final SqlHint.HintOptionFormat optionFormat; +} +{ + hintName = SimpleIdentifier() + ( + LOOKAHEAD(5) + hintOptions = ParenthesizedKeyValueOptionCommaList() { + optionFormat = SqlHint.HintOptionFormat.KV_LIST; + } + | + LOOKAHEAD(3) + hintOptions = ParenthesizedSimpleIdentifierList() { + optionFormat = SqlHint.HintOptionFormat.ID_LIST; + } + | + LOOKAHEAD(3) + hintOptions = ParenthesizedLiteralOptionCommaList() { + optionFormat = SqlHint.HintOptionFormat.LITERAL_LIST; + } + | + LOOKAHEAD(2) + [ ] + { + hintOptions = SqlNodeList.EMPTY; + optionFormat = SqlHint.HintOptionFormat.EMPTY; + } + ) + { + hints.add( + new SqlHint(Span.of(hintOptions).end(this), hintName, hintOptions, + optionFormat)); + } +} + +/** Parses hints following a table reference, + * and returns the wrapped table reference. */ +SqlNode TableHints(SqlIdentifier tableName) : +{ + final List hints = new ArrayList(); +} +{ + AddHint(hints) ( AddHint(hints) )* { + final SqlParserPos pos = Span.of(tableName).addAll(hints).end(this); + final SqlNodeList hintList = new SqlNodeList(hints, pos); + return new SqlTableRef(pos, tableName, hintList); + } +} + +/** + * Parses a leaf SELECT expression without ORDER BY. + */ +SqlSelect SqlSelect() : +{ + final List keywords = new ArrayList(); + final SqlLiteral keyword; + final SqlNodeList keywordList; + final List selectList = new ArrayList(); + final SqlNode fromClause; + final SqlNode where; + final SqlNodeList groupBy; + final SqlNode having; + final SqlNodeList windowDecls; + final SqlNode qualify; + final List hints = new ArrayList(); + final Span s; +} +{ +

+ | + | + | + | + | + | ) + (
)? + table = CompoundIdentifier() + ( column = SimpleIdentifier() | { column = null; } ) + { + return new SqlDescribeTable(s.add(table).addIf(column).pos(), + table, column); + } + | + (LOOKAHEAD(1) )? + stmt = SqlQueryOrDml() { + // DESCRIBE STATEMENT currently does the same as EXPLAIN. See + // [CALCITE-1221] Implement DESCRIBE DATABASE, CATALOG, STATEMENT + final SqlExplainLevel detailLevel = SqlExplainLevel.EXPPLAN_ATTRIBUTES; + final SqlExplain.Depth depth = SqlExplain.Depth.PHYSICAL; + final SqlExplainFormat format = SqlExplainFormat.TEXT; + return new SqlExplain(s.end(stmt), + stmt, + detailLevel.symbol(SqlParserPos.ZERO), + depth.symbol(SqlParserPos.ZERO), + format.symbol(SqlParserPos.ZERO), + nDynamicParams); + } + ) +} + +/** + * Parses a CALL statement. + */ +SqlNode SqlProcedureCall() : +{ + final Span s; + SqlNode routineCall; +} +{ + { + s = span(); + } + routineCall = NamedRoutineCall( + SqlFunctionCategory.USER_DEFINED_PROCEDURE, + ExprContext.ACCEPT_SUB_QUERY) + { + return SqlStdOperatorTable.PROCEDURE_CALL.createCall( + s.end(routineCall), routineCall); + } +} + +SqlNode NamedRoutineCall( + SqlFunctionCategory routineType, + ExprContext exprContext) : +{ + final SqlIdentifier name; + final List list = new ArrayList(); + final Span s; +} +{ + name = CompoundIdentifier() { + s = span(); + } + + [ + AddArg0(list, exprContext) + ( + { + // a comma-list can't appear where only a query is expected + checkNonQueryExpression(exprContext); + } + AddArg(list, exprContext) + )* + ] + + { + return createCall(name, s.end(this), routineType, null, list); + } +} + +/** + * Table parameter of a table function. + * The input table with set semantics may be partitioned/ordered on one or more columns. + */ +SqlNode TableParam() : +{ + final Span s; + final SqlNodeList partitionList; + final SqlNodeList orderList; + SqlNode tableRef; +} +{ + { s = span(); } + tableRef = ExplicitTable(getPos()) + ( + + partitionList = SimpleIdentifierOrList() + | { partitionList = SqlNodeList.EMPTY; } + ) + ( + orderList = OrderByOfSetSemanticsTable() + | { orderList = SqlNodeList.EMPTY; } + ) + { return CreateSetSemanticsTableIfNeeded(s, tableRef, partitionList, orderList); } +} + +SqlNode PartitionedQueryOrQueryOrExpr(ExprContext exprContext) : +{ + SqlNode e; +} +{ + e = OrderedQueryOrExpr(exprContext) + e = PartitionedByAndOrderBy(e) + + { return e; } +} + +SqlNode PartitionedByAndOrderBy(SqlNode e) : +{ + final Span s; + final SqlNodeList partitionList; + final SqlNodeList orderList; +} +{ + { s = span(); } + ( + + partitionList = SimpleIdentifierOrList() + | { partitionList = SqlNodeList.EMPTY; } + ) + ( + orderList = OrderByOfSetSemanticsTable() + | { orderList = SqlNodeList.EMPTY; } + ) + { return CreateSetSemanticsTableIfNeeded(s, e, partitionList, orderList); } +} + +SqlNodeList OrderByOfSetSemanticsTable() : +{ + final List list = new ArrayList(); + final Span s; +} +{ + + { s = span(); } + + ( + LOOKAHEAD(2) + AddOrderItem(list) + ( + // NOTE jvs 6-Feb-2004: See comments at top of file for why + // hint is necessary here. + LOOKAHEAD(2) AddOrderItem(list) + )* + { + return new SqlNodeList(list, s.addAll(list).pos()); + } + | + AddOrderItem(list) + { + return new SqlNodeList(list, s.addAll(list).pos()); + } + ) +} + +SqlNode CreateSetSemanticsTableIfNeeded( + final Span s, + final SqlNode e, + final SqlNodeList partitionList, + final SqlNodeList orderList) : +{ + +} +{ + + { + if (partitionList.isEmpty() && orderList.isEmpty()) { + return e; + } else { + return SqlStdOperatorTable.SET_SEMANTICS_TABLE.createCall( + s.pos(), e, partitionList, orderList); + } + } +} + +/** + * Parses an INSERT statement. + */ +SqlNode SqlInsert() : +{ + final List keywords = new ArrayList(); + final SqlNodeList keywordList; + final SqlIdentifier tableName; + SqlNode tableRef; + SqlNode source; + final SqlNodeList columnList; + final Span s; + final Pair p; +} +{ + ( + + | + { keywords.add(SqlInsertKeyword.UPSERT.symbol(getPos())); } + ) + { s = span(); } + SqlInsertKeywords(keywords) { + keywordList = new SqlNodeList(keywords, s.addAll(keywords).pos()); + } + tableName = CompoundTableIdentifier() + ( tableRef = TableHints(tableName) | { tableRef = tableName; } ) + [ LOOKAHEAD(5) tableRef = ExtendTable(tableRef) ] + ( + LOOKAHEAD(2) + p = ParenthesizedCompoundIdentifierList() { + if (p.right.size() > 0) { + tableRef = extend(tableRef, p.right); + } + if (p.left.size() > 0) { + columnList = p.left; + } else { + columnList = null; + } + } + | { columnList = null; } + ) + source = OrderedQueryOrExpr(ExprContext.ACCEPT_QUERY) { + return new SqlInsert(s.end(source), keywordList, tableRef, source, + columnList); + } +} + +/* + * Abstract production: + * + * void SqlInsertKeywords(List keywords) + * + * Parses dialect-specific keywords immediately following the INSERT keyword. + */ + +/** + * Parses a DELETE statement. + */ +SqlNode SqlDelete() : +{ + final SqlIdentifier tableName; + SqlNode tableRef; + final SqlIdentifier alias; + final SqlNode where; + final Span s; +} +{ + { + s = span(); + } + tableName = CompoundTableIdentifier() + ( tableRef = TableHints(tableName) | { tableRef = tableName; } ) + [ tableRef = ExtendTable(tableRef) ] + ( [ ] alias = SimpleIdentifier() | { alias = null; } ) + ( where = Where() | { where = null; } ) + { + return new SqlDelete(s.add(tableRef).addIf(alias).addIf(where).pos(), + tableRef, where, null, alias); + } +} + +/** + * Parses an UPDATE statement. + */ +SqlNode SqlUpdate() : +{ + final SqlIdentifier tableName; + SqlNode tableRef; + final SqlIdentifier alias; + final SqlNode where; + final SqlNodeList sourceExpressionList; + final SqlNodeList targetColumnList; + SqlIdentifier id; + final Span s; +} +{ + { + s = span(); + targetColumnList = new SqlNodeList(s.pos()); + sourceExpressionList = new SqlNodeList(s.pos()); + } + tableName = CompoundTableIdentifier() + ( tableRef = TableHints(tableName) | { tableRef = tableName; } ) + [ tableRef = ExtendTable(tableRef) ] + ( [ ] alias = SimpleIdentifier() | { alias = null; } ) + id = SimpleIdentifier() { + targetColumnList.add(id); + } + // TODO: support DEFAULT also + AddExpression(sourceExpressionList, ExprContext.ACCEPT_SUB_QUERY) + ( + + id = SimpleIdentifier() { targetColumnList.add(id); } + AddExpression(sourceExpressionList, ExprContext.ACCEPT_SUB_QUERY) + )* + ( where = Where() | { where = null; } ) + { + final SqlParserPos pos = s.addAll(targetColumnList) + .addAll(sourceExpressionList).addIf(where).pos(); + return new SqlUpdate(pos, tableRef, targetColumnList, + sourceExpressionList, where, null, alias); + } +} + +/** + * Parses a MERGE statement. + */ +SqlNode SqlMerge() : +{ + final SqlIdentifier tableName; + SqlNode tableRef; + final SqlIdentifier alias; + final SqlNode sourceTableRef; + final SqlNode condition; + final SqlUpdate updateCall; + final SqlInsert insertCall; + final Span s; +} +{ + { s = span(); } tableName = CompoundTableIdentifier() + ( tableRef = TableHints(tableName) | { tableRef = tableName; } ) + [ tableRef = ExtendTable(tableRef) ] + ( [ ] alias = SimpleIdentifier() | { alias = null; } ) + sourceTableRef = TableRef() + condition = Expression(ExprContext.ACCEPT_SUB_QUERY) + ( + LOOKAHEAD(2) + updateCall = WhenMatchedClause(tableRef, alias) + ( insertCall = WhenNotMatchedClause(tableRef) | { insertCall = null; } ) + | + { updateCall = null; } + insertCall = WhenNotMatchedClause(tableRef) + ) + { + final SqlParserPos pos = s.addIf(updateCall).addIf(insertCall).pos(); + return new SqlMerge(pos, tableRef, condition, sourceTableRef, + updateCall, insertCall, null, alias); + } +} + +SqlUpdate WhenMatchedClause(SqlNode table, SqlIdentifier alias) : +{ + SqlIdentifier id; + final Span s; + final SqlNodeList updateColumnList = new SqlNodeList(SqlParserPos.ZERO); + final SqlNodeList updateExprList = new SqlNodeList(SqlParserPos.ZERO); +} +{ + { s = span(); } + id = CompoundIdentifier() { + updateColumnList.add(id); + } + AddExpression(updateExprList, ExprContext.ACCEPT_SUB_QUERY) + ( + + id = CompoundIdentifier() { + updateColumnList.add(id); + } + AddExpression(updateExprList, ExprContext.ACCEPT_SUB_QUERY) + )* + { + return new SqlUpdate(s.addAll(updateExprList).pos(), table, + updateColumnList, updateExprList, null, null, alias); + } +} + +SqlInsert WhenNotMatchedClause(SqlNode table) : +{ + final Span insertSpan, valuesSpan; + final List keywords = new ArrayList(); + final SqlNodeList keywordList; + final SqlNodeList insertColumnList; + SqlNode rowConstructor; + SqlNode insertValues; +} +{ + { + insertSpan = span(); + } + SqlInsertKeywords(keywords) { + keywordList = new SqlNodeList(keywords, insertSpan.end(this)); + } + ( + LOOKAHEAD(2) + insertColumnList = ParenthesizedSimpleIdentifierList() + | { insertColumnList = null; } + ) + ( + + { valuesSpan = span(); } rowConstructor = RowConstructor() + + | + { valuesSpan = span(); } rowConstructor = RowConstructor() + ) + { + // TODO zfong 5/26/06: note that extra parentheses are accepted above + // around the VALUES clause as a hack for unparse, but this is + // actually invalid SQL; should fix unparse + insertValues = SqlStdOperatorTable.VALUES.createCall( + valuesSpan.end(this), rowConstructor); + return new SqlInsert(insertSpan.end(this), keywordList, + table, insertValues, insertColumnList); + } +} + +/** + * Parses one item in a select list. + */ +void AddSelectItem(List list) : +{ + final SqlNode e; + final SqlIdentifier id; +} +{ + e = SelectExpression() + ( + [ ] + ( + id = SimpleIdentifier() + | + // Mute the warning about ambiguity between alias and continued + // string literal. + LOOKAHEAD(1) + id = SimpleIdentifierFromStringLiteral() + ) + { list.add(SqlStdOperatorTable.AS.createCall(span().end(e), e, id)); } + | { list.add(e); } + ) +} + +/** + * Parses one unaliased expression in a select list. + */ +SqlNode SelectExpression() : +{ + SqlNode e; +} +{ + { + return SqlIdentifier.star(getPos()); + } +| + e = Expression(ExprContext.ACCEPT_SUB_QUERY) { + return e; + } +} + +SqlLiteral Natural() : +{ +} +{ + { return SqlLiteral.createBoolean(true, getPos()); } +| + { return SqlLiteral.createBoolean(false, getPos()); } +} + +SqlLiteral JoinType() : +{ + JoinType joinType; +} +{ + ( +<#list (parser.joinTypes!default.parser.joinTypes) as method> + LOOKAHEAD(3) // required for "LEFT SEMI JOIN" and "LEFT ANTI JOIN" in Babel + joinType = ${method}() + | + + { joinType = JoinType.INNER; } + | + { joinType = JoinType.INNER; } + | + [ ] { joinType = JoinType.LEFT; } + | + [ ] { joinType = JoinType.RIGHT; } + | + [ ] { joinType = JoinType.FULL; } + | + { joinType = JoinType.CROSS; } + ) + { + return joinType.symbol(getPos()); + } +} + +/** + * Parses the FROM clause for a SELECT. + * + *

FROM is mandatory in standard SQL, optional in dialects such as MySQL, + * PostgreSQL. The parser allows SELECT without FROM, but the validator fails + * if conformance is, say, STRICT_2003. + */ +SqlNode FromClause() : +{ + SqlNode e, e2; + SqlLiteral joinType; +} +{ + e = Join() + ( + // Comma joins should only occur at top-level in the FROM clause. + // Valid: + // * FROM a, b + // * FROM (a CROSS JOIN b), c + // Not valid: + // * FROM a CROSS JOIN (b, c) + LOOKAHEAD(1) + { joinType = JoinType.COMMA.symbol(getPos()); } + e2 = Join() { + e = new SqlJoin(joinType.getParserPosition(), + e, + SqlLiteral.createBoolean(false, joinType.getParserPosition()), + joinType, + e2, + JoinConditionType.NONE.symbol(SqlParserPos.ZERO), + null); + } + )* + { return e; } +} + +SqlNode Join() : +{ + SqlNode e; +} +{ + e = TableRef1(ExprContext.ACCEPT_QUERY_OR_JOIN) + ( + LOOKAHEAD(2) + e = JoinTable(e) + )* + { + return e; + } +} + +/** Matches "LEFT JOIN t ON ...", "RIGHT JOIN t USING ...", "JOIN t". */ +SqlNode JoinTable(SqlNode e) : +{ + SqlNode e2, condition; + final SqlLiteral natural, joinType, on, using; + SqlNodeList list; +} +{ + // LOOKAHEAD(3) is needed here rather than a LOOKAHEAD(2) because JavaCC + // calculates minimum lookahead count incorrectly for choice that contains + // zero size child. For instance, with the generated code, + // "LOOKAHEAD(2, Natural(), JoinType())" + // returns true immediately if it sees a single "" token. Where we + // expect the lookahead succeeds after " ". + // + // For more information about the issue, + // see https://github.com/javacc/javacc/issues/86 + // + // We allow CROSS JOIN (joinType = CROSS_JOIN) to have a join condition, + // even though that is not valid SQL; the validator will catch it. + LOOKAHEAD(3) + natural = Natural() + joinType = JoinType() + e2 = TableRef1(ExprContext.ACCEPT_QUERY_OR_JOIN) + ( + { on = JoinConditionType.ON.symbol(getPos()); } + condition = Expression(ExprContext.ACCEPT_SUB_QUERY) { + return new SqlJoin(joinType.getParserPosition(), + e, + natural, + joinType, + e2, + on, + condition); + } + | + { using = JoinConditionType.USING.symbol(getPos()); } + list = ParenthesizedSimpleIdentifierList() { + return new SqlJoin(joinType.getParserPosition(), + e, + natural, + joinType, + e2, + using, + new SqlNodeList(list, Span.of(using).end(this))); + } + | + { + return new SqlJoin(joinType.getParserPosition(), + e, + natural, + joinType, + e2, + JoinConditionType.NONE.symbol(joinType.getParserPosition()), + null); + } + ) +| + { joinType = JoinType.CROSS.symbol(getPos()); } + e2 = TableRef2(true) { + if (!this.conformance.isApplyAllowed()) { + throw SqlUtil.newContextException(getPos(), RESOURCE.applyNotAllowed()); + } + return new SqlJoin(joinType.getParserPosition(), + e, + SqlLiteral.createBoolean(false, joinType.getParserPosition()), + joinType, + e2, + JoinConditionType.NONE.symbol(SqlParserPos.ZERO), + null); + } +| + { joinType = JoinType.LEFT.symbol(getPos()); } + e2 = TableRef2(true) { + if (!this.conformance.isApplyAllowed()) { + throw SqlUtil.newContextException(getPos(), RESOURCE.applyNotAllowed()); + } + return new SqlJoin(joinType.getParserPosition(), + e, + SqlLiteral.createBoolean(false, joinType.getParserPosition()), + joinType, + e2, + JoinConditionType.ON.symbol(SqlParserPos.ZERO), + SqlLiteral.createBoolean(true, joinType.getParserPosition())); + } +} + +/** + * Parses a table reference in a FROM clause, not lateral unless LATERAL + * is explicitly specified. + */ +SqlNode TableRef() : +{ + final SqlNode e; +} +{ + e = TableRef3(ExprContext.ACCEPT_QUERY, false) { return e; } +} + +SqlNode TableRef1(ExprContext exprContext) : +{ + final SqlNode e; +} +{ + e = TableRef3(exprContext, false) { return e; } +} + +/** + * Parses a table reference in a FROM clause. + */ +SqlNode TableRef2(boolean lateral) : +{ + final SqlNode e; +} +{ + e = TableRef3(ExprContext.ACCEPT_QUERY, lateral) { return e; } +} + +SqlNode TableRef3(ExprContext exprContext, boolean lateral) : +{ + final SqlIdentifier tableName; + SqlNode tableRef; + final SqlIdentifier alias; + final Span s; + SqlNodeList args; + final SqlNodeList columnAliasList; + SqlUnnestOperator unnestOp = SqlStdOperatorTable.UNNEST; +} +{ + ( + LOOKAHEAD(2) + tableName = CompoundTableIdentifier() { s = span(); } + ( + // Table call syntax like FROM a.b() instead of FROM TABLE(a.b()) + // Three tokens needed to disambiguate EXTEND syntax from CALCITE-493. + // Example: "FROM EventLog(lastGCTime TIME)". + LOOKAHEAD(3) + tableRef = ImplicitTableFunctionCallArgs(tableName) + | + ( tableRef = TableHints(tableName) | { tableRef = tableName; } ) + [ tableRef = ExtendTable(tableRef) ] + tableRef = Over(tableRef) + [ tableRef = Snapshot(tableRef) ] + [ tableRef = MatchRecognize(tableRef) ] + ) + | + LOOKAHEAD(2) + [ { lateral = true; } ] + tableRef = ParenthesizedExpression(exprContext) + tableRef = Over(tableRef) + tableRef = addLateral(tableRef, lateral) + [ tableRef = MatchRecognize(tableRef) ] + | + { s = span(); } + args = ParenthesizedQueryOrCommaList(ExprContext.ACCEPT_SUB_QUERY) + [ + { + unnestOp = SqlStdOperatorTable.UNNEST_WITH_ORDINALITY; + } + ] + { + tableRef = unnestOp.createCall(s.end(this), (List) args); + } + | + [ { lateral = true; } ] + tableRef = TableFunctionCall() + tableRef = addLateral(tableRef, lateral) + | + tableRef = ExtendedTableRef() + ) + [ + LOOKAHEAD(2) + tableRef = Pivot(tableRef) + ] + [ + LOOKAHEAD(2) + tableRef = Unpivot(tableRef) + ] + [ + [ ] alias = SimpleIdentifier() + ( + columnAliasList = ParenthesizedSimpleIdentifierList() + | { columnAliasList = null; } + ) + { + // Standard SQL (and Postgres) allow applying "AS alias" to a JOIN, + // e.g. "FROM (a CROSS JOIN b) AS c". The new alias obscures the + // internal aliases, and columns cannot be referenced if they are + // not unique. TODO: Support this behavior; see + // [CALCITE-5168] Allow AS after parenthesized JOIN + checkNotJoin(tableRef); + if (columnAliasList == null) { + tableRef = SqlStdOperatorTable.AS.createCall( + Span.of(tableRef).end(this), tableRef, alias); + } else { + List idList = new ArrayList(); + idList.add(tableRef); + idList.add(alias); + idList.addAll(columnAliasList.getList()); + tableRef = SqlStdOperatorTable.AS.createCall( + Span.of(tableRef).end(this), idList); + } + } + ] + [ tableRef = Tablesample(tableRef) ] + { return tableRef; } +} + +SqlNode Tablesample(SqlNode tableRef) : +{ + final Span s; + final SqlNode sample; + final boolean isBernoulli; + final SqlNumericLiteral samplePercentage; + boolean isRepeatable = false; + int repeatableSeed = 0; +} +{ + { s = span(); checkNotJoin(tableRef); } + ( + sample = StringLiteral() + { + String sampleName = + SqlLiteral.unchain(sample).getValueAs(String.class); + SqlSampleSpec sampleSpec = SqlSampleSpec.createNamed(sampleName); + final SqlLiteral sampleLiteral = + SqlLiteral.createSample(sampleSpec, s.end(this)); + return SqlStdOperatorTable.TABLESAMPLE.createCall( + s.add(tableRef).end(this), tableRef, sampleLiteral); + } + | + ( + { isBernoulli = true; } + | + { isBernoulli = false; } + ) + samplePercentage = UnsignedNumericLiteral() + [ + repeatableSeed = IntLiteral() + { + isRepeatable = true; + } + ] + { + BigDecimal rate = + samplePercentage.bigDecimalValue().divide(ONE_HUNDRED); + SqlSampleSpec tableSampleSpec = + isRepeatable + ? SqlSampleSpec.createTableSample(isBernoulli, rate, + repeatableSeed) + : SqlSampleSpec.createTableSample(isBernoulli, rate); + SqlLiteral tableSampleLiteral = + SqlLiteral.createSample(tableSampleSpec, s.end(this)); + return SqlStdOperatorTable.TABLESAMPLE.createCall( + s.end(this), tableRef, tableSampleLiteral); + } + ) +} + +/** Wraps a table reference in a call to EXTEND if an optional "EXTEND" clause + * is present. */ +SqlNode ExtendTable(SqlNode tableRef) : +{ + final SqlNodeList extendList; +} +{ + [ ] + extendList = ExtendList() { + return extend(tableRef, extendList); + } +} + +SqlNodeList ExtendList() : +{ + final Span s; + List list = new ArrayList(); +} +{ + { s = span(); } + AddColumnType(list) + ( + AddColumnType(list) + )* + { + return new SqlNodeList(list, s.end(this)); + } +} + +void AddColumnType(List list) : +{ + final SqlIdentifier name; + final SqlDataTypeSpec type; + final boolean nullable; +} +{ + name = CompoundIdentifier() + type = DataType() + nullable = NotNullOpt() + { + list.add(name); + list.add(type.withNullable(nullable, getPos())); + } +} + +/** + * Parses a compound identifier with optional type. + */ +void AddCompoundIdentifierType(List list, List extendList) : +{ + final SqlIdentifier name; + final SqlDataTypeSpec type; + final boolean nullable; +} +{ + name = CompoundIdentifier() + ( + type = DataType() + nullable = NotNullOpt() + | + { type = null; nullable = true; } + ) + { + if (type != null) { + if (!this.conformance.allowExtend()) { + throw SqlUtil.newContextException(type.getParserPosition(), + RESOURCE.extendNotAllowed()); + } + extendList.add(name); + extendList.add(type.withNullable(nullable, getPos())); + } + list.add(name); + } +} + +SqlNode ImplicitTableFunctionCallArgs(SqlIdentifier name) : +{ + final List tableFuncArgs = new ArrayList(); + final SqlNode call; + final Span s; +} +{ + // Table call syntax like FROM a.b() instead of FROM TABLE(a.b()) + // We've already parsed the name, so we don't use NamedRoutineCall. + { s = span(); } + + [ + AddArg0(tableFuncArgs, ExprContext.ACCEPT_CURSOR) + ( + { + // a comma-list can't appear where only a query is expected + checkNonQueryExpression(ExprContext.ACCEPT_CURSOR); + } + AddArg(tableFuncArgs, ExprContext.ACCEPT_CURSOR) + )* + ] + + { + final SqlParserPos pos = s.end(this); + call = createCall(name, pos, + SqlFunctionCategory.USER_DEFINED_TABLE_FUNCTION, null, + tableFuncArgs); + return SqlStdOperatorTable.COLLECTION_TABLE.createCall(pos, + call); + } +} + +SqlNode TableFunctionCall() : +{ + final Span s; + final SqlNode call; + SqlFunctionCategory funcType = SqlFunctionCategory.USER_DEFINED_TABLE_FUNCTION; +} +{ +

{ s = span(); } + [ + + { + funcType = SqlFunctionCategory.USER_DEFINED_TABLE_SPECIFIC_FUNCTION; + } + ] + call = NamedRoutineCall(funcType, ExprContext.ACCEPT_CURSOR) + + { + return SqlStdOperatorTable.COLLECTION_TABLE.createCall(s.end(this), call); + } +} + +/** + * Abstract production: + * SqlNode ExtendedTableRef() + * + *

Allows parser to be extended with new types of table references. The + * default implementation of this production is empty. + */ + +/* + * Abstract production: + * + * SqlNode TableOverOpt() + * + * Allows an OVER clause following a table expression as an extension to + * standard SQL syntax. The default implementation of this production is empty. + */ + +/** + * Parses an explicit TABLE t reference. + */ +SqlNode ExplicitTable(SqlParserPos pos) : +{ + SqlNode tableRef; +} +{ +

tableRef = CompoundIdentifier() + { + return SqlStdOperatorTable.EXPLICIT_TABLE.createCall(pos, tableRef); + } +} + +/** + * Parses a VALUES leaf query expression. + */ +SqlNode TableConstructor() : +{ + final List list = new ArrayList(); + final Span s; +} +{ + ( + { s = span(); } + | + + { + s = span(); + if (!this.conformance.isValueAllowed()) { + throw SqlUtil.newContextException(getPos(), RESOURCE.valueNotAllowed()); + } + } + ) + AddRowConstructor(list) + ( + LOOKAHEAD(2) + AddRowConstructor(list) + )* + { + return SqlStdOperatorTable.VALUES.createCall(s.end(this), list); + } +} + +/** Parses a row constructor and adds it to a list. */ +void AddRowConstructor(List list) : +{ + SqlNode e; +} +{ + e = RowConstructor() { list.add(e); } +} + +/** + * Parses a row constructor in the context of a VALUES expression. + */ +SqlNode RowConstructor() : +{ + final SqlNodeList valueList; + final SqlNode value; + final Span s; +} +{ + // hints are necessary here due to common LPAREN prefixes + ( + // TODO jvs 8-Feb-2004: extra parentheses are accepted here as a hack + // for unparse, but this is actually invalid SQL; should + // fix unparse + LOOKAHEAD(3) + { s = span(); } + + valueList = ParenthesizedQueryOrCommaListWithDefault(ExprContext.ACCEPT_NONCURSOR) + { s.add(this); } + | + LOOKAHEAD(3) + ( + { s = span(); } + | + { s = Span.of(); } + ) + valueList = ParenthesizedQueryOrCommaListWithDefault(ExprContext.ACCEPT_NONCURSOR) + | + value = Expression(ExprContext.ACCEPT_NONCURSOR) + { + // NOTE: A bare value here is standard SQL syntax, believe it or + // not. Taken together with multi-row table constructors, it leads + // to very easy mistakes if you forget the parentheses on a + // single-row constructor. This is also the reason for the + // LOOKAHEAD in TableConstructor(). It would be so much more + // reasonable to require parentheses. Sigh. + s = Span.of(value); + valueList = new SqlNodeList(ImmutableList.of(value), + value.getParserPosition()); + } + ) + { + // REVIEW jvs 8-Feb-2004: Should we discriminate between scalar + // sub-queries inside of ROW and row sub-queries? The standard does, + // but the distinction seems to be purely syntactic. + return SqlStdOperatorTable.ROW.createCall(s.end(valueList), + (List) valueList); + } +} + +/** Parses a WHERE clause for SELECT, DELETE, and UPDATE. */ +SqlNode Where() : +{ + SqlNode condition; +} +{ + condition = Expression(ExprContext.ACCEPT_SUB_QUERY) { + return condition; + } +} + +/** Parses a GROUP BY clause for SELECT. */ +SqlNodeList GroupBy() : +{ + final List list; + final boolean distinct; + final Span s; +} +{ + { s = span(); } + + ( + { distinct = true; } + | { distinct = false; } + | { distinct = false; } + ) + list = GroupingElementList() { + final SqlParserPos pos = s.end(this); + final List list2 = distinct + ? ImmutableList.of( + SqlInternalOperators.GROUP_BY_DISTINCT.createCall(pos, list)) + : list; + return new SqlNodeList(list2, pos); + } +} + +List GroupingElementList() : +{ + final List list = new ArrayList(); +} +{ + AddGroupingElement(list) + ( LOOKAHEAD(2) AddGroupingElement(list) )* + { return list; } +} + +void AddGroupingElement(List list) : +{ + final List subList; + final SqlNodeList nodes; + final Span s; +} +{ + LOOKAHEAD(2) + { s = span(); } + subList = GroupingElementList() { + list.add( + SqlStdOperatorTable.GROUPING_SETS.createCall(s.end(this), subList)); + } +| { s = span(); } + nodes = ExpressionCommaList(s, ExprContext.ACCEPT_SUB_QUERY) + { + list.add( + SqlStdOperatorTable.ROLLUP.createCall(s.end(this), nodes.getList())); + } +| { s = span(); } + nodes = ExpressionCommaList(s, ExprContext.ACCEPT_SUB_QUERY) + { + list.add( + SqlStdOperatorTable.CUBE.createCall(s.end(this), nodes.getList())); + } +| LOOKAHEAD(3) + { s = span(); } { + list.add(new SqlNodeList(s.end(this))); + } +| AddExpression(list, ExprContext.ACCEPT_SUB_QUERY) +} + +/** + * Parses a list of expressions separated by commas. + */ +SqlNodeList ExpressionCommaList( + final Span s, + ExprContext exprContext) : +{ + final List list = new ArrayList(); +} +{ + AddExpressions(list, exprContext) { + return new SqlNodeList(list, s.addAll(list).pos()); + } +} + +/** + * Parses a list of expressions separated by commas, + * appending expressions to a given list. + */ +void AddExpressions(List list, ExprContext exprContext) : +{ +} +{ + AddExpression(list, exprContext) + ( + // NOTE jvs 6-Feb-2004: See comments at top of file for why + // hint is necessary here. + LOOKAHEAD(2) + AddExpression(list, ExprContext.ACCEPT_SUB_QUERY) + )* +} + +/** Parses a HAVING clause for SELECT. */ +SqlNode Having() : +{ + SqlNode e; +} +{ + e = Expression(ExprContext.ACCEPT_SUB_QUERY) { return e; } +} + +/** Parses a WINDOW clause for SELECT. */ +SqlNodeList Window() : +{ + final List list = new ArrayList(); + final Span s; +} +{ + { s = span(); } + AddWindowSpec(list) + ( + LOOKAHEAD(2) + AddWindowSpec(list) + )* + { + return new SqlNodeList(list, s.addAll(list).pos()); + } +} + +void AddWindowSpec(List list) : +{ + final SqlIdentifier id; + final SqlWindow e; +} +{ + id = SimpleIdentifier() e = WindowSpecification() { + e.setDeclName(id); + list.add(e); + } +} + +/** + * Parses a window specification. + */ +SqlWindow WindowSpecification() : +{ + final SqlIdentifier id; + final SqlNodeList partitionList; + final SqlNodeList orderList; + final SqlLiteral isRows; + final SqlNode lowerBound, upperBound; + final Span s, s1, s2; + final SqlLiteral allowPartial; +} +{ + { s = span(); } + ( + id = SimpleIdentifier() + | { id = null; } + ) + ( + { s1 = span(); } + + partitionList = ExpressionCommaList(s1, ExprContext.ACCEPT_NON_QUERY) + | { partitionList = SqlNodeList.EMPTY; } + ) + ( + orderList = OrderBy(true) + | { orderList = SqlNodeList.EMPTY; } + ) + ( + ( + { isRows = SqlLiteral.createBoolean(true, getPos()); } + | + { isRows = SqlLiteral.createBoolean(false, getPos()); } + ) + ( + lowerBound = WindowRange() + upperBound = WindowRange() + | + lowerBound = WindowRange() + { upperBound = null; } + ) + | + { + isRows = SqlLiteral.createBoolean(false, SqlParserPos.ZERO); + lowerBound = upperBound = null; + } + ) + ( + { s2 = span(); } { + allowPartial = SqlLiteral.createBoolean(true, s2.end(this)); + } + | + { s2 = span(); } { + allowPartial = SqlLiteral.createBoolean(false, s2.end(this)); + } + | { allowPartial = null; } + ) + + { + return SqlWindow.create(null, id, partitionList, orderList, + isRows, lowerBound, upperBound, allowPartial, s.end(this)); + } +} + +SqlNode WindowRange() : +{ + final SqlNode e; + final Span s; +} +{ + LOOKAHEAD(2) + { s = span(); } { + return SqlWindow.createCurrentRow(s.end(this)); + } +| + LOOKAHEAD(2) + { s = span(); } + ( + { + return SqlWindow.createUnboundedPreceding(s.end(this)); + } + | + { + return SqlWindow.createUnboundedFollowing(s.end(this)); + } + ) +| + e = Expression(ExprContext.ACCEPT_NON_QUERY) + ( + { + return SqlWindow.createPreceding(e, getPos()); + } + | + { + return SqlWindow.createFollowing(e, getPos()); + } + ) +} + +/** Parses a QUALIFY clause for SELECT. */ +SqlNode Qualify() : +{ + SqlNode e; +} +{ + e = Expression(ExprContext.ACCEPT_SUB_QUERY) { return e; } +} + +/** + * Parses an ORDER BY clause. + */ +SqlNodeList OrderBy(boolean accept) : +{ + final List list = new ArrayList(); + final Span s; +} +{ + { + s = span(); + if (!accept) { + // Someone told us ORDER BY wasn't allowed here. So why + // did they bother calling us? To get the correct + // parser position for error reporting. + throw SqlUtil.newContextException(s.pos(), RESOURCE.illegalOrderBy()); + } + } + AddOrderItem(list) + ( + // NOTE jvs 6-Feb-2004: See comments at top of file for why + // hint is necessary here. + LOOKAHEAD(2) AddOrderItem(list) + )* + { + return new SqlNodeList(list, s.addAll(list).pos()); + } +} + +/** + * Parses one item in an ORDER BY clause, and adds it to a list. + */ +void AddOrderItem(List list) : +{ + SqlNode e; +} +{ + e = Expression(ExprContext.ACCEPT_SUB_QUERY) + ( + + | { + e = SqlStdOperatorTable.DESC.createCall(getPos(), e); + } + )? + ( + LOOKAHEAD(2) + { + e = SqlStdOperatorTable.NULLS_FIRST.createCall(getPos(), e); + } + | + { + e = SqlStdOperatorTable.NULLS_LAST.createCall(getPos(), e); + } + )? + { + list.add(e); + } +} + +/** Wraps a table reference in a call to OVER if an optional "OVER" clause + * is present (if the dialect supports OVER for table expressions). */ +SqlNode Over(SqlNode tableRef) : +{ + final SqlNode over; +} +{ + over = TableOverOpt() { + if (over != null) { + return SqlStdOperatorTable.OVER.createCall( + getPos(), checkNotJoin(tableRef), over); + } else { + return tableRef; + } + } +} + +/** Wraps a table reference in a call to LATERAL if {@code lateral} is true. */ +JAVACODE SqlNode addLateral(SqlNode tableRef, boolean lateral) { + return lateral + ? SqlStdOperatorTable.LATERAL.createCall(getPos(), + checkNotJoin(tableRef)) + : tableRef; +} + +/** + * Parses a FOR SYSTEM_TIME clause following a table expression. + */ +SqlSnapshot Snapshot(SqlNode tableRef) : +{ + final Span s; + final SqlNode e; +} +{ + { s = span(); } + // Syntax for temporal table in + // standard SQL 2011 IWD 9075-2:201?(E) 7.6
+ // supports grammar as following: + // 1. datetime literal + // 2. datetime value function, i.e. CURRENT_TIMESTAMP + // 3. datetime term in 1 or 2 +(or -) interval term + + // We extend to support column reference, use Expression + // to simplify the parsing code. + e = Expression(ExprContext.ACCEPT_NON_QUERY) { + return new SqlSnapshot(s.end(this), tableRef, e); + } +} + +/** Parses a PIVOT clause following a table expression. */ +SqlNode Pivot(SqlNode tableRef) : +{ + final Span s; + final Span s2; + final List aggList = new ArrayList(); + final List valueList = new ArrayList(); + final SqlNodeList axisList; + final SqlNodeList inList; +} +{ + { s = span(); checkNotJoin(tableRef); } + + AddPivotAgg(aggList) ( AddPivotAgg(aggList) )* + axisList = SimpleIdentifierOrList() + { s2 = span(); } + [ AddPivotValue(valueList) ( AddPivotValue(valueList) )* ] + { + inList = new SqlNodeList(valueList, s2.end(this)); + } + + { + return new SqlPivot(s.end(this), tableRef, + new SqlNodeList(aggList, SqlParserPos.sum(aggList)), + axisList, inList); + } +} + +void AddPivotAgg(List list) : +{ + final SqlNode e; + final SqlIdentifier alias; +} +{ + e = NamedFunctionCall() + ( + // Because babel put FOR into non-reserved keyword set. + LOOKAHEAD({getToken(1).kind != COMMA && getToken(1).kind != FOR}) + [ ] alias = SimpleIdentifier() { + list.add( + SqlStdOperatorTable.AS.createCall(Span.of(e).end(this), e, + alias)); + } + | + { list.add(e); } + ) +} + +void AddPivotValue(List list) : +{ + final SqlNode e; + final SqlNodeList tuple; + final SqlIdentifier alias; +} +{ + e = RowConstructor() { tuple = SqlParserUtil.stripRow(e); } + ( + [ ] alias = SimpleIdentifier() { + list.add( + SqlStdOperatorTable.AS.createCall(Span.of(tuple).end(this), + tuple, alias)); + } + | + { list.add(tuple); } + ) +} + +/** Parses an UNPIVOT clause following a table expression. */ +SqlNode Unpivot(SqlNode tableRef) : +{ + final Span s; + final boolean includeNulls; + final SqlNodeList measureList; + final SqlNodeList axisList; + final Span s2; + final List values = new ArrayList(); + final SqlNodeList inList; +} +{ + { s = span(); checkNotJoin(tableRef); } + ( + { includeNulls = true; } + | { includeNulls = false; } + | { includeNulls = false; } + ) + + measureList = SimpleIdentifierOrList() + axisList = SimpleIdentifierOrList() + + { s2 = span(); } + AddUnpivotValue(values) ( AddUnpivotValue(values) )* + + { inList = new SqlNodeList(values, s2.end(this)); } + { + return new SqlUnpivot(s.end(this), tableRef, includeNulls, measureList, + axisList, inList); + } +} + +void AddUnpivotValue(List list) : +{ + final SqlNodeList columnList; + final SqlNode values; +} +{ + columnList = SimpleIdentifierOrList() + ( + values = RowConstructor() { + final SqlNodeList valueList = SqlParserUtil.stripRow(values); + list.add( + SqlStdOperatorTable.AS.createCall(Span.of(columnList).end(this), + columnList, valueList)); + } + | + { list.add(columnList); } + ) +} + +/** + * Parses a MATCH_RECOGNIZE clause following a table expression. + */ +SqlMatchRecognize MatchRecognize(SqlNode tableRef) : +{ + final Span s, s0, s1, s2; + final SqlNodeList measureList; + final SqlNodeList partitionList; + final SqlNodeList orderList; + final SqlNode pattern; + final SqlLiteral interval; + final SqlNodeList patternDefList; + final SqlNode after; + final SqlNode var; + final SqlLiteral rowsPerMatch; + final SqlNodeList subsetList; + final SqlLiteral isStrictStarts; + final SqlLiteral isStrictEnds; +} +{ + { s = span(); checkNotJoin(tableRef); } + ( + { s2 = span(); } + partitionList = ExpressionCommaList(s2, ExprContext.ACCEPT_NON_QUERY) + | + { partitionList = SqlNodeList.EMPTY; } + ) + ( + orderList = OrderBy(true) + | + { orderList = SqlNodeList.EMPTY; } + ) + ( + + measureList = MeasureColumnCommaList(span()) + | + { measureList = SqlNodeList.EMPTY; } + ) + ( + { s0 = span(); } { + rowsPerMatch = SqlMatchRecognize.RowsPerMatchOption.ONE_ROW.symbol(s0.end(this)); + } + | + { s0 = span(); } { + rowsPerMatch = SqlMatchRecognize.RowsPerMatchOption.ALL_ROWS.symbol(s0.end(this)); + } + | { rowsPerMatch = null; } + ) + ( + { s1 = span(); } + ( + + ( + LOOKAHEAD(2) + { + after = SqlMatchRecognize.AfterOption.SKIP_TO_NEXT_ROW + .symbol(s1.end(this)); + } + | + LOOKAHEAD(2) + var = SimpleIdentifier() { + after = SqlMatchRecognize.SKIP_TO_FIRST.createCall( + s1.end(var), var); + } + | + // This "LOOKAHEAD({true})" is a workaround for Babel. + // Because of babel parser uses option "LOOKAHEAD=2" globally, + // JavaCC generates something like "LOOKAHEAD(2, [] SimpleIdentifier())" + // here. But the correct LOOKAHEAD should be + // "LOOKAHEAD(2, [ LOOKAHEAD(2, SimpleIdentifier()) ] + // SimpleIdentifier())" which have the syntactic lookahead for considered. + // + // Overall LOOKAHEAD({true}) is even better as this is the last branch in the + // choice. + LOOKAHEAD({true}) + [ LOOKAHEAD(2, SimpleIdentifier()) ] var = SimpleIdentifier() { + after = SqlMatchRecognize.SKIP_TO_LAST.createCall( + s1.end(var), var); + } + ) + | + { + after = SqlMatchRecognize.AfterOption.SKIP_PAST_LAST_ROW + .symbol(s1.end(this)); + } + ) + | { after = null; } + ) + + + ( + { isStrictStarts = SqlLiteral.createBoolean(true, getPos()); } + | { isStrictStarts = SqlLiteral.createBoolean(false, getPos()); } + ) + pattern = PatternExpression() + ( + { isStrictEnds = SqlLiteral.createBoolean(true, getPos()); } + | { isStrictEnds = SqlLiteral.createBoolean(false, getPos()); } + ) + + ( + interval = IntervalLiteral() + | { interval = null; } + ) + ( + subsetList = SubsetDefinitionCommaList(span()) + | { subsetList = SqlNodeList.EMPTY; } + ) + + patternDefList = PatternDefinitionCommaList(span()) + { + return new SqlMatchRecognize(s.end(this), tableRef, + pattern, isStrictStarts, isStrictEnds, patternDefList, measureList, + after, subsetList, rowsPerMatch, partitionList, orderList, interval); + } +} + +SqlNodeList MeasureColumnCommaList(Span s) : +{ + final List list = new ArrayList(); +} +{ + AddMeasureColumn(list) + ( AddMeasureColumn(list) )* + { return new SqlNodeList(list, s.addAll(list).pos()); } +} + +void AddMeasureColumn(List list) : +{ + final SqlNode e; + final SqlIdentifier alias; +} +{ + e = Expression(ExprContext.ACCEPT_NON_QUERY) + + alias = SimpleIdentifier() { + list.add(SqlStdOperatorTable.AS.createCall(Span.of(e).end(this), e, alias)); + } +} + +SqlNode PatternExpression() : +{ + SqlNode left; + SqlNode right; +} +{ + left = PatternTerm() + ( + + right = PatternTerm() { + left = SqlStdOperatorTable.PATTERN_ALTER.createCall( + Span.of(left).end(right), left, right); + } + )* + { + return left; + } +} + +SqlNode PatternTerm() : +{ + SqlNode left; + SqlNode right; +} +{ + left = PatternFactor() + ( + right = PatternFactor() { + left = SqlStdOperatorTable.PATTERN_CONCAT.createCall( + Span.of(left).end(right), left, right); + } + )* + { + return left; + } +} + +SqlNode PatternFactor() : +{ + final SqlNode e; + final SqlNode extra; + final SqlLiteral startNum; + final SqlLiteral endNum; + final SqlLiteral reluctant; +} +{ + e = PatternPrimary() + ( + LOOKAHEAD(1) + ( + { + startNum = LITERAL_ZERO; + endNum = LITERAL_MINUS_ONE; + } + | + { + startNum = LITERAL_ONE; + endNum = LITERAL_MINUS_ONE; + } + | + { + startNum = LITERAL_ZERO; + endNum = LITERAL_ONE; + } + | + + ( + startNum = UnsignedNumericLiteral() + ( + + ( + endNum = UnsignedNumericLiteral() + | + { endNum = LITERAL_MINUS_ONE; } + ) + | + { endNum = startNum; } + ) + + | + + endNum = UnsignedNumericLiteral() + + { startNum = LITERAL_MINUS_ONE; } + | + extra = PatternExpression() { + return SqlStdOperatorTable.PATTERN_CONCAT.createCall( + Span.of(e).end(this), e, + SqlStdOperatorTable.PATTERN_EXCLUDE.createCall( + Span.of(extra).end(this), extra)); + } + ) + ) + ( + { + reluctant = SqlLiteral.createBoolean( + startNum.intValue(true) != endNum.intValue(true), + SqlParserPos.ZERO); + } + | + { reluctant = SqlLiteral.createBoolean(false, SqlParserPos.ZERO); } + ) + | + { return e; } + ) + { + return SqlStdOperatorTable.PATTERN_QUANTIFIER.createCall( + span().end(e), e, startNum, endNum, reluctant); + } +} + +SqlNode PatternPrimary() : +{ + final Span s; + SqlNode e; + final List list; +} +{ + e = SimpleIdentifier() { return e; } +| + e = PatternExpression() { return e; } +| + { s = span(); } + e = PatternExpression() + { + return SqlStdOperatorTable.PATTERN_EXCLUDE.createCall(s.end(this), e); + } +| + ( + { s = span(); list = new ArrayList(); } + + e = PatternExpression() { list.add(e); } + ( e = PatternExpression() { list.add(e); } )* + { + return SqlStdOperatorTable.PATTERN_PERMUTE.createCall( + s.end(this), list); + } + ) +} + +SqlNodeList SubsetDefinitionCommaList(Span s) : +{ + final List list = new ArrayList(); +} +{ + AddSubsetDefinition(list) + ( AddSubsetDefinition(list) )* + { return new SqlNodeList(list, s.addAll(list).pos()); } +} + +void AddSubsetDefinition(List list) : +{ + final SqlNode var; + final SqlNodeList varList; +} +{ + var = SimpleIdentifier() + + + varList = ExpressionCommaList(span(), ExprContext.ACCEPT_NON_QUERY) + { + list.add( + SqlStdOperatorTable.EQUALS.createCall(span().end(var), var, + varList)); + } +} + +SqlNodeList PatternDefinitionCommaList(Span s) : +{ + SqlNode e; + final List eList = new ArrayList(); +} +{ + e = PatternDefinition() { + eList.add(e); + } + ( + + e = PatternDefinition() { + eList.add(e); + } + )* + { + return new SqlNodeList(eList, s.addAll(eList).pos()); + } +} + +SqlNode PatternDefinition() : +{ + final SqlNode var; + final SqlNode e; +} +{ + var = SimpleIdentifier() + + e = Expression(ExprContext.ACCEPT_SUB_QUERY) { + return SqlStdOperatorTable.AS.createCall(Span.of(var, e).pos(), e, var); + } +} + +// ---------------------------------------------------------------------------- +// Expressions + +/** + * Parses a SQL expression (such as might occur in a WHERE clause) followed by + * the end-of-file symbol. + */ +SqlNode SqlExpressionEof() : +{ + SqlNode e; +} +{ + e = Expression(ExprContext.ACCEPT_SUB_QUERY) () + { + return e; + } +} + +/** + * Parses either a row expression or a query expression without ORDER BY. + * + *

Examples of valid queries: + *

    + *
  • {@code SELECT c FROM t} + *
  • {@code SELECT c} (valid in some dialects) + *
  • {@code SELECT c FROM t UNION SELECT c2 FROM t2} + *
  • {@code WITH q AS (SELECT 1) SELECT * FROM q} + *
  • {@code VALUES (1, 2)} + *
  • {@code TABLE t} + *
+ * + *

Non-examples: + *

    + *
  • {@code emp CROSS JOIN dept} + *
  • {@code SELECT c FROM t ORDER BY c} + *
  • {@code (SELECT c FROM t)} + *
+ */ +SqlNode QueryOrExpr(ExprContext exprContext) : +{ + SqlNodeList withList = null; + final SqlNode e; + final List list = new ArrayList(); +} +{ + [ withList = WithList() ] + e = LeafQueryOrExpr(exprContext) { list.add(e); } + ( AddSetOpQuery(list, exprContext) )* + { return addWith(withList, SqlParserUtil.toTree(list)); } +} + +SqlNode Query(ExprContext exprContext) : +{ + SqlNodeList withList = null; + final SqlNode e; + final List list = new ArrayList(); +} +{ + [ withList = WithList() ] + e = LeafQuery(exprContext) { list.add(e); } + ( AddSetOpQuery(list, exprContext) )* + { return addWith(withList, SqlParserUtil.toTree(list)); } +} + +JAVACODE SqlNode addWith(SqlNodeList withList, SqlNode e) { + return withList == null + ? e + : new SqlWith(withList.getParserPosition(), withList, e); +} + +/** Parses a set operator (e.g. UNION or INTERSECT) + * followed by a query or expression, + * and adds both to {@code list}. */ +void AddSetOpQueryOrExpr(List list, ExprContext exprContext) : +{ + final SqlOperator op; + final SqlParserPos pos; + final SqlNode e; +} +{ + { + if (list.size() == 1 && !((SqlNode) list.get(0)).isA(SqlKind.QUERY)) { + // whoops, expression we just parsed wasn't a query, + // but we're about to see something like UNION, so + // force an exception retroactively + checkNonQueryExpression(ExprContext.ACCEPT_QUERY); + } + } + op = BinaryQueryOperator() { + // ensure a query is legal in this context + pos = getPos(); + checkQueryExpression(exprContext); + } + e = LeafQueryOrExpr(ExprContext.ACCEPT_QUERY) { + list.add(new SqlParserUtil.ToTreeListItem(op, pos)); + list.add(e); + } +} + +/** Parses a set operator (e.g. UNION or INTERSECT) + * followed by a query, + * and adds both to {@code list}. */ +void AddSetOpQuery(List list, ExprContext exprContext) : +{ + final SqlOperator op; + final SqlParserPos pos; + final SqlNode e; +} +{ + { + if (list.size() == 1 && !((SqlNode) list.get(0)).isA(SqlKind.QUERY)) { + // whoops, expression we just parsed wasn't a query, + // but we're about to see something like UNION, so + // force an exception retroactively + checkNonQueryExpression(ExprContext.ACCEPT_QUERY); + } + } + op = BinaryQueryOperator() { + // ensure a query is legal in this context + pos = getPos(); + checkQueryExpression(exprContext); + } + e = LeafQueryOrExpr(ExprContext.ACCEPT_QUERY) { + list.add(new SqlParserUtil.ToTreeListItem(op, pos)); + list.add(e); + } +} + +SqlNodeList WithList() : +{ + final Span s; + final List list = new ArrayList(); + boolean recursive = false; +} +{ + [ { recursive = true; } ]{ s = span(); } + AddWithItem(list, SqlLiteral.createBoolean(recursive, getPos())) + ( AddWithItem(list, SqlLiteral.createBoolean(recursive, getPos())) )* + { return new SqlNodeList(list, s.end(this)); } +} + +void AddWithItem(List list, SqlLiteral recursive) : +{ + final SqlIdentifier id; + final SqlNodeList columnList; + final SqlNode definition; +} +{ + id = SimpleIdentifier() + ( columnList = ParenthesizedSimpleIdentifierList() | { columnList = null; } ) + + definition = ParenthesizedExpression(ExprContext.ACCEPT_QUERY) + { list.add(new SqlWithItem(id.getParserPosition(), id, columnList, definition, recursive)); } +} + +/** + * Parses either a row expression, a leaf query expression, or + * a parenthesized expression of any kind. + */ +SqlNode LeafQueryOrExpr(ExprContext exprContext) : +{ + SqlNode e; +} +{ + e = LeafQuery(exprContext) { return e; } +| + e = Expression(exprContext) { return e; } +} + +/** As {@link #Expression} but appends to a list. */ +void AddExpression(List list, ExprContext exprContext) : +{ + final SqlNode e; +} +{ + e = Expression(exprContext) { list.add(e); } +} + +/** + * Parses a row expression or a parenthesized expression of any kind. + */ +SqlNode Expression(ExprContext exprContext) : +{ + final List list; +} +{ + list = Expression2(exprContext) { return SqlParserUtil.toTree(list); } +} + +void AddExpression2b(List list, ExprContext exprContext) : +{ + SqlNode e; + SqlOperator op; + SqlNode ext; +} +{ + ( + LOOKAHEAD(1) + op = PrefixRowOperator() { + checkNonQueryExpression(exprContext); + list.add(new SqlParserUtil.ToTreeListItem(op, getPos())); + } + )* + e = Expression3(exprContext) { + list.add(e); + } + ( + LOOKAHEAD(2) + ext = RowExpressionExtension() { + list.add( + new SqlParserUtil.ToTreeListItem( + SqlStdOperatorTable.DOT, getPos())); + list.add(ext); + } + )* +} + +/** + * Parses a binary row expression, or a parenthesized expression of any + * kind. + * + *

The result is as a flat list of operators and operands. The top-level + * call to get an expression should call {@link #Expression}, but lower-level + * calls should call this, to give the parser the opportunity to associate + * operator calls. + * + *

For example 'a = b like c = d' should come out '((a = b) like c) = d' + * because LIKE and '=' have the same precedence, but tends to come out as '(a + * = b) like (c = d)' because (a = b) and (c = d) are parsed as separate + * expressions. + */ +List Expression2(ExprContext exprContext) : +{ + final List list = new ArrayList(); + List list2; + final List list3 = new ArrayList(); + SqlNodeList nodeList; + SqlNode e; + SqlOperator itemOp; + SqlOperator op; + SqlIdentifier p; + final Span s = span(); +} +{ + AddExpression2b(list, exprContext) + ( + LOOKAHEAD(2) + ( + LOOKAHEAD(2) + ( + // Special case for "IN", because RHS of "IN" is the only place + // that an expression-list is allowed ("exp IN (exp1, exp2)"). + LOOKAHEAD(2) { + checkNonQueryExpression(exprContext); + } + ( + { op = SqlStdOperatorTable.NOT_IN; } + | + { op = SqlStdOperatorTable.IN; } + | + { final SqlKind k; } + k = comp() + ( + { op = SqlStdOperatorTable.some(k); } + | + { op = SqlStdOperatorTable.some(k); } + | + { op = SqlStdOperatorTable.all(k); } + ) + ) + { s.clear().add(this); } + nodeList = ParenthesizedQueryOrCommaList(ExprContext.ACCEPT_NONCURSOR) + { + list.add(new SqlParserUtil.ToTreeListItem(op, s.pos())); + s.add(nodeList); + // special case for stuff like IN (s1 UNION s2) + if (nodeList.size() == 1) { + SqlNode item = nodeList.get(0); + if (item.isA(SqlKind.QUERY)) { + list.add(item); + } else { + list.add(nodeList); + } + } else { + list.add(nodeList); + } + } + | + LOOKAHEAD(2) { + checkNonQueryExpression(exprContext); + } + ( + { + op = SqlStdOperatorTable.NOT_BETWEEN; + s.clear().add(this); + } + [ + { op = SqlStdOperatorTable.SYMMETRIC_NOT_BETWEEN; } + | + + ] + | + + { + op = SqlStdOperatorTable.BETWEEN; + s.clear().add(this); + } + [ + { op = SqlStdOperatorTable.SYMMETRIC_BETWEEN; } + | + + ] + ) + AddExpression2b(list3, ExprContext.ACCEPT_SUB_QUERY) { + list.add(new SqlParserUtil.ToTreeListItem(op, s.pos())); + list.addAll(list3); + list3.clear(); + } + | + LOOKAHEAD(2) { + checkNonQueryExpression(exprContext); + s.clear().add(this); + } + ( + ( + + ( + { op = SqlStdOperatorTable.NOT_LIKE; } + | + { op = SqlLibraryOperators.NOT_ILIKE; } + | + { op = SqlLibraryOperators.NOT_RLIKE; } + | + { op = SqlStdOperatorTable.NOT_SIMILAR_TO; } + ) + | + { op = SqlStdOperatorTable.LIKE; } + | + { op = SqlLibraryOperators.ILIKE; } + | + { op = SqlLibraryOperators.RLIKE; } + | + { op = SqlStdOperatorTable.SIMILAR_TO; } + ) + <#if (parser.includePosixOperators!default.parser.includePosixOperators)> + | + { op = SqlStdOperatorTable.NEGATED_POSIX_REGEX_CASE_SENSITIVE; } + [ { op = SqlStdOperatorTable.NEGATED_POSIX_REGEX_CASE_INSENSITIVE; } ] + | + { op = SqlStdOperatorTable.POSIX_REGEX_CASE_SENSITIVE; } + [ { op = SqlStdOperatorTable.POSIX_REGEX_CASE_INSENSITIVE; } ] + + ) + list2 = Expression2(ExprContext.ACCEPT_SUB_QUERY) { + list.add(new SqlParserUtil.ToTreeListItem(op, s.pos())); + list.addAll(list2); + } + [ + LOOKAHEAD(2) + e = Expression3(ExprContext.ACCEPT_SUB_QUERY) { + s.clear().add(this); + list.add( + new SqlParserUtil.ToTreeListItem( + SqlStdOperatorTable.ESCAPE, s.pos())); + list.add(e); + } + ] + | + <#list (parser.extraBinaryExpressions!default.parser.extraBinaryExpressions) as extra > + ${extra}(list, exprContext, s) + | + + LOOKAHEAD(3) op = BinaryRowOperator() { + checkNonQueryExpression(exprContext); + list.add(new SqlParserUtil.ToTreeListItem(op, getPos())); + } + AddExpression2b(list, ExprContext.ACCEPT_SUB_QUERY) + | + + itemOp = getItemOp() + e = Expression(ExprContext.ACCEPT_SUB_QUERY) + { + list.add( + new SqlParserUtil.ToTreeListItem( + itemOp, getPos())); + list.add(e); + } + ( + LOOKAHEAD(2) + p = SimpleIdentifier() { + list.add( + new SqlParserUtil.ToTreeListItem( + SqlStdOperatorTable.DOT, getPos())); + list.add(p); + } + )* + | + { + checkNonQueryExpression(exprContext); + } + op = PostfixRowOperator() { + list.add(new SqlParserUtil.ToTreeListItem(op, getPos())); + } + ) + )+ + { + return list; + } + | + { + return list; + } + ) +} + +/** Returns the appropriate ITEM operator for indexing arrays. */ +SqlOperator getItemOp() : +{ +} +{ + { return SqlLibraryOperators.OFFSET; } +| + { return SqlLibraryOperators.ORDINAL; } +| + { return SqlLibraryOperators.SAFE_OFFSET; } +| + { return SqlLibraryOperators.SAFE_ORDINAL; } +| + { return SqlStdOperatorTable.ITEM; } +} + +/** Parses a comparison operator inside a SOME / ALL predicate. */ +SqlKind comp() : +{ +} +{ + { return SqlKind.LESS_THAN; } +| + { return SqlKind.LESS_THAN_OR_EQUAL; } +| + { return SqlKind.GREATER_THAN; } +| + { return SqlKind.GREATER_THAN_OR_EQUAL; } +| + { return SqlKind.EQUALS; } +| + { return SqlKind.NOT_EQUALS; } +| + { + if (!this.conformance.isBangEqualAllowed()) { + throw SqlUtil.newContextException(getPos(), RESOURCE.bangEqualNotAllowed()); + } + return SqlKind.NOT_EQUALS; + } +} + +/** + * Parses a unary row expression, or a parenthesized expression of any + * kind. + */ +SqlNode Expression3(ExprContext exprContext) : +{ + final SqlNode e; + final SqlNodeList list; + final SqlNodeList list1; + final Span s; + final Span rowSpan; +} +{ + LOOKAHEAD(2) + e = AtomicRowExpression() + { + checkNonQueryExpression(exprContext); + return e; + } +| + e = CursorExpression(exprContext) { return e; } +| + LOOKAHEAD(3) + { + s = span(); + } + list = ParenthesizedQueryOrCommaList(exprContext) { + if (exprContext != ExprContext.ACCEPT_ALL + && exprContext != ExprContext.ACCEPT_CURSOR + && !this.conformance.allowExplicitRowValueConstructor()) + { + throw SqlUtil.newContextException(s.end(list), + RESOURCE.illegalRowExpression()); + } + return SqlStdOperatorTable.ROW.createCall(list); + } +| + ( + { rowSpan = span(); } + | { rowSpan = null; } + ) + list1 = ParenthesizedQueryOrCommaList(exprContext) { + if (rowSpan != null) { + // interpret as row constructor + return SqlStdOperatorTable.ROW.createCall(rowSpan.end(list1), + (List) list1); + } + } + [ + LOOKAHEAD(2) + /* TODO: + ( + op = periodOperator() + list2 = ParenthesizedQueryOrCommaList(exprContext) + { + if (list1.size() != 2 || list2.size() != 2) { + throw SqlUtil.newContextException( + list1.getParserPosition().plus( + list2.getParserPosition()), + RESOURCE.illegalOverlaps()); + } + for (SqlNode node : list2) { + list1.add(node); + } + return op.createCall( + list1.getParserPosition().plus(list2.getParserPosition()), + list1.toArray()); + } + ) + | + */ + ( + e = IntervalQualifier() + { + if ((list1.size() == 1) + && list1.get(0) instanceof SqlCall) + { + final SqlCall call = (SqlCall) list1.get(0); + if (call.getKind() == SqlKind.MINUS + && call.operandCount() == 2) { + return SqlStdOperatorTable.MINUS_DATE.createCall( + Span.of(list1).end(this), call.operand(0), + call.operand(1), e); + } + } + throw SqlUtil.newContextException(span().end(list1), + RESOURCE.illegalMinusDate()); + } + ) + ] + { + if (list1.size() == 1) { + // interpret as single value or query + return list1.get(0); + } else { + // interpret as row constructor + return SqlStdOperatorTable.ROW.createCall(span().end(list1), + (List) list1); + } + } +} + +/** + * Parses a lambda expression. + */ +SqlNode LambdaExpression() : +{ + final SqlNodeList parameters; + final SqlNode expression; + final Span s; +} +{ + parameters = SimpleIdentifierOrListOrEmpty() + { s = span(); } + expression = Expression(ExprContext.ACCEPT_NON_QUERY) + { + return new SqlLambda(s.end(this), parameters, expression); + } +} + +/** + * List of simple identifiers in parentheses or empty parentheses or one simple identifier. + *
    Examples: + *
  • {@code ()} + *
  • {@code DEPTNO} + *
  • {@code (EMPNO, DEPTNO)} + *
+ */ +SqlNodeList SimpleIdentifierOrListOrEmpty() : +{ + SqlNodeList list; +} +{ + LOOKAHEAD(2) + { return SqlNodeList.EMPTY; } +| + list = SimpleIdentifierOrList() { return list; } +} + +SqlOperator periodOperator() : +{ +} +{ + { return SqlStdOperatorTable.OVERLAPS; } +| + LOOKAHEAD(2) + { return SqlStdOperatorTable.IMMEDIATELY_PRECEDES; } +| + { return SqlStdOperatorTable.PRECEDES; } +| + { return SqlStdOperatorTable.IMMEDIATELY_SUCCEEDS; } +| + { return SqlStdOperatorTable.SUCCEEDS; } +| + { return SqlStdOperatorTable.PERIOD_EQUALS; } +} + +/** + * Parses a COLLATE clause + */ +SqlCollation CollateClause() : +{ +} +{ + + { + return new SqlCollation( + getToken(0).image, SqlCollation.Coercibility.EXPLICIT); + } +} + +/** + * Numeric literal or parameter; used in LIMIT, OFFSET and FETCH clauses. + */ +SqlNode UnsignedNumericLiteralOrParam() : +{ + final SqlNode e; +} +{ + ( + e = UnsignedNumericLiteral() + | + e = DynamicParam() + ) + { return e; } +} + +/** + * Parses a row expression extension, it can be either an identifier, + * or a call to a named function. + */ +SqlNode RowExpressionExtension() : +{ + final SqlFunctionCategory funcType = SqlFunctionCategory.USER_DEFINED_FUNCTION; + final SqlIdentifier p; + final Span s; + final List args; + final SqlLiteral quantifier; +} +{ + p = SimpleIdentifier() + ( + LOOKAHEAD( ) { s = span(); } + ( + LOOKAHEAD(2) { + quantifier = null; + args = ImmutableList.of(SqlIdentifier.star(getPos())); + } + + | + LOOKAHEAD(2) { + quantifier = null; + args = ImmutableList.of(); + } + | + args = FunctionParameterList(ExprContext.ACCEPT_SUB_QUERY) { + quantifier = (SqlLiteral) args.get(0); + args.remove(0); + } + ) + { return createCall(p, s.end(this), funcType, quantifier, args); } + | + { return p; } + ) +} + +/** + * Parses a call to the STRING_AGG aggregate function (or to an aggregate + * function with similar syntax: ARRAY_AGG, ARRAY_CONCAT_AGG, GROUP_CONCAT). + */ +SqlCall StringAggFunctionCall() : +{ + final Span s, s2; + final SqlOperator op; + final List args = new ArrayList(); + final SqlLiteral qualifier; + final SqlNodeList orderBy; + final Pair nullTreatment; + final SqlNode separator; +} +{ + ( + { s = span(); op = SqlLibraryOperators.ARRAY_AGG; } + | { s = span(); op = SqlLibraryOperators.ARRAY_CONCAT_AGG; } + | { s = span(); op = SqlLibraryOperators.GROUP_CONCAT; } + | { s = span(); op = SqlLibraryOperators.STRING_AGG; } + ) + + ( + qualifier = AllOrDistinct() + | { qualifier = null; } + ) + AddArg(args, ExprContext.ACCEPT_SUB_QUERY) + ( + { + // a comma-list can't appear where only a query is expected + // TODO: the following line is a no-op; remove it? + checkNonQueryExpression(ExprContext.ACCEPT_SUB_QUERY); + } + AddArg(args, ExprContext.ACCEPT_SUB_QUERY) + )* + ( + nullTreatment = NullTreatment() + | { nullTreatment = null; } + ) + [ + orderBy = OrderBy(true) { + args.add(orderBy); + } + ] + [ + { s2 = span(); } separator = StringLiteral() { + args.add(SqlInternalOperators.SEPARATOR.createCall(s2.end(this), separator)); + } + ] + + { + SqlCall call = op.createCall(qualifier, s.end(this), args); + if (nullTreatment != null) { + // Wrap in RESPECT_NULLS or IGNORE_NULLS. + call = nullTreatment.right.createCall(nullTreatment.left, call); + } + return call; + } +} + +/** + * Parses both the standard and the BigQuery PERCENTILE_CONT/PERCENTILE_DISC + * functions. + * + *

The standard is of the form "PERCENTILE_CONT(fraction)" while BigQuery is + * of the form "PERCENTILE_CONT(value, fraction [ {RESPECT | IGNORE} NULLS ] )". + * Handles the parsing of the operator and its operands but not the WITHIN GROUP + * (for the standard) or OVER (for BigQuery) clauses. + */ +SqlCall PercentileFunctionCall() : +{ + final Span s; + SqlOperator op; + final SqlNode e; + final List args = new ArrayList(); + final Pair nullTreatment; +} +{ + ( + { op = SqlStdOperatorTable.PERCENTILE_CONT; } + | { op = SqlStdOperatorTable.PERCENTILE_DISC; } + ) + { s = span(); } + + AddArg(args, ExprContext.ACCEPT_SUB_QUERY) + ( + { + return op.createCall(s.end(this), args); + } + | + + e = NumericLiteral() { args.add(e); } + ( + nullTreatment = NullTreatment() + | { nullTreatment = null; } + ) + + { + op = + op == SqlStdOperatorTable.PERCENTILE_CONT + ? SqlLibraryOperators.PERCENTILE_CONT2 + : SqlLibraryOperators.PERCENTILE_DISC2; + SqlCall call = op.createCall(s.end(this), args); + if (nullTreatment != null) { + // Wrap in RESPECT_NULLS or IGNORE_NULLS. + call = nullTreatment.right.createCall(nullTreatment.left, call); + } + return call; + } + ) +} + + +/** + * Parses an atomic row expression. + */ +SqlNode AtomicRowExpression() : +{ + final SqlNode e; +} +{ + ( + LOOKAHEAD(2) + e = LiteralOrIntervalExpression() + | + e = DynamicParam() + | + LOOKAHEAD(2) + e = BuiltinFunctionCall() + | + e = JdbcFunctionCall() + | + e = MultisetConstructor() + | + e = ArrayConstructor() + | + LOOKAHEAD(3) + e = MapConstructor() + | + e = PeriodConstructor() + | + // NOTE jvs 18-Jan-2005: use syntactic lookahead to discriminate + // compound identifiers from function calls in which the function + // name is a compound identifier + LOOKAHEAD( [] FunctionName() ) + e = NamedFunctionCall() + | + e = ContextVariable() + | + e = CompoundIdentifier() + | + e = NewSpecification() + | + e = CaseExpression() + | + e = SequenceExpression() + ) + { return e; } +} + +SqlNode CaseExpression() : +{ + final Span whenSpan = Span.of(); + final Span thenSpan = Span.of(); + final Span s; + SqlNode e; + final SqlNode caseIdentifier; + final SqlNode elseClause; + final List whenList = new ArrayList(); + final List thenList = new ArrayList(); +} +{ + { s = span(); } + ( + caseIdentifier = Expression(ExprContext.ACCEPT_SUB_QUERY) + | { caseIdentifier = null; } + ) + ( + { whenSpan.add(this); } + e = ExpressionCommaList(s, ExprContext.ACCEPT_SUB_QUERY) { + if (((SqlNodeList) e).size() == 1) { + e = ((SqlNodeList) e).get(0); + } + whenList.add(e); + } + { thenSpan.add(this); } + e = Expression(ExprContext.ACCEPT_SUB_QUERY) { + thenList.add(e); + } + )+ + ( + elseClause = Expression(ExprContext.ACCEPT_SUB_QUERY) + | { elseClause = null; } + ) + { + return SqlCase.createSwitched(s.end(this), caseIdentifier, + new SqlNodeList(whenList, whenSpan.addAll(whenList).pos()), + new SqlNodeList(thenList, thenSpan.addAll(thenList).pos()), + elseClause); + } +} + +SqlCall SequenceExpression() : +{ + final Span s; + final SqlOperator f; + final SqlNode sequenceRef; +} +{ + ( + { f = SqlStdOperatorTable.NEXT_VALUE; s = span(); } + | + LOOKAHEAD(3) + { f = SqlStdOperatorTable.CURRENT_VALUE; s = span(); } + ) + sequenceRef = CompoundIdentifier() { + return f.createCall(s.end(sequenceRef), sequenceRef); + } +} + +/** + * Parses "SET <NAME> = VALUE" or "RESET <NAME>", without a leading + * "ALTER <SCOPE>". + */ +SqlSetOption SqlSetOption(Span s, String scope) : +{ + SqlIdentifier name; + final SqlNode val; +} +{ + ( + { + s.add(this); + } + name = CompoundIdentifier() + + ( + val = Literal() + | + val = SimpleIdentifier() + | + { + // OFF is handled by SimpleIdentifier, ON handled here. + val = new SqlIdentifier(token.image.toUpperCase(Locale.ROOT), + getPos()); + } + ) + { + return new SqlSetOption(s.end(val), scope, name, val); + } + | + { + s.add(this); + } + ( + name = CompoundIdentifier() + | + { + name = new SqlIdentifier(token.image.toUpperCase(Locale.ROOT), + getPos()); + } + ) + { + return new SqlSetOption(s.end(name), scope, name, null); + } + ) +} + +/** + * Parses an expression for setting or resetting an option in SQL, such as QUOTED_IDENTIFIERS, + * or explain plan level (physical/logical). + */ +SqlAlter SqlAlter() : +{ + final Span s; + final String scope; + final SqlAlter alterNode; +} +{ + { s = span(); } + scope = Scope() + ( +<#-- additional literal parser methods are included here --> +<#list (parser.alterStatementParserMethods!default.parser.alterStatementParserMethods) as method> + alterNode = ${method}(s, scope) + | + + + alterNode = SqlSetOption(s, scope) + ) + { + return alterNode; + } +} + +String Scope() : +{ +} +{ + ( | ) { return token.image.toUpperCase(Locale.ROOT); } +} + +<#if (parser.createStatementParserMethods!default.parser.createStatementParserMethods)?size != 0> +/** + * Parses a CREATE statement. + */ +SqlCreate SqlCreate() : +{ + final Span s; + boolean replace = false; + final SqlCreate create; +} +{ + { s = span(); } + [ + { + replace = true; + } + ] + ( +<#-- additional literal parser methods are included here --> +<#list (parser.createStatementParserMethods!default.parser.createStatementParserMethods) as method> + create = ${method}(s, replace) + <#sep>| LOOKAHEAD(2) + + ) + { + return create; + } +} + + +<#if (parser.dropStatementParserMethods!default.parser.dropStatementParserMethods)?size != 0> +/** + * Parses a DROP statement. + */ +SqlDrop SqlDrop() : +{ + final Span s; + boolean replace = false; + final SqlDrop drop; +} +{ + { s = span(); } + ( +<#-- additional literal parser methods are included here --> +<#list (parser.dropStatementParserMethods!default.parser.dropStatementParserMethods) as method> + drop = ${method}(s, replace) + <#sep>| + + ) + { + return drop; + } +} + + +<#if (parser.truncateStatementParserMethods!default.parser.truncateStatementParserMethods)?size != 0> +/** + * Parses a TRUNCATE statement. + */ +SqlTruncate SqlTruncate() : +{ + final Span s; + final SqlTruncate truncate; +} +{ + { s = span(); } + ( +<#-- additional literal parser methods are included here --> +<#list (parser.truncateStatementParserMethods!default.parser.truncateStatementParserMethods) as method> + truncate = ${method}(s) + <#sep>| + + ) + { + return truncate; + } +} + + +/** + * Parses a literal expression, allowing continued string literals. + * Usually returns an SqlLiteral, but a continued string literal + * is an SqlCall expression, which concatenates 2 or more string + * literals; the validator reduces this. + * + *

If the context allows both literals and expressions, + * use {@link #LiteralOrIntervalExpression}, which requires less + * lookahead. + */ +SqlNode Literal() : +{ + SqlNode e; +} +{ + ( + e = NonIntervalLiteral() + | + e = IntervalLiteral() + ) + { return e; } +} + +/** Parses a literal that is not an interval literal. */ +SqlNode NonIntervalLiteral() : +{ + final SqlNode e; +} +{ + ( + e = NumericLiteral() + | + e = StringLiteral() + | + e = SpecialLiteral() + | + e = DateTimeLiteral() +<#-- additional literal parser methods are included here --> +<#list (parser.literalParserMethods!default.parser.literalParserMethods) as method> + | + e = ${method} + + ) + { + return e; + } +} + +/** Parses a literal or an interval expression. + * + *

We include them in the same production because it is difficult to + * distinguish interval literals from interval expression (both of which + * start with the {@code INTERVAL} keyword); this way, we can use less + * LOOKAHEAD. */ +SqlNode LiteralOrIntervalExpression() : +{ + final SqlNode e; +} +{ + ( + e = IntervalLiteralOrExpression() + | + e = NonIntervalLiteral() + ) + { return e; } +} + +/** Parses a unsigned numeric literal */ +SqlNumericLiteral UnsignedNumericLiteral() : +{ +final String p; +} +{ + { + return SqlLiteral.createExactNumeric(token.image, getPos()); + } +| + { + return SqlLiteral.createExactNumeric(token.image, getPos()); + } +| + + p = SimpleStringLiteral() { + return SqlParserUtil.parseDecimalLiteral(SqlParserUtil.trim(p, " "), getPos()); + } +| + { + return SqlLiteral.createApproxNumeric(token.image, getPos()); + } +} + +/** Parses a numeric literal (can be signed) */ +SqlLiteral NumericLiteral() : +{ + final SqlNumericLiteral num; + final Span s; +} +{ + num = UnsignedNumericLiteral() { + return num; + } +| + { s = span(); } num = UnsignedNumericLiteral() { + return SqlLiteral.createNegative(num, s.end(this)); + } +| + num = UnsignedNumericLiteral() { + return num; + } +} + +/** Parse a special literal keyword */ +SqlLiteral SpecialLiteral() : +{ +} +{ + { return SqlLiteral.createBoolean(true, getPos()); } +| + { return SqlLiteral.createBoolean(false, getPos()); } +| + { return SqlLiteral.createUnknown(getPos()); } +| + { return SqlLiteral.createNull(getPos()); } +} + +/** + * Parses a string literal. The literal may be continued onto several + * lines. For a simple literal, the result is an SqlLiteral. For a continued + * literal, the result is an SqlCall expression, which concatenates 2 or more + * string literals; the validator reduces this. + * + * @see SqlLiteral#unchain(SqlNode) + * @see SqlLiteral#stringValue(SqlNode) + * + * @return a literal expression + */ +SqlNode StringLiteral() : +{ + String p; + final List frags; + char unicodeEscapeChar = 0; + String charSet = null; + SqlCharStringLiteral literal; +} +{ + // A continued string literal consists of a head fragment and one or more + // tail fragments. Since comments may occur between the fragments, and + // comments are special tokens, each fragment is a token. But since spaces + // or comments may not occur between the prefix and the first quote, the + // head fragment, with any prefix, is one token. + + + { + frags = new ArrayList(); + try { + p = SqlParserUtil.trim(token.image, "xX'"); + frags.add(SqlLiteral.createBinaryString(p, getPos())); + } catch (NumberFormatException ex) { + throw SqlUtil.newContextException(getPos(), + RESOURCE.illegalBinaryString(token.image)); + } + } + ( + // The grammar is ambiguous when a continued literals and a character + // string alias are both possible. For example, in + // SELECT x'01'\n'ab' + // we prefer that 'ab' continues the literal, and is not an alias. + // The following LOOKAHEAD mutes the warning about ambiguity. + LOOKAHEAD(1) + + { + try { + p = SqlParserUtil.trim(token.image, "'"); // no embedded quotes + frags.add(SqlLiteral.createBinaryString(p, getPos())); + } catch (NumberFormatException ex) { + throw SqlUtil.newContextException(getPos(), + RESOURCE.illegalBinaryString(token.image)); + } + } + )* + { + assert !frags.isEmpty(); + if (frags.size() == 1) { + return frags.get(0); // just the head fragment + } else { + SqlParserPos pos2 = SqlParserPos.sum(frags); + return SqlStdOperatorTable.LITERAL_CHAIN.createCall(pos2, frags); + } + } +| + ( + + { charSet = SqlParserUtil.getCharacterSet(token.image); } + | + | { + // TODO jvs 2-Feb-2009: support the explicit specification of + // a character set for Unicode string literals, per SQL:2003 + unicodeEscapeChar = BACKSLASH; + charSet = "UTF16"; + } + ) + { + frags = new ArrayList(); + p = SqlParserUtil.parseString(token.image); + try { + literal = SqlLiteral.createCharString(p, charSet, getPos()); + frags.add(literal); + } catch (java.nio.charset.UnsupportedCharsetException e) { + throw SqlUtil.newContextException(getPos(), + RESOURCE.unknownCharacterSet(charSet)); + } + } + ( + // The grammar is ambiguous when a continued literals and a character + // string alias are both possible. For example, in + // SELECT 'taxi'\n'cab' + // we prefer that 'cab' continues the literal, and is not an alias. + // The following LOOKAHEAD mutes the warning about ambiguity. + LOOKAHEAD(1) + + { + p = SqlParserUtil.parseString(token.image); + try { + literal = SqlLiteral.createCharString(p, charSet, getPos()); + frags.add(literal); + } catch (java.nio.charset.UnsupportedCharsetException e) { + throw SqlUtil.newContextException(getPos(), + RESOURCE.unknownCharacterSet(charSet)); + } + } + )* + [ + + { + if (unicodeEscapeChar == 0) { + throw SqlUtil.newContextException(getPos(), + RESOURCE.unicodeEscapeUnexpected()); + } + String s = SqlParserUtil.parseString(token.image); + unicodeEscapeChar = SqlParserUtil.checkUnicodeEscapeChar(s); + } + ] + { + assert !frags.isEmpty(); + if (frags.size() == 1) { + // just the head fragment + SqlLiteral lit = (SqlLiteral) frags.get(0); + return lit.unescapeUnicode(unicodeEscapeChar); + } else { + SqlNode[] rands = (SqlNode[]) frags.toArray(new SqlNode[0]); + for (int i = 0; i < rands.length; ++i) { + rands[i] = ((SqlLiteral) rands[i]).unescapeUnicode( + unicodeEscapeChar); + } + SqlParserPos pos2 = SqlParserPos.sum(rands); + return SqlStdOperatorTable.LITERAL_CHAIN.createCall(pos2, rands); + } + } +| + + { + try { + p = SqlParserUtil.parseCString(getToken(0).image); + } catch (SqlParserUtil.MalformedUnicodeEscape e) { + throw SqlUtil.newContextException(getPos(), + RESOURCE.unicodeEscapeMalformed(e.i)); + } + return SqlLiteral.createCharString(p, "UTF16", getPos()); + } +| + + { + p = SqlParserUtil.stripQuotes(getToken(0).image, DQ, DQ, "\\\"", + Casing.UNCHANGED); + try { + return SqlLiteral.createCharString(p, charSet, getPos()); + } catch (java.nio.charset.UnsupportedCharsetException e) { + throw SqlUtil.newContextException(getPos(), + RESOURCE.unknownCharacterSet(charSet)); + } + } +| + + { + p = SqlParserUtil.stripQuotes(getToken(0).image, "'", "'", "\\'", + Casing.UNCHANGED); + try { + return SqlLiteral.createCharString(p, charSet, getPos()); + } catch (java.nio.charset.UnsupportedCharsetException e) { + throw SqlUtil.newContextException(getPos(), + RESOURCE.unknownCharacterSet(charSet)); + } + } +} + +/** Parses a character literal. + * Matches a single-quoted string, such as 'foo'; + * on BigQuery also matches a double-quoted string, such as "foo". + * Returns the value of the string with quotes removed. */ +String SimpleStringLiteral() : +{ +} +{ + { + return SqlParserUtil.parseString(token.image); + } +| + { + return SqlParserUtil.stripQuotes(token.image, "'", "'", "\\'", Casing.UNCHANGED); + } +| + { + return SqlParserUtil.stripQuotes(token.image, DQ, DQ, "\\\"", Casing.UNCHANGED); + } +} + +/** + * Parses a date/time literal. + */ +SqlLiteral DateTimeLiteral() : +{ + final String p; + final Span s; + boolean local = false; +} +{ + { + p = SqlParserUtil.parseString(token.image); + } + { + return SqlParserUtil.parseDateLiteral(p, getPos()); + } +| + { + p = SqlParserUtil.parseString(token.image); + } + { + return SqlParserUtil.parseTimeLiteral(p, getPos()); + } +| + { s = span(); } { + p = SqlParserUtil.parseString(token.image); + } + { + return SqlParserUtil.parseTimestampLiteral(p, s.end(this)); + } +| + { s = span(); } p = SimpleStringLiteral() { + return SqlLiteral.createUnknown("DATE", p, s.end(this)); + } +| + { s = span(); } p = SimpleStringLiteral() { + return SqlLiteral.createUnknown("DATETIME", p, s.end(this)); + } +| + LOOKAHEAD(2) +

The units are used in several functions, incuding CEIL, FLOOR, EXTRACT. + * Includes NANOSECOND, MILLISECOND, which were previously allowed in EXTRACT + * but not CEIL, FLOOR. + * + *

Includes {@code WEEK} and {@code WEEK(SUNDAY)} through + {@code WEEK(SATURDAY)}. + * + *

Does not include SQL_TSI_DAY, SQL_TSI_FRAC_SECOND etc. These will be + * parsed as identifiers and can be resolved in the validator if they are + * registered as abbreviations in your time frame set. + */ +SqlIntervalQualifier TimeUnitOrName() : { + final SqlIdentifier unitName; + final SqlIntervalQualifier intervalQualifier; +} +{ + // When we see a time unit that is also a non-reserved keyword, such as + // NANOSECOND, there is a choice between using the TimeUnit enum + // (TimeUnit.NANOSECOND) or the name. The following LOOKAHEAD directive + // tells the parser that we prefer the former. + // + // Reserved keywords, such as SECOND, cannot be identifiers, and are + // therefore not ambiguous. + LOOKAHEAD(2) + intervalQualifier = TimeUnit() { + return intervalQualifier; + } +| unitName = SimpleIdentifier() { + return new SqlIntervalQualifier(unitName.getSimple(), + unitName.getParserPosition()); + } +} + +/** Parses a built-in time unit (e.g. "YEAR") + * and returns a {@link SqlIntervalQualifier}. + * + *

Includes {@code WEEK} and {@code WEEK(SUNDAY)} through + {@code WEEK(SATURDAY)}. + * + *

Does not include SQL_TSI_DAY, SQL_TSI_FRAC_SECOND etc. These will be + * parsed as identifiers and can be resolved in the validator if they are + * registered as abbreviations in your time frame set. + */ +SqlIntervalQualifier TimeUnit() : { + final Span span; + final String w; +} +{ + { return new SqlIntervalQualifier(TimeUnit.NANOSECOND, null, getPos()); } +| { return new SqlIntervalQualifier(TimeUnit.MICROSECOND, null, getPos()); } +| { return new SqlIntervalQualifier(TimeUnit.MILLISECOND, null, getPos()); } +| { return new SqlIntervalQualifier(TimeUnit.SECOND, null, getPos()); } +| { return new SqlIntervalQualifier(TimeUnit.MINUTE, null, getPos()); } +| { return new SqlIntervalQualifier(TimeUnit.HOUR, null, getPos()); } +| { return new SqlIntervalQualifier(TimeUnit.DAY, null, getPos()); } +| { return new SqlIntervalQualifier(TimeUnit.DOW, null, getPos()); } +| { return new SqlIntervalQualifier(TimeUnit.DOY, null, getPos()); } +| { return new SqlIntervalQualifier(TimeUnit.DOW, null, getPos()); } +| { return new SqlIntervalQualifier(TimeUnit.DOY, null, getPos()); } +| { return new SqlIntervalQualifier(TimeUnit.ISODOW, null, getPos()); } +| { return new SqlIntervalQualifier(TimeUnit.ISOYEAR, null, getPos()); } +| { span = span(); } + ( + // There is a choice between "WEEK(weekday)" and "WEEK". We prefer + // the former, and the parser will look ahead for '('. + LOOKAHEAD(2) + w = weekdayName() { + return new SqlIntervalQualifier(w, span.end(this)); + } + | + { return new SqlIntervalQualifier(TimeUnit.WEEK, null, getPos()); } + ) +| { return new SqlIntervalQualifier(TimeUnit.MONTH, null, getPos()); } +| { return new SqlIntervalQualifier(TimeUnit.QUARTER, null, getPos()); } +| { return new SqlIntervalQualifier(TimeUnit.YEAR, null, getPos()); } +| { return new SqlIntervalQualifier(TimeUnit.EPOCH, null, getPos()); } +| { return new SqlIntervalQualifier(TimeUnit.DECADE, null, getPos()); } +| { return new SqlIntervalQualifier(TimeUnit.CENTURY, null, getPos()); } +| { return new SqlIntervalQualifier(TimeUnit.MILLENNIUM, null, getPos()); } +} + +String weekdayName() : +{ +} +{ + { return "WEEK_SUNDAY"; } +| { return "WEEK_MONDAY"; } +| { return "WEEK_TUESDAY"; } +| { return "WEEK_WEDNESDAY"; } +| { return "WEEK_THURSDAY"; } +| { return "WEEK_FRIDAY"; } +| { return "WEEK_SATURDAY"; } +} + +/** + * Parses a dynamic parameter marker. + */ +SqlDynamicParam DynamicParam() : +{ +} +{ + { + return new SqlDynamicParam(nDynamicParams++, getPos()); + } +} + +/** + * Parses one segment of an identifier that may be composite. + * + *

Each time it reads an identifier it writes one element to each list; + * the entry in {@code positions} records its position and whether the + * segment was quoted. + */ +void AddIdentifierSegment(List names, List positions) : +{ + final String id; + char unicodeEscapeChar = BACKSLASH; + final SqlParserPos pos; + final Span span; +} +{ + ( + { + id = unquotedIdentifier(); + pos = getPos(); + } + | + { + id = unquotedIdentifier(); + pos = getPos(); + } + | + { + id = SqlParserUtil.stripQuotes(getToken(0).image, DQ, DQ, DQDQ, + quotedCasing); + pos = getPos().withQuoting(true); + } + | + { + id = SqlParserUtil.stripQuotes(getToken(0).image, "`", "`", "``", + quotedCasing); + pos = getPos().withQuoting(true); + } + | + { + id = SqlParserUtil.stripQuotes(getToken(0).image, "`", "`", "\\`", + quotedCasing); + pos = getPos().withQuoting(true); + } + | + { + id = SqlParserUtil.stripQuotes(getToken(0).image, "[", "]", "]]", + quotedCasing); + pos = getPos().withQuoting(true); + } + | + { + span = span(); + String image = getToken(0).image; + image = image.substring(image.indexOf('"')); + image = SqlParserUtil.stripQuotes(image, DQ, DQ, DQDQ, quotedCasing); + } + [ + { + String s = SqlParserUtil.parseString(token.image); + unicodeEscapeChar = SqlParserUtil.checkUnicodeEscapeChar(s); + } + ] + { + pos = span.end(this).withQuoting(true); + SqlLiteral lit = SqlLiteral.createCharString(image, "UTF16", pos); + lit = lit.unescapeUnicode(unicodeEscapeChar); + id = lit.toValue(); + } + | + id = NonReservedKeyWord() { + pos = getPos(); + } + ) + { + if (id.length() > this.identifierMaxLength) { + throw SqlUtil.newContextException(pos, + RESOURCE.identifierTooLong(id, this.identifierMaxLength)); + } + names.add(id); + if (positions != null) { + positions.add(pos); + } + } +} + +/** As {@link #AddIdentifierSegment} but part of a table name (for example, + * following {@code FROM}, {@code INSERT} or {@code UPDATE}). + * + *

In some dialects the lexical rules for table names are different from + * for other identifiers. For example, in BigQuery, table names may contain + * hyphens. */ +void AddTableIdentifierSegment(List names, List positions) : +{ +} +{ + AddIdentifierSegment(names, positions) { + final int n = names.size(); + if (n > 0 + && positions.size() == n + && names.get(n - 1).contains(".") + && positions.get(n - 1).isQuoted() + && this.conformance.splitQuotedTableName()) { + final String name = names.remove(n - 1); + final SqlParserPos pos = positions.remove(n - 1); + final String[] splitNames = name.split("\\."); + for (String splitName : splitNames) { + names.add(splitName); + positions.add(pos); + } + } + } +} + +/** + * Parses a simple identifier as a String. + */ +String Identifier() : +{ + final List names = new ArrayList(); +} +{ + AddIdentifierSegment(names, null) { + return names.get(0); + } +} + +/** + * Parses a simple identifier as an SqlIdentifier. + */ +SqlIdentifier SimpleIdentifier() : +{ + final List names = new ArrayList(); + final List positions = new ArrayList(); +} +{ + AddIdentifierSegment(names, positions) { + return new SqlIdentifier(names.get(0), positions.get(0)); + } +} + +/** + * Parses a character literal as an SqlIdentifier. + * Only valid for column aliases in certain dialects. + */ +SqlIdentifier SimpleIdentifierFromStringLiteral() : +{ +} +{ + { + if (!this.conformance.allowCharLiteralAlias()) { + throw SqlUtil.newContextException(getPos(), RESOURCE.charLiteralAliasNotValid()); + } + final String s = SqlParserUtil.parseString(token.image); + return new SqlIdentifier(s, getPos()); + } +} + +/** + * Parses a comma-separated list of simple identifiers. + */ +void AddSimpleIdentifiers(List list) : +{ + SqlIdentifier id; +} +{ + id = SimpleIdentifier() {list.add(id);} + ( + id = SimpleIdentifier() { + list.add(id); + } + )* +} + +/** + * List of simple identifiers in parentheses. The position extends from the + * open parenthesis to the close parenthesis. + */ +SqlNodeList ParenthesizedSimpleIdentifierList() : +{ + final Span s; + final List list = new ArrayList(); +} +{ + { s = span(); } + AddSimpleIdentifiers(list) + { + return new SqlNodeList(list, s.end(this)); + } +} + +/** List of simple identifiers in parentheses or one simple identifier. + * + *

    Examples: + *
  • {@code DEPTNO} + *
  • {@code (EMPNO, DEPTNO)} + *
+ */ +SqlNodeList SimpleIdentifierOrList() : +{ + SqlIdentifier id; + SqlNodeList list; +} +{ + id = SimpleIdentifier() { + return new SqlNodeList(Collections.singletonList(id), id.getParserPosition()); + } +| + list = ParenthesizedSimpleIdentifierList() { + return list; + } +} + +<#if (parser.includeCompoundIdentifier!default.parser.includeCompoundIdentifier) > +/** + * Parses a compound identifier. + */ +SqlIdentifier CompoundIdentifier() : +{ + final List nameList = new ArrayList(); + final List posList = new ArrayList(); + boolean star = false; +} +{ + AddIdentifierSegment(nameList, posList) + ( + LOOKAHEAD(2) + + AddIdentifierSegment(nameList, posList) + )* + ( + LOOKAHEAD(2) + + { + star = true; + nameList.add(""); + posList.add(getPos()); + } + )? + { + SqlParserPos pos = SqlParserPos.sum(posList); + if (star) { + return SqlIdentifier.star(nameList, pos, posList); + } + return new SqlIdentifier(nameList, null, pos, posList); + } +} + +/** + * Parses a compound identifier in the FROM clause. + */ +SqlIdentifier CompoundTableIdentifier() : +{ + final List nameList = new ArrayList(); + final List posList = new ArrayList(); +} +{ + AddTableIdentifierSegment(nameList, posList) + ( + LOOKAHEAD(2) + + AddTableIdentifierSegment(nameList, posList) + )* + { + SqlParserPos pos = SqlParserPos.sum(posList); + return new SqlIdentifier(nameList, null, pos, posList); + } +} + +/** + * Parses a comma-separated list of compound identifiers. + */ +void AddCompoundIdentifierTypes(List list, List extendList) : +{ +} +{ + AddCompoundIdentifierType(list, extendList) + ( AddCompoundIdentifierType(list, extendList))* +} + +/** + * List of compound identifiers in parentheses. The position extends from the + * open parenthesis to the close parenthesis. + */ +Pair ParenthesizedCompoundIdentifierList() : +{ + final Span s; + final List list = new ArrayList(); + final List extendList = new ArrayList(); +} +{ + { s = span(); } + AddCompoundIdentifierTypes(list, extendList) + { + return Pair.of(new SqlNodeList(list, s.end(this)), new SqlNodeList(extendList, s.end(this))); + } +} +<#else> + <#include "/@includes/compoundIdentifier.ftl" /> + + +/** + * Parses a NEW UDT(...) expression. + */ +SqlNode NewSpecification() : +{ + final Span s; + final SqlNode routineCall; +} +{ + { s = span(); } + routineCall = + NamedRoutineCall(SqlFunctionCategory.USER_DEFINED_CONSTRUCTOR, + ExprContext.ACCEPT_SUB_QUERY) { + return SqlStdOperatorTable.NEW.createCall(s.end(routineCall), routineCall); + } +} + +//TODO: real parse errors. +int UnsignedIntLiteral() : +{ + Token t; +} +{ + t = + { + try { + return Integer.parseInt(t.image); + } catch (NumberFormatException ex) { + throw SqlUtil.newContextException(getPos(), + RESOURCE.invalidLiteral(t.image, Integer.class.getCanonicalName())); + } + } +} + +int IntLiteral() : +{ + Token t; +} +{ + ( + t = + | + t = + ) + { + try { + return Integer.parseInt(t.image); + } catch (NumberFormatException ex) { + throw SqlUtil.newContextException(getPos(), + RESOURCE.invalidLiteral(t.image, Integer.class.getCanonicalName())); + } + } +| + t = { + try { + return -Integer.parseInt(t.image); + } catch (NumberFormatException ex) { + throw SqlUtil.newContextException(getPos(), + RESOURCE.invalidLiteral(t.image, Integer.class.getCanonicalName())); + } + } +} + +// Type name with optional scale and precision. +SqlDataTypeSpec DataType() : +{ + SqlTypeNameSpec typeName; + final Span s; +} +{ + typeName = TypeName() { + s = Span.of(typeName.getParserPos()); + } + ( + typeName = CollectionsTypeName(typeName) + )* + { + return new SqlDataTypeSpec(typeName, s.add(typeName.getParserPos()).pos()); + } +} + +// Some SQL type names need special handling due to the fact that they have +// spaces in them but are not quoted. +SqlTypeNameSpec TypeName() : +{ + final SqlTypeNameSpec typeNameSpec; + final SqlIdentifier typeName; + final Span s = Span.of(); +} +{ + ( +<#-- additional types are included here --> +<#-- put custom data types in front of Calcite core data types --> +<#list (parser.dataTypeParserMethods!default.parser.dataTypeParserMethods) as method> + LOOKAHEAD(2) + typeNameSpec = ${method} + | + + LOOKAHEAD(2) + typeNameSpec = SqlTypeName(s) + | + typeNameSpec = RowTypeName() + | + LOOKAHEAD(2) + typeNameSpec = MapTypeName() + | + typeName = CompoundIdentifier() { + typeNameSpec = new SqlUserDefinedTypeNameSpec(typeName, s.end(this)); + } + ) + { + return typeNameSpec; + } +} + +// Types used for JDBC and ODBC scalar conversion function +SqlTypeNameSpec SqlTypeName(Span s) : +{ + final SqlTypeNameSpec sqlTypeNameSpec; +} +{ + ( + sqlTypeNameSpec = SqlTypeName1(s) + | + sqlTypeNameSpec = SqlTypeName2(s) + | + sqlTypeNameSpec = SqlTypeName3(s) + | + sqlTypeNameSpec = CharacterTypeName(s) + | + sqlTypeNameSpec = DateTimeTypeName() + ) + { + return sqlTypeNameSpec; + } +} + +// Parse sql type name that don't allow any extra specifications except the type name. +// For extra specification, we mean precision, scale, charSet, etc. +SqlTypeNameSpec SqlTypeName1(Span s) : +{ + final SqlTypeName sqlTypeName; +} +{ + ( + { + if (!this.conformance.allowGeometry()) { + throw SqlUtil.newContextException(getPos(), RESOURCE.geometryDisabled()); + } + s.add(this); + sqlTypeName = SqlTypeName.GEOMETRY; + } + | + { s.add(this); sqlTypeName = SqlTypeName.BOOLEAN; } + | + ( | ) { s.add(this); sqlTypeName = SqlTypeName.INTEGER; } + | + { s.add(this); sqlTypeName = SqlTypeName.TINYINT; } + | + { s.add(this); sqlTypeName = SqlTypeName.SMALLINT; } + | + { s.add(this); sqlTypeName = SqlTypeName.BIGINT; } + | + { s.add(this); sqlTypeName = SqlTypeName.REAL; } + | + { s.add(this); } + [ ] { sqlTypeName = SqlTypeName.DOUBLE; } + | + { s.add(this); sqlTypeName = SqlTypeName.FLOAT; } + ) + { + return new SqlBasicTypeNameSpec(sqlTypeName, s.end(this)); + } +} + +// Parse sql type name that allows precision specification. +SqlTypeNameSpec SqlTypeName2(Span s) : +{ + final SqlTypeName sqlTypeName; + int precision = -1; +} +{ + ( + { s.add(this); } + ( + { sqlTypeName = SqlTypeName.VARBINARY; } + | + { sqlTypeName = SqlTypeName.BINARY; } + ) + | + { s.add(this); sqlTypeName = SqlTypeName.VARBINARY; } + ) + precision = PrecisionOpt() + { + return new SqlBasicTypeNameSpec(sqlTypeName, precision, s.end(this)); + } +} + +// Parse sql type name that allows precision and scale specifications. +SqlTypeNameSpec SqlTypeName3(Span s) : +{ + final SqlTypeName sqlTypeName; + int precision = -1; + int scale = -1; +} +{ + ( + ( | | ) { s.add(this); sqlTypeName = SqlTypeName.DECIMAL; } + | + { s.add(this); sqlTypeName = SqlTypeName.ANY; } + ) + [ + + precision = UnsignedIntLiteral() + [ + + scale = UnsignedIntLiteral() + ] + + ] + { + return new SqlBasicTypeNameSpec(sqlTypeName, precision, scale, s.end(this)); + } +} + +// Types used for for JDBC and ODBC scalar conversion function +SqlJdbcDataTypeName JdbcOdbcDataTypeName() : +{ +} +{ + ( | ) { return SqlJdbcDataTypeName.SQL_CHAR; } +| ( | ) { return SqlJdbcDataTypeName.SQL_VARCHAR; } +| ( | ) { return SqlJdbcDataTypeName.SQL_DATE; } +| ( |

: - No segment found in table. + No reload status found in table. } { + const columns = ['Tenant Name', 'Server', 'Broker', 'Tables']; + const [tenantsData, setTenantsData] = useState({ + records: [columns.map((_) => Loading)], + columns: columns, + }); + + const fetchData = async () => { + getTenants().then((res) => { + const tenantNames = union( + res.data.SERVER_TENANTS, + res.data.BROKER_TENANTS + ); + setTenantsData({ + columns: columns, + records: tenantNames.map((tenantName) => { + return [tenantName, Loading, Loading, Loading]; + }), + }); + + tenantNames.forEach((tenantName) => { + Promise.all([ + PinotMethodUtils.getServerOfTenant(tenantName).then((res) => { + return res?.length || 0; + }), + PinotMethodUtils.getBrokerOfTenant(tenantName).then((res) => { + return Array.isArray(res) ? res?.length || 0 : 0; + }), + getTenantTable(tenantName).then((res) => { + return res?.data?.tables?.length || 0; + }), + ]).then((res) => { + const numServers = res[0]; + const numBrokers = res[1]; + const numTables = res[2]; + setTenantsData((prev) => { + const newRecords = prev.records.map((record) => { + if (record[0] === tenantName) { + return [tenantName, numServers, numBrokers, numTables]; + } + return record; + }); + return { + columns: prev.columns, + records: newRecords, + }; + }); + }); + }); + }); + }; + + useEffect(() => { + fetchData(); + }, []); -const TenantsTable = ({tenantsData}) => { - return ( }; + +export default Loading; diff --git a/pinot-controller/src/main/resources/app/components/NotFound.tsx b/pinot-controller/src/main/resources/app/components/NotFound.tsx new file mode 100644 index 000000000000..93af7854721b --- /dev/null +++ b/pinot-controller/src/main/resources/app/components/NotFound.tsx @@ -0,0 +1,69 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import TableToolbar from './TableToolbar'; +import { Grid } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; + +const useStyles = makeStyles((theme) => ({ + root: { + border: '1px #BDCCD9 solid', + borderRadius: 4, + marginBottom: '20px', + }, + background: { + padding: 20, + backgroundColor: 'white', + maxHeight: 'calc(100vh - 70px)', + overflowY: 'auto', + }, + highlightBackground: { + border: '1px #4285f4 solid', + backgroundColor: 'rgba(66, 133, 244, 0.05)', + borderRadius: 4, + marginBottom: '20px', + }, + body: { + borderTop: '1px solid #BDCCD9', + fontSize: '16px', + lineHeight: '3rem', + paddingLeft: '15px', + }, +})); + +type Props = { + message: String; +}; + +export default function NotFound(props: Props) { + const classes = useStyles(); + return ( + +
+ + + +

{props.message}

+
+
+
+
+ ); +} diff --git a/pinot-controller/src/main/resources/app/components/Query/QuerySideBar.tsx b/pinot-controller/src/main/resources/app/components/Query/QuerySideBar.tsx index d886d3b1286c..8e73d422d7cc 100644 --- a/pinot-controller/src/main/resources/app/components/Query/QuerySideBar.tsx +++ b/pinot-controller/src/main/resources/app/components/Query/QuerySideBar.tsx @@ -66,7 +66,8 @@ const useStyles = makeStyles((theme: Theme) => }, leftPanel: { width: 300, - padding: '0 20px' + padding: '0 20px', + wordBreak: 'break-all', }, }), ); @@ -101,6 +102,7 @@ const Sidebar = ({ tableList, fetchSQLData, tableSchema, selectedTable, queryLoa cellClickCallback={fetchSQLData} isCellClickable showSearchBox={true} + inAccordionFormat /> {!queryLoader && tableSchema.records.length ? ( @@ -109,6 +111,7 @@ const Sidebar = ({ tableList, fetchSQLData, tableSchema, selectedTable, queryLoa data={tableSchema} highlightBackground showSearchBox={true} + inAccordionFormat /> ) : null} diff --git a/pinot-controller/src/main/resources/app/components/SegmentStatusRenderer.tsx b/pinot-controller/src/main/resources/app/components/SegmentStatusRenderer.tsx index 7cd34fc1deaa..066bf1ccc810 100644 --- a/pinot-controller/src/main/resources/app/components/SegmentStatusRenderer.tsx +++ b/pinot-controller/src/main/resources/app/components/SegmentStatusRenderer.tsx @@ -99,7 +99,7 @@ export const SegmentStatusRenderer = ({ break; } - case DISPLAY_SEGMENT_STATUS.PARTIAL: { + case DISPLAY_SEGMENT_STATUS.UPDATING: { setStatusColor(StatusColor.Warning); setStatusTooltipTitle("External view is OFFLINE or missing for one or more servers of this segment"); diff --git a/pinot-controller/src/main/resources/app/components/Table.tsx b/pinot-controller/src/main/resources/app/components/Table.tsx index b5871d788e0b..09226e28d582 100644 --- a/pinot-controller/src/main/resources/app/components/Table.tsx +++ b/pinot-controller/src/main/resources/app/components/Table.tsx @@ -281,7 +281,7 @@ export default function CustomizedTables({ accordionToggleObject, tooltipData }: Props) { - // Separate the initial and final data into two separte state variables. + // Separate the initial and final data into two separated state variables. // This way we can filter and sort the data without affecting the original data. // If the component receives new data, we can simply set the new data to the initial data, // and the filters and sorts will be applied to the new data. @@ -333,7 +333,8 @@ export default function CustomizedTables({ } return false; }); - setFinalData(filteredRescords); + let filteredData = {...initialData, records: filteredRescords}; + setFinalData(Utils.tableFormat(filteredData)); } }, [initialData, setFinalData]); @@ -341,6 +342,9 @@ export default function CustomizedTables({ clearTimeout(timeoutId.current); timeoutId.current = setTimeout(() => { filterSearchResults(search.toLowerCase()); + // Table.tsx currently doesn't support sorting after filtering. So for now, we just + // remove the visual indicator of the sorted column until users sort again. + setColumnClicked('') }, 200); return () => { @@ -349,7 +353,7 @@ export default function CustomizedTables({ }, [search, timeoutId, filterSearchResults]); const styleCell = (str: string) => { - if (str === 'Good' || str.toLowerCase() === 'online' || str.toLowerCase() === 'alive' || str.toLowerCase() === 'true') { + if (str.toLowerCase() === 'good' || str.toLowerCase() === 'online' || str.toLowerCase() === 'alive' || str.toLowerCase() === 'true') { return ( ); } - if (str === 'Bad' || str.toLowerCase() === 'offline' || str.toLowerCase() === 'dead' || str.toLowerCase() === 'false') { + if (str.toLocaleLowerCase() === 'bad' || str.toLowerCase() === 'offline' || str.toLowerCase() === 'dead' || str.toLowerCase() === 'false') { return ( ); } - if (str.toLowerCase() === 'consuming') { + if (str.toLowerCase() === 'consuming' || str.toLocaleLowerCase() === "partial" || str.toLocaleLowerCase() === "updating" ) { return ( ); } + if (str.search('\n') !== -1) { + return (
{str.toString()}
); + } return ({str.toString()}); }; @@ -530,7 +537,7 @@ export default function CustomizedTables({ const matches = baseURL.match(regex); url = baseURL.replace(matches[0], row[matches[0].replace(/:/g, '')]); } - return addLinks && !idx ? ( + return addLinks && typeof cell === 'string' && !idx ? ( {cell} diff --git a/pinot-controller/src/main/resources/app/components/auth/AuthProvider.tsx b/pinot-controller/src/main/resources/app/components/auth/AuthProvider.tsx new file mode 100644 index 000000000000..c510438bc0d3 --- /dev/null +++ b/pinot-controller/src/main/resources/app/components/auth/AuthProvider.tsx @@ -0,0 +1,281 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { AuthLocalStorageKeys, AuthWorkflow } from 'Models'; +import React, { createContext, useContext, useEffect, useState } from 'react' +import { useHistory, useLocation } from 'react-router'; +import { baseApi, getAxiosErrorInterceptor, getAxiosRequestInterceptor, getAxiosResponseInterceptor, transformApi } from '../../utils/axios-config'; +import PinotMethodUtils from '../../utils/PinotMethodUtils'; +import { AppLoadingIndicator } from '../AppLoadingIndicator'; + +interface AuthProviderContextProps { + accessToken: string; + authUserName: string; + authUserEmail: string; + authenticated: boolean; + authWorkflow: AuthWorkflow +} + +export const AuthProvider = ({ children }) => { + const [loading, setLoading] = useState(true); + const [redirectUri, setRedirectUri] = useState(null); + const [clientId, setClientId] = useState(null); + const [authWorkflow, setAuthWorkflow] = useState(null); + const [accessToken, setAccessToken] = useState(""); + const [authUserName, setAuthUserName] = useState(""); + const [authUserEmail, setAuthUserEmail] = useState(""); + const [authenticated, setAuthenticated] = useState(false); + const [authorizationEndpoint, setAuthorizationEndpoint] = useState(null); + const [autoLogout, setAutoLogout] = useState(false); + const history = useHistory(); + const location = useLocation(); + const [axiosRequestInterceptorIds, setAxiosRequestInterceptorIds] = + useState([0, 1]); + const [axiosResponseInterceptorIds, setAxiosResponseInterceptorIds] = + useState([0, 1]); + const oidcSignInFormRef = React.useRef(null); + + useEffect(() => { + initAuthDetails(); + }, []); + + useEffect(() => { + if (loading || authenticated) { + return; + } + + initOidcAuth(); + }, [loading, authenticated]); + + useEffect(() => { + if (!autoLogout) { + return; + } + + submitLoginForm(); + }, [autoLogout]) + + const initAuthDetails = async () => { + // fetch auth info details + const authInfoResponse = await PinotMethodUtils.getAuthInfo(); + + const authWorkFlowInternal = + authInfoResponse && authInfoResponse.workflow + ? authInfoResponse.workflow + : AuthWorkflow.NONE; + + // set auth workflow + setAuthWorkflow(authWorkFlowInternal); + + if (authWorkFlowInternal === AuthWorkflow.NONE) { + // No authentication required + setAuthenticated(true); + } + + if (authWorkFlowInternal === AuthWorkflow.BASIC) { + // basic auth is handled by login page + } + + // set OIDC auth details + if (authWorkFlowInternal === AuthWorkflow.OIDC) { + const issuer = + authInfoResponse && authInfoResponse.issuer ? authInfoResponse.issuer : ''; + + setAuthorizationEndpoint(`${issuer}/auth`); + setRedirectUri( + authInfoResponse && authInfoResponse.redirectUri + ? authInfoResponse.redirectUri + : '' + ); + setClientId( + authInfoResponse && authInfoResponse.clientId + ? authInfoResponse.clientId + : '' + ); + } + + // auth loading complete + setLoading(false); + } + + const initOidcAuth = () => { + // access token already available in the localStorage + const accessToken = getAuthLocalStorageValue(AuthLocalStorageKeys.AccessToken); + if (accessToken) { + setAccessToken(accessToken); + setAuthUserName(PinotMethodUtils.getAuthUserNameFromAccessToken(accessToken.replace("Bearer ", ""))) + setAuthUserEmail(PinotMethodUtils.getAuthUserEmailFromAccessToken(accessToken.replace("Bearer ", ""))) + + initAxios(accessToken); + setAuthenticated(true); + + return; + } + + // access token available in hash params + const accessTokenFromHashParam = PinotMethodUtils.getAccessTokenFromHashParams(); + if (accessTokenFromHashParam) { + const accessToken = `Bearer ${accessTokenFromHashParam}`; + setAccessToken(accessToken); + setAuthUserName(PinotMethodUtils.getAuthUserNameFromAccessToken(accessTokenFromHashParam)) + setAuthUserEmail(PinotMethodUtils.getAuthUserEmailFromAccessToken(accessTokenFromHashParam)) + + setAuthLocalStorageValue(AuthLocalStorageKeys.AccessToken, accessToken); + initAxios(accessToken); + setAuthenticated(true); + redirectToApp(); + + return; + } + + // no access token available + const redirectPathAfterLogin = location.pathname; + // save current path to redirect after login + setAuthLocalStorageValue(AuthLocalStorageKeys.RedirectLocation, redirectPathAfterLogin); + // login + submitLoginForm(); + } + + const submitLoginForm = () => { + // submit auth login form + if (clientId && authorizationEndpoint && redirectUri && oidcSignInFormRef && oidcSignInFormRef.current) { + oidcSignInFormRef.current.submit(); + } + } + + const handleUnauthenticatedAccess = () => { + setAuthLocalStorageValue(AuthLocalStorageKeys.AccessToken, ""); + setAuthLocalStorageValue(AuthLocalStorageKeys.RedirectLocation, ""); + + setAutoLogout(true); + } + + // initialize axios instance with authToken + const initAxios = (accessToken: string) => { + + // Clear existing interceptors + baseApi.interceptors.request.eject(axiosRequestInterceptorIds[0]); + baseApi.interceptors.response.eject(axiosResponseInterceptorIds[0]); + + transformApi.interceptors.request.eject(axiosRequestInterceptorIds[1]); + transformApi.interceptors.response.eject(axiosResponseInterceptorIds[1]); + + const requestInterceptorId1 = baseApi.interceptors.request.use( + getAxiosRequestInterceptor(accessToken), + getAxiosErrorInterceptor(handleUnauthenticatedAccess) + ); + + const requestInterceptorId2 = transformApi.interceptors.request.use( + getAxiosRequestInterceptor(accessToken), + getAxiosErrorInterceptor(handleUnauthenticatedAccess) + ); + + const responseInterceptor1 = baseApi.interceptors.response.use( + getAxiosResponseInterceptor(), + getAxiosErrorInterceptor(handleUnauthenticatedAccess) + ); + + const responseInterceptor2 = transformApi.interceptors.response.use( + getAxiosResponseInterceptor(), + getAxiosErrorInterceptor(handleUnauthenticatedAccess) + ); + + // Set new interceptors + setAxiosRequestInterceptorIds([requestInterceptorId1, requestInterceptorId2]); + setAxiosResponseInterceptorIds([responseInterceptor1, responseInterceptor2]); + } + + // redirect to app with appropriate location after login + const redirectToApp = () => { + const redirectLocation = getAuthLocalStorageValue(AuthLocalStorageKeys.RedirectLocation); + if (redirectLocation && redirectLocation !== "/login" && redirectLocation !== "/logout") { + setAuthLocalStorageValue(AuthLocalStorageKeys.RedirectLocation, ""); + history.push(redirectLocation); + } + } + + const getAuthLocalStorageValue = (key: AuthLocalStorageKeys) => { + return (localStorage.getItem(key) || ""); + } + + const setAuthLocalStorageValue = (key: AuthLocalStorageKeys, value: string) => { + localStorage.setItem(key, value); + } + + const authProvider: AuthProviderContextProps = { + authWorkflow: authWorkflow, + accessToken: accessToken, + authUserName: authUserName, + authUserEmail: authUserEmail, + authenticated: authenticated + } + + if (loading) { + return + } + + + return ( + + {children} + + {authWorkflow === AuthWorkflow.OIDC && (
+ {/* Login form */} + +
)} +
+ ) +} + +const AuthProviderContext = createContext( + {} as AuthProviderContextProps +); + +export const useAuthProvider = (): AuthProviderContextProps => { + return useContext(AuthProviderContext); +}; diff --git a/pinot-controller/src/main/resources/app/index.tsx b/pinot-controller/src/main/resources/app/index.tsx new file mode 100644 index 000000000000..d06d57bbc491 --- /dev/null +++ b/pinot-controller/src/main/resources/app/index.tsx @@ -0,0 +1,42 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { MuiThemeProvider } from "@material-ui/core"; +import { AuthProvider } from "./components/auth/AuthProvider"; +import CustomNotification from "./components/CustomNotification"; +import { NotificationContextProvider } from "./components/Notification/NotificationContextProvider"; +import theme from "./theme"; +import React from "react"; +import ReactDOM from "react-dom"; +import { App } from "./App"; +import { HashRouter } from "react-router-dom"; + +ReactDOM.render( + + + + + + + + + + , + document.getElementById('app') +); diff --git a/pinot-controller/src/main/resources/app/interfaces/types.d.ts b/pinot-controller/src/main/resources/app/interfaces/types.d.ts index 79b28d7e16d9..52f937bf0a31 100644 --- a/pinot-controller/src/main/resources/app/interfaces/types.d.ts +++ b/pinot-controller/src/main/resources/app/interfaces/types.d.ts @@ -18,13 +18,31 @@ */ declare module 'Models' { + export type SegmentStatus = { + value: DISPLAY_SEGMENT_STATUS, + tooltip: string, + component?: JSX.Element + } + + export type LoadingRecord = { + customRenderer: JSX.Element + } + export type TableData = { - records: Array>; + records: Array>; columns: Array; error?: string; isLoading? : boolean }; + export type PinotTableDetails = { + name: string, + reported_size: string, + estimated_size: string, + number_of_segments: string, + segment_status: SegmentStatus, + } + type SchemaDetails = { schemaName: string, totalColumns: number, @@ -47,6 +65,11 @@ declare module 'Models' { hostName: string; enabled: boolean; port: number; + grpcPort: number; + adminPort: number; + queryServicePort: number; + queryMailboxPort: number; + queriesDisabled: boolean; tags: Array; pools?: string; }; @@ -74,6 +97,12 @@ declare module 'Models' { segments: Object; }; + type SegmentMetadata = { + indexes?: any; + columns: Array; + code?: number; + }; + export type IdealState = { OFFLINE: Object | null; REALTIME: Object | null; @@ -92,6 +121,7 @@ declare module 'Models' { metricFieldSpecs?: Array; dateTimeFieldSpecs?: Array; error?: string; + code?: number; }; type schema = { @@ -140,7 +170,8 @@ declare module 'Models' { export type ZKConfig = { ctime: any, - mtime: any + mtime: any, + code?: number }; export type OperationResponse = any; @@ -165,6 +196,11 @@ declare module 'Models' { OIDC = 'OIDC', } + export const enum AuthLocalStorageKeys { + RedirectLocation = "redirectLocation", + AccessToken = "AccessToken", + } + export type TableList = { tables: Array } @@ -235,6 +271,28 @@ declare module 'Models' { export const enum DISPLAY_SEGMENT_STATUS { BAD = "BAD", GOOD = "GOOD", - PARTIAL = "PARTIAL", + UPDATING = "UPDATING", + } + + export const enum InstanceState { + ENABLE = "enable", + DISABLE = "disable" + } + + export const enum InstanceType { + BROKER = "BROKER", + CONTROLLER = "CONTROLLER", + MINION = "MINION", + SERVER = "SERVER" + } + + export const enum TableType { + REALTIME = "realtime", + OFFLINE = "offline" + } + + export interface SqlException { + errorCode: number, + message: string } } diff --git a/pinot-controller/src/main/resources/app/pages/HomePage.tsx b/pinot-controller/src/main/resources/app/pages/HomePage.tsx index 1b17c046de96..73f16342cf27 100644 --- a/pinot-controller/src/main/resources/app/pages/HomePage.tsx +++ b/pinot-controller/src/main/resources/app/pages/HomePage.tsx @@ -17,19 +17,24 @@ * under the License. */ -import React, {useState, useEffect} from 'react'; +import React, { useState, useEffect } from 'react'; +import { get, union } from 'lodash'; import { Grid, makeStyles, Paper, Box } from '@material-ui/core'; -import { TableData, DataTable } from 'Models'; import { Link } from 'react-router-dom'; -import AppLoader from '../components/AppLoader'; import PinotMethodUtils from '../utils/PinotMethodUtils'; import TenantsListing from '../components/Homepage/TenantsListing'; import Instances from '../components/Homepage/InstancesTables'; import ClusterConfig from '../components/Homepage/ClusterConfig'; import useTaskTypesTable from '../components/Homepage/useTaskTypesTable'; +import Skeleton from '@material-ui/lab/Skeleton'; +import { getTenants } from '../requests'; const useStyles = makeStyles((theme) => ({ - paper:{ + paper: { + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', padding: '10px 0', height: '100%', color: '#4285f4', @@ -43,71 +48,98 @@ const useStyles = makeStyles((theme) => ({ '& h2, h4': { margin: 0, }, - '& h4':{ + '& h4': { textTransform: 'uppercase', letterSpacing: 1, - fontWeight: 600 + fontWeight: 600, }, '&:hover': { - borderColor: '#4285f4' - } + borderColor: '#4285f4', + }, }, gridContainer: { padding: 20, backgroundColor: 'white', maxHeight: 'calc(100vh - 70px)', - overflowY: 'auto' + overflowY: 'auto', }, paperLinks: { textDecoration: 'none', - height: '100%' - } + height: '100%', + }, })); const HomePage = () => { const classes = useStyles(); - const [fetching, setFetching] = useState(true); - const [tenantsData, setTenantsData] = useState({ records: [], columns: [] }); - const [instances, setInstances] = useState(); const [clusterName, setClusterName] = useState(''); - const [tables, setTables] = useState([]); + + const [fetchingTenants, setFetchingTenants] = useState(true); + const [tenantsCount, setTenantscount] = useState(0); + + const [fetchingInstances, setFetchingInstances] = useState(true); + const [controllerCount, setControllerCount] = useState(0); + const [brokerCount, setBrokerCount] = useState(0); + const [serverCount, setServerCount] = useState(0); + const [minionCount, setMinionCount] = useState(0); + // const [instances, setInstances] = useState(); + + const [fetchingTables, setFetchingTables] = useState(true); + const [tablesCount, setTablesCount] = useState(0); const { taskTypes, taskTypesTable } = useTaskTypesTable(); const fetchData = async () => { - const tenantsDataResponse = await PinotMethodUtils.getTenantsData(); - const instanceResponse = await PinotMethodUtils.getAllInstances(); - const taskTypes = await PinotMethodUtils.getAllTaskTypes(); - const tablesResponse = await PinotMethodUtils.getQueryTablesList({bothType: true}); - const tablesList = []; - tablesResponse.records.map((record)=>{ - tablesList.push(...record); + PinotMethodUtils.getAllInstances().then((res) => { + setControllerCount(get(res, 'Controller', []).length); + setBrokerCount(get(res, 'Broker', []).length); + setServerCount(get(res, 'Server', []).length); + setMinionCount(get(res, 'Minion', []).length); + setFetchingInstances(false); }); - setTenantsData(tenantsDataResponse); - setInstances(instanceResponse); - setTables(tablesList); + + PinotMethodUtils.getQueryTablesList({ bothType: true }).then((res) => { + setTablesCount(res.records.length); + setFetchingTables(false); + }); + + getTenants().then((res) => { + const tenantNames = union( + res.data.SERVER_TENANTS, + res.data.BROKER_TENANTS + ); + setTenantscount(tenantNames.length); + setFetchingTenants(false); + }); + + fetchClusterName().then((clusterNameRes) => { + setClusterName(clusterNameRes); + }); + }; + + const fetchClusterName = () => { let clusterNameRes = localStorage.getItem('pinot_ui:clusterName'); - if(!clusterNameRes){ - clusterNameRes = await PinotMethodUtils.getClusterName(); + if (!clusterNameRes) { + return PinotMethodUtils.getClusterName(); + } else { + return Promise.resolve(clusterNameRes); } - setClusterName(clusterNameRes); - setFetching(false); }; + useEffect(() => { fetchData(); }, []); - - return fetching ? ( - - ) : ( + + const loading = ; + + return (

Controllers

-

{Array.isArray(instances.Controller) ? instances.Controller.length : 0}

+

{fetchingInstances ? loading : controllerCount}

@@ -115,7 +147,8 @@ const HomePage = () => {

Brokers

-

{Array.isArray(instances.Broker) ? instances.Broker.length : 0}

+

{fetchingInstances ? loading : brokerCount}

+ {/*

{Array.isArray(instances.Broker) ? instances.Broker.length : 0}

*/}
@@ -123,7 +156,8 @@ const HomePage = () => {

Servers

-

{Array.isArray(instances.Server) ? instances.Server.length : 0}

+

{fetchingInstances ? loading : serverCount}

+ {/*

{Array.isArray(instances.Server) ? instances.Server.length : 0}

*/}
@@ -131,7 +165,8 @@ const HomePage = () => {

Minions

-

{Array.isArray(instances.Minion) ? instances.Minion.length : 0}

+

{fetchingInstances ? loading : minionCount}

+ {/*

{Array.isArray(instances.Minion) ? instances.Minion.length : 0}

*/}
@@ -139,7 +174,8 @@ const HomePage = () => {

Tenants

-

{Array.isArray(tenantsData.records) ? tenantsData.records.length : 0}

+

{fetchingTenants ? loading : tenantsCount}

+ {/*

{Array.isArray(tenantsData.records) ? tenantsData.records.length : 0}

*/}
@@ -147,7 +183,8 @@ const HomePage = () => {

Tables

-

{Array.isArray(tables) ? tables.length : 0}

+

{fetchingTables ? loading : tablesCount}

+ {/*

{Array.isArray(tables) ? tables.length : 0}

*/}
@@ -155,19 +192,22 @@ const HomePage = () => {

Minion Task Manager

-

{Array.isArray(taskTypes.records) ? taskTypes?.records?.length : 0}

+

+ {Array.isArray(taskTypes.records) + ? taskTypes?.records?.length + : 0} +

- - - + + {taskTypesTable} ); }; -export default HomePage; \ No newline at end of file +export default HomePage; diff --git a/pinot-controller/src/main/resources/app/pages/InstanceDetails.tsx b/pinot-controller/src/main/resources/app/pages/InstanceDetails.tsx index 744751f36ec5..8aaabd4a64e5 100644 --- a/pinot-controller/src/main/resources/app/pages/InstanceDetails.tsx +++ b/pinot-controller/src/main/resources/app/pages/InstanceDetails.tsx @@ -17,31 +17,32 @@ * under the License. */ -import React, { useState, useEffect } from 'react'; -import { Button, FormControlLabel, Grid, makeStyles, Switch, Tooltip } from '@material-ui/core'; +import React, { useEffect, useState } from 'react'; +import { + FormControlLabel, + Grid, + makeStyles, + Switch, + Tooltip, +} from '@material-ui/core'; import { UnControlled as CodeMirror } from 'react-codemirror2'; import 'codemirror/lib/codemirror.css'; import 'codemirror/theme/material.css'; import 'codemirror/mode/javascript/javascript'; -import { TableData } from 'Models'; +import { InstanceState, InstanceType } from 'Models'; import { RouteComponentProps } from 'react-router-dom'; import PinotMethodUtils from '../utils/PinotMethodUtils'; import AppLoader from '../components/AppLoader'; -import CustomizedTables from '../components/Table'; import SimpleAccordion from '../components/SimpleAccordion'; import CustomButton from '../components/CustomButton'; import EditTagsOp from '../components/Homepage/Operations/EditTagsOp'; import EditConfigOp from '../components/Homepage/Operations/EditConfigOp'; import { NotificationContext } from '../components/Notification/NotificationContext'; -import { uniq, startCase } from 'lodash'; +import { startCase } from 'lodash'; import Confirm from '../components/Confirm'; -import Utils from "../utils/Utils"; - -const instanceTypes = { - broker: 'BROKER', - minion: 'MINION', - server: 'SERVER', -} +import { getInstanceTypeFromInstanceName } from '../utils/Utils'; +import AsyncPinotTables from '../components/AsyncPinotTables'; +import NotFound from '../components/NotFound'; const useStyles = makeStyles((theme) => ({ codeMirrorDiv: { @@ -56,7 +57,7 @@ const useStyles = makeStyles((theme) => ({ border: '1px #BDCCD9 solid', borderRadius: 4, marginBottom: 20, - } + }, })); const jsonoptions = { @@ -65,38 +66,31 @@ const jsonoptions = { styleActiveLine: true, gutters: ['CodeMirror-lint-markers'], theme: 'default', - readOnly: true + readOnly: true, }; type Props = { - instanceName: string + instanceName: string; }; const InstanceDetails = ({ match }: RouteComponentProps) => { const classes = useStyles(); - const {instanceName} = match.params; - let instanceType; - if (instanceName.toLowerCase().startsWith(instanceTypes.broker.toLowerCase())) { - instanceType = instanceTypes.broker; - } else if (instanceName.toLowerCase().startsWith(instanceTypes.minion.toLowerCase())) { - instanceType = instanceTypes.minion; - } else { - instanceType = instanceTypes.server; - } - const clutserName = localStorage.getItem('pinot_ui:clusterName'); + const { instanceName } = match.params; + const instanceType = getInstanceTypeFromInstanceName(instanceName); + const clusterName = localStorage.getItem('pinot_ui:clusterName'); const [fetching, setFetching] = useState(true); + const [instanceNotFound, setInstanceNotFound] = useState(false); const [confirmDialog, setConfirmDialog] = React.useState(false); const [dialogDetails, setDialogDetails] = React.useState(null); const [instanceConfig, setInstanceConfig] = useState(null); const [liveConfig, setLiveConfig] = useState(null); const [instanceDetails, setInstanceDetails] = useState(null); - const [tableData, setTableData] = useState({ - columns: [], - records: [] - }); const [tagsList, setTagsList] = useState([]); - const [tagsErrorObj, setTagsErrorObj] = useState({isError: false, errorMessage: null}) + const [tagsErrorObj, setTagsErrorObj] = useState({ + isError: false, + errorMessage: null, + }); const [config, setConfig] = useState('{}'); const [state, setState] = React.useState({ @@ -105,117 +99,102 @@ const InstanceDetails = ({ match }: RouteComponentProps) => { const [showEditTag, setShowEditTag] = useState(false); const [showEditConfig, setShowEditConfig] = useState(false); - const {dispatch} = React.useContext(NotificationContext); + const { dispatch } = React.useContext(NotificationContext); const fetchData = async () => { - const configResponse = await PinotMethodUtils.getInstanceConfig(clutserName, instanceName); - const liveConfigResponse = await PinotMethodUtils.getLiveInstanceConfig(clutserName, instanceName); - const instanceDetails = await PinotMethodUtils.getInstanceDetails(instanceName); - const tenantListResponse = getTenants(instanceDetails); - setInstanceConfig(JSON.stringify(configResponse, null, 2)); - const instanceHost = instanceDetails.hostName.replace(`${startCase(instanceType.toLowerCase())}_`, ''); - const instancePutObj = { - host: instanceHost, - port: instanceDetails.port, - type: instanceType, - tags: instanceDetails.tags - }; - setState({enabled: instanceDetails.enabled}); - setInstanceDetails(JSON.stringify(instancePutObj, null, 2)); - setLiveConfig(JSON.stringify(liveConfigResponse, null, 2)); - if(tenantListResponse){ - fetchTableDetails(tenantListResponse); + const configResponse = await PinotMethodUtils.getInstanceConfig( + clusterName, + instanceName + ); + + if (configResponse?.code === 404) { + setInstanceNotFound(true); } else { - setFetching(false); + const liveConfigResponse = await PinotMethodUtils.getLiveInstanceConfig( + clusterName, + instanceName + ); + const instanceDetails = await PinotMethodUtils.getInstanceDetails( + instanceName + ); + setInstanceConfig(JSON.stringify(configResponse, null, 2)); + const instanceHost = instanceDetails.hostName.replace( + `${startCase(instanceType.toLowerCase())}_`, + '' + ); + const instancePutObj = { + host: instanceHost, + port: instanceDetails.port, + type: instanceType, + tags: instanceDetails.tags, + pools: instanceDetails.pools, + grpcPort: instanceDetails.grpcPort, + adminPort: instanceDetails.adminPort, + queryServicePort: instanceDetails.queryServicePort, + queryMailboxPort: instanceDetails.queryMailboxPort, + queriesDisabled: instanceDetails.queriesDisabled, + }; + setState({ enabled: instanceDetails.enabled }); + setInstanceDetails(JSON.stringify(instancePutObj, null, 2)); + setLiveConfig(JSON.stringify(liveConfigResponse, null, 2)); } + setFetching(false); }; useEffect(() => { fetchData(); }, []); - const fetchTableDetails = (tenantList) => { - const promiseArr = []; - tenantList.map((tenantName) => { - promiseArr.push(PinotMethodUtils.getTenantTableData(tenantName)); - }); - const tenantTableData = { - columns: [], - records: [] - }; - Promise.all(promiseArr).then((results)=>{ - results.map((result)=>{ - tenantTableData.columns = result.columns; - tenantTableData.records.push(...result.records); - }); - setTableData(tenantTableData); - setFetching(false); - }); - }; - - const getTenants = (instanceDetails) => { - const tenantsList = []; - instanceDetails.tags.forEach((tag) => { - if(tag.search('_BROKER') !== -1 || - tag.search('_REALTIME') !== -1 || - tag.search('_OFFLINE') !== -1 - ){ - let [baseTag, ] = Utils.splitStringByLastUnderscore(tag); - tenantsList.push(baseTag); - } - }); - return uniq(tenantsList); - }; - - const handleTagsChange = (e: React.ChangeEvent, tags: Array|null) => { + const handleTagsChange = ( + e: React.ChangeEvent, + tags: Array | null + ) => { isTagsValid(tags); setTagsList(tags); }; const isTagsValid = (_tagsList) => { let isValid = true; - setTagsErrorObj({isError: false, errorMessage: null}); - _tagsList.map((tag)=>{ - if(!isValid){ + setTagsErrorObj({ isError: false, errorMessage: null }); + _tagsList.map((tag) => { + if (!isValid) { return; } - if(instanceType === 'BROKER'){ - if(!tag.endsWith('_BROKER')){ + if (instanceType === InstanceType.BROKER) { + if (!tag.endsWith('_BROKER')) { isValid = false; setTagsErrorObj({ isError: true, - errorMessage: "Tags should end with _BROKER." + errorMessage: 'Tags should end with _BROKER.', }); } - } else if(instanceType === 'SERVER'){ - if(!tag.endsWith('_REALTIME') && - !tag.endsWith('_OFFLINE') - ){ + } else if (instanceType === InstanceType.SERVER) { + if (!tag.endsWith('_REALTIME') && !tag.endsWith('_OFFLINE')) { isValid = false; setTagsErrorObj({ isError: true, - errorMessage: "Tags should end with _OFFLINE or _REALTIME." + errorMessage: 'Tags should end with _OFFLINE or _REALTIME.', }); } } }); return isValid; - } + }; const saveTagsAction = async (event, typedTag) => { let newTagsList = [...tagsList]; - if(typedTag.length > 0){ + if (typedTag.length > 0) { newTagsList.push(typedTag); } - if(!isTagsValid(newTagsList)){ + if (!isTagsValid(newTagsList)) { return; } const result = await PinotMethodUtils.updateTags(instanceName, newTagsList); - if(result.status){ - dispatch({type: 'success', message: result.status, show: true}); + if (result.status) { + dispatch({ type: 'success', message: result.status, show: true }); fetchData(); } else { - dispatch({type: 'error', message: result.error, show: true}); + dispatch({ type: 'error', message: result.error, show: true }); } setShowEditTag(false); }; @@ -224,18 +203,18 @@ const InstanceDetails = ({ match }: RouteComponentProps) => { setDialogDetails({ title: 'Drop Instance', content: 'Are you sure want to drop this instance?', - successCb: () => dropInstance() + successCb: () => dropInstance(), }); setConfirmDialog(true); }; const dropInstance = async () => { const result = await PinotMethodUtils.deleteInstance(instanceName); - if(result.status){ - dispatch({type: 'success', message: result.status, show: true}); + if (result.status) { + dispatch({ type: 'success', message: result.status, show: true }); fetchData(); } else { - dispatch({type: 'error', message: result.error, show: true}); + dispatch({ type: 'error', message: result.error, show: true }); } closeDialog(); }; @@ -243,19 +222,24 @@ const InstanceDetails = ({ match }: RouteComponentProps) => { const handleSwitchChange = (event) => { setDialogDetails({ title: state.enabled ? 'Disable Instance' : 'Enable Instance', - content: `Are you sure want to ${state.enabled ? 'disable' : 'enable'} this instance?`, - successCb: () => toggleInstanceState() + content: `Are you sure want to ${ + state.enabled ? 'disable' : 'enable' + } this instance?`, + successCb: () => toggleInstanceState(), }); setConfirmDialog(true); }; const toggleInstanceState = async () => { - const result = await PinotMethodUtils.toggleInstanceState(instanceName, state.enabled ? 'DISABLE' : 'ENABLE'); - if(result.status){ - dispatch({type: 'success', message: result.status, show: true}); + const result = await PinotMethodUtils.toggleInstanceState( + instanceName, + state.enabled ? InstanceState.DISABLE : InstanceState.ENABLE + ); + if (result.status) { + dispatch({ type: 'success', message: result.status, show: true }); fetchData(); } else { - dispatch({type: 'error', message: result.error, show: true}); + dispatch({ type: 'error', message: result.error, show: true }); } setState({ enabled: !state.enabled }); closeDialog(); @@ -266,13 +250,16 @@ const InstanceDetails = ({ match }: RouteComponentProps) => { }; const saveConfigAction = async () => { - if(JSON.parse(config)){ - const result = await PinotMethodUtils.updateInstanceDetails(instanceName, config); - if(result.status){ - dispatch({type: 'success', message: result.status, show: true}); + if (JSON.parse(config)) { + const result = await PinotMethodUtils.updateInstanceDetails( + instanceName, + config + ); + if (result.status) { + dispatch({ type: 'success', message: result.status, show: true }); fetchData(); } else { - dispatch({type: 'error', message: result.error, show: true}); + dispatch({ type: 'error', message: result.error, show: true }); } setShowEditConfig(false); } @@ -283,137 +270,154 @@ const InstanceDetails = ({ match }: RouteComponentProps) => { setDialogDetails(null); }; - return ( - fetching ? : - - {!instanceName.toLowerCase().startsWith('controller') && -
- -
- { - setTagsList(JSON.parse(instanceConfig)?.listFields?.TAG_LIST || []); - setShowEditTag(true); - }} - tooltipTitle="Add/remove tags from this node" - enableTooltip={true} - > - Edit Tags - - { - setConfig(instanceDetails); - setShowEditConfig(true); - }} - enableTooltip={true} - > - Edit Config - - - Drop - - - ; + } else if (instanceNotFound) { + return ; + } else { + return ( + + {!instanceName.toLowerCase().startsWith('controller') && ( +
+ +
+ { + setTagsList( + JSON.parse(instanceConfig)?.listFields?.TAG_LIST || [] + ); + setShowEditTag(true); + }} + tooltipTitle="Add/remove tags from this node" + enableTooltip={true} + > + Edit Tags + + { + setConfig(instanceDetails); + setShowEditConfig(true); + }} + enableTooltip={true} + > + Edit Config + + + Drop + + + + } + label="Enable" /> - } - label="Enable" - /> - -
-
-
} - - -
- - + +
- - {liveConfig ? - + )} + +
- : null} -
- {tableData.columns.length ? - +
+ + + +
+
+ ) : null} + + {instanceType == InstanceType.BROKER || + instanceType == InstanceType.SERVER ? ( + + ) : null} + { + setShowEditTag(false); + }} + saveTags={saveTagsAction} + tags={tagsList} + handleTagsChange={handleTagsChange} + error={tagsErrorObj} /> - : null} - {setShowEditTag(false);}} - saveTags={saveTagsAction} - tags={tagsList} - handleTagsChange={handleTagsChange} - error={tagsErrorObj} - /> - {setShowEditConfig(false);}} - saveConfig={saveConfigAction} - config={config} - handleConfigChange={handleConfigChange} - /> - {confirmDialog && dialogDetails && } - - ); + { + setShowEditConfig(false); + }} + saveConfig={saveConfigAction} + config={config} + handleConfigChange={handleConfigChange} + /> + {confirmDialog && dialogDetails && ( + + )} + + ); + } }; -export default InstanceDetails; \ No newline at end of file +export default InstanceDetails; diff --git a/pinot-controller/src/main/resources/app/pages/InstanceListingPage.tsx b/pinot-controller/src/main/resources/app/pages/InstanceListingPage.tsx index d224ef7a25ad..9d0070725ce4 100644 --- a/pinot-controller/src/main/resources/app/pages/InstanceListingPage.tsx +++ b/pinot-controller/src/main/resources/app/pages/InstanceListingPage.tsx @@ -20,10 +20,11 @@ import React, {useState, useEffect} from 'react'; import { Grid, makeStyles } from '@material-ui/core'; import { startCase, pick } from 'lodash'; -import { DataTable } from 'Models'; +import { DataTable, InstanceType } from 'Models'; import AppLoader from '../components/AppLoader'; import PinotMethodUtils from '../utils/PinotMethodUtils'; import Instances from '../components/Homepage/InstancesTables'; +import { getInstanceTypeFromString } from '../utils/Utils'; const useStyles = makeStyles(() => ({ gridContainer: { @@ -38,13 +39,9 @@ const InstanceListingPage = () => { const classes = useStyles(); const [fetching, setFetching] = useState(true); - const [instances, setInstances] = useState(); const [clusterName, setClusterName] = useState(''); const fetchData = async () => { - const instanceResponse = await PinotMethodUtils.getAllInstances(); - const instanceType = startCase(window.location.hash.split('/')[1].slice(0, -1)); - setInstances(pick(instanceResponse, instanceType)); let clusterNameRes = localStorage.getItem('pinot_ui:clusterName'); if(!clusterNameRes){ clusterNameRes = await PinotMethodUtils.getClusterName(); @@ -57,11 +54,13 @@ const InstanceListingPage = () => { fetchData(); }, []); + const instanceType = getInstanceTypeFromString(window.location.hash.split('/')[1].slice(0, -1)); + return fetching ? ( ) : ( - + ); }; diff --git a/pinot-controller/src/main/resources/app/pages/Query.tsx b/pinot-controller/src/main/resources/app/pages/Query.tsx index 1974be43769c..349d5775be0c 100644 --- a/pinot-controller/src/main/resources/app/pages/Query.tsx +++ b/pinot-controller/src/main/resources/app/pages/Query.tsx @@ -20,10 +20,10 @@ import React, { useEffect, useState } from 'react'; import { makeStyles } from '@material-ui/core/styles'; -import { Grid, Checkbox, Button, FormControl, Input, InputLabel } from '@material-ui/core'; +import { Grid, Checkbox, Button, FormControl, Input, InputLabel, Box, Typography } from '@material-ui/core'; import Alert from '@material-ui/lab/Alert'; import FileCopyIcon from '@material-ui/icons/FileCopy'; -import { TableData } from 'Models'; +import { SqlException, TableData } from 'Models'; import { UnControlled as CodeMirror } from 'react-codemirror2'; import 'codemirror/lib/codemirror.css'; import 'codemirror/theme/material.css'; @@ -47,6 +47,7 @@ import PinotMethodUtils from '../utils/PinotMethodUtils'; import '../styles/styles.css'; import {Resizable} from "re-resizable"; import { useHistory, useLocation } from 'react-router'; +import sqlFormatter from '@sqltools/formatter'; const useStyles = makeStyles((theme) => ({ title: { @@ -78,7 +79,15 @@ const useStyles = makeStyles((theme) => ({ }, runNowBtn: { marginLeft: 'auto', - paddingLeft: '74px', + paddingLeft: '10px', + }, + formatSQLBtn: { + marginLeft: 'auto', + paddingLeft: '30px', + }, + formatMSE: { + marginLeft: '-30px', + paddingLeft: 'auto', }, sqlDiv: { height: '100%', @@ -88,7 +97,8 @@ const useStyles = makeStyles((theme) => ({ paddingBottom: '48px', }, sqlError: { - whiteSpace: 'pre-wrap', + whiteSpace: 'pre', + overflow: "auto" }, timeoutControl: { bottom: 10 @@ -165,6 +175,7 @@ const QueryPage = () => { columns: [], records: [], }); + const [showException, setShowException] = useState(false); const [tableSchema, setTableSchema] = useState({ columns: [], @@ -183,7 +194,7 @@ const QueryPage = () => { const [outputResult, setOutputResult] = useState(''); - const [resultError, setResultError] = useState(''); + const [resultError, setResultError] = useState([]); const [queryStats, setQueryStats] = useState({ columns: [], @@ -212,12 +223,14 @@ const QueryPage = () => { }; const handleQueryInterfaceKeyDown = (editor, event) => { - // Map Cmd + Enter KeyPress to executing the query - if (event.metaKey == true && event.keyCode == 13) { + const modifiedEnabled = event.metaKey == true || event.ctrlKey == true; + + // Map (Cmd/Ctrl) + Enter KeyPress to executing the query + if (modifiedEnabled && event.keyCode == 13) { handleRunNow(editor.getValue()); } - // Map Cmd + / KeyPress to toggle commenting the query - if (event.metaKey == true && event.keyCode == 191) { + // Map (Cmd/Ctrl) + / KeyPress to toggle commenting the query + if (modifiedEnabled && event.keyCode == 191) { handleComment(editor); } } @@ -267,22 +280,27 @@ const QueryPage = () => { setInputQuery(querySplit.join("\n")); } + const handleFormatSQL = (query?: string) => { + const formatted = sqlFormatter.format(query); + setInputQuery(formatted); + }; + const handleRunNow = async (query?: string) => { setQueryLoader(true); queryExecuted.current = true; let params; - let queryOptions = ''; + let queryOptions = []; if(queryTimeout){ - queryOptions += `timeoutMs=${queryTimeout}`; + queryOptions.push(`timeoutMs=${queryTimeout}`); } if(checked.useMSE){ - queryOptions += `useMultistageEngine=true`; + queryOptions.push(`useMultistageEngine=true`); } const finalQuery = `${query || inputQuery.trim()}`; params = JSON.stringify({ sql: `${finalQuery}`, trace: checked.tracing, - queryOptions: `${queryOptions}`, + queryOptions: `${queryOptions.join(";")}`, }); if(finalQuery !== ''){ @@ -299,7 +317,7 @@ const QueryPage = () => { } const results = await PinotMethodUtils.getQueryResults(params); - setResultError(results.error || ''); + setResultError(results.exceptions || []); setResultData(results.result || { columns: [], records: [] }); setQueryStats(results.queryStats || { columns: responseStatCols, records: [] }); setOutputResult(JSON.stringify(results.data, null, 2) || ''); @@ -316,10 +334,6 @@ const QueryPage = () => { + `created with an older schema. ` + `Please reload the table in order to refresh these segments to the new schema.`); } - if (checked.useMSE) { - warnings.push(`Using V2 Multi-Stage Query Engine. This is an experimental feature. Please report any bugs to ` - + `Apache Pinot Slack channel.`); - } return warnings; } @@ -503,28 +517,38 @@ const QueryPage = () => { Tracing - + - Use V2 Engine + Use Multi-Stage Engine - Timeout (in Milliseconds) + Timeout (Milliseconds) setQueryTimeout(Number(e.target.value) || '')}/> - + + + + + @@ -553,93 +577,122 @@ const QueryPage = () => { ) } - - {resultError ? ( - - {resultError} - - ) : ( - <> - - {resultData.columns.length ? ( - <> - - - - + + + {copyMsg ? ( + } + severity="info" + > + Copied {resultData.records.length} rows to + Clipboard + + ) : null} + + copyToClipboard()} - > - Copy - - {copyMsg ? ( - } - severity="info" - > - Copied {resultData.records.length} rows to - Clipboard - - ) : null} - - - } - label="Show JSON format" - className={classes.runNowBtn} - /> - - {!checked.showResultJSON ? ( - - ) : resultData.columns.length ? ( - - - - ) : null} - + } + label="Show JSON format" + className={classes.runNowBtn} + /> + + {!checked.showResultJSON ? ( + + ) : resultData.columns.length ? ( + + + ) : null} - - - )} + + ) : null} + )} diff --git a/pinot-controller/src/main/resources/app/pages/SchemaPageDetails.tsx b/pinot-controller/src/main/resources/app/pages/SchemaPageDetails.tsx index 23be997dfb81..b44b3e1f7b26 100644 --- a/pinot-controller/src/main/resources/app/pages/SchemaPageDetails.tsx +++ b/pinot-controller/src/main/resources/app/pages/SchemaPageDetails.tsx @@ -19,7 +19,14 @@ import React, { useState, useEffect } from 'react'; import { makeStyles } from '@material-ui/core/styles'; -import { Checkbox, DialogContent, FormControlLabel, Grid, IconButton, Tooltip } from '@material-ui/core'; +import { + Checkbox, + DialogContent, + FormControlLabel, + Grid, + IconButton, + Tooltip, +} from '@material-ui/core'; import { RouteComponentProps, useHistory } from 'react-router-dom'; import { UnControlled as CodeMirror } from 'react-codemirror2'; import { TableData } from 'Models'; @@ -38,6 +45,7 @@ import Confirm from '../components/Confirm'; import CustomCodemirror from '../components/CustomCodemirror'; import CustomDialog from '../components/CustomDialog'; import { HelpOutlineOutlined } from '@material-ui/icons'; +import NotFound from '../components/NotFound'; const useStyles = makeStyles(() => ({ root: { @@ -69,8 +77,8 @@ const useStyles = makeStyles(() => ({ operationDiv: { border: '1px #BDCCD9 solid', borderRadius: 4, - marginBottom: 20 - } + marginBottom: 20, + }, })); const jsonoptions = { @@ -79,7 +87,7 @@ const jsonoptions = { styleActiveLine: true, gutters: ['CodeMirror-lint-markers'], theme: 'default', - readOnly: true + readOnly: true, }; type Props = { @@ -89,7 +97,7 @@ type Props = { }; type Summary = { - schemaName: string; + schemaName: string; reportedSize: string | number; estimatedSize: string | number; }; @@ -99,6 +107,7 @@ const SchemaPageDetails = ({ match }: RouteComponentProps) => { const classes = useStyles(); const history = useHistory(); const [fetching, setFetching] = useState(true); + const [schemaNotFound, setSchemaNotFound] = useState(false); const [] = useState({ schemaName: match.params.schemaName, reportedSize: '', @@ -111,7 +120,7 @@ const SchemaPageDetails = ({ match }: RouteComponentProps) => { const [confirmDialog, setConfirmDialog] = React.useState(false); const [dialogDetails, setDialogDetails] = React.useState(null); - const {dispatch} = React.useContext(NotificationContext); + const { dispatch } = React.useContext(NotificationContext); const [showEditConfig, setShowEditConfig] = useState(false); const [config, setConfig] = useState('{}'); @@ -122,24 +131,28 @@ const SchemaPageDetails = ({ match }: RouteComponentProps) => { }); const [tableConfig, setTableConfig] = useState(''); const [schemaJSON, setSchemaJSON] = useState(null); - const [actionType,setActionType] = useState(null); + const [actionType, setActionType] = useState(null); const [schemaJSONFormat, setSchemaJSONFormat] = useState(false); const [reloadSegmentsOnUpdate, setReloadSegmentsOnUpdate] = useState(false); const fetchTableSchema = async () => { const result = await PinotMethodUtils.getTableSchemaData(schemaName); - if(result.error){ + if (result?.code === 404) { + setSchemaNotFound(true); + setFetching(false); + } else if (result.error) { setSchemaJSON(null); setTableSchema({ - columns: ['Column', 'Type', 'Field Type'], - records: [] + columns: ['Column', 'Type', 'Field Type', 'Multi Value'], + records: [], }); + setFetching(false); } else { setSchemaJSON(JSON.parse(JSON.stringify(result))); const tableSchema = Utils.syncTableSchemaData(result, true); setTableSchema(tableSchema); + fetchTableJSON(); } - fetchTableJSON(); }; const fetchTableJSON = async () => { @@ -148,9 +161,9 @@ const SchemaPageDetails = ({ match }: RouteComponentProps) => { setFetching(false); }; - useEffect(()=>{ + useEffect(() => { fetchTableSchema(); - },[]) + }, []); const handleConfigChange = (value: string) => { setConfig(value); @@ -158,34 +171,39 @@ const SchemaPageDetails = ({ match }: RouteComponentProps) => { const saveConfigAction = async () => { let configObj = JSON.parse(config); - if(actionType === 'editTable'){ - if(configObj.OFFLINE || configObj.REALTIME){ + if (actionType === 'editTable') { + if (configObj.OFFLINE || configObj.REALTIME) { configObj = configObj.OFFLINE || configObj.REALTIME; } const result = await PinotMethodUtils.updateTable(schemaName, configObj); syncResponse(result); - } else if(actionType === 'editSchema'){ - const result = await PinotMethodUtils.updateSchema(schemaJSON.schemaName, configObj, reloadSegmentsOnUpdate); + } else if (actionType === 'editSchema') { + const result = await PinotMethodUtils.updateSchema( + schemaJSON.schemaName, + configObj, + reloadSegmentsOnUpdate + ); syncResponse(result); } }; const syncResponse = (result) => { - if(result.status){ - dispatch({type: 'success', message: result.status, show: true}); + if (result.status) { + dispatch({ type: 'success', message: result.status, show: true }); setShowEditConfig(false); fetchTableJSON(); setReloadSegmentsOnUpdate(false); } else { - dispatch({type: 'error', message: result.error, show: true}); + dispatch({ type: 'error', message: result.error, show: true }); } }; const handleDeleteSchemaAction = () => { setDialogDetails({ title: 'Delete Schema', - content: 'Are you sure want to delete this schema? Any tables using this schema might not function correctly.', - successCb: () => deleteSchema() + content: + 'Are you sure want to delete this schema? Any tables using this schema might not function correctly.', + successCb: () => deleteSchema(), }); setConfirmDialog(true); }; @@ -204,143 +222,146 @@ const SchemaPageDetails = ({ match }: RouteComponentProps) => { const handleSegmentDialogHide = () => { setShowEditConfig(false); setReloadSegmentsOnUpdate(false); - } + }; - return fetching ? ( - - ) : ( - -
- -
- { - setActionType('editSchema'); - setConfig(JSON.stringify(schemaJSON, null, 2)); - setShowEditConfig(true); - }} - tooltipTitle="Edit Schema" - enableTooltip={true} - > - Edit Schema - - - Delete Schema - + if (fetching) { + return ; + } else if (schemaNotFound) { + return ; + } else { + return ( + +
+ +
+ { + setActionType('editSchema'); + setConfig(JSON.stringify(schemaJSON, null, 2)); + setShowEditConfig(true); + }} + tooltipTitle="Edit Schema" + enableTooltip={true} + > + Edit Schema + + + Delete Schema +
-
-
- - -
- - +
+ + +
+ + + +
+
+ + {!schemaJSONFormat ? ( + - -
+ ) : ( +
+ { + setSchemaJSONFormat(!schemaJSONFormat); + }, + }} + > + + +
+ )} + - - {!schemaJSONFormat ? - + + setReloadSegmentsOnUpdate(e.target.checked)} + name="reload" + /> + } + label="Reload all segments" /> - : -
- {setSchemaJSONFormat(!schemaJSONFormat);} + + + + + + { + handleConfigChange(newValue); }} - > - - -
- } -
- - {/* Segment config edit dialog */} - - - setReloadSegmentsOnUpdate(e.target.checked)} - name="reload" - /> - } - label="Reload all segments" - /> - - - - - - { - handleConfigChange(newValue); - }} - /> - - + /> + + - {confirmDialog && dialogDetails && } - - ); + {confirmDialog && dialogDetails && ( + + )} + + ); + } }; export default SchemaPageDetails; diff --git a/pinot-controller/src/main/resources/app/pages/SegmentDetails.tsx b/pinot-controller/src/main/resources/app/pages/SegmentDetails.tsx index 1caddabc377d..8ba9ed969648 100644 --- a/pinot-controller/src/main/resources/app/pages/SegmentDetails.tsx +++ b/pinot-controller/src/main/resources/app/pages/SegmentDetails.tsx @@ -18,11 +18,12 @@ */ import React, { useState, useEffect } from 'react'; +import moment from 'moment'; +import { keys } from 'lodash'; import { makeStyles } from '@material-ui/core/styles'; import { Grid } from '@material-ui/core'; import { RouteComponentProps, useHistory, useLocation } from 'react-router-dom'; import { UnControlled as CodeMirror } from 'react-codemirror2'; -import AppLoader from '../components/AppLoader'; import TableToolbar from '../components/TableToolbar'; import 'codemirror/lib/codemirror.css'; import 'codemirror/theme/material.css'; @@ -35,6 +36,15 @@ import CustomButton from '../components/CustomButton'; import Confirm from '../components/Confirm'; import { NotificationContext } from '../components/Notification/NotificationContext'; import Utils from '../utils/Utils'; +import { + getExternalView, + getSegmentDebugInfo, + getSegmentMetadata, +} from '../requests'; +import { SegmentMetadata } from 'Models'; +import Skeleton from '@material-ui/lab/Skeleton'; +import NotFound from '../components/NotFound'; +import AppLoader from '../components/AppLoader'; const useStyles = makeStyles((theme) => ({ root: { @@ -66,8 +76,8 @@ const useStyles = makeStyles((theme) => ({ operationDiv: { border: '1px #BDCCD9 solid', borderRadius: 4, - marginBottom: 20 - } + marginBottom: 20, + }, })); const jsonoptions = { @@ -76,7 +86,7 @@ const jsonoptions = { styleActiveLine: true, gutters: ['CodeMirror-lint-markers'], theme: 'default', - readOnly: true + readOnly: true, }; type Props = { @@ -95,38 +105,181 @@ const SegmentDetails = ({ match }: RouteComponentProps) => { const classes = useStyles(); const history = useHistory(); const location = useLocation(); - const { tableName, segmentName: encodedSegmentName} = match.params; + const { tableName, segmentName: encodedSegmentName } = match.params; const segmentName = Utils.encodeString(encodedSegmentName); - const [fetching, setFetching] = useState(true); const [confirmDialog, setConfirmDialog] = React.useState(false); const [dialogDetails, setDialogDetails] = React.useState(null); - const {dispatch} = React.useContext(NotificationContext); + const { dispatch } = React.useContext(NotificationContext); - const [segmentSummary, setSegmentSummary] = useState({ + const [initialLoad, setInitialLoad] = useState(true); + const [segmentNotFound, setSegmentNotFound] = useState(false); + + const initialSummary = { segmentName, - totalDocs: '', - createTime: '', - }); - - const [replica, setReplica] = useState({ - columns: [], - records: [] - }); - - const [indexes, setIndexes] = useState({ - columns: [], - records: [] - }); - const [value, setValue] = useState(''); + totalDocs: null, + createTime: null, + }; + const [segmentSummary, setSegmentSummary] = useState(initialSummary); + + const replicaColumns = ['Server Name', 'Status']; + const initialReplica = Utils.getLoadingTableData(replicaColumns); + const [replica, setReplica] = useState(initialReplica); + + const indexColumns = [ + 'Field Name', + 'Bloom Filter', + 'Dictionary', + 'Forward Index', + 'Sorted', + 'Inverted Index', + 'JSON Index', + 'Null Value Vector Reader', + 'Range Index', + ]; + const initialIndexes = Utils.getLoadingTableData(indexColumns); + const [indexes, setIndexes] = useState(initialIndexes); + const initialSegmentMetadataJson = 'Loading...'; + const [segmentMetadataJson, setSegmentMetadataJson] = useState( + initialSegmentMetadataJson + ); + const fetchData = async () => { - const result = await PinotMethodUtils.getSegmentDetails(tableName, segmentName); - setSegmentSummary(result.summary); - setIndexes(result.indexes); - setReplica(result.replicaSet); - setValue(JSON.stringify(result.JSON, null, 2)); - setFetching(false); + // reset all state in case the segment was reloaded or deleted. + setInitialData(); + + getSegmentMetadata(tableName, segmentName).then((result) => { + if (result.data?.code === 404) { + setSegmentNotFound(true); + setInitialLoad(false); + } else { + setInitialLoad(false); + setSummary(result.data); + setSegmentMetadata(result.data); + setSegmentIndexes(result.data); + } + }); + setSegmentReplicas(); + }; + + const setInitialData = () => { + setInitialLoad(true); + setSegmentSummary(initialSummary); + setReplica(initialReplica); + setIndexes(initialIndexes); + setSegmentMetadataJson(initialSegmentMetadataJson); + }; + + const setSummary = (segmentMetadata: SegmentMetadata) => { + const segmentMetaDataJson = { ...segmentMetadata }; + setSegmentSummary({ + segmentName, + totalDocs: segmentMetaDataJson['segment.total.docs'] || 0, + createTime: moment(+segmentMetaDataJson['segment.creation.time']).format( + 'MMMM Do YYYY, h:mm:ss' + ), + }); + }; + + const setSegmentMetadata = (segmentMetadata: SegmentMetadata) => { + const segmentMetaDataJson = { ...segmentMetadata }; + delete segmentMetaDataJson.indexes; + delete segmentMetaDataJson.columns; + setSegmentMetadataJson(JSON.stringify(segmentMetaDataJson, null, 2)); + }; + + const setSegmentIndexes = (segmentMetadata: SegmentMetadata) => { + setIndexes({ + columns: indexColumns, + records: Object.keys(segmentMetadata.indexes).map((fieldName) => [ + fieldName, + segmentMetadata.indexes?.[fieldName]?.['bloom-filter'] === 'YES', + segmentMetadata.indexes?.[fieldName]?.['dictionary'] === 'YES', + segmentMetadata.indexes?.[fieldName]?.['forward-index'] === 'YES', + ( + (segmentMetadata.columns || []).filter( + (row) => row.columnName === fieldName + )[0] || { sorted: false } + ).sorted, + segmentMetadata.indexes?.[fieldName]?.['inverted-index'] === 'YES', + segmentMetadata.indexes?.[fieldName]?.['json-index'] === 'YES', + segmentMetadata.indexes?.[fieldName]?.['null-value-vector-reader'] === + 'YES', + segmentMetadata.indexes?.[fieldName]?.['range-index'] === 'YES', + ]), + }); + }; + + const setSegmentReplicas = () => { + let [baseTableName, tableType] = Utils.splitStringByLastUnderscore( + tableName + ); + + getExternalView(tableName).then((results) => { + const externalView = results.data.OFFLINE || results.data.REALTIME; + + const records = keys(externalView?.[segmentName] || {}).map((prop) => { + const status = externalView?.[segmentName]?.[prop]; + return [ + prop, + { value: status, tooltip: `Segment is ${status.toLowerCase()}` }, + ]; + }); + + setReplica({ + columns: replicaColumns, + records: records, + }); + + getSegmentDebugInfo(baseTableName, tableType.toLowerCase()).then( + (debugInfo) => { + const segmentDebugInfo = debugInfo.data; + + let debugInfoObj = {}; + if (segmentDebugInfo && segmentDebugInfo[0]) { + const debugInfosObj = segmentDebugInfo[0].segmentDebugInfos?.find( + (o) => { + return o.segmentName === segmentName; + } + ); + if (debugInfosObj) { + const serverNames = keys(debugInfosObj?.serverState || {}); + serverNames?.map((serverName) => { + debugInfoObj[serverName] = + debugInfosObj.serverState[ + serverName + ]?.errorInfo?.errorMessage; + }); + } + } + + const records = keys(externalView?.[segmentName] || {}).map( + (prop) => { + const status = externalView?.[segmentName]?.[prop]; + return [ + prop, + status === 'ERROR' + ? { + value: status, + tooltip: debugInfoObj?.[prop] || 'testing', + } + : { + value: status, + tooltip: `Segment is ${status.toLowerCase()}`, + }, + ]; + } + ); + + setReplica({ + columns: replicaColumns, + records: records, + }); + } + ); + }); }; + useEffect(() => { fetchData(); }, []); @@ -139,22 +292,26 @@ const SegmentDetails = ({ match }: RouteComponentProps) => { const handleDeleteSegmentClick = () => { setDialogDetails({ title: 'Delete Segment', - content: 'Are you sure want to delete this instance? Data from this segment will be permanently deleted.', - successCb: () => handleDeleteSegment() + content: + 'Are you sure want to delete this instance? Data from this segment will be permanently deleted.', + successCb: () => handleDeleteSegment(), }); setConfirmDialog(true); }; const handleDeleteSegment = async () => { - const result = await PinotMethodUtils.deleteSegmentOp(tableName, segmentName); - if(result && result.status){ - dispatch({type: 'success', message: result.status, show: true}); + const result = await PinotMethodUtils.deleteSegmentOp( + tableName, + segmentName + ); + if (result && result.status) { + dispatch({ type: 'success', message: result.status, show: true }); fetchData(); } else { - dispatch({type: 'error', message: result.error, show: true}); + dispatch({ type: 'error', message: result.error, show: true }); } closeDialog(); - setTimeout(()=>{ + setTimeout(() => { history.push(Utils.navigateToPreviousPage(location, false)); }, 1000); }; @@ -163,120 +320,147 @@ const SegmentDetails = ({ match }: RouteComponentProps) => { setDialogDetails({ title: 'Reload Segment', content: 'Are you sure want to reload this segment?', - successCb: () => handleReloadOp() + successCb: () => handleReloadOp(), }); setConfirmDialog(true); }; const handleReloadOp = async () => { - const result = await PinotMethodUtils.reloadSegmentOp(tableName, segmentName); - if(result.status){ - dispatch({type: 'success', message: result.status, show: true}); + const result = await PinotMethodUtils.reloadSegmentOp( + tableName, + segmentName + ); + if (result.status) { + dispatch({ type: 'success', message: result.status, show: true }); fetchData(); } else { - dispatch({type: 'error', message: result.error, show: true}); + dispatch({ type: 'error', message: result.error, show: true }); } closeDialog(); - } + }; - return fetching ? ( - - ) : ( - -
- -
- {handleDeleteSegmentClick()}} - tooltipTitle="Delete Segment" - enableTooltip={true} - > - Delete Segment - - {handleReloadSegmentClick()}} - tooltipTitle="Reload the segment to apply changes such as indexing, column default values, etc" - enableTooltip={true} - > - Reload Segment - -
-
-
-
- - - - Segment Name: {unescape(segmentSummary.segmentName)} + if (initialLoad) { + return ; + } else if (segmentNotFound) { + return ; + } else { + return ( + +
+ +
+ { + handleDeleteSegmentClick(); + }} + tooltipTitle="Delete Segment" + enableTooltip={true} + > + Delete Segment + + { + handleReloadSegmentClick(); + }} + tooltipTitle="Reload the segment to apply changes such as indexing, column default values, etc" + enableTooltip={true} + > + Reload Segment + +
+
+
+
+ + + + Segment Name:{' '} + {unescape(segmentSummary.segmentName)} + + + + Total Docs: + + + {segmentSummary.totalDocs ? ( + segmentSummary.totalDocs + ) : ( + + )} + + + + + Create Time: + + + {segmentSummary.createTime ? ( + segmentSummary.createTime + ) : ( + + )} + + - - Total Docs: {segmentSummary.totalDocs} +
+ + + + - - Create Time: {segmentSummary.createTime} + +
+ + + +
-
- - + - -
- - - -
-
+ {confirmDialog && dialogDetails && ( + + )}
- - - - - - {confirmDialog && dialogDetails && } -
- ); + ); + } }; export default SegmentDetails; diff --git a/pinot-controller/src/main/resources/app/pages/TablesListingPage.tsx b/pinot-controller/src/main/resources/app/pages/TablesListingPage.tsx index dc2bd66f6e57..0e586662d108 100644 --- a/pinot-controller/src/main/resources/app/pages/TablesListingPage.tsx +++ b/pinot-controller/src/main/resources/app/pages/TablesListingPage.tsx @@ -17,18 +17,15 @@ * under the License. */ -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import { Grid, makeStyles } from '@material-ui/core'; -import { TableData } from 'Models'; -import AppLoader from '../components/AppLoader'; -import PinotMethodUtils from '../utils/PinotMethodUtils'; -import CustomizedTables from '../components/Table'; import CustomButton from '../components/CustomButton'; import SimpleAccordion from '../components/SimpleAccordion'; import AddSchemaOp from '../components/Homepage/Operations/AddSchemaOp'; import AddOfflineTableOp from '../components/Homepage/Operations/AddOfflineTableOp'; import AddRealtimeTableOp from '../components/Homepage/Operations/AddRealtimeTableOp'; -import Skeleton from '@material-ui/lab/Skeleton'; +import AsyncPinotTables from '../components/AsyncPinotTables'; +import { AsyncPinotSchemas } from '../components/AsyncPinotSchemas'; const useStyles = makeStyles(() => ({ gridContainer: { @@ -44,27 +41,9 @@ const useStyles = makeStyles(() => ({ }, })); -const TableTooltipData = [ - null, - 'Uncompressed size of all data segments', - 'Estimated size of all data segments, in case any servers are not reachable for actual size', - null, - 'GOOD if all replicas of all segments are up', -]; - const TablesListingPage = () => { const classes = useStyles(); - const [schemaDetails, setSchemaDetails] = useState({ - columns: PinotMethodUtils.allSchemaDetailsColumnHeader, - records: [], - isLoading: true, - }); - const [tableData, setTableData] = useState({ - columns: PinotMethodUtils.allTableDetailsColumnHeader, - records: [], - isLoading: true, - }); const [showSchemaModal, setShowSchemaModal] = useState(false); const [showAddOfflineTableModal, setShowAddOfflineTableModal] = useState( false @@ -72,55 +51,11 @@ const TablesListingPage = () => { const [showAddRealtimeTableModal, setShowAddRealtimeTableModal] = useState( false ); - - const loading = { customRenderer: }; - - const fetchData = async () => { - const schemaResponse = await PinotMethodUtils.getQuerySchemaList(); - const schemaList = []; - const schemaData = []; - schemaResponse.records.map((record) => { - schemaList.push(...record); - }); - schemaList.map((schema) => { - schemaData.push([schema].concat([...Array(PinotMethodUtils.allSchemaDetailsColumnHeader.length - 1)].map((e) => loading))); - }); - const tablesResponse = await PinotMethodUtils.getQueryTablesList({ - bothType: true, - }); - const tablesList = []; - const tableData = []; - tablesResponse.records.map((record) => { - tablesList.push(...record); - }); - tablesList.map((table) => { - tableData.push([table].concat([...Array(PinotMethodUtils.allTableDetailsColumnHeader.length - 1)].map((e) => loading))); - }); - // Set the table data to "Loading..." at first as tableSize can take minutes to fetch - // for larger tables. - setTableData({ - columns: PinotMethodUtils.allTableDetailsColumnHeader, - records: tableData, - isLoading: false, - }); - - // Set just the column headers so these do not have to load with the data - setSchemaDetails({ - columns: PinotMethodUtils.allSchemaDetailsColumnHeader, - records: schemaData, - isLoading: false, - }); - - // these implicitly set isLoading=false by leaving it undefined - const tableDetails = await PinotMethodUtils.getAllTableDetails(tablesList); - setTableData(tableDetails); - const schemaDetailsData = await PinotMethodUtils.getAllSchemaDetails(schemaList); - setSchemaDetails(schemaDetailsData); - }; - - useEffect(() => { - fetchData(); - }, []); + // This is used to refresh the tables and schemas data after a new table or schema is added. + // This is quite hacky, but it's simpler than trying to useRef or useContext to maintain + // a link between this component and the child table and schema components. + const [tablesKey, setTablesKey] = useState(0); + const [schemasKey, setSchemasKey] = useState(0); return ( @@ -157,29 +92,20 @@ const TablesListingPage = () => {
- - + {showSchemaModal && ( { setShowSchemaModal(false); }} - fetchData={fetchData} + fetchData={() => { + setSchemasKey((prevKey) => prevKey + 1); + }} /> )} {showAddOfflineTableModal && ( @@ -187,7 +113,9 @@ const TablesListingPage = () => { hideModal={() => { setShowAddOfflineTableModal(false); }} - fetchData={fetchData} + fetchData={() => { + setTablesKey((prevKey) => prevKey + 1); + }} tableType={'OFFLINE'} /> )} @@ -196,7 +124,9 @@ const TablesListingPage = () => { hideModal={() => { setShowAddRealtimeTableModal(false); }} - fetchData={fetchData} + fetchData={() => { + setTablesKey((prevKey) => prevKey + 1); + }} tableType={'REALTIME'} /> )} diff --git a/pinot-controller/src/main/resources/app/pages/TaskQueue.tsx b/pinot-controller/src/main/resources/app/pages/TaskQueue.tsx index 0c9fe5d45c40..3bd62ef7a235 100644 --- a/pinot-controller/src/main/resources/app/pages/TaskQueue.tsx +++ b/pinot-controller/src/main/resources/app/pages/TaskQueue.tsx @@ -68,7 +68,7 @@ const TaskQueue = (props) => { const tablesResponse:any = await PinotMethodUtils.getTableData({ taskType }); setTaskInfo(taskInfoRes); setTables((prevState): TableData => { - const _records = map(get(tablesResponse, 'tables', []), table => [[table]]); + const _records = map(get(tablesResponse, 'tables', []), table => [table]); return { ...prevState, records: _records }; }); setFetching(false); diff --git a/pinot-controller/src/main/resources/app/pages/TaskQueueTable.tsx b/pinot-controller/src/main/resources/app/pages/TaskQueueTable.tsx index aa50548f29ec..0fb2d4e2fae1 100644 --- a/pinot-controller/src/main/resources/app/pages/TaskQueueTable.tsx +++ b/pinot-controller/src/main/resources/app/pages/TaskQueueTable.tsx @@ -107,7 +107,7 @@ const TaskQueueTable = (props) => { const handleScheduleNow = async () => { const res = await PinotMethodUtils.scheduleTaskAction(tableName, taskType); - if (get(res, `data.${taskType}`, null) === null) { + if (get(res, `${taskType}`, null) === null) { dispatch({ type: 'error', message: `Could not schedule task`, @@ -116,7 +116,7 @@ const TaskQueueTable = (props) => { } else { dispatch({ type: 'success', - message: `${get(res, `data.${taskType}`, null)} scheduled successfully`, + message: `${get(res, `${taskType}`, null)} scheduled successfully`, show: true }); } diff --git a/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx b/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx index f456dbb7e0a1..0fbd9c6af451 100644 --- a/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx +++ b/pinot-controller/src/main/resources/app/pages/TenantDetails.tsx @@ -22,7 +22,7 @@ import { makeStyles } from '@material-ui/core/styles'; import { Box, Button, FormControlLabel, Grid, Switch, Tooltip, Typography } from '@material-ui/core'; import { RouteComponentProps, useHistory, useLocation } from 'react-router-dom'; import { UnControlled as CodeMirror } from 'react-codemirror2'; -import { DISPLAY_SEGMENT_STATUS, TableData, TableSegmentJobs } from 'Models'; +import { DISPLAY_SEGMENT_STATUS, InstanceState, TableData, TableSegmentJobs, TableType } from 'Models'; import AppLoader from '../components/AppLoader'; import CustomizedTables from '../components/Table'; import TableToolbar from '../components/TableToolbar'; @@ -40,8 +40,10 @@ import Confirm from '../components/Confirm'; import { NotificationContext } from '../components/Notification/NotificationContext'; import Utils from '../utils/Utils'; import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined'; -import { get } from "lodash"; +import { get, isEmpty } from "lodash"; import { SegmentStatusRenderer } from '../components/SegmentStatusRenderer'; +import Skeleton from '@material-ui/lab/Skeleton'; +import NotFound from '../components/NotFound'; const useStyles = makeStyles((theme) => ({ root: { @@ -98,8 +100,8 @@ type Props = { type Summary = { tableName: string; - reportedSize: string | number; - estimatedSize: string | number; + reportedSize: number; + estimatedSize: number; }; const TenantPageDetails = ({ match }: RouteComponentProps) => { @@ -108,13 +110,16 @@ const TenantPageDetails = ({ match }: RouteComponentProps) => { const history = useHistory(); const location = useLocation(); const [fetching, setFetching] = useState(true); - const [tableSummary, setTableSummary] = useState({ + const [tableNotFound, setTableNotFound] = useState(false); + + const initialTableSummary: Summary = { tableName: match.params.tableName, - reportedSize: '', - estimatedSize: '', - }); + reportedSize: null, + estimatedSize: null, + }; + const [tableSummary, setTableSummary] = useState(initialTableSummary); - const [state, setState] = React.useState({ + const [tableState, setTableState] = React.useState({ enabled: true, }); @@ -124,15 +129,14 @@ const TenantPageDetails = ({ match }: RouteComponentProps) => { const [showEditConfig, setShowEditConfig] = useState(false); const [config, setConfig] = useState('{}'); - const [instanceCountData, setInstanceCountData] = useState({ - columns: [], - records: [], - }); - const [segmentList, setSegmentList] = useState({ - columns: [], - records: [], - }); + const instanceColumns = ["Instance Name", "# of segments"]; + const loadingInstanceData = Utils.getLoadingTableData(instanceColumns); + const [instanceCountData, setInstanceCountData] = useState(loadingInstanceData); + + const segmentListColumns = ['Segment Name', 'Status']; + const loadingSegmentList = Utils.getLoadingTableData(segmentListColumns); + const [segmentList, setSegmentList] = useState(loadingSegmentList); const [tableSchema, setTableSchema] = useState({ columns: [], @@ -149,12 +153,37 @@ const TenantPageDetails = ({ match }: RouteComponentProps) => { const [schemaJSONFormat, setSchemaJSONFormat] = useState(false); const fetchTableData = async () => { + // We keep all the fetching inside this component since we need to be able + // to handle users making changes to the table and then reloading the json. setFetching(true); - const result = await PinotMethodUtils.getTableSummaryData(tableName); - setTableSummary(result); - fetchSegmentData(); + fetchSyncTableData().then(()=> { + setFetching(false); + if (!tableNotFound) { + fetchAsyncTableData(); + } + }); }; + const fetchSyncTableData = async () => { + return Promise.all([ + fetchTableSchema(), + fetchTableJSON(), + ]); + } + + const fetchAsyncTableData = async () => { + // set async data back to loading + setTableSummary(initialTableSummary); + setInstanceCountData(loadingInstanceData); + setSegmentList(loadingSegmentList); + + // load async data + PinotMethodUtils.getTableSummaryData(tableName).then((result) => { + setTableSummary(result); + }); + fetchSegmentData() + } + const fetchSegmentData = async () => { const result = await PinotMethodUtils.getSegmentList(tableName); const {columns, records, externalViewObj} = result; @@ -173,7 +202,7 @@ const TenantPageDetails = ({ match }: RouteComponentProps) => { instanceRecords.push([instanceName, instanceObj[instanceName]]); }) setInstanceCountData({ - columns: ["Instance Name", "# of segments"], + columns: instanceColumns, records: instanceRecords }); @@ -194,7 +223,6 @@ const TenantPageDetails = ({ match }: RouteComponentProps) => { ); setSegmentList({columns, records: segmentTableRows}); - fetchTableSchema(); }; const fetchTableSchema = async () => { @@ -202,7 +230,7 @@ const TenantPageDetails = ({ match }: RouteComponentProps) => { if(result.error){ setSchemaJSON(null); setTableSchema({ - columns: ['Column', 'Type', 'Field Type'], + columns: ['Column', 'Type', 'Field Type', 'Multi Value'], records: [] }); } else { @@ -210,26 +238,30 @@ const TenantPageDetails = ({ match }: RouteComponentProps) => { const tableSchema = Utils.syncTableSchemaData(result, true); setTableSchema(tableSchema); } - fetchTableJSON(); }; const fetchTableJSON = async () => { - const result = await PinotMethodUtils.getTableDetails(tableName); - if(result.error){ - setFetching(false); - dispatch({type: 'error', message: result.error, show: true}); - } else { - const tableObj:any = result.OFFLINE || result.REALTIME; - setTableType(tableObj.tableType); - setTableConfig(JSON.stringify(result, null, 2)); - fetchTableState(tableObj.tableType); - } + return PinotMethodUtils.getTableDetails(tableName).then((result) => { + if(result.error){ + dispatch({type: 'error', message: result.error, show: true}); + } else { + if (isEmpty(result)) { + setTableNotFound(true); + return; + } + const tableObj:any = result.OFFLINE || result.REALTIME; + setTableType(tableObj.tableType); + setTableConfig(JSON.stringify(result, null, 2)); + return fetchTableState(tableObj.tableType); + } + }); }; const fetchTableState = async (type) => { - const stateResponse = await PinotMethodUtils.getTableState(tableName, type); - setState({enabled: stateResponse.state === 'enabled'}); - setFetching(false); + return PinotMethodUtils.getTableState(tableName, type) + .then((stateResponse) => { + return setTableState({enabled: stateResponse.state === 'enabled'}); + }); }; useEffect(() => { @@ -238,27 +270,16 @@ const TenantPageDetails = ({ match }: RouteComponentProps) => { const handleSwitchChange = (event) => { setDialogDetails({ - title: state.enabled ? 'Disable Table' : 'Enable Table', - content: `Are you sure want to ${state.enabled ? 'disable' : 'enable'} this table?`, + title: tableState.enabled ? 'Disable Table' : 'Enable Table', + content: `Are you sure want to ${tableState.enabled ? 'disable' : 'enable'} this table?`, successCb: () => toggleTableState() }); setConfirmDialog(true); }; const toggleTableState = async () => { - const result = await PinotMethodUtils.toggleTableState(tableName, state.enabled ? 'disable' : 'enable', tableType.toLowerCase()); - if(!result.error && result[0].state){ - if(result[0].state.successful){ - dispatch({type: 'success', message: result[0].state.message, show: true}); - setState({ enabled: !state.enabled }); - fetchTableData(); - } else { - dispatch({type: 'error', message: result[0].state.message, show: true}); - } - closeDialog(); - } else { - syncResponse(result); - } + const result = await PinotMethodUtils.toggleTableState(tableName, tableState.enabled ? InstanceState.DISABLE : InstanceState.ENABLE, tableType.toLowerCase() as TableType); + syncResponse(result); }; const handleConfigChange = (value: string) => { @@ -348,7 +369,7 @@ const TenantPageDetails = ({ match }: RouteComponentProps) => { try { // extract reloadJobId from response - const statusResponseObj = JSON.parse(result.status.replace("Segment reload details: ", "")) + const statusResponseObj = JSON.parse(result.status) reloadJobId = get(statusResponseObj, `${tableName}.reloadJobId`, null) } catch { reloadJobId = null; @@ -384,7 +405,7 @@ const TenantPageDetails = ({ match }: RouteComponentProps) => { setShowReloadStatusModal(true); const [reloadStatusData, tableJobsData] = await Promise.all([ PinotMethodUtils.reloadStatusOp(tableName, tableType), - PinotMethodUtils.fetchTableJobs(tableName), + PinotMethodUtils.fetchTableJobs(tableName, "RELOAD_SEGMENT"), ]); if(reloadStatusData.error || tableJobsData.error) { @@ -420,236 +441,257 @@ const TenantPageDetails = ({ match }: RouteComponentProps) => { setDialogDetails(null); }; - return fetching ? ( - - ) : ( - -
- -
- { - setActionType('editTable'); - setConfig(tableConfig); - setShowEditConfig(true); - }} - tooltipTitle="Edit Table" - enableTooltip={true} - > - Edit Table - - - Delete Table - - { - setActionType('editSchema'); - setConfig(JSON.stringify(schemaJSON, null, 2)); - setShowEditConfig(true); - }} - tooltipTitle="Edit Schema" - enableTooltip={true} - > - Edit Schema - - - Delete Schema - - {console.log('truncate table');}} - // tooltipTitle="Truncate Table" - // enableTooltip={true} - > - Truncate Table - - - Reload All Segments - - - Reload Status - - {setShowRebalanceServerModal(true);}} - tooltipTitle="Recalculates the segment to server mapping for this table" - enableTooltip={true} - > - Rebalance Servers - - - Rebalance Brokers - - - ; + } else if (tableNotFound) { + return ; + } else { + return ( + +
+ +
+ { + setActionType('editTable'); + setConfig(tableConfig); + setShowEditConfig(true); + }} + tooltipTitle="Edit Table" + enableTooltip={true} + > + Edit Table + + + Delete Table + + { + setActionType('editSchema'); + setConfig(JSON.stringify(schemaJSON, null, 2)); + setShowEditConfig(true); + }} + tooltipTitle="Edit Schema" + enableTooltip={true} + > + Edit Schema + + + Delete Schema + + {}} + tooltipTitle="Truncate Table" + enableTooltip={true} + > + Truncate Table + + + Reload All Segments + + + Reload Status + + {setShowRebalanceServerModal(true);}} + tooltipTitle="Recalculates the segment to server mapping for this table" + enableTooltip={true} + > + Rebalance Servers + + + Rebalance Brokers + + + + } + label="Enable" + /> + +
+
+
+
+ + + + Table Name: {tableSummary.tableName} + + + + + Reported Size: + + + + {/* Now Skeleton can be a block element because it's the only thing inside this grid item */} + {tableSummary.reportedSize ? + Utils.formatBytes(tableSummary.reportedSize) : + + } + + + + + + Estimated Size: + + + + {/* Now Skeleton can be a block element because it's the only thing inside this grid item */} + {tableSummary.estimatedSize ? + Utils.formatBytes(tableSummary.estimatedSize) : + + } + + + +
+ + + +
+ + + +
+ -
-
-
-
-
- - - - Table Name: {tableSummary.tableName} - - - - Reported Size: {Utils.formatBytes(tableSummary.reportedSize)} - - - - - Estimated Size: - {Utils.formatBytes(tableSummary.estimatedSize)} - - - - -
- - - -
- - + {!schemaJSONFormat ? + {setSchemaJSONFormat(!schemaJSONFormat);} + }} /> - -
- + {setSchemaJSONFormat(!schemaJSONFormat);} + }} + > + + + } - addLinks - showSearchBox={true} - inAccordionFormat={true} - /> -
- - {!schemaJSONFormat ? {setSchemaJSONFormat(!schemaJSONFormat);} - }} /> - : -
- {setSchemaJSONFormat(!schemaJSONFormat);} - }} - > - - -
- } - +
-
- {setShowEditConfig(false);}} - saveConfig={saveConfigAction} - config={config} - handleConfigChange={handleConfigChange} - /> - { - showReloadStatusModal && - {setShowReloadStatusModal(false); setReloadStatusData(null)}} - reloadStatusData={reloadStatusData} - tableJobsData={tableJobsData} - /> - } - {showRebalanceServerModal && - {setShowRebalanceServerModal(false)}} - tableType={tableType.toUpperCase()} - tableName={tableName} + {setShowEditConfig(false);}} + saveConfig={saveConfigAction} + config={config} + handleConfigChange={handleConfigChange} /> - } - {confirmDialog && dialogDetails && } - - ); + { + showReloadStatusModal && + {setShowReloadStatusModal(false); setReloadStatusData(null)}} + reloadStatusData={reloadStatusData} + tableJobsData={tableJobsData} + /> + } + {showRebalanceServerModal && + {setShowRebalanceServerModal(false)}} + tableType={tableType.toUpperCase()} + tableName={tableName} + /> + } + {confirmDialog && dialogDetails && } + + ); + } }; export default TenantPageDetails; diff --git a/pinot-controller/src/main/resources/app/pages/Tenants.tsx b/pinot-controller/src/main/resources/app/pages/Tenants.tsx index dd39b9a8e7e1..19f61ec6e11e 100644 --- a/pinot-controller/src/main/resources/app/pages/Tenants.tsx +++ b/pinot-controller/src/main/resources/app/pages/Tenants.tsx @@ -17,76 +17,47 @@ * under the License. */ -import React, { useState, useEffect } from 'react'; +import React from 'react'; import { Grid, makeStyles } from '@material-ui/core'; -import { TableData } from 'Models'; +import { InstanceType } from 'Models'; import { RouteComponentProps } from 'react-router-dom'; -import CustomizedTables from '../components/Table'; -import AppLoader from '../components/AppLoader'; -import PinotMethodUtils from '../utils/PinotMethodUtils'; import SimpleAccordion from '../components/SimpleAccordion'; +import AsyncPinotTables from '../components/AsyncPinotTables'; import CustomButton from '../components/CustomButton'; +import { AsyncInstanceTable } from '../components/AsyncInstanceTable'; const useStyles = makeStyles((theme) => ({ operationDiv: { border: '1px #BDCCD9 solid', borderRadius: 4, - marginBottom: 20 - } + marginBottom: 20, + }, })); type Props = { - tenantName: string + tenantName: string; }; -const TableTooltipData = [ - null, - "Uncompressed size of all data segments", - "Estimated size of all data segments, in case any servers are not reachable for actual size", - null, - "GOOD if all replicas of all segments are up" -]; - const TenantPage = ({ match }: RouteComponentProps) => { - - const {tenantName} = match.params; - const columnHeaders = ['Table Name', 'Reported Size', 'Estimated Size', 'Number of Segments', 'Status']; - const [fetching, setFetching] = useState(true); - const [tableData, setTableData] = useState({ - columns: columnHeaders, - records: [] - }); - const [brokerData, setBrokerData] = useState(null); - const [serverData, setServerData] = useState([]); - - const fetchData = async () => { - const tenantData = await PinotMethodUtils.getTenantTableData(tenantName); - const brokersData = await PinotMethodUtils.getBrokerOfTenant(tenantName); - const serversData = await PinotMethodUtils.getServerOfTenant(tenantName); - setTableData(tenantData); - const separatedBrokers = Array.isArray(brokersData) ? brokersData.map((elm) => [elm]) : []; - setBrokerData(separatedBrokers || []); - const separatedServers = Array.isArray(serversData) ? serversData.map((elm) => [elm]) : []; - setServerData(separatedServers || []); - setFetching(false); - }; - useEffect(() => { - fetchData(); - }, []); - + const { tenantName } = match.params; const classes = useStyles(); return ( - fetching ? : - +
- +
{console.log('rebalance');}} + onClick={() => {}} tooltipTitle="Recalculates the segment to server mapping for all tables in this tenant" enableTooltip={true} isDisabled={true} @@ -94,7 +65,7 @@ const TenantPage = ({ match }: RouteComponentProps) => { Rebalance Server Tenant {console.log('rebuild');}} + onClick={() => {}} tooltipTitle="Rebuilds brokerResource mappings for all tables in this tenant" enableTooltip={true} isDisabled={true} @@ -104,40 +75,22 @@ const TenantPage = ({ match }: RouteComponentProps) => {
- - 0 ? brokerData : [] - }} - addLinks - baseURL="/instance/" - showSearchBox={true} - inAccordionFormat={true} + - 0 ? serverData : [] - }} - addLinks - baseURL="/instance/" - showSearchBox={true} - inAccordionFormat={true} + diff --git a/pinot-controller/src/main/resources/app/pages/TenantsListingPage.tsx b/pinot-controller/src/main/resources/app/pages/TenantsListingPage.tsx index 531d35e1ed01..2fc32803a078 100644 --- a/pinot-controller/src/main/resources/app/pages/TenantsListingPage.tsx +++ b/pinot-controller/src/main/resources/app/pages/TenantsListingPage.tsx @@ -17,46 +17,27 @@ * under the License. */ -import React, {useState, useEffect} from 'react'; +import React from 'react'; import { Grid, makeStyles } from '@material-ui/core'; -import { TableData } from 'Models'; -import AppLoader from '../components/AppLoader'; -import PinotMethodUtils from '../utils/PinotMethodUtils'; -import TenantsListing from '../components/Homepage/TenantsListing'; +import TenantsTable from '../components/Homepage/TenantsListing'; const useStyles = makeStyles(() => ({ gridContainer: { padding: 20, backgroundColor: 'white', maxHeight: 'calc(100vh - 70px)', - overflowY: 'auto' + overflowY: 'auto', }, - })); const TenantsListingPage = () => { const classes = useStyles(); - const [fetching, setFetching] = useState(true); - const [tenantsData, setTenantsData] = useState({ records: [], columns: [] }); - - const fetchData = async () => { - const tenantsDataResponse = await PinotMethodUtils.getTenantsData(); - setTenantsData(tenantsDataResponse); - setFetching(false); - }; - - useEffect(() => { - fetchData(); - }, []); - - return fetching ? ( - - ) : ( + return ( - + ); }; -export default TenantsListingPage; \ No newline at end of file +export default TenantsListingPage; diff --git a/pinot-controller/src/main/resources/app/requests/index.ts b/pinot-controller/src/main/resources/app/requests/index.ts index 88811594870d..e2d4d54910ea 100644 --- a/pinot-controller/src/main/resources/app/requests/index.ts +++ b/pinot-controller/src/main/resources/app/requests/index.ts @@ -18,10 +18,34 @@ */ import { AxiosResponse } from 'axios'; -import { TableData, Instances, Instance, Tenants, ClusterConfig, TableName, TableSize, - IdealState, QueryTables, TableSchema, SQLResult, ClusterName, ZKGetList, ZKConfig, OperationResponse, - BrokerList, ServerList, UserList, TableList, UserObject, TaskProgressResponse, TableSegmentJobs, TaskRuntimeConfig, - SegmentDebugDetails, QuerySchemas +import { + TableData, + Instances, + Instance, + Tenants, + ClusterConfig, + TableName, + TableSize, + IdealState, + QueryTables, + TableSchema, + SQLResult, + ClusterName, + ZKGetList, + ZKConfig, + OperationResponse, + BrokerList, + ServerList, + UserList, + TableList, + UserObject, + TaskProgressResponse, + TableSegmentJobs, + TaskRuntimeConfig, + SegmentDebugDetails, + QuerySchemas, + TableType, + InstanceState, SegmentMetadata, } from 'Models'; const headers = { @@ -62,7 +86,7 @@ export const putSchema = (name: string, params: string, reload?: boolean): Promi return baseApi.put(`/schemas/${name}`, params, { headers, params: queryParams }); } -export const getSegmentMetadata = (tableName: string, segmentName: string): Promise> => +export const getSegmentMetadata = (tableName: string, segmentName: string): Promise> => baseApi.get(`/segments/${tableName}/${segmentName}/metadata?columns=*`); export const getTableSize = (name: string): Promise> => @@ -86,11 +110,11 @@ export const putInstance = (name: string, params: string): Promise> => baseApi.put(`/instances/${name}/updateTags?tags=${params}`, null, { headers }); -export const setInstanceState = (name: string, stateName: string): Promise> => - baseApi.post(`/instances/${name}/state`, stateName, { headers: {'Content-Type': 'text/plain', 'Accept': 'application/json'} }); +export const setInstanceState = (name: string, state: InstanceState): Promise> => + baseApi.put(`/instances/${name}/state?state=${state}`, { headers: {'Content-Type': 'text/plain', 'Accept': 'application/json'} }); -export const setTableState = (name: string, stateName: string, tableType: string): Promise> => - baseApi.get(`/tables/${name}?state=${stateName}&type=${tableType}`); +export const setTableState = (tableName: string, state: InstanceState, tableType: TableType): Promise> => + baseApi.put(`/tables/${tableName}/state?state=${state}&type=${tableType}`); export const dropInstance = (name: string): Promise> => baseApi.delete(`instances/${name}`, { headers }); @@ -119,7 +143,7 @@ export const cleanupTasks = (taskType: string): Promise> => baseApi.delete(`/tasks/${taskType}`, { headers: { ...headers, Accept: 'application/json' } }); -export const sheduleTask = (tableName: string, taskType: string): Promise> => +export const scheduleTask = (tableName: string, taskType: string): Promise> => baseApi.post(`/tasks/schedule?tableName=${tableName}&taskType=${taskType}`, null, { headers: { ...headers, Accept: 'application/json' } }); export const executeTask = (data): Promise> => @@ -198,13 +222,20 @@ export const reloadAllSegments = (tableName: string, tableType: string): Promise baseApi.post(`/segments/${tableName}/reload?type=${tableType}`, null, {headers}); export const reloadStatus = (tableName: string, tableType: string): Promise> => - baseApi.get(`/segments/${tableName}/metadata?type=${tableType}&columns=*`); + baseApi.get(`/tables/${tableName}/indexes?type=${tableType}`); export const deleteSegment = (tableName: string, instanceName: string): Promise> => baseApi.delete(`/segments/${tableName}/${instanceName}`, {headers}); -export const getTableJobs = (tableName: string): Promise> => - baseApi.get(`/table/${tableName}/jobs`); +export const getTableJobs = (tableName: string, jobTypes?: string): Promise> => { + let queryParams = {}; + + if (jobTypes) { + queryParams["jobTypes"] = jobTypes + } + + return baseApi.get(`/table/${tableName}/jobs`, { params: queryParams }); +} export const getSegmentReloadStatus = (jobId: string): Promise> => baseApi.get(`/segments/segmentReloadStatus/${jobId}`, {headers}); @@ -222,13 +253,13 @@ export const rebalanceBrokersForTable = (tableName: string): Promise> => - baseApi.post(`/schemas/validate`, JSON.stringify(schemaObject), {headers}); + baseApi.post(`/schemas/validate`, schemaObject, {headers}); export const validateTable = (tableObject: string): Promise> => baseApi.post(`/tables/validate`, JSON.stringify(tableObject), {headers}); export const saveSchema = (schemaObject: string): Promise> => - baseApi.post(`/schemas`, JSON.stringify(schemaObject), {headers}); + baseApi.post(`/schemas`, schemaObject, {headers}); export const saveTable = (tableObject: string): Promise> => baseApi.post(`/tables`, JSON.stringify(tableObject), {headers}); diff --git a/pinot-controller/src/main/resources/app/router.tsx b/pinot-controller/src/main/resources/app/router.tsx index 3975ebf50796..90bb5da9459c 100644 --- a/pinot-controller/src/main/resources/app/router.tsx +++ b/pinot-controller/src/main/resources/app/router.tsx @@ -37,6 +37,7 @@ import LoginPage from './pages/LoginPage'; import UserPage from "./pages/UserPage"; export default [ + // TODO: make async { path: '/', Component: HomePage }, { path: '/query', Component: QueryPage }, { path: '/tenants', Component: TenantsListingPage }, diff --git a/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts b/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts index 8809db3946b3..7d971036ddf7 100644 --- a/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts +++ b/pinot-controller/src/main/resources/app/utils/PinotMethodUtils.ts @@ -17,8 +17,9 @@ * under the License. */ +import jwtDecode from "jwt-decode"; import { get, map, each, isEqual, isArray, keys, union } from 'lodash'; -import { DataTable, SQLResult } from 'Models'; +import { DataTable, SegmentMetadata, SqlException, SQLResult, TableSize } from 'Models'; import moment from 'moment'; import { getTenants, @@ -38,7 +39,7 @@ import { resumeTasks, cleanupTasks, deleteTasks, - sheduleTask, + scheduleTask, executeTask, getJobDetail, getMinionMeta, @@ -266,22 +267,28 @@ const getQueryResults = (params) => { return getQueryResult(params).then(({ data }) => { let queryResponse = getAsObject(data); - let errorStr = ''; + let exceptions: SqlException[] = []; let dataArray = []; let columnList = []; // if sql api throws error, handle here if(typeof queryResponse === 'string'){ - errorStr = queryResponse; - } else if (queryResponse && queryResponse.exceptions && queryResponse.exceptions.length) { - errorStr = JSON.stringify(queryResponse.exceptions, null, 2); - } else - { - if (queryResponse.resultTable?.dataSchema?.columnNames?.length) - { - columnList = queryResponse.resultTable.dataSchema.columnNames; - dataArray = queryResponse.resultTable.rows; + exceptions.push({errorCode: null, message: queryResponse}); + } + // if sql api returns a structured error with a `code`, handle here + if (queryResponse && queryResponse.code) { + if (queryResponse.error) { + exceptions.push({errorCode: null, message: "Query failed with error code: " + queryResponse.code + " and error: " + queryResponse.error}); + } else { + exceptions.push({errorCode: null, message: "Query failed with error code: " + queryResponse.code + " but no logs. Please see controller logs for error."}); } } + if (queryResponse && queryResponse.exceptions && queryResponse.exceptions.length) { + exceptions = queryResponse.exceptions as SqlException[]; + } + if (queryResponse.resultTable?.dataSchema?.columnNames?.length) { + columnList = queryResponse.resultTable.dataSchema.columnNames; + dataArray = queryResponse.resultTable.rows; + } const columnStats = ['timeUsedMs', 'numDocsScanned', @@ -308,7 +315,7 @@ const getQueryResults = (params) => { ]; return { - error: errorStr, + exceptions: exceptions, result: { columns: columnList, records: dataArray, @@ -345,7 +352,6 @@ const getTenantTableData = (tenantName) => { const getSchemaObject = async (schemaName) =>{ let schemaObj:Array = []; let {data} = await getSchema(schemaName); - console.log(data); schemaObj.push(data.schemaName); schemaObj.push(data.dimensionFieldSpecs ? data.dimensionFieldSpecs.length : 0); schemaObj.push(data.dateTimeFieldSpecs ? data.dateTimeFieldSpecs.length : 0); @@ -402,6 +408,30 @@ const allTableDetailsColumnHeader = [ 'Status', ]; +const getTableSizes = (tableName: string) => { + return getTableSize(tableName).then(result => { + return { + reported_size: Utils.formatBytes(result.data.reportedSizeInBytes), + estimated_size: Utils.formatBytes(result.data.estimatedSizeInBytes), + }; + }) +} + +const getSegmentCountAndStatus = (tableName: string) => { + return getIdealState(tableName).then(result => { + const idealState = result.data.OFFLINE || result.data.REALTIME || {}; + return getExternalView(tableName).then(result => { + const externalView = result.data.OFFLINE || result.data.REALTIME || {}; + const externalSegmentCount = Object.keys(externalView).length; + const idealSegmentCount = Object.keys(idealState).length; + return { + segment_count: `${externalSegmentCount} / ${idealSegmentCount}`, + segment_status: Utils.getSegmentStatus(idealState, externalView) + }; + }); + }); +} + const getAllTableDetails = (tablesList) => { if (tablesList.length) { const promiseArr = []; @@ -457,10 +487,10 @@ const getAllTableDetails = (tablesList) => { }; }); } - return { + return Promise.resolve({ columns: allTableDetailsColumnHeader, records: [] - }; + }); }; // This method is used to display summary of a particular tenant table @@ -549,7 +579,7 @@ const getSegmentDetails = (tableName, segmentName) => { return Promise.all(promiseArr).then((results) => { const obj = results[0].data.OFFLINE || results[0].data.REALTIME; - const segmentMetaData = results[1].data; + const segmentMetaData: SegmentMetadata = results[1].data; const debugObj = results[2].data; let debugInfoObj = {}; @@ -562,7 +592,6 @@ const getSegmentDetails = (tableName, segmentName) => { }); } } - console.log(debugInfoObj); const result = []; for (const prop in obj[segmentName]) { @@ -575,6 +604,7 @@ const getSegmentDetails = (tableName, segmentName) => { const segmentMetaDataJson = { ...segmentMetaData } delete segmentMetaDataJson.indexes delete segmentMetaDataJson.columns + const indexes = get(segmentMetaData, 'indexes', {}) return { replicaSet: { @@ -583,7 +613,7 @@ const getSegmentDetails = (tableName, segmentName) => { }, indexes: { columns: ['Field Name', 'Bloom Filter', 'Dictionary', 'Forward Index', 'Sorted', 'Inverted Index', 'JSON Index', 'Null Value Vector Reader', 'Range Index'], - records: Object.keys(segmentMetaData.indexes).map(fieldName => [ + records: Object.keys(indexes).map(fieldName => [ fieldName, segmentMetaData.indexes[fieldName]["bloom-filter"] === "YES", segmentMetaData.indexes[fieldName]["dictionary"] === "YES", @@ -711,13 +741,13 @@ const deleteNode = (path) => { }); }; -const getBrokerOfTenant = (tenantName) => { +const getBrokerOfTenant = (tenantName: string) => { return getBrokerListOfTenant(tenantName).then((response)=>{ return !response.data.error ? response.data : []; }); }; -const getServerOfTenant = (tenantName) => { +const getServerOfTenant = (tenantName: string) => { return getServerListOfTenant(tenantName).then((response)=>{ return !response.data.error ? response.data.ServerInstances : []; }); @@ -778,7 +808,12 @@ const getAllTaskTypes = async () => { const getTaskInfo = async (taskType) => { const tasksRes = await getTaskTypeTasks(taskType); const stateRes = await getTaskTypeState(taskType); - const state = get(stateRes, 'data', ''); + + let state = get(stateRes, 'data', ''); + // response contains error + if(typeof state !== "string") { + state = ""; + } return [tasksRes?.data?.length || 0, state]; }; @@ -893,8 +928,8 @@ const deleteSegmentOp = (tableName, segmentName) => { }); }; -const fetchTableJobs = async (tableName: string) => { - const response = await getTableJobs(tableName); +const fetchTableJobs = async (tableName: string, jobTypes?: string) => { + const response = await getTableJobs(tableName, jobTypes); return response.data; } @@ -1000,7 +1035,9 @@ const verifyAuth = (authToken) => { const getAccessTokenFromHashParams = () => { let accessToken = ''; - const urlSearchParams = new URLSearchParams(location.hash.substr(1)); + const hashParam = removeAllLeadingForwardSlash(location.hash.substring(1)); + + const urlSearchParams = new URLSearchParams(hashParam); if (urlSearchParams.has('access_token')) { accessToken = urlSearchParams.get('access_token') as string; } @@ -1008,6 +1045,13 @@ const getAccessTokenFromHashParams = () => { return accessToken; }; +const removeAllLeadingForwardSlash = (string: string) => { + if(!string) { + return ""; + } + + return string.replace(new RegExp("^/+", "g"), ""); +} // validates app redirect path with known routes const validateRedirectPath = (path: string): boolean => { @@ -1101,7 +1145,7 @@ const getTableData = (params)=>{ }; const scheduleTaskAction = (tableName, taskType)=>{ - return sheduleTask(tableName, taskType).then(response=>{ + return scheduleTask(tableName, taskType).then(response=>{ return response.data; }) }; @@ -1142,6 +1186,52 @@ const updateUser = (userObject, passwordChanged) =>{ }) } +const getAuthUserNameFromAccessToken = ( + accessToken: string +): string => { + if (!accessToken) { + return ""; + } + + let decoded; + try { + decoded = jwtDecode(accessToken); + } catch (e) { + return ""; + } + + if (!decoded) { + return ""; + } + + const name = get(decoded, "name") || ""; + return name; +}; + +const getAuthUserEmailFromAccessToken = ( + accessToken: string +): string => { + if (!accessToken) { + return ""; + } + + let decoded; + try { + decoded = jwtDecode(accessToken); + } catch (e) { + return ""; + } + + if (!decoded) { + return ""; + } + + const email = + get(decoded, "email") || ""; + + return email; +}; + export default { getTenantsData, getAllInstances, @@ -1159,6 +1249,7 @@ export default { getSegmentStatus, getTableDetails, getSegmentDetails, + getSegmentCountAndStatus, getClusterName, getLiveInstance, getLiveInstanceConfig, @@ -1181,6 +1272,7 @@ export default { fetchSegmentReloadStatus, getTaskTypeDebugData, getTableData, + getTableSizes, getTaskRuntimeConfigData, getTaskInfo, stopAllTasks, @@ -1224,5 +1316,7 @@ export default { getUserList, addUser, deleteUser, - updateUser + updateUser, + getAuthUserNameFromAccessToken, + getAuthUserEmailFromAccessToken }; diff --git a/pinot-controller/src/main/resources/app/utils/Utils.tsx b/pinot-controller/src/main/resources/app/utils/Utils.tsx index 62f07fdd2a48..c0983230e1a2 100644 --- a/pinot-controller/src/main/resources/app/utils/Utils.tsx +++ b/pinot-controller/src/main/resources/app/utils/Utils.tsx @@ -22,7 +22,14 @@ import React from 'react'; import ReactDiffViewer, {DiffMethod} from 'react-diff-viewer'; import { map, isEqual, findIndex, findLast } from 'lodash'; import app_state from '../app_state'; -import { DISPLAY_SEGMENT_STATUS, SEGMENT_STATUS } from 'Models'; +import { + DISPLAY_SEGMENT_STATUS, InstanceType, + PinotTableDetails, + SEGMENT_STATUS, + SegmentStatus, + TableData, +} from 'Models'; +import Loading from '../components/Loading'; const sortArray = function (sortingArr, keyName, ascendingFlag) { if (ascendingFlag) { @@ -47,13 +54,33 @@ const sortArray = function (sortingArr, keyName, ascendingFlag) { }); }; -const tableFormat = (data) => { +const pinotTableDetailsFormat = (tableDetails: PinotTableDetails): Array => { + return [ + tableDetails.name, + tableDetails.estimated_size || Loading, + tableDetails.reported_size || Loading, + tableDetails.number_of_segments || Loading, + tableDetails.segment_status || Loading + ]; +} + +const pinotTableDetailsFromArray = (tableDetails: Array): PinotTableDetails => { + return { + name: tableDetails[0] as string, + estimated_size: tableDetails[1] as string, + reported_size: tableDetails[2] as string, + number_of_segments: tableDetails[3] as string, + segment_status: tableDetails[4] as any + }; +} + +const tableFormat = (data: TableData): Array<{ [key: string]: any }> => { const rows = data.records; const header = data.columns; - const results = []; + const results: Array<{ [key: string]: any }> = []; rows.forEach((singleRow) => { - const obj = {}; + const obj: { [key: string]: any } = {}; singleRow.forEach((val: any, index: number) => { obj[header[index]+app_state.columnNameSeparator+index] = val; }); @@ -63,55 +90,39 @@ const tableFormat = (data) => { }; const getSegmentStatus = (idealStateObj, externalViewObj) => { - const idealSegmentKeys = Object.keys(idealStateObj); - const idealSegmentCount = idealSegmentKeys.length; - - const externalSegmentKeys = Object.keys(externalViewObj); - const externalSegmentCount = externalSegmentKeys.length; - - if (idealSegmentCount !== externalSegmentCount) { - let segmentStatusComponent = ( - - ) - return { - value: 'Bad', - tooltip: `Ideal Segment Count: ${idealSegmentCount} does not match external Segment Count: ${externalSegmentCount}`, - component: segmentStatusComponent, - }; + const tableStatus = getDisplayTableStatus(idealStateObj, externalViewObj); + const statusMismatchDiffComponent = ( + + ); + + if(tableStatus === DISPLAY_SEGMENT_STATUS.BAD) { + return ({ + value: tableStatus, + tooltip: "One or more segments in this table are in bad state. Click the status to view more details.", + component: statusMismatchDiffComponent, + }) } - let segmentStatus = {value: 'Good', tooltip: null, component: null}; - idealSegmentKeys.map((segmentKey) => { - if (segmentStatus.value === 'Good') { - if (!isEqual(idealStateObj[segmentKey], externalViewObj[segmentKey])) { - let segmentStatusComponent = ( - - ) - segmentStatus = { - value: 'Bad', - tooltip: "Ideal Status does not match external status", - component: segmentStatusComponent - }; - } - } + if(tableStatus === DISPLAY_SEGMENT_STATUS.UPDATING) { + return ({ + value: tableStatus, + tooltip: "One or more segments in this table are in updating state. Click the status to view more details.", + component: statusMismatchDiffComponent, + }) + } + + return ({ + value: tableStatus, + tooltip: "All segments in this table are in good state.", }); - return segmentStatus; }; const findNestedObj = (entireObj, keyToFind, valToFind) => { @@ -304,9 +315,9 @@ const syncTableSchemaData = (data, showFieldType) => { const columnList = [...dimensionFields, ...metricFields, ...dateTimeField]; if (showFieldType) { return { - columns: ['Column', 'Type', 'Field Type'], + columns: ['Column', 'Type', 'Field Type', 'Multi Value'], records: columnList.map((field) => { - return [field.name, field.dataType, field.fieldType]; + return [field.name, field.dataType, field.fieldType, getMultiValueField(field)]; }), }; } @@ -318,6 +329,18 @@ const syncTableSchemaData = (data, showFieldType) => { }; }; +const getMultiValueField = (field): boolean => { + if(!field) { + return false; + } + + if("singleValueField" in field && field.singleValueField === false) { + return true; + } + + return false; +} + const encodeString = (str: string) => { if(str === unescape(str)){ return escape(str); @@ -325,7 +348,7 @@ const encodeString = (str: string) => { return str; } -const formatBytes = (bytes, decimals = 2) => { +const formatBytes = (bytes: number, decimals = 2) => { if (bytes === 0) return '0 Bytes'; const k = 1024; @@ -346,6 +369,21 @@ const splitStringByLastUnderscore = (str: string) => { return [beforeUnderscore, afterUnderscore]; } +export const getDisplayTableStatus = (idealStateObj, externalViewObj): DISPLAY_SEGMENT_STATUS => { + const segmentStatusArr = []; + Object.keys(idealStateObj).forEach((key) => { + segmentStatusArr.push(getDisplaySegmentStatus(idealStateObj[key], externalViewObj[key])) + }) + + if(segmentStatusArr.includes(DISPLAY_SEGMENT_STATUS.BAD)) { + return DISPLAY_SEGMENT_STATUS.BAD; + } + if(segmentStatusArr.includes(DISPLAY_SEGMENT_STATUS.UPDATING)) { + return DISPLAY_SEGMENT_STATUS.UPDATING; + } + return DISPLAY_SEGMENT_STATUS.GOOD; +} + export const getDisplaySegmentStatus = (idealState, externalView): DISPLAY_SEGMENT_STATUS => { const externalViewStatesArray = Object.values(externalView || {}); @@ -355,7 +393,7 @@ export const getDisplaySegmentStatus = (idealState, externalView): DISPLAY_SEGME } // if EV status is CONSUMING or ONLINE then segment is in Good state - if(externalViewStatesArray.every((status) => status === SEGMENT_STATUS.CONSUMING || status === SEGMENT_STATUS.ONLINE)) { + if(externalViewStatesArray.every((status) => status === SEGMENT_STATUS.CONSUMING || status === SEGMENT_STATUS.ONLINE) && isEqual(idealState, externalView)) { return DISPLAY_SEGMENT_STATUS.GOOD; } @@ -367,11 +405,48 @@ export const getDisplaySegmentStatus = (idealState, externalView): DISPLAY_SEGME // If EV is empty or EV state is OFFLINE and does not matches IS then segment is in Partial state. // PARTIAL state can also be interpreted as we're waiting for segments to converge if(externalViewStatesArray.length === 0 || externalViewStatesArray.includes(SEGMENT_STATUS.OFFLINE) && !isEqual(idealState, externalView)) { - return DISPLAY_SEGMENT_STATUS.PARTIAL; + return DISPLAY_SEGMENT_STATUS.UPDATING; } // does not match any condition -> assume PARTIAL state as we are waiting for segments to converge - return DISPLAY_SEGMENT_STATUS.PARTIAL; + return DISPLAY_SEGMENT_STATUS.UPDATING; +} + +export const getInstanceTypeFromInstanceName = (instanceName: string): InstanceType => { + if (instanceName.toLowerCase().startsWith(InstanceType.BROKER.toLowerCase())) { + return InstanceType.BROKER; + } else if (instanceName.toLowerCase().startsWith(InstanceType.CONTROLLER.toLowerCase())) { + return InstanceType.CONTROLLER; + } else if (instanceName.toLowerCase().startsWith(InstanceType.MINION.toLowerCase())) { + return InstanceType.MINION; + } else if (instanceName.toLowerCase().startsWith(InstanceType.SERVER.toLowerCase())) { + return InstanceType.SERVER; + } else { + return null; + } +} + +export const getInstanceTypeFromString = (instanceType: string): InstanceType => { + if (instanceType.toLowerCase() === InstanceType.BROKER.toLowerCase()) { + return InstanceType.BROKER; + } else if (instanceType.toLowerCase() === InstanceType.CONTROLLER.toLowerCase()) { + return InstanceType.CONTROLLER; + } else if (instanceType.toLowerCase() === InstanceType.MINION.toLowerCase()) { + return InstanceType.MINION; + } else if (instanceType.toLowerCase() === InstanceType.SERVER.toLowerCase()) { + return InstanceType.SERVER; + } else { + return null; + } +} + +const getLoadingTableData = (columns: string[]): TableData => { + return { + columns: columns, + records: [ + columns.map((_) => Loading), + ], + }; } export default { @@ -385,5 +460,8 @@ export default { syncTableSchemaData, encodeString, formatBytes, - splitStringByLastUnderscore + splitStringByLastUnderscore, + pinotTableDetailsFormat, + pinotTableDetailsFromArray, + getLoadingTableData }; diff --git a/pinot-controller/src/main/resources/app/utils/axios-config.ts b/pinot-controller/src/main/resources/app/utils/axios-config.ts index 595628c4fe4b..491c5e270b26 100644 --- a/pinot-controller/src/main/resources/app/utils/axios-config.ts +++ b/pinot-controller/src/main/resources/app/utils/axios-config.ts @@ -17,60 +17,81 @@ * under the License. */ -/* eslint-disable no-console */ - import axios from 'axios'; import { AuthWorkflow } from 'Models'; import app_state from '../app_state'; +import { AxiosError, AxiosRequestConfig } from "axios"; const isDev = process.env.NODE_ENV !== 'production'; -const handleError = (error: any) => { - if (isDev) { - console.log(error); - } - return error.response || error; -}; +// Returns axios request interceptor +export const getAxiosRequestInterceptor = ( + accessToken?: string +): ((requestConfig: AxiosRequestConfig) => AxiosRequestConfig) => { + const requestInterceptor = ( + requestConfig: AxiosRequestConfig + ): AxiosRequestConfig => { + // If access token is available, attach it to the request + // basic auth + if (app_state.authWorkflow === AuthWorkflow.BASIC && app_state.authToken) { + requestConfig.headers = { + Authorization: app_state.authToken, + }; + } + + // OIDC auth + if (accessToken) { + requestConfig.headers = { + Authorization: accessToken, + }; + } -const handleResponse = (response: any) => { - if (isDev) { - console.log(response); - } - return response; + return requestConfig; + }; + + return requestInterceptor; }; -const handleConfig = (config: any) => { - // Attach auth token for basic auth - if (app_state.authWorkflow === AuthWorkflow.BASIC && app_state.authToken) { - Object.assign(config.headers, { Authorization: app_state.authToken }); - } +// Returns axios rejected response interceptor +export const getAxiosErrorInterceptor = ( + unauthenticatedAccessFn?: () => void +): ((error: AxiosError) => void) => { + const rejectedResponseInterceptor = (error: AxiosError): any => { + if (error && error.response && (error.response.status === 401 || error.response.status === 403)) { + // Unauthenticated access + unauthenticatedAccessFn && unauthenticatedAccessFn(); + } + + return error.response || error; + }; - // Attach auth token for OIDC auth - if (app_state.authWorkflow === AuthWorkflow.OIDC && app_state.authToken) { - Object.assign(config.headers, { - Authorization: `Bearer ${app_state.authToken}`, - }); - } + return rejectedResponseInterceptor; +}; - if (isDev) { - console.log(config); - } +// Returns axios fulfilled response interceptor +export const getAxiosResponseInterceptor = (): (( + response: T +) => T | Promise) => { + const fulfilledResponseInterceptor = (response: T): T | Promise => { + // Forward the fulfilled response + return response; + }; - return config; + return fulfilledResponseInterceptor; }; export const baseApi = axios.create({ baseURL: '/' }); -baseApi.interceptors.request.use(handleConfig, handleError); -baseApi.interceptors.response.use(handleResponse, handleError); +baseApi.interceptors.request.use(getAxiosRequestInterceptor(), getAxiosErrorInterceptor()); +baseApi.interceptors.response.use(getAxiosResponseInterceptor(), getAxiosErrorInterceptor()); export const transformApi = axios.create({baseURL: '/', transformResponse: [data => data]}); -transformApi.interceptors.request.use(handleConfig, handleError); -transformApi.interceptors.response.use(handleResponse, handleError); +transformApi.interceptors.request.use(getAxiosRequestInterceptor(), getAxiosErrorInterceptor()); +transformApi.interceptors.response.use(getAxiosResponseInterceptor(), getAxiosErrorInterceptor()); // baseApi axios instance does not throw an error when API fails hence the control will never go to catch block // changing the handleError method of baseApi will cause current UI to break (as UI might have not handle error properly) // creating a new axios instance baseApiWithErrors which can be used when adding new API's // NOTE: It is an add-on utility and can be used in case you want to handle/show UI when API fails. export const baseApiWithErrors = axios.create({ baseURL: '/' }); -baseApiWithErrors.interceptors.request.use(handleConfig); -baseApiWithErrors.interceptors.response.use(handleResponse); +baseApiWithErrors.interceptors.request.use(getAxiosRequestInterceptor()); +baseApiWithErrors.interceptors.response.use(getAxiosResponseInterceptor()); diff --git a/pinot-controller/src/main/resources/package-lock.json b/pinot-controller/src/main/resources/package-lock.json index f755f3ab8551..f56f0aaeff8a 100644 --- a/pinot-controller/src/main/resources/package-lock.json +++ b/pinot-controller/src/main/resources/package-lock.json @@ -1,15 +1,14286 @@ { "name": "pinot-controller-ui", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "pinot-controller-ui", + "version": "1.0.0", + "dependencies": { + "@fortawesome/fontawesome-svg-core": "1.2.36", + "@fortawesome/free-solid-svg-icons": "5.15.4", + "@fortawesome/react-fontawesome": "0.1.18", + "@material-ui/core": "4.11.0", + "@material-ui/icons": "4.11.3", + "@material-ui/lab": "4.0.0-alpha.51", + "@sqltools/formatter": "1.2.5", + "@types/codemirror": "0.0.104", + "@types/react": "16.14.26", + "@types/react-dom": "16.9.16", + "@types/react-router": "5.1.18", + "@types/react-router-dom": "5.3.3", + "axios": "0.27.2", + "clsx": "1.1.1", + "codemirror": "5.65.5", + "cross-fetch": "3.1.5", + "export-from-json": "1.6.0", + "file": "0.2.2", + "json-bigint": "1.0.0", + "jsonlint": "1.6.3", + "jwt-decode": "^3.1.2", + "lodash": "4.17.21", + "moment": "2.29.4", + "prop-types": "15.8.1", + "re-resizable": "6.9.9", + "react": "16.13.1", + "react-codemirror2": "7.2.1", + "react-diff-viewer": "3.1.1", + "react-dom": "16.13.1", + "react-hook-form": "6.15.8", + "react-router-dom": "5.3.3", + "react-spring": "8.0.27" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "2.30.0", + "@typescript-eslint/parser": "2.30.0", + "clean-webpack-plugin": "3.0.0", + "copy-webpack-plugin": "5.1.1", + "css-loader": "3.6.0", + "eslint": "6.8.0", + "eslint-config-airbnb": "18.1.0", + "eslint-config-airbnb-typescript": "7.2.1", + "eslint-config-prettier": "6.11.0", + "eslint-config-react-app": "5.2.1", + "eslint-import-resolver-typescript": "2.0.0", + "eslint-loader": "4.0.2", + "eslint-plugin-flowtype": "4.7.0", + "eslint-plugin-import": "2.20.2", + "eslint-plugin-jsx-a11y": "6.2.3", + "eslint-plugin-prettier": "3.1.3", + "eslint-plugin-react": "7.19.0", + "eslint-plugin-react-hooks": "3.0.0", + "file-loader": "6.2.0", + "fs": "0.0.1-security", + "html-loader": "0.5.5", + "html-webpack-plugin": "4.5.2", + "npm-run-all": "4.1.5", + "prettier": "2.0.5", + "prettier-eslint": "9.0.1", + "prettier-eslint-cli": "5.0.0", + "source-map-loader": "0.2.4", + "style-loader": "1.3.0", + "system": "2.0.1", + "ts-loader": "7.0.5", + "typescript": "3.8.3", + "url-loader": "4.1.1", + "webpack": "4.29.6", + "webpack-cli": "3.3.0", + "webpack-dev-server": "3.2.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "dependencies": { + "@babel/types": "^7.16.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", + "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", + "dev": true, + "peer": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.3.tgz", + "integrity": "sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug==", + "dependencies": { + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.18.3.tgz", + "integrity": "sha512-l4ddFwrc9rnR+EJsHsh+TJ4A35YqQz/UqcjtlX2ov53hlJYG5CxtQmNZxyajwDVmCxwy++rtvGU5HazCK4W41Q==", + "dev": true, + "dependencies": { + "core-js-pure": "^3.20.2", + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.0.tgz", + "integrity": "sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emotion/cache": { + "version": "10.0.29", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz", + "integrity": "sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==", + "dependencies": { + "@emotion/sheet": "0.9.4", + "@emotion/stylis": "0.8.5", + "@emotion/utils": "0.11.3", + "@emotion/weak-memoize": "0.2.5" + } + }, + "node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" + }, + "node_modules/@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" + }, + "node_modules/@emotion/serialize": { + "version": "0.11.16", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.16.tgz", + "integrity": "sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg==", + "dependencies": { + "@emotion/hash": "0.8.0", + "@emotion/memoize": "0.7.4", + "@emotion/unitless": "0.7.5", + "@emotion/utils": "0.11.3", + "csstype": "^2.5.7" + } + }, + "node_modules/@emotion/sheet": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.4.tgz", + "integrity": "sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==" + }, + "node_modules/@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, + "node_modules/@emotion/utils": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz", + "integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", + "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" + }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "0.2.36", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.36.tgz", + "integrity": "sha512-a/7BiSgobHAgBWeN7N0w+lAhInrGxksn13uK7231n2m8EDPE3BMCl9NZLTGrj9ZXfCmC6LM0QLqXidIizVQ6yg==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "1.2.36", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.36.tgz", + "integrity": "sha512-YUcsLQKYb6DmaJjIHdDWpBIGCcyE/W+p/LMGvjQem55Mm2XWVAP5kWTMKWLv9lwpCVjpLxPyOMOyUocP1GxrtA==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "^0.2.36" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.15.4.tgz", + "integrity": "sha512-JLmQfz6tdtwxoihXLg6lT78BorrFyCf59SAwBM6qV/0zXyVeDygJVb3fk+j5Qat+Yvcxp1buLTY5iDh1ZSAQ8w==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "^0.2.36" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.1.18", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.18.tgz", + "integrity": "sha512-RwLIB4TZw0M9gvy5u+TusAA0afbwM4JQIimNH/j3ygd6aIvYPQLqXMhC9ErY26J23rDPyDZldIfPq/HpTTJ/tQ==", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.x" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true, + "peer": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@material-ui/core": { + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.11.0.tgz", + "integrity": "sha512-bYo9uIub8wGhZySHqLQ833zi4ZML+XCBE1XwJ8EuUVSpTWWG57Pm+YugQToJNFsEyiKFhPh8DPD0bgupz8n01g==", + "deprecated": "Material UI v4 doesn't receive active development since September 2021. See the guide https://mui.com/material-ui/migration/migration-v4/ to upgrade to v5.", + "dependencies": { + "@babel/runtime": "^7.4.4", + "@material-ui/styles": "^4.10.0", + "@material-ui/system": "^4.9.14", + "@material-ui/types": "^5.1.0", + "@material-ui/utils": "^4.10.2", + "@types/react-transition-group": "^4.2.0", + "clsx": "^1.0.4", + "hoist-non-react-statics": "^3.3.2", + "popper.js": "1.16.1-lts", + "prop-types": "^15.7.2", + "react-is": "^16.8.0", + "react-transition-group": "^4.4.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/material-ui" + }, + "peerDependencies": { + "@types/react": "^16.8.6", + "react": "^16.8.0", + "react-dom": "^16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@material-ui/icons": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.3.tgz", + "integrity": "sha512-IKHlyx6LDh8n19vzwH5RtHIOHl9Tu90aAAxcbWME6kp4dmvODM3UvOHJeMIDzUbd4muuJKHmlNoBN+mDY4XkBA==", + "dependencies": { + "@babel/runtime": "^7.4.4" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@material-ui/core": "^4.0.0", + "@types/react": "^16.8.6 || ^17.0.0", + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@material-ui/lab": { + "version": "4.0.0-alpha.51", + "resolved": "https://registry.npmjs.org/@material-ui/lab/-/lab-4.0.0-alpha.51.tgz", + "integrity": "sha512-X/qv/sZQGhXhKDn83L94gNahGDQj2Rd6r7/9tPpQbSn2A1LAt1+jlTiWD1HUgDXZEPqTsJMajOjWSEmTL7/q7w==", + "deprecated": "Material UI v4 doesn't receive active development since September 2021. See the guide https://mui.com/material-ui/migration/migration-v4/ to upgrade to v5.", + "dependencies": { + "@babel/runtime": "^7.4.4", + "@material-ui/utils": "^4.9.6", + "clsx": "^1.0.4", + "prop-types": "^15.7.2", + "react-is": "^16.8.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "@material-ui/core": "^4.9.10", + "@types/react": "^16.8.6", + "react": "^16.8.0", + "react-dom": "^16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@material-ui/styles": { + "version": "4.11.5", + "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.5.tgz", + "integrity": "sha512-o/41ot5JJiUsIETME9wVLAJrmIWL3j0R0Bj2kCOLbSfqEkKf0fmaPt+5vtblUh5eXr2S+J/8J3DaCb10+CzPGA==", + "deprecated": "Material UI v4 doesn't receive active development since September 2021. See the guide https://mui.com/material-ui/migration/migration-v4/ to upgrade to v5.", + "dependencies": { + "@babel/runtime": "^7.4.4", + "@emotion/hash": "^0.8.0", + "@material-ui/types": "5.1.0", + "@material-ui/utils": "^4.11.3", + "clsx": "^1.0.4", + "csstype": "^2.5.2", + "hoist-non-react-statics": "^3.3.2", + "jss": "^10.5.1", + "jss-plugin-camel-case": "^10.5.1", + "jss-plugin-default-unit": "^10.5.1", + "jss-plugin-global": "^10.5.1", + "jss-plugin-nested": "^10.5.1", + "jss-plugin-props-sort": "^10.5.1", + "jss-plugin-rule-value-function": "^10.5.1", + "jss-plugin-vendor-prefixer": "^10.5.1", + "prop-types": "^15.7.2" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/material-ui" + }, + "peerDependencies": { + "@types/react": "^16.8.6 || ^17.0.0", + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@material-ui/system": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.12.2.tgz", + "integrity": "sha512-6CSKu2MtmiJgcCGf6nBQpM8fLkuB9F55EKfbdTC80NND5wpTmKzwdhLYLH3zL4cLlK0gVaaltW7/wMuyTnN0Lw==", + "dependencies": { + "@babel/runtime": "^7.4.4", + "@material-ui/utils": "^4.11.3", + "csstype": "^2.5.2", + "prop-types": "^15.7.2" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/material-ui" + }, + "peerDependencies": { + "@types/react": "^16.8.6 || ^17.0.0", + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@material-ui/types": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz", + "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==", + "peerDependencies": { + "@types/react": "*" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@material-ui/utils": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.11.3.tgz", + "integrity": "sha512-ZuQPV4rBK/V1j2dIkSSEcH5uT6AaHuKWFfotADHsC0wVL1NLd2WkFCm4ZZbX33iO4ydl6V0GPngKm8HZQ2oujg==", + "dependencies": { + "@babel/runtime": "^7.4.4", + "prop-types": "^15.7.2", + "react-is": "^16.8.0 || ^17.0.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + } + }, + "node_modules/@sqltools/formatter": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", + "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==" + }, + "node_modules/@types/codemirror": { + "version": "0.0.104", + "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.104.tgz", + "integrity": "sha512-b417c7DZifNj1IQzOurj+HDBzHEW+C6Y8DaBai3va7p7hR/mh3YLs4wYyDapqjYiIcvJSu/kPd8XUSPaDRftbg==", + "dependencies": { + "@types/tern": "*" + } + }, + "node_modules/@types/eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", + "dev": true + }, + "node_modules/@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" + }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" + }, + "node_modules/@types/html-minifier-terser": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz", + "integrity": "sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, + "node_modules/@types/minimatch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz", + "integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.0.tgz", + "integrity": "sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==", + "dev": true + }, + "node_modules/@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.5", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", + "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" + }, + "node_modules/@types/react": { + "version": "16.14.26", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.26.tgz", + "integrity": "sha512-c/5CYyciOO4XdFcNhZW1O2woVx86k4T+DO2RorHZL7EhitkNQgSD/SgpdZJAUJa/qjVgOmTM44gHkAdZSXeQuQ==", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "16.9.16", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.16.tgz", + "integrity": "sha512-Oqc0RY4fggGA3ltEgyPLc3IV9T73IGoWjkONbsyJ3ZBn+UPPCYpU2ec0i3cEbJuEdZtkqcCF2l1zf2pBdgUGSg==", + "dependencies": { + "@types/react": "^16" + } + }, + "node_modules/@types/react-router": { + "version": "5.1.18", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.18.tgz", + "integrity": "sha512-YYknwy0D0iOwKQgz9v8nOzt2J6l4gouBmDnWqUUznltOTaon+r8US8ky8HvN0tXvc38U9m6z/t2RsVsnd1zM0g==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/react/node_modules/csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" + }, + "node_modules/@types/scheduler": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", + "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" + }, + "node_modules/@types/source-list-map": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", + "integrity": "sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA==", + "dev": true + }, + "node_modules/@types/tapable": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz", + "integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==", + "dev": true + }, + "node_modules/@types/tern": { + "version": "0.23.4", + "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.23.4.tgz", + "integrity": "sha512-JAUw1iXGO1qaWwEOzxTKJZ/5JxVeON9kvGZ/osgZaJImBnyjyn0cjovPsf6FNLmyGY8Vw9DoXZCMlfMkMwHRWg==", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/uglify-js": { + "version": "3.16.0", + "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.16.0.tgz", + "integrity": "sha512-0yeUr92L3r0GLRnBOvtYK1v2SjqMIqQDHMl7GLb+l2L8+6LSFWEEWEIgVsPdMn5ImLM8qzWT8xFPtQYpp8co0g==", + "dev": true, + "dependencies": { + "source-map": "^0.6.1" + } + }, + "node_modules/@types/uglify-js/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@types/webpack": { + "version": "4.41.32", + "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.32.tgz", + "integrity": "sha512-cb+0ioil/7oz5//7tZUSwbrSAN/NWHrQylz5cW8G0dWTcF/g+/dSdMlKVZspBYuMAN1+WnwHrkxiRrLcwd0Heg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/tapable": "^1", + "@types/uglify-js": "*", + "@types/webpack-sources": "*", + "anymatch": "^3.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/@types/webpack-sources": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@types/webpack-sources/-/webpack-sources-3.2.0.tgz", + "integrity": "sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/source-list-map": "*", + "source-map": "^0.7.3" + } + }, + "node_modules/@types/webpack-sources/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/webpack/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.30.0.tgz", + "integrity": "sha512-PGejii0qIZ9Q40RB2jIHyUpRWs1GJuHP1pkoCiaeicfwO9z7Fx03NQzupuyzAmv+q9/gFNHu7lo1ByMXe8PNyg==", + "dev": true, + "dependencies": { + "@typescript-eslint/experimental-utils": "2.30.0", + "functional-red-black-tree": "^1.0.1", + "regexpp": "^3.0.0", + "tsutils": "^3.17.1" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^2.0.0", + "eslint": "^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.30.0.tgz", + "integrity": "sha512-L3/tS9t+hAHksy8xuorhOzhdefN0ERPDWmR9CclsIGOUqGKy6tqc/P+SoXeJRye5gazkuPO0cK9MQRnolykzkA==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "2.30.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-2.30.0.tgz", + "integrity": "sha512-9kDOxzp0K85UnpmPJqUzdWaCNorYYgk1yZmf4IKzpeTlSAclnFsrLjfwD9mQExctLoLoGAUXq1co+fbr+3HeFw==", + "dev": true, + "dependencies": { + "@types/eslint-visitor-keys": "^1.0.0", + "@typescript-eslint/experimental-utils": "2.30.0", + "@typescript-eslint/typescript-estree": "2.30.0", + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.30.0.tgz", + "integrity": "sha512-nI5WOechrA0qAhnr+DzqwmqHsx7Ulr/+0H7bWCcClDhhWkSyZR5BmTvnBEyONwJCTWHfc5PAQExX24VD26IAVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "eslint-visitor-keys": "^1.1.0", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^6.3.0", + "tsutils": "^3.17.1" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.8.5.tgz", + "integrity": "sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-module-context": "1.8.5", + "@webassemblyjs/helper-wasm-bytecode": "1.8.5", + "@webassemblyjs/wast-parser": "1.8.5" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz", + "integrity": "sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz", + "integrity": "sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz", + "integrity": "sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-code-frame": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz", + "integrity": "sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/wast-printer": "1.8.5" + } + }, + "node_modules/@webassemblyjs/helper-fsm": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz", + "integrity": "sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-module-context": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz", + "integrity": "sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.8.5", + "mamacro": "^0.0.3" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz", + "integrity": "sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz", + "integrity": "sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA==", + "dev": true, + "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" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz", + "integrity": "sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.8.5.tgz", + "integrity": "sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.8.5.tgz", + "integrity": "sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz", + "integrity": "sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q==", + "dev": true, + "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" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz", + "integrity": "sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg==", + "dev": true, + "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" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz", + "integrity": "sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/helper-buffer": "1.8.5", + "@webassemblyjs/wasm-gen": "1.8.5", + "@webassemblyjs/wasm-parser": "1.8.5" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz", + "integrity": "sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw==", + "dev": true, + "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" + } + }, + "node_modules/@webassemblyjs/wast-parser": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz", + "integrity": "sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg==", + "dev": true, + "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" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz", + "integrity": "sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.8.5", + "@webassemblyjs/wast-parser": "1.8.5", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-dynamic-import": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz", + "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==", + "deprecated": "This is probably built in to whatever tool you're using. If you still need it... idk", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "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" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-errors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "dev": true, + "peerDependencies": { + "ajv": ">=5.0.0" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-colors": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz", + "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz", + "integrity": "sha1-yxAt8cVvUSPquLZ817mAJ6AnkXg=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/aria-query": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", + "integrity": "sha1-ZbP8wcoRVajJrmTW7uKX8V1RM8w=", + "dev": true, + "dependencies": { + "ast-types-flow": "0.0.7", + "commander": "^2.11.0" + } + }, + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-flatten": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", + "integrity": "sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ==", + "dev": true + }, + "node_modules/array-includes": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.5.tgz", + "integrity": "sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5", + "get-intrinsic": "^1.1.1", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", + "integrity": "sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.reduce": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz", + "integrity": "sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.2", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/assert": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", + "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", + "dev": true, + "dependencies": { + "object-assign": "^4.1.1", + "util": "0.10.3" + } + }, + "node_modules/assert/node_modules/inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", + "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", + "dev": true + }, + "node_modules/assert/node_modules/util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", + "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", + "dev": true, + "dependencies": { + "inherits": "2.0.1" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ast-types": { + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.9.6.tgz", + "integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", + "dev": true + }, + "node_modules/astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/async-each": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", + "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", + "dev": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true, + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "dependencies": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, + "node_modules/axobject-query": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", + "dev": true + }, + "node_modules/babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "deprecated": "babel-eslint is now @babel/eslint-parser. This package will no longer receive updates.", + "dev": true, + "peer": true, + "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" + }, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "eslint": ">= 4.12.1" + } + }, + "node_modules/babel-plugin-emotion": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.2.2.tgz", + "integrity": "sha512-SMSkGoqTbTyUTDeuVuPIWifPdUGkTk1Kf9BWRiXIOIcuyMfsdp2EjeiiFvOzX8NOBvEh/ypKYvUh2rkgAJMCLA==", + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@emotion/hash": "0.8.0", + "@emotion/memoize": "0.7.4", + "@emotion/serialize": "^0.11.16", + "babel-plugin-macros": "^2.0.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^1.0.5", + "find-root": "^1.1.0", + "source-map": "^0.5.7" + } + }, + "node_modules/babel-plugin-macros": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", + "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", + "dependencies": { + "@babel/runtime": "^7.7.2", + "cosmiconfig": "^6.0.0", + "resolve": "^1.12.0" + } + }, + "node_modules/babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", + "dev": true + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/bignumber.js": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.2.tgz", + "integrity": "sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true + }, + "node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "dev": true + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/bonjour": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", + "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", + "dev": true, + "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" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", + "dev": true + }, + "node_modules/boolify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/boolify/-/boolify-1.0.1.tgz", + "integrity": "sha1-tcCeF8rNET0Rt7s+04TMASmU2Gs=", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", + "dev": true + }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "dev": true, + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "dev": true, + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.0.tgz", + "integrity": "sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog==", + "dev": true, + "dependencies": { + "bn.js": "^5.0.0", + "randombytes": "^2.0.1" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", + "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "dev": true, + "dependencies": { + "bn.js": "^5.1.1", + "browserify-rsa": "^4.0.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.5.3", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.5", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + } + }, + "node_modules/browserify-sign/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/browserify-sign/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "dependencies": { + "pako": "~1.0.5" + } + }, + "node_modules/buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "dev": true, + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/buffer-indexof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz", + "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==", + "dev": true + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", + "dev": true + }, + "node_modules/buffer/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/builtin-status-codes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", + "dev": true + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "12.0.4", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", + "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", + "dev": true, + "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" + } + }, + "node_modules/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "dev": true, + "dependencies": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-keys": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz", + "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz", + "integrity": "sha1-UZmj3c0MHv4jvAjBsCewYXbgxk8=", + "dependencies": { + "ansi-styles": "~1.0.0", + "has-color": "~0.1.0", + "strip-ansi": "~0.1.0" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "optional": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "optional": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/cipher-base": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/classnames": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz", + "integrity": "sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==" + }, + "node_modules/clean-css": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz", + "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==", + "dev": true, + "dependencies": { + "source-map": "~0.6.0" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/clean-css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clean-webpack-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clean-webpack-plugin/-/clean-webpack-plugin-3.0.0.tgz", + "integrity": "sha512-MciirUH5r+cYLGCOL5JX/ZLzOZbVr1ot3Fw+KcvbhUb6PM+yycqd9ZhIlcigQ5gl+XhppNmw3bEFuaaMNyLj3A==", + "dev": true, + "dependencies": { + "@types/webpack": "^4.4.31", + "del": "^4.1.1" + }, + "engines": { + "node": ">=8.9.0" + }, + "peerDependencies": { + "webpack": "*" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clsx": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz", + "integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/codemirror": { + "version": "5.65.5", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.5.tgz", + "integrity": "sha512-HNyhvGLnYz5c+kIsB9QKVitiZUevha3ovbIYaQiGzKo7ECSL/elWD9RXt3JgNr0NdnyqE9/Rc/7uLfkJQL638w==" + }, + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "dependencies": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, + "node_modules/connect-history-api-fallback": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz", + "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/console-browserify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true + }, + "node_modules/constants-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", + "dev": true + }, + "node_modules/contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", + "dev": true + }, + "node_modules/copy-concurrently": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", + "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", + "dev": true, + "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" + } + }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-5.1.1.tgz", + "integrity": "sha512-P15M5ZC8dyCjQHWwd4Ia/dm0SgVvZJMYeykVIVYXbGyqO4dWB5oyPHp9i7wjwo5LhtlhKbiBCdS2NvM07Wlybg==", + "dev": true, + "dependencies": { + "cacache": "^12.0.3", + "find-cache-dir": "^2.1.0", + "glob-parent": "^3.1.0", + "globby": "^7.1.1", + "is-glob": "^4.0.1", + "loader-utils": "^1.2.3", + "minimatch": "^3.0.4", + "normalize-path": "^3.0.0", + "p-limit": "^2.2.1", + "schema-utils": "^1.0.0", + "serialize-javascript": "^2.1.2", + "webpack-log": "^2.0.0" + }, + "engines": { + "node": ">= 6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", + "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", + "dev": true, + "dependencies": { + "array-union": "^1.0.1", + "dir-glob": "^2.0.0", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/copy-webpack-plugin/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/core-js": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.23.2.tgz", + "integrity": "sha512-ELJOWxNrJfOH/WK4VJ3Qd+fOqZuOuDNDJz0xG6Bt4mGg2eO/UT9CljCrbqDGovjLKUrGajEEBcoTOc0w+yBYeQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-js-pure": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.23.2.tgz", + "integrity": "sha512-t6u7H4Ff/yZNk+zqTr74UjCcZ3k8ApBryeLLV4rYQd9aF3gqmjjGjjR44ENfeBMH8VVvSynIjAJ0mUuFhzQtrA==", + "deprecated": "core-js-pure@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js-pure.", + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/create-emotion": { + "version": "10.0.27", + "resolved": "https://registry.npmjs.org/create-emotion/-/create-emotion-10.0.27.tgz", + "integrity": "sha512-fIK73w82HPPn/RsAij7+Zt8eCE8SptcJ3WoRMfxMtjteYxud8GDTKKld7MYwAX2TVhrw29uR1N/bVGxeStHILg==", + "dependencies": { + "@emotion/cache": "^10.0.27", + "@emotion/serialize": "^0.11.15", + "@emotion/sheet": "0.9.4", + "@emotion/utils": "0.11.3" + } + }, + "node_modules/create-hash": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "dev": true, + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/cross-fetch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", + "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "dependencies": { + "node-fetch": "2.6.7" + } + }, + "node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" + } + }, + "node_modules/cross-spawn/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "dev": true, + "dependencies": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + }, + "engines": { + "node": "*" + } + }, + "node_modules/css-loader": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-3.6.0.tgz", + "integrity": "sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==", + "dev": true, + "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.32", + "postcss-modules-extract-imports": "^2.0.0", + "postcss-modules-local-by-default": "^3.0.2", + "postcss-modules-scope": "^2.2.0", + "postcss-modules-values": "^3.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^2.7.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/css-loader/node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-vendor": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.8.tgz", + "integrity": "sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==", + "dependencies": { + "@babel/runtime": "^7.8.3", + "is-in-browser": "^1.0.2" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "2.6.20", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.20.tgz", + "integrity": "sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA==" + }, + "node_modules/cyclist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", + "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", + "dev": true + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/deep-equal": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", + "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", + "dev": true, + "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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/default-gateway": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz", + "integrity": "sha512-h6sMrVB1VMWVrW13mSc6ia/DwYYw5MN6+exNu1OaJeFac5aSAvwM7lZ0NVfTABuSkQelr4h5oebg3KB1XPdjgA==", + "dev": true, + "dependencies": { + "execa": "^1.0.0", + "ip-regex": "^2.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/del": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-4.1.1.tgz", + "integrity": "sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ==", + "dev": true, + "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" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/des.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "dev": true, + "dependencies": { + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/dir-glob/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/dir-glob/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/dns-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", + "dev": true + }, + "node_modules/dns-packet": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.4.tgz", + "integrity": "sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==", + "dev": true, + "dependencies": { + "ip": "^1.1.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/dns-txt": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", + "dev": true, + "dependencies": { + "buffer-indexof": "^1.0.0" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-converter": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", + "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", + "dev": true, + "dependencies": { + "utila": "~0.4" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dom-helpers/node_modules/csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domain-browser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", + "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", + "dev": true, + "engines": { + "node": ">=0.4", + "npm": ">=1.2" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dot-case/node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/dot-case/node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/dot-case/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + }, + "node_modules/duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", + "dev": true + }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dev": true, + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/elliptic/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/emotion": { + "version": "10.0.27", + "resolved": "https://registry.npmjs.org/emotion/-/emotion-10.0.27.tgz", + "integrity": "sha512-2xdDzdWWzue8R8lu4G76uWX5WhyQuzATon9LmNeCy/2BHVC6dsEpfhN1a0qhELgtDVdjyEA6J8Y/VlI5ZnaH0g==", + "dependencies": { + "babel-plugin-emotion": "^10.0.27", + "create-emotion": "^10.0.27" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", + "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", + "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.1", + "get-symbol-description": "^1.0.0", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "is-callable": "^1.2.4", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "regexp.prototype.flags": "^1.4.3", + "string.prototype.trimend": "^1.0.5", + "string.prototype.trimstart": "^1.0.5", + "unbox-primitive": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", + "dev": true + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es6-templates": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/es6-templates/-/es6-templates-0.2.3.tgz", + "integrity": "sha1-XLmsn7He1usSOTQrgdeSu7QHjuQ=", + "dev": true, + "dependencies": { + "recast": "~0.11.12", + "through": "~2.3.6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "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", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.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", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.3", + "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", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-airbnb": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-18.1.0.tgz", + "integrity": "sha512-kZFuQC/MPnH7KJp6v95xsLBf63G/w7YqdPfQ0MUanxQ7zcKUNG8j+sSY860g3NwCBOa62apw16J6pRN+AOgXzw==", + "dev": true, + "dependencies": { + "eslint-config-airbnb-base": "^14.1.0", + "object.assign": "^4.1.0", + "object.entries": "^1.1.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "eslint": "^5.16.0 || ^6.8.0", + "eslint-plugin-import": "^2.20.1", + "eslint-plugin-jsx-a11y": "^6.2.3", + "eslint-plugin-react": "^7.19.0", + "eslint-plugin-react-hooks": "^2.5.0 || ^1.7.0" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.1.tgz", + "integrity": "sha512-GOrQyDtVEc1Xy20U7vsB2yAoB4nBlfH5HZJeatRXHleO+OS5Ot+MWij4Dpltw4/DyIkqUfqz1epfhVR5XWWQPA==", + "dev": true, + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.2" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "eslint": "^5.16.0 || ^6.8.0 || ^7.2.0", + "eslint-plugin-import": "^2.22.1" + } + }, + "node_modules/eslint-config-airbnb-typescript": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-7.2.1.tgz", + "integrity": "sha512-D3elVKUbdsCfkOVstSyWuiu+KGCVTrYxJPoenPIqZtL6Li/R4xBeVTXjZIui8B8D17bDN3Pz5dSr7jRLY5HqIg==", + "dev": true, + "dependencies": { + "@typescript-eslint/parser": "^2.24.0", + "eslint-config-airbnb": "^18.1.0", + "eslint-config-airbnb-base": "^14.1.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^2.24.0" + } + }, + "node_modules/eslint-config-prettier": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz", + "integrity": "sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA==", + "dev": true, + "dependencies": { + "get-stdin": "^6.0.0" + }, + "bin": { + "eslint-config-prettier-check": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=3.14.1" + } + }, + "node_modules/eslint-config-react-app": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-5.2.1.tgz", + "integrity": "sha512-pGIZ8t0mFLcV+6ZirRgYK6RVqUIKRIi9MmgzUEmrIknsn3AdO0I32asO86dJgloHq+9ZPl8UIg8mYrvgP5u2wQ==", + "dev": true, + "dependencies": { + "confusing-browser-globals": "^1.0.9" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "2.x", + "@typescript-eslint/parser": "2.x", + "babel-eslint": "10.x", + "eslint": "6.x", + "eslint-plugin-flowtype": "3.x || 4.x", + "eslint-plugin-import": "2.x", + "eslint-plugin-jsx-a11y": "6.x", + "eslint-plugin-react": "7.x", + "eslint-plugin-react-hooks": "1.x || 2.x" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", + "integrity": "sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "resolve": "^1.20.0" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.0.0.tgz", + "integrity": "sha512-bT5Frpl8UWoHBtY25vKUOMoVIMlJQOMefHLyQ4Tz3MQpIZ2N6yYKEEIHMo38bszBNUuMBW6M3+5JNYxeiGFH4w==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "is-glob": "^4.0.1", + "resolve": "^1.12.0", + "tiny-glob": "^0.2.6", + "tsconfig-paths": "^3.9.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" + } + }, + "node_modules/eslint-loader": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/eslint-loader/-/eslint-loader-4.0.2.tgz", + "integrity": "sha512-EDpXor6lsjtTzZpLUn7KmXs02+nIjGcgees9BYjNkWra3jVq5vVa8IoCKgzT2M7dNNeoMBtaSG83Bd40N3poLw==", + "deprecated": "This loader has been deprecated. Please use eslint-webpack-plugin", + "dev": true, + "dependencies": { + "find-cache-dir": "^3.3.1", + "fs-extra": "^8.1.0", + "loader-utils": "^2.0.0", + "object-hash": "^2.0.3", + "schema-utils": "^2.6.5" + }, + "engines": { + "node": ">= 10.13.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0", + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/eslint-loader/node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/eslint-loader/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint-loader/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint-loader/node_modules/loader-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/eslint-loader/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint-loader/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-loader/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint-loader/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint-loader/node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint-loader/node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz", + "integrity": "sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "find-up": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-module-utils/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-flowtype": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-flowtype/-/eslint-plugin-flowtype-4.7.0.tgz", + "integrity": "sha512-M+hxhSCk5QBEValO5/UqrS4UunT+MgplIJK5wA1sCtXjzBcZkpTGRwxmLHhGpbHcrmQecgt6ZL/KDdXWqGB7VA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": ">=6.1.0" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.20.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz", + "integrity": "sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg==", + "dev": true, + "dependencies": { + "array-includes": "^3.0.3", + "array.prototype.flat": "^1.2.1", + "contains-path": "^0.1.0", + "debug": "^2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.2", + "eslint-module-utils": "^2.4.1", + "has": "^1.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.0", + "read-pkg-up": "^2.0.0", + "resolve": "^1.12.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "2.x - 6.x" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "dependencies": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/eslint-plugin-import/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz", + "integrity": "sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.4.5", + "aria-query": "^3.0.0", + "array-includes": "^3.0.3", + "ast-types-flow": "^0.0.7", + "axobject-query": "^2.0.2", + "damerau-levenshtein": "^1.0.4", + "emoji-regex": "^7.0.2", + "has": "^1.0.3", + "jsx-ast-utils": "^2.2.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/eslint-plugin-prettier": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.3.tgz", + "integrity": "sha512-+HG5jmu/dN3ZV3T6eCD7a4BlAySdN7mLIbJYo0z1cFQuI+r2DiTJEFeF68ots93PsnrMxbzIZ2S/ieX+mkrBeQ==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "eslint": ">= 5.0.0", + "prettier": ">= 1.13.0" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz", + "integrity": "sha512-SPT8j72CGuAP+JFbT0sJHOB80TX/pu44gQ4vXH/cq+hQTiY2PuZ6IHkqXJV6x1b28GDdo1lbInjKUrrdUf0LOQ==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.1", + "doctrine": "^2.1.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", + "prop-types": "^15.7.2", + "resolve": "^1.15.1", + "semver": "^6.3.0", + "string.prototype.matchall": "^4.0.2", + "xregexp": "^4.3.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-3.0.0.tgz", + "integrity": "sha512-EjxTHxjLKIBWFgDJdhKKzLh5q+vjTFrqNZX36uIxWS4OfyXe5DawqPj3U5qeJ1ngLwatjzQnmR0Lz0J0YH3kxw==", + "dev": true, + "engines": { + "node": ">=7" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint/node_modules/eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true, + "engines": { + "node": ">=6.5.0" + } + }, + "node_modules/eslint/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/espree": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", + "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", + "dev": true, + "dependencies": { + "acorn": "^7.1.1", + "acorn-jsx": "^5.2.0", + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/espree/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/eventsource": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-1.1.2.tgz", + "integrity": "sha512-xAH3zWhgO2/3KIniEKYPr8plNSzlGINOUqYj0m0u7AB81iRw8b/3E73W6AuU+6klLbaSFmZnaETQ2lXPfAydrA==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/evp_bytestokey": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", + "dev": true, + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "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" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "dev": true, + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/export-from-json": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/export-from-json/-/export-from-json-1.6.0.tgz", + "integrity": "sha512-DLHwOGYeGnATM6tOMOWgs9dbzCjO+DwO3YGaha2R6kmLCE5iL8dz5sOywWeJs4P1rhxpdaVILKhCB4mUrTbbGg==" + }, + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/express/node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "dev": true + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend-shallow/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "node_modules/fastparse": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", + "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", + "dev": true + }, + "node_modules/faye-websocket": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", + "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/figgy-pudding": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", + "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", + "dev": true + }, + "node_modules/figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/file": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/file/-/file-0.2.2.tgz", + "integrity": "sha1-w9/Y+M81Na5FXCtCPC5SY112tNM=" + }, + "node_modules/file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "dependencies": { + "flat-cache": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/file-loader": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", + "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/file-loader/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/file-loader/node_modules/loader-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/file-loader/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "dev": true, + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/findup-sync/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/findup-sync/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "dependencies": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/flat-cache/node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, + "node_modules/flush-write-stream": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", + "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "readable-stream": "^2.3.6" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "node_modules/fs": { + "version": "0.0.1-security", + "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", + "integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ=", + "dev": true + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs-write-stream-atomic": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", + "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "dependencies": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + } + }, + "node_modules/glob-parent/node_modules/is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/globals": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", + "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", + "dev": true, + "dependencies": { + "type-fest": "^0.8.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalyzer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", + "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==", + "dev": true + }, + "node_modules/globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", + "dev": true, + "dependencies": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/globby/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-color": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz", + "integrity": "sha1-ZxRKUmDDT8PMpnfQQdr1L+e3iy8=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-values/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hash-base": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", + "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^3.6.0", + "safe-buffer": "^5.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/hash-base/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/hash-base/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/history": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", + "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" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "dev": true, + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/html-entities": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.4.0.tgz", + "integrity": "sha512-8nxjcBcd8wovbeKx7h3wTji4e6+rhaVuPNpMqwWgnHh+N9ToqsCs6XztWRBPQ+UtzsoMAdKZtUENoVzU/EMtZA==", + "dev": true + }, + "node_modules/html-loader": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/html-loader/-/html-loader-0.5.5.tgz", + "integrity": "sha512-7hIW7YinOYUpo//kSYcPB6dCKoceKLmOwjEMmhIobHuWGDVl0Nwe4l68mdG/Ru0wcUxQjVMEoZpkalZ/SE7zog==", + "dev": true, + "dependencies": { + "es6-templates": "^0.2.3", + "fastparse": "^1.1.1", + "html-minifier": "^3.5.8", + "loader-utils": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "node_modules/html-minifier": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz", + "integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==", + "dev": true, + "dependencies": { + "camel-case": "3.0.x", + "clean-css": "4.2.x", + "commander": "2.17.x", + "he": "1.2.x", + "param-case": "2.1.x", + "relateurl": "0.2.x", + "uglify-js": "3.4.x" + }, + "bin": { + "html-minifier": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/html-minifier-terser": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz", + "integrity": "sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg==", + "dev": true, + "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" + }, + "bin": { + "html-minifier-terser": "cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/html-minifier-terser/node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dev": true, + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/html-minifier-terser/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/html-minifier-terser/node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/html-minifier-terser/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + }, + "node_modules/html-minifier/node_modules/commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, + "node_modules/html-webpack-plugin": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.5.2.tgz", + "integrity": "sha512-q5oYdzjKUIPQVjOosjgvCHQOv9Ett9CYYHlgvJeXG0qQvdSojnBq4vAdQBwn1+yGveAwHCoe/rMR86ozX3+c2A==", + "dev": true, + "dependencies": { + "@types/html-minifier-terser": "^5.0.0", + "@types/tapable": "^1.0.5", + "@types/webpack": "^4.41.8", + "html-minifier-terser": "^5.0.1", + "loader-utils": "^1.2.3", + "lodash": "^4.17.20", + "pretty-error": "^2.1.1", + "tapable": "^1.1.3", + "util.promisify": "1.0.0" + }, + "engines": { + "node": ">=6.9" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/htmlparser2": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", + "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.0.0", + "domutils": "^2.5.2", + "entities": "^2.0.0" + } + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.7.tgz", + "integrity": "sha512-8gQM8ZcewlONQLnik2AKzS13euQhaZcu4rK5QBSYOszW0T1upLW9VA2MdWvTvMmRo42HjXp7igFmdROoBCCrfg==", + "dev": true + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-middleware": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.2.tgz", + "integrity": "sha512-aYk1rTKqLTus23X3L96LGNCGNgWpG4cG0XoZIT1GUPhhulEHX/QalnO6Vbo+WmKWi4AL2IidjuC0wZtbpg0yhQ==", + "dev": true, + "dependencies": { + "http-proxy": "^1.18.1", + "is-glob": "^4.0.0", + "lodash": "^4.17.11", + "micromatch": "^3.1.10" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-proxy-middleware/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "node_modules/hyphenate-style-name": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz", + "integrity": "sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==" + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-4.1.1.tgz", + "integrity": "sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA==", + "dev": true, + "dependencies": { + "postcss": "^7.0.14" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/iferr": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", + "dev": true + }, + "node_modules/ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "dependencies": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "dev": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, + "node_modules/inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "dev": true, + "dependencies": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/inquirer/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/inquirer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/inquirer/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/inquirer/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/inquirer/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/inquirer/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/internal-ip": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", + "integrity": "sha512-S1zBo1D6zcsyuC6PMmY5+55YMILQ9av8lotMx447Bq6SAgo/sDK6y6uUKmuYhW7eacnIhFfsPmCNYdDzsnnDCg==", + "dev": true, + "dependencies": { + "default-gateway": "^4.2.0", + "ipaddr.js": "^1.9.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ip": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", + "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", + "dev": true + }, + "node_modules/ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "optional": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "node_modules/is-callable": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", + "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-in-browser": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", + "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-path-in-cwd": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz", + "integrity": "sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ==", + "dev": true, + "dependencies": { + "is-path-inside": "^2.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-path-inside": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz", + "integrity": "sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg==", + "dev": true, + "dependencies": { + "path-is-inside": "^1.0.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "peer": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, + "node_modules/json3": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", + "integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonlint": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/jsonlint/-/jsonlint-1.6.3.tgz", + "integrity": "sha512-jMVTMzP+7gU/IyC6hvKyWpUU8tmTkK5b3BPNuMI9U8Sit+YAWLlZwB6Y6YrdCxfg2kNz05p3XY3Bmm4m26Nv3A==", + "dependencies": { + "JSV": "^4.0.x", + "nomnom": "^1.5.x" + }, + "bin": { + "jsonlint": "lib/cli.js" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/jss": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss/-/jss-10.9.0.tgz", + "integrity": "sha512-YpzpreB6kUunQBbrlArlsMpXYyndt9JATbt95tajx0t4MTJJcCJdd4hdNpHmOIDiUJrF/oX5wtVFrS3uofWfGw==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "csstype": "^3.0.2", + "is-in-browser": "^1.1.3", + "tiny-warning": "^1.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/jss" + } + }, + "node_modules/jss-plugin-camel-case": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.9.0.tgz", + "integrity": "sha512-UH6uPpnDk413/r/2Olmw4+y54yEF2lRIV8XIZyuYpgPYTITLlPOsq6XB9qeqv+75SQSg3KLocq5jUBXW8qWWww==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "hyphenate-style-name": "^1.0.3", + "jss": "10.9.0" + } + }, + "node_modules/jss-plugin-default-unit": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.9.0.tgz", + "integrity": "sha512-7Ju4Q9wJ/MZPsxfu4T84mzdn7pLHWeqoGd/D8O3eDNNJ93Xc8PxnLmV8s8ZPNRYkLdxZqKtm1nPQ0BM4JRlq2w==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.9.0" + } + }, + "node_modules/jss-plugin-global": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.9.0.tgz", + "integrity": "sha512-4G8PHNJ0x6nwAFsEzcuVDiBlyMsj2y3VjmFAx/uHk/R/gzJV+yRHICjT4MKGGu1cJq2hfowFWCyrr/Gg37FbgQ==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.9.0" + } + }, + "node_modules/jss-plugin-nested": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.9.0.tgz", + "integrity": "sha512-2UJnDrfCZpMYcpPYR16oZB7VAC6b/1QLsRiAutOt7wJaaqwCBvNsosLEu/fUyKNQNGdvg2PPJFDO5AX7dwxtoA==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.9.0", + "tiny-warning": "^1.0.2" + } + }, + "node_modules/jss-plugin-props-sort": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.9.0.tgz", + "integrity": "sha512-7A76HI8bzwqrsMOJTWKx/uD5v+U8piLnp5bvru7g/3ZEQOu1+PjHvv7bFdNO3DwNPC9oM0a//KwIJsIcDCjDzw==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.9.0" + } + }, + "node_modules/jss-plugin-rule-value-function": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.9.0.tgz", + "integrity": "sha512-IHJv6YrEf8pRzkY207cPmdbBstBaE+z8pazhPShfz0tZSDtRdQua5jjg6NMz3IbTasVx9FdnmptxPqSWL5tyJg==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "jss": "10.9.0", + "tiny-warning": "^1.0.2" + } + }, + "node_modules/jss-plugin-vendor-prefixer": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.9.0.tgz", + "integrity": "sha512-MbvsaXP7iiVdYVSEoi+blrW+AYnTDvHTW6I6zqi7JcwXdc6I9Kbm234nEblayhF38EftoenbM+5218pidmC5gA==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "css-vendor": "^2.0.8", + "jss": "10.9.0" + } + }, + "node_modules/jss/node_modules/csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==" + }, + "node_modules/JSV": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz", + "integrity": "sha1-0Hf2glVx+CEy+d/67Vh7QCn+/1c=", + "engines": { + "node": "*" + } + }, + "node_modules/jsx-ast-utils": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz", + "integrity": "sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.1", + "object.assign": "^4.1.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/jwt-decode": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", + "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + }, + "node_modules/killable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", + "integrity": "sha512-LzqtLKlUwirEUyl/nicirVmNiPvYs7l5n8wOPP7fyJVpUPkvCnW/vuiXGpylGUlnPDnB7311rARzAt3Mhswpjg==", + "dev": true + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "dependencies": { + "invert-kv": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/load-json-file/node_modules/parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "dev": true, + "dependencies": { + "error-ex": "^1.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/load-json-file/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loader-runner": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "dev": true, + "engines": { + "node": ">=4.3.0 <5.0.0 || >=5.10" + } + }, + "node_modules/loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.unescape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz", + "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=", + "dev": true + }, + "node_modules/loglevel": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", + "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "node_modules/loglevel-colored-level-prefix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz", + "integrity": "sha1-akAhj9x64V/HbD0PPmdsRlOIYD4=", + "dev": true, + "dependencies": { + "chalk": "^1.1.3", + "loglevel": "^1.4.1" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/loglevel-colored-level-prefix/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", + "dev": true + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/make-plural": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-4.3.0.tgz", + "integrity": "sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA==", + "dev": true, + "bin": { + "make-plural": "bin/make-plural" + }, + "optionalDependencies": { + "minimist": "^1.2.0" + } + }, + "node_modules/mamacro": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/mamacro/-/mamacro-0.0.3.tgz", + "integrity": "sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA==", + "dev": true + }, + "node_modules/map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "dependencies": { + "p-defer": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-obj": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz", + "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "dependencies": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, + "node_modules/memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "dependencies": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + }, + "engines": { + "node": ">=4.3.0 <5.0.0 || >=5.10" + } + }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", + "dev": true + }, + "node_modules/messageformat": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/messageformat/-/messageformat-2.3.0.tgz", + "integrity": "sha512-uTzvsv0lTeQxYI2y1NPa1lItL5VRI8Gb93Y2K2ue5gBPyrbJxfDi/EYWxh2PKv5yO42AJeeqblS9MJSh/IEk4w==", + "deprecated": "Package renamed as '@messageformat/core', see messageformat.github.io for more details. 'messageformat@4' will eventually provide a polyfill for Intl.MessageFormat, once it's been defined by Unicode & ECMA.", + "dev": true, + "dependencies": { + "make-plural": "^4.3.0", + "messageformat-formatters": "^2.0.1", + "messageformat-parser": "^4.1.2" + } + }, + "node_modules/messageformat-formatters": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/messageformat-formatters/-/messageformat-formatters-2.0.1.tgz", + "integrity": "sha512-E/lQRXhtHwGuiQjI7qxkLp8AHbMD5r2217XNe/SREbBlSawe0lOqsFb7rflZJmlQFSULNLIqlcjjsCPlB3m3Mg==", + "dev": true + }, + "node_modules/messageformat-parser": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-4.1.3.tgz", + "integrity": "sha512-2fU3XDCanRqeOCkn7R5zW5VQHWf+T3hH65SzuqRvjatBK7r4uyFa5mEX+k6F9Bd04LVM5G4/BHBTUJsOdW7uyg==", + "dev": true + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "dev": true, + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mini-create-react-context": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/mini-create-react-context/-/mini-create-react-context-0.4.1.tgz", + "integrity": "sha512-YWCYEmd5CQeHGSAKrYvXgmzzkrvssZcuuQDDeqkT+PziKGMgE+0MCCtcKbROzocGBG1meBLl2FotlRwf4gAzbQ==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dependencies": { + "@babel/runtime": "^7.12.1", + "tiny-warning": "^1.0.3" + }, + "peerDependencies": { + "prop-types": "^15.0.0", + "react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", + "dev": true + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", + "dev": true + }, + "node_modules/mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "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" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mixin-deep/node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, + "node_modules/move-concurrently": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", + "dev": true, + "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" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/multicast-dns": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz", + "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==", + "dev": true, + "dependencies": { + "dns-packet": "^1.3.1", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/multicast-dns-service-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", + "dev": true + }, + "node_modules/mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, + "node_modules/nan": { + "version": "2.16.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz", + "integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==", + "dev": true, + "optional": true + }, + "node_modules/nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node_modules/no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", + "dev": true, + "dependencies": { + "lower-case": "^1.1.1" + } + }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", + "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==", + "dev": true, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/node-libs-browser": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", + "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", + "dev": true, + "dependencies": { + "assert": "^1.1.1", + "browserify-zlib": "^0.2.0", + "buffer": "^4.3.0", + "console-browserify": "^1.1.0", + "constants-browserify": "^1.0.0", + "crypto-browserify": "^3.11.0", + "domain-browser": "^1.1.1", + "events": "^3.0.0", + "https-browserify": "^1.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "0.0.1", + "process": "^0.11.10", + "punycode": "^1.2.4", + "querystring-es3": "^0.2.0", + "readable-stream": "^2.3.3", + "stream-browserify": "^2.0.1", + "stream-http": "^2.7.2", + "string_decoder": "^1.0.0", + "timers-browserify": "^2.0.4", + "tty-browserify": "0.0.0", + "url": "^0.11.0", + "util": "^0.11.0", + "vm-browserify": "^1.0.1" + } + }, + "node_modules/node-libs-browser/node_modules/punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "node_modules/nomnom": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.8.1.tgz", + "integrity": "sha1-IVH3Ikcrp55Qp2/BJbuMjy5Nwqc=", + "deprecated": "Package no longer supported. Contact support@npmjs.com for more info.", + "dependencies": { + "chalk": "~0.4.0", + "underscore": "~1.6.0" + } + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm-run-all/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-all/node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", + "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", + "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", + "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz", + "integrity": "sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ==", + "dev": true, + "dependencies": { + "array.prototype.reduce": "^1.0.4", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.values": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz", + "integrity": "sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/opn": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", + "integrity": "sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==", + "dev": true, + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "node_modules/os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "dependencies": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-map": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, + "node_modules/parallel-transform": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", + "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", + "dev": true, + "dependencies": { + "cyclist": "^1.0.1", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "node_modules/param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "dev": true, + "dependencies": { + "no-case": "^2.2.0" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-asn1": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", + "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", + "dev": true, + "dependencies": { + "asn1.js": "^5.2.0", + "browserify-aes": "^1.0.0", + "evp_bytestokey": "^1.0.0", + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/pascal-case/node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/pascal-case/node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/pascal-case/node_modules/tslib": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", + "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", + "dev": true + }, + "node_modules/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-browserify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", + "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", + "dev": true + }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/pbkdf2": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", + "integrity": "sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==", + "dev": true, + "dependencies": { + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4", + "ripemd160": "^2.0.1", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", + "dev": true, + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/popper.js": { + "version": "1.16.1-lts", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz", + "integrity": "sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA==" + }, + "node_modules/portfinder": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", + "dev": true, + "dependencies": { + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.5" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/portfinder/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz", + "integrity": "sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ==", + "dev": true, + "dependencies": { + "postcss": "^7.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz", + "integrity": "sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw==", + "dev": true, + "dependencies": { + "icss-utils": "^4.1.1", + "postcss": "^7.0.32", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss-modules-scope": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz", + "integrity": "sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ==", + "dev": true, + "dependencies": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^6.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss-modules-values": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz", + "integrity": "sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg==", + "dev": true, + "dependencies": { + "icss-utils": "^4.0.0", + "postcss": "^7.0.6" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/postcss/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", + "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/prettier-eslint": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-9.0.1.tgz", + "integrity": "sha512-KZT65QTosSAqBBqmrC+RpXbsMRe7Os2YSR9cAfFbDlyPAopzA/S5bioiZ3rpziNQNSJaOxmtXSx07EQ+o2Dlug==", + "dev": true, + "dependencies": { + "@typescript-eslint/parser": "^1.10.2", + "common-tags": "^1.4.0", + "core-js": "^3.1.4", + "dlv": "^1.1.0", + "eslint": "^5.0.0", + "indent-string": "^4.0.0", + "lodash.merge": "^4.6.0", + "loglevel-colored-level-prefix": "^1.0.0", + "prettier": "^1.7.0", + "pretty-format": "^23.0.1", + "require-relative": "^0.8.7", + "typescript": "^3.2.1", + "vue-eslint-parser": "^2.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/prettier-eslint-cli": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/prettier-eslint-cli/-/prettier-eslint-cli-5.0.0.tgz", + "integrity": "sha512-cei9UbN1aTrz3sQs88CWpvY/10PYTevzd76zoG1tdJ164OhmNTFRKPTOZrutVvscoQWzbnLKkviS3gu5JXwvZg==", + "dev": true, + "dependencies": { + "arrify": "^2.0.1", + "boolify": "^1.0.0", + "camelcase-keys": "^6.0.0", + "chalk": "^2.4.2", + "common-tags": "^1.8.0", + "core-js": "^3.1.4", + "eslint": "^5.0.0", + "find-up": "^4.1.0", + "get-stdin": "^7.0.0", + "glob": "^7.1.4", + "ignore": "^5.1.2", + "lodash.memoize": "^4.1.2", + "loglevel-colored-level-prefix": "^1.0.0", + "messageformat": "^2.2.1", + "prettier-eslint": "^9.0.0", + "rxjs": "^6.5.2", + "yargs": "^13.2.4" + }, + "bin": { + "prettier-eslint": "dist/index.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prettier-eslint-cli/node_modules/ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint-cli/node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint-cli/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint-cli/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint-cli/node_modules/cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "dependencies": { + "restore-cursor": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint-cli/node_modules/cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "dev": true + }, + "node_modules/prettier-eslint-cli/node_modules/eslint": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", + "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.9.1", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^4.0.3", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^5.0.1", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.2.2", + "js-yaml": "^3.13.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^6.14.0 || ^8.10.0 || >=9.10.0" + } + }, + "node_modules/prettier-eslint-cli/node_modules/eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/prettier-eslint-cli/node_modules/eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/prettier-eslint-cli/node_modules/eslint/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/prettier-eslint-cli/node_modules/espree": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", + "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", + "dev": true, + "dependencies": { + "acorn": "^6.0.7", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/prettier-eslint-cli/node_modules/figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint-cli/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prettier-eslint-cli/node_modules/get-stdin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz", + "integrity": "sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/prettier-eslint-cli/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint-cli/node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/prettier-eslint-cli/node_modules/inquirer": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "dev": true, + "dependencies": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/prettier-eslint-cli/node_modules/inquirer/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/prettier-eslint-cli/node_modules/inquirer/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/prettier-eslint-cli/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint-cli/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prettier-eslint-cli/node_modules/mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint-cli/node_modules/mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "node_modules/prettier-eslint-cli/node_modules/onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "dependencies": { + "mimic-fn": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint-cli/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prettier-eslint-cli/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/prettier-eslint-cli/node_modules/regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true, + "engines": { + "node": ">=6.5.0" + } + }, + "node_modules/prettier-eslint-cli/node_modules/restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "dependencies": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint-cli/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/prettier-eslint-cli/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint-cli/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint-cli/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prettier-eslint/node_modules/@typescript-eslint/experimental-utils": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz", + "integrity": "sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "1.13.0", + "eslint-scope": "^4.0.0" + }, + "engines": { + "node": "^6.14.0 || ^8.10.0 || >=9.10.0" + }, + "peerDependencies": { + "eslint": "*" + } + }, + "node_modules/prettier-eslint/node_modules/@typescript-eslint/parser": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-1.13.0.tgz", + "integrity": "sha512-ITMBs52PCPgLb2nGPoeT4iU3HdQZHcPaZVw+7CsFagRJHUhyeTgorEwHXhFf3e7Evzi8oujKNpHc8TONth8AdQ==", + "dev": true, + "dependencies": { + "@types/eslint-visitor-keys": "^1.0.0", + "@typescript-eslint/experimental-utils": "1.13.0", + "@typescript-eslint/typescript-estree": "1.13.0", + "eslint-visitor-keys": "^1.0.0" + }, + "engines": { + "node": "^6.14.0 || ^8.10.0 || >=9.10.0" + }, + "peerDependencies": { + "eslint": "^5.0.0" + } + }, + "node_modules/prettier-eslint/node_modules/@typescript-eslint/typescript-estree": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz", + "integrity": "sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==", + "dev": true, + "dependencies": { + "lodash.unescape": "4.0.1", + "semver": "5.5.0" + }, + "engines": { + "node": ">=6.14.0" + } + }, + "node_modules/prettier-eslint/node_modules/ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint/node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint/node_modules/cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "dev": true, + "dependencies": { + "restore-cursor": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint/node_modules/cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "dev": true + }, + "node_modules/prettier-eslint/node_modules/eslint": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", + "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.9.1", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^4.0.3", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^5.0.1", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.2.2", + "js-yaml": "^3.13.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^6.14.0 || ^8.10.0 || >=9.10.0" + } + }, + "node_modules/prettier-eslint/node_modules/eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/prettier-eslint/node_modules/eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/prettier-eslint/node_modules/eslint/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/prettier-eslint/node_modules/espree": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", + "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", + "dev": true, + "dependencies": { + "acorn": "^6.0.7", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/prettier-eslint/node_modules/figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/prettier-eslint/node_modules/inquirer": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "dev": true, + "dependencies": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/prettier-eslint/node_modules/inquirer/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/prettier-eslint/node_modules/inquirer/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/prettier-eslint/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint/node_modules/mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint/node_modules/mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", + "dev": true + }, + "node_modules/prettier-eslint/node_modules/onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "dependencies": { + "mimic-fn": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint/node_modules/prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint/node_modules/regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true, + "engines": { + "node": ">=6.5.0" + } + }, + "node_modules/prettier-eslint/node_modules/restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "dev": true, + "dependencies": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint/node_modules/semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/prettier-eslint/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prettier-eslint/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pretty-error": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", + "integrity": "sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw==", + "dev": true, + "dependencies": { + "lodash": "^4.17.20", + "renderkid": "^2.0.4" + } + }, + "node_modules/pretty-format": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", + "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0", + "ansi-styles": "^3.2.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", + "dev": true + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "dev": true, + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "dev": true, + "dependencies": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "node_modules/pumpify/node_modules/pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/querystring-es3": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", + "dev": true, + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, + "node_modules/quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "dev": true, + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/re-resizable": { + "version": "6.9.9", + "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.9.9.tgz", + "integrity": "sha512-l+MBlKZffv/SicxDySKEEh42hR6m5bAHfNu3Tvxks2c4Ah+ldnWjfnVRwxo/nxF27SsUsxDS0raAzFuJNKABXA==", + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz", + "integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-codemirror2": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/react-codemirror2/-/react-codemirror2-7.2.1.tgz", + "integrity": "sha512-t7YFmz1AXdlImgHXA9Ja0T6AWuopilub24jRaQdPVbzUJVNKIYuy3uCFZYa7CE5S3UW6SrSa5nAqVQvtzRF9gw==", + "peerDependencies": { + "codemirror": "5.x", + "react": ">=15.5 <=16.x" + } + }, + "node_modules/react-diff-viewer": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/react-diff-viewer/-/react-diff-viewer-3.1.1.tgz", + "integrity": "sha512-rmvwNdcClp6ZWdS11m1m01UnBA4OwYaLG/li0dB781e/bQEzsGyj+qewVd6W5ztBwseQ72pO7nwaCcq5jnlzcw==", + "dependencies": { + "classnames": "^2.2.6", + "create-emotion": "^10.0.14", + "diff": "^4.0.1", + "emotion": "^10.0.14", + "memoize-one": "^5.0.4", + "prop-types": "^15.6.2" + }, + "engines": { + "node": ">= 8" + }, + "peerDependencies": { + "react": "^15.3.0 || ^16.0.0", + "react-dom": "^15.3.0 || ^16.0.0" + } + }, + "node_modules/react-dom": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.13.1.tgz", + "integrity": "sha512-81PIMmVLnCNLO/fFOQxdQkvEq/+Hfpv24XNJfpyZhTRfO0QcmQIF/PgCa1zCOj2w1hrn12MFLyaJ/G0+Mxtfag==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.19.1" + }, + "peerDependencies": { + "react": "^16.13.1" + } + }, + "node_modules/react-hook-form": { + "version": "6.15.8", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-6.15.8.tgz", + "integrity": "sha512-prq82ofMbnRyj5wqDe8hsTRcdR25jQ+B8KtCS7BLCzjFHAwNuCjRwzPuP4eYLsEBjEIeYd6try+pdLdw0kPkpg==", + "peerDependencies": { + "react": "^16.8.0 || ^17" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-router": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.3.tgz", + "integrity": "sha512-mzQGUvS3bM84TnbtMYR8ZjKnuPJ71IjSzR+DE6UkUqvN4czWIqEs17yLL8xkAycv4ev0AiN+IGrWu88vJs/p2w==", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "mini-create-react-context": "^0.4.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" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-Ov0tGPMBgqmbu5CDmN++tv2HQ9HlWDuWIIqn4b88gjlAN5IHI+4ZUZRcpz9Hl0azFIwihbLDYw1OiHGRo7ZIng==", + "dependencies": { + "@babel/runtime": "^7.12.13", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.3.3", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "peerDependencies": { + "react": ">=15" + } + }, + "node_modules/react-spring": { + "version": "8.0.27", + "resolved": "https://registry.npmjs.org/react-spring/-/react-spring-8.0.27.tgz", + "integrity": "sha512-nDpWBe3ZVezukNRandTeLSPcwwTMjNVu1IDq9qA/AMiUqHuRN4BeSWvKr3eIxxg1vtiYiOLy4FqdfCP5IoP77g==", + "dependencies": { + "@babel/runtime": "^7.3.1", + "prop-types": "^15.5.8" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz", + "integrity": "sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "dependencies": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "dependencies": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up/node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up/node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up/node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up/node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up/node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg/node_modules/path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha512-dUnb5dXUf+kzhC/W/F4e5/SkluXIFf5VUHolW1Eg1irn1hGWjPGdsRcvYJ1nD6lhk8Ir7VM0bHJKsYTx8Jx9OQ==", + "dev": true, + "dependencies": { + "pify": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "optional": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recast": { + "version": "0.11.23", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.11.23.tgz", + "integrity": "sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM=", + "dev": true, + "dependencies": { + "ast-types": "0.9.6", + "esprima": "~3.1.0", + "private": "~0.1.5", + "source-map": "~0.5.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/recast/node_modules/esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha512-AWwVMNxwhN8+NIPQzAQZCm7RkLC4RbM3B1OobMuyp3i+w73X57KCKaVIxaRZb+DYCojq7rspo+fmuQfAboyhFg==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.9", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", + "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + }, + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "node_modules/renderkid": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.7.tgz", + "integrity": "sha512-oCcFyxaMrKsKcTY59qnCAtmDVSLfPbrv6A3tVbPdFMMrv5jaK10V6m40cKsoPNhAqN6rmHW9sswW4o3ruSrwUQ==", + "dev": true, + "dependencies": { + "css-select": "^4.1.3", + "dom-converter": "^0.2.0", + "htmlparser2": "^6.1.0", + "lodash": "^4.17.21", + "strip-ansi": "^3.0.1" + } + }, + "node_modules/renderkid/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/renderkid/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/require-relative": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", + "integrity": "sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=", + "dev": true + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "dependencies": { + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "dev": true, + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pathname": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", + "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==" + }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "deprecated": "https://github.com/lydell/resolve-url#deprecated", + "dev": true + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/ripemd160": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", + "dev": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1" + } + }, + "node_modules/run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-queue": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", + "dev": true, + "dependencies": { + "aproba": "^1.1.1" + } + }, + "node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "dependencies": { + "ret": "~0.1.10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/scheduler": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", + "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "node_modules/schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "dependencies": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", + "dev": true + }, + "node_modules/selfsigned": { + "version": "1.10.14", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.14.tgz", + "integrity": "sha512-lkjaiAye+wBZDCBsu5BGi0XiLRxeUlsGod5ZP924CRSEoGuZAw/f7y9RKu28rwTfiHVhdavhB0qH0INV6P1lEA==", + "dev": true, + "dependencies": { + "node-forge": "^0.10.0" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/serialize-javascript": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", + "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", + "dev": true + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", + "dev": true, + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/serve-index/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, + "node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/shell-quote": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", + "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", + "dev": true + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "dependencies": { + "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/snapdragon/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/sockjs": { + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz", + "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==", + "dev": true, + "dependencies": { + "faye-websocket": "^0.10.0", + "uuid": "^3.0.1" + } + }, + "node_modules/sockjs-client": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.3.0.tgz", + "integrity": "sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg==", + "dev": true, + "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" + } + }, + "node_modules/sockjs-client/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/sockjs-client/node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.4.tgz", + "integrity": "sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ==", + "dev": true, + "dependencies": { + "async": "^2.5.0", + "loader-utils": "^1.1.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dev": true, + "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" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "deprecated": "See https://github.com/lydell/source-map-url#deprecated", + "dev": true + }, + "node_modules/spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", + "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", + "dev": true + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "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" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "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" + } + }, + "node_modules/spdy-transport/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/ssri": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz", + "integrity": "sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==", + "dev": true, + "dependencies": { + "figgy-pudding": "^3.5.1" + } + }, + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-browserify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", + "dev": true, + "dependencies": { + "inherits": "~2.0.1", + "readable-stream": "^2.0.2" + } + }, + "node_modules/stream-each": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", + "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "node_modules/stream-http": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", + "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", + "dev": true, + "dependencies": { + "builtin-status-codes": "^3.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.3.6", + "to-arraybuffer": "^1.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "dev": true + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", + "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1", + "get-intrinsic": "^1.1.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.4.1", + "side-channel": "^1.0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.padend": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.3.tgz", + "integrity": "sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", + "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", + "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.19.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", + "integrity": "sha512-behete+3uqxecWlDAm5lmskaSaISA+ThQ4oNNBDTBJt0x2ppR6IPqfZNuj6BLaLJ/Sji4TPZlcRyOis8wXQTLg==", + "bin": { + "strip-ansi": "cli.js" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-loader": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.3.0.tgz", + "integrity": "sha512-V7TCORko8rs9rIqkSrlMfkqA63DfoGBBJmK1kKGCcSi+BWb4cqz0SRsnp4l6rU5iwOEd0/2ePv68SV22VXon4Q==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^2.7.0" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/style-loader/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/style-loader/node_modules/loader-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/style-loader/node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/system": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/system/-/system-2.0.1.tgz", + "integrity": "sha512-BwSUSa8LMHZouGadZ34ck3TsrH5s3oMmTKPK+xHdbBnTCZOZMJ38fHGKLAHkBl0PXru1Z4BsymQU4qqvTxWzdQ==", + "dev": true, + "bin": { + "jscat": "bundle.js" + } + }, + "node_modules/table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "dependencies": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/table/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/table/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/table/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/table/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/table/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tapable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", + "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", + "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", + "dev": true, + "dependencies": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", + "dev": true, + "dependencies": { + "cacache": "^12.0.2", + "find-cache-dir": "^2.1.0", + "is-wsl": "^1.1.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^4.0.0", + "source-map": "^0.6.1", + "terser": "^4.1.2", + "webpack-sources": "^1.4.0", + "worker-farm": "^1.7.0" + }, + "engines": { + "node": ">= 6.9.0" + }, + "peerDependencies": { + "webpack": "^4.0.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/terser-webpack-plugin/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/terser/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "node_modules/timers-browserify": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", + "dev": true, + "dependencies": { + "setimmediate": "^1.0.4" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/tiny-glob": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", + "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", + "dev": true, + "dependencies": { + "globalyzer": "0.1.0", + "globrex": "^0.1.2" + } + }, + "node_modules/tiny-invariant": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.2.0.tgz", + "integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==" + }, + "node_modules/tiny-warning": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-arraybuffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", + "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/ts-loader": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-7.0.5.tgz", + "integrity": "sha512-zXypEIT6k3oTc+OZNx/cqElrsbBtYqDknf48OZos0NQ3RTt045fBIU8RRSu+suObBzYB355aIPGOe/3kj9h7Ig==", + "dev": true, + "dependencies": { + "chalk": "^2.3.0", + "enhanced-resolve": "^4.0.0", + "loader-utils": "^1.0.2", + "micromatch": "^4.0.0", + "semver": "^6.0.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "node_modules/ts-loader/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/ts-loader/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tty-browserify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", + "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", + "dev": true + }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "node_modules/typescript": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.8.3.tgz", + "integrity": "sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uglify-js": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", + "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==", + "dev": true, + "dependencies": { + "commander": "~2.19.0", + "source-map": "~0.6.1" + }, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/uglify-js/node_modules/commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + }, + "node_modules/uglify-js/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/underscore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=" + }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "dev": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "engines": { + "node": ">=4", + "yarn": "*" + } + }, + "node_modules/upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", + "dev": true + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "deprecated": "Please see https://github.com/lydell/urix#deprecated", + "dev": true + }, + "node_modules/url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dev": true, + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/url-loader": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", + "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "mime-types": "^2.1.27", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "file-loader": "*", + "webpack": "^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "file-loader": { + "optional": true + } + } + }, + "node_modules/url-loader/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/url-loader/node_modules/loader-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/url-loader/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + }, + "node_modules/use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/util": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "dev": true, + "dependencies": { + "inherits": "2.0.3" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "node_modules/util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, + "node_modules/util/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "node_modules/utila": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", + "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=", + "dev": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "dev": true, + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/value-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", + "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vm-browserify": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", + "dev": true + }, + "node_modules/vue-eslint-parser": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-2.0.3.tgz", + "integrity": "sha512-ZezcU71Owm84xVF6gfurBQUGg8WQ+WZGxgDEQu1IHFBZNx7BFZg3L1yHxrCBNNwbwFtE1GuvfJKMtb6Xuwc/Bw==", + "dev": true, + "dependencies": { + "debug": "^3.1.0", + "eslint-scope": "^3.7.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^3.5.2", + "esquery": "^1.0.0", + "lodash": "^4.17.4" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": ">=3.9.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "dependencies": { + "acorn": "^3.0.4" + } + }, + "node_modules/vue-eslint-parser/node_modules/acorn-jsx/node_modules/acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/vue-eslint-parser/node_modules/eslint-scope": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", + "dev": true, + "dependencies": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/vue-eslint-parser/node_modules/espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, + "dependencies": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", + "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "neo-async": "^2.5.0" + }, + "optionalDependencies": { + "chokidar": "^3.4.1", + "watchpack-chokidar2": "^2.0.1" + } + }, + "node_modules/watchpack-chokidar2": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", + "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", + "dev": true, + "optional": true, + "dependencies": { + "chokidar": "^2.1.8" + } + }, + "node_modules/watchpack-chokidar2/node_modules/anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "optional": true, + "dependencies": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "node_modules/watchpack-chokidar2/node_modules/anymatch/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dev": true, + "optional": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "optional": true, + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "optional": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", + "dev": true, + "optional": true, + "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" + } + }, + "node_modules/watchpack-chokidar2/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "optional": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "optional": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "deprecated": "The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", + "dev": true, + "optional": true, + "dependencies": { + "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "optional": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "optional": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "optional": true, + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack-chokidar2/node_modules/readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "optional": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/watchpack-chokidar2/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "optional": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/webpack": { + "version": "4.29.6", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.29.6.tgz", + "integrity": "sha512-MwBwpiE1BQpMDkbnUUaW6K8RFZjljJHArC6tWQJoFm0oQtfoSebtg4Y7/QHnJ/SddtjYLHaKGX64CFjG5rehJw==", + "dev": true, + "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.0.5", + "acorn-dynamic-import": "^4.0.0", + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0", + "chrome-trace-event": "^1.0.0", + "enhanced-resolve": "^4.1.0", + "eslint-scope": "^4.0.0", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^2.3.0", + "loader-utils": "^1.1.0", + "memory-fs": "~0.4.1", + "micromatch": "^3.1.8", + "mkdirp": "~0.5.0", + "neo-async": "^2.5.0", + "node-libs-browser": "^2.0.0", + "schema-utils": "^1.0.0", + "tapable": "^1.1.0", + "terser-webpack-plugin": "^1.1.0", + "watchpack": "^1.5.0", + "webpack-sources": "^1.3.0" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/webpack-cli": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-3.3.0.tgz", + "integrity": "sha512-t1M7G4z5FhHKJ92WRKwZ1rtvi7rHc0NZoZRbSkol0YKl4HvcC8+DsmGDmK7MmZxHSAetHagiOsjOB6MmzC2TUw==", + "dev": true, + "dependencies": { + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "enhanced-resolve": "^4.1.0", + "findup-sync": "^2.0.0", + "global-modules": "^1.0.0", + "import-local": "^2.0.0", + "interpret": "^1.1.0", + "loader-utils": "^1.1.0", + "supports-color": "^5.5.0", + "v8-compile-cache": "^2.0.2", + "yargs": "^12.0.5" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=6.11.5" + }, + "peerDependencies": { + "webpack": "4.x.x" + } + }, + "node_modules/webpack-cli/node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-cli/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-cli/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-cli/node_modules/cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "dependencies": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "node_modules/webpack-cli/node_modules/get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "node_modules/webpack-cli/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-cli/node_modules/require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", + "dev": true + }, + "node_modules/webpack-cli/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-cli/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-cli/node_modules/wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==", + "dev": true, + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-cli/node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-cli/node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "dev": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-cli/node_modules/wrap-ansi/node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "dev": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-cli/node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-cli/node_modules/yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "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" + } + }, + "node_modules/webpack-cli/node_modules/yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/webpack-dev-middleware": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz", + "integrity": "sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ==", + "dev": true, + "dependencies": { + "memory-fs": "^0.4.1", + "mime": "^2.4.4", + "mkdirp": "^0.5.1", + "range-parser": "^1.2.1", + "webpack-log": "^2.0.0" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/webpack-dev-middleware/node_modules/memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha512-cda4JKCxReDXFXRqOHPQscuIYg1PvxbE2S2GP45rnwfEK+vZaXC8C1OFvdHIbgw0DLzowXGVoxLaAmlgRy14GQ==", + "dev": true, + "dependencies": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "node_modules/webpack-dev-middleware/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/webpack-dev-server": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.2.1.tgz", + "integrity": "sha512-sjuE4mnmx6JOh9kvSbPYw3u/6uxCLHNWfhWaIPwcXWsvWOPN+nc5baq4i9jui3oOBRXGonK9+OI0jVkaz6/rCw==", + "dev": true, + "dependencies": { + "ansi-html": "0.0.7", + "bonjour": "^3.5.0", + "chokidar": "^2.0.0", + "compression": "^1.5.2", + "connect-history-api-fallback": "^1.3.0", + "debug": "^4.1.1", + "del": "^3.0.0", + "express": "^4.16.2", + "html-entities": "^1.2.0", + "http-proxy-middleware": "^0.19.1", + "import-local": "^2.0.0", + "internal-ip": "^4.2.0", + "ip": "^1.1.5", + "killable": "^1.0.0", + "loglevel": "^1.4.1", + "opn": "^5.1.0", + "portfinder": "^1.0.9", + "schema-utils": "^1.0.0", + "selfsigned": "^1.9.1", + "semver": "^5.6.0", + "serve-index": "^1.7.2", + "sockjs": "0.3.19", + "sockjs-client": "1.3.0", + "spdy": "^4.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^6.1.0", + "url": "^0.11.0", + "webpack-dev-middleware": "^3.5.1", + "webpack-log": "^2.0.0", + "yargs": "12.0.2" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 6.11.5" + }, + "peerDependencies": { + "webpack": "^4.0.0" + } + }, + "node_modules/webpack-dev-server/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "dependencies": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "node_modules/webpack-dev-server/node_modules/anymatch/node_modules/normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "dev": true, + "dependencies": { + "remove-trailing-separator": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/binary-extensions": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", + "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-dev-server/node_modules/chokidar": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", + "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", + "dev": true, + "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" + } + }, + "node_modules/webpack-dev-server/node_modules/cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "dependencies": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "node_modules/webpack-dev-server/node_modules/cliui/node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-dev-server/node_modules/cliui/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-dev-server/node_modules/decamelize": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", + "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", + "dev": true, + "dependencies": { + "xregexp": "4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-dev-server/node_modules/del": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", + "integrity": "sha512-7yjqSoVSlJzA4t/VUwazuEagGeANEKB3f/aNI//06pfKgwoCb7f6Q1gETN1sZzYaj6chTQ0AhIwDiPdfOjko4A==", + "dev": true, + "dependencies": { + "globby": "^6.1.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "p-map": "^1.1.1", + "pify": "^3.0.0", + "rimraf": "^2.2.8" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-dev-server/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/fsevents": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", + "deprecated": "The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/webpack-dev-server/node_modules/get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "node_modules/webpack-dev-server/node_modules/is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", + "dev": true, + "dependencies": { + "binary-extensions": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-dev-server/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/is-path-cwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha512-cnS56eR9SPAscL77ik76ATVqoPARTqPIVkMDVxRaWH06zT+6+CzIroYRJ0VVvm0Z1zfAvxvz9i/D3Ppjaqt5Nw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/is-path-in-cwd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", + "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", + "dev": true, + "dependencies": { + "is-path-inside": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/is-path-inside": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", + "integrity": "sha512-qhsCR/Esx4U4hg/9I19OVUAJkGWtjRYHMRgUMZE2TDdj+Ag+kttZanLupfddNyglzz50cUlmWzUaI37GDfNx/g==", + "dev": true, + "dependencies": { + "path-is-inside": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/p-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", + "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-dev-server/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-dev-server/node_modules/readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/webpack-dev-server/node_modules/require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", + "dev": true + }, + "node_modules/webpack-dev-server/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/webpack-dev-server/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-dev-server/node_modules/string-width/node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-dev-server/node_modules/string-width/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/webpack-dev-server/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/webpack-dev-server/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==", + "dev": true, + "dependencies": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", + "dev": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/wrap-ansi/node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", + "dev": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack-dev-server/node_modules/xregexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", + "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==", + "dev": true + }, + "node_modules/webpack-dev-server/node_modules/yargs": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.2.tgz", + "integrity": "sha512-e7SkEx6N6SIZ5c5H22RTZae61qtn3PYUE8JYbBFlK9sYmh3DMQ6E5ygtaG/2BW0JZi4WGgTR2IV5ChqlqrDGVQ==", + "dev": true, + "dependencies": { + "cliui": "^4.0.0", + "decamelize": "^2.0.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": "^10.1.0" + } + }, + "node_modules/webpack-dev-server/node_modules/yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "dependencies": { + "camelcase": "^4.1.0" + } + }, + "node_modules/webpack-log": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", + "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==", + "dev": true, + "dependencies": { + "ansi-colors": "^3.0.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/webpack-sources": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", + "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "dev": true, + "dependencies": { + "source-list-map": "^2.0.0", + "source-map": "~0.6.1" + } + }, + "node_modules/webpack-sources/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/webpack/node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dev": true, + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha512-cda4JKCxReDXFXRqOHPQscuIYg1PvxbE2S2GP45rnwfEK+vZaXC8C1OFvdHIbgw0DLzowXGVoxLaAmlgRy14GQ==", + "dev": true, + "dependencies": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "node_modules/webpack/node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "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" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webpack/node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dev": true, + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/worker-farm": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", + "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", + "dev": true, + "dependencies": { + "errno": "~0.1.7" + } + }, + "node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "dependencies": { + "mkdirp": "^0.5.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/xregexp": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.4.1.tgz", + "integrity": "sha512-2u9HwfadaJaY9zHtRRnH6BY6CQVNQKkYm3oLtC9gJXXzfsbACg5X5e4EZZGVAH+YIfa+QA9lsFQTTe3HURF3ag==", + "dev": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.12.1" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "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" + } + }, + "node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + } + }, "dependencies": { "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "requires": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "@babel/generator": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "dev": true, + "peer": true, + "requires": { + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "peer": true + }, + "@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "peer": true, + "requires": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "peer": true, "requires": { - "@babel/highlight": "^7.16.7" + "@babel/types": "^7.22.5" } }, "@babel/helper-module-imports": { @@ -20,18 +14291,33 @@ "@babel/types": "^7.16.7" } }, + "@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "peer": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==" + }, "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==" + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" }, "@babel/highlight": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.17.12.tgz", - "integrity": "sha512-7yykMVF3hfZY2jsHZEEgLc+3x4o1O+fYyULu11GynEUQNwB6lua+IIQn1FiJxNucd5UlyJryrwsOh8PL9Sn8Qg==", + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "dependencies": { @@ -55,6 +14341,13 @@ } } }, + "@babel/parser": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz", + "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==", + "dev": true, + "peer": true + }, "@babel/runtime": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.18.3.tgz", @@ -73,12 +14366,53 @@ "regenerator-runtime": "^0.13.4" } }, + "@babel/template": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "dev": true, + "peer": true, + "requires": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" + } + }, + "@babel/traverse": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.0.tgz", + "integrity": "sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==", + "dev": true, + "peer": true, + "requires": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "dependencies": { + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "peer": true + } + } + }, "@babel/types": { - "version": "7.18.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.4.tgz", - "integrity": "sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==", + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz", + "integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==", "requires": { - "@babel/helper-validator-identifier": "^7.16.7", + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, @@ -169,6 +14503,50 @@ "prop-types": "^15.8.1" } }, + "@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "peer": true, + "requires": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "peer": true + }, + "@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "peer": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true, + "peer": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "peer": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "@material-ui/core": { "version": "4.11.0", "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.11.0.tgz", @@ -245,7 +14623,8 @@ "@material-ui/types": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz", - "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==" + "integrity": "sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==", + "requires": {} }, "@material-ui/utils": { "version": "4.11.3", @@ -257,6 +14636,11 @@ "react-is": "^16.8.0 || ^17.0.0" } }, + "@sqltools/formatter": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", + "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==" + }, "@types/codemirror": { "version": "0.0.104", "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.104.tgz", @@ -705,11 +15089,6 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, - "JSV": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz", - "integrity": "sha1-0Hf2glVx+CEy+d/67Vh7QCn+/1c=" - }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -721,22 +15100,24 @@ } }, "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", + "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", "dev": true }, "acorn-dynamic-import": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz", "integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==", - "dev": true + "dev": true, + "requires": {} }, "acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "ajv": { "version": "6.12.6", @@ -754,13 +15135,15 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true + "dev": true, + "requires": {} }, "ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "ansi-colors": { "version": "3.2.4", @@ -1038,6 +15421,21 @@ "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", "dev": true }, + "babel-eslint": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", + "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", + "dev": true, + "peer": true, + "requires": { + "@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-plugin-emotion": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.2.2.tgz", @@ -1184,21 +15582,21 @@ "dev": true }, "body-parser": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", - "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dev": true, "requires": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.10.3", - "raw-body": "2.5.1", + "qs": "6.11.0", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -1866,9 +16264,9 @@ } }, "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true }, "convert-source-map": { @@ -1880,9 +16278,9 @@ } }, "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "dev": true }, "cookie-signature": { @@ -2944,9 +17342,9 @@ } }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "loader-utils": { @@ -3210,7 +17608,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-3.0.0.tgz", "integrity": "sha512-EjxTHxjLKIBWFgDJdhKKzLh5q+vjTFrqNZX36uIxWS4OfyXe5DawqPj3U5qeJ1ngLwatjzQnmR0Lz0J0YH3kxw==", - "dev": true + "dev": true, + "requires": {} }, "eslint-scope": { "version": "5.1.1", @@ -3246,6 +17645,14 @@ "acorn": "^7.1.1", "acorn-jsx": "^5.2.0", "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + } } }, "esprima": { @@ -3414,17 +17821,17 @@ "integrity": "sha512-DLHwOGYeGnATM6tOMOWgs9dbzCjO+DwO3YGaha2R6kmLCE5iL8dz5sOywWeJs4P1rhxpdaVILKhCB4mUrTbbGg==" }, "express": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", - "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dev": true, "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.0", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -3440,7 +17847,7 @@ "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", "proxy-addr": "~2.0.7", - "qs": "6.10.3", + "qs": "6.11.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.18.0", @@ -3663,9 +18070,9 @@ }, "dependencies": { "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "loader-utils": { @@ -3930,9 +18337,9 @@ } }, "follow-redirects": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==" + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" }, "for-in": { "version": "1.0.2", @@ -5260,6 +19667,13 @@ "esprima": "^4.0.0" } }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "peer": true + }, "json-bigint": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", @@ -5298,9 +19712,9 @@ "dev": true }, "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "requires": { "minimist": "^1.2.0" @@ -5409,6 +19823,11 @@ "jss": "10.9.0" } }, + "JSV": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz", + "integrity": "sha1-0Hf2glVx+CEy+d/67Vh7QCn+/1c=" + }, "jsx-ast-utils": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz", @@ -5419,6 +19838,11 @@ "object.assign": "^4.1.0" } }, + "jwt-decode": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", + "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==" + }, "killable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", @@ -5692,7 +20116,7 @@ "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true }, "mem": { @@ -5900,9 +20324,9 @@ } }, "moment": { - "version": "2.29.3", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz", - "integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==" + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" }, "move-concurrently": { "version": "1.0.1", @@ -6878,12 +21302,6 @@ "semver": "5.5.0" } }, - "acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "dev": true - }, "ansi-escapes": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", @@ -7179,12 +21597,6 @@ "yargs": "^13.2.4" }, "dependencies": { - "acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "dev": true - }, "ansi-escapes": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", @@ -7653,9 +22065,9 @@ "dev": true }, "qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", "dev": true, "requires": { "side-channel": "^1.0.4" @@ -7711,9 +22123,9 @@ "dev": true }, "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dev": true, "requires": { "bytes": "3.1.2", @@ -7733,7 +22145,8 @@ "re-resizable": { "version": "6.9.9", "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.9.9.tgz", - "integrity": "sha512-l+MBlKZffv/SicxDySKEEh42hR6m5bAHfNu3Tvxks2c4Ah+ldnWjfnVRwxo/nxF27SsUsxDS0raAzFuJNKABXA==" + "integrity": "sha512-l+MBlKZffv/SicxDySKEEh42hR6m5bAHfNu3Tvxks2c4Ah+ldnWjfnVRwxo/nxF27SsUsxDS0raAzFuJNKABXA==", + "requires": {} }, "react": { "version": "16.13.1", @@ -7748,7 +22161,8 @@ "react-codemirror2": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/react-codemirror2/-/react-codemirror2-7.2.1.tgz", - "integrity": "sha512-t7YFmz1AXdlImgHXA9Ja0T6AWuopilub24jRaQdPVbzUJVNKIYuy3uCFZYa7CE5S3UW6SrSa5nAqVQvtzRF9gw==" + "integrity": "sha512-t7YFmz1AXdlImgHXA9Ja0T6AWuopilub24jRaQdPVbzUJVNKIYuy3uCFZYa7CE5S3UW6SrSa5nAqVQvtzRF9gw==", + "requires": {} }, "react-diff-viewer": { "version": "3.1.1", @@ -7777,7 +22191,8 @@ "react-hook-form": { "version": "6.15.8", "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-6.15.8.tgz", - "integrity": "sha512-prq82ofMbnRyj5wqDe8hsTRcdR25jQ+B8KtCS7BLCzjFHAwNuCjRwzPuP4eYLsEBjEIeYd6try+pdLdw0kPkpg==" + "integrity": "sha512-prq82ofMbnRyj5wqDe8hsTRcdR25jQ+B8KtCS7BLCzjFHAwNuCjRwzPuP4eYLsEBjEIeYd6try+pdLdw0kPkpg==", + "requires": {} }, "react-is": { "version": "16.13.1", @@ -8895,6 +23310,15 @@ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -8966,15 +23390,6 @@ "es-abstract": "^1.19.5" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, "strip-ansi": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz", @@ -9009,9 +23424,9 @@ }, "dependencies": { "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "loader-utils": { @@ -9116,9 +23531,9 @@ "dev": true }, "terser": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", - "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", + "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", "dev": true, "requires": { "commander": "^2.20.0", @@ -9588,9 +24003,9 @@ }, "dependencies": { "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true }, "loader-utils": { @@ -10050,12 +24465,6 @@ "webpack-sources": "^1.3.0" }, "dependencies": { - "acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "dev": true - }, "braces": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", diff --git a/pinot-controller/src/main/resources/package.json b/pinot-controller/src/main/resources/package.json index fa9050120718..865af39bd42f 100644 --- a/pinot-controller/src/main/resources/package.json +++ b/pinot-controller/src/main/resources/package.json @@ -7,6 +7,7 @@ "start": "npm-run-all --parallel lint dev", "build": "webpack --mode production", "build-dev": "webpack --mode development", + "build-ci": "if [ \"$CI\" = true ]; then npm run-script build; else npm run-script build-dev; fi", "build-analyze": "webpack --mode production --analyze", "lint": "eslint 'app/**/*.{js,ts,tsx,jsx}' --quiet --fix", "test": "echo \"Error: no test specified\" && exit 1" @@ -78,8 +79,9 @@ "file": "0.2.2", "json-bigint": "1.0.0", "jsonlint": "1.6.3", + "jwt-decode": "^3.1.2", "lodash": "4.17.21", - "moment": "2.29.3", + "moment": "2.29.4", "prop-types": "15.8.1", "re-resizable": "6.9.9", "react": "16.13.1", @@ -88,6 +90,7 @@ "react-dom": "16.13.1", "react-hook-form": "6.15.8", "react-router-dom": "5.3.3", - "react-spring": "8.0.27" + "react-spring": "8.0.27", + "@sqltools/formatter": "1.2.5" } } diff --git a/pinot-controller/src/main/resources/webpack.config.js b/pinot-controller/src/main/resources/webpack.config.js index 6ed7a39660bf..65e89e366ab5 100644 --- a/pinot-controller/src/main/resources/webpack.config.js +++ b/pinot-controller/src/main/resources/webpack.config.js @@ -38,7 +38,7 @@ module.exports = (env, argv) => { extensions: ['.ts', '.tsx', '.js'], modules: ['./app', 'node_modules'], }, - entry: './app/App.tsx', + entry: './app/index.tsx', output: { path: path.resolve(__dirname, 'dist/webapp'), filename: './js/main.js' diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/ControllerConfTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/ControllerConfTest.java index 159af6dc004f..49494b914e3d 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/ControllerConfTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/ControllerConfTest.java @@ -158,6 +158,29 @@ public void shouldBeAbleToDisableUsingNewConfig() { Assert.assertEquals(conf.getTaskManagerFrequencyInSeconds(), -1); } + @Test + public void shouldBeAbleToSetDataDir() { + Map controllerConfig = new HashMap<>(); + ControllerConf conf = new ControllerConf(controllerConfig); + Assert.assertEquals(conf.getDataDir(), null); + + // test for the dataDir s3 value with ending slash + conf.setDataDir("s3:///controller/"); + Assert.assertEquals(conf.getDataDir(), "s3:///controller"); + + // test for the dataDir s3 value without ending slash + conf.setDataDir("s3:///controller"); + Assert.assertEquals(conf.getDataDir(), "s3:///controller"); + + // test for the dataDir non-s3 value without ending slash + conf.setDataDir("/tmp/PinotController"); + Assert.assertEquals(conf.getDataDir(), "/tmp/PinotController"); + + // test for the dataDir non-s3 value with ending slash + conf.setDataDir("/tmp/PinotController/"); + Assert.assertEquals(conf.getDataDir(), "/tmp/PinotController"); + } + private void assertOnDurations(ControllerConf conf, long expectedDuration, Map controllerConfig) { int segmentLevelValidationIntervalInSeconds = conf.getSegmentLevelValidationIntervalInSeconds(); int segmentRelocatorFrequencyInSeconds = conf.getSegmentRelocatorFrequencyInSeconds(); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/ControllerStarterStatelessTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/ControllerStarterStatelessTest.java index 5706e80a7cfd..afa3e17d2fe6 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/ControllerStarterStatelessTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/ControllerStarterStatelessTest.java @@ -39,10 +39,8 @@ public class ControllerStarterStatelessTest extends ControllerTest { private final Map _configOverride = new HashMap<>(); @Override - public Map getDefaultControllerConfiguration() { - Map defaultConfig = super.getDefaultControllerConfiguration(); - defaultConfig.putAll(_configOverride); - return defaultConfig; + protected void overrideControllerConf(Map properties) { + properties.putAll(_configOverride); } @Test diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/ConsumingSegmentInfoReaderStatelessTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/ConsumingSegmentInfoReaderStatelessTest.java index af25904593c6..9c29142b4698 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/ConsumingSegmentInfoReaderStatelessTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/ConsumingSegmentInfoReaderStatelessTest.java @@ -28,15 +28,15 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.TreeSet; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import java.util.stream.Collectors; -import org.apache.commons.httpclient.HttpConnectionManager; -import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; +import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.pinot.common.exception.InvalidConfigException; import org.apache.pinot.common.restlet.resources.SegmentConsumerInfo; import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; @@ -74,7 +74,7 @@ public class ConsumingSegmentInfoReaderStatelessTest { private static final int EXTENDED_TIMEOUT_FACTOR = 100; private final Executor _executor = Executors.newFixedThreadPool(1); - private final HttpConnectionManager _connectionManager = new MultiThreadedHttpConnectionManager(); + private final HttpClientConnectionManager _connectionManager = new PoolingHttpClientConnectionManager(); private PinotHelixResourceManager _helix; private final Map _serverMap = new HashMap<>(); @@ -197,11 +197,11 @@ private BiMap serverEndpoints(String... servers) { private void mockSetup(final String[] servers, final Set consumingSegments) throws InvalidConfigException { when(_helix.getServerToSegmentsMap(anyString())).thenAnswer(invocationOnMock -> subsetOfServerSegments(servers)); + when(_helix.getServers(anyString(), anyString())).thenAnswer( + invocationOnMock -> new TreeSet<>(Arrays.asList(servers))); when(_helix.getDataInstanceAdminEndpoints(ArgumentMatchers.anySet())).thenAnswer( invocationOnMock -> serverEndpoints(servers)); when(_helix.getConsumingSegments(anyString())).thenAnswer(invocationOnMock -> consumingSegments); - when(_helix.getServersForSegment(anyString(), anyString())).thenAnswer( - invocationOnMock -> new HashSet<>(Arrays.asList(servers))); } private ConsumingSegmentInfoReader.ConsumingSegmentsInfoMap testRunner(final String[] servers, diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotBrokerRestletResourceStatelessTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotBrokerRestletResourceStatelessTest.java index 5c0242c5c553..4d2b1b6a064a 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotBrokerRestletResourceStatelessTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotBrokerRestletResourceStatelessTest.java @@ -140,6 +140,10 @@ public void testGetBrokers() Assert.assertEquals(_helixAdmin.getInstancesInClusterWithTag(getHelixClusterName(), "DefaultTenant_BROKER").size(), 10); + // Create schema + addDummySchema(TABLE_NAME_1); + addDummySchema(TABLE_NAME_2); + // Adding table _helixResourceManager .addTable(new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME_1).setNumReplicas(1).build()); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotFileUploadTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotFileUploadTest.java index e499edf634ea..51330ce48af5 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotFileUploadTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotFileUploadTest.java @@ -46,6 +46,8 @@ public void setUp() throws Exception { TEST_INSTANCE.setupSharedStateAndValidate(); + // Create schema + TEST_INSTANCE.addDummySchema(TABLE_NAME); // Adding table TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME) .setSegmentAssignmentStrategy("RandomAssignmentStrategy").setNumReplicas(2).build(); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceAssignmentRestletResourceStatelessTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceAssignmentRestletResourceStatelessTest.java index 38334bb25a87..5512ae314260 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceAssignmentRestletResourceStatelessTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceAssignmentRestletResourceStatelessTest.java @@ -21,15 +21,19 @@ import com.fasterxml.jackson.core.type.TypeReference; import java.io.IOException; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.TreeMap; import org.apache.pinot.common.assignment.InstancePartitions; +import org.apache.pinot.common.assignment.InstancePartitionsUtils; +import org.apache.pinot.common.tier.TierFactory; import org.apache.pinot.common.utils.config.TagNameUtils; import org.apache.pinot.controller.ControllerConf; import org.apache.pinot.controller.helix.ControllerTest; import org.apache.pinot.core.realtime.impl.fakestream.FakeStreamConfigUtils; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.config.table.TierConfig; import org.apache.pinot.spi.config.table.assignment.InstanceAssignmentConfig; import org.apache.pinot.spi.config.table.assignment.InstancePartitionsType; import org.apache.pinot.spi.config.table.assignment.InstanceReplicaGroupPartitionConfig; @@ -59,13 +63,18 @@ public class PinotInstanceAssignmentRestletResourceStatelessTest extends Control private static final String RAW_TABLE_NAME = "testTable"; private static final String TIME_COLUMN_NAME = "daysSinceEpoch"; + private static final String TIER_NAME = "tier1"; + + @Override + protected void overrideControllerConf(Map properties) { + properties.put(ControllerConf.CLUSTER_TENANT_ISOLATION_ENABLE, false); + } + @BeforeClass public void setUp() throws Exception { startZk(); - Map properties = getDefaultControllerConfiguration(); - properties.put(ControllerConf.CLUSTER_TENANT_ISOLATION_ENABLE, false); - startController(properties); + startController(); addFakeBrokerInstancesToAutoJoinHelixCluster(1, false); addFakeServerInstancesToAutoJoinHelixCluster(2, false); @@ -89,7 +98,7 @@ public void testInstanceAssignment() _helixResourceManager.addTable(offlineTableConfig); TableConfig realtimeTableConfig = new TableConfigBuilder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setBrokerTenant(BROKER_TENANT_NAME) - .setServerTenant(SERVER_TENANT_NAME).setLLC(true) + .setServerTenant(SERVER_TENANT_NAME) .setStreamConfigs(FakeStreamConfigUtils.getDefaultLowLevelStreamConfigs().getStreamConfigsMap()).build(); _helixResourceManager.addTable(realtimeTableConfig); @@ -112,15 +121,15 @@ public void testInstanceAssignment() // Add OFFLINE instance assignment config to the offline table config InstanceAssignmentConfig offlineInstanceAssignmentConfig = new InstanceAssignmentConfig( new InstanceTagPoolConfig(TagNameUtils.getOfflineTagForTenant(SERVER_TENANT_NAME), false, 0, null), null, - new InstanceReplicaGroupPartitionConfig(false, 0, 0, 0, 0, 0, false)); + new InstanceReplicaGroupPartitionConfig(false, 0, 0, 0, 0, 0, false, null), null, false); offlineTableConfig.setInstanceAssignmentConfigMap( - Collections.singletonMap(InstancePartitionsType.OFFLINE, offlineInstanceAssignmentConfig)); + Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), offlineInstanceAssignmentConfig)); _helixResourceManager.setExistingTableConfig(offlineTableConfig); // OFFLINE instance partitions should be generated - Map instancePartitionsMap = getInstancePartitionsMap(); + Map instancePartitionsMap = getInstancePartitionsMap(); assertEquals(instancePartitionsMap.size(), 1); - InstancePartitions offlineInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.OFFLINE); + InstancePartitions offlineInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.OFFLINE.toString()); assertNotNull(offlineInstancePartitions); assertEquals(offlineInstancePartitions.getNumReplicaGroups(), 1); assertEquals(offlineInstancePartitions.getNumPartitions(), 1); @@ -130,74 +139,110 @@ public void testInstanceAssignment() // Add CONSUMING instance assignment config to the real-time table config InstanceAssignmentConfig consumingInstanceAssignmentConfig = new InstanceAssignmentConfig( new InstanceTagPoolConfig(TagNameUtils.getRealtimeTagForTenant(SERVER_TENANT_NAME), false, 0, null), null, - new InstanceReplicaGroupPartitionConfig(false, 0, 0, 0, 0, 0, false)); + new InstanceReplicaGroupPartitionConfig(false, 0, 0, 0, 0, 0, false, null), null, false); realtimeTableConfig.setInstanceAssignmentConfigMap( - Collections.singletonMap(InstancePartitionsType.CONSUMING, consumingInstanceAssignmentConfig)); + Collections.singletonMap(InstancePartitionsType.CONSUMING.toString(), consumingInstanceAssignmentConfig)); _helixResourceManager.setExistingTableConfig(realtimeTableConfig); // CONSUMING instance partitions should be generated instancePartitionsMap = getInstancePartitionsMap(); assertEquals(instancePartitionsMap.size(), 2); - offlineInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.OFFLINE); + offlineInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.OFFLINE.toString()); assertNotNull(offlineInstancePartitions); assertEquals(offlineInstancePartitions.getNumReplicaGroups(), 1); assertEquals(offlineInstancePartitions.getNumPartitions(), 1); assertEquals(offlineInstancePartitions.getInstances(0, 0), Collections.singletonList(offlineInstanceId)); - InstancePartitions consumingInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.CONSUMING); + InstancePartitions consumingInstancePartitions = + instancePartitionsMap.get(InstancePartitionsType.CONSUMING.toString()); assertNotNull(consumingInstancePartitions); assertEquals(consumingInstancePartitions.getNumReplicaGroups(), 1); assertEquals(consumingInstancePartitions.getNumPartitions(), 1); assertEquals(consumingInstancePartitions.getInstances(0, 0).size(), 1); String consumingInstanceId = consumingInstancePartitions.getInstances(0, 0).get(0); + // Add tier config and tier instance assignment config to the offline table config + offlineTableConfig.setTierConfigsList(Collections.singletonList( + new TierConfig(TIER_NAME, TierFactory.TIME_SEGMENT_SELECTOR_TYPE, "7d", null, + TierFactory.PINOT_SERVER_STORAGE_TYPE, TagNameUtils.getOfflineTagForTenant(SERVER_TENANT_NAME), null, + null))); + InstanceAssignmentConfig tierInstanceAssignmentConfig = new InstanceAssignmentConfig( + new InstanceTagPoolConfig(TagNameUtils.getOfflineTagForTenant(SERVER_TENANT_NAME), false, 0, null), null, + new InstanceReplicaGroupPartitionConfig(false, 0, 0, 0, 0, 0, false, null), null, false); + Map instanceAssignmentConfigMap = new HashMap<>(); + instanceAssignmentConfigMap.put(InstancePartitionsType.OFFLINE.toString(), offlineInstanceAssignmentConfig); + instanceAssignmentConfigMap.put(TIER_NAME, tierInstanceAssignmentConfig); + offlineTableConfig.setInstanceAssignmentConfigMap(instanceAssignmentConfigMap); + _helixResourceManager.setExistingTableConfig(offlineTableConfig); + + // tier instance partitions should be generated + Map tierInstancePartitionsMap = getInstancePartitionsMap(); + assertEquals(tierInstancePartitionsMap.size(), 3); + InstancePartitions tierInstancePartitions = tierInstancePartitionsMap.get(TIER_NAME); + assertNotNull(tierInstancePartitions); + assertEquals(tierInstancePartitions.getNumReplicaGroups(), 1); + assertEquals(tierInstancePartitions.getNumPartitions(), 1); + assertEquals(tierInstancePartitions.getInstances(0, 0).size(), 1); + // Use OFFLINE instance assignment config as the COMPLETED instance assignment config - realtimeTableConfig.setInstanceAssignmentConfigMap( - new TreeMap() {{ - put(InstancePartitionsType.CONSUMING, consumingInstanceAssignmentConfig); - put(InstancePartitionsType.COMPLETED, offlineInstanceAssignmentConfig); - }}); + realtimeTableConfig.setInstanceAssignmentConfigMap(new TreeMap() {{ + put(InstancePartitionsType.CONSUMING.toString(), consumingInstanceAssignmentConfig); + put(InstancePartitionsType.COMPLETED.toString(), offlineInstanceAssignmentConfig); + }}); _helixResourceManager.setExistingTableConfig(realtimeTableConfig); // COMPLETED instance partitions should be generated instancePartitionsMap = getInstancePartitionsMap(); - assertEquals(instancePartitionsMap.size(), 3); - offlineInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.OFFLINE); + assertEquals(instancePartitionsMap.size(), 4); + offlineInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.OFFLINE.toString()); assertNotNull(offlineInstancePartitions); assertEquals(offlineInstancePartitions.getNumReplicaGroups(), 1); assertEquals(offlineInstancePartitions.getNumPartitions(), 1); assertEquals(offlineInstancePartitions.getInstances(0, 0), Collections.singletonList(offlineInstanceId)); - consumingInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.CONSUMING); + consumingInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.CONSUMING.toString()); assertNotNull(consumingInstancePartitions); assertEquals(consumingInstancePartitions.getNumReplicaGroups(), 1); assertEquals(consumingInstancePartitions.getNumPartitions(), 1); assertEquals(consumingInstancePartitions.getInstances(0, 0), Collections.singletonList(consumingInstanceId)); - InstancePartitions completedInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.COMPLETED); + InstancePartitions completedInstancePartitions = + instancePartitionsMap.get(InstancePartitionsType.COMPLETED.toString()); assertEquals(completedInstancePartitions.getNumReplicaGroups(), 1); assertEquals(completedInstancePartitions.getNumPartitions(), 1); assertEquals(completedInstancePartitions.getInstances(0, 0), Collections.singletonList(offlineInstanceId)); + InstancePartitions tInstancePartitions = instancePartitionsMap.get(TIER_NAME); + assertEquals(tInstancePartitions.getNumReplicaGroups(), 1); + assertEquals(tInstancePartitions.getNumPartitions(), 1); + assertEquals(tInstancePartitions.getInstances(0, 0), Collections.singletonList(offlineInstanceId)); // Test fetching instance partitions by table name with type suffix instancePartitionsMap = deserializeInstancePartitionsMap(sendGetRequest( _controllerRequestURLBuilder.forInstancePartitions(TableNameBuilder.OFFLINE.tableNameWithType(RAW_TABLE_NAME), null))); - assertEquals(instancePartitionsMap.size(), 1); - assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.OFFLINE)); + assertEquals(instancePartitionsMap.size(), 2); + assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.OFFLINE.toString())); + assertTrue(instancePartitionsMap.containsKey(TIER_NAME)); instancePartitionsMap = deserializeInstancePartitionsMap(sendGetRequest( _controllerRequestURLBuilder.forInstancePartitions(TableNameBuilder.REALTIME.tableNameWithType(RAW_TABLE_NAME), null))); assertEquals(instancePartitionsMap.size(), 2); - assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.CONSUMING)); - assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.COMPLETED)); + assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.CONSUMING.toString())); + assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.COMPLETED.toString())); // Test fetching instance partitions by table name and instance partitions type for (InstancePartitionsType instancePartitionsType : InstancePartitionsType.values()) { - instancePartitionsMap = deserializeInstancePartitionsMap( - sendGetRequest(_controllerRequestURLBuilder.forInstancePartitions(RAW_TABLE_NAME, instancePartitionsType))); + instancePartitionsMap = deserializeInstancePartitionsMap(sendGetRequest( + _controllerRequestURLBuilder.forInstancePartitions(RAW_TABLE_NAME, instancePartitionsType.toString()))); assertEquals(instancePartitionsMap.size(), 1); - assertEquals(instancePartitionsMap.get(instancePartitionsType).getInstancePartitionsName(), + assertEquals(instancePartitionsMap.get(instancePartitionsType.toString()).getInstancePartitionsName(), instancePartitionsType.getInstancePartitionsName(RAW_TABLE_NAME)); } + // Test fetching instance partitions by table name and tier name + instancePartitionsMap = deserializeInstancePartitionsMap( + sendGetRequest(_controllerRequestURLBuilder.forInstancePartitions(RAW_TABLE_NAME, TIER_NAME))); + assertEquals(instancePartitionsMap.size(), 1); + assertEquals(instancePartitionsMap.get(TIER_NAME).getInstancePartitionsName(), + InstancePartitionsUtils.getInstancePartitionsNameForTier(RAW_TABLE_NAME, TIER_NAME)); + // Remove the instance partitions for both offline and real-time table sendDeleteRequest(_controllerRequestURLBuilder.forInstancePartitions(RAW_TABLE_NAME, null)); try { @@ -210,21 +255,25 @@ public void testInstanceAssignment() // Assign instances without instance partitions type (dry run) instancePartitionsMap = deserializeInstancePartitionsMap( sendPostRequest(_controllerRequestURLBuilder.forInstanceAssign(RAW_TABLE_NAME, null, true), null)); - assertEquals(instancePartitionsMap.size(), 3); - offlineInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.OFFLINE); + assertEquals(instancePartitionsMap.size(), 4); + offlineInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.OFFLINE.toString()); assertNotNull(offlineInstancePartitions); assertEquals(offlineInstancePartitions.getNumReplicaGroups(), 1); assertEquals(offlineInstancePartitions.getNumPartitions(), 1); assertEquals(offlineInstancePartitions.getInstances(0, 0), Collections.singletonList(offlineInstanceId)); - consumingInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.CONSUMING); + consumingInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.CONSUMING.toString()); assertNotNull(consumingInstancePartitions); assertEquals(consumingInstancePartitions.getNumReplicaGroups(), 1); assertEquals(consumingInstancePartitions.getNumPartitions(), 1); assertEquals(consumingInstancePartitions.getInstances(0, 0), Collections.singletonList(consumingInstanceId)); - completedInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.COMPLETED); + completedInstancePartitions = instancePartitionsMap.get(InstancePartitionsType.COMPLETED.toString()); assertEquals(completedInstancePartitions.getNumReplicaGroups(), 1); assertEquals(completedInstancePartitions.getNumPartitions(), 1); assertEquals(completedInstancePartitions.getInstances(0, 0), Collections.singletonList(offlineInstanceId)); + tInstancePartitions = instancePartitionsMap.get(TIER_NAME); + assertEquals(tInstancePartitions.getNumReplicaGroups(), 1); + assertEquals(tInstancePartitions.getNumPartitions(), 1); + assertEquals(tInstancePartitions.getInstances(0, 0), Collections.singletonList(offlineInstanceId)); // Instance partitions should not be persisted try { @@ -239,34 +288,36 @@ public void testInstanceAssignment() // Instance partitions should be persisted instancePartitionsMap = getInstancePartitionsMap(); - assertEquals(instancePartitionsMap.size(), 3); + assertEquals(instancePartitionsMap.size(), 4); // Remove the instance partitions for real-time table sendDeleteRequest( _controllerRequestURLBuilder.forInstancePartitions(TableNameBuilder.REALTIME.tableNameWithType(RAW_TABLE_NAME), null)); instancePartitionsMap = getInstancePartitionsMap(); - assertEquals(instancePartitionsMap.size(), 1); - assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.OFFLINE)); + assertEquals(instancePartitionsMap.size(), 2); + assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.OFFLINE.toString())); + assertTrue(instancePartitionsMap.containsKey(TIER_NAME)); // Assign instances for COMPLETED segments instancePartitionsMap = deserializeInstancePartitionsMap(sendPostRequest( _controllerRequestURLBuilder.forInstanceAssign(RAW_TABLE_NAME, InstancePartitionsType.COMPLETED, false), null)); assertEquals(instancePartitionsMap.size(), 1); - assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.COMPLETED)); + assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.COMPLETED.toString())); // There should be OFFLINE and COMPLETED instance partitions persisted instancePartitionsMap = getInstancePartitionsMap(); - assertEquals(instancePartitionsMap.size(), 2); - assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.OFFLINE)); - assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.COMPLETED)); + assertEquals(instancePartitionsMap.size(), 3); + assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.OFFLINE.toString())); + assertTrue(instancePartitionsMap.containsKey(TIER_NAME)); + assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.COMPLETED.toString())); // Replace OFFLINE instance with CONSUMING instance for COMPLETED instance partitions instancePartitionsMap = deserializeInstancePartitionsMap(sendPostRequest( _controllerRequestURLBuilder.forInstanceReplace(RAW_TABLE_NAME, InstancePartitionsType.COMPLETED, offlineInstanceId, consumingInstanceId), null)); assertEquals(instancePartitionsMap.size(), 1); - assertEquals(instancePartitionsMap.get(InstancePartitionsType.COMPLETED).getInstances(0, 0), + assertEquals(instancePartitionsMap.get(InstancePartitionsType.COMPLETED.toString()).getInstances(0, 0), Collections.singletonList(consumingInstanceId)); // Replace the instance again using real-time table name (old instance does not exist) @@ -284,26 +335,27 @@ public void testInstanceAssignment() sendPutRequest(_controllerRequestURLBuilder.forInstancePartitions(RAW_TABLE_NAME, null), consumingInstancePartitions.toJsonString())); assertEquals(instancePartitionsMap.size(), 1); - assertEquals(instancePartitionsMap.get(InstancePartitionsType.CONSUMING).getInstances(0, 0), + assertEquals(instancePartitionsMap.get(InstancePartitionsType.CONSUMING.toString()).getInstances(0, 0), Collections.singletonList(consumingInstanceId)); // OFFLINE instance partitions should have OFFLINE instance, CONSUMING and COMPLETED instance partitions should have // CONSUMING instance instancePartitionsMap = getInstancePartitionsMap(); - assertEquals(instancePartitionsMap.size(), 3); - assertEquals(instancePartitionsMap.get(InstancePartitionsType.OFFLINE).getInstances(0, 0), + assertEquals(instancePartitionsMap.size(), 4); + assertEquals(instancePartitionsMap.get(InstancePartitionsType.OFFLINE.toString()).getInstances(0, 0), Collections.singletonList(offlineInstanceId)); - assertEquals(instancePartitionsMap.get(InstancePartitionsType.CONSUMING).getInstances(0, 0), + assertEquals(instancePartitionsMap.get(TIER_NAME).getInstances(0, 0), Collections.singletonList(offlineInstanceId)); + assertEquals(instancePartitionsMap.get(InstancePartitionsType.CONSUMING.toString()).getInstances(0, 0), Collections.singletonList(consumingInstanceId)); - assertEquals(instancePartitionsMap.get(InstancePartitionsType.COMPLETED).getInstances(0, 0), + assertEquals(instancePartitionsMap.get(InstancePartitionsType.COMPLETED.toString()).getInstances(0, 0), Collections.singletonList(consumingInstanceId)); // Delete the offline table _helixResourceManager.deleteOfflineTable(RAW_TABLE_NAME); instancePartitionsMap = getInstancePartitionsMap(); assertEquals(instancePartitionsMap.size(), 2); - assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.CONSUMING)); - assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.COMPLETED)); + assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.CONSUMING.toString())); + assertTrue(instancePartitionsMap.containsKey(InstancePartitionsType.COMPLETED.toString())); // Delete the real-time table _helixResourceManager.deleteRealtimeTable(RAW_TABLE_NAME); @@ -315,18 +367,16 @@ public void testInstanceAssignment() } } - private Map getInstancePartitionsMap() + private Map getInstancePartitionsMap() throws Exception { return deserializeInstancePartitionsMap( sendGetRequest(_controllerRequestURLBuilder.forInstancePartitions(RAW_TABLE_NAME, null))); } - private Map deserializeInstancePartitionsMap( - String instancePartitionsMapString) + private Map deserializeInstancePartitionsMap(String instancePartitionsMapString) throws Exception { - return JsonUtils.stringToObject(instancePartitionsMapString, - new TypeReference>() { - }); + return JsonUtils.stringToObject(instancePartitionsMapString, new TypeReference<>() { + }); } @AfterClass diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceRestletResourceTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceRestletResourceTest.java index 9b84c134ebb7..93a142f221da 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceRestletResourceTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceRestletResourceTest.java @@ -19,24 +19,34 @@ package org.apache.pinot.controller.api; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Lists; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.TreeMap; import javax.annotation.Nullable; +import org.apache.pinot.controller.api.resources.InstanceTagUpdateRequest; +import org.apache.pinot.controller.api.resources.OperationValidationResponse; import org.apache.pinot.controller.helix.ControllerTest; import org.apache.pinot.spi.config.instance.Instance; import org.apache.pinot.spi.config.instance.InstanceType; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.utils.CommonConstants.Helix; import org.apache.pinot.spi.utils.JsonUtils; import org.apache.pinot.spi.utils.builder.ControllerRequestURLBuilder; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertThrows; import static org.testng.Assert.assertTrue; @@ -47,22 +57,24 @@ */ public class PinotInstanceRestletResourceTest extends ControllerTest { + private ControllerRequestURLBuilder _urlBuilder = null; + @BeforeClass public void setUp() throws Exception { DEFAULT_INSTANCE.setupSharedStateAndValidate(); + _urlBuilder = DEFAULT_INSTANCE.getControllerRequestURLBuilder(); } @Test public void testInstanceListingAndCreation() throws Exception { - ControllerRequestURLBuilder requestURLBuilder = DEFAULT_INSTANCE.getControllerRequestURLBuilder(); - String listInstancesUrl = requestURLBuilder.forInstanceList(); + String listInstancesUrl = _urlBuilder.forInstanceList(); int expectedNumInstances = 1 + DEFAULT_NUM_BROKER_INSTANCES + DEFAULT_NUM_SERVER_INSTANCES; checkNumInstances(listInstancesUrl, expectedNumInstances); // Create untagged broker and server instances - String createInstanceUrl = requestURLBuilder.forInstanceCreate(); + String createInstanceUrl = _urlBuilder.forInstanceCreate(); Instance brokerInstance1 = new Instance("1.2.3.4", 1234, InstanceType.BROKER, null, null, 0, 0, 0, 0, false); sendPostRequest(createInstanceUrl, brokerInstance1.toJsonString()); Instance serverInstance1 = @@ -110,14 +122,14 @@ public void testInstanceListingAndCreation() new Instance("1.2.3.4", 1234, InstanceType.BROKER, Collections.singletonList(newBrokerTag), null, 0, 0, 0, 0, false); String brokerInstanceId = "Broker_1.2.3.4_1234"; - String brokerInstanceUrl = requestURLBuilder.forInstance(brokerInstanceId); + String brokerInstanceUrl = _urlBuilder.forInstance(brokerInstanceId); sendPutRequest(brokerInstanceUrl, newBrokerInstance.toJsonString()); String newServerTag = "new-server-tag"; Instance newServerInstance = new Instance("1.2.3.4", 2345, InstanceType.SERVER, Collections.singletonList(newServerTag), null, 28090, 28091, 28092, 28093, true); String serverInstanceId = "Server_1.2.3.4_2345"; - String serverInstanceUrl = requestURLBuilder.forInstance(serverInstanceId); + String serverInstanceUrl = _urlBuilder.forInstance(serverInstanceId); sendPutRequest(serverInstanceUrl, newServerInstance.toJsonString()); checkInstanceInfo(brokerInstanceId, "1.2.3.4", 1234, new String[]{newBrokerTag}, null, -1, -1, -1, -1, false); @@ -126,9 +138,9 @@ public void testInstanceListingAndCreation() // Test Instance updateTags API String brokerInstanceUpdateTagsUrl = - requestURLBuilder.forInstanceUpdateTags(brokerInstanceId, Lists.newArrayList("tag_BROKER", "newTag_BROKER")); + _urlBuilder.forInstanceUpdateTags(brokerInstanceId, Lists.newArrayList("tag_BROKER", "newTag_BROKER")); sendPutRequest(brokerInstanceUpdateTagsUrl); - String serverInstanceUpdateTagsUrl = requestURLBuilder.forInstanceUpdateTags(serverInstanceId, + String serverInstanceUpdateTagsUrl = _urlBuilder.forInstanceUpdateTags(serverInstanceId, Lists.newArrayList("tag_REALTIME", "newTag_OFFLINE", "newTag_REALTIME")); sendPutRequest(serverInstanceUpdateTagsUrl); checkInstanceInfo(brokerInstanceId, "1.2.3.4", 1234, new String[]{"tag_BROKER", "newTag_BROKER"}, null, -1, -1, -1, @@ -137,10 +149,10 @@ public void testInstanceListingAndCreation() new String[]{"tag_REALTIME", "newTag_OFFLINE", "newTag_REALTIME"}, null, 28090, 28091, 28092, 28093, true); // Test DELETE instance API - sendDeleteRequest(requestURLBuilder.forInstance("Broker_1.2.3.4_1234")); - sendDeleteRequest(requestURLBuilder.forInstance("Server_1.2.3.4_2345")); - sendDeleteRequest(requestURLBuilder.forInstance("Broker_2.3.4.5_1234")); - sendDeleteRequest(requestURLBuilder.forInstance("Server_2.3.4.5_2345")); + sendDeleteRequest(_urlBuilder.forInstance("Broker_1.2.3.4_1234")); + sendDeleteRequest(_urlBuilder.forInstance("Server_1.2.3.4_2345")); + sendDeleteRequest(_urlBuilder.forInstance("Broker_2.3.4.5_1234")); + sendDeleteRequest(_urlBuilder.forInstance("Server_2.3.4.5_2345")); checkNumInstances(listInstancesUrl, expectedNumInstances); } @@ -163,7 +175,7 @@ private void checkInstanceInfo(String instanceName, String hostName, int port, S boolean queriesDisabled) throws Exception { JsonNode response = JsonUtils.stringToJsonNode( - ControllerTest.sendGetRequest(DEFAULT_INSTANCE.getControllerRequestURLBuilder().forInstance(instanceName))); + ControllerTest.sendGetRequest(_urlBuilder.forInstance(instanceName))); assertEquals(response.get("instanceName").asText(), instanceName); assertEquals(response.get("hostName").asText(), hostName); assertTrue(response.get("enabled").asBoolean()); @@ -193,6 +205,96 @@ private void checkInstanceInfo(String instanceName, String hostName, int port, S } } + @Test + public void instanceRetagHappyPathTest() + throws IOException { + Map> currentInstanceTagsMap = getCurrentInstanceTagsMap(); + List request = new ArrayList<>(); + currentInstanceTagsMap.forEach((instance, tags) -> { + if (instance.startsWith(Helix.PREFIX_OF_SERVER_INSTANCE) + || instance.startsWith(Helix.PREFIX_OF_BROKER_INSTANCE)) { + InstanceTagUpdateRequest payload = new InstanceTagUpdateRequest(); + payload.setInstanceName(instance); + payload.setNewTags(tags); + request.add(payload); + } + }); + List response = Arrays.asList(new ObjectMapper().readValue( + sendPostRequest(_urlBuilder.forUpdateTagsValidation(), JsonUtils.objectToString(request)), + OperationValidationResponse[].class)); + assertNotNull(response); + response.forEach(item -> assertTrue(item.isSafe())); + } + + @Test + public void instanceRetagServerDeficiencyTest() + throws Exception { + String tableName = "testTable"; + DEFAULT_INSTANCE.addDummySchema(tableName); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(tableName) + .setNumReplicas(2).build(); + // create table with replication as 2 so that DefaultTenant has a minimum server requirement as 2. + DEFAULT_INSTANCE.addTableConfig(tableConfig); + Map> currentInstanceTagsMap = getCurrentInstanceTagsMap(); + List request = new ArrayList<>(); + currentInstanceTagsMap.forEach((instance, tags) -> { + if (instance.startsWith(Helix.PREFIX_OF_SERVER_INSTANCE) + || instance.startsWith(Helix.PREFIX_OF_BROKER_INSTANCE)) { + InstanceTagUpdateRequest payload = new InstanceTagUpdateRequest(); + payload.setInstanceName(instance); + payload.setNewTags(Lists.newArrayList()); + request.add(payload); + } + }); + List response = Arrays.asList(new ObjectMapper().readValue( + sendPostRequest(_urlBuilder.forUpdateTagsValidation(), JsonUtils.objectToString(request)), + OperationValidationResponse[].class)); + assertNotNull(response); + + int deficientServers = 2; + int deficientBrokers = 1; + for (OperationValidationResponse item : response) { + String instanceName = item.getInstanceName(); + boolean validity = item.isSafe(); + if (!validity) { + List issues = item.getIssues(); + assertEquals(issues.size(), 1); + assertEquals(issues.get(0).getCode(), OperationValidationResponse.ErrorCode.MINIMUM_INSTANCE_UNSATISFIED); + if (instanceName.startsWith(Helix.PREFIX_OF_SERVER_INSTANCE)) { + deficientServers--; + } else if (instanceName.startsWith(Helix.PREFIX_OF_BROKER_INSTANCE)) { + deficientBrokers--; + } + } + } + assertEquals(deficientServers, 0); + assertEquals(deficientBrokers, 0); + DEFAULT_INSTANCE.dropOfflineTable(tableName); + } + + private Map> getCurrentInstanceTagsMap() + throws IOException { + String listInstancesUrl = _urlBuilder.forInstanceList(); + JsonNode response = JsonUtils.stringToJsonNode(sendGetRequest(listInstancesUrl)); + JsonNode instances = response.get("instances"); + Map> map = new HashMap<>(instances.size()); + for (int i = 0; i < instances.size(); i++) { + String instance = instances.get(i).asText(); + map.put(instance, getInstanceTags(instance)); + } + return map; + } + + private List getInstanceTags(String instance) + throws IOException { + String getInstancesUrl = _urlBuilder.forInstance(instance); + List tags = new ArrayList<>(); + for (JsonNode tag : JsonUtils.stringToJsonNode(sendGetRequest(getInstancesUrl)).get("tags")) { + tags.add(tag.asText()); + } + return tags; + } + @AfterClass public void tearDown() throws Exception { diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotSegmentRestletResourceTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotSegmentRestletResourceTest.java index d09031603152..78a9923d999b 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotSegmentRestletResourceTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotSegmentRestletResourceTest.java @@ -20,15 +20,19 @@ import java.io.IOException; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; import org.apache.pinot.controller.helix.ControllerTest; +import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; import org.apache.pinot.controller.utils.SegmentMetadataMockUtils; import org.apache.pinot.segment.spi.SegmentMetadata; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.utils.JsonUtils; +import org.apache.pinot.spi.utils.builder.ControllerRequestURLBuilder; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.testng.annotations.AfterClass; @@ -36,14 +40,14 @@ import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertThrows; import static org.testng.Assert.assertTrue; public class PinotSegmentRestletResourceTest { private static final ControllerTest TEST_INSTANCE = ControllerTest.getInstance(); - private static final String TABLE_NAME = "pinotSegmentRestletResourceTestTable"; - private static final String TABLE_NAME_OFFLINE = TABLE_NAME + "_OFFLINE"; + private static final String TEST_RAW_OFFLINE_TABLE_NAME = "offlineTableName1"; @BeforeClass public void setUp() @@ -55,45 +59,43 @@ public void setUp() public void testListSegmentLineage() throws Exception { // Adding table + String rawTableName = "lineageTestTable"; + TEST_INSTANCE.addDummySchema(rawTableName); + String offlineTableName = TableNameBuilder.OFFLINE.tableNameWithType(rawTableName); TableConfig tableConfig = - new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).setNumReplicas(1).build(); - TEST_INSTANCE.getHelixResourceManager().addTable(tableConfig); - - // Wait for the table addition - while (!TEST_INSTANCE.getHelixResourceManager().hasOfflineTable(TABLE_NAME)) { - Thread.sleep(100); - } - - Map segmentMetadataTable = new HashMap<>(); + new TableConfigBuilder(TableType.OFFLINE).setTableName(rawTableName).setNumReplicas(1).build(); + PinotHelixResourceManager resourceManager = TEST_INSTANCE.getHelixResourceManager(); + resourceManager.addTable(tableConfig); // Upload Segments + Map segmentMetadataTable = new HashMap<>(); for (int i = 0; i < 4; i++) { - SegmentMetadata segmentMetadata = SegmentMetadataMockUtils.mockSegmentMetadata(TABLE_NAME, "s" + i); - TEST_INSTANCE.getHelixResourceManager() - .addNewSegment(TableNameBuilder.OFFLINE.tableNameWithType(TABLE_NAME), segmentMetadata, "downloadUrl"); + SegmentMetadata segmentMetadata = SegmentMetadataMockUtils.mockSegmentMetadata(rawTableName, "s" + i); + resourceManager.addNewSegment(offlineTableName, segmentMetadata, "downloadUrl"); segmentMetadataTable.put(segmentMetadata.getName(), segmentMetadata); } // There should be no segment lineage at this point. - String segmentLineageResponse = ControllerTest.sendGetRequest(TEST_INSTANCE.getControllerRequestURLBuilder() - .forListAllSegmentLineages(TABLE_NAME, TableType.OFFLINE.toString())); + ControllerRequestURLBuilder urlBuilder = TEST_INSTANCE.getControllerRequestURLBuilder(); + String segmentLineageResponse = + ControllerTest.sendGetRequest(urlBuilder.forListAllSegmentLineages(rawTableName, TableType.OFFLINE.name())); assertEquals(segmentLineageResponse, ""); // Now starts to replace segments. List segmentsFrom = Arrays.asList("s0", "s1"); - List segmentsTo = Arrays.asList("some_segment"); - String segmentLineageId = TEST_INSTANCE.getHelixResourceManager() - .startReplaceSegments(TABLE_NAME_OFFLINE, segmentsFrom, segmentsTo, false); + List segmentsTo = Collections.singletonList("some_segment"); + String segmentLineageId = resourceManager.startReplaceSegments(offlineTableName, segmentsFrom, segmentsTo, false, + null); // Replace more segments to add another entry to segment lineage. segmentsFrom = Arrays.asList("s2", "s3"); - segmentsTo = Arrays.asList("another_segment"); - String nextSegmentLineageId = TEST_INSTANCE.getHelixResourceManager() - .startReplaceSegments(TABLE_NAME_OFFLINE, segmentsFrom, segmentsTo, false); + segmentsTo = Collections.singletonList("another_segment"); + String nextSegmentLineageId = + resourceManager.startReplaceSegments(offlineTableName, segmentsFrom, segmentsTo, false, null); // There should now be two segment lineage entries resulting from the operations above. - segmentLineageResponse = ControllerTest.sendGetRequest(TEST_INSTANCE.getControllerRequestURLBuilder() - .forListAllSegmentLineages(TABLE_NAME, TableType.OFFLINE.toString())); + segmentLineageResponse = + ControllerTest.sendGetRequest(urlBuilder.forListAllSegmentLineages(rawTableName, TableType.OFFLINE.toString())); assertTrue(segmentLineageResponse.contains("\"state\":\"IN_PROGRESS\"")); assertTrue(segmentLineageResponse.contains("\"segmentsFrom\":[\"s0\",\"s1\"]")); assertTrue(segmentLineageResponse.contains("\"segmentsTo\":[\"some_segment\"]")); @@ -103,93 +105,146 @@ public void testListSegmentLineage() assertTrue(segmentLineageResponse.indexOf(segmentLineageId) < segmentLineageResponse.indexOf(nextSegmentLineageId)); // List segment lineage should fail for non-existing table - assertThrows(IOException.class, () -> ControllerTest.sendGetRequest(TEST_INSTANCE.getControllerRequestURLBuilder() - .forListAllSegmentLineages("non-existing-table", TableType.OFFLINE.toString()))); - - // List segment lineage should also fail for invalid table type. assertThrows(IOException.class, () -> ControllerTest.sendGetRequest( - TEST_INSTANCE.getControllerRequestURLBuilder().forListAllSegmentLineages(TABLE_NAME, "invalid-type"))); + urlBuilder.forListAllSegmentLineages("non-existing-table", TableType.OFFLINE.toString()))); - // Delete segments - TEST_INSTANCE.getHelixResourceManager().deleteSegment(TableNameBuilder.OFFLINE.tableNameWithType(TABLE_NAME), - segmentMetadataTable.values().iterator().next().getName()); - - // Delete offline table - TEST_INSTANCE.getHelixResourceManager().deleteOfflineTable(TABLE_NAME); + // List segment lineage should also fail for invalid table type. + assertThrows(IOException.class, + () -> ControllerTest.sendGetRequest(urlBuilder.forListAllSegmentLineages(rawTableName, "invalid-type"))); } @Test public void testSegmentCrcApi() throws Exception { // Adding table + String rawTableName = "crcTestTable"; + TEST_INSTANCE.addDummySchema(rawTableName); + String offlineTableName = TableNameBuilder.OFFLINE.tableNameWithType(rawTableName); TableConfig tableConfig = - new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).setNumReplicas(1).build(); - TEST_INSTANCE.getHelixResourceManager().addTable(tableConfig); - - // Wait for the table addition - while (!TEST_INSTANCE.getHelixResourceManager().hasOfflineTable(TABLE_NAME)) { - Thread.sleep(100); - } + new TableConfigBuilder(TableType.OFFLINE).setTableName(rawTableName).setNumReplicas(1).build(); + PinotHelixResourceManager resourceManager = TEST_INSTANCE.getHelixResourceManager(); + resourceManager.addTable(tableConfig); // Check when there is no segment. Map segmentMetadataTable = new HashMap<>(); - checkCrcRequest(segmentMetadataTable, 0); + checkCrcRequest(rawTableName, segmentMetadataTable, 0); // Upload Segments for (int i = 0; i < 5; i++) { - SegmentMetadata segmentMetadata = SegmentMetadataMockUtils.mockSegmentMetadata(TABLE_NAME); - TEST_INSTANCE.getHelixResourceManager() - .addNewSegment(TableNameBuilder.OFFLINE.tableNameWithType(TABLE_NAME), segmentMetadata, "downloadUrl"); + SegmentMetadata segmentMetadata = SegmentMetadataMockUtils.mockSegmentMetadata(rawTableName); + resourceManager.addNewSegment(offlineTableName, segmentMetadata, "downloadUrl"); segmentMetadataTable.put(segmentMetadata.getName(), segmentMetadata); } // Get crc info from API and check that they are correct. - checkCrcRequest(segmentMetadataTable, 5); + checkCrcRequest(rawTableName, segmentMetadataTable, 5); // validate the segment metadata - Map.Entry entry = - (Map.Entry) segmentMetadataTable.entrySet().toArray()[0]; + String sampleSegment = segmentMetadataTable.keySet().iterator().next(); String resp = ControllerTest.sendGetRequest( - TEST_INSTANCE.getControllerRequestURLBuilder().forSegmentMetadata(TABLE_NAME, entry.getKey())); + TEST_INSTANCE.getControllerRequestURLBuilder().forSegmentMetadata(rawTableName, sampleSegment)); Map fetchedMetadata = JsonUtils.stringToObject(resp, Map.class); assertEquals(fetchedMetadata.get("segment.download.url"), "downloadUrl"); // use table name with table type resp = ControllerTest.sendGetRequest( - TEST_INSTANCE.getControllerRequestURLBuilder().forSegmentMetadata(TABLE_NAME + "_OFFLINE", entry.getKey())); + TEST_INSTANCE.getControllerRequestURLBuilder().forSegmentMetadata(offlineTableName, sampleSegment)); fetchedMetadata = JsonUtils.stringToObject(resp, Map.class); assertEquals(fetchedMetadata.get("segment.download.url"), "downloadUrl"); // Add more segments for (int i = 0; i < 5; i++) { - SegmentMetadata segmentMetadata = SegmentMetadataMockUtils.mockSegmentMetadata(TABLE_NAME); - TEST_INSTANCE.getHelixResourceManager() - .addNewSegment(TableNameBuilder.OFFLINE.tableNameWithType(TABLE_NAME), segmentMetadata, "downloadUrl"); + SegmentMetadata segmentMetadata = SegmentMetadataMockUtils.mockSegmentMetadata(rawTableName); + resourceManager.addNewSegment(offlineTableName, segmentMetadata, "downloadUrl"); segmentMetadataTable.put(segmentMetadata.getName(), segmentMetadata); } // Get crc info from API and check that they are correct. - checkCrcRequest(segmentMetadataTable, 10); + checkCrcRequest(rawTableName, segmentMetadataTable, 10); - // Delete segments - TEST_INSTANCE.getHelixResourceManager().deleteSegment(TableNameBuilder.OFFLINE.tableNameWithType(TABLE_NAME), - segmentMetadataTable.values().iterator().next().getName()); + // Delete one segment + resourceManager.deleteSegment(offlineTableName, sampleSegment); // Check crc api - checkCrcRequest(segmentMetadataTable, 9); + checkCrcRequest(rawTableName, segmentMetadataTable, 9); + } + + @Test + public void testDeleteSegmentsWithTimeWindow() + throws Exception { + // Adding table and segment + String rawTableName = "deleteWithTimeWindowTestTable"; + TEST_INSTANCE.addDummySchema(rawTableName); + String offlineTableName = TableNameBuilder.OFFLINE.tableNameWithType(rawTableName); + TableConfig tableConfig = + new TableConfigBuilder(TableType.OFFLINE).setTableName(rawTableName).setNumReplicas(1) + .setDeletedSegmentsRetentionPeriod("0d").build(); + PinotHelixResourceManager resourceManager = TEST_INSTANCE.getHelixResourceManager(); + resourceManager.addTable(tableConfig); + SegmentMetadata segmentMetadata = SegmentMetadataMockUtils.mockSegmentMetadata(rawTableName, + 10L, 20L, TimeUnit.MILLISECONDS); + resourceManager.addNewSegment(offlineTableName, segmentMetadata, "downloadUrl"); + + // Send query and verify + ControllerRequestURLBuilder urlBuilder = TEST_INSTANCE.getControllerRequestURLBuilder(); + // case 1: no overlapping + String reply = ControllerTest.sendDeleteRequest(urlBuilder.forSegmentDeleteWithTimeWindowAPI( + rawTableName, 0L, 10L)); + assertTrue(reply.contains("Deleted 0 segments")); + + // case 2: partial overlapping + reply = ControllerTest.sendDeleteRequest(urlBuilder.forSegmentDeleteWithTimeWindowAPI( + rawTableName, 10L, 20L)); + assertTrue(reply.contains("Deleted 0 segments")); + + // case 3: fully within the time window + reply = ControllerTest.sendDeleteRequest(urlBuilder.forSegmentDeleteWithTimeWindowAPI( + rawTableName, 10L, 21L)); + assertTrue(reply.contains("Deleted 1 segments")); + } + + @Test + public void testDeleteMultipleSegments() + throws Exception { + // Adding table and segment + TEST_INSTANCE.addDummySchema(TEST_RAW_OFFLINE_TABLE_NAME); + String offlineTableName = TableNameBuilder.OFFLINE.tableNameWithType(TEST_RAW_OFFLINE_TABLE_NAME); + TableConfig tableConfig = + new TableConfigBuilder(TableType.OFFLINE).setTableName(TEST_RAW_OFFLINE_TABLE_NAME).setNumReplicas(1) + .setDeletedSegmentsRetentionPeriod("0d").build(); + PinotHelixResourceManager resourceManager = TEST_INSTANCE.getHelixResourceManager(); + resourceManager.addTable(tableConfig); + SegmentMetadata segmentMetadata = SegmentMetadataMockUtils.mockSegmentMetadata(TEST_RAW_OFFLINE_TABLE_NAME, + "segment1"); + resourceManager.addNewSegment(offlineTableName, segmentMetadata, "downloadUrl"); + segmentMetadata = SegmentMetadataMockUtils.mockSegmentMetadata(TEST_RAW_OFFLINE_TABLE_NAME, + "segment2"); + resourceManager.addNewSegment(offlineTableName, segmentMetadata, "downloadUrl"); + segmentMetadata = SegmentMetadataMockUtils.mockSegmentMetadata(TEST_RAW_OFFLINE_TABLE_NAME, + "segment3"); + resourceManager.addNewSegment(offlineTableName, segmentMetadata, "downloadUrl"); + + // Send query and verify + ControllerRequestURLBuilder urlBuilder = TEST_INSTANCE.getControllerRequestURLBuilder(); + // case 1: send list of segments + String reply = ControllerTest.sendDeleteRequest(urlBuilder.forDeleteMultipleSegments( + TEST_RAW_OFFLINE_TABLE_NAME, TableType.OFFLINE.toString(), List.of("segment1"))); + assertTrue(reply.contains("Deleted segments: [segment1] from table: offlineTableName1_OFFLINE")); - // Delete offline table - TEST_INSTANCE.getHelixResourceManager().deleteOfflineTable(TABLE_NAME); + // case 2: delete all remaining segments + reply = ControllerTest.sendDeleteRequest(urlBuilder.forDeleteMultipleSegments( + TEST_RAW_OFFLINE_TABLE_NAME, TableType.OFFLINE.toString(), Collections.emptyList())); + assertTrue(reply.contains("All segments of table offlineTableName1_OFFLINE deleted")); } - private void checkCrcRequest(Map metadataTable, int expectedSize) + private void checkCrcRequest(String tableName, Map metadataTable, int expectedSize) throws Exception { String crcMapStr = ControllerTest.sendGetRequest( - TEST_INSTANCE.getControllerRequestURLBuilder().forListAllCrcInformationForTable(TABLE_NAME)); + TEST_INSTANCE.getControllerRequestURLBuilder().forListAllCrcInformationForTable(tableName)); Map crcMap = JsonUtils.stringToObject(crcMapStr, Map.class); for (String segmentName : crcMap.keySet()) { SegmentMetadata metadata = metadataTable.get(segmentName); - assertTrue(metadata != null); + assertNotNull(metadata); assertEquals(crcMap.get(segmentName), metadata.getCrc()); } assertEquals(crcMap.size(), expectedSize); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotSegmentsMetadataTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotSegmentsMetadataTest.java index 3c49ecc80fb3..e8b8d377faf9 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotSegmentsMetadataTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotSegmentsMetadataTest.java @@ -32,8 +32,8 @@ import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.Executors; -import org.apache.commons.httpclient.HttpConnectionManager; -import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; +import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; import org.apache.pinot.controller.util.ServerSegmentMetadataReader; import org.apache.pinot.controller.utils.FakeHttpServer; @@ -56,7 +56,7 @@ public class PinotSegmentsMetadataTest { private static final String URI_PATH = "/tables/"; private static final int TIMEOUT_MSEC = 10000; private final Executor _executor = Executors.newFixedThreadPool(1); - private final HttpConnectionManager _connectionManager = new MultiThreadedHttpConnectionManager(); + private final HttpClientConnectionManager _connectionManager = new PoolingHttpClientConnectionManager(); private final Map _serverMap = new HashMap<>(); private PinotHelixResourceManager _helix; diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotTableRestletResourceTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotTableRestletResourceTest.java index 8339e3025fe6..54644c26aa62 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotTableRestletResourceTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotTableRestletResourceTest.java @@ -28,7 +28,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.pinot.controller.api.resources.TableAndSchemaConfig; import org.apache.pinot.controller.helix.ControllerTest; import org.apache.pinot.controller.helix.core.minion.PinotTaskManager; import org.apache.pinot.controller.helix.core.rebalance.RebalanceResult; @@ -65,16 +64,16 @@ public class PinotTableRestletResourceTest extends ControllerTest { public void setUp() throws Exception { DEFAULT_INSTANCE.setupSharedStateAndValidate(); - _createTableUrl = DEFAULT_INSTANCE.getControllerRequestURLBuilder().forTableCreate(); _offlineBuilder.setTableName(OFFLINE_TABLE_NAME).setTimeColumnName("timeColumn").setTimeType("DAYS") .setRetentionTimeUnit("DAYS").setRetentionTimeValue("5"); // add schema for realtime table DEFAULT_INSTANCE.addDummySchema(REALTIME_TABLE_NAME); - StreamConfig streamConfig = FakeStreamConfigUtils.getDefaultHighLevelStreamConfigs(); + DEFAULT_INSTANCE.addDummySchema(OFFLINE_TABLE_NAME); + StreamConfig streamConfig = FakeStreamConfigUtils.getDefaultLowLevelStreamConfigs(); _realtimeBuilder.setTableName(REALTIME_TABLE_NAME).setTimeColumnName("timeColumn").setTimeType("DAYS") - .setRetentionTimeUnit("DAYS").setRetentionTimeValue("5").setSchemaName(REALTIME_TABLE_NAME) + .setRetentionTimeUnit("DAYS").setRetentionTimeValue("5") .setStreamConfigs(streamConfig.getStreamConfigsMap()); } @@ -105,7 +104,18 @@ public void testCreateTable() assertTrue(e.getMessage().contains("Got error status code: 400")); } + // Creating an OFFLINE table without a valid schema should fail + offlineTableConfig = _offlineBuilder.setTableName("no_schema").build(); + try { + sendPostRequest(_createTableUrl, offlineTableConfig.toJsonString()); + fail("Creation of a OFFLINE table without a valid schema does not fail"); + } catch (IOException e) { + // Expected 400 Bad Request + assertTrue(e.getMessage().contains("Got error status code: 400")); + } + // Create an OFFLINE table with a valid name which should succeed + DEFAULT_INSTANCE.addDummySchema("valid_table_name"); offlineTableConfig = _offlineBuilder.setTableName("valid_table_name").build(); String offlineTableConfigString = offlineTableConfig.toJsonString(); sendPostRequest(_createTableUrl, offlineTableConfigString); @@ -143,7 +153,7 @@ public void testCreateTable() } // Creating a REALTIME table without a valid schema should fail - realtimeTableConfig = _realtimeBuilder.setTableName("noSchema").setSchemaName("noSchema").build(); + realtimeTableConfig = _realtimeBuilder.setTableName("noSchema").build(); try { sendPostRequest(_createTableUrl, realtimeTableConfig.toJsonString()); fail("Creation of a REALTIME table without a valid schema does not fail"); @@ -152,10 +162,6 @@ public void testCreateTable() assertTrue(e.getMessage().contains("Got error status code: 400")); } - // Creating a REALTIME table with a different schema name in the config should succeed (backwards compatibility) - realtimeTableConfig = _realtimeBuilder.setSchemaName(REALTIME_TABLE_NAME).build(); - sendPostRequest(_createTableUrl, realtimeTableConfig.toJsonString()); - // Create a REALTIME table with the invalid time column name should fail realtimeTableConfig = _realtimeBuilder.setTableName(REALTIME_TABLE_NAME).setTimeColumnName("invalidTimeColumn").build(); @@ -174,8 +180,10 @@ public void testCreateTable() } @Test - public void testTableCronSchedule() { + public void testTableCronSchedule() + throws IOException { String rawTableName = "test_table_cron_schedule"; + DEFAULT_INSTANCE.addDummySchema(rawTableName); // Failed to create a table TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(rawTableName).setTaskConfig( new TableTaskConfig(ImmutableMap.of(MinionConstants.SegmentGenerationAndPushTask.TASK_TYPE, @@ -224,6 +232,7 @@ public void testTableMinReplication() private void testTableMinReplicationInternal(String tableName, int tableReplication) throws Exception { + DEFAULT_INSTANCE.addDummySchema(tableName); String tableJSONConfigString = _offlineBuilder.setTableName(tableName).setNumReplicas(tableReplication).build().toJsonString(); sendPostRequest(_createTableUrl, tableJSONConfigString); @@ -232,7 +241,6 @@ private void testTableMinReplicationInternal(String tableName, int tableReplicat assertEquals(tableConfig.getReplication(), Math.max(tableReplication, DEFAULT_MIN_NUM_REPLICAS)); - DEFAULT_INSTANCE.addDummySchema(tableName); tableJSONConfigString = _realtimeBuilder.setTableName(tableName).setNumReplicas(tableReplication).build().toJsonString(); sendPostRequest(_createTableUrl, tableJSONConfigString); @@ -299,6 +307,7 @@ private TableConfig getTableConfig(String tableName, String tableType) public void testUpdateTableConfig() throws Exception { String tableName = "updateTC"; + DEFAULT_INSTANCE.addDummySchema(tableName); String tableConfigString = _offlineBuilder.setTableName(tableName).setNumReplicas(2).build().toJsonString(); sendPostRequest(_createTableUrl, tableConfigString); // table creation should succeed @@ -365,12 +374,13 @@ public void testListTables() // post 2 offline, 1 realtime String rawTableName1 = "pqr"; + DEFAULT_INSTANCE.addDummySchema(rawTableName1); TableConfig offlineTableConfig1 = _offlineBuilder.setTableName(rawTableName1).build(); sendPostRequest(_createTableUrl, offlineTableConfig1.toJsonString()); - DEFAULT_INSTANCE.addDummySchema(rawTableName1); TableConfig realtimeTableConfig1 = _realtimeBuilder.setTableName(rawTableName1).setNumReplicas(2).build(); sendPostRequest(_createTableUrl, realtimeTableConfig1.toJsonString()); String rawTableName2 = "abc"; + DEFAULT_INSTANCE.addDummySchema(rawTableName2); TableConfig offlineTableConfig2 = _offlineBuilder.setTableName(rawTableName2).build(); sendPostRequest(_createTableUrl, offlineTableConfig2.toJsonString()); @@ -479,6 +489,7 @@ public void rebalanceTableWithoutSegments() public void testDeleteTable() throws IOException { // Case 1: Create a REALTIME table and delete it directly w/o using query param. + DEFAULT_INSTANCE.addDummySchema("table0"); TableConfig realtimeTableConfig = _realtimeBuilder.setTableName("table0").build(); String creationResponse = sendPostRequest(_createTableUrl, realtimeTableConfig.toJsonString()); assertEquals(creationResponse, @@ -501,6 +512,7 @@ public void testDeleteTable() assertEquals(deleteResponse, "{\"status\":\"Tables: [table0_OFFLINE] deleted\"}"); // Case 3: Create REALTIME and OFFLINE tables and delete both of them. + DEFAULT_INSTANCE.addDummySchema("table1"); TableConfig rtConfig1 = _realtimeBuilder.setTableName("table1").build(); creationResponse = sendPostRequest(_createTableUrl, rtConfig1.toJsonString()); assertEquals(creationResponse, @@ -516,6 +528,7 @@ public void testDeleteTable() assertEquals(deleteResponse, "{\"status\":\"Tables: [table1_OFFLINE, table1_REALTIME] deleted\"}"); // Case 4: Create REALTIME and OFFLINE tables and delete the realtime/offline table using query params. + DEFAULT_INSTANCE.addDummySchema("table2"); TableConfig rtConfig2 = _realtimeBuilder.setTableName("table2").build(); creationResponse = sendPostRequest(_createTableUrl, rtConfig2.toJsonString()); assertEquals(creationResponse, @@ -553,6 +566,7 @@ public void testDeleteTable() } // Case 6: Create REALTIME and OFFLINE tables and delete the realtime/offline table using query params and suffixes. + DEFAULT_INSTANCE.addDummySchema("table3"); TableConfig rtConfig3 = _realtimeBuilder.setTableName("table3").build(); creationResponse = sendPostRequest(_createTableUrl, rtConfig3.toJsonString()); assertEquals(creationResponse, @@ -577,13 +591,15 @@ public void testCheckTableState() throws IOException { // Create a valid REALTIME table - TableConfig realtimeTableConfig = _realtimeBuilder.setTableName("testTable").build(); + String tableName = "testTable"; + DEFAULT_INSTANCE.addDummySchema(tableName); + TableConfig realtimeTableConfig = _realtimeBuilder.setTableName(tableName).build(); String creationResponse = sendPostRequest(_createTableUrl, realtimeTableConfig.toJsonString()); assertEquals(creationResponse, "{\"unrecognizedProperties\":{},\"status\":\"Table testTable_REALTIME successfully added\"}"); // Create a valid OFFLINE table - TableConfig offlineTableConfig = _offlineBuilder.setTableName("testTable").build(); + TableConfig offlineTableConfig = _offlineBuilder.setTableName(tableName).build(); creationResponse = sendPostRequest(_createTableUrl, offlineTableConfig.toJsonString()); assertEquals(creationResponse, "{\"unrecognizedProperties\":{},\"status\":\"Table testTable_OFFLINE successfully added\"}"); @@ -655,59 +671,13 @@ public void testValidate() } } - @Test - public void testValidateTableAndSchema() - throws IOException { - String tableName = "verificationTestTableAndSchema"; - // Create a dummy schema - Schema schema = DEFAULT_INSTANCE.createDummySchema(tableName); - - // Create a valid OFFLINE table config - TableConfig offlineTableConfig = - _offlineBuilder.setTableName(tableName).setInvertedIndexColumns(Arrays.asList("dimA", "dimB")).build(); - TableAndSchemaConfig tableAndSchemaConfig = new TableAndSchemaConfig(offlineTableConfig, schema); - - try { - sendPostRequest( - StringUtil.join("/", DEFAULT_INSTANCE.getControllerBaseApiUrl(), "tables", "validateTableAndSchema"), - tableAndSchemaConfig.toJsonString()); - } catch (IOException e) { - fail("Valid table config and schema validation should succeed."); - } - - // Add a dummy schema to Pinot - DEFAULT_INSTANCE.addDummySchema(tableName); - tableAndSchemaConfig = new TableAndSchemaConfig(offlineTableConfig, null); - try { - sendPostRequest( - StringUtil.join("/", DEFAULT_INSTANCE.getControllerBaseApiUrl(), "tables", "validateTableAndSchema"), - tableAndSchemaConfig.toJsonString()); - } catch (IOException e) { - fail("Valid table config and existing schema validation should succeed."); - } - - // Create an invalid table config - offlineTableConfig = - _offlineBuilder.setTableName(tableName).setInvertedIndexColumns(Arrays.asList("invalidColA", "invalidColB")) - .build(); - tableAndSchemaConfig = new TableAndSchemaConfig(offlineTableConfig, schema); - try { - sendPostRequest( - StringUtil.join("/", DEFAULT_INSTANCE.getControllerBaseApiUrl(), "tables", "validateTableAndSchema"), - tableAndSchemaConfig.toJsonString()); - fail("Validation of an invalid table config and schema should fail."); - } catch (IOException e) { - // Expected - assertTrue(e.getMessage().contains("Got error status code: 400")); - } - } - @Test public void testUnrecognizedProperties() throws IOException { // Create an OFFLINE table with a valid name but with unrecognizedProperties which should succeed // Should have unrecognizedProperties set correctly String tableName = "valid_table_name_extra_props"; + DEFAULT_INSTANCE.addDummySchema(tableName); TableConfig offlineTableConfig = _realtimeBuilder.setTableName("valid_table_name_extra_props").build(); JsonNode jsonNode = JsonUtils.objectToJsonNode(offlineTableConfig); ((ObjectNode) jsonNode).put("illegalKey1", 1); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotTenantRestletResourceTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotTenantRestletResourceTest.java index cdd482fa97ad..34c06f73a830 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotTenantRestletResourceTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotTenantRestletResourceTest.java @@ -19,58 +19,207 @@ package org.apache.pinot.controller.api; import com.fasterxml.jackson.databind.JsonNode; +import java.io.IOException; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import org.apache.helix.HelixAdmin; import org.apache.pinot.common.utils.config.TagNameUtils; import org.apache.pinot.controller.helix.ControllerTest; +import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; +import org.apache.pinot.controller.utils.SegmentMetadataMockUtils; +import org.apache.pinot.spi.config.instance.Instance; +import org.apache.pinot.spi.config.instance.InstanceType; import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.JsonUtils; +import org.apache.pinot.spi.utils.builder.ControllerRequestURLBuilder; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; +import org.testng.collections.Sets; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; -public class PinotTenantRestletResourceTest { - private static final ControllerTest TEST_INSTANCE = ControllerTest.getInstance(); - private static final String TABLE_NAME = "restletTable_OFFLINE"; +public class PinotTenantRestletResourceTest extends ControllerTest { + private static final String RAW_TABLE_NAME = "testTale"; + private static final String OFFLINE_TABLE_NAME = TableNameBuilder.OFFLINE.tableNameWithType(RAW_TABLE_NAME); + + private ControllerRequestURLBuilder _urlBuilder; @BeforeClass public void setUp() throws Exception { - TEST_INSTANCE.setupSharedStateAndValidate(); + DEFAULT_INSTANCE.setupSharedStateAndValidate(); + _urlBuilder = DEFAULT_INSTANCE.getControllerRequestURLBuilder(); } @Test public void testTableListForTenant() throws Exception { - // Check that no tables on tenant works - String listTablesUrl = - TEST_INSTANCE.getControllerRequestURLBuilder().forTablesFromTenant(TagNameUtils.DEFAULT_TENANT_NAME); - JsonNode tableList = JsonUtils.stringToJsonNode(ControllerTest.sendGetRequest(listTablesUrl)); - assertEquals(tableList.get("tables").size(), 0); - - // Add a table - ControllerTest.sendPostRequest(TEST_INSTANCE.getControllerRequestURLBuilder().forTableCreate(), - new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build().toJsonString()); - - // There should be 1 table on the tenant - tableList = JsonUtils.stringToJsonNode(ControllerTest.sendGetRequest(listTablesUrl)); - JsonNode tables = tableList.get("tables"); + // Check that there is no existing tables + String listTablesUrl = _urlBuilder.forTablesFromTenant(TagNameUtils.DEFAULT_TENANT_NAME); + JsonNode listTablesResponse = JsonUtils.stringToJsonNode(ControllerTest.sendGetRequest(listTablesUrl)); + assertTrue(listTablesResponse.get("tables").isEmpty()); + + // Add 2 brokers with non-default broker tag + String createInstanceUrl = _urlBuilder.forInstanceCreate(); + String brokerTenant = "test"; + String brokerTag = TagNameUtils.getBrokerTagForTenant(brokerTenant); + Instance brokerInstance1 = + new Instance("1.2.3.4", 1234, InstanceType.BROKER, Collections.singletonList(brokerTag), null, 0, 0, 0, 0, + false); + Instance brokerInstance2 = + new Instance("2.3.4.5", 2345, InstanceType.BROKER, Collections.singletonList(brokerTag), null, 0, 0, 0, 0, + false); + sendPostRequest(createInstanceUrl, brokerInstance1.toJsonString()); + sendPostRequest(createInstanceUrl, brokerInstance2.toJsonString()); + + // Add a table to the default tenant + String createSchemaUrl = _urlBuilder.forSchemaCreate(); + ControllerTest.sendPostRequest(createSchemaUrl, createDummySchema(RAW_TABLE_NAME).toSingleLineJsonString()); + String createTableUrl = _urlBuilder.forTableCreate(); + ControllerTest.sendPostRequest(createTableUrl, + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build().toJsonString()); + + // Add a second table to the non-default tenant + String rawTableName2 = "testTable2"; + String offlineTableName2 = TableNameBuilder.OFFLINE.tableNameWithType(rawTableName2); + ControllerTest.sendPostRequest(createSchemaUrl, createDummySchema(rawTableName2).toSingleLineJsonString()); + ControllerTest.sendPostRequest(createTableUrl, + new TableConfigBuilder(TableType.OFFLINE).setTableName(rawTableName2).setBrokerTenant(brokerTenant).build() + .toJsonString()); + + // There should be 2 tables returned when querying default tenant for servers w/o specifying ?type=server + listTablesResponse = JsonUtils.stringToJsonNode(ControllerTest.sendGetRequest(listTablesUrl)); + JsonNode tables = listTablesResponse.get("tables"); + assertEquals(tables.size(), 2); + Set tableSet = new HashSet<>(); + tableSet.add(tables.get(0).asText()); + tableSet.add(tables.get(1).asText()); + assertEquals(tableSet, Sets.newHashSet(OFFLINE_TABLE_NAME, offlineTableName2)); + + // There should be 2 tables returned when specifying ?type=server as that is the default + listTablesUrl = _urlBuilder.forTablesFromTenant(TagNameUtils.DEFAULT_TENANT_NAME, "server"); + listTablesResponse = JsonUtils.stringToJsonNode(ControllerTest.sendGetRequest(listTablesUrl)); + tables = listTablesResponse.get("tables"); + assertEquals(tables.size(), 2); + tableSet = new HashSet<>(); + tableSet.add(tables.get(0).asText()); + tableSet.add(tables.get(1).asText()); + assertEquals(tableSet, Sets.newHashSet(OFFLINE_TABLE_NAME, offlineTableName2)); + + // There should be only 1 table returned when specifying ?type=broker for the default tenant + listTablesUrl = _urlBuilder.forTablesFromTenant(TagNameUtils.DEFAULT_TENANT_NAME, "broker"); + listTablesResponse = JsonUtils.stringToJsonNode(ControllerTest.sendGetRequest(listTablesUrl)); + tables = listTablesResponse.get("tables"); assertEquals(tables.size(), 1); + assertEquals(tables.get(0).asText(), OFFLINE_TABLE_NAME); + + // There should be only 1 table returned when specifying ?type=broker for the non-default tenant + listTablesUrl = _urlBuilder.forTablesFromTenant(brokerTenant, "broker"); + listTablesResponse = JsonUtils.stringToJsonNode(ControllerTest.sendGetRequest(listTablesUrl)); + tables = listTablesResponse.get("tables"); + assertEquals(tables.size(), 1); + assertEquals(tables.get(0).asText(), offlineTableName2); + + // Remove the tables and brokers + DEFAULT_INSTANCE.waitForEVToAppear(OFFLINE_TABLE_NAME); + DEFAULT_INSTANCE.waitForEVToAppear(offlineTableName2); + DEFAULT_INSTANCE.dropOfflineTable(RAW_TABLE_NAME); + DEFAULT_INSTANCE.deleteSchema(RAW_TABLE_NAME); + DEFAULT_INSTANCE.dropOfflineTable(rawTableName2); + DEFAULT_INSTANCE.deleteSchema(rawTableName2); + DEFAULT_INSTANCE.waitForEVToDisappear(OFFLINE_TABLE_NAME); + DEFAULT_INSTANCE.waitForEVToDisappear(offlineTableName2); + sendDeleteRequest(_urlBuilder.forInstance("Broker_1.2.3.4_1234")); + sendDeleteRequest(_urlBuilder.forInstance("Broker_2.3.4.5_2345")); + } + + @Test + public void testListInstance() + throws Exception { + String listInstancesUrl = _urlBuilder.forTenantGet(TagNameUtils.DEFAULT_TENANT_NAME); + JsonNode instanceList = JsonUtils.stringToJsonNode(ControllerTest.sendGetRequest(listInstancesUrl)); + assertEquals(instanceList.get("ServerInstances").size(), DEFAULT_NUM_SERVER_INSTANCES); + assertEquals(instanceList.get("BrokerInstances").size(), DEFAULT_NUM_BROKER_INSTANCES); + } + + @Test + public void testToggleTenantState() + throws Exception { + // Create an offline table + String createSchemaUrl = _urlBuilder.forSchemaCreate(); + ControllerTest.sendPostRequest(createSchemaUrl, createDummySchema(RAW_TABLE_NAME).toSingleLineJsonString()); + String createTableUrl = _urlBuilder.forTableCreate(); + sendPostRequest(createTableUrl, + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setNumReplicas(DEFAULT_MIN_NUM_REPLICAS) + .build().toJsonString()); + + // Broker resource should be updated + HelixAdmin helixAdmin = DEFAULT_INSTANCE.getHelixAdmin(); + String clusterName = DEFAULT_INSTANCE.getHelixClusterName(); + assertEquals(helixAdmin.getResourceIdealState(clusterName, CommonConstants.Helix.BROKER_RESOURCE_INSTANCE) + .getInstanceSet(OFFLINE_TABLE_NAME).size(), DEFAULT_NUM_BROKER_INSTANCES); + + // Add segments + PinotHelixResourceManager resourceManager = DEFAULT_INSTANCE.getHelixResourceManager(); + for (int i = 0; i < DEFAULT_NUM_SERVER_INSTANCES; i++) { + resourceManager.addNewSegment(OFFLINE_TABLE_NAME, SegmentMetadataMockUtils.mockSegmentMetadata(RAW_TABLE_NAME), + "downloadUrl"); + assertEquals(helixAdmin.getResourceIdealState(clusterName, OFFLINE_TABLE_NAME).getNumPartitions(), i + 1); + } + + // Disable server instances + String disableServerInstanceUrl = + _urlBuilder.forTenantInstancesToggle(TagNameUtils.DEFAULT_TENANT_NAME, "server", "disable"); + JsonUtils.stringToJsonNode(ControllerTest.sendPostRequest(disableServerInstanceUrl)); + checkNumOnlineInstancesFromExternalView(OFFLINE_TABLE_NAME, 0); + + // Enable server instances + String enableServerInstanceUrl = + _urlBuilder.forTenantInstancesToggle(TagNameUtils.DEFAULT_TENANT_NAME, "server", "enable"); + JsonUtils.stringToJsonNode(ControllerTest.sendPostRequest(enableServerInstanceUrl)); + checkNumOnlineInstancesFromExternalView(OFFLINE_TABLE_NAME, DEFAULT_NUM_SERVER_INSTANCES); + + // Disable broker instances + String disableBrokerInstanceUrl = + _urlBuilder.forTenantInstancesToggle(TagNameUtils.DEFAULT_TENANT_NAME, "broker", "disable"); + JsonUtils.stringToJsonNode(ControllerTest.sendPostRequest(disableBrokerInstanceUrl)); + checkNumOnlineInstancesFromExternalView(CommonConstants.Helix.BROKER_RESOURCE_INSTANCE, 0); + + // Enable broker instances + String enableBrokerInstanceUrl = + _urlBuilder.forTenantInstancesToggle(TagNameUtils.DEFAULT_TENANT_NAME, "broker", "enable"); + JsonUtils.stringToJsonNode(ControllerTest.sendPostRequest(enableBrokerInstanceUrl)); + checkNumOnlineInstancesFromExternalView(CommonConstants.Helix.BROKER_RESOURCE_INSTANCE, + DEFAULT_NUM_BROKER_INSTANCES); - // Check to make sure that test table exists. - boolean found = false; - for (int i = 0; !found && i < tables.size(); i++) { - found = tables.get(i).asText().equals(TABLE_NAME); + // Check exception in case of enum mismatch of State + try { + String mismatchStateBrokerInstanceUrl = + _urlBuilder.forTenantInstancesToggle(TagNameUtils.DEFAULT_TENANT_NAME, "broker", "random"); + sendPostRequest(mismatchStateBrokerInstanceUrl); + fail("Passing invalid state to tenant toggle state does not fail."); + } catch (IOException e) { + // Expected 500 Bad Request + assertTrue(e.getMessage().contains("Error: State mentioned random is wrong. Valid States: Enable, Disable")); } - assertTrue(found); + // Delete table and schema + DEFAULT_INSTANCE.dropOfflineTable(RAW_TABLE_NAME); + DEFAULT_INSTANCE.deleteSchema(RAW_TABLE_NAME); + DEFAULT_INSTANCE.waitForEVToDisappear(OFFLINE_TABLE_NAME); } @AfterClass - public void tearDown() { - TEST_INSTANCE.cleanup(); + public void tearDown() + throws IOException { + DEFAULT_INSTANCE.cleanup(); } } diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/SegmentCompletionProtocolDeserTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/SegmentCompletionProtocolDeserTest.java index 3420d069f866..b7ebe1805833 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/SegmentCompletionProtocolDeserTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/SegmentCompletionProtocolDeserTest.java @@ -26,7 +26,6 @@ import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; @@ -42,14 +41,13 @@ public void testCompleteResponseParams() { // Test with all params SegmentCompletionProtocol.Response.Params params = new SegmentCompletionProtocol.Response.Params().withBuildTimeSeconds(BUILD_TIME_MILLIS) - .withStreamPartitionMsgOffset(OFFSET.toString()).withSegmentLocation(SEGMENT_LOCATION).withSplitCommit(true) + .withStreamPartitionMsgOffset(OFFSET.toString()).withSegmentLocation(SEGMENT_LOCATION) .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.COMMIT); SegmentCompletionProtocol.Response response = new SegmentCompletionProtocol.Response(params); assertEquals(response.getBuildTimeSeconds(), BUILD_TIME_MILLIS); assertEquals(new LongMsgOffset(response.getStreamPartitionMsgOffset()).compareTo(OFFSET), 0); assertEquals(response.getSegmentLocation(), SEGMENT_LOCATION); - assertTrue(response.isSplitCommit()); assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT); } @@ -65,7 +63,6 @@ public void testIncompleteResponseParams() { assertEquals(response.getBuildTimeSeconds(), BUILD_TIME_MILLIS); assertEquals(new LongMsgOffset(response.getStreamPartitionMsgOffset()).compareTo(OFFSET), 0); assertNull(response.getSegmentLocation()); - assertFalse(response.isSplitCommit()); assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT); } @@ -74,14 +71,14 @@ public void testJsonResponseWithAllParams() { // Test with all params SegmentCompletionProtocol.Response.Params params = new SegmentCompletionProtocol.Response.Params().withBuildTimeSeconds(BUILD_TIME_MILLIS) - .withStreamPartitionMsgOffset(OFFSET.toString()).withSegmentLocation(SEGMENT_LOCATION).withSplitCommit(true) + .withStreamPartitionMsgOffset(OFFSET.toString()).withSegmentLocation(SEGMENT_LOCATION) .withControllerVipUrl(CONTROLLER_VIP_URL) .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.COMMIT); SegmentCompletionProtocol.Response response = new SegmentCompletionProtocol.Response(params); JsonNode jsonNode = JsonUtils.objectToJsonNode(response); - assertEquals(jsonNode.get("offset").asText(), OFFSET.toString()); + assertEquals(jsonNode.get("streamPartitionMsgOffset").asText(), OFFSET.toString()); assertEquals(jsonNode.get("segmentLocation").asText(), SEGMENT_LOCATION); assertTrue(jsonNode.get("isSplitCommitType").asBoolean()); assertEquals(jsonNode.get("status").asText(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT.toString()); @@ -92,32 +89,15 @@ public void testJsonResponseWithAllParams() { public void testJsonNullSegmentLocationAndVip() { SegmentCompletionProtocol.Response.Params params = new SegmentCompletionProtocol.Response.Params().withBuildTimeSeconds(BUILD_TIME_MILLIS) - .withStreamPartitionMsgOffset(OFFSET.toString()).withSplitCommit(false) - .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.COMMIT); - - SegmentCompletionProtocol.Response response = new SegmentCompletionProtocol.Response(params); - JsonNode jsonNode = JsonUtils.objectToJsonNode(response); - - assertEquals(jsonNode.get("offset").asText(), OFFSET.toString()); - assertNull(jsonNode.get("segmentLocation")); - assertFalse(jsonNode.get("isSplitCommitType").asBoolean()); - assertEquals(jsonNode.get("status").asText(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT.toString()); - assertNull(jsonNode.get("controllerVipUrl")); - } - - @Test - public void testJsonResponseWithoutSplitCommit() { - SegmentCompletionProtocol.Response.Params params = - new SegmentCompletionProtocol.Response.Params().withBuildTimeSeconds(BUILD_TIME_MILLIS) - .withStreamPartitionMsgOffset(OFFSET.toString()).withSplitCommit(false) + .withStreamPartitionMsgOffset(OFFSET.toString()) .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.COMMIT); SegmentCompletionProtocol.Response response = new SegmentCompletionProtocol.Response(params); JsonNode jsonNode = JsonUtils.objectToJsonNode(response); - assertEquals(jsonNode.get("offset").asText(), OFFSET.toString()); + assertEquals(jsonNode.get("streamPartitionMsgOffset").asText(), OFFSET.toString()); assertNull(jsonNode.get("segmentLocation")); - assertFalse(jsonNode.get("isSplitCommitType").asBoolean()); + assertTrue(jsonNode.get("isSplitCommitType").asBoolean()); assertEquals(jsonNode.get("status").asText(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT.toString()); assertNull(jsonNode.get("controllerVipUrl")); } @@ -129,14 +109,14 @@ public void testJsonResponseWithSegmentLocationNullVip() { SegmentCompletionProtocol.Response.Params params = new SegmentCompletionProtocol.Response.Params().withBuildTimeSeconds(BUILD_TIME_MILLIS) .withStreamPartitionMsgOffset(OFFSET.toString()).withSegmentLocation(SEGMENT_LOCATION) - .withSplitCommit(false).withStatus(SegmentCompletionProtocol.ControllerResponseStatus.COMMIT); + .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.COMMIT); SegmentCompletionProtocol.Response response = new SegmentCompletionProtocol.Response(params); JsonNode jsonNode = JsonUtils.objectToJsonNode(response); - assertEquals(jsonNode.get("offset").asText(), OFFSET.toString()); + assertEquals(jsonNode.get("streamPartitionMsgOffset").asText(), OFFSET.toString()); assertEquals(jsonNode.get("segmentLocation").asText(), SEGMENT_LOCATION); - assertFalse(jsonNode.get("isSplitCommitType").asBoolean()); + assertTrue(jsonNode.get("isSplitCommitType").asBoolean()); assertEquals(jsonNode.get("status").asText(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT.toString()); assertNull(jsonNode.get("controllerVipUrl")); } @@ -148,14 +128,14 @@ public void testJsonResponseWithVipAndNullSegmentLocation() { SegmentCompletionProtocol.Response.Params params = new SegmentCompletionProtocol.Response.Params().withBuildTimeSeconds(BUILD_TIME_MILLIS) .withStreamPartitionMsgOffset(OFFSET.toString()).withControllerVipUrl(CONTROLLER_VIP_URL) - .withSplitCommit(false).withStatus(SegmentCompletionProtocol.ControllerResponseStatus.COMMIT); + .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.COMMIT); SegmentCompletionProtocol.Response response = new SegmentCompletionProtocol.Response(params); JsonNode jsonNode = JsonUtils.objectToJsonNode(response); - assertEquals(jsonNode.get("offset").asText(), OFFSET.toString()); + assertEquals(jsonNode.get("streamPartitionMsgOffset").asText(), OFFSET.toString()); assertNull(jsonNode.get("segmentLocation")); - assertFalse(jsonNode.get("isSplitCommitType").asBoolean()); + assertTrue(jsonNode.get("isSplitCommitType").asBoolean()); assertEquals(jsonNode.get("status").asText(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT.toString()); assertEquals(jsonNode.get("controllerVipUrl").asText(), CONTROLLER_VIP_URL); } diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/SegmentCompletionUtilsTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/SegmentCompletionUtilsTest.java deleted file mode 100644 index 06f9cd43c015..000000000000 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/SegmentCompletionUtilsTest.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.controller.api; - -import org.apache.pinot.controller.util.SegmentCompletionUtils; -import org.testng.annotations.Test; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; - - -public class SegmentCompletionUtilsTest { - - @Test - public void testGenerateSegmentFilePrefix() { - String segmentName = "segment"; - assertEquals(SegmentCompletionUtils.getSegmentNamePrefix(segmentName), "segment.tmp."); - } - - @Test - public void testGenerateSegmentLocation() { - String segmentName = "segment"; - String segmentNamePrefix = SegmentCompletionUtils.getSegmentNamePrefix(segmentName); - assertTrue(SegmentCompletionUtils.generateSegmentFileName(segmentName).startsWith(segmentNamePrefix)); - } -} diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/ServerTableSizeReaderTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/ServerTableSizeReaderTest.java index f694738b90f6..36fdfe9edc22 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/ServerTableSizeReaderTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/ServerTableSizeReaderTest.java @@ -32,8 +32,7 @@ import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import org.apache.commons.httpclient.HttpConnectionManager; -import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.pinot.common.restlet.resources.SegmentSizeInfo; import org.apache.pinot.common.restlet.resources.TableSizeInfo; import org.apache.pinot.controller.api.resources.ServerTableSizeReader; @@ -58,7 +57,7 @@ public class ServerTableSizeReaderTest { private static final int SERVER_COUNT = 6; private final ExecutorService _executor = Executors.newFixedThreadPool(3); - private final HttpConnectionManager _httpConnectionManager = new MultiThreadedHttpConnectionManager(); + private final PoolingHttpClientConnectionManager _httpConnectionManager = new PoolingHttpClientConnectionManager(); private final List _servers = new ArrayList<>(); private final List _serverList = new ArrayList<>(); private final List _endpointList = new ArrayList<>(); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableConfigsRestletResourceTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableConfigsRestletResourceTest.java index a506f38ac320..18ae1b14a546 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableConfigsRestletResourceTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableConfigsRestletResourceTest.java @@ -67,8 +67,7 @@ private TableConfigBuilder getBaseTableConfigBuilder(String tableName, TableType } else { StreamConfig streamConfig = FakeStreamConfigUtils.getDefaultLowLevelStreamConfigs(); return new TableConfigBuilder(TableType.REALTIME).setTableName(tableName).setTimeColumnName("timeColumn") - .setRetentionTimeUnit("DAYS").setLLC(true).setRetentionTimeValue("5") - .setStreamConfigs(streamConfig.getStreamConfigsMap()); + .setRetentionTimeUnit("DAYS").setRetentionTimeValue("5").setStreamConfigs(streamConfig.getStreamConfigsMap()); } } @@ -485,6 +484,46 @@ public void testUpdateConfig() sendDeleteRequest(DEFAULT_INSTANCE.getControllerRequestURLBuilder().forTableConfigsDelete(tableName)); } + @Test + public void testForceUpdateTableSchemaAndConfigs() + throws IOException { + String tableName = "testUpdate1"; + TableConfig offlineTableConfig = createOfflineTableConfig(tableName); + Schema schema = createDummySchema(tableName); + TableConfigs tableConfigs = new TableConfigs(tableName, schema, offlineTableConfig, null); + + sendPostRequest(_createTableConfigsUrl, tableConfigs.toPrettyJsonString()); + String response = sendGetRequest(DEFAULT_INSTANCE.getControllerRequestURLBuilder().forTableConfigsGet(tableName)); + TableConfigs tableConfigsResponse = JsonUtils.stringToObject(response, TableConfigs.class); + Assert.assertNotNull(tableConfigs.getOffline()); + + // Remove field from schema and try to update schema without the 'forceTableSchemaUpdate' option + schema.removeField("dimA"); + tableConfigs = + new TableConfigs(tableName, schema, tableConfigsResponse.getOffline(), tableConfigsResponse.getRealtime()); + + String tableConfigUpdateUrl = DEFAULT_INSTANCE.getControllerRequestURLBuilder().forTableConfigsUpdate(tableName); + try { + sendPutRequest(tableConfigUpdateUrl, tableConfigs.toPrettyJsonString()); + } catch (IOException e) { + Assert.assertTrue(e.getMessage().contains("is not backward-compatible with the existing schema")); + } + + // Skip validate table configs – Exception is still thrown + String newTableConfigUpdateUrl = tableConfigUpdateUrl + "?validationTypesToSkip=ALL"; + try { + sendPutRequest(newTableConfigUpdateUrl, tableConfigs.toPrettyJsonString()); + } catch (IOException e) { + Assert.assertTrue(e.getMessage().contains("is not backward-compatible with the existing schema")); + } + + // Skip table config validation as well as force update the table schema – no exceptions are thrown + newTableConfigUpdateUrl = tableConfigUpdateUrl + "?validationTypesToSkip=ALL&forceTableSchemaUpdate=true"; + response = sendPutRequest(newTableConfigUpdateUrl, tableConfigs.toPrettyJsonString()); + Assert.assertTrue(response.contains("TableConfigs updated for testUpdate1")); + sendDeleteRequest(DEFAULT_INSTANCE.getControllerRequestURLBuilder().forTableConfigsDelete(tableName)); + } + @Test public void testDeleteConfig() throws Exception { @@ -561,14 +600,10 @@ public void testUnrecognizedProperties() @Test public void testGetConfigCompatibility() throws IOException { - // Should not fail if schema name does not match raw table name in the case they are created separately - String schemaName = "schema1"; - Schema schema = createDummySchema(schemaName); - sendPostRequest(DEFAULT_INSTANCE.getControllerRequestURLBuilder().forSchemaCreate(), schema.toPrettyJsonString()); String tableName = "table1"; + DEFAULT_INSTANCE.addDummySchema(tableName); TableConfig offlineTableConfig = createOfflineTableConfig(tableName); SegmentsValidationAndRetentionConfig validationConfig = new SegmentsValidationAndRetentionConfig(); - validationConfig.setSchemaName(schemaName); validationConfig.setReplication("1"); offlineTableConfig.setValidationConfig(validationConfig); sendPostRequest(DEFAULT_INSTANCE.getControllerRequestURLBuilder().forTableCreate(), @@ -578,11 +613,11 @@ public void testGetConfigCompatibility() TableConfigs tableConfigsResponse = JsonUtils.stringToObject(response, TableConfigs.class); Assert.assertEquals(tableConfigsResponse.getTableName(), tableName); Assert.assertEquals(tableConfigsResponse.getOffline().getTableName(), offlineTableConfig.getTableName()); - Assert.assertEquals(tableConfigsResponse.getSchema().getSchemaName(), schema.getSchemaName()); + Assert.assertEquals(tableConfigsResponse.getSchema().getSchemaName(), tableName); // Delete sendDeleteRequest(DEFAULT_INSTANCE.getControllerRequestURLBuilder().forTableDelete(tableName)); - sendDeleteRequest(DEFAULT_INSTANCE.getControllerRequestURLBuilder().forSchemaDelete(schemaName)); + sendDeleteRequest(DEFAULT_INSTANCE.getControllerRequestURLBuilder().forSchemaDelete(tableName)); } @AfterClass diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableSizeReaderTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableSizeReaderTest.java index a46fd912c54c..b39fb4d75f1d 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableSizeReaderTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableSizeReaderTest.java @@ -31,16 +31,18 @@ import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.Executors; -import org.apache.commons.httpclient.HttpConnectionManager; -import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; import org.apache.helix.AccessOption; import org.apache.helix.store.zk.ZkHelixPropertyStore; +import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.pinot.common.exception.InvalidConfigException; import org.apache.pinot.common.metrics.ControllerGauge; import org.apache.pinot.common.metrics.ControllerMetrics; +import org.apache.pinot.common.metrics.MetricValueUtils; import org.apache.pinot.common.restlet.resources.SegmentSizeInfo; import org.apache.pinot.common.restlet.resources.TableSizeInfo; import org.apache.pinot.common.utils.config.TableConfigUtils; +import org.apache.pinot.controller.LeadControllerManager; import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; import org.apache.pinot.controller.util.TableSizeReader; import org.apache.pinot.controller.utils.FakeHttpServer; @@ -62,10 +64,7 @@ import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; +import static org.testng.Assert.*; public class TableSizeReaderTest { @@ -76,35 +75,38 @@ public class TableSizeReaderTest { private static final int NUM_REPLICAS = 2; private final Executor _executor = Executors.newFixedThreadPool(1); - private final HttpConnectionManager _connectionManager = new MultiThreadedHttpConnectionManager(); + private final HttpClientConnectionManager _connectionManager = new PoolingHttpClientConnectionManager(); private final ControllerMetrics _controllerMetrics = new ControllerMetrics(PinotMetricUtils.getPinotMetricsRegistry()); private final Map _serverMap = new HashMap<>(); private PinotHelixResourceManager _helix; + private LeadControllerManager _leadControllerManager; @BeforeClass - public void setUp() - throws IOException { + public void setUp() throws IOException { _helix = mock(PinotHelixResourceManager.class); + _leadControllerManager = mock(LeadControllerManager.class); TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName("myTable").setNumReplicas(NUM_REPLICAS).build(); ZkHelixPropertyStore mockPropertyStore = mock(ZkHelixPropertyStore.class); - when(mockPropertyStore.get(ArgumentMatchers.anyString(), ArgumentMatchers.eq(null), - ArgumentMatchers.eq(AccessOption.PERSISTENT))).thenAnswer((Answer) invocationOnMock -> { - String path = (String) invocationOnMock.getArguments()[0]; - if (path.contains("realtime_REALTIME")) { - return TableConfigUtils.toZNRecord(tableConfig); - } - if (path.contains("offline_OFFLINE")) { - return TableConfigUtils.toZNRecord(tableConfig); - } - return null; - }); + when(mockPropertyStore + .get(ArgumentMatchers.anyString(), ArgumentMatchers.eq(null), ArgumentMatchers.eq(AccessOption.PERSISTENT))) + .thenAnswer((Answer) invocationOnMock -> { + String path = (String) invocationOnMock.getArguments()[0]; + if (path.contains("realtime_REALTIME")) { + return TableConfigUtils.toZNRecord(tableConfig); + } + if (path.contains("offline_OFFLINE")) { + return TableConfigUtils.toZNRecord(tableConfig); + } + return null; + }); when(_helix.getPropertyStore()).thenReturn(mockPropertyStore); when(_helix.getNumReplicas(ArgumentMatchers.eq(tableConfig))).thenReturn(NUM_REPLICAS); + when(_leadControllerManager.isLeaderForTable(anyString())).thenReturn(true); int counter = 0; // server0 @@ -154,8 +156,7 @@ public void tearDown() { private HttpHandler createHandler(final int status, final List segmentSizes, final int sleepTimeMs) { return new HttpHandler() { @Override - public void handle(HttpExchange httpExchange) - throws IOException { + public void handle(HttpExchange httpExchange) throws IOException { if (sleepTimeMs > 0) { try { Thread.sleep(sleepTimeMs); @@ -223,7 +224,8 @@ private BiMap serverEndpoints(String... servers) { @Test public void testNoSuchTable() throws InvalidConfigException { - TableSizeReader reader = new TableSizeReader(_executor, _connectionManager, _controllerMetrics, _helix); + TableSizeReader reader = + new TableSizeReader(_executor, _connectionManager, _controllerMetrics, _helix, _leadControllerManager); assertNull(reader.getTableSizeDetails("mytable", 5000)); } @@ -231,21 +233,20 @@ private TableSizeReader.TableSizeDetails testRunner(final String[] servers, Stri throws InvalidConfigException { when(_helix.getServerToSegmentsMap(anyString())).thenAnswer(new Answer() { @Override - public Object answer(InvocationOnMock invocationOnMock) - throws Throwable { + public Object answer(InvocationOnMock invocationOnMock) throws Throwable { return subsetOfServerSegments(servers); } }); when(_helix.getDataInstanceAdminEndpoints(ArgumentMatchers.anySet())).thenAnswer(new Answer() { @Override - public Object answer(InvocationOnMock invocationOnMock) - throws Throwable { + public Object answer(InvocationOnMock invocationOnMock) throws Throwable { return serverEndpoints(servers); } }); - TableSizeReader reader = new TableSizeReader(_executor, _connectionManager, _controllerMetrics, _helix); + TableSizeReader reader = new TableSizeReader(_executor, _connectionManager, _controllerMetrics, _helix, + _leadControllerManager); return reader.getTableSizeDetails(table, TIMEOUT_MSEC); } @@ -254,11 +255,7 @@ private Map> segmentToServers(final String... servers) { for (String server : servers) { List segments = _serverMap.get(server)._segments; for (String segment : segments) { - List segServers = segmentServers.get(segment); - if (segServers == null) { - segServers = new ArrayList(); - segmentServers.put(segment, segServers); - } + List segServers = segmentServers.computeIfAbsent(segment, k -> new ArrayList()); segServers.add(server); } } @@ -269,7 +266,7 @@ private void validateTableSubTypeSize(String[] servers, TableSizeReader.TableSub Map> segmentServers = segmentToServers(servers); long reportedSize = 0; long estimatedSize = 0; - long maxSegmentSize = 0; + long reportedSizePerReplica = 0L; boolean hasErrors = false; for (Map.Entry> segmentEntry : segmentServers.entrySet()) { final String segmentName = segmentEntry.getKey(); @@ -280,6 +277,9 @@ private void validateTableSubTypeSize(String[] servers, TableSizeReader.TableSub if (segmentDetails._estimatedSizeInBytes != TableSizeReader.DEFAULT_SIZE_WHEN_MISSING_OR_ERROR) { estimatedSize += segmentDetails._estimatedSizeInBytes; } + if (segmentDetails._maxReportedSizePerReplicaInBytes != TableSizeReader.DEFAULT_SIZE_WHEN_MISSING_OR_ERROR) { + reportedSizePerReplica += segmentDetails._maxReportedSizePerReplicaInBytes; + } assertNotNull(segmentDetails); final List expectedServers = segmentEntry.getValue(); @@ -295,13 +295,17 @@ private void validateTableSubTypeSize(String[] servers, TableSizeReader.TableSub if (numResponses != 0) { assertEquals(segmentDetails._reportedSizeInBytes, numResponses * expectedSegmentSize); assertEquals(segmentDetails._estimatedSizeInBytes, expectedServers.size() * expectedSegmentSize); + assertEquals(segmentDetails._maxReportedSizePerReplicaInBytes, expectedSegmentSize); } else { assertEquals(segmentDetails._reportedSizeInBytes, TableSizeReader.DEFAULT_SIZE_WHEN_MISSING_OR_ERROR); assertEquals(segmentDetails._estimatedSizeInBytes, TableSizeReader.DEFAULT_SIZE_WHEN_MISSING_OR_ERROR); + assertEquals(segmentDetails._maxReportedSizePerReplicaInBytes, + TableSizeReader.DEFAULT_SIZE_WHEN_MISSING_OR_ERROR); } } assertEquals(tableSize._reportedSizeInBytes, reportedSize); assertEquals(tableSize._estimatedSizeInBytes, estimatedSize); + assertEquals(tableSize._reportedSizePerReplicaInBytes, reportedSizePerReplica); if (hasErrors) { assertTrue(tableSize._reportedSizeInBytes != tableSize._estimatedSizeInBytes); assertTrue(tableSize._missingSegments > 0); @@ -309,8 +313,7 @@ private void validateTableSubTypeSize(String[] servers, TableSizeReader.TableSub } @Test - public void testGetTableSubTypeSizeAllSuccess() - throws InvalidConfigException { + public void testGetTableSubTypeSizeAllSuccess() throws InvalidConfigException { final String[] servers = {"server0", "server1"}; String table = "offline"; TableSizeReader.TableSizeDetails tableSizeDetails = testRunner(servers, table); @@ -322,22 +325,24 @@ public void testGetTableSubTypeSizeAllSuccess() assertNull(tableSizeDetails._realtimeSegments); assertEquals(tableSizeDetails._reportedSizeInBytes, offlineSizes._reportedSizeInBytes); assertEquals(tableSizeDetails._estimatedSizeInBytes, offlineSizes._estimatedSizeInBytes); + assertEquals(tableSizeDetails._reportedSizePerReplicaInBytes, offlineSizes._reportedSizePerReplicaInBytes); String tableNameWithType = TableNameBuilder.OFFLINE.tableNameWithType(table); - assertEquals(_controllerMetrics.getValueOfTableGauge(tableNameWithType, + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, tableNameWithType, ControllerGauge.TABLE_STORAGE_EST_MISSING_SEGMENT_PERCENT), 0); - assertEquals( - _controllerMetrics.getValueOfTableGauge(tableNameWithType, ControllerGauge.TABLE_SIZE_PER_REPLICA_ON_SERVER), + assertEquals(MetricValueUtils + .getTableGaugeValue(_controllerMetrics, tableNameWithType, + ControllerGauge.TABLE_SIZE_PER_REPLICA_ON_SERVER), offlineSizes._estimatedSizeInBytes / NUM_REPLICAS); - assertEquals(_controllerMetrics.getValueOfTableGauge(tableNameWithType, ControllerGauge.TABLE_TOTAL_SIZE_ON_SERVER), + assertEquals(MetricValueUtils + .getTableGaugeValue(_controllerMetrics, tableNameWithType, ControllerGauge.TABLE_TOTAL_SIZE_ON_SERVER), offlineSizes._estimatedSizeInBytes); - assertEquals( - _controllerMetrics.getValueOfTableGauge(tableNameWithType, ControllerGauge.LARGEST_SEGMENT_SIZE_ON_SERVER), + assertEquals(MetricValueUtils + .getTableGaugeValue(_controllerMetrics, tableNameWithType, ControllerGauge.LARGEST_SEGMENT_SIZE_ON_SERVER), 160); } @Test - public void testGetTableSubTypeSizeAllErrors() - throws InvalidConfigException { + public void testGetTableSubTypeSizeAllErrors() throws InvalidConfigException { final String[] servers = {"server2", "server5"}; String table = "offline"; TableSizeReader.TableSizeDetails tableSizeDetails = testRunner(servers, table); @@ -347,22 +352,23 @@ public void testGetTableSubTypeSizeAllErrors() assertEquals(offlineSizes._segments.size(), 3); assertEquals(offlineSizes._reportedSizeInBytes, TableSizeReader.DEFAULT_SIZE_WHEN_MISSING_OR_ERROR); assertEquals(tableSizeDetails._estimatedSizeInBytes, TableSizeReader.DEFAULT_SIZE_WHEN_MISSING_OR_ERROR); + assertEquals(tableSizeDetails._reportedSizePerReplicaInBytes, TableSizeReader.DEFAULT_SIZE_WHEN_MISSING_OR_ERROR); String tableNameWithType = TableNameBuilder.OFFLINE.tableNameWithType(table); - assertEquals(_controllerMetrics.getValueOfTableGauge(tableNameWithType, + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, tableNameWithType, ControllerGauge.TABLE_STORAGE_EST_MISSING_SEGMENT_PERCENT), 100); - assertEquals( - _controllerMetrics.getValueOfTableGauge(tableNameWithType, ControllerGauge.TABLE_SIZE_PER_REPLICA_ON_SERVER), + assertEquals(MetricValueUtils + .getTableGaugeValue(_controllerMetrics, tableNameWithType, + ControllerGauge.TABLE_SIZE_PER_REPLICA_ON_SERVER), offlineSizes._estimatedSizeInBytes / NUM_REPLICAS); - assertEquals(_controllerMetrics.getValueOfTableGauge(tableNameWithType, ControllerGauge.TABLE_TOTAL_SIZE_ON_SERVER), + assertEquals(MetricValueUtils + .getTableGaugeValue(_controllerMetrics, tableNameWithType, ControllerGauge.TABLE_TOTAL_SIZE_ON_SERVER), offlineSizes._estimatedSizeInBytes); - // 0 means not found for the gauge - assertEquals( - _controllerMetrics.getValueOfTableGauge(tableNameWithType, ControllerGauge.LARGEST_SEGMENT_SIZE_ON_SERVER), 0); + assertFalse(MetricValueUtils + .tableGaugeExists(_controllerMetrics, tableNameWithType, ControllerGauge.LARGEST_SEGMENT_SIZE_ON_SERVER)); } @Test - public void testGetTableSubTypeSizesWithErrors() - throws InvalidConfigException { + public void testGetTableSubTypeSizesWithErrors() throws InvalidConfigException { final String[] servers = {"server0", "server1", "server2", "server5"}; String table = "offline"; TableSizeReader.TableSizeDetails tableSizeDetails = testRunner(servers, "offline"); @@ -373,21 +379,25 @@ public void testGetTableSubTypeSizesWithErrors() validateTableSubTypeSize(servers, offlineSizes); assertNull(tableSizeDetails._realtimeSegments); String tableNameWithType = TableNameBuilder.OFFLINE.tableNameWithType(table); - assertEquals(_controllerMetrics.getValueOfTableGauge(tableNameWithType, + assertEquals(tableSizeDetails._reportedSizeInBytes, offlineSizes._reportedSizeInBytes); + assertEquals(tableSizeDetails._estimatedSizeInBytes, offlineSizes._estimatedSizeInBytes); + assertEquals(tableSizeDetails._reportedSizePerReplicaInBytes, offlineSizes._reportedSizePerReplicaInBytes); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, tableNameWithType, ControllerGauge.TABLE_STORAGE_EST_MISSING_SEGMENT_PERCENT), 20); - assertEquals( - _controllerMetrics.getValueOfTableGauge(tableNameWithType, ControllerGauge.TABLE_SIZE_PER_REPLICA_ON_SERVER), + assertEquals(MetricValueUtils + .getTableGaugeValue(_controllerMetrics, tableNameWithType, + ControllerGauge.TABLE_SIZE_PER_REPLICA_ON_SERVER), offlineSizes._estimatedSizeInBytes / NUM_REPLICAS); - assertEquals(_controllerMetrics.getValueOfTableGauge(tableNameWithType, ControllerGauge.TABLE_TOTAL_SIZE_ON_SERVER), + assertEquals(MetricValueUtils + .getTableGaugeValue(_controllerMetrics, tableNameWithType, ControllerGauge.TABLE_TOTAL_SIZE_ON_SERVER), offlineSizes._estimatedSizeInBytes); - assertEquals( - _controllerMetrics.getValueOfTableGauge(tableNameWithType, ControllerGauge.LARGEST_SEGMENT_SIZE_ON_SERVER), + assertEquals(MetricValueUtils + .getTableGaugeValue(_controllerMetrics, tableNameWithType, ControllerGauge.LARGEST_SEGMENT_SIZE_ON_SERVER), 160); } @Test - public void getTableSizeDetailsRealtimeOnly() - throws InvalidConfigException { + public void getTableSizeDetailsRealtimeOnly() throws InvalidConfigException { final String[] servers = {"server3", "server4"}; String table = "realtime"; TableSizeReader.TableSizeDetails tableSizeDetails = testRunner(servers, table); @@ -396,14 +406,19 @@ public void getTableSizeDetailsRealtimeOnly() assertEquals(realtimeSegments._segments.size(), 2); assertEquals(realtimeSegments._estimatedSizeInBytes, realtimeSegments._reportedSizeInBytes); validateTableSubTypeSize(servers, realtimeSegments); + assertEquals(tableSizeDetails._reportedSizeInBytes, realtimeSegments._reportedSizeInBytes); + assertEquals(tableSizeDetails._estimatedSizeInBytes, realtimeSegments._estimatedSizeInBytes); + assertEquals(tableSizeDetails._reportedSizePerReplicaInBytes, realtimeSegments._reportedSizePerReplicaInBytes); String tableNameWithType = TableNameBuilder.REALTIME.tableNameWithType(table); - assertEquals( - _controllerMetrics.getValueOfTableGauge(tableNameWithType, ControllerGauge.TABLE_SIZE_PER_REPLICA_ON_SERVER), + assertEquals(MetricValueUtils + .getTableGaugeValue(_controllerMetrics, tableNameWithType, + ControllerGauge.TABLE_SIZE_PER_REPLICA_ON_SERVER), realtimeSegments._estimatedSizeInBytes / NUM_REPLICAS); - assertEquals(_controllerMetrics.getValueOfTableGauge(tableNameWithType, ControllerGauge.TABLE_TOTAL_SIZE_ON_SERVER), + assertEquals(MetricValueUtils + .getTableGaugeValue(_controllerMetrics, tableNameWithType, ControllerGauge.TABLE_TOTAL_SIZE_ON_SERVER), realtimeSegments._estimatedSizeInBytes); - assertEquals( - _controllerMetrics.getValueOfTableGauge(tableNameWithType, ControllerGauge.LARGEST_SEGMENT_SIZE_ON_SERVER), + assertEquals(MetricValueUtils + .getTableGaugeValue(_controllerMetrics, tableNameWithType, ControllerGauge.LARGEST_SEGMENT_SIZE_ON_SERVER), 120); } } diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableTierReaderTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableTierReaderTest.java index 140f57bf2193..00869b182f03 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableTierReaderTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableTierReaderTest.java @@ -33,8 +33,8 @@ import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Executors; -import org.apache.commons.httpclient.HttpConnectionManager; -import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; +import org.apache.http.conn.HttpClientConnectionManager; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.pinot.common.exception.InvalidConfigException; import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; import org.apache.pinot.common.restlet.resources.TableTierInfo; @@ -63,7 +63,7 @@ public class TableTierReaderTest { private static final int EXTENDED_TIMEOUT_FACTOR = 100; private final Executor _executor = Executors.newFixedThreadPool(1); - private final HttpConnectionManager _connectionManager = new MultiThreadedHttpConnectionManager(); + private final HttpClientConnectionManager _connectionManager = new PoolingHttpClientConnectionManager(); private final Map _serverMap = new HashMap<>(); private PinotHelixResourceManager _helix; diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableViewsTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableViewsTest.java index 5e88e7f7c37e..639542eab17b 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableViewsTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/TableViewsTest.java @@ -18,7 +18,6 @@ */ package org.apache.pinot.controller.api; -import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; import java.util.Map; @@ -34,12 +33,16 @@ import org.apache.pinot.spi.utils.JsonUtils; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.apache.pinot.spi.utils.builder.TableNameBuilder; +import org.apache.pinot.util.TestUtils; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import static org.testng.Assert.*; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; public class TableViewsTest extends ControllerTest { @@ -53,6 +56,7 @@ public void setUp() DEFAULT_INSTANCE.setupSharedStateAndValidate(); // Create the offline table and add one segment + DEFAULT_INSTANCE.addDummySchema(OFFLINE_TABLE_NAME); TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(OFFLINE_TABLE_NAME).setNumReplicas(2).build(); assertEquals(DEFAULT_INSTANCE.getHelixManager().getInstanceType(), InstanceType.CONTROLLER); @@ -62,41 +66,30 @@ public void setUp() SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, OFFLINE_SEGMENT_NAME), "downloadUrl"); // Create the hybrid table + DEFAULT_INSTANCE.addDummySchema(HYBRID_TABLE_NAME); tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(HYBRID_TABLE_NAME) .setNumReplicas(DEFAULT_MIN_NUM_REPLICAS).build(); DEFAULT_INSTANCE.getHelixResourceManager().addTable(tableConfig); - - // add schema for realtime table - DEFAULT_INSTANCE.addDummySchema(HYBRID_TABLE_NAME); - StreamConfig streamConfig = FakeStreamConfigUtils.getDefaultHighLevelStreamConfigs(); + StreamConfig streamConfig = FakeStreamConfigUtils.getDefaultLowLevelStreamConfigs(4); tableConfig = new TableConfigBuilder(TableType.REALTIME).setTableName(HYBRID_TABLE_NAME) .setNumReplicas(DEFAULT_MIN_NUM_REPLICAS).setStreamConfigs(streamConfig.getStreamConfigsMap()).build(); DEFAULT_INSTANCE.getHelixResourceManager().addTable(tableConfig); // Wait for external view get updated - long endTime = System.currentTimeMillis() + 10_000L; - while (System.currentTimeMillis() < endTime) { - Thread.sleep(100L); - TableViews.TableView tableView; + TestUtils.waitForCondition(aVoid -> { try { - tableView = getTableView(OFFLINE_TABLE_NAME, TableViews.EXTERNALVIEW, null); - } catch (IOException e) { - // Table may not be created yet. - continue; - } - if ((tableView._offline == null) || (tableView._offline.size() != 1)) { - continue; + TableViews.TableView tableView = getTableView(OFFLINE_TABLE_NAME, TableViews.EXTERNALVIEW, null); + if (tableView._offline == null || tableView._offline.size() != 1) { + return false; + } + tableView = getTableView(HYBRID_TABLE_NAME, TableViews.EXTERNALVIEW, null); + return tableView._offline != null && tableView._realtime != null + && tableView._realtime.size() == DEFAULT_NUM_SERVER_INSTANCES; + } catch (Exception e) { + // Expected before external view is created + return false; } - tableView = getTableView(HYBRID_TABLE_NAME, TableViews.EXTERNALVIEW, null); - if (tableView._offline == null) { - continue; - } - if ((tableView._realtime == null) || (tableView._realtime.size() != DEFAULT_NUM_SERVER_INSTANCES)) { - continue; - } - return; - } - fail("Failed to get external view updated"); + }, 10_000L, "Failed to get external view updated"); } @DataProvider(name = "viewProvider") diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/access/AccessControlUtilsTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/access/AccessControlUtilsTest.java new file mode 100644 index 000000000000..5270414d34f4 --- /dev/null +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/access/AccessControlUtilsTest.java @@ -0,0 +1,75 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.controller.api.access; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; +import org.apache.pinot.controller.api.exception.ControllerApplicationException; +import org.mockito.Mockito; +import org.testng.Assert; +import org.testng.annotations.Test; + + +public class AccessControlUtilsTest { + + private final String _table = "testTable"; + private final String _endpoint = "/testEndpoint"; + + @Test + public void testValidatePermissionAllowed() { + AccessControl ac = Mockito.mock(AccessControl.class); + HttpHeaders mockHttpHeaders = Mockito.mock(HttpHeaders.class); + + Mockito.when(ac.hasAccess(_table, AccessType.READ, mockHttpHeaders, _endpoint)).thenReturn(true); + + AccessControlUtils.validatePermission(_table, AccessType.READ, mockHttpHeaders, _endpoint, ac); + } + + @Test + public void testValidatePermissionDenied() { + AccessControl ac = Mockito.mock(AccessControl.class); + HttpHeaders mockHttpHeaders = Mockito.mock(HttpHeaders.class); + + Mockito.when(ac.hasAccess(_table, AccessType.READ, mockHttpHeaders, _endpoint)).thenReturn(false); + + try { + AccessControlUtils.validatePermission(_table, AccessType.READ, mockHttpHeaders, _endpoint, ac); + Assert.fail("Expected ControllerApplicationException"); + } catch (ControllerApplicationException e) { + Assert.assertTrue(e.getMessage().contains("Permission is denied")); + Assert.assertEquals(e.getResponse().getStatus(), Response.Status.FORBIDDEN.getStatusCode()); + } + } + + @Test + public void testValidatePermissionWithNoSuchMethodError() { + AccessControl ac = Mockito.mock(AccessControl.class); + HttpHeaders mockHttpHeaders = Mockito.mock(HttpHeaders.class); + + Mockito.when(ac.hasAccess(_table, AccessType.READ, mockHttpHeaders, _endpoint)) + .thenThrow(new NoSuchMethodError("Method not found")); + + try { + AccessControlUtils.validatePermission(_table, AccessType.READ, mockHttpHeaders, _endpoint, ac); + } catch (ControllerApplicationException e) { + Assert.assertTrue(e.getMessage().contains("Caught exception while validating permission")); + Assert.assertEquals(e.getResponse().getStatus(), Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } + } +} diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotDatabaseRestletResourceTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotDatabaseRestletResourceTest.java new file mode 100644 index 000000000000..877c65f96b66 --- /dev/null +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotDatabaseRestletResourceTest.java @@ -0,0 +1,102 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.controller.api.resources; + +import com.google.common.collect.Lists; +import java.util.ArrayList; +import java.util.List; +import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + + +public class PinotDatabaseRestletResourceTest { + private static final String DATABASE = "db"; + private static final List TABLES = Lists.newArrayList("a_REALTIME", "b_OFFLINE", "c_REALTIME", "d_OFFLINE"); + + @Mock + PinotHelixResourceManager _resourceManager; + @InjectMocks + PinotDatabaseRestletResource _resource; + + @BeforeMethod + public void setup() { + MockitoAnnotations.openMocks(this); + when(_resourceManager.getAllTables(DATABASE)).thenReturn(TABLES); + doNothing().when(_resourceManager).deleteTable(anyString(), any(TableType.class), any()); + when(_resourceManager.deleteSchema(anyString())).thenReturn(true); + } + + @Test + public void successfulDatabaseDeletionDryRunTest() { + successfulDatabaseDeletionCheck(true); + } + + @Test + public void successfulDatabaseDeletionTest() { + successfulDatabaseDeletionCheck(false); + } + + private void successfulDatabaseDeletionCheck(boolean dryRun) { + DeleteDatabaseResponse response = _resource.deleteTablesInDatabase(DATABASE, dryRun); + assertEquals(response.isDryRun(), dryRun); + assertTrue(response.getFailedTables().isEmpty()); + assertEquals(response.getDeletedTables(), TABLES); + } + + @Test + public void partialDatabaseDeletionWithDeleteTableFailureTest() { + int failureTableIdx = TABLES.size() / 2; + doThrow(new RuntimeException()).when(_resourceManager) + .deleteTable(TABLES.get(failureTableIdx), TableType.REALTIME, null); + partialDatabaseDeletionCheck(failureTableIdx); + } + + @Test + public void partialDatabaseDeletionWithDeleteSchemaFailureTest() { + int failureSchemaIdx = TABLES.size() / 2; + doThrow(new RuntimeException()).when(_resourceManager) + .deleteSchema(TableNameBuilder.extractRawTableName(TABLES.get(failureSchemaIdx))); + partialDatabaseDeletionCheck(failureSchemaIdx); + } + + private void partialDatabaseDeletionCheck(int idx) { + DeleteDatabaseResponse response = _resource.deleteTablesInDatabase(DATABASE, false); + List resultList = new ArrayList<>(TABLES); + String failedTable = resultList.remove(idx); + assertFalse(response.isDryRun()); + assertEquals(response.getFailedTables().size(), 1); + assertEquals(response.getFailedTables().get(0).getTableName(), failedTable); + assertEquals(response.getDeletedTables(), resultList); + } +} diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotDummyExtraRestletResourceStatelessTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotDummyExtraRestletResourceStatelessTest.java index 934271ce25c0..339047c31004 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotDummyExtraRestletResourceStatelessTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotDummyExtraRestletResourceStatelessTest.java @@ -36,14 +36,17 @@ @Test(groups = "stateless") public class PinotDummyExtraRestletResourceStatelessTest extends ControllerTest { + @Override + protected void overrideControllerConf(Map properties) { + properties.put(CONTROLLER_RESOURCE_PACKAGES, + DEFAULT_CONTROLLER_RESOURCE_PACKAGES + ",org.apache.pinot.controller.api.extraresources"); + } + @BeforeClass public void setUp() throws Exception { startZk(); - Map properties = getDefaultControllerConfiguration(); - properties.put(CONTROLLER_RESOURCE_PACKAGES, - DEFAULT_CONTROLLER_RESOURCE_PACKAGES + ",org.apache.pinot.controller.api.extraresources"); - startController(properties); + startController(); } @Test diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotTaskRestletResourceTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotTaskRestletResourceTest.java new file mode 100644 index 000000000000..764b1d53d16b --- /dev/null +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/resources/PinotTaskRestletResourceTest.java @@ -0,0 +1,139 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.controller.api.resources; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collections; +import java.util.Map; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.UriInfo; +import org.apache.helix.model.InstanceConfig; +import org.apache.pinot.controller.ControllerConf; +import org.apache.pinot.controller.api.exception.ControllerApplicationException; +import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; +import org.apache.pinot.controller.helix.core.minion.PinotHelixTaskResourceManager; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertThrows; + + +public class PinotTaskRestletResourceTest { + @Mock + PinotHelixResourceManager _pinotHelixResourceManager; + @Mock + PinotHelixTaskResourceManager _pinotHelixTaskResourceManager; + @Mock + ControllerConf _controllerConf; + @Mock + UriInfo _uriInfo; + + @InjectMocks + PinotTaskRestletResource _pinotTaskRestletResource; + + @BeforeMethod + public void init() + throws URISyntaxException { + MockitoAnnotations.openMocks(this); + when(_uriInfo.getRequestUri()).thenReturn(new URI("http://localhost:9000")); + } + + @Test + public void testGetSubtaskWithGivenStateProgressWhenMinionWorkerIdsAreNotSpecified() + throws JsonProcessingException { + Map minionWorkerEndpoints + = invokeGetSubtaskWithGivenStateProgressAndReturnCapturedMinionWorkerEndpoints(null); + assertEquals(minionWorkerEndpoints, + ImmutableMap.of("minion1", "http://minion1:9514", "minion2", "http://minion2:9514")); + } + + @Test + public void testGetSubtaskWithGivenStateProgressWhenAllMinionWorkerIdsAreSpecified() + throws JsonProcessingException { + // use minion worker ids with spaces ensure they will be trimmed. + Map minionWorkerEndpoints + = invokeGetSubtaskWithGivenStateProgressAndReturnCapturedMinionWorkerEndpoints(" minion1 , minion2 "); + assertEquals(minionWorkerEndpoints, + ImmutableMap.of("minion1", "http://minion1:9514", "minion2", "http://minion2:9514")); + } + + @Test + public void testGetSubtaskWithGivenStateProgressWhenOneMinionWorkerIdIsSpecified() + throws JsonProcessingException { + Map minionWorkerEndpoints + = invokeGetSubtaskWithGivenStateProgressAndReturnCapturedMinionWorkerEndpoints("minion1"); + assertEquals(minionWorkerEndpoints, + ImmutableMap.of("minion1", "http://minion1:9514")); + } + + private Map invokeGetSubtaskWithGivenStateProgressAndReturnCapturedMinionWorkerEndpoints( + String minionWorkerIds) + throws JsonProcessingException { + InstanceConfig minion1 = createInstanceConfig("minion1", "minion1", "9514"); + InstanceConfig minion2 = createInstanceConfig("minion2", "minion2", "9514"); + when(_pinotHelixResourceManager.getAllMinionInstanceConfigs()).thenReturn(ImmutableList.of(minion1, minion2)); + HttpHeaders httpHeaders = Mockito.mock(HttpHeaders.class); + when(httpHeaders.getRequestHeaders()).thenReturn(new MultivaluedHashMap<>()); + ArgumentCaptor> minionWorkerEndpointsCaptor = ArgumentCaptor.forClass(Map.class); + when(_pinotHelixTaskResourceManager.getSubtaskOnWorkerProgress(anyString(), any(), any(), + minionWorkerEndpointsCaptor.capture(), anyMap(), anyInt())) + .thenReturn(Collections.emptyMap()); + String progress = + _pinotTaskRestletResource.getSubtaskOnWorkerProgress(httpHeaders, "IN_PROGRESS", minionWorkerIds); + assertEquals(progress, "{}"); + return minionWorkerEndpointsCaptor.getValue(); + } + + private InstanceConfig createInstanceConfig(String instanceId, String hostName, String port) { + InstanceConfig instanceConfig = new InstanceConfig(instanceId); + instanceConfig.setHostName(hostName); + instanceConfig.setPort(port); + return instanceConfig; + } + + + @Test + public void testGetSubtaskWithGivenStateProgressWithException() + throws JsonProcessingException { + when(_pinotHelixResourceManager.getAllMinionInstanceConfigs()).thenReturn(Collections.emptyList()); + HttpHeaders httpHeaders = Mockito.mock(HttpHeaders.class); + when(httpHeaders.getRequestHeaders()).thenReturn(new MultivaluedHashMap<>()); + when(_pinotHelixTaskResourceManager + .getSubtaskOnWorkerProgress(anyString(), any(), any(), anyMap(), anyMap(), anyInt())) + .thenThrow(new RuntimeException()); + assertThrows(ControllerApplicationException.class, + () -> _pinotTaskRestletResource.getSubtaskOnWorkerProgress(httpHeaders, "IN_PROGRESS", null)); + } +} diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/api/upload/ZKOperatorTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/api/upload/ZKOperatorTest.java index e54f62b81c64..0e69dd105edb 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/api/upload/ZKOperatorTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/api/upload/ZKOperatorTest.java @@ -89,7 +89,7 @@ public void setUp() TableConfig offlineTableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); TableConfig realtimeTableConfig = new TableConfigBuilder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setTimeColumnName(TIME_COLUMN) - .setStreamConfigs(getStreamConfigs()).setLLC(true).setNumReplicas(1).build(); + .setStreamConfigs(getStreamConfigs()).setNumReplicas(1).build(); _resourceManager.addSchema(schema, false, false); _resourceManager.addTable(offlineTableConfig); @@ -292,8 +292,9 @@ public void testCompleteSegmentOperations() assertEquals(segmentZKMetadata.getCreationTime(), 456L); long refreshTime = segmentZKMetadata.getRefreshTime(); assertTrue(refreshTime > 0); - // DownloadURL and crypter should not unchanged - assertEquals(segmentZKMetadata.getDownloadUrl(), "downloadUrl"); + // Download URL should change. Refer: https://github.com/apache/pinot/issues/11535 + assertEquals(segmentZKMetadata.getDownloadUrl(), "otherDownloadUrl"); + // crypter should not be changed assertEquals(segmentZKMetadata.getCrypterName(), "crypter"); assertEquals(segmentZKMetadata.getSegmentUploadStartTime(), -1); assertEquals(segmentZKMetadata.getSizeInBytes(), 10); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerInstanceToggleTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerInstanceToggleTest.java index 746966ec47d2..d74a6fd0a3e5 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerInstanceToggleTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerInstanceToggleTest.java @@ -19,10 +19,7 @@ package org.apache.pinot.controller.helix; import java.io.IOException; -import java.util.Set; -import org.apache.helix.model.ExternalView; import org.apache.pinot.common.utils.config.TagNameUtils; -import org.apache.pinot.common.utils.helix.HelixHelper; import org.apache.pinot.controller.utils.SegmentMetadataMockUtils; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; @@ -35,7 +32,6 @@ import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; -import static org.testng.Assert.fail; public class ControllerInstanceToggleTest extends ControllerTest { @@ -54,6 +50,10 @@ public void setUp() @Test public void testInstanceToggle() throws Exception { + // Create schema + sendPostRequest(DEFAULT_INSTANCE.getControllerRequestURLBuilder().forSchemaCreate(), + createDummySchema(RAW_TABLE_NAME).toPrettyJsonString()); + // Create an offline table TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setNumReplicas(DEFAULT_MIN_NUM_REPLICAS) @@ -120,7 +120,8 @@ private void toggleInstanceState(String instanceName, String state) { // It may take time for an instance to toggle the state. TestUtils.waitForCondition(aVoid -> { try { - sendPostRequest(DEFAULT_INSTANCE.getControllerRequestURLBuilder().forInstanceState(instanceName), state); + sendPutRequest( + DEFAULT_INSTANCE.getControllerRequestURLBuilder().forInstanceState(instanceName) + "?state=" + state); } catch (IOException ioe) { // receive non-200 status code return false; @@ -129,21 +130,6 @@ private void toggleInstanceState(String instanceName, String state) { }, TIMEOUT_MS, "Failed to toggle instance state: '" + state + "' for instance: " + instanceName); } - private void checkNumOnlineInstancesFromExternalView(String resourceName, int expectedNumOnlineInstances) - throws InterruptedException { - long endTime = System.currentTimeMillis() + TIMEOUT_MS; - while (System.currentTimeMillis() < endTime) { - ExternalView resourceExternalView = DEFAULT_INSTANCE.getHelixAdmin() - .getResourceExternalView(DEFAULT_INSTANCE.getHelixClusterName(), resourceName); - Set instanceSet = HelixHelper.getOnlineInstanceFromExternalView(resourceExternalView); - if (instanceSet.size() == expectedNumOnlineInstances) { - return; - } - Thread.sleep(100L); - } - fail("Failed to reach " + expectedNumOnlineInstances + " online instances for resource: " + resourceName); - } - @AfterClass public void tearDown() { DEFAULT_INSTANCE.cleanup(); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerPeriodicTaskStarterStatelessTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerPeriodicTaskStarterStatelessTest.java index 6172c67def54..e3014b82a87a 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerPeriodicTaskStarterStatelessTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerPeriodicTaskStarterStatelessTest.java @@ -52,12 +52,12 @@ public void teardown() { } @Override - public ControllerStarter getControllerStarter() { + public ControllerStarter createControllerStarter() { return new MockControllerStarter(); } private class MockControllerStarter extends ControllerStarter { - private static final int NUM_PERIODIC_TASKS = 10; + private static final int NUM_PERIODIC_TASKS = 11; public MockControllerStarter() { super(); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerSentinelTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerSentinelTest.java index 1fd7b0614098..0e6fea5abbd2 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerSentinelTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerSentinelTest.java @@ -26,7 +26,7 @@ import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.apache.pinot.spi.utils.builder.TableNameBuilder; -import org.testng.annotations.AfterTest; +import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -45,6 +45,9 @@ public void setUp() @Test public void testOfflineTableLifeCycle() throws IOException { + // Create schema + sendPostRequest(DEFAULT_INSTANCE.getControllerRequestURLBuilder().forSchemaCreate(), + createDummySchema(TABLE_NAME).toPrettyJsonString()); // Create offline table creation request TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).setNumReplicas(DEFAULT_MIN_NUM_REPLICAS) @@ -83,7 +86,7 @@ public void testOfflineTableLifeCycle() TagNameUtils.getRealtimeTagForTenant(TagNameUtils.DEFAULT_TENANT_NAME)).size(), DEFAULT_NUM_SERVER_INSTANCES); } - @AfterTest + @AfterClass public void tearDown() { DEFAULT_INSTANCE.cleanup(); } diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerTest.java index 3259c5148fa3..af08e1741d75 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/ControllerTest.java @@ -18,7 +18,6 @@ */ package org.apache.pinot.controller.helix; -import com.google.common.base.Preconditions; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; @@ -26,14 +25,14 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; -import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.StringUtils; import org.apache.helix.ConfigAccessor; import org.apache.helix.HelixAdmin; import org.apache.helix.HelixDataAccessor; @@ -42,6 +41,7 @@ import org.apache.helix.InstanceType; import org.apache.helix.NotificationContext; import org.apache.helix.model.ClusterConfig; +import org.apache.helix.model.ExternalView; import org.apache.helix.model.HelixConfigScope; import org.apache.helix.model.Message; import org.apache.helix.model.ResourceConfig; @@ -57,6 +57,7 @@ import org.apache.pinot.common.utils.SimpleHttpResponse; import org.apache.pinot.common.utils.ZkStarter; import org.apache.pinot.common.utils.config.TagNameUtils; +import org.apache.pinot.common.utils.helix.HelixHelper; import org.apache.pinot.common.utils.http.HttpClient; import org.apache.pinot.controller.BaseControllerStarter; import org.apache.pinot.controller.ControllerConf; @@ -71,24 +72,22 @@ import org.apache.pinot.spi.data.MetricFieldSpec; import org.apache.pinot.spi.data.Schema; import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.CommonConstants.Helix; -import org.apache.pinot.spi.utils.CommonConstants.Server; import org.apache.pinot.spi.utils.NetUtils; import org.apache.pinot.spi.utils.builder.ControllerRequestURLBuilder; import org.apache.pinot.spi.utils.builder.TableNameBuilder; +import org.apache.pinot.util.TestUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import static org.apache.pinot.spi.utils.CommonConstants.Helix.UNTAGGED_BROKER_INSTANCE; import static org.apache.pinot.spi.utils.CommonConstants.Helix.UNTAGGED_SERVER_INSTANCE; -import static org.apache.pinot.spi.utils.CommonConstants.Server.DEFAULT_ADMIN_API_PORT; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.*; public class ControllerTest { public static final String LOCAL_HOST = "localhost"; - public static final int DEFAULT_CONTROLLER_PORT = 18998; public static final String DEFAULT_DATA_DIR = new File(FileUtils.getTempDirectoryPath(), "test-controller-data-dir" + System.currentTimeMillis()).getAbsolutePath(); public static final String DEFAULT_LOCAL_TEMP_DIR = new File(FileUtils.getTempDirectoryPath(), @@ -103,34 +102,41 @@ public class ControllerTest { // NOTE: To add HLC realtime table, number of Server instances must be multiple of replicas public static final int DEFAULT_NUM_SERVER_INSTANCES = 4; + public static final long TIMEOUT_MS = 10_000L; + /** * default static instance used to access all wrapped static instances. */ public static final ControllerTest DEFAULT_INSTANCE = new ControllerTest(); + protected static HttpClient _httpClient; + protected final String _clusterName = getClass().getSimpleName(); + protected final List _fakeInstanceHelixManagers = new ArrayList<>(); - protected static HttpClient _httpClient = null; + protected int _nextControllerPort = 20000; + protected int _nextBrokerPort = _nextControllerPort + 1000; + protected int _nextServerPort = _nextBrokerPort + 1000; + protected int _nextMinionPort = _nextServerPort + 1000; + private ZkStarter.ZookeeperInstance _zookeeperInstance; + + // The following fields need to be reset when stopping the controller. + protected BaseControllerStarter _controllerStarter; protected int _controllerPort; - protected String _controllerBaseApiUrl; + protected ControllerRequestClient _controllerRequestClient; + + // The following fields are always set when controller is started. No need to reset them when stopping the controller. protected ControllerConf _controllerConfig; + protected String _controllerBaseApiUrl; protected ControllerRequestURLBuilder _controllerRequestURLBuilder; - - protected ControllerRequestClient _controllerRequestClient = null; - - protected final List _fakeInstanceHelixManagers = new ArrayList<>(); protected String _controllerDataDir; - - protected BaseControllerStarter _controllerStarter; protected PinotHelixResourceManager _helixResourceManager; protected HelixManager _helixManager; - protected HelixAdmin _helixAdmin; protected HelixDataAccessor _helixDataAccessor; + protected HelixAdmin _helixAdmin; protected ZkHelixPropertyStore _propertyStore; - private ZkStarter.ZookeeperInstance _zookeeperInstance; - /** * Acquire the {@link ControllerTest} default instance that can be shared across different test cases. * @@ -201,18 +207,37 @@ public String getZkUrl() { public Map getDefaultControllerConfiguration() { Map properties = new HashMap<>(); - + properties.put(ControllerConf.ZK_STR, getZkUrl()); + properties.put(ControllerConf.HELIX_CLUSTER_NAME, getHelixClusterName()); properties.put(ControllerConf.CONTROLLER_HOST, LOCAL_HOST); - properties.put(ControllerConf.CONTROLLER_PORT, NetUtils.findOpenPort(DEFAULT_CONTROLLER_PORT)); + int controllerPort = NetUtils.findOpenPort(_nextControllerPort); + properties.put(ControllerConf.CONTROLLER_PORT, controllerPort); + if (_controllerPort == 0) { + _controllerPort = controllerPort; + } + _nextControllerPort = controllerPort + 1; properties.put(ControllerConf.DATA_DIR, DEFAULT_DATA_DIR); properties.put(ControllerConf.LOCAL_TEMP_DIR, DEFAULT_LOCAL_TEMP_DIR); - properties.put(ControllerConf.ZK_STR, getZkUrl()); - properties.put(ControllerConf.HELIX_CLUSTER_NAME, getHelixClusterName()); // Enable groovy on the controller properties.put(ControllerConf.DISABLE_GROOVY, false); + properties.put(CommonConstants.CONFIG_OF_TIMEZONE, "UTC"); + overrideControllerConf(properties); return properties; } + /** + * Can be overridden to add more properties. + */ + protected void overrideControllerConf(Map properties) { + } + + /** + * Can be overridden to use a different implementation. + */ + public BaseControllerStarter createControllerStarter() { + return new ControllerStarter(); + } + public void startController() throws Exception { startController(getDefaultControllerConfiguration()); @@ -220,27 +245,16 @@ public void startController() public void startController(Map properties) throws Exception { - Preconditions.checkState(_controllerStarter == null); - - _controllerConfig = new ControllerConf(properties); + assertNull(_controllerStarter, "Controller is already started"); + assertTrue(_controllerPort > 0, "Controller port is not assigned"); - String controllerScheme = "http"; - if (StringUtils.isNotBlank(_controllerConfig.getControllerVipProtocol())) { - controllerScheme = _controllerConfig.getControllerVipProtocol(); - } - - _controllerPort = DEFAULT_CONTROLLER_PORT; - if (StringUtils.isNotBlank(_controllerConfig.getControllerPort())) { - _controllerPort = Integer.parseInt(_controllerConfig.getControllerPort()); - } - - _controllerBaseApiUrl = controllerScheme + "://localhost:" + _controllerPort; - _controllerRequestURLBuilder = ControllerRequestURLBuilder.baseUrl(_controllerBaseApiUrl); - _controllerDataDir = _controllerConfig.getDataDir(); - - _controllerStarter = getControllerStarter(); + _controllerStarter = createControllerStarter(); _controllerStarter.init(new PinotConfiguration(properties)); _controllerStarter.start(); + _controllerConfig = _controllerStarter.getConfig(); + _controllerBaseApiUrl = _controllerConfig.generateVipUrl(); + _controllerRequestURLBuilder = ControllerRequestURLBuilder.baseUrl(_controllerBaseApiUrl); + _controllerDataDir = _controllerConfig.getDataDir(); _helixResourceManager = _controllerStarter.getHelixResourceManager(); _helixManager = _controllerStarter.getHelixControllerManager(); _helixDataAccessor = _helixManager.getHelixDataAccessor(); @@ -270,15 +284,26 @@ public void startController(Map properties) configAccessor.set(scope, Helix.ENABLE_CASE_INSENSITIVE_KEY, Boolean.toString(true)); // Set hyperloglog log2m value to 12. configAccessor.set(scope, Helix.DEFAULT_HYPERLOGLOG_LOG2M_KEY, Integer.toString(12)); + assertEquals(System.getProperty("user.timezone"), "UTC"); } public void stopController() { - Preconditions.checkState(_controllerStarter != null); + assertNotNull(_controllerStarter, "Controller hasn't been started"); _controllerStarter.stop(); _controllerStarter = null; + _controllerPort = 0; + _controllerRequestClient = null; FileUtils.deleteQuietly(new File(_controllerDataDir)); } + public void restartController() + throws Exception { + assertNotNull(_controllerStarter, "Controller hasn't been started"); + _controllerStarter.stop(); + _controllerStarter = null; + startController(_controllerConfig.toMap()); + } + public int getFakeBrokerInstanceCount() { return _helixAdmin.getInstancesInClusterWithTag(getHelixClusterName(), "DefaultTenant_BROKER").size() + _helixAdmin.getInstancesInClusterWithTag(getHelixClusterName(), UNTAGGED_BROKER_INSTANCE).size(); @@ -373,28 +398,13 @@ public void onBecomeOfflineFromError(Message message, NotificationContext contex public void addFakeServerInstancesToAutoJoinHelixCluster(int numInstances, boolean isSingleTenant) throws Exception { - addFakeServerInstancesToAutoJoinHelixCluster(numInstances, isSingleTenant, Server.DEFAULT_ADMIN_API_PORT); - } - - public void addFakeServerInstancesToAutoJoinHelixCluster(int numInstances, boolean isSingleTenant, int baseAdminPort) - throws Exception { for (int i = 0; i < numInstances; i++) { - addFakeServerInstanceToAutoJoinHelixCluster(SERVER_INSTANCE_ID_PREFIX + i, isSingleTenant, baseAdminPort + i); + addFakeServerInstanceToAutoJoinHelixCluster(SERVER_INSTANCE_ID_PREFIX + i, isSingleTenant); } } - public int getFakeServerInstanceCount() { - return _helixAdmin.getInstancesInClusterWithTag(getHelixClusterName(), "DefaultTenant_OFFLINE").size() - + _helixAdmin.getInstancesInClusterWithTag(getHelixClusterName(), UNTAGGED_SERVER_INSTANCE).size(); - } - public void addFakeServerInstanceToAutoJoinHelixCluster(String instanceId, boolean isSingleTenant) throws Exception { - addFakeServerInstanceToAutoJoinHelixCluster(instanceId, isSingleTenant, Server.DEFAULT_ADMIN_API_PORT); - } - - public void addFakeServerInstanceToAutoJoinHelixCluster(String instanceId, boolean isSingleTenant, int adminPort) - throws Exception { HelixManager helixManager = HelixManagerFactory.getZKHelixManager(getHelixClusterName(), instanceId, InstanceType.PARTICIPANT, getZkUrl()); helixManager.getStateMachineEngine() @@ -410,32 +420,29 @@ public void addFakeServerInstanceToAutoJoinHelixCluster(String instanceId, boole } HelixConfigScope configScope = new HelixConfigScopeBuilder(HelixConfigScope.ConfigScopeProperty.PARTICIPANT, getHelixClusterName()).forParticipant(instanceId).build(); - helixAdmin.setConfig(configScope, - Collections.singletonMap(Helix.Instance.ADMIN_PORT_KEY, Integer.toString(adminPort))); + int adminPort = NetUtils.findOpenPort(_nextServerPort); + helixAdmin.setConfig(configScope, Map.of(Helix.Instance.ADMIN_PORT_KEY, Integer.toString(adminPort))); + _nextServerPort = adminPort + 1; _fakeInstanceHelixManagers.add(helixManager); } /** Add fake server instances until total number of server instances reaches maxCount */ public void addMoreFakeServerInstancesToAutoJoinHelixCluster(int maxCount, boolean isSingleTenant) throws Exception { - addMoreFakeServerInstancesToAutoJoinHelixCluster(maxCount, isSingleTenant, DEFAULT_ADMIN_API_PORT); - } - - /** Add fake server instances until total number of server instances reaches maxCount */ - public void addMoreFakeServerInstancesToAutoJoinHelixCluster(int maxCount, boolean isSingleTenant, int baseAdminPort) - throws Exception { - // get current instance count int currentCount = getFakeServerInstanceCount(); // Add more instances if current count is less than max instance count. - if (currentCount < maxCount) { - for (int i = currentCount; i < maxCount; i++) { - addFakeServerInstanceToAutoJoinHelixCluster(SERVER_INSTANCE_ID_PREFIX + i, isSingleTenant, baseAdminPort + i); - } + for (int i = currentCount; i < maxCount; i++) { + addFakeServerInstanceToAutoJoinHelixCluster(SERVER_INSTANCE_ID_PREFIX + i, isSingleTenant); } } + public int getFakeServerInstanceCount() { + return _helixAdmin.getInstancesInClusterWithTag(getHelixClusterName(), "DefaultTenant_OFFLINE").size() + + _helixAdmin.getInstancesInClusterWithTag(getHelixClusterName(), UNTAGGED_SERVER_INSTANCE).size(); + } + public static class FakeSegmentOnlineOfflineStateModelFactory extends StateModelFactory { private static final String STATE_MODEL_DEF = "SegmentOnlineOfflineStateModel"; @@ -585,6 +592,12 @@ public void stopFakeInstance(String instanceId) { } } + public void stopAndDropFakeInstance(String instanceId) { + stopFakeInstance(instanceId); + TestUtils.waitForCondition(aVoid -> _helixResourceManager.dropInstance(instanceId).isSuccessful(), 60_000L, + "Failed to drop fake instance: " + instanceId); + } + public static Schema createDummySchema(String tableName) { Schema schema = new Schema(); schema.setSchemaName(tableName); @@ -615,6 +628,11 @@ public void addSchema(Schema schema) getControllerRequestClient().addSchema(schema); } + public void updateSchema(Schema schema) + throws IOException { + getControllerRequestClient().updateSchema(schema); + } + public Schema getSchema(String schemaName) { Schema schema = _helixResourceManager.getSchema(schemaName); assertNotNull(schema); @@ -658,6 +676,16 @@ public void dropRealtimeTable(String tableName) getControllerRequestClient().deleteTable(TableNameBuilder.REALTIME.tableNameWithType(tableName)); } + public void waitForEVToAppear(String tableNameWithType) { + TestUtils.waitForCondition(aVoid -> _helixResourceManager.getTableExternalView(tableNameWithType) != null, 60_000L, + "Failed to create the external view for table: " + tableNameWithType); + } + + public void waitForEVToDisappear(String tableNameWithType) { + TestUtils.waitForCondition(aVoid -> _helixResourceManager.getTableExternalView(tableNameWithType) == null, 60_000L, + "Failed to clean up the external view for table: " + tableNameWithType); + } + public List listSegments(String tableName) throws IOException { return listSegments(tableName, null, false); @@ -683,14 +711,14 @@ public long getTableSize(String tableName) return getControllerRequestClient().getTableSize(tableName); } - public void reloadOfflineTable(String tableName) + public String reloadOfflineTable(String tableName) throws IOException { - reloadOfflineTable(tableName, false); + return reloadOfflineTable(tableName, false); } - public void reloadOfflineTable(String tableName, boolean forceDownload) + public String reloadOfflineTable(String tableName, boolean forceDownload) throws IOException { - getControllerRequestClient().reloadTable(tableName, TableType.OFFLINE, forceDownload); + return getControllerRequestClient().reloadTable(tableName, TableType.OFFLINE, forceDownload); } public void reloadOfflineSegment(String tableName, String segmentName, boolean forceDownload) @@ -698,9 +726,9 @@ public void reloadOfflineSegment(String tableName, String segmentName, boolean f getControllerRequestClient().reloadSegment(tableName, segmentName, forceDownload); } - public void reloadRealtimeTable(String tableName) + public String reloadRealtimeTable(String tableName) throws IOException { - getControllerRequestClient().reloadTable(tableName, TableType.REALTIME, false); + return getControllerRequestClient().reloadTable(tableName, TableType.REALTIME, false); } public void createBrokerTenant(String tenantName, int numBrokers) @@ -713,6 +741,11 @@ public void updateBrokerTenant(String tenantName, int numBrokers) getControllerRequestClient().updateBrokerTenant(tenantName, numBrokers); } + public void deleteBrokerTenant(String tenantName) + throws IOException { + getControllerRequestClient().deleteBrokerTenant(tenantName); + } + public void createServerTenant(String tenantName, int numOfflineServers, int numRealtimeServers) throws IOException { getControllerRequestClient().createServerTenant(tenantName, numOfflineServers, numRealtimeServers); @@ -754,6 +787,11 @@ public static String sendGetRequestRaw(String urlString) return IOUtils.toString(new URL(urlString).openStream()); } + public static String sendPostRequest(String urlString) + throws IOException { + return sendPostRequest(urlString, null); + } + public static String sendPostRequest(String urlString, String payload) throws IOException { return sendPostRequest(urlString, payload, Collections.emptyMap()); @@ -878,6 +916,10 @@ public HelixAdmin getHelixAdmin() { return _helixAdmin; } + public BaseControllerStarter getControllerStarter() { + return _controllerStarter; + } + public PinotHelixResourceManager getHelixResourceManager() { return _helixResourceManager; } @@ -898,10 +940,6 @@ public int getControllerPort() { return _controllerPort; } - public BaseControllerStarter getControllerStarter() { - return _controllerStarter == null ? new ControllerStarter() : _controllerStarter; - } - public ControllerConf getControllerConfig() { return _controllerConfig; } @@ -948,13 +986,31 @@ public void stopSharedTestSetup() { stopZk(); } + /** + * Checks if the number of online instances for a given resource matches the expected num of instances or not. + */ + public void checkNumOnlineInstancesFromExternalView(String resourceName, int expectedNumOnlineInstances) + throws InterruptedException { + long endTime = System.currentTimeMillis() + TIMEOUT_MS; + while (System.currentTimeMillis() < endTime) { + ExternalView resourceExternalView = DEFAULT_INSTANCE.getHelixAdmin() + .getResourceExternalView(DEFAULT_INSTANCE.getHelixClusterName(), resourceName); + Set instanceSet = HelixHelper.getOnlineInstanceFromExternalView(resourceExternalView); + if (instanceSet.size() == expectedNumOnlineInstances) { + return; + } + Thread.sleep(100L); + } + fail("Failed to reach " + expectedNumOnlineInstances + " online instances for resource: " + resourceName); + } + /** * Make sure shared state is setup and valid before each test case class is run. */ public void setupSharedStateAndValidate() throws Exception { if (_zookeeperInstance == null || _helixResourceManager == null) { - // this is expected to happen only when running a single test case outside of testNG group, i.e when test + // this is expected to happen only when running a single test case outside testNG group, i.e. when test // cases are run one at a time within IntelliJ or through maven command line. When running under a testNG // group, state will have already been setup by @BeforeGroups method in ControllerTestSetup. startSharedTestSetup(); @@ -971,7 +1027,9 @@ public void setupSharedStateAndValidate() DEFAULT_NUM_SERVER_INSTANCES); // No pre-existing tables - assertEquals(getHelixResourceManager().getAllTables().size(), 0); + assertTrue(CollectionUtils.isEmpty(getHelixResourceManager().getAllTables())); + // No pre-existing schemas + assertTrue(CollectionUtils.isEmpty(getHelixResourceManager().getSchemaNames())); } /** @@ -979,19 +1037,28 @@ public void setupSharedStateAndValidate() * test functionality. */ public void cleanup() { - - // Delete all tables. - List tables = getHelixResourceManager().getAllTables(); - for (String table : tables) { - getHelixResourceManager().deleteOfflineTable(table); - getHelixResourceManager().deleteRealtimeTable(table); + // Delete all tables + List tables = _helixResourceManager.getAllTables(); + for (String tableNameWithType : tables) { + if (TableNameBuilder.isOfflineTableResource(tableNameWithType)) { + _helixResourceManager.deleteOfflineTable(tableNameWithType); + } else { + _helixResourceManager.deleteRealtimeTable(tableNameWithType); + } } + // Wait for all external views to disappear + Set tablesWithEV = new HashSet<>(tables); + TestUtils.waitForCondition(aVoid -> { + tablesWithEV.removeIf(t -> _helixResourceManager.getTableExternalView(t) == null); + return tablesWithEV.isEmpty(); + }, 60_000L, "Failed to clean up all the external views"); + // Delete all schemas. - List schemaNames = getHelixResourceManager().getSchemaNames(); + List schemaNames = _helixResourceManager.getSchemaNames(); if (CollectionUtils.isNotEmpty(schemaNames)) { for (String schemaName : schemaNames) { - getHelixResourceManager().deleteSchema(getHelixResourceManager().getSchema(schemaName)); + getHelixResourceManager().deleteSchema(schemaName); } } } diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/PinotResourceManagerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/PinotResourceManagerTest.java index 445e43a8712d..1ed304740f5f 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/PinotResourceManagerTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/PinotResourceManagerTest.java @@ -23,10 +23,12 @@ import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.Future; import org.apache.helix.model.IdealState; +import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; import org.apache.pinot.common.utils.LLCSegmentName; +import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; import org.apache.pinot.controller.utils.SegmentMetadataMockUtils; import org.apache.pinot.core.realtime.impl.fakestream.FakeStreamConfigUtils; import org.apache.pinot.spi.config.table.ReplicaGroupStrategyConfig; @@ -36,65 +38,67 @@ import org.apache.pinot.spi.data.Schema; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.apache.pinot.spi.utils.builder.TableNameBuilder; -import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; +import static org.testng.Assert.*; + public class PinotResourceManagerTest { - private static final ControllerTest TEST_INSTANCE = ControllerTest.getInstance(); - private static final String OFFLINE_TABLE_NAME = "offlineResourceManagerTestTable_OFFLINE"; - private static final String REALTIME_TABLE_NAME = "realtimeResourceManagerTestTable_REALTIME"; - private static final String NUM_REPLICAS_STRING = "2"; - private static final String PARTITION_COLUMN = "Partition_Column"; + private static final String RAW_TABLE_NAME = "testTable"; + private static final String OFFLINE_TABLE_NAME = TableNameBuilder.OFFLINE.tableNameWithType(RAW_TABLE_NAME); + private static final String REALTIME_TABLE_NAME = TableNameBuilder.REALTIME.tableNameWithType(RAW_TABLE_NAME); + private static final int NUM_REPLICAS = 2; + private static final String PARTITION_COLUMN = "partitionColumn"; + + private final ControllerTest _testInstance = ControllerTest.getInstance(); + private PinotHelixResourceManager _resourceManager; @BeforeClass public void setUp() throws Exception { - TEST_INSTANCE.setupSharedStateAndValidate(); + _testInstance.setupSharedStateAndValidate(); + _resourceManager = _testInstance.getHelixResourceManager(); + + // Create schema + _testInstance.addDummySchema(RAW_TABLE_NAME); // Adding an offline table - TableConfig offlineTableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(OFFLINE_TABLE_NAME).build(); - TEST_INSTANCE.getHelixResourceManager().addTable(offlineTableConfig); + TableConfig offlineTableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + _resourceManager.addTable(offlineTableConfig); // Adding an upsert enabled realtime table which consumes from a stream with 2 partitions - Schema dummySchema = TEST_INSTANCE.createDummySchema(REALTIME_TABLE_NAME); - TEST_INSTANCE.addSchema(dummySchema); + Schema dummySchema = ControllerTest.createDummySchema(RAW_TABLE_NAME); + _testInstance.addSchema(dummySchema); Map streamConfigs = FakeStreamConfigUtils.getDefaultLowLevelStreamConfigs().getStreamConfigsMap(); TableConfig realtimeTableConfig = - new TableConfigBuilder(TableType.REALTIME).setStreamConfigs(streamConfigs).setTableName(REALTIME_TABLE_NAME) - .setSchemaName(dummySchema.getSchemaName()).build(); - realtimeTableConfig.getValidationConfig().setReplicasPerPartition(NUM_REPLICAS_STRING); - realtimeTableConfig.getValidationConfig() - .setReplicaGroupStrategyConfig(new ReplicaGroupStrategyConfig(PARTITION_COLUMN, 1)); - realtimeTableConfig.setUpsertConfig(new UpsertConfig(UpsertConfig.Mode.FULL)); - TEST_INSTANCE.getHelixResourceManager().addTable(realtimeTableConfig); + new TableConfigBuilder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setNumReplicas(NUM_REPLICAS) + .setStreamConfigs(streamConfigs) + .setReplicaGroupStrategyConfig(new ReplicaGroupStrategyConfig(PARTITION_COLUMN, 1)) + .setUpsertConfig(new UpsertConfig(UpsertConfig.Mode.FULL)).build(); + _resourceManager.addTable(realtimeTableConfig); } @Test public void testTableCleanupAfterRealtimeClusterException() throws Exception { - String invalidRealtimeTable = "invalidTable_REALTIME"; - Schema dummySchema = TEST_INSTANCE.createDummySchema(invalidRealtimeTable); - TEST_INSTANCE.addSchema(dummySchema); + String invalidRawTableName = "invalidTable"; + Schema dummySchema = ControllerTest.createDummySchema(invalidRawTableName); + _testInstance.addSchema(dummySchema); - // Missing replicasPerPartition + // Missing stream config TableConfig invalidRealtimeTableConfig = - new TableConfigBuilder(TableType.REALTIME).setTableName(invalidRealtimeTable) - .setSchemaName(dummySchema.getSchemaName()).build(); - + new TableConfigBuilder(TableType.REALTIME).setTableName(invalidRawTableName).build(); try { - TEST_INSTANCE.getHelixResourceManager().addTable(invalidRealtimeTableConfig); - Assert.fail( - "Table creation should have thrown exception due to missing stream config and replicasPerPartition in " - + "validation config"); + _resourceManager.addTable(invalidRealtimeTableConfig); + fail("Table creation should have thrown exception due to missing stream config in validation config"); } catch (Exception e) { // expected } // Verify invalid table config is cleaned up - Assert.assertNull(TEST_INSTANCE.getHelixResourceManager().getTableConfig(invalidRealtimeTable)); + assertNull(_resourceManager.getTableConfig(invalidRealtimeTableConfig.getTableName())); } @Test @@ -102,144 +106,130 @@ public void testUpdateSegmentZKMetadata() { SegmentZKMetadata segmentZKMetadata = new SegmentZKMetadata("testSegment"); // Segment ZK metadata does not exist - Assert.assertFalse(TEST_INSTANCE.getHelixResourceManager() - .updateZkMetadata(OFFLINE_TABLE_NAME + "_OFFLINE", segmentZKMetadata, 0)); + assertFalse(_resourceManager.updateZkMetadata(OFFLINE_TABLE_NAME, segmentZKMetadata, 0)); // Set segment ZK metadata - Assert.assertTrue(TEST_INSTANCE.getHelixResourceManager() - .updateZkMetadata(OFFLINE_TABLE_NAME + "_OFFLINE", segmentZKMetadata)); + assertTrue(_resourceManager.updateZkMetadata(OFFLINE_TABLE_NAME, segmentZKMetadata)); // Update ZK metadata - Assert.assertEquals(TEST_INSTANCE.getHelixResourceManager() - .getSegmentMetadataZnRecord(OFFLINE_TABLE_NAME + "_OFFLINE", "testSegment").getVersion(), 0); - Assert.assertTrue(TEST_INSTANCE.getHelixResourceManager() - .updateZkMetadata(OFFLINE_TABLE_NAME + "_OFFLINE", segmentZKMetadata, 0)); - Assert.assertEquals(TEST_INSTANCE.getHelixResourceManager() - .getSegmentMetadataZnRecord(OFFLINE_TABLE_NAME + "_OFFLINE", "testSegment").getVersion(), 1); - Assert.assertFalse(TEST_INSTANCE.getHelixResourceManager() - .updateZkMetadata(OFFLINE_TABLE_NAME + "_OFFLINE", segmentZKMetadata, 0)); + ZNRecord segmentMetadataZnRecord = _resourceManager.getSegmentMetadataZnRecord(OFFLINE_TABLE_NAME, "testSegment"); + assertNotNull(segmentMetadataZnRecord); + assertEquals(segmentMetadataZnRecord.getVersion(), 0); + assertTrue(_resourceManager.updateZkMetadata(OFFLINE_TABLE_NAME, segmentZKMetadata, 0)); + segmentMetadataZnRecord = _resourceManager.getSegmentMetadataZnRecord(OFFLINE_TABLE_NAME, "testSegment"); + assertNotNull(segmentMetadataZnRecord); + assertEquals(segmentMetadataZnRecord.getVersion(), 1); + assertFalse(_resourceManager.updateZkMetadata(OFFLINE_TABLE_NAME, segmentZKMetadata, 0)); } /** * First tests basic segment adding/deleting. - * Then creates 3 threads that concurrently try to add 10 segments each, and asserts that we have - * 100 segments in the end. Then launches 5 threads again that concurrently try to delete all segments, - * and makes sure that we have zero segments left in the end. - * @throws Exception + * Then creates 3 threads that concurrently try to add 10 segments each, and asserts that we have 30 segments in the + * end. Then launches 3 threads again that concurrently try to delete all segments, and makes sure that we have 0 + * segments left in the end. */ - @Test public void testBasicAndConcurrentAddingAndDeletingSegments() throws Exception { - final String offlineTableName = TableNameBuilder.OFFLINE.tableNameWithType(OFFLINE_TABLE_NAME); + PinotHelixResourceManager resourceManager = _resourceManager; - // Basic add/delete case + // Basic add/delete for (int i = 1; i <= 2; i++) { - TEST_INSTANCE.getHelixResourceManager() - .addNewSegment(OFFLINE_TABLE_NAME, SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME), - "downloadUrl"); + resourceManager.addNewSegment(OFFLINE_TABLE_NAME, + SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME), "downloadUrl"); } - IdealState idealState = TEST_INSTANCE.getHelixAdmin() - .getResourceIdealState(TEST_INSTANCE.getHelixClusterName(), offlineTableName); + IdealState idealState = resourceManager.getTableIdealState(OFFLINE_TABLE_NAME); + assertNotNull(idealState); Set segments = idealState.getPartitionSet(); - Assert.assertEquals(segments.size(), 2); + assertEquals(segments.size(), 2); for (String segmentName : segments) { - TEST_INSTANCE.getHelixResourceManager().deleteSegment(offlineTableName, segmentName); + resourceManager.deleteSegment(OFFLINE_TABLE_NAME, segmentName); } - idealState = TEST_INSTANCE.getHelixAdmin() - .getResourceIdealState(TEST_INSTANCE.getHelixClusterName(), offlineTableName); - Assert.assertEquals(idealState.getPartitionSet().size(), 0); + idealState = resourceManager.getTableIdealState(OFFLINE_TABLE_NAME); + assertNotNull(idealState); + assertEquals(idealState.getNumPartitions(), 0); - // Concurrent segment deletion - ExecutorService addSegmentExecutor = Executors.newFixedThreadPool(3); + // Concurrent add/deletion + ExecutorService executor = Executors.newFixedThreadPool(3); + Future[] futures = new Future[3]; for (int i = 0; i < 3; i++) { - addSegmentExecutor.execute(new Runnable() { - @Override - public void run() { - for (int i = 0; i < 10; i++) { - TEST_INSTANCE.getHelixResourceManager() - .addNewSegment(OFFLINE_TABLE_NAME, SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME), - "downloadUrl"); - } + futures[i] = executor.submit(() -> { + for (int i1 = 0; i1 < 10; i1++) { + resourceManager.addNewSegment(OFFLINE_TABLE_NAME, + SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME), "downloadUrl"); } }); } - addSegmentExecutor.shutdown(); - addSegmentExecutor.awaitTermination(1, TimeUnit.MINUTES); - - idealState = TEST_INSTANCE.getHelixAdmin() - .getResourceIdealState(TEST_INSTANCE.getHelixClusterName(), offlineTableName); - Assert.assertEquals(idealState.getPartitionSet().size(), 30); - - ExecutorService deleteSegmentExecutor = Executors.newFixedThreadPool(3); - for (final String segmentName : idealState.getPartitionSet()) { - deleteSegmentExecutor.execute(new Runnable() { - @Override - public void run() { - TEST_INSTANCE.getHelixResourceManager().deleteSegment(offlineTableName, segmentName); - } - }); + for (int i = 0; i < 3; i++) { + futures[i].get(); + } + idealState = resourceManager.getTableIdealState(OFFLINE_TABLE_NAME); + assertNotNull(idealState); + segments = idealState.getPartitionSet(); + assertEquals(segments.size(), 30); + + futures = new Future[30]; + int index = 0; + for (String segment : segments) { + futures[index++] = executor.submit(() -> resourceManager.deleteSegment(OFFLINE_TABLE_NAME, segment)); + } + for (int i = 0; i < 30; i++) { + futures[i].get(); } - deleteSegmentExecutor.shutdown(); - deleteSegmentExecutor.awaitTermination(1, TimeUnit.MINUTES); + idealState = resourceManager.getTableIdealState(OFFLINE_TABLE_NAME); + assertNotNull(idealState); + assertEquals(idealState.getNumPartitions(), 0); - idealState = TEST_INSTANCE.getHelixAdmin() - .getResourceIdealState(TEST_INSTANCE.getHelixClusterName(), offlineTableName); - Assert.assertEquals(idealState.getPartitionSet().size(), 0); + executor.shutdown(); } @Test public void testAddingRealtimeTableSegmentsWithPartitionIdInZkMetadata() { // Add three segments: two from partition 0 and 1 from partition 1; - String partition0Segment0 = "realtimeResourceManagerTestTable__aa"; - String partition0Segment1 = "realtimeResourceManagerTestTable__bb"; - String partition1Segment1 = "realtimeResourceManagerTestTable__cc"; - TEST_INSTANCE.getHelixResourceManager().addNewSegment(REALTIME_TABLE_NAME, SegmentMetadataMockUtils - .mockSegmentMetadataWithPartitionInfo(REALTIME_TABLE_NAME, partition0Segment0, PARTITION_COLUMN, 0), - "downloadUrl"); - TEST_INSTANCE.getHelixResourceManager().addNewSegment(REALTIME_TABLE_NAME, SegmentMetadataMockUtils - .mockSegmentMetadataWithPartitionInfo(REALTIME_TABLE_NAME, partition0Segment1, PARTITION_COLUMN, 0), - "downloadUrl"); - TEST_INSTANCE.getHelixResourceManager().addNewSegment(REALTIME_TABLE_NAME, SegmentMetadataMockUtils - .mockSegmentMetadataWithPartitionInfo(REALTIME_TABLE_NAME, partition1Segment1, PARTITION_COLUMN, 1), - "downloadUrl"); - Map segment2PartitionId = new HashMap<>(); - segment2PartitionId.put(partition0Segment0, 0); - segment2PartitionId.put(partition0Segment1, 0); - segment2PartitionId.put(partition1Segment1, 1); - - IdealState idealState = TEST_INSTANCE.getHelixAdmin() - .getResourceIdealState(TEST_INSTANCE.getHelixClusterName(), - TableNameBuilder.REALTIME.tableNameWithType(REALTIME_TABLE_NAME)); + String partition0Segment0 = "p0s0"; + String partition0Segment1 = "p0s1"; + String partition1Segment0 = "p1s0"; + _resourceManager.addNewSegment(REALTIME_TABLE_NAME, + SegmentMetadataMockUtils.mockSegmentMetadataWithPartitionInfo(RAW_TABLE_NAME, partition0Segment0, + PARTITION_COLUMN, 0), "downloadUrl"); + _resourceManager.addNewSegment(REALTIME_TABLE_NAME, + SegmentMetadataMockUtils.mockSegmentMetadataWithPartitionInfo(RAW_TABLE_NAME, partition0Segment1, + PARTITION_COLUMN, 0), "downloadUrl"); + _resourceManager.addNewSegment(REALTIME_TABLE_NAME, + SegmentMetadataMockUtils.mockSegmentMetadataWithPartitionInfo(RAW_TABLE_NAME, partition1Segment0, + PARTITION_COLUMN, 1), "downloadUrl"); + + IdealState idealState = _resourceManager.getTableIdealState(REALTIME_TABLE_NAME); + assertNotNull(idealState); Set segments = idealState.getPartitionSet(); - Assert.assertEquals(segments.size(), 5); - Assert.assertTrue(segments.contains(partition0Segment0)); - Assert.assertTrue(segments.contains(partition0Segment1)); - Assert.assertTrue(segments.contains(partition1Segment1)); + // 2 consuming segments, 3 uploaded segments + assertEquals(segments.size(), 5); + assertTrue(segments.contains(partition0Segment0)); + assertTrue(segments.contains(partition0Segment1)); + assertTrue(segments.contains(partition1Segment0)); // Check the segments of the same partition is assigned to the same set of servers. - Map> segmentAssignment = new HashMap<>(); + Map> partitionIdToServersMap = new HashMap<>(); for (String segment : segments) { - Integer partitionId; + int partitionId; LLCSegmentName llcSegmentName = LLCSegmentName.of(segment); if (llcSegmentName != null) { partitionId = llcSegmentName.getPartitionGroupId(); } else { - partitionId = segment2PartitionId.get(segment); + partitionId = Integer.parseInt(segment.substring(1, 2)); } - Assert.assertNotNull(partitionId); Set instances = idealState.getInstanceSet(segment); - if (segmentAssignment.containsKey(partitionId)) { - Assert.assertEquals(instances, segmentAssignment.get(partitionId)); + if (partitionIdToServersMap.containsKey(partitionId)) { + assertEquals(instances, partitionIdToServersMap.get(partitionId)); } else { - segmentAssignment.put(partitionId, instances); + partitionIdToServersMap.put(partitionId, instances); } } } @AfterClass public void tearDown() { - TEST_INSTANCE.cleanup(); + _testInstance.cleanup(); } } diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/RealtimeConsumerMonitorTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/RealtimeConsumerMonitorTest.java index 1450c02a35a2..26928deb3c0f 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/RealtimeConsumerMonitorTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/RealtimeConsumerMonitorTest.java @@ -18,9 +18,6 @@ */ package org.apache.pinot.controller.helix; -import com.google.common.collect.ImmutableMap; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.TreeMap; @@ -30,6 +27,7 @@ import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.common.metrics.ControllerGauge; import org.apache.pinot.common.metrics.ControllerMetrics; +import org.apache.pinot.common.metrics.MetricValueUtils; import org.apache.pinot.common.utils.LLCSegmentName; import org.apache.pinot.controller.ControllerConf; import org.apache.pinot.controller.LeadControllerManager; @@ -42,7 +40,6 @@ import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.apache.pinot.spi.utils.builder.TableNameBuilder; -import org.testng.Assert; import org.testng.annotations.Test; import static org.mockito.ArgumentMatchers.any; @@ -50,6 +47,7 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; public class RealtimeConsumerMonitorTest { @@ -57,17 +55,16 @@ public class RealtimeConsumerMonitorTest { @Test public void realtimeBasicTest() throws Exception { - final String tableName = "myTable_REALTIME"; - final String rawTableName = TableNameBuilder.extractRawTableName(tableName); - List allTableNames = new ArrayList(); - allTableNames.add(tableName); + String rawTableName = "myTable"; + String realtimeTableName = TableNameBuilder.REALTIME.tableNameWithType(rawTableName); TableConfig tableConfig = - new TableConfigBuilder(TableType.REALTIME).setTableName(tableName).setTimeColumnName("timeColumn").setLLC(true) + new TableConfigBuilder(TableType.REALTIME).setTableName(rawTableName).setTimeColumnName("timeColumn") .setNumReplicas(2).setStreamConfigs(getStreamConfigMap()).build(); + LLCSegmentName segmentPartition1Seq0 = new LLCSegmentName(rawTableName, 1, 0, System.currentTimeMillis()); LLCSegmentName segmentPartition1Seq1 = new LLCSegmentName(rawTableName, 1, 1, System.currentTimeMillis()); LLCSegmentName segmentPartition2Seq0 = new LLCSegmentName(rawTableName, 2, 0, System.currentTimeMillis()); - IdealState idealState = new IdealState(tableName); + IdealState idealState = new IdealState(realtimeTableName); idealState.setPartitionState(segmentPartition1Seq0.getSegmentName(), "pinot1", "ONLINE"); idealState.setPartitionState(segmentPartition1Seq0.getSegmentName(), "pinot2", "ONLINE"); idealState.setPartitionState(segmentPartition1Seq1.getSegmentName(), "pinot1", "CONSUMING"); @@ -77,7 +74,7 @@ public void realtimeBasicTest() idealState.setReplicas("3"); idealState.setRebalanceMode(IdealState.RebalanceMode.CUSTOMIZED); - ExternalView externalView = new ExternalView(tableName); + ExternalView externalView = new ExternalView(realtimeTableName); externalView.setState(segmentPartition1Seq0.getSegmentName(), "pinot1", "ONLINE"); externalView.setState(segmentPartition1Seq0.getSegmentName(), "pinot2", "ONLINE"); externalView.setState(segmentPartition1Seq1.getSegmentName(), "pinot1", "CONSUMING"); @@ -89,11 +86,11 @@ public void realtimeBasicTest() { helixResourceManager = mock(PinotHelixResourceManager.class); ZkHelixPropertyStore helixPropertyStore = mock(ZkHelixPropertyStore.class); - when(helixResourceManager.getTableConfig(tableName)).thenReturn(tableConfig); + when(helixResourceManager.getTableConfig(realtimeTableName)).thenReturn(tableConfig); when(helixResourceManager.getPropertyStore()).thenReturn(helixPropertyStore); - when(helixResourceManager.getAllTables()).thenReturn(allTableNames); - when(helixResourceManager.getTableIdealState(tableName)).thenReturn(idealState); - when(helixResourceManager.getTableExternalView(tableName)).thenReturn(externalView); + when(helixResourceManager.getAllTables()).thenReturn(List.of(realtimeTableName)); + when(helixResourceManager.getTableIdealState(realtimeTableName)).thenReturn(idealState); + when(helixResourceManager.getTableExternalView(realtimeTableName)).thenReturn(externalView); ZNRecord znRecord = new ZNRecord("0"); znRecord.setSimpleField(CommonConstants.Segment.Realtime.END_OFFSET, "10000"); when(helixPropertyStore.get(anyString(), any(), anyInt())).thenReturn(znRecord); @@ -117,61 +114,53 @@ public void realtimeBasicTest() // So, the consumer monitor should show: 1. partition-1 has 0 lag; partition-2 has some non-zero lag. // Segment 1 in replicas: TreeMap> response = new TreeMap<>(); - List part1ServerConsumingSegmentInfo = new ArrayList<>(2); - part1ServerConsumingSegmentInfo.add( - getConsumingSegmentInfoForServer("pinot1", "1", "100", "100", "0")); - part1ServerConsumingSegmentInfo.add( - getConsumingSegmentInfoForServer("pinot2", "1", "100", "100", "0")); - + List part1ServerConsumingSegmentInfo = + List.of(getConsumingSegmentInfoForServer("pinot1", "1", "100", "100", "0"), + getConsumingSegmentInfoForServer("pinot2", "1", "100", "100", "0")); response.put(segmentPartition1Seq1.getSegmentName(), part1ServerConsumingSegmentInfo); // Segment 2 in replicas - List part2ServerConsumingSegmentInfo = new ArrayList<>(2); - part2ServerConsumingSegmentInfo.add( - getConsumingSegmentInfoForServer("pinot1", "2", "120", "120", "0")); - part2ServerConsumingSegmentInfo.add( - getConsumingSegmentInfoForServer("pinot2", "2", "80", "120", "60000")); - + List part2ServerConsumingSegmentInfo = + List.of(getConsumingSegmentInfoForServer("pinot1", "2", "120", "120", "0"), + getConsumingSegmentInfoForServer("pinot2", "2", "80", "120", "60000")); response.put(segmentPartition2Seq0.getSegmentName(), part2ServerConsumingSegmentInfo); ConsumingSegmentInfoReader consumingSegmentReader = mock(ConsumingSegmentInfoReader.class); - when(consumingSegmentReader.getConsumingSegmentsInfo(tableName, 10000)) - .thenReturn(new ConsumingSegmentInfoReader.ConsumingSegmentsInfoMap(response)); + when(consumingSegmentReader.getConsumingSegmentsInfo(realtimeTableName, 10000)).thenReturn( + new ConsumingSegmentInfoReader.ConsumingSegmentsInfoMap(response, 0, 0)); RealtimeConsumerMonitor realtimeConsumerMonitor = - new RealtimeConsumerMonitor(config, helixResourceManager, leadControllerManager, - controllerMetrics, consumingSegmentReader); + new RealtimeConsumerMonitor(config, helixResourceManager, leadControllerManager, controllerMetrics, + consumingSegmentReader); realtimeConsumerMonitor.start(); realtimeConsumerMonitor.run(); - Assert.assertEquals(controllerMetrics.getValueOfPartitionGauge(tableName, 1, + + assertEquals(MetricValueUtils.getPartitionGaugeValue(controllerMetrics, realtimeTableName, 1, ControllerGauge.MAX_RECORDS_LAG), 0); - Assert.assertEquals(controllerMetrics.getValueOfPartitionGauge(tableName, 2, + assertEquals(MetricValueUtils.getPartitionGaugeValue(controllerMetrics, realtimeTableName, 2, ControllerGauge.MAX_RECORDS_LAG), 40); - Assert.assertEquals(controllerMetrics.getValueOfPartitionGauge(tableName, 1, + assertEquals(MetricValueUtils.getPartitionGaugeValue(controllerMetrics, realtimeTableName, 1, ControllerGauge.MAX_RECORD_AVAILABILITY_LAG_MS), 0); - Assert.assertEquals(controllerMetrics.getValueOfPartitionGauge(tableName, 2, + assertEquals(MetricValueUtils.getPartitionGaugeValue(controllerMetrics, realtimeTableName, 2, ControllerGauge.MAX_RECORD_AVAILABILITY_LAG_MS), 60000); } ConsumingSegmentInfoReader.ConsumingSegmentInfo getConsumingSegmentInfoForServer(String serverName, String partitionId, String currentOffset, String upstreamLatestOffset, String availabilityLagMs) { - Map currentOffsetMap = Collections.singletonMap(partitionId, currentOffset); - Map latestUpstreamOffsetMap = Collections.singletonMap(partitionId, upstreamLatestOffset); - Map recordsLagMap = Collections.singletonMap(partitionId, String.valueOf( - Long.parseLong(upstreamLatestOffset) - Long.parseLong(currentOffset))); - Map availabilityLagMsMap = Collections.singletonMap(partitionId, availabilityLagMs); + Map currentOffsetMap = Map.of(partitionId, currentOffset); + Map latestUpstreamOffsetMap = Map.of(partitionId, upstreamLatestOffset); + Map recordsLagMap = + Map.of(partitionId, String.valueOf(Long.parseLong(upstreamLatestOffset) - Long.parseLong(currentOffset))); + Map availabilityLagMsMap = Map.of(partitionId, availabilityLagMs); ConsumingSegmentInfoReader.PartitionOffsetInfo partitionOffsetInfo = new ConsumingSegmentInfoReader.PartitionOffsetInfo(currentOffsetMap, latestUpstreamOffsetMap, recordsLagMap, availabilityLagMsMap); - return new ConsumingSegmentInfoReader.ConsumingSegmentInfo(serverName, "CONSUMING", -1, - currentOffsetMap, partitionOffsetInfo); + return new ConsumingSegmentInfoReader.ConsumingSegmentInfo(serverName, "CONSUMING", -1, currentOffsetMap, + partitionOffsetInfo); } Map getStreamConfigMap() { - return ImmutableMap.of( - "streamType", "kafka", - "stream.kafka.consumer.type", "simple", - "stream.kafka.topic.name", "test", + return Map.of("streamType", "kafka", "stream.kafka.consumer.type", "simple", "stream.kafka.topic.name", "test", "stream.kafka.decoder.class.name", "org.apache.pinot.plugin.stream.kafka.KafkaAvroMessageDecoder", "stream.kafka.consumer.factory.class.name", "org.apache.pinot.core.realtime.impl.fakestream.FakeStreamConsumerFactory"); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/SegmentStatusCheckerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/SegmentStatusCheckerTest.java index 83147e677c08..3161c9da200a 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/SegmentStatusCheckerTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/SegmentStatusCheckerTest.java @@ -18,10 +18,6 @@ */ package org.apache.pinot.controller.helix; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; @@ -39,6 +35,7 @@ import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; import org.apache.pinot.common.metrics.ControllerGauge; import org.apache.pinot.common.metrics.ControllerMetrics; +import org.apache.pinot.common.metrics.MetricValueUtils; import org.apache.pinot.common.utils.LLCSegmentName; import org.apache.pinot.controller.ControllerConf; import org.apache.pinot.controller.LeadControllerManager; @@ -51,15 +48,21 @@ import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.apache.pinot.spi.utils.builder.TableNameBuilder; -import org.testng.Assert; import org.testng.annotations.Test; -import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; public class SegmentStatusCheckerTest { + private final ExecutorService _executorService = Executors.newFixedThreadPool(1); + private SegmentStatusChecker _segmentStatusChecker; private PinotHelixResourceManager _helixResourceManager; private ZkHelixPropertyStore _helixPropertyStore; @@ -68,18 +71,15 @@ public class SegmentStatusCheckerTest { private ControllerMetrics _controllerMetrics; private ControllerConf _config; private TableSizeReader _tableSizeReader; - private ExecutorService _executorService = Executors.newFixedThreadPool(1); @Test public void offlineBasicTest() throws Exception { - final String tableName = "myTable_OFFLINE"; - List allTableNames = new ArrayList(); - allTableNames.add(tableName); + String offlineTableName = "myTable_OFFLINE"; TableConfig tableConfig = - new TableConfigBuilder(TableType.OFFLINE).setTableName(tableName).setNumReplicas(2).build(); + new TableConfigBuilder(TableType.OFFLINE).setTableName(offlineTableName).setNumReplicas(2).build(); - IdealState idealState = new IdealState(tableName); + IdealState idealState = new IdealState(offlineTableName); idealState.setPartitionState("myTable_0", "pinot1", "ONLINE"); idealState.setPartitionState("myTable_0", "pinot2", "ONLINE"); idealState.setPartitionState("myTable_0", "pinot3", "ONLINE"); @@ -96,7 +96,7 @@ public void offlineBasicTest() idealState.setReplicas("2"); idealState.setRebalanceMode(IdealState.RebalanceMode.CUSTOMIZED); - ExternalView externalView = new ExternalView(tableName); + ExternalView externalView = new ExternalView(offlineTableName); externalView.setState("myTable_0", "pinot1", "ONLINE"); externalView.setState("myTable_0", "pinot2", "ONLINE"); externalView.setState("myTable_1", "pinot1", "ERROR"); @@ -109,25 +109,23 @@ public void offlineBasicTest() { _helixResourceManager = mock(PinotHelixResourceManager.class); - when(_helixResourceManager.getAllTables()).thenReturn(allTableNames); - when(_helixResourceManager.getTableConfig(tableName)).thenReturn(tableConfig); - when(_helixResourceManager.getTableIdealState(tableName)).thenReturn(idealState); - when(_helixResourceManager.getTableExternalView(tableName)).thenReturn(externalView); + when(_helixResourceManager.getAllTables()).thenReturn(List.of(offlineTableName)); + when(_helixResourceManager.getTableConfig(offlineTableName)).thenReturn(tableConfig); + when(_helixResourceManager.getTableIdealState(offlineTableName)).thenReturn(idealState); + when(_helixResourceManager.getTableExternalView(offlineTableName)).thenReturn(externalView); } { _helixPropertyStore = mock(ZkHelixPropertyStore.class); when(_helixResourceManager.getPropertyStore()).thenReturn(_helixPropertyStore); // Based on the lineage entries: {myTable_1 -> myTable_3, COMPLETED}, {myTable_3 -> myTable_4, IN_PROGRESS}, // myTable_1 and myTable_4 will be skipped for the metrics. - SegmentLineage segmentLineage = new SegmentLineage(tableName); + SegmentLineage segmentLineage = new SegmentLineage(offlineTableName); segmentLineage.addLineageEntry(SegmentLineageUtils.generateLineageEntryId(), - new LineageEntry(Collections.singletonList("myTable_1"), Collections.singletonList("myTable_3"), - LineageEntryState.COMPLETED, 11111L)); + new LineageEntry(List.of("myTable_1"), List.of("myTable_3"), LineageEntryState.COMPLETED, 11111L)); segmentLineage.addLineageEntry(SegmentLineageUtils.generateLineageEntryId(), - new LineageEntry(Collections.singletonList("myTable_3"), Collections.singletonList("myTable_4"), - LineageEntryState.IN_PROGRESS, 11111L)); - when(_helixPropertyStore.get(eq("/SEGMENT_LINEAGE/" + tableName), any(), eq(AccessOption.PERSISTENT))) - .thenReturn(segmentLineage.toZNRecord()); + new LineageEntry(List.of("myTable_3"), List.of("myTable_4"), LineageEntryState.IN_PROGRESS, 11111L)); + when(_helixPropertyStore.get(eq("/SEGMENT_LINEAGE/" + offlineTableName), any(), + eq(AccessOption.PERSISTENT))).thenReturn(segmentLineage.toZNRecord()); } { _config = mock(ControllerConf.class); @@ -142,6 +140,7 @@ public void offlineBasicTest() _tableSizeReader = mock(TableSizeReader.class); when(_tableSizeReader.getTableSizeDetails(anyString(), anyInt())).thenReturn(null); } + PinotMetricUtils.cleanUp(); _metricsRegistry = PinotMetricUtils.getPinotMetricsRegistry(); _controllerMetrics = new ControllerMetrics(_metricsRegistry); _segmentStatusChecker = @@ -150,41 +149,41 @@ public void offlineBasicTest() _segmentStatusChecker.setTableSizeReader(_tableSizeReader); _segmentStatusChecker.start(); _segmentStatusChecker.run(); - Assert.assertEquals(_controllerMetrics.getValueOfTableGauge(tableName, ControllerGauge.REPLICATION_FROM_CONFIG), 2); - Assert - .assertEquals(_controllerMetrics.getValueOfTableGauge(externalView.getId(), ControllerGauge.SEGMENT_COUNT), 3); - Assert.assertEquals( - _controllerMetrics.getValueOfTableGauge(externalView.getId(), ControllerGauge.SEGMENT_COUNT_INCLUDING_REPLACED), - 5); - Assert.assertEquals( - _controllerMetrics.getValueOfTableGauge(externalView.getId(), ControllerGauge.SEGMENTS_IN_ERROR_STATE), 1); - Assert - .assertEquals(_controllerMetrics.getValueOfTableGauge(externalView.getId(), ControllerGauge.NUMBER_OF_REPLICAS), - 2); - Assert - .assertEquals( - _controllerMetrics.getValueOfTableGauge(externalView.getId(), ControllerGauge.PERCENT_OF_REPLICAS), - 66); - Assert.assertEquals( - _controllerMetrics.getValueOfTableGauge(externalView.getId(), ControllerGauge.PERCENT_SEGMENTS_AVAILABLE), 100); - Assert.assertEquals( - _controllerMetrics.getValueOfTableGauge(externalView.getId(), ControllerGauge.TABLE_COMPRESSED_SIZE), 0); + + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, offlineTableName, + ControllerGauge.REPLICATION_FROM_CONFIG), 2); + assertEquals( + MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), ControllerGauge.SEGMENT_COUNT), + 3); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.SEGMENT_COUNT_INCLUDING_REPLACED), 5); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.SEGMENTS_IN_ERROR_STATE), 1); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.SEGMENTS_WITH_LESS_REPLICAS), 2); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.NUMBER_OF_REPLICAS), 2); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.PERCENT_OF_REPLICAS), 66); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.PERCENT_SEGMENTS_AVAILABLE), 100); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.TABLE_COMPRESSED_SIZE), 0); } @Test public void realtimeBasicTest() throws Exception { - final String tableName = "myTable_REALTIME"; - final String rawTableName = TableNameBuilder.extractRawTableName(tableName); - List allTableNames = new ArrayList(); - allTableNames.add(tableName); + String rawTableName = "myTable"; + String realtimeTableName = TableNameBuilder.REALTIME.tableNameWithType(rawTableName); TableConfig tableConfig = - new TableConfigBuilder(TableType.REALTIME).setTableName(tableName).setTimeColumnName("timeColumn").setLLC(true) + new TableConfigBuilder(TableType.REALTIME).setTableName(rawTableName).setTimeColumnName("timeColumn") .setNumReplicas(3).setStreamConfigs(getStreamConfigMap()).build(); - final LLCSegmentName seg1 = new LLCSegmentName(rawTableName, 1, 0, System.currentTimeMillis()); - final LLCSegmentName seg2 = new LLCSegmentName(rawTableName, 1, 1, System.currentTimeMillis()); - final LLCSegmentName seg3 = new LLCSegmentName(rawTableName, 2, 1, System.currentTimeMillis()); - IdealState idealState = new IdealState(tableName); + + LLCSegmentName seg1 = new LLCSegmentName(rawTableName, 1, 0, System.currentTimeMillis()); + LLCSegmentName seg2 = new LLCSegmentName(rawTableName, 1, 1, System.currentTimeMillis()); + LLCSegmentName seg3 = new LLCSegmentName(rawTableName, 2, 1, System.currentTimeMillis()); + IdealState idealState = new IdealState(realtimeTableName); idealState.setPartitionState(seg1.getSegmentName(), "pinot1", "ONLINE"); idealState.setPartitionState(seg1.getSegmentName(), "pinot2", "ONLINE"); idealState.setPartitionState(seg1.getSegmentName(), "pinot3", "ONLINE"); @@ -197,7 +196,7 @@ public void realtimeBasicTest() idealState.setReplicas("3"); idealState.setRebalanceMode(IdealState.RebalanceMode.CUSTOMIZED); - ExternalView externalView = new ExternalView(tableName); + ExternalView externalView = new ExternalView(realtimeTableName); externalView.setState(seg1.getSegmentName(), "pinot1", "ONLINE"); externalView.setState(seg1.getSegmentName(), "pinot2", "ONLINE"); externalView.setState(seg1.getSegmentName(), "pinot3", "ONLINE"); @@ -211,11 +210,11 @@ public void realtimeBasicTest() { _helixResourceManager = mock(PinotHelixResourceManager.class); _helixPropertyStore = mock(ZkHelixPropertyStore.class); - when(_helixResourceManager.getTableConfig(tableName)).thenReturn(tableConfig); + when(_helixResourceManager.getTableConfig(realtimeTableName)).thenReturn(tableConfig); when(_helixResourceManager.getPropertyStore()).thenReturn(_helixPropertyStore); - when(_helixResourceManager.getAllTables()).thenReturn(allTableNames); - when(_helixResourceManager.getTableIdealState(tableName)).thenReturn(idealState); - when(_helixResourceManager.getTableExternalView(tableName)).thenReturn(externalView); + when(_helixResourceManager.getAllTables()).thenReturn(List.of(realtimeTableName)); + when(_helixResourceManager.getTableIdealState(realtimeTableName)).thenReturn(idealState); + when(_helixResourceManager.getTableExternalView(realtimeTableName)).thenReturn(externalView); ZNRecord znRecord = new ZNRecord("0"); znRecord.setSimpleField(CommonConstants.Segment.Realtime.END_OFFSET, "10000"); when(_helixPropertyStore.get(anyString(), any(), anyInt())).thenReturn(znRecord); @@ -233,6 +232,7 @@ public void realtimeBasicTest() _tableSizeReader = mock(TableSizeReader.class); when(_tableSizeReader.getTableSizeDetails(anyString(), anyInt())).thenReturn(null); } + PinotMetricUtils.cleanUp(); _metricsRegistry = PinotMetricUtils.getPinotMetricsRegistry(); _controllerMetrics = new ControllerMetrics(_metricsRegistry); _segmentStatusChecker = @@ -241,27 +241,25 @@ public void realtimeBasicTest() _segmentStatusChecker.setTableSizeReader(_tableSizeReader); _segmentStatusChecker.start(); _segmentStatusChecker.run(); - Assert.assertEquals(_controllerMetrics.getValueOfTableGauge(tableName, ControllerGauge.REPLICATION_FROM_CONFIG), 3); - Assert.assertEquals( - _controllerMetrics.getValueOfTableGauge(externalView.getId(), ControllerGauge.SEGMENTS_IN_ERROR_STATE), 0); - Assert - .assertEquals(_controllerMetrics.getValueOfTableGauge(externalView.getId(), ControllerGauge.NUMBER_OF_REPLICAS), - 3); - Assert - .assertEquals( - _controllerMetrics.getValueOfTableGauge(externalView.getId(), ControllerGauge.PERCENT_OF_REPLICAS), - 100); - Assert.assertEquals( - _controllerMetrics.getValueOfTableGauge(externalView.getId(), ControllerGauge.PERCENT_SEGMENTS_AVAILABLE), 100); - Assert.assertEquals(_controllerMetrics - .getValueOfTableGauge(externalView.getId(), ControllerGauge.MISSING_CONSUMING_SEGMENT_TOTAL_COUNT), 2); + + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, realtimeTableName, + ControllerGauge.REPLICATION_FROM_CONFIG), 3); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.SEGMENTS_IN_ERROR_STATE), 0); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.SEGMENTS_WITH_LESS_REPLICAS), 0); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.NUMBER_OF_REPLICAS), 3); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.PERCENT_OF_REPLICAS), 100); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.PERCENT_SEGMENTS_AVAILABLE), 100); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.MISSING_CONSUMING_SEGMENT_TOTAL_COUNT), 2); } Map getStreamConfigMap() { - return ImmutableMap.of( - "streamType", "kafka", - "stream.kafka.consumer.type", "simple", - "stream.kafka.topic.name", "test", + return Map.of("streamType", "kafka", "stream.kafka.consumer.type", "simple", "stream.kafka.topic.name", "test", "stream.kafka.decoder.class.name", "org.apache.pinot.plugin.stream.kafka.KafkaAvroMessageDecoder", "stream.kafka.consumer.factory.class.name", "org.apache.pinot.core.realtime.impl.fakestream.FakeStreamConsumerFactory"); @@ -271,8 +269,7 @@ Map getStreamConfigMap() { public void missingEVPartitionTest() throws Exception { String offlineTableName = "myTable_OFFLINE"; - List allTableNames = new ArrayList(); - allTableNames.add(offlineTableName); + IdealState idealState = new IdealState(offlineTableName); idealState.setPartitionState("myTable_0", "pinot1", "ONLINE"); idealState.setPartitionState("myTable_0", "pinot2", "ONLINE"); @@ -307,19 +304,19 @@ public void missingEVPartitionTest() ZkHelixPropertyStore propertyStore; { propertyStore = (ZkHelixPropertyStore) mock(ZkHelixPropertyStore.class); - when(propertyStore.get("/SEGMENTS/myTable_OFFLINE/myTable_3", null, AccessOption.PERSISTENT)) - .thenReturn(znrecord); + when(propertyStore.get("/SEGMENTS/myTable_OFFLINE/myTable_3", null, AccessOption.PERSISTENT)).thenReturn( + znrecord); } { _helixResourceManager = mock(PinotHelixResourceManager.class); _helixPropertyStore = mock(ZkHelixPropertyStore.class); when(_helixResourceManager.getPropertyStore()).thenReturn(_helixPropertyStore); - when(_helixResourceManager.getAllTables()).thenReturn(allTableNames); + when(_helixResourceManager.getAllTables()).thenReturn(List.of(offlineTableName)); when(_helixResourceManager.getTableIdealState(offlineTableName)).thenReturn(idealState); when(_helixResourceManager.getTableExternalView(offlineTableName)).thenReturn(externalView); - when(_helixResourceManager.getSegmentZKMetadata(offlineTableName, "myTable_3")) - .thenReturn(new SegmentZKMetadata(znrecord)); + when(_helixResourceManager.getSegmentZKMetadata(offlineTableName, "myTable_3")).thenReturn( + new SegmentZKMetadata(znrecord)); } { _config = mock(ControllerConf.class); @@ -334,6 +331,7 @@ public void missingEVPartitionTest() _tableSizeReader = mock(TableSizeReader.class); when(_tableSizeReader.getTableSizeDetails(anyString(), anyInt())).thenReturn(null); } + PinotMetricUtils.cleanUp(); _metricsRegistry = PinotMetricUtils.getPinotMetricsRegistry(); _controllerMetrics = new ControllerMetrics(_metricsRegistry); _segmentStatusChecker = @@ -342,24 +340,25 @@ public void missingEVPartitionTest() _segmentStatusChecker.setTableSizeReader(_tableSizeReader); _segmentStatusChecker.start(); _segmentStatusChecker.run(); - Assert.assertEquals( - _controllerMetrics.getValueOfTableGauge(externalView.getId(), ControllerGauge.SEGMENTS_IN_ERROR_STATE), 1); - Assert - .assertEquals(_controllerMetrics.getValueOfTableGauge(externalView.getId(), ControllerGauge.NUMBER_OF_REPLICAS), - 0); - Assert.assertEquals( - _controllerMetrics.getValueOfTableGauge(externalView.getId(), ControllerGauge.PERCENT_SEGMENTS_AVAILABLE), 75); - Assert.assertEquals( - _controllerMetrics.getValueOfTableGauge(externalView.getId(), ControllerGauge.TABLE_COMPRESSED_SIZE), 1111); + + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.SEGMENTS_IN_ERROR_STATE), 1); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.SEGMENTS_WITH_LESS_REPLICAS), 2); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.NUMBER_OF_REPLICAS), 0); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.PERCENT_SEGMENTS_AVAILABLE), 75); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.TABLE_COMPRESSED_SIZE), 1111); } @Test public void missingEVTest() throws Exception { - final String tableName = "myTable_REALTIME"; - List allTableNames = new ArrayList(); - allTableNames.add(tableName); - IdealState idealState = new IdealState(tableName); + String realtimeTableName = "myTable_REALTIME"; + + IdealState idealState = new IdealState(realtimeTableName); idealState.setPartitionState("myTable_0", "pinot1", "ONLINE"); idealState.setPartitionState("myTable_0", "pinot2", "ONLINE"); idealState.setPartitionState("myTable_0", "pinot3", "ONLINE"); @@ -374,9 +373,9 @@ public void missingEVTest() _helixResourceManager = mock(PinotHelixResourceManager.class); _helixPropertyStore = mock(ZkHelixPropertyStore.class); when(_helixResourceManager.getPropertyStore()).thenReturn(_helixPropertyStore); - when(_helixResourceManager.getAllTables()).thenReturn(allTableNames); - when(_helixResourceManager.getTableIdealState(tableName)).thenReturn(idealState); - when(_helixResourceManager.getTableExternalView(tableName)).thenReturn(null); + when(_helixResourceManager.getAllTables()).thenReturn(List.of(realtimeTableName)); + when(_helixResourceManager.getTableIdealState(realtimeTableName)).thenReturn(idealState); + when(_helixResourceManager.getTableExternalView(realtimeTableName)).thenReturn(null); } { _config = mock(ControllerConf.class); @@ -391,6 +390,7 @@ public void missingEVTest() _tableSizeReader = mock(TableSizeReader.class); when(_tableSizeReader.getTableSizeDetails(anyString(), anyInt())).thenReturn(null); } + PinotMetricUtils.cleanUp(); _metricsRegistry = PinotMetricUtils.getPinotMetricsRegistry(); _controllerMetrics = new ControllerMetrics(_metricsRegistry); _segmentStatusChecker = @@ -399,24 +399,28 @@ public void missingEVTest() _segmentStatusChecker.setTableSizeReader(_tableSizeReader); _segmentStatusChecker.start(); _segmentStatusChecker.run(); - Assert.assertEquals(_controllerMetrics.getValueOfTableGauge(tableName, ControllerGauge.SEGMENTS_IN_ERROR_STATE), 0); - Assert.assertEquals(_controllerMetrics.getValueOfTableGauge(tableName, ControllerGauge.NUMBER_OF_REPLICAS), 0); - Assert.assertEquals( - _controllerMetrics.getValueOfTableGauge(tableName, ControllerGauge.TABLE_COMPRESSED_SIZE), 0); + + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, realtimeTableName, + ControllerGauge.SEGMENTS_IN_ERROR_STATE), 0); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, realtimeTableName, + ControllerGauge.SEGMENTS_WITH_LESS_REPLICAS), 0); + assertEquals( + MetricValueUtils.getTableGaugeValue(_controllerMetrics, realtimeTableName, ControllerGauge.NUMBER_OF_REPLICAS), + 0); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, realtimeTableName, + ControllerGauge.TABLE_COMPRESSED_SIZE), 0); } @Test public void missingIdealTest() throws Exception { - final String tableName = "myTable_REALTIME"; - List allTableNames = new ArrayList<>(); - allTableNames.add(tableName); + String realtimeTableName = "myTable_REALTIME"; { _helixResourceManager = mock(PinotHelixResourceManager.class); - when(_helixResourceManager.getAllTables()).thenReturn(allTableNames); - when(_helixResourceManager.getTableIdealState(tableName)).thenReturn(null); - when(_helixResourceManager.getTableExternalView(tableName)).thenReturn(null); + when(_helixResourceManager.getAllTables()).thenReturn(List.of(realtimeTableName)); + when(_helixResourceManager.getTableIdealState(realtimeTableName)).thenReturn(null); + when(_helixResourceManager.getTableExternalView(realtimeTableName)).thenReturn(null); } { _config = mock(ControllerConf.class); @@ -431,6 +435,7 @@ public void missingIdealTest() _tableSizeReader = mock(TableSizeReader.class); when(_tableSizeReader.getTableSizeDetails(anyString(), anyInt())).thenReturn(null); } + PinotMetricUtils.cleanUp(); _metricsRegistry = PinotMetricUtils.getPinotMetricsRegistry(); _controllerMetrics = new ControllerMetrics(_metricsRegistry); _segmentStatusChecker = @@ -439,22 +444,24 @@ public void missingIdealTest() _segmentStatusChecker.setTableSizeReader(_tableSizeReader); _segmentStatusChecker.start(); _segmentStatusChecker.run(); - Assert.assertEquals(_controllerMetrics.getValueOfTableGauge(tableName, ControllerGauge.SEGMENTS_IN_ERROR_STATE), - Long.MIN_VALUE); - Assert.assertEquals(_controllerMetrics.getValueOfTableGauge(tableName, ControllerGauge.NUMBER_OF_REPLICAS), - Long.MIN_VALUE); - Assert.assertEquals(_controllerMetrics.getValueOfTableGauge(tableName, ControllerGauge.PERCENT_OF_REPLICAS), - Long.MIN_VALUE); - Assert.assertEquals( - _controllerMetrics.getValueOfTableGauge(tableName, ControllerGauge.TABLE_COMPRESSED_SIZE), 0); + + assertFalse(MetricValueUtils.tableGaugeExists(_controllerMetrics, realtimeTableName, + ControllerGauge.SEGMENTS_IN_ERROR_STATE)); + assertFalse(MetricValueUtils.tableGaugeExists(_controllerMetrics, realtimeTableName, + ControllerGauge.SEGMENTS_WITH_LESS_REPLICAS)); + assertFalse( + MetricValueUtils.tableGaugeExists(_controllerMetrics, realtimeTableName, ControllerGauge.NUMBER_OF_REPLICAS)); + assertFalse( + MetricValueUtils.tableGaugeExists(_controllerMetrics, realtimeTableName, ControllerGauge.PERCENT_OF_REPLICAS)); + assertFalse(MetricValueUtils.tableGaugeExists(_controllerMetrics, realtimeTableName, + ControllerGauge.TABLE_COMPRESSED_SIZE)); } @Test public void missingEVPartitionPushTest() throws Exception { String offlineTableName = "myTable_OFFLINE"; - List allTableNames = new ArrayList(); - allTableNames.add(offlineTableName); + IdealState idealState = new IdealState(offlineTableName); idealState.setPartitionState("myTable_0", "pinot1", "ONLINE"); idealState.setPartitionState("myTable_1", "pinot1", "ONLINE"); @@ -501,13 +508,13 @@ public void missingEVPartitionPushTest() _helixResourceManager = mock(PinotHelixResourceManager.class); _helixPropertyStore = mock(ZkHelixPropertyStore.class); when(_helixResourceManager.getPropertyStore()).thenReturn(_helixPropertyStore); - when(_helixResourceManager.getAllTables()).thenReturn(allTableNames); + when(_helixResourceManager.getAllTables()).thenReturn(List.of(offlineTableName)); when(_helixResourceManager.getTableIdealState(offlineTableName)).thenReturn(idealState); when(_helixResourceManager.getTableExternalView(offlineTableName)).thenReturn(externalView); - when(_helixResourceManager.getSegmentZKMetadata(offlineTableName, "myTable_0")) - .thenReturn(new SegmentZKMetadata(znrecord)); - when(_helixResourceManager.getSegmentZKMetadata(offlineTableName, "myTable_2")) - .thenReturn(new SegmentZKMetadata(znrecord2)); + when(_helixResourceManager.getSegmentZKMetadata(offlineTableName, "myTable_0")).thenReturn( + new SegmentZKMetadata(znrecord)); + when(_helixResourceManager.getSegmentZKMetadata(offlineTableName, "myTable_2")).thenReturn( + new SegmentZKMetadata(znrecord2)); } { _config = mock(ControllerConf.class); @@ -522,6 +529,7 @@ public void missingEVPartitionPushTest() _tableSizeReader = mock(TableSizeReader.class); when(_tableSizeReader.getTableSizeDetails(anyString(), anyInt())).thenReturn(null); } + PinotMetricUtils.cleanUp(); _metricsRegistry = PinotMetricUtils.getPinotMetricsRegistry(); _controllerMetrics = new ControllerMetrics(_metricsRegistry); _segmentStatusChecker = @@ -530,28 +538,27 @@ public void missingEVPartitionPushTest() _segmentStatusChecker.setTableSizeReader(_tableSizeReader); _segmentStatusChecker.start(); _segmentStatusChecker.run(); - Assert.assertEquals( - _controllerMetrics.getValueOfTableGauge(externalView.getId(), ControllerGauge.SEGMENTS_IN_ERROR_STATE), 0); - Assert - .assertEquals(_controllerMetrics.getValueOfTableGauge(externalView.getId(), ControllerGauge.NUMBER_OF_REPLICAS), - 2); - Assert - .assertEquals( - _controllerMetrics.getValueOfTableGauge(externalView.getId(), ControllerGauge.PERCENT_OF_REPLICAS), - 100); - Assert.assertEquals( - _controllerMetrics.getValueOfTableGauge(externalView.getId(), ControllerGauge.PERCENT_SEGMENTS_AVAILABLE), 100); - Assert.assertEquals( - _controllerMetrics.getValueOfTableGauge(externalView.getId(), ControllerGauge.TABLE_COMPRESSED_SIZE), 0); + + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.SEGMENTS_IN_ERROR_STATE), 0); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.SEGMENTS_WITH_LESS_REPLICAS), 0); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.NUMBER_OF_REPLICAS), 2); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.PERCENT_OF_REPLICAS), 100); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.PERCENT_SEGMENTS_AVAILABLE), 100); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.TABLE_COMPRESSED_SIZE), 0); } @Test public void noReplicas() throws Exception { - final String tableName = "myTable_REALTIME"; - List allTableNames = new ArrayList(); - allTableNames.add(tableName); - IdealState idealState = new IdealState(tableName); + String realtimeTableName = "myTable_REALTIME"; + + IdealState idealState = new IdealState(realtimeTableName); idealState.setPartitionState("myTable_0", "pinot1", "OFFLINE"); idealState.setPartitionState("myTable_0", "pinot2", "OFFLINE"); idealState.setPartitionState("myTable_0", "pinot3", "OFFLINE"); @@ -562,9 +569,9 @@ public void noReplicas() _helixResourceManager = mock(PinotHelixResourceManager.class); _helixPropertyStore = mock(ZkHelixPropertyStore.class); when(_helixResourceManager.getPropertyStore()).thenReturn(_helixPropertyStore); - when(_helixResourceManager.getAllTables()).thenReturn(allTableNames); - when(_helixResourceManager.getTableIdealState(tableName)).thenReturn(idealState); - when(_helixResourceManager.getTableExternalView(tableName)).thenReturn(null); + when(_helixResourceManager.getAllTables()).thenReturn(List.of(realtimeTableName)); + when(_helixResourceManager.getTableIdealState(realtimeTableName)).thenReturn(idealState); + when(_helixResourceManager.getTableExternalView(realtimeTableName)).thenReturn(null); } { _config = mock(ControllerConf.class); @@ -579,6 +586,7 @@ public void noReplicas() _tableSizeReader = mock(TableSizeReader.class); when(_tableSizeReader.getTableSizeDetails(anyString(), anyInt())).thenReturn(null); } + PinotMetricUtils.cleanUp(); _metricsRegistry = PinotMetricUtils.getPinotMetricsRegistry(); _controllerMetrics = new ControllerMetrics(_metricsRegistry); _segmentStatusChecker = @@ -587,21 +595,26 @@ public void noReplicas() _segmentStatusChecker.setTableSizeReader(_tableSizeReader); _segmentStatusChecker.start(); _segmentStatusChecker.run(); - Assert.assertEquals(_controllerMetrics.getValueOfTableGauge(tableName, ControllerGauge.SEGMENTS_IN_ERROR_STATE), 0); - Assert.assertEquals(_controllerMetrics.getValueOfTableGauge(tableName, ControllerGauge.NUMBER_OF_REPLICAS), 1); - Assert.assertEquals(_controllerMetrics.getValueOfTableGauge(tableName, ControllerGauge.PERCENT_OF_REPLICAS), 100); - Assert.assertEquals(_controllerMetrics.getValueOfTableGauge(tableName, ControllerGauge.PERCENT_SEGMENTS_AVAILABLE), + + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, realtimeTableName, + ControllerGauge.SEGMENTS_IN_ERROR_STATE), 0); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, realtimeTableName, + ControllerGauge.SEGMENTS_WITH_LESS_REPLICAS), 0); + assertEquals( + MetricValueUtils.getTableGaugeValue(_controllerMetrics, realtimeTableName, ControllerGauge.NUMBER_OF_REPLICAS), + 1); + assertEquals( + MetricValueUtils.getTableGaugeValue(_controllerMetrics, realtimeTableName, ControllerGauge.PERCENT_OF_REPLICAS), 100); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, realtimeTableName, + ControllerGauge.PERCENT_SEGMENTS_AVAILABLE), 100); } @Test - public void disabledTableTest() - throws Exception { + public void disabledTableTest() { + String offlineTableName = "myTable_OFFLINE"; - final String tableName = "myTable_OFFLINE"; - List allTableNames = new ArrayList(); - allTableNames.add(tableName); - IdealState idealState = new IdealState(tableName); + IdealState idealState = new IdealState(offlineTableName); // disable table in idealstate idealState.enable(false); idealState.setPartitionState("myTable_OFFLINE", "pinot1", "OFFLINE"); @@ -612,9 +625,9 @@ public void disabledTableTest() { _helixResourceManager = mock(PinotHelixResourceManager.class); - when(_helixResourceManager.getAllTables()).thenReturn(allTableNames); - when(_helixResourceManager.getTableIdealState(tableName)).thenReturn(idealState); - when(_helixResourceManager.getTableExternalView(tableName)).thenReturn(null); + when(_helixResourceManager.getAllTables()).thenReturn(List.of(offlineTableName)); + when(_helixResourceManager.getTableIdealState(offlineTableName)).thenReturn(idealState); + when(_helixResourceManager.getTableExternalView(offlineTableName)).thenReturn(null); } { _config = mock(ControllerConf.class); @@ -625,26 +638,27 @@ public void disabledTableTest() _leadControllerManager = mock(LeadControllerManager.class); when(_leadControllerManager.isLeaderForTable(anyString())).thenReturn(true); } + PinotMetricUtils.cleanUp(); _metricsRegistry = PinotMetricUtils.getPinotMetricsRegistry(); _controllerMetrics = new ControllerMetrics(_metricsRegistry); _segmentStatusChecker = new SegmentStatusChecker(_helixResourceManager, _leadControllerManager, _config, _controllerMetrics, _executorService); + // verify state before test - Assert.assertEquals(_controllerMetrics.getValueOfGlobalGauge(ControllerGauge.DISABLED_TABLE_COUNT), 0); + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerMetrics, ControllerGauge.DISABLED_TABLE_COUNT), 0); + // update metrics _segmentStatusChecker.start(); _segmentStatusChecker.run(); - Assert.assertEquals(_controllerMetrics.getValueOfGlobalGauge(ControllerGauge.DISABLED_TABLE_COUNT), 1); + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerMetrics, ControllerGauge.DISABLED_TABLE_COUNT), 1); } @Test - public void disabledEmptyTableTest() - throws Exception { + public void disabledEmptyTableTest() { + String offlineTableName = "myTable_OFFLINE"; - final String tableName = "myTable_OFFLINE"; - List allTableNames = Lists.newArrayList(tableName); - IdealState idealState = new IdealState(tableName); + IdealState idealState = new IdealState(offlineTableName); // disable table in idealstate idealState.enable(false); idealState.setReplicas("1"); @@ -652,9 +666,9 @@ public void disabledEmptyTableTest() { _helixResourceManager = mock(PinotHelixResourceManager.class); - when(_helixResourceManager.getAllTables()).thenReturn(allTableNames); - when(_helixResourceManager.getTableIdealState(tableName)).thenReturn(idealState); - when(_helixResourceManager.getTableExternalView(tableName)).thenReturn(null); + when(_helixResourceManager.getAllTables()).thenReturn(List.of(offlineTableName)); + when(_helixResourceManager.getTableIdealState(offlineTableName)).thenReturn(idealState); + when(_helixResourceManager.getTableExternalView(offlineTableName)).thenReturn(null); } { _config = mock(ControllerConf.class); @@ -665,17 +679,20 @@ public void disabledEmptyTableTest() _leadControllerManager = mock(LeadControllerManager.class); when(_leadControllerManager.isLeaderForTable(anyString())).thenReturn(true); } + PinotMetricUtils.cleanUp(); _metricsRegistry = PinotMetricUtils.getPinotMetricsRegistry(); _controllerMetrics = new ControllerMetrics(_metricsRegistry); _segmentStatusChecker = new SegmentStatusChecker(_helixResourceManager, _leadControllerManager, _config, _controllerMetrics, _executorService); + // verify state before test - Assert.assertEquals(_controllerMetrics.getValueOfGlobalGauge(ControllerGauge.DISABLED_TABLE_COUNT), 0); + assertFalse(MetricValueUtils.globalGaugeExists(_controllerMetrics, ControllerGauge.DISABLED_TABLE_COUNT)); + // update metrics _segmentStatusChecker.start(); _segmentStatusChecker.run(); - Assert.assertEquals(_controllerMetrics.getValueOfGlobalGauge(ControllerGauge.DISABLED_TABLE_COUNT), 1); + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerMetrics, ControllerGauge.DISABLED_TABLE_COUNT), 1); } @Test @@ -686,26 +703,87 @@ public void noSegments() noSegmentsInternal(-1); } + @Test + public void lessThanOnePercentSegmentsUnavailableTest() + throws Exception { + String offlineTableName = "myTable_OFFLINE"; + TableConfig tableConfig = + new TableConfigBuilder(TableType.OFFLINE).setTableName(offlineTableName).setNumReplicas(1).build(); + + IdealState idealState = new IdealState(offlineTableName); + int numSegments = 200; + for (int i = 0; i < numSegments; i++) { + idealState.setPartitionState("myTable_" + i, "pinot1", "ONLINE"); + } + idealState.setReplicas("1"); + idealState.setRebalanceMode(IdealState.RebalanceMode.CUSTOMIZED); + + ExternalView externalView = new ExternalView(offlineTableName); + externalView.setState("myTable_0", "pinot1", "OFFLINE"); + for (int i = 1; i < numSegments; i++) { + externalView.setState("myTable_" + i, "pinot1", "ONLINE"); + } + + { + _helixResourceManager = mock(PinotHelixResourceManager.class); + when(_helixResourceManager.getAllTables()).thenReturn(List.of(offlineTableName)); + when(_helixResourceManager.getTableConfig(offlineTableName)).thenReturn(tableConfig); + when(_helixResourceManager.getTableIdealState(offlineTableName)).thenReturn(idealState); + when(_helixResourceManager.getTableExternalView(offlineTableName)).thenReturn(externalView); + } + { + _helixPropertyStore = mock(ZkHelixPropertyStore.class); + when(_helixResourceManager.getPropertyStore()).thenReturn(_helixPropertyStore); + SegmentLineage segmentLineage = new SegmentLineage(offlineTableName); + when(_helixPropertyStore.get(eq("/SEGMENT_LINEAGE/" + offlineTableName), any(), + eq(AccessOption.PERSISTENT))).thenReturn(segmentLineage.toZNRecord()); + } + { + _config = mock(ControllerConf.class); + when(_config.getStatusCheckerFrequencyInSeconds()).thenReturn(300); + when(_config.getStatusCheckerWaitForPushTimeInSeconds()).thenReturn(300); + } + { + _leadControllerManager = mock(LeadControllerManager.class); + when(_leadControllerManager.isLeaderForTable(anyString())).thenReturn(true); + } + { + _tableSizeReader = mock(TableSizeReader.class); + when(_tableSizeReader.getTableSizeDetails(anyString(), anyInt())).thenReturn(null); + } + PinotMetricUtils.cleanUp(); + _metricsRegistry = PinotMetricUtils.getPinotMetricsRegistry(); + _controllerMetrics = new ControllerMetrics(_metricsRegistry); + _segmentStatusChecker = + new SegmentStatusChecker(_helixResourceManager, _leadControllerManager, _config, _controllerMetrics, + _executorService); + _segmentStatusChecker.setTableSizeReader(_tableSizeReader); + _segmentStatusChecker.start(); + _segmentStatusChecker.run(); + + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, externalView.getId(), + ControllerGauge.PERCENT_SEGMENTS_AVAILABLE), 99); + } + public void noSegmentsInternal(final int nReplicas) throws Exception { - final String tableName = "myTable_REALTIME"; + String realtimeTableName = "myTable_REALTIME"; + String nReplicasStr = Integer.toString(nReplicas); int nReplicasExpectedValue = nReplicas; if (nReplicas < 0) { nReplicasStr = "abc"; nReplicasExpectedValue = 1; } - List allTableNames = new ArrayList(); - allTableNames.add(tableName); - IdealState idealState = new IdealState(tableName); + IdealState idealState = new IdealState(realtimeTableName); idealState.setReplicas(nReplicasStr); idealState.setRebalanceMode(IdealState.RebalanceMode.CUSTOMIZED); { _helixResourceManager = mock(PinotHelixResourceManager.class); - when(_helixResourceManager.getAllTables()).thenReturn(allTableNames); - when(_helixResourceManager.getTableIdealState(tableName)).thenReturn(idealState); - when(_helixResourceManager.getTableExternalView(tableName)).thenReturn(null); + when(_helixResourceManager.getAllTables()).thenReturn(List.of(realtimeTableName)); + when(_helixResourceManager.getTableIdealState(realtimeTableName)).thenReturn(idealState); + when(_helixResourceManager.getTableExternalView(realtimeTableName)).thenReturn(null); } { _config = mock(ControllerConf.class); @@ -720,6 +798,7 @@ public void noSegmentsInternal(final int nReplicas) _tableSizeReader = mock(TableSizeReader.class); when(_tableSizeReader.getTableSizeDetails(anyString(), anyInt())).thenReturn(null); } + PinotMetricUtils.cleanUp(); _metricsRegistry = PinotMetricUtils.getPinotMetricsRegistry(); _controllerMetrics = new ControllerMetrics(_metricsRegistry); _segmentStatusChecker = @@ -728,12 +807,18 @@ public void noSegmentsInternal(final int nReplicas) _segmentStatusChecker.setTableSizeReader(_tableSizeReader); _segmentStatusChecker.start(); _segmentStatusChecker.run(); - Assert.assertEquals(_controllerMetrics.getValueOfTableGauge(tableName, ControllerGauge.SEGMENTS_IN_ERROR_STATE), - Long.MIN_VALUE); - Assert.assertEquals(_controllerMetrics.getValueOfTableGauge(tableName, ControllerGauge.NUMBER_OF_REPLICAS), + + assertFalse(MetricValueUtils.tableGaugeExists(_controllerMetrics, realtimeTableName, + ControllerGauge.SEGMENTS_IN_ERROR_STATE)); + assertFalse(MetricValueUtils.tableGaugeExists(_controllerMetrics, realtimeTableName, + ControllerGauge.SEGMENTS_IN_ERROR_STATE)); + assertEquals( + MetricValueUtils.getTableGaugeValue(_controllerMetrics, realtimeTableName, ControllerGauge.NUMBER_OF_REPLICAS), nReplicasExpectedValue); - Assert.assertEquals(_controllerMetrics.getValueOfTableGauge(tableName, ControllerGauge.PERCENT_OF_REPLICAS), 100); - Assert.assertEquals(_controllerMetrics.getValueOfTableGauge(tableName, ControllerGauge.PERCENT_SEGMENTS_AVAILABLE), + assertEquals( + MetricValueUtils.getTableGaugeValue(_controllerMetrics, realtimeTableName, ControllerGauge.PERCENT_OF_REPLICAS), 100); + assertEquals(MetricValueUtils.getTableGaugeValue(_controllerMetrics, realtimeTableName, + ControllerGauge.PERCENT_SEGMENTS_AVAILABLE), 100); } } diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/TableCacheTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/TableCacheTest.java index 9f2024dd99c9..56fe2464cfb7 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/TableCacheTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/TableCacheTest.java @@ -43,7 +43,6 @@ public class TableCacheTest { private static final ControllerTest TEST_INSTANCE = ControllerTest.getInstance(); - private static final String SCHEMA_NAME = "cacheTestSchema"; private static final String RAW_TABLE_NAME = "cacheTestTable"; private static final String OFFLINE_TABLE_NAME = TableNameBuilder.OFFLINE.tableNameWithType(RAW_TABLE_NAME); private static final String REALTIME_TABLE_NAME = TableNameBuilder.REALTIME.tableNameWithType(RAW_TABLE_NAME); @@ -62,8 +61,6 @@ public void testTableCache(boolean isCaseInsensitive) throws Exception { TableCache tableCache = new TableCache(TEST_INSTANCE.getPropertyStore(), isCaseInsensitive); - assertNull(tableCache.getSchema(SCHEMA_NAME)); - assertNull(tableCache.getColumnNameMap(SCHEMA_NAME)); assertNull(tableCache.getSchema(RAW_TABLE_NAME)); assertNull(tableCache.getColumnNameMap(RAW_TABLE_NAME)); assertNull(tableCache.getTableConfig(OFFLINE_TABLE_NAME)); @@ -71,15 +68,15 @@ public void testTableCache(boolean isCaseInsensitive) // Add a schema Schema schema = - new Schema.SchemaBuilder().setSchemaName(SCHEMA_NAME).addSingleValueDimension("testColumn", DataType.INT) + new Schema.SchemaBuilder().setSchemaName(RAW_TABLE_NAME).addSingleValueDimension("testColumn", DataType.INT) .build(); TEST_INSTANCE.getHelixResourceManager().addSchema(schema, false, false); // Wait for at most 10 seconds for the callback to add the schema to the cache - TestUtils.waitForCondition(aVoid -> tableCache.getSchema(SCHEMA_NAME) != null, 10_000L, + TestUtils.waitForCondition(aVoid -> tableCache.getSchema(RAW_TABLE_NAME) != null, 10_000L, "Failed to add the schema to the cache"); // Schema can be accessed by the schema name, but not by the table name because table config is not added yet Schema expectedSchema = - new Schema.SchemaBuilder().setSchemaName(SCHEMA_NAME).addSingleValueDimension("testColumn", DataType.INT) + new Schema.SchemaBuilder().setSchemaName(RAW_TABLE_NAME).addSingleValueDimension("testColumn", DataType.INT) .addSingleValueDimension(BuiltInVirtualColumn.DOCID, DataType.INT) .addSingleValueDimension(BuiltInVirtualColumn.HOSTNAME, DataType.STRING) .addSingleValueDimension(BuiltInVirtualColumn.SEGMENTNAME, DataType.STRING).build(); @@ -88,16 +85,15 @@ public void testTableCache(boolean isCaseInsensitive) expectedColumnMap.put(isCaseInsensitive ? "$docid" : "$docId", "$docId"); expectedColumnMap.put(isCaseInsensitive ? "$hostname" : "$hostName", "$hostName"); expectedColumnMap.put(isCaseInsensitive ? "$segmentname" : "$segmentName", "$segmentName"); - assertEquals(tableCache.getSchema(SCHEMA_NAME), expectedSchema); - assertEquals(tableCache.getColumnNameMap(SCHEMA_NAME), expectedColumnMap); - assertNull(tableCache.getSchema(RAW_TABLE_NAME)); - assertNull(tableCache.getColumnNameMap(RAW_TABLE_NAME)); + assertEquals(tableCache.getSchema(RAW_TABLE_NAME), expectedSchema); + assertEquals(tableCache.getColumnNameMap(RAW_TABLE_NAME), expectedColumnMap); // Case-insensitive table name are handled based on the table config instead of the schema assertNull(tableCache.getActualTableName(RAW_TABLE_NAME)); // Add a table config TableConfig tableConfig = - new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setSchemaName(SCHEMA_NAME).build(); + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + TEST_INSTANCE.waitForEVToDisappear(tableConfig.getTableName()); TEST_INSTANCE.getHelixResourceManager().addTable(tableConfig); // Wait for at most 10 seconds for the callback to add the table config to the cache TestUtils.waitForCondition( @@ -108,8 +104,6 @@ public void testTableCache(boolean isCaseInsensitive) // It should only add OFFLINE and normal table. assertNull(tableCache.getActualTableName(REALTIME_TABLE_NAME)); // Schema can be accessed by both the schema name and the raw table name - assertEquals(tableCache.getSchema(SCHEMA_NAME), expectedSchema); - assertEquals(tableCache.getColumnNameMap(SCHEMA_NAME), expectedColumnMap); assertEquals(tableCache.getSchema(RAW_TABLE_NAME), expectedSchema); assertEquals(tableCache.getColumnNameMap(RAW_TABLE_NAME), expectedColumnMap); @@ -128,7 +122,7 @@ public void testTableCache(boolean isCaseInsensitive) // Update the schema schema.addField(new DimensionFieldSpec("newColumn", DataType.LONG, true)); - TEST_INSTANCE.getHelixResourceManager().updateSchema(schema, false); + TEST_INSTANCE.getHelixResourceManager().updateSchema(schema, false, false); // Wait for at most 10 seconds for the callback to update the schema in the cache // NOTE: // - Schema should never be null during the transitioning @@ -138,18 +132,14 @@ public void testTableCache(boolean isCaseInsensitive) expectedSchema.addField(new DimensionFieldSpec("newColumn", DataType.LONG, true)); expectedColumnMap.put(isCaseInsensitive ? "newcolumn" : "newColumn", "newColumn"); TestUtils.waitForCondition(aVoid -> { - assertNotNull(tableCache.getSchema(SCHEMA_NAME)); + assertNotNull(tableCache.getSchema(RAW_TABLE_NAME)); assertEquals(schemaChangeListener._schemaList.size(), 1); return schemaChangeListener._schemaList.get(0).equals(expectedSchema); }, 10_000L, "Failed to update the schema in the cache"); // Schema can be accessed by both the schema name and the raw table name - assertEquals(tableCache.getSchema(SCHEMA_NAME), expectedSchema); - assertEquals(tableCache.getColumnNameMap(SCHEMA_NAME), expectedColumnMap); assertEquals(tableCache.getSchema(RAW_TABLE_NAME), expectedSchema); assertEquals(tableCache.getColumnNameMap(RAW_TABLE_NAME), expectedColumnMap); - // Update the table config and drop the schema name - tableConfig.getValidationConfig().setSchemaName(null); TEST_INSTANCE.getHelixResourceManager().updateTableConfig(tableConfig); // Wait for at most 10 seconds for the callback to update the table config in the cache // NOTE: @@ -165,8 +155,8 @@ public void testTableCache(boolean isCaseInsensitive) // After dropping the schema name from the table config, schema can only be accessed by the schema name, but not by // the table name assertEquals(tableCache.getTableConfig(OFFLINE_TABLE_NAME), tableConfig); - assertNull(tableCache.getSchema(RAW_TABLE_NAME)); - assertNull(tableCache.getColumnNameMap(RAW_TABLE_NAME)); + assertNotNull(tableCache.getSchema(RAW_TABLE_NAME)); + assertNotNull(tableCache.getColumnNameMap(RAW_TABLE_NAME)); if (isCaseInsensitive) { assertEquals(tableCache.getActualTableName(MANGLED_RAW_TABLE_NAME), RAW_TABLE_NAME); assertEquals(tableCache.getActualTableName(MANGLED_OFFLINE_TABLE_NAME), OFFLINE_TABLE_NAME); @@ -177,8 +167,12 @@ public void testTableCache(boolean isCaseInsensitive) assertEquals(tableCache.getActualTableName(OFFLINE_TABLE_NAME), OFFLINE_TABLE_NAME); } assertNull(tableCache.getActualTableName(REALTIME_TABLE_NAME)); - assertEquals(tableCache.getSchema(SCHEMA_NAME), expectedSchema); - assertEquals(tableCache.getColumnNameMap(SCHEMA_NAME), expectedColumnMap); + assertEquals(tableCache.getSchema(RAW_TABLE_NAME), expectedSchema); + assertEquals(tableCache.getColumnNameMap(RAW_TABLE_NAME), expectedColumnMap); + + // Wait for external view to appear before deleting the table to prevent external view being created after the + // waitForEVToDisappear() call + TEST_INSTANCE.waitForEVToAppear(OFFLINE_TABLE_NAME); // Remove the table config TEST_INSTANCE.getHelixResourceManager().deleteOfflineTable(RAW_TABLE_NAME); @@ -190,25 +184,26 @@ public void testTableCache(boolean isCaseInsensitive) "Failed to remove the table config from the cache"); assertNull(tableCache.getTableConfig(OFFLINE_TABLE_NAME)); assertNull(tableCache.getActualTableName(RAW_TABLE_NAME)); - assertEquals(tableCache.getSchema(SCHEMA_NAME), expectedSchema); - assertEquals(tableCache.getColumnNameMap(SCHEMA_NAME), expectedColumnMap); - assertNull(tableCache.getSchema(RAW_TABLE_NAME)); - assertNull(tableCache.getColumnNameMap(RAW_TABLE_NAME)); + assertEquals(tableCache.getSchema(RAW_TABLE_NAME), expectedSchema); + assertEquals(tableCache.getColumnNameMap(RAW_TABLE_NAME), expectedColumnMap); // Remove the schema - TEST_INSTANCE.getHelixResourceManager().deleteSchema(schema); + TEST_INSTANCE.getHelixResourceManager().deleteSchema(RAW_TABLE_NAME); // Wait for at most 10 seconds for the callback to remove the schema from the cache // NOTE: // - Verify if the callback is fully done by checking the schema change lister because it is the last step of the // callback handling TestUtils.waitForCondition(aVoid -> schemaChangeListener._schemaList.isEmpty(), 10_000L, "Failed to remove the schema from the cache"); - assertNull(tableCache.getSchema(SCHEMA_NAME)); - assertNull(tableCache.getColumnNameMap(SCHEMA_NAME)); + assertNull(tableCache.getSchema(RAW_TABLE_NAME)); + assertNull(tableCache.getColumnNameMap(RAW_TABLE_NAME)); assertNull(tableCache.getSchema(RAW_TABLE_NAME)); assertNull(tableCache.getColumnNameMap(RAW_TABLE_NAME)); assertEquals(schemaChangeListener._schemaList.size(), 0); assertEquals(tableConfigChangeListener._tableConfigList.size(), 0); + + // Wait for external view to disappear to ensure a clean start for the next test + TEST_INSTANCE.waitForEVToDisappear(OFFLINE_TABLE_NAME); } @DataProvider(name = "testTableCacheDataProvider") diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManagerAssignmentTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManagerAssignmentTest.java new file mode 100644 index 000000000000..657d8b54cd1e --- /dev/null +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManagerAssignmentTest.java @@ -0,0 +1,171 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.controller.helix.core; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.apache.helix.model.IdealState; +import org.apache.helix.model.InstanceConfig; +import org.apache.pinot.common.metadata.ZKMetadataProvider; +import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; +import org.apache.pinot.common.tier.TierFactory; +import org.apache.pinot.common.utils.helix.HelixHelper; +import org.apache.pinot.controller.ControllerConf; +import org.apache.pinot.controller.helix.ControllerTest; +import org.apache.pinot.controller.utils.SegmentMetadataMockUtils; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.config.table.TierConfig; +import org.apache.pinot.spi.config.tenant.Tenant; +import org.apache.pinot.spi.config.tenant.TenantRole; +import org.apache.pinot.spi.utils.CommonConstants.Helix; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + + +@Test(groups = "stateless") +public class PinotHelixResourceManagerAssignmentTest extends ControllerTest { + private static final int NUM_BROKER_INSTANCES = 3; + private static final int NUM_OFFLINE_SERVER_INSTANCES = 2; + private static final int NUM_OFFLINE_COLD_SERVER_INSTANCES = 2; + private static final int NUM_REALTIME_SERVER_INSTANCES = 2; + private static final int NUM_SERVER_INSTANCES = + NUM_OFFLINE_SERVER_INSTANCES + NUM_REALTIME_SERVER_INSTANCES + NUM_OFFLINE_COLD_SERVER_INSTANCES; + private static final String BROKER_TENANT_NAME = "brokerTenant"; + private static final String SERVER_TENANT_NAME = "serverTenant"; + private static final String SERVER_COLD_TENANT_NAME = "coldServerTenant"; + + private static final String RAW_TABLE_NAME = "testTable"; + private static final String OFFLINE_TABLE_NAME = TableNameBuilder.OFFLINE.tableNameWithType(RAW_TABLE_NAME); + + @Override + protected void overrideControllerConf(Map properties) { + properties.put(ControllerConf.CONTROLLER_ENABLE_TIERED_SEGMENT_ASSIGNMENT, true); + properties.put(ControllerConf.CLUSTER_TENANT_ISOLATION_ENABLE, false); + } + + @BeforeClass + public void setUp() + throws Exception { + startZk(); + startController(); + + addFakeBrokerInstancesToAutoJoinHelixCluster(NUM_BROKER_INSTANCES, false); + addFakeServerInstancesToAutoJoinHelixCluster(NUM_SERVER_INSTANCES, false); + + resetBrokerTags(); + resetServerTags(); + + addDummySchema(RAW_TABLE_NAME); + } + + private void untagBrokers() { + for (String brokerInstance : _helixResourceManager.getAllInstancesForBrokerTenant(BROKER_TENANT_NAME)) { + _helixResourceManager.updateInstanceTags(brokerInstance, Helix.UNTAGGED_BROKER_INSTANCE, false); + } + } + + private void resetBrokerTags() { + untagBrokers(); + assertEquals(_helixResourceManager.getOnlineUnTaggedBrokerInstanceList().size(), NUM_BROKER_INSTANCES); + Tenant brokerTenant = new Tenant(TenantRole.BROKER, BROKER_TENANT_NAME, NUM_BROKER_INSTANCES, 0, 0); + _helixResourceManager.createBrokerTenant(brokerTenant); + assertEquals(_helixResourceManager.getOnlineUnTaggedBrokerInstanceList().size(), 0); + } + + private void untagServers() { + for (String serverInstance : _helixResourceManager.getAllInstancesForServerTenant(SERVER_TENANT_NAME)) { + _helixResourceManager.updateInstanceTags(serverInstance, Helix.UNTAGGED_SERVER_INSTANCE, false); + } + + for (String serverInstance : _helixResourceManager.getAllInstancesForServerTenant(SERVER_COLD_TENANT_NAME)) { + _helixResourceManager.updateInstanceTags(serverInstance, Helix.UNTAGGED_SERVER_INSTANCE, false); + } + } + + private void resetServerTags() { + untagServers(); + assertEquals(_helixResourceManager.getOnlineUnTaggedServerInstanceList().size(), NUM_SERVER_INSTANCES); + + // Create default tenant + Tenant serverTenant = + new Tenant(TenantRole.SERVER, SERVER_TENANT_NAME, NUM_SERVER_INSTANCES - NUM_OFFLINE_COLD_SERVER_INSTANCES, + NUM_OFFLINE_SERVER_INSTANCES, NUM_REALTIME_SERVER_INSTANCES); + _helixResourceManager.createServerTenant(serverTenant); + + // Create cold tenant + Tenant coldTenant = new Tenant(TenantRole.SERVER, SERVER_COLD_TENANT_NAME, NUM_OFFLINE_COLD_SERVER_INSTANCES, + NUM_OFFLINE_COLD_SERVER_INSTANCES, 0); + _helixResourceManager.createServerTenant(coldTenant); + + assertEquals(_helixResourceManager.getOnlineUnTaggedServerInstanceList().size(), 0); + } + + @Test + public void testAssignTargetTier() + throws Exception { + String coldOfflineServerTag = SERVER_COLD_TENANT_NAME + "_OFFLINE"; + TierConfig tierConfig = + new TierConfig("tier1", TierFactory.FIXED_SEGMENT_SELECTOR_TYPE, null, Collections.singletonList("testSegment"), + TierFactory.PINOT_SERVER_STORAGE_TYPE, coldOfflineServerTag, null, null); + addDummySchema(RAW_TABLE_NAME); + TableConfig tableConfig = + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setBrokerTenant(BROKER_TENANT_NAME) + .setTierConfigList(Collections.singletonList(tierConfig)).setServerTenant(SERVER_TENANT_NAME).build(); + waitForEVToDisappear(tableConfig.getTableName()); + _helixResourceManager.addTable(tableConfig); + + String segmentName = "testSegment"; + ZKMetadataProvider.setSegmentZKMetadata(_propertyStore, OFFLINE_TABLE_NAME, new SegmentZKMetadata(segmentName)); + _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, + SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, segmentName), "downloadUrl"); + + List retrievedSegmentsZKMetadata = + _helixResourceManager.getSegmentsZKMetadata(OFFLINE_TABLE_NAME); + SegmentZKMetadata retrievedSegmentZKMetadata = retrievedSegmentsZKMetadata.get(0); + assertEquals(retrievedSegmentZKMetadata.getSegmentName(), segmentName); + assertEquals(retrievedSegmentZKMetadata.getTier(), "tier1"); + + // Retrieve current assignment of the table and ensure segment's presence there + IdealState idealState = HelixHelper.getTableIdealState(_helixManager, OFFLINE_TABLE_NAME); + assertNotNull(idealState); + Map> currentAssignment = idealState.getRecord().getMapFields(); + assertTrue(currentAssignment.size() == 1 && currentAssignment.get(segmentName).size() == 1); + + // Ensure that the server instance belongs to the cold tenant + String coldServerName = currentAssignment.get(segmentName).keySet().iterator().next(); + InstanceConfig coldServerConfig = HelixHelper.getInstanceConfig(_helixManager, coldServerName); + assertTrue(coldServerConfig.containsTag(coldOfflineServerTag)); + } + + @AfterClass + public void tearDown() { + stopFakeInstances(); + stopController(); + stopZk(); + } +} diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManagerStatelessTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManagerStatelessTest.java index 6569a6a4bebe..5af87ce0e547 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManagerStatelessTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManagerStatelessTest.java @@ -46,6 +46,8 @@ import org.apache.pinot.common.lineage.SegmentLineageAccessHelper; import org.apache.pinot.common.metadata.ZKMetadataProvider; import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; +import org.apache.pinot.common.restlet.resources.EndReplaceSegmentsRequest; +import org.apache.pinot.common.tier.TierFactory; import org.apache.pinot.common.utils.LLCSegmentName; import org.apache.pinot.common.utils.config.InstanceUtils; import org.apache.pinot.common.utils.config.TagNameUtils; @@ -63,6 +65,7 @@ import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.config.table.TagOverrideConfig; import org.apache.pinot.spi.config.table.TenantConfig; +import org.apache.pinot.spi.config.table.TierConfig; import org.apache.pinot.spi.config.table.ingestion.BatchIngestionConfig; import org.apache.pinot.spi.config.table.ingestion.IngestionConfig; import org.apache.pinot.spi.config.tenant.Tenant; @@ -98,19 +101,23 @@ public class PinotHelixResourceManagerStatelessTest extends ControllerTest { private static final String OFFLINE_TABLE_NAME = TableNameBuilder.OFFLINE.tableNameWithType(RAW_TABLE_NAME); private static final String REALTIME_TABLE_NAME = TableNameBuilder.REALTIME.tableNameWithType(RAW_TABLE_NAME); + @Override + protected void overrideControllerConf(Map properties) { + properties.put(ControllerConf.CLUSTER_TENANT_ISOLATION_ENABLE, false); + } + @BeforeClass public void setUp() throws Exception { startZk(); - Map properties = getDefaultControllerConfiguration(); - properties.put(ControllerConf.CLUSTER_TENANT_ISOLATION_ENABLE, false); - startController(properties); + startController(); addFakeBrokerInstancesToAutoJoinHelixCluster(NUM_BROKER_INSTANCES, false); addFakeServerInstancesToAutoJoinHelixCluster(NUM_SERVER_INSTANCES, false); resetBrokerTags(); resetServerTags(); + addDummySchema(RAW_TABLE_NAME); } private void untagBrokers() { @@ -143,6 +150,17 @@ private void resetServerTags() { assertEquals(_helixResourceManager.getOnlineUnTaggedServerInstanceList().size(), 0); } + @Test + public void testGetInstancesByTag() { + List controllersByTag = _helixResourceManager.getAllInstancesWithTag("controller"); + List controllerConfigs = _helixResourceManager.getAllControllerInstanceConfigs(); + + assertEquals(controllersByTag.size(), controllerConfigs.size()); + for (InstanceConfig c : controllerConfigs) { + assertTrue(controllersByTag.contains(c.getInstanceName())); + } + } + @Test public void testGetDataInstanceAdminEndpoints() throws Exception { @@ -153,7 +171,8 @@ public void testGetDataInstanceAdminEndpoints() for (Map.Entry entry : adminEndpoints.entrySet()) { String key = entry.getKey(); int port = Server.DEFAULT_ADMIN_API_PORT + Integer.parseInt(key.substring("Server_localhost_".length())); - assertEquals(entry.getValue(), "http://localhost:" + port); + // ports are random generated + assertTrue(port > 0); } // Add a new server @@ -182,7 +201,7 @@ public void testGetDataInstanceAdminEndpoints() }, 60_000L, "Failed to update the admin port"); // Remove the new added server - _helixResourceManager.dropInstance(serverName); + assertTrue(_helixResourceManager.dropInstance(serverName).isSuccessful()); TestUtils.waitForCondition(aVoid -> { try { _helixResourceManager.getDataInstanceAdminEndpoints(Collections.singleton(serverName)); @@ -207,6 +226,8 @@ public void testAddRemoveInstance() { String instanceName = "Server_localhost_" + NUM_SERVER_INSTANCES; List allInstances = _helixResourceManager.getAllInstances(); assertFalse(allInstances.contains(instanceName)); + List allLiveInstances = _helixResourceManager.getAllLiveInstances(); + assertFalse(allLiveInstances.contains(instanceName)); Instance instance = new Instance("localhost", NUM_SERVER_INSTANCES, InstanceType.SERVER, Collections.singletonList(Helix.UNTAGGED_SERVER_INSTANCE), null, 0, 0, 0, 0, false); @@ -215,9 +236,11 @@ public void testAddRemoveInstance() { assertTrue(allInstances.contains(instanceName)); // Remove the added instance - _helixResourceManager.dropInstance(instanceName); + assertTrue(_helixResourceManager.dropInstance(instanceName).isSuccessful()); allInstances = _helixResourceManager.getAllInstances(); assertFalse(allInstances.contains(instanceName)); + allLiveInstances = _helixResourceManager.getAllLiveInstances(); + assertFalse(allLiveInstances.contains(instanceName)); } @Test @@ -236,9 +259,11 @@ public void testUpdateBrokerResource() assertEquals(untaggedBrokers.size(), 1); // Add a table + addDummySchema(RAW_TABLE_NAME); TableConfig offlineTableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setBrokerTenant(BROKER_TENANT_NAME) .setServerTenant(SERVER_TENANT_NAME).build(); + waitForEVToDisappear(offlineTableConfig.getTableName()); _helixResourceManager.addTable(offlineTableConfig); checkBrokerResource(taggedBrokers); @@ -293,9 +318,11 @@ private void checkBrokerResource(List expectedBrokers) { public void testRebuildBrokerResourceFromHelixTags() throws Exception { // Create the table + addDummySchema(RAW_TABLE_NAME); TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setBrokerTenant(BROKER_TENANT_NAME) .setServerTenant(SERVER_TENANT_NAME).build(); + waitForEVToDisappear(tableConfig.getTableName()); _helixResourceManager.addTable(tableConfig); // Untag all Brokers assigned to broker tenant @@ -334,6 +361,7 @@ public void testGetLiveBrokers() TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setBrokerTenant(BROKER_TENANT_NAME) .setServerTenant(SERVER_TENANT_NAME).build(); + waitForEVToDisappear(tableConfig.getTableName()); _helixResourceManager.addTable(tableConfig); waitForTableOnlineInBrokerResourceEV(OFFLINE_TABLE_NAME); @@ -361,7 +389,8 @@ public void testGetLiveBrokers() tableConfig = new TableConfigBuilder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setBrokerTenant(BROKER_TENANT_NAME) .setServerTenant(SERVER_TENANT_NAME) - .setStreamConfigs(FakeStreamConfigUtils.getDefaultHighLevelStreamConfigs().getStreamConfigsMap()).build(); + .setStreamConfigs(FakeStreamConfigUtils.getDefaultLowLevelStreamConfigs().getStreamConfigsMap()).build(); + waitForEVToDisappear(tableConfig.getTableName()); _helixResourceManager.addTable(tableConfig); waitForTableOnlineInBrokerResourceEV(REALTIME_TABLE_NAME); @@ -384,6 +413,30 @@ public void testGetLiveBrokers() assertThrows(TableNotFoundException.class, () -> _helixResourceManager.getLiveBrokersForTable("fake_OFFLINE")); assertThrows(TableNotFoundException.class, () -> _helixResourceManager.getLiveBrokersForTable("fake_REALTIME")); + // Test retrieving table name to live broker mapping for table without type suffix + Map> rawTableToLiveBrokersMapping = + _helixResourceManager.getTableToLiveBrokersMapping(null, List.of(RAW_TABLE_NAME)); + assertEquals(rawTableToLiveBrokersMapping.size(), 2); + assertEquals(rawTableToLiveBrokersMapping.get(OFFLINE_TABLE_NAME).size(), NUM_BROKER_INSTANCES); + assertEquals(rawTableToLiveBrokersMapping.get(REALTIME_TABLE_NAME).size(), NUM_BROKER_INSTANCES); + + // Test retrieving table names list to live broker mapping for each table without type suffix + Map> tablesListToLiveBrokersMapping = + _helixResourceManager.getTableToLiveBrokersMapping(List.of(OFFLINE_TABLE_NAME, REALTIME_TABLE_NAME)); + assertEquals(tablesListToLiveBrokersMapping.size(), 2); + assertEquals(tablesListToLiveBrokersMapping.get(OFFLINE_TABLE_NAME).size(), NUM_BROKER_INSTANCES); + assertEquals(tablesListToLiveBrokersMapping.get(REALTIME_TABLE_NAME).size(), NUM_BROKER_INSTANCES); + + // Test retrieving table name to live broker mapping for table with type suffix + Map> offlineTableToLiveBrokersMapping = + _helixResourceManager.getTableToLiveBrokersMapping(List.of(OFFLINE_TABLE_NAME)); + assertEquals(offlineTableToLiveBrokersMapping.size(), 1); + assertEquals(offlineTableToLiveBrokersMapping.get(OFFLINE_TABLE_NAME).size(), NUM_BROKER_INSTANCES); + + // Test that default value behaves the same as empty for optional argument + tableToLiveBrokersMapping = _helixResourceManager.getTableToLiveBrokersMapping(); + assertEquals(tableToLiveBrokersMapping.size(), 2); + // Delete the tables _helixResourceManager.deleteRealtimeTable(RAW_TABLE_NAME); _helixResourceManager.deleteOfflineTable(RAW_TABLE_NAME); @@ -533,6 +586,26 @@ public void testValidateTenantConfig() { // Valid tenant config without tagOverrideConfig _helixResourceManager.validateTableTenantConfig(offlineTableConfig); + // Invalid serverTag in tierConfigs + TierConfig tierConfig = new TierConfig("myTier", TierFactory.TIME_SEGMENT_SELECTOR_TYPE, "10d", null, + TierFactory.PINOT_SERVER_STORAGE_TYPE, "Unknown_OFFLINE", null, null); + offlineTableConfig.setTierConfigsList(Collections.singletonList(tierConfig)); + assertThrows(InvalidTableConfigException.class, + () -> _helixResourceManager.validateTableTenantConfig(offlineTableConfig)); + + // A null serverTag has no instances associated with it, so it's invalid. + tierConfig = new TierConfig("myTier", TierFactory.TIME_SEGMENT_SELECTOR_TYPE, "10d", null, + TierFactory.PINOT_SERVER_STORAGE_TYPE, null, null, null); + offlineTableConfig.setTierConfigsList(Collections.singletonList(tierConfig)); + assertThrows(InvalidTableConfigException.class, + () -> _helixResourceManager.validateTableTenantConfig(offlineTableConfig)); + + // Valid serverTag in tierConfigs + tierConfig = new TierConfig("myTier", TierFactory.TIME_SEGMENT_SELECTOR_TYPE, "10d", null, + TierFactory.PINOT_SERVER_STORAGE_TYPE, SERVER_TENANT_NAME + "_OFFLINE", null, null); + offlineTableConfig.setTierConfigsList(Collections.singletonList(tierConfig)); + _helixResourceManager.validateTableTenantConfig(offlineTableConfig); + TableConfig realtimeTableConfig = new TableConfigBuilder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setBrokerTenant(BROKER_TENANT_NAME) .setServerTenant(SERVER_TENANT_NAME).build(); @@ -588,6 +661,47 @@ public void testValidateTenantConfig() { resetServerTags(); } + @Test + public void testCreateColocatedTenant() { + untagServers(); + Tenant serverTenant = new Tenant(TenantRole.SERVER, SERVER_TENANT_NAME, NUM_SERVER_INSTANCES, NUM_SERVER_INSTANCES, + NUM_SERVER_INSTANCES); + assertTrue(_helixResourceManager.createServerTenant(serverTenant).isSuccessful()); + assertEquals( + _helixResourceManager.getInstancesWithTag(TagNameUtils.getOfflineTagForTenant(SERVER_TENANT_NAME)).size(), + NUM_SERVER_INSTANCES); + assertEquals( + _helixResourceManager.getInstancesWithTag(TagNameUtils.getRealtimeTagForTenant(SERVER_TENANT_NAME)).size(), + NUM_SERVER_INSTANCES); + assertTrue(_helixResourceManager.getOnlineUnTaggedServerInstanceList().isEmpty()); + + untagServers(); + serverTenant = new Tenant(TenantRole.SERVER, SERVER_TENANT_NAME, NUM_SERVER_INSTANCES, NUM_SERVER_INSTANCES - 1, + NUM_SERVER_INSTANCES - 1); + assertTrue(_helixResourceManager.createServerTenant(serverTenant).isSuccessful()); + assertEquals( + _helixResourceManager.getInstancesWithTag(TagNameUtils.getOfflineTagForTenant(SERVER_TENANT_NAME)).size(), + NUM_SERVER_INSTANCES - 1); + assertEquals( + _helixResourceManager.getInstancesWithTag(TagNameUtils.getRealtimeTagForTenant(SERVER_TENANT_NAME)).size(), + NUM_SERVER_INSTANCES - 1); + assertTrue(_helixResourceManager.getOnlineUnTaggedServerInstanceList().isEmpty()); + + untagServers(); + serverTenant = new Tenant(TenantRole.SERVER, SERVER_TENANT_NAME, NUM_SERVER_INSTANCES - 1, NUM_SERVER_INSTANCES - 1, + NUM_SERVER_INSTANCES - 1); + assertTrue(_helixResourceManager.createServerTenant(serverTenant).isSuccessful()); + assertEquals( + _helixResourceManager.getInstancesWithTag(TagNameUtils.getOfflineTagForTenant(SERVER_TENANT_NAME)).size(), + NUM_SERVER_INSTANCES - 1); + assertEquals( + _helixResourceManager.getInstancesWithTag(TagNameUtils.getRealtimeTagForTenant(SERVER_TENANT_NAME)).size(), + NUM_SERVER_INSTANCES - 1); + assertEquals(_helixResourceManager.getOnlineUnTaggedServerInstanceList().size(), 1); + + resetServerTags(); + } + @Test public void testLeadControllerResource() { IdealState leadControllerResourceIdealState = @@ -611,7 +725,7 @@ public void testLeadControllerResource() { if (stateMap.size() != 1) { return false; } - String instanceId = LeadControllerUtils.generateParticipantInstanceId(LOCAL_HOST, _controllerPort); + String instanceId = LeadControllerUtils.generateParticipantInstanceId(LOCAL_HOST, getControllerPort()); return MasterSlaveSMD.States.MASTER.name().equals(stateMap.get(instanceId)); } return true; @@ -683,13 +797,93 @@ public void testLeadControllerAssignment() { } } + @Test + public void testUpdateTargetTier() + throws Exception { + TierConfig tierConfig = + new TierConfig("tier1", TierFactory.FIXED_SEGMENT_SELECTOR_TYPE, null, Collections.singletonList("testSegment"), + TierFactory.PINOT_SERVER_STORAGE_TYPE, SERVER_TENANT_NAME + "_OFFLINE", null, null); + TableConfig tableConfig = + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setBrokerTenant(BROKER_TENANT_NAME) + .setTierConfigList(Collections.singletonList(tierConfig)).setServerTenant(SERVER_TENANT_NAME).build(); + waitForEVToDisappear(tableConfig.getTableName()); + addDummySchema(RAW_TABLE_NAME); + _helixResourceManager.addTable(tableConfig); + + String segmentName = "testSegment"; + SegmentZKMetadata segmentZKMetadata = new SegmentZKMetadata(segmentName); + ZKMetadataProvider.setSegmentZKMetadata(_propertyStore, OFFLINE_TABLE_NAME, segmentZKMetadata); + _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, + SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "testSegment"), "downloadUrl"); + assertNull(segmentZKMetadata.getTier()); + + // Move on to new tier + _helixResourceManager.updateTargetTier("j1", tableConfig.getTableName(), tableConfig); + List retrievedSegmentsZKMetadata = + _helixResourceManager.getSegmentsZKMetadata(OFFLINE_TABLE_NAME); + SegmentZKMetadata retrievedSegmentZKMetadata = retrievedSegmentsZKMetadata.get(0); + assertEquals(retrievedSegmentZKMetadata.getTier(), "tier1"); + + // Move back to default tier + tableConfig = + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setBrokerTenant(BROKER_TENANT_NAME) + .setServerTenant(SERVER_TENANT_NAME).build(); + _helixResourceManager.updateTableConfig(tableConfig); + _helixResourceManager.updateTargetTier("j2", tableConfig.getTableName(), tableConfig); + retrievedSegmentsZKMetadata = _helixResourceManager.getSegmentsZKMetadata(OFFLINE_TABLE_NAME); + retrievedSegmentZKMetadata = retrievedSegmentsZKMetadata.get(0); + assertNull(retrievedSegmentZKMetadata.getTier()); + } + + /** + * Tests the code path where a subset of merged segments (from the original segmentsTo list) + * is passed to the endReplace API. + * @throws Exception + */ + @Test + public void testSegmentReplacementWithCustomToSegments() + throws Exception { + // Create the table + addDummySchema(RAW_TABLE_NAME); + TableConfig tableConfig = + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setBrokerTenant(BROKER_TENANT_NAME) + .setServerTenant(SERVER_TENANT_NAME).build(); + waitForEVToDisappear(tableConfig.getTableName()); + _helixResourceManager.addTable(tableConfig); + + List segmentsFrom = Collections.emptyList(); + List segmentsTo = Arrays.asList("s20", "s21"); + String lineageEntryId = + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom, segmentsTo, false, null); + assertThrows(RuntimeException.class, + () -> _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId, + new EndReplaceSegmentsRequest(Arrays.asList("s9", "s6"), null))); + // Try after new segments added to the table + _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, + SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s20"), "downloadUrl"); + _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, + SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s21"), "downloadUrl"); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId, + new EndReplaceSegmentsRequest(Arrays.asList("s21"), null)); + SegmentLineage segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); + assertEquals(segmentLineage.getLineageEntryIds(), Collections.singleton(lineageEntryId)); + assertEquals(segmentLineage.getLineageEntry(lineageEntryId).getSegmentsFrom(), segmentsFrom); + assertEquals(segmentLineage.getLineageEntry(lineageEntryId).getState(), LineageEntryState.COMPLETED); + // Delete the table + _helixResourceManager.deleteOfflineTable(RAW_TABLE_NAME); + segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); + assertNull(segmentLineage); + } + @Test public void testSegmentReplacementRegular() throws Exception { // Create the table + addDummySchema(RAW_TABLE_NAME); TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setBrokerTenant(BROKER_TENANT_NAME) .setServerTenant(SERVER_TENANT_NAME).build(); + waitForEVToDisappear(tableConfig.getTableName()); _helixResourceManager.addTable(tableConfig); // Add 5 segments @@ -703,7 +897,7 @@ public void testSegmentReplacementRegular() List segmentsFrom1 = Collections.emptyList(); List segmentsTo1 = Arrays.asList("s5", "s6"); String lineageEntryId1 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom1, segmentsTo1, false); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom1, segmentsTo1, false, null); SegmentLineage segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntryIds(), Collections.singleton(lineageEntryId1)); assertEquals(segmentLineage.getLineageEntry(lineageEntryId1).getSegmentsFrom(), segmentsFrom1); @@ -711,35 +905,39 @@ public void testSegmentReplacementRegular() assertEquals(segmentLineage.getLineageEntry(lineageEntryId1).getState(), LineageEntryState.IN_PROGRESS); // Check invalid segmentsTo - assertThrows(IllegalArgumentException.class, + assertThrows(IllegalStateException.class, () -> _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, Arrays.asList("s1", "s2"), - Arrays.asList("s3", "s4"), false)); - assertThrows(IllegalArgumentException.class, + Arrays.asList("s3", "s4"), false, null)); + assertThrows(IllegalStateException.class, () -> _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, Arrays.asList("s1", "s2"), - Collections.singletonList("s2"), false)); + Collections.singletonList("s2"), false, null)); // Check invalid segmentsFrom - assertThrows(IllegalArgumentException.class, + assertThrows(IllegalStateException.class, () -> _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, Arrays.asList("s1", "s6"), - Collections.singletonList("s7"), false)); + Collections.singletonList("s7"), false, null)); // Invalid table assertThrows(RuntimeException.class, - () -> _helixResourceManager.endReplaceSegments(REALTIME_TABLE_NAME, lineageEntryId1)); + () -> _helixResourceManager.endReplaceSegments(REALTIME_TABLE_NAME, lineageEntryId1, null)); + // Invalid table + assertThrows(RuntimeException.class, + () -> _helixResourceManager.endReplaceSegments(REALTIME_TABLE_NAME, lineageEntryId1, null)); // Invalid lineage entry id - assertThrows(RuntimeException.class, () -> _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, "invalid")); + assertThrows(RuntimeException.class, + () -> _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, "invalid", null)); // New segments not available in the table assertThrows(RuntimeException.class, - () -> _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId1)); + () -> _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId1, null)); // Try after new segments added to the table _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s5"), "downloadUrl"); _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s6"), "downloadUrl"); - _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId1); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId1, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntryIds(), Collections.singleton(lineageEntryId1)); assertEquals(segmentLineage.getLineageEntry(lineageEntryId1).getSegmentsFrom(), segmentsFrom1); @@ -750,7 +948,7 @@ public void testSegmentReplacementRegular() List segmentsFrom2 = Arrays.asList("s1", "s2"); List segmentsTo2 = Arrays.asList("merged_t1_0", "merged_t1_1"); String lineageEntryId2 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom2, segmentsTo2, false); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom2, segmentsTo2, false, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertSetEquals(segmentLineage.getLineageEntryIds(), lineageEntryId1, lineageEntryId2); assertEquals(segmentLineage.getLineageEntry(lineageEntryId2).getSegmentsFrom(), segmentsFrom2); @@ -766,10 +964,10 @@ public void testSegmentReplacementRegular() // Revert the entry with partial data uploaded without forceRevert assertThrows(RuntimeException.class, - () -> _helixResourceManager.revertReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId2, false)); + () -> _helixResourceManager.revertReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId2, false, null)); // Revert the entry with partial data uploaded with forceRevert - _helixResourceManager.revertReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId2, true); + _helixResourceManager.revertReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId2, true, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntry(lineageEntryId2).getState(), LineageEntryState.REVERTED); @@ -782,7 +980,7 @@ public void testSegmentReplacementRegular() List segmentsFrom3 = Arrays.asList("s1", "s2"); List segmentsTo3 = Arrays.asList("merged_t2_0", "merged_t2_1"); String lineageEntryId3 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom3, segmentsTo3, false); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom3, segmentsTo3, false, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertSetEquals(segmentLineage.getLineageEntryIds(), lineageEntryId1, lineageEntryId2, lineageEntryId3); assertEquals(segmentLineage.getLineageEntry(lineageEntryId3).getSegmentsFrom(), segmentsFrom3); @@ -797,11 +995,11 @@ public void testSegmentReplacementRegular() List segmentsFrom4 = Arrays.asList("s1", "s2"); List segmentsTo4 = Arrays.asList("merged_t3_0", "merged_t3_1"); assertThrows(RuntimeException.class, - () -> _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom4, segmentsTo4, false)); + () -> _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom4, segmentsTo4, false, null)); // Test force clean up case String lineageEntryId4 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom4, segmentsTo4, true); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom4, segmentsTo4, true, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertSetEquals(segmentLineage.getLineageEntryIds(), lineageEntryId1, lineageEntryId2, lineageEntryId3, lineageEntryId4); @@ -824,7 +1022,7 @@ public void testSegmentReplacementRegular() SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "merged_t3_1"), "downloadUrl"); // Finish the replacement - _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId4); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId4, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertSetEquals(segmentLineage.getLineageEntryIds(), lineageEntryId1, lineageEntryId2, lineageEntryId3, lineageEntryId4); @@ -837,7 +1035,7 @@ public void testSegmentReplacementRegular() List segmentsFrom5 = Collections.emptyList(); List segmentsTo5 = Arrays.asList("s7", "s8"); String lineageEntryId5 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom5, segmentsTo5, false); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom5, segmentsTo5, false, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertSetEquals(segmentLineage.getLineageEntryIds(), lineageEntryId1, lineageEntryId2, lineageEntryId3, lineageEntryId4, lineageEntryId5); @@ -850,7 +1048,7 @@ public void testSegmentReplacementRegular() List segmentsFrom6 = Collections.emptyList(); List segmentsTo6 = Arrays.asList("s7", "s8"); String lineageEntryId6 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom6, segmentsTo6, true); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom6, segmentsTo6, true, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertSetEquals(segmentLineage.getLineageEntryIds(), lineageEntryId1, lineageEntryId2, lineageEntryId3, lineageEntryId4, lineageEntryId6); @@ -867,7 +1065,7 @@ public void testSegmentReplacementRegular() List segmentsFrom7 = Collections.emptyList(); List segmentsTo7 = Arrays.asList("s9", "s10"); String lineageEntryId7 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom7, segmentsTo7, true); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom7, segmentsTo7, true, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntryIds().size(), 6); assertEquals(segmentLineage.getLineageEntry(lineageEntryId6).getState(), LineageEntryState.IN_PROGRESS); @@ -878,7 +1076,7 @@ public void testSegmentReplacementRegular() SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s9"), "downloadUrl"); _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s10"), "downloadUrl"); - _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId7); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId7, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntryIds().size(), 6); assertEquals(segmentLineage.getLineageEntry(lineageEntryId6).getState(), LineageEntryState.IN_PROGRESS); @@ -891,7 +1089,7 @@ public void testSegmentReplacementRegular() List segmentsFrom8 = Arrays.asList("s9", "s10"); List segmentsTo8 = Arrays.asList("s11", "s12"); String lineageEntryId8 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom8, segmentsTo8, false); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom8, segmentsTo8, false, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntryIds().size(), 7); assertEquals(segmentLineage.getLineageEntry(lineageEntryId8).getSegmentsFrom(), segmentsFrom8); @@ -907,7 +1105,7 @@ public void testSegmentReplacementRegular() List segmentsFrom9 = Arrays.asList("s0", "s9"); List segmentsTo9 = Arrays.asList("s13", "s14"); String lineageEntryId9 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom9, segmentsTo9, true); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom9, segmentsTo9, true, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntryIds().size(), 8); assertEquals(segmentLineage.getLineageEntry(lineageEntryId8).getState(), LineageEntryState.REVERTED); @@ -918,7 +1116,7 @@ public void testSegmentReplacementRegular() SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s13"), "downloadUrl"); _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s14"), "downloadUrl"); - _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId9); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId9, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntryIds().size(), 8); assertEquals(segmentLineage.getLineageEntry(lineageEntryId9).getSegmentsFrom(), segmentsFrom9); @@ -926,9 +1124,23 @@ public void testSegmentReplacementRegular() assertEquals(segmentLineage.getLineageEntry(lineageEntryId9).getState(), LineageEntryState.COMPLETED); // Check endReplaceSegments is idempotent - _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId9); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId9, null); assertEquals(segmentLineage.getLineageEntry(lineageEntryId9).getState(), LineageEntryState.COMPLETED); + // Test empty segmentsTo. This is a special case where we are atomically removing segments. + List segmentsFrom10 = Arrays.asList("s13", "s14"); + List segmentsTo10 = Collections.emptyList(); + String lineageEntryId10 = + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom10, segmentsTo10, true, null); + segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); + assertEquals(segmentLineage.getLineageEntryIds().size(), 9); + assertEquals(segmentLineage.getLineageEntry(lineageEntryId10).getState(), LineageEntryState.IN_PROGRESS); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId10, null); + segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); + assertEquals(segmentLineage.getLineageEntry(lineageEntryId10).getSegmentsFrom(), segmentsFrom10); + assertEquals(segmentLineage.getLineageEntry(lineageEntryId10).getSegmentsTo(), segmentsTo10); + assertEquals(segmentLineage.getLineageEntry(lineageEntryId10).getState(), LineageEntryState.COMPLETED); + // Delete the table _helixResourceManager.deleteOfflineTable(RAW_TABLE_NAME); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); @@ -939,11 +1151,13 @@ public void testSegmentReplacementRegular() public void testSegmentReplacementForRefresh() throws Exception { // Create the table + addDummySchema(RAW_TABLE_NAME); IngestionConfig ingestionConfig = new IngestionConfig(); ingestionConfig.setBatchIngestionConfig(new BatchIngestionConfig(null, "REFRESH", "DAILY")); TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setBrokerTenant(BROKER_TENANT_NAME) .setServerTenant(SERVER_TENANT_NAME).setIngestionConfig(ingestionConfig).build(); + waitForEVToDisappear(tableConfig.getTableName()); _helixResourceManager.addTable(tableConfig); // Add 3 segments @@ -958,7 +1172,7 @@ public void testSegmentReplacementForRefresh() List segmentsFrom1 = Arrays.asList("s0", "s1", "s2"); List segmentsTo1 = Arrays.asList("s3", "s4", "s5"); String lineageEntryId1 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom1, segmentsTo1, false); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom1, segmentsTo1, false, null); SegmentLineage segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntryIds(), Collections.singleton(lineageEntryId1)); assertEquals(segmentLineage.getLineageEntry(lineageEntryId1).getSegmentsFrom(), segmentsFrom1); @@ -976,7 +1190,7 @@ public void testSegmentReplacementForRefresh() assertSetEquals(_helixResourceManager.getSegmentsFor(OFFLINE_TABLE_NAME, true), "s0", "s1", "s2"); // Call end segment replacements - _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId1); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId1, null); assertEquals(_helixResourceManager.getSegmentsFor(OFFLINE_TABLE_NAME, false).size(), 6); assertSetEquals(_helixResourceManager.getSegmentsFor(OFFLINE_TABLE_NAME, true), "s3", "s4", "s5"); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); @@ -990,7 +1204,7 @@ public void testSegmentReplacementForRefresh() List segmentsFrom2 = Arrays.asList("s3", "s4", "s5"); List segmentsTo2 = Arrays.asList("s6", "s7", "s8"); String lineageEntryId2 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom2, segmentsTo2, false); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom2, segmentsTo2, false, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntryIds().size(), 2); assertEquals(segmentLineage.getLineageEntry(lineageEntryId2).getSegmentsFrom(), segmentsFrom2); @@ -1001,7 +1215,7 @@ public void testSegmentReplacementForRefresh() // Reverting the first entry should fail assertThrows(RuntimeException.class, - () -> _helixResourceManager.revertReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId1, false)); + () -> _helixResourceManager.revertReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId1, false, null)); // Add partial segments to indicate incomplete protocol _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, @@ -1017,7 +1231,7 @@ public void testSegmentReplacementForRefresh() List segmentsFrom3 = Arrays.asList("s3", "s4", "s5"); List segmentsTo3 = Arrays.asList("s9", "s10", "s11"); String lineageEntryId3 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom3, segmentsTo3, true); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom3, segmentsTo3, true, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntryIds().size(), 3); @@ -1033,7 +1247,7 @@ public void testSegmentReplacementForRefresh() // Invoking end segment replacement for the reverted entry should fail assertThrows(RuntimeException.class, - () -> _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId2)); + () -> _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId2, null)); // Add new segments for (int i = 9; i < 12; i++) { @@ -1042,7 +1256,7 @@ public void testSegmentReplacementForRefresh() } // Call end segment replacements - _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId3); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId3, null); assertEquals(_helixResourceManager.getSegmentsFor(OFFLINE_TABLE_NAME, false).size(), 6); assertSetEquals(_helixResourceManager.getSegmentsFor(OFFLINE_TABLE_NAME, true), "s9", "s10", "s11"); @@ -1052,7 +1266,7 @@ public void testSegmentReplacementForRefresh() // Call revert segment replacements (s3, s4, s5) <- (s9, s10, s11) to check if the revertReplaceSegments correctly // deleted (s9, s10, s11). - _helixResourceManager.revertReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId3, false); + _helixResourceManager.revertReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId3, false, null); assertEquals(_helixResourceManager.getSegmentsFor(OFFLINE_TABLE_NAME, false).size(), 3); assertSetEquals(_helixResourceManager.getSegmentsFor(OFFLINE_TABLE_NAME, true), "s3", "s4", "s5"); @@ -1068,7 +1282,7 @@ public void testSegmentReplacementForRefresh() List segmentsFrom4 = Arrays.asList("s3", "s4", "s5"); List segmentsTo4 = Arrays.asList("s12", "s13", "s14"); String lineageEntryId4 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom4, segmentsTo4, true); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom4, segmentsTo4, true, null); assertSetEquals(_helixResourceManager.getSegmentsFor(OFFLINE_TABLE_NAME, false), "s3", "s4", "s5"); // Upload the new segments (s12, s13, s14) @@ -1078,7 +1292,7 @@ public void testSegmentReplacementForRefresh() } // Call endReplaceSegments to start to use (s12, s13, s14) - _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId4); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId4, null); assertSetEquals(_helixResourceManager.getSegmentsFor(OFFLINE_TABLE_NAME, false), "s3", "s4", "s5", "s12", "s13", "s14"); assertSetEquals(_helixResourceManager.getSegmentsFor(OFFLINE_TABLE_NAME, true), "s12", "s13", "s14"); @@ -1088,7 +1302,7 @@ public void testSegmentReplacementForRefresh() List segmentsFrom5 = Collections.emptyList(); List segmentsTo5 = Arrays.asList("s15", "s16"); String lineageEntryId5 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom5, segmentsTo5, false); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom5, segmentsTo5, false, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntry(lineageEntryId5).getSegmentsFrom(), segmentsFrom5); assertEquals(segmentLineage.getLineageEntry(lineageEntryId5).getSegmentsTo(), segmentsTo5); @@ -1103,7 +1317,7 @@ public void testSegmentReplacementForRefresh() List segmentsFrom6 = Collections.emptyList(); List segmentsTo6 = Arrays.asList("s17", "s18"); String lineageEntryId6 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom6, segmentsTo6, true); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom6, segmentsTo6, true, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntry(lineageEntryId5).getState(), LineageEntryState.IN_PROGRESS); assertEquals(segmentLineage.getLineageEntry(lineageEntryId6).getState(), LineageEntryState.IN_PROGRESS); @@ -1113,7 +1327,7 @@ public void testSegmentReplacementForRefresh() SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s17"), "downloadUrl"); _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s18"), "downloadUrl"); - _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId6); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId6, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntry(lineageEntryId6).getSegmentsFrom(), segmentsFrom6); assertEquals(segmentLineage.getLineageEntry(lineageEntryId6).getSegmentsTo(), segmentsTo6); @@ -1124,7 +1338,7 @@ public void testSegmentReplacementForRefresh() List segmentsFrom7 = Arrays.asList("s17", "s18"); List segmentsTo7 = Arrays.asList("s19", "s20"); String lineageEntryId7 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom7, segmentsTo7, false); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom7, segmentsTo7, false, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntry(lineageEntryId7).getSegmentsFrom(), segmentsFrom7); assertEquals(segmentLineage.getLineageEntry(lineageEntryId7).getSegmentsTo(), segmentsTo7); @@ -1139,7 +1353,7 @@ public void testSegmentReplacementForRefresh() List segmentsFrom8 = Arrays.asList("s14", "s17"); List segmentsTo8 = Arrays.asList("s21", "s22"); String lineageEntryId8 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom8, segmentsTo8, true); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom8, segmentsTo8, true, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntry(lineageEntryId7).getState(), LineageEntryState.REVERTED); assertEquals(segmentLineage.getLineageEntry(lineageEntryId8).getState(), LineageEntryState.IN_PROGRESS); @@ -1150,7 +1364,7 @@ public void testSegmentReplacementForRefresh() _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s22"), "downloadUrl"); - _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId8); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId8, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntry(lineageEntryId8).getSegmentsFrom(), segmentsFrom8); assertEquals(segmentLineage.getLineageEntry(lineageEntryId8).getSegmentsTo(), segmentsTo8); @@ -1161,7 +1375,7 @@ public void testSegmentReplacementForRefresh() List segmentsFrom9 = Arrays.asList("s21", "s22"); List segmentsTo9 = Arrays.asList("s23", "s24"); String lineageEntryId9 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom9, segmentsTo9, false); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom9, segmentsTo9, false, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntry(lineageEntryId9).getSegmentsFrom(), segmentsFrom9); assertEquals(segmentLineage.getLineageEntry(lineageEntryId9).getSegmentsTo(), segmentsTo9); @@ -1178,7 +1392,7 @@ public void testSegmentReplacementForRefresh() List segmentsFrom10 = Arrays.asList("s21", "s22"); List segmentsTo10 = Arrays.asList("s24", "s25"); String lineageEntryId10 = - _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom10, segmentsTo10, true); + _helixResourceManager.startReplaceSegments(OFFLINE_TABLE_NAME, segmentsFrom10, segmentsTo10, true, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntry(lineageEntryId9).getSegmentsFrom(), segmentsFrom9); assertEquals(segmentLineage.getLineageEntry(lineageEntryId9).getSegmentsTo(), Collections.singletonList("s23")); @@ -1192,7 +1406,7 @@ public void testSegmentReplacementForRefresh() SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s24"), "downloadUrl"); _helixResourceManager.addNewSegment(OFFLINE_TABLE_NAME, SegmentMetadataMockUtils.mockSegmentMetadata(OFFLINE_TABLE_NAME, "s25"), "downloadUrl"); - _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId10); + _helixResourceManager.endReplaceSegments(OFFLINE_TABLE_NAME, lineageEntryId10, null); segmentLineage = SegmentLineageAccessHelper.getSegmentLineage(_propertyStore, OFFLINE_TABLE_NAME); assertEquals(segmentLineage.getLineageEntry(lineageEntryId10).getSegmentsFrom(), segmentsFrom10); assertEquals(segmentLineage.getLineageEntry(lineageEntryId10).getSegmentsTo(), segmentsTo10); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceAssignmentTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceAssignmentTest.java index 4715271fc55c..113d4e164965 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceAssignmentTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceAssignmentTest.java @@ -18,12 +18,20 @@ */ package org.apache.pinot.controller.helix.core.assignment.instance; +import java.io.FileNotFoundException; +import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Random; +import java.util.Set; import org.apache.helix.model.InstanceConfig; +import org.apache.log4j.Level; +import org.apache.log4j.LogManager; +import org.apache.log4j.Logger; import org.apache.pinot.common.assignment.InstanceAssignmentConfigUtils; import org.apache.pinot.common.assignment.InstancePartitions; import org.apache.pinot.common.utils.config.InstanceUtils; @@ -54,6 +62,7 @@ public class InstanceAssignmentTest { private static final String SERVER_INSTANCE_ID_PREFIX = "Server_localhost_"; private static final String SERVER_INSTANCE_POOL_PREFIX = "_pool_"; private static final String TABLE_NAME_ZERO_HASH_COMPLEMENT = "12"; + public static final Logger LOGGER = LogManager.getLogger(InstanceAssignmentTest.class); @Test public void testDefaultOfflineReplicaGroup() { @@ -191,8 +200,8 @@ public void testDefaultOfflineReplicaGroup() { // r0: [i8, i1, i4] // p0, p0, p1 // p1 - // r1: [i9, i10, i5] - // p0, p0, p1 + // r1: [i9, i5, i10] + // p0, p1, p0 // p1 // r2: [i0, i3, i11] // p0, p0, p1 @@ -210,7 +219,7 @@ public void testDefaultOfflineReplicaGroup() { assertEquals(instancePartitions.getInstances(1, 2), Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 11, SERVER_INSTANCE_ID_PREFIX + 0)); - // Add 2 more instances to the ZK and increase the number of instances per replica group from 2 to 3. + // Add 2 more instances to the ZK and increase the number of instances per partition from 2 to 3. for (int i = numInstances + 2; i < numInstances + 4; i++) { InstanceConfig instanceConfig = new InstanceConfig(SERVER_INSTANCE_ID_PREFIX + i); instanceConfig.addTag(OFFLINE_TAG); @@ -226,34 +235,29 @@ public void testDefaultOfflineReplicaGroup() { // Math.abs("myTable_OFFLINE".hashCode()) % 12 = 2 // [i10, i11, i12, i13, i3, i4, i5, i11, i7, i8, i9, i0, i1] - // For r0, the candidate instances are [i12, i13, i4, i7, i8, i1]. - // For p0, since the existing assignment is [i8, i1], the next available instance from the candidates is i12. - // For p1, the existing assignment is [i4, i8], the next available instance is also i12. - // r0: [i12, i4, i8, i1] - // For r1, the candidate instances become [i10, i13, i5, i7, i9]. - // For p0, since the existing assignment is [i9, i10], the next available instance is i13 (new instance). - // For p1, the existing assignment is [i5, i9], the next available one from the candidates is i10, but since - // i10 is already used in the former partition, it got added to the tail, so the next available one is i13. - // r1: [i10, i13, i5, i9] - // For r2, the candidate instances become [i11, i3, i7, i0]. - // For p0, the existing assignment is [i0, i3], the next available instance from the candidates is i11. - // For p1, the existing assignment is [i11, i0], the next available instance from the candidates is i3, but - // since i3 is already used in the former partition, it got appended to the tail, so the next available one is i7. - // r2: [i11, i3, i7, i0] + // r0: [i8, i1, i4, i12] + // p0, p0, p1, p0 + // p1, p1 + // r1: [i9, i5, i10, i13] + // p0, p1, p0, p0 + // p1, p1 + // r2: [i0, i3, i11, i7] + // p0, p0, p1, p0 + // p1, p1 assertEquals(instancePartitions.getInstances(0, 0), Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 8, SERVER_INSTANCE_ID_PREFIX + 1, SERVER_INSTANCE_ID_PREFIX + 12)); assertEquals(instancePartitions.getInstances(1, 0), - Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 4, SERVER_INSTANCE_ID_PREFIX + 8, SERVER_INSTANCE_ID_PREFIX + 12)); + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 4, SERVER_INSTANCE_ID_PREFIX + 8, SERVER_INSTANCE_ID_PREFIX + 1)); assertEquals(instancePartitions.getInstances(0, 1), Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 9, SERVER_INSTANCE_ID_PREFIX + 10, SERVER_INSTANCE_ID_PREFIX + 13)); assertEquals(instancePartitions.getInstances(1, 1), - Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 5, SERVER_INSTANCE_ID_PREFIX + 9, SERVER_INSTANCE_ID_PREFIX + 13)); + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 5, SERVER_INSTANCE_ID_PREFIX + 9, SERVER_INSTANCE_ID_PREFIX + 10)); assertEquals(instancePartitions.getInstances(0, 2), - Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 0, SERVER_INSTANCE_ID_PREFIX + 3, SERVER_INSTANCE_ID_PREFIX + 11)); + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 0, SERVER_INSTANCE_ID_PREFIX + 3, SERVER_INSTANCE_ID_PREFIX + 7)); assertEquals(instancePartitions.getInstances(1, 2), - Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 11, SERVER_INSTANCE_ID_PREFIX + 0, SERVER_INSTANCE_ID_PREFIX + 7)); + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 11, SERVER_INSTANCE_ID_PREFIX + 0, SERVER_INSTANCE_ID_PREFIX + 3)); - // Reduce the number of instances per replica group from 3 to 2. + // Reduce the number of instances per partition from 3 to 2. numInstancesPerPartition = 2; tableConfig.getValidationConfig() .setReplicaGroupStrategyConfig(new ReplicaGroupStrategyConfig(partitionColumnName, numInstancesPerPartition)); @@ -329,6 +333,957 @@ public void testDefaultOfflineReplicaGroup() { Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 11, SERVER_INSTANCE_ID_PREFIX + 0)); } + public void testMirrorServerSetBasedRandom() throws FileNotFoundException { + testMirrorServerSetBasedRandomInner(10000000); + } + + public void testMirrorServerSetBasedRandomInner(int loopCount) throws FileNotFoundException { + PrintStream o = new PrintStream("output.txt"); + System.setOut(o); + for (int iter = 0; iter < loopCount; iter++) { + System.out.printf("_____________________________ITERATION:%d________________________________%n", iter); + Random random1 = new Random(); + int numTargetReplicaGroups = random1.nextInt(7) + 1; + int numExistingReplicaGroups = random1.nextInt(7) + 1; + int numPreConfiguredInstancesPerReplicaGroup = random1.nextInt(10) + 5; + int numTargetInstancesPerReplicaGroup = Math.max(random1.nextInt(numPreConfiguredInstancesPerReplicaGroup), 5); + int numExistingInstancesPerReplicaGroup = Math.max(random1.nextInt(numPreConfiguredInstancesPerReplicaGroup), 5); + int numPools = random1.nextInt(10) + 1; + + int numPartitions = 0; + int numInstancesPerPartition = 0; + List instanceConfigs = new ArrayList<>(); + + int preConfiguredOffsetStart = random1.nextInt(10); + for (int i = 0; i < 1000; i++) { + int pool = i % numPools; + InstanceConfig instanceConfig = new InstanceConfig(SERVER_INSTANCE_ID_PREFIX + i); + instanceConfig.addTag(OFFLINE_TAG); + instanceConfig.getRecord() + .setMapField(InstanceUtils.POOL_KEY, Collections.singletonMap(OFFLINE_TAG, Integer.toString(pool))); + instanceConfigs.add(instanceConfig); + } + InstanceTagPoolConfig tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, numPools, null); + InstanceReplicaGroupPartitionConfig replicaPartitionConfig = + new InstanceReplicaGroupPartitionConfig(true, 0, numTargetReplicaGroups, numTargetInstancesPerReplicaGroup, + numPartitions, numInstancesPerPartition, false, null); + + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, + InstanceAssignmentConfig.PartitionSelector.MIRROR_SERVER_SET_PARTITION_SELECTOR.toString(), false))) + .setInstancePartitionsMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, "preConfigured")) + .build(); + InstanceAssignmentDriver driver = new InstanceAssignmentDriver(tableConfig); + InstancePartitions preConfigured = new InstancePartitions("preConfigured"); + InstancePartitions existing = new InstancePartitions("existing"); + + List preconfiguredInstances = new LinkedList<>(); + List existingInstances = new LinkedList<>(); + + Set preConfiguredUsed = new HashSet<>(); + Set existingUsed = new HashSet<>(); + + for (int i = 0; i < numTargetReplicaGroups; i++) { + for (int j = 0; j < numPreConfiguredInstancesPerReplicaGroup; j++) { + int instance = + random1.nextInt((int) (1.5 * numTargetReplicaGroups * numPreConfiguredInstancesPerReplicaGroup)); + while (preConfiguredUsed.contains(instance)) { + instance = random1.nextInt((int) (1.5 * numTargetReplicaGroups * numPreConfiguredInstancesPerReplicaGroup)); + } + preConfiguredUsed.add(instance); + preconfiguredInstances.add(SERVER_INSTANCE_ID_PREFIX + (instance + preConfiguredOffsetStart)); + } + } + + for (int i = 0; i < numExistingReplicaGroups; i++) { + for (int j = 0; j < numExistingInstancesPerReplicaGroup; j++) { + int instance = random1.nextInt((int) (1.5 * numExistingReplicaGroups * numExistingInstancesPerReplicaGroup)); + while (existingUsed.contains(instance)) { + instance = random1.nextInt((int) (1.5 * numExistingReplicaGroups * numExistingInstancesPerReplicaGroup)); + } + existingUsed.add(instance); + existingInstances.add(SERVER_INSTANCE_ID_PREFIX + instance); + } + } + + Collections.shuffle(preconfiguredInstances); + Collections.shuffle(existingInstances); + + for (int i = 0; i < numTargetReplicaGroups; i++) { + preConfigured.setInstances(0, i, preconfiguredInstances.subList(i * numPreConfiguredInstancesPerReplicaGroup, + (i + 1) * numPreConfiguredInstancesPerReplicaGroup)); + } + + for (int i = 0; i < numExistingReplicaGroups; i++) { + existing.setInstances(0, i, existingInstances.subList(i * numExistingInstancesPerReplicaGroup, + (i + 1) * numExistingInstancesPerReplicaGroup)); + } + + System.out.println("Done initializing preconfigured and existing instances"); + System.out.println("numTargetReplicaGroups " + numTargetReplicaGroups); + System.out.println("numPreConfiguredInstancesPerReplicaGroup " + numPreConfiguredInstancesPerReplicaGroup); + System.out.println("numTargetInstancesPerReplicaGroup " + numTargetInstancesPerReplicaGroup); + + System.out.println("numExistingReplicaGroups " + numExistingReplicaGroups); + System.out.println("numExistingInstancesPerReplicaGroup " + numExistingInstancesPerReplicaGroup); + System.out.println(""); + for (int i = 0; i < numTargetReplicaGroups; i++) { + System.out.println("Preconfigured instances for replica group " + i + " : " + preConfigured.getInstances(0, i)); + } + System.out.println(""); + for (int i = 0; i < numExistingReplicaGroups; i++) { + System.out.println("Existing instances for replica group " + i + " : " + existing.getInstances(0, i)); + } + System.out.println(""); + InstancePartitions instancePartitions = + driver.assignInstances(InstancePartitionsType.OFFLINE, instanceConfigs, existing, preConfigured); + assertEquals(instancePartitions.getNumReplicaGroups(), numTargetReplicaGroups); + assertEquals(instancePartitions.getNumPartitions(), 1); + + for (int i = 0; i < numTargetReplicaGroups; i++) { + System.out.println("Assigned instances for replica group " + i + " : " + instancePartitions.getInstances(0, i)); + } + } + } + + @Test + public void testMirrorServerSetBased() { + LogManager.getLogger(MirrorServerSetInstancePartitionSelector.class) + .setLevel(Level.INFO); + + // Test initial assignment 3 replica groups, 7 instances per rg. + int numPartitions = 0; + int numInstancesPerPartition = 0; + int numInstances = 21; + int numPools = 5; + int numReplicaGroups = 3; + int numInstancesPerReplicaGroup = numInstances / numReplicaGroups; + List instanceConfigs = new ArrayList<>(numInstances); + for (int i = 0; i < 100; i++) { + int pool = i % numPools; + InstanceConfig instanceConfig = + new InstanceConfig(SERVER_INSTANCE_ID_PREFIX + i); + instanceConfig.addTag(OFFLINE_TAG); + instanceConfig.getRecord() + .setMapField(InstanceUtils.POOL_KEY, Collections.singletonMap(OFFLINE_TAG, Integer.toString(pool))); + instanceConfigs.add(instanceConfig); + } + InstanceTagPoolConfig tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, numPools, null); + InstanceReplicaGroupPartitionConfig replicaPartitionConfig = + new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, + numInstancesPerPartition, false, null); + + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, + InstanceAssignmentConfig.PartitionSelector.MIRROR_SERVER_SET_PARTITION_SELECTOR.toString(), false))) + .setInstancePartitionsMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, "preConfigured")).build(); + InstanceAssignmentDriver driver = new InstanceAssignmentDriver(tableConfig); + InstancePartitions preConfigured = new InstancePartitions("preConfigured"); + preConfigured.setInstances(0, 0, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 0, SERVER_INSTANCE_ID_PREFIX + 3, SERVER_INSTANCE_ID_PREFIX + 6, + SERVER_INSTANCE_ID_PREFIX + 9, SERVER_INSTANCE_ID_PREFIX + 12, SERVER_INSTANCE_ID_PREFIX + 15, + SERVER_INSTANCE_ID_PREFIX + 18)); + preConfigured.setInstances(0, 1, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 1, SERVER_INSTANCE_ID_PREFIX + 4, SERVER_INSTANCE_ID_PREFIX + 7, + SERVER_INSTANCE_ID_PREFIX + 10, SERVER_INSTANCE_ID_PREFIX + 13, SERVER_INSTANCE_ID_PREFIX + 16, + SERVER_INSTANCE_ID_PREFIX + 19)); + preConfigured.setInstances(0, 2, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 2, SERVER_INSTANCE_ID_PREFIX + 5, SERVER_INSTANCE_ID_PREFIX + 8, + SERVER_INSTANCE_ID_PREFIX + 11, SERVER_INSTANCE_ID_PREFIX + 14, SERVER_INSTANCE_ID_PREFIX + 17, + SERVER_INSTANCE_ID_PREFIX + 20)); + + InstancePartitions instancePartitions = + driver.assignInstances(InstancePartitionsType.OFFLINE, instanceConfigs, null, preConfigured); + assertEquals(instancePartitions.getNumReplicaGroups(), numReplicaGroups); + assertEquals(instancePartitions.getNumPartitions(), 1); + /* + * Pre-configured partitioning: + * RG1 RG2 RG3 + * Host 0 1 2 + * Host 3 4 5 + * Host 6 7 8 + * Host 9 10 11 + * Host 12 13 14 + * Host 15 16 17 + * Host 18 19 20 + * + * Final assignment for this table: + * RG1 RG2 RG3 + * Host 0 1 2 + * Host 3 4 5 + * Host 6 7 8 + * Host 9 10 11 + * Host 12 13 14 + * Host 15 16 17 + * Host 18 19 20 + */ + assertEquals(instancePartitions.getInstances(0, 0), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 0, + SERVER_INSTANCE_ID_PREFIX + 3, + SERVER_INSTANCE_ID_PREFIX + 6, + SERVER_INSTANCE_ID_PREFIX + 9, + SERVER_INSTANCE_ID_PREFIX + 12, + SERVER_INSTANCE_ID_PREFIX + 15, + SERVER_INSTANCE_ID_PREFIX + 18)); + assertEquals(instancePartitions.getInstances(0, 1), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 1, + SERVER_INSTANCE_ID_PREFIX + 4, + SERVER_INSTANCE_ID_PREFIX + 7, + SERVER_INSTANCE_ID_PREFIX + 10, + SERVER_INSTANCE_ID_PREFIX + 13, + SERVER_INSTANCE_ID_PREFIX + 16, + SERVER_INSTANCE_ID_PREFIX + 19)); + assertEquals(instancePartitions.getInstances(0, 2), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 2, + SERVER_INSTANCE_ID_PREFIX + 5, + SERVER_INSTANCE_ID_PREFIX + 8, + SERVER_INSTANCE_ID_PREFIX + 11, + SERVER_INSTANCE_ID_PREFIX + 14, + SERVER_INSTANCE_ID_PREFIX + 17, + SERVER_INSTANCE_ID_PREFIX + 20)); + + // Test instance shuffling/uplifting from 3*5 to 3*7 + numPartitions = 0; + numInstancesPerPartition = 0; + numInstances = 21; + numReplicaGroups = 3; + numInstancesPerReplicaGroup = numInstances / numReplicaGroups; + tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, numPools, null); + replicaPartitionConfig = + new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, + numInstancesPerPartition, false, null); + + tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, + InstanceAssignmentConfig.PartitionSelector.MIRROR_SERVER_SET_PARTITION_SELECTOR.toString(), false))) + .setInstancePartitionsMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, "preConfigured")).build(); + driver = new InstanceAssignmentDriver(tableConfig); + preConfigured = new InstancePartitions("preConfigured"); + preConfigured.setInstances(0, 0, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 0, SERVER_INSTANCE_ID_PREFIX + 3, SERVER_INSTANCE_ID_PREFIX + 6, + SERVER_INSTANCE_ID_PREFIX + 9, SERVER_INSTANCE_ID_PREFIX + 12, SERVER_INSTANCE_ID_PREFIX + 15, + SERVER_INSTANCE_ID_PREFIX + 18)); + preConfigured.setInstances(0, 1, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 1, SERVER_INSTANCE_ID_PREFIX + 4, SERVER_INSTANCE_ID_PREFIX + 7, + SERVER_INSTANCE_ID_PREFIX + 10, SERVER_INSTANCE_ID_PREFIX + 13, SERVER_INSTANCE_ID_PREFIX + 16, + SERVER_INSTANCE_ID_PREFIX + 19)); + preConfigured.setInstances(0, 2, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 2, SERVER_INSTANCE_ID_PREFIX + 5, SERVER_INSTANCE_ID_PREFIX + 8, + SERVER_INSTANCE_ID_PREFIX + 11, SERVER_INSTANCE_ID_PREFIX + 14, SERVER_INSTANCE_ID_PREFIX + 17, + SERVER_INSTANCE_ID_PREFIX + 20)); + + InstancePartitions existingInstancePartitions = new InstancePartitions("existing"); + existingInstancePartitions.setInstances(0, 0, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 0, SERVER_INSTANCE_ID_PREFIX + 12, SERVER_INSTANCE_ID_PREFIX + 1, + SERVER_INSTANCE_ID_PREFIX + 3, SERVER_INSTANCE_ID_PREFIX + 9)); + existingInstancePartitions.setInstances(0, 1, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 6, SERVER_INSTANCE_ID_PREFIX + 7, SERVER_INSTANCE_ID_PREFIX + 4, + SERVER_INSTANCE_ID_PREFIX + 13, SERVER_INSTANCE_ID_PREFIX + 10)); + existingInstancePartitions.setInstances(0, 2, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 2, SERVER_INSTANCE_ID_PREFIX + 14, SERVER_INSTANCE_ID_PREFIX + 5, + SERVER_INSTANCE_ID_PREFIX + 8, SERVER_INSTANCE_ID_PREFIX + 11)); + + instancePartitions = + driver.assignInstances(InstancePartitionsType.OFFLINE, instanceConfigs, + existingInstancePartitions, preConfigured); + assertEquals(instancePartitions.getNumReplicaGroups(), numReplicaGroups); + assertEquals(instancePartitions.getNumPartitions(), 1); + /* + * uplift from 15 instances in 3 replicas to 21 instance in 3 replicas + * 21 instances in 4 pools + * Pre-configured partitioning: + * RG1 RG2 RG3 + * Host 0 1 2 + * Host 3 4 5 + * Host 6 7 8 + * Host 9 10 11 + * Host 12 13 14 + * Host 15 16 17 + * Host 18 19 20 + * + * Existing configured partitioning: + * RG1 RG2 RG3 + * Host 0 6 2 + * Host 12 7 14 + * Host 1 4 5 + * Host 3 13 8 + * Host 9 10 11 + * + * Final assignment for this table: + * RG1 RG2 RG3 + * Host 0 1 2 + * Host 12 13 14 + * Host 3 4 5 + * Host 6 7 8 + * Host 9 10 11 + * Host 15 16 17 + * Host 18 19 20 + */ + assertEquals(instancePartitions.getInstances(0, 0), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 0, + SERVER_INSTANCE_ID_PREFIX + 12, + SERVER_INSTANCE_ID_PREFIX + 3, + SERVER_INSTANCE_ID_PREFIX + 6, + SERVER_INSTANCE_ID_PREFIX + 9, + SERVER_INSTANCE_ID_PREFIX + 15, + SERVER_INSTANCE_ID_PREFIX + 18)); + assertEquals(instancePartitions.getInstances(0, 1), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 1, + SERVER_INSTANCE_ID_PREFIX + 13, + SERVER_INSTANCE_ID_PREFIX + 4, + SERVER_INSTANCE_ID_PREFIX + 7, + SERVER_INSTANCE_ID_PREFIX + 10, + SERVER_INSTANCE_ID_PREFIX + 16, + SERVER_INSTANCE_ID_PREFIX + 19)); + assertEquals(instancePartitions.getInstances(0, 2), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 2, + SERVER_INSTANCE_ID_PREFIX + 14, + SERVER_INSTANCE_ID_PREFIX + 5, + SERVER_INSTANCE_ID_PREFIX + 8, + SERVER_INSTANCE_ID_PREFIX + 11, + SERVER_INSTANCE_ID_PREFIX + 17, + SERVER_INSTANCE_ID_PREFIX + 20)); + + // Test instance replacement from 3*6 to 3*5 + numPartitions = 0; + numInstancesPerPartition = 0; + numInstances = 15; + numReplicaGroups = 3; + numInstancesPerReplicaGroup = numInstances / numReplicaGroups; + tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, numPools, null); + replicaPartitionConfig = + new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, + numInstancesPerPartition, false, null); + + tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, + InstanceAssignmentConfig.PartitionSelector.MIRROR_SERVER_SET_PARTITION_SELECTOR.toString(), false))) + .setInstancePartitionsMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, "preConfigured")).build(); + driver = new InstanceAssignmentDriver(tableConfig); + preConfigured = new InstancePartitions("preConfigured"); + preConfigured.setInstances(0, 0, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 18, SERVER_INSTANCE_ID_PREFIX + 21, SERVER_INSTANCE_ID_PREFIX + 24, + SERVER_INSTANCE_ID_PREFIX + 27, SERVER_INSTANCE_ID_PREFIX + 30)); + preConfigured.setInstances(0, 1, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 19, SERVER_INSTANCE_ID_PREFIX + 22, SERVER_INSTANCE_ID_PREFIX + 25, + SERVER_INSTANCE_ID_PREFIX + 28, SERVER_INSTANCE_ID_PREFIX + 31)); + preConfigured.setInstances(0, 2, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 20, SERVER_INSTANCE_ID_PREFIX + 23, SERVER_INSTANCE_ID_PREFIX + 26, + SERVER_INSTANCE_ID_PREFIX + 29, SERVER_INSTANCE_ID_PREFIX + 32)); + + existingInstancePartitions = new InstancePartitions("existing"); + existingInstancePartitions.setInstances(0, 0, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 0, SERVER_INSTANCE_ID_PREFIX + 3, SERVER_INSTANCE_ID_PREFIX + 6, + SERVER_INSTANCE_ID_PREFIX + 9, SERVER_INSTANCE_ID_PREFIX + 15, SERVER_INSTANCE_ID_PREFIX + 18)); + existingInstancePartitions.setInstances(0, 1, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 1, SERVER_INSTANCE_ID_PREFIX + 4, SERVER_INSTANCE_ID_PREFIX + 7, + SERVER_INSTANCE_ID_PREFIX + 10, SERVER_INSTANCE_ID_PREFIX + 16, SERVER_INSTANCE_ID_PREFIX + 19)); + existingInstancePartitions.setInstances(0, 2, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 2, SERVER_INSTANCE_ID_PREFIX + 5, SERVER_INSTANCE_ID_PREFIX + 8, + SERVER_INSTANCE_ID_PREFIX + 11, SERVER_INSTANCE_ID_PREFIX + 17, SERVER_INSTANCE_ID_PREFIX + 20)); + + instancePartitions = + driver.assignInstances(InstancePartitionsType.OFFLINE, instanceConfigs, existingInstancePartitions, + preConfigured); + assertEquals(instancePartitions.getNumReplicaGroups(), numReplicaGroups); + assertEquals(instancePartitions.getNumPartitions(), 1); + /* + * From 18 instances in 3 replicas to 15 instance in 3 replicas + * Pre-configured partitioning: + * RG1 RG2 RG3 + * Host 18 19 20 + * Host 21 22 23 + * Host 24 25 26 + * Host 27 28 29 + * Host 30 31 32 + * + * Existing configured partitioning: + * RG1 RG2 RG3 + * Host 0 1 2 + * Host 3 4 5 + * Host 6 7 8 + * Host 9 10 11 + * Host 15 16 17 + * Host 18 19 20 + * + * Final assignment for this table: + * RG1 RG2 RG3 + * Host 18 19 20 + * Host 21 22 23 + * Host 24 25 26 + * Host 27 28 29 + * Host 30 31 32 + * + */ + + assertEquals(instancePartitions.getInstances(0, 0), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 18, + SERVER_INSTANCE_ID_PREFIX + 21, + SERVER_INSTANCE_ID_PREFIX + 24, + SERVER_INSTANCE_ID_PREFIX + 27, + SERVER_INSTANCE_ID_PREFIX + 30)); + assertEquals(instancePartitions.getInstances(0, 1), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 19, + SERVER_INSTANCE_ID_PREFIX + 22, + SERVER_INSTANCE_ID_PREFIX + 25, + SERVER_INSTANCE_ID_PREFIX + 28, + SERVER_INSTANCE_ID_PREFIX + 31)); + assertEquals(instancePartitions.getInstances(0, 2), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 20, + SERVER_INSTANCE_ID_PREFIX + 23, + SERVER_INSTANCE_ID_PREFIX + 26, + SERVER_INSTANCE_ID_PREFIX + 29, + SERVER_INSTANCE_ID_PREFIX + 32)); + + // Test instance shuffling/uplifting from 3*5 to 3*7, with some instance replacement + numPartitions = 0; + numInstancesPerPartition = 0; + numInstances = 18; + numReplicaGroups = 3; + numInstancesPerReplicaGroup = numInstances / numReplicaGroups; + tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, numPools, null); + replicaPartitionConfig = + new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, + numInstancesPerPartition, false, null); + + tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, + InstanceAssignmentConfig.PartitionSelector.MIRROR_SERVER_SET_PARTITION_SELECTOR.toString(), false))) + .setInstancePartitionsMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, "preConfigured")).build(); + driver = new InstanceAssignmentDriver(tableConfig); + preConfigured = new InstancePartitions("preConfigured"); + preConfigured.setInstances(0, 0, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 0, SERVER_INSTANCE_ID_PREFIX + 3, SERVER_INSTANCE_ID_PREFIX + 6, + SERVER_INSTANCE_ID_PREFIX + 9, SERVER_INSTANCE_ID_PREFIX + 15, SERVER_INSTANCE_ID_PREFIX + 18)); + preConfigured.setInstances(0, 1, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 1, SERVER_INSTANCE_ID_PREFIX + 4, SERVER_INSTANCE_ID_PREFIX + 7, + SERVER_INSTANCE_ID_PREFIX + 10, SERVER_INSTANCE_ID_PREFIX + 16, SERVER_INSTANCE_ID_PREFIX + 19)); + preConfigured.setInstances(0, 2, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 2, SERVER_INSTANCE_ID_PREFIX + 5, SERVER_INSTANCE_ID_PREFIX + 8, + SERVER_INSTANCE_ID_PREFIX + 11, SERVER_INSTANCE_ID_PREFIX + 17, SERVER_INSTANCE_ID_PREFIX + 20)); + + existingInstancePartitions = new InstancePartitions("existing"); + existingInstancePartitions.setInstances(0, 0, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 0, SERVER_INSTANCE_ID_PREFIX + 12, SERVER_INSTANCE_ID_PREFIX + 1, + SERVER_INSTANCE_ID_PREFIX + 3, SERVER_INSTANCE_ID_PREFIX + 9)); + existingInstancePartitions.setInstances(0, 1, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 6, SERVER_INSTANCE_ID_PREFIX + 7, SERVER_INSTANCE_ID_PREFIX + 4, + SERVER_INSTANCE_ID_PREFIX + 13, SERVER_INSTANCE_ID_PREFIX + 10)); + existingInstancePartitions.setInstances(0, 2, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 2, SERVER_INSTANCE_ID_PREFIX + 14, SERVER_INSTANCE_ID_PREFIX + 5, + SERVER_INSTANCE_ID_PREFIX + 8, SERVER_INSTANCE_ID_PREFIX + 11)); + + instancePartitions = + driver.assignInstances(InstancePartitionsType.OFFLINE, instanceConfigs, existingInstancePartitions, + preConfigured); + assertEquals(instancePartitions.getNumReplicaGroups(), numReplicaGroups); + assertEquals(instancePartitions.getNumPartitions(), 1); + /* + * uplift from 15 instances in 3 replicas to 21 instance in 3 replicas + * Pre-configured partitioning: + * RG1 RG2 RG3 + * Host 0 1 2 + * Host 3 4 5 + * Host 6 7 8 + * Host 9 10 11 + * Host 15 16 17 + * Host 18 19 20 + * + * Existing configured partitioning: + * RG1 RG2 RG3 + * Host 0 6 2 + * Host 12 7 14 + * Host 1 4 5 + * Host 3 13 8 + * Host 9 10 11 + * + * Final assignment for this table: + * RG1 RG2 RG3 + * Host 0 1 2 + * Host 6 7 8 + * Host 3 4 5 + * Host 15 16 17 + * Host 9 10 11 + * Host 18 19 20 + */ + + assertEquals(instancePartitions.getInstances(0, 0), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 0, + SERVER_INSTANCE_ID_PREFIX + 6, + SERVER_INSTANCE_ID_PREFIX + 3, + SERVER_INSTANCE_ID_PREFIX + 15, + SERVER_INSTANCE_ID_PREFIX + 9, + SERVER_INSTANCE_ID_PREFIX + 18)); + assertEquals(instancePartitions.getInstances(0, 1), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 1, + SERVER_INSTANCE_ID_PREFIX + 7, + SERVER_INSTANCE_ID_PREFIX + 4, + SERVER_INSTANCE_ID_PREFIX + 16, + SERVER_INSTANCE_ID_PREFIX + 10, + SERVER_INSTANCE_ID_PREFIX + 19)); + assertEquals(instancePartitions.getInstances(0, 2), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 2, + SERVER_INSTANCE_ID_PREFIX + 8, + SERVER_INSTANCE_ID_PREFIX + 5, + SERVER_INSTANCE_ID_PREFIX + 17, + SERVER_INSTANCE_ID_PREFIX + 11, + SERVER_INSTANCE_ID_PREFIX + 20)); + + // Test instance shuffling/uplifting from 3*5 to 4*6 + numPartitions = 0; + numInstancesPerPartition = 0; + numInstances = 24; + numReplicaGroups = 4; + numInstancesPerReplicaGroup = numInstances / numReplicaGroups; + tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, numPools, null); + replicaPartitionConfig = + new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, + numInstancesPerPartition, false, null); + + tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, + InstanceAssignmentConfig.PartitionSelector.MIRROR_SERVER_SET_PARTITION_SELECTOR.toString(), false))) + .setInstancePartitionsMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, "preConfigured")).build(); + driver = new InstanceAssignmentDriver(tableConfig); + preConfigured = new InstancePartitions("preConfigured"); + preConfigured.setInstances(0, 0, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 0, SERVER_INSTANCE_ID_PREFIX + 3, SERVER_INSTANCE_ID_PREFIX + 6, + SERVER_INSTANCE_ID_PREFIX + 9, SERVER_INSTANCE_ID_PREFIX + 12, SERVER_INSTANCE_ID_PREFIX + 15)); + preConfigured.setInstances(0, 1, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 1, SERVER_INSTANCE_ID_PREFIX + 4, SERVER_INSTANCE_ID_PREFIX + 7, + SERVER_INSTANCE_ID_PREFIX + 10, SERVER_INSTANCE_ID_PREFIX + 13, SERVER_INSTANCE_ID_PREFIX + 16)); + preConfigured.setInstances(0, 2, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 2, SERVER_INSTANCE_ID_PREFIX + 5, SERVER_INSTANCE_ID_PREFIX + 8, + SERVER_INSTANCE_ID_PREFIX + 11, SERVER_INSTANCE_ID_PREFIX + 14, SERVER_INSTANCE_ID_PREFIX + 17)); + preConfigured.setInstances(0, 3, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 18, SERVER_INSTANCE_ID_PREFIX + 19, SERVER_INSTANCE_ID_PREFIX + 20, + SERVER_INSTANCE_ID_PREFIX + 21, SERVER_INSTANCE_ID_PREFIX + 22, SERVER_INSTANCE_ID_PREFIX + 23)); + + existingInstancePartitions = new InstancePartitions("existing"); + existingInstancePartitions.setInstances(0, 0, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 0, SERVER_INSTANCE_ID_PREFIX + 12, SERVER_INSTANCE_ID_PREFIX + 1, + SERVER_INSTANCE_ID_PREFIX + 3, SERVER_INSTANCE_ID_PREFIX + 9)); + existingInstancePartitions.setInstances(0, 1, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 6, SERVER_INSTANCE_ID_PREFIX + 7, SERVER_INSTANCE_ID_PREFIX + 4, + SERVER_INSTANCE_ID_PREFIX + 13, SERVER_INSTANCE_ID_PREFIX + 10)); + existingInstancePartitions.setInstances(0, 2, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 2, SERVER_INSTANCE_ID_PREFIX + 14, SERVER_INSTANCE_ID_PREFIX + 5, + SERVER_INSTANCE_ID_PREFIX + 8, SERVER_INSTANCE_ID_PREFIX + 11)); + + instancePartitions = + driver.assignInstances(InstancePartitionsType.OFFLINE, instanceConfigs, existingInstancePartitions, + preConfigured); + assertEquals(instancePartitions.getNumReplicaGroups(), numReplicaGroups); + assertEquals(instancePartitions.getNumPartitions(), 1); + /* + * Test instance shuffling/uplifting from 3*5 to 4*6 + * Pre-configured partitioning: + * RG1 RG2 RG3 RG4 + * Host 0 1 2 18 + * Host 3 4 5 19 + * Host 6 7 8 20 + * Host 9 10 11 21 + * Host 12 13 14 22 + * Host 15 16 17 23 + * + * Existing configured partitioning: + * RG1 RG2 RG3 + * Host 0 6 2 + * Host 12 7 14 + * Host 1 4 5 + * Host 3 13 8 + * Host 9 10 11 + * + * Final assignment for this table: + * RG1 RG2 RG3 + * Host 0 1 2 18 + * Host 12 13 14 22 + * Host 3 4 5 19 + * Host 6 7 8 20 + * Host 9 10 11 21 + * Host 15 16 17 23 + */ + + assertEquals(instancePartitions.getInstances(0, 0), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 0, + SERVER_INSTANCE_ID_PREFIX + 12, + SERVER_INSTANCE_ID_PREFIX + 3, + SERVER_INSTANCE_ID_PREFIX + 6, + SERVER_INSTANCE_ID_PREFIX + 9, + SERVER_INSTANCE_ID_PREFIX + 15)); + assertEquals(instancePartitions.getInstances(0, 1), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 1, + SERVER_INSTANCE_ID_PREFIX + 13, + SERVER_INSTANCE_ID_PREFIX + 4, + SERVER_INSTANCE_ID_PREFIX + 7, + SERVER_INSTANCE_ID_PREFIX + 10, + SERVER_INSTANCE_ID_PREFIX + 16)); + assertEquals(instancePartitions.getInstances(0, 2), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 2, + SERVER_INSTANCE_ID_PREFIX + 14, + SERVER_INSTANCE_ID_PREFIX + 5, + SERVER_INSTANCE_ID_PREFIX + 8, + SERVER_INSTANCE_ID_PREFIX + 11, + SERVER_INSTANCE_ID_PREFIX + 17)); + assertEquals(instancePartitions.getInstances(0, 3), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 18, + SERVER_INSTANCE_ID_PREFIX + 22, + SERVER_INSTANCE_ID_PREFIX + 19, + SERVER_INSTANCE_ID_PREFIX + 20, + SERVER_INSTANCE_ID_PREFIX + 21, + SERVER_INSTANCE_ID_PREFIX + 23)); + + // Test instance shuffling/downlifting from 4 * 6 to 3 * 4 with shuffling of instances + numPartitions = 0; + numInstancesPerPartition = 0; + numInstances = 12; + numReplicaGroups = 3; + numInstancesPerReplicaGroup = numInstances / numReplicaGroups; + tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, numPools, null); + replicaPartitionConfig = + new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, + numInstancesPerPartition, false, null); + + tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, + InstanceAssignmentConfig.PartitionSelector.MIRROR_SERVER_SET_PARTITION_SELECTOR.toString(), false))) + .setInstancePartitionsMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, "preConfigured")).build(); + driver = new InstanceAssignmentDriver(tableConfig); + preConfigured = new InstancePartitions("preConfigured"); + preConfigured.setInstances(0, 0, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 1, SERVER_INSTANCE_ID_PREFIX + 4, SERVER_INSTANCE_ID_PREFIX + 7, + SERVER_INSTANCE_ID_PREFIX + 10, SERVER_INSTANCE_ID_PREFIX + 14)); + preConfigured.setInstances(0, 1, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 2, SERVER_INSTANCE_ID_PREFIX + 22, SERVER_INSTANCE_ID_PREFIX + 13, + SERVER_INSTANCE_ID_PREFIX + 11, SERVER_INSTANCE_ID_PREFIX + 17)); + preConfigured.setInstances(0, 2, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 18, SERVER_INSTANCE_ID_PREFIX + 19, SERVER_INSTANCE_ID_PREFIX + 20, + SERVER_INSTANCE_ID_PREFIX + 21, SERVER_INSTANCE_ID_PREFIX + 23)); + + existingInstancePartitions = new InstancePartitions("existing"); + existingInstancePartitions.setInstances(0, 0, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 0, + SERVER_INSTANCE_ID_PREFIX + 12, + SERVER_INSTANCE_ID_PREFIX + 3, + SERVER_INSTANCE_ID_PREFIX + 6, + SERVER_INSTANCE_ID_PREFIX + 9, + SERVER_INSTANCE_ID_PREFIX + 15)); + existingInstancePartitions.setInstances(0, 1, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 1, + SERVER_INSTANCE_ID_PREFIX + 13, + SERVER_INSTANCE_ID_PREFIX + 4, + SERVER_INSTANCE_ID_PREFIX + 7, + SERVER_INSTANCE_ID_PREFIX + 10, + SERVER_INSTANCE_ID_PREFIX + 16)); + existingInstancePartitions.setInstances(0, 2, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 2, + SERVER_INSTANCE_ID_PREFIX + 14, + SERVER_INSTANCE_ID_PREFIX + 5, + SERVER_INSTANCE_ID_PREFIX + 8, + SERVER_INSTANCE_ID_PREFIX + 11, + SERVER_INSTANCE_ID_PREFIX + 17)); + existingInstancePartitions.setInstances(0, 3, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 18, + SERVER_INSTANCE_ID_PREFIX + 22, + SERVER_INSTANCE_ID_PREFIX + 19, + SERVER_INSTANCE_ID_PREFIX + 20, + SERVER_INSTANCE_ID_PREFIX + 21, + SERVER_INSTANCE_ID_PREFIX + 23)); + + instancePartitions = + driver.assignInstances(InstancePartitionsType.OFFLINE, instanceConfigs, existingInstancePartitions, + preConfigured); + assertEquals(instancePartitions.getNumReplicaGroups(), numReplicaGroups); + assertEquals(instancePartitions.getNumPartitions(), 1); + /* + * Test instance shuffling/downlifting from 4 * 6 to 3 * 4 with shuffling of instances + * Pre-configured partitioning: + * RG2 RG3 RG4 + * Host 1 2 18 + * Host 4 22 19 + * Host 7 13 20 + * Host 10 11 21 + * Host 14 17 23 + * + * Existing configured partitioning: + * RG1 RG2 RG3 RG4 + * Host 0 1 2 18 + * Host 3 4 5 19 + * Host 6 7 8 20 + * Host 9 10 11 21 + * Host 12 13 14 22 + * Host 15 16 17 23 + * + * Final assignment for this table: + * RG1 RG2 RG3 + * Host 1 2 18 + * Host 10 11 21 + * Host 7 13 20 + * Host 14 17 23 + */ + + assertEquals(instancePartitions.getInstances(0, 0), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 1, + SERVER_INSTANCE_ID_PREFIX + 10, + SERVER_INSTANCE_ID_PREFIX + 7, + SERVER_INSTANCE_ID_PREFIX + 14)); + assertEquals(instancePartitions.getInstances(0, 1), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 2, + SERVER_INSTANCE_ID_PREFIX + 11, + SERVER_INSTANCE_ID_PREFIX + 13, + SERVER_INSTANCE_ID_PREFIX + 17)); + assertEquals(instancePartitions.getInstances(0, 2), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 18, + SERVER_INSTANCE_ID_PREFIX + 21, + SERVER_INSTANCE_ID_PREFIX + 20, + SERVER_INSTANCE_ID_PREFIX + 23)); + + + // upscale 3*3 to 3*5 + numPartitions = 0; + numInstancesPerPartition = 0; + numInstances = 15; + numReplicaGroups = 3; + numInstancesPerReplicaGroup = numInstances / numReplicaGroups; + tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, numPools, null); + replicaPartitionConfig = + new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, + numInstancesPerPartition, false, null); + + tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, + InstanceAssignmentConfig.PartitionSelector.MIRROR_SERVER_SET_PARTITION_SELECTOR.toString(), false))) + .setInstancePartitionsMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, "preConfigured")).build(); + driver = new InstanceAssignmentDriver(tableConfig); + preConfigured = new InstancePartitions("preConfigured"); + preConfigured.setInstances(0, 0, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 1, SERVER_INSTANCE_ID_PREFIX + 4, SERVER_INSTANCE_ID_PREFIX + 7, + SERVER_INSTANCE_ID_PREFIX + 10, SERVER_INSTANCE_ID_PREFIX + 13)); + preConfigured.setInstances(0, 1, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 2, SERVER_INSTANCE_ID_PREFIX + 5, SERVER_INSTANCE_ID_PREFIX + 8, + SERVER_INSTANCE_ID_PREFIX + 11, SERVER_INSTANCE_ID_PREFIX + 14)); + preConfigured.setInstances(0, 2, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 3, SERVER_INSTANCE_ID_PREFIX + 6, SERVER_INSTANCE_ID_PREFIX + 9, + SERVER_INSTANCE_ID_PREFIX + 12, SERVER_INSTANCE_ID_PREFIX + 15)); + + existingInstancePartitions = new InstancePartitions("existing"); + existingInstancePartitions.setInstances(0, 0, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 1, + SERVER_INSTANCE_ID_PREFIX + 2, + SERVER_INSTANCE_ID_PREFIX + 3)); + + existingInstancePartitions.setInstances(0, 1, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 4, + SERVER_INSTANCE_ID_PREFIX + 5, + SERVER_INSTANCE_ID_PREFIX + 6)); + + existingInstancePartitions.setInstances(0, 2, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 7, + SERVER_INSTANCE_ID_PREFIX + 8, + SERVER_INSTANCE_ID_PREFIX + 9)); + + instancePartitions = + driver.assignInstances(InstancePartitionsType.OFFLINE, instanceConfigs, existingInstancePartitions, + preConfigured); + assertEquals(instancePartitions.getNumReplicaGroups(), numReplicaGroups); + assertEquals(instancePartitions.getNumPartitions(), 1); + /* + * Test instance shuffling/downlifting from 4 * 6 to 3 * 4 with shuffling of instances + * Pre-configured partitioning: + * RG2 RG3 RG4 + * Host 1 2 3 + * Host 4 5 6 + * Host 7 8 9 + * Host 10 11 12 + * Host 13 14 15 + * + * Existing configured partitioning: + * RG1 RG2 RG3 + * Host 1 2 3 + * Host 4 5 6 + * Host 7 8 9 + * + * Final assignment for this table: + * RG1 RG2 RG3 + * Host 1 2 3 + * Host 4 5 6 + * Host 7 8 9 + * Host 10 11 12 + * Host 13 14 15 + */ + + assertEquals(instancePartitions.getInstances(0, 0), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 1, + SERVER_INSTANCE_ID_PREFIX + 4, + SERVER_INSTANCE_ID_PREFIX + 7, + SERVER_INSTANCE_ID_PREFIX + 10, + SERVER_INSTANCE_ID_PREFIX + 13)); + assertEquals(instancePartitions.getInstances(0, 1), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 2, + SERVER_INSTANCE_ID_PREFIX + 5, + SERVER_INSTANCE_ID_PREFIX + 8, + SERVER_INSTANCE_ID_PREFIX + 11, + SERVER_INSTANCE_ID_PREFIX + 14)); + assertEquals(instancePartitions.getInstances(0, 2), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 3, + SERVER_INSTANCE_ID_PREFIX + 6, + SERVER_INSTANCE_ID_PREFIX + 9, + SERVER_INSTANCE_ID_PREFIX + 12, + SERVER_INSTANCE_ID_PREFIX + 15)); + + // downscale 3*5 to 3*3 + numPartitions = 0; + numInstancesPerPartition = 0; + numInstances = 9; + numReplicaGroups = 3; + numInstancesPerReplicaGroup = numInstances / numReplicaGroups; + tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, numPools, null); + replicaPartitionConfig = + new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, + numInstancesPerPartition, false, null); + + tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, + InstanceAssignmentConfig.PartitionSelector.MIRROR_SERVER_SET_PARTITION_SELECTOR.toString(), false))) + .setInstancePartitionsMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, "preConfigured")).build(); + driver = new InstanceAssignmentDriver(tableConfig); + + preConfigured = new InstancePartitions("preConfigured"); + preConfigured.setInstances(0, 0, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 1, SERVER_INSTANCE_ID_PREFIX + 4, SERVER_INSTANCE_ID_PREFIX + 7)); + preConfigured.setInstances(0, 1, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 2, SERVER_INSTANCE_ID_PREFIX + 5, SERVER_INSTANCE_ID_PREFIX + 8)); + preConfigured.setInstances(0, 2, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 3, SERVER_INSTANCE_ID_PREFIX + 6, SERVER_INSTANCE_ID_PREFIX + 9)); + + existingInstancePartitions = new InstancePartitions("existing"); + existingInstancePartitions.setInstances(0, 0, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 1, SERVER_INSTANCE_ID_PREFIX + 4, SERVER_INSTANCE_ID_PREFIX + 7, + SERVER_INSTANCE_ID_PREFIX + 10, SERVER_INSTANCE_ID_PREFIX + 13)); + existingInstancePartitions.setInstances(0, 1, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 2, SERVER_INSTANCE_ID_PREFIX + 5, SERVER_INSTANCE_ID_PREFIX + 8, + SERVER_INSTANCE_ID_PREFIX + 11, SERVER_INSTANCE_ID_PREFIX + 14)); + existingInstancePartitions.setInstances(0, 2, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 3, SERVER_INSTANCE_ID_PREFIX + 6, SERVER_INSTANCE_ID_PREFIX + 9, + SERVER_INSTANCE_ID_PREFIX + 12, SERVER_INSTANCE_ID_PREFIX + 15)); + + instancePartitions = + driver.assignInstances(InstancePartitionsType.OFFLINE, instanceConfigs, existingInstancePartitions, + preConfigured); + + assertEquals(instancePartitions.getNumReplicaGroups(), numReplicaGroups); + assertEquals(instancePartitions.getNumPartitions(), 1); + + /* + * Test instance shuffling/downlifting from 4 * 6 to 3 * 4 with shuffling of instances + * Pre-configured partitioning: + * RG2 RG3 RG4 + * Host 1 2 3 + * Host 4 5 6 + * Host 7 8 9 + * + * Existing configured partitioning: + * RG1 RG2 RG3 + * Host 1 2 3 + * Host 4 5 6 + * Host 7 8 9 + * Host 10 11 12 + * Host 13 14 15 + * + * Final assignment for this table: + * RG1 RG2 RG3 + * Host 1 2 3 + * Host 4 5 6 + * Host 7 8 9 + */ + + assertEquals(instancePartitions.getInstances(0, 0), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 1, SERVER_INSTANCE_ID_PREFIX + 4, SERVER_INSTANCE_ID_PREFIX + 7)); + assertEquals(instancePartitions.getInstances(0, 1), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 2, SERVER_INSTANCE_ID_PREFIX + 5, SERVER_INSTANCE_ID_PREFIX + 8)); + assertEquals(instancePartitions.getInstances(0, 2), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 3, SERVER_INSTANCE_ID_PREFIX + 6, SERVER_INSTANCE_ID_PREFIX + 9)); + + // replace instance 5 with instance 11 + numPartitions = 0; + numInstancesPerPartition = 0; + numInstances = 9; + numReplicaGroups = 3; + numInstancesPerReplicaGroup = numInstances / numReplicaGroups; + tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, numPools, null); + replicaPartitionConfig = + new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, + numInstancesPerPartition, false, null); + + tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, + InstanceAssignmentConfig.PartitionSelector.MIRROR_SERVER_SET_PARTITION_SELECTOR.toString(), false))) + .setInstancePartitionsMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, "preConfigured")).build(); + driver = new InstanceAssignmentDriver(tableConfig); + + preConfigured = new InstancePartitions("preConfigured"); + preConfigured.setInstances(0, 0, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 1, SERVER_INSTANCE_ID_PREFIX + 4, SERVER_INSTANCE_ID_PREFIX + 7)); + preConfigured.setInstances(0, 1, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 2, SERVER_INSTANCE_ID_PREFIX + 11, SERVER_INSTANCE_ID_PREFIX + 8)); + preConfigured.setInstances(0, 2, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 3, SERVER_INSTANCE_ID_PREFIX + 6, SERVER_INSTANCE_ID_PREFIX + 9)); + + existingInstancePartitions = new InstancePartitions("existing"); + existingInstancePartitions.setInstances(0, 0, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 1, SERVER_INSTANCE_ID_PREFIX + 4, SERVER_INSTANCE_ID_PREFIX + 7)); + existingInstancePartitions.setInstances(0, 1, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 2, SERVER_INSTANCE_ID_PREFIX + 5, SERVER_INSTANCE_ID_PREFIX + 8)); + existingInstancePartitions.setInstances(0, 2, + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 3, SERVER_INSTANCE_ID_PREFIX + 6, SERVER_INSTANCE_ID_PREFIX + 9)); + + instancePartitions = + driver.assignInstances(InstancePartitionsType.OFFLINE, instanceConfigs, existingInstancePartitions, + preConfigured); + + assertEquals(instancePartitions.getNumReplicaGroups(), numReplicaGroups); + assertEquals(instancePartitions.getNumPartitions(), 1); + + /* + * Test instance shuffling/downlifting from 4 * 6 to 3 * 4 with shuffling of instances + * Pre-configured partitioning: + * RG2 RG3 RG4 + * Host 1 2 3 + * Host 4 11 6 + * Host 7 8 9 + * + * Existing configured partitioning: + * RG2 RG3 RG4 + * Host 1 2 3 + * Host 4 5 6 + * Host 7 8 9 + * + * Final assignment for this table: + * RG1 RG2 RG3 + * Host 1 2 3 + * Host 4 11 6 + * Host 7 8 9 + */ + + // Verifying the final configuration after downlifting + assertEquals(instancePartitions.getInstances(0, 0), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 1, SERVER_INSTANCE_ID_PREFIX + 4, SERVER_INSTANCE_ID_PREFIX + 7)); + assertEquals(instancePartitions.getInstances(0, 1), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 2, SERVER_INSTANCE_ID_PREFIX + 11, SERVER_INSTANCE_ID_PREFIX + 8)); + assertEquals(instancePartitions.getInstances(0, 2), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 3, SERVER_INSTANCE_ID_PREFIX + 6, SERVER_INSTANCE_ID_PREFIX + 9)); + } + @Test public void testPoolBased() { // 10 instances in 2 pools, each with 5 instances @@ -350,10 +1305,10 @@ public void testPoolBased() { // Assign to 2 replica-groups so that each replica-group is assigned to one pool int numReplicaGroups = numPools; InstanceReplicaGroupPartitionConfig replicaPartitionConfig = - new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, 0, 0, 0, false); + new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, 0, 0, 0, false, null); TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) - .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, - new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig))).build(); + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, null, false))).build(); InstanceAssignmentDriver driver = new InstanceAssignmentDriver(tableConfig); // Math.abs("myTable_OFFLINE".hashCode()) % 2 = 0 @@ -405,8 +1360,8 @@ public void testPoolBased() { // Select all 3 pools in pool selection tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, numPools, null); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, - new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig))); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, null, false))); // Math.abs("myTable_OFFLINE".hashCode()) % 3 = 2 // All instances in pool 2 should be assigned to replica-group 0, and all instances in pool 0 should be assigned to @@ -427,8 +1382,8 @@ public void testPoolBased() { // Select pool 0 and 1 in pool selection tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, 0, Arrays.asList(0, 1)); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, - new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig))); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, null, false))); // Math.abs("myTable_OFFLINE".hashCode()) % 2 = 0 // All instances in pool 0 should be assigned to replica-group 0, and all instances in pool 1 should be assigned to @@ -448,9 +1403,9 @@ public void testPoolBased() { // Assign instances from 2 pools to 3 replica-groups numReplicaGroups = numPools; - replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, 0, 0, 0, false); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, - new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig))); + replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, 0, 0, 0, false, null); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, null, false))); // Math.abs("myTable_OFFLINE".hashCode()) % 2 = 0 // [pool0, pool1] @@ -477,10 +1432,10 @@ public void testPoolBased() { // Reset the number of replica groups to 2 and pools to 2. numReplicaGroups = 2; numPools = 2; - replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, 0, 0, 0, true); + replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, 0, 0, 0, true, null); tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, numPools, null); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, - new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig))); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, null, true))); // Reset the instance configs to have only two pools. instanceConfigs.clear(); numInstances = 10; @@ -528,8 +1483,8 @@ public void testPoolBased() { // Select pool 0 and 1 in pool selection tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, 0, Arrays.asList(0, 1)); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, - new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig))); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, null, true))); // Get the latest existingInstancePartitions from last computation. existingInstancePartitions = instancePartitions; @@ -554,9 +1509,9 @@ public void testPoolBased() { // Assign instances from 2 pools to 3 replica-groups numReplicaGroups = 3; - replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, 0, 0, 0, true); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, - new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig))); + replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, 0, 0, 0, true, null); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, null, true))); // Get the latest existingInstancePartitions from last computation. existingInstancePartitions = instancePartitions; @@ -633,9 +1588,9 @@ public void testPoolBased() { // Reduce number of replica groups from 3 to 2. numReplicaGroups = 2; - replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, 0, 0, 0, true); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, - new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig))); + replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, 0, 0, 0, true, null); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, null, true))); // Get the latest existingInstancePartitions from last computation. existingInstancePartitions = instancePartitions; @@ -735,6 +1690,109 @@ public void testPoolBased() { assertEquals(instancePartitions.getInstances(0, 1), Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 7, SERVER_INSTANCE_ID_PREFIX + 9, SERVER_INSTANCE_ID_PREFIX + 11, SERVER_INSTANCE_ID_PREFIX + 13, SERVER_INSTANCE_ID_PREFIX + 6)); + + // The below is the test suite for testing out minimizeDataMovement with pool configs + // Add the third pool with same number of instances but keep number of pools the same (i.e. 2) + numPools = 3; + numInstances = numPools * numInstancesPerPool; + for (int i = numInstances + 4; i < numInstances + 9; i++) { + InstanceConfig instanceConfig = new InstanceConfig(SERVER_INSTANCE_ID_PREFIX + i); + instanceConfig.addTag(OFFLINE_TAG); + int pool = numPools - 1; + instanceConfig.getRecord() + .setMapField(InstanceUtils.POOL_KEY, Collections.singletonMap(OFFLINE_TAG, Integer.toString(pool))); + instanceConfigs.add(instanceConfig); + } + + // Get the latest existingInstancePartitions from last computation. + existingInstancePartitions = instancePartitions; + + // Math.abs("myTable_OFFLINE".hashCode()) % 3 = 2, but since minimizeDataMovement is enabled, + // same pools would be re-used. + // [pool0, pool1] + // r0 r1 + // Thus, the instance partition assignment remains the same as the previous one. + // pool 0: [ i12, i4, i0, i1, i10 ] + // pool 1: [ i7, i9, i11, i13, i6 ] + instancePartitions = + driver.assignInstances(InstancePartitionsType.OFFLINE, instanceConfigs, existingInstancePartitions); + assertEquals(instancePartitions.getNumReplicaGroups(), numReplicaGroups); + assertEquals(instancePartitions.getNumPartitions(), 1); + assertEquals(instancePartitions.getInstances(0, 0), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 12, SERVER_INSTANCE_ID_PREFIX + 4, SERVER_INSTANCE_ID_PREFIX + 0, + SERVER_INSTANCE_ID_PREFIX + 1, SERVER_INSTANCE_ID_PREFIX + 10)); + assertEquals(instancePartitions.getInstances(0, 1), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 7, SERVER_INSTANCE_ID_PREFIX + 9, SERVER_INSTANCE_ID_PREFIX + 11, + SERVER_INSTANCE_ID_PREFIX + 13, SERVER_INSTANCE_ID_PREFIX + 6)); + + // Set tag pool config to 3. + tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, numPools, null); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, null, true))); + + // Get the latest existingInstancePartitions from last computation. + existingInstancePartitions = instancePartitions; + + // Putting the existingPoolToInstancesMap shouldn't change the instance assignment, + // as there are only 2 replica groups needed. + // Math.abs("myTable_OFFLINE".hashCode()) % 2 = 0 + // Math.abs("myTable_OFFLINE".hashCode()) % 3 = 2 + // But since Pool 0 and Pool 1 is already being used for the table, the numReplica remains at 2, + // so the 3rd pool (Pool 2) won't be picked up. + // Thus, the instance partition assignment remains the same as the existing one. + // All instances in pool 0 should be assigned to replica-group 0, and all instances in pool 1 should be assigned to + // replica-group 1 + // Now in poolToInstancesMap: + // pool 0: [ i12, i4, i0, i1, i10 ] + // pool 1: [ i7, i9, i11, i13, i6 ] + instancePartitions = + driver.assignInstances(InstancePartitionsType.OFFLINE, instanceConfigs, existingInstancePartitions); + assertEquals(instancePartitions.getNumReplicaGroups(), numReplicaGroups); + assertEquals(instancePartitions.getNumPartitions(), 1); + assertEquals(instancePartitions.getInstances(0, 0), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 12, SERVER_INSTANCE_ID_PREFIX + 4, SERVER_INSTANCE_ID_PREFIX + 0, + SERVER_INSTANCE_ID_PREFIX + 1, SERVER_INSTANCE_ID_PREFIX + 10)); + assertEquals(instancePartitions.getInstances(0, 1), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 7, SERVER_INSTANCE_ID_PREFIX + 9, SERVER_INSTANCE_ID_PREFIX + 11, + SERVER_INSTANCE_ID_PREFIX + 13, SERVER_INSTANCE_ID_PREFIX + 6)); + + // Set replica group from 2 to 3 + numReplicaGroups = 3; + replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, 0, 0, 0, true, null); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, null, true))); + + // Get the latest existingInstancePartitions from last computation. + existingInstancePartitions = instancePartitions; + + // Now that 1 more replica group is needed, Pool 2 will be chosen for the 3rd replica group + // Math.abs("myTable_OFFLINE".hashCode()) % 2 = 0 + // Math.abs("myTable_OFFLINE".hashCode()) % 3 = 2 + // [pool0, pool1, pool2] + // r0 r1 r2 + // Each replica-group should have 2 instances assigned + // Math.abs("myTable_OFFLINE".hashCode()) % 5 = 3 + // Latest instances from ZK: + // pool 0: [ i3, i4, i0, i1, i2 ] + // pool 1: [ i8, i9, i5, i6, i7 ] + // pool 2: [ i22,i23,i19,i20,i21] + // Thus, the new assignment will become: + // pool 0: [ i12, i4, i0, i1, i10 ] + // pool 1: [ i7, i9, i11, i13, i6 ] + // pool 2: [ i22, i23, i19, i20,i21 ] + instancePartitions = + driver.assignInstances(InstancePartitionsType.OFFLINE, instanceConfigs, existingInstancePartitions); + assertEquals(instancePartitions.getNumReplicaGroups(), numReplicaGroups); + assertEquals(instancePartitions.getNumPartitions(), 1); + assertEquals(instancePartitions.getInstances(0, 0), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 12, SERVER_INSTANCE_ID_PREFIX + 4, SERVER_INSTANCE_ID_PREFIX + 0, + SERVER_INSTANCE_ID_PREFIX + 1, SERVER_INSTANCE_ID_PREFIX + 10)); + assertEquals(instancePartitions.getInstances(0, 1), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 7, SERVER_INSTANCE_ID_PREFIX + 9, SERVER_INSTANCE_ID_PREFIX + 11, + SERVER_INSTANCE_ID_PREFIX + 13, SERVER_INSTANCE_ID_PREFIX + 6)); + assertEquals(instancePartitions.getInstances(0, 2), + Arrays.asList(SERVER_INSTANCE_ID_PREFIX + 22, SERVER_INSTANCE_ID_PREFIX + 23, SERVER_INSTANCE_ID_PREFIX + 19, + SERVER_INSTANCE_ID_PREFIX + 20, SERVER_INSTANCE_ID_PREFIX + 21)); } @Test @@ -760,9 +1818,9 @@ public void testIllegalConfig() { InstanceTagPoolConfig tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, false, 0, null); InstanceReplicaGroupPartitionConfig replicaGroupPartitionConfig = - new InstanceReplicaGroupPartitionConfig(false, 0, 0, 0, 0, 0, false); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, - new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig))); + new InstanceReplicaGroupPartitionConfig(false, 0, 0, 0, 0, 0, false, null); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig, null, false))); // No instance with correct tag try { @@ -791,8 +1849,8 @@ public void testIllegalConfig() { // Enable pool tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, 0, null); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, - new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig))); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig, null, false))); // No instance has correct pool configured try { @@ -825,8 +1883,8 @@ public void testIllegalConfig() { assertEquals(instancePartitions.getInstances(0, 0), expectedInstances); tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, 3, null); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, - new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig))); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig, null, false))); // Ask for too many pools try { @@ -837,8 +1895,8 @@ public void testIllegalConfig() { } tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, 0, Arrays.asList(0, 2)); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, - new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig))); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig, null, false))); // Ask for pool that does not exist try { @@ -849,9 +1907,10 @@ public void testIllegalConfig() { } tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, 0, null); - replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(false, 6, 0, 0, 0, 0, false); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, - new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig))); + replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(false, 6, 0, 0, 0, 0, false, null + ); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig, null, false))); // Ask for too many instances try { @@ -862,9 +1921,10 @@ public void testIllegalConfig() { } // Enable replica-group - replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, 0, 0, 0, 0, false); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, - new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig))); + replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, 0, 0, 0, 0, false, null + ); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig, null, false))); // Number of replica-groups must be positive try { @@ -874,9 +1934,9 @@ public void testIllegalConfig() { assertEquals(e.getMessage(), "Number of replica-groups must be positive"); } - replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, 11, 0, 0, 0, false); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, - new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig))); + replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, 11, 0, 0, 0, false, null); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig, null, false))); // Ask for too many replica-groups try { @@ -887,9 +1947,9 @@ public void testIllegalConfig() { "Not enough qualified instances from pool: 0, cannot select 6 replica-groups from 5 instances"); } - replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, 3, 3, 0, 0, false); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, - new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig))); + replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, 3, 3, 0, 0, false, null); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig, null, false))); // Ask for too many instances try { @@ -899,9 +1959,9 @@ public void testIllegalConfig() { assertEquals(e.getMessage(), "Not enough qualified instances from pool: 0 (5 in the pool, asked for 6)"); } - replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, 3, 2, 0, 3, false); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, - new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig))); + replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, 3, 2, 0, 3, false, null); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig, null, false))); // Ask for too many instances per partition try { @@ -912,9 +1972,9 @@ public void testIllegalConfig() { "Number of instances per partition: 3 must be smaller or equal to number of instances per replica-group: 2"); } - replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, 3, 2, 0, 0, false); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, - new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig))); + replicaGroupPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, 3, 2, 0, 0, false, null); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig, null, false))); // Math.abs("myTable_OFFLINE".hashCode()) % 5 = 3 // pool0: [i3, i4, i0, i1, i2] @@ -948,12 +2008,14 @@ public void testIllegalConfig() { } tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, numPools, null); InstanceReplicaGroupPartitionConfig replicaPartitionConfig = - new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, 0, 0, false); + new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, 0, + 0, false, null); try { tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) - .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, - new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, "ILLEGAL_SELECTOR"))).build(); + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, "ILLEGAL_SELECTOR", false))) + .build(); } catch (IllegalArgumentException e) { assertEquals(e.getMessage(), "No enum constant org.apache.pinot.spi.config.table.assignment.InstanceAssignmentConfig.PartitionSelector" @@ -977,11 +2039,13 @@ public void testIllegalConfig() { } tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, numPools, null); replicaPartitionConfig = - new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, 0, 0, false); + new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, + 0, 0, false, null); tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setInstanceAssignmentConfigMap( - Collections.singletonMap(InstancePartitionsType.OFFLINE, + Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, - InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))).build(); + InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString(), false))) + .build(); driver = new InstanceAssignmentDriver(tableConfig); try { instancePartitions = driver.assignInstances(InstancePartitionsType.OFFLINE, instanceConfigs, null); @@ -1009,11 +2073,13 @@ public void testIllegalConfig() { } tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, numPools, null); replicaPartitionConfig = - new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, 0, 0, false); + new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, + numInstancesPerReplicaGroup, 0, 0, false, null); tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setInstanceAssignmentConfigMap( - Collections.singletonMap(InstancePartitionsType.OFFLINE, + Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, - InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))).build(); + InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString(), false))) + .build(); driver = new InstanceAssignmentDriver(tableConfig); try { instancePartitions = driver.assignInstances(InstancePartitionsType.OFFLINE, instanceConfigs, null); @@ -1049,11 +2115,13 @@ public void testIllegalConfig() { tagPoolConfig = new InstanceTagPoolConfig(OFFLINE_TAG, true, numPools, null); replicaPartitionConfig = - new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, 0, 0, false); + new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, + numInstancesPerReplicaGroup, 0, 0, false, null); tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setInstanceAssignmentConfigMap( - Collections.singletonMap(InstancePartitionsType.OFFLINE, + Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, - InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))).build(); + InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString(), false))) + .build(); driver = new InstanceAssignmentDriver(tableConfig); try { instancePartitions = driver.assignInstances(InstancePartitionsType.OFFLINE, instanceConfigs, null); @@ -1087,11 +2155,12 @@ public void testPoolBasedFDAware() { // Assign to 3 replica-groups so that each replica-group is assigned 7 instances InstanceReplicaGroupPartitionConfig replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, - numInstancesPerPartition, false); + numInstancesPerPartition, false, null); TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) - .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, - InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))).build(); + InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString(), false))) + .build(); InstanceAssignmentDriver driver = new InstanceAssignmentDriver(tableConfig); InstancePartitions instancePartitions = @@ -1159,11 +2228,12 @@ public void testPoolBasedFDAware() { // Assign to 3 replica-groups so that each replica-group is assigned 7 instances replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, - numInstancesPerPartition, true); + numInstancesPerPartition, true, null); tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setInstanceAssignmentConfigMap( - Collections.singletonMap(InstancePartitionsType.OFFLINE, + Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, - InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))).build(); + InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString(), true))) + .build(); driver = new InstanceAssignmentDriver(tableConfig); // existingInstancePartitions = instancePartitions instancePartitions = driver.assignInstances(InstancePartitionsType.OFFLINE, instanceConfigs, instancePartitions); @@ -1237,14 +2307,14 @@ public void testPoolBasedFDAware() { // Assign to 3 replica-groups so that each replica-group is assigned 7 instances replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, - numInstancesPerPartition, false); + numInstancesPerPartition, false, null); String partitionColumnName = "partition"; SegmentPartitionConfig segmentPartitionConfig = new SegmentPartitionConfig( Collections.singletonMap(partitionColumnName, new ColumnPartitionConfig("Modulo", numPartitionsSegment, null))); tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setInstanceAssignmentConfigMap( - Collections.singletonMap(InstancePartitionsType.OFFLINE, + Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, null, replicaPartitionConfig, - InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))) + InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString(), false))) .setReplicaGroupStrategyConfig(new ReplicaGroupStrategyConfig(partitionColumnName, numInstancesPerReplicaGroup)) .setSegmentPartitionConfig(segmentPartitionConfig).build(); driver = new InstanceAssignmentDriver(tableConfig); @@ -1310,15 +2380,15 @@ public void testPoolBasedFDAware() { // Assign to 3 replica-groups so that each replica-group is assigned 3 instances replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, - numInstancesPerPartition, false); + numInstancesPerPartition, false, null); // Do not rotate for testing InstanceConstraintConfig instanceConstraintConfig = new InstanceConstraintConfig(Arrays.asList("constraint1", "constraint2")); tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME + TABLE_NAME_ZERO_HASH_COMPLEMENT) - .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, instanceConstraintConfig, replicaPartitionConfig, - InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))) + InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString(), false))) .build(); driver = new InstanceAssignmentDriver(tableConfig); @@ -1367,14 +2437,14 @@ public void testPoolBasedFDAware() { // Assign to 3 replica-groups so that each replica-group is assigned 3 instances replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, - numInstancesPerPartition, true); + numInstancesPerPartition, true, null); // Do not rotate for testing instanceConstraintConfig = new InstanceConstraintConfig(Arrays.asList("constraint1", "constraint2")); tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME + TABLE_NAME_ZERO_HASH_COMPLEMENT) - .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, instanceConstraintConfig, replicaPartitionConfig, - InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))) + InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString(), true))) .build(); driver = new InstanceAssignmentDriver(tableConfig); @@ -1433,15 +2503,15 @@ public void testPoolBasedFDAware() { // Assign to 3 replica-groups so that each replica-group is assigned 5 instances replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, - numInstancesPerPartition, false); + numInstancesPerPartition, false, null); // Do not rotate instance sequence in pool (for testing) instanceConstraintConfig = new InstanceConstraintConfig(Arrays.asList("constraint1", "constraint2")); // Do not rotate pool sequence (for testing) tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME + TABLE_NAME_ZERO_HASH_COMPLEMENT) - .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, instanceConstraintConfig, replicaPartitionConfig, - InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))) + InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString(), false))) .build(); driver = new InstanceAssignmentDriver(tableConfig); @@ -1499,15 +2569,15 @@ public void testPoolBasedFDAware() { // Assign to 3 replica-groups so that each replica-group is assigned 5 instances replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, - numInstancesPerPartition, true); + numInstancesPerPartition, true, null); // Do not rotate instance sequence in pool (for testing) instanceConstraintConfig = new InstanceConstraintConfig(Arrays.asList("constraint1", "constraint2")); // Do not rotate pool sequence (for testing) tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME + TABLE_NAME_ZERO_HASH_COMPLEMENT) - .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, instanceConstraintConfig, replicaPartitionConfig, - InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))) + InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString(), true))) .build(); driver = new InstanceAssignmentDriver(tableConfig); @@ -1571,14 +2641,14 @@ public void testPoolBasedFDAware() { // Assign to 3 replica-groups so that each replica-group is assigned 1 instances replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, - numInstancesPerPartition, false); + numInstancesPerPartition, false, null); // Do not rotate for testing instanceConstraintConfig = new InstanceConstraintConfig(Arrays.asList("constraint1", "constraint2")); tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME + TABLE_NAME_ZERO_HASH_COMPLEMENT) - .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, instanceConstraintConfig, replicaPartitionConfig, - InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))) + InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString(), false))) .build(); driver = new InstanceAssignmentDriver(tableConfig); @@ -1621,15 +2691,15 @@ public void testPoolBasedFDAware() { // Assign to 6 replica-groups so that each replica-group is assigned 2 instances replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, - numInstancesPerPartition, false); + numInstancesPerPartition, false, null); // Do not rotate instance sequence in pool (for testing) instanceConstraintConfig = new InstanceConstraintConfig(Arrays.asList("constraint1", "constraint2")); // Do not rotate pool sequence (for testing) tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME + TABLE_NAME_ZERO_HASH_COMPLEMENT) - .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, instanceConstraintConfig, replicaPartitionConfig, - InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))) + InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString(), false))) .build(); driver = new InstanceAssignmentDriver(tableConfig); @@ -1685,15 +2755,15 @@ public void testPoolBasedFDAware() { // Assign to 6 replica-groups so that each replica-group is assigned 2 instances replicaPartitionConfig = new InstanceReplicaGroupPartitionConfig(true, 0, numReplicaGroups, numInstancesPerReplicaGroup, numPartitions, - numInstancesPerPartition, true); + numInstancesPerPartition, true, null); // Do not rotate instance sequence in pool (for testing) instanceConstraintConfig = new InstanceConstraintConfig(Arrays.asList("constraint1", "constraint2")); // Do not rotate pool sequence (for testing) tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME + TABLE_NAME_ZERO_HASH_COMPLEMENT) - .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, + .setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), new InstanceAssignmentConfig(tagPoolConfig, instanceConstraintConfig, replicaPartitionConfig, - InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString()))) + InstanceAssignmentConfig.PartitionSelector.FD_AWARE_INSTANCE_PARTITION_SELECTOR.toString(), true))) .build(); driver = new InstanceAssignmentDriver(tableConfig); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceReplicaGroupPartitionSelectorTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceReplicaGroupPartitionSelectorTest.java new file mode 100644 index 000000000000..288b789aee39 --- /dev/null +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/instance/InstanceReplicaGroupPartitionSelectorTest.java @@ -0,0 +1,217 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.controller.helix.core.assignment.instance; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.commons.text.StringSubstitutor; +import org.apache.helix.model.InstanceConfig; +import org.apache.helix.zookeeper.datamodel.ZNRecord; +import org.apache.pinot.common.assignment.InstancePartitions; +import org.apache.pinot.spi.config.table.assignment.InstanceReplicaGroupPartitionConfig; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; + + +public class InstanceReplicaGroupPartitionSelectorTest { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + //@formatter:off + private static final String INSTANCE_CONFIG_TEMPLATE = + "{\n" + + " \"id\": \"Server_pinot-server-${serverName}.pinot-server-headless.pinot.svc.cluster.local_8098\",\n" + + " \"simpleFields\": {\n" + + " \"HELIX_ENABLED\": \"true\",\n" + + " \"HELIX_ENABLED_TIMESTAMP\": \"1688959934305\",\n" + + " \"HELIX_HOST\": \"pinot-server-${serverName}.pinot-server-headless.pinot.svc.cluster.local\",\n" + + " \"HELIX_PORT\": \"8098\",\n" + + " \"adminPort\": \"8097\",\n" + + " \"grpcPort\": \"8090\",\n" + + " \"queryMailboxPort\": \"46347\",\n" + + " \"queryServerPort\": \"45031\",\n" + + " \"shutdownInProgress\": \"false\"\n" + + " },\n" + + " \"mapFields\": {\n" + + " \"SYSTEM_RESOURCE_INFO\": {\n" + + " \"numCores\": \"16\",\n" + + " \"totalMemoryMB\": \"126976\",\n" + + " \"maxHeapSizeMB\": \"65536\"\n" + + " },\n" + + " \"pool\": {\n" + + " \"DefaultTenant_OFFLINE\": \"${pool}\",\n" + + " \"${poolName}\": \"${pool}\",\n" + + " \"AllReplicationGroups\": \"1\"\n" + + " }\n" + + " },\n" + + " \"listFields\": {\n" + + " \"TAG_LIST\": [\n" + + " \"DefaultTenant_OFFLINE\",\n" + + " \"DefaultTenant_REALTIME\",\n" + + " \"${poolName}\",\n" + + " \"AllReplicationGroups\"\n" + + " ]\n" + + " }\n" + + "}"; + //@formatter:on + + @Test + public void testPoolsWhenOneMorePoolAddedAndOneMoreReplicaGroupsNeeded() + throws JsonProcessingException { + //@formatter:off + String existingPartitionsJson = + "{\n" + + " \"instancePartitionsName\": \"0f97dac8-4123-47c6-9a4d-b8ce039c5ea5_OFFLINE\",\n" + + " \"partitionToInstancesMap\": {\n" + + " \"0_0\": [\n" + + " \"Server_pinot-server-rg0-0.pinot-server-headless.pinot.svc.cluster.local_8098\",\n" + + " \"Server_pinot-server-rg0-1.pinot-server-headless.pinot.svc.cluster.local_8098\"\n" + + " ]\n" + + " }\n" + + "}"; + //@formatter:on + InstancePartitions existing = OBJECT_MAPPER.readValue(existingPartitionsJson, InstancePartitions.class); + InstanceReplicaGroupPartitionConfig config = + new InstanceReplicaGroupPartitionConfig(true, 0, 2, 2, 1, 2, true, null); + + InstanceReplicaGroupPartitionSelector selector = + new InstanceReplicaGroupPartitionSelector(config, "tableNameBlah", existing, true); + + String[] serverNames = {"rg0-0", "rg0-1", "rg1-0", "rg1-1"}; + String[] poolNumbers = {"0", "0", "1", "1"}; + String[] poolNames = { + "FirstHalfReplicationGroups", "FirstHalfReplicationGroups", "SecondHalfReplicationGroups", + "SecondHalfReplicationGroups" + }; + Map> poolToInstanceConfigsMap = new HashMap<>(); + + for (int i = 0; i < serverNames.length; i++) { + Map valuesMap = new HashMap<>(); + valuesMap.put("serverName", serverNames[i]); + valuesMap.put("pool", poolNumbers[i]); + valuesMap.put("poolName", poolNames[i]); + + StringSubstitutor substitutor = new StringSubstitutor(valuesMap); + String resolvedString = substitutor.replace(INSTANCE_CONFIG_TEMPLATE); + + ZNRecord znRecord = OBJECT_MAPPER.readValue(resolvedString, ZNRecord.class); + int poolNumber = Integer.parseInt(poolNumbers[i]); + poolToInstanceConfigsMap.computeIfAbsent(poolNumber, k -> new ArrayList<>()).add(new InstanceConfig(znRecord)); + } + InstancePartitions assignedPartitions = new InstancePartitions("0f97dac8-4123-47c6-9a4d-b8ce039c5ea5_OFFLINE"); + selector.selectInstances(poolToInstanceConfigsMap, assignedPartitions); + + // Now that 1 more pool is added and 1 more RG is needed, a new set called "0_1" is generated, + // and the instances from Pool 1 are assigned to this new replica. + //@formatter:off + String expectedInstancePartitions = + "{\n" + + " \"instancePartitionsName\": \"0f97dac8-4123-47c6-9a4d-b8ce039c5ea5_OFFLINE\",\n" + + " \"partitionToInstancesMap\": {\n" + + " \"0_0\": [\n" + + " \"Server_pinot-server-rg0-0.pinot-server-headless.pinot.svc.cluster.local_8098\",\n" + + " \"Server_pinot-server-rg0-1.pinot-server-headless.pinot.svc.cluster.local_8098\"\n" + + " ],\n" + + " \"0_1\": [\n" + + " \"Server_pinot-server-rg1-0.pinot-server-headless.pinot.svc.cluster.local_8098\",\n" + + " \"Server_pinot-server-rg1-1.pinot-server-headless.pinot.svc.cluster.local_8098\"\n" + + " ]\n" + + " }\n" + + "}"; + //@formatter:on + InstancePartitions expectedPartitions = + OBJECT_MAPPER.readValue(expectedInstancePartitions, InstancePartitions.class); + assertEquals(assignedPartitions, expectedPartitions); + } + + @Test + public void testSelectPoolsWhenExistingReplicaGroupMapsToMultiplePools() + throws JsonProcessingException { + // The "rg0-2" instance used to belong to Pool 1, but now it belongs to Pool 0. + //@formatter:off + String existingPartitionsJson = + "{\n" + + " \"instancePartitionsName\": \"0f97dac8-4123-47c6-9a4d-b8ce039c5ea5_OFFLINE\",\n" + + " \"partitionToInstancesMap\": {\n" + + " \"0_0\": [\n" + + " \"Server_pinot-server-rg0-0.pinot-server-headless.pinot.svc.cluster.local_8098\",\n" + + " \"Server_pinot-server-rg0-1.pinot-server-headless.pinot.svc.cluster.local_8098\"\n" + + " ],\n" + + " \"0_1\": [\n" + + " \"Server_pinot-server-rg0-2.pinot-server-headless.pinot.svc.cluster.local_8098\",\n" + + " \"Server_pinot-server-rg1-0.pinot-server-headless.pinot.svc.cluster.local_8098\"\n" + + " ]\n" + + " }\n" + + "}"; + //@formatter:on + InstancePartitions existing = OBJECT_MAPPER.readValue(existingPartitionsJson, InstancePartitions.class); + InstanceReplicaGroupPartitionConfig config = + new InstanceReplicaGroupPartitionConfig(true, 0, 2, 2, 1, 2, true, null); + + InstanceReplicaGroupPartitionSelector selector = + new InstanceReplicaGroupPartitionSelector(config, "tableNameBlah", existing, true); + + String[] serverNames = {"rg0-0", "rg0-1", "rg0-2", "rg1-0", "rg1-1", "rg1-2"}; + String[] poolNumbers = {"0", "0", "0", "1", "1", "1"}; + Map> poolToInstanceConfigsMap = new HashMap<>(); + + for (int i = 0; i < serverNames.length; i++) { + Map valuesMap = new HashMap<>(); + valuesMap.put("serverName", serverNames[i]); + valuesMap.put("pool", poolNumbers[i]); + + StringSubstitutor substitutor = new StringSubstitutor(valuesMap); + String resolvedString = substitutor.replace(INSTANCE_CONFIG_TEMPLATE); + + ZNRecord znRecord = OBJECT_MAPPER.readValue(resolvedString, ZNRecord.class); + int poolNumber = Integer.parseInt(poolNumbers[i]); + poolToInstanceConfigsMap.computeIfAbsent(poolNumber, k -> new ArrayList<>()).add(new InstanceConfig(znRecord)); + } + + InstancePartitions assignedPartitions = new InstancePartitions("0f97dac8-4123-47c6-9a4d-b8ce039c5ea5_OFFLINE"); + selector.selectInstances(poolToInstanceConfigsMap, assignedPartitions); + + // The "rg0-2" instance is replaced by "rg1-0" (which belongs to Pool 1), as "rg0-2" no longer belongs to Pool 1. + // And "rg1-0" remains the same position as it's always under Pool 1. + //@formatter:off + String expectedInstancePartitions = + "{\n" + + " \"instancePartitionsName\": \"0f97dac8-4123-47c6-9a4d-b8ce039c5ea5_OFFLINE\",\n" + + " \"partitionToInstancesMap\": {\n" + + " \"0_0\": [\n" + + " \"Server_pinot-server-rg0-0.pinot-server-headless.pinot.svc.cluster.local_8098\",\n" + + " \"Server_pinot-server-rg0-1.pinot-server-headless.pinot.svc.cluster.local_8098\"\n" + + " ],\n" + + " \"0_1\": [\n" + + " \"Server_pinot-server-rg1-1.pinot-server-headless.pinot.svc.cluster.local_8098\",\n" + + " \"Server_pinot-server-rg1-0.pinot-server-headless.pinot.svc.cluster.local_8098\"\n" + + " ]\n" + + " }\n" + + "}"; + //@formatter:on + InstancePartitions expectedPartitions = + OBJECT_MAPPER.readValue(expectedInstancePartitions, InstancePartitions.class); + assertEquals(assignedPartitions, expectedPartitions); + } +} diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineNonReplicaGroupTieredSegmentAssignmentTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineNonReplicaGroupTieredSegmentAssignmentTest.java index d2af2c8dfd8f..c9a42536d3d6 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineNonReplicaGroupTieredSegmentAssignmentTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/OfflineNonReplicaGroupTieredSegmentAssignmentTest.java @@ -25,20 +25,19 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; -import org.apache.commons.configuration.BaseConfiguration; -import org.apache.commons.configuration.Configuration; +import java.util.TreeSet; import org.apache.pinot.common.assignment.InstancePartitions; import org.apache.pinot.common.assignment.InstancePartitionsUtils; import org.apache.pinot.common.tier.PinotServerTierStorage; import org.apache.pinot.common.tier.Tier; import org.apache.pinot.common.tier.TierFactory; import org.apache.pinot.common.tier.TierSegmentSelector; +import org.apache.pinot.controller.helix.core.rebalance.RebalanceConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.config.table.TierConfig; import org.apache.pinot.spi.config.table.assignment.InstancePartitionsType; import org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel; -import org.apache.pinot.spi.utils.RebalanceConfigConstants; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.testng.Assert; import org.testng.annotations.BeforeClass; @@ -112,7 +111,7 @@ public void setUp() { new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setNumReplicas(NUM_REPLICAS) .setTierConfigList(tierConfigList).build(); - _segmentAssignment = SegmentAssignmentFactory.getSegmentAssignment(null, tableConfig); + _segmentAssignment = SegmentAssignmentFactory.getSegmentAssignment(null, tableConfig, null); // { // 0_0=[instance_0, instance_1, instance_2, instance_3, instance_4, instance_5, instance_6, instance_7, @@ -165,7 +164,7 @@ public void testTableBalanced() { // On rebalancing, segments move to tiers Map> newAssignment = _segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, _sortedTiers, - _tierInstancePartitionsMap, new BaseConfiguration()); + _tierInstancePartitionsMap, new RebalanceConfig()); assertEquals(newAssignment.size(), NUM_SEGMENTS); // segments 0-49 remain unchanged @@ -205,7 +204,7 @@ public void testTableBalanced() { // rebalance without tierInstancePartitions resets the assignment Map> resetAssignment = - _segmentAssignment.rebalanceTable(newAssignment, _instancePartitionsMap, null, null, new BaseConfiguration()); + _segmentAssignment.rebalanceTable(newAssignment, _instancePartitionsMap, null, null, new RebalanceConfig()); for (String segment : SEGMENTS) { Assert.assertTrue(INSTANCES.containsAll(resetAssignment.get(segment).keySet())); } @@ -214,7 +213,10 @@ public void testTableBalanced() { @Test public void testBootstrapTable() { Map> currentAssignment = new TreeMap<>(); - for (String segmentName : SEGMENTS) { + // The list of segments are segment_0, segment_1, ... segment_10, ...; but TreeMap sorts segments as segment_0, + // segment_1, segment_10, ... segment_2, ... So to make bootstrap generate same assignment as assigning each + // segment separately, we need to process the segments in the same order. + for (String segmentName : new TreeSet<>(SEGMENTS)) { List instancesAssigned = _segmentAssignment.assignSegment(segmentName, currentAssignment, _instancePartitionsMap); currentAssignment.put(segmentName, @@ -222,11 +224,11 @@ public void testBootstrapTable() { } // Bootstrap table should reassign all segments - Configuration rebalanceConfig = new BaseConfiguration(); - rebalanceConfig.setProperty(RebalanceConfigConstants.BOOTSTRAP, true); + RebalanceConfig rebalanceConfig = new RebalanceConfig(); + rebalanceConfig.setBootstrap(true); Map> newAssignment = _segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, _sortedTiers, - _tierInstancePartitionsMap, new BaseConfiguration()); + _tierInstancePartitionsMap, rebalanceConfig); assertEquals(newAssignment.size(), NUM_SEGMENTS); // segments 0-49 remain unchanged diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeNonReplicaGroupSegmentAssignmentTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeNonReplicaGroupSegmentAssignmentTest.java index 2bf6fd11e059..9d921d3af845 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeNonReplicaGroupSegmentAssignmentTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeNonReplicaGroupSegmentAssignmentTest.java @@ -24,18 +24,17 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; -import org.apache.commons.configuration.BaseConfiguration; import org.apache.helix.HelixManager; import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.common.assignment.InstancePartitions; import org.apache.pinot.common.utils.LLCSegmentName; +import org.apache.pinot.controller.helix.core.rebalance.RebalanceConfig; import org.apache.pinot.core.realtime.impl.fakestream.FakeStreamConfigUtils; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.config.table.assignment.InstancePartitionsType; import org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel; -import org.apache.pinot.spi.utils.RebalanceConfigConstants; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -83,8 +82,8 @@ public void setUp() { Map streamConfigs = FakeStreamConfigUtils.getDefaultLowLevelStreamConfigs().getStreamConfigsMap(); TableConfig tableConfig = new TableConfigBuilder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setNumReplicas(NUM_REPLICAS) - .setLLC(true).setStreamConfigs(streamConfigs).build(); - _segmentAssignment = SegmentAssignmentFactory.getSegmentAssignment(createHelixManager(), tableConfig); + .setStreamConfigs(streamConfigs).build(); + _segmentAssignment = SegmentAssignmentFactory.getSegmentAssignment(createHelixManager(), tableConfig, null); _instancePartitionsMap = new TreeMap<>(); // CONSUMING instances: @@ -118,11 +117,11 @@ public void testReplicationForSegmentAssignment() { Map streamConfigs = FakeStreamConfigUtils.getDefaultLowLevelStreamConfigs().getStreamConfigsMap(); TableConfig tableConfig = new TableConfigBuilder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setNumReplicas(NUM_REPLICAS) - .setLLC(true).setStreamConfigs(streamConfigs).build(); + .setStreamConfigs(streamConfigs).build(); // Update the replication by changing the NUM_REPLICAS_PER_PARTITION tableConfig.getValidationConfig().setReplicasPerPartition(NUM_REPLICAS_PER_PARTITION); SegmentAssignment segmentAssignment = - SegmentAssignmentFactory.getSegmentAssignment(createHelixManager(), tableConfig); + SegmentAssignmentFactory.getSegmentAssignment(createHelixManager(), tableConfig, null); Map onlyCompletedInstancePartitionMap = ImmutableMap.of(InstancePartitionsType.COMPLETED, _instancePartitionsMap.get(InstancePartitionsType.COMPLETED)); @@ -227,13 +226,12 @@ public void testRelocateCompletedSegments() { Map noRelocationInstancePartitionsMap = ImmutableMap.of(InstancePartitionsType.CONSUMING, _instancePartitionsMap.get(InstancePartitionsType.CONSUMING)); assertEquals(_segmentAssignment.rebalanceTable(currentAssignment, noRelocationInstancePartitionsMap, null, null, - new BaseConfiguration()), currentAssignment); + new RebalanceConfig()), currentAssignment); // Rebalance with COMPLETED instance partitions should relocate all COMPLETED (ONLINE) segments to the COMPLETED // instances Map> newAssignment = - _segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, null, null, - new BaseConfiguration()); + _segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, null, null, new RebalanceConfig()); assertEquals(newAssignment.size(), NUM_SEGMENTS + numUploadedSegments + 1); for (int segmentId = 0; segmentId < NUM_SEGMENTS; segmentId++) { if (segmentId < NUM_SEGMENTS - NUM_PARTITIONS) { @@ -270,8 +268,8 @@ public void testRelocateCompletedSegments() { assertEquals(totalNumSegmentsAssigned, expectedTotalNumSegmentsAssigned); // Rebalance with COMPLETED instance partitions including CONSUMING segments should give the same assignment - BaseConfiguration rebalanceConfig = new BaseConfiguration(); - rebalanceConfig.setProperty(RebalanceConfigConstants.INCLUDE_CONSUMING, true); + RebalanceConfig rebalanceConfig = new RebalanceConfig(); + rebalanceConfig.setIncludeConsuming(true); assertEquals( _segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, null, null, rebalanceConfig), newAssignment); @@ -279,11 +277,11 @@ public void testRelocateCompletedSegments() { // Rebalance without COMPLETED instance partitions again should change the segment assignment back currentAssignment.put(offlineSegmentName, offlineSegmentInstanceStateMap); assertEquals(_segmentAssignment.rebalanceTable(newAssignment, noRelocationInstancePartitionsMap, null, null, - new BaseConfiguration()), currentAssignment); + new RebalanceConfig()), currentAssignment); // Bootstrap table without COMPLETED instance partitions should be the same as regular rebalance - rebalanceConfig = new BaseConfiguration(); - rebalanceConfig.setProperty(RebalanceConfigConstants.BOOTSTRAP, true); + rebalanceConfig = new RebalanceConfig(); + rebalanceConfig.setBootstrap(true); assertEquals(_segmentAssignment.rebalanceTable(currentAssignment, noRelocationInstancePartitionsMap, null, null, rebalanceConfig), currentAssignment); assertEquals(_segmentAssignment.rebalanceTable(newAssignment, noRelocationInstancePartitionsMap, null, null, diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeNonReplicaGroupTieredSegmentAssignmentTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeNonReplicaGroupTieredSegmentAssignmentTest.java index 73cb94c4fa96..e03a8d4a46ab 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeNonReplicaGroupTieredSegmentAssignmentTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeNonReplicaGroupTieredSegmentAssignmentTest.java @@ -25,7 +25,6 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; -import org.apache.commons.configuration.BaseConfiguration; import org.apache.pinot.common.assignment.InstancePartitions; import org.apache.pinot.common.assignment.InstancePartitionsUtils; import org.apache.pinot.common.tier.PinotServerTierStorage; @@ -33,13 +32,13 @@ import org.apache.pinot.common.tier.TierFactory; import org.apache.pinot.common.tier.TierSegmentSelector; import org.apache.pinot.common.utils.LLCSegmentName; +import org.apache.pinot.controller.helix.core.rebalance.RebalanceConfig; import org.apache.pinot.core.realtime.impl.fakestream.FakeStreamConfigUtils; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.config.table.TierConfig; import org.apache.pinot.spi.config.table.assignment.InstancePartitionsType; import org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel; -import org.apache.pinot.spi.utils.RebalanceConfigConstants; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -122,8 +121,8 @@ public void setUp() { Map streamConfigs = FakeStreamConfigUtils.getDefaultLowLevelStreamConfigs().getStreamConfigsMap(); TableConfig tableConfig = new TableConfigBuilder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setNumReplicas(NUM_REPLICAS) - .setTierConfigList(tierConfigList).setLLC(true).setStreamConfigs(streamConfigs).build(); - _segmentAssignment = SegmentAssignmentFactory.getSegmentAssignment(null, tableConfig); + .setTierConfigList(tierConfigList).setStreamConfigs(streamConfigs).build(); + _segmentAssignment = SegmentAssignmentFactory.getSegmentAssignment(null, tableConfig, null); _instancePartitionsMap = new TreeMap<>(); // CONSUMING instances: @@ -184,8 +183,7 @@ public void testRelocateCompletedSegments() { // Rebalance without tier instancePartitions moves all instances to COMPLETED Map> newAssignment = - _segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, null, null, - new BaseConfiguration()); + _segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, null, null, new RebalanceConfig()); assertEquals(newAssignment.size(), NUM_SEGMENTS); for (int segmentId = 0; segmentId < NUM_SEGMENTS; segmentId++) { if (segmentId < NUM_SEGMENTS - NUM_PARTITIONS) { // ONLINE segments @@ -208,7 +206,7 @@ public void testRelocateCompletedSegments() { int expectedOnTierB = 20; int expectedOnCompleted = NUM_SEGMENTS - NUM_PARTITIONS - expectedOnTierA - expectedOnTierB; newAssignment = _segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, _sortedTiers, - _tierInstancePartitionsMap, new BaseConfiguration()); + _tierInstancePartitionsMap, new RebalanceConfig()); assertEquals(newAssignment.size(), NUM_SEGMENTS); for (int segmentId = 0; segmentId < NUM_SEGMENTS; segmentId++) { if (segmentId < NUM_SEGMENTS - NUM_PARTITIONS) { @@ -259,8 +257,8 @@ public void testRelocateCompletedSegments() { } // Rebalance with including CONSUMING should give the same assignment - BaseConfiguration rebalanceConfig = new BaseConfiguration(); - rebalanceConfig.setProperty(RebalanceConfigConstants.INCLUDE_CONSUMING, true); + RebalanceConfig rebalanceConfig = new RebalanceConfig(); + rebalanceConfig.setIncludeConsuming(true); assertEquals(_segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, _sortedTiers, _tierInstancePartitionsMap, rebalanceConfig), newAssignment); @@ -270,13 +268,13 @@ public void testRelocateCompletedSegments() { noRelocationInstancePartitionsMap.put(InstancePartitionsType.CONSUMING, _instancePartitionsMap.get(InstancePartitionsType.CONSUMING)); assertEquals(_segmentAssignment.rebalanceTable(newAssignment, noRelocationInstancePartitionsMap, null, null, - new BaseConfiguration()), currentAssignment); + new RebalanceConfig()), currentAssignment); // Rebalance without COMPLETED instance partitions and with tierInstancePartitions should move ONLINE segments to // Tiers and CONSUMING segments to CONSUMING tenant. newAssignment = _segmentAssignment.rebalanceTable(currentAssignment, noRelocationInstancePartitionsMap, _sortedTiers, - _tierInstancePartitionsMap, new BaseConfiguration()); + _tierInstancePartitionsMap, new RebalanceConfig()); numSegmentsAssignedPerInstance = SegmentAssignmentUtils.getNumSegmentsAssignedPerInstance(newAssignment, INSTANCES_TIER_A); @@ -299,8 +297,8 @@ public void testRelocateCompletedSegments() { assertEquals(numSegmentsAssignedPerInstance.length, NUM_CONSUMING_INSTANCES); // Bootstrap - rebalanceConfig = new BaseConfiguration(); - rebalanceConfig.setProperty(RebalanceConfigConstants.BOOTSTRAP, true); + rebalanceConfig = new RebalanceConfig(); + rebalanceConfig.setBootstrap(true); newAssignment = _segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, _sortedTiers, _tierInstancePartitionsMap, rebalanceConfig); int index = 0; diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeReplicaGroupSegmentAssignmentTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeReplicaGroupSegmentAssignmentTest.java index 713b4c442a86..2c590ef25e33 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeReplicaGroupSegmentAssignmentTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/RealtimeReplicaGroupSegmentAssignmentTest.java @@ -25,12 +25,12 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; -import org.apache.commons.configuration.BaseConfiguration; import org.apache.helix.HelixManager; import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.common.assignment.InstancePartitions; import org.apache.pinot.common.utils.LLCSegmentName; +import org.apache.pinot.controller.helix.core.rebalance.RebalanceConfig; import org.apache.pinot.core.realtime.impl.fakestream.FakeStreamConfigUtils; import org.apache.pinot.spi.config.table.ReplicaGroupStrategyConfig; import org.apache.pinot.spi.config.table.TableConfig; @@ -38,7 +38,6 @@ import org.apache.pinot.spi.config.table.assignment.InstancePartitionsType; import org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel; import org.apache.pinot.spi.utils.CommonConstants.Segment.AssignmentStrategy; -import org.apache.pinot.spi.utils.RebalanceConfigConstants; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -87,10 +86,10 @@ public void setUp() { Map streamConfigs = FakeStreamConfigUtils.getDefaultLowLevelStreamConfigs().getStreamConfigsMap(); TableConfig tableConfig = new TableConfigBuilder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setNumReplicas(NUM_REPLICAS) - .setLLC(true).setStreamConfigs(streamConfigs) + .setStreamConfigs(streamConfigs) .setSegmentAssignmentStrategy(AssignmentStrategy.REPLICA_GROUP_SEGMENT_ASSIGNMENT_STRATEGY) .setReplicaGroupStrategyConfig(new ReplicaGroupStrategyConfig(PARTITION_COLUMN, 1)).build(); - _segmentAssignment = SegmentAssignmentFactory.getSegmentAssignment(createHelixManager(), tableConfig); + _segmentAssignment = SegmentAssignmentFactory.getSegmentAssignment(createHelixManager(), tableConfig, null); _instancePartitionsMap = new TreeMap<>(); // CONSUMING instances: @@ -214,13 +213,12 @@ public void testRelocateCompletedSegments() { Map noRelocationInstancePartitionsMap = ImmutableMap.of(InstancePartitionsType.CONSUMING, _instancePartitionsMap.get(InstancePartitionsType.CONSUMING)); assertEquals(_segmentAssignment.rebalanceTable(currentAssignment, noRelocationInstancePartitionsMap, null, null, - new BaseConfiguration()), currentAssignment); + new RebalanceConfig()), currentAssignment); // Rebalance with COMPLETED instance partitions should relocate all COMPLETED (ONLINE) segments to the COMPLETED // instances Map> newAssignment = - _segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, null, null, - new BaseConfiguration()); + _segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, null, null, new RebalanceConfig()); assertEquals(newAssignment.size(), NUM_SEGMENTS + uploadedSegmentNames.size() + 1); for (int segmentId = 0; segmentId < NUM_SEGMENTS; segmentId++) { if (segmentId < NUM_SEGMENTS - NUM_PARTITIONS) { @@ -255,19 +253,19 @@ public void testRelocateCompletedSegments() { } // Rebalance with COMPLETED instance partitions including CONSUMING segments should give the same assignment - BaseConfiguration rebalanceConfig = new BaseConfiguration(); - rebalanceConfig.setProperty(RebalanceConfigConstants.INCLUDE_CONSUMING, true); + RebalanceConfig rebalanceConfig = new RebalanceConfig(); + rebalanceConfig.setIncludeConsuming(true); assertEquals( _segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, null, null, rebalanceConfig), newAssignment); // Rebalance without COMPLETED instance partitions again should change the segment assignment back assertEquals(_segmentAssignment.rebalanceTable(newAssignment, noRelocationInstancePartitionsMap, null, null, - new BaseConfiguration()), currentAssignment); + new RebalanceConfig()), currentAssignment); // Bootstrap table without COMPLETED instance partitions should be the same as regular rebalance - rebalanceConfig = new BaseConfiguration(); - rebalanceConfig.setProperty(RebalanceConfigConstants.BOOTSTRAP, true); + rebalanceConfig = new RebalanceConfig(); + rebalanceConfig.setBootstrap(true); assertEquals(_segmentAssignment.rebalanceTable(currentAssignment, noRelocationInstancePartitionsMap, null, null, rebalanceConfig), currentAssignment); assertEquals(_segmentAssignment.rebalanceTable(newAssignment, noRelocationInstancePartitionsMap, null, null, @@ -417,8 +415,8 @@ public void testExplicitPartition() { Map instancePartitionsMap = ImmutableMap.of(InstancePartitionsType.CONSUMING, consumingInstancePartitions, InstancePartitionsType.COMPLETED, completedInstancePartitions); - BaseConfiguration rebalanceConfig = new BaseConfiguration(); - rebalanceConfig.setProperty(RebalanceConfigConstants.INCLUDE_CONSUMING, true); + RebalanceConfig rebalanceConfig = new RebalanceConfig(); + rebalanceConfig.setIncludeConsuming(true); Map> newAssignment = _segmentAssignment.rebalanceTable(uploadedCurrentAssignment, instancePartitionsMap, null, null, rebalanceConfig); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/StrictRealtimeSegmentAssignmentTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/StrictRealtimeSegmentAssignmentTest.java new file mode 100644 index 000000000000..13520bb4f84c --- /dev/null +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/StrictRealtimeSegmentAssignmentTest.java @@ -0,0 +1,287 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.controller.helix.core.assignment.segment; + +import com.google.common.collect.ImmutableMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import org.apache.helix.HelixManager; +import org.apache.helix.store.zk.ZkHelixPropertyStore; +import org.apache.helix.zookeeper.datamodel.ZNRecord; +import org.apache.pinot.common.assignment.InstancePartitions; +import org.apache.pinot.common.utils.LLCSegmentName; +import org.apache.pinot.core.realtime.impl.fakestream.FakeStreamConfigUtils; +import org.apache.pinot.spi.config.table.ReplicaGroupStrategyConfig; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.config.table.UpsertConfig; +import org.apache.pinot.spi.config.table.assignment.InstancePartitionsType; +import org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel; +import org.apache.pinot.spi.utils.CommonConstants.Segment.AssignmentStrategy; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + + +@SuppressWarnings("unchecked") +public class StrictRealtimeSegmentAssignmentTest { + private static final int NUM_REPLICAS = 3; + private static final String PARTITION_COLUMN = "partitionColumn"; + private static final int NUM_PARTITIONS = 4; + private static final int NUM_SEGMENTS = 24; + private static final String CONSUMING_INSTANCE_NAME_PREFIX = "consumingInstance_"; + private static final int NUM_CONSUMING_INSTANCES = 9; + private static final List CONSUMING_INSTANCES = + SegmentAssignmentTestUtils.getNameList(CONSUMING_INSTANCE_NAME_PREFIX, NUM_CONSUMING_INSTANCES); + private static final List NEW_CONSUMING_INSTANCES = + SegmentAssignmentTestUtils.getNameList("new_" + CONSUMING_INSTANCE_NAME_PREFIX, NUM_CONSUMING_INSTANCES); + private static final String RAW_TABLE_NAME = "testTable"; + private static final String CONSUMING_INSTANCE_PARTITIONS_NAME = + InstancePartitionsType.CONSUMING.getInstancePartitionsName(RAW_TABLE_NAME); + + private List _segments; + private SegmentAssignment _segmentAssignment; + private Map _instancePartitionsMap; + private InstancePartitions _newConsumingInstancePartitions; + + @BeforeClass + public void setUp() { + _segments = new ArrayList<>(NUM_SEGMENTS); + for (int segmentId = 0; segmentId < NUM_SEGMENTS; segmentId++) { + _segments.add(new LLCSegmentName(RAW_TABLE_NAME, segmentId % NUM_PARTITIONS, segmentId / NUM_PARTITIONS, + System.currentTimeMillis()).getSegmentName()); + } + + Map streamConfigs = FakeStreamConfigUtils.getDefaultLowLevelStreamConfigs().getStreamConfigsMap(); + UpsertConfig upsertConfig = new UpsertConfig(UpsertConfig.Mode.FULL); + TableConfig tableConfig = + new TableConfigBuilder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setNumReplicas(NUM_REPLICAS) + .setStreamConfigs(streamConfigs).setUpsertConfig(upsertConfig) + .setSegmentAssignmentStrategy(AssignmentStrategy.REPLICA_GROUP_SEGMENT_ASSIGNMENT_STRATEGY) + .setReplicaGroupStrategyConfig(new ReplicaGroupStrategyConfig(PARTITION_COLUMN, 1)).build(); + _segmentAssignment = SegmentAssignmentFactory.getSegmentAssignment(createHelixManager(), tableConfig, null); + + _instancePartitionsMap = new TreeMap<>(); + // CONSUMING instances: + // { + // 0_0=[instance_0, instance_1, instance_2], + // 0_1=[instance_3, instance_4, instance_5], + // 0_2=[instance_6, instance_7, instance_8] + // } + // p0 p1 p2 + // p3 + InstancePartitions consumingInstancePartitions = new InstancePartitions(CONSUMING_INSTANCE_PARTITIONS_NAME); + int numConsumingInstancesPerReplicaGroup = NUM_CONSUMING_INSTANCES / NUM_REPLICAS; + int consumingInstanceIdToAdd = 0; + for (int replicaGroupId = 0; replicaGroupId < NUM_REPLICAS; replicaGroupId++) { + List consumingInstancesForReplicaGroup = new ArrayList<>(numConsumingInstancesPerReplicaGroup); + for (int i = 0; i < numConsumingInstancesPerReplicaGroup; i++) { + consumingInstancesForReplicaGroup.add(CONSUMING_INSTANCES.get(consumingInstanceIdToAdd++)); + } + consumingInstancePartitions.setInstances(0, replicaGroupId, consumingInstancesForReplicaGroup); + } + _instancePartitionsMap.put(InstancePartitionsType.CONSUMING, consumingInstancePartitions); + + // new CONSUMING instances: + // { + // 0_0=[new_instance_0, new_instance_1, new_instance_2], + // 0_1=[new_instance_3, new_instance_4, new_instance_5], + // 0_2=[new_instance_6, new_instance_7, new_instance_8] + // } + consumingInstanceIdToAdd = 0; + _newConsumingInstancePartitions = new InstancePartitions(CONSUMING_INSTANCE_PARTITIONS_NAME); + for (int replicaGroupId = 0; replicaGroupId < NUM_REPLICAS; replicaGroupId++) { + List consumingInstancesForReplicaGroup = new ArrayList<>(numConsumingInstancesPerReplicaGroup); + for (int i = 0; i < numConsumingInstancesPerReplicaGroup; i++) { + consumingInstancesForReplicaGroup.add(NEW_CONSUMING_INSTANCES.get(consumingInstanceIdToAdd++)); + } + _newConsumingInstancePartitions.setInstances(0, replicaGroupId, consumingInstancesForReplicaGroup); + } + } + + @Test + public void testFactory() { + assertTrue(_segmentAssignment instanceof StrictRealtimeSegmentAssignment); + } + + @Test + public void testAssignSegment() { + assertTrue(_segmentAssignment instanceof StrictRealtimeSegmentAssignment); + Map onlyConsumingInstancePartitionMap = + ImmutableMap.of(InstancePartitionsType.CONSUMING, _instancePartitionsMap.get(InstancePartitionsType.CONSUMING)); + int numInstancesPerReplicaGroup = NUM_CONSUMING_INSTANCES / NUM_REPLICAS; + Map> currentAssignment = new TreeMap<>(); + // Add segments for partition 0/1/2, but add no segment for partition 3. + List instancesAssigned; + boolean consistent; + for (int segmentId = 0; segmentId < 3; segmentId++) { + String segmentName = _segments.get(segmentId); + instancesAssigned = + _segmentAssignment.assignSegment(segmentName, currentAssignment, onlyConsumingInstancePartitionMap); + assertEquals(instancesAssigned.size(), NUM_REPLICAS); + // Segment 0 (partition 0) should be assigned to instance 0, 3, 6 + // Segment 1 (partition 1) should be assigned to instance 1, 4, 7 + // Segment 2 (partition 2) should be assigned to instance 2, 5, 8 + // Following segments are assigned to those instances if continue to use the same instancePartition + // Segment 3 (partition 3) should be assigned to instance 0, 3, 6 + // Segment 4 (partition 0) should be assigned to instance 0, 3, 6 + // Segment 5 (partition 1) should be assigned to instance 1, 4, 7 + // ... + for (int replicaGroupId = 0; replicaGroupId < NUM_REPLICAS; replicaGroupId++) { + int partitionId = segmentId % NUM_PARTITIONS; + int expectedAssignedInstanceId = + partitionId % numInstancesPerReplicaGroup + replicaGroupId * numInstancesPerReplicaGroup; + assertEquals(instancesAssigned.get(replicaGroupId), CONSUMING_INSTANCES.get(expectedAssignedInstanceId)); + } + addToAssignment(currentAssignment, segmentId, instancesAssigned); + } + // Use new instancePartition to assign the new segments below. + ImmutableMap newConsumingInstancePartitionMap = + ImmutableMap.of(InstancePartitionsType.CONSUMING, _newConsumingInstancePartitions); + + // No existing segments for partition 3, so use the assignment decided by new instancePartition. + // So segment 3 (partition 3) should be assigned to instance new_0, new_3, new_6 + int segmentId = 3; + String segmentName = _segments.get(segmentId); + instancesAssigned = + _segmentAssignment.assignSegment(segmentName, currentAssignment, newConsumingInstancePartitionMap); + assertEquals(instancesAssigned, + Arrays.asList("new_consumingInstance_0", "new_consumingInstance_3", "new_consumingInstance_6")); + addToAssignment(currentAssignment, segmentId, instancesAssigned); + + // Use existing assignment for partition 0/1/2, instead of the one decided by new instancePartition. + for (segmentId = 4; segmentId < 7; segmentId++) { + segmentName = _segments.get(segmentId); + instancesAssigned = + _segmentAssignment.assignSegment(segmentName, currentAssignment, newConsumingInstancePartitionMap); + assertEquals(instancesAssigned.size(), NUM_REPLICAS); + + // Those segments are assigned according to the assignment from idealState, instead of using new_xxx instances + // Segment 4 (partition 0) should be assigned to instance 0, 3, 6 + // Segment 5 (partition 1) should be assigned to instance 1, 4, 7 + // Segment 6 (partition 2) should be assigned to instance 2, 5, 8 + for (int replicaGroupId = 0; replicaGroupId < NUM_REPLICAS; replicaGroupId++) { + int partitionId = segmentId % NUM_PARTITIONS; + int expectedAssignedInstanceId = + partitionId % numInstancesPerReplicaGroup + replicaGroupId * numInstancesPerReplicaGroup; + assertEquals(instancesAssigned.get(replicaGroupId), CONSUMING_INSTANCES.get(expectedAssignedInstanceId)); + } + addToAssignment(currentAssignment, segmentId, instancesAssigned); + } + } + + @Test + public void testAssignSegmentWithOfflineSegment() { + assertTrue(_segmentAssignment instanceof StrictRealtimeSegmentAssignment); + Map onlyConsumingInstancePartitionMap = + ImmutableMap.of(InstancePartitionsType.CONSUMING, _instancePartitionsMap.get(InstancePartitionsType.CONSUMING)); + int numInstancesPerReplicaGroup = NUM_CONSUMING_INSTANCES / NUM_REPLICAS; + Map> currentAssignment = new TreeMap<>(); + // Add segments for partition 0/1/2, but add no segment for partition 3. + List instancesAssigned; + boolean consistent; + for (int segmentId = 0; segmentId < 3; segmentId++) { + String segmentName = _segments.get(segmentId); + instancesAssigned = + _segmentAssignment.assignSegment(segmentName, currentAssignment, onlyConsumingInstancePartitionMap); + assertEquals(instancesAssigned.size(), NUM_REPLICAS); + // Segment 0 (partition 0) should be assigned to instance 0, 3, 6 + // Segment 1 (partition 1) should be assigned to instance 1, 4, 7 + // Segment 2 (partition 2) should be assigned to instance 2, 5, 8 + // Following segments are assigned to those instances if continue to use the same instancePartition + // Segment 3 (partition 3) should be assigned to instance 0, 3, 6 + // Segment 4 (partition 0) should be assigned to instance 0, 3, 6 + // Segment 5 (partition 1) should be assigned to instance 1, 4, 7 + // ... + for (int replicaGroupId = 0; replicaGroupId < NUM_REPLICAS; replicaGroupId++) { + int partitionId = segmentId % NUM_PARTITIONS; + int expectedAssignedInstanceId = + partitionId % numInstancesPerReplicaGroup + replicaGroupId * numInstancesPerReplicaGroup; + assertEquals(instancesAssigned.get(replicaGroupId), CONSUMING_INSTANCES.get(expectedAssignedInstanceId)); + } + currentAssignment.put(segmentName, + SegmentAssignmentUtils.getInstanceStateMap(instancesAssigned, SegmentStateModel.OFFLINE)); + } + // Use new instancePartition to assign the new segments below. + ImmutableMap newConsumingInstancePartitionMap = + ImmutableMap.of(InstancePartitionsType.CONSUMING, _newConsumingInstancePartitions); + + // No existing segments for partition 3, so use the assignment decided by new instancePartition. All existing + // segments for partition 0/1/2 are offline, thus skipped, so use the assignment decided by new instancePartition. + for (int segmentId = 3; segmentId < 7; segmentId++) { + String segmentName = _segments.get(segmentId); + instancesAssigned = + _segmentAssignment.assignSegment(segmentName, currentAssignment, newConsumingInstancePartitionMap); + assertEquals(instancesAssigned.size(), NUM_REPLICAS); + + // Those segments are assigned according to the assignment from idealState, instead of using new_xxx instances + // Segment 4 (partition 0) should be assigned to instance 0, 3, 6 + // Segment 5 (partition 1) should be assigned to instance 1, 4, 7 + // Segment 6 (partition 2) should be assigned to instance 2, 5, 8 + for (int replicaGroupId = 0; replicaGroupId < NUM_REPLICAS; replicaGroupId++) { + int partitionId = segmentId % NUM_PARTITIONS; + int expectedAssignedInstanceId = + partitionId % numInstancesPerReplicaGroup + replicaGroupId * numInstancesPerReplicaGroup; + assertEquals(instancesAssigned.get(replicaGroupId), NEW_CONSUMING_INSTANCES.get(expectedAssignedInstanceId)); + } + addToAssignment(currentAssignment, segmentId, instancesAssigned); + } + System.out.println(currentAssignment); + } + + @Test(expectedExceptions = IllegalStateException.class) + public void testAssignSegmentToCompletedServers() { + _segmentAssignment.assignSegment("seg01", new TreeMap<>(), new TreeMap<>()); + } + + private void addToAssignment(Map> currentAssignment, int segmentId, + List instancesAssigned) { + // Change the state of the last segment in the same partition from CONSUMING to ONLINE if exists + if (segmentId >= NUM_PARTITIONS) { + String lastSegmentInPartition = _segments.get(segmentId - NUM_PARTITIONS); + Map instanceStateMap = currentAssignment.get(lastSegmentInPartition); + currentAssignment.put(lastSegmentInPartition, + SegmentAssignmentUtils.getInstanceStateMap(new ArrayList<>(instanceStateMap.keySet()), + SegmentStateModel.ONLINE)); + } + + // Add the new segment into the assignment as CONSUMING + currentAssignment.put(_segments.get(segmentId), + SegmentAssignmentUtils.getInstanceStateMap(instancesAssigned, SegmentStateModel.CONSUMING)); + } + + private HelixManager createHelixManager() { + HelixManager helixManager = mock(HelixManager.class); + ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); + when(helixManager.getHelixPropertyStore()).thenReturn(propertyStore); + when(propertyStore.get(anyString(), isNull(), anyInt())).thenReturn(new ZNRecord("0")); + return helixManager; + } +} diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/strategy/AllServersSegmentAssignmentStrategyTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/strategy/AllServersSegmentAssignmentStrategyTest.java index 0f64d608c616..f8df6b53eb33 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/strategy/AllServersSegmentAssignmentStrategyTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/strategy/AllServersSegmentAssignmentStrategyTest.java @@ -35,6 +35,7 @@ import org.apache.pinot.controller.helix.core.assignment.segment.SegmentAssignment; import org.apache.pinot.controller.helix.core.assignment.segment.SegmentAssignmentFactory; import org.apache.pinot.controller.helix.core.assignment.segment.SegmentAssignmentTestUtils; +import org.apache.pinot.controller.helix.core.rebalance.RebalanceConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.config.table.assignment.InstancePartitionsType; @@ -86,7 +87,7 @@ public void setup() { _instancePartitionsMap.put(InstancePartitionsType.OFFLINE, instancePartitions); _instancePartitionsMap.put(InstancePartitionsType.COMPLETED, completedInstancePartitions); - _segmentAssignment = SegmentAssignmentFactory.getSegmentAssignment(_helixManager, tableConfig); + _segmentAssignment = SegmentAssignmentFactory.getSegmentAssignment(_helixManager, tableConfig, null); } @Test @@ -124,7 +125,7 @@ public void testSegmentAssignmentAndRebalance() { when(dataAccessor.getChildValues(builder.instanceConfigs(), true)).thenReturn(instanceConfigList); Map> newAssignment = - _segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, null, null, null); + _segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, null, null, new RebalanceConfig()); assertEquals(newAssignment.get(SEGMENT_NAME).size(), NUM_INSTANCES - 1); } diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/strategy/BalancedNumSegmentAssignmentStrategyTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/strategy/BalancedNumSegmentAssignmentStrategyTest.java index 8cef7ef57ec8..c62f03fef21b 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/strategy/BalancedNumSegmentAssignmentStrategyTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/strategy/BalancedNumSegmentAssignmentStrategyTest.java @@ -24,19 +24,17 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; -import org.apache.commons.configuration.BaseConfiguration; -import org.apache.commons.configuration.Configuration; import org.apache.pinot.common.assignment.InstancePartitions; import org.apache.pinot.controller.helix.core.assignment.segment.OfflineSegmentAssignment; import org.apache.pinot.controller.helix.core.assignment.segment.SegmentAssignment; import org.apache.pinot.controller.helix.core.assignment.segment.SegmentAssignmentFactory; import org.apache.pinot.controller.helix.core.assignment.segment.SegmentAssignmentTestUtils; import org.apache.pinot.controller.helix.core.assignment.segment.SegmentAssignmentUtils; +import org.apache.pinot.controller.helix.core.rebalance.RebalanceConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.config.table.assignment.InstancePartitionsType; import org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel; -import org.apache.pinot.spi.utils.RebalanceConfigConstants; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -65,7 +63,7 @@ public class BalancedNumSegmentAssignmentStrategyTest { public void setUp() { TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setNumReplicas(NUM_REPLICAS).build(); - _segmentAssignment = SegmentAssignmentFactory.getSegmentAssignment(null, tableConfig); + _segmentAssignment = SegmentAssignmentFactory.getSegmentAssignment(null, tableConfig, null); // { // 0_0=[instance_0, instance_1, instance_2, instance_3, instance_4, instance_5, instance_6, instance_7, @@ -129,8 +127,8 @@ public void testTableBalanced() { Arrays.fill(expectedNumSegmentsAssignedPerInstance, numSegmentsPerInstance); assertEquals(numSegmentsAssignedPerInstance, expectedNumSegmentsAssignedPerInstance); // Current assignment should already be balanced - assertEquals(_segmentAssignment - .rebalanceTable(currentAssignment, _instancePartitionsMap, null, null, new BaseConfiguration()), + assertEquals( + _segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, null, null, new RebalanceConfig()), currentAssignment); } @@ -145,8 +143,8 @@ public void testBootstrapTable() { } // Bootstrap table should reassign all segments based on their alphabetical order - Configuration rebalanceConfig = new BaseConfiguration(); - rebalanceConfig.setProperty(RebalanceConfigConstants.BOOTSTRAP, true); + RebalanceConfig rebalanceConfig = new RebalanceConfig(); + rebalanceConfig.setBootstrap(true); Map> newAssignment = _segmentAssignment.rebalanceTable(currentAssignment, _instancePartitionsMap, null, null, rebalanceConfig); assertEquals(newAssignment.size(), NUM_SEGMENTS); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/strategy/ReplicaGroupSegmentAssignmentStrategyTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/strategy/ReplicaGroupSegmentAssignmentStrategyTest.java index f2bf9fca91a2..ea4c88774b45 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/strategy/ReplicaGroupSegmentAssignmentStrategyTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/strategy/ReplicaGroupSegmentAssignmentStrategyTest.java @@ -25,8 +25,6 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; -import org.apache.commons.configuration.BaseConfiguration; -import org.apache.commons.configuration.Configuration; import org.apache.helix.HelixManager; import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; @@ -39,6 +37,7 @@ import org.apache.pinot.controller.helix.core.assignment.segment.SegmentAssignmentFactory; import org.apache.pinot.controller.helix.core.assignment.segment.SegmentAssignmentTestUtils; import org.apache.pinot.controller.helix.core.assignment.segment.SegmentAssignmentUtils; +import org.apache.pinot.controller.helix.core.rebalance.RebalanceConfig; import org.apache.pinot.segment.spi.partition.metadata.ColumnPartitionMetadata; import org.apache.pinot.spi.config.table.ReplicaGroupStrategyConfig; import org.apache.pinot.spi.config.table.TableConfig; @@ -46,7 +45,6 @@ import org.apache.pinot.spi.config.table.assignment.InstancePartitionsType; import org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel; import org.apache.pinot.spi.utils.CommonConstants.Segment.AssignmentStrategy; -import org.apache.pinot.spi.utils.RebalanceConfigConstants; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.testng.annotations.BeforeClass; @@ -95,7 +93,7 @@ public void setUp() { .setNumReplicas(NUM_REPLICAS) .setSegmentAssignmentStrategy(AssignmentStrategy.REPLICA_GROUP_SEGMENT_ASSIGNMENT_STRATEGY).build(); _segmentAssignmentWithoutPartition = - SegmentAssignmentFactory.getSegmentAssignment(null, tableConfigWithoutPartitions); + SegmentAssignmentFactory.getSegmentAssignment(null, tableConfigWithoutPartitions, null); // { // 0_0=[instance_0, instance_1, instance_2, instance_3, instance_4, instance_5], @@ -146,7 +144,7 @@ public void setUp() { .setSegmentAssignmentStrategy(AssignmentStrategy.REPLICA_GROUP_SEGMENT_ASSIGNMENT_STRATEGY) .setReplicaGroupStrategyConfig(replicaGroupStrategyConfig).build(); _segmentAssignmentWithPartition = - SegmentAssignmentFactory.getSegmentAssignment(helixManagerWithPartitions, tableConfigWithPartitions); + SegmentAssignmentFactory.getSegmentAssignment(helixManagerWithPartitions, tableConfigWithPartitions, null); // { // 0_0=[instance_0, instance_1], 1_0=[instance_2, instance_3], 2_0=[instance_4, instance_5], @@ -260,10 +258,9 @@ public void testTableBalancedWithoutPartition() { Arrays.fill(expectedNumSegmentsAssignedPerInstance, numSegmentsPerInstance); assertEquals(numSegmentsAssignedPerInstance, expectedNumSegmentsAssignedPerInstance); // Current assignment should already be balanced - assertEquals(_segmentAssignmentWithoutPartition - .rebalanceTable(currentAssignment, _instancePartitionsMapWithoutPartition, null, null, - new BaseConfiguration()), - currentAssignment); + assertEquals( + _segmentAssignmentWithoutPartition.rebalanceTable(currentAssignment, _instancePartitionsMapWithoutPartition, + null, null, new RebalanceConfig()), currentAssignment); } @Test @@ -288,10 +285,9 @@ public void testTableBalancedWithPartition() { Arrays.fill(expectedNumSegmentsAssignedPerInstance, numSegmentsPerInstance); assertEquals(numSegmentsAssignedPerInstance, expectedNumSegmentsAssignedPerInstance); // Current assignment should already be balanced - assertEquals(_segmentAssignmentWithPartition - .rebalanceTable(currentAssignment, _instancePartitionsMapWithPartition, null, null, - new BaseConfiguration()), - currentAssignment); + assertEquals( + _segmentAssignmentWithPartition.rebalanceTable(currentAssignment, _instancePartitionsMapWithPartition, null, + null, new RebalanceConfig()), currentAssignment); } @Test @@ -305,10 +301,11 @@ public void testBootstrapTableWithoutPartition() { } // Bootstrap table should reassign all segments based on their alphabetical order - Configuration rebalanceConfig = new BaseConfiguration(); - rebalanceConfig.setProperty(RebalanceConfigConstants.BOOTSTRAP, true); - Map> newAssignment = _segmentAssignmentWithoutPartition - .rebalanceTable(currentAssignment, _instancePartitionsMapWithoutPartition, null, null, rebalanceConfig); + RebalanceConfig rebalanceConfig = new RebalanceConfig(); + rebalanceConfig.setBootstrap(true); + Map> newAssignment = + _segmentAssignmentWithoutPartition.rebalanceTable(currentAssignment, _instancePartitionsMapWithoutPartition, + null, null, rebalanceConfig); assertEquals(newAssignment.size(), NUM_SEGMENTS); List sortedSegments = new ArrayList<>(SEGMENTS); sortedSegments.sort(null); @@ -328,10 +325,11 @@ public void testBootstrapTableWithPartition() { } // Bootstrap table should reassign all segments based on their alphabetical order within the partition - Configuration rebalanceConfig = new BaseConfiguration(); - rebalanceConfig.setProperty(RebalanceConfigConstants.BOOTSTRAP, true); - Map> newAssignment = _segmentAssignmentWithPartition - .rebalanceTable(currentAssignment, _instancePartitionsMapWithPartition, null, null, rebalanceConfig); + RebalanceConfig rebalanceConfig = new RebalanceConfig(); + rebalanceConfig.setBootstrap(true); + Map> newAssignment = + _segmentAssignmentWithPartition.rebalanceTable(currentAssignment, _instancePartitionsMapWithPartition, null, + null, rebalanceConfig); assertEquals(newAssignment.size(), NUM_SEGMENTS); int numSegmentsPerPartition = NUM_SEGMENTS / NUM_PARTITIONS; String[][] partitionIdToSegmentsMap = new String[NUM_PARTITIONS][numSegmentsPerPartition]; @@ -363,9 +361,9 @@ public void testRebalanceTableWithPartitionColumnAndInstancePartitionsMapWithOne SEGMENTS.forEach(segName -> unbalancedAssignment.put(segName, ImmutableMap .of(instance0, SegmentStateModel.ONLINE, instance1, SegmentStateModel.ONLINE, instance2, SegmentStateModel.ONLINE))); - Map> balancedAssignment = _segmentAssignmentWithPartition - .rebalanceTable(unbalancedAssignment, _instancePartitionsMapWithoutPartition, null, null, - new BaseConfiguration()); + Map> balancedAssignment = + _segmentAssignmentWithPartition.rebalanceTable(unbalancedAssignment, _instancePartitionsMapWithoutPartition, + null, null, new RebalanceConfig()); int[] actualNumSegmentsAssignedPerInstance = SegmentAssignmentUtils.getNumSegmentsAssignedPerInstance(balancedAssignment, INSTANCES); int[] expectedNumSegmentsAssignedPerInstance = new int[NUM_INSTANCES]; @@ -404,7 +402,7 @@ public void testOneReplicaWithPartition() { .setSegmentAssignmentStrategy(AssignmentStrategy.REPLICA_GROUP_SEGMENT_ASSIGNMENT_STRATEGY).build(); tableConfigWithPartitions.getValidationConfig().setReplicaGroupStrategyConfig(replicaGroupStrategyConfig); SegmentAssignment segmentAssignment = - SegmentAssignmentFactory.getSegmentAssignment(helixManager, tableConfigWithPartitions); + SegmentAssignmentFactory.getSegmentAssignment(helixManager, tableConfigWithPartitions, null); // { // 0_0=[instance_0, instance_1, instance_2, instance_3, instance_4, instance_5], @@ -451,13 +449,13 @@ public void testOneReplicaWithPartition() { // Current assignment should already be balanced assertEquals( - segmentAssignment.rebalanceTable(currentAssignment, instancePartitionsMap, null, null, new BaseConfiguration()), + segmentAssignment.rebalanceTable(currentAssignment, instancePartitionsMap, null, null, new RebalanceConfig()), currentAssignment); // Test bootstrap // Bootstrap table should reassign all segments based on their alphabetical order within the partition - Configuration rebalanceConfig = new BaseConfiguration(); - rebalanceConfig.setProperty(RebalanceConfigConstants.BOOTSTRAP, true); + RebalanceConfig rebalanceConfig = new RebalanceConfig(); + rebalanceConfig.setBootstrap(true); Map> newAssignment = segmentAssignment.rebalanceTable(currentAssignment, instancePartitionsMap, null, null, rebalanceConfig); assertEquals(newAssignment.size(), NUM_SEGMENTS); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/strategy/SegmentAssignmentStrategyFactoryTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/strategy/SegmentAssignmentStrategyFactoryTest.java index 619d61ef82e3..e452820c9888 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/strategy/SegmentAssignmentStrategyFactoryTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/assignment/segment/strategy/SegmentAssignmentStrategyFactoryTest.java @@ -100,8 +100,8 @@ public void testBalancedNumSegmentAssignmentStrategyforOfflineTables() { @Test public void testBalancedNumSegmentAssignmentStrategyForRealtimeTables() { Map streamConfigs = FakeStreamConfigUtils.getDefaultLowLevelStreamConfigs().getStreamConfigsMap(); - TableConfig tableConfig = new TableConfigBuilder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setLLC(true) - .setStreamConfigs(streamConfigs).build(); + TableConfig tableConfig = + new TableConfigBuilder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setStreamConfigs(streamConfigs).build(); InstancePartitions instancePartitions = new InstancePartitions(INSTANCE_PARTITIONS_NAME); instancePartitions.setInstances(0, 0, INSTANCES); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/cleanup/SchemaCleanupTaskStatelessTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/cleanup/SchemaCleanupTaskStatelessTest.java new file mode 100644 index 000000000000..19c475663492 --- /dev/null +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/cleanup/SchemaCleanupTaskStatelessTest.java @@ -0,0 +1,288 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.controller.helix.core.cleanup; + +import org.apache.helix.HelixManager; +import org.apache.helix.HelixManagerFactory; +import org.apache.helix.InstanceType; +import org.apache.pinot.common.metrics.ControllerGauge; +import org.apache.pinot.common.metrics.MetricValueUtils; +import org.apache.pinot.common.utils.config.TagNameUtils; +import org.apache.pinot.controller.BaseControllerStarter; +import org.apache.pinot.controller.helix.ControllerTest; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.metrics.PinotMetricUtils; +import org.apache.pinot.spi.utils.CommonConstants; +import org.apache.pinot.spi.utils.NetUtils; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; + + +/** + * This test can be deleted once {@link BaseControllerStarter#fixSchemaNameInTableConfig()} is deleted. Likely in 2.0.0. + */ +@Test(groups = "stateless") +public class SchemaCleanupTaskStatelessTest extends ControllerTest { + @BeforeClass + public void setup() + throws Exception { + startZk(); + startController(); + startFakeBroker(); + startFakeServer(); + } + + private void startFakeBroker() + throws Exception { + String brokerInstance = CommonConstants.Helix.PREFIX_OF_BROKER_INSTANCE + NetUtils.getHostAddress() + "_" + + CommonConstants.Helix.DEFAULT_BROKER_QUERY_PORT; + + // Create server instance with the fake server state model + HelixManager brokerHelixManager = + HelixManagerFactory.getZKHelixManager(getHelixClusterName(), brokerInstance, InstanceType.PARTICIPANT, + getZkUrl()); + brokerHelixManager.connect(); + + // Add Helix tag to the server + brokerHelixManager.getClusterManagmentTool().addInstanceTag(getHelixClusterName(), brokerInstance, + TagNameUtils.getBrokerTagForTenant(TagNameUtils.DEFAULT_TENANT_NAME)); + } + + private void startFakeServer() + throws Exception { + String serverInstance = CommonConstants.Helix.PREFIX_OF_SERVER_INSTANCE + NetUtils.getHostAddress() + "_" + + CommonConstants.Helix.DEFAULT_SERVER_NETTY_PORT; + + // Create server instance with the fake server state model + HelixManager serverHelixManager = HelixManagerFactory + .getZKHelixManager(getHelixClusterName(), serverInstance, InstanceType.PARTICIPANT, getZkUrl()); + serverHelixManager.connect(); + + // Add Helix tag to the server + serverHelixManager.getClusterManagmentTool().addInstanceTag(getHelixClusterName(), serverInstance, + TableNameBuilder.OFFLINE.tableNameWithType(TagNameUtils.DEFAULT_TENANT_NAME)); + } + + @AfterClass + public void teardown() { + stopController(); + stopZk(); + } + + @Test + public void testSchemaCleanupTask() + throws Exception { + PinotMetricUtils.cleanUp(); + PinotMetricUtils.getPinotMetricsRegistry(); + // 1. Add a schema + addSchema(createDummySchema("t1")); + addSchema(createDummySchema("t2")); + addSchema(createDummySchema("t3")); + + // 2. Add a table with the schema name reference + addTableConfig(createDummyTableConfig("t1", "t1")); + addTableConfig(createDummyTableConfig("t2", "t2")); + addTableConfig(createDummyTableConfig("t3", "t3")); + + _helixResourceManager.setExistingTableConfig(createDummyTableConfig("t1", "t2")); + _helixResourceManager.setExistingTableConfig(createDummyTableConfig("t2", "t3")); + _helixResourceManager.setExistingTableConfig(createDummyTableConfig("t3", "t1")); + + // 3. Fix table schema + _controllerStarter.fixSchemaNameInTableConfig(); + + // 4. validate + assertEquals(getHelixResourceManager().getAllTables().size(), 3); + assertEquals(getHelixResourceManager().getSchemaNames().size(), 3); + + assertNull(getHelixResourceManager().getTableConfig("t1_OFFLINE").getValidationConfig().getSchemaName()); + assertNull(getHelixResourceManager().getTableConfig("t2_OFFLINE").getValidationConfig().getSchemaName()); + assertNull(getHelixResourceManager().getTableConfig("t3_OFFLINE").getValidationConfig().getSchemaName()); + + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.MISCONFIGURED_SCHEMA_TABLE_COUNT), 3); + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.TABLE_WITHOUT_SCHEMA_COUNT), 0); + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.FIXED_SCHEMA_TABLE_COUNT), 3); + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.FAILED_TO_COPY_SCHEMA_COUNT), 0); + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.FAILED_TO_UPDATE_TABLE_CONFIG_COUNT), 0); + + // 5. Clean up + for (String table : getHelixResourceManager().getAllOfflineTables()) { + getHelixResourceManager().deleteOfflineTable(table); + } + for (String schema : getHelixResourceManager().getSchemaNames()) { + getHelixResourceManager().deleteSchema(schema); + } + } + + @Test + public void testSchemaCleanupTaskNormalCase() + throws Exception { + PinotMetricUtils.cleanUp(); + PinotMetricUtils.getPinotMetricsRegistry(); + // 1. Add a schema + addSchema(createDummySchema("t1")); + addSchema(createDummySchema("t2")); + addSchema(createDummySchema("t3")); + + assertEquals(getHelixResourceManager().getSchemaNames().size(), 3); + + // 2. Add a table with the schema name reference + addTableConfig(createDummyTableConfig("t1", "t1")); + addTableConfig(createDummyTableConfig("t2", "t2")); + addTableConfig(createDummyTableConfig("t3", "t3")); + + assertEquals(getHelixResourceManager().getAllTables().size(), 3); + + // 3. Create new schemas and update table to new schema + addSchema(createDummySchema("t11")); + addSchema(createDummySchema("t21")); + addSchema(createDummySchema("t31")); + _helixResourceManager.setExistingTableConfig(createDummyTableConfig("t1", "t11")); + _helixResourceManager.setExistingTableConfig(createDummyTableConfig("t2", "t21")); + _helixResourceManager.setExistingTableConfig(createDummyTableConfig("t3", "t31")); + + assertEquals(getHelixResourceManager().getAllTables().size(), 3); + assertEquals(getHelixResourceManager().getSchemaNames().size(), 6); + assertEquals(getHelixResourceManager().getTableConfig("t1_OFFLINE").getValidationConfig().getSchemaName(), "t11"); + assertEquals(getHelixResourceManager().getTableConfig("t2_OFFLINE").getValidationConfig().getSchemaName(), "t21"); + assertEquals(getHelixResourceManager().getTableConfig("t3_OFFLINE").getValidationConfig().getSchemaName(), "t31"); + + // 4. Delete schema t1, t2, t3, so we can check if those schemas are fixed later. + deleteSchema("t1"); + deleteSchema("t2"); + deleteSchema("t3"); + + assertEquals(getHelixResourceManager().getSchemaNames().size(), 3); + + // 5. Fix table schema + _controllerStarter.fixSchemaNameInTableConfig(); + + // 6. All tables will directly set schema. + assertEquals(getHelixResourceManager().getAllTables().size(), 3); + assertEquals(getHelixResourceManager().getSchemaNames().size(), 6); + assertTrue(getHelixResourceManager().getSchemaNames().contains("t1")); + assertTrue(getHelixResourceManager().getSchemaNames().contains("t2")); + assertTrue(getHelixResourceManager().getSchemaNames().contains("t3")); + + assertNull(getHelixResourceManager().getTableConfig("t1_OFFLINE").getValidationConfig().getSchemaName()); + assertNull(getHelixResourceManager().getTableConfig("t2_OFFLINE").getValidationConfig().getSchemaName()); + assertNull(getHelixResourceManager().getTableConfig("t3_OFFLINE").getValidationConfig().getSchemaName()); + + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.MISCONFIGURED_SCHEMA_TABLE_COUNT), 3); + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.TABLE_WITHOUT_SCHEMA_COUNT), 0); + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.FIXED_SCHEMA_TABLE_COUNT), 3); + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.FAILED_TO_COPY_SCHEMA_COUNT), 0); + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.FAILED_TO_UPDATE_TABLE_CONFIG_COUNT), 0); + + // 7. Clean up + for (String table : getHelixResourceManager().getAllOfflineTables()) { + getHelixResourceManager().deleteOfflineTable(table); + } + for (String schema : getHelixResourceManager().getSchemaNames()) { + getHelixResourceManager().deleteSchema(schema); + } + } + + @Test + public void testMissingSchema() + throws Exception { + PinotMetricUtils.cleanUp(); + PinotMetricUtils.getPinotMetricsRegistry(); + // 1. Add a schema + addSchema(createDummySchema("t1")); + addSchema(createDummySchema("t2")); + addSchema(createDummySchema("t3")); + + assertEquals(getHelixResourceManager().getSchemaNames().size(), 3); + + // 2. Add a table with the schema name reference + addTableConfig(createDummyTableConfig("t1")); + addTableConfig(createDummyTableConfig("t2")); + addTableConfig(createDummyTableConfig("t3")); + + assertEquals(getHelixResourceManager().getAllTables().size(), 3); + + // 4. Delete schema t1, t2, t3, so we can check if those schemas are fixed later. + deleteSchema("t1"); + deleteSchema("t2"); + deleteSchema("t3"); + + assertEquals(getHelixResourceManager().getSchemaNames().size(), 0); + + // 5. Fix table schema + _controllerStarter.fixSchemaNameInTableConfig(); + + // 6. We cannot fix schema + assertEquals(getHelixResourceManager().getAllTables().size(), 3); + assertEquals(getHelixResourceManager().getSchemaNames().size(), 0); + + assertNull(getHelixResourceManager().getTableConfig("t1_OFFLINE").getValidationConfig().getSchemaName()); + assertNull(getHelixResourceManager().getTableConfig("t2_OFFLINE").getValidationConfig().getSchemaName()); + assertNull(getHelixResourceManager().getTableConfig("t3_OFFLINE").getValidationConfig().getSchemaName()); + + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.MISCONFIGURED_SCHEMA_TABLE_COUNT), 0); + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.TABLE_WITHOUT_SCHEMA_COUNT), 3); + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.FIXED_SCHEMA_TABLE_COUNT), 0); + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.FAILED_TO_COPY_SCHEMA_COUNT), 0); + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.FAILED_TO_UPDATE_TABLE_CONFIG_COUNT), 0); + + // 7. Clean up + for (String table : getHelixResourceManager().getAllOfflineTables()) { + getHelixResourceManager().deleteOfflineTable(table); + } + for (String schema : getHelixResourceManager().getSchemaNames()) { + getHelixResourceManager().deleteSchema(schema); + } + } + + private TableConfig createDummyTableConfig(String table) { + return new TableConfigBuilder(TableType.OFFLINE) + .setTableName(table) + .build(); + } + + private TableConfig createDummyTableConfig(String table, String schema) { + TableConfig tableConfig = createDummyTableConfig(table); + tableConfig.getValidationConfig().setSchemaName(schema); + return tableConfig; + } +} diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/cleanup/StaleInstancesCleanupTaskStatelessTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/cleanup/StaleInstancesCleanupTaskStatelessTest.java new file mode 100644 index 000000000000..2afd1147082f --- /dev/null +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/cleanup/StaleInstancesCleanupTaskStatelessTest.java @@ -0,0 +1,139 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.controller.helix.core.cleanup; + +import java.util.Map; +import java.util.Properties; +import org.apache.pinot.common.metrics.ControllerGauge; +import org.apache.pinot.common.metrics.MetricValueUtils; +import org.apache.pinot.controller.ControllerConf; +import org.apache.pinot.controller.helix.ControllerTest; +import org.apache.pinot.spi.metrics.PinotMetricUtils; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + + +@Test(groups = "stateless") +public class StaleInstancesCleanupTaskStatelessTest extends ControllerTest { + + @Override + protected void overrideControllerConf(Map properties) { + properties.put(ControllerConf.ControllerPeriodicTasksConf.STALE_INSTANCES_CLEANUP_TASK_INSTANCES_RETENTION_PERIOD, + "1s"); + } + + @BeforeClass + public void setUp() + throws Exception { + startZk(); + startController(); + } + + @AfterClass + public void tearDown() { + stopController(); + stopZk(); + } + + @Test + public void testStaleInstancesCleanupTaskForBrokers() + throws Exception { + PinotMetricUtils.cleanUp(); + StaleInstancesCleanupTask staleInstancesCleanupTask = _controllerStarter.getStaleInstancesCleanupTask(); + staleInstancesCleanupTask.runTask(new Properties()); + Assert.assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.DROPPED_BROKER_INSTANCES), 0); + addFakeBrokerInstancesToAutoJoinHelixCluster(3, true); + Assert.assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.DROPPED_BROKER_INSTANCES), 0); + stopFakeInstance("Broker_localhost_0"); + Thread.sleep(1000); + staleInstancesCleanupTask.runTask(new Properties()); + Assert.assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.DROPPED_BROKER_INSTANCES), 1); + stopFakeInstance("Broker_localhost_1"); + Thread.sleep(1000); + staleInstancesCleanupTask.runTask(new Properties()); + Assert.assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.DROPPED_BROKER_INSTANCES), 2); + stopFakeInstance("Broker_localhost_2"); + Thread.sleep(1000); + staleInstancesCleanupTask.runTask(new Properties()); + Assert.assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.DROPPED_BROKER_INSTANCES), 3); + } + + @Test + public void testStaleInstancesCleanupTaskForServers() + throws Exception { + PinotMetricUtils.cleanUp(); + StaleInstancesCleanupTask staleInstancesCleanupTask = _controllerStarter.getStaleInstancesCleanupTask(); + staleInstancesCleanupTask.runTask(new Properties()); + Assert.assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.DROPPED_SERVER_INSTANCES), 0); + addFakeServerInstancesToAutoJoinHelixCluster(3, true); + Assert.assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.DROPPED_SERVER_INSTANCES), 0); + stopFakeInstance("Server_localhost_0"); + Thread.sleep(1000); + staleInstancesCleanupTask.runTask(new Properties()); + Assert.assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.DROPPED_SERVER_INSTANCES), 1); + stopFakeInstance("Server_localhost_1"); + Thread.sleep(1000); + staleInstancesCleanupTask.runTask(new Properties()); + Assert.assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.DROPPED_SERVER_INSTANCES), 2); + stopFakeInstance("Server_localhost_2"); + Thread.sleep(1000); + staleInstancesCleanupTask.runTask(new Properties()); + Assert.assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.DROPPED_SERVER_INSTANCES), 3); + } + + @Test + public void testStaleInstancesCleanupTaskForMinions() + throws Exception { + PinotMetricUtils.cleanUp(); + StaleInstancesCleanupTask staleInstancesCleanupTask = _controllerStarter.getStaleInstancesCleanupTask(); + staleInstancesCleanupTask.runTask(new Properties()); + Assert.assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.DROPPED_MINION_INSTANCES), 0); + addFakeMinionInstancesToAutoJoinHelixCluster(3); + Assert.assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.DROPPED_MINION_INSTANCES), 0); + stopFakeInstance("Minion_localhost_0"); + Thread.sleep(1000); + staleInstancesCleanupTask.runTask(new Properties()); + Assert.assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.DROPPED_MINION_INSTANCES), 1); + stopFakeInstance("Minion_localhost_1"); + Thread.sleep(1000); + staleInstancesCleanupTask.runTask(new Properties()); + Assert.assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.DROPPED_MINION_INSTANCES), 2); + stopFakeInstance("Minion_localhost_2"); + Thread.sleep(1000); + staleInstancesCleanupTask.runTask(new Properties()); + Assert.assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerStarter.getControllerMetrics(), + ControllerGauge.DROPPED_MINION_INSTANCES), 3); + } +} diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/MinionInstancesCleanupTaskStatelessTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/MinionInstancesCleanupTaskStatelessTest.java deleted file mode 100644 index 950f7c5492c9..000000000000 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/MinionInstancesCleanupTaskStatelessTest.java +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.controller.helix.core.minion; - -import java.util.Map; -import java.util.Properties; -import org.apache.pinot.common.metrics.ControllerGauge; -import org.apache.pinot.controller.ControllerConf; -import org.apache.pinot.controller.helix.ControllerTest; -import org.testng.Assert; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - - -@Test(groups = "stateless") -public class MinionInstancesCleanupTaskStatelessTest extends ControllerTest { - @BeforeClass - public void setup() - throws Exception { - startZk(); - startController(); - } - - @Test - public void testMinionInstancesCleanupTask() - throws Exception { - MinionInstancesCleanupTask minionInstancesCleanupTask = _controllerStarter.getMinionInstancesCleanupTask(); - minionInstancesCleanupTask.runTask(new Properties()); - Assert.assertEquals( - _controllerStarter.getControllerMetrics().getValueOfGlobalGauge(ControllerGauge.DROPPED_MINION_INSTANCES), 0); - addFakeMinionInstancesToAutoJoinHelixCluster(3); - Assert.assertEquals( - _controllerStarter.getControllerMetrics().getValueOfGlobalGauge(ControllerGauge.DROPPED_MINION_INSTANCES), 0); - stopFakeInstance("Minion_localhost_0"); - Thread.sleep(1000); - minionInstancesCleanupTask.runTask(new Properties()); - Assert.assertEquals( - _controllerStarter.getControllerMetrics().getValueOfGlobalGauge(ControllerGauge.DROPPED_MINION_INSTANCES), 1); - stopFakeInstance("Minion_localhost_1"); - Thread.sleep(1000); - minionInstancesCleanupTask.runTask(new Properties()); - Assert.assertEquals( - _controllerStarter.getControllerMetrics().getValueOfGlobalGauge(ControllerGauge.DROPPED_MINION_INSTANCES), 2); - stopFakeInstance("Minion_localhost_2"); - Thread.sleep(1000); - minionInstancesCleanupTask.runTask(new Properties()); - Assert.assertEquals( - _controllerStarter.getControllerMetrics().getValueOfGlobalGauge(ControllerGauge.DROPPED_MINION_INSTANCES), 3); - } - - @Override - public Map getDefaultControllerConfiguration() { - Map properties = super.getDefaultControllerConfiguration(); - // Override the cleanup before deletion period so that test can avoid stuck failure - properties.put(ControllerConf.ControllerPeriodicTasksConf. - MINION_INSTANCES_CLEANUP_TASK_MIN_OFFLINE_TIME_BEFORE_DELETION_PERIOD, "1s"); - return properties; - } - - @AfterClass - public void teardown() { - stopController(); - stopZk(); - } -} diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/PinotHelixTaskResourceManagerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/PinotHelixTaskResourceManagerTest.java index bf354d5622c2..9cbe2f5cec6e 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/PinotHelixTaskResourceManagerTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/PinotHelixTaskResourceManagerTest.java @@ -18,18 +18,27 @@ */ package org.apache.pinot.controller.helix.core.minion; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.collect.ImmutableMap; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; +import org.apache.helix.task.JobConfig; import org.apache.helix.task.JobContext; +import org.apache.helix.task.TaskConfig; import org.apache.helix.task.TaskDriver; import org.apache.helix.task.TaskPartitionState; import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; import org.apache.pinot.controller.util.CompletionServiceHelper; import org.apache.pinot.spi.utils.JsonUtils; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; import org.testng.annotations.Test; import static org.mockito.ArgumentMatchers.any; @@ -37,17 +46,20 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; public class PinotHelixTaskResourceManagerTest { + @Test public void testGetSubtaskProgressNoWorker() throws Exception { TaskDriver taskDriver = mock(TaskDriver.class); - when(taskDriver.getJobContext(anyString())).thenReturn(mock(JobContext.class)); + JobConfig jobConfig = mock(JobConfig.class); + when(taskDriver.getJobConfig(anyString())).thenReturn(jobConfig); CompletionServiceHelper httpHelper = mock(CompletionServiceHelper.class); CompletionServiceHelper.CompletionServiceResponse httpResp = new CompletionServiceHelper.CompletionServiceResponse(); @@ -61,6 +73,11 @@ public void testGetSubtaskProgressNoWorker() for (int i = 0; i < 3; i++) { subtaskNames[i] = taskName + "_" + i; } + Map taskConfigMap = new HashMap<>(); + for (String subtaskName : subtaskNames) { + taskConfigMap.put(subtaskName, mock(TaskConfig.class)); + } + when(jobConfig.getTaskConfigMap()).thenReturn(taskConfigMap); Map progress = mgr.getSubtaskProgress(taskName, StringUtils.join(subtaskNames, ','), httpHelper, workerEndpoints, Collections.emptyMap(), 1000); @@ -73,6 +90,8 @@ public void testGetSubtaskProgressNoWorker() public void testGetSubtaskProgressNoResponse() throws Exception { TaskDriver taskDriver = mock(TaskDriver.class); + JobConfig jobConfig = mock(JobConfig.class); + when(taskDriver.getJobConfig(anyString())).thenReturn(jobConfig); JobContext jobContext = mock(JobContext.class); when(taskDriver.getJobContext(anyString())).thenReturn(jobContext); PinotHelixTaskResourceManager mgr = @@ -90,17 +109,23 @@ public void testGetSubtaskProgressNoResponse() } String taskName = "Task_SegmentGenerationAndPushTask_someone"; String[] subtaskNames = new String[3]; - Set subtaskIds = new HashSet<>(); + Map taskIdPartitionMap = new HashMap<>(); for (int i = 0; i < 3; i++) { - subtaskIds.add(i); - subtaskNames[i] = taskName + "_" + i; + String subtaskName = taskName + "_" + i; + subtaskNames[i] = subtaskName; + taskIdPartitionMap.put(subtaskName, i); + } + Map taskConfigMap = new HashMap<>(); + for (String subtaskName : subtaskNames) { + taskConfigMap.put(subtaskName, mock(TaskConfig.class)); } + when(jobConfig.getTaskConfigMap()).thenReturn(taskConfigMap); TaskPartitionState[] helixStates = new TaskPartitionState[]{TaskPartitionState.INIT, TaskPartitionState.RUNNING, TaskPartitionState.TASK_ERROR}; - when(jobContext.getTaskIdForPartition(anyInt())).thenReturn(subtaskNames[0], subtaskNames[1], subtaskNames[2]); - when(jobContext.getAssignedParticipant(anyInt())).thenReturn(workers[0], workers[1], workers[2]); - when(jobContext.getPartitionState(anyInt())).thenReturn(helixStates[0], helixStates[1], helixStates[2]); - when(jobContext.getPartitionSet()).thenReturn(subtaskIds); + when(jobContext.getTaskIdPartitionMap()).thenReturn(taskIdPartitionMap); + when(jobContext.getAssignedParticipant(anyInt())).thenAnswer( + invocation -> workers[(int) invocation.getArgument(0)]); + when(jobContext.getPartitionState(anyInt())).thenAnswer(invocation -> helixStates[(int) invocation.getArgument(0)]); Map progress = mgr.getSubtaskProgress(taskName, StringUtils.join(subtaskNames, ','), httpHelper, workerEndpoints, Collections.emptyMap(), 1000); @@ -114,6 +139,8 @@ public void testGetSubtaskProgressNoResponse() public void testGetSubtaskProgressWithResponse() throws Exception { TaskDriver taskDriver = mock(TaskDriver.class); + JobConfig jobConfig = mock(JobConfig.class); + when(taskDriver.getJobConfig(anyString())).thenReturn(jobConfig); JobContext jobContext = mock(JobContext.class); when(taskDriver.getJobContext(anyString())).thenReturn(jobContext); PinotHelixTaskResourceManager mgr = @@ -129,19 +156,25 @@ public void testGetSubtaskProgressWithResponse() } String taskName = "Task_SegmentGenerationAndPushTask_someone"; String[] subtaskNames = new String[3]; - Set subtaskIds = new HashSet<>(); + Map taskIdPartitionMap = new HashMap<>(); for (int i = 0; i < 3; i++) { - subtaskIds.add(i); - subtaskNames[i] = taskName + "_" + i; + String subtaskName = taskName + "_" + i; + subtaskNames[i] = subtaskName; + taskIdPartitionMap.put(subtaskName, i); httpResp._httpResponses.put(workers[i], JsonUtils.objectToString(Collections.singletonMap(subtaskNames[i], "running on worker: " + i))); } + Map taskConfigMap = new HashMap<>(); + for (String subtaskName : subtaskNames) { + taskConfigMap.put(subtaskName, mock(TaskConfig.class)); + } + when(jobConfig.getTaskConfigMap()).thenReturn(taskConfigMap); TaskPartitionState[] helixStates = new TaskPartitionState[]{TaskPartitionState.INIT, TaskPartitionState.RUNNING, TaskPartitionState.TASK_ERROR}; - when(jobContext.getTaskIdForPartition(anyInt())).thenReturn(subtaskNames[0], subtaskNames[1], subtaskNames[2]); - when(jobContext.getAssignedParticipant(anyInt())).thenReturn(workers[0], workers[1], workers[2]); - when(jobContext.getPartitionState(anyInt())).thenReturn(helixStates[0], helixStates[1], helixStates[2]); - when(jobContext.getPartitionSet()).thenReturn(subtaskIds); + when(jobContext.getTaskIdPartitionMap()).thenReturn(taskIdPartitionMap); + when(jobContext.getAssignedParticipant(anyInt())).thenAnswer( + invocation -> workers[(int) invocation.getArgument(0)]); + when(jobContext.getPartitionState(anyInt())).thenAnswer(invocation -> helixStates[(int) invocation.getArgument(0)]); Map progress = mgr.getSubtaskProgress(taskName, StringUtils.join(subtaskNames, ','), httpHelper, workerEndpoints, Collections.emptyMap(), 1000); @@ -155,6 +188,8 @@ public void testGetSubtaskProgressWithResponse() public void testGetSubtaskProgressPending() throws Exception { TaskDriver taskDriver = mock(TaskDriver.class); + JobConfig jobConfig = mock(JobConfig.class); + when(taskDriver.getJobConfig(anyString())).thenReturn(jobConfig); JobContext jobContext = mock(JobContext.class); when(taskDriver.getJobContext(anyString())).thenReturn(jobContext); PinotHelixTaskResourceManager mgr = @@ -170,19 +205,23 @@ public void testGetSubtaskProgressPending() } String taskName = "Task_SegmentGenerationAndPushTask_someone"; String[] subtaskNames = new String[3]; - Set subtaskIds = new HashSet<>(); + Map taskIdPartitionMap = new HashMap<>(); for (int i = 0; i < 3; i++) { - subtaskIds.add(i); - subtaskNames[i] = taskName + "_" + i; + String subtaskName = taskName + "_" + i; + subtaskNames[i] = subtaskName; + taskIdPartitionMap.put(subtaskName, i); } - // Some subtasks are pending to be run. - TaskPartitionState[] helixStates = new TaskPartitionState[]{TaskPartitionState.RUNNING, null, null}; + Map taskConfigMap = new HashMap<>(); + for (String subtaskName : subtaskNames) { + taskConfigMap.put(subtaskName, mock(TaskConfig.class)); + } + when(jobConfig.getTaskConfigMap()).thenReturn(taskConfigMap); + // Some subtasks are pending to be run httpResp._httpResponses.put(workers[0], JsonUtils.objectToString(Collections.singletonMap(subtaskNames[0], "running on worker: 0"))); - when(jobContext.getTaskIdForPartition(anyInt())).thenReturn(subtaskNames[0], subtaskNames[1], subtaskNames[2]); - when(jobContext.getAssignedParticipant(anyInt())).thenReturn(workers[0], null, null); - when(jobContext.getPartitionState(anyInt())).thenReturn(helixStates[0], null, null); - when(jobContext.getPartitionSet()).thenReturn(subtaskIds); + when(jobContext.getTaskIdPartitionMap()).thenReturn(taskIdPartitionMap); + when(jobContext.getAssignedParticipant(0)).thenReturn(workers[0]); + when(jobContext.getPartitionState(0)).thenReturn(TaskPartitionState.RUNNING); Map progress = mgr.getSubtaskProgress(taskName, StringUtils.join(subtaskNames, ','), httpHelper, workerEndpoints, Collections.emptyMap(), 1000); @@ -193,4 +232,111 @@ public void testGetSubtaskProgressPending() taskProgress = (String) progress.get(subtaskNames[2]); assertEquals(taskProgress, "No worker has run this subtask"); } + + @Test + public void testGetSubtaskWithGivenStateProgressNoWorker() + throws JsonProcessingException { + CompletionServiceHelper httpHelper = mock(CompletionServiceHelper.class); + PinotHelixTaskResourceManager mgr = + new PinotHelixTaskResourceManager(mock(PinotHelixResourceManager.class), mock(TaskDriver.class)); + // No worker to run subtasks. + Map selectedMinionWorkerEndpoints = new HashMap<>(); + Map progress = + mgr.getSubtaskOnWorkerProgress("IN_PROGRESS", httpHelper, selectedMinionWorkerEndpoints, Collections.emptyMap(), + 1000); + assertTrue(progress.isEmpty()); + verify(httpHelper, Mockito.never()).doMultiGetRequest(any(), any(), anyBoolean(), any(), anyInt()); + } + + @Test + public void testGetSubtaskWithGivenStateProgress() + throws IOException { + CompletionServiceHelper httpHelper = mock(CompletionServiceHelper.class); + CompletionServiceHelper.CompletionServiceResponse httpResp = + new CompletionServiceHelper.CompletionServiceResponse(); + String taskIdPrefix = "Task_SegmentGenerationAndPushTask_someone"; + String workerIdPrefix = "worker"; + String[] subtaskIds = new String[6]; + String[] workerIds = new String[3]; + Map selectedMinionWorkerEndpoints = new HashMap<>(); + for (int i = 0; i < 3; i++) { + workerIds[i] = workerIdPrefix + i; + String workerEndpoint = "http://" + workerIds[i] + ":9000"; + selectedMinionWorkerEndpoints.put(workerIds[i], workerEndpoint); + + subtaskIds[2 * i] = taskIdPrefix + "_" + (2 * i); + subtaskIds[2 * i + 1] = taskIdPrefix + "_" + (2 * i + 1); + // Notice that for testing purpose, we map subtask names to empty strings. In reality, subtask names will be + // mapped to jsonized org.apache.pinot.minion.event.MinionEventObserver + httpResp._httpResponses.put( + String.format("%s/tasks/subtask/state/progress?subTaskState=IN_PROGRESS", workerEndpoint), + JsonUtils.objectToString(ImmutableMap.of(subtaskIds[2 * i], "", subtaskIds[2 * i + 1], ""))); + } + httpResp._failedResponseCount = 1; + ArgumentCaptor> workerEndpointCaptor = ArgumentCaptor.forClass(List.class); + when(httpHelper.doMultiGetRequest(workerEndpointCaptor.capture(), any(), anyBoolean(), any(), anyInt())).thenReturn( + httpResp); + + PinotHelixTaskResourceManager mgr = + new PinotHelixTaskResourceManager(mock(PinotHelixResourceManager.class), mock(TaskDriver.class)); + Map progress = + mgr.getSubtaskOnWorkerProgress("IN_PROGRESS", httpHelper, selectedMinionWorkerEndpoints, Collections.emptyMap(), + 1000); + List value = workerEndpointCaptor.getValue(); + Set expectedWorkerUrls = selectedMinionWorkerEndpoints.values().stream().map( + workerEndpoint -> String.format("%s/tasks/subtask/state/progress?subTaskState=IN_PROGRESS", workerEndpoint)) + .collect(Collectors.toSet()); + assertEquals(new HashSet<>(value), expectedWorkerUrls); + assertEquals(progress.size(), 3); + for (int i = 0; i < 3; i++) { + Object responseFromMinionWorker = progress.get(workerIds[i]); + Map subtaskProgressMap = (Map) responseFromMinionWorker; + assertEquals(subtaskProgressMap.size(), 2); + assertTrue(subtaskProgressMap.containsKey(subtaskIds[2 * i])); + assertTrue(subtaskProgressMap.containsKey(subtaskIds[2 * i + 1])); + } + } + + @Test + public void testGetTableTaskCount() { + String taskName = "Task_TestTask_12345"; + String helixJobName = PinotHelixTaskResourceManager.getHelixJobName(taskName); + TaskDriver taskDriver = mock(TaskDriver.class); + JobConfig jobConfig = mock(JobConfig.class); + when(taskDriver.getJobConfig(anyString())).thenReturn(jobConfig); + Map taskConfigMap = new HashMap<>(); + taskConfigMap.put("taskId0", new TaskConfig("", new HashMap<>())); + taskConfigMap.put("taskId1", + new TaskConfig("", new HashMap<>(Collections.singletonMap("tableName", "table1_OFFLINE")))); + when(jobConfig.getTaskConfigMap()).thenReturn(taskConfigMap); + JobContext jobContext = mock(JobContext.class); + when(taskDriver.getJobContext(helixJobName)).thenReturn(jobContext); + Map taskIdPartitionMap = new HashMap<>(); + taskIdPartitionMap.put("taskId0", 0); + taskIdPartitionMap.put("taskId1", 1); + when(jobContext.getTaskIdPartitionMap()).thenReturn(taskIdPartitionMap); + when(jobContext.getTaskIdForPartition(0)).thenReturn("taskId0"); + when(jobContext.getTaskIdForPartition(1)).thenReturn("taskId1"); + when(jobContext.getPartitionState(0)).thenReturn(TaskPartitionState.RUNNING); + when(jobContext.getPartitionState(1)).thenReturn(TaskPartitionState.COMPLETED); + + PinotHelixTaskResourceManager mgr = + new PinotHelixTaskResourceManager(mock(PinotHelixResourceManager.class), taskDriver); + Map tableTaskCount = mgr.getTableTaskCount(taskName); + assertEquals(tableTaskCount.size(), 2); + PinotHelixTaskResourceManager.TaskCount taskCount = tableTaskCount.get("table1_OFFLINE"); + assertEquals(taskCount.getTotal(), 1); + assertEquals(taskCount.getCompleted(), 1); + assertEquals(taskCount.getRunning(), 0); + assertEquals(taskCount.getWaiting(), 0); + assertEquals(taskCount.getError(), 0); + assertEquals(taskCount.getUnknown(), 0); + taskCount = tableTaskCount.get("unknown"); + assertEquals(taskCount.getTotal(), 1); + assertEquals(taskCount.getCompleted(), 0); + assertEquals(taskCount.getRunning(), 1); + assertEquals(taskCount.getWaiting(), 0); + assertEquals(taskCount.getError(), 0); + assertEquals(taskCount.getUnknown(), 0); + } } diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/PinotTaskManagerStatelessTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/PinotTaskManagerStatelessTest.java index 6b6e0406ef9b..6c63fd3a5a3d 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/PinotTaskManagerStatelessTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/PinotTaskManagerStatelessTest.java @@ -25,6 +25,7 @@ import java.util.function.Predicate; import org.apache.pinot.controller.ControllerConf; import org.apache.pinot.controller.helix.ControllerTest; +import org.apache.pinot.controller.util.TableSizeReader; import org.apache.pinot.core.common.MinionConstants; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableTaskConfig; @@ -95,6 +96,7 @@ public void testSkipLateCronSchedule() TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setTaskConfig( new TableTaskConfig( ImmutableMap.of("SegmentGenerationAndPushTask", ImmutableMap.of("schedule", "0 * * ? * * *")))).build(); + waitForEVToDisappear(tableConfig.getTableName()); addTableConfig(tableConfig); waitForJobGroupNames(_controllerStarter.getTaskManager(), jgn -> jgn.size() == 1 && jgn.contains(MinionConstants.SegmentGenerationAndPushTask.TASK_TYPE), @@ -128,6 +130,7 @@ public void testPinotTaskManagerSchedulerWithUpdate() TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setTaskConfig( new TableTaskConfig( ImmutableMap.of("SegmentGenerationAndPushTask", ImmutableMap.of("schedule", "0 */10 * ? * * *")))).build(); + waitForEVToDisappear(tableConfig.getTableName()); addTableConfig(tableConfig); waitForJobGroupNames(_controllerStarter.getTaskManager(), jgn -> jgn.size() == 1 && jgn.contains(MinionConstants.SegmentGenerationAndPushTask.TASK_TYPE), @@ -149,8 +152,8 @@ public void testPinotTaskManagerSchedulerWithUpdate() "MergeRollupTask", ImmutableMap.of("schedule", "0 */10 * ? * * *")))); updateTableConfig(tableConfig); waitForJobGroupNames(_controllerStarter.getTaskManager(), - jgn -> jgn.size() == 2 && jgn.contains(MinionConstants.SegmentGenerationAndPushTask.TASK_TYPE) && jgn - .contains(MinionConstants.MergeRollupTask.TASK_TYPE), + jgn -> jgn.size() == 2 && jgn.contains(MinionConstants.SegmentGenerationAndPushTask.TASK_TYPE) && jgn.contains( + MinionConstants.MergeRollupTask.TASK_TYPE), "JobGroupNames should have SegmentGenerationAndPushTask and MergeRollupTask"); validateJob(MinionConstants.SegmentGenerationAndPushTask.TASK_TYPE, "0 */30 * ? * * *"); validateJob(MinionConstants.MergeRollupTask.TASK_TYPE, "0 */10 * ? * * *"); @@ -193,20 +196,19 @@ public void testPinotTaskManagerSchedulerWithRestart() TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setTaskConfig( new TableTaskConfig( ImmutableMap.of("SegmentGenerationAndPushTask", ImmutableMap.of("schedule", "0 */10 * ? * * *")))).build(); + waitForEVToDisappear(tableConfig.getTableName()); addTableConfig(tableConfig); waitForJobGroupNames(_controllerStarter.getTaskManager(), jgn -> jgn.size() == 1 && jgn.contains(MinionConstants.SegmentGenerationAndPushTask.TASK_TYPE), "JobGroupNames should have SegmentGenerationAndPushTask only"); validateJob(MinionConstants.SegmentGenerationAndPushTask.TASK_TYPE, "0 */10 * ? * * *"); - // Restart controller - stopController(); - startController(properties); - // wait for controller to start correctly. + // Restart controller. + restartController(); TestUtils.waitForCondition((aVoid) -> { try { long tableSize = getTableSize(OFFLINE_TABLE_NAME); - return tableSize >= 0; + return tableSize == TableSizeReader.DEFAULT_SIZE_WHEN_MISSING_OR_ERROR; } catch (Exception e) { return false; } @@ -228,8 +230,8 @@ public void testPinotTaskManagerSchedulerWithRestart() // The new MergeRollup task wouldn't be scheduled if not eagerly checking table configs // after setting up subscriber on ChildChanges zk event when controller gets restarted. waitForJobGroupNames(_controllerStarter.getTaskManager(), - jgn -> jgn.size() == 2 && jgn.contains(MinionConstants.SegmentGenerationAndPushTask.TASK_TYPE) && jgn - .contains(MinionConstants.MergeRollupTask.TASK_TYPE), + jgn -> jgn.size() == 2 && jgn.contains(MinionConstants.SegmentGenerationAndPushTask.TASK_TYPE) && jgn.contains( + MinionConstants.MergeRollupTask.TASK_TYPE), "JobGroupNames should have SegmentGenerationAndPushTask and MergeRollupTask"); dropOfflineTable(RAW_TABLE_NAME); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/TaskMetricsEmitterTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/TaskMetricsEmitterTest.java new file mode 100644 index 000000000000..6fcb708c7177 --- /dev/null +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/TaskMetricsEmitterTest.java @@ -0,0 +1,308 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.controller.helix.core.minion; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import org.apache.helix.task.TaskPartitionState; +import org.apache.pinot.common.metrics.ControllerMetrics; +import org.apache.pinot.controller.ControllerConf; +import org.apache.pinot.controller.LeadControllerManager; +import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; +import org.apache.pinot.plugin.metrics.yammer.YammerMetricName; +import org.apache.pinot.plugin.metrics.yammer.YammerMetricsRegistry; +import org.apache.pinot.plugin.metrics.yammer.YammerSettableGauge; +import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.metrics.PinotMetricUtils; +import org.apache.pinot.spi.metrics.PinotMetricsRegistry; +import org.mockito.Mockito; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.apache.pinot.spi.utils.CommonConstants.CONFIG_OF_METRICS_FACTORY_CLASS_NAME; + + +public class TaskMetricsEmitterTest { + private TaskMetricsEmitter _taskMetricsEmitter; + private ControllerMetrics _controllerMetrics; + private PinotHelixTaskResourceManager _pinotHelixTaskResourceManager; + + @BeforeMethod + public void setUp() { + // initialize PinotMetrics + PinotConfiguration pinotConfiguration = new PinotConfiguration(); + pinotConfiguration.setProperty(CONFIG_OF_METRICS_FACTORY_CLASS_NAME, + "org.apache.pinot.plugin.metrics.yammer.YammerMetricsFactory"); + PinotMetricUtils.init(pinotConfiguration); + + _controllerMetrics = new ControllerMetrics(new YammerMetricsRegistry()); + _pinotHelixTaskResourceManager = Mockito.mock(PinotHelixTaskResourceManager.class); + PinotHelixResourceManager pinotHelixResourceManager = Mockito.mock(PinotHelixResourceManager.class); + LeadControllerManager leadControllerManager = Mockito.mock(LeadControllerManager.class); + + Mockito.when(_pinotHelixTaskResourceManager.getTaskMetadataLastUpdateTimeMs()).thenReturn(ImmutableMap.of()); + Mockito.when(leadControllerManager.isLeaderForTable("TaskMetricsEmitter")).thenReturn(true); + Mockito.when(pinotHelixResourceManager.getOnlineInstanceList()).thenReturn(ImmutableList.of()); + + _taskMetricsEmitter = new TaskMetricsEmitter(pinotHelixResourceManager, + _pinotHelixTaskResourceManager, leadControllerManager, new ControllerConf(), _controllerMetrics); + } + + @Test + public void noTaskTypeMetrics() { + PinotMetricsRegistry metricsRegistry = _controllerMetrics.getMetricsRegistry(); + Mockito.when(_pinotHelixTaskResourceManager.getTaskTypes()).thenReturn(ImmutableSet.of()); + _taskMetricsEmitter.runTask(null); + Assert.assertEquals(metricsRegistry.allMetrics().size(), 1); + Assert.assertTrue(metricsRegistry.allMetrics().containsKey( + new YammerMetricName(ControllerMetrics.class, "pinot.controller.onlineMinionInstances"))); + } + + @Test + public void taskType1ButNoInProgressTask() { + PinotMetricsRegistry metricsRegistry = _controllerMetrics.getMetricsRegistry(); + String taskType = "taskType1"; + Mockito.when(_pinotHelixTaskResourceManager.getTaskTypes()).thenReturn(ImmutableSet.of(taskType)); + Mockito.when(_pinotHelixTaskResourceManager.getTasksInProgress(taskType)).thenReturn(ImmutableSet.of()); + _taskMetricsEmitter.runTask(null); + + Assert.assertEquals(metricsRegistry.allMetrics().size(), 7); + Assert.assertTrue(metricsRegistry.allMetrics().containsKey( + new YammerMetricName(ControllerMetrics.class, "pinot.controller.onlineMinionInstances"))); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, "pinot.controller.numMinionTasksInProgress.taskType1")) + .getMetric()).value(), 0L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, "pinot.controller.numMinionSubtasksRunning.taskType1")) + .getMetric()).value(), 0L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, "pinot.controller.numMinionSubtasksWaiting.taskType1")) + .getMetric()).value(), 0L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, "pinot.controller.numMinionSubtasksError.taskType1")) + .getMetric()).value(), 0L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, "pinot.controller.percentMinionSubtasksInQueue.taskType1")) + .getMetric()).value(), 0L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, "pinot.controller.percentMinionSubtasksInError.taskType1")) + .getMetric()).value(), 0L); + } + + @Test + public void oneSingleTaskTypeWithTwoTables() { + String taskType = "taskType1"; + Mockito.when(_pinotHelixTaskResourceManager.getTaskTypes()).thenReturn(ImmutableSet.of(taskType)); + String task11 = "task11"; + String task12 = "task12"; + Mockito.when(_pinotHelixTaskResourceManager.getTasksInProgress(taskType)) + .thenReturn(ImmutableSet.of(task11, task12)); + + String table1 = "table1_OFFLINE"; + String table2 = "table2_OFFLINE"; + PinotHelixTaskResourceManager.TaskCount taskCount1 = new PinotHelixTaskResourceManager.TaskCount(); + taskCount1.addTaskState(TaskPartitionState.COMPLETED); + PinotHelixTaskResourceManager.TaskCount taskCount2 = new PinotHelixTaskResourceManager.TaskCount(); + taskCount2.addTaskState(TaskPartitionState.RUNNING); + Mockito.when(_pinotHelixTaskResourceManager.getTableTaskCount(task11)).thenReturn( + ImmutableMap.of(table1, taskCount1, table2, taskCount2)); + taskCount1 = new PinotHelixTaskResourceManager.TaskCount(); + taskCount1.addTaskState(null); + taskCount2 = new PinotHelixTaskResourceManager.TaskCount(); + taskCount2.addTaskState(TaskPartitionState.TASK_ERROR); + Mockito.when(_pinotHelixTaskResourceManager.getTableTaskCount(task12)).thenReturn( + ImmutableMap.of(table1, taskCount1, table2, taskCount2)); + + runAndAssertForTaskType1WithTwoTables(); + } + + @Test + public void taskType1WithTwoTablesEmitMetricTwice() { + oneSingleTaskTypeWithTwoTables(); + // the second run does not change anything + runAndAssertForTaskType1WithTwoTables(); + } + + private void runAndAssertForTaskType1WithTwoTables() { + PinotMetricsRegistry metricsRegistry = _controllerMetrics.getMetricsRegistry(); + _taskMetricsEmitter.runTask(null); + Assert.assertEquals(metricsRegistry.allMetrics().size(), 17); + + Assert.assertTrue(metricsRegistry.allMetrics().containsKey( + new YammerMetricName(ControllerMetrics.class, "pinot.controller.onlineMinionInstances"))); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, "pinot.controller.numMinionTasksInProgress.taskType1")) + .getMetric()).value(), 2L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, "pinot.controller.numMinionSubtasksRunning.taskType1")) + .getMetric()).value(), 1L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, "pinot.controller.numMinionSubtasksWaiting.taskType1")) + .getMetric()).value(), 1L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, "pinot.controller.numMinionSubtasksError.taskType1")) + .getMetric()).value(), 1L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, "pinot.controller.percentMinionSubtasksInQueue.taskType1")) + .getMetric()).value(), 50L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, "pinot.controller.percentMinionSubtasksInError.taskType1")) + .getMetric()).value(), 25L); + + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, + "pinot.controller.numMinionSubtasksRunning.table1_OFFLINE.taskType1")) + .getMetric()).value(), 0L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, + "pinot.controller.numMinionSubtasksWaiting.table1_OFFLINE.taskType1")) + .getMetric()).value(), 1L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, + "pinot.controller.numMinionSubtasksError.table1_OFFLINE.taskType1")) + .getMetric()).value(), 0L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, + "pinot.controller.percentMinionSubtasksInQueue.table1_OFFLINE.taskType1")) + .getMetric()).value(), 50L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, + "pinot.controller.percentMinionSubtasksInError.table1_OFFLINE.taskType1")) + .getMetric()).value(), 0L); + + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, + "pinot.controller.numMinionSubtasksRunning.table2_OFFLINE.taskType1")) + .getMetric()).value(), 1L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, + "pinot.controller.numMinionSubtasksWaiting.table2_OFFLINE.taskType1")) + .getMetric()).value(), 0L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, + "pinot.controller.numMinionSubtasksError.table2_OFFLINE.taskType1")) + .getMetric()).value(), 1L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, + "pinot.controller.percentMinionSubtasksInQueue.table2_OFFLINE.taskType1")) + .getMetric()).value(), 50L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, + "pinot.controller.percentMinionSubtasksInError.table2_OFFLINE.taskType1")) + .getMetric()).value(), 50L); + } + + @Test + public void taskType2WithOneTable() { + oneTaskTypeWithOneTable("taskType2", "task21", "task22", "table3_OFFLINE"); + } + + private void oneTaskTypeWithOneTable(String taskType, String taskName1, String taskName2, String tableName) { + Mockito.when(_pinotHelixTaskResourceManager.getTaskTypes()).thenReturn(ImmutableSet.of(taskType)); + Mockito.when(_pinotHelixTaskResourceManager.getTasksInProgress(taskType)) + .thenReturn(ImmutableSet.of(taskName1, taskName2)); + + PinotHelixTaskResourceManager.TaskCount taskCount = new PinotHelixTaskResourceManager.TaskCount(); + taskCount.addTaskState(TaskPartitionState.COMPLETED); + Mockito.when(_pinotHelixTaskResourceManager.getTableTaskCount(taskName1)) + .thenReturn(ImmutableMap.of(tableName, taskCount)); + taskCount = new PinotHelixTaskResourceManager.TaskCount(); + taskCount.addTaskState(null); + Mockito.when(_pinotHelixTaskResourceManager.getTableTaskCount(taskName2)) + .thenReturn(ImmutableMap.of(tableName, taskCount)); + + PinotMetricsRegistry metricsRegistry = _controllerMetrics.getMetricsRegistry(); + _taskMetricsEmitter.runTask(null); + Assert.assertEquals(metricsRegistry.allMetrics().size(), 12); + + Assert.assertTrue(metricsRegistry.allMetrics().containsKey( + new YammerMetricName(ControllerMetrics.class, "pinot.controller.onlineMinionInstances"))); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, + String.format("pinot.controller.numMinionTasksInProgress.%s", taskType))) + .getMetric()).value(), 2L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, + String.format("pinot.controller.numMinionSubtasksRunning.%s", taskType))) + .getMetric()).value(), 0L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, + String.format("pinot.controller.numMinionSubtasksWaiting.%s", taskType))) + .getMetric()).value(), 1L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, + String.format("pinot.controller.numMinionSubtasksError.%s", taskType))) + .getMetric()).value(), 0L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, + String.format("pinot.controller.percentMinionSubtasksInQueue.%s", taskType))) + .getMetric()).value(), 50L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, + String.format("pinot.controller.percentMinionSubtasksInError.%s", taskType))) + .getMetric()).value(), 0L); + + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, + String.format("pinot.controller.numMinionSubtasksRunning.%s.%s", tableName, taskType))) + .getMetric()).value(), 0L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, + String.format("pinot.controller.numMinionSubtasksWaiting.%s.%s", tableName, taskType))) + .getMetric()).value(), 1L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, + String.format("pinot.controller.numMinionSubtasksError.%s.%s", tableName, taskType))) + .getMetric()).value(), 0L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, + String.format("pinot.controller.percentMinionSubtasksInQueue.%s.%s", tableName, taskType))) + .getMetric()).value(), 50L); + Assert.assertEquals(((YammerSettableGauge) metricsRegistry.allMetrics().get( + new YammerMetricName(ControllerMetrics.class, + String.format("pinot.controller.percentMinionSubtasksInError.%s.%s", tableName, taskType))) + .getMetric()).value(), 0L); + } + + @Test + public void removeOneTableFromMinionTasks() { + oneSingleTaskTypeWithTwoTables(); + oneTaskTypeWithOneTable("taskType1", "task11", "task12", "table1_OFFLINE"); + } + + @Test + public void addOneTableToMinionTasks() { + oneTaskTypeWithOneTable("taskType1", "task11", "task12", "table1_OFFLINE"); + oneSingleTaskTypeWithTwoTables(); + } + + @Test + public void removeTheTaskTypeFromMinionTasks() { + oneSingleTaskTypeWithTwoTables(); + noTaskTypeMetrics(); + } + + @Test + public void removeOldTaskTypeAddNewTaskType() { + oneSingleTaskTypeWithTwoTables(); + taskType2WithOneTable(); + } +} diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/generator/TaskGeneratorUtilsTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/generator/TaskGeneratorUtilsTest.java index c9821b3a6c90..17dd966482f4 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/generator/TaskGeneratorUtilsTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/minion/generator/TaskGeneratorUtilsTest.java @@ -26,8 +26,14 @@ import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; import org.apache.pinot.controller.helix.core.minion.ClusterInfoAccessor; +import org.apache.pinot.controller.helix.core.minion.PinotTaskManager; import org.apache.pinot.core.common.MinionConstants; import org.apache.pinot.core.minion.PinotTaskConfig; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableTaskConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.utils.CommonConstants; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.mockito.Mockito; import org.testng.annotations.Test; @@ -98,4 +104,36 @@ private static ClusterInfoAccessor createMockClusterInfoAccessor() { when(mockClusterInfoAcessor.getPinotHelixResourceManager()).thenReturn(mockHelixResourceManager); return mockClusterInfoAcessor; } + + @Test + public void testExtractMinionInstanceTag() { + // correct minionInstanceTag extraction + Map tableTaskConfigs = getDummyTaskConfig(); + tableTaskConfigs.put(PinotTaskManager.MINION_INSTANCE_TAG_CONFIG, "minionInstance1"); + TableTaskConfig tableTaskConfig = + new TableTaskConfig(Collections.singletonMap(MinionConstants.MergeRollupTask.TASK_TYPE, tableTaskConfigs)); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName("sampleTable") + .setTaskConfig(tableTaskConfig).build(); + assertEquals(TaskGeneratorUtils.extractMinionInstanceTag(tableConfig, + MinionConstants.MergeRollupTask.TASK_TYPE), "minionInstance1"); + + // no minionInstanceTag passed + tableTaskConfigs = getDummyTaskConfig(); + tableTaskConfig = + new TableTaskConfig(Collections.singletonMap(MinionConstants.MergeRollupTask.TASK_TYPE, tableTaskConfigs)); + tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName("sampleTable") + .setTaskConfig(tableTaskConfig).build(); + assertEquals(TaskGeneratorUtils.extractMinionInstanceTag(tableConfig, + MinionConstants.MergeRollupTask.TASK_TYPE), CommonConstants.Helix.UNTAGGED_MINION_INSTANCE); + } + + private Map getDummyTaskConfig() { + Map tableTaskConfigs = new HashMap<>(); + tableTaskConfigs.put("100days.mergeType", "concat"); + tableTaskConfigs.put("100days.bufferTimePeriod", "1d"); + tableTaskConfigs.put("100days.bucketTimePeriod", "100d"); + tableTaskConfigs.put("100days.maxNumRecordsPerSegment", "15000"); + tableTaskConfigs.put("100days.maxNumRecordsPerTask", "15000"); + return tableTaskConfigs; + } } diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/periodictask/ControllerPeriodicTaskTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/periodictask/ControllerPeriodicTaskTest.java index a012d6a27521..f4e0eb46b14f 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/periodictask/ControllerPeriodicTaskTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/periodictask/ControllerPeriodicTaskTest.java @@ -26,6 +26,7 @@ import java.util.stream.IntStream; import org.apache.pinot.common.metrics.ControllerGauge; import org.apache.pinot.common.metrics.ControllerMetrics; +import org.apache.pinot.common.metrics.MetricValueUtils; import org.apache.pinot.controller.ControllerConf; import org.apache.pinot.controller.LeadControllerManager; import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; @@ -104,7 +105,6 @@ public void testRandomInitialDelay() { _task.getInitialDelayInSeconds() >= ControllerConf.ControllerPeriodicTasksConf.MIN_INITIAL_DELAY_IN_SECONDS); assertTrue( _task.getInitialDelayInSeconds() < ControllerConf.ControllerPeriodicTasksConf.MAX_INITIAL_DELAY_IN_SECONDS); - assertEquals(_task.getIntervalInSeconds(), RUN_FREQUENCY_IN_SECONDS); } @@ -118,8 +118,8 @@ public void testControllerPeriodicTaskCalls() { assertEquals(_tablesProcessed.get(), 0); assertFalse(_stopTaskCalled.get()); assertTrue(_task.isStarted()); - assertEquals( - _controllerMetrics.getValueOfGlobalGauge(ControllerGauge.PERIODIC_TASK_NUM_TABLES_PROCESSED, TASK_NAME), 0); + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerMetrics, TASK_NAME, + ControllerGauge.PERIODIC_TASK_NUM_TABLES_PROCESSED), 0); // Run periodic task with leadership resetState(); @@ -127,9 +127,8 @@ public void testControllerPeriodicTaskCalls() { assertFalse(_startTaskCalled.get()); assertTrue(_processTablesCalled.get()); assertEquals(_tablesProcessed.get(), _numTables); - assertEquals( - _controllerMetrics.getValueOfGlobalGauge(ControllerGauge.PERIODIC_TASK_NUM_TABLES_PROCESSED, TASK_NAME), - _numTables); + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerMetrics, TASK_NAME, + ControllerGauge.PERIODIC_TASK_NUM_TABLES_PROCESSED), _numTables); assertFalse(_stopTaskCalled.get()); assertTrue(_task.isStarted()); @@ -139,8 +138,8 @@ public void testControllerPeriodicTaskCalls() { assertFalse(_startTaskCalled.get()); assertFalse(_processTablesCalled.get()); assertEquals(_tablesProcessed.get(), 0); - assertEquals( - _controllerMetrics.getValueOfGlobalGauge(ControllerGauge.PERIODIC_TASK_NUM_TABLES_PROCESSED, TASK_NAME), 0); + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerMetrics, TASK_NAME, + ControllerGauge.PERIODIC_TASK_NUM_TABLES_PROCESSED), 0); assertTrue(_stopTaskCalled.get()); assertFalse(_task.isStarted()); @@ -150,8 +149,8 @@ public void testControllerPeriodicTaskCalls() { assertFalse(_startTaskCalled.get()); assertFalse(_processTablesCalled.get()); assertEquals(_tablesProcessed.get(), 0); - assertEquals( - _controllerMetrics.getValueOfGlobalGauge(ControllerGauge.PERIODIC_TASK_NUM_TABLES_PROCESSED, TASK_NAME), 0); + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerMetrics, TASK_NAME, + ControllerGauge.PERIODIC_TASK_NUM_TABLES_PROCESSED), 0); assertFalse(_stopTaskCalled.get()); assertFalse(_task.isStarted()); @@ -163,8 +162,8 @@ public void testControllerPeriodicTaskCalls() { assertEquals(_tablesProcessed.get(), 0); assertFalse(_stopTaskCalled.get()); assertTrue(_task.isStarted()); - assertEquals( - _controllerMetrics.getValueOfGlobalGauge(ControllerGauge.PERIODIC_TASK_NUM_TABLES_PROCESSED, TASK_NAME), 0); + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerMetrics, TASK_NAME, + ControllerGauge.PERIODIC_TASK_NUM_TABLES_PROCESSED), 0); // Run periodic task with leadership resetState(); @@ -172,9 +171,8 @@ public void testControllerPeriodicTaskCalls() { assertFalse(_startTaskCalled.get()); assertTrue(_processTablesCalled.get()); assertEquals(_tablesProcessed.get(), _numTables); - assertEquals( - _controllerMetrics.getValueOfGlobalGauge(ControllerGauge.PERIODIC_TASK_NUM_TABLES_PROCESSED, TASK_NAME), - _numTables); + assertEquals(MetricValueUtils.getGlobalGaugeValue(_controllerMetrics, TASK_NAME, + ControllerGauge.PERIODIC_TASK_NUM_TABLES_PROCESSED), _numTables); assertFalse(_stopTaskCalled.get()); assertTrue(_task.isStarted()); } diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManagerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManagerTest.java index eee1f2a8a805..f0496a8ee7e2 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManagerTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/PinotLLCRealtimeSegmentManagerTest.java @@ -23,6 +23,7 @@ import java.io.File; import java.io.IOException; import java.net.URISyntaxException; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -30,7 +31,9 @@ import java.util.List; import java.util.Map; import java.util.Random; +import java.util.Set; import java.util.TreeMap; +import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -40,7 +43,6 @@ import org.apache.helix.HelixAdmin; import org.apache.helix.HelixManager; import org.apache.helix.model.ExternalView; -import org.apache.helix.model.HelixConfigScope; import org.apache.helix.model.IdealState; import org.apache.helix.model.InstanceConfig; import org.apache.helix.store.zk.ZkHelixPropertyStore; @@ -56,7 +58,7 @@ import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; import org.apache.pinot.controller.helix.core.assignment.segment.SegmentAssignment; import org.apache.pinot.controller.helix.core.realtime.segment.CommittingSegmentDescriptor; -import org.apache.pinot.controller.util.SegmentCompletionUtils; +import org.apache.pinot.core.data.manager.realtime.SegmentCompletionUtils; import org.apache.pinot.core.realtime.impl.fakestream.FakeStreamConfigUtils; import org.apache.pinot.segment.spi.creator.SegmentVersion; import org.apache.pinot.segment.spi.index.metadata.SegmentMetadataImpl; @@ -69,24 +71,25 @@ import org.apache.pinot.spi.stream.LongMsgOffset; import org.apache.pinot.spi.stream.PartitionGroupConsumptionStatus; import org.apache.pinot.spi.stream.PartitionGroupMetadata; -import org.apache.pinot.spi.stream.PartitionLevelStreamConfig; import org.apache.pinot.spi.stream.StreamConfig; import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.CommonConstants.Helix; +import org.apache.pinot.spi.utils.CommonConstants.Helix.Instance; import org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel; import org.apache.pinot.spi.utils.CommonConstants.Segment.Realtime.Status; import org.apache.pinot.spi.utils.IngestionConfigUtils; -import org.apache.pinot.spi.utils.StringUtil; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.apache.pinot.spi.utils.builder.TableNameBuilder; +import org.apache.pinot.util.TestUtils; import org.apache.zookeeper.data.Stat; import org.joda.time.Interval; import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.Test; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; +import static org.apache.pinot.controller.ControllerConf.ControllerPeriodicTasksConf.ENABLE_TMP_SEGMENT_ASYNC_DELETION; +import static org.apache.pinot.controller.ControllerConf.ControllerPeriodicTasksConf.TMP_SEGMENT_RETENTION_IN_SECONDS; +import static org.apache.pinot.spi.utils.CommonConstants.Segment.METADATA_URI_FOR_PEER_DOWNLOAD; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.*; @@ -95,6 +98,7 @@ public class PinotLLCRealtimeSegmentManagerTest { private static final File TEMP_DIR = new File(FileUtils.getTempDirectory(), "PinotLLCRealtimeSegmentManagerTest"); private static final String SCHEME = "file:"; + private static final String CLUSTER_NAME = "testCluster"; private static final String RAW_TABLE_NAME = "testTable"; private static final String REALTIME_TABLE_NAME = TableNameBuilder.REALTIME.tableNameWithType(RAW_TABLE_NAME); @@ -751,20 +755,6 @@ private void verifyRepairs(FakePinotLLCRealtimeSegmentManager segmentManager, Li } } - @Test(expectedExceptions = IllegalStateException.class) - public void testPreExistingSegments() { - FakePinotLLCRealtimeSegmentManager segmentManager = new FakePinotLLCRealtimeSegmentManager(); - segmentManager._numReplicas = 2; - segmentManager.makeTableConfig(); - segmentManager._numInstances = 5; - segmentManager.makeConsumingInstancePartitions(); - segmentManager._numPartitions = 4; - - String existingSegmentName = new LLCSegmentName(RAW_TABLE_NAME, 0, 0, CURRENT_TIME_MS).getSegmentName(); - segmentManager._segmentZKMetadataMap.put(existingSegmentName, new SegmentZKMetadata(existingSegmentName)); - segmentManager.setUpNewTable(); - } - @Test public void testCommitSegmentWhenControllerWentThroughGC() { // Set up a new table with 2 replicas, 5 instances, 4 partitions @@ -801,7 +791,7 @@ public void testCommitSegmentFile() PinotFSFactory.init(new PinotConfiguration()); File tableDir = new File(TEMP_DIR, RAW_TABLE_NAME); String segmentName = new LLCSegmentName(RAW_TABLE_NAME, 0, 0, CURRENT_TIME_MS).getSegmentName(); - String segmentFileName = SegmentCompletionUtils.generateSegmentFileName(segmentName); + String segmentFileName = SegmentCompletionUtils.generateTmpSegmentFileName(segmentName); File segmentFile = new File(tableDir, segmentFileName); FileUtils.write(segmentFile, "temporary file contents"); @@ -822,9 +812,9 @@ public void testSegmentAlreadyThereAndExtraneousFilesDeleted() File tableDir = new File(TEMP_DIR, RAW_TABLE_NAME); String segmentName = new LLCSegmentName(RAW_TABLE_NAME, 0, 0, CURRENT_TIME_MS).getSegmentName(); String otherSegmentName = new LLCSegmentName(RAW_TABLE_NAME, 1, 0, CURRENT_TIME_MS).getSegmentName(); - String segmentFileName = SegmentCompletionUtils.generateSegmentFileName(segmentName); - String extraSegmentFileName = SegmentCompletionUtils.generateSegmentFileName(segmentName); - String otherSegmentFileName = SegmentCompletionUtils.generateSegmentFileName(otherSegmentName); + String segmentFileName = SegmentCompletionUtils.generateTmpSegmentFileName(segmentName); + String extraSegmentFileName = SegmentCompletionUtils.generateTmpSegmentFileName(segmentName); + String otherSegmentFileName = SegmentCompletionUtils.generateTmpSegmentFileName(otherSegmentName); File segmentFile = new File(tableDir, segmentFileName); File extraSegmentFile = new File(tableDir, extraSegmentFileName); File otherSegmentFile = new File(tableDir, otherSegmentFileName); @@ -862,12 +852,6 @@ public void testStopSegmentManager() } catch (IllegalStateException e) { // Expected } - try { - segmentManager.removeLLCSegments(new IdealState(REALTIME_TABLE_NAME)); - fail(); - } catch (IllegalStateException e) { - // Expected - } try { segmentManager.commitSegmentFile(REALTIME_TABLE_NAME, mock(CommittingSegmentDescriptor.class)); fail(); @@ -941,13 +925,14 @@ public void testUploadToSegmentStore() (ZkHelixPropertyStore) mock(ZkHelixPropertyStore.class); when(pinotHelixResourceManager.getHelixZkManager()).thenReturn(helixManager); when(helixManager.getClusterManagmentTool()).thenReturn(helixAdmin); - when(helixManager.getClusterName()).thenReturn("cluster_name"); + when(helixManager.getClusterName()).thenReturn(CLUSTER_NAME); when(pinotHelixResourceManager.getPropertyStore()).thenReturn(zkHelixPropertyStore); // init fake PinotLLCRealtimeSegmentManager ControllerConf controllerConfig = new ControllerConf(); - controllerConfig.setProperty( - ControllerConf.ControllerPeriodicTasksConf.ENABLE_DEEP_STORE_RETRY_UPLOAD_LLC_SEGMENT, true); + controllerConfig.setProperty(ControllerConf.ControllerPeriodicTasksConf.ENABLE_DEEP_STORE_RETRY_UPLOAD_LLC_SEGMENT, + true); + controllerConfig.setDataDir(TEMP_DIR.toString()); FakePinotLLCRealtimeSegmentManager segmentManager = new FakePinotLLCRealtimeSegmentManager(pinotHelixResourceManager, controllerConfig); Assert.assertTrue(segmentManager.isDeepStoreLLCSegmentUploadRetryEnabled()); @@ -959,71 +944,63 @@ public void testUploadToSegmentStore() segmentsValidationAndRetentionConfig.setRetentionTimeUnit(TimeUnit.DAYS.toString()); segmentsValidationAndRetentionConfig.setRetentionTimeValue("3"); segmentManager._tableConfig.setValidationConfig(segmentsValidationAndRetentionConfig); - List segmentsZKMetadata = - new ArrayList<>(segmentManager._segmentZKMetadataMap.values()); + List segmentsZKMetadata = new ArrayList<>(segmentManager._segmentZKMetadataMap.values()); Assert.assertEquals(segmentsZKMetadata.size(), 5); // Set up external view for this table ExternalView externalView = new ExternalView(REALTIME_TABLE_NAME); - when(helixAdmin.getResourceExternalView("cluster_name", REALTIME_TABLE_NAME)) - .thenReturn(externalView); - when(helixAdmin.getConfigKeys(any(HelixConfigScope.class))).thenReturn(new ArrayList<>()); - String adminPort = "2077"; - Map instanceConfigMap = new HashMap<>(); - instanceConfigMap.put(CommonConstants.Helix.Instance.ADMIN_PORT_KEY, adminPort); - when(helixAdmin.getConfig(any(HelixConfigScope.class), any(List.class))).thenReturn(instanceConfigMap); + when(helixAdmin.getResourceExternalView(CLUSTER_NAME, REALTIME_TABLE_NAME)).thenReturn(externalView); // Change 1st segment status to be DONE, but with default peer download url. // Verify later the download url is fixed after upload success. segmentsZKMetadata.get(0).setStatus(Status.DONE); - segmentsZKMetadata.get(0).setDownloadUrl(CommonConstants.Segment.METADATA_URI_FOR_PEER_DOWNLOAD); + segmentsZKMetadata.get(0).setDownloadUrl(METADATA_URI_FOR_PEER_DOWNLOAD); // set up the external view for 1st segment String instance0 = "instance0"; + int adminPort = 2077; externalView.setState(segmentsZKMetadata.get(0).getSegmentName(), instance0, "ONLINE"); InstanceConfig instanceConfig0 = new InstanceConfig(instance0); instanceConfig0.setHostName(instance0); - when(helixAdmin.getInstanceConfig(any(String.class), eq(instance0))).thenReturn(instanceConfig0); + instanceConfig0.getRecord().setIntField(Instance.ADMIN_PORT_KEY, adminPort); + when(helixAdmin.getInstanceConfig(CLUSTER_NAME, instance0)).thenReturn(instanceConfig0); // mock the request/response for 1st segment upload - String serverUploadRequestUrl0 = StringUtil - .join("/", - CommonConstants.HTTP_PROTOCOL + "://" + instance0 + ":" + adminPort, - "segments", - REALTIME_TABLE_NAME, - segmentsZKMetadata.get(0).getSegmentName(), - "upload"); - String segmentDownloadUrl0 = String.format("segmentDownloadUr_%s", segmentsZKMetadata.get(0) - .getSegmentName()); - when(segmentManager._mockedFileUploadDownloadClient - .uploadToSegmentStore(serverUploadRequestUrl0)).thenReturn(segmentDownloadUrl0); + String serverUploadRequestUrl0 = + String.format("http://%s:%d/segments/%s/%s/upload?uploadTimeoutMs=-1", instance0, adminPort, + REALTIME_TABLE_NAME, segmentsZKMetadata.get(0).getSegmentName()); + // tempSegmentFileLocation is the location where the segment uploader will upload the segment. This usually ends + // with a random UUID + File tempSegmentFileLocation = new File(TEMP_DIR, segmentsZKMetadata.get(0).getSegmentName() + UUID.randomUUID()); + FileUtils.write(tempSegmentFileLocation, "test"); + // After the deep-store retry task gets the segment location returned by Pinot server, it will move the segment to + // its final location. This is the expected segment location. + String expectedSegmentLocation = + segmentManager.createSegmentPath(RAW_TABLE_NAME, segmentsZKMetadata.get(0).getSegmentName()).toString(); + when(segmentManager._mockedFileUploadDownloadClient.uploadToSegmentStore(serverUploadRequestUrl0)).thenReturn( + tempSegmentFileLocation.getPath()); // Change 2nd segment status to be DONE, but with default peer download url. // Verify later the download url isn't fixed after upload failure. segmentsZKMetadata.get(1).setStatus(Status.DONE); - segmentsZKMetadata.get(1).setDownloadUrl(CommonConstants.Segment.METADATA_URI_FOR_PEER_DOWNLOAD); + segmentsZKMetadata.get(1).setDownloadUrl(METADATA_URI_FOR_PEER_DOWNLOAD); // set up the external view for 2nd segment String instance1 = "instance1"; externalView.setState(segmentsZKMetadata.get(1).getSegmentName(), instance1, "ONLINE"); InstanceConfig instanceConfig1 = new InstanceConfig(instance1); instanceConfig1.setHostName(instance1); - when(helixAdmin.getInstanceConfig(any(String.class), eq(instance1))).thenReturn(instanceConfig1); + instanceConfig1.getRecord().setIntField(Instance.ADMIN_PORT_KEY, adminPort); + when(helixAdmin.getInstanceConfig(CLUSTER_NAME, instance1)).thenReturn(instanceConfig1); // mock the request/response for 2nd segment upload - String serverUploadRequestUrl1 = StringUtil - .join("/", - CommonConstants.HTTP_PROTOCOL + "://" + instance1 + ":" + adminPort, - "segments", - REALTIME_TABLE_NAME, - segmentsZKMetadata.get(1).getSegmentName(), - "upload"); - when(segmentManager._mockedFileUploadDownloadClient - .uploadToSegmentStore(serverUploadRequestUrl1)) - .thenThrow(new HttpErrorStatusException( - "failed to upload segment", Response.Status.INTERNAL_SERVER_ERROR.getStatusCode())); + String serverUploadRequestUrl1 = + String.format("http://%s:%d/segments/%s/%s/upload?uploadTimeoutMs=-1", instance1, adminPort, + REALTIME_TABLE_NAME, segmentsZKMetadata.get(1).getSegmentName()); + when(segmentManager._mockedFileUploadDownloadClient.uploadToSegmentStore(serverUploadRequestUrl1)).thenThrow( + new HttpErrorStatusException("failed to upload segment", + Response.Status.INTERNAL_SERVER_ERROR.getStatusCode())); // Change 3rd segment status to be DONE, but with default peer download url. // Verify later the download url isn't fixed because no ONLINE replica found in any server. segmentsZKMetadata.get(2).setStatus(Status.DONE); - segmentsZKMetadata.get(2).setDownloadUrl( - CommonConstants.Segment.METADATA_URI_FOR_PEER_DOWNLOAD); + segmentsZKMetadata.get(2).setDownloadUrl(METADATA_URI_FOR_PEER_DOWNLOAD); // set up the external view for 3rd segment String instance2 = "instance2"; externalView.setState(segmentsZKMetadata.get(2).getSegmentName(), instance2, "OFFLINE"); @@ -1036,27 +1013,68 @@ public void testUploadToSegmentStore() // Keep 5th segment status as IN_PROGRESS. - List segmentNames = segmentsZKMetadata.stream() - .map(SegmentZKMetadata::getSegmentName).collect(Collectors.toList()); - when(pinotHelixResourceManager.getTableConfig(REALTIME_TABLE_NAME)) - .thenReturn(segmentManager._tableConfig); + List segmentNames = + segmentsZKMetadata.stream().map(SegmentZKMetadata::getSegmentName).collect(Collectors.toList()); + when(pinotHelixResourceManager.getTableConfig(REALTIME_TABLE_NAME)).thenReturn(segmentManager._tableConfig); // Verify the result segmentManager.uploadToDeepStoreIfMissing(segmentManager._tableConfig, segmentsZKMetadata); - assertEquals( - segmentManager.getSegmentZKMetadata(REALTIME_TABLE_NAME, segmentNames.get(0), null).getDownloadUrl(), - segmentDownloadUrl0); - assertEquals( - segmentManager.getSegmentZKMetadata(REALTIME_TABLE_NAME, segmentNames.get(1), null).getDownloadUrl(), - CommonConstants.Segment.METADATA_URI_FOR_PEER_DOWNLOAD); - assertEquals( - segmentManager.getSegmentZKMetadata(REALTIME_TABLE_NAME, segmentNames.get(2), null).getDownloadUrl(), - CommonConstants.Segment.METADATA_URI_FOR_PEER_DOWNLOAD); - assertEquals( - segmentManager.getSegmentZKMetadata(REALTIME_TABLE_NAME, segmentNames.get(3), null).getDownloadUrl(), + + // Block until all tasks have been able to complete + TestUtils.waitForCondition(aVoid -> segmentManager.deepStoreUploadExecutorPendingSegmentsIsEmpty(), 30_000L, + "Timed out waiting for upload retry tasks to finish"); + + assertEquals(segmentManager.getSegmentZKMetadata(REALTIME_TABLE_NAME, segmentNames.get(0), null).getDownloadUrl(), + expectedSegmentLocation); + assertFalse(tempSegmentFileLocation.exists(), + "Deep-store retry task should move the file from temp location to permanent location"); + + assertEquals(segmentManager.getSegmentZKMetadata(REALTIME_TABLE_NAME, segmentNames.get(1), null).getDownloadUrl(), + METADATA_URI_FOR_PEER_DOWNLOAD); + assertEquals(segmentManager.getSegmentZKMetadata(REALTIME_TABLE_NAME, segmentNames.get(2), null).getDownloadUrl(), + METADATA_URI_FOR_PEER_DOWNLOAD); + assertEquals(segmentManager.getSegmentZKMetadata(REALTIME_TABLE_NAME, segmentNames.get(3), null).getDownloadUrl(), defaultDownloadUrl); - assertNull( - segmentManager.getSegmentZKMetadata(REALTIME_TABLE_NAME, segmentNames.get(4), null).getDownloadUrl()); + assertNull(segmentManager.getSegmentZKMetadata(REALTIME_TABLE_NAME, segmentNames.get(4), null).getDownloadUrl()); + } + + @Test + public void testDeleteTmpSegmentFiles() throws Exception { + // turn on knobs for async deletion of tmp files + ControllerConf config = new ControllerConf(); + config.setDataDir(TEMP_DIR.toString()); + config.setProperty(TMP_SEGMENT_RETENTION_IN_SECONDS, Integer.MIN_VALUE); + config.setProperty(ENABLE_TMP_SEGMENT_ASYNC_DELETION, true); + + // simulate there's an orphan tmp file in localFS + PinotFSFactory.init(new PinotConfiguration()); + File tableDir = new File(TEMP_DIR, RAW_TABLE_NAME); + String segmentName = new LLCSegmentName(RAW_TABLE_NAME, 0, 0, CURRENT_TIME_MS).getSegmentName(); + String segmentFileName = SegmentCompletionUtils.generateTmpSegmentFileName(segmentName); + File segmentFile = new File(tableDir, segmentFileName); + FileUtils.write(segmentFile, "temporary file contents", Charset.defaultCharset()); + + SegmentZKMetadata segZKMeta = mock(SegmentZKMetadata.class); + PinotHelixResourceManager helixResourceManager = mock(PinotHelixResourceManager.class); + when(helixResourceManager.getTableConfig(REALTIME_TABLE_NAME)) + .thenReturn(new TableConfigBuilder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setLLC(true) + .setStreamConfigs(FakeStreamConfigUtils.getDefaultLowLevelStreamConfigs().getStreamConfigsMap()).build()); + PinotLLCRealtimeSegmentManager segmentManager = new FakePinotLLCRealtimeSegmentManager( + helixResourceManager, config); + + long deletedTmpSegCount; + // case 1: the segmentMetadata download uri is identical to the uri of the tmp segment. Should not delete + when(segZKMeta.getStatus()).thenReturn(Status.DONE); + when(segZKMeta.getDownloadUrl()).thenReturn(SCHEME + tableDir + "/" + segmentFileName); + deletedTmpSegCount = segmentManager.deleteTmpSegments(REALTIME_TABLE_NAME, Collections.singletonList(segZKMeta)); + assertTrue(segmentFile.exists()); + assertEquals(0L, deletedTmpSegCount); + + // case 2: download url is empty, indicating the tmp segment is absolutely orphan. Delete the file + when(segZKMeta.getDownloadUrl()).thenReturn(METADATA_URI_FOR_PEER_DOWNLOAD); + deletedTmpSegCount = segmentManager.deleteTmpSegments(REALTIME_TABLE_NAME, Collections.singletonList(segZKMeta)); + assertFalse(segmentFile.exists()); + assertEquals(1L, deletedTmpSegCount); } ////////////////////////////////////////////////////////////////////////////////// @@ -1072,7 +1090,7 @@ private static class FakePinotLLCRealtimeSegmentManager extends PinotLLCRealtime int _numReplicas; TableConfig _tableConfig; - PartitionLevelStreamConfig _streamConfig; + StreamConfig _streamConfig; int _numInstances; InstancePartitions _consumingInstancePartitions; Map _segmentZKMetadataMap = new HashMap<>(); @@ -1095,9 +1113,9 @@ void makeTableConfig() { Map streamConfigs = FakeStreamConfigUtils.getDefaultLowLevelStreamConfigs().getStreamConfigsMap(); _tableConfig = new TableConfigBuilder(TableType.REALTIME).setTableName(RAW_TABLE_NAME).setNumReplicas(_numReplicas) - .setLLC(true).setStreamConfigs(streamConfigs).build(); - _streamConfig = new PartitionLevelStreamConfig(_tableConfig.getTableName(), - IngestionConfigUtils.getStreamConfigMap(_tableConfig)); + .setStreamConfigs(streamConfigs).build(); + _streamConfig = + new StreamConfig(_tableConfig.getTableName(), IngestionConfigUtils.getStreamConfigMap(_tableConfig)); } void makeConsumingInstancePartitions() { @@ -1186,6 +1204,14 @@ void updateIdealStateOnSegmentCompletion(String realtimeTableName, String commit segmentAssignment, instancePartitionsMap); } + @Override + Set getPartitionIds(StreamConfig streamConfig) { + if (_partitionGroupMetadataList != null) { + throw new UnsupportedOperationException(); + } + return IntStream.range(0, _numPartitions).boxed().collect(Collectors.toSet()); + } + @Override List getNewPartitionGroupMetadataList(StreamConfig streamConfig, List currentPartitionGroupConsumptionStatusList) { diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/SegmentCompletionTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/SegmentCompletionTest.java index 2ada570ac87f..4d85223b4111 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/SegmentCompletionTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/SegmentCompletionTest.java @@ -659,6 +659,81 @@ public void testWinnerOnTimeLimit() Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD); } + @Test + public void testWinnerOnForceCommit() + throws Exception { + SegmentCompletionProtocol.Response response; + Request.Params params; + // S1 comes to force commit + _segmentCompletionMgr._seconds = 10L; + params = new Request.Params().withInstanceId(S_1).withStreamPartitionMsgOffset(_s1Offset.toString()) + .withSegmentName(_segmentNameStr).withReason(SegmentCompletionProtocol.REASON_FORCE_COMMIT_MESSAGE_RECEIVED); + response = _segmentCompletionMgr.segmentConsumed(params); + // Still need to wait since we haven't hit time limit or heard from all servers + Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.HOLD); + + // S2 comes with a higher offset 1 second later + _segmentCompletionMgr._seconds += 1; + params = new Request.Params().withInstanceId(S_2).withStreamPartitionMsgOffset(_s2Offset.toString()) + .withSegmentName(_segmentNameStr).withReason(SegmentCompletionProtocol.REASON_FORCE_COMMIT_MESSAGE_RECEIVED); + response = _segmentCompletionMgr.segmentConsumed(params); + // Still need to wait since we haven't hit time limit or heard from all servers + Assert.assertEquals(response.getStatus(), ControllerResponseStatus.HOLD); + + // S3 comes with a lower offset than S2 3 seconds later + _segmentCompletionMgr._seconds += 3; + params = new Request.Params().withInstanceId(S_3).withStreamPartitionMsgOffset(_s3Offset.toString()) + .withSegmentName(_segmentNameStr).withReason(SegmentCompletionProtocol.REASON_FORCE_COMMIT_MESSAGE_RECEIVED); + response = _segmentCompletionMgr.segmentConsumed(params); + // We've met winner criteria, but it should be S_2 with the highest offset. S_3 should catch up. + Assert.assertEquals(response.getStatus(), ControllerResponseStatus.CATCH_UP); + + // S1 comes back at the same offset + _segmentCompletionMgr._seconds += 1; + params = new Request.Params().withInstanceId(S_1).withStreamPartitionMsgOffset(_s1Offset.toString()) + .withSegmentName(_segmentNameStr).withReason(SegmentCompletionProtocol.REASON_FORCE_COMMIT_MESSAGE_RECEIVED); + response = _segmentCompletionMgr.segmentConsumed(params); + // We've met winner criteria, but it should be S2 with the highest offset. S1 should catch up. + Assert.assertEquals(response.getStatus(), ControllerResponseStatus.CATCH_UP); + + // S2 comes back at the same offset + _segmentCompletionMgr._seconds += 1; + params = new Request.Params().withInstanceId(S_2).withStreamPartitionMsgOffset(_s2Offset.toString()) + .withSegmentName(_segmentNameStr).withReason(SegmentCompletionProtocol.REASON_FORCE_COMMIT_MESSAGE_RECEIVED); + response = _segmentCompletionMgr.segmentConsumed(params); + // S2 is told to commit + Assert.assertEquals(response.getStatus(), ControllerResponseStatus.COMMIT); + + // S2 comes back to commit the segment + _segmentCompletionMgr._seconds += 1; + params = new Request.Params().withInstanceId(S_2).withStreamPartitionMsgOffset(_s2Offset.toString()) + .withSegmentName(_segmentNameStr); + response = _segmentCompletionMgr.segmentCommitStart(params); + Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT_CONTINUE); + _segmentCompletionMgr._seconds += 5; + params = new Request.Params().withInstanceId(S_2).withStreamPartitionMsgOffset(_s2Offset.toString()) + .withSegmentName(_segmentNameStr).withSegmentLocation("location"); + response = _segmentCompletionMgr + .segmentCommitEnd(params, true, false, CommittingSegmentDescriptor.fromSegmentCompletionReqParams(params)); + Assert.assertEquals(response.getStatus(), SegmentCompletionProtocol.ControllerResponseStatus.COMMIT_SUCCESS); + + // S3 comes back at the latest offset + _segmentCompletionMgr._seconds += 1; + params = new Request.Params().withInstanceId(S_3).withStreamPartitionMsgOffset(_s2Offset.toString()) + .withSegmentName(_segmentNameStr).withReason(SegmentCompletionProtocol.REASON_FORCE_COMMIT_MESSAGE_RECEIVED); + response = _segmentCompletionMgr.segmentConsumed(params); + // S3 is told to keep since it caught up + Assert.assertEquals(response.getStatus(), ControllerResponseStatus.KEEP); + + // S1 comes back with a higher offset than before, but still not caught up + _segmentCompletionMgr._seconds += 1; + params = new Request.Params().withInstanceId(S_1).withStreamPartitionMsgOffset(_s3Offset.toString()) + .withSegmentName(_segmentNameStr).withReason(SegmentCompletionProtocol.REASON_FORCE_COMMIT_MESSAGE_RECEIVED); + response = _segmentCompletionMgr.segmentConsumed(params); + // S1 is told to discard since S2 already uploaded + Assert.assertEquals(response.getStatus(), ControllerResponseStatus.DISCARD); + } + @Test public void testWinnerOnRowLimit() throws Exception { diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/segment/FlushThresholdUpdaterTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/segment/FlushThresholdUpdaterTest.java index 0e45731f94a9..9ae04827b608 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/segment/FlushThresholdUpdaterTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/segment/FlushThresholdUpdaterTest.java @@ -18,15 +18,10 @@ */ package org.apache.pinot.controller.helix.core.realtime.segment; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.List; import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; import org.apache.pinot.common.utils.LLCSegmentName; import org.apache.pinot.spi.stream.LongMsgOffset; -import org.apache.pinot.spi.stream.PartitionGroupMetadata; -import org.apache.pinot.spi.stream.PartitionLevelStreamConfig; import org.apache.pinot.spi.stream.StreamConfig; import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.testng.annotations.Test; @@ -58,50 +53,64 @@ public class FlushThresholdUpdaterTest { public void testFlushThresholdUpdateManager() { FlushThresholdUpdateManager flushThresholdUpdateManager = new FlushThresholdUpdateManager(); - // Flush threshold rows larger than 0 - DefaultFlushThresholdUpdater should be returned - FlushThresholdUpdater defaultFlushThresholdUpdater = flushThresholdUpdateManager - .getFlushThresholdUpdater(mockStreamConfig(StreamConfig.DEFAULT_FLUSH_THRESHOLD_ROWS)); - assertTrue(defaultFlushThresholdUpdater instanceof DefaultFlushThresholdUpdater); - assertEquals(((DefaultFlushThresholdUpdater) defaultFlushThresholdUpdater).getTableFlushSize(), + // None of the flush threshold set - DefaultFlushThresholdUpdater should be returned + FlushThresholdUpdater flushThresholdUpdater = + flushThresholdUpdateManager.getFlushThresholdUpdater(mockStreamConfig(-1, -1, -1)); + assertTrue(flushThresholdUpdater instanceof DefaultFlushThresholdUpdater); + assertEquals(((DefaultFlushThresholdUpdater) flushThresholdUpdater).getTableFlushSize(), StreamConfig.DEFAULT_FLUSH_THRESHOLD_ROWS); + // Flush threshold rows larger than 0 - DefaultFlushThresholdUpdater should be returned + flushThresholdUpdater = flushThresholdUpdateManager.getFlushThresholdUpdater(mockStreamConfig(1234, -1, -1)); + assertTrue(flushThresholdUpdater instanceof DefaultFlushThresholdUpdater); + assertEquals(((DefaultFlushThresholdUpdater) flushThresholdUpdater).getTableFlushSize(), 1234); + + // Flush threshold segment rows larger than 0 - FixedFlushThresholdUpdater should be returned + flushThresholdUpdater = flushThresholdUpdateManager.getFlushThresholdUpdater(mockStreamConfig(-1, 1234, -1)); + assertTrue(flushThresholdUpdater instanceof FixedFlushThresholdUpdater); + // Flush threshold rows set to 0 - SegmentSizeBasedFlushThresholdUpdater should be returned - PartitionLevelStreamConfig autotuneStreamConfig = mockDefaultAutotuneStreamConfig(); - FlushThresholdUpdater autotuneFlushThresholdUpdater = - flushThresholdUpdateManager.getFlushThresholdUpdater(autotuneStreamConfig); - assertTrue(autotuneFlushThresholdUpdater instanceof SegmentSizeBasedFlushThresholdUpdater); + FlushThresholdUpdater segmentBasedflushThresholdUpdater = + flushThresholdUpdateManager.getFlushThresholdUpdater(mockStreamConfig(0, -1, -1)); + assertTrue(segmentBasedflushThresholdUpdater instanceof SegmentSizeBasedFlushThresholdUpdater); - // Call again with flush threshold rows set to 0 - same Object should be returned - assertSame(flushThresholdUpdateManager.getFlushThresholdUpdater(mockAutotuneStreamConfig(10000L, 10000L, 10000)), - autotuneFlushThresholdUpdater); + // Flush threshold segment size larger than 0 - SegmentSizeBasedFlushThresholdUpdater should be returned + flushThresholdUpdater = flushThresholdUpdateManager.getFlushThresholdUpdater(mockStreamConfig(0, -1, 1234)); + assertSame(flushThresholdUpdater, segmentBasedflushThresholdUpdater); - // Call again with flush threshold rows set larger than 0 - DefaultFlushThresholdUpdater should be returned - defaultFlushThresholdUpdater = flushThresholdUpdateManager.getFlushThresholdUpdater(mockStreamConfig(10000)); - assertTrue(defaultFlushThresholdUpdater instanceof DefaultFlushThresholdUpdater); - assertEquals(((DefaultFlushThresholdUpdater) defaultFlushThresholdUpdater).getTableFlushSize(), 10000); + // Flush threshold rows set larger than 0 - DefaultFlushThresholdUpdater should be returned + flushThresholdUpdater = flushThresholdUpdateManager.getFlushThresholdUpdater(mockStreamConfig(12345, -1, -1)); + assertTrue(flushThresholdUpdater instanceof DefaultFlushThresholdUpdater); + assertEquals(((DefaultFlushThresholdUpdater) flushThresholdUpdater).getTableFlushSize(), 12345); // Call again with flush threshold rows set to 0 - a different Object should be returned - assertNotSame(flushThresholdUpdateManager.getFlushThresholdUpdater(autotuneStreamConfig), - autotuneFlushThresholdUpdater); + flushThresholdUpdater = flushThresholdUpdateManager.getFlushThresholdUpdater(mockStreamConfig(0, -1, -1)); + assertTrue(flushThresholdUpdater instanceof SegmentSizeBasedFlushThresholdUpdater); + assertNotSame(flushThresholdUpdater, segmentBasedflushThresholdUpdater); + segmentBasedflushThresholdUpdater = flushThresholdUpdater; // Clear the updater flushThresholdUpdateManager.clearFlushThresholdUpdater(REALTIME_TABLE_NAME); // Call again with flush threshold rows set to 0 - a different Object should be returned - assertNotSame(flushThresholdUpdateManager.getFlushThresholdUpdater(autotuneStreamConfig), - autotuneFlushThresholdUpdater); + flushThresholdUpdater = flushThresholdUpdateManager.getFlushThresholdUpdater(mockStreamConfig(0, -1, -1)); + assertTrue(flushThresholdUpdater instanceof SegmentSizeBasedFlushThresholdUpdater); + assertNotSame(flushThresholdUpdater, segmentBasedflushThresholdUpdater); } - private PartitionLevelStreamConfig mockStreamConfig(int flushThresholdRows) { - PartitionLevelStreamConfig streamConfig = mock(PartitionLevelStreamConfig.class); + private StreamConfig mockStreamConfig(int flushThresholdRows, int flushThresholdSegmentRows, + long flushThresholdSegmentSize) { + StreamConfig streamConfig = mock(StreamConfig.class); when(streamConfig.getTableNameWithType()).thenReturn(REALTIME_TABLE_NAME); when(streamConfig.getFlushThresholdRows()).thenReturn(flushThresholdRows); + when(streamConfig.getFlushThresholdSegmentRows()).thenReturn(flushThresholdSegmentRows); + when(streamConfig.getFlushThresholdSegmentSizeBytes()).thenReturn(flushThresholdSegmentSize); return streamConfig; } - private PartitionLevelStreamConfig mockAutotuneStreamConfig(long flushSegmentDesiredSizeBytes, - long flushThresholdTimeMillis, int flushAutotuneInitialRows) { - PartitionLevelStreamConfig streamConfig = mock(PartitionLevelStreamConfig.class); + private StreamConfig mockAutotuneStreamConfig(long flushSegmentDesiredSizeBytes, long flushThresholdTimeMillis, + int flushAutotuneInitialRows) { + StreamConfig streamConfig = mock(StreamConfig.class); when(streamConfig.getTableNameWithType()).thenReturn(REALTIME_TABLE_NAME); when(streamConfig.getFlushThresholdRows()).thenReturn(0); when(streamConfig.getFlushThresholdSegmentSizeBytes()).thenReturn(flushSegmentDesiredSizeBytes); @@ -110,7 +119,7 @@ private PartitionLevelStreamConfig mockAutotuneStreamConfig(long flushSegmentDes return streamConfig; } - private PartitionLevelStreamConfig mockDefaultAutotuneStreamConfig() { + private StreamConfig mockDefaultAutotuneStreamConfig() { return mockAutotuneStreamConfig(StreamConfig.DEFAULT_FLUSH_THRESHOLD_SEGMENT_SIZE_BYTES, StreamConfig.DEFAULT_FLUSH_THRESHOLD_TIME_MILLIS, StreamConfig.DEFAULT_FLUSH_AUTOTUNE_INITIAL_ROWS); } @@ -123,21 +132,20 @@ private PartitionLevelStreamConfig mockDefaultAutotuneStreamConfig() { */ @Test public void testSegmentSizeBasedFlushThreshold() { - PartitionLevelStreamConfig streamConfig = mockDefaultAutotuneStreamConfig(); + StreamConfig streamConfig = mockDefaultAutotuneStreamConfig(); long desiredSegmentSizeBytes = streamConfig.getFlushThresholdSegmentSizeBytes(); long segmentSizeLowerLimit = (long) (desiredSegmentSizeBytes * 0.99); long segmentSizeHigherLimit = (long) (desiredSegmentSizeBytes * 1.01); - for (long[] segmentSizesMB : Arrays - .asList(EXPONENTIAL_GROWTH_SEGMENT_SIZES_MB, LOGARITHMIC_GROWTH_SEGMENT_SIZES_MB, STEPS_SEGMENT_SIZES_MB)) { + for (long[] segmentSizesMB : Arrays.asList(EXPONENTIAL_GROWTH_SEGMENT_SIZES_MB, LOGARITHMIC_GROWTH_SEGMENT_SIZES_MB, + STEPS_SEGMENT_SIZES_MB)) { SegmentSizeBasedFlushThresholdUpdater flushThresholdUpdater = new SegmentSizeBasedFlushThresholdUpdater(); // Start consumption SegmentZKMetadata newSegmentZKMetadata = getNewSegmentZKMetadata(0); CommittingSegmentDescriptor committingSegmentDescriptor = getCommittingSegmentDescriptor(0L); - flushThresholdUpdater - .updateFlushThreshold(streamConfig, newSegmentZKMetadata, committingSegmentDescriptor, null, 1, - Collections.emptyList()); + flushThresholdUpdater.updateFlushThreshold(streamConfig, newSegmentZKMetadata, committingSegmentDescriptor, null, + 1); assertEquals(newSegmentZKMetadata.getSizeThresholdToFlushSegment(), streamConfig.getFlushAutotuneInitialRows()); int numRuns = 500; @@ -149,7 +157,7 @@ public void testSegmentSizeBasedFlushThreshold() { SegmentZKMetadata committingSegmentZKMetadata = getCommittingSegmentZKMetadata(System.currentTimeMillis(), numRowsConsumed, numRowsConsumed); flushThresholdUpdater.updateFlushThreshold(streamConfig, newSegmentZKMetadata, committingSegmentDescriptor, - committingSegmentZKMetadata, 1, Collections.emptyList()); + committingSegmentZKMetadata, 1); // Assert that segment size is in limits if (run > checkRunsAfter) { @@ -161,21 +169,20 @@ public void testSegmentSizeBasedFlushThreshold() { @Test public void testSegmentSizeBasedFlushThresholdMinPartition() { - PartitionLevelStreamConfig streamConfig = mockDefaultAutotuneStreamConfig(); + StreamConfig streamConfig = mockDefaultAutotuneStreamConfig(); long desiredSegmentSizeBytes = streamConfig.getFlushThresholdSegmentSizeBytes(); long segmentSizeLowerLimit = (long) (desiredSegmentSizeBytes * 0.99); long segmentSizeHigherLimit = (long) (desiredSegmentSizeBytes * 1.01); - for (long[] segmentSizesMB : Arrays - .asList(EXPONENTIAL_GROWTH_SEGMENT_SIZES_MB, LOGARITHMIC_GROWTH_SEGMENT_SIZES_MB, STEPS_SEGMENT_SIZES_MB)) { + for (long[] segmentSizesMB : Arrays.asList(EXPONENTIAL_GROWTH_SEGMENT_SIZES_MB, LOGARITHMIC_GROWTH_SEGMENT_SIZES_MB, + STEPS_SEGMENT_SIZES_MB)) { SegmentSizeBasedFlushThresholdUpdater flushThresholdUpdater = new SegmentSizeBasedFlushThresholdUpdater(); // Start consumption SegmentZKMetadata newSegmentZKMetadata = getNewSegmentZKMetadata(1); CommittingSegmentDescriptor committingSegmentDescriptor = getCommittingSegmentDescriptor(0L); - flushThresholdUpdater - .updateFlushThreshold(streamConfig, newSegmentZKMetadata, committingSegmentDescriptor, null, 1, - getPartitionGroupMetadataList(3, 1)); + flushThresholdUpdater.updateFlushThreshold(streamConfig, newSegmentZKMetadata, committingSegmentDescriptor, null, + 1); assertEquals(newSegmentZKMetadata.getSizeThresholdToFlushSegment(), streamConfig.getFlushAutotuneInitialRows()); int numRuns = 500; @@ -187,7 +194,7 @@ public void testSegmentSizeBasedFlushThresholdMinPartition() { SegmentZKMetadata committingSegmentZKMetadata = getCommittingSegmentZKMetadata(System.currentTimeMillis(), numRowsConsumed, numRowsConsumed); flushThresholdUpdater.updateFlushThreshold(streamConfig, newSegmentZKMetadata, committingSegmentDescriptor, - committingSegmentZKMetadata, 1, getPartitionGroupMetadataList(3, 1)); + committingSegmentZKMetadata, 1); // Assert that segment size is in limits if (run > checkRunsAfter) { @@ -202,16 +209,6 @@ private SegmentZKMetadata getNewSegmentZKMetadata(int partitionId) { new LLCSegmentName(RAW_TABLE_NAME, partitionId, 0, System.currentTimeMillis()).getSegmentName()); } - private List getPartitionGroupMetadataList(int numPartitions, int startPartitionId) { - List newPartitionGroupMetadataList = new ArrayList<>(); - - for (int i = 0; i < numPartitions; i++) { - newPartitionGroupMetadataList.add(new PartitionGroupMetadata(startPartitionId + i, null)); - } - - return newPartitionGroupMetadataList; - } - private CommittingSegmentDescriptor getCommittingSegmentDescriptor(long segmentSizeBytes) { return new CommittingSegmentDescriptor(null, new LongMsgOffset(0).toString(), segmentSizeBytes); } @@ -240,13 +237,13 @@ private long getSegmentSizeBytes(int numRowsConsumed, long[] segmentSizesMB) { @Test public void testTimeThreshold() { SegmentSizeBasedFlushThresholdUpdater flushThresholdUpdater = new SegmentSizeBasedFlushThresholdUpdater(); - PartitionLevelStreamConfig streamConfig = mockDefaultAutotuneStreamConfig(); + StreamConfig streamConfig = mockDefaultAutotuneStreamConfig(); // Start consumption SegmentZKMetadata newSegmentZKMetadata = getNewSegmentZKMetadata(0); CommittingSegmentDescriptor committingSegmentDescriptor = getCommittingSegmentDescriptor(0L); - flushThresholdUpdater.updateFlushThreshold(streamConfig, newSegmentZKMetadata, committingSegmentDescriptor, null, 1, - Collections.emptyList()); + flushThresholdUpdater.updateFlushThreshold(streamConfig, newSegmentZKMetadata, committingSegmentDescriptor, null, + 1); int sizeThreshold = newSegmentZKMetadata.getSizeThresholdToFlushSegment(); // First segment consumes rows less than the threshold @@ -255,7 +252,7 @@ public void testTimeThreshold() { SegmentZKMetadata committingSegmentZKMetadata = getCommittingSegmentZKMetadata(System.currentTimeMillis(), sizeThreshold, numRowsConsumed); flushThresholdUpdater.updateFlushThreshold(streamConfig, newSegmentZKMetadata, committingSegmentDescriptor, - committingSegmentZKMetadata, 1, Collections.emptyList()); + committingSegmentZKMetadata, 1); sizeThreshold = newSegmentZKMetadata.getSizeThresholdToFlushSegment(); assertEquals(sizeThreshold, (int) (numRowsConsumed * SegmentFlushThresholdComputer.ROWS_MULTIPLIER_WHEN_TIME_THRESHOLD_HIT)); @@ -265,7 +262,7 @@ public void testTimeThreshold() { committingSegmentZKMetadata = getCommittingSegmentZKMetadata(System.currentTimeMillis(), sizeThreshold, numRowsConsumed); flushThresholdUpdater.updateFlushThreshold(streamConfig, newSegmentZKMetadata, committingSegmentDescriptor, - committingSegmentZKMetadata, 1, Collections.emptyList()); + committingSegmentZKMetadata, 1); assertNotEquals(newSegmentZKMetadata.getSizeThresholdToFlushSegment(), (int) (numRowsConsumed * SegmentFlushThresholdComputer.ROWS_MULTIPLIER_WHEN_TIME_THRESHOLD_HIT)); } @@ -273,13 +270,13 @@ public void testTimeThreshold() { @Test public void testMinThreshold() { SegmentSizeBasedFlushThresholdUpdater flushThresholdUpdater = new SegmentSizeBasedFlushThresholdUpdater(); - PartitionLevelStreamConfig streamConfig = mockDefaultAutotuneStreamConfig(); + StreamConfig streamConfig = mockDefaultAutotuneStreamConfig(); // Start consumption SegmentZKMetadata newSegmentZKMetadata = getNewSegmentZKMetadata(0); CommittingSegmentDescriptor committingSegmentDescriptor = getCommittingSegmentDescriptor(0L); - flushThresholdUpdater.updateFlushThreshold(streamConfig, newSegmentZKMetadata, committingSegmentDescriptor, null, 1, - Collections.emptyList()); + flushThresholdUpdater.updateFlushThreshold(streamConfig, newSegmentZKMetadata, committingSegmentDescriptor, null, + 1); int sizeThreshold = newSegmentZKMetadata.getSizeThresholdToFlushSegment(); // First segment only consumed 15 rows, so next segment should have size threshold of 10_000 @@ -288,7 +285,7 @@ public void testMinThreshold() { SegmentZKMetadata committingSegmentZKMetadata = getCommittingSegmentZKMetadata(System.currentTimeMillis(), sizeThreshold, numRowsConsumed); flushThresholdUpdater.updateFlushThreshold(streamConfig, newSegmentZKMetadata, committingSegmentDescriptor, - committingSegmentZKMetadata, 1, Collections.emptyList()); + committingSegmentZKMetadata, 1); sizeThreshold = newSegmentZKMetadata.getSizeThresholdToFlushSegment(); assertEquals(sizeThreshold, SegmentFlushThresholdComputer.MINIMUM_NUM_ROWS_THRESHOLD); @@ -297,63 +294,11 @@ public void testMinThreshold() { committingSegmentZKMetadata = getCommittingSegmentZKMetadata(System.currentTimeMillis(), sizeThreshold, numRowsConsumed); flushThresholdUpdater.updateFlushThreshold(streamConfig, newSegmentZKMetadata, committingSegmentDescriptor, - committingSegmentZKMetadata, 1, Collections.emptyList()); + committingSegmentZKMetadata, 1); sizeThreshold = newSegmentZKMetadata.getSizeThresholdToFlushSegment(); assertEquals(sizeThreshold, SegmentFlushThresholdComputer.MINIMUM_NUM_ROWS_THRESHOLD); } - @Test - public void testNonZeroPartitionUpdates() { - SegmentSizeBasedFlushThresholdUpdater flushThresholdUpdater = new SegmentSizeBasedFlushThresholdUpdater(); - PartitionLevelStreamConfig streamConfig = mockDefaultAutotuneStreamConfig(); - - // Start consumption for 2 partitions - SegmentZKMetadata newSegmentZKMetadataForPartition0 = getNewSegmentZKMetadata(0); - SegmentZKMetadata newSegmentZKMetadataForPartition1 = getNewSegmentZKMetadata(1); - CommittingSegmentDescriptor committingSegmentDescriptor = getCommittingSegmentDescriptor(0L); - flushThresholdUpdater - .updateFlushThreshold(streamConfig, newSegmentZKMetadataForPartition0, committingSegmentDescriptor, null, 1, - Collections.emptyList()); - flushThresholdUpdater - .updateFlushThreshold(streamConfig, newSegmentZKMetadataForPartition1, committingSegmentDescriptor, null, 1, - Collections.emptyList()); - int sizeThresholdForPartition0 = newSegmentZKMetadataForPartition0.getSizeThresholdToFlushSegment(); - int sizeThresholdForPartition1 = newSegmentZKMetadataForPartition1.getSizeThresholdToFlushSegment(); - double sizeRatio = flushThresholdUpdater.getLatestSegmentRowsToSizeRatio(); - assertEquals(sizeThresholdForPartition0, streamConfig.getFlushAutotuneInitialRows()); - assertEquals(sizeThresholdForPartition1, streamConfig.getFlushAutotuneInitialRows()); - assertEquals(sizeRatio, 0.0); - - // First segment from partition 1 should change the size ratio - committingSegmentDescriptor = getCommittingSegmentDescriptor(128_000_000L); - SegmentZKMetadata committingSegmentZKMetadata = - getCommittingSegmentZKMetadata(System.currentTimeMillis(), sizeThresholdForPartition1, - sizeThresholdForPartition1); - flushThresholdUpdater - .updateFlushThreshold(streamConfig, newSegmentZKMetadataForPartition1, committingSegmentDescriptor, - committingSegmentZKMetadata, 1, Collections.emptyList()); - sizeThresholdForPartition1 = newSegmentZKMetadataForPartition1.getSizeThresholdToFlushSegment(); - sizeRatio = flushThresholdUpdater.getLatestSegmentRowsToSizeRatio(); - assertTrue(sizeRatio > 0.0); - - // Second segment update from partition 1 should not change the size ratio - committingSegmentDescriptor = getCommittingSegmentDescriptor(256_000_000L); - committingSegmentZKMetadata = getCommittingSegmentZKMetadata(System.currentTimeMillis(), sizeThresholdForPartition1, - sizeThresholdForPartition1); - flushThresholdUpdater - .updateFlushThreshold(streamConfig, newSegmentZKMetadataForPartition1, committingSegmentDescriptor, - committingSegmentZKMetadata, 1, Collections.emptyList()); - assertEquals(flushThresholdUpdater.getLatestSegmentRowsToSizeRatio(), sizeRatio); - - // First segment update from partition 0 should change the size ratio - committingSegmentZKMetadata = getCommittingSegmentZKMetadata(System.currentTimeMillis(), sizeThresholdForPartition0, - sizeThresholdForPartition0); - flushThresholdUpdater - .updateFlushThreshold(streamConfig, newSegmentZKMetadataForPartition0, committingSegmentDescriptor, - committingSegmentZKMetadata, 1, Collections.emptyList()); - assertNotEquals(flushThresholdUpdater.getLatestSegmentRowsToSizeRatio(), sizeRatio); - } - @Test public void testSegmentSizeBasedUpdaterWithModifications() { SegmentSizeBasedFlushThresholdUpdater flushThresholdUpdater = new SegmentSizeBasedFlushThresholdUpdater(); @@ -362,14 +307,14 @@ public void testSegmentSizeBasedUpdaterWithModifications() { long flushSegmentDesiredSizeBytes = StreamConfig.DEFAULT_FLUSH_THRESHOLD_SEGMENT_SIZE_BYTES / 2; long flushThresholdTimeMillis = StreamConfig.DEFAULT_FLUSH_THRESHOLD_TIME_MILLIS / 2; int flushAutotuneInitialRows = StreamConfig.DEFAULT_FLUSH_AUTOTUNE_INITIAL_ROWS / 2; - PartitionLevelStreamConfig streamConfig = + StreamConfig streamConfig = mockAutotuneStreamConfig(flushSegmentDesiredSizeBytes, flushThresholdTimeMillis, flushAutotuneInitialRows); // Start consumption SegmentZKMetadata newSegmentZKMetadata = getNewSegmentZKMetadata(0); CommittingSegmentDescriptor committingSegmentDescriptor = getCommittingSegmentDescriptor(0L); - flushThresholdUpdater.updateFlushThreshold(streamConfig, newSegmentZKMetadata, committingSegmentDescriptor, null, 1, - Collections.emptyList()); + flushThresholdUpdater.updateFlushThreshold(streamConfig, newSegmentZKMetadata, committingSegmentDescriptor, null, + 1); int sizeThreshold = newSegmentZKMetadata.getSizeThresholdToFlushSegment(); assertEquals(sizeThreshold, flushAutotuneInitialRows); @@ -383,7 +328,7 @@ public void testSegmentSizeBasedUpdaterWithModifications() { SegmentZKMetadata committingSegmentZKMetadata = getCommittingSegmentZKMetadata(creationTime, sizeThreshold, numRowsConsumed); flushThresholdUpdater.updateFlushThreshold(streamConfig, newSegmentZKMetadata, committingSegmentDescriptor, - committingSegmentZKMetadata, 1, Collections.emptyList()); + committingSegmentZKMetadata, 1); sizeThreshold = newSegmentZKMetadata.getSizeThresholdToFlushSegment(); assertTrue(sizeThreshold > numRowsConsumed); @@ -396,7 +341,7 @@ public void testSegmentSizeBasedUpdaterWithModifications() { mockAutotuneStreamConfig(flushSegmentDesiredSizeBytes, flushThresholdTimeMillis, flushAutotuneInitialRows); committingSegmentZKMetadata = getCommittingSegmentZKMetadata(creationTime, sizeThreshold, numRowsConsumed); flushThresholdUpdater.updateFlushThreshold(streamConfig, newSegmentZKMetadata, committingSegmentDescriptor, - committingSegmentZKMetadata, 1, Collections.emptyList()); + committingSegmentZKMetadata, 1); sizeThreshold = newSegmentZKMetadata.getSizeThresholdToFlushSegment(); assertTrue(sizeThreshold < numRowsConsumed); @@ -407,7 +352,7 @@ public void testSegmentSizeBasedUpdaterWithModifications() { committingSegmentDescriptor = getCommittingSegmentDescriptor(committingSegmentSize); committingSegmentZKMetadata = getCommittingSegmentZKMetadata(creationTime, sizeThreshold, numRowsConsumed); flushThresholdUpdater.updateFlushThreshold(streamConfig, newSegmentZKMetadata, committingSegmentDescriptor, - committingSegmentZKMetadata, 1, Collections.emptyList()); + committingSegmentZKMetadata, 1); sizeThreshold = newSegmentZKMetadata.getSizeThresholdToFlushSegment(); assertEquals(sizeThreshold, (long) (numRowsConsumed * SegmentFlushThresholdComputer.ROWS_MULTIPLIER_WHEN_TIME_THRESHOLD_HIT)); @@ -420,7 +365,7 @@ public void testSegmentSizeBasedUpdaterWithModifications() { mockAutotuneStreamConfig(flushSegmentDesiredSizeBytes, flushThresholdTimeMillis, flushAutotuneInitialRows); committingSegmentZKMetadata = getCommittingSegmentZKMetadata(creationTime, sizeThreshold, numRowsConsumed); flushThresholdUpdater.updateFlushThreshold(streamConfig, newSegmentZKMetadata, committingSegmentDescriptor, - committingSegmentZKMetadata, 1, Collections.emptyList()); + committingSegmentZKMetadata, 1); sizeThreshold = newSegmentZKMetadata.getSizeThresholdToFlushSegment(); assertTrue(sizeThreshold < numRowsConsumed); } diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/segment/SegmentFlushThresholdComputerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/segment/SegmentFlushThresholdComputerTest.java index 6cb21af4ff7e..a9d1c272217d 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/segment/SegmentFlushThresholdComputerTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/realtime/segment/SegmentFlushThresholdComputerTest.java @@ -20,36 +20,31 @@ import java.time.Clock; import java.time.ZoneId; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.TimeUnit; import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; -import org.apache.pinot.spi.stream.PartitionGroupMetadata; -import org.apache.pinot.spi.stream.PartitionLevelStreamConfig; +import org.apache.pinot.spi.stream.StreamConfig; import org.testng.annotations.Test; import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.apache.pinot.common.protocols.SegmentCompletionProtocol.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; public class SegmentFlushThresholdComputerTest { + @Test public void testUseAutoTuneInitialRowsIfFirstSegmentInPartition() { int autoTuneInitialRows = 1_000; SegmentFlushThresholdComputer computer = new SegmentFlushThresholdComputer(); - PartitionLevelStreamConfig streamConfig = mock(PartitionLevelStreamConfig.class); + StreamConfig streamConfig = mock(StreamConfig.class); when(streamConfig.getFlushAutotuneInitialRows()).thenReturn(autoTuneInitialRows); CommittingSegmentDescriptor committingSegmentDescriptor = mock(CommittingSegmentDescriptor.class); - SegmentZKMetadata committingSegmentZKMetadata = null; - List partitionGroupMetadataList = new ArrayList<>(); - int threshold = computer.computeThreshold( - streamConfig, committingSegmentDescriptor, committingSegmentZKMetadata, - partitionGroupMetadataList, "newSegmentName"); + int threshold = computer.computeThreshold(streamConfig, committingSegmentDescriptor, null, "newSegmentName"); assertEquals(threshold, autoTuneInitialRows); } @@ -58,19 +53,15 @@ public void testUseAutoTuneInitialRowsIfFirstSegmentInPartition() { public void testUseLastSegmentSizeTimesRatioIfFirstSegmentInPartitionAndNewPartitionGroup() { double segmentRowsToSizeRatio = 1.5; long segmentSizeBytes = 20000L; - SegmentFlushThresholdComputer computer = new SegmentFlushThresholdComputer( - Clock.systemUTC(), segmentRowsToSizeRatio); + SegmentFlushThresholdComputer computer = + new SegmentFlushThresholdComputer(Clock.systemUTC(), segmentRowsToSizeRatio); - PartitionLevelStreamConfig streamConfig = mock(PartitionLevelStreamConfig.class); + StreamConfig streamConfig = mock(StreamConfig.class); when(streamConfig.getFlushThresholdSegmentSizeBytes()).thenReturn(segmentSizeBytes); CommittingSegmentDescriptor committingSegmentDescriptor = mock(CommittingSegmentDescriptor.class); - SegmentZKMetadata committingSegmentZKMetadata = null; - List partitionGroupMetadataList = new ArrayList<>(); - int threshold = computer.computeThreshold( - streamConfig, committingSegmentDescriptor, committingSegmentZKMetadata, - partitionGroupMetadataList, "newSegmentName"); + int threshold = computer.computeThreshold(streamConfig, committingSegmentDescriptor, null, "newSegmentName"); // segmentSize * 1.5 // 20000 * 1.5 @@ -81,19 +72,15 @@ public void testUseLastSegmentSizeTimesRatioIfFirstSegmentInPartitionAndNewParti public void testUseLastSegmentSizeTimesRatioIfFirstSegmentInPartitionAndNewPartitionGroupMinimumSize10000Rows() { double segmentRowsToSizeRatio = 1.5; long segmentSizeBytes = 2000L; - SegmentFlushThresholdComputer computer = new SegmentFlushThresholdComputer( - Clock.systemUTC(), segmentRowsToSizeRatio); + SegmentFlushThresholdComputer computer = + new SegmentFlushThresholdComputer(Clock.systemUTC(), segmentRowsToSizeRatio); - PartitionLevelStreamConfig streamConfig = mock(PartitionLevelStreamConfig.class); + StreamConfig streamConfig = mock(StreamConfig.class); when(streamConfig.getFlushThresholdSegmentSizeBytes()).thenReturn(segmentSizeBytes); CommittingSegmentDescriptor committingSegmentDescriptor = mock(CommittingSegmentDescriptor.class); - SegmentZKMetadata committingSegmentZKMetadata = null; - List partitionGroupMetadataList = new ArrayList<>(); - int threshold = computer.computeThreshold( - streamConfig, committingSegmentDescriptor, committingSegmentZKMetadata, - partitionGroupMetadataList, "newSegmentName"); + int threshold = computer.computeThreshold(streamConfig, committingSegmentDescriptor, null, "newSegmentName"); assertEquals(threshold, 10000); } @@ -104,7 +91,7 @@ public void testUseLastSegmentsThresholdIfSegmentSizeMissing() { int segmentSizeThreshold = 5_000; SegmentFlushThresholdComputer computer = new SegmentFlushThresholdComputer(); - PartitionLevelStreamConfig streamConfig = mock(PartitionLevelStreamConfig.class); + StreamConfig streamConfig = mock(StreamConfig.class); when(streamConfig.getFlushThresholdTimeMillis()).thenReturn(123L); CommittingSegmentDescriptor committingSegmentDescriptor = mock(CommittingSegmentDescriptor.class); @@ -113,14 +100,34 @@ public void testUseLastSegmentsThresholdIfSegmentSizeMissing() { SegmentZKMetadata committingSegmentZKMetadata = mock(SegmentZKMetadata.class); when(committingSegmentZKMetadata.getSizeThresholdToFlushSegment()).thenReturn(segmentSizeThreshold); - List partitionGroupMetadataList = new ArrayList<>(); - int threshold = computer.computeThreshold( - streamConfig, committingSegmentDescriptor, committingSegmentZKMetadata, - partitionGroupMetadataList, "newSegmentName"); + int threshold = computer.computeThreshold(streamConfig, committingSegmentDescriptor, committingSegmentZKMetadata, + "newSegmentName"); assertEquals(threshold, segmentSizeThreshold); } + @Test + public void testUseLastSegmentsThresholdIfSegmentIsCommittingDueToForceCommit() { + long committingSegmentSizeBytes = 500_000L; + int committingSegmentSizeThreshold = 25_000; + SegmentFlushThresholdComputer computer = new SegmentFlushThresholdComputer(); + + CommittingSegmentDescriptor committingSegmentDescriptor = mock(CommittingSegmentDescriptor.class); + when(committingSegmentDescriptor.getSegmentSizeBytes()).thenReturn(committingSegmentSizeBytes); + when(committingSegmentDescriptor.getStopReason()).thenReturn(REASON_FORCE_COMMIT_MESSAGE_RECEIVED); + + SegmentZKMetadata committingSegmentZKMetadata = mock(SegmentZKMetadata.class); + when(committingSegmentZKMetadata.getSizeThresholdToFlushSegment()).thenReturn(committingSegmentSizeThreshold); + + StreamConfig streamConfig = mock(StreamConfig.class); + + int newSegmentSizeThreshold = + computer.computeThreshold(streamConfig, committingSegmentDescriptor, committingSegmentZKMetadata, + "newSegmentName"); + + assertEquals(newSegmentSizeThreshold, committingSegmentSizeThreshold); + } + @Test public void testApplyMultiplierToTotalDocsWhenTimeThresholdNotReached() { long currentTime = 1640216032391L; @@ -128,10 +135,9 @@ public void testApplyMultiplierToTotalDocsWhenTimeThresholdNotReached() { SegmentFlushThresholdComputer computer = new SegmentFlushThresholdComputer(clock); - PartitionLevelStreamConfig streamConfig = mock(PartitionLevelStreamConfig.class); + StreamConfig streamConfig = mock(StreamConfig.class); when(streamConfig.getFlushThresholdSegmentSizeBytes()).thenReturn(300_0000L); - when(streamConfig.getFlushThresholdTimeMillis()).thenReturn( - MILLISECONDS.convert(6, TimeUnit.HOURS)); + when(streamConfig.getFlushThresholdTimeMillis()).thenReturn(MILLISECONDS.convert(6, TimeUnit.HOURS)); CommittingSegmentDescriptor committingSegmentDescriptor = mock(CommittingSegmentDescriptor.class); when(committingSegmentDescriptor.getSegmentSizeBytes()).thenReturn(200_0000L); @@ -142,10 +148,8 @@ public void testApplyMultiplierToTotalDocsWhenTimeThresholdNotReached() { when(committingSegmentZKMetadata.getCreationTime()).thenReturn( currentTime - MILLISECONDS.convert(1, TimeUnit.HOURS)); - List partitionGroupMetadataList = new ArrayList<>(); - int threshold = computer.computeThreshold( - streamConfig, committingSegmentDescriptor, committingSegmentZKMetadata, - partitionGroupMetadataList, "events3__0__0__20211222T1646Z"); + int threshold = computer.computeThreshold(streamConfig, committingSegmentDescriptor, committingSegmentZKMetadata, + "events3__0__0__20211222T1646Z"); // totalDocs * 1.1 // 10000 * 1.1 @@ -159,10 +163,9 @@ public void testApplyMultiplierToAdjustedTotalDocsWhenTimeThresholdIsReached() { SegmentFlushThresholdComputer computer = new SegmentFlushThresholdComputer(clock); - PartitionLevelStreamConfig streamConfig = mock(PartitionLevelStreamConfig.class); + StreamConfig streamConfig = mock(StreamConfig.class); when(streamConfig.getFlushThresholdSegmentSizeBytes()).thenReturn(300_0000L); - when(streamConfig.getFlushThresholdTimeMillis()).thenReturn( - MILLISECONDS.convert(1, TimeUnit.HOURS)); + when(streamConfig.getFlushThresholdTimeMillis()).thenReturn(MILLISECONDS.convert(1, TimeUnit.HOURS)); CommittingSegmentDescriptor committingSegmentDescriptor = mock(CommittingSegmentDescriptor.class); when(committingSegmentDescriptor.getSegmentSizeBytes()).thenReturn(200_0000L); @@ -173,10 +176,8 @@ public void testApplyMultiplierToAdjustedTotalDocsWhenTimeThresholdIsReached() { when(committingSegmentZKMetadata.getCreationTime()).thenReturn( currentTime - MILLISECONDS.convert(2, TimeUnit.HOURS)); - List partitionGroupMetadataList = new ArrayList<>(); - int threshold = computer.computeThreshold( - streamConfig, committingSegmentDescriptor, committingSegmentZKMetadata, - partitionGroupMetadataList, "events3__0__0__20211222T1646Z"); + int threshold = computer.computeThreshold(streamConfig, committingSegmentDescriptor, committingSegmentZKMetadata, + "events3__0__0__20211222T1646Z"); // (totalDocs / 2) * 1.1 // (30000 / 2) * 1.1 @@ -188,7 +189,7 @@ public void testApplyMultiplierToAdjustedTotalDocsWhenTimeThresholdIsReached() { public void testSegmentSizeTooSmall() { SegmentFlushThresholdComputer computer = new SegmentFlushThresholdComputer(); - PartitionLevelStreamConfig streamConfig = mock(PartitionLevelStreamConfig.class); + StreamConfig streamConfig = mock(StreamConfig.class); when(streamConfig.getFlushThresholdSegmentSizeBytes()).thenReturn(300_0000L); CommittingSegmentDescriptor committingSegmentDescriptor = mock(CommittingSegmentDescriptor.class); @@ -198,10 +199,8 @@ public void testSegmentSizeTooSmall() { when(committingSegmentZKMetadata.getTotalDocs()).thenReturn(30_000L); when(committingSegmentZKMetadata.getSizeThresholdToFlushSegment()).thenReturn(20_000); - List partitionGroupMetadataList = new ArrayList<>(); - int threshold = computer.computeThreshold( - streamConfig, committingSegmentDescriptor, committingSegmentZKMetadata, - partitionGroupMetadataList, "events3__0__0__20211222T1646Z"); + int threshold = computer.computeThreshold(streamConfig, committingSegmentDescriptor, committingSegmentZKMetadata, + "events3__0__0__20211222T1646Z"); // totalDocs / 2 // 30000 / 2 @@ -212,7 +211,7 @@ public void testSegmentSizeTooSmall() { public void testSegmentSizeTooBig() { SegmentFlushThresholdComputer computer = new SegmentFlushThresholdComputer(); - PartitionLevelStreamConfig streamConfig = mock(PartitionLevelStreamConfig.class); + StreamConfig streamConfig = mock(StreamConfig.class); when(streamConfig.getFlushThresholdSegmentSizeBytes()).thenReturn(500_0000L); CommittingSegmentDescriptor committingSegmentDescriptor = mock(CommittingSegmentDescriptor.class); @@ -222,10 +221,8 @@ public void testSegmentSizeTooBig() { when(committingSegmentZKMetadata.getTotalDocs()).thenReturn(30_000L); when(committingSegmentZKMetadata.getSizeThresholdToFlushSegment()).thenReturn(20_000); - List partitionGroupMetadataList = new ArrayList<>(); - int threshold = computer.computeThreshold( - streamConfig, committingSegmentDescriptor, committingSegmentZKMetadata, - partitionGroupMetadataList, "events3__0__0__20211222T1646Z"); + int threshold = computer.computeThreshold(streamConfig, committingSegmentDescriptor, committingSegmentZKMetadata, + "events3__0__0__20211222T1646Z"); // totalDocs + (totalDocs / 2) // 30000 + (30000 / 2) @@ -236,7 +233,7 @@ public void testSegmentSizeTooBig() { public void testSegmentSizeJustRight() { SegmentFlushThresholdComputer computer = new SegmentFlushThresholdComputer(); - PartitionLevelStreamConfig streamConfig = mock(PartitionLevelStreamConfig.class); + StreamConfig streamConfig = mock(StreamConfig.class); when(streamConfig.getFlushThresholdSegmentSizeBytes()).thenReturn(300_0000L); CommittingSegmentDescriptor committingSegmentDescriptor = mock(CommittingSegmentDescriptor.class); @@ -246,10 +243,8 @@ public void testSegmentSizeJustRight() { when(committingSegmentZKMetadata.getTotalDocs()).thenReturn(30_000L); when(committingSegmentZKMetadata.getSizeThresholdToFlushSegment()).thenReturn(20_000); - List partitionGroupMetadataList = new ArrayList<>(); - int threshold = computer.computeThreshold( - streamConfig, committingSegmentDescriptor, committingSegmentZKMetadata, - partitionGroupMetadataList, "events3__0__0__20211222T1646Z"); + int threshold = computer.computeThreshold(streamConfig, committingSegmentDescriptor, committingSegmentZKMetadata, + "events3__0__0__20211222T1646Z"); // (totalDocs / segmentSize) * flushThresholdSegmentSize // (30000 / 250000) * 300000 @@ -260,7 +255,7 @@ public void testSegmentSizeJustRight() { public void testNoRows() { SegmentFlushThresholdComputer computer = new SegmentFlushThresholdComputer(); - PartitionLevelStreamConfig streamConfig = mock(PartitionLevelStreamConfig.class); + StreamConfig streamConfig = mock(StreamConfig.class); when(streamConfig.getFlushThresholdSegmentSizeBytes()).thenReturn(300_0000L); CommittingSegmentDescriptor committingSegmentDescriptor = mock(CommittingSegmentDescriptor.class); @@ -270,10 +265,8 @@ public void testNoRows() { when(committingSegmentZKMetadata.getTotalDocs()).thenReturn(0L); when(committingSegmentZKMetadata.getSizeThresholdToFlushSegment()).thenReturn(0); - List partitionGroupMetadataList = new ArrayList<>(); - int threshold = computer.computeThreshold( - streamConfig, committingSegmentDescriptor, committingSegmentZKMetadata, - partitionGroupMetadataList, "events3__0__0__20211222T1646Z"); + int threshold = computer.computeThreshold(streamConfig, committingSegmentDescriptor, committingSegmentZKMetadata, + "events3__0__0__20211222T1646Z"); // max((totalDocs / segmentSize) * flushThresholdSegmentSize, 10000) // max(0, 10000) @@ -284,7 +277,7 @@ public void testNoRows() { public void testAdjustRowsToSizeRatio() { SegmentFlushThresholdComputer computer = new SegmentFlushThresholdComputer(); - PartitionLevelStreamConfig streamConfig = mock(PartitionLevelStreamConfig.class); + StreamConfig streamConfig = mock(StreamConfig.class); when(streamConfig.getFlushThresholdSegmentSizeBytes()).thenReturn(300_0000L); CommittingSegmentDescriptor committingSegmentDescriptor = mock(CommittingSegmentDescriptor.class); @@ -294,17 +287,15 @@ public void testAdjustRowsToSizeRatio() { when(committingSegmentZKMetadata.getTotalDocs()).thenReturn(30_000L, 50_000L); when(committingSegmentZKMetadata.getSizeThresholdToFlushSegment()).thenReturn(60_000); - List partitionGroupMetadataList = new ArrayList<>(); - computer.computeThreshold(streamConfig, committingSegmentDescriptor, committingSegmentZKMetadata, - partitionGroupMetadataList, "events3__0__0__20211222T1646Z"); + "events3__0__0__20211222T1646Z"); // (totalDocs / segmentSize) // (30000 / 200000) assertEquals(computer.getLatestSegmentRowsToSizeRatio(), 0.15); computer.computeThreshold(streamConfig, committingSegmentDescriptor, committingSegmentZKMetadata, - partitionGroupMetadataList, "events3__0__0__20211222T1646Z"); + "events3__0__0__20211222T1646Z"); // (0.1 * (totalDocs / segmentSize)) + (0.9 * lastRatio) // (0.1 * (50000 / 200000)) + (0.9 * 0.15) diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/RebalanceCheckerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/RebalanceCheckerTest.java new file mode 100644 index 000000000000..22663fa333b6 --- /dev/null +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/RebalanceCheckerTest.java @@ -0,0 +1,388 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.controller.helix.core.rebalance; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.util.concurrent.MoreExecutors; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.helix.AccessOption; +import org.apache.helix.HelixAdmin; +import org.apache.helix.HelixDataAccessor; +import org.apache.helix.HelixManager; +import org.apache.helix.store.zk.ZkHelixPropertyStore; +import org.apache.helix.zookeeper.datamodel.ZNRecord; +import org.apache.pinot.common.metadata.ZKMetadataProvider; +import org.apache.pinot.common.metadata.controllerjob.ControllerJobType; +import org.apache.pinot.common.metrics.ControllerMetrics; +import org.apache.pinot.controller.ControllerConf; +import org.apache.pinot.controller.LeadControllerManager; +import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.utils.CommonConstants; +import org.apache.pinot.spi.utils.JsonUtils; +import org.mockito.ArgumentCaptor; +import org.testng.annotations.Test; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; + + +public class RebalanceCheckerTest { + + @Test + public void testGetRetryDelayInMs() { + assertEquals(RebalanceChecker.getRetryDelayInMs(0, 1), 0); + assertEquals(RebalanceChecker.getRetryDelayInMs(0, 2), 0); + assertEquals(RebalanceChecker.getRetryDelayInMs(0, 3), 0); + + for (long initDelayMs : new long[]{1, 30000, 3600000}) { + long delayMs = RebalanceChecker.getRetryDelayInMs(initDelayMs, 1); + assertTrue(delayMs >= initDelayMs && delayMs < initDelayMs * 2); + delayMs = RebalanceChecker.getRetryDelayInMs(initDelayMs, 2); + assertTrue(delayMs >= initDelayMs * 2 && delayMs < initDelayMs * 4); + delayMs = RebalanceChecker.getRetryDelayInMs(initDelayMs, 3); + assertTrue(delayMs >= initDelayMs * 4 && delayMs < initDelayMs * 8); + } + } + + @Test + public void testGetCandidateJobs() + throws Exception { + String tableName = "table01"; + Map> allJobMetadata = new HashMap<>(); + + // Original job run as job1, and all its retry jobs failed too. + RebalanceConfig jobCfg = new RebalanceConfig(); + jobCfg.setMaxAttempts(4); + TableRebalanceProgressStats stats = new TableRebalanceProgressStats(); + stats.setStatus(RebalanceResult.Status.FAILED); + stats.setStartTimeMs(1000); + TableRebalanceContext jobCtx = TableRebalanceContext.forInitialAttempt("job1", jobCfg); + Map jobMetadata = ZkBasedTableRebalanceObserver.createJobMetadata(tableName, "job1", stats, jobCtx); + allJobMetadata.put("job1", jobMetadata); + // 3 failed retry runs for job1 + jobMetadata = createDummyJobMetadata(tableName, "job1", 2, 1100, RebalanceResult.Status.FAILED); + allJobMetadata.put("job1_2", jobMetadata); + jobMetadata = createDummyJobMetadata(tableName, "job1", 3, 1200, RebalanceResult.Status.ABORTED); + allJobMetadata.put("job1_3", jobMetadata); + jobMetadata = createDummyJobMetadata(tableName, "job1", 4, 1300, RebalanceResult.Status.FAILED); + allJobMetadata.put("job1_4", jobMetadata); + + // Original job run as job2, and its retry job job2_1 completed. + jobCfg = new RebalanceConfig(); + jobCfg.setMaxAttempts(4); + stats = new TableRebalanceProgressStats(); + stats.setStatus(RebalanceResult.Status.FAILED); + stats.setStartTimeMs(2000); + jobCtx = TableRebalanceContext.forInitialAttempt("job2", jobCfg); + jobMetadata = ZkBasedTableRebalanceObserver.createJobMetadata(tableName, "job2", stats, jobCtx); + allJobMetadata.put("job2", jobMetadata); + jobMetadata = createDummyJobMetadata(tableName, "job2", 2, 2100, RebalanceResult.Status.DONE); + allJobMetadata.put("job2_2", jobMetadata); + + // Original job run as job3, and failed to send out heartbeat in time. + jobCfg = new RebalanceConfig(); + jobCfg.setMaxAttempts(4); + stats = new TableRebalanceProgressStats(); + stats.setStatus(RebalanceResult.Status.IN_PROGRESS); + stats.setStartTimeMs(3000); + jobCtx = TableRebalanceContext.forInitialAttempt("job3", jobCfg); + jobMetadata = ZkBasedTableRebalanceObserver.createJobMetadata(tableName, "job3", stats, jobCtx); + jobMetadata.put(CommonConstants.ControllerJob.SUBMISSION_TIME_MS, "3000"); + allJobMetadata.put("job3", jobMetadata); + + // Original job run as job4, which didn't have retryJobCfg as from old version of the code. + stats = new TableRebalanceProgressStats(); + stats.setStatus(RebalanceResult.Status.FAILED); + stats.setStartTimeMs(4000); + jobMetadata = ZkBasedTableRebalanceObserver.createJobMetadata(tableName, "job4", stats, null); + jobMetadata.remove(RebalanceJobConstants.JOB_METADATA_KEY_REBALANCE_CONTEXT); + allJobMetadata.put("job4", jobMetadata); + + // Only need to retry job1 and job3, as job2 is completed and job4 is from old version of code. + Map>> jobs = + RebalanceChecker.getCandidateJobs(tableName, allJobMetadata); + assertEquals(jobs.size(), 2); + assertTrue(jobs.containsKey("job1")); + assertTrue(jobs.containsKey("job3")); + assertEquals(jobs.get("job1").size(), 4); // four runs including job1,job1_1,job1_2,job1_3 + assertEquals(jobs.get("job3").size(), 1); // just a single run job3 + + // Abort job1 and cancel its retries, then only job3 is retry candidate. + jobMetadata = allJobMetadata.get("job1_4"); + cancelRebalanceJob(jobMetadata); + jobs = RebalanceChecker.getCandidateJobs(tableName, allJobMetadata); + assertEquals(jobs.size(), 1); + assertTrue(jobs.containsKey("job3")); + assertEquals(jobs.get("job3").size(), 1); // just a single run job3 + + // Add latest job5 that's already done, thus no need to retry for table. + jobCfg = new RebalanceConfig(); + jobCfg.setMaxAttempts(4); + stats = new TableRebalanceProgressStats(); + stats.setStatus(RebalanceResult.Status.DONE); + stats.setStartTimeMs(5000); + jobCtx = TableRebalanceContext.forInitialAttempt("job5", jobCfg); + jobMetadata = ZkBasedTableRebalanceObserver.createJobMetadata(tableName, "job5", stats, jobCtx); + allJobMetadata.put("job5", jobMetadata); + jobs = RebalanceChecker.getCandidateJobs(tableName, allJobMetadata); + assertEquals(jobs.size(), 0); + } + + @Test + public void testGetLatestJob() { + Map>> jobs = new HashMap<>(); + // The most recent job run is job1_3, and within 3 maxAttempts. + jobs.put("job1", + ImmutableSet.of(Pair.of(createDummyJobCtx("job1", 1), 10L), Pair.of(createDummyJobCtx("job1", 2), 20L), + Pair.of(createDummyJobCtx("job1", 3), 1020L))); + jobs.put("job2", ImmutableSet.of(Pair.of(createDummyJobCtx("job2", 1), 1000L))); + Pair jobTime = RebalanceChecker.getLatestJob(jobs); + assertNotNull(jobTime); + assertEquals(jobTime.getLeft().getJobId(), "job1_3"); + + // The most recent job run is job1_4, but reached 3 maxAttempts. + jobs.put("job1", + ImmutableSet.of(Pair.of(createDummyJobCtx("job1", 1), 10L), Pair.of(createDummyJobCtx("job1", 2), 20L), + Pair.of(createDummyJobCtx("job1", 3), 1020L), Pair.of(createDummyJobCtx("job1", 4), 2020L))); + jobTime = RebalanceChecker.getLatestJob(jobs); + assertNotNull(jobTime); + assertEquals(jobTime.getLeft().getJobId(), "job2"); + + // Add job3 that's started more recently. + jobs.put("job3", ImmutableSet.of(Pair.of(createDummyJobCtx("job3", 1), 3000L))); + jobTime = RebalanceChecker.getLatestJob(jobs); + assertNotNull(jobTime); + assertEquals(jobTime.getLeft().getJobId(), "job3"); + + // Remove job2 and job3, and we'd have no job to retry then. + jobs.remove("job2"); + jobs.remove("job3"); + jobTime = RebalanceChecker.getLatestJob(jobs); + assertNull(jobTime); + } + + @Test + public void testRetryRebalance() + throws Exception { + String tableName = "table01"; + LeadControllerManager leadController = mock(LeadControllerManager.class); + ControllerMetrics metrics = mock(ControllerMetrics.class); + ExecutorService exec = MoreExecutors.newDirectExecutorService(); + ControllerConf cfg = new ControllerConf(); + + Map> allJobMetadata = new HashMap<>(); + // Original job run as job1, and all its retry jobs failed too. + RebalanceConfig jobCfg = new RebalanceConfig(); + jobCfg.setMaxAttempts(4); + TableRebalanceProgressStats stats = new TableRebalanceProgressStats(); + stats.setStatus(RebalanceResult.Status.FAILED); + stats.setStartTimeMs(1000); + TableRebalanceContext jobCtx = TableRebalanceContext.forInitialAttempt("job1", jobCfg); + Map jobMetadata = ZkBasedTableRebalanceObserver.createJobMetadata(tableName, "job1", stats, jobCtx); + allJobMetadata.put("job1", jobMetadata); + // 3 failed retry runs for job1 + jobMetadata = createDummyJobMetadata(tableName, "job1", 2, 1100, RebalanceResult.Status.FAILED); + allJobMetadata.put("job1_2", jobMetadata); + jobMetadata = createDummyJobMetadata(tableName, "job1", 3, 1200, RebalanceResult.Status.FAILED); + allJobMetadata.put("job1_3", jobMetadata); + jobMetadata = createDummyJobMetadata(tableName, "job1", 4, 5300, RebalanceResult.Status.FAILED); + allJobMetadata.put("job1_4", jobMetadata); + + // Original job run as job2, and its retry job job2_1 completed. + jobCfg = new RebalanceConfig(); + jobCfg.setMaxAttempts(4); + stats = new TableRebalanceProgressStats(); + stats.setStatus(RebalanceResult.Status.FAILED); + stats.setStartTimeMs(2000); + jobCtx = TableRebalanceContext.forInitialAttempt("job2", jobCfg); + jobMetadata = ZkBasedTableRebalanceObserver.createJobMetadata(tableName, "job2", stats, jobCtx); + allJobMetadata.put("job2", jobMetadata); + jobMetadata = createDummyJobMetadata(tableName, "job2", 2, 2100, RebalanceResult.Status.DONE); + allJobMetadata.put("job2_2", jobMetadata); + + // Original job run as job3, and failed to send out heartbeat in time. + jobCfg = new RebalanceConfig(); + jobCfg.setMaxAttempts(4); + stats = new TableRebalanceProgressStats(); + stats.setStatus(RebalanceResult.Status.IN_PROGRESS); + stats.setStartTimeMs(3000); + jobCtx = TableRebalanceContext.forInitialAttempt("job3", jobCfg); + jobMetadata = ZkBasedTableRebalanceObserver.createJobMetadata(tableName, "job3", stats, jobCtx); + jobMetadata.put(CommonConstants.ControllerJob.SUBMISSION_TIME_MS, "3000"); + allJobMetadata.put("job3", jobMetadata); + + TableConfig tableConfig = mock(TableConfig.class); + PinotHelixResourceManager helixManager = mock(PinotHelixResourceManager.class); + when(helixManager.getTableConfig(tableName)).thenReturn(tableConfig); + when(helixManager.getAllJobs(any(), any())).thenReturn(allJobMetadata); + RebalanceChecker checker = new RebalanceChecker(helixManager, leadController, cfg, metrics, exec); + // Although job1_3 was submitted most recently but job1 had exceeded maxAttempts. Chose job3 to retry, which got + // stuck at in progress status. + checker.retryRebalanceTable(tableName, allJobMetadata); + // The new retry job is for job3 and attemptId is increased to 2. + ArgumentCaptor observerCaptor = + ArgumentCaptor.forClass(ZkBasedTableRebalanceObserver.class); + verify(helixManager, times(1)).rebalanceTable(eq(tableName), any(), anyString(), any(), observerCaptor.capture()); + ZkBasedTableRebalanceObserver observer = observerCaptor.getValue(); + jobCtx = observer.getTableRebalanceContext(); + assertEquals(jobCtx.getOriginalJobId(), "job3"); + assertEquals(jobCtx.getAttemptId(), 2); + } + + @Test + public void testRetryRebalanceWithBackoff() + throws Exception { + String tableName = "table01"; + LeadControllerManager leadController = mock(LeadControllerManager.class); + ControllerMetrics metrics = mock(ControllerMetrics.class); + ExecutorService exec = MoreExecutors.newDirectExecutorService(); + ControllerConf cfg = new ControllerConf(); + + Map> allJobMetadata = new HashMap<>(); + // Original job run as job1, and all its retry jobs failed too. + RebalanceConfig jobCfg = new RebalanceConfig(); + jobCfg.setMaxAttempts(4); + long nowMs = System.currentTimeMillis(); + TableRebalanceProgressStats stats = new TableRebalanceProgressStats(); + stats.setStatus(RebalanceResult.Status.FAILED); + stats.setStartTimeMs(nowMs); + TableRebalanceContext jobCtx = TableRebalanceContext.forInitialAttempt("job1", jobCfg); + Map jobMetadata = ZkBasedTableRebalanceObserver.createJobMetadata(tableName, "job1", stats, jobCtx); + allJobMetadata.put("job1", jobMetadata); + + PinotHelixResourceManager helixManager = mock(PinotHelixResourceManager.class); + TableConfig tableConfig = mock(TableConfig.class); + when(helixManager.getTableConfig(tableName)).thenReturn(tableConfig); + RebalanceChecker checker = new RebalanceChecker(helixManager, leadController, cfg, metrics, exec); + checker.retryRebalanceTable(tableName, allJobMetadata); + // Retry for job1 is delayed with 5min backoff. + ArgumentCaptor observerCaptor = + ArgumentCaptor.forClass(ZkBasedTableRebalanceObserver.class); + verify(helixManager, times(0)).rebalanceTable(eq(tableName), any(), anyString(), any(), observerCaptor.capture()); + + // Set initial delay to 0 to disable retry backoff. + jobCfg.setRetryInitialDelayInMs(0); + jobMetadata = ZkBasedTableRebalanceObserver.createJobMetadata(tableName, "job1", stats, jobCtx); + allJobMetadata.put("job1", jobMetadata); + checker.retryRebalanceTable(tableName, allJobMetadata); + // Retry for job1 is delayed with 0 backoff. + observerCaptor = ArgumentCaptor.forClass(ZkBasedTableRebalanceObserver.class); + verify(helixManager, times(1)).rebalanceTable(eq(tableName), any(), anyString(), any(), observerCaptor.capture()); + } + + @Test + public void testAddUpdateControllerJobsForTable() { + ControllerConf cfg = new ControllerConf(); + cfg.setZkStr("localhost:2181"); + cfg.setHelixClusterName("cluster01"); + PinotHelixResourceManager pinotHelixManager = new PinotHelixResourceManager(cfg); + HelixManager helixZkManager = mock(HelixManager.class); + ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); + String zkPath = ZKMetadataProvider.constructPropertyStorePathForControllerJob(ControllerJobType.TABLE_REBALANCE); + ZNRecord jobsZnRecord = new ZNRecord("jobs"); + when(propertyStore.get(eq(zkPath), any(), eq(AccessOption.PERSISTENT))).thenReturn(jobsZnRecord); + when(helixZkManager.getClusterManagmentTool()).thenReturn(mock(HelixAdmin.class)); + when(helixZkManager.getHelixPropertyStore()).thenReturn(propertyStore); + when(helixZkManager.getHelixDataAccessor()).thenReturn(mock(HelixDataAccessor.class)); + pinotHelixManager.start(helixZkManager, null); + + pinotHelixManager.addControllerJobToZK("job1", + ImmutableMap.of("jobId", "job1", "submissionTimeMs", "1000", "tableName", "table01"), + ControllerJobType.TABLE_REBALANCE, jmd -> true); + pinotHelixManager.addControllerJobToZK("job2", + ImmutableMap.of("jobId", "job2", "submissionTimeMs", "2000", "tableName", "table01"), + ControllerJobType.TABLE_REBALANCE, jmd -> false); + pinotHelixManager.addControllerJobToZK("job3", + ImmutableMap.of("jobId", "job3", "submissionTimeMs", "3000", "tableName", "table02"), + ControllerJobType.TABLE_REBALANCE, jmd -> true); + pinotHelixManager.addControllerJobToZK("job4", + ImmutableMap.of("jobId", "job4", "submissionTimeMs", "4000", "tableName", "table02"), + ControllerJobType.TABLE_REBALANCE, jmd -> true); + Map> jmds = jobsZnRecord.getMapFields(); + assertEquals(jmds.size(), 3); + assertTrue(jmds.containsKey("job1")); + assertTrue(jmds.containsKey("job3")); + assertTrue(jmds.containsKey("job4")); + + Set expectedJobs01 = new HashSet<>(); + pinotHelixManager.updateJobsForTable("table01", ControllerJobType.TABLE_REBALANCE, + jmd -> expectedJobs01.add(jmd.get("jobId"))); + assertEquals(expectedJobs01.size(), 1); + assertTrue(expectedJobs01.contains("job1")); + + Set expectedJobs02 = new HashSet<>(); + pinotHelixManager.updateJobsForTable("table02", ControllerJobType.TABLE_REBALANCE, + jmd -> expectedJobs02.add(jmd.get("jobId"))); + assertEquals(expectedJobs02.size(), 2); + assertTrue(expectedJobs02.contains("job3")); + assertTrue(expectedJobs02.contains("job4")); + } + + private static TableRebalanceContext createDummyJobCtx(String originalJobId, int attemptId) { + TableRebalanceContext jobCtx = new TableRebalanceContext(); + RebalanceConfig cfg = new RebalanceConfig(); + cfg.setMaxAttempts(4); + jobCtx.setJobId(originalJobId); + if (attemptId > 1) { + jobCtx.setJobId(originalJobId + "_" + attemptId); + } + jobCtx.setOriginalJobId(originalJobId); + jobCtx.setConfig(cfg); + jobCtx.setAttemptId(attemptId); + return jobCtx; + } + + private static Map createDummyJobMetadata(String tableName, String originalJobId, int attemptId, + long startTimeMs, RebalanceResult.Status status) { + RebalanceConfig cfg = new RebalanceConfig(); + cfg.setMaxAttempts(4); + TableRebalanceProgressStats stats = new TableRebalanceProgressStats(); + stats.setStatus(status); + stats.setStartTimeMs(startTimeMs); + TableRebalanceContext jobCtx = TableRebalanceContext.forRetry(originalJobId, cfg, attemptId); + String attemptJobId = originalJobId + "_" + attemptId; + return ZkBasedTableRebalanceObserver.createJobMetadata(tableName, attemptJobId, stats, jobCtx); + } + + private static void cancelRebalanceJob(Map jobMetadata) + throws JsonProcessingException { + String jobStatsInStr = jobMetadata.get(RebalanceJobConstants.JOB_METADATA_KEY_REBALANCE_PROGRESS_STATS); + TableRebalanceProgressStats jobStats = JsonUtils.stringToObject(jobStatsInStr, TableRebalanceProgressStats.class); + jobStats.setStatus(RebalanceResult.Status.CANCELLED); + jobMetadata.put(RebalanceJobConstants.JOB_METADATA_KEY_REBALANCE_PROGRESS_STATS, + JsonUtils.objectToString(jobStats)); + } +} diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalancerClusterStatelessTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalancerClusterStatelessTest.java index 4c006967d182..1df7109ef288 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalancerClusterStatelessTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalancerClusterStatelessTest.java @@ -24,8 +24,6 @@ import java.util.Collections; import java.util.Map; import java.util.concurrent.TimeUnit; -import org.apache.commons.configuration.BaseConfiguration; -import org.apache.commons.configuration.Configuration; import org.apache.pinot.common.assignment.InstancePartitions; import org.apache.pinot.common.assignment.InstancePartitionsUtils; import org.apache.pinot.common.tier.TierFactory; @@ -42,7 +40,6 @@ import org.apache.pinot.spi.config.table.assignment.InstanceTagPoolConfig; import org.apache.pinot.spi.config.tenant.Tenant; import org.apache.pinot.spi.config.tenant.TenantRole; -import org.apache.pinot.spi.utils.RebalanceConfigConstants; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.testng.annotations.AfterClass; @@ -100,10 +97,11 @@ public void testRebalance() new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setNumReplicas(NUM_REPLICAS).build(); // Rebalance should fail without creating the table - RebalanceResult rebalanceResult = tableRebalancer.rebalance(tableConfig, new BaseConfiguration()); + RebalanceResult rebalanceResult = tableRebalancer.rebalance(tableConfig, new RebalanceConfig(), null); assertEquals(rebalanceResult.getStatus(), RebalanceResult.Status.FAILED); // Create the table + addDummySchema(RAW_TABLE_NAME); _helixResourceManager.addTable(tableConfig); // Add the segments @@ -116,7 +114,7 @@ public void testRebalance() _helixResourceManager.getTableIdealState(OFFLINE_TABLE_NAME).getRecord().getMapFields(); // Rebalance should return NO_OP status - rebalanceResult = tableRebalancer.rebalance(tableConfig, new BaseConfiguration()); + rebalanceResult = tableRebalancer.rebalance(tableConfig, new RebalanceConfig(), null); assertEquals(rebalanceResult.getStatus(), RebalanceResult.Status.NO_OP); // All servers should be assigned to the table @@ -139,9 +137,9 @@ public void testRebalance() } // Rebalance in dry-run mode - Configuration rebalanceConfig = new BaseConfiguration(); - rebalanceConfig.addProperty(RebalanceConfigConstants.DRY_RUN, true); - rebalanceResult = tableRebalancer.rebalance(tableConfig, rebalanceConfig); + RebalanceConfig rebalanceConfig = new RebalanceConfig(); + rebalanceConfig.setDryRun(true); + rebalanceResult = tableRebalancer.rebalance(tableConfig, rebalanceConfig, null); assertEquals(rebalanceResult.getStatus(), RebalanceResult.Status.DONE); // All servers should be assigned to the table @@ -169,9 +167,9 @@ public void testRebalance() oldSegmentAssignment); // Rebalance with 3 min available replicas should fail as the table only have 3 replicas - rebalanceConfig = new BaseConfiguration(); - rebalanceConfig.addProperty(RebalanceConfigConstants.MIN_REPLICAS_TO_KEEP_UP_FOR_NO_DOWNTIME, 3); - rebalanceResult = tableRebalancer.rebalance(tableConfig, rebalanceConfig); + rebalanceConfig = new RebalanceConfig(); + rebalanceConfig.setMinAvailableReplicas(3); + rebalanceResult = tableRebalancer.rebalance(tableConfig, rebalanceConfig, null); assertEquals(rebalanceResult.getStatus(), RebalanceResult.Status.FAILED); // IdealState should not change for FAILED rebalance @@ -179,9 +177,9 @@ public void testRebalance() oldSegmentAssignment); // Rebalance with 2 min available replicas should succeed - rebalanceConfig = new BaseConfiguration(); - rebalanceConfig.addProperty(RebalanceConfigConstants.MIN_REPLICAS_TO_KEEP_UP_FOR_NO_DOWNTIME, 2); - rebalanceResult = tableRebalancer.rebalance(tableConfig, rebalanceConfig); + rebalanceConfig = new RebalanceConfig(); + rebalanceConfig.setMinAvailableReplicas(2); + rebalanceResult = tableRebalancer.rebalance(tableConfig, rebalanceConfig, null); assertEquals(rebalanceResult.getStatus(), RebalanceResult.Status.DONE); // Result should be the same as the result in dry-run mode @@ -195,13 +193,13 @@ public void testRebalance() InstanceTagPoolConfig tagPoolConfig = new InstanceTagPoolConfig(TagNameUtils.getOfflineTagForTenant(null), false, 0, null); InstanceReplicaGroupPartitionConfig replicaGroupPartitionConfig = - new InstanceReplicaGroupPartitionConfig(true, 0, NUM_REPLICAS, 0, 0, 0, false); - tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE, - new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig))); + new InstanceReplicaGroupPartitionConfig(true, 0, NUM_REPLICAS, 0, 0, 0, false, null); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(InstancePartitionsType.OFFLINE.toString(), + new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig, null, false))); _helixResourceManager.updateTableConfig(tableConfig); // No need to reassign instances because instances should be automatically assigned when updating the table config - rebalanceResult = tableRebalancer.rebalance(tableConfig, new BaseConfiguration()); + rebalanceResult = tableRebalancer.rebalance(tableConfig, new RebalanceConfig(), null); assertEquals(rebalanceResult.getStatus(), RebalanceResult.Status.DONE); // There should be 3 replica-groups, each with 2 servers @@ -246,16 +244,18 @@ public void testRebalance() tableConfig.setInstanceAssignmentConfigMap(null); _helixResourceManager.updateTableConfig(tableConfig); - // Without instances reassignment, the rebalance should return status NO_OP as instance partitions are already - // generated - rebalanceResult = tableRebalancer.rebalance(tableConfig, new BaseConfiguration()); + // Without instances reassignment, the rebalance should return status NO_OP, and the existing instance partitions + // should be used + rebalanceResult = tableRebalancer.rebalance(tableConfig, new RebalanceConfig(), null); assertEquals(rebalanceResult.getStatus(), RebalanceResult.Status.NO_OP); + assertEquals(rebalanceResult.getInstanceAssignment(), instanceAssignment); + assertEquals(rebalanceResult.getSegmentAssignment(), newSegmentAssignment); - // With instances reassignment, the instance partitions should be removed, and the default instance partitions - // should be used for segment assignment - rebalanceConfig = new BaseConfiguration(); - rebalanceConfig.addProperty(RebalanceConfigConstants.REASSIGN_INSTANCES, true); - rebalanceResult = tableRebalancer.rebalance(tableConfig, rebalanceConfig); + // With instances reassignment, the rebalance should return status DONE, the existing instance partitions should be + // removed, and the default instance partitions should be used + rebalanceConfig = new RebalanceConfig(); + rebalanceConfig.setReassignInstances(true); + rebalanceResult = tableRebalancer.rebalance(tableConfig, rebalanceConfig, null); assertEquals(rebalanceResult.getStatus(), RebalanceResult.Status.DONE); assertNull(InstancePartitionsUtils.fetchInstancePartitions(_propertyStore, InstancePartitionsType.OFFLINE.getInstancePartitionsName(RAW_TABLE_NAME))); @@ -281,9 +281,9 @@ public void testRebalance() } // Rebalance with downtime should succeed - rebalanceConfig = new BaseConfiguration(); - rebalanceConfig.addProperty(RebalanceConfigConstants.DOWNTIME, true); - rebalanceResult = tableRebalancer.rebalance(tableConfig, rebalanceConfig); + rebalanceConfig = new RebalanceConfig(); + rebalanceConfig.setDowntime(true); + rebalanceResult = tableRebalancer.rebalance(tableConfig, rebalanceConfig, null); assertEquals(rebalanceResult.getStatus(), RebalanceResult.Status.DONE); // All servers with tag should be assigned to the table @@ -330,6 +330,7 @@ public void testRebalanceWithTiers() new TableConfigBuilder(TableType.OFFLINE).setTableName(TIERED_TABLE_NAME).setNumReplicas(NUM_REPLICAS) .setServerTenant(NO_TIER_NAME).build(); // Create the table + addDummySchema(TIERED_TABLE_NAME); _helixResourceManager.addTable(tableConfig); // Add the segments @@ -346,7 +347,7 @@ public void testRebalanceWithTiers() _helixResourceManager.getTableIdealState(OFFLINE_TIERED_TABLE_NAME).getRecord().getMapFields(); TableRebalancer tableRebalancer = new TableRebalancer(_helixManager); - RebalanceResult rebalanceResult = tableRebalancer.rebalance(tableConfig, new BaseConfiguration()); + RebalanceResult rebalanceResult = tableRebalancer.rebalance(tableConfig, new RebalanceConfig(), null); assertEquals(rebalanceResult.getStatus(), RebalanceResult.Status.NO_OP); // Segment assignment should not change assertEquals(rebalanceResult.getSegmentAssignment(), oldSegmentAssignment); @@ -362,7 +363,7 @@ public void testRebalanceWithTiers() _helixResourceManager.createServerTenant(new Tenant(TenantRole.SERVER, TIER_B_NAME, 3, 3, 0)); // rebalance is NOOP and no change in assignment caused by new instances - rebalanceResult = tableRebalancer.rebalance(tableConfig, new BaseConfiguration()); + rebalanceResult = tableRebalancer.rebalance(tableConfig, new RebalanceConfig(), null); assertEquals(rebalanceResult.getStatus(), RebalanceResult.Status.NO_OP); // Segment assignment should not change assertEquals(rebalanceResult.getSegmentAssignment(), oldSegmentAssignment); @@ -380,7 +381,7 @@ public void testRebalanceWithTiers() _helixResourceManager.updateTableConfig(tableConfig); // rebalance should change assignment - rebalanceResult = tableRebalancer.rebalance(tableConfig, new BaseConfiguration()); + rebalanceResult = tableRebalancer.rebalance(tableConfig, new RebalanceConfig(), null); assertEquals(rebalanceResult.getStatus(), RebalanceResult.Status.DONE); // check that segments have moved to tiers @@ -403,6 +404,132 @@ public void testRebalanceWithTiers() assertTrue(instance.startsWith(expectedPrefix)); } } + _helixResourceManager.deleteOfflineTable(TIERED_TABLE_NAME); + } + + @Test + public void testRebalanceWithTiersAndInstanceAssignments() + throws Exception { + int numServers = 3; + for (int i = 0; i < numServers; i++) { + addFakeServerInstanceToAutoJoinHelixCluster( + "replicaAssignment" + NO_TIER_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + i, false); + } + _helixResourceManager.createServerTenant( + new Tenant(TenantRole.SERVER, "replicaAssignment" + NO_TIER_NAME, numServers, numServers, 0)); + + TableConfig tableConfig = + new TableConfigBuilder(TableType.OFFLINE).setTableName(TIERED_TABLE_NAME).setNumReplicas(NUM_REPLICAS) + .setServerTenant("replicaAssignment" + NO_TIER_NAME).build(); + // Create the table + addDummySchema(TIERED_TABLE_NAME); + _helixResourceManager.addTable(tableConfig); + + // Add the segments + int numSegments = 10; + long nowInDays = TimeUnit.MILLISECONDS.toDays(System.currentTimeMillis()); + + for (int i = 0; i < numSegments; i++) { + _helixResourceManager.addNewSegment(OFFLINE_TIERED_TABLE_NAME, + SegmentMetadataMockUtils.mockSegmentMetadataWithEndTimeInfo(TIERED_TABLE_NAME, SEGMENT_NAME_PREFIX + i, + nowInDays), null); + } + Map> oldSegmentAssignment = + _helixResourceManager.getTableIdealState(OFFLINE_TIERED_TABLE_NAME).getRecord().getMapFields(); + + TableRebalancer tableRebalancer = new TableRebalancer(_helixManager); + RebalanceResult rebalanceResult = tableRebalancer.rebalance(tableConfig, new RebalanceConfig(), null); + assertEquals(rebalanceResult.getStatus(), RebalanceResult.Status.NO_OP); + // Segment assignment should not change + assertEquals(rebalanceResult.getSegmentAssignment(), oldSegmentAssignment); + + // add 6 nodes tierA + for (int i = 0; i < 6; i++) { + addFakeServerInstanceToAutoJoinHelixCluster( + "replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + i, false); + } + _helixResourceManager.createServerTenant(new Tenant(TenantRole.SERVER, "replicaAssignment" + TIER_A_NAME, 6, 6, 0)); + // rebalance is NOOP and no change in assignment caused by new instances + rebalanceResult = tableRebalancer.rebalance(tableConfig, new RebalanceConfig(), null); + assertEquals(rebalanceResult.getStatus(), RebalanceResult.Status.NO_OP); + // Segment assignment should not change + assertEquals(rebalanceResult.getSegmentAssignment(), oldSegmentAssignment); + + // add tier config + tableConfig.setTierConfigsList(Lists.newArrayList( + new TierConfig(TIER_A_NAME, TierFactory.TIME_SEGMENT_SELECTOR_TYPE, "0d", null, + TierFactory.PINOT_SERVER_STORAGE_TYPE, "replicaAssignment" + TIER_A_NAME + "_OFFLINE", null, null))); + _helixResourceManager.updateTableConfig(tableConfig); + + // rebalance should change assignment + rebalanceResult = tableRebalancer.rebalance(tableConfig, new RebalanceConfig(), null); + assertEquals(rebalanceResult.getStatus(), RebalanceResult.Status.DONE); + + // check that segments have moved to tier a + Map> tierSegmentAssignment = rebalanceResult.getSegmentAssignment(); + for (Map.Entry> entry : tierSegmentAssignment.entrySet()) { + Map instanceStateMap = entry.getValue(); + for (String instance : instanceStateMap.keySet()) { + assertTrue(instance.startsWith("replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX)); + } + } + + // Test rebalance with tier instance assignment + InstanceTagPoolConfig tagPoolConfig = + new InstanceTagPoolConfig(TagNameUtils.getOfflineTagForTenant("replicaAssignment" + TIER_A_NAME), false, 0, + null); + InstanceReplicaGroupPartitionConfig replicaGroupPartitionConfig = + new InstanceReplicaGroupPartitionConfig(true, 0, NUM_REPLICAS, 0, 0, 0, false, null); + tableConfig.setInstanceAssignmentConfigMap(Collections.singletonMap(TIER_A_NAME, + new InstanceAssignmentConfig(tagPoolConfig, null, replicaGroupPartitionConfig, null, false))); + _helixResourceManager.updateTableConfig(tableConfig); + + rebalanceResult = tableRebalancer.rebalance(tableConfig, new RebalanceConfig(), null); + assertEquals(rebalanceResult.getStatus(), RebalanceResult.Status.DONE); + assertTrue(rebalanceResult.getTierInstanceAssignment().containsKey(TIER_A_NAME)); + + InstancePartitions instancePartitions = rebalanceResult.getTierInstanceAssignment().get(TIER_A_NAME); + + // Math.abs("testTable_OFFLINE".hashCode()) % 6 = 2 + // [i2, i3, i4, i5, i0, i1] + // r0 r1 r2 r0 r1 r2 + assertEquals(instancePartitions.getInstances(0, 0), + Arrays.asList("replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 2, + "replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 5)); + assertEquals(instancePartitions.getInstances(0, 1), + Arrays.asList("replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 3, + "replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 0)); + assertEquals(instancePartitions.getInstances(0, 2), + Arrays.asList("replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 4, + "replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 1)); + + // The assignment are based on replica-group 0 and mirrored to all the replica-groups, so server of index 0, 1, 5 + // should have the same segments assigned, and server of index 2, 3, 4 should have the same segments assigned, each + // with 5 segments + Map> newSegmentAssignment = rebalanceResult.getSegmentAssignment(); + int numSegmentsOnServer0 = 0; + for (int i = 0; i < numSegments; i++) { + String segmentName = SEGMENT_NAME_PREFIX + i; + Map instanceStateMap = newSegmentAssignment.get(segmentName); + assertEquals(instanceStateMap.size(), NUM_REPLICAS); + if (instanceStateMap.containsKey("replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 0)) { + numSegmentsOnServer0++; + assertEquals(instanceStateMap.get("replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 0), + ONLINE); + assertEquals(instanceStateMap.get("replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 1), + ONLINE); + assertEquals(instanceStateMap.get("replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 5), + ONLINE); + } else { + assertEquals(instanceStateMap.get("replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 2), + ONLINE); + assertEquals(instanceStateMap.get("replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 3), + ONLINE); + assertEquals(instanceStateMap.get("replicaAssignment" + TIER_A_NAME + "_" + SERVER_INSTANCE_ID_PREFIX + 4), + ONLINE); + } + } + assertEquals(numSegmentsOnServer0, numSegments / 2); _helixResourceManager.deleteOfflineTable(TIERED_TABLE_NAME); } diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalancerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalancerTest.java index 84a660cc5e4c..ecf1e0feda6c 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalancerTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/TableRebalancerTest.java @@ -43,189 +43,512 @@ public class TableRebalancerTest { @Test public void testDowntimeMode() { - // With common instance, next assignment should be the same as target assignment + // With common instance, first assignment should be the same as target assignment Map currentInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2"), ONLINE); Map targetInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host3"), ONLINE); TableRebalancer.SingleSegmentAssignment assignment = - getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 0); + getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 0, false); assertEquals(assignment._instanceStateMap, targetInstanceStateMap); assertEquals(assignment._availableInstances, Collections.singleton("host1")); - // Without common instance, next assignment should be the same as target assignment + // Without common instance, first assignment should be the same as target assignment targetInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host3", "host4"), ONLINE); - assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 0); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 0, false); assertEquals(assignment._instanceStateMap, targetInstanceStateMap); assertTrue(assignment._availableInstances.isEmpty()); - // With increasing number of replicas, next assignment should be the same as target assignment + // With increasing number of replicas, first assignment should be the same as target assignment targetInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host3", "host4", "host5"), ONLINE); - assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 0); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 0, false); assertEquals(assignment._instanceStateMap, targetInstanceStateMap); assertTrue(assignment._availableInstances.isEmpty()); - // With decreasing number of replicas, next assignment should be the same as target assignment + // With decreasing number of replicas, first assignment should be the same as target assignment currentInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host3"), ONLINE); targetInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host4", "host5"), ONLINE); - assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 0); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 0, false); + assertEquals(assignment._instanceStateMap, targetInstanceStateMap); + assertTrue(assignment._availableInstances.isEmpty()); + } + + @Test + public void testDowntimeWithLowDiskMode() { + // With common instance, first assignment should keep the common instance and remove the not common instance + Map currentInstanceStateMap = + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2"), ONLINE); + Map targetInstanceStateMap = + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host3"), ONLINE); + TableRebalancer.SingleSegmentAssignment assignment = + getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 0, true); + assertEquals(assignment._instanceStateMap, Collections.singletonMap("host1", ONLINE)); + assertEquals(assignment._availableInstances, Collections.singleton("host1")); + // Second assignment should be the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 0, true); + assertEquals(assignment._instanceStateMap, targetInstanceStateMap); + assertEquals(assignment._availableInstances, Collections.singleton("host1")); + + // Without common instance, first assignment should drop all instances + targetInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host3", "host4"), ONLINE); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 0, true); + assertTrue(assignment._instanceStateMap.isEmpty()); + assertTrue(assignment._availableInstances.isEmpty()); + // Second assignment should be the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 0, true); + assertEquals(assignment._instanceStateMap, targetInstanceStateMap); + assertTrue(assignment._availableInstances.isEmpty()); + + // With increasing number of replicas, first assignment should drop all instances + targetInstanceStateMap = + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host3", "host4", "host5"), ONLINE); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 0, true); + assertTrue(assignment._instanceStateMap.isEmpty()); + assertTrue(assignment._availableInstances.isEmpty()); + // Second assignment should be the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 0, true); + assertEquals(assignment._instanceStateMap, targetInstanceStateMap); + assertTrue(assignment._availableInstances.isEmpty()); + + // With decreasing number of replicas, first assignment should drop all instances + currentInstanceStateMap = + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host3"), ONLINE); + targetInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host4", "host5"), ONLINE); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 0, true); + assertTrue(assignment._instanceStateMap.isEmpty()); + assertTrue(assignment._availableInstances.isEmpty()); + // Second assignment should be the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 0, true); assertEquals(assignment._instanceStateMap, targetInstanceStateMap); assertTrue(assignment._availableInstances.isEmpty()); } @Test public void testOneMinAvailableReplicas() { - // With 2 common instances, next assignment should be the same as target assignment + // With 2 common instances, first assignment should be the same as target assignment Map currentInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host3"), ONLINE); Map targetInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host4"), ONLINE); TableRebalancer.SingleSegmentAssignment assignment = - getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 1); + getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 1, false); assertEquals(assignment._instanceStateMap, targetInstanceStateMap); assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host2"))); - // With 1 common instance, next assignment should be the same as target assignment + // With 1 common instance, first assignment should be the same as target assignment targetInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host4", "host5"), ONLINE); - assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 1); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 1, false); assertEquals(assignment._instanceStateMap, targetInstanceStateMap); assertEquals(assignment._availableInstances, Collections.singleton("host1")); - // Without common instance, next assignment should have 1 common instances with current assignment + // Without common instance, first assignment should have 1 common instances with current assignment targetInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host4", "host5", "host6"), ONLINE); - // [host1, host4, host5] - assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 1); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 1, false); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host4", "host5"), ONLINE)); assertEquals(assignment._availableInstances, Collections.singleton("host1")); - // Next round should make the assignment the same as target assignment - assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 1); + // Second assignment should be the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 1, false); assertEquals(assignment._instanceStateMap, targetInstanceStateMap); assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host4", "host5"))); - // With increasing number of replicas, next assignment should have 1 common instances with current assignment + // With increasing number of replicas, first assignment should have 1 common instances with current assignment targetInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host4", "host5", "host6", "host7"), ONLINE); - // [host1, host4, host5, host6] - assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 1); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 1, false); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host4", "host5", "host6"), ONLINE)); assertEquals(assignment._availableInstances, Collections.singleton("host1")); - // Next round should make the assignment the same as target assignment - assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 1); + // Second assignment should be the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 1, false); assertEquals(assignment._instanceStateMap, targetInstanceStateMap); assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host4", "host5", "host6"))); - // With decreasing number of replicas, next assignment should have 1 common instances with current assignment + // With decreasing number of replicas, first assignment should have 1 common instances with current assignment currentInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host3", "host4"), ONLINE); targetInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host5", "host6", "host7"), ONLINE); - // [host1, host5, host6] - assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 1); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 1, false); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host5", "host6"), ONLINE)); assertEquals(assignment._availableInstances, Collections.singleton("host1")); - // Next round should make the assignment the same as target assignment - assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 1); + // Second assignment should be the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 1, false); assertEquals(assignment._instanceStateMap, targetInstanceStateMap); assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host5", "host6"))); - // With increasing from 1 replica, next assignment should have 1 common instances with current assignment + // With increasing from 1 replica, first assignment should have 1 common instances with current assignment currentInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Collections.singletonList("host1"), ONLINE); targetInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host2", "host3", "host4"), ONLINE); - // [host1, host2, host3] - assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 1); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 1, false); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host3"), ONLINE)); assertEquals(assignment._availableInstances, Collections.singleton("host1")); - // Next round should make the assignment the same as target assignment - assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 1); + // Second assignment should be the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 1, false); + assertEquals(assignment._instanceStateMap, targetInstanceStateMap); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host2", "host3"))); + } + + @Test + public void testOneMinAvailableReplicasWithLowDiskMode() { + // With 2 common instances, first assignment should keep the common instances and remove the not common instance + Map currentInstanceStateMap = + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host3"), ONLINE); + Map targetInstanceStateMap = + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host4"), ONLINE); + TableRebalancer.SingleSegmentAssignment assignment = + getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 1, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2"), ONLINE)); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host2"))); + // Second assignment should be the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 1, true); + assertEquals(assignment._instanceStateMap, targetInstanceStateMap); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host2"))); + + // With 1 common instance, first assignment should keep the common instance and remove the not common instances + targetInstanceStateMap = + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host4", "host5"), ONLINE); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 1, true); + assertEquals(assignment._instanceStateMap, Collections.singletonMap("host1", ONLINE)); + assertEquals(assignment._availableInstances, Collections.singletonList("host1")); + // Second assignment should be the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 1, true); + assertEquals(assignment._instanceStateMap, targetInstanceStateMap); + assertEquals(assignment._availableInstances, Collections.singleton("host1")); + + // Without common instance, fist assignment should keep 1 instance from current assignment + targetInstanceStateMap = + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host4", "host5", "host6"), ONLINE); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 1, true); + assertEquals(assignment._instanceStateMap, Collections.singletonMap("host1", ONLINE)); + assertEquals(assignment._availableInstances, Collections.singleton("host1")); + // Second assignment should add 2 instances from target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 1, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host4", "host5"), ONLINE)); + assertEquals(assignment._availableInstances, Collections.singleton("host1")); + // Third assignment should remove the old instance from current assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 1, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host4", "host5"), ONLINE)); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host4", "host5"))); + // Fourth assignment should make the assignment the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 1, true); + assertEquals(assignment._instanceStateMap, targetInstanceStateMap); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host4", "host5"))); + + // With increasing number of replicas, fist assignment should keep 1 instance from current assignment + targetInstanceStateMap = + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host4", "host5", "host6", "host7"), ONLINE); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 1, true); + assertEquals(assignment._instanceStateMap, Collections.singletonMap("host1", ONLINE)); + assertEquals(assignment._availableInstances, Collections.singleton("host1")); + // Second assignment should add 3 instances from target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 1, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host4", "host5", "host6"), ONLINE)); + assertEquals(assignment._availableInstances, Collections.singleton("host1")); + // Third assignment should remove the old instance from current assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 1, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host4", "host5", "host6"), ONLINE)); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host4", "host5", "host6"))); + // Fourth assignment should make the assignment the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 1, true); + assertEquals(assignment._instanceStateMap, targetInstanceStateMap); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host4", "host5", "host6"))); + + // With decreasing number of replicas, fist assignment should keep 1 instance from current assignment + currentInstanceStateMap = + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host3", "host4"), ONLINE); + targetInstanceStateMap = + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host5", "host6", "host7"), ONLINE); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 1, true); + assertEquals(assignment._instanceStateMap, Collections.singletonMap("host1", ONLINE)); + assertEquals(assignment._availableInstances, Collections.singleton("host1")); + // Second assignment should add 2 instances from target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 1, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host5", "host6"), ONLINE)); + assertEquals(assignment._availableInstances, Collections.singleton("host1")); + // Third assignment should remove the old instance from current assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 1, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host5", "host6"), ONLINE)); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host5", "host6"))); + // Fourth assignment should make the assignment the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 1, true); + assertEquals(assignment._instanceStateMap, targetInstanceStateMap); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host5", "host6"))); + + // With increasing from 1 replica, fist assignment should keep the instance from current assignment, and add 2 + // instances from target assignment + currentInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Collections.singletonList("host1"), ONLINE); + targetInstanceStateMap = + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host2", "host3", "host4"), ONLINE); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 1, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host3"), ONLINE)); + assertEquals(assignment._availableInstances, Collections.singleton("host1")); + // Second assignment should remove the old instance from current assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 1, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host2", "host3"), ONLINE)); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host2", "host3"))); + // Third assignment should make the assignment the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 1, true); assertEquals(assignment._instanceStateMap, targetInstanceStateMap); assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host2", "host3"))); } @Test public void testTwoMinAvailableReplicas() { - // With 3 common instances, next assignment should be the same as target assignment + // With 3 common instances, first assignment should be the same as target assignment Map currentInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host3", "host4"), ONLINE); Map targetInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host3", "host5"), ONLINE); TableRebalancer.SingleSegmentAssignment assignment = - getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 2); + getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 2, false); assertEquals(assignment._instanceStateMap, targetInstanceStateMap); assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host2", "host3"))); - // With 2 common instances, next assignment should be the same as target assignment + // With 2 common instances, first assignment should be the same as target assignment targetInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host5", "host6"), ONLINE); - assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 2); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 2, false); assertEquals(assignment._instanceStateMap, targetInstanceStateMap); assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host2"))); - // With 1 common instance, next assignment should have 2 common instances with current assignment + // With 1 common instance, first assignment should have 2 common instances with current assignment targetInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host5", "host6", "host7"), ONLINE); - // [host1, host2, host5, host6] - assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 2); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 2, false); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host5", "host6"), ONLINE)); assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host2"))); - // Next round should make the assignment the same as target assignment - assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2); + // Second assignment should be the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, false); assertEquals(assignment._instanceStateMap, targetInstanceStateMap); assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host5", "host6"))); - // Without common instance, next assignment should have 2 common instances with current assignment + // Without common instance, first assignment should have 2 common instances with current assignment targetInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host5", "host6", "host7", "host8"), ONLINE); - // [host1, host2, host5, host6] - assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 2); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 2, false); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host5", "host6"), ONLINE)); assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host2"))); - // Next round should make the assignment the same as target assignment - assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2); + // Second assignment should be the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, false); assertEquals(assignment._instanceStateMap, targetInstanceStateMap); assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host5", "host6"))); - // With increasing number of replicas, next assignment should have 1 common instances with current assignment + // With increasing number of replicas, first assignment should have 1 common instances with current assignment targetInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host5", "host6", "host7", "host8", "host9"), ONLINE); // [host1, host2, host5, host6, host7] - assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 2); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 2, false); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host5", "host6", "host7"), ONLINE)); assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host2"))); - // Next round should make the assignment the same as target assignment - assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2); + // Second assignment should be the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, false); assertEquals(assignment._instanceStateMap, targetInstanceStateMap); assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host5", "host6", "host7"))); - // With decreasing number of replicas, next assignment should have 2 common instances with current assignment + // With decreasing number of replicas, first assignment should have 2 common instances with current assignment targetInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host5", "host6", "host7"), ONLINE); - // [host1, host2, host5] - assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 2); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 2, false); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host5"), ONLINE)); assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host2"))); - // Next round should have 2 common instances with first round assignment - // [host1, host5, host6] - assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2); + // Second assignment should have 2 common instances with first assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, false); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host5", "host6"), ONLINE)); assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host5"))); - // Next round should make the assignment the same as target assignment - assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2); + // Third assignment should be the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, false); assertEquals(assignment._instanceStateMap, targetInstanceStateMap); assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host5", "host6"))); - // With increasing from 1 replica, next assignment should have 1 common instances with current assignment + // With increasing from 1 replica, first assignment should have 1 common instances with current assignment // NOTE: This is the best we can do because we don't have 2 replicas available currentInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Collections.singletonList("host1"), ONLINE); targetInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host2", "host3", "host4"), ONLINE); - // [host1, host2, host3] - assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 2); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 2, false); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host3"), ONLINE)); assertEquals(assignment._availableInstances, Collections.singleton("host1")); - // Next round should make the assignment the same as target assignment - assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2); + // Second assignment should make the assignment the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, false); + assertEquals(assignment._instanceStateMap, targetInstanceStateMap); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host2", "host3"))); + } + + @Test + public void testTwoMinAvailableReplicasWithLowDiskMode() { + // With 3 common instances, first assignment should keep the common instances and remove the not common instance + Map currentInstanceStateMap = + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host3", "host4"), ONLINE); + Map targetInstanceStateMap = + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host3", "host5"), ONLINE); + TableRebalancer.SingleSegmentAssignment assignment = + getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host3"), ONLINE)); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host2", "host3"))); + // Second assignment should be the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, targetInstanceStateMap); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host2", "host3"))); + + // With 2 common instances, first assignment should keep the common instances and remove the not common instances + targetInstanceStateMap = + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host5", "host6"), ONLINE); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2"), ONLINE)); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host2"))); + // Second assignment should be the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, targetInstanceStateMap); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host2"))); + + // With 1 common instance, fist assignment should keep the common instance, and 1 more instance from current + // assignment + targetInstanceStateMap = + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host5", "host6", "host7"), ONLINE); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2"), ONLINE)); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host2"))); + // Second assignment should add 2 instances from target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host5", "host6"), ONLINE)); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host2"))); + // Third assignment should remove the old instance from current assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host5", "host6"), ONLINE)); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host5", "host6"))); + // Fourth assignment should make the assignment the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, targetInstanceStateMap); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host5", "host6"))); + + // Without common instance, fist assignment should keep 2 instances from current assignment + targetInstanceStateMap = + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host5", "host6", "host7", "host8"), ONLINE); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2"), ONLINE)); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host2"))); + // Second assignment should add 2 instances from target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host5", "host6"), ONLINE)); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host2"))); + // Third assignment should remove the old instances from current assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host5", "host6"), ONLINE)); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host5", "host6"))); + // Fourth assignment should make the assignment the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, targetInstanceStateMap); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host5", "host6"))); + + // With increasing number of replicas, fist assignment should keep 2 instances from current assignment + targetInstanceStateMap = + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host5", "host6", "host7", "host8", "host9"), ONLINE); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2"), ONLINE)); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host2"))); + // Second assignment should add 3 instances from target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host5", "host6", "host7"), ONLINE)); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host2"))); + // Third assignment should remove the old instances from current assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host5", "host6", "host7"), ONLINE)); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host5", "host6", "host7"))); + // Fourth assignment should make the assignment the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, targetInstanceStateMap); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host5", "host6", "host7"))); + + // With decreasing number of replicas, fist assignment should keep 2 instances from current assignment + targetInstanceStateMap = + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host5", "host6", "host7"), ONLINE); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2"), ONLINE)); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host2"))); + // Second assignment should add 1 instance from target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host5"), ONLINE)); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host2"))); + // Third assignment should remove 1 old instance from current assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host5"), ONLINE)); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host5"))); + // Forth assignment should add 1 more instance from target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host5", "host6"), ONLINE)); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host1", "host5"))); + // Fifth assignment should remove the other old instance from current assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host5", "host6"), ONLINE)); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host5", "host6"))); + // Sixth assignment should make the assignment the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, targetInstanceStateMap); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host5", "host6"))); + + // With increasing from 1 replica, fist assignment should keep the instance from current assignment, and add 2 + // instances from target assignment + // NOTE: This is the best we can do because we don't have 2 replicas available + currentInstanceStateMap = SegmentAssignmentUtils.getInstanceStateMap(Collections.singletonList("host1"), ONLINE); + targetInstanceStateMap = + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host2", "host3", "host4"), ONLINE); + assignment = getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host3"), ONLINE)); + assertEquals(assignment._availableInstances, Collections.singleton("host1")); + // Second assignment should remove the old instance from current assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, true); + assertEquals(assignment._instanceStateMap, + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host2", "host3"), ONLINE)); + assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host2", "host3"))); + // Third assignment should make the assignment the same as target assignment + assignment = getNextSingleSegmentAssignment(assignment._instanceStateMap, targetInstanceStateMap, 2, true); assertEquals(assignment._instanceStateMap, targetInstanceStateMap); assertEquals(assignment._availableInstances, new TreeSet<>(Arrays.asList("host2", "host3"))); } private TableRebalancer.SingleSegmentAssignment getNextSingleSegmentAssignment( - Map currentInstanceStateMap, Map targetInstanceStateMap, - int minAvailableReplicas) { + Map currentInstanceStateMap, Map targetInstanceStateMap, int minAvailableReplicas, + boolean lowDiskMode) { Map numSegmentsToOffloadMap = new HashMap<>(); for (String currentInstance : currentInstanceStateMap.keySet()) { numSegmentsToOffloadMap.put(currentInstance, 1); @@ -235,7 +558,7 @@ private TableRebalancer.SingleSegmentAssignment getNextSingleSegmentAssignment( } Map, Set>, Set> assignmentMap = new HashMap<>(); return TableRebalancer.getNextSingleSegmentAssignment(currentInstanceStateMap, targetInstanceStateMap, - minAvailableReplicas, numSegmentsToOffloadMap, assignmentMap); + minAvailableReplicas, lowDiskMode, numSegmentsToOffloadMap, assignmentMap); } @Test @@ -329,7 +652,7 @@ public void testAssignment() { // assignment for (boolean enableStrictReplicaGroup : Arrays.asList(false, true)) { Map> nextAssignment = - TableRebalancer.getNextAssignment(currentAssignment, targetAssignment, 2, enableStrictReplicaGroup); + TableRebalancer.getNextAssignment(currentAssignment, targetAssignment, 2, enableStrictReplicaGroup, false); assertEquals(nextAssignment, targetAssignment); } @@ -415,13 +738,14 @@ public void testAssignment() { // The second assignment should reach the target assignment for (boolean enableStrictReplicaGroup : Arrays.asList(false, true)) { Map> nextAssignment = - TableRebalancer.getNextAssignment(currentAssignment, targetAssignment, 2, enableStrictReplicaGroup); + TableRebalancer.getNextAssignment(currentAssignment, targetAssignment, 2, enableStrictReplicaGroup, false); assertEquals(nextAssignment.get("segment1").keySet(), new TreeSet<>(Arrays.asList("host1", "host2", "host4"))); assertEquals(nextAssignment.get("segment2").keySet(), new TreeSet<>(Arrays.asList("host2", "host4", "host5"))); assertEquals(nextAssignment.get("segment3").keySet(), new TreeSet<>(Arrays.asList("host1", "host2", "host4"))); assertEquals(nextAssignment.get("segment4").keySet(), new TreeSet<>(Arrays.asList("host2", "host4", "host5"))); - nextAssignment = TableRebalancer.getNextAssignment(nextAssignment, targetAssignment, 2, enableStrictReplicaGroup); + nextAssignment = + TableRebalancer.getNextAssignment(nextAssignment, targetAssignment, 2, enableStrictReplicaGroup, false); assertEquals(nextAssignment, targetAssignment); } @@ -474,7 +798,7 @@ public void testAssignment() { // Next assignment with 2 minimum available replicas without strict replica-group should reach the target assignment Map> nextAssignment = - TableRebalancer.getNextAssignment(currentAssignment, targetAssignment, 2, false); + TableRebalancer.getNextAssignment(currentAssignment, targetAssignment, 2, false, false); assertEquals(nextAssignment, targetAssignment); // Next assignment with 2 minimum available replicas with strict replica-group should finish in 2 steps: @@ -506,12 +830,429 @@ public void testAssignment() { // } // // The second assignment should reach the target assignment - nextAssignment = TableRebalancer.getNextAssignment(currentAssignment, targetAssignment, 2, true); + nextAssignment = TableRebalancer.getNextAssignment(currentAssignment, targetAssignment, 2, true, false); assertEquals(nextAssignment.get("segment1").keySet(), new TreeSet<>(Arrays.asList("host1", "host3", "host4"))); assertEquals(nextAssignment.get("segment2").keySet(), new TreeSet<>(Arrays.asList("host2", "host3", "host4"))); assertEquals(nextAssignment.get("segment3").keySet(), new TreeSet<>(Arrays.asList("host1", "host3", "host4"))); assertEquals(nextAssignment.get("segment4").keySet(), new TreeSet<>(Arrays.asList("host2", "host3", "host4"))); - nextAssignment = TableRebalancer.getNextAssignment(nextAssignment, targetAssignment, 2, true); + nextAssignment = TableRebalancer.getNextAssignment(nextAssignment, targetAssignment, 2, true, false); + assertEquals(nextAssignment, targetAssignment); + } + + @Test + public void testAssignmentWithLowDiskMode() { + // Current assignment: + // { + // "segment1": { + // "host1": "ONLINE", + // "host2": "ONLINE", + // "host3": "ONLINE" + // }, + // "segment2": { + // "host2": "ONLINE", + // "host3": "ONLINE", + // "host4": "ONLINE" + // }, + // "segment3": { + // "host1": "ONLINE", + // "host2": "ONLINE", + // "host3": "ONLINE" + // }, + // "segment4": { + // "host2": "ONLINE", + // "host3": "ONLINE", + // "host4": "ONLINE" + // } + // } + Map> currentAssignment = new TreeMap<>(); + currentAssignment.put("segment1", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host3"), ONLINE)); + currentAssignment.put("segment2", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host2", "host3", "host4"), ONLINE)); + currentAssignment.put("segment3", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host3"), ONLINE)); + currentAssignment.put("segment4", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host2", "host3", "host4"), ONLINE)); + + // Target assignment 1: + // { + // "segment1": { + // "host1": "ONLINE", + // "host3": "ONLINE", + // "host5": "ONLINE" + // }, + // "segment2": { + // "host2": "ONLINE", + // "host4": "ONLINE", + // "host6": "ONLINE" + // }, + // "segment3": { + // "host1": "ONLINE", + // "host3": "ONLINE", + // "host5": "ONLINE" + // }, + // "segment4": { + // "host2": "ONLINE", + // "host4": "ONLINE", + // "host6": "ONLINE" + // } + // } + Map> targetAssignment = new TreeMap<>(); + targetAssignment.put("segment1", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host3", "host5"), ONLINE)); + targetAssignment.put("segment2", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host2", "host4", "host6"), ONLINE)); + targetAssignment.put("segment3", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host3", "host5"), ONLINE)); + targetAssignment.put("segment4", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host2", "host4", "host6"), ONLINE)); + + // Number of segments to offload: + // { + // "host1": 0, + // "host2": 2, + // "host3": 2, + // "host4": 0, + // "host5": -2, + // "host6": -2 + // } + Map numSegmentsToOffloadMap = + TableRebalancer.getNumSegmentsToOffloadMap(currentAssignment, targetAssignment); + assertEquals(numSegmentsToOffloadMap.size(), 6); + assertEquals((int) numSegmentsToOffloadMap.get("host1"), 0); + assertEquals((int) numSegmentsToOffloadMap.get("host2"), 2); + assertEquals((int) numSegmentsToOffloadMap.get("host3"), 2); + assertEquals((int) numSegmentsToOffloadMap.get("host4"), 0); + assertEquals((int) numSegmentsToOffloadMap.get("host5"), -2); + assertEquals((int) numSegmentsToOffloadMap.get("host6"), -2); + + // Next assignment with 2 minimum available replicas with or without strict replica-group should finish in 2 steps: + // + // The first assignment will remove "segment1" and "segment3" from "host2", and remove "segment2" and "segment4" + // from "host3": + // { + // "segment1": { + // "host1": "ONLINE", + // "host3": "ONLINE" + // }, + // "segment2": { + // "host2": "ONLINE", + // "host4": "ONLINE" + // }, + // "segment3": { + // "host1": "ONLINE", + // "host3": "ONLINE" + // }, + // "segment4": { + // "host2": "ONLINE", + // "host4": "ONLINE" + // } + // } + // + // The second assignment should reach the target assignment + for (boolean enableStrictReplicaGroup : Arrays.asList(false, true)) { + Map> nextAssignment = + TableRebalancer.getNextAssignment(currentAssignment, targetAssignment, 2, enableStrictReplicaGroup, true); + assertEquals(nextAssignment.get("segment1").keySet(), new TreeSet<>(Arrays.asList("host1", "host3"))); + assertEquals(nextAssignment.get("segment2").keySet(), new TreeSet<>(Arrays.asList("host2", "host4"))); + assertEquals(nextAssignment.get("segment3").keySet(), new TreeSet<>(Arrays.asList("host1", "host3"))); + assertEquals(nextAssignment.get("segment4").keySet(), new TreeSet<>(Arrays.asList("host2", "host4"))); + + nextAssignment = + TableRebalancer.getNextAssignment(nextAssignment, targetAssignment, 2, enableStrictReplicaGroup, true); + assertEquals(nextAssignment, targetAssignment); + } + + // Target assignment 2: + // { + // "segment1": { + // "host2": "ONLINE", + // "host4": "ONLINE", + // "host6": "ONLINE" + // }, + // "segment2": { + // "host1": "ONLINE", + // "host4": "ONLINE", + // "host5": "ONLINE" + // }, + // "segment3": { + // "host2": "ONLINE", + // "host4": "ONLINE", + // "host6": "ONLINE" + // }, + // "segment4": { + // "host1": "ONLINE", + // "host4": "ONLINE", + // "host5": "ONLINE" + // } + // } + targetAssignment = new TreeMap<>(); + targetAssignment.put("segment1", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host2", "host4", "host6"), ONLINE)); + targetAssignment.put("segment2", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host4", "host5"), ONLINE)); + targetAssignment.put("segment3", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host2", "host4", "host6"), ONLINE)); + targetAssignment.put("segment4", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host4", "host5"), ONLINE)); + + // Number of segments to offload: + // { + // "host1": 0, + // "host2": 2, + // "host3": 4, + // "host4": -2, + // "host5": -2, + // "host6": -2 + // } + numSegmentsToOffloadMap = TableRebalancer.getNumSegmentsToOffloadMap(currentAssignment, targetAssignment); + assertEquals(numSegmentsToOffloadMap.size(), 6); + assertEquals((int) numSegmentsToOffloadMap.get("host1"), 0); + assertEquals((int) numSegmentsToOffloadMap.get("host2"), 2); + assertEquals((int) numSegmentsToOffloadMap.get("host3"), 4); + assertEquals((int) numSegmentsToOffloadMap.get("host4"), -2); + assertEquals((int) numSegmentsToOffloadMap.get("host5"), -2); + assertEquals((int) numSegmentsToOffloadMap.get("host6"), -2); + + // Next assignment with 2 minimum available replicas with or without strict replica-group should finish in 4 steps: + // + // The first assignment will remove "segment1" and "segment3" from "host3" (with the most segments to offload), and + // remove "segment2" and "segment4" from "host3 (with the most segments to offload)": + // { + // "segment1": { + // "host1": "ONLINE", + // "host2": "ONLINE" + // }, + // "segment2": { + // "host2": "ONLINE", + // "host4": "ONLINE" + // }, + // "segment3": { + // "host1": "ONLINE", + // "host2": "ONLINE" + // }, + // "segment4": { + // "host2": "ONLINE", + // "host4": "ONLINE" + // } + // } + // + // The second assignment will add "segment1" and "segment3" to "host4" (with the least segments to offload), and add + // "segment2" and "segment4" to "host5" (with the least segments to offload): + // { + // "segment1": { + // "host1": "ONLINE", + // "host2": "ONLINE", + // "host4": "ONLINE" + // }, + // "segment2": { + // "host2": "ONLINE", + // "host4": "ONLINE", + // "host5": "ONLINE" + // }, + // "segment3": { + // "host1": "ONLINE", + // "host2": "ONLINE", + // "host4": "ONLINE" + // }, + // "segment4": { + // "host2": "ONLINE", + // "host4": "ONLINE", + // "host5": "ONLINE" + // } + // } + // + // The third assignment will remove "segment1" and "segment3" from "host1", and remove "segment2" and "segment4" + // from "host2": + // { + // "segment1": { + // "host2": "ONLINE", + // "host4": "ONLINE" + // }, + // "segment2": { + // "host4": "ONLINE", + // "host5": "ONLINE" + // }, + // "segment3": { + // "host2": "ONLINE", + // "host4": "ONLINE" + // }, + // "segment4": { + // "host4": "ONLINE", + // "host5": "ONLINE" + // } + // } + // + // The fourth assignment should reach the target assignment + for (boolean enableStrictReplicaGroup : Arrays.asList(false, true)) { + Map> nextAssignment = + TableRebalancer.getNextAssignment(currentAssignment, targetAssignment, 2, enableStrictReplicaGroup, true); + assertEquals(nextAssignment.get("segment1").keySet(), new TreeSet<>(Arrays.asList("host1", "host2"))); + assertEquals(nextAssignment.get("segment2").keySet(), new TreeSet<>(Arrays.asList("host2", "host4"))); + assertEquals(nextAssignment.get("segment3").keySet(), new TreeSet<>(Arrays.asList("host1", "host2"))); + assertEquals(nextAssignment.get("segment4").keySet(), new TreeSet<>(Arrays.asList("host2", "host4"))); + + nextAssignment = + TableRebalancer.getNextAssignment(nextAssignment, targetAssignment, 2, enableStrictReplicaGroup, true); + assertEquals(nextAssignment.get("segment1").keySet(), new TreeSet<>(Arrays.asList("host1", "host2", "host4"))); + assertEquals(nextAssignment.get("segment2").keySet(), new TreeSet<>(Arrays.asList("host2", "host4", "host5"))); + assertEquals(nextAssignment.get("segment3").keySet(), new TreeSet<>(Arrays.asList("host1", "host2", "host4"))); + assertEquals(nextAssignment.get("segment4").keySet(), new TreeSet<>(Arrays.asList("host2", "host4", "host5"))); + + nextAssignment = + TableRebalancer.getNextAssignment(nextAssignment, targetAssignment, 2, enableStrictReplicaGroup, true); + assertEquals(nextAssignment.get("segment1").keySet(), new TreeSet<>(Arrays.asList("host2", "host4"))); + assertEquals(nextAssignment.get("segment2").keySet(), new TreeSet<>(Arrays.asList("host4", "host5"))); + assertEquals(nextAssignment.get("segment3").keySet(), new TreeSet<>(Arrays.asList("host2", "host4"))); + assertEquals(nextAssignment.get("segment4").keySet(), new TreeSet<>(Arrays.asList("host4", "host5"))); + + nextAssignment = + TableRebalancer.getNextAssignment(nextAssignment, targetAssignment, 2, enableStrictReplicaGroup, true); + assertEquals(nextAssignment, targetAssignment); + } + + // Target assignment 3: + // { + // "segment1": { + // "host1": "ONLINE", + // "host3": "ONLINE", + // "host4": "ONLINE" + // }, + // "segment2": { + // "host1": "ONLINE", + // "host3": "ONLINE", + // "host4": "ONLINE" + // }, + // "segment3": { + // "host1": "ONLINE", + // "host3": "ONLINE", + // "host4": "ONLINE" + // }, + // "segment4": { + // "host1": "ONLINE", + // "host3": "ONLINE", + // "host4": "ONLINE" + // } + // } + targetAssignment = new TreeMap<>(); + targetAssignment.put("segment1", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host3", "host4"), ONLINE)); + targetAssignment.put("segment2", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host3", "host4"), ONLINE)); + targetAssignment.put("segment3", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host3", "host4"), ONLINE)); + targetAssignment.put("segment4", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host3", "host4"), ONLINE)); + + // Number of segments to offload: + // { + // "host1": -2, + // "host2": 4, + // "host3": 0, + // "host4": -2 + // } + numSegmentsToOffloadMap = TableRebalancer.getNumSegmentsToOffloadMap(currentAssignment, targetAssignment); + assertEquals(numSegmentsToOffloadMap.size(), 4); + assertEquals((int) numSegmentsToOffloadMap.get("host1"), -2); + assertEquals((int) numSegmentsToOffloadMap.get("host2"), 4); + assertEquals((int) numSegmentsToOffloadMap.get("host3"), 0); + assertEquals((int) numSegmentsToOffloadMap.get("host4"), -2); + + // Next assignment with 2 minimum available replicas without strict replica-group should finish in 2 steps: + // + // The first assignment will remove "segment1" and "segment3" from "host2", and remove "segment2" and "segment4" + // from "host2": + // { + // "segment1": { + // "host1": "ONLINE", + // "host3": "ONLINE" + // }, + // "segment2": { + // "host3": "ONLINE", + // "host4": "ONLINE" + // }, + // "segment3": { + // "host1": "ONLINE", + // "host3": "ONLINE" + // }, + // "segment4": { + // "host3": "ONLINE", + // "host4": "ONLINE" + // } + // } + // + // The second assignment should reach the target assignment + Map> nextAssignment = + TableRebalancer.getNextAssignment(currentAssignment, targetAssignment, 2, false, true); + assertEquals(nextAssignment.get("segment1").keySet(), new TreeSet<>(Arrays.asList("host1", "host3"))); + assertEquals(nextAssignment.get("segment2").keySet(), new TreeSet<>(Arrays.asList("host3", "host4"))); + assertEquals(nextAssignment.get("segment3").keySet(), new TreeSet<>(Arrays.asList("host1", "host3"))); + assertEquals(nextAssignment.get("segment4").keySet(), new TreeSet<>(Arrays.asList("host3", "host4"))); + + nextAssignment = TableRebalancer.getNextAssignment(nextAssignment, targetAssignment, 2, false, true); + assertEquals(nextAssignment, targetAssignment); + + // Next assignment with 2 minimum available replicas with strict replica-group should finish in 3 steps: + // + // The first assignment will remove "segment1" and "segment3" from "host2", and remove "segment2" and "segment4" + // from "host2": + // { + // "segment1": { + // "host1": "ONLINE", + // "host3": "ONLINE" + // }, + // "segment2": { + // "host3": "ONLINE", + // "host4": "ONLINE" + // }, + // "segment3": { + // "host1": "ONLINE", + // "host3": "ONLINE" + // }, + // "segment4": { + // "host3": "ONLINE", + // "host4": "ONLINE" + // } + // } + // + // The second assignment will bring "segment1" and "segment3" to the target state. It cannot bring "segment2" and + // "segment4" to the target state because "host1" and "host4" might be unavailable for strict replica-group routing, + // which breaks the minimum available replicas requirement: + // { + // "segment1": { + // "host1": "ONLINE", + // "host3": "ONLINE", + // "host4": "ONLINE" + // }, + // "segment2": { + // "host3": "ONLINE", + // "host4": "ONLINE" + // }, + // "segment3": { + // "host1": "ONLINE", + // "host3": "ONLINE", + // "host4": "ONLINE" + // }, + // "segment4": { + // "host3": "ONLINE", + // "host4": "ONLINE" + // } + // } + // + // The third assignment should reach the target assignment + nextAssignment = TableRebalancer.getNextAssignment(currentAssignment, targetAssignment, 2, true, true); + assertEquals(nextAssignment.get("segment1").keySet(), new TreeSet<>(Arrays.asList("host1", "host3"))); + assertEquals(nextAssignment.get("segment2").keySet(), new TreeSet<>(Arrays.asList("host3", "host4"))); + assertEquals(nextAssignment.get("segment3").keySet(), new TreeSet<>(Arrays.asList("host1", "host3"))); + assertEquals(nextAssignment.get("segment4").keySet(), new TreeSet<>(Arrays.asList("host3", "host4"))); + + nextAssignment = TableRebalancer.getNextAssignment(nextAssignment, targetAssignment, 2, true, true); + assertEquals(nextAssignment.get("segment1").keySet(), new TreeSet<>(Arrays.asList("host1", "host3", "host4"))); + assertEquals(nextAssignment.get("segment2").keySet(), new TreeSet<>(Arrays.asList("host3", "host4"))); + assertEquals(nextAssignment.get("segment3").keySet(), new TreeSet<>(Arrays.asList("host1", "host3", "host4"))); + assertEquals(nextAssignment.get("segment4").keySet(), new TreeSet<>(Arrays.asList("host3", "host4"))); + + nextAssignment = TableRebalancer.getNextAssignment(nextAssignment, targetAssignment, 2, true, true); assertEquals(nextAssignment, targetAssignment); } diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/TestZkBasedTableRebalanceObserver.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/TestZkBasedTableRebalanceObserver.java new file mode 100644 index 000000000000..245aa73aeea1 --- /dev/null +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/TestZkBasedTableRebalanceObserver.java @@ -0,0 +1,111 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.controller.helix.core.rebalance; + +import java.util.Arrays; +import java.util.Map; +import java.util.TreeMap; +import org.apache.pinot.common.metrics.ControllerMetrics; +import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; +import org.apache.pinot.controller.helix.core.assignment.segment.SegmentAssignmentUtils; +import org.mockito.Mockito; +import org.testng.annotations.Test; + +import static org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel.ERROR; +import static org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel.ONLINE; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; + + +public class TestZkBasedTableRebalanceObserver { + + // This is a test to verify if Zk stats are pushed out correctly + @Test + void testZkObserverTracking() { + PinotHelixResourceManager pinotHelixResourceManager = mock(PinotHelixResourceManager.class); + // Mocking this. We will verify using numZkUpdate stat + when(pinotHelixResourceManager.addControllerJobToZK(any(), any(), any())).thenReturn(true); + ControllerMetrics controllerMetrics = Mockito.mock(ControllerMetrics.class); + TableRebalanceContext retryCtx = new TableRebalanceContext(); + retryCtx.setConfig(new RebalanceConfig()); + ZkBasedTableRebalanceObserver observer = + new ZkBasedTableRebalanceObserver("dummy", "dummyId", retryCtx, pinotHelixResourceManager); + Map> source = new TreeMap<>(); + Map> target = new TreeMap<>(); + target.put("segment1", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host3"), ONLINE)); + source.put("segment2", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host2", "host3", "host4"), ONLINE)); + + observer.onTrigger(TableRebalanceObserver.Trigger.START_TRIGGER, source, target); + assertEquals(observer.getNumUpdatesToZk(), 1); + observer.onTrigger(TableRebalanceObserver.Trigger.IDEAL_STATE_CHANGE_TRIGGER, source, source); + observer.onTrigger(TableRebalanceObserver.Trigger.EXTERNAL_VIEW_TO_IDEAL_STATE_CONVERGENCE_TRIGGER, source, source); + assertEquals(observer.getNumUpdatesToZk(), 1); + observer.onTrigger(TableRebalanceObserver.Trigger.IDEAL_STATE_CHANGE_TRIGGER, source, target); + observer.onTrigger(TableRebalanceObserver.Trigger.EXTERNAL_VIEW_TO_IDEAL_STATE_CONVERGENCE_TRIGGER, source, target); + assertEquals(observer.getNumUpdatesToZk(), 3); + } + + @Test + void testDifferenceBetweenTableRebalanceStates() { + Map> target = new TreeMap<>(); + target.put("segment1", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host3"), ONLINE)); + target.put("segment2", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host2", "host3", "host4"), ONLINE)); + + // Stats when there's nothing to rebalance + TableRebalanceProgressStats.RebalanceStateStats stats = + ZkBasedTableRebalanceObserver.getDifferenceBetweenTableRebalanceStates(target, target); + assertEquals(stats._segmentsToRebalance, 0); + assertEquals(stats._segmentsMissing, 0); + assertEquals(stats._percentSegmentsToRebalance, 0.0); + + // Stats when there's something to converge + Map> current = new TreeMap<>(); + current.put("segment1", SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1"), ONLINE)); + current.put("segment2", SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host2"), ONLINE)); + + stats = ZkBasedTableRebalanceObserver.getDifferenceBetweenTableRebalanceStates(target, current); + assertEquals(stats._segmentsToRebalance, 2); + assertEquals(stats._percentSegmentsToRebalance, 100.0); + assertEquals(stats._replicasToRebalance, 4); + + // Stats when there are errors + current = new TreeMap<>(); + current.put("segment1", SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1"), ERROR)); + + stats = ZkBasedTableRebalanceObserver.getDifferenceBetweenTableRebalanceStates(target, current); + assertEquals(stats._segmentsToRebalance, 2); + assertEquals(stats._segmentsMissing, 1); + assertEquals(stats._replicasToRebalance, 3); + + // Stats when partially converged + current = new TreeMap<>(); + current.put("segment1", + SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host1", "host2", "host3"), ONLINE)); + current.put("segment2", SegmentAssignmentUtils.getInstanceStateMap(Arrays.asList("host2", "host3"), ONLINE)); + + stats = ZkBasedTableRebalanceObserver.getDifferenceBetweenTableRebalanceStates(target, current); + assertEquals(stats._percentSegmentsToRebalance, 50.0); + } +} diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/tenant/TenantRebalancerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/tenant/TenantRebalancerTest.java new file mode 100644 index 000000000000..76189e926867 --- /dev/null +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/rebalance/tenant/TenantRebalancerTest.java @@ -0,0 +1,196 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.controller.helix.core.rebalance.tenant; + +import com.fasterxml.jackson.core.JsonProcessingException; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import org.apache.pinot.common.assignment.InstancePartitions; +import org.apache.pinot.common.metadata.controllerjob.ControllerJobType; +import org.apache.pinot.common.utils.config.TagNameUtils; +import org.apache.pinot.controller.helix.ControllerTest; +import org.apache.pinot.controller.helix.core.rebalance.RebalanceJobConstants; +import org.apache.pinot.controller.helix.core.rebalance.RebalanceResult; +import org.apache.pinot.controller.utils.SegmentMetadataMockUtils; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.config.table.assignment.InstancePartitionsType; +import org.apache.pinot.spi.utils.JsonUtils; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + + +public class TenantRebalancerTest extends ControllerTest { + + private static final String DEFAULT_TENANT_NAME = "DefaultTenant"; + private static final String TENANT_NAME = "TestTenant"; + private static final String RAW_TABLE_NAME_A = "testTableA"; + private static final String OFFLINE_TABLE_NAME_A = TableNameBuilder.OFFLINE.tableNameWithType(RAW_TABLE_NAME_A); + private static final String RAW_TABLE_NAME_B = "testTableB"; + private static final String OFFLINE_TABLE_NAME_B = TableNameBuilder.OFFLINE.tableNameWithType(RAW_TABLE_NAME_B); + private static final int NUM_REPLICAS = 3; + ExecutorService _executorService; + + @BeforeClass + public void setUp() + throws Exception { + startZk(); + startController(); + addFakeBrokerInstancesToAutoJoinHelixCluster(1, true); + _executorService = Executors.newFixedThreadPool(3); + } + + @Test + public void testRebalance() + throws Exception { + int numServers = 3; + for (int i = 0; i < numServers; i++) { + addFakeServerInstanceToAutoJoinHelixCluster(SERVER_INSTANCE_ID_PREFIX + i, true); + } + + TenantRebalancer tenantRebalancer = new DefaultTenantRebalancer(_helixResourceManager, _executorService); + + // tag all servers and brokers to test tenant + addTenantTagToInstances(TENANT_NAME); + + // create 2 schemas + addDummySchema(RAW_TABLE_NAME_A); + addDummySchema(RAW_TABLE_NAME_B); + + // create 2 tables, one on each of test tenant and default tenant + createTableWithSegments(RAW_TABLE_NAME_A, DEFAULT_TENANT_NAME); + createTableWithSegments(RAW_TABLE_NAME_B, TENANT_NAME); + + // Add 3 more servers which will be tagged to default tenant + int numServersToAdd = 3; + for (int i = 0; i < numServersToAdd; i++) { + addFakeServerInstanceToAutoJoinHelixCluster(SERVER_INSTANCE_ID_PREFIX + (numServers + i), true); + } + + Map> oldSegmentAssignment = + _helixResourceManager.getTableIdealState(OFFLINE_TABLE_NAME_B).getRecord().getMapFields(); + + // rebalance the tables on test tenant + TenantRebalanceConfig config = new TenantRebalanceConfig(); + config.setTenantName(TENANT_NAME); + config.setVerboseResult(true); + TenantRebalanceResult result = tenantRebalancer.rebalance(config); + RebalanceResult rebalanceResult = result.getRebalanceTableResults().get(OFFLINE_TABLE_NAME_B); + Map> rebalancedAssignment = rebalanceResult.getSegmentAssignment(); + // assignment should not change, with a NO_OP status as no now server is added to test tenant + assertEquals(rebalanceResult.getStatus(), RebalanceResult.Status.NO_OP); + assertEquals(oldSegmentAssignment, rebalancedAssignment); + + // rebalance the tables on default tenant + config.setTenantName(DEFAULT_TENANT_NAME); + result = tenantRebalancer.rebalance(config); + // rebalancing default tenant should distribute the segment of table A over 6 servers + rebalanceResult = result.getRebalanceTableResults().get(OFFLINE_TABLE_NAME_A); + InstancePartitions partitions = rebalanceResult.getInstanceAssignment().get(InstancePartitionsType.OFFLINE); + assertEquals(partitions.getPartitionToInstancesMap().get("0_0").size(), 6); + + // ensure the ideal state and external view converges + assertTrue(waitForCompletion(result.getJobId())); + TenantRebalanceProgressStats progressStats = getProgress(result.getJobId()); + assertTrue(progressStats.getTableRebalanceJobIdMap().containsKey(OFFLINE_TABLE_NAME_A)); + assertEquals(progressStats.getTableStatusMap().get(OFFLINE_TABLE_NAME_A), + TenantRebalanceProgressStats.TableStatus.PROCESSED.name()); + Map> idealState = + _helixResourceManager.getTableIdealState(OFFLINE_TABLE_NAME_A).getRecord().getMapFields(); + Map> externalView = + _helixResourceManager.getTableExternalView(OFFLINE_TABLE_NAME_A).getRecord().getMapFields(); + assertEquals(idealState, externalView); + } + + private boolean waitForCompletion(String jobId) { + int retries = 5; + while (retries > 0) { + try { + TenantRebalanceProgressStats stats = getProgress(jobId); + if (stats != null && stats.getRemainingTables() == 0) { + return true; + } + retries--; + Thread.sleep(2000); + } catch (JsonProcessingException | InterruptedException e) { + return false; + } + } + return false; + } + + private TenantRebalanceProgressStats getProgress(String jobId) + throws JsonProcessingException { + Map controllerJobZKMetadata = + _helixResourceManager.getControllerJobZKMetadata(jobId, ControllerJobType.TENANT_REBALANCE); + if (controllerJobZKMetadata == null) { + return null; + } + return JsonUtils.stringToObject( + controllerJobZKMetadata.get(RebalanceJobConstants.JOB_METADATA_KEY_REBALANCE_PROGRESS_STATS), + TenantRebalanceProgressStats.class); + } + + private void createTableWithSegments(String rawTableName, String tenant) + throws IOException { + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(rawTableName) + .setServerTenant(tenant).setBrokerTenant(tenant).setNumReplicas(NUM_REPLICAS).build(); + // Create the table + _helixResourceManager.addTable(tableConfig); + // Add the segments + int numSegments = 10; + String offlineTableName = TableNameBuilder.OFFLINE.tableNameWithType(rawTableName); + for (int i = 0; i < numSegments; i++) { + _helixResourceManager.addNewSegment(offlineTableName, + SegmentMetadataMockUtils.mockSegmentMetadata(rawTableName, "segment_" + i), null); + } + } + + private void addTenantTagToInstances(String testTenant) { + String offlineTag = TagNameUtils.getOfflineTagForTenant(testTenant); + String brokerTag = TagNameUtils.getBrokerTagForTenant(testTenant); + _helixResourceManager.getAllInstances().forEach(instance -> { + List existingTags = _helixResourceManager.getHelixInstanceConfig(instance).getTags(); + if (instance.startsWith(SERVER_INSTANCE_ID_PREFIX)) { + existingTags.add(offlineTag); + } else if (instance.startsWith(BROKER_INSTANCE_ID_PREFIX)) { + existingTags.add(brokerTag); + } + _helixResourceManager.updateInstanceTags(instance, String.join(",", existingTags), true); + }); + } + + @AfterClass + public void tearDown() { + stopFakeInstances(); + stopController(); + stopZk(); + _executorService.shutdown(); + } +} diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/relocation/SegmentRelocatorTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/relocation/SegmentRelocatorTest.java index b5144d2a6f1a..35a3106ecfa4 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/relocation/SegmentRelocatorTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/relocation/SegmentRelocatorTest.java @@ -18,22 +18,28 @@ */ package org.apache.pinot.controller.helix.core.relocation; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import org.apache.commons.lang3.RandomUtils; import org.apache.helix.ClusterMessagingService; import org.apache.helix.Criteria; import org.apache.helix.zookeeper.datamodel.ZNRecord; +import org.apache.http.conn.HttpClientConnectionManager; import org.apache.pinot.common.messages.SegmentReloadMessage; -import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; -import org.apache.pinot.common.tier.FixedTierSegmentSelector; -import org.apache.pinot.common.tier.Tier; +import org.apache.pinot.common.metrics.ControllerMetrics; +import org.apache.pinot.controller.ControllerConf; +import org.apache.pinot.controller.LeadControllerManager; import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; import org.apache.pinot.controller.util.TableTierReader; import org.apache.pinot.spi.utils.CommonConstants; +import org.apache.pinot.util.TestUtils; import org.mockito.ArgumentCaptor; import org.testng.annotations.Test; @@ -43,7 +49,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import static org.testng.Assert.fail; @@ -109,36 +114,93 @@ public void testTriggerLocalTierMigration() { } @Test - public void testUpdateSegmentTargetTierBackToDefault() { - String tableName = "table01_OFFLINE"; - String segmentName = "seg01"; - PinotHelixResourceManager helixMgrMock = mock(PinotHelixResourceManager.class); - when(helixMgrMock.getSegmentMetadataZnRecord(tableName, segmentName)).thenReturn( - createSegmentMetadataZNRecord(segmentName, "hotTier")); - // Move back to default as not tier configs. - List sortedTiers = new ArrayList<>(); - SegmentRelocator.updateSegmentTargetTier(tableName, "seg01", sortedTiers, helixMgrMock); - ArgumentCaptor recordCapture = ArgumentCaptor.forClass(SegmentZKMetadata.class); - verify(helixMgrMock).updateZkMetadata(eq(tableName), recordCapture.capture(), eq(10)); - SegmentZKMetadata record = recordCapture.getValue(); - assertNull(record.getTier()); + public void testRebalanceTablesSequentially() + throws InterruptedException { + ControllerConf conf = mock(ControllerConf.class); + when(conf.isSegmentRelocatorRebalanceTablesSequentially()).thenReturn(true); + SegmentRelocator relocator = + new SegmentRelocator(mock(PinotHelixResourceManager.class), mock(LeadControllerManager.class), conf, + mock(ControllerMetrics.class), mock(ExecutorService.class), mock(HttpClientConnectionManager.class)); + int cnt = 10; + for (int i = 0; i < cnt; i++) { + relocator.putTableToWait("t_" + i); + } + for (int i = 0; i < cnt; i++) { + relocator.putTableToWait("t_" + RandomUtils.nextInt(0, cnt)); + } + // All tables are tracked and no duplicate table names. + Queue waitingQueue = relocator.getWaitingQueue(); + assertEquals(waitingQueue.size(), cnt); + Set tablesInQueue = new HashSet<>(waitingQueue); + for (int i = 0; i < cnt; i++) { + assertTrue(tablesInQueue.contains("t_" + i)); + } + String[] tableName = new String[1]; + for (int i = 0; i < cnt; i++) { + assertEquals(waitingQueue.size(), cnt - i); + relocator.rebalanceWaitingTable(s -> tableName[0] = s); + assertEquals(tableName[0], "t_" + i); + } + assertEquals(waitingQueue.size(), 0); } @Test - public void testUpdateSegmentTargetTierToNewTier() { - String tableName = "table01_OFFLINE"; - String segmentName = "seg01"; - PinotHelixResourceManager helixMgrMock = mock(PinotHelixResourceManager.class); - when(helixMgrMock.getSegmentMetadataZnRecord(tableName, segmentName)).thenReturn( - createSegmentMetadataZNRecord(segmentName, "hotTier")); - // Move to new tier as set in tier configs. - List sortedTiers = Collections.singletonList( - new Tier("coldTier", new FixedTierSegmentSelector(null, Collections.singleton("seg01")), null)); - SegmentRelocator.updateSegmentTargetTier(tableName, "seg01", sortedTiers, helixMgrMock); - ArgumentCaptor recordCapture = ArgumentCaptor.forClass(SegmentZKMetadata.class); - verify(helixMgrMock).updateZkMetadata(eq(tableName), recordCapture.capture(), eq(10)); - SegmentZKMetadata record = recordCapture.getValue(); - assertEquals(record.getTier(), "coldTier"); + public void testRebalanceTablesSequentiallyWithMultiRequesters() { + ControllerConf conf = mock(ControllerConf.class); + when(conf.isSegmentRelocatorRebalanceTablesSequentially()).thenReturn(true); + SegmentRelocator relocator = + new SegmentRelocator(mock(PinotHelixResourceManager.class), mock(LeadControllerManager.class), conf, + mock(ControllerMetrics.class), mock(ExecutorService.class), mock(HttpClientConnectionManager.class)); + ExecutorService runner = Executors.newCachedThreadPool(); + int cnt = 10; + // Three threads to submit tables randomly. + runner.submit(() -> { + for (int i = 0; i < cnt; i++) { + relocator.putTableToWait("t_" + RandomUtils.nextInt(0, cnt)); + Thread.sleep(RandomUtils.nextLong(10, 30)); + } + return null; + }); + runner.submit(() -> { + for (int i = 0; i < cnt; i++) { + relocator.putTableToWait("t_" + RandomUtils.nextInt(0, cnt)); + Thread.sleep(RandomUtils.nextLong(10, 30)); + } + return null; + }); + runner.submit(() -> { + // This thread puts all tables into queue so let it kick in a bit later. + Thread.sleep(100); + for (int i = 0; i < cnt; i++) { + relocator.putTableToWait("t_" + i); + Thread.sleep(RandomUtils.nextLong(10, 30)); + } + return null; + }); + try { + Queue waitingQueue = relocator.getWaitingQueue(); + TestUtils.waitForCondition((Void v) -> waitingQueue.size() == cnt, 100, 3000, + "Expecting all tables get in waiting queue"); + // No duplicates of tasks. + Set tablesInQueue = new HashSet<>(waitingQueue); + for (int i = 0; i < cnt; i++) { + assertTrue(tablesInQueue.contains("t_" + i)); + } + // t_X will get its turn. + relocator.putTableToWait("t_X"); + assertEquals(waitingQueue.size(), cnt + 1); + TestUtils.waitForCondition((Void v) -> { + try { + String[] tableName = new String[1]; + relocator.rebalanceWaitingTable(s -> tableName[0] = s); + return tableName[0].equals("t_X"); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }, 10, 3000, "Table t_X should get its turn"); + } finally { + runner.shutdownNow(); + } } private static ZNRecord createSegmentMetadataZNRecord(String segmentName, String tierName) { diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/retention/RetentionManagerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/retention/RetentionManagerTest.java index 85aba90f36d0..b3e656de9ead 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/retention/RetentionManagerTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/retention/RetentionManagerTest.java @@ -19,7 +19,6 @@ package org.apache.pinot.controller.helix.core.retention; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -43,14 +42,13 @@ import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.apache.pinot.spi.utils.builder.TableNameBuilder; -import org.mockito.ArgumentMatchers; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.testng.Assert; import org.testng.annotations.Test; +import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; public class RetentionManagerTest { @@ -59,8 +57,7 @@ public class RetentionManagerTest { private static final String OFFLINE_TABLE_NAME = TableNameBuilder.OFFLINE.tableNameWithType(TEST_TABLE_NAME); private static final String REALTIME_TABLE_NAME = TableNameBuilder.REALTIME.tableNameWithType(TEST_TABLE_NAME); - private void testDifferentTimeUnits(long pastTimeStamp, TimeUnit timeUnit, long dayAfterTomorrowTimeStamp) - throws Exception { + private void testDifferentTimeUnits(long pastTimeStamp, TimeUnit timeUnit, long dayAfterTomorrowTimeStamp) { List segmentsZKMetadata = new ArrayList<>(); // Create metadata for 10 segments really old, that will be removed by the retention manager. final int numOlderSegments = 10; @@ -77,11 +74,10 @@ private void testDifferentTimeUnits(long pastTimeStamp, TimeUnit timeUnit, long segmentsZKMetadata.add(segmentZKMetadata); } final TableConfig tableConfig = createOfflineTableConfig(); - PinotHelixResourceManager pinotHelixResourceManager = mock(PinotHelixResourceManager.class); - setupPinotHelixResourceManager(tableConfig, removedSegments, pinotHelixResourceManager); - LeadControllerManager leadControllerManager = mock(LeadControllerManager.class); when(leadControllerManager.isLeaderForTable(anyString())).thenReturn(true); + PinotHelixResourceManager pinotHelixResourceManager = mock(PinotHelixResourceManager.class); + setupPinotHelixResourceManager(tableConfig, removedSegments, pinotHelixResourceManager, leadControllerManager); when(pinotHelixResourceManager.getTableConfig(OFFLINE_TABLE_NAME)).thenReturn(tableConfig); when(pinotHelixResourceManager.getSegmentsZKMetadata(OFFLINE_TABLE_NAME)).thenReturn(segmentsZKMetadata); @@ -98,15 +94,14 @@ private void testDifferentTimeUnits(long pastTimeStamp, TimeUnit timeUnit, long SegmentDeletionManager deletionManager = pinotHelixResourceManager.getSegmentDeletionManager(); // Verify that the removeAgedDeletedSegments() method in deletion manager is actually called. - verify(deletionManager, times(1)).removeAgedDeletedSegments(); + verify(deletionManager, times(1)).removeAgedDeletedSegments(leadControllerManager); // Verify that the deleteSegments method is actually called. verify(pinotHelixResourceManager, times(1)).deleteSegments(anyString(), anyList()); } @Test - public void testRetentionWithMinutes() - throws Exception { + public void testRetentionWithMinutes() { final long theDayAfterTomorrowSinceEpoch = System.currentTimeMillis() / 1000 / 60 / 60 / 24 + 2; final long minutesSinceEpochTimeStamp = theDayAfterTomorrowSinceEpoch * 24 * 60; final long pastMinutesSinceEpoch = 22383360L; @@ -114,8 +109,7 @@ public void testRetentionWithMinutes() } @Test - public void testRetentionWithSeconds() - throws Exception { + public void testRetentionWithSeconds() { final long theDayAfterTomorrowSinceEpoch = System.currentTimeMillis() / 1000 / 60 / 60 / 24 + 2; final long secondsSinceEpochTimeStamp = theDayAfterTomorrowSinceEpoch * 24 * 60 * 60; final long pastSecondsSinceEpoch = 1343001600L; @@ -123,8 +117,7 @@ public void testRetentionWithSeconds() } @Test - public void testRetentionWithMillis() - throws Exception { + public void testRetentionWithMillis() { final long theDayAfterTomorrowSinceEpoch = System.currentTimeMillis() / 1000 / 60 / 60 / 24 + 2; final long millisSinceEpochTimeStamp = theDayAfterTomorrowSinceEpoch * 24 * 60 * 60 * 1000; final long pastMillisSinceEpoch = 1343001600000L; @@ -132,8 +125,7 @@ public void testRetentionWithMillis() } @Test - public void testRetentionWithHours() - throws Exception { + public void testRetentionWithHours() { final long theDayAfterTomorrowSinceEpoch = System.currentTimeMillis() / 1000 / 60 / 60 / 24 + 2; final long hoursSinceEpochTimeStamp = theDayAfterTomorrowSinceEpoch * 24; final long pastHoursSinceEpoch = 373056L; @@ -141,8 +133,7 @@ public void testRetentionWithHours() } @Test - public void testRetentionWithDays() - throws Exception { + public void testRetentionWithDays() { final long daysSinceEpochTimeStamp = System.currentTimeMillis() / 1000 / 60 / 60 / 24 + 2; final long pastDaysSinceEpoch = 15544L; testDifferentTimeUnits(pastDaysSinceEpoch, TimeUnit.DAYS, daysSinceEpochTimeStamp); @@ -155,15 +146,14 @@ private TableConfig createOfflineTableConfig() { private TableConfig createRealtimeTableConfig1(int replicaCount) { Map streamConfigs = FakeStreamConfigUtils.getDefaultLowLevelStreamConfigs().getStreamConfigsMap(); - return new TableConfigBuilder(TableType.REALTIME).setTableName(TEST_TABLE_NAME).setLLC(true) - .setStreamConfigs(streamConfigs).setRetentionTimeUnit("DAYS").setRetentionTimeValue("5") - .setNumReplicas(replicaCount).build(); + return new TableConfigBuilder(TableType.REALTIME).setTableName(TEST_TABLE_NAME).setStreamConfigs(streamConfigs) + .setRetentionTimeUnit("DAYS").setRetentionTimeValue("5").setNumReplicas(replicaCount).build(); } private void setupPinotHelixResourceManager(TableConfig tableConfig, final List removedSegments, - PinotHelixResourceManager resourceManager) { - final String tableNameWithType = tableConfig.getTableName(); - when(resourceManager.getAllTables()).thenReturn(Collections.singletonList(tableNameWithType)); + PinotHelixResourceManager resourceManager, LeadControllerManager leadControllerManager) { + String tableNameWithType = tableConfig.getTableName(); + when(resourceManager.getAllTables()).thenReturn(List.of(tableNameWithType)); ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); when(resourceManager.getPropertyStore()).thenReturn(propertyStore); @@ -171,38 +161,27 @@ private void setupPinotHelixResourceManager(TableConfig tableConfig, final List< SegmentDeletionManager deletionManager = mock(SegmentDeletionManager.class); // Ignore the call to SegmentDeletionManager.removeAgedDeletedSegments. we only test that the call is made once per // run of the retention manager - doAnswer(new Answer() { - @Override - public Void answer(InvocationOnMock invocationOnMock) - throws Throwable { - return null; - } - }).when(deletionManager).removeAgedDeletedSegments(); + doAnswer(invocationOnMock -> null).when(deletionManager).removeAgedDeletedSegments(leadControllerManager); when(resourceManager.getSegmentDeletionManager()).thenReturn(deletionManager); // If and when PinotHelixResourceManager.deleteSegments() is invoked, make sure that the segments deleted // are exactly the same as the ones we expect to be deleted. - doAnswer(new Answer() { - @Override - public Object answer(InvocationOnMock invocationOnMock) - throws Throwable { - Object[] args = invocationOnMock.getArguments(); - String tableNameArg = (String) args[0]; - Assert.assertEquals(tableNameArg, tableNameWithType); - List segmentListArg = (List) args[1]; - Assert.assertEquals(segmentListArg.size(), removedSegments.size()); - for (String segmentName : removedSegments) { - Assert.assertTrue(segmentListArg.contains(segmentName)); - } - return null; + doAnswer(invocationOnMock -> { + Object[] args = invocationOnMock.getArguments(); + String tableNameArg = (String) args[0]; + assertEquals(tableNameArg, tableNameWithType); + List segmentListArg = (List) args[1]; + assertEquals(segmentListArg.size(), removedSegments.size()); + for (String segmentName : removedSegments) { + assertTrue(segmentListArg.contains(segmentName)); } - }).when(resourceManager).deleteSegments(anyString(), ArgumentMatchers.anyList()); + return null; + }).when(resourceManager).deleteSegments(anyString(), anyList()); } // This test makes sure that we clean up the segments marked OFFLINE in realtime for more than 7 days @Test - public void testRealtimeLLCCleanup() - throws Exception { + public void testRealtimeLLCCleanup() { final int initialNumSegments = 8; final long now = System.currentTimeMillis(); @@ -210,12 +189,43 @@ public void testRealtimeLLCCleanup() TableConfig tableConfig = createRealtimeTableConfig1(replicaCount); List removedSegments = new ArrayList<>(); + LeadControllerManager leadControllerManager = mock(LeadControllerManager.class); + when(leadControllerManager.isLeaderForTable(anyString())).thenReturn(true); PinotHelixResourceManager pinotHelixResourceManager = setupSegmentMetadata(tableConfig, now, initialNumSegments, removedSegments); - setupPinotHelixResourceManager(tableConfig, removedSegments, pinotHelixResourceManager); + setupPinotHelixResourceManager(tableConfig, removedSegments, pinotHelixResourceManager, leadControllerManager); + + ControllerConf conf = new ControllerConf(); + ControllerMetrics controllerMetrics = new ControllerMetrics(PinotMetricUtils.getPinotMetricsRegistry()); + conf.setRetentionControllerFrequencyInSeconds(0); + conf.setDeletedSegmentsRetentionInDays(0); + RetentionManager retentionManager = + new RetentionManager(pinotHelixResourceManager, leadControllerManager, conf, controllerMetrics); + retentionManager.start(); + retentionManager.run(); + + SegmentDeletionManager deletionManager = pinotHelixResourceManager.getSegmentDeletionManager(); + + // Verify that the removeAgedDeletedSegments() method in deletion manager is actually called. + verify(deletionManager, times(1)).removeAgedDeletedSegments(leadControllerManager); + + // Verify that the deleteSegments method is actually called. + verify(pinotHelixResourceManager, times(1)).deleteSegments(anyString(), anyList()); + } + + // This test makes sure that we do not clean up last llc completed segments + @Test + public void testRealtimeLastLLCCleanup() { + final long now = System.currentTimeMillis(); + final int replicaCount = 1; + TableConfig tableConfig = createRealtimeTableConfig1(replicaCount); + List removedSegments = new ArrayList<>(); LeadControllerManager leadControllerManager = mock(LeadControllerManager.class); when(leadControllerManager.isLeaderForTable(anyString())).thenReturn(true); + PinotHelixResourceManager pinotHelixResourceManager = + setupSegmentMetadataForPausedTable(tableConfig, now, removedSegments); + setupPinotHelixResourceManager(tableConfig, removedSegments, pinotHelixResourceManager, leadControllerManager); ControllerConf conf = new ControllerConf(); ControllerMetrics controllerMetrics = new ControllerMetrics(PinotMetricUtils.getPinotMetricsRegistry()); @@ -229,7 +239,7 @@ public void testRealtimeLLCCleanup() SegmentDeletionManager deletionManager = pinotHelixResourceManager.getSegmentDeletionManager(); // Verify that the removeAgedDeletedSegments() method in deletion manager is actually called. - verify(deletionManager, times(1)).removeAgedDeletedSegments(); + verify(deletionManager, times(1)).removeAgedDeletedSegments(leadControllerManager); // Verify that the deleteSegments method is actually called. verify(pinotHelixResourceManager, times(1)).deleteSegments(anyString(), anyList()); @@ -242,7 +252,7 @@ private PinotHelixResourceManager setupSegmentMetadata(TableConfig tableConfig, List segmentsZKMetadata = new ArrayList<>(); IdealState idealState = - PinotTableIdealStateBuilder.buildEmptyRealtimeIdealStateFor(REALTIME_TABLE_NAME, replicaCount, true); + PinotTableIdealStateBuilder.buildEmptyIdealStateFor(REALTIME_TABLE_NAME, replicaCount, true); final int kafkaPartition = 5; final long millisInDays = TimeUnit.DAYS.toMillis(1); @@ -299,6 +309,50 @@ private PinotHelixResourceManager setupSegmentMetadata(TableConfig tableConfig, return pinotHelixResourceManager; } + private PinotHelixResourceManager setupSegmentMetadataForPausedTable(TableConfig tableConfig, final long now, + List segmentsToBeDeleted) { + final int replicaCount = tableConfig.getReplication(); + + List segmentsZKMetadata = new ArrayList<>(); + + IdealState idealState = + PinotTableIdealStateBuilder.buildEmptyIdealStateFor(REALTIME_TABLE_NAME, replicaCount, true); + + final int kafkaPartition = 5; + final long millisInDays = TimeUnit.DAYS.toMillis(1); + final String serverName = "Server_localhost_0"; + LLCSegmentName llcSegmentName0 = new LLCSegmentName(TEST_TABLE_NAME, kafkaPartition, 0, now); + SegmentZKMetadata segmentZKMetadata0 = createSegmentZKMetadata(llcSegmentName0.getSegmentName(), replicaCount, now); + segmentZKMetadata0.setTimeUnit(TimeUnit.MILLISECONDS); + segmentZKMetadata0.setStartTime(now - 30 * millisInDays); + segmentZKMetadata0.setEndTime(now - 20 * millisInDays); + segmentZKMetadata0.setStatus(CommonConstants.Segment.Realtime.Status.DONE); + segmentsZKMetadata.add(segmentZKMetadata0); + idealState.setPartitionState(llcSegmentName0.getSegmentName(), serverName, "ONLINE"); + segmentsToBeDeleted.add(llcSegmentName0.getSegmentName()); + + LLCSegmentName llcSegmentName1 = new LLCSegmentName(TEST_TABLE_NAME, kafkaPartition, 1, now); + SegmentZKMetadata segmentZKMetadata1 = createSegmentZKMetadata(llcSegmentName1.getSegmentName(), replicaCount, now); + segmentZKMetadata1.setTimeUnit(TimeUnit.MILLISECONDS); + segmentZKMetadata1.setStartTime(now - 20 * millisInDays); + segmentZKMetadata1.setEndTime(now - 10 * millisInDays); + segmentZKMetadata1.setStatus(CommonConstants.Segment.Realtime.Status.DONE); + segmentsZKMetadata.add(segmentZKMetadata1); + idealState.setPartitionState(llcSegmentName1.getSegmentName(), serverName, "ONLINE"); + + PinotHelixResourceManager pinotHelixResourceManager = mock(PinotHelixResourceManager.class); + when(pinotHelixResourceManager.getTableConfig(REALTIME_TABLE_NAME)).thenReturn(tableConfig); + when(pinotHelixResourceManager.getSegmentsZKMetadata(REALTIME_TABLE_NAME)).thenReturn(segmentsZKMetadata); + when(pinotHelixResourceManager.getHelixClusterName()).thenReturn(HELIX_CLUSTER_NAME); + when(pinotHelixResourceManager.getLastLLCCompletedSegments(REALTIME_TABLE_NAME)).thenCallRealMethod(); + + HelixAdmin helixAdmin = mock(HelixAdmin.class); + when(helixAdmin.getResourceIdealState(HELIX_CLUSTER_NAME, REALTIME_TABLE_NAME)).thenReturn(idealState); + when(pinotHelixResourceManager.getHelixAdmin()).thenReturn(helixAdmin); + + return pinotHelixResourceManager; + } + private SegmentZKMetadata createSegmentZKMetadata(String segmentName, int replicaCount, long segmentCreationTime) { SegmentZKMetadata segmentMetadata = new SegmentZKMetadata(segmentName); segmentMetadata.setCreationTime(segmentCreationTime); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/retention/SegmentLineageCleanupTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/retention/SegmentLineageCleanupTest.java index be1de7f92d5b..5915dab1871e 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/retention/SegmentLineageCleanupTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/retention/SegmentLineageCleanupTest.java @@ -36,6 +36,7 @@ import org.apache.pinot.spi.config.table.ingestion.BatchIngestionConfig; import org.apache.pinot.spi.config.table.ingestion.IngestionConfig; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.apache.pinot.util.TestUtils; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; @@ -66,6 +67,10 @@ public void setUp() _retentionManager = new RetentionManager(_resourceManager, mock(LeadControllerManager.class), controllerConf, mock(ControllerMetrics.class)); + // Create a schema + TEST_INSTANCE.addDummySchema(TableNameBuilder.extractRawTableName(OFFLINE_TABLE_NAME)); + TEST_INSTANCE.addDummySchema(TableNameBuilder.extractRawTableName(REFRESH_OFFLINE_TABLE_NAME)); + // Update table config TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(OFFLINE_TABLE_NAME).setNumReplicas(1).build(); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/util/SegmentDeletionManagerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/util/SegmentDeletionManagerTest.java index a617dd8612a4..e600643934ef 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/util/SegmentDeletionManagerTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/helix/core/util/SegmentDeletionManagerTest.java @@ -38,12 +38,14 @@ import org.apache.helix.model.IdealState; import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; +import org.apache.pinot.controller.LeadControllerManager; import org.apache.pinot.controller.helix.core.SegmentDeletionManager; import org.apache.pinot.spi.config.table.SegmentsValidationAndRetentionConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.env.PinotConfiguration; import org.apache.pinot.spi.filesystem.LocalPinotFS; import org.apache.pinot.spi.filesystem.PinotFSFactory; +import org.apache.pinot.spi.ingestion.batch.spec.Constants; import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.util.TestUtils; import org.joda.time.DateTime; @@ -228,9 +230,11 @@ public void testRemoveDeletedSegments() tempDir.deleteOnExit(); FakeDeletionManager deletionManager = new FakeDeletionManager( tempDir.getAbsolutePath(), helixAdmin, propertyStore, 7); + LeadControllerManager leadControllerManager = mock(LeadControllerManager.class); + when(leadControllerManager.isLeaderForTable(anyString())).thenReturn(true); // Test delete when deleted segments directory does not exists - deletionManager.removeAgedDeletedSegments(); + deletionManager.removeAgedDeletedSegments(leadControllerManager); // Create deleted directory String deletedDirectoryPath = tempDir + File.separator + "Deleted_Segments"; @@ -238,18 +242,18 @@ public void testRemoveDeletedSegments() deletedDirectory.mkdir(); // Test delete when deleted segments directory is empty - deletionManager.removeAgedDeletedSegments(); + deletionManager.removeAgedDeletedSegments(leadControllerManager); // Create dummy directories and files - File dummyDir1 = new File(deletedDirectoryPath + File.separator + "dummy1"); + File dummyDir1 = new File(deletedDirectoryPath + File.separator + "dummy1 %"); dummyDir1.mkdir(); - File dummyDir2 = new File(deletedDirectoryPath + File.separator + "dummy2"); + File dummyDir2 = new File(deletedDirectoryPath + File.separator + "dummy2 %"); dummyDir2.mkdir(); - File dummyDir3 = new File(deletedDirectoryPath + File.separator + "dummy3"); + File dummyDir3 = new File(deletedDirectoryPath + File.separator + "dummy3 %"); dummyDir3.mkdir(); // Test delete when there is no files but some directories exist - deletionManager.removeAgedDeletedSegments(); + deletionManager.removeAgedDeletedSegments(leadControllerManager); Assert.assertEquals(dummyDir1.exists(), false); Assert.assertEquals(dummyDir2.exists(), false); Assert.assertEquals(dummyDir3.exists(), false); @@ -261,13 +265,15 @@ public void testRemoveDeletedSegments() // Create dummy files for (int i = 0; i < 3; i++) { - createTestFileWithAge(dummyDir1.getAbsolutePath() + File.separator + genDeletedSegmentName("file" + i, i, 1), i); + createTestFileWithAge(dummyDir1.getAbsolutePath() + File.separator + genDeletedSegmentName("file %" + i, i, 1), + i); } for (int i = 2; i < 5; i++) { - createTestFileWithAge(dummyDir2.getAbsolutePath() + File.separator + genDeletedSegmentName("file" + i, i, 1), i); + createTestFileWithAge(dummyDir2.getAbsolutePath() + File.separator + genDeletedSegmentName("file %" + i, i, 1), + i); } for (int i = 6; i < 9; i++) { - createTestFileWithAge(dummyDir3.getAbsolutePath() + File.separator + "file" + i, i); + createTestFileWithAge(dummyDir3.getAbsolutePath() + File.separator + "file %" + i, i); } // Sleep 1 second to ensure the clock moves. @@ -279,7 +285,7 @@ public void testRemoveDeletedSegments() Assert.assertEquals(dummyDir3.list().length, 3); // Try to remove files with the retention of 1 days. - deletionManager.removeAgedDeletedSegments(); + deletionManager.removeAgedDeletedSegments(leadControllerManager); // Check that only 1 day retention file is remaining Assert.assertEquals(dummyDir1.list().length, 1); @@ -352,6 +358,9 @@ public void createTableAndSegmentFiles(File tempDir, List segmentIds) tableDir.mkdir(); for (String segmentId : segmentIds) { createTestFileWithAge(tableDir.getAbsolutePath() + File.separator + segmentId, 0); + // Create segment metadata file + createTestFileWithAge( + tableDir.getAbsolutePath() + File.separator + segmentId + Constants.METADATA_TAR_GZ_FILE_EXT, 0); } } diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/recommender/data/generator/JsonGeneratorTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/recommender/data/generator/JsonGeneratorTest.java index c4bcc1ccc319..52244d0a7067 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/recommender/data/generator/JsonGeneratorTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/recommender/data/generator/JsonGeneratorTest.java @@ -20,7 +20,7 @@ import com.fasterxml.jackson.databind.JsonNode; import java.io.IOException; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.pinot.spi.utils.JsonUtils; import org.testng.Assert; import org.testng.annotations.Test; diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/util/ListenerConfigUtilTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/util/ListenerConfigUtilTest.java index 9384604b7534..f88e87fb7609 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/util/ListenerConfigUtilTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/util/ListenerConfigUtilTest.java @@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableList; import java.util.List; import org.apache.pinot.controller.ControllerConf; +import org.apache.pinot.core.transport.HttpServerThreadPoolConfig; import org.apache.pinot.core.transport.ListenerConfig; import org.apache.pinot.core.util.ListenerConfigUtil; import org.apache.pinot.spi.env.PinotConfiguration; @@ -51,6 +52,30 @@ public void testControllerPortConfig() { ListenerConfigUtil.buildControllerConfigs(new ControllerConf()); } + @Test + public void testThreadPoolConfig() { + ControllerConf controllerConf = new ControllerConf(); + + controllerConf.setProperty("controller.port", "9000"); + + // When server thread pool config is not set, default configs should be used + List listenerConfigs = ListenerConfigUtil.buildControllerConfigs(controllerConf); + Assert.assertEquals(listenerConfigs.size(), 1); + Assert.assertEquals(HttpServerThreadPoolConfig.defaultInstance().getCorePoolSize(), + listenerConfigs.get(0).getThreadPoolConfig().getCorePoolSize()); + Assert.assertEquals(HttpServerThreadPoolConfig.defaultInstance().getMaxPoolSize(), + listenerConfigs.get(0).getThreadPoolConfig().getMaxPoolSize()); + + // Set server thread pool configs and assert that they are set + controllerConf.setProperty("pinot.controller.http.server.thread.pool.corePoolSize", 7); + controllerConf.setProperty("pinot.controller.http.server.thread.pool.maxPoolSize", 9); + + listenerConfigs = ListenerConfigUtil.buildControllerConfigs(controllerConf); + Assert.assertEquals(listenerConfigs.size(), 1); + Assert.assertEquals(7, listenerConfigs.get(0).getThreadPoolConfig().getCorePoolSize()); + Assert.assertEquals(9, listenerConfigs.get(0).getThreadPoolConfig().getMaxPoolSize()); + } + /** * Asserts that enabling https generates the existing legacy listener as well as the another one configured with * TLS settings. @@ -176,21 +201,30 @@ public void testEmptyHttpsPort() { @Test public void testFindLastTlsPort() { - List configs = ImmutableList.of(new ListenerConfig("conf1", "host1", 9000, "http", null), - new ListenerConfig("conf2", "host2", 9001, "https", null), - new ListenerConfig("conf3", "host3", 9002, "http", null), - new ListenerConfig("conf4", "host4", 9003, "https", null), - new ListenerConfig("conf5", "host5", 9004, "http", null)); + List configs = ImmutableList.of(new ListenerConfig("conf1", "host1", 9000, "http", null, + HttpServerThreadPoolConfig.defaultInstance()), + new ListenerConfig("conf2", "host2", 9001, "https", null, + HttpServerThreadPoolConfig.defaultInstance()), + new ListenerConfig("conf3", "host3", 9002, "http", null, + HttpServerThreadPoolConfig.defaultInstance()), + new ListenerConfig("conf4", "host4", 9003, "https", null, + HttpServerThreadPoolConfig.defaultInstance()), + new ListenerConfig("conf5", "host5", 9004, "http", null, + HttpServerThreadPoolConfig.defaultInstance())); int tlsPort = ListenerConfigUtil.findLastTlsPort(configs, -1); Assert.assertEquals(tlsPort, 9003); } @Test public void testFindLastTlsPortMissing() { - List configs = ImmutableList.of(new ListenerConfig("conf1", "host1", 9000, "http", null), - new ListenerConfig("conf2", "host2", 9001, "http", null), - new ListenerConfig("conf3", "host3", 9002, "http", null), - new ListenerConfig("conf4", "host4", 9004, "http", null)); + List configs = ImmutableList.of(new ListenerConfig("conf1", "host1", 9000, "http", null, + HttpServerThreadPoolConfig.defaultInstance()), + new ListenerConfig("conf2", "host2", 9001, "http", null, + HttpServerThreadPoolConfig.defaultInstance()), + new ListenerConfig("conf3", "host3", 9002, "http", null, + HttpServerThreadPoolConfig.defaultInstance()), + new ListenerConfig("conf4", "host4", 9004, "http", null, + HttpServerThreadPoolConfig.defaultInstance())); int tlsPort = ListenerConfigUtil.findLastTlsPort(configs, -1); Assert.assertEquals(tlsPort, -1); } diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/utils/SegmentMetadataMockUtils.java b/pinot-controller/src/test/java/org/apache/pinot/controller/utils/SegmentMetadataMockUtils.java index 6c769d620a53..a604e7e70e1f 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/utils/SegmentMetadataMockUtils.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/utils/SegmentMetadataMockUtils.java @@ -19,9 +19,8 @@ package org.apache.pinot.controller.utils; import java.util.Collections; -import java.util.HashMap; -import java.util.Map; import java.util.Set; +import java.util.TreeMap; import java.util.concurrent.TimeUnit; import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; import org.apache.pinot.segment.spi.ColumnMetadata; @@ -40,26 +39,38 @@ private SegmentMetadataMockUtils() { } public static SegmentMetadata mockSegmentMetadata(String tableName, String segmentName, int numTotalDocs, - String crc) { + String crc, long startTime, long endTime, TimeUnit timeUnit) { SegmentMetadata segmentMetadata = Mockito.mock(SegmentMetadata.class); Mockito.when(segmentMetadata.getTableName()).thenReturn(tableName); Mockito.when(segmentMetadata.getName()).thenReturn(segmentName); Mockito.when(segmentMetadata.getTotalDocs()).thenReturn(numTotalDocs); Mockito.when(segmentMetadata.getCrc()).thenReturn(crc); - Mockito.when(segmentMetadata.getStartTime()).thenReturn(1L); - Mockito.when(segmentMetadata.getEndTime()).thenReturn(10L); + Mockito.when(segmentMetadata.getStartTime()).thenReturn(startTime); + Mockito.when(segmentMetadata.getEndTime()).thenReturn(endTime); Mockito.when(segmentMetadata.getTimeInterval()).thenReturn( - new Interval(TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS), - TimeUnit.MILLISECONDS.convert(10, TimeUnit.DAYS))); - Mockito.when(segmentMetadata.getTimeUnit()).thenReturn(TimeUnit.DAYS); + new Interval(TimeUnit.MILLISECONDS.convert(startTime, timeUnit), + TimeUnit.MILLISECONDS.convert(endTime, timeUnit))); + Mockito.when(segmentMetadata.getTimeUnit()).thenReturn(timeUnit); return segmentMetadata; } + public static SegmentMetadata mockSegmentMetadata(String tableName, String segmentName, int numTotalDocs, + String crc) { + return mockSegmentMetadata(tableName, segmentName, numTotalDocs, crc, 1L, 10L, TimeUnit.DAYS); + } + public static SegmentMetadata mockSegmentMetadata(String tableName) { String uniqueNumericString = Long.toString(System.nanoTime()); return mockSegmentMetadata(tableName, tableName + uniqueNumericString, 100, uniqueNumericString); } + public static SegmentMetadata mockSegmentMetadata(String tableName, long startTime, + long endTime, TimeUnit timeUnit) { + String uniqueNumericString = Long.toString(System.nanoTime()); + return mockSegmentMetadata(tableName, tableName + uniqueNumericString, 100, + uniqueNumericString, startTime, endTime, timeUnit); + } + public static SegmentMetadata mockSegmentMetadata(String tableName, String segmentName) { String uniqueNumericString = Long.toString(System.nanoTime()); return mockSegmentMetadata(tableName, segmentName, 100, uniqueNumericString); @@ -72,7 +83,7 @@ public static SegmentZKMetadata mockSegmentZKMetadata(String segmentName, long n return segmentZKMetadata; } - public static SegmentMetadata mockSegmentMetadataWithPartitionInfo(String tableName, String segmentName, + public static SegmentMetadata mockSegmentMetadataWithPartitionInfo(String rawTableName, String segmentName, String columnName, int partitionNumber) { ColumnMetadata columnMetadata = mock(ColumnMetadata.class); Set partitions = Collections.singleton(partitionNumber); @@ -83,11 +94,11 @@ public static SegmentMetadata mockSegmentMetadataWithPartitionInfo(String tableN if (columnName != null) { when(segmentMetadata.getColumnMetadataFor(columnName)).thenReturn(columnMetadata); } - when(segmentMetadata.getTableName()).thenReturn(tableName); + when(segmentMetadata.getTableName()).thenReturn(rawTableName); when(segmentMetadata.getName()).thenReturn(segmentName); when(segmentMetadata.getCrc()).thenReturn("0"); - Map columnMetadataMap = new HashMap<>(); + TreeMap columnMetadataMap = new TreeMap<>(); columnMetadataMap.put(columnName, columnMetadata); when(segmentMetadata.getColumnMetadataMap()).thenReturn(columnMetadataMap); return segmentMetadata; diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/validation/StorageQuotaCheckerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/validation/StorageQuotaCheckerTest.java index e00d2470b7d8..888db0e4cc4d 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/validation/StorageQuotaCheckerTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/validation/StorageQuotaCheckerTest.java @@ -22,6 +22,7 @@ import org.apache.pinot.common.exception.InvalidConfigException; import org.apache.pinot.common.metrics.ControllerGauge; import org.apache.pinot.common.metrics.ControllerMetrics; +import org.apache.pinot.common.metrics.MetricValueUtils; import org.apache.pinot.controller.helix.core.PinotHelixResourceManager; import org.apache.pinot.controller.util.TableSizeReader; import org.apache.pinot.spi.config.table.QuotaConfig; @@ -104,34 +105,38 @@ public void testWithinQuota() // No response from server, should pass without updating metrics mockTableSizeResult(-1, 0); assertTrue(isSegmentWithinQuota()); - assertEquals( - controllerMetrics.getValueOfTableGauge(OFFLINE_TABLE_NAME, ControllerGauge.OFFLINE_TABLE_ESTIMATED_SIZE), 0); + assertFalse( + MetricValueUtils.tableGaugeExists(controllerMetrics, OFFLINE_TABLE_NAME, + ControllerGauge.OFFLINE_TABLE_ESTIMATED_SIZE)); // Within quota but with missing segments, should pass without updating metrics mockTableSizeResult(4 * 1024, 1); assertTrue(isSegmentWithinQuota()); - assertEquals( - controllerMetrics.getValueOfTableGauge(OFFLINE_TABLE_NAME, ControllerGauge.OFFLINE_TABLE_ESTIMATED_SIZE), 0); + assertFalse( + MetricValueUtils.tableGaugeExists(controllerMetrics, OFFLINE_TABLE_NAME, + ControllerGauge.OFFLINE_TABLE_ESTIMATED_SIZE)); + // Exceed quota and with missing segments, should fail without updating metrics mockTableSizeResult(8 * 1024, 1); assertFalse(isSegmentWithinQuota()); - assertEquals( - controllerMetrics.getValueOfTableGauge(OFFLINE_TABLE_NAME, ControllerGauge.OFFLINE_TABLE_ESTIMATED_SIZE), 0); + assertFalse( + MetricValueUtils.tableGaugeExists(controllerMetrics, OFFLINE_TABLE_NAME, + ControllerGauge.OFFLINE_TABLE_ESTIMATED_SIZE)); // Within quota without missing segments, should pass and update metrics mockTableSizeResult(3 * 1024, 0); assertTrue(isSegmentWithinQuota()); assertEquals( - controllerMetrics.getValueOfTableGauge(OFFLINE_TABLE_NAME, ControllerGauge.OFFLINE_TABLE_ESTIMATED_SIZE), - 3 * 1024); + MetricValueUtils.getTableGaugeValue(controllerMetrics, OFFLINE_TABLE_NAME, + ControllerGauge.OFFLINE_TABLE_ESTIMATED_SIZE), 3 * 1024); // Exceed quota without missing segments, should fail and update metrics mockTableSizeResult(4 * 1024, 0); assertFalse(isSegmentWithinQuota()); assertEquals( - controllerMetrics.getValueOfTableGauge(OFFLINE_TABLE_NAME, ControllerGauge.OFFLINE_TABLE_ESTIMATED_SIZE), - 4 * 1024); + MetricValueUtils.getTableGaugeValue(controllerMetrics, OFFLINE_TABLE_NAME, + ControllerGauge.OFFLINE_TABLE_ESTIMATED_SIZE), 4 * 1024); } private boolean isSegmentWithinQuota() diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/validation/ValidationManagerStatelessTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/validation/ValidationManagerStatelessTest.java index 20da21375726..f66230c6d12e 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/validation/ValidationManagerStatelessTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/validation/ValidationManagerStatelessTest.java @@ -51,7 +51,9 @@ public void setUp() startController(); addFakeBrokerInstancesToAutoJoinHelixCluster(2, true); addFakeServerInstancesToAutoJoinHelixCluster(2, true); - + // Create a schema + addDummySchema(TEST_TABLE_NAME); + // Create a table _offlineTableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TEST_TABLE_NAME).setNumReplicas(2).build(); _helixResourceManager.addTable(_offlineTableConfig); @@ -72,6 +74,7 @@ public void testRebuildBrokerResourceWhenBrokerAdded() _helixResourceManager.rebuildBrokerResourceFromHelixTags(partitionName); // Add another table that needs to be rebuilt + addDummySchema(TEST_TABLE_TWO); TableConfig offlineTableConfigTwo = new TableConfigBuilder(TableType.OFFLINE).setTableName(TEST_TABLE_TWO).build(); _helixResourceManager.addTable(offlineTableConfigTwo); String partitionNameTwo = offlineTableConfigTwo.getTableName(); diff --git a/pinot-controller/src/test/java/org/apache/pinot/controller/validation/ValidationManagerTest.java b/pinot-controller/src/test/java/org/apache/pinot/controller/validation/ValidationManagerTest.java index c371dc9dc23e..7e2f22f1fda8 100644 --- a/pinot-controller/src/test/java/org/apache/pinot/controller/validation/ValidationManagerTest.java +++ b/pinot-controller/src/test/java/org/apache/pinot/controller/validation/ValidationManagerTest.java @@ -22,13 +22,14 @@ import java.util.List; import org.apache.helix.model.ExternalView; import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; -import org.apache.pinot.common.utils.HLCSegmentName; import org.apache.pinot.common.utils.LLCSegmentName; import org.apache.pinot.controller.helix.ControllerTest; import org.apache.pinot.controller.utils.SegmentMetadataMockUtils; import org.apache.pinot.segment.spi.SegmentMetadata; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.data.Schema; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.apache.pinot.util.TestUtils; @@ -59,7 +60,14 @@ public class ValidationManagerTest { public void setUp() throws Exception { TEST_INSTANCE.setupSharedStateAndValidate(); - + // Create a schema + Schema schema = new Schema.SchemaBuilder() + .setSchemaName(TEST_TABLE_NAME) + .addSingleValueDimension("dim1", FieldSpec.DataType.STRING) + .addMetric("metric1", FieldSpec.DataType.INT) + .build(); + TEST_INSTANCE.getHelixResourceManager().addSchema(schema, true, true); + // Create a table TableConfig offlineTableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TEST_TABLE_NAME).setNumReplicas(2).build(); TEST_INSTANCE.getHelixResourceManager().addTable(offlineTableConfig); @@ -102,32 +110,14 @@ public void testPushTimePersistence() { @Test public void testTotalDocumentCountRealTime() { - // Create a bunch of dummy segments - final String group1 = TEST_TABLE_NAME + "_REALTIME_1466446700000_34"; - final String group2 = TEST_TABLE_NAME + "_REALTIME_1466446700000_17"; - String segmentName1 = new HLCSegmentName(group1, "0", "1").getSegmentName(); - String segmentName2 = new HLCSegmentName(group1, "0", "2").getSegmentName(); - String segmentName3 = new HLCSegmentName(group1, "0", "3").getSegmentName(); - String segmentName4 = new HLCSegmentName(group2, "0", "3").getSegmentName(); - + // Create some dummy LLC segments (both committed and uploaded) List segmentsZKMetadata = new ArrayList<>(); - segmentsZKMetadata.add(SegmentMetadataMockUtils.mockSegmentZKMetadata(segmentName1, 10)); - segmentsZKMetadata.add(SegmentMetadataMockUtils.mockSegmentZKMetadata(segmentName2, 20)); - segmentsZKMetadata.add(SegmentMetadataMockUtils.mockSegmentZKMetadata(segmentName3, 30)); - // This should get ignored in the count as it belongs to a different group id - segmentsZKMetadata.add(SegmentMetadataMockUtils.mockSegmentZKMetadata(segmentName4, 20)); - - assertEquals(RealtimeSegmentValidationManager.computeTotalDocumentCount(segmentsZKMetadata, true), 60); - - // Now add some LLC segments (both committed and uploaded) String segmentName5 = new LLCSegmentName(TEST_TABLE_NAME, 1, 0, 1000).getSegmentName(); String segmentName6 = new LLCSegmentName(TEST_TABLE_NAME, 2, 27, 10000).getSegmentName(); segmentsZKMetadata.add(SegmentMetadataMockUtils.mockSegmentZKMetadata(segmentName5, 10)); segmentsZKMetadata.add(SegmentMetadataMockUtils.mockSegmentZKMetadata(segmentName6, 5)); segmentsZKMetadata.add(SegmentMetadataMockUtils.mockSegmentZKMetadata(TEST_SEGMENT_NAME, 15)); - - // Only the LLC segments should get counted. - assertEquals(RealtimeSegmentValidationManager.computeTotalDocumentCount(segmentsZKMetadata, false), 30); + assertEquals(RealtimeSegmentValidationManager.computeTotalDocumentCount(segmentsZKMetadata), 30); } @Test diff --git a/pinot-core/pom.xml b/pinot-core/pom.xml index 3c8463a05be1..aa654e3a0709 100644 --- a/pinot-core/pom.xml +++ b/pinot-core/pom.xml @@ -19,13 +19,12 @@ under the License. --> - + 4.0.0 pinot org.apache.pinot - 0.13.0-SNAPSHOT + 1.2.0 .. pinot-core @@ -35,34 +34,7 @@ ${basedir}/.. - - - - maven-jar-plugin - - - - test-jar - - - - - - org.apache.maven.plugins - maven-enforcer-plugin - - - - - - com.uber - h3 - - - org.roaringbitmap - RoaringBitmap - org.apache.pinot pinot-spi @@ -79,54 +51,16 @@ org.apache.pinot pinot-common + - joda-time - joda-time - - - commons-collections - commons-collections - - - commons-configuration - commons-configuration - - - commons-logging - commons-logging - - - commons-lang - commons-lang - - - - - commons-logging - commons-logging - - - commons-lang - commons-lang - - - commons-io - commons-io + io.netty + netty-transport-native-epoll + linux-x86_64 - io.netty netty-transport-native-epoll - linux-x86_64 + linux-aarch_64 io.netty @@ -135,65 +69,32 @@ io.netty - netty-tcnative-boringssl-static - linux-x86_64 + netty-transport-native-kqueue + osx-aarch_64 io.netty netty-tcnative-boringssl-static - osx-x86_64 + linux-x86_64 io.netty - netty-all - - - org.slf4j - slf4j-api - - - org.apache.commons - commons-math - - - com.clearspring.analytics - stream - - - org.apache.datasketches - datasketches-java - - - com.tdunning - t-digest - - - org.xerial.larray - larray-mmap - - - net.sf.jopt-simple - jopt-simple - - - com.jayway.jsonpath - json-path - - - org.locationtech.jts - jts-core + netty-tcnative-boringssl-static + linux-aarch_64 - org.glassfish.jersey.containers - jersey-container-grizzly2-http + io.netty + netty-tcnative-boringssl-static + osx-x86_64 - org.glassfish.grizzly - grizzly-http-server + io.netty + netty-tcnative-boringssl-static + osx-aarch_64 - org.glassfish.hk2 - hk2-locator + io.netty + netty-all @@ -218,7 +119,7 @@ org.mockito - mockito-inline + mockito-core test @@ -257,21 +158,25 @@ ${project.version} test + + + + org.apache.lucene + lucene-backward-codecs + org.apache.lucene lucene-core - ${lucene.version} org.apache.lucene lucene-queryparser - ${lucene.version} org.apache.lucene - lucene-analyzers-common - ${lucene.version} + lucene-analysis-common + @@ -279,33 +184,9 @@ false - - - - maven-shade-plugin - - - package - - shade - - - - - com.google.common.base - ${shade.prefix}.com.google.common.base - - - org.apache.http - ${shade.prefix}.org.apache.http - - - - - - - - + + package + diff --git a/pinot-core/src/main/java/org/apache/pinot/core/accounting/CPUMemThreadLevelAccountingObjects.java b/pinot-core/src/main/java/org/apache/pinot/core/accounting/CPUMemThreadLevelAccountingObjects.java index 0cf6688e489b..431643942aa9 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/accounting/CPUMemThreadLevelAccountingObjects.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/accounting/CPUMemThreadLevelAccountingObjects.java @@ -18,8 +18,6 @@ */ package org.apache.pinot.core.accounting; -import java.util.HashMap; -import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -32,33 +30,47 @@ */ public class CPUMemThreadLevelAccountingObjects { - public static class StatsDigest { - - // The current usage sampling for each thread - final long[] _currentStatsSample; - // The previous usage sampling for each thread - final long[] _lastStatSample; - // The aggregated usage sampling for the finished tasks of a (still) running queries - final HashMap _finishedTaskStatAggregator; - - StatsDigest(int numThreads) { - _currentStatsSample = new long[numThreads]; - _lastStatSample = new long[numThreads]; - _finishedTaskStatAggregator = new HashMap<>(); - } - } - /** - * Entry to track the task execution status of a worker/runner given thread + * Entry to track the task execution status and usage stats of a Thread + * (including but not limited to server worker thread, runner thread, broker jetty thread, or broker netty thread) */ - public static class TaskEntryHolder { - AtomicReference _threadTaskStatus = new AtomicReference<>(null); + public static class ThreadEntry { + // current query_id, task_id of the thread; this field is accessed by the thread itself and the accountant + AtomicReference _currentThreadTaskStatus = new AtomicReference<>(); + // current sample of thread memory usage/cputime ; this field is accessed by the thread itself and the accountant + volatile long _currentThreadCPUTimeSampleMS = 0; + volatile long _currentThreadMemoryAllocationSampleBytes = 0; + + // previous query_id, task_id of the thread, this field should only be accessed by the accountant + TaskEntry _previousThreadTaskStatus = null; + // previous cpu time and memory allocation of the thread + // these fields should only be accessed by the accountant + long _previousThreadCPUTimeSampleMS = 0; + long _previousThreadMemoryAllocationSampleBytes = 0; + + // error message store per runner/worker thread, + // will put preemption reasons in this for the killed thread to pickup + AtomicReference _errorStatus = new AtomicReference<>(); + + @Override + public String toString() { + TaskEntry taskEntry = _currentThreadTaskStatus.get(); + return "ThreadEntry{" + + "_currentThreadTaskStatus=" + (taskEntry == null ? "idle" : taskEntry.toString()) + + ", _errorStatus=" + _errorStatus + + '}'; + } /** - * set the thread tracking info to null + * set the thread tracking info to null and usage samples to zero */ public void setToIdle() { - _threadTaskStatus.set(null); + // clear task info + _currentThreadTaskStatus.set(null); + // clear CPU time + _currentThreadCPUTimeSampleMS = 0; + // clear memory usage + _currentThreadMemoryAllocationSampleBytes = 0; } /** @@ -66,16 +78,20 @@ public void setToIdle() { * @return the current query id on the thread, {@code null} if idle */ @Nullable - public TaskEntry getThreadTaskStatus() { - return _threadTaskStatus.get(); + public TaskEntry getCurrentThreadTaskStatus() { + return _currentThreadTaskStatus.get(); } - public TaskEntryHolder setThreadTaskStatus(@Nonnull String queryId, int taskId, @Nonnull Thread thread) { - _threadTaskStatus.set(new TaskEntry(queryId, taskId, thread)); - return this; + public void setThreadTaskStatus(@Nonnull String queryId, int taskId, @Nonnull Thread anchorThread) { + _currentThreadTaskStatus.set(new TaskEntry(queryId, taskId, anchorThread)); } } + /** + * Class to track the execution status of a thread. query_id is an instance level unique query_id, + * taskId is the worker thread id when we have a runner-worker thread model + * anchor thread refers to the runner in runner-worker thread model + */ public static class TaskEntry implements ThreadExecutionContext { private final String _queryId; private final int _taskId; @@ -91,17 +107,6 @@ public TaskEntry(String queryId, int taskId, Thread anchorThread) { _anchorThread = anchorThread; } - public static boolean isSameTask(TaskEntry currentTaskStatus, TaskEntry lastQueryTask) { - if (currentTaskStatus == null) { - return lastQueryTask == null; - } else if (lastQueryTask == null) { - return false; - } else { - return Objects.equals(currentTaskStatus.getQueryId(), lastQueryTask.getQueryId()) - || currentTaskStatus.getTaskId() == lastQueryTask.getTaskId(); - } - } - public String getQueryId() { return _queryId; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/accounting/HeapUsagePublishingAccountantFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/accounting/HeapUsagePublishingAccountantFactory.java index 03d510b949e5..e29d33d0445e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/accounting/HeapUsagePublishingAccountantFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/accounting/HeapUsagePublishingAccountantFactory.java @@ -38,8 +38,8 @@ public class HeapUsagePublishingAccountantFactory implements ThreadAccountantFactory { @Override - public ThreadResourceUsageAccountant init(int numRunnerThreads, int numWorkerThreads, PinotConfiguration config) { - int period = config.getProperty(CommonConstants.Accounting.CONFIG_OF_HEAP_USAGE_PUBLISH_PERIOD, + public ThreadResourceUsageAccountant init(PinotConfiguration config, String instanceId) { + int period = config.getProperty(CommonConstants.Accounting.CONFIG_OF_HEAP_USAGE_PUBLISHING_PERIOD_MS, CommonConstants.Accounting.DEFAULT_HEAP_USAGE_PUBLISH_PERIOD); return new HeapUsagePublishingResourceUsageAccountant(period); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/accounting/PerQueryCPUMemAccountantFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/accounting/PerQueryCPUMemAccountantFactory.java index 4f488deca70b..598b68b34462 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/accounting/PerQueryCPUMemAccountantFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/accounting/PerQueryCPUMemAccountantFactory.java @@ -20,27 +20,31 @@ import java.lang.management.ManagementFactory; import java.lang.management.MemoryMXBean; -import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; +import org.apache.pinot.common.metrics.AbstractMetrics; +import org.apache.pinot.common.metrics.BrokerGauge; +import org.apache.pinot.common.metrics.BrokerMeter; +import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.metrics.ServerGauge; import org.apache.pinot.common.metrics.ServerMeter; import org.apache.pinot.common.metrics.ServerMetrics; -import org.apache.pinot.core.accounting.utils.RunnerWorkerThreadOffsetProvider; import org.apache.pinot.spi.accounting.ThreadAccountantFactory; import org.apache.pinot.spi.accounting.ThreadExecutionContext; import org.apache.pinot.spi.accounting.ThreadResourceUsageAccountant; import org.apache.pinot.spi.accounting.ThreadResourceUsageProvider; +import org.apache.pinot.spi.config.instance.InstanceType; import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.metrics.PinotMetricUtils; import org.apache.pinot.spi.trace.Tracing; import org.apache.pinot.spi.utils.CommonConstants; import org.slf4j.Logger; @@ -55,8 +59,8 @@ public class PerQueryCPUMemAccountantFactory implements ThreadAccountantFactory { @Override - public ThreadResourceUsageAccountant init(int numRunnerThreads, int numWorkerThreads, PinotConfiguration config) { - return new PerQueryCPUMemResourceUsageAccountant(numRunnerThreads + numWorkerThreads, config); + public ThreadResourceUsageAccountant init(PinotConfiguration config, String instanceId) { + return new PerQueryCPUMemResourceUsageAccountant(config, instanceId); } public static class PerQueryCPUMemResourceUsageAccountant extends Tracing.DefaultThreadResourceUsageAccountant { @@ -66,6 +70,7 @@ public static class PerQueryCPUMemResourceUsageAccountant extends Tracing.Defaul */ static final MemoryMXBean MEMORY_MX_BEAN = ManagementFactory.getMemoryMXBean(); private static final Logger LOGGER = LoggerFactory.getLogger(PerQueryCPUMemResourceUsageAccountant.class); + private static final boolean IS_DEBUG_MODE_ENABLED = LOGGER.isDebugEnabled(); /** * Executor service for the thread accounting task, slightly higher priority than normal priority */ @@ -79,44 +84,55 @@ public static class PerQueryCPUMemResourceUsageAccountant extends Tracing.Defaul return thread; }); - // number of total threads - private final int _numThreads; private final PinotConfiguration _config; - private final RunnerWorkerThreadOffsetProvider _runnerWorkerThreadOffsetProvider; - // query_id, task_id per runner/worker thread - private final CPUMemThreadLevelAccountingObjects.TaskEntryHolder[] _taskStatus; + // the map to track stats entry for each thread, the entry will automatically be added when one calls + // setThreadResourceUsageProvider on the thread, including but not limited to + // server worker thread, runner thread, broker jetty thread, or broker netty thread + private final ConcurrentHashMap _threadEntriesMap + = new ConcurrentHashMap<>(); + + // For one time concurrent update of stats. This is to provide stats collection for parts that are not + // performance sensitive and query_id is not known beforehand (e.g. broker inbound netty thread) + private final ConcurrentHashMap _concurrentTaskCPUStatsAggregator = new ConcurrentHashMap<>(); + private final ConcurrentHashMap _concurrentTaskMemStatsAggregator = new ConcurrentHashMap<>(); + + // for stats aggregation of finished (worker) threads when the runner is still running + private final HashMap _finishedTaskCPUStatsAggregator = new HashMap<>(); + private final HashMap _finishedTaskMemStatsAggregator = new HashMap<>(); + + private final ThreadLocal _threadLocalEntry + = ThreadLocal.withInitial(() -> { + CPUMemThreadLevelAccountingObjects.ThreadEntry ret = + new CPUMemThreadLevelAccountingObjects.ThreadEntry(); + _threadEntriesMap.put(Thread.currentThread(), ret); + LOGGER.info("Adding thread to _threadLocalEntry: {}", Thread.currentThread().getName()); + return ret; + } + ); // ThreadResourceUsageProvider(ThreadMXBean wrapper) per runner/worker thread private final ThreadLocal _threadResourceUsageProvider; // track thread cpu time private final boolean _isThreadCPUSamplingEnabled; - // cpu time samples per runner/worker thread - private final CPUMemThreadLevelAccountingObjects.StatsDigest _cpuTimeSamplesNS; // track memory usage private final boolean _isThreadMemorySamplingEnabled; - // memory usage samples per runner/worker thread - private final CPUMemThreadLevelAccountingObjects.StatsDigest _memorySamplesBytes; - - // the last seen task_id-query_id - private final CPUMemThreadLevelAccountingObjects.TaskEntry[] _lastQueryTask; private final Set _inactiveQuery; - // error message store per runner/worker thread, - // will put preemption reasons in this for the killed thread to pickup - private final List> _errorStatus; // the periodical task that aggregates and preempts queries private final WatcherTask _watcherTask; - public PerQueryCPUMemResourceUsageAccountant(int numThreads, PinotConfiguration config) { + // instance id of the current instance, for logging purpose + private final String _instanceId; + + public PerQueryCPUMemResourceUsageAccountant(PinotConfiguration config, String instanceId) { LOGGER.info("Initializing PerQueryCPUMemResourceUsageAccountant"); - _numThreads = numThreads; _config = config; - _runnerWorkerThreadOffsetProvider = new RunnerWorkerThreadOffsetProvider(); + _instanceId = instanceId; boolean threadCpuTimeMeasurementEnabled = ThreadResourceUsageProvider.isThreadCpuTimeMeasurementEnabled(); boolean threadMemoryMeasurementEnabled = ThreadResourceUsageProvider.isThreadMemoryMeasurementEnabled(); @@ -137,29 +153,10 @@ public PerQueryCPUMemResourceUsageAccountant(int numThreads, PinotConfiguration LOGGER.info("_isThreadCPUSamplingEnabled: {}, _isThreadMemorySamplingEnabled: {}", _isThreadCPUSamplingEnabled, _isThreadMemorySamplingEnabled); - _taskStatus = new CPUMemThreadLevelAccountingObjects.TaskEntryHolder[_numThreads]; - _errorStatus = new ArrayList<>(_numThreads); - for (int i = 0; i < _numThreads; i++) { - _taskStatus[i] = new CPUMemThreadLevelAccountingObjects.TaskEntryHolder(); - _errorStatus.add(new AtomicReference<>(null)); - } - - if (_isThreadCPUSamplingEnabled) { - _cpuTimeSamplesNS = new CPUMemThreadLevelAccountingObjects.StatsDigest(_numThreads); - } else { - _cpuTimeSamplesNS = null; - } - if (_isThreadMemorySamplingEnabled) { - _memorySamplesBytes = new CPUMemThreadLevelAccountingObjects.StatsDigest(_numThreads); - } else { - _memorySamplesBytes = null; - } - // ThreadMXBean wrapper _threadResourceUsageProvider = new ThreadLocal<>(); // task/query tracking - _lastQueryTask = new CPUMemThreadLevelAccountingObjects.TaskEntry[_numThreads]; _inactiveQuery = new HashSet<>(); _watcherTask = new WatcherTask(); @@ -171,6 +168,28 @@ public void sampleUsage() { sampleThreadCPUTime(); } + /** + * for testing only + */ + public int getEntryCount() { + return _threadEntriesMap.size(); + } + + @Override + public void updateQueryUsageConcurrently(String queryId) { + if (_isThreadCPUSamplingEnabled) { + long cpuUsageNS = getThreadResourceUsageProvider().getThreadTimeNs(); + _concurrentTaskCPUStatsAggregator.compute(queryId, + (key, value) -> (value == null) ? cpuUsageNS : (value + cpuUsageNS)); + } + if (_isThreadMemorySamplingEnabled) { + long memoryAllocatedBytes = getThreadResourceUsageProvider().getThreadAllocatedBytes(); + _concurrentTaskMemStatsAggregator.compute(queryId, + (key, value) -> (value == null) ? memoryAllocatedBytes : (value + memoryAllocatedBytes)); + } + } + + /** * The thread would need to do {@code setThreadResourceUsageProvider} first upon it is scheduled. * This is to be called from a worker or a runner thread to update its corresponding cpu usage entry @@ -178,8 +197,7 @@ public void sampleUsage() { @SuppressWarnings("ConstantConditions") public void sampleThreadCPUTime() { if (_isThreadCPUSamplingEnabled) { - int tid = _runnerWorkerThreadOffsetProvider.get(); - _cpuTimeSamplesNS._currentStatsSample[tid] = getThreadResourceUsageProvider().getThreadTimeNs(); + _threadLocalEntry.get()._currentThreadCPUTimeSampleMS = getThreadResourceUsageProvider().getThreadTimeNs(); } } @@ -190,8 +208,8 @@ public void sampleThreadCPUTime() { @SuppressWarnings("ConstantConditions") public void sampleThreadBytesAllocated() { if (_isThreadMemorySamplingEnabled) { - int tid = _runnerWorkerThreadOffsetProvider.get(); - _memorySamplesBytes._currentStatsSample[tid] = getThreadResourceUsageProvider().getThreadAllocatedBytes(); + _threadLocalEntry.get()._currentThreadMemoryAllocationSampleBytes + = getThreadResourceUsageProvider().getThreadAllocatedBytes(); } } @@ -207,22 +225,22 @@ public void setThreadResourceUsageProvider(ThreadResourceUsageProvider threadRes @Override public void createExecutionContextInner(@Nullable String queryId, int taskId, @Nullable ThreadExecutionContext parentContext) { - int tid = _runnerWorkerThreadOffsetProvider.get(); + _threadLocalEntry.get()._errorStatus.set(null); if (parentContext == null) { // is anchor thread assert queryId != null; - _taskStatus[tid].setThreadTaskStatus(queryId, CommonConstants.Accounting.ANCHOR_TASK_ID, + _threadLocalEntry.get().setThreadTaskStatus(queryId, CommonConstants.Accounting.ANCHOR_TASK_ID, Thread.currentThread()); } else { // not anchor thread - _taskStatus[tid].setThreadTaskStatus(parentContext.getQueryId(), taskId, parentContext.getAnchorThread()); + _threadLocalEntry.get().setThreadTaskStatus(parentContext.getQueryId(), taskId, + parentContext.getAnchorThread()); } } @Override public ThreadExecutionContext getThreadExecutionContext() { - int tid = _runnerWorkerThreadOffsetProvider.get(); - return _taskStatus[tid].getThreadTaskStatus(); + return _threadLocalEntry.get().getCurrentThreadTaskStatus(); } /** @@ -231,20 +249,12 @@ public ThreadExecutionContext getThreadExecutionContext() { @SuppressWarnings("ConstantConditions") @Override public void clear() { - int tid = _runnerWorkerThreadOffsetProvider.get(); - // clear task info - _taskStatus[tid].setToIdle(); - // clear CPU time - if (_isThreadCPUSamplingEnabled) { - _cpuTimeSamplesNS._currentStatsSample[tid] = 0; - } - // clear memory usage - if (_isThreadMemorySamplingEnabled) { - _memorySamplesBytes._currentStatsSample[tid] = 0; - } + CPUMemThreadLevelAccountingObjects.ThreadEntry threadEntry = _threadLocalEntry.get(); + // clear task info + stats + threadEntry.setToIdle(); // clear threadResourceUsageProvider _threadResourceUsageProvider.set(null); - // clear _rootThread + // clear _anchorThread super.clear(); } @@ -259,18 +269,22 @@ public void startWatcherTask() { public void cleanInactive() { for (String inactiveQueryId : _inactiveQuery) { if (_isThreadCPUSamplingEnabled) { - _cpuTimeSamplesNS._finishedTaskStatAggregator.remove(inactiveQueryId); + _finishedTaskCPUStatsAggregator.remove(inactiveQueryId); + _concurrentTaskCPUStatsAggregator.remove(inactiveQueryId); } if (_isThreadMemorySamplingEnabled) { - _memorySamplesBytes._finishedTaskStatAggregator.remove(inactiveQueryId); + _finishedTaskMemStatsAggregator.remove(inactiveQueryId); + _concurrentTaskMemStatsAggregator.remove(inactiveQueryId); } } _inactiveQuery.clear(); if (_isThreadCPUSamplingEnabled) { - _inactiveQuery.addAll(_cpuTimeSamplesNS._finishedTaskStatAggregator.keySet()); + _inactiveQuery.addAll(_finishedTaskCPUStatsAggregator.keySet()); + _inactiveQuery.addAll(_concurrentTaskCPUStatsAggregator.keySet()); } if (_isThreadMemorySamplingEnabled) { - _inactiveQuery.addAll(_memorySamplesBytes._finishedTaskStatAggregator.keySet()); + _inactiveQuery.addAll(_finishedTaskMemStatsAggregator.keySet()); + _inactiveQuery.addAll(_concurrentTaskMemStatsAggregator.keySet()); } } @@ -286,43 +300,45 @@ public Map aggregate(boolean isTriggered) { } // for each {pqr, pqw} - for (int threadId = 0; threadId < _numThreads; threadId++) { + for (Map.Entry entry : _threadEntriesMap.entrySet()) { // sample current usage + CPUMemThreadLevelAccountingObjects.ThreadEntry threadEntry = entry.getValue(); long currentCPUSample = _isThreadCPUSamplingEnabled - ? _cpuTimeSamplesNS._currentStatsSample[threadId] : 0; + ? threadEntry._currentThreadCPUTimeSampleMS : 0; long currentMemSample = _isThreadMemorySamplingEnabled - ? _memorySamplesBytes._currentStatsSample[threadId] : 0; + ? threadEntry._currentThreadMemoryAllocationSampleBytes : 0; // sample current running task status - CPUMemThreadLevelAccountingObjects.TaskEntry currentTaskStatus = _taskStatus[threadId].getThreadTaskStatus(); - LOGGER.trace("tid: {}, task: {}", threadId, currentTaskStatus); + CPUMemThreadLevelAccountingObjects.TaskEntry currentTaskStatus = threadEntry.getCurrentThreadTaskStatus(); + Thread thread = entry.getKey(); + LOGGER.trace("tid: {}, task: {}", thread.getId(), currentTaskStatus); // get last task on the thread - CPUMemThreadLevelAccountingObjects.TaskEntry lastQueryTask = _lastQueryTask[threadId]; + CPUMemThreadLevelAccountingObjects.TaskEntry lastQueryTask = threadEntry._previousThreadTaskStatus; // accumulate recorded previous stat to it's _finishedTaskStatAggregator // if the last task on the same thread has finished - if (!CPUMemThreadLevelAccountingObjects.TaskEntry.isSameTask(currentTaskStatus, lastQueryTask)) { + if (!(currentTaskStatus == lastQueryTask)) { // set previous value to current task stats - _lastQueryTask[threadId] = currentTaskStatus; + threadEntry._previousThreadTaskStatus = currentTaskStatus; if (lastQueryTask != null) { String lastQueryId = lastQueryTask.getQueryId(); if (_isThreadCPUSamplingEnabled) { - long lastSample = _cpuTimeSamplesNS._lastStatSample[threadId]; - _cpuTimeSamplesNS._finishedTaskStatAggregator.merge(lastQueryId, lastSample, Long::sum); + long lastSample = threadEntry._previousThreadCPUTimeSampleMS; + _finishedTaskCPUStatsAggregator.merge(lastQueryId, lastSample, Long::sum); } if (_isThreadMemorySamplingEnabled) { - long lastSample = _memorySamplesBytes._lastStatSample[threadId]; - _memorySamplesBytes._finishedTaskStatAggregator.merge(lastQueryId, lastSample, Long::sum); + long lastSample = threadEntry._previousThreadMemoryAllocationSampleBytes; + _finishedTaskMemStatsAggregator.merge(lastQueryId, lastSample, Long::sum); } } } // record current usage values for future accumulation if this task is done if (_isThreadCPUSamplingEnabled) { - _cpuTimeSamplesNS._lastStatSample[threadId] = currentCPUSample; + threadEntry._previousThreadCPUTimeSampleMS = currentCPUSample; } if (_isThreadMemorySamplingEnabled) { - _memorySamplesBytes._lastStatSample[threadId] = currentMemSample; + threadEntry._previousThreadMemoryAllocationSampleBytes = currentMemSample; } // if current thread is not idle @@ -333,15 +349,19 @@ public Map aggregate(boolean isTriggered) { _inactiveQuery.remove(queryId); // if triggered, accumulate active query task stats if (isTriggered) { - Thread thread = currentTaskStatus.getAnchorThread(); - int finalThreadId = threadId; + Thread anchorThread = currentTaskStatus.getAnchorThread(); boolean isAnchorThread = currentTaskStatus.isAnchorThread(); ret.compute(queryId, (k, v) -> v == null - ? new AggregatedStats(currentCPUSample, currentMemSample, thread, isAnchorThread, - finalThreadId, queryId) - : v.merge(currentCPUSample, currentMemSample, isAnchorThread, finalThreadId)); + ? new AggregatedStats(currentCPUSample, currentMemSample, anchorThread, + isAnchorThread, threadEntry._errorStatus, queryId) + : v.merge(currentCPUSample, currentMemSample, isAnchorThread, threadEntry._errorStatus)); } } + + if (!thread.isAlive()) { + _threadEntriesMap.remove(thread); + LOGGER.info("Removing thread from _threadLocalEntry: {}", thread.getName()); + } } // if triggered, accumulate stats of finished tasks of each active query @@ -349,19 +369,26 @@ public Map aggregate(boolean isTriggered) { for (Map.Entry queryIdResult : ret.entrySet()) { String activeQueryId = queryIdResult.getKey(); long accumulatedCPUValue = _isThreadCPUSamplingEnabled - ? _cpuTimeSamplesNS._finishedTaskStatAggregator.getOrDefault(activeQueryId, 0L) : 0; + ? _finishedTaskCPUStatsAggregator.getOrDefault(activeQueryId, 0L) : 0; + long concurrentCPUValue = _isThreadCPUSamplingEnabled + ? _concurrentTaskCPUStatsAggregator.getOrDefault(activeQueryId, 0L) : 0; long accumulatedMemValue = _isThreadMemorySamplingEnabled - ? _memorySamplesBytes._finishedTaskStatAggregator.getOrDefault(activeQueryId, 0L) : 0; - queryIdResult.getValue().merge(accumulatedCPUValue, accumulatedMemValue, - false, CommonConstants.Accounting.IGNORED_TASK_ID); + ? _finishedTaskMemStatsAggregator.getOrDefault(activeQueryId, 0L) : 0; + long concurrentMemValue = _isThreadMemorySamplingEnabled + ? _concurrentTaskMemStatsAggregator.getOrDefault(activeQueryId, 0L) : 0; + queryIdResult.getValue().merge(accumulatedCPUValue + concurrentCPUValue, + accumulatedMemValue + concurrentMemValue, false, null); } } return ret; } + public void postAggregation(Map aggregatedUsagePerActiveQuery) { + } + @Override public Exception getErrorStatus() { - return _errorStatus.get(_runnerWorkerThreadOffsetProvider.get()).getAndSet(null); + return _threadLocalEntry.get()._errorStatus.getAndSet(null); } /** @@ -369,39 +396,39 @@ public Exception getErrorStatus() { * the ordinal Normal(0) does not trigger any action. */ enum TriggeringLevel { - Normal, HeapMemoryAlarmingVerbose, HeapMemoryCritical, HeapMemoryPanic + Normal, HeapMemoryAlarmingVerbose, CPUTimeBasedKilling, HeapMemoryCritical, HeapMemoryPanic } /** * aggregated usage of a query, _thread is the runner */ - static class AggregatedStats { + protected static class AggregatedStats { final String _queryId; - final Thread _thread; - int _threadId; + final Thread _anchorThread; boolean _isAnchorThread; + AtomicReference _exceptionAtomicReference; long _allocatedBytes; long _cpuNS; - - public AggregatedStats(long cpuNS, long allocatedBytes, Thread thread, Boolean isAnchorThread, int threadId, - String queryId) { + public AggregatedStats(long cpuNS, long allocatedBytes, Thread anchorThread, boolean isAnchorThread, + AtomicReference exceptionAtomicReference, String queryId) { _cpuNS = cpuNS; _allocatedBytes = allocatedBytes; - _thread = thread; - _threadId = threadId; - _queryId = queryId; + _anchorThread = anchorThread; _isAnchorThread = isAnchorThread; + _exceptionAtomicReference = exceptionAtomicReference; + _queryId = queryId; } @Override public String toString() { return "AggregatedStats{" + "_queryId='" + _queryId + '\'' + + ", _anchorThread=" + _anchorThread + + ", _isAnchorThread=" + _isAnchorThread + + ", _exceptionAtomicReference=" + _exceptionAtomicReference + ", _allocatedBytes=" + _allocatedBytes + ", _cpuNS=" + _cpuNS - + ", _thread=" + _thread - + ", _threadId=" + _threadId + '}'; } @@ -413,19 +440,21 @@ public long getAllocatedBytes() { return _allocatedBytes; } - public Thread getThread() { - return _thread; + public Thread getAnchorThread() { + return _anchorThread; } - public AggregatedStats merge(long cpuNS, long memoryBytes, boolean isAnchorThread, int threadId) { + public AggregatedStats merge(long cpuNS, long memoryBytes, boolean isAnchorThread, + AtomicReference exceptionAtomicReference) { _cpuNS += cpuNS; _allocatedBytes += memoryBytes; // the merging results is from an anchor thread if (isAnchorThread) { _isAnchorThread = true; - _threadId = threadId; + _exceptionAtomicReference = exceptionAtomicReference; } + // everything else is already set during creation return this; } @@ -454,8 +483,17 @@ class WatcherTask implements Runnable { * _config.getProperty(CommonConstants.Accounting.CONFIG_OF_CRITICAL_LEVEL_HEAP_USAGE_RATIO, CommonConstants.Accounting.DEFAULT_CRITICAL_LEVEL_HEAP_USAGE_RATIO)); + // if after gc the heap usage is still above this, kill the most expensive query + // use this to prevent heap size oscillation and repeatedly triggering gc + private final long _criticalLevelAfterGC = _criticalLevel - (long) (_maxHeapSize + * _config.getProperty(CommonConstants.Accounting.CONFIG_OF_CRITICAL_LEVEL_HEAP_USAGE_RATIO_DELTA_AFTER_GC, + CommonConstants.Accounting.DEFAULT_CONFIG_OF_CRITICAL_LEVEL_HEAP_USAGE_RATIO_DELTA_AFTER_GC)); + // trigger gc if consecutively kill more than some number of queries - private final int _gcTriggerCount = + // set this to 0 to always trigger gc before killing a query to give gc a second chance + // as would minimize the chance of false positive killing in some usecases + // should consider use -XX:+ExplicitGCInvokesConcurrent to avoid STW for some gc algorithms + private final int _gcBackoffCount = _config.getProperty(CommonConstants.Accounting.CONFIG_OF_GC_BACKOFF_COUNT, CommonConstants.Accounting.DEFAULT_GC_BACKOFF_COUNT); @@ -467,8 +505,16 @@ class WatcherTask implements Runnable { // normal sleep time private final int _normalSleepTime = - _config.getProperty(CommonConstants.Accounting.CONFIG_OF_SLEEP_TIME, - CommonConstants.Accounting.DEFAULT_SLEEP_TIME); + _config.getProperty(CommonConstants.Accounting.CONFIG_OF_SLEEP_TIME_MS, + CommonConstants.Accounting.DEFAULT_SLEEP_TIME_MS); + + // wait for gc to complete, according to system.gc() javadoc, when control returns from the method call, + // the Java Virtual Machine has made a best effort to reclaim space from all discarded objects. + // Therefore, we default this to 0. + // Tested with Shenandoah GC and G1GC, with -XX:+ExplicitGCInvokesConcurrent + private final int _gcWaitTime = + _config.getProperty(CommonConstants.Accounting.CONFIG_OF_GC_WAIT_TIME_MS, + CommonConstants.Accounting.DEFAULT_CONFIG_OF_GC_WAIT_TIME_MS); // alarming sleep time denominator, should be > 1 to sample more frequent at alarming level private final int _alarmingSleepTimeDenominator = @@ -488,18 +534,84 @@ class WatcherTask implements Runnable { _config.getProperty(CommonConstants.Accounting.CONFIG_OF_PUBLISHING_JVM_USAGE, CommonConstants.Accounting.DEFAULT_PUBLISHING_JVM_USAGE); + // if we want kill query based on CPU time + private final boolean _isCPUTimeBasedKillingEnabled = + _config.getProperty(CommonConstants.Accounting.CONFIG_OF_CPU_TIME_BASED_KILLING_ENABLED, + CommonConstants.Accounting.DEFAULT_CPU_TIME_BASED_KILLING_ENABLED) && _isThreadCPUSamplingEnabled; + + // CPU time based killing threshold + private final long _cpuTimeBasedKillingThresholdNS = + _config.getProperty(CommonConstants.Accounting.CONFIG_OF_CPU_TIME_BASED_KILLING_THRESHOLD_MS, + CommonConstants.Accounting.DEFAULT_CPU_TIME_BASED_KILLING_THRESHOLD_MS) * 1000_000L; + + // + private final boolean _isQueryKilledMetricEnabled = + _config.getProperty(CommonConstants.Accounting.CONFIG_OF_QUERY_KILLED_METRIC_ENABLED, + CommonConstants.Accounting.DEFAULT_QUERY_KILLED_METRIC_ENABLED); + + private final InstanceType _instanceType = + InstanceType.valueOf(_config.getProperty(CommonConstants.Accounting.CONFIG_OF_INSTANCE_TYPE, + CommonConstants.Accounting.DEFAULT_CONFIG_OF_INSTANCE_TYPE.toString())); + private long _usedBytes; private int _sleepTime; private int _numQueriesKilledConsecutively = 0; - private Map _aggregatedUsagePerActiveQuery; + protected Map _aggregatedUsagePerActiveQuery; private TriggeringLevel _triggeringLevel; + // metrics class + private final AbstractMetrics _metrics; + private final AbstractMetrics.Meter _queryKilledMeter; + private final AbstractMetrics.Meter _heapMemoryCriticalExceededMeter; + private final AbstractMetrics.Meter _heapMemoryPanicExceededMeter; + private final AbstractMetrics.Gauge _memoryUsageGauge; + + WatcherTask() { + switch (_instanceType) { + case SERVER: + _metrics = ServerMetrics.get(); + _queryKilledMeter = ServerMeter.QUERIES_KILLED; + _memoryUsageGauge = ServerGauge.JVM_HEAP_USED_BYTES; + _heapMemoryCriticalExceededMeter = ServerMeter.HEAP_CRITICAL_LEVEL_EXCEEDED; + _heapMemoryPanicExceededMeter = ServerMeter.HEAP_PANIC_LEVEL_EXCEEDED; + break; + case BROKER: + _metrics = BrokerMetrics.get(); + _queryKilledMeter = BrokerMeter.QUERIES_KILLED; + _memoryUsageGauge = BrokerGauge.JVM_HEAP_USED_BYTES; + _heapMemoryCriticalExceededMeter = BrokerMeter.HEAP_CRITICAL_LEVEL_EXCEEDED; + _heapMemoryPanicExceededMeter = BrokerMeter.HEAP_PANIC_LEVEL_EXCEEDED; + break; + default: + LOGGER.error("instanceType: {} not supported, using server metrics", _instanceType); + _metrics = new ServerMetrics(PinotMetricUtils.getPinotMetricsRegistry()); + _queryKilledMeter = ServerMeter.QUERIES_KILLED; + _memoryUsageGauge = ServerGauge.JVM_HEAP_USED_BYTES; + _heapMemoryCriticalExceededMeter = ServerMeter.HEAP_CRITICAL_LEVEL_EXCEEDED; + _heapMemoryPanicExceededMeter = ServerMeter.HEAP_PANIC_LEVEL_EXCEEDED; + break; + } + } + @Override public void run() { + // Log info for the accountant configs LOGGER.info("Starting accountant task for PerQueryCPUMemAccountant."); LOGGER.info("Xmx is {}", _maxHeapSize); + LOGGER.info("_instanceType is {}", _instanceType); LOGGER.info("_alarmingLevel of on heap memory is {}", _alarmingLevel); LOGGER.info("_criticalLevel of on heap memory is {}", _criticalLevel); + LOGGER.info("_criticalLevelAfterGC of on heap memory is {}", _criticalLevelAfterGC); + LOGGER.info("_panicLevel of on heap memory is {}", _panicLevel); + LOGGER.info("_gcBackoffCount is {}", _gcBackoffCount); + LOGGER.info("_gcWaitTime is {}", _gcWaitTime); + LOGGER.info("_normalSleepTime is {}", _normalSleepTime); + LOGGER.info("_alarmingSleepTime is {}", _alarmingSleepTime); + LOGGER.info("_oomKillQueryEnabled: {}", _oomKillQueryEnabled); + LOGGER.info("_minMemoryFootprintForKill: {}", _minMemoryFootprintForKill); + LOGGER.info("_isCPUTimeBasedKillingEnabled: {}, _cpuTimeBasedKillingThresholdNS: {}", + _isCPUTimeBasedKillingEnabled, _cpuTimeBasedKillingThresholdNS); + while (true) { LOGGER.debug("Running timed task for PerQueryCPUMemAccountant."); _triggeringLevel = TriggeringLevel.Normal; @@ -516,17 +628,20 @@ public void run() { evalTriggers(); // Refresh thread usage and aggregate to per query usage if triggered _aggregatedUsagePerActiveQuery = aggregate(_triggeringLevel.ordinal() > TriggeringLevel.Normal.ordinal()); + // post aggregation function + postAggregation(_aggregatedUsagePerActiveQuery); // Act on one triggered actions triggeredActions(); } catch (Exception e) { LOGGER.error("Caught exception while executing stats aggregation and query kill", e); } finally { - if (_aggregatedUsagePerActiveQuery != null) { - LOGGER.debug(_aggregatedUsagePerActiveQuery.toString()); - } + LOGGER.debug(_aggregatedUsagePerActiveQuery == null ? "_aggregatedUsagePerActiveQuery : null" + : _aggregatedUsagePerActiveQuery.toString()); + LOGGER.debug("_threadEntriesMap size: {}", _threadEntriesMap.size()); + // Publish server heap usage metrics if (_publishHeapUsageMetric) { - ServerMetrics.get().setValueOfGlobalGauge(ServerGauge.JVM_HEAP_USED_BYTES, _usedBytes); + _metrics.setValueOfGlobalGauge(_memoryUsageGauge, _usedBytes); } // Clean inactive query stats cleanInactive(); @@ -551,6 +666,7 @@ private boolean outOfMemoryPanicTrigger() { if (_usedBytes >= _panicLevel) { killAllQueries(); _triggeringLevel = TriggeringLevel.HeapMemoryPanic; + _metrics.addMeteredGlobalValue(_heapMemoryPanicExceededMeter, 1); LOGGER.error("Heap used bytes {}, greater than _panicLevel {}, Killed all queries and triggered gc!", _usedBytes, _panicLevel); // call aggregate here as will throw exception and @@ -565,11 +681,18 @@ private boolean outOfMemoryPanicTrigger() { * Triggers should be mutually exclusive and evaluated following level high -> low */ private void evalTriggers() { + if (_isCPUTimeBasedKillingEnabled) { + _triggeringLevel = TriggeringLevel.CPUTimeBasedKilling; + } + if (_usedBytes > _criticalLevel) { _triggeringLevel = TriggeringLevel.HeapMemoryCritical; + _metrics.addMeteredGlobalValue(_heapMemoryCriticalExceededMeter, 1); } else if (_usedBytes > _alarmingLevel) { - _triggeringLevel = LOGGER.isDebugEnabled() ? TriggeringLevel.HeapMemoryAlarmingVerbose : _triggeringLevel; _sleepTime = _alarmingSleepTime; + // For debugging + _triggeringLevel = (IS_DEBUG_MODE_ENABLED && _triggeringLevel == TriggeringLevel.Normal) + ? TriggeringLevel.HeapMemoryAlarmingVerbose : _triggeringLevel; } } @@ -579,9 +702,12 @@ private void evalTriggers() { private void triggeredActions() { switch (_triggeringLevel) { case HeapMemoryCritical: - LOGGER.debug("Heap used bytes {} exceeds critical level", _usedBytes); + LOGGER.warn("Heap used bytes {} exceeds critical level {}", _usedBytes, _criticalLevel); killMostExpensiveQuery(); break; + case CPUTimeBasedKilling: + killCPUTimeExceedQueries(); + break; case HeapMemoryAlarmingVerbose: LOGGER.warn("Heap used bytes {} exceeds alarming level", _usedBytes); LOGGER.warn("Query usage aggregation results {}", _aggregatedUsagePerActiveQuery.toString()); @@ -603,16 +729,19 @@ void reschedule() { void killAllQueries() { if (_oomKillQueryEnabled) { int killedCount = 0; - for (int i = 0; i < _numThreads; i++) { - CPUMemThreadLevelAccountingObjects.TaskEntry - taskEntry = _taskStatus[i].getThreadTaskStatus(); + for (Map.Entry entry : _threadEntriesMap.entrySet()) { + CPUMemThreadLevelAccountingObjects.ThreadEntry threadEntry = entry.getValue(); + CPUMemThreadLevelAccountingObjects.TaskEntry taskEntry = threadEntry.getCurrentThreadTaskStatus(); if (taskEntry != null && taskEntry.isAnchorThread()) { - _errorStatus.get(i).set(new RuntimeException("Query killed due to server out of memory!")); + threadEntry._errorStatus + .set(new RuntimeException(String.format("Query killed due to %s out of memory!", _instanceType))); taskEntry.getAnchorThread().interrupt(); killedCount += 1; } } - ServerMetrics.get().addMeteredGlobalValue(ServerMeter.QUERIES_PREEMPTED, killedCount); + if (_isQueryKilledMetricEnabled) { + _metrics.addMeteredGlobalValue(_queryKilledMeter, killedCount); + } try { Thread.sleep(_normalSleepTime); } catch (InterruptedException ignored) { @@ -629,26 +758,27 @@ void killAllQueries() { * use XX:+ExplicitGCInvokesConcurrent to avoid a full gc when system.gc is triggered */ private void killMostExpensiveQuery() { - if (!_aggregatedUsagePerActiveQuery.isEmpty() && _numQueriesKilledConsecutively >= _gcTriggerCount) { - System.gc(); + if (!_aggregatedUsagePerActiveQuery.isEmpty() && _numQueriesKilledConsecutively >= _gcBackoffCount) { _numQueriesKilledConsecutively = 0; + System.gc(); try { - Thread.sleep(_normalSleepTime); + Thread.sleep(_gcWaitTime); } catch (InterruptedException ignored) { } _usedBytes = MEMORY_MX_BEAN.getHeapMemoryUsage().getUsed(); - if (_usedBytes < _criticalLevel) { + if (_usedBytes < _criticalLevelAfterGC) { return; } + LOGGER.error("After GC, heap used bytes {} still exceeds _criticalLevelAfterGC level {}", + _usedBytes, _criticalLevelAfterGC); } if (!(_isThreadMemorySamplingEnabled || _isThreadCPUSamplingEnabled)) { - LOGGER.warn("Heap used bytes {} exceeds critical level", _usedBytes); LOGGER.warn("But unable to kill query because neither memory nor cpu tracking is enabled"); return; } // Critical heap memory usage while no queries running if (_aggregatedUsagePerActiveQuery.isEmpty()) { - LOGGER.debug("Heap used bytes {} exceeds critical level, but no active queries", _usedBytes); + LOGGER.debug("No active queries to kill"); return; } AggregatedStats maxUsageTuple; @@ -657,33 +787,63 @@ private void killMostExpensiveQuery() { Comparator.comparing(AggregatedStats::getAllocatedBytes)); boolean shouldKill = _oomKillQueryEnabled && maxUsageTuple._allocatedBytes > _minMemoryFootprintForKill; if (shouldKill) { - _errorStatus.get(maxUsageTuple._threadId) - .set(new RuntimeException(String.format(" Query %s got killed because using %d bytes of memory, " - + "exceeding the quota", maxUsageTuple._queryId, maxUsageTuple.getAllocatedBytes()))); - interruptRunnerThread(maxUsageTuple.getThread()); + maxUsageTuple._exceptionAtomicReference + .set(new RuntimeException(String.format( + " Query %s got killed because using %d bytes of memory on %s: %s, exceeding the quota", + maxUsageTuple._queryId, maxUsageTuple.getAllocatedBytes(), _instanceType, _instanceId))); + interruptRunnerThread(maxUsageTuple.getAnchorThread()); + LOGGER.error("Query {} got picked because using {} bytes of memory, actual kill committed true}", + maxUsageTuple._queryId, maxUsageTuple._allocatedBytes); + LOGGER.error("Current task status recorded is {}", _threadEntriesMap); + } else if (!_oomKillQueryEnabled) { + LOGGER.warn("Query {} got picked because using {} bytes of memory, actual kill committed false " + + "because oomKillQueryEnabled is false", + maxUsageTuple._queryId, maxUsageTuple._allocatedBytes); + } else { + LOGGER.warn("But all queries are below quota, no query killed"); } - LOGGER.error("Heap used bytes {} exceeds critical level {}", _usedBytes, _criticalLevel); - LOGGER.error("Query {} got picked because using {} bytes of memory, actual kill committed {}", - maxUsageTuple._queryId, maxUsageTuple._allocatedBytes, shouldKill); } else { maxUsageTuple = Collections.max(_aggregatedUsagePerActiveQuery.values(), Comparator.comparing(AggregatedStats::getCpuNS)); if (_oomKillQueryEnabled) { - _errorStatus.get(maxUsageTuple._threadId) - .set(new RuntimeException(String.format(" Query %s got killed because server memory pressure, using " - + "%d ns of CPU time", maxUsageTuple._queryId, maxUsageTuple.getAllocatedBytes()))); - interruptRunnerThread(maxUsageTuple.getThread()); + maxUsageTuple._exceptionAtomicReference + .set(new RuntimeException(String.format( + " Query %s got killed because memory pressure, using %d ns of CPU time on %s: %s", + maxUsageTuple._queryId, maxUsageTuple.getAllocatedBytes(), _instanceType, _instanceId))); + interruptRunnerThread(maxUsageTuple.getAnchorThread()); + LOGGER.error("Query {} got picked because using {} ns of cpu time, actual kill committed true", + maxUsageTuple._allocatedBytes, maxUsageTuple._queryId); + LOGGER.error("Current task status recorded is {}", _threadEntriesMap); + } else { + LOGGER.warn("Query {} got picked because using {} bytes of memory, actual kill committed false " + + "because oomKillQueryEnabled is false", + maxUsageTuple._queryId, maxUsageTuple._allocatedBytes); + } + } + LOGGER.warn("Query aggregation results {} for the previous kill.", _aggregatedUsagePerActiveQuery.toString()); + } + + private void killCPUTimeExceedQueries() { + for (Map.Entry entry : _aggregatedUsagePerActiveQuery.entrySet()) { + AggregatedStats value = entry.getValue(); + if (value._cpuNS > _cpuTimeBasedKillingThresholdNS) { + LOGGER.error("Current task status recorded is {}. Query {} got picked because using {} ns of cpu time," + + " greater than threshold {}", _threadEntriesMap, value._queryId, value.getCpuNS(), + _cpuTimeBasedKillingThresholdNS); + value._exceptionAtomicReference.set(new RuntimeException( + String.format("Query %s got killed on %s: %s because using %d " + + "CPU time exceeding limit of %d ns CPU time", + value._queryId, _instanceType, _instanceId, value.getCpuNS(), _cpuTimeBasedKillingThresholdNS))); + interruptRunnerThread(value.getAnchorThread()); } - LOGGER.error("Heap used bytes {} exceeds critical level {}", _usedBytes, _criticalLevel); - LOGGER.error("Query {} got picked because using {} ns of cpu time, actual kill committed {}", - maxUsageTuple._allocatedBytes, maxUsageTuple._queryId, _oomKillQueryEnabled); } - LOGGER.error("Query aggregation results {} for the previous kill.", _aggregatedUsagePerActiveQuery.toString()); } private void interruptRunnerThread(Thread thread) { thread.interrupt(); - ServerMetrics.get().addMeteredGlobalValue(ServerMeter.QUERIES_PREEMPTED, 1); + if (_isQueryKilledMetricEnabled) { + _metrics.addMeteredGlobalValue(_queryKilledMeter, 1); + } _numQueriesKilledConsecutively += 1; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/accounting/PerQueryCPUMemAccountantFactoryForTest.java b/pinot-core/src/main/java/org/apache/pinot/core/accounting/PerQueryCPUMemAccountantFactoryForTest.java new file mode 100644 index 000000000000..3e6c9004a88c --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/accounting/PerQueryCPUMemAccountantFactoryForTest.java @@ -0,0 +1,49 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.accounting; + +import java.util.Map; +import org.apache.pinot.core.query.utils.QueryIdUtils; +import org.apache.pinot.spi.accounting.ThreadAccountantFactory; +import org.apache.pinot.spi.accounting.ThreadResourceUsageAccountant; +import org.apache.pinot.spi.env.PinotConfiguration; + + +/** + * For broker integration test, remove tracking for severs + */ +public class PerQueryCPUMemAccountantFactoryForTest implements ThreadAccountantFactory { + @Override + public ThreadResourceUsageAccountant init(PinotConfiguration config, String instanceId) { + return new PerQueryCPUMemResourceUsageAccountantBrokerKillingTest(config, instanceId); + } + + public static class PerQueryCPUMemResourceUsageAccountantBrokerKillingTest + extends PerQueryCPUMemAccountantFactory.PerQueryCPUMemResourceUsageAccountant { + public PerQueryCPUMemResourceUsageAccountantBrokerKillingTest(PinotConfiguration config, String instanceId) { + super(config, instanceId); + } + + public void postAggregation(Map aggregatedUsagePerActiveQuery) { + if (aggregatedUsagePerActiveQuery != null) { + aggregatedUsagePerActiveQuery.entrySet().removeIf(item -> item.getKey().endsWith(QueryIdUtils.OFFLINE_SUFFIX)); + } + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/accounting/utils/RunnerWorkerThreadOffsetProvider.java b/pinot-core/src/main/java/org/apache/pinot/core/accounting/utils/RunnerWorkerThreadOffsetProvider.java deleted file mode 100644 index 3fe00a58dd88..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/accounting/utils/RunnerWorkerThreadOffsetProvider.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.accounting.utils; - -import com.google.common.annotations.VisibleForTesting; -import java.util.concurrent.atomic.AtomicInteger; - - -/** - * map each thread to a unique id, starting from zero, used as its offset to access - * cputime/memory/job status, etc - */ -public class RunnerWorkerThreadOffsetProvider { - - // Thread local variable containing each thread's ID - private final AtomicInteger _atomicInteger = new AtomicInteger(0); - private final ThreadLocal _threadId = ThreadLocal.withInitial(_atomicInteger::getAndIncrement); - - public RunnerWorkerThreadOffsetProvider() { - } - - @VisibleForTesting - public void reset() { - _atomicInteger.set(0); - } - - // TODO: make this not dependent on numRunnerThreads - /** - * Returns the current thread's unique ID, assigning it if necessary - */ - public int get() { - return _threadId.get(); - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/auth/Actions.java b/pinot-core/src/main/java/org/apache/pinot/core/auth/Actions.java new file mode 100644 index 000000000000..877041eb7d28 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/auth/Actions.java @@ -0,0 +1,151 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.auth; + +/** + * Different action types used in finer grain access control of the rest endpoints + * Action names are in format, e.g. GetSchema, ListTables, etc. + */ +public class Actions { + // Action names for cluster + public static class Cluster { + public static final String CANCEL_QUERY = "CancelQuery"; + public static final String CLEANUP_TASK = "CleanupTask"; + public static final String COMMIT_SEGMENT = "CommitSegment"; + public static final String CREATE_INSTANCE = "CreateInstance"; + public static final String CREATE_TASK = "CreateTask"; + public static final String CREATE_TENANT = "CreateTenant"; + public static final String CREATE_USER = "CreateUser"; + public static final String DEBUG_TASK = "DebugTask"; + public static final String DELETE_CLUSTER_CONFIG = "DeleteClusterConfig"; + public static final String DELETE_INSTANCE = "DeleteInstance"; + public static final String DELETE_TASK = "DeleteTask"; + public static final String DELETE_TENANT = "DeleteTenant"; + public static final String DELETE_USER = "DeleteUser"; + public static final String DELETE_ZNODE = "DeleteZnode"; + public static final String DELETE_DATABASE = "DeleteDatabase"; + public static final String ESTIMATE_UPSERT_MEMORY = "EstimateUpsertMemory"; + public static final String EXECUTE_TASK = "ExecuteTask"; + public static final String GET_ADMIN_INFO = "GetAdminInfo"; + public static final String GET_APP_CONFIG = "GetAppConfig"; + public static final String GET_AUTH = "GetAuth"; + public static final String GET_BROKER = "GetBroker"; + public static final String GET_CLUSTER_CONFIG = "GetClusterConfig"; + public static final String GET_FORCE_COMMIT_STATUS = "GetForceCommitStatus"; + public static final String GET_HEALTH = "GetHealth"; + public static final String GET_INSTANCE = "GetInstance"; + public static final String GET_LOGGER = "GetLogger"; + public static final String GET_LOG_FILE = "GetLogFile"; + public static final String GET_REBALANCE_STATUS = "GetRebalanceStatus"; + public static final String GET_RUNNING_QUERY = "GetRunningQuery"; + public static final String GET_SCHEDULER_INFO = "GetSchedulerInfo"; + public static final String GET_SCHEMA = "GetSchema"; + public static final String GET_SEGMENT = "GetSegment"; + public static final String GET_SEGMENT_RELOAD_STATUS = "GetSegmentReloadStatus"; + public static final String GET_SERVER_ROUTING_STATS = "GetServerRoutingStats"; + public static final String GET_DATABASE = "GetDatabase"; + public static final String GET_TABLE = "GetTable"; + public static final String GET_TABLE_CONFIG = "GetTableConfig"; + public static final String GET_TABLE_LEADER = "GetTableLeader"; + public static final String GET_TASK = "GetTask"; + public static final String GET_TENANT = "GetTenant"; + public static final String GET_USER = "GetUser"; + public static final String GET_VERSION = "GetVersion"; + public static final String GET_ZNODE = "GetZnode"; + public static final String INGEST_FILE = "IngestFile"; + public static final String RECOMMEND_CONFIG = "RecommendConfig"; + public static final String RESET_SEGMENT = "ResetSegment"; + public static final String RESUME_TASK = "ResumeTask"; + public static final String STOP_TASK = "StopTask"; + public static final String UPDATE_BROKER_RESOURCE = "UpdateBrokerResource"; + public static final String UPDATE_CLUSTER_CONFIG = "UpdateClusterConfig"; + public static final String UPDATE_INSTANCE = "UpdateInstance"; + public static final String UPDATE_LOGGER = "UpdateLogger"; + public static final String UPDATE_QPS = "UpdateQPS"; + public static final String UPDATE_TASK_QUEUE = "UpdateTaskQueue"; + public static final String UPDATE_TENANT = "UpdateTenant"; + public static final String UPDATE_TENANT_METADATA = "UpdateTenantMetadata"; + public static final String REBALANCE_TENANT_TABLES = "RebalanceTenantTables"; + public static final String UPDATE_TIME_INTERVAL = "UpdateTimeInterval"; + public static final String UPDATE_USER = "UpdateUser"; + public static final String UPDATE_ZNODE = "UpdateZnode"; + public static final String UPLOAD_SEGMENT = "UploadSegment"; + public static final String GET_INSTANCE_PARTITIONS = "GetInstancePartitions"; + public static final String UPDATE_INSTANCE_PARTITIONS = "UpdateInstancePartitions"; + } + + // Action names for table + public static class Table { + public static final String BUILD_ROUTING = "BuildRouting"; + public static final String CANCEL_REBALANCE = "CancelRebalance"; + public static final String CREATE_INSTANCE_PARTITIONS = "CreateInstancePartitions"; + public static final String CREATE_SCHEMA = "CreateSchema"; + public static final String CREATE_TABLE = "CreateTable"; + public static final String DELETE_INSTANCE_PARTITIONS = "DeleteInstancePartitions"; + public static final String DELETE_ROUTING = "DeleteRouting"; + public static final String DELETE_SCHEMA = "DeleteSchema"; + public static final String DELETE_SEGMENT = "DeleteSegment"; + public static final String DELETE_TABLE = "DeleteTable"; + public static final String DELETE_TIME_BOUNDARY = "DeleteTimeBoundary"; + public static final String DISABLE_TABLE = "DisableTable"; + public static final String DOWNLOAD_SEGMENT = "DownloadSegment"; + public static final String ENABLE_TABLE = "EnableTable"; + public static final String FORCE_COMMIT = "ForceCommit"; + public static final String GET_BROKER = "GetBroker"; + public static final String GET_CONSUMING_SEGMENTS = "GetConsumingSegments"; + public static final String GET_CONTROLLER_JOBS = "GetControllerJobs"; + public static final String GET_DEBUG_INFO = "GetDebugInfo"; + public static final String GET_EXTERNAL_VIEW = "GetExternalView"; + public static final String GET_IDEAL_STATE = "GetIdealState"; + public static final String GET_INSTANCE = "GetInstance"; + public static final String GET_INSTANCE_PARTITIONS = "GetInstancePartitions"; + public static final String GET_METADATA = "GetMetadata"; + public static final String GET_PAUSE_STATUS = "GetPauseStatus"; + public static final String GET_ROUTING_TABLE = "GetRoutingTable"; + public static final String GET_SCHEMA = "GetSchema"; + public static final String GET_SEGMENT = "GetSegment"; + public static final String GET_SEGMENT_LINEAGE = "GetSegmentLineage"; + public static final String GET_SEGMENT_MAP = "GetSegmentMap"; + public static final String GET_SERVER_MAP = "GetServerMap"; + public static final String GET_SIZE = "GetSize"; + public static final String GET_STATE = "GetState"; + public static final String GET_STORAGE_TIER = "GetStorageTier"; + public static final String GET_TABLE_CONFIG = "GetTableConfig"; + public static final String GET_TABLE_CONFIGS = "GetTableConfigs"; + public static final String GET_TABLE_LEADER = "GetTableLeader"; + public static final String GET_TIME_BOUNDARY = "GetTimeBoundary"; + public static final String GET_SCHEDULER_JOB_DETAILS = "GetSchedulerJobDetails"; + public static final String PAUSE_CONSUMPTION = "PauseConsumption"; + public static final String QUERY = "Query"; + public static final String REBALANCE_TABLE = "RebalanceTable"; + public static final String REBUILD_BROKER_RESOURCE = "RebuildBrokerResource"; + public static final String REFRESH_ROUTING = "RefreshRouting"; + public static final String RELOAD_SEGMENT = "ReloadSegment"; + public static final String REPLACE_SEGMENT = "ReplaceSegment"; + public static final String RESUME_CONSUMPTION = "ResumeConsumption"; + public static final String UPDATE_INSTANCE_PARTITIONS = "UpdateInstancePartitions"; + public static final String UPDATE_SCHEMA = "UpdateSchema"; + public static final String UPDATE_TABLE_CONFIG = "UpdateTableConfig"; + public static final String UPDATE_TABLE_CONFIGS = "UpdateTableConfigs"; + public static final String UPLOAD_SEGMENT = "UploadSegment"; + public static final String VALIDATE_SCHEMA = "ValidateSchema"; + public static final String VALIDATE_TABLE_CONFIGS = "ValidateTableConfigs"; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/auth/Authorize.java b/pinot-core/src/main/java/org/apache/pinot/core/auth/Authorize.java new file mode 100644 index 000000000000..75c7f52d7d26 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/auth/Authorize.java @@ -0,0 +1,44 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.auth; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + + +/** + * Annotation to be used on top of REST endpoints. Methods annotated with this annotation automatically get + * authorized in {@link AuthenticationFilter} and if validation passes, then the methods get executed. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Authorize { + // Type of the target resource + TargetType targetType(); + + // The query or path parameter to use to get the id of the resource + // If the target type is the Pinot cluster, then this field is not required + String paramName() default ""; + + // action to validate on the specific resource + String action(); +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/auth/BasicAuthUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/auth/BasicAuthUtils.java index 271fd44450c1..9ad74943b633 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/auth/BasicAuthUtils.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/auth/BasicAuthUtils.java @@ -20,15 +20,12 @@ import com.google.common.base.Preconditions; import java.util.Arrays; -import java.util.Base64; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; -import javax.annotation.Nullable; import org.apache.commons.lang3.StringUtils; -import org.apache.pinot.common.utils.BcryptUtils; import org.apache.pinot.spi.config.user.UserConfig; import org.apache.pinot.spi.env.PinotConfiguration; @@ -64,21 +61,22 @@ private BasicAuthUtils() { * @return list of BasicAuthPrincipals */ public static List extractBasicAuthPrincipals(PinotConfiguration configuration, String prefix) { - String principalNames = configuration.getProperty(prefix); - Preconditions.checkArgument(StringUtils.isNotBlank(principalNames), "must provide principals"); + String principalNames = configuration.getProperty(prefix); + Preconditions.checkArgument(StringUtils.isNotBlank(principalNames), "must provide principals"); - return Arrays.stream(principalNames.split(",")).map(rawName -> { - String name = rawName.trim(); - Preconditions.checkArgument(StringUtils.isNotBlank(name), "%s is not a valid name", name); + return Arrays.stream(principalNames.split(",")).map(rawName -> { + String name = rawName.trim(); + Preconditions.checkArgument(StringUtils.isNotBlank(name), "%s is not a valid name", name); - String password = configuration.getProperty(String.format("%s.%s.%s", prefix, name, PASSWORD)); - Preconditions.checkArgument(StringUtils.isNotBlank(password), "must provide a password for %s", name); + String password = configuration.getProperty(String.format("%s.%s.%s", prefix, name, PASSWORD)); + Preconditions.checkArgument(StringUtils.isNotBlank(password), "must provide a password for %s", name); - Set tables = extractSet(configuration, String.format("%s.%s.%s", prefix, name, TABLES)); - Set permissions = extractSet(configuration, String.format("%s.%s.%s", prefix, name, PERMISSIONS)); + Set tables = extractSet(configuration, String.format("%s.%s.%s", prefix, name, TABLES)); + Set permissions = extractSet(configuration, String.format("%s.%s.%s", prefix, name, PERMISSIONS)); - return new BasicAuthPrincipal(name, toBasicAuthToken(name, password), tables, permissions); - }).collect(Collectors.toList()); + return new BasicAuthPrincipal(name, org.apache.pinot.common.auth.BasicAuthUtils.toBasicAuthToken(name, password), + tables, permissions); + }).collect(Collectors.toList()); } public static List extractBasicAuthPrincipals(List userConfigList) { @@ -98,7 +96,8 @@ public static List extractBasicAuthPrincipals(List Collections.emptyList()) .stream().map(x -> x.toString()) .collect(Collectors.toSet()); - return new ZkBasicAuthPrincipal(name, toBasicAuthToken(name, password), password, + return new ZkBasicAuthPrincipal(name, + org.apache.pinot.common.auth.BasicAuthUtils.toBasicAuthToken(name, password), password, component, role, tables, permissions); }).collect(Collectors.toList()); } @@ -110,74 +109,4 @@ private static Set extractSet(PinotConfiguration configuration, String k } return Collections.emptySet(); } - - /** - * Convert a pair of name and password into a http header-compliant base64 encoded token - * - * @param name user name - * @param password password - * @return base64 encoded basic auth token - */ - @Nullable - public static String toBasicAuthToken(String name, String password) { - if (StringUtils.isBlank(name)) { - return null; - } - String identifier = String.format("%s:%s", name, password); - return normalizeBase64Token(String.format("Basic %s", Base64.getEncoder().encodeToString(identifier.getBytes()))); - } - - public static String decodeBasicAuthToken(String auth) { - if (StringUtils.isBlank(auth)) { - return null; - } - String replacedAuth = StringUtils.replace(auth, "Basic ", ""); - byte[] decodedBytes = Base64.getDecoder().decode(replacedAuth); - String decodedString = new String(decodedBytes); - return decodedString; - } - - public static String extractUsername(String auth) { - String decodedString = decodeBasicAuthToken(auth); - return StringUtils.split(decodedString, ":")[0]; - } - - public static String extractPassword(String auth) { - String decodedString = decodeBasicAuthToken(auth); - return StringUtils.split(decodedString, ":")[1]; - } - - /** - * Convert http header-compliant base64 encoded token to password-encrypted base64 token - * - * @param auth http base64 token - * @return base64 encoded basic auth token - */ - public static String toEncryptBasicAuthToken(String auth) { - if (StringUtils.isBlank(auth)) { - return null; - } - String replacedAuth = StringUtils.replace(auth, "Basic ", ""); - byte[] decodedBytes = Base64.getDecoder().decode(replacedAuth); - String decodedString = new String(decodedBytes); - String[] cretential = StringUtils.split(decodedString, ":"); - String rawUsername = cretential[0]; - String rawPassword = cretential[1]; - String encryptedPassword = BcryptUtils.encrypt(rawPassword); - return toBasicAuthToken(rawUsername, encryptedPassword); - } - - /** - * Normalize a base64 encoded auth token by stripping redundant padding (spaces, '=') - * - * @param token base64 encoded auth token - * @return normalized auth token - */ - @Nullable - public static String normalizeBase64Token(String token) { - if (token == null) { - return null; - } - return StringUtils.remove(token.trim(), '='); - } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/auth/FineGrainedAccessControl.java b/pinot-core/src/main/java/org/apache/pinot/core/auth/FineGrainedAccessControl.java new file mode 100644 index 000000000000..df6b51c66f36 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/auth/FineGrainedAccessControl.java @@ -0,0 +1,68 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.auth; + +import javax.ws.rs.core.HttpHeaders; +import org.apache.pinot.spi.auth.AuthorizationResult; +import org.apache.pinot.spi.auth.BasicAuthorizationResultImpl; + + +/** + * Interface for fine-grained access control. + */ +public interface FineGrainedAccessControl { + /** + * Checks whether the user has access to perform action on the particular resource + * + * @param httpHeaders HTTP headers + * @param targetType type of resource being accessed + * @param targetId id of the resource + * @param action type to validate + * @return true if user is allowed to perform the action + */ + default boolean hasAccess(HttpHeaders httpHeaders, TargetType targetType, String targetId, String action) { + return true; + } + + /** + * Verifies if the user has access to perform a specific action on a particular resource. + * The default implementation returns a {@link BasicAuthorizationResultImpl} with the result of the hasAccess() of + * the implementation + * + * @param httpHeaders HTTP headers + * @param targetType type of resource being accessed + * @param targetId id of the resource + * @param action type to validate + * @return An AuthorizationResult object, encapsulating whether the access is granted or not. + */ + default AuthorizationResult authorize(HttpHeaders httpHeaders, TargetType targetType, String targetId, + String action) { + return new BasicAuthorizationResultImpl(hasAccess(httpHeaders, targetType, targetId, action)); + } + + /** + * If an API is neither annotated with Authorize nor ManualAuthorization, + * this method will be called to check the default authorization. + * If the return is false, then API will be terminated by the filter. + * @return true to allow + */ + default boolean defaultAccess(HttpHeaders httpHeaders) { + return true; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/auth/FineGrainedAuthUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/auth/FineGrainedAuthUtils.java new file mode 100644 index 000000000000..d55d6e8b71b0 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/auth/FineGrainedAuthUtils.java @@ -0,0 +1,134 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.auth; + +import java.lang.reflect.Method; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import org.apache.commons.lang3.StringUtils; +import org.apache.pinot.common.utils.DatabaseUtils; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Utility methods to share in Broker and Controller request filters related to fine grain authorization. + */ +public class FineGrainedAuthUtils { + + private static final Logger LOGGER = LoggerFactory.getLogger(FineGrainedAuthUtils.class); + + private FineGrainedAuthUtils() { + } + + /** + * Returns the parameter from the path or query params. + * @param paramName to look for + * @param pathParams path params + * @param queryParams query params + * @return the value of the parameter + */ + private static String findParam(String paramName, MultivaluedMap pathParams, + MultivaluedMap queryParams) { + String name = pathParams.getFirst(paramName); + if (name == null) { + name = queryParams.getFirst(paramName); + } + return name; + } + + /** + * Validate fine-grained authorization for APIs. + * There are 2 possible cases: + * 1. {@link Authorize} annotation is present on the method. In this case, do the finer grain authorization using the + * fields of the annotation. There are 2 possibilities depending on the targetType ({@link TargetType}): + * a. The targetType is {@link TargetType#CLUSTER}. In this case, the paramName field + * ({@link Authorize#paramName()}) is not used, since the target is the Pinot cluster. + * b. The targetType is {@link TargetType#TABLE}. In this case, the paramName field + * ({@link Authorize#paramName()}) is mandatory, and it must be found in either the path parameters or the + * query parameters. + * 2. {@link Authorize} annotation is not present on the method. In this use the default authorization. + * + * @param endpointMethod of the API + * @param uriInfo of the API + * @param httpHeaders of the API + * @param accessControl to check the fine-grained authorization + */ + public static void validateFineGrainedAuth(Method endpointMethod, UriInfo uriInfo, HttpHeaders httpHeaders, + FineGrainedAccessControl accessControl) { + if (endpointMethod.isAnnotationPresent(Authorize.class)) { + final Authorize auth = endpointMethod.getAnnotation(Authorize.class); + String targetId = null; + // Message to use in the access denied exception + String accessDeniedMsg; + if (auth.targetType() == TargetType.TABLE) { + // paramName is mandatory for table level authorization + if (StringUtils.isEmpty(auth.paramName())) { + throw new WebApplicationException( + "paramName not found for table level authorization in API: " + uriInfo.getRequestUri(), + Response.Status.INTERNAL_SERVER_ERROR); + } + + // find the paramName in the path or query params + targetId = findParam(auth.paramName(), uriInfo.getPathParameters(), uriInfo.getQueryParameters()); + + if (StringUtils.isEmpty(targetId)) { + throw new WebApplicationException( + "Could not find paramName " + auth.paramName() + " in path or query params of the API: " + + uriInfo.getRequestUri(), Response.Status.INTERNAL_SERVER_ERROR); + } + + // Table name may contain type, hence get raw table name for checking access + targetId = DatabaseUtils.translateTableName(TableNameBuilder.extractRawTableName(targetId), httpHeaders); + + accessDeniedMsg = "Access denied to " + auth.action() + " for table: " + targetId; + } else if (auth.targetType() == TargetType.CLUSTER) { + accessDeniedMsg = "Access denied to " + auth.action() + " in the cluster"; + } else { + throw new WebApplicationException( + "Unsupported targetType: " + auth.targetType() + " in API: " + uriInfo.getRequestUri(), + Response.Status.INTERNAL_SERVER_ERROR); + } + + boolean hasAccess; + try { + hasAccess = accessControl.hasAccess(httpHeaders, auth.targetType(), targetId, auth.action()); + } catch (Throwable t) { + // catch and log Throwable for NoSuchMethodError which can happen when there are classpath conflicts + // otherwise, grizzly will return a 500 without any logs or indication of what failed + String errorMsg = String.format("Failed to check for access for target type %s and target ID %s with action %s", + auth.targetType(), targetId, auth.action()); + LOGGER.error(errorMsg, t); + throw new WebApplicationException(errorMsg, t, Response.Status.INTERNAL_SERVER_ERROR); + } + + // Check for access now + if (!hasAccess) { + throw new WebApplicationException(accessDeniedMsg, Response.Status.FORBIDDEN); + } + } else if (!accessControl.defaultAccess(httpHeaders)) { + throw new WebApplicationException("Access denied - default authorization failed", Response.Status.FORBIDDEN); + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/auth/TargetType.java b/pinot-core/src/main/java/org/apache/pinot/core/auth/TargetType.java new file mode 100644 index 000000000000..7363261d1917 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/auth/TargetType.java @@ -0,0 +1,27 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.auth; + +/** + * Target object types used in fine grain access control of the rest endpoints + */ +public enum TargetType { + CLUSTER, TABLE +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/common/Block.java b/pinot-core/src/main/java/org/apache/pinot/core/common/Block.java index 03397a59481f..2ee6585ecf47 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/common/Block.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/common/Block.java @@ -19,41 +19,8 @@ package org.apache.pinot.core.common; /** - * - * A block represents a set of rows.A segment will contain one or more blocks - * Currently, it assumes only one column per block. We might change this in - * future + * A {@code Block} represents the data block returned by the {@link Operator}. Each operator can return multiple blocks. + * It can contain document ids, column values or result rows depending on the operator. */ public interface Block { - - /** - * Returns valset that allows one to iterate over the docId. If no predicate - * is provided, this will consists of all docIds within the block - * - * @return {@link BlockDocIdSet} - */ - - BlockDocIdSet getBlockDocIdSet(); - - /** - * Returns valset that allows one to iterate over the values - * - * @return {@link BlockValSet} - */ - BlockValSet getBlockValueSet(); - - /** - * Allows one to iterate over the DocId And Value in parallel - * - * @return - */ - BlockDocIdValueSet getBlockDocIdValueSet(); - - /** - * For future optimizations. The metadata can consists of bloom filter, - * min/max, sum, count etc that can be used in filtering, aggregation - * - * @return - */ - BlockMetadata getMetadata(); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/common/BlockDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/common/BlockDocIdSet.java index bedff54d8333..dec522db9a67 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/common/BlockDocIdSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/common/BlockDocIdSet.java @@ -18,9 +18,21 @@ */ package org.apache.pinot.core.common; +import org.apache.pinot.core.operator.blocks.FilterBlock; +import org.apache.pinot.core.operator.dociditerators.AndDocIdIterator; +import org.apache.pinot.core.operator.dociditerators.BitmapDocIdIterator; +import org.apache.pinot.core.operator.dociditerators.OrDocIdIterator; +import org.apache.pinot.core.operator.dociditerators.RangelessBitmapDocIdIterator; +import org.apache.pinot.core.operator.dociditerators.ScanBasedDocIdIterator; +import org.apache.pinot.core.operator.docidsets.BitmapDocIdSet; +import org.apache.pinot.core.operator.docidsets.RangelessBitmapDocIdSet; +import org.apache.pinot.segment.spi.Constants; +import org.roaringbitmap.RoaringBitmapWriter; +import org.roaringbitmap.buffer.MutableRoaringBitmap; + + /** - * The interface {@code BlockDocIdSet} represents all the matching document ids for a predicate. - * TODO: Redesign the filtering to skip the BlockDocIdSet and directly use BlockDocIdIterator + * The {@code BlockDocIdSet} contains the matching document ids returned by the {@link FilterBlock}. */ public interface BlockDocIdSet { @@ -29,4 +41,41 @@ public interface BlockDocIdSet { * ascending order. */ BlockDocIdIterator iterator(); + + /** + * Returns the number of entries (SV value contains one entry, MV value contains multiple entries) scanned in the + * filtering phase. This method should be called after the filtering is done. + */ + long getNumEntriesScannedInFilter(); + + /** + * For scan-based FilterBlockDocIdSet, pre-scans the documents and returns a non-scan-based FilterBlockDocIdSet. + */ + default BlockDocIdSet toNonScanDocIdSet() { + BlockDocIdIterator docIdIterator = iterator(); + + // NOTE: AND and OR DocIdIterator might contain scan-based DocIdIterator + // TODO: This scan is not counted in the execution stats + if (docIdIterator instanceof ScanBasedDocIdIterator || docIdIterator instanceof AndDocIdIterator + || docIdIterator instanceof OrDocIdIterator) { + RoaringBitmapWriter bitmapWriter = + RoaringBitmapWriter.bufferWriter().runCompress(false).get(); + int docId; + while ((docId = docIdIterator.next()) != Constants.EOF) { + bitmapWriter.add(docId); + } + return new RangelessBitmapDocIdSet(bitmapWriter.get()); + } + + // NOTE: AND and OR DocIdSet might return BitmapBasedDocIdIterator after processing the iterators. Create a new + // DocIdSet to prevent processing the iterators again + if (docIdIterator instanceof RangelessBitmapDocIdIterator) { + return new RangelessBitmapDocIdSet((RangelessBitmapDocIdIterator) docIdIterator); + } + if (docIdIterator instanceof BitmapDocIdIterator) { + return new BitmapDocIdSet((BitmapDocIdIterator) docIdIterator); + } + + return this; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/common/BlockDocIdValueIterator.java b/pinot-core/src/main/java/org/apache/pinot/core/common/BlockDocIdValueIterator.java deleted file mode 100644 index 350ef0afcb9f..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/common/BlockDocIdValueIterator.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.common; - -/** - * - * Not used for now, since most of the predicates make use of inverted index. - * Revisit when we push predicate evaluation up into the operator - */ -public interface BlockDocIdValueIterator { - - boolean advance(); - - int currentDocId(); - - int currentVal(); -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/common/BlockDocIdValueSet.java b/pinot-core/src/main/java/org/apache/pinot/core/common/BlockDocIdValueSet.java deleted file mode 100644 index f917df22c3d7..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/common/BlockDocIdValueSet.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.common; - -public interface BlockDocIdValueSet { - - public BlockDocIdValueIterator iterator(); -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/common/BlockMetadata.java b/pinot-core/src/main/java/org/apache/pinot/core/common/BlockMetadata.java deleted file mode 100644 index 041ea219dec4..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/common/BlockMetadata.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.common; - -import org.apache.pinot.segment.spi.index.reader.Dictionary; -import org.apache.pinot.spi.data.FieldSpec; - - -public interface BlockMetadata { - - int getLength(); - - int getStartDocId(); - - int getEndDocId(); - - FieldSpec.DataType getDataType(); - - boolean isSingleValue(); - - int getMaxNumberOfMultiValues(); - - boolean hasDictionary(); - - Dictionary getDictionary(); -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/common/MinionConstants.java b/pinot-core/src/main/java/org/apache/pinot/core/common/MinionConstants.java index c4007ee561da..9b1b89b4b72b 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/common/MinionConstants.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/common/MinionConstants.java @@ -52,6 +52,7 @@ private MinionConstants() { */ public static final String TIMEOUT_MS_KEY_SUFFIX = ".timeoutMs"; public static final String NUM_CONCURRENT_TASKS_PER_INSTANCE_KEY_SUFFIX = ".numConcurrentTasksPerInstance"; + public static final String MAX_ATTEMPTS_PER_TASK_KEY_SUFFIX = ".maxAttemptsPerTask"; /** * Table level configs @@ -59,10 +60,10 @@ private MinionConstants() { public static final String TABLE_MAX_NUM_TASKS_KEY = "tableMaxNumTasks"; public static final String ENABLE_REPLACE_SEGMENTS_KEY = "enableReplaceSegments"; - public static class ConvertToRawIndexTask { - public static final String TASK_TYPE = "ConvertToRawIndexTask"; - public static final String COLUMNS_TO_CONVERT_KEY = "columnsToConvert"; - } + /** + * Job configs + */ + public static final int DEFAULT_MAX_ATTEMPTS_PER_TASK = 1; // Purges rows inside segment that match chosen criteria public static class PurgeTask { @@ -96,10 +97,14 @@ public static abstract class MergeTask { // Merge config public static final String MERGE_TYPE_KEY = "mergeType"; public static final String AGGREGATION_TYPE_KEY_SUFFIX = ".aggregationType"; + public static final String MODE = "mode"; + public static final String PROCESS_FROM_WATERMARK_MODE = "processFromWatermark"; + public static final String PROCESS_ALL_MODE = "processAll"; // Segment config public static final String MAX_NUM_RECORDS_PER_TASK_KEY = "maxNumRecordsPerTask"; public static final String MAX_NUM_RECORDS_PER_SEGMENT_KEY = "maxNumRecordsPerSegment"; + public static final String SEGMENT_MAPPER_FILE_SIZE_IN_BYTES = "segmentMapperFileSizeThresholdInBytes"; public static final String MAX_NUM_PARALLEL_BUCKETS = "maxNumParallelBuckets"; public static final String SEGMENT_NAME_PREFIX_KEY = "segmentNamePrefix"; public static final String SEGMENT_NAME_POSTFIX_KEY = "segmentNamePostfix"; @@ -120,6 +125,9 @@ public static class MergeRollupTask extends MergeTask { public static final String SEGMENT_ZK_METADATA_TIME_KEY = TASK_TYPE + TASK_TIME_SUFFIX; public static final String MERGED_SEGMENT_NAME_PREFIX = "merged_"; + + // Custom segment group manager class name + public static final String SEGMENT_GROUP_MANAGER_CLASS_NAME_KEY = "segment.group.manager.class.name"; } /** @@ -138,4 +146,33 @@ public static class SegmentGenerationAndPushTask { public static final String CONFIG_NUMBER_CONCURRENT_TASKS_PER_INSTANCE = "SegmentGenerationAndPushTask.numConcurrentTasksPerInstance"; } + + public static class UpsertCompactionTask { + public static final String TASK_TYPE = "UpsertCompactionTask"; + /** + * The time period to wait before picking segments for this task + * e.g. if set to "2d", no task will be scheduled for a time window younger than 2 days + */ + public static final String BUFFER_TIME_PERIOD_KEY = "bufferTimePeriod"; + /** + * The maximum percent of old records allowed for a completed segment. + * e.g. if the percent surpasses 30, then the segment may be compacted + */ + public static final String INVALID_RECORDS_THRESHOLD_PERCENT = "invalidRecordsThresholdPercent"; + /** + * The maximum count of old records for a completed segment + * e.g. if the count surpasses 100k, then the segment may be compacted + */ + public static final String INVALID_RECORDS_THRESHOLD_COUNT = "invalidRecordsThresholdCount"; + + /** + * Valid doc ids type + */ + public static final String VALID_DOC_IDS_TYPE = "validDocIdsType"; + + /** + * number of segments to query in one batch to fetch valid doc id metadata, by default 500 + */ + public static final String NUM_SEGMENTS_BATCH_PER_SERVER_REQUEST = "numSegmentsBatchPerServerRequest"; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/common/ObjectSerDeUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/common/ObjectSerDeUtils.java index fbfee474e9af..477d78d45021 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/common/ObjectSerDeUtils.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/common/ObjectSerDeUtils.java @@ -19,7 +19,9 @@ package org.apache.pinot.core.common; import com.clearspring.analytics.stream.cardinality.HyperLogLog; +import com.clearspring.analytics.stream.cardinality.HyperLogLogPlus; import com.clearspring.analytics.stream.cardinality.RegisterSet; +import com.dynatrace.hash4j.distinctcount.UltraLogLog; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.primitives.Longs; @@ -33,19 +35,24 @@ import it.unimi.dsi.fastutil.doubles.DoubleSet; import it.unimi.dsi.fastutil.floats.Float2LongMap; import it.unimi.dsi.fastutil.floats.Float2LongOpenHashMap; +import it.unimi.dsi.fastutil.floats.FloatArrayList; import it.unimi.dsi.fastutil.floats.FloatIterator; import it.unimi.dsi.fastutil.floats.FloatOpenHashSet; import it.unimi.dsi.fastutil.floats.FloatSet; import it.unimi.dsi.fastutil.ints.Int2LongMap; import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntIterator; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.longs.Long2LongMap; import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongArrayList; import it.unimi.dsi.fastutil.longs.LongIterator; import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.longs.LongSet; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectSet; import java.io.IOException; @@ -56,15 +63,27 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.PriorityQueue; import java.util.Set; +import org.apache.datasketches.common.ArrayOfStringsSerDe; +import org.apache.datasketches.cpc.CpcSketch; +import org.apache.datasketches.frequencies.ItemsSketch; +import org.apache.datasketches.frequencies.LongsSketch; +import org.apache.datasketches.kll.KllDoublesSketch; import org.apache.datasketches.memory.Memory; import org.apache.datasketches.theta.Sketch; -import org.apache.pinot.common.datatable.DataTable; +import org.apache.datasketches.tuple.aninteger.IntegerSummary; +import org.apache.datasketches.tuple.aninteger.IntegerSummaryDeserializer; +import org.apache.pinot.common.CustomObject; +import org.apache.pinot.common.utils.HashUtil; +import org.apache.pinot.core.query.aggregation.function.funnel.FunnelStepEvent; +import org.apache.pinot.core.query.aggregation.utils.exprminmax.ExprMinMaxObject; import org.apache.pinot.core.query.distinct.DistinctTable; import org.apache.pinot.core.query.utils.idset.IdSet; import org.apache.pinot.core.query.utils.idset.IdSets; import org.apache.pinot.segment.local.customobject.AvgPair; import org.apache.pinot.segment.local.customobject.CovarianceTuple; +import org.apache.pinot.segment.local.customobject.CpcSketchAccumulator; import org.apache.pinot.segment.local.customobject.DoubleLongPair; import org.apache.pinot.segment.local.customobject.FloatLongPair; import org.apache.pinot.segment.local.customobject.IntLongPair; @@ -73,6 +92,8 @@ import org.apache.pinot.segment.local.customobject.PinotFourthMoment; import org.apache.pinot.segment.local.customobject.QuantileDigest; import org.apache.pinot.segment.local.customobject.StringLongPair; +import org.apache.pinot.segment.local.customobject.ThetaSketchAccumulator; +import org.apache.pinot.segment.local.customobject.TupleIntSketchAccumulator; import org.apache.pinot.segment.local.customobject.VarianceTuple; import org.apache.pinot.segment.local.utils.GeometrySerializer; import org.apache.pinot.spi.utils.BigDecimalUtils; @@ -127,7 +148,24 @@ public enum ObjectType { StringLongPair(31), CovarianceTuple(32), VarianceTuple(33), - PinotFourthMoment(34); + PinotFourthMoment(34), + ExprMinMaxObject(35), + KllDataSketch(36), + IntegerTupleSketch(37), + FrequentStringsSketch(38), + FrequentLongsSketch(39), + HyperLogLogPlus(40), + CompressedProbabilisticCounting(41), + IntArrayList(42), + LongArrayList(43), + FloatArrayList(44), + StringArrayList(45), + UltraLogLog(46), + ThetaSketchAccumulator(47), + TupleIntSketchAccumulator(48), + CpcSketchAccumulator(49), + OrderedStringSet(50), + FunnelStepEventAccumulator(51); private final int _value; @@ -148,8 +186,25 @@ public static ObjectType getObjectType(Object value) { return ObjectType.Double; } else if (value instanceof BigDecimal) { return ObjectType.BigDecimal; + } else if (value instanceof IntArrayList) { + return ObjectType.IntArrayList; + } else if (value instanceof LongArrayList) { + return ObjectType.LongArrayList; + } else if (value instanceof FloatArrayList) { + return ObjectType.FloatArrayList; } else if (value instanceof DoubleArrayList) { return ObjectType.DoubleArrayList; + } else if (value instanceof ObjectArrayList) { + ObjectArrayList objectArrayList = (ObjectArrayList) value; + if (!objectArrayList.isEmpty()) { + Object next = objectArrayList.get(0); + if (next instanceof String) { + return ObjectType.StringArrayList; + } + throw new IllegalArgumentException( + "Unsupported type of value: " + next.getClass().getSimpleName()); + } + return ObjectType.StringArrayList; } else if (value instanceof AvgPair) { return ObjectType.AvgPair; } else if (value instanceof MinMaxRangePair) { @@ -176,6 +231,8 @@ public static ObjectType getObjectType(Object value) { return ObjectType.DistinctTable; } else if (value instanceof Sketch) { return ObjectType.DataSketch; + } else if (value instanceof KllDoublesSketch) { + return ObjectType.KllDataSketch; } else if (value instanceof Geometry) { return ObjectType.Geometry; } else if (value instanceof RoaringBitmap) { @@ -186,6 +243,13 @@ public static ObjectType getObjectType(Object value) { return ObjectType.FloatSet; } else if (value instanceof DoubleSet) { return ObjectType.DoubleSet; + } else if (value instanceof ObjectLinkedOpenHashSet) { + ObjectLinkedOpenHashSet objectSet = (ObjectLinkedOpenHashSet) value; + if (objectSet.isEmpty() || objectSet.first() instanceof String) { + return ObjectType.OrderedStringSet; + } + throw new IllegalArgumentException( + "Unsupported type of value: " + objectSet.first().getClass().getSimpleName()); } else if (value instanceof ObjectSet) { ObjectSet objectSet = (ObjectSet) value; if (objectSet.isEmpty() || objectSet.iterator().next() instanceof String) { @@ -213,6 +277,33 @@ public static ObjectType getObjectType(Object value) { return ObjectType.VarianceTuple; } else if (value instanceof PinotFourthMoment) { return ObjectType.PinotFourthMoment; + } else if (value instanceof org.apache.datasketches.tuple.Sketch) { + return ObjectType.IntegerTupleSketch; + } else if (value instanceof ExprMinMaxObject) { + return ObjectType.ExprMinMaxObject; + } else if (value instanceof ItemsSketch) { + return ObjectType.FrequentStringsSketch; + } else if (value instanceof LongsSketch) { + return ObjectType.FrequentLongsSketch; + } else if (value instanceof HyperLogLogPlus) { + return ObjectType.HyperLogLogPlus; + } else if (value instanceof CpcSketch) { + return ObjectType.CompressedProbabilisticCounting; + } else if (value instanceof UltraLogLog) { + return ObjectType.UltraLogLog; + } else if (value instanceof ThetaSketchAccumulator) { + return ObjectType.ThetaSketchAccumulator; + } else if (value instanceof TupleIntSketchAccumulator) { + return ObjectType.TupleIntSketchAccumulator; + } else if (value instanceof CpcSketchAccumulator) { + return ObjectType.CpcSketchAccumulator; + } else if (value instanceof PriorityQueue) { + PriorityQueue priorityQueue = (PriorityQueue) value; + if (priorityQueue.isEmpty() || priorityQueue.peek() instanceof FunnelStepEvent) { + return ObjectType.FunnelStepEventAccumulator; + } + throw new IllegalArgumentException( + "Unsupported type of value: " + priorityQueue.peek().getClass().getSimpleName()); } else { throw new IllegalArgumentException("Unsupported type of value: " + value.getClass().getSimpleName()); } @@ -298,6 +389,99 @@ public Double deserialize(ByteBuffer byteBuffer) { } }; + public static final ObjectSerDe INT_ARRAY_LIST_SER_DE = new ObjectSerDe() { + + @Override + public byte[] serialize(IntArrayList intArrayList) { + int size = intArrayList.size(); + byte[] bytes = new byte[Integer.BYTES + size * Integer.BYTES]; + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.putInt(size); + int[] values = intArrayList.elements(); + for (int i = 0; i < size; i++) { + byteBuffer.putInt(values[i]); + } + return bytes; + } + + @Override + public IntArrayList deserialize(byte[] bytes) { + return deserialize(ByteBuffer.wrap(bytes)); + } + + @Override + public IntArrayList deserialize(ByteBuffer byteBuffer) { + int numValues = byteBuffer.getInt(); + IntArrayList intArrayList = new IntArrayList(numValues); + for (int i = 0; i < numValues; i++) { + intArrayList.add(byteBuffer.getInt()); + } + return intArrayList; + } + }; + + public static final ObjectSerDe LONG_ARRAY_LIST_SER_DE = new ObjectSerDe() { + + @Override + public byte[] serialize(LongArrayList longArrayList) { + int size = longArrayList.size(); + byte[] bytes = new byte[Integer.BYTES + size * Long.BYTES]; + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.putInt(size); + long[] values = longArrayList.elements(); + for (int i = 0; i < size; i++) { + byteBuffer.putLong(values[i]); + } + return bytes; + } + + @Override + public LongArrayList deserialize(byte[] bytes) { + return deserialize(ByteBuffer.wrap(bytes)); + } + + @Override + public LongArrayList deserialize(ByteBuffer byteBuffer) { + int numValues = byteBuffer.getInt(); + LongArrayList longArrayList = new LongArrayList(numValues); + for (int i = 0; i < numValues; i++) { + longArrayList.add(byteBuffer.getLong()); + } + return longArrayList; + } + }; + + public static final ObjectSerDe FLOAT_ARRAY_LIST_SER_DE = new ObjectSerDe() { + + @Override + public byte[] serialize(FloatArrayList floatArrayList) { + int size = floatArrayList.size(); + byte[] bytes = new byte[Integer.BYTES + size * Float.BYTES]; + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.putInt(size); + float[] values = floatArrayList.elements(); + for (int i = 0; i < size; i++) { + byteBuffer.putFloat(values[i]); + } + return bytes; + } + + @Override + public FloatArrayList deserialize(byte[] bytes) { + return deserialize(ByteBuffer.wrap(bytes)); + } + + @Override + public FloatArrayList deserialize(ByteBuffer byteBuffer) { + int numValues = byteBuffer.getInt(); + FloatArrayList floatArrayList = new FloatArrayList(numValues); + for (int i = 0; i < numValues; i++) { + floatArrayList.add(byteBuffer.getFloat()); + } + return floatArrayList; + } + }; + public static final ObjectSerDe DOUBLE_ARRAY_LIST_SER_DE = new ObjectSerDe() { @Override @@ -329,6 +513,50 @@ public DoubleArrayList deserialize(ByteBuffer byteBuffer) { } }; + public static final ObjectSerDe STRING_ARRAY_LIST_SER_DE = + new ObjectSerDe() { + @Override + public byte[] serialize(ObjectArrayList stringArrayList) { + int size = stringArrayList.size(); + // Besides the value bytes, we store: size, length for each value + long bufferSize = (1 + (long) size) * Integer.BYTES; + byte[][] valueBytesArray = new byte[size][]; + for (int index = 0; index < size; index++) { + Object value = stringArrayList.get(index); + byte[] valueBytes = value.toString().getBytes(UTF_8); + bufferSize += valueBytes.length; + valueBytesArray[index] = valueBytes; + } + Preconditions.checkState(bufferSize <= Integer.MAX_VALUE, "Buffer size exceeds 2GB"); + byte[] bytes = new byte[(int) bufferSize]; + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.putInt(size); + for (byte[] valueBytes : valueBytesArray) { + byteBuffer.putInt(valueBytes.length); + byteBuffer.put(valueBytes); + } + return bytes; + } + + @Override + public ObjectArrayList deserialize(byte[] bytes) { + return deserialize(ByteBuffer.wrap(bytes)); + } + + @Override + public ObjectArrayList deserialize(ByteBuffer byteBuffer) { + int size = byteBuffer.getInt(); + ObjectArrayList stringArrayList = new ObjectArrayList(size); + for (int i = 0; i < size; i++) { + int length = byteBuffer.getInt(); + byte[] valueBytes = new byte[length]; + byteBuffer.get(valueBytes); + stringArrayList.add(new String(valueBytes, UTF_8)); + } + return stringArrayList; + } + }; + public static final ObjectSerDe AVG_PAIR_SER_DE = new ObjectSerDe() { @Override @@ -541,6 +769,34 @@ private HyperLogLog deserialize(IntBuffer intBuffer) { } }; + public static final ObjectSerDe HYPER_LOG_LOG_PLUS_SER_DE = new ObjectSerDe() { + + @Override + public byte[] serialize(HyperLogLogPlus hyperLogLogPlus) { + try { + return hyperLogLogPlus.getBytes(); + } catch (IOException e) { + throw new RuntimeException("Caught exception while serializing HyperLogLogPlus", e); + } + } + + @Override + public HyperLogLogPlus deserialize(byte[] bytes) { + try { + return HyperLogLogPlus.Builder.build(bytes); + } catch (IOException e) { + throw new RuntimeException("Caught exception while serializing HyperLogLogPlus", e); + } + } + + @Override + public HyperLogLogPlus deserialize(ByteBuffer byteBuffer) { + byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.get(bytes); + return deserialize(bytes); + } + }; + public static final ObjectSerDe DISTINCT_TABLE_SER_DE = new ObjectSerDe() { @Override @@ -643,7 +899,7 @@ public HashMap deserialize(byte[] bytes) { @Override public HashMap deserialize(ByteBuffer byteBuffer) { int size = byteBuffer.getInt(); - HashMap map = new HashMap<>(size); + HashMap map = new HashMap<>(HashUtil.getHashMapCapacity(size)); if (size == 0) { return map; } @@ -896,13 +1152,16 @@ public TDigest deserialize(ByteBuffer byteBuffer) { } }; - public static final ObjectSerDe DATA_SKETCH_SER_DE = new ObjectSerDe() { + public static final ObjectSerDe DATA_SKETCH_THETA_SER_DE = new ObjectSerDe() { @Override public byte[] serialize(Sketch value) { - // NOTE: Compact the sketch in unsorted, on-heap fashion for performance concern. - // See https://datasketches.apache.org/docs/Theta/ThetaSize.html for more details. - return value.compact(false, null).toByteArray(); + // The serializer should respect existing ordering to enable "early stop" + // optimisations on unions. + if (!value.isCompact()) { + return value.compact(value.isOrdered(), null).toByteArray(); + } + return value.toByteArray(); } @Override @@ -918,6 +1177,67 @@ public Sketch deserialize(ByteBuffer byteBuffer) { } }; + public static final ObjectSerDe> DATA_SKETCH_INT_TUPLE_SER_DE = + new ObjectSerDe>() { + @Override + public byte[] serialize(org.apache.datasketches.tuple.Sketch value) { + return value.compact().toByteArray(); + } + + @Override + public org.apache.datasketches.tuple.Sketch deserialize(byte[] bytes) { + return org.apache.datasketches.tuple.Sketches.heapifySketch(Memory.wrap(bytes), + new IntegerSummaryDeserializer()); + } + + @Override + public org.apache.datasketches.tuple.Sketch deserialize(ByteBuffer byteBuffer) { + byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.get(bytes); + return org.apache.datasketches.tuple.Sketches.heapifySketch(Memory.wrap(bytes), + new IntegerSummaryDeserializer()); + } + }; + + public static final ObjectSerDe KLL_SKETCH_SER_DE = new ObjectSerDe() { + + @Override + public byte[] serialize(KllDoublesSketch value) { + return value.toByteArray(); + } + + @Override + public KllDoublesSketch deserialize(byte[] bytes) { + return KllDoublesSketch.wrap(Memory.wrap(bytes)); + } + + @Override + public KllDoublesSketch deserialize(ByteBuffer byteBuffer) { + byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.get(bytes); + return KllDoublesSketch.wrap(Memory.wrap(bytes)); + } + }; + + public static final ObjectSerDe DATA_SKETCH_CPC_SER_DE = new ObjectSerDe() { + @Override + public byte[] serialize(CpcSketch value) { + return value.toByteArray(); + } + + @Override + public CpcSketch deserialize(byte[] bytes) { + return CpcSketch.heapify(Memory.wrap(bytes)); + } + + @Override + public CpcSketch deserialize(ByteBuffer byteBuffer) { + byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.get(bytes); + return CpcSketch.heapify(Memory.wrap(bytes)); + } + }; + public static final ObjectSerDe GEOMETRY_SER_DE = new ObjectSerDe() { @Override @@ -1199,6 +1519,267 @@ public Double2LongOpenHashMap deserialize(ByteBuffer byteBuffer) { } }; + public static final ObjectSerDe ARG_MIN_MAX_OBJECT_SER_DE = + new ObjectSerDe() { + + @Override + public byte[] serialize(ExprMinMaxObject value) { + try { + return value.toBytes(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public ExprMinMaxObject deserialize(byte[] bytes) { + try { + return ExprMinMaxObject.fromBytes(bytes); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public ExprMinMaxObject deserialize(ByteBuffer byteBuffer) { + try { + return ExprMinMaxObject.fromByteBuffer(byteBuffer); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + }; + + public static final ObjectSerDe> FREQUENT_STRINGS_SKETCH_SER_DE = + new ObjectSerDe<>() { + @Override + public byte[] serialize(ItemsSketch sketch) { + return sketch.toByteArray(new ArrayOfStringsSerDe()); + } + + @Override + public ItemsSketch deserialize(byte[] bytes) { + return ItemsSketch.getInstance(Memory.wrap(bytes), new ArrayOfStringsSerDe()); + } + + @Override + public ItemsSketch deserialize(ByteBuffer byteBuffer) { + byte[] arr = new byte[byteBuffer.remaining()]; + byteBuffer.get(arr); + return ItemsSketch.getInstance(Memory.wrap(arr), new ArrayOfStringsSerDe()); + } + }; + + public static final ObjectSerDe FREQUENT_LONGS_SKETCH_SER_DE = + new ObjectSerDe<>() { + @Override + public byte[] serialize(LongsSketch sketch) { + return sketch.toByteArray(); + } + + @Override + public LongsSketch deserialize(byte[] bytes) { + return LongsSketch.getInstance(Memory.wrap(bytes)); + } + + @Override + public LongsSketch deserialize(ByteBuffer byteBuffer) { + byte[] arr = new byte[byteBuffer.remaining()]; + byteBuffer.get(arr); + return LongsSketch.getInstance(Memory.wrap(arr)); + } + }; + + public static final ObjectSerDe ULTRA_LOG_LOG_OBJECT_SER_DE = new ObjectSerDe() { + + @Override + public byte[] serialize(UltraLogLog value) { + ByteBuffer buff = ByteBuffer.wrap(new byte[(1 << value.getP()) + 1]); + buff.put((byte) value.getP()); + buff.put(value.getState()); + return buff.array(); + } + + @Override + public UltraLogLog deserialize(byte[] bytes) { + return deserialize(ByteBuffer.wrap(bytes)); + } + + @Override + public UltraLogLog deserialize(ByteBuffer byteBuffer) { + byte p = byteBuffer.get(); + byte[] state = new byte[1 << p]; + byteBuffer.get(state); + return UltraLogLog.wrap(state); + } + }; + + public static final ObjectSerDe DATA_SKETCH_THETA_ACCUMULATOR_SER_DE = + new ObjectSerDe() { + + @Override + public byte[] serialize(ThetaSketchAccumulator thetaSketchBuffer) { + Sketch sketch = thetaSketchBuffer.getResult(); + return sketch.toByteArray(); + } + + @Override + public ThetaSketchAccumulator deserialize(byte[] bytes) { + return deserialize(ByteBuffer.wrap(bytes)); + } + + // Note: The accumulator is designed to serialize as a sketch and should + // not be deserialized in practice. + @Override + public ThetaSketchAccumulator deserialize(ByteBuffer byteBuffer) { + ThetaSketchAccumulator thetaSketchAccumulator = new ThetaSketchAccumulator(); + byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.get(bytes); + Sketch sketch = Sketch.wrap(Memory.wrap(bytes)); + thetaSketchAccumulator.apply(sketch); + return thetaSketchAccumulator; + } + }; + + public static final ObjectSerDe DATA_SKETCH_INT_TUPLE_ACCUMULATOR_SER_DE = + new ObjectSerDe() { + + @Override + public byte[] serialize(TupleIntSketchAccumulator tupleIntSketchBuffer) { + org.apache.datasketches.tuple.Sketch sketch = tupleIntSketchBuffer.getResult(); + return sketch.toByteArray(); + } + + @Override + public TupleIntSketchAccumulator deserialize(byte[] bytes) { + return deserialize(ByteBuffer.wrap(bytes)); + } + + // Note: The accumulator is designed to serialize as a sketch and should + // not be deserialized in practice. + @Override + public TupleIntSketchAccumulator deserialize(ByteBuffer byteBuffer) { + TupleIntSketchAccumulator tupleIntSketchAccumulator = new TupleIntSketchAccumulator(); + byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.get(bytes); + org.apache.datasketches.tuple.Sketch sketch = + org.apache.datasketches.tuple.Sketches.heapifySketch(Memory.wrap(bytes), + new IntegerSummaryDeserializer()); + tupleIntSketchAccumulator.apply(sketch); + return tupleIntSketchAccumulator; + } + }; + + public static final ObjectSerDe DATA_SKETCH_CPC_ACCUMULATOR_SER_DE = + new ObjectSerDe() { + + @Override + public byte[] serialize(CpcSketchAccumulator cpcSketchBuffer) { + CpcSketch sketch = cpcSketchBuffer.getResult(); + return sketch.toByteArray(); + } + + @Override + public CpcSketchAccumulator deserialize(byte[] bytes) { + return deserialize(ByteBuffer.wrap(bytes)); + } + + // Note: The accumulator is designed to serialize as a sketch and should + // not be deserialized in practice. + @Override + public CpcSketchAccumulator deserialize(ByteBuffer byteBuffer) { + CpcSketchAccumulator cpcSketchAccumulator = new CpcSketchAccumulator(); + byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.get(bytes); + CpcSketch sketch = CpcSketch.heapify(Memory.wrap(bytes)); + cpcSketchAccumulator.apply(sketch); + return cpcSketchAccumulator; + } + }; + + public static final ObjectSerDe> ORDERED_STRING_SET_SER_DE = + new ObjectSerDe>() { + + @Override + public byte[] serialize(ObjectLinkedOpenHashSet stringSet) { + int size = stringSet.size(); + // Besides the value bytes, we store: size, length for each value + long bufferSize = (1 + (long) size) * Integer.BYTES; + byte[][] valueBytesArray = new byte[size][]; + int index = 0; + for (String value : stringSet) { + byte[] valueBytes = value.getBytes(UTF_8); + bufferSize += valueBytes.length; + valueBytesArray[index++] = valueBytes; + } + Preconditions.checkState(bufferSize <= Integer.MAX_VALUE, "Buffer size exceeds 2GB"); + byte[] bytes = new byte[(int) bufferSize]; + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.putInt(size); + for (byte[] valueBytes : valueBytesArray) { + byteBuffer.putInt(valueBytes.length); + byteBuffer.put(valueBytes); + } + return bytes; + } + + @Override + public ObjectLinkedOpenHashSet deserialize(byte[] bytes) { + return deserialize(ByteBuffer.wrap(bytes)); + } + + @Override + public ObjectLinkedOpenHashSet deserialize(ByteBuffer byteBuffer) { + int size = byteBuffer.getInt(); + ObjectLinkedOpenHashSet stringSet = new ObjectLinkedOpenHashSet<>(size); + for (int i = 0; i < size; i++) { + int length = byteBuffer.getInt(); + byte[] bytes = new byte[length]; + byteBuffer.get(bytes); + stringSet.add(new String(bytes, UTF_8)); + } + return stringSet; + } + }; + + public static final ObjectSerDe> FUNNEL_STEP_EVENT_ACCUMULATOR_SER_DE = + new ObjectSerDe>() { + + @Override + public byte[] serialize(PriorityQueue funnelStepEvents) { + int numEvents = funnelStepEvents.size(); + long bufferSize = Integer.BYTES + (long) numEvents * FunnelStepEvent.SIZE_IN_BYTES; + Preconditions.checkState(bufferSize <= Integer.MAX_VALUE, "Buffer size exceeds 2GB"); + byte[] bytes = new byte[(int) bufferSize]; + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + byteBuffer.putInt(numEvents); + for (FunnelStepEvent funnelStepEvent : funnelStepEvents) { + byteBuffer.put(funnelStepEvent.getBytes()); + } + return bytes; + } + + @Override + public PriorityQueue deserialize(byte[] bytes) { + return deserialize(ByteBuffer.wrap(bytes)); + } + + @Override + public PriorityQueue deserialize(ByteBuffer byteBuffer) { + int size = byteBuffer.getInt(); + if (size == 0) { + return new PriorityQueue<>(); + } + PriorityQueue funnelStepEvents = new PriorityQueue<>(size); + byte[] bytes = new byte[FunnelStepEvent.SIZE_IN_BYTES]; + for (int i = 0; i < size; i++) { + byteBuffer.get(bytes); + funnelStepEvents.add(new FunnelStepEvent(bytes)); + } + return funnelStepEvents; + } + }; + // NOTE: DO NOT change the order, it has to be the same order as the ObjectType //@formatter:off private static final ObjectSerDe[] SER_DES = { @@ -1214,7 +1795,7 @@ public Double2LongOpenHashMap deserialize(ByteBuffer byteBuffer) { INT_SET_SER_DE, TDIGEST_SER_DE, DISTINCT_TABLE_SER_DE, - DATA_SKETCH_SER_DE, + DATA_SKETCH_THETA_SER_DE, GEOMETRY_SER_DE, ROARING_BITMAP_SER_DE, LONG_SET_SER_DE, @@ -1236,7 +1817,24 @@ public Double2LongOpenHashMap deserialize(ByteBuffer byteBuffer) { STRING_LONG_PAIR_SER_DE, COVARIANCE_TUPLE_OBJECT_SER_DE, VARIANCE_TUPLE_OBJECT_SER_DE, - PINOT_FOURTH_MOMENT_OBJECT_SER_DE + PINOT_FOURTH_MOMENT_OBJECT_SER_DE, + ARG_MIN_MAX_OBJECT_SER_DE, + KLL_SKETCH_SER_DE, + DATA_SKETCH_INT_TUPLE_SER_DE, + FREQUENT_STRINGS_SKETCH_SER_DE, + FREQUENT_LONGS_SKETCH_SER_DE, + HYPER_LOG_LOG_PLUS_SER_DE, + DATA_SKETCH_CPC_SER_DE, + INT_ARRAY_LIST_SER_DE, + LONG_ARRAY_LIST_SER_DE, + FLOAT_ARRAY_LIST_SER_DE, + STRING_ARRAY_LIST_SER_DE, + ULTRA_LOG_LOG_OBJECT_SER_DE, + DATA_SKETCH_THETA_ACCUMULATOR_SER_DE, + DATA_SKETCH_INT_TUPLE_ACCUMULATOR_SER_DE, + DATA_SKETCH_CPC_ACCUMULATOR_SER_DE, + ORDERED_STRING_SET_SER_DE, + FUNNEL_STEP_EVENT_ACCUMULATOR_SER_DE, }; //@formatter:on @@ -1244,7 +1842,7 @@ public static byte[] serialize(Object value, int objectTypeValue) { return SER_DES[objectTypeValue].serialize(value); } - public static T deserialize(DataTable.CustomObject customObject) { + public static T deserialize(CustomObject customObject) { return (T) SER_DES[customObject.getType()].deserialize(customObject.getBuffer()); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/common/Operator.java b/pinot-core/src/main/java/org/apache/pinot/core/common/Operator.java index 5926805c6ab6..760074538b4e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/common/Operator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/common/Operator.java @@ -42,17 +42,41 @@ public interface Operator { T nextBlock(); /** @return List of {@link Operator}s that this operator depends upon. */ - List getChildOperators(); + List getChildOperators(); /** @return Explain Plan description if available; otherwise, null. */ @Nullable String toExplainString(); + default void prepareForExplainPlan(ExplainPlanRows explainPlanRows) { + } + + default void postExplainPlan(ExplainPlanRows explainPlanRows) { + } + + default void explainPlan(ExplainPlanRows explainPlanRows, int[] globalId, int parentId) { + prepareForExplainPlan(explainPlanRows); + String explainPlanString = toExplainString(); + if (explainPlanString != null) { + ExplainPlanRowData explainPlanRowData = new ExplainPlanRowData(explainPlanString, globalId[0], parentId); + parentId = globalId[0]++; + explainPlanRows.appendExplainPlanRowData(explainPlanRowData); + } + + List children = getChildOperators(); + for (Operator child : children) { + if (child != null) { + child.explainPlan(explainPlanRows, globalId, parentId); + } + } + postExplainPlan(explainPlanRows); + } + /** * Returns the index segment associated with the operator. */ default IndexSegment getIndexSegment() { - throw new UnsupportedOperationException(); + return null; } /** diff --git a/pinot-core/src/main/java/org/apache/pinot/core/common/RowBasedBlockValueFetcher.java b/pinot-core/src/main/java/org/apache/pinot/core/common/RowBasedBlockValueFetcher.java index 64a3f6a788d3..21c6d34f0490 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/common/RowBasedBlockValueFetcher.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/common/RowBasedBlockValueFetcher.java @@ -67,6 +67,8 @@ private ValueFetcher createFetcher(BlockValSet blockValSet) { return new StringSingleValueFetcher(blockValSet.getStringValuesSV()); case BYTES: return new BytesValueFetcher(blockValSet.getBytesValuesSV()); + case UNKNOWN: + return new UnknownValueFetcher(); default: throw new IllegalStateException("Unsupported value type: " + storedType + " for single-value column"); } @@ -235,4 +237,13 @@ public String[] getValue(int docId) { return _values[docId]; } } + + private static class UnknownValueFetcher implements ValueFetcher { + UnknownValueFetcher() { + } + + public Object getValue(int docId) { + return null; + } + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/common/datablock/DataBlockBuilder.java b/pinot-core/src/main/java/org/apache/pinot/core/common/datablock/DataBlockBuilder.java index 19ed9cd05af6..98e2f32c4a99 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/common/datablock/DataBlockBuilder.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/common/datablock/DataBlockBuilder.java @@ -20,23 +20,22 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap; import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.math.BigDecimal; import java.nio.ByteBuffer; -import java.sql.Timestamp; import java.util.List; import javax.annotation.Nullable; +import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream; +import org.apache.pinot.common.CustomObject; import org.apache.pinot.common.datablock.ColumnarDataBlock; import org.apache.pinot.common.datablock.DataBlock; import org.apache.pinot.common.datablock.DataBlockUtils; import org.apache.pinot.common.datablock.RowDataBlock; -import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.common.utils.RoaringBitmapUtils; import org.apache.pinot.core.common.ObjectSerDeUtils; -import org.apache.pinot.spi.utils.ArrayCopyUtils; import org.apache.pinot.spi.utils.BigDecimalUtils; import org.apache.pinot.spi.utils.ByteArray; import org.roaringbitmap.RoaringBitmap; @@ -45,40 +44,49 @@ public class DataBlockBuilder { private final DataSchema _dataSchema; private final DataBlock.Type _blockType; - private final DataSchema.ColumnDataType[] _columnDataTypes; + private final int _numRows; + private final int _numColumns; private int[] _columnOffsets; private int _rowSizeInBytes; private int[] _cumulativeColumnOffsetSizeInBytes; private int[] _columnSizeInBytes; - private int _numRows; - private int _numColumns; - private final Object2IntOpenHashMap _dictionary = new Object2IntOpenHashMap<>(); - private final ByteArrayOutputStream _fixedSizeDataByteArrayOutputStream = new ByteArrayOutputStream(); - private final DataOutputStream _fixedSizeDataOutputStream = new DataOutputStream(_fixedSizeDataByteArrayOutputStream); - private final ByteArrayOutputStream _variableSizeDataByteArrayOutputStream = new ByteArrayOutputStream(); + private final UnsynchronizedByteArrayOutputStream _fixedSizeDataByteArrayOutputStream; + private final DataOutputStream _fixedSizeDataOutputStream; + private final UnsynchronizedByteArrayOutputStream _variableSizeDataByteArrayOutputStream = + new UnsynchronizedByteArrayOutputStream(8192); private final DataOutputStream _variableSizeDataOutputStream = new DataOutputStream(_variableSizeDataByteArrayOutputStream); - private DataBlockBuilder(DataSchema dataSchema, DataBlock.Type blockType) { + private DataBlockBuilder(DataSchema dataSchema, DataBlock.Type blockType, int numRows) { _dataSchema = dataSchema; - _columnDataTypes = dataSchema.getColumnDataTypes(); _blockType = blockType; + _numRows = numRows; _numColumns = dataSchema.size(); - if (_blockType == DataBlock.Type.COLUMNAR) { - _cumulativeColumnOffsetSizeInBytes = new int[_numColumns]; - _columnSizeInBytes = new int[_numColumns]; - DataBlockUtils.computeColumnSizeInBytes(_dataSchema, _columnSizeInBytes); - int cumulativeColumnOffset = 0; - for (int i = 0; i < _numColumns; i++) { - _cumulativeColumnOffsetSizeInBytes[i] = cumulativeColumnOffset; - cumulativeColumnOffset += _columnSizeInBytes[i] * _numRows; - } - } else if (_blockType == DataBlock.Type.ROW) { + if (_blockType == DataBlock.Type.ROW) { _columnOffsets = new int[_numColumns]; _rowSizeInBytes = DataBlockUtils.computeColumnOffsets(dataSchema, _columnOffsets); + + int nullBytes = _numColumns * 8; // we need 2 ints per column to store the roaring bitmaps offsets + int expectedFixedSizeStreamSize = (_rowSizeInBytes + nullBytes) * numRows; + _fixedSizeDataByteArrayOutputStream = new UnsynchronizedByteArrayOutputStream(expectedFixedSizeStreamSize); + _fixedSizeDataOutputStream = new DataOutputStream(_fixedSizeDataByteArrayOutputStream); + } else { + _fixedSizeDataByteArrayOutputStream = new UnsynchronizedByteArrayOutputStream(8192); + _fixedSizeDataOutputStream = new DataOutputStream(_fixedSizeDataByteArrayOutputStream); + + if (_blockType == DataBlock.Type.COLUMNAR) { + _cumulativeColumnOffsetSizeInBytes = new int[_numColumns]; + _columnSizeInBytes = new int[_numColumns]; + DataBlockUtils.computeColumnSizeInBytes(_dataSchema, _columnSizeInBytes); + int cumulativeColumnOffset = 0; + for (int i = 0; i < _numColumns; i++) { + _cumulativeColumnOffsetSizeInBytes[i] = cumulativeColumnOffset; + cumulativeColumnOffset += _columnSizeInBytes[i] * _numRows; + } + } } } @@ -96,131 +104,87 @@ public void setNullRowIds(@Nullable RoaringBitmap nullRowIds) public static RowDataBlock buildFromRows(List rows, DataSchema dataSchema) throws IOException { - DataBlockBuilder rowBuilder = new DataBlockBuilder(dataSchema, DataBlock.Type.ROW); + int numRows = rows.size(); + DataBlockBuilder rowBuilder = new DataBlockBuilder(dataSchema, DataBlock.Type.ROW, numRows); // TODO: consolidate these null utils into data table utils. // Selection / Agg / Distinct all have similar code. - int numColumns = rowBuilder._numColumns; + ColumnDataType[] storedTypes = dataSchema.getStoredColumnDataTypes(); + int numColumns = storedTypes.length; RoaringBitmap[] nullBitmaps = new RoaringBitmap[numColumns]; - DataSchema.ColumnDataType[] columnDataTypes = dataSchema.getColumnDataTypes(); - DataSchema.ColumnDataType[] storedColumnDataTypes = dataSchema.getStoredColumnDataTypes(); Object[] nullPlaceholders = new Object[numColumns]; for (int colId = 0; colId < numColumns; colId++) { nullBitmaps[colId] = new RoaringBitmap(); - nullPlaceholders[colId] = columnDataTypes[colId].convert(storedColumnDataTypes[colId].getNullPlaceholder()); + nullPlaceholders[colId] = storedTypes[colId].getNullPlaceholder(); } - rowBuilder._numRows = rows.size(); - for (int rowId = 0; rowId < rows.size(); rowId++) { + ByteBuffer byteBuffer = ByteBuffer.allocate(rowBuilder._rowSizeInBytes); + for (int rowId = 0; rowId < numRows; rowId++) { + byteBuffer.clear(); Object[] row = rows.get(rowId); - ByteBuffer byteBuffer = ByteBuffer.allocate(rowBuilder._rowSizeInBytes); - for (int colId = 0; colId < rowBuilder._numColumns; colId++) { + for (int colId = 0; colId < numColumns; colId++) { Object value = row[colId]; if (value == null) { nullBitmaps[colId].add(rowId); value = nullPlaceholders[colId]; } - switch (rowBuilder._columnDataTypes[colId]) { + + // NOTE: + // We intentionally make the type casting very strict here (e.g. only accepting Integer for INT) to ensure the + // rows conform to the data schema. This can help catch the unexpected data type issues early. + switch (storedTypes[colId]) { // Single-value column case INT: - byteBuffer.putInt(((Number) value).intValue()); + byteBuffer.putInt((int) value); break; case LONG: - byteBuffer.putLong(((Number) value).longValue()); + byteBuffer.putLong((long) value); break; case FLOAT: - byteBuffer.putFloat(((Number) value).floatValue()); + byteBuffer.putFloat((float) value); break; case DOUBLE: - byteBuffer.putDouble(((Number) value).doubleValue()); + byteBuffer.putDouble((double) value); break; case BIG_DECIMAL: setColumn(rowBuilder, byteBuffer, (BigDecimal) value); break; - case BOOLEAN: - byteBuffer.putInt(((Boolean) value) ? 1 : 0); - break; - case TIMESTAMP: - byteBuffer.putLong(((Timestamp) value).getTime()); - break; case STRING: setColumn(rowBuilder, byteBuffer, (String) value); break; case BYTES: - if (value instanceof byte[]) { - setColumn(rowBuilder, byteBuffer, new ByteArray((byte[]) value)); - } else { - setColumn(rowBuilder, byteBuffer, (ByteArray) value); - } - break; - case OBJECT: - setColumn(rowBuilder, byteBuffer, value); + setColumn(rowBuilder, byteBuffer, (ByteArray) value); break; + // Multi-value column case INT_ARRAY: setColumn(rowBuilder, byteBuffer, (int[]) value); break; case LONG_ARRAY: - // LONG_ARRAY type covers INT_ARRAY and LONG_ARRAY - if (value instanceof int[]) { - int[] ints = (int[]) value; - int length = ints.length; - long[] longs = new long[length]; - ArrayCopyUtils.copy(ints, longs, length); - setColumn(rowBuilder, byteBuffer, longs); - } else { - setColumn(rowBuilder, byteBuffer, (long[]) value); - } + setColumn(rowBuilder, byteBuffer, (long[]) value); break; case FLOAT_ARRAY: setColumn(rowBuilder, byteBuffer, (float[]) value); break; case DOUBLE_ARRAY: - // DOUBLE_ARRAY type covers INT_ARRAY, LONG_ARRAY, FLOAT_ARRAY and DOUBLE_ARRAY - if (value instanceof int[]) { - int[] ints = (int[]) value; - int length = ints.length; - double[] doubles = new double[length]; - ArrayCopyUtils.copy(ints, doubles, length); - setColumn(rowBuilder, byteBuffer, doubles); - } else if (value instanceof long[]) { - long[] longs = (long[]) value; - int length = longs.length; - double[] doubles = new double[length]; - ArrayCopyUtils.copy(longs, doubles, length); - setColumn(rowBuilder, byteBuffer, doubles); - } else if (value instanceof float[]) { - float[] floats = (float[]) value; - int length = floats.length; - double[] doubles = new double[length]; - ArrayCopyUtils.copy(floats, doubles, length); - setColumn(rowBuilder, byteBuffer, doubles); - } else { - setColumn(rowBuilder, byteBuffer, (double[]) value); - } - break; - case BYTES_ARRAY: - setColumn(rowBuilder, byteBuffer, (byte[][]) value); + setColumn(rowBuilder, byteBuffer, (double[]) value); break; case STRING_ARRAY: setColumn(rowBuilder, byteBuffer, (String[]) value); break; - case BOOLEAN_ARRAY: - boolean[] booleans = (boolean[]) value; - int length = booleans.length; - int[] ints = new int[length]; - ArrayCopyUtils.copy(booleans, ints, length); - setColumn(rowBuilder, byteBuffer, ints); + + // Special intermediate result for aggregation function + case OBJECT: + setColumn(rowBuilder, byteBuffer, value); break; - case TIMESTAMP_ARRAY: - Timestamp[] timestamps = (Timestamp[]) value; - length = timestamps.length; - long[] longs = new long[length]; - ArrayCopyUtils.copy(timestamps, longs, length); - setColumn(rowBuilder, byteBuffer, longs); + + // Null + case UNKNOWN: + setColumn(rowBuilder, byteBuffer, (Object) null); break; + default: throw new IllegalStateException( - String.format("Unsupported data type: %s for column: %s", rowBuilder._columnDataTypes[colId], - rowBuilder._dataSchema.getColumnName(colId))); + String.format("Unsupported stored type: %s for column: %s", storedTypes[colId], + dataSchema.getColumnName(colId))); } } rowBuilder._fixedSizeDataByteArrayOutputStream.write(byteBuffer.array(), 0, byteBuffer.position()); @@ -234,68 +198,70 @@ public static RowDataBlock buildFromRows(List rows, DataSchema dataSch public static ColumnarDataBlock buildFromColumns(List columns, DataSchema dataSchema) throws IOException { - DataBlockBuilder columnarBuilder = new DataBlockBuilder(dataSchema, DataBlock.Type.COLUMNAR); - + int numRows = columns.isEmpty() ? 0 : columns.get(0).length; + DataBlockBuilder columnarBuilder = new DataBlockBuilder(dataSchema, DataBlock.Type.COLUMNAR, numRows); // TODO: consolidate these null utils into data table utils. // Selection / Agg / Distinct all have similar code. - int numColumns = columnarBuilder._numColumns; + ColumnDataType[] storedTypes = dataSchema.getStoredColumnDataTypes(); + int numColumns = storedTypes.length; RoaringBitmap[] nullBitmaps = new RoaringBitmap[numColumns]; - DataSchema.ColumnDataType[] columnDataTypes = dataSchema.getColumnDataTypes(); - DataSchema.ColumnDataType[] storedColumnDataTypes = dataSchema.getStoredColumnDataTypes(); Object[] nullPlaceholders = new Object[numColumns]; for (int colId = 0; colId < numColumns; colId++) { nullBitmaps[colId] = new RoaringBitmap(); - nullPlaceholders[colId] = columnDataTypes[colId].convert(storedColumnDataTypes[colId].getNullPlaceholder()); + nullPlaceholders[colId] = storedTypes[colId].getNullPlaceholder(); } - for (int colId = 0; colId < columns.size(); colId++) { + for (int colId = 0; colId < numColumns; colId++) { Object[] column = columns.get(colId); - columnarBuilder._numRows = column.length; - ByteBuffer byteBuffer = ByteBuffer.allocate(columnarBuilder._numRows * columnarBuilder._columnSizeInBytes[colId]); + ByteBuffer byteBuffer = ByteBuffer.allocate(numRows * columnarBuilder._columnSizeInBytes[colId]); Object value; - switch (columnarBuilder._columnDataTypes[colId]) { + + // NOTE: + // We intentionally make the type casting very strict here (e.g. only accepting Integer for INT) to ensure the + // rows conform to the data schema. This can help catch the unexpected data type issues early. + switch (storedTypes[colId]) { // Single-value column case INT: - for (int rowId = 0; rowId < columnarBuilder._numRows; rowId++) { + for (int rowId = 0; rowId < numRows; rowId++) { value = column[rowId]; if (value == null) { nullBitmaps[colId].add(rowId); value = nullPlaceholders[colId]; } - byteBuffer.putInt(((Number) value).intValue()); + byteBuffer.putInt((int) value); } break; case LONG: - for (int rowId = 0; rowId < columnarBuilder._numRows; rowId++) { + for (int rowId = 0; rowId < numRows; rowId++) { value = column[rowId]; if (value == null) { nullBitmaps[colId].add(rowId); value = nullPlaceholders[colId]; } - byteBuffer.putLong(((Number) value).longValue()); + byteBuffer.putLong((long) value); } break; case FLOAT: - for (int rowId = 0; rowId < columnarBuilder._numRows; rowId++) { + for (int rowId = 0; rowId < numRows; rowId++) { value = column[rowId]; if (value == null) { nullBitmaps[colId].add(rowId); value = nullPlaceholders[colId]; } - byteBuffer.putFloat(((Number) value).floatValue()); + byteBuffer.putFloat((float) value); } break; case DOUBLE: - for (int rowId = 0; rowId < columnarBuilder._numRows; rowId++) { + for (int rowId = 0; rowId < numRows; rowId++) { value = column[rowId]; if (value == null) { nullBitmaps[colId].add(rowId); value = nullPlaceholders[colId]; } - byteBuffer.putDouble(((Number) value).doubleValue()); + byteBuffer.putDouble((double) value); } break; case BIG_DECIMAL: - for (int rowId = 0; rowId < columnarBuilder._numRows; rowId++) { + for (int rowId = 0; rowId < numRows; rowId++) { value = column[rowId]; if (value == null) { nullBitmaps[colId].add(rowId); @@ -304,28 +270,8 @@ public static ColumnarDataBlock buildFromColumns(List columns, DataSch setColumn(columnarBuilder, byteBuffer, (BigDecimal) value); } break; - case BOOLEAN: - for (int rowId = 0; rowId < columnarBuilder._numRows; rowId++) { - value = column[rowId]; - if (value == null) { - nullBitmaps[colId].add(rowId); - value = nullPlaceholders[colId]; - } - byteBuffer.putInt(((Boolean) value) ? 1 : 0); - } - break; - case TIMESTAMP: - for (int rowId = 0; rowId < columnarBuilder._numRows; rowId++) { - value = column[rowId]; - if (value == null) { - nullBitmaps[colId].add(rowId); - value = nullPlaceholders[colId]; - } - byteBuffer.putLong(((Timestamp) value).getTime()); - } - break; case STRING: - for (int rowId = 0; rowId < columnarBuilder._numRows; rowId++) { + for (int rowId = 0; rowId < numRows; rowId++) { value = column[rowId]; if (value == null) { nullBitmaps[colId].add(rowId); @@ -335,7 +281,7 @@ public static ColumnarDataBlock buildFromColumns(List columns, DataSch } break; case BYTES: - for (int rowId = 0; rowId < columnarBuilder._numRows; rowId++) { + for (int rowId = 0; rowId < numRows; rowId++) { value = column[rowId]; if (value == null) { nullBitmaps[colId].add(rowId); @@ -344,19 +290,10 @@ public static ColumnarDataBlock buildFromColumns(List columns, DataSch setColumn(columnarBuilder, byteBuffer, (ByteArray) value); } break; - case OBJECT: - for (int rowId = 0; rowId < columnarBuilder._numRows; rowId++) { - value = column[rowId]; - if (value == null) { - nullBitmaps[colId].add(rowId); - value = nullPlaceholders[colId]; - } - setColumn(columnarBuilder, byteBuffer, value); - } - break; + // Multi-value column case INT_ARRAY: - for (int rowId = 0; rowId < columnarBuilder._numRows; rowId++) { + for (int rowId = 0; rowId < numRows; rowId++) { value = column[rowId]; if (value == null) { nullBitmaps[colId].add(rowId); @@ -366,26 +303,17 @@ public static ColumnarDataBlock buildFromColumns(List columns, DataSch } break; case LONG_ARRAY: - for (int rowId = 0; rowId < columnarBuilder._numRows; rowId++) { + for (int rowId = 0; rowId < numRows; rowId++) { value = column[rowId]; if (value == null) { nullBitmaps[colId].add(rowId); value = nullPlaceholders[colId]; } - if (value instanceof int[]) { - // LONG_ARRAY type covers INT_ARRAY and LONG_ARRAY - int[] ints = (int[]) value; - int length = ints.length; - long[] longs = new long[length]; - ArrayCopyUtils.copy(ints, longs, length); - setColumn(columnarBuilder, byteBuffer, longs); - } else { - setColumn(columnarBuilder, byteBuffer, (long[]) value); - } + setColumn(columnarBuilder, byteBuffer, (long[]) value); } break; case FLOAT_ARRAY: - for (int rowId = 0; rowId < columnarBuilder._numRows; rowId++) { + for (int rowId = 0; rowId < numRows; rowId++) { value = column[rowId]; if (value == null) { nullBitmaps[colId].add(rowId); @@ -395,77 +323,44 @@ public static ColumnarDataBlock buildFromColumns(List columns, DataSch } break; case DOUBLE_ARRAY: - for (int rowId = 0; rowId < columnarBuilder._numRows; rowId++) { + for (int rowId = 0; rowId < numRows; rowId++) { value = column[rowId]; if (value == null) { nullBitmaps[colId].add(rowId); value = nullPlaceholders[colId]; } - // DOUBLE_ARRAY type covers INT_ARRAY, LONG_ARRAY, FLOAT_ARRAY and DOUBLE_ARRAY - if (value instanceof int[]) { - int[] ints = (int[]) value; - int length = ints.length; - double[] doubles = new double[length]; - ArrayCopyUtils.copy(ints, doubles, length); - setColumn(columnarBuilder, byteBuffer, doubles); - } else if (value instanceof long[]) { - long[] longs = (long[]) value; - int length = longs.length; - double[] doubles = new double[length]; - ArrayCopyUtils.copy(longs, doubles, length); - setColumn(columnarBuilder, byteBuffer, doubles); - } else if (value instanceof float[]) { - float[] floats = (float[]) value; - int length = floats.length; - double[] doubles = new double[length]; - ArrayCopyUtils.copy(floats, doubles, length); - setColumn(columnarBuilder, byteBuffer, doubles); - } else { - setColumn(columnarBuilder, byteBuffer, (double[]) value); - } + setColumn(columnarBuilder, byteBuffer, (double[]) value); } break; - case BOOLEAN_ARRAY: - for (int rowId = 0; rowId < columnarBuilder._numRows; rowId++) { + case STRING_ARRAY: + for (int rowId = 0; rowId < numRows; rowId++) { value = column[rowId]; if (value == null) { nullBitmaps[colId].add(rowId); value = nullPlaceholders[colId]; } - int length = ((boolean[]) value).length; - int[] ints = new int[length]; - ArrayCopyUtils.copy((boolean[]) value, ints, length); - setColumn(columnarBuilder, byteBuffer, ints); + setColumn(columnarBuilder, byteBuffer, (String[]) value); } break; - case TIMESTAMP_ARRAY: - for (int rowId = 0; rowId < columnarBuilder._numRows; rowId++) { - value = column[rowId]; - if (value == null) { - nullBitmaps[colId].add(rowId); - value = nullPlaceholders[colId]; - } - int length = ((Timestamp[]) value).length; - long[] longs = new long[length]; - ArrayCopyUtils.copy((Timestamp[]) value, longs, length); - setColumn(columnarBuilder, byteBuffer, longs); + + // Special intermediate result for aggregation function + case OBJECT: + for (int rowId = 0; rowId < numRows; rowId++) { + setColumn(columnarBuilder, byteBuffer, column[rowId]); } break; - case BYTES_ARRAY: - case STRING_ARRAY: - for (int rowId = 0; rowId < columnarBuilder._numRows; rowId++) { - value = column[rowId]; - if (value == null) { - nullBitmaps[colId].add(rowId); - value = nullPlaceholders[colId]; - } - setColumn(columnarBuilder, byteBuffer, (String[]) value); + + // Null + case UNKNOWN: + for (int rowId = 0; rowId < numRows; rowId++) { + setColumn(columnarBuilder, byteBuffer, (Object) null); } break; + default: throw new IllegalStateException( - String.format("Unsupported data type: %s for column: %s", columnarBuilder._columnDataTypes[colId], - columnarBuilder._dataSchema.getColumnName(colId))); + String.format("Unsupported stored type: %s for column: %s", storedTypes[colId], + dataSchema.getColumnName(colId))); } columnarBuilder._fixedSizeDataByteArrayOutputStream.write(byteBuffer.array(), 0, byteBuffer.position()); } @@ -524,7 +419,7 @@ private static void setColumn(DataBlockBuilder builder, ByteBuffer byteBuffer, @ byteBuffer.putInt(builder._variableSizeDataByteArrayOutputStream.size()); if (value == null) { byteBuffer.putInt(0); - builder._variableSizeDataOutputStream.writeInt(DataTable.CustomObject.NULL_TYPE_VALUE); + builder._variableSizeDataOutputStream.writeInt(CustomObject.NULL_TYPE_VALUE); } else { int objectTypeValue = ObjectSerDeUtils.ObjectType.getObjectType(value).getValue(); byte[] bytes = ObjectSerDeUtils.serialize(value, objectTypeValue); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/common/datatable/BaseDataTableBuilder.java b/pinot-core/src/main/java/org/apache/pinot/core/common/datatable/BaseDataTableBuilder.java index 9547924ac54e..c0a8ff8ea1cb 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/common/datatable/BaseDataTableBuilder.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/common/datatable/BaseDataTableBuilder.java @@ -24,7 +24,7 @@ import java.math.BigDecimal; import java.nio.ByteBuffer; import javax.annotation.Nullable; -import org.apache.pinot.common.datatable.DataTable; +import org.apache.pinot.common.CustomObject; import org.apache.pinot.common.datatable.DataTableUtils; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.common.ObjectSerDeUtils; @@ -103,7 +103,7 @@ public void setColumn(int colId, @Nullable Object value) _currentRowDataByteBuffer.putInt(_variableSizeDataByteArrayOutputStream.size()); if (value == null) { _currentRowDataByteBuffer.putInt(0); - _variableSizeDataOutputStream.writeInt(DataTable.CustomObject.NULL_TYPE_VALUE); + _variableSizeDataOutputStream.writeInt(CustomObject.NULL_TYPE_VALUE); } else { int objectTypeValue = ObjectSerDeUtils.ObjectType.getObjectType(value).getValue(); byte[] bytes = ObjectSerDeUtils.serialize(value, objectTypeValue); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/common/datatable/DataTableBuilderFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/common/datatable/DataTableBuilderFactory.java index 2bf0426a7871..d4e93e74763f 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/common/datatable/DataTableBuilderFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/common/datatable/DataTableBuilderFactory.java @@ -34,7 +34,7 @@ private DataTableBuilderFactory() { private static final Logger LOGGER = LoggerFactory.getLogger(DataTableBuilderFactory.class); - public static final int DEFAULT_VERSION = DataTableFactory.VERSION_3; + public static final int DEFAULT_VERSION = DataTableFactory.VERSION_4; private static int _version = DEFAULT_VERSION; @@ -43,7 +43,7 @@ public static int getDataTableVersion() { } public static void setDataTableVersion(int version) { - LOGGER.info("Setting DataTable version to: " + version); + LOGGER.info("Setting DataTable version to: {}", version); if (version != DataTableFactory.VERSION_2 && version != DataTableFactory.VERSION_3 && version != DataTableFactory.VERSION_4) { throw new IllegalArgumentException("Unsupported version: " + version); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/common/evaluators/DefaultJsonPathEvaluator.java b/pinot-core/src/main/java/org/apache/pinot/core/common/evaluators/DefaultJsonPathEvaluator.java index f22474354c5f..cae1e3c17bc0 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/common/evaluators/DefaultJsonPathEvaluator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/common/evaluators/DefaultJsonPathEvaluator.java @@ -25,6 +25,7 @@ import com.jayway.jsonpath.JsonPath; import com.jayway.jsonpath.Option; import com.jayway.jsonpath.ParseContext; +import com.jayway.jsonpath.TypeRef; import com.jayway.jsonpath.spi.json.JacksonJsonProvider; import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider; import java.math.BigDecimal; @@ -58,6 +59,16 @@ public final class DefaultJsonPathEvaluator implements JsonPathEvaluator { private static final float[] EMPTY_FLOATS = new float[0]; private static final double[] EMPTY_DOUBLES = new double[0]; private static final String[] EMPTY_STRINGS = new String[0]; + private static final TypeRef> INTEGER_LIST_TYPE = new TypeRef>() { + }; + private static final TypeRef> LONG_LIST_TYPE = new TypeRef>() { + }; + private static final TypeRef> FLOAT_LIST_TYPE = new TypeRef>() { + }; + private static final TypeRef> DOUBLE_LIST_TYPE = new TypeRef>() { + }; + private static final TypeRef> STRING_LIST_TYPE = new TypeRef>() { + }; public static JsonPathEvaluator create(String jsonPath, @Nullable Object defaultValue) { try { @@ -274,23 +285,23 @@ public void evaluateBlock(int[] docIds, in reader.readDictIds(docIds, length, dictIdsBuffer, context); if (dictionary.getValueType() == FieldSpec.DataType.BYTES) { for (int i = 0; i < length; i++) { - processList(i, extractFromBytes(dictionary, dictIdsBuffer[i]), valueBuffer); + processList(i, extractFromBytes(dictionary, dictIdsBuffer[i], INTEGER_LIST_TYPE), valueBuffer); } } else { for (int i = 0; i < length; i++) { - processList(i, extractFromString(dictionary, dictIdsBuffer[i]), valueBuffer); + processList(i, extractFromString(dictionary, dictIdsBuffer[i], INTEGER_LIST_TYPE), valueBuffer); } } } else { switch (reader.getStoredType()) { case STRING: for (int i = 0; i < length; i++) { - processList(i, extractFromString(reader, context, docIds[i]), valueBuffer); + processList(i, extractFromString(reader, context, docIds[i], INTEGER_LIST_TYPE), valueBuffer); } break; case BYTES: for (int i = 0; i < length; i++) { - processList(i, extractFromBytes(reader, context, docIds[i]), valueBuffer); + processList(i, extractFromBytes(reader, context, docIds[i], INTEGER_LIST_TYPE), valueBuffer); } break; default: @@ -305,23 +316,23 @@ public void evaluateBlock(int[] docIds, in reader.readDictIds(docIds, length, dictIdsBuffer, context); if (dictionary.getValueType() == FieldSpec.DataType.BYTES) { for (int i = 0; i < length; i++) { - processList(i, extractFromBytes(dictionary, dictIdsBuffer[i]), valueBuffer); + processList(i, extractFromBytes(dictionary, dictIdsBuffer[i], LONG_LIST_TYPE), valueBuffer); } } else { for (int i = 0; i < length; i++) { - processList(i, extractFromString(dictionary, dictIdsBuffer[i]), valueBuffer); + processList(i, extractFromString(dictionary, dictIdsBuffer[i], LONG_LIST_TYPE), valueBuffer); } } } else { switch (reader.getStoredType()) { case STRING: for (int i = 0; i < length; i++) { - processList(i, extractFromString(reader, context, docIds[i]), valueBuffer); + processList(i, extractFromString(reader, context, docIds[i], LONG_LIST_TYPE), valueBuffer); } break; case BYTES: for (int i = 0; i < length; i++) { - processList(i, extractFromBytes(reader, context, docIds[i]), valueBuffer); + processList(i, extractFromBytes(reader, context, docIds[i], LONG_LIST_TYPE), valueBuffer); } break; default: @@ -336,23 +347,23 @@ public void evaluateBlock(int[] docIds, in reader.readDictIds(docIds, length, dictIdsBuffer, context); if (dictionary.getValueType() == FieldSpec.DataType.BYTES) { for (int i = 0; i < length; i++) { - processList(i, extractFromBytes(dictionary, dictIdsBuffer[i]), valueBuffer); + processList(i, extractFromBytes(dictionary, dictIdsBuffer[i], FLOAT_LIST_TYPE), valueBuffer); } } else { for (int i = 0; i < length; i++) { - processList(i, extractFromString(dictionary, dictIdsBuffer[i]), valueBuffer); + processList(i, extractFromString(dictionary, dictIdsBuffer[i], FLOAT_LIST_TYPE), valueBuffer); } } } else { switch (reader.getStoredType()) { case STRING: for (int i = 0; i < length; i++) { - processList(i, extractFromString(reader, context, docIds[i]), valueBuffer); + processList(i, extractFromString(reader, context, docIds[i], FLOAT_LIST_TYPE), valueBuffer); } break; case BYTES: for (int i = 0; i < length; i++) { - processList(i, extractFromBytes(reader, context, docIds[i]), valueBuffer); + processList(i, extractFromBytes(reader, context, docIds[i], FLOAT_LIST_TYPE), valueBuffer); } break; default: @@ -367,23 +378,23 @@ public void evaluateBlock(int[] docIds, in reader.readDictIds(docIds, length, dictIdsBuffer, context); if (dictionary.getValueType() == FieldSpec.DataType.BYTES) { for (int i = 0; i < length; i++) { - processList(i, extractFromBytes(dictionary, dictIdsBuffer[i]), valueBuffer); + processList(i, extractFromBytes(dictionary, dictIdsBuffer[i], DOUBLE_LIST_TYPE), valueBuffer); } } else { for (int i = 0; i < length; i++) { - processList(i, extractFromString(dictionary, dictIdsBuffer[i]), valueBuffer); + processList(i, extractFromString(dictionary, dictIdsBuffer[i], DOUBLE_LIST_TYPE), valueBuffer); } } } else { switch (reader.getStoredType()) { case STRING: for (int i = 0; i < length; i++) { - processList(i, extractFromString(reader, context, docIds[i]), valueBuffer); + processList(i, extractFromString(reader, context, docIds[i], DOUBLE_LIST_TYPE), valueBuffer); } break; case BYTES: for (int i = 0; i < length; i++) { - processList(i, extractFromBytes(reader, context, docIds[i]), valueBuffer); + processList(i, extractFromBytes(reader, context, docIds[i], DOUBLE_LIST_TYPE), valueBuffer); } break; default: @@ -398,23 +409,23 @@ public void evaluateBlock(int[] docIds, in reader.readDictIds(docIds, length, dictIdsBuffer, context); if (dictionary.getValueType() == FieldSpec.DataType.BYTES) { for (int i = 0; i < length; i++) { - processList(i, extractFromBytes(dictionary, dictIdsBuffer[i]), valueBuffer); + processList(i, extractFromBytes(dictionary, dictIdsBuffer[i], STRING_LIST_TYPE), valueBuffer); } } else { for (int i = 0; i < length; i++) { - processList(i, extractFromString(dictionary, dictIdsBuffer[i]), valueBuffer); + processList(i, extractFromString(dictionary, dictIdsBuffer[i], STRING_LIST_TYPE), valueBuffer); } } } else { switch (reader.getStoredType()) { case STRING: for (int i = 0; i < length; i++) { - processList(i, extractFromString(reader, context, docIds[i]), valueBuffer); + processList(i, extractFromString(reader, context, docIds[i], STRING_LIST_TYPE), valueBuffer); } break; case BYTES: for (int i = 0; i < length; i++) { - processList(i, extractFromBytes(reader, context, docIds[i]), valueBuffer); + processList(i, extractFromBytes(reader, context, docIds[i], STRING_LIST_TYPE), valueBuffer); } break; default: @@ -432,6 +443,15 @@ private T extractFromBytes(Dictionary dictionary, int dictId) { } } + @Nullable + private T extractFromBytes(Dictionary dictionary, int dictId, TypeRef ref) { + try { + return JSON_PARSER_CONTEXT.parseUtf8(dictionary.getBytesValue(dictId)).read(_jsonPath, ref); + } catch (Exception e) { + return null; + } + } + @Nullable private T extractFromBytes(ForwardIndexReader reader, R context, int docId) { @@ -442,6 +462,16 @@ private T extractFromBytes(ForwardIndex } } + @Nullable + private T extractFromBytes(ForwardIndexReader reader, R context, + int docId, TypeRef ref) { + try { + return JSON_PARSER_CONTEXT.parseUtf8(reader.getBytes(docId, context)).read(_jsonPath, ref); + } catch (Exception e) { + return null; + } + } + @Nullable private T extractFromBytesWithExactBigDecimal(Dictionary dictionary, int dictId) { try { @@ -470,6 +500,15 @@ private T extractFromString(Dictionary dictionary, int dictId) { } } + @Nullable + private T extractFromString(Dictionary dictionary, int dictId, TypeRef ref) { + try { + return JSON_PARSER_CONTEXT.parse(dictionary.getStringValue(dictId)).read(_jsonPath, ref); + } catch (Exception e) { + return null; + } + } + @Nullable private T extractFromString(ForwardIndexReader reader, R context, int docId) { @@ -480,6 +519,16 @@ private T extractFromString(ForwardInde } } + @Nullable + private T extractFromString(ForwardIndexReader reader, R context, + int docId, TypeRef ref) { + try { + return JSON_PARSER_CONTEXT.parseUtf8(reader.getBytes(docId, context)).read(_jsonPath, ref); + } catch (Exception e) { + return null; + } + } + @Nullable private T extractFromStringWithExactBigDecimal(Dictionary dictionary, int dictId) { try { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/BaseTableDataManager.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/BaseTableDataManager.java index ac8a717670c2..bced2704db84 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/BaseTableDataManager.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/BaseTableDataManager.java @@ -28,23 +28,27 @@ import java.net.URI; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; -import org.apache.commons.configuration.Configuration; -import org.apache.commons.configuration.ConfigurationConverter; import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.helix.HelixManager; import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; import org.apache.pinot.common.auth.AuthProviderUtils; +import org.apache.pinot.common.metadata.ZKMetadataProvider; import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; import org.apache.pinot.common.metrics.ServerGauge; import org.apache.pinot.common.metrics.ServerMeter; @@ -57,12 +61,13 @@ import org.apache.pinot.core.util.PeerServerSegmentFinder; import org.apache.pinot.segment.local.data.manager.SegmentDataManager; import org.apache.pinot.segment.local.data.manager.TableDataManager; -import org.apache.pinot.segment.local.data.manager.TableDataManagerConfig; -import org.apache.pinot.segment.local.data.manager.TableDataManagerParams; import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; import org.apache.pinot.segment.local.segment.index.loader.IndexLoadingConfig; import org.apache.pinot.segment.local.segment.index.loader.LoaderUtils; +import org.apache.pinot.segment.local.utils.SegmentLocks; import org.apache.pinot.segment.spi.ImmutableSegment; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; import org.apache.pinot.segment.spi.SegmentMetadata; import org.apache.pinot.segment.spi.index.metadata.SegmentMetadataImpl; import org.apache.pinot.segment.spi.loader.SegmentDirectoryLoader; @@ -70,36 +75,40 @@ import org.apache.pinot.segment.spi.loader.SegmentDirectoryLoaderRegistry; import org.apache.pinot.segment.spi.store.SegmentDirectory; import org.apache.pinot.spi.auth.AuthProvider; +import org.apache.pinot.spi.config.instance.InstanceDataManagerConfig; import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.data.Schema; -import org.apache.pinot.spi.env.PinotConfiguration; import org.apache.pinot.spi.utils.CommonConstants; -import org.apache.pinot.spi.utils.retry.AttemptsExceededException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @ThreadSafe public abstract class BaseTableDataManager implements TableDataManager { - private static final Logger LOGGER = LoggerFactory.getLogger(BaseTableDataManager.class); + protected static final Logger LOGGER = LoggerFactory.getLogger(BaseTableDataManager.class); protected final ConcurrentHashMap _segmentDataManagerMap = new ConcurrentHashMap<>(); - // Semaphore to restrict the maximum number of parallel segment downloads for a table. - private Semaphore _segmentDownloadSemaphore; + protected final ServerMetrics _serverMetrics = ServerMetrics.get(); - protected TableDataManagerConfig _tableDataManagerConfig; + protected InstanceDataManagerConfig _instanceDataManagerConfig; protected String _instanceId; + protected HelixManager _helixManager; protected ZkHelixPropertyStore _propertyStore; - protected ServerMetrics _serverMetrics; + protected SegmentLocks _segmentLocks; + protected TableConfig _tableConfig; protected String _tableNameWithType; protected String _tableDataDir; protected File _indexDir; protected File _resourceTmpDir; protected Logger _logger; - protected HelixManager _helixManager; + protected ExecutorService _segmentPreloadExecutor; protected AuthProvider _authProvider; + protected String _peerDownloadScheme; protected long _streamSegmentDownloadUntarRateLimitBytesPerSec; protected boolean _isStreamSegmentDownloadUntar; + // Semaphore to restrict the maximum number of parallel segment downloads for a table + private Semaphore _segmentDownloadSemaphore; // Fixed size LRU cache with TableName - SegmentName pair as key, and segment related // errors as the value. @@ -107,24 +116,25 @@ public abstract class BaseTableDataManager implements TableDataManager { // Cache used for identifying segments which could not be acquired since they were recently deleted. protected Cache _recentlyDeletedSegments; - @Override - public void init(TableDataManagerConfig tableDataManagerConfig, String instanceId, - ZkHelixPropertyStore propertyStore, ServerMetrics serverMetrics, HelixManager helixManager, - @Nullable LoadingCache, SegmentErrorInfo> errorCache, - TableDataManagerParams tableDataManagerParams) { - LOGGER.info("Initializing table data manager for table: {}", tableDataManagerConfig.getTableName()); + protected volatile boolean _shutDown; - _tableDataManagerConfig = tableDataManagerConfig; - _instanceId = instanceId; - _propertyStore = propertyStore; - _serverMetrics = serverMetrics; + @Override + public void init(InstanceDataManagerConfig instanceDataManagerConfig, HelixManager helixManager, + SegmentLocks segmentLocks, TableConfig tableConfig, @Nullable ExecutorService segmentPreloadExecutor, + @Nullable LoadingCache, SegmentErrorInfo> errorCache) { + LOGGER.info("Initializing table data manager for table: {}", tableConfig.getTableName()); + + _instanceDataManagerConfig = instanceDataManagerConfig; + _instanceId = instanceDataManagerConfig.getInstanceId(); + _tableConfig = tableConfig; + _segmentLocks = segmentLocks; _helixManager = helixManager; + _propertyStore = helixManager.getHelixPropertyStore(); + _segmentPreloadExecutor = segmentPreloadExecutor; + _authProvider = AuthProviderUtils.extractAuthProvider(_instanceDataManagerConfig.getAuthConfig(), null); - _authProvider = - AuthProviderUtils.extractAuthProvider(toPinotConfiguration(_tableDataManagerConfig.getAuthConfig()), null); - - _tableNameWithType = tableDataManagerConfig.getTableName(); - _tableDataDir = tableDataManagerConfig.getDataDir(); + _tableNameWithType = tableConfig.getTableName(); + _tableDataDir = _instanceDataManagerConfig.getInstanceDataDir() + File.separator + _tableNameWithType; _indexDir = new File(_tableDataDir); if (!_indexDir.exists()) { Preconditions.checkState(_indexDir.mkdirs(), "Unable to create index directory at %s. " @@ -140,18 +150,30 @@ public void init(TableDataManagerConfig tableDataManagerConfig, String instanceI } _errorCache = errorCache; _recentlyDeletedSegments = - CacheBuilder.newBuilder().maximumSize(tableDataManagerConfig.getTableDeletedSegmentsCacheSize()) - .expireAfterWrite(tableDataManagerConfig.getTableDeletedSegmentsCacheTtlMinutes(), TimeUnit.MINUTES) - .build(); + CacheBuilder.newBuilder().maximumSize(instanceDataManagerConfig.getDeletedSegmentsCacheSize()) + .expireAfterWrite(instanceDataManagerConfig.getDeletedSegmentsCacheTtlMinutes(), TimeUnit.MINUTES).build(); + + _peerDownloadScheme = tableConfig.getValidationConfig().getPeerSegmentDownloadScheme(); + if (_peerDownloadScheme == null) { + _peerDownloadScheme = instanceDataManagerConfig.getSegmentPeerDownloadScheme(); + } + if (_peerDownloadScheme != null) { + _peerDownloadScheme = _peerDownloadScheme.toLowerCase(); + Preconditions.checkState( + CommonConstants.HTTP_PROTOCOL.equals(_peerDownloadScheme) || CommonConstants.HTTPS_PROTOCOL.equals( + _peerDownloadScheme), "Unsupported peer download scheme: %s for table: %s", _peerDownloadScheme, + _tableNameWithType); + } + _streamSegmentDownloadUntarRateLimitBytesPerSec = - tableDataManagerParams.getStreamSegmentDownloadUntarRateLimitBytesPerSec(); - _isStreamSegmentDownloadUntar = tableDataManagerParams.isStreamSegmentDownloadUntar(); + instanceDataManagerConfig.getStreamSegmentDownloadUntarRateLimit(); + _isStreamSegmentDownloadUntar = instanceDataManagerConfig.isStreamSegmentDownloadUntar(); if (_isStreamSegmentDownloadUntar) { LOGGER.info("Using streamed download-untar for segment download! " + "The rate limit interval for streamed download-untar is {} bytes/s", _streamSegmentDownloadUntarRateLimitBytesPerSec); } - int maxParallelSegmentDownloads = tableDataManagerParams.getMaxParallelSegmentDownloads(); + int maxParallelSegmentDownloads = instanceDataManagerConfig.getMaxParallelSegmentDownloads(); if (maxParallelSegmentDownloads > 0) { LOGGER.info( "Construct segment download semaphore for Table: {}. Maximum number of parallel segment downloads: {}", @@ -164,30 +186,90 @@ public void init(TableDataManagerConfig tableDataManagerConfig, String instanceI doInit(); - _logger.info("Initialized table data manager for table: {} with data directory: {}", _tableNameWithType, - _tableDataDir); + _logger.info("Initialized table data manager with data directory: {}", _tableDataDir); } protected abstract void doInit(); @Override - public void start() { - _logger.info("Starting table data manager for table: {}", _tableNameWithType); + public String getInstanceId() { + return _instanceId; + } + + @Override + public InstanceDataManagerConfig getInstanceDataManagerConfig() { + return _instanceDataManagerConfig; + } + + @Override + public synchronized void start() { + _logger.info("Starting table data manager"); doStart(); - _logger.info("Started table data manager for table: {}", _tableNameWithType); + _logger.info("Started table data manager"); } protected abstract void doStart(); @Override - public void shutDown() { - _logger.info("Shutting down table data manager for table: {}", _tableNameWithType); + public synchronized void shutDown() { + if (_shutDown) { + _logger.warn("Table data manager is already shut down"); + return; + } + _logger.info("Shutting down table data manager"); + _shutDown = true; doShutdown(); - _logger.info("Shut down table data manager for table: {}", _tableNameWithType); + _logger.info("Shut down table data manager"); } protected abstract void doShutdown(); + /** + * Releases and removes all segments tracked by the table data manager. + */ + protected void releaseAndRemoveAllSegments() { + List segmentDataManagers; + synchronized (_segmentDataManagerMap) { + segmentDataManagers = new ArrayList<>(_segmentDataManagerMap.values()); + _segmentDataManagerMap.clear(); + } + if (!segmentDataManagers.isEmpty()) { + int numThreads = Math.min(Runtime.getRuntime().availableProcessors(), segmentDataManagers.size()); + ExecutorService stopExecutorService = Executors.newFixedThreadPool(numThreads); + for (SegmentDataManager segmentDataManager : segmentDataManagers) { + stopExecutorService.submit(() -> { + segmentDataManager.offload(); + releaseSegment(segmentDataManager); + }); + } + stopExecutorService.shutdown(); + try { + // Wait at most 10 minutes before exiting this method. + if (!stopExecutorService.awaitTermination(10, TimeUnit.MINUTES)) { + stopExecutorService.shutdownNow(); + } + } catch (InterruptedException e) { + stopExecutorService.shutdownNow(); + Thread.currentThread().interrupt(); + } + } + } + + @Override + public boolean isShutDown() { + return _shutDown; + } + + @Override + public Lock getSegmentLock(String segmentName) { + return _segmentLocks.getLock(_tableNameWithType, segmentName); + } + + @Override + public boolean hasSegment(String segmentName) { + return _segmentDataManagerMap.containsKey(segmentName); + } + /** * {@inheritDoc} *

If one segment already exists with the same name, replaces it with the new one. @@ -200,7 +282,9 @@ public void shutDown() { @Override public void addSegment(ImmutableSegment immutableSegment) { String segmentName = immutableSegment.getSegmentName(); - _logger.info("Adding immutable segment: {} to table: {}", segmentName, _tableNameWithType); + Preconditions.checkState(!_shutDown, "Table data manager is already shut down, cannot add segment: %s to table: %s", + segmentName, _tableNameWithType); + _logger.info("Adding immutable segment: {}", segmentName); _serverMetrics.addValueToTableGauge(_tableNameWithType, ServerGauge.DOCUMENT_COUNT, immutableSegment.getSegmentMetadata().getTotalDocs()); _serverMetrics.addValueToTableGauge(_tableNameWithType, ServerGauge.SEGMENT_COUNT, 1L); @@ -208,47 +292,174 @@ public void addSegment(ImmutableSegment immutableSegment) { ImmutableSegmentDataManager newSegmentManager = new ImmutableSegmentDataManager(immutableSegment); SegmentDataManager oldSegmentManager = registerSegment(segmentName, newSegmentManager); if (oldSegmentManager == null) { - _logger.info("Added new immutable segment: {} to table: {}", segmentName, _tableNameWithType); + _logger.info("Added new immutable segment: {}", segmentName); } else { - _logger.info("Replaced immutable segment: {} of table: {}", segmentName, _tableNameWithType); + _logger.info("Replaced immutable segment: {}", segmentName); + oldSegmentManager.offload(); releaseSegment(oldSegmentManager); } } @Override - public void addSegment(File indexDir, IndexLoadingConfig indexLoadingConfig) + public void addOnlineSegment(String segmentName) throws Exception { + Preconditions.checkState(!_shutDown, + "Table data manager is already shut down, cannot add ONLINE segment: %s to table: %s", segmentName, + _tableNameWithType); + _logger.info("Adding ONLINE segment: {}", segmentName); + Lock segmentLock = getSegmentLock(segmentName); + segmentLock.lock(); + try { + doAddOnlineSegment(segmentName); + } catch (Exception e) { + addSegmentError(segmentName, + new SegmentErrorInfo(System.currentTimeMillis(), "Caught exception while adding ONLINE segment", e)); + throw e; + } finally { + segmentLock.unlock(); + } + } + + protected abstract void doAddOnlineSegment(String segmentName) + throws Exception; + + @Override + public SegmentZKMetadata fetchZKMetadata(String segmentName) { + SegmentZKMetadata zkMetadata = + ZKMetadataProvider.getSegmentZKMetadata(_propertyStore, _tableNameWithType, segmentName); + Preconditions.checkState(zkMetadata != null, "Failed to find ZK metadata for segment: %s of table: %s", segmentName, + _tableNameWithType); + return zkMetadata; + } + + @Override + public Pair fetchTableConfigAndSchema() { + TableConfig tableConfig = ZKMetadataProvider.getTableConfig(_propertyStore, _tableNameWithType); + Preconditions.checkState(tableConfig != null, "Failed to find table config for table: %s", _tableNameWithType); + Schema schema = ZKMetadataProvider.getTableSchema(_propertyStore, tableConfig); + // NOTE: Schema is mandatory for REALTIME table. + if (tableConfig.getTableType() == TableType.REALTIME) { + Preconditions.checkState(schema != null, "Failed to find schema for table: %s", _tableNameWithType); + } + return Pair.of(tableConfig, schema); + } + + @Override + public IndexLoadingConfig getIndexLoadingConfig(TableConfig tableConfig, @Nullable Schema schema) { + IndexLoadingConfig indexLoadingConfig = new IndexLoadingConfig(_instanceDataManagerConfig, tableConfig, schema); indexLoadingConfig.setTableDataDir(_tableDataDir); - addSegment(ImmutableSegmentLoader.load(indexDir, indexLoadingConfig, indexLoadingConfig.getSchema())); + indexLoadingConfig.setInstanceTierConfigs(_instanceDataManagerConfig.getTierConfigs()); + return indexLoadingConfig; } @Override - public void addSegment(String segmentName, IndexLoadingConfig indexLoadingConfig, SegmentZKMetadata zkMetadata) + public void addNewOnlineSegment(SegmentZKMetadata zkMetadata, IndexLoadingConfig indexLoadingConfig) throws Exception { - throw new UnsupportedOperationException(); + _logger.info("Adding new ONLINE segment: {}", zkMetadata.getSegmentName()); + if (!tryLoadExistingSegment(zkMetadata, indexLoadingConfig)) { + downloadAndLoadSegment(zkMetadata, indexLoadingConfig); + } } /** - * Called when we get a helix transition to go to offline or dropped state. - * We need to remove it safely, keeping in mind that there may be queries that are - * using the segment, - * @param segmentName name of the segment to remove. + * Replaces an already loaded segment in a table if the segment has been overridden in the deep store (CRC mismatch). */ + protected void replaceSegmentIfCrcMismatch(SegmentDataManager segmentDataManager, SegmentZKMetadata zkMetadata, + IndexLoadingConfig indexLoadingConfig) + throws Exception { + String segmentName = segmentDataManager.getSegmentName(); + Preconditions.checkState(segmentDataManager instanceof ImmutableSegmentDataManager, + "Cannot replace CONSUMING segment: %s in table: %s", segmentName, _tableNameWithType); + SegmentMetadata localMetadata = segmentDataManager.getSegment().getSegmentMetadata(); + if (hasSameCRC(zkMetadata, localMetadata)) { + _logger.info("Segment: {} has CRC: {} same as before, not replacing it", segmentName, localMetadata.getCrc()); + return; + } + _logger.info("Replacing segment: {} because its CRC has changed from: {} to: {}", segmentName, + localMetadata.getCrc(), zkMetadata.getCrc()); + downloadAndLoadSegment(zkMetadata, indexLoadingConfig); + _logger.info("Replaced segment: {} with new CRC: {}", segmentName, zkMetadata.getCrc()); + } + + @Override + public void downloadAndLoadSegment(SegmentZKMetadata zkMetadata, IndexLoadingConfig indexLoadingConfig) + throws Exception { + String segmentName = zkMetadata.getSegmentName(); + _logger.info("Downloading and loading segment: {}", segmentName); + File indexDir = downloadSegment(zkMetadata); + addSegment(ImmutableSegmentLoader.load(indexDir, indexLoadingConfig)); + _logger.info("Downloaded and loaded segment: {} with CRC: {} on tier: {}", segmentName, zkMetadata.getCrc(), + TierConfigUtils.normalizeTierName(zkMetadata.getTier())); + } + + @Override + public void replaceSegment(String segmentName) + throws Exception { + Preconditions.checkState(!_shutDown, + "Table data manager is already shut down, cannot replace segment: %s in table: %s", segmentName, + _tableNameWithType); + _logger.info("Replacing segment: {}", segmentName); + Lock segmentLock = getSegmentLock(segmentName); + segmentLock.lock(); + try { + doReplaceSegment(segmentName); + } catch (Exception e) { + addSegmentError(segmentName, + new SegmentErrorInfo(System.currentTimeMillis(), "Caught exception while replacing segment", e)); + throw e; + } finally { + segmentLock.unlock(); + } + } + + protected void doReplaceSegment(String segmentName) + throws Exception { + SegmentDataManager segmentDataManager = _segmentDataManagerMap.get(segmentName); + if (segmentDataManager != null) { + SegmentZKMetadata zkMetadata = fetchZKMetadata(segmentName); + IndexLoadingConfig indexLoadingConfig = fetchIndexLoadingConfig(); + indexLoadingConfig.setSegmentTier(zkMetadata.getTier()); + replaceSegmentIfCrcMismatch(segmentDataManager, zkMetadata, indexLoadingConfig); + } else { + _logger.warn("Failed to find segment: {}, skipping replacing it", segmentName); + } + } + @Override - public void removeSegment(String segmentName) { - _logger.info("Removing segment: {} from table: {}", segmentName, _tableNameWithType); + public void offloadSegment(String segmentName) { + // NOTE: Do not throw exception when data manager has been shut down. This is regular flow when a table is deleted. + if (_shutDown) { + _logger.info("Table data manager is already shut down, skipping offloading segment: {}", segmentName); + return; + } + _logger.info("Offloading segment: {}", segmentName); + Lock segmentLock = getSegmentLock(segmentName); + segmentLock.lock(); + try { + doOffloadSegment(segmentName); + } catch (Exception e) { + addSegmentError(segmentName, + new SegmentErrorInfo(System.currentTimeMillis(), "Caught exception while offloading segment", e)); + throw e; + } finally { + segmentLock.unlock(); + } + } + + protected void doOffloadSegment(String segmentName) { SegmentDataManager segmentDataManager = unregisterSegment(segmentName); if (segmentDataManager != null) { + segmentDataManager.offload(); releaseSegment(segmentDataManager); - _logger.info("Removed segment: {} from table: {}", segmentName, _tableNameWithType); + _logger.info("Offloaded segment: {}", segmentName); } else { - _logger.info("Failed to find segment: {} in table: {}", segmentName, _tableNameWithType); + _logger.warn("Failed to find segment: {}, skipping offloading it", segmentName); } } /** * Returns true if the given segment has been deleted recently. The time range is determined by - * {@link org.apache.pinot.spi.config.instance.InstanceDataManagerConfig#getDeletedSegmentsCacheTtlMinutes()}. + * {@link InstanceDataManagerConfig#getDeletedSegmentsCacheTtlMinutes()}. */ @Override public boolean isSegmentDeletedRecently(String segmentName) { @@ -268,6 +479,12 @@ public List acquireAllSegments() { @Override public List acquireSegments(List segmentNames, List missingSegments) { + return acquireSegments(segmentNames, null, missingSegments); + } + + @Override + public List acquireSegments(List segmentNames, + @Nullable List optionalSegmentNames, List missingSegments) { List segmentDataManagers = new ArrayList<>(); for (String segmentName : segmentNames) { SegmentDataManager segmentDataManager = _segmentDataManagerMap.get(segmentName); @@ -277,6 +494,15 @@ public List acquireSegments(List segmentNames, List< missingSegments.add(segmentName); } } + if (optionalSegmentNames != null) { + for (String segmentName : optionalSegmentNames) { + SegmentDataManager segmentDataManager = _segmentDataManagerMap.get(segmentName); + // Optional segments are not counted to missing segments that are reported back in query exception. + if (segmentDataManager != null && segmentDataManager.increaseReferenceCount()) { + segmentDataManagers.add(segmentDataManager); + } + } + } return segmentDataManagers; } @@ -300,13 +526,13 @@ public void releaseSegment(SegmentDataManager segmentDataManager) { private void closeSegment(SegmentDataManager segmentDataManager) { String segmentName = segmentDataManager.getSegmentName(); - _logger.info("Closing segment: {} of table: {}", segmentName, _tableNameWithType); + _logger.info("Closing segment: {}", segmentName); _serverMetrics.addValueToTableGauge(_tableNameWithType, ServerGauge.SEGMENT_COUNT, -1L); _serverMetrics.addMeteredTableValue(_tableNameWithType, ServerMeter.DELETED_SEGMENT_COUNT, 1L); _serverMetrics.addValueToTableGauge(_tableNameWithType, ServerGauge.DOCUMENT_COUNT, -segmentDataManager.getSegment().getSegmentMetadata().getTotalDocs()); segmentDataManager.destroy(); - _logger.info("Closed segment: {} of table: {}", segmentName, _tableNameWithType); + _logger.info("Closed segment: {}", segmentName); } @Override @@ -325,67 +551,106 @@ public File getTableDataDir() { } @Override - public TableDataManagerConfig getTableDataManagerConfig() { - return _tableDataManagerConfig; + public HelixManager getHelixManager() { + return _helixManager; + } + + @Override + public ExecutorService getSegmentPreloadExecutor() { + return _segmentPreloadExecutor; } @Override public void addSegmentError(String segmentName, SegmentErrorInfo segmentErrorInfo) { - _errorCache.put(Pair.of(_tableNameWithType, segmentName), segmentErrorInfo); + if (_errorCache != null) { + _errorCache.put(Pair.of(_tableNameWithType, segmentName), segmentErrorInfo); + } } @Override public Map getSegmentErrors() { - if (_errorCache == null) { - return Collections.emptyMap(); + if (_errorCache != null) { + // Filter out entries that match the table name + Map segmentErrors = new HashMap<>(); + for (Map.Entry, SegmentErrorInfo> entry : _errorCache.asMap().entrySet()) { + Pair tableSegmentPair = entry.getKey(); + if (tableSegmentPair.getLeft().equals(_tableNameWithType)) { + segmentErrors.put(tableSegmentPair.getRight(), entry.getValue()); + } + } + return segmentErrors; } else { - // Filter out entries that match the table name. - return _errorCache.asMap().entrySet().stream().filter(map -> map.getKey().getLeft().equals(_tableNameWithType)) - .collect(Collectors.toMap(map -> map.getKey().getRight(), Map.Entry::getValue)); + return Map.of(); } } + @Override + public List getSegmentContexts(List selectedSegments, + Map queryOptions) { + List segmentContexts = new ArrayList<>(selectedSegments.size()); + selectedSegments.forEach(s -> segmentContexts.add(new SegmentContext(s))); + return segmentContexts; + } + @Override public void reloadSegment(String segmentName, IndexLoadingConfig indexLoadingConfig, SegmentZKMetadata zkMetadata, SegmentMetadata localMetadata, @Nullable Schema schema, boolean forceDownload) throws Exception { + Preconditions.checkState(!_shutDown, + "Table data manager is already shut down, cannot reload segment: %s of table: %s", segmentName, + _tableNameWithType); + _logger.info("Reloading segment: {}", segmentName); String segmentTier = getSegmentCurrentTier(segmentName); indexLoadingConfig.setSegmentTier(segmentTier); indexLoadingConfig.setTableDataDir(_tableDataDir); - indexLoadingConfig.setInstanceTierConfigs(_tableDataManagerConfig.getInstanceTierConfigs()); + indexLoadingConfig.setInstanceTierConfigs(_instanceDataManagerConfig.getTierConfigs()); File indexDir = getSegmentDataDir(segmentName, segmentTier, indexLoadingConfig.getTableConfig()); + Lock segmentLock = getSegmentLock(segmentName); + segmentLock.lock(); try { - // Create backup directory to handle failure of segment reloading. - createBackup(indexDir); - // Download segment from deep store if CRC changes or forced to download; // otherwise, copy backup directory back to the original index directory. // And then continue to load the segment from the index directory. boolean shouldDownload = forceDownload || !hasSameCRC(zkMetadata, localMetadata); - if (shouldDownload && allowDownload(segmentName, zkMetadata)) { + if (shouldDownload) { + // Create backup directory to handle failure of segment reloading. + createBackup(indexDir); if (forceDownload) { - LOGGER.info("Segment: {} of table: {} is forced to download", segmentName, _tableNameWithType); + _logger.info("Force downloading segment: {}", segmentName); } else { - LOGGER.info("Download segment:{} of table: {} as crc changes from: {} to: {}", segmentName, - _tableNameWithType, localMetadata.getCrc(), zkMetadata.getCrc()); + _logger.info("Downloading segment: {} because its CRC has changed from: {} to: {}", segmentName, + localMetadata.getCrc(), zkMetadata.getCrc()); } - indexDir = downloadSegment(segmentName, zkMetadata); + indexDir = downloadSegment(zkMetadata); } else { - LOGGER.info("Reload existing segment: {} of table: {} on tier: {}", segmentName, _tableNameWithType, + _logger.info("Reloading existing segment: {} on tier: {}", segmentName, TierConfigUtils.normalizeTierName(segmentTier)); + SegmentDirectory segmentDirectory = + initSegmentDirectory(segmentName, String.valueOf(zkMetadata.getCrc()), indexLoadingConfig); + // We should first try to reuse existing segment directory + if (canReuseExistingDirectoryForReload(zkMetadata, segmentTier, segmentDirectory, indexLoadingConfig, schema)) { + _logger.info("Reloading segment: {} using existing segment directory as no reprocessing needed", segmentName); + // No reprocessing needed, reuse the same segment + ImmutableSegment segment = ImmutableSegmentLoader.load(segmentDirectory, indexLoadingConfig, schema); + addSegment(segment); + return; + } + // Create backup directory to handle failure of segment reloading. + createBackup(indexDir); // The indexDir is empty after calling createBackup, as it's renamed to a backup directory. // The SegmentDirectory should initialize accordingly. Like for SegmentLocalFSDirectory, it // doesn't load anything from an empty indexDir, but gets the info to complete the copyTo. - try (SegmentDirectory segmentDirectory = initSegmentDirectory(segmentName, String.valueOf(zkMetadata.getCrc()), - indexLoadingConfig)) { + try { segmentDirectory.copyTo(indexDir); + } finally { + segmentDirectory.close(); } } // Load from indexDir and replace the old segment in memory. What's inside indexDir // may come from SegmentDirectory.copyTo() or the segment downloaded from deep store. indexLoadingConfig.setSegmentTier(zkMetadata.getTier()); - LOGGER.info("Load segment with data from indexDir: {} to tier: {}", indexDir, + _logger.info("Loading segment: {} from indexDir: {} to tier: {}", segmentName, indexDir, TierConfigUtils.normalizeTierName(zkMetadata.getTier())); ImmutableSegment segment = ImmutableSegmentLoader.load(indexDir, indexLoadingConfig, schema); addSegment(segment); @@ -396,53 +661,26 @@ public void reloadSegment(String segmentName, IndexLoadingConfig indexLoadingCon try { LoaderUtils.reloadFailureRecovery(indexDir); } catch (Exception recoveryFailureException) { - LOGGER.error("Failed to recover after reload failure", recoveryFailureException); + _logger.error("Failed to recover segment: {} after reload failure", segmentName, recoveryFailureException); reloadFailureException.addSuppressed(recoveryFailureException); } + addSegmentError(segmentName, + new SegmentErrorInfo(System.currentTimeMillis(), "Caught exception while reloading segment", + reloadFailureException)); throw reloadFailureException; + } finally { + segmentLock.unlock(); } + _logger.info("Reloaded segment: {}", segmentName); } - @Override - public void addOrReplaceSegment(String segmentName, IndexLoadingConfig indexLoadingConfig, - SegmentZKMetadata zkMetadata, @Nullable SegmentMetadata localMetadata) + private boolean canReuseExistingDirectoryForReload(SegmentZKMetadata segmentZKMetadata, String currentSegmentTier, + SegmentDirectory segmentDirectory, IndexLoadingConfig indexLoadingConfig, Schema schema) throws Exception { - if (localMetadata != null && hasSameCRC(zkMetadata, localMetadata)) { - LOGGER.info("Segment: {} of table: {} has crc: {} same as before, already loaded, do nothing", segmentName, - _tableNameWithType, localMetadata.getCrc()); - return; - } - - // The segment is not loaded by the server if the metadata object is null. But the segment - // may still be kept on the server. For example when server gets restarted, the segment is - // still on the server but the metadata object has not been initialized yet. In this case, - // we should check if the segment exists on server and try to load it. If the segment does - // not exist or fails to get loaded, we download segment from deep store to load it again. - String segmentTier = zkMetadata.getTier(); - indexLoadingConfig.setSegmentTier(segmentTier); - indexLoadingConfig.setTableDataDir(_tableDataDir); - indexLoadingConfig.setInstanceTierConfigs(_tableDataManagerConfig.getInstanceTierConfigs()); - if (localMetadata == null && tryLoadExistingSegment(segmentName, indexLoadingConfig, zkMetadata)) { - return; - } - - Preconditions.checkState(allowDownload(segmentName, zkMetadata), "Segment: %s of table: %s does not allow download", - segmentName, _tableNameWithType); - - // Download segment and replace the local one, either due to failure to recover local segment, - // or the segment data is updated and has new CRC now. - if (localMetadata == null) { - LOGGER.info("Download segment: {} of table: {} as it doesn't exist", segmentName, _tableNameWithType); - } else { - LOGGER.info("Download segment: {} of table: {} as crc changes from: {} to: {}", segmentName, _tableNameWithType, - localMetadata.getCrc(), zkMetadata.getCrc()); - } - File indexDir = downloadSegment(segmentName, zkMetadata); - ImmutableSegment segment = - ImmutableSegmentLoader.load(indexDir, indexLoadingConfig, indexLoadingConfig.getSchema(), true); - addSegment(segment); - LOGGER.info("Downloaded and loaded segment: {} of table: {} with crc: {} on tier: {}", segmentName, - _tableNameWithType, zkMetadata.getCrc(), TierConfigUtils.normalizeTierName(segmentTier)); + SegmentDirectoryLoader segmentDirectoryLoader = + SegmentDirectoryLoaderRegistry.getSegmentDirectoryLoader(indexLoadingConfig.getSegmentDirectoryLoader()); + return !segmentDirectoryLoader.needsTierMigration(segmentZKMetadata.getTier(), currentSegmentTier) + && !ImmutableSegmentLoader.needPreprocess(segmentDirectory, indexLoadingConfig, schema); } /** @@ -452,7 +690,10 @@ public void addOrReplaceSegment(String segmentName, IndexLoadingConfig indexLoad */ @Nullable protected SegmentDataManager registerSegment(String segmentName, SegmentDataManager segmentDataManager) { - SegmentDataManager oldSegmentDataManager = _segmentDataManagerMap.put(segmentName, segmentDataManager); + SegmentDataManager oldSegmentDataManager; + synchronized (_segmentDataManagerMap) { + oldSegmentDataManager = _segmentDataManagerMap.put(segmentName, segmentDataManager); + } _recentlyDeletedSegments.invalidate(segmentName); return oldSegmentDataManager; } @@ -467,194 +708,175 @@ protected SegmentDataManager registerSegment(String segmentName, SegmentDataMana @Nullable protected SegmentDataManager unregisterSegment(String segmentName) { _recentlyDeletedSegments.put(segmentName, segmentName); - return _segmentDataManagerMap.remove(segmentName); - } - - protected boolean allowDownload(String segmentName, SegmentZKMetadata zkMetadata) { - return true; - } - - protected File downloadSegment(String segmentName, SegmentZKMetadata zkMetadata) - throws Exception { - // TODO: may support download from peer servers for RealTime table. - return downloadSegmentFromDeepStore(segmentName, zkMetadata); - } - - private File downloadSegmentFromDeepStore(String segmentName, SegmentZKMetadata zkMetadata) - throws Exception { - File tempRootDir = getTmpSegmentDataDir("tmp-" + segmentName + "-" + UUID.randomUUID()); - if (_isStreamSegmentDownloadUntar && zkMetadata.getCrypterName() == null) { - try { - File untaredSegDir = downloadAndStreamUntarWithRateLimit(segmentName, zkMetadata, tempRootDir, - _streamSegmentDownloadUntarRateLimitBytesPerSec); - return moveSegment(segmentName, untaredSegDir); - } finally { - FileUtils.deleteQuietly(tempRootDir); - } - } else { - try { - File tarFile = downloadAndDecrypt(segmentName, zkMetadata, tempRootDir); - return untarAndMoveSegment(segmentName, tarFile, tempRootDir); - } finally { - FileUtils.deleteQuietly(tempRootDir); - } + synchronized (_segmentDataManagerMap) { + return _segmentDataManagerMap.remove(segmentName); } } - private File moveSegment(String segmentName, File untaredSegDir) - throws IOException { + /** + * Downloads an immutable segment into the index directory. + * Segment can be downloaded from deep store or from peer servers. Downloaded segment might be compressed or + * encrypted, and this method takes care of decompressing and decrypting the segment. + */ + protected File downloadSegment(SegmentZKMetadata zkMetadata) + throws Exception { + String segmentName = zkMetadata.getSegmentName(); + String downloadUrl = zkMetadata.getDownloadUrl(); + Preconditions.checkState(downloadUrl != null, + "Failed to find download URL in ZK metadata for segment: %s of table: %s", segmentName, _tableNameWithType); try { - File indexDir = getSegmentDataDir(segmentName); - FileUtils.deleteDirectory(indexDir); - FileUtils.moveDirectory(untaredSegDir, indexDir); - return indexDir; + if (!CommonConstants.Segment.METADATA_URI_FOR_PEER_DOWNLOAD.equals(downloadUrl)) { + try { + return downloadSegmentFromDeepStore(zkMetadata); + } catch (Exception e) { + if (_peerDownloadScheme != null) { + _logger.warn("Caught exception while downloading segment: {} from: {}, trying to download from peers", + segmentName, downloadUrl, e); + return downloadSegmentFromPeers(zkMetadata); + } else { + throw e; + } + } + } else { + return downloadSegmentFromPeers(zkMetadata); + } } catch (Exception e) { - LOGGER.error("Failed to move segment: {} of table: {}", segmentName, _tableNameWithType); - _serverMetrics.addMeteredTableValue(_tableNameWithType, ServerMeter.SEGMENT_DIR_MOVEMENT_FAILURES, 1L); + _serverMetrics.addMeteredTableValue(_tableNameWithType, ServerMeter.SEGMENT_DOWNLOAD_FAILURES, 1); throw e; } } - @VisibleForTesting - File downloadAndDecrypt(String segmentName, SegmentZKMetadata zkMetadata, File tempRootDir) + private File downloadSegmentFromDeepStore(SegmentZKMetadata zkMetadata) throws Exception { - File tarFile = new File(tempRootDir, segmentName + TarGzCompressionUtils.TAR_GZ_FILE_EXTENSION); - String uri = zkMetadata.getDownloadUrl(); - boolean downloadSuccess = false; + String segmentName = zkMetadata.getSegmentName(); + String downloadUrl = zkMetadata.getDownloadUrl(); + _logger.info("Downloading segment: {} from: {}", segmentName, downloadUrl); + File tempRootDir = getTmpSegmentDataDir("tmp-" + segmentName + "-" + UUID.randomUUID()); + if (_segmentDownloadSemaphore != null) { + long startTime = System.currentTimeMillis(); + _logger.info("Acquiring segment download semaphore for segment: {}, queue-length: {} ", segmentName, + _segmentDownloadSemaphore.getQueueLength()); + _segmentDownloadSemaphore.acquire(); + _logger.info("Acquired segment download semaphore for segment: {} (lock-time={}ms, queue-length={}).", + segmentName, System.currentTimeMillis() - startTime, _segmentDownloadSemaphore.getQueueLength()); + } try { - if (_segmentDownloadSemaphore != null) { - long startTime = System.currentTimeMillis(); - LOGGER.info("Trying to acquire segment download semaphore for: {}. queue-length: {} ", segmentName, - _segmentDownloadSemaphore.getQueueLength()); - _segmentDownloadSemaphore.acquire(); - LOGGER.info("Acquired segment download semaphore for: {} (lock-time={}ms, queue-length={}).", segmentName, - System.currentTimeMillis() - startTime, _segmentDownloadSemaphore.getQueueLength()); - } - SegmentFetcherFactory.fetchAndDecryptSegmentToLocal(uri, tarFile, zkMetadata.getCrypterName()); - LOGGER.info("Downloaded tarred segment: {} for table: {} from: {} to: {}, file length: {}", segmentName, - _tableNameWithType, uri, tarFile, tarFile.length()); - downloadSuccess = true; - return tarFile; - } catch (AttemptsExceededException e) { - LOGGER.error("Attempts exceeded when downloading segment: {} for table: {} from: {} to: {}", segmentName, - _tableNameWithType, uri, tarFile); - _serverMetrics.addMeteredTableValue(_tableNameWithType, ServerMeter.SEGMENT_DOWNLOAD_FROM_REMOTE_FAILURES, 1L); - if (_tableDataManagerConfig.getTablePeerDownloadScheme() == null) { - throw e; + File untarredSegmentDir; + if (_isStreamSegmentDownloadUntar && zkMetadata.getCrypterName() == null) { + _logger.info("Downloading segment: {} using streamed download-untar with maxStreamRateInByte: {}", segmentName, + _streamSegmentDownloadUntarRateLimitBytesPerSec); + AtomicInteger failedAttempts = new AtomicInteger(0); + try { + untarredSegmentDir = SegmentFetcherFactory.fetchAndStreamUntarToLocal(downloadUrl, tempRootDir, + _streamSegmentDownloadUntarRateLimitBytesPerSec, failedAttempts); + _logger.info("Downloaded and untarred segment: {} from: {}, failed attempts: {}", segmentName, downloadUrl, + failedAttempts.get()); + } finally { + _serverMetrics.addMeteredTableValue(_tableNameWithType, ServerMeter.SEGMENT_STREAMED_DOWNLOAD_UNTAR_FAILURES, + failedAttempts.get()); + } + } else { + File segmentTarFile = new File(tempRootDir, segmentName + TarGzCompressionUtils.TAR_GZ_FILE_EXTENSION); + SegmentFetcherFactory.fetchAndDecryptSegmentToLocal(downloadUrl, segmentTarFile, zkMetadata.getCrypterName()); + _logger.info("Downloaded tarred segment: {} from: {} to: {}, file length: {}", segmentName, downloadUrl, + segmentTarFile, segmentTarFile.length()); + untarredSegmentDir = untarSegment(segmentName, segmentTarFile, tempRootDir); } - downloadFromPeersWithoutStreaming(segmentName, zkMetadata, tarFile); - downloadSuccess = true; - return tarFile; + File indexDir = moveSegment(segmentName, untarredSegmentDir); + _logger.info("Downloaded segment: {} from: {} to: {}", segmentName, downloadUrl, indexDir); + return indexDir; + } catch (Exception e) { + _serverMetrics.addMeteredTableValue(_tableNameWithType, ServerMeter.SEGMENT_DOWNLOAD_FROM_REMOTE_FAILURES, 1); + throw e; } finally { - if (!downloadSuccess) { - _serverMetrics.addMeteredTableValue(_tableNameWithType, ServerMeter.SEGMENT_DOWNLOAD_FAILURES, 1L); - } if (_segmentDownloadSemaphore != null) { _segmentDownloadSemaphore.release(); } + FileUtils.deleteQuietly(tempRootDir); } } - // not thread safe. Caller should invoke it with safe concurrency control. - protected void downloadFromPeersWithoutStreaming(String segmentName, SegmentZKMetadata zkMetadata, - File destTarFile) throws Exception { - Preconditions.checkArgument(_tableDataManagerConfig.getTablePeerDownloadScheme() != null, - "Download peers require non null peer download scheme"); - List peerSegmentURIs = PeerServerSegmentFinder.getPeerServerURIs(segmentName, - _tableDataManagerConfig.getTablePeerDownloadScheme(), _helixManager, _tableNameWithType); - if (peerSegmentURIs.isEmpty()) { - String msg = String.format("segment %s doesn't have any peers", segmentName); - LOGGER.warn(msg); - // HelixStateTransitionHandler would catch the runtime exception and mark the segment state as Error - throw new RuntimeException(msg); - } + private File downloadSegmentFromPeers(SegmentZKMetadata zkMetadata) + throws Exception { + String segmentName = zkMetadata.getSegmentName(); + Preconditions.checkState(_peerDownloadScheme != null, "Peer download is not enabled for table: %s", + _tableNameWithType); + _logger.info("Downloading segment: {} from peers", segmentName); + File tempRootDir = getTmpSegmentDataDir("tmp-" + segmentName + "-" + UUID.randomUUID()); + File segmentTarFile = new File(tempRootDir, segmentName + TarGzCompressionUtils.TAR_GZ_FILE_EXTENSION); try { - // Next download the segment from a randomly chosen server using configured scheme. - SegmentFetcherFactory.fetchAndDecryptSegmentToLocal(peerSegmentURIs, destTarFile, zkMetadata.getCrypterName()); - LOGGER.info("Fetched segment {} from peers: {} to: {} of size: {}", segmentName, peerSegmentURIs, destTarFile, - destTarFile.length()); - } catch (AttemptsExceededException e) { - LOGGER.error("Attempts exceeded when downloading segment: {} for table: {} from peers {} to: {}", segmentName, - _tableNameWithType, peerSegmentURIs, destTarFile); - _serverMetrics.addMeteredTableValue(_tableNameWithType, ServerMeter.SEGMENT_DOWNLOAD_FROM_PEERS_FAILURES, 1L); + SegmentFetcherFactory.fetchAndDecryptSegmentToLocal(segmentName, _peerDownloadScheme, () -> { + List peerServerURIs = + PeerServerSegmentFinder.getPeerServerURIs(_helixManager, _tableNameWithType, segmentName, + _peerDownloadScheme); + Collections.shuffle(peerServerURIs); + return peerServerURIs; + }, segmentTarFile, zkMetadata.getCrypterName()); + _logger.info("Downloaded tarred segment: {} from peers to: {}, file length: {}", segmentName, segmentTarFile, + segmentTarFile.length()); + File indexDir = untarAndMoveSegment(segmentName, segmentTarFile, tempRootDir); + _logger.info("Downloaded segment: {} from peers to: {}", segmentName, indexDir); + return indexDir; + } catch (Exception e) { + _serverMetrics.addMeteredTableValue(_tableNameWithType, ServerMeter.SEGMENT_DOWNLOAD_FROM_PEERS_FAILURES, 1); throw e; + } finally { + FileUtils.deleteQuietly(tempRootDir); } } - private File downloadAndStreamUntarWithRateLimit(String segmentName, SegmentZKMetadata zkMetadata, File tempRootDir, - long maxStreamRateInByte) - throws Exception { - if (_segmentDownloadSemaphore != null) { - long startTime = System.currentTimeMillis(); - LOGGER.info("Trying to acquire segment download semaphore for: {}. queue-length: {} ", segmentName, - _segmentDownloadSemaphore.getQueueLength()); - _segmentDownloadSemaphore.acquire(); - LOGGER.info("Acquired segment download semaphore for: {} (lock-time={}ms, queue-length={}).", segmentName, - System.currentTimeMillis() - startTime, _segmentDownloadSemaphore.getQueueLength()); - } - LOGGER.info("Trying to download segment {} using streamed download-untar with maxStreamRateInByte {}", segmentName, - maxStreamRateInByte); - String uri = zkMetadata.getDownloadUrl(); + private File untarSegment(String segmentName, File segmentTarFile, File tempRootDir) + throws IOException { + File untarDir = new File(tempRootDir, segmentName); + _logger.info("Untarring segment: {} from: {} to: {}", segmentName, segmentTarFile, untarDir); try { - File ret = SegmentFetcherFactory.fetchAndStreamUntarToLocal(uri, tempRootDir, maxStreamRateInByte); - LOGGER.info("Download and untarred segment: {} for table: {} from: {}", segmentName, _tableNameWithType, uri); - return ret; - } catch (AttemptsExceededException e) { - LOGGER.error("Attempts exceeded when stream download-untarring segment: {} for table: {} from: {} to: {}", - segmentName, _tableNameWithType, uri, tempRootDir); - _serverMetrics.addMeteredTableValue(_tableNameWithType, ServerMeter.SEGMENT_STREAMED_DOWNLOAD_UNTAR_FAILURES, 1L); + // If an exception is thrown when untarring, it means the tar file is broken or not found after the retry. Thus, + // there's no need to retry again. + File untarredSegmentDir = TarGzCompressionUtils.untar(segmentTarFile, untarDir).get(0); + _logger.info("Untarred segment: {} into: {}", segmentName, untarredSegmentDir); + return untarredSegmentDir; + } catch (Exception e) { + _serverMetrics.addMeteredTableValue(_tableNameWithType, ServerMeter.UNTAR_FAILURES, 1); throw e; - } finally { - if (_segmentDownloadSemaphore != null) { - _segmentDownloadSemaphore.release(); - } } } - @VisibleForTesting - File untarAndMoveSegment(String segmentName, File tarFile, File tempRootDir) + private File moveSegment(String segmentName, File untarredSegmentDir) throws IOException { - File untarDir = new File(tempRootDir, segmentName); + File indexDir = getSegmentDataDir(segmentName); try { - // If an exception is thrown when untarring, it means the tar file is broken - // or not found after the retry. Thus, there's no need to retry again. - File untaredSegDir = TarGzCompressionUtils.untar(tarFile, untarDir).get(0); - LOGGER.info("Uncompressed tar file: {} into target dir: {}", tarFile, untarDir); - // Replace the existing index directory. - File indexDir = getSegmentDataDir(segmentName); FileUtils.deleteDirectory(indexDir); - FileUtils.moveDirectory(untaredSegDir, indexDir); - LOGGER.info("Successfully downloaded segment: {} of table: {} to index dir: {}", segmentName, _tableNameWithType, - indexDir); + FileUtils.moveDirectory(untarredSegmentDir, indexDir); return indexDir; } catch (Exception e) { - LOGGER.error("Failed to untar segment: {} of table: {} from: {} to: {}", segmentName, _tableNameWithType, tarFile, - untarDir); - _serverMetrics.addMeteredTableValue(_tableNameWithType, ServerMeter.UNTAR_FAILURES, 1L); + _serverMetrics.addMeteredTableValue(_tableNameWithType, ServerMeter.SEGMENT_DIR_MOVEMENT_FAILURES, 1); throw e; } } + @VisibleForTesting + File untarAndMoveSegment(String segmentName, File segmentTarFile, File tempRootDir) + throws IOException { + return moveSegment(segmentName, untarSegment(segmentName, segmentTarFile, tempRootDir)); + } + @VisibleForTesting File getSegmentDataDir(String segmentName) { return new File(_indexDir, segmentName); } - @VisibleForTesting - File getSegmentDataDir(String segmentName, @Nullable String segmentTier, TableConfig tableConfig) { + @Override + public File getSegmentDataDir(String segmentName, @Nullable String segmentTier, TableConfig tableConfig) { if (segmentTier == null) { return getSegmentDataDir(segmentName); } - try { - String tierDataDir = - TierConfigUtils.getDataDirForTier(tableConfig, segmentTier, _tableDataManagerConfig.getInstanceTierConfigs()); - File tierTableDataDir = new File(tierDataDir, _tableNameWithType); - return new File(tierTableDataDir, segmentName); - } catch (Exception e) { - LOGGER.warn("Failed to get dataDir for segment: {} of table: {} on tier: {} due to error: {}", segmentName, - _tableNameWithType, segmentTier, e.getMessage()); + String tierDataDir = + TierConfigUtils.getDataDirForTier(tableConfig, segmentTier, _instanceDataManagerConfig.getTierConfigs()); + if (StringUtils.isEmpty(tierDataDir)) { return getSegmentDataDir(segmentName); } + File tierTableDataDir = new File(tierDataDir, _tableNameWithType); + return new File(tierTableDataDir, segmentName); } @Nullable @@ -716,17 +938,13 @@ private void removeBackup(File indexDir) FileUtils.deleteDirectory(segmentTempDir); } - /** - * Try to load the segment potentially still existing on the server. - * - * @return true if the segment still exists on server, its CRC is still same with the - * one in SegmentZKMetadata and is loaded into memory successfully; false if it doesn't - * exist on the server, its CRC has changed, or it fails to be loaded. SegmentDirectory - * object may be created when trying to load the segment, but it's closed if the method - * returns false; otherwise it's opened and to be referred by ImmutableSegment object. - */ - protected boolean tryLoadExistingSegment(String segmentName, IndexLoadingConfig indexLoadingConfig, - SegmentZKMetadata zkMetadata) { + @Override + public boolean tryLoadExistingSegment(SegmentZKMetadata zkMetadata, IndexLoadingConfig indexLoadingConfig) { + String segmentName = zkMetadata.getSegmentName(); + Preconditions.checkState(!_shutDown, + "Table data manager is already shut down, cannot load existing segment: %s of table: %s", segmentName, + _tableNameWithType); + // Try to recover the segment from potential segment reloading failure. String segmentTier = zkMetadata.getTier(); File indexDir = getSegmentDataDir(segmentName, segmentTier, indexLoadingConfig.getTableConfig()); @@ -734,7 +952,6 @@ protected boolean tryLoadExistingSegment(String segmentName, IndexLoadingConfig // Creates the SegmentDirectory object to access the segment metadata. // The metadata is null if the segment doesn't exist yet. - SegmentDirectory segmentDirectory = tryInitSegmentDirectory(segmentName, String.valueOf(zkMetadata.getCrc()), indexLoadingConfig); SegmentMetadataImpl segmentMetadata = (segmentDirectory == null) ? null : segmentDirectory.getSegmentMetadata(); @@ -743,10 +960,10 @@ protected boolean tryLoadExistingSegment(String segmentName, IndexLoadingConfig // need to fall back to download the segment from deep store to load it. if (segmentMetadata == null || !hasSameCRC(zkMetadata, segmentMetadata)) { if (segmentMetadata == null) { - LOGGER.info("Segment: {} of table: {} does not exist", segmentName, _tableNameWithType); + _logger.info("Segment: {} does not exist", segmentName); } else if (!hasSameCRC(zkMetadata, segmentMetadata)) { - LOGGER.info("Segment: {} of table: {} has crc change from: {} to: {}", segmentName, _tableNameWithType, - segmentMetadata.getCrc(), zkMetadata.getCrc()); + _logger.info("Segment: {} has CRC changed from: {} to: {}", segmentName, segmentMetadata.getCrc(), + zkMetadata.getCrc()); } closeSegmentDirectoryQuietly(segmentDirectory); return false; @@ -758,11 +975,9 @@ protected boolean tryLoadExistingSegment(String segmentName, IndexLoadingConfig // or reprocess it to reflect latest table config and schema before loading. Schema schema = indexLoadingConfig.getSchema(); if (!ImmutableSegmentLoader.needPreprocess(segmentDirectory, indexLoadingConfig, schema)) { - LOGGER.info("Segment: {} of table: {} is consistent with latest table config and schema", segmentName, - _tableNameWithType); + _logger.info("Segment: {} is consistent with latest table config and schema", segmentName); } else { - LOGGER.info("Segment: {} of table: {} needs reprocess to reflect latest table config and schema", segmentName, - _tableNameWithType); + _logger.info("Segment: {} needs reprocess to reflect latest table config and schema", segmentName); segmentDirectory.copyTo(indexDir); // Close the stale SegmentDirectory object and recreate it with reprocessed segment. closeSegmentDirectoryQuietly(segmentDirectory); @@ -771,24 +986,24 @@ protected boolean tryLoadExistingSegment(String segmentName, IndexLoadingConfig } ImmutableSegment segment = ImmutableSegmentLoader.load(segmentDirectory, indexLoadingConfig, schema); addSegment(segment); - LOGGER.info("Loaded existing segment: {} of table: {} with crc: {} on tier: {}", segmentName, _tableNameWithType, - zkMetadata.getCrc(), TierConfigUtils.normalizeTierName(segmentTier)); + _logger.info("Loaded existing segment: {} with CRC: {} on tier: {}", segmentName, zkMetadata.getCrc(), + TierConfigUtils.normalizeTierName(segmentTier)); return true; } catch (Exception e) { - LOGGER.error("Failed to load existing segment: {} of table: {} with crc: {} on tier: {}", segmentName, - _tableNameWithType, zkMetadata.getCrc(), TierConfigUtils.normalizeTierName(segmentTier), e); + _logger.error("Failed to load existing segment: {} with CRC: {} on tier: {}", segmentName, zkMetadata.getCrc(), + TierConfigUtils.normalizeTierName(segmentTier), e); closeSegmentDirectoryQuietly(segmentDirectory); return false; } } + @Nullable private SegmentDirectory tryInitSegmentDirectory(String segmentName, String segmentCrc, IndexLoadingConfig indexLoadingConfig) { try { return initSegmentDirectory(segmentName, segmentCrc, indexLoadingConfig); } catch (Exception e) { - LOGGER.warn("Failed to initialize SegmentDirectory for segment: {} of table: {} with error: {}", segmentName, - _tableNameWithType, e.getMessage()); + _logger.warn("Failed to initialize SegmentDirectory for segment: {} with error: {}", segmentName, e.getMessage()); return null; } } @@ -832,11 +1047,4 @@ private static void closeSegmentDirectoryQuietly(SegmentDirectory segmentDirecto } } } - - private static PinotConfiguration toPinotConfiguration(Configuration configuration) { - if (configuration == null) { - return new PinotConfiguration(); - } - return new PinotConfiguration((Map) (Map) ConfigurationConverter.getMap(configuration)); - } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/InstanceDataManager.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/InstanceDataManager.java index 77cab8135a19..96d8534a65e2 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/InstanceDataManager.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/InstanceDataManager.java @@ -21,9 +21,10 @@ import java.io.File; import java.util.List; import java.util.Set; +import java.util.function.Supplier; import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; -import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration2.ex.ConfigurationException; import org.apache.helix.HelixManager; import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.helix.zookeeper.datamodel.ZNRecord; @@ -45,6 +46,7 @@ public interface InstanceDataManager { /** * Initializes the data manager. *

Should be called only once and before calling any other method. + *

NOTE: The config is the subset of server config with prefix 'pinot.server.instance' */ void init(PinotConfiguration config, HelixManager helixManager, ServerMetrics serverMetrics) throws ConfigurationException; @@ -73,26 +75,37 @@ void deleteTable(String tableNameWithType) throws Exception; /** - * Adds a segment from local disk into an OFFLINE table. + * Adds an ONLINE segment into a table. + * This method is triggered by state transition to ONLINE state. */ - void addOfflineSegment(String offlineTableName, String segmentName, File indexDir) + void addOnlineSegment(String tableNameWithType, String segmentName) throws Exception; /** - * Adds a segment into an REALTIME table. - *

The segment might be committed or under consuming. + * Adds a CONSUMING segment into a REALTIME table. + * This method is triggered by state transition to CONSUMING state. */ - void addRealtimeSegment(String realtimeTableName, String segmentName) + void addConsumingSegment(String realtimeTableName, String segmentName) + throws Exception; + + /** + * Replaces an already loaded segment in a table if the segment has been overridden in the deep store (CRC mismatch). + * This method is triggered by a custom message (NOT state transition), and the target segment should be in ONLINE + * state. + */ + void replaceSegment(String tableNameWithType, String segmentName) throws Exception; /** * Offloads a segment from table but not dropping its data from server. + * This method is triggered by state transition to OFFLINE state. */ void offloadSegment(String tableNameWithType, String segmentName) throws Exception; /** * Delete segment data from the server physically. + * This method is triggered by state transition to DROPPED state. */ void deleteSegment(String tableNameWithType, String segmentName) throws Exception; @@ -122,15 +135,6 @@ void reloadSegments(String tableNameWithType, List segmentNames, boolean SegmentRefreshSemaphore segmentRefreshSemaphore) throws Exception; - /** - * Adds or replaces a segment in a table. Different from segment reloading, this method - * doesn't assume the existence of TableDataManager object and it can actually initialize - * the TableDataManager for the segment. A new segment is downloaded if the local one is - * not working or has a different CRC from the remote one. - */ - void addOrReplaceSegment(String tableNameWithType, String segmentName) - throws Exception; - /** * Returns all tables served by the instance. */ @@ -183,4 +187,11 @@ void addOrReplaceSegment(String tableNameWithType, String segmentName) * Immediately stop consumption and start committing the consuming segments. */ void forceCommit(String tableNameWithType, Set segmentNames); + + /** + * Enables the installation of a method to determine if a server is ready to server queries. + * + * @param isServerReadyToServeQueries supplier to retrieve state of server. + */ + void setSupplierOfIsServerReadyToServeQueries(Supplier isServerReadyToServeQueries); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/DimensionTableDataManager.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/DimensionTableDataManager.java index 7031c135dcd4..a5ec45750e21 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/DimensionTableDataManager.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/DimensionTableDataManager.java @@ -20,14 +20,17 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; -import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections4.CollectionUtils; import org.apache.pinot.common.metadata.ZKMetadataProvider; import org.apache.pinot.segment.local.data.manager.SegmentDataManager; import org.apache.pinot.segment.local.segment.readers.PinotSegmentRecordReader; @@ -77,11 +80,14 @@ public static DimensionTableDataManager getInstanceByTableName(String tableNameW return INSTANCES.get(tableNameWithType); } - private static final AtomicReferenceFieldUpdater UPDATER = - AtomicReferenceFieldUpdater.newUpdater(DimensionTableDataManager.class, DimensionTable.class, "_dimensionTable"); + private final AtomicReference _dimensionTable = new AtomicReference<>(); + + // Assign a token when loading the lookup table, cancel the loading when token changes because we will load it again + // anyway + private final AtomicInteger _loadToken = new AtomicInteger(); - private volatile DimensionTable _dimensionTable; private boolean _disablePreload = false; + private boolean _errorOnDuplicatePrimaryKey = false; @Override protected void doInit() { @@ -98,14 +104,16 @@ protected void doInit() { DimensionTableConfig dimensionTableConfig = tableConfig.getDimensionTableConfig(); if (dimensionTableConfig != null) { _disablePreload = dimensionTableConfig.isDisablePreload(); + _errorOnDuplicatePrimaryKey = dimensionTableConfig.isErrorOnDuplicatePrimaryKey(); } } if (_disablePreload) { - _dimensionTable = new MemoryOptimizedDimensionTable(schema, primaryKeyColumns, Collections.emptyMap(), - Collections.emptyList(), this); + _dimensionTable.set( + new MemoryOptimizedDimensionTable(schema, primaryKeyColumns, Collections.emptyMap(), Collections.emptyList(), + Collections.emptyList(), this)); } else { - _dimensionTable = new FastLookupDimensionTable(schema, primaryKeyColumns, new HashMap<>()); + _dimensionTable.set(new FastLookupDimensionTable(schema, primaryKeyColumns, new HashMap<>())); } } @@ -113,64 +121,67 @@ protected void doInit() { public void addSegment(ImmutableSegment immutableSegment) { super.addSegment(immutableSegment); String segmentName = immutableSegment.getSegmentName(); - try { - loadLookupTable(); - _logger.info("Successfully loaded lookup table: {} after adding segment: {}", _tableNameWithType, segmentName); - } catch (Exception e) { - throw new RuntimeException( - String.format("Caught exception while loading lookup table: %s after adding segment: %s", _tableNameWithType, - segmentName), e); + if (loadLookupTable()) { + _logger.info("Successfully loaded lookup table after adding segment: {}", segmentName); + } else { + _logger.info("Skip loading lookup table after adding segment: {}, another loading in progress", segmentName); } } @Override - public void removeSegment(String segmentName) { - super.removeSegment(segmentName); - try { - loadLookupTable(); - _logger.info("Successfully loaded lookup table: {} after removing segment: {}", _tableNameWithType, segmentName); - } catch (Exception e) { - throw new RuntimeException( - String.format("Caught exception while loading lookup table: %s after removing segment: %s", - _tableNameWithType, segmentName), e); + protected void doOffloadSegment(String segmentName) { + SegmentDataManager segmentDataManager = unregisterSegment(segmentName); + if (segmentDataManager != null) { + segmentDataManager.offload(); + releaseSegment(segmentDataManager); + _logger.info("Offloaded segment: {}", segmentName); + if (loadLookupTable()) { + _logger.info("Successfully loaded lookup table after offloading segment: {}", segmentName); + } else { + _logger.info("Skip loading lookup table after offloading segment: {}, another loading in progress", + segmentName); + } + } else { + _logger.warn("Failed to find segment: {}, skipping offloading it", segmentName); } } @Override protected void doShutdown() { - closeDimensionTable(_dimensionTable); + releaseAndRemoveAllSegments(); + closeDimensionTable(_dimensionTable.get()); } private void closeDimensionTable(DimensionTable dimensionTable) { try { dimensionTable.close(); } catch (Exception e) { - _logger.warn("Cannot close dimension table: {}", _tableNameWithType, e); + _logger.error("Caught exception while closing the dimension table", e); } } /** * `loadLookupTable()` reads contents of the DimensionTable into _lookupTable HashMap for fast lookup. */ - private void loadLookupTable() { - DimensionTable snapshot; - DimensionTable replacement; - do { - snapshot = _dimensionTable; - if (_disablePreload) { - replacement = createMemOptimisedDimensionTable(); - } else { - replacement = createFastLookupDimensionTable(); - } - } while (!UPDATER.compareAndSet(this, snapshot, replacement)); - - closeDimensionTable(snapshot); + private boolean loadLookupTable() { + DimensionTable dimensionTable = + _disablePreload ? createMemOptimisedDimensionTable() : createFastLookupDimensionTable(); + if (dimensionTable != null) { + closeDimensionTable(_dimensionTable.getAndSet(dimensionTable)); + return true; + } else { + return false; + } } + @Nullable private DimensionTable createFastLookupDimensionTable() { + // Acquire a token in the beginning. Abort the loading and return null when the token changes because another + // loading is in progress. + int token = _loadToken.incrementAndGet(); + Schema schema = ZKMetadataProvider.getTableSchema(_propertyStore, _tableNameWithType); Preconditions.checkState(schema != null, "Failed to find schema for dimension table: %s", _tableNameWithType); - List primaryKeyColumns = schema.getPrimaryKeyColumns(); Preconditions.checkState(CollectionUtils.isNotEmpty(primaryKeyColumns), "Primary key columns must be configured for dimension table: %s", _tableNameWithType); @@ -185,9 +196,18 @@ private DimensionTable createFastLookupDimensionTable() { try (PinotSegmentRecordReader recordReader = new PinotSegmentRecordReader()) { recordReader.init(indexSegment); for (int i = 0; i < numTotalDocs; i++) { + if (_loadToken.get() != token) { + // Token changed during the loading, abort the loading + return null; + } GenericRow row = new GenericRow(); recordReader.getRecord(i, row); - lookupTable.put(row.getPrimaryKey(primaryKeyColumns), row); + GenericRow previousRow = lookupTable.put(row.getPrimaryKey(primaryKeyColumns), row); + if (_errorOnDuplicatePrimaryKey && previousRow != null) { + throw new IllegalStateException( + "Caught exception while reading records from segment: " + indexSegment.getSegmentName() + + "primary key already exist for: " + row.getPrimaryKey(primaryKeyColumns)); + } } } catch (Exception e) { throw new RuntimeException( @@ -203,16 +223,22 @@ private DimensionTable createFastLookupDimensionTable() { } } + @Nullable private DimensionTable createMemOptimisedDimensionTable() { + // Acquire a token in the beginning. Abort the loading and return null when the token changes because another + // loading is in progress. + int token = _loadToken.incrementAndGet(); + Schema schema = ZKMetadataProvider.getTableSchema(_propertyStore, _tableNameWithType); Preconditions.checkState(schema != null, "Failed to find schema for dimension table: %s", _tableNameWithType); - List primaryKeyColumns = schema.getPrimaryKeyColumns(); Preconditions.checkState(CollectionUtils.isNotEmpty(primaryKeyColumns), "Primary key columns must be configured for dimension table: %s", _tableNameWithType); + int numPrimaryKeyColumns = primaryKeyColumns.size(); Map lookupTable = new HashMap<>(); List segmentDataManagers = acquireAllSegments(); + List recordReaders = new ArrayList<>(segmentDataManagers.size()); for (SegmentDataManager segmentManager : segmentDataManagers) { IndexSegment indexSegment = segmentManager.getSegment(); int numTotalDocs = indexSegment.getSegmentMetadata().getTotalDocs(); @@ -220,10 +246,28 @@ private DimensionTable createMemOptimisedDimensionTable() { try { PinotSegmentRecordReader recordReader = new PinotSegmentRecordReader(); recordReader.init(indexSegment); + recordReaders.add(recordReader); for (int i = 0; i < numTotalDocs; i++) { - GenericRow row = new GenericRow(); - recordReader.getRecord(i, row); - lookupTable.put(row.getPrimaryKey(primaryKeyColumns), new LookupRecordLocation(recordReader, i)); + if (_loadToken.get() != token) { + // Token changed during the loading, abort the loading + for (PinotSegmentRecordReader reader : recordReaders) { + try { + reader.close(); + } catch (Exception e) { + _logger.error("Caught exception while closing record reader for segment: {}", reader.getSegmentName(), + e); + } + } + for (SegmentDataManager dataManager : segmentDataManagers) { + releaseSegment(dataManager); + } + return null; + } + Object[] values = new Object[numPrimaryKeyColumns]; + for (int j = 0; j < numPrimaryKeyColumns; j++) { + values[j] = recordReader.getValue(i, primaryKeyColumns.get(j)); + } + lookupTable.put(new PrimaryKey(values), new LookupRecordLocation(recordReader, i)); } } catch (Exception e) { throw new RuntimeException( @@ -231,23 +275,23 @@ private DimensionTable createMemOptimisedDimensionTable() { } } } - return new MemoryOptimizedDimensionTable(schema, primaryKeyColumns, lookupTable, - segmentDataManagers, this); + return new MemoryOptimizedDimensionTable(schema, primaryKeyColumns, lookupTable, segmentDataManagers, recordReaders, + this); } public boolean isPopulated() { - return !_dimensionTable.isEmpty(); + return !_dimensionTable.get().isEmpty(); } public GenericRow lookupRowByPrimaryKey(PrimaryKey pk) { - return _dimensionTable.get(pk); + return _dimensionTable.get().get(pk); } public FieldSpec getColumnFieldSpec(String columnName) { - return _dimensionTable.getFieldSpecFor(columnName); + return _dimensionTable.get().getFieldSpecFor(columnName); } public List getPrimaryKeyColumns() { - return _dimensionTable.getPrimaryKeyColumns(); + return _dimensionTable.get().getPrimaryKeyColumns(); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/FastLookupDimensionTable.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/FastLookupDimensionTable.java index ae6776aba312..81798e4cce5d 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/FastLookupDimensionTable.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/FastLookupDimensionTable.java @@ -18,7 +18,6 @@ */ package org.apache.pinot.core.data.manager.offline; -import java.io.IOException; import java.util.List; import java.util.Map; import org.apache.pinot.spi.data.FieldSpec; @@ -27,9 +26,8 @@ import org.apache.pinot.spi.data.readers.PrimaryKey; -class FastLookupDimensionTable implements DimensionTable { - - private Map _lookupTable; +public class FastLookupDimensionTable implements DimensionTable { + private final Map _lookupTable; private final Schema _tableSchema; private final List _primaryKeyColumns; @@ -61,7 +59,6 @@ public FieldSpec getFieldSpecFor(String columnName) { } @Override - public void close() - throws IOException { + public void close() { } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/ImmutableSegmentDataManager.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/ImmutableSegmentDataManager.java index 71c79b941b62..6dbb66630c12 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/ImmutableSegmentDataManager.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/ImmutableSegmentDataManager.java @@ -44,7 +44,12 @@ public ImmutableSegment getSegment() { } @Override - public void destroy() { + public void doOffload() { + _immutableSegment.offload(); + } + + @Override + protected void doDestroy() { _immutableSegment.destroy(); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/MemoryOptimizedDimensionTable.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/MemoryOptimizedDimensionTable.java index 8f74015f0185..96fe847e545e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/MemoryOptimizedDimensionTable.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/MemoryOptimizedDimensionTable.java @@ -18,11 +18,11 @@ */ package org.apache.pinot.core.data.manager.offline; -import java.io.IOException; import java.util.List; import java.util.Map; import org.apache.pinot.segment.local.data.manager.SegmentDataManager; import org.apache.pinot.segment.local.data.manager.TableDataManager; +import org.apache.pinot.segment.local.segment.readers.PinotSegmentRecordReader; import org.apache.pinot.spi.data.FieldSpec; import org.apache.pinot.spi.data.Schema; import org.apache.pinot.spi.data.readers.GenericRow; @@ -31,23 +31,25 @@ import org.slf4j.LoggerFactory; -class MemoryOptimizedDimensionTable implements DimensionTable { +public class MemoryOptimizedDimensionTable implements DimensionTable { private static final Logger LOGGER = LoggerFactory.getLogger(MemoryOptimizedDimensionTable.class); private final Map _lookupTable; private final Schema _tableSchema; private final List _primaryKeyColumns; - private final GenericRow _reuse = new GenericRow(); + private final ThreadLocal _reuse = ThreadLocal.withInitial(GenericRow::new); private final List _segmentDataManagers; + private final List _recordReaders; private final TableDataManager _tableDataManager; MemoryOptimizedDimensionTable(Schema tableSchema, List primaryKeyColumns, Map lookupTable, List segmentDataManagers, - TableDataManager tableDataManager) { + List recordReaders, TableDataManager tableDataManager) { _tableSchema = tableSchema; _primaryKeyColumns = primaryKeyColumns; _lookupTable = lookupTable; _segmentDataManagers = segmentDataManagers; + _recordReaders = recordReaders; _tableDataManager = tableDataManager; } @@ -62,7 +64,9 @@ public GenericRow get(PrimaryKey pk) { if (lookupRecordLocation == null) { return null; } - return lookupRecordLocation.getRecord(_reuse); + GenericRow reuse = _reuse.get(); + reuse.clear(); + return lookupRecordLocation.getRecord(reuse); } @Override @@ -76,16 +80,14 @@ public FieldSpec getFieldSpecFor(String columnName) { } @Override - public void close() - throws IOException { - for (LookupRecordLocation lookupRecordLocation : _lookupTable.values()) { + public void close() { + for (PinotSegmentRecordReader recordReader : _recordReaders) { try { - lookupRecordLocation.getPinotSegmentRecordReader().close(); + recordReader.close(); } catch (Exception e) { - LOGGER.warn("Cannot close segment record reader", e); + LOGGER.error("Caught exception while closing record reader for segment: {}", recordReader.getSegmentName(), e); } } - for (SegmentDataManager segmentDataManager : _segmentDataManagers) { _tableDataManager.releaseSegment(segmentDataManager); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/OfflineTableDataManager.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/OfflineTableDataManager.java index 7e17da42cbf3..a99712ec3bb0 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/OfflineTableDataManager.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/OfflineTableDataManager.java @@ -19,7 +19,10 @@ package org.apache.pinot.core.data.manager.offline; import javax.annotation.concurrent.ThreadSafe; +import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; import org.apache.pinot.core.data.manager.BaseTableDataManager; +import org.apache.pinot.segment.local.data.manager.SegmentDataManager; +import org.apache.pinot.segment.local.segment.index.loader.IndexLoadingConfig; /** @@ -38,5 +41,24 @@ protected void doStart() { @Override protected void doShutdown() { + releaseAndRemoveAllSegments(); + } + + protected void doAddOnlineSegment(String segmentName) + throws Exception { + SegmentZKMetadata zkMetadata = fetchZKMetadata(segmentName); + IndexLoadingConfig indexLoadingConfig = fetchIndexLoadingConfig(); + indexLoadingConfig.setSegmentTier(zkMetadata.getTier()); + SegmentDataManager segmentDataManager = _segmentDataManagerMap.get(segmentName); + if (segmentDataManager == null) { + addNewOnlineSegment(zkMetadata, indexLoadingConfig); + } else { + replaceSegmentIfCrcMismatch(segmentDataManager, zkMetadata, indexLoadingConfig); + } + } + + @Override + public void addConsumingSegment(String segmentName) { + throw new UnsupportedOperationException("Cannot add CONSUMING segment to OFFLINE table"); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/TableDataManagerProvider.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/TableDataManagerProvider.java index 15f284ae090f..e2b9a190de04 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/TableDataManagerProvider.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/offline/TableDataManagerProvider.java @@ -19,58 +19,74 @@ package org.apache.pinot.core.data.manager.offline; import com.google.common.cache.LoadingCache; +import java.util.Map; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Semaphore; +import java.util.function.Supplier; +import javax.annotation.Nullable; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.helix.HelixManager; -import org.apache.helix.store.zk.ZkHelixPropertyStore; -import org.apache.helix.zookeeper.datamodel.ZNRecord; -import org.apache.pinot.common.metrics.ServerMetrics; import org.apache.pinot.common.restlet.resources.SegmentErrorInfo; import org.apache.pinot.core.data.manager.realtime.RealtimeTableDataManager; import org.apache.pinot.segment.local.data.manager.TableDataManager; -import org.apache.pinot.segment.local.data.manager.TableDataManagerConfig; -import org.apache.pinot.segment.local.data.manager.TableDataManagerParams; +import org.apache.pinot.segment.local.utils.SegmentLocks; import org.apache.pinot.spi.config.instance.InstanceDataManagerConfig; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.stream.StreamConfigProperties; +import org.apache.pinot.spi.utils.CommonConstants; +import org.apache.pinot.spi.utils.IngestionConfigUtils; /** * Factory for {@link TableDataManager}. */ public class TableDataManagerProvider { - private static Semaphore _segmentBuildSemaphore; - private static TableDataManagerParams _tableDataManagerParams; + private final InstanceDataManagerConfig _instanceDataManagerConfig; + private final HelixManager _helixManager; + private final SegmentLocks _segmentLocks; + private final Semaphore _segmentBuildSemaphore; - private TableDataManagerProvider() { + public TableDataManagerProvider(InstanceDataManagerConfig instanceDataManagerConfig, HelixManager helixManager, + SegmentLocks segmentLocks) { + _instanceDataManagerConfig = instanceDataManagerConfig; + _helixManager = helixManager; + _segmentLocks = segmentLocks; + int maxParallelSegmentBuilds = instanceDataManagerConfig.getMaxParallelSegmentBuilds(); + _segmentBuildSemaphore = maxParallelSegmentBuilds > 0 ? new Semaphore(maxParallelSegmentBuilds, true) : null; } - public static void init(InstanceDataManagerConfig instanceDataManagerConfig) { - int maxParallelBuilds = instanceDataManagerConfig.getMaxParallelSegmentBuilds(); - if (maxParallelBuilds > 0) { - _segmentBuildSemaphore = new Semaphore(maxParallelBuilds, true); - } - _tableDataManagerParams = new TableDataManagerParams(instanceDataManagerConfig); + public TableDataManager getTableDataManager(TableConfig tableConfig) { + return getTableDataManager(tableConfig, null, null, () -> true); } - public static TableDataManager getTableDataManager(TableDataManagerConfig tableDataManagerConfig, String instanceId, - ZkHelixPropertyStore propertyStore, ServerMetrics serverMetrics, HelixManager helixManager, - LoadingCache, SegmentErrorInfo> errorCache) { + public TableDataManager getTableDataManager(TableConfig tableConfig, @Nullable ExecutorService segmentPreloadExecutor, + @Nullable LoadingCache, SegmentErrorInfo> errorCache, + Supplier isServerReadyToServeQueries) { TableDataManager tableDataManager; - switch (tableDataManagerConfig.getTableType()) { + switch (tableConfig.getTableType()) { case OFFLINE: - if (tableDataManagerConfig.isDimTable()) { - tableDataManager = DimensionTableDataManager.createInstanceByTableName(tableDataManagerConfig.getTableName()); + if (tableConfig.isDimTable()) { + tableDataManager = DimensionTableDataManager.createInstanceByTableName(tableConfig.getTableName()); } else { tableDataManager = new OfflineTableDataManager(); } break; case REALTIME: - tableDataManager = new RealtimeTableDataManager(_segmentBuildSemaphore); + Map streamConfigMap = IngestionConfigUtils.getStreamConfigMap(tableConfig); + if (Boolean.parseBoolean(streamConfigMap.get(StreamConfigProperties.SERVER_UPLOAD_TO_DEEPSTORE)) + && StringUtils.isEmpty(_instanceDataManagerConfig.getSegmentStoreUri())) { + throw new IllegalStateException(String.format("Table has enabled %s config. But the server has not " + + "configured the segmentstore uri. Configure the server config %s", + StreamConfigProperties.SERVER_UPLOAD_TO_DEEPSTORE, CommonConstants.Server.CONFIG_OF_SEGMENT_STORE_URI)); + } + tableDataManager = new RealtimeTableDataManager(_segmentBuildSemaphore, isServerReadyToServeQueries); break; default: throw new IllegalStateException(); } - tableDataManager.init(tableDataManagerConfig, instanceId, propertyStore, serverMetrics, helixManager, errorCache, - _tableDataManagerParams); + tableDataManager.init(_instanceDataManagerConfig, _helixManager, _segmentLocks, tableConfig, segmentPreloadExecutor, + errorCache); return tableDataManager; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/DefaultSegmentCommitter.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/DefaultSegmentCommitter.java deleted file mode 100644 index 036b01189fed..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/DefaultSegmentCommitter.java +++ /dev/null @@ -1,55 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.data.manager.realtime; - -import java.io.File; -import org.apache.pinot.common.protocols.SegmentCompletionProtocol; -import org.apache.pinot.server.realtime.ServerSegmentCompletionProtocolHandler; -import org.slf4j.Logger; - - -/** - * Sends segmentCommit() to the controller. - * If that succeeds, swap in-memory segment with the one built. - */ -public class DefaultSegmentCommitter implements SegmentCommitter { - private final SegmentCompletionProtocol.Request.Params _params; - private final ServerSegmentCompletionProtocolHandler _protocolHandler; - - private final Logger _segmentLogger; - - public DefaultSegmentCommitter(Logger segmentLogger, ServerSegmentCompletionProtocolHandler protocolHandler, - SegmentCompletionProtocol.Request.Params params) { - _segmentLogger = segmentLogger; - _protocolHandler = protocolHandler; - _params = params; - } - - @Override - public SegmentCompletionProtocol.Response commit( - LLRealtimeSegmentDataManager.SegmentBuildDescriptor segmentBuildDescriptor) { - File segmentTarFile = segmentBuildDescriptor.getSegmentTarFile(); - - SegmentCompletionProtocol.Response response = _protocolHandler.segmentCommit(_params, segmentTarFile); - if (!response.getStatus().equals(SegmentCompletionProtocol.ControllerResponseStatus.COMMIT_SUCCESS)) { - _segmentLogger.warn("Commit failed with response {}", response.toJsonString()); - } - return response; - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/HLRealtimeSegmentDataManager.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/HLRealtimeSegmentDataManager.java deleted file mode 100644 index aa16a3214701..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/HLRealtimeSegmentDataManager.java +++ /dev/null @@ -1,484 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.data.manager.realtime; - -import com.google.common.base.Preconditions; -import com.google.common.util.concurrent.Uninterruptibles; -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TimerTask; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; -import org.apache.commons.io.FileUtils; -import org.apache.pinot.common.metadata.instance.InstanceZKMetadata; -import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; -import org.apache.pinot.common.metrics.ServerGauge; -import org.apache.pinot.common.metrics.ServerMeter; -import org.apache.pinot.common.metrics.ServerMetrics; -import org.apache.pinot.segment.local.indexsegment.mutable.MutableSegmentImpl; -import org.apache.pinot.segment.local.realtime.converter.ColumnIndicesForRealtimeTable; -import org.apache.pinot.segment.local.realtime.converter.RealtimeSegmentConverter; -import org.apache.pinot.segment.local.realtime.impl.RealtimeSegmentConfig; -import org.apache.pinot.segment.local.recordtransformer.CompositeTransformer; -import org.apache.pinot.segment.local.recordtransformer.RecordTransformer; -import org.apache.pinot.segment.local.segment.index.loader.IndexLoadingConfig; -import org.apache.pinot.segment.local.utils.IngestionUtils; -import org.apache.pinot.segment.spi.MutableSegment; -import org.apache.pinot.segment.spi.creator.SegmentVersion; -import org.apache.pinot.spi.config.table.IndexingConfig; -import org.apache.pinot.spi.config.table.TableConfig; -import org.apache.pinot.spi.data.DateTimeFieldSpec; -import org.apache.pinot.spi.data.Schema; -import org.apache.pinot.spi.data.readers.GenericRow; -import org.apache.pinot.spi.metrics.PinotMeter; -import org.apache.pinot.spi.stream.ConsumerPartitionState; -import org.apache.pinot.spi.stream.PartitionLagState; -import org.apache.pinot.spi.stream.StreamConfig; -import org.apache.pinot.spi.stream.StreamConsumerFactory; -import org.apache.pinot.spi.stream.StreamConsumerFactoryProvider; -import org.apache.pinot.spi.stream.StreamLevelConsumer; -import org.apache.pinot.spi.utils.CommonConstants.ConsumerState; -import org.apache.pinot.spi.utils.CommonConstants.Segment.Realtime.Status; -import org.apache.pinot.spi.utils.IngestionConfigUtils; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - - -public class HLRealtimeSegmentDataManager extends RealtimeSegmentDataManager { - private static final Logger LOGGER = LoggerFactory.getLogger(HLRealtimeSegmentDataManager.class); - private final static long ONE_MINUTE_IN_MILLSEC = 1000 * 60; - - private final String _tableNameWithType; - private final String _segmentName; - private final String _timeColumnName; - private final TimeUnit _timeType; - private final RecordTransformer _recordTransformer; - - private final StreamLevelConsumer _streamLevelConsumer; - private final File _resourceTmpDir; - private final MutableSegmentImpl _realtimeSegment; - private final String _tableStreamName; - private final StreamConfig _streamConfig; - - private final long _start = System.currentTimeMillis(); - private long _segmentEndTimeThreshold; - private AtomicLong _lastUpdatedRawDocuments = new AtomicLong(0); - - private volatile boolean _keepIndexing = true; - private volatile boolean _isShuttingDown = false; - - private final TimerTask _segmentStatusTask; - private final ServerMetrics _serverMetrics; - private final RealtimeTableDataManager _notifier; - private Thread _indexingThread; - - private final String _sortedColumn; - private final List _invertedIndexColumns; - private final Logger _segmentLogger; - private final SegmentVersion _segmentVersion; - - private PinotMeter _tableAndStreamRowsConsumed = null; - private PinotMeter _tableRowsConsumed = null; - - // An instance of this class exists only for the duration of the realtime segment that is currently being consumed. - // Once the segment is committed, the segment is handled by OfflineSegmentDataManager - public HLRealtimeSegmentDataManager(final SegmentZKMetadata segmentZKMetadata, final TableConfig tableConfig, - InstanceZKMetadata instanceMetadata, final RealtimeTableDataManager realtimeTableDataManager, - final String resourceDataDir, final IndexLoadingConfig indexLoadingConfig, final Schema schema, - final ServerMetrics serverMetrics) - throws Exception { - super(); - _segmentVersion = indexLoadingConfig.getSegmentVersion(); - _recordTransformer = CompositeTransformer.getDefaultTransformer(tableConfig, schema); - _serverMetrics = serverMetrics; - _segmentName = segmentZKMetadata.getSegmentName(); - _tableNameWithType = tableConfig.getTableName(); - _timeColumnName = tableConfig.getValidationConfig().getTimeColumnName(); - Preconditions - .checkNotNull(_timeColumnName, "Must provide valid timeColumnName in tableConfig for realtime table {}", - _tableNameWithType); - DateTimeFieldSpec dateTimeFieldSpec = schema.getSpecForTimeColumn(_timeColumnName); - Preconditions.checkNotNull(dateTimeFieldSpec, "Must provide field spec for time column {}", _timeColumnName); - _timeType = dateTimeFieldSpec.getFormatSpec().getColumnUnit(); - - List sortedColumns = indexLoadingConfig.getSortedColumns(); - if (sortedColumns.isEmpty()) { - LOGGER.info("RealtimeDataResourceZKMetadata contains no information about sorted column for segment {}", - _segmentName); - _sortedColumn = null; - } else { - String firstSortedColumn = sortedColumns.get(0); - if (schema.hasColumn(firstSortedColumn)) { - LOGGER.info("Setting sorted column name: {} from RealtimeDataResourceZKMetadata for segment {}", - firstSortedColumn, _segmentName); - _sortedColumn = firstSortedColumn; - } else { - LOGGER - .warn("Sorted column name: {} from RealtimeDataResourceZKMetadata is not existed in schema for segment {}.", - firstSortedColumn, _segmentName); - _sortedColumn = null; - } - } - - // Inverted index columns - Set invertedIndexColumns = indexLoadingConfig.getInvertedIndexColumns(); - // We need to add sorted column into inverted index columns because when we convert realtime in memory segment into - // offline segment, we use sorted column's inverted index to maintain the order of the records so that the records - // are sorted on the sorted column. - if (_sortedColumn != null) { - invertedIndexColumns.add(_sortedColumn); - } - _invertedIndexColumns = new ArrayList<>(invertedIndexColumns); - _streamConfig = new StreamConfig(_tableNameWithType, IngestionConfigUtils.getStreamConfigMap(tableConfig)); - - _segmentLogger = LoggerFactory.getLogger( - HLRealtimeSegmentDataManager.class.getName() + "_" + _segmentName + "_" + _streamConfig.getTopicName()); - _segmentLogger.info("Created segment data manager with Sorted column:{}, invertedIndexColumns:{}", _sortedColumn, - invertedIndexColumns); - - _segmentEndTimeThreshold = _start + _streamConfig.getFlushThresholdTimeMillis(); - _resourceTmpDir = new File(resourceDataDir, "_tmp"); - if (!_resourceTmpDir.exists()) { - _resourceTmpDir.mkdirs(); - } - // create and init stream level consumer - StreamConsumerFactory streamConsumerFactory = StreamConsumerFactoryProvider.create(_streamConfig); - String clientId = HLRealtimeSegmentDataManager.class.getSimpleName() + "-" + _streamConfig.getTopicName(); - Set fieldsToRead = IngestionUtils.getFieldsForRecordExtractor(tableConfig.getIngestionConfig(), schema); - _streamLevelConsumer = streamConsumerFactory.createStreamLevelConsumer(clientId, _tableNameWithType, fieldsToRead, - instanceMetadata.getGroupId(_tableNameWithType)); - _streamLevelConsumer.start(); - _tableStreamName = _tableNameWithType + "_" + _streamConfig.getTopicName(); - - IndexingConfig indexingConfig = tableConfig.getIndexingConfig(); - if (indexingConfig != null && indexingConfig.isAggregateMetrics()) { - LOGGER.warn("Updating of metrics only supported for LLC consumer, ignoring."); - } - - // lets create a new realtime segment - _segmentLogger.info("Started {} stream provider", _streamConfig.getType()); - final int capacity = _streamConfig.getFlushThresholdRows(); - RealtimeSegmentConfig realtimeSegmentConfig = - new RealtimeSegmentConfig.Builder().setTableNameWithType(_tableNameWithType).setSegmentName(_segmentName) - .setStreamName(_streamConfig.getTopicName()).setSchema(schema).setTimeColumnName(_timeColumnName) - .setCapacity(capacity).setAvgNumMultiValues(indexLoadingConfig.getRealtimeAvgMultiValueCount()) - .setNoDictionaryColumns(indexLoadingConfig.getNoDictionaryColumns()) - .setVarLengthDictionaryColumns(indexLoadingConfig.getVarLengthDictionaryColumns()) - .setInvertedIndexColumns(invertedIndexColumns).setSegmentZKMetadata(segmentZKMetadata) - .setOffHeap(indexLoadingConfig.isRealtimeOffHeapAllocation()).setMemoryManager( - getMemoryManager(realtimeTableDataManager.getConsumerDir(), _segmentName, - indexLoadingConfig.isRealtimeOffHeapAllocation(), - indexLoadingConfig.isDirectRealtimeOffHeapAllocation(), serverMetrics)) - .setStatsHistory(realtimeTableDataManager.getStatsHistory()) - .setNullHandlingEnabled(indexingConfig.isNullHandlingEnabled()).build(); - _realtimeSegment = new MutableSegmentImpl(realtimeSegmentConfig, serverMetrics); - - _notifier = realtimeTableDataManager; - - LOGGER.info("Starting consumption on realtime consuming segment {} maxRowCount {} maxEndTime {}", _segmentName, - capacity, new DateTime(_segmentEndTimeThreshold, DateTimeZone.UTC).toString()); - _segmentStatusTask = new TimerTask() { - @Override - public void run() { - computeKeepIndexing(); - } - }; - - // start the indexing thread - _indexingThread = new Thread(new Runnable() { - @Override - public void run() { - // continue indexing until criteria is met - boolean notFull = true; - long exceptionSleepMillis = 50L; - _segmentLogger.info("Starting to collect rows"); - - int numRowsErrored = 0; - GenericRow reuse = new GenericRow(); - do { - reuse.clear(); - try { - GenericRow consumedRow; - try { - consumedRow = _streamLevelConsumer.next(reuse); - _tableAndStreamRowsConsumed = serverMetrics - .addMeteredTableValue(_tableStreamName, ServerMeter.REALTIME_ROWS_CONSUMED, 1L, - _tableAndStreamRowsConsumed); - _tableRowsConsumed = - serverMetrics.addMeteredGlobalValue(ServerMeter.REALTIME_ROWS_CONSUMED, 1L, _tableRowsConsumed); - } catch (Exception e) { - _segmentLogger.warn("Caught exception while consuming row, sleeping for {} ms", exceptionSleepMillis, e); - numRowsErrored++; - serverMetrics.addMeteredTableValue(_tableStreamName, ServerMeter.REALTIME_CONSUMPTION_EXCEPTIONS, 1L); - serverMetrics.addMeteredGlobalValue(ServerMeter.REALTIME_CONSUMPTION_EXCEPTIONS, 1L); - - // Sleep for a short time as to avoid filling the logs with exceptions too quickly - Uninterruptibles.sleepUninterruptibly(exceptionSleepMillis, TimeUnit.MILLISECONDS); - exceptionSleepMillis = Math.min(60000L, exceptionSleepMillis * 2); - continue; - } - if (consumedRow != null) { - try { - GenericRow transformedRow = _recordTransformer.transform(consumedRow); - // FIXME: handle MULTIPLE_RECORDS_KEY for HLL - if (transformedRow != null && IngestionUtils.shouldIngestRow(transformedRow)) { - // we currently do not get ingestion data through stream-consumer - notFull = _realtimeSegment.index(transformedRow, null); - exceptionSleepMillis = 50L; - } - } catch (Exception e) { - _segmentLogger.warn("Caught exception while indexing row, sleeping for {} ms, row contents {}", - exceptionSleepMillis, consumedRow, e); - numRowsErrored++; - - // Sleep for a short time as to avoid filling the logs with exceptions too quickly - Uninterruptibles.sleepUninterruptibly(exceptionSleepMillis, TimeUnit.MILLISECONDS); - exceptionSleepMillis = Math.min(60000L, exceptionSleepMillis * 2); - } - } - } catch (Error e) { - _segmentLogger.error("Caught error in indexing thread", e); - throw e; - } - } while (notFull && _keepIndexing && (!_isShuttingDown)); - - if (_isShuttingDown) { - _segmentLogger.info("Shutting down indexing thread!"); - return; - } - try { - if (numRowsErrored > 0) { - serverMetrics.addMeteredTableValue(_tableStreamName, ServerMeter.ROWS_WITH_ERRORS, numRowsErrored); - } - _segmentLogger.info("Indexing threshold reached, proceeding with index conversion"); - // kill the timer first - _segmentStatusTask.cancel(); - updateCurrentDocumentCountMetrics(); - _segmentLogger.info("Indexed {} raw events", _realtimeSegment.getNumDocsIndexed()); - File tempSegmentFolder = new File(_resourceTmpDir, "tmp-" + System.currentTimeMillis()); - ColumnIndicesForRealtimeTable columnIndicesForRealtimeTable = - new ColumnIndicesForRealtimeTable(_sortedColumn, _invertedIndexColumns, Collections.emptyList(), - Collections.emptyList(), new ArrayList<>(indexLoadingConfig.getNoDictionaryColumns()), - new ArrayList<>(indexLoadingConfig.getVarLengthDictionaryColumns())); - // lets convert the segment now - RealtimeSegmentConverter converter = - new RealtimeSegmentConverter(_realtimeSegment, null, tempSegmentFolder.getAbsolutePath(), - schema, _tableNameWithType, tableConfig, segmentZKMetadata.getSegmentName(), - columnIndicesForRealtimeTable, indexingConfig.isNullHandlingEnabled()); - - _segmentLogger.info("Trying to build segment"); - final long buildStartTime = System.nanoTime(); - converter.build(_segmentVersion, serverMetrics); - final long buildEndTime = System.nanoTime(); - _segmentLogger.info("Built segment in {} ms", - TimeUnit.MILLISECONDS.convert((buildEndTime - buildStartTime), TimeUnit.NANOSECONDS)); - File destDir = new File(resourceDataDir, segmentZKMetadata.getSegmentName()); - FileUtils.deleteQuietly(destDir); - FileUtils.moveDirectory(tempSegmentFolder.listFiles()[0], destDir); - - FileUtils.deleteQuietly(tempSegmentFolder); - long segStartTime = _realtimeSegment.getMinTime(); - long segEndTime = _realtimeSegment.getMaxTime(); - - _segmentLogger.info("Committing {} offsets", _streamConfig.getType()); - boolean commitSuccessful = false; - try { - _streamLevelConsumer.commit(); - commitSuccessful = true; - _streamLevelConsumer.shutdown(); - _segmentLogger - .info("Successfully committed {} offsets, consumer release requested.", _streamConfig.getType()); - serverMetrics.addMeteredTableValue(_tableStreamName, ServerMeter.REALTIME_OFFSET_COMMITS, 1L); - serverMetrics.addMeteredGlobalValue(ServerMeter.REALTIME_OFFSET_COMMITS, 1L); - } catch (Throwable e) { - // If we got here, it means that either the commit or the shutdown failed. Considering that the - // KafkaConsumerManager delays shutdown and only adds the consumer to be released in a deferred way, this - // likely means that writing the Kafka offsets failed. - // - // The old logic (mark segment as done, then commit offsets and shutdown the consumer immediately) would die - // in a terrible way, leaving the consumer open and causing us to only get half the records from that point - // on. In this case, because we keep the consumer open for a little while, we should be okay if the - // controller reassigns us a new segment before the consumer gets released. Hopefully by the next time that - // we get to committing the offsets, the transient ZK failure that caused the write to fail will not - // happen again and everything will be good. - // - // Several things can happen: - // - The controller reassigns us a new segment before we release the consumer (KafkaConsumerManager will - // keep the consumer open for about a minute, which should be enough time for the controller to reassign - // us a new segment) and the next time we close the segment the offsets commit successfully; we're good. - // - The controller reassigns us a new segment, but after we released the consumer (if the controller was - // down or there was a ZK failure on writing the Kafka offsets but not the Helix state). We lose whatever - // data was in this segment. Not good. - // - The server crashes after this comment and before we mark the current segment as done; if the Kafka - // offsets didn't get written, then when the server restarts it'll start consuming the current segment - // from the previously committed offsets; we're good. - // - The server crashes after this comment, the Kafka offsets were written but the segment wasn't marked as - // done in Helix, but we got a failure (or not) on the commit; we lose whatever data was in this segment - // if we restart the server (not good). If we manually mark the segment as done in Helix by editing the - // state in ZK, everything is good, we'll consume a new segment that starts from the correct offsets. - // - // This is still better than the previous logic, which would have these failure modes: - // - Consumer was left open and the controller reassigned us a new segment; consume only half the events - // (because there are two consumers and Kafka will try to rebalance partitions between those two) - // - We got a segment assigned to us before we got around to committing the offsets, reconsume the data that - // we got in this segment again, as we're starting consumption from the previously committed offset (eg. - // duplicate data). - // - // This is still not very satisfactory, which is why this part is due for a redesign. - // - // Assuming you got here because the realtime offset commit metric has fired, check the logs to determine - // which of the above scenarios happened. If you're in one of the good scenarios, then there's nothing to - // do. If you're not, then based on how critical it is to get those rows back, then your options are: - // - Wipe the realtime table and reconsume everything (mark the replica as disabled so that clients don't - // see query results from partially consumed data, then re-enable it when this replica has caught up) - // - Accept that those rows are gone in this replica and move on (they'll be replaced by good offline data - // soon anyway) - // - If there's a replica that has consumed properly, you could shut it down, copy its segments onto this - // replica, assign a new consumer group id to this replica, rename the copied segments and edit their - // metadata to reflect the new consumer group id, copy the Kafka offsets from the shutdown replica onto - // the new consumer group id and then restart both replicas. This should get you the missing rows. - - _segmentLogger - .error("FATAL: Exception committing or shutting down consumer commitSuccessful={}", commitSuccessful, - e); - serverMetrics.addMeteredTableValue(_tableNameWithType, ServerMeter.REALTIME_OFFSET_COMMIT_EXCEPTIONS, 1L); - if (!commitSuccessful) { - _streamLevelConsumer.shutdown(); - } - } - - try { - _segmentLogger.info("Marking current segment as completed in Helix"); - SegmentZKMetadata metadataToOverwrite = new SegmentZKMetadata(segmentZKMetadata.getSegmentName()); - metadataToOverwrite.setStatus(Status.DONE); - metadataToOverwrite.setStartTime(segStartTime); - metadataToOverwrite.setEndTime(segEndTime); - metadataToOverwrite.setTimeUnit(_timeType); - metadataToOverwrite.setTotalDocs(_realtimeSegment.getNumDocsIndexed()); - _notifier.replaceHLSegment(metadataToOverwrite, indexLoadingConfig); - _segmentLogger - .info("Completed write of segment completion to Helix, waiting for controller to assign a new segment"); - } catch (Exception e) { - if (commitSuccessful) { - _segmentLogger.error( - "Offsets were committed to Kafka but we were unable to mark this segment as completed in Helix. " - + "Manually mark the segment as completed in Helix; restarting this instance will result in " - + "data loss.", e); - } else { - _segmentLogger.warn( - "Caught exception while marking segment as completed in Helix. Offsets were not written, restarting" - + " the instance should be safe.", e); - } - } - } catch (Exception e) { - _segmentLogger.error("Caught exception in the realtime indexing thread", e); - } - } - }); - - _indexingThread.start(); - serverMetrics.addValueToTableGauge(_tableNameWithType, ServerGauge.SEGMENT_COUNT, 1L); - _segmentLogger.debug("scheduling keepIndexing timer check"); - // start a schedule timer to keep track of the segment - TimerService.TIMER.schedule(_segmentStatusTask, ONE_MINUTE_IN_MILLSEC, ONE_MINUTE_IN_MILLSEC); - _segmentLogger.info("finished scheduling keepIndexing timer check"); - } - - @Override - public MutableSegment getSegment() { - return _realtimeSegment; - } - - @Override - public Map getPartitionToCurrentOffset() { - throw new UnsupportedOperationException(); - } - - @Override - public ConsumerState getConsumerState() { - throw new UnsupportedOperationException(); - } - - @Override - public long getLastConsumedTimestamp() { - throw new UnsupportedOperationException(); - } - - @Override - public Map getConsumerPartitionState() { - throw new UnsupportedOperationException(); - } - - @Override - public Map getPartitionToLagState( - Map consumerPartitionStateMap) { - throw new UnsupportedOperationException(); - } - - @Override - public String getSegmentName() { - return _segmentName; - } - - private void computeKeepIndexing() { - if (_keepIndexing) { - _segmentLogger.debug("Current indexed {} raw events", _realtimeSegment.getNumDocsIndexed()); - if ((System.currentTimeMillis() >= _segmentEndTimeThreshold) - || _realtimeSegment.getNumDocsIndexed() >= _streamConfig.getFlushThresholdRows()) { - if (_realtimeSegment.getNumDocsIndexed() == 0) { - _segmentLogger.info("no new events coming in, extending the end time by another hour"); - _segmentEndTimeThreshold = System.currentTimeMillis() + _streamConfig.getFlushThresholdTimeMillis(); - return; - } - _segmentLogger.info( - "Stopped indexing due to reaching segment limit: {} raw documents indexed, segment is aged {} minutes", - _realtimeSegment.getNumDocsIndexed(), ((System.currentTimeMillis() - _start) / (ONE_MINUTE_IN_MILLSEC))); - _keepIndexing = false; - } - } - updateCurrentDocumentCountMetrics(); - } - - private void updateCurrentDocumentCountMetrics() { - int currentRawDocs = _realtimeSegment.getNumDocsIndexed(); - _serverMetrics.addValueToTableGauge(_tableNameWithType, ServerGauge.DOCUMENT_COUNT, - (currentRawDocs - _lastUpdatedRawDocuments.get())); - _lastUpdatedRawDocuments.set(currentRawDocs); - } - - @Override - public void destroy() { - LOGGER.info("Trying to shutdown RealtimeSegmentDataManager : {}!", _segmentName); - _isShuttingDown = true; - try { - _streamLevelConsumer.shutdown(); - } catch (Exception e) { - LOGGER.error("Failed to shutdown stream consumer!", e); - } - _keepIndexing = false; - _segmentStatusTask.cancel(); - _realtimeSegment.destroy(); - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/IdleTimer.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/IdleTimer.java new file mode 100644 index 000000000000..bb44bdaae1dd --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/IdleTimer.java @@ -0,0 +1,71 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.data.manager.realtime; + +/** + * The IdleTimer is responsible for keeping track of 2 different idle times: + * - The stream idle time which resets every time we remake the stream consumer. + * This depends on the user configured "idle.timeout.millis" stream config. + * - the total idle time which only resets when we consume something. + */ +public class IdleTimer { + + private volatile long _timeWhenStreamLastCreatedOrConsumedMs = 0; + private volatile long _timeWhenEventLastConsumedMs = 0; + + public IdleTimer() { + } + + protected long now() { + return System.currentTimeMillis(); + } + + public void init() { + long nowMs = now(); + // When an event is consumed, we consider the stream no longer idle. + // Event consumption idleness, should always be greater than stream + // idleness since we recreate the stream after some amount of idleness, + // but that does not guarantee we'll consume an event. + _timeWhenStreamLastCreatedOrConsumedMs = nowMs; + _timeWhenEventLastConsumedMs = nowMs; + } + + public void markStreamCreated() { + _timeWhenStreamLastCreatedOrConsumedMs = now(); + } + + public void markEventConsumed() { + init(); + } + + public long getTimeSinceStreamLastCreatedOrConsumedMs() { + if (_timeWhenStreamLastCreatedOrConsumedMs == 0) { + return 0; + } + return now() - _timeWhenStreamLastCreatedOrConsumedMs; + } + + public long getTimeSinceEventLastConsumedMs() { + if (_timeWhenEventLastConsumedMs == 0) { + return 0; + } + return now() - _timeWhenEventLastConsumedMs; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/IngestionDelayTracker.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/IngestionDelayTracker.java new file mode 100644 index 000000000000..eed130270845 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/IngestionDelayTracker.java @@ -0,0 +1,451 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.data.manager.realtime; + +import com.google.common.annotations.VisibleForTesting; +import java.time.Clock; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; +import org.apache.pinot.common.metrics.ServerGauge; +import org.apache.pinot.common.metrics.ServerMetrics; +import org.apache.pinot.spi.stream.LongMsgOffset; +import org.apache.pinot.spi.stream.StreamPartitionMsgOffset; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * A Class to track realtime ingestion delay for table partitions on a given server. + * Highlights: + * 1-An object of this class is hosted by each RealtimeTableDataManager. + * 2-The object tracks ingestion delays for all partitions hosted by the current server for the given Realtime table. + * 3-Partition delays are updated by all RealtimeSegmentDataManager objects hosted in the corresponding + * RealtimeTableDataManager. + * 4-Individual metrics are associated with each partition being tracked. + * 5-Delays for partitions that do not have events to consume are reported as zero. + * 6-Partitions whose Segments go from CONSUMING to DROPPED state stop being tracked so their delays do not cloud + * delays of active partitions. + * 7-When a segment goes from CONSUMING to ONLINE, we start a timeout for the corresponding partition. + * If no consumption is noticed after the timeout, we then read ideal state to confirm the server still hosts the + * partition. If not, we stop tracking the respective partition. + * 8-A scheduled executor thread is started by this object to track timeouts of partitions and drive the reading + * of their ideal state. + * + * The following diagram illustrates the object interactions with main external APIs + * + * (CONSUMING -> ONLINE state change) + * | + * markPartitionForConfirmation(partitionId) + * | |<-updateIngestionDelay()-{RealtimeSegmentDataManager(Partition 0}} + * | | + * ___________V_________________________V_ + * | (Table X) |<-updateIngestionDelay()-{RealtimeSegmentDataManager(Partition 1}} + * | IngestionDelayTracker | ... + * |____________________________________|<-updateIngestionDelay()-{RealtimeSegmentDataManager (Partition n}} + * ^ ^ + * | \ + * timeoutInactivePartitions() stopTrackingPartitionIngestionDelay(partitionId) + * _________|__________ \ + * | TimerTrackingTask | (CONSUMING -> DROPPED state change) + * |___________________| + * + * TODO: handle bug situations like the one where a partition is not allocated to a given server due to a bug. + */ + +public class IngestionDelayTracker { + + // Class to wrap supported timestamps collected for an ingested event + private static class IngestionTimestamps { + private final long _firstStreamIngestionTimeMs; + private final long _ingestionTimeMs; + IngestionTimestamps(long ingestionTimesMs, long firstStreamIngestionTimeMs) { + _ingestionTimeMs = ingestionTimesMs; + _firstStreamIngestionTimeMs = firstStreamIngestionTimeMs; + } + } + + private static class IngestionOffsets { + private final StreamPartitionMsgOffset _latestOffset; + private final StreamPartitionMsgOffset _offset; + IngestionOffsets(StreamPartitionMsgOffset offset, StreamPartitionMsgOffset latestOffset) { + _offset = offset; + _latestOffset = latestOffset; + } + } + + // Sleep interval for scheduled executor service thread that triggers read of ideal state + private static final int SCHEDULED_EXECUTOR_THREAD_TICK_INTERVAL_MS = 300000; // 5 minutes +/- precision in timeouts + // Once a partition is marked for verification, we wait 10 minutes to pull its ideal state. + private static final int PARTITION_TIMEOUT_MS = 600000; // 10 minutes timeouts + // Delay scheduled executor service for this amount of time after starting service + private static final int INITIAL_SCHEDULED_EXECUTOR_THREAD_DELAY_MS = 100; + private static final Logger _logger = LoggerFactory.getLogger(IngestionDelayTracker.class.getSimpleName()); + + // HashMap used to store ingestion time measures for all partitions active for the current table. + private final Map _partitionToIngestionTimestampsMap = new ConcurrentHashMap<>(); + + private final Map _partitionToOffsetMap = new ConcurrentHashMap<>(); + // We mark partitions that go from CONSUMING to ONLINE in _partitionsMarkedForVerification: if they do not + // go back to CONSUMING in some period of time, we verify whether they are still hosted in this server by reading + // ideal state. This is done with the goal of minimizing reading ideal state for efficiency reasons. + private final Map _partitionsMarkedForVerification = new ConcurrentHashMap<>(); + + final int _scheduledExecutorThreadTickIntervalMs; + // TODO: Make thread pool a server/cluster level config + // ScheduledExecutorService to check partitions that are inactive against ideal state. + private final ScheduledExecutorService _scheduledExecutor = Executors.newScheduledThreadPool(2); + + private final ServerMetrics _serverMetrics; + private final String _tableNameWithType; + private final String _metricName; + + private final RealtimeTableDataManager _realTimeTableDataManager; + private final Supplier _isServerReadyToServeQueries; + + private Clock _clock; + + public IngestionDelayTracker(ServerMetrics serverMetrics, String tableNameWithType, + RealtimeTableDataManager realtimeTableDataManager, int scheduledExecutorThreadTickIntervalMs, + Supplier isServerReadyToServeQueries) + throws RuntimeException { + _serverMetrics = serverMetrics; + _tableNameWithType = tableNameWithType; + _metricName = tableNameWithType; + _realTimeTableDataManager = realtimeTableDataManager; + _clock = Clock.systemUTC(); + _isServerReadyToServeQueries = isServerReadyToServeQueries; + // Handle negative timer values + if (scheduledExecutorThreadTickIntervalMs <= 0) { + throw new RuntimeException(String.format("Illegal timer timeout argument, expected > 0, got=%d for table=%s", + scheduledExecutorThreadTickIntervalMs, _tableNameWithType)); + } + _scheduledExecutorThreadTickIntervalMs = scheduledExecutorThreadTickIntervalMs; + + // ThreadFactory to set the thread's name + ThreadFactory threadFactory = new ThreadFactory() { + private final ThreadFactory _defaultFactory = Executors.defaultThreadFactory(); + + @Override + public Thread newThread(Runnable r) { + Thread thread = _defaultFactory.newThread(r); + thread.setName("IngestionDelayTimerThread-" + TableNameBuilder.extractRawTableName(tableNameWithType)); + return thread; + } + }; + ((ScheduledThreadPoolExecutor) _scheduledExecutor).setThreadFactory(threadFactory); + + _scheduledExecutor.scheduleWithFixedDelay(this::timeoutInactivePartitions, + INITIAL_SCHEDULED_EXECUTOR_THREAD_DELAY_MS, _scheduledExecutorThreadTickIntervalMs, TimeUnit.MILLISECONDS); + } + + public IngestionDelayTracker(ServerMetrics serverMetrics, String tableNameWithType, + RealtimeTableDataManager tableDataManager, Supplier isServerReadyToServeQueries) { + this(serverMetrics, tableNameWithType, tableDataManager, SCHEDULED_EXECUTOR_THREAD_TICK_INTERVAL_MS, + isServerReadyToServeQueries); + } + + /* + * Helper function to get the ingestion delay for a given ingestion time. + * Ingestion delay == Current Time - Ingestion Time + * + * @param ingestionTimeMs original ingestion time in milliseconds. + */ + private long getIngestionDelayMs(long ingestionTimeMs) { + if (ingestionTimeMs < 0) { + return 0; + } + // Compute aged delay for current partition + long agedIngestionDelayMs = _clock.millis() - ingestionTimeMs; + // Correct to zero for any time shifts due to NTP or time reset. + agedIngestionDelayMs = Math.max(agedIngestionDelayMs, 0); + return agedIngestionDelayMs; + } + + private long getPartitionOffsetLag(IngestionOffsets offset) { + if (offset == null) { + return 0; + } + StreamPartitionMsgOffset currentOffset = offset._offset; + StreamPartitionMsgOffset latestOffset = offset._latestOffset; + + // Compute aged delay for current partition + // TODO: Support other types of offsets + if (!(currentOffset instanceof LongMsgOffset && latestOffset instanceof LongMsgOffset)) { + return 0; + } + + return ((LongMsgOffset) latestOffset).getOffset() - ((LongMsgOffset) currentOffset).getOffset(); + } + + /* + * Helper function to be called when we should stop tracking a given partition. Removes the partition from + * all our maps. + * + * @param partitionGroupId partition ID which we should stop tracking. + */ + private void removePartitionId(int partitionGroupId) { + _partitionToIngestionTimestampsMap.remove(partitionGroupId); + _partitionToOffsetMap.remove(partitionGroupId); + // If we are removing a partition we should stop reading its ideal state. + _partitionsMarkedForVerification.remove(partitionGroupId); + _serverMetrics.removePartitionGauge(_metricName, partitionGroupId, ServerGauge.REALTIME_INGESTION_DELAY_MS); + _serverMetrics.removePartitionGauge(_metricName, partitionGroupId, + ServerGauge.END_TO_END_REALTIME_INGESTION_DELAY_MS); + _serverMetrics.removePartitionGauge(_metricName, partitionGroupId, ServerGauge.REALTIME_INGESTION_OFFSET_LAG); + } + + /* + * Helper functions that creates a list of all the partitions that are marked for verification and whose + * timeouts are expired. This helps us optimize checks of the ideal state. + */ + private List getPartitionsToBeVerified() { + List partitionsToVerify = new ArrayList<>(); + for (Map.Entry entry : _partitionsMarkedForVerification.entrySet()) { + long timeMarked = _clock.millis() - entry.getValue(); + if (timeMarked > PARTITION_TIMEOUT_MS) { + // Partition must be verified + partitionsToVerify.add(entry.getKey()); + } + } + return partitionsToVerify; + } + + + /** + * Function that enable use to set predictable clocks for testing purposes. + * + * @param clock clock to be used by the class + */ + @VisibleForTesting + void setClock(Clock clock) { + _clock = clock; + } + + /** + * Called by RealTimeSegmentDataManagers to update the ingestion delay metrics for a given partition. + * + * @param ingestionTimeMs ingestion time being recorded. + * @param firstStreamIngestionTimeMs time the event was ingested in the first stage of the ingestion pipeline. + * @param msgOffset message offset of the event being ingested. + * @param latestOffset latest message offset in the stream. + * @param partitionGroupId partition ID for which the ingestion metrics are being recorded. + */ + public void updateIngestionMetrics(long ingestionTimeMs, long firstStreamIngestionTimeMs, + StreamPartitionMsgOffset msgOffset, StreamPartitionMsgOffset latestOffset, + int partitionGroupId) { + if (!_isServerReadyToServeQueries.get() || _realTimeTableDataManager.isShutDown()) { + // Do not update the ingestion delay metrics during server startup period + // or once the table data manager has been shutdown. + return; + } + + updateIngestionDelay(ingestionTimeMs, firstStreamIngestionTimeMs, partitionGroupId); + updateIngestionOffsets(msgOffset, latestOffset, partitionGroupId); + + // If we are consuming we do not need to track this partition for removal. + _partitionsMarkedForVerification.remove(partitionGroupId); + } + + public void updateIngestionDelay(long ingestionTimeMs, long firstStreamIngestionTimeMs, int partitionGroupId) { + if ((ingestionTimeMs < 0) && (firstStreamIngestionTimeMs < 0)) { + // If stream does not return a valid ingestion timestamps don't publish a metric + return; + } + IngestionTimestamps previousMeasure = _partitionToIngestionTimestampsMap.put(partitionGroupId, + new IngestionTimestamps(ingestionTimeMs, firstStreamIngestionTimeMs)); + if (previousMeasure == null) { + // First time we start tracking a partition we should start tracking it via metric + // Only publish the metric if supported by the underlying stream. If not supported the stream + // returns Long.MIN_VALUE + if (ingestionTimeMs >= 0) { + _serverMetrics.setOrUpdatePartitionGauge(_metricName, partitionGroupId, ServerGauge.REALTIME_INGESTION_DELAY_MS, + () -> getPartitionIngestionDelayMs(partitionGroupId)); + } + if (firstStreamIngestionTimeMs >= 0) { + // Only publish this metric when creation time is supported by the underlying stream + // When this timestamp is not supported it always returns the value Long.MIN_VALUE + _serverMetrics.setOrUpdatePartitionGauge(_metricName, partitionGroupId, + ServerGauge.END_TO_END_REALTIME_INGESTION_DELAY_MS, + () -> getPartitionEndToEndIngestionDelayMs(partitionGroupId)); + } + } + } + + public void updateIngestionOffsets(StreamPartitionMsgOffset currentOffset, StreamPartitionMsgOffset latestOffset, + int partitionGroupId) { + if ((currentOffset == null)) { + // If stream does not return a valid ingestion offset don't publish a metric + return; + } + IngestionOffsets previousMeasure = + _partitionToOffsetMap.put(partitionGroupId, new IngestionOffsets(currentOffset, latestOffset)); + if (previousMeasure == null) { + // First time we start tracking a partition we should start tracking it via metric + // Only publish the metric if supported by the underlying stream. If not supported the stream + // returns Long.MIN_VALUE + if (currentOffset != null) { + _serverMetrics.setOrUpdatePartitionGauge(_metricName, partitionGroupId, + ServerGauge.REALTIME_INGESTION_OFFSET_LAG, () -> getPartitionIngestionOffsetLag(partitionGroupId)); + } + } + } + + /* + * Handle partition removal event. This must be invoked when we stop serving a given partition for + * this table in the current server. + * + * @param partitionGroupId partition id that we should stop tracking. + */ + public void stopTrackingPartitionIngestionDelay(int partitionGroupId) { + removePartitionId(partitionGroupId); + } + + /* + * This method is used for timing out inactive partitions, so we don't display their metrics on current server. + * When the inactive time exceeds some threshold, we read from ideal state to confirm we still host the partition, + * if not we remove the partition from being tracked locally. + * This call is to be invoked by a scheduled executor thread that will periodically wake up and invoke this function. + */ + public void timeoutInactivePartitions() { + if (!_isServerReadyToServeQueries.get()) { + // Do not update the tracker state during server startup period + return; + } + // Check if we have any partition to verify, else don't make the call to check ideal state as that + // involves network traffic and may be inefficient. + List partitionsToVerify = getPartitionsToBeVerified(); + if (partitionsToVerify.size() == 0) { + // Don't make the call to getHostedPartitionsGroupIds() as it involves checking ideal state. + return; + } + Set partitionsHostedByThisServer; + try { + partitionsHostedByThisServer = _realTimeTableDataManager.getHostedPartitionsGroupIds(); + } catch (Exception e) { + _logger.error("Failed to get partitions hosted by this server, table={}, exception={}:{}", _tableNameWithType, + e.getClass(), e.getMessage()); + return; + } + for (int partitionGroupId : partitionsToVerify) { + if (!partitionsHostedByThisServer.contains(partitionGroupId)) { + // Partition is not hosted in this server anymore, stop tracking it + removePartitionId(partitionGroupId); + } + } + } + + /* + * This function is invoked when a partition goes from CONSUMING to ONLINE, so we can assert whether the + * partition is still hosted by this server after some interval of time. + * + * @param partitionGroupId Partition id that we need confirmed via ideal state as still hosted by this server. + */ + public void markPartitionForVerification(int partitionGroupId) { + if (!_isServerReadyToServeQueries.get()) { + // Do not update the tracker state during server startup period + return; + } + _partitionsMarkedForVerification.put(partitionGroupId, _clock.millis()); + } + + /* + * Method to get timestamp used for the ingestion delay for a given partition. + * + * @param partitionGroupId partition for which we are retrieving the delay + * + * @return ingestion delay timestamp in milliseconds for the given partition ID. + */ + public long getPartitionIngestionTimeMs(int partitionGroupId) { + // Not protected as this will only be invoked when metric is installed which happens after server ready + IngestionTimestamps currentMeasure = _partitionToIngestionTimestampsMap.get(partitionGroupId); + if (currentMeasure == null) { // Guard just in case we read the metric without initializing it + return Long.MIN_VALUE; + } + return currentMeasure._ingestionTimeMs; + } + + /* + * Method to get ingestion delay for a given partition. + * + * @param partitionGroupId partition for which we are retrieving the delay + * + * @return ingestion delay in milliseconds for the given partition ID. + */ + public long getPartitionIngestionDelayMs(int partitionGroupId) { + // Not protected as this will only be invoked when metric is installed which happens after server ready + IngestionTimestamps currentMeasure = _partitionToIngestionTimestampsMap.get(partitionGroupId); + if (currentMeasure == null) { // Guard just in case we read the metric without initializing it + return 0; + } + return getIngestionDelayMs(currentMeasure._ingestionTimeMs); + } + + public long getPartitionIngestionOffsetLag(int partitionGroupId) { + // Not protected as this will only be invoked when metric is installed which happens after server ready + IngestionOffsets currentMeasure = _partitionToOffsetMap.get(partitionGroupId); + if (currentMeasure == null) { // Guard just in case we read the metric without initializing it + return 0; + } + return getPartitionOffsetLag(currentMeasure); + } + + /* + * Method to get end to end ingestion delay for a given partition. + * + * @param partitionGroupId partition for which we are retrieving the delay + * + * @return End to end ingestion delay in milliseconds for the given partition ID. + */ + public long getPartitionEndToEndIngestionDelayMs(int partitionGroupId) { + // Not protected as this will only be invoked when metric is installed which happens after server ready + IngestionTimestamps currentMeasure = _partitionToIngestionTimestampsMap.get(partitionGroupId); + if (currentMeasure == null) { // Guard just in case we read the metric without initializing it + return 0; + } + return getIngestionDelayMs(currentMeasure._firstStreamIngestionTimeMs); + } + + /* + * We use this method to clean up when a table is being removed. No updates are expected at this time as all + * RealtimeSegmentManagers should be down now. + */ + public void shutdown() { + // Now that segments can't report metric, destroy metric for this table + _scheduledExecutor.shutdown(); // ScheduledExecutor is installed in constructor so must always be cancelled + if (!_isServerReadyToServeQueries.get()) { + // Do not update the tracker state during server startup period + return; + } + // Remove partitions so their related metrics get uninstalled. + for (Map.Entry entry : _partitionToIngestionTimestampsMap.entrySet()) { + removePartitionId(entry.getKey()); + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/LLRealtimeSegmentDataManager.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/LLRealtimeSegmentDataManager.java deleted file mode 100644 index b720f486bb7f..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/LLRealtimeSegmentDataManager.java +++ /dev/null @@ -1,1597 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.data.manager.realtime; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; -import com.google.common.util.concurrent.Uninterruptibles; -import java.io.File; -import java.io.IOException; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; -import javax.annotation.Nullable; -import org.apache.commons.io.FileUtils; -import org.apache.pinot.common.Utils; -import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; -import org.apache.pinot.common.metrics.ServerGauge; -import org.apache.pinot.common.metrics.ServerMeter; -import org.apache.pinot.common.metrics.ServerMetrics; -import org.apache.pinot.common.protocols.SegmentCompletionProtocol; -import org.apache.pinot.common.restlet.resources.SegmentErrorInfo; -import org.apache.pinot.common.utils.LLCSegmentName; -import org.apache.pinot.common.utils.TarGzCompressionUtils; -import org.apache.pinot.core.data.manager.realtime.RealtimeConsumptionRateManager.ConsumptionRateLimiter; -import org.apache.pinot.segment.local.dedup.PartitionDedupMetadataManager; -import org.apache.pinot.segment.local.indexsegment.mutable.MutableSegmentImpl; -import org.apache.pinot.segment.local.realtime.converter.ColumnIndicesForRealtimeTable; -import org.apache.pinot.segment.local.realtime.converter.RealtimeSegmentConverter; -import org.apache.pinot.segment.local.realtime.impl.RealtimeSegmentConfig; -import org.apache.pinot.segment.local.segment.creator.TransformPipeline; -import org.apache.pinot.segment.local.segment.index.loader.IndexLoadingConfig; -import org.apache.pinot.segment.local.upsert.PartitionUpsertMetadataManager; -import org.apache.pinot.segment.local.utils.IngestionUtils; -import org.apache.pinot.segment.spi.MutableSegment; -import org.apache.pinot.segment.spi.V1Constants; -import org.apache.pinot.segment.spi.creator.SegmentVersion; -import org.apache.pinot.segment.spi.memory.PinotDataBufferMemoryManager; -import org.apache.pinot.segment.spi.partition.PartitionFunctionFactory; -import org.apache.pinot.segment.spi.store.SegmentDirectoryPaths; -import org.apache.pinot.server.realtime.ServerSegmentCompletionProtocolHandler; -import org.apache.pinot.spi.config.table.ColumnPartitionConfig; -import org.apache.pinot.spi.config.table.CompletionConfig; -import org.apache.pinot.spi.config.table.IndexingConfig; -import org.apache.pinot.spi.config.table.SegmentPartitionConfig; -import org.apache.pinot.spi.config.table.SegmentZKPropsConfig; -import org.apache.pinot.spi.config.table.TableConfig; -import org.apache.pinot.spi.data.Schema; -import org.apache.pinot.spi.data.readers.GenericRow; -import org.apache.pinot.spi.metrics.PinotMeter; -import org.apache.pinot.spi.stream.ConsumerPartitionState; -import org.apache.pinot.spi.stream.LongMsgOffset; -import org.apache.pinot.spi.stream.MessageBatch; -import org.apache.pinot.spi.stream.OffsetCriteria; -import org.apache.pinot.spi.stream.PartitionGroupConsumer; -import org.apache.pinot.spi.stream.PartitionGroupConsumptionStatus; -import org.apache.pinot.spi.stream.PartitionLagState; -import org.apache.pinot.spi.stream.PartitionLevelStreamConfig; -import org.apache.pinot.spi.stream.PermanentConsumerException; -import org.apache.pinot.spi.stream.RowMetadata; -import org.apache.pinot.spi.stream.StreamConsumerFactory; -import org.apache.pinot.spi.stream.StreamConsumerFactoryProvider; -import org.apache.pinot.spi.stream.StreamDataDecoder; -import org.apache.pinot.spi.stream.StreamDataDecoderImpl; -import org.apache.pinot.spi.stream.StreamDataDecoderResult; -import org.apache.pinot.spi.stream.StreamDecoderProvider; -import org.apache.pinot.spi.stream.StreamMessageDecoder; -import org.apache.pinot.spi.stream.StreamMetadataProvider; -import org.apache.pinot.spi.stream.StreamPartitionMsgOffset; -import org.apache.pinot.spi.stream.StreamPartitionMsgOffsetFactory; -import org.apache.pinot.spi.utils.CommonConstants.ConsumerState; -import org.apache.pinot.spi.utils.CommonConstants.Segment.Realtime.CompletionMode; -import org.apache.pinot.spi.utils.IngestionConfigUtils; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - - -/** - * Segment data manager for low level consumer realtime segments, which manages consumption and segment completion. - */ -public class LLRealtimeSegmentDataManager extends RealtimeSegmentDataManager { - protected enum State { - // The state machine starts off with this state. While in this state we consume stream events - // and index them in memory. We continue to be in this state until the end criteria is satisfied - // (time or number of rows) - INITIAL_CONSUMING, - - // In this state, we consume from stream until we reach the _finalOffset (exclusive) - CATCHING_UP, - - // In this state, we sleep for MAX_HOLDING_TIME_MS, and the make a segmentConsumed() call to the - // controller. - HOLDING, - - // We have been asked to go Online from Consuming state, and are trying a last attempt to catch up to the - // target offset. In this state, we have a time constraint as well as a final offset constraint to look into - // before we stop consuming. - CONSUMING_TO_ONLINE, - - // We have been asked by the controller to retain the segment we have in memory at the current offset. - // We should build the segment, and replace it with the in-memory segment. - RETAINING, - - // We have been asked by the controller to commit the segment at the current offset. Build the segment - // and make a segmentCommit() call to the controller. - COMMITTING, - - // We have been asked to discard the in-memory segment we have. We will be serving queries, but not consuming - // anymore rows from stream. We wait for a helix transition to go ONLINE, at which point, we can download the - // segment from the controller and replace it with the in-memory segment. - DISCARDED, - - // We have replaced our in-memory segment with the segment that has been built locally. - RETAINED, - - // We have committed our segment to the controller, and also replaced it locally with the constructed segment. - COMMITTED, - - // Something went wrong, we need to download the segment when we get the ONLINE transition. - ERROR; - - public boolean shouldConsume() { - return this.equals(INITIAL_CONSUMING) || this.equals(CATCHING_UP) || this.equals(CONSUMING_TO_ONLINE); - } - - public boolean isFinal() { - return this.equals(ERROR) || this.equals(COMMITTED) || this.equals(RETAINED) || this.equals(DISCARDED); - } - } - - private static final int MINIMUM_CONSUME_TIME_MINUTES = 10; - - @VisibleForTesting - public class SegmentBuildDescriptor { - final File _segmentTarFile; - final Map _metadataFileMap; - final StreamPartitionMsgOffset _offset; - final long _waitTimeMillis; - final long _buildTimeMillis; - final long _segmentSizeBytes; - - public SegmentBuildDescriptor(@Nullable File segmentTarFile, @Nullable Map metadataFileMap, - StreamPartitionMsgOffset offset, long buildTimeMillis, long waitTimeMillis, long segmentSizeBytes) { - _segmentTarFile = segmentTarFile; - _metadataFileMap = metadataFileMap; - _offset = _streamPartitionMsgOffsetFactory.create(offset); - _buildTimeMillis = buildTimeMillis; - _waitTimeMillis = waitTimeMillis; - _segmentSizeBytes = segmentSizeBytes; - } - - public StreamPartitionMsgOffset getOffset() { - return _offset; - } - - public long getBuildTimeMillis() { - return _buildTimeMillis; - } - - public long getWaitTimeMillis() { - return _waitTimeMillis; - } - - @Nullable - public File getSegmentTarFile() { - return _segmentTarFile; - } - - @Nullable - public Map getMetadataFiles() { - return _metadataFileMap; - } - - public long getSegmentSizeBytes() { - return _segmentSizeBytes; - } - - public void deleteSegmentFile() { - if (_segmentTarFile != null) { - FileUtils.deleteQuietly(_segmentTarFile); - } - } - } - - private static final long TIME_THRESHOLD_FOR_LOG_MINUTES = 1; - private static final long TIME_EXTENSION_ON_EMPTY_SEGMENT_HOURS = 1; - private static final int MSG_COUNT_THRESHOLD_FOR_LOG = 100000; - private static final int BUILD_TIME_LEASE_SECONDS = 30; - private static final int MAX_CONSECUTIVE_ERROR_COUNT = 5; - - private final SegmentZKMetadata _segmentZKMetadata; - private final TableConfig _tableConfig; - private final RealtimeTableDataManager _realtimeTableDataManager; - private final StreamDataDecoder _streamDataDecoder; - private final int _segmentMaxRowCount; - private final String _resourceDataDir; - private final IndexLoadingConfig _indexLoadingConfig; - private final Schema _schema; - // Semaphore for each partitionGroupId only, which is to prevent two different stream consumers - // from consuming with the same partitionGroupId in parallel in the same host. - // See the comments in {@link RealtimeTableDataManager}. - private final Semaphore _partitionGroupConsumerSemaphore; - // A boolean flag to check whether the current thread has acquired the semaphore. - // This boolean is needed because the semaphore is shared by threads; every thread holding this semaphore can - // modify the permit. This boolean make sure the semaphore gets released only once when the partition group stops - // consuming. - private final AtomicBoolean _acquiredConsumerSemaphore; - private final String _metricKeyName; - private final ServerMetrics _serverMetrics; - private final MutableSegmentImpl _realtimeSegment; - private volatile StreamPartitionMsgOffset _currentOffset; - private volatile State _state; - private volatile int _numRowsConsumed = 0; - private volatile int _numRowsIndexed = 0; // Can be different from _numRowsConsumed when metrics update is enabled. - private volatile int _numRowsErrored = 0; - private volatile int _consecutiveErrorCount = 0; - private long _startTimeMs = 0; - private final String _segmentNameStr; - private final SegmentVersion _segmentVersion; - private final SegmentBuildTimeLeaseExtender _leaseExtender; - private SegmentBuildDescriptor _segmentBuildDescriptor; - private final StreamConsumerFactory _streamConsumerFactory; - private final StreamPartitionMsgOffsetFactory _streamPartitionMsgOffsetFactory; - - // Segment end criteria - private volatile long _consumeEndTime = 0; - private volatile boolean _hasMessagesFetched = false; - private volatile boolean _endOfPartitionGroup = false; - private volatile boolean _forceCommitMessageReceived = false; - private StreamPartitionMsgOffset _finalOffset; // Used when we want to catch up to this one - private volatile boolean _shouldStop = false; - - // It takes 30s to locate controller leader, and more if there are multiple controller failures. - // For now, we let 31s pass for this state transition. - private static final int MAX_TIME_FOR_CONSUMING_TO_ONLINE_IN_SECONDS = 31; - - private Thread _consumerThread; - private final int _partitionGroupId; - private final PartitionGroupConsumptionStatus _partitionGroupConsumptionStatus; - final String _clientId; - private final LLCSegmentName _llcSegmentName; - private final TransformPipeline _transformPipeline; - private PartitionGroupConsumer _partitionGroupConsumer = null; - private StreamMetadataProvider _partitionMetadataProvider = null; - private final File _resourceTmpDir; - private final String _tableNameWithType; - private final ColumnIndicesForRealtimeTable _columnIndicesForRealtimeTable; - private final Logger _segmentLogger; - private final String _tableStreamName; - private final PinotDataBufferMemoryManager _memoryManager; - private final AtomicLong _lastUpdatedRowsIndexed = new AtomicLong(0); - private final String _instanceId; - private final ServerSegmentCompletionProtocolHandler _protocolHandler; - private final long _consumeStartTime; - private final StreamPartitionMsgOffset _startOffset; - private final PartitionLevelStreamConfig _partitionLevelStreamConfig; - - private RowMetadata _lastRowMetadata; - private long _lastConsumedTimestampMs = -1; - - private long _lastLogTime = 0; - private int _lastConsumedCount = 0; - private String _stopReason = null; - private final Semaphore _segBuildSemaphore; - private final boolean _isOffHeap; - private final boolean _nullHandlingEnabled; - private final SegmentCommitterFactory _segmentCommitterFactory; - private final ConsumptionRateLimiter _rateLimiter; - - private final StreamPartitionMsgOffset _latestStreamOffsetAtStartupTime; - - // TODO each time this method is called, we print reason for stop. Good to print only once. - private boolean endCriteriaReached() { - Preconditions.checkState(_state.shouldConsume(), "Incorrect state %s", _state); - long now = now(); - switch (_state) { - case INITIAL_CONSUMING: - // The segment has been created, and we have not posted a segmentConsumed() message on the controller yet. - // We need to consume as much data as available, until we have encountered one of the following scenarios: - // - the max number of rows has been reached - // - the max time we are allowed to consume has passed; - // - partition group is ended - // - force commit message has been received - if (now >= _consumeEndTime) { - if (!_hasMessagesFetched) { - _segmentLogger.info("No events came in, extending time by {} hours", TIME_EXTENSION_ON_EMPTY_SEGMENT_HOURS); - _consumeEndTime += TimeUnit.HOURS.toMillis(TIME_EXTENSION_ON_EMPTY_SEGMENT_HOURS); - return false; - } - _segmentLogger - .info("Stopping consumption due to time limit start={} now={} numRowsConsumed={} numRowsIndexed={}", - _startTimeMs, now, _numRowsConsumed, _numRowsIndexed); - _stopReason = SegmentCompletionProtocol.REASON_TIME_LIMIT; - return true; - } else if (_numRowsIndexed >= _segmentMaxRowCount) { - _segmentLogger.info("Stopping consumption due to row limit nRows={} numRowsIndexed={}, numRowsConsumed={}", - _segmentMaxRowCount, _numRowsIndexed, _numRowsConsumed); - _stopReason = SegmentCompletionProtocol.REASON_ROW_LIMIT; - return true; - } else if (_endOfPartitionGroup) { - _segmentLogger.info("Stopping consumption due to end of partitionGroup reached nRows={} numRowsIndexed={}, " - + "numRowsConsumed={}", _segmentMaxRowCount, _numRowsIndexed, _numRowsConsumed); - _stopReason = SegmentCompletionProtocol.REASON_END_OF_PARTITION_GROUP; - return true; - } else if (_forceCommitMessageReceived) { - _segmentLogger.info("Stopping consumption due to force commit - numRowsConsumed={} numRowsIndexed={}", - _numRowsConsumed, _numRowsIndexed); - _stopReason = SegmentCompletionProtocol.REASON_FORCE_COMMIT_MESSAGE_RECEIVED; - return true; - } - return false; - - case CATCHING_UP: - _stopReason = null; - // We have posted segmentConsumed() at least once, and the controller is asking us to catch up to a certain - // offset. - // There is no time limit here, so just check to see that we are still within the offset we need to reach. - // Going past the offset is an exception. - if (_currentOffset.compareTo(_finalOffset) == 0) { - _segmentLogger.info("Caught up to offset={}, state={}", _finalOffset, _state.toString()); - return true; - } - if (_currentOffset.compareTo(_finalOffset) > 0) { - _segmentLogger.error("Offset higher in state={}, current={}, final={}", _state.toString(), _currentOffset, - _finalOffset); - throw new RuntimeException("Past max offset"); - } - return false; - - case CONSUMING_TO_ONLINE: - // We are attempting to go from CONSUMING to ONLINE state. We are making a last attempt to catch up to the - // target offset. We have a time constraint, and need to stop consuming if we cannot get to the target offset - // within that time. - if (_currentOffset.compareTo(_finalOffset) == 0) { - _segmentLogger.info("Caught up to offset={}, state={}", _finalOffset, _state.toString()); - return true; - } else if (now >= _consumeEndTime) { - _segmentLogger.info("Past max time budget: offset={}, state={}", _currentOffset, _state.toString()); - return true; - } - if (_currentOffset.compareTo(_finalOffset) > 0) { - _segmentLogger.error("Offset higher in state={}, current={}, final={}", _state.toString(), _currentOffset, - _finalOffset); - throw new RuntimeException("Past max offset"); - } - return false; - default: - _segmentLogger.error("Illegal state {}" + _state.toString()); - throw new RuntimeException("Illegal state to consume"); - } - } - - private void handleTransientStreamErrors(Exception e) - throws Exception { - _consecutiveErrorCount++; - if (_consecutiveErrorCount > MAX_CONSECUTIVE_ERROR_COUNT) { - _segmentLogger.warn("Stream transient exception when fetching messages, stopping consumption after {} attempts", - _consecutiveErrorCount, e); - throw e; - } else { - _segmentLogger - .warn("Stream transient exception when fetching messages, retrying (count={})", _consecutiveErrorCount, e); - Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS); - recreateStreamConsumer("Too many transient errors"); - } - } - - protected boolean consumeLoop() - throws Exception { - _numRowsErrored = 0; - final long idlePipeSleepTimeMillis = 100; - final long idleTimeoutMillis = _partitionLevelStreamConfig.getIdleTimeoutMillis(); - long idleStartTimeMillis = -1; - boolean idle = false; - - StreamPartitionMsgOffset lastUpdatedOffset = _streamPartitionMsgOffsetFactory - .create(_currentOffset); // so that we always update the metric when we enter this method. - // At this point, we know that we can potentially move the offset, so the old saved segment file is not valid - // anymore. Remove the file if it exists. - removeSegmentFile(); - - _segmentLogger.info("Starting consumption loop start offset {}, finalOffset {}", _currentOffset, _finalOffset); - while (!_shouldStop && !endCriteriaReached()) { - // Consume for the next readTime ms, or we get to final offset, whichever happens earlier, - // Update _currentOffset upon return from this method - MessageBatch messageBatch; - try { - messageBatch = _partitionGroupConsumer - .fetchMessages(_currentOffset, null, _partitionLevelStreamConfig.getFetchTimeoutMillis()); - if (_segmentLogger.isDebugEnabled()) { - _segmentLogger.debug("message batch received. filtered={} unfiltered={} endOfPartitionGroup={}", - messageBatch.getMessageCount(), messageBatch.getUnfilteredMessageCount(), - messageBatch.isEndOfPartitionGroup()); - } - _endOfPartitionGroup = messageBatch.isEndOfPartitionGroup(); - _consecutiveErrorCount = 0; - } catch (PermanentConsumerException e) { - _segmentLogger.warn("Permanent exception from stream when fetching messages, stopping consumption", e); - throw e; - } catch (Exception e) { - // all exceptions but PermanentConsumerException are handled the same way - // can be a TimeoutException or TransientConsumerException routinely - // Unknown exception from stream. Treat as a transient exception. - // One such exception seen so far is java.net.SocketTimeoutException - handleTransientStreamErrors(e); - continue; - } - - boolean endCriteriaReached = processStreamEvents(messageBatch, idlePipeSleepTimeMillis); - - if (_currentOffset.compareTo(lastUpdatedOffset) != 0) { - idle = false; - // We consumed something. Update the highest stream offset as well as partition-consuming metric. - // TODO Issue 5359 Need to find a way to bump metrics without getting actual offset value. - if (_currentOffset instanceof LongMsgOffset) { - // TODO: only LongMsgOffset supplies long offset value. - _serverMetrics.setValueOfTableGauge(_metricKeyName, ServerGauge.HIGHEST_STREAM_OFFSET_CONSUMED, - ((LongMsgOffset) _currentOffset).getOffset()); - } - _serverMetrics.setValueOfTableGauge(_metricKeyName, ServerGauge.LLC_PARTITION_CONSUMING, 1); - lastUpdatedOffset = _streamPartitionMsgOffsetFactory.create(_currentOffset); - } else if (endCriteriaReached) { - // At this point current offset has not moved because processStreamEvents() has exited before processing a - // single message - if (_segmentLogger.isDebugEnabled()) { - _segmentLogger.debug("No messages processed before end criteria was reached. Staying at offset {}", - _currentOffset); - } - // We check this flag again further down - } else if (messageBatch.getUnfilteredMessageCount() > 0) { - idle = false; - // we consumed something from the stream but filtered all the content out, - // so we need to advance the offsets to avoid getting stuck - StreamPartitionMsgOffset nextOffset = messageBatch.getOffsetOfNextBatch(); - if (_segmentLogger.isDebugEnabled()) { - _segmentLogger.debug("Skipped empty batch. Advancing from {} to {}", _currentOffset, nextOffset); - } - _currentOffset = nextOffset; - lastUpdatedOffset = _streamPartitionMsgOffsetFactory.create(nextOffset); - } else { - // We did not consume any rows. - if (!idle) { - idleStartTimeMillis = now(); - idle = true; - } - if (idleTimeoutMillis >= 0) { - long totalIdleTimeMillis = now() - idleStartTimeMillis; - if (totalIdleTimeMillis > idleTimeoutMillis) { - // Update the partition-consuming metric only if we have been idling beyond idle timeout. - // Create a new stream consumer wrapper, in case we are stuck on something. - _serverMetrics.setValueOfTableGauge(_metricKeyName, ServerGauge.LLC_PARTITION_CONSUMING, 1); - recreateStreamConsumer( - String.format("Total idle time: %d ms exceeded idle timeout: %d ms", totalIdleTimeMillis, - idleTimeoutMillis)); - idle = false; - } - } - } - - if (endCriteriaReached) { - // check this flag to avoid calling endCriteriaReached() at the beginning of the loop - break; - } - } - - if (_numRowsErrored > 0) { - _serverMetrics.addMeteredTableValue(_metricKeyName, ServerMeter.ROWS_WITH_ERRORS, _numRowsErrored); - _serverMetrics.addMeteredTableValue(_tableStreamName, ServerMeter.ROWS_WITH_ERRORS, _numRowsErrored); - } - return true; - } - - /** - * @param messagesAndOffsets batch of messages to process - * @param idlePipeSleepTimeMillis wait time in case no messages were read - * @return returns true if the process loop ended before processing the batch, false - * otherwise - */ - private boolean processStreamEvents(MessageBatch messagesAndOffsets, long idlePipeSleepTimeMillis) { - int messageCount = messagesAndOffsets.getMessageCount(); - _rateLimiter.throttle(messageCount); - - PinotMeter realtimeRowsConsumedMeter = null; - PinotMeter realtimeRowsDroppedMeter = null; - PinotMeter realtimeIncompleteRowsConsumedMeter = null; - - int indexedMessageCount = 0; - int streamMessageCount = 0; - boolean canTakeMore = true; - - TransformPipeline.Result reusedResult = new TransformPipeline.Result(); - boolean prematureExit = false; - for (int index = 0; index < messageCount; index++) { - prematureExit = _shouldStop || endCriteriaReached(); - if (prematureExit) { - if (_segmentLogger.isDebugEnabled()) { - _segmentLogger.debug("stop processing message batch early shouldStop: {}", _shouldStop); - } - break; - } - if (!canTakeMore) { - // The RealtimeSegmentImpl that we are pushing rows into has indicated that it cannot accept any more - // rows. This can happen in one of two conditions: - // 1. We are in INITIAL_CONSUMING state, and we somehow exceeded the max number of rows we are allowed to - // consume - // for this row. Something is seriously wrong, because endCriteriaReached() should have returned true when - // we hit the row limit. - // Throw an exception. - // - // 2. We are in CATCHING_UP state, and we legally hit this error due to unclean leader election where - // offsets get changed with higher generation numbers for some pinot servers but not others. So, if another - // server (who got a larger stream offset) asked us to catch up to that offset, but we are connected to a - // broker who has smaller offsets, then we may try to push more rows into the buffer than maximum. This - // is a rare case, and we really don't know how to handle this at this time. - // Throw an exception. - // - _segmentLogger - .error("Buffer full with {} rows consumed (row limit {}, indexed {})", _numRowsConsumed, _numRowsIndexed, - _segmentMaxRowCount); - throw new RuntimeException("Realtime segment full"); - } - - // Decode message - StreamDataDecoderResult decodedRow = _streamDataDecoder.decode(messagesAndOffsets.getStreamMessage(index)); - RowMetadata msgMetadata = messagesAndOffsets.getStreamMessage(index).getMetadata(); - if (decodedRow.getException() != null) { - // TODO: based on a config, decide whether the record should be silently dropped or stop further consumption on - // decode error - realtimeRowsDroppedMeter = - _serverMetrics.addMeteredTableValue(_metricKeyName, ServerMeter.INVALID_REALTIME_ROWS_DROPPED, 1, - realtimeRowsDroppedMeter); - } else { - try { - _transformPipeline.processRow(decodedRow.getResult(), reusedResult); - } catch (Exception e) { - _numRowsErrored++; - // when exception happens we prefer abandoning the whole batch and not partially indexing some rows - reusedResult.getTransformedRows().clear(); - String errorMessage = String.format("Caught exception while transforming the record: %s", decodedRow); - _segmentLogger.error(errorMessage, e); - _realtimeTableDataManager.addSegmentError(_segmentNameStr, - new SegmentErrorInfo(now(), errorMessage, e)); - } - if (reusedResult.getSkippedRowCount() > 0) { - realtimeRowsDroppedMeter = - _serverMetrics.addMeteredTableValue(_metricKeyName, ServerMeter.INVALID_REALTIME_ROWS_DROPPED, - reusedResult.getSkippedRowCount(), realtimeRowsDroppedMeter); - } - if (reusedResult.getIncompleteRowCount() > 0) { - realtimeIncompleteRowsConsumedMeter = - _serverMetrics.addMeteredTableValue(_metricKeyName, ServerMeter.INCOMPLETE_REALTIME_ROWS_CONSUMED, - reusedResult.getIncompleteRowCount(), realtimeIncompleteRowsConsumedMeter); - } - for (GenericRow transformedRow : reusedResult.getTransformedRows()) { - try { - canTakeMore = _realtimeSegment.index(transformedRow, msgMetadata); - indexedMessageCount++; - _lastRowMetadata = msgMetadata; - _lastConsumedTimestampMs = System.currentTimeMillis(); - realtimeRowsConsumedMeter = - _serverMetrics.addMeteredTableValue(_metricKeyName, ServerMeter.REALTIME_ROWS_CONSUMED, 1, - realtimeRowsConsumedMeter); - } catch (Exception e) { - _numRowsErrored++; - String errorMessage = String.format("Caught exception while indexing the record: %s", transformedRow); - _segmentLogger.error(errorMessage, e); - _realtimeTableDataManager.addSegmentError(_segmentNameStr, - new SegmentErrorInfo(now(), errorMessage, e)); - } - } - } - - _currentOffset = messagesAndOffsets.getNextStreamPartitionMsgOffsetAtIndex(index); - _numRowsIndexed = _realtimeSegment.getNumDocsIndexed(); - _numRowsConsumed++; - streamMessageCount++; - } - updateCurrentDocumentCountMetrics(); - if (messagesAndOffsets.getUnfilteredMessageCount() > 0) { - _hasMessagesFetched = true; - if (streamMessageCount > 0 && _segmentLogger.isDebugEnabled()) { - _segmentLogger.debug("Indexed {} messages ({} messages read from stream) current offset {}", - indexedMessageCount, streamMessageCount, _currentOffset); - } - } else if (!prematureExit) { - if (_segmentLogger.isDebugEnabled()) { - _segmentLogger.debug("empty batch received - sleeping for {}ms", idlePipeSleepTimeMillis); - } - // If there were no messages to be fetched from stream, wait for a little bit as to avoid hammering the stream - Uninterruptibles.sleepUninterruptibly(idlePipeSleepTimeMillis, TimeUnit.MILLISECONDS); - } - return prematureExit; - } - - public class PartitionConsumer implements Runnable { - public void run() { - long initialConsumptionEnd = 0L; - long lastCatchUpStart = 0L; - long catchUpTimeMillis = 0L; - _startTimeMs = now(); - try { - while (!_state.isFinal()) { - if (_state.shouldConsume()) { - consumeLoop(); // Consume until we reached the end criteria, or we are stopped. - } - _serverMetrics.setValueOfTableGauge(_metricKeyName, ServerGauge.LLC_PARTITION_CONSUMING, 0); - if (_shouldStop) { - break; - } - - if (_state == State.INITIAL_CONSUMING) { - initialConsumptionEnd = now(); - _serverMetrics.setValueOfTableGauge(_metricKeyName, - ServerGauge.LAST_REALTIME_SEGMENT_INITIAL_CONSUMPTION_DURATION_SECONDS, - TimeUnit.MILLISECONDS.toSeconds(initialConsumptionEnd - _startTimeMs)); - } else if (_state == State.CATCHING_UP) { - catchUpTimeMillis += now() - lastCatchUpStart; - _serverMetrics - .setValueOfTableGauge(_metricKeyName, ServerGauge.LAST_REALTIME_SEGMENT_CATCHUP_DURATION_SECONDS, - TimeUnit.MILLISECONDS.toSeconds(catchUpTimeMillis)); - } - - // If we are sending segmentConsumed() to the controller, we are in HOLDING state. - _state = State.HOLDING; - SegmentCompletionProtocol.Response response = postSegmentConsumedMsg(); - SegmentCompletionProtocol.ControllerResponseStatus status = response.getStatus(); - StreamPartitionMsgOffset rspOffset = extractOffset(response); - boolean success; - switch (status) { - case NOT_LEADER: - // Retain the same state - _segmentLogger.warn("Got not leader response"); - hold(); - break; - case CATCH_UP: - if (rspOffset.compareTo(_currentOffset) <= 0) { - // Something wrong with the controller. Back off and try again. - _segmentLogger.error("Invalid catchup offset {} in controller response, current offset {}", rspOffset, - _currentOffset); - hold(); - } else { - _state = State.CATCHING_UP; - _finalOffset = rspOffset; - lastCatchUpStart = now(); - // We will restart consumption when we loop back above. - } - break; - case HOLD: - hold(); - break; - case DISCARD: - // Keep this in memory, but wait for the online transition, and download when it comes in. - _state = State.DISCARDED; - break; - case KEEP: - _state = State.RETAINING; - CompletionMode segmentCompletionMode = getSegmentCompletionMode(); - switch (segmentCompletionMode) { - case DOWNLOAD: - _state = State.DISCARDED; - break; - case DEFAULT: - success = buildSegmentAndReplace(); - if (success) { - _state = State.RETAINED; - } else { - // Could not build segment for some reason. We can only download it. - _state = State.ERROR; - _segmentLogger.error("Could not build segment for {}", _segmentNameStr); - } - break; - default: - break; - } - break; - case COMMIT: - _state = State.COMMITTING; - _currentOffset = _partitionGroupConsumer.checkpoint(_currentOffset); - long buildTimeSeconds = response.getBuildTimeSeconds(); - buildSegmentForCommit(buildTimeSeconds * 1000L); - if (_segmentBuildDescriptor == null) { - // We could not build the segment. Go into error state. - _state = State.ERROR; - _segmentLogger.error("Could not build segment for {}", _segmentNameStr); - } else { - success = commitSegment(response.getControllerVipUrl(), - response.isSplitCommit() && _indexLoadingConfig.isEnableSplitCommit()); - if (success) { - _state = State.COMMITTED; - } else { - // If for any reason commit failed, we don't want to be in COMMITTING state when we hold. - // Change the state to HOLDING before looping around. - _state = State.HOLDING; - _segmentLogger.info("Could not commit segment. Retrying after hold"); - hold(); - } - } - break; - default: - _segmentLogger.error("Holding after response from Controller: {}", response.toJsonString()); - hold(); - break; - } - } - } catch (Exception e) { - String errorMessage = "Exception while in work"; - _segmentLogger.error(errorMessage, e); - postStopConsumedMsg(e.getClass().getName()); - _state = State.ERROR; - _realtimeTableDataManager - .addSegmentError(_segmentNameStr, new SegmentErrorInfo(now(), errorMessage, e)); - _serverMetrics.setValueOfTableGauge(_metricKeyName, ServerGauge.LLC_PARTITION_CONSUMING, 0); - return; - } - - removeSegmentFile(); - - if (initialConsumptionEnd != 0L) { - _serverMetrics - .setValueOfTableGauge(_metricKeyName, ServerGauge.LAST_REALTIME_SEGMENT_COMPLETION_DURATION_SECONDS, - TimeUnit.MILLISECONDS.toSeconds(now() - initialConsumptionEnd)); - } - // There is a race condition that the destroy() method can be called which ends up calling stop on the consumer. - // The destroy() method does not wait for the thread to terminate (and reasonably so, we dont want to wait - // forever). - // Since the _shouldStop variable is set to true only in stop() method, we know that the metric will be destroyed, - // so it is ok not to mark it non-consuming, as the main thread will clean up this metric in destroy() method - // as the final step. - if (!_shouldStop) { - _serverMetrics.setValueOfTableGauge(_metricKeyName, ServerGauge.LLC_PARTITION_CONSUMING, 0); - } - } - } - - /** - * Fetches the completion mode for the segment completion for the given realtime table - */ - private CompletionMode getSegmentCompletionMode() { - CompletionConfig completionConfig = _tableConfig.getValidationConfig().getCompletionConfig(); - if (completionConfig != null) { - if (CompletionMode.DOWNLOAD.toString().equalsIgnoreCase(completionConfig.getCompletionMode())) { - return CompletionMode.DOWNLOAD; - } - } - return CompletionMode.DEFAULT; - } - - @VisibleForTesting - protected StreamPartitionMsgOffset extractOffset(SegmentCompletionProtocol.Response response) { - if (response.getStreamPartitionMsgOffset() != null) { - return _streamPartitionMsgOffsetFactory.create(response.getStreamPartitionMsgOffset()); - } else { - // TODO Issue 5359 Remove this once the protocol is upgraded on server and controller - return _streamPartitionMsgOffsetFactory.create(Long.toString(response.getOffset())); - } - } - - // Side effect: Modifies _segmentBuildDescriptor if we do not have a valid built segment file and we - // built the segment successfully. - protected void buildSegmentForCommit(long buildTimeLeaseMs) { - try { - if (_segmentBuildDescriptor != null && _segmentBuildDescriptor.getOffset().compareTo(_currentOffset) == 0) { - // Double-check that we have the file, just in case. - File segmentTarFile = _segmentBuildDescriptor.getSegmentTarFile(); - if (segmentTarFile != null && segmentTarFile.exists()) { - return; - } - } - removeSegmentFile(); - if (buildTimeLeaseMs <= 0) { - if (_segBuildSemaphore == null) { - buildTimeLeaseMs = SegmentCompletionProtocol.getDefaultMaxSegmentCommitTimeSeconds() * 1000L; - } else { - // We know we are going to use a semaphore to limit number of segment builds, and could be - // blocked for a long time. The controller has not provided a lease time, so set one to - // some reasonable guess here. - buildTimeLeaseMs = BUILD_TIME_LEASE_SECONDS * 1000; - } - } - _leaseExtender.addSegment(_segmentNameStr, buildTimeLeaseMs, _currentOffset); - _segmentBuildDescriptor = buildSegmentInternal(true); - } finally { - _leaseExtender.removeSegment(_segmentNameStr); - } - } - - @Override - public Map getPartitionToCurrentOffset() { - Map partitionToCurrentOffset = new HashMap<>(); - partitionToCurrentOffset.put(String.valueOf(_partitionGroupId), _currentOffset.toString()); - return partitionToCurrentOffset; - } - - @Override - public ConsumerState getConsumerState() { - return _state == State.ERROR ? ConsumerState.NOT_CONSUMING : ConsumerState.CONSUMING; - } - - @Override - public long getLastConsumedTimestamp() { - return _lastConsumedTimestampMs; - } - - @Override - public Map getConsumerPartitionState() { - String partitionGroupId = String.valueOf(_partitionGroupId); - return Collections.singletonMap(partitionGroupId, new ConsumerPartitionState(partitionGroupId, getCurrentOffset(), - getLastConsumedTimestamp(), fetchLatestStreamOffset(5_000), _lastRowMetadata)); - } - - @Override - public Map getPartitionToLagState( - Map consumerPartitionStateMap) { - if (_partitionMetadataProvider == null) { - createPartitionMetadataProvider("Get Partition Lag State"); - } - ; - return _partitionMetadataProvider.getCurrentPartitionLagState(consumerPartitionStateMap); - } - - public StreamPartitionMsgOffset getCurrentOffset() { - return _currentOffset; - } - - public StreamPartitionMsgOffset getLatestStreamOffsetAtStartupTime() { - return _latestStreamOffsetAtStartupTime; - } - - @VisibleForTesting - protected SegmentBuildDescriptor getSegmentBuildDescriptor() { - return _segmentBuildDescriptor; - } - - @VisibleForTesting - protected Semaphore getPartitionGroupConsumerSemaphore() { - return _partitionGroupConsumerSemaphore; - } - - @VisibleForTesting - protected AtomicBoolean getAcquiredConsumerSemaphore() { - return _acquiredConsumerSemaphore; - } - - protected SegmentBuildDescriptor buildSegmentInternal(boolean forCommit) { - closeStreamConsumers(); - try { - final long startTimeMillis = now(); - if (_segBuildSemaphore != null) { - _segmentLogger.info("Waiting to acquire semaphore for building segment"); - _segBuildSemaphore.acquire(); - } - // Increment llc simultaneous segment builds. - _serverMetrics.addValueToGlobalGauge(ServerGauge.LLC_SIMULTANEOUS_SEGMENT_BUILDS, 1L); - - final long lockAcquireTimeMillis = now(); - // Build a segment from in-memory rows.If buildTgz is true, then build the tar.gz file as well - // TODO Use an auto-closeable object to delete temp resources. - File tempSegmentFolder = new File(_resourceTmpDir, "tmp-" + _segmentNameStr + "-" + now()); - - SegmentZKPropsConfig segmentZKPropsConfig = new SegmentZKPropsConfig(); - segmentZKPropsConfig.setStartOffset(_segmentZKMetadata.getStartOffset()); - segmentZKPropsConfig.setEndOffset(_currentOffset.toString()); - // lets convert the segment now - RealtimeSegmentConverter converter = - new RealtimeSegmentConverter(_realtimeSegment, segmentZKPropsConfig, tempSegmentFolder.getAbsolutePath(), - _schema, _tableNameWithType, _tableConfig, _segmentZKMetadata.getSegmentName(), - _columnIndicesForRealtimeTable, _nullHandlingEnabled); - _segmentLogger.info("Trying to build segment"); - try { - converter.build(_segmentVersion, _serverMetrics); - } catch (Exception e) { - _segmentLogger.error("Could not build segment", e); - FileUtils.deleteQuietly(tempSegmentFolder); - _realtimeTableDataManager.addSegmentError(_segmentNameStr, - new SegmentErrorInfo(now(), "Could not build segment", e)); - return null; - } - final long buildTimeMillis = now() - lockAcquireTimeMillis; - final long waitTimeMillis = lockAcquireTimeMillis - startTimeMillis; - _segmentLogger - .info("Successfully built segment in {} ms, after lockWaitTime {} ms", buildTimeMillis, waitTimeMillis); - - File dataDir = new File(_resourceDataDir); - File indexDir = new File(dataDir, _segmentNameStr); - FileUtils.deleteQuietly(indexDir); - - File[] tempFiles = tempSegmentFolder.listFiles(); - assert tempFiles != null; - File tempIndexDir = tempFiles[0]; - try { - FileUtils.moveDirectory(tempIndexDir, indexDir); - } catch (IOException e) { - String errorMessage = - String.format("Caught exception while moving index directory from: %s to: %s", tempIndexDir, indexDir); - _segmentLogger.error(errorMessage, e); - _realtimeTableDataManager - .addSegmentError(_segmentNameStr, new SegmentErrorInfo(now(), errorMessage, e)); - return null; - } finally { - FileUtils.deleteQuietly(tempSegmentFolder); - } - - long segmentSizeBytes = FileUtils.sizeOfDirectory(indexDir); - _serverMetrics.setValueOfTableGauge(_metricKeyName, ServerGauge.LAST_REALTIME_SEGMENT_CREATION_DURATION_SECONDS, - TimeUnit.MILLISECONDS.toSeconds(buildTimeMillis)); - _serverMetrics.setValueOfTableGauge(_metricKeyName, ServerGauge.LAST_REALTIME_SEGMENT_CREATION_WAIT_TIME_SECONDS, - TimeUnit.MILLISECONDS.toSeconds(waitTimeMillis)); - - if (forCommit) { - File segmentTarFile = new File(dataDir, _segmentNameStr + TarGzCompressionUtils.TAR_GZ_FILE_EXTENSION); - try { - TarGzCompressionUtils.createTarGzFile(indexDir, segmentTarFile); - } catch (IOException e) { - String errorMessage = - String.format("Caught exception while taring index directory from: %s to: %s", indexDir, segmentTarFile); - _segmentLogger.error(errorMessage, e); - _realtimeTableDataManager - .addSegmentError(_segmentNameStr, new SegmentErrorInfo(now(), errorMessage, e)); - return null; - } - - File metadataFile = SegmentDirectoryPaths.findMetadataFile(indexDir); - if (metadataFile == null) { - String errorMessage = String.format("Failed to find file: %s under index directory: %s", - V1Constants.MetadataKeys.METADATA_FILE_NAME, indexDir); - _segmentLogger.error(errorMessage); - _realtimeTableDataManager - .addSegmentError(_segmentNameStr, new SegmentErrorInfo(now(), errorMessage, null)); - return null; - } - File creationMetaFile = SegmentDirectoryPaths.findCreationMetaFile(indexDir); - if (creationMetaFile == null) { - String errorMessage = String.format("Failed to find file: %s under index directory: %s", - V1Constants.SEGMENT_CREATION_META, indexDir); - _segmentLogger.error(errorMessage); - _realtimeTableDataManager - .addSegmentError(_segmentNameStr, new SegmentErrorInfo(now(), errorMessage, null)); - return null; - } - Map metadataFiles = new HashMap<>(); - metadataFiles.put(V1Constants.MetadataKeys.METADATA_FILE_NAME, metadataFile); - metadataFiles.put(V1Constants.SEGMENT_CREATION_META, creationMetaFile); - - return new SegmentBuildDescriptor(segmentTarFile, metadataFiles, _currentOffset, buildTimeMillis, - waitTimeMillis, segmentSizeBytes); - } else { - return new SegmentBuildDescriptor(null, null, _currentOffset, buildTimeMillis, waitTimeMillis, - segmentSizeBytes); - } - } catch (InterruptedException e) { - String errorMessage = "Interrupted while waiting for semaphore"; - _segmentLogger.error(errorMessage, e); - _realtimeTableDataManager - .addSegmentError(_segmentNameStr, new SegmentErrorInfo(now(), errorMessage, e)); - return null; - } finally { - if (_segBuildSemaphore != null) { - _segBuildSemaphore.release(); - } - // Decrement llc simultaneous segment builds. - _serverMetrics.addValueToGlobalGauge(ServerGauge.LLC_SIMULTANEOUS_SEGMENT_BUILDS, -1L); - } - } - - protected boolean commitSegment(String controllerVipUrl, boolean isSplitCommit) { - File segmentTarFile = _segmentBuildDescriptor.getSegmentTarFile(); - if (segmentTarFile == null || !segmentTarFile.exists()) { - throw new RuntimeException("Segment file does not exist: " + segmentTarFile); - } - SegmentCompletionProtocol.Response commitResponse = commit(controllerVipUrl, isSplitCommit); - - if (!commitResponse.getStatus().equals(SegmentCompletionProtocol.ControllerResponseStatus.COMMIT_SUCCESS)) { - return false; - } - - _realtimeTableDataManager.replaceLLSegment(_segmentNameStr, _indexLoadingConfig); - removeSegmentFile(); - return true; - } - - protected SegmentCompletionProtocol.Response commit(String controllerVipUrl, boolean isSplitCommit) { - SegmentCompletionProtocol.Request.Params params = new SegmentCompletionProtocol.Request.Params(); - - params.withSegmentName(_segmentNameStr).withStreamPartitionMsgOffset(_currentOffset.toString()) - .withNumRows(_numRowsConsumed).withInstanceId(_instanceId) - .withBuildTimeMillis(_segmentBuildDescriptor.getBuildTimeMillis()) - .withSegmentSizeBytes(_segmentBuildDescriptor.getSegmentSizeBytes()) - .withWaitTimeMillis(_segmentBuildDescriptor.getWaitTimeMillis()); - if (_isOffHeap) { - params.withMemoryUsedBytes(_memoryManager.getTotalAllocatedBytes()); - } - - SegmentCommitter segmentCommitter; - try { - segmentCommitter = _segmentCommitterFactory.createSegmentCommitter(isSplitCommit, params, controllerVipUrl); - } catch (URISyntaxException e) { - _segmentLogger.error("Failed to create a segment committer: ", e); - return SegmentCompletionProtocol.RESP_NOT_SENT; - } - return segmentCommitter.commit(_segmentBuildDescriptor); - } - - protected boolean buildSegmentAndReplace() { - SegmentBuildDescriptor descriptor = buildSegmentInternal(false); - if (descriptor == null) { - return false; - } - _realtimeTableDataManager.replaceLLSegment(_segmentNameStr, _indexLoadingConfig); - return true; - } - - private void closeStreamConsumers() { - closePartitionGroupConsumer(); - closePartitionMetadataProvider(); - if (_acquiredConsumerSemaphore.compareAndSet(true, false)) { - _partitionGroupConsumerSemaphore.release(); - } - } - - private void closePartitionGroupConsumer() { - try { - _partitionGroupConsumer.close(); - } catch (Exception e) { - _segmentLogger.warn("Could not close stream consumer", e); - } - } - - private void closePartitionMetadataProvider() { - if (_partitionMetadataProvider != null) { - try { - _partitionMetadataProvider.close(); - } catch (Exception e) { - _segmentLogger.warn("Could not close stream metadata provider", e); - } - } - } - - /** - * Cleans up the metrics that reflects the state of the realtime segment. - * This step is essential as the instance may not be the target location for some of the partitions. - * E.g. if the number of partitions increases, or a host swap is needed, the target location for some partitions - * may change, - * and the current host remains to run. In this case, the current server would still keep the state of the old - * partitions, - * which no longer resides in this host any more, thus causes false positive information to the metric system. - */ - private void cleanupMetrics() { - _serverMetrics.removeTableGauge(_metricKeyName, ServerGauge.LLC_PARTITION_CONSUMING); - } - - protected void hold() { - try { - Thread.sleep(SegmentCompletionProtocol.MAX_HOLD_TIME_MS); - } catch (InterruptedException e) { - _segmentLogger.warn("Interrupted while holding"); - } - } - - // Inform the controller that the server had to stop consuming due to an error. - protected void postStopConsumedMsg(String reason) { - do { - SegmentCompletionProtocol.Request.Params params = new SegmentCompletionProtocol.Request.Params(); - params.withStreamPartitionMsgOffset(_currentOffset.toString()).withReason(reason).withSegmentName(_segmentNameStr) - .withInstanceId(_instanceId); - - SegmentCompletionProtocol.Response response = _protocolHandler.segmentStoppedConsuming(params); - if (response.getStatus() == SegmentCompletionProtocol.ControllerResponseStatus.PROCESSED) { - _segmentLogger.info("Got response {}", response.toJsonString()); - break; - } - Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS); - _segmentLogger.info("Retrying after response {}", response.toJsonString()); - } while (!_shouldStop); - } - - protected SegmentCompletionProtocol.Response postSegmentConsumedMsg() { - // Post segmentConsumed to current leader. - // Retry maybe once if leader is not found. - SegmentCompletionProtocol.Request.Params params = new SegmentCompletionProtocol.Request.Params(); - params.withStreamPartitionMsgOffset(_currentOffset.toString()).withSegmentName(_segmentNameStr) - .withReason(_stopReason).withNumRows(_numRowsConsumed).withInstanceId(_instanceId); - if (_isOffHeap) { - params.withMemoryUsedBytes(_memoryManager.getTotalAllocatedBytes()); - } - return _protocolHandler.segmentConsumed(params); - } - - private void removeSegmentFile() { - if (_segmentBuildDescriptor != null) { - _segmentBuildDescriptor.deleteSegmentFile(); - _segmentBuildDescriptor = null; - } - } - - public void goOnlineFromConsuming(SegmentZKMetadata segmentZKMetadata) - throws InterruptedException { - _serverMetrics.setValueOfTableGauge(_metricKeyName, ServerGauge.LLC_PARTITION_CONSUMING, 0); - try { - // Remove the segment file before we do anything else. - removeSegmentFile(); - _leaseExtender.removeSegment(_segmentNameStr); - final StreamPartitionMsgOffset endOffset = - _streamPartitionMsgOffsetFactory.create(segmentZKMetadata.getEndOffset()); - _segmentLogger - .info("State: {}, transitioning from CONSUMING to ONLINE (startOffset: {}, endOffset: {})", _state.toString(), - _startOffset, endOffset); - stop(); - _segmentLogger.info("Consumer thread stopped in state {}", _state.toString()); - - switch (_state) { - case COMMITTED: - case RETAINED: - // Nothing to do. we already built local segment and swapped it with in-memory data. - _segmentLogger.info("State {}. Nothing to do", _state.toString()); - break; - case DISCARDED: - case ERROR: - _segmentLogger.info("State {}. Downloading to replace", _state.toString()); - downloadSegmentAndReplace(segmentZKMetadata); - break; - case CATCHING_UP: - case HOLDING: - case INITIAL_CONSUMING: - CompletionMode segmentCompletionMode = getSegmentCompletionMode(); - switch (segmentCompletionMode) { - case DOWNLOAD: - _segmentLogger.info("State {}. CompletionMode {}. Downloading to replace", _state.toString(), - segmentCompletionMode); - downloadSegmentAndReplace(segmentZKMetadata); - break; - case DEFAULT: - // Allow to catch up upto final offset, and then replace. - if (_currentOffset.compareTo(endOffset) > 0) { - // We moved ahead of the offset that is committed in ZK. - _segmentLogger - .warn("Current offset {} ahead of the offset in zk {}. Downloading to replace", _currentOffset, - endOffset); - downloadSegmentAndReplace(segmentZKMetadata); - } else if (_currentOffset.compareTo(endOffset) == 0) { - _segmentLogger - .info("Current offset {} matches offset in zk {}. Replacing segment", _currentOffset, endOffset); - buildSegmentAndReplace(); - } else { - _segmentLogger.info("Attempting to catch up from offset {} to {} ", _currentOffset, endOffset); - boolean success = catchupToFinalOffset(endOffset, - TimeUnit.MILLISECONDS.convert(MAX_TIME_FOR_CONSUMING_TO_ONLINE_IN_SECONDS, TimeUnit.SECONDS)); - if (success) { - _segmentLogger.info("Caught up to offset {}", _currentOffset); - buildSegmentAndReplace(); - } else { - _segmentLogger - .info("Could not catch up to offset (current = {}). Downloading to replace", _currentOffset); - downloadSegmentAndReplace(segmentZKMetadata); - } - } - break; - default: - break; - } - break; - default: - _segmentLogger.info("Downloading to replace segment while in state {}", _state.toString()); - downloadSegmentAndReplace(segmentZKMetadata); - break; - } - } catch (Exception e) { - Utils.rethrowException(e); - } finally { - _serverMetrics.setValueOfTableGauge(_metricKeyName, ServerGauge.LLC_PARTITION_CONSUMING, 0); - } - } - - protected void downloadSegmentAndReplace(SegmentZKMetadata segmentZKMetadata) { - closeStreamConsumers(); - _realtimeTableDataManager - .downloadAndReplaceSegment(_segmentNameStr, segmentZKMetadata, _indexLoadingConfig, _tableConfig); - } - - protected long now() { - return System.currentTimeMillis(); - } - - private boolean catchupToFinalOffset(StreamPartitionMsgOffset endOffset, long timeoutMs) { - _finalOffset = endOffset; - _consumeEndTime = now() + timeoutMs; - _state = State.CONSUMING_TO_ONLINE; - _shouldStop = false; - try { - consumeLoop(); - } catch (Exception e) { - // We will end up downloading the segment, so this is not a serious problem - _segmentLogger.warn("Exception when catching up to final offset", e); - return false; - } finally { - _serverMetrics.setValueOfTableGauge(_metricKeyName, ServerGauge.LLC_PARTITION_CONSUMING, 0); - } - if (_currentOffset.compareTo(endOffset) != 0) { - // Timeout? - _segmentLogger.warn("Could not consume up to {} (current offset {})", endOffset, _currentOffset); - return false; - } - - return true; - } - - public void destroy() { - try { - stop(); - } catch (InterruptedException e) { - _segmentLogger.error("Could not stop consumer thread"); - } - _realtimeSegment.destroy(); - closeStreamConsumers(); - cleanupMetrics(); - } - - protected void startConsumerThread() { - _consumerThread = new Thread(new PartitionConsumer(), _segmentNameStr); - _segmentLogger.info("Created new consumer thread {} for {}", _consumerThread, this); - _consumerThread.start(); - } - - /** - * Stop the consuming thread. - */ - public void stop() - throws InterruptedException { - _shouldStop = true; - // This method could be called either when we get an ONLINE transition or - // when we commit a segment and replace the realtime segment with a committed - // one. In the latter case, we don't want to call join. - if (Thread.currentThread() != _consumerThread) { - Uninterruptibles.joinUninterruptibly(_consumerThread, 10, TimeUnit.MINUTES); - - if (_consumerThread.isAlive()) { - _segmentLogger.warn("Failed to stop consumer thread within 10 minutes"); - } - } - } - - // Assume that this is called only on OFFLINE to CONSUMING transition. - // If the transition is OFFLINE to ONLINE, the caller should have downloaded the segment and we don't reach here. - public LLRealtimeSegmentDataManager(SegmentZKMetadata segmentZKMetadata, TableConfig tableConfig, - RealtimeTableDataManager realtimeTableDataManager, String resourceDataDir, IndexLoadingConfig indexLoadingConfig, - Schema schema, LLCSegmentName llcSegmentName, Semaphore partitionGroupConsumerSemaphore, - ServerMetrics serverMetrics, @Nullable PartitionUpsertMetadataManager partitionUpsertMetadataManager, - @Nullable PartitionDedupMetadataManager partitionDedupMetadataManager) { - _segBuildSemaphore = realtimeTableDataManager.getSegmentBuildSemaphore(); - _segmentZKMetadata = segmentZKMetadata; - _tableConfig = tableConfig; - _tableNameWithType = _tableConfig.getTableName(); - _realtimeTableDataManager = realtimeTableDataManager; - _resourceDataDir = resourceDataDir; - _indexLoadingConfig = indexLoadingConfig; - _schema = schema; - _serverMetrics = serverMetrics; - _segmentVersion = indexLoadingConfig.getSegmentVersion(); - _instanceId = _realtimeTableDataManager.getServerInstance(); - _leaseExtender = SegmentBuildTimeLeaseExtender.getLeaseExtender(_tableNameWithType); - _protocolHandler = new ServerSegmentCompletionProtocolHandler(_serverMetrics, _tableNameWithType); - - String timeColumnName = tableConfig.getValidationConfig().getTimeColumnName(); - // TODO Validate configs - IndexingConfig indexingConfig = _tableConfig.getIndexingConfig(); - _partitionLevelStreamConfig = - new PartitionLevelStreamConfig(_tableNameWithType, IngestionConfigUtils.getStreamConfigMap(_tableConfig)); - _streamConsumerFactory = StreamConsumerFactoryProvider.create(_partitionLevelStreamConfig); - _streamPartitionMsgOffsetFactory = _streamConsumerFactory.createStreamMsgOffsetFactory(); - String streamTopic = _partitionLevelStreamConfig.getTopicName(); - _segmentNameStr = _segmentZKMetadata.getSegmentName(); - _llcSegmentName = llcSegmentName; - _partitionGroupId = _llcSegmentName.getPartitionGroupId(); - _partitionGroupConsumptionStatus = - new PartitionGroupConsumptionStatus(_partitionGroupId, _llcSegmentName.getSequenceNumber(), - _streamPartitionMsgOffsetFactory.create(_segmentZKMetadata.getStartOffset()), - _segmentZKMetadata.getEndOffset() == null ? null - : _streamPartitionMsgOffsetFactory.create(_segmentZKMetadata.getEndOffset()), - _segmentZKMetadata.getStatus().toString()); - _partitionGroupConsumerSemaphore = partitionGroupConsumerSemaphore; - _acquiredConsumerSemaphore = new AtomicBoolean(false); - _metricKeyName = _tableNameWithType + "-" + streamTopic + "-" + _partitionGroupId; - _segmentLogger = LoggerFactory.getLogger(LLRealtimeSegmentDataManager.class.getName() + "_" + _segmentNameStr); - _tableStreamName = _tableNameWithType + "_" + streamTopic; - _memoryManager = getMemoryManager(realtimeTableDataManager.getConsumerDir(), _segmentNameStr, - indexLoadingConfig.isRealtimeOffHeapAllocation(), indexLoadingConfig.isDirectRealtimeOffHeapAllocation(), - serverMetrics); - - _rateLimiter = RealtimeConsumptionRateManager.getInstance() - .createRateLimiter(_partitionLevelStreamConfig, _tableNameWithType, _serverMetrics, _metricKeyName); - - List sortedColumns = indexLoadingConfig.getSortedColumns(); - String sortedColumn; - if (sortedColumns.isEmpty()) { - _segmentLogger.info("RealtimeDataResourceZKMetadata contains no information about sorted column for segment {}", - _llcSegmentName); - sortedColumn = null; - } else { - String firstSortedColumn = sortedColumns.get(0); - if (_schema.hasColumn(firstSortedColumn)) { - _segmentLogger.info("Setting sorted column name: {} from RealtimeDataResourceZKMetadata for segment {}", - firstSortedColumn, _llcSegmentName); - sortedColumn = firstSortedColumn; - } else { - _segmentLogger - .warn("Sorted column name: {} from RealtimeDataResourceZKMetadata is not existed in schema for segment {}.", - firstSortedColumn, _llcSegmentName); - sortedColumn = null; - } - } - // Inverted index columns - Set invertedIndexColumns = indexLoadingConfig.getInvertedIndexColumns(); - // We need to add sorted column into inverted index columns because when we convert realtime in memory segment into - // offline segment, we use sorted column's inverted index to maintain the order of the records so that the records - // are sorted on the sorted column. - if (sortedColumn != null) { - invertedIndexColumns.add(sortedColumn); - } - - // Read the max number of rows - int segmentMaxRowCount = _partitionLevelStreamConfig.getFlushThresholdRows(); - int flushThresholdSize = segmentZKMetadata.getSizeThresholdToFlushSegment(); - if (flushThresholdSize > 0) { - segmentMaxRowCount = flushThresholdSize; - } - _segmentMaxRowCount = segmentMaxRowCount; - - _isOffHeap = indexLoadingConfig.isRealtimeOffHeapAllocation(); - - _nullHandlingEnabled = indexingConfig.isNullHandlingEnabled(); - - _columnIndicesForRealtimeTable = new ColumnIndicesForRealtimeTable(sortedColumn, - new ArrayList<>(invertedIndexColumns), - new ArrayList<>(indexLoadingConfig.getTextIndexColumns()), - new ArrayList<>(indexLoadingConfig.getFSTIndexColumns()), - new ArrayList<>(indexLoadingConfig.getNoDictionaryColumns()), - new ArrayList<>(indexLoadingConfig.getVarLengthDictionaryColumns())); - - // Start new realtime segment - String consumerDir = realtimeTableDataManager.getConsumerDir(); - RealtimeSegmentConfig.Builder realtimeSegmentConfigBuilder = - new RealtimeSegmentConfig.Builder().setTableNameWithType(_tableNameWithType).setSegmentName(_segmentNameStr) - .setStreamName(streamTopic).setSchema(_schema).setTimeColumnName(timeColumnName) - .setCapacity(_segmentMaxRowCount).setAvgNumMultiValues(indexLoadingConfig.getRealtimeAvgMultiValueCount()) - .setNoDictionaryColumns(indexLoadingConfig.getNoDictionaryColumns()) - .setVarLengthDictionaryColumns(indexLoadingConfig.getVarLengthDictionaryColumns()) - .setInvertedIndexColumns(invertedIndexColumns).setTextIndexColumns(indexLoadingConfig.getTextIndexColumns()) - .setFSTIndexColumns(indexLoadingConfig.getFSTIndexColumns()) - .setJsonIndexConfigs(indexLoadingConfig.getJsonIndexConfigs()) - .setH3IndexConfigs(indexLoadingConfig.getH3IndexConfigs()).setSegmentZKMetadata(segmentZKMetadata) - .setOffHeap(_isOffHeap).setMemoryManager(_memoryManager) - .setStatsHistory(realtimeTableDataManager.getStatsHistory()) - .setAggregateMetrics(indexingConfig.isAggregateMetrics()) - .setIngestionAggregationConfigs(IngestionConfigUtils.getAggregationConfigs(tableConfig)) - .setNullHandlingEnabled(_nullHandlingEnabled) - .setConsumerDir(consumerDir).setUpsertMode(tableConfig.getUpsertMode()) - .setPartitionUpsertMetadataManager(partitionUpsertMetadataManager) - .setPartitionDedupMetadataManager(partitionDedupMetadataManager) - .setUpsertComparisonColumn(tableConfig.getUpsertComparisonColumn()) - .setFieldConfigList(tableConfig.getFieldConfigList()); - - // Create message decoder - Set fieldsToRead = IngestionUtils.getFieldsForRecordExtractor(_tableConfig.getIngestionConfig(), _schema); - StreamMessageDecoder streamMessageDecoder = StreamDecoderProvider.create(_partitionLevelStreamConfig, fieldsToRead); - _streamDataDecoder = new StreamDataDecoderImpl(streamMessageDecoder); - _clientId = streamTopic + "-" + _partitionGroupId; - - _transformPipeline = new TransformPipeline(tableConfig, schema); - // Acquire semaphore to create stream consumers - try { - _partitionGroupConsumerSemaphore.acquire(); - _acquiredConsumerSemaphore.set(true); - } catch (InterruptedException e) { - String errorMsg = "InterruptedException when acquiring the partitionConsumerSemaphore"; - _segmentLogger.error(errorMsg); - throw new RuntimeException(errorMsg + " for segment: " + _segmentNameStr); - } - - try { - _startOffset = _partitionGroupConsumptionStatus.getStartOffset(); - _currentOffset = _streamPartitionMsgOffsetFactory.create(_startOffset); - makeStreamConsumer("Starting"); - createPartitionMetadataProvider("Starting"); - setPartitionParameters(realtimeSegmentConfigBuilder, indexingConfig.getSegmentPartitionConfig()); - _realtimeSegment = new MutableSegmentImpl(realtimeSegmentConfigBuilder.build(), serverMetrics); - _resourceTmpDir = new File(resourceDataDir, "_tmp"); - if (!_resourceTmpDir.exists()) { - _resourceTmpDir.mkdirs(); - } - _state = State.INITIAL_CONSUMING; - _latestStreamOffsetAtStartupTime = fetchLatestStreamOffset(5000); - _consumeStartTime = now(); - setConsumeEndTime(segmentZKMetadata, _consumeStartTime); - _segmentCommitterFactory = - new SegmentCommitterFactory(_segmentLogger, _protocolHandler, tableConfig, indexLoadingConfig, serverMetrics); - _segmentLogger - .info("Starting consumption on realtime consuming segment {} maxRowCount {} maxEndTime {}", _llcSegmentName, - _segmentMaxRowCount, new DateTime(_consumeEndTime, DateTimeZone.UTC)); - startConsumerThread(); - } catch (Exception e) { - // In case of exception thrown here, segment goes to ERROR state. Then any attempt to reset the segment from - // ERROR -> OFFLINE -> CONSUMING via Helix Admin fails because the semaphore is acquired, but not released. - // Hence releasing the semaphore here to unblock reset operation via Helix Admin. - _partitionGroupConsumerSemaphore.release(); - throw e; - } - } - - private void setConsumeEndTime(SegmentZKMetadata segmentZKMetadata, long now) { - long maxConsumeTimeMillis = _partitionLevelStreamConfig.getFlushThresholdTimeMillis(); - _consumeEndTime = segmentZKMetadata.getCreationTime() + maxConsumeTimeMillis; - - // When we restart a server, the consuming segments retain their creationTime (derived from segment - // metadata), but a couple of corner cases can happen: - // (1) The server was down for a very long time, and the consuming segment is not yet completed. - // (2) The consuming segment was just about to be completed, but the server went down. - // In either of these two cases, if a different replica could not complete the segment, it is possible - // that we get a value for _consumeEndTime that is in the very near future, or even in the past. In such - // cases, we let some minimum consumption happen before we attempt to complete the segment (unless, of course - // the max consumption time has been configured to be less than the minimum time we use in this class). - long minConsumeTimeMillis = - Math.min(maxConsumeTimeMillis, TimeUnit.MILLISECONDS.convert(MINIMUM_CONSUME_TIME_MINUTES, TimeUnit.MINUTES)); - if (_consumeEndTime - now < minConsumeTimeMillis) { - _consumeEndTime = now + minConsumeTimeMillis; - } - } - - public StreamPartitionMsgOffset fetchLatestStreamOffset(long maxWaitTimeMs) { - if (_partitionMetadataProvider == null) { - createPartitionMetadataProvider("Fetch latest stream offset"); - } - try { - return _partitionMetadataProvider.fetchStreamPartitionOffset(OffsetCriteria.LARGEST_OFFSET_CRITERIA, - maxWaitTimeMs); - } catch (Exception e) { - _segmentLogger.warn( - "Cannot fetch latest stream offset for clientId {} and partitionGroupId {} with maxWaitTime {}", _clientId, - _partitionGroupId, maxWaitTimeMs); - } - return null; - } - - /* - * set the following partition parameters in RT segment config builder: - * - partition column - * - partition function - * - partition group id - */ - private void setPartitionParameters(RealtimeSegmentConfig.Builder realtimeSegmentConfigBuilder, - SegmentPartitionConfig segmentPartitionConfig) { - if (segmentPartitionConfig != null) { - Map columnPartitionMap = segmentPartitionConfig.getColumnPartitionMap(); - if (columnPartitionMap.size() == 1) { - Map.Entry entry = columnPartitionMap.entrySet().iterator().next(); - String partitionColumn = entry.getKey(); - ColumnPartitionConfig columnPartitionConfig = entry.getValue(); - String partitionFunctionName = columnPartitionConfig.getFunctionName(); - - // NOTE: Here we compare the number of partitions from the config and the stream, and log a warning and emit a - // metric when they don't match, but use the one from the stream. The mismatch could happen when the - // stream partitions are changed, but the table config has not been updated to reflect the change. - // In such case, picking the number of partitions from the stream can keep the segment properly - // partitioned as long as the partition function is not changed. - int numPartitions = columnPartitionConfig.getNumPartitions(); - try { - // TODO: currentPartitionGroupConsumptionStatus should be fetched from idealState + segmentZkMetadata, - // so that we get back accurate partitionGroups info - // However this is not an issue for Kafka, since partitionGroups never expire and every partitionGroup has - // a single partition - // Fix this before opening support for partitioning in Kinesis - int numPartitionGroups = _partitionMetadataProvider - .computePartitionGroupMetadata(_clientId, _partitionLevelStreamConfig, - Collections.emptyList(), /*maxWaitTimeMs=*/5000).size(); - - if (numPartitionGroups != numPartitions) { - _segmentLogger.warn( - "Number of stream partitions: {} does not match number of partitions in the partition config: {}, " - + "using number of stream " + "partitions", numPartitionGroups, numPartitions); - _serverMetrics.addMeteredTableValue(_tableNameWithType, ServerMeter.REALTIME_PARTITION_MISMATCH, 1); - numPartitions = numPartitionGroups; - } - } catch (Exception e) { - _segmentLogger.warn("Failed to get number of stream partitions in 5s, " - + "using number of partitions in the partition config: {}", numPartitions, e); - createPartitionMetadataProvider("Timeout getting number of stream partitions"); - } - - realtimeSegmentConfigBuilder.setPartitionColumn(partitionColumn); - realtimeSegmentConfigBuilder.setPartitionFunction( - PartitionFunctionFactory.getPartitionFunction(partitionFunctionName, numPartitions, null)); - realtimeSegmentConfigBuilder.setPartitionId(_partitionGroupId); - } else { - _segmentLogger.warn("Cannot partition on multiple columns: {}", columnPartitionMap.keySet()); - } - } - } - - /** - * Creates a new stream consumer - */ - private void makeStreamConsumer(String reason) { - if (_partitionGroupConsumer != null) { - closePartitionGroupConsumer(); - } - _segmentLogger.info("Creating new stream consumer for topic partition {} , reason: {}", _clientId, reason); - _partitionGroupConsumer = - _streamConsumerFactory.createPartitionGroupConsumer(_clientId, _partitionGroupConsumptionStatus); - _partitionGroupConsumer.start(_currentOffset); - } - - /** - * Checkpoints existing consumer before creating a new consumer instance - * Assumes there is a valid instance of {@link PartitionGroupConsumer} - */ - private void recreateStreamConsumer(String reason) { - _segmentLogger.warn("Recreating stream consumer for topic partition {}, reason: {}", _clientId, reason); - _currentOffset = _partitionGroupConsumer.checkpoint(_currentOffset); - closePartitionGroupConsumer(); - _partitionGroupConsumer = - _streamConsumerFactory.createPartitionGroupConsumer(_clientId, _partitionGroupConsumptionStatus); - _partitionGroupConsumer.start(_currentOffset); - } - - /** - * Creates a new stream metadata provider - */ - private void createPartitionMetadataProvider(String reason) { - closePartitionMetadataProvider(); - _segmentLogger.info("Creating new partition metadata provider, reason: {}", reason); - _partitionMetadataProvider = _streamConsumerFactory.createPartitionMetadataProvider(_clientId, _partitionGroupId); - } - - // This should be done during commit? We may not always commit when we build a segment.... - // TODO Call this method when we are loading the segment, which we do from table datamanager afaik - private void updateCurrentDocumentCountMetrics() { - - // When updating of metrics is enabled, numRowsIndexed can be <= numRowsConsumed. This is because when in this - // case when a new row with existing dimension combination comes in, we find the existing row and update metrics. - - // Number of rows indexed should be used for DOCUMENT_COUNT metric, and also for segment flush. Whereas, - // Number of rows consumed should be used for consumption metric. - long rowsIndexed = _numRowsIndexed - _lastUpdatedRowsIndexed.get(); - _serverMetrics.addValueToTableGauge(_tableNameWithType, ServerGauge.DOCUMENT_COUNT, rowsIndexed); - _lastUpdatedRowsIndexed.set(_numRowsIndexed); - final long now = now(); - final int rowsConsumed = _numRowsConsumed - _lastConsumedCount; - final long prevTime = _lastConsumedCount == 0 ? _consumeStartTime : _lastLogTime; - // Log every minute or 100k events - if (now - prevTime > TimeUnit.MINUTES.toMillis(TIME_THRESHOLD_FOR_LOG_MINUTES) - || rowsConsumed >= MSG_COUNT_THRESHOLD_FOR_LOG) { - _segmentLogger.info( - "Consumed {} events from (rate:{}/s), currentOffset={}, numRowsConsumedSoFar={}, numRowsIndexedSoFar={}", - rowsConsumed, (float) (rowsConsumed) * 1000 / (now - prevTime), _currentOffset, _numRowsConsumed, - _numRowsIndexed); - _lastConsumedCount = _numRowsConsumed; - _lastLogTime = now; - } - } - - @Override - public MutableSegment getSegment() { - return _realtimeSegment; - } - - @Override - public String getSegmentName() { - return _segmentNameStr; - } - - public void forceCommit() { - _forceCommitMessageReceived = true; - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/PeerSchemeSplitSegmentCommitter.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/PeerSchemeSplitSegmentCommitter.java deleted file mode 100644 index 70d9de86d7e9..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/PeerSchemeSplitSegmentCommitter.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.data.manager.realtime; - -import java.io.File; -import java.net.URI; -import org.apache.pinot.common.protocols.SegmentCompletionProtocol; -import org.apache.pinot.common.utils.LLCSegmentName; -import org.apache.pinot.server.realtime.ServerSegmentCompletionProtocolHandler; -import org.apache.pinot.spi.utils.CommonConstants; -import org.apache.pinot.spi.utils.StringUtil; -import org.slf4j.Logger; - - -public class PeerSchemeSplitSegmentCommitter extends SplitSegmentCommitter { - public PeerSchemeSplitSegmentCommitter(Logger segmentLogger, ServerSegmentCompletionProtocolHandler protocolHandler, - SegmentCompletionProtocol.Request.Params params, SegmentUploader segmentUploader) { - super(segmentLogger, protocolHandler, params, segmentUploader); - } - - // Always return a uri string even if the segment upload fails and returns a null uri. - // If the segment upload fails, put peer:///segment_name in the segment location to notify the controller it is a - // peer download scheme. - protected String uploadSegment(File segmentTarFile, SegmentUploader segmentUploader, - SegmentCompletionProtocol.Request.Params params) { - URI segmentLocation = segmentUploader.uploadSegment(segmentTarFile, new LLCSegmentName(params.getSegmentName())); - if (segmentLocation == null) { - return StringUtil.join("/", CommonConstants.Segment.PEER_SEGMENT_DOWNLOAD_SCHEME, params.getSegmentName()); - } - return segmentLocation.toString(); - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/PinotFSSegmentUploader.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/PinotFSSegmentUploader.java index 909d863b987e..c39a96c1a243 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/PinotFSSegmentUploader.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/PinotFSSegmentUploader.java @@ -20,16 +20,20 @@ import java.io.File; import java.net.URI; -import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.apache.pinot.common.metrics.ServerMeter; +import org.apache.pinot.common.metrics.ServerMetrics; +import org.apache.pinot.common.metrics.ServerTimer; import org.apache.pinot.common.utils.LLCSegmentName; import org.apache.pinot.spi.filesystem.PinotFS; import org.apache.pinot.spi.filesystem.PinotFSFactory; import org.apache.pinot.spi.utils.StringUtil; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -43,22 +47,34 @@ public class PinotFSSegmentUploader implements SegmentUploader { private static final Logger LOGGER = LoggerFactory.getLogger(PinotFSSegmentUploader.class); public static final int DEFAULT_SEGMENT_UPLOAD_TIMEOUT_MILLIS = 10 * 1000; - private String _segmentStoreUriStr; - private ExecutorService _executorService = Executors.newCachedThreadPool(); - private int _timeoutInMs; + private final String _segmentStoreUriStr; + private final ExecutorService _executorService = Executors.newCachedThreadPool(); + private final int _timeoutInMs; + private final ServerMetrics _serverMetrics; - public PinotFSSegmentUploader(String segmentStoreDirUri, int timeoutMillis) { + public PinotFSSegmentUploader(String segmentStoreDirUri, int timeoutMillis, ServerMetrics serverMetrics) { _segmentStoreUriStr = segmentStoreDirUri; _timeoutInMs = timeoutMillis; + _serverMetrics = serverMetrics; } + @Override public URI uploadSegment(File segmentFile, LLCSegmentName segmentName) { + return uploadSegment(segmentFile, segmentName, _timeoutInMs); + } + + @Override + public URI uploadSegment(File segmentFile, LLCSegmentName segmentName, int timeoutInMillis) { if (_segmentStoreUriStr == null || _segmentStoreUriStr.isEmpty()) { + LOGGER.error("Missing segment store uri. Failed to upload segment file {} for {}.", segmentFile.getName(), + segmentName.getSegmentName()); return null; } + final String rawTableName = TableNameBuilder.extractRawTableName(segmentName.getTableName()); Callable uploadTask = () -> { URI destUri = new URI(StringUtil.join(File.separator, _segmentStoreUriStr, segmentName.getTableName(), - segmentName.getSegmentName() + UUID.randomUUID().toString())); + SegmentCompletionUtils.generateTmpSegmentFileName(segmentName.getSegmentName()))); + long startTime = System.currentTimeMillis(); try { PinotFS pinotFS = PinotFSFactory.create(new URI(_segmentStoreUriStr).getScheme()); // Check and delete any existing segment file. @@ -69,21 +85,32 @@ public URI uploadSegment(File segmentFile, LLCSegmentName segmentName) { return destUri; } catch (Exception e) { LOGGER.warn("Failed copy segment tar file {} to segment store {}: {}", segmentFile.getName(), destUri, e); + } finally { + long duration = System.currentTimeMillis() - startTime; + _serverMetrics.addTimedTableValue(rawTableName, ServerTimer.SEGMENT_UPLOAD_TIME_MS, duration, + TimeUnit.MILLISECONDS); } return null; }; Future future = _executorService.submit(uploadTask); try { - URI segmentLocation = future.get(_timeoutInMs, TimeUnit.MILLISECONDS); + URI segmentLocation = future.get(timeoutInMillis, TimeUnit.MILLISECONDS); LOGGER.info("Successfully upload segment {} to {}.", segmentName, segmentLocation); + _serverMetrics.addMeteredTableValue(rawTableName, + segmentLocation == null ? ServerMeter.SEGMENT_UPLOAD_FAILURE : ServerMeter.SEGMENT_UPLOAD_SUCCESS, 1); return segmentLocation; } catch (InterruptedException e) { LOGGER.info("Interrupted while waiting for segment upload of {} to {}.", segmentName, _segmentStoreUriStr); Thread.currentThread().interrupt(); + } catch (TimeoutException e) { + // Emit a separate metric for timeout since this is relatively more common than other errors. + _serverMetrics.addMeteredTableValue(rawTableName, ServerMeter.SEGMENT_UPLOAD_TIMEOUT, 1); + LOGGER.warn("Timed out waiting to upload segment: {} for table: {}", segmentName.getSegmentName(), rawTableName); } catch (Exception e) { - LOGGER - .warn("Failed to upload file {} of segment {} for table {} ", segmentFile.getAbsolutePath(), segmentName, e); + LOGGER.warn("Failed to upload file {} of segment {} for table {}", + segmentFile.getAbsolutePath(), segmentName, rawTableName, e); } + _serverMetrics.addMeteredTableValue(rawTableName, ServerMeter.SEGMENT_UPLOAD_FAILURE, 1); return null; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeConsumptionRateManager.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeConsumptionRateManager.java index f2bb60ce819b..d0a3890e0819 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeConsumptionRateManager.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeConsumptionRateManager.java @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - package org.apache.pinot.core.data.manager.realtime; import com.google.common.annotations.VisibleForTesting; @@ -31,19 +30,26 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import org.apache.pinot.common.metrics.ServerGauge; +import org.apache.pinot.common.metrics.ServerMeter; import org.apache.pinot.common.metrics.ServerMetrics; +import org.apache.pinot.spi.env.PinotConfiguration; import org.apache.pinot.spi.stream.StreamConfig; import org.apache.pinot.spi.stream.StreamConsumerFactory; import org.apache.pinot.spi.stream.StreamConsumerFactoryProvider; import org.apache.pinot.spi.stream.StreamMetadataProvider; +import org.apache.pinot.spi.utils.CommonConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * This class is responsible for creating realtime consumption rate limiters. The rate limit, specified in - * StreamConfig of table config, is for the entire topic. The effective rate limit for each partition is simply the - * specified rate limit divided by the partition count. + * This class is responsible for creating realtime consumption rate limiters. + * It contains one rate limiter for the entire server and multiple table partition level rate limiters. + * Server rate limiter is used to throttle the overall consumption rate of the server and configured via + * cluster or server config. + * For table partition level rate limiter, the rate limit value specified in StreamConfig of table config, is for the + * entire topic. The effective rate limit for each partition is simply the specified rate limit divided by the + * partition count. * This class leverages a cache for storing partition count for different topics as retrieving partition count from * stream is a bit expensive and also the same count will be used of all partition consumers of the same topic. */ @@ -51,6 +57,10 @@ public class RealtimeConsumptionRateManager { private static final Logger LOGGER = LoggerFactory.getLogger(RealtimeConsumptionRateManager.class); private static final int CACHE_ENTRY_EXPIRATION_TIME_IN_MINUTES = 10; + private static final String SERVER_CONSUMPTION_RATE_METRIC_KEY_NAME = + ServerMeter.REALTIME_ROWS_CONSUMED.getMeterName(); + private ConsumptionRateLimiter _serverRateLimiter = NOOP_RATE_LIMITER; + // stream config object is required for fetching the partition count from the stream private final LoadingCache _streamConfigToTopicPartitionCountMap; private volatile boolean _isThrottlingAllowed = false; @@ -73,9 +83,28 @@ public void enableThrottling() { _isThrottlingAllowed = true; } + public ConsumptionRateLimiter createServerRateLimiter(PinotConfiguration serverConfig, ServerMetrics serverMetrics) { + double serverRateLimit = + serverConfig.getProperty(CommonConstants.Server.CONFIG_OF_SERVER_CONSUMPTION_RATE_LIMIT, + CommonConstants.Server.DEFAULT_SERVER_CONSUMPTION_RATE_LIMIT); + if (serverRateLimit <= 0) { + LOGGER.warn("Invalid server consumption rate limit: {}, throttling is disabled", serverRateLimit); + _serverRateLimiter = NOOP_RATE_LIMITER; + } else { + LOGGER.info("A server consumption rate limiter is set up with rate limit: {}", serverRateLimit); + MetricEmitter metricEmitter = new MetricEmitter(serverMetrics, SERVER_CONSUMPTION_RATE_METRIC_KEY_NAME); + _serverRateLimiter = new RateLimiterImpl(serverRateLimit, metricEmitter); + } + return _serverRateLimiter; + } + + public ConsumptionRateLimiter getServerRateLimiter() { + return _serverRateLimiter; + } + public ConsumptionRateLimiter createRateLimiter(StreamConfig streamConfig, String tableName, ServerMetrics serverMetrics, String metricKeyName) { - if (!streamConfig.getTopicConsumptionRateLimit().isPresent()) { + if (streamConfig.getTopicConsumptionRateLimit().isEmpty()) { return NOOP_RATE_LIMITER; } int partitionCount; @@ -147,9 +176,14 @@ private RateLimiterImpl(double rate, MetricEmitter metricEmitter) { @Override public void throttle(int numMsgs) { - _metricEmitter.emitMetric(numMsgs, _rate, Clock.systemUTC().instant()); - if (InstanceHolder.INSTANCE._isThrottlingAllowed && numMsgs > 0) { - _rateLimiter.acquire(numMsgs); + if (InstanceHolder.INSTANCE._isThrottlingAllowed) { + // Only emit metrics when throttling is allowed. Throttling is not enabled. + // until the server has passed startup checks. Otherwise, we will see + // consumption well over 100% during startup. + _metricEmitter.emitMetric(numMsgs, _rate, Clock.systemUTC().instant()); + if (numMsgs > 0) { + _rateLimiter.acquire(numMsgs); + } } } @@ -172,7 +206,7 @@ interface PartitionCountFetcher { try (StreamMetadataProvider streamMetadataProvider = factory.createStreamMetadataProvider(clientId)) { return streamMetadataProvider.fetchPartitionCount(/*maxWaitTimeMs*/10_000); } catch (Exception e) { - LOGGER.warn("Error fetching metadata for topic " + streamConfig.getTopicName(), e); + LOGGER.warn("Error fetching metadata for topic {}", streamConfig.getTopicName(), e); return null; } }; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeSegmentDataManager.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeSegmentDataManager.java index e1e06c908109..dbfe885cc0d6 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeSegmentDataManager.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeSegmentDataManager.java @@ -18,55 +18,1881 @@ */ package org.apache.pinot.core.data.manager.realtime; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.util.concurrent.Uninterruptibles; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Lock; +import java.util.function.BooleanSupplier; +import javax.annotation.Nullable; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.pinot.common.Utils; +import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; +import org.apache.pinot.common.metrics.ServerGauge; +import org.apache.pinot.common.metrics.ServerMeter; import org.apache.pinot.common.metrics.ServerMetrics; +import org.apache.pinot.common.protocols.SegmentCompletionProtocol; +import org.apache.pinot.common.restlet.resources.SegmentErrorInfo; +import org.apache.pinot.common.utils.LLCSegmentName; +import org.apache.pinot.common.utils.TarGzCompressionUtils; +import org.apache.pinot.core.data.manager.realtime.RealtimeConsumptionRateManager.ConsumptionRateLimiter; import org.apache.pinot.segment.local.data.manager.SegmentDataManager; +import org.apache.pinot.segment.local.dedup.PartitionDedupMetadataManager; +import org.apache.pinot.segment.local.indexsegment.mutable.MutableSegmentImpl; import org.apache.pinot.segment.local.io.writer.impl.DirectMemoryManager; import org.apache.pinot.segment.local.io.writer.impl.MmapMemoryManager; +import org.apache.pinot.segment.local.realtime.converter.ColumnIndicesForRealtimeTable; +import org.apache.pinot.segment.local.realtime.converter.RealtimeSegmentConverter; +import org.apache.pinot.segment.local.realtime.impl.RealtimeSegmentConfig; +import org.apache.pinot.segment.local.segment.creator.TransformPipeline; +import org.apache.pinot.segment.local.segment.index.loader.IndexLoadingConfig; +import org.apache.pinot.segment.local.upsert.PartitionUpsertMetadataManager; +import org.apache.pinot.segment.local.utils.IngestionUtils; import org.apache.pinot.segment.spi.MutableSegment; +import org.apache.pinot.segment.spi.V1Constants; +import org.apache.pinot.segment.spi.creator.SegmentVersion; import org.apache.pinot.segment.spi.memory.PinotDataBufferMemoryManager; +import org.apache.pinot.segment.spi.partition.PartitionFunctionFactory; +import org.apache.pinot.segment.spi.store.SegmentDirectoryPaths; +import org.apache.pinot.server.realtime.ServerSegmentCompletionProtocolHandler; +import org.apache.pinot.spi.config.instance.InstanceDataManagerConfig; +import org.apache.pinot.spi.config.table.ColumnPartitionConfig; +import org.apache.pinot.spi.config.table.CompletionConfig; +import org.apache.pinot.spi.config.table.IndexingConfig; +import org.apache.pinot.spi.config.table.SegmentPartitionConfig; +import org.apache.pinot.spi.config.table.SegmentZKPropsConfig; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.spi.data.readers.GenericRow; +import org.apache.pinot.spi.metrics.PinotMeter; +import org.apache.pinot.spi.plugin.PluginManager; +import org.apache.pinot.spi.recordenricher.RecordEnricherPipeline; import org.apache.pinot.spi.stream.ConsumerPartitionState; +import org.apache.pinot.spi.stream.LongMsgOffset; +import org.apache.pinot.spi.stream.MessageBatch; +import org.apache.pinot.spi.stream.OffsetCriteria; +import org.apache.pinot.spi.stream.PartitionGroupConsumer; +import org.apache.pinot.spi.stream.PartitionGroupConsumptionStatus; import org.apache.pinot.spi.stream.PartitionLagState; +import org.apache.pinot.spi.stream.PermanentConsumerException; +import org.apache.pinot.spi.stream.RowMetadata; +import org.apache.pinot.spi.stream.StreamConfig; +import org.apache.pinot.spi.stream.StreamConsumerFactory; +import org.apache.pinot.spi.stream.StreamConsumerFactoryProvider; +import org.apache.pinot.spi.stream.StreamDataDecoder; +import org.apache.pinot.spi.stream.StreamDataDecoderImpl; +import org.apache.pinot.spi.stream.StreamDataDecoderResult; +import org.apache.pinot.spi.stream.StreamMessage; +import org.apache.pinot.spi.stream.StreamMessageDecoder; +import org.apache.pinot.spi.stream.StreamMessageMetadata; +import org.apache.pinot.spi.stream.StreamMetadataProvider; +import org.apache.pinot.spi.stream.StreamPartitionMsgOffset; +import org.apache.pinot.spi.stream.StreamPartitionMsgOffsetFactory; import org.apache.pinot.spi.utils.CommonConstants.ConsumerState; +import org.apache.pinot.spi.utils.CommonConstants.Segment.Realtime.CompletionMode; +import org.apache.pinot.spi.utils.IngestionConfigUtils; +import org.apache.pinot.spi.utils.retry.AttemptsExceededException; +import org.apache.pinot.spi.utils.retry.RetriableOperationException; +import org.apache.pinot.spi.utils.retry.RetryPolicies; +import org.apache.pinot.spi.utils.retry.RetryPolicy; +import org.joda.time.DateTime; +import org.joda.time.DateTimeZone; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -public abstract class RealtimeSegmentDataManager extends SegmentDataManager { +/** + * Segment data manager for low level consumer realtime segments, which manages consumption and segment completion. + */ +public class RealtimeSegmentDataManager extends SegmentDataManager { + + @VisibleForTesting + public enum State { + // The state machine starts off with this state. While in this state we consume stream events + // and index them in memory. We continue to be in this state until the end criteria is satisfied + // (time or number of rows) + INITIAL_CONSUMING, + + // In this state, we consume from stream until we reach the _finalOffset (exclusive) + CATCHING_UP, + + // In this state, we sleep for MAX_HOLDING_TIME_MS, and the make a segmentConsumed() call to the + // controller. + HOLDING, + + // We have been asked to go Online from Consuming state, and are trying a last attempt to catch up to the + // target offset. In this state, we have a time constraint as well as a final offset constraint to look into + // before we stop consuming. + CONSUMING_TO_ONLINE, + + // We have been asked by the controller to retain the segment we have in memory at the current offset. + // We should build the segment, and replace it with the in-memory segment. + RETAINING, + + // We have been asked by the controller to commit the segment at the current offset. Build the segment + // and make a segmentCommit() call to the controller. + COMMITTING, + + // We have been asked to discard the in-memory segment we have. We will be serving queries, but not consuming + // anymore rows from stream. We wait for a helix transition to go ONLINE, at which point, we can download the + // segment from the controller and replace it with the in-memory segment. + DISCARDED, + + // We have replaced our in-memory segment with the segment that has been built locally. + RETAINED, + + // We have committed our segment to the controller, and also replaced it locally with the constructed segment. + COMMITTED, + + // Something went wrong, we need to download the segment when we get the ONLINE transition. + ERROR; + + public boolean shouldConsume() { + return this.equals(INITIAL_CONSUMING) || this.equals(CATCHING_UP) || this.equals(CONSUMING_TO_ONLINE); + } + + public boolean isFinal() { + return this.equals(ERROR) || this.equals(COMMITTED) || this.equals(RETAINED) || this.equals(DISCARDED); + } + } + + @VisibleForTesting + public class SegmentBuildDescriptor { + final File _segmentTarFile; + final Map _metadataFileMap; + final StreamPartitionMsgOffset _offset; + final long _waitTimeMillis; + final long _buildTimeMillis; + final long _segmentSizeBytes; + + public SegmentBuildDescriptor(@Nullable File segmentTarFile, @Nullable Map metadataFileMap, + StreamPartitionMsgOffset offset, long buildTimeMillis, long waitTimeMillis, long segmentSizeBytes) { + _segmentTarFile = segmentTarFile; + _metadataFileMap = metadataFileMap; + _offset = _streamPartitionMsgOffsetFactory.create(offset); + _buildTimeMillis = buildTimeMillis; + _waitTimeMillis = waitTimeMillis; + _segmentSizeBytes = segmentSizeBytes; + } + + public StreamPartitionMsgOffset getOffset() { + return _offset; + } + + public long getBuildTimeMillis() { + return _buildTimeMillis; + } + + public long getWaitTimeMillis() { + return _waitTimeMillis; + } + + @Nullable + public File getSegmentTarFile() { + return _segmentTarFile; + } + + @Nullable + public Map getMetadataFiles() { + return _metadataFileMap; + } + + public long getSegmentSizeBytes() { + return _segmentSizeBytes; + } + + public void deleteSegmentFile() { + if (_segmentTarFile != null) { + FileUtils.deleteQuietly(_segmentTarFile); + } + } + } + + public static final String RESOURCE_TEMP_DIR_NAME = "_tmp"; + + private static final int MINIMUM_CONSUME_TIME_MINUTES = 10; + private static final long TIME_THRESHOLD_FOR_LOG_MINUTES = 1; + private static final long TIME_EXTENSION_ON_EMPTY_SEGMENT_HOURS = 1; + private static final int MSG_COUNT_THRESHOLD_FOR_LOG = 100000; + private static final int BUILD_TIME_LEASE_SECONDS = 30; + private static final int MAX_CONSECUTIVE_ERROR_COUNT = 5; + + private final SegmentZKMetadata _segmentZKMetadata; + private final TableConfig _tableConfig; + private final RealtimeTableDataManager _realtimeTableDataManager; + private final StreamDataDecoder _streamDataDecoder; + private final int _segmentMaxRowCount; + private final String _resourceDataDir; + private final IndexLoadingConfig _indexLoadingConfig; + private final Schema _schema; + // Semaphore for each partitionGroupId only, which is to prevent two different stream consumers + // from consuming with the same partitionGroupId in parallel in the same host. + // See the comments in {@link RealtimeTableDataManager}. + private final Semaphore _partitionGroupConsumerSemaphore; + // A boolean flag to check whether the current thread has acquired the semaphore. + // This boolean is needed because the semaphore is shared by threads; every thread holding this semaphore can + // modify the permit. This boolean make sure the semaphore gets released only once when the partition group stops + // consuming. + private final AtomicBoolean _acquiredConsumerSemaphore; + private final ServerMetrics _serverMetrics; + private final PartitionUpsertMetadataManager _partitionUpsertMetadataManager; + private final BooleanSupplier _isReadyToConsumeData; + private final MutableSegmentImpl _realtimeSegment; + private volatile StreamPartitionMsgOffset _currentOffset; // Next offset to be consumed + private volatile State _state; + private volatile int _numRowsConsumed = 0; + private volatile int _numRowsIndexed = 0; // Can be different from _numRowsConsumed when metrics update is enabled. + private volatile int _numRowsErrored = 0; + private volatile int _consecutiveErrorCount = 0; + private long _startTimeMs = 0; + private final IdleTimer _idleTimer = new IdleTimer(); + private final String _segmentNameStr; + private final SegmentVersion _segmentVersion; + private final SegmentBuildTimeLeaseExtender _leaseExtender; + private SegmentBuildDescriptor _segmentBuildDescriptor; + private final StreamConsumerFactory _streamConsumerFactory; + private final StreamPartitionMsgOffsetFactory _streamPartitionMsgOffsetFactory; + + // Segment end criteria + private volatile long _consumeEndTime = 0; + private volatile boolean _hasMessagesFetched = false; + private volatile boolean _endOfPartitionGroup = false; + private volatile boolean _forceCommitMessageReceived = false; + private volatile StreamPartitionMsgOffset _finalOffset; // Exclusive, used when we want to catch up to this one + private volatile boolean _shouldStop = false; + + // It takes 30s to locate controller leader, and more if there are multiple controller failures. + // For now, we let 31s pass for this state transition. + private static final int MAX_TIME_FOR_CONSUMING_TO_ONLINE_IN_SECONDS = 31; + + private Thread _consumerThread; + private final int _partitionGroupId; + private final PartitionGroupConsumptionStatus _partitionGroupConsumptionStatus; + final String _clientId; + private final RecordEnricherPipeline _recordEnricherPipeline; + private final TransformPipeline _transformPipeline; + private PartitionGroupConsumer _partitionGroupConsumer = null; + private StreamMetadataProvider _partitionMetadataProvider = null; + private final File _resourceTmpDir; + private final String _tableNameWithType; + private final ColumnIndicesForRealtimeTable _columnIndicesForRealtimeTable; + private final Logger _segmentLogger; + private final String _tableStreamName; + private final PinotDataBufferMemoryManager _memoryManager; + private final AtomicLong _lastUpdatedRowsIndexed = new AtomicLong(0); + private final String _instanceId; + private final ServerSegmentCompletionProtocolHandler _protocolHandler; + private final long _consumeStartTime; + private final StreamPartitionMsgOffset _startOffset; + private final StreamConfig _streamConfig; + + private RowMetadata _lastRowMetadata; + private long _lastConsumedTimestampMs = -1; + + private long _lastLogTime = 0; + private int _lastConsumedCount = 0; + private String _stopReason = null; + private final Semaphore _segBuildSemaphore; + private final boolean _isOffHeap; + private final boolean _nullHandlingEnabled; + private final SegmentCommitterFactory _segmentCommitterFactory; + private final ConsumptionRateLimiter _partitionRateLimiter; + private final ConsumptionRateLimiter _serverRateLimiter; + + private final StreamPartitionMsgOffset _latestStreamOffsetAtStartupTime; + private final CompletionMode _segmentCompletionMode; + private final List _filteredMessageOffsets = new ArrayList<>(); + private final boolean _allowConsumptionDuringCommit; + private boolean _trackFilteredMessageOffsets = false; + + // TODO each time this method is called, we print reason for stop. Good to print only once. + private boolean endCriteriaReached() { + Preconditions.checkState(_state.shouldConsume(), "Incorrect state %s", _state); + long now = now(); + switch (_state) { + case INITIAL_CONSUMING: + // The segment has been created, and we have not posted a segmentConsumed() message on the controller yet. + // We need to consume as much data as available, until we have encountered one of the following scenarios: + // - the max number of rows has been reached + // - the max time we are allowed to consume has passed; + // - partition group is ended + // - force commit message has been received + if (now >= _consumeEndTime) { + if (!_hasMessagesFetched) { + _segmentLogger.info("No events came in, extending time by {} hours", TIME_EXTENSION_ON_EMPTY_SEGMENT_HOURS); + _consumeEndTime += TimeUnit.HOURS.toMillis(TIME_EXTENSION_ON_EMPTY_SEGMENT_HOURS); + return false; + } + _segmentLogger + .info("Stopping consumption due to time limit start={} now={} numRowsConsumed={} numRowsIndexed={}", + _startTimeMs, now, _numRowsConsumed, _numRowsIndexed); + _stopReason = SegmentCompletionProtocol.REASON_TIME_LIMIT; + return true; + } else if (_numRowsIndexed >= _segmentMaxRowCount) { + _segmentLogger.info("Stopping consumption due to row limit nRows={} numRowsIndexed={}, numRowsConsumed={}", + _segmentMaxRowCount, _numRowsIndexed, _numRowsConsumed); + _stopReason = SegmentCompletionProtocol.REASON_ROW_LIMIT; + return true; + } else if (_endOfPartitionGroup) { + _segmentLogger.info("Stopping consumption due to end of partitionGroup reached nRows={} numRowsIndexed={}, " + + "numRowsConsumed={}", _segmentMaxRowCount, _numRowsIndexed, _numRowsConsumed); + _stopReason = SegmentCompletionProtocol.REASON_END_OF_PARTITION_GROUP; + return true; + } else if (_forceCommitMessageReceived) { + _segmentLogger.info("Stopping consumption due to force commit - numRowsConsumed={} numRowsIndexed={}", + _numRowsConsumed, _numRowsIndexed); + _stopReason = SegmentCompletionProtocol.REASON_FORCE_COMMIT_MESSAGE_RECEIVED; + return true; + } + return false; + + case CATCHING_UP: + _stopReason = null; + // We have posted segmentConsumed() at least once, and the controller is asking us to catch up to a certain + // offset. + // There is no time limit here, so just check to see that we are still within the offset we need to reach. + // Going past the offset is an exception. + if (_currentOffset.compareTo(_finalOffset) == 0) { + _segmentLogger.info("Caught up to offset={}, state={}", _finalOffset, _state.toString()); + return true; + } + if (_currentOffset.compareTo(_finalOffset) > 0) { + _segmentLogger.error("Offset higher in state={}, current={}, final={}", _state.toString(), _currentOffset, + _finalOffset); + throw new RuntimeException("Past max offset"); + } + return false; + + case CONSUMING_TO_ONLINE: + // We are attempting to go from CONSUMING to ONLINE state. We are making a last attempt to catch up to the + // target offset. We have a time constraint, and need to stop consuming if we cannot get to the target offset + // within that time. + if (_currentOffset.compareTo(_finalOffset) == 0) { + _segmentLogger.info("Caught up to offset={}, state={}", _finalOffset, _state.toString()); + return true; + } else if (now >= _consumeEndTime) { + _segmentLogger.info("Past max time budget: offset={}, state={}", _currentOffset, _state.toString()); + return true; + } + if (_currentOffset.compareTo(_finalOffset) > 0) { + _segmentLogger.error("Offset higher in state={}, current={}, final={}", _state.toString(), _currentOffset, + _finalOffset); + throw new RuntimeException("Past max offset"); + } + return false; + default: + _segmentLogger.error("Illegal state: {}", _state); + throw new RuntimeException("Illegal state to consume"); + } + } + + private void handleTransientStreamErrors(Exception e) + throws Exception { + _consecutiveErrorCount++; + _serverMetrics.addMeteredGlobalValue(ServerMeter.REALTIME_CONSUMPTION_EXCEPTIONS, 1L); + _serverMetrics.addMeteredTableValue(_tableStreamName, ServerMeter.REALTIME_CONSUMPTION_EXCEPTIONS, + 1L); + if (_consecutiveErrorCount > MAX_CONSECUTIVE_ERROR_COUNT) { + _segmentLogger.warn("Stream transient exception when fetching messages, stopping consumption after {} attempts", + _consecutiveErrorCount, e); + throw e; + } else { + _segmentLogger.warn("Stream transient exception when fetching messages, retrying (count={})", + _consecutiveErrorCount, e); + Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS); + recreateStreamConsumer("Too many transient errors"); + } + } + + protected boolean consumeLoop() + throws Exception { + // At this point, we know that we can potentially move the offset, so the old saved segment file is not valid + // anymore. Remove the file if it exists. + removeSegmentFile(); + + _numRowsErrored = 0; + long idlePipeSleepTimeMillis = 100; + long idleTimeoutMillis = _streamConfig.getIdleTimeoutMillis(); + _idleTimer.init(); + + StreamPartitionMsgOffset lastUpdatedOffset = _streamPartitionMsgOffsetFactory + .create(_currentOffset); // so that we always update the metric when we enter this method. + + _segmentLogger.info("Starting consumption loop start offset {}, finalOffset {}", _currentOffset, _finalOffset); + while (!_shouldStop && !endCriteriaReached()) { + _serverMetrics.setValueOfTableGauge(_clientId, ServerGauge.LLC_PARTITION_CONSUMING, 1); + // Consume for the next readTime ms, or we get to final offset, whichever happens earlier, + // Update _currentOffset upon return from this method + MessageBatch messageBatch; + try { + messageBatch = _partitionGroupConsumer.fetchMessages(_currentOffset, _streamConfig.getFetchTimeoutMillis()); + //track realtime rows fetched on a table level. This included valid + invalid rows + _serverMetrics.addMeteredTableValue(_clientId, ServerMeter.REALTIME_ROWS_FETCHED, + messageBatch.getUnfilteredMessageCount()); + if (_segmentLogger.isDebugEnabled()) { + _segmentLogger.debug("message batch received. filtered={} unfiltered={} endOfPartitionGroup={}", + messageBatch.getMessageCount(), messageBatch.getUnfilteredMessageCount(), + messageBatch.isEndOfPartitionGroup()); + } + _endOfPartitionGroup = messageBatch.isEndOfPartitionGroup(); + _consecutiveErrorCount = 0; + } catch (PermanentConsumerException e) { + _serverMetrics.addMeteredGlobalValue(ServerMeter.REALTIME_CONSUMPTION_EXCEPTIONS, 1L); + _serverMetrics.addMeteredTableValue(_tableStreamName, ServerMeter.REALTIME_CONSUMPTION_EXCEPTIONS, 1L); + _segmentLogger.warn("Permanent exception from stream when fetching messages, stopping consumption", e); + throw e; + } catch (Exception e) { + //track realtime rows fetched on a table level. This included valid + invalid rows + // all exceptions but PermanentConsumerException are handled the same way + // can be a TimeoutException or TransientConsumerException routinely + // Unknown exception from stream. Treat as a transient exception. + // One such exception seen so far is java.net.SocketTimeoutException + handleTransientStreamErrors(e); + continue; + } catch (Throwable t) { + //track realtime rows fetched on a table level. This included valid + invalid rows + _segmentLogger.warn("Stream error when fetching messages, stopping consumption", t); + throw t; + } + + reportDataLoss(messageBatch); + + boolean endCriteriaReached = processStreamEvents(messageBatch, idlePipeSleepTimeMillis); + + if (_currentOffset.compareTo(lastUpdatedOffset) != 0) { + _idleTimer.markEventConsumed(); + // We consumed something. Update the highest stream offset as well as partition-consuming metric. + // TODO Issue 5359 Need to find a way to bump metrics without getting actual offset value. + if (_currentOffset instanceof LongMsgOffset) { + // TODO: only LongMsgOffset supplies long offset value. + _serverMetrics.setValueOfTableGauge(_clientId, ServerGauge.HIGHEST_STREAM_OFFSET_CONSUMED, + ((LongMsgOffset) _currentOffset).getOffset()); + } + lastUpdatedOffset = _streamPartitionMsgOffsetFactory.create(_currentOffset); + } else if (endCriteriaReached) { + // At this point current offset has not moved because processStreamEvents() has exited before processing a + // single message + if (_segmentLogger.isDebugEnabled()) { + _segmentLogger.debug("No messages processed before end criteria was reached. Staying at offset {}", + _currentOffset); + } + // We check this flag again further down + } else if (messageBatch.getUnfilteredMessageCount() > 0) { + _idleTimer.markEventConsumed(); + // we consumed something from the stream but filtered all the content out, + // so we need to advance the offsets to avoid getting stuck + StreamPartitionMsgOffset nextOffset = messageBatch.getOffsetOfNextBatch(); + if (_segmentLogger.isDebugEnabled()) { + _segmentLogger.debug("Skipped empty batch. Advancing from {} to {}", _currentOffset, nextOffset); + } + _currentOffset = nextOffset; + lastUpdatedOffset = _streamPartitionMsgOffsetFactory.create(nextOffset); + } else { + // We did not consume any rows. + long timeSinceStreamLastCreatedOrConsumedMs = _idleTimer.getTimeSinceStreamLastCreatedOrConsumedMs(); + + if (idleTimeoutMillis >= 0 && (timeSinceStreamLastCreatedOrConsumedMs > idleTimeoutMillis)) { + // Create a new stream consumer wrapper, in case we are stuck on something. + recreateStreamConsumer( + String.format("Total idle time: %d ms exceeded idle timeout: %d ms", + timeSinceStreamLastCreatedOrConsumedMs, idleTimeoutMillis)); + _idleTimer.markStreamCreated(); + } + } + + if (endCriteriaReached) { + // check this flag to avoid calling endCriteriaReached() at the beginning of the loop + break; + } + } + + if (_numRowsErrored > 0) { + _serverMetrics.addMeteredTableValue(_clientId, ServerMeter.ROWS_WITH_ERRORS, _numRowsErrored); + _serverMetrics.addMeteredTableValue(_tableStreamName, ServerMeter.ROWS_WITH_ERRORS, _numRowsErrored); + } + return true; + } + + /** + * @param messageBatch batch of messages to process + * @param idlePipeSleepTimeMillis wait time in case no messages were read + * @return returns true if the process loop ended before processing the batch, false + * otherwise + */ + private boolean processStreamEvents(MessageBatch messageBatch, long idlePipeSleepTimeMillis) { + int messageCount = messageBatch.getMessageCount(); + _partitionRateLimiter.throttle(messageCount); + _serverRateLimiter.throttle(messageCount); + + PinotMeter realtimeRowsConsumedMeter = null; + PinotMeter realtimeRowsDroppedMeter = null; + PinotMeter realtimeIncompleteRowsConsumedMeter = null; + PinotMeter realtimeRowsSanitizedMeter = null; + + int indexedMessageCount = 0; + int streamMessageCount = 0; + boolean canTakeMore = true; + + TransformPipeline.Result reusedResult = new TransformPipeline.Result(); + boolean prematureExit = false; + + for (int index = 0; index < messageCount; index++) { + prematureExit = _shouldStop || endCriteriaReached(); + if (prematureExit) { + if (_segmentLogger.isDebugEnabled()) { + _segmentLogger.debug("stop processing message batch early shouldStop: {}", _shouldStop); + } + break; + } + if (!canTakeMore) { + // The RealtimeSegmentImpl that we are pushing rows into has indicated that it cannot accept any more + // rows. This can happen in one of two conditions: + // 1. We are in INITIAL_CONSUMING state, and we somehow exceeded the max number of rows we are allowed to + // consume + // for this row. Something is seriously wrong, because endCriteriaReached() should have returned true when + // we hit the row limit. + // Throw an exception. + // + // 2. We are in CATCHING_UP state, and we legally hit this error due to unclean leader election where + // offsets get changed with higher generation numbers for some pinot servers but not others. So, if another + // server (who got a larger stream offset) asked us to catch up to that offset, but we are connected to a + // broker who has smaller offsets, then we may try to push more rows into the buffer than maximum. This + // is a rare case, and we really don't know how to handle this at this time. + // Throw an exception. + // + _segmentLogger + .error("Buffer full with {} rows consumed (row limit {}, indexed {})", _numRowsConsumed, _numRowsIndexed, + _segmentMaxRowCount); + throw new RuntimeException("Realtime segment full"); + } + + // Decode message + StreamMessage streamMessage = messageBatch.getStreamMessage(index); + StreamDataDecoderResult decodedRow = _streamDataDecoder.decode(streamMessage); + StreamMessageMetadata metadata = streamMessage.getMetadata(); + StreamPartitionMsgOffset offset = null; + StreamPartitionMsgOffset nextOffset = null; + if (metadata != null) { + offset = metadata.getOffset(); + nextOffset = metadata.getNextOffset(); + } + // Backward compatible + if (nextOffset == null) { + nextOffset = messageBatch.getNextStreamPartitionMsgOffsetAtIndex(index); + } + if (decodedRow.getException() != null) { + // TODO: based on a config, decide whether the record should be silently dropped or stop further consumption on + // decode error + realtimeRowsDroppedMeter = + _serverMetrics.addMeteredTableValue(_clientId, ServerMeter.INVALID_REALTIME_ROWS_DROPPED, 1, + realtimeRowsDroppedMeter); + _numRowsErrored++; + } else { + try { + _recordEnricherPipeline.run(decodedRow.getResult()); + _transformPipeline.processRow(decodedRow.getResult(), reusedResult); + } catch (Exception e) { + _numRowsErrored++; + // when exception happens we prefer abandoning the whole batch and not partially indexing some rows + reusedResult.getTransformedRows().clear(); + String errorMessage = + String.format("Caught exception while transforming the record at offset: %s , row: %s", offset, + decodedRow.getResult()); + _segmentLogger.error(errorMessage, e); + _realtimeTableDataManager.addSegmentError(_segmentNameStr, new SegmentErrorInfo(now(), errorMessage, e)); + } + if (reusedResult.getSkippedRowCount() > 0) { + realtimeRowsDroppedMeter = _serverMetrics.addMeteredTableValue(_clientId, ServerMeter.REALTIME_ROWS_FILTERED, + reusedResult.getSkippedRowCount(), realtimeRowsDroppedMeter); + if (_trackFilteredMessageOffsets) { + _filteredMessageOffsets.add(offset.toString()); + } + } + if (reusedResult.getIncompleteRowCount() > 0) { + realtimeIncompleteRowsConsumedMeter = + _serverMetrics.addMeteredTableValue(_clientId, ServerMeter.INCOMPLETE_REALTIME_ROWS_CONSUMED, + reusedResult.getIncompleteRowCount(), realtimeIncompleteRowsConsumedMeter); + } + if (reusedResult.getSanitizedRowCount() > 0) { + realtimeRowsSanitizedMeter = + _serverMetrics.addMeteredTableValue(_clientId, ServerMeter.REALTIME_ROWS_SANITIZED, + reusedResult.getSanitizedRowCount(), realtimeRowsSanitizedMeter); + } + List transformedRows = reusedResult.getTransformedRows(); + for (GenericRow transformedRow : transformedRows) { + try { + canTakeMore = _realtimeSegment.index(transformedRow, metadata); + indexedMessageCount++; + _lastRowMetadata = metadata; + _lastConsumedTimestampMs = System.currentTimeMillis(); + realtimeRowsConsumedMeter = + _serverMetrics.addMeteredTableValue(_clientId, ServerMeter.REALTIME_ROWS_CONSUMED, 1, + realtimeRowsConsumedMeter); + _serverMetrics.addMeteredGlobalValue(ServerMeter.REALTIME_ROWS_CONSUMED, 1L); + } catch (Exception e) { + _numRowsErrored++; + String errorMessage = + String.format("Caught exception while indexing the record at offset: %s , row: %s", offset, + transformedRow); + _segmentLogger.error(errorMessage, e); + _realtimeTableDataManager.addSegmentError(_segmentNameStr, new SegmentErrorInfo(now(), errorMessage, e)); + } + } + } + _currentOffset = nextOffset; + _numRowsIndexed = _realtimeSegment.getNumDocsIndexed(); + _numRowsConsumed++; + streamMessageCount++; + } + + updateCurrentDocumentCountMetrics(); + if (messageBatch.getUnfilteredMessageCount() > 0) { + updateIngestionMetrics(messageBatch.getLastMessageMetadata()); + _hasMessagesFetched = true; + if (streamMessageCount > 0 && _segmentLogger.isDebugEnabled()) { + _segmentLogger.debug("Indexed {} messages ({} messages read from stream) current offset {}", + indexedMessageCount, streamMessageCount, _currentOffset); + } + } else if (!prematureExit) { + // Record Pinot ingestion delay as zero since we are up-to-date and no new events + setIngestionDelayToZero(); + if (_segmentLogger.isDebugEnabled()) { + _segmentLogger.debug("empty batch received - sleeping for {}ms", idlePipeSleepTimeMillis); + } + // If there were no messages to be fetched from stream, wait for a little bit as to avoid hammering the stream + Uninterruptibles.sleepUninterruptibly(idlePipeSleepTimeMillis, TimeUnit.MILLISECONDS); + } + return prematureExit; + } + + public class PartitionConsumer implements Runnable { + public void run() { + long initialConsumptionEnd = 0L; + long lastCatchUpStart = 0L; + long catchUpTimeMillis = 0L; + _startTimeMs = now(); + try { + if (!_isReadyToConsumeData.getAsBoolean()) { + do { + //noinspection BusyWait + Thread.sleep(RealtimeTableDataManager.READY_TO_CONSUME_DATA_CHECK_INTERVAL_MS); + } while (!_shouldStop && !_isReadyToConsumeData.getAsBoolean()); + } + + // TODO: + // When reaching here, the current consuming segment has already acquired the consumer semaphore, but there is + // no guarantee that the previous consuming segment is already persisted (replaced with immutable segment). It + // can potentially cause the following problems: + // 1. The snapshot for the previous consuming segment might not be taken since it is not persisted yet + // 2. If the previous consuming segment is dropped but immutable segment is not downloaded and replaced yet, + // it might cause inconsistency (especially for partial upsert because events are not consumed in sequence) + // To address this problem, we should consider releasing the consumer semaphore after the consuming segment is + // persisted. + // Take upsert snapshot before starting consuming events + if (_partitionUpsertMetadataManager != null) { + if (_tableConfig.getUpsertMetadataTTL() > 0) { + // If upsertMetadataTTL is enabled, we will remove expired primary keys from upsertMetadata + // AFTER taking a snapshot. Taking the snapshot first is crucial to capture the final + // state of each key before it exits the TTL window. Out-of-TTL segments are skipped in + // the doAddSegment flow, and the snapshot is used to enableUpsert on the immutable out-of-TTL segment. + // If no snapshot is found, the entire segment is marked as valid and queryable. + _partitionUpsertMetadataManager.takeSnapshot(); + _partitionUpsertMetadataManager.removeExpiredPrimaryKeys(); + } else { + // We should remove deleted-keys first and then take a snapshot. This is because the deletedKeysTTL + // flow removes keys from the map and updates to remove valid doc IDs. By taking the snapshot immediately + // after this process, we save one commit cycle, ensuring that the deletion of valid doc IDs is reflected + // immediately + _partitionUpsertMetadataManager.removeExpiredPrimaryKeys(); + _partitionUpsertMetadataManager.takeSnapshot(); + } + } + + while (!_state.isFinal()) { + if (_state.shouldConsume()) { + consumeLoop(); // Consume until we reached the end criteria, or we are stopped. + } + _serverMetrics.setValueOfTableGauge(_clientId, ServerGauge.LLC_PARTITION_CONSUMING, 0); + if (_shouldStop) { + break; + } + + if (_state == State.INITIAL_CONSUMING) { + initialConsumptionEnd = now(); + _serverMetrics.setValueOfTableGauge(_clientId, + ServerGauge.LAST_REALTIME_SEGMENT_INITIAL_CONSUMPTION_DURATION_SECONDS, + TimeUnit.MILLISECONDS.toSeconds(initialConsumptionEnd - _startTimeMs)); + } else if (_state == State.CATCHING_UP) { + catchUpTimeMillis += now() - lastCatchUpStart; + _serverMetrics + .setValueOfTableGauge(_clientId, ServerGauge.LAST_REALTIME_SEGMENT_CATCHUP_DURATION_SECONDS, + TimeUnit.MILLISECONDS.toSeconds(catchUpTimeMillis)); + } + + // If we are sending segmentConsumed() to the controller, we are in HOLDING state. + _state = State.HOLDING; + SegmentCompletionProtocol.Response response = postSegmentConsumedMsg(); + SegmentCompletionProtocol.ControllerResponseStatus status = response.getStatus(); + switch (status) { + case NOT_LEADER: + // Retain the same state + _segmentLogger.warn("Got not leader response"); + hold(); + break; + case CATCH_UP: + StreamPartitionMsgOffset rspOffset = extractOffset(response); + if (rspOffset.compareTo(_currentOffset) <= 0) { + // Something wrong with the controller. Back off and try again. + _segmentLogger.error("Invalid catchup offset {} in controller response, current offset {}", rspOffset, + _currentOffset); + hold(); + } else { + _state = State.CATCHING_UP; + _finalOffset = rspOffset; + lastCatchUpStart = now(); + // We will restart consumption when we loop back above. + } + break; + case HOLD: + hold(); + break; + case DISCARD: + // Keep this in memory, but wait for the online transition, and download when it comes in. + _state = State.DISCARDED; + break; + case KEEP: { + if (_segmentCompletionMode == CompletionMode.DOWNLOAD) { + _state = State.DISCARDED; + break; + } + _state = State.RETAINING; + // Lock the segment to avoid multiple threads touching the same segment. + Lock segmentLock = _realtimeTableDataManager.getSegmentLock(_segmentNameStr); + // NOTE: We need to lock interruptibly because the lock might already be held by the Helix thread for the + // CONSUMING -> ONLINE state transition. + segmentLock.lockInterruptibly(); + try { + if (buildSegmentAndReplace()) { + _state = State.RETAINED; + } else { + // Could not build segment for some reason. We can only download it. + _state = State.ERROR; + _segmentLogger.error("Could not build segment for {}", _segmentNameStr); + } + } finally { + segmentLock.unlock(); + } + break; + } + case COMMIT: { + _state = State.COMMITTING; + _currentOffset = _partitionGroupConsumer.checkpoint(_currentOffset); + // Lock the segment to avoid multiple threads touching the same segment. + Lock segmentLock = _realtimeTableDataManager.getSegmentLock(_segmentNameStr); + // NOTE: We need to lock interruptibly because the lock might already be held by the Helix thread for the + // CONSUMING -> ONLINE state transition. + segmentLock.lockInterruptibly(); + try { + long buildTimeSeconds = response.getBuildTimeSeconds(); + buildSegmentForCommit(buildTimeSeconds * 1000L); + if (_segmentBuildDescriptor == null) { + // We could not build the segment. Go into error state. + _state = State.ERROR; + _segmentLogger.error("Could not build segment for {}", _segmentNameStr); + break; + } + if (commitSegment(response.getControllerVipUrl())) { + _state = State.COMMITTED; + break; + } + } finally { + segmentLock.unlock(); + } + // If for any reason commit failed, we don't want to be in COMMITTING state when we hold. + // Change the state to HOLDING before looping around. + _state = State.HOLDING; + _segmentLogger.info("Could not commit segment. Retrying after hold"); + hold(); + break; + } + default: + _segmentLogger.error("Holding after response from Controller: {}", response.toJsonString()); + hold(); + break; + } + } + } catch (Exception e) { + if (_shouldStop) { + _segmentLogger.info("Caught exception in consumer thread after stop() is invoked: {}, ignoring the exception", + e.toString()); + } else { + String errorMessage = "Exception while in work"; + _segmentLogger.error(errorMessage, e); + postStopConsumedMsg(e.getClass().getName()); + _state = State.ERROR; + _realtimeTableDataManager.addSegmentError(_segmentNameStr, new SegmentErrorInfo(now(), errorMessage, e)); + _serverMetrics.setValueOfTableGauge(_clientId, ServerGauge.LLC_PARTITION_CONSUMING, 0); + return; + } + } + + removeSegmentFile(); + + if (initialConsumptionEnd != 0L) { + _serverMetrics + .setValueOfTableGauge(_clientId, ServerGauge.LAST_REALTIME_SEGMENT_COMPLETION_DURATION_SECONDS, + TimeUnit.MILLISECONDS.toSeconds(now() - initialConsumptionEnd)); + } + // There is a race condition that the destroy() method can be called which ends up calling stop on the consumer. + // The destroy() method does not wait for the thread to terminate (and reasonably so, we dont want to wait + // forever). + // Since the _shouldStop variable is set to true only in stop() method, we know that the metric will be destroyed, + // so it is ok not to mark it non-consuming, as the main thread will clean up this metric in destroy() method + // as the final step. + if (!_shouldStop) { + _serverMetrics.setValueOfTableGauge(_clientId, ServerGauge.LLC_PARTITION_CONSUMING, 0); + } + } + } + + @VisibleForTesting + protected StreamPartitionMsgOffset extractOffset(SegmentCompletionProtocol.Response response) { + return _streamPartitionMsgOffsetFactory.create(response.getStreamPartitionMsgOffset()); + } + + // Side effect: Modifies _segmentBuildDescriptor if we do not have a valid built segment file and we + // built the segment successfully. + protected void buildSegmentForCommit(long buildTimeLeaseMs) { + try { + if (_segmentBuildDescriptor != null && _segmentBuildDescriptor.getOffset().compareTo(_currentOffset) == 0) { + // Double-check that we have the file, just in case. + File segmentTarFile = _segmentBuildDescriptor.getSegmentTarFile(); + if (segmentTarFile != null && segmentTarFile.exists()) { + return; + } + } + removeSegmentFile(); + if (buildTimeLeaseMs <= 0) { + if (_segBuildSemaphore == null) { + buildTimeLeaseMs = SegmentCompletionProtocol.getDefaultMaxSegmentCommitTimeSeconds() * 1000L; + } else { + // We know we are going to use a semaphore to limit number of segment builds, and could be + // blocked for a long time. The controller has not provided a lease time, so set one to + // some reasonable guess here. + buildTimeLeaseMs = BUILD_TIME_LEASE_SECONDS * 1000; + } + } + _leaseExtender.addSegment(_segmentNameStr, buildTimeLeaseMs, _currentOffset); + _segmentBuildDescriptor = buildSegmentInternal(true); + } finally { + _leaseExtender.removeSegment(_segmentNameStr); + } + } + + /** + * Returns the current offset for the partition group. + */ + public Map getPartitionToCurrentOffset() { + return Collections.singletonMap(String.valueOf(_partitionGroupId), _currentOffset.toString()); + } + + /** + * Returns the state of the consumer. + */ + public ConsumerState getConsumerState() { + return _state == State.ERROR ? ConsumerState.NOT_CONSUMING : ConsumerState.CONSUMING; + } + + /** + * Returns the timestamp of the last consumed message. + */ + public long getLastConsumedTimestamp() { + return _lastConsumedTimestampMs; + } + + /** + * Returns the {@link ConsumerPartitionState} for the partition group. + */ + public Map getConsumerPartitionState() { + String partitionGroupId = String.valueOf(_partitionGroupId); + return Collections.singletonMap(partitionGroupId, new ConsumerPartitionState(partitionGroupId, getCurrentOffset(), + getLastConsumedTimestamp(), fetchLatestStreamOffset(5_000), _lastRowMetadata)); + } + + /** + * Returns the {@link PartitionLagState} for the partition group. + */ + public Map getPartitionToLagState( + Map consumerPartitionStateMap) { + if (_partitionMetadataProvider == null) { + createPartitionMetadataProvider("Get Partition Lag State"); + } + return _partitionMetadataProvider.getCurrentPartitionLagState(consumerPartitionStateMap); + } + + /** + * Checks and reports if the consumer is going through data loss. + * + * @param messageBatch Message batch to validate + */ + private void reportDataLoss(MessageBatch messageBatch) { + if (messageBatch.hasDataLoss()) { + _serverMetrics.addMeteredTableValue(_tableStreamName, ServerMeter.STREAM_DATA_LOSS, 1L); + String message = String.format("Message loss detected in stream partition: %s for table: %s startOffset: %s " + + "batchFirstOffset: %s", _partitionGroupId, _tableNameWithType, _startOffset, + messageBatch.getFirstMessageOffset()); + _segmentLogger.error(message); + _realtimeTableDataManager.addSegmentError(_segmentNameStr, new SegmentErrorInfo(now(), message, null)); + } + } + + public StreamPartitionMsgOffset getCurrentOffset() { + return _currentOffset; + } + + public StreamPartitionMsgOffset getLatestStreamOffsetAtStartupTime() { + return _latestStreamOffsetAtStartupTime; + } + + @VisibleForTesting + SegmentBuildDescriptor getSegmentBuildDescriptor() { + return _segmentBuildDescriptor; + } + + @VisibleForTesting + Semaphore getPartitionGroupConsumerSemaphore() { + return _partitionGroupConsumerSemaphore; + } + + @VisibleForTesting + AtomicBoolean getAcquiredConsumerSemaphore() { + return _acquiredConsumerSemaphore; + } + + @VisibleForTesting + SegmentBuildDescriptor buildSegmentInternal(boolean forCommit) { + // for partial upsert tables, do not release _partitionGroupConsumerSemaphore proactively and rely on offload() + // to release the semaphore. This ensures new consuming segment is not consuming until the segment replacement is + // complete. + if (_allowConsumptionDuringCommit) { + closeStreamConsumers(); + } + // Do not allow building segment when table data manager is already shut down + if (_realtimeTableDataManager.isShutDown()) { + _segmentLogger.warn("Table data manager is already shut down"); + return null; + } + try { + final long startTimeMillis = now(); + if (_segBuildSemaphore != null) { + _segmentLogger.info("Waiting to acquire semaphore for building segment"); + _segBuildSemaphore.acquire(); + } + // Increment llc simultaneous segment builds. + _serverMetrics.addValueToGlobalGauge(ServerGauge.LLC_SIMULTANEOUS_SEGMENT_BUILDS, 1L); + + final long lockAcquireTimeMillis = now(); + // Build a segment from in-memory rows.If buildTgz is true, then build the tar.gz file as well + // TODO Use an auto-closeable object to delete temp resources. + File tempSegmentFolder = new File(_resourceTmpDir, "tmp-" + _segmentNameStr + "-" + now()); + + SegmentZKPropsConfig segmentZKPropsConfig = new SegmentZKPropsConfig(); + segmentZKPropsConfig.setStartOffset(_segmentZKMetadata.getStartOffset()); + segmentZKPropsConfig.setEndOffset(_currentOffset.toString()); + // let's convert the segment now + RealtimeSegmentConverter converter = + new RealtimeSegmentConverter(_realtimeSegment, segmentZKPropsConfig, tempSegmentFolder.getAbsolutePath(), + _schema, _tableNameWithType, _tableConfig, _segmentZKMetadata.getSegmentName(), + _columnIndicesForRealtimeTable, _nullHandlingEnabled); + _segmentLogger.info("Trying to build segment"); + try { + converter.build(_segmentVersion, _serverMetrics); + } catch (Exception e) { + _segmentLogger.error("Could not build segment", e); + FileUtils.deleteQuietly(tempSegmentFolder); + _realtimeTableDataManager.addSegmentError(_segmentNameStr, + new SegmentErrorInfo(now(), "Could not build segment", e)); + return null; + } + final long buildTimeMillis = now() - lockAcquireTimeMillis; + final long waitTimeMillis = lockAcquireTimeMillis - startTimeMillis; + _segmentLogger.info("Successfully built segment (Column Mode: {}) in {} ms, after lockWaitTime {} ms", + converter.isColumnMajorEnabled(), buildTimeMillis, waitTimeMillis); + + File dataDir = new File(_resourceDataDir); + File indexDir = new File(dataDir, _segmentNameStr); + FileUtils.deleteQuietly(indexDir); + + File[] tempFiles = tempSegmentFolder.listFiles(); + assert tempFiles != null; + File tempIndexDir = tempFiles[0]; + try { + FileUtils.moveDirectory(tempIndexDir, indexDir); + } catch (IOException e) { + String errorMessage = + String.format("Caught exception while moving index directory from: %s to: %s", tempIndexDir, indexDir); + _segmentLogger.error(errorMessage, e); + _realtimeTableDataManager.addSegmentError(_segmentNameStr, new SegmentErrorInfo(now(), errorMessage, e)); + return null; + } finally { + FileUtils.deleteQuietly(tempSegmentFolder); + } + + long segmentSizeBytes = FileUtils.sizeOfDirectory(indexDir); + _serverMetrics.setValueOfTableGauge(_clientId, ServerGauge.LAST_REALTIME_SEGMENT_CREATION_DURATION_SECONDS, + TimeUnit.MILLISECONDS.toSeconds(buildTimeMillis)); + _serverMetrics.setValueOfTableGauge(_clientId, ServerGauge.LAST_REALTIME_SEGMENT_CREATION_WAIT_TIME_SECONDS, + TimeUnit.MILLISECONDS.toSeconds(waitTimeMillis)); + + if (forCommit) { + File segmentTarFile = new File(dataDir, _segmentNameStr + TarGzCompressionUtils.TAR_GZ_FILE_EXTENSION); + try { + TarGzCompressionUtils.createTarGzFile(indexDir, segmentTarFile); + } catch (IOException e) { + String errorMessage = + String.format("Caught exception while taring index directory from: %s to: %s", indexDir, segmentTarFile); + _segmentLogger.error(errorMessage, e); + _realtimeTableDataManager.addSegmentError(_segmentNameStr, new SegmentErrorInfo(now(), errorMessage, e)); + return null; + } + + File metadataFile = SegmentDirectoryPaths.findMetadataFile(indexDir); + if (metadataFile == null) { + String errorMessage = String.format("Failed to find file: %s under index directory: %s", + V1Constants.MetadataKeys.METADATA_FILE_NAME, indexDir); + _segmentLogger.error(errorMessage); + _realtimeTableDataManager.addSegmentError(_segmentNameStr, new SegmentErrorInfo(now(), errorMessage, null)); + return null; + } + File creationMetaFile = SegmentDirectoryPaths.findCreationMetaFile(indexDir); + if (creationMetaFile == null) { + String errorMessage = String.format("Failed to find file: %s under index directory: %s", + V1Constants.SEGMENT_CREATION_META, indexDir); + _segmentLogger.error(errorMessage); + _realtimeTableDataManager.addSegmentError(_segmentNameStr, new SegmentErrorInfo(now(), errorMessage, null)); + return null; + } + Map metadataFiles = new HashMap<>(); + metadataFiles.put(V1Constants.MetadataKeys.METADATA_FILE_NAME, metadataFile); + metadataFiles.put(V1Constants.SEGMENT_CREATION_META, creationMetaFile); + + return new SegmentBuildDescriptor(segmentTarFile, metadataFiles, _currentOffset, buildTimeMillis, + waitTimeMillis, segmentSizeBytes); + } else { + return new SegmentBuildDescriptor(null, null, _currentOffset, buildTimeMillis, waitTimeMillis, + segmentSizeBytes); + } + } catch (InterruptedException e) { + String errorMessage = "Interrupted while waiting for semaphore"; + _segmentLogger.error(errorMessage, e); + _realtimeTableDataManager.addSegmentError(_segmentNameStr, new SegmentErrorInfo(now(), errorMessage, e)); + return null; + } finally { + if (_segBuildSemaphore != null) { + _segBuildSemaphore.release(); + } + // Decrement llc simultaneous segment builds. + _serverMetrics.addValueToGlobalGauge(ServerGauge.LLC_SIMULTANEOUS_SEGMENT_BUILDS, -1L); + } + } + + @VisibleForTesting + boolean commitSegment(String controllerVipUrl) + throws Exception { + File segmentTarFile = _segmentBuildDescriptor.getSegmentTarFile(); + Preconditions.checkState(segmentTarFile != null && segmentTarFile.exists(), "Segment tar file: %s does not exist", + segmentTarFile); + SegmentCompletionProtocol.Response commitResponse = commit(controllerVipUrl); + if (commitResponse.getStatus() != SegmentCompletionProtocol.ControllerResponseStatus.COMMIT_SUCCESS) { + _segmentLogger.warn("Controller response was {} and not {}", commitResponse.getStatus(), + SegmentCompletionProtocol.ControllerResponseStatus.COMMIT_SUCCESS); + return false; + } + _realtimeTableDataManager.replaceConsumingSegment(_segmentNameStr); + removeSegmentFile(); + return true; + } + + @VisibleForTesting + SegmentCompletionProtocol.Response commit(String controllerVipUrl) { + SegmentCompletionProtocol.Request.Params params = new SegmentCompletionProtocol.Request.Params(); + + params.withSegmentName(_segmentNameStr).withStreamPartitionMsgOffset(_currentOffset.toString()) + .withNumRows(_numRowsConsumed).withInstanceId(_instanceId).withReason(_stopReason) + .withBuildTimeMillis(_segmentBuildDescriptor.getBuildTimeMillis()) + .withSegmentSizeBytes(_segmentBuildDescriptor.getSegmentSizeBytes()) + .withWaitTimeMillis(_segmentBuildDescriptor.getWaitTimeMillis()); + if (_isOffHeap) { + params.withMemoryUsedBytes(_memoryManager.getTotalAllocatedBytes()); + } + + SegmentCommitter segmentCommitter; + try { + segmentCommitter = _segmentCommitterFactory.createSegmentCommitter(params, controllerVipUrl); + } catch (URISyntaxException e) { + _segmentLogger.error("Failed to create a segment committer: ", e); + return SegmentCompletionProtocol.RESP_NOT_SENT; + } + return segmentCommitter.commit(_segmentBuildDescriptor); + } + + protected boolean buildSegmentAndReplace() + throws Exception { + SegmentBuildDescriptor descriptor = buildSegmentInternal(false); + if (descriptor == null) { + return false; + } + _realtimeTableDataManager.replaceConsumingSegment(_segmentNameStr); + return true; + } + + private void closeStreamConsumers() { + closePartitionGroupConsumer(); + closePartitionMetadataProvider(); + if (_acquiredConsumerSemaphore.compareAndSet(true, false)) { + _partitionGroupConsumerSemaphore.release(); + } + } + + private void closePartitionGroupConsumer() { + try { + _partitionGroupConsumer.close(); + } catch (Exception e) { + _segmentLogger.warn("Could not close stream consumer", e); + } + } + + private void closePartitionMetadataProvider() { + if (_partitionMetadataProvider != null) { + try { + _partitionMetadataProvider.close(); + } catch (Exception e) { + _segmentLogger.warn("Could not close stream metadata provider", e); + } + } + } + + /** + * Cleans up the metrics that reflects the state of the realtime segment. + * This step is essential as the instance may not be the target location for some of the partitions. + * E.g. if the number of partitions increases, or a host swap is needed, the target location for some partitions + * may change, + * and the current host remains to run. In this case, the current server would still keep the state of the old + * partitions, + * which no longer resides in this host any more, thus causes false positive information to the metric system. + */ + private void cleanupMetrics() { + _serverMetrics.removeTableGauge(_clientId, ServerGauge.LLC_PARTITION_CONSUMING); + } + + protected void hold() + throws InterruptedException { + Thread.sleep(SegmentCompletionProtocol.MAX_HOLD_TIME_MS); + } + + private static class ConsumptionStopIndicator { + final StreamPartitionMsgOffset _offset; + final String _segmentName; + final String _instanceId; + final Logger _logger; + final ServerSegmentCompletionProtocolHandler _protocolHandler; + final String _reason; + private ConsumptionStopIndicator(StreamPartitionMsgOffset offset, String segmentName, String instanceId, + ServerSegmentCompletionProtocolHandler protocolHandler, String reason, Logger logger) { + _offset = offset; + _segmentName = segmentName; + _instanceId = instanceId; + _protocolHandler = protocolHandler; + _logger = logger; + _reason = reason; + } + + SegmentCompletionProtocol.Response postSegmentStoppedConsuming() { + SegmentCompletionProtocol.Request.Params params = new SegmentCompletionProtocol.Request.Params(); + params.withStreamPartitionMsgOffset(_offset.toString()).withReason(_reason).withSegmentName(_segmentName) + .withInstanceId(_instanceId); + + SegmentCompletionProtocol.Response response = _protocolHandler.segmentStoppedConsuming(params); + _logger.info("Got response {}", response.toJsonString()); + return response; + } + } + + // Inform the controller that the server had to stop consuming due to an error. + protected void postStopConsumedMsg(String reason) { + ConsumptionStopIndicator indicator = new ConsumptionStopIndicator(_currentOffset, + _segmentNameStr, _instanceId, _protocolHandler, reason, _segmentLogger); + do { + SegmentCompletionProtocol.Response response = indicator.postSegmentStoppedConsuming(); + if (response.getStatus() == SegmentCompletionProtocol.ControllerResponseStatus.PROCESSED) { + break; + } + Uninterruptibles.sleepUninterruptibly(10, TimeUnit.SECONDS); + _segmentLogger.info("Retrying after response {}", response.toJsonString()); + } while (!_shouldStop); + } + + protected SegmentCompletionProtocol.Response postSegmentConsumedMsg() { + // Post segmentConsumed to current leader. + // Retry maybe once if leader is not found. + SegmentCompletionProtocol.Request.Params params = new SegmentCompletionProtocol.Request.Params(); + params.withStreamPartitionMsgOffset(_currentOffset.toString()).withSegmentName(_segmentNameStr) + .withReason(_stopReason).withNumRows(_numRowsConsumed).withInstanceId(_instanceId); + if (_isOffHeap) { + params.withMemoryUsedBytes(_memoryManager.getTotalAllocatedBytes()); + } + return _protocolHandler.segmentConsumed(params); + } + + private void removeSegmentFile() { + if (_segmentBuildDescriptor != null) { + _segmentBuildDescriptor.deleteSegmentFile(); + _segmentBuildDescriptor = null; + } + } + + public void goOnlineFromConsuming(SegmentZKMetadata segmentZKMetadata) + throws InterruptedException { + _serverMetrics.setValueOfTableGauge(_clientId, ServerGauge.LLC_PARTITION_CONSUMING, 0); + try { + // Remove the segment file before we do anything else. + removeSegmentFile(); + _leaseExtender.removeSegment(_segmentNameStr); + StreamPartitionMsgOffset endOffset = _streamPartitionMsgOffsetFactory.create(segmentZKMetadata.getEndOffset()); + _segmentLogger.info("State: {}, transitioning from CONSUMING to ONLINE (startOffset: {}, endOffset: {})", _state, + _startOffset, endOffset); + stop(); + _segmentLogger.info("Consumer thread stopped in state {}", _state); + + switch (_state) { + case COMMITTED: + case RETAINED: + // Nothing to do. we already built local segment and swapped it with in-memory data. + _segmentLogger.info("State {}. Nothing to do", _state.toString()); + break; + case DISCARDED: + case ERROR: + _segmentLogger.info("State {}. Downloading to replace", _state.toString()); + downloadSegmentAndReplace(segmentZKMetadata); + break; + case CATCHING_UP: + case HOLDING: + case INITIAL_CONSUMING: + switch (_segmentCompletionMode) { + case DOWNLOAD: + _segmentLogger.info("State {}. CompletionMode {}. Downloading to replace", _state.toString(), + _segmentCompletionMode); + downloadSegmentAndReplace(segmentZKMetadata); + break; + case DEFAULT: + // Allow to catch up upto final offset, and then replace. + if (_currentOffset.compareTo(endOffset) > 0) { + // We moved ahead of the offset that is committed in ZK. + _segmentLogger + .warn("Current offset {} ahead of the offset in zk {}. Downloading to replace", _currentOffset, + endOffset); + downloadSegmentAndReplace(segmentZKMetadata); + } else if (_currentOffset.compareTo(endOffset) == 0) { + _segmentLogger + .info("Current offset {} matches offset in zk {}. Replacing segment", _currentOffset, endOffset); + buildSegmentAndReplace(); + } else { + _segmentLogger.info("Attempting to catch up from offset {} to {} ", _currentOffset, endOffset); + boolean success = catchupToFinalOffset(endOffset, + TimeUnit.MILLISECONDS.convert(MAX_TIME_FOR_CONSUMING_TO_ONLINE_IN_SECONDS, TimeUnit.SECONDS)); + if (success) { + _segmentLogger.info("Caught up to offset {}", _currentOffset); + buildSegmentAndReplace(); + } else { + _segmentLogger + .info("Could not catch up to offset (current = {}). Downloading to replace", _currentOffset); + downloadSegmentAndReplace(segmentZKMetadata); + } + } + break; + default: + break; + } + break; + default: + _segmentLogger.info("Downloading to replace segment while in state {}", _state.toString()); + downloadSegmentAndReplace(segmentZKMetadata); + break; + } + } catch (Exception e) { + Utils.rethrowException(e); + } finally { + _serverMetrics.setValueOfTableGauge(_clientId, ServerGauge.LLC_PARTITION_CONSUMING, 0); + } + } + + protected void downloadSegmentAndReplace(SegmentZKMetadata segmentZKMetadata) + throws Exception { + // for partial upsert tables, do not release _partitionGroupConsumerSemaphore proactively and rely on offload() + // to release the semaphore. This ensures new consuming segment is not consuming until the segment replacement is + // complete. + if (_allowConsumptionDuringCommit) { + closeStreamConsumers(); + } + _realtimeTableDataManager.downloadAndReplaceConsumingSegment(segmentZKMetadata); + } + + protected long now() { + return System.currentTimeMillis(); + } + + private boolean catchupToFinalOffset(StreamPartitionMsgOffset endOffset, long timeoutMs) { + _finalOffset = endOffset; + _consumeEndTime = now() + timeoutMs; + _state = State.CONSUMING_TO_ONLINE; + _shouldStop = false; + try { + consumeLoop(); + } catch (Exception e) { + // We will end up downloading the segment, so this is not a serious problem + _segmentLogger.warn("Exception when catching up to final offset", e); + return false; + } finally { + _serverMetrics.setValueOfTableGauge(_clientId, ServerGauge.LLC_PARTITION_CONSUMING, 0); + } + if (_currentOffset.compareTo(endOffset) != 0) { + // Timeout? + _segmentLogger.warn("Could not consume up to {} (current offset {})", endOffset, _currentOffset); + return false; + } + + return true; + } @Override - public abstract MutableSegment getSegment(); + public void doOffload() { + try { + stop(); + } catch (Exception e) { + _segmentLogger.error("Caught exception while stopping the consumer thread", e); + } + closeStreamConsumers(); + cleanupMetrics(); + _realtimeSegment.offload(); + } - protected static PinotDataBufferMemoryManager getMemoryManager(String consumerDir, String segmentName, - boolean offHeap, boolean directOffHeap, ServerMetrics serverMetrics) { - if (offHeap && !directOffHeap) { - return new MmapMemoryManager(consumerDir, segmentName, serverMetrics); + @Override + protected void doDestroy() { + _realtimeSegment.destroy(); + } + + public void startConsumption() { + _consumerThread = new Thread(new PartitionConsumer(), _segmentNameStr); + _segmentLogger.info("Created new consumer thread {} for {}", _consumerThread, this); + _consumerThread.start(); + } + + /** + * Stop the consuming thread. + * + * This method is invoked in 2 places: + * 1. By the Helix thread when handling the segment state transition from CONSUMING to ONLINE. When this method is + * invoked, Helix thread should already hold the segment lock, and the consumer thread is not building the segment. + * We can safely interrupt the consumer thread and wait for it to join. + * 2. By either the Helix thread or consumer thread to offload the segment. In this case, we can also safely interrupt + * the consumer thread because there is no need to build the segment. + */ + public void stop() + throws InterruptedException { + _shouldStop = true; + if (Thread.currentThread() != _consumerThread && _consumerThread.isAlive()) { + // Interrupt the consumer thread and wait for it to join. + _consumerThread.interrupt(); + _consumerThread.join(); + } + } + + // Assume that this is called only on OFFLINE to CONSUMING transition. + // If the transition is OFFLINE to ONLINE, the caller should have downloaded the segment and we don't reach here. + public RealtimeSegmentDataManager(SegmentZKMetadata segmentZKMetadata, TableConfig tableConfig, + RealtimeTableDataManager realtimeTableDataManager, String resourceDataDir, IndexLoadingConfig indexLoadingConfig, + Schema schema, LLCSegmentName llcSegmentName, Semaphore partitionGroupConsumerSemaphore, + ServerMetrics serverMetrics, @Nullable PartitionUpsertMetadataManager partitionUpsertMetadataManager, + @Nullable PartitionDedupMetadataManager partitionDedupMetadataManager, BooleanSupplier isReadyToConsumeData) + throws AttemptsExceededException, RetriableOperationException { + _segBuildSemaphore = realtimeTableDataManager.getSegmentBuildSemaphore(); + _segmentZKMetadata = segmentZKMetadata; + _tableConfig = tableConfig; + _tableNameWithType = _tableConfig.getTableName(); + _realtimeTableDataManager = realtimeTableDataManager; + _resourceDataDir = resourceDataDir; + _indexLoadingConfig = indexLoadingConfig; + _schema = schema; + _serverMetrics = serverMetrics; + _partitionUpsertMetadataManager = partitionUpsertMetadataManager; + _isReadyToConsumeData = isReadyToConsumeData; + _segmentVersion = indexLoadingConfig.getSegmentVersion(); + _instanceId = _realtimeTableDataManager.getInstanceId(); + _leaseExtender = SegmentBuildTimeLeaseExtender.getLeaseExtender(_tableNameWithType); + _protocolHandler = new ServerSegmentCompletionProtocolHandler(_serverMetrics, _tableNameWithType); + CompletionConfig completionConfig = _tableConfig.getValidationConfig().getCompletionConfig(); + _segmentCompletionMode = completionConfig != null + && CompletionMode.DOWNLOAD.toString().equalsIgnoreCase(completionConfig.getCompletionMode()) + ? CompletionMode.DOWNLOAD : CompletionMode.DEFAULT; + + String timeColumnName = tableConfig.getValidationConfig().getTimeColumnName(); + // TODO Validate configs + IndexingConfig indexingConfig = _tableConfig.getIndexingConfig(); + _streamConfig = new StreamConfig(_tableNameWithType, IngestionConfigUtils.getStreamConfigMap(_tableConfig)); + _streamConsumerFactory = StreamConsumerFactoryProvider.create(_streamConfig); + _streamPartitionMsgOffsetFactory = _streamConsumerFactory.createStreamMsgOffsetFactory(); + String streamTopic = _streamConfig.getTopicName(); + _segmentNameStr = _segmentZKMetadata.getSegmentName(); + _partitionGroupId = llcSegmentName.getPartitionGroupId(); + _partitionGroupConsumptionStatus = + new PartitionGroupConsumptionStatus(_partitionGroupId, llcSegmentName.getSequenceNumber(), + _streamPartitionMsgOffsetFactory.create(_segmentZKMetadata.getStartOffset()), + _segmentZKMetadata.getEndOffset() == null ? null + : _streamPartitionMsgOffsetFactory.create(_segmentZKMetadata.getEndOffset()), + _segmentZKMetadata.getStatus().toString()); + _partitionGroupConsumerSemaphore = partitionGroupConsumerSemaphore; + _acquiredConsumerSemaphore = new AtomicBoolean(false); + InstanceDataManagerConfig instanceDataManagerConfig = _indexLoadingConfig.getInstanceDataManagerConfig(); + String clientIdSuffix = + instanceDataManagerConfig != null ? instanceDataManagerConfig.getConsumerClientIdSuffix() : null; + if (StringUtils.isNotBlank(clientIdSuffix)) { + _clientId = _tableNameWithType + "-" + streamTopic + "-" + _partitionGroupId + "-" + clientIdSuffix; + } else { + _clientId = _tableNameWithType + "-" + streamTopic + "-" + _partitionGroupId; + } + _segmentLogger = LoggerFactory.getLogger(RealtimeSegmentDataManager.class.getName() + "_" + _segmentNameStr); + _tableStreamName = _tableNameWithType + "_" + streamTopic; + if (_indexLoadingConfig.isRealtimeOffHeapAllocation() && !_indexLoadingConfig.isDirectRealtimeOffHeapAllocation()) { + _memoryManager = + new MmapMemoryManager(_realtimeTableDataManager.getConsumerDir(), _segmentNameStr, _serverMetrics); } else { // For on-heap allocation, we still need a memory manager for forward index. // Dictionary will be allocated on heap. - return new DirectMemoryManager(segmentName, serverMetrics); + _memoryManager = new DirectMemoryManager(_segmentNameStr, _serverMetrics); + } + + _partitionRateLimiter = RealtimeConsumptionRateManager.getInstance() + .createRateLimiter(_streamConfig, _tableNameWithType, _serverMetrics, _clientId); + _serverRateLimiter = RealtimeConsumptionRateManager.getInstance().getServerRateLimiter(); + + if (tableConfig.getIngestionConfig() != null + && tableConfig.getIngestionConfig().getStreamIngestionConfig() != null) { + _trackFilteredMessageOffsets = + tableConfig.getIngestionConfig().getStreamIngestionConfig().isTrackFilteredMessageOffsets(); + } + + List sortedColumns = indexLoadingConfig.getSortedColumns(); + String sortedColumn; + if (sortedColumns.isEmpty()) { + _segmentLogger.info("RealtimeDataResourceZKMetadata contains no information about sorted column for segment {}", + llcSegmentName); + sortedColumn = null; + } else { + String firstSortedColumn = sortedColumns.get(0); + if (_schema.hasColumn(firstSortedColumn)) { + _segmentLogger.info("Setting sorted column name: {} from RealtimeDataResourceZKMetadata for segment {}", + firstSortedColumn, llcSegmentName); + sortedColumn = firstSortedColumn; + } else { + _segmentLogger + .warn("Sorted column name: {} from RealtimeDataResourceZKMetadata is not existed in schema for segment {}.", + firstSortedColumn, llcSegmentName); + sortedColumn = null; + } + } + // Inverted index columns + // We need to add sorted column into inverted index columns because when we convert realtime in memory segment into + // offline segment, we use sorted column's inverted index to maintain the order of the records so that the records + // are sorted on the sorted column. + if (sortedColumn != null) { + indexLoadingConfig.addInvertedIndexColumns(sortedColumn); + } + + // Read the max number of rows + int segmentMaxRowCount = segmentZKMetadata.getSizeThresholdToFlushSegment(); + if (segmentMaxRowCount <= 0) { + segmentMaxRowCount = _streamConfig.getFlushThresholdRows(); + } + if (segmentMaxRowCount <= 0) { + segmentMaxRowCount = StreamConfig.DEFAULT_FLUSH_THRESHOLD_ROWS; + } + _segmentMaxRowCount = segmentMaxRowCount; + + _isOffHeap = indexLoadingConfig.isRealtimeOffHeapAllocation(); + + _nullHandlingEnabled = indexingConfig.isNullHandlingEnabled(); + + _columnIndicesForRealtimeTable = new ColumnIndicesForRealtimeTable(sortedColumn, + new ArrayList<>(indexLoadingConfig.getInvertedIndexColumns()), + new ArrayList<>(indexLoadingConfig.getTextIndexColumns()), + new ArrayList<>(indexLoadingConfig.getFSTIndexColumns()), + new ArrayList<>(indexLoadingConfig.getNoDictionaryColumns()), + new ArrayList<>(indexLoadingConfig.getVarLengthDictionaryColumns())); + + // Start new realtime segment + String consumerDir = realtimeTableDataManager.getConsumerDir(); + RealtimeSegmentConfig.Builder realtimeSegmentConfigBuilder = + new RealtimeSegmentConfig.Builder(indexLoadingConfig).setTableNameWithType(_tableNameWithType) + .setSegmentName(_segmentNameStr) + .setStreamName(streamTopic).setSchema(_schema).setTimeColumnName(timeColumnName) + .setCapacity(_segmentMaxRowCount).setAvgNumMultiValues(indexLoadingConfig.getRealtimeAvgMultiValueCount()) + .setSegmentZKMetadata(segmentZKMetadata) + .setOffHeap(_isOffHeap).setMemoryManager(_memoryManager) + .setStatsHistory(realtimeTableDataManager.getStatsHistory()) + .setAggregateMetrics(indexingConfig.isAggregateMetrics()) + .setIngestionAggregationConfigs(IngestionConfigUtils.getAggregationConfigs(tableConfig)) + .setNullHandlingEnabled(_nullHandlingEnabled) + .setConsumerDir(consumerDir).setUpsertMode(tableConfig.getUpsertMode()) + .setPartitionUpsertMetadataManager(partitionUpsertMetadataManager) + .setPartitionDedupMetadataManager(partitionDedupMetadataManager) + .setUpsertComparisonColumns(tableConfig.getUpsertComparisonColumns()) + .setUpsertDeleteRecordColumn(tableConfig.getUpsertDeleteRecordColumn()) + .setUpsertOutOfOrderRecordColumn(tableConfig.getOutOfOrderRecordColumn()) + .setUpsertDropOutOfOrderRecord(tableConfig.isDropOutOfOrderRecord()) + .setFieldConfigList(tableConfig.getFieldConfigList()); + + // Create message decoder + Set fieldsToRead = IngestionUtils.getFieldsForRecordExtractor(_tableConfig.getIngestionConfig(), _schema); + RetryPolicy retryPolicy = RetryPolicies.exponentialBackoffRetryPolicy(5, 1000L, 1.2f); + AtomicReference localStreamDataDecoder = new AtomicReference<>(); + try { + retryPolicy.attempt(() -> { + try { + StreamMessageDecoder streamMessageDecoder = createMessageDecoder(fieldsToRead); + localStreamDataDecoder.set(new StreamDataDecoderImpl(streamMessageDecoder)); + return true; + } catch (Exception e) { + _segmentLogger.warn("Failed to initialize the StreamMessageDecoder: ", e); + return false; + } + }); + } catch (Exception e) { + _realtimeTableDataManager.addSegmentError(_segmentNameStr, new SegmentErrorInfo(now(), + "Failed to initialize the StreamMessageDecoder", e)); + throw e; + } + _streamDataDecoder = localStreamDataDecoder.get(); + + try { + _recordEnricherPipeline = RecordEnricherPipeline.fromTableConfig(tableConfig); + } catch (Exception e) { + _realtimeTableDataManager.addSegmentError(_segmentNameStr, + new SegmentErrorInfo(now(), "Failed to initialize the RecordEnricherPipeline", e)); + throw e; + } + _transformPipeline = new TransformPipeline(tableConfig, schema); + // Acquire semaphore to create stream consumers + try { + _partitionGroupConsumerSemaphore.acquire(); + _acquiredConsumerSemaphore.set(true); + } catch (InterruptedException e) { + String errorMsg = "InterruptedException when acquiring the partitionConsumerSemaphore"; + _segmentLogger.error(errorMsg); + throw new RuntimeException(errorMsg + " for segment: " + _segmentNameStr); + } + + try { + _startOffset = _partitionGroupConsumptionStatus.getStartOffset(); + _currentOffset = _streamPartitionMsgOffsetFactory.create(_startOffset); + makeStreamConsumer("Starting"); + createPartitionMetadataProvider("Starting"); + setPartitionParameters(realtimeSegmentConfigBuilder, indexingConfig.getSegmentPartitionConfig()); + _realtimeSegment = new MutableSegmentImpl(realtimeSegmentConfigBuilder.build(), serverMetrics); + _resourceTmpDir = new File(resourceDataDir, RESOURCE_TEMP_DIR_NAME); + if (!_resourceTmpDir.exists()) { + _resourceTmpDir.mkdirs(); + } + _state = State.INITIAL_CONSUMING; + _latestStreamOffsetAtStartupTime = fetchLatestStreamOffset(5000); + _consumeStartTime = now(); + setConsumeEndTime(segmentZKMetadata, _consumeStartTime); + _segmentCommitterFactory = + new SegmentCommitterFactory(_segmentLogger, _protocolHandler, tableConfig, indexLoadingConfig, serverMetrics); + _segmentLogger + .info("Starting consumption on realtime consuming segment {} maxRowCount {} maxEndTime {}", llcSegmentName, + _segmentMaxRowCount, new DateTime(_consumeEndTime, DateTimeZone.UTC)); + _allowConsumptionDuringCommit = !_realtimeTableDataManager.isPartialUpsertEnabled() ? true + : _tableConfig.getUpsertConfig().isAllowPartialUpsertConsumptionDuringCommit(); + } catch (Exception e) { + // In case of exception thrown here, segment goes to ERROR state. Then any attempt to reset the segment from + // ERROR -> OFFLINE -> CONSUMING via Helix Admin fails because the semaphore is acquired, but not released. + // Hence releasing the semaphore here to unblock reset operation via Helix Admin. + _partitionGroupConsumerSemaphore.release(); + _realtimeTableDataManager.addSegmentError(_segmentNameStr, new SegmentErrorInfo(now(), + "Failed to initialize segment data manager", e)); + _segmentLogger.warn( + "Scheduling task to call controller to mark the segment as OFFLINE in Ideal State due" + + " to initialization error: '{}'", + e.getMessage()); + // Since we are going to throw exception from this thread (helix execution thread), the externalview + // entry for this segment will be ERROR. We allow time for Helix to make this transition, and then + // invoke controller API mark it OFFLINE in the idealstate. + new Thread(() -> { + ConsumptionStopIndicator indicator = new ConsumptionStopIndicator(_currentOffset, _segmentNameStr, _instanceId, + _protocolHandler, "Consuming segment initialization error", _segmentLogger); + try { + // Allow 30s for Helix to mark currentstate and externalview to ERROR, because + // we are about to receive an ERROR->OFFLINE state transition once we call + // postSegmentStoppedConsuming() method. + Thread.sleep(30_000); + indicator.postSegmentStoppedConsuming(); + } catch (InterruptedException ie) { + // We got interrupted trying to post stop-consumed message. Give up at this point + return; + } + }).start(); + throw e; + } + } + + private void setConsumeEndTime(SegmentZKMetadata segmentZKMetadata, long now) { + long maxConsumeTimeMillis = _streamConfig.getFlushThresholdTimeMillis(); + _consumeEndTime = segmentZKMetadata.getCreationTime() + maxConsumeTimeMillis; + + // When we restart a server, the consuming segments retain their creationTime (derived from segment + // metadata), but a couple of corner cases can happen: + // (1) The server was down for a very long time, and the consuming segment is not yet completed. + // (2) The consuming segment was just about to be completed, but the server went down. + // In either of these two cases, if a different replica could not complete the segment, it is possible + // that we get a value for _consumeEndTime that is in the very near future, or even in the past. In such + // cases, we let some minimum consumption happen before we attempt to complete the segment (unless, of course + // the max consumption time has been configured to be less than the minimum time we use in this class). + long minConsumeTimeMillis = + Math.min(maxConsumeTimeMillis, TimeUnit.MILLISECONDS.convert(MINIMUM_CONSUME_TIME_MINUTES, TimeUnit.MINUTES)); + if (_consumeEndTime - now < minConsumeTimeMillis) { + _consumeEndTime = now + minConsumeTimeMillis; + } + } + + public long getTimeSinceEventLastConsumedMs() { + return _idleTimer.getTimeSinceEventLastConsumedMs(); + } + + public StreamPartitionMsgOffset fetchLatestStreamOffset(long maxWaitTimeMs) { + return fetchStreamOffset(OffsetCriteria.LARGEST_OFFSET_CRITERIA, maxWaitTimeMs); + } + + public StreamPartitionMsgOffset fetchEarliestStreamOffset(long maxWaitTimeMs) { + return fetchStreamOffset(OffsetCriteria.SMALLEST_OFFSET_CRITERIA, maxWaitTimeMs); + } + + private StreamPartitionMsgOffset fetchStreamOffset(OffsetCriteria offsetCriteria, long maxWaitTimeMs) { + if (_partitionMetadataProvider == null) { + createPartitionMetadataProvider("Fetch latest stream offset"); + } + try { + return _partitionMetadataProvider.fetchStreamPartitionOffset(offsetCriteria, maxWaitTimeMs); + } catch (Exception e) { + _segmentLogger.warn( + String.format( + "Cannot fetch stream offset with criteria %s for clientId %s and partitionGroupId %d with maxWaitTime %d", + offsetCriteria, _clientId, _partitionGroupId, maxWaitTimeMs), e); + } + return null; + } + + /* + * set the following partition parameters in RT segment config builder: + * - partition column + * - partition function + * - partition group id + */ + private void setPartitionParameters(RealtimeSegmentConfig.Builder realtimeSegmentConfigBuilder, + SegmentPartitionConfig segmentPartitionConfig) { + if (segmentPartitionConfig != null) { + Map columnPartitionMap = segmentPartitionConfig.getColumnPartitionMap(); + if (columnPartitionMap.size() == 1) { + Map.Entry entry = columnPartitionMap.entrySet().iterator().next(); + String partitionColumn = entry.getKey(); + ColumnPartitionConfig columnPartitionConfig = entry.getValue(); + String partitionFunctionName = columnPartitionConfig.getFunctionName(); + + // NOTE: Here we compare the number of partitions from the config and the stream, and log a warning and emit a + // metric when they don't match, but use the one from the stream. The mismatch could happen when the + // stream partitions are changed, but the table config has not been updated to reflect the change. + // In such case, picking the number of partitions from the stream can keep the segment properly + // partitioned as long as the partition function is not changed. + int numPartitions = columnPartitionConfig.getNumPartitions(); + try { + // TODO: currentPartitionGroupConsumptionStatus should be fetched from idealState + segmentZkMetadata, + // so that we get back accurate partitionGroups info + // However this is not an issue for Kafka, since partitionGroups never expire and every partitionGroup has + // a single partition + // Fix this before opening support for partitioning in Kinesis + int numPartitionGroups = _partitionMetadataProvider.computePartitionGroupMetadata(_clientId, _streamConfig, + Collections.emptyList(), /*maxWaitTimeMs=*/5000).size(); + + if (numPartitionGroups != numPartitions) { + _segmentLogger.info( + "Number of stream partitions: {} does not match number of partitions in the partition config: {}, " + + "using number of stream " + "partitions", numPartitionGroups, numPartitions); + numPartitions = numPartitionGroups; + } + } catch (Exception e) { + _segmentLogger.warn("Failed to get number of stream partitions in 5s, " + + "using number of partitions in the partition config: {}", numPartitions, e); + createPartitionMetadataProvider("Timeout getting number of stream partitions"); + } + + realtimeSegmentConfigBuilder.setPartitionColumn(partitionColumn); + realtimeSegmentConfigBuilder.setPartitionFunction( + PartitionFunctionFactory.getPartitionFunction(partitionFunctionName, numPartitions, null)); + realtimeSegmentConfigBuilder.setPartitionId(_partitionGroupId); + } else { + _segmentLogger.warn("Cannot partition on multiple columns: {}", columnPartitionMap.keySet()); + } } } /** - * Get the current offsets for all partitions of this consumer + * Creates a new stream consumer */ - public abstract Map getPartitionToCurrentOffset(); + private void makeStreamConsumer(String reason) { + if (_partitionGroupConsumer != null) { + closePartitionGroupConsumer(); + } + _segmentLogger.info("Creating new stream consumer for topic partition {} , reason: {}", _clientId, reason); + try { + _partitionGroupConsumer = + _streamConsumerFactory.createPartitionGroupConsumer(_clientId, _partitionGroupConsumptionStatus); + _partitionGroupConsumer.start(_currentOffset); + } catch (Exception e) { + _segmentLogger.error("Faced exception while trying to recreate stream consumer for topic partition {} reason {}", + _clientId, reason, e); + _serverMetrics.addMeteredTableValue(_clientId, ServerMeter.STREAM_CONSUMER_CREATE_EXCEPTIONS, 1L); + throw e; + } + } /** - * Get the state of the consumer + * Checkpoints existing consumer before creating a new consumer instance + * Assumes there is a valid instance of {@link PartitionGroupConsumer} */ - public abstract ConsumerState getConsumerState(); + private void recreateStreamConsumer(String reason) { + _segmentLogger.info("Recreating stream consumer for topic partition {}, reason: {}", _clientId, reason); + _currentOffset = _partitionGroupConsumer.checkpoint(_currentOffset); + closePartitionGroupConsumer(); + try { + _partitionGroupConsumer = + _streamConsumerFactory.createPartitionGroupConsumer(_clientId, _partitionGroupConsumptionStatus); + _partitionGroupConsumer.start(_currentOffset); + } catch (Exception e) { + _segmentLogger.error("Faced exception while trying to recreate stream consumer for topic partition {}", _clientId, + e); + _serverMetrics.addMeteredTableValue(_clientId, ServerMeter.STREAM_CONSUMER_CREATE_EXCEPTIONS, 1L); + throw e; + } + } /** - * @return Timestamp at which the last record was indexed + * Creates a new stream metadata provider + */ + private void createPartitionMetadataProvider(String reason) { + closePartitionMetadataProvider(); + _segmentLogger.info("Creating new partition metadata provider, reason: {}", reason); + _partitionMetadataProvider = _streamConsumerFactory.createPartitionMetadataProvider(_clientId, _partitionGroupId); + } + + private void updateIngestionMetrics(RowMetadata metadata) { + if (metadata != null) { + try { + StreamPartitionMsgOffset latestOffset = + _partitionMetadataProvider.fetchStreamPartitionOffset(OffsetCriteria.LARGEST_OFFSET_CRITERIA, 5000); + _realtimeTableDataManager.updateIngestionMetrics(metadata.getRecordIngestionTimeMs(), + metadata.getFirstStreamRecordIngestionTimeMs(), metadata.getOffset(), latestOffset, _partitionGroupId); + } catch (Exception e) { + _segmentLogger.warn("Failed to fetch latest offset for updating ingestion delay", e); + } + } + } + + /* + * Sets ingestion delay to zero in situations where we are caught up processing events. */ - public abstract long getLastConsumedTimestamp(); + private void setIngestionDelayToZero() { + long currentTimeMs = System.currentTimeMillis(); + _realtimeTableDataManager.updateIngestionMetrics(currentTimeMs, currentTimeMs, null, null, _partitionGroupId); + } + + // This should be done during commit? We may not always commit when we build a segment.... + // TODO Call this method when we are loading the segment, which we do from table datamanager afaik + private void updateCurrentDocumentCountMetrics() { + + // When updating of metrics is enabled, numRowsIndexed can be <= numRowsConsumed. This is because when in this + // case when a new row with existing dimension combination comes in, we find the existing row and update metrics. + + // Number of rows indexed should be used for DOCUMENT_COUNT metric, and also for segment flush. Whereas, + // Number of rows consumed should be used for consumption metric. + long rowsIndexed = _numRowsIndexed - _lastUpdatedRowsIndexed.get(); + _serverMetrics.addValueToTableGauge(_tableNameWithType, ServerGauge.DOCUMENT_COUNT, rowsIndexed); + _lastUpdatedRowsIndexed.set(_numRowsIndexed); + final long now = now(); + final int rowsConsumed = _numRowsConsumed - _lastConsumedCount; + final long prevTime = _lastLogTime == 0 ? _consumeStartTime : _lastLogTime; + // Log every minute or 100k events + if (now - prevTime > TimeUnit.MINUTES.toMillis(TIME_THRESHOLD_FOR_LOG_MINUTES) + || rowsConsumed >= MSG_COUNT_THRESHOLD_FOR_LOG) { + // multiply by 1000 to get events/sec. now and prevTime are in milliseconds. + float consumedRate = ((float) rowsConsumed) * 1000 / (now - prevTime); + _segmentLogger.info( + "Consumed {} events from (rate:{}/s), currentOffset={}, numRowsConsumedSoFar={}, numRowsIndexedSoFar={}", + rowsConsumed, consumedRate, _currentOffset, _numRowsConsumed, _numRowsIndexed); + if (_filteredMessageOffsets.size() > 0) { + if (_trackFilteredMessageOffsets) { + _segmentLogger.info("Filtered events with offsets: {}", _filteredMessageOffsets); + } + _filteredMessageOffsets.clear(); + } + _lastConsumedCount = _numRowsConsumed; + _lastLogTime = now; + } + } /** - * @return Per-partition consumer's status, which typically includes last consumed message timestamp, - * latest available upstream offset etc + * Creates a {@link StreamMessageDecoder} using properties in {@link StreamConfig}. + * + * @param streamConfig The stream config from the table config + * @param fieldsToRead The fields to read from the source stream + * @return The initialized StreamMessageDecoder */ - public abstract Map getConsumerPartitionState(); + private StreamMessageDecoder createMessageDecoder(Set fieldsToRead) { + String decoderClass = _streamConfig.getDecoderClass(); + try { + Map decoderProperties = _streamConfig.getDecoderProperties(); + StreamMessageDecoder decoder = PluginManager.get().createInstance(decoderClass); + decoder.init(fieldsToRead, _streamConfig, _tableConfig, _schema); + return decoder; + } catch (Exception e) { + throw new RuntimeException( + "Caught exception while creating StreamMessageDecoder from stream config: " + _streamConfig, e); + } + } + + @Override + public MutableSegment getSegment() { + return _realtimeSegment; + } - public abstract Map getPartitionToLagState( - Map consumerPartitionStateMap); + @Override + public String getSegmentName() { + return _segmentNameStr; + } + + public void forceCommit() { + _forceCommitMessageReceived = true; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeTableDataManager.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeTableDataManager.java index 89b45f1cce77..2db6a5dbd0d9 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeTableDataManager.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/RealtimeTableDataManager.java @@ -18,44 +18,45 @@ */ package org.apache.pinot.core.data.manager.realtime; +import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import java.io.File; import java.io.IOException; -import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Lock; +import java.util.function.BooleanSupplier; +import java.util.function.Supplier; import javax.annotation.concurrent.ThreadSafe; -import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.pinot.common.Utils; import org.apache.pinot.common.metadata.ZKMetadataProvider; -import org.apache.pinot.common.metadata.instance.InstanceZKMetadata; import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; import org.apache.pinot.common.metrics.ServerGauge; +import org.apache.pinot.common.restlet.resources.SegmentErrorInfo; import org.apache.pinot.common.utils.LLCSegmentName; -import org.apache.pinot.common.utils.NamedThreadFactory; -import org.apache.pinot.common.utils.SegmentName; import org.apache.pinot.common.utils.SegmentUtils; -import org.apache.pinot.common.utils.TarGzCompressionUtils; -import org.apache.pinot.common.utils.fetcher.SegmentFetcherFactory; +import org.apache.pinot.common.utils.config.QueryOptionsUtils; import org.apache.pinot.core.data.manager.BaseTableDataManager; import org.apache.pinot.core.data.manager.offline.ImmutableSegmentDataManager; -import org.apache.pinot.core.util.PeerServerSegmentFinder; import org.apache.pinot.segment.local.data.manager.SegmentDataManager; import org.apache.pinot.segment.local.dedup.PartitionDedupMetadataManager; import org.apache.pinot.segment.local.dedup.TableDedupMetadataManager; +import org.apache.pinot.segment.local.dedup.TableDedupMetadataManagerFactory; import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentImpl; import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; import org.apache.pinot.segment.local.realtime.impl.RealtimeSegmentStatsHistory; import org.apache.pinot.segment.local.segment.index.loader.IndexLoadingConfig; -import org.apache.pinot.segment.local.segment.index.loader.LoaderUtils; import org.apache.pinot.segment.local.segment.virtualcolumn.VirtualColumnProviderFactory; import org.apache.pinot.segment.local.upsert.PartitionUpsertMetadataManager; import org.apache.pinot.segment.local.upsert.TableUpsertMetadataManager; @@ -64,23 +65,25 @@ import org.apache.pinot.segment.local.utils.tablestate.TableStateUtils; import org.apache.pinot.segment.spi.ImmutableSegment; import org.apache.pinot.segment.spi.IndexSegment; -import org.apache.pinot.spi.config.instance.InstanceDataManagerConfig; +import org.apache.pinot.segment.spi.SegmentContext; import org.apache.pinot.spi.config.table.DedupConfig; import org.apache.pinot.spi.config.table.IndexingConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.UpsertConfig; +import org.apache.pinot.spi.data.DateTimeFieldSpec; +import org.apache.pinot.spi.data.DateTimeFormatSpec; import org.apache.pinot.spi.data.FieldSpec; import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.spi.stream.StreamPartitionMsgOffset; import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.CommonConstants.Segment.Realtime.Status; - -import static org.apache.pinot.spi.utils.CommonConstants.Segment.METADATA_URI_FOR_PEER_DOWNLOAD; +import org.apache.pinot.spi.utils.TimeUtils; +import org.apache.pinot.spi.utils.retry.AttemptsExceededException; +import org.apache.pinot.spi.utils.retry.RetriableOperationException; @ThreadSafe public class RealtimeTableDataManager extends BaseTableDataManager { - private final ExecutorService _segmentAsyncExecutorService = - Executors.newSingleThreadExecutor(new NamedThreadFactory("SegmentAsyncExecutorService")); private SegmentBuildTimeLeaseExtender _leaseExtender; private RealtimeSegmentStatsHistory _statsHistory; private final Semaphore _segmentBuildSemaphore; @@ -111,25 +114,38 @@ public class RealtimeTableDataManager extends BaseTableDataManager { // likely that we get fresh data each time instead of multiple copies of roughly same data. private static final int MIN_INTERVAL_BETWEEN_STATS_UPDATES_MINUTES = 30; - private final AtomicBoolean _allSegmentsLoaded = new AtomicBoolean(); + public static final long READY_TO_CONSUME_DATA_CHECK_INTERVAL_MS = TimeUnit.SECONDS.toMillis(5); + + // TODO: Change it to BooleanSupplier + private final Supplier _isServerReadyToServeQueries; + + // Object to track ingestion delay for all partitions + private IngestionDelayTracker _ingestionDelayTracker; private TableDedupMetadataManager _tableDedupMetadataManager; private TableUpsertMetadataManager _tableUpsertMetadataManager; + private BooleanSupplier _isTableReadyToConsumeData; public RealtimeTableDataManager(Semaphore segmentBuildSemaphore) { + this(segmentBuildSemaphore, () -> true); + } + + public RealtimeTableDataManager(Semaphore segmentBuildSemaphore, Supplier isServerReadyToServeQueries) { _segmentBuildSemaphore = segmentBuildSemaphore; + _isServerReadyToServeQueries = isServerReadyToServeQueries; } @Override protected void doInit() { _leaseExtender = SegmentBuildTimeLeaseExtender.getOrCreate(_instanceId, _serverMetrics, _tableNameWithType); - + // Tracks ingestion delay of all partitions being served for this table + _ingestionDelayTracker = + new IngestionDelayTracker(_serverMetrics, _tableNameWithType, this, _isServerReadyToServeQueries); File statsFile = new File(_tableDataDir, STATS_FILE_NAME); try { _statsHistory = RealtimeSegmentStatsHistory.deserialzeFrom(statsFile); } catch (IOException | ClassNotFoundException e) { - _logger.error("Error reading history object for table {} from {}", _tableNameWithType, - statsFile.getAbsolutePath(), e); + _logger.error("Caught exception while reading stats history from: {}", statsFile.getAbsolutePath(), e); File savedFile = new File(_tableDataDir, STATS_FILE_NAME + "." + UUID.randomUUID()); try { FileUtils.moveFile(statsFile, savedFile); @@ -152,7 +168,7 @@ protected void doInit() { File consumerDir = new File(consumerDirPath); if (consumerDir.exists()) { File[] segmentFiles = consumerDir.listFiles((dir, name) -> !name.equals(STATS_FILE_NAME)); - Preconditions.checkState(segmentFiles != null, "Failed to list segment files from consumer dir: {} for table: {}", + Preconditions.checkState(segmentFiles != null, "Failed to list segment files from consumer dir: %s for table: %s", consumerDirPath, _tableNameWithType); for (File file : segmentFiles) { if (file.delete()) { @@ -166,10 +182,7 @@ protected void doInit() { // Set up dedup/upsert metadata manager // NOTE: Dedup/upsert has to be set up when starting the server. Changing the table config without restarting the // server won't enable/disable them on the fly. - TableConfig tableConfig = ZKMetadataProvider.getTableConfig(_propertyStore, _tableNameWithType); - Preconditions.checkState(tableConfig != null, "Failed to find table config for table: %s", _tableNameWithType); - - DedupConfig dedupConfig = tableConfig.getDedupConfig(); + DedupConfig dedupConfig = _tableConfig.getDedupConfig(); boolean dedupEnabled = dedupConfig != null && dedupConfig.isDedupEnabled(); if (dedupEnabled) { Schema schema = ZKMetadataProvider.getTableSchema(_propertyStore, _tableNameWithType); @@ -178,17 +191,48 @@ protected void doInit() { List primaryKeyColumns = schema.getPrimaryKeyColumns(); Preconditions.checkState(!CollectionUtils.isEmpty(primaryKeyColumns), "Primary key columns must be configured for dedup"); - _tableDedupMetadataManager = new TableDedupMetadataManager(_tableNameWithType, primaryKeyColumns, _serverMetrics, - dedupConfig.getHashFunction()); + _tableDedupMetadataManager = TableDedupMetadataManagerFactory.create(_tableConfig, schema, this, _serverMetrics); } - UpsertConfig upsertConfig = tableConfig.getUpsertConfig(); + UpsertConfig upsertConfig = _tableConfig.getUpsertConfig(); if (upsertConfig != null && upsertConfig.getMode() != UpsertConfig.Mode.NONE) { Preconditions.checkState(!dedupEnabled, "Dedup and upsert cannot be both enabled for table: %s", _tableUpsertMetadataManager); Schema schema = ZKMetadataProvider.getTableSchema(_propertyStore, _tableNameWithType); Preconditions.checkState(schema != null, "Failed to find schema for table: %s", _tableNameWithType); - _tableUpsertMetadataManager = TableUpsertMetadataManagerFactory.create(tableConfig, schema, this, _serverMetrics); + _tableUpsertMetadataManager = + TableUpsertMetadataManagerFactory.create(_tableConfig, _instanceDataManagerConfig.getUpsertConfig()); + _tableUpsertMetadataManager.init(_tableConfig, schema, this); + } + + // For dedup and partial-upsert, need to wait for all segments loaded before starting consuming data + if (isDedupEnabled() || isPartialUpsertEnabled()) { + _isTableReadyToConsumeData = new BooleanSupplier() { + volatile boolean _allSegmentsLoaded; + long _lastCheckTimeMs; + + @Override + public boolean getAsBoolean() { + if (_allSegmentsLoaded) { + return true; + } else { + synchronized (this) { + if (_allSegmentsLoaded) { + return true; + } + long currentTimeMs = System.currentTimeMillis(); + if (currentTimeMs - _lastCheckTimeMs <= READY_TO_CONSUME_DATA_CHECK_INTERVAL_MS) { + return false; + } + _lastCheckTimeMs = currentTimeMs; + _allSegmentsLoaded = TableStateUtils.isAllSegmentsLoaded(_helixManager, _tableNameWithType); + return _allSegmentsLoaded; + } + } + } + }; + } else { + _isTableReadyToConsumeData = () -> true; } } @@ -198,22 +242,105 @@ protected void doStart() { @Override protected void doShutdown() { - _segmentAsyncExecutorService.shutdown(); + // Make sure we do metric cleanup when we shut down the table. + // Do this first, so we do not show ingestion lag during shutdown. + _ingestionDelayTracker.shutdown(); if (_tableUpsertMetadataManager != null) { + // Stop the upsert metadata manager first to prevent removing metadata when destroying segments + _tableUpsertMetadataManager.stop(); + releaseAndRemoveAllSegments(); try { _tableUpsertMetadataManager.close(); } catch (IOException e) { - _logger.warn("Cannot close upsert metadata manager properly for table: {}", _tableNameWithType, e); + _logger.warn("Caught exception while closing upsert metadata manager", e); } - } - for (SegmentDataManager segmentDataManager : _segmentDataManagerMap.values()) { - segmentDataManager.destroy(); + } else { + releaseAndRemoveAllSegments(); } if (_leaseExtender != null) { _leaseExtender.shutDown(); } } + /* + * Method used by RealtimeSegmentManagers to update their partition delays + * + * @param ingestionTimeMs Ingestion delay being reported. + * @param firstStreamIngestionTimeMs Ingestion time of the first message in the stream. + * @param partitionGroupId Partition ID for which delay is being updated. + * @param offset last offset received for the partition. + * @param latestOffset latest upstream offset for the partition. + */ + public void updateIngestionMetrics(long ingestionTimeMs, long firstStreamIngestionTimeMs, + StreamPartitionMsgOffset offset, StreamPartitionMsgOffset latestOffset, int partitionGroupId) { + _ingestionDelayTracker.updateIngestionMetrics(ingestionTimeMs, firstStreamIngestionTimeMs, offset, latestOffset, + partitionGroupId); + } + + /* + * Method used during query execution (ServerQueryExecutorV1Impl) to get the current timestamp for the ingestion + * delay for a partition + * + * @param segmentNameStr name of segment for which we want the ingestion delay timestamp. + * @return timestamp of the ingestion delay for the partition. + */ + public long getPartitionIngestionTimeMs(String segmentNameStr) { + LLCSegmentName segmentName = new LLCSegmentName(segmentNameStr); + int partitionGroupId = segmentName.getPartitionGroupId(); + return _ingestionDelayTracker.getPartitionIngestionTimeMs(partitionGroupId); + } + + /* + * Method to handle CONSUMING -> DROPPED segment state transitions: + * We stop tracking partitions whose segments are dropped. + * + * @param segmentNameStr name of segment which is transitioning state. + */ + @Override + public void onConsumingToDropped(String segmentNameStr) { + LLCSegmentName segmentName = new LLCSegmentName(segmentNameStr); + _ingestionDelayTracker.stopTrackingPartitionIngestionDelay(segmentName.getPartitionGroupId()); + } + + /* + * Method to handle CONSUMING -> ONLINE segment state transitions: + * We mark partitions for verification against ideal state when we do not see a consuming segment for some time + * for that partition. The idea is to remove the related metrics when the partition moves from the current server. + * + * @param segmentNameStr name of segment which is transitioning state. + */ + @Override + public void onConsumingToOnline(String segmentNameStr) { + LLCSegmentName segmentName = new LLCSegmentName(segmentNameStr); + _ingestionDelayTracker.markPartitionForVerification(segmentName.getPartitionGroupId()); + } + + @Override + public List getSegmentContexts(List selectedSegments, + Map queryOptions) { + List segmentContexts = new ArrayList<>(selectedSegments.size()); + selectedSegments.forEach(s -> segmentContexts.add(new SegmentContext(s))); + if (isUpsertEnabled() && !QueryOptionsUtils.isSkipUpsert(queryOptions)) { + _tableUpsertMetadataManager.setSegmentContexts(segmentContexts, queryOptions); + } + return segmentContexts; + } + + /** + * Returns all partitionGroupIds for the partitions hosted by this server for current table. + * @apiNote this involves Zookeeper read and should not be used frequently due to efficiency concerns. + */ + public Set getHostedPartitionsGroupIds() { + Set partitionsHostedByThisServer = new HashSet<>(); + List segments = TableStateUtils.getSegmentsInGivenStateForThisInstance(_helixManager, _tableNameWithType, + CommonConstants.Helix.StateModel.SegmentStateModel.CONSUMING); + for (String segmentNameStr : segments) { + LLCSegmentName segmentName = new LLCSegmentName(segmentNameStr); + partitionsHostedByThisServer.add(segmentName.getPartitionGroupId()); + } + return partitionsHostedByThisServer; + } + public RealtimeSegmentStatsHistory getStatsHistory() { return _statsHistory; } @@ -223,7 +350,7 @@ public Semaphore getSegmentBuildSemaphore() { } public String getConsumerDir() { - String consumerDirPath = _tableDataManagerConfig.getConsumerDir(); + String consumerDirPath = _instanceDataManagerConfig.getConsumerDir(); File consumerDir; // If a consumer directory has been configured, use it to create a per-table path under the consumer dir. // Otherwise, create a sub-dir under the table-specific data director and use it for consumer mmaps @@ -256,115 +383,160 @@ public boolean isPartialUpsertEnabled() { && _tableUpsertMetadataManager.getUpsertMode() == UpsertConfig.Mode.PARTIAL; } - /* - * This call comes in one of two ways: - * For HL Segments: - * - We are being directed by helix to own up all the segments that we committed and are still in retention. In - * this case we treat it exactly like how OfflineTableDataManager would -- wrap it into an - * OfflineSegmentDataManager, and put it in the map. - * - We are being asked to own up a new realtime segment. In this case, we wrap the segment with a - * RealTimeSegmentDataManager (that kicks off consumption). When the segment is committed we get notified via the - * notifySegmentCommitted call, at which time we replace the segment with the OfflineSegmentDataManager - * - * For LL Segments: - * - We are being asked to start consuming from a partition. - * - We did not know about the segment and are being asked to download and own the segment (re-balancing, or - * replacing a realtime server with a fresh one, maybe). We need to look at segment metadata and decide whether - * to start consuming or download the segment. + private boolean isUpsertPreloadEnabled() { + UpsertConfig upsertConfig = _tableConfig.getUpsertConfig(); + return _tableUpsertMetadataManager != null && _segmentPreloadExecutor != null && upsertConfig != null + && upsertConfig.isEnableSnapshot() && upsertConfig.isEnablePreload(); + } + + /** + * Handles upsert preload, and returns whether the upsert preload is enabled. */ - @Override - public void addSegment(String segmentName, IndexLoadingConfig indexLoadingConfig, SegmentZKMetadata segmentZKMetadata) + private void handleUpsertPreload(SegmentZKMetadata zkMetadata, IndexLoadingConfig indexLoadingConfig) { + if (!isUpsertPreloadEnabled()) { + return; + } + String segmentName = zkMetadata.getSegmentName(); + Integer partitionId = SegmentUtils.getRealtimeSegmentPartitionId(segmentName, zkMetadata, null); + Preconditions.checkState(partitionId != null, + String.format("Failed to get partition id for segment: %s in upsert-enabled table: %s", segmentName, + _tableNameWithType)); + _tableUpsertMetadataManager.getOrCreatePartitionManager(partitionId).preloadSegments(indexLoadingConfig); + } + + protected void doAddOnlineSegment(String segmentName) throws Exception { + SegmentZKMetadata zkMetadata = fetchZKMetadata(segmentName); + Preconditions.checkState(zkMetadata.getStatus() != Status.IN_PROGRESS, + "Segment: %s of table: %s is not committed, cannot make it ONLINE", segmentName, _tableNameWithType); + IndexLoadingConfig indexLoadingConfig = fetchIndexLoadingConfig(); + indexLoadingConfig.setSegmentTier(zkMetadata.getTier()); + handleUpsertPreload(zkMetadata, indexLoadingConfig); + SegmentDataManager segmentDataManager = _segmentDataManagerMap.get(segmentName); + if (segmentDataManager == null) { + addNewOnlineSegment(zkMetadata, indexLoadingConfig); + } else { + if (segmentDataManager instanceof RealtimeSegmentDataManager) { + _logger.info("Changing segment: {} from CONSUMING to ONLINE", segmentName); + ((RealtimeSegmentDataManager) segmentDataManager).goOnlineFromConsuming(zkMetadata); + onConsumingToOnline(segmentName); + } else { + replaceSegmentIfCrcMismatch(segmentDataManager, zkMetadata, indexLoadingConfig); + } + } + } + + @Override + public void addConsumingSegment(String segmentName) + throws AttemptsExceededException, RetriableOperationException { + Preconditions.checkState(!_shutDown, + "Table data manager is already shut down, cannot add CONSUMING segment: %s to table: %s", segmentName, + _tableNameWithType); + _logger.info("Adding CONSUMING segment: {}", segmentName); + Lock segmentLock = getSegmentLock(segmentName); + segmentLock.lock(); + try { + doAddConsumingSegment(segmentName); + } catch (Exception e) { + addSegmentError(segmentName, + new SegmentErrorInfo(System.currentTimeMillis(), "Caught exception while adding CONSUMING segment", e)); + throw e; + } finally { + segmentLock.unlock(); + } + } + + private void doAddConsumingSegment(String segmentName) + throws AttemptsExceededException, RetriableOperationException { + SegmentZKMetadata zkMetadata = fetchZKMetadata(segmentName); + if (zkMetadata.getStatus() != Status.IN_PROGRESS) { + // NOTE: We do not throw exception here because the segment might have just been committed before the state + // transition is processed. We can skip adding this segment, and the segment will enter CONSUMING state in + // Helix, then we can rely on the following CONSUMING -> ONLINE state transition to add it. + _logger.warn("Segment: {} is already committed, skipping adding it as CONSUMING segment", segmentName); + return; + } + IndexLoadingConfig indexLoadingConfig = fetchIndexLoadingConfig(); + handleUpsertPreload(zkMetadata, indexLoadingConfig); SegmentDataManager segmentDataManager = _segmentDataManagerMap.get(segmentName); if (segmentDataManager != null) { - _logger.warn("Skipping adding existing segment: {} for table: {} with data manager class: {}", segmentName, - _tableNameWithType, segmentDataManager.getClass().getSimpleName()); + _logger.warn("Segment: {} ({}) already exists, skipping adding it as CONSUMING segment", segmentName, + segmentDataManager instanceof RealtimeSegmentDataManager ? "CONSUMING" : "COMPLETED"); return; } + _logger.info("Adding new CONSUMING segment: {}", segmentName); + + // Delete the segment directory if it already exists File segmentDir = new File(_indexDir, segmentName); - // Restart during segment reload might leave segment in inconsistent state (index directory might not exist but - // segment backup directory existed), need to first try to recover from reload failure before checking the existence - // of the index directory and loading segment from it - LoaderUtils.reloadFailureRecovery(segmentDir); + FileUtils.deleteQuietly(segmentDir); TableConfig tableConfig = indexLoadingConfig.getTableConfig(); Schema schema = indexLoadingConfig.getSchema(); - assert schema != null; - boolean isHLCSegment = SegmentName.isHighLevelConsumerSegmentName(segmentName); - if (segmentZKMetadata.getStatus().isCompleted()) { - if (isHLCSegment && !segmentDir.exists()) { - throw new RuntimeException("Failed to find local copy for committed HLC segment: " + segmentName); - } - if (tryLoadExistingSegment(segmentName, indexLoadingConfig, segmentZKMetadata)) { - // The existing completed segment has been loaded successfully - return; - } else { - if (!isHLCSegment) { - // For LLC and uploaded segments, delete the local copy and download a new copy - _logger.error("Failed to load LLC segment: {}, downloading a new copy", segmentName); - FileUtils.deleteQuietly(segmentDir); - } else { - // For HLC segments, throw out the exception because there is no way to recover (controller does not have a - // copy of the segment) - throw new RuntimeException("Failed to load local HLC segment: " + segmentName); - } - } - // Local segment doesn't exist or cannot load, download a new copy - downloadAndReplaceSegment(segmentName, segmentZKMetadata, indexLoadingConfig, tableConfig); - return; - } else { - // Metadata has not been committed, delete the local segment if exists - FileUtils.deleteQuietly(segmentDir); - } - - // Start a new consuming segment - if (!isValid(schema, tableConfig.getIndexingConfig())) { - _logger.error("Not adding segment {}", segmentName); - throw new RuntimeException("Mismatching schema/table config for " + _tableNameWithType); - } + assert tableConfig != null && schema != null; + validate(tableConfig, schema); VirtualColumnProviderFactory.addBuiltInVirtualColumnsToSegmentSchema(schema, segmentName); + setDefaultTimeValueIfInvalid(tableConfig, schema, zkMetadata); - if (!isHLCSegment) { - // Generates only one semaphore for every partitionGroupId - LLCSegmentName llcSegmentName = new LLCSegmentName(segmentName); - int partitionGroupId = llcSegmentName.getPartitionGroupId(); - Semaphore semaphore = _partitionGroupIdToSemaphoreMap.computeIfAbsent(partitionGroupId, k -> new Semaphore(1)); - PartitionUpsertMetadataManager partitionUpsertMetadataManager = - _tableUpsertMetadataManager != null ? _tableUpsertMetadataManager.getOrCreatePartitionManager( - partitionGroupId) : null; - PartitionDedupMetadataManager partitionDedupMetadataManager = - _tableDedupMetadataManager != null ? _tableDedupMetadataManager.getOrCreatePartitionManager(partitionGroupId) - : null; - // For dedup and partial-upsert, wait for all segments loaded before creating the consuming segment - if (isDedupEnabled() || isPartialUpsertEnabled()) { - if (!_allSegmentsLoaded.get()) { - synchronized (_allSegmentsLoaded) { - if (!_allSegmentsLoaded.get()) { - TableStateUtils.waitForAllSegmentsLoaded(_helixManager, _tableNameWithType); - _allSegmentsLoaded.set(true); - } - } - } - } + // Generates only one semaphore for every partition + LLCSegmentName llcSegmentName = new LLCSegmentName(segmentName); + int partitionGroupId = llcSegmentName.getPartitionGroupId(); + Semaphore semaphore = _partitionGroupIdToSemaphoreMap.computeIfAbsent(partitionGroupId, k -> new Semaphore(1)); - segmentDataManager = - new LLRealtimeSegmentDataManager(segmentZKMetadata, tableConfig, this, _indexDir.getAbsolutePath(), - indexLoadingConfig, schema, llcSegmentName, semaphore, _serverMetrics, partitionUpsertMetadataManager, - partitionDedupMetadataManager); - } else { - InstanceZKMetadata instanceZKMetadata = ZKMetadataProvider.getInstanceZKMetadata(_propertyStore, _instanceId); - segmentDataManager = new HLRealtimeSegmentDataManager(segmentZKMetadata, tableConfig, instanceZKMetadata, this, - _indexDir.getAbsolutePath(), indexLoadingConfig, schema, _serverMetrics); - } + // Create the segment data manager and register it + PartitionUpsertMetadataManager partitionUpsertMetadataManager = + _tableUpsertMetadataManager != null ? _tableUpsertMetadataManager.getOrCreatePartitionManager(partitionGroupId) + : null; + PartitionDedupMetadataManager partitionDedupMetadataManager = + _tableDedupMetadataManager != null ? _tableDedupMetadataManager.getOrCreatePartitionManager(partitionGroupId) + : null; + RealtimeSegmentDataManager realtimeSegmentDataManager = + new RealtimeSegmentDataManager(zkMetadata, tableConfig, this, _indexDir.getAbsolutePath(), indexLoadingConfig, + schema, llcSegmentName, semaphore, _serverMetrics, partitionUpsertMetadataManager, + partitionDedupMetadataManager, _isTableReadyToConsumeData); + realtimeSegmentDataManager.startConsumption(); + _serverMetrics.addValueToTableGauge(_tableNameWithType, ServerGauge.SEGMENT_COUNT, 1); + registerSegment(segmentName, realtimeSegmentDataManager); + + _logger.info("Added new CONSUMING segment: {}", segmentName); + } - _logger.info("Initialized RealtimeSegmentDataManager - " + segmentName); - registerSegment(segmentName, segmentDataManager); - _serverMetrics.addValueToTableGauge(_tableNameWithType, ServerGauge.SEGMENT_COUNT, 1L); + /** + * Sets the default time value in the schema as the segment creation time if it is invalid. Time column is used to + * manage the segments, so its values have to be within the valid range. + */ + @VisibleForTesting + static void setDefaultTimeValueIfInvalid(TableConfig tableConfig, Schema schema, SegmentZKMetadata zkMetadata) { + String timeColumnName = tableConfig.getValidationConfig().getTimeColumnName(); + if (StringUtils.isEmpty(timeColumnName)) { + return; + } + DateTimeFieldSpec timeColumnSpec = schema.getSpecForTimeColumn(timeColumnName); + Preconditions.checkState(timeColumnSpec != null, "Failed to find time field: %s from schema: %s", timeColumnName, + schema.getSchemaName()); + String defaultTimeString = timeColumnSpec.getDefaultNullValueString(); + DateTimeFormatSpec dateTimeFormatSpec = timeColumnSpec.getFormatSpec(); + try { + long defaultTimeMs = dateTimeFormatSpec.fromFormatToMillis(defaultTimeString); + if (TimeUtils.timeValueInValidRange(defaultTimeMs)) { + return; + } + } catch (Exception e) { + // Ignore + } + String creationTimeString = dateTimeFormatSpec.fromMillisToFormat(zkMetadata.getCreationTime()); + Object creationTime = timeColumnSpec.getDataType().convert(creationTimeString); + timeColumnSpec.setDefaultNullValue(creationTime); + LOGGER.info( + "Default time: {} does not comply with format: {}, using creation time: {} as the default time for table: {}", + defaultTimeString, timeColumnSpec.getFormat(), creationTime, tableConfig.getTableName()); } @Override public void addSegment(ImmutableSegment immutableSegment) { + String segmentName = immutableSegment.getSegmentName(); + Preconditions.checkState(!_shutDown, "Table data manager is already shut down, cannot add segment: %s to table: %s", + segmentName, _tableNameWithType); if (isUpsertEnabled()) { handleUpsert(immutableSegment); return; @@ -393,7 +565,7 @@ private void buildDedupMeta(ImmutableSegmentImpl immutableSegment) { private void handleUpsert(ImmutableSegment immutableSegment) { String segmentName = immutableSegment.getSegmentName(); - _logger.info("Adding immutable segment: {} to upsert-enabled table: {}", segmentName, _tableNameWithType); + _logger.info("Adding immutable segment: {} with upsert enabled", segmentName); Integer partitionId = SegmentUtils.getRealtimeSegmentPartitionId(segmentName, _tableNameWithType, _helixManager, null); @@ -407,154 +579,88 @@ private void handleUpsert(ImmutableSegment immutableSegment) { immutableSegment.getSegmentMetadata().getTotalDocs()); _serverMetrics.addValueToTableGauge(_tableNameWithType, ServerGauge.SEGMENT_COUNT, 1L); ImmutableSegmentDataManager newSegmentManager = new ImmutableSegmentDataManager(immutableSegment); - SegmentDataManager oldSegmentManager = registerSegment(segmentName, newSegmentManager); + if (partitionUpsertMetadataManager.isPreloading()) { + // Preloading segment is ensured to be handled by a single thread, so no need to take the segment upsert lock. + // Besides, preloading happens before the table partition is made ready for any queries. + partitionUpsertMetadataManager.preloadSegment(immutableSegment); + registerSegment(segmentName, newSegmentManager); + _logger.info("Preloaded immutable segment: {} with upsert enabled", segmentName); + return; + } + SegmentDataManager oldSegmentManager = _segmentDataManagerMap.get(segmentName); if (oldSegmentManager == null) { + // When adding a new segment, we should register it 'before' it is fully initialized by + // partitionUpsertMetadataManager. Because when processing docs in the new segment, the docs in the other + // segments may be invalidated, making the queries see less valid docs than expected. We should let query + // access the new segment asap even though its validDocId bitmap is still being filled by + // partitionUpsertMetadataManager. + registerSegment(segmentName, newSegmentManager); partitionUpsertMetadataManager.addSegment(immutableSegment); - _logger.info("Added new immutable segment: {} to upsert-enabled table: {}", segmentName, _tableNameWithType); + _logger.info("Added new immutable segment: {} with upsert enabled", segmentName); } else { + // When replacing a segment, we should register the new segment 'after' it is fully initialized by + // partitionUpsertMetadataManager to fill up its validDocId bitmap. Otherwise, the queries will lose the access + // to the valid docs in the old segment immediately, but the validDocId bitmap of the new segment is still + // being filled by partitionUpsertMetadataManager, making the queries see less valid docs than expected. + // When replacing a segment, the new and old segments are assumed to have same set of valid docs for data + // consistency, otherwise the new segment should be named differently to go through the addSegment flow above. IndexSegment oldSegment = oldSegmentManager.getSegment(); partitionUpsertMetadataManager.replaceSegment(immutableSegment, oldSegment); - _logger.info("Replaced {} segment: {} of upsert-enabled table: {}", - oldSegment instanceof ImmutableSegment ? "immutable" : "mutable", segmentName, _tableNameWithType); + registerSegment(segmentName, newSegmentManager); + _logger.info("Replaced {} segment: {} with upsert enabled", + oldSegment instanceof ImmutableSegment ? "immutable" : "mutable", segmentName); + oldSegmentManager.offload(); releaseSegment(oldSegmentManager); } } - @Override - protected boolean allowDownload(String segmentName, SegmentZKMetadata zkMetadata) { - // Cannot download HLC segment or consuming segment - if (SegmentName.isHighLevelConsumerSegmentName(segmentName) || zkMetadata.getStatus() == Status.IN_PROGRESS) { - return false; - } - // TODO: may support download from peer servers as well. - return !METADATA_URI_FOR_PEER_DOWNLOAD.equals(zkMetadata.getDownloadUrl()); - } - - void downloadAndReplaceSegment(String segmentName, SegmentZKMetadata segmentZKMetadata, - IndexLoadingConfig indexLoadingConfig, TableConfig tableConfig) { - String uri = segmentZKMetadata.getDownloadUrl(); - if (!METADATA_URI_FOR_PEER_DOWNLOAD.equals(uri)) { - try { - // TODO: cleanup and consolidate the segment loading logic a bit for OFFLINE and REALTIME tables. - // https://github.com/apache/pinot/issues/9752 - downloadSegmentFromDeepStore(segmentName, indexLoadingConfig, uri); - } catch (Exception e) { - _logger.warn("Download segment {} from deepstore uri {} failed.", segmentName, uri, e); - // Download from deep store failed; try to download from peer if peer download is setup for the table. - if (isPeerSegmentDownloadEnabled(tableConfig)) { - downloadSegmentFromPeer(segmentName, tableConfig.getValidationConfig().getPeerSegmentDownloadScheme(), - indexLoadingConfig); - } else { - throw e; - } - } - } else { - if (isPeerSegmentDownloadEnabled(tableConfig)) { - downloadSegmentFromPeer(segmentName, tableConfig.getValidationConfig().getPeerSegmentDownloadScheme(), - indexLoadingConfig); - } else { - throw new RuntimeException("Peer segment download not enabled for segment " + segmentName); - } - } - } - - private void downloadSegmentFromDeepStore(String segmentName, IndexLoadingConfig indexLoadingConfig, String uri) { - // This could leave temporary directories in _indexDir if JVM shuts down before the temp directory is deleted. - // This is fine since the temporary directories are deleted when the table data manager calls init. - File tempRootDir = null; - try { - tempRootDir = getTmpSegmentDataDir("tmp-" + segmentName + "." + System.currentTimeMillis()); - File segmentTarFile = new File(tempRootDir, segmentName + TarGzCompressionUtils.TAR_GZ_FILE_EXTENSION); - SegmentFetcherFactory.fetchSegmentToLocal(uri, segmentTarFile); - _logger.info("Downloaded file from {} to {}; Length of downloaded file: {}", uri, segmentTarFile, - segmentTarFile.length()); - untarAndMoveSegment(segmentName, indexLoadingConfig, segmentTarFile, tempRootDir); - } catch (Exception e) { - _logger.warn("Failed to download segment {} from deep store: ", segmentName, e); - throw new RuntimeException(e); - } finally { - FileUtils.deleteQuietly(tempRootDir); - } + /** + * Replaces the CONSUMING segment with a downloaded committed one. + */ + public void downloadAndReplaceConsumingSegment(SegmentZKMetadata zkMetadata) + throws Exception { + String segmentName = zkMetadata.getSegmentName(); + _logger.info("Downloading and replacing CONSUMING segment: {} with committed one", segmentName); + File indexDir = downloadSegment(zkMetadata); + // Get a new index loading config with latest table config and schema to load the segment + IndexLoadingConfig indexLoadingConfig = fetchIndexLoadingConfig(); + indexLoadingConfig.setSegmentTier(zkMetadata.getTier()); + addSegment(ImmutableSegmentLoader.load(indexDir, indexLoadingConfig)); + _logger.info("Downloaded and replaced CONSUMING segment: {}", segmentName); } /** - * Untars the new segment and replaces the existing segment. + * Replaces the CONSUMING segment with the one sealed locally. */ - private void untarAndMoveSegment(String segmentName, IndexLoadingConfig indexLoadingConfig, File segmentTarFile, - File tempRootDir) - throws IOException { - File untarDir = new File(tempRootDir, segmentName); - File untaredSegDir = TarGzCompressionUtils.untar(segmentTarFile, untarDir).get(0); - _logger.info("Uncompressed file {} into tmp dir {}", segmentTarFile, untarDir); + public void replaceConsumingSegment(String segmentName) + throws Exception { + _logger.info("Replacing CONSUMING segment: {} with the one sealed locally", segmentName); File indexDir = new File(_indexDir, segmentName); - FileUtils.deleteQuietly(indexDir); - FileUtils.moveDirectory(untaredSegDir, indexDir); - _logger.info("Replacing LLC Segment {}", segmentName); - replaceLLSegment(segmentName, indexLoadingConfig); + // Get a new index loading config with latest table config and schema to load the segment + IndexLoadingConfig indexLoadingConfig = fetchIndexLoadingConfig(); + addSegment(ImmutableSegmentLoader.load(indexDir, indexLoadingConfig)); + _logger.info("Replaced CONSUMING segment: {}", segmentName); } - private boolean isPeerSegmentDownloadEnabled(TableConfig tableConfig) { - return - CommonConstants.HTTP_PROTOCOL.equalsIgnoreCase(tableConfig.getValidationConfig().getPeerSegmentDownloadScheme()) - || CommonConstants.HTTPS_PROTOCOL.equalsIgnoreCase( - tableConfig.getValidationConfig().getPeerSegmentDownloadScheme()); + public String getServerInstance() { + return _instanceId; } - private void downloadSegmentFromPeer(String segmentName, String downloadScheme, - IndexLoadingConfig indexLoadingConfig) { - File tempRootDir = null; - try { - tempRootDir = getTmpSegmentDataDir("tmp-" + segmentName + "." + System.currentTimeMillis()); - File segmentTarFile = new File(tempRootDir, segmentName + TarGzCompressionUtils.TAR_GZ_FILE_EXTENSION); - // First find servers hosting the segment in a ONLINE state. - List peerSegmentURIs = PeerServerSegmentFinder.getPeerServerURIs(segmentName, downloadScheme, _helixManager); - // Next download the segment from a randomly chosen server using configured scheme. - SegmentFetcherFactory.getSegmentFetcher(downloadScheme).fetchSegmentToLocal(peerSegmentURIs, segmentTarFile); - _logger.info("Fetched segment {} from: {} to: {} of size: {}", segmentName, peerSegmentURIs, segmentTarFile, - segmentTarFile.length()); - untarAndMoveSegment(segmentName, indexLoadingConfig, segmentTarFile, tempRootDir); - } catch (Exception e) { - _logger.warn("Download and move segment {} from peer with scheme {} failed.", segmentName, downloadScheme, e); - throw new RuntimeException(e); - } finally { - FileUtils.deleteQuietly(tempRootDir); - } + @VisibleForTesting + public TableUpsertMetadataManager getTableUpsertMetadataManager() { + return _tableUpsertMetadataManager; } /** - * Replaces a committed HLC REALTIME segment. - */ - public void replaceHLSegment(SegmentZKMetadata segmentZKMetadata, IndexLoadingConfig indexLoadingConfig) - throws Exception { - ZKMetadataProvider.setSegmentZKMetadata(_propertyStore, _tableNameWithType, segmentZKMetadata); - File indexDir = new File(_indexDir, segmentZKMetadata.getSegmentName()); - Schema schema = ZKMetadataProvider.getTableSchema(_propertyStore, _tableNameWithType); - addSegment(ImmutableSegmentLoader.load(indexDir, indexLoadingConfig, schema)); - } - - /** - * Replaces a committed LLC REALTIME segment. + * Retrieves a mapping of partition id to the primary key count for the partition. + * + * @return A {@code Map} where keys are partition id and values are count of primary keys for that specific partition. */ - public void replaceLLSegment(String segmentName, IndexLoadingConfig indexLoadingConfig) { - File indexDir = new File(_indexDir, segmentName); - // Use the latest table config and schema to load the segment - TableConfig tableConfig = ZKMetadataProvider.getTableConfig(_propertyStore, _tableNameWithType); - Preconditions.checkState(tableConfig != null, "Failed to get table config for table: {}", _tableNameWithType); - Schema schema = ZKMetadataProvider.getTableSchema(_propertyStore, tableConfig); - - // Construct a new indexLoadingConfig with the updated tableConfig and schema. - InstanceDataManagerConfig instanceDataManagerConfig = indexLoadingConfig.getInstanceDataManagerConfig(); - IndexLoadingConfig newIndexLoadingConfig = new IndexLoadingConfig(instanceDataManagerConfig, tableConfig, schema); - - try { - addSegment(indexDir, newIndexLoadingConfig); - } catch (Exception e) { - throw new RuntimeException(e); + public Map getUpsertPartitionToPrimaryKeyCount() { + if (isUpsertEnabled()) { + return _tableUpsertMetadataManager.getPartitionToPrimaryKeyCount(); } - } - - public String getServerInstance() { - return _instanceId; + return Collections.emptyMap(); } /** @@ -572,31 +678,23 @@ public String getServerInstance() { * * If we add more validations, it may make sense to split this method into multiple validation methods. * But then, we are trying to figure out all the invalid cases before we return from this method... - * - * @return true if schema is valid. */ - private boolean isValid(Schema schema, IndexingConfig indexingConfig) { + private void validate(TableConfig tableConfig, Schema schema) { // 1. Make sure that the sorted column is not a multi-value field. + IndexingConfig indexingConfig = tableConfig.getIndexingConfig(); List sortedColumns = indexingConfig.getSortedColumn(); - boolean isValid = true; if (CollectionUtils.isNotEmpty(sortedColumns)) { - final String sortedColumn = sortedColumns.get(0); + String sortedColumn = sortedColumns.get(0); if (sortedColumns.size() > 1) { _logger.warn("More than one sorted column configured. Using {}", sortedColumn); } FieldSpec fieldSpec = schema.getFieldSpecFor(sortedColumn); - if (!fieldSpec.isSingleValueField()) { - _logger.error("Cannot configure multi-valued column {} as sorted column", sortedColumn); - isValid = false; - } - } - // 2. We want to get the schema errors, if any, even if isValid is false; - try { - SchemaUtils.validate(schema); - } catch (Exception e) { - _logger.error("Caught exception while validating schema: {}", schema.getSchemaName(), e); - isValid = false; + Preconditions.checkArgument(fieldSpec != null, "Failed to find sorted column: %s in schema for table: %s", + sortedColumn, _tableNameWithType); + Preconditions.checkArgument(fieldSpec.isSingleValueField(), + "Cannot configure multi-valued column %s as sorted column for table: %s", sortedColumn, _tableNameWithType); } - return isValid; + // 2. Validate the schema itself + SchemaUtils.validate(schema); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentBuildTimeLeaseExtender.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentBuildTimeLeaseExtender.java index 19dee37401e0..397a78305ec2 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentBuildTimeLeaseExtender.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentBuildTimeLeaseExtender.java @@ -102,7 +102,7 @@ public void shutDown() { Future future = entry.getValue(); boolean cancelled = future.cancel(true); if (!cancelled) { - LOGGER.warn("Task could not be cancelled for {}" + entry.getKey()); + LOGGER.warn("Task could not be cancelled for {}", entry.getKey()); } } _segmentToFutureMap.clear(); @@ -133,7 +133,7 @@ public void removeSegment(final String segmentId) { if (future != null) { boolean cancelled = future.cancel(true); if (!cancelled) { - LOGGER.warn("Task could not be cancelled for {}" + segmentId); + LOGGER.warn("Task could not be cancelled for {}", segmentId); } } _segmentToFutureMap.remove(segmentId); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentCommitter.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentCommitter.java index 922c54bd5527..b9c1270e39b8 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentCommitter.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentCommitter.java @@ -30,5 +30,5 @@ public interface SegmentCommitter { * @param segmentBuildDescriptor object that describes segment to be committed * @return */ - SegmentCompletionProtocol.Response commit(LLRealtimeSegmentDataManager.SegmentBuildDescriptor segmentBuildDescriptor); + SegmentCompletionProtocol.Response commit(RealtimeSegmentDataManager.SegmentBuildDescriptor segmentBuildDescriptor); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentCommitterFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentCommitterFactory.java index 30e4d1d92f20..f8315b99d968 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentCommitterFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentCommitterFactory.java @@ -24,6 +24,8 @@ import org.apache.pinot.segment.local.segment.index.loader.IndexLoadingConfig; import org.apache.pinot.server.realtime.ServerSegmentCompletionProtocolHandler; import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.stream.StreamConfig; +import org.apache.pinot.spi.utils.IngestionConfigUtils; import org.slf4j.Logger; @@ -34,6 +36,7 @@ public class SegmentCommitterFactory { private static Logger _logger; private final ServerSegmentCompletionProtocolHandler _protocolHandler; private final TableConfig _tableConfig; + private final StreamConfig _streamConfig; private final ServerMetrics _serverMetrics; private final IndexLoadingConfig _indexLoadingConfig; @@ -42,29 +45,32 @@ public SegmentCommitterFactory(Logger segmentLogger, ServerSegmentCompletionProt _logger = segmentLogger; _protocolHandler = protocolHandler; _tableConfig = tableConfig; + _streamConfig = new StreamConfig(_tableConfig.getTableName(), + IngestionConfigUtils.getStreamConfigMap(_tableConfig)); _indexLoadingConfig = indexLoadingConfig; _serverMetrics = serverMetrics; } - public SegmentCommitter createSegmentCommitter(boolean isSplitCommit, SegmentCompletionProtocol.Request.Params params, + public SegmentCommitter createSegmentCommitter(SegmentCompletionProtocol.Request.Params params, String controllerVipUrl) throws URISyntaxException { - if (!isSplitCommit) { - return new DefaultSegmentCommitter(_logger, _protocolHandler, params); - } + boolean uploadToFs = _streamConfig.isServerUploadToDeepStore(); + String peerSegmentDownloadScheme = _tableConfig.getValidationConfig().getPeerSegmentDownloadScheme(); + String segmentStoreUri = _indexLoadingConfig.getSegmentStoreURI(); + SegmentUploader segmentUploader; - // TODO Instead of using a peer segment download scheme to control how the servers do split commit, we should use - // other configs such as server or controller configs or controller responses to the servers. - if (_tableConfig.getValidationConfig().getPeerSegmentDownloadScheme() != null) { - segmentUploader = new PinotFSSegmentUploader(_indexLoadingConfig.getSegmentStoreURI(), - PinotFSSegmentUploader.DEFAULT_SEGMENT_UPLOAD_TIMEOUT_MILLIS); - return new PeerSchemeSplitSegmentCommitter(_logger, _protocolHandler, params, segmentUploader); + if (uploadToFs || peerSegmentDownloadScheme != null) { + // TODO: peer scheme non-null check exists for backwards compatibility. remove check once users have migrated + segmentUploader = new PinotFSSegmentUploader(segmentStoreUri, + ServerSegmentCompletionProtocolHandler.getSegmentUploadRequestTimeoutMs(), _serverMetrics); + } else { + segmentUploader = new Server2ControllerSegmentUploader(_logger, + _protocolHandler.getFileUploadDownloadClient(), + _protocolHandler.getSegmentCommitUploadURL(params, controllerVipUrl), params.getSegmentName(), + ServerSegmentCompletionProtocolHandler.getSegmentUploadRequestTimeoutMs(), _serverMetrics, + _protocolHandler.getAuthProvider(), _tableConfig.getTableName()); } - segmentUploader = new Server2ControllerSegmentUploader(_logger, _protocolHandler.getFileUploadDownloadClient(), - _protocolHandler.getSegmentCommitUploadURL(params, controllerVipUrl), params.getSegmentName(), - ServerSegmentCompletionProtocolHandler.getSegmentUploadRequestTimeoutMs(), _serverMetrics, - _protocolHandler.getAuthProvider()); - return new SplitSegmentCommitter(_logger, _protocolHandler, params, segmentUploader); + return new SplitSegmentCommitter(_logger, _protocolHandler, params, segmentUploader, peerSegmentDownloadScheme); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentCompletionUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentCompletionUtils.java new file mode 100644 index 000000000000..c7198eded79f --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentCompletionUtils.java @@ -0,0 +1,61 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.data.manager.realtime; + +import java.util.UUID; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class SegmentCompletionUtils { + private SegmentCompletionUtils() { + } + + private static final Logger LOGGER = LoggerFactory.getLogger(SegmentCompletionUtils.class); + // Used to create temporary segment file names + private static final String TMP = ".tmp."; + + /** + * Takes in a segment name, and returns a file name prefix that is used to store all attempted uploads of this + * segment when a segment is uploaded using split commit. Each attempt has a unique file name suffix + * @param segmentName segment name + * @return + */ + public static String getTmpSegmentNamePrefix(String segmentName) { + return segmentName + TMP; + } + + public static String generateTmpSegmentFileName(String segmentNameStr) { + return getTmpSegmentNamePrefix(segmentNameStr) + UUID.randomUUID(); + } + + public static boolean isTmpFile(String uri) { + String[] splits = StringUtils.splitByWholeSeparator(uri, TMP); + if (splits.length < 2) { + return false; + } + try { + UUID.fromString(splits[splits.length - 1]); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentUploader.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentUploader.java index 44d0a3606799..63bbe99a5bfb 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentUploader.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SegmentUploader.java @@ -24,6 +24,15 @@ public interface SegmentUploader { - // Returns the URI of the uploaded segment. null if the upload fails. + + /** + * Uploads the given segmentFile to the deep-store. Returns the URI where the segment is uploaded. + */ URI uploadSegment(File segmentFile, LLCSegmentName segmentName); + + /** + * Uploads the given segmentFile to the deep-store. Returns the URI where the segment is uploaded. The upload will + * wait for the specified timeout. + */ + URI uploadSegment(File segmentFile, LLCSegmentName segmentName, int timeoutInMillis); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/Server2ControllerSegmentUploader.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/Server2ControllerSegmentUploader.java index 2559616b49fc..4b00563125a8 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/Server2ControllerSegmentUploader.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/Server2ControllerSegmentUploader.java @@ -21,14 +21,18 @@ import java.io.File; import java.net.URI; import java.net.URISyntaxException; +import java.util.concurrent.TimeUnit; import org.apache.pinot.common.auth.AuthProviderUtils; +import org.apache.pinot.common.metrics.ServerMeter; import org.apache.pinot.common.metrics.ServerMetrics; +import org.apache.pinot.common.metrics.ServerTimer; import org.apache.pinot.common.protocols.SegmentCompletionProtocol; import org.apache.pinot.common.utils.FileUploadDownloadClient; import org.apache.pinot.common.utils.LLCSegmentName; import org.apache.pinot.core.util.SegmentCompletionProtocolUtils; import org.apache.pinot.server.realtime.ControllerLeaderLocator; import org.apache.pinot.spi.auth.AuthProvider; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.slf4j.Logger; @@ -42,10 +46,11 @@ public class Server2ControllerSegmentUploader implements SegmentUploader { private final int _segmentUploadRequestTimeoutMs; private final ServerMetrics _serverMetrics; private final AuthProvider _authProvider; + private final String _rawTableName; public Server2ControllerSegmentUploader(Logger segmentLogger, FileUploadDownloadClient fileUploadDownloadClient, String controllerSegmentUploadCommitUrl, String segmentName, int segmentUploadRequestTimeoutMs, - ServerMetrics serverMetrics, AuthProvider authProvider) + ServerMetrics serverMetrics, AuthProvider authProvider, String tableName) throws URISyntaxException { _segmentLogger = segmentLogger; _fileUploadDownloadClient = fileUploadDownloadClient; @@ -54,27 +59,41 @@ public Server2ControllerSegmentUploader(Logger segmentLogger, FileUploadDownload _segmentUploadRequestTimeoutMs = segmentUploadRequestTimeoutMs; _serverMetrics = serverMetrics; _authProvider = authProvider; + _rawTableName = TableNameBuilder.extractRawTableName(tableName); } @Override public URI uploadSegment(File segmentFile, LLCSegmentName segmentName) { - SegmentCompletionProtocol.Response response = uploadSegmentToController(segmentFile); + return uploadSegment(segmentFile, segmentName, _segmentUploadRequestTimeoutMs); + } + + @Override + public URI uploadSegment(File segmentFile, LLCSegmentName segmentName, int timeoutInMillis) { + SegmentCompletionProtocol.Response response = uploadSegmentToController(segmentFile, timeoutInMillis); if (response.getStatus() == SegmentCompletionProtocol.ControllerResponseStatus.UPLOAD_SUCCESS) { try { - return new URI(response.getSegmentLocation()); + URI uri = new URI(response.getSegmentLocation()); + _serverMetrics.addMeteredTableValue(_rawTableName, ServerMeter.SEGMENT_UPLOAD_SUCCESS, 1); + return uri; } catch (URISyntaxException e) { _segmentLogger.error("Error in segment location format: ", e); } } + _serverMetrics.addMeteredTableValue(_rawTableName, ServerMeter.SEGMENT_UPLOAD_FAILURE, 1); return null; } public SegmentCompletionProtocol.Response uploadSegmentToController(File segmentFile) { + return uploadSegmentToController(segmentFile, _segmentUploadRequestTimeoutMs); + } + + private SegmentCompletionProtocol.Response uploadSegmentToController(File segmentFile, int timeoutInMillis) { SegmentCompletionProtocol.Response response; + long startTime = System.currentTimeMillis(); try { String responseStr = _fileUploadDownloadClient .uploadSegment(_controllerSegmentUploadCommitUrl, _segmentName, segmentFile, - AuthProviderUtils.toRequestHeaders(_authProvider), null, _segmentUploadRequestTimeoutMs).getResponse(); + AuthProviderUtils.toRequestHeaders(_authProvider), null, timeoutInMillis).getResponse(); response = SegmentCompletionProtocol.Response.fromJsonString(responseStr); _segmentLogger.info("Controller response {} for {}", response.toJsonString(), _controllerSegmentUploadCommitUrl); if (response.getStatus().equals(SegmentCompletionProtocol.ControllerResponseStatus.NOT_LEADER)) { @@ -88,6 +107,10 @@ public SegmentCompletionProtocol.Response uploadSegmentToController(File segment // hence unable to send {@link SegmentCompletionProtocol.ControllerResponseStatus.NOT_LEADER} // If cache is not invalidated, we will not recover from exceptions until the controller comes back up ControllerLeaderLocator.getInstance().invalidateCachedControllerLeader(); + } finally { + long duration = System.currentTimeMillis() - startTime; + _serverMetrics.addTimedTableValue(_rawTableName, ServerTimer.SEGMENT_UPLOAD_TIME_MS, duration, + TimeUnit.MILLISECONDS); } SegmentCompletionProtocolUtils.raiseSegmentCompletionProtocolResponseMetric(_serverMetrics, response); return response; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SplitSegmentCommitter.java b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SplitSegmentCommitter.java index 33b2ac009315..1e4ebfe1f856 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SplitSegmentCommitter.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/manager/realtime/SplitSegmentCommitter.java @@ -18,11 +18,15 @@ */ package org.apache.pinot.core.data.manager.realtime; +import com.google.common.annotations.VisibleForTesting; import java.io.File; import java.net.URI; +import javax.annotation.Nullable; import org.apache.pinot.common.protocols.SegmentCompletionProtocol; import org.apache.pinot.common.utils.LLCSegmentName; import org.apache.pinot.server.realtime.ServerSegmentCompletionProtocolHandler; +import org.apache.pinot.spi.utils.CommonConstants; +import org.apache.pinot.spi.utils.StringUtil; import org.slf4j.Logger; @@ -34,19 +38,32 @@ public class SplitSegmentCommitter implements SegmentCommitter { private final SegmentCompletionProtocol.Request.Params _params; private final ServerSegmentCompletionProtocolHandler _protocolHandler; private final SegmentUploader _segmentUploader; + private final String _peerDownloadScheme; private final Logger _segmentLogger; public SplitSegmentCommitter(Logger segmentLogger, ServerSegmentCompletionProtocolHandler protocolHandler, - SegmentCompletionProtocol.Request.Params params, SegmentUploader segmentUploader) { + SegmentCompletionProtocol.Request.Params params, SegmentUploader segmentUploader, + @Nullable String peerDownloadScheme) { _segmentLogger = segmentLogger; _protocolHandler = protocolHandler; _params = new SegmentCompletionProtocol.Request.Params(params); _segmentUploader = segmentUploader; + _peerDownloadScheme = peerDownloadScheme; + } + + @VisibleForTesting + SegmentUploader getSegmentUploader() { + return _segmentUploader; + } + + public SplitSegmentCommitter(Logger segmentLogger, ServerSegmentCompletionProtocolHandler protocolHandler, + SegmentCompletionProtocol.Request.Params params, SegmentUploader segmentUploader) { + this(segmentLogger, protocolHandler, params, segmentUploader, null); } @Override public SegmentCompletionProtocol.Response commit( - LLRealtimeSegmentDataManager.SegmentBuildDescriptor segmentBuildDescriptor) { + RealtimeSegmentDataManager.SegmentBuildDescriptor segmentBuildDescriptor) { File segmentTarFile = segmentBuildDescriptor.getSegmentTarFile(); SegmentCompletionProtocol.Response segmentCommitStartResponse = _protocolHandler.segmentCommitStart(_params); @@ -79,6 +96,10 @@ protected String uploadSegment(File segmentTarFile, SegmentUploader segmentUploa if (segmentLocation != null) { return segmentLocation.toString(); } + if (_peerDownloadScheme != null) { + return StringUtil.join("/", CommonConstants.Segment.PEER_SEGMENT_DOWNLOAD_SCHEME, + params.getSegmentName()); + } return null; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/table/ConcurrentIndexedTable.java b/pinot-core/src/main/java/org/apache/pinot/core/data/table/ConcurrentIndexedTable.java index b5f8d6e0d004..119d47c79e89 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/table/ConcurrentIndexedTable.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/table/ConcurrentIndexedTable.java @@ -34,7 +34,12 @@ public class ConcurrentIndexedTable extends IndexedTable { public ConcurrentIndexedTable(DataSchema dataSchema, QueryContext queryContext, int resultSize, int trimSize, int trimThreshold) { - super(dataSchema, queryContext, resultSize, trimSize, trimThreshold, new ConcurrentHashMap<>()); + this(dataSchema, false, queryContext, resultSize, trimSize, trimThreshold); + } + + public ConcurrentIndexedTable(DataSchema dataSchema, boolean hasFinalInput, QueryContext queryContext, int resultSize, + int trimSize, int trimThreshold) { + super(dataSchema, hasFinalInput, queryContext, resultSize, trimSize, trimThreshold, new ConcurrentHashMap<>()); } /** diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/table/IndexedTable.java b/pinot-core/src/main/java/org/apache/pinot/core/data/table/IndexedTable.java index 012fdc1170d7..04598f424c99 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/table/IndexedTable.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/table/IndexedTable.java @@ -38,6 +38,7 @@ @SuppressWarnings({"rawtypes", "unchecked"}) public abstract class IndexedTable extends BaseTable { protected final Map _lookupMap; + protected final boolean _hasFinalInput; protected final int _resultSize; protected final int _numKeyColumns; protected final AggregationFunction[] _aggregationFunctions; @@ -54,16 +55,18 @@ public abstract class IndexedTable extends BaseTable { * Constructor for the IndexedTable. * * @param dataSchema Data schema of the table + * @param hasFinalInput Whether the input is the final aggregate result * @param queryContext Query context * @param resultSize Number of records to keep in the final result after calling {@link #finish(boolean, boolean)} * @param trimSize Number of records to keep when trimming the table * @param trimThreshold Trim the table when the number of records exceeds the threshold * @param lookupMap Map from keys to records */ - protected IndexedTable(DataSchema dataSchema, QueryContext queryContext, int resultSize, int trimSize, - int trimThreshold, Map lookupMap) { + protected IndexedTable(DataSchema dataSchema, boolean hasFinalInput, QueryContext queryContext, int resultSize, + int trimSize, int trimThreshold, Map lookupMap) { super(dataSchema); _lookupMap = lookupMap; + _hasFinalInput = hasFinalInput; _resultSize = resultSize; List groupByExpressions = queryContext.getGroupByExpressions(); @@ -74,7 +77,7 @@ protected IndexedTable(DataSchema dataSchema, QueryContext queryContext, int res if (orderByExpressions != null) { // GROUP BY with ORDER BY _hasOrderBy = true; - _tableResizer = new TableResizer(dataSchema, queryContext); + _tableResizer = new TableResizer(dataSchema, hasFinalInput, queryContext); // NOTE: trimSize is bounded by trimThreshold/2 to protect the server from using too much memory. // TODO: Re-evaluate it as it can lead to in-accurate results _trimSize = Math.min(trimSize, trimThreshold / 2); @@ -102,34 +105,32 @@ public boolean upsert(Record record) { * Adds a record with new key or updates a record with existing key. */ protected void addOrUpdateRecord(Key key, Record newRecord) { - _lookupMap.compute(key, (k, v) -> { - if (v == null) { - return newRecord; - } else { - Object[] existingValues = v.getValues(); - Object[] newValues = newRecord.getValues(); - int aggNum = 0; - for (int i = _numKeyColumns; i < _numColumns; i++) { - existingValues[i] = _aggregationFunctions[aggNum++].merge(existingValues[i], newValues[i]); - } - return v; - } - }); + _lookupMap.compute(key, (k, v) -> v == null ? newRecord : updateRecord(v, newRecord)); } /** * Updates a record with existing key. Record with new key will be ignored. */ protected void updateExistingRecord(Key key, Record newRecord) { - _lookupMap.computeIfPresent(key, (k, v) -> { - Object[] existingValues = v.getValues(); - Object[] newValues = newRecord.getValues(); - int aggNum = 0; - for (int i = _numKeyColumns; i < _numColumns; i++) { - existingValues[i] = _aggregationFunctions[aggNum++].merge(existingValues[i], newValues[i]); + _lookupMap.computeIfPresent(key, (k, v) -> updateRecord(v, newRecord)); + } + + private Record updateRecord(Record existingRecord, Record newRecord) { + Object[] existingValues = existingRecord.getValues(); + Object[] newValues = newRecord.getValues(); + int numAggregations = _aggregationFunctions.length; + int index = _numKeyColumns; + if (!_hasFinalInput) { + for (int i = 0; i < numAggregations; i++, index++) { + existingValues[index] = _aggregationFunctions[i].merge(existingValues[index], newValues[index]); } - return v; - }); + } else { + for (int i = 0; i < numAggregations; i++, index++) { + existingValues[index] = _aggregationFunctions[i].mergeFinalResult((Comparable) existingValues[index], + (Comparable) newValues[index]); + } + } + return existingRecord; } /** @@ -156,7 +157,8 @@ public void finish(boolean sort, boolean storeFinalResult) { _topRecords = _lookupMap.values(); } // TODO: Directly return final result in _tableResizer.getTopRecords to avoid extracting final result multiple times - if (storeFinalResult) { + assert !(_hasFinalInput && !storeFinalResult); + if (storeFinalResult && !_hasFinalInput) { ColumnDataType[] columnDataTypes = _dataSchema.getColumnDataTypes(); int numAggregationFunctions = _aggregationFunctions.length; for (int i = 0; i < numAggregationFunctions; i++) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/table/SimpleIndexedTable.java b/pinot-core/src/main/java/org/apache/pinot/core/data/table/SimpleIndexedTable.java index 800c64911231..2163620225b6 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/table/SimpleIndexedTable.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/table/SimpleIndexedTable.java @@ -32,7 +32,12 @@ public class SimpleIndexedTable extends IndexedTable { public SimpleIndexedTable(DataSchema dataSchema, QueryContext queryContext, int resultSize, int trimSize, int trimThreshold) { - super(dataSchema, queryContext, resultSize, trimSize, trimThreshold, new HashMap<>()); + this(dataSchema, false, queryContext, resultSize, trimSize, trimThreshold); + } + + public SimpleIndexedTable(DataSchema dataSchema, boolean hasFinalInput, QueryContext queryContext, int resultSize, + int trimSize, int trimThreshold) { + super(dataSchema, hasFinalInput, queryContext, resultSize, trimSize, trimThreshold, new HashMap<>()); } /** diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/table/TableResizer.java b/pinot-core/src/main/java/org/apache/pinot/core/data/table/TableResizer.java index 7f6704fd7a9e..452186b9098a 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/table/TableResizer.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/table/TableResizer.java @@ -28,9 +28,12 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import org.apache.commons.lang3.tuple.Pair; import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.request.context.FilterContext; import org.apache.pinot.common.request.context.FunctionContext; import org.apache.pinot.common.request.context.OrderByExpressionContext; +import org.apache.pinot.common.request.context.RequestContextUtils; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.query.aggregation.function.AggregationFunction; @@ -47,16 +50,22 @@ @SuppressWarnings({"rawtypes", "unchecked"}) public class TableResizer { private final DataSchema _dataSchema; + private final boolean _hasFinalInput; private final int _numGroupByExpressions; private final Map _groupByExpressionIndexMap; private final AggregationFunction[] _aggregationFunctions; - private final Map _aggregationFunctionIndexMap; + private final Map, Integer> _filteredAggregationIndexMap; private final int _numOrderByExpressions; private final OrderByValueExtractor[] _orderByValueExtractors; private final Comparator _intermediateRecordComparator; public TableResizer(DataSchema dataSchema, QueryContext queryContext) { + this(dataSchema, false, queryContext); + } + + public TableResizer(DataSchema dataSchema, boolean hasFinalInput, QueryContext queryContext) { _dataSchema = dataSchema; + _hasFinalInput = hasFinalInput; // NOTE: The data schema will always have group-by expressions in the front, followed by aggregation functions of // the same order as in the query context. This is handled in AggregationGroupByOrderByOperator. @@ -71,18 +80,20 @@ public TableResizer(DataSchema dataSchema, QueryContext queryContext) { _aggregationFunctions = queryContext.getAggregationFunctions(); assert _aggregationFunctions != null; - _aggregationFunctionIndexMap = queryContext.getAggregationFunctionIndexMap(); - assert _aggregationFunctionIndexMap != null; + _filteredAggregationIndexMap = queryContext.getFilteredAggregationsIndexMap(); + assert _filteredAggregationIndexMap != null; List orderByExpressions = queryContext.getOrderByExpressions(); assert orderByExpressions != null; _numOrderByExpressions = orderByExpressions.size(); _orderByValueExtractors = new OrderByValueExtractor[_numOrderByExpressions]; Comparator[] comparators = new Comparator[_numOrderByExpressions]; + int[] nullComparisonResults = new int[_numOrderByExpressions]; for (int i = 0; i < _numOrderByExpressions; i++) { OrderByExpressionContext orderByExpression = orderByExpressions.get(i); _orderByValueExtractors[i] = getOrderByValueExtractor(orderByExpression.getExpression()); comparators[i] = orderByExpression.isAsc() ? Comparator.naturalOrder() : Comparator.reverseOrder(); + nullComparisonResults[i] = orderByExpression.isNullsLast() ? -1 : 1; } boolean nullHandlingEnabled = queryContext.isNullHandlingEnabled(); if (nullHandlingEnabled) { @@ -94,10 +105,9 @@ public TableResizer(DataSchema dataSchema, QueryContext queryContext) { if (v2 == null) { continue; } - // The default null ordering is NULLS LAST, regardless of the ordering direction. - return 1; + return -nullComparisonResults[i]; } else if (v2 == null) { - return -1; + return nullComparisonResults[i]; } int result = comparators[i].compare(v1, v2); if (result != 0) { @@ -124,7 +134,7 @@ public TableResizer(DataSchema dataSchema, QueryContext queryContext) { */ private OrderByValueExtractor getOrderByValueExtractor(ExpressionContext expression) { if (expression.getType() == ExpressionContext.Type.LITERAL) { - return new LiteralExtractor(expression.getLiteralString()); + return new LiteralExtractor(expression.getLiteral().getStringValue()); } Integer groupByExpressionIndex = _groupByExpressionIndexMap.get(expression); if (groupByExpressionIndex != null) { @@ -134,13 +144,26 @@ private OrderByValueExtractor getOrderByValueExtractor(ExpressionContext express FunctionContext function = expression.getFunction(); Preconditions.checkState(function != null, "Failed to find ORDER-BY expression: %s in the GROUP-BY clause", expression); + FunctionContext aggregation; + FilterContext filter; if (function.getType() == FunctionContext.Type.AGGREGATION) { // Aggregation function - return new AggregationFunctionExtractor(_aggregationFunctionIndexMap.get(function)); + aggregation = function; + filter = null; + } else if (function.getType() == FunctionContext.Type.TRANSFORM && "FILTER".equalsIgnoreCase( + function.getFunctionName())) { + // Filtered aggregation + aggregation = function.getArguments().get(0).getFunction(); + filter = RequestContextUtils.getFilter(function.getArguments().get(1)); } else { // Post-aggregation function return new PostAggregationFunctionExtractor(function); } + + int index = _filteredAggregationIndexMap.get(Pair.of(aggregation, filter)); + // For final aggregate result, we can handle it the same way as group key + return _hasFinalInput ? new GroupByExpressionExtractor(_numGroupByExpressions + index) + : new AggregationFunctionExtractor(index); } /** diff --git a/pinot-core/src/main/java/org/apache/pinot/core/data/table/UnboundedConcurrentIndexedTable.java b/pinot-core/src/main/java/org/apache/pinot/core/data/table/UnboundedConcurrentIndexedTable.java index 78788f51008e..67f82b201194 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/data/table/UnboundedConcurrentIndexedTable.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/data/table/UnboundedConcurrentIndexedTable.java @@ -36,7 +36,12 @@ public class UnboundedConcurrentIndexedTable extends ConcurrentIndexedTable { public UnboundedConcurrentIndexedTable(DataSchema dataSchema, QueryContext queryContext, int resultSize) { - super(dataSchema, queryContext, resultSize, Integer.MAX_VALUE, Integer.MAX_VALUE); + this(dataSchema, false, queryContext, resultSize); + } + + public UnboundedConcurrentIndexedTable(DataSchema dataSchema, boolean hasFinalInput, QueryContext queryContext, + int resultSize) { + super(dataSchema, hasFinalInput, queryContext, resultSize, Integer.MAX_VALUE, Integer.MAX_VALUE); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/function/scalar/SketchFunctions.java b/pinot-core/src/main/java/org/apache/pinot/core/function/scalar/SketchFunctions.java new file mode 100644 index 000000000000..90e313edb268 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/function/scalar/SketchFunctions.java @@ -0,0 +1,520 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.function.scalar; + +import com.clearspring.analytics.stream.cardinality.HyperLogLog; +import com.dynatrace.hash4j.distinctcount.UltraLogLog; +import java.math.BigDecimal; +import java.util.Base64; +import javax.annotation.Nullable; +import org.apache.datasketches.cpc.CpcSketch; +import org.apache.datasketches.cpc.CpcUnion; +import org.apache.datasketches.memory.Memory; +import org.apache.datasketches.theta.AnotB; +import org.apache.datasketches.theta.Intersection; +import org.apache.datasketches.theta.SetOperationBuilder; +import org.apache.datasketches.theta.Sketch; +import org.apache.datasketches.theta.Sketches; +import org.apache.datasketches.theta.Union; +import org.apache.datasketches.theta.UpdateSketch; +import org.apache.datasketches.tuple.aninteger.IntegerSketch; +import org.apache.datasketches.tuple.aninteger.IntegerSummary; +import org.apache.datasketches.tuple.aninteger.IntegerSummarySetOperations; +import org.apache.pinot.core.common.ObjectSerDeUtils; +import org.apache.pinot.segment.local.utils.UltraLogLogUtils; +import org.apache.pinot.spi.annotations.ScalarFunction; +import org.apache.pinot.spi.utils.CommonConstants; + + +/** + * Inbuilt Sketch Transformation Functions + * The functions can be used as UDFs in Query when added in the FunctionRegistry. + * @ScalarFunction annotation is used with each method for the registration + * + * Note these will just make sketches that contain a single item, these are intended to be used during ingestion to + * create sketches from raw data, which can be rolled up later. + * + * Note this is defined in pinot-core rather than pinot-common because pinot-core has dependencies on + * datasketches/clearspring analytics. + * + * Example usage: + * + * { + * "transformConfigs": [ + * { + * "columnName": "players", + * "transformFunction": "toThetaSketch(playerID)" + * }, + * { + * "columnName": "players", + * "transformFunction": "toThetaSketch(playerID, 1024)" + * }, + * { + * "columnName": "names", + * "transformFunction": "toHLL(playerName)" + * }, + * { + * "columnName": "names", + * "transformFunction": "toHLL(playerName, 8)" + * }, + * { + * "columnName": "players", + * "transformFunction": "toCpcSketch(playerID)" + * }, + * { + * "columnName": "players", + * "transformFunction": "toCpcSketch(playerID, 11)" + * } + * ] + * } + */ +public class SketchFunctions { + private static final SetOperationBuilder SET_OPERATION_BUILDER = new SetOperationBuilder(); + + private SketchFunctions() { + } + + /** + * Create a Theta Sketch containing the input + * + * @param input an Object we want to insert into the sketch, may be null to return an empty sketch + * @return serialized theta sketch as bytes + */ + @ScalarFunction(nullableParameters = true) + public static byte[] toThetaSketch(@Nullable Object input) { + return toThetaSketch(input, CommonConstants.Helix.DEFAULT_THETA_SKETCH_NOMINAL_ENTRIES); + } + + /** + * Create a Theta Sketch containing the input, with a configured nominal entries + * + * @param input an Object we want to insert into the sketch, may be null to return an empty sketch + * @param nominalEntries number of nominal entries the sketch is configured to keep + * @return serialized theta sketch as bytes + */ + @ScalarFunction(nullableParameters = true) + public static byte[] toThetaSketch(@Nullable Object input, int nominalEntries) { + UpdateSketch sketch = Sketches.updateSketchBuilder().setNominalEntries(nominalEntries).build(); + if (input != null) { + if (input instanceof Integer) { + sketch.update((Integer) input); + } else if (input instanceof Long) { + sketch.update((Long) input); + } else if (input instanceof Float) { + sketch.update((Float) input); + } else if (input instanceof Double) { + sketch.update((Double) input); + } else if (input instanceof BigDecimal) { + sketch.update(((BigDecimal) input).toString()); + } else if (input instanceof String) { + sketch.update((String) input); + } else if (input instanceof byte[]) { + sketch.update((byte[]) input); + } else { + throw new IllegalArgumentException( + "Unrecognised input type for Theta sketch: " + input.getClass().getSimpleName()); + } + } + return ObjectSerDeUtils.DATA_SKETCH_THETA_SER_DE.serialize(sketch.compact()); + } + + /** + * Create a HyperLogLog containing the input + * + * @param input an Object we want to insert into the HLL, may be null to return an empty HLL + * @return serialized HLL as bytes + */ + @ScalarFunction(nullableParameters = true) + public static byte[] toHLL(@Nullable Object input) { + return toHLL(input, CommonConstants.Helix.DEFAULT_HYPERLOGLOG_LOG2M); + } + + /** + * Create a HyperLogLog containing the input, with a configurable log2m + * + * @param input an Object we want to insert into the HLL, may be null to return an empty HLL + * @param log2m the log2m value for the created HyperLogLog + * @return serialized HLL as bytes + */ + @ScalarFunction(nullableParameters = true) + public static byte[] toHLL(@Nullable Object input, int log2m) { + HyperLogLog hll = new HyperLogLog(log2m); + if (input != null) { + hll.offer(input); + } + return ObjectSerDeUtils.HYPER_LOG_LOG_SER_DE.serialize(hll); + } + + /** + * Create a Tuple Sketch containing the key and value supplied + * + * @param key an Object we want to insert as the key of the sketch, may be null to return an empty sketch + * @param value an Integer we want to associate as the value to go along with the key, may be null to return an + * empty sketch + * @return serialized tuple sketch as bytes + */ + @ScalarFunction(nullableParameters = true) + public static byte[] toIntegerSumTupleSketch(@Nullable Object key, @Nullable Integer value) { + return toIntegerSumTupleSketch(key, value, CommonConstants.Helix.DEFAULT_TUPLE_SKETCH_LGK); + } + + /** + * Create a Tuple Sketch containing the key and value supplied + * + * @param key an Object we want to insert as the key of the sketch, may be null to return an empty sketch + * @param value an Integer we want to associate as the value to go along with the key, may be null to return an + * empty sketch + * @param lgK integer representing the log of the maximum number of retained entries in the sketch, between 4 and 26 + * @return serialized tuple sketch as bytes + */ + @ScalarFunction(nullableParameters = true) + public static byte[] toIntegerSumTupleSketch(@Nullable Object key, Integer value, int lgK) { + IntegerSketch is = new IntegerSketch(lgK, IntegerSummary.Mode.Sum); + if (value != null && key != null) { + if (key instanceof Integer) { + is.update((Integer) key, value); + } else if (key instanceof Long) { + is.update((Long) key, value); + } else if (key instanceof Float) { + is.update((float) key, value); + } else if (key instanceof Double) { + is.update((double) key, value); + } else if (key instanceof BigDecimal) { + is.update(((BigDecimal) key).toString(), value); + } else if (key instanceof String) { + is.update((String) key, value); + } else if (key instanceof byte[]) { + is.update((byte[]) key, value); + } else { + throw new IllegalArgumentException("Unrecognised key type for Theta sketch: " + key.getClass().getSimpleName()); + } + } + return ObjectSerDeUtils.DATA_SKETCH_INT_TUPLE_SER_DE.serialize(is.compact()); + } + + @ScalarFunction(names = {"getThetaSketchEstimate", "get_theta_sketch_estimate"}) + public static long getThetaSketchEstimate(Object sketchObject) { + return Math.round(asThetaSketch(sketchObject).getEstimate()); + } + + @ScalarFunction(names = {"thetaSketchUnion", "theta_sketch_union"}) + public static Sketch thetaSketchUnion(Object o1, Object o2) { + return thetaSketchUnionVar(o1, o2); + } + + @ScalarFunction(names = {"thetaSketchUnion", "theta_sketch_union"}) + public static Sketch thetaSketchUnion(Object o1, Object o2, Object o3) { + return thetaSketchUnionVar(o1, o2, o3); + } + + @ScalarFunction(names = {"thetaSketchUnion", "theta_sketch_union"}) + public static Sketch thetaSketchUnion(Object o1, Object o2, Object o3, Object o4) { + return thetaSketchUnionVar(o1, o2, o3, o4); + } + + @ScalarFunction(names = {"thetaSketchUnion", "theta_sketch_union"}) + public static Sketch thetaSketchUnion(Object o1, Object o2, Object o3, Object o4, Object o5) { + return thetaSketchUnionVar(o1, o2, o3, o4, o5); + } + + @ScalarFunction(names = {"thetaSketchIntersect", "theta_sketch_intersect"}) + public static Sketch thetaSketchIntersect(Object o1, Object o2) { + return thetaSketchIntersectVar(o1, o2); + } + + @ScalarFunction(names = {"thetaSketchIntersect", "theta_sketch_intersect"}) + public static Sketch thetaSketchIntersect(Object o1, Object o2, Object o3) { + return thetaSketchIntersectVar(o1, o2, o3); + } + + @ScalarFunction(names = {"thetaSketchIntersect", "theta_sketch_intersect"}) + public static Sketch thetaSketchIntersect(Object o1, Object o2, Object o3, Object o4) { + return thetaSketchIntersectVar(o1, o2, o3, o4); + } + + @ScalarFunction(names = {"thetaSketchIntersect", "theta_sketch_intersect"}) + public static Sketch thetaSketchIntersect(Object o1, Object o2, Object o3, Object o4, Object o5) { + return thetaSketchIntersectVar(o1, o2, o3, o4, o5); + } + + @ScalarFunction(names = {"thetaSketchDiff", "theta_sketch_diff"}) + public static Sketch thetaSketchDiff(Object sketchObjectA, Object sketchObjectB) { + AnotB diff = SET_OPERATION_BUILDER.buildANotB(); + diff.setA(asThetaSketch(sketchObjectA)); + diff.notB(asThetaSketch(sketchObjectB)); + return diff.getResult(false, null, false); + } + + @ScalarFunction(names = {"thetaSketchToString", "theta_sketch_to_string"}) + public static String thetaSketchToString(Object sketchObject) { + return asThetaSketch(sketchObject).toString(); + } + + private static Sketch thetaSketchUnionVar(Object... sketchObjects) { + Union union = SET_OPERATION_BUILDER.buildUnion(); + for (Object sketchObj : sketchObjects) { + union.union(asThetaSketch(sketchObj)); + } + return union.getResult(false, null); + } + + private static Sketch thetaSketchIntersectVar(Object... sketchObjects) { + Intersection intersection = SET_OPERATION_BUILDER.buildIntersection(); + for (Object sketchObj : sketchObjects) { + intersection.intersect(asThetaSketch(sketchObj)); + } + return intersection.getResult(false, null); + } + + private static Sketch asThetaSketch(Object sketchObj) { + if (sketchObj instanceof String) { + byte[] decoded = Base64.getDecoder().decode((String) sketchObj); + return Sketches.wrapSketch(Memory.wrap((decoded))); + } else if (sketchObj instanceof Sketch) { + return (Sketch) sketchObj; + } else if (sketchObj instanceof byte[]) { + return Sketches.wrapSketch(Memory.wrap((byte[]) sketchObj)); + } else { + throw new RuntimeException( + "Exception occurred getting estimate from Theta Sketch, unsupported Object type: " + sketchObj.getClass()); + } + } + + @ScalarFunction(names = {"intSumTupleSketchUnion", "int_sum_tuple_sketch_union"}) + public static byte[] intSumTupleSketchUnion(Object o1, Object o2) { + return intSumTupleSketchUnion((int) Math.pow(2, CommonConstants.Helix.DEFAULT_TUPLE_SKETCH_LGK), o1, o2); + } + + @ScalarFunction(names = {"intSumTupleSketchUnion", "int_sum_tuple_sketch_union"}) + public static byte[] intSumTupleSketchUnion(int nomEntries, Object o1, Object o2) { + return intTupleSketchUnionVar(IntegerSummary.Mode.Sum, nomEntries, o1, o2); + } + + @ScalarFunction(names = {"intMinTupleSketchUnion", "int_min_tuple_sketch_union"}) + public static byte[] intMinTupleSketchUnion(Object o1, Object o2) { + return intMinTupleSketchUnion((int) Math.pow(2, CommonConstants.Helix.DEFAULT_TUPLE_SKETCH_LGK), o1, o2); + } + + @ScalarFunction(names = {"intMinTupleSketchUnion", "int_min_tuple_sketch_union"}) + public static byte[] intMinTupleSketchUnion(int nomEntries, Object o1, Object o2) { + return intTupleSketchUnionVar(IntegerSummary.Mode.Min, nomEntries, o1, o2); + } + + @ScalarFunction(names = {"intMaxTupleSketchUnion", "int_max_tuple_sketch_union"}) + public static byte[] intMaxTupleSketchUnion(Object o1, Object o2) { + return intMaxTupleSketchUnion((int) Math.pow(2, CommonConstants.Helix.DEFAULT_TUPLE_SKETCH_LGK), o1, o2); + } + + @ScalarFunction(names = {"intMaxTupleSketchUnion", "int_max_tuple_sketch_union"}) + public static byte[] intMaxTupleSketchUnion(int nomEntries, Object o1, Object o2) { + return intTupleSketchUnionVar(IntegerSummary.Mode.Max, nomEntries, o1, o2); + } + + private static byte[] intTupleSketchUnionVar(IntegerSummary.Mode mode, int nomEntries, Object... sketchObjects) { + org.apache.datasketches.tuple.Union union = + new org.apache.datasketches.tuple.Union<>(nomEntries, new IntegerSummarySetOperations(mode, mode)); + for (Object sketchObj : sketchObjects) { + union.union(asIntegerSketch(sketchObj)); + } + return ObjectSerDeUtils.DATA_SKETCH_INT_TUPLE_SER_DE.serialize(union.getResult().compact()); + } + + @ScalarFunction(names = {"intSumTupleSketchIntersect", "int_sum_tuple_sketch_intersect"}) + public static byte[] intSumTupleSketchIntersect(Object o1, Object o2) { + return intTupleSketchIntersectVar(IntegerSummary.Mode.Sum, o1, o2); + } + + @ScalarFunction(names = {"intMinTupleSketchIntersect", "int_min_tuple_sketch_intersect"}) + public static byte[] intMinTupleSketchIntersect(Object o1, Object o2) { + return intTupleSketchIntersectVar(IntegerSummary.Mode.Min, o1, o2); + } + + @ScalarFunction(names = {"intMaxTupleSketchIntersect", "int_max_tuple_sketch_intersect"}) + public static byte[] intMaxTupleSketchIntersect(Object o1, Object o2) { + return intTupleSketchIntersectVar(IntegerSummary.Mode.Max, o1, o2); + } + + private static byte[] intTupleSketchIntersectVar(IntegerSummary.Mode mode, Object... sketchObjects) { + org.apache.datasketches.tuple.Intersection intersection = + new org.apache.datasketches.tuple.Intersection<>(new IntegerSummarySetOperations(mode, mode)); + for (Object sketchObj : sketchObjects) { + intersection.intersect(asIntegerSketch(sketchObj)); + } + return ObjectSerDeUtils.DATA_SKETCH_INT_TUPLE_SER_DE.serialize(intersection.getResult().compact()); + } + + @ScalarFunction(names = {"intTupleSketchDiff", "int_tuple_sketch_diff"}) + public static byte[] intSumTupleSketchDiff(Object o1, Object o2) { + org.apache.datasketches.tuple.AnotB diff = new org.apache.datasketches.tuple.AnotB<>(); + diff.setA(asIntegerSketch(o1)); + diff.notB(asIntegerSketch(o2)); + return ObjectSerDeUtils.DATA_SKETCH_INT_TUPLE_SER_DE.serialize(diff.getResult(false).compact()); + } + + private static org.apache.datasketches.tuple.Sketch asIntegerSketch(Object sketchObj) { + if (sketchObj instanceof String) { + byte[] decoded = Base64.getDecoder().decode((String) sketchObj); + return ObjectSerDeUtils.DATA_SKETCH_INT_TUPLE_SER_DE.deserialize(decoded); + } else if (sketchObj instanceof org.apache.datasketches.tuple.Sketch) { + return (org.apache.datasketches.tuple.Sketch) sketchObj; + } else if (sketchObj instanceof byte[]) { + return ObjectSerDeUtils.DATA_SKETCH_INT_TUPLE_SER_DE.deserialize((byte[]) sketchObj); + } else { + throw new RuntimeException( + "Exception occurred getting reading Tuple Sketch, unsupported Object type: " + sketchObj.getClass()); + } + } + + @ScalarFunction(names = {"getIntTupleSketchEstimate", "get_int_tuple_sketch_estimate"}) + public static long getIntTupleSketchEstimate(Object o1) { + return Math.round(asIntegerSketch(o1).getEstimate()); + } + + /** + * Create a CPC Sketch containing the input + * + * @param input an Object we want to insert into the sketch, may be null to return an empty sketch + * @return serialized CPC sketch as bytes + */ + @ScalarFunction(nullableParameters = true) + public static byte[] toCpcSketch(@Nullable Object input) { + return toCpcSketch(input, CommonConstants.Helix.DEFAULT_CPC_SKETCH_LGK); + } + + @ScalarFunction(names = {"getCpcSketchEstimate", "get_cpc_sketch_estimate"}) + public static long getCpcSketchEstimate(Object o1) { + return Math.round(asCpcSketch(o1).getEstimate()); + } + + @ScalarFunction(names = {"cpcSketchUnion", "cpc_sketch_union"}) + public static byte[] cpcSketchUnion(Object o1, Object o2) { + return cpcSketchUnionVar(o1, o2); + } + + @ScalarFunction(names = {"cpcSketchUnion", "cpc_sketch_union"}) + public static byte[] cpcSketchUnion(Object o1, Object o2, Object o3) { + return cpcSketchUnionVar(o1, o2, o3); + } + + @ScalarFunction(names = {"cpcSketchUnion", "cpc_sketch_union"}) + public static byte[] cpcSketchUnion(Object o1, Object o2, Object o3, Object o4) { + return cpcSketchUnionVar(o1, o2, o3, o4); + } + + @ScalarFunction(names = {"cpcSketchUnion", "cpc_sketch_union"}) + public static byte[] cpcSketchUnion(Object o1, Object o2, Object o3, Object o4, Object o5) { + return cpcSketchUnionVar(o1, o2, o3, o4, o5); + } + + @ScalarFunction(names = {"cpcSketchToString", "cpc_sketch_to_string"}) + public static String cpcSketchToString(Object sketchObject) { + return asCpcSketch(sketchObject).toString(); + } + + /** + * Create a CPC Sketch containing the input, with a configured nominal entries + * + * @param input an Object we want to insert into the sketch, may be null to return an empty sketch + * @param lgK the given log_base2 of k, which is the nominal entries that the sketch is configured to keep + * @return serialized CPC sketch as bytes + */ + @ScalarFunction(nullableParameters = true) + public static byte[] toCpcSketch(@Nullable Object input, int lgK) { + CpcSketch sketch = new CpcSketch(lgK); + if (input != null) { + if (input instanceof Integer) { + sketch.update((Integer) input); + } else if (input instanceof Long) { + sketch.update((Long) input); + } else if (input instanceof Float) { + sketch.update((Float) input); + } else if (input instanceof Double) { + sketch.update((Double) input); + } else if (input instanceof BigDecimal) { + sketch.update(((BigDecimal) input).toString()); + } else if (input instanceof String) { + sketch.update((String) input); + } else if (input instanceof byte[]) { + sketch.update((byte[]) input); + } else { + throw new IllegalArgumentException( + "Unrecognised input type for CPC sketch: " + input.getClass().getSimpleName()); + } + } + return ObjectSerDeUtils.DATA_SKETCH_CPC_SER_DE.serialize(sketch); + } + + private static CpcSketch asCpcSketch(Object sketchObj) { + if (sketchObj instanceof CpcSketch) { + return (CpcSketch) sketchObj; + } else if (sketchObj instanceof byte[]) { + return CpcSketch.heapify(Memory.wrap((byte[]) sketchObj)); + } else if (sketchObj instanceof String) { + byte[] decoded = Base64.getDecoder().decode((String) sketchObj); + return CpcSketch.heapify(Memory.wrap((decoded))); + } else { + throw new RuntimeException( + "Exception occurred getting estimate from CPC Sketch, unsupported Object type: " + sketchObj.getClass()); + } + } + + private static byte[] cpcSketchUnionVar(Object... sketchObjects) { + CpcUnion union = new CpcUnion(CommonConstants.Helix.DEFAULT_CPC_SKETCH_LGK); + for (Object sketchObj : sketchObjects) { + union.update(asCpcSketch(sketchObj)); + } + return union.getResult().toByteArray(); + } + + /** + * Create an UltraLogLog containing the input + * + * @param input an Object we want to insert into the ULL, may be null to return an empty ULL + * @return serialized ULL as bytes + */ + @ScalarFunction(nullableParameters = true) + public static byte[] toULL(@Nullable Object input) { + return toULL(input, CommonConstants.Helix.DEFAULT_ULTRALOGLOG_P); + } + + /** + * Create an UltraLogLog containing the input, with a configurable p + * + * @param input an Object we want to insert into the ULL, may be null to return an empty HLL + * @param p the p value for the created UltraLogLog + * @return serialized HLL as bytes + */ + @ScalarFunction(nullableParameters = true) + public static byte[] toULL(@Nullable Object input, int p) { + UltraLogLog sketch = UltraLogLog.create(p); + UltraLogLogUtils.hashObject(input).ifPresent(sketch::add); + return ObjectSerDeUtils.ULTRA_LOG_LOG_OBJECT_SER_DE.serialize(sketch); + } + + /** + * Takes a default UltraLogLog byte array and loads it into the format used in Pinot + * + * This adds the P value into the serialized byte stream, so it can be used easily + */ + @ScalarFunction + public static byte[] fromULL(byte[] input) { + UltraLogLog ull = UltraLogLog.wrap(input); + return ObjectSerDeUtils.ULTRA_LOG_LOG_OBJECT_SER_DE.serialize(ull); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/BaseBinaryGeoTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/BaseBinaryGeoTransformFunction.java index c528cedb51d9..9990d25286c0 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/BaseBinaryGeoTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/BaseBinaryGeoTransformFunction.java @@ -22,15 +22,14 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.function.BaseTransformFunction; import org.apache.pinot.core.operator.transform.function.LiteralTransformFunction; import org.apache.pinot.core.operator.transform.function.TransformFunction; import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.segment.local.utils.GeometrySerializer; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec; -import org.apache.pinot.spi.utils.BytesUtils; import org.locationtech.jts.geom.Geometry; @@ -46,20 +45,18 @@ public abstract class BaseBinaryGeoTransformFunction extends BaseTransformFuncti private double[] _doubleResults; @Override - public void init(List arguments, Map dataSourceMap) { - Preconditions - .checkArgument(arguments.size() == 2, "2 arguments are required for transform function: %s", getName()); + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); + Preconditions.checkArgument(arguments.size() == 2, "2 arguments are required for transform function: %s", + getName()); TransformFunction transformFunction = arguments.get(0); Preconditions.checkArgument(transformFunction.getResultMetadata().isSingleValue(), "First argument must be single-valued for transform function: %s", getName()); Preconditions.checkArgument(transformFunction.getResultMetadata().getDataType() == FieldSpec.DataType.BYTES || transformFunction instanceof LiteralTransformFunction, - "The first argument must be of type BYTES , but was %s", - transformFunction.getResultMetadata().getDataType() - ); + "The first argument must be of type BYTES , but was %s", transformFunction.getResultMetadata().getDataType()); if (transformFunction instanceof LiteralTransformFunction) { - _firstLiteral = GeometrySerializer.deserialize( - BytesUtils.toBytes(((LiteralTransformFunction) transformFunction).getLiteral())); + _firstLiteral = GeometrySerializer.deserialize(((LiteralTransformFunction) transformFunction).getBytesLiteral()); } else { _firstArgument = transformFunction; } @@ -68,40 +65,37 @@ public void init(List arguments, Map data "Second argument must be single-valued for transform function: %s", getName()); Preconditions.checkArgument(transformFunction.getResultMetadata().getDataType() == FieldSpec.DataType.BYTES || transformFunction instanceof LiteralTransformFunction, - "The second argument must be of type BYTES , but was %s", - transformFunction.getResultMetadata().getDataType() - ); + "The second argument must be of type BYTES , but was %s", transformFunction.getResultMetadata().getDataType()); if (transformFunction instanceof LiteralTransformFunction) { - _secondLiteral = GeometrySerializer.deserialize( - BytesUtils.toBytes(((LiteralTransformFunction) transformFunction).getLiteral())); + _secondLiteral = GeometrySerializer.deserialize(((LiteralTransformFunction) transformFunction).getBytesLiteral()); } else { _secondArgument = transformFunction; } } - protected int[] transformGeometryToIntValuesSV(ProjectionBlock projectionBlock) { + protected int[] transformGeometryToIntValuesSV(ValueBlock valueBlock) { if (_intResults == null) { _intResults = new int[DocIdSetPlanNode.MAX_DOC_PER_CALL]; } byte[][] firstValues; byte[][] secondValues; if (_firstArgument == null && _secondArgument == null) { - _intResults = new int[Math.min(projectionBlock.getNumDocs(), DocIdSetPlanNode.MAX_DOC_PER_CALL)]; + _intResults = new int[Math.min(valueBlock.getNumDocs(), DocIdSetPlanNode.MAX_DOC_PER_CALL)]; Arrays.fill(_intResults, transformGeometryToInt(_firstLiteral, _secondLiteral)); } else if (_firstArgument == null) { - secondValues = _secondArgument.transformToBytesValuesSV(projectionBlock); - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + secondValues = _secondArgument.transformToBytesValuesSV(valueBlock); + for (int i = 0; i < valueBlock.getNumDocs(); i++) { _intResults[i] = transformGeometryToInt(_firstLiteral, GeometrySerializer.deserialize(secondValues[i])); } } else if (_secondArgument == null) { - firstValues = _firstArgument.transformToBytesValuesSV(projectionBlock); - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + firstValues = _firstArgument.transformToBytesValuesSV(valueBlock); + for (int i = 0; i < valueBlock.getNumDocs(); i++) { _intResults[i] = transformGeometryToInt(GeometrySerializer.deserialize(firstValues[i]), _secondLiteral); } } else { - firstValues = _firstArgument.transformToBytesValuesSV(projectionBlock); - secondValues = _secondArgument.transformToBytesValuesSV(projectionBlock); - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + firstValues = _firstArgument.transformToBytesValuesSV(valueBlock); + secondValues = _secondArgument.transformToBytesValuesSV(valueBlock); + for (int i = 0; i < valueBlock.getNumDocs(); i++) { _intResults[i] = transformGeometryToInt(GeometrySerializer.deserialize(firstValues[i]), GeometrySerializer.deserialize(secondValues[i])); } @@ -109,29 +103,29 @@ protected int[] transformGeometryToIntValuesSV(ProjectionBlock projectionBlock) return _intResults; } - protected double[] transformGeometryToDoubleValuesSV(ProjectionBlock projectionBlock) { + protected double[] transformGeometryToDoubleValuesSV(ValueBlock valueBlock) { if (_doubleResults == null) { _doubleResults = new double[DocIdSetPlanNode.MAX_DOC_PER_CALL]; } byte[][] firstValues; byte[][] secondValues; if (_firstArgument == null && _secondArgument == null) { - _doubleResults = new double[Math.min(projectionBlock.getNumDocs(), DocIdSetPlanNode.MAX_DOC_PER_CALL)]; + _doubleResults = new double[Math.min(valueBlock.getNumDocs(), DocIdSetPlanNode.MAX_DOC_PER_CALL)]; Arrays.fill(_doubleResults, transformGeometryToDouble(_firstLiteral, _secondLiteral)); } else if (_firstArgument == null) { - secondValues = _secondArgument.transformToBytesValuesSV(projectionBlock); - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + secondValues = _secondArgument.transformToBytesValuesSV(valueBlock); + for (int i = 0; i < valueBlock.getNumDocs(); i++) { _doubleResults[i] = transformGeometryToDouble(_firstLiteral, GeometrySerializer.deserialize(secondValues[i])); } } else if (_secondArgument == null) { - firstValues = _firstArgument.transformToBytesValuesSV(projectionBlock); - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + firstValues = _firstArgument.transformToBytesValuesSV(valueBlock); + for (int i = 0; i < valueBlock.getNumDocs(); i++) { _doubleResults[i] = transformGeometryToDouble(GeometrySerializer.deserialize(firstValues[i]), _secondLiteral); } } else { - firstValues = _firstArgument.transformToBytesValuesSV(projectionBlock); - secondValues = _secondArgument.transformToBytesValuesSV(projectionBlock); - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + firstValues = _firstArgument.transformToBytesValuesSV(valueBlock); + secondValues = _secondArgument.transformToBytesValuesSV(valueBlock); + for (int i = 0; i < valueBlock.getNumDocs(); i++) { _doubleResults[i] = transformGeometryToDouble(GeometrySerializer.deserialize(firstValues[i]), GeometrySerializer.deserialize(secondValues[i])); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ConstructFromTextFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ConstructFromTextFunction.java index d0ff874aee3c..ae69b5ca844a 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ConstructFromTextFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ConstructFromTextFunction.java @@ -22,13 +22,13 @@ import java.util.List; import java.util.Map; import org.apache.pinot.common.Utils; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.operator.transform.function.BaseTransformFunction; import org.apache.pinot.core.operator.transform.function.TransformFunction; import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.segment.local.utils.GeometrySerializer; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.io.ParseException; @@ -44,7 +44,8 @@ abstract class ConstructFromTextFunction extends BaseTransformFunction { protected WKTReader _reader; @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); Preconditions.checkArgument(arguments.size() == 1, "Exactly 1 argument is required for transform function: %s", getName()); TransformFunction transformFunction = arguments.get(0); @@ -64,12 +65,12 @@ public TransformResultMetadata getResultMetadata() { } @Override - public byte[][] transformToBytesValuesSV(ProjectionBlock projectionBlock) { + public byte[][] transformToBytesValuesSV(ValueBlock valueBlock) { if (_results == null) { _results = new byte[DocIdSetPlanNode.MAX_DOC_PER_CALL][]; } - String[] argumentValues = _transformFunction.transformToStringValuesSV(projectionBlock); - int length = projectionBlock.getNumDocs(); + String[] argumentValues = _transformFunction.transformToStringValuesSV(valueBlock); + int length = valueBlock.getNumDocs(); for (int i = 0; i < length; i++) { try { Geometry geometry = _reader.read(argumentValues[i]); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ConstructFromWKBFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ConstructFromWKBFunction.java index 761fe06a9272..007d912e040f 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ConstructFromWKBFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ConstructFromWKBFunction.java @@ -21,13 +21,13 @@ import com.google.common.base.Preconditions; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.operator.transform.function.BaseTransformFunction; import org.apache.pinot.core.operator.transform.function.TransformFunction; import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.segment.local.utils.GeometrySerializer; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec; import org.apache.pinot.spi.utils.BytesUtils; import org.locationtech.jts.geom.Geometry; @@ -44,7 +44,8 @@ abstract class ConstructFromWKBFunction extends BaseTransformFunction { private WKBReader _reader; @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); Preconditions.checkArgument(arguments.size() == 1, "Exactly 1 argument is required for transform function: %s", getName()); TransformFunction transformFunction = arguments.get(0); @@ -64,12 +65,12 @@ public TransformResultMetadata getResultMetadata() { } @Override - public byte[][] transformToBytesValuesSV(ProjectionBlock projectionBlock) { + public byte[][] transformToBytesValuesSV(ValueBlock valueBlock) { if (_results == null) { _results = new byte[DocIdSetPlanNode.MAX_DOC_PER_CALL][]; } - byte[][] argumentValues = _transformFunction.transformToBytesValuesSV(projectionBlock); - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + byte[][] argumentValues = _transformFunction.transformToBytesValuesSV(valueBlock); + for (int i = 0; i < valueBlock.getNumDocs(); i++) { try { Geometry geometry = _reader.read(argumentValues[i]); _results[i] = GeometrySerializer.serialize(geometry); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/GeoToH3Function.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/GeoToH3Function.java index 75f46c7b2cce..237a5619e5de 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/GeoToH3Function.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/GeoToH3Function.java @@ -21,14 +21,14 @@ import com.google.common.base.Preconditions; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.operator.transform.function.BaseTransformFunction; import org.apache.pinot.core.operator.transform.function.LiteralTransformFunction; import org.apache.pinot.core.operator.transform.function.TransformFunction; import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.segment.local.utils.GeometrySerializer; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec; import org.locationtech.jts.geom.Geometry; @@ -51,7 +51,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); Preconditions.checkArgument(arguments.size() == 3 || arguments.size() == 2, "Transform function %s requires 2 or 3 arguments", getName()); if (arguments.size() == 3) { @@ -77,8 +78,9 @@ public void init(List arguments, Map data TransformFunction transformFunction = arguments.get(0); Preconditions.checkArgument(transformFunction.getResultMetadata().isSingleValue(), "First argument must be single-valued for transform function: %s", getName()); - Preconditions.checkArgument(transformFunction.getResultMetadata().getDataType() == FieldSpec.DataType.BYTES, - "The first argument must be bytes"); + Preconditions.checkArgument(transformFunction.getResultMetadata().getDataType() == FieldSpec.DataType.BYTES + || transformFunction.getResultMetadata().getDataType() == FieldSpec.DataType.STRING, + "The first argument must be bytes/string"); _firstArgument = transformFunction; transformFunction = arguments.get(1); Preconditions.checkArgument(transformFunction.getResultMetadata().isSingleValue(), @@ -95,23 +97,23 @@ public TransformResultMetadata getResultMetadata() { } @Override - public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { + public long[] transformToLongValuesSV(ValueBlock valueBlock) { if (_results == null) { _results = new long[DocIdSetPlanNode.MAX_DOC_PER_CALL]; } if (_thirdArgument == null) { - byte[][] geoValues = _firstArgument.transformToBytesValuesSV(projectionBlock); - int[] resValues = _secondArgument.transformToIntValuesSV(projectionBlock); - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + byte[][] geoValues = _firstArgument.transformToBytesValuesSV(valueBlock); + int[] resValues = _secondArgument.transformToIntValuesSV(valueBlock); + for (int i = 0; i < valueBlock.getNumDocs(); i++) { Geometry geometry = GeometrySerializer.deserialize(geoValues[i]); _results[i] = ScalarFunctions.geoToH3(geometry.getCoordinate().x, geometry.getCoordinate().y, resValues[i]); } } else { - double[] lonValues = _firstArgument.transformToDoubleValuesSV(projectionBlock); - double[] latValues = _secondArgument.transformToDoubleValuesSV(projectionBlock); - int[] resValues = _thirdArgument.transformToIntValuesSV(projectionBlock); - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + double[] lonValues = _firstArgument.transformToDoubleValuesSV(valueBlock); + double[] latValues = _secondArgument.transformToDoubleValuesSV(valueBlock); + int[] resValues = _thirdArgument.transformToIntValuesSV(valueBlock); + for (int i = 0; i < valueBlock.getNumDocs(); i++) { _results[i] = ScalarFunctions.geoToH3(lonValues[i], latValues[i], resValues[i]); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ScalarFunctions.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ScalarFunctions.java index 4ea84e581511..2d259c03a7bc 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ScalarFunctions.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/ScalarFunctions.java @@ -22,6 +22,7 @@ import org.apache.pinot.segment.local.utils.GeometryUtils; import org.apache.pinot.segment.local.utils.H3Utils; import org.apache.pinot.spi.annotations.ScalarFunction; +import org.apache.pinot.spi.utils.BooleanUtils; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.Point; @@ -42,7 +43,7 @@ private ScalarFunctions() { * @param y y * @return the created point */ - @ScalarFunction + @ScalarFunction(names = {"stPoint", "ST_point"}) public static byte[] stPoint(double x, double y) { return GeometrySerializer.serialize(GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(x, y))); } @@ -55,10 +56,10 @@ public static byte[] stPoint(double x, double y) { * @param isGeography if it's geography * @return the created point */ - @ScalarFunction - public static byte[] stPoint(double x, double y, boolean isGeography) { + @ScalarFunction(names = {"stPoint", "ST_point"}) + public static byte[] stPoint(double x, double y, Object isGeography) { Point point = GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(x, y)); - if (isGeography) { + if (BooleanUtils.toBoolean(isGeography)) { GeometryUtils.setGeography(point); } return GeometrySerializer.serialize(point); @@ -67,7 +68,7 @@ public static byte[] stPoint(double x, double y, boolean isGeography) { /** * Reads a geometry object from the WKT format. */ - @ScalarFunction + @ScalarFunction(names = {"stGeomFromText", "ST_geom_from_text"}) public static byte[] stGeomFromText(String wkt) throws ParseException { return GeometrySerializer.serialize(GeometryUtils.GEOMETRY_WKT_READER.read(wkt)); @@ -76,7 +77,7 @@ public static byte[] stGeomFromText(String wkt) /** * Reads a geography object from the WKT format. */ - @ScalarFunction + @ScalarFunction(names = {"stGeogFromText", "ST_geog_from_text"}) public static byte[] stGeogFromText(String wkt) throws ParseException { return GeometrySerializer.serialize(GeometryUtils.GEOGRAPHY_WKT_READER.read(wkt)); @@ -85,7 +86,7 @@ public static byte[] stGeogFromText(String wkt) /** * Reads a geometry object from the WKB format. */ - @ScalarFunction + @ScalarFunction(names = {"stGeomFromWKB", "ST_geom_from_wkb"}) public static byte[] stGeomFromWKB(byte[] wkb) throws ParseException { return GeometrySerializer.serialize(GeometryUtils.GEOMETRY_WKB_READER.read(wkb)); @@ -94,7 +95,7 @@ public static byte[] stGeomFromWKB(byte[] wkb) /** * Reads a geography object from the WKB format. */ - @ScalarFunction + @ScalarFunction(names = {"stGeogFromWKB", "ST_geog_from_wkb"}) public static byte[] stGeogFromWKB(byte[] wkb) throws ParseException { return GeometrySerializer.serialize(GeometryUtils.GEOGRAPHY_WKB_READER.read(wkb)); @@ -106,7 +107,7 @@ public static byte[] stGeogFromWKB(byte[] wkb) * @param bytes the serialized geometry object * @return the geometry in WKT */ - @ScalarFunction + @ScalarFunction(names = {"stAsText", "ST_as_text"}) public static String stAsText(byte[] bytes) { return GeometryUtils.WKT_WRITER.write(GeometrySerializer.deserialize(bytes)); } @@ -117,7 +118,7 @@ public static String stAsText(byte[] bytes) { * @param bytes the serialized geometry object * @return the geometry in WKB */ - @ScalarFunction + @ScalarFunction(names = {"stAsBinary", "ST_as_binary"}) public static byte[] stAsBinary(byte[] bytes) { return GeometryUtils.WKB_WRITER.write(GeometrySerializer.deserialize(bytes)); } @@ -128,7 +129,7 @@ public static byte[] stAsBinary(byte[] bytes) { * @param bytes the serialized geometry object * @return the geographical object */ - @ScalarFunction + @ScalarFunction(names = {"toSphericalGeography", "to_spherical_geography"}) public static byte[] toSphericalGeography(byte[] bytes) { Geometry geometry = GeometrySerializer.deserialize(bytes); GeometryUtils.setGeography(geometry); @@ -141,7 +142,7 @@ public static byte[] toSphericalGeography(byte[] bytes) { * @param bytes the serialized geographical object * @return the geometry object */ - @ScalarFunction + @ScalarFunction(names = {"toGeometry", "to_geometry"}) public static byte[] toGeometry(byte[] bytes) { Geometry geometry = GeometrySerializer.deserialize(bytes); GeometryUtils.setGeometry(geometry); @@ -155,8 +156,72 @@ public static byte[] toGeometry(byte[] bytes) { * @param resolution H3 index resolution * @return the H3 index address */ - @ScalarFunction + @ScalarFunction(names = {"geoToH3", "geo_to_h3"}) public static long geoToH3(double longitude, double latitude, int resolution) { return H3Utils.H3_CORE.geoToH3(latitude, longitude, resolution); } + + /** + * Gets the H3 hexagon address from the location + * @param geoBytes ST_point serialized bytes + * @param resolution H3 index resolution + * @return the H3 index address + */ + @ScalarFunction(names = {"geoToH3", "geo_to_h3"}) + public static long geoToH3(byte[] geoBytes, int resolution) { + Geometry geometry = GeometrySerializer.deserialize(geoBytes); + double latitude = geometry.getCoordinate().y; + double longitude = geometry.getCoordinate().x; + return H3Utils.H3_CORE.geoToH3(latitude, longitude, resolution); + } + + @ScalarFunction(names = {"stDistance", "ST_distance"}) + public static double stDistance(byte[] firstPoint, byte[] secondPoint) { + Geometry firstGeometry = GeometrySerializer.deserialize(firstPoint); + Geometry secondGeometry = GeometrySerializer.deserialize(secondPoint); + if (GeometryUtils.isGeography(firstGeometry) != GeometryUtils.isGeography(secondGeometry)) { + throw new RuntimeException("The first and second arguments shall either all be geometry or all geography"); + } + if (GeometryUtils.isGeography(firstGeometry)) { + return StDistanceFunction.sphericalDistance(firstGeometry, secondGeometry); + } else { + return firstGeometry.isEmpty() || secondGeometry.isEmpty() ? Double.NaN : firstGeometry.distance(secondGeometry); + } + } + + @ScalarFunction(names = {"stContains", "st_contains"}) + public static int stContains(byte[] first, byte[] second) { + Geometry firstGeometry = GeometrySerializer.deserialize(first); + Geometry secondGeometry = GeometrySerializer.deserialize(second); + if (GeometryUtils.isGeography(firstGeometry) != GeometryUtils.isGeography(secondGeometry)) { + throw new RuntimeException("The first and second arguments should either both be geometry or both be geography"); + } + // TODO: to fully support Geography contains operation. + return firstGeometry.contains(secondGeometry) ? 1 : 0; + } + + @ScalarFunction(names = {"stEquals", "st_equals"}) + public static int stEquals(byte[] first, byte[] second) { + Geometry firstGeometry = GeometrySerializer.deserialize(first); + Geometry secondGeometry = GeometrySerializer.deserialize(second); + + return firstGeometry.equals(secondGeometry) ? 1 : 0; + } + + @ScalarFunction(names = {"stGeometryType", "st_geometry_type"}) + public static String stGeometryType(byte[] bytes) { + Geometry geometry = GeometrySerializer.deserialize(bytes); + return geometry.getGeometryType(); + } + + @ScalarFunction(names = {"stWithin", "st_within"}) + public static int stWithin(byte[] first, byte[] second) { + Geometry firstGeometry = GeometrySerializer.deserialize(first); + Geometry secondGeometry = GeometrySerializer.deserialize(second); + if (GeometryUtils.isGeography(firstGeometry) != GeometryUtils.isGeography(secondGeometry)) { + throw new RuntimeException("The first and second arguments should either both be geometry or both be geography"); + } + // TODO: to fully support Geography within operation. + return firstGeometry.within(secondGeometry) ? 1 : 0; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAreaFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAreaFunction.java index 5ac874335c80..ef51a30dd57a 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAreaFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAreaFunction.java @@ -21,7 +21,8 @@ import com.google.common.base.Preconditions; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.operator.transform.function.BaseTransformFunction; import org.apache.pinot.core.operator.transform.function.LiteralTransformFunction; @@ -29,7 +30,6 @@ import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.segment.local.utils.GeometrySerializer; import org.apache.pinot.segment.local.utils.GeometryUtils; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec; import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.LineString; @@ -57,7 +57,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); Preconditions .checkArgument(arguments.size() == 1, "Exactly 1 argument is required for transform function: %s", getName()); TransformFunction transformFunction = arguments.get(0); @@ -76,13 +77,13 @@ public TransformResultMetadata getResultMetadata() { } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { if (_results == null) { _results = new double[DocIdSetPlanNode.MAX_DOC_PER_CALL]; } - byte[][] values = _transformFunction.transformToBytesValuesSV(projectionBlock); - int numDocs = projectionBlock.getNumDocs(); + byte[][] values = _transformFunction.transformToBytesValuesSV(valueBlock); + int numDocs = valueBlock.getNumDocs(); for (int i = 0; i < numDocs; i++) { Geometry geometry = GeometrySerializer.deserialize(values[i]); _results[i] = GeometryUtils.isGeography(geometry) ? calculateGeographyArea(geometry) : geometry.getArea(); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAsBinaryFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAsBinaryFunction.java index 66e399925d83..5b7d8da836f3 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAsBinaryFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAsBinaryFunction.java @@ -21,14 +21,14 @@ import com.google.common.base.Preconditions; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.operator.transform.function.BaseTransformFunction; import org.apache.pinot.core.operator.transform.function.TransformFunction; import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.segment.local.utils.GeometrySerializer; import org.apache.pinot.segment.local.utils.GeometryUtils; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec; import org.locationtech.jts.geom.Geometry; @@ -48,7 +48,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); Preconditions.checkArgument(arguments.size() == 1, "Exactly 1 argument is required for transform function: %s", getName()); TransformFunction transformFunction = arguments.get(0); @@ -65,13 +66,13 @@ public TransformResultMetadata getResultMetadata() { } @Override - public byte[][] transformToBytesValuesSV(ProjectionBlock projectionBlock) { + public byte[][] transformToBytesValuesSV(ValueBlock valueBlock) { if (_results == null) { _results = new byte[DocIdSetPlanNode.MAX_DOC_PER_CALL][]; } - byte[][] values = _transformFunction.transformToBytesValuesSV(projectionBlock); + byte[][] values = _transformFunction.transformToBytesValuesSV(valueBlock); Geometry geometry; - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + for (int i = 0; i < valueBlock.getNumDocs(); i++) { geometry = GeometrySerializer.deserialize(values[i]); _results[i] = GeometryUtils.WKB_WRITER.write(geometry); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAsTextFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAsTextFunction.java index 4f277fb3232b..8d620deb4f06 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAsTextFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StAsTextFunction.java @@ -21,14 +21,14 @@ import com.google.common.base.Preconditions; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.operator.transform.function.BaseTransformFunction; import org.apache.pinot.core.operator.transform.function.TransformFunction; import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.segment.local.utils.GeometrySerializer; import org.apache.pinot.segment.local.utils.GeometryUtils; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec; import org.locationtech.jts.geom.Geometry; @@ -48,7 +48,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); Preconditions.checkArgument(arguments.size() == 1, "Exactly 1 argument is required for transform function: %s", getName()); TransformFunction transformFunction = arguments.get(0); @@ -65,13 +66,13 @@ public TransformResultMetadata getResultMetadata() { } @Override - public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { + public String[] transformToStringValuesSV(ValueBlock valueBlock) { if (_results == null) { _results = new String[DocIdSetPlanNode.MAX_DOC_PER_CALL]; } - byte[][] values = _transformFunction.transformToBytesValuesSV(projectionBlock); + byte[][] values = _transformFunction.transformToBytesValuesSV(valueBlock); Geometry geometry; - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + for (int i = 0; i < valueBlock.getNumDocs(); i++) { geometry = GeometrySerializer.deserialize(values[i]); _results[i] = GeometryUtils.WKT_WRITER.write(geometry); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StContainsFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StContainsFunction.java index 210215c7e6d8..1045da58bb91 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StContainsFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StContainsFunction.java @@ -18,7 +18,7 @@ */ package org.apache.pinot.core.geospatial.transform.function; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.segment.local.utils.GeometryUtils; import org.locationtech.jts.geom.Geometry; @@ -43,8 +43,8 @@ public TransformResultMetadata getResultMetadata() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - return transformGeometryToIntValuesSV(projectionBlock); + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + return transformGeometryToIntValuesSV(valueBlock); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StDistanceFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StDistanceFunction.java index a28b0ad40a91..b774b932879a 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StDistanceFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StDistanceFunction.java @@ -19,7 +19,7 @@ package org.apache.pinot.core.geospatial.transform.function; import com.google.common.base.Preconditions; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.segment.local.utils.GeometryUtils; import org.locationtech.jts.geom.Geometry; @@ -49,8 +49,8 @@ public TransformResultMetadata getResultMetadata() { } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { - return transformGeometryToDoubleValuesSV(projectionBlock); + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + return transformGeometryToDoubleValuesSV(valueBlock); } @Override @@ -75,7 +75,7 @@ private static void checkLongitude(double longitude) { "Longitude must be between -180 and 180"); } - private static double sphericalDistance(Geometry leftGeometry, Geometry rightGeometry) { + public static double sphericalDistance(Geometry leftGeometry, Geometry rightGeometry) { Preconditions.checkArgument(leftGeometry instanceof Point, "The left argument must be a point"); Preconditions.checkArgument(rightGeometry instanceof Point, "The right argument must be a point"); Point leftPoint = (Point) leftGeometry; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StEqualsFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StEqualsFunction.java index 45b4f269e1a6..73d5e06c2a00 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StEqualsFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StEqualsFunction.java @@ -18,7 +18,7 @@ */ package org.apache.pinot.core.geospatial.transform.function; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.locationtech.jts.geom.Geometry; @@ -40,8 +40,8 @@ public TransformResultMetadata getResultMetadata() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - return transformGeometryToIntValuesSV(projectionBlock); + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + return transformGeometryToIntValuesSV(valueBlock); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StGeometryTypeFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StGeometryTypeFunction.java index 27b1a203a1ae..c5273dcd3af0 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StGeometryTypeFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StGeometryTypeFunction.java @@ -21,13 +21,13 @@ import com.google.common.base.Preconditions; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.operator.transform.function.BaseTransformFunction; import org.apache.pinot.core.operator.transform.function.TransformFunction; import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.segment.local.utils.GeometrySerializer; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec; import org.locationtech.jts.geom.Geometry; @@ -46,7 +46,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); Preconditions .checkArgument(arguments.size() == 1, "Exactly 1 argument is required for transform function: %s", getName()); TransformFunction transformFunction = arguments.get(0); @@ -63,13 +64,13 @@ public TransformResultMetadata getResultMetadata() { } @Override - public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { + public String[] transformToStringValuesSV(ValueBlock valueBlock) { if (_results == null) { _results = new String[DocIdSetPlanNode.MAX_DOC_PER_CALL]; } - byte[][] values = _transformFunction.transformToBytesValuesSV(projectionBlock); + byte[][] values = _transformFunction.transformToBytesValuesSV(valueBlock); Geometry geometry; - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { + for (int i = 0; i < valueBlock.getNumDocs(); i++) { geometry = GeometrySerializer.deserialize(values[i]); _results[i] = geometry.getGeometryType(); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StPointFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StPointFunction.java index a780c4750179..0568c1b8daeb 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StPointFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StPointFunction.java @@ -21,14 +21,17 @@ import com.google.common.base.Preconditions; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.operator.transform.function.BaseTransformFunction; import org.apache.pinot.core.operator.transform.function.LiteralTransformFunction; import org.apache.pinot.core.operator.transform.function.TransformFunction; import org.apache.pinot.core.plan.DocIdSetPlanNode; -import org.apache.pinot.segment.spi.datasource.DataSource; -import org.apache.pinot.spi.utils.BooleanUtils; +import org.apache.pinot.segment.local.utils.GeometrySerializer; +import org.apache.pinot.segment.local.utils.GeometryUtils; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.Point; /** @@ -47,7 +50,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); Preconditions.checkArgument(arguments.size() == 2 || arguments.size() == 3, "2 or 3 arguments are required for transform function: %s", getName()); TransformFunction transformFunction = arguments.get(0); @@ -61,8 +65,8 @@ public void init(List arguments, Map data if (arguments.size() == 3) { transformFunction = arguments.get(2); Preconditions.checkArgument(transformFunction instanceof LiteralTransformFunction, - "Third argument must be a literal of integer: %s", getName()); - _isGeography = BooleanUtils.toBoolean(((LiteralTransformFunction) transformFunction).getLiteral()); + "Third argument must be a literal of boolean: %s", getName()); + _isGeography = ((LiteralTransformFunction) transformFunction).getBooleanLiteral(); } } @@ -72,14 +76,18 @@ public TransformResultMetadata getResultMetadata() { } @Override - public byte[][] transformToBytesValuesSV(ProjectionBlock projectionBlock) { + public byte[][] transformToBytesValuesSV(ValueBlock valueBlock) { if (_results == null) { _results = new byte[DocIdSetPlanNode.MAX_DOC_PER_CALL][]; } - double[] firstValues = _firstArgument.transformToDoubleValuesSV(projectionBlock); - double[] secondValues = _secondArgument.transformToDoubleValuesSV(projectionBlock); - for (int i = 0; i < projectionBlock.getNumDocs(); i++) { - _results[i] = ScalarFunctions.stPoint(firstValues[i], secondValues[i], _isGeography); + double[] firstValues = _firstArgument.transformToDoubleValuesSV(valueBlock); + double[] secondValues = _secondArgument.transformToDoubleValuesSV(valueBlock); + for (int i = 0; i < valueBlock.getNumDocs(); i++) { + Point point = GeometryUtils.GEOMETRY_FACTORY.createPoint(new Coordinate(firstValues[i], secondValues[i])); + if (_isGeography) { + GeometryUtils.setGeography(point); + } + _results[i] = GeometrySerializer.serialize(point); } return _results; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StPolygonFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StPolygonFunction.java index 6f477662d2f1..7680eafa535e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StPolygonFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StPolygonFunction.java @@ -19,7 +19,7 @@ package org.apache.pinot.core.geospatial.transform.function; import com.google.common.base.Preconditions; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.segment.local.utils.GeometrySerializer; import org.apache.pinot.segment.local.utils.GeometryUtils; @@ -46,12 +46,12 @@ public String getName() { } @Override - public byte[][] transformToBytesValuesSV(ProjectionBlock projectionBlock) { + public byte[][] transformToBytesValuesSV(ValueBlock valueBlock) { if (_results == null) { _results = new byte[DocIdSetPlanNode.MAX_DOC_PER_CALL][]; } - String[] argumentValues = _transformFunction.transformToStringValuesSV(projectionBlock); - int length = projectionBlock.getNumDocs(); + String[] argumentValues = _transformFunction.transformToStringValuesSV(valueBlock); + int length = valueBlock.getNumDocs(); for (int i = 0; i < length; i++) { try { Geometry geometry = _reader.read(argumentValues[i]); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StWithinFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StWithinFunction.java index 474af0d22805..60b76ac65ad6 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StWithinFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/geospatial/transform/function/StWithinFunction.java @@ -18,7 +18,7 @@ */ package org.apache.pinot.core.geospatial.transform.function; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.segment.local.utils.GeometryUtils; import org.locationtech.jts.geom.Geometry; @@ -42,8 +42,8 @@ public TransformResultMetadata getResultMetadata() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - return transformGeometryToIntValuesSV(projectionBlock); + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + return transformGeometryToIntValuesSV(valueBlock); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/minion/RawIndexConverter.java b/pinot-core/src/main/java/org/apache/pinot/core/minion/RawIndexConverter.java deleted file mode 100644 index 49a786c340c8..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/minion/RawIndexConverter.java +++ /dev/null @@ -1,263 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.minion; - -import com.google.common.base.Preconditions; -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import javax.annotation.Nullable; -import org.apache.commons.configuration.PropertiesConfiguration; -import org.apache.commons.io.FileUtils; -import org.apache.commons.lang.StringUtils; -import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; -import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; -import org.apache.pinot.segment.local.segment.index.loader.IndexLoadingConfig; -import org.apache.pinot.segment.local.utils.CrcUtils; -import org.apache.pinot.segment.spi.ColumnMetadata; -import org.apache.pinot.segment.spi.ImmutableSegment; -import org.apache.pinot.segment.spi.SegmentMetadata; -import org.apache.pinot.segment.spi.V1Constants; -import org.apache.pinot.segment.spi.compression.ChunkCompressionType; -import org.apache.pinot.segment.spi.creator.ForwardIndexCreatorProvider; -import org.apache.pinot.segment.spi.creator.IndexCreationContext; -import org.apache.pinot.segment.spi.creator.SegmentVersion; -import org.apache.pinot.segment.spi.datasource.DataSource; -import org.apache.pinot.segment.spi.index.creator.ForwardIndexCreator; -import org.apache.pinot.segment.spi.index.reader.Dictionary; -import org.apache.pinot.segment.spi.index.reader.ForwardIndexReader; -import org.apache.pinot.segment.spi.index.reader.ForwardIndexReaderContext; -import org.apache.pinot.spi.data.DimensionFieldSpec; -import org.apache.pinot.spi.data.FieldSpec; -import org.apache.pinot.spi.data.FieldSpec.DataType; -import org.apache.pinot.spi.data.MetricFieldSpec; -import org.apache.pinot.spi.data.Schema; -import org.apache.pinot.spi.utils.ReadMode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - - -/** - * The RawIndexConverter class takes a segment and converts the dictionary-based indexes inside the segment - * into raw indexes. - *

    - *
  • - * If columns to convert are specified, check whether their dictionary-based indexes exist and convert them. - *
  • - *
  • - * If not specified, for each metric column, calculate the size of dictionary-based index and uncompressed raw - * index. If the size of raw index is smaller or equal to (size of dictionary-based index * CONVERSION_THRESHOLD), - * convert it. - *
  • - *
- *

After the conversion, add "rawIndex" into the segment metadata "optimizations" field. - */ -@SuppressWarnings({"rawtypes", "unchecked"}) -public class RawIndexConverter { - private static final Logger LOGGER = LoggerFactory.getLogger(RawIndexConverter.class); - - // Threshold for the ratio of uncompressed raw index size and dictionary-based index size to trigger conversion - private static final int CONVERSION_THRESHOLD = 4; - - // BITS_PER_ELEMENT is not applicable for raw index - private static final int BITS_PER_ELEMENT_FOR_RAW_INDEX = -1; - - private final String _rawTableName; - private final ImmutableSegment _originalImmutableSegment; - private final SegmentMetadata _originalSegmentMetadata; - private final File _convertedIndexDir; - private final PropertiesConfiguration _convertedProperties; - private final String _columnsToConvert; - private final ForwardIndexCreatorProvider _indexCreatorProvider; - - /** - * NOTE: original segment should be in V1 format. - * TODO: support V3 format - */ - public RawIndexConverter(String rawTableName, File originalIndexDir, File convertedIndexDir, - @Nullable String columnsToConvert, ForwardIndexCreatorProvider indexCreatorProvider) - throws Exception { - FileUtils.copyDirectory(originalIndexDir, convertedIndexDir); - IndexLoadingConfig indexLoadingConfig = new IndexLoadingConfig(); - indexLoadingConfig.setSegmentVersion(SegmentVersion.v1); - indexLoadingConfig.setReadMode(ReadMode.mmap); - _rawTableName = rawTableName; - _originalImmutableSegment = ImmutableSegmentLoader.load(originalIndexDir, indexLoadingConfig, null, false); - _originalSegmentMetadata = _originalImmutableSegment.getSegmentMetadata(); - _convertedIndexDir = convertedIndexDir; - _convertedProperties = - new PropertiesConfiguration(new File(_convertedIndexDir, V1Constants.MetadataKeys.METADATA_FILE_NAME)); - _columnsToConvert = columnsToConvert; - _indexCreatorProvider = indexCreatorProvider; - } - - public boolean convert() - throws Exception { - String segmentName = _originalSegmentMetadata.getName(); - LOGGER.info("Start converting segment: {} in table: {}", segmentName, _rawTableName); - - List columnsToConvert = new ArrayList<>(); - Schema schema = _originalSegmentMetadata.getSchema(); - if (_columnsToConvert == null) { - LOGGER.info("Columns to convert are not specified, check each metric column"); - for (MetricFieldSpec metricFieldSpec : schema.getMetricFieldSpecs()) { - if (_originalSegmentMetadata.getColumnMetadataFor(metricFieldSpec.getName()).hasDictionary() - && shouldConvertColumn(metricFieldSpec)) { - columnsToConvert.add(metricFieldSpec); - } - } - } else { - LOGGER.info("Columns to convert: {}", _columnsToConvert); - for (String columnToConvert : StringUtils.split(_columnsToConvert, ',')) { - FieldSpec fieldSpec = schema.getFieldSpecFor(columnToConvert); - if (fieldSpec == null) { - LOGGER.warn("Skip converting column: {} because is does not exist in the schema", columnsToConvert); - continue; - } - if (!fieldSpec.isSingleValueField()) { - LOGGER.warn("Skip converting column: {} because it's a multi-value column", columnsToConvert); - continue; - } - if (!_originalSegmentMetadata.getColumnMetadataFor(columnToConvert).hasDictionary()) { - LOGGER.warn("Skip converting column: {} because its index is not dictionary-based", columnsToConvert); - continue; - } - columnsToConvert.add(fieldSpec); - } - } - - if (columnsToConvert.isEmpty()) { - LOGGER.info("No column converted for segment: {} in table: {}", segmentName, _rawTableName); - return false; - } else { - // Convert columns - for (FieldSpec columnToConvert : columnsToConvert) { - convertColumn(columnToConvert); - } - _convertedProperties.save(); - - // Update creation metadata with new computed CRC and original segment creation time - SegmentIndexCreationDriverImpl - .persistCreationMeta(_convertedIndexDir, CrcUtils.forAllFilesInFolder(_convertedIndexDir).computeCrc(), - _originalSegmentMetadata.getIndexCreationTime()); - - LOGGER.info("{} columns converted for segment: {} in table: {}", columnsToConvert.size(), segmentName, - _rawTableName); - return true; - } - } - - private boolean shouldConvertColumn(FieldSpec fieldSpec) { - String columnName = fieldSpec.getName(); - DataType storedType = fieldSpec.getDataType().getStoredType(); - int numTotalDocs = _originalSegmentMetadata.getTotalDocs(); - ColumnMetadata columnMetadata = _originalSegmentMetadata.getColumnMetadataFor(columnName); - - int cardinality = columnMetadata.getCardinality(); - - // In bits - int lengthOfEachEntry; - if (storedType.isFixedWidth()) { - lengthOfEachEntry = storedType.size() * Byte.SIZE; - } else { - lengthOfEachEntry = columnMetadata.getColumnMaxLength() * Byte.SIZE; - } - long dictionaryBasedIndexSize = - (long) numTotalDocs * columnMetadata.getBitsPerElement() + (long) cardinality * lengthOfEachEntry; - long rawIndexSize = (long) numTotalDocs * lengthOfEachEntry; - LOGGER.info( - "For column: {}, size of dictionary based index: {} bits, size of raw index (without compression): {} bits", - columnName, dictionaryBasedIndexSize, rawIndexSize); - - return rawIndexSize <= dictionaryBasedIndexSize * CONVERSION_THRESHOLD; - } - - private void convertColumn(FieldSpec fieldSpec) - throws Exception { - String columnName = fieldSpec.getName(); - LOGGER.info("Converting column: {}", columnName); - - // Delete dictionary and existing indexes - FileUtils.deleteQuietly(new File(_convertedIndexDir, columnName + V1Constants.Dict.FILE_EXTENSION)); - FileUtils.deleteQuietly( - new File(_convertedIndexDir, columnName + V1Constants.Indexes.UNSORTED_SV_FORWARD_INDEX_FILE_EXTENSION)); - FileUtils.deleteQuietly( - new File(_convertedIndexDir, columnName + V1Constants.Indexes.SORTED_SV_FORWARD_INDEX_FILE_EXTENSION)); - FileUtils.deleteQuietly( - new File(_convertedIndexDir, columnName + V1Constants.Indexes.BITMAP_INVERTED_INDEX_FILE_EXTENSION)); - - // Create the raw index - DataSource dataSource = _originalImmutableSegment.getDataSource(columnName); - ForwardIndexReader forwardIndexReader = dataSource.getForwardIndex(); - Preconditions.checkState(forwardIndexReader != null, - "Forward index disabled for column: %s, raw index conversion operation unsupported!", columnName); - Dictionary dictionary = dataSource.getDictionary(); - assert dictionary != null; - DataType storedType = dictionary.getValueType(); - int numDocs = _originalSegmentMetadata.getTotalDocs(); - ColumnMetadata columnMetadata = _originalSegmentMetadata.getColumnMetadataFor(columnName); - try (ForwardIndexCreator rawIndexCreator = _indexCreatorProvider.newForwardIndexCreator( - IndexCreationContext.builder().withIndexDir(_convertedIndexDir).withColumnMetadata(columnMetadata) - .withFieldSpec(new DimensionFieldSpec(columnName, storedType, columnMetadata.isSingleValue())) - .withDictionary(false).build().forForwardIndex(ChunkCompressionType.LZ4, null)); - ForwardIndexReaderContext readerContext = forwardIndexReader.createContext()) { - switch (storedType) { - case INT: - for (int docId = 0; docId < numDocs; docId++) { - rawIndexCreator.putInt(dictionary.getIntValue(forwardIndexReader.getDictId(docId, readerContext))); - } - break; - case LONG: - for (int docId = 0; docId < numDocs; docId++) { - rawIndexCreator.putLong(dictionary.getLongValue(forwardIndexReader.getDictId(docId, readerContext))); - } - break; - case FLOAT: - for (int docId = 0; docId < numDocs; docId++) { - rawIndexCreator.putFloat(dictionary.getFloatValue(forwardIndexReader.getDictId(docId, readerContext))); - } - break; - case DOUBLE: - for (int docId = 0; docId < numDocs; docId++) { - rawIndexCreator.putDouble(dictionary.getDoubleValue(forwardIndexReader.getDictId(docId, readerContext))); - } - break; - case STRING: - for (int docId = 0; docId < numDocs; docId++) { - rawIndexCreator.putString(dictionary.getStringValue(forwardIndexReader.getDictId(docId, readerContext))); - } - break; - case BYTES: - for (int docId = 0; docId < numDocs; docId++) { - rawIndexCreator.putBytes(dictionary.getBytesValue(forwardIndexReader.getDictId(docId, readerContext))); - } - break; - default: - throw new IllegalStateException(); - } - } - - // Update the segment metadata - _convertedProperties.setProperty( - V1Constants.MetadataKeys.Column.getKeyFor(columnName, V1Constants.MetadataKeys.Column.HAS_DICTIONARY), false); - _convertedProperties.setProperty( - V1Constants.MetadataKeys.Column.getKeyFor(columnName, V1Constants.MetadataKeys.Column.BITS_PER_ELEMENT), - BITS_PER_ELEMENT_FOR_RAW_INDEX); - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/minion/SegmentPurger.java b/pinot-core/src/main/java/org/apache/pinot/core/minion/SegmentPurger.java index 2b655085859f..2ab65bbe9c39 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/minion/SegmentPurger.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/minion/SegmentPurger.java @@ -28,9 +28,11 @@ import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; import org.apache.pinot.segment.spi.index.metadata.SegmentMetadataImpl; import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.data.Schema; import org.apache.pinot.spi.data.readers.GenericRow; import org.apache.pinot.spi.data.readers.RecordReader; import org.apache.pinot.spi.data.readers.RecordReaderConfig; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,19 +47,21 @@ public class SegmentPurger { private final File _indexDir; private final File _workingDir; private final TableConfig _tableConfig; + private final Schema _schema; private final RecordPurger _recordPurger; private final RecordModifier _recordModifier; private int _numRecordsPurged; private int _numRecordsModified; - public SegmentPurger(File indexDir, File workingDir, TableConfig tableConfig, @Nullable RecordPurger recordPurger, - @Nullable RecordModifier recordModifier) { + public SegmentPurger(File indexDir, File workingDir, TableConfig tableConfig, Schema schema, + @Nullable RecordPurger recordPurger, @Nullable RecordModifier recordModifier) { Preconditions.checkArgument(recordPurger != null || recordModifier != null, "At least one of record purger and modifier should be non-null"); _indexDir = indexDir; _workingDir = workingDir; _tableConfig = tableConfig; + _schema = schema; _recordPurger = recordPurger; _recordModifier = recordModifier; } @@ -66,7 +70,8 @@ public File purgeSegment() throws Exception { SegmentMetadataImpl segmentMetadata = new SegmentMetadataImpl(_indexDir); String segmentName = segmentMetadata.getName(); - LOGGER.info("Start purging table: {}, segment: {}", _tableConfig.getTableName(), segmentName); + String tableNameWithType = _tableConfig.getTableName(); + LOGGER.info("Start purging table: {}, segment: {}", tableNameWithType, segmentName); try (PurgeRecordReader purgeRecordReader = new PurgeRecordReader()) { // Make a first pass through the data to see if records need to be purged or modified @@ -79,7 +84,7 @@ public File purgeSegment() return null; } - SegmentGeneratorConfig config = new SegmentGeneratorConfig(_tableConfig, segmentMetadata.getSchema()); + SegmentGeneratorConfig config = new SegmentGeneratorConfig(_tableConfig, _schema); config.setOutDir(_workingDir.getPath()); config.setSegmentName(segmentName); @@ -103,8 +108,9 @@ public File purgeSegment() driver.build(); } - LOGGER.info("Finish purging table: {}, segment: {}, purged {} records, modified {} records", - _tableConfig.getTableName(), segmentName, _numRecordsPurged, _numRecordsModified); + LOGGER.info("Finish purging table: {}, segment: {}, purged {} records, modified {} records", tableNameWithType, + segmentName, _numRecordsPurged, _numRecordsModified); + return new File(_workingDir, segmentName); } @@ -227,6 +233,13 @@ public interface RecordPurgerFactory { * Get the {@link RecordPurger} for the given table. */ RecordPurger getRecordPurger(String rawTableName); + + /** + * Get the {@link RecordPurger} associated with the given taskConfig, tableConfig and tableSchema + */ + default RecordPurger getRecordPurger(PinotTaskConfig taskConfig, TableConfig tableConfig, Schema tableSchema) { + return getRecordPurger(TableNameBuilder.extractRawTableName(tableConfig.getTableName())); + } } /** diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/AcquireReleaseColumnsSegmentOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/AcquireReleaseColumnsSegmentOperator.java index 4d79eeea4ab4..0f535b780638 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/AcquireReleaseColumnsSegmentOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/AcquireReleaseColumnsSegmentOperator.java @@ -20,6 +20,7 @@ import java.util.Collections; import java.util.List; +import org.apache.pinot.core.common.ExplainPlanRows; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock; import org.apache.pinot.core.plan.PlanNode; @@ -54,12 +55,16 @@ public AcquireReleaseColumnsSegmentOperator(PlanNode planNode, IndexSegment inde _fetchContext = fetchContext; } + public void materializeChildOperator() { + _childOperator = (Operator) _planNode.run(); + } + /** * Runs the planNode to get the childOperator, and then proceeds with execution. */ @Override protected BaseResultsBlock getNextBlock() { - _childOperator = (Operator) _planNode.run(); + materializeChildOperator(); return _childOperator.nextBlock(); } @@ -69,7 +74,7 @@ protected BaseResultsBlock getNextBlock() { public void acquire() { // do not acquire if interrupted (similar to the check inside the nextBlock()) if (Thread.interrupted()) { - throw new EarlyTerminationException(); + throw new EarlyTerminationException("Interrupted while acquiring segment"); } _indexSegment.acquire(_fetchContext); } @@ -81,6 +86,16 @@ public void release() { _indexSegment.release(_fetchContext); } + @Override + public void prepareForExplainPlan(ExplainPlanRows explainPlanRows) { + acquire(); + materializeChildOperator(); + } + + @Override + public void postExplainPlan(ExplainPlanRows explainPlanRows) { + release(); + } @Override public String toExplainString() { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/BaseOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/BaseOperator.java index b0694bcd75f8..c9ab35457d8a 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/BaseOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/BaseOperator.java @@ -37,7 +37,7 @@ public final T nextBlock() { itself will cancel all worker's futures. Therefore, the worker will interrupt even if we only kill the runner thread. */ if (Tracing.ThreadAccountantOps.isInterrupted()) { - throw new EarlyTerminationException(); + throw new EarlyTerminationException("Interrupted while processing next block"); } try (InvocationScope ignored = Tracing.getTracer().createScope(getClass())) { return getNextBlock(); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/BaseProjectOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/BaseProjectOperator.java new file mode 100644 index 000000000000..aaaa8cc41555 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/BaseProjectOperator.java @@ -0,0 +1,44 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator; + +import java.util.Map; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; + + +public abstract class BaseProjectOperator extends BaseOperator { + + /** + * Returns a map from source column name to context. + */ + public abstract Map getSourceColumnContextMap(); + + /** + * Returns the result column context. Without transform, the source and result column context are the same. + */ + public abstract ColumnContext getResultColumnContext(ExpressionContext expression); + + /** + * Returns the number of columns projected. + */ + public int getNumColumnsProjected() { + return getSourceColumnContextMap().size(); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/BitmapDocIdSetOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/BitmapDocIdSetOperator.java index b251c552d892..4f203599c1e3 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/BitmapDocIdSetOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/BitmapDocIdSetOperator.java @@ -52,6 +52,11 @@ public BitmapDocIdSetOperator(ImmutableBitmapDataProvider bitmap, int numDocs) { _docIdBuffer = new int[Math.min(numDocs, DocIdSetPlanNode.MAX_DOC_PER_CALL)]; } + public BitmapDocIdSetOperator(IntIterator intIterator, int[] docIdBuffer) { + _intIterator = intIterator; + _docIdBuffer = docIdBuffer; + } + public BitmapDocIdSetOperator(ImmutableBitmapDataProvider bitmap, int[] docIdBuffer) { _intIterator = bitmap.getIntIterator(); _docIdBuffer = docIdBuffer; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/ColumnContext.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/ColumnContext.java new file mode 100644 index 000000000000..878ef0b3f952 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/ColumnContext.java @@ -0,0 +1,73 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator; + +import javax.annotation.Nullable; +import org.apache.pinot.core.operator.transform.TransformResultMetadata; +import org.apache.pinot.core.operator.transform.function.TransformFunction; +import org.apache.pinot.segment.spi.datasource.DataSource; +import org.apache.pinot.segment.spi.datasource.DataSourceMetadata; +import org.apache.pinot.segment.spi.index.reader.Dictionary; +import org.apache.pinot.spi.data.FieldSpec.DataType; + + +public class ColumnContext { + private final DataType _dataType; + private final boolean _isSingleValue; + private final Dictionary _dictionary; + private final DataSource _dataSource; + + private ColumnContext(DataType dataType, boolean isSingleValue, @Nullable Dictionary dictionary, + @Nullable DataSource dataSource) { + _dataType = dataType; + _isSingleValue = isSingleValue; + _dictionary = dictionary; + _dataSource = dataSource; + } + + public DataType getDataType() { + return _dataType; + } + + public boolean isSingleValue() { + return _isSingleValue; + } + + @Nullable + public Dictionary getDictionary() { + return _dictionary; + } + + @Nullable + public DataSource getDataSource() { + return _dataSource; + } + + public static ColumnContext fromDataSource(DataSource dataSource) { + DataSourceMetadata dataSourceMetadata = dataSource.getDataSourceMetadata(); + return new ColumnContext(dataSourceMetadata.getDataType(), dataSourceMetadata.isSingleValue(), + dataSource.getDictionary(), dataSource); + } + + public static ColumnContext fromTransformFunction(TransformFunction transformFunction) { + TransformResultMetadata resultMetadata = transformFunction.getResultMetadata(); + return new ColumnContext(resultMetadata.getDataType(), resultMetadata.isSingleValue(), + transformFunction.getDictionary(), null); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/DocIdSetOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/DocIdSetOperator.java index 6ee1314459fe..2d6e9acc67dd 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/DocIdSetOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/DocIdSetOperator.java @@ -22,9 +22,9 @@ import java.util.Collections; import java.util.List; import org.apache.pinot.core.common.BlockDocIdIterator; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.blocks.DocIdSetBlock; -import org.apache.pinot.core.operator.docidsets.FilterBlockDocIdSet; import org.apache.pinot.core.operator.filter.BaseFilterOperator; import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.segment.spi.Constants; @@ -45,7 +45,7 @@ public class DocIdSetOperator extends BaseOperator { private final BaseFilterOperator _filterOperator; private final int _maxSizeOfDocIdSet; - private FilterBlockDocIdSet _filterBlockDocIdSet; + private BlockDocIdSet _blockDocIdSet; private BlockDocIdIterator _blockDocIdIterator; private int _currentDocId = 0; @@ -61,11 +61,10 @@ protected DocIdSetBlock getNextBlock() { return null; } - // Initialize filter block document Id set - if (_filterBlockDocIdSet == null) { - _filterBlockDocIdSet = _filterOperator.nextBlock().getBlockDocIdSet(); - _blockDocIdIterator = _filterBlockDocIdSet.iterator(); + if (_blockDocIdSet == null) { + _blockDocIdSet = _filterOperator.nextBlock().getBlockDocIdSet(); + _blockDocIdIterator = _blockDocIdSet.iterator(); } Tracing.ThreadAccountantOps.sample(); @@ -86,7 +85,6 @@ protected DocIdSetBlock getNextBlock() { } } - @Override public String toExplainString() { return EXPLAIN_NAME; @@ -99,8 +97,7 @@ public List getChildOperators() { @Override public ExecutionStatistics getExecutionStatistics() { - long numEntriesScannedInFilter = - _filterBlockDocIdSet != null ? _filterBlockDocIdSet.getNumEntriesScannedInFilter() : 0; + long numEntriesScannedInFilter = _blockDocIdSet != null ? _blockDocIdSet.getNumEntriesScannedInFilter() : 0; return new ExecutionStatistics(0, numEntriesScannedInFilter, 0, 0); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/InstanceResponseOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/InstanceResponseOperator.java index e0ca51302fc4..b6686c3a9018 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/InstanceResponseOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/InstanceResponseOperator.java @@ -20,30 +20,38 @@ import java.util.Collections; import java.util.List; +import org.apache.commons.lang3.StringUtils; import org.apache.pinot.common.datatable.DataTable.MetadataKey; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.blocks.InstanceResponseBlock; import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock; +import org.apache.pinot.core.operator.blocks.results.ExceptionResultsBlock; import org.apache.pinot.core.operator.combine.BaseCombineOperator; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.FetchContext; -import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; import org.apache.pinot.spi.accounting.ThreadResourceUsageProvider; +import org.apache.pinot.spi.exception.EarlyTerminationException; +import org.apache.pinot.spi.exception.QueryCancelledException; +import org.apache.pinot.spi.trace.Tracing; public class InstanceResponseOperator extends BaseOperator { private static final String EXPLAIN_NAME = "INSTANCE_RESPONSE"; - private final BaseCombineOperator _combineOperator; - private final List _indexSegments; - private final List _fetchContexts; - private final int _fetchContextSize; - private final QueryContext _queryContext; + protected final BaseCombineOperator _combineOperator; + protected final List _segmentContexts; + protected final List _fetchContexts; + protected final int _fetchContextSize; + protected final QueryContext _queryContext; - public InstanceResponseOperator(BaseCombineOperator combineOperator, List indexSegments, + protected long _threadCpuTimeNs; + protected long _systemActivitiesCpuTimeNs; + + public InstanceResponseOperator(BaseCombineOperator combineOperator, List segmentContexts, List fetchContexts, QueryContext queryContext) { _combineOperator = combineOperator; - _indexSegments = indexSegments; + _segmentContexts = segmentContexts; _fetchContexts = fetchContexts; _fetchContextSize = fetchContexts.size(); _queryContext = queryContext; @@ -76,12 +84,24 @@ public static long calSystemActivitiesCpuTimeNs(long totalWallClockTimeNs, long @Override protected InstanceResponseBlock getNextBlock() { + BaseResultsBlock baseResultsBlock = getBaseBlock(); + return buildInstanceResponseBlock(baseResultsBlock); + } + + protected InstanceResponseBlock buildInstanceResponseBlock(BaseResultsBlock baseResultsBlock) { + InstanceResponseBlock instanceResponseBlock = new InstanceResponseBlock(baseResultsBlock); + instanceResponseBlock.addMetadata(MetadataKey.THREAD_CPU_TIME_NS.getName(), String.valueOf(_threadCpuTimeNs)); + instanceResponseBlock.addMetadata(MetadataKey.SYSTEM_ACTIVITIES_CPU_TIME_NS.getName(), + String.valueOf(_systemActivitiesCpuTimeNs)); + return instanceResponseBlock; + } + + protected BaseResultsBlock getBaseBlock() { if (ThreadResourceUsageProvider.isThreadCpuTimeMeasurementEnabled()) { long startWallClockTimeNs = System.nanoTime(); ThreadResourceUsageProvider mainThreadResourceUsageProvider = new ThreadResourceUsageProvider(); BaseResultsBlock resultsBlock = getCombinedResults(); - InstanceResponseBlock instanceResponseBlock = new InstanceResponseBlock(resultsBlock, _queryContext); long mainThreadCpuTimeNs = mainThreadResourceUsageProvider.getThreadTimeNs(); long totalWallClockTimeNs = System.nanoTime() - startWallClockTimeNs; @@ -92,39 +112,39 @@ protected InstanceResponseBlock getNextBlock() { */ long multipleThreadCpuTimeNs = resultsBlock.getExecutionThreadCpuTimeNs(); int numServerThreads = resultsBlock.getNumServerThreads(); - long systemActivitiesCpuTimeNs = - calSystemActivitiesCpuTimeNs(totalWallClockTimeNs, multipleThreadCpuTimeNs, mainThreadCpuTimeNs, - numServerThreads); - - long threadCpuTimeNs = mainThreadCpuTimeNs + multipleThreadCpuTimeNs; - instanceResponseBlock.addMetadata(MetadataKey.THREAD_CPU_TIME_NS.getName(), String.valueOf(threadCpuTimeNs)); - instanceResponseBlock.addMetadata(MetadataKey.SYSTEM_ACTIVITIES_CPU_TIME_NS.getName(), - String.valueOf(systemActivitiesCpuTimeNs)); + _systemActivitiesCpuTimeNs = calSystemActivitiesCpuTimeNs(totalWallClockTimeNs, multipleThreadCpuTimeNs, + mainThreadCpuTimeNs, numServerThreads); + _threadCpuTimeNs = mainThreadCpuTimeNs + multipleThreadCpuTimeNs; - return instanceResponseBlock; + return resultsBlock; } else { - return new InstanceResponseBlock(getCombinedResults(), _queryContext); + return getCombinedResults(); } } - private BaseResultsBlock getCombinedResults() { + protected BaseResultsBlock getCombinedResults() { try { prefetchAll(); return _combineOperator.nextBlock(); + } catch (EarlyTerminationException e) { + Exception killedErrorMsg = Tracing.getThreadAccountant().getErrorStatus(); + return new ExceptionResultsBlock(new QueryCancelledException( + "Cancelled while combining results" + (killedErrorMsg == null ? StringUtils.EMPTY : " " + killedErrorMsg), + e)); } finally { releaseAll(); } } - private void prefetchAll() { + public void prefetchAll() { for (int i = 0; i < _fetchContextSize; i++) { - _indexSegments.get(i).prefetch(_fetchContexts.get(i)); + _segmentContexts.get(i).getIndexSegment().prefetch(_fetchContexts.get(i)); } } - private void releaseAll() { + public void releaseAll() { for (int i = 0; i < _fetchContextSize; i++) { - _indexSegments.get(i).release(_fetchContexts.get(i)); + _segmentContexts.get(i).getIndexSegment().release(_fetchContexts.get(i)); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/ProjectionOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/ProjectionOperator.java index fb3c26df75db..3a48d6ed929f 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/ProjectionOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/ProjectionOperator.java @@ -19,9 +19,12 @@ package org.apache.pinot.core.operator; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.Nullable; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.HashUtil; import org.apache.pinot.core.common.DataBlockCache; import org.apache.pinot.core.common.DataFetcher; import org.apache.pinot.core.common.Operator; @@ -31,28 +34,33 @@ import org.apache.pinot.spi.trace.Tracing; -public class ProjectionOperator extends BaseOperator { - +public class ProjectionOperator extends BaseProjectOperator { private static final String EXPLAIN_NAME = "PROJECT"; private final Map _dataSourceMap; private final BaseOperator _docIdSetOperator; private final DataBlockCache _dataBlockCache; + private final Map _columnContextMap; public ProjectionOperator(Map dataSourceMap, @Nullable BaseOperator docIdSetOperator) { _dataSourceMap = dataSourceMap; _docIdSetOperator = docIdSetOperator; _dataBlockCache = new DataBlockCache(new DataFetcher(dataSourceMap)); + _columnContextMap = new HashMap<>(HashUtil.getHashMapCapacity(dataSourceMap.size())); + dataSourceMap.forEach( + (column, dataSource) -> _columnContextMap.put(column, ColumnContext.fromDataSource(dataSource))); + } + + @Override + public Map getSourceColumnContextMap() { + return _columnContextMap; } - /** - * Returns the map from column to data source. - * - * @return Map from column to data source - */ - public Map getDataSourceMap() { - return _dataSourceMap; + @Override + public ColumnContext getResultColumnContext(ExpressionContext expression) { + assert expression.getType() == ExpressionContext.Type.IDENTIFIER; + return _columnContextMap.get(expression.getIdentifier()); } @Override @@ -64,14 +72,13 @@ protected ProjectionBlock getNextBlock() { return null; } else { Tracing.activeRecording().setNumChildren(_dataSourceMap.size()); - _dataBlockCache.initNewBlock(docIdSetBlock.getDocIdSet(), docIdSetBlock.getSearchableLength()); + _dataBlockCache.initNewBlock(docIdSetBlock.getDocIds(), docIdSetBlock.getLength()); return new ProjectionBlock(_dataSourceMap, _dataBlockCache); } } - @Override - public List getChildOperators() { + public List> getChildOperators() { return Collections.singletonList(_docIdSetOperator); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/ProjectionOperatorUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/ProjectionOperatorUtils.java new file mode 100644 index 000000000000..356d4da3480e --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/ProjectionOperatorUtils.java @@ -0,0 +1,57 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator; + +import java.util.Map; +import javax.annotation.Nullable; +import org.apache.pinot.core.operator.blocks.DocIdSetBlock; +import org.apache.pinot.segment.spi.datasource.DataSource; + + +public class ProjectionOperatorUtils { + private static Implementation _instance = new DefaultImplementation(); + + private ProjectionOperatorUtils() { + } + + public static void setImplementation(Implementation newImplementation) { + _instance = newImplementation; + } + + public static ProjectionOperator getProjectionOperator(Map dataSourceMap, + @Nullable BaseOperator docIdSetOperator) { + return _instance.getProjectionOperator(dataSourceMap, docIdSetOperator); + } + + public interface Implementation { + /** + * Returns the projection operator + */ + ProjectionOperator getProjectionOperator(Map dataSourceMap, + @Nullable BaseOperator docIdSetOperator); + } + + public static class DefaultImplementation implements Implementation { + @Override + public ProjectionOperator getProjectionOperator(Map dataSourceMap, + @Nullable BaseOperator docIdSetOperator) { + return new ProjectionOperator(dataSourceMap, docIdSetOperator); + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/DocIdSetBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/DocIdSetBlock.java index bf2528bbb884..d4bb15627695 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/DocIdSetBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/DocIdSetBlock.java @@ -19,48 +19,27 @@ package org.apache.pinot.core.operator.blocks; import org.apache.pinot.core.common.Block; -import org.apache.pinot.core.common.BlockDocIdSet; -import org.apache.pinot.core.common.BlockDocIdValueSet; -import org.apache.pinot.core.common.BlockMetadata; -import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.docidsets.ArrayBasedDocIdSet; +import org.apache.pinot.core.operator.DocIdSetOperator; +/** + * The {@code DocIdSetBlock} contains a block of document ids (sorted), and is returned from {@link DocIdSetOperator}. + * Each {@code DocIdSetOperator} can return multiple {@code DocIdSetBlock}s. + */ public class DocIdSetBlock implements Block { + private final int[] _docIds; + private final int _length; - private final int[] _docIdArray; - private final int _searchableLength; - - public DocIdSetBlock(int[] docIdSet, int searchableLength) { - _docIdArray = docIdSet; - _searchableLength = searchableLength; - } - - public int[] getDocIdSet() { - return _docIdArray; - } - - public int getSearchableLength() { - return _searchableLength; - } - - @Override - public BlockValSet getBlockValueSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockDocIdValueSet getBlockDocIdValueSet() { - throw new UnsupportedOperationException(); + public DocIdSetBlock(int[] docIds, int length) { + _docIds = docIds; + _length = length; } - @Override - public BlockDocIdSet getBlockDocIdSet() { - return new ArrayBasedDocIdSet(_docIdArray, _searchableLength); + public int[] getDocIds() { + return _docIds; } - @Override - public BlockMetadata getMetadata() { - throw new UnsupportedOperationException(); + public int getLength() { + return _length; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/FilterBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/FilterBlock.java index 8afdd8cd3d67..185939283bf1 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/FilterBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/FilterBlock.java @@ -19,50 +19,31 @@ package org.apache.pinot.core.operator.blocks; import org.apache.pinot.core.common.Block; -import org.apache.pinot.core.common.BlockDocIdValueSet; -import org.apache.pinot.core.common.BlockMetadata; -import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.docidsets.FilterBlockDocIdSet; +import org.apache.pinot.core.common.BlockDocIdSet; /** * The {@code FilterBlock} class is the block holding the document Ids returned from the filter operator. */ public class FilterBlock implements Block { - private final FilterBlockDocIdSet _filterBlockDocIdSet; - private FilterBlockDocIdSet _nonScanFilterBlockDocIdSet; + private final BlockDocIdSet _blockDocIdSet; + private BlockDocIdSet _nonScanBlockDocIdSet; - public FilterBlock(FilterBlockDocIdSet filterBlockDocIdSet) { - _filterBlockDocIdSet = filterBlockDocIdSet; + public FilterBlock(BlockDocIdSet blockDocIdSet) { + _blockDocIdSet = blockDocIdSet; } /** * Pre-scans the documents if needed, and returns a non-scan-based FilterBlockDocIdSet. */ - public FilterBlockDocIdSet getNonScanFilterBLockDocIdSet() { - if (_nonScanFilterBlockDocIdSet == null) { - _nonScanFilterBlockDocIdSet = _filterBlockDocIdSet.toNonScanDocIdSet(); + public BlockDocIdSet getNonScanFilterBLockDocIdSet() { + if (_nonScanBlockDocIdSet == null) { + _nonScanBlockDocIdSet = _blockDocIdSet.toNonScanDocIdSet(); } - return _nonScanFilterBlockDocIdSet; + return _nonScanBlockDocIdSet; } - @Override - public FilterBlockDocIdSet getBlockDocIdSet() { - return _nonScanFilterBlockDocIdSet != null ? _nonScanFilterBlockDocIdSet : _filterBlockDocIdSet; - } - - @Override - public BlockValSet getBlockValueSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockDocIdValueSet getBlockDocIdValueSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockMetadata getMetadata() { - throw new UnsupportedOperationException(); + public BlockDocIdSet getBlockDocIdSet() { + return _nonScanBlockDocIdSet != null ? _nonScanBlockDocIdSet : _blockDocIdSet; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/InstanceResponseBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/InstanceResponseBlock.java index 4708d5cf832a..ef705f3e65fb 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/InstanceResponseBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/InstanceResponseBlock.java @@ -19,7 +19,6 @@ package org.apache.pinot.core.operator.blocks; import java.io.IOException; -import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -28,13 +27,8 @@ import org.apache.pinot.common.response.ProcessingException; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.common.Block; -import org.apache.pinot.core.common.BlockDocIdSet; -import org.apache.pinot.core.common.BlockDocIdValueSet; -import org.apache.pinot.core.common.BlockMetadata; -import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.common.datatable.DataTableBuilderFactory; import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock; -import org.apache.pinot.core.query.request.context.QueryContext; /** @@ -42,13 +36,11 @@ */ public class InstanceResponseBlock implements Block { private final BaseResultsBlock _resultsBlock; - private final QueryContext _queryContext; private final Map _exceptions; private final Map _metadata; - public InstanceResponseBlock(BaseResultsBlock resultsBlock, QueryContext queryContext) { + public InstanceResponseBlock(BaseResultsBlock resultsBlock) { _resultsBlock = resultsBlock; - _queryContext = queryContext; _exceptions = new HashMap<>(); List processingExceptions = resultsBlock.getProcessingExceptions(); if (processingExceptions != null) { @@ -64,14 +56,12 @@ public InstanceResponseBlock(BaseResultsBlock resultsBlock, QueryContext queryCo */ public InstanceResponseBlock() { _resultsBlock = null; - _queryContext = null; _exceptions = new HashMap<>(); _metadata = new HashMap<>(); } private InstanceResponseBlock(Map exceptions, Map metadata) { _resultsBlock = null; - _queryContext = null; _exceptions = exceptions; _metadata = metadata; } @@ -97,11 +87,6 @@ public BaseResultsBlock getResultsBlock() { return _resultsBlock; } - @Nullable - public QueryContext getQueryContext() { - return _queryContext; - } - public Map getExceptions() { return _exceptions; } @@ -112,12 +97,12 @@ public Map getResponseMetadata() { @Nullable public DataSchema getDataSchema() { - return _resultsBlock != null ? _resultsBlock.getDataSchema(_queryContext) : null; + return _resultsBlock != null ? _resultsBlock.getDataSchema() : null; } @Nullable - public Collection getRows() { - return _resultsBlock != null ? _resultsBlock.getRows(_queryContext) : null; + public List getRows() { + return _resultsBlock != null ? _resultsBlock.getRows() : null; } public DataTable toDataTable() @@ -129,8 +114,7 @@ public DataTable toDataTable() public DataTable toDataOnlyDataTable() throws IOException { - return _resultsBlock != null ? _resultsBlock.getDataTable(_queryContext) - : DataTableBuilderFactory.getEmptyDataTable(); + return _resultsBlock != null ? _resultsBlock.getDataTable() : DataTableBuilderFactory.getEmptyDataTable(); } public DataTable toMetadataOnlyDataTable() { @@ -145,24 +129,4 @@ private void attachMetadata(DataTable dataTable) { } dataTable.getMetadata().putAll(_metadata); } - - @Override - public BlockValSet getBlockValueSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockDocIdValueSet getBlockDocIdValueSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockDocIdSet getBlockDocIdSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockMetadata getMetadata() { - throw new UnsupportedOperationException(); - } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/PassThroughTransformBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/PassThroughTransformBlock.java deleted file mode 100644 index c9f7d0d7e198..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/PassThroughTransformBlock.java +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.operator.blocks; - -import java.util.Map; -import org.apache.pinot.common.request.context.ExpressionContext; -import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.transform.function.TransformFunction; - - -/** - * Transform Block servers as a pass-through to projection block. - */ -public class PassThroughTransformBlock extends TransformBlock { - - public PassThroughTransformBlock(ProjectionBlock projectionBlock, - Map transformFunctionMap) { - super(projectionBlock, transformFunctionMap); - } - - @Override - public BlockValSet getBlockValueSet(ExpressionContext expression) { - return _projectionBlock.getBlockValueSet(expression.getIdentifier()); - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/ProjectionBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/ProjectionBlock.java index 066d27b350db..efef8693d030 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/ProjectionBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/ProjectionBlock.java @@ -20,10 +20,7 @@ import java.math.BigDecimal; import java.util.Map; -import org.apache.pinot.core.common.Block; -import org.apache.pinot.core.common.BlockDocIdSet; -import org.apache.pinot.core.common.BlockDocIdValueSet; -import org.apache.pinot.core.common.BlockMetadata; +import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.common.DataBlockCache; import org.apache.pinot.core.operator.docvalsets.ProjectionBlockValSet; @@ -35,7 +32,7 @@ * ProjectionBlock holds a column name to Block Map. * It provides DocIdSetBlock for a given column. */ -public class ProjectionBlock implements Block { +public class ProjectionBlock implements ValueBlock { private final Map _dataSourceMap; private final DataBlockCache _dataBlockCache; @@ -44,36 +41,25 @@ public ProjectionBlock(Map dataSourceMap, DataBlockCache dat _dataBlockCache = dataBlockCache; } + @Override public int getNumDocs() { return _dataBlockCache.getNumDocs(); } + @Override public int[] getDocIds() { return _dataBlockCache.getDocIds(); } - public BlockValSet getBlockValueSet(String column) { - return new ProjectionBlockValSet(_dataBlockCache, column, _dataSourceMap.get(column)); - } - - @Override - public BlockDocIdSet getBlockDocIdSet() { - throw new UnsupportedOperationException(); - } - @Override - public BlockValSet getBlockValueSet() { - throw new UnsupportedOperationException(); + public BlockValSet getBlockValueSet(ExpressionContext expression) { + assert expression.getType() == ExpressionContext.Type.IDENTIFIER; + return getBlockValueSet(expression.getIdentifier()); } @Override - public BlockDocIdValueSet getBlockDocIdValueSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockMetadata getMetadata() { - throw new UnsupportedOperationException(); + public BlockValSet getBlockValueSet(String column) { + return new ProjectionBlockValSet(_dataBlockCache, column, _dataSourceMap.get(column)); } /** diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/TransformBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/TransformBlock.java index 5625a5704d41..65460d857c91 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/TransformBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/TransformBlock.java @@ -19,67 +19,47 @@ package org.apache.pinot.core.operator.blocks; import java.util.Map; +import javax.annotation.Nullable; import org.apache.pinot.common.request.context.ExpressionContext; -import org.apache.pinot.core.common.Block; -import org.apache.pinot.core.common.BlockDocIdSet; -import org.apache.pinot.core.common.BlockDocIdValueSet; -import org.apache.pinot.core.common.BlockMetadata; import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.operator.docvalsets.TransformBlockValSet; import org.apache.pinot.core.operator.transform.function.TransformFunction; /** - * Transform Block holds blocks of transformed columns. - *

In absence of transforms, it servers as a pass-through to projection block. + * The {@code TransformBlock} contains values of the transformed columns. */ -public class TransformBlock implements Block { - protected final ProjectionBlock _projectionBlock; +public class TransformBlock implements ValueBlock { + protected final ValueBlock _sourceBlock; protected final Map _transformFunctionMap; - public TransformBlock(ProjectionBlock projectionBlock, - Map transformFunctionMap) { - _projectionBlock = projectionBlock; + public TransformBlock(ValueBlock sourceBlock, Map transformFunctionMap) { + _sourceBlock = sourceBlock; _transformFunctionMap = transformFunctionMap; } + @Override public int getNumDocs() { - return _projectionBlock.getNumDocs(); + return _sourceBlock.getNumDocs(); } + @Nullable + @Override public int[] getDocIds() { - return _projectionBlock.getDocIds(); + return _sourceBlock.getDocIds(); } + @Override public BlockValSet getBlockValueSet(ExpressionContext expression) { if (expression.getType() == ExpressionContext.Type.IDENTIFIER) { - return _projectionBlock.getBlockValueSet(expression.getIdentifier()); + return _sourceBlock.getBlockValueSet(expression); } else { - return new TransformBlockValSet(_projectionBlock, _transformFunctionMap.get(expression), expression); + return new TransformBlockValSet(_sourceBlock, _transformFunctionMap.get(expression)); } } - public BlockValSet getBlockValueSet(String column) { - return _projectionBlock.getBlockValueSet(column); - } - - @Override - public BlockDocIdSet getBlockDocIdSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockValSet getBlockValueSet() { - throw new UnsupportedOperationException(); - } - @Override - public BlockDocIdValueSet getBlockDocIdValueSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockMetadata getMetadata() { - throw new UnsupportedOperationException(); + public BlockValSet getBlockValueSet(String column) { + return _sourceBlock.getBlockValueSet(column); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/ValueBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/ValueBlock.java new file mode 100644 index 000000000000..a135c1edf096 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/ValueBlock.java @@ -0,0 +1,52 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.blocks; + +import javax.annotation.Nullable; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.common.Block; +import org.apache.pinot.core.common.BlockValSet; + + +/** + * The {@code ValueBlock} contains a block of values for multiple expressions. + */ +public interface ValueBlock extends Block { + + /** + * Returns the number of documents within the block. + */ + int getNumDocs(); + + /** + * Returns the document ids from the segment, or {@code null} if it is not available. + */ + @Nullable + int[] getDocIds(); + + /** + * Returns the values for a given expression. + */ + BlockValSet getBlockValueSet(ExpressionContext expression); + + /** + * Returns the values for a given column (identifier). + */ + BlockValSet getBlockValueSet(String column); +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/AggregationResultsBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/AggregationResultsBlock.java index 54fa0b9558b2..4e0a1b306311 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/AggregationResultsBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/AggregationResultsBlock.java @@ -19,17 +19,24 @@ package org.apache.pinot.core.operator.blocks.results; import it.unimi.dsi.fastutil.doubles.DoubleArrayList; +import it.unimi.dsi.fastutil.floats.FloatArrayList; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import java.io.IOException; import java.math.BigDecimal; -import java.util.Collection; import java.util.Collections; import java.util.List; +import org.apache.commons.lang3.tuple.Pair; import org.apache.pinot.common.datatable.DataTable; +import org.apache.pinot.common.request.context.FilterContext; +import org.apache.pinot.common.utils.ArrayListUtils; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.common.datatable.DataTableBuilder; import org.apache.pinot.core.common.datatable.DataTableBuilderFactory; import org.apache.pinot.core.query.aggregation.function.AggregationFunction; +import org.apache.pinot.core.query.aggregation.function.AggregationFunctionUtils; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.spi.utils.ByteArray; import org.roaringbitmap.RoaringBitmap; @@ -42,10 +49,13 @@ public class AggregationResultsBlock extends BaseResultsBlock { private final AggregationFunction[] _aggregationFunctions; private final List _results; + private final QueryContext _queryContext; - public AggregationResultsBlock(AggregationFunction[] aggregationFunctions, List results) { + public AggregationResultsBlock(AggregationFunction[] aggregationFunctions, List results, + QueryContext queryContext) { _aggregationFunctions = aggregationFunctions; _results = results; + _queryContext = queryContext; } public AggregationFunction[] getAggregationFunctions() { @@ -62,14 +72,23 @@ public int getNumRows() { } @Override - public DataSchema getDataSchema(QueryContext queryContext) { - boolean returnFinalResult = queryContext.isServerReturnFinalResult(); - int numColumns = _aggregationFunctions.length; + public QueryContext getQueryContext() { + return _queryContext; + } + + @Override + public DataSchema getDataSchema() { + List> filteredAggregationFunctions = + _queryContext.getFilteredAggregationFunctions(); + assert filteredAggregationFunctions != null; + int numColumns = filteredAggregationFunctions.size(); String[] columnNames = new String[numColumns]; ColumnDataType[] columnDataTypes = new ColumnDataType[numColumns]; + boolean returnFinalResult = _queryContext.isServerReturnFinalResult(); for (int i = 0; i < numColumns; i++) { - AggregationFunction aggregationFunction = _aggregationFunctions[i]; - columnNames[i] = aggregationFunction.getColumnName(); + Pair pair = filteredAggregationFunctions.get(i); + AggregationFunction aggregationFunction = pair.getLeft(); + columnNames[i] = AggregationFunctionUtils.getResultColumnName(aggregationFunction, pair.getRight()); columnDataTypes[i] = returnFinalResult ? aggregationFunction.getFinalResultColumnType() : aggregationFunction.getIntermediateResultColumnType(); } @@ -77,20 +96,20 @@ public DataSchema getDataSchema(QueryContext queryContext) { } @Override - public Collection getRows(QueryContext queryContext) { + public List getRows() { return Collections.singletonList(_results.toArray()); } @Override - public DataTable getDataTable(QueryContext queryContext) + public DataTable getDataTable() throws IOException { - boolean returnFinalResult = queryContext.isServerReturnFinalResult(); - DataSchema dataSchema = getDataSchema(queryContext); + DataSchema dataSchema = getDataSchema(); assert dataSchema != null; ColumnDataType[] columnDataTypes = dataSchema.getColumnDataTypes(); int numColumns = columnDataTypes.length; DataTableBuilder dataTableBuilder = DataTableBuilderFactory.getDataTableBuilder(dataSchema); - if (queryContext.isNullHandlingEnabled()) { + boolean returnFinalResult = _queryContext.isServerReturnFinalResult(); + if (_queryContext.isNullHandlingEnabled()) { RoaringBitmap[] nullBitmaps = new RoaringBitmap[numColumns]; for (int i = 0; i < numColumns; i++) { nullBitmaps[i] = new RoaringBitmap(); @@ -133,6 +152,9 @@ private void setIntermediateResult(DataTableBuilder dataTableBuilder, ColumnData throws IOException { ColumnDataType columnDataType = columnDataTypes[index]; switch (columnDataType) { + case INT: + dataTableBuilder.setColumn(index, (int) result); + break; case LONG: dataTableBuilder.setColumn(index, (long) result); break; @@ -151,7 +173,7 @@ private void setFinalResult(DataTableBuilder dataTableBuilder, ColumnDataType[] Object result) throws IOException { ColumnDataType columnDataType = columnDataTypes[index]; - switch (columnDataType) { + switch (columnDataType.getStoredType()) { case INT: dataTableBuilder.setColumn(index, (int) result); break; @@ -173,8 +195,20 @@ private void setFinalResult(DataTableBuilder dataTableBuilder, ColumnDataType[] case BYTES: dataTableBuilder.setColumn(index, (ByteArray) result); break; + case INT_ARRAY: + dataTableBuilder.setColumn(index, ArrayListUtils.toIntArray((IntArrayList) result)); + break; + case LONG_ARRAY: + dataTableBuilder.setColumn(index, ArrayListUtils.toLongArray((LongArrayList) result)); + break; + case FLOAT_ARRAY: + dataTableBuilder.setColumn(index, ArrayListUtils.toFloatArray((FloatArrayList) result)); + break; case DOUBLE_ARRAY: - dataTableBuilder.setColumn(index, ((DoubleArrayList) result).elements()); + dataTableBuilder.setColumn(index, ArrayListUtils.toDoubleArray((DoubleArrayList) result)); + break; + case STRING_ARRAY: + dataTableBuilder.setColumn(index, ArrayListUtils.toStringArray((ObjectArrayList) result)); break; default: throw new IllegalStateException("Illegal column data type in final result: " + columnDataType); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/BaseResultsBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/BaseResultsBlock.java index 7c17a7abb659..41654d271d21 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/BaseResultsBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/BaseResultsBlock.java @@ -21,7 +21,6 @@ import com.google.common.annotations.VisibleForTesting; import java.io.IOException; import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -31,10 +30,6 @@ import org.apache.pinot.common.response.ProcessingException; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.common.Block; -import org.apache.pinot.core.common.BlockDocIdSet; -import org.apache.pinot.core.common.BlockDocIdValueSet; -import org.apache.pinot.core.common.BlockMetadata; -import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.query.request.context.QueryContext; @@ -161,26 +156,32 @@ public void setNumServerThreads(int numServerThreads) { /** * Returns the total size (number of rows) in this result block, without having to materialize the rows. * - * @see BaseResultsBlock#getRows(QueryContext) + * @see BaseResultsBlock#getRows() */ public abstract int getNumRows(); + /** + * Returns the query for the results. Return {@code null} when the block only contains metadata. + */ + @Nullable + public abstract QueryContext getQueryContext(); + /** * Returns the data schema for the results. Return {@code null} when the block only contains metadata. */ @Nullable - public abstract DataSchema getDataSchema(QueryContext queryContext); + public abstract DataSchema getDataSchema(); /** * Returns the rows for the results. Return {@code null} when the block only contains metadata. */ @Nullable - public abstract Collection getRows(QueryContext queryContext); + public abstract List getRows(); /** * Returns a data table without metadata or exception attached. */ - public abstract DataTable getDataTable(QueryContext queryContext) + public abstract DataTable getDataTable() throws IOException; /** @@ -199,24 +200,4 @@ public Map getResultsMetadata() { metadata.put(MetadataKey.NUM_CONSUMING_SEGMENTS_MATCHED.getName(), Integer.toString(_numConsumingSegmentsMatched)); return metadata; } - - @Override - public BlockDocIdSet getBlockDocIdSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockValSet getBlockValueSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockDocIdValueSet getBlockDocIdValueSet() { - throw new UnsupportedOperationException(); - } - - @Override - public BlockMetadata getMetadata() { - throw new UnsupportedOperationException(); - } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/DistinctResultsBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/DistinctResultsBlock.java index 99a0f868c6b7..3c791fba7ba8 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/DistinctResultsBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/DistinctResultsBlock.java @@ -25,7 +25,6 @@ import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.data.table.Record; -import org.apache.pinot.core.query.aggregation.function.DistinctAggregationFunction; import org.apache.pinot.core.query.distinct.DistinctTable; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.selection.SelectionOperatorUtils; @@ -35,12 +34,13 @@ * Results block for distinct queries. */ public class DistinctResultsBlock extends BaseResultsBlock { - private final DistinctAggregationFunction _distinctFunction; + private final QueryContext _queryContext; + private DistinctTable _distinctTable; - public DistinctResultsBlock(DistinctAggregationFunction distinctFunction, DistinctTable distinctTable) { - _distinctFunction = distinctFunction; + public DistinctResultsBlock(DistinctTable distinctTable, QueryContext queryContext) { _distinctTable = distinctTable; + _queryContext = queryContext; } public DistinctTable getDistinctTable() { @@ -57,12 +57,17 @@ public int getNumRows() { } @Override - public DataSchema getDataSchema(QueryContext queryContext) { + public QueryContext getQueryContext() { + return _queryContext; + } + + @Override + public DataSchema getDataSchema() { return _distinctTable.getDataSchema(); } @Override - public Collection getRows(QueryContext queryContext) { + public List getRows() { List rows = new ArrayList<>(_distinctTable.size()); for (Record record : _distinctTable.getRecords()) { rows.add(record.getValues()); @@ -71,10 +76,10 @@ public Collection getRows(QueryContext queryContext) { } @Override - public DataTable getDataTable(QueryContext queryContext) + public DataTable getDataTable() throws IOException { - Collection rows = getRows(queryContext); + Collection rows = getRows(); return SelectionOperatorUtils.getDataTableFromRows(rows, _distinctTable.getDataSchema(), - queryContext.isNullHandlingEnabled()); + _queryContext.isNullHandlingEnabled()); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ExceptionResultsBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ExceptionResultsBlock.java index 783c7c22a4ec..f148fd0d1737 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ExceptionResultsBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ExceptionResultsBlock.java @@ -18,7 +18,7 @@ */ package org.apache.pinot.core.operator.blocks.results; -import java.util.Collection; +import java.util.List; import javax.annotation.Nullable; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.exception.QueryException; @@ -26,6 +26,7 @@ import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.common.datatable.DataTableBuilderFactory; import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.spi.exception.QueryCancelledException; public class ExceptionResultsBlock extends BaseResultsBlock { @@ -38,6 +39,10 @@ public ExceptionResultsBlock(Throwable t) { this(QueryException.QUERY_EXECUTION_ERROR, t); } + public ExceptionResultsBlock(QueryCancelledException t) { + this(QueryException.QUERY_CANCELLATION_ERROR, t); + } + @Override public int getNumRows() { return 0; @@ -45,18 +50,24 @@ public int getNumRows() { @Nullable @Override - public DataSchema getDataSchema(QueryContext queryContext) { + public QueryContext getQueryContext() { + return null; + } + + @Nullable + @Override + public DataSchema getDataSchema() { return null; } @Nullable @Override - public Collection getRows(QueryContext queryContext) { + public List getRows() { return null; } @Override - public DataTable getDataTable(QueryContext queryContext) { + public DataTable getDataTable() { return DataTableBuilderFactory.getEmptyDataTable(); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ExplainResultsBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ExplainResultsBlock.java index bb3e71958be0..8947381acae4 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ExplainResultsBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ExplainResultsBlock.java @@ -20,10 +20,10 @@ import java.io.IOException; import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.annotation.Nullable; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.common.datatable.DataTableBuilder; @@ -35,24 +35,35 @@ * Results block for EXPLAIN queries. */ public class ExplainResultsBlock extends BaseResultsBlock { + private final QueryContext _queryContext; private final List _entries = new ArrayList<>(); + public ExplainResultsBlock(QueryContext queryContext) { + _queryContext = queryContext; + } + public void addOperator(String operatorName, int operatorId, int parentId) { _entries.add(new ExplainEntry(operatorName, operatorId, parentId)); } @Override - public DataSchema getDataSchema(QueryContext queryContext) { - return DataSchema.EXPLAIN_RESULT_SCHEMA; + public int getNumRows() { + return _entries.size(); } @Override - public int getNumRows() { - return _entries.size(); + public QueryContext getQueryContext() { + return _queryContext; + } + + @Nullable + @Override + public DataSchema getDataSchema() { + return DataSchema.EXPLAIN_RESULT_SCHEMA; } @Override - public Collection getRows(QueryContext queryContext) { + public List getRows() { List rows = new ArrayList<>(_entries.size()); for (ExplainEntry entry : _entries) { rows.add(entry.toRow()); @@ -61,7 +72,7 @@ public Collection getRows(QueryContext queryContext) { } @Override - public DataTable getDataTable(QueryContext queryContext) + public DataTable getDataTable() throws IOException { DataTableBuilder dataTableBuilder = DataTableBuilderFactory.getDataTableBuilder(DataSchema.EXPLAIN_RESULT_SCHEMA); for (ExplainEntry entry : _entries) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/GroupByResultsBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/GroupByResultsBlock.java index f431f4bd6f76..dfc5faa28930 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/GroupByResultsBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/GroupByResultsBlock.java @@ -19,6 +19,10 @@ package org.apache.pinot.core.operator.blocks.results; import it.unimi.dsi.fastutil.doubles.DoubleArrayList; +import it.unimi.dsi.fastutil.floats.FloatArrayList; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; @@ -27,9 +31,9 @@ import java.util.Iterator; import java.util.List; import java.util.Map; -import javax.annotation.Nullable; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.datatable.DataTable.MetadataKey; +import org.apache.pinot.common.utils.ArrayListUtils; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.common.datatable.DataTableBuilder; @@ -39,6 +43,7 @@ import org.apache.pinot.core.data.table.Table; import org.apache.pinot.core.query.aggregation.groupby.AggregationGroupByResult; import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.spi.trace.Tracing; import org.apache.pinot.spi.utils.ByteArray; import org.roaringbitmap.RoaringBitmap; @@ -51,6 +56,7 @@ public class GroupByResultsBlock extends BaseResultsBlock { private final AggregationGroupByResult _aggregationGroupByResult; private final Collection _intermediateRecords; private final Table _table; + private final QueryContext _queryContext; private boolean _numGroupsLimitReached; private int _numResizes; @@ -59,45 +65,47 @@ public class GroupByResultsBlock extends BaseResultsBlock { /** * For segment level group-by results. */ - public GroupByResultsBlock(DataSchema dataSchema, AggregationGroupByResult aggregationGroupByResult) { + public GroupByResultsBlock(DataSchema dataSchema, AggregationGroupByResult aggregationGroupByResult, + QueryContext queryContext) { _dataSchema = dataSchema; _aggregationGroupByResult = aggregationGroupByResult; _intermediateRecords = null; _table = null; + _queryContext = queryContext; } /** * For segment level group-by results. */ - public GroupByResultsBlock(DataSchema dataSchema, Collection intermediateRecords) { + public GroupByResultsBlock(DataSchema dataSchema, Collection intermediateRecords, + QueryContext queryContext) { _dataSchema = dataSchema; _aggregationGroupByResult = null; _intermediateRecords = intermediateRecords; _table = null; + _queryContext = queryContext; } /** * For instance level group-by results. */ - public GroupByResultsBlock(Table table) { + public GroupByResultsBlock(Table table, QueryContext queryContext) { _dataSchema = table.getDataSchema(); _aggregationGroupByResult = null; _intermediateRecords = null; _table = table; + _queryContext = queryContext; } /** * For instance level empty group-by results. */ - public GroupByResultsBlock(DataSchema dataSchema) { + public GroupByResultsBlock(DataSchema dataSchema, QueryContext queryContext) { _dataSchema = dataSchema; _aggregationGroupByResult = null; _intermediateRecords = null; _table = null; - } - - public DataSchema getDataSchema() { - return _dataSchema; + _queryContext = queryContext; } public AggregationGroupByResult getAggregationGroupByResult() { @@ -141,15 +149,18 @@ public int getNumRows() { return _table == null ? 0 : _table.size(); } - @Nullable @Override - public DataSchema getDataSchema(QueryContext queryContext) { + public QueryContext getQueryContext() { + return _queryContext; + } + + @Override + public DataSchema getDataSchema() { return _dataSchema; } - @Nullable @Override - public Collection getRows(QueryContext queryContext) { + public List getRows() { if (_table == null) { return Collections.emptyList(); } @@ -162,7 +173,7 @@ public Collection getRows(QueryContext queryContext) { } @Override - public DataTable getDataTable(QueryContext queryContext) + public DataTable getDataTable() throws IOException { DataTableBuilder dataTableBuilder = DataTableBuilderFactory.getDataTableBuilder(_dataSchema); if (_table == null) { @@ -171,7 +182,8 @@ public DataTable getDataTable(QueryContext queryContext) ColumnDataType[] storedColumnDataTypes = _dataSchema.getStoredColumnDataTypes(); int numColumns = _dataSchema.size(); Iterator iterator = _table.iterator(); - if (queryContext.isNullHandlingEnabled()) { + int numRowsAdded = 0; + if (_queryContext.isNullHandlingEnabled()) { RoaringBitmap[] nullBitmaps = new RoaringBitmap[numColumns]; Object[] nullPlaceholders = new Object[numColumns]; for (int colId = 0; colId < numColumns; colId++) { @@ -180,6 +192,7 @@ public DataTable getDataTable(QueryContext queryContext) } int rowId = 0; while (iterator.hasNext()) { + Tracing.ThreadAccountantOps.sampleAndCheckInterruptionPeriodically(numRowsAdded); dataTableBuilder.startRow(); Object[] values = iterator.next().getValues(); for (int colId = 0; colId < numColumns; colId++) { @@ -191,6 +204,7 @@ public DataTable getDataTable(QueryContext queryContext) setDataTableColumn(storedColumnDataTypes[colId], dataTableBuilder, colId, value); } dataTableBuilder.finishRow(); + numRowsAdded++; rowId++; } for (RoaringBitmap nullBitmap : nullBitmaps) { @@ -198,12 +212,14 @@ public DataTable getDataTable(QueryContext queryContext) } } else { while (iterator.hasNext()) { + Tracing.ThreadAccountantOps.sampleAndCheckInterruptionPeriodically(numRowsAdded); dataTableBuilder.startRow(); Object[] values = iterator.next().getValues(); for (int colId = 0; colId < numColumns; colId++) { setDataTableColumn(storedColumnDataTypes[colId], dataTableBuilder, colId, values[colId]); } dataTableBuilder.finishRow(); + numRowsAdded++; } } return dataTableBuilder.build(); @@ -235,23 +251,40 @@ private void setDataTableColumn(ColumnDataType storedColumnDataType, DataTableBu dataTableBuilder.setColumn(columnIndex, (ByteArray) value); break; case INT_ARRAY: - dataTableBuilder.setColumn(columnIndex, (int[]) value); + if (value instanceof IntArrayList) { + dataTableBuilder.setColumn(columnIndex, ArrayListUtils.toIntArray((IntArrayList) value)); + } else { + dataTableBuilder.setColumn(columnIndex, (int[]) value); + } break; case LONG_ARRAY: - dataTableBuilder.setColumn(columnIndex, (long[]) value); + if (value instanceof LongArrayList) { + dataTableBuilder.setColumn(columnIndex, ArrayListUtils.toLongArray((LongArrayList) value)); + } else { + dataTableBuilder.setColumn(columnIndex, (long[]) value); + } break; case FLOAT_ARRAY: - dataTableBuilder.setColumn(columnIndex, (float[]) value); + if (value instanceof FloatArrayList) { + dataTableBuilder.setColumn(columnIndex, ArrayListUtils.toFloatArray((FloatArrayList) value)); + } else { + dataTableBuilder.setColumn(columnIndex, (float[]) value); + } break; case DOUBLE_ARRAY: if (value instanceof DoubleArrayList) { - dataTableBuilder.setColumn(columnIndex, ((DoubleArrayList) value).elements()); + dataTableBuilder.setColumn(columnIndex, ArrayListUtils.toDoubleArray((DoubleArrayList) value)); } else { dataTableBuilder.setColumn(columnIndex, (double[]) value); } break; case STRING_ARRAY: - dataTableBuilder.setColumn(columnIndex, (String[]) value); + if (value instanceof ObjectArrayList) { + //noinspection unchecked + dataTableBuilder.setColumn(columnIndex, ArrayListUtils.toStringArray((ObjectArrayList) value)); + } else { + dataTableBuilder.setColumn(columnIndex, (String[]) value); + } break; case OBJECT: dataTableBuilder.setColumn(columnIndex, value); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/MetadataResultsBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/MetadataResultsBlock.java index 3ef747d2a142..37a4383fa30b 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/MetadataResultsBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/MetadataResultsBlock.java @@ -18,7 +18,7 @@ */ package org.apache.pinot.core.operator.blocks.results; -import java.util.Collection; +import java.util.List; import javax.annotation.Nullable; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.utils.DataSchema; @@ -35,18 +35,24 @@ public int getNumRows() { @Nullable @Override - public DataSchema getDataSchema(QueryContext queryContext) { + public QueryContext getQueryContext() { return null; } @Nullable @Override - public Collection getRows(QueryContext queryContext) { + public DataSchema getDataSchema() { + return null; + } + + @Nullable + @Override + public List getRows() { return null; } @Override - public DataTable getDataTable(QueryContext queryContext) { + public DataTable getDataTable() { return DataTableBuilderFactory.getEmptyDataTable(); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ResultsBlockUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ResultsBlockUtils.java index 5f5e7d0769f9..9775d04d1cb1 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ResultsBlockUtils.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/ResultsBlockUtils.java @@ -22,11 +22,13 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.apache.commons.lang3.tuple.Pair; import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.request.context.FilterContext; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.query.aggregation.function.AggregationFunction; -import org.apache.pinot.core.query.aggregation.function.DistinctAggregationFunction; +import org.apache.pinot.core.query.aggregation.function.AggregationFunctionUtils; import org.apache.pinot.core.query.distinct.DistinctTable; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.request.context.utils.QueryContextUtils; @@ -40,16 +42,16 @@ private ResultsBlockUtils() { public static BaseResultsBlock buildEmptyQueryResults(QueryContext queryContext) { if (QueryContextUtils.isSelectionQuery(queryContext)) { return buildEmptySelectionQueryResults(queryContext); - } else if (QueryContextUtils.isAggregationQuery(queryContext)) { + } + if (QueryContextUtils.isAggregationQuery(queryContext)) { if (queryContext.getGroupByExpressions() == null) { return buildEmptyAggregationQueryResults(queryContext); } else { return buildEmptyGroupByQueryResults(queryContext); } - } else { - assert QueryContextUtils.isDistinctQuery(queryContext); - return buildEmptyDistinctQueryResults(queryContext); } + assert QueryContextUtils.isDistinctQuery(queryContext); + return buildEmptyDistinctQueryResults(queryContext); } private static SelectionResultsBlock buildEmptySelectionQueryResults(QueryContext queryContext) { @@ -63,7 +65,7 @@ private static SelectionResultsBlock buildEmptySelectionQueryResults(QueryContex // NOTE: Use STRING column data type as default for selection query Arrays.fill(columnDataTypes, ColumnDataType.STRING); DataSchema dataSchema = new DataSchema(columnNames, columnDataTypes); - return new SelectionResultsBlock(dataSchema, Collections.emptyList()); + return new SelectionResultsBlock(dataSchema, Collections.emptyList(), queryContext); } private static AggregationResultsBlock buildEmptyAggregationQueryResults(QueryContext queryContext) { @@ -74,16 +76,15 @@ private static AggregationResultsBlock buildEmptyAggregationQueryResults(QueryCo for (AggregationFunction aggregationFunction : aggregationFunctions) { results.add(aggregationFunction.extractAggregationResult(aggregationFunction.createAggregationResultHolder())); } - return new AggregationResultsBlock(aggregationFunctions, results); + return new AggregationResultsBlock(aggregationFunctions, results, queryContext); } private static GroupByResultsBlock buildEmptyGroupByQueryResults(QueryContext queryContext) { - AggregationFunction[] aggregationFunctions = queryContext.getAggregationFunctions(); - assert aggregationFunctions != null; - int numAggregations = aggregationFunctions.length; + List> filteredAggregationFunctions = + queryContext.getFilteredAggregationFunctions(); List groupByExpressions = queryContext.getGroupByExpressions(); - assert groupByExpressions != null; - int numColumns = groupByExpressions.size() + numAggregations; + assert filteredAggregationFunctions != null && groupByExpressions != null; + int numColumns = groupByExpressions.size() + filteredAggregationFunctions.size(); String[] columnNames = new String[numColumns]; ColumnDataType[] columnDataTypes = new ColumnDataType[numColumns]; int index = 0; @@ -93,27 +94,27 @@ private static GroupByResultsBlock buildEmptyGroupByQueryResults(QueryContext qu columnDataTypes[index] = ColumnDataType.STRING; index++; } - for (AggregationFunction aggregationFunction : aggregationFunctions) { - // NOTE: Use AggregationFunction.getResultColumnName() for SQL format response - columnNames[index] = aggregationFunction.getResultColumnName(); + for (Pair pair : filteredAggregationFunctions) { + AggregationFunction aggregationFunction = pair.getLeft(); + columnNames[index] = AggregationFunctionUtils.getResultColumnName(aggregationFunction, pair.getRight()); columnDataTypes[index] = aggregationFunction.getIntermediateResultColumnType(); index++; } - return new GroupByResultsBlock(new DataSchema(columnNames, columnDataTypes)); + return new GroupByResultsBlock(new DataSchema(columnNames, columnDataTypes), queryContext); } private static DistinctResultsBlock buildEmptyDistinctQueryResults(QueryContext queryContext) { - AggregationFunction[] aggregationFunctions = queryContext.getAggregationFunctions(); - assert aggregationFunctions != null && aggregationFunctions.length == 1 - && aggregationFunctions[0] instanceof DistinctAggregationFunction; - DistinctAggregationFunction distinctAggregationFunction = (DistinctAggregationFunction) aggregationFunctions[0]; - String[] columnNames = distinctAggregationFunction.getColumns(); - ColumnDataType[] columnDataTypes = new ColumnDataType[columnNames.length]; + List expressions = queryContext.getSelectExpressions(); + int numExpressions = expressions.size(); + String[] columns = new String[numExpressions]; + for (int i = 0; i < numExpressions; i++) { + columns[i] = expressions.get(i).toString(); + } + ColumnDataType[] columnDataTypes = new ColumnDataType[numExpressions]; // NOTE: Use STRING column data type as default for distinct query Arrays.fill(columnDataTypes, ColumnDataType.STRING); - DistinctTable distinctTable = - new DistinctTable(new DataSchema(columnNames, columnDataTypes), Collections.emptySet(), - queryContext.isNullHandlingEnabled()); - return new DistinctResultsBlock(distinctAggregationFunction, distinctTable); + DistinctTable distinctTable = new DistinctTable(new DataSchema(columns, columnDataTypes), Collections.emptySet(), + queryContext.isNullHandlingEnabled()); + return new DistinctResultsBlock(distinctTable, queryContext); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/SelectionResultsBlock.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/SelectionResultsBlock.java index 363cd1a4f5a0..665d03a02990 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/SelectionResultsBlock.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/blocks/results/SelectionResultsBlock.java @@ -18,12 +18,9 @@ */ package org.apache.pinot.core.operator.blocks.results; -import com.google.common.base.Preconditions; import java.io.IOException; -import java.util.Collection; import java.util.Comparator; import java.util.List; -import java.util.PriorityQueue; import javax.annotation.Nullable; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.utils.DataSchema; @@ -36,30 +33,29 @@ */ public class SelectionResultsBlock extends BaseResultsBlock { private final DataSchema _dataSchema; - private final Collection _rows; private final Comparator _comparator; + private final QueryContext _queryContext; + private List _rows; - public SelectionResultsBlock(DataSchema dataSchema, List rows) { - this(dataSchema, rows, null); - } - - public SelectionResultsBlock(DataSchema dataSchema, PriorityQueue rows) { - this(dataSchema, rows, rows.comparator()); - } - - public SelectionResultsBlock(DataSchema dataSchema, Collection rows, - @Nullable Comparator comparator) { + public SelectionResultsBlock(DataSchema dataSchema, List rows, + @Nullable Comparator comparator, QueryContext queryContext) { _dataSchema = dataSchema; _rows = rows; _comparator = comparator; + _queryContext = queryContext; } - public DataSchema getDataSchema() { - return _dataSchema; + public SelectionResultsBlock(DataSchema dataSchema, List rows, QueryContext queryContext) { + this(dataSchema, rows, null, queryContext); } - public Collection getRows() { - return _rows; + public void setRows(List rows) { + _rows = rows; + } + + @Nullable + public Comparator getComparator() { + return _comparator; } @Override @@ -68,33 +64,23 @@ public int getNumRows() { } @Override - public DataSchema getDataSchema(QueryContext queryContext) { - return _dataSchema; + public QueryContext getQueryContext() { + return _queryContext; } @Override - public Collection getRows(QueryContext queryContext) { - return _rows; - } - - public SelectionResultsBlock convertToPriorityQueueBased() { - if (_rows instanceof PriorityQueue) { - return this; - } - Preconditions.checkState(_comparator != null, "No comparator specified in the results block"); - PriorityQueue result = new PriorityQueue<>(_comparator); - result.addAll(_rows); - return new SelectionResultsBlock(_dataSchema, result); + public DataSchema getDataSchema() { + return _dataSchema; } - public PriorityQueue getRowsAsPriorityQueue() { - Preconditions.checkState(_rows instanceof PriorityQueue, "The results block is not backed by a priority queue"); - return (PriorityQueue) _rows; + @Override + public List getRows() { + return _rows; } @Override - public DataTable getDataTable(QueryContext queryContext) + public DataTable getDataTable() throws IOException { - return SelectionOperatorUtils.getDataTableFromRows(_rows, _dataSchema, queryContext.isNullHandlingEnabled()); + return SelectionOperatorUtils.getDataTableFromRows(_rows, _dataSchema, _queryContext.isNullHandlingEnabled()); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/AggregationCombineOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/AggregationCombineOperator.java index 6dbb6fd839b3..e7f888ef0064 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/AggregationCombineOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/AggregationCombineOperator.java @@ -22,37 +22,24 @@ import java.util.concurrent.ExecutorService; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.blocks.results.AggregationResultsBlock; -import org.apache.pinot.core.query.aggregation.function.AggregationFunction; +import org.apache.pinot.core.operator.combine.merger.AggregationResultsBlockMerger; import org.apache.pinot.core.query.request.context.QueryContext; /** * Combine operator for aggregation queries. */ -@SuppressWarnings({"rawtypes", "unchecked"}) -public class AggregationCombineOperator extends BaseCombineOperator { +@SuppressWarnings({"rawtypes"}) +public class AggregationCombineOperator extends BaseSingleBlockCombineOperator { private static final String EXPLAIN_NAME = "COMBINE_AGGREGATE"; public AggregationCombineOperator(List operators, QueryContext queryContext, ExecutorService executorService) { - super(operators, queryContext, executorService); + super(new AggregationResultsBlockMerger(queryContext), operators, queryContext, executorService); } @Override public String toExplainString() { return EXPLAIN_NAME; } - - @Override - protected void mergeResultsBlocks(AggregationResultsBlock mergedBlock, AggregationResultsBlock blockToMerge) { - AggregationFunction[] aggregationFunctions = mergedBlock.getAggregationFunctions(); - List mergedResults = mergedBlock.getResults(); - List resultsToMerge = blockToMerge.getResults(); - assert aggregationFunctions != null && mergedResults != null && resultsToMerge != null; - - int numAggregationFunctions = aggregationFunctions.length; - for (int i = 0; i < numAggregationFunctions; i++) { - mergedResults.set(i, aggregationFunctions[i].merge(mergedResults.get(i), resultsToMerge.get(i))); - } - } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/BaseCombineOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/BaseCombineOperator.java index 49e7502aa2f9..b892c2185fa5 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/BaseCombineOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/BaseCombineOperator.java @@ -19,29 +19,25 @@ package org.apache.pinot.core.operator.combine; import java.util.List; -import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; -import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.Phaser; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; -import org.apache.commons.lang.StringUtils; +import java.util.concurrent.atomic.AtomicReference; import org.apache.pinot.common.exception.QueryException; import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.AcquireReleaseColumnsSegmentOperator; import org.apache.pinot.core.operator.BaseOperator; import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock; import org.apache.pinot.core.operator.blocks.results.ExceptionResultsBlock; +import org.apache.pinot.core.operator.combine.merger.ResultsBlockMerger; import org.apache.pinot.core.query.request.context.QueryContext; -import org.apache.pinot.core.query.scheduler.resources.ResourceManager; +import org.apache.pinot.core.util.QueryMultiThreadingUtils; import org.apache.pinot.core.util.trace.TraceRunnable; import org.apache.pinot.spi.accounting.ThreadExecutionContext; import org.apache.pinot.spi.accounting.ThreadResourceUsageProvider; import org.apache.pinot.spi.exception.EarlyTerminationException; -import org.apache.pinot.spi.exception.QueryCancelledException; import org.apache.pinot.spi.trace.Tracing; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,23 +49,29 @@ * the results blocks from the processed segments. It can early-terminate the query to save the system resources if it * detects that the merged results can already satisfy the query, or the query is already errored out or timed out. */ -@SuppressWarnings({"rawtypes", "unchecked"}) +@SuppressWarnings({"rawtypes"}) public abstract class BaseCombineOperator extends BaseOperator { private static final Logger LOGGER = LoggerFactory.getLogger(BaseCombineOperator.class); + protected final ResultsBlockMerger _resultsBlockMerger; protected final List _operators; protected final int _numOperators; protected final QueryContext _queryContext; protected final ExecutorService _executorService; protected final int _numTasks; + protected final Phaser _phaser; protected final Future[] _futures; + // Use an AtomicInteger to track the next operator to execute protected final AtomicInteger _nextOperatorId = new AtomicInteger(); - // Use a BlockingQueue to store the intermediate results blocks - protected final BlockingQueue _blockingQueue = new LinkedBlockingQueue<>(); + // Use an AtomicReference to track the exception/error during segment processing + protected final AtomicReference _processingException = new AtomicReference<>(); + protected final AtomicLong _totalWorkerThreadCpuTimeNs = new AtomicLong(0); - protected BaseCombineOperator(List operators, QueryContext queryContext, ExecutorService executorService) { + protected BaseCombineOperator(ResultsBlockMerger resultsBlockMerger, List operators, + QueryContext queryContext, ExecutorService executorService) { + _resultsBlockMerger = resultsBlockMerger; _operators = operators; _numOperators = _operators.size(); _queryContext = queryContext; @@ -78,17 +80,20 @@ protected BaseCombineOperator(List operators, QueryContext queryContex // NOTE: We split the query execution into multiple tasks, where each task handles the query execution on multiple // (>=1) segments. These tasks are assigned to multiple execution threads so that they can run in parallel. // The parallelism is bounded by the task count. - _numTasks = CombineOperatorUtils.getNumTasksForQuery(operators.size(), queryContext.getMaxExecutionThreads()); - _futures = new Future[_numTasks]; - } + _numTasks = QueryMultiThreadingUtils.getNumTasksForQuery(operators.size(), queryContext.getMaxExecutionThreads()); - @Override - protected BaseResultsBlock getNextBlock() { // Use a Phaser to ensure all the Futures are done (not scheduled, finished or interrupted) before the main thread // returns. We need to ensure this because the main thread holds the reference to the segments. If a segment is // deleted/refreshed, the segment will be released after the main thread returns, which would lead to undefined // behavior (even JVM crash) when processing queries against it. - Phaser phaser = new Phaser(1); + _phaser = new Phaser(1); + _futures = new Future[_numTasks]; + } + + /** + * Start the combine operator process. This will spin up multiple threads to process data segments in parallel. + */ + protected void startProcess() { Tracing.activeRecording().setNumTasks(_numTasks); ThreadExecutionContext parentContext = Tracing.getThreadAccountant().getThreadExecutionContext(); for (int i = 0; i < _numTasks; i++) { @@ -104,7 +109,7 @@ public void runJob() { // NOTE: If the phaser is terminated (returning negative value) when trying to register the task, that means // the query execution has finished, and the main thread has deregistered itself and returned the // result. Directly return as no execution result will be taken. - if (phaser.register() < 0) { + if (_phaser.register() < 0) { Tracing.ThreadAccountantOps.clear(); return; } @@ -118,14 +123,14 @@ public void runJob() { // exception into the query response, and the main thread might wait infinitely (until timeout) or // throw unexpected exceptions (such as NPE). if (t instanceof Exception) { - LOGGER.error("Caught exception while processing query: " + _queryContext, t); + LOGGER.error("Caught exception while processing query: {}", _queryContext, t); } else { - LOGGER.error("Caught serious error while processing query: " + _queryContext, t); + LOGGER.error("Caught serious error while processing query: {}", _queryContext, t); } - onException(t); + onProcessSegmentsException(t); } finally { - onFinish(); - phaser.arriveAndDeregister(); + onProcessSegmentsFinish(); + _phaser.arriveAndDeregister(); Tracing.ThreadAccountantOps.clear(); } @@ -133,155 +138,46 @@ public void runJob() { } }); } - - BaseResultsBlock mergedBlock; - try { - mergedBlock = mergeResults(); - } catch (InterruptedException | EarlyTerminationException e) { - Exception killedErrorMsg = Tracing.getThreadAccountant().getErrorStatus(); - throw new QueryCancelledException( - "Cancelled while merging results blocks" - + (killedErrorMsg == null ? StringUtils.EMPTY : " " + killedErrorMsg), e); - } catch (Exception e) { - LOGGER.error("Caught exception while merging results blocks (query: {})", _queryContext, e); - mergedBlock = new ExceptionResultsBlock(QueryException.getException(QueryException.INTERNAL_ERROR, e)); - } finally { - // Cancel all ongoing jobs - for (Future future : _futures) { - if (!future.isDone()) { - future.cancel(true); - } - } - // Deregister the main thread and wait for all threads done - phaser.awaitAdvance(phaser.arriveAndDeregister()); - } - /* - * _numTasks are number of async tasks submitted to the _executorService, but it does not mean Pinot server - * use those number of threads to concurrently process segments. Instead, if _executorService thread pool has - * less number of threads than _numTasks, the number of threads that used to concurrently process segments equals - * to the pool size. - * TODO: Get the actual number of query worker threads instead of using the default value. - */ - int numServerThreads = Math.min(_numTasks, ResourceManager.DEFAULT_QUERY_WORKER_THREADS); - CombineOperatorUtils.setExecutionStatistics(mergedBlock, _operators, _totalWorkerThreadCpuTimeNs.get(), - numServerThreads); - return mergedBlock; - } - - /** - * Executes query on one or more segments in a worker thread. - */ - protected void processSegments() { - int operatorId; - while ((operatorId = _nextOperatorId.getAndIncrement()) < _numOperators) { - Operator operator = _operators.get(operatorId); - T resultsBlock; - try { - if (operator instanceof AcquireReleaseColumnsSegmentOperator) { - ((AcquireReleaseColumnsSegmentOperator) operator).acquire(); - } - resultsBlock = (T) operator.nextBlock(); - } finally { - if (operator instanceof AcquireReleaseColumnsSegmentOperator) { - ((AcquireReleaseColumnsSegmentOperator) operator).release(); - } - } - - if (isQuerySatisfied(resultsBlock)) { - // Query is satisfied, skip processing the remaining segments - _blockingQueue.offer(resultsBlock); - return; - } else { - _blockingQueue.offer(resultsBlock); - } - } - } - - /** - * Invoked when {@link #processSegments()} throws exception/error. - */ - protected void onException(Throwable t) { - _blockingQueue.offer(new ExceptionResultsBlock(t)); - } - - /** - * Invoked when {@link #processSegments()} is finished (called in the finally block). - */ - protected void onFinish() { } /** - * Merges the results from the worker threads into a results block. + * Stop the combine operator process. This will stop all sub-tasks that were spun up to process data segments. */ - protected BaseResultsBlock mergeResults() - throws Exception { - T mergedBlock = null; - int numBlocksMerged = 0; - long endTimeMs = _queryContext.getEndTimeMs(); - while (numBlocksMerged < _numOperators) { - // Timeout has reached, shouldn't continue to process. `_blockingQueue.poll` will continue to return blocks even - // if negative timeout is provided; therefore an extra check is needed - long waitTimeMs = endTimeMs - System.currentTimeMillis(); - if (waitTimeMs <= 0) { - return getTimeoutResultsBlock(numBlocksMerged); - } - BaseResultsBlock blockToMerge = _blockingQueue.poll(waitTimeMs, TimeUnit.MILLISECONDS); - if (blockToMerge == null) { - return getTimeoutResultsBlock(numBlocksMerged); - } - if (blockToMerge.getProcessingExceptions() != null) { - // Caught exception while processing segment, skip merging the remaining results blocks and directly return the - // exception - return blockToMerge; - } - if (mergedBlock == null) { - mergedBlock = convertToMergeableBlock((T) blockToMerge); - } else { - mergeResultsBlocks(mergedBlock, (T) blockToMerge); - } - numBlocksMerged++; - if (isQuerySatisfied(mergedBlock)) { - // Query is satisfied, skip merging the remaining results blocks - return mergedBlock; + protected void stopProcess() { + // Cancel all ongoing jobs + for (Future future : _futures) { + if (future != null && !future.isDone()) { + future.cancel(true); } } - return mergedBlock; + // Deregister the main thread and wait for all threads done + _phaser.awaitAdvance(_phaser.arriveAndDeregister()); } - private ExceptionResultsBlock getTimeoutResultsBlock(int numBlocksMerged) { + protected ExceptionResultsBlock getTimeoutResultsBlock(int numBlocksMerged) { LOGGER.error("Timed out while polling results block, numBlocksMerged: {} (query: {})", numBlocksMerged, _queryContext); return new ExceptionResultsBlock(QueryException.EXECUTION_TIMEOUT_ERROR, new TimeoutException("Timed out while polling results block")); } - /** - * Can be overridden for early termination. The input results block might not be mergeable. - */ - protected boolean isQuerySatisfied(T resultsBlock) { - return false; + @Override + public List getChildOperators() { + return _operators; } /** - * Merges a results block into the main mergeable results block. - *

NOTE: {@code blockToMerge} should contain the result for a segment without any exception. The errored segment - * result is already handled. - * - * @param mergedBlock The block that accumulates previous results. It should be modified to add the information of the - * other block. - * @param blockToMerge The new block that needs to be merged into the mergedBlock. + * Executes query on one or more segments in a worker thread. */ - protected abstract void mergeResultsBlocks(T mergedBlock, T blockToMerge); + protected abstract void processSegments(); /** - * Converts the given results block into a mergeable results block if necessary. + * Invoked when {@link #processSegments()} throws exception/error. */ - protected T convertToMergeableBlock(T resultsBlock) { - return resultsBlock; - } + protected abstract void onProcessSegmentsException(Throwable t); - @Override - public List getChildOperators() { - return _operators; - } + /** + * Invoked when {@link #processSegments()} is finished (called in the finally block). + */ + protected abstract void onProcessSegmentsFinish(); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/BaseSingleBlockCombineOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/BaseSingleBlockCombineOperator.java new file mode 100644 index 000000000000..4d6b27899cf7 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/BaseSingleBlockCombineOperator.java @@ -0,0 +1,156 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.combine; + +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import org.apache.pinot.common.exception.QueryException; +import org.apache.pinot.core.common.Operator; +import org.apache.pinot.core.operator.AcquireReleaseColumnsSegmentOperator; +import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock; +import org.apache.pinot.core.operator.blocks.results.ExceptionResultsBlock; +import org.apache.pinot.core.operator.combine.merger.ResultsBlockMerger; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.query.scheduler.resources.ResourceManager; +import org.apache.pinot.spi.exception.EarlyTerminationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Base implementation of the combine operator. + *

Combine operator uses multiple worker threads to process segments in parallel, and uses the main thread to merge + * the results blocks from the processed segments. It can early-terminate the query to save the system resources if it + * detects that the merged results can already satisfy the query, or the query is already errored out or timed out. + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public abstract class BaseSingleBlockCombineOperator extends BaseCombineOperator { + private static final Logger LOGGER = LoggerFactory.getLogger(BaseSingleBlockCombineOperator.class); + + // Use an unlimited BlockingQueue to store the intermediate results blocks + protected final BlockingQueue _blockingQueue = new LinkedBlockingQueue<>(); + + protected BaseSingleBlockCombineOperator(ResultsBlockMerger resultsBlockMerger, List operators, + QueryContext queryContext, ExecutorService executorService) { + super(resultsBlockMerger, operators, queryContext, executorService); + } + + @Override + protected BaseResultsBlock getNextBlock() { + BaseResultsBlock mergedBlock; + try { + startProcess(); + mergedBlock = mergeResults(); + } catch (InterruptedException e) { + throw new EarlyTerminationException("Interrupted while merging results blocks", e); + } catch (Exception e) { + LOGGER.error("Caught exception while merging results blocks (query: {})", _queryContext, e); + mergedBlock = new ExceptionResultsBlock(QueryException.getException(QueryException.INTERNAL_ERROR, e)); + } finally { + stopProcess(); + } + /* + * _numTasks are number of async tasks submitted to the _executorService, but it does not mean Pinot server + * use those number of threads to concurrently process segments. Instead, if _executorService thread pool has + * less number of threads than _numTasks, the number of threads that used to concurrently process segments equals + * to the pool size. + * TODO: Get the actual number of query worker threads instead of using the default value. + */ + int numServerThreads = Math.min(_numTasks, ResourceManager.DEFAULT_QUERY_WORKER_THREADS); + CombineOperatorUtils.setExecutionStatistics(mergedBlock, _operators, _totalWorkerThreadCpuTimeNs.get(), + numServerThreads); + return mergedBlock; + } + + @Override + protected void processSegments() { + int operatorId; + while (_processingException.get() == null && (operatorId = _nextOperatorId.getAndIncrement()) < _numOperators) { + Operator operator = _operators.get(operatorId); + T resultsBlock; + try { + if (operator instanceof AcquireReleaseColumnsSegmentOperator) { + ((AcquireReleaseColumnsSegmentOperator) operator).acquire(); + } + resultsBlock = (T) operator.nextBlock(); + } finally { + if (operator instanceof AcquireReleaseColumnsSegmentOperator) { + ((AcquireReleaseColumnsSegmentOperator) operator).release(); + } + } + _blockingQueue.offer(resultsBlock); + // When query is satisfied, skip processing the remaining segments + if (_resultsBlockMerger.isQuerySatisfied(resultsBlock)) { + _nextOperatorId.set(_numOperators); + return; + } + } + } + + @Override + protected void onProcessSegmentsException(Throwable t) { + _processingException.compareAndSet(null, t); + _blockingQueue.offer(new ExceptionResultsBlock(t)); + } + + @Override + protected void onProcessSegmentsFinish() { + } + + /** + * Merges the results from the worker threads into a results block. + */ + protected BaseResultsBlock mergeResults() + throws Exception { + T mergedBlock = null; + int numBlocksMerged = 0; + long endTimeMs = _queryContext.getEndTimeMs(); + while (numBlocksMerged < _numOperators) { + // Timeout has reached, shouldn't continue to process. `_blockingQueue.poll` will continue to return blocks even + // if negative timeout is provided; therefore an extra check is needed + long waitTimeMs = endTimeMs - System.currentTimeMillis(); + if (waitTimeMs <= 0) { + return getTimeoutResultsBlock(numBlocksMerged); + } + BaseResultsBlock blockToMerge = _blockingQueue.poll(waitTimeMs, TimeUnit.MILLISECONDS); + if (blockToMerge == null) { + return getTimeoutResultsBlock(numBlocksMerged); + } + if (blockToMerge.getProcessingExceptions() != null) { + // Caught exception while processing segment, skip merging the remaining results blocks and directly return the + // exception + return blockToMerge; + } + if (mergedBlock == null) { + mergedBlock = (T) blockToMerge; + } else { + _resultsBlockMerger.mergeResultsBlocks(mergedBlock, (T) blockToMerge); + } + numBlocksMerged++; + if (_resultsBlockMerger.isQuerySatisfied(mergedBlock)) { + // Query is satisfied, skip merging the remaining results blocks + return mergedBlock; + } + } + return mergedBlock; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/CombineOperatorUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/CombineOperatorUtils.java index 842f5053f796..4715d26a7798 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/CombineOperatorUtils.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/CombineOperatorUtils.java @@ -30,26 +30,6 @@ public class CombineOperatorUtils { private CombineOperatorUtils() { } - /** - * Use at most 10 or half of the processors threads for each query. If there are less than 2 processors, use 1 thread. - *

NOTE: Runtime.getRuntime().availableProcessors() may return value < 2 in container based environment, e.g. - * Kubernetes. - */ - public static final int MAX_NUM_THREADS_PER_QUERY = - Math.max(1, Math.min(10, Runtime.getRuntime().availableProcessors() / 2)); - - /** - * Returns the number of tasks for the query execution. The tasks can be assigned to multiple execution threads so - * that they can run in parallel. The parallelism is bounded by the task count. - */ - public static int getNumTasksForQuery(int numOperators, int maxExecutionThreads) { - if (maxExecutionThreads > 0) { - return Math.min(numOperators, maxExecutionThreads); - } else { - return Math.min(numOperators, MAX_NUM_THREADS_PER_QUERY); - } - } - /** * Sets the execution statistics into the results block. */ @@ -68,15 +48,13 @@ public static void setExecutionStatistics(BaseResultsBlock resultsBlock, List 0) { numSegmentsMatched++; } - // TODO: Check all operators and properly implement the getIndexSegment and remove this exception handling - try { - if (operator.getIndexSegment() instanceof MutableSegment) { - numConsumingSegmentsProcessed += 1; - if (executionStatistics.getNumDocsScanned() > 0) { - numConsumingSegmentsMatched++; - } + + // TODO: Check all operators and properly implement the getIndexSegment. + if (operator.getIndexSegment() instanceof MutableSegment) { + numConsumingSegmentsProcessed += 1; + if (executionStatistics.getNumDocsScanned() > 0) { + numConsumingSegmentsMatched++; } - } catch (UnsupportedOperationException ignored) { } numDocsScanned += executionStatistics.getNumDocsScanned(); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/DistinctCombineOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/DistinctCombineOperator.java index 2014350359da..6d3bb77a2dfb 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/DistinctCombineOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/DistinctCombineOperator.java @@ -22,7 +22,7 @@ import java.util.concurrent.ExecutorService; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.blocks.results.DistinctResultsBlock; -import org.apache.pinot.core.query.distinct.DistinctTable; +import org.apache.pinot.core.operator.combine.merger.DistinctResultsBlockMerger; import org.apache.pinot.core.query.request.context.QueryContext; @@ -30,45 +30,15 @@ * Combine operator for distinct queries. */ @SuppressWarnings("rawtypes") -public class DistinctCombineOperator extends BaseCombineOperator { +public class DistinctCombineOperator extends BaseSingleBlockCombineOperator { private static final String EXPLAIN_NAME = "COMBINE_DISTINCT"; - private final boolean _hasOrderBy; - public DistinctCombineOperator(List operators, QueryContext queryContext, ExecutorService executorService) { - super(operators, queryContext, executorService); - _hasOrderBy = queryContext.getOrderByExpressions() != null; + super(new DistinctResultsBlockMerger(queryContext), operators, queryContext, executorService); } @Override public String toExplainString() { return EXPLAIN_NAME; } - - @Override - protected boolean isQuerySatisfied(DistinctResultsBlock resultsBlock) { - if (_hasOrderBy) { - return false; - } - return resultsBlock.getDistinctTable().size() >= _queryContext.getLimit(); - } - - @Override - protected void mergeResultsBlocks(DistinctResultsBlock mergedBlock, DistinctResultsBlock blockToMerge) { - DistinctTable mergedDistinctTable = mergedBlock.getDistinctTable(); - DistinctTable distinctTableToMerge = blockToMerge.getDistinctTable(); - assert mergedDistinctTable != null && distinctTableToMerge != null; - - // Convert the merged table into a main table if necessary in order to merge other tables - if (!mergedDistinctTable.isMainTable()) { - DistinctTable mainDistinctTable = - new DistinctTable(distinctTableToMerge.getDataSchema(), _queryContext.getOrderByExpressions(), - _queryContext.getLimit(), _queryContext.isNullHandlingEnabled()); - mainDistinctTable.mergeTable(mergedDistinctTable); - mergedBlock.setDistinctTable(mainDistinctTable); - mergedDistinctTable = mainDistinctTable; - } - - mergedDistinctTable.mergeTable(distinctTableToMerge); - } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/GroupByCombineOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/GroupByCombineOperator.java index 643962dc9d70..5faf3bf9744f 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/GroupByCombineOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/GroupByCombineOperator.java @@ -18,18 +18,14 @@ */ package org.apache.pinot.core.operator.combine; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import org.apache.pinot.common.exception.QueryException; -import org.apache.pinot.common.response.ProcessingException; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.data.table.ConcurrentIndexedTable; @@ -47,7 +43,7 @@ import org.apache.pinot.core.query.aggregation.groupby.GroupKeyGenerator; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.util.GroupByUtils; -import org.apache.pinot.spi.utils.LoopUtils; +import org.apache.pinot.spi.trace.Tracing; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,9 +54,8 @@ * all threads */ @SuppressWarnings("rawtypes") -public class GroupByCombineOperator extends BaseCombineOperator { +public class GroupByCombineOperator extends BaseSingleBlockCombineOperator { public static final int MAX_TRIM_THRESHOLD = 1_000_000_000; - public static final int MAX_GROUP_BY_KEYS_MERGED_PER_INTERRUPTION_CHECK = 10_000; private static final Logger LOGGER = LoggerFactory.getLogger(GroupByCombineOperator.class); private static final String EXPLAIN_NAME = "COMBINE_GROUP_BY"; @@ -70,7 +65,6 @@ public class GroupByCombineOperator extends BaseCombineOperator _mergedProcessingExceptions = new ConcurrentLinkedQueue<>(); // We use a CountDownLatch to track if all Futures are finished by the query timeout, and cancel the unfinished // _futures (try to interrupt the execution if it already started). private final CountDownLatch _operatorLatch; @@ -79,7 +73,7 @@ public class GroupByCombineOperator extends BaseCombineOperator operators, QueryContext queryContext, ExecutorService executorService) { - super(operators, overrideMaxExecutionThreads(queryContext, operators.size()), executorService); + super(null, operators, overrideMaxExecutionThreads(queryContext, operators.size()), executorService); int minTrimSize = queryContext.getMinServerGroupTrimSize(); if (minTrimSize > 0) { @@ -130,7 +124,7 @@ public String toExplainString() { @Override protected void processSegments() { int operatorId; - while ((operatorId = _nextOperatorId.getAndIncrement()) < _numOperators) { + while (_processingException.get() == null && (operatorId = _nextOperatorId.getAndIncrement()) < _numOperators) { Operator operator = _operators.get(operatorId); try { if (operator instanceof AcquireReleaseColumnsSegmentOperator) { @@ -156,12 +150,6 @@ protected void processSegments() { } } - // Merge processing exceptions. - List processingExceptionsToMerge = resultsBlock.getProcessingExceptions(); - if (processingExceptionsToMerge != null) { - _mergedProcessingExceptions.addAll(processingExceptionsToMerge); - } - // Set groups limit reached flag. if (resultsBlock.isNumGroupsLimitReached()) { _numGroupsLimitReached = true; @@ -188,16 +176,16 @@ protected void processSegments() { values[_numGroupByExpressions + i] = aggregationGroupByResult.getResultForGroupId(i, groupId); } _indexedTable.upsert(new Key(keys), new Record(values)); + Tracing.ThreadAccountantOps.sampleAndCheckInterruptionPeriodically(mergedKeys); mergedKeys++; - LoopUtils.checkMergePhaseInterruption(mergedKeys); } } } else { for (IntermediateRecord intermediateResult : intermediateRecords) { //TODO: change upsert api so that it accepts intermediateRecord directly _indexedTable.upsert(intermediateResult._key, intermediateResult._record); + Tracing.ThreadAccountantOps.sampleAndCheckInterruptionPeriodically(mergedKeys); mergedKeys++; - LoopUtils.checkMergePhaseInterruption(mergedKeys); } } } finally { @@ -209,12 +197,12 @@ protected void processSegments() { } @Override - protected void onException(Throwable t) { - _mergedProcessingExceptions.add(QueryException.getException(QueryException.QUERY_EXECUTION_ERROR, t)); + public void onProcessSegmentsException(Throwable t) { + _processingException.compareAndSet(null, t); } @Override - protected void onFinish() { + public void onProcessSegmentsFinish() { _operatorLatch.countDown(); } @@ -232,7 +220,7 @@ protected void onFinish() { * */ @Override - protected BaseResultsBlock mergeResults() + public BaseResultsBlock mergeResults() throws Exception { long timeoutMs = _queryContext.getEndTimeMs() - System.currentTimeMillis(); boolean opCompleted = _operatorLatch.await(timeoutMs, TimeUnit.MILLISECONDS); @@ -245,26 +233,23 @@ protected BaseResultsBlock mergeResults() return new ExceptionResultsBlock(new TimeoutException(errorMessage)); } + Throwable processingException = _processingException.get(); + if (processingException != null) { + return new ExceptionResultsBlock(processingException); + } + IndexedTable indexedTable = _indexedTable; - if (!_queryContext.isServerReturnFinalResult()) { - indexedTable.finish(false); - } else { + if (_queryContext.isServerReturnFinalResult()) { indexedTable.finish(true, true); + } else if (_queryContext.isServerReturnFinalResultKeyUnpartitioned()) { + indexedTable.finish(false, true); + } else { + indexedTable.finish(false); } - GroupByResultsBlock mergedBlock = new GroupByResultsBlock(indexedTable); + GroupByResultsBlock mergedBlock = new GroupByResultsBlock(indexedTable, _queryContext); mergedBlock.setNumGroupsLimitReached(_numGroupsLimitReached); mergedBlock.setNumResizes(indexedTable.getNumResizes()); mergedBlock.setResizeTimeMs(indexedTable.getResizeTimeMs()); - - // Set the processing exceptions. - if (!_mergedProcessingExceptions.isEmpty()) { - mergedBlock.setProcessingExceptions(new ArrayList<>(_mergedProcessingExceptions)); - } - return mergedBlock; } - - @Override - protected void mergeResultsBlocks(GroupByResultsBlock mergedBlock, GroupByResultsBlock blockToMerge) { - } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/MinMaxValueBasedSelectionOrderByCombineOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/MinMaxValueBasedSelectionOrderByCombineOperator.java index e73d27228092..eabd7c0542ab 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/MinMaxValueBasedSelectionOrderByCombineOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/MinMaxValueBasedSelectionOrderByCombineOperator.java @@ -20,7 +20,6 @@ import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.PriorityQueue; import java.util.concurrent.ExecutorService; @@ -36,6 +35,7 @@ import org.apache.pinot.core.operator.AcquireReleaseColumnsSegmentOperator; import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock; import org.apache.pinot.core.operator.blocks.results.ExceptionResultsBlock; +import org.apache.pinot.core.operator.blocks.results.MetadataResultsBlock; import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.selection.SelectionOperatorUtils; @@ -55,16 +55,15 @@ * */ @SuppressWarnings({"rawtypes", "unchecked"}) -public class MinMaxValueBasedSelectionOrderByCombineOperator extends BaseCombineOperator { +public class MinMaxValueBasedSelectionOrderByCombineOperator + extends BaseSingleBlockCombineOperator { private static final Logger LOGGER = LoggerFactory.getLogger(MinMaxValueBasedSelectionOrderByCombineOperator.class); private static final String EXPLAIN_NAME = "COMBINE_SELECT_ORDERBY_MINMAX"; // For min/max value based combine, when a thread detects that no more segment needs to be processed, it inserts this // special results block, which can be skipped during the merge phase - private static final BaseResultsBlock EMPTY_RESULTS_BLOCK = - new SelectionResultsBlock(new DataSchema(new String[0], new DataSchema.ColumnDataType[0]), - Collections.emptyList()); + private static final BaseResultsBlock EMPTY_RESULTS_BLOCK = new MetadataResultsBlock(); // Use an AtomicInteger to track the end operator id, beyond which no operator needs to be processed private final AtomicInteger _endOperatorId; @@ -72,9 +71,9 @@ public class MinMaxValueBasedSelectionOrderByCombineOperator extends BaseCombine private final List _minMaxValueContexts; private final AtomicReference _globalBoundaryValue = new AtomicReference<>(); - MinMaxValueBasedSelectionOrderByCombineOperator(List operators, QueryContext queryContext, + public MinMaxValueBasedSelectionOrderByCombineOperator(List operators, QueryContext queryContext, ExecutorService executorService) { - super(operators, queryContext, executorService); + super(null, operators, queryContext, executorService); _endOperatorId = new AtomicInteger(_numOperators); _numRowsToKeep = queryContext.getLimit() + queryContext.getOffset(); @@ -146,7 +145,7 @@ protected void processSegments() { Comparable threadBoundaryValue = null; int operatorId; - while ((operatorId = _nextOperatorId.getAndIncrement()) < _numOperators) { + while (_processingException.get() == null && (operatorId = _nextOperatorId.getAndIncrement()) < _numOperators) { if (operatorId >= _endOperatorId.get()) { _blockingQueue.offer(EMPTY_RESULTS_BLOCK); continue; @@ -284,23 +283,21 @@ protected BaseResultsBlock mergeResults() return blockToMerge; } if (mergedBlock == null) { - mergedBlock = convertToMergeableBlock((SelectionResultsBlock) blockToMerge); + mergedBlock = (SelectionResultsBlock) blockToMerge; } else { mergeResultsBlocks(mergedBlock, (SelectionResultsBlock) blockToMerge); } numBlocksMerged++; // Update the boundary value if enough rows are collected - PriorityQueue selectionResult = mergedBlock.getRowsAsPriorityQueue(); - if (selectionResult != null && selectionResult.size() == _numRowsToKeep) { - assert selectionResult.peek() != null; - _globalBoundaryValue.set((Comparable) selectionResult.peek()[0]); + List rows = mergedBlock.getRows(); + if (rows.size() == _numRowsToKeep) { + _globalBoundaryValue.set((Comparable) rows.get(_numRowsToKeep - 1)[0]); } } return mergedBlock; } - @Override protected void mergeResultsBlocks(SelectionResultsBlock mergedBlock, SelectionResultsBlock blockToMerge) { DataSchema mergedDataSchema = mergedBlock.getDataSchema(); DataSchema dataSchemaToMerge = blockToMerge.getDataSchema(); @@ -315,18 +312,7 @@ protected void mergeResultsBlocks(SelectionResultsBlock mergedBlock, SelectionRe QueryException.getException(QueryException.MERGE_RESPONSE_ERROR, errorMessage)); return; } - - PriorityQueue mergedRows = mergedBlock.getRowsAsPriorityQueue(); - Collection rowsToMerge = blockToMerge.getRows(); - assert mergedRows != null && rowsToMerge != null; - SelectionOperatorUtils.mergeWithOrdering(mergedRows, rowsToMerge, _numRowsToKeep); - } - - @Override - protected SelectionResultsBlock convertToMergeableBlock(SelectionResultsBlock resultsBlock) { - // This may create a copy or return the same instance. Anyway, this operator is the owner of the - // value now, so it can mutate it. - return resultsBlock.convertToPriorityQueueBased(); + SelectionOperatorUtils.mergeWithOrdering(mergedBlock, blockToMerge, _numRowsToKeep); } private static class MinMaxValueContext { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/SelectionOnlyCombineOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/SelectionOnlyCombineOperator.java index d0e3461b15ac..9f8b84c0dafb 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/SelectionOnlyCombineOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/SelectionOnlyCombineOperator.java @@ -18,18 +18,13 @@ */ package org.apache.pinot.core.operator.combine; -import java.util.Collection; import java.util.List; import java.util.concurrent.ExecutorService; -import org.apache.pinot.common.exception.QueryException; -import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock; import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; +import org.apache.pinot.core.operator.combine.merger.SelectionOnlyResultsBlockMerger; import org.apache.pinot.core.query.request.context.QueryContext; -import org.apache.pinot.core.query.selection.SelectionOperatorUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** @@ -39,16 +34,13 @@ *

NOTE: Selection order-by query with LIMIT 0 is treated as selection only query. */ @SuppressWarnings("rawtypes") -public class SelectionOnlyCombineOperator extends BaseCombineOperator { - private static final Logger LOGGER = LoggerFactory.getLogger(SelectionOnlyCombineOperator.class); - +public class SelectionOnlyCombineOperator extends BaseSingleBlockCombineOperator { private static final String EXPLAIN_NAME = "COMBINE_SELECT"; - private final int _numRowsToKeep; public SelectionOnlyCombineOperator(List operators, QueryContext queryContext, ExecutorService executorService) { - super(operators, queryContext, executorService); + super(new SelectionOnlyResultsBlockMerger(queryContext), operators, queryContext, executorService); _numRowsToKeep = queryContext.getLimit(); } @@ -68,31 +60,4 @@ protected BaseResultsBlock getNextBlock() { return super.getNextBlock(); } - - @Override - protected boolean isQuerySatisfied(SelectionResultsBlock resultsBlock) { - return resultsBlock.getRows().size() == _numRowsToKeep; - } - - @Override - protected void mergeResultsBlocks(SelectionResultsBlock mergedBlock, SelectionResultsBlock blockToMerge) { - DataSchema mergedDataSchema = mergedBlock.getDataSchema(); - DataSchema dataSchemaToMerge = blockToMerge.getDataSchema(); - assert mergedDataSchema != null && dataSchemaToMerge != null; - if (!mergedDataSchema.equals(dataSchemaToMerge)) { - String errorMessage = - String.format("Data schema mismatch between merged block: %s and block to merge: %s, drop block to merge", - mergedDataSchema, dataSchemaToMerge); - // NOTE: This is segment level log, so log at debug level to prevent flooding the log. - LOGGER.debug(errorMessage); - mergedBlock.addToProcessingExceptions( - QueryException.getException(QueryException.MERGE_RESPONSE_ERROR, errorMessage)); - return; - } - - Collection mergedRows = mergedBlock.getRows(); - Collection rowsToMerge = blockToMerge.getRows(); - assert mergedRows != null && rowsToMerge != null; - SelectionOperatorUtils.mergeWithoutOrdering(mergedRows, rowsToMerge, _numRowsToKeep); - } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/SelectionOrderByCombineOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/SelectionOrderByCombineOperator.java index d9bdb0447541..583916fa75b2 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/SelectionOrderByCombineOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/SelectionOrderByCombineOperator.java @@ -18,23 +18,12 @@ */ package org.apache.pinot.core.operator.combine; -import java.util.Collection; import java.util.List; -import java.util.PriorityQueue; import java.util.concurrent.ExecutorService; -import org.apache.pinot.common.exception.QueryException; -import org.apache.pinot.common.request.context.ExpressionContext; -import org.apache.pinot.common.request.context.OrderByExpressionContext; -import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock; import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; +import org.apache.pinot.core.operator.combine.merger.SelectionOrderByResultsBlockMerger; import org.apache.pinot.core.query.request.context.QueryContext; -import org.apache.pinot.core.query.selection.SelectionOperatorUtils; -import org.apache.pinot.spi.exception.QueryCancelledException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** * Combine operator for selection order-by queries. @@ -44,76 +33,16 @@ * (process all segments). */ @SuppressWarnings("rawtypes") -public class SelectionOrderByCombineOperator extends BaseCombineOperator { - private static final Logger LOGGER = LoggerFactory.getLogger(SelectionOrderByCombineOperator.class); - +public class SelectionOrderByCombineOperator extends BaseSingleBlockCombineOperator { private static final String EXPLAIN_NAME = "COMBINE_SELECT_ORDERBY"; - private final int _numRowsToKeep; - public SelectionOrderByCombineOperator(List operators, QueryContext queryContext, ExecutorService executorService) { - super(operators, queryContext, executorService); - _numRowsToKeep = queryContext.getLimit() + queryContext.getOffset(); + super(new SelectionOrderByResultsBlockMerger(queryContext), operators, queryContext, executorService); } @Override public String toExplainString() { return EXPLAIN_NAME; } - - /** - * {@inheritDoc} - * - *

Execute query on one or more segments in a single thread, and store multiple intermediate result blocks - * into BlockingQueue. Try to use - * {@link org.apache.pinot.core.operator.combine.MinMaxValueBasedSelectionOrderByCombineOperator} first, which - * will skip processing some segments based on the column min/max value. Otherwise fall back to the default combine - * (process all segments). - */ - @Override - protected BaseResultsBlock getNextBlock() { - List orderByExpressions = _queryContext.getOrderByExpressions(); - assert orderByExpressions != null; - if (orderByExpressions.get(0).getExpression().getType() == ExpressionContext.Type.IDENTIFIER) { - try { - return new MinMaxValueBasedSelectionOrderByCombineOperator(_operators, _queryContext, - _executorService).getNextBlock(); - } catch (QueryCancelledException e) { - throw e; - } catch (Exception e) { - LOGGER.warn("Caught exception while using min/max value based combine, using the default combine", e); - } - } - return super.getNextBlock(); - } - - @Override - protected void mergeResultsBlocks(SelectionResultsBlock mergedBlock, SelectionResultsBlock blockToMerge) { - DataSchema mergedDataSchema = mergedBlock.getDataSchema(); - DataSchema dataSchemaToMerge = blockToMerge.getDataSchema(); - assert mergedDataSchema != null && dataSchemaToMerge != null; - if (!mergedDataSchema.equals(dataSchemaToMerge)) { - String errorMessage = - String.format("Data schema mismatch between merged block: %s and block to merge: %s, drop block to merge", - mergedDataSchema, dataSchemaToMerge); - // NOTE: This is segment level log, so log at debug level to prevent flooding the log. - LOGGER.debug(errorMessage); - mergedBlock.addToProcessingExceptions( - QueryException.getException(QueryException.MERGE_RESPONSE_ERROR, errorMessage)); - return; - } - - PriorityQueue mergedRows = mergedBlock.getRowsAsPriorityQueue(); - Collection rowsToMerge = blockToMerge.getRows(); - assert mergedRows != null && rowsToMerge != null; - SelectionOperatorUtils.mergeWithOrdering(mergedRows, rowsToMerge, _numRowsToKeep); - } - - @Override - protected SelectionResultsBlock convertToMergeableBlock(SelectionResultsBlock resultsBlock) { - // This may create a copy or return the same instance. Anyway, this operator is the owner of the - // value now, so it can mutate it. - return resultsBlock.convertToPriorityQueueBased(); - } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/AggregationResultsBlockMerger.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/AggregationResultsBlockMerger.java new file mode 100644 index 000000000000..ccdf86bd3c34 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/AggregationResultsBlockMerger.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.combine.merger; + +import java.util.List; +import org.apache.pinot.core.operator.blocks.results.AggregationResultsBlock; +import org.apache.pinot.core.query.aggregation.function.AggregationFunction; +import org.apache.pinot.core.query.request.context.QueryContext; + + +@SuppressWarnings({"rawtypes", "unchecked"}) +public class AggregationResultsBlockMerger implements ResultsBlockMerger { + + public AggregationResultsBlockMerger(QueryContext queryContext) { + } + + @Override + public void mergeResultsBlocks(AggregationResultsBlock mergedBlock, AggregationResultsBlock blockToMerge) { + AggregationFunction[] aggregationFunctions = mergedBlock.getAggregationFunctions(); + List mergedResults = mergedBlock.getResults(); + List resultsToMerge = blockToMerge.getResults(); + assert aggregationFunctions != null && mergedResults != null && resultsToMerge != null; + + int numAggregationFunctions = aggregationFunctions.length; + for (int i = 0; i < numAggregationFunctions; i++) { + mergedResults.set(i, aggregationFunctions[i].merge(mergedResults.get(i), resultsToMerge.get(i))); + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/DistinctResultsBlockMerger.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/DistinctResultsBlockMerger.java new file mode 100644 index 000000000000..28c41feaf3d2 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/DistinctResultsBlockMerger.java @@ -0,0 +1,61 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.combine.merger; + +import org.apache.pinot.core.operator.blocks.results.DistinctResultsBlock; +import org.apache.pinot.core.query.distinct.DistinctTable; +import org.apache.pinot.core.query.request.context.QueryContext; + + +public class DistinctResultsBlockMerger implements ResultsBlockMerger { + private final QueryContext _queryContext; + private final boolean _hasOrderBy; + + public DistinctResultsBlockMerger(QueryContext queryContext) { + _queryContext = queryContext; + _hasOrderBy = queryContext.getOrderByExpressions() != null; + } + + @Override + public boolean isQuerySatisfied(DistinctResultsBlock resultsBlock) { + if (_hasOrderBy) { + return false; + } + return resultsBlock.getDistinctTable().size() >= _queryContext.getLimit(); + } + + @Override + public void mergeResultsBlocks(DistinctResultsBlock mergedBlock, DistinctResultsBlock blockToMerge) { + DistinctTable mergedDistinctTable = mergedBlock.getDistinctTable(); + DistinctTable distinctTableToMerge = blockToMerge.getDistinctTable(); + assert mergedDistinctTable != null && distinctTableToMerge != null; + + // Convert the merged table into a main table if necessary in order to merge other tables + if (!mergedDistinctTable.isMainTable()) { + DistinctTable mainDistinctTable = + new DistinctTable(distinctTableToMerge.getDataSchema(), _queryContext.getOrderByExpressions(), + _queryContext.getLimit(), _queryContext.isNullHandlingEnabled()); + mainDistinctTable.mergeTable(mergedDistinctTable); + mergedBlock.setDistinctTable(mainDistinctTable); + mergedDistinctTable = mainDistinctTable; + } + + mergedDistinctTable.mergeTable(distinctTableToMerge); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/ResultsBlockMerger.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/ResultsBlockMerger.java new file mode 100644 index 000000000000..318dfd2bd003 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/ResultsBlockMerger.java @@ -0,0 +1,47 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.combine.merger; + +import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock; + + +public interface ResultsBlockMerger { + + /** + * Merges a results block into the main mergeable results block. + * + *

NOTE: {@code blockToMerge} should contain the result for a segment without any exception. The errored segment + * results are handled by {@link org.apache.pinot.core.operator.combine.BaseCombineOperator}. + * + * @param mergedBlock The block that accumulates previous results. It should be modified to add the information of the + * other block. + * @param blockToMerge The new block that needs to be merged into the mergedBlock. + */ + void mergeResultsBlocks(T mergedBlock, T blockToMerge); + + /** + * Determine if a block satisfies the query result requirement. This is mostly used to determine early termination + * conditions. Default to always false and terminate should be done normally by processing all blocks. + * + *

The input results block might not be mergeable. + */ + default boolean isQuerySatisfied(T resultsBlock) { + return false; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/SelectionOnlyResultsBlockMerger.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/SelectionOnlyResultsBlockMerger.java new file mode 100644 index 000000000000..aec95823c83a --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/SelectionOnlyResultsBlockMerger.java @@ -0,0 +1,60 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.combine.merger; + +import org.apache.pinot.common.exception.QueryException; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.query.selection.SelectionOperatorUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class SelectionOnlyResultsBlockMerger implements ResultsBlockMerger { + private static final Logger LOGGER = LoggerFactory.getLogger(SelectionOnlyResultsBlockMerger.class); + private final int _numRowsToKeep; + + public SelectionOnlyResultsBlockMerger(QueryContext queryContext) { + _numRowsToKeep = queryContext.getLimit(); + } + + @Override + public boolean isQuerySatisfied(SelectionResultsBlock resultsBlock) { + return resultsBlock.getRows().size() >= _numRowsToKeep; + } + + @Override + public void mergeResultsBlocks(SelectionResultsBlock mergedBlock, SelectionResultsBlock blockToMerge) { + DataSchema mergedDataSchema = mergedBlock.getDataSchema(); + DataSchema dataSchemaToMerge = blockToMerge.getDataSchema(); + assert mergedDataSchema != null && dataSchemaToMerge != null; + if (!mergedDataSchema.equals(dataSchemaToMerge)) { + String errorMessage = + String.format("Data schema mismatch between merged block: %s and block to merge: %s, drop block to merge", + mergedDataSchema, dataSchemaToMerge); + // NOTE: This is segment level log, so log at debug level to prevent flooding the log. + LOGGER.debug(errorMessage); + mergedBlock.addToProcessingExceptions( + QueryException.getException(QueryException.MERGE_RESPONSE_ERROR, errorMessage)); + return; + } + SelectionOperatorUtils.mergeWithoutOrdering(mergedBlock, blockToMerge, _numRowsToKeep); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/SelectionOrderByResultsBlockMerger.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/SelectionOrderByResultsBlockMerger.java new file mode 100644 index 000000000000..a5483853cbe6 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/combine/merger/SelectionOrderByResultsBlockMerger.java @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.combine.merger; + +import org.apache.pinot.common.exception.QueryException; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.query.selection.SelectionOperatorUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class SelectionOrderByResultsBlockMerger implements ResultsBlockMerger { + private static final Logger LOGGER = LoggerFactory.getLogger(SelectionOrderByResultsBlockMerger.class); + private final int _numRowsToKeep; + + public SelectionOrderByResultsBlockMerger(QueryContext queryContext) { + _numRowsToKeep = queryContext.getLimit() + queryContext.getOffset(); + } + + @Override + public void mergeResultsBlocks(SelectionResultsBlock mergedBlock, SelectionResultsBlock blockToMerge) { + DataSchema mergedDataSchema = mergedBlock.getDataSchema(); + DataSchema dataSchemaToMerge = blockToMerge.getDataSchema(); + assert mergedDataSchema != null && dataSchemaToMerge != null; + if (!mergedDataSchema.equals(dataSchemaToMerge)) { + String errorMessage = + String.format("Data schema mismatch between merged block: %s and block to merge: %s, drop block to merge", + mergedDataSchema, dataSchemaToMerge); + // NOTE: This is segment level log, so log at debug level to prevent flooding the log. + LOGGER.debug(errorMessage); + mergedBlock.addToProcessingExceptions( + QueryException.getException(QueryException.MERGE_RESPONSE_ERROR, errorMessage)); + return; + } + SelectionOperatorUtils.mergeWithOrdering(mergedBlock, blockToMerge, _numRowsToKeep); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/ArrayBasedDocIdIterator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/ArrayBasedDocIdIterator.java deleted file mode 100644 index 3b8591bfed03..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/ArrayBasedDocIdIterator.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.operator.dociditerators; - -import org.apache.pinot.core.common.BlockDocIdIterator; -import org.apache.pinot.segment.spi.Constants; - - -/** - * The {@code ArrayBasedDocIdIterator} is the iterator for ArrayBasedDocIdSet. It iterates on an array of matching - * document ids. - */ -public final class ArrayBasedDocIdIterator implements BlockDocIdIterator { - private final int[] _docIds; - private final int _searchableLength; - - private int _nextIndex = 0; - - public ArrayBasedDocIdIterator(int[] docIds, int searchableLength) { - _docIds = docIds; - _searchableLength = searchableLength; - } - - @Override - public int next() { - if (_nextIndex < _searchableLength) { - return _docIds[_nextIndex++]; - } else { - return Constants.EOF; - } - } - - @Override - public int advance(int targetDocId) { - throw new UnsupportedOperationException(); - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/ExpressionScanDocIdIterator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/ExpressionScanDocIdIterator.java index 64987293bc22..82afa7d6e3f9 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/ExpressionScanDocIdIterator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/ExpressionScanDocIdIterator.java @@ -18,13 +18,17 @@ */ package org.apache.pinot.core.operator.dociditerators; +import java.math.BigDecimal; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.OptionalInt; +import javax.annotation.Nullable; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.BaseOperator; import org.apache.pinot.core.operator.BitmapDocIdSetOperator; import org.apache.pinot.core.operator.ProjectionOperator; +import org.apache.pinot.core.operator.ProjectionOperatorUtils; import org.apache.pinot.core.operator.blocks.DocIdSetBlock; import org.apache.pinot.core.operator.blocks.ProjectionBlock; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; @@ -33,7 +37,9 @@ import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.segment.spi.Constants; import org.apache.pinot.segment.spi.datasource.DataSource; +import org.roaringbitmap.BatchIterator; import org.roaringbitmap.BitmapDataProvider; +import org.roaringbitmap.IntIterator; import org.roaringbitmap.PeekableIntIterator; import org.roaringbitmap.RoaringBitmap; import org.roaringbitmap.buffer.ImmutableRoaringBitmap; @@ -49,8 +55,9 @@ public final class ExpressionScanDocIdIterator implements ScanBasedDocIdIterator private final PredicateEvaluator _predicateEvaluator; private final Map _dataSourceMap; private final int _endDocId; - private final int[] _docIdBuffer = new int[DocIdSetPlanNode.MAX_DOC_PER_CALL]; + private final boolean _nullHandlingEnabled; + private final PredicateEvaluationResult _predicateEvaluationResult; private int _blockEndDocId = 0; private PeekableIntIterator _docIdIterator; @@ -59,12 +66,15 @@ public final class ExpressionScanDocIdIterator implements ScanBasedDocIdIterator // the expression, but we only track the number of entries scanned for the resolved expression. private long _numEntriesScanned = 0L; - public ExpressionScanDocIdIterator(TransformFunction transformFunction, PredicateEvaluator predicateEvaluator, - Map dataSourceMap, int numDocs) { + public ExpressionScanDocIdIterator(TransformFunction transformFunction, + @Nullable PredicateEvaluator predicateEvaluator, Map dataSourceMap, int numDocs, + boolean nullHandlingEnabled, PredicateEvaluationResult predicateEvaluationResult) { _transformFunction = transformFunction; _predicateEvaluator = predicateEvaluator; _dataSourceMap = dataSourceMap; _endDocId = numDocs; + _nullHandlingEnabled = nullHandlingEnabled; + _predicateEvaluationResult = predicateEvaluationResult; } @Override @@ -78,9 +88,8 @@ public int next() { while (_blockEndDocId < _endDocId) { int blockStartDocId = _blockEndDocId; _blockEndDocId = Math.min(blockStartDocId + DocIdSetPlanNode.MAX_DOC_PER_CALL, _endDocId); - ProjectionBlock projectionBlock = - new ProjectionOperator(_dataSourceMap, new RangeDocIdSetOperator(blockStartDocId, _blockEndDocId)) - .nextBlock(); + ProjectionBlock projectionBlock = ProjectionOperatorUtils.getProjectionOperator(_dataSourceMap, + new RangeDocIdSetOperator(blockStartDocId, _blockEndDocId)).nextBlock(); RoaringBitmap matchingDocIds = new RoaringBitmap(); processProjectionBlock(projectionBlock, matchingDocIds); if (!matchingDocIds.isEmpty()) { @@ -110,10 +119,23 @@ public int advance(int targetDocId) { return next(); } + @Override + public MutableRoaringBitmap applyAnd(BatchIterator batchIterator, OptionalInt firstDoc, OptionalInt lastDoc) { + IntIterator intIterator = batchIterator.asIntIterator(new int[OPTIMAL_ITERATOR_BATCH_SIZE]); + ProjectionOperator projectionOperator = ProjectionOperatorUtils.getProjectionOperator(_dataSourceMap, + new BitmapDocIdSetOperator(intIterator, _docIdBuffer)); + MutableRoaringBitmap matchingDocIds = new MutableRoaringBitmap(); + ProjectionBlock projectionBlock; + while ((projectionBlock = projectionOperator.nextBlock()) != null) { + processProjectionBlock(projectionBlock, matchingDocIds); + } + return matchingDocIds; + } + @Override public MutableRoaringBitmap applyAnd(ImmutableRoaringBitmap docIds) { ProjectionOperator projectionOperator = - new ProjectionOperator(_dataSourceMap, new BitmapDocIdSetOperator(docIds, _docIdBuffer)); + ProjectionOperatorUtils.getProjectionOperator(_dataSourceMap, new BitmapDocIdSetOperator(docIds, _docIdBuffer)); MutableRoaringBitmap matchingDocIds = new MutableRoaringBitmap(); ProjectionBlock projectionBlock; while ((projectionBlock = projectionOperator.nextBlock()) != null) { @@ -127,60 +149,174 @@ private void processProjectionBlock(ProjectionBlock projectionBlock, BitmapDataP TransformResultMetadata resultMetadata = _transformFunction.getResultMetadata(); if (resultMetadata.isSingleValue()) { _numEntriesScanned += numDocs; + RoaringBitmap nullBitmap = null; + if (_predicateEvaluationResult == PredicateEvaluationResult.NULL) { + nullBitmap = _transformFunction.getNullBitmap(projectionBlock); + if (nullBitmap != null) { + for (int i : nullBitmap) { + matchingDocIds.add(_docIdBuffer[i]); + } + } + return; + } + boolean predicateEvaluationResult = _predicateEvaluationResult == PredicateEvaluationResult.TRUE; + assert (_predicateEvaluator != null); if (resultMetadata.hasDictionary()) { int[] dictIds = _transformFunction.transformToDictIdsSV(projectionBlock); - for (int i = 0; i < numDocs; i++) { - if (_predicateEvaluator.applySV(dictIds[i])) { - matchingDocIds.add(_docIdBuffer[i]); + if (_nullHandlingEnabled) { + nullBitmap = _transformFunction.getNullBitmap(projectionBlock); + } + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < numDocs; i++) { + if (_predicateEvaluator.applySV(dictIds[i]) == predicateEvaluationResult && !nullBitmap.contains(i)) { + matchingDocIds.add(_docIdBuffer[i]); + } + } + } else { + for (int i = 0; i < numDocs; i++) { + if (_predicateEvaluator.applySV(dictIds[i]) == predicateEvaluationResult) { + matchingDocIds.add(_docIdBuffer[i]); + } } } } else { switch (resultMetadata.getDataType().getStoredType()) { case INT: int[] intValues = _transformFunction.transformToIntValuesSV(projectionBlock); - for (int i = 0; i < numDocs; i++) { - if (_predicateEvaluator.applySV(intValues[i])) { - matchingDocIds.add(_docIdBuffer[i]); + if (_nullHandlingEnabled) { + nullBitmap = _transformFunction.getNullBitmap(projectionBlock); + } + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < numDocs; i++) { + if (_predicateEvaluator.applySV(intValues[i]) == predicateEvaluationResult && !nullBitmap.contains(i)) { + matchingDocIds.add(_docIdBuffer[i]); + } + } + } else { + for (int i = 0; i < numDocs; i++) { + if (_predicateEvaluator.applySV(intValues[i]) == predicateEvaluationResult) { + matchingDocIds.add(_docIdBuffer[i]); + } } } break; case LONG: long[] longValues = _transformFunction.transformToLongValuesSV(projectionBlock); - for (int i = 0; i < numDocs; i++) { - if (_predicateEvaluator.applySV(longValues[i])) { - matchingDocIds.add(_docIdBuffer[i]); + if (_nullHandlingEnabled) { + nullBitmap = _transformFunction.getNullBitmap(projectionBlock); + } + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < numDocs; i++) { + if (_predicateEvaluator.applySV(longValues[i]) == predicateEvaluationResult && !nullBitmap.contains( + i)) { + matchingDocIds.add(_docIdBuffer[i]); + } + } + } else { + for (int i = 0; i < numDocs; i++) { + if (_predicateEvaluator.applySV(longValues[i]) == predicateEvaluationResult) { + matchingDocIds.add(_docIdBuffer[i]); + } } } break; case FLOAT: float[] floatValues = _transformFunction.transformToFloatValuesSV(projectionBlock); - for (int i = 0; i < numDocs; i++) { - if (_predicateEvaluator.applySV(floatValues[i])) { - matchingDocIds.add(_docIdBuffer[i]); + if (_nullHandlingEnabled) { + nullBitmap = _transformFunction.getNullBitmap(projectionBlock); + } + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < numDocs; i++) { + if (_predicateEvaluator.applySV(floatValues[i]) == predicateEvaluationResult && !nullBitmap.contains( + i)) { + matchingDocIds.add(_docIdBuffer[i]); + } + } + } else { + for (int i = 0; i < numDocs; i++) { + if (_predicateEvaluator.applySV(floatValues[i]) == predicateEvaluationResult) { + matchingDocIds.add(_docIdBuffer[i]); + } } } break; case DOUBLE: double[] doubleValues = _transformFunction.transformToDoubleValuesSV(projectionBlock); - for (int i = 0; i < numDocs; i++) { - if (_predicateEvaluator.applySV(doubleValues[i])) { - matchingDocIds.add(_docIdBuffer[i]); + if (_nullHandlingEnabled) { + nullBitmap = _transformFunction.getNullBitmap(projectionBlock); + } + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < numDocs; i++) { + if (_predicateEvaluator.applySV(doubleValues[i]) == predicateEvaluationResult && !nullBitmap.contains( + i)) { + matchingDocIds.add(_docIdBuffer[i]); + } + } + } else { + for (int i = 0; i < numDocs; i++) { + if (_predicateEvaluator.applySV(doubleValues[i]) == predicateEvaluationResult) { + matchingDocIds.add(_docIdBuffer[i]); + } } } break; case STRING: String[] stringValues = _transformFunction.transformToStringValuesSV(projectionBlock); - for (int i = 0; i < numDocs; i++) { - if (_predicateEvaluator.applySV(stringValues[i])) { - matchingDocIds.add(_docIdBuffer[i]); + if (_nullHandlingEnabled) { + nullBitmap = _transformFunction.getNullBitmap(projectionBlock); + } + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < numDocs; i++) { + if (_predicateEvaluator.applySV(stringValues[i]) == predicateEvaluationResult && !nullBitmap.contains( + i)) { + matchingDocIds.add(_docIdBuffer[i]); + } + } + } else { + for (int i = 0; i < numDocs; i++) { + if (_predicateEvaluator.applySV(stringValues[i]) == predicateEvaluationResult) { + matchingDocIds.add(_docIdBuffer[i]); + } } } break; case BYTES: byte[][] bytesValues = _transformFunction.transformToBytesValuesSV(projectionBlock); - for (int i = 0; i < numDocs; i++) { - if (_predicateEvaluator.applySV(bytesValues[i])) { - matchingDocIds.add(_docIdBuffer[i]); + if (_nullHandlingEnabled) { + nullBitmap = _transformFunction.getNullBitmap(projectionBlock); + } + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < numDocs; i++) { + if (_predicateEvaluator.applySV(bytesValues[i]) == predicateEvaluationResult && !nullBitmap.contains( + i)) { + matchingDocIds.add(_docIdBuffer[i]); + } + } + } else { + for (int i = 0; i < numDocs; i++) { + if (_predicateEvaluator.applySV(bytesValues[i]) == predicateEvaluationResult) { + matchingDocIds.add(_docIdBuffer[i]); + } + } + } + break; + case BIG_DECIMAL: + BigDecimal[] bigDecimalValues = _transformFunction.transformToBigDecimalValuesSV(projectionBlock); + if (_nullHandlingEnabled) { + nullBitmap = _transformFunction.getNullBitmap(projectionBlock); + } + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < numDocs; i++) { + if (_predicateEvaluator.applySV(bigDecimalValues[i]) == predicateEvaluationResult + && !nullBitmap.contains(i)) { + matchingDocIds.add(_docIdBuffer[i]); + } + } + } else { + for (int i = 0; i < numDocs; i++) { + if (_predicateEvaluator.applySV(bigDecimalValues[i]) == predicateEvaluationResult) { + matchingDocIds.add(_docIdBuffer[i]); + } } } break; @@ -189,13 +325,19 @@ private void processProjectionBlock(ProjectionBlock projectionBlock, BitmapDataP } } } else { + // TODO(https://github.com/apache/pinot/issues/10882): support NULL for multi-value. + if (_predicateEvaluationResult == PredicateEvaluationResult.NULL) { + return; + } + boolean predicateEvaluationResult = _predicateEvaluationResult == PredicateEvaluationResult.TRUE; + assert (_predicateEvaluator != null); if (resultMetadata.hasDictionary()) { int[][] dictIdsArray = _transformFunction.transformToDictIdsMV(projectionBlock); for (int i = 0; i < numDocs; i++) { int[] dictIds = dictIdsArray[i]; int numDictIds = dictIds.length; _numEntriesScanned += numDictIds; - if (_predicateEvaluator.applyMV(dictIds, numDictIds)) { + if (_predicateEvaluator.applyMV(dictIds, numDictIds) == predicateEvaluationResult) { matchingDocIds.add(_docIdBuffer[i]); } } @@ -207,7 +349,7 @@ private void processProjectionBlock(ProjectionBlock projectionBlock, BitmapDataP int[] values = intValuesArray[i]; int numValues = values.length; _numEntriesScanned += numValues; - if (_predicateEvaluator.applyMV(values, numValues)) { + if (_predicateEvaluator.applyMV(values, numValues) == predicateEvaluationResult) { matchingDocIds.add(_docIdBuffer[i]); } } @@ -218,7 +360,7 @@ private void processProjectionBlock(ProjectionBlock projectionBlock, BitmapDataP long[] values = longValuesArray[i]; int numValues = values.length; _numEntriesScanned += numValues; - if (_predicateEvaluator.applyMV(values, numValues)) { + if (_predicateEvaluator.applyMV(values, numValues) == predicateEvaluationResult) { matchingDocIds.add(_docIdBuffer[i]); } } @@ -229,7 +371,7 @@ private void processProjectionBlock(ProjectionBlock projectionBlock, BitmapDataP float[] values = floatValuesArray[i]; int numValues = values.length; _numEntriesScanned += numValues; - if (_predicateEvaluator.applyMV(values, numValues)) { + if (_predicateEvaluator.applyMV(values, numValues) == predicateEvaluationResult) { matchingDocIds.add(_docIdBuffer[i]); } } @@ -240,7 +382,7 @@ private void processProjectionBlock(ProjectionBlock projectionBlock, BitmapDataP double[] values = doubleValuesArray[i]; int numValues = values.length; _numEntriesScanned += numValues; - if (_predicateEvaluator.applyMV(values, numValues)) { + if (_predicateEvaluator.applyMV(values, numValues) == predicateEvaluationResult) { matchingDocIds.add(_docIdBuffer[i]); } } @@ -251,7 +393,7 @@ private void processProjectionBlock(ProjectionBlock projectionBlock, BitmapDataP String[] values = valuesArray[i]; int numValues = values.length; _numEntriesScanned += numValues; - if (_predicateEvaluator.applyMV(values, numValues)) { + if (_predicateEvaluator.applyMV(values, numValues) == predicateEvaluationResult) { matchingDocIds.add(_docIdBuffer[i]); } } @@ -301,4 +443,8 @@ public List getChildOperators() { return Collections.emptyList(); } } + + public enum PredicateEvaluationResult { + TRUE, NULL, FALSE + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/MVScanDocIdIterator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/MVScanDocIdIterator.java index 558c4003d9ed..1c794e81c007 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/MVScanDocIdIterator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/MVScanDocIdIterator.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.core.operator.dociditerators; +import java.util.OptionalInt; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; import org.apache.pinot.segment.spi.Constants; import org.apache.pinot.segment.spi.datasource.DataSource; @@ -26,7 +27,6 @@ import org.apache.pinot.spi.utils.CommonConstants.Query.OptimizationConstants; import org.roaringbitmap.BatchIterator; import org.roaringbitmap.RoaringBitmapWriter; -import org.roaringbitmap.buffer.ImmutableRoaringBitmap; import org.roaringbitmap.buffer.MutableRoaringBitmap; @@ -79,13 +79,21 @@ public int advance(int targetDocId) { } @Override - public MutableRoaringBitmap applyAnd(ImmutableRoaringBitmap docIds) { - if (docIds.isEmpty()) { + public MutableRoaringBitmap applyAnd(BatchIterator docIdIterator, OptionalInt firstDoc, OptionalInt lastDoc) { + if (!docIdIterator.hasNext()) { return new MutableRoaringBitmap(); } - RoaringBitmapWriter result = RoaringBitmapWriter.bufferWriter() - .expectedRange(docIds.first(), docIds.last()).runCompress(false).get(); - BatchIterator docIdIterator = docIds.getBatchIterator(); + RoaringBitmapWriter result; + if (firstDoc.isPresent() && lastDoc.isPresent()) { + result = RoaringBitmapWriter.bufferWriter() + .expectedRange(firstDoc.getAsInt(), lastDoc.getAsInt()) + .runCompress(false) + .get(); + } else { + result = RoaringBitmapWriter.bufferWriter() + .runCompress(false) + .get(); + } int[] buffer = new int[OPTIMAL_ITERATOR_BATCH_SIZE]; while (docIdIterator.hasNext()) { int limit = docIdIterator.nextBatch(buffer); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/SVScanDocIdIterator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/SVScanDocIdIterator.java index 1a4f9c2c5437..2050347ae6f3 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/SVScanDocIdIterator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/SVScanDocIdIterator.java @@ -18,16 +18,15 @@ */ package org.apache.pinot.core.operator.dociditerators; -import javax.annotation.Nullable; +import java.util.OptionalInt; +import org.apache.pinot.core.common.BlockDocIdIterator; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; import org.apache.pinot.segment.spi.Constants; import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.segment.spi.index.reader.ForwardIndexReader; import org.apache.pinot.segment.spi.index.reader.ForwardIndexReaderContext; -import org.apache.pinot.segment.spi.index.reader.NullValueVectorReader; import org.roaringbitmap.BatchIterator; import org.roaringbitmap.RoaringBitmapWriter; -import org.roaringbitmap.buffer.ImmutableRoaringBitmap; import org.roaringbitmap.buffer.MutableRoaringBitmap; @@ -44,7 +43,7 @@ public final class SVScanDocIdIterator implements ScanBasedDocIdIterator { private final ForwardIndexReaderContext _readerContext; private final int _numDocs; private final ValueMatcher _valueMatcher; - private final int[] _batch = new int[OPTIMAL_ITERATOR_BATCH_SIZE]; + private final int[] _batch; private int _firstMismatch; private int _cursor; private final int _cardinality; @@ -52,32 +51,24 @@ public final class SVScanDocIdIterator implements ScanBasedDocIdIterator { private int _nextDocId = 0; private long _numEntriesScanned = 0L; - public SVScanDocIdIterator(PredicateEvaluator predicateEvaluator, DataSource dataSource, int numDocs, - @Nullable NullValueVectorReader nullValueReader) { + public SVScanDocIdIterator(PredicateEvaluator predicateEvaluator, DataSource dataSource, int numDocs, int batchSize) { + _batch = new int[batchSize]; _predicateEvaluator = predicateEvaluator; _reader = dataSource.getForwardIndex(); _readerContext = _reader.createContext(); _numDocs = numDocs; - ImmutableRoaringBitmap nullBitmap = nullValueReader != null ? nullValueReader.getNullBitmap() : null; - if (nullBitmap != null && nullBitmap.isEmpty()) { - nullBitmap = null; - } - _valueMatcher = getValueMatcher(nullBitmap); + _valueMatcher = getValueMatcher(); _cardinality = dataSource.getDataSourceMetadata().getCardinality(); } // for testing - public SVScanDocIdIterator(PredicateEvaluator predicateEvaluator, ForwardIndexReader reader, int numDocs, - @Nullable NullValueVectorReader nullValueReader) { + public SVScanDocIdIterator(PredicateEvaluator predicateEvaluator, ForwardIndexReader reader, int numDocs) { + _batch = new int[BlockDocIdIterator.OPTIMAL_ITERATOR_BATCH_SIZE]; _predicateEvaluator = predicateEvaluator; _reader = reader; _readerContext = reader.createContext(); _numDocs = numDocs; - ImmutableRoaringBitmap nullBitmap = nullValueReader != null ? nullValueReader.getNullBitmap() : null; - if (nullBitmap != null && nullBitmap.isEmpty()) { - nullBitmap = null; - } - _valueMatcher = getValueMatcher(nullBitmap); + _valueMatcher = getValueMatcher(); _cardinality = -1; } @@ -87,7 +78,7 @@ public int next() { int limit; int batchSize = 0; do { - limit = Math.min(_numDocs - _nextDocId, OPTIMAL_ITERATOR_BATCH_SIZE); + limit = Math.min(_numDocs - _nextDocId, _batch.length); if (limit > 0) { for (int i = 0; i < limit; i++) { _batch[i] = _nextDocId + i; @@ -121,14 +112,22 @@ public int advance(int targetDocId) { } @Override - public MutableRoaringBitmap applyAnd(ImmutableRoaringBitmap docIds) { - if (docIds.isEmpty()) { + public MutableRoaringBitmap applyAnd(BatchIterator docIdIterator, OptionalInt firstDoc, OptionalInt lastDoc) { + if (!docIdIterator.hasNext()) { return new MutableRoaringBitmap(); } - RoaringBitmapWriter result = RoaringBitmapWriter.bufferWriter() - .expectedRange(docIds.first(), docIds.last()).runCompress(false).get(); - BatchIterator docIdIterator = docIds.getBatchIterator(); - int[] buffer = new int[OPTIMAL_ITERATOR_BATCH_SIZE]; + RoaringBitmapWriter result; + if (firstDoc.isPresent() && lastDoc.isPresent()) { + result = RoaringBitmapWriter.bufferWriter() + .expectedRange(firstDoc.getAsInt(), lastDoc.getAsInt()) + .runCompress(false) + .get(); + } else { + result = RoaringBitmapWriter.bufferWriter() + .runCompress(false) + .get(); + } + int[] buffer = new int[_batch.length]; while (docIdIterator.hasNext()) { int limit = docIdIterator.nextBatch(buffer); if (limit > 0) { @@ -161,25 +160,25 @@ public float getEstimatedCardinality(boolean isAndDocIdSet) { return ((float) _cardinality) / numMatchingItems; } - private ValueMatcher getValueMatcher(@Nullable ImmutableRoaringBitmap nullBitmap) { + private ValueMatcher getValueMatcher() { if (_reader.isDictionaryEncoded()) { - return nullBitmap == null ? new DictIdMatcher() : new DictIdMatcherAndNullHandler(nullBitmap); + return new DictIdMatcher(); } else { switch (_reader.getStoredType()) { case INT: - return nullBitmap == null ? new IntMatcher() : new IntMatcherAndNullHandler(nullBitmap); + return new IntMatcher(); case LONG: - return nullBitmap == null ? new LongMatcher() : new LongMatcherAndNullHandler(nullBitmap); + return new LongMatcher(); case FLOAT: - return nullBitmap == null ? new FloatMatcher() : new FloatMatcherAndNullHandler(nullBitmap); + return new FloatMatcher(); case DOUBLE: - return nullBitmap == null ? new DoubleMatcher() : new DoubleMatcherAndNullHandler(nullBitmap); + return new DoubleMatcher(); case BIG_DECIMAL: - return nullBitmap == null ? new BigDecimalMatcher() : new BigDecimalMatcherAndNullHandler(nullBitmap); + return new BigDecimalMatcher(); case STRING: - return nullBitmap == null ? new StringMatcher() : new StringMatcherAndNullHandler(nullBitmap); + return new StringMatcher(); case BYTES: - return nullBitmap == null ? new BytesMatcher() : new BytesMatcherAndNullHandler(nullBitmap); + return new BytesMatcher(); default: throw new UnsupportedOperationException(); } @@ -211,60 +210,9 @@ default int matchValues(int limit, int[] docIds) { } } - private static class MatcherUtils { - public static int removeNullDocs(int[] docIds, int[] values, int limit, ImmutableRoaringBitmap nullBitmap) { - assert !nullBitmap.isEmpty(); - int copyToIdx = 0; - for (int i = 0; i < limit; i++) { - if (!nullBitmap.contains(docIds[i])) { - // Compact non-null entries into the prefix of the docIds and values arrays. - docIds[copyToIdx] = docIds[i]; - values[copyToIdx++] = values[i]; - } - } - return copyToIdx; - } - - public static int removeNullDocs(int[] docIds, long[] values, int limit, ImmutableRoaringBitmap nullBitmap) { - assert !nullBitmap.isEmpty(); - int copyToIdx = 0; - for (int i = 0; i < limit; i++) { - if (!nullBitmap.contains(docIds[i])) { - docIds[copyToIdx] = docIds[i]; - values[copyToIdx++] = values[i]; - } - } - return copyToIdx; - } - - public static int removeNullDocs(int[] docIds, float[] values, int limit, ImmutableRoaringBitmap nullBitmap) { - assert !nullBitmap.isEmpty(); - int copyToIdx = 0; - for (int i = 0; i < limit; i++) { - if (!nullBitmap.contains(docIds[i])) { - docIds[copyToIdx] = docIds[i]; - values[copyToIdx++] = values[i]; - } - } - return copyToIdx; - } - - public static int removeNullDocs(int[] docIds, double[] values, int limit, ImmutableRoaringBitmap nullBitmap) { - assert !nullBitmap.isEmpty(); - int copyToIdx = 0; - for (int i = 0; i < limit; i++) { - if (!nullBitmap.contains(docIds[i])) { - docIds[copyToIdx] = docIds[i]; - values[copyToIdx++] = values[i]; - } - } - return copyToIdx; - } - } - private class DictIdMatcher implements ValueMatcher { - private final int[] _buffer = new int[OPTIMAL_ITERATOR_BATCH_SIZE]; + private final int[] _buffer = new int[_batch.length]; @Override public boolean doesValueMatch(int docId) { @@ -278,37 +226,9 @@ public int matchValues(int limit, int[] docIds) { } } - private class DictIdMatcherAndNullHandler implements ValueMatcher { - - private final int[] _buffer = new int[OPTIMAL_ITERATOR_BATCH_SIZE]; - private final ImmutableRoaringBitmap _nullBitmap; - - public DictIdMatcherAndNullHandler(ImmutableRoaringBitmap nullBitmap) { - _nullBitmap = nullBitmap; - } - - @Override - public boolean doesValueMatch(int docId) { - // Any comparison (equality, inequality, or membership) with null results in false (similar to Presto) even if - // the compared with value is null, and comparison is equality. - // To consider nulls, use: IS NULL, or IS NOT NULL operators. - if (_nullBitmap.contains(docId)) { - return false; - } - return _predicateEvaluator.applySV(_reader.getDictId(docId, _readerContext)); - } - - @Override - public int matchValues(int limit, int[] docIds) { - _reader.readDictIds(docIds, limit, _buffer, _readerContext); - int newLimit = MatcherUtils.removeNullDocs(docIds, _buffer, limit, _nullBitmap); - return _predicateEvaluator.applySV(newLimit, docIds, _buffer); - } - } - private class IntMatcher implements ValueMatcher { - private final int[] _buffer = new int[OPTIMAL_ITERATOR_BATCH_SIZE]; + private final int[] _buffer = new int[_batch.length]; @Override public boolean doesValueMatch(int docId) { @@ -322,34 +242,9 @@ public int matchValues(int limit, int[] docIds) { } } - private class IntMatcherAndNullHandler implements ValueMatcher { - - private final ImmutableRoaringBitmap _nullBitmap; - private final int[] _buffer = new int[OPTIMAL_ITERATOR_BATCH_SIZE]; - - public IntMatcherAndNullHandler(ImmutableRoaringBitmap nullBitmap) { - _nullBitmap = nullBitmap; - } - - @Override - public boolean doesValueMatch(int docId) { - if (_nullBitmap.contains(docId)) { - return false; - } - return _predicateEvaluator.applySV(_reader.getInt(docId, _readerContext)); - } - - @Override - public int matchValues(int limit, int[] docIds) { - _reader.readValuesSV(docIds, limit, _buffer, _readerContext); - int newLimit = MatcherUtils.removeNullDocs(docIds, _buffer, limit, _nullBitmap); - return _predicateEvaluator.applySV(newLimit, docIds, _buffer); - } - } - private class LongMatcher implements ValueMatcher { - private final long[] _buffer = new long[OPTIMAL_ITERATOR_BATCH_SIZE]; + private final long[] _buffer = new long[_batch.length]; @Override public boolean doesValueMatch(int docId) { @@ -363,34 +258,9 @@ public int matchValues(int limit, int[] docIds) { } } - private class LongMatcherAndNullHandler implements ValueMatcher { - - private final ImmutableRoaringBitmap _nullBitmap; - private final long[] _buffer = new long[OPTIMAL_ITERATOR_BATCH_SIZE]; - - public LongMatcherAndNullHandler(ImmutableRoaringBitmap nullBitmap) { - _nullBitmap = nullBitmap; - } - - @Override - public boolean doesValueMatch(int docId) { - if (_nullBitmap.contains(docId)) { - return false; - } - return _predicateEvaluator.applySV(_reader.getLong(docId, _readerContext)); - } - - @Override - public int matchValues(int limit, int[] docIds) { - _reader.readValuesSV(docIds, limit, _buffer, _readerContext); - int newLimit = MatcherUtils.removeNullDocs(docIds, _buffer, limit, _nullBitmap); - return _predicateEvaluator.applySV(newLimit, docIds, _buffer); - } - } - private class FloatMatcher implements ValueMatcher { - private final float[] _buffer = new float[OPTIMAL_ITERATOR_BATCH_SIZE]; + private final float[] _buffer = new float[_batch.length]; @Override public boolean doesValueMatch(int docId) { @@ -404,34 +274,9 @@ public int matchValues(int limit, int[] docIds) { } } - private class FloatMatcherAndNullHandler implements ValueMatcher { - - private final ImmutableRoaringBitmap _nullBitmap; - private final float[] _buffer = new float[OPTIMAL_ITERATOR_BATCH_SIZE]; - - public FloatMatcherAndNullHandler(ImmutableRoaringBitmap nullBitmap) { - _nullBitmap = nullBitmap; - } - - @Override - public boolean doesValueMatch(int docId) { - if (_nullBitmap.contains(docId)) { - return false; - } - return _predicateEvaluator.applySV(_reader.getFloat(docId, _readerContext)); - } - - @Override - public int matchValues(int limit, int[] docIds) { - _reader.readValuesSV(docIds, limit, _buffer, _readerContext); - int newLimit = MatcherUtils.removeNullDocs(docIds, _buffer, limit, _nullBitmap); - return _predicateEvaluator.applySV(newLimit, docIds, _buffer); - } - } - private class DoubleMatcher implements ValueMatcher { - private final double[] _buffer = new double[OPTIMAL_ITERATOR_BATCH_SIZE]; + private final double[] _buffer = new double[_batch.length]; @Override public boolean doesValueMatch(int docId) { @@ -445,31 +290,6 @@ public int matchValues(int limit, int[] docIds) { } } - private class DoubleMatcherAndNullHandler implements ValueMatcher { - - private final ImmutableRoaringBitmap _nullBitmap; - private final double[] _buffer = new double[OPTIMAL_ITERATOR_BATCH_SIZE]; - - public DoubleMatcherAndNullHandler(ImmutableRoaringBitmap nullBitmap) { - _nullBitmap = nullBitmap; - } - - @Override - public boolean doesValueMatch(int docId) { - if (_nullBitmap.contains(docId)) { - return false; - } - return _predicateEvaluator.applySV(_reader.getDouble(docId, _readerContext)); - } - - @Override - public int matchValues(int limit, int[] docIds) { - _reader.readValuesSV(docIds, limit, _buffer, _readerContext); - int newLimit = MatcherUtils.removeNullDocs(docIds, _buffer, limit, _nullBitmap); - return _predicateEvaluator.applySV(newLimit, docIds, _buffer); - } - } - private class BigDecimalMatcher implements ValueMatcher { @Override @@ -478,23 +298,6 @@ public boolean doesValueMatch(int docId) { } } - private class BigDecimalMatcherAndNullHandler implements ValueMatcher { - - private final ImmutableRoaringBitmap _nullBitmap; - - public BigDecimalMatcherAndNullHandler(ImmutableRoaringBitmap nullBitmap) { - _nullBitmap = nullBitmap; - } - - @Override - public boolean doesValueMatch(int docId) { - if (_nullBitmap.contains(docId)) { - return false; - } - return _predicateEvaluator.applySV(_reader.getBigDecimal(docId, _readerContext)); - } - } - private class StringMatcher implements ValueMatcher { @Override @@ -503,23 +306,6 @@ public boolean doesValueMatch(int docId) { } } - private class StringMatcherAndNullHandler implements ValueMatcher { - - private final ImmutableRoaringBitmap _nullBitmap; - - public StringMatcherAndNullHandler(ImmutableRoaringBitmap nullBitmap) { - _nullBitmap = nullBitmap; - } - - @Override - public boolean doesValueMatch(int docId) { - if (_nullBitmap.contains(docId)) { - return false; - } - return _predicateEvaluator.applySV(_reader.getString(docId, _readerContext)); - } - } - private class BytesMatcher implements ValueMatcher { @Override @@ -527,21 +313,4 @@ public boolean doesValueMatch(int docId) { return _predicateEvaluator.applySV(_reader.getBytes(docId, _readerContext)); } } - - private class BytesMatcherAndNullHandler implements ValueMatcher { - - private final ImmutableRoaringBitmap _nullBitmap; - - public BytesMatcherAndNullHandler(ImmutableRoaringBitmap nullBitmap) { - _nullBitmap = nullBitmap; - } - - @Override - public boolean doesValueMatch(int docId) { - if (_nullBitmap.contains(docId)) { - return false; - } - return _predicateEvaluator.applySV(_reader.getBytes(docId, _readerContext)); - } - } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/ScanBasedDocIdIterator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/ScanBasedDocIdIterator.java index 1ed890cc834a..1caa52644430 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/ScanBasedDocIdIterator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/dociditerators/ScanBasedDocIdIterator.java @@ -18,7 +18,9 @@ */ package org.apache.pinot.core.operator.dociditerators; +import java.util.OptionalInt; import org.apache.pinot.core.common.BlockDocIdIterator; +import org.roaringbitmap.BatchIterator; import org.roaringbitmap.buffer.ImmutableRoaringBitmap; import org.roaringbitmap.buffer.MutableRoaringBitmap; @@ -34,10 +36,17 @@ */ public interface ScanBasedDocIdIterator extends BlockDocIdIterator { + MutableRoaringBitmap applyAnd(BatchIterator batchIterator, OptionalInt firstDoc, OptionalInt lastDoc); + /** * Applies AND operation to the given bitmap of document ids, returns a bitmap of the matching document ids. */ - MutableRoaringBitmap applyAnd(ImmutableRoaringBitmap docIds); + default MutableRoaringBitmap applyAnd(ImmutableRoaringBitmap docIds) { + if (docIds.isEmpty()) { + return new MutableRoaringBitmap(); + } + return applyAnd(docIds.getBatchIterator(), OptionalInt.of(docIds.first()), OptionalInt.of(docIds.last())); + } /** * Returns the number of entries (SV value contains one entry, MV value contains multiple entries) scanned during the diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/AndDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/AndDocIdSet.java index 8b43b7d34060..64ec2816aaa2 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/AndDocIdSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/AndDocIdSet.java @@ -22,9 +22,11 @@ import java.util.Comparator; import java.util.List; import java.util.Map; -import org.apache.commons.collections.MapUtils; +import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Nullable; import org.apache.pinot.common.utils.config.QueryOptionsUtils; import org.apache.pinot.core.common.BlockDocIdIterator; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.dociditerators.AndDocIdIterator; import org.apache.pinot.core.operator.dociditerators.BitmapBasedDocIdIterator; import org.apache.pinot.core.operator.dociditerators.RangelessBitmapDocIdIterator; @@ -37,9 +39,9 @@ /** - * The FilterBlockDocIdSet to perform AND on all child FilterBlockDocIdSets. + * The BlockDocIdSet to perform AND on all child BlockDocIdSets. *

The AndBlockDocIdSet will construct the BlockDocIdIterator based on the BlockDocIdIterators from the child - * FilterBlockDocIdSets: + * BlockDocIdSets: *

    *
  • * When there are at least one index-base BlockDocIdIterator (SortedDocIdIterator or BitmapBasedDocIdIterator) and @@ -53,40 +55,56 @@ *
  • *
*/ -public final class AndDocIdSet implements FilterBlockDocIdSet { - private final List _docIdSets; +public final class AndDocIdSet implements BlockDocIdSet { + // Keep the scan based BlockDocIdSets to be accessed when collecting query execution stats + private final AtomicReference> _scanBasedDocIdSets = new AtomicReference<>(); private final boolean _cardinalityBasedRankingForScan; + private List _docIdSets; + private volatile long _numEntriesScannedInFilter; - public AndDocIdSet(List docIdSets, Map queryOptions) { + public AndDocIdSet(List docIdSets, @Nullable Map queryOptions) { _docIdSets = docIdSets; _cardinalityBasedRankingForScan = - !MapUtils.isEmpty(queryOptions) && QueryOptionsUtils.isAndScanReorderingEnabled(queryOptions); + queryOptions != null && QueryOptionsUtils.isAndScanReorderingEnabled(queryOptions); } @Override public BlockDocIdIterator iterator() { int numDocIdSets = _docIdSets.size(); - // NOTE: Keep the order of FilterBlockDocIdSets to preserve the order decided within FilterOperatorUtils. + // NOTE: Keep the order of BlockDocIdSets to preserve the order decided within FilterOperatorUtils. // TODO: Consider deciding the order based on the stats of BlockDocIdIterators BlockDocIdIterator[] allDocIdIterators = new BlockDocIdIterator[numDocIdSets]; List sortedDocIdIterators = new ArrayList<>(); List bitmapBasedDocIdIterators = new ArrayList<>(); List scanBasedDocIdIterators = new ArrayList<>(); List remainingDocIdIterators = new ArrayList<>(); + long numEntriesScannedForNonScanBasedDocIdSets = 0L; + List scanBasedDocIdSets = new ArrayList<>(); + for (int i = 0; i < numDocIdSets; i++) { - BlockDocIdIterator docIdIterator = _docIdSets.get(i).iterator(); + BlockDocIdSet docIdSet = _docIdSets.get(i); + BlockDocIdIterator docIdIterator = docIdSet.iterator(); allDocIdIterators[i] = docIdIterator; if (docIdIterator instanceof SortedDocIdIterator) { sortedDocIdIterators.add((SortedDocIdIterator) docIdIterator); + numEntriesScannedForNonScanBasedDocIdSets += docIdSet.getNumEntriesScannedInFilter(); } else if (docIdIterator instanceof BitmapBasedDocIdIterator) { bitmapBasedDocIdIterators.add((BitmapBasedDocIdIterator) docIdIterator); + numEntriesScannedForNonScanBasedDocIdSets += docIdSet.getNumEntriesScannedInFilter(); } else if (docIdIterator instanceof ScanBasedDocIdIterator) { scanBasedDocIdIterators.add((ScanBasedDocIdIterator) docIdIterator); + scanBasedDocIdSets.add(docIdSet); } else { remainingDocIdIterators.add(docIdIterator); + scanBasedDocIdSets.add(docIdSet); } } + // Set _docIdSets to null so that underlying BlockDocIdSets can be garbage collected + _docIdSets = null; + _numEntriesScannedInFilter = numEntriesScannedForNonScanBasedDocIdSets; + _scanBasedDocIdSets.set(scanBasedDocIdSets); + // evaluate the bitmaps in the order of the lowest matching num docIds comes first, so that we minimize the number // of containers (range) for comparison from the beginning, as will minimize the effort of bitmap AND application bitmapBasedDocIdIterators.sort(Comparator.comparing(x -> x.getDocIds().getCardinality())); @@ -169,10 +187,13 @@ public BlockDocIdIterator iterator() { @Override public long getNumEntriesScannedInFilter() { - long numEntriesScannedInFilter = 0L; - for (FilterBlockDocIdSet child : _docIdSets) { - numEntriesScannedInFilter += child.getNumEntriesScannedInFilter(); + List scanBasedDocIdSets = _scanBasedDocIdSets.get(); + long numEntriesScannedForScanBasedDocIdSets = 0L; + if (scanBasedDocIdSets != null) { + for (BlockDocIdSet scanBasedDocIdSet : scanBasedDocIdSets) { + numEntriesScannedForScanBasedDocIdSets += scanBasedDocIdSet.getNumEntriesScannedInFilter(); + } } - return numEntriesScannedInFilter; + return _numEntriesScannedInFilter + numEntriesScannedForScanBasedDocIdSets; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/ArrayBasedDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/ArrayBasedDocIdSet.java deleted file mode 100644 index 4275586d2bc9..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/ArrayBasedDocIdSet.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.operator.docidsets; - -import org.apache.pinot.core.common.BlockDocIdSet; -import org.apache.pinot.core.operator.dociditerators.ArrayBasedDocIdIterator; - - -public final class ArrayBasedDocIdSet implements BlockDocIdSet { - private final int[] _docIds; - private final int _searchableLength; - - public ArrayBasedDocIdSet(int[] docIds, int searchableLength) { - _docIds = docIds; - _searchableLength = searchableLength; - } - - @Override - public ArrayBasedDocIdIterator iterator() { - return new ArrayBasedDocIdIterator(_docIds, _searchableLength); - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/BitmapDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/BitmapDocIdSet.java index a69ac0066a8d..6abf65545b0b 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/BitmapDocIdSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/BitmapDocIdSet.java @@ -18,11 +18,12 @@ */ package org.apache.pinot.core.operator.docidsets; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.dociditerators.BitmapDocIdIterator; import org.roaringbitmap.buffer.ImmutableRoaringBitmap; -public class BitmapDocIdSet implements FilterBlockDocIdSet { +public class BitmapDocIdSet implements BlockDocIdSet { private final BitmapDocIdIterator _iterator; public BitmapDocIdSet(ImmutableRoaringBitmap docIds, int numDocs) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/EmptyDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/EmptyDocIdSet.java index fc78df51a383..1db64ff5577e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/EmptyDocIdSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/EmptyDocIdSet.java @@ -18,13 +18,14 @@ */ package org.apache.pinot.core.operator.docidsets; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.dociditerators.EmptyDocIdIterator; /** - * Singleton class which extends {@link FilterBlockDocIdSet} that is empty, i.e. does not contain any document. + * Singleton class which extends {@link BlockDocIdSet} that is empty, i.e. does not contain any document. */ -public final class EmptyDocIdSet implements FilterBlockDocIdSet { +public final class EmptyDocIdSet implements BlockDocIdSet { private EmptyDocIdSet() { } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/ExpressionDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/ExpressionDocIdSet.java new file mode 100644 index 000000000000..d9fdcfbbcb6b --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/ExpressionDocIdSet.java @@ -0,0 +1,49 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.docidsets; + +import java.util.Map; +import javax.annotation.Nullable; +import org.apache.pinot.core.common.BlockDocIdSet; +import org.apache.pinot.core.operator.dociditerators.ExpressionScanDocIdIterator; +import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; +import org.apache.pinot.core.operator.transform.function.TransformFunction; +import org.apache.pinot.segment.spi.datasource.DataSource; + + +public final class ExpressionDocIdSet implements BlockDocIdSet { + private final ExpressionScanDocIdIterator _docIdIterator; + + public ExpressionDocIdSet(TransformFunction transformFunction, @Nullable PredicateEvaluator predicateEvaluator, + Map dataSourceMap, int numDocs, boolean nullHandlingEnabled, + ExpressionScanDocIdIterator.PredicateEvaluationResult predicateEvaluationResult) { + _docIdIterator = new ExpressionScanDocIdIterator(transformFunction, predicateEvaluator, dataSourceMap, numDocs, + nullHandlingEnabled, predicateEvaluationResult); + } + + @Override + public ExpressionScanDocIdIterator iterator() { + return _docIdIterator; + } + + @Override + public long getNumEntriesScannedInFilter() { + return _docIdIterator.getNumEntriesScanned(); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/ExpressionFilterDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/ExpressionFilterDocIdSet.java deleted file mode 100644 index 3fbdf4752328..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/ExpressionFilterDocIdSet.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.operator.docidsets; - -import java.util.Map; -import org.apache.pinot.core.operator.dociditerators.ExpressionScanDocIdIterator; -import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; -import org.apache.pinot.core.operator.transform.function.TransformFunction; -import org.apache.pinot.segment.spi.datasource.DataSource; - - -public final class ExpressionFilterDocIdSet implements FilterBlockDocIdSet { - private final ExpressionScanDocIdIterator _docIdIterator; - - public ExpressionFilterDocIdSet(TransformFunction transformFunction, PredicateEvaluator predicateEvaluator, - Map dataSourceMap, int numDocs) { - _docIdIterator = new ExpressionScanDocIdIterator(transformFunction, predicateEvaluator, dataSourceMap, numDocs); - } - - @Override - public ExpressionScanDocIdIterator iterator() { - return _docIdIterator; - } - - @Override - public long getNumEntriesScannedInFilter() { - return _docIdIterator.getNumEntriesScanned(); - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/FilterBlockDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/FilterBlockDocIdSet.java deleted file mode 100644 index 18dab2b21c04..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/FilterBlockDocIdSet.java +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.operator.docidsets; - -import org.apache.pinot.core.common.BlockDocIdIterator; -import org.apache.pinot.core.common.BlockDocIdSet; -import org.apache.pinot.core.operator.dociditerators.AndDocIdIterator; -import org.apache.pinot.core.operator.dociditerators.BitmapDocIdIterator; -import org.apache.pinot.core.operator.dociditerators.OrDocIdIterator; -import org.apache.pinot.core.operator.dociditerators.RangelessBitmapDocIdIterator; -import org.apache.pinot.core.operator.dociditerators.ScanBasedDocIdIterator; -import org.apache.pinot.segment.spi.Constants; -import org.roaringbitmap.RoaringBitmapWriter; -import org.roaringbitmap.buffer.MutableRoaringBitmap; - - -/** - * The FilterBlockDocIdSet interface represents the BlockDocIdSet returned by the - * BaseFilterBlock. - */ -public interface FilterBlockDocIdSet extends BlockDocIdSet { - - /** - * Returns the number of entries (SV value contains one entry, MV value contains multiple entries) scanned in the - * filtering phase. This method should be called after the filtering is done. - */ - long getNumEntriesScannedInFilter(); - - /** - * For scan-based FilterBlockDocIdSet, pre-scans the documents and returns a non-scan-based FilterBlockDocIdSet. - */ - default FilterBlockDocIdSet toNonScanDocIdSet() { - BlockDocIdIterator docIdIterator = iterator(); - - // NOTE: AND and OR DocIdIterator might contain scan-based DocIdIterator - // TODO: This scan is not counted in the execution stats - if (docIdIterator instanceof ScanBasedDocIdIterator || docIdIterator instanceof AndDocIdIterator - || docIdIterator instanceof OrDocIdIterator) { - RoaringBitmapWriter bitmapWriter = - RoaringBitmapWriter.bufferWriter().runCompress(false).get(); - int docId; - while ((docId = docIdIterator.next()) != Constants.EOF) { - bitmapWriter.add(docId); - } - return new RangelessBitmapDocIdSet(bitmapWriter.get()); - } - - // NOTE: AND and OR DocIdSet might return BitmapBasedDocIdIterator after processing the iterators. Create a new - // DocIdSet to prevent processing the iterators again - if (docIdIterator instanceof RangelessBitmapDocIdIterator) { - return new RangelessBitmapDocIdSet((RangelessBitmapDocIdIterator) docIdIterator); - } - if (docIdIterator instanceof BitmapDocIdIterator) { - return new BitmapDocIdSet((BitmapDocIdIterator) docIdIterator); - } - - return this; - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/MVScanDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/MVScanDocIdSet.java index 6f85c2b946ea..dc9361e0d81d 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/MVScanDocIdSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/MVScanDocIdSet.java @@ -18,12 +18,13 @@ */ package org.apache.pinot.core.operator.docidsets; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.dociditerators.MVScanDocIdIterator; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; import org.apache.pinot.segment.spi.datasource.DataSource; -public final class MVScanDocIdSet implements FilterBlockDocIdSet { +public final class MVScanDocIdSet implements BlockDocIdSet { private final MVScanDocIdIterator _docIdIterator; public MVScanDocIdSet(PredicateEvaluator predicateEvaluator, DataSource dataSource, int numDocs) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/MatchAllDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/MatchAllDocIdSet.java index 8e6b1bdccb47..497be02bd4f1 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/MatchAllDocIdSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/MatchAllDocIdSet.java @@ -18,10 +18,11 @@ */ package org.apache.pinot.core.operator.docidsets; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.dociditerators.MatchAllDocIdIterator; -public final class MatchAllDocIdSet implements FilterBlockDocIdSet { +public final class MatchAllDocIdSet implements BlockDocIdSet { private final int _numDocs; public MatchAllDocIdSet(int numDocs) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/NotDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/NotDocIdSet.java index 1fe6462b6842..c874722e43cf 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/NotDocIdSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/NotDocIdSet.java @@ -19,14 +19,15 @@ package org.apache.pinot.core.operator.docidsets; import org.apache.pinot.core.common.BlockDocIdIterator; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.dociditerators.NotDocIdIterator; -public class NotDocIdSet implements FilterBlockDocIdSet { - private final FilterBlockDocIdSet _childDocIdSet; +public class NotDocIdSet implements BlockDocIdSet { + private final BlockDocIdSet _childDocIdSet; private final int _numDocs; - public NotDocIdSet(FilterBlockDocIdSet childDocIdSet, int numDocs) { + public NotDocIdSet(BlockDocIdSet childDocIdSet, int numDocs) { _childDocIdSet = childDocIdSet; _numDocs = numDocs; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/OrDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/OrDocIdSet.java index 6ead802acf58..fdc522286bca 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/OrDocIdSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/OrDocIdSet.java @@ -20,7 +20,9 @@ import java.util.ArrayList; import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import org.apache.pinot.core.common.BlockDocIdIterator; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.dociditerators.BitmapBasedDocIdIterator; import org.apache.pinot.core.operator.dociditerators.BitmapDocIdIterator; import org.apache.pinot.core.operator.dociditerators.OrDocIdIterator; @@ -30,9 +32,9 @@ /** - * The FilterBlockDocIdSet to perform OR on all child FilterBlockDocIdSets. + * The BlockDocIdSet to perform OR on all child BlockDocIdSets. *

The OrBlockDocIdSet will construct the BlockDocIdIterator based on the BlockDocIdIterators from the child - * FilterBlockDocIdSets: + * BlockDocIdSets: *

    *
  • * When there are more than one index-base BlockDocIdIterator (SortedDocIdIterator or BitmapBasedDocIdIterator), @@ -45,11 +47,14 @@ *
  • *
*/ -public final class OrDocIdSet implements FilterBlockDocIdSet { - private final List _docIdSets; +public final class OrDocIdSet implements BlockDocIdSet { + // Keep the scan based BlockDocIdSets to be accessed when collecting query execution stats + private final AtomicReference> _scanBasedDocIdSets = new AtomicReference<>(); private final int _numDocs; + private List _docIdSets; + private volatile long _numEntriesScannedInFilter = 0L; - public OrDocIdSet(List docIdSets, int numDocs) { + public OrDocIdSet(List docIdSets, int numDocs) { _docIdSets = docIdSets; _numDocs = numDocs; } @@ -61,17 +66,29 @@ public BlockDocIdIterator iterator() { List sortedDocIdIterators = new ArrayList<>(); List bitmapBasedDocIdIterators = new ArrayList<>(); List remainingDocIdIterators = new ArrayList<>(); + long numEntriesScannedForNonScanBasedDocIdSets = 0L; + List scanBasedDocIdSets = new ArrayList<>(); + for (int i = 0; i < numDocIdSets; i++) { - BlockDocIdIterator docIdIterator = _docIdSets.get(i).iterator(); + BlockDocIdSet docIdSet = _docIdSets.get(i); + BlockDocIdIterator docIdIterator = docIdSet.iterator(); allDocIdIterators[i] = docIdIterator; if (docIdIterator instanceof SortedDocIdIterator) { sortedDocIdIterators.add((SortedDocIdIterator) docIdIterator); + numEntriesScannedForNonScanBasedDocIdSets += docIdSet.getNumEntriesScannedInFilter(); } else if (docIdIterator instanceof BitmapBasedDocIdIterator) { - bitmapBasedDocIdIterators.add((BitmapBasedDocIdIterator) docIdIterator); + numEntriesScannedForNonScanBasedDocIdSets += docIdSet.getNumEntriesScannedInFilter(); } else { remainingDocIdIterators.add(docIdIterator); + scanBasedDocIdSets.add(docIdSet); } } + + // Set _docIdSets to null so that underlying BlockDocIdSets can be garbage collected + _docIdSets = null; + _numEntriesScannedInFilter = numEntriesScannedForNonScanBasedDocIdSets; + _scanBasedDocIdSets.set(scanBasedDocIdSets); + int numSortedDocIdIterators = sortedDocIdIterators.size(); int numBitmapBasedDocIdIterators = bitmapBasedDocIdIterators.size(); if (numSortedDocIdIterators + numBitmapBasedDocIdIterators > 1) { @@ -111,10 +128,13 @@ public BlockDocIdIterator iterator() { @Override public long getNumEntriesScannedInFilter() { - long numEntriesScannedInFilter = 0L; - for (FilterBlockDocIdSet docIdSet : _docIdSets) { - numEntriesScannedInFilter += docIdSet.getNumEntriesScannedInFilter(); + List scanBasedDocIdSets = _scanBasedDocIdSets.get(); + long numEntriesScannedForScanBasedDocIdSets = 0L; + if (scanBasedDocIdSets != null) { + for (BlockDocIdSet scanBasedDocIdSet : scanBasedDocIdSets) { + numEntriesScannedForScanBasedDocIdSets += scanBasedDocIdSet.getNumEntriesScannedInFilter(); + } } - return numEntriesScannedInFilter; + return _numEntriesScannedInFilter + numEntriesScannedForScanBasedDocIdSets; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/RangelessBitmapDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/RangelessBitmapDocIdSet.java index 463a2df84ab7..20fb0af6f57f 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/RangelessBitmapDocIdSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/RangelessBitmapDocIdSet.java @@ -18,11 +18,12 @@ */ package org.apache.pinot.core.operator.docidsets; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.dociditerators.RangelessBitmapDocIdIterator; import org.roaringbitmap.buffer.ImmutableRoaringBitmap; -public class RangelessBitmapDocIdSet implements FilterBlockDocIdSet { +public class RangelessBitmapDocIdSet implements BlockDocIdSet { private final RangelessBitmapDocIdIterator _iterator; public RangelessBitmapDocIdSet(ImmutableRoaringBitmap docIds) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/SVScanDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/SVScanDocIdSet.java index 9167304561bc..2e0e5b881066 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/SVScanDocIdSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/SVScanDocIdSet.java @@ -18,19 +18,17 @@ */ package org.apache.pinot.core.operator.docidsets; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.dociditerators.SVScanDocIdIterator; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; import org.apache.pinot.segment.spi.datasource.DataSource; -import org.apache.pinot.segment.spi.index.reader.NullValueVectorReader; -public final class SVScanDocIdSet implements FilterBlockDocIdSet { +public final class SVScanDocIdSet implements BlockDocIdSet { private final SVScanDocIdIterator _docIdIterator; - public SVScanDocIdSet(PredicateEvaluator predicateEvaluator, DataSource dataSource, int numDocs, - boolean nullHandlingEnabled) { - NullValueVectorReader nullValueVector = nullHandlingEnabled ? dataSource.getNullValueVector() : null; - _docIdIterator = new SVScanDocIdIterator(predicateEvaluator, dataSource, numDocs, nullValueVector); + public SVScanDocIdSet(PredicateEvaluator predicateEvaluator, DataSource dataSource, int numDocs, int batchSize) { + _docIdIterator = new SVScanDocIdIterator(predicateEvaluator, dataSource, numDocs, batchSize); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/SortedDocIdSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/SortedDocIdSet.java index 2c57caf2357c..280f9daeb994 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/SortedDocIdSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docidsets/SortedDocIdSet.java @@ -19,11 +19,12 @@ package org.apache.pinot.core.operator.docidsets; import java.util.List; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.dociditerators.SortedDocIdIterator; import org.apache.pinot.spi.utils.Pairs.IntPair; -public final class SortedDocIdSet implements FilterBlockDocIdSet { +public final class SortedDocIdSet implements BlockDocIdSet { private final List _docIdRanges; // NOTE: No need to track numDocs because sorted index can only apply to ImmutableSegment, so the document ids are diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/DataBlockValSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/DataBlockValSet.java new file mode 100644 index 000000000000..4b5eaab2fe45 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/DataBlockValSet.java @@ -0,0 +1,156 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.docvalsets; + +import java.math.BigDecimal; +import javax.annotation.Nullable; +import org.apache.pinot.common.datablock.DataBlock; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.util.DataBlockExtractUtils; +import org.apache.pinot.segment.spi.index.reader.Dictionary; +import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.roaringbitmap.RoaringBitmap; + + +/** + * In the multistage engine, the leaf stage servers process the data in columnar fashion. By the time the + * intermediate stage receives the projected column, they are converted to a row based format. This class provides + * the capability to convert the row based representation into columnar blocks so that they can be used to process + * aggregations using v1 aggregation functions. + * TODO: Support MV + */ +public class DataBlockValSet implements BlockValSet { + private final DataType _dataType; + private final DataType _storedType; + private final DataBlock _dataBlock; + private final int _colId; + private final RoaringBitmap _nullBitmap; + + public DataBlockValSet(ColumnDataType columnDataType, DataBlock dataBlock, int colId) { + _dataType = columnDataType.toDataType(); + _storedType = _dataType.getStoredType(); + _dataBlock = dataBlock; + _colId = colId; + _nullBitmap = dataBlock.getNullRowIds(colId); + } + + @Nullable + @Override + public RoaringBitmap getNullBitmap() { + return _nullBitmap; + } + + @Override + public DataType getValueType() { + return _dataType; + } + + @Override + public boolean isSingleValue() { + // TODO: Needs to be changed when we start supporting MV in multistage + return true; + } + + @Nullable + @Override + public Dictionary getDictionary() { + return null; + } + + @Override + public int[] getDictionaryIdsSV() { + throw new UnsupportedOperationException(); + } + + @Override + public int[] getIntValuesSV() { + return DataBlockExtractUtils.extractIntColumn(_storedType, _dataBlock, _colId, _nullBitmap); + } + + @Override + public long[] getLongValuesSV() { + return DataBlockExtractUtils.extractLongColumn(_storedType, _dataBlock, _colId, _nullBitmap); + } + + @Override + public float[] getFloatValuesSV() { + return DataBlockExtractUtils.extractFloatColumn(_storedType, _dataBlock, _colId, _nullBitmap); + } + + @Override + public double[] getDoubleValuesSV() { + return DataBlockExtractUtils.extractDoubleColumn(_storedType, _dataBlock, _colId, _nullBitmap); + } + + @Override + public BigDecimal[] getBigDecimalValuesSV() { + return DataBlockExtractUtils.extractBigDecimalColumn(_storedType, _dataBlock, _colId, _nullBitmap); + } + + @Override + public String[] getStringValuesSV() { + return DataBlockExtractUtils.extractStringColumn(_storedType, _dataBlock, _colId, _nullBitmap); + } + + @Override + public byte[][] getBytesValuesSV() { + return DataBlockExtractUtils.extractBytesColumn(_storedType, _dataBlock, _colId, _nullBitmap); + } + + @Override + public int[][] getDictionaryIdsMV() { + throw new UnsupportedOperationException(); + } + + @Override + public int[][] getIntValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public long[][] getLongValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public float[][] getFloatValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public double[][] getDoubleValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public String[][] getStringValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public byte[][][] getBytesValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public int[] getNumMVEntries() { + throw new UnsupportedOperationException(); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/FilteredDataBlockValSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/FilteredDataBlockValSet.java new file mode 100644 index 000000000000..020c1679b672 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/FilteredDataBlockValSet.java @@ -0,0 +1,186 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.docvalsets; + +import java.math.BigDecimal; +import javax.annotation.Nullable; +import org.apache.pinot.common.datablock.DataBlock; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.util.DataBlockExtractUtils; +import org.apache.pinot.segment.spi.index.reader.Dictionary; +import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.roaringbitmap.PeekableIntIterator; +import org.roaringbitmap.RoaringBitmap; + + +/** + * In the multistage engine, the leaf stage servers process the data in columnar fashion. By the time the + * intermediate stage receives the projected column, they are converted to a row based format. This class provides + * the capability to convert the row based representation into columnar blocks so that they can be used to process + * aggregations using v1 aggregation functions. + * TODO: Support MV + */ +public class FilteredDataBlockValSet implements BlockValSet { + private final DataType _dataType; + private final DataType _storedType; + private final DataBlock _dataBlock; + private final int _colId; + private final int _numMatchedRows; + private final RoaringBitmap _matchedBitmap; + private final RoaringBitmap _matchedNullBitmap; + + public FilteredDataBlockValSet(ColumnDataType columnDataType, DataBlock dataBlock, int colId, int numMatchedRows, + RoaringBitmap matchedBitmap) { + _dataType = columnDataType.toDataType(); + _storedType = _dataType.getStoredType(); + _dataBlock = dataBlock; + _colId = colId; + _numMatchedRows = numMatchedRows; + _matchedBitmap = matchedBitmap; + + RoaringBitmap nullBitmap = dataBlock.getNullRowIds(colId); + if (nullBitmap == null) { + _matchedNullBitmap = null; + } else { + RoaringBitmap matchedNullBitmap = new RoaringBitmap(); + PeekableIntIterator iterator = matchedBitmap.getIntIterator(); + for (int i = 0; i < numMatchedRows; i++) { + int rowId = iterator.next(); + if (nullBitmap.contains(rowId)) { + matchedNullBitmap.add(i); + } + } + _matchedNullBitmap = !matchedNullBitmap.isEmpty() ? matchedNullBitmap : null; + } + } + + /** + * Returns a bitmap of indices where null values are found. + */ + @Nullable + @Override + public RoaringBitmap getNullBitmap() { + return _matchedNullBitmap; + } + + @Override + public DataType getValueType() { + return _dataType; + } + + @Override + public boolean isSingleValue() { + // TODO: Needs to be changed when we start supporting MV in multistage + return true; + } + + @Nullable + @Override + public Dictionary getDictionary() { + return null; + } + + @Override + public int[] getDictionaryIdsSV() { + throw new UnsupportedOperationException(); + } + + @Override + public int[] getIntValuesSV() { + return DataBlockExtractUtils.extractIntColumn(_storedType, _dataBlock, _colId, _numMatchedRows, _matchedBitmap, + _matchedNullBitmap); + } + + @Override + public long[] getLongValuesSV() { + return DataBlockExtractUtils.extractLongColumn(_storedType, _dataBlock, _colId, _numMatchedRows, _matchedBitmap, + _matchedNullBitmap); + } + + @Override + public float[] getFloatValuesSV() { + return DataBlockExtractUtils.extractFloatColumn(_storedType, _dataBlock, _colId, _numMatchedRows, _matchedBitmap, + _matchedNullBitmap); + } + + @Override + public double[] getDoubleValuesSV() { + return DataBlockExtractUtils.extractDoubleColumn(_storedType, _dataBlock, _colId, _numMatchedRows, _matchedBitmap, + _matchedNullBitmap); + } + + @Override + public BigDecimal[] getBigDecimalValuesSV() { + return DataBlockExtractUtils.extractBigDecimalColumn(_storedType, _dataBlock, _colId, _numMatchedRows, + _matchedBitmap, _matchedNullBitmap); + } + + @Override + public String[] getStringValuesSV() { + return DataBlockExtractUtils.extractStringColumn(_storedType, _dataBlock, _colId, _numMatchedRows, _matchedBitmap, + _matchedNullBitmap); + } + + @Override + public byte[][] getBytesValuesSV() { + return DataBlockExtractUtils.extractBytesColumn(_storedType, _dataBlock, _colId, _numMatchedRows, _matchedBitmap, + _matchedNullBitmap); + } + + @Override + public int[][] getDictionaryIdsMV() { + throw new UnsupportedOperationException(); + } + + @Override + public int[][] getIntValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public long[][] getLongValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public float[][] getFloatValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public double[][] getDoubleValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public String[][] getStringValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public byte[][][] getBytesValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public int[] getNumMVEntries() { + throw new UnsupportedOperationException(); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/FilteredRowBasedBlockValSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/FilteredRowBasedBlockValSet.java new file mode 100644 index 000000000000..f94d2a03b928 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/FilteredRowBasedBlockValSet.java @@ -0,0 +1,311 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.docvalsets; + +import com.google.common.base.Preconditions; +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; +import javax.annotation.Nullable; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.segment.spi.index.reader.Dictionary; +import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.utils.BigDecimalUtils; +import org.apache.pinot.spi.utils.ByteArray; +import org.apache.pinot.spi.utils.CommonConstants.NullValuePlaceHolder; +import org.roaringbitmap.PeekableIntIterator; +import org.roaringbitmap.RoaringBitmap; + + +/** + * A {@link BlockValSet} implementation backed by row major data with a filter column (BOOLEAN type). + * + * TODO: Support MV + */ +public class FilteredRowBasedBlockValSet implements BlockValSet { + private final DataType _dataType; + private final DataType _storedType; + private final List _rows; + private final int _colId; + private final int _numMatchedRows; + private final RoaringBitmap _matchedBitmap; + private final RoaringBitmap _matchedNullBitmap; + + public FilteredRowBasedBlockValSet(ColumnDataType columnDataType, List rows, int colId, int numMatchedRows, + RoaringBitmap matchedBitmap, boolean nullHandlingEnabled) { + _dataType = columnDataType.toDataType(); + _storedType = _dataType.getStoredType(); + _rows = rows; + _colId = colId; + _numMatchedRows = numMatchedRows; + _matchedBitmap = matchedBitmap; + + if (nullHandlingEnabled) { + RoaringBitmap matchedNullBitmap = new RoaringBitmap(); + PeekableIntIterator iterator = matchedBitmap.getIntIterator(); + for (int i = 0; i < numMatchedRows; i++) { + int rowId = iterator.next(); + if (rows.get(rowId)[colId] == null) { + matchedNullBitmap.add(i); + } + } + _matchedNullBitmap = !matchedNullBitmap.isEmpty() ? matchedNullBitmap : null; + } else { + _matchedNullBitmap = null; + } + } + + @Nullable + @Override + public RoaringBitmap getNullBitmap() { + return _matchedNullBitmap; + } + + @Override + public DataType getValueType() { + return _dataType; + } + + @Override + public boolean isSingleValue() { + return true; + } + + @Nullable + @Override + public Dictionary getDictionary() { + return null; + } + + @Override + public int[] getDictionaryIdsSV() { + throw new UnsupportedOperationException(); + } + + @Override + public int[] getIntValuesSV() { + Preconditions.checkState(_dataType == DataType.UNKNOWN || _storedType.isNumeric() || _storedType == DataType.STRING, + "Cannot read int values from data type: %s", _dataType); + int[] values = new int[_numMatchedRows]; + if (_numMatchedRows == 0 || _dataType == DataType.UNKNOWN) { + return values; + } + PeekableIntIterator iterator = _matchedBitmap.getIntIterator(); + for (int matchedRowId = 0; matchedRowId < _numMatchedRows; matchedRowId++) { + int rowId = iterator.next(); + if (_matchedNullBitmap != null && _matchedNullBitmap.contains(matchedRowId)) { + continue; + } + if (_storedType.isNumeric()) { + values[matchedRowId] = ((Number) _rows.get(rowId)[_colId]).intValue(); + } else { + values[matchedRowId] = Integer.parseInt((String) _rows.get(rowId)[_colId]); + } + } + return values; + } + + @Override + public long[] getLongValuesSV() { + Preconditions.checkState(_dataType == DataType.UNKNOWN || _storedType.isNumeric() || _storedType == DataType.STRING, + "Cannot read long values from data type: %s", _dataType); + long[] values = new long[_numMatchedRows]; + if (_numMatchedRows == 0 || _dataType == DataType.UNKNOWN) { + return values; + } + PeekableIntIterator iterator = _matchedBitmap.getIntIterator(); + for (int matchedRowId = 0; matchedRowId < _numMatchedRows; matchedRowId++) { + int rowId = iterator.next(); + if (_matchedNullBitmap != null && _matchedNullBitmap.contains(matchedRowId)) { + continue; + } + if (_storedType.isNumeric()) { + values[matchedRowId] = ((Number) _rows.get(rowId)[_colId]).longValue(); + } else { + values[matchedRowId] = Long.parseLong((String) _rows.get(rowId)[_colId]); + } + } + return values; + } + + @Override + public float[] getFloatValuesSV() { + Preconditions.checkState(_dataType == DataType.UNKNOWN || _storedType.isNumeric() || _storedType == DataType.STRING, + "Cannot read float values from data type: %s", _dataType); + float[] values = new float[_numMatchedRows]; + if (_numMatchedRows == 0 || _dataType == DataType.UNKNOWN) { + return values; + } + PeekableIntIterator iterator = _matchedBitmap.getIntIterator(); + for (int matchedRowId = 0; matchedRowId < _numMatchedRows; matchedRowId++) { + int rowId = iterator.next(); + if (_matchedNullBitmap != null && _matchedNullBitmap.contains(matchedRowId)) { + continue; + } + if (_storedType.isNumeric()) { + values[matchedRowId] = ((Number) _rows.get(rowId)[_colId]).floatValue(); + } else { + values[matchedRowId] = Float.parseFloat((String) _rows.get(rowId)[_colId]); + } + } + return values; + } + + @Override + public double[] getDoubleValuesSV() { + Preconditions.checkState(_dataType == DataType.UNKNOWN || _storedType.isNumeric() || _storedType == DataType.STRING, + "Cannot read double values from data type: %s", _dataType); + double[] values = new double[_numMatchedRows]; + if (_numMatchedRows == 0 || _dataType == DataType.UNKNOWN) { + return values; + } + PeekableIntIterator iterator = _matchedBitmap.getIntIterator(); + for (int matchedRowId = 0; matchedRowId < _numMatchedRows; matchedRowId++) { + int rowId = iterator.next(); + if (_matchedNullBitmap != null && _matchedNullBitmap.contains(matchedRowId)) { + continue; + } + if (_storedType.isNumeric()) { + values[matchedRowId] = ((Number) _rows.get(rowId)[_colId]).doubleValue(); + } else { + values[matchedRowId] = Double.parseDouble((String) _rows.get(rowId)[_colId]); + } + } + return values; + } + + @Override + public BigDecimal[] getBigDecimalValuesSV() { + BigDecimal[] values = new BigDecimal[_numMatchedRows]; + if (_numMatchedRows == 0) { + return values; + } + if (_dataType == DataType.UNKNOWN) { + Arrays.fill(values, NullValuePlaceHolder.BIG_DECIMAL); + return values; + } + PeekableIntIterator iterator = _matchedBitmap.getIntIterator(); + for (int matchedRowId = 0; matchedRowId < _numMatchedRows; matchedRowId++) { + int rowId = iterator.next(); + if (_matchedNullBitmap != null && _matchedNullBitmap.contains(matchedRowId)) { + values[matchedRowId] = NullValuePlaceHolder.BIG_DECIMAL; + continue; + } + switch (_storedType) { + case INT: + case LONG: + values[matchedRowId] = BigDecimal.valueOf(((Number) _rows.get(rowId)[_colId]).longValue()); + break; + case FLOAT: + case DOUBLE: + case STRING: + values[matchedRowId] = new BigDecimal(_rows.get(rowId)[_colId].toString()); + break; + case BIG_DECIMAL: + values[matchedRowId] = (BigDecimal) _rows.get(rowId)[_colId]; + break; + case BYTES: + values[matchedRowId] = BigDecimalUtils.deserialize((ByteArray) _rows.get(rowId)[_colId]); + break; + default: + throw new IllegalStateException("Cannot read BigDecimal values from data type: " + _dataType); + } + } + return values; + } + + @Override + public String[] getStringValuesSV() { + String[] values = new String[_numMatchedRows]; + if (_numMatchedRows == 0) { + return values; + } + if (_dataType == DataType.UNKNOWN) { + Arrays.fill(values, NullValuePlaceHolder.STRING); + return values; + } + PeekableIntIterator iterator = _matchedBitmap.getIntIterator(); + for (int matchedRowId = 0; matchedRowId < _numMatchedRows; matchedRowId++) { + int rowId = iterator.next(); + boolean isNull = _matchedNullBitmap != null && _matchedNullBitmap.contains(matchedRowId); + values[matchedRowId] = !isNull ? _rows.get(rowId)[_colId].toString() : NullValuePlaceHolder.STRING; + } + return values; + } + + @Override + public byte[][] getBytesValuesSV() { + byte[][] values = new byte[_numMatchedRows][]; + if (_numMatchedRows == 0) { + return values; + } + if (_dataType == DataType.UNKNOWN) { + Arrays.fill(values, NullValuePlaceHolder.BYTES); + return values; + } + PeekableIntIterator iterator = _matchedBitmap.getIntIterator(); + for (int matchedRowId = 0; matchedRowId < _numMatchedRows; matchedRowId++) { + int rowId = iterator.next(); + boolean isNull = _matchedNullBitmap != null && _matchedNullBitmap.contains(matchedRowId); + values[matchedRowId] = !isNull ? ((ByteArray) _rows.get(rowId)[_colId]).getBytes() : NullValuePlaceHolder.BYTES; + } + return values; + } + + @Override + public int[][] getDictionaryIdsMV() { + throw new UnsupportedOperationException(); + } + + @Override + public int[][] getIntValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public long[][] getLongValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public float[][] getFloatValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public double[][] getDoubleValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public String[][] getStringValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public byte[][][] getBytesValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public int[] getNumMVEntries() { + throw new UnsupportedOperationException(); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/RowBasedBlockValSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/RowBasedBlockValSet.java new file mode 100644 index 000000000000..c18cd3acb486 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/RowBasedBlockValSet.java @@ -0,0 +1,620 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.docvalsets; + +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; +import javax.annotation.Nullable; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.segment.spi.index.reader.Dictionary; +import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.utils.ArrayCopyUtils; +import org.apache.pinot.spi.utils.BigDecimalUtils; +import org.apache.pinot.spi.utils.ByteArray; +import org.apache.pinot.spi.utils.CommonConstants.NullValuePlaceHolder; +import org.roaringbitmap.RoaringBitmap; + + +/** + * A {@link BlockValSet} implementation backed by row major data. + * + * TODO: Support MV + */ +public class RowBasedBlockValSet implements BlockValSet { + private final DataType _dataType; + private final DataType _storedType; + private final List _rows; + private final int _colId; + private final RoaringBitmap _nullBitmap; + private final Object _nullPlaceHolder; + + public RowBasedBlockValSet(ColumnDataType columnDataType, List rows, int colId, + boolean nullHandlingEnabled) { + _dataType = columnDataType.toDataType(); + _storedType = _dataType.getStoredType(); + _rows = rows; + _colId = colId; + _nullPlaceHolder = columnDataType.getNullPlaceholder(); + + if (nullHandlingEnabled) { + RoaringBitmap nullBitmap; + int numRows = rows.size(); + if (_dataType == DataType.UNKNOWN) { + nullBitmap = new RoaringBitmap(); + nullBitmap.add(0L, numRows); + } else { + nullBitmap = new RoaringBitmap(); + for (int i = 0; i < numRows; i++) { + if (rows.get(i)[colId] == null) { + nullBitmap.add(i); + } + } + } + _nullBitmap = nullBitmap.isEmpty() ? null : nullBitmap; + } else { + _nullBitmap = null; + } + } + + @Nullable + @Override + public RoaringBitmap getNullBitmap() { + return _nullBitmap; + } + + @Override + public DataType getValueType() { + return _dataType; + } + + @Override + public boolean isSingleValue() { + return true; + } + + @Nullable + @Override + public Dictionary getDictionary() { + return null; + } + + @Override + public int[] getDictionaryIdsSV() { + throw new UnsupportedOperationException(); + } + + @Override + public int[] getIntValuesSV() { + int numRows = _rows.size(); + int[] values = new int[numRows]; + if (numRows == 0 || _dataType == DataType.UNKNOWN) { + return values; + } + if (_nullBitmap == null) { + if (_storedType.isNumeric()) { + for (int i = 0; i < numRows; i++) { + values[i] = ((Number) _rows.get(i)[_colId]).intValue(); + } + } else if (_storedType == DataType.STRING) { + for (int i = 0; i < numRows; i++) { + values[i] = Integer.parseInt((String) _rows.get(i)[_colId]); + } + } else { + throw new IllegalStateException("Cannot read int values from data type: " + _dataType); + } + } else { + if (_storedType.isNumeric()) { + for (int i = 0; i < numRows; i++) { + Number value = (Number) _rows.get(i)[_colId]; + if (value != null) { + values[i] = value.intValue(); + } + } + } else if (_storedType == DataType.STRING) { + for (int i = 0; i < numRows; i++) { + String value = (String) _rows.get(i)[_colId]; + if (value != null) { + values[i] = Integer.parseInt(value); + } + } + } else { + throw new IllegalStateException("Cannot read int values from data type: " + _dataType); + } + } + return values; + } + + @Override + public long[] getLongValuesSV() { + int numRows = _rows.size(); + long[] values = new long[numRows]; + if (numRows == 0 || _dataType == DataType.UNKNOWN) { + return values; + } + if (_nullBitmap == null) { + if (_storedType.isNumeric()) { + for (int i = 0; i < numRows; i++) { + values[i] = ((Number) _rows.get(i)[_colId]).longValue(); + } + } else if (_storedType == DataType.STRING) { + for (int i = 0; i < numRows; i++) { + values[i] = Long.parseLong((String) _rows.get(i)[_colId]); + } + } else { + throw new IllegalStateException("Cannot read long values from data type: " + _dataType); + } + } else { + if (_storedType.isNumeric()) { + for (int i = 0; i < numRows; i++) { + Number value = (Number) _rows.get(i)[_colId]; + if (value != null) { + values[i] = value.longValue(); + } + } + } else if (_storedType == DataType.STRING) { + for (int i = 0; i < numRows; i++) { + String value = (String) _rows.get(i)[_colId]; + if (value != null) { + values[i] = Long.parseLong(value); + } + } + } else { + throw new IllegalStateException("Cannot read long values from data type: " + _dataType); + } + } + return values; + } + + @Override + public float[] getFloatValuesSV() { + int numRows = _rows.size(); + float[] values = new float[numRows]; + if (numRows == 0 || _dataType == DataType.UNKNOWN) { + return values; + } + if (_nullBitmap == null) { + if (_storedType.isNumeric()) { + for (int i = 0; i < numRows; i++) { + values[i] = ((Number) _rows.get(i)[_colId]).floatValue(); + } + } else if (_storedType == DataType.STRING) { + for (int i = 0; i < numRows; i++) { + values[i] = Float.parseFloat((String) _rows.get(i)[_colId]); + } + } else { + throw new IllegalStateException("Cannot read float values from data type: " + _dataType); + } + } else { + if (_storedType.isNumeric()) { + for (int i = 0; i < numRows; i++) { + Number value = (Number) _rows.get(i)[_colId]; + if (value != null) { + values[i] = value.floatValue(); + } + } + } else if (_storedType == DataType.STRING) { + for (int i = 0; i < numRows; i++) { + String value = (String) _rows.get(i)[_colId]; + if (value != null) { + values[i] = Float.parseFloat(value); + } + } + } else { + throw new IllegalStateException("Cannot read float values from data type: " + _dataType); + } + } + return values; + } + + @Override + public double[] getDoubleValuesSV() { + int numRows = _rows.size(); + double[] values = new double[numRows]; + if (numRows == 0 || _dataType == DataType.UNKNOWN) { + return values; + } + if (_nullBitmap == null) { + if (_storedType.isNumeric()) { + for (int i = 0; i < numRows; i++) { + values[i] = ((Number) _rows.get(i)[_colId]).doubleValue(); + } + } else if (_storedType == DataType.STRING) { + for (int i = 0; i < numRows; i++) { + values[i] = Double.parseDouble((String) _rows.get(i)[_colId]); + } + } else { + throw new IllegalStateException("Cannot read double values from data type: " + _dataType); + } + } else { + if (_storedType.isNumeric()) { + for (int i = 0; i < numRows; i++) { + Number value = (Number) _rows.get(i)[_colId]; + if (value != null) { + values[i] = value.doubleValue(); + } + } + } else if (_storedType == DataType.STRING) { + for (int i = 0; i < numRows; i++) { + String value = (String) _rows.get(i)[_colId]; + if (value != null) { + values[i] = Double.parseDouble(value); + } + } + } else { + throw new IllegalStateException("Cannot read double values from data type: " + _dataType); + } + } + return values; + } + + @Override + public BigDecimal[] getBigDecimalValuesSV() { + int numRows = _rows.size(); + BigDecimal[] values = new BigDecimal[numRows]; + if (numRows == 0) { + return values; + } + if (_dataType == DataType.UNKNOWN) { + Arrays.fill(values, NullValuePlaceHolder.BIG_DECIMAL); + return values; + } + if (_nullBitmap == null) { + switch (_storedType) { + case INT: + case LONG: + for (int i = 0; i < numRows; i++) { + values[i] = BigDecimal.valueOf(((Number) _rows.get(i)[_colId]).longValue()); + } + break; + case FLOAT: + case DOUBLE: + case STRING: + for (int i = 0; i < numRows; i++) { + values[i] = new BigDecimal(_rows.get(i)[_colId].toString()); + } + break; + case BIG_DECIMAL: + for (int i = 0; i < numRows; i++) { + values[i] = (BigDecimal) _rows.get(i)[_colId]; + } + break; + case BYTES: + for (int i = 0; i < numRows; i++) { + values[i] = BigDecimalUtils.deserialize((ByteArray) _rows.get(i)[_colId]); + } + break; + default: + throw new IllegalStateException("Cannot read BigDecimal values from data type: " + _dataType); + } + } else { + switch (_storedType) { + case INT: + case LONG: + for (int i = 0; i < numRows; i++) { + Number value = (Number) _rows.get(i)[_colId]; + values[i] = value != null ? BigDecimal.valueOf(value.longValue()) : NullValuePlaceHolder.BIG_DECIMAL; + } + break; + case FLOAT: + case DOUBLE: + case STRING: + for (int i = 0; i < numRows; i++) { + Object value = _rows.get(i)[_colId]; + values[i] = value != null ? new BigDecimal(value.toString()) : NullValuePlaceHolder.BIG_DECIMAL; + } + break; + case BIG_DECIMAL: + for (int i = 0; i < numRows; i++) { + BigDecimal value = (BigDecimal) _rows.get(i)[_colId]; + values[i] = value != null ? value : NullValuePlaceHolder.BIG_DECIMAL; + } + break; + case BYTES: + for (int i = 0; i < numRows; i++) { + ByteArray value = (ByteArray) _rows.get(i)[_colId]; + values[i] = value != null ? BigDecimalUtils.deserialize(value) : NullValuePlaceHolder.BIG_DECIMAL; + } + break; + default: + throw new IllegalStateException("Cannot read BigDecimal values from data type: " + _dataType); + } + } + return values; + } + + @Override + public String[] getStringValuesSV() { + int numRows = _rows.size(); + String[] values = new String[numRows]; + if (numRows == 0) { + return values; + } + if (_dataType == DataType.UNKNOWN) { + Arrays.fill(values, NullValuePlaceHolder.STRING); + return values; + } + if (_nullBitmap == null) { + for (int i = 0; i < numRows; i++) { + values[i] = _rows.get(i)[_colId].toString(); + } + } else { + for (int i = 0; i < numRows; i++) { + Object value = _rows.get(i)[_colId]; + values[i] = value != null ? value.toString() : NullValuePlaceHolder.STRING; + } + } + return values; + } + + @Override + public byte[][] getBytesValuesSV() { + int numRows = _rows.size(); + byte[][] values = new byte[numRows][]; + if (numRows == 0) { + return values; + } + if (_dataType == DataType.UNKNOWN) { + Arrays.fill(values, NullValuePlaceHolder.BYTES); + return values; + } + if (_nullBitmap == null) { + if (_storedType == DataType.BYTES) { + for (int i = 0; i < numRows; i++) { + values[i] = ((ByteArray) _rows.get(i)[_colId]).getBytes(); + } + } else { + throw new IllegalStateException("Cannot read bytes values from data type: " + _dataType); + } + } else { + if (_storedType == DataType.BYTES) { + for (int i = 0; i < numRows; i++) { + ByteArray value = (ByteArray) _rows.get(i)[_colId]; + values[i] = value != null ? value.getBytes() : NullValuePlaceHolder.BYTES; + } + } else { + throw new IllegalStateException("Cannot read bytes values from data type: " + _dataType); + } + } + return values; + } + + @Override + public int[][] getDictionaryIdsMV() { + throw new UnsupportedOperationException(); + } + + @Override + public int[][] getIntValuesMV() { + int numRows = _rows.size(); + int[][] values = new int[numRows][]; + if (numRows == 0) { + return values; + } + if (_dataType == DataType.UNKNOWN) { + Arrays.fill(values, new int[0]); + return values; + } + + for (int i = 0; i < numRows; i++) { + Object storedValue = _rows.get(i)[_colId]; + if (storedValue instanceof int[]) { + values[i] = (int[]) storedValue; + } else if (storedValue instanceof long[]) { + long[] longArray = (long[]) storedValue; + values[i] = new int[longArray.length]; + ArrayCopyUtils.copy(longArray, values[i], longArray.length); + } else if (storedValue instanceof float[]) { + float[] floatArray = (float[]) storedValue; + values[i] = new int[floatArray.length]; + ArrayCopyUtils.copy(floatArray, values[i], floatArray.length); + } else if (storedValue instanceof double[]) { + double[] doubleArray = (double[]) storedValue; + values[i] = new int[doubleArray.length]; + ArrayCopyUtils.copy(doubleArray, values[i], doubleArray.length); + } else if (storedValue instanceof String[]) { + String[] stringArray = (String[]) storedValue; + values[i] = new int[stringArray.length]; + for (int j = 0; j < stringArray.length; j++) { + values[i][j] = Integer.parseInt(stringArray[j]); + } + } else { + throw new IllegalStateException("Unsupported data type: " + storedValue.getClass().getName()); + } + } + return values; + } + + @Override + public long[][] getLongValuesMV() { + int numRows = _rows.size(); + long[][] values = new long[numRows][]; + if (numRows == 0) { + return values; + } + if (_dataType == DataType.UNKNOWN) { + Arrays.fill(values, new long[0]); + return values; + } + for (int i = 0; i < numRows; i++) { + Object storedValue = _rows.get(i)[_colId]; + if (storedValue instanceof int[]) { + int[] intArray = (int[]) storedValue; + values[i] = new long[intArray.length]; + ArrayCopyUtils.copy(intArray, values[i], intArray.length); + } else if (storedValue instanceof long[]) { + values[i] = (long[]) storedValue; + } else if (storedValue instanceof float[]) { + float[] floatArray = (float[]) storedValue; + values[i] = new long[floatArray.length]; + ArrayCopyUtils.copy(floatArray, values[i], floatArray.length); + } else if (storedValue instanceof double[]) { + double[] doubleArray = (double[]) storedValue; + values[i] = new long[doubleArray.length]; + ArrayCopyUtils.copy(doubleArray, values[i], doubleArray.length); + } else if (storedValue instanceof String[]) { + String[] stringArray = (String[]) storedValue; + values[i] = new long[stringArray.length]; + for (int j = 0; j < stringArray.length; j++) { + values[i][j] = Long.parseLong(stringArray[j]); + } + } else { + throw new IllegalStateException("Unsupported data type: " + storedValue.getClass().getName()); + } + } + return values; + } + + @Override + public float[][] getFloatValuesMV() { + int numRows = _rows.size(); + float[][] values = new float[numRows][]; + if (numRows == 0) { + return values; + } + if (_dataType == DataType.UNKNOWN) { + Arrays.fill(values, new float[0]); + return values; + } + for (int i = 0; i < numRows; i++) { + Object storedValue = _rows.get(i)[_colId]; + if (storedValue instanceof int[]) { + int[] intArray = (int[]) storedValue; + values[i] = new float[intArray.length]; + ArrayCopyUtils.copy(intArray, values[i], intArray.length); + } else if (storedValue instanceof long[]) { + long[] longArray = (long[]) storedValue; + values[i] = new float[longArray.length]; + ArrayCopyUtils.copy(longArray, values[i], longArray.length); + } else if (storedValue instanceof float[]) { + values[i] = (float[]) storedValue; + } else if (storedValue instanceof double[]) { + double[] doubleArray = (double[]) storedValue; + values[i] = new float[doubleArray.length]; + ArrayCopyUtils.copy(doubleArray, values[i], doubleArray.length); + } else if (storedValue instanceof String[]) { + String[] stringArray = (String[]) storedValue; + values[i] = new float[stringArray.length]; + for (int j = 0; j < stringArray.length; j++) { + values[i][j] = Float.parseFloat(stringArray[j]); + } + } else { + throw new IllegalStateException("Unsupported data type: " + storedValue.getClass().getName()); + } + } + return values; + } + + @Override + public double[][] getDoubleValuesMV() { + int numRows = _rows.size(); + double[][] values = new double[numRows][]; + if (numRows == 0) { + return values; + } + if (_dataType == DataType.UNKNOWN) { + Arrays.fill(values, new double[0]); + return values; + } + for (int i = 0; i < numRows; i++) { + Object storedValue = _rows.get(i)[_colId]; + if (storedValue instanceof int[]) { + int[] intArray = (int[]) storedValue; + values[i] = new double[intArray.length]; + ArrayCopyUtils.copy(intArray, values[i], intArray.length); + } else if (storedValue instanceof long[]) { + long[] longArray = (long[]) storedValue; + values[i] = new double[longArray.length]; + ArrayCopyUtils.copy(longArray, values[i], longArray.length); + } else if (storedValue instanceof float[]) { + float[] floatArray = (float[]) storedValue; + values[i] = new double[floatArray.length]; + ArrayCopyUtils.copy(floatArray, values[i], floatArray.length); + } else if (storedValue instanceof double[]) { + values[i] = (double[]) storedValue; + } else if (storedValue instanceof String[]) { + String[] stringArray = (String[]) storedValue; + values[i] = new double[stringArray.length]; + for (int j = 0; j < stringArray.length; j++) { + values[i][j] = Double.parseDouble(stringArray[j]); + } + } else { + throw new IllegalStateException("Unsupported data type: " + storedValue.getClass().getName()); + } + } + return values; + } + + @Override + public String[][] getStringValuesMV() { + int numRows = _rows.size(); + String[][] values = new String[numRows][]; + if (numRows == 0) { + return values; + } + if (_dataType == DataType.UNKNOWN) { + Arrays.fill(values, new String[0]); + return values; + } + for (int i = 0; i < numRows; i++) { + Object storedValue = _rows.get(i)[_colId]; + if (storedValue instanceof int[]) { + int[] intArray = (int[]) storedValue; + values[i] = new String[intArray.length]; + for (int j = 0; j < intArray.length; j++) { + values[i][j] = Integer.toString(intArray[j]); + } + } else if (storedValue instanceof long[]) { + long[] longArray = (long[]) storedValue; + values[i] = new String[longArray.length]; + for (int j = 0; j < longArray.length; j++) { + values[i][j] = Long.toString(longArray[j]); + } + } else if (storedValue instanceof float[]) { + float[] floatArray = (float[]) storedValue; + values[i] = new String[floatArray.length]; + for (int j = 0; j < floatArray.length; j++) { + values[i][j] = Float.toString(floatArray[j]); + } + } else if (storedValue instanceof double[]) { + double[] doubleArray = (double[]) storedValue; + values[i] = new String[doubleArray.length]; + for (int j = 0; j < doubleArray.length; j++) { + values[i][j] = Double.toString(doubleArray[j]); + } + } else if (storedValue instanceof String[]) { + values[i] = (String[]) storedValue; + } else { + throw new IllegalStateException("Unsupported data type: " + storedValue.getClass().getName()); + } + } + return values; + } + + @Override + public byte[][][] getBytesValuesMV() { + throw new UnsupportedOperationException(); + } + + @Override + public int[] getNumMVEntries() { + throw new UnsupportedOperationException(); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/TransformBlockValSet.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/TransformBlockValSet.java index 97a767cf19cd..6ae584a486a2 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/TransformBlockValSet.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/docvalsets/TransformBlockValSet.java @@ -19,12 +19,9 @@ package org.apache.pinot.core.operator.docvalsets; import java.math.BigDecimal; -import java.util.HashSet; -import java.util.Set; import javax.annotation.Nullable; -import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.operator.transform.function.TransformFunction; import org.apache.pinot.core.plan.DocIdSetPlanNode; @@ -42,51 +39,26 @@ *

Caller is responsible for calling the correct method based on the data source metadata for the block value set. */ public class TransformBlockValSet implements BlockValSet { - private final ProjectionBlock _projectionBlock; + private final ValueBlock _valueBlock; private final TransformFunction _transformFunction; - private final ExpressionContext _expression; private boolean _nullBitmapSet; private RoaringBitmap _nullBitmap; private int[] _numMVEntries; - public TransformBlockValSet(ProjectionBlock projectionBlock, TransformFunction transformFunction, - ExpressionContext expression) { - _projectionBlock = projectionBlock; + public TransformBlockValSet(ValueBlock valueBlock, TransformFunction transformFunction) { + _valueBlock = valueBlock; _transformFunction = transformFunction; - _expression = expression; } @Nullable @Override public RoaringBitmap getNullBitmap() { if (!_nullBitmapSet) { - RoaringBitmap nullBitmap = null; - if (_expression.getType() == ExpressionContext.Type.FUNCTION) { - Set columns = new HashSet<>(); - _expression.getFunction().getColumns(columns); - for (String column : columns) { - BlockValSet blockValSet = _projectionBlock.getBlockValueSet(column); - RoaringBitmap columnNullBitmap = blockValSet.getNullBitmap(); - if (columnNullBitmap != null) { - if (nullBitmap == null) { - nullBitmap = columnNullBitmap.clone(); - } - nullBitmap.or(columnNullBitmap); - } - } - } - _nullBitmap = nullBitmap; + _nullBitmap = _transformFunction.getNullBitmap(_valueBlock); _nullBitmapSet = true; } - - // The assumption is that any transformation applied to null values will result in null values. - // Examples: - // CAST(null as STRING) -> null. This is similar to Presto behaviour. - // YEAR(null) -> null. This is similar to Presto behaviour. - // TODO(nhejazi): revisit this part in the future because some transform functions can take null input and return - // non-null result (e.g. isNull()), and we should move this logic into the specific transform function. return _nullBitmap; } @@ -110,7 +82,7 @@ public Dictionary getDictionary() { public int[] getDictionaryIdsSV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.INT, true); - return _transformFunction.transformToDictIdsSV(_projectionBlock); + return _transformFunction.transformToDictIdsSV(_valueBlock); } } @@ -118,7 +90,7 @@ public int[] getDictionaryIdsSV() { public int[] getIntValuesSV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.INT, true); - return _transformFunction.transformToIntValuesSV(_projectionBlock); + return _transformFunction.transformToIntValuesSV(_valueBlock); } } @@ -126,7 +98,7 @@ public int[] getIntValuesSV() { public long[] getLongValuesSV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.LONG, true); - return _transformFunction.transformToLongValuesSV(_projectionBlock); + return _transformFunction.transformToLongValuesSV(_valueBlock); } } @@ -134,7 +106,7 @@ public long[] getLongValuesSV() { public float[] getFloatValuesSV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.FLOAT, true); - return _transformFunction.transformToFloatValuesSV(_projectionBlock); + return _transformFunction.transformToFloatValuesSV(_valueBlock); } } @@ -142,7 +114,7 @@ public float[] getFloatValuesSV() { public double[] getDoubleValuesSV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.DOUBLE, true); - return _transformFunction.transformToDoubleValuesSV(_projectionBlock); + return _transformFunction.transformToDoubleValuesSV(_valueBlock); } } @@ -150,7 +122,7 @@ public double[] getDoubleValuesSV() { public BigDecimal[] getBigDecimalValuesSV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.BIG_DECIMAL, true); - return _transformFunction.transformToBigDecimalValuesSV(_projectionBlock); + return _transformFunction.transformToBigDecimalValuesSV(_valueBlock); } } @@ -158,7 +130,7 @@ public BigDecimal[] getBigDecimalValuesSV() { public String[] getStringValuesSV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.STRING, true); - return _transformFunction.transformToStringValuesSV(_projectionBlock); + return _transformFunction.transformToStringValuesSV(_valueBlock); } } @@ -166,7 +138,7 @@ public String[] getStringValuesSV() { public byte[][] getBytesValuesSV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.BYTES, true); - return _transformFunction.transformToBytesValuesSV(_projectionBlock); + return _transformFunction.transformToBytesValuesSV(_valueBlock); } } @@ -174,7 +146,7 @@ public byte[][] getBytesValuesSV() { public int[][] getDictionaryIdsMV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.INT, false); - return _transformFunction.transformToDictIdsMV(_projectionBlock); + return _transformFunction.transformToDictIdsMV(_valueBlock); } } @@ -182,7 +154,7 @@ public int[][] getDictionaryIdsMV() { public int[][] getIntValuesMV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.INT, false); - return _transformFunction.transformToIntValuesMV(_projectionBlock); + return _transformFunction.transformToIntValuesMV(_valueBlock); } } @@ -190,7 +162,7 @@ public int[][] getIntValuesMV() { public long[][] getLongValuesMV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.LONG, false); - return _transformFunction.transformToLongValuesMV(_projectionBlock); + return _transformFunction.transformToLongValuesMV(_valueBlock); } } @@ -198,7 +170,7 @@ public long[][] getLongValuesMV() { public float[][] getFloatValuesMV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.FLOAT, false); - return _transformFunction.transformToFloatValuesMV(_projectionBlock); + return _transformFunction.transformToFloatValuesMV(_valueBlock); } } @@ -206,7 +178,7 @@ public float[][] getFloatValuesMV() { public double[][] getDoubleValuesMV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.DOUBLE, false); - return _transformFunction.transformToDoubleValuesMV(_projectionBlock); + return _transformFunction.transformToDoubleValuesMV(_valueBlock); } } @@ -214,7 +186,7 @@ public double[][] getDoubleValuesMV() { public String[][] getStringValuesMV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.STRING, false); - return _transformFunction.transformToStringValuesMV(_projectionBlock); + return _transformFunction.transformToStringValuesMV(_valueBlock); } } @@ -222,7 +194,7 @@ public String[][] getStringValuesMV() { public byte[][][] getBytesValuesMV() { try (InvocationScope scope = Tracing.getTracer().createScope(TransformBlockValSet.class)) { recordTransformValues(scope, DataType.BYTES, false); - return _transformFunction.transformToBytesValuesMV(_projectionBlock); + return _transformFunction.transformToBytesValuesMV(_valueBlock); } } @@ -231,7 +203,7 @@ public int[] getNumMVEntries() { if (_numMVEntries == null) { _numMVEntries = new int[DocIdSetPlanNode.MAX_DOC_PER_CALL]; } - int numDocs = _projectionBlock.getNumDocs(); + int numDocs = _valueBlock.getNumDocs(); TransformResultMetadata resultMetadata = _transformFunction.getResultMetadata(); if (resultMetadata.hasDictionary()) { int[][] dictionaryIds = getDictionaryIdsMV(); @@ -279,7 +251,7 @@ public int[] getNumMVEntries() { private void recordTransformValues(InvocationRecording recording, DataType dataType, boolean singleValue) { if (recording.isEnabled()) { - int numDocs = _projectionBlock.getNumDocs(); + int numDocs = _valueBlock.getNumDocs(); recording.setNumDocsScanned(numDocs); recording.setFunctionName(_transformFunction.getName()); recording.setOutputDataType(dataType, singleValue); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/AndFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/AndFilterOperator.java index de2062fd6c68..94545d888906 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/AndFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/AndFilterOperator.java @@ -19,12 +19,17 @@ package org.apache.pinot.core.operator.filter; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; +import javax.annotation.Nullable; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.blocks.FilterBlock; import org.apache.pinot.core.operator.docidsets.AndDocIdSet; -import org.apache.pinot.core.operator.docidsets.FilterBlockDocIdSet; +import org.apache.pinot.core.operator.docidsets.EmptyDocIdSet; +import org.apache.pinot.core.operator.docidsets.MatchAllDocIdSet; +import org.apache.pinot.core.operator.docidsets.NotDocIdSet; +import org.apache.pinot.core.operator.docidsets.OrDocIdSet; import org.apache.pinot.spi.trace.Tracing; import org.roaringbitmap.buffer.BufferFastAggregation; import org.roaringbitmap.buffer.ImmutableRoaringBitmap; @@ -36,23 +41,50 @@ public class AndFilterOperator extends BaseFilterOperator { private final List _filterOperators; private final Map _queryOptions; - public AndFilterOperator(List filterOperators, Map queryOptions) { + public AndFilterOperator(List filterOperators, @Nullable Map queryOptions, + int numDocs, boolean nullHandlingEnabled) { + super(numDocs, nullHandlingEnabled); _filterOperators = filterOperators; _queryOptions = queryOptions; } - public AndFilterOperator(List filterOperators) { - this(filterOperators, null); + @Override + protected BlockDocIdSet getTrues() { + Tracing.activeRecording().setNumChildren(_filterOperators.size()); + List blockDocIdSets = new ArrayList<>(_filterOperators.size()); + for (BaseFilterOperator filterOperator : _filterOperators) { + blockDocIdSets.add(filterOperator.getTrues()); + } + return new AndDocIdSet(blockDocIdSets, _queryOptions); } @Override - protected FilterBlock getNextBlock() { - Tracing.activeRecording().setNumChildren(_filterOperators.size()); - List filterBlockDocIdSets = new ArrayList<>(_filterOperators.size()); + protected BlockDocIdSet getFalses() { + List blockDocIdSets = new ArrayList<>(_filterOperators.size()); for (BaseFilterOperator filterOperator : _filterOperators) { - filterBlockDocIdSets.add(filterOperator.nextBlock().getBlockDocIdSet()); + BlockDocIdSet trues = filterOperator.getTrues(); + if (trues instanceof EmptyDocIdSet) { + return new MatchAllDocIdSet(_numDocs); + } + if (trues instanceof MatchAllDocIdSet) { + continue; + } + if (_nullHandlingEnabled) { + BlockDocIdSet nulls = filterOperator.getNulls(); + if (!(nulls instanceof EmptyDocIdSet)) { + blockDocIdSets.add(new OrDocIdSet(Arrays.asList(trues, nulls), _numDocs)); + continue; + } + } + blockDocIdSets.add(trues); + } + if (blockDocIdSets.isEmpty()) { + return EmptyDocIdSet.getInstance(); } - return new FilterBlock(new AndDocIdSet(filterBlockDocIdSets, _queryOptions)); + if (blockDocIdSets.size() == 1) { + return new NotDocIdSet(blockDocIdSets.get(0), _numDocs); + } + return new NotDocIdSet(new AndDocIdSet(blockDocIdSets, _queryOptions), _numDocs); } @Override @@ -77,7 +109,6 @@ public int getNumMatchingDocs() { return BufferFastAggregation.andCardinality(bitmaps); } - @Override public List getChildOperators() { return new ArrayList<>(_filterOperators); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/BaseColumnFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/BaseColumnFilterOperator.java new file mode 100644 index 000000000000..7b31b13d4456 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/BaseColumnFilterOperator.java @@ -0,0 +1,81 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.filter; + +import java.util.Arrays; +import javax.annotation.Nullable; +import org.apache.pinot.core.common.BlockDocIdSet; +import org.apache.pinot.core.operator.docidsets.AndDocIdSet; +import org.apache.pinot.core.operator.docidsets.BitmapDocIdSet; +import org.apache.pinot.core.operator.docidsets.EmptyDocIdSet; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.segment.spi.datasource.DataSource; +import org.apache.pinot.segment.spi.index.reader.NullValueVectorReader; +import org.roaringbitmap.buffer.ImmutableRoaringBitmap; + + +public abstract class BaseColumnFilterOperator extends BaseFilterOperator { + protected final QueryContext _queryContext; + protected final DataSource _dataSource; + + protected BaseColumnFilterOperator(QueryContext queryContext, DataSource dataSource, int numDocs) { + super(numDocs, queryContext.isNullHandlingEnabled()); + _queryContext = queryContext; + _dataSource = dataSource; + } + + protected abstract BlockDocIdSet getNextBlockWithoutNullHandling(); + + @Override + protected BlockDocIdSet getTrues() { + if (_nullHandlingEnabled) { + ImmutableRoaringBitmap nullBitmap = getNullBitmap(); + if (nullBitmap != null && !nullBitmap.isEmpty()) { + return excludeNulls(getNextBlockWithoutNullHandling(), nullBitmap); + } + } + return getNextBlockWithoutNullHandling(); + } + + @Override + protected BlockDocIdSet getNulls() { + ImmutableRoaringBitmap nullBitmap = getNullBitmap(); + if (nullBitmap != null && !nullBitmap.isEmpty()) { + return new BitmapDocIdSet(nullBitmap, _numDocs); + } else { + return EmptyDocIdSet.getInstance(); + } + } + + private BlockDocIdSet excludeNulls(BlockDocIdSet blockDocIdSet, ImmutableRoaringBitmap nullBitmap) { + return new AndDocIdSet(Arrays.asList(blockDocIdSet, + new BitmapDocIdSet(ImmutableRoaringBitmap.flip(nullBitmap, 0, (long) _numDocs), _numDocs)), + _queryContext.getQueryOptions()); + } + + @Nullable + private ImmutableRoaringBitmap getNullBitmap() { + NullValueVectorReader nullValueVector = _dataSource.getNullValueVector(); + if (nullValueVector != null) { + return nullValueVector.getNullBitmap(); + } else { + return null; + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/BaseFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/BaseFilterOperator.java index eb70c8992c2b..7acbd6ed7c95 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/BaseFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/BaseFilterOperator.java @@ -18,14 +18,27 @@ */ package org.apache.pinot.core.operator.filter; +import java.util.Arrays; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.BaseOperator; import org.apache.pinot.core.operator.blocks.FilterBlock; +import org.apache.pinot.core.operator.docidsets.EmptyDocIdSet; +import org.apache.pinot.core.operator.docidsets.MatchAllDocIdSet; +import org.apache.pinot.core.operator.docidsets.NotDocIdSet; +import org.apache.pinot.core.operator.docidsets.OrDocIdSet; /** * The {@link BaseFilterOperator} class is the base class for all filter operators. */ public abstract class BaseFilterOperator extends BaseOperator { + protected final int _numDocs; + protected final boolean _nullHandlingEnabled; + + public BaseFilterOperator(int numDocs, boolean nullHandlingEnabled) { + _numDocs = numDocs; + _nullHandlingEnabled = nullHandlingEnabled; + } /** * Returns {@code true} if the result is always empty, {@code false} otherwise. @@ -68,4 +81,42 @@ public boolean canProduceBitmaps() { public BitmapCollection getBitmaps() { throw new UnsupportedOperationException(); } + + @Override + protected FilterBlock getNextBlock() { + return new FilterBlock(getTrues()); + } + + /** + * @return document IDs in which the predicate evaluates to true. + */ + protected abstract BlockDocIdSet getTrues(); + + /** + * @return document IDs in which the predicate evaluates to NULL. + */ + protected BlockDocIdSet getNulls() { + return EmptyDocIdSet.getInstance(); + } + + /** + * @return document IDs in which the predicate evaluates to false. + */ + protected BlockDocIdSet getFalses() { + BlockDocIdSet trues = getTrues(); + if (trues instanceof MatchAllDocIdSet) { + return EmptyDocIdSet.getInstance(); + } + if (_nullHandlingEnabled) { + BlockDocIdSet nulls = getNulls(); + if (!(nulls instanceof EmptyDocIdSet)) { + return new NotDocIdSet(new OrDocIdSet(Arrays.asList(trues, nulls), _numDocs), + _numDocs); + } + } + if (trues instanceof EmptyDocIdSet) { + return new MatchAllDocIdSet(_numDocs); + } + return new NotDocIdSet(trues, _numDocs); + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/BitmapBasedFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/BitmapBasedFilterOperator.java index bd8fb41ce1f9..56a08abf969d 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/BitmapBasedFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/BitmapBasedFilterOperator.java @@ -20,92 +20,30 @@ import java.util.Collections; import java.util.List; -import org.apache.pinot.common.request.context.predicate.Predicate; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.blocks.EmptyFilterBlock; -import org.apache.pinot.core.operator.blocks.FilterBlock; import org.apache.pinot.core.operator.docidsets.BitmapDocIdSet; -import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; -import org.apache.pinot.segment.spi.datasource.DataSource; -import org.apache.pinot.segment.spi.index.reader.InvertedIndexReader; -import org.apache.pinot.spi.trace.FilterType; -import org.apache.pinot.spi.trace.InvocationRecording; -import org.apache.pinot.spi.trace.Tracing; import org.roaringbitmap.buffer.ImmutableRoaringBitmap; -import org.roaringbitmap.buffer.MutableRoaringBitmap; -@SuppressWarnings("rawtypes") public class BitmapBasedFilterOperator extends BaseFilterOperator { - private static final String EXPLAIN_NAME = "FILTER_INVERTED_INDEX"; + private static final String EXPLAIN_NAME = "FILTER_BITMAP"; - private final PredicateEvaluator _predicateEvaluator; - private final InvertedIndexReader _invertedIndexReader; private final ImmutableRoaringBitmap _docIds; private final boolean _exclusive; - private final int _numDocs; - - @SuppressWarnings("unchecked") - BitmapBasedFilterOperator(PredicateEvaluator predicateEvaluator, DataSource dataSource, int numDocs) { - _predicateEvaluator = predicateEvaluator; - _invertedIndexReader = (InvertedIndexReader) dataSource.getInvertedIndex(); - _docIds = null; - _exclusive = predicateEvaluator.isExclusive(); - _numDocs = numDocs; - } public BitmapBasedFilterOperator(ImmutableRoaringBitmap docIds, boolean exclusive, int numDocs) { - _predicateEvaluator = null; - _invertedIndexReader = null; + super(numDocs, false); _docIds = docIds; _exclusive = exclusive; - _numDocs = numDocs; } @Override - protected FilterBlock getNextBlock() { - if (_docIds != null) { - if (_exclusive) { - return new FilterBlock(new BitmapDocIdSet(ImmutableRoaringBitmap.flip(_docIds, 0L, _numDocs), _numDocs)); - } else { - return new FilterBlock(new BitmapDocIdSet(_docIds, _numDocs)); - } - } - - int[] dictIds = _exclusive ? _predicateEvaluator.getNonMatchingDictIds() : _predicateEvaluator.getMatchingDictIds(); - int numDictIds = dictIds.length; - if (numDictIds == 0) { - return EmptyFilterBlock.getInstance(); - } - if (numDictIds == 1) { - ImmutableRoaringBitmap docIds = _invertedIndexReader.getDocIds(dictIds[0]); - if (_exclusive) { - if (docIds instanceof MutableRoaringBitmap) { - MutableRoaringBitmap mutableRoaringBitmap = (MutableRoaringBitmap) docIds; - mutableRoaringBitmap.flip(0L, _numDocs); - return new FilterBlock(new BitmapDocIdSet(mutableRoaringBitmap, _numDocs)); - } else { - return new FilterBlock(new BitmapDocIdSet(ImmutableRoaringBitmap.flip(docIds, 0L, _numDocs), _numDocs)); - } - } else { - return new FilterBlock(new BitmapDocIdSet(docIds, _numDocs)); - } + protected BlockDocIdSet getTrues() { + if (_exclusive) { + return new BitmapDocIdSet(ImmutableRoaringBitmap.flip(_docIds, 0L, _numDocs), _numDocs); } else { - ImmutableRoaringBitmap[] bitmaps = new ImmutableRoaringBitmap[numDictIds]; - for (int i = 0; i < numDictIds; i++) { - bitmaps[i] = _invertedIndexReader.getDocIds(dictIds[i]); - } - MutableRoaringBitmap docIds = ImmutableRoaringBitmap.or(bitmaps); - if (_exclusive) { - docIds.flip(0L, _numDocs); - } - InvocationRecording recording = Tracing.activeRecording(); - if (recording.isEnabled()) { - recording.setColumnName(_predicateEvaluator.getPredicate().getLhs().getIdentifier()); - recording.setNumDocsMatchingAfterFilter(docIds.getCardinality()); - recording.setFilter(FilterType.INDEX, String.valueOf(_predicateEvaluator.getPredicateType())); - } - return new FilterBlock(new BitmapDocIdSet(docIds, _numDocs)); + return new BitmapDocIdSet(_docIds, _numDocs); } } @@ -116,36 +54,7 @@ public boolean canOptimizeCount() { @Override public int getNumMatchingDocs() { - int count = 0; - if (_docIds == null) { - int[] dictIds = _exclusive - ? _predicateEvaluator.getNonMatchingDictIds() - : _predicateEvaluator.getMatchingDictIds(); - switch (dictIds.length) { - case 0: - break; - case 1: { - count = _invertedIndexReader.getDocIds(dictIds[0]).getCardinality(); - break; - } - case 2: { - count = ImmutableRoaringBitmap.orCardinality(_invertedIndexReader.getDocIds(dictIds[0]), - _invertedIndexReader.getDocIds(dictIds[1])); - break; - } - default: { - // this could be optimised if the bitmaps are known to be disjoint (as in a single value bitmap index) - MutableRoaringBitmap bitmap = new MutableRoaringBitmap(); - for (int dictId : dictIds) { - bitmap.or(_invertedIndexReader.getDocIds(dictId)); - } - count = bitmap.getCardinality(); - break; - } - } - } else { - count = _docIds.getCardinality(); - } + int count = _docIds.getCardinality(); return _exclusive ? _numDocs - count : count; } @@ -156,34 +65,18 @@ public boolean canProduceBitmaps() { @Override public BitmapCollection getBitmaps() { - if (_docIds == null) { - int[] dictIds = _exclusive - ? _predicateEvaluator.getNonMatchingDictIds() - : _predicateEvaluator.getMatchingDictIds(); - ImmutableRoaringBitmap[] bitmaps = new ImmutableRoaringBitmap[dictIds.length]; - for (int i = 0; i < dictIds.length; i++) { - bitmaps[i] = _invertedIndexReader.getDocIds(dictIds[i]); - } - return new BitmapCollection(_numDocs, _exclusive, bitmaps); - } else { - return new BitmapCollection(_numDocs, _exclusive, _docIds); - } + return new BitmapCollection(_numDocs, _exclusive, _docIds); } @Override + @SuppressWarnings("rawtypes") public List getChildOperators() { return Collections.emptyList(); } @Override public String toExplainString() { - StringBuilder stringBuilder = new StringBuilder(EXPLAIN_NAME).append("(indexLookUp:inverted_index"); - Predicate predicate = _predicateEvaluator != null ? _predicateEvaluator.getPredicate() : null; - if (predicate != null) { - stringBuilder.append(",operator:").append(predicate.getType()); - stringBuilder.append(",predicate:").append(predicate.toString()); - } - return stringBuilder.append(')').toString(); + return EXPLAIN_NAME; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/CombinedFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/CombinedFilterOperator.java index 89b856d9b654..b9aeb1c99d14 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/CombinedFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/CombinedFilterOperator.java @@ -21,10 +21,8 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.blocks.FilterBlock; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.docidsets.AndDocIdSet; -import org.apache.pinot.core.operator.docidsets.FilterBlockDocIdSet; import org.apache.pinot.spi.trace.Tracing; @@ -41,13 +39,17 @@ public class CombinedFilterOperator extends BaseFilterOperator { public CombinedFilterOperator(BaseFilterOperator mainFilterOperator, BaseFilterOperator subFilterOperator, Map queryOptions) { + // This filter operator does not support AND/OR/NOT operations. + super(0, false); + assert !mainFilterOperator.isResultEmpty() && !mainFilterOperator.isResultMatchingAll() + && !subFilterOperator.isResultEmpty() && !subFilterOperator.isResultMatchingAll(); _mainFilterOperator = mainFilterOperator; _subFilterOperator = subFilterOperator; _queryOptions = queryOptions; } @Override - public List getChildOperators() { + public List getChildOperators() { return Arrays.asList(_mainFilterOperator, _subFilterOperator); } @@ -57,10 +59,10 @@ public String toExplainString() { } @Override - protected FilterBlock getNextBlock() { + protected BlockDocIdSet getTrues() { Tracing.activeRecording().setNumChildren(2); - FilterBlockDocIdSet mainFilterDocIdSet = _mainFilterOperator.nextBlock().getNonScanFilterBLockDocIdSet(); - FilterBlockDocIdSet subFilterDocIdSet = _subFilterOperator.nextBlock().getBlockDocIdSet(); - return new FilterBlock(new AndDocIdSet(Arrays.asList(mainFilterDocIdSet, subFilterDocIdSet), _queryOptions)); + BlockDocIdSet mainFilterDocIdSet = _mainFilterOperator.nextBlock().getNonScanFilterBLockDocIdSet(); + BlockDocIdSet subFilterDocIdSet = _subFilterOperator.nextBlock().getBlockDocIdSet(); + return new AndDocIdSet(Arrays.asList(mainFilterDocIdSet, subFilterDocIdSet), _queryOptions); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/EmptyFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/EmptyFilterOperator.java index 974eff26f432..55144918ca59 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/EmptyFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/EmptyFilterOperator.java @@ -20,9 +20,10 @@ import java.util.Collections; import java.util.List; +import org.apache.pinot.core.common.BlockDocIdSet; +import org.apache.pinot.core.common.ExplainPlanRows; import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.blocks.EmptyFilterBlock; -import org.apache.pinot.core.operator.blocks.FilterBlock; +import org.apache.pinot.core.operator.docidsets.EmptyDocIdSet; /** @@ -30,6 +31,8 @@ */ public final class EmptyFilterOperator extends BaseFilterOperator { private EmptyFilterOperator() { + // We will never call its getFalses() method. + super(0, false); } @@ -57,8 +60,8 @@ public int getNumMatchingDocs() { } @Override - protected FilterBlock getNextBlock() { - return EmptyFilterBlock.getInstance(); + protected BlockDocIdSet getTrues() { + return EmptyDocIdSet.getInstance(); } @@ -71,4 +74,9 @@ public String toExplainString() { public List getChildOperators() { return Collections.emptyList(); } + + @Override + public void prepareForExplainPlan(ExplainPlanRows explainPlanRows) { + explainPlanRows.setHasEmptyFilter(true); + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/ExpressionFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/ExpressionFilterOperator.java index 9342dc804722..7c0fe3b829c1 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/ExpressionFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/ExpressionFilterOperator.java @@ -26,13 +26,18 @@ import java.util.Set; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.predicate.Predicate; +import org.apache.pinot.common.utils.HashUtil; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.blocks.FilterBlock; -import org.apache.pinot.core.operator.docidsets.ExpressionFilterDocIdSet; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.dociditerators.ExpressionScanDocIdIterator; +import org.apache.pinot.core.operator.docidsets.ExpressionDocIdSet; +import org.apache.pinot.core.operator.docidsets.NotDocIdSet; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluatorProvider; import org.apache.pinot.core.operator.transform.function.TransformFunction; import org.apache.pinot.core.operator.transform.function.TransformFunctionFactory; +import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.segment.spi.datasource.DataSource; @@ -40,37 +45,70 @@ public class ExpressionFilterOperator extends BaseFilterOperator { private static final String EXPLAIN_NAME = "FILTER_EXPRESSION"; - private final int _numDocs; + private final QueryContext _queryContext; private final Map _dataSourceMap; private final TransformFunction _transformFunction; + private final Predicate.Type _predicateType; private final PredicateEvaluator _predicateEvaluator; - public ExpressionFilterOperator(IndexSegment segment, Predicate predicate, int numDocs) { - _numDocs = numDocs; + public ExpressionFilterOperator(IndexSegment segment, QueryContext queryContext, Predicate predicate, int numDocs) { + super(numDocs, queryContext.isNullHandlingEnabled()); + _queryContext = queryContext; - _dataSourceMap = new HashMap<>(); Set columns = new HashSet<>(); ExpressionContext lhs = predicate.getLhs(); lhs.getColumns(columns); - for (String column : columns) { - _dataSourceMap.put(column, segment.getDataSource(column)); + int mapCapacity = HashUtil.getHashMapCapacity(columns.size()); + _dataSourceMap = new HashMap<>(mapCapacity); + Map columnContextMap = new HashMap<>(mapCapacity); + columns.forEach(column -> { + DataSource dataSource = segment.getDataSource(column); + _dataSourceMap.put(column, dataSource); + columnContextMap.put(column, ColumnContext.fromDataSource(dataSource)); + }); + _transformFunction = TransformFunctionFactory.get(lhs, columnContextMap, _queryContext); + _predicateType = predicate.getType(); + if (_predicateType == Predicate.Type.IS_NULL || _predicateType == Predicate.Type.IS_NOT_NULL) { + _predicateEvaluator = null; + } else { + _predicateEvaluator = + PredicateEvaluatorProvider.getPredicateEvaluator(predicate, _transformFunction.getDictionary(), + _transformFunction.getResultMetadata().getDataType()); } + } - _transformFunction = TransformFunctionFactory.get(lhs, _dataSourceMap); - _predicateEvaluator = PredicateEvaluatorProvider - .getPredicateEvaluator(predicate, _transformFunction.getDictionary(), - _transformFunction.getResultMetadata().getDataType()); + @Override + protected BlockDocIdSet getTrues() { + if (_predicateType == Predicate.Type.IS_NULL) { + return getNulls(); + } else if (_predicateType == Predicate.Type.IS_NOT_NULL) { + return new NotDocIdSet(getNulls(), _numDocs); + } else { + return new ExpressionDocIdSet(_transformFunction, _predicateEvaluator, _dataSourceMap, _numDocs, + _queryContext.isNullHandlingEnabled(), ExpressionScanDocIdIterator.PredicateEvaluationResult.TRUE); + } } @Override - protected FilterBlock getNextBlock() { - return new FilterBlock( - new ExpressionFilterDocIdSet(_transformFunction, _predicateEvaluator, _dataSourceMap, _numDocs)); + protected BlockDocIdSet getNulls() { + return new ExpressionDocIdSet(_transformFunction, null, _dataSourceMap, _numDocs, + _queryContext.isNullHandlingEnabled(), ExpressionScanDocIdIterator.PredicateEvaluationResult.NULL); } + @Override + protected BlockDocIdSet getFalses() { + if (_predicateType == Predicate.Type.IS_NULL) { + return new NotDocIdSet(getNulls(), _numDocs); + } else if (_predicateType == Predicate.Type.IS_NOT_NULL) { + return getNulls(); + } else { + return new ExpressionDocIdSet(_transformFunction, _predicateEvaluator, _dataSourceMap, _numDocs, + _queryContext.isNullHandlingEnabled(), ExpressionScanDocIdIterator.PredicateEvaluationResult.FALSE); + } + } @Override - public List getChildOperators() { + public List> getChildOperators() { return Collections.emptyList(); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/FilterOperatorUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/FilterOperatorUtils.java index c343dcea88ec..ac30591c6070 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/FilterOperatorUtils.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/FilterOperatorUtils.java @@ -21,94 +21,264 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.OptionalInt; import org.apache.pinot.common.request.context.predicate.Predicate; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.datasource.DataSource; +import org.apache.pinot.segment.spi.index.reader.NullValueVectorReader; +import org.apache.pinot.spi.config.table.FieldConfig; +import org.roaringbitmap.buffer.ImmutableRoaringBitmap; public class FilterOperatorUtils { + + private static Implementation _instance = new DefaultImplementation(); + private FilterOperatorUtils() { } - /** - * Returns the leaf filter operator (i.e. not {@link AndFilterOperator} or {@link OrFilterOperator}). - */ - public static BaseFilterOperator getLeafFilterOperator(PredicateEvaluator predicateEvaluator, DataSource dataSource, - int numDocs) { - return getLeafFilterOperator(predicateEvaluator, dataSource, numDocs, false); + public static void setImplementation(Implementation newImplementation) { + _instance = newImplementation; } - /** - * Returns the leaf filter operator (i.e. not {@link AndFilterOperator} or {@link OrFilterOperator}). - */ - public static BaseFilterOperator getLeafFilterOperator(PredicateEvaluator predicateEvaluator, DataSource dataSource, - int numDocs, boolean nullHandlingEnabled) { - if (predicateEvaluator.isAlwaysFalse()) { - return EmptyFilterOperator.getInstance(); - } else if (predicateEvaluator.isAlwaysTrue()) { - return new MatchAllFilterOperator(numDocs); + public interface Implementation { + + /** + * Returns the leaf filter operator (i.e. not {@link AndFilterOperator} or {@link OrFilterOperator}). + */ + BaseFilterOperator getLeafFilterOperator(QueryContext queryContext, PredicateEvaluator predicateEvaluator, + DataSource dataSource, int numDocs); + + /** + * Returns the AND filter operator or equivalent filter operator. + */ + BaseFilterOperator getAndFilterOperator(QueryContext queryContext, + List filterOperators, int numDocs); + + /** + * Returns the OR filter operator or equivalent filter operator. + */ + BaseFilterOperator getOrFilterOperator(QueryContext queryContext, + List filterOperators, int numDocs); + + /** + * Returns the NOT filter operator or equivalent filter operator. + */ + BaseFilterOperator getNotFilterOperator(QueryContext queryContext, BaseFilterOperator filterOperator, + int numDocs); + } + + public static class DefaultImplementation implements Implementation { + @Override + public BaseFilterOperator getLeafFilterOperator(QueryContext queryContext, PredicateEvaluator predicateEvaluator, + DataSource dataSource, int numDocs) { + if (predicateEvaluator.isAlwaysFalse()) { + return EmptyFilterOperator.getInstance(); + } else if (predicateEvaluator.isAlwaysTrue()) { + if (queryContext.isNullHandlingEnabled()) { + NullValueVectorReader nullValueVectorReader = dataSource.getNullValueVector(); + if (nullValueVectorReader != null) { + ImmutableRoaringBitmap nullBitmap = nullValueVectorReader.getNullBitmap(); + if (nullBitmap != null && !nullBitmap.isEmpty()) { + return new BitmapBasedFilterOperator(nullBitmap, true, numDocs); + } + } + } + return new MatchAllFilterOperator(numDocs); + } + + // Currently sorted index based filtering is supported only for + // dictionary encoded columns. The on-disk segment metadata + // will indicate if the column is sorted or not regardless of + // whether it is raw or dictionary encoded. Here when creating + // the filter operator, we need to make sure that sort filter + // operator is used only if the column is sorted and has dictionary. + Predicate.Type predicateType = predicateEvaluator.getPredicateType(); + if (predicateType == Predicate.Type.RANGE) { + if (dataSource.getDataSourceMetadata().isSorted() && dataSource.getDictionary() != null + && queryContext.isIndexUseAllowed(dataSource, FieldConfig.IndexType.SORTED)) { + return new SortedIndexBasedFilterOperator(queryContext, predicateEvaluator, dataSource, numDocs); + } + if (RangeIndexBasedFilterOperator.canEvaluate(predicateEvaluator, dataSource) + && queryContext.isIndexUseAllowed(dataSource, FieldConfig.IndexType.RANGE)) { + return new RangeIndexBasedFilterOperator(queryContext, predicateEvaluator, dataSource, numDocs); + } + return new ScanBasedFilterOperator(queryContext, predicateEvaluator, dataSource, numDocs); + } else if (predicateType == Predicate.Type.REGEXP_LIKE) { + if (dataSource.getFSTIndex() != null && dataSource.getDataSourceMetadata().isSorted() + && queryContext.isIndexUseAllowed(dataSource, FieldConfig.IndexType.SORTED)) { + return new SortedIndexBasedFilterOperator(queryContext, predicateEvaluator, dataSource, numDocs); + } + if (dataSource.getFSTIndex() != null && dataSource.getInvertedIndex() != null + && queryContext.isIndexUseAllowed(dataSource, FieldConfig.IndexType.INVERTED)) { + return new InvertedIndexFilterOperator(queryContext, predicateEvaluator, dataSource, numDocs); + } + return new ScanBasedFilterOperator(queryContext, predicateEvaluator, dataSource, numDocs); + } else { + if (dataSource.getDataSourceMetadata().isSorted() && dataSource.getDictionary() != null + && queryContext.isIndexUseAllowed(dataSource, FieldConfig.IndexType.SORTED)) { + return new SortedIndexBasedFilterOperator(queryContext, predicateEvaluator, dataSource, numDocs); + } + if (dataSource.getInvertedIndex() != null + && queryContext.isIndexUseAllowed(dataSource, FieldConfig.IndexType.INVERTED)) { + return new InvertedIndexFilterOperator(queryContext, predicateEvaluator, dataSource, numDocs); + } + if (RangeIndexBasedFilterOperator.canEvaluate(predicateEvaluator, dataSource) + && queryContext.isIndexUseAllowed(dataSource, FieldConfig.IndexType.RANGE)) { + return new RangeIndexBasedFilterOperator(queryContext, predicateEvaluator, dataSource, numDocs); + } + return new ScanBasedFilterOperator(queryContext, predicateEvaluator, dataSource, numDocs); + } } - // Currently sorted index based filtering is supported only for - // dictionary encoded columns. The on-disk segment metadata - // will indicate if the column is sorted or not regardless of - // whether it is raw or dictionary encoded. Here when creating - // the filter operator, we need to make sure that sort filter - // operator is used only if the column is sorted and has dictionary. - Predicate.Type predicateType = predicateEvaluator.getPredicateType(); - if (predicateType == Predicate.Type.RANGE) { - if (dataSource.getDataSourceMetadata().isSorted() && dataSource.getDictionary() != null) { - return new SortedIndexBasedFilterOperator(predicateEvaluator, dataSource, numDocs); + @Override + public BaseFilterOperator getAndFilterOperator(QueryContext queryContext, List filterOperators, + int numDocs) { + List childFilterOperators = new ArrayList<>(filterOperators.size()); + for (BaseFilterOperator filterOperator : filterOperators) { + if (filterOperator.isResultEmpty()) { + return EmptyFilterOperator.getInstance(); + } else if (!filterOperator.isResultMatchingAll()) { + childFilterOperators.add(filterOperator); + } + } + int numChildFilterOperators = childFilterOperators.size(); + if (numChildFilterOperators == 0) { + // Return match all filter operator if all child filter operators match all records + return new MatchAllFilterOperator(numDocs); + } else if (numChildFilterOperators == 1) { + // Return the child filter operator if only one left + return childFilterOperators.get(0); + } else { + // Return the AND filter operator with re-ordered child filter operators + reorderAndFilterChildOperators(queryContext, childFilterOperators); + return new AndFilterOperator(childFilterOperators, queryContext.getQueryOptions(), numDocs, + queryContext.isNullHandlingEnabled()); } - if (dataSource.getRangeIndex() != null) { - return new RangeIndexBasedFilterOperator(predicateEvaluator, dataSource, numDocs); + } + + @Override + public BaseFilterOperator getOrFilterOperator(QueryContext queryContext, + List filterOperators, int numDocs) { + List childFilterOperators = new ArrayList<>(filterOperators.size()); + for (BaseFilterOperator filterOperator : filterOperators) { + if (filterOperator.isResultMatchingAll()) { + return new MatchAllFilterOperator(numDocs); + } else if (!filterOperator.isResultEmpty()) { + childFilterOperators.add(filterOperator); + } } - return new ScanBasedFilterOperator(predicateEvaluator, dataSource, numDocs, nullHandlingEnabled); - } else if (predicateType == Predicate.Type.REGEXP_LIKE) { - if (dataSource.getFSTIndex() != null && dataSource.getDataSourceMetadata().isSorted()) { - return new SortedIndexBasedFilterOperator(predicateEvaluator, dataSource, numDocs); + int numChildFilterOperators = childFilterOperators.size(); + if (numChildFilterOperators == 0) { + // Return empty filter operator if all child filter operators's result is empty + return EmptyFilterOperator.getInstance(); + } else if (numChildFilterOperators == 1) { + // Return the child filter operator if only one left + return childFilterOperators.get(0); + } else { + // Return the OR filter operator with child filter operators + return new OrFilterOperator(childFilterOperators, queryContext.getQueryOptions(), numDocs, + queryContext.isNullHandlingEnabled()); } - if (dataSource.getFSTIndex() != null && dataSource.getInvertedIndex() != null) { - return new BitmapBasedFilterOperator(predicateEvaluator, dataSource, numDocs); + } + + @Override + public BaseFilterOperator getNotFilterOperator(QueryContext queryContext, BaseFilterOperator filterOperator, + int numDocs) { + if (filterOperator.isResultMatchingAll()) { + return EmptyFilterOperator.getInstance(); + } else if (filterOperator.isResultEmpty()) { + return new MatchAllFilterOperator(numDocs); } - return new ScanBasedFilterOperator(predicateEvaluator, dataSource, numDocs, nullHandlingEnabled); - } else { - if (dataSource.getDataSourceMetadata().isSorted() && dataSource.getDictionary() != null) { - return new SortedIndexBasedFilterOperator(predicateEvaluator, dataSource, numDocs); + + return new NotFilterOperator(filterOperator, numDocs, queryContext.isNullHandlingEnabled()); + } + + + /** + * For AND filter operator, reorders its child filter operators based on their cost and puts the ones with + * inverted index first in order to reduce the number of documents to be processed. + *

Special filter operators such as {@link MatchAllFilterOperator} and {@link EmptyFilterOperator} should be + * removed from the list before calling this method. + */ + protected void reorderAndFilterChildOperators(QueryContext queryContext, List filterOperators) { + filterOperators.sort(new Comparator() { + @Override + public int compare(BaseFilterOperator o1, BaseFilterOperator o2) { + return getPriority(o1) - getPriority(o2); + } + + int getPriority(BaseFilterOperator filterOperator) { + if (filterOperator instanceof PrioritizedFilterOperator) { + OptionalInt priority = ((PrioritizedFilterOperator) filterOperator).getPriority(); + if (priority.isPresent()) { + return priority.getAsInt(); + } + } + if (filterOperator instanceof SortedIndexBasedFilterOperator) { + return PrioritizedFilterOperator.HIGH_PRIORITY; + } + if (filterOperator instanceof BitmapBasedFilterOperator) { + return PrioritizedFilterOperator.MEDIUM_PRIORITY; + } + if (filterOperator instanceof RangeIndexBasedFilterOperator + || filterOperator instanceof TextContainsFilterOperator + || filterOperator instanceof TextMatchFilterOperator || filterOperator instanceof JsonMatchFilterOperator + || filterOperator instanceof H3IndexFilterOperator + || filterOperator instanceof H3InclusionIndexFilterOperator) { + return PrioritizedFilterOperator.LOW_PRIORITY; + } + if (filterOperator instanceof AndFilterOperator) { + return PrioritizedFilterOperator.AND_PRIORITY; + } + if (filterOperator instanceof OrFilterOperator) { + return PrioritizedFilterOperator.OR_PRIORITY; + } + if (filterOperator instanceof NotFilterOperator) { + return getPriority(((NotFilterOperator) filterOperator).getChildFilterOperator()); + } + if (filterOperator instanceof ScanBasedFilterOperator) { + int basePriority = PrioritizedFilterOperator.SCAN_PRIORITY; + return getScanBasedFilterPriority(queryContext, (ScanBasedFilterOperator) filterOperator, basePriority); + } + if (filterOperator instanceof ExpressionFilterOperator) { + return PrioritizedFilterOperator.EXPRESSION_PRIORITY; + } + return PrioritizedFilterOperator.UNKNOWN_FILTER_PRIORITY; + } + }); + } + + public static int getScanBasedFilterPriority(QueryContext queryContext, + ScanBasedFilterOperator scanBasedFilterOperator, int basePriority) { + if (queryContext.isSkipScanFilterReorder()) { + return basePriority; } - if (dataSource.getInvertedIndex() != null) { - return new BitmapBasedFilterOperator(predicateEvaluator, dataSource, numDocs); + + if (scanBasedFilterOperator.getDataSourceMetadata().isSingleValue()) { + return basePriority; + } else { + // Lower priority for multi-value column + return basePriority + 50; } - return new ScanBasedFilterOperator(predicateEvaluator, dataSource, numDocs, nullHandlingEnabled); } } + /** + * Returns the leaf filter operator (i.e. not {@link AndFilterOperator} or {@link OrFilterOperator}). + */ + public static BaseFilterOperator getLeafFilterOperator(QueryContext queryContext, + PredicateEvaluator predicateEvaluator, DataSource dataSource, int numDocs) { + return _instance.getLeafFilterOperator(queryContext, predicateEvaluator, dataSource, numDocs); + } + /** * Returns the AND filter operator or equivalent filter operator. */ public static BaseFilterOperator getAndFilterOperator(QueryContext queryContext, List filterOperators, int numDocs) { - List childFilterOperators = new ArrayList<>(filterOperators.size()); - for (BaseFilterOperator filterOperator : filterOperators) { - if (filterOperator.isResultEmpty()) { - return EmptyFilterOperator.getInstance(); - } else if (!filterOperator.isResultMatchingAll()) { - childFilterOperators.add(filterOperator); - } - } - int numChildFilterOperators = childFilterOperators.size(); - if (numChildFilterOperators == 0) { - // Return match all filter operator if all child filter operators match all records - return new MatchAllFilterOperator(numDocs); - } else if (numChildFilterOperators == 1) { - // Return the child filter operator if only one left - return childFilterOperators.get(0); - } else { - // Return the AND filter operator with re-ordered child filter operators - FilterOperatorUtils.reorderAndFilterChildOperators(queryContext, childFilterOperators); - return new AndFilterOperator(childFilterOperators, queryContext.getQueryOptions()); - } + return _instance.getAndFilterOperator(queryContext, filterOperators, numDocs); } /** @@ -116,25 +286,7 @@ public static BaseFilterOperator getAndFilterOperator(QueryContext queryContext, */ public static BaseFilterOperator getOrFilterOperator(QueryContext queryContext, List filterOperators, int numDocs) { - List childFilterOperators = new ArrayList<>(filterOperators.size()); - for (BaseFilterOperator filterOperator : filterOperators) { - if (filterOperator.isResultMatchingAll()) { - return new MatchAllFilterOperator(numDocs); - } else if (!filterOperator.isResultEmpty()) { - childFilterOperators.add(filterOperator); - } - } - int numChildFilterOperators = childFilterOperators.size(); - if (numChildFilterOperators == 0) { - // Return empty filter operator if all child filter operators's result is empty - return EmptyFilterOperator.getInstance(); - } else if (numChildFilterOperators == 1) { - // Return the child filter operator if only one left - return childFilterOperators.get(0); - } else { - // Return the OR filter operator with child filter operators - return new OrFilterOperator(childFilterOperators, numDocs); - } + return _instance.getOrFilterOperator(queryContext, filterOperators, numDocs); } /** @@ -142,84 +294,6 @@ public static BaseFilterOperator getOrFilterOperator(QueryContext queryContext, */ public static BaseFilterOperator getNotFilterOperator(QueryContext queryContext, BaseFilterOperator filterOperator, int numDocs) { - if (filterOperator.isResultMatchingAll()) { - return EmptyFilterOperator.getInstance(); - } else if (filterOperator.isResultEmpty()) { - return new MatchAllFilterOperator(numDocs); - } - - return new NotFilterOperator(filterOperator, numDocs); - } - - /** - * For AND filter operator, reorders its child filter operators based on the their cost and puts the ones with - * inverted index first in order to reduce the number of documents to be processed. - *

Special filter operators such as {@link MatchAllFilterOperator} and {@link EmptyFilterOperator} should be - * removed from the list before calling this method. - */ - private static void reorderAndFilterChildOperators(QueryContext queryContext, - List filterOperators) { - filterOperators.sort(new Comparator() { - @Override - public int compare(BaseFilterOperator o1, BaseFilterOperator o2) { - return getPriority(o1) - getPriority(o2); - } - - int getPriority(BaseFilterOperator filterOperator) { - if (filterOperator instanceof SortedIndexBasedFilterOperator) { - return 0; - } - if (filterOperator instanceof BitmapBasedFilterOperator) { - return 1; - } - if (filterOperator instanceof RangeIndexBasedFilterOperator - || filterOperator instanceof TextContainsFilterOperator || filterOperator instanceof TextMatchFilterOperator - || filterOperator instanceof JsonMatchFilterOperator || filterOperator instanceof H3IndexFilterOperator - || filterOperator instanceof H3InclusionIndexFilterOperator) { - return 2; - } - if (filterOperator instanceof AndFilterOperator) { - return 3; - } - if (filterOperator instanceof OrFilterOperator) { - return 4; - } - if (filterOperator instanceof NotFilterOperator) { - return getPriority(((NotFilterOperator) filterOperator).getChildFilterOperator()); - } - if (filterOperator instanceof ScanBasedFilterOperator) { - return getScanBasedFilterPriority(queryContext, (ScanBasedFilterOperator) filterOperator, 5); - } - if (filterOperator instanceof ExpressionFilterOperator) { - return 10; - } - throw new IllegalStateException(filterOperator.getClass().getSimpleName() - + " should not be reordered, remove it from the list before calling this method"); - } - }); - } - - /** - * Returns the priority for scan based filtering. Multivalue column evaluation is costly, so - * reorder such that multivalue columns are evaluated after single value columns. - * - * TODO: additional cost based prioritization to be added - * - * @param scanBasedFilterOperator the filter operator to prioritize - * @param queryContext query context - * @return the priority to be associated with the filter - */ - private static int getScanBasedFilterPriority(QueryContext queryContext, - ScanBasedFilterOperator scanBasedFilterOperator, int basePriority) { - if (queryContext.isSkipScanFilterReorder()) { - return basePriority; - } - - if (scanBasedFilterOperator.getDataSourceMetadata().isSingleValue()) { - return basePriority; - } else { - // Lower priority for multi-value column - return basePriority + 1; - } + return _instance.getNotFilterOperator(queryContext, filterOperator, numDocs); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/H3InclusionIndexFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/H3InclusionIndexFilterOperator.java index 081afdf27164..5861d027e792 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/H3InclusionIndexFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/H3InclusionIndexFilterOperator.java @@ -26,8 +26,8 @@ import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.predicate.EqPredicate; import org.apache.pinot.common.request.context.predicate.Predicate; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.blocks.FilterBlock; import org.apache.pinot.core.operator.dociditerators.ScanBasedDocIdIterator; import org.apache.pinot.core.operator.docidsets.BitmapDocIdSet; import org.apache.pinot.core.query.request.context.QueryContext; @@ -36,7 +36,6 @@ import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.segment.spi.index.reader.H3IndexReader; import org.apache.pinot.spi.utils.BooleanUtils; -import org.apache.pinot.spi.utils.BytesUtils; import org.locationtech.jts.geom.Geometry; import org.roaringbitmap.buffer.BufferFastAggregation; import org.roaringbitmap.buffer.ImmutableRoaringBitmap; @@ -47,24 +46,22 @@ * A filter operator that uses H3 index for geospatial data inclusion */ public class H3InclusionIndexFilterOperator extends BaseFilterOperator { - private static final String EXPLAIN_NAME = "INCLUSION_FILTER_H3_INDEX"; private static final String LITERAL_H3_CELLS_CACHE_NAME = "st_contain_literal_h3_cells"; private final IndexSegment _segment; + private final QueryContext _queryContext; private final Predicate _predicate; - private final int _numDocs; private final H3IndexReader _h3IndexReader; private final Geometry _geometry; private final boolean _isPositiveCheck; - private final QueryContext _queryContext; - public H3InclusionIndexFilterOperator(IndexSegment segment, Predicate predicate, QueryContext queryContext, + public H3InclusionIndexFilterOperator(IndexSegment segment, QueryContext queryContext, Predicate predicate, int numDocs) { + super(numDocs, false); _segment = segment; - _predicate = predicate; - _numDocs = numDocs; _queryContext = queryContext; + _predicate = predicate; List arguments = predicate.getLhs().getFunction().getArguments(); EqPredicate eqPredicate = (EqPredicate) predicate; @@ -72,42 +69,40 @@ public H3InclusionIndexFilterOperator(IndexSegment segment, Predicate predicate, if (arguments.get(0).getType() == ExpressionContext.Type.IDENTIFIER) { _h3IndexReader = segment.getDataSource(arguments.get(0).getIdentifier()).getH3Index(); - _geometry = GeometrySerializer.deserialize(BytesUtils.toBytes(arguments.get(1).getLiteralString())); + _geometry = GeometrySerializer.deserialize(arguments.get(1).getLiteral().getBytesValue()); } else { _h3IndexReader = segment.getDataSource(arguments.get(1).getIdentifier()).getH3Index(); - _geometry = GeometrySerializer.deserialize(BytesUtils.toBytes(arguments.get(0).getLiteralString())); + _geometry = GeometrySerializer.deserialize(arguments.get(0).getLiteral().getBytesValue()); } // must be some h3 index assert _h3IndexReader != null : "the column must have H3 index setup."; } @Override - protected FilterBlock getNextBlock() { - // get the set of H3 cells at the specified resolution which completely cover the input shape and potential cover. - Pair fullCoverAndPotentialCoverCells = _queryContext - .getOrComputeSharedValue(Pair.class, LITERAL_H3_CELLS_CACHE_NAME, + protected BlockDocIdSet getTrues() { + // NOTE: LHS is the fully covered cells, RHS is the potentially covered cells (excluding fully covered cells). + Pair fullyAndPotentiallyCoveredCells = + _queryContext.getOrComputeSharedValue(Pair.class, LITERAL_H3_CELLS_CACHE_NAME, k -> H3Utils.coverGeometryInH3(_geometry, _h3IndexReader.getH3IndexResolution().getLowestResolution())); - LongSet fullyCoverH3Cells = fullCoverAndPotentialCoverCells.getLeft(); - LongSet potentialCoverH3Cells = fullCoverAndPotentialCoverCells.getRight(); + LongSet fullyCoveredCells = fullyAndPotentiallyCoveredCells.getLeft(); + LongSet potentiallyCoveredCells = fullyAndPotentiallyCoveredCells.getRight(); - int i = 0; - ImmutableRoaringBitmap[] fullMatchDocIds = new ImmutableRoaringBitmap[fullyCoverH3Cells.size()]; - LongIterator fullyCoverH3CellsIterator = fullyCoverH3Cells.iterator(); - while (fullyCoverH3CellsIterator.hasNext()) { - fullMatchDocIds[i++] = _h3IndexReader.getDocIds(fullyCoverH3CellsIterator.nextLong()); + int numFullyCoveredCells = fullyCoveredCells.size(); + ImmutableRoaringBitmap[] fullMatchDocIds = new ImmutableRoaringBitmap[numFullyCoveredCells]; + LongIterator fullyCoveredCellsIterator = fullyCoveredCells.iterator(); + for (int i = 0; i < numFullyCoveredCells; i++) { + fullMatchDocIds[i] = _h3IndexReader.getDocIds(fullyCoveredCellsIterator.nextLong()); } MutableRoaringBitmap fullMatch = BufferFastAggregation.or(fullMatchDocIds); - // remove full match from potential match to get potential not match cells. - i = 0; - potentialCoverH3Cells.removeAll(fullyCoverH3Cells); - ImmutableRoaringBitmap[] potentialNotMatchDocIds = new ImmutableRoaringBitmap[potentialCoverH3Cells.size()]; - LongIterator potentialMatchH3CellsIterator = potentialCoverH3Cells.iterator(); - while (potentialMatchH3CellsIterator.hasNext()) { - potentialNotMatchDocIds[i++] = _h3IndexReader.getDocIds(potentialMatchH3CellsIterator.nextLong()); + int numPotentiallyCoveredCells = potentiallyCoveredCells.size(); + ImmutableRoaringBitmap[] potentialMatchDocIds = new ImmutableRoaringBitmap[numPotentiallyCoveredCells]; + LongIterator potentiallyCoveredCellsIterator = potentiallyCoveredCells.iterator(); + for (int i = 0; i < numPotentiallyCoveredCells; i++) { + potentialMatchDocIds[i] = _h3IndexReader.getDocIds(potentiallyCoveredCellsIterator.nextLong()); } - MutableRoaringBitmap potentialMatch = BufferFastAggregation.or(potentialNotMatchDocIds); + MutableRoaringBitmap potentialMatch = BufferFastAggregation.or(potentialMatchDocIds); if (_isPositiveCheck) { return getFilterBlock(fullMatch, potentialMatch); @@ -122,20 +117,20 @@ protected FilterBlock getNextBlock() { } /** - * Returns the filter block based on the given the partial match doc ids. + * Returns the filter block document IDs based on the given the partial match doc ids. */ - private FilterBlock getFilterBlock(MutableRoaringBitmap fullMatchDocIds, MutableRoaringBitmap partialMatchDocIds) { - ExpressionFilterOperator expressionFilterOperator = new ExpressionFilterOperator(_segment, _predicate, _numDocs); - ScanBasedDocIdIterator docIdIterator = - (ScanBasedDocIdIterator) expressionFilterOperator.getNextBlock().getBlockDocIdSet().iterator(); + private BlockDocIdSet getFilterBlock(MutableRoaringBitmap fullMatchDocIds, MutableRoaringBitmap partialMatchDocIds) { + ExpressionFilterOperator expressionFilterOperator = + new ExpressionFilterOperator(_segment, _queryContext, _predicate, _numDocs); + ScanBasedDocIdIterator docIdIterator = (ScanBasedDocIdIterator) expressionFilterOperator.getTrues().iterator(); MutableRoaringBitmap result = docIdIterator.applyAnd(partialMatchDocIds); result.or(fullMatchDocIds); - return new FilterBlock(new BitmapDocIdSet(result, _numDocs) { + return new BitmapDocIdSet(result, _numDocs) { @Override public long getNumEntriesScannedInFilter() { return docIdIterator.getNumEntriesScanned(); } - }); + }; } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/H3IndexFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/H3IndexFilterOperator.java index 9f91127c1210..b3ce7aefe96c 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/H3IndexFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/H3IndexFilterOperator.java @@ -27,17 +27,17 @@ import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.predicate.Predicate; import org.apache.pinot.common.request.context.predicate.RangePredicate; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.blocks.FilterBlock; import org.apache.pinot.core.operator.dociditerators.ScanBasedDocIdIterator; import org.apache.pinot.core.operator.docidsets.BitmapDocIdSet; import org.apache.pinot.core.operator.docidsets.EmptyDocIdSet; import org.apache.pinot.core.operator.docidsets.MatchAllDocIdSet; +import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.local.utils.GeometrySerializer; import org.apache.pinot.segment.local.utils.H3Utils; import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.segment.spi.index.reader.H3IndexReader; -import org.apache.pinot.spi.utils.BytesUtils; import org.locationtech.jts.geom.Coordinate; import org.roaringbitmap.buffer.MutableRoaringBitmap; @@ -46,33 +46,32 @@ * A filter operator that uses H3 index for geospatial data retrieval */ public class H3IndexFilterOperator extends BaseFilterOperator { - private static final String EXPLAIN_NAME = "FILTER_H3_INDEX"; + private final IndexSegment _segment; + private final QueryContext _queryContext; private final Predicate _predicate; - private final int _numDocs; private final H3IndexReader _h3IndexReader; private final long _h3Id; private final double _edgeLength; private final double _lowerBound; private final double _upperBound; - public H3IndexFilterOperator(IndexSegment segment, Predicate predicate, int numDocs) { + public H3IndexFilterOperator(IndexSegment segment, QueryContext queryContext, Predicate predicate, int numDocs) { + super(numDocs, false); _segment = segment; + _queryContext = queryContext; _predicate = predicate; - _numDocs = numDocs; // TODO: handle nested geography/geometry conversion functions List arguments = predicate.getLhs().getFunction().getArguments(); Coordinate coordinate; if (arguments.get(0).getType() == ExpressionContext.Type.IDENTIFIER) { _h3IndexReader = segment.getDataSource(arguments.get(0).getIdentifier()).getH3Index(); - coordinate = - GeometrySerializer.deserialize(BytesUtils.toBytes(arguments.get(1).getLiteralString())).getCoordinate(); + coordinate = GeometrySerializer.deserialize(arguments.get(1).getLiteral().getBytesValue()).getCoordinate(); } else { _h3IndexReader = segment.getDataSource(arguments.get(1).getIdentifier()).getH3Index(); - coordinate = - GeometrySerializer.deserialize(BytesUtils.toBytes(arguments.get(0).getLiteralString())).getCoordinate(); + coordinate = GeometrySerializer.deserialize(arguments.get(0).getLiteral().getBytesValue()).getCoordinate(); } assert _h3IndexReader != null; int resolution = _h3IndexReader.getH3IndexResolution().getLowestResolution(); @@ -93,10 +92,10 @@ public H3IndexFilterOperator(IndexSegment segment, Predicate predicate, int numD } @Override - protected FilterBlock getNextBlock() { + protected BlockDocIdSet getTrues() { if (_upperBound < 0 || _lowerBound > _upperBound) { // Invalid upper bound, return an empty block - return new FilterBlock(EmptyDocIdSet.getInstance()); + return EmptyDocIdSet.getInstance(); } try { @@ -105,7 +104,7 @@ protected FilterBlock getNextBlock() { if (Double.isNaN(_upperBound)) { // No bound, return a match-all block - return new FilterBlock(new MatchAllDocIdSet(_numDocs)); + return new MatchAllDocIdSet(_numDocs); } // Upper bound only @@ -182,7 +181,7 @@ protected FilterBlock getNextBlock() { return getFilterBlock(fullMatchDocIds, partialMatchDocIds); } catch (Exception e) { // Fall back to ExpressionFilterOperator when the execution encounters exception (e.g. numRings is too large) - return new ExpressionFilterOperator(_segment, _predicate, _numDocs).getNextBlock(); + return new ExpressionFilterOperator(_segment, _queryContext, _predicate, _numDocs).getTrues(); } } @@ -226,23 +225,23 @@ private List getH3Ids(int numRings) { } /** - * Returns the filter block based on the given full match doc ids and the partial match doc ids. + * Returns the filter block document IDs based on the given full match doc ids and the partial match doc ids. */ - private FilterBlock getFilterBlock(MutableRoaringBitmap fullMatchDocIds, MutableRoaringBitmap partialMatchDocIds) { - ExpressionFilterOperator expressionFilterOperator = new ExpressionFilterOperator(_segment, _predicate, _numDocs); + private BlockDocIdSet getFilterBlock(MutableRoaringBitmap fullMatchDocIds, MutableRoaringBitmap partialMatchDocIds) { + ExpressionFilterOperator expressionFilterOperator = + new ExpressionFilterOperator(_segment, _queryContext, _predicate, _numDocs); ScanBasedDocIdIterator docIdIterator = - (ScanBasedDocIdIterator) expressionFilterOperator.getNextBlock().getBlockDocIdSet().iterator(); + (ScanBasedDocIdIterator) expressionFilterOperator.getTrues().iterator(); MutableRoaringBitmap result = docIdIterator.applyAnd(partialMatchDocIds); result.or(fullMatchDocIds); - return new FilterBlock(new BitmapDocIdSet(result, _numDocs) { + return new BitmapDocIdSet(result, _numDocs) { @Override public long getNumEntriesScannedInFilter() { return docIdIterator.getNumEntriesScanned(); } - }); + }; } - @Override public List getChildOperators() { return Collections.emptyList(); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/InvertedIndexFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/InvertedIndexFilterOperator.java new file mode 100644 index 000000000000..b27c95aff4ea --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/InvertedIndexFilterOperator.java @@ -0,0 +1,159 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.filter; + +import java.util.Collections; +import java.util.List; +import org.apache.pinot.common.request.context.predicate.Predicate; +import org.apache.pinot.core.common.BlockDocIdSet; +import org.apache.pinot.core.common.Operator; +import org.apache.pinot.core.operator.docidsets.BitmapDocIdSet; +import org.apache.pinot.core.operator.docidsets.EmptyDocIdSet; +import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.segment.spi.datasource.DataSource; +import org.apache.pinot.segment.spi.index.reader.InvertedIndexReader; +import org.apache.pinot.spi.trace.FilterType; +import org.apache.pinot.spi.trace.InvocationRecording; +import org.apache.pinot.spi.trace.Tracing; +import org.roaringbitmap.buffer.ImmutableRoaringBitmap; +import org.roaringbitmap.buffer.MutableRoaringBitmap; + + +public class InvertedIndexFilterOperator extends BaseColumnFilterOperator { + private static final String EXPLAIN_NAME = "FILTER_INVERTED_INDEX"; + + private final PredicateEvaluator _predicateEvaluator; + private final InvertedIndexReader _invertedIndexReader; + private final boolean _exclusive; + + InvertedIndexFilterOperator(QueryContext queryContext, PredicateEvaluator predicateEvaluator, DataSource dataSource, + int numDocs) { + super(queryContext, dataSource, numDocs); + _predicateEvaluator = predicateEvaluator; + @SuppressWarnings("unchecked") + InvertedIndexReader invertedIndexReader = + (InvertedIndexReader) dataSource.getInvertedIndex(); + _invertedIndexReader = invertedIndexReader; + _exclusive = predicateEvaluator.isExclusive(); + } + + @Override + protected BlockDocIdSet getNextBlockWithoutNullHandling() { + int[] dictIds = _exclusive ? _predicateEvaluator.getNonMatchingDictIds() : _predicateEvaluator.getMatchingDictIds(); + int numDictIds = dictIds.length; + if (numDictIds == 0) { + return EmptyDocIdSet.getInstance(); + } + if (numDictIds == 1) { + ImmutableRoaringBitmap docIds = _invertedIndexReader.getDocIds(dictIds[0]); + if (_exclusive) { + if (docIds instanceof MutableRoaringBitmap) { + MutableRoaringBitmap mutableRoaringBitmap = (MutableRoaringBitmap) docIds; + mutableRoaringBitmap.flip(0L, _numDocs); + return new BitmapDocIdSet(mutableRoaringBitmap, _numDocs); + } else { + return new BitmapDocIdSet(ImmutableRoaringBitmap.flip(docIds, 0L, _numDocs), _numDocs); + } + } else { + return new BitmapDocIdSet(docIds, _numDocs); + } + } else { + ImmutableRoaringBitmap[] bitmaps = new ImmutableRoaringBitmap[numDictIds]; + for (int i = 0; i < numDictIds; i++) { + bitmaps[i] = _invertedIndexReader.getDocIds(dictIds[i]); + } + MutableRoaringBitmap docIds = ImmutableRoaringBitmap.or(bitmaps); + if (_exclusive) { + docIds.flip(0L, _numDocs); + } + InvocationRecording recording = Tracing.activeRecording(); + if (recording.isEnabled()) { + recording.setColumnName(_predicateEvaluator.getPredicate().getLhs().getIdentifier()); + recording.setNumDocsMatchingAfterFilter(docIds.getCardinality()); + recording.setFilter(FilterType.INDEX, String.valueOf(_predicateEvaluator.getPredicateType())); + } + return new BitmapDocIdSet(docIds, _numDocs); + } + } + + @Override + public boolean canOptimizeCount() { + return true; + } + + @Override + public int getNumMatchingDocs() { + int count = 0; + int[] dictIds = _exclusive ? _predicateEvaluator.getNonMatchingDictIds() : _predicateEvaluator.getMatchingDictIds(); + switch (dictIds.length) { + case 0: + break; + case 1: { + count = _invertedIndexReader.getDocIds(dictIds[0]).getCardinality(); + break; + } + case 2: { + count = ImmutableRoaringBitmap.orCardinality(_invertedIndexReader.getDocIds(dictIds[0]), + _invertedIndexReader.getDocIds(dictIds[1])); + break; + } + default: { + // this could be optimised if the bitmaps are known to be disjoint (as in a single value bitmap index) + MutableRoaringBitmap bitmap = new MutableRoaringBitmap(); + for (int dictId : dictIds) { + bitmap.or(_invertedIndexReader.getDocIds(dictId)); + } + count = bitmap.getCardinality(); + break; + } + } + return _exclusive ? _numDocs - count : count; + } + + @Override + public boolean canProduceBitmaps() { + return true; + } + + @Override + public BitmapCollection getBitmaps() { + int[] dictIds = _exclusive ? _predicateEvaluator.getNonMatchingDictIds() : _predicateEvaluator.getMatchingDictIds(); + ImmutableRoaringBitmap[] bitmaps = new ImmutableRoaringBitmap[dictIds.length]; + for (int i = 0; i < dictIds.length; i++) { + bitmaps[i] = _invertedIndexReader.getDocIds(dictIds[i]); + } + return new BitmapCollection(_numDocs, _exclusive, bitmaps); + } + + @Override + @SuppressWarnings("rawtypes") + public List getChildOperators() { + return Collections.emptyList(); + } + + @Override + public String toExplainString() { + StringBuilder stringBuilder = new StringBuilder(EXPLAIN_NAME).append("(indexLookUp:inverted_index"); + Predicate predicate = _predicateEvaluator.getPredicate(); + stringBuilder.append(",operator:").append(predicate.getType()); + stringBuilder.append(",predicate:").append(predicate.toString()); + return stringBuilder.append(')').toString(); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/JsonMatchFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/JsonMatchFilterOperator.java index 75ebaa246c66..ccf93983cfdb 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/JsonMatchFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/JsonMatchFilterOperator.java @@ -21,8 +21,8 @@ import java.util.Collections; import java.util.List; import org.apache.pinot.common.request.context.predicate.JsonMatchPredicate; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.blocks.FilterBlock; import org.apache.pinot.core.operator.docidsets.BitmapDocIdSet; import org.apache.pinot.segment.spi.index.reader.JsonIndexReader; import org.apache.pinot.spi.trace.FilterType; @@ -38,21 +38,20 @@ public class JsonMatchFilterOperator extends BaseFilterOperator { private static final String EXPLAIN_NAME = "FILTER_JSON_INDEX"; private final JsonIndexReader _jsonIndex; - private final int _numDocs; private final JsonMatchPredicate _predicate; public JsonMatchFilterOperator(JsonIndexReader jsonIndex, JsonMatchPredicate predicate, int numDocs) { + super(numDocs, false); _jsonIndex = jsonIndex; _predicate = predicate; - _numDocs = numDocs; } @Override - protected FilterBlock getNextBlock() { + protected BlockDocIdSet getTrues() { ImmutableRoaringBitmap bitmap = _jsonIndex.getMatchingDocIds(_predicate.getValue()); record(bitmap); - return new FilterBlock(new BitmapDocIdSet(bitmap, _numDocs)); + return new BitmapDocIdSet(bitmap, _numDocs); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/MatchAllFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/MatchAllFilterOperator.java index 490851db8a7c..44ad60e7b4df 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/MatchAllFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/MatchAllFilterOperator.java @@ -20,17 +20,17 @@ import java.util.Collections; import java.util.List; +import org.apache.pinot.core.common.BlockDocIdSet; +import org.apache.pinot.core.common.ExplainPlanRows; import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.blocks.FilterBlock; import org.apache.pinot.core.operator.docidsets.MatchAllDocIdSet; public class MatchAllFilterOperator extends BaseFilterOperator { public static final String EXPLAIN_NAME = "FILTER_MATCH_ENTIRE_SEGMENT"; - private final int _numDocs; public MatchAllFilterOperator(int numDocs) { - _numDocs = numDocs; + super(numDocs, false); } @Override @@ -39,8 +39,8 @@ public final boolean isResultMatchingAll() { } @Override - protected FilterBlock getNextBlock() { - return new FilterBlock(new MatchAllDocIdSet(_numDocs)); + protected BlockDocIdSet getTrues() { + return new MatchAllDocIdSet(_numDocs); } @@ -63,4 +63,9 @@ public int getNumMatchingDocs() { public String toExplainString() { return new StringBuilder(EXPLAIN_NAME).append("(docs:").append(_numDocs).append(')').toString(); } + + @Override + public void prepareForExplainPlan(ExplainPlanRows explainPlanRows) { + explainPlanRows.setHasMatchAllFilter(true); + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/NotFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/NotFilterOperator.java index ac3e2bb00b22..f473f7a0ec1e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/NotFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/NotFilterOperator.java @@ -18,26 +18,25 @@ */ package org.apache.pinot.core.operator.filter; + import java.util.Collections; import java.util.List; import javax.annotation.Nullable; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.blocks.FilterBlock; -import org.apache.pinot.core.operator.docidsets.NotDocIdSet; +import org.apache.pinot.core.operator.docidsets.MatchAllDocIdSet; public class NotFilterOperator extends BaseFilterOperator { private static final String EXPLAIN_NAME = "FILTER_NOT"; private final BaseFilterOperator _filterOperator; - private final int _numDocs; - public NotFilterOperator(BaseFilterOperator filterOperator, int numDocs) { + public NotFilterOperator(BaseFilterOperator filterOperator, int numDocs, boolean nullHandlingEnabled) { + super(numDocs, nullHandlingEnabled); _filterOperator = filterOperator; - _numDocs = numDocs; } - @Override public List getChildOperators() { return Collections.singletonList(_filterOperator); @@ -50,8 +49,17 @@ public String toExplainString() { } @Override - protected FilterBlock getNextBlock() { - return new FilterBlock(new NotDocIdSet(_filterOperator.nextBlock().getBlockDocIdSet(), _numDocs)); + protected BlockDocIdSet getTrues() { + if (_filterOperator.isResultEmpty()) { + return new MatchAllDocIdSet(_numDocs); + } else { + return _filterOperator.getFalses(); + } + } + + @Override + protected BlockDocIdSet getFalses() { + return _filterOperator.getTrues(); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/OrFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/OrFilterOperator.java index 3d262d5f0174..9f8477f96d5f 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/OrFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/OrFilterOperator.java @@ -19,10 +19,15 @@ package org.apache.pinot.core.operator.filter; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.blocks.FilterBlock; -import org.apache.pinot.core.operator.docidsets.FilterBlockDocIdSet; +import org.apache.pinot.core.operator.docidsets.EmptyDocIdSet; +import org.apache.pinot.core.operator.docidsets.MatchAllDocIdSet; +import org.apache.pinot.core.operator.docidsets.NotDocIdSet; import org.apache.pinot.core.operator.docidsets.OrDocIdSet; import org.apache.pinot.spi.trace.Tracing; import org.roaringbitmap.buffer.BufferFastAggregation; @@ -30,26 +35,56 @@ public class OrFilterOperator extends BaseFilterOperator { - private static final String EXPLAIN_NAME = "FILTER_OR"; + private final List _filterOperators; - private final int _numDocs; + private final Map _queryOptions; - public OrFilterOperator(List filterOperators, int numDocs) { + public OrFilterOperator(List filterOperators, @Nullable Map queryOptions, + int numDocs, boolean nullHandlingEnabled) { + super(numDocs, nullHandlingEnabled); _filterOperators = filterOperators; - _numDocs = numDocs; + _queryOptions = queryOptions; } @Override - protected FilterBlock getNextBlock() { + protected BlockDocIdSet getTrues() { Tracing.activeRecording().setNumChildren(_filterOperators.size()); - List filterBlockDocIdSets = new ArrayList<>(_filterOperators.size()); + List blockDocIdSets = new ArrayList<>(_filterOperators.size()); for (BaseFilterOperator filterOperator : _filterOperators) { - filterBlockDocIdSets.add(filterOperator.nextBlock().getBlockDocIdSet()); + blockDocIdSets.add(filterOperator.getTrues()); } - return new FilterBlock(new OrDocIdSet(filterBlockDocIdSets, _numDocs)); + return new OrDocIdSet(blockDocIdSets, _numDocs); } + @Override + protected BlockDocIdSet getFalses() { + List blockDocIdSets = new ArrayList<>(_filterOperators.size()); + for (BaseFilterOperator filterOperator : _filterOperators) { + BlockDocIdSet trues = filterOperator.getTrues(); + if (trues instanceof MatchAllDocIdSet) { + return EmptyDocIdSet.getInstance(); + } + if (trues instanceof EmptyDocIdSet) { + continue; + } + if (_nullHandlingEnabled) { + BlockDocIdSet nulls = filterOperator.getNulls(); + if (!(nulls instanceof EmptyDocIdSet)) { + blockDocIdSets.add(new OrDocIdSet(Arrays.asList(trues, nulls), _numDocs)); + continue; + } + } + blockDocIdSets.add(trues); + } + if (blockDocIdSets.isEmpty()) { + return new MatchAllDocIdSet(_numDocs); + } + if (blockDocIdSets.size() == 1) { + return new NotDocIdSet(blockDocIdSets.get(0), _numDocs); + } + return new NotDocIdSet(new OrDocIdSet(blockDocIdSets, _numDocs), _numDocs); + } @Override public String toExplainString() { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/PrioritizedFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/PrioritizedFilterOperator.java new file mode 100644 index 000000000000..d847a34a6d36 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/PrioritizedFilterOperator.java @@ -0,0 +1,52 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.filter; + +import java.util.OptionalInt; +import org.apache.pinot.core.common.Block; +import org.apache.pinot.core.common.Operator; + + +/** + * Operators that implements this interface can define how to be sorted in an AND, as defined in + * {@link FilterOperatorUtils.Implementation#}. + */ +public interface PrioritizedFilterOperator extends Operator { + + int HIGH_PRIORITY = 0; + int MEDIUM_PRIORITY = 100; + int LOW_PRIORITY = 200; + int AND_PRIORITY = 300; + int OR_PRIORITY = 400; + int SCAN_PRIORITY = 500; + int EXPRESSION_PRIORITY = 1000; + int UNKNOWN_FILTER_PRIORITY = 10000; + + /** + * Priority is a number that is used to compare different filters. Some predicates, like AND, sort their sub + * predicates in order to first apply the ones that should be more efficient. + * + * For example, {@link SortedIndexBasedFilterOperator} is assigned to {@link #HIGH_PRIORITY}, + * {@link BitmapBasedFilterOperator} is assigned to {@link #MEDIUM_PRIORITY} and {@link H3IndexFilterOperator} to + * {@link #LOW_PRIORITY} + * + * @return the priority of this filter operator or an empty optional if no special priority should be used. + */ + OptionalInt getPriority(); +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/RangeIndexBasedFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/RangeIndexBasedFilterOperator.java index 4b968ba2ed91..53161190d5c3 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/RangeIndexBasedFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/RangeIndexBasedFilterOperator.java @@ -20,19 +20,24 @@ import java.util.Collections; import java.util.List; +import org.apache.pinot.common.request.context.predicate.Predicate; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.blocks.FilterBlock; import org.apache.pinot.core.operator.dociditerators.ScanBasedDocIdIterator; import org.apache.pinot.core.operator.docidsets.BitmapDocIdSet; -import org.apache.pinot.core.operator.docidsets.FilterBlockDocIdSet; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; -import org.apache.pinot.core.operator.filter.predicate.RangePredicateEvaluatorFactory.DoubleRawValueBasedRangePredicateEvaluator; -import org.apache.pinot.core.operator.filter.predicate.RangePredicateEvaluatorFactory.FloatRawValueBasedRangePredicateEvaluator; -import org.apache.pinot.core.operator.filter.predicate.RangePredicateEvaluatorFactory.IntRawValueBasedRangePredicateEvaluator; -import org.apache.pinot.core.operator.filter.predicate.RangePredicateEvaluatorFactory.LongRawValueBasedRangePredicateEvaluator; -import org.apache.pinot.core.operator.filter.predicate.RangePredicateEvaluatorFactory.SortedDictionaryBasedRangePredicateEvaluator; +import org.apache.pinot.core.operator.filter.predicate.traits.DoubleRange; +import org.apache.pinot.core.operator.filter.predicate.traits.DoubleValue; +import org.apache.pinot.core.operator.filter.predicate.traits.FloatRange; +import org.apache.pinot.core.operator.filter.predicate.traits.FloatValue; +import org.apache.pinot.core.operator.filter.predicate.traits.IntRange; +import org.apache.pinot.core.operator.filter.predicate.traits.IntValue; +import org.apache.pinot.core.operator.filter.predicate.traits.LongRange; +import org.apache.pinot.core.operator.filter.predicate.traits.LongValue; +import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.segment.spi.index.reader.RangeIndexReader; +import org.apache.pinot.spi.data.FieldSpec; import org.apache.pinot.spi.trace.FilterType; import org.apache.pinot.spi.trace.InvocationRecording; import org.apache.pinot.spi.trace.Tracing; @@ -40,81 +45,176 @@ import org.roaringbitmap.buffer.MutableRoaringBitmap; -public class RangeIndexBasedFilterOperator extends BaseFilterOperator { +public class RangeIndexBasedFilterOperator extends BaseColumnFilterOperator { private static final String EXPLAIN_NAME = "FILTER_RANGE_INDEX"; - private final RangeEvaluator _rangeEvaluator; - private final PredicateEvaluator _rangePredicateEvaluator; - private final DataSource _dataSource; - private final int _numDocs; + private final RangeIndexReader _rangeIndexReader; + private final PredicateEvaluator _predicateEvaluator; + private final FieldSpec.DataType _parameterType; + + static boolean canEvaluate(PredicateEvaluator predicateEvaluator, DataSource dataSource) { + Predicate.Type type = predicateEvaluator.getPredicateType(); + RangeIndexReader rangeIndex = dataSource.getRangeIndex(); + return rangeIndex != null && (type == Predicate.Type.RANGE || (type == Predicate.Type.EQ + && dataSource.getRangeIndex().isExact())); + } @SuppressWarnings("unchecked") - public RangeIndexBasedFilterOperator(PredicateEvaluator rangePredicateEvaluator, DataSource dataSource, int numDocs) { - _rangePredicateEvaluator = rangePredicateEvaluator; - _rangeEvaluator = RangeEvaluator.of((RangeIndexReader) dataSource.getRangeIndex(), - rangePredicateEvaluator); - _dataSource = dataSource; - _numDocs = numDocs; + public RangeIndexBasedFilterOperator(QueryContext queryContext, PredicateEvaluator predicateEvaluator, + DataSource dataSource, int numDocs) { + super(queryContext, dataSource, numDocs); + _predicateEvaluator = predicateEvaluator; + _rangeIndexReader = (RangeIndexReader) dataSource.getRangeIndex(); + _parameterType = predicateEvaluator.isDictionaryBased() ? FieldSpec.DataType.INT : predicateEvaluator.getDataType(); } @Override - protected FilterBlock getNextBlock() { - if (_rangeEvaluator.isExact()) { - ImmutableRoaringBitmap matches = _rangeEvaluator.getMatchingDocIds(); + protected BlockDocIdSet getNextBlockWithoutNullHandling() { + if (_rangeIndexReader.isExact()) { + ImmutableRoaringBitmap matches = getMatchingDocIds(); recordFilter(matches); - return new FilterBlock(new BitmapDocIdSet(matches, _numDocs)); + return new BitmapDocIdSet(matches, _numDocs); } return evaluateLegacyRangeFilter(); } - private FilterBlock evaluateLegacyRangeFilter() { - ImmutableRoaringBitmap matches = _rangeEvaluator.getMatchingDocIds(); + private BlockDocIdSet evaluateLegacyRangeFilter() { + ImmutableRoaringBitmap matches = getMatchingDocIds(); // if the implementation cannot match the entire query exactly, it will // yield partial matches, which need to be verified by scanning. If it // can answer the query exactly, this will be null. - ImmutableRoaringBitmap partialMatches = _rangeEvaluator.getPartiallyMatchingDocIds(); + ImmutableRoaringBitmap partialMatches = getPartiallyMatchingDocIds(); // this branch is likely until RangeIndexReader reimplemented and enabled by default if (partialMatches == null) { - return new FilterBlock(new BitmapDocIdSet(matches == null ? new MutableRoaringBitmap() : matches, _numDocs)); + return new BitmapDocIdSet(matches == null ? new MutableRoaringBitmap() : matches, _numDocs); } - // TODO: support proper null handling in range index. // Need to scan the first and last range as they might be partially matched ScanBasedFilterOperator scanBasedFilterOperator = - new ScanBasedFilterOperator(_rangePredicateEvaluator, _dataSource, _numDocs, false); - FilterBlockDocIdSet scanBasedDocIdSet = scanBasedFilterOperator.getNextBlock().getBlockDocIdSet(); + new ScanBasedFilterOperator(_queryContext, _predicateEvaluator, _dataSource, _numDocs); + BlockDocIdSet scanBasedDocIdSet = scanBasedFilterOperator.getTrues(); MutableRoaringBitmap docIds = ((ScanBasedDocIdIterator) scanBasedDocIdSet.iterator()).applyAnd(partialMatches); if (matches != null) { docIds.or(matches); } recordFilter(matches); - return new FilterBlock(new BitmapDocIdSet(docIds, _numDocs) { + return new BitmapDocIdSet(docIds, _numDocs) { // Override this method to reflect the entries scanned @Override public long getNumEntriesScannedInFilter() { return scanBasedDocIdSet.getNumEntriesScannedInFilter(); } - }); + }; + } + + ImmutableRoaringBitmap getMatchingDocIds() { + switch (_parameterType) { + case INT: + if (_predicateEvaluator instanceof IntValue) { + return _rangeIndexReader.getMatchingDocIds(((IntValue) _predicateEvaluator).getInt()); + } + IntRange intRange = (IntRange) _predicateEvaluator; + return _rangeIndexReader.getMatchingDocIds(intRange.getInclusiveLowerBound(), + intRange.getInclusiveUpperBound()); + case LONG: + if (_predicateEvaluator instanceof LongValue) { + return _rangeIndexReader.getMatchingDocIds(((LongValue) _predicateEvaluator).getLong()); + } + LongRange longRange = (LongRange) _predicateEvaluator; + return _rangeIndexReader.getMatchingDocIds(longRange.getInclusiveLowerBound(), + longRange.getInclusiveUpperBound()); + case FLOAT: + if (_predicateEvaluator instanceof FloatValue) { + return _rangeIndexReader.getMatchingDocIds(((FloatValue) _predicateEvaluator).getFloat()); + } + FloatRange floatRange = (FloatRange) _predicateEvaluator; + return _rangeIndexReader.getMatchingDocIds(floatRange.getInclusiveLowerBound(), + floatRange.getInclusiveUpperBound()); + case DOUBLE: + if (_predicateEvaluator instanceof DoubleValue) { + return _rangeIndexReader.getMatchingDocIds(((DoubleValue) _predicateEvaluator).getDouble()); + } + DoubleRange doubleRange = (DoubleRange) _predicateEvaluator; + return _rangeIndexReader.getMatchingDocIds(doubleRange.getInclusiveLowerBound(), + doubleRange.getInclusiveUpperBound()); + default: + throw unsupportedDataType(_parameterType); + } + } + + ImmutableRoaringBitmap getPartiallyMatchingDocIds() { + assert !_rangeIndexReader.isExact(); + switch (_parameterType) { + case INT: + IntRange intRange = (IntRange) _predicateEvaluator; + return _rangeIndexReader.getPartiallyMatchingDocIds(intRange.getInclusiveLowerBound(), + intRange.getInclusiveUpperBound()); + case LONG: + LongRange longRange = (LongRange) _predicateEvaluator; + return _rangeIndexReader.getPartiallyMatchingDocIds(longRange.getInclusiveLowerBound(), + longRange.getInclusiveUpperBound()); + case FLOAT: + FloatRange floatRange = (FloatRange) _predicateEvaluator; + return _rangeIndexReader.getPartiallyMatchingDocIds(floatRange.getInclusiveLowerBound(), + floatRange.getInclusiveUpperBound()); + case DOUBLE: + DoubleRange doubleRange = (DoubleRange) _predicateEvaluator; + return _rangeIndexReader.getPartiallyMatchingDocIds(doubleRange.getInclusiveLowerBound(), + doubleRange.getInclusiveUpperBound()); + default: + throw unsupportedDataType(_parameterType); + } } @Override public boolean canOptimizeCount() { - return _rangeEvaluator.isExact(); + return _rangeIndexReader.isExact(); } @Override public int getNumMatchingDocs() { - return _rangeEvaluator.getNumMatchingDocs(); + switch (_parameterType) { + case INT: + if (_predicateEvaluator instanceof IntValue) { + return _rangeIndexReader.getNumMatchingDocs(((IntValue) _predicateEvaluator).getInt()); + } + IntRange intRange = (IntRange) _predicateEvaluator; + return _rangeIndexReader.getNumMatchingDocs(intRange.getInclusiveLowerBound(), + intRange.getInclusiveUpperBound()); + case LONG: + if (_predicateEvaluator instanceof LongValue) { + return _rangeIndexReader.getNumMatchingDocs(((LongValue) _predicateEvaluator).getLong()); + } + LongRange longRange = (LongRange) _predicateEvaluator; + return _rangeIndexReader.getNumMatchingDocs(longRange.getInclusiveLowerBound(), + longRange.getInclusiveUpperBound()); + case FLOAT: + if (_predicateEvaluator instanceof FloatValue) { + return _rangeIndexReader.getNumMatchingDocs(((FloatValue) _predicateEvaluator).getFloat()); + } + FloatRange floatRange = (FloatRange) _predicateEvaluator; + return _rangeIndexReader.getNumMatchingDocs(floatRange.getInclusiveLowerBound(), + floatRange.getInclusiveUpperBound()); + case DOUBLE: + if (_predicateEvaluator instanceof DoubleValue) { + return _rangeIndexReader.getNumMatchingDocs(((DoubleValue) _predicateEvaluator).getDouble()); + } + DoubleRange doubleRange = (DoubleRange) _predicateEvaluator; + return _rangeIndexReader.getNumMatchingDocs(doubleRange.getInclusiveLowerBound(), + doubleRange.getInclusiveUpperBound()); + default: + throw unsupportedDataType(_parameterType); + } } @Override public boolean canProduceBitmaps() { - return _rangeEvaluator.isExact(); + return _rangeIndexReader.isExact(); } @Override public BitmapCollection getBitmaps() { - return new BitmapCollection(_numDocs, false, _rangeEvaluator.getMatchingDocIds()); + return new BitmapCollection(_numDocs, false, getMatchingDocIds()); } @Override @@ -124,187 +224,16 @@ public List getChildOperators() { @Override public String toExplainString() { - return EXPLAIN_NAME + "(indexLookUp:range_index" - + ",operator:" + _rangePredicateEvaluator.getPredicateType() - + ",predicate:" + _rangePredicateEvaluator.getPredicate().toString() - + ')'; - } - - interface RangeEvaluator { - static RangeEvaluator of(RangeIndexReader rangeIndexReader, - PredicateEvaluator predicateEvaluator) { - if (predicateEvaluator instanceof SortedDictionaryBasedRangePredicateEvaluator) { - return new IntRangeEvaluator(rangeIndexReader, - (SortedDictionaryBasedRangePredicateEvaluator) predicateEvaluator); - } else { - switch (predicateEvaluator.getDataType()) { - case INT: - return new IntRangeEvaluator(rangeIndexReader, - (IntRawValueBasedRangePredicateEvaluator) predicateEvaluator); - case LONG: - return new LongRangeEvaluator(rangeIndexReader, - (LongRawValueBasedRangePredicateEvaluator) predicateEvaluator); - case FLOAT: - return new FloatRangeEvaluator(rangeIndexReader, - (FloatRawValueBasedRangePredicateEvaluator) predicateEvaluator); - case DOUBLE: - return new DoubleRangeEvaluator(rangeIndexReader, - (DoubleRawValueBasedRangePredicateEvaluator) predicateEvaluator); - default: - throw new IllegalStateException("String and Bytes data type not supported for Range Indexing"); - } - } - } - - ImmutableRoaringBitmap getMatchingDocIds(); - - ImmutableRoaringBitmap getPartiallyMatchingDocIds(); - - int getNumMatchingDocs(); - - boolean isExact(); - } - - private static final class IntRangeEvaluator implements RangeEvaluator { - final RangeIndexReader _rangeIndexReader; - final int _min; - final int _max; - - private IntRangeEvaluator(RangeIndexReader rangeIndexReader, int min, int max) { - _rangeIndexReader = rangeIndexReader; - _min = min; - _max = max; - } - - IntRangeEvaluator(RangeIndexReader rangeIndexReader, - IntRawValueBasedRangePredicateEvaluator predicateEvaluator) { - this(rangeIndexReader, predicateEvaluator.getInclusiveLowerBound(), predicateEvaluator.getInclusiveUpperBound()); - } - - IntRangeEvaluator(RangeIndexReader rangeIndexReader, - SortedDictionaryBasedRangePredicateEvaluator predicateEvaluator) { - // NOTE: End dictionary id is exclusive in OfflineDictionaryBasedRangePredicateEvaluator. - this(rangeIndexReader, predicateEvaluator.getStartDictId(), predicateEvaluator.getEndDictId() - 1); - } - - @Override - public ImmutableRoaringBitmap getMatchingDocIds() { - return _rangeIndexReader.getMatchingDocIds(_min, _max); - } - - @Override - public ImmutableRoaringBitmap getPartiallyMatchingDocIds() { - return _rangeIndexReader.getPartiallyMatchingDocIds(_min, _max); - } - - @Override - public int getNumMatchingDocs() { - return _rangeIndexReader.getNumMatchingDocs(_min, _max); - } - - @Override - public boolean isExact() { - return _rangeIndexReader.isExact(); - } + return EXPLAIN_NAME + "(indexLookUp:range_index" + ",operator:" + _predicateEvaluator.getPredicateType() + + ",predicate:" + _predicateEvaluator.getPredicate().toString() + ')'; } - private static final class LongRangeEvaluator implements RangeEvaluator { - final RangeIndexReader _rangeIndexReader; - final long _min; - final long _max; - - LongRangeEvaluator(RangeIndexReader rangeIndexReader, - LongRawValueBasedRangePredicateEvaluator predicateEvaluator) { - _rangeIndexReader = rangeIndexReader; - _min = predicateEvaluator.getInclusiveLowerBound(); - _max = predicateEvaluator.getInclusiveUpperBound(); - } - - @Override - public ImmutableRoaringBitmap getMatchingDocIds() { - return _rangeIndexReader.getMatchingDocIds(_min, _max); - } - - @Override - public ImmutableRoaringBitmap getPartiallyMatchingDocIds() { - return _rangeIndexReader.getPartiallyMatchingDocIds(_min, _max); - } - - @Override - public int getNumMatchingDocs() { - return _rangeIndexReader.getNumMatchingDocs(_min, _max); - } - - @Override - public boolean isExact() { - return _rangeIndexReader.isExact(); - } + static RuntimeException unsupportedPredicateType(Predicate.Type type) { + return new IllegalStateException("Range index cannot satisfy " + type); } - private static final class FloatRangeEvaluator implements RangeEvaluator { - final RangeIndexReader _rangeIndexReader; - final float _min; - final float _max; - - FloatRangeEvaluator(RangeIndexReader rangeIndexReader, - FloatRawValueBasedRangePredicateEvaluator predicateEvaluator) { - _rangeIndexReader = rangeIndexReader; - _min = predicateEvaluator.getInclusiveLowerBound(); - _max = predicateEvaluator.getInclusiveUpperBound(); - } - - @Override - public ImmutableRoaringBitmap getMatchingDocIds() { - return _rangeIndexReader.getMatchingDocIds(_min, _max); - } - - @Override - public ImmutableRoaringBitmap getPartiallyMatchingDocIds() { - return _rangeIndexReader.getPartiallyMatchingDocIds(_min, _max); - } - - @Override - public int getNumMatchingDocs() { - return _rangeIndexReader.getNumMatchingDocs(_min, _max); - } - - @Override - public boolean isExact() { - return _rangeIndexReader.isExact(); - } - } - - private static final class DoubleRangeEvaluator implements RangeEvaluator { - final RangeIndexReader _rangeIndexReader; - final double _min; - final double _max; - - DoubleRangeEvaluator(RangeIndexReader rangeIndexReader, - DoubleRawValueBasedRangePredicateEvaluator predicateEvaluator) { - _rangeIndexReader = rangeIndexReader; - _min = predicateEvaluator.getInclusiveLowerBound(); - _max = predicateEvaluator.getInclusiveUpperBound(); - } - - @Override - public ImmutableRoaringBitmap getMatchingDocIds() { - return _rangeIndexReader.getMatchingDocIds(_min, _max); - } - - @Override - public ImmutableRoaringBitmap getPartiallyMatchingDocIds() { - return _rangeIndexReader.getPartiallyMatchingDocIds(_min, _max); - } - - @Override - public int getNumMatchingDocs() { - return _rangeIndexReader.getNumMatchingDocs(_min, _max); - } - - @Override - public boolean isExact() { - return _rangeIndexReader.isExact(); - } + static RuntimeException unsupportedDataType(FieldSpec.DataType dataType) { + return new IllegalStateException("Range index does not support " + dataType); } private void recordFilter(ImmutableRoaringBitmap bitmap) { @@ -312,7 +241,7 @@ private void recordFilter(ImmutableRoaringBitmap bitmap) { if (recording.isEnabled()) { recording.setNumDocsMatchingAfterFilter(bitmap == null ? 0 : bitmap.getCardinality()); recording.setColumnName(_dataSource.getDataSourceMetadata().getFieldSpec().getName()); - recording.setFilter(FilterType.INDEX, _rangePredicateEvaluator.getPredicateType().name()); + recording.setFilter(FilterType.INDEX, _predicateEvaluator.getPredicateType().name()); recording.setNumDocsScanned(_numDocs); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/ScanBasedFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/ScanBasedFilterOperator.java index 89305d24fb14..938e30425677 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/ScanBasedFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/ScanBasedFilterOperator.java @@ -21,41 +21,45 @@ import com.google.common.base.Preconditions; import java.util.Collections; import java.util.List; +import org.apache.pinot.core.common.BlockDocIdIterator; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.blocks.FilterBlock; import org.apache.pinot.core.operator.docidsets.MVScanDocIdSet; import org.apache.pinot.core.operator.docidsets.SVScanDocIdSet; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; +import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.segment.spi.datasource.DataSourceMetadata; -public class ScanBasedFilterOperator extends BaseFilterOperator { +public class ScanBasedFilterOperator extends BaseColumnFilterOperator { private static final String EXPLAIN_NAME = "FILTER_FULL_SCAN"; private final PredicateEvaluator _predicateEvaluator; - private final DataSource _dataSource; - private final int _numDocs; - private final boolean _nullHandlingEnabled; + private final int _batchSize; - ScanBasedFilterOperator(PredicateEvaluator predicateEvaluator, DataSource dataSource, int numDocs, - boolean nullHandlingEnabled) { + public ScanBasedFilterOperator(QueryContext queryContext, PredicateEvaluator predicateEvaluator, + DataSource dataSource, int numDocs) { + this(queryContext, predicateEvaluator, dataSource, numDocs, BlockDocIdIterator.OPTIMAL_ITERATOR_BATCH_SIZE); + } + + public ScanBasedFilterOperator(QueryContext queryContext, PredicateEvaluator predicateEvaluator, + DataSource dataSource, int numDocs, int batchSize) { + super(queryContext, dataSource, numDocs); _predicateEvaluator = predicateEvaluator; - _dataSource = dataSource; - _numDocs = numDocs; - _nullHandlingEnabled = nullHandlingEnabled; Preconditions.checkState(_dataSource.getForwardIndex() != null, "Forward index disabled for column: %s, scan based filtering not supported!", _dataSource.getDataSourceMetadata().getFieldSpec().getName()); + _batchSize = batchSize; } @Override - protected FilterBlock getNextBlock() { + protected BlockDocIdSet getNextBlockWithoutNullHandling() { DataSourceMetadata dataSourceMetadata = _dataSource.getDataSourceMetadata(); if (dataSourceMetadata.isSingleValue()) { - return new FilterBlock(new SVScanDocIdSet(_predicateEvaluator, _dataSource, _numDocs, _nullHandlingEnabled)); + return new SVScanDocIdSet(_predicateEvaluator, _dataSource, _numDocs, _batchSize); } else { - return new FilterBlock(new MVScanDocIdSet(_predicateEvaluator, _dataSource, _numDocs)); + return new MVScanDocIdSet(_predicateEvaluator, _dataSource, _numDocs); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/SortedIndexBasedFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/SortedIndexBasedFilterOperator.java index cef4ef2ba011..09144af9ff6d 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/SortedIndexBasedFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/SortedIndexBasedFilterOperator.java @@ -20,35 +20,35 @@ import com.google.common.base.Preconditions; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.blocks.FilterBlock; import org.apache.pinot.core.operator.docidsets.SortedDocIdSet; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; import org.apache.pinot.core.operator.filter.predicate.RangePredicateEvaluatorFactory.SortedDictionaryBasedRangePredicateEvaluator; +import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.segment.spi.index.reader.SortedIndexReader; import org.apache.pinot.spi.utils.Pairs.IntPair; import org.roaringbitmap.buffer.MutableRoaringBitmap; -public class SortedIndexBasedFilterOperator extends BaseFilterOperator { +public class SortedIndexBasedFilterOperator extends BaseColumnFilterOperator { private static final String EXPLAIN_NAME = "FILTER_SORTED_INDEX"; private final PredicateEvaluator _predicateEvaluator; private final SortedIndexReader _sortedIndexReader; - private final int _numDocs; - SortedIndexBasedFilterOperator(PredicateEvaluator predicateEvaluator, DataSource dataSource, int numDocs) { + SortedIndexBasedFilterOperator(QueryContext queryContext, PredicateEvaluator predicateEvaluator, + DataSource dataSource, int numDocs) { + super(queryContext, dataSource, numDocs); _predicateEvaluator = predicateEvaluator; _sortedIndexReader = (SortedIndexReader) dataSource.getInvertedIndex(); - _numDocs = numDocs; } @Override - protected FilterBlock getNextBlock() { + protected BlockDocIdSet getNextBlockWithoutNullHandling() { // At this point, we need to create a list of matching docIdRanges. // // There are two kinds of operators: @@ -63,7 +63,7 @@ protected FilterBlock getNextBlock() { int startDocId = _sortedIndexReader.getDocIds(rangePredicateEvaluator.getStartDictId()).getLeft(); // NOTE: End dictionary id is exclusive in OfflineDictionaryBasedRangePredicateEvaluator. int endDocId = _sortedIndexReader.getDocIds(rangePredicateEvaluator.getEndDictId() - 1).getRight(); - return new FilterBlock(new SortedDocIdSet(Collections.singletonList(new IntPair(startDocId, endDocId)))); + return new SortedDocIdSet(Collections.singletonList(new IntPair(startDocId, endDocId))); } else { boolean exclusive = _predicateEvaluator.isExclusive(); int[] dictIds = @@ -84,15 +84,12 @@ protected FilterBlock getNextBlock() { if (lastDocId < _numDocs - 1) { docIdRanges.add(new IntPair(lastDocId + 1, _numDocs - 1)); } - return new FilterBlock(new SortedDocIdSet(docIdRanges)); + return new SortedDocIdSet(docIdRanges); } else { - return new FilterBlock(new SortedDocIdSet(Collections.singletonList(docIdRange))); + return new SortedDocIdSet(Collections.singletonList(docIdRange)); } } else { - // Sort the dictIds in ascending order so that their respective docIdRanges are adjacent if they are adjacent - Arrays.sort(dictIds); - - // Merge adjacent docIdRanges + // Merge adjacent docIdRanges (dictIds are already sorted) List docIdRanges = new ArrayList<>(); IntPair lastDocIdRange = _sortedIndexReader.getDocIds(dictIds[0]); for (int i = 1; i < numDictIds; i++) { @@ -127,7 +124,7 @@ protected FilterBlock getNextBlock() { docIdRanges = invertedDocIdRanges; } - return new FilterBlock(new SortedDocIdSet(docIdRanges)); + return new SortedDocIdSet(docIdRanges); } } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/TestFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/TestFilterOperator.java new file mode 100644 index 000000000000..270e4cee39f3 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/TestFilterOperator.java @@ -0,0 +1,118 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.filter; + +import java.util.Collections; +import java.util.List; +import org.apache.pinot.core.common.BlockDocIdIterator; +import org.apache.pinot.core.common.BlockDocIdSet; +import org.apache.pinot.core.common.Operator; +import org.apache.pinot.core.operator.docidsets.EmptyDocIdSet; +import org.apache.pinot.core.operator.docidsets.MatchAllDocIdSet; +import org.apache.pinot.segment.spi.Constants; + + +public class TestFilterOperator extends BaseFilterOperator { + private static final String EXPLAIN_NAME = "FILTER_TEST"; + + private final int[] _trueDocIds; + private final int[] _nullDocIds; + + public TestFilterOperator(int[] trueDocIds, int[] nullDocIds, int numDocs) { + super(numDocs, true); + _trueDocIds = trueDocIds; + _nullDocIds = nullDocIds; + } + + public TestFilterOperator(int[] docIds, int numDocs) { + super(numDocs, false); + _trueDocIds = docIds; + _nullDocIds = new int[0]; + } + + @Override + public String toExplainString() { + return EXPLAIN_NAME; + } + + @Override + public List getChildOperators() { + return Collections.emptyList(); + } + + @Override + protected BlockDocIdSet getTrues() { + if (_trueDocIds.length == _numDocs) { + return new MatchAllDocIdSet(_numDocs); + } + if (_trueDocIds.length == 0) { + return EmptyDocIdSet.getInstance(); + } + return new TestBlockDocIdSet(_trueDocIds); + } + + @Override + protected BlockDocIdSet getNulls() { + if (_nullDocIds.length == 0) { + return EmptyDocIdSet.getInstance(); + } + return new TestBlockDocIdSet(_nullDocIds); + } + + private static class TestBlockDocIdSet implements BlockDocIdSet { + private final int[] _docIds; + + public TestBlockDocIdSet(int[] docIds) { + _docIds = docIds; + } + + @Override + public BlockDocIdIterator iterator() { + return new BlockDocIdIterator() { + private final int _numDocIds = _docIds.length; + private int _nextIndex = 0; + + @Override + public int next() { + if (_nextIndex < _numDocIds) { + return _docIds[_nextIndex++]; + } else { + return Constants.EOF; + } + } + + @Override + public int advance(int targetDocId) { + while (_nextIndex < _numDocIds) { + int docId = _docIds[_nextIndex++]; + if (docId >= targetDocId) { + return docId; + } + } + return Constants.EOF; + } + }; + } + + @Override + public long getNumEntriesScannedInFilter() { + return 0L; + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/TextContainsFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/TextContainsFilterOperator.java index b435d2f72ac8..d76f12d5bb68 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/TextContainsFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/TextContainsFilterOperator.java @@ -21,8 +21,8 @@ import java.util.Collections; import java.util.List; import org.apache.pinot.common.request.context.predicate.TextContainsPredicate; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.blocks.FilterBlock; import org.apache.pinot.core.operator.docidsets.BitmapDocIdSet; import org.apache.pinot.segment.spi.index.reader.TextIndexReader; import org.apache.pinot.spi.trace.FilterType; @@ -38,18 +38,17 @@ public class TextContainsFilterOperator extends BaseFilterOperator { private static final String EXPLAIN_NAME = "FILTER_TEXT_INDEX"; private final TextIndexReader _textIndexReader; - private final int _numDocs; private final TextContainsPredicate _predicate; public TextContainsFilterOperator(TextIndexReader textIndexReader, TextContainsPredicate predicate, int numDocs) { + super(numDocs, false); _textIndexReader = textIndexReader; _predicate = predicate; - _numDocs = numDocs; } @Override - protected FilterBlock getNextBlock() { - return new FilterBlock(new BitmapDocIdSet(_textIndexReader.getDocIds(_predicate.getValue()), _numDocs)); + protected BlockDocIdSet getTrues() { + return new BitmapDocIdSet(_textIndexReader.getDocIds(_predicate.getValue()), _numDocs); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/TextMatchFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/TextMatchFilterOperator.java index 6a906ba836fa..8a668f593dd9 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/TextMatchFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/TextMatchFilterOperator.java @@ -21,8 +21,8 @@ import java.util.Collections; import java.util.List; import org.apache.pinot.common.request.context.predicate.TextMatchPredicate; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.blocks.FilterBlock; import org.apache.pinot.core.operator.docidsets.BitmapDocIdSet; import org.apache.pinot.segment.spi.index.reader.TextIndexReader; import org.apache.pinot.spi.trace.FilterType; @@ -43,14 +43,15 @@ public class TextMatchFilterOperator extends BaseFilterOperator { private final TextMatchPredicate _predicate; public TextMatchFilterOperator(TextIndexReader textIndexReader, TextMatchPredicate predicate, int numDocs) { + super(numDocs, false); _textIndexReader = textIndexReader; _predicate = predicate; _numDocs = numDocs; } @Override - protected FilterBlock getNextBlock() { - return new FilterBlock(new BitmapDocIdSet(_textIndexReader.getDocIds(_predicate.getValue()), _numDocs)); + protected BlockDocIdSet getTrues() { + return new BitmapDocIdSet(_textIndexReader.getDocIds(_predicate.getValue()), _numDocs); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/VectorSimilarityFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/VectorSimilarityFilterOperator.java new file mode 100644 index 000000000000..633418560ed9 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/VectorSimilarityFilterOperator.java @@ -0,0 +1,118 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.filter; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.apache.pinot.common.request.context.predicate.VectorSimilarityPredicate; +import org.apache.pinot.core.common.BlockDocIdSet; +import org.apache.pinot.core.common.Operator; +import org.apache.pinot.core.operator.docidsets.BitmapDocIdSet; +import org.apache.pinot.segment.spi.index.reader.VectorIndexReader; +import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.trace.FilterType; +import org.apache.pinot.spi.trace.InvocationRecording; +import org.apache.pinot.spi.trace.Tracing; +import org.roaringbitmap.buffer.ImmutableRoaringBitmap; + + +/** + * Operator for Vector Search query. + *

Currently, we only support vector similarity search on float array column. + * Example: + * { + * "type": "vectorSimilarity", + * "leftValue": "embedding", + * "rightValue": [1.0, 2.0, 3.0], + * "topK": 10 + * } + * + */ +public class VectorSimilarityFilterOperator extends BaseFilterOperator { + private static final String EXPLAIN_NAME = "VECTOR_SIMILARITY_INDEX"; + + private final VectorIndexReader _vectorIndexReader; + private final VectorSimilarityPredicate _predicate; + private ImmutableRoaringBitmap _matches; + + public VectorSimilarityFilterOperator(VectorIndexReader vectorIndexReader, VectorSimilarityPredicate predicate, + int numDocs) { + super(numDocs, false); + _vectorIndexReader = vectorIndexReader; + _predicate = predicate; + _matches = null; + } + + @Override + protected BlockDocIdSet getTrues() { + if (_matches == null) { + _matches = _vectorIndexReader.getDocIds(_predicate.getValue(), _predicate.getTopK()); + } + return new BitmapDocIdSet(_matches, _numDocs); + } + + @Override + public int getNumMatchingDocs() { + if (_matches == null) { + _matches = _vectorIndexReader.getDocIds(_predicate.getValue(), _predicate.getTopK()); + } + return _matches.getCardinality(); + } + + @Override + public boolean canProduceBitmaps() { + return true; + } + + @Override + public BitmapCollection getBitmaps() { + if (_matches == null) { + _matches = _vectorIndexReader.getDocIds(_predicate.getValue(), _predicate.getTopK()); + } + record(_matches); + return new BitmapCollection(_numDocs, false, _matches); + } + + @Override + public List getChildOperators() { + return Collections.emptyList(); + } + + @Override + public String toExplainString() { + return EXPLAIN_NAME + "(indexLookUp:vector_index" + + ", operator:" + _predicate.getType() + + ", vector identifier:" + _predicate.getLhs().getIdentifier() + + ", vector literal:" + Arrays.toString(_predicate.getValue()) + + ", topK to search:" + _predicate.getTopK() + + ')'; + } + + private void record(ImmutableRoaringBitmap matches) { + InvocationRecording recording = Tracing.activeRecording(); + if (recording.isEnabled()) { + recording.setNumDocsMatchingAfterFilter(matches.getCardinality()); + recording.setColumnName(_predicate.getLhs().getIdentifier()); + recording.setFilter(FilterType.INDEX, "VECTOR_SIMILARITY"); + recording.setInputDataType(FieldSpec.DataType.FLOAT, false); + recording.setNumDocsMatchingAfterFilter(matches.getCardinality()); + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/BaseDictionaryBasedPredicateEvaluator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/BaseDictionaryBasedPredicateEvaluator.java index 92050c3cefa0..18d15d367353 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/BaseDictionaryBasedPredicateEvaluator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/BaseDictionaryBasedPredicateEvaluator.java @@ -18,17 +18,34 @@ */ package org.apache.pinot.core.operator.filter.predicate; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.ints.IntList; import java.math.BigDecimal; import org.apache.pinot.common.request.context.predicate.Predicate; +import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.spi.data.FieldSpec.DataType; public abstract class BaseDictionaryBasedPredicateEvaluator extends BasePredicateEvaluator { + protected final Dictionary _dictionary; protected boolean _alwaysTrue; protected boolean _alwaysFalse; + protected int[] _matchingDictIds; + protected int[] _nonMatchingDictIds; - protected BaseDictionaryBasedPredicateEvaluator(Predicate predicate) { + protected BaseDictionaryBasedPredicateEvaluator(Predicate predicate, Dictionary dictionary) { super(predicate); + _dictionary = dictionary; + } + + @Override + public final boolean isDictionaryBased() { + return true; + } + + @Override + public DataType getDataType() { + return DataType.INT; } @Override @@ -42,13 +59,33 @@ public boolean isAlwaysFalse() { } @Override - public final boolean isDictionaryBased() { - return true; + public int[] getMatchingDictIds() { + if (_matchingDictIds == null) { + _matchingDictIds = calculateMatchingDictIds(); + } + return _matchingDictIds; } - @Override - public DataType getDataType() { - return DataType.INT; + protected int[] calculateMatchingDictIds() { + IntList matchingDictIds = new IntArrayList(); + int dictionarySize = _dictionary.length(); + for (int dictId = 0; dictId < dictionarySize; dictId++) { + if (applySV(dictId)) { + matchingDictIds.add(dictId); + } + } + return matchingDictIds.toIntArray(); + } + + public int[] getNonMatchingDictIds() { + if (_nonMatchingDictIds == null) { + _nonMatchingDictIds = calculateNonMatchingDictIds(); + } + return _nonMatchingDictIds; + } + + protected int[] calculateNonMatchingDictIds() { + return PredicateUtils.flipDictIds(getMatchingDictIds(), _dictionary.length()); } @Override @@ -106,12 +143,6 @@ public final boolean applyMV(byte[][] values, int length) { throw new UnsupportedOperationException(); } - // NOTE: override it for exclusive predicate - @Override - public int[] getNonMatchingDictIds() { - throw new UnsupportedOperationException(); - } - /** * Apply a single-value entry to the predicate. * diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/BasePredicateEvaluator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/BasePredicateEvaluator.java index 0e04954675c7..407e619ae33d 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/BasePredicateEvaluator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/BasePredicateEvaluator.java @@ -42,14 +42,4 @@ public Predicate.Type getPredicateType() { public final boolean isExclusive() { return getPredicateType().isExclusive(); } - - @Override - public int getNumMatchingDictIds() { - return getMatchingDictIds().length; - } - - @Override - public int getNumNonMatchingDictIds() { - return getNonMatchingDictIds().length; - } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/EqualsPredicateEvaluatorFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/EqualsPredicateEvaluatorFactory.java index 814a45253b04..d99b8249d452 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/EqualsPredicateEvaluatorFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/EqualsPredicateEvaluatorFactory.java @@ -21,8 +21,15 @@ import java.math.BigDecimal; import java.util.Arrays; import org.apache.pinot.common.request.context.predicate.EqPredicate; +import org.apache.pinot.common.request.context.predicate.Predicate; +import org.apache.pinot.core.operator.filter.predicate.traits.DoubleValue; +import org.apache.pinot.core.operator.filter.predicate.traits.FloatValue; +import org.apache.pinot.core.operator.filter.predicate.traits.IntValue; +import org.apache.pinot.core.operator.filter.predicate.traits.LongValue; import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.data.MultiValueVisitor; +import org.apache.pinot.spi.data.SingleValueVisitor; import org.apache.pinot.spi.utils.BooleanUtils; import org.apache.pinot.spi.utils.BytesUtils; import org.apache.pinot.spi.utils.TimestampUtils; @@ -55,8 +62,7 @@ public static BaseDictionaryBasedPredicateEvaluator newDictionaryBasedEvaluator( * @param dataType Data type for the column * @return Raw value based EQ predicate evaluator */ - public static BaseRawValueBasedPredicateEvaluator newRawValueBasedEvaluator(EqPredicate eqPredicate, - DataType dataType) { + public static EqRawPredicateEvaluator newRawValueBasedEvaluator(EqPredicate eqPredicate, DataType dataType) { String value = eqPredicate.getValue(); switch (dataType) { case INT: @@ -74,6 +80,7 @@ public static BaseRawValueBasedPredicateEvaluator newRawValueBasedEvaluator(EqPr case TIMESTAMP: return new LongRawValueBasedEqPredicateEvaluator(eqPredicate, TimestampUtils.toMillisSinceEpoch(value)); case STRING: + case JSON: return new StringRawValueBasedEqPredicateEvaluator(eqPredicate, value); case BYTES: return new BytesRawValueBasedEqPredicateEvaluator(eqPredicate, BytesUtils.toBytes(value)); @@ -82,12 +89,12 @@ public static BaseRawValueBasedPredicateEvaluator newRawValueBasedEvaluator(EqPr } } - private static final class DictionaryBasedEqPredicateEvaluator extends BaseDictionaryBasedPredicateEvaluator { + private static final class DictionaryBasedEqPredicateEvaluator extends BaseDictionaryBasedPredicateEvaluator + implements IntValue { final int _matchingDictId; - final int[] _matchingDictIds; DictionaryBasedEqPredicateEvaluator(EqPredicate eqPredicate, Dictionary dictionary, DataType dataType) { - super(eqPredicate); + super(eqPredicate, dictionary); String predicateValue = PredicateUtils.getStoredValue(eqPredicate.getValue(), dataType); _matchingDictId = dictionary.indexOf(predicateValue); if (_matchingDictId >= 0) { @@ -101,6 +108,11 @@ private static final class DictionaryBasedEqPredicateEvaluator extends BaseDicti } } + @Override + protected int[] calculateNonMatchingDictIds() { + return PredicateUtils.getDictIds(_dictionary.length(), _matchingDictId); + } + @Override public int getNumMatchingItems() { return 1; @@ -125,12 +137,30 @@ public int applySV(int limit, int[] docIds, int[] values) { } @Override - public int[] getMatchingDictIds() { - return _matchingDictIds; + public int getInt() { + return _matchingDictId; + } + } + + public static abstract class EqRawPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + public EqRawPredicateEvaluator(Predicate predicate) { + super(predicate); + } + + /** + * Visits the matching value of this predicate. + */ + public abstract R accept(SingleValueVisitor visitor); + + /** + * Visits the matching value of this predicate, which will be transformed into an array with a single value. + */ + public R accept(MultiValueVisitor visitor) { + return accept(visitor.asSingleValueVisitor()); } } - private static final class IntRawValueBasedEqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class IntRawValueBasedEqPredicateEvaluator extends EqRawPredicateEvaluator implements IntValue { final int _matchingValue; IntRawValueBasedEqPredicateEvaluator(EqPredicate eqPredicate, int matchingValue) { @@ -138,6 +168,11 @@ private static final class IntRawValueBasedEqPredicateEvaluator extends BaseRawV _matchingValue = matchingValue; } + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitInt(_matchingValue); + } + @Override public int getNumMatchingItems() { return 1; @@ -165,9 +200,15 @@ public int applySV(int limit, int[] docIds, int[] values) { } return matches; } + + @Override + public int getInt() { + return _matchingValue; + } } - private static final class LongRawValueBasedEqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class LongRawValueBasedEqPredicateEvaluator extends EqRawPredicateEvaluator + implements LongValue { final long _matchingValue; LongRawValueBasedEqPredicateEvaluator(EqPredicate eqPredicate, long matchingValue) { @@ -175,6 +216,16 @@ private static final class LongRawValueBasedEqPredicateEvaluator extends BaseRaw _matchingValue = matchingValue; } + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitLong(_matchingValue); + } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.asSingleValueVisitor().visitLong(_matchingValue); + } + @Override public int getNumMatchingItems() { return 1; @@ -202,9 +253,15 @@ public int applySV(int limit, int[] docIds, long[] values) { } return matches; } + + @Override + public long getLong() { + return _matchingValue; + } } - private static final class FloatRawValueBasedEqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class FloatRawValueBasedEqPredicateEvaluator extends EqRawPredicateEvaluator + implements FloatValue { final float _matchingValue; FloatRawValueBasedEqPredicateEvaluator(EqPredicate eqPredicate, float matchingValue) { @@ -212,6 +269,11 @@ private static final class FloatRawValueBasedEqPredicateEvaluator extends BaseRa _matchingValue = matchingValue; } + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitFloat(_matchingValue); + } + @Override public int getNumMatchingItems() { return 1; @@ -239,9 +301,15 @@ public int applySV(int limit, int[] docIds, float[] values) { } return matches; } + + @Override + public float getFloat() { + return _matchingValue; + } } - private static final class DoubleRawValueBasedEqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class DoubleRawValueBasedEqPredicateEvaluator extends EqRawPredicateEvaluator + implements DoubleValue { final double _matchingValue; DoubleRawValueBasedEqPredicateEvaluator(EqPredicate eqPredicate, double matchingValue) { @@ -249,6 +317,11 @@ private static final class DoubleRawValueBasedEqPredicateEvaluator extends BaseR _matchingValue = matchingValue; } + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitDouble(_matchingValue); + } + @Override public int getNumMatchingItems() { return 1; @@ -276,9 +349,14 @@ public int applySV(int limit, int[] docIds, double[] values) { } return matches; } + + @Override + public double getDouble() { + return _matchingValue; + } } - private static final class BigDecimalRawValueBasedEqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class BigDecimalRawValueBasedEqPredicateEvaluator extends EqRawPredicateEvaluator { final BigDecimal _matchingValue; BigDecimalRawValueBasedEqPredicateEvaluator(EqPredicate eqPredicate, BigDecimal matchingValue) { @@ -286,6 +364,11 @@ private static final class BigDecimalRawValueBasedEqPredicateEvaluator extends B _matchingValue = matchingValue; } + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitBigDecimal(_matchingValue); + } + @Override public int getNumMatchingItems() { return 1; @@ -302,7 +385,7 @@ public boolean applySV(BigDecimal value) { } } - private static final class StringRawValueBasedEqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class StringRawValueBasedEqPredicateEvaluator extends EqRawPredicateEvaluator { final String _matchingValue; StringRawValueBasedEqPredicateEvaluator(EqPredicate eqPredicate, String matchingValue) { @@ -310,6 +393,11 @@ private static final class StringRawValueBasedEqPredicateEvaluator extends BaseR _matchingValue = matchingValue; } + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitString(_matchingValue); + } + @Override public int getNumMatchingItems() { return 1; @@ -326,7 +414,7 @@ public boolean applySV(String value) { } } - private static final class BytesRawValueBasedEqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class BytesRawValueBasedEqPredicateEvaluator extends EqRawPredicateEvaluator { final byte[] _matchingValue; BytesRawValueBasedEqPredicateEvaluator(EqPredicate eqPredicate, byte[] matchingValue) { @@ -334,6 +422,11 @@ private static final class BytesRawValueBasedEqPredicateEvaluator extends BaseRa _matchingValue = matchingValue; } + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitBytes(_matchingValue); + } + @Override public int getNumMatchingItems() { return 1; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/FSTBasedRegexpPredicateEvaluatorFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/FSTBasedRegexpPredicateEvaluatorFactory.java index b1a0559a92a2..11dbe7aa995c 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/FSTBasedRegexpPredicateEvaluatorFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/FSTBasedRegexpPredicateEvaluatorFactory.java @@ -50,30 +50,29 @@ public static BaseDictionaryBasedPredicateEvaluator newFSTBasedEvaluator(RegexpL * Matches regexp query using FSTIndexReader. */ private static class FSTBasedRegexpPredicateEvaluator extends BaseDictionaryBasedPredicateEvaluator { - final Dictionary _dictionary; - final ImmutableRoaringBitmap _dictIds; + final ImmutableRoaringBitmap _matchingDictIdBitmap; public FSTBasedRegexpPredicateEvaluator(RegexpLikePredicate regexpLikePredicate, TextIndexReader fstIndexReader, Dictionary dictionary) { - super(regexpLikePredicate); - _dictionary = dictionary; + super(regexpLikePredicate, dictionary); String searchQuery = RegexpPatternConverterUtils.regexpLikeToLuceneRegExp(regexpLikePredicate.getValue()); - _dictIds = fstIndexReader.getDictIds(searchQuery); - } - - @Override - public boolean isAlwaysFalse() { - return _dictIds.isEmpty(); + _matchingDictIdBitmap = fstIndexReader.getDictIds(searchQuery); + int numMatchingDictIds = _matchingDictIdBitmap.getCardinality(); + if (numMatchingDictIds == 0) { + _alwaysFalse = true; + } else if (dictionary.length() == numMatchingDictIds) { + _alwaysTrue = true; + } } @Override - public boolean isAlwaysTrue() { - return _dictIds.getCardinality() == _dictionary.length(); + protected int[] calculateMatchingDictIds() { + return _matchingDictIdBitmap.toArray(); } @Override public boolean applySV(int dictId) { - return _dictIds.contains(dictId); + return _matchingDictIdBitmap.contains(dictId); } @Override @@ -88,10 +87,5 @@ public int applySV(int limit, int[] docIds, int[] values) { } return matches; } - - @Override - public int[] getMatchingDictIds() { - return _dictIds.toArray(); - } } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/InPredicateEvaluatorFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/InPredicateEvaluatorFactory.java index 6559ba502fff..ed7fb5273ff9 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/InPredicateEvaluatorFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/InPredicateEvaluatorFactory.java @@ -32,10 +32,14 @@ import java.util.List; import java.util.Set; import java.util.TreeSet; +import javax.annotation.Nullable; import org.apache.pinot.common.request.context.predicate.InPredicate; +import org.apache.pinot.common.request.context.predicate.Predicate; import org.apache.pinot.common.utils.HashUtil; +import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.data.MultiValueVisitor; import org.apache.pinot.spi.utils.ByteArray; @@ -49,14 +53,15 @@ private InPredicateEvaluatorFactory() { /** * Create a new instance of dictionary based IN predicate evaluator. * - * @param inPredicate IN predicate to evaluate - * @param dictionary Dictionary for the column - * @param dataType Data type for the column + * @param inPredicate IN predicate to evaluate + * @param dictionary Dictionary for the column + * @param dataType Data type for the column + * @param queryContext Query context * @return Dictionary based IN predicate evaluator */ public static BaseDictionaryBasedPredicateEvaluator newDictionaryBasedEvaluator(InPredicate inPredicate, - Dictionary dictionary, DataType dataType) { - return new DictionaryBasedInPredicateEvaluator(inPredicate, dictionary, dataType); + Dictionary dictionary, DataType dataType, @Nullable QueryContext queryContext) { + return new DictionaryBasedInPredicateEvaluator(inPredicate, dictionary, dataType, queryContext); } /** @@ -66,8 +71,7 @@ public static BaseDictionaryBasedPredicateEvaluator newDictionaryBasedEvaluator( * @param dataType Data type for the column * @return Raw value based IN predicate evaluator */ - public static BaseRawValueBasedPredicateEvaluator newRawValueBasedEvaluator(InPredicate inPredicate, - DataType dataType) { + public static InRawPredicateEvaluator newRawValueBasedEvaluator(InPredicate inPredicate, DataType dataType) { switch (dataType) { case INT: { int[] intValues = inPredicate.getIntValues(); @@ -124,7 +128,8 @@ public static BaseRawValueBasedPredicateEvaluator newRawValueBasedEvaluator(InPr } return new LongRawValueBasedInPredicateEvaluator(inPredicate, matchingValues); } - case STRING: { + case STRING: + case JSON: { List stringValues = inPredicate.getValues(); Set matchingValues = new ObjectOpenHashSet<>(HashUtil.getMinHashSetSize(stringValues.size())); // NOTE: Add value-by-value to avoid overhead @@ -152,41 +157,34 @@ public static BaseRawValueBasedPredicateEvaluator newRawValueBasedEvaluator(InPr private static final class DictionaryBasedInPredicateEvaluator extends BaseDictionaryBasedPredicateEvaluator { final IntSet _matchingDictIdSet; - final int _numMatchingDictIds; - int[] _matchingDictIds; - DictionaryBasedInPredicateEvaluator(InPredicate inPredicate, Dictionary dictionary, DataType dataType) { - super(inPredicate); - _matchingDictIdSet = PredicateUtils.getDictIdSet(inPredicate, dictionary, dataType); - _numMatchingDictIds = _matchingDictIdSet.size(); - if (_numMatchingDictIds == 0) { + DictionaryBasedInPredicateEvaluator(InPredicate inPredicate, Dictionary dictionary, DataType dataType, + @Nullable QueryContext queryContext) { + super(inPredicate, dictionary); + _matchingDictIdSet = PredicateUtils.getDictIdSet(inPredicate, dictionary, dataType, queryContext); + int numMatchingDictIds = _matchingDictIdSet.size(); + if (numMatchingDictIds == 0) { _alwaysFalse = true; - } else if (dictionary.length() == _numMatchingDictIds) { + } else if (dictionary.length() == numMatchingDictIds) { _alwaysTrue = true; } } @Override - public boolean applySV(int dictId) { - return _matchingDictIdSet.contains(dictId); - } - - @Override - public int getNumMatchingDictIds() { - return _numMatchingDictIds; + protected int[] calculateMatchingDictIds() { + int[] matchingDictIds = _matchingDictIdSet.toIntArray(); + Arrays.sort(matchingDictIds); + return matchingDictIds; } @Override public int getNumMatchingItems() { - return getNumMatchingDictIds(); + return _matchingDictIdSet.size(); } @Override - public int[] getMatchingDictIds() { - if (_matchingDictIds == null) { - _matchingDictIds = _matchingDictIdSet.toIntArray(); - } - return _matchingDictIds; + public boolean applySV(int dictId) { + return _matchingDictIdSet.contains(dictId); } @Override @@ -203,7 +201,18 @@ public int applySV(int limit, int[] docIds, int[] values) { } } - private static final class IntRawValueBasedInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + public static abstract class InRawPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + public InRawPredicateEvaluator(Predicate predicate) { + super(predicate); + } + + /** + * Visits the matching value of this predicate. + */ + public abstract R accept(MultiValueVisitor visitor); + } + + private static final class IntRawValueBasedInPredicateEvaluator extends InRawPredicateEvaluator { final IntSet _matchingValues; IntRawValueBasedInPredicateEvaluator(InPredicate inPredicate, IntSet matchingValues) { @@ -238,9 +247,14 @@ public int applySV(int limit, int[] docIds, int[] values) { } return matches; } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitInt(_matchingValues.toIntArray()); + } } - private static final class LongRawValueBasedInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class LongRawValueBasedInPredicateEvaluator extends InRawPredicateEvaluator { final LongSet _matchingValues; LongRawValueBasedInPredicateEvaluator(InPredicate inPredicate, LongSet matchingValues) { @@ -275,9 +289,14 @@ public int applySV(int limit, int[] docIds, long[] values) { } return matches; } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitLong(_matchingValues.toLongArray()); + } } - private static final class FloatRawValueBasedInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class FloatRawValueBasedInPredicateEvaluator extends InRawPredicateEvaluator { final FloatSet _matchingValues; FloatRawValueBasedInPredicateEvaluator(InPredicate inPredicate, FloatSet matchingValues) { @@ -312,9 +331,14 @@ public int applySV(int limit, int[] docIds, float[] values) { } return matches; } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitFloat(_matchingValues.toFloatArray()); + } } - private static final class DoubleRawValueBasedInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class DoubleRawValueBasedInPredicateEvaluator extends InRawPredicateEvaluator { final DoubleSet _matchingValues; DoubleRawValueBasedInPredicateEvaluator(InPredicate inPredicate, DoubleSet matchingValues) { @@ -349,9 +373,14 @@ public int applySV(int limit, int[] docIds, double[] values) { } return matches; } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitDouble(_matchingValues.toDoubleArray()); + } } - private static final class BigDecimalRawValueBasedInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class BigDecimalRawValueBasedInPredicateEvaluator extends InRawPredicateEvaluator { // Note: BigDecimal's compareTo is not consistent with equals (e.g. compareTo(3.0, 3) returns zero when // equals(3.0, 3) returns false). // - HashSet implementation consider both hashCode() and equals() for the key. @@ -379,9 +408,14 @@ public DataType getDataType() { public boolean applySV(BigDecimal value) { return _matchingValues.contains(value); } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitBigDecimal(_matchingValues.toArray(new BigDecimal[0])); + } } - private static final class StringRawValueBasedInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class StringRawValueBasedInPredicateEvaluator extends InRawPredicateEvaluator { final Set _matchingValues; StringRawValueBasedInPredicateEvaluator(InPredicate inPredicate, Set matchingValues) { @@ -403,9 +437,14 @@ public DataType getDataType() { public boolean applySV(String value) { return _matchingValues.contains(value); } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitString(_matchingValues.toArray(new String[0])); + } } - private static final class BytesRawValueBasedInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class BytesRawValueBasedInPredicateEvaluator extends InRawPredicateEvaluator { final Set _matchingValues; BytesRawValueBasedInPredicateEvaluator(InPredicate inPredicate, Set matchingValues) { @@ -427,5 +466,11 @@ public DataType getDataType() { public boolean applySV(byte[] value) { return _matchingValues.contains(new ByteArray(value)); } + + @Override + public R accept(MultiValueVisitor visitor) { + byte[][] bytes = _matchingValues.stream().map(ByteArray::getBytes).toArray(byte[][]::new); + return visitor.visitBytes(bytes); + } } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/NotEqualsPredicateEvaluatorFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/NotEqualsPredicateEvaluatorFactory.java index f091039f1df1..742b69c1b19c 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/NotEqualsPredicateEvaluatorFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/NotEqualsPredicateEvaluatorFactory.java @@ -21,8 +21,11 @@ import java.math.BigDecimal; import java.util.Arrays; import org.apache.pinot.common.request.context.predicate.NotEqPredicate; +import org.apache.pinot.common.request.context.predicate.Predicate; import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.data.MultiValueVisitor; +import org.apache.pinot.spi.data.SingleValueVisitor; import org.apache.pinot.spi.utils.BooleanUtils; import org.apache.pinot.spi.utils.BytesUtils; import org.apache.pinot.spi.utils.TimestampUtils; @@ -55,8 +58,7 @@ public static BaseDictionaryBasedPredicateEvaluator newDictionaryBasedEvaluator( * @param dataType Data type for the column * @return Raw value based NOT_EQ predicate evaluator */ - public static BaseRawValueBasedPredicateEvaluator newRawValueBasedEvaluator(NotEqPredicate notEqPredicate, - DataType dataType) { + public static NeqRawPredicateEvaluator newRawValueBasedEvaluator(NotEqPredicate notEqPredicate, DataType dataType) { String value = notEqPredicate.getValue(); switch (dataType) { case INT: @@ -74,6 +76,7 @@ public static BaseRawValueBasedPredicateEvaluator newRawValueBasedEvaluator(NotE case TIMESTAMP: return new LongRawValueBasedNeqPredicateEvaluator(notEqPredicate, TimestampUtils.toMillisSinceEpoch(value)); case STRING: + case JSON: return new StringRawValueBasedNeqPredicateEvaluator(notEqPredicate, value); case BYTES: return new BytesRawValueBasedNeqPredicateEvaluator(notEqPredicate, BytesUtils.toBytes(value)); @@ -84,12 +87,9 @@ public static BaseRawValueBasedPredicateEvaluator newRawValueBasedEvaluator(NotE private static final class DictionaryBasedNeqPredicateEvaluator extends BaseDictionaryBasedPredicateEvaluator { final int _nonMatchingDictId; - final int[] _nonMatchingDictIds; - final Dictionary _dictionary; - int[] _matchingDictIds; DictionaryBasedNeqPredicateEvaluator(NotEqPredicate notEqPredicate, Dictionary dictionary, DataType dataType) { - super(notEqPredicate); + super(notEqPredicate, dictionary); String predicateValue = PredicateUtils.getStoredValue(notEqPredicate.getValue(), dataType); _nonMatchingDictId = dictionary.indexOf(predicateValue); if (_nonMatchingDictId >= 0) { @@ -101,7 +101,11 @@ private static final class DictionaryBasedNeqPredicateEvaluator extends BaseDict _nonMatchingDictIds = new int[0]; _alwaysTrue = true; } - _dictionary = dictionary; + } + + @Override + protected int[] calculateMatchingDictIds() { + return PredicateUtils.getDictIds(_dictionary.length(), _nonMatchingDictId); } @Override @@ -126,36 +130,27 @@ public int applySV(int limit, int[] docIds, int[] values) { } return matches; } + } - @Override - public int[] getMatchingDictIds() { - if (_matchingDictIds == null) { - int dictionarySize = _dictionary.length(); - if (_nonMatchingDictId >= 0) { - _matchingDictIds = new int[dictionarySize - 1]; - int index = 0; - for (int dictId = 0; dictId < dictionarySize; dictId++) { - if (dictId != _nonMatchingDictId) { - _matchingDictIds[index++] = dictId; - } - } - } else { - _matchingDictIds = new int[dictionarySize]; - for (int dictId = 0; dictId < dictionarySize; dictId++) { - _matchingDictIds[dictId] = dictId; - } - } - } - return _matchingDictIds; + public static abstract class NeqRawPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + public NeqRawPredicateEvaluator(Predicate predicate) { + super(predicate); } - @Override - public int[] getNonMatchingDictIds() { - return _nonMatchingDictIds; + /** + * Visits the not matching value of this predicate. + */ + public abstract R accept(SingleValueVisitor visitor); + + /** + * Visits the not matching value of this predicate, which will be transformed into an array with a single value. + */ + public R accept(MultiValueVisitor visitor) { + return accept(visitor.asSingleValueVisitor()); } } - private static final class IntRawValueBasedNeqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class IntRawValueBasedNeqPredicateEvaluator extends NeqRawPredicateEvaluator { final int _nonMatchingValue; IntRawValueBasedNeqPredicateEvaluator(NotEqPredicate notEqPredicate, int nonMatchingValue) { @@ -190,9 +185,14 @@ public int applySV(int limit, int[] docIds, int[] values) { } return matches; } + + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitInt(_nonMatchingValue); + } } - private static final class LongRawValueBasedNeqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class LongRawValueBasedNeqPredicateEvaluator extends NeqRawPredicateEvaluator { final long _nonMatchingValue; LongRawValueBasedNeqPredicateEvaluator(NotEqPredicate notEqPredicate, long nonMatchingValue) { @@ -227,9 +227,14 @@ public int applySV(int limit, int[] docIds, long[] values) { } return matches; } + + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitLong(_nonMatchingValue); + } } - private static final class FloatRawValueBasedNeqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class FloatRawValueBasedNeqPredicateEvaluator extends NeqRawPredicateEvaluator { final float _nonMatchingValue; FloatRawValueBasedNeqPredicateEvaluator(NotEqPredicate notEqPredicate, float nonMatchingValue) { @@ -264,9 +269,14 @@ public int applySV(int limit, int[] docIds, float[] values) { } return matches; } + + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitFloat(_nonMatchingValue); + } } - private static final class DoubleRawValueBasedNeqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class DoubleRawValueBasedNeqPredicateEvaluator extends NeqRawPredicateEvaluator { final double _nonMatchingValue; DoubleRawValueBasedNeqPredicateEvaluator(NotEqPredicate notEqPredicate, double nonMatchingValue) { @@ -301,9 +311,14 @@ public int applySV(int limit, int[] docIds, double[] values) { } return matches; } + + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitDouble(_nonMatchingValue); + } } - private static final class BigDecimalRawValueBasedNeqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class BigDecimalRawValueBasedNeqPredicateEvaluator extends NeqRawPredicateEvaluator { final BigDecimal _nonMatchingValue; BigDecimalRawValueBasedNeqPredicateEvaluator(NotEqPredicate notEqPredicate, BigDecimal nonMatchingValue) { @@ -325,9 +340,14 @@ public DataType getDataType() { public boolean applySV(BigDecimal value) { return _nonMatchingValue.compareTo(value) != 0; } + + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitBigDecimal(_nonMatchingValue); + } } - private static final class StringRawValueBasedNeqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class StringRawValueBasedNeqPredicateEvaluator extends NeqRawPredicateEvaluator { final String _nonMatchingValue; StringRawValueBasedNeqPredicateEvaluator(NotEqPredicate notEqPredicate, String nonMatchingValue) { @@ -349,9 +369,14 @@ public DataType getDataType() { public boolean applySV(String value) { return !_nonMatchingValue.equals(value); } + + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitString(_nonMatchingValue); + } } - private static final class BytesRawValueBasedNeqPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class BytesRawValueBasedNeqPredicateEvaluator extends NeqRawPredicateEvaluator { final byte[] _nonMatchingValue; BytesRawValueBasedNeqPredicateEvaluator(NotEqPredicate notEqPredicate, byte[] nonMatchingValue) { @@ -373,5 +398,10 @@ public DataType getDataType() { public boolean applySV(byte[] value) { return !Arrays.equals(_nonMatchingValue, value); } + + @Override + public R accept(SingleValueVisitor visitor) { + return visitor.visitBytes(_nonMatchingValue); + } } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/NotInPredicateEvaluatorFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/NotInPredicateEvaluatorFactory.java index b3666b932f19..daabde106942 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/NotInPredicateEvaluatorFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/NotInPredicateEvaluatorFactory.java @@ -32,10 +32,14 @@ import java.util.List; import java.util.Set; import java.util.TreeSet; +import javax.annotation.Nullable; import org.apache.pinot.common.request.context.predicate.NotInPredicate; +import org.apache.pinot.common.request.context.predicate.Predicate; import org.apache.pinot.common.utils.HashUtil; +import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.data.MultiValueVisitor; import org.apache.pinot.spi.utils.ByteArray; @@ -50,13 +54,14 @@ private NotInPredicateEvaluatorFactory() { * Create a new instance of dictionary based NOT_IN predicate evaluator. * * @param notInPredicate NOT_IN predicate to evaluate - * @param dictionary Dictionary for the column - * @param dataType Data type for the column + * @param dictionary Dictionary for the column + * @param dataType Data type for the column + * @param queryContext Query context * @return Dictionary based NOT_IN predicate evaluator */ public static BaseDictionaryBasedPredicateEvaluator newDictionaryBasedEvaluator(NotInPredicate notInPredicate, - Dictionary dictionary, DataType dataType) { - return new DictionaryBasedNotInPredicateEvaluator(notInPredicate, dictionary, dataType); + Dictionary dictionary, DataType dataType, @Nullable QueryContext queryContext) { + return new DictionaryBasedNotInPredicateEvaluator(notInPredicate, dictionary, dataType, queryContext); } /** @@ -66,8 +71,7 @@ public static BaseDictionaryBasedPredicateEvaluator newDictionaryBasedEvaluator( * @param dataType Data type for the column * @return Raw value based NOT_IN predicate evaluator */ - public static BaseRawValueBasedPredicateEvaluator newRawValueBasedEvaluator(NotInPredicate notInPredicate, - DataType dataType) { + public static NotInRawPredicateEvaluator newRawValueBasedEvaluator(NotInPredicate notInPredicate, DataType dataType) { switch (dataType) { case INT: { int[] intValues = notInPredicate.getIntValues(); @@ -124,7 +128,8 @@ public static BaseRawValueBasedPredicateEvaluator newRawValueBasedEvaluator(NotI } return new LongRawValueBasedNotInPredicateEvaluator(notInPredicate, nonMatchingValues); } - case STRING: { + case STRING: + case JSON: { List stringValues = notInPredicate.getValues(); Set nonMatchingValues = new ObjectOpenHashSet<>(HashUtil.getMinHashSetSize(stringValues.size())); // NOTE: Add value-by-value to avoid overhead @@ -152,26 +157,34 @@ public static BaseRawValueBasedPredicateEvaluator newRawValueBasedEvaluator(NotI public static final class DictionaryBasedNotInPredicateEvaluator extends BaseDictionaryBasedPredicateEvaluator { final IntSet _nonMatchingDictIdSet; - final int _numNonMatchingDictIds; - final Dictionary _dictionary; - int[] _matchingDictIds; - int[] _nonMatchingDictIds; - DictionaryBasedNotInPredicateEvaluator(NotInPredicate notInPredicate, Dictionary dictionary, DataType dataType) { - super(notInPredicate); - _nonMatchingDictIdSet = PredicateUtils.getDictIdSet(notInPredicate, dictionary, dataType); - _numNonMatchingDictIds = _nonMatchingDictIdSet.size(); - if (_numNonMatchingDictIds == 0) { + DictionaryBasedNotInPredicateEvaluator(NotInPredicate notInPredicate, Dictionary dictionary, DataType dataType, + @Nullable QueryContext queryContext) { + super(notInPredicate, dictionary); + _nonMatchingDictIdSet = PredicateUtils.getDictIdSet(notInPredicate, dictionary, dataType, queryContext); + int numNonMatchingDictIds = _nonMatchingDictIdSet.size(); + if (numNonMatchingDictIds == 0) { _alwaysTrue = true; - } else if (dictionary.length() == _numNonMatchingDictIds) { + } else if (dictionary.length() == numNonMatchingDictIds) { _alwaysFalse = true; } - _dictionary = dictionary; + } + + @Override + protected int[] calculateMatchingDictIds() { + return PredicateUtils.flipDictIds(getNonMatchingDictIds(), _dictionary.length()); + } + + @Override + protected int[] calculateNonMatchingDictIds() { + int[] nonMatchingDictIds = _nonMatchingDictIdSet.toIntArray(); + Arrays.sort(nonMatchingDictIds); + return nonMatchingDictIds; } @Override public int getNumMatchingItems() { - return -_numNonMatchingDictIds; + return -_nonMatchingDictIdSet.size(); } @Override @@ -191,37 +204,20 @@ public int applySV(int limit, int[] docIds, int[] values) { } return matches; } + } - @Override - public int[] getMatchingDictIds() { - if (_matchingDictIds == null) { - int dictionarySize = _dictionary.length(); - _matchingDictIds = new int[dictionarySize - _numNonMatchingDictIds]; - int index = 0; - for (int dictId = 0; dictId < dictionarySize; dictId++) { - if (!_nonMatchingDictIdSet.contains(dictId)) { - _matchingDictIds[index++] = dictId; - } - } - } - return _matchingDictIds; - } - - @Override - public int getNumNonMatchingDictIds() { - return _numNonMatchingDictIds; + public static abstract class NotInRawPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + public NotInRawPredicateEvaluator(Predicate predicate) { + super(predicate); } - @Override - public int[] getNonMatchingDictIds() { - if (_nonMatchingDictIds == null) { - _nonMatchingDictIds = _nonMatchingDictIdSet.toIntArray(); - } - return _nonMatchingDictIds; - } + /** + * Visits the not matching value of this predicate. + */ + public abstract R accept(MultiValueVisitor visitor); } - private static final class IntRawValueBasedNotInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class IntRawValueBasedNotInPredicateEvaluator extends NotInRawPredicateEvaluator { final IntSet _nonMatchingValues; IntRawValueBasedNotInPredicateEvaluator(NotInPredicate notInPredicate, IntSet nonMatchingValues) { @@ -256,9 +252,14 @@ public int applySV(int limit, int[] docIds, int[] values) { } return matches; } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitInt(_nonMatchingValues.toIntArray()); + } } - private static final class LongRawValueBasedNotInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class LongRawValueBasedNotInPredicateEvaluator extends NotInRawPredicateEvaluator { final LongSet _nonMatchingValues; LongRawValueBasedNotInPredicateEvaluator(NotInPredicate notInPredicate, LongSet nonMatchingValues) { @@ -293,9 +294,14 @@ public int applySV(int limit, int[] docIds, long[] values) { } return matches; } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitLong(_nonMatchingValues.toLongArray()); + } } - private static final class FloatRawValueBasedNotInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class FloatRawValueBasedNotInPredicateEvaluator extends NotInRawPredicateEvaluator { final FloatSet _nonMatchingValues; FloatRawValueBasedNotInPredicateEvaluator(NotInPredicate notInPredicate, FloatSet nonMatchingValues) { @@ -330,9 +336,14 @@ public int applySV(int limit, int[] docIds, float[] values) { } return matches; } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitFloat(_nonMatchingValues.toFloatArray()); + } } - private static final class DoubleRawValueBasedNotInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class DoubleRawValueBasedNotInPredicateEvaluator extends NotInRawPredicateEvaluator { final DoubleSet _nonMatchingValues; DoubleRawValueBasedNotInPredicateEvaluator(NotInPredicate notInPredicate, DoubleSet nonMatchingValues) { @@ -367,10 +378,14 @@ public int applySV(int limit, int[] docIds, double[] values) { } return matches; } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitDouble(_nonMatchingValues.toDoubleArray()); + } } - private static final class BigDecimalRawValueBasedNotInPredicateEvaluator - extends BaseRawValueBasedPredicateEvaluator { + private static final class BigDecimalRawValueBasedNotInPredicateEvaluator extends NotInRawPredicateEvaluator { // See: BigDecimalRawValueBasedInPredicateEvaluator. final TreeSet _nonMatchingValues; @@ -394,9 +409,14 @@ public DataType getDataType() { public boolean applySV(BigDecimal value) { return !_nonMatchingValues.contains(value); } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitBigDecimal(_nonMatchingValues.toArray(new BigDecimal[0])); + } } - private static final class StringRawValueBasedNotInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class StringRawValueBasedNotInPredicateEvaluator extends NotInRawPredicateEvaluator { final Set _nonMatchingValues; StringRawValueBasedNotInPredicateEvaluator(NotInPredicate notInPredicate, Set nonMatchingValues) { @@ -418,9 +438,14 @@ public DataType getDataType() { public boolean applySV(String value) { return !_nonMatchingValues.contains(value); } + + @Override + public R accept(MultiValueVisitor visitor) { + return visitor.visitString(_nonMatchingValues.toArray(new String[0])); + } } - private static final class BytesRawValueBasedNotInPredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class BytesRawValueBasedNotInPredicateEvaluator extends NotInRawPredicateEvaluator { final Set _nonMatchingValues; BytesRawValueBasedNotInPredicateEvaluator(NotInPredicate notInPredicate, Set nonMatchingValues) { @@ -442,5 +467,11 @@ public DataType getDataType() { public boolean applySV(byte[] value) { return !_nonMatchingValues.contains(new ByteArray(value)); } + + @Override + public R accept(MultiValueVisitor visitor) { + byte[][] bytes = _nonMatchingValues.stream().map(ByteArray::getBytes).toArray(byte[][]::new); + return visitor.visitBytes(bytes); + } } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateEvaluator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateEvaluator.java index 889e28710794..09b420f48f04 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateEvaluator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateEvaluator.java @@ -102,35 +102,24 @@ default int applySV(int limit, int[] docIds, int[] values) { boolean applyMV(int[] values, int length); /** - * APIs for dictionary based predicate evaluator - */ - - /** - * return the number of matching items specified by predicate - * negative number indicates exclusive (not eq, not in) match - * return {@code Integer.MIN_VALUE} for not applicable + * Get the number of matching items. Negative number indicates exclusive (e.g. NOT_EQ, NOT_IN) match. Returns + * {@code Integer.MIN_VALUE} if not applicable. */ default int getNumMatchingItems() { return Integer.MIN_VALUE; - }; + } /** - * Get the number of matching dictionary ids. + * APIs for dictionary based predicate evaluator */ - int getNumMatchingDictIds(); /** - * Get the matching dictionary ids. + * Get the matching dictionary ids. The returned ids should be sorted. */ int[] getMatchingDictIds(); /** - * Get the number of non-matching dictionary ids. - */ - int getNumNonMatchingDictIds(); - - /** - * Get the non-matching dictionary ids. + * Get the non-matching dictionary ids. The returned ids should be sorted. */ int[] getNonMatchingDictIds(); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateEvaluatorProvider.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateEvaluatorProvider.java index 0a3bc7f952f0..519168818b83 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateEvaluatorProvider.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateEvaluatorProvider.java @@ -26,6 +26,8 @@ import org.apache.pinot.common.request.context.predicate.Predicate; import org.apache.pinot.common.request.context.predicate.RangePredicate; import org.apache.pinot.common.request.context.predicate.RegexpLikePredicate; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.exception.BadQueryRequestException; @@ -37,6 +39,11 @@ private PredicateEvaluatorProvider() { public static PredicateEvaluator getPredicateEvaluator(Predicate predicate, @Nullable Dictionary dictionary, DataType dataType) { + return getPredicateEvaluator(predicate, dictionary, dataType, null); + } + + public static PredicateEvaluator getPredicateEvaluator(Predicate predicate, @Nullable Dictionary dictionary, + DataType dataType, @Nullable QueryContext queryContext) { try { if (dictionary != null) { // dictionary based predicate evaluators @@ -48,11 +55,11 @@ public static PredicateEvaluator getPredicateEvaluator(Predicate predicate, @Nul return NotEqualsPredicateEvaluatorFactory .newDictionaryBasedEvaluator((NotEqPredicate) predicate, dictionary, dataType); case IN: - return InPredicateEvaluatorFactory - .newDictionaryBasedEvaluator((InPredicate) predicate, dictionary, dataType); + return InPredicateEvaluatorFactory.newDictionaryBasedEvaluator((InPredicate) predicate, dictionary, + dataType, queryContext); case NOT_IN: - return NotInPredicateEvaluatorFactory - .newDictionaryBasedEvaluator((NotInPredicate) predicate, dictionary, dataType); + return NotInPredicateEvaluatorFactory.newDictionaryBasedEvaluator((NotInPredicate) predicate, dictionary, + dataType, queryContext); case RANGE: return RangePredicateEvaluatorFactory .newDictionaryBasedEvaluator((RangePredicate) predicate, dictionary, dataType); @@ -87,4 +94,10 @@ public static PredicateEvaluator getPredicateEvaluator(Predicate predicate, @Nul throw new BadQueryRequestException(e); } } + + public static PredicateEvaluator getPredicateEvaluator(Predicate predicate, DataSource dataSource, + QueryContext queryContext) { + return getPredicateEvaluator(predicate, dataSource.getDictionary(), + dataSource.getDataSourceMetadata().getDataType(), queryContext); + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateUtils.java index a487d36654fd..c7b93cf086c7 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateUtils.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/PredicateUtils.java @@ -18,16 +18,21 @@ */ package org.apache.pinot.core.operator.filter.predicate; +import com.google.common.base.Equivalence; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.List; +import javax.annotation.Nullable; import org.apache.pinot.common.request.context.predicate.BaseInPredicate; import org.apache.pinot.common.utils.HashUtil; +import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.utils.BooleanUtils; import org.apache.pinot.spi.utils.ByteArray; +import org.apache.pinot.spi.utils.CommonConstants.Broker.Request.QueryOptionKey; import org.apache.pinot.spi.utils.TimestampUtils; @@ -70,7 +75,8 @@ public static String getStoredTimestampValue(String timestampValue) { /** * Returns a dictionary id set of the values in the given IN/NOT_IN predicate. */ - public static IntSet getDictIdSet(BaseInPredicate inPredicate, Dictionary dictionary, DataType dataType) { + public static IntSet getDictIdSet(BaseInPredicate inPredicate, Dictionary dictionary, DataType dataType, + @Nullable QueryContext queryContext) { List values = inPredicate.getValues(); int hashSetSize = Integer.min(HashUtil.getMinHashSetSize(values.size()), MAX_INITIAL_DICT_ID_SET_SIZE); IntSet dictIdSet = new IntOpenHashSet(hashSetSize); @@ -139,12 +145,36 @@ public static IntSet getDictIdSet(BaseInPredicate inPredicate, Dictionary dictio } break; case STRING: - for (String value : values) { - int dictId = dictionary.indexOf(value); - if (dictId >= 0) { - dictIdSet.add(dictId); + if (queryContext == null || values.size() <= 1) { + dictionary.getDictIds(values, dictIdSet); + break; + } + Dictionary.SortedBatchLookupAlgorithm lookupAlgorithm = + Dictionary.SortedBatchLookupAlgorithm.DIVIDE_BINARY_SEARCH; + String inPredicateLookupAlgorithm = + queryContext.getQueryOptions().get(QueryOptionKey.IN_PREDICATE_LOOKUP_ALGORITHM); + if (inPredicateLookupAlgorithm != null) { + try { + lookupAlgorithm = Dictionary.SortedBatchLookupAlgorithm.valueOf(inPredicateLookupAlgorithm.toUpperCase()); + } catch (Exception e) { + throw new IllegalArgumentException("Illegal IN predicate lookup algorithm: " + inPredicateLookupAlgorithm); } } + if (lookupAlgorithm == Dictionary.SortedBatchLookupAlgorithm.PLAIN_BINARY_SEARCH) { + dictionary.getDictIds(values, dictIdSet); + break; + } + if (Boolean.parseBoolean(queryContext.getQueryOptions().get(QueryOptionKey.IN_PREDICATE_PRE_SORTED))) { + dictionary.getDictIds(values, dictIdSet, lookupAlgorithm); + } else { + //noinspection unchecked + dictionary.getDictIds( + queryContext.getOrComputeSharedValue(List.class, Equivalence.identity().wrap(inPredicate), k -> { + List sortedValues = new ArrayList<>(values); + sortedValues.sort(null); + return sortedValues; + }), dictIdSet, lookupAlgorithm); + } break; case BYTES: ByteArray[] bytesValues = inPredicate.getBytesValues(); @@ -160,4 +190,38 @@ public static IntSet getDictIdSet(BaseInPredicate inPredicate, Dictionary dictio } return dictIdSet; } + + public static int[] flipDictIds(int[] dictIds, int length) { + int numDictIds = dictIds.length; + int[] flippedDictIds = new int[length - numDictIds]; + int flippedDictIdsIndex = 0; + int dictIdsIndex = 0; + for (int dictId = 0; dictId < length; dictId++) { + if (dictIdsIndex < numDictIds && dictId == dictIds[dictIdsIndex]) { + dictIdsIndex++; + } else { + flippedDictIds[flippedDictIdsIndex++] = dictId; + } + } + return flippedDictIds; + } + + public static int[] getDictIds(int length, int excludeId) { + int[] dictIds; + if (excludeId >= 0) { + dictIds = new int[length - 1]; + int index = 0; + for (int dictId = 0; dictId < length; dictId++) { + if (dictId != excludeId) { + dictIds[index++] = dictId; + } + } + } else { + dictIds = new int[length]; + for (int dictId = 0; dictId < length; dictId++) { + dictIds[dictId] = dictId; + } + } + return dictIds; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/RangePredicateEvaluatorFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/RangePredicateEvaluatorFactory.java index f3b1cef1af45..e9bd3b4b0a7d 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/RangePredicateEvaluatorFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/RangePredicateEvaluatorFactory.java @@ -22,6 +22,10 @@ import it.unimi.dsi.fastutil.ints.IntSet; import java.math.BigDecimal; import org.apache.pinot.common.request.context.predicate.RangePredicate; +import org.apache.pinot.core.operator.filter.predicate.traits.DoubleRange; +import org.apache.pinot.core.operator.filter.predicate.traits.FloatRange; +import org.apache.pinot.core.operator.filter.predicate.traits.IntRange; +import org.apache.pinot.core.operator.filter.predicate.traits.LongRange; import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.utils.BooleanUtils; @@ -112,16 +116,16 @@ public static BaseRawValueBasedPredicateEvaluator newRawValueBasedEvaluator(Rang } } - public static final class SortedDictionaryBasedRangePredicateEvaluator extends BaseDictionaryBasedPredicateEvaluator { + public static final class SortedDictionaryBasedRangePredicateEvaluator extends BaseDictionaryBasedPredicateEvaluator + implements IntRange { final int _startDictId; // Exclusive final int _endDictId; final int _numMatchingDictIds; - int[] _matchingDictIds; SortedDictionaryBasedRangePredicateEvaluator(RangePredicate rangePredicate, Dictionary dictionary, DataType dataType) { - super(rangePredicate); + super(rangePredicate, dictionary); String lowerBound = rangePredicate.getLowerBound(); String upperBound = rangePredicate.getUpperBound(); boolean lowerInclusive = rangePredicate.isLowerInclusive(); @@ -156,8 +160,8 @@ public static final class SortedDictionaryBasedRangePredicateEvaluator extends B } } - _numMatchingDictIds = _endDictId - _startDictId; - if (_numMatchingDictIds <= 0) { + _numMatchingDictIds = Integer.max(_endDictId - _startDictId, 0); + if (_numMatchingDictIds == 0) { _alwaysFalse = true; } else if (dictionary.length() == _numMatchingDictIds) { _alwaysTrue = true; @@ -172,6 +176,46 @@ public int getEndDictId() { return _endDictId; } + @Override + protected int[] calculateMatchingDictIds() { + if (_numMatchingDictIds == 0) { + return new int[0]; + } else { + int[] matchingDictIds = new int[_numMatchingDictIds]; + for (int i = 0; i < _numMatchingDictIds; i++) { + matchingDictIds[i] = _startDictId + i; + } + return matchingDictIds; + } + } + + @Override + protected int[] calculateNonMatchingDictIds() { + int dictionarySize = _dictionary.length(); + if (_numMatchingDictIds == 0) { + int[] nonMatchingDictIds = new int[dictionarySize]; + for (int i = 0; i < dictionarySize; i++) { + nonMatchingDictIds[i] = i; + } + return nonMatchingDictIds; + } else { + int[] nonMatchingDictIds = new int[dictionarySize - _numMatchingDictIds]; + int index = 0; + for (int i = 0; i < _startDictId; i++) { + nonMatchingDictIds[index++] = i; + } + for (int i = _endDictId; i < dictionarySize; i++) { + nonMatchingDictIds[index++] = i; + } + return nonMatchingDictIds; + } + } + + @Override + public int getNumMatchingItems() { + return _numMatchingDictIds; + } + @Override public boolean applySV(int dictId) { return _startDictId <= dictId && _endDictId > dictId; @@ -191,28 +235,13 @@ public int applySV(int limit, int[] docIds, int[] dictIds) { } @Override - public int getNumMatchingDictIds() { - return _numMatchingDictIds; - } - - @Override - public int[] getMatchingDictIds() { - if (_matchingDictIds == null) { - if (_numMatchingDictIds <= 0) { - _matchingDictIds = new int[0]; - } else { - _matchingDictIds = new int[_numMatchingDictIds]; - for (int i = 0; i < _numMatchingDictIds; i++) { - _matchingDictIds[i] = _startDictId + i; - } - } - } - return _matchingDictIds; + public int getInclusiveLowerBound() { + return getStartDictId(); } @Override - public int getNumMatchingItems() { - return Math.max(_numMatchingDictIds, 0); + public int getInclusiveUpperBound() { + return getEndDictId() - 1; } } @@ -223,15 +252,13 @@ private static final class UnsortedDictionaryBasedRangePredicateEvaluator // TODO: Tune this threshold private static final int DICT_ID_SET_BASED_CARDINALITY_THRESHOLD = 1000; - final Dictionary _dictionary; final boolean _dictIdSetBased; final IntSet _matchingDictIdSet; final BaseRawValueBasedPredicateEvaluator _rawValueBasedEvaluator; UnsortedDictionaryBasedRangePredicateEvaluator(RangePredicate rangePredicate, Dictionary dictionary, DataType dataType) { - super(rangePredicate); - _dictionary = dictionary; + super(rangePredicate, dictionary); int cardinality = dictionary.length(); if (cardinality < DICT_ID_SET_BASED_CARDINALITY_THRESHOLD) { _dictIdSetBased = true; @@ -259,6 +286,16 @@ private static final class UnsortedDictionaryBasedRangePredicateEvaluator } } + @Override + public int[] getMatchingDictIds() { + throw new UnsupportedOperationException(); + } + + @Override + public int getNumMatchingItems() { + return _matchingDictIdSet != null ? _matchingDictIdSet.size() : Integer.MIN_VALUE; + } + @Override public boolean applySV(int dictId) { if (_dictIdSetBased) { @@ -273,28 +310,21 @@ public boolean applySV(int dictId) { return _rawValueBasedEvaluator.applySV(_dictionary.getFloatValue(dictId)); case DOUBLE: return _rawValueBasedEvaluator.applySV(_dictionary.getDoubleValue(dictId)); + case BIG_DECIMAL: + return _rawValueBasedEvaluator.applySV(_dictionary.getBigDecimalValue(dictId)); case STRING: return _rawValueBasedEvaluator.applySV(_dictionary.getStringValue(dictId)); case BYTES: return _rawValueBasedEvaluator.applySV(_dictionary.getBytesValue(dictId)); default: - throw new IllegalStateException(); + throw new IllegalStateException("Unsupported value type: " + _dictionary.getValueType()); } } } - - @Override - public int getNumMatchingItems() { - return _matchingDictIdSet == null ? super.getNumMatchingItems() : _matchingDictIdSet.size(); - } - - @Override - public int[] getMatchingDictIds() { - throw new UnsupportedOperationException(); - } } - public static final class IntRawValueBasedRangePredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class IntRawValueBasedRangePredicateEvaluator extends BaseRawValueBasedPredicateEvaluator + implements IntRange { final int _inclusiveLowerBound; final int _inclusiveUpperBound; @@ -315,10 +345,12 @@ public static final class IntRawValueBasedRangePredicateEvaluator extends BaseRa } } + @Override public int getInclusiveLowerBound() { return _inclusiveLowerBound; } + @Override public int getInclusiveUpperBound() { return _inclusiveUpperBound; } @@ -347,7 +379,8 @@ public int applySV(int limit, int[] docIds, int[] values) { } } - public static final class LongRawValueBasedRangePredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class LongRawValueBasedRangePredicateEvaluator extends BaseRawValueBasedPredicateEvaluator + implements LongRange { final long _inclusiveLowerBound; final long _inclusiveUpperBound; @@ -368,10 +401,12 @@ public static final class LongRawValueBasedRangePredicateEvaluator extends BaseR } } + @Override public long getInclusiveLowerBound() { return _inclusiveLowerBound; } + @Override public long getInclusiveUpperBound() { return _inclusiveUpperBound; } @@ -400,7 +435,8 @@ public int applySV(int limit, int[] docIds, long[] values) { } } - public static final class FloatRawValueBasedRangePredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class FloatRawValueBasedRangePredicateEvaluator extends BaseRawValueBasedPredicateEvaluator + implements FloatRange { final float _inclusiveLowerBound; final float _inclusiveUpperBound; @@ -421,10 +457,12 @@ public static final class FloatRawValueBasedRangePredicateEvaluator extends Base } } + @Override public float getInclusiveLowerBound() { return _inclusiveLowerBound; } + @Override public float getInclusiveUpperBound() { return _inclusiveUpperBound; } @@ -453,7 +491,8 @@ public int applySV(int limit, int[] docIds, float[] values) { } } - public static final class DoubleRawValueBasedRangePredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { + private static final class DoubleRawValueBasedRangePredicateEvaluator extends BaseRawValueBasedPredicateEvaluator + implements DoubleRange { final double _inclusiveLowerBound; final double _inclusiveUpperBound; @@ -474,10 +513,12 @@ public static final class DoubleRawValueBasedRangePredicateEvaluator extends Bas } } + @Override public double getInclusiveLowerBound() { return _inclusiveLowerBound; } + @Override public double getInclusiveUpperBound() { return _inclusiveUpperBound; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/RegexpLikePredicateEvaluatorFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/RegexpLikePredicateEvaluatorFactory.java index a3af9de19494..e9c2bb73fd95 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/RegexpLikePredicateEvaluatorFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/RegexpLikePredicateEvaluatorFactory.java @@ -19,10 +19,8 @@ package org.apache.pinot.core.operator.filter.predicate; import com.google.common.base.Preconditions; -import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.ints.IntList; -import java.util.regex.Matcher; import org.apache.pinot.common.request.context.predicate.RegexpLikePredicate; +import org.apache.pinot.common.utils.regex.Matcher; import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.spi.data.FieldSpec.DataType; @@ -44,7 +42,8 @@ private RegexpLikePredicateEvaluatorFactory() { */ public static BaseDictionaryBasedPredicateEvaluator newDictionaryBasedEvaluator( RegexpLikePredicate regexpLikePredicate, Dictionary dictionary, DataType dataType) { - Preconditions.checkArgument(dataType == DataType.STRING, "Unsupported data type: " + dataType); + boolean condition = (dataType == DataType.STRING || dataType == DataType.JSON); + Preconditions.checkArgument(condition, "Unsupported data type: " + dataType); return new DictionaryBasedRegexpLikePredicateEvaluator(regexpLikePredicate, dictionary); } @@ -65,12 +64,9 @@ private static final class DictionaryBasedRegexpLikePredicateEvaluator extends B // Reuse matcher to avoid excessive allocation. This is safe to do because the evaluator is always used // within the scope of a single thread. final Matcher _matcher; - final Dictionary _dictionary; - int[] _matchingDictIds; public DictionaryBasedRegexpLikePredicateEvaluator(RegexpLikePredicate regexpLikePredicate, Dictionary dictionary) { - super(regexpLikePredicate); - _dictionary = dictionary; + super(regexpLikePredicate, dictionary); _matcher = regexpLikePredicate.getPattern().matcher(""); } @@ -91,21 +87,6 @@ public int applySV(int limit, int[] docIds, int[] values) { } return matches; } - - @Override - public int[] getMatchingDictIds() { - if (_matchingDictIds == null) { - IntList matchingDictIds = new IntArrayList(); - int dictionarySize = _dictionary.length(); - for (int dictId = 0; dictId < dictionarySize; dictId++) { - if (applySV(dictId)) { - matchingDictIds.add(dictId); - } - } - _matchingDictIds = matchingDictIds.toIntArray(); - } - return _matchingDictIds; - } } private static final class RawValueBasedRegexpLikePredicateEvaluator extends BaseRawValueBasedPredicateEvaluator { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/DoubleRange.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/DoubleRange.java new file mode 100644 index 000000000000..bd2fe790b691 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/DoubleRange.java @@ -0,0 +1,25 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.filter.predicate.traits; + +public interface DoubleRange { + double getInclusiveLowerBound(); + + double getInclusiveUpperBound(); +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/DoubleValue.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/DoubleValue.java new file mode 100644 index 000000000000..27b29f93e477 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/DoubleValue.java @@ -0,0 +1,23 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.filter.predicate.traits; + +public interface DoubleValue { + double getDouble(); +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/FloatRange.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/FloatRange.java new file mode 100644 index 000000000000..44b38ae62631 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/FloatRange.java @@ -0,0 +1,25 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.filter.predicate.traits; + +public interface FloatRange { + float getInclusiveLowerBound(); + + float getInclusiveUpperBound(); +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/FloatValue.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/FloatValue.java new file mode 100644 index 000000000000..4dac96a0498a --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/FloatValue.java @@ -0,0 +1,23 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.filter.predicate.traits; + +public interface FloatValue { + float getFloat(); +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/IntRange.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/IntRange.java new file mode 100644 index 000000000000..c5b77d487f28 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/IntRange.java @@ -0,0 +1,25 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.filter.predicate.traits; + +public interface IntRange { + int getInclusiveLowerBound(); + + int getInclusiveUpperBound(); +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/IntValue.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/IntValue.java new file mode 100644 index 000000000000..4e3aa09ce180 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/IntValue.java @@ -0,0 +1,23 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.filter.predicate.traits; + +public interface IntValue { + int getInt(); +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/LongRange.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/LongRange.java new file mode 100644 index 000000000000..f3a4d17aeb07 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/LongRange.java @@ -0,0 +1,25 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.filter.predicate.traits; + +public interface LongRange { + long getInclusiveLowerBound(); + + long getInclusiveUpperBound(); +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/LongValue.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/LongValue.java new file mode 100644 index 000000000000..62d4c097d396 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/filter/predicate/traits/LongValue.java @@ -0,0 +1,23 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.filter.predicate.traits; + +public interface LongValue { + long getLong(); +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/AggregationOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/AggregationOperator.java index 97d81b61cfa4..0c89cec32ec8 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/AggregationOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/AggregationOperator.java @@ -20,15 +20,16 @@ import java.util.Collections; import java.util.List; -import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.BaseOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; import org.apache.pinot.core.operator.ExecutionStatistics; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.blocks.results.AggregationResultsBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; import org.apache.pinot.core.query.aggregation.AggregationExecutor; import org.apache.pinot.core.query.aggregation.DefaultAggregationExecutor; import org.apache.pinot.core.query.aggregation.function.AggregationFunction; +import org.apache.pinot.core.query.aggregation.function.AggregationFunctionUtils.AggregationInfo; +import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.startree.executor.StarTreeAggregationExecutor; @@ -39,19 +40,20 @@ public class AggregationOperator extends BaseOperator { private static final String EXPLAIN_NAME = "AGGREGATE"; + private final QueryContext _queryContext; private final AggregationFunction[] _aggregationFunctions; - private final TransformOperator _transformOperator; - private final long _numTotalDocs; + private final BaseProjectOperator _projectOperator; private final boolean _useStarTree; + private final long _numTotalDocs; private int _numDocsScanned = 0; - public AggregationOperator(AggregationFunction[] aggregationFunctions, TransformOperator transformOperator, - long numTotalDocs, boolean useStarTree) { - _aggregationFunctions = aggregationFunctions; - _transformOperator = transformOperator; + public AggregationOperator(QueryContext queryContext, AggregationInfo aggregationInfo, long numTotalDocs) { + _queryContext = queryContext; + _aggregationFunctions = queryContext.getAggregationFunctions(); + _projectOperator = aggregationInfo.getProjectOperator(); + _useStarTree = aggregationInfo.isUseStarTree(); _numTotalDocs = numTotalDocs; - _useStarTree = useStarTree; } @Override @@ -63,25 +65,25 @@ protected AggregationResultsBlock getNextBlock() { } else { aggregationExecutor = new DefaultAggregationExecutor(_aggregationFunctions); } - TransformBlock transformBlock; - while ((transformBlock = _transformOperator.nextBlock()) != null) { - _numDocsScanned += transformBlock.getNumDocs(); - aggregationExecutor.aggregate(transformBlock); + ValueBlock valueBlock; + while ((valueBlock = _projectOperator.nextBlock()) != null) { + _numDocsScanned += valueBlock.getNumDocs(); + aggregationExecutor.aggregate(valueBlock); } // Build intermediate result block based on aggregation result from the executor - return new AggregationResultsBlock(_aggregationFunctions, aggregationExecutor.getResult()); + return new AggregationResultsBlock(_aggregationFunctions, aggregationExecutor.getResult(), _queryContext); } @Override - public List getChildOperators() { - return Collections.singletonList(_transformOperator); + public List> getChildOperators() { + return Collections.singletonList(_projectOperator); } @Override public ExecutionStatistics getExecutionStatistics() { - long numEntriesScannedInFilter = _transformOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); - long numEntriesScannedPostFilter = (long) _numDocsScanned * _transformOperator.getNumColumnsProjected(); + long numEntriesScannedInFilter = _projectOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); + long numEntriesScannedPostFilter = (long) _numDocsScanned * _projectOperator.getNumColumnsProjected(); return new ExecutionStatistics(_numDocsScanned, numEntriesScannedInFilter, numEntriesScannedPostFilter, _numTotalDocs); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/DictionaryBasedDistinctOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/DictionaryBasedDistinctOperator.java index e8f1e015ca00..280fae66fd29 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/DictionaryBasedDistinctOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/DictionaryBasedDistinctOperator.java @@ -21,7 +21,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.OrderByExpressionContext; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.common.Operator; @@ -29,11 +28,12 @@ import org.apache.pinot.core.operator.BaseOperator; import org.apache.pinot.core.operator.ExecutionStatistics; import org.apache.pinot.core.operator.blocks.results.DistinctResultsBlock; -import org.apache.pinot.core.query.aggregation.function.DistinctAggregationFunction; import org.apache.pinot.core.query.distinct.DistinctTable; -import org.apache.pinot.segment.spi.AggregationFunctionType; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.segment.spi.datasource.DataSource; +import org.apache.pinot.segment.spi.datasource.DataSourceMetadata; import org.apache.pinot.segment.spi.index.reader.Dictionary; -import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.trace.Tracing; /** @@ -42,96 +42,77 @@ public class DictionaryBasedDistinctOperator extends BaseOperator { private static final String EXPLAIN_NAME = "DISTINCT_DICTIONARY"; - private final DistinctAggregationFunction _distinctAggregationFunction; - private final Dictionary _dictionary; - private final int _numTotalDocs; - private final boolean _nullHandlingEnabled; - private final FieldSpec.DataType _dataType; + private final DataSource _dataSource; + private final QueryContext _queryContext; - private boolean _hasOrderBy; - private boolean _isAscending; private int _numDocsScanned; - public DictionaryBasedDistinctOperator(FieldSpec.DataType dataType, - DistinctAggregationFunction distinctAggregationFunction, Dictionary dictionary, int numTotalDocs, - boolean nullHandlingEnabled) { - _dataType = dataType; - _distinctAggregationFunction = distinctAggregationFunction; - _dictionary = dictionary; - _numTotalDocs = numTotalDocs; - _nullHandlingEnabled = nullHandlingEnabled; - - List orderByExpressionContexts = _distinctAggregationFunction.getOrderByExpressions(); - if (orderByExpressionContexts != null) { - OrderByExpressionContext orderByExpressionContext = orderByExpressionContexts.get(0); - _isAscending = orderByExpressionContext.isAsc(); - _hasOrderBy = true; - } + public DictionaryBasedDistinctOperator(DataSource dataSource, QueryContext queryContext) { + _dataSource = dataSource; + _queryContext = queryContext; } @Override protected DistinctResultsBlock getNextBlock() { - return new DistinctResultsBlock(_distinctAggregationFunction, buildResult()); - } - - /** - * Build the final result for this operation - */ - private DistinctTable buildResult() { - - assert _distinctAggregationFunction.getType() == AggregationFunctionType.DISTINCT; - - List expressions = _distinctAggregationFunction.getInputExpressions(); - ExpressionContext expression = expressions.get(0); - DataSchema dataSchema = new DataSchema(new String[]{expression.toString()}, - new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.fromDataTypeSV(_dataType)}); - int dictLength = _dictionary.length(); - List records; - - int limit = _distinctAggregationFunction.getLimit(); - int actualLimit = Math.min(limit, dictLength); + String column = _queryContext.getSelectExpressions().get(0).getIdentifier(); + Dictionary dictionary = _dataSource.getDictionary(); + assert dictionary != null; + DataSourceMetadata dataSourceMetadata = _dataSource.getDataSourceMetadata(); + DataSchema dataSchema = new DataSchema(new String[]{column}, + new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.fromDataTypeSV(dataSourceMetadata.getDataType())}); + int limit = _queryContext.getLimit(); + int dictLength = dictionary.length(); + int numValuesToKeep = Math.min(limit, dictLength); + boolean nullHandlingEnabled = _queryContext.isNullHandlingEnabled(); // If ORDER BY is not present, we read the first limit values from the dictionary and return. // If ORDER BY is present and the dictionary is sorted, then we read the first/last limit values // from the dictionary. If not sorted, then we read the entire dictionary and return it. - if (!_hasOrderBy) { - records = new ArrayList<>(actualLimit); - - _numDocsScanned = actualLimit; - - for (int i = 0; i < actualLimit; i++) { - records.add(new Record(new Object[]{_dictionary.getInternal(i)})); - } + DistinctTable distinctTable; + List orderByExpressions = _queryContext.getOrderByExpressions(); + if (orderByExpressions == null) { + distinctTable = + new DistinctTable(dataSchema, iterateOnDictionary(dictionary, numValuesToKeep), nullHandlingEnabled); + _numDocsScanned = numValuesToKeep; } else { - if (_dictionary.isSorted()) { - records = new ArrayList<>(actualLimit); - if (_isAscending) { - _numDocsScanned = actualLimit; - for (int i = 0; i < actualLimit; i++) { - records.add(new Record(new Object[]{_dictionary.getInternal(i)})); - } + if (dictionary.isSorted()) { + if (orderByExpressions.get(0).isAsc()) { + distinctTable = + new DistinctTable(dataSchema, iterateOnDictionary(dictionary, numValuesToKeep), nullHandlingEnabled); } else { - _numDocsScanned = actualLimit; - for (int i = dictLength - 1; i >= (dictLength - actualLimit); i--) { - records.add(new Record(new Object[]{_dictionary.getInternal(i)})); - } + distinctTable = + new DistinctTable(dataSchema, iterateOnDictionaryDesc(dictionary, numValuesToKeep), nullHandlingEnabled); } + _numDocsScanned = numValuesToKeep; } else { - // DictionaryBasedDistinctOperator cannot handle nulls. - DistinctTable distinctTable = - new DistinctTable(dataSchema, _distinctAggregationFunction.getOrderByExpressions(), limit, - _nullHandlingEnabled); - - _numDocsScanned = dictLength; + distinctTable = new DistinctTable(dataSchema, orderByExpressions, limit, nullHandlingEnabled); for (int i = 0; i < dictLength; i++) { - distinctTable.addWithOrderBy(new Record(new Object[]{_dictionary.getInternal(i)})); + distinctTable.addWithOrderBy(new Record(new Object[]{dictionary.getInternal(i)})); } - - return distinctTable; + _numDocsScanned = dictLength; } } - return new DistinctTable(dataSchema, records, _nullHandlingEnabled); + return new DistinctResultsBlock(distinctTable, _queryContext); + } + + private static List iterateOnDictionary(Dictionary dictionary, int length) { + List records = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + Tracing.ThreadAccountantOps.sampleAndCheckInterruptionPeriodically(i); + records.add(new Record(new Object[]{dictionary.getInternal(i)})); + } + return records; + } + + private static List iterateOnDictionaryDesc(Dictionary dictionary, int length) { + List records = new ArrayList<>(length); + int dictLength = dictionary.length(); + for (int i = dictLength - 1, j = 0; i >= (dictLength - length); i--, j++) { + Tracing.ThreadAccountantOps.sampleAndCheckInterruptionPeriodically(j); + records.add(new Record(new Object[]{dictionary.getInternal(i)})); + } + return records; } @Override @@ -147,6 +128,7 @@ public List getChildOperators() { @Override public ExecutionStatistics getExecutionStatistics() { // NOTE: Set numDocsScanned to numTotalDocs for backward compatibility. - return new ExecutionStatistics(_numDocsScanned, 0, _numDocsScanned, _numTotalDocs); + return new ExecutionStatistics(_numDocsScanned, 0, _numDocsScanned, + _dataSource.getDataSourceMetadata().getNumDocs()); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/DistinctOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/DistinctOperator.java index 9ba1a65e86b6..8b0d8287471a 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/DistinctOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/DistinctOperator.java @@ -20,13 +20,12 @@ import java.util.Collections; import java.util.List; -import org.apache.pinot.core.common.Operator; +import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.core.operator.BaseOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; import org.apache.pinot.core.operator.ExecutionStatistics; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.blocks.results.DistinctResultsBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; -import org.apache.pinot.core.query.aggregation.function.DistinctAggregationFunction; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.core.query.distinct.DistinctExecutorFactory; import org.apache.pinot.core.query.request.context.QueryContext; @@ -40,36 +39,34 @@ public class DistinctOperator extends BaseOperator { private static final String EXPLAIN_NAME = "DISTINCT"; private final IndexSegment _indexSegment; - private final DistinctAggregationFunction _distinctAggregationFunction; - private final TransformOperator _transformOperator; - private final DistinctExecutor _distinctExecutor; + private final QueryContext _queryContext; + private final BaseProjectOperator _projectOperator; private int _numDocsScanned = 0; - public DistinctOperator(IndexSegment indexSegment, DistinctAggregationFunction distinctAggregationFunction, - TransformOperator transformOperator, QueryContext queryContext) { + public DistinctOperator(IndexSegment indexSegment, QueryContext queryContext, + BaseProjectOperator projectOperator) { _indexSegment = indexSegment; - _distinctAggregationFunction = distinctAggregationFunction; - _transformOperator = transformOperator; - _distinctExecutor = DistinctExecutorFactory.getDistinctExecutor(distinctAggregationFunction, transformOperator, - queryContext.isNullHandlingEnabled()); + _queryContext = queryContext; + _projectOperator = projectOperator; } @Override protected DistinctResultsBlock getNextBlock() { - TransformBlock transformBlock; - while ((transformBlock = _transformOperator.nextBlock()) != null) { - _numDocsScanned += transformBlock.getNumDocs(); - if (_distinctExecutor.process(transformBlock)) { + DistinctExecutor executor = DistinctExecutorFactory.getDistinctExecutor(_projectOperator, _queryContext); + ValueBlock valueBlock; + while ((valueBlock = _projectOperator.nextBlock()) != null) { + _numDocsScanned += valueBlock.getNumDocs(); + if (executor.process(valueBlock)) { break; } } - return new DistinctResultsBlock(_distinctAggregationFunction, _distinctExecutor.getResult()); + return new DistinctResultsBlock(executor.getResult(), _queryContext); } @Override - public List getChildOperators() { - return Collections.singletonList(_transformOperator); + public List> getChildOperators() { + return Collections.singletonList(_projectOperator); } @Override @@ -79,8 +76,8 @@ public IndexSegment getIndexSegment() { @Override public ExecutionStatistics getExecutionStatistics() { - long numEntriesScannedInFilter = _transformOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); - long numEntriesScannedPostFilter = (long) _numDocsScanned * _transformOperator.getNumColumnsProjected(); + long numEntriesScannedInFilter = _projectOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); + long numEntriesScannedPostFilter = (long) _numDocsScanned * _projectOperator.getNumColumnsProjected(); int numTotalDocs = _indexSegment.getSegmentMetadata().getTotalDocs(); return new ExecutionStatistics(_numDocsScanned, numEntriesScannedInFilter, numEntriesScannedPostFilter, numTotalDocs); @@ -88,13 +85,12 @@ public ExecutionStatistics getExecutionStatistics() { @Override public String toExplainString() { - String[] keys = _distinctAggregationFunction.getColumns(); + List expressions = _queryContext.getSelectExpressions(); + int numExpressions = expressions.size(); StringBuilder stringBuilder = new StringBuilder(EXPLAIN_NAME).append("(keyColumns:"); - if (keys.length > 0) { - stringBuilder.append(keys[0]); - for (int i = 1; i < keys.length; i++) { - stringBuilder.append(", ").append(keys[i]); - } + stringBuilder.append(expressions.get(0).toString()); + for (int i = 1; i < numExpressions; i++) { + stringBuilder.append(", ").append(expressions.get(i).toString()); } return stringBuilder.append(')').toString(); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/EmptySelectionOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/EmptySelectionOperator.java index f2bb79b384c5..708ea826ebff 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/EmptySelectionOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/EmptySelectionOperator.java @@ -22,12 +22,12 @@ import java.util.List; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.utils.DataSchema; -import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.BaseOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; +import org.apache.pinot.core.operator.ColumnContext; import org.apache.pinot.core.operator.ExecutionStatistics; import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; -import org.apache.pinot.core.operator.transform.TransformResultMetadata; +import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.IndexSegment; @@ -37,25 +37,27 @@ *

NOTE: this operator short circuit underlying operators and directly returns the data schema without any rows. */ public class EmptySelectionOperator extends BaseOperator { - private static final String EXPLAIN_NAME = "SELECT_EMPTY"; + private final BaseProjectOperator _projectOperator; + private final QueryContext _queryContext; private final DataSchema _dataSchema; private final ExecutionStatistics _executionStatistics; - private final TransformOperator _transformOperator; - public EmptySelectionOperator(IndexSegment indexSegment, List expressions, - TransformOperator transformOperator) { + public EmptySelectionOperator(IndexSegment indexSegment, QueryContext queryContext, + List expressions, BaseProjectOperator projectOperator) { + _projectOperator = projectOperator; + _queryContext = queryContext; + int numExpressions = expressions.size(); String[] columnNames = new String[numExpressions]; - _transformOperator = transformOperator; DataSchema.ColumnDataType[] columnDataTypes = new DataSchema.ColumnDataType[numExpressions]; for (int i = 0; i < numExpressions; i++) { ExpressionContext expression = expressions.get(i); - TransformResultMetadata expressionMetadata = _transformOperator.getResultMetadata(expression); columnNames[i] = expression.toString(); + ColumnContext columnContext = projectOperator.getResultColumnContext(expression); columnDataTypes[i] = - DataSchema.ColumnDataType.fromDataType(expressionMetadata.getDataType(), expressionMetadata.isSingleValue()); + DataSchema.ColumnDataType.fromDataType(columnContext.getDataType(), columnContext.isSingleValue()); } _dataSchema = new DataSchema(columnNames, columnDataTypes); @@ -64,7 +66,7 @@ public EmptySelectionOperator(IndexSegment indexSegment, List @Override protected SelectionResultsBlock getNextBlock() { - return new SelectionResultsBlock(_dataSchema, Collections.emptyList()); + return new SelectionResultsBlock(_dataSchema, Collections.emptyList(), _queryContext); } @Override @@ -73,8 +75,8 @@ public String toExplainString() { } @Override - public List getChildOperators() { - return Collections.singletonList(_transformOperator); + public List> getChildOperators() { + return Collections.singletonList(_projectOperator); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/FastFilteredCountOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/FastFilteredCountOperator.java index 71877e1372b2..1806aa595989 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/FastFilteredCountOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/FastFilteredCountOperator.java @@ -27,6 +27,7 @@ import org.apache.pinot.core.operator.blocks.results.AggregationResultsBlock; import org.apache.pinot.core.operator.filter.BaseFilterOperator; import org.apache.pinot.core.query.aggregation.function.AggregationFunction; +import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.SegmentMetadata; @@ -35,17 +36,19 @@ public class FastFilteredCountOperator extends BaseOperator aggregates = new ArrayList<>(1); aggregates.add(count); _docsCounted += count; - return new AggregationResultsBlock(_aggregationFunctions, aggregates); + return new AggregationResultsBlock(_aggregationFunctions, aggregates, _queryContext); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/FilteredAggregationOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/FilteredAggregationOperator.java index f478f36d921b..ed68c54ab198 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/FilteredAggregationOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/FilteredAggregationOperator.java @@ -22,16 +22,18 @@ import java.util.IdentityHashMap; import java.util.List; import java.util.stream.Collectors; -import org.apache.commons.lang3.tuple.Pair; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.BaseOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; import org.apache.pinot.core.operator.ExecutionStatistics; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.blocks.results.AggregationResultsBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; import org.apache.pinot.core.query.aggregation.AggregationExecutor; import org.apache.pinot.core.query.aggregation.DefaultAggregationExecutor; import org.apache.pinot.core.query.aggregation.function.AggregationFunction; +import org.apache.pinot.core.query.aggregation.function.AggregationFunctionUtils.AggregationInfo; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.startree.executor.StarTreeAggregationExecutor; /** @@ -44,20 +46,20 @@ public class FilteredAggregationOperator extends BaseOperator { private static final String EXPLAIN_NAME = "AGGREGATE_FILTERED"; + private final QueryContext _queryContext; private final AggregationFunction[] _aggregationFunctions; - private final List> _aggFunctionsWithTransformOperator; + private final List _aggregationInfos; private final long _numTotalDocs; private long _numDocsScanned; private long _numEntriesScannedInFilter; private long _numEntriesScannedPostFilter; - // We can potentially do away with aggregationFunctions parameter, but its cleaner to pass it in than to construct - // it from aggFunctionsWithTransformOperator - public FilteredAggregationOperator(AggregationFunction[] aggregationFunctions, - List> aggFunctionsWithTransformOperator, long numTotalDocs) { - _aggregationFunctions = aggregationFunctions; - _aggFunctionsWithTransformOperator = aggFunctionsWithTransformOperator; + public FilteredAggregationOperator(QueryContext queryContext, List aggregationInfos, + long numTotalDocs) { + _queryContext = queryContext; + _aggregationFunctions = queryContext.getAggregationFunctions(); + _aggregationInfos = aggregationInfos; _numTotalDocs = numTotalDocs; } @@ -70,15 +72,21 @@ protected AggregationResultsBlock getNextBlock() { resultIndexMap.put(_aggregationFunctions[i], i); } - for (Pair filteredAggregation : _aggFunctionsWithTransformOperator) { - AggregationFunction[] aggregationFunctions = filteredAggregation.getLeft(); - AggregationExecutor aggregationExecutor = new DefaultAggregationExecutor(aggregationFunctions); - TransformOperator transformOperator = filteredAggregation.getRight(); - TransformBlock transformBlock; + for (AggregationInfo aggregationInfo : _aggregationInfos) { + AggregationFunction[] aggregationFunctions = aggregationInfo.getFunctions(); + BaseProjectOperator projectOperator = aggregationInfo.getProjectOperator(); + AggregationExecutor aggregationExecutor; + if (aggregationInfo.isUseStarTree()) { + aggregationExecutor = new StarTreeAggregationExecutor(aggregationFunctions); + } else { + aggregationExecutor = new DefaultAggregationExecutor(aggregationFunctions); + } + + ValueBlock valueBlock; int numDocsScanned = 0; - while ((transformBlock = transformOperator.nextBlock()) != null) { - aggregationExecutor.aggregate(transformBlock); - numDocsScanned += transformBlock.getNumDocs(); + while ((valueBlock = projectOperator.nextBlock()) != null) { + aggregationExecutor.aggregate(valueBlock); + numDocsScanned += valueBlock.getNumDocs(); } List filteredResult = aggregationExecutor.getResult(); @@ -86,15 +94,15 @@ protected AggregationResultsBlock getNextBlock() { result[resultIndexMap.get(aggregationFunctions[i])] = filteredResult.get(i); } _numDocsScanned += numDocsScanned; - _numEntriesScannedInFilter += transformOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); - _numEntriesScannedPostFilter += (long) numDocsScanned * transformOperator.getNumColumnsProjected(); + _numEntriesScannedInFilter += projectOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); + _numEntriesScannedPostFilter += (long) numDocsScanned * projectOperator.getNumColumnsProjected(); } - return new AggregationResultsBlock(_aggregationFunctions, Arrays.asList(result)); + return new AggregationResultsBlock(_aggregationFunctions, Arrays.asList(result), _queryContext); } @Override public List getChildOperators() { - return _aggFunctionsWithTransformOperator.stream().map(Pair::getRight).collect(Collectors.toList()); + return _aggregationInfos.stream().map(AggregationInfo::getProjectOperator).collect(Collectors.toList()); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/FilteredGroupByOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/FilteredGroupByOperator.java new file mode 100644 index 000000000000..0a8c20bc939d --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/FilteredGroupByOperator.java @@ -0,0 +1,233 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.query; + +import java.util.Collection; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.request.context.FilterContext; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.common.Operator; +import org.apache.pinot.core.data.table.IntermediateRecord; +import org.apache.pinot.core.data.table.TableResizer; +import org.apache.pinot.core.operator.BaseOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; +import org.apache.pinot.core.operator.ExecutionStatistics; +import org.apache.pinot.core.operator.blocks.ValueBlock; +import org.apache.pinot.core.operator.blocks.results.GroupByResultsBlock; +import org.apache.pinot.core.query.aggregation.function.AggregationFunction; +import org.apache.pinot.core.query.aggregation.function.AggregationFunctionUtils; +import org.apache.pinot.core.query.aggregation.function.AggregationFunctionUtils.AggregationInfo; +import org.apache.pinot.core.query.aggregation.groupby.AggregationGroupByResult; +import org.apache.pinot.core.query.aggregation.groupby.DefaultGroupByExecutor; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupKeyGenerator; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.startree.executor.StarTreeGroupByExecutor; +import org.apache.pinot.core.util.GroupByUtils; +import org.apache.pinot.spi.trace.Tracing; + + +/** + * The FilteredGroupByOperator class provides the operator for group-by query on a single segment when + * there are 1 or more filter expressions on aggregations. + */ +@SuppressWarnings("rawtypes") +public class FilteredGroupByOperator extends BaseOperator { + private static final String EXPLAIN_NAME = "GROUP_BY_FILTERED"; + + private final QueryContext _queryContext; + private final AggregationFunction[] _aggregationFunctions; + private final ExpressionContext[] _groupByExpressions; + private final List _aggregationInfos; + private final long _numTotalDocs; + private final DataSchema _dataSchema; + + private long _numDocsScanned; + private long _numEntriesScannedInFilter; + private long _numEntriesScannedPostFilter; + + public FilteredGroupByOperator(QueryContext queryContext, List aggregationInfos, long numTotalDocs) { + assert queryContext.getAggregationFunctions() != null && queryContext.getFilteredAggregationFunctions() != null + && queryContext.getGroupByExpressions() != null; + _queryContext = queryContext; + _aggregationFunctions = queryContext.getAggregationFunctions(); + _groupByExpressions = queryContext.getGroupByExpressions().toArray(new ExpressionContext[0]); + _aggregationInfos = aggregationInfos; + _numTotalDocs = numTotalDocs; + + // NOTE: The indexedTable expects that the data schema will have group by columns before aggregation columns + int numGroupByExpressions = _groupByExpressions.length; + int numAggregationFunctions = _aggregationFunctions.length; + int numColumns = numGroupByExpressions + numAggregationFunctions; + String[] columnNames = new String[numColumns]; + DataSchema.ColumnDataType[] columnDataTypes = new DataSchema.ColumnDataType[numColumns]; + + // Extract column names and data types for group-by columns + BaseProjectOperator projectOperator = aggregationInfos.get(0).getProjectOperator(); + for (int i = 0; i < numGroupByExpressions; i++) { + ExpressionContext groupByExpression = _groupByExpressions[i]; + columnNames[i] = groupByExpression.toString(); + columnDataTypes[i] = DataSchema.ColumnDataType.fromDataTypeSV( + projectOperator.getResultColumnContext(groupByExpression).getDataType()); + } + + // Extract column names and data types for aggregation functions + for (int i = 0; i < numAggregationFunctions; i++) { + int index = numGroupByExpressions + i; + Pair pair = queryContext.getFilteredAggregationFunctions().get(i); + AggregationFunction aggregationFunction = pair.getLeft(); + String columnName = AggregationFunctionUtils.getResultColumnName(aggregationFunction, pair.getRight()); + columnNames[index] = columnName; + columnDataTypes[index] = aggregationFunction.getIntermediateResultColumnType(); + } + + _dataSchema = new DataSchema(columnNames, columnDataTypes); + } + + @Override + protected GroupByResultsBlock getNextBlock() { + int numAggregations = _aggregationFunctions.length; + GroupByResultHolder[] groupByResultHolders = new GroupByResultHolder[numAggregations]; + IdentityHashMap resultHolderIndexMap = + new IdentityHashMap<>(_aggregationFunctions.length); + for (int i = 0; i < numAggregations; i++) { + resultHolderIndexMap.put(_aggregationFunctions[i], i); + } + + GroupKeyGenerator groupKeyGenerator = null; + for (AggregationInfo aggregationInfo : _aggregationInfos) { + AggregationFunction[] aggregationFunctions = aggregationInfo.getFunctions(); + BaseProjectOperator projectOperator = aggregationInfo.getProjectOperator(); + + // Perform aggregation group-by on all the blocks + DefaultGroupByExecutor groupByExecutor; + if (groupKeyGenerator == null) { + // The group key generator should be shared across all AggregationFunctions so that agg results can be + // aligned. Given that filtered aggregations are stored as an iterable of iterables so that all filtered aggs + // with the same filter can share transform blocks, rather than a singular flat iterable in the case where + // aggs are all non-filtered, sharing a GroupKeyGenerator across all aggs cannot be accomplished by allowing + // the GroupByExecutor to have sole ownership of the GroupKeyGenerator. Therefore, we allow constructing a + // GroupByExecutor with a pre-existing GroupKeyGenerator so that the GroupKeyGenerator can be shared across + // loop iterations i.e. across all aggs. + if (aggregationInfo.isUseStarTree()) { + groupByExecutor = + new StarTreeGroupByExecutor(_queryContext, aggregationFunctions, _groupByExpressions, projectOperator); + } else { + groupByExecutor = + new DefaultGroupByExecutor(_queryContext, aggregationFunctions, _groupByExpressions, projectOperator); + } + groupKeyGenerator = groupByExecutor.getGroupKeyGenerator(); + } else { + if (aggregationInfo.isUseStarTree()) { + groupByExecutor = + new StarTreeGroupByExecutor(_queryContext, aggregationFunctions, _groupByExpressions, projectOperator, + groupKeyGenerator); + } else { + groupByExecutor = + new DefaultGroupByExecutor(_queryContext, aggregationFunctions, _groupByExpressions, projectOperator, + groupKeyGenerator); + } + } + + int numDocsScanned = 0; + ValueBlock valueBlock; + while ((valueBlock = projectOperator.nextBlock()) != null) { + numDocsScanned += valueBlock.getNumDocs(); + groupByExecutor.process(valueBlock); + } + + _numDocsScanned += numDocsScanned; + _numEntriesScannedInFilter += projectOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); + _numEntriesScannedPostFilter += (long) numDocsScanned * projectOperator.getNumColumnsProjected(); + GroupByResultHolder[] filterGroupByResults = groupByExecutor.getGroupByResultHolders(); + for (int i = 0; i < aggregationFunctions.length; i++) { + groupByResultHolders[resultHolderIndexMap.get(aggregationFunctions[i])] = filterGroupByResults[i]; + } + } + assert groupKeyGenerator != null; + for (GroupByResultHolder groupByResultHolder : groupByResultHolders) { + groupByResultHolder.ensureCapacity(groupKeyGenerator.getNumKeys()); + } + + // Check if the groups limit is reached + boolean numGroupsLimitReached = groupKeyGenerator.getNumKeys() >= _queryContext.getNumGroupsLimit(); + Tracing.activeRecording().setNumGroups(_queryContext.getNumGroupsLimit(), groupKeyGenerator.getNumKeys()); + + // Trim the groups when iff: + // - Query has ORDER BY clause + // - Segment group trim is enabled + // - There are more groups than the trim size + // TODO: Currently the groups are not trimmed if there is no ordering specified. Consider ordering on group-by + // columns if no ordering is specified. + int minGroupTrimSize = _queryContext.getMinSegmentGroupTrimSize(); + if (_queryContext.getOrderByExpressions() != null && minGroupTrimSize > 0) { + int trimSize = GroupByUtils.getTableCapacity(_queryContext.getLimit(), minGroupTrimSize); + if (groupKeyGenerator.getNumKeys() > trimSize) { + TableResizer tableResizer = new TableResizer(_dataSchema, _queryContext); + Collection intermediateRecords = + tableResizer.trimInSegmentResults(groupKeyGenerator, groupByResultHolders, trimSize); + GroupByResultsBlock resultsBlock = new GroupByResultsBlock(_dataSchema, intermediateRecords, _queryContext); + resultsBlock.setNumGroupsLimitReached(numGroupsLimitReached); + return resultsBlock; + } + } + + AggregationGroupByResult aggGroupByResult = + new AggregationGroupByResult(groupKeyGenerator, _aggregationFunctions, groupByResultHolders); + GroupByResultsBlock resultsBlock = new GroupByResultsBlock(_dataSchema, aggGroupByResult, _queryContext); + resultsBlock.setNumGroupsLimitReached(numGroupsLimitReached); + return resultsBlock; + } + + @Override + public List getChildOperators() { + return _aggregationInfos.stream().map(AggregationInfo::getProjectOperator).collect(Collectors.toList()); + } + + @Override + public ExecutionStatistics getExecutionStatistics() { + return new ExecutionStatistics(_numDocsScanned, _numEntriesScannedInFilter, _numEntriesScannedPostFilter, + _numTotalDocs); + } + + @Override + public String toExplainString() { + StringBuilder stringBuilder = new StringBuilder(EXPLAIN_NAME).append("(groupKeys:"); + if (_groupByExpressions.length > 0) { + stringBuilder.append(_groupByExpressions[0].toString()); + for (int i = 1; i < _groupByExpressions.length; i++) { + stringBuilder.append(", ").append(_groupByExpressions[i].toString()); + } + } + + stringBuilder.append(", aggregations:"); + if (_aggregationFunctions.length > 0) { + stringBuilder.append(_aggregationFunctions[0].toExplainString()); + for (int i = 1; i < _aggregationFunctions.length; i++) { + stringBuilder.append(", ").append(_aggregationFunctions[i].toExplainString()); + } + } + + return stringBuilder.append(')').toString(); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/GroupByOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/GroupByOperator.java index 463cc60d4410..eaae20e7e3c3 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/GroupByOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/GroupByOperator.java @@ -27,11 +27,12 @@ import org.apache.pinot.core.data.table.IntermediateRecord; import org.apache.pinot.core.data.table.TableResizer; import org.apache.pinot.core.operator.BaseOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; import org.apache.pinot.core.operator.ExecutionStatistics; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.blocks.results.GroupByResultsBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; import org.apache.pinot.core.query.aggregation.function.AggregationFunction; +import org.apache.pinot.core.query.aggregation.function.AggregationFunctionUtils.AggregationInfo; import org.apache.pinot.core.query.aggregation.groupby.DefaultGroupByExecutor; import org.apache.pinot.core.query.aggregation.groupby.GroupByExecutor; import org.apache.pinot.core.query.request.context.QueryContext; @@ -47,43 +48,43 @@ public class GroupByOperator extends BaseOperator { private static final String EXPLAIN_NAME = "GROUP_BY"; + private final QueryContext _queryContext; private final AggregationFunction[] _aggregationFunctions; private final ExpressionContext[] _groupByExpressions; - private final TransformOperator _transformOperator; - private final long _numTotalDocs; + private final BaseProjectOperator _projectOperator; private final boolean _useStarTree; + private final long _numTotalDocs; private final DataSchema _dataSchema; - private final QueryContext _queryContext; private int _numDocsScanned = 0; - public GroupByOperator(AggregationFunction[] aggregationFunctions, ExpressionContext[] groupByExpressions, - TransformOperator transformOperator, long numTotalDocs, QueryContext queryContext, boolean useStarTree) { - _aggregationFunctions = aggregationFunctions; - _groupByExpressions = groupByExpressions; - _transformOperator = transformOperator; - _numTotalDocs = numTotalDocs; - _useStarTree = useStarTree; + public GroupByOperator(QueryContext queryContext, AggregationInfo aggregationInfo, long numTotalDocs) { + assert queryContext.getAggregationFunctions() != null && queryContext.getGroupByExpressions() != null; _queryContext = queryContext; + _aggregationFunctions = queryContext.getAggregationFunctions(); + _groupByExpressions = queryContext.getGroupByExpressions().toArray(new ExpressionContext[0]); + _projectOperator = aggregationInfo.getProjectOperator(); + _useStarTree = aggregationInfo.isUseStarTree(); + _numTotalDocs = numTotalDocs; - // NOTE: The indexedTable expects that the the data schema will have group by columns before aggregation columns - int numGroupByExpressions = groupByExpressions.length; - int numAggregationFunctions = aggregationFunctions.length; + // NOTE: The indexedTable expects that the data schema will have group by columns before aggregation columns + int numGroupByExpressions = _groupByExpressions.length; + int numAggregationFunctions = _aggregationFunctions.length; int numColumns = numGroupByExpressions + numAggregationFunctions; String[] columnNames = new String[numColumns]; DataSchema.ColumnDataType[] columnDataTypes = new DataSchema.ColumnDataType[numColumns]; // Extract column names and data types for group-by columns for (int i = 0; i < numGroupByExpressions; i++) { - ExpressionContext groupByExpression = groupByExpressions[i]; + ExpressionContext groupByExpression = _groupByExpressions[i]; columnNames[i] = groupByExpression.toString(); columnDataTypes[i] = DataSchema.ColumnDataType.fromDataTypeSV( - _transformOperator.getResultMetadata(groupByExpression).getDataType()); + _projectOperator.getResultColumnContext(groupByExpression).getDataType()); } // Extract column names and data types for aggregation functions for (int i = 0; i < numAggregationFunctions; i++) { - AggregationFunction aggregationFunction = aggregationFunctions[i]; + AggregationFunction aggregationFunction = _aggregationFunctions[i]; int index = numGroupByExpressions + i; columnNames[index] = aggregationFunction.getResultColumnName(); columnDataTypes[index] = aggregationFunction.getIntermediateResultColumnType(); @@ -97,14 +98,14 @@ protected GroupByResultsBlock getNextBlock() { // Perform aggregation group-by on all the blocks GroupByExecutor groupByExecutor; if (_useStarTree) { - groupByExecutor = new StarTreeGroupByExecutor(_queryContext, _groupByExpressions, _transformOperator); + groupByExecutor = new StarTreeGroupByExecutor(_queryContext, _groupByExpressions, _projectOperator); } else { - groupByExecutor = new DefaultGroupByExecutor(_queryContext, _groupByExpressions, _transformOperator); + groupByExecutor = new DefaultGroupByExecutor(_queryContext, _groupByExpressions, _projectOperator); } - TransformBlock transformBlock; - while ((transformBlock = _transformOperator.nextBlock()) != null) { - _numDocsScanned += transformBlock.getNumDocs(); - groupByExecutor.process(transformBlock); + ValueBlock valueBlock; + while ((valueBlock = _projectOperator.nextBlock()) != null) { + _numDocsScanned += valueBlock.getNumDocs(); + groupByExecutor.process(valueBlock); } // Check if the groups limit is reached @@ -123,26 +124,26 @@ protected GroupByResultsBlock getNextBlock() { if (groupByExecutor.getNumGroups() > trimSize) { TableResizer tableResizer = new TableResizer(_dataSchema, _queryContext); Collection intermediateRecords = groupByExecutor.trimGroupByResult(trimSize, tableResizer); - GroupByResultsBlock resultsBlock = new GroupByResultsBlock(_dataSchema, intermediateRecords); + GroupByResultsBlock resultsBlock = new GroupByResultsBlock(_dataSchema, intermediateRecords, _queryContext); resultsBlock.setNumGroupsLimitReached(numGroupsLimitReached); return resultsBlock; } } - GroupByResultsBlock resultsBlock = new GroupByResultsBlock(_dataSchema, groupByExecutor.getResult()); + GroupByResultsBlock resultsBlock = new GroupByResultsBlock(_dataSchema, groupByExecutor.getResult(), _queryContext); resultsBlock.setNumGroupsLimitReached(numGroupsLimitReached); return resultsBlock; } @Override public List getChildOperators() { - return Collections.singletonList(_transformOperator); + return Collections.singletonList(_projectOperator); } @Override public ExecutionStatistics getExecutionStatistics() { - long numEntriesScannedInFilter = _transformOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); - long numEntriesScannedPostFilter = (long) _numDocsScanned * _transformOperator.getNumColumnsProjected(); + long numEntriesScannedInFilter = _projectOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); + long numEntriesScannedPostFilter = (long) _numDocsScanned * _projectOperator.getNumColumnsProjected(); return new ExecutionStatistics(_numDocsScanned, numEntriesScannedInFilter, numEntriesScannedPostFilter, _numTotalDocs); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/LinearSelectionOrderByOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/LinearSelectionOrderByOperator.java index e9fe801262b7..6caf1d98cded 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/LinearSelectionOrderByOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/LinearSelectionOrderByOperator.java @@ -32,14 +32,13 @@ import org.apache.pinot.common.request.context.OrderByExpressionContext; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.common.RowBasedBlockValueFetcher; import org.apache.pinot.core.operator.BaseOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; +import org.apache.pinot.core.operator.ColumnContext; import org.apache.pinot.core.operator.ExecutionStatistics; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; -import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.selection.SelectionOperatorUtils; import org.apache.pinot.core.query.utils.OrderByComparatorFactory; @@ -63,35 +62,31 @@ */ public abstract class LinearSelectionOrderByOperator extends BaseOperator { protected final IndexSegment _indexSegment; - + protected final QueryContext _queryContext; protected final boolean _nullHandlingEnabled; // Deduped order-by expressions followed by output expressions from SelectionOperatorUtils.extractExpressions() protected final List _expressions; protected final List _alreadySorted; protected final List _toSort; - protected final TransformOperator _transformOperator; + protected final BaseProjectOperator _projectOperator; protected final List _orderByExpressions; - protected final TransformResultMetadata[] _expressionsMetadata; + protected final ColumnContext[] _columnContexts; protected final int _numRowsToKeep; - private final Supplier _listBuilderSupplier; - protected boolean _used = false; - /** - * The comparator used to build the resulting {@link SelectionResultsBlock}, which sorts rows in reverse order to the - * one specified in the query. - */ - protected Comparator _comparator; + protected final Comparator _comparator; + protected final Supplier _listBuilderSupplier; /** - * @param expressions Order-by expressions must be at the head of the list. + * @param expressions Order-by expressions must be at the head of the list. * @param numSortedExpressions Number of expressions in the order-by expressions that are sorted. */ public LinearSelectionOrderByOperator(IndexSegment indexSegment, QueryContext queryContext, - List expressions, TransformOperator transformOperator, int numSortedExpressions) { + List expressions, BaseProjectOperator projectOperator, int numSortedExpressions) { _indexSegment = indexSegment; + _queryContext = queryContext; _nullHandlingEnabled = queryContext.isNullHandlingEnabled(); _expressions = expressions; - _transformOperator = transformOperator; + _projectOperator = projectOperator; _orderByExpressions = queryContext.getOrderByExpressions(); assert _orderByExpressions != null; @@ -100,28 +95,26 @@ public LinearSelectionOrderByOperator(IndexSegment indexSegment, QueryContext qu _alreadySorted = expressions.subList(0, numSortedExpressions); _toSort = expressions.subList(numSortedExpressions, numOrderByExpressions); - _expressionsMetadata = new TransformResultMetadata[_expressions.size()]; - for (int i = 0; i < _expressionsMetadata.length; i++) { + _columnContexts = new ColumnContext[_expressions.size()]; + for (int i = 0; i < _columnContexts.length; i++) { ExpressionContext expression = _expressions.get(i); - _expressionsMetadata[i] = _transformOperator.getResultMetadata(expression); + _columnContexts[i] = _projectOperator.getResultColumnContext(expression); } _numRowsToKeep = queryContext.getOffset() + queryContext.getLimit(); + _comparator = OrderByComparatorFactory.getComparator(_orderByExpressions, _columnContexts, _nullHandlingEnabled); if (_toSort.isEmpty()) { _listBuilderSupplier = () -> new TotallySortedListBuilder(_numRowsToKeep); } else { Comparator sortedComparator = - OrderByComparatorFactory.getComparator(_orderByExpressions, _expressionsMetadata, false, _nullHandlingEnabled, - 0, numSortedExpressions); + OrderByComparatorFactory.getComparator(_orderByExpressions, _columnContexts, _nullHandlingEnabled, 0, + numSortedExpressions); Comparator unsortedComparator = - OrderByComparatorFactory.getComparator(_orderByExpressions, _expressionsMetadata, true, _nullHandlingEnabled, + OrderByComparatorFactory.getComparator(_orderByExpressions, _columnContexts, _nullHandlingEnabled, numSortedExpressions, numOrderByExpressions); _listBuilderSupplier = () -> new PartiallySortedListBuilder(_numRowsToKeep, sortedComparator, unsortedComparator); } - - _comparator = - OrderByComparatorFactory.getComparator(_orderByExpressions, _expressionsMetadata, true, _nullHandlingEnabled); } @Override @@ -131,20 +124,20 @@ public IndexSegment getIndexSegment() { @Override public ExecutionStatistics getExecutionStatistics() { - long numEntriesScannedInFilter = _transformOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); + long numEntriesScannedInFilter = _projectOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); int numDocsScanned = getNumDocsScanned(); - long numEntriesScannedPostFilter = (long) numDocsScanned * _transformOperator.getNumColumnsProjected(); + long numEntriesScannedPostFilter = (long) numDocsScanned * _projectOperator.getNumColumnsProjected(); int numTotalDocs = _indexSegment.getSegmentMetadata().getTotalDocs(); return new ExecutionStatistics(numDocsScanned, numEntriesScannedInFilter, numEntriesScannedPostFilter, numTotalDocs); } - protected IntFunction fetchBlock(TransformBlock transformBlock, BlockValSet[] blockValSets) { + protected IntFunction fetchBlock(ValueBlock valueBlock, BlockValSet[] blockValSets) { int numExpressions = _expressions.size(); for (int i = 0; i < numExpressions; i++) { ExpressionContext expression = _expressions.get(i); - blockValSets[i] = transformBlock.getBlockValueSet(expression); + blockValSets[i] = valueBlock.getBlockValueSet(expression); } RowBasedBlockValueFetcher blockValueFetcher = new RowBasedBlockValueFetcher(blockValSets); @@ -184,8 +177,8 @@ protected IntFunction fetchBlock(TransformBlock transformBlock, BlockV protected abstract List fetch(Supplier listBuilderSupplier); @Override - public List getChildOperators() { - return Collections.singletonList(_transformOperator); + public List> getChildOperators() { + return Collections.singletonList(_projectOperator); } protected abstract String getExplainName(); @@ -221,17 +214,7 @@ private void concatList(StringBuilder sb, List list) { @Override protected SelectionResultsBlock getNextBlock() { - Preconditions.checkState(!_used, "nextBlock was called more than once"); - _used = true; - List list = fetch(_listBuilderSupplier); - - DataSchema dataSchema = createDataSchema(); - - if (list.size() > _numRowsToKeep) { - list = new ArrayList<>(list.subList(0, _numRowsToKeep)); - } - - return new SelectionResultsBlock(dataSchema, list, _comparator); + return new SelectionResultsBlock(createDataSchema(), fetch(_listBuilderSupplier), _comparator, _queryContext); } protected DataSchema createDataSchema() { @@ -244,9 +227,8 @@ protected DataSchema createDataSchema() { columnNames[i] = _expressions.get(i).toString(); } for (int i = 0; i < numExpressions; i++) { - TransformResultMetadata expressionMetadata = _expressionsMetadata[i]; columnDataTypes[i] = - DataSchema.ColumnDataType.fromDataType(expressionMetadata.getDataType(), expressionMetadata.isSingleValue()); + DataSchema.ColumnDataType.fromDataType(_columnContexts[i].getDataType(), _columnContexts[i].isSingleValue()); } return new DataSchema(columnNames, columnDataTypes); } @@ -335,25 +317,28 @@ public List build() { */ @VisibleForTesting static class PartiallySortedListBuilder implements ListBuilder { - /** - * A list with all the elements that have been already sorted. - */ - private final ArrayList _sorted; - /** - * This attribute is used to store the last partition when the builder already contains {@link #_maxNumRows} rows. - */ - private PriorityQueue _lastPartitionQueue; + + private final int _maxNumRows; + /** * The comparator that defines the partitions and the one that impose in which order add has to be called. */ private final Comparator _partitionComparator; + /** - * The comparator that sorts different rows on each partition, which sorts rows in reverse order to the one - * specified in the query. + * The comparator that sorts different rows on each partition. */ private final Comparator _unsortedComparator; - private final int _maxNumRows; + /** + * List of rows, where the first _numSortedRows are sorted. + */ + private final ArrayList _rows; + + /** + * This attribute is used to store the last partition when the builder already contains {@link #_maxNumRows} rows. + */ + private PriorityQueue _lastPartitionQueue; private Object[] _lastPartitionRow; private int _numSortedRows; @@ -361,50 +346,52 @@ static class PartiallySortedListBuilder implements ListBuilder { public PartiallySortedListBuilder(int maxNumRows, Comparator partitionComparator, Comparator unsortedComparator) { _maxNumRows = maxNumRows; - _sorted = new ArrayList<>(Integer.min(maxNumRows, SelectionOperatorUtils.MAX_ROW_HOLDER_INITIAL_CAPACITY)); _partitionComparator = partitionComparator; _unsortedComparator = unsortedComparator; + _rows = new ArrayList<>(Integer.min(maxNumRows, SelectionOperatorUtils.MAX_ROW_HOLDER_INITIAL_CAPACITY)); } @Override public boolean add(Object[] row) { if (_lastPartitionRow == null) { _lastPartitionRow = row; - _sorted.add(row); + _rows.add(row); return false; } - int cmp = _partitionComparator.compare(row, _lastPartitionRow); - if (cmp < 0) { - throw new IllegalArgumentException( - "Row with docId " + _sorted.size() + " is not sorted compared to the previous one"); - } + int compareResult = _partitionComparator.compare(row, _lastPartitionRow); + Preconditions.checkState(compareResult >= 0, "Rows are not sorted"); - boolean newPartition = cmp > 0; - if (_sorted.size() < _maxNumRows) { + boolean newPartition = compareResult > 0; + int numRows = _rows.size(); + if (numRows < _maxNumRows) { // we don't have enough rows yet if (newPartition) { _lastPartitionRow = row; - _numSortedRows = _sorted.size(); + if (numRows - _numSortedRows > 1) { + _rows.subList(_numSortedRows, numRows).sort(_unsortedComparator); + } + _numSortedRows = numRows; } // just add the new row to the result list - _sorted.add(row); + _rows.add(row); return false; } // enough rows have been collected - assert _sorted.size() == _maxNumRows; - if (newPartition) { // and the new element belongs to a new partition, so we can just ignore it + assert numRows == _maxNumRows; + if (newPartition) { + // new element belongs to a new partition, so we can just ignore it return true; } // new element doesn't belong to a new partition, so we may need to add it - if (_lastPartitionQueue == null) { // we have exactly _numRows rows, and the new belongs to the last partition + if (_lastPartitionQueue == null) { // we need to prepare the priority queue - int numRowsInPriorityQueue = _maxNumRows - _numSortedRows; - _lastPartitionQueue = new PriorityQueue<>(numRowsInPriorityQueue, _unsortedComparator); - _lastPartitionQueue.addAll(_sorted.subList(_numSortedRows, _maxNumRows)); + int numRowsInPriorityQueue = numRows - _numSortedRows; + _lastPartitionQueue = new PriorityQueue<>(numRowsInPriorityQueue, _unsortedComparator.reversed()); + _lastPartitionQueue.addAll(_rows.subList(_numSortedRows, numRows)); } // add the new element if it is lower than the greatest element stored in the partition - if (_unsortedComparator.compare(row, _lastPartitionQueue.peek()) > 0) { + if (_unsortedComparator.compare(row, _lastPartitionQueue.peek()) < 0) { _lastPartitionQueue.poll(); _lastPartitionQueue.offer(row); } @@ -413,14 +400,18 @@ public boolean add(Object[] row) { @Override public List build() { - if (_lastPartitionQueue != null) { - assert _lastPartitionQueue.size() == _maxNumRows - _numSortedRows; - Iterator lastPartitionIt = _lastPartitionQueue.iterator(); - for (int i = _numSortedRows; i < _maxNumRows; i++) { - _sorted.set(i, lastPartitionIt.next()); + int numRows = _rows.size(); + if (_lastPartitionQueue == null) { + if (numRows - _numSortedRows > 1) { + _rows.subList(_numSortedRows, numRows).sort(_unsortedComparator); + } + } else { + assert numRows == _maxNumRows && _lastPartitionQueue.size() == numRows - _numSortedRows; + for (int i = numRows - 1; i >= _numSortedRows; i--) { + _rows.set(i, _lastPartitionQueue.poll()); } } - return _sorted; + return _rows; } } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/NonScanBasedAggregationOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/NonScanBasedAggregationOperator.java index 12db19543536..fb6e8d02f6ba 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/NonScanBasedAggregationOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/NonScanBasedAggregationOperator.java @@ -19,6 +19,7 @@ package org.apache.pinot.core.operator.query; import com.clearspring.analytics.stream.cardinality.HyperLogLog; +import com.clearspring.analytics.stream.cardinality.HyperLogLogPlus; import it.unimi.dsi.fastutil.doubles.DoubleOpenHashSet; import it.unimi.dsi.fastutil.floats.FloatOpenHashSet; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; @@ -36,8 +37,11 @@ import org.apache.pinot.core.operator.blocks.results.AggregationResultsBlock; import org.apache.pinot.core.query.aggregation.function.AggregationFunction; import org.apache.pinot.core.query.aggregation.function.DistinctCountHLLAggregationFunction; +import org.apache.pinot.core.query.aggregation.function.DistinctCountHLLPlusAggregationFunction; import org.apache.pinot.core.query.aggregation.function.DistinctCountRawHLLAggregationFunction; +import org.apache.pinot.core.query.aggregation.function.DistinctCountRawHLLPlusAggregationFunction; import org.apache.pinot.core.query.aggregation.function.DistinctCountSmartHLLAggregationFunction; +import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.local.customobject.MinMaxRangePair; import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.segment.spi.index.reader.Dictionary; @@ -49,8 +53,8 @@ * Aggregation operator that utilizes dictionary or column metadata for serving aggregation queries to avoid scanning. * The scanless operator is selected in the plan maker, if the query is of aggregation type min, max, minmaxrange, * distinctcount, distinctcounthll, distinctcountrawhll, segmentpartitioneddistinctcount, distinctcountsmarthll, - * and the column has a dictionary, or has column metadata with min and max value defined. It also supports count(*) if - * the query has no filter. + * distinctcounthllplus, distinctcountrawhllplus, and the column has a dictionary, or has column metadata with min and + * max value defined. It also supports count(*) if the query has no filter. * We don't use this operator if the segment has star tree, * as the dictionary will have aggregated values for the metrics, and dimensions will have star node value. * @@ -61,16 +65,16 @@ */ @SuppressWarnings("rawtypes") public class NonScanBasedAggregationOperator extends BaseOperator { - private static final String EXPLAIN_NAME = "AGGREGATE_NO_SCAN"; + private final QueryContext _queryContext; private final AggregationFunction[] _aggregationFunctions; private final DataSource[] _dataSources; private final int _numTotalDocs; - public NonScanBasedAggregationOperator(AggregationFunction[] aggregationFunctions, DataSource[] dataSources, - int numTotalDocs) { - _aggregationFunctions = aggregationFunctions; + public NonScanBasedAggregationOperator(QueryContext queryContext, DataSource[] dataSources, int numTotalDocs) { + _queryContext = queryContext; + _aggregationFunctions = queryContext.getAggregationFunctions(); _dataSources = dataSources; _numTotalDocs = numTotalDocs; } @@ -100,7 +104,11 @@ protected AggregationResultsBlock getNextBlock() { result = new MinMaxRangePair(getMinValue(dataSource), getMaxValue(dataSource)); break; case DISTINCTCOUNT: + case DISTINCTSUM: + case DISTINCTAVG: case DISTINCTCOUNTMV: + case DISTINCTSUMMV: + case DISTINCTAVGMV: result = getDistinctValueSet(Objects.requireNonNull(dataSource.getDictionary())); break; case DISTINCTCOUNTHLL: @@ -113,6 +121,17 @@ protected AggregationResultsBlock getNextBlock() { result = getDistinctCountHLLResult(Objects.requireNonNull(dataSource.getDictionary()), ((DistinctCountRawHLLAggregationFunction) aggregationFunction).getDistinctCountHLLAggregationFunction()); break; + case DISTINCTCOUNTHLLPLUS: + case DISTINCTCOUNTHLLPLUSMV: + result = getDistinctCountHLLPlusResult(Objects.requireNonNull(dataSource.getDictionary()), + (DistinctCountHLLPlusAggregationFunction) aggregationFunction); + break; + case DISTINCTCOUNTRAWHLLPLUS: + case DISTINCTCOUNTRAWHLLPLUSMV: + result = getDistinctCountHLLPlusResult(Objects.requireNonNull(dataSource.getDictionary()), + ((DistinctCountRawHLLPlusAggregationFunction) aggregationFunction) + .getDistinctCountHLLPlusAggregationFunction()); + break; case SEGMENTPARTITIONEDDISTINCTCOUNT: result = (long) Objects.requireNonNull(dataSource.getDictionary()).length(); break; @@ -128,7 +147,7 @@ protected AggregationResultsBlock getNextBlock() { } // Build intermediate result block based on aggregation result from the executor. - return new AggregationResultsBlock(_aggregationFunctions, aggregationResults); + return new AggregationResultsBlock(_aggregationFunctions, aggregationResults, _queryContext); } private static Double getMinValue(DataSource dataSource) { @@ -210,6 +229,15 @@ private static HyperLogLog getDistinctValueHLL(Dictionary dictionary, int log2m) return hll; } + private static HyperLogLogPlus getDistinctValueHLLPlus(Dictionary dictionary, int p, int sp) { + HyperLogLogPlus hllPlus = new HyperLogLogPlus(p, sp); + int length = dictionary.length(); + for (int i = 0; i < length; i++) { + hllPlus.offer(dictionary.get(i)); + } + return hllPlus; + } + private static HyperLogLog getDistinctCountHLLResult(Dictionary dictionary, DistinctCountHLLAggregationFunction function) { if (dictionary.getValueType() == FieldSpec.DataType.BYTES) { @@ -229,6 +257,25 @@ private static HyperLogLog getDistinctCountHLLResult(Dictionary dictionary, } } + private static HyperLogLogPlus getDistinctCountHLLPlusResult(Dictionary dictionary, + DistinctCountHLLPlusAggregationFunction function) { + if (dictionary.getValueType() == FieldSpec.DataType.BYTES) { + // Treat BYTES value as serialized HyperLogLogPlus + try { + HyperLogLogPlus hllplus = ObjectSerDeUtils.HYPER_LOG_LOG_PLUS_SER_DE.deserialize(dictionary.getBytesValue(0)); + int length = dictionary.length(); + for (int i = 1; i < length; i++) { + hllplus.addAll(ObjectSerDeUtils.HYPER_LOG_LOG_PLUS_SER_DE.deserialize(dictionary.getBytesValue(i))); + } + return hllplus; + } catch (Exception e) { + throw new RuntimeException("Caught exception while merging HyperLogLogPluses", e); + } + } else { + return getDistinctValueHLLPlus(dictionary, function.getP(), function.getSp()); + } + } + private static Object getDistinctCountSmartHLLResult(Dictionary dictionary, DistinctCountSmartHLLAggregationFunction function) { if (dictionary.length() > function.getThreshold()) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionOnlyOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionOnlyOperator.java index 6db1f4e35220..d2b9b8274146 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionOnlyOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionOnlyOperator.java @@ -27,11 +27,11 @@ import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.common.RowBasedBlockValueFetcher; import org.apache.pinot.core.operator.BaseOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; +import org.apache.pinot.core.operator.ColumnContext; import org.apache.pinot.core.operator.ExecutionStatistics; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; -import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.selection.SelectionOperatorUtils; import org.apache.pinot.segment.spi.IndexSegment; @@ -39,38 +39,39 @@ public class SelectionOnlyOperator extends BaseOperator { - private static final String EXPLAIN_NAME = "SELECT"; private final IndexSegment _indexSegment; + private final QueryContext _queryContext; private final boolean _nullHandlingEnabled; - private final TransformOperator _transformOperator; + private final BaseProjectOperator _projectOperator; private final List _expressions; private final BlockValSet[] _blockValSets; private final DataSchema _dataSchema; private final int _numRowsToKeep; - private final List _rows; + private final ArrayList _rows; private final RoaringBitmap[] _nullBitmaps; private int _numDocsScanned = 0; public SelectionOnlyOperator(IndexSegment indexSegment, QueryContext queryContext, - List expressions, TransformOperator transformOperator) { + List expressions, BaseProjectOperator projectOperator) { _indexSegment = indexSegment; + _queryContext = queryContext; _nullHandlingEnabled = queryContext.isNullHandlingEnabled(); - _transformOperator = transformOperator; + _projectOperator = projectOperator; _expressions = expressions; - int numExpressions = _expressions.size(); + int numExpressions = expressions.size(); _blockValSets = new BlockValSet[numExpressions]; String[] columnNames = new String[numExpressions]; DataSchema.ColumnDataType[] columnDataTypes = new DataSchema.ColumnDataType[numExpressions]; for (int i = 0; i < numExpressions; i++) { - ExpressionContext expression = _expressions.get(i); - TransformResultMetadata expressionMetadata = _transformOperator.getResultMetadata(expression); + ExpressionContext expression = expressions.get(i); columnNames[i] = expression.toString(); + ColumnContext columnContext = projectOperator.getResultColumnContext(expression); columnDataTypes[i] = - DataSchema.ColumnDataType.fromDataType(expressionMetadata.getDataType(), expressionMetadata.isSingleValue()); + DataSchema.ColumnDataType.fromDataType(columnContext.getDataType(), columnContext.isSingleValue()); } _dataSchema = new DataSchema(columnNames, columnDataTypes); @@ -93,15 +94,16 @@ public String toExplainString() { @Override protected SelectionResultsBlock getNextBlock() { - TransformBlock transformBlock; - while ((transformBlock = _transformOperator.nextBlock()) != null) { + ValueBlock valueBlock; + while ((valueBlock = _projectOperator.nextBlock()) != null) { int numExpressions = _expressions.size(); for (int i = 0; i < numExpressions; i++) { - _blockValSets[i] = transformBlock.getBlockValueSet(_expressions.get(i)); + _blockValSets[i] = valueBlock.getBlockValueSet(_expressions.get(i)); } RowBasedBlockValueFetcher blockValueFetcher = new RowBasedBlockValueFetcher(_blockValSets); - int numDocsToAdd = Math.min(_numRowsToKeep - _rows.size(), transformBlock.getNumDocs()); + int numDocsToAdd = Math.min(_numRowsToKeep - _rows.size(), valueBlock.getNumDocs()); + _rows.ensureCapacity(_rows.size() + numDocsToAdd); _numDocsScanned += numDocsToAdd; if (_nullHandlingEnabled) { for (int i = 0; i < numExpressions; i++) { @@ -126,12 +128,12 @@ protected SelectionResultsBlock getNextBlock() { } } - return new SelectionResultsBlock(_dataSchema, _rows); + return new SelectionResultsBlock(_dataSchema, _rows, _queryContext); } @Override public List getChildOperators() { - return Collections.singletonList(_transformOperator); + return Collections.singletonList(_projectOperator); } @Override @@ -141,8 +143,8 @@ public IndexSegment getIndexSegment() { @Override public ExecutionStatistics getExecutionStatistics() { - long numEntriesScannedInFilter = _transformOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); - long numEntriesScannedPostFilter = (long) _numDocsScanned * _transformOperator.getNumColumnsProjected(); + long numEntriesScannedInFilter = _projectOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); + long numEntriesScannedPostFilter = (long) _numDocsScanned * _projectOperator.getNumColumnsProjected(); int numTotalDocs = _indexSegment.getSegmentMetadata().getTotalDocs(); return new ExecutionStatistics(_numDocsScanned, numEntriesScannedInFilter, numEntriesScannedPostFilter, numTotalDocs); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionOrderByOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionOrderByOperator.java index d45652bba821..49f0dcc078ea 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionOrderByOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionOrderByOperator.java @@ -19,6 +19,7 @@ package org.apache.pinot.core.operator.query; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -34,13 +35,15 @@ import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.common.RowBasedBlockValueFetcher; import org.apache.pinot.core.operator.BaseOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; import org.apache.pinot.core.operator.BitmapDocIdSetOperator; +import org.apache.pinot.core.operator.ColumnContext; import org.apache.pinot.core.operator.ExecutionStatistics; import org.apache.pinot.core.operator.ProjectionOperator; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.ProjectionOperatorUtils; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; import org.apache.pinot.core.operator.transform.TransformOperator; -import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.selection.SelectionOperatorUtils; import org.apache.pinot.core.query.utils.OrderByComparatorFactory; @@ -68,44 +71,45 @@ * */ public class SelectionOrderByOperator extends BaseOperator { - private static final String EXPLAIN_NAME = "SELECT_ORDERBY"; private final IndexSegment _indexSegment; + private final QueryContext _queryContext; private final boolean _nullHandlingEnabled; // Deduped order-by expressions followed by output expressions from SelectionOperatorUtils.extractExpressions() private final List _expressions; - private final TransformOperator _transformOperator; + private final BaseProjectOperator _projectOperator; private final List _orderByExpressions; - private final TransformResultMetadata[] _orderByExpressionMetadata; + private final ColumnContext[] _orderByColumnContexts; private final int _numRowsToKeep; + private final Comparator _comparator; private final PriorityQueue _rows; private int _numDocsScanned = 0; private long _numEntriesScannedPostFilter = 0; public SelectionOrderByOperator(IndexSegment indexSegment, QueryContext queryContext, - List expressions, TransformOperator transformOperator) { + List expressions, BaseProjectOperator projectOperator) { _indexSegment = indexSegment; + _queryContext = queryContext; _nullHandlingEnabled = queryContext.isNullHandlingEnabled(); _expressions = expressions; - _transformOperator = transformOperator; + _projectOperator = projectOperator; _orderByExpressions = queryContext.getOrderByExpressions(); assert _orderByExpressions != null; int numOrderByExpressions = _orderByExpressions.size(); - _orderByExpressionMetadata = new TransformResultMetadata[numOrderByExpressions]; + _orderByColumnContexts = new ColumnContext[numOrderByExpressions]; for (int i = 0; i < numOrderByExpressions; i++) { ExpressionContext expression = _orderByExpressions.get(i).getExpression(); - _orderByExpressionMetadata[i] = _transformOperator.getResultMetadata(expression); + _orderByColumnContexts[i] = _projectOperator.getResultColumnContext(expression); } _numRowsToKeep = queryContext.getOffset() + queryContext.getLimit(); - Comparator comparator = - OrderByComparatorFactory.getComparator(_orderByExpressions, _orderByExpressionMetadata, true, - _nullHandlingEnabled); + _comparator = + OrderByComparatorFactory.getComparator(_orderByExpressions, _orderByColumnContexts, _nullHandlingEnabled); _rows = new PriorityQueue<>(Math.min(_numRowsToKeep, SelectionOperatorUtils.MAX_ROW_HOLDER_INITIAL_CAPACITY), - comparator); + _comparator.reversed()); } @Override @@ -137,15 +141,15 @@ private SelectionResultsBlock computeAllOrdered() { // Fetch all the expressions and insert them into the priority queue BlockValSet[] blockValSets = new BlockValSet[numExpressions]; - int numColumnsProjected = _transformOperator.getNumColumnsProjected(); - TransformBlock transformBlock; - while ((transformBlock = _transformOperator.nextBlock()) != null) { + int numColumnsProjected = _projectOperator.getNumColumnsProjected(); + ValueBlock valueBlock; + while ((valueBlock = _projectOperator.nextBlock()) != null) { for (int i = 0; i < numExpressions; i++) { ExpressionContext expression = _expressions.get(i); - blockValSets[i] = transformBlock.getBlockValueSet(expression); + blockValSets[i] = valueBlock.getBlockValueSet(expression); } RowBasedBlockValueFetcher blockValueFetcher = new RowBasedBlockValueFetcher(blockValSets); - int numDocsFetched = transformBlock.getNumDocs(); + int numDocsFetched = valueBlock.getNumDocs(); if (_nullHandlingEnabled) { RoaringBitmap[] nullBitmaps = new RoaringBitmap[numExpressions]; for (int i = 0; i < numExpressions; i++) { @@ -175,13 +179,12 @@ private SelectionResultsBlock computeAllOrdered() { DataSchema.ColumnDataType[] columnDataTypes = new DataSchema.ColumnDataType[numExpressions]; for (int i = 0; i < numExpressions; i++) { columnNames[i] = _expressions.get(i).toString(); - TransformResultMetadata expressionMetadata = _orderByExpressionMetadata[i]; - columnDataTypes[i] = - DataSchema.ColumnDataType.fromDataType(expressionMetadata.getDataType(), expressionMetadata.isSingleValue()); + columnDataTypes[i] = DataSchema.ColumnDataType.fromDataType(_orderByColumnContexts[i].getDataType(), + _orderByColumnContexts[i].isSingleValue()); } DataSchema dataSchema = new DataSchema(columnNames, columnDataTypes); - return new SelectionResultsBlock(dataSchema, _rows); + return new SelectionResultsBlock(dataSchema, getSortedRows(), _comparator, _queryContext); } /** @@ -193,16 +196,16 @@ private SelectionResultsBlock computePartiallyOrdered() { // Fetch the order-by expressions and docIds and insert them into the priority queue BlockValSet[] blockValSets = new BlockValSet[numOrderByExpressions]; - int numColumnsProjected = _transformOperator.getNumColumnsProjected(); - TransformBlock transformBlock; - while ((transformBlock = _transformOperator.nextBlock()) != null) { + int numColumnsProjected = _projectOperator.getNumColumnsProjected(); + ValueBlock valueBlock; + while ((valueBlock = _projectOperator.nextBlock()) != null) { for (int i = 0; i < numOrderByExpressions; i++) { ExpressionContext expression = _orderByExpressions.get(i).getExpression(); - blockValSets[i] = transformBlock.getBlockValueSet(expression); + blockValSets[i] = valueBlock.getBlockValueSet(expression); } RowBasedBlockValueFetcher blockValueFetcher = new RowBasedBlockValueFetcher(blockValSets); - int numDocsFetched = transformBlock.getNumDocs(); - int[] docIds = transformBlock.getDocIds(); + int numDocsFetched = valueBlock.getNumDocs(); + int[] docIds = valueBlock.getDocIds(); if (_nullHandlingEnabled) { RoaringBitmap[] nullBitmaps = new RoaringBitmap[numOrderByExpressions]; for (int i = 0; i < numOrderByExpressions; i++) { @@ -261,23 +264,39 @@ private SelectionResultsBlock computePartiallyOrdered() { dataSourceMap.put(column, _indexSegment.getDataSource(column)); } ProjectionOperator projectionOperator = - new ProjectionOperator(dataSourceMap, new BitmapDocIdSetOperator(docIds, numRows)); - TransformOperator transformOperator = new TransformOperator(projectionOperator, nonOrderByExpressions); + ProjectionOperatorUtils.getProjectionOperator(dataSourceMap, new BitmapDocIdSetOperator(docIds, numRows)); + TransformOperator transformOperator = + new TransformOperator(_queryContext, projectionOperator, nonOrderByExpressions); // Fill the non-order-by expression values int numNonOrderByExpressions = nonOrderByExpressions.size(); blockValSets = new BlockValSet[numNonOrderByExpressions]; int rowBaseId = 0; - while ((transformBlock = transformOperator.nextBlock()) != null) { + while ((valueBlock = transformOperator.nextBlock()) != null) { for (int i = 0; i < numNonOrderByExpressions; i++) { ExpressionContext expression = nonOrderByExpressions.get(i); - blockValSets[i] = transformBlock.getBlockValueSet(expression); + blockValSets[i] = valueBlock.getBlockValueSet(expression); } RowBasedBlockValueFetcher blockValueFetcher = new RowBasedBlockValueFetcher(blockValSets); - int numDocsFetched = transformBlock.getNumDocs(); + int numDocsFetched = valueBlock.getNumDocs(); for (int i = 0; i < numDocsFetched; i++) { blockValueFetcher.getRow(i, rowList.get(rowBaseId + i), numOrderByExpressions); } + if (_nullHandlingEnabled) { + RoaringBitmap[] nullBitmaps = new RoaringBitmap[numNonOrderByExpressions]; + for (int i = 0; i < numNonOrderByExpressions; i++) { + nullBitmaps[i] = blockValSets[i].getNullBitmap(); + } + for (int i = 0; i < numDocsFetched; i++) { + Object[] values = rowList.get(rowBaseId + i); + for (int colId = 0; colId < numNonOrderByExpressions; colId++) { + if (nullBitmaps[colId] != null && nullBitmaps[colId].contains(i)) { + int valueColId = numOrderByExpressions + colId; + values[valueColId] = null; + } + } + } + } _numEntriesScannedPostFilter += (long) numDocsFetched * numColumns; rowBaseId += numDocsFetched; } @@ -289,23 +308,31 @@ private SelectionResultsBlock computePartiallyOrdered() { columnNames[i] = _expressions.get(i).toString(); } for (int i = 0; i < numOrderByExpressions; i++) { - TransformResultMetadata expressionMetadata = _orderByExpressionMetadata[i]; - columnDataTypes[i] = - DataSchema.ColumnDataType.fromDataType(expressionMetadata.getDataType(), expressionMetadata.isSingleValue()); + columnDataTypes[i] = DataSchema.ColumnDataType.fromDataType(_orderByColumnContexts[i].getDataType(), + _orderByColumnContexts[i].isSingleValue()); } for (int i = 0; i < numNonOrderByExpressions; i++) { - TransformResultMetadata expressionMetadata = transformOperator.getResultMetadata(nonOrderByExpressions.get(i)); + ColumnContext columnContext = transformOperator.getResultColumnContext(nonOrderByExpressions.get(i)); columnDataTypes[numOrderByExpressions + i] = - DataSchema.ColumnDataType.fromDataType(expressionMetadata.getDataType(), expressionMetadata.isSingleValue()); + DataSchema.ColumnDataType.fromDataType(columnContext.getDataType(), columnContext.isSingleValue()); } DataSchema dataSchema = new DataSchema(columnNames, columnDataTypes); - return new SelectionResultsBlock(dataSchema, _rows); + return new SelectionResultsBlock(dataSchema, getSortedRows(), _comparator, _queryContext); + } + + private List getSortedRows() { + int numRows = _rows.size(); + Object[][] sortedRows = new Object[numRows][]; + for (int i = numRows - 1; i >= 0; i--) { + sortedRows[i] = _rows.poll(); + } + return Arrays.asList(sortedRows); } @Override public List getChildOperators() { - return Collections.singletonList(_transformOperator); + return Collections.singletonList(_projectOperator); } @Override @@ -315,7 +342,7 @@ public IndexSegment getIndexSegment() { @Override public ExecutionStatistics getExecutionStatistics() { - long numEntriesScannedInFilter = _transformOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); + long numEntriesScannedInFilter = _projectOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); int numTotalDocs = _indexSegment.getSegmentMetadata().getTotalDocs(); return new ExecutionStatistics(_numDocsScanned, numEntriesScannedInFilter, _numEntriesScannedPostFilter, numTotalDocs); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionPartiallyOrderedByAscOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionPartiallyOrderedByAscOperator.java index 62de4824cd8a..bc4d42a20c22 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionPartiallyOrderedByAscOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionPartiallyOrderedByAscOperator.java @@ -24,8 +24,8 @@ import java.util.function.Supplier; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.TransformBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.IndexSegment; @@ -41,8 +41,8 @@ public class SelectionPartiallyOrderedByAscOperator extends LinearSelectionOrder private int _numDocsScanned = 0; public SelectionPartiallyOrderedByAscOperator(IndexSegment indexSegment, QueryContext queryContext, - List expressions, TransformOperator transformOperator, int numSortedExpressions) { - super(indexSegment, queryContext, expressions, transformOperator, numSortedExpressions); + List expressions, BaseProjectOperator projectOperator, int numSortedExpressions) { + super(indexSegment, queryContext, expressions, projectOperator, numSortedExpressions); Preconditions.checkArgument(queryContext.getOrderByExpressions().stream() .filter(expr -> expr.getExpression().getType() == ExpressionContext.Type.IDENTIFIER) .findFirst() @@ -56,10 +56,10 @@ protected List fetch(Supplier listBuilderSupplier) { int numExpressions = _expressions.size(); BlockValSet[] blockValSets = new BlockValSet[numExpressions]; ListBuilder listBuilder = listBuilderSupplier.get(); - TransformBlock transformBlock; - while ((transformBlock = _transformOperator.nextBlock()) != null) { - IntFunction rowFetcher = fetchBlock(transformBlock, blockValSets); - int numDocsFetched = transformBlock.getNumDocs(); + ValueBlock valueBlock; + while ((valueBlock = _projectOperator.nextBlock()) != null) { + IntFunction rowFetcher = fetchBlock(valueBlock, blockValSets); + int numDocsFetched = valueBlock.getNumDocs(); _numDocsScanned += numDocsFetched; for (int i = 0; i < numDocsFetched; i++) { if (listBuilder.add(rowFetcher.apply(i))) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionPartiallyOrderedByDescOperation.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionPartiallyOrderedByDescOperation.java index 5322e0a6e660..a9e74c1a004f 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionPartiallyOrderedByDescOperation.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/query/SelectionPartiallyOrderedByDescOperation.java @@ -26,8 +26,8 @@ import java.util.function.Supplier; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.TransformBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.IndexSegment; @@ -44,12 +44,12 @@ public class SelectionPartiallyOrderedByDescOperation extends LinearSelectionOrd private int _numDocsScanned = 0; public SelectionPartiallyOrderedByDescOperation(IndexSegment indexSegment, QueryContext queryContext, - List expressions, TransformOperator transformOperator, int numSortedExpressions) { - super(indexSegment, queryContext, expressions, transformOperator, numSortedExpressions); + List expressions, BaseProjectOperator projectOperator, int numSortedExpressions) { + super(indexSegment, queryContext, expressions, projectOperator, numSortedExpressions); assert queryContext.getOrderByExpressions() != null; - Preconditions.checkArgument(queryContext.getOrderByExpressions().stream() + Preconditions.checkArgument(!queryContext.getOrderByExpressions().stream() .filter(expr -> expr.getExpression().getType() == ExpressionContext.Type.IDENTIFIER).findFirst() - .orElseThrow(() -> new IllegalArgumentException("The query is not order by identifiers")).isDesc(), + .orElseThrow(() -> new IllegalArgumentException("The query is not order by identifiers")).isAsc(), "%s can only be used when the first column in order by is DESC", EXPLAIN_NAME); } @@ -64,10 +64,10 @@ protected List fetch(Supplier listBuilderSupplier) { int numExpressions = _expressions.size(); BlockValSet[] blockValSets = new BlockValSet[numExpressions]; List localBestRows = new ArrayList<>(); - TransformBlock transformBlock; - while ((transformBlock = _transformOperator.nextBlock()) != null) { - IntFunction rowFetcher = fetchBlock(transformBlock, blockValSets); - int numDocsFetched = transformBlock.getNumDocs(); + ValueBlock valueBlock; + while ((valueBlock = _projectOperator.nextBlock()) != null) { + IntFunction rowFetcher = fetchBlock(valueBlock, blockValSets); + int numDocsFetched = valueBlock.getNumDocs(); _numDocsScanned += numDocsFetched; ListBuilder listBuilder = listBuilderSupplier.get(); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/BaseStreamingCombineOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/BaseStreamingCombineOperator.java new file mode 100644 index 000000000000..6b2dc92d8247 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/BaseStreamingCombineOperator.java @@ -0,0 +1,207 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.streaming; + +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.apache.pinot.common.exception.QueryException; +import org.apache.pinot.common.utils.config.QueryOptionsUtils; +import org.apache.pinot.core.common.Operator; +import org.apache.pinot.core.operator.AcquireReleaseColumnsSegmentOperator; +import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock; +import org.apache.pinot.core.operator.blocks.results.ExceptionResultsBlock; +import org.apache.pinot.core.operator.blocks.results.MetadataResultsBlock; +import org.apache.pinot.core.operator.combine.BaseCombineOperator; +import org.apache.pinot.core.operator.combine.CombineOperatorUtils; +import org.apache.pinot.core.operator.combine.merger.ResultsBlockMerger; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.query.scheduler.resources.ResourceManager; +import org.apache.pinot.spi.exception.EarlyTerminationException; +import org.apache.pinot.spi.utils.CommonConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +@SuppressWarnings({"rawtypes", "unchecked"}) +public abstract class BaseStreamingCombineOperator extends BaseCombineOperator { + private static final Logger LOGGER = LoggerFactory.getLogger(BaseStreamingCombineOperator.class); + + // Use a special results block to indicate that this is the last results block for a child operator in the list + public static final MetadataResultsBlock LAST_RESULTS_BLOCK = new MetadataResultsBlock(); + + // Use a limit-sized BlockingQueue to store the results blocks and apply back pressure to the worker threads + protected final BlockingQueue _blockingQueue; + + protected final Object _querySatisfiedTracker; + + protected boolean _querySatisfied; + protected int _numOperatorsFinished; + + public BaseStreamingCombineOperator(ResultsBlockMerger resultsBlockMerger, List operators, + QueryContext queryContext, ExecutorService executorService) { + super(resultsBlockMerger, operators, queryContext, executorService); + Integer maxStreamingPendingBlocks = QueryOptionsUtils.getMaxStreamingPendingBlocks(queryContext.getQueryOptions()); + _blockingQueue = new ArrayBlockingQueue<>(maxStreamingPendingBlocks != null ? maxStreamingPendingBlocks + : CommonConstants.Broker.Request.QueryOptionValue.DEFAULT_MAX_STREAMING_PENDING_BLOCKS); + _querySatisfiedTracker = createQuerySatisfiedTracker(); + } + + /** + * {@inheritDoc} + * + * When all the results blocks are returned, returns a final metadata block. Caller shouldn't call this method after + * it returns the metadata block or exception block. + */ + @Override + protected BaseResultsBlock getNextBlock() { + long endTimeMs = _queryContext.getEndTimeMs(); + while (!_querySatisfied && _numOperatorsFinished < _numOperators) { + try { + BaseResultsBlock resultsBlock = + _blockingQueue.poll(endTimeMs - System.currentTimeMillis(), TimeUnit.MILLISECONDS); + if (resultsBlock == null) { + // Query times out, skip streaming the remaining results blocks + LOGGER.error("Timed out while polling results block (query: {})", _queryContext); + return new ExceptionResultsBlock(QueryException.getException(QueryException.EXECUTION_TIMEOUT_ERROR, + new TimeoutException("Timed out while polling results block"))); + } + if (resultsBlock.getProcessingExceptions() != null) { + // Caught exception while processing segment, skip streaming the remaining results blocks and directly return + // the exception + return resultsBlock; + } + if (resultsBlock == LAST_RESULTS_BLOCK) { + // Caught LAST_RESULTS_BLOCK from a specific task, indicated it has finished. + // Skip returning this metadata block and continue to process the next from the _blockingQueue. + _numOperatorsFinished++; + continue; + } + _querySatisfied = isQuerySatisfied((T) resultsBlock, _querySatisfiedTracker); + return resultsBlock; + } catch (InterruptedException e) { + throw new EarlyTerminationException("Interrupted while streaming results blocks", e); + } catch (Exception e) { + LOGGER.error("Caught exception while streaming results blocks (query: {})", _queryContext, e); + return new ExceptionResultsBlock(QueryException.getException(QueryException.INTERNAL_ERROR, e)); + } + } + // Setting the execution stats for the final return + BaseResultsBlock finalBlock = new MetadataResultsBlock(); + int numServerThreads = Math.min(_numTasks, ResourceManager.DEFAULT_QUERY_WORKER_THREADS); + CombineOperatorUtils.setExecutionStatistics(finalBlock, _operators, _totalWorkerThreadCpuTimeNs.get(), + numServerThreads); + return finalBlock; + } + + @Override + protected void processSegments() { + int operatorId; + Object tracker = createQuerySatisfiedTracker(); + while (_processingException.get() == null && (operatorId = _nextOperatorId.getAndIncrement()) < _numOperators) { + Operator operator = _operators.get(operatorId); + try { + if (operator instanceof AcquireReleaseColumnsSegmentOperator) { + ((AcquireReleaseColumnsSegmentOperator) operator).acquire(); + } + if (isChildOperatorSingleBlock()) { + T resultsBlock = operator.nextBlock(); + addResultsBlock(resultsBlock); + // When query is satisfied, skip processing the remaining segments + if (isQuerySatisfied(resultsBlock, tracker)) { + _nextOperatorId.set(_numOperators); + return; + } + } else { + T resultsBlock; + while ((resultsBlock = operator.nextBlock()) != null) { + addResultsBlock(resultsBlock); + // When query is satisfied, skip processing the remaining segments + if (isQuerySatisfied(resultsBlock, tracker)) { + _nextOperatorId.set(_numOperators); + return; + } + } + } + } finally { + if (operator instanceof AcquireReleaseColumnsSegmentOperator) { + ((AcquireReleaseColumnsSegmentOperator) operator).release(); + } + } + // offer the LAST_RESULTS_BLOCK indicate finish of the current operator. + addResultsBlock(LAST_RESULTS_BLOCK); + } + } + + // NOTE: Throw EarlyTerminationException when interrupted or timed out + private void addResultsBlock(BaseResultsBlock resultsBlock) { + try { + if (!_blockingQueue.offer(resultsBlock, _queryContext.getEndTimeMs() - System.currentTimeMillis(), + TimeUnit.MILLISECONDS)) { + throw new EarlyTerminationException("Timed out waiting to add results block"); + } + } catch (InterruptedException e) { + throw new EarlyTerminationException("Interrupted waiting to add results block"); + } + } + + @Override + protected void onProcessSegmentsException(Throwable t) { + _processingException.compareAndSet(null, t); + // Clear the blocking queue and add the exception results block to terminate the main thread + _blockingQueue.clear(); + _blockingQueue.offer(new ExceptionResultsBlock(t)); + } + + @Override + protected void onProcessSegmentsFinish() { + } + + /** + * Returns whether the child operator returns only a single block. + */ + protected boolean isChildOperatorSingleBlock() { + return true; + } + + /** + * Creates a tracker object to track if the query is satisfied. + */ + protected Object createQuerySatisfiedTracker() { + return null; + } + + /** + * Returns {@code true} if the query is already satisfied with the results block. + */ + protected boolean isQuerySatisfied(T resultsBlock, Object tracker) { + return _resultsBlockMerger.isQuerySatisfied(resultsBlock); + } + + public void start() { + startProcess(); + } + + public void stop() { + stopProcess(); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingAggregationCombineOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingAggregationCombineOperator.java new file mode 100644 index 000000000000..ff5820611dc5 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingAggregationCombineOperator.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.streaming; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import org.apache.pinot.core.common.Operator; +import org.apache.pinot.core.operator.blocks.results.AggregationResultsBlock; +import org.apache.pinot.core.operator.combine.merger.AggregationResultsBlockMerger; +import org.apache.pinot.core.query.request.context.QueryContext; + + +/** + * Combine operator for aggregation queries with streaming response. + */ +@SuppressWarnings("rawtypes") +public class StreamingAggregationCombineOperator extends BaseStreamingCombineOperator { + private static final String EXPLAIN_NAME = "STREAMING_COMBINE_AGGREGATE"; + + public StreamingAggregationCombineOperator(List operators, QueryContext queryContext, + ExecutorService executorService) { + super(new AggregationResultsBlockMerger(queryContext), operators, queryContext, executorService); + } + + @Override + public String toExplainString() { + return EXPLAIN_NAME; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingDistinctCombineOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingDistinctCombineOperator.java new file mode 100644 index 000000000000..6834e30145f0 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingDistinctCombineOperator.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.streaming; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import org.apache.pinot.core.common.Operator; +import org.apache.pinot.core.operator.blocks.results.DistinctResultsBlock; +import org.apache.pinot.core.operator.combine.merger.DistinctResultsBlockMerger; +import org.apache.pinot.core.query.request.context.QueryContext; + + +/** + * Combine operator for distinct queries with streaming response. + */ +@SuppressWarnings("rawtypes") +public class StreamingDistinctCombineOperator extends BaseStreamingCombineOperator { + private static final String EXPLAIN_NAME = "STREAMING_COMBINE_DISTINCT"; + + public StreamingDistinctCombineOperator(List operators, QueryContext queryContext, + ExecutorService executorService) { + super(new DistinctResultsBlockMerger(queryContext), operators, queryContext, executorService); + } + + @Override + public String toExplainString() { + return EXPLAIN_NAME; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingGroupByCombineOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingGroupByCombineOperator.java new file mode 100644 index 000000000000..759564c8559b --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingGroupByCombineOperator.java @@ -0,0 +1,270 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.streaming; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.apache.pinot.common.exception.QueryException; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.common.Operator; +import org.apache.pinot.core.data.table.ConcurrentIndexedTable; +import org.apache.pinot.core.data.table.IndexedTable; +import org.apache.pinot.core.data.table.IntermediateRecord; +import org.apache.pinot.core.data.table.Key; +import org.apache.pinot.core.data.table.Record; +import org.apache.pinot.core.data.table.UnboundedConcurrentIndexedTable; +import org.apache.pinot.core.operator.AcquireReleaseColumnsSegmentOperator; +import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock; +import org.apache.pinot.core.operator.blocks.results.ExceptionResultsBlock; +import org.apache.pinot.core.operator.blocks.results.GroupByResultsBlock; +import org.apache.pinot.core.operator.blocks.results.MetadataResultsBlock; +import org.apache.pinot.core.operator.combine.CombineOperatorUtils; +import org.apache.pinot.core.query.aggregation.function.AggregationFunction; +import org.apache.pinot.core.query.aggregation.groupby.AggregationGroupByResult; +import org.apache.pinot.core.query.aggregation.groupby.GroupKeyGenerator; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.query.scheduler.resources.ResourceManager; +import org.apache.pinot.core.util.GroupByUtils; +import org.apache.pinot.spi.exception.EarlyTerminationException; +import org.apache.pinot.spi.trace.Tracing; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Combine operator for group-by queries. + * TODO: Use CombineOperatorUtils.getNumThreadsForQuery() to get the parallelism of the query instead of using + * all threads + */ +@SuppressWarnings("rawtypes") +public class StreamingGroupByCombineOperator extends BaseStreamingCombineOperator { + public static final int MAX_TRIM_THRESHOLD = 1_000_000_000; + + private static final Logger LOGGER = LoggerFactory.getLogger(StreamingGroupByCombineOperator.class); + private static final String EXPLAIN_NAME = "STREAMING_COMBINE_GROUP_BY"; + + private final int _trimSize; + private final int _trimThreshold; + private final int _numAggregationFunctions; + private final int _numGroupByExpressions; + private final int _numColumns; + // We use a CountDownLatch to track if all Futures are finished by the query timeout, and cancel the unfinished + // _futures (try to interrupt the execution if it already started). + private final CountDownLatch _operatorLatch; + private boolean _opCompleted; + + private volatile IndexedTable _indexedTable; + private volatile boolean _numGroupsLimitReached; + + public StreamingGroupByCombineOperator(List operators, QueryContext queryContext, + ExecutorService executorService) { + super(null, operators, overrideMaxExecutionThreads(queryContext, operators.size()), executorService); + + int minTrimSize = queryContext.getMinServerGroupTrimSize(); + if (minTrimSize > 0) { + int limit = queryContext.getLimit(); + if ((!queryContext.isServerReturnFinalResult() && queryContext.getOrderByExpressions() != null) + || queryContext.getHavingFilter() != null) { + _trimSize = GroupByUtils.getTableCapacity(limit, minTrimSize); + } else { + // TODO: Keeping only 'LIMIT' groups can cause inaccurate result because the groups are randomly selected + // without ordering. Consider ordering on group-by columns if no ordering is specified. + _trimSize = limit; + } + _trimThreshold = queryContext.getGroupTrimThreshold(); + } else { + // Server trim is disabled + _trimSize = Integer.MAX_VALUE; + _trimThreshold = Integer.MAX_VALUE; + } + + AggregationFunction[] aggregationFunctions = _queryContext.getAggregationFunctions(); + assert aggregationFunctions != null; + _numAggregationFunctions = aggregationFunctions.length; + assert _queryContext.getGroupByExpressions() != null; + _numGroupByExpressions = _queryContext.getGroupByExpressions().size(); + _numColumns = _numGroupByExpressions + _numAggregationFunctions; + _operatorLatch = new CountDownLatch(_numTasks); + _opCompleted = false; + } + + @Override + protected BaseResultsBlock getNextBlock() { + if (!_opCompleted) { + try { + return getFinalResult(); + } catch (InterruptedException e) { + throw new EarlyTerminationException("Interrupted while merging results blocks", e); + } catch (Exception e) { + LOGGER.error("Caught exception while merging results blocks (query: {})", _queryContext, e); + return new ExceptionResultsBlock(QueryException.getException(QueryException.INTERNAL_ERROR, e)); + } + } + // Setting the execution stats for the final return + BaseResultsBlock finalBlock = new MetadataResultsBlock(); + int numServerThreads = Math.min(_numTasks, ResourceManager.DEFAULT_QUERY_WORKER_THREADS); + CombineOperatorUtils.setExecutionStatistics(finalBlock, _operators, _totalWorkerThreadCpuTimeNs.get(), + numServerThreads); + return finalBlock; + } + + /** + * For group-by queries, when maxExecutionThreads is not explicitly configured, create one task per operator. + */ + private static QueryContext overrideMaxExecutionThreads(QueryContext queryContext, int numOperators) { + int maxExecutionThreads = queryContext.getMaxExecutionThreads(); + if (maxExecutionThreads <= 0) { + queryContext.setMaxExecutionThreads(numOperators); + } + return queryContext; + } + + @Override + public String toExplainString() { + return EXPLAIN_NAME; + } + + /** + * Executes query on one segment in a worker thread and merges the results into the indexed table. + */ + @Override + public void processSegments() { + int operatorId; + while (_processingException.get() == null && (operatorId = _nextOperatorId.getAndIncrement()) < _numOperators) { + Operator operator = _operators.get(operatorId); + try { + if (operator instanceof AcquireReleaseColumnsSegmentOperator) { + ((AcquireReleaseColumnsSegmentOperator) operator).acquire(); + } + GroupByResultsBlock resultsBlock = (GroupByResultsBlock) operator.nextBlock(); + if (_indexedTable == null) { + synchronized (this) { + if (_indexedTable == null) { + DataSchema dataSchema = resultsBlock.getDataSchema(); + // NOTE: Use trimSize as resultSize on server size. + if (_trimThreshold >= MAX_TRIM_THRESHOLD) { + // special case of trim threshold where it is set to max value. + // there won't be any trimming during upsert in this case. + // thus we can avoid the overhead of read-lock and write-lock + // in the upsert method. + _indexedTable = new UnboundedConcurrentIndexedTable(dataSchema, _queryContext, _trimSize); + } else { + _indexedTable = + new ConcurrentIndexedTable(dataSchema, _queryContext, _trimSize, _trimSize, _trimThreshold); + } + } + } + } + + // Set groups limit reached flag. + if (resultsBlock.isNumGroupsLimitReached()) { + _numGroupsLimitReached = true; + } + + // Merge aggregation group-by result. + // Iterate over the group-by keys, for each key, update the group-by result in the indexedTable + Collection intermediateRecords = resultsBlock.getIntermediateRecords(); + // Count the number of merged keys + int mergedKeys = 0; + // For now, only GroupBy OrderBy query has pre-constructed intermediate records + if (intermediateRecords == null) { + // Merge aggregation group-by result. + AggregationGroupByResult aggregationGroupByResult = resultsBlock.getAggregationGroupByResult(); + if (aggregationGroupByResult != null) { + // Iterate over the group-by keys, for each key, update the group-by result in the indexedTable + Iterator dicGroupKeyIterator = aggregationGroupByResult.getGroupKeyIterator(); + while (dicGroupKeyIterator.hasNext()) { + GroupKeyGenerator.GroupKey groupKey = dicGroupKeyIterator.next(); + Object[] keys = groupKey._keys; + Object[] values = Arrays.copyOf(keys, _numColumns); + int groupId = groupKey._groupId; + for (int i = 0; i < _numAggregationFunctions; i++) { + values[_numGroupByExpressions + i] = aggregationGroupByResult.getResultForGroupId(i, groupId); + } + _indexedTable.upsert(new Key(keys), new Record(values)); + Tracing.ThreadAccountantOps.sampleAndCheckInterruptionPeriodically(mergedKeys); + mergedKeys++; + } + } + } else { + for (IntermediateRecord intermediateResult : intermediateRecords) { + //TODO: change upsert api so that it accepts intermediateRecord directly + _indexedTable.upsert(intermediateResult._key, intermediateResult._record); + Tracing.ThreadAccountantOps.sampleAndCheckInterruptionPeriodically(mergedKeys); + mergedKeys++; + } + } + } finally { + if (operator instanceof AcquireReleaseColumnsSegmentOperator) { + ((AcquireReleaseColumnsSegmentOperator) operator).release(); + } + } + } + } + + // TODO: combine this with the single block group by combine operator + private BaseResultsBlock getFinalResult() + throws InterruptedException { + long timeoutMs = _queryContext.getEndTimeMs() - System.currentTimeMillis(); + _opCompleted = _operatorLatch.await(timeoutMs, TimeUnit.MILLISECONDS); + if (!_opCompleted) { + // If this happens, the broker side should already timed out, just log the error and return + String errorMessage = + String.format("Timed out while combining group-by order-by results after %dms, queryContext = %s", timeoutMs, + _queryContext); + LOGGER.error(errorMessage); + return new ExceptionResultsBlock(new TimeoutException(errorMessage)); + } + + Throwable processingException = _processingException.get(); + if (processingException != null) { + return new ExceptionResultsBlock(processingException); + } + + IndexedTable indexedTable = _indexedTable; + if (_queryContext.isServerReturnFinalResult()) { + indexedTable.finish(true, true); + } else if (_queryContext.isServerReturnFinalResultKeyUnpartitioned()) { + indexedTable.finish(false, true); + } else { + indexedTable.finish(false); + } + GroupByResultsBlock mergedBlock = new GroupByResultsBlock(indexedTable, _queryContext); + mergedBlock.setNumGroupsLimitReached(_numGroupsLimitReached); + mergedBlock.setNumResizes(indexedTable.getNumResizes()); + mergedBlock.setResizeTimeMs(indexedTable.getResizeTimeMs()); + return mergedBlock; + } + + @Override + public void onProcessSegmentsException(Throwable t) { + _processingException.compareAndSet(null, t); + } + + @Override + public void onProcessSegmentsFinish() { + _operatorLatch.countDown(); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingInstanceResponseOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingInstanceResponseOperator.java index 4bbd21ba3c90..ca5393b8fd2a 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingInstanceResponseOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingInstanceResponseOperator.java @@ -18,41 +18,96 @@ */ package org.apache.pinot.core.operator.streaming; -import io.grpc.stub.StreamObserver; -import java.io.IOException; import java.util.List; +import org.apache.commons.lang3.StringUtils; import org.apache.pinot.common.exception.QueryException; -import org.apache.pinot.common.proto.Server; import org.apache.pinot.core.operator.InstanceResponseOperator; import org.apache.pinot.core.operator.blocks.InstanceResponseBlock; +import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock; +import org.apache.pinot.core.operator.blocks.results.ExceptionResultsBlock; +import org.apache.pinot.core.operator.blocks.results.MetadataResultsBlock; import org.apache.pinot.core.operator.combine.BaseCombineOperator; +import org.apache.pinot.core.query.executor.ResultsBlockStreamer; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.FetchContext; -import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; +import org.apache.pinot.spi.exception.EarlyTerminationException; +import org.apache.pinot.spi.exception.QueryCancelledException; +import org.apache.pinot.spi.trace.Tracing; +/** + * Like {@link InstanceResponseOperator}, but instead of sending all the data to the broker at once, it streams the data + * to a given {@link ResultsBlockStreamer}. + * + * This is used in multi-stage to stream data to the receiving mailboxes. + */ public class StreamingInstanceResponseOperator extends InstanceResponseOperator { + private static final String EXPLAIN_NAME = "STREAMING_INSTANCE_RESPONSE"; - private final StreamObserver _streamObserver; + private final BaseStreamingCombineOperator _streamingCombineOperator; + private final ResultsBlockStreamer _streamer; - public StreamingInstanceResponseOperator(BaseCombineOperator combinedOperator, List indexSegments, - List fetchContexts, StreamObserver streamObserver, + public StreamingInstanceResponseOperator(BaseCombineOperator combinedOperator, + List segmentContexts, List fetchContexts, ResultsBlockStreamer streamer, QueryContext queryContext) { - super(combinedOperator, indexSegments, fetchContexts, queryContext); - _streamObserver = streamObserver; + super(combinedOperator, segmentContexts, fetchContexts, queryContext); + _streamingCombineOperator = + combinedOperator instanceof BaseStreamingCombineOperator ? (BaseStreamingCombineOperator) combinedOperator + : null; + _streamer = streamer; } @Override protected InstanceResponseBlock getNextBlock() { - InstanceResponseBlock responseBlock = super.getNextBlock(); - InstanceResponseBlock metadataOnlyResponseBlock = responseBlock.toMetadataOnlyResponseBlock(); try { - _streamObserver.onNext(StreamingResponseUtils.getDataResponse(responseBlock.toDataOnlyDataTable())); - } catch (IOException e) { - metadataOnlyResponseBlock.addException( - QueryException.getException(QueryException.DATA_TABLE_SERIALIZATION_ERROR, e)); + prefetchAll(); + if (_streamingCombineOperator != null) { + _streamingCombineOperator.start(); + BaseResultsBlock resultsBlock = getBaseBlock(); + while (!(resultsBlock instanceof MetadataResultsBlock)) { + if (resultsBlock instanceof ExceptionResultsBlock) { + return new InstanceResponseBlock(resultsBlock); + } + if (resultsBlock.getNumRows() > 0) { + _streamer.send(resultsBlock); + } + resultsBlock = getBaseBlock(); + } + // Return a metadata-only block in the end + return buildInstanceResponseBlock(resultsBlock); + } else { + // Handle single block combine operator in streaming fashion + BaseResultsBlock resultsBlock = getBaseBlock(); + if (resultsBlock instanceof ExceptionResultsBlock) { + return new InstanceResponseBlock(resultsBlock); + } + if (resultsBlock.getNumRows() > 0) { + _streamer.send(resultsBlock); + } + return buildInstanceResponseBlock(resultsBlock).toMetadataOnlyResponseBlock(); + } + } catch (EarlyTerminationException e) { + Exception killedErrorMsg = Tracing.getThreadAccountant().getErrorStatus(); + return new InstanceResponseBlock(new ExceptionResultsBlock(new QueryCancelledException( + "Cancelled while streaming results" + (killedErrorMsg == null ? StringUtils.EMPTY : " " + killedErrorMsg), + e))); + } catch (Exception e) { + return new InstanceResponseBlock(new ExceptionResultsBlock(QueryException.INTERNAL_ERROR, e)); + } finally { + if (_streamingCombineOperator != null) { + _streamingCombineOperator.stop(); + } + releaseAll(); } - // return a metadata-only block. - return metadataOnlyResponseBlock; + } + + protected BaseResultsBlock getCombinedResults() { + return _combineOperator.nextBlock(); + } + + @Override + public String toExplainString() { + return EXPLAIN_NAME; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingSelectionOnlyCombineOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingSelectionOnlyCombineOperator.java index 771eefdee4f5..ca41a7c07ef4 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingSelectionOnlyCombineOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingSelectionOnlyCombineOperator.java @@ -18,50 +18,26 @@ */ package org.apache.pinot.core.operator.streaming; -import io.grpc.stub.StreamObserver; -import java.util.Collection; import java.util.List; import java.util.concurrent.ExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicLong; -import org.apache.pinot.common.datatable.DataTable; -import org.apache.pinot.common.exception.QueryException; -import org.apache.pinot.common.proto.Server; -import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.AcquireReleaseColumnsSegmentOperator; -import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock; -import org.apache.pinot.core.operator.blocks.results.ExceptionResultsBlock; -import org.apache.pinot.core.operator.blocks.results.MetadataResultsBlock; import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; -import org.apache.pinot.core.operator.combine.BaseCombineOperator; +import org.apache.pinot.core.operator.combine.merger.SelectionOnlyResultsBlockMerger; import org.apache.pinot.core.query.request.context.QueryContext; -import org.apache.pinot.core.query.selection.SelectionOperatorUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** - * Combine operator for selection only streaming queries. + * Combine operator for selection queries with streaming response. */ @SuppressWarnings("rawtypes") -public class StreamingSelectionOnlyCombineOperator extends BaseCombineOperator { - private static final Logger LOGGER = LoggerFactory.getLogger(StreamingSelectionOnlyCombineOperator.class); - - private static final String EXPLAIN_NAME = "SELECT_STREAMING_COMBINE"; - - // Special results block to indicate that this is the last results block for an operator - private static final MetadataResultsBlock LAST_RESULTS_BLOCK = new MetadataResultsBlock(); - - private final StreamObserver _streamObserver; +public class StreamingSelectionOnlyCombineOperator extends BaseStreamingCombineOperator { + private static final String EXPLAIN_NAME = "STREAMING_COMBINE_SELECT"; private final int _limit; - private final AtomicLong _numRowsCollected = new AtomicLong(); public StreamingSelectionOnlyCombineOperator(List operators, QueryContext queryContext, - ExecutorService executorService, StreamObserver streamObserver) { - super(operators, queryContext, executorService); - _streamObserver = streamObserver; + ExecutorService executorService) { + super(new SelectionOnlyResultsBlockMerger(queryContext), operators, queryContext, executorService); _limit = queryContext.getLimit(); } @@ -71,71 +47,17 @@ public String toExplainString() { } @Override - protected void processSegments() { - int operatorId; - while ((operatorId = _nextOperatorId.getAndIncrement()) < _numOperators) { - Operator operator = _operators.get(operatorId); - SelectionResultsBlock resultsBlock; - try { - if (operator instanceof AcquireReleaseColumnsSegmentOperator) { - ((AcquireReleaseColumnsSegmentOperator) operator).acquire(); - } - while ((resultsBlock = (SelectionResultsBlock) operator.nextBlock()) != null) { - Collection rows = resultsBlock.getRows(); - assert rows != null; - long numRowsCollected = _numRowsCollected.addAndGet(rows.size()); - _blockingQueue.offer(resultsBlock); - if (numRowsCollected >= _limit) { - return; - } - } - } finally { - if (operator instanceof AcquireReleaseColumnsSegmentOperator) { - ((AcquireReleaseColumnsSegmentOperator) operator).release(); - } - } - _blockingQueue.offer(LAST_RESULTS_BLOCK); - } + protected boolean isChildOperatorSingleBlock() { + return false; } @Override - protected BaseResultsBlock mergeResults() - throws Exception { - long numRowsCollected = 0; - int numOperatorsFinished = 0; - long endTimeMs = _queryContext.getEndTimeMs(); - while (numRowsCollected < _limit && numOperatorsFinished < _numOperators) { - BaseResultsBlock resultsBlock = - _blockingQueue.poll(endTimeMs - System.currentTimeMillis(), TimeUnit.MILLISECONDS); - if (resultsBlock == null) { - // Query times out, skip streaming the remaining results blocks - LOGGER.error("Timed out while polling results block (query: {})", _queryContext); - return new ExceptionResultsBlock(QueryException.getException(QueryException.EXECUTION_TIMEOUT_ERROR, - new TimeoutException("Timed out while polling results block"))); - } - if (resultsBlock.getProcessingExceptions() != null) { - // Caught exception while processing segment, skip streaming the remaining results blocks and directly return - // the exception - return resultsBlock; - } - if (resultsBlock == LAST_RESULTS_BLOCK) { - numOperatorsFinished++; - continue; - } - SelectionResultsBlock selectionResultsBlock = (SelectionResultsBlock) resultsBlock; - DataSchema dataSchema = selectionResultsBlock.getDataSchema(); - Collection rows = selectionResultsBlock.getRows(); - assert dataSchema != null && rows != null; - numRowsCollected += rows.size(); - DataTable dataTable = - SelectionOperatorUtils.getDataTableFromRows(rows, dataSchema, _queryContext.isNullHandlingEnabled()); - _streamObserver.onNext(StreamingResponseUtils.getDataResponse(dataTable)); - } - // Return a metadata results block in the end - return new MetadataResultsBlock(); + protected Object createQuerySatisfiedTracker() { + return new AtomicLong(); } @Override - protected void mergeResultsBlocks(SelectionResultsBlock mergedBlock, SelectionResultsBlock blockToMerge) { + protected boolean isQuerySatisfied(SelectionResultsBlock resultsBlock, Object tracker) { + return ((AtomicLong) tracker).addAndGet(resultsBlock.getNumRows()) >= _limit; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingSelectionOnlyOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingSelectionOnlyOperator.java index 504a8cfe3306..1ed0f7bd62d1 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingSelectionOnlyOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingSelectionOnlyOperator.java @@ -25,36 +25,46 @@ import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.common.RowBasedBlockValueFetcher; import org.apache.pinot.core.operator.BaseOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; +import org.apache.pinot.core.operator.ColumnContext; import org.apache.pinot.core.operator.ExecutionStatistics; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; -import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.IndexSegment; +import org.roaringbitmap.RoaringBitmap; +/** + * Streaming only selection operator returns one block at a time not one block per-segment. + * This is for efficient streaming of data return on a selection-only situation. + * + * This optimization doesn't apply to any other combine/merge required operators. + */ public class StreamingSelectionOnlyOperator extends BaseOperator { - private static final String EXPLAIN_NAME = "SELECT_STREAMING"; private final IndexSegment _indexSegment; - private final TransformOperator _transformOperator; + private final QueryContext _queryContext; private final List _expressions; + private final BaseProjectOperator _projectOperator; private final BlockValSet[] _blockValSets; private final DataSchema _dataSchema; private final int _limit; + private final boolean _nullHandlingEnabled; + private final RoaringBitmap[] _nullBitmaps; private int _numDocsScanned = 0; public StreamingSelectionOnlyOperator(IndexSegment indexSegment, QueryContext queryContext, - List expressions, TransformOperator transformOperator) { + List expressions, BaseProjectOperator projectOperator) { _indexSegment = indexSegment; - _transformOperator = transformOperator; + _queryContext = queryContext; _expressions = expressions; + _projectOperator = projectOperator; + _nullHandlingEnabled = queryContext.isNullHandlingEnabled(); int numExpressions = expressions.size(); _blockValSets = new BlockValSet[numExpressions]; @@ -62,13 +72,13 @@ public StreamingSelectionOnlyOperator(IndexSegment indexSegment, QueryContext qu DataSchema.ColumnDataType[] columnDataTypes = new DataSchema.ColumnDataType[numExpressions]; for (int i = 0; i < numExpressions; i++) { ExpressionContext expression = expressions.get(i); - TransformResultMetadata expressionMetadata = transformOperator.getResultMetadata(expression); columnNames[i] = expression.toString(); + ColumnContext columnContext = projectOperator.getResultColumnContext(expression); columnDataTypes[i] = - DataSchema.ColumnDataType.fromDataType(expressionMetadata.getDataType(), expressionMetadata.isSingleValue()); + DataSchema.ColumnDataType.fromDataType(columnContext.getDataType(), columnContext.isSingleValue()); } _dataSchema = new DataSchema(columnNames, columnDataTypes); - + _nullBitmaps = _nullHandlingEnabled ? new RoaringBitmap[numExpressions] : null; _limit = queryContext.getLimit(); } @@ -79,23 +89,38 @@ protected SelectionResultsBlock getNextBlock() { // Already returned enough documents return null; } - TransformBlock transformBlock = _transformOperator.nextBlock(); - if (transformBlock == null) { + ValueBlock valueBlock = _projectOperator.nextBlock(); + if (valueBlock == null) { return null; } int numExpressions = _expressions.size(); for (int i = 0; i < numExpressions; i++) { - _blockValSets[i] = transformBlock.getBlockValueSet(_expressions.get(i)); + _blockValSets[i] = valueBlock.getBlockValueSet(_expressions.get(i)); } RowBasedBlockValueFetcher blockValueFetcher = new RowBasedBlockValueFetcher(_blockValSets); - int numDocs = transformBlock.getNumDocs(); + int numDocs = valueBlock.getNumDocs(); int numDocsToReturn = Math.min(_limit - _numDocsScanned, numDocs); List rows = new ArrayList<>(numDocsToReturn); - for (int i = 0; i < numDocsToReturn; i++) { - rows.add(blockValueFetcher.getRow(i)); + if (_nullHandlingEnabled) { + for (int i = 0; i < numExpressions; i++) { + _nullBitmaps[i] = _blockValSets[i].getNullBitmap(); + } + for (int docId = 0; docId < numDocsToReturn; docId++) { + Object[] values = blockValueFetcher.getRow(docId); + for (int colId = 0; colId < numExpressions; colId++) { + if (_nullBitmaps[colId] != null && _nullBitmaps[colId].contains(docId)) { + values[colId] = null; + } + } + rows.add(values); + } + } else { + for (int i = 0; i < numDocsToReturn; i++) { + rows.add(blockValueFetcher.getRow(i)); + } } _numDocsScanned += numDocs; - return new SelectionResultsBlock(_dataSchema, rows); + return new SelectionResultsBlock(_dataSchema, rows, _queryContext); } @Override @@ -104,14 +129,14 @@ public String toExplainString() { } @Override - public List getChildOperators() { - return Collections.singletonList(_transformOperator); + public List> getChildOperators() { + return Collections.singletonList(_projectOperator); } @Override public ExecutionStatistics getExecutionStatistics() { - long numEntriesScannedInFilter = _transformOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); - long numEntriesScannedPostFilter = (long) _numDocsScanned * _transformOperator.getNumColumnsProjected(); + long numEntriesScannedInFilter = _projectOperator.getExecutionStatistics().getNumEntriesScannedInFilter(); + long numEntriesScannedPostFilter = (long) _numDocsScanned * _projectOperator.getNumColumnsProjected(); int numTotalDocs = _indexSegment.getSegmentMetadata().getTotalDocs(); return new ExecutionStatistics(_numDocsScanned, numEntriesScannedInFilter, numEntriesScannedPostFilter, numTotalDocs); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingSelectionOrderByCombineOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingSelectionOrderByCombineOperator.java new file mode 100644 index 000000000000..064b2ebf3aa5 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/streaming/StreamingSelectionOrderByCombineOperator.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.streaming; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import org.apache.pinot.core.common.Operator; +import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; +import org.apache.pinot.core.operator.combine.merger.SelectionOrderByResultsBlockMerger; +import org.apache.pinot.core.query.request.context.QueryContext; + + +/** + * Combine operator for selection queries with order-by, with streaming response. + */ +@SuppressWarnings("rawtypes") +public class StreamingSelectionOrderByCombineOperator extends BaseStreamingCombineOperator { + private static final String EXPLAIN_NAME = "STREAMING_COMBINE_SELECT_ORDERBY"; + + public StreamingSelectionOrderByCombineOperator(List operators, QueryContext queryContext, + ExecutorService executorService) { + super(new SelectionOrderByResultsBlockMerger(queryContext), operators, queryContext, executorService); + } + + @Override + public String toExplainString() { + return EXPLAIN_NAME; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/PassThroughTransformOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/PassThroughTransformOperator.java deleted file mode 100644 index 75991bf7eaa7..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/PassThroughTransformOperator.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.operator.transform; - -import java.util.Collection; -import org.apache.pinot.common.request.context.ExpressionContext; -import org.apache.pinot.core.operator.ProjectionOperator; -import org.apache.pinot.core.operator.blocks.PassThroughTransformBlock; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; - - -/** - * Class for evaluating pass through transform expressions. - */ -public class PassThroughTransformOperator extends TransformOperator { - - private static final String EXPLAIN_NAME = "TRANSFORM_PASSTHROUGH"; - - /** - * Constructor for the class - * - * @param projectionOperator Projection operator - * @param expressions Collection of expressions to evaluate - */ - public PassThroughTransformOperator(ProjectionOperator projectionOperator, - Collection expressions) { - super(projectionOperator, expressions); - } - - @Override - protected PassThroughTransformBlock getNextBlock() { - ProjectionBlock projectionBlock = _projectionOperator.nextBlock(); - if (projectionBlock == null) { - return null; - } else { - return new PassThroughTransformBlock(projectionBlock, _transformFunctionMap); - } - } - - - @Override - public String toExplainString() { - return toExplainString(EXPLAIN_NAME); - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/TransformOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/TransformOperator.java index 9f52fe839d99..90576a4893c4 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/TransformOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/TransformOperator.java @@ -18,133 +18,84 @@ */ package org.apache.pinot.core.operator.transform; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.annotation.Nullable; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; import org.apache.pinot.common.request.context.ExpressionContext; -import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.BaseOperator; +import org.apache.pinot.common.utils.HashUtil; +import org.apache.pinot.core.operator.BaseProjectOperator; +import org.apache.pinot.core.operator.ColumnContext; import org.apache.pinot.core.operator.ExecutionStatistics; -import org.apache.pinot.core.operator.ProjectionOperator; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.function.TransformFunction; import org.apache.pinot.core.operator.transform.function.TransformFunctionFactory; import org.apache.pinot.core.query.request.context.QueryContext; -import org.apache.pinot.segment.spi.datasource.DataSource; -import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.spi.trace.Tracing; /** * Class for evaluating transform expressions. */ -public class TransformOperator extends BaseOperator { +public class TransformOperator extends BaseProjectOperator { private static final String EXPLAIN_NAME = "TRANSFORM"; - protected final ProjectionOperator _projectionOperator; - protected final Map _dataSourceMap; - protected final Map _transformFunctionMap = new HashMap<>(); + private final BaseProjectOperator _projectOperator; + private final Map _transformFunctionMap; - /** - * - * @param queryContext the query context - * @param projectionOperator Projection operator - * @param expressions Collection of expressions to evaluate - */ - public TransformOperator(@Nullable QueryContext queryContext, ProjectionOperator projectionOperator, + public TransformOperator(QueryContext queryContext, BaseProjectOperator projectOperator, Collection expressions) { - _projectionOperator = projectionOperator; - _dataSourceMap = projectionOperator.getDataSourceMap(); + _projectOperator = projectOperator; + _transformFunctionMap = new HashMap<>(HashUtil.getHashMapCapacity(expressions.size())); for (ExpressionContext expression : expressions) { - TransformFunction transformFunction = TransformFunctionFactory.get(queryContext, expression, _dataSourceMap); + TransformFunction transformFunction = + TransformFunctionFactory.get(expression, projectOperator.getSourceColumnContextMap(), queryContext); _transformFunctionMap.put(expression, transformFunction); } } - /** - * - * @param projectionOperator Projection operator - * @param expressions Collection of expressions to evaluate - */ - public TransformOperator(ProjectionOperator projectionOperator, Collection expressions) { - this(null, projectionOperator, expressions); - } - - /** - * Returns the number of columns projected. - * - * @return Number of columns projected - */ - public int getNumColumnsProjected() { - return _dataSourceMap.size(); - } - - /** - * Returns the transform result metadata associated with the given expression. - * - * @param expression Expression - * @return Transform result metadata - */ - public TransformResultMetadata getResultMetadata(ExpressionContext expression) { - return _transformFunctionMap.get(expression).getResultMetadata(); + @Override + public Map getSourceColumnContextMap() { + return _projectOperator.getSourceColumnContextMap(); } - /** - * Returns the dictionary associated with the given expression if the transform result is dictionary-encoded, or - * {@code null} if not. - * - * @return Dictionary - */ - public Dictionary getDictionary(ExpressionContext expression) { - return _transformFunctionMap.get(expression).getDictionary(); + @Override + public ColumnContext getResultColumnContext(ExpressionContext expression) { + return ColumnContext.fromTransformFunction(_transformFunctionMap.get(expression)); } @Override protected TransformBlock getNextBlock() { - ProjectionBlock projectionBlock = _projectionOperator.nextBlock(); - if (projectionBlock == null) { + ValueBlock sourceBlock = _projectOperator.nextBlock(); + if (sourceBlock == null) { return null; - } else { - Tracing.activeRecording().setNumChildren(_dataSourceMap.size()); - return new TransformBlock(projectionBlock, _transformFunctionMap); } + Tracing.activeRecording().setNumChildren(_transformFunctionMap.size()); + return new TransformBlock(sourceBlock, _transformFunctionMap); } - @Override public String toExplainString() { - return toExplainString(EXPLAIN_NAME); + List expressions = + _transformFunctionMap.keySet().stream().map(ExpressionContext::toString).sorted().collect(Collectors.toList()); + return getExplainName() + "(" + StringUtils.join(expressions, ", ") + ")"; } - public String toExplainString(String explainName) { - ExpressionContext[] functions = _transformFunctionMap.keySet().toArray(new ExpressionContext[0]); - - // Sort to make the order, in which names appear within the operator, deterministic. - Arrays.sort(functions, Comparator.comparing(ExpressionContext::toString)); - - StringBuilder stringBuilder = new StringBuilder(explainName).append("("); - if (functions != null && functions.length > 0) { - stringBuilder.append(functions[0].toString()); - for (int i = 1; i < functions.length; i++) { - stringBuilder.append(", ").append(functions[i].toString()); - } - } - return stringBuilder.append(')').toString(); + protected String getExplainName() { + return EXPLAIN_NAME; } @Override - public List getChildOperators() { - return Collections.singletonList(_projectionOperator); + public List> getChildOperators() { + return Collections.singletonList(_projectOperator); } @Override public ExecutionStatistics getExecutionStatistics() { - return _projectionOperator.getExecutionStatistics(); + return _projectOperator.getExecutionStatistics(); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/AdditionTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/AdditionTransformFunction.java index 5cd0a3ab7416..101afec48f10 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/AdditionTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/AdditionTransformFunction.java @@ -23,9 +23,9 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.utils.ArrayCopyUtils; @@ -44,7 +44,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); // Check that there are more than 1 arguments if (arguments.size() < 2) { throw new IllegalArgumentException("At least 2 arguments are required for ADD transform function"); @@ -56,10 +57,10 @@ public void init(List arguments, Map data LiteralTransformFunction literalTransformFunction = (LiteralTransformFunction) argument; DataType dataType = literalTransformFunction.getResultMetadata().getDataType(); if (dataType == DataType.BIG_DECIMAL) { - _literalBigDecimalSum = _literalBigDecimalSum.add(new BigDecimal(literalTransformFunction.getLiteral())); + _literalBigDecimalSum = _literalBigDecimalSum.add(literalTransformFunction.getBigDecimalLiteral()); _resultDataType = DataType.BIG_DECIMAL; } else { - _literalDoubleSum += Double.parseDouble(((LiteralTransformFunction) argument).getLiteral()); + _literalDoubleSum += ((LiteralTransformFunction) argument).getDoubleLiteral(); } } else { if (!argument.getResultMetadata().isSingleValue()) { @@ -85,18 +86,16 @@ public TransformResultMetadata getResultMetadata() { } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_doubleValuesSV == null) { - _doubleValuesSV = new double[length]; - } + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initDoubleValuesSV(length); if (_resultDataType == DataType.BIG_DECIMAL) { - BigDecimal[] values = transformToBigDecimalValuesSV(projectionBlock); + BigDecimal[] values = transformToBigDecimalValuesSV(valueBlock); ArrayCopyUtils.copy(values, _doubleValuesSV, length); } else { Arrays.fill(_doubleValuesSV, 0, length, _literalDoubleSum); for (TransformFunction transformFunction : _transformFunctions) { - double[] values = transformFunction.transformToDoubleValuesSV(projectionBlock); + double[] values = transformFunction.transformToDoubleValuesSV(valueBlock); for (int i = 0; i < length; i++) { _doubleValuesSV[i] += values[i]; } @@ -106,18 +105,16 @@ public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { } @Override - public BigDecimal[] transformToBigDecimalValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_bigDecimalValuesSV == null) { - _bigDecimalValuesSV = new BigDecimal[length]; - } + public BigDecimal[] transformToBigDecimalValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initBigDecimalValuesSV(length); if (_resultDataType == DataType.DOUBLE) { - double[] values = transformToDoubleValuesSV(projectionBlock); + double[] values = transformToDoubleValuesSV(valueBlock); ArrayCopyUtils.copy(values, _bigDecimalValuesSV, length); } else { Arrays.fill(_bigDecimalValuesSV, 0, length, _literalBigDecimalSum); for (TransformFunction transformFunction : _transformFunctions) { - BigDecimal[] values = transformFunction.transformToBigDecimalValuesSV(projectionBlock); + BigDecimal[] values = transformFunction.transformToBigDecimalValuesSV(valueBlock); for (int i = 0; i < length; i++) { _bigDecimalValuesSV[i] = _bigDecimalValuesSV[i].add(values[i]); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/AndOperatorTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/AndOperatorTransformFunction.java index 2ac1035f8694..4a60b941bc0e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/AndOperatorTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/AndOperatorTransformFunction.java @@ -45,4 +45,9 @@ int getLogicalFuncResult(int left, int right) { } return 0; } + + @Override + boolean valueSupersedesNull(int i) { + return i == 0; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArrayAverageTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArrayAverageTransformFunction.java index 3767b423d80c..2ef7a5f6b40c 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArrayAverageTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArrayAverageTransformFunction.java @@ -20,9 +20,9 @@ import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; /** @@ -44,7 +44,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); // Check that there is only 1 argument if (arguments.size() != 1) { throw new IllegalArgumentException("Exactly 1 argument is required for ArrayAverage transform function"); @@ -68,14 +69,12 @@ public TransformResultMetadata getResultMetadata() { } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_doubleValuesSV == null) { - _doubleValuesSV = new double[length]; - } + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initDoubleValuesSV(length); switch (_argument.getResultMetadata().getDataType().getStoredType()) { case INT: - int[][] intValuesMV = _argument.transformToIntValuesMV(projectionBlock); + int[][] intValuesMV = _argument.transformToIntValuesMV(valueBlock); for (int i = 0; i < length; i++) { double sumRes = 0; for (int value : intValuesMV[i]) { @@ -85,7 +84,7 @@ public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { } break; case LONG: - long[][] longValuesMV = _argument.transformToLongValuesMV(projectionBlock); + long[][] longValuesMV = _argument.transformToLongValuesMV(valueBlock); for (int i = 0; i < length; i++) { double sumRes = 0; for (long value : longValuesMV[i]) { @@ -95,7 +94,7 @@ public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { } break; case FLOAT: - float[][] floatValuesMV = _argument.transformToFloatValuesMV(projectionBlock); + float[][] floatValuesMV = _argument.transformToFloatValuesMV(valueBlock); for (int i = 0; i < length; i++) { double sumRes = 0; for (float value : floatValuesMV[i]) { @@ -105,7 +104,7 @@ public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { } break; case DOUBLE: - double[][] doubleValuesMV = _argument.transformToDoubleValuesMV(projectionBlock); + double[][] doubleValuesMV = _argument.transformToDoubleValuesMV(valueBlock); for (int i = 0; i < length; i++) { double sumRes = 0; for (double value : doubleValuesMV[i]) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArrayLengthTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArrayLengthTransformFunction.java index 7cd8e4e1bdfe..8b65c42f268f 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArrayLengthTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArrayLengthTransformFunction.java @@ -20,9 +20,9 @@ import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; /** @@ -44,7 +44,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); // Check that there is only 1 argument if (arguments.size() != 1) { throw new IllegalArgumentException("Exactly 1 argument is required for ARRAYLENGTH transform function"); @@ -65,38 +66,36 @@ public TransformResultMetadata getResultMetadata() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_intValuesSV == null) { - _intValuesSV = new int[length]; - } + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initIntValuesSV(length); switch (_argument.getResultMetadata().getDataType().getStoredType()) { case INT: - int[][] intValuesMV = _argument.transformToIntValuesMV(projectionBlock); + int[][] intValuesMV = _argument.transformToIntValuesMV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = intValuesMV[i].length; } break; case LONG: - long[][] longValuesMV = _argument.transformToLongValuesMV(projectionBlock); + long[][] longValuesMV = _argument.transformToLongValuesMV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = longValuesMV[i].length; } break; case FLOAT: - float[][] floatValuesMV = _argument.transformToFloatValuesMV(projectionBlock); + float[][] floatValuesMV = _argument.transformToFloatValuesMV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = floatValuesMV[i].length; } break; case DOUBLE: - double[][] doubleValuesMV = _argument.transformToDoubleValuesMV(projectionBlock); + double[][] doubleValuesMV = _argument.transformToDoubleValuesMV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = doubleValuesMV[i].length; } break; case STRING: - String[][] stringValuesMV = _argument.transformToStringValuesMV(projectionBlock); + String[][] stringValuesMV = _argument.transformToStringValuesMV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = stringValuesMV[i].length; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArrayLiteralTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArrayLiteralTransformFunction.java new file mode 100644 index 000000000000..084d34bf2e0f --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArrayLiteralTransformFunction.java @@ -0,0 +1,501 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.transform.function; + +import com.google.common.base.Preconditions; +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.request.context.LiteralContext; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; +import org.apache.pinot.core.operator.transform.TransformResultMetadata; +import org.apache.pinot.segment.spi.index.reader.Dictionary; +import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.roaringbitmap.RoaringBitmap; + + +/** + * The LiteralTransformFunction class is a special transform function which is a wrapper on top of a + * LITERAL. The data type is inferred from the literal string. + */ +public class ArrayLiteralTransformFunction implements TransformFunction { + public static final String FUNCTION_NAME = "arrayValueConstructor"; + + private final DataType _dataType; + + private final int[] _intArrayLiteral; + private final long[] _longArrayLiteral; + private final float[] _floatArrayLiteral; + private final double[] _doubleArrayLiteral; + private final String[] _stringArrayLiteral; + + // literals may be shared but values are intentionally not volatile as assignment races are benign + private int[][] _intArrayResult; + private long[][] _longArrayResult; + private float[][] _floatArrayResult; + private double[][] _doubleArrayResult; + private String[][] _stringArrayResult; + + public ArrayLiteralTransformFunction(LiteralContext literalContext) { + _dataType = literalContext.getType(); + Object value = literalContext.getValue(); + if (value == null) { + _intArrayLiteral = new int[0]; + _longArrayLiteral = new long[0]; + _floatArrayLiteral = new float[0]; + _doubleArrayLiteral = new double[0]; + _stringArrayLiteral = new String[0]; + return; + } + switch (_dataType) { + case INT: + _intArrayLiteral = (int[]) value; + _longArrayLiteral = null; + _floatArrayLiteral = null; + _doubleArrayLiteral = null; + _stringArrayLiteral = null; + break; + case LONG: + _longArrayLiteral = (long[]) value; + _intArrayLiteral = null; + _floatArrayLiteral = null; + _doubleArrayLiteral = null; + _stringArrayLiteral = null; + break; + case FLOAT: + _floatArrayLiteral = (float[]) value; + _intArrayLiteral = null; + _longArrayLiteral = null; + _doubleArrayLiteral = null; + _stringArrayLiteral = null; + break; + case DOUBLE: + _doubleArrayLiteral = (double[]) value; + _intArrayLiteral = null; + _longArrayLiteral = null; + _floatArrayLiteral = null; + _stringArrayLiteral = null; + break; + case STRING: + _stringArrayLiteral = (String[]) value; + _intArrayLiteral = null; + _longArrayLiteral = null; + _floatArrayLiteral = null; + _doubleArrayLiteral = null; + break; + default: + throw new IllegalStateException( + "Illegal data type for ArrayLiteralTransformFunction: " + _dataType + ", literal context: " + + literalContext); + } + } + + public ArrayLiteralTransformFunction(List literalContexts) { + Preconditions.checkNotNull(literalContexts); + if (literalContexts.isEmpty()) { + _dataType = DataType.UNKNOWN; + _intArrayLiteral = new int[0]; + _longArrayLiteral = new long[0]; + _floatArrayLiteral = new float[0]; + _doubleArrayLiteral = new double[0]; + _stringArrayLiteral = new String[0]; + return; + } + for (ExpressionContext literalContext : literalContexts) { + Preconditions.checkState(literalContext.getType() == ExpressionContext.Type.LITERAL, + "ArrayLiteralTransformFunction only takes literals as arguments, found: %s", literalContext); + } + _dataType = literalContexts.get(0).getLiteral().getType(); + switch (_dataType) { + case INT: + _intArrayLiteral = new int[literalContexts.size()]; + for (int i = 0; i < _intArrayLiteral.length; i++) { + _intArrayLiteral[i] = literalContexts.get(i).getLiteral().getIntValue(); + } + _longArrayLiteral = null; + _floatArrayLiteral = null; + _doubleArrayLiteral = null; + _stringArrayLiteral = null; + break; + case LONG: + _longArrayLiteral = new long[literalContexts.size()]; + for (int i = 0; i < _longArrayLiteral.length; i++) { + _longArrayLiteral[i] = literalContexts.get(i).getLiteral().getLongValue(); + } + _intArrayLiteral = null; + _floatArrayLiteral = null; + _doubleArrayLiteral = null; + _stringArrayLiteral = null; + break; + case FLOAT: + _floatArrayLiteral = new float[literalContexts.size()]; + for (int i = 0; i < _floatArrayLiteral.length; i++) { + _floatArrayLiteral[i] = literalContexts.get(i).getLiteral().getFloatValue(); + } + _intArrayLiteral = null; + _longArrayLiteral = null; + _doubleArrayLiteral = null; + _stringArrayLiteral = null; + break; + case DOUBLE: + _doubleArrayLiteral = new double[literalContexts.size()]; + for (int i = 0; i < _doubleArrayLiteral.length; i++) { + _doubleArrayLiteral[i] = literalContexts.get(i).getLiteral().getDoubleValue(); + } + _intArrayLiteral = null; + _longArrayLiteral = null; + _floatArrayLiteral = null; + _stringArrayLiteral = null; + break; + case STRING: + _stringArrayLiteral = new String[literalContexts.size()]; + for (int i = 0; i < _stringArrayLiteral.length; i++) { + _stringArrayLiteral[i] = literalContexts.get(i).getLiteral().getStringValue(); + } + _intArrayLiteral = null; + _longArrayLiteral = null; + _floatArrayLiteral = null; + _doubleArrayLiteral = null; + break; + default: + throw new IllegalStateException( + "Illegal data type for ArrayLiteralTransformFunction: " + _dataType + ", literal contexts: " + + Arrays.toString(literalContexts.toArray())); + } + } + + public int[] getIntArrayLiteral() { + return _intArrayLiteral; + } + + public long[] getLongArrayLiteral() { + return _longArrayLiteral; + } + + public float[] getFloatArrayLiteral() { + return _floatArrayLiteral; + } + + public double[] getDoubleArrayLiteral() { + return _doubleArrayLiteral; + } + + public String[] getStringArrayLiteral() { + return _stringArrayLiteral; + } + + @Override + public String getName() { + return FUNCTION_NAME; + } + + @Override + public void init(List arguments, Map columnContextMap) { + } + + @Override + public TransformResultMetadata getResultMetadata() { + return new TransformResultMetadata(_dataType, false, false); + } + + @Override + public Dictionary getDictionary() { + return null; + } + + @Override + public int[] transformToDictIdsSV(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + @Override + public int[][] transformToDictIdsMV(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + @Override + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + @Override + public long[] transformToLongValuesSV(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + @Override + public float[] transformToFloatValuesSV(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + @Override + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + @Override + public BigDecimal[] transformToBigDecimalValuesSV(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + @Override + public String[] transformToStringValuesSV(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + @Override + public byte[][] transformToBytesValuesSV(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + @Override + public int[][] transformToIntValuesMV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + int[][] intArrayResult = _intArrayResult; + if (intArrayResult == null || intArrayResult.length < numDocs) { + intArrayResult = new int[numDocs][]; + int[] intArrayLiteral = _intArrayLiteral; + if (intArrayLiteral == null) { + switch (_dataType) { + case LONG: + intArrayLiteral = new int[_longArrayLiteral.length]; + for (int i = 0; i < _longArrayLiteral.length; i++) { + intArrayLiteral[i] = (int) _longArrayLiteral[i]; + } + break; + case FLOAT: + intArrayLiteral = new int[_floatArrayLiteral.length]; + for (int i = 0; i < _floatArrayLiteral.length; i++) { + intArrayLiteral[i] = (int) _floatArrayLiteral[i]; + } + break; + case DOUBLE: + intArrayLiteral = new int[_doubleArrayLiteral.length]; + for (int i = 0; i < _doubleArrayLiteral.length; i++) { + intArrayLiteral[i] = (int) _doubleArrayLiteral[i]; + } + break; + case STRING: + intArrayLiteral = new int[_stringArrayLiteral.length]; + for (int i = 0; i < _stringArrayLiteral.length; i++) { + intArrayLiteral[i] = Integer.parseInt(_stringArrayLiteral[i]); + } + break; + default: + throw new IllegalStateException("Unable to convert data type: " + _dataType + " to int array"); + } + } + Arrays.fill(intArrayResult, intArrayLiteral); + _intArrayResult = intArrayResult; + } + return intArrayResult; + } + + @Override + public long[][] transformToLongValuesMV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + long[][] longArrayResult = _longArrayResult; + if (longArrayResult == null || longArrayResult.length < numDocs) { + longArrayResult = new long[numDocs][]; + long[] longArrayLiteral = _longArrayLiteral; + if (longArrayLiteral == null) { + switch (_dataType) { + case INT: + longArrayLiteral = new long[_intArrayLiteral.length]; + for (int i = 0; i < _intArrayLiteral.length; i++) { + longArrayLiteral[i] = _intArrayLiteral[i]; + } + break; + case FLOAT: + longArrayLiteral = new long[_floatArrayLiteral.length]; + for (int i = 0; i < _floatArrayLiteral.length; i++) { + longArrayLiteral[i] = (long) _floatArrayLiteral[i]; + } + break; + case DOUBLE: + longArrayLiteral = new long[_doubleArrayLiteral.length]; + for (int i = 0; i < _doubleArrayLiteral.length; i++) { + longArrayLiteral[i] = (long) _doubleArrayLiteral[i]; + } + break; + case STRING: + longArrayLiteral = new long[_stringArrayLiteral.length]; + for (int i = 0; i < _stringArrayLiteral.length; i++) { + longArrayLiteral[i] = Long.parseLong(_stringArrayLiteral[i]); + } + break; + default: + throw new IllegalStateException("Unable to convert data type: " + _dataType + " to long array"); + } + } + Arrays.fill(longArrayResult, longArrayLiteral); + _longArrayResult = longArrayResult; + } + return longArrayResult; + } + + @Override + public float[][] transformToFloatValuesMV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + float[][] floatArrayResult = _floatArrayResult; + if (floatArrayResult == null || floatArrayResult.length < numDocs) { + floatArrayResult = new float[numDocs][]; + float[] floatArrayLiteral = _floatArrayLiteral; + if (floatArrayLiteral == null) { + switch (_dataType) { + case INT: + floatArrayLiteral = new float[_intArrayLiteral.length]; + for (int i = 0; i < _intArrayLiteral.length; i++) { + floatArrayLiteral[i] = _intArrayLiteral[i]; + } + break; + case LONG: + floatArrayLiteral = new float[_longArrayLiteral.length]; + for (int i = 0; i < _longArrayLiteral.length; i++) { + floatArrayLiteral[i] = _longArrayLiteral[i]; + } + break; + case DOUBLE: + floatArrayLiteral = new float[_doubleArrayLiteral.length]; + for (int i = 0; i < _doubleArrayLiteral.length; i++) { + floatArrayLiteral[i] = (float) _doubleArrayLiteral[i]; + } + break; + case STRING: + floatArrayLiteral = new float[_stringArrayLiteral.length]; + for (int i = 0; i < _stringArrayLiteral.length; i++) { + floatArrayLiteral[i] = Float.parseFloat(_stringArrayLiteral[i]); + } + break; + default: + throw new IllegalStateException("Unable to convert data type: " + _dataType + " to float array"); + } + } + Arrays.fill(floatArrayResult, floatArrayLiteral); + _floatArrayResult = floatArrayResult; + } + return floatArrayResult; + } + + @Override + public double[][] transformToDoubleValuesMV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + double[][] doubleArrayResult = _doubleArrayResult; + if (doubleArrayResult == null || doubleArrayResult.length < numDocs) { + doubleArrayResult = new double[numDocs][]; + double[] doubleArrayLiteral = _doubleArrayLiteral; + if (doubleArrayLiteral == null) { + switch (_dataType) { + case INT: + doubleArrayLiteral = new double[_intArrayLiteral.length]; + for (int i = 0; i < _intArrayLiteral.length; i++) { + doubleArrayLiteral[i] = _intArrayLiteral[i]; + } + break; + case LONG: + doubleArrayLiteral = new double[_longArrayLiteral.length]; + for (int i = 0; i < _longArrayLiteral.length; i++) { + doubleArrayLiteral[i] = _longArrayLiteral[i]; + } + break; + case FLOAT: + doubleArrayLiteral = new double[_floatArrayLiteral.length]; + for (int i = 0; i < _floatArrayLiteral.length; i++) { + doubleArrayLiteral[i] = _floatArrayLiteral[i]; + } + break; + case STRING: + doubleArrayLiteral = new double[_stringArrayLiteral.length]; + for (int i = 0; i < _stringArrayLiteral.length; i++) { + doubleArrayLiteral[i] = Double.parseDouble(_stringArrayLiteral[i]); + } + break; + default: + throw new IllegalStateException("Unable to convert data type: " + _dataType + " to double array"); + } + } + Arrays.fill(doubleArrayResult, doubleArrayLiteral); + _doubleArrayResult = doubleArrayResult; + } + return doubleArrayResult; + } + + @Override + public String[][] transformToStringValuesMV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + String[][] stringArrayResult = _stringArrayResult; + if (stringArrayResult == null || stringArrayResult.length < numDocs) { + stringArrayResult = new String[numDocs][]; + String[] stringArrayLiteral = _stringArrayLiteral; + if (stringArrayLiteral == null) { + switch (_dataType) { + case INT: + stringArrayLiteral = new String[_intArrayLiteral.length]; + for (int i = 0; i < _intArrayLiteral.length; i++) { + stringArrayLiteral[i] = Integer.toString(_intArrayLiteral[i]); + } + break; + case LONG: + stringArrayLiteral = new String[_longArrayLiteral.length]; + for (int i = 0; i < _longArrayLiteral.length; i++) { + stringArrayLiteral[i] = Long.toString(_longArrayLiteral[i]); + } + break; + case FLOAT: + stringArrayLiteral = new String[_floatArrayLiteral.length]; + for (int i = 0; i < _floatArrayLiteral.length; i++) { + stringArrayLiteral[i] = Float.toString(_floatArrayLiteral[i]); + } + break; + case DOUBLE: + stringArrayLiteral = new String[_doubleArrayLiteral.length]; + for (int i = 0; i < _doubleArrayLiteral.length; i++) { + stringArrayLiteral[i] = Double.toString(_doubleArrayLiteral[i]); + } + break; + default: + throw new IllegalStateException("Unable to convert data type: " + _dataType + " to string array"); + } + } + Arrays.fill(stringArrayResult, stringArrayLiteral); + _stringArrayResult = stringArrayResult; + } + return stringArrayResult; + } + + @Override + public byte[][][] transformToBytesValuesMV(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + @Override + public RoaringBitmap getNullBitmap(ValueBlock valueBlock) { + // Treat all unknown type values as null regardless of the value. + if (_dataType != DataType.UNKNOWN) { + return null; + } + int length = valueBlock.getNumDocs(); + RoaringBitmap bitmap = new RoaringBitmap(); + bitmap.add(0L, length); + return bitmap; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArrayMaxTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArrayMaxTransformFunction.java index bc831e70e2eb..68c95bce3ca5 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArrayMaxTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArrayMaxTransformFunction.java @@ -21,9 +21,9 @@ import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec.DataType; @@ -47,7 +47,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); // Check that there is only 1 argument if (arguments.size() != 1) { throw new IllegalArgumentException("Exactly 1 argument is required for ArrayMax transform function"); @@ -69,15 +70,13 @@ public TransformResultMetadata getResultMetadata() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { + public int[] transformToIntValuesSV(ValueBlock valueBlock) { if (_argument.getResultMetadata().getDataType().getStoredType() != DataType.INT) { - return super.transformToIntValuesSV(projectionBlock); + return super.transformToIntValuesSV(valueBlock); } - int length = projectionBlock.getNumDocs(); - if (_intValuesSV == null) { - _intValuesSV = new int[length]; - } - int[][] intValuesMV = _argument.transformToIntValuesMV(projectionBlock); + int length = valueBlock.getNumDocs(); + initIntValuesSV(length); + int[][] intValuesMV = _argument.transformToIntValuesMV(valueBlock); for (int i = 0; i < length; i++) { int maxRes = Integer.MIN_VALUE; for (int value : intValuesMV[i]) { @@ -89,15 +88,13 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { } @Override - public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { + public long[] transformToLongValuesSV(ValueBlock valueBlock) { if (_argument.getResultMetadata().getDataType().getStoredType() != DataType.LONG) { - return super.transformToLongValuesSV(projectionBlock); - } - int length = projectionBlock.getNumDocs(); - if (_longValuesSV == null) { - _longValuesSV = new long[length]; + return super.transformToLongValuesSV(valueBlock); } - long[][] longValuesMV = _argument.transformToLongValuesMV(projectionBlock); + int length = valueBlock.getNumDocs(); + initLongValuesSV(length); + long[][] longValuesMV = _argument.transformToLongValuesMV(valueBlock); for (int i = 0; i < length; i++) { long maxRes = Long.MIN_VALUE; for (long value : longValuesMV[i]) { @@ -109,15 +106,13 @@ public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { } @Override - public float[] transformToFloatValuesSV(ProjectionBlock projectionBlock) { + public float[] transformToFloatValuesSV(ValueBlock valueBlock) { if (_argument.getResultMetadata().getDataType().getStoredType() != DataType.FLOAT) { - return super.transformToFloatValuesSV(projectionBlock); - } - int length = projectionBlock.getNumDocs(); - if (_floatValuesSV == null) { - _floatValuesSV = new float[length]; + return super.transformToFloatValuesSV(valueBlock); } - float[][] floatValuesMV = _argument.transformToFloatValuesMV(projectionBlock); + int length = valueBlock.getNumDocs(); + initFloatValuesSV(length); + float[][] floatValuesMV = _argument.transformToFloatValuesMV(valueBlock); for (int i = 0; i < length; i++) { float maxRes = Float.NEGATIVE_INFINITY; for (float value : floatValuesMV[i]) { @@ -129,15 +124,13 @@ public float[] transformToFloatValuesSV(ProjectionBlock projectionBlock) { } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { if (_argument.getResultMetadata().getDataType().getStoredType() != DataType.DOUBLE) { - return super.transformToDoubleValuesSV(projectionBlock); + return super.transformToDoubleValuesSV(valueBlock); } - int length = projectionBlock.getNumDocs(); - if (_doubleValuesSV == null) { - _doubleValuesSV = new double[length]; - } - double[][] doubleValuesMV = _argument.transformToDoubleValuesMV(projectionBlock); + int length = valueBlock.getNumDocs(); + initDoubleValuesSV(length); + double[][] doubleValuesMV = _argument.transformToDoubleValuesMV(valueBlock); for (int i = 0; i < length; i++) { double maxRes = Double.NEGATIVE_INFINITY; for (double value : doubleValuesMV[i]) { @@ -149,15 +142,13 @@ public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { } @Override - public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { + public String[] transformToStringValuesSV(ValueBlock valueBlock) { if (_argument.getResultMetadata().getDataType().getStoredType() != DataType.STRING) { - return super.transformToStringValuesSV(projectionBlock); - } - int length = projectionBlock.getNumDocs(); - if (_stringValuesSV == null) { - _stringValuesSV = new String[length]; + return super.transformToStringValuesSV(valueBlock); } - String[][] stringValuesMV = _argument.transformToStringValuesMV(projectionBlock); + int length = valueBlock.getNumDocs(); + initStringValuesSV(length); + String[][] stringValuesMV = _argument.transformToStringValuesMV(valueBlock); for (int i = 0; i < length; i++) { String maxRes = null; for (String value : stringValuesMV[i]) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArrayMinTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArrayMinTransformFunction.java index 1dca59c6bdfe..bf0018753cc4 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArrayMinTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArrayMinTransformFunction.java @@ -21,9 +21,9 @@ import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec.DataType; @@ -47,7 +47,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); // Check that there is only 1 argument if (arguments.size() != 1) { throw new IllegalArgumentException("Exactly 1 argument is required for ArrayMin transform function"); @@ -69,15 +70,13 @@ public TransformResultMetadata getResultMetadata() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { + public int[] transformToIntValuesSV(ValueBlock valueBlock) { if (_argument.getResultMetadata().getDataType().getStoredType() != DataType.INT) { - return super.transformToIntValuesSV(projectionBlock); + return super.transformToIntValuesSV(valueBlock); } - int length = projectionBlock.getNumDocs(); - if (_intValuesSV == null) { - _intValuesSV = new int[length]; - } - int[][] intValuesMV = _argument.transformToIntValuesMV(projectionBlock); + int length = valueBlock.getNumDocs(); + initIntValuesSV(length); + int[][] intValuesMV = _argument.transformToIntValuesMV(valueBlock); for (int i = 0; i < length; i++) { int minRes = Integer.MAX_VALUE; for (int value : intValuesMV[i]) { @@ -89,15 +88,13 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { } @Override - public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { + public long[] transformToLongValuesSV(ValueBlock valueBlock) { if (_argument.getResultMetadata().getDataType().getStoredType() != DataType.LONG) { - return super.transformToLongValuesSV(projectionBlock); - } - int length = projectionBlock.getNumDocs(); - if (_longValuesSV == null) { - _longValuesSV = new long[length]; + return super.transformToLongValuesSV(valueBlock); } - long[][] longValuesMV = _argument.transformToLongValuesMV(projectionBlock); + int length = valueBlock.getNumDocs(); + initLongValuesSV(length); + long[][] longValuesMV = _argument.transformToLongValuesMV(valueBlock); for (int i = 0; i < length; i++) { long minRes = Long.MAX_VALUE; for (long value : longValuesMV[i]) { @@ -109,15 +106,13 @@ public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { } @Override - public float[] transformToFloatValuesSV(ProjectionBlock projectionBlock) { + public float[] transformToFloatValuesSV(ValueBlock valueBlock) { if (_argument.getResultMetadata().getDataType().getStoredType() != DataType.FLOAT) { - return super.transformToFloatValuesSV(projectionBlock); - } - int length = projectionBlock.getNumDocs(); - if (_floatValuesSV == null) { - _floatValuesSV = new float[length]; + return super.transformToFloatValuesSV(valueBlock); } - float[][] floatValuesMV = _argument.transformToFloatValuesMV(projectionBlock); + int length = valueBlock.getNumDocs(); + initFloatValuesSV(length); + float[][] floatValuesMV = _argument.transformToFloatValuesMV(valueBlock); for (int i = 0; i < length; i++) { float minRes = Float.POSITIVE_INFINITY; for (float value : floatValuesMV[i]) { @@ -129,15 +124,13 @@ public float[] transformToFloatValuesSV(ProjectionBlock projectionBlock) { } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { if (_argument.getResultMetadata().getDataType().getStoredType() != DataType.DOUBLE) { - return super.transformToDoubleValuesSV(projectionBlock); + return super.transformToDoubleValuesSV(valueBlock); } - int length = projectionBlock.getNumDocs(); - if (_doubleValuesSV == null) { - _doubleValuesSV = new double[length]; - } - double[][] doubleValuesMV = _argument.transformToDoubleValuesMV(projectionBlock); + int length = valueBlock.getNumDocs(); + initDoubleValuesSV(length); + double[][] doubleValuesMV = _argument.transformToDoubleValuesMV(valueBlock); for (int i = 0; i < length; i++) { double minRes = Double.POSITIVE_INFINITY; for (double value : doubleValuesMV[i]) { @@ -149,15 +142,13 @@ public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { } @Override - public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { + public String[] transformToStringValuesSV(ValueBlock valueBlock) { if (_argument.getResultMetadata().getDataType().getStoredType() != DataType.STRING) { - return super.transformToStringValuesSV(projectionBlock); - } - int length = projectionBlock.getNumDocs(); - if (_stringValuesSV == null) { - _stringValuesSV = new String[length]; + return super.transformToStringValuesSV(valueBlock); } - String[][] stringValuesMV = _argument.transformToStringValuesMV(projectionBlock); + int length = valueBlock.getNumDocs(); + initStringValuesSV(length); + String[][] stringValuesMV = _argument.transformToStringValuesMV(valueBlock); for (int i = 0; i < length; i++) { String minRes = null; for (String value : stringValuesMV[i]) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArraySumTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArraySumTransformFunction.java index 143cce2204f8..3ad7c1b7bce0 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArraySumTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ArraySumTransformFunction.java @@ -20,9 +20,9 @@ import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; /** @@ -44,7 +44,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); // Check that there is only 1 argument if (arguments.size() != 1) { throw new IllegalArgumentException("Exactly 1 argument is required for ArraySum transform function"); @@ -68,12 +69,10 @@ public TransformResultMetadata getResultMetadata() { } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_doubleValuesSV == null) { - _doubleValuesSV = new double[length]; - } - double[][] doubleValuesMV = _argument.transformToDoubleValuesMV(projectionBlock); + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initDoubleValuesSV(length); + double[][] doubleValuesMV = _argument.transformToDoubleValuesMV(valueBlock); for (int i = 0; i < length; i++) { double sumRes = 0; for (double value : doubleValuesMV[i]) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/BaseBooleanAssertionTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/BaseBooleanAssertionTransformFunction.java new file mode 100644 index 000000000000..972214990389 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/BaseBooleanAssertionTransformFunction.java @@ -0,0 +1,84 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.transform.function; + +import com.google.common.base.Preconditions; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; +import org.apache.pinot.core.operator.transform.TransformResultMetadata; +import org.roaringbitmap.RoaringBitmap; + + +public abstract class BaseBooleanAssertionTransformFunction extends BaseTransformFunction { + private TransformFunction _transformFunction; + + @Override + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); + Preconditions.checkArgument(arguments.size() == 1, "Exact 1 argument is required for " + getName()); + _transformFunction = arguments.get(0); + } + + @Override + public TransformResultMetadata getResultMetadata() { + return BOOLEAN_SV_NO_DICTIONARY_METADATA; + } + + @Override + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initIntValuesSV(length); + Arrays.fill(_intValuesSV, 0); + int[] intValuesSV = _transformFunction.transformToIntValuesSV(valueBlock); + RoaringBitmap nullBitmap = null; + if (_nullHandlingEnabled) { + nullBitmap = _transformFunction.getNullBitmap(valueBlock); + } + if (nullBitmap != null) { + for (int docId = 0; docId < length; docId++) { + if (nullBitmap.contains(docId)) { + if (returnsTrueWhenValueIsNull()) { + _intValuesSV[docId] = 1; + } + } else if (valueEvaluatesToTrue(intValuesSV[docId])) { + _intValuesSV[docId] = 1; + } + } + } else { + for (int docId = 0; docId < length; docId++) { + if (valueEvaluatesToTrue(intValuesSV[docId])) { + _intValuesSV[docId] = 1; + } + } + } + return _intValuesSV; + } + + protected abstract boolean returnsTrueWhenValueIsNull(); + + protected abstract boolean valueEvaluatesToTrue(int value); + + @Override + public RoaringBitmap getNullBitmap(ValueBlock valueBlock) { + return null; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/BaseTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/BaseTransformFunction.java index b40f1c6a9bad..e2223ec392e7 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/BaseTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/BaseTransformFunction.java @@ -19,11 +19,18 @@ package org.apache.pinot.core.operator.transform.function; import java.math.BigDecimal; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.utils.ArrayCopyUtils; +import org.apache.pinot.spi.utils.CommonConstants.NullValuePlaceHolder; +import org.roaringbitmap.RoaringBitmap; /** @@ -70,9 +77,10 @@ public abstract class BaseTransformFunction implements TransformFunction { new TransformResultMetadata(DataType.STRING, false, false); protected static final TransformResultMetadata JSON_MV_NO_DICTIONARY_METADATA = new TransformResultMetadata(DataType.JSON, false, false); - // TODO: Support MV BYTES protected static final TransformResultMetadata BYTES_MV_NO_DICTIONARY_METADATA = new TransformResultMetadata(DataType.BYTES, false, false); + protected static final TransformResultMetadata UNKNOWN_METADATA = + new TransformResultMetadata(DataType.UNKNOWN, true, false); // These buffers are used to hold the result for different result types. When the subclass overrides a method, it can // reuse the buffer for that method. E.g. if transformToIntValuesSV is overridden, the result can be written into @@ -91,54 +99,94 @@ public abstract class BaseTransformFunction implements TransformFunction { protected String[][] _stringValuesMV; protected byte[][][] _bytesValuesMV; + protected List _arguments; + protected boolean _nullHandlingEnabled; + + protected void fillResultUnknown(int length) { + for (int i = 0; i < length; i++) { + _intValuesSV[i] = NullValuePlaceHolder.INT; + } + } + + // NOTE: this init has to be called for default getNullBitmap() implementation to be effective. + @Override + public void init(List arguments, Map columnContextMap) { + _arguments = arguments; + } + + @Override + public void init(List arguments, Map columnContextMap, + boolean nullHandlingEnabled) { + init(arguments, columnContextMap); + _nullHandlingEnabled = nullHandlingEnabled; + } + @Override public Dictionary getDictionary() { return null; } @Override - public int[] transformToDictIdsSV(ProjectionBlock projectionBlock) { + public int[] transformToDictIdsSV(ValueBlock valueBlock) { throw new UnsupportedOperationException(); } @Override - public int[][] transformToDictIdsMV(ProjectionBlock projectionBlock) { + public int[][] transformToDictIdsMV(ValueBlock valueBlock) { throw new UnsupportedOperationException(); } - @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_intValuesSV == null) { + protected void initIntValuesSV(int length) { + if (_intValuesSV == null || _intValuesSV.length < length) { _intValuesSV = new int[length]; } + } + + protected void initZeroFillingIntValuesSV(int length) { + if (_intValuesSV == null || _intValuesSV.length < length) { + _intValuesSV = new int[length]; + } else { + Arrays.fill(_intValuesSV, 0, length, 0); + } + } + + @Override + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initIntValuesSV(length); Dictionary dictionary = getDictionary(); if (dictionary != null) { - int[] dictIds = transformToDictIdsSV(projectionBlock); + int[] dictIds = transformToDictIdsSV(valueBlock); dictionary.readIntValues(dictIds, length, _intValuesSV); } else { DataType resultDataType = getResultMetadata().getDataType(); switch (resultDataType.getStoredType()) { case LONG: - long[] longValues = transformToLongValuesSV(projectionBlock); + long[] longValues = transformToLongValuesSV(valueBlock); ArrayCopyUtils.copy(longValues, _intValuesSV, length); break; case FLOAT: - float[] floatValues = transformToFloatValuesSV(projectionBlock); + float[] floatValues = transformToFloatValuesSV(valueBlock); ArrayCopyUtils.copy(floatValues, _intValuesSV, length); break; case DOUBLE: - double[] doubleValues = transformToDoubleValuesSV(projectionBlock); + double[] doubleValues = transformToDoubleValuesSV(valueBlock); ArrayCopyUtils.copy(doubleValues, _intValuesSV, length); break; case BIG_DECIMAL: - BigDecimal[] bigDecimalValues = transformToBigDecimalValuesSV(projectionBlock); + BigDecimal[] bigDecimalValues = transformToBigDecimalValuesSV(valueBlock); ArrayCopyUtils.copy(bigDecimalValues, _intValuesSV, length); break; case STRING: - String[] stringValues = transformToStringValuesSV(projectionBlock); + String[] stringValues = transformToStringValuesSV(valueBlock); ArrayCopyUtils.copy(stringValues, _intValuesSV, length); break; + case UNKNOWN: + // Copy the values to ensure behaviour consistency with non null-handling. + for (int i = 0; i < length; i++) { + _intValuesSV[i] = NullValuePlaceHolder.INT; + } + break; default: throw new IllegalStateException(String.format("Cannot read SV %s as INT", resultDataType)); } @@ -146,39 +194,49 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { return _intValuesSV; } - @Override - public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_longValuesSV == null) { + protected void initLongValuesSV(int length) { + if (_longValuesSV == null || _longValuesSV.length < length) { _longValuesSV = new long[length]; } + } + + @Override + public long[] transformToLongValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initLongValuesSV(length); Dictionary dictionary = getDictionary(); if (dictionary != null) { - int[] dictIds = transformToDictIdsSV(projectionBlock); + int[] dictIds = transformToDictIdsSV(valueBlock); dictionary.readLongValues(dictIds, length, _longValuesSV); } else { DataType resultDataType = getResultMetadata().getDataType(); switch (resultDataType.getStoredType()) { case INT: - int[] intValues = transformToIntValuesSV(projectionBlock); + int[] intValues = transformToIntValuesSV(valueBlock); ArrayCopyUtils.copy(intValues, _longValuesSV, length); break; case FLOAT: - float[] floatValues = transformToFloatValuesSV(projectionBlock); + float[] floatValues = transformToFloatValuesSV(valueBlock); ArrayCopyUtils.copy(floatValues, _longValuesSV, length); break; case DOUBLE: - double[] doubleValues = transformToDoubleValuesSV(projectionBlock); + double[] doubleValues = transformToDoubleValuesSV(valueBlock); ArrayCopyUtils.copy(doubleValues, _longValuesSV, length); break; case BIG_DECIMAL: - BigDecimal[] bigDecimalValues = transformToBigDecimalValuesSV(projectionBlock); + BigDecimal[] bigDecimalValues = transformToBigDecimalValuesSV(valueBlock); ArrayCopyUtils.copy(bigDecimalValues, _longValuesSV, length); break; case STRING: - String[] stringValues = transformToStringValuesSV(projectionBlock); + String[] stringValues = transformToStringValuesSV(valueBlock); ArrayCopyUtils.copy(stringValues, _longValuesSV, length); break; + case UNKNOWN: + // Copy the values to ensure behaviour consistency with non null-handling. + for (int i = 0; i < length; i++) { + _longValuesSV[i] = NullValuePlaceHolder.LONG; + } + break; default: throw new IllegalStateException(String.format("Cannot read SV %s as LONG", resultDataType)); } @@ -186,39 +244,49 @@ public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { return _longValuesSV; } - @Override - public float[] transformToFloatValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_floatValuesSV == null) { + protected void initFloatValuesSV(int length) { + if (_floatValuesSV == null || _floatValuesSV.length < length) { _floatValuesSV = new float[length]; } + } + + @Override + public float[] transformToFloatValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initFloatValuesSV(length); Dictionary dictionary = getDictionary(); if (dictionary != null) { - int[] dictIds = transformToDictIdsSV(projectionBlock); + int[] dictIds = transformToDictIdsSV(valueBlock); dictionary.readFloatValues(dictIds, length, _floatValuesSV); } else { DataType resultDataType = getResultMetadata().getDataType(); switch (resultDataType.getStoredType()) { case INT: - int[] intValues = transformToIntValuesSV(projectionBlock); + int[] intValues = transformToIntValuesSV(valueBlock); ArrayCopyUtils.copy(intValues, _floatValuesSV, length); break; case LONG: - long[] longValues = transformToLongValuesSV(projectionBlock); + long[] longValues = transformToLongValuesSV(valueBlock); ArrayCopyUtils.copy(longValues, _floatValuesSV, length); break; case DOUBLE: - double[] doubleValues = transformToDoubleValuesSV(projectionBlock); + double[] doubleValues = transformToDoubleValuesSV(valueBlock); ArrayCopyUtils.copy(doubleValues, _floatValuesSV, length); break; case BIG_DECIMAL: - BigDecimal[] bigDecimalValues = transformToBigDecimalValuesSV(projectionBlock); + BigDecimal[] bigDecimalValues = transformToBigDecimalValuesSV(valueBlock); ArrayCopyUtils.copy(bigDecimalValues, _floatValuesSV, length); break; case STRING: - String[] stringValues = transformToStringValuesSV(projectionBlock); + String[] stringValues = transformToStringValuesSV(valueBlock); ArrayCopyUtils.copy(stringValues, _floatValuesSV, length); break; + case UNKNOWN: + // Copy the values to ensure behaviour consistency with non null-handling. + for (int i = 0; i < length; i++) { + _floatValuesSV[i] = NullValuePlaceHolder.FLOAT; + } + break; default: throw new IllegalStateException(String.format("Cannot read SV %s as FLOAT", resultDataType)); } @@ -226,39 +294,49 @@ public float[] transformToFloatValuesSV(ProjectionBlock projectionBlock) { return _floatValuesSV; } - @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_doubleValuesSV == null) { + protected void initDoubleValuesSV(int length) { + if (_doubleValuesSV == null || _doubleValuesSV.length < length) { _doubleValuesSV = new double[length]; } + } + + @Override + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initDoubleValuesSV(length); Dictionary dictionary = getDictionary(); if (dictionary != null) { - int[] dictIds = transformToDictIdsSV(projectionBlock); + int[] dictIds = transformToDictIdsSV(valueBlock); dictionary.readDoubleValues(dictIds, length, _doubleValuesSV); } else { DataType resultDataType = getResultMetadata().getDataType(); switch (resultDataType.getStoredType()) { case INT: - int[] intValues = transformToIntValuesSV(projectionBlock); + int[] intValues = transformToIntValuesSV(valueBlock); ArrayCopyUtils.copy(intValues, _doubleValuesSV, length); break; case LONG: - long[] longValues = transformToLongValuesSV(projectionBlock); + long[] longValues = transformToLongValuesSV(valueBlock); ArrayCopyUtils.copy(longValues, _doubleValuesSV, length); break; case FLOAT: - float[] floatValues = transformToFloatValuesSV(projectionBlock); + float[] floatValues = transformToFloatValuesSV(valueBlock); ArrayCopyUtils.copy(floatValues, _doubleValuesSV, length); break; case BIG_DECIMAL: - BigDecimal[] bigDecimalValues = transformToBigDecimalValuesSV(projectionBlock); + BigDecimal[] bigDecimalValues = transformToBigDecimalValuesSV(valueBlock); ArrayCopyUtils.copy(bigDecimalValues, _doubleValuesSV, length); break; case STRING: - String[] stringValues = transformToStringValuesSV(projectionBlock); + String[] stringValues = transformToStringValuesSV(valueBlock); ArrayCopyUtils.copy(stringValues, _doubleValuesSV, length); break; + case UNKNOWN: + // Copy the values to ensure behaviour consistency with non null-handling. + for (int i = 0; i < length; i++) { + _doubleValuesSV[i] = NullValuePlaceHolder.DOUBLE; + } + break; default: throw new IllegalStateException(String.format("Cannot read SV %s as DOUBLE", resultDataType)); } @@ -266,43 +344,53 @@ public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { return _doubleValuesSV; } - @Override - public BigDecimal[] transformToBigDecimalValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_bigDecimalValuesSV == null) { + protected void initBigDecimalValuesSV(int length) { + if (_bigDecimalValuesSV == null || _bigDecimalValuesSV.length < length) { _bigDecimalValuesSV = new BigDecimal[length]; } + } + + @Override + public BigDecimal[] transformToBigDecimalValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initBigDecimalValuesSV(length); Dictionary dictionary = getDictionary(); if (dictionary != null) { - int[] dictIds = transformToDictIdsSV(projectionBlock); + int[] dictIds = transformToDictIdsSV(valueBlock); dictionary.readBigDecimalValues(dictIds, length, _bigDecimalValuesSV); } else { DataType resultDataType = getResultMetadata().getDataType(); switch (resultDataType.getStoredType()) { case INT: - int[] intValues = transformToIntValuesSV(projectionBlock); + int[] intValues = transformToIntValuesSV(valueBlock); ArrayCopyUtils.copy(intValues, _bigDecimalValuesSV, length); break; case LONG: - long[] longValues = transformToLongValuesSV(projectionBlock); + long[] longValues = transformToLongValuesSV(valueBlock); ArrayCopyUtils.copy(longValues, _bigDecimalValuesSV, length); break; case FLOAT: - float[] floatValues = transformToFloatValuesSV(projectionBlock); + float[] floatValues = transformToFloatValuesSV(valueBlock); ArrayCopyUtils.copy(floatValues, _bigDecimalValuesSV, length); break; case DOUBLE: - double[] doubleValues = transformToDoubleValuesSV(projectionBlock); + double[] doubleValues = transformToDoubleValuesSV(valueBlock); ArrayCopyUtils.copy(doubleValues, _bigDecimalValuesSV, length); break; case STRING: - String[] stringValues = transformToStringValuesSV(projectionBlock); + String[] stringValues = transformToStringValuesSV(valueBlock); ArrayCopyUtils.copy(stringValues, _bigDecimalValuesSV, length); break; case BYTES: - byte[][] bytesValues = transformToBytesValuesSV(projectionBlock); + byte[][] bytesValues = transformToBytesValuesSV(valueBlock); ArrayCopyUtils.copy(bytesValues, _bigDecimalValuesSV, length); break; + case UNKNOWN: + // Copy the values to ensure behaviour consistency with non null-handling. + for (int i = 0; i < length; i++) { + _bigDecimalValuesSV[i] = NullValuePlaceHolder.BIG_DECIMAL; + } + break; default: throw new IllegalStateException(String.format("Cannot read SV %s as BIG_DECIMAL", resultDataType)); } @@ -310,43 +398,53 @@ public BigDecimal[] transformToBigDecimalValuesSV(ProjectionBlock projectionBloc return _bigDecimalValuesSV; } - @Override - public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_stringValuesSV == null) { + protected void initStringValuesSV(int length) { + if (_stringValuesSV == null || _stringValuesSV.length < length) { _stringValuesSV = new String[length]; } + } + + @Override + public String[] transformToStringValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initStringValuesSV(length); Dictionary dictionary = getDictionary(); if (dictionary != null) { - int[] dictIds = transformToDictIdsSV(projectionBlock); + int[] dictIds = transformToDictIdsSV(valueBlock); dictionary.readStringValues(dictIds, length, _stringValuesSV); } else { DataType resultDataType = getResultMetadata().getDataType(); switch (resultDataType.getStoredType()) { case INT: - int[] intValues = transformToIntValuesSV(projectionBlock); + int[] intValues = transformToIntValuesSV(valueBlock); ArrayCopyUtils.copy(intValues, _stringValuesSV, length); break; case LONG: - long[] longValues = transformToLongValuesSV(projectionBlock); + long[] longValues = transformToLongValuesSV(valueBlock); ArrayCopyUtils.copy(longValues, _stringValuesSV, length); break; case FLOAT: - float[] floatValues = transformToFloatValuesSV(projectionBlock); + float[] floatValues = transformToFloatValuesSV(valueBlock); ArrayCopyUtils.copy(floatValues, _stringValuesSV, length); break; case DOUBLE: - double[] doubleValues = transformToDoubleValuesSV(projectionBlock); + double[] doubleValues = transformToDoubleValuesSV(valueBlock); ArrayCopyUtils.copy(doubleValues, _stringValuesSV, length); break; case BIG_DECIMAL: - BigDecimal[] bigDecimalValues = transformToBigDecimalValuesSV(projectionBlock); + BigDecimal[] bigDecimalValues = transformToBigDecimalValuesSV(valueBlock); ArrayCopyUtils.copy(bigDecimalValues, _stringValuesSV, length); break; case BYTES: - byte[][] bytesValues = transformToBytesValuesSV(projectionBlock); + byte[][] bytesValues = transformToBytesValuesSV(valueBlock); ArrayCopyUtils.copy(bytesValues, _stringValuesSV, length); break; + case UNKNOWN: + // Copy the values to ensure behaviour consistency with non null-handling. + for (int i = 0; i < length; i++) { + _stringValuesSV[i] = NullValuePlaceHolder.STRING; + } + break; default: throw new IllegalStateException(String.format("Cannot read SV %s as STRING", resultDataType)); } @@ -354,27 +452,37 @@ public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { return _stringValuesSV; } - @Override - public byte[][] transformToBytesValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_bytesValuesSV == null) { + protected void initBytesValuesSV(int length) { + if (_bytesValuesSV == null || _bytesValuesSV.length < length) { _bytesValuesSV = new byte[length][]; } + } + + @Override + public byte[][] transformToBytesValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initBytesValuesSV(length); Dictionary dictionary = getDictionary(); if (dictionary != null) { - int[] dictIds = transformToDictIdsSV(projectionBlock); + int[] dictIds = transformToDictIdsSV(valueBlock); dictionary.readBytesValues(dictIds, length, _bytesValuesSV); } else { DataType resultDataType = getResultMetadata().getDataType(); switch (resultDataType.getStoredType()) { case BIG_DECIMAL: - BigDecimal[] bigDecimalValues = transformToBigDecimalValuesSV(projectionBlock); + BigDecimal[] bigDecimalValues = transformToBigDecimalValuesSV(valueBlock); ArrayCopyUtils.copy(bigDecimalValues, _bytesValuesSV, length); break; case STRING: - String[] stringValues = transformToStringValuesSV(projectionBlock); + String[] stringValues = transformToStringValuesSV(valueBlock); ArrayCopyUtils.copy(stringValues, _bytesValuesSV, length); break; + case UNKNOWN: + // Copy the values to ensure behaviour consistency with non null-handling. + for (int i = 0; i < length; i++) { + _bytesValuesSV[i] = NullValuePlaceHolder.BYTES; + } + break; default: throw new IllegalStateException(String.format("Cannot read SV %s as BYTES", resultDataType)); } @@ -382,15 +490,19 @@ public byte[][] transformToBytesValuesSV(ProjectionBlock projectionBlock) { return _bytesValuesSV; } - @Override - public int[][] transformToIntValuesMV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_intValuesMV == null) { + protected void initIntValuesMV(int length) { + if (_intValuesMV == null || _intValuesMV.length < length) { _intValuesMV = new int[length][]; } + } + + @Override + public int[][] transformToIntValuesMV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initIntValuesMV(length); Dictionary dictionary = getDictionary(); if (dictionary != null) { - int[][] dictIdsMV = transformToDictIdsMV(projectionBlock); + int[][] dictIdsMV = transformToDictIdsMV(valueBlock); for (int i = 0; i < length; i++) { int[] dictIds = dictIdsMV[i]; int numValues = dictIds.length; @@ -402,21 +514,27 @@ public int[][] transformToIntValuesMV(ProjectionBlock projectionBlock) { DataType resultDataType = getResultMetadata().getDataType(); switch (resultDataType.getStoredType()) { case LONG: - long[][] longValuesMV = transformToLongValuesMV(projectionBlock); + long[][] longValuesMV = transformToLongValuesMV(valueBlock); ArrayCopyUtils.copy(longValuesMV, _intValuesMV, length); break; case FLOAT: - float[][] floatValuesMV = transformToFloatValuesMV(projectionBlock); + float[][] floatValuesMV = transformToFloatValuesMV(valueBlock); ArrayCopyUtils.copy(floatValuesMV, _intValuesMV, length); break; case DOUBLE: - double[][] doubleValuesMV = transformToDoubleValuesMV(projectionBlock); + double[][] doubleValuesMV = transformToDoubleValuesMV(valueBlock); ArrayCopyUtils.copy(doubleValuesMV, _intValuesMV, length); break; case STRING: - String[][] stringValuesMV = transformToStringValuesMV(projectionBlock); + String[][] stringValuesMV = transformToStringValuesMV(valueBlock); ArrayCopyUtils.copy(stringValuesMV, _intValuesMV, length); break; + case UNKNOWN: + // Copy the values to ensure behaviour consistency with non null-handling. + for (int i = 0; i < length; i++) { + _intValuesMV[i] = NullValuePlaceHolder.INT_ARRAY; + } + break; default: throw new IllegalStateException(String.format("Cannot read MV %s as INT", resultDataType)); } @@ -424,15 +542,19 @@ public int[][] transformToIntValuesMV(ProjectionBlock projectionBlock) { return _intValuesMV; } - @Override - public long[][] transformToLongValuesMV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_longValuesMV == null) { + protected void initLongValuesMV(int length) { + if (_longValuesMV == null || _longValuesMV.length < length) { _longValuesMV = new long[length][]; } + } + + @Override + public long[][] transformToLongValuesMV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initLongValuesMV(length); Dictionary dictionary = getDictionary(); if (dictionary != null) { - int[][] dictIdsMV = transformToDictIdsMV(projectionBlock); + int[][] dictIdsMV = transformToDictIdsMV(valueBlock); for (int i = 0; i < length; i++) { int[] dictIds = dictIdsMV[i]; int numValues = dictIds.length; @@ -444,21 +566,27 @@ public long[][] transformToLongValuesMV(ProjectionBlock projectionBlock) { DataType resultDataType = getResultMetadata().getDataType(); switch (resultDataType.getStoredType()) { case INT: - int[][] intValuesMV = transformToIntValuesMV(projectionBlock); + int[][] intValuesMV = transformToIntValuesMV(valueBlock); ArrayCopyUtils.copy(intValuesMV, _longValuesMV, length); break; case FLOAT: - float[][] floatValuesMV = transformToFloatValuesMV(projectionBlock); + float[][] floatValuesMV = transformToFloatValuesMV(valueBlock); ArrayCopyUtils.copy(floatValuesMV, _longValuesMV, length); break; case DOUBLE: - double[][] doubleValuesMV = transformToDoubleValuesMV(projectionBlock); + double[][] doubleValuesMV = transformToDoubleValuesMV(valueBlock); ArrayCopyUtils.copy(doubleValuesMV, _longValuesMV, length); break; case STRING: - String[][] stringValuesMV = transformToStringValuesMV(projectionBlock); + String[][] stringValuesMV = transformToStringValuesMV(valueBlock); ArrayCopyUtils.copy(stringValuesMV, _longValuesMV, length); break; + case UNKNOWN: + // Copy the values to ensure behaviour consistency with non null-handling. + for (int i = 0; i < length; i++) { + _longValuesMV[i] = NullValuePlaceHolder.LONG_ARRAY; + } + break; default: throw new IllegalStateException(String.format("Cannot read MV %s as LONG", resultDataType)); } @@ -466,15 +594,19 @@ public long[][] transformToLongValuesMV(ProjectionBlock projectionBlock) { return _longValuesMV; } - @Override - public float[][] transformToFloatValuesMV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_floatValuesMV == null) { + protected void initFloatValuesMV(int length) { + if (_floatValuesMV == null || _floatValuesMV.length < length) { _floatValuesMV = new float[length][]; } + } + + @Override + public float[][] transformToFloatValuesMV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initFloatValuesMV(length); Dictionary dictionary = getDictionary(); if (dictionary != null) { - int[][] dictIdsMV = transformToDictIdsMV(projectionBlock); + int[][] dictIdsMV = transformToDictIdsMV(valueBlock); for (int i = 0; i < length; i++) { int[] dictIds = dictIdsMV[i]; int numValues = dictIds.length; @@ -486,21 +618,27 @@ public float[][] transformToFloatValuesMV(ProjectionBlock projectionBlock) { DataType resultDataType = getResultMetadata().getDataType(); switch (resultDataType.getStoredType()) { case INT: - int[][] intValuesMV = transformToIntValuesMV(projectionBlock); + int[][] intValuesMV = transformToIntValuesMV(valueBlock); ArrayCopyUtils.copy(intValuesMV, _floatValuesMV, length); break; case LONG: - long[][] longValuesMV = transformToLongValuesMV(projectionBlock); + long[][] longValuesMV = transformToLongValuesMV(valueBlock); ArrayCopyUtils.copy(longValuesMV, _floatValuesMV, length); break; case DOUBLE: - double[][] doubleValuesMV = transformToDoubleValuesMV(projectionBlock); + double[][] doubleValuesMV = transformToDoubleValuesMV(valueBlock); ArrayCopyUtils.copy(doubleValuesMV, _floatValuesMV, length); break; case STRING: - String[][] stringValuesMV = transformToStringValuesMV(projectionBlock); + String[][] stringValuesMV = transformToStringValuesMV(valueBlock); ArrayCopyUtils.copy(stringValuesMV, _floatValuesMV, length); break; + case UNKNOWN: + // Copy the values to ensure behaviour consistency with non null-handling. + for (int i = 0; i < length; i++) { + _floatValuesMV[i] = NullValuePlaceHolder.FLOAT_ARRAY; + } + break; default: throw new IllegalStateException(String.format("Cannot read MV %s as FLOAT", resultDataType)); } @@ -508,15 +646,19 @@ public float[][] transformToFloatValuesMV(ProjectionBlock projectionBlock) { return _floatValuesMV; } - @Override - public double[][] transformToDoubleValuesMV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_doubleValuesMV == null) { + protected void initDoubleValuesMV(int length) { + if (_doubleValuesMV == null || _doubleValuesMV.length < length) { _doubleValuesMV = new double[length][]; } + } + + @Override + public double[][] transformToDoubleValuesMV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initDoubleValuesMV(length); Dictionary dictionary = getDictionary(); if (dictionary != null) { - int[][] dictIdsMV = transformToDictIdsMV(projectionBlock); + int[][] dictIdsMV = transformToDictIdsMV(valueBlock); for (int i = 0; i < length; i++) { int[] dictIds = dictIdsMV[i]; int numValues = dictIds.length; @@ -528,21 +670,27 @@ public double[][] transformToDoubleValuesMV(ProjectionBlock projectionBlock) { DataType resultDataType = getResultMetadata().getDataType(); switch (resultDataType.getStoredType()) { case INT: - int[][] intValuesMV = transformToIntValuesMV(projectionBlock); + int[][] intValuesMV = transformToIntValuesMV(valueBlock); ArrayCopyUtils.copy(intValuesMV, _doubleValuesMV, length); break; case LONG: - long[][] longValuesMV = transformToLongValuesMV(projectionBlock); + long[][] longValuesMV = transformToLongValuesMV(valueBlock); ArrayCopyUtils.copy(longValuesMV, _doubleValuesMV, length); break; case FLOAT: - float[][] floatValuesMV = transformToFloatValuesMV(projectionBlock); + float[][] floatValuesMV = transformToFloatValuesMV(valueBlock); ArrayCopyUtils.copy(floatValuesMV, _doubleValuesMV, length); break; case STRING: - String[][] stringValuesMV = transformToStringValuesMV(projectionBlock); + String[][] stringValuesMV = transformToStringValuesMV(valueBlock); ArrayCopyUtils.copy(stringValuesMV, _doubleValuesMV, length); break; + case UNKNOWN: + // Copy the values to ensure behaviour consistency with non null-handling. + for (int i = 0; i < length; i++) { + _doubleValuesMV[i] = NullValuePlaceHolder.DOUBLE_ARRAY; + } + break; default: throw new IllegalStateException(String.format("Cannot read MV %s as DOUBLE", resultDataType)); } @@ -550,15 +698,19 @@ public double[][] transformToDoubleValuesMV(ProjectionBlock projectionBlock) { return _doubleValuesMV; } - @Override - public String[][] transformToStringValuesMV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_stringValuesMV == null) { + protected void initStringValuesMV(int length) { + if (_stringValuesMV == null || _stringValuesMV.length < length) { _stringValuesMV = new String[length][]; } + } + + @Override + public String[][] transformToStringValuesMV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initStringValuesMV(length); Dictionary dictionary = getDictionary(); if (dictionary != null) { - int[][] dictIdsMV = transformToDictIdsMV(projectionBlock); + int[][] dictIdsMV = transformToDictIdsMV(valueBlock); for (int i = 0; i < length; i++) { int[] dictIds = dictIdsMV[i]; int numValues = dictIds.length; @@ -570,21 +722,27 @@ public String[][] transformToStringValuesMV(ProjectionBlock projectionBlock) { DataType resultDataType = getResultMetadata().getDataType(); switch (resultDataType) { case INT: - int[][] intValuesMV = transformToIntValuesMV(projectionBlock); + int[][] intValuesMV = transformToIntValuesMV(valueBlock); ArrayCopyUtils.copy(intValuesMV, _stringValuesMV, length); break; case LONG: - long[][] longValuesMV = transformToLongValuesMV(projectionBlock); + long[][] longValuesMV = transformToLongValuesMV(valueBlock); ArrayCopyUtils.copy(longValuesMV, _stringValuesMV, length); break; case FLOAT: - float[][] floatValuesMV = transformToFloatValuesMV(projectionBlock); + float[][] floatValuesMV = transformToFloatValuesMV(valueBlock); ArrayCopyUtils.copy(floatValuesMV, _stringValuesMV, length); break; case DOUBLE: - double[][] doubleValuesMV = transformToDoubleValuesMV(projectionBlock); + double[][] doubleValuesMV = transformToDoubleValuesMV(valueBlock); ArrayCopyUtils.copy(doubleValuesMV, _stringValuesMV, length); break; + case UNKNOWN: + // Copy the values to ensure behaviour consistency with non null-handling. + for (int i = 0; i < length; i++) { + _stringValuesMV[i] = NullValuePlaceHolder.STRING_ARRAY; + } + break; default: throw new IllegalStateException(String.format("Cannot read MV %s as STRING", resultDataType)); } @@ -592,15 +750,19 @@ public String[][] transformToStringValuesMV(ProjectionBlock projectionBlock) { return _stringValuesMV; } - @Override - public byte[][][] transformToBytesValuesMV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_bytesValuesMV == null) { + protected void initBytesValuesMV(int length) { + if (_bytesValuesMV == null || _bytesValuesMV.length < length) { _bytesValuesMV = new byte[length][][]; } + } + + @Override + public byte[][][] transformToBytesValuesMV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initBytesValuesMV(length); Dictionary dictionary = getDictionary(); if (dictionary != null) { - int[][] dictIdsMV = transformToDictIdsMV(projectionBlock); + int[][] dictIdsMV = transformToDictIdsMV(valueBlock); for (int i = 0; i < length; i++) { int[] dictIds = dictIdsMV[i]; int numValues = dictIds.length; @@ -610,9 +772,25 @@ public byte[][][] transformToBytesValuesMV(ProjectionBlock projectionBlock) { } } else { assert getResultMetadata().getDataType().getStoredType() == DataType.STRING; - String[][] stringValuesMV = transformToStringValuesMV(projectionBlock); + String[][] stringValuesMV = transformToStringValuesMV(valueBlock); ArrayCopyUtils.copy(stringValuesMV, _bytesValuesMV, length); } return _bytesValuesMV; } + + @Nullable + @Override + public RoaringBitmap getNullBitmap(ValueBlock valueBlock) { + RoaringBitmap bitmap = new RoaringBitmap(); + for (TransformFunction arg : _arguments) { + RoaringBitmap argBitmap = arg.getNullBitmap(valueBlock); + if (argBitmap != null) { + bitmap.or(argBitmap); + } + } + if (bitmap.isEmpty()) { + return null; + } + return bitmap; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/BinaryOperatorTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/BinaryOperatorTransformFunction.java index d056322442e2..24850868e9df 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/BinaryOperatorTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/BinaryOperatorTransformFunction.java @@ -23,9 +23,9 @@ import java.util.List; import java.util.Map; import org.apache.pinot.common.function.TransformFunctionType; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.utils.ByteArray; @@ -82,7 +82,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); // Check that there are exact 2 arguments Preconditions.checkArgument(arguments.size() == 2, "Exact 2 arguments are required for binary operator transform function"); @@ -105,37 +106,38 @@ public TransformResultMetadata getResultMetadata() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - fillResultArray(projectionBlock); + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + fillResultArray(valueBlock); return _intValuesSV; } - private void fillResultArray(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_intValuesSV == null) { - _intValuesSV = new int[length]; - } + private void fillResultArray(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initIntValuesSV(length); switch (_leftStoredType) { case INT: - fillResultInt(projectionBlock, length); + fillResultInt(valueBlock, length); break; case LONG: - fillResultLong(projectionBlock, length); + fillResultLong(valueBlock, length); break; case FLOAT: - fillResultFloat(projectionBlock, length); + fillResultFloat(valueBlock, length); break; case DOUBLE: - fillResultDouble(projectionBlock, length); + fillResultDouble(valueBlock, length); break; case BIG_DECIMAL: - fillResultBigDecimal(projectionBlock, length); + fillResultBigDecimal(valueBlock, length); break; case STRING: - fillResultString(projectionBlock, length); + fillResultString(valueBlock, length); break; case BYTES: - fillResultBytes(projectionBlock, length); + fillResultBytes(valueBlock, length); + break; + case UNKNOWN: + fillResultUnknown(length); break; // NOTE: Multi-value columns are not comparable, so we should not reach here default: @@ -143,130 +145,145 @@ private void fillResultArray(ProjectionBlock projectionBlock) { } } - private void fillResultInt(ProjectionBlock projectionBlock, int length) { - int[] leftIntValues = _leftTransformFunction.transformToIntValuesSV(projectionBlock); + private void fillResultInt(ValueBlock valueBlock, int length) { + int[] leftIntValues = _leftTransformFunction.transformToIntValuesSV(valueBlock); switch (_rightStoredType) { case INT: - fillIntResultArray(projectionBlock, leftIntValues, length); + fillIntResultArray(valueBlock, leftIntValues, length); break; case LONG: - fillLongResultArray(projectionBlock, leftIntValues, length); + fillLongResultArray(valueBlock, leftIntValues, length); break; case FLOAT: - fillFloatResultArray(projectionBlock, leftIntValues, length); + fillFloatResultArray(valueBlock, leftIntValues, length); break; case DOUBLE: - fillDoubleResultArray(projectionBlock, leftIntValues, length); + fillDoubleResultArray(valueBlock, leftIntValues, length); break; case BIG_DECIMAL: - fillBigDecimalResultArray(projectionBlock, leftIntValues, length); + fillBigDecimalResultArray(valueBlock, leftIntValues, length); break; case STRING: - fillStringResultArray(projectionBlock, leftIntValues, length); + fillStringResultArray(valueBlock, leftIntValues, length); + break; + case UNKNOWN: + fillResultUnknown(length); break; default: throw illegalState(); } } - private void fillResultLong(ProjectionBlock projectionBlock, int length) { - long[] leftLongValues = _leftTransformFunction.transformToLongValuesSV(projectionBlock); + private void fillResultLong(ValueBlock valueBlock, int length) { + long[] leftLongValues = _leftTransformFunction.transformToLongValuesSV(valueBlock); switch (_rightStoredType) { case INT: - fillIntResultArray(projectionBlock, leftLongValues, length); + fillIntResultArray(valueBlock, leftLongValues, length); break; case LONG: - fillLongResultArray(projectionBlock, leftLongValues, length); + fillLongResultArray(valueBlock, leftLongValues, length); break; case FLOAT: - fillFloatResultArray(projectionBlock, leftLongValues, length); + fillFloatResultArray(valueBlock, leftLongValues, length); break; case DOUBLE: - fillDoubleResultArray(projectionBlock, leftLongValues, length); + fillDoubleResultArray(valueBlock, leftLongValues, length); break; case BIG_DECIMAL: - fillBigDecimalResultArray(projectionBlock, leftLongValues, length); + fillBigDecimalResultArray(valueBlock, leftLongValues, length); break; case STRING: - fillStringResultArray(projectionBlock, leftLongValues, length); + fillStringResultArray(valueBlock, leftLongValues, length); + break; + case UNKNOWN: + fillResultUnknown(length); break; default: throw illegalState(); } } - private void fillResultFloat(ProjectionBlock projectionBlock, int length) { - float[] leftFloatValues = _leftTransformFunction.transformToFloatValuesSV(projectionBlock); + private void fillResultFloat(ValueBlock valueBlock, int length) { + float[] leftFloatValues = _leftTransformFunction.transformToFloatValuesSV(valueBlock); switch (_rightStoredType) { case INT: - fillIntResultArray(projectionBlock, leftFloatValues, length); + fillIntResultArray(valueBlock, leftFloatValues, length); break; case LONG: - fillLongResultArray(projectionBlock, leftFloatValues, length); + fillLongResultArray(valueBlock, leftFloatValues, length); break; case FLOAT: - fillFloatResultArray(projectionBlock, leftFloatValues, length); + fillFloatResultArray(valueBlock, leftFloatValues, length); break; case DOUBLE: - fillDoubleResultArray(projectionBlock, leftFloatValues, length); + fillDoubleResultArray(valueBlock, leftFloatValues, length); break; case BIG_DECIMAL: - fillBigDecimalResultArray(projectionBlock, leftFloatValues, length); + fillBigDecimalResultArray(valueBlock, leftFloatValues, length); break; case STRING: - fillStringResultArray(projectionBlock, leftFloatValues, length); + fillStringResultArray(valueBlock, leftFloatValues, length); + break; + case UNKNOWN: + fillResultUnknown(length); break; default: throw illegalState(); } } - private void fillResultDouble(ProjectionBlock projectionBlock, int length) { - double[] leftDoubleValues = _leftTransformFunction.transformToDoubleValuesSV(projectionBlock); + private void fillResultDouble(ValueBlock valueBlock, int length) { + double[] leftDoubleValues = _leftTransformFunction.transformToDoubleValuesSV(valueBlock); switch (_rightStoredType) { case INT: - fillIntResultArray(projectionBlock, leftDoubleValues, length); + fillIntResultArray(valueBlock, leftDoubleValues, length); break; case LONG: - fillLongResultArray(projectionBlock, leftDoubleValues, length); + fillLongResultArray(valueBlock, leftDoubleValues, length); break; case FLOAT: - fillFloatResultArray(projectionBlock, leftDoubleValues, length); + fillFloatResultArray(valueBlock, leftDoubleValues, length); break; case DOUBLE: - fillDoubleResultArray(projectionBlock, leftDoubleValues, length); + fillDoubleResultArray(valueBlock, leftDoubleValues, length); break; case BIG_DECIMAL: - fillBigDecimalResultArray(projectionBlock, leftDoubleValues, length); + fillBigDecimalResultArray(valueBlock, leftDoubleValues, length); break; case STRING: - fillStringResultArray(projectionBlock, leftDoubleValues, length); + fillStringResultArray(valueBlock, leftDoubleValues, length); + break; + case UNKNOWN: + fillResultUnknown(length); break; default: throw illegalState(); } } - private void fillResultBigDecimal(ProjectionBlock projectionBlock, int length) { - BigDecimal[] leftBigDecimalValues = _leftTransformFunction.transformToBigDecimalValuesSV(projectionBlock); + private void fillResultBigDecimal(ValueBlock valueBlock, int length) { + BigDecimal[] leftBigDecimalValues = _leftTransformFunction.transformToBigDecimalValuesSV(valueBlock); switch (_rightStoredType) { case INT: - fillIntResultArray(projectionBlock, leftBigDecimalValues, length); + fillIntResultArray(valueBlock, leftBigDecimalValues, length); break; case LONG: - fillLongResultArray(projectionBlock, leftBigDecimalValues, length); + fillLongResultArray(valueBlock, leftBigDecimalValues, length); break; case FLOAT: - fillFloatResultArray(projectionBlock, leftBigDecimalValues, length); + fillFloatResultArray(valueBlock, leftBigDecimalValues, length); break; case DOUBLE: - fillDoubleResultArray(projectionBlock, leftBigDecimalValues, length); + fillDoubleResultArray(valueBlock, leftBigDecimalValues, length); break; case STRING: - fillStringResultArray(projectionBlock, leftBigDecimalValues, length); + fillStringResultArray(valueBlock, leftBigDecimalValues, length); break; case BIG_DECIMAL: - fillBigDecimalResultArray(projectionBlock, leftBigDecimalValues, length); + fillBigDecimalResultArray(valueBlock, leftBigDecimalValues, length); + break; + case UNKNOWN: + fillResultUnknown(length); break; default: throw illegalState(); @@ -280,59 +297,59 @@ private IllegalStateException illegalState() { _rightTransformFunction.getName(), _rightStoredType)); } - private void fillResultString(ProjectionBlock projectionBlock, int length) { - String[] leftStringValues = _leftTransformFunction.transformToStringValuesSV(projectionBlock); - String[] rightStringValues = _rightTransformFunction.transformToStringValuesSV(projectionBlock); + private void fillResultString(ValueBlock valueBlock, int length) { + String[] leftStringValues = _leftTransformFunction.transformToStringValuesSV(valueBlock); + String[] rightStringValues = _rightTransformFunction.transformToStringValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(leftStringValues[i].compareTo(rightStringValues[i])); } } - private void fillResultBytes(ProjectionBlock projectionBlock, int length) { - byte[][] leftBytesValues = _leftTransformFunction.transformToBytesValuesSV(projectionBlock); - byte[][] rightBytesValues = _rightTransformFunction.transformToBytesValuesSV(projectionBlock); + private void fillResultBytes(ValueBlock valueBlock, int length) { + byte[][] leftBytesValues = _leftTransformFunction.transformToBytesValuesSV(valueBlock); + byte[][] rightBytesValues = _rightTransformFunction.transformToBytesValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult((ByteArray.compare(leftBytesValues[i], rightBytesValues[i]))); } } - private void fillIntResultArray(ProjectionBlock projectionBlock, int[] leftIntValues, int length) { - int[] rightIntValues = _rightTransformFunction.transformToIntValuesSV(projectionBlock); + private void fillIntResultArray(ValueBlock valueBlock, int[] leftIntValues, int length) { + int[] rightIntValues = _rightTransformFunction.transformToIntValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(Integer.compare(leftIntValues[i], rightIntValues[i])); } } - private void fillLongResultArray(ProjectionBlock projectionBlock, int[] leftValues, int length) { - long[] rightValues = _rightTransformFunction.transformToLongValuesSV(projectionBlock); + private void fillLongResultArray(ValueBlock valueBlock, int[] leftValues, int length) { + long[] rightValues = _rightTransformFunction.transformToLongValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(Long.compare(leftValues[i], rightValues[i])); } } - private void fillFloatResultArray(ProjectionBlock projectionBlock, int[] leftValues, int length) { - float[] rightFloatValues = _rightTransformFunction.transformToFloatValuesSV(projectionBlock); + private void fillFloatResultArray(ValueBlock valueBlock, int[] leftValues, int length) { + float[] rightFloatValues = _rightTransformFunction.transformToFloatValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(Double.compare(leftValues[i], rightFloatValues[i])); } } - private void fillDoubleResultArray(ProjectionBlock projectionBlock, int[] leftValues, int length) { - double[] rightDoubleValues = _rightTransformFunction.transformToDoubleValuesSV(projectionBlock); + private void fillDoubleResultArray(ValueBlock valueBlock, int[] leftValues, int length) { + double[] rightDoubleValues = _rightTransformFunction.transformToDoubleValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(Double.compare(leftValues[i], rightDoubleValues[i])); } } - private void fillBigDecimalResultArray(ProjectionBlock projectionBlock, int[] leftValues, int length) { - BigDecimal[] rightBigDecimalValues = _rightTransformFunction.transformToBigDecimalValuesSV(projectionBlock); + private void fillBigDecimalResultArray(ValueBlock valueBlock, int[] leftValues, int length) { + BigDecimal[] rightBigDecimalValues = _rightTransformFunction.transformToBigDecimalValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(BigDecimal.valueOf(leftValues[i]).compareTo(rightBigDecimalValues[i])); } } - private void fillStringResultArray(ProjectionBlock projectionBlock, int[] leftValues, int length) { - String[] rightStringValues = _rightTransformFunction.transformToStringValuesSV(projectionBlock); + private void fillStringResultArray(ValueBlock valueBlock, int[] leftValues, int length) { + String[] rightStringValues = _rightTransformFunction.transformToStringValuesSV(valueBlock); for (int i = 0; i < length; i++) { try { _intValuesSV[i] = @@ -343,43 +360,43 @@ private void fillStringResultArray(ProjectionBlock projectionBlock, int[] leftVa } } - private void fillIntResultArray(ProjectionBlock projectionBlock, long[] leftIntValues, int length) { - int[] rightIntValues = _rightTransformFunction.transformToIntValuesSV(projectionBlock); + private void fillIntResultArray(ValueBlock valueBlock, long[] leftIntValues, int length) { + int[] rightIntValues = _rightTransformFunction.transformToIntValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(Long.compare(leftIntValues[i], rightIntValues[i])); } } - private void fillLongResultArray(ProjectionBlock projectionBlock, long[] leftValues, int length) { - long[] rightValues = _rightTransformFunction.transformToLongValuesSV(projectionBlock); + private void fillLongResultArray(ValueBlock valueBlock, long[] leftValues, int length) { + long[] rightValues = _rightTransformFunction.transformToLongValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(Long.compare(leftValues[i], rightValues[i])); } } - private void fillFloatResultArray(ProjectionBlock projectionBlock, long[] leftValues, int length) { - float[] rightFloatValues = _rightTransformFunction.transformToFloatValuesSV(projectionBlock); + private void fillFloatResultArray(ValueBlock valueBlock, long[] leftValues, int length) { + float[] rightFloatValues = _rightTransformFunction.transformToFloatValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(compare(leftValues[i], rightFloatValues[i])); } } - private void fillDoubleResultArray(ProjectionBlock projectionBlock, long[] leftValues, int length) { - double[] rightDoubleValues = _rightTransformFunction.transformToDoubleValuesSV(projectionBlock); + private void fillDoubleResultArray(ValueBlock valueBlock, long[] leftValues, int length) { + double[] rightDoubleValues = _rightTransformFunction.transformToDoubleValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(compare(leftValues[i], rightDoubleValues[i])); } } - private void fillBigDecimalResultArray(ProjectionBlock projectionBlock, long[] leftValues, int length) { - BigDecimal[] rightBigDecimalValues = _rightTransformFunction.transformToBigDecimalValuesSV(projectionBlock); + private void fillBigDecimalResultArray(ValueBlock valueBlock, long[] leftValues, int length) { + BigDecimal[] rightBigDecimalValues = _rightTransformFunction.transformToBigDecimalValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(BigDecimal.valueOf(leftValues[i]).compareTo(rightBigDecimalValues[i])); } } - private void fillStringResultArray(ProjectionBlock projectionBlock, long[] leftValues, int length) { - String[] rightStringValues = _rightTransformFunction.transformToStringValuesSV(projectionBlock); + private void fillStringResultArray(ValueBlock valueBlock, long[] leftValues, int length) { + String[] rightStringValues = _rightTransformFunction.transformToStringValuesSV(valueBlock); for (int i = 0; i < length; i++) { try { _intValuesSV[i] = @@ -390,43 +407,43 @@ private void fillStringResultArray(ProjectionBlock projectionBlock, long[] leftV } } - private void fillIntResultArray(ProjectionBlock projectionBlock, float[] leftValues, int length) { - int[] rightIntValues = _rightTransformFunction.transformToIntValuesSV(projectionBlock); + private void fillIntResultArray(ValueBlock valueBlock, float[] leftValues, int length) { + int[] rightIntValues = _rightTransformFunction.transformToIntValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(Double.compare(leftValues[i], rightIntValues[i])); } } - private void fillLongResultArray(ProjectionBlock projectionBlock, float[] leftValues, int length) { - long[] rightValues = _rightTransformFunction.transformToLongValuesSV(projectionBlock); + private void fillLongResultArray(ValueBlock valueBlock, float[] leftValues, int length) { + long[] rightValues = _rightTransformFunction.transformToLongValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(compare(leftValues[i], rightValues[i])); } } - private void fillFloatResultArray(ProjectionBlock projectionBlock, float[] leftValues, int length) { - float[] rightFloatValues = _rightTransformFunction.transformToFloatValuesSV(projectionBlock); + private void fillFloatResultArray(ValueBlock valueBlock, float[] leftValues, int length) { + float[] rightFloatValues = _rightTransformFunction.transformToFloatValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(Float.compare(leftValues[i], rightFloatValues[i])); } } - private void fillDoubleResultArray(ProjectionBlock projectionBlock, float[] leftValues, int length) { - double[] rightDoubleValues = _rightTransformFunction.transformToDoubleValuesSV(projectionBlock); + private void fillDoubleResultArray(ValueBlock valueBlock, float[] leftValues, int length) { + double[] rightDoubleValues = _rightTransformFunction.transformToDoubleValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(Double.compare(leftValues[i], rightDoubleValues[i])); } } - private void fillBigDecimalResultArray(ProjectionBlock projectionBlock, float[] leftValues, int length) { - BigDecimal[] rightBigDecimalValues = _rightTransformFunction.transformToBigDecimalValuesSV(projectionBlock); + private void fillBigDecimalResultArray(ValueBlock valueBlock, float[] leftValues, int length) { + BigDecimal[] rightBigDecimalValues = _rightTransformFunction.transformToBigDecimalValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(BigDecimal.valueOf(leftValues[i]).compareTo(rightBigDecimalValues[i])); } } - private void fillStringResultArray(ProjectionBlock projectionBlock, float[] leftValues, int length) { - String[] rightStringValues = _rightTransformFunction.transformToStringValuesSV(projectionBlock); + private void fillStringResultArray(ValueBlock valueBlock, float[] leftValues, int length) { + String[] rightStringValues = _rightTransformFunction.transformToStringValuesSV(valueBlock); for (int i = 0; i < length; i++) { try { _intValuesSV[i] = @@ -437,43 +454,43 @@ private void fillStringResultArray(ProjectionBlock projectionBlock, float[] left } } - private void fillIntResultArray(ProjectionBlock projectionBlock, double[] leftValues, int length) { - int[] rightIntValues = _rightTransformFunction.transformToIntValuesSV(projectionBlock); + private void fillIntResultArray(ValueBlock valueBlock, double[] leftValues, int length) { + int[] rightIntValues = _rightTransformFunction.transformToIntValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(Double.compare(leftValues[i], rightIntValues[i])); } } - private void fillLongResultArray(ProjectionBlock projectionBlock, double[] leftValues, int length) { - long[] rightValues = _rightTransformFunction.transformToLongValuesSV(projectionBlock); + private void fillLongResultArray(ValueBlock valueBlock, double[] leftValues, int length) { + long[] rightValues = _rightTransformFunction.transformToLongValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(compare(leftValues[i], rightValues[i])); } } - private void fillFloatResultArray(ProjectionBlock projectionBlock, double[] leftValues, int length) { - float[] rightFloatValues = _rightTransformFunction.transformToFloatValuesSV(projectionBlock); + private void fillFloatResultArray(ValueBlock valueBlock, double[] leftValues, int length) { + float[] rightFloatValues = _rightTransformFunction.transformToFloatValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(Double.compare(leftValues[i], rightFloatValues[i])); } } - private void fillDoubleResultArray(ProjectionBlock projectionBlock, double[] leftValues, int length) { - double[] rightDoubleValues = _rightTransformFunction.transformToDoubleValuesSV(projectionBlock); + private void fillDoubleResultArray(ValueBlock valueBlock, double[] leftValues, int length) { + double[] rightDoubleValues = _rightTransformFunction.transformToDoubleValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(Double.compare(leftValues[i], rightDoubleValues[i])); } } - private void fillBigDecimalResultArray(ProjectionBlock projectionBlock, double[] leftValues, int length) { - BigDecimal[] rightBigDecimalValues = _rightTransformFunction.transformToBigDecimalValuesSV(projectionBlock); + private void fillBigDecimalResultArray(ValueBlock valueBlock, double[] leftValues, int length) { + BigDecimal[] rightBigDecimalValues = _rightTransformFunction.transformToBigDecimalValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(BigDecimal.valueOf(leftValues[i]).compareTo(rightBigDecimalValues[i])); } } - private void fillStringResultArray(ProjectionBlock projectionBlock, double[] leftValues, int length) { - String[] rightStringValues = _rightTransformFunction.transformToStringValuesSV(projectionBlock); + private void fillStringResultArray(ValueBlock valueBlock, double[] leftValues, int length) { + String[] rightStringValues = _rightTransformFunction.transformToStringValuesSV(valueBlock); for (int i = 0; i < length; i++) { try { _intValuesSV[i] = @@ -484,43 +501,43 @@ private void fillStringResultArray(ProjectionBlock projectionBlock, double[] lef } } - private void fillIntResultArray(ProjectionBlock projectionBlock, BigDecimal[] leftValues, int length) { - int[] rightIntValues = _rightTransformFunction.transformToIntValuesSV(projectionBlock); + private void fillIntResultArray(ValueBlock valueBlock, BigDecimal[] leftValues, int length) { + int[] rightIntValues = _rightTransformFunction.transformToIntValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(leftValues[i].compareTo(BigDecimal.valueOf(rightIntValues[i]))); } } - private void fillLongResultArray(ProjectionBlock projectionBlock, BigDecimal[] leftValues, int length) { - long[] rightLongValues = _rightTransformFunction.transformToLongValuesSV(projectionBlock); + private void fillLongResultArray(ValueBlock valueBlock, BigDecimal[] leftValues, int length) { + long[] rightLongValues = _rightTransformFunction.transformToLongValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(leftValues[i].compareTo(BigDecimal.valueOf(rightLongValues[i]))); } } - private void fillFloatResultArray(ProjectionBlock projectionBlock, BigDecimal[] leftValues, int length) { - float[] rightFloatValues = _rightTransformFunction.transformToFloatValuesSV(projectionBlock); + private void fillFloatResultArray(ValueBlock valueBlock, BigDecimal[] leftValues, int length) { + float[] rightFloatValues = _rightTransformFunction.transformToFloatValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(leftValues[i].compareTo(BigDecimal.valueOf(rightFloatValues[i]))); } } - private void fillDoubleResultArray(ProjectionBlock projectionBlock, BigDecimal[] leftValues, int length) { - double[] rightDoubleValues = _rightTransformFunction.transformToDoubleValuesSV(projectionBlock); + private void fillDoubleResultArray(ValueBlock valueBlock, BigDecimal[] leftValues, int length) { + double[] rightDoubleValues = _rightTransformFunction.transformToDoubleValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(leftValues[i].compareTo(BigDecimal.valueOf(rightDoubleValues[i]))); } } - private void fillBigDecimalResultArray(ProjectionBlock projectionBlock, BigDecimal[] leftValues, int length) { - BigDecimal[] rightBigDecimalValues = _rightTransformFunction.transformToBigDecimalValuesSV(projectionBlock); + private void fillBigDecimalResultArray(ValueBlock valueBlock, BigDecimal[] leftValues, int length) { + BigDecimal[] rightBigDecimalValues = _rightTransformFunction.transformToBigDecimalValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(leftValues[i].compareTo(rightBigDecimalValues[i])); } } - private void fillStringResultArray(ProjectionBlock projectionBlock, BigDecimal[] leftValues, int length) { - String[] rightStringValues = _rightTransformFunction.transformToStringValuesSV(projectionBlock); + private void fillStringResultArray(ValueBlock valueBlock, BigDecimal[] leftValues, int length) { + String[] rightStringValues = _rightTransformFunction.transformToStringValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = getIntResult(leftValues[i].compareTo(new BigDecimal(rightStringValues[i]))); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/CLPDecodeTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/CLPDecodeTransformFunction.java new file mode 100644 index 000000000000..bf2eb79cd789 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/CLPDecodeTransformFunction.java @@ -0,0 +1,132 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.transform.function; + +import com.google.common.base.Preconditions; +import com.yscope.clp.compressorfrontend.BuiltInVariableHandlingRuleVersions; +import com.yscope.clp.compressorfrontend.MessageDecoder; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import org.apache.pinot.common.function.TransformFunctionType; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; +import org.apache.pinot.core.operator.transform.TransformResultMetadata; +import org.apache.pinot.spi.data.FieldSpec; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.pinot.spi.data.FieldSpec.DEFAULT_DIMENSION_NULL_VALUE_OF_STRING; + + +/** + * Decodes a CLP-encoded column group into the original value. In Pinot, a CLP-encoded field is encoded into three + * Pinot columns, what we collectively refer to as a column group. E.g., A CLP-encoded "message" field would be + * stored in three columns: "message_logtype", "message_dictionaryVars", and "message_encodedVars". + *

+ * Usage: + *

+ *   clpDecode("columnGroupName_logtype", "columnGroupName_dictionaryVars",
+ *             "columnGroupName_encodedVars"[, defaultValue])
+ * 
+ * The "defaultValue" is optional and is used when a column group can't be decoded for some reason. + *

+ * Sample queries: + *

+ *   SELECT clpDecode("message_logtype", "message_dictionaryVars", "message_encodedVars") FROM table
+ *   SELECT clpDecode("message_logtype", "message_dictionaryVars", "message_encodedVars", 'null') FROM table
+ * 
+ * For instance, consider a record that contains a "message" field with this value: + *
INFO Task task_12 assigned to container: [ContainerID:container_15], operation took 0.335 seconds. 8 tasks
+ * remaining.
+ * {@link org.apache.pinot.plugin.inputformat.clplog.CLPLogMessageDecoder} will encode it into 3 columns: + *
    + *
  • message_logtype: "INFO Task \x12 assigned to container: [ContainerID:\x12], operation took \x13 seconds. + * \x11 tasks remaining."
  • + *
  • message_dictionaryVars: [“task_12”, “container_15”]
  • + *
  • message_encodedVars: [[0x190000000000014f, 8]]
  • + *
+ * Then we can use the sample queries above to decode the columns back into the original value of the "message" field. + */ +public class CLPDecodeTransformFunction extends BaseTransformFunction { + private static final Logger _logger = LoggerFactory.getLogger(CLPDecodeTransformFunction.class); + private final List _transformFunctions = new ArrayList<>(); + private String _defaultValue = DEFAULT_DIMENSION_NULL_VALUE_OF_STRING; + + @Override + public String getName() { + return TransformFunctionType.CLP_DECODE.getName(); + } + + @Override + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); + int numArgs = arguments.size(); + Preconditions.checkArgument(3 == numArgs || 4 == numArgs, + "Syntax error: clpDecode takes 3 or 4 arguments - " + + "clpDecode(ColumnGroupName_logtype, ColumnGroupName_dictionaryVars, ColumnGroupName_encodedVars, " + + "defaultValue)"); + + int i; + for (i = 0; i < 3; i++) { + TransformFunction f = arguments.get(i); + Preconditions.checkArgument(f instanceof IdentifierTransformFunction, + "Argument " + i + " must be a column name (identifier)"); + _transformFunctions.add(f); + } + if (i < numArgs) { + TransformFunction f = arguments.get(i++); + Preconditions.checkArgument(f instanceof LiteralTransformFunction, + "Argument " + i + " must be a default value (literal)"); + _defaultValue = ((LiteralTransformFunction) f).getStringLiteral(); + } + } + + @Override + public TransformResultMetadata getResultMetadata() { + return new TransformResultMetadata(FieldSpec.DataType.STRING, true, false); + } + + @Override + public String[] transformToStringValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initStringValuesSV(length); + + int functionIdx = 0; + TransformFunction logtypeTransformFunction = _transformFunctions.get(functionIdx++); + TransformFunction dictionaryVarsTransformFunction = _transformFunctions.get(functionIdx++); + TransformFunction encodedVarsTransformFunction = _transformFunctions.get(functionIdx); + String[] logtypes = logtypeTransformFunction.transformToStringValuesSV(valueBlock); + String[][] dictionaryVars = dictionaryVarsTransformFunction.transformToStringValuesMV(valueBlock); + long[][] encodedVars = encodedVarsTransformFunction.transformToLongValuesMV(valueBlock); + + MessageDecoder clpMessageDecoder = new MessageDecoder(BuiltInVariableHandlingRuleVersions.VariablesSchemaV2, + BuiltInVariableHandlingRuleVersions.VariableEncodingMethodsV1); + for (int i = 0; i < length; i++) { + try { + _stringValuesSV[i] = clpMessageDecoder.decodeMessage(logtypes[i], dictionaryVars[i], encodedVars[i]); + } catch (Exception ex) { + _logger.error("Failed to decode CLP-encoded field.", ex); + _stringValuesSV[i] = _defaultValue; + } + } + + return _stringValuesSV; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/CaseTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/CaseTransformFunction.java index 580c608bf309..0d008c212eec 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/CaseTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/CaseTransformFunction.java @@ -22,46 +22,51 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; +import java.util.BitSet; +import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.MutablePair; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.utils.BytesUtils; +import org.apache.pinot.spi.utils.CommonConstants.NullValuePlaceHolder; +import org.apache.pinot.spi.utils.TimestampUtils; +import org.roaringbitmap.RoaringBitmap; /** * The CaseTransformFunction class implements the CASE-WHEN-THEN-ELSE transformation. - * - * The SQL Syntax is: - * CASE - * WHEN condition1 THEN result1 - * WHEN condition2 THEN result2 - * WHEN conditionN THEN resultN - * ELSE result - * END; - * - * Usage: - * case(${WHEN_STATEMENT_1}, ..., ${WHEN_STATEMENT_N}, - * ${THEN_EXPRESSION_1}, ..., ${THEN_EXPRESSION_N}, - * ${ELSE_EXPRESSION}) - * + *

+ * The SQL Syntax is: CASE WHEN condition1 THEN result1 WHEN condition2 THEN result2 WHEN conditionN THEN resultN ELSE + * result END; + *

+ * Usage: case(${WHEN_STATEMENT_1}, ${THEN_EXPRESSION_1}, ..., ${WHEN_STATEMENT_N}, ${THEN_EXPRESSION_N}, ..., + * ${ELSE_EXPRESSION}) + *

* There are 2 * N + 1 arguments: - * WHEN_STATEMENT_$i is a BinaryOperatorTransformFunction represents - * condition$i - * THEN_EXPRESSION_$i is a TransformFunction represents result$i - * ELSE_EXPRESSION is a TransformFunction represents result - * + * WHEN_STATEMENT_$i is a BinaryOperatorTransformFunction represents condition$i + * THEN_EXPRESSION_$i is a TransformFunction represents result$i + * ELSE_EXPRESSION is a TransformFunction represents result + *

+ * ELSE_EXPRESSION can be omitted. When none of when statements is evaluated to be true, and there is no else + * expression, we output null. Note that when statement is considered as false if it is evaluated to be null. + *

+ * PostgreSQL documentation: CASE */ -public class CaseTransformFunction extends BaseTransformFunction { +public class CaseTransformFunction extends ComputeDifferentlyWhenNullHandlingEnabledTransformFunction { public static final String FUNCTION_NAME = "case"; private List _whenStatements = new ArrayList<>(); - private List _elseThenStatements = new ArrayList<>(); - private boolean[] _selections; - private int _numSelections; + private List _thenStatements = new ArrayList<>(); + private TransformFunction _elseStatement; private TransformResultMetadata _resultMetadata; + + private boolean[] _computeThenStatements; private int[] _selectedResults; @Override @@ -70,169 +75,149 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { - // Check that there are more than 1 arguments - if (arguments.size() % 2 != 1 || arguments.size() < 3) { - throw new IllegalArgumentException("At least 3 odd number of arguments are required for CASE-WHEN-ELSE function"); + public void init(List arguments, Map columnContextMap, + boolean nullHandlingEnabled) { + super.init(arguments, columnContextMap, nullHandlingEnabled); + // Check that there are more than 2 arguments + // Else statement can be omitted. + if (arguments.size() < 2) { + throw new IllegalArgumentException("At least two arguments are required for CASE-WHEN function"); } int numWhenStatements = arguments.size() / 2; _whenStatements = new ArrayList<>(numWhenStatements); - _elseThenStatements = new ArrayList<>(numWhenStatements + 1); - constructStatementList(arguments); - _selections = new boolean[_elseThenStatements.size()]; - Collections.reverse(_elseThenStatements); - Collections.reverse(_whenStatements); - _resultMetadata = calculateResultMetadata(); - } - - private void constructStatementList(List arguments) { - int numWhenStatements = arguments.size() / 2; - boolean allBooleanFirstHalf = true; - boolean notAllBooleanOddHalf = false; + _thenStatements = new ArrayList<>(numWhenStatements); + // Alternating WHEN and THEN clause, last one ELSE for (int i = 0; i < numWhenStatements; i++) { - if (arguments.get(i).getResultMetadata().getDataType() != DataType.BOOLEAN) { - allBooleanFirstHalf = false; - } - if (arguments.get(i * 2).getResultMetadata().getDataType() != DataType.BOOLEAN) { - notAllBooleanOddHalf = true; - } + _whenStatements.add(arguments.get(i * 2)); + _thenStatements.add(arguments.get(i * 2 + 1)); } - if (allBooleanFirstHalf && notAllBooleanOddHalf) { - constructStatementListLegacy(arguments); - } else { - constructStatementListCalcite(arguments); + if (arguments.size() % 2 != 0 && !isNullLiteral(arguments.get(arguments.size() - 1))) { + _elseStatement = arguments.get(arguments.size() - 1); } + _resultMetadata = new TransformResultMetadata(calculateResultType(), true, false); + _computeThenStatements = new boolean[numWhenStatements]; } - private void constructStatementListCalcite(List arguments) { - int numWhenStatements = arguments.size() / 2; - // alternating WHEN and THEN clause, last one ELSE - for (int i = 0; i < numWhenStatements; i++) { - _whenStatements.add(arguments.get(i * 2)); - _elseThenStatements.add(arguments.get(i * 2 + 1)); - } - _elseThenStatements.add(arguments.get(arguments.size() - 1)); + private boolean isNullLiteral(TransformFunction function) { + return function instanceof LiteralTransformFunction && ((LiteralTransformFunction) function).isNull(); } - // TODO: Legacy format, this is here for backward compatibility support, remove after release 0.12 - private void constructStatementListLegacy(List arguments) { - int numWhenStatements = arguments.size() / 2; - // first half WHEN, second half THEN, last one ELSE - for (int i = 0; i < numWhenStatements; i++) { - _whenStatements.add(arguments.get(i)); - } - for (int i = numWhenStatements; i < numWhenStatements * 2 + 1; i++) { - _elseThenStatements.add(arguments.get(i)); - } + private DataType calculateResultType() { + MutablePair> typeAndUnresolvedLiterals = + new MutablePair<>(DataType.UNKNOWN, new ArrayList<>()); + for (TransformFunction thenStatement : _thenStatements) { + upcast(typeAndUnresolvedLiterals, thenStatement); + } + if (_elseStatement != null) { + upcast(typeAndUnresolvedLiterals, _elseStatement); + } + DataType dataType = typeAndUnresolvedLiterals.getLeft(); + // If all inputs are of type UNKNOWN, resolve as type STRING + return dataType != DataType.UNKNOWN ? dataType : DataType.STRING; } - private TransformResultMetadata calculateResultMetadata() { - TransformFunction elseStatement = _elseThenStatements.get(0); - TransformResultMetadata elseStatementResultMetadata = elseStatement.getResultMetadata(); - DataType dataType = elseStatementResultMetadata.getDataType(); - Preconditions.checkState(elseStatementResultMetadata.isSingleValue(), - "Unsupported multi-value expression in the ELSE clause"); - int numThenStatements = _elseThenStatements.size() - 1; - for (int i = 0; i < numThenStatements; i++) { - TransformFunction thenStatement = _elseThenStatements.get(i + 1); - TransformResultMetadata thenStatementResultMetadata = thenStatement.getResultMetadata(); - if (!thenStatementResultMetadata.isSingleValue()) { - throw new IllegalStateException("Unsupported multi-value expression in the THEN clause of index: " + i); + private void upcast(MutablePair> currentTypeAndUnresolvedLiterals, + TransformFunction newFunction) { + TransformResultMetadata newMetadata = newFunction.getResultMetadata(); + Preconditions.checkArgument(newMetadata.isSingleValue(), "Unsupported multi-value expression in THEN/ELSE clause"); + DataType newType = newMetadata.getDataType(); + if (newType == DataType.UNKNOWN) { + return; + } + DataType currentType = currentTypeAndUnresolvedLiterals.getLeft(); + if (currentType == newType) { + return; + } + List unresolvedLiterals = currentTypeAndUnresolvedLiterals.getRight(); + // Treat string literals as UNKNOWN type. Resolve them when we get a non-UNKNOWN type. + boolean isNewFunctionStringLiteral = newFunction instanceof LiteralTransformFunction && newType == DataType.STRING; + if (currentType == DataType.UNKNOWN) { + if (isNewFunctionStringLiteral) { + unresolvedLiterals.add(((LiteralTransformFunction) newFunction).getStringLiteral()); + } else { + currentTypeAndUnresolvedLiterals.setLeft(newType); + for (String unresolvedLiteral : unresolvedLiterals) { + checkLiteral(newType, unresolvedLiteral); + } + unresolvedLiterals.clear(); } - DataType thenStatementDataType = thenStatementResultMetadata.getDataType(); - - // Upcast the data type to cover all the data types in THEN and ELSE clauses if they don't match - // For numeric types: - // - INT & LONG -> LONG - // - INT & FLOAT/DOUBLE -> DOUBLE - // - LONG & FLOAT/DOUBLE -> DOUBLE (might lose precision) - // - FLOAT & DOUBLE -> DOUBLE - // - Any numeric data type with BIG_DECIMAL -> BIG_DECIMAL - // Use STRING to handle non-numeric types - if (thenStatementDataType == dataType) { - continue; - } - switch (dataType) { - case INT: - switch (thenStatementDataType) { - case LONG: - dataType = DataType.LONG; - break; - case FLOAT: - case DOUBLE: - dataType = DataType.DOUBLE; - break; - case BIG_DECIMAL: - dataType = DataType.BIG_DECIMAL; - break; - default: - dataType = DataType.STRING; - break; - } - break; - case LONG: - switch (thenStatementDataType) { - case INT: - break; - case FLOAT: - case DOUBLE: - dataType = DataType.DOUBLE; - break; - case BIG_DECIMAL: - dataType = DataType.BIG_DECIMAL; - break; - default: - dataType = DataType.STRING; - break; - } - break; - case FLOAT: - switch (thenStatementDataType) { - case INT: - case LONG: - case DOUBLE: - dataType = DataType.DOUBLE; - break; - case BIG_DECIMAL: - dataType = DataType.BIG_DECIMAL; - break; - default: - dataType = DataType.STRING; - break; - } - break; - case DOUBLE: - switch (thenStatementDataType) { - case INT: - case FLOAT: - case LONG: - break; - case BIG_DECIMAL: - dataType = DataType.BIG_DECIMAL; - break; - default: - dataType = DataType.STRING; - break; - } - break; - case BIG_DECIMAL: - switch (thenStatementDataType) { - case INT: - case FLOAT: - case LONG: - case DOUBLE: - break; - default: - dataType = DataType.STRING; - break; - } - break; - default: - dataType = DataType.STRING; - break; + } else { + assert unresolvedLiterals.isEmpty(); + if (isNewFunctionStringLiteral) { + checkLiteral(currentType, ((LiteralTransformFunction) newFunction).getStringLiteral()); + } else { + // Only allow upcast from numeric to numeric: INT -> LONG -> FLOAT -> DOUBLE -> BIG_DECIMAL + Preconditions.checkArgument(currentType.isNumeric() && newType.isNumeric(), "Cannot upcast from %s to %s", + currentType, newType); + if (newType.ordinal() > currentType.ordinal()) { + currentTypeAndUnresolvedLiterals.setLeft(newType); + } } } - return new TransformResultMetadata(dataType, true, false); + } + + private void checkLiteral(DataType dataType, String literal) { + switch (dataType) { + case INT: + try { + Integer.parseInt(literal); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid literal: " + literal + " for INT"); + } + break; + case LONG: + try { + Long.parseLong(literal); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid literal: " + literal + " for LONG"); + } + break; + case FLOAT: + try { + Float.parseFloat(literal); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid literal: " + literal + " for FLOAT"); + } + break; + case DOUBLE: + try { + Double.parseDouble(literal); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid literal: " + literal + " for DOUBLE"); + } + break; + case BIG_DECIMAL: + try { + new BigDecimal(literal); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid literal: " + literal + " for BIG_DECIMAL"); + } + break; + case BOOLEAN: + Preconditions.checkArgument( + literal.equalsIgnoreCase("true") || literal.equalsIgnoreCase("false") || literal.equals("1") + || literal.equals("0"), "Invalid literal: %s for BOOLEAN", literal); + break; + case TIMESTAMP: + try { + TimestampUtils.toTimestamp(literal); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid literal: " + literal + " for TIMESTAMP"); + } + break; + case STRING: + case JSON: + break; + case BYTES: + try { + BytesUtils.toBytes(literal); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid literal: " + literal + " for BYTES"); + } + break; + default: + throw new IllegalStateException("Unsupported data type: " + dataType); + } } @Override @@ -241,62 +226,136 @@ public TransformResultMetadata getResultMetadata() { } /** - * Evaluate the ProjectionBlock for the WHEN statements, returns an array with the - * index(1 to N) of matched WHEN clause ordered by match priority, 0 means nothing - * matched, so go to ELSE. + * Evaluate the ValueBlock for the WHEN statements, returns an array with the index(1 to N) of matched WHEN clause -1 + * means there is no match. */ - private int[] getSelectedArray(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); + private int[] getSelectedArray(ValueBlock valueBlock, boolean nullHandlingEnabled) { + int numDocs = valueBlock.getNumDocs(); if (_selectedResults == null || _selectedResults.length < numDocs) { _selectedResults = new int[numDocs]; - } else { - Arrays.fill(_selectedResults, 0, numDocs, 0); - Arrays.fill(_selections, false); } + Arrays.fill(_selectedResults, -1); + Arrays.fill(_computeThenStatements, false); + BitSet unselectedDocs = new BitSet(); + unselectedDocs.set(0, numDocs); int numWhenStatements = _whenStatements.size(); - for (int i = numWhenStatements - 1; i >= 0; i--) { + for (int i = 0; i < numWhenStatements; i++) { TransformFunction whenStatement = _whenStatements.get(i); - int[] conditions = whenStatement.transformToIntValuesSV(projectionBlock); - for (int j = 0; j < numDocs & j < conditions.length; j++) { - _selectedResults[j] = Math.max(conditions[j] * (i + 1), _selectedResults[j]); + int[] conditions = getWhenConditions(whenStatement, valueBlock, nullHandlingEnabled); + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + if (conditions[docId] == 1) { + unselectedDocs.clear(docId); + _selectedResults[docId] = i; + } + } + if (unselectedDocs.isEmpty()) { + break; } } // try to prune clauses now for (int i = 0; i < numDocs; i++) { - _selections[_selectedResults[i]] = true; - } - int numSelections = 0; - for (boolean selection : _selections) { - if (selection) { - numSelections++; + if (_selectedResults[i] != -1) { + _computeThenStatements[_selectedResults[i]] = true; } } - _numSelections = numSelections; return _selectedResults; } + // Returns an array of valueBlock length to indicate whether a row is selected or not. + // When nullHandlingEnabled is set to true, we also check whether the row is null and set to false if null. + private static int[] getWhenConditions(TransformFunction whenStatement, ValueBlock valueBlock, + boolean nullHandlingEnabled) { + if (!nullHandlingEnabled) { + return whenStatement.transformToIntValuesSV(valueBlock); + } + int[] intResult = whenStatement.transformToIntValuesSV(valueBlock); + RoaringBitmap bitmap = whenStatement.getNullBitmap(valueBlock); + if (bitmap != null) { + for (int i : bitmap) { + intResult[i] = 0; + } + } + return intResult; + } + + @Override + protected int[] transformToIntValuesSVUsingValue(ValueBlock valueBlock) { + int[] selected = getSelectedArray(valueBlock, false); + int numDocs = valueBlock.getNumDocs(); + initIntValuesSV(numDocs); + int numThenStatements = _thenStatements.size(); + BitSet unselectedDocs = new BitSet(); + unselectedDocs.set(0, numDocs); + Map thenStatementsIndexToValues = new HashMap<>(); + for (int i = 0; i < numThenStatements; i++) { + if (_computeThenStatements[i]) { + thenStatementsIndexToValues.put(i, _thenStatements.get(i).transformToIntValuesSV(valueBlock)); + } + } + for (int docId = 0; docId < numDocs; docId++) { + if (selected[docId] >= 0) { + _intValuesSV[docId] = thenStatementsIndexToValues.get(selected[docId])[docId]; + unselectedDocs.clear(docId); + } + } + if (!unselectedDocs.isEmpty()) { + if (_elseStatement == null) { + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _intValuesSV[docId] = NullValuePlaceHolder.INT; + } + } else { + int[] intValues = _elseStatement.transformToIntValuesSV(valueBlock); + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _intValuesSV[docId] = intValues[docId]; + } + } + } + return _intValuesSV; + } + @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - if (_resultMetadata.getDataType().getStoredType() != DataType.INT) { - return super.transformToIntValuesSV(projectionBlock); - } - int[] selected = getSelectedArray(projectionBlock); - int numDocs = projectionBlock.getNumDocs(); - if (_intValuesSV == null) { - _intValuesSV = new int[numDocs]; - } - int numElseThenStatements = _elseThenStatements.size(); - for (int i = 0; i < numElseThenStatements; i++) { - if (_selections[i]) { - TransformFunction transformFunction = _elseThenStatements.get(i); - int[] intValues = transformFunction.transformToIntValuesSV(projectionBlock); - if (_numSelections == 1) { - System.arraycopy(intValues, 0, _intValuesSV, 0, numDocs); - } else { - for (int j = 0; j < numDocs; j++) { - if (selected[j] == i) { - _intValuesSV[j] = intValues[j]; - } + protected int[] transformToIntValuesSVUsingValueAndNull(ValueBlock valueBlock) { + final RoaringBitmap bitmap = new RoaringBitmap(); + int[] selected = getSelectedArray(valueBlock, true); + int numDocs = valueBlock.getNumDocs(); + initIntValuesSV(numDocs); + int numThenStatements = _thenStatements.size(); + BitSet unselectedDocs = new BitSet(); + unselectedDocs.set(0, numDocs); + Map> thenStatementsIndexToValues = new HashMap<>(); + for (int i = 0; i < numThenStatements; i++) { + if (_computeThenStatements[i]) { + thenStatementsIndexToValues.put(i, ImmutablePair.of(_thenStatements.get(i).transformToIntValuesSV(valueBlock), + _thenStatements.get(i).getNullBitmap(valueBlock))); + } + } + for (int docId = 0; docId < numDocs; docId++) { + if (selected[docId] >= 0) { + Pair nullValuePair = thenStatementsIndexToValues.get(selected[docId]); + _intValuesSV[docId] = nullValuePair.getLeft()[docId]; + RoaringBitmap nullBitmap = nullValuePair.getRight(); + if (nullBitmap != null && nullBitmap.contains(docId)) { + bitmap.add(docId); + } + unselectedDocs.clear(docId); + if (unselectedDocs.isEmpty()) { + break; + } + } + } + if (!unselectedDocs.isEmpty()) { + if (_elseStatement == null) { + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _intValuesSV[docId] = NullValuePlaceHolder.INT; + bitmap.add(docId); + } + } else { + int[] intValues = _elseStatement.transformToIntValuesSV(valueBlock); + RoaringBitmap nullBitmap = _elseStatement.getNullBitmap(valueBlock); + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _intValuesSV[docId] = intValues[docId]; + if (nullBitmap != null && nullBitmap.contains(docId)) { + bitmap.add(docId); } } } @@ -305,27 +364,83 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { } @Override - public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { - if (_resultMetadata.getDataType().getStoredType() != DataType.LONG) { - return super.transformToLongValuesSV(projectionBlock); - } - int[] selected = getSelectedArray(projectionBlock); - int numDocs = projectionBlock.getNumDocs(); - if (_longValuesSV == null) { - _longValuesSV = new long[numDocs]; - } - int numElseThenStatements = _elseThenStatements.size(); - for (int i = 0; i < numElseThenStatements; i++) { - if (_selections[i]) { - TransformFunction transformFunction = _elseThenStatements.get(i); - long[] longValues = transformFunction.transformToLongValuesSV(projectionBlock); - if (_numSelections == 1) { - System.arraycopy(longValues, 0, _longValuesSV, 0, numDocs); - } else { - for (int j = 0; j < numDocs; j++) { - if (selected[j] == i) { - _longValuesSV[j] = longValues[j]; - } + protected long[] transformToLongValuesSVUsingValue(ValueBlock valueBlock) { + int[] selected = getSelectedArray(valueBlock, false); + int numDocs = valueBlock.getNumDocs(); + initLongValuesSV(numDocs); + int numThenStatements = _thenStatements.size(); + BitSet unselectedDocs = new BitSet(); + unselectedDocs.set(0, numDocs); + Map thenStatementsIndexToValues = new HashMap<>(); + for (int i = 0; i < numThenStatements; i++) { + if (_computeThenStatements[i]) { + thenStatementsIndexToValues.put(i, _thenStatements.get(i).transformToLongValuesSV(valueBlock)); + } + } + for (int docId = 0; docId < numDocs; docId++) { + if (selected[docId] >= 0) { + _longValuesSV[docId] = thenStatementsIndexToValues.get(selected[docId])[docId]; + unselectedDocs.clear(docId); + } + } + if (!unselectedDocs.isEmpty()) { + if (_elseStatement == null) { + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _longValuesSV[docId] = NullValuePlaceHolder.LONG; + } + } else { + long[] longValues = _elseStatement.transformToLongValuesSV(valueBlock); + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _longValuesSV[docId] = longValues[docId]; + } + } + } + return _longValuesSV; + } + + @Override + protected long[] transformToLongValuesSVUsingValueAndNull(ValueBlock valueBlock) { + final RoaringBitmap bitmap = new RoaringBitmap(); + int[] selected = getSelectedArray(valueBlock, true); + int numDocs = valueBlock.getNumDocs(); + initLongValuesSV(numDocs); + int numThenStatements = _thenStatements.size(); + BitSet unselectedDocs = new BitSet(); + unselectedDocs.set(0, numDocs); + Map> thenStatementsIndexToValues = new HashMap<>(); + for (int i = 0; i < numThenStatements; i++) { + if (_computeThenStatements[i]) { + thenStatementsIndexToValues.put(i, ImmutablePair.of(_thenStatements.get(i).transformToLongValuesSV(valueBlock), + _thenStatements.get(i).getNullBitmap(valueBlock))); + } + } + for (int docId = 0; docId < numDocs; docId++) { + if (selected[docId] >= 0) { + Pair nullValuePair = thenStatementsIndexToValues.get(selected[docId]); + _longValuesSV[docId] = nullValuePair.getLeft()[docId]; + RoaringBitmap nullBitmap = nullValuePair.getRight(); + if (nullBitmap != null && nullBitmap.contains(docId)) { + bitmap.add(docId); + } + unselectedDocs.clear(docId); + if (unselectedDocs.isEmpty()) { + break; + } + } + } + if (!unselectedDocs.isEmpty()) { + if (_elseStatement == null) { + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _longValuesSV[docId] = NullValuePlaceHolder.LONG; + bitmap.add(docId); + } + } else { + long[] longValues = _elseStatement.transformToLongValuesSV(valueBlock); + RoaringBitmap nullBitmap = _elseStatement.getNullBitmap(valueBlock); + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _longValuesSV[docId] = longValues[docId]; + if (nullBitmap != null && nullBitmap.contains(docId)) { + bitmap.add(docId); } } } @@ -334,27 +449,83 @@ public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { } @Override - public float[] transformToFloatValuesSV(ProjectionBlock projectionBlock) { - if (_resultMetadata.getDataType().getStoredType() != DataType.FLOAT) { - return super.transformToFloatValuesSV(projectionBlock); - } - int[] selected = getSelectedArray(projectionBlock); - int numDocs = projectionBlock.getNumDocs(); - if (_floatValuesSV == null) { - _floatValuesSV = new float[numDocs]; - } - int numElseThenStatements = _elseThenStatements.size(); - for (int i = 0; i < numElseThenStatements; i++) { - if (_selections[i]) { - TransformFunction transformFunction = _elseThenStatements.get(i); - float[] floatValues = transformFunction.transformToFloatValuesSV(projectionBlock); - if (_numSelections == 1) { - System.arraycopy(floatValues, 0, _floatValuesSV, 0, numDocs); - } else { - for (int j = 0; j < numDocs; j++) { - if (selected[j] == i) { - _floatValuesSV[j] = floatValues[j]; - } + protected float[] transformToFloatValuesSVUsingValue(ValueBlock valueBlock) { + int[] selected = getSelectedArray(valueBlock, false); + int numDocs = valueBlock.getNumDocs(); + initFloatValuesSV(numDocs); + int numThenStatements = _thenStatements.size(); + BitSet unselectedDocs = new BitSet(); + unselectedDocs.set(0, numDocs); + Map thenStatementsIndexToValues = new HashMap<>(); + for (int i = 0; i < numThenStatements; i++) { + if (_computeThenStatements[i]) { + thenStatementsIndexToValues.put(i, _thenStatements.get(i).transformToFloatValuesSV(valueBlock)); + } + } + for (int docId = 0; docId < numDocs; docId++) { + if (selected[docId] >= 0) { + _floatValuesSV[docId] = thenStatementsIndexToValues.get(selected[docId])[docId]; + unselectedDocs.clear(docId); + } + } + if (!unselectedDocs.isEmpty()) { + if (_elseStatement == null) { + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _floatValuesSV[docId] = NullValuePlaceHolder.FLOAT; + } + } else { + float[] floatValues = _elseStatement.transformToFloatValuesSV(valueBlock); + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _floatValuesSV[docId] = floatValues[docId]; + } + } + } + return _floatValuesSV; + } + + @Override + protected float[] transformToFloatValuesSVUsingValueAndNull(ValueBlock valueBlock) { + final RoaringBitmap bitmap = new RoaringBitmap(); + int[] selected = getSelectedArray(valueBlock, true); + int numDocs = valueBlock.getNumDocs(); + initFloatValuesSV(numDocs); + int numThenStatements = _thenStatements.size(); + BitSet unselectedDocs = new BitSet(); + unselectedDocs.set(0, numDocs); + Map> thenStatementsIndexToValues = new HashMap<>(); + for (int i = 0; i < numThenStatements; i++) { + if (_computeThenStatements[i]) { + thenStatementsIndexToValues.put(i, ImmutablePair.of(_thenStatements.get(i).transformToFloatValuesSV(valueBlock), + _thenStatements.get(i).getNullBitmap(valueBlock))); + } + } + for (int docId = 0; docId < numDocs; docId++) { + if (selected[docId] >= 0) { + Pair nullValuePair = thenStatementsIndexToValues.get(selected[docId]); + _floatValuesSV[docId] = nullValuePair.getLeft()[docId]; + RoaringBitmap nullBitmap = nullValuePair.getRight(); + if (nullBitmap != null && nullBitmap.contains(docId)) { + bitmap.add(docId); + } + unselectedDocs.clear(docId); + if (unselectedDocs.isEmpty()) { + break; + } + } + } + if (!unselectedDocs.isEmpty()) { + if (_elseStatement == null) { + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _floatValuesSV[docId] = NullValuePlaceHolder.FLOAT; + bitmap.add(docId); + } + } else { + float[] floatValues = _elseStatement.transformToFloatValuesSV(valueBlock); + RoaringBitmap nullBitmap = _elseStatement.getNullBitmap(valueBlock); + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _floatValuesSV[docId] = floatValues[docId]; + if (nullBitmap != null && nullBitmap.contains(docId)) { + bitmap.add(docId); } } } @@ -363,27 +534,84 @@ public float[] transformToFloatValuesSV(ProjectionBlock projectionBlock) { } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { - if (_resultMetadata.getDataType().getStoredType() != DataType.DOUBLE) { - return super.transformToDoubleValuesSV(projectionBlock); - } - int[] selected = getSelectedArray(projectionBlock); - int numDocs = projectionBlock.getNumDocs(); - if (_doubleValuesSV == null) { - _doubleValuesSV = new double[numDocs]; - } - int numElseThenStatements = _elseThenStatements.size(); - for (int i = 0; i < numElseThenStatements; i++) { - if (_selections[i]) { - TransformFunction transformFunction = _elseThenStatements.get(i); - double[] doubleValues = transformFunction.transformToDoubleValuesSV(projectionBlock); - if (_numSelections == 1) { - System.arraycopy(doubleValues, 0, _doubleValuesSV, 0, numDocs); - } else { - for (int j = 0; j < numDocs; j++) { - if (selected[j] == i) { - _doubleValuesSV[j] = doubleValues[j]; - } + protected double[] transformToDoubleValuesSVUsingValue(ValueBlock valueBlock) { + int[] selected = getSelectedArray(valueBlock, false); + int numDocs = valueBlock.getNumDocs(); + initDoubleValuesSV(numDocs); + int numThenStatements = _thenStatements.size(); + BitSet unselectedDocs = new BitSet(); + unselectedDocs.set(0, numDocs); + Map thenStatementsIndexToValues = new HashMap<>(); + for (int i = 0; i < numThenStatements; i++) { + if (_computeThenStatements[i]) { + thenStatementsIndexToValues.put(i, _thenStatements.get(i).transformToDoubleValuesSV(valueBlock)); + } + } + for (int docId = 0; docId < numDocs; docId++) { + if (selected[docId] >= 0) { + _doubleValuesSV[docId] = thenStatementsIndexToValues.get(selected[docId])[docId]; + unselectedDocs.clear(docId); + } + } + if (!unselectedDocs.isEmpty()) { + if (_elseStatement == null) { + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _doubleValuesSV[docId] = NullValuePlaceHolder.DOUBLE; + } + } else { + double[] doubleValues = _elseStatement.transformToDoubleValuesSV(valueBlock); + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _doubleValuesSV[docId] = doubleValues[docId]; + } + } + } + return _doubleValuesSV; + } + + @Override + protected double[] transformToDoubleValuesSVUsingValueAndNull(ValueBlock valueBlock) { + final RoaringBitmap bitmap = new RoaringBitmap(); + int[] selected = getSelectedArray(valueBlock, true); + int numDocs = valueBlock.getNumDocs(); + initDoubleValuesSV(numDocs); + int numThenStatements = _thenStatements.size(); + BitSet unselectedDocs = new BitSet(); + unselectedDocs.set(0, numDocs); + Map> thenStatementsIndexToValues = new HashMap<>(); + for (int i = 0; i < numThenStatements; i++) { + if (_computeThenStatements[i]) { + thenStatementsIndexToValues.put(i, + ImmutablePair.of(_thenStatements.get(i).transformToDoubleValuesSV(valueBlock), + _thenStatements.get(i).getNullBitmap(valueBlock))); + } + } + for (int docId = 0; docId < numDocs; docId++) { + if (selected[docId] >= 0) { + Pair nullValuePair = thenStatementsIndexToValues.get(selected[docId]); + _doubleValuesSV[docId] = nullValuePair.getLeft()[docId]; + RoaringBitmap nullBitmap = nullValuePair.getRight(); + if (nullBitmap != null && nullBitmap.contains(docId)) { + bitmap.add(docId); + } + unselectedDocs.clear(docId); + if (unselectedDocs.isEmpty()) { + break; + } + } + } + if (!unselectedDocs.isEmpty()) { + if (_elseStatement == null) { + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _doubleValuesSV[docId] = NullValuePlaceHolder.DOUBLE; + bitmap.add(docId); + } + } else { + double[] doubleValues = _elseStatement.transformToDoubleValuesSV(valueBlock); + RoaringBitmap nullBitmap = _elseStatement.getNullBitmap(valueBlock); + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _doubleValuesSV[docId] = doubleValues[docId]; + if (nullBitmap != null && nullBitmap.contains(docId)) { + bitmap.add(docId); } } } @@ -392,27 +620,84 @@ public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { } @Override - public BigDecimal[] transformToBigDecimalValuesSV(ProjectionBlock projectionBlock) { - if (_resultMetadata.getDataType() != DataType.BIG_DECIMAL) { - return super.transformToBigDecimalValuesSV(projectionBlock); - } - int[] selected = getSelectedArray(projectionBlock); - int numDocs = projectionBlock.getNumDocs(); - if (_bigDecimalValuesSV == null) { - _bigDecimalValuesSV = new BigDecimal[numDocs]; - } - int numElseThenStatements = _elseThenStatements.size(); - for (int i = 0; i < numElseThenStatements; i++) { - if (_selections[i]) { - TransformFunction transformFunction = _elseThenStatements.get(i); - BigDecimal[] bigDecimalValues = transformFunction.transformToBigDecimalValuesSV(projectionBlock); - if (_numSelections == 1) { - System.arraycopy(bigDecimalValues, 0, _bigDecimalValuesSV, 0, numDocs); - } else { - for (int j = 0; j < numDocs; j++) { - if (selected[j] == i) { - _bigDecimalValuesSV[j] = bigDecimalValues[j]; - } + protected BigDecimal[] transformToBigDecimalValuesSVUsingValue(ValueBlock valueBlock) { + int[] selected = getSelectedArray(valueBlock, false); + int numDocs = valueBlock.getNumDocs(); + initBigDecimalValuesSV(numDocs); + int numThenStatements = _thenStatements.size(); + BitSet unselectedDocs = new BitSet(); + unselectedDocs.set(0, numDocs); + Map thenStatementsIndexToValues = new HashMap<>(); + for (int i = 0; i < numThenStatements; i++) { + if (_computeThenStatements[i]) { + thenStatementsIndexToValues.put(i, _thenStatements.get(i).transformToBigDecimalValuesSV(valueBlock)); + } + } + for (int docId = 0; docId < numDocs; docId++) { + if (selected[docId] >= 0) { + _bigDecimalValuesSV[docId] = thenStatementsIndexToValues.get(selected[docId])[docId]; + unselectedDocs.clear(docId); + } + } + if (!unselectedDocs.isEmpty()) { + if (_elseStatement == null) { + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _bigDecimalValuesSV[docId] = NullValuePlaceHolder.BIG_DECIMAL; + } + } else { + BigDecimal[] bigDecimalValues = _elseStatement.transformToBigDecimalValuesSV(valueBlock); + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _bigDecimalValuesSV[docId] = bigDecimalValues[docId]; + } + } + } + return _bigDecimalValuesSV; + } + + @Override + protected BigDecimal[] transformToBigDecimalValuesSVUsingValueAndNull(ValueBlock valueBlock) { + final RoaringBitmap bitmap = new RoaringBitmap(); + int[] selected = getSelectedArray(valueBlock, true); + int numDocs = valueBlock.getNumDocs(); + initBigDecimalValuesSV(numDocs); + int numThenStatements = _thenStatements.size(); + BitSet unselectedDocs = new BitSet(); + unselectedDocs.set(0, numDocs); + Map> thenStatementsIndexToValues = new HashMap<>(); + for (int i = 0; i < numThenStatements; i++) { + if (_computeThenStatements[i]) { + thenStatementsIndexToValues.put(i, + ImmutablePair.of(_thenStatements.get(i).transformToBigDecimalValuesSV(valueBlock), + _thenStatements.get(i).getNullBitmap(valueBlock))); + } + } + for (int docId = 0; docId < numDocs; docId++) { + if (selected[docId] >= 0) { + Pair nullValuePair = thenStatementsIndexToValues.get(selected[docId]); + _bigDecimalValuesSV[docId] = nullValuePair.getLeft()[docId]; + RoaringBitmap nullBitmap = nullValuePair.getRight(); + if (nullBitmap != null && nullBitmap.contains(docId)) { + bitmap.add(docId); + } + unselectedDocs.clear(docId); + if (unselectedDocs.isEmpty()) { + break; + } + } + } + if (!unselectedDocs.isEmpty()) { + if (_elseStatement == null) { + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _bigDecimalValuesSV[docId] = NullValuePlaceHolder.BIG_DECIMAL; + bitmap.add(docId); + } + } else { + BigDecimal[] bigDecimalValues = _elseStatement.transformToBigDecimalValuesSV(valueBlock); + RoaringBitmap nullBitmap = _elseStatement.getNullBitmap(valueBlock); + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _bigDecimalValuesSV[docId] = bigDecimalValues[docId]; + if (nullBitmap != null && nullBitmap.contains(docId)) { + bitmap.add(docId); } } } @@ -421,27 +706,84 @@ public BigDecimal[] transformToBigDecimalValuesSV(ProjectionBlock projectionBloc } @Override - public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { - if (_resultMetadata.getDataType().getStoredType() != DataType.STRING) { - return super.transformToStringValuesSV(projectionBlock); - } - int[] selected = getSelectedArray(projectionBlock); - int numDocs = projectionBlock.getNumDocs(); - if (_stringValuesSV == null) { - _stringValuesSV = new String[numDocs]; - } - int numElseThenStatements = _elseThenStatements.size(); - for (int i = 0; i < numElseThenStatements; i++) { - if (_selections[i]) { - TransformFunction transformFunction = _elseThenStatements.get(i); - String[] stringValues = transformFunction.transformToStringValuesSV(projectionBlock); - if (_numSelections == 1) { - System.arraycopy(stringValues, 0, _stringValuesSV, 0, numDocs); - } else { - for (int j = 0; j < numDocs; j++) { - if (selected[j] == i) { - _stringValuesSV[j] = stringValues[j]; - } + protected String[] transformToStringValuesSVUsingValue(ValueBlock valueBlock) { + int[] selected = getSelectedArray(valueBlock, false); + int numDocs = valueBlock.getNumDocs(); + initStringValuesSV(numDocs); + int numThenStatements = _thenStatements.size(); + BitSet unselectedDocs = new BitSet(); + unselectedDocs.set(0, numDocs); + Map thenStatementsIndexToValues = new HashMap<>(); + for (int i = 0; i < numThenStatements; i++) { + if (_computeThenStatements[i]) { + thenStatementsIndexToValues.put(i, _thenStatements.get(i).transformToStringValuesSV(valueBlock)); + } + } + for (int docId = 0; docId < numDocs; docId++) { + if (selected[docId] >= 0) { + _stringValuesSV[docId] = thenStatementsIndexToValues.get(selected[docId])[docId]; + unselectedDocs.clear(docId); + } + } + if (!unselectedDocs.isEmpty()) { + if (_elseStatement == null) { + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _stringValuesSV[docId] = NullValuePlaceHolder.STRING; + } + } else { + String[] stringValues = _elseStatement.transformToStringValuesSV(valueBlock); + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _stringValuesSV[docId] = stringValues[docId]; + } + } + } + return _stringValuesSV; + } + + @Override + protected String[] transformToStringValuesSVUsingValueAndNull(ValueBlock valueBlock) { + final RoaringBitmap bitmap = new RoaringBitmap(); + int[] selected = getSelectedArray(valueBlock, true); + int numDocs = valueBlock.getNumDocs(); + initStringValuesSV(numDocs); + int numThenStatements = _thenStatements.size(); + BitSet unselectedDocs = new BitSet(); + unselectedDocs.set(0, numDocs); + Map> thenStatementsIndexToValues = new HashMap<>(); + for (int i = 0; i < numThenStatements; i++) { + if (_computeThenStatements[i]) { + thenStatementsIndexToValues.put(i, + ImmutablePair.of(_thenStatements.get(i).transformToStringValuesSV(valueBlock), + _thenStatements.get(i).getNullBitmap(valueBlock))); + } + } + for (int docId = 0; docId < numDocs; docId++) { + if (selected[docId] >= 0) { + Pair nullValuePair = thenStatementsIndexToValues.get(selected[docId]); + _stringValuesSV[docId] = nullValuePair.getLeft()[docId]; + RoaringBitmap nullBitmap = nullValuePair.getRight(); + if (nullBitmap != null && nullBitmap.contains(docId)) { + bitmap.add(docId); + } + unselectedDocs.clear(docId); + if (unselectedDocs.isEmpty()) { + break; + } + } + } + if (!unselectedDocs.isEmpty()) { + if (_elseStatement == null) { + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _stringValuesSV[docId] = NullValuePlaceHolder.STRING; + bitmap.add(docId); + } + } else { + String[] stringValues = _elseStatement.transformToStringValuesSV(valueBlock); + RoaringBitmap nullBitmap = _elseStatement.getNullBitmap(valueBlock); + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _stringValuesSV[docId] = stringValues[docId]; + if (nullBitmap != null && nullBitmap.contains(docId)) { + bitmap.add(docId); } } } @@ -450,31 +792,130 @@ public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { } @Override - public byte[][] transformToBytesValuesSV(ProjectionBlock projectionBlock) { - if (_resultMetadata.getDataType().getStoredType() != DataType.BYTES) { - return super.transformToBytesValuesSV(projectionBlock); - } - int[] selected = getSelectedArray(projectionBlock); - int numDocs = projectionBlock.getNumDocs(); - if (_bytesValuesSV == null) { - _bytesValuesSV = new byte[numDocs][]; - } - int numElseThenStatements = _elseThenStatements.size(); - for (int i = 0; i < numElseThenStatements; i++) { - if (_selections[i]) { - TransformFunction transformFunction = _elseThenStatements.get(i); - byte[][] bytesValues = transformFunction.transformToBytesValuesSV(projectionBlock); - if (_numSelections == 1) { - System.arraycopy(bytesValues, 0, _bytesValuesSV, 0, numDocs); - } else { - for (int j = 0; j < numDocs; j++) { - if (selected[j] == i) { - _bytesValuesSV[j] = bytesValues[j]; - } + protected byte[][] transformToBytesValuesSVUsingValue(ValueBlock valueBlock) { + int[] selected = getSelectedArray(valueBlock, false); + int numDocs = valueBlock.getNumDocs(); + initBytesValuesSV(numDocs); + int numThenStatements = _thenStatements.size(); + BitSet unselectedDocs = new BitSet(); + unselectedDocs.set(0, numDocs); + Map thenStatementsIndexToValues = new HashMap<>(); + for (int i = 0; i < numThenStatements; i++) { + if (_computeThenStatements[i]) { + thenStatementsIndexToValues.put(i, _thenStatements.get(i).transformToBytesValuesSV(valueBlock)); + } + } + for (int docId = 0; docId < numDocs; docId++) { + if (selected[docId] >= 0) { + _bytesValuesSV[docId] = thenStatementsIndexToValues.get(selected[docId])[docId]; + unselectedDocs.clear(docId); + } + } + if (!unselectedDocs.isEmpty()) { + if (_elseStatement == null) { + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _bytesValuesSV[docId] = NullValuePlaceHolder.BYTES; + } + } else { + byte[][] bytesValues = _elseStatement.transformToBytesValuesSV(valueBlock); + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _bytesValuesSV[docId] = bytesValues[docId]; + } + } + } + return _bytesValuesSV; + } + + @Override + protected byte[][] transformToBytesValuesSVUsingValueAndNull(ValueBlock valueBlock) { + final RoaringBitmap bitmap = new RoaringBitmap(); + int[] selected = getSelectedArray(valueBlock, true); + int numDocs = valueBlock.getNumDocs(); + initStringValuesSV(numDocs); + int numThenStatements = _thenStatements.size(); + BitSet unselectedDocs = new BitSet(); + unselectedDocs.set(0, numDocs); + Map> thenStatementsIndexToValues = new HashMap<>(); + for (int i = 0; i < numThenStatements; i++) { + if (_computeThenStatements[i]) { + thenStatementsIndexToValues.put(i, ImmutablePair.of(_thenStatements.get(i).transformToBytesValuesSV(valueBlock), + _thenStatements.get(i).getNullBitmap(valueBlock))); + } + } + for (int docId = 0; docId < numDocs; docId++) { + if (selected[docId] >= 0) { + Pair nullValuePair = thenStatementsIndexToValues.get(selected[docId]); + _bytesValuesSV[docId] = nullValuePair.getLeft()[docId]; + RoaringBitmap nullBitmap = nullValuePair.getRight(); + if (nullBitmap != null && nullBitmap.contains(docId)) { + bitmap.add(docId); + } + unselectedDocs.clear(docId); + if (unselectedDocs.isEmpty()) { + break; + } + } + } + if (!unselectedDocs.isEmpty()) { + if (_elseStatement == null) { + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _bytesValuesSV[docId] = NullValuePlaceHolder.BYTES; + bitmap.add(docId); + } + } else { + byte[][] bytesValues = _elseStatement.transformToBytesValuesSV(valueBlock); + RoaringBitmap nullBitmap = _elseStatement.getNullBitmap(valueBlock); + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + _bytesValuesSV[docId] = bytesValues[docId]; + if (nullBitmap != null && nullBitmap.contains(docId)) { + bitmap.add(docId); } } } } return _bytesValuesSV; } + + @Override + public RoaringBitmap getNullBitmap(ValueBlock valueBlock) { + int[] selected = getSelectedArray(valueBlock, true); + int numDocs = valueBlock.getNumDocs(); + int numThenStatements = _thenStatements.size(); + BitSet unselectedDocs = new BitSet(); + unselectedDocs.set(0, numDocs); + final RoaringBitmap bitmap = new RoaringBitmap(); + Map thenStatementsIndexToValues = new HashMap<>(); + for (int i = 0; i < numThenStatements; i++) { + if (_computeThenStatements[i]) { + thenStatementsIndexToValues.put(i, _thenStatements.get(i).getNullBitmap(valueBlock)); + } + } + for (int docId = 0; docId < numDocs; docId++) { + if (selected[docId] >= 0) { + RoaringBitmap nullBitmap = thenStatementsIndexToValues.get(selected[docId]); + if (nullBitmap != null && nullBitmap.contains(docId)) { + bitmap.add(docId); + } + unselectedDocs.clear(docId); + } + } + if (!unselectedDocs.isEmpty()) { + if (_elseStatement == null) { + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + bitmap.add(docId); + } + } else { + RoaringBitmap nullBitmap = _elseStatement.getNullBitmap(valueBlock); + for (int docId = unselectedDocs.nextSetBit(0); docId >= 0; docId = unselectedDocs.nextSetBit(docId + 1)) { + if (nullBitmap != null && nullBitmap.contains(docId)) { + bitmap.add(docId); + } + } + } + } + if (bitmap.isEmpty()) { + return null; + } + return bitmap; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/CastTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/CastTransformFunction.java index 52ab528e8973..02d6a42629c5 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/CastTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/CastTransformFunction.java @@ -22,9 +22,9 @@ import java.math.BigDecimal; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.utils.ArrayCopyUtils; @@ -42,7 +42,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); // Check that there are more than 1 arguments if (arguments.size() != 2) { throw new IllegalArgumentException("Exactly 2 arguments are required for CAST transform function"); @@ -54,7 +55,7 @@ public void init(List arguments, Map data boolean sourceSV = sourceMetadata.isSingleValue(); TransformFunction castFormatTransformFunction = arguments.get(1); if (castFormatTransformFunction instanceof LiteralTransformFunction) { - String targetType = ((LiteralTransformFunction) castFormatTransformFunction).getLiteral().toUpperCase(); + String targetType = ((LiteralTransformFunction) castFormatTransformFunction).getStringLiteral().toUpperCase(); switch (targetType) { case "INT": case "INTEGER": @@ -87,6 +88,23 @@ public void init(List arguments, Map data case "VARCHAR": _resultMetadata = sourceSV ? STRING_SV_NO_DICTIONARY_METADATA : STRING_MV_NO_DICTIONARY_METADATA; break; + case "INT_ARRAY": + case "INTEGER_ARRAY": + _resultMetadata = INT_MV_NO_DICTIONARY_METADATA; + break; + case "LONG_ARRAY": + _resultMetadata = LONG_MV_NO_DICTIONARY_METADATA; + break; + case "FLOAT_ARRAY": + _resultMetadata = FLOAT_MV_NO_DICTIONARY_METADATA; + break; + case "DOUBLE_ARRAY": + _resultMetadata = DOUBLE_MV_NO_DICTIONARY_METADATA; + break; + case "STRING_ARRAY": + case "VARCHAR_ARRAY": + _resultMetadata = STRING_MV_NO_DICTIONARY_METADATA; + break; case "JSON": _resultMetadata = sourceSV ? JSON_SV_NO_DICTIONARY_METADATA : JSON_MV_NO_DICTIONARY_METADATA; break; @@ -104,46 +122,44 @@ public TransformResultMetadata getResultMetadata() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { + public int[] transformToIntValuesSV(ValueBlock valueBlock) { switch (_resultMetadata.getDataType()) { case INT: - return _transformFunction.transformToIntValuesSV(projectionBlock); + return _transformFunction.transformToIntValuesSV(valueBlock); case BOOLEAN: - return transformToBooleanValuesSV(projectionBlock); + return transformToBooleanValuesSV(valueBlock); default: - return super.transformToIntValuesSV(projectionBlock); + return super.transformToIntValuesSV(valueBlock); } } // TODO: Add it to the interface - private int[] transformToBooleanValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_intValuesSV == null) { - _intValuesSV = new int[length]; - } + private int[] transformToBooleanValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initIntValuesSV(length); switch (_sourceDataType.getStoredType()) { case INT: - int[] intValues = _transformFunction.transformToIntValuesSV(projectionBlock); + int[] intValues = _transformFunction.transformToIntValuesSV(valueBlock); ArrayCopyUtils.copyToBoolean(intValues, _intValuesSV, length); break; case LONG: - long[] longValues = _transformFunction.transformToLongValuesSV(projectionBlock); + long[] longValues = _transformFunction.transformToLongValuesSV(valueBlock); ArrayCopyUtils.copyToBoolean(longValues, _intValuesSV, length); break; case FLOAT: - float[] floatValues = _transformFunction.transformToFloatValuesSV(projectionBlock); + float[] floatValues = _transformFunction.transformToFloatValuesSV(valueBlock); ArrayCopyUtils.copyToBoolean(floatValues, _intValuesSV, length); break; case DOUBLE: - double[] doubleValues = _transformFunction.transformToDoubleValuesSV(projectionBlock); + double[] doubleValues = _transformFunction.transformToDoubleValuesSV(valueBlock); ArrayCopyUtils.copyToBoolean(doubleValues, _intValuesSV, length); break; case BIG_DECIMAL: - BigDecimal[] bigDecimalValues = _transformFunction.transformToBigDecimalValuesSV(projectionBlock); + BigDecimal[] bigDecimalValues = _transformFunction.transformToBigDecimalValuesSV(valueBlock); ArrayCopyUtils.copyToBoolean(bigDecimalValues, _intValuesSV, length); break; case STRING: - String[] stringValues = _transformFunction.transformToStringValuesSV(projectionBlock); + String[] stringValues = _transformFunction.transformToStringValuesSV(valueBlock); ArrayCopyUtils.copyToBoolean(stringValues, _intValuesSV, length); break; default: @@ -153,119 +169,111 @@ private int[] transformToBooleanValuesSV(ProjectionBlock projectionBlock) { } @Override - public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { + public long[] transformToLongValuesSV(ValueBlock valueBlock) { switch (_resultMetadata.getDataType()) { case LONG: - return _transformFunction.transformToLongValuesSV(projectionBlock); + return _transformFunction.transformToLongValuesSV(valueBlock); case TIMESTAMP: - return transformToTimestampValuesSV(projectionBlock); + return transformToTimestampValuesSV(valueBlock); default: - return super.transformToLongValuesSV(projectionBlock); + return super.transformToLongValuesSV(valueBlock); } } // TODO: Add it to the interface - private long[] transformToTimestampValuesSV(ProjectionBlock projectionBlock) { + private long[] transformToTimestampValuesSV(ValueBlock valueBlock) { if (_sourceDataType.getStoredType() == DataType.STRING) { - int length = projectionBlock.getNumDocs(); - if (_longValuesSV == null) { - _longValuesSV = new long[length]; - } - String[] stringValues = _transformFunction.transformToStringValuesSV(projectionBlock); + int length = valueBlock.getNumDocs(); + initLongValuesSV(length); + String[] stringValues = _transformFunction.transformToStringValuesSV(valueBlock); ArrayCopyUtils.copyToTimestamp(stringValues, _longValuesSV, length); return _longValuesSV; } else { - return _transformFunction.transformToLongValuesSV(projectionBlock); + return _transformFunction.transformToLongValuesSV(valueBlock); } } @Override - public float[] transformToFloatValuesSV(ProjectionBlock projectionBlock) { + public float[] transformToFloatValuesSV(ValueBlock valueBlock) { if (_resultMetadata.getDataType().getStoredType() == DataType.FLOAT) { - return _transformFunction.transformToFloatValuesSV(projectionBlock); + return _transformFunction.transformToFloatValuesSV(valueBlock); } else { - return super.transformToFloatValuesSV(projectionBlock); + return super.transformToFloatValuesSV(valueBlock); } } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { if (_resultMetadata.getDataType().getStoredType() == DataType.DOUBLE) { - return _transformFunction.transformToDoubleValuesSV(projectionBlock); + return _transformFunction.transformToDoubleValuesSV(valueBlock); } else { - return super.transformToDoubleValuesSV(projectionBlock); + return super.transformToDoubleValuesSV(valueBlock); } } @Override - public BigDecimal[] transformToBigDecimalValuesSV(ProjectionBlock projectionBlock) { + public BigDecimal[] transformToBigDecimalValuesSV(ValueBlock valueBlock) { if (_resultMetadata.getDataType().getStoredType() == DataType.BIG_DECIMAL) { - return _transformFunction.transformToBigDecimalValuesSV(projectionBlock); + return _transformFunction.transformToBigDecimalValuesSV(valueBlock); } else { - return super.transformToBigDecimalValuesSV(projectionBlock); + return super.transformToBigDecimalValuesSV(valueBlock); } } @Override - public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { + public String[] transformToStringValuesSV(ValueBlock valueBlock) { DataType resultDataType = _resultMetadata.getDataType(); if (resultDataType.getStoredType() == DataType.STRING) { switch (_sourceDataType) { case BOOLEAN: - int length = projectionBlock.getNumDocs(); - if (_stringValuesSV == null) { - _stringValuesSV = new String[length]; - } - int[] intValues = _transformFunction.transformToIntValuesSV(projectionBlock); + int length = valueBlock.getNumDocs(); + initStringValuesSV(length); + int[] intValues = _transformFunction.transformToIntValuesSV(valueBlock); ArrayCopyUtils.copyFromBoolean(intValues, _stringValuesSV, length); return _stringValuesSV; case TIMESTAMP: - length = projectionBlock.getNumDocs(); - if (_stringValuesSV == null) { - _stringValuesSV = new String[length]; - } - long[] longValues = _transformFunction.transformToLongValuesSV(projectionBlock); + length = valueBlock.getNumDocs(); + initStringValuesSV(length); + long[] longValues = _transformFunction.transformToLongValuesSV(valueBlock); ArrayCopyUtils.copyFromTimestamp(longValues, _stringValuesSV, length); return _stringValuesSV; default: - return _transformFunction.transformToStringValuesSV(projectionBlock); + return _transformFunction.transformToStringValuesSV(valueBlock); } } else { - int length = projectionBlock.getNumDocs(); - if (_stringValuesSV == null) { - _stringValuesSV = new String[length]; - } + int length = valueBlock.getNumDocs(); + initStringValuesSV(length); switch (resultDataType) { case INT: - int[] intValues = _transformFunction.transformToIntValuesSV(projectionBlock); + int[] intValues = _transformFunction.transformToIntValuesSV(valueBlock); ArrayCopyUtils.copy(intValues, _stringValuesSV, length); break; case LONG: - long[] longValues = _transformFunction.transformToLongValuesSV(projectionBlock); + long[] longValues = _transformFunction.transformToLongValuesSV(valueBlock); ArrayCopyUtils.copy(longValues, _stringValuesSV, length); break; case FLOAT: - float[] floatValues = _transformFunction.transformToFloatValuesSV(projectionBlock); + float[] floatValues = _transformFunction.transformToFloatValuesSV(valueBlock); ArrayCopyUtils.copy(floatValues, _stringValuesSV, length); break; case DOUBLE: - double[] doubleValues = _transformFunction.transformToDoubleValuesSV(projectionBlock); + double[] doubleValues = _transformFunction.transformToDoubleValuesSV(valueBlock); ArrayCopyUtils.copy(doubleValues, _stringValuesSV, length); break; case BIG_DECIMAL: - BigDecimal[] bigDecimalValues = _transformFunction.transformToBigDecimalValuesSV(projectionBlock); + BigDecimal[] bigDecimalValues = _transformFunction.transformToBigDecimalValuesSV(valueBlock); ArrayCopyUtils.copy(bigDecimalValues, _stringValuesSV, length); break; case BOOLEAN: - intValues = transformToBooleanValuesSV(projectionBlock); + intValues = transformToBooleanValuesSV(valueBlock); ArrayCopyUtils.copyFromBoolean(intValues, _stringValuesSV, length); break; case TIMESTAMP: - longValues = transformToTimestampValuesSV(projectionBlock); + longValues = transformToTimestampValuesSV(valueBlock); ArrayCopyUtils.copyFromTimestamp(longValues, _stringValuesSV, length); break; case BYTES: - byte[][] bytesValues = transformToBytesValuesSV(projectionBlock); + byte[][] bytesValues = transformToBytesValuesSV(valueBlock); ArrayCopyUtils.copy(bytesValues, _stringValuesSV, length); break; default: @@ -276,42 +284,40 @@ public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { } @Override - public int[][] transformToIntValuesMV(ProjectionBlock projectionBlock) { + public int[][] transformToIntValuesMV(ValueBlock valueBlock) { switch (_resultMetadata.getDataType()) { case INT: - return _transformFunction.transformToIntValuesMV(projectionBlock); + return _transformFunction.transformToIntValuesMV(valueBlock); case BOOLEAN: - return transformToBooleanValuesMV(projectionBlock); + return transformToBooleanValuesMV(valueBlock); default: - return super.transformToIntValuesMV(projectionBlock); + return super.transformToIntValuesMV(valueBlock); } } // TODO: Add it to the interface - private int[][] transformToBooleanValuesMV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_intValuesMV == null) { - _intValuesMV = new int[length][]; - } + private int[][] transformToBooleanValuesMV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initIntValuesMV(length); switch (_sourceDataType.getStoredType()) { case INT: - int[][] intValuesMV = _transformFunction.transformToIntValuesMV(projectionBlock); + int[][] intValuesMV = _transformFunction.transformToIntValuesMV(valueBlock); ArrayCopyUtils.copyToBoolean(intValuesMV, _intValuesMV, length); break; case LONG: - long[][] longValuesMV = _transformFunction.transformToLongValuesMV(projectionBlock); + long[][] longValuesMV = _transformFunction.transformToLongValuesMV(valueBlock); ArrayCopyUtils.copyToBoolean(longValuesMV, _intValuesMV, length); break; case FLOAT: - float[][] floatValuesMV = _transformFunction.transformToFloatValuesMV(projectionBlock); + float[][] floatValuesMV = _transformFunction.transformToFloatValuesMV(valueBlock); ArrayCopyUtils.copyToBoolean(floatValuesMV, _intValuesMV, length); break; case DOUBLE: - double[][] doubleValuesMV = _transformFunction.transformToDoubleValuesMV(projectionBlock); + double[][] doubleValuesMV = _transformFunction.transformToDoubleValuesMV(valueBlock); ArrayCopyUtils.copyToBoolean(doubleValuesMV, _intValuesMV, length); break; case STRING: - String[][] stringValuesMV = _transformFunction.transformToStringValuesMV(projectionBlock); + String[][] stringValuesMV = _transformFunction.transformToStringValuesMV(valueBlock); ArrayCopyUtils.copyToBoolean(stringValuesMV, _intValuesMV, length); break; default: @@ -321,102 +327,94 @@ private int[][] transformToBooleanValuesMV(ProjectionBlock projectionBlock) { } @Override - public long[][] transformToLongValuesMV(ProjectionBlock projectionBlock) { + public long[][] transformToLongValuesMV(ValueBlock valueBlock) { switch (_resultMetadata.getDataType()) { case LONG: - return _transformFunction.transformToLongValuesMV(projectionBlock); + return _transformFunction.transformToLongValuesMV(valueBlock); case TIMESTAMP: - return transformToTimestampValuesMV(projectionBlock); + return transformToTimestampValuesMV(valueBlock); default: - return super.transformToLongValuesMV(projectionBlock); + return super.transformToLongValuesMV(valueBlock); } } // TODO: Add it to the interface - private long[][] transformToTimestampValuesMV(ProjectionBlock projectionBlock) { + private long[][] transformToTimestampValuesMV(ValueBlock valueBlock) { if (_sourceDataType.getStoredType() == DataType.STRING) { - int length = projectionBlock.getNumDocs(); - if (_longValuesMV == null) { - _longValuesMV = new long[length][]; - } - String[][] stringValuesMV = _transformFunction.transformToStringValuesMV(projectionBlock); + int length = valueBlock.getNumDocs(); + initLongValuesMV(length); + String[][] stringValuesMV = _transformFunction.transformToStringValuesMV(valueBlock); ArrayCopyUtils.copyToTimestamp(stringValuesMV, _longValuesMV, length); return _longValuesMV; } else { - return _transformFunction.transformToLongValuesMV(projectionBlock); + return _transformFunction.transformToLongValuesMV(valueBlock); } } @Override - public float[][] transformToFloatValuesMV(ProjectionBlock projectionBlock) { + public float[][] transformToFloatValuesMV(ValueBlock valueBlock) { if (_resultMetadata.getDataType().getStoredType() == DataType.FLOAT) { - return _transformFunction.transformToFloatValuesMV(projectionBlock); + return _transformFunction.transformToFloatValuesMV(valueBlock); } else { - return super.transformToFloatValuesMV(projectionBlock); + return super.transformToFloatValuesMV(valueBlock); } } @Override - public double[][] transformToDoubleValuesMV(ProjectionBlock projectionBlock) { + public double[][] transformToDoubleValuesMV(ValueBlock valueBlock) { if (_resultMetadata.getDataType().getStoredType() == DataType.DOUBLE) { - return _transformFunction.transformToDoubleValuesMV(projectionBlock); + return _transformFunction.transformToDoubleValuesMV(valueBlock); } else { - return super.transformToDoubleValuesMV(projectionBlock); + return super.transformToDoubleValuesMV(valueBlock); } } @Override - public String[][] transformToStringValuesMV(ProjectionBlock projectionBlock) { + public String[][] transformToStringValuesMV(ValueBlock valueBlock) { DataType resultDataType = _resultMetadata.getDataType(); if (resultDataType.getStoredType() == DataType.STRING) { switch (_sourceDataType) { case BOOLEAN: - int length = projectionBlock.getNumDocs(); - if (_stringValuesMV == null) { - _stringValuesMV = new String[length][]; - } - int[][] intValuesMV = _transformFunction.transformToIntValuesMV(projectionBlock); + int length = valueBlock.getNumDocs(); + initStringValuesMV(length); + int[][] intValuesMV = _transformFunction.transformToIntValuesMV(valueBlock); ArrayCopyUtils.copyFromBoolean(intValuesMV, _stringValuesMV, length); return _stringValuesMV; case TIMESTAMP: - length = projectionBlock.getNumDocs(); - if (_stringValuesMV == null) { - _stringValuesMV = new String[length][]; - } - long[][] longValuesMV = _transformFunction.transformToLongValuesMV(projectionBlock); + length = valueBlock.getNumDocs(); + initStringValuesMV(length); + long[][] longValuesMV = _transformFunction.transformToLongValuesMV(valueBlock); ArrayCopyUtils.copyFromTimestamp(longValuesMV, _stringValuesMV, length); return _stringValuesMV; default: - return _transformFunction.transformToStringValuesMV(projectionBlock); + return _transformFunction.transformToStringValuesMV(valueBlock); } } else { - int length = projectionBlock.getNumDocs(); - if (_stringValuesMV == null) { - _stringValuesMV = new String[length][]; - } + int length = valueBlock.getNumDocs(); + initStringValuesMV(length); switch (resultDataType) { case INT: - int[][] intValuesMV = _transformFunction.transformToIntValuesMV(projectionBlock); + int[][] intValuesMV = _transformFunction.transformToIntValuesMV(valueBlock); ArrayCopyUtils.copy(intValuesMV, _stringValuesMV, length); break; case LONG: - long[][] longValuesMV = _transformFunction.transformToLongValuesMV(projectionBlock); + long[][] longValuesMV = _transformFunction.transformToLongValuesMV(valueBlock); ArrayCopyUtils.copy(longValuesMV, _stringValuesMV, length); break; case FLOAT: - float[][] floatValuesMV = _transformFunction.transformToFloatValuesMV(projectionBlock); + float[][] floatValuesMV = _transformFunction.transformToFloatValuesMV(valueBlock); ArrayCopyUtils.copy(floatValuesMV, _stringValuesMV, length); break; case DOUBLE: - double[][] doubleValuesMV = _transformFunction.transformToDoubleValuesMV(projectionBlock); + double[][] doubleValuesMV = _transformFunction.transformToDoubleValuesMV(valueBlock); ArrayCopyUtils.copy(doubleValuesMV, _stringValuesMV, length); break; case BOOLEAN: - intValuesMV = transformToBooleanValuesMV(projectionBlock); + intValuesMV = transformToBooleanValuesMV(valueBlock); ArrayCopyUtils.copyFromBoolean(intValuesMV, _stringValuesMV, length); break; case TIMESTAMP: - longValuesMV = transformToTimestampValuesMV(projectionBlock); + longValuesMV = transformToTimestampValuesMV(valueBlock); ArrayCopyUtils.copyFromTimestamp(longValuesMV, _stringValuesMV, length); break; default: diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ClpEncodedVarsMatchTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ClpEncodedVarsMatchTransformFunction.java new file mode 100644 index 000000000000..ba116e8eb534 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ClpEncodedVarsMatchTransformFunction.java @@ -0,0 +1,150 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.transform.function; + +import com.google.common.base.Preconditions; +import com.yscope.clp.compressorfrontend.AbstractClpEncodedSubquery; +import com.yscope.clp.compressorfrontend.BuiltInVariableHandlingRuleVersions; +import com.yscope.clp.compressorfrontend.EightByteClpEncodedSubquery; +import com.yscope.clp.compressorfrontend.EightByteClpWildcardQueryEncoder; +import com.yscope.clp.compressorfrontend.MessageDecoder; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.pinot.common.function.TransformFunctionType; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; +import org.apache.pinot.core.operator.transform.TransformResultMetadata; +import org.apache.pinot.spi.data.FieldSpec; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Performs a wildcard match on the encoded variables of a CLP-encoded column group. This is used by the clpMatch + * function (implemented using {@link org.apache.pinot.sql.parsers.rewriter.ClpRewriter}) and likely wouldn't be called + * manually by a user. + *

+ * Syntax: + *

+ *   clpEncodedVarsMatch(columnGroupName_logtype, columnGroupName_encodedVars, wildcardQuery, subQueryIndex)
+ * 
+ */ +public class ClpEncodedVarsMatchTransformFunction extends BaseTransformFunction { + private static final Logger _logger = LoggerFactory.getLogger(ClpEncodedVarsMatchTransformFunction.class); + + private final List _transformFunctions = new ArrayList<>(); + private byte[] _serializedVarTypes; + private byte[] _serializedVarWildcardQueries; + private int[] _varWildcardQueryEndIndexes; + + @Override + public String getName() { + return TransformFunctionType.CLP_ENCODED_VARS_MATCH.getName(); + } + + @Override + public void init(List arguments, Map columnContextMap) { + Preconditions.checkArgument(arguments.size() == 4, "Syntax error: clpEncodedVarsMatch takes 4 arguments - " + + "clpEncodedVarsMatch(columnGroupName_logtype, columnGroupName_encodedVars, wildcardQuery, subQueryIndex"); + + Iterator argsIter = arguments.iterator(); + + TransformFunction f = argsIter.next(); + Preconditions.checkArgument(f instanceof IdentifierTransformFunction, "1st argument must be an identifier"); + _transformFunctions.add(f); + + f = argsIter.next(); + Preconditions.checkArgument(f instanceof IdentifierTransformFunction, "2nd argument must be an identifier"); + _transformFunctions.add(f); + + f = argsIter.next(); + Preconditions.checkArgument(f instanceof LiteralTransformFunction, "3rd argument must be a string literal"); + String wildcardQuery = ((LiteralTransformFunction) f).getStringLiteral(); + + f = argsIter.next(); + Preconditions.checkArgument(f instanceof LiteralTransformFunction, "4th argument must be a long literal"); + long subqueryIndex = ((LiteralTransformFunction) f).getLongLiteral(); + + EightByteClpWildcardQueryEncoder queryEncoder = + new EightByteClpWildcardQueryEncoder(BuiltInVariableHandlingRuleVersions.VariablesSchemaV2, + BuiltInVariableHandlingRuleVersions.VariableEncodingMethodsV1); + EightByteClpEncodedSubquery[] subqueries = queryEncoder.encode(wildcardQuery); + if (subqueryIndex < 0 || subqueryIndex > subqueries.length) { + throw new IllegalArgumentException("Invalid subquery index."); + } + EightByteClpEncodedSubquery subquery = subqueries[(int) subqueryIndex]; + int numEncodedVarWildcardQueries = subquery.getNumEncodedVarWildcardQueries(); + if (0 == numEncodedVarWildcardQueries) { + throw new IllegalArgumentException("Subquery doesn't contain any wildcard queries for encoded variables."); + } + + try { + ByteArrayOutputStream serializedVarTypes = new ByteArrayOutputStream(); + ByteArrayOutputStream serializedWildcardQueries = new ByteArrayOutputStream(); + List serializedWildcardQueryEndIndices = new ArrayList<>(); + for (AbstractClpEncodedSubquery.VariableWildcardQuery q : subquery.getEncodedVarWildcardQueries()) { + serializedVarTypes.write(q.getType()); + serializedWildcardQueries.write(q.getQuery().toByteArray()); + serializedWildcardQueryEndIndices.add(serializedWildcardQueries.size()); + } + _serializedVarTypes = serializedVarTypes.toByteArray(); + _serializedVarWildcardQueries = serializedWildcardQueries.toByteArray(); + _varWildcardQueryEndIndexes = ArrayUtils.toPrimitive(serializedWildcardQueryEndIndices.toArray(new Integer[0])); + } catch (IOException e) { + throw new IllegalArgumentException("Wildcard query could not be serialized", e); + } + } + + @Override + public TransformResultMetadata getResultMetadata() { + return new TransformResultMetadata(FieldSpec.DataType.BOOLEAN, true, false); + } + + @Override + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + if (null == _intValuesSV) { + _intValuesSV = new int[length]; + } + + int functionIdx = 0; + TransformFunction logtypeTransformFunction = _transformFunctions.get(functionIdx++); + TransformFunction encodedVarsTransformFunction = _transformFunctions.get(functionIdx++); + byte[][] logtypes = logtypeTransformFunction.transformToBytesValuesSV(valueBlock); + long[][] encodedVars = encodedVarsTransformFunction.transformToLongValuesMV(valueBlock); + + MessageDecoder clpMessageDecoder = new MessageDecoder(BuiltInVariableHandlingRuleVersions.VariablesSchemaV2, + BuiltInVariableHandlingRuleVersions.VariableEncodingMethodsV1); + try { + clpMessageDecoder.batchEncodedVarsWildcardMatch(logtypes, encodedVars, _serializedVarTypes, + _serializedVarWildcardQueries, _varWildcardQueryEndIndexes, _intValuesSV); + } catch (IOException ex) { + _logger.error("Failed to perform wildcard match on (CLP) encoded variables field.", ex); + Arrays.fill(_intValuesSV, 0); + } + + return _intValuesSV; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/CoalesceTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/CoalesceTransformFunction.java index 7a5b477764f1..676911604013 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/CoalesceTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/CoalesceTransformFunction.java @@ -23,76 +23,89 @@ import java.util.List; import java.util.Map; import org.apache.pinot.common.function.TransformFunctionType; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.utils.CommonConstants.NullValuePlaceHolder; import org.roaringbitmap.RoaringBitmap; /** * The CoalesceTransformFunction implements the Coalesce operator. * - * The results are in String format for first non-null value in the argument list. - * If all arguments are null, return a 'null' string. - * Note: arguments have to be column names and single type. The type can be either numeric or string. + * The result is first non-null value in the argument list. + * If all arguments are null, return null. + * + * Note: arguments have to be compatible type. * Number of arguments has to be greater than 0. * * Expected result: * Coalesce(nullColumn, columnA): columnA - * Coalesce(columnA, nullColumn): nullColumn - * Coalesce(nullColumnA, nullColumnB): "null" + * Coalesce(columnA, nullColumn): columnA + * Coalesce(nullColumnA, nullColumnB): null * - * Note this operator only takes column names for now. - * SQL Syntax: - * Coalesce(columnA, columnB) */ public class CoalesceTransformFunction extends BaseTransformFunction { - public static final int NULL_INT = Integer.MIN_VALUE; - public static final long NULL_LONG = Long.MIN_VALUE; - public static final float NULL_FLOAT = Float.NEGATIVE_INFINITY; - public static final double NULL_DOUBLE = Double.NEGATIVE_INFINITY; - public static final BigDecimal NULL_BIG_DECIMAL = BigDecimal.valueOf(Long.MIN_VALUE); - public static final String NULL_STRING = "null"; - private TransformFunction[] _transformFunctions; private DataType _dataType; private TransformResultMetadata _resultMetadata; /** - * Returns a bit map of corresponding column. - * Returns an empty bitmap by default if null option is disabled. + * Returns a bit map of corresponding column. Returns an empty bitmap by default if null option is disabled. */ - private static RoaringBitmap[] getNullBitMaps(ProjectionBlock projectionBlock, - TransformFunction[] transformFunctions) { + private static RoaringBitmap[] getNullBitMaps(ValueBlock valueBlock, TransformFunction[] transformFunctions) { RoaringBitmap[] roaringBitmaps = new RoaringBitmap[transformFunctions.length]; for (int i = 0; i < roaringBitmaps.length; i++) { TransformFunction func = transformFunctions[i]; - if (func instanceof IdentifierTransformFunction) { - String columnName = ((IdentifierTransformFunction) func).getColumnName(); - RoaringBitmap nullBitmap = projectionBlock.getBlockValueSet(columnName).getNullBitmap(); - roaringBitmaps[i] = nullBitmap; - } else { - // Consider literal as not null. - roaringBitmaps[i] = new RoaringBitmap(); - } + roaringBitmaps[i] = func.getNullBitmap(valueBlock); } return roaringBitmaps; } /** - * Get transform int results based on store type. - * @param projectionBlock + * Get compatible data type of left and right. + * When left or right is numerical, we check both data types are numerical and widen the type. + * Otherwise, return string type. + * + * @param left data type + * @param right data type + * @return compatible data type. */ - private int[] getIntTransformResults(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_intValuesSV == null) { - _intValuesSV = new int[length]; + private static DataType getCompatibleType(DataType left, DataType right) { + if (left.isNumeric() && right.isNumeric()) { + if (left == DataType.BIG_DECIMAL || right == DataType.BIG_DECIMAL) { + return DataType.BIG_DECIMAL; + } + if (left == DataType.DOUBLE || right == DataType.DOUBLE) { + return DataType.DOUBLE; + } + if (left == DataType.FLOAT || right == DataType.FLOAT) { + return DataType.FLOAT; + } + if (left == DataType.LONG || right == DataType.LONG) { + return DataType.LONG; + } + return DataType.INT; + } + if (left == DataType.UNKNOWN) { + return right; } + if (right == DataType.UNKNOWN) { + return left; + } + return DataType.STRING; + } + + /** + * Get transform int results based on store type. + */ + private int[] getIntTransformResults(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initIntValuesSV(length); int width = _transformFunctions.length; - RoaringBitmap[] nullBitMaps = getNullBitMaps(projectionBlock, _transformFunctions); - int[][] data = new int[width][length]; - RoaringBitmap filledData = new RoaringBitmap(); + RoaringBitmap[] nullBitMaps = getNullBitMaps(valueBlock, _transformFunctions); + int[][] data = new int[width][]; for (int i = 0; i < length; i++) { boolean hasNonNullValue = false; for (int j = 0; j < width; j++) { @@ -100,16 +113,15 @@ private int[] getIntTransformResults(ProjectionBlock projectionBlock) { if (nullBitMaps[j] != null && nullBitMaps[j].contains(i)) { continue; } - if (!filledData.contains(j)) { - filledData.add(j); - data[j] = _transformFunctions[j].transformToIntValuesSV(projectionBlock); + if (data[j] == null) { + data[j] = _transformFunctions[j].transformToIntValuesSV(valueBlock); } hasNonNullValue = true; _intValuesSV[i] = data[j][i]; break; } if (!hasNonNullValue) { - _intValuesSV[i] = NULL_INT; + _intValuesSV[i] = NullValuePlaceHolder.INT; } } return _intValuesSV; @@ -117,17 +129,13 @@ private int[] getIntTransformResults(ProjectionBlock projectionBlock) { /** * Get transform long results based on store type. - * @param projectionBlock */ - private long[] getLongTransformResults(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_longValuesSV == null) { - _longValuesSV = new long[length]; - } + private long[] getLongTransformResults(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initLongValuesSV(length); int width = _transformFunctions.length; - RoaringBitmap[] nullBitMaps = getNullBitMaps(projectionBlock, _transformFunctions); - long[][] data = new long[width][length]; - RoaringBitmap filledData = new RoaringBitmap(); // indicates whether certain column has be filled in data. + RoaringBitmap[] nullBitMaps = getNullBitMaps(valueBlock, _transformFunctions); + long[][] data = new long[width][]; for (int i = 0; i < length; i++) { boolean hasNonNullValue = false; for (int j = 0; j < width; j++) { @@ -135,16 +143,15 @@ private long[] getLongTransformResults(ProjectionBlock projectionBlock) { if (nullBitMaps[j] != null && nullBitMaps[j].contains(i)) { continue; } - if (!filledData.contains(j)) { - filledData.add(j); - data[j] = _transformFunctions[j].transformToLongValuesSV(projectionBlock); + if (data[j] == null) { + data[j] = _transformFunctions[j].transformToLongValuesSV(valueBlock); } hasNonNullValue = true; _longValuesSV[i] = data[j][i]; break; } if (!hasNonNullValue) { - _longValuesSV[i] = NULL_LONG; + _longValuesSV[i] = NullValuePlaceHolder.LONG; } } return _longValuesSV; @@ -152,17 +159,13 @@ private long[] getLongTransformResults(ProjectionBlock projectionBlock) { /** * Get transform float results based on store type. - * @param projectionBlock */ - private float[] getFloatTransformResults(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_floatValuesSV == null) { - _floatValuesSV = new float[length]; - } + private float[] getFloatTransformResults(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initFloatValuesSV(length); int width = _transformFunctions.length; - RoaringBitmap[] nullBitMaps = getNullBitMaps(projectionBlock, _transformFunctions); - float[][] data = new float[width][length]; - RoaringBitmap filledData = new RoaringBitmap(); // indicates whether certain column has be filled in data. + RoaringBitmap[] nullBitMaps = getNullBitMaps(valueBlock, _transformFunctions); + float[][] data = new float[width][]; for (int i = 0; i < length; i++) { boolean hasNonNullValue = false; for (int j = 0; j < width; j++) { @@ -170,16 +173,15 @@ private float[] getFloatTransformResults(ProjectionBlock projectionBlock) { if (nullBitMaps[j] != null && nullBitMaps[j].contains(i)) { continue; } - if (!filledData.contains(j)) { - filledData.add(j); - data[j] = _transformFunctions[j].transformToFloatValuesSV(projectionBlock); + if (data[j] == null) { + data[j] = _transformFunctions[j].transformToFloatValuesSV(valueBlock); } hasNonNullValue = true; _floatValuesSV[i] = data[j][i]; break; } if (!hasNonNullValue) { - _floatValuesSV[i] = NULL_FLOAT; + _floatValuesSV[i] = NullValuePlaceHolder.FLOAT; } } return _floatValuesSV; @@ -187,17 +189,13 @@ private float[] getFloatTransformResults(ProjectionBlock projectionBlock) { /** * Get transform double results based on store type. - * @param projectionBlock */ - private double[] getDoubleTransformResults(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_doubleValuesSV == null) { - _doubleValuesSV = new double[length]; - } + private double[] getDoubleTransformResults(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initDoubleValuesSV(length); int width = _transformFunctions.length; - RoaringBitmap[] nullBitMaps = getNullBitMaps(projectionBlock, _transformFunctions); - double[][] data = new double[width][length]; - RoaringBitmap filledData = new RoaringBitmap(); // indicates whether certain column has be filled in data. + RoaringBitmap[] nullBitMaps = getNullBitMaps(valueBlock, _transformFunctions); + double[][] data = new double[width][]; for (int i = 0; i < length; i++) { boolean hasNonNullValue = false; for (int j = 0; j < width; j++) { @@ -205,16 +203,15 @@ private double[] getDoubleTransformResults(ProjectionBlock projectionBlock) { if (nullBitMaps[j] != null && nullBitMaps[j].contains(i)) { continue; } - if (!filledData.contains(j)) { - filledData.add(j); - data[j] = _transformFunctions[j].transformToDoubleValuesSV(projectionBlock); + if (data[j] == null) { + data[j] = _transformFunctions[j].transformToDoubleValuesSV(valueBlock); } hasNonNullValue = true; _doubleValuesSV[i] = data[j][i]; break; } if (!hasNonNullValue) { - _doubleValuesSV[i] = NULL_DOUBLE; + _doubleValuesSV[i] = NullValuePlaceHolder.DOUBLE; } } return _doubleValuesSV; @@ -222,17 +219,13 @@ private double[] getDoubleTransformResults(ProjectionBlock projectionBlock) { /** * Get transform BigDecimal results based on store type. - * @param projectionBlock */ - private BigDecimal[] getBigDecimalTransformResults(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_bigDecimalValuesSV == null) { - _bigDecimalValuesSV = new BigDecimal[length]; - } + private BigDecimal[] getBigDecimalTransformResults(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initBigDecimalValuesSV(length); int width = _transformFunctions.length; - RoaringBitmap[] nullBitMaps = getNullBitMaps(projectionBlock, _transformFunctions); - BigDecimal[][] data = new BigDecimal[width][length]; - RoaringBitmap filledData = new RoaringBitmap(); // indicates whether certain column has be filled in data. + RoaringBitmap[] nullBitMaps = getNullBitMaps(valueBlock, _transformFunctions); + BigDecimal[][] data = new BigDecimal[width][]; for (int i = 0; i < length; i++) { boolean hasNonNullValue = false; for (int j = 0; j < width; j++) { @@ -240,16 +233,15 @@ private BigDecimal[] getBigDecimalTransformResults(ProjectionBlock projectionBlo if (nullBitMaps[j] != null && nullBitMaps[j].contains(i)) { continue; } - if (!filledData.contains(j)) { - filledData.add(j); - data[j] = _transformFunctions[j].transformToBigDecimalValuesSV(projectionBlock); + if (data[j] == null) { + data[j] = _transformFunctions[j].transformToBigDecimalValuesSV(valueBlock); } hasNonNullValue = true; _bigDecimalValuesSV[i] = data[j][i]; break; } if (!hasNonNullValue) { - _bigDecimalValuesSV[i] = NULL_BIG_DECIMAL; + _bigDecimalValuesSV[i] = NullValuePlaceHolder.BIG_DECIMAL; } } return _bigDecimalValuesSV; @@ -257,17 +249,13 @@ private BigDecimal[] getBigDecimalTransformResults(ProjectionBlock projectionBlo /** * Get transform String results based on store type. - * @param projectionBlock */ - private String[] getStringTransformResults(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_stringValuesSV == null) { - _stringValuesSV = new String[length]; - } + private String[] getStringTransformResults(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initStringValuesSV(length); int width = _transformFunctions.length; - RoaringBitmap[] nullBitMaps = getNullBitMaps(projectionBlock, _transformFunctions); - String[][] data = new String[width][length]; - RoaringBitmap filledData = new RoaringBitmap(); // indicates whether certain column has be filled in data. + RoaringBitmap[] nullBitMaps = getNullBitMaps(valueBlock, _transformFunctions); + String[][] data = new String[width][]; for (int i = 0; i < length; i++) { boolean hasNonNullValue = false; for (int j = 0; j < width; j++) { @@ -275,16 +263,15 @@ private String[] getStringTransformResults(ProjectionBlock projectionBlock) { if (nullBitMaps[j] != null && nullBitMaps[j].contains(i)) { continue; } - if (!filledData.contains(j)) { - filledData.add(j); - data[j] = _transformFunctions[j].transformToStringValuesSV(projectionBlock); + if (data[j] == null) { + data[j] = _transformFunctions[j].transformToStringValuesSV(valueBlock); } hasNonNullValue = true; _stringValuesSV[i] = data[j][i]; break; } if (!hasNonNullValue) { - _stringValuesSV[i] = NULL_STRING; + _stringValuesSV[i] = NullValuePlaceHolder.STRING; } } return _stringValuesSV; @@ -296,20 +283,17 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { int argSize = arguments.size(); Preconditions.checkArgument(argSize > 0, "COALESCE needs to have at least one argument."); _transformFunctions = new TransformFunction[argSize]; for (int i = 0; i < argSize; i++) { TransformFunction func = arguments.get(i); - Preconditions.checkArgument( - func instanceof IdentifierTransformFunction || func instanceof LiteralTransformFunction, - "Only column names and literals are supported in COALESCE."); DataType dataType = func.getResultMetadata().getDataType(); - if (_dataType == null) { - _dataType = dataType; + if (_dataType != null) { + _dataType = getCompatibleType(_dataType, dataType); } else { - Preconditions.checkArgument(dataType == _dataType, "Argument types have to be the same."); + _dataType = dataType; } _transformFunctions[i] = func; } @@ -333,6 +317,9 @@ public void init(List arguments, Map data case STRING: _resultMetadata = STRING_SV_NO_DICTIONARY_METADATA; break; + case UNKNOWN: + _resultMetadata = UNKNOWN_METADATA; + break; default: throw new UnsupportedOperationException("Coalesce only supports numerical and string data type"); } @@ -344,50 +331,70 @@ public TransformResultMetadata getResultMetadata() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { + public int[] transformToIntValuesSV(ValueBlock valueBlock) { if (_dataType != DataType.INT) { - return super.transformToIntValuesSV(projectionBlock); + return super.transformToIntValuesSV(valueBlock); } - return getIntTransformResults(projectionBlock); + return getIntTransformResults(valueBlock); } @Override - public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { + public long[] transformToLongValuesSV(ValueBlock valueBlock) { if (_dataType != DataType.LONG) { - return super.transformToLongValuesSV(projectionBlock); + return super.transformToLongValuesSV(valueBlock); } - return getLongTransformResults(projectionBlock); + return getLongTransformResults(valueBlock); } @Override - public float[] transformToFloatValuesSV(ProjectionBlock projectionBlock) { + public float[] transformToFloatValuesSV(ValueBlock valueBlock) { if (_dataType != DataType.FLOAT) { - return super.transformToFloatValuesSV(projectionBlock); + return super.transformToFloatValuesSV(valueBlock); } - return getFloatTransformResults(projectionBlock); + return getFloatTransformResults(valueBlock); } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { if (_dataType != DataType.DOUBLE) { - return super.transformToDoubleValuesSV(projectionBlock); + return super.transformToDoubleValuesSV(valueBlock); } - return getDoubleTransformResults(projectionBlock); + return getDoubleTransformResults(valueBlock); } @Override - public BigDecimal[] transformToBigDecimalValuesSV(ProjectionBlock projectionBlock) { + public BigDecimal[] transformToBigDecimalValuesSV(ValueBlock valueBlock) { if (_dataType != DataType.BIG_DECIMAL) { - return super.transformToBigDecimalValuesSV(projectionBlock); + return super.transformToBigDecimalValuesSV(valueBlock); } - return getBigDecimalTransformResults(projectionBlock); + return getBigDecimalTransformResults(valueBlock); } @Override - public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { + public String[] transformToStringValuesSV(ValueBlock valueBlock) { if (_dataType != DataType.STRING) { - return super.transformToStringValuesSV(projectionBlock); + return super.transformToStringValuesSV(valueBlock); + } + return getStringTransformResults(valueBlock); + } + + @Override + public RoaringBitmap getNullBitmap(ValueBlock valueBlock) { + RoaringBitmap[] nullBitMaps = getNullBitMaps(valueBlock, _transformFunctions); + RoaringBitmap bitmap = nullBitMaps[0]; + if (bitmap == null || bitmap.isEmpty()) { + return null; + } + for (int i = 1; i < nullBitMaps.length; i++) { + RoaringBitmap curBitmap = nullBitMaps[i]; + if (curBitmap == null || curBitmap.isEmpty()) { + return null; + } + bitmap.and(curBitmap); + } + if (bitmap == null || bitmap.isEmpty()) { + return null; } - return getStringTransformResults(projectionBlock); + return bitmap; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ComputeDifferentlyWhenNullHandlingEnabledTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ComputeDifferentlyWhenNullHandlingEnabledTransformFunction.java new file mode 100644 index 000000000000..5d2d6b8da662 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ComputeDifferentlyWhenNullHandlingEnabledTransformFunction.java @@ -0,0 +1,177 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.transform.function; + +import java.math.BigDecimal; +import javax.annotation.Nullable; +import org.apache.pinot.core.operator.blocks.ValueBlock; +import org.apache.pinot.spi.data.FieldSpec; +import org.roaringbitmap.RoaringBitmap; + + +/** + * Base class for transform functions that compute differently (using value and NULL together) when NULL handling is + * enabled. + */ +public abstract class ComputeDifferentlyWhenNullHandlingEnabledTransformFunction extends BaseTransformFunction { + + @Override + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + if (getResultMetadata().getDataType().getStoredType() != FieldSpec.DataType.INT) { + return super.transformToIntValuesSV(valueBlock); + } + if (_nullHandlingEnabled) { + return transformToIntValuesSVUsingValueAndNull(valueBlock); + } else { + return transformToIntValuesSVUsingValue(valueBlock); + } + } + + protected int[] transformToIntValuesSVUsingValue(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + protected int[] transformToIntValuesSVUsingValueAndNull(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + @Override + public long[] transformToLongValuesSV(ValueBlock valueBlock) { + if (getResultMetadata().getDataType().getStoredType() != FieldSpec.DataType.LONG) { + return super.transformToLongValuesSV(valueBlock); + } + if (_nullHandlingEnabled) { + return transformToLongValuesSVUsingValueAndNull(valueBlock); + } else { + return transformToLongValuesSVUsingValue(valueBlock); + } + } + + protected long[] transformToLongValuesSVUsingValue(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + protected long[] transformToLongValuesSVUsingValueAndNull(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + @Override + public float[] transformToFloatValuesSV(ValueBlock valueBlock) { + if (getResultMetadata().getDataType().getStoredType() != FieldSpec.DataType.FLOAT) { + return super.transformToFloatValuesSV(valueBlock); + } + if (_nullHandlingEnabled) { + return transformToFloatValuesSVUsingValueAndNull(valueBlock); + } else { + return transformToFloatValuesSVUsingValue(valueBlock); + } + } + + protected float[] transformToFloatValuesSVUsingValue(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + protected float[] transformToFloatValuesSVUsingValueAndNull(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + @Override + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + if (getResultMetadata().getDataType().getStoredType() != FieldSpec.DataType.DOUBLE) { + return super.transformToDoubleValuesSV(valueBlock); + } + if (_nullHandlingEnabled) { + return transformToDoubleValuesSVUsingValueAndNull(valueBlock); + } else { + return transformToDoubleValuesSVUsingValue(valueBlock); + } + } + + protected double[] transformToDoubleValuesSVUsingValue(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + protected double[] transformToDoubleValuesSVUsingValueAndNull(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + @Override + public BigDecimal[] transformToBigDecimalValuesSV(ValueBlock valueBlock) { + if (getResultMetadata().getDataType().getStoredType() != FieldSpec.DataType.BIG_DECIMAL) { + return super.transformToBigDecimalValuesSV(valueBlock); + } + if (_nullHandlingEnabled) { + return transformToBigDecimalValuesSVUsingValueAndNull(valueBlock); + } else { + return transformToBigDecimalValuesSVUsingValue(valueBlock); + } + } + + protected BigDecimal[] transformToBigDecimalValuesSVUsingValue(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + protected BigDecimal[] transformToBigDecimalValuesSVUsingValueAndNull(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + @Override + public String[] transformToStringValuesSV(ValueBlock valueBlock) { + if (getResultMetadata().getDataType().getStoredType() != FieldSpec.DataType.STRING) { + return super.transformToStringValuesSV(valueBlock); + } + if (_nullHandlingEnabled) { + return transformToStringValuesSVUsingValueAndNull(valueBlock); + } else { + return transformToStringValuesSVUsingValue(valueBlock); + } + } + + protected String[] transformToStringValuesSVUsingValue(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + protected String[] transformToStringValuesSVUsingValueAndNull(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + @Override + public byte[][] transformToBytesValuesSV(ValueBlock valueBlock) { + if (getResultMetadata().getDataType().getStoredType() != FieldSpec.DataType.BYTES) { + return super.transformToBytesValuesSV(valueBlock); + } + if (_nullHandlingEnabled) { + return transformToBytesValuesSVUsingValueAndNull(valueBlock); + } else { + return transformToBytesValuesSVUsingValue(valueBlock); + } + } + + protected byte[][] transformToBytesValuesSVUsingValue(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + protected byte[][] transformToBytesValuesSVUsingValueAndNull(ValueBlock valueBlock) { + throw new UnsupportedOperationException(); + } + + @Nullable + @Override + public abstract RoaringBitmap getNullBitmap(ValueBlock valueBlock); +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/DateTimeConversionHopTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/DateTimeConversionHopTransformFunction.java new file mode 100644 index 000000000000..9211ef62fc00 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/DateTimeConversionHopTransformFunction.java @@ -0,0 +1,151 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.transform.function; + +import java.util.List; +import java.util.Map; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; +import org.apache.pinot.core.operator.transform.TransformResultMetadata; +import org.apache.pinot.core.operator.transform.transformer.datetimehop.BaseDateTimeWindowHopTransformer; +import org.apache.pinot.core.operator.transform.transformer.datetimehop.DateTimeWindowHopTransformerFactory; +import org.apache.pinot.core.operator.transform.transformer.datetimehop.EpochToEpochWindowHopTransformer; +import org.apache.pinot.core.operator.transform.transformer.datetimehop.EpochToSDFHopWindowTransformer; +import org.apache.pinot.core.operator.transform.transformer.datetimehop.SDFToEpochWindowHopTransformer; +import org.apache.pinot.core.operator.transform.transformer.datetimehop.SDFToSDFWindowHopTransformer; +import org.roaringbitmap.RoaringBitmap; + + +/** + * The DateTimeConversionHopTransformFunction class implements the date time conversion + * with hop transform function. + *
    + *
  • + * This transform function should be invoked with arguments: + *
      + *
    • Column name to convert. E.g. Date
    • + *
    • Input format of the column. E.g. EPOCH|MILLISECONDS (See Pipe Format in DateTimeFormatSpec)
    • + *
    • Output format. E.g. EPOCH|MILLISECONDS/|10
    • + *
    • Output granularity. E.g. MINUTES|15
    • + *
    • Hop window size. E.g. HOURS
    • + *
    + *
  • + *
  • + * Outputs: + *
      + *
    • Time values converted to the desired format and bucketed to desired granularity with hop windows
    • + *
    • Below is an example for one hour window with 15min hop for 12:10
    • + * |-----------------| 11:15 - 12:15 + * |-----------------| 11:30 - 12:30 + * |-----------------| 11:45 - 12:45 + * |-----------------| 12:00 - 13:00 + *
    • The beginning of the windows returned + *
    • The end of the window can be fetched by adding window size + *
    + *
  • + *
+ */ +public class DateTimeConversionHopTransformFunction extends BaseTransformFunction { + public static final String FUNCTION_NAME = "dateTimeConvertWindowHop"; + + private TransformFunction _mainTransformFunction; + private TransformResultMetadata _resultMetadata; + private BaseDateTimeWindowHopTransformer _dateTimeTransformer; + + @Override + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); + // Check that there are exactly 4 arguments + if (arguments.size() != 5) { + throw new IllegalArgumentException("Exactly 5 arguments are required for DATE_TIME_CONVERT_HOP function"); + } + TransformFunction firstArgument = arguments.get(0); + if (firstArgument instanceof LiteralTransformFunction || !firstArgument.getResultMetadata().isSingleValue()) { + throw new IllegalArgumentException( + "The first argument of DATE_TIME_CONVERT_HOP transform function must be a single-valued column or " + + "a transform function"); + } + _mainTransformFunction = firstArgument; + + _dateTimeTransformer = DateTimeWindowHopTransformerFactory.getDateTimeTransformer( + ((LiteralTransformFunction) arguments.get(1)).getStringLiteral(), + ((LiteralTransformFunction) arguments.get(2)).getStringLiteral(), + ((LiteralTransformFunction) arguments.get(3)).getStringLiteral(), + ((LiteralTransformFunction) arguments.get(4)).getStringLiteral()); + if (_dateTimeTransformer instanceof EpochToEpochWindowHopTransformer + || _dateTimeTransformer instanceof SDFToEpochWindowHopTransformer) { + _resultMetadata = LONG_MV_NO_DICTIONARY_METADATA; + } else { + _resultMetadata = STRING_MV_NO_DICTIONARY_METADATA; + } + } + + @Override + public String getName() { + return FUNCTION_NAME; + } + + @Override + public TransformResultMetadata getResultMetadata() { + return _resultMetadata; + } + + @Override + public long[][] transformToLongValuesMV(ValueBlock valueBlock) { + if (_resultMetadata != LONG_MV_NO_DICTIONARY_METADATA) { + return super.transformToLongValuesMV(valueBlock); + } + + int length = valueBlock.getNumDocs(); + initLongValuesMV(length); + if (_dateTimeTransformer instanceof EpochToEpochWindowHopTransformer) { + EpochToEpochWindowHopTransformer dateTimeTransformer = (EpochToEpochWindowHopTransformer) _dateTimeTransformer; + dateTimeTransformer.transform(_mainTransformFunction.transformToLongValuesSV(valueBlock), _longValuesMV, length); + } else if (_dateTimeTransformer instanceof SDFToEpochWindowHopTransformer) { + SDFToEpochWindowHopTransformer dateTimeTransformer = (SDFToEpochWindowHopTransformer) _dateTimeTransformer; + dateTimeTransformer.transform(_mainTransformFunction.transformToStringValuesSV(valueBlock), _longValuesMV, + length); + } + return _longValuesMV; + } + + public String[][] transformToStringValuesMV(ValueBlock valueBlock) { + if (_resultMetadata != STRING_MV_NO_DICTIONARY_METADATA) { + return super.transformToStringValuesMV(valueBlock); + } + + int length = valueBlock.getNumDocs(); + initStringValuesMV(length); + if (_dateTimeTransformer instanceof EpochToSDFHopWindowTransformer) { + EpochToSDFHopWindowTransformer dateTimeTransformer = (EpochToSDFHopWindowTransformer) _dateTimeTransformer; + dateTimeTransformer.transform(_mainTransformFunction.transformToLongValuesSV(valueBlock), _stringValuesMV, + length); + } else if (_dateTimeTransformer instanceof SDFToSDFWindowHopTransformer) { + SDFToSDFWindowHopTransformer dateTimeTransformer = (SDFToSDFWindowHopTransformer) _dateTimeTransformer; + dateTimeTransformer.transform(_mainTransformFunction.transformToStringValuesSV(valueBlock), _stringValuesMV, + length); + } + return _stringValuesMV; + } + + @Override + public RoaringBitmap getNullBitmap(ValueBlock valueBlock) { + return _mainTransformFunction.getNullBitmap(valueBlock); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/DateTimeConversionTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/DateTimeConversionTransformFunction.java index 6fe5fedc4e85..6eece75ac2eb 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/DateTimeConversionTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/DateTimeConversionTransformFunction.java @@ -20,7 +20,8 @@ import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.operator.transform.transformer.datetime.BaseDateTimeTransformer; import org.apache.pinot.core.operator.transform.transformer.datetime.DateTimeTransformerFactory; @@ -28,8 +29,8 @@ import org.apache.pinot.core.operator.transform.transformer.datetime.EpochToSDFTransformer; import org.apache.pinot.core.operator.transform.transformer.datetime.SDFToEpochTransformer; import org.apache.pinot.core.operator.transform.transformer.datetime.SDFToSDFTransformer; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.DateTimeFieldSpec; +import org.roaringbitmap.RoaringBitmap; /** @@ -90,12 +91,12 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); // Check that there are exactly 4 arguments if (arguments.size() != 4) { throw new IllegalArgumentException("Exactly 4 arguments are required for DATE_TIME_CONVERT transform function"); } - TransformFunction firstArgument = arguments.get(0); if (firstArgument instanceof LiteralTransformFunction || !firstArgument.getResultMetadata().isSingleValue()) { throw new IllegalArgumentException( @@ -104,10 +105,10 @@ public void init(List arguments, Map data } _mainTransformFunction = firstArgument; - _dateTimeTransformer = - DateTimeTransformerFactory.getDateTimeTransformer(((LiteralTransformFunction) arguments.get(1)).getLiteral(), - ((LiteralTransformFunction) arguments.get(2)).getLiteral(), - ((LiteralTransformFunction) arguments.get(3)).getLiteral()); + _dateTimeTransformer = DateTimeTransformerFactory.getDateTimeTransformer( + ((LiteralTransformFunction) arguments.get(1)).getStringLiteral(), + ((LiteralTransformFunction) arguments.get(2)).getStringLiteral(), + ((LiteralTransformFunction) arguments.get(3)).getStringLiteral()); if (_dateTimeTransformer instanceof EpochToEpochTransformer || _dateTimeTransformer instanceof SDFToEpochTransformer) { _resultMetadata = LONG_SV_NO_DICTIONARY_METADATA; @@ -122,44 +123,44 @@ public TransformResultMetadata getResultMetadata() { } @Override - public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { + public long[] transformToLongValuesSV(ValueBlock valueBlock) { if (_resultMetadata != LONG_SV_NO_DICTIONARY_METADATA) { - return super.transformToLongValuesSV(projectionBlock); - } - int length = projectionBlock.getNumDocs(); - if (_longValuesSV == null) { - _longValuesSV = new long[length]; + return super.transformToLongValuesSV(valueBlock); } + int length = valueBlock.getNumDocs(); + initLongValuesSV(length); if (_dateTimeTransformer instanceof EpochToEpochTransformer) { EpochToEpochTransformer dateTimeTransformer = (EpochToEpochTransformer) _dateTimeTransformer; - dateTimeTransformer.transform(_mainTransformFunction.transformToLongValuesSV(projectionBlock), _longValuesSV, - length); + dateTimeTransformer.transform(_mainTransformFunction.transformToLongValuesSV(valueBlock), _longValuesSV, length); } else { SDFToEpochTransformer dateTimeTransformer = (SDFToEpochTransformer) _dateTimeTransformer; - dateTimeTransformer.transform(_mainTransformFunction.transformToStringValuesSV(projectionBlock), _longValuesSV, + dateTimeTransformer.transform(_mainTransformFunction.transformToStringValuesSV(valueBlock), _longValuesSV, length); } return _longValuesSV; } @Override - public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { + public String[] transformToStringValuesSV(ValueBlock valueBlock) { if (_resultMetadata != STRING_SV_NO_DICTIONARY_METADATA) { - return super.transformToStringValuesSV(projectionBlock); - } - int length = projectionBlock.getNumDocs(); - if (_stringValuesSV == null) { - _stringValuesSV = new String[length]; + return super.transformToStringValuesSV(valueBlock); } + int length = valueBlock.getNumDocs(); + initStringValuesSV(length); if (_dateTimeTransformer instanceof EpochToSDFTransformer) { EpochToSDFTransformer dateTimeTransformer = (EpochToSDFTransformer) _dateTimeTransformer; - dateTimeTransformer.transform(_mainTransformFunction.transformToLongValuesSV(projectionBlock), _stringValuesSV, + dateTimeTransformer.transform(_mainTransformFunction.transformToLongValuesSV(valueBlock), _stringValuesSV, length); } else { SDFToSDFTransformer dateTimeTransformer = (SDFToSDFTransformer) _dateTimeTransformer; - dateTimeTransformer.transform(_mainTransformFunction.transformToStringValuesSV(projectionBlock), _stringValuesSV, + dateTimeTransformer.transform(_mainTransformFunction.transformToStringValuesSV(valueBlock), _stringValuesSV, length); } return _stringValuesSV; } + + @Override + public RoaringBitmap getNullBitmap(ValueBlock valueBlock) { + return _mainTransformFunction.getNullBitmap(valueBlock); + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/DateTimeTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/DateTimeTransformFunction.java index 3baf71a990cd..af69953ef52d 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/DateTimeTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/DateTimeTransformFunction.java @@ -22,14 +22,15 @@ import java.util.List; import java.util.Map; import org.apache.pinot.common.function.TransformFunctionType; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec; import org.joda.time.Chronology; import org.joda.time.DateTimeField; import org.joda.time.DateTimeZone; import org.joda.time.chrono.ISOChronology; +import org.roaringbitmap.RoaringBitmap; public abstract class DateTimeTransformFunction extends BaseTransformFunction { @@ -46,14 +47,15 @@ protected DateTimeTransformFunction(String name) { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); Preconditions.checkArgument(!arguments.isEmpty() && arguments.size() <= 2, "%s takes one or two arguments", _name); _timestampsFunction = arguments.get(0); if (arguments.size() == 2) { Preconditions.checkArgument(arguments.get(1) instanceof LiteralTransformFunction, "zoneId parameter %s must be a literal", _name); - _chronology = - ISOChronology.getInstance(DateTimeZone.forID(((LiteralTransformFunction) arguments.get(1)).getLiteral())); + _chronology = ISOChronology.getInstance( + DateTimeZone.forID(((LiteralTransformFunction) arguments.get(1)).getStringLiteral())); } else { _chronology = UTC; } @@ -70,12 +72,10 @@ public TransformResultMetadata getResultMetadata() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_intValuesSV == null) { - _intValuesSV = new int[numDocs]; - } - long[] timestamps = _timestampsFunction.transformToLongValuesSV(projectionBlock); + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + initIntValuesSV(numDocs); + long[] timestamps = _timestampsFunction.transformToLongValuesSV(valueBlock); convert(timestamps, numDocs, _intValuesSV); return _intValuesSV; } @@ -261,4 +261,9 @@ protected void convert(long[] timestamps, int numDocs, int[] output) { } } } + + @Override + public RoaringBitmap getNullBitmap(ValueBlock valueBlock) { + return _timestampsFunction.getNullBitmap(valueBlock); + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/DateTruncTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/DateTruncTransformFunction.java index 6a9a7ce47bd8..53aee59ba86b 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/DateTruncTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/DateTruncTransformFunction.java @@ -24,10 +24,11 @@ import java.util.concurrent.TimeUnit; import org.apache.pinot.common.function.DateTimeUtils; import org.apache.pinot.common.function.TimeZoneKey; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.joda.time.DateTimeField; +import org.roaringbitmap.RoaringBitmap; /** @@ -96,23 +97,24 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); Preconditions.checkArgument(arguments.size() >= 2 && arguments.size() <= 5, "Between two to five arguments are required, example: %s", EXAMPLE_INVOCATION); - String unit = ((LiteralTransformFunction) arguments.get(0)).getLiteral().toLowerCase(); + String unit = ((LiteralTransformFunction) arguments.get(0)).getStringLiteral().toLowerCase(); TransformFunction valueArgument = arguments.get(1); Preconditions.checkArgument( !(valueArgument instanceof LiteralTransformFunction) && valueArgument.getResultMetadata().isSingleValue(), "The second argument of dateTrunc transform function must be a single-valued column or a transform function"); _mainTransformFunction = valueArgument; String inputTimeUnitStr = - (arguments.size() >= 3) ? ((LiteralTransformFunction) arguments.get(2)).getLiteral().toUpperCase() + (arguments.size() >= 3) ? ((LiteralTransformFunction) arguments.get(2)).getStringLiteral().toUpperCase() : TimeUnit.MILLISECONDS.name(); _inputTimeUnit = TimeUnit.valueOf(inputTimeUnitStr); - String timeZone = arguments.size() >= 4 ? ((LiteralTransformFunction) arguments.get(3)).getLiteral() : UTC_TZ; + String timeZone = arguments.size() >= 4 ? ((LiteralTransformFunction) arguments.get(3)).getStringLiteral() : UTC_TZ; String outputTimeUnitStr = - arguments.size() >= 5 ? ((LiteralTransformFunction) arguments.get(4)).getLiteral().toUpperCase() + arguments.size() >= 5 ? ((LiteralTransformFunction) arguments.get(4)).getStringLiteral().toUpperCase() : inputTimeUnitStr; TimeZoneKey timeZoneKey = TimeZoneKey.getTimeZoneKey(timeZone); @@ -127,12 +129,10 @@ public TransformResultMetadata getResultMetadata() { } @Override - public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_longValuesSV == null) { - _longValuesSV = new long[length]; - } - long[] input = _mainTransformFunction.transformToLongValuesSV(projectionBlock); + public long[] transformToLongValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initLongValuesSV(length); + long[] input = _mainTransformFunction.transformToLongValuesSV(valueBlock); for (int i = 0; i < length; i++) { _longValuesSV[i] = _outputTimeUnit.convert(_field.roundFloor(TimeUnit.MILLISECONDS.convert(input[i], _inputTimeUnit)), @@ -140,4 +140,9 @@ public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { } return _longValuesSV; } + + @Override + public RoaringBitmap getNullBitmap(ValueBlock valueBlock) { + return _mainTransformFunction.getNullBitmap(valueBlock); + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/DistinctFromTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/DistinctFromTransformFunction.java index 606e63d52551..07c2e0cabb6e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/DistinctFromTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/DistinctFromTransformFunction.java @@ -18,13 +18,10 @@ */ package org.apache.pinot.core.operator.transform.function; -import java.util.List; -import java.util.Map; import javax.annotation.Nullable; import org.apache.pinot.common.function.TransformFunctionType; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.roaringbitmap.IntConsumer; import org.roaringbitmap.RoaringBitmap; @@ -44,16 +41,6 @@ public class DistinctFromTransformFunction extends BinaryOperatorTransformFuncti // 0 for isDistinct, 1 for isNotDistinct private final int _notDistinctResult; - /** - * Returns a bit map of corresponding column. - * Returns null by default if null option is disabled. - */ - @Nullable - private static RoaringBitmap getNullBitMap(ProjectionBlock projectionBlock, TransformFunction transformFunction) { - String columnName = ((IdentifierTransformFunction) transformFunction).getColumnName(); - return projectionBlock.getBlockValueSet(columnName).getNullBitmap(); - } - /** * Returns true when bitmap is null (null option is disabled) or bitmap is empty. */ @@ -78,25 +65,16 @@ public String getName() { return TransformFunctionType.IS_NOT_DISTINCT_FROM.getName(); } - @Override - public void init(List arguments, Map dataSourceMap) { - super.init(arguments, dataSourceMap); - if (!(_leftTransformFunction instanceof IdentifierTransformFunction) - || !(_rightTransformFunction instanceof IdentifierTransformFunction)) { - throw new IllegalArgumentException("Only column names are supported in DistinctFrom transformation."); - } - } - @Override public TransformResultMetadata getResultMetadata() { return BOOLEAN_SV_NO_DICTIONARY_METADATA; } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - _intValuesSV = super.transformToIntValuesSV(projectionBlock); - RoaringBitmap leftNull = getNullBitMap(projectionBlock, _leftTransformFunction); - RoaringBitmap rightNull = getNullBitMap(projectionBlock, _rightTransformFunction); + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + _intValuesSV = super.transformToIntValuesSV(valueBlock); + RoaringBitmap leftNull = _leftTransformFunction.getNullBitmap(valueBlock); + RoaringBitmap rightNull = _rightTransformFunction.getNullBitmap(valueBlock); // Both sides are not null. if (isEmpty(leftNull) && isEmpty(rightNull)) { return _intValuesSV; @@ -121,4 +99,10 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { andNull.forEach((IntConsumer) i -> _intValuesSV[i] = _notDistinctResult); return _intValuesSV; } + + @Nullable + @Override + public RoaringBitmap getNullBitmap(ValueBlock valueBlock) { + return null; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/DivisionTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/DivisionTransformFunction.java index 0b90fdebb845..b684df347040 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/DivisionTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/DivisionTransformFunction.java @@ -23,9 +23,9 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.utils.ArrayCopyUtils; @@ -45,7 +45,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); // Check that there are exactly 2 arguments if (arguments.size() != 2) { throw new IllegalArgumentException("Exactly 2 arguments are required for DIV transform function"); @@ -69,9 +70,9 @@ public void init(List arguments, Map data if (argument instanceof LiteralTransformFunction) { LiteralTransformFunction literalTransformFunction = (LiteralTransformFunction) argument; if (_resultDataType == DataType.BIG_DECIMAL) { - _bigDecimalLiterals[i] = new BigDecimal(literalTransformFunction.getLiteral()); + _bigDecimalLiterals[i] = literalTransformFunction.getBigDecimalLiteral(); } else { - _doubleLiterals[i] = Double.parseDouble(((LiteralTransformFunction) argument).getLiteral()); + _doubleLiterals[i] = ((LiteralTransformFunction) argument).getDoubleLiteral(); } } else { if (!argument.getResultMetadata().isSingleValue()) { @@ -96,19 +97,17 @@ public TransformResultMetadata getResultMetadata() { @SuppressWarnings("Duplicates") @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_doubleValuesSV == null) { - _doubleValuesSV = new double[length]; - } + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initDoubleValuesSV(length); if (_resultDataType == DataType.BIG_DECIMAL) { - BigDecimal[] values = transformToBigDecimalValuesSV(projectionBlock); + BigDecimal[] values = transformToBigDecimalValuesSV(valueBlock); ArrayCopyUtils.copy(values, _doubleValuesSV, length); } else { if (_firstTransformFunction == null) { Arrays.fill(_doubleValuesSV, 0, length, _doubleLiterals[0]); } else { - double[] values = _firstTransformFunction.transformToDoubleValuesSV(projectionBlock); + double[] values = _firstTransformFunction.transformToDoubleValuesSV(valueBlock); System.arraycopy(values, 0, _doubleValuesSV, 0, length); } if (_secondTransformFunction == null) { @@ -116,7 +115,7 @@ public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { _doubleValuesSV[i] /= _doubleLiterals[1]; } } else { - double[] values = _secondTransformFunction.transformToDoubleValuesSV(projectionBlock); + double[] values = _secondTransformFunction.transformToDoubleValuesSV(valueBlock); for (int i = 0; i < length; i++) { _doubleValuesSV[i] /= values[i]; } @@ -126,19 +125,17 @@ public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { } @Override - public BigDecimal[] transformToBigDecimalValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_bigDecimalValuesSV == null) { - _bigDecimalValuesSV = new BigDecimal[length]; - } + public BigDecimal[] transformToBigDecimalValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initBigDecimalValuesSV(length); if (_resultDataType == DataType.DOUBLE) { - double[] values = transformToDoubleValuesSV(projectionBlock); + double[] values = transformToDoubleValuesSV(valueBlock); ArrayCopyUtils.copy(values, _bigDecimalValuesSV, length); } else { if (_firstTransformFunction == null) { Arrays.fill(_bigDecimalValuesSV, 0, length, _bigDecimalLiterals[0]); } else { - BigDecimal[] values = _firstTransformFunction.transformToBigDecimalValuesSV(projectionBlock); + BigDecimal[] values = _firstTransformFunction.transformToBigDecimalValuesSV(valueBlock); System.arraycopy(values, 0, _bigDecimalValuesSV, 0, length); } if (_secondTransformFunction == null) { @@ -147,7 +144,7 @@ public BigDecimal[] transformToBigDecimalValuesSV(ProjectionBlock projectionBloc _bigDecimalValuesSV[i] = _bigDecimalValuesSV[i].divide(_bigDecimalLiterals[1], RoundingMode.HALF_EVEN); } } else { - BigDecimal[] values = _secondTransformFunction.transformToBigDecimalValuesSV(projectionBlock); + BigDecimal[] values = _secondTransformFunction.transformToBigDecimalValuesSV(valueBlock); for (int i = 0; i < length; i++) { // todo: expose roundingMode/mathContext as parameter in DivisionTransformFunction. _bigDecimalValuesSV[i] = _bigDecimalValuesSV[i].divide(values[i], RoundingMode.HALF_EVEN); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ExtractTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ExtractTransformFunction.java index 9c08b6c514dc..906ce387813f 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ExtractTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ExtractTransformFunction.java @@ -20,23 +20,18 @@ import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import java.util.stream.IntStream; +import org.apache.pinot.common.function.DateTimeUtils; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; -import org.joda.time.Chronology; -import org.joda.time.DateTimeField; -import org.joda.time.chrono.ISOChronology; +import org.roaringbitmap.RoaringBitmap; public class ExtractTransformFunction extends BaseTransformFunction { public static final String FUNCTION_NAME = "extract"; private TransformFunction _mainTransformFunction; - protected Field _field; - protected Chronology _chronology = ISOChronology.getInstanceUTC(); - - private enum Field { - YEAR, MONTH, DAY, HOUR, MINUTE, SECOND - } + private DateTimeUtils.ExtractFieldType _field; @Override public String getName() { @@ -44,12 +39,13 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); if (arguments.size() != 2) { throw new IllegalArgumentException("Exactly 2 arguments are required for EXTRACT transform function"); } - _field = Field.valueOf(((LiteralTransformFunction) arguments.get(0)).getLiteral()); + _field = DateTimeUtils.ExtractFieldType.valueOf(((LiteralTransformFunction) arguments.get(0)).getStringLiteral()); _mainTransformFunction = arguments.get(1); } @@ -59,47 +55,16 @@ public TransformResultMetadata getResultMetadata() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_intValuesSV == null) { - _intValuesSV = new int[numDocs]; - } - long[] timestamps = _mainTransformFunction.transformToLongValuesSV(projectionBlock); - convert(timestamps, numDocs, _intValuesSV); + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + initIntValuesSV(numDocs); + long[] timestamps = _mainTransformFunction.transformToLongValuesSV(valueBlock); + IntStream.range(0, numDocs).forEach(i -> _intValuesSV[i] = DateTimeUtils.extract(_field, timestamps[i])); return _intValuesSV; } - private void convert(long[] timestamps, int numDocs, int[] output) { - for (int i = 0; i < numDocs; i++) { - DateTimeField accessor; - switch (_field) { - case YEAR: - accessor = _chronology.year(); - output[i] = accessor.get(timestamps[i]); - break; - case MONTH: - accessor = _chronology.monthOfYear(); - output[i] = accessor.get(timestamps[i]); - break; - case DAY: - accessor = _chronology.dayOfMonth(); - output[i] = accessor.get(timestamps[i]); - break; - case HOUR: - accessor = _chronology.hourOfDay(); - output[i] = accessor.get(timestamps[i]); - break; - case MINUTE: - accessor = _chronology.minuteOfHour(); - output[i] = accessor.get(timestamps[i]); - break; - case SECOND: - accessor = _chronology.secondOfMinute(); - output[i] = accessor.get(timestamps[i]); - break; - default: - throw new IllegalArgumentException("Unsupported FIELD type"); - } - } + @Override + public RoaringBitmap getNullBitmap(ValueBlock valueBlock) { + return _mainTransformFunction.getNullBitmap(valueBlock); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/GreatestTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/GreatestTransformFunction.java index 3cafa769d8ab..686a8d4f77ca 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/GreatestTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/GreatestTransformFunction.java @@ -16,13 +16,22 @@ * specific language governing permissions and limitations * under the License. */ + package org.apache.pinot.core.operator.transform.function; import java.math.BigDecimal; import org.apache.pinot.common.function.TransformFunctionType; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; - +/** + * The GreatestTransformFunction implements the Greatest operator. + * + * Return the greatest results for the arguments + * + * Expected result: + * greatest(columnA, columnB, columnC): largest among columnA, columnB, columnC + * + * Note that null values will be ignored for evaluation. If all values are null, we return null. + */ public class GreatestTransformFunction extends SelectTupleElementTransformFunction { public GreatestTransformFunction() { @@ -30,106 +39,36 @@ public GreatestTransformFunction() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_intValuesSV == null) { - _intValuesSV = new int[numDocs]; - } - int[] values = _arguments.get(0).transformToIntValuesSV(projectionBlock); - System.arraycopy(values, 0, _intValuesSV, 0, numDocs); - for (int i = 1; i < _arguments.size(); i++) { - values = _arguments.get(i).transformToIntValuesSV(projectionBlock); - for (int j = 0; j < numDocs & j < values.length; j++) { - _intValuesSV[j] = Math.max(_intValuesSV[j], values[j]); - } - } - return _intValuesSV; + protected int binaryFunction(int a, int b) { + return Math.max(a, b); } @Override - public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_longValuesSV == null) { - _longValuesSV = new long[numDocs]; - } - long[] values = _arguments.get(0).transformToLongValuesSV(projectionBlock); - System.arraycopy(values, 0, _longValuesSV, 0, numDocs); - for (int i = 1; i < _arguments.size(); i++) { - values = _arguments.get(i).transformToLongValuesSV(projectionBlock); - for (int j = 0; j < numDocs & j < values.length; j++) { - _longValuesSV[j] = Math.max(_longValuesSV[j], values[j]); - } - } - return _longValuesSV; + protected long binaryFunction(long a, long b) { + return Math.max(a, b); } @Override - public float[] transformToFloatValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_floatValuesSV == null) { - _floatValuesSV = new float[numDocs]; - } - float[] values = _arguments.get(0).transformToFloatValuesSV(projectionBlock); - System.arraycopy(values, 0, _floatValuesSV, 0, numDocs); - for (int i = 1; i < _arguments.size(); i++) { - values = _arguments.get(i).transformToFloatValuesSV(projectionBlock); - for (int j = 0; j < numDocs & j < values.length; j++) { - _floatValuesSV[j] = Math.max(_floatValuesSV[j], values[j]); - } - } - return _floatValuesSV; + protected float binaryFunction(float a, float b) { + return Math.max(a, b); } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_doubleValuesSV == null) { - _doubleValuesSV = new double[numDocs]; - } - double[] values = _arguments.get(0).transformToDoubleValuesSV(projectionBlock); - System.arraycopy(values, 0, _doubleValuesSV, 0, numDocs); - for (int i = 1; i < _arguments.size(); i++) { - values = _arguments.get(i).transformToDoubleValuesSV(projectionBlock); - for (int j = 0; j < numDocs & j < values.length; j++) { - _doubleValuesSV[j] = Math.max(_doubleValuesSV[j], values[j]); - } - } - return _doubleValuesSV; + protected double binaryFunction(double a, double b) { + return Math.max(a, b); } @Override - public BigDecimal[] transformToBigDecimalValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_bigDecimalValuesSV == null) { - _bigDecimalValuesSV = new BigDecimal[numDocs]; - } - BigDecimal[] values = _arguments.get(0).transformToBigDecimalValuesSV(projectionBlock); - System.arraycopy(values, 0, _bigDecimalValuesSV, 0, numDocs); - for (int i = 1; i < _arguments.size(); i++) { - values = _arguments.get(i).transformToBigDecimalValuesSV(projectionBlock); - for (int j = 0; j < numDocs & j < values.length; j++) { - _bigDecimalValuesSV[j] = _bigDecimalValuesSV[j].max(values[j]); - } - } - return _bigDecimalValuesSV; + protected BigDecimal binaryFunction(BigDecimal a, BigDecimal b) { + return a.max(b); } @Override - public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_stringValuesSV == null) { - _stringValuesSV = new String[numDocs]; - } - String[] values = _arguments.get(0).transformToStringValuesSV(projectionBlock); - System.arraycopy(values, 0, _stringValuesSV, 0, numDocs); - for (int i = 1; i < _arguments.size(); i++) { - values = _arguments.get(i).transformToStringValuesSV(projectionBlock); - for (int j = 0; j < numDocs & j < values.length; j++) { - if (_stringValuesSV[j].compareTo(values[j]) < 0) { - _stringValuesSV[j] = values[j]; - } - } + protected String binaryFunction(String a, String b) { + if (a.compareTo(b) < 0) { + return b; + } else { + return a; } - return _stringValuesSV; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/GroovyTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/GroovyTransformFunction.java index f940b8fcd860..074100c8c0bf 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/GroovyTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/GroovyTransformFunction.java @@ -32,11 +32,10 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import org.apache.commons.lang3.EnumUtils; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.segment.local.function.GroovyFunctionEvaluator; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.utils.JsonUtils; @@ -70,7 +69,7 @@ public class GroovyTransformFunction extends BaseTransformFunction { private TransformFunction[] _groovyArguments; private boolean[] _isSourceSingleValue; private DataType[] _sourceStoredTypes; - private BiFunction[] _transformToValuesFunctions; + private BiFunction[] _transformToValuesFunctions; private BiFunction[] _fetchElementFunctions; private Object[] _sourceArrays; private Object[] _bindingValues; @@ -81,7 +80,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); int numArgs = arguments.size(); if (numArgs < 2) { throw new IllegalArgumentException("GROOVY transform function requires at least 2 arguments"); @@ -91,7 +91,7 @@ public void init(List arguments, Map data TransformFunction returnValueMetadata = arguments.get(0); Preconditions.checkState(returnValueMetadata instanceof LiteralTransformFunction, "First argument of GROOVY transform function must be a literal, representing a json string"); - String returnValueMetadataStr = ((LiteralTransformFunction) returnValueMetadata).getLiteral(); + String returnValueMetadataStr = ((LiteralTransformFunction) returnValueMetadata).getStringLiteral(); try { JsonNode returnValueMetadataJson = JsonUtils.stringToJsonNode(returnValueMetadataStr); Preconditions.checkState(returnValueMetadataJson.hasNonNull(RETURN_TYPE_KEY), @@ -133,8 +133,8 @@ public void init(List arguments, Map data // construct arguments string for GroovyFunctionEvaluator String argumentsStr = IntStream.range(0, _numGroovyArgs).mapToObj(i -> ARGUMENT_PREFIX + i) .collect(Collectors.joining(GROOVY_ARG_DELIMITER)); - _groovyFunctionEvaluator = new GroovyFunctionEvaluator( - String.format(GROOVY_TEMPLATE_WITH_ARGS, ((LiteralTransformFunction) groovyTransformFunction).getLiteral(), + _groovyFunctionEvaluator = new GroovyFunctionEvaluator(String.format(GROOVY_TEMPLATE_WITH_ARGS, + ((LiteralTransformFunction) groovyTransformFunction).getStringLiteral(), argumentsStr)); _transformToValuesFunctions = new BiFunction[_numGroovyArgs]; @@ -142,7 +142,7 @@ public void init(List arguments, Map data initFunctions(); } else { _groovyFunctionEvaluator = new GroovyFunctionEvaluator(String.format(GROOVY_TEMPLATE_WITHOUT_ARGS, - ((LiteralTransformFunction) groovyTransformFunction).getLiteral())); + ((LiteralTransformFunction) groovyTransformFunction).getStringLiteral())); } _sourceArrays = new Object[_numGroovyArgs]; _bindingValues = new Object[_numGroovyArgs]; @@ -156,7 +156,7 @@ public TransformResultMetadata getResultMetadata() { private void initFunctions() { for (int i = 0; i < _numGroovyArgs; i++) { BiFunction getElementFunction; - BiFunction transformToValuesFunction; + BiFunction transformToValuesFunction; if (_isSourceSingleValue[i]) { switch (_sourceStoredTypes[i]) { case INT: @@ -220,13 +220,11 @@ private void initFunctions() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_intValuesSV == null) { - _intValuesSV = new int[length]; - } + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initIntValuesSV(length); for (int i = 0; i < _numGroovyArgs; i++) { - _sourceArrays[i] = _transformToValuesFunctions[i].apply(_groovyArguments[i], projectionBlock); + _sourceArrays[i] = _transformToValuesFunctions[i].apply(_groovyArguments[i], valueBlock); } for (int i = 0; i < length; i++) { for (int j = 0; j < _numGroovyArgs; j++) { @@ -238,13 +236,11 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { } @Override - public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_longValuesSV == null) { - _longValuesSV = new long[length]; - } + public long[] transformToLongValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initLongValuesSV(length); for (int i = 0; i < _numGroovyArgs; i++) { - _sourceArrays[i] = _transformToValuesFunctions[i].apply(_groovyArguments[i], projectionBlock); + _sourceArrays[i] = _transformToValuesFunctions[i].apply(_groovyArguments[i], valueBlock); } for (int i = 0; i < length; i++) { for (int j = 0; j < _numGroovyArgs; j++) { @@ -256,13 +252,11 @@ public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { } @Override - public float[] transformToFloatValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_floatValuesSV == null) { - _floatValuesSV = new float[length]; - } + public float[] transformToFloatValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initFloatValuesSV(length); for (int i = 0; i < _numGroovyArgs; i++) { - _sourceArrays[i] = _transformToValuesFunctions[i].apply(_groovyArguments[i], projectionBlock); + _sourceArrays[i] = _transformToValuesFunctions[i].apply(_groovyArguments[i], valueBlock); } for (int i = 0; i < length; i++) { for (int j = 0; j < _numGroovyArgs; j++) { @@ -274,14 +268,12 @@ public float[] transformToFloatValuesSV(ProjectionBlock projectionBlock) { } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { - if (_doubleValuesSV == null) { - _doubleValuesSV = new double[DocIdSetPlanNode.MAX_DOC_PER_CALL]; - } + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initDoubleValuesSV(length); for (int i = 0; i < _numGroovyArgs; i++) { - _sourceArrays[i] = _transformToValuesFunctions[i].apply(_groovyArguments[i], projectionBlock); + _sourceArrays[i] = _transformToValuesFunctions[i].apply(_groovyArguments[i], valueBlock); } - int length = projectionBlock.getNumDocs(); for (int i = 0; i < length; i++) { for (int j = 0; j < _numGroovyArgs; j++) { _bindingValues[j] = _fetchElementFunctions[j].apply(_sourceArrays[j], i); @@ -292,13 +284,11 @@ public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { } @Override - public BigDecimal[] transformToBigDecimalValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_bigDecimalValuesSV == null) { - _bigDecimalValuesSV = new BigDecimal[length]; - } + public BigDecimal[] transformToBigDecimalValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initBigDecimalValuesSV(length); for (int i = 0; i < _numGroovyArgs; i++) { - _sourceArrays[i] = _transformToValuesFunctions[i].apply(_groovyArguments[i], projectionBlock); + _sourceArrays[i] = _transformToValuesFunctions[i].apply(_groovyArguments[i], valueBlock); } for (int i = 0; i < length; i++) { for (int j = 0; j < _numGroovyArgs; j++) { @@ -310,13 +300,11 @@ public BigDecimal[] transformToBigDecimalValuesSV(ProjectionBlock projectionBloc } @Override - public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_stringValuesSV == null) { - _stringValuesSV = new String[length]; - } + public String[] transformToStringValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initStringValuesSV(length); for (int i = 0; i < _numGroovyArgs; i++) { - _sourceArrays[i] = _transformToValuesFunctions[i].apply(_groovyArguments[i], projectionBlock); + _sourceArrays[i] = _transformToValuesFunctions[i].apply(_groovyArguments[i], valueBlock); } for (int i = 0; i < length; i++) { for (int j = 0; j < _numGroovyArgs; j++) { @@ -328,13 +316,11 @@ public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { } @Override - public int[][] transformToIntValuesMV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_intValuesMV == null) { - _intValuesMV = new int[length][]; - } + public int[][] transformToIntValuesMV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initIntValuesMV(length); for (int i = 0; i < _numGroovyArgs; i++) { - _sourceArrays[i] = _transformToValuesFunctions[i].apply(_groovyArguments[i], projectionBlock); + _sourceArrays[i] = _transformToValuesFunctions[i].apply(_groovyArguments[i], valueBlock); } for (int i = 0; i < length; i++) { for (int j = 0; j < _numGroovyArgs; j++) { @@ -353,13 +339,11 @@ public int[][] transformToIntValuesMV(ProjectionBlock projectionBlock) { } @Override - public long[][] transformToLongValuesMV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_longValuesMV == null) { - _longValuesMV = new long[length][]; - } + public long[][] transformToLongValuesMV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initLongValuesMV(length); for (int i = 0; i < _numGroovyArgs; i++) { - _sourceArrays[i] = _transformToValuesFunctions[i].apply(_groovyArguments[i], projectionBlock); + _sourceArrays[i] = _transformToValuesFunctions[i].apply(_groovyArguments[i], valueBlock); } for (int i = 0; i < length; i++) { for (int j = 0; j < _numGroovyArgs; j++) { @@ -378,13 +362,11 @@ public long[][] transformToLongValuesMV(ProjectionBlock projectionBlock) { } @Override - public float[][] transformToFloatValuesMV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_floatValuesMV == null) { - _floatValuesMV = new float[length][]; - } + public float[][] transformToFloatValuesMV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initFloatValuesMV(length); for (int i = 0; i < _numGroovyArgs; i++) { - _sourceArrays[i] = _transformToValuesFunctions[i].apply(_groovyArguments[i], projectionBlock); + _sourceArrays[i] = _transformToValuesFunctions[i].apply(_groovyArguments[i], valueBlock); } for (int i = 0; i < length; i++) { for (int j = 0; j < _numGroovyArgs; j++) { @@ -403,13 +385,11 @@ public float[][] transformToFloatValuesMV(ProjectionBlock projectionBlock) { } @Override - public double[][] transformToDoubleValuesMV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_doubleValuesMV == null) { - _doubleValuesMV = new double[length][]; - } + public double[][] transformToDoubleValuesMV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initDoubleValuesMV(length); for (int i = 0; i < _numGroovyArgs; i++) { - _sourceArrays[i] = _transformToValuesFunctions[i].apply(_groovyArguments[i], projectionBlock); + _sourceArrays[i] = _transformToValuesFunctions[i].apply(_groovyArguments[i], valueBlock); } for (int i = 0; i < length; i++) { for (int j = 0; j < _numGroovyArgs; j++) { @@ -428,14 +408,12 @@ public double[][] transformToDoubleValuesMV(ProjectionBlock projectionBlock) { } @Override - public String[][] transformToStringValuesMV(ProjectionBlock projectionBlock) { - if (_stringValuesMV == null) { - _stringValuesMV = new String[DocIdSetPlanNode.MAX_DOC_PER_CALL][]; - } + public String[][] transformToStringValuesMV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initStringValuesMV(length); for (int i = 0; i < _numGroovyArgs; i++) { - _sourceArrays[i] = _transformToValuesFunctions[i].apply(_groovyArguments[i], projectionBlock); + _sourceArrays[i] = _transformToValuesFunctions[i].apply(_groovyArguments[i], valueBlock); } - int length = projectionBlock.getNumDocs(); for (int i = 0; i < length; i++) { for (int j = 0; j < _numGroovyArgs; j++) { _bindingValues[j] = _fetchElementFunctions[j].apply(_sourceArrays[j], i); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IdentifierTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IdentifierTransformFunction.java index 42a83af3d9c1..1d0cd84fb592 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IdentifierTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IdentifierTransformFunction.java @@ -21,12 +21,14 @@ import java.math.BigDecimal; import java.util.List; import java.util.Map; +import javax.annotation.Nullable; +import org.apache.pinot.core.operator.ColumnContext; import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; -import org.apache.pinot.segment.spi.datasource.DataSourceMetadata; import org.apache.pinot.segment.spi.evaluator.TransformEvaluator; import org.apache.pinot.segment.spi.index.reader.Dictionary; +import org.roaringbitmap.RoaringBitmap; /** @@ -38,12 +40,11 @@ public class IdentifierTransformFunction implements TransformFunction, PushDownT private final Dictionary _dictionary; private final TransformResultMetadata _resultMetadata; - public IdentifierTransformFunction(String columnName, DataSource dataSource) { + public IdentifierTransformFunction(String columnName, ColumnContext columnContext) { _columnName = columnName; - _dictionary = dataSource.getDictionary(); - DataSourceMetadata dataSourceMetadata = dataSource.getDataSourceMetadata(); - _resultMetadata = new TransformResultMetadata(dataSourceMetadata.getDataType(), dataSourceMetadata.isSingleValue(), - _dictionary != null); + _dictionary = columnContext.getDictionary(); + _resultMetadata = + new TransformResultMetadata(columnContext.getDataType(), columnContext.isSingleValue(), _dictionary != null); } public String getColumnName() { @@ -56,7 +57,7 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { throw new UnsupportedOperationException(); } @@ -65,85 +66,85 @@ public TransformResultMetadata getResultMetadata() { return _resultMetadata; } + @Nullable @Override public Dictionary getDictionary() { return _dictionary; } @Override - public int[] transformToDictIdsSV(ProjectionBlock projectionBlock) { - return projectionBlock.getBlockValueSet(_columnName).getDictionaryIdsSV(); + public int[] transformToDictIdsSV(ValueBlock valueBlock) { + return valueBlock.getBlockValueSet(_columnName).getDictionaryIdsSV(); } @Override - public int[][] transformToDictIdsMV(ProjectionBlock projectionBlock) { - return projectionBlock.getBlockValueSet(_columnName).getDictionaryIdsMV(); + public int[][] transformToDictIdsMV(ValueBlock valueBlock) { + return valueBlock.getBlockValueSet(_columnName).getDictionaryIdsMV(); } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - return projectionBlock.getBlockValueSet(_columnName).getIntValuesSV(); + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + return valueBlock.getBlockValueSet(_columnName).getIntValuesSV(); } @Override - public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { - return projectionBlock.getBlockValueSet(_columnName).getLongValuesSV(); + public long[] transformToLongValuesSV(ValueBlock valueBlock) { + return valueBlock.getBlockValueSet(_columnName).getLongValuesSV(); } @Override - public float[] transformToFloatValuesSV(ProjectionBlock projectionBlock) { - return projectionBlock.getBlockValueSet(_columnName).getFloatValuesSV(); + public float[] transformToFloatValuesSV(ValueBlock valueBlock) { + return valueBlock.getBlockValueSet(_columnName).getFloatValuesSV(); } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { - return projectionBlock.getBlockValueSet(_columnName).getDoubleValuesSV(); + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + return valueBlock.getBlockValueSet(_columnName).getDoubleValuesSV(); } - @Override - public BigDecimal[] transformToBigDecimalValuesSV(ProjectionBlock projectionBlock) { - return projectionBlock.getBlockValueSet(_columnName).getBigDecimalValuesSV(); + public BigDecimal[] transformToBigDecimalValuesSV(ValueBlock valueBlock) { + return valueBlock.getBlockValueSet(_columnName).getBigDecimalValuesSV(); } @Override - public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { - return projectionBlock.getBlockValueSet(_columnName).getStringValuesSV(); + public String[] transformToStringValuesSV(ValueBlock valueBlock) { + return valueBlock.getBlockValueSet(_columnName).getStringValuesSV(); } @Override - public byte[][] transformToBytesValuesSV(ProjectionBlock projectionBlock) { - return projectionBlock.getBlockValueSet(_columnName).getBytesValuesSV(); + public byte[][] transformToBytesValuesSV(ValueBlock valueBlock) { + return valueBlock.getBlockValueSet(_columnName).getBytesValuesSV(); } @Override - public int[][] transformToIntValuesMV(ProjectionBlock projectionBlock) { - return projectionBlock.getBlockValueSet(_columnName).getIntValuesMV(); + public int[][] transformToIntValuesMV(ValueBlock valueBlock) { + return valueBlock.getBlockValueSet(_columnName).getIntValuesMV(); } @Override - public long[][] transformToLongValuesMV(ProjectionBlock projectionBlock) { - return projectionBlock.getBlockValueSet(_columnName).getLongValuesMV(); + public long[][] transformToLongValuesMV(ValueBlock valueBlock) { + return valueBlock.getBlockValueSet(_columnName).getLongValuesMV(); } @Override - public float[][] transformToFloatValuesMV(ProjectionBlock projectionBlock) { - return projectionBlock.getBlockValueSet(_columnName).getFloatValuesMV(); + public float[][] transformToFloatValuesMV(ValueBlock valueBlock) { + return valueBlock.getBlockValueSet(_columnName).getFloatValuesMV(); } @Override - public double[][] transformToDoubleValuesMV(ProjectionBlock projectionBlock) { - return projectionBlock.getBlockValueSet(_columnName).getDoubleValuesMV(); + public double[][] transformToDoubleValuesMV(ValueBlock valueBlock) { + return valueBlock.getBlockValueSet(_columnName).getDoubleValuesMV(); } @Override - public String[][] transformToStringValuesMV(ProjectionBlock projectionBlock) { - return projectionBlock.getBlockValueSet(_columnName).getStringValuesMV(); + public String[][] transformToStringValuesMV(ValueBlock valueBlock) { + return valueBlock.getBlockValueSet(_columnName).getStringValuesMV(); } @Override - public byte[][][] transformToBytesValuesMV(ProjectionBlock projectionBlock) { - return projectionBlock.getBlockValueSet(_columnName).getBytesValuesMV(); + public byte[][][] transformToBytesValuesMV(ValueBlock valueBlock) { + return valueBlock.getBlockValueSet(_columnName).getBytesValuesMV(); } @Override @@ -206,4 +207,9 @@ public void transformToStringValuesMV(ProjectionBlock projectionBlock, Transform String[][] buffer) { projectionBlock.fillValues(_columnName, evaluator, buffer); } + + @Override + public RoaringBitmap getNullBitmap(ValueBlock valueBlock) { + return valueBlock.getBlockValueSet(_columnName).getNullBitmap(); + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/InIdSetTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/InIdSetTransformFunction.java index 72ca718ac0b5..7752c13742fd 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/InIdSetTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/InIdSetTransformFunction.java @@ -23,11 +23,11 @@ import java.util.List; import java.util.Map; import org.apache.pinot.common.function.TransformFunctionType; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.query.utils.idset.IdSet; import org.apache.pinot.core.query.utils.idset.IdSets; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec.DataType; @@ -46,11 +46,12 @@ public class InIdSetTransformFunction extends BaseTransformFunction { @Override public String getName() { - return TransformFunctionType.INIDSET.getName(); + return TransformFunctionType.IN_ID_SET.getName(); } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); Preconditions.checkArgument(arguments.size() == 2, "2 arguments are required for IN_ID_SET transform function: expression, base64 encoded IdSet"); Preconditions.checkArgument(arguments.get(0).getResultMetadata().isSingleValue(), @@ -60,7 +61,7 @@ public void init(List arguments, Map data _transformFunction = arguments.get(0); try { - _idSet = IdSets.fromBase64String(((LiteralTransformFunction) arguments.get(1)).getLiteral()); + _idSet = IdSets.fromBase64String(((LiteralTransformFunction) arguments.get(1)).getStringLiteral()); } catch (IOException e) { throw new IllegalArgumentException("Caught exception while deserializing IdSet", e); } @@ -72,45 +73,43 @@ public TransformResultMetadata getResultMetadata() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_intValuesSV == null) { - _intValuesSV = new int[length]; - } + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initIntValuesSV(length); DataType storedType = _transformFunction.getResultMetadata().getDataType().getStoredType(); switch (storedType) { case INT: - int[] intValues = _transformFunction.transformToIntValuesSV(projectionBlock); + int[] intValues = _transformFunction.transformToIntValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = _idSet.contains(intValues[i]) ? 1 : 0; } break; case LONG: - long[] longValues = _transformFunction.transformToLongValuesSV(projectionBlock); + long[] longValues = _transformFunction.transformToLongValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = _idSet.contains(longValues[i]) ? 1 : 0; } break; case FLOAT: - float[] floatValues = _transformFunction.transformToFloatValuesSV(projectionBlock); + float[] floatValues = _transformFunction.transformToFloatValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = _idSet.contains(floatValues[i]) ? 1 : 0; } break; case DOUBLE: - double[] doubleValues = _transformFunction.transformToDoubleValuesSV(projectionBlock); + double[] doubleValues = _transformFunction.transformToDoubleValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = _idSet.contains(doubleValues[i]) ? 1 : 0; } break; case STRING: - String[] stringValues = _transformFunction.transformToStringValuesSV(projectionBlock); + String[] stringValues = _transformFunction.transformToStringValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = _idSet.contains(stringValues[i]) ? 1 : 0; } break; case BYTES: - byte[][] bytesValues = _transformFunction.transformToBytesValuesSV(projectionBlock); + byte[][] bytesValues = _transformFunction.transformToBytesValuesSV(valueBlock); for (int i = 0; i < length; i++) { _intValuesSV[i] = _idSet.contains(bytesValues[i]) ? 1 : 0; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/InTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/InTransformFunction.java index 39419cd95915..3a293b2d5953 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/InTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/InTransformFunction.java @@ -28,13 +28,15 @@ import java.util.List; import java.util.Map; import java.util.Set; +import javax.annotation.Nullable; import org.apache.pinot.common.function.TransformFunctionType; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.utils.ByteArray; import org.apache.pinot.spi.utils.BytesUtils; +import org.roaringbitmap.RoaringBitmap; /** @@ -48,6 +50,7 @@ public class InTransformFunction extends BaseTransformFunction { private TransformFunction _mainFunction; private TransformFunction[] _valueFunctions; private Set _valueSet; + private boolean _valueSetContainsNull; @Override public String getName() { @@ -55,7 +58,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); int numArguments = arguments.size(); Preconditions.checkArgument(numArguments >= 2, "At least 2 arguments are required for [%s] " + "transform function: (expression, values)", getName()); @@ -66,7 +70,12 @@ public void init(List arguments, Map data for (int i = 1; i < numArguments; i++) { TransformFunction valueFunction = arguments.get(i); if (valueFunction instanceof LiteralTransformFunction) { - stringValues.add(((LiteralTransformFunction) valueFunction).getLiteral()); + LiteralTransformFunction literalTransformFunction = ((LiteralTransformFunction) valueFunction); + if (literalTransformFunction.getResultMetadata().getDataType() == DataType.UNKNOWN) { + _valueSetContainsNull = true; + } else { + stringValues.add(literalTransformFunction.getStringLiteral()); + } } else { allLiteralValues = false; break; @@ -115,6 +124,9 @@ public void init(List arguments, Map data } _valueSet = bytesValues; break; + case UNKNOWN: + _valueSet = new ObjectOpenHashSet<>(); + break; default: throw new IllegalStateException(); } @@ -137,15 +149,9 @@ public TransformResultMetadata getResultMetadata() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - - if (_intValuesSV == null) { - _intValuesSV = new int[length]; - } else { - Arrays.fill(_intValuesSV, 0); - } - + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initZeroFillingIntValuesSV(length); TransformResultMetadata mainFunctionMetadata = _mainFunction.getResultMetadata(); DataType storedType = mainFunctionMetadata.getDataType().getStoredType(); if (_valueSet != null) { @@ -153,7 +159,7 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { switch (storedType) { case INT: IntOpenHashSet inIntValues = (IntOpenHashSet) _valueSet; - int[] intValues = _mainFunction.transformToIntValuesSV(projectionBlock); + int[] intValues = _mainFunction.transformToIntValuesSV(valueBlock); for (int i = 0; i < length; i++) { if (inIntValues.contains(intValues[i])) { _intValuesSV[i] = 1; @@ -162,7 +168,7 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { break; case LONG: LongOpenHashSet inLongValues = (LongOpenHashSet) _valueSet; - long[] longValues = _mainFunction.transformToLongValuesSV(projectionBlock); + long[] longValues = _mainFunction.transformToLongValuesSV(valueBlock); for (int i = 0; i < length; i++) { if (inLongValues.contains(longValues[i])) { _intValuesSV[i] = 1; @@ -171,7 +177,7 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { break; case FLOAT: FloatOpenHashSet inFloatValues = (FloatOpenHashSet) _valueSet; - float[] floatValues = _mainFunction.transformToFloatValuesSV(projectionBlock); + float[] floatValues = _mainFunction.transformToFloatValuesSV(valueBlock); for (int i = 0; i < length; i++) { if (inFloatValues.contains(floatValues[i])) { _intValuesSV[i] = 1; @@ -180,7 +186,7 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { break; case DOUBLE: DoubleOpenHashSet inDoubleValues = (DoubleOpenHashSet) _valueSet; - double[] doubleValues = _mainFunction.transformToDoubleValuesSV(projectionBlock); + double[] doubleValues = _mainFunction.transformToDoubleValuesSV(valueBlock); for (int i = 0; i < length; i++) { if (inDoubleValues.contains(doubleValues[i])) { _intValuesSV[i] = 1; @@ -189,7 +195,7 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { break; case STRING: ObjectOpenHashSet inStringValues = (ObjectOpenHashSet) _valueSet; - String[] stringValues = _mainFunction.transformToStringValuesSV(projectionBlock); + String[] stringValues = _mainFunction.transformToStringValuesSV(valueBlock); for (int i = 0; i < length; i++) { if (inStringValues.contains(stringValues[i])) { _intValuesSV[i] = 1; @@ -198,13 +204,16 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { break; case BYTES: ObjectOpenHashSet inBytesValues = (ObjectOpenHashSet) _valueSet; - byte[][] bytesValues = _mainFunction.transformToBytesValuesSV(projectionBlock); + byte[][] bytesValues = _mainFunction.transformToBytesValuesSV(valueBlock); for (int i = 0; i < length; i++) { if (inBytesValues.contains(new ByteArray(bytesValues[i]))) { _intValuesSV[i] = 1; } } break; + case UNKNOWN: + fillResultUnknown(length); + break; default: throw new IllegalStateException(); } @@ -212,7 +221,7 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { switch (storedType) { case INT: IntOpenHashSet inIntValues = (IntOpenHashSet) _valueSet; - int[][] intValues = _mainFunction.transformToIntValuesMV(projectionBlock); + int[][] intValues = _mainFunction.transformToIntValuesMV(valueBlock); for (int i = 0; i < length; i++) { for (int intValue : intValues[i]) { if (inIntValues.contains(intValue)) { @@ -224,7 +233,7 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { break; case LONG: LongOpenHashSet inLongValues = (LongOpenHashSet) _valueSet; - long[][] longValues = _mainFunction.transformToLongValuesMV(projectionBlock); + long[][] longValues = _mainFunction.transformToLongValuesMV(valueBlock); for (int i = 0; i < length; i++) { for (long longValue : longValues[i]) { if (inLongValues.contains(longValue)) { @@ -236,7 +245,7 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { break; case FLOAT: FloatOpenHashSet inFloatValues = (FloatOpenHashSet) _valueSet; - float[][] floatValues = _mainFunction.transformToFloatValuesMV(projectionBlock); + float[][] floatValues = _mainFunction.transformToFloatValuesMV(valueBlock); for (int i = 0; i < length; i++) { for (float floatValue : floatValues[i]) { if (inFloatValues.contains(floatValue)) { @@ -248,7 +257,7 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { break; case DOUBLE: DoubleOpenHashSet inDoubleValues = (DoubleOpenHashSet) _valueSet; - double[][] doubleValues = _mainFunction.transformToDoubleValuesMV(projectionBlock); + double[][] doubleValues = _mainFunction.transformToDoubleValuesMV(valueBlock); for (int i = 0; i < length; i++) { for (double doubleValue : doubleValues[i]) { if (inDoubleValues.contains(doubleValue)) { @@ -260,7 +269,7 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { break; case STRING: ObjectOpenHashSet inStringValues = (ObjectOpenHashSet) _valueSet; - String[][] stringValues = _mainFunction.transformToStringValuesMV(projectionBlock); + String[][] stringValues = _mainFunction.transformToStringValuesMV(valueBlock); for (int i = 0; i < length; i++) { for (String stringValue : stringValues[i]) { if (inStringValues.contains(stringValue)) { @@ -270,6 +279,9 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { } } break; + case UNKNOWN: + fillResultUnknown(length); + break; default: throw new IllegalStateException(); } @@ -278,10 +290,10 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { int numValues = _valueFunctions.length; switch (storedType) { case INT: - int[] intValues = _mainFunction.transformToIntValuesSV(projectionBlock); + int[] intValues = _mainFunction.transformToIntValuesSV(valueBlock); int[][] inIntValues = new int[numValues][]; for (int i = 0; i < numValues; i++) { - inIntValues[i] = _valueFunctions[i].transformToIntValuesSV(projectionBlock); + inIntValues[i] = _valueFunctions[i].transformToIntValuesSV(valueBlock); } for (int i = 0; i < length; i++) { for (int[] inIntValue : inIntValues) { @@ -293,10 +305,10 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { } break; case LONG: - long[] longValues = _mainFunction.transformToLongValuesSV(projectionBlock); + long[] longValues = _mainFunction.transformToLongValuesSV(valueBlock); long[][] inLongValues = new long[numValues][]; for (int i = 0; i < numValues; i++) { - inLongValues[i] = _valueFunctions[i].transformToLongValuesSV(projectionBlock); + inLongValues[i] = _valueFunctions[i].transformToLongValuesSV(valueBlock); } for (int i = 0; i < length; i++) { for (long[] inLongValue : inLongValues) { @@ -308,10 +320,10 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { } break; case FLOAT: - float[] floatValues = _mainFunction.transformToFloatValuesSV(projectionBlock); + float[] floatValues = _mainFunction.transformToFloatValuesSV(valueBlock); float[][] inFloatValues = new float[numValues][]; for (int i = 0; i < numValues; i++) { - inFloatValues[i] = _valueFunctions[i].transformToFloatValuesSV(projectionBlock); + inFloatValues[i] = _valueFunctions[i].transformToFloatValuesSV(valueBlock); } for (int i = 0; i < length; i++) { // Check int bits to be aligned with the Set (Float.equals()) behavior @@ -325,10 +337,10 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { } break; case DOUBLE: - double[] doubleValues = _mainFunction.transformToDoubleValuesSV(projectionBlock); + double[] doubleValues = _mainFunction.transformToDoubleValuesSV(valueBlock); double[][] inDoubleValues = new double[numValues][]; for (int i = 0; i < numValues; i++) { - inDoubleValues[i] = _valueFunctions[i].transformToDoubleValuesSV(projectionBlock); + inDoubleValues[i] = _valueFunctions[i].transformToDoubleValuesSV(valueBlock); } for (int i = 0; i < length; i++) { // Check long bits to be aligned with the Set (Double.equals()) behavior @@ -342,10 +354,10 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { } break; case STRING: - String[] stringValues = _mainFunction.transformToStringValuesSV(projectionBlock); + String[] stringValues = _mainFunction.transformToStringValuesSV(valueBlock); String[][] inStringValues = new String[numValues][]; for (int i = 0; i < numValues; i++) { - inStringValues[i] = _valueFunctions[i].transformToStringValuesSV(projectionBlock); + inStringValues[i] = _valueFunctions[i].transformToStringValuesSV(valueBlock); } for (int i = 0; i < length; i++) { for (String[] inStringValue : inStringValues) { @@ -357,10 +369,10 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { } break; case BYTES: - byte[][] bytesValues = _mainFunction.transformToBytesValuesSV(projectionBlock); + byte[][] bytesValues = _mainFunction.transformToBytesValuesSV(valueBlock); byte[][][] inBytesValues = new byte[numValues][][]; for (int i = 0; i < numValues; i++) { - inBytesValues[i] = _valueFunctions[i].transformToBytesValuesSV(projectionBlock); + inBytesValues[i] = _valueFunctions[i].transformToBytesValuesSV(valueBlock); } for (int i = 0; i < length; i++) { for (byte[][] inBytesValue : inBytesValues) { @@ -371,6 +383,9 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { } } break; + case UNKNOWN: + fillResultUnknown(length); + break; default: throw new IllegalStateException(); } @@ -378,4 +393,50 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { return _intValuesSV; } + + @Nullable + @Override + public RoaringBitmap getNullBitmap(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + RoaringBitmap result = new RoaringBitmap(); + RoaringBitmap mainFunctionNullBitmap = _mainFunction.getNullBitmap(valueBlock); + if (mainFunctionNullBitmap != null) { + result.or(mainFunctionNullBitmap); + if (result.getCardinality() == length) { + return result; + } + } + int[] intValuesSV = transformToIntValuesSV(valueBlock); + if (_valueSet == null) { + RoaringBitmap valueFunctionsContainNull = new RoaringBitmap(); + RoaringBitmap[] valueFunctionNullBitmaps = new RoaringBitmap[_valueFunctions.length]; + for (int i = 0; i < _valueFunctions.length; i++) { + valueFunctionNullBitmaps[i] = _valueFunctions[i].getNullBitmap(valueBlock); + } + for (int i = 0; i < length; i++) { + for (int j = 0; j < _valueFunctions.length; j++) { + if (valueFunctionNullBitmaps[j] != null && valueFunctionNullBitmaps[j].contains(i)) { + valueFunctionsContainNull.add(i); + break; + } + } + } + for (int i = 0; i < length; i++) { + if (mainFunctionNotContainedInValues(intValuesSV[i]) && valueFunctionsContainNull.contains(i)) { + result.add(i); + } + } + } else { + for (int i = 0; i < length; i++) { + if (mainFunctionNotContainedInValues(intValuesSV[i]) && _valueSetContainsNull) { + result.add(i); + } + } + } + return result; + } + + protected boolean mainFunctionNotContainedInValues(int value) { + return value == 0; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IsFalseTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IsFalseTransformFunction.java new file mode 100644 index 000000000000..5a0cb4ba35b3 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IsFalseTransformFunction.java @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.transform.function; + +import java.util.List; +import java.util.Map; +import org.apache.pinot.common.function.TransformFunctionType; +import org.apache.pinot.core.operator.ColumnContext; + + +public class IsFalseTransformFunction extends BaseBooleanAssertionTransformFunction { + + @Override + public String getName() { + return TransformFunctionType.IS_FALSE.getName(); + } + + @Override + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); + } + + @Override + protected boolean returnsTrueWhenValueIsNull() { + return false; + } + + @Override + protected boolean valueEvaluatesToTrue(int value) { + return value == 0; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IsNotFalseTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IsNotFalseTransformFunction.java new file mode 100644 index 000000000000..3e574830f07f --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IsNotFalseTransformFunction.java @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.transform.function; + +import java.util.List; +import java.util.Map; +import org.apache.pinot.common.function.TransformFunctionType; +import org.apache.pinot.core.operator.ColumnContext; + + +public class IsNotFalseTransformFunction extends BaseBooleanAssertionTransformFunction { + + @Override + public String getName() { + return TransformFunctionType.IS_NOT_TRUE.getName(); + } + + @Override + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); + } + + @Override + protected boolean returnsTrueWhenValueIsNull() { + return true; + } + + @Override + protected boolean valueEvaluatesToTrue(int value) { + return value != 0; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IsNotNullTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IsNotNullTransformFunction.java index c7ba0ae93a42..5b73a6214977 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IsNotNullTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IsNotNullTransformFunction.java @@ -18,72 +18,17 @@ */ package org.apache.pinot.core.operator.transform.function; -import com.google.common.base.Preconditions; -import java.util.Arrays; -import java.util.List; -import java.util.Map; import org.apache.pinot.common.function.TransformFunctionType; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; -import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; -import org.apache.pinot.segment.spi.index.reader.NullValueVectorReader; -import org.roaringbitmap.PeekableIntIterator; -public class IsNotNullTransformFunction extends BaseTransformFunction { - private PeekableIntIterator _nullValueVectorIterator; - +public class IsNotNullTransformFunction extends IsNullTransformFunction { @Override public String getName() { return TransformFunctionType.IS_NOT_NULL.getName(); } @Override - public void init(List arguments, Map dataSourceMap) { - Preconditions.checkArgument(arguments.size() == 1, - "Exact 1 argument is required for IS_NOT_NULL operator function"); - TransformFunction transformFunction = arguments.get(0); - if (!(transformFunction instanceof IdentifierTransformFunction)) { - throw new IllegalArgumentException( - "Only column names are supported in IS_NOT_NULL. Support for functions is planned for future release"); - } - String columnName = ((IdentifierTransformFunction) transformFunction).getColumnName(); - NullValueVectorReader nullValueVectorReader = dataSourceMap.get(columnName).getNullValueVector(); - if (nullValueVectorReader != null) { - _nullValueVectorIterator = nullValueVectorReader.getNullBitmap().getIntIterator(); - } else { - _nullValueVectorIterator = null; - } - } - - @Override - public TransformResultMetadata getResultMetadata() { - return BOOLEAN_SV_NO_DICTIONARY_METADATA; - } - - @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_intValuesSV == null) { - _intValuesSV = new int[length]; - } - Arrays.fill(_intValuesSV, 1); - int[] docIds = projectionBlock.getDocIds(); - if (_nullValueVectorIterator != null) { - int currentDocIdIndex = 0; - while (_nullValueVectorIterator.hasNext() & currentDocIdIndex < length) { - _nullValueVectorIterator.advanceIfNeeded(docIds[currentDocIdIndex]); - if (_nullValueVectorIterator.hasNext()) { - currentDocIdIndex = Arrays.binarySearch(docIds, currentDocIdIndex, length, _nullValueVectorIterator.next()); - if (currentDocIdIndex >= 0) { - _intValuesSV[currentDocIdIndex] = 0; - currentDocIdIndex++; - } else { - currentDocIdIndex = -currentDocIdIndex - 1; - } - } - } - } - return _intValuesSV; + protected int getIsNullValue() { + return 0; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IsNotTrueTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IsNotTrueTransformFunction.java new file mode 100644 index 000000000000..bc9d8bbbc9c9 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IsNotTrueTransformFunction.java @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.transform.function; + +import java.util.List; +import java.util.Map; +import org.apache.pinot.common.function.TransformFunctionType; +import org.apache.pinot.core.operator.ColumnContext; + + +public class IsNotTrueTransformFunction extends BaseBooleanAssertionTransformFunction { + + @Override + public String getName() { + return TransformFunctionType.IS_NOT_TRUE.getName(); + } + + @Override + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); + } + + @Override + protected boolean returnsTrueWhenValueIsNull() { + return true; + } + + @Override + protected boolean valueEvaluatesToTrue(int value) { + return value == 0; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IsNullTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IsNullTransformFunction.java index 5f7a05be953d..47b8d8b328b0 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IsNullTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IsNullTransformFunction.java @@ -23,15 +23,15 @@ import java.util.List; import java.util.Map; import org.apache.pinot.common.function.TransformFunctionType; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; -import org.apache.pinot.segment.spi.index.reader.NullValueVectorReader; -import org.roaringbitmap.PeekableIntIterator; +import org.roaringbitmap.IntConsumer; +import org.roaringbitmap.RoaringBitmap; public class IsNullTransformFunction extends BaseTransformFunction { - private PeekableIntIterator _nullValueVectorIterator; + private TransformFunction _transformFunction; @Override public String getName() { @@ -39,20 +39,10 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { - Preconditions.checkArgument(arguments.size() == 1, "Exact 1 argument is required for IS_NULL operator function"); - TransformFunction transformFunction = arguments.get(0); - if (!(transformFunction instanceof IdentifierTransformFunction)) { - throw new IllegalArgumentException( - "Only column names are supported in IS_NULL. Support for functions is planned for future release"); - } - String columnName = ((IdentifierTransformFunction) transformFunction).getColumnName(); - NullValueVectorReader nullValueVectorReader = dataSourceMap.get(columnName).getNullValueVector(); - if (nullValueVectorReader != null) { - _nullValueVectorIterator = nullValueVectorReader.getNullBitmap().getIntIterator(); - } else { - _nullValueVectorIterator = null; - } + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); + Preconditions.checkArgument(arguments.size() == 1, "Exact 1 argument is required for IS_NULL"); + _transformFunction = arguments.get(0); } @Override @@ -61,28 +51,23 @@ public TransformResultMetadata getResultMetadata() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_intValuesSV == null) { - _intValuesSV = new int[length]; - } - Arrays.fill(_intValuesSV, 0); - int[] docIds = projectionBlock.getDocIds(); - if (_nullValueVectorIterator != null) { - int currentDocIdIndex = 0; - while (_nullValueVectorIterator.hasNext() & currentDocIdIndex < length) { - _nullValueVectorIterator.advanceIfNeeded(docIds[currentDocIdIndex]); - if (_nullValueVectorIterator.hasNext()) { - currentDocIdIndex = Arrays.binarySearch(docIds, currentDocIdIndex, length, _nullValueVectorIterator.next()); - if (currentDocIdIndex >= 0) { - _intValuesSV[currentDocIdIndex] = 1; - currentDocIdIndex++; - } else { - currentDocIdIndex = -currentDocIdIndex - 1; - } - } - } + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + RoaringBitmap bitmap = _transformFunction.getNullBitmap(valueBlock); + int length = valueBlock.getNumDocs(); + initIntValuesSV(length); + Arrays.fill(_intValuesSV, getIsNullValue() ^ 1); + if (bitmap != null) { + bitmap.forEach((IntConsumer) i -> _intValuesSV[i] = getIsNullValue()); } return _intValuesSV; } + + @Override + public RoaringBitmap getNullBitmap(ValueBlock valueBlock) { + return null; + } + + protected int getIsNullValue() { + return 1; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IsTrueTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IsTrueTransformFunction.java new file mode 100644 index 000000000000..68619be91ccc --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/IsTrueTransformFunction.java @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.transform.function; + +import java.util.List; +import java.util.Map; +import org.apache.pinot.common.function.TransformFunctionType; +import org.apache.pinot.core.operator.ColumnContext; + + +public class IsTrueTransformFunction extends BaseBooleanAssertionTransformFunction { + + @Override + public String getName() { + return TransformFunctionType.IS_TRUE.getName(); + } + + @Override + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); + } + + @Override + protected boolean returnsTrueWhenValueIsNull() { + return false; + } + + @Override + protected boolean valueEvaluatesToTrue(int value) { + return value != 0; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/JsonExtractIndexTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/JsonExtractIndexTransformFunction.java new file mode 100644 index 000000000000..c7bff50334e2 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/JsonExtractIndexTransformFunction.java @@ -0,0 +1,424 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.transform.function; + +import com.fasterxml.jackson.databind.JsonNode; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.List; +import java.util.Map; +import org.apache.pinot.common.function.JsonPathCache; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; +import org.apache.pinot.core.operator.transform.TransformResultMetadata; +import org.apache.pinot.segment.spi.index.reader.JsonIndexReader; +import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.utils.JsonUtils; +import org.roaringbitmap.RoaringBitmap; + + +/** + * The JsonExtractIndexTransformFunction provides the same behavior as JsonExtractScalar, with the + * implementation changed to read values from the JSON index. For large JSON blobs this can be faster than parsing + * GBs of JSON at query time. For small JSON blobs/highly filtered input this is generally slower than the *scalar + * implementation. The inflection point is highly dependent on the number of docs remaining post filter. + */ +public class JsonExtractIndexTransformFunction extends BaseTransformFunction { + public static final String FUNCTION_NAME = "jsonExtractIndex"; + + private TransformFunction _jsonFieldTransformFunction; + private String _jsonPathString; + private TransformResultMetadata _resultMetadata; + private JsonIndexReader _jsonIndexReader; + private Object _defaultValue; + private Map _valueToMatchingDocsMap; + private boolean _isSingleValue; + private String _filterJsonPath; + + @Override + public String getName() { + return FUNCTION_NAME; + } + + @Override + public void init(List arguments, Map columnContextMap) { + // Check that there are exactly 3 or 4 or 5 arguments + if (arguments.size() < 3 || arguments.size() > 5) { + throw new IllegalArgumentException( + "Expected 3/4/5 arguments for transform function: jsonExtractIndex(jsonFieldName, 'jsonPath', 'resultsType'," + + " ['defaultValue'], ['jsonFilterExpression'])"); + } + + TransformFunction firstArgument = arguments.get(0); + if (firstArgument instanceof IdentifierTransformFunction) { + String columnName = ((IdentifierTransformFunction) firstArgument).getColumnName(); + _jsonIndexReader = columnContextMap.get(columnName).getDataSource().getJsonIndex(); + if (_jsonIndexReader == null) { + throw new IllegalStateException("jsonExtractIndex can only be applied on a column with JSON index"); + } + } else { + throw new IllegalArgumentException("jsonExtractIndex can only be applied to a raw column"); + } + _jsonFieldTransformFunction = firstArgument; + + TransformFunction secondArgument = arguments.get(1); + if (!(secondArgument instanceof LiteralTransformFunction)) { + throw new IllegalArgumentException("JSON path argument must be a literal"); + } + _jsonPathString = ((LiteralTransformFunction) secondArgument).getStringLiteral(); + try { + JsonPathCache.INSTANCE.getOrCompute(_jsonPathString); + } catch (Exception e) { + throw new IllegalArgumentException("JSON path argument is not a valid JSON path"); + } + + TransformFunction thirdArgument = arguments.get(2); + if (!(thirdArgument instanceof LiteralTransformFunction)) { + throw new IllegalArgumentException("Result type argument must be a literal"); + } + String resultsType = ((LiteralTransformFunction) thirdArgument).getStringLiteral().toUpperCase(); + _isSingleValue = !resultsType.endsWith("_ARRAY"); + if (_isSingleValue && _jsonPathString.contains("[*]")) { + throw new IllegalArgumentException( + "[*] syntax in json path is unsupported for singleValue field json_extract_index"); + } + DataType dataType = _isSingleValue ? DataType.valueOf(resultsType) + : DataType.valueOf(resultsType.substring(0, resultsType.length() - 6)); + + if (arguments.size() >= 4) { + TransformFunction fourthArgument = arguments.get(3); + if (!(fourthArgument instanceof LiteralTransformFunction)) { + throw new IllegalArgumentException("Default value must be a literal"); + } + + if (_isSingleValue) { + _defaultValue = dataType.convert(((LiteralTransformFunction) fourthArgument).getStringLiteral()); + } else { + try { + JsonNode mvArray = JsonUtils.stringToJsonNode(((LiteralTransformFunction) fourthArgument).getStringLiteral()); + if (!mvArray.isArray()) { + throw new IllegalArgumentException("Default value must be a valid JSON array"); + } + Object[] defaultValues = new Object[mvArray.size()]; + for (int i = 0; i < mvArray.size(); i++) { + defaultValues[i] = dataType.convert(mvArray.get(i).asText()); + } + _defaultValue = defaultValues; + } catch (IOException e) { + throw new IllegalArgumentException("Default value must be a valid JSON array"); + } + } + } + + if (arguments.size() == 5) { + TransformFunction fifthArgument = arguments.get(4); + if (!(fifthArgument instanceof LiteralTransformFunction)) { + throw new IllegalArgumentException("JSON path filter argument must be a literal"); + } + _filterJsonPath = ((LiteralTransformFunction) fifthArgument).getStringLiteral(); + } + + _resultMetadata = new TransformResultMetadata(dataType, _isSingleValue, false); + } + + @Override + public TransformResultMetadata getResultMetadata() { + return _resultMetadata; + } + + @Override + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + int[] inputDocIds = valueBlock.getDocIds(); + initIntValuesSV(numDocs); + String[] valuesFromIndex = _jsonIndexReader.getValuesSV(valueBlock.getDocIds(), valueBlock.getNumDocs(), + getValueToMatchingDocsMap(), false); + for (int i = 0; i < numDocs; i++) { + String value = valuesFromIndex[i]; + if (value == null) { + if (_defaultValue != null) { + _intValuesSV[i] = (int) _defaultValue; + continue; + } + throw new RuntimeException( + String.format("Illegal Json Path: [%s], for docId [%s]", _jsonPathString, inputDocIds[i])); + } + _intValuesSV[i] = Integer.parseInt(value); + } + return _intValuesSV; + } + + @Override + public long[] transformToLongValuesSV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + int[] inputDocIds = valueBlock.getDocIds(); + initLongValuesSV(numDocs); + String[] valuesFromIndex = _jsonIndexReader.getValuesSV(valueBlock.getDocIds(), valueBlock.getNumDocs(), + getValueToMatchingDocsMap(), false); + for (int i = 0; i < numDocs; i++) { + String value = valuesFromIndex[i]; + if (value == null) { + if (_defaultValue != null) { + _longValuesSV[i] = (long) _defaultValue; + continue; + } + throw new RuntimeException( + String.format("Illegal Json Path: [%s], for docId [%s]", _jsonPathString, inputDocIds[i])); + } + _longValuesSV[i] = Long.parseLong(value); + } + return _longValuesSV; + } + + @Override + public float[] transformToFloatValuesSV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + int[] inputDocIds = valueBlock.getDocIds(); + initFloatValuesSV(numDocs); + String[] valuesFromIndex = _jsonIndexReader.getValuesSV(valueBlock.getDocIds(), valueBlock.getNumDocs(), + getValueToMatchingDocsMap(), false); + for (int i = 0; i < numDocs; i++) { + String value = valuesFromIndex[i]; + if (value == null) { + if (_defaultValue != null) { + _floatValuesSV[i] = (float) _defaultValue; + continue; + } + throw new RuntimeException( + String.format("Illegal Json Path: [%s], for docId [%s]", _jsonPathString, inputDocIds[i])); + } + _floatValuesSV[i] = Float.parseFloat(value); + } + return _floatValuesSV; + } + + @Override + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + int[] inputDocIds = valueBlock.getDocIds(); + initDoubleValuesSV(numDocs); + String[] valuesFromIndex = _jsonIndexReader.getValuesSV(valueBlock.getDocIds(), valueBlock.getNumDocs(), + getValueToMatchingDocsMap(), false); + for (int i = 0; i < numDocs; i++) { + String value = valuesFromIndex[i]; + if (value == null) { + if (_defaultValue != null) { + _doubleValuesSV[i] = (double) _defaultValue; + continue; + } + throw new RuntimeException( + String.format("Illegal Json Path: [%s], for docId [%s]", _jsonPathString, inputDocIds[i])); + } + _doubleValuesSV[i] = Double.parseDouble(value); + } + return _doubleValuesSV; + } + + @Override + public BigDecimal[] transformToBigDecimalValuesSV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + int[] inputDocIds = valueBlock.getDocIds(); + initBigDecimalValuesSV(numDocs); + String[] valuesFromIndex = _jsonIndexReader.getValuesSV(valueBlock.getDocIds(), valueBlock.getNumDocs(), + getValueToMatchingDocsMap(), false); + for (int i = 0; i < numDocs; i++) { + String value = valuesFromIndex[i]; + if (value == null) { + if (_defaultValue != null) { + _bigDecimalValuesSV[i] = (BigDecimal) _defaultValue; + continue; + } + throw new RuntimeException( + String.format("Illegal Json Path: [%s], for docId [%s]", _jsonPathString, inputDocIds[i])); + } + _bigDecimalValuesSV[i] = new BigDecimal(value); + } + return _bigDecimalValuesSV; + } + + @Override + public String[] transformToStringValuesSV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + int[] inputDocIds = valueBlock.getDocIds(); + initStringValuesSV(numDocs); + String[] valuesFromIndex = _jsonIndexReader.getValuesSV(valueBlock.getDocIds(), valueBlock.getNumDocs(), + getValueToMatchingDocsMap(), false); + for (int i = 0; i < numDocs; i++) { + String value = valuesFromIndex[i]; + if (value == null) { + if (_defaultValue != null) { + _stringValuesSV[i] = (String) _defaultValue; + continue; + } + throw new RuntimeException( + String.format("Illegal Json Path: [%s], for docId [%s]", _jsonPathString, inputDocIds[i])); + } + _stringValuesSV[i] = value; + } + return _stringValuesSV; + } + + @Override + public int[][] transformToIntValuesMV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + initIntValuesMV(numDocs); + String[][] valuesFromIndex = _jsonIndexReader.getValuesMV(valueBlock.getDocIds(), valueBlock.getNumDocs(), + getValueToMatchingDocsMap()); + + for (int i = 0; i < numDocs; i++) { + String[] value = valuesFromIndex[i]; + if (value.length == 0) { + if (_defaultValue != null) { + _intValuesMV[i] = new int[((Object[]) (_defaultValue)).length]; + for (int j = 0; j < _intValuesMV[i].length; j++) { + _intValuesMV[i][j] = (int) ((Object[]) _defaultValue)[j]; + } + continue; + } + throw new RuntimeException( + String.format("Illegal Json Path: [%s], for docId [%s]", _jsonPathString, valueBlock.getDocIds()[i])); + } + _intValuesMV[i] = new int[value.length]; + for (int j = 0; j < value.length; j++) { + _intValuesMV[i][j] = Integer.parseInt(value[j]); + } + } + return _intValuesMV; + } + + @Override + public long[][] transformToLongValuesMV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + initLongValuesMV(numDocs); + String[][] valuesFromIndex = _jsonIndexReader.getValuesMV(valueBlock.getDocIds(), valueBlock.getNumDocs(), + getValueToMatchingDocsMap()); + for (int i = 0; i < numDocs; i++) { + String[] value = valuesFromIndex[i]; + if (value.length == 0) { + if (_defaultValue != null) { + _longValuesMV[i] = new long[((Object[]) (_defaultValue)).length]; + for (int j = 0; j < _longValuesMV[i].length; j++) { + _longValuesMV[i][j] = (long) ((Object[]) _defaultValue)[j]; + } + continue; + } + throw new RuntimeException( + String.format("Illegal Json Path: [%s], for docId [%s]", _jsonPathString, valueBlock.getDocIds()[i])); + } + _longValuesMV[i] = new long[value.length]; + for (int j = 0; j < value.length; j++) { + _longValuesMV[i][j] = Long.parseLong(value[j]); + } + } + return _longValuesMV; + } + + @Override + public float[][] transformToFloatValuesMV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + initFloatValuesMV(numDocs); + String[][] valuesFromIndex = _jsonIndexReader.getValuesMV(valueBlock.getDocIds(), valueBlock.getNumDocs(), + getValueToMatchingDocsMap()); + for (int i = 0; i < numDocs; i++) { + String[] value = valuesFromIndex[i]; + if (value.length == 0) { + if (_defaultValue != null) { + _floatValuesMV[i] = new float[((Object[]) (_defaultValue)).length]; + for (int j = 0; j < _floatValuesMV[i].length; j++) { + _floatValuesMV[i][j] = (float) ((Object[]) _defaultValue)[j]; + } + continue; + } + throw new RuntimeException( + String.format("Illegal Json Path: [%s], for docId [%s]", _jsonPathString, valueBlock.getDocIds()[i])); + } + _floatValuesMV[i] = new float[value.length]; + for (int j = 0; j < value.length; j++) { + _floatValuesMV[i][j] = Float.parseFloat(value[j]); + } + } + return _floatValuesMV; + } + + @Override + public double[][] transformToDoubleValuesMV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + initDoubleValuesMV(numDocs); + String[][] valuesFromIndex = _jsonIndexReader.getValuesMV(valueBlock.getDocIds(), valueBlock.getNumDocs(), + getValueToMatchingDocsMap()); + for (int i = 0; i < numDocs; i++) { + String[] value = valuesFromIndex[i]; + if (value.length == 0) { + if (_defaultValue != null) { + _doubleValuesMV[i] = new double[((Object[]) (_defaultValue)).length]; + for (int j = 0; j < _doubleValuesMV[i].length; j++) { + _doubleValuesMV[i][j] = (double) ((Object[]) _defaultValue)[j]; + } + continue; + } + throw new RuntimeException( + String.format("Illegal Json Path: [%s], for docId [%s]", _jsonPathString, valueBlock.getDocIds()[i])); + } + _doubleValuesMV[i] = new double[value.length]; + for (int j = 0; j < value.length; j++) { + _doubleValuesMV[i][j] = Double.parseDouble(value[j]); + } + } + return _doubleValuesMV; + } + + @Override + public String[][] transformToStringValuesMV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + initStringValuesMV(numDocs); + String[][] valuesFromIndex = _jsonIndexReader.getValuesMV(valueBlock.getDocIds(), valueBlock.getNumDocs(), + getValueToMatchingDocsMap()); + for (int i = 0; i < numDocs; i++) { + String[] value = valuesFromIndex[i]; + if (value.length == 0) { + if (_defaultValue != null) { + _stringValuesMV[i] = new String[((Object[]) (_defaultValue)).length]; + for (int j = 0; j < _stringValuesMV[i].length; j++) { + _stringValuesMV[i][j] = (String) ((Object[]) _defaultValue)[j]; + } + continue; + } + throw new RuntimeException( + String.format("Illegal Json Path: [%s], for docId [%s]", _jsonPathString, valueBlock.getDocIds()[i])); + } + _stringValuesMV[i] = new String[value.length]; + System.arraycopy(value, 0, _stringValuesMV[i], 0, value.length); + } + return _stringValuesMV; + } + + /** + * Lazily initialize _valueToMatchingDocsMap, so that map generation is skipped when filtering excludes all values + */ + private Map getValueToMatchingDocsMap() { + if (_valueToMatchingDocsMap == null) { + _valueToMatchingDocsMap = _jsonIndexReader.getMatchingFlattenedDocsMap(_jsonPathString, _filterJsonPath); + if (_isSingleValue) { + // For single value result type, it's more efficient to use original docIDs map + _jsonIndexReader.convertFlattenedDocIdsToDocIds(_valueToMatchingDocsMap); + } + } + return _valueToMatchingDocsMap; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/JsonExtractKeyTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/JsonExtractKeyTransformFunction.java index 60b5b659ab69..7ce7c29ae921 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/JsonExtractKeyTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/JsonExtractKeyTransformFunction.java @@ -27,9 +27,9 @@ import java.util.List; import java.util.Map; import org.apache.pinot.common.function.JsonPathCache; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; /** @@ -61,7 +61,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); // Check that there are exactly 2 arguments if (arguments.size() != 2) { throw new IllegalArgumentException( @@ -75,7 +76,7 @@ public void init(List arguments, Map data + "function"); } _jsonFieldTransformFunction = firstArgument; - _jsonPath = JsonPathCache.INSTANCE.getOrCompute(((LiteralTransformFunction) arguments.get(1)).getLiteral()); + _jsonPath = JsonPathCache.INSTANCE.getOrCompute(((LiteralTransformFunction) arguments.get(1)).getStringLiteral()); } @Override @@ -84,12 +85,10 @@ public TransformResultMetadata getResultMetadata() { } @Override - public String[][] transformToStringValuesMV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_stringValuesMV == null) { - _stringValuesMV = new String[length][]; - } - String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(projectionBlock); + public String[][] transformToStringValuesMV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initStringValuesMV(length); + String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(valueBlock); for (int i = 0; i < length; i++) { List values = JSON_PARSER_CONTEXT.parse(jsonStrings[i]).read(_jsonPath); _stringValuesMV[i] = values.toArray(new String[0]); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/JsonExtractScalarTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/JsonExtractScalarTransformFunction.java index 1a534a1856e1..4970f3738fd4 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/JsonExtractScalarTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/JsonExtractScalarTransformFunction.java @@ -30,9 +30,10 @@ import java.util.List; import java.util.Map; import org.apache.pinot.common.function.JsonPathCache; +import org.apache.pinot.core.operator.ColumnContext; import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.segment.spi.evaluator.json.JsonPathEvaluator; import org.apache.pinot.segment.spi.evaluator.json.JsonPathEvaluators; import org.apache.pinot.spi.data.FieldSpec.DataType; @@ -81,7 +82,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); // Check that there are exactly 3 or 4 arguments if (arguments.size() < 3 || arguments.size() > 4) { throw new IllegalArgumentException( @@ -96,23 +98,27 @@ public void init(List arguments, Map data + "function"); } _jsonFieldTransformFunction = firstArgument; - _jsonPathString = ((LiteralTransformFunction) arguments.get(1)).getLiteral(); - String resultsType = ((LiteralTransformFunction) arguments.get(2)).getLiteral().toUpperCase(); + _jsonPathString = ((LiteralTransformFunction) arguments.get(1)).getStringLiteral(); + String resultsType = ((LiteralTransformFunction) arguments.get(2)).getStringLiteral().toUpperCase(); boolean isSingleValue = !resultsType.endsWith("_ARRAY"); + DataType dataType; try { - DataType dataType = - DataType.valueOf(isSingleValue ? resultsType : resultsType.substring(0, resultsType.length() - 6)); - if (arguments.size() == 4) { - _defaultValue = dataType.convert(((LiteralTransformFunction) arguments.get(3)).getLiteral()); - } - _resultMetadata = new TransformResultMetadata(dataType, isSingleValue, false); - _jsonPathEvaluator = JsonPathEvaluators.create(_jsonPathString, _defaultValue); + dataType = DataType.valueOf(isSingleValue ? resultsType : resultsType.substring(0, resultsType.length() - 6)); } catch (Exception e) { - throw new IllegalStateException(String.format( + throw new IllegalArgumentException(String.format( "Unsupported results type: %s for jsonExtractScalar function. Supported types are: " + "INT/LONG/FLOAT/DOUBLE/BOOLEAN/BIG_DECIMAL/TIMESTAMP/STRING/INT_ARRAY/LONG_ARRAY/FLOAT_ARRAY" + "/DOUBLE_ARRAY/STRING_ARRAY", resultsType)); } + if (arguments.size() == 4) { + _defaultValue = dataType.convert(((LiteralTransformFunction) arguments.get(3)).getStringLiteral()); + } + _resultMetadata = new TransformResultMetadata(dataType, isSingleValue, false); + try { + _jsonPathEvaluator = JsonPathEvaluators.create(_jsonPathString, _defaultValue); + } catch (Exception e) { + throw new IllegalArgumentException("Invalid json path: " + _jsonPathString, e); + } } @Override @@ -121,25 +127,22 @@ public TransformResultMetadata getResultMetadata() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_intValuesSV == null) { - _intValuesSV = new int[numDocs]; - } - if (_jsonFieldTransformFunction instanceof PushDownTransformFunction) { - ((PushDownTransformFunction) _jsonFieldTransformFunction).transformToIntValuesSV(projectionBlock, + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + initIntValuesSV(valueBlock.getNumDocs()); + if (_jsonFieldTransformFunction instanceof PushDownTransformFunction && valueBlock instanceof ProjectionBlock) { + ((PushDownTransformFunction) _jsonFieldTransformFunction).transformToIntValuesSV((ProjectionBlock) valueBlock, _jsonPathEvaluator, _intValuesSV); return _intValuesSV; } // operating on the output of another transform so can't pass the evaluation down to the storage - return transformTransformedValuesToIntValuesSV(projectionBlock); + return transformTransformedValuesToIntValuesSV(valueBlock); } - private int[] transformTransformedValuesToIntValuesSV(ProjectionBlock projectionBlock) { + private int[] transformTransformedValuesToIntValuesSV(ValueBlock valueBlock) { // operating on the output of another transform so can't pass the evaluation down to the storage ensureJsonPathCompiled(); - String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(projectionBlock); - int numDocs = projectionBlock.getNumDocs(); + String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(valueBlock); + int numDocs = valueBlock.getNumDocs(); for (int i = 0; i < numDocs; i++) { Object result = null; try { @@ -164,24 +167,21 @@ private int[] transformTransformedValuesToIntValuesSV(ProjectionBlock projection } @Override - public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_longValuesSV == null) { - _longValuesSV = new long[numDocs]; - } - if (_jsonFieldTransformFunction instanceof PushDownTransformFunction) { - ((PushDownTransformFunction) _jsonFieldTransformFunction).transformToLongValuesSV(projectionBlock, + public long[] transformToLongValuesSV(ValueBlock valueBlock) { + initLongValuesSV(valueBlock.getNumDocs()); + if (_jsonFieldTransformFunction instanceof PushDownTransformFunction && valueBlock instanceof ProjectionBlock) { + ((PushDownTransformFunction) _jsonFieldTransformFunction).transformToLongValuesSV((ProjectionBlock) valueBlock, _jsonPathEvaluator, _longValuesSV); return _longValuesSV; } - return transformTransformedValuesToLongValuesSV(projectionBlock); + return transformTransformedValuesToLongValuesSV(valueBlock); } - private long[] transformTransformedValuesToLongValuesSV(ProjectionBlock projectionBlock) { + private long[] transformTransformedValuesToLongValuesSV(ValueBlock valueBlock) { // operating on the output of another transform so can't pass the evaluation down to the storage ensureJsonPathCompiled(); - String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(projectionBlock); - int numDocs = projectionBlock.getNumDocs(); + String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(valueBlock); + int numDocs = valueBlock.getNumDocs(); for (int i = 0; i < numDocs; i++) { Object result = null; try { @@ -207,24 +207,21 @@ private long[] transformTransformedValuesToLongValuesSV(ProjectionBlock projecti } @Override - public float[] transformToFloatValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_floatValuesSV == null) { - _floatValuesSV = new float[numDocs]; - } - if (_jsonFieldTransformFunction instanceof PushDownTransformFunction) { - ((PushDownTransformFunction) _jsonFieldTransformFunction).transformToFloatValuesSV(projectionBlock, + public float[] transformToFloatValuesSV(ValueBlock valueBlock) { + initFloatValuesSV(valueBlock.getNumDocs()); + if (_jsonFieldTransformFunction instanceof PushDownTransformFunction && valueBlock instanceof ProjectionBlock) { + ((PushDownTransformFunction) _jsonFieldTransformFunction).transformToFloatValuesSV((ProjectionBlock) valueBlock, _jsonPathEvaluator, _floatValuesSV); return _floatValuesSV; } - return transformTransformedValuesToFloatValuesSV(projectionBlock); + return transformTransformedValuesToFloatValuesSV(valueBlock); } - private float[] transformTransformedValuesToFloatValuesSV(ProjectionBlock projectionBlock) { + private float[] transformTransformedValuesToFloatValuesSV(ValueBlock valueBlock) { // operating on the output of another transform so can't pass the evaluation down to the storage ensureJsonPathCompiled(); - String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(projectionBlock); - int numDocs = projectionBlock.getNumDocs(); + String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(valueBlock); + int numDocs = valueBlock.getNumDocs(); for (int i = 0; i < numDocs; i++) { Object result = null; try { @@ -249,24 +246,21 @@ private float[] transformTransformedValuesToFloatValuesSV(ProjectionBlock projec } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_doubleValuesSV == null) { - _doubleValuesSV = new double[numDocs]; - } - if (_jsonFieldTransformFunction instanceof PushDownTransformFunction) { - ((PushDownTransformFunction) _jsonFieldTransformFunction).transformToDoubleValuesSV(projectionBlock, + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + initDoubleValuesSV(valueBlock.getNumDocs()); + if (_jsonFieldTransformFunction instanceof PushDownTransformFunction && valueBlock instanceof ProjectionBlock) { + ((PushDownTransformFunction) _jsonFieldTransformFunction).transformToDoubleValuesSV((ProjectionBlock) valueBlock, _jsonPathEvaluator, _doubleValuesSV); return _doubleValuesSV; } - return transformTransformedValuesToDoubleValuesSV(projectionBlock); + return transformTransformedValuesToDoubleValuesSV(valueBlock); } - private double[] transformTransformedValuesToDoubleValuesSV(ProjectionBlock projectionBlock) { + private double[] transformTransformedValuesToDoubleValuesSV(ValueBlock valueBlock) { // operating on the output of another transform so can't pass the evaluation down to the storage ensureJsonPathCompiled(); - String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(projectionBlock); - int numDocs = projectionBlock.getNumDocs(); + String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(valueBlock); + int numDocs = valueBlock.getNumDocs(); for (int i = 0; i < numDocs; i++) { Object result = null; try { @@ -291,24 +285,21 @@ private double[] transformTransformedValuesToDoubleValuesSV(ProjectionBlock proj } @Override - public BigDecimal[] transformToBigDecimalValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_bigDecimalValuesSV == null) { - _bigDecimalValuesSV = new BigDecimal[numDocs]; - } - if (_jsonFieldTransformFunction instanceof PushDownTransformFunction) { - ((PushDownTransformFunction) _jsonFieldTransformFunction).transformToBigDecimalValuesSV(projectionBlock, - _jsonPathEvaluator, _bigDecimalValuesSV); + public BigDecimal[] transformToBigDecimalValuesSV(ValueBlock valueBlock) { + initBigDecimalValuesSV(valueBlock.getNumDocs()); + if (_jsonFieldTransformFunction instanceof PushDownTransformFunction && valueBlock instanceof ProjectionBlock) { + ((PushDownTransformFunction) _jsonFieldTransformFunction).transformToBigDecimalValuesSV( + (ProjectionBlock) valueBlock, _jsonPathEvaluator, _bigDecimalValuesSV); return _bigDecimalValuesSV; } - return transformTransformedValuesToBigDecimalValuesSV(projectionBlock); + return transformTransformedValuesToBigDecimalValuesSV(valueBlock); } - private BigDecimal[] transformTransformedValuesToBigDecimalValuesSV(ProjectionBlock projectionBlock) { + private BigDecimal[] transformTransformedValuesToBigDecimalValuesSV(ValueBlock valueBlock) { // operating on the output of another transform so can't pass the evaluation down to the storage ensureJsonPathCompiled(); - String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(projectionBlock); - int numDocs = projectionBlock.getNumDocs(); + String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(valueBlock); + int numDocs = valueBlock.getNumDocs(); for (int i = 0; i < numDocs; i++) { Object result = null; try { @@ -333,24 +324,21 @@ private BigDecimal[] transformTransformedValuesToBigDecimalValuesSV(ProjectionBl } @Override - public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_stringValuesSV == null) { - _stringValuesSV = new String[numDocs]; - } - if (_jsonFieldTransformFunction instanceof PushDownTransformFunction) { - ((PushDownTransformFunction) _jsonFieldTransformFunction).transformToStringValuesSV(projectionBlock, + public String[] transformToStringValuesSV(ValueBlock valueBlock) { + initStringValuesSV(valueBlock.getNumDocs()); + if (_jsonFieldTransformFunction instanceof PushDownTransformFunction && valueBlock instanceof ProjectionBlock) { + ((PushDownTransformFunction) _jsonFieldTransformFunction).transformToStringValuesSV((ProjectionBlock) valueBlock, _jsonPathEvaluator, _stringValuesSV); return _stringValuesSV; } - return transformTransformedValuesToStringValuesSV(projectionBlock); + return transformTransformedValuesToStringValuesSV(valueBlock); } - private String[] transformTransformedValuesToStringValuesSV(ProjectionBlock projectionBlock) { + private String[] transformTransformedValuesToStringValuesSV(ValueBlock valueBlock) { // operating on the output of another transform so can't pass the evaluation down to the storage ensureJsonPathCompiled(); - String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(projectionBlock); - int numDocs = projectionBlock.getNumDocs(); + String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(valueBlock); + int numDocs = valueBlock.getNumDocs(); for (int i = 0; i < numDocs; i++) { Object result = null; try { @@ -375,24 +363,21 @@ private String[] transformTransformedValuesToStringValuesSV(ProjectionBlock proj } @Override - public int[][] transformToIntValuesMV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_intValuesMV == null) { - _intValuesMV = new int[numDocs][]; - } - if (_jsonFieldTransformFunction instanceof PushDownTransformFunction) { - ((PushDownTransformFunction) _jsonFieldTransformFunction).transformToIntValuesMV(projectionBlock, + public int[][] transformToIntValuesMV(ValueBlock valueBlock) { + initIntValuesMV(valueBlock.getNumDocs()); + if (_jsonFieldTransformFunction instanceof PushDownTransformFunction && valueBlock instanceof ProjectionBlock) { + ((PushDownTransformFunction) _jsonFieldTransformFunction).transformToIntValuesMV((ProjectionBlock) valueBlock, _jsonPathEvaluator, _intValuesMV); return _intValuesMV; } - return transformTransformedValuesToIntValuesMV(projectionBlock); + return transformTransformedValuesToIntValuesMV(valueBlock); } - private int[][] transformTransformedValuesToIntValuesMV(ProjectionBlock projectionBlock) { + private int[][] transformTransformedValuesToIntValuesMV(ValueBlock valueBlock) { // operating on the output of another transform so can't pass the evaluation down to the storage ensureJsonPathCompiled(); - String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(projectionBlock); - int numDocs = projectionBlock.getNumDocs(); + String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(valueBlock); + int numDocs = valueBlock.getNumDocs(); for (int i = 0; i < numDocs; i++) { List result = null; try { @@ -414,24 +399,21 @@ private int[][] transformTransformedValuesToIntValuesMV(ProjectionBlock projecti } @Override - public long[][] transformToLongValuesMV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_longValuesMV == null) { - _longValuesMV = new long[numDocs][]; - } - if (_jsonFieldTransformFunction instanceof PushDownTransformFunction) { - ((PushDownTransformFunction) _jsonFieldTransformFunction).transformToLongValuesMV(projectionBlock, + public long[][] transformToLongValuesMV(ValueBlock valueBlock) { + initLongValuesMV(valueBlock.getNumDocs()); + if (_jsonFieldTransformFunction instanceof PushDownTransformFunction && valueBlock instanceof ProjectionBlock) { + ((PushDownTransformFunction) _jsonFieldTransformFunction).transformToLongValuesMV((ProjectionBlock) valueBlock, _jsonPathEvaluator, _longValuesMV); return _longValuesMV; } - return transformTransformedValuesToLongValuesMV(projectionBlock); + return transformTransformedValuesToLongValuesMV(valueBlock); } - private long[][] transformTransformedValuesToLongValuesMV(ProjectionBlock projectionBlock) { + private long[][] transformTransformedValuesToLongValuesMV(ValueBlock valueBlock) { // operating on the output of another transform so can't pass the evaluation down to the storage ensureJsonPathCompiled(); - String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(projectionBlock); - int length = projectionBlock.getNumDocs(); + String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(valueBlock); + int length = valueBlock.getNumDocs(); for (int i = 0; i < length; i++) { List result = null; try { @@ -453,24 +435,21 @@ private long[][] transformTransformedValuesToLongValuesMV(ProjectionBlock projec } @Override - public float[][] transformToFloatValuesMV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_floatValuesMV == null) { - _floatValuesMV = new float[numDocs][]; - } - if (_jsonFieldTransformFunction instanceof PushDownTransformFunction) { - ((PushDownTransformFunction) _jsonFieldTransformFunction).transformToFloatValuesMV(projectionBlock, + public float[][] transformToFloatValuesMV(ValueBlock valueBlock) { + initFloatValuesMV(valueBlock.getNumDocs()); + if (_jsonFieldTransformFunction instanceof PushDownTransformFunction && valueBlock instanceof ProjectionBlock) { + ((PushDownTransformFunction) _jsonFieldTransformFunction).transformToFloatValuesMV((ProjectionBlock) valueBlock, _jsonPathEvaluator, _floatValuesMV); return _floatValuesMV; } - return transformTransformedValuesToFloatValuesMV(projectionBlock); + return transformTransformedValuesToFloatValuesMV(valueBlock); } - private float[][] transformTransformedValuesToFloatValuesMV(ProjectionBlock projectionBlock) { + private float[][] transformTransformedValuesToFloatValuesMV(ValueBlock valueBlock) { // operating on the output of another transform so can't pass the evaluation down to the storage ensureJsonPathCompiled(); - String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(projectionBlock); - int length = projectionBlock.getNumDocs(); + String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(valueBlock); + int length = valueBlock.getNumDocs(); for (int i = 0; i < length; i++) { List result = null; try { @@ -492,24 +471,21 @@ private float[][] transformTransformedValuesToFloatValuesMV(ProjectionBlock proj } @Override - public double[][] transformToDoubleValuesMV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_doubleValuesMV == null) { - _doubleValuesMV = new double[numDocs][]; - } - if (_jsonFieldTransformFunction instanceof PushDownTransformFunction) { - ((PushDownTransformFunction) _jsonFieldTransformFunction).transformToDoubleValuesMV(projectionBlock, + public double[][] transformToDoubleValuesMV(ValueBlock valueBlock) { + initDoubleValuesMV(valueBlock.getNumDocs()); + if (_jsonFieldTransformFunction instanceof PushDownTransformFunction && valueBlock instanceof ProjectionBlock) { + ((PushDownTransformFunction) _jsonFieldTransformFunction).transformToDoubleValuesMV((ProjectionBlock) valueBlock, _jsonPathEvaluator, _doubleValuesMV); return _doubleValuesMV; } - return transformTransformedToDoubleValuesMV(projectionBlock); + return transformTransformedToDoubleValuesMV(valueBlock); } - private double[][] transformTransformedToDoubleValuesMV(ProjectionBlock projectionBlock) { + private double[][] transformTransformedToDoubleValuesMV(ValueBlock valueBlock) { // operating on the output of another transform so can't pass the evaluation down to the storage ensureJsonPathCompiled(); - String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(projectionBlock); - int length = projectionBlock.getNumDocs(); + String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(valueBlock); + int length = valueBlock.getNumDocs(); for (int i = 0; i < length; i++) { List result = null; try { @@ -531,24 +507,21 @@ private double[][] transformTransformedToDoubleValuesMV(ProjectionBlock projecti } @Override - public String[][] transformToStringValuesMV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_stringValuesMV == null || _stringValuesMV.length < numDocs) { - _stringValuesMV = new String[numDocs][]; - } - if (_jsonFieldTransformFunction instanceof PushDownTransformFunction) { - ((PushDownTransformFunction) _jsonFieldTransformFunction).transformToStringValuesMV(projectionBlock, + public String[][] transformToStringValuesMV(ValueBlock valueBlock) { + initStringValuesMV(valueBlock.getNumDocs()); + if (_jsonFieldTransformFunction instanceof PushDownTransformFunction && valueBlock instanceof ProjectionBlock) { + ((PushDownTransformFunction) _jsonFieldTransformFunction).transformToStringValuesMV((ProjectionBlock) valueBlock, _jsonPathEvaluator, _stringValuesMV); return _stringValuesMV; } - return transformTransformedValuesToStringValuesMV(projectionBlock); + return transformTransformedValuesToStringValuesMV(valueBlock); } - private String[][] transformTransformedValuesToStringValuesMV(ProjectionBlock projectionBlock) { + private String[][] transformTransformedValuesToStringValuesMV(ValueBlock valueBlock) { // operating on the output of another transform so can't pass the evaluation down to the storage ensureJsonPathCompiled(); - String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(projectionBlock); - int length = projectionBlock.getNumDocs(); + String[] jsonStrings = _jsonFieldTransformFunction.transformToStringValuesSV(valueBlock); + int length = valueBlock.getNumDocs(); for (int i = 0; i < length; i++) { List result = null; try { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/LeastTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/LeastTransformFunction.java index e621abd688f6..0420f2fd1d4d 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/LeastTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/LeastTransformFunction.java @@ -20,9 +20,17 @@ import java.math.BigDecimal; import org.apache.pinot.common.function.TransformFunctionType; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; - +/** + * The LeastTransformFunction implements the Least operator. + * + * Return the smallest results for the arguments + * + * Expected result: + * Least(columnA, columnB, columnC): smallest among columnA, columnB, columnC + * + * Note that null values will be ignored for evaluation. If all values are null, we return null. + */ public class LeastTransformFunction extends SelectTupleElementTransformFunction { public LeastTransformFunction() { @@ -30,106 +38,36 @@ public LeastTransformFunction() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_intValuesSV == null) { - _intValuesSV = new int[numDocs]; - } - int[] values = _arguments.get(0).transformToIntValuesSV(projectionBlock); - System.arraycopy(values, 0, _intValuesSV, 0, numDocs); - for (int i = 1; i < _arguments.size(); i++) { - values = _arguments.get(i).transformToIntValuesSV(projectionBlock); - for (int j = 0; j < numDocs & j < values.length; j++) { - _intValuesSV[j] = Math.min(_intValuesSV[j], values[j]); - } - } - return _intValuesSV; + protected int binaryFunction(int a, int b) { + return Math.min(a, b); } @Override - public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_longValuesSV == null) { - _longValuesSV = new long[numDocs]; - } - long[] values = _arguments.get(0).transformToLongValuesSV(projectionBlock); - System.arraycopy(values, 0, _longValuesSV, 0, numDocs); - for (int i = 1; i < _arguments.size(); i++) { - values = _arguments.get(i).transformToLongValuesSV(projectionBlock); - for (int j = 0; j < numDocs & j < values.length; j++) { - _longValuesSV[j] = Math.min(_longValuesSV[j], values[j]); - } - } - return _longValuesSV; + protected long binaryFunction(long a, long b) { + return Math.min(a, b); } @Override - public float[] transformToFloatValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_floatValuesSV == null) { - _floatValuesSV = new float[numDocs]; - } - float[] values = _arguments.get(0).transformToFloatValuesSV(projectionBlock); - System.arraycopy(values, 0, _floatValuesSV, 0, numDocs); - for (int i = 1; i < _arguments.size(); i++) { - values = _arguments.get(i).transformToFloatValuesSV(projectionBlock); - for (int j = 0; j < numDocs & j < values.length; j++) { - _floatValuesSV[j] = Math.min(_floatValuesSV[j], values[j]); - } - } - return _floatValuesSV; + protected float binaryFunction(float a, float b) { + return Math.min(a, b); } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_doubleValuesSV == null) { - _doubleValuesSV = new double[numDocs]; - } - double[] values = _arguments.get(0).transformToDoubleValuesSV(projectionBlock); - System.arraycopy(values, 0, _doubleValuesSV, 0, numDocs); - for (int i = 1; i < _arguments.size(); i++) { - values = _arguments.get(i).transformToDoubleValuesSV(projectionBlock); - for (int j = 0; j < numDocs & j < values.length; j++) { - _doubleValuesSV[j] = Math.min(_doubleValuesSV[j], values[j]); - } - } - return _doubleValuesSV; + protected double binaryFunction(double a, double b) { + return Math.min(a, b); } @Override - public BigDecimal[] transformToBigDecimalValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_bigDecimalValuesSV == null) { - _bigDecimalValuesSV = new BigDecimal[numDocs]; - } - BigDecimal[] values = _arguments.get(0).transformToBigDecimalValuesSV(projectionBlock); - System.arraycopy(values, 0, _bigDecimalValuesSV, 0, numDocs); - for (int i = 1; i < _arguments.size(); i++) { - values = _arguments.get(i).transformToBigDecimalValuesSV(projectionBlock); - for (int j = 0; j < numDocs & j < values.length; j++) { - _bigDecimalValuesSV[j] = _bigDecimalValuesSV[j].min(values[j]); - } - } - return _bigDecimalValuesSV; + protected BigDecimal binaryFunction(BigDecimal a, BigDecimal b) { + return a.min(b); } @Override - public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_stringValuesSV == null) { - _stringValuesSV = new String[numDocs]; - } - String[] values = _arguments.get(0).transformToStringValuesSV(projectionBlock); - System.arraycopy(values, 0, _stringValuesSV, 0, numDocs); - for (int i = 1; i < _arguments.size(); i++) { - values = _arguments.get(i).transformToStringValuesSV(projectionBlock); - for (int j = 0; j < numDocs & j < values.length; j++) { - if (_stringValuesSV[j].compareTo(values[j]) > 0) { - _stringValuesSV[j] = values[j]; - } - } + protected String binaryFunction(String a, String b) { + if (a.compareTo(b) < 0) { + return a; + } else { + return b; } - return _stringValuesSV; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/LiteralTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/LiteralTransformFunction.java index dc63437f36b2..80db464546b4 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/LiteralTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/LiteralTransformFunction.java @@ -18,39 +18,27 @@ */ package org.apache.pinot.core.operator.transform.function; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Preconditions; import java.math.BigDecimal; -import java.math.BigInteger; -import java.sql.Timestamp; import java.util.Arrays; import java.util.List; import java.util.Map; -import org.apache.commons.lang3.math.NumberUtils; +import javax.annotation.Nullable; import org.apache.pinot.common.request.context.LiteralContext; -import org.apache.pinot.common.utils.PinotDataType; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.segment.spi.index.reader.Dictionary; -import org.apache.pinot.spi.data.FieldSpec.DataType; -import org.apache.pinot.spi.utils.BytesUtils; +import org.roaringbitmap.RoaringBitmap; /** * The LiteralTransformFunction class is a special transform function which is a wrapper on top of a * LITERAL. The data type is inferred from the literal string. - * TODO: Preserve the type of the literal instead of inferring the type from the string */ public class LiteralTransformFunction implements TransformFunction { - // TODO: Deprecate the string representation of literal. - private final String _literal; - private final DataType _dataType; - private final int _intLiteral; - private final long _longLiteral; - private final float _floatLiteral; - private final double _doubleLiteral; - private final BigDecimal _bigDecimalLiteral; + public static final String FUNCTION_NAME = "literal"; + + private final LiteralContext _literalContext; // literals may be shared but values are intentionally not volatile as assignment races are benign private int[] _intResult; @@ -62,108 +50,83 @@ public class LiteralTransformFunction implements TransformFunction { private byte[][] _bytesResult; public LiteralTransformFunction(LiteralContext literalContext) { - Preconditions.checkNotNull(literalContext); - _literal = literalContext.getValue() == null ? "" : literalContext.getValue().toString(); - if (literalContext.getType() == DataType.BOOLEAN) { - _bigDecimalLiteral = PinotDataType.BOOLEAN.toBigDecimal(literalContext.getValue()); - _dataType = DataType.BOOLEAN; - } else { - _dataType = inferLiteralDataType(_literal); - if (_dataType.isNumeric()) { - _bigDecimalLiteral = new BigDecimal(_literal); - } else if (_dataType == DataType.TIMESTAMP) { - // inferLiteralDataType successfully interpreted the literal as TIMESTAMP. _bigDecimalLiteral is populated and - // assigned to _longLiteral. - _bigDecimalLiteral = PinotDataType.TIMESTAMP.toBigDecimal(Timestamp.valueOf(_literal)); - } else { - _bigDecimalLiteral = BigDecimal.ZERO; - } - } - _intLiteral = _bigDecimalLiteral.intValue(); - _longLiteral = _bigDecimalLiteral.longValue(); - _floatLiteral = _bigDecimalLiteral.floatValue(); - _doubleLiteral = _bigDecimalLiteral.doubleValue(); + _literalContext = literalContext; } - // TODO: Deprecate the usage case for this function. - @VisibleForTesting - static DataType inferLiteralDataType(String literal) { - // Try to interpret the literal as number - try { - Number number = NumberUtils.createNumber(literal); - if (number instanceof Integer) { - return DataType.INT; - } else if (number instanceof Long) { - return DataType.LONG; - } else if (number instanceof Float) { - return DataType.FLOAT; - } else if (number instanceof Double) { - return DataType.DOUBLE; - } else if (number instanceof BigDecimal | number instanceof BigInteger) { - return DataType.BIG_DECIMAL; - } else { - return DataType.STRING; - } - } catch (Exception e) { - // Ignored - } + public boolean getBooleanLiteral() { + return _literalContext.getBooleanValue(); + } - // Try to interpret the literal as TIMESTAMP - try { - Timestamp.valueOf(literal); - return DataType.TIMESTAMP; - } catch (Exception e) { - // Ignored - } + public int getIntLiteral() { + return _literalContext.getIntValue(); + } + + public long getLongLiteral() { + return _literalContext.getLongValue(); + } + + public float getFloatLiteral() { + return _literalContext.getFloatValue(); + } - return DataType.STRING; + public double getDoubleLiteral() { + return _literalContext.getDoubleValue(); } - public String getLiteral() { - return _literal; + public BigDecimal getBigDecimalLiteral() { + return _literalContext.getBigDecimalValue(); + } + + public String getStringLiteral() { + return _literalContext.getStringValue(); + } + + public byte[] getBytesLiteral() { + return _literalContext.getBytesValue(); + } + + public boolean isNull() { + return _literalContext.isNull(); } @Override public String getName() { - throw new UnsupportedOperationException(); + return FUNCTION_NAME; } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { } @Override public TransformResultMetadata getResultMetadata() { - return new TransformResultMetadata(_dataType, true, false); + return new TransformResultMetadata(_literalContext.getType(), true, false); } @Override public Dictionary getDictionary() { - throw new UnsupportedOperationException(); + return null; } @Override - public int[] transformToDictIdsSV(ProjectionBlock projectionBlock) { + public int[] transformToDictIdsSV(ValueBlock valueBlock) { throw new UnsupportedOperationException(); } @Override - public int[][] transformToDictIdsMV(ProjectionBlock projectionBlock) { + public int[][] transformToDictIdsMV(ValueBlock valueBlock) { throw new UnsupportedOperationException(); } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); int[] intResult = _intResult; if (intResult == null || intResult.length < numDocs) { + int intValue = getIntLiteral(); intResult = new int[numDocs]; - if (_dataType != DataType.BOOLEAN) { - if (_intLiteral != 0) { - Arrays.fill(intResult, _intLiteral); - } - } else { - Arrays.fill(intResult, _intLiteral); + if (intValue != 0) { + Arrays.fill(intResult, intValue); } _intResult = intResult; } @@ -171,17 +134,14 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { } @Override - public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); + public long[] transformToLongValuesSV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); long[] longResult = _longResult; if (longResult == null || longResult.length < numDocs) { + long longValue = getLongLiteral(); longResult = new long[numDocs]; - if (_dataType != DataType.TIMESTAMP) { - if (_longLiteral != 0) { - Arrays.fill(longResult, _longLiteral); - } - } else { - Arrays.fill(longResult, Timestamp.valueOf(_literal).getTime()); + if (longValue != 0) { + Arrays.fill(longResult, longValue); } _longResult = longResult; } @@ -189,13 +149,14 @@ public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { } @Override - public float[] transformToFloatValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); + public float[] transformToFloatValuesSV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); float[] floatResult = _floatResult; if (floatResult == null || floatResult.length < numDocs) { + float floatValue = getFloatLiteral(); floatResult = new float[numDocs]; - if (_floatLiteral != 0F) { - Arrays.fill(floatResult, _floatLiteral); + if (floatValue != 0) { + Arrays.fill(floatResult, floatValue); } _floatResult = floatResult; } @@ -203,13 +164,14 @@ public float[] transformToFloatValuesSV(ProjectionBlock projectionBlock) { } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); double[] doubleResult = _doubleResult; if (doubleResult == null || doubleResult.length < numDocs) { + double doubleValue = getDoubleLiteral(); doubleResult = new double[numDocs]; - if (_doubleLiteral != 0) { - Arrays.fill(doubleResult, _doubleLiteral); + if (doubleValue != 0) { + Arrays.fill(doubleResult, doubleValue); } _doubleResult = doubleResult; } @@ -217,68 +179,80 @@ public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { } @Override - public BigDecimal[] transformToBigDecimalValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); + public BigDecimal[] transformToBigDecimalValuesSV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); BigDecimal[] bigDecimalResult = _bigDecimalResult; if (bigDecimalResult == null || bigDecimalResult.length < numDocs) { bigDecimalResult = new BigDecimal[numDocs]; - Arrays.fill(bigDecimalResult, _bigDecimalLiteral); + Arrays.fill(bigDecimalResult, getBigDecimalLiteral()); _bigDecimalResult = bigDecimalResult; } return bigDecimalResult; } @Override - public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); + public String[] transformToStringValuesSV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); String[] stringResult = _stringResult; if (stringResult == null || stringResult.length < numDocs) { stringResult = new String[numDocs]; - Arrays.fill(stringResult, _literal); + Arrays.fill(stringResult, getStringLiteral()); _stringResult = stringResult; } return stringResult; } @Override - public byte[][] transformToBytesValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); + public byte[][] transformToBytesValuesSV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); byte[][] bytesResult = _bytesResult; if (bytesResult == null || bytesResult.length < numDocs) { bytesResult = new byte[numDocs][]; - Arrays.fill(bytesResult, BytesUtils.toBytes(_literal)); + Arrays.fill(bytesResult, getBytesLiteral()); _bytesResult = bytesResult; } return bytesResult; } @Override - public int[][] transformToIntValuesMV(ProjectionBlock projectionBlock) { + public int[][] transformToIntValuesMV(ValueBlock valueBlock) { throw new UnsupportedOperationException(); } @Override - public long[][] transformToLongValuesMV(ProjectionBlock projectionBlock) { + public long[][] transformToLongValuesMV(ValueBlock valueBlock) { throw new UnsupportedOperationException(); } @Override - public float[][] transformToFloatValuesMV(ProjectionBlock projectionBlock) { + public float[][] transformToFloatValuesMV(ValueBlock valueBlock) { throw new UnsupportedOperationException(); } @Override - public double[][] transformToDoubleValuesMV(ProjectionBlock projectionBlock) { + public double[][] transformToDoubleValuesMV(ValueBlock valueBlock) { throw new UnsupportedOperationException(); } @Override - public String[][] transformToStringValuesMV(ProjectionBlock projectionBlock) { + public String[][] transformToStringValuesMV(ValueBlock valueBlock) { throw new UnsupportedOperationException(); } @Override - public byte[][][] transformToBytesValuesMV(ProjectionBlock projectionBlock) { + public byte[][][] transformToBytesValuesMV(ValueBlock valueBlock) { throw new UnsupportedOperationException(); } + + @Nullable + @Override + public RoaringBitmap getNullBitmap(ValueBlock valueBlock) { + if (!isNull()) { + return null; + } + int length = valueBlock.getNumDocs(); + RoaringBitmap bitmap = new RoaringBitmap(); + bitmap.add(0L, length); + return bitmap; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/LogicalOperatorTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/LogicalOperatorTransformFunction.java index 10e834c63544..ce421416d5cf 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/LogicalOperatorTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/LogicalOperatorTransformFunction.java @@ -21,9 +21,13 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import javax.annotation.Nullable; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; +import org.apache.pinot.spi.data.FieldSpec; +import org.glassfish.jersey.internal.guava.Preconditions; +import org.roaringbitmap.RoaringBitmap; /** @@ -31,11 +35,10 @@ * The results are BOOLEAN type. */ public abstract class LogicalOperatorTransformFunction extends BaseTransformFunction { - protected List _arguments; @Override - public void init(List arguments, Map dataSourceMap) { - _arguments = arguments; + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); int numArguments = arguments.size(); if (numArguments <= 1) { throw new IllegalArgumentException( @@ -44,10 +47,10 @@ public void init(List arguments, Map data } for (int i = 0; i < numArguments; i++) { TransformResultMetadata argumentMetadata = arguments.get(i).getResultMetadata(); - if (!(argumentMetadata.isSingleValue() && argumentMetadata.getDataType().getStoredType().isNumeric())) { - throw new IllegalArgumentException( - "Unsupported argument of index: " + i + ", expecting single-valued boolean/number"); - } + FieldSpec.DataType storedType = argumentMetadata.getDataType().getStoredType(); + Preconditions.checkState( + argumentMetadata.isSingleValue() && storedType.isNumeric() || storedType.isUnknown(), + "Unsupported argument type. Expecting single-valued boolean/number"); } } @@ -57,16 +60,14 @@ public TransformResultMetadata getResultMetadata() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_intValuesSV == null) { - _intValuesSV = new int[numDocs]; - } - System.arraycopy(_arguments.get(0).transformToIntValuesSV(projectionBlock), 0, _intValuesSV, 0, numDocs); + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + initIntValuesSV(numDocs); + System.arraycopy(_arguments.get(0).transformToIntValuesSV(valueBlock), 0, _intValuesSV, 0, numDocs); int numArguments = _arguments.size(); for (int i = 1; i < numArguments; i++) { TransformFunction transformFunction = _arguments.get(i); - int[] results = transformFunction.transformToIntValuesSV(projectionBlock); + int[] results = transformFunction.transformToIntValuesSV(valueBlock); for (int j = 0; j < numDocs; j++) { _intValuesSV[j] = getLogicalFuncResult(_intValuesSV[j], results[j]); } @@ -74,5 +75,35 @@ public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { return _intValuesSV; } + @Nullable + @Override + public RoaringBitmap getNullBitmap(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + int numArguments = _arguments.size(); + RoaringBitmap nullBitmap = new RoaringBitmap(); + boolean[] supersedesNull = new boolean[numDocs]; + for (int i = 0; i < numArguments; i++) { + int[] intValues = _arguments.get(i).transformToIntValuesSV(valueBlock); + RoaringBitmap argumentNullBitmap = _arguments.get(i).getNullBitmap(valueBlock); + for (int docId = 0; docId < numDocs; docId++) { + if ((argumentNullBitmap == null || !argumentNullBitmap.contains(docId)) && valueSupersedesNull( + intValues[docId])) { + supersedesNull[docId] = true; + nullBitmap.remove(docId); + } + } + if (argumentNullBitmap != null) { + for (int docId : argumentNullBitmap) { + if (!supersedesNull[docId]) { + nullBitmap.add(docId); + } + } + } + } + return nullBitmap.isEmpty() ? null : nullBitmap; + } + abstract int getLogicalFuncResult(int left, int right); + + abstract boolean valueSupersedesNull(int i); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/LookupTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/LookupTransformFunction.java index 460966972fd3..a3dec3e00845 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/LookupTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/LookupTransformFunction.java @@ -24,9 +24,9 @@ import java.util.Map; import javax.annotation.Nullable; import org.apache.pinot.core.data.manager.offline.DimensionTableDataManager; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.data.readers.GenericRow; @@ -91,25 +91,26 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); // Check that there are correct number of arguments Preconditions.checkArgument(arguments.size() >= 4, "At least 4 arguments are required for LOOKUP transform function: " + "LOOKUP(TableName, ColumnName, JoinKey, JoinValue [, JoinKey2, JoinValue2 ...])"); - Preconditions - .checkArgument(arguments.size() % 2 == 0, "Should have the same number of JoinKey and JoinValue arguments"); + Preconditions.checkArgument(arguments.size() % 2 == 0, + "Should have the same number of JoinKey and JoinValue arguments"); TransformFunction dimTableNameFunction = arguments.get(0); Preconditions.checkArgument(dimTableNameFunction instanceof LiteralTransformFunction, "First argument must be a literal(string) representing the dimension table name"); // Lookup parameters - String dimTableName = - TableNameBuilder.OFFLINE.tableNameWithType(((LiteralTransformFunction) dimTableNameFunction).getLiteral()); + String dimTableName = TableNameBuilder.OFFLINE.tableNameWithType( + ((LiteralTransformFunction) dimTableNameFunction).getStringLiteral()); TransformFunction dimColumnFunction = arguments.get(1); Preconditions.checkArgument(dimColumnFunction instanceof LiteralTransformFunction, "Second argument must be a literal(string) representing the column name from dimension table to lookup"); - _dimColumnName = ((LiteralTransformFunction) dimColumnFunction).getLiteral(); + _dimColumnName = ((LiteralTransformFunction) dimColumnFunction).getStringLiteral(); List joinArguments = arguments.subList(2, arguments.size()); int numJoinArguments = joinArguments.size(); @@ -117,7 +118,7 @@ public void init(List arguments, Map data TransformFunction dimJoinKeyFunction = joinArguments.get((i * 2)); Preconditions.checkArgument(dimJoinKeyFunction instanceof LiteralTransformFunction, "JoinKey argument must be a literal(string) representing the primary key for the dimension table"); - _joinKeys.add(((LiteralTransformFunction) dimJoinKeyFunction).getLiteral()); + _joinKeys.add(((LiteralTransformFunction) dimJoinKeyFunction).getStringLiteral()); TransformFunction factJoinValueFunction = joinArguments.get((i * 2) + 1); TransformResultMetadata factJoinValueFunctionResultMetadata = factJoinValueFunction.getResultMetadata(); @@ -133,9 +134,8 @@ public void init(List arguments, Map data Preconditions.checkArgument(_dataManager.isPopulated(), "Dimension table is not populated: %s", dimTableName); _lookupColumnFieldSpec = _dataManager.getColumnFieldSpec(_dimColumnName); - Preconditions - .checkArgument(_lookupColumnFieldSpec != null, "Column does not exist in dimension table: %s:%s", dimTableName, - _dimColumnName); + Preconditions.checkArgument(_lookupColumnFieldSpec != null, "Column does not exist in dimension table: %s:%s", + dimTableName, _dimColumnName); for (String joinKey : _joinKeys) { FieldSpec pkColumnSpec = _dataManager.getColumnFieldSpec(joinKey); @@ -168,31 +168,31 @@ private interface ValueAcceptor { void accept(int index, @Nullable Object value); } - private void lookup(ProjectionBlock projectionBlock, ValueAcceptor valueAcceptor) { + private void lookup(ValueBlock valueBlock, ValueAcceptor valueAcceptor) { int numPkColumns = _joinKeys.size(); - int numDocuments = projectionBlock.getNumDocs(); + int numDocuments = valueBlock.getNumDocs(); Object[] pkColumns = new Object[numPkColumns]; for (int c = 0; c < numPkColumns; c++) { DataType storedType = _joinValueFieldSpecs.get(c).getDataType().getStoredType(); TransformFunction tf = _joinValueFunctions.get(c); switch (storedType) { case INT: - pkColumns[c] = tf.transformToIntValuesSV(projectionBlock); + pkColumns[c] = tf.transformToIntValuesSV(valueBlock); break; case LONG: - pkColumns[c] = tf.transformToLongValuesSV(projectionBlock); + pkColumns[c] = tf.transformToLongValuesSV(valueBlock); break; case FLOAT: - pkColumns[c] = tf.transformToFloatValuesSV(projectionBlock); + pkColumns[c] = tf.transformToFloatValuesSV(valueBlock); break; case DOUBLE: - pkColumns[c] = tf.transformToDoubleValuesSV(projectionBlock); + pkColumns[c] = tf.transformToDoubleValuesSV(valueBlock); break; case STRING: - pkColumns[c] = tf.transformToStringValuesSV(projectionBlock); + pkColumns[c] = tf.transformToStringValuesSV(valueBlock); break; case BYTES: - pkColumns[c] = tf.transformToBytesValuesSV(projectionBlock); + pkColumns[c] = tf.transformToBytesValuesSV(valueBlock); break; default: throw new IllegalStateException("Unknown column type for primary key"); @@ -226,145 +226,112 @@ private void lookup(ProjectionBlock projectionBlock, ValueAcceptor valueAcceptor } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { + public int[] transformToIntValuesSV(ValueBlock valueBlock) { if (_lookupColumnFieldSpec.getDataType().getStoredType() != DataType.INT) { - return super.transformToIntValuesSV(projectionBlock); + return super.transformToIntValuesSV(valueBlock); } - int numDocs = projectionBlock.getNumDocs(); - if (_intValuesSV == null) { - _intValuesSV = new int[numDocs]; - } - lookup(projectionBlock, this::setIntSV); + initIntValuesSV(valueBlock.getNumDocs()); + lookup(valueBlock, this::setIntSV); return _intValuesSV; } @Override - public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { + public long[] transformToLongValuesSV(ValueBlock valueBlock) { if (_lookupColumnFieldSpec.getDataType().getStoredType() != DataType.LONG) { - return super.transformToLongValuesSV(projectionBlock); - } - int numDocs = projectionBlock.getNumDocs(); - if (_longValuesSV == null) { - _longValuesSV = new long[numDocs]; + return super.transformToLongValuesSV(valueBlock); } - lookup(projectionBlock, this::setLongSV); + initLongValuesSV(valueBlock.getNumDocs()); + lookup(valueBlock, this::setLongSV); return _longValuesSV; } @Override - public float[] transformToFloatValuesSV(ProjectionBlock projectionBlock) { + public float[] transformToFloatValuesSV(ValueBlock valueBlock) { if (_lookupColumnFieldSpec.getDataType().getStoredType() != DataType.FLOAT) { - return super.transformToFloatValuesSV(projectionBlock); - } - int numDocs = projectionBlock.getNumDocs(); - if (_floatValuesSV == null) { - _floatValuesSV = new float[numDocs]; + return super.transformToFloatValuesSV(valueBlock); } - lookup(projectionBlock, this::setFloatSV); + initFloatValuesSV(valueBlock.getNumDocs()); + lookup(valueBlock, this::setFloatSV); return _floatValuesSV; } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { if (_lookupColumnFieldSpec.getDataType().getStoredType() != DataType.DOUBLE) { - return super.transformToDoubleValuesSV(projectionBlock); + return super.transformToDoubleValuesSV(valueBlock); } - int numDocs = projectionBlock.getNumDocs(); - if (_doubleValuesSV == null) { - _doubleValuesSV = new double[numDocs]; - } - lookup(projectionBlock, this::setDoubleSV); + initDoubleValuesSV(valueBlock.getNumDocs()); + lookup(valueBlock, this::setDoubleSV); return _doubleValuesSV; } @Override - public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { + public String[] transformToStringValuesSV(ValueBlock valueBlock) { if (_lookupColumnFieldSpec.getDataType().getStoredType() != DataType.STRING) { - return super.transformToStringValuesSV(projectionBlock); - } - int numDocs = projectionBlock.getNumDocs(); - if (_stringValuesSV == null) { - _stringValuesSV = new String[numDocs]; + return super.transformToStringValuesSV(valueBlock); } - lookup(projectionBlock, this::setStringSV); + initStringValuesSV(valueBlock.getNumDocs()); + lookup(valueBlock, this::setStringSV); return _stringValuesSV; } @Override - public byte[][] transformToBytesValuesSV(ProjectionBlock projectionBlock) { + public byte[][] transformToBytesValuesSV(ValueBlock valueBlock) { if (_lookupColumnFieldSpec.getDataType().getStoredType() != DataType.BYTES) { - return super.transformToBytesValuesSV(projectionBlock); - } - int numDocs = projectionBlock.getNumDocs(); - if (_bytesValuesSV == null) { - _bytesValuesSV = new byte[numDocs][]; + return super.transformToBytesValuesSV(valueBlock); } - lookup(projectionBlock, this::setBytesSV); + initBytesValuesSV(valueBlock.getNumDocs()); + lookup(valueBlock, this::setBytesSV); return _bytesValuesSV; } @Override - public int[][] transformToIntValuesMV(ProjectionBlock projectionBlock) { + public int[][] transformToIntValuesMV(ValueBlock valueBlock) { if (_lookupColumnFieldSpec.getDataType().getStoredType() != DataType.INT) { - return super.transformToIntValuesMV(projectionBlock); + return super.transformToIntValuesMV(valueBlock); } - int numDocs = projectionBlock.getNumDocs(); - if (_intValuesMV == null) { - _intValuesMV = new int[numDocs][]; - } - lookup(projectionBlock, this::setIntMV); + initIntValuesMV(valueBlock.getNumDocs()); + lookup(valueBlock, this::setIntMV); return _intValuesMV; } @Override - public long[][] transformToLongValuesMV(ProjectionBlock projectionBlock) { + public long[][] transformToLongValuesMV(ValueBlock valueBlock) { if (_lookupColumnFieldSpec.getDataType().getStoredType() != DataType.LONG) { - return super.transformToLongValuesMV(projectionBlock); - } - int numDocs = projectionBlock.getNumDocs(); - if (_longValuesMV == null) { - _longValuesMV = new long[numDocs][]; + return super.transformToLongValuesMV(valueBlock); } - lookup(projectionBlock, this::setLongMV); + initLongValuesMV(valueBlock.getNumDocs()); + lookup(valueBlock, this::setLongMV); return _longValuesMV; } @Override - public float[][] transformToFloatValuesMV(ProjectionBlock projectionBlock) { + public float[][] transformToFloatValuesMV(ValueBlock valueBlock) { if (_lookupColumnFieldSpec.getDataType().getStoredType() != DataType.FLOAT) { - return super.transformToFloatValuesMV(projectionBlock); - } - int numDocs = projectionBlock.getNumDocs(); - if (_floatValuesMV == null) { - _floatValuesMV = new float[numDocs][]; + return super.transformToFloatValuesMV(valueBlock); } - lookup(projectionBlock, this::setFloatMV); + initFloatValuesMV(valueBlock.getNumDocs()); + lookup(valueBlock, this::setFloatMV); return _floatValuesMV; } @Override - public double[][] transformToDoubleValuesMV(ProjectionBlock projectionBlock) { + public double[][] transformToDoubleValuesMV(ValueBlock valueBlock) { if (_lookupColumnFieldSpec.getDataType().getStoredType() != DataType.DOUBLE) { - return super.transformToDoubleValuesMV(projectionBlock); + return super.transformToDoubleValuesMV(valueBlock); } - int numDocs = projectionBlock.getNumDocs(); - if (_doubleValuesMV == null) { - _doubleValuesMV = new double[numDocs][]; - } - lookup(projectionBlock, this::setDoubleMV); + initDoubleValuesMV(valueBlock.getNumDocs()); + lookup(valueBlock, this::setDoubleMV); return _doubleValuesMV; } @Override - public String[][] transformToStringValuesMV(ProjectionBlock projectionBlock) { + public String[][] transformToStringValuesMV(ValueBlock valueBlock) { if (_lookupColumnFieldSpec.getDataType().getStoredType() != DataType.STRING) { - return super.transformToStringValuesMV(projectionBlock); - } - int numDocs = projectionBlock.getNumDocs(); - if (_stringValuesMV == null) { - _stringValuesMV = new String[numDocs][]; + return super.transformToStringValuesMV(valueBlock); } - lookup(projectionBlock, this::setStringMV); + initStringValuesMV(valueBlock.getNumDocs()); + lookup(valueBlock, this::setStringMV); return _stringValuesMV; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/MapValueTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/MapValueTransformFunction.java index d992e0fccffa..2f16765538e4 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/MapValueTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/MapValueTransformFunction.java @@ -21,9 +21,9 @@ import com.google.common.base.Preconditions; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.segment.spi.index.reader.Dictionary; @@ -59,7 +59,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); Preconditions.checkArgument(arguments.size() == 3, "3 arguments are required for MAP_VALUE transform function: keyColumn, keyValue, valueColumn, e.g. MAP_VALUE" + "(key, 'myKey', value)"); @@ -73,7 +74,7 @@ public void init(List arguments, Map data TransformFunction keyValueFunction = arguments.get(1); Preconditions.checkState(keyValueFunction instanceof LiteralTransformFunction, "Key value must be a literal (number or string)"); - String keyValue = ((LiteralTransformFunction) keyValueFunction).getLiteral(); + String keyValue = ((LiteralTransformFunction) keyValueFunction).getStringLiteral(); _keyDictId = keyColumnDictionary.indexOf(keyValue); _valueColumnFunction = arguments.get(2); @@ -95,13 +96,13 @@ public Dictionary getDictionary() { } @Override - public int[] transformToDictIdsSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_dictIds == null) { + public int[] transformToDictIdsSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + if (_dictIds == null || _dictIds.length < length) { _dictIds = new int[length]; } - int[][] keyDictIdsMV = _keyColumnFunction.transformToDictIdsMV(projectionBlock); - int[][] valueDictIdsMV = _valueColumnFunction.transformToDictIdsMV(projectionBlock); + int[][] keyDictIdsMV = _keyColumnFunction.transformToDictIdsMV(valueBlock); + int[][] valueDictIdsMV = _valueColumnFunction.transformToDictIdsMV(valueBlock); for (int i = 0; i < length; i++) { int[] keyDictIds = keyDictIdsMV[i]; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ModuloTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ModuloTransformFunction.java index 435584fdb25b..71f2b3ad86dc 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ModuloTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ModuloTransformFunction.java @@ -21,9 +21,9 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; public class ModuloTransformFunction extends BaseTransformFunction { @@ -40,7 +40,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); // Check that there are exactly 2 arguments if (arguments.size() != 2) { throw new IllegalArgumentException("Exactly 2 arguments are required for MOD transform function"); @@ -48,7 +49,7 @@ public void init(List arguments, Map data TransformFunction firstArgument = arguments.get(0); if (firstArgument instanceof LiteralTransformFunction) { - _firstLiteral = Double.parseDouble(((LiteralTransformFunction) firstArgument).getLiteral()); + _firstLiteral = ((LiteralTransformFunction) firstArgument).getDoubleLiteral(); } else { if (!firstArgument.getResultMetadata().isSingleValue()) { throw new IllegalArgumentException("First argument of MOD transform function must be single-valued"); @@ -58,7 +59,7 @@ public void init(List arguments, Map data TransformFunction secondArgument = arguments.get(1); if (secondArgument instanceof LiteralTransformFunction) { - _secondLiteral = Double.parseDouble(((LiteralTransformFunction) secondArgument).getLiteral()); + _secondLiteral = ((LiteralTransformFunction) secondArgument).getDoubleLiteral(); } else { if (!secondArgument.getResultMetadata().isSingleValue()) { throw new IllegalArgumentException("Second argument of MOD transform function must be single-valued"); @@ -73,15 +74,13 @@ public TransformResultMetadata getResultMetadata() { } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_doubleValuesSV == null) { - _doubleValuesSV = new double[length]; - } + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initDoubleValuesSV(length); if (_firstTransformFunction == null) { Arrays.fill(_doubleValuesSV, 0, length, _firstLiteral); } else { - double[] values = _firstTransformFunction.transformToDoubleValuesSV(projectionBlock); + double[] values = _firstTransformFunction.transformToDoubleValuesSV(valueBlock); System.arraycopy(values, 0, _doubleValuesSV, 0, length); } if (_secondTransformFunction == null) { @@ -89,7 +88,7 @@ public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { _doubleValuesSV[i] %= _secondLiteral; } } else { - double[] values = _secondTransformFunction.transformToDoubleValuesSV(projectionBlock); + double[] values = _secondTransformFunction.transformToDoubleValuesSV(valueBlock); for (int i = 0; i < length; i++) { _doubleValuesSV[i] %= values[i]; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/MultiplicationTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/MultiplicationTransformFunction.java index b2adb8cc6450..e8526c7ad4dd 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/MultiplicationTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/MultiplicationTransformFunction.java @@ -23,9 +23,9 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.utils.ArrayCopyUtils; @@ -44,7 +44,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); // Check that there are more than 1 arguments if (arguments.size() < 2) { throw new IllegalArgumentException("At least 2 arguments are required for MULT transform function"); @@ -57,10 +58,10 @@ public void init(List arguments, Map data DataType dataType = literalTransformFunction.getResultMetadata().getDataType(); if (dataType == DataType.BIG_DECIMAL) { _literalBigDecimalProduct = - _literalBigDecimalProduct.multiply(new BigDecimal(literalTransformFunction.getLiteral())); + _literalBigDecimalProduct.multiply(literalTransformFunction.getBigDecimalLiteral()); _resultDataType = DataType.BIG_DECIMAL; } else { - _literalDoubleProduct *= Double.parseDouble(((LiteralTransformFunction) argument).getLiteral()); + _literalDoubleProduct *= ((LiteralTransformFunction) argument).getDoubleLiteral(); } } else { if (!argument.getResultMetadata().isSingleValue()) { @@ -86,18 +87,16 @@ public TransformResultMetadata getResultMetadata() { } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_doubleValuesSV == null) { - _doubleValuesSV = new double[length]; - } + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initDoubleValuesSV(length); if (_resultDataType == DataType.BIG_DECIMAL) { - BigDecimal[] values = transformToBigDecimalValuesSV(projectionBlock); + BigDecimal[] values = transformToBigDecimalValuesSV(valueBlock); ArrayCopyUtils.copy(values, _doubleValuesSV, length); } else { Arrays.fill(_doubleValuesSV, 0, length, _literalDoubleProduct); for (TransformFunction transformFunction : _transformFunctions) { - double[] values = transformFunction.transformToDoubleValuesSV(projectionBlock); + double[] values = transformFunction.transformToDoubleValuesSV(valueBlock); for (int i = 0; i < length; i++) { _doubleValuesSV[i] *= values[i]; } @@ -107,18 +106,16 @@ public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { } @Override - public BigDecimal[] transformToBigDecimalValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_bigDecimalValuesSV == null) { - _bigDecimalValuesSV = new BigDecimal[length]; - } + public BigDecimal[] transformToBigDecimalValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initBigDecimalValuesSV(length); if (_resultDataType == DataType.DOUBLE) { - double[] values = transformToDoubleValuesSV(projectionBlock); + double[] values = transformToDoubleValuesSV(valueBlock); ArrayCopyUtils.copy(values, _bigDecimalValuesSV, length); } else { Arrays.fill(_bigDecimalValuesSV, 0, length, _literalBigDecimalProduct); for (TransformFunction transformFunction : _transformFunctions) { - BigDecimal[] values = transformFunction.transformToBigDecimalValuesSV(projectionBlock); + BigDecimal[] values = transformFunction.transformToBigDecimalValuesSV(valueBlock); for (int i = 0; i < length; i++) { _bigDecimalValuesSV[i] = _bigDecimalValuesSV[i].multiply(values[i]); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/NotInTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/NotInTransformFunction.java index dd09574847c0..4bf2885e50ea 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/NotInTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/NotInTransformFunction.java @@ -21,8 +21,8 @@ import java.util.List; import java.util.Map; import org.apache.pinot.common.function.TransformFunctionType; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; -import org.apache.pinot.segment.spi.datasource.DataSource; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; /** @@ -36,16 +36,21 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { - super.init(arguments, dataSourceMap); + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - int[] intValuesSV = super.transformToIntValuesSV(projectionBlock); + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + int[] intValuesSV = super.transformToIntValuesSV(valueBlock); for (int i = 0; i < intValuesSV.length; i++) { intValuesSV[i] = 1 - intValuesSV[i]; } return intValuesSV; } + + @Override + protected boolean mainFunctionNotContainedInValues(int value) { + return value == 1; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/NotOperatorTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/NotOperatorTransformFunction.java index 2870d4836d77..3bff952c918a 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/NotOperatorTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/NotOperatorTransformFunction.java @@ -22,9 +22,10 @@ import java.util.List; import java.util.Map; import org.apache.pinot.common.function.TransformFunctionType; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; +import org.apache.pinot.spi.data.FieldSpec; /** @@ -48,11 +49,13 @@ public class NotOperatorTransformFunction extends BaseTransformFunction { private TransformFunction _argument; @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); Preconditions.checkArgument(arguments.size() == 1, "Exact 1 argument1 is required for not transform function"); TransformResultMetadata argumentMetadata = arguments.get(0).getResultMetadata(); + FieldSpec.DataType storedType = argumentMetadata.getDataType().getStoredType(); Preconditions.checkState( - argumentMetadata.isSingleValue() && argumentMetadata.getDataType().getStoredType().isNumeric(), + argumentMetadata.isSingleValue() && storedType.isNumeric() || storedType.isUnknown(), "Unsupported argument type. Expecting single-valued boolean/number"); _argument = arguments.get(0); } @@ -68,12 +71,10 @@ public String getName() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { - int numDocs = projectionBlock.getNumDocs(); - if (_intValuesSV == null) { - _intValuesSV = new int[numDocs]; - } - int[] intValues = _argument.transformToIntValuesSV(projectionBlock); + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + initIntValuesSV(numDocs); + int[] intValues = _argument.transformToIntValuesSV(valueBlock); for (int i = 0; i < numDocs; i++) { _intValuesSV[i] = getLogicalNegate(intValues[i]); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/OrOperatorTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/OrOperatorTransformFunction.java index f279e147cfba..47a728582278 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/OrOperatorTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/OrOperatorTransformFunction.java @@ -45,4 +45,9 @@ int getLogicalFuncResult(int left, int right) { } return 1; } + + @Override + boolean valueSupersedesNull(int i) { + return i != 0; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/PowerTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/PowerTransformFunction.java index 0ac45eb9ed80..0d5324d440c0 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/PowerTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/PowerTransformFunction.java @@ -21,9 +21,9 @@ import com.google.common.base.Preconditions; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; public class PowerTransformFunction extends BaseTransformFunction { @@ -39,7 +39,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); // Check that there are more than 1 arguments if (arguments.size() != 2) { throw new IllegalArgumentException("Exactly 2 arguments are required for power transform function"); @@ -49,7 +50,7 @@ public void init(List arguments, Map data _leftTransformFunction = arguments.get(0); _rightTransformFunction = arguments.get(1); if (_rightTransformFunction instanceof LiteralTransformFunction) { - _exponent = Double.parseDouble(((LiteralTransformFunction) _rightTransformFunction).getLiteral()); + _exponent = ((LiteralTransformFunction) _rightTransformFunction).getDoubleLiteral(); _fixedExponent = true; } Preconditions.checkArgument( @@ -63,18 +64,16 @@ public TransformResultMetadata getResultMetadata() { } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_doubleValuesSV == null) { - _doubleValuesSV = new double[length]; - } - double[] leftValues = _leftTransformFunction.transformToDoubleValuesSV(projectionBlock); + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initDoubleValuesSV(length); + double[] leftValues = _leftTransformFunction.transformToDoubleValuesSV(valueBlock); if (_fixedExponent) { for (int i = 0; i < length; i++) { _doubleValuesSV[i] = Math.pow(leftValues[i], _exponent); } } else { - double[] rightValues = _rightTransformFunction.transformToDoubleValuesSV(projectionBlock); + double[] rightValues = _rightTransformFunction.transformToDoubleValuesSV(valueBlock); for (int i = 0; i < length; i++) { _doubleValuesSV[i] = Math.pow(leftValues[i], rightValues[i]); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/RegexpExtractTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/RegexpExtractTransformFunction.java index f163d9a70557..ba0d7042a56c 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/RegexpExtractTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/RegexpExtractTransformFunction.java @@ -21,11 +21,12 @@ import com.google.common.base.Preconditions; import java.util.List; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.common.utils.regex.Matcher; +import org.apache.pinot.common.utils.regex.Pattern; +import org.apache.pinot.common.utils.regex.PatternFactory; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; /** @@ -57,7 +58,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); Preconditions.checkArgument(arguments.size() >= 2 && arguments.size() <= 4, "REGEXP_EXTRACT takes between 2 to 4 arguments. See usage: " + "REGEXP_EXTRACT(`value`, `regexp`[, `group`[, `default_value`]]"); @@ -66,14 +68,14 @@ public void init(List arguments, Map data TransformFunction regexpFunction = arguments.get(1); Preconditions.checkState(regexpFunction instanceof LiteralTransformFunction, "`regexp` must be a literal regex expression."); - _regexp = Pattern.compile(((LiteralTransformFunction) regexpFunction).getLiteral()); + _regexp = PatternFactory.compile(((LiteralTransformFunction) regexpFunction).getStringLiteral()); if (arguments.size() >= 3) { TransformFunction groupFunction = arguments.get(2); Preconditions.checkState(groupFunction instanceof LiteralTransformFunction - && Integer.parseInt(((LiteralTransformFunction) groupFunction).getLiteral()) >= 0, + && ((LiteralTransformFunction) groupFunction).getIntLiteral() >= 0, "`group` must be a literal, non-negative integer."); - _group = Integer.parseInt(((LiteralTransformFunction) groupFunction).getLiteral()); + _group = ((LiteralTransformFunction) groupFunction).getIntLiteral(); } else { _group = 0; } @@ -82,7 +84,7 @@ public void init(List arguments, Map data TransformFunction positionFunction = arguments.get(3); Preconditions.checkState(positionFunction instanceof LiteralTransformFunction, "`default_value` must be a literal expression."); - _defaultValue = ((LiteralTransformFunction) regexpFunction).getLiteral(); + _defaultValue = ((LiteralTransformFunction) regexpFunction).getStringLiteral(); } else { _defaultValue = ""; } @@ -95,12 +97,10 @@ public TransformResultMetadata getResultMetadata() { } @Override - public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_stringValuesSV == null) { - _stringValuesSV = new String[length]; - } - String[] valuesSV = _valueFunction.transformToStringValuesSV(projectionBlock); + public String[] transformToStringValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initStringValuesSV(length); + String[] valuesSV = _valueFunction.transformToStringValuesSV(valueBlock); for (int i = 0; i < length; i++) { Matcher matcher = _regexp.matcher(valuesSV[i]); if (matcher.find() && matcher.groupCount() >= _group) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/RoundDecimalTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/RoundDecimalTransformFunction.java index 53baed25283c..1fbfcecaf3a7 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/RoundDecimalTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/RoundDecimalTransformFunction.java @@ -23,9 +23,9 @@ import java.math.RoundingMode; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec; @@ -44,7 +44,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); int numArguments = arguments.size(); // Check that there are more than 2 arguments or no arguments if (numArguments < 1 || numArguments > 2) { @@ -57,7 +58,7 @@ public void init(List arguments, Map data if (numArguments > 1) { _rightTransformFunction = arguments.get(1); if (_rightTransformFunction instanceof LiteralTransformFunction) { - _scale = Integer.parseInt(((LiteralTransformFunction) _rightTransformFunction).getLiteral()); + _scale = ((LiteralTransformFunction) _rightTransformFunction).getIntLiteral(); _fixedScale = true; } Preconditions.checkArgument( @@ -83,19 +84,17 @@ public TransformResultMetadata getResultMetadata() { } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_doubleValuesSV == null) { - _doubleValuesSV = new double[length]; - } - double[] leftValues = _leftTransformFunction.transformToDoubleValuesSV(projectionBlock); + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initDoubleValuesSV(length); + double[] leftValues = _leftTransformFunction.transformToDoubleValuesSV(valueBlock); if (_fixedScale) { for (int i = 0; i < length; i++) { _doubleValuesSV[i] = BigDecimal.valueOf(leftValues[i]) .setScale(_scale, RoundingMode.HALF_UP).doubleValue(); } } else if (_rightTransformFunction != null) { - int[] rightValues = _rightTransformFunction.transformToIntValuesSV(projectionBlock); + int[] rightValues = _rightTransformFunction.transformToIntValuesSV(valueBlock); for (int i = 0; i < length; i++) { _doubleValuesSV[i] = BigDecimal.valueOf(leftValues[i]) .setScale(rightValues[i], RoundingMode.HALF_UP).doubleValue(); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ScalarTransformFunctionWrapper.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ScalarTransformFunctionWrapper.java index 097c387457e3..d8ea23b492d2 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ScalarTransformFunctionWrapper.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ScalarTransformFunctionWrapper.java @@ -27,11 +27,14 @@ import org.apache.pinot.common.function.FunctionInfo; import org.apache.pinot.common.function.FunctionInvoker; import org.apache.pinot.common.function.FunctionUtils; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.common.utils.PinotDataType; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.utils.ByteArray; +import org.apache.pinot.spi.utils.CommonConstants.NullValuePlaceHolder; /** @@ -40,10 +43,10 @@ public class ScalarTransformFunctionWrapper extends BaseTransformFunction { private final String _name; private final FunctionInvoker _functionInvoker; - private final PinotDataType _resultType; + private final ColumnDataType _resultType; private final TransformResultMetadata _resultMetadata; - private Object[] _arguments; + private Object[] _scalarArguments; private int _numNonLiteralArguments; private int[] _nonLiteralIndices; private TransformFunction[] _nonLiteralFunctions; @@ -60,14 +63,13 @@ public ScalarTransformFunctionWrapper(FunctionInfo functionInfo) { parameterClasses[i], functionInfo.getMethod()); } Class resultClass = _functionInvoker.getResultClass(); - PinotDataType resultType = FunctionUtils.getParameterType(resultClass); + ColumnDataType resultType = FunctionUtils.getColumnDataType(resultClass); if (resultType != null) { _resultType = resultType; - _resultMetadata = - new TransformResultMetadata(FunctionUtils.getDataType(resultClass), _resultType.isSingleValue(), false); + _resultMetadata = new TransformResultMetadata(resultType.toDataType(), !_resultType.isArray(), false); } else { // Handle unrecognized result class with STRING - _resultType = PinotDataType.STRING; + _resultType = ColumnDataType.STRING; _resultMetadata = new TransformResultMetadata(DataType.STRING, true, false); } } @@ -78,21 +80,69 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); int numArguments = arguments.size(); PinotDataType[] parameterTypes = _functionInvoker.getParameterTypes(); Preconditions.checkArgument(numArguments == parameterTypes.length, "Wrong number of arguments for method: %s, expected: %s, actual: %s", _functionInvoker.getMethod(), parameterTypes.length, numArguments); - _arguments = new Object[numArguments]; + _scalarArguments = new Object[numArguments]; _nonLiteralIndices = new int[numArguments]; _nonLiteralFunctions = new TransformFunction[numArguments]; for (int i = 0; i < numArguments; i++) { TransformFunction transformFunction = arguments.get(i); if (transformFunction instanceof LiteralTransformFunction) { - String literal = ((LiteralTransformFunction) transformFunction).getLiteral(); - _arguments[i] = parameterTypes[i].convert(literal, PinotDataType.STRING); + LiteralTransformFunction literalTransformFunction = (LiteralTransformFunction) transformFunction; + DataType dataType = literalTransformFunction.getResultMetadata().getDataType(); + switch (dataType) { + case BOOLEAN: + _scalarArguments[i] = + parameterTypes[i].convert(literalTransformFunction.getBooleanLiteral(), PinotDataType.BOOLEAN); + break; + case INT: + _scalarArguments[i] = + parameterTypes[i].convert(literalTransformFunction.getIntLiteral(), PinotDataType.INTEGER); + break; + case LONG: + _scalarArguments[i] = + parameterTypes[i].convert(literalTransformFunction.getLongLiteral(), PinotDataType.LONG); + break; + case FLOAT: + _scalarArguments[i] = + parameterTypes[i].convert(literalTransformFunction.getFloatLiteral(), PinotDataType.FLOAT); + break; + case DOUBLE: + _scalarArguments[i] = + parameterTypes[i].convert(literalTransformFunction.getDoubleLiteral(), PinotDataType.DOUBLE); + break; + case BIG_DECIMAL: + if (parameterTypes[i] == PinotDataType.STRING) { + _scalarArguments[i] = literalTransformFunction.getStringLiteral(); + break; + } + _scalarArguments[i] = + parameterTypes[i].convert(literalTransformFunction.getBigDecimalLiteral(), PinotDataType.BIG_DECIMAL); + break; + case TIMESTAMP: + if (parameterTypes[i] == PinotDataType.STRING) { + _scalarArguments[i] = literalTransformFunction.getStringLiteral(); + break; + } + _scalarArguments[i] = + parameterTypes[i].convert(literalTransformFunction.getLongLiteral(), PinotDataType.TIMESTAMP); + break; + case STRING: + _scalarArguments[i] = + parameterTypes[i].convert(literalTransformFunction.getStringLiteral(), PinotDataType.STRING); + break; + case UNKNOWN: + _scalarArguments[i] = null; + break; + default: + throw new RuntimeException("Unsupported data type:" + dataType); + } } else { _nonLiteralIndices[_numNonLiteralArguments] = i; _nonLiteralFunctions[_numNonLiteralArguments] = transformFunction; @@ -108,231 +158,218 @@ public TransformResultMetadata getResultMetadata() { } @Override - public int[] transformToIntValuesSV(ProjectionBlock projectionBlock) { + public int[] transformToIntValuesSV(ValueBlock valueBlock) { if (_resultMetadata.getDataType().getStoredType() != DataType.INT) { - return super.transformToIntValuesSV(projectionBlock); - } - int length = projectionBlock.getNumDocs(); - if (_intValuesSV == null) { - _intValuesSV = new int[length]; + return super.transformToIntValuesSV(valueBlock); } - getNonLiteralValues(projectionBlock); + int length = valueBlock.getNumDocs(); + initIntValuesSV(length); + getNonLiteralValues(valueBlock); for (int i = 0; i < length; i++) { for (int j = 0; j < _numNonLiteralArguments; j++) { - _arguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; + _scalarArguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; } - _intValuesSV[i] = (int) _resultType.toInternal(_functionInvoker.invoke(_arguments)); + Object value = _functionInvoker.invoke(_scalarArguments); + _intValuesSV[i] = value != null ? (int) _resultType.toInternal(value) : NullValuePlaceHolder.INT; } return _intValuesSV; } @Override - public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { + public long[] transformToLongValuesSV(ValueBlock valueBlock) { if (_resultMetadata.getDataType().getStoredType() != DataType.LONG) { - return super.transformToLongValuesSV(projectionBlock); + return super.transformToLongValuesSV(valueBlock); } - int length = projectionBlock.getNumDocs(); - if (_longValuesSV == null) { - _longValuesSV = new long[length]; - } - getNonLiteralValues(projectionBlock); + int length = valueBlock.getNumDocs(); + initLongValuesSV(length); + getNonLiteralValues(valueBlock); for (int i = 0; i < length; i++) { for (int j = 0; j < _numNonLiteralArguments; j++) { - _arguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; + _scalarArguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; } - _longValuesSV[i] = (long) _resultType.toInternal(_functionInvoker.invoke(_arguments)); + Object value = _functionInvoker.invoke(_scalarArguments); + _longValuesSV[i] = value != null ? (long) _resultType.toInternal(value) : NullValuePlaceHolder.LONG; } return _longValuesSV; } @Override - public float[] transformToFloatValuesSV(ProjectionBlock projectionBlock) { + public float[] transformToFloatValuesSV(ValueBlock valueBlock) { if (_resultMetadata.getDataType().getStoredType() != DataType.FLOAT) { - return super.transformToFloatValuesSV(projectionBlock); - } - int length = projectionBlock.getNumDocs(); - if (_floatValuesSV == null) { - _floatValuesSV = new float[length]; + return super.transformToFloatValuesSV(valueBlock); } - getNonLiteralValues(projectionBlock); + int length = valueBlock.getNumDocs(); + initFloatValuesSV(length); + getNonLiteralValues(valueBlock); for (int i = 0; i < length; i++) { for (int j = 0; j < _numNonLiteralArguments; j++) { - _arguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; + _scalarArguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; } - _floatValuesSV[i] = (float) _resultType.toInternal(_functionInvoker.invoke(_arguments)); + Object value = _functionInvoker.invoke(_scalarArguments); + _floatValuesSV[i] = + value != null ? (float) _resultType.toInternal(value) : NullValuePlaceHolder.FLOAT; } return _floatValuesSV; } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { if (_resultMetadata.getDataType().getStoredType() != DataType.DOUBLE) { - return super.transformToDoubleValuesSV(projectionBlock); + return super.transformToDoubleValuesSV(valueBlock); } - int length = projectionBlock.getNumDocs(); - if (_doubleValuesSV == null) { - _doubleValuesSV = new double[length]; - } - getNonLiteralValues(projectionBlock); + int length = valueBlock.getNumDocs(); + initDoubleValuesSV(length); + getNonLiteralValues(valueBlock); for (int i = 0; i < length; i++) { for (int j = 0; j < _numNonLiteralArguments; j++) { - _arguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; + _scalarArguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; } - _doubleValuesSV[i] = (double) _resultType.toInternal(_functionInvoker.invoke(_arguments)); + Object value = _functionInvoker.invoke(_scalarArguments); + _doubleValuesSV[i] = + value != null ? (double) _resultType.toInternal(value) : NullValuePlaceHolder.DOUBLE; } return _doubleValuesSV; } @Override - public BigDecimal[] transformToBigDecimalValuesSV(ProjectionBlock projectionBlock) { + public BigDecimal[] transformToBigDecimalValuesSV(ValueBlock valueBlock) { if (_resultMetadata.getDataType().getStoredType() != DataType.BIG_DECIMAL) { - return super.transformToBigDecimalValuesSV(projectionBlock); - } - int length = projectionBlock.getNumDocs(); - if (_bigDecimalValuesSV == null) { - _bigDecimalValuesSV = new BigDecimal[length]; + return super.transformToBigDecimalValuesSV(valueBlock); } - getNonLiteralValues(projectionBlock); + int length = valueBlock.getNumDocs(); + initBigDecimalValuesSV(length); + getNonLiteralValues(valueBlock); for (int i = 0; i < length; i++) { for (int j = 0; j < _numNonLiteralArguments; j++) { - _arguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; + _scalarArguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; } - _bigDecimalValuesSV[i] = (BigDecimal) _resultType.toInternal(_functionInvoker.invoke(_arguments)); + Object value = _functionInvoker.invoke(_scalarArguments); + _bigDecimalValuesSV[i] = + value != null ? (BigDecimal) _resultType.toInternal(value) : NullValuePlaceHolder.BIG_DECIMAL; } return _bigDecimalValuesSV; } @Override - public String[] transformToStringValuesSV(ProjectionBlock projectionBlock) { + public String[] transformToStringValuesSV(ValueBlock valueBlock) { if (_resultMetadata.getDataType().getStoredType() != DataType.STRING) { - return super.transformToStringValuesSV(projectionBlock); - } - int length = projectionBlock.getNumDocs(); - if (_stringValuesSV == null) { - _stringValuesSV = new String[length]; + return super.transformToStringValuesSV(valueBlock); } - getNonLiteralValues(projectionBlock); + int length = valueBlock.getNumDocs(); + initStringValuesSV(length); + getNonLiteralValues(valueBlock); for (int i = 0; i < length; i++) { for (int j = 0; j < _numNonLiteralArguments; j++) { - _arguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; + _scalarArguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; } - Object result = _functionInvoker.invoke(_arguments); + Object value = _functionInvoker.invoke(_scalarArguments); _stringValuesSV[i] = - _resultType == PinotDataType.STRING ? result.toString() : (String) _resultType.toInternal(result); + value != null ? (String) _resultType.toInternal(value) : NullValuePlaceHolder.STRING; } return _stringValuesSV; } @Override - public byte[][] transformToBytesValuesSV(ProjectionBlock projectionBlock) { + public byte[][] transformToBytesValuesSV(ValueBlock valueBlock) { if (_resultMetadata.getDataType().getStoredType() != DataType.BYTES) { - return super.transformToBytesValuesSV(projectionBlock); + return super.transformToBytesValuesSV(valueBlock); } - int length = projectionBlock.getNumDocs(); - if (_bytesValuesSV == null) { - _bytesValuesSV = new byte[length][]; - } - getNonLiteralValues(projectionBlock); + int length = valueBlock.getNumDocs(); + initBytesValuesSV(length); + getNonLiteralValues(valueBlock); for (int i = 0; i < length; i++) { for (int j = 0; j < _numNonLiteralArguments; j++) { - _arguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; + _scalarArguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; } - _bytesValuesSV[i] = (byte[]) _resultType.toInternal(_functionInvoker.invoke(_arguments)); + Object value = _functionInvoker.invoke(_scalarArguments); + byte[] bytes = + value != null ? ((ByteArray) _resultType.toInternal(value)).getBytes() : NullValuePlaceHolder.BYTES; + _bytesValuesSV[i] = bytes; } return _bytesValuesSV; } @Override - public int[][] transformToIntValuesMV(ProjectionBlock projectionBlock) { + public int[][] transformToIntValuesMV(ValueBlock valueBlock) { if (_resultMetadata.getDataType().getStoredType() != DataType.INT) { - return super.transformToIntValuesMV(projectionBlock); - } - int length = projectionBlock.getNumDocs(); - if (_intValuesMV == null) { - _intValuesMV = new int[length][]; + return super.transformToIntValuesMV(valueBlock); } - getNonLiteralValues(projectionBlock); + int length = valueBlock.getNumDocs(); + initIntValuesMV(length); + getNonLiteralValues(valueBlock); for (int i = 0; i < length; i++) { for (int j = 0; j < _numNonLiteralArguments; j++) { - _arguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; + _scalarArguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; } - _intValuesMV[i] = (int[]) _resultType.toInternal(_functionInvoker.invoke(_arguments)); + _intValuesMV[i] = (int[]) _resultType.toInternal(_functionInvoker.invoke(_scalarArguments)); } return _intValuesMV; } @Override - public long[][] transformToLongValuesMV(ProjectionBlock projectionBlock) { + public long[][] transformToLongValuesMV(ValueBlock valueBlock) { if (_resultMetadata.getDataType().getStoredType() != DataType.LONG) { - return super.transformToLongValuesMV(projectionBlock); - } - int length = projectionBlock.getNumDocs(); - if (_longValuesMV == null) { - _longValuesMV = new long[length][]; + return super.transformToLongValuesMV(valueBlock); } - getNonLiteralValues(projectionBlock); + int length = valueBlock.getNumDocs(); + initLongValuesMV(length); + getNonLiteralValues(valueBlock); for (int i = 0; i < length; i++) { for (int j = 0; j < _numNonLiteralArguments; j++) { - _arguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; + _scalarArguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; } - _longValuesMV[i] = (long[]) _resultType.toInternal(_functionInvoker.invoke(_arguments)); + _longValuesMV[i] = (long[]) _resultType.toInternal(_functionInvoker.invoke(_scalarArguments)); } return _longValuesMV; } @Override - public float[][] transformToFloatValuesMV(ProjectionBlock projectionBlock) { + public float[][] transformToFloatValuesMV(ValueBlock valueBlock) { if (_resultMetadata.getDataType().getStoredType() != DataType.FLOAT) { - return super.transformToFloatValuesMV(projectionBlock); + return super.transformToFloatValuesMV(valueBlock); } - int length = projectionBlock.getNumDocs(); - if (_floatValuesMV == null) { - _floatValuesMV = new float[length][]; - } - getNonLiteralValues(projectionBlock); + int length = valueBlock.getNumDocs(); + initFloatValuesMV(length); + getNonLiteralValues(valueBlock); for (int i = 0; i < length; i++) { for (int j = 0; j < _numNonLiteralArguments; j++) { - _arguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; + _scalarArguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; } - _floatValuesMV[i] = (float[]) _resultType.toInternal(_functionInvoker.invoke(_arguments)); + _floatValuesMV[i] = (float[]) _resultType.toInternal(_functionInvoker.invoke(_scalarArguments)); } return _floatValuesMV; } @Override - public double[][] transformToDoubleValuesMV(ProjectionBlock projectionBlock) { + public double[][] transformToDoubleValuesMV(ValueBlock valueBlock) { if (_resultMetadata.getDataType().getStoredType() != DataType.DOUBLE) { - return super.transformToDoubleValuesMV(projectionBlock); - } - int length = projectionBlock.getNumDocs(); - if (_doubleValuesMV == null) { - _doubleValuesMV = new double[length][]; + return super.transformToDoubleValuesMV(valueBlock); } - getNonLiteralValues(projectionBlock); + int length = valueBlock.getNumDocs(); + initDoubleValuesMV(length); + getNonLiteralValues(valueBlock); for (int i = 0; i < length; i++) { for (int j = 0; j < _numNonLiteralArguments; j++) { - _arguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; + _scalarArguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; } - _doubleValuesMV[i] = (double[]) _resultType.toInternal(_functionInvoker.invoke(_arguments)); + _doubleValuesMV[i] = (double[]) _resultType.toInternal(_functionInvoker.invoke(_scalarArguments)); } return _doubleValuesMV; } @Override - public String[][] transformToStringValuesMV(ProjectionBlock projectionBlock) { + public String[][] transformToStringValuesMV(ValueBlock valueBlock) { if (_resultMetadata.getDataType().getStoredType() != DataType.STRING) { - return super.transformToStringValuesMV(projectionBlock); - } - int length = projectionBlock.getNumDocs(); - if (_stringValuesMV == null) { - _stringValuesMV = new String[length][]; + return super.transformToStringValuesMV(valueBlock); } - getNonLiteralValues(projectionBlock); + int length = valueBlock.getNumDocs(); + initStringValuesMV(length); + getNonLiteralValues(valueBlock); for (int i = 0; i < length; i++) { for (int j = 0; j < _numNonLiteralArguments; j++) { - _arguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; + _scalarArguments[_nonLiteralIndices[j]] = _nonLiteralValues[j][i]; } - _stringValuesMV[i] = (String[]) _resultType.toInternal(_functionInvoker.invoke(_arguments)); + _stringValuesMV[i] = (String[]) _resultType.toInternal(_functionInvoker.invoke(_scalarArguments)); } return _stringValuesMV; } @@ -340,29 +377,29 @@ public String[][] transformToStringValuesMV(ProjectionBlock projectionBlock) { /** * Helper method to fetch values for the non-literal transform functions based on the parameter types. */ - private void getNonLiteralValues(ProjectionBlock projectionBlock) { + private void getNonLiteralValues(ValueBlock valueBlock) { PinotDataType[] parameterTypes = _functionInvoker.getParameterTypes(); for (int i = 0; i < _numNonLiteralArguments; i++) { PinotDataType parameterType = parameterTypes[_nonLiteralIndices[i]]; TransformFunction transformFunction = _nonLiteralFunctions[i]; switch (parameterType) { case INTEGER: - _nonLiteralValues[i] = ArrayUtils.toObject(transformFunction.transformToIntValuesSV(projectionBlock)); + _nonLiteralValues[i] = ArrayUtils.toObject(transformFunction.transformToIntValuesSV(valueBlock)); break; case LONG: - _nonLiteralValues[i] = ArrayUtils.toObject(transformFunction.transformToLongValuesSV(projectionBlock)); + _nonLiteralValues[i] = ArrayUtils.toObject(transformFunction.transformToLongValuesSV(valueBlock)); break; case FLOAT: - _nonLiteralValues[i] = ArrayUtils.toObject(transformFunction.transformToFloatValuesSV(projectionBlock)); + _nonLiteralValues[i] = ArrayUtils.toObject(transformFunction.transformToFloatValuesSV(valueBlock)); break; case DOUBLE: - _nonLiteralValues[i] = ArrayUtils.toObject(transformFunction.transformToDoubleValuesSV(projectionBlock)); + _nonLiteralValues[i] = ArrayUtils.toObject(transformFunction.transformToDoubleValuesSV(valueBlock)); break; case BIG_DECIMAL: - _nonLiteralValues[i] = transformFunction.transformToBigDecimalValuesSV(projectionBlock); + _nonLiteralValues[i] = transformFunction.transformToBigDecimalValuesSV(valueBlock); break; case BOOLEAN: { - int[] intValues = transformFunction.transformToIntValuesSV(projectionBlock); + int[] intValues = transformFunction.transformToIntValuesSV(valueBlock); int numValues = intValues.length; Boolean[] booleanValues = new Boolean[numValues]; for (int j = 0; j < numValues; j++) { @@ -372,7 +409,7 @@ private void getNonLiteralValues(ProjectionBlock projectionBlock) { break; } case TIMESTAMP: { - long[] longValues = transformFunction.transformToLongValuesSV(projectionBlock); + long[] longValues = transformFunction.transformToLongValuesSV(valueBlock); int numValues = longValues.length; Timestamp[] timestampValues = new Timestamp[numValues]; for (int j = 0; j < numValues; j++) { @@ -382,25 +419,25 @@ private void getNonLiteralValues(ProjectionBlock projectionBlock) { break; } case STRING: - _nonLiteralValues[i] = transformFunction.transformToStringValuesSV(projectionBlock); + _nonLiteralValues[i] = transformFunction.transformToStringValuesSV(valueBlock); break; case BYTES: - _nonLiteralValues[i] = transformFunction.transformToBytesValuesSV(projectionBlock); + _nonLiteralValues[i] = transformFunction.transformToBytesValuesSV(valueBlock); break; case PRIMITIVE_INT_ARRAY: - _nonLiteralValues[i] = transformFunction.transformToIntValuesMV(projectionBlock); + _nonLiteralValues[i] = transformFunction.transformToIntValuesMV(valueBlock); break; case PRIMITIVE_LONG_ARRAY: - _nonLiteralValues[i] = transformFunction.transformToLongValuesMV(projectionBlock); + _nonLiteralValues[i] = transformFunction.transformToLongValuesMV(valueBlock); break; case PRIMITIVE_FLOAT_ARRAY: - _nonLiteralValues[i] = transformFunction.transformToFloatValuesMV(projectionBlock); + _nonLiteralValues[i] = transformFunction.transformToFloatValuesMV(valueBlock); break; case PRIMITIVE_DOUBLE_ARRAY: - _nonLiteralValues[i] = transformFunction.transformToDoubleValuesMV(projectionBlock); + _nonLiteralValues[i] = transformFunction.transformToDoubleValuesMV(valueBlock); break; case STRING_ARRAY: - _nonLiteralValues[i] = transformFunction.transformToStringValuesMV(projectionBlock); + _nonLiteralValues[i] = transformFunction.transformToStringValuesMV(valueBlock); break; default: throw new IllegalStateException("Unsupported parameter type: " + parameterType); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/SelectTupleElementTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/SelectTupleElementTransformFunction.java index a6314770534f..a4d9aaeba398 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/SelectTupleElementTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/SelectTupleElementTransformFunction.java @@ -18,12 +18,14 @@ */ package org.apache.pinot.core.operator.transform.function; +import java.math.BigDecimal; import java.util.EnumMap; import java.util.EnumSet; import java.util.List; import java.util.Map; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec; @@ -31,7 +33,7 @@ public abstract class SelectTupleElementTransformFunction extends BaseTransformF private static final EnumSet SUPPORTED_DATATYPES = EnumSet.of(FieldSpec.DataType.INT, FieldSpec.DataType.LONG, FieldSpec.DataType.FLOAT, FieldSpec.DataType.DOUBLE, FieldSpec.DataType.BIG_DECIMAL, - FieldSpec.DataType.TIMESTAMP, FieldSpec.DataType.STRING); + FieldSpec.DataType.TIMESTAMP, FieldSpec.DataType.STRING, FieldSpec.DataType.UNKNOWN); private static final EnumMap> ACCEPTABLE_COMBINATIONS = createAcceptableCombinations(); @@ -46,7 +48,9 @@ public SelectTupleElementTransformFunction(String name) { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap, + boolean nullHandlingEnabled) { + super.init(arguments, columnContextMap, nullHandlingEnabled); if (arguments.isEmpty()) { throw new IllegalArgumentException(_name + " takes at least one argument"); } @@ -63,7 +67,8 @@ public void init(List arguments, Map data } if (dataType == null) { dataType = argumentType; - } else if (ACCEPTABLE_COMBINATIONS.get(dataType).contains(argumentType)) { + } else if (dataType.isUnknown() || argumentType.isUnknown() || ACCEPTABLE_COMBINATIONS.get(dataType) + .contains(argumentType)) { dataType = getLowestCommonDenominatorType(dataType, argumentType); } else { throw new IllegalArgumentException( @@ -109,4 +114,106 @@ private static EnumMap> createAc combinations.put(FieldSpec.DataType.STRING, EnumSet.of(FieldSpec.DataType.STRING)); return combinations; } + + @Override + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + initIntValuesSV(numDocs); + int[] values = _arguments.get(0).transformToIntValuesSV(valueBlock); + System.arraycopy(values, 0, _intValuesSV, 0, numDocs); + for (int i = 1; i < _arguments.size(); i++) { + values = _arguments.get(i).transformToIntValuesSV(valueBlock); + for (int j = 0; j < numDocs & j < values.length; j++) { + _intValuesSV[j] = binaryFunction(_intValuesSV[j], values[j]); + } + } + return _intValuesSV; + } + + abstract protected int binaryFunction(int a, int b); + + @Override + public long[] transformToLongValuesSV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + initLongValuesSV(numDocs); + long[] values = _arguments.get(0).transformToLongValuesSV(valueBlock); + System.arraycopy(values, 0, _longValuesSV, 0, numDocs); + for (int i = 1; i < _arguments.size(); i++) { + values = _arguments.get(i).transformToLongValuesSV(valueBlock); + for (int j = 0; j < numDocs & j < values.length; j++) { + _longValuesSV[j] = binaryFunction(_longValuesSV[j], values[j]); + } + } + return _longValuesSV; + } + + abstract protected long binaryFunction(long a, long b); + + @Override + public float[] transformToFloatValuesSV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + initFloatValuesSV(numDocs); + float[] values = _arguments.get(0).transformToFloatValuesSV(valueBlock); + System.arraycopy(values, 0, _floatValuesSV, 0, numDocs); + for (int i = 1; i < _arguments.size(); i++) { + values = _arguments.get(i).transformToFloatValuesSV(valueBlock); + for (int j = 0; j < numDocs & j < values.length; j++) { + _floatValuesSV[j] = binaryFunction(_floatValuesSV[j], values[j]); + } + } + return _floatValuesSV; + } + + abstract protected float binaryFunction(float a, float b); + + @Override + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + initDoubleValuesSV(numDocs); + double[] values = _arguments.get(0).transformToDoubleValuesSV(valueBlock); + System.arraycopy(values, 0, _doubleValuesSV, 0, numDocs); + for (int i = 1; i < _arguments.size(); i++) { + values = _arguments.get(i).transformToDoubleValuesSV(valueBlock); + for (int j = 0; j < numDocs & j < values.length; j++) { + _doubleValuesSV[j] = binaryFunction(_doubleValuesSV[j], values[j]); + } + } + return _doubleValuesSV; + } + + abstract protected double binaryFunction(double a, double b); + + @Override + public BigDecimal[] transformToBigDecimalValuesSV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + initBigDecimalValuesSV(numDocs); + BigDecimal[] values = _arguments.get(0).transformToBigDecimalValuesSV(valueBlock); + System.arraycopy(values, 0, _bigDecimalValuesSV, 0, numDocs); + for (int i = 1; i < _arguments.size(); i++) { + values = _arguments.get(i).transformToBigDecimalValuesSV(valueBlock); + for (int j = 0; j < numDocs & j < values.length; j++) { + _bigDecimalValuesSV[j] = binaryFunction(_bigDecimalValuesSV[j], values[j]); + } + } + return _bigDecimalValuesSV; + } + + abstract protected BigDecimal binaryFunction(BigDecimal a, BigDecimal b); + + @Override + public String[] transformToStringValuesSV(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); + initStringValuesSV(numDocs); + String[] values = _arguments.get(0).transformToStringValuesSV(valueBlock); + System.arraycopy(values, 0, _stringValuesSV, 0, numDocs); + for (int i = 1; i < _arguments.size(); i++) { + values = _arguments.get(i).transformToStringValuesSV(valueBlock); + for (int j = 0; j < numDocs & j < values.length; j++) { + _stringValuesSV[j] = binaryFunction(_stringValuesSV[j], (values[j])); + } + } + return _stringValuesSV; + } + + abstract protected String binaryFunction(String a, String b); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/SingleParamMathTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/SingleParamMathTransformFunction.java index 39c1d8258092..d18c9673c9fe 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/SingleParamMathTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/SingleParamMathTransformFunction.java @@ -23,9 +23,9 @@ import java.math.RoundingMode; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.utils.ArrayCopyUtils; @@ -46,7 +46,8 @@ public abstract class SingleParamMathTransformFunction extends BaseTransformFunc private DataType _resultDataType; @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); Preconditions.checkArgument(arguments.size() == 1, "Exactly 1 argument is required for transform function: %s", getName()); TransformFunction transformFunction = arguments.get(0); @@ -70,33 +71,29 @@ public TransformResultMetadata getResultMetadata() { } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_doubleValuesSV == null) { - _doubleValuesSV = new double[length]; - } + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initDoubleValuesSV(length); if (_resultDataType == DataType.BIG_DECIMAL) { - BigDecimal[] values = transformToBigDecimalValuesSV(projectionBlock); + BigDecimal[] values = transformToBigDecimalValuesSV(valueBlock); ArrayCopyUtils.copy(values, _doubleValuesSV, length); } else { - double[] values = _transformFunction.transformToDoubleValuesSV(projectionBlock); - applyMathOperator(values, projectionBlock.getNumDocs()); + double[] values = _transformFunction.transformToDoubleValuesSV(valueBlock); + applyMathOperator(values, valueBlock.getNumDocs()); } return _doubleValuesSV; } @Override - public BigDecimal[] transformToBigDecimalValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_bigDecimalValuesSV == null) { - _bigDecimalValuesSV = new BigDecimal[length]; - } + public BigDecimal[] transformToBigDecimalValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initBigDecimalValuesSV(length); if (_resultDataType == DataType.DOUBLE) { - double[] values = transformToDoubleValuesSV(projectionBlock); + double[] values = transformToDoubleValuesSV(valueBlock); ArrayCopyUtils.copy(values, _bigDecimalValuesSV, length); } else { - BigDecimal[] values = _transformFunction.transformToBigDecimalValuesSV(projectionBlock); - applyMathOperator(values, projectionBlock.getNumDocs()); + BigDecimal[] values = _transformFunction.transformToBigDecimalValuesSV(valueBlock); + applyMathOperator(values, valueBlock.getNumDocs()); } return _bigDecimalValuesSV; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/SubtractionTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/SubtractionTransformFunction.java index ae039d8cfe1b..e46cc1151863 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/SubtractionTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/SubtractionTransformFunction.java @@ -22,9 +22,9 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.utils.ArrayCopyUtils; @@ -44,7 +44,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); // Check that there are exactly 2 arguments if (arguments.size() != 2) { throw new IllegalArgumentException("Exactly 2 arguments are required for SUB transform function"); @@ -68,9 +69,9 @@ public void init(List arguments, Map data if (argument instanceof LiteralTransformFunction) { LiteralTransformFunction literalTransformFunction = (LiteralTransformFunction) argument; if (_resultDataType == DataType.BIG_DECIMAL) { - _bigDecimalLiterals[i] = new BigDecimal(literalTransformFunction.getLiteral()); + _bigDecimalLiterals[i] = literalTransformFunction.getBigDecimalLiteral(); } else { - _doubleLiterals[i] = Double.parseDouble(((LiteralTransformFunction) argument).getLiteral()); + _doubleLiterals[i] = ((LiteralTransformFunction) argument).getDoubleLiteral(); } } else { if (!argument.getResultMetadata().isSingleValue()) { @@ -95,19 +96,17 @@ public TransformResultMetadata getResultMetadata() { @SuppressWarnings("Duplicates") @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_doubleValuesSV == null) { - _doubleValuesSV = new double[length]; - } + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initDoubleValuesSV(length); if (_resultDataType == DataType.BIG_DECIMAL) { - BigDecimal[] values = transformToBigDecimalValuesSV(projectionBlock); + BigDecimal[] values = transformToBigDecimalValuesSV(valueBlock); ArrayCopyUtils.copy(values, _doubleValuesSV, length); } else { if (_firstTransformFunction == null) { Arrays.fill(_doubleValuesSV, 0, length, _doubleLiterals[0]); } else { - double[] values = _firstTransformFunction.transformToDoubleValuesSV(projectionBlock); + double[] values = _firstTransformFunction.transformToDoubleValuesSV(valueBlock); System.arraycopy(values, 0, _doubleValuesSV, 0, length); } if (_secondTransformFunction == null) { @@ -115,7 +114,7 @@ public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { _doubleValuesSV[i] -= _doubleLiterals[1]; } } else { - double[] values = _secondTransformFunction.transformToDoubleValuesSV(projectionBlock); + double[] values = _secondTransformFunction.transformToDoubleValuesSV(valueBlock); for (int i = 0; i < length; i++) { _doubleValuesSV[i] -= values[i]; } @@ -125,19 +124,17 @@ public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { } @Override - public BigDecimal[] transformToBigDecimalValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_bigDecimalValuesSV == null) { - _bigDecimalValuesSV = new BigDecimal[length]; - } + public BigDecimal[] transformToBigDecimalValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initBigDecimalValuesSV(length); if (_resultDataType == DataType.DOUBLE) { - double[] values = transformToDoubleValuesSV(projectionBlock); + double[] values = transformToDoubleValuesSV(valueBlock); ArrayCopyUtils.copy(values, _bigDecimalValuesSV, length); } else { if (_firstTransformFunction == null) { Arrays.fill(_bigDecimalValuesSV, 0, length, _bigDecimalLiterals[0]); } else { - BigDecimal[] values = _firstTransformFunction.transformToBigDecimalValuesSV(projectionBlock); + BigDecimal[] values = _firstTransformFunction.transformToBigDecimalValuesSV(valueBlock); System.arraycopy(values, 0, _bigDecimalValuesSV, 0, length); } if (_secondTransformFunction == null) { @@ -145,7 +142,7 @@ public BigDecimal[] transformToBigDecimalValuesSV(ProjectionBlock projectionBloc _bigDecimalValuesSV[i] = _bigDecimalValuesSV[i].subtract(_bigDecimalLiterals[1]); } } else { - BigDecimal[] values = _secondTransformFunction.transformToBigDecimalValuesSV(projectionBlock); + BigDecimal[] values = _secondTransformFunction.transformToBigDecimalValuesSV(valueBlock); for (int i = 0; i < length; i++) { _bigDecimalValuesSV[i] = _bigDecimalValuesSV[i].subtract(values[i]); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TimeConversionTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TimeConversionTransformFunction.java index d4382e409afd..f8a9cbbc4dc6 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TimeConversionTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TimeConversionTransformFunction.java @@ -21,11 +21,12 @@ import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.core.operator.transform.transformer.timeunit.TimeUnitTransformer; import org.apache.pinot.core.operator.transform.transformer.timeunit.TimeUnitTransformerFactory; -import org.apache.pinot.segment.spi.datasource.DataSource; +import org.roaringbitmap.RoaringBitmap; public class TimeConversionTransformFunction extends BaseTransformFunction { @@ -40,7 +41,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); // Check that there are exactly 3 arguments if (arguments.size() != 3) { throw new IllegalArgumentException("Exactly 3 arguments are required for TIME_CONVERT transform function"); @@ -55,8 +57,8 @@ public void init(List arguments, Map data _mainTransformFunction = firstArgument; _timeUnitTransformer = TimeUnitTransformerFactory.getTimeUnitTransformer( - TimeUnit.valueOf(((LiteralTransformFunction) arguments.get(1)).getLiteral().toUpperCase()), - ((LiteralTransformFunction) arguments.get(2)).getLiteral()); + TimeUnit.valueOf(((LiteralTransformFunction) arguments.get(1)).getStringLiteral().toUpperCase()), + ((LiteralTransformFunction) arguments.get(2)).getStringLiteral()); } @Override @@ -65,13 +67,15 @@ public TransformResultMetadata getResultMetadata() { } @Override - public long[] transformToLongValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_longValuesSV == null) { - _longValuesSV = new long[length]; - } - _timeUnitTransformer.transform(_mainTransformFunction.transformToLongValuesSV(projectionBlock), _longValuesSV, - length); + public long[] transformToLongValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initLongValuesSV(length); + _timeUnitTransformer.transform(_mainTransformFunction.transformToLongValuesSV(valueBlock), _longValuesSV, length); return _longValuesSV; } + + @Override + public RoaringBitmap getNullBitmap(ValueBlock valueBlock) { + return _mainTransformFunction.getNullBitmap(valueBlock); + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TransformFunction.java index f3fc209db926..95ee40f7279a 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TransformFunction.java @@ -21,10 +21,12 @@ import java.math.BigDecimal; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import javax.annotation.Nullable; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.segment.spi.index.reader.Dictionary; +import org.roaringbitmap.RoaringBitmap; /** @@ -43,10 +45,22 @@ public interface TransformFunction { /** * Initializes the transform function. * - * @param arguments Arguments for the transform function - * @param dataSourceMap Map from column to data source + * @param arguments Arguments for the transform function + * @param columnContextMap Map from column name to context */ - void init(List arguments, Map dataSourceMap); + void init(List arguments, Map columnContextMap); + + /** + * Initializes the transform function. + * + * @param arguments Arguments for the transform function + * @param columnContextMap Map from column name to context + * @param nullHandlingEnabled Whether this transform function handles {@code null} + */ + default void init(List arguments, Map columnContextMap, + boolean nullHandlingEnabled) { + init(arguments, columnContextMap); + } /** * Returns the metadata for the result of the transform function. @@ -61,136 +75,100 @@ public interface TransformFunction { /** * Returns the dictionary for the transform result if the result is dictionary-encoded, or {@code null} if not. - * - * @return Dictionary */ + @Nullable Dictionary getDictionary(); /** - * Transforms the data from the given projection block to single-valued dictionary Ids. - * - * @param projectionBlock Projection block - * @return Transformation result + * Transforms the data from the given value block to single-valued dictionary ids. */ - int[] transformToDictIdsSV(ProjectionBlock projectionBlock); + int[] transformToDictIdsSV(ValueBlock valueBlock); /** - * Transforms the data from the given projection block to multi-valued dictionary Ids. - * - * @param projectionBlock Projection block - * @return Transformation result + * Transforms the data from the given value block to multi-valued dictionary ids. */ - int[][] transformToDictIdsMV(ProjectionBlock projectionBlock); + int[][] transformToDictIdsMV(ValueBlock valueBlock); /** * SINGLE-VALUED APIs */ /** - * Transforms the data from the given projection block to single-valued int values. - * - * @param projectionBlock Projection result - * @return Transformation result + * Transforms the data from the given value block to single-valued int values. */ - int[] transformToIntValuesSV(ProjectionBlock projectionBlock); + int[] transformToIntValuesSV(ValueBlock valueBlock); /** - * Transforms the data from the given projection block to single-valued long values. - * - * @param projectionBlock Projection result - * @return Transformation result + * Transforms the data from the given value block to single-valued long values. */ - long[] transformToLongValuesSV(ProjectionBlock projectionBlock); + long[] transformToLongValuesSV(ValueBlock valueBlock); /** - * Transforms the data from the given projection block to single-valued float values. - * - * @param projectionBlock Projection result - * @return Transformation result + * Transforms the data from the given value block to single-valued float values. */ - float[] transformToFloatValuesSV(ProjectionBlock projectionBlock); + float[] transformToFloatValuesSV(ValueBlock valueBlock); /** - * Transforms the data from the given projection block to single-valued double values. - * - * @param projectionBlock Projection result - * @return Transformation result + * Transforms the data from the given value block to single-valued double values. */ - double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock); + double[] transformToDoubleValuesSV(ValueBlock valueBlock); /** - * Transforms the data from the given projection block to single-valued BigDecimal values. - * - * @param projectionBlock Projection result - * @return Transformation result + * Transforms the data from the given value block to single-valued BigDecimal values. */ - BigDecimal[] transformToBigDecimalValuesSV(ProjectionBlock projectionBlock); + BigDecimal[] transformToBigDecimalValuesSV(ValueBlock valueBlock); /** - * Transforms the data from the given projection block to single-valued string values. - * - * @param projectionBlock Projection result - * @return Transformation result + * Transforms the data from the given value block to single-valued string values. */ - String[] transformToStringValuesSV(ProjectionBlock projectionBlock); + String[] transformToStringValuesSV(ValueBlock valueBlock); /** - * Transforms the data from the given projection block to single-valued bytes values. - * - * @param projectionBlock Projection result - * @return Transformation result + * Transforms the data from the given value block to single-valued bytes values. */ - byte[][] transformToBytesValuesSV(ProjectionBlock projectionBlock); + byte[][] transformToBytesValuesSV(ValueBlock valueBlock); /** * MULTI-VALUED APIs */ /** - * Transforms the data from the given projection block to multi-valued int values. - * - * @param projectionBlock Projection result - * @return Transformation result + * Transforms the data from the given value block to multi-valued int values. */ - int[][] transformToIntValuesMV(ProjectionBlock projectionBlock); + int[][] transformToIntValuesMV(ValueBlock valueBlock); /** - * Transforms the data from the given projection block to multi-valued long values. - * - * @param projectionBlock Projection result - * @return Transformation result + * Transforms the data from the given value block to multi-valued long values. */ - long[][] transformToLongValuesMV(ProjectionBlock projectionBlock); + long[][] transformToLongValuesMV(ValueBlock valueBlock); /** - * Transforms the data from the given projection block to multi-valued float values. - * - * @param projectionBlock Projection result - * @return Transformation result + * Transforms the data from the given value block to multi-valued float values. */ - float[][] transformToFloatValuesMV(ProjectionBlock projectionBlock); + float[][] transformToFloatValuesMV(ValueBlock valueBlock); /** - * Transforms the data from the given projection block to multi-valued double values. - * - * @param projectionBlock Projection result - * @return Transformation result + * Transforms the data from the given value block to multi-valued double values. */ - double[][] transformToDoubleValuesMV(ProjectionBlock projectionBlock); + double[][] transformToDoubleValuesMV(ValueBlock valueBlock); /** - * Transforms the data from the given projection block to multi-valued string values. - * - * @param projectionBlock Projection result - * @return Transformation result + * Transforms the data from the given value block to multi-valued string values. + */ + String[][] transformToStringValuesMV(ValueBlock valueBlock); + + /** + * Transforms the data from the given value block to multi-valued bytes values. */ - String[][] transformToStringValuesMV(ProjectionBlock projectionBlock); + byte[][][] transformToBytesValuesMV(ValueBlock valueBlock); /** - * Transforms the data from the given projection block to multi-valued bytes values. + * Gets the null rows for transformation result. Should be called when only null information is needed for + * transformation. * - * @param projectionBlock Projection result - * @return Transformation result + * @return Null bit vector that indicates null rows for transformation result + * If returns null, it means no record is null. */ - byte[][][] transformToBytesValuesMV(ProjectionBlock projectionBlock); + @Nullable + RoaringBitmap getNullBitmap(ValueBlock block); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TransformFunctionFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TransformFunctionFactory.java index cec8847f1af3..de7668ca26a9 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TransformFunctionFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TransformFunctionFactory.java @@ -18,19 +18,21 @@ */ package org.apache.pinot.core.operator.transform.function; +import com.google.common.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.EnumMap; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -import javax.annotation.Nullable; import org.apache.commons.lang3.StringUtils; import org.apache.pinot.common.function.FunctionInfo; import org.apache.pinot.common.function.FunctionRegistry; import org.apache.pinot.common.function.TransformFunctionType; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.FunctionContext; +import org.apache.pinot.common.request.context.LiteralContext; +import org.apache.pinot.common.utils.HashUtil; import org.apache.pinot.core.geospatial.transform.function.GeoToH3Function; import org.apache.pinot.core.geospatial.transform.function.StAreaFunction; import org.apache.pinot.core.geospatial.transform.function.StAsBinaryFunction; @@ -46,6 +48,7 @@ import org.apache.pinot.core.geospatial.transform.function.StPointFunction; import org.apache.pinot.core.geospatial.transform.function.StPolygonFunction; import org.apache.pinot.core.geospatial.transform.function.StWithinFunction; +import org.apache.pinot.core.operator.ColumnContext; import org.apache.pinot.core.operator.transform.function.SingleParamMathTransformFunction.AbsTransformFunction; import org.apache.pinot.core.operator.transform.function.SingleParamMathTransformFunction.CeilTransformFunction; import org.apache.pinot.core.operator.transform.function.SingleParamMathTransformFunction.ExpTransformFunction; @@ -68,9 +71,20 @@ import org.apache.pinot.core.operator.transform.function.TrigonometricTransformFunctions.SinhTransformFunction; import org.apache.pinot.core.operator.transform.function.TrigonometricTransformFunctions.TanTransformFunction; import org.apache.pinot.core.operator.transform.function.TrigonometricTransformFunctions.TanhTransformFunction; +import org.apache.pinot.core.operator.transform.function.VectorTransformFunctions.CosineDistanceTransformFunction; +import org.apache.pinot.core.operator.transform.function.VectorTransformFunctions.InnerProductTransformFunction; +import org.apache.pinot.core.operator.transform.function.VectorTransformFunctions.L1DistanceTransformFunction; +import org.apache.pinot.core.operator.transform.function.VectorTransformFunctions.L2DistanceTransformFunction; +import org.apache.pinot.core.operator.transform.function.VectorTransformFunctions.VectorDimsTransformFunction; +import org.apache.pinot.core.operator.transform.function.VectorTransformFunctions.VectorNormTransformFunction; import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.query.request.context.utils.QueryContextConverterUtils; import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.exception.BadQueryRequestException; +import org.apache.pinot.sql.parsers.CalciteSqlParser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * Factory class for transformation functions. @@ -79,6 +93,7 @@ public class TransformFunctionFactory { private TransformFunctionFactory() { } + private static final Logger LOGGER = LoggerFactory.getLogger(TransformFunctionFactory.class); private static final Map> TRANSFORM_FUNCTION_MAP = createRegistry(); private static Map> createRegistry() { @@ -105,15 +120,14 @@ private static Map> createRegistry() typeToImplementation.put(TransformFunctionType.TRUNCATE, TruncateDecimalTransformFunction.class); typeToImplementation.put(TransformFunctionType.CAST, CastTransformFunction.class); - typeToImplementation.put(TransformFunctionType.JSONEXTRACTSCALAR, - JsonExtractScalarTransformFunction.class); - typeToImplementation.put(TransformFunctionType.JSONEXTRACTKEY, - JsonExtractKeyTransformFunction.class); - typeToImplementation.put(TransformFunctionType.TIMECONVERT, - TimeConversionTransformFunction.class); - typeToImplementation.put(TransformFunctionType.DATETIMECONVERT, - DateTimeConversionTransformFunction.class); - typeToImplementation.put(TransformFunctionType.DATETRUNC, DateTruncTransformFunction.class); + typeToImplementation.put(TransformFunctionType.JSON_EXTRACT_SCALAR, JsonExtractScalarTransformFunction.class); + typeToImplementation.put(TransformFunctionType.JSON_EXTRACT_KEY, JsonExtractKeyTransformFunction.class); + typeToImplementation.put(TransformFunctionType.TIME_CONVERT, TimeConversionTransformFunction.class); + typeToImplementation.put(TransformFunctionType.DATE_TIME_CONVERT, DateTimeConversionTransformFunction.class); + typeToImplementation.put(TransformFunctionType.DATE_TIME_CONVERT_WINDOW_HOP, + DateTimeConversionHopTransformFunction.class); + typeToImplementation.put(TransformFunctionType.DATE_TRUNC, DateTruncTransformFunction.class); + typeToImplementation.put(TransformFunctionType.JSON_EXTRACT_INDEX, JsonExtractIndexTransformFunction.class); typeToImplementation.put(TransformFunctionType.YEAR, DateTimeTransformFunction.Year.class); typeToImplementation.put(TransformFunctionType.YEAR_OF_WEEK, DateTimeTransformFunction.YearOfWeek.class); typeToImplementation.put(TransformFunctionType.QUARTER, DateTimeTransformFunction.Quarter.class); @@ -126,37 +140,35 @@ private static Map> createRegistry() typeToImplementation.put(TransformFunctionType.MINUTE, DateTimeTransformFunction.Minute.class); typeToImplementation.put(TransformFunctionType.SECOND, DateTimeTransformFunction.Second.class); typeToImplementation.put(TransformFunctionType.MILLISECOND, DateTimeTransformFunction.Millisecond.class); - typeToImplementation.put(TransformFunctionType.ARRAYLENGTH, ArrayLengthTransformFunction.class); - typeToImplementation.put(TransformFunctionType.VALUEIN, ValueInTransformFunction.class); - typeToImplementation.put(TransformFunctionType.MAPVALUE, MapValueTransformFunction.class); - typeToImplementation.put(TransformFunctionType.INIDSET, InIdSetTransformFunction.class); + typeToImplementation.put(TransformFunctionType.ARRAY_LENGTH, ArrayLengthTransformFunction.class); + typeToImplementation.put(TransformFunctionType.VALUE_IN, ValueInTransformFunction.class); + typeToImplementation.put(TransformFunctionType.MAP_VALUE, MapValueTransformFunction.class); + typeToImplementation.put(TransformFunctionType.IN_ID_SET, InIdSetTransformFunction.class); typeToImplementation.put(TransformFunctionType.LOOKUP, LookupTransformFunction.class); + typeToImplementation.put(TransformFunctionType.CLP_DECODE, CLPDecodeTransformFunction.class); + typeToImplementation.put(TransformFunctionType.CLP_ENCODED_VARS_MATCH, ClpEncodedVarsMatchTransformFunction.class); typeToImplementation.put(TransformFunctionType.EXTRACT, ExtractTransformFunction.class); // Regexp functions - typeToImplementation.put(TransformFunctionType.REGEXP_EXTRACT, - RegexpExtractTransformFunction.class); + typeToImplementation.put(TransformFunctionType.REGEXP_EXTRACT, RegexpExtractTransformFunction.class); // Array functions - typeToImplementation.put(TransformFunctionType.ARRAYAVERAGE, - ArrayAverageTransformFunction.class); - typeToImplementation.put(TransformFunctionType.ARRAYMAX, ArrayMaxTransformFunction.class); - typeToImplementation.put(TransformFunctionType.ARRAYMIN, ArrayMinTransformFunction.class); - typeToImplementation.put(TransformFunctionType.ARRAYSUM, ArraySumTransformFunction.class); + typeToImplementation.put(TransformFunctionType.ARRAY_AVERAGE, ArrayAverageTransformFunction.class); + typeToImplementation.put(TransformFunctionType.ARRAY_MAX, ArrayMaxTransformFunction.class); + typeToImplementation.put(TransformFunctionType.ARRAY_MIN, ArrayMinTransformFunction.class); + typeToImplementation.put(TransformFunctionType.ARRAY_SUM, ArraySumTransformFunction.class); + typeToImplementation.put(TransformFunctionType.ARRAY_VALUE_CONSTRUCTOR, ArrayLiteralTransformFunction.class); typeToImplementation.put(TransformFunctionType.GROOVY, GroovyTransformFunction.class); typeToImplementation.put(TransformFunctionType.CASE, CaseTransformFunction.class); typeToImplementation.put(TransformFunctionType.EQUALS, EqualsTransformFunction.class); typeToImplementation.put(TransformFunctionType.NOT_EQUALS, NotEqualsTransformFunction.class); - typeToImplementation.put(TransformFunctionType.GREATER_THAN, - GreaterThanTransformFunction.class); - typeToImplementation.put(TransformFunctionType.GREATER_THAN_OR_EQUAL, - GreaterThanOrEqualTransformFunction.class); + typeToImplementation.put(TransformFunctionType.GREATER_THAN, GreaterThanTransformFunction.class); + typeToImplementation.put(TransformFunctionType.GREATER_THAN_OR_EQUAL, GreaterThanOrEqualTransformFunction.class); typeToImplementation.put(TransformFunctionType.LESS_THAN, LessThanTransformFunction.class); - typeToImplementation.put(TransformFunctionType.LESS_THAN_OR_EQUAL, - LessThanOrEqualTransformFunction.class); + typeToImplementation.put(TransformFunctionType.LESS_THAN_OR_EQUAL, LessThanOrEqualTransformFunction.class); typeToImplementation.put(TransformFunctionType.IN, InTransformFunction.class); typeToImplementation.put(TransformFunctionType.NOT_IN, NotInTransformFunction.class); @@ -167,22 +179,17 @@ private static Map> createRegistry() // geo functions // geo constructors - typeToImplementation.put(TransformFunctionType.ST_GEOG_FROM_TEXT, - StGeogFromTextFunction.class); - typeToImplementation.put(TransformFunctionType.ST_GEOG_FROM_WKB, - StGeogFromWKBFunction.class); - typeToImplementation.put(TransformFunctionType.ST_GEOM_FROM_TEXT, - StGeomFromTextFunction.class); - typeToImplementation.put(TransformFunctionType.ST_GEOM_FROM_WKB, - StGeomFromWKBFunction.class); + typeToImplementation.put(TransformFunctionType.ST_GEOG_FROM_TEXT, StGeogFromTextFunction.class); + typeToImplementation.put(TransformFunctionType.ST_GEOG_FROM_WKB, StGeogFromWKBFunction.class); + typeToImplementation.put(TransformFunctionType.ST_GEOM_FROM_TEXT, StGeomFromTextFunction.class); + typeToImplementation.put(TransformFunctionType.ST_GEOM_FROM_WKB, StGeomFromWKBFunction.class); typeToImplementation.put(TransformFunctionType.ST_POINT, StPointFunction.class); typeToImplementation.put(TransformFunctionType.ST_POLYGON, StPolygonFunction.class); // geo measurements typeToImplementation.put(TransformFunctionType.ST_AREA, StAreaFunction.class); typeToImplementation.put(TransformFunctionType.ST_DISTANCE, StDistanceFunction.class); - typeToImplementation.put(TransformFunctionType.ST_GEOMETRY_TYPE, - StGeometryTypeFunction.class); + typeToImplementation.put(TransformFunctionType.ST_GEOMETRY_TYPE, StGeometryTypeFunction.class); // geo outputs typeToImplementation.put(TransformFunctionType.ST_AS_BINARY, StAsBinaryFunction.class); @@ -194,16 +201,19 @@ private static Map> createRegistry() typeToImplementation.put(TransformFunctionType.ST_WITHIN, StWithinFunction.class); // geo indexing - typeToImplementation.put(TransformFunctionType.GEOTOH3, GeoToH3Function.class); + typeToImplementation.put(TransformFunctionType.GEO_TO_H3, GeoToH3Function.class); // tuple selection typeToImplementation.put(TransformFunctionType.LEAST, LeastTransformFunction.class); typeToImplementation.put(TransformFunctionType.GREATEST, GreatestTransformFunction.class); // null handling + typeToImplementation.put(TransformFunctionType.IS_TRUE, IsTrueTransformFunction.class); + typeToImplementation.put(TransformFunctionType.IS_NOT_TRUE, IsNotTrueTransformFunction.class); + typeToImplementation.put(TransformFunctionType.IS_FALSE, IsFalseTransformFunction.class); + typeToImplementation.put(TransformFunctionType.IS_NOT_FALSE, IsNotFalseTransformFunction.class); typeToImplementation.put(TransformFunctionType.IS_NULL, IsNullTransformFunction.class); - typeToImplementation.put(TransformFunctionType.IS_NOT_NULL, - IsNotNullTransformFunction.class); + typeToImplementation.put(TransformFunctionType.IS_NOT_NULL, IsNotNullTransformFunction.class); typeToImplementation.put(TransformFunctionType.COALESCE, CoalesceTransformFunction.class); typeToImplementation.put(TransformFunctionType.IS_DISTINCT_FROM, IsDistinctFromTransformFunction.class); typeToImplementation.put(TransformFunctionType.IS_NOT_DISTINCT_FROM, IsNotDistinctFromTransformFunction.class); @@ -223,9 +233,18 @@ private static Map> createRegistry() typeToImplementation.put(TransformFunctionType.DEGREES, DegreesTransformFunction.class); typeToImplementation.put(TransformFunctionType.RADIANS, RadiansTransformFunction.class); - Map> registry = new HashMap<>(typeToImplementation.size()); + // Vector functions + typeToImplementation.put(TransformFunctionType.COSINE_DISTANCE, CosineDistanceTransformFunction.class); + typeToImplementation.put(TransformFunctionType.INNER_PRODUCT, InnerProductTransformFunction.class); + typeToImplementation.put(TransformFunctionType.L1_DISTANCE, L1DistanceTransformFunction.class); + typeToImplementation.put(TransformFunctionType.L2_DISTANCE, L2DistanceTransformFunction.class); + typeToImplementation.put(TransformFunctionType.VECTOR_DIMS, VectorDimsTransformFunction.class); + typeToImplementation.put(TransformFunctionType.VECTOR_NORM, VectorNormTransformFunction.class); + + Map> registry + = new HashMap<>(HashUtil.getHashMapCapacity(typeToImplementation.size())); for (Map.Entry> entry : typeToImplementation.entrySet()) { - for (String alias : entry.getKey().getAliases()) { + for (String alias : entry.getKey().getAlternativeNames()) { registry.put(canonicalize(alias), entry.getValue()); } } @@ -234,7 +253,7 @@ private static Map> createRegistry() /** * Initializes the factory with a set of transform function classes. - *

Should be called only once before calling {@link #get(ExpressionContext, Map)}. + *

Should be called only once before using the factory. * * @param transformFunctionClasses Set of transform function classes */ @@ -242,41 +261,30 @@ public static void init(Set> transformFunctionClasses) for (Class transformFunctionClass : transformFunctionClasses) { TransformFunction transformFunction; try { - transformFunction = transformFunctionClass.newInstance(); - } catch (InstantiationException | IllegalAccessException e) { + transformFunction = transformFunctionClass.getDeclaredConstructor().newInstance(); + } catch (Exception e) { throw new RuntimeException( - "Caught exception while instantiating transform function from class: " + transformFunctionClass.toString(), - e); + "Caught exception while instantiating transform function from class: " + transformFunctionClass, e); } String transformFunctionName = canonicalize(transformFunction.getName()); - if (TRANSFORM_FUNCTION_MAP.containsKey(transformFunctionName)) { - throw new IllegalArgumentException("Transform function: " + transformFunctionName + " already exists"); + if (TRANSFORM_FUNCTION_MAP.put(transformFunctionName, transformFunctionClass) == null) { + LOGGER.info("Registering function: {} with class: {}", transformFunctionName, transformFunctionClass); + } else { + LOGGER.info("Replacing function: {} with class: {}", transformFunctionName, transformFunctionClass); } - TRANSFORM_FUNCTION_MAP.put(transformFunctionName, transformFunctionClass); } } /** * Returns an instance of transform function for the given expression. * - * @param expression Transform expression - * @param dataSourceMap Map from column name to column data source + * @param expression Transform expression + * @param columnContextMap Map from column name to context + * @param queryContext Query context * @return Transform function */ - public static TransformFunction get(ExpressionContext expression, Map dataSourceMap) { - return get(null, expression, dataSourceMap); - } - - /** - * Returns an instance of transform function for the given expression. - * - * @param queryContext the query context if available - * @param expression Transform expression - * @param dataSourceMap Map from column name to column data source - * @return Transform function - */ - public static TransformFunction get(@Nullable QueryContext queryContext, ExpressionContext expression, - Map dataSourceMap) { + public static TransformFunction get(ExpressionContext expression, Map columnContextMap, + QueryContext queryContext) { switch (expression.getType()) { case FUNCTION: FunctionContext function = expression.getFunction(); @@ -284,12 +292,19 @@ public static TransformFunction get(@Nullable QueryContext queryContext, Express List arguments = function.getArguments(); int numArguments = arguments.size(); + // Check if the function is ArrayLiteraltransform function + if (functionName.equalsIgnoreCase(ArrayLiteralTransformFunction.FUNCTION_NAME)) { + return queryContext.getOrComputeSharedValue(ArrayLiteralTransformFunction.class, + expression.getFunction().getArguments(), + ArrayLiteralTransformFunction::new); + } + TransformFunction transformFunction; Class transformFunctionClass = TRANSFORM_FUNCTION_MAP.get(functionName); if (transformFunctionClass != null) { // Transform function try { - transformFunction = transformFunctionClass.newInstance(); + transformFunction = transformFunctionClass.getDeclaredConstructor().newInstance(); } catch (Exception e) { throw new RuntimeException("Caught exception while constructing transform function: " + functionName, e); } @@ -309,10 +324,10 @@ public static TransformFunction get(@Nullable QueryContext queryContext, Express List transformFunctionArguments = new ArrayList<>(numArguments); for (ExpressionContext argument : arguments) { - transformFunctionArguments.add(TransformFunctionFactory.get(queryContext, argument, dataSourceMap)); + transformFunctionArguments.add(TransformFunctionFactory.get(argument, columnContextMap, queryContext)); } try { - transformFunction.init(transformFunctionArguments, dataSourceMap); + transformFunction.init(transformFunctionArguments, columnContextMap, queryContext.isNullHandlingEnabled()); } catch (Exception e) { throw new BadQueryRequestException("Caught exception while initializing transform function: " + functionName, e); @@ -320,16 +335,40 @@ public static TransformFunction get(@Nullable QueryContext queryContext, Express return transformFunction; case IDENTIFIER: String columnName = expression.getIdentifier(); - return new IdentifierTransformFunction(columnName, dataSourceMap.get(columnName)); + return new IdentifierTransformFunction(columnName, columnContextMap.get(columnName)); case LITERAL: - return queryContext == null ? new LiteralTransformFunction(expression.getLiteral()) - : queryContext.getOrComputeSharedValue(LiteralTransformFunction.class, expression.getLiteral(), - LiteralTransformFunction::new); + LiteralContext literal = expression.getLiteral(); + if (literal.isSingleValue()) { + return queryContext.getOrComputeSharedValue(LiteralTransformFunction.class, literal, + LiteralTransformFunction::new); + } else { + return queryContext.getOrComputeSharedValue(ArrayLiteralTransformFunction.class, literal, + ArrayLiteralTransformFunction::new); + } default: throw new IllegalStateException(); } } + @VisibleForTesting + public static TransformFunction get(ExpressionContext expression, Map dataSourceMap) { + Map columnContextMap = new HashMap<>(HashUtil.getHashMapCapacity(dataSourceMap.size())); + dataSourceMap.forEach((k, v) -> columnContextMap.put(k, ColumnContext.fromDataSource(v))); + QueryContext dummy = QueryContextConverterUtils.getQueryContext( + CalciteSqlParser.compileToPinotQuery("SELECT * from testTable;")); + return get(expression, columnContextMap, dummy); + } + + @VisibleForTesting + public static TransformFunction getNullHandlingEnabled(ExpressionContext expression, + Map dataSourceMap) { + Map columnContextMap = new HashMap<>(HashUtil.getHashMapCapacity(dataSourceMap.size())); + dataSourceMap.forEach((k, v) -> columnContextMap.put(k, ColumnContext.fromDataSource(v))); + QueryContext dummy = QueryContextConverterUtils.getQueryContext( + CalciteSqlParser.compileToPinotQuery("SET enableNullHandling = true; SELECT * from testTable;")); + return get(expression, columnContextMap, dummy); + } + /** * Converts the transform function name into its canonical form * diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TrigonometricTransformFunctions.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TrigonometricTransformFunctions.java index c7febb5973f7..0771d0488ff0 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TrigonometricTransformFunctions.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TrigonometricTransformFunctions.java @@ -21,9 +21,9 @@ import com.google.common.base.Preconditions; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; public class TrigonometricTransformFunctions { @@ -38,7 +38,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); // Check that there are more than 1 arguments if (arguments.size() != 2) { throw new IllegalArgumentException("Exactly 2 arguments are required for Atan2 transform function"); @@ -48,7 +49,9 @@ public void init(List arguments, Map data _rightTransformFunction = arguments.get(1); Preconditions.checkArgument( _leftTransformFunction.getResultMetadata().isSingleValue() || _rightTransformFunction.getResultMetadata() - .isSingleValue(), "Argument must be single-valued for transform function: %s", getName()); + .isSingleValue() || _leftTransformFunction.getResultMetadata().getDataType().isUnknown() + || _rightTransformFunction.getResultMetadata().getDataType().isUnknown(), + "Argument must be single-valued for transform function: %s", getName()); } @Override @@ -57,19 +60,14 @@ public TransformResultMetadata getResultMetadata() { } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - - if (_doubleValuesSV == null || _doubleValuesSV.length < length) { - _doubleValuesSV = new double[length]; - } - - double[] leftValues = _leftTransformFunction.transformToDoubleValuesSV(projectionBlock); - double[] rightValues = _rightTransformFunction.transformToDoubleValuesSV(projectionBlock); + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initDoubleValuesSV(length); + double[] leftValues = _leftTransformFunction.transformToDoubleValuesSV(valueBlock); + double[] rightValues = _rightTransformFunction.transformToDoubleValuesSV(valueBlock); for (int i = 0; i < length; i++) { _doubleValuesSV[i] = Math.atan2(leftValues[i], rightValues[i]); } - return _doubleValuesSV; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TruncateDecimalTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TruncateDecimalTransformFunction.java index 7815dbf9f6f0..9dfb0c651575 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TruncateDecimalTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/TruncateDecimalTransformFunction.java @@ -23,9 +23,9 @@ import java.math.RoundingMode; import java.util.List; import java.util.Map; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.spi.data.FieldSpec; @@ -42,7 +42,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); int numArguments = arguments.size(); // Check that there are more than 2 arguments or no arguments if (numArguments < 1 || numArguments > 2) { @@ -55,7 +56,7 @@ public void init(List arguments, Map data if (numArguments > 1) { _rightTransformFunction = arguments.get(1); if (_rightTransformFunction instanceof LiteralTransformFunction) { - _scale = Integer.parseInt(((LiteralTransformFunction) _rightTransformFunction).getLiteral()); + _scale = ((LiteralTransformFunction) _rightTransformFunction).getIntLiteral(); _fixedScale = true; } Preconditions.checkArgument( @@ -81,22 +82,19 @@ public TransformResultMetadata getResultMetadata() { } @Override - public double[] transformToDoubleValuesSV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); - if (_doubleValuesSV == null) { - _doubleValuesSV = new double[length]; - } - double[] leftValues = _leftTransformFunction.transformToDoubleValuesSV(projectionBlock); + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initDoubleValuesSV(length); + double[] leftValues = _leftTransformFunction.transformToDoubleValuesSV(valueBlock); if (_fixedScale) { for (int i = 0; i < length; i++) { - _doubleValuesSV[i] = BigDecimal.valueOf(leftValues[i]) - .setScale(_scale, RoundingMode.DOWN).doubleValue(); + _doubleValuesSV[i] = BigDecimal.valueOf(leftValues[i]).setScale(_scale, RoundingMode.DOWN).doubleValue(); } } else if (_rightTransformFunction != null) { - int[] rightValues = _rightTransformFunction.transformToIntValuesSV(projectionBlock); + int[] rightValues = _rightTransformFunction.transformToIntValuesSV(valueBlock); for (int i = 0; i < length; i++) { - _doubleValuesSV[i] = BigDecimal.valueOf(leftValues[i]) - .setScale(rightValues[i], RoundingMode.DOWN).doubleValue(); + _doubleValuesSV[i] = + BigDecimal.valueOf(leftValues[i]).setScale(rightValues[i], RoundingMode.DOWN).doubleValue(); } } else { for (int i = 0; i < length; i++) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ValueInTransformFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ValueInTransformFunction.java index 616be3b8662d..c46f894d5af5 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ValueInTransformFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/ValueInTransformFunction.java @@ -39,13 +39,19 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import javax.annotation.Nullable; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.spi.data.FieldSpec.DataType; +/** + * This class implements the valueIn function for multi-valued columns. It takes at least 2 arguments, where the first + * argument is a multi-valued column, and the following arguments are constant values. The transform function will + * filter the values from the multi-valued column with the given constant values. + */ public class ValueInTransformFunction extends BaseTransformFunction { public static final String FUNCTION_NAME = "valueIn"; @@ -54,7 +60,7 @@ public class ValueInTransformFunction extends BaseTransformFunction { private Dictionary _dictionary; private IntSet _dictIdSet; - private int[][] _dictIds; + private int[][] _dictIdsMV; private IntSet _intValueSet; private LongSet _longValueSet; private FloatSet _floatValueSet; @@ -67,7 +73,8 @@ public String getName() { } @Override - public void init(List arguments, Map dataSourceMap) { + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); // Check that there are more than 1 arguments int numArguments = arguments.size(); if (numArguments < 2) { @@ -87,7 +94,7 @@ public void init(List arguments, Map data // Collect all values for the VALUE_IN transform function _stringValueSet = new HashSet<>(numArguments - 1); for (int i = 1; i < numArguments; i++) { - _stringValueSet.add(((LiteralTransformFunction) arguments.get(i)).getLiteral()); + _stringValueSet.add(((LiteralTransformFunction) arguments.get(i)).getStringLiteral()); } } @@ -96,14 +103,18 @@ public TransformResultMetadata getResultMetadata() { return _resultMetadata; } + @Nullable @Override public Dictionary getDictionary() { return _dictionary; } @Override - public int[][] transformToDictIdsMV(ProjectionBlock projectionBlock) { - int length = projectionBlock.getNumDocs(); + public int[][] transformToDictIdsMV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + if (_dictIdsMV == null || _dictIdsMV.length < length) { + _dictIdsMV = new int[length][]; + } if (_dictIdSet == null) { _dictIdSet = new IntOpenHashSet(); assert _dictionary != null; @@ -113,33 +124,28 @@ public int[][] transformToDictIdsMV(ProjectionBlock projectionBlock) { _dictIdSet.add(dictId); } } - if (_dictIds == null) { - _dictIds = new int[length][]; - } } - int[][] unFilteredDictIds = _mainTransformFunction.transformToDictIdsMV(projectionBlock); + int[][] unFilteredDictIds = _mainTransformFunction.transformToDictIdsMV(valueBlock); for (int i = 0; i < length; i++) { - _dictIds[i] = filterInts(_dictIdSet, unFilteredDictIds[i]); + _dictIdsMV[i] = filterInts(_dictIdSet, unFilteredDictIds[i]); } - return _dictIds; + return _dictIdsMV; } @Override - public int[][] transformToIntValuesMV(ProjectionBlock projectionBlock) { + public int[][] transformToIntValuesMV(ValueBlock valueBlock) { if (_dictionary != null || _resultMetadata.getDataType().getStoredType() != DataType.INT) { - return super.transformToIntValuesMV(projectionBlock); + return super.transformToIntValuesMV(valueBlock); } - int length = projectionBlock.getNumDocs(); + int length = valueBlock.getNumDocs(); + initIntValuesMV(length); if (_intValueSet == null) { _intValueSet = new IntOpenHashSet(); for (String inValue : _stringValueSet) { _intValueSet.add(Integer.parseInt(inValue)); } - if (_intValuesMV == null) { - _intValuesMV = new int[length][]; - } } - int[][] unFilteredIntValues = _mainTransformFunction.transformToIntValuesMV(projectionBlock); + int[][] unFilteredIntValues = _mainTransformFunction.transformToIntValuesMV(valueBlock); for (int i = 0; i < length; i++) { _intValuesMV[i] = filterInts(_intValueSet, unFilteredIntValues[i]); } @@ -147,21 +153,19 @@ public int[][] transformToIntValuesMV(ProjectionBlock projectionBlock) { } @Override - public long[][] transformToLongValuesMV(ProjectionBlock projectionBlock) { + public long[][] transformToLongValuesMV(ValueBlock valueBlock) { if (_dictionary != null || _resultMetadata.getDataType().getStoredType() != DataType.LONG) { - return super.transformToLongValuesMV(projectionBlock); + return super.transformToLongValuesMV(valueBlock); } - int length = projectionBlock.getNumDocs(); + int length = valueBlock.getNumDocs(); + initLongValuesMV(length); if (_longValueSet == null) { _longValueSet = new LongOpenHashSet(); for (String inValue : _stringValueSet) { _longValueSet.add(Long.parseLong(inValue)); } - if (_longValuesMV == null) { - _longValuesMV = new long[length][]; - } } - long[][] unFilteredLongValues = _mainTransformFunction.transformToLongValuesMV(projectionBlock); + long[][] unFilteredLongValues = _mainTransformFunction.transformToLongValuesMV(valueBlock); for (int i = 0; i < length; i++) { _longValuesMV[i] = filterLongs(_longValueSet, unFilteredLongValues[i]); } @@ -169,21 +173,19 @@ public long[][] transformToLongValuesMV(ProjectionBlock projectionBlock) { } @Override - public float[][] transformToFloatValuesMV(ProjectionBlock projectionBlock) { + public float[][] transformToFloatValuesMV(ValueBlock valueBlock) { if (_dictionary != null || _resultMetadata.getDataType().getStoredType() != DataType.FLOAT) { - return super.transformToFloatValuesMV(projectionBlock); + return super.transformToFloatValuesMV(valueBlock); } - int length = projectionBlock.getNumDocs(); + int length = valueBlock.getNumDocs(); + initFloatValuesMV(length); if (_floatValueSet == null) { _floatValueSet = new FloatOpenHashSet(); for (String inValue : _stringValueSet) { _floatValueSet.add(Float.parseFloat(inValue)); } - if (_floatValuesMV == null) { - _floatValuesMV = new float[length][]; - } } - float[][] unFilteredFloatValues = _mainTransformFunction.transformToFloatValuesMV(projectionBlock); + float[][] unFilteredFloatValues = _mainTransformFunction.transformToFloatValuesMV(valueBlock); for (int i = 0; i < length; i++) { _floatValuesMV[i] = filterFloats(_floatValueSet, unFilteredFloatValues[i]); } @@ -191,21 +193,19 @@ public float[][] transformToFloatValuesMV(ProjectionBlock projectionBlock) { } @Override - public double[][] transformToDoubleValuesMV(ProjectionBlock projectionBlock) { + public double[][] transformToDoubleValuesMV(ValueBlock valueBlock) { if (_dictionary != null || _resultMetadata.getDataType().getStoredType() != DataType.DOUBLE) { - return super.transformToDoubleValuesMV(projectionBlock); + return super.transformToDoubleValuesMV(valueBlock); } - int length = projectionBlock.getNumDocs(); + int length = valueBlock.getNumDocs(); + initDoubleValuesMV(length); if (_doubleValueSet == null) { _doubleValueSet = new DoubleOpenHashSet(); for (String inValue : _stringValueSet) { _doubleValueSet.add(Double.parseDouble(inValue)); } - if (_doubleValuesMV == null) { - _doubleValuesMV = new double[length][]; - } } - double[][] unFilteredDoubleValues = _mainTransformFunction.transformToDoubleValuesMV(projectionBlock); + double[][] unFilteredDoubleValues = _mainTransformFunction.transformToDoubleValuesMV(valueBlock); for (int i = 0; i < length; i++) { _doubleValuesMV[i] = filterDoubles(_doubleValueSet, unFilteredDoubleValues[i]); } @@ -213,15 +213,13 @@ public double[][] transformToDoubleValuesMV(ProjectionBlock projectionBlock) { } @Override - public String[][] transformToStringValuesMV(ProjectionBlock projectionBlock) { + public String[][] transformToStringValuesMV(ValueBlock valueBlock) { if (_dictionary != null || _resultMetadata.getDataType().getStoredType() != DataType.STRING) { - return super.transformToStringValuesMV(projectionBlock); - } - int length = projectionBlock.getNumDocs(); - if (_stringValuesMV == null) { - _stringValuesMV = new String[length][]; + return super.transformToStringValuesMV(valueBlock); } - String[][] unFilteredStringValues = _mainTransformFunction.transformToStringValuesMV(projectionBlock); + int length = valueBlock.getNumDocs(); + initStringValuesMV(length); + String[][] unFilteredStringValues = _mainTransformFunction.transformToStringValuesMV(valueBlock); for (int i = 0; i < length; i++) { _stringValuesMV[i] = filterStrings(_stringValueSet, unFilteredStringValues[i]); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/VectorTransformFunctions.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/VectorTransformFunctions.java new file mode 100644 index 000000000000..d5d7508b0cd9 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/function/VectorTransformFunctions.java @@ -0,0 +1,229 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.transform.function; + +import com.google.common.base.Preconditions; +import java.util.List; +import java.util.Map; +import org.apache.pinot.common.function.scalar.VectorFunctions; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; +import org.apache.pinot.core.operator.transform.TransformResultMetadata; + + +public class VectorTransformFunctions { + public static class CosineDistanceTransformFunction extends VectorDistanceTransformFunction { + public static final String FUNCTION_NAME = "cosineDistance"; + private Double _defaultValue = null; + + @Override + protected void checkArgumentSize(List arguments) { + // Check that there are 2 or 3 arguments + if (arguments.size() < 2 || arguments.size() > 3) { + throw new IllegalArgumentException("2 or 3 arguments are required for CosineDistance function"); + } + } + + @Override + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); + if (arguments.size() == 3) { + _defaultValue = ((LiteralTransformFunction) arguments.get(2)).getDoubleLiteral(); + } + } + + @Override + public String getName() { + return FUNCTION_NAME; + } + + @Override + protected double computeDistance(float[] vector1, float[] vector2) { + if (_defaultValue != null) { + return VectorFunctions.cosineDistance(vector1, vector2, _defaultValue); + } + return VectorFunctions.cosineDistance(vector1, vector2); + } + } + + public static class InnerProductTransformFunction extends VectorDistanceTransformFunction { + public static final String FUNCTION_NAME = "innerProduct"; + + @Override + public String getName() { + return FUNCTION_NAME; + } + + @Override + protected double computeDistance(float[] vector1, float[] vector2) { + return VectorFunctions.innerProduct(vector1, vector2); + } + } + + public static class L1DistanceTransformFunction extends VectorDistanceTransformFunction { + public static final String FUNCTION_NAME = "l1Distance"; + + @Override + public String getName() { + return FUNCTION_NAME; + } + + @Override + protected double computeDistance(float[] vector1, float[] vector2) { + return VectorFunctions.l1Distance(vector1, vector2); + } + } + + public static class L2DistanceTransformFunction extends VectorDistanceTransformFunction { + public static final String FUNCTION_NAME = "l2Distance"; + + @Override + public String getName() { + return FUNCTION_NAME; + } + + @Override + protected double computeDistance(float[] vector1, float[] vector2) { + return VectorFunctions.l2Distance(vector1, vector2); + } + } + + public static abstract class VectorDistanceTransformFunction extends BaseTransformFunction { + + protected TransformFunction _leftTransformFunction; + protected TransformFunction _rightTransformFunction; + + @Override + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); + checkArgumentSize(arguments); + _leftTransformFunction = arguments.get(0); + _rightTransformFunction = arguments.get(1); + Preconditions.checkArgument( + !_leftTransformFunction.getResultMetadata().isSingleValue() + && !_rightTransformFunction.getResultMetadata().isSingleValue(), + "Argument must be multi-valued float vector for vector distance transform function: %s", getName()); + } + + protected void checkArgumentSize(List arguments) { + // Check that there are 2 arguments + if (arguments.size() != 2) { + throw new IllegalArgumentException("Exactly 2 arguments are required for Vector transform function"); + } + } + + @Override + public TransformResultMetadata getResultMetadata() { + return DOUBLE_SV_NO_DICTIONARY_METADATA; + } + + @Override + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initDoubleValuesSV(length); + float[][] leftValues = _leftTransformFunction.transformToFloatValuesMV(valueBlock); + float[][] rightValues = _rightTransformFunction.transformToFloatValuesMV(valueBlock); + for (int i = 0; i < length; i++) { + _doubleValuesSV[i] = computeDistance(leftValues[i], rightValues[i]); + } + return _doubleValuesSV; + } + + protected abstract double computeDistance(float[] vector1, float[] vector2); + } + + public static class VectorDimsTransformFunction extends BaseTransformFunction { + public static final String FUNCTION_NAME = "vectorDims"; + + private TransformFunction _transformFunction; + + @Override + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); + // Check that there is exact 1 argument + if (arguments.size() != 1) { + throw new IllegalArgumentException("Exactly 1 argument is required for Vector transform function"); + } + _transformFunction = arguments.get(0); + Preconditions.checkArgument(!_transformFunction.getResultMetadata().isSingleValue(), + "Argument must be multi-valued float vector for vector distance transform function: %s", getName()); + } + + @Override + public String getName() { + return FUNCTION_NAME; + } + + @Override + public TransformResultMetadata getResultMetadata() { + return INT_SV_NO_DICTIONARY_METADATA; + } + + @Override + public int[] transformToIntValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initIntValuesSV(length); + float[][] values = _transformFunction.transformToFloatValuesMV(valueBlock); + for (int i = 0; i < length; i++) { + _intValuesSV[i] = VectorFunctions.vectorDims(values[i]); + } + return _intValuesSV; + } + } + + public static class VectorNormTransformFunction extends BaseTransformFunction { + public static final String FUNCTION_NAME = "vectorNorm"; + + private TransformFunction _transformFunction; + + @Override + public void init(List arguments, Map columnContextMap) { + super.init(arguments, columnContextMap); + // Check that there is exact 1 argument + if (arguments.size() != 1) { + throw new IllegalArgumentException("Exactly 1 argument is required for Vector transform function"); + } + + _transformFunction = arguments.get(0); + Preconditions.checkArgument(!_transformFunction.getResultMetadata().isSingleValue(), + "Argument must be multi-valued float vector for vector distance transform function: %s", getName()); + } + + @Override + public String getName() { + return FUNCTION_NAME; + } + + @Override + public TransformResultMetadata getResultMetadata() { + return DOUBLE_SV_NO_DICTIONARY_METADATA; + } + + @Override + public double[] transformToDoubleValuesSV(ValueBlock valueBlock) { + int length = valueBlock.getNumDocs(); + initDoubleValuesSV(length); + float[][] values = _transformFunction.transformToFloatValuesMV(valueBlock); + for (int i = 0; i < length; i++) { + _doubleValuesSV[i] = VectorFunctions.vectorNorm(values[i]); + } + return _doubleValuesSV; + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/transformer/datetimehop/BaseDateTimeWindowHopTransformer.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/transformer/datetimehop/BaseDateTimeWindowHopTransformer.java new file mode 100644 index 000000000000..c05b6f1001c0 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/transformer/datetimehop/BaseDateTimeWindowHopTransformer.java @@ -0,0 +1,89 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.operator.transform.transformer.datetimehop; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.apache.pinot.core.operator.transform.transformer.DataTransformer; +import org.apache.pinot.spi.data.DateTimeFormatSpec; +import org.apache.pinot.spi.data.DateTimeFormatUnitSpec; +import org.apache.pinot.spi.data.DateTimeGranularitySpec; +import org.joda.time.DateTime; +import org.joda.time.format.DateTimeFormatter; + + +public abstract class BaseDateTimeWindowHopTransformer implements DataTransformer { + protected final long _hopWindowSizeMillis; + private final int _inputTimeSize; + private final TimeUnit _inputTimeUnit; + private final DateTimeFormatter _inputDateTimeFormatter; + private final int _outputTimeSize; + private final DateTimeFormatUnitSpec.DateTimeTransformUnit _outputTimeUnit; + private final DateTimeFormatter _outputDateTimeFormatter; + private final long _outputGranularityMillis; + + public BaseDateTimeWindowHopTransformer(DateTimeFormatSpec inputFormat, DateTimeFormatSpec outputFormat, + DateTimeGranularitySpec outputGranularity, DateTimeGranularitySpec hopWindowSize) { + _inputTimeSize = inputFormat.getColumnSize(); + _inputTimeUnit = inputFormat.getColumnUnit(); + _inputDateTimeFormatter = inputFormat.getDateTimeFormatter(); + _outputTimeSize = outputFormat.getColumnSize(); + _outputTimeUnit = outputFormat.getColumnDateTimeTransformUnit(); + _outputDateTimeFormatter = outputFormat.getDateTimeFormatter(); + _outputGranularityMillis = outputGranularity.granularityToMillis(); + _hopWindowSizeMillis = hopWindowSize.granularityToMillis(); + } + + protected long transformEpochToMillis(long epochTime) { + return _inputTimeUnit.toMillis(epochTime * _inputTimeSize); + } + + protected long transformSDFToMillis(String sdfTime) { + return _inputDateTimeFormatter.parseMillis(sdfTime); + } + + protected long transformMillisToEpoch(long millisSinceEpoch) { + return _outputTimeUnit.fromMillis(millisSinceEpoch) / _outputTimeSize; + } + + protected String transformMillisToSDF(long millisSinceEpoch) { + return _outputDateTimeFormatter.print(new DateTime(millisSinceEpoch)); + } + + protected long transformToOutputGranularity(long millisSinceEpoch) { + return (millisSinceEpoch / _outputGranularityMillis) * _outputGranularityMillis; + } + + protected List hopWindows(long millisSinceEpoch) { + List hops = new ArrayList<>(); + long totalHopMillis = _hopWindowSizeMillis; + long granularityMillis = _outputGranularityMillis; + + long adjustedMillis = (millisSinceEpoch / granularityMillis) * granularityMillis; + + // Start from the adjusted timestamp and decrement by the hop until we've covered the entire window duration + for (long currentMillis = adjustedMillis; currentMillis > millisSinceEpoch - totalHopMillis; + currentMillis -= granularityMillis) { + hops.add(currentMillis); + } + return hops; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/transformer/datetimehop/DateTimeWindowHopTransformerFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/transformer/datetimehop/DateTimeWindowHopTransformerFactory.java new file mode 100644 index 000000000000..3dba64fd9ff5 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/transformer/datetimehop/DateTimeWindowHopTransformerFactory.java @@ -0,0 +1,65 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.operator.transform.transformer.datetimehop; + +import org.apache.pinot.spi.data.DateTimeFieldSpec; +import org.apache.pinot.spi.data.DateTimeFieldSpec.TimeFormat; +import org.apache.pinot.spi.data.DateTimeFormatSpec; +import org.apache.pinot.spi.data.DateTimeGranularitySpec; + + +public class DateTimeWindowHopTransformerFactory { + + private static final TimeFormat EPOCH = DateTimeFieldSpec.TimeFormat.EPOCH; + private static final TimeFormat TIMESTAMP = DateTimeFieldSpec.TimeFormat.TIMESTAMP; + + private DateTimeWindowHopTransformerFactory() { + } + + public static BaseDateTimeWindowHopTransformer getDateTimeTransformer(String inputFormatStr, String outputFormatStr, + String outputGranularityStr, String hopSizeStr) { + DateTimeFormatSpec inputFormatSpec = new DateTimeFormatSpec(inputFormatStr); + DateTimeFormatSpec outputFormatSpec = new DateTimeFormatSpec(outputFormatStr); + DateTimeGranularitySpec outputGranularity = new DateTimeGranularitySpec(outputGranularityStr); + DateTimeGranularitySpec hopSizeFormat = new DateTimeGranularitySpec(hopSizeStr); + + TimeFormat inputFormat = inputFormatSpec.getTimeFormat(); + TimeFormat outputFormat = outputFormatSpec.getTimeFormat(); + + if (isEpochOrTimestamp(inputFormat) && isEpochOrTimestamp(outputFormat)) { + return new EpochToEpochWindowHopTransformer(inputFormatSpec, outputFormatSpec, outputGranularity, hopSizeFormat); + } else if (isEpochOrTimestamp(inputFormat) && isStringFormat(outputFormat)) { + return new EpochToSDFHopWindowTransformer(inputFormatSpec, outputFormatSpec, outputGranularity, hopSizeFormat); + } else if (isStringFormat(inputFormat) && isEpochOrTimestamp(outputFormat)) { + return new SDFToEpochWindowHopTransformer(inputFormatSpec, outputFormatSpec, outputGranularity, hopSizeFormat); + } else if (isStringFormat(inputFormat) && isStringFormat(outputFormat)) { + return new SDFToSDFWindowHopTransformer(inputFormatSpec, outputFormatSpec, outputGranularity, hopSizeFormat); + } + throw new IllegalArgumentException("Wrong inputFormat: " + inputFormat + " outputFormat: " + outputFormat); + } + + private static boolean isEpochOrTimestamp(TimeFormat format) { + return format == EPOCH || format == TIMESTAMP; + } + + private static boolean isStringFormat(TimeFormat format) { + return format == TimeFormat.SIMPLE_DATE_FORMAT; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/transformer/datetimehop/EpochToEpochWindowHopTransformer.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/transformer/datetimehop/EpochToEpochWindowHopTransformer.java new file mode 100644 index 000000000000..943c02f8772d --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/transformer/datetimehop/EpochToEpochWindowHopTransformer.java @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.operator.transform.transformer.datetimehop; + +import java.util.List; +import org.apache.pinot.spi.data.DateTimeFormatSpec; +import org.apache.pinot.spi.data.DateTimeGranularitySpec; + + +public class EpochToEpochWindowHopTransformer extends BaseDateTimeWindowHopTransformer { + public EpochToEpochWindowHopTransformer(DateTimeFormatSpec inputFormat, DateTimeFormatSpec outputFormat, + DateTimeGranularitySpec outputGranularity, DateTimeGranularitySpec hopSize) { + super(inputFormat, outputFormat, outputGranularity, hopSize); + } + + @Override + public void transform(long[] input, long[][] output, int length) { + for (int i = 0; i < length; i++) { + long epochTime = input[i]; + long millisSinceEpoch = transformEpochToMillis(epochTime); + List hopWindows = hopWindows(millisSinceEpoch); + + long[] transformedArray = new long[hopWindows.size()]; + for (int j = 0; j < hopWindows.size(); j++) { + long millis = hopWindows.get(j); + transformedArray[j] = transformMillisToEpoch(millis); + } + output[i] = transformedArray; + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/transformer/datetimehop/EpochToSDFHopWindowTransformer.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/transformer/datetimehop/EpochToSDFHopWindowTransformer.java new file mode 100644 index 000000000000..331cf368f471 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/transformer/datetimehop/EpochToSDFHopWindowTransformer.java @@ -0,0 +1,49 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.operator.transform.transformer.datetimehop; + +import java.util.List; +import org.apache.pinot.spi.data.DateTimeFormatSpec; +import org.apache.pinot.spi.data.DateTimeGranularitySpec; + + +public class EpochToSDFHopWindowTransformer extends BaseDateTimeWindowHopTransformer { + + public EpochToSDFHopWindowTransformer(DateTimeFormatSpec inputFormat, DateTimeFormatSpec outputFormat, + DateTimeGranularitySpec outputGranularity, DateTimeGranularitySpec hopWindowSize) { + super(inputFormat, outputFormat, outputGranularity, hopWindowSize); + } + + @Override + public void transform(long[] input, String[][] output, int length) { + for (int i = 0; i < length; i++) { + long epochTime = input[i]; + long millisSinceEpoch = transformEpochToMillis(epochTime); + List hopWindows = hopWindows(millisSinceEpoch); + + String[] transformedArray = new String[hopWindows.size()]; + for (int j = 0; j < hopWindows.size(); j++) { + long millis = hopWindows.get(j); + transformedArray[j] = transformMillisToSDF(millis); + } + output[i] = transformedArray; + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/transformer/datetimehop/SDFToEpochWindowHopTransformer.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/transformer/datetimehop/SDFToEpochWindowHopTransformer.java new file mode 100644 index 000000000000..e43ef6cf79f2 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/transformer/datetimehop/SDFToEpochWindowHopTransformer.java @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.operator.transform.transformer.datetimehop; + +import java.util.List; +import org.apache.pinot.spi.data.DateTimeFormatSpec; +import org.apache.pinot.spi.data.DateTimeGranularitySpec; + + +public class SDFToEpochWindowHopTransformer extends BaseDateTimeWindowHopTransformer { + + public SDFToEpochWindowHopTransformer(DateTimeFormatSpec inputFormat, DateTimeFormatSpec outputFormat, + DateTimeGranularitySpec outputGranularity, DateTimeGranularitySpec hopWindowSize) { + super(inputFormat, outputFormat, outputGranularity, hopWindowSize); + } + + @Override + public void transform(String[] input, long[][] output, int length) { + for (int i = 0; i < length; i++) { + String sdfTime = input[i]; + long millisSinceEpoch = transformSDFToMillis(sdfTime); + List hopWindows = hopWindows(millisSinceEpoch); + + long[] epochTimes = new long[hopWindows.size()]; + for (int j = 0; j < hopWindows.size(); j++) { + epochTimes[j] = transformMillisToEpoch(hopWindows.get(j)); + } + output[i] = epochTimes; + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/transformer/datetimehop/SDFToSDFWindowHopTransformer.java b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/transformer/datetimehop/SDFToSDFWindowHopTransformer.java new file mode 100644 index 000000000000..5016a47033f1 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/operator/transform/transformer/datetimehop/SDFToSDFWindowHopTransformer.java @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.operator.transform.transformer.datetimehop; + +import java.util.List; +import org.apache.pinot.spi.data.DateTimeFormatSpec; +import org.apache.pinot.spi.data.DateTimeGranularitySpec; + + +public class SDFToSDFWindowHopTransformer extends BaseDateTimeWindowHopTransformer { + + public SDFToSDFWindowHopTransformer(DateTimeFormatSpec inputFormat, DateTimeFormatSpec outputFormat, + DateTimeGranularitySpec outputGranularity, DateTimeGranularitySpec hopWindowSize) { + super(inputFormat, outputFormat, outputGranularity, hopWindowSize); + } + + @Override + public void transform(String[] input, String[][] output, int length) { + for (int i = 0; i < length; i++) { + String sdfTime = input[i]; + long millisSinceEpoch = transformSDFToMillis(sdfTime); + List hopWindows = hopWindows(millisSinceEpoch); + + String[] transformedStrings = new String[hopWindows.size()]; + for (int j = 0; j < hopWindows.size(); j++) { + transformedStrings[j] = transformMillisToSDF(hopWindows.get(j)); + } + output[i] = transformedStrings; + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/periodictask/PeriodicTaskScheduler.java b/pinot-core/src/main/java/org/apache/pinot/core/periodictask/PeriodicTaskScheduler.java index 68d3c9906ac1..977f72effa09 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/periodictask/PeriodicTaskScheduler.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/periodictask/PeriodicTaskScheduler.java @@ -149,7 +149,7 @@ public void scheduleNow(String periodicTaskName, Properties periodicTaskProperti // level) whether the periodic task exists. PeriodicTask periodicTask = getPeriodicTask(periodicTaskName); if (periodicTask == null) { - LOGGER.error("Unknown Periodic Task " + periodicTaskName); + LOGGER.error("Unknown Periodic Task {}", periodicTaskName); return; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/plan/AcquireReleaseColumnsSegmentPlanNode.java b/pinot-core/src/main/java/org/apache/pinot/core/plan/AcquireReleaseColumnsSegmentPlanNode.java index 73406c20fa60..0d9ed602f8ab 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/plan/AcquireReleaseColumnsSegmentPlanNode.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/plan/AcquireReleaseColumnsSegmentPlanNode.java @@ -20,7 +20,7 @@ import org.apache.pinot.core.operator.AcquireReleaseColumnsSegmentOperator; import org.apache.pinot.segment.spi.FetchContext; -import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; /** @@ -36,13 +36,13 @@ public class AcquireReleaseColumnsSegmentPlanNode implements PlanNode { private final PlanNode _childPlanNode; - private final IndexSegment _indexSegment; + private final SegmentContext _segmentContext; private final FetchContext _fetchContext; - public AcquireReleaseColumnsSegmentPlanNode(PlanNode childPlanNode, IndexSegment indexSegment, + public AcquireReleaseColumnsSegmentPlanNode(PlanNode childPlanNode, SegmentContext segmentContext, FetchContext fetchContext) { _childPlanNode = childPlanNode; - _indexSegment = indexSegment; + _segmentContext = segmentContext; _fetchContext = fetchContext; } @@ -52,6 +52,6 @@ public AcquireReleaseColumnsSegmentPlanNode(PlanNode childPlanNode, IndexSegment */ @Override public AcquireReleaseColumnsSegmentOperator run() { - return new AcquireReleaseColumnsSegmentOperator(_childPlanNode, _indexSegment, _fetchContext); + return new AcquireReleaseColumnsSegmentOperator(_childPlanNode, _segmentContext.getIndexSegment(), _fetchContext); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/plan/AggregationPlanNode.java b/pinot-core/src/main/java/org/apache/pinot/core/plan/AggregationPlanNode.java index 58d74fb00f10..2a1321f1b978 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/plan/AggregationPlanNode.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/plan/AggregationPlanNode.java @@ -18,35 +18,24 @@ */ package org.apache.pinot.core.plan; -import java.util.ArrayList; import java.util.EnumSet; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.Set; -import org.apache.commons.lang3.tuple.Pair; import org.apache.pinot.common.request.context.ExpressionContext; -import org.apache.pinot.common.request.context.FilterContext; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.blocks.results.AggregationResultsBlock; import org.apache.pinot.core.operator.filter.BaseFilterOperator; -import org.apache.pinot.core.operator.filter.CombinedFilterOperator; import org.apache.pinot.core.operator.query.AggregationOperator; import org.apache.pinot.core.operator.query.FastFilteredCountOperator; import org.apache.pinot.core.operator.query.FilteredAggregationOperator; import org.apache.pinot.core.operator.query.NonScanBasedAggregationOperator; -import org.apache.pinot.core.operator.transform.TransformOperator; import org.apache.pinot.core.query.aggregation.function.AggregationFunction; import org.apache.pinot.core.query.aggregation.function.AggregationFunctionUtils; +import org.apache.pinot.core.query.aggregation.function.AggregationFunctionUtils.AggregationInfo; import org.apache.pinot.core.query.request.context.QueryContext; -import org.apache.pinot.core.startree.CompositePredicateEvaluator; -import org.apache.pinot.core.startree.StarTreeUtils; -import org.apache.pinot.core.startree.plan.StarTreeTransformPlanNode; import org.apache.pinot.segment.spi.AggregationFunctionType; import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; import org.apache.pinot.segment.spi.datasource.DataSource; -import org.apache.pinot.segment.spi.index.startree.AggregationFunctionColumnPair; -import org.apache.pinot.segment.spi.index.startree.StarTreeV2; import static org.apache.pinot.segment.spi.AggregationFunctionType.*; @@ -60,107 +49,36 @@ public class AggregationPlanNode implements PlanNode { private static final EnumSet DICTIONARY_BASED_FUNCTIONS = EnumSet.of(MIN, MINMV, MAX, MAXMV, MINMAXRANGE, MINMAXRANGEMV, DISTINCTCOUNT, DISTINCTCOUNTMV, DISTINCTCOUNTHLL, DISTINCTCOUNTHLLMV, DISTINCTCOUNTRAWHLL, DISTINCTCOUNTRAWHLLMV, SEGMENTPARTITIONEDDISTINCTCOUNT, - DISTINCTCOUNTSMARTHLL); + DISTINCTCOUNTSMARTHLL, DISTINCTSUM, DISTINCTAVG, DISTINCTSUMMV, DISTINCTAVGMV, DISTINCTCOUNTHLLPLUS, + DISTINCTCOUNTHLLPLUSMV, DISTINCTCOUNTRAWHLLPLUS, DISTINCTCOUNTRAWHLLPLUSMV); // DISTINCTCOUNT excluded because consuming segment metadata contains unknown cardinality when there is no dictionary private static final EnumSet METADATA_BASED_FUNCTIONS = EnumSet.of(COUNT, MIN, MINMV, MAX, MAXMV, MINMAXRANGE, MINMAXRANGEMV); private final IndexSegment _indexSegment; + private final SegmentContext _segmentContext; private final QueryContext _queryContext; - public AggregationPlanNode(IndexSegment indexSegment, QueryContext queryContext) { - _indexSegment = indexSegment; + public AggregationPlanNode(SegmentContext segmentContext, QueryContext queryContext) { + _indexSegment = segmentContext.getIndexSegment(); + _segmentContext = segmentContext; _queryContext = queryContext; } @Override public Operator run() { assert _queryContext.getAggregationFunctions() != null; - return _queryContext.isHasFilteredAggregations() ? buildFilteredAggOperator() : buildNonFilteredAggOperator(); + return _queryContext.hasFilteredAggregations() ? buildFilteredAggOperator() : buildNonFilteredAggOperator(); } /** * Build the operator to be used for filtered aggregations */ private FilteredAggregationOperator buildFilteredAggOperator() { - int numTotalDocs = _indexSegment.getSegmentMetadata().getTotalDocs(); - // Build the operator chain for the main predicate - Pair filterOperatorPair = buildFilterOperator(_queryContext.getFilter()); - TransformOperator transformOperator = buildTransformOperatorForFilteredAggregates(filterOperatorPair.getRight()); - - return buildFilterOperatorInternal(filterOperatorPair.getRight(), transformOperator, numTotalDocs); - } - - /** - * Build a FilteredAggregationOperator given the parameters. - * @param mainPredicateFilterOperator Filter operator corresponding to the main predicate - * @param mainTransformOperator Transform operator corresponding to the main predicate - * @param numTotalDocs Number of total docs - */ - private FilteredAggregationOperator buildFilterOperatorInternal(BaseFilterOperator mainPredicateFilterOperator, - TransformOperator mainTransformOperator, int numTotalDocs) { - Map, TransformOperator>> filterContextToAggFuncsMap = new HashMap<>(); - List nonFilteredAggregationFunctions = new ArrayList<>(); - List> aggregationFunctions = - _queryContext.getFilteredAggregationFunctions(); - - // For each aggregation function, check if the aggregation function is a filtered agg. - // If it is, populate the corresponding filter operator and corresponding transform operator - for (Pair inputPair : aggregationFunctions) { - if (inputPair.getLeft() != null) { - FilterContext currentFilterExpression = inputPair.getRight(); - if (filterContextToAggFuncsMap.get(currentFilterExpression) != null) { - filterContextToAggFuncsMap.get(currentFilterExpression).getLeft().add(inputPair.getLeft()); - continue; - } - Pair pair = buildFilterOperator(currentFilterExpression); - BaseFilterOperator wrappedFilterOperator = - new CombinedFilterOperator(mainPredicateFilterOperator, pair.getRight(), _queryContext.getQueryOptions()); - TransformOperator newTransformOperator = buildTransformOperatorForFilteredAggregates(wrappedFilterOperator); - // For each transform operator, associate it with the underlying expression. This allows - // fetching the relevant TransformOperator when resolving blocks during aggregation - // execution - List aggFunctionList = new ArrayList<>(); - aggFunctionList.add(inputPair.getLeft()); - filterContextToAggFuncsMap.put(currentFilterExpression, Pair.of(aggFunctionList, newTransformOperator)); - } else { - nonFilteredAggregationFunctions.add(inputPair.getLeft()); - } - } - List> aggToTransformOpList = new ArrayList<>(); - // Convert to array since FilteredAggregationOperator expects it - for (Pair, TransformOperator> pair : filterContextToAggFuncsMap.values()) { - List aggregationFunctionList = pair.getLeft(); - if (aggregationFunctionList == null) { - throw new IllegalStateException("Null aggregation list seen"); - } - aggToTransformOpList.add(Pair.of(aggregationFunctionList.toArray(new AggregationFunction[0]), pair.getRight())); - } - aggToTransformOpList.add( - Pair.of(nonFilteredAggregationFunctions.toArray(new AggregationFunction[0]), mainTransformOperator)); - - return new FilteredAggregationOperator(_queryContext.getAggregationFunctions(), aggToTransformOpList, numTotalDocs); - } - - /** - * Build a filter operator from the given FilterContext. - * - * It returns the FilterPlanNode to allow reusing plan level components such as predicate - * evaluator map - */ - private Pair buildFilterOperator(FilterContext filterContext) { - FilterPlanNode filterPlanNode = new FilterPlanNode(_indexSegment, _queryContext, filterContext); - return Pair.of(filterPlanNode, filterPlanNode.run()); - } - - private TransformOperator buildTransformOperatorForFilteredAggregates(BaseFilterOperator filterOperator) { - AggregationFunction[] aggregationFunctions = _queryContext.getAggregationFunctions(); - Set expressionsToTransform = - AggregationFunctionUtils.collectExpressionsToTransform(aggregationFunctions, null); - - return new TransformPlanNode(_indexSegment, _queryContext, expressionsToTransform, - DocIdSetPlanNode.MAX_DOC_PER_CALL, filterOperator).run(); + return new FilteredAggregationOperator(_queryContext, + AggregationFunctionUtils.buildFilteredAggregationInfos(_segmentContext, _queryContext), + _indexSegment.getSegmentMetadata().getTotalDocs()); } /** @@ -169,20 +87,19 @@ private TransformOperator buildTransformOperatorForFilteredAggregates(BaseFilter * aggregates code will be invoked */ public Operator buildNonFilteredAggOperator() { - assert _queryContext.getAggregationFunctions() != null; - - int numTotalDocs = _indexSegment.getSegmentMetadata().getTotalDocs(); AggregationFunction[] aggregationFunctions = _queryContext.getAggregationFunctions(); + assert aggregationFunctions != null; - FilterPlanNode filterPlanNode = new FilterPlanNode(_indexSegment, _queryContext); + int numTotalDocs = _indexSegment.getSegmentMetadata().getTotalDocs(); + FilterPlanNode filterPlanNode = new FilterPlanNode(_segmentContext, _queryContext); BaseFilterOperator filterOperator = filterPlanNode.run(); - if (canOptimizeFilteredCount(filterOperator, aggregationFunctions) && !_queryContext.isNullHandlingEnabled()) { - return new FastFilteredCountOperator(aggregationFunctions, filterOperator, _indexSegment.getSegmentMetadata()); - } + if (!_queryContext.isNullHandlingEnabled()) { + if (canOptimizeFilteredCount(filterOperator, aggregationFunctions)) { + return new FastFilteredCountOperator(_queryContext, filterOperator, _indexSegment.getSegmentMetadata()); + } - if (filterOperator.isResultMatchingAll() && !_queryContext.isNullHandlingEnabled()) { - if (isFitForNonScanBasedPlan(aggregationFunctions, _indexSegment)) { + if (filterOperator.isResultMatchingAll() && isFitForNonScanBasedPlan(aggregationFunctions, _indexSegment)) { DataSource[] dataSources = new DataSource[aggregationFunctions.length]; for (int i = 0; i < aggregationFunctions.length; i++) { List inputExpressions = aggregationFunctions[i].getInputExpressions(); @@ -191,39 +108,14 @@ public Operator buildNonFilteredAggOperator() { dataSources[i] = _indexSegment.getDataSource(column); } } - return new NonScanBasedAggregationOperator(aggregationFunctions, dataSources, numTotalDocs); - } - } - - // Use star-tree to solve the query if possible - List starTrees = _indexSegment.getStarTrees(); - if (starTrees != null && !_queryContext.isSkipStarTree() && !_queryContext.isNullHandlingEnabled()) { - AggregationFunctionColumnPair[] aggregationFunctionColumnPairs = - StarTreeUtils.extractAggregationFunctionPairs(aggregationFunctions); - if (aggregationFunctionColumnPairs != null) { - Map> predicateEvaluatorsMap = - StarTreeUtils.extractPredicateEvaluatorsMap(_indexSegment, _queryContext.getFilter(), - filterPlanNode.getPredicateEvaluators()); - if (predicateEvaluatorsMap != null) { - for (StarTreeV2 starTreeV2 : starTrees) { - if (StarTreeUtils.isFitForStarTree(starTreeV2.getMetadata(), aggregationFunctionColumnPairs, null, - predicateEvaluatorsMap.keySet())) { - TransformOperator transformOperator = - new StarTreeTransformPlanNode(_queryContext, starTreeV2, aggregationFunctionColumnPairs, null, - predicateEvaluatorsMap).run(); - return new AggregationOperator(aggregationFunctions, transformOperator, numTotalDocs, true); - } - } - } + return new NonScanBasedAggregationOperator(_queryContext, dataSources, numTotalDocs); } } - Set expressionsToTransform = - AggregationFunctionUtils.collectExpressionsToTransform(aggregationFunctions, null); - TransformOperator transformOperator = - new TransformPlanNode(_indexSegment, _queryContext, expressionsToTransform, DocIdSetPlanNode.MAX_DOC_PER_CALL, - filterOperator).run(); - return new AggregationOperator(aggregationFunctions, transformOperator, numTotalDocs, false); + AggregationInfo aggregationInfo = + AggregationFunctionUtils.buildAggregationInfo(_segmentContext, _queryContext, aggregationFunctions, + _queryContext.getFilter(), filterOperator, filterPlanNode.getPredicateEvaluators()); + return new AggregationOperator(_queryContext, aggregationInfo, numTotalDocs); } /** diff --git a/pinot-core/src/main/java/org/apache/pinot/core/plan/CombinePlanNode.java b/pinot-core/src/main/java/org/apache/pinot/core/plan/CombinePlanNode.java index 77288e5acc2a..1ee5de7dc1be 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/plan/CombinePlanNode.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/plan/CombinePlanNode.java @@ -18,28 +18,25 @@ */ package org.apache.pinot.core.plan; -import io.grpc.stub.StreamObserver; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; -import java.util.concurrent.Phaser; -import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; -import org.apache.pinot.common.proto.Server; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.request.context.OrderByExpressionContext; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.operator.combine.AggregationCombineOperator; import org.apache.pinot.core.operator.combine.BaseCombineOperator; -import org.apache.pinot.core.operator.combine.CombineOperatorUtils; import org.apache.pinot.core.operator.combine.DistinctCombineOperator; import org.apache.pinot.core.operator.combine.GroupByCombineOperator; +import org.apache.pinot.core.operator.combine.MinMaxValueBasedSelectionOrderByCombineOperator; import org.apache.pinot.core.operator.combine.SelectionOnlyCombineOperator; import org.apache.pinot.core.operator.combine.SelectionOrderByCombineOperator; import org.apache.pinot.core.operator.streaming.StreamingSelectionOnlyCombineOperator; +import org.apache.pinot.core.query.executor.ResultsBlockStreamer; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.request.context.utils.QueryContextUtils; -import org.apache.pinot.core.util.trace.TraceCallable; +import org.apache.pinot.core.util.QueryMultiThreadingUtils; import org.apache.pinot.spi.exception.BadQueryRequestException; import org.apache.pinot.spi.exception.QueryCancelledException; import org.apache.pinot.spi.trace.InvocationRecording; @@ -58,7 +55,7 @@ public class CombinePlanNode implements PlanNode { private final List _planNodes; private final QueryContext _queryContext; private final ExecutorService _executorService; - private final StreamObserver _streamObserver; + private final ResultsBlockStreamer _streamer; /** * Constructor for the class. @@ -66,14 +63,14 @@ public class CombinePlanNode implements PlanNode { * @param planNodes List of underlying plan nodes * @param queryContext Query context * @param executorService Executor service - * @param streamObserver Optional stream observer for streaming query + * @param streamer Optional results block streamer for streaming query */ public CombinePlanNode(List planNodes, QueryContext queryContext, ExecutorService executorService, - @Nullable StreamObserver streamObserver) { + @Nullable ResultsBlockStreamer streamer) { _planNodes = planNodes; _queryContext = queryContext; _executorService = executorService; - _streamObserver = streamObserver; + _streamer = streamer; } @Override @@ -98,103 +95,63 @@ private BaseCombineOperator getCombineOperator() { // Large number of plan nodes, run them in parallel // NOTE: Even if we get single executor thread, still run it using a separate thread so that the timeout can be // honored - - int maxExecutionThreads = _queryContext.getMaxExecutionThreads(); - if (maxExecutionThreads <= 0) { - maxExecutionThreads = CombineOperatorUtils.MAX_NUM_THREADS_PER_QUERY; - } - int numTasks = - Math.min((numPlanNodes + TARGET_NUM_PLANS_PER_THREAD - 1) / TARGET_NUM_PLANS_PER_THREAD, maxExecutionThreads); + int numTasks = QueryMultiThreadingUtils.getNumTasks(numPlanNodes, TARGET_NUM_PLANS_PER_THREAD, + _queryContext.getMaxExecutionThreads()); recording.setNumTasks(numTasks); - - // Use a Phaser to ensure all the Futures are done (not scheduled, finished or interrupted) before the main thread - // returns. We need to ensure no execution left before the main thread returning because the main thread holds the - // reference to the segments, and if the segments are deleted/refreshed, the segments can be released after the - // main thread returns, which would lead to undefined behavior (even JVM crash) when executing queries against - // them. - Phaser phaser = new Phaser(1); - - // Submit all jobs - Future[] futures = new Future[numTasks]; - for (int i = 0; i < numTasks; i++) { - int index = i; - futures[i] = _executorService.submit(new TraceCallable>() { - @Override - public List callJob() { - try { - // Register the thread to the phaser. - // If the phaser is terminated (returning negative value) when trying to register the thread, that means - // the query execution has timed out, and the main thread has deregistered itself and returned the result. - // Directly return as no execution result will be taken. - if (phaser.register() < 0) { - return Collections.emptyList(); - } - - List operators = new ArrayList<>(); - for (int i = index; i < numPlanNodes; i += numTasks) { - operators.add(_planNodes.get(i).run()); - } - return operators; - } finally { - phaser.arriveAndDeregister(); - } - } - }); - } - - // Get all results - try { - for (Future future : futures) { - List ops = (List) future.get(_queryContext.getEndTimeMs() - System.currentTimeMillis(), - TimeUnit.MILLISECONDS); - operators.addAll(ops); + QueryMultiThreadingUtils.runTasksWithDeadline(numTasks, index -> { + List ops = new ArrayList<>(); + for (int i = index; i < numPlanNodes; i += numTasks) { + ops.add(_planNodes.get(i).run()); + } + return ops; + }, taskRes -> { + if (taskRes != null) { + operators.addAll(taskRes); } - } catch (Exception e) { + }, e -> { // Future object will throw ExecutionException for execution exception, need to check the cause to determine // whether it is caused by bad query Throwable cause = e.getCause(); if (cause instanceof BadQueryRequestException) { throw (BadQueryRequestException) cause; - } else if (e instanceof InterruptedException) { + } + if (e instanceof InterruptedException) { throw new QueryCancelledException("Cancelled while running CombinePlanNode", e); + } + throw new RuntimeException("Caught exception while running CombinePlanNode.", e); + }, _executorService, _queryContext.getEndTimeMs()); + } + + if (_streamer != null && QueryContextUtils.isSelectionOnlyQuery(_queryContext) && _queryContext.getLimit() != 0) { + // Use streaming operator only for non-empty selection-only query + return new StreamingSelectionOnlyCombineOperator(operators, _queryContext, _executorService); + } else { + if (QueryContextUtils.isAggregationQuery(_queryContext)) { + if (_queryContext.getGroupByExpressions() == null) { + // Aggregation only + return new AggregationCombineOperator(operators, _queryContext, _executorService); } else { - throw new RuntimeException("Caught exception while running CombinePlanNode.", e); + // Aggregation group-by + return new GroupByCombineOperator(operators, _queryContext, _executorService); } - } finally { - // Cancel all ongoing jobs - for (Future future : futures) { - if (!future.isDone()) { - future.cancel(true); + } else if (QueryContextUtils.isSelectionQuery(_queryContext)) { + if (_queryContext.getLimit() == 0 || _queryContext.getOrderByExpressions() == null) { + // Selection only + return new SelectionOnlyCombineOperator(operators, _queryContext, _executorService); + } else { + // Selection order-by + List orderByExpressions = _queryContext.getOrderByExpressions(); + assert orderByExpressions != null; + if (orderByExpressions.get(0).getExpression().getType() == ExpressionContext.Type.IDENTIFIER) { + return new MinMaxValueBasedSelectionOrderByCombineOperator(operators, _queryContext, _executorService); + } else { + return new SelectionOrderByCombineOperator(operators, _queryContext, _executorService); } } - // Deregister the main thread and wait for all threads done - phaser.awaitAdvance(phaser.arriveAndDeregister()); - } - } - - if (_streamObserver != null && QueryContextUtils.isSelectionOnlyQuery(_queryContext)) { - // Streaming query (only support selection only) - return new StreamingSelectionOnlyCombineOperator(operators, _queryContext, _executorService, _streamObserver); - } - if (QueryContextUtils.isAggregationQuery(_queryContext)) { - if (_queryContext.getGroupByExpressions() == null) { - // Aggregation only - return new AggregationCombineOperator(operators, _queryContext, _executorService); } else { - // Aggregation group-by - return new GroupByCombineOperator(operators, _queryContext, _executorService); + assert QueryContextUtils.isDistinctQuery(_queryContext); + return new DistinctCombineOperator(operators, _queryContext, _executorService); } - } else if (QueryContextUtils.isSelectionQuery(_queryContext)) { - if (_queryContext.getLimit() == 0 || _queryContext.getOrderByExpressions() == null) { - // Selection only - return new SelectionOnlyCombineOperator(operators, _queryContext, _executorService); - } else { - // Selection order-by - return new SelectionOrderByCombineOperator(operators, _queryContext, _executorService); - } - } else { - assert QueryContextUtils.isDistinctQuery(_queryContext); - return new DistinctCombineOperator(operators, _queryContext, _executorService); } } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/plan/DistinctPlanNode.java b/pinot-core/src/main/java/org/apache/pinot/core/plan/DistinctPlanNode.java index fe68ce88e18e..44ffcd98aa70 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/plan/DistinctPlanNode.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/plan/DistinctPlanNode.java @@ -21,49 +21,44 @@ import java.util.List; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.core.common.Operator; +import org.apache.pinot.core.operator.BaseProjectOperator; import org.apache.pinot.core.operator.blocks.results.DistinctResultsBlock; import org.apache.pinot.core.operator.query.DictionaryBasedDistinctOperator; import org.apache.pinot.core.operator.query.DistinctOperator; -import org.apache.pinot.core.operator.transform.TransformOperator; -import org.apache.pinot.core.query.aggregation.function.AggregationFunction; -import org.apache.pinot.core.query.aggregation.function.DistinctAggregationFunction; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; import org.apache.pinot.segment.spi.datasource.DataSource; -import org.apache.pinot.segment.spi.datasource.DataSourceMetadata; -import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.segment.spi.index.reader.NullValueVectorReader; /** * Execution plan for distinct queries on a single segment. */ -@SuppressWarnings("rawtypes") public class DistinctPlanNode implements PlanNode { private final IndexSegment _indexSegment; + private final SegmentContext _segmentContext; private final QueryContext _queryContext; - public DistinctPlanNode(IndexSegment indexSegment, QueryContext queryContext) { - _indexSegment = indexSegment; + public DistinctPlanNode(SegmentContext segmentContext, QueryContext queryContext) { + _indexSegment = segmentContext.getIndexSegment(); + _segmentContext = segmentContext; _queryContext = queryContext; } @Override public Operator run() { - AggregationFunction[] aggregationFunctions = _queryContext.getAggregationFunctions(); - assert aggregationFunctions != null && aggregationFunctions.length == 1 - && aggregationFunctions[0] instanceof DistinctAggregationFunction; - DistinctAggregationFunction distinctAggregationFunction = (DistinctAggregationFunction) aggregationFunctions[0]; - List expressions = distinctAggregationFunction.getInputExpressions(); + List expressions = _queryContext.getSelectExpressions(); // Use dictionary to solve the query if possible - if (_queryContext.getFilter() == null && !_queryContext.isNullHandlingEnabled() && expressions.size() == 1) { - ExpressionContext expression = expressions.get(0); - if (expression.getType() == ExpressionContext.Type.IDENTIFIER) { - DataSource dataSource = _indexSegment.getDataSource(expression.getIdentifier()); - Dictionary dictionary = dataSource.getDictionary(); - if (dictionary != null) { - DataSourceMetadata dataSourceMetadata = dataSource.getDataSourceMetadata(); + if (_queryContext.getFilter() == null && expressions.size() == 1) { + String column = expressions.get(0).getIdentifier(); + if (column != null) { + DataSource dataSource = _indexSegment.getDataSource(column); + if (dataSource.getDictionary() != null) { + if (!_queryContext.isNullHandlingEnabled()) { + return new DictionaryBasedDistinctOperator(dataSource, _queryContext); + } // If nullHandlingEnabled is set to true, and the column contains null values, call DistinctOperator instead // of DictionaryBasedDistinctOperator since nullValueVectorReader is a form of a filter. // TODO: reserve special value in dictionary (e.g. -1) for null in the future so @@ -71,15 +66,14 @@ public Operator run() { // dictionary-encoded columns. NullValueVectorReader nullValueReader = dataSource.getNullValueVector(); if (nullValueReader == null || nullValueReader.getNullBitmap().isEmpty()) { - return new DictionaryBasedDistinctOperator(dataSourceMetadata.getDataType(), distinctAggregationFunction, - dictionary, dataSourceMetadata.getNumDocs(), _queryContext.isNullHandlingEnabled()); + return new DictionaryBasedDistinctOperator(dataSource, _queryContext); } } } } - TransformOperator transformOperator = - new TransformPlanNode(_indexSegment, _queryContext, expressions, DocIdSetPlanNode.MAX_DOC_PER_CALL).run(); - return new DistinctOperator(_indexSegment, distinctAggregationFunction, transformOperator, _queryContext); + BaseProjectOperator projectOperator = + new ProjectPlanNode(_segmentContext, _queryContext, expressions, DocIdSetPlanNode.MAX_DOC_PER_CALL).run(); + return new DistinctOperator(_indexSegment, _queryContext, projectOperator); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/plan/DocIdSetPlanNode.java b/pinot-core/src/main/java/org/apache/pinot/core/plan/DocIdSetPlanNode.java index 766d80e74dba..0e3558014b58 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/plan/DocIdSetPlanNode.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/plan/DocIdSetPlanNode.java @@ -22,22 +22,21 @@ import org.apache.pinot.core.operator.DocIdSetOperator; import org.apache.pinot.core.operator.filter.BaseFilterOperator; import org.apache.pinot.core.query.request.context.QueryContext; -import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; public class DocIdSetPlanNode implements PlanNode { public static final int MAX_DOC_PER_CALL = 10_000; - - private final IndexSegment _indexSegment; + private final SegmentContext _segmentContext; private final QueryContext _queryContext; private final int _maxDocPerCall; private final BaseFilterOperator _filterOperator; - public DocIdSetPlanNode(IndexSegment indexSegment, QueryContext queryContext, int maxDocPerCall, + public DocIdSetPlanNode(SegmentContext segmentContext, QueryContext queryContext, int maxDocPerCall, @Nullable BaseFilterOperator filterOperator) { assert maxDocPerCall > 0 && maxDocPerCall <= MAX_DOC_PER_CALL; - _indexSegment = indexSegment; + _segmentContext = segmentContext; _queryContext = queryContext; _maxDocPerCall = maxDocPerCall; _filterOperator = filterOperator; @@ -46,7 +45,7 @@ public DocIdSetPlanNode(IndexSegment indexSegment, QueryContext queryContext, in @Override public DocIdSetOperator run() { return new DocIdSetOperator( - _filterOperator != null ? _filterOperator : new FilterPlanNode(_indexSegment, _queryContext).run(), + _filterOperator != null ? _filterOperator : new FilterPlanNode(_segmentContext, _queryContext).run(), _maxDocPerCall); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/plan/FilterPlanNode.java b/pinot-core/src/main/java/org/apache/pinot/core/plan/FilterPlanNode.java index 97a6a420dea6..156624922093 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/plan/FilterPlanNode.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/plan/FilterPlanNode.java @@ -32,6 +32,7 @@ import org.apache.pinot.common.request.context.predicate.RegexpLikePredicate; import org.apache.pinot.common.request.context.predicate.TextContainsPredicate; import org.apache.pinot.common.request.context.predicate.TextMatchPredicate; +import org.apache.pinot.common.request.context.predicate.VectorSimilarityPredicate; import org.apache.pinot.core.geospatial.transform.function.StDistanceFunction; import org.apache.pinot.core.operator.filter.BaseFilterOperator; import org.apache.pinot.core.operator.filter.BitmapBasedFilterOperator; @@ -44,6 +45,7 @@ import org.apache.pinot.core.operator.filter.MatchAllFilterOperator; import org.apache.pinot.core.operator.filter.TextContainsFilterOperator; import org.apache.pinot.core.operator.filter.TextMatchFilterOperator; +import org.apache.pinot.core.operator.filter.VectorSimilarityFilterOperator; import org.apache.pinot.core.operator.filter.predicate.FSTBasedRegexpPredicateEvaluatorFactory; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluatorProvider; @@ -51,54 +53,53 @@ import org.apache.pinot.segment.local.realtime.impl.invertedindex.NativeMutableTextIndex; import org.apache.pinot.segment.local.segment.index.readers.text.NativeTextIndexReader; import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; import org.apache.pinot.segment.spi.datasource.DataSource; -import org.apache.pinot.segment.spi.index.mutable.ThreadSafeMutableRoaringBitmap; import org.apache.pinot.segment.spi.index.reader.JsonIndexReader; import org.apache.pinot.segment.spi.index.reader.NullValueVectorReader; import org.apache.pinot.segment.spi.index.reader.TextIndexReader; +import org.apache.pinot.segment.spi.index.reader.VectorIndexReader; +import org.apache.pinot.spi.config.table.FieldConfig; import org.apache.pinot.spi.exception.BadQueryRequestException; import org.roaringbitmap.buffer.MutableRoaringBitmap; public class FilterPlanNode implements PlanNode { - private final IndexSegment _indexSegment; + private final SegmentContext _segmentContext; private final QueryContext _queryContext; private final FilterContext _filter; // Cache the predicate evaluators private final List> _predicateEvaluators = new ArrayList<>(4); - public FilterPlanNode(IndexSegment indexSegment, QueryContext queryContext) { - this(indexSegment, queryContext, null); + public FilterPlanNode(SegmentContext segmentContext, QueryContext queryContext) { + this(segmentContext, queryContext, null); } - public FilterPlanNode(IndexSegment indexSegment, QueryContext queryContext, @Nullable FilterContext filter) { - _indexSegment = indexSegment; + public FilterPlanNode(SegmentContext segmentContext, QueryContext queryContext, @Nullable FilterContext filter) { + _indexSegment = segmentContext.getIndexSegment(); + _segmentContext = segmentContext; _queryContext = queryContext; - _filter = filter; + _filter = filter != null ? filter : _queryContext.getFilter(); } @Override public BaseFilterOperator run() { - // NOTE: Snapshot the validDocIds before reading the numDocs to prevent the latest updates getting lost - ThreadSafeMutableRoaringBitmap validDocIds = _indexSegment.getValidDocIds(); - MutableRoaringBitmap validDocIdsSnapshot = - validDocIds != null && !_queryContext.isSkipUpsert() ? validDocIds.getMutableRoaringBitmap() : null; + MutableRoaringBitmap queryableDocIdsSnapshot = _segmentContext.getQueryableDocIdsSnapshot(); int numDocs = _indexSegment.getSegmentMetadata().getTotalDocs(); - FilterContext filter = _filter != null ? _filter : _queryContext.getFilter(); - if (filter != null) { - BaseFilterOperator filterOperator = constructPhysicalOperator(filter, numDocs); - if (validDocIdsSnapshot != null) { - BaseFilterOperator validDocFilter = new BitmapBasedFilterOperator(validDocIdsSnapshot, false, numDocs); + if (_filter != null) { + BaseFilterOperator filterOperator = constructPhysicalOperator(_filter, numDocs); + if (queryableDocIdsSnapshot != null) { + BaseFilterOperator validDocFilter = new BitmapBasedFilterOperator(queryableDocIdsSnapshot, false, numDocs); return FilterOperatorUtils.getAndFilterOperator(_queryContext, Arrays.asList(filterOperator, validDocFilter), numDocs); } else { return filterOperator; } - } else if (validDocIdsSnapshot != null) { - return new BitmapBasedFilterOperator(validDocIdsSnapshot, false, numDocs); + } else if (queryableDocIdsSnapshot != null) { + return new BitmapBasedFilterOperator(queryableDocIdsSnapshot, false, numDocs); } else { return new MatchAllFilterOperator(numDocs); } @@ -142,7 +143,8 @@ private boolean canApplyH3IndexForDistanceCheck(Predicate predicate, FunctionCon findLiteral = true; } } - return columnName != null && _indexSegment.getDataSource(columnName).getH3Index() != null && findLiteral; + return columnName != null && _indexSegment.getDataSource(columnName).getH3Index() != null && findLiteral + && _queryContext.isIndexUseAllowed(columnName, FieldConfig.IndexType.H3); } /** @@ -172,14 +174,16 @@ private boolean canApplyH3IndexForInclusionCheck(Predicate predicate, FunctionCo if (arguments.get(0).getType() == ExpressionContext.Type.IDENTIFIER && arguments.get(1).getType() == ExpressionContext.Type.LITERAL) { String columnName = arguments.get(0).getIdentifier(); - return _indexSegment.getDataSource(columnName).getH3Index() != null; + return _indexSegment.getDataSource(columnName).getH3Index() != null + && _queryContext.isIndexUseAllowed(columnName, FieldConfig.IndexType.H3); } return false; } else { if (arguments.get(1).getType() == ExpressionContext.Type.IDENTIFIER && arguments.get(0).getType() == ExpressionContext.Type.LITERAL) { String columnName = arguments.get(1).getIdentifier(); - return _indexSegment.getDataSource(columnName).getH3Index() != null; + return _indexSegment.getDataSource(columnName).getH3Index() != null + && _queryContext.isIndexUseAllowed(columnName, FieldConfig.IndexType.H3); } return false; } @@ -228,13 +232,12 @@ private BaseFilterOperator constructPhysicalOperator(FilterContext filter, int n ExpressionContext lhs = predicate.getLhs(); if (lhs.getType() == ExpressionContext.Type.FUNCTION) { if (canApplyH3IndexForDistanceCheck(predicate, lhs.getFunction())) { - return new H3IndexFilterOperator(_indexSegment, predicate, numDocs); + return new H3IndexFilterOperator(_indexSegment, _queryContext, predicate, numDocs); } else if (canApplyH3IndexForInclusionCheck(predicate, lhs.getFunction())) { - return new H3InclusionIndexFilterOperator(_indexSegment, predicate, _queryContext, numDocs); + return new H3InclusionIndexFilterOperator(_indexSegment, _queryContext, predicate, numDocs); } else { - // TODO: ExpressionFilterOperator does not support predicate types without PredicateEvaluator (IS_NULL, - // IS_NOT_NULL, TEXT_MATCH) - return new ExpressionFilterOperator(_indexSegment, predicate, numDocs); + // TODO: ExpressionFilterOperator does not support predicate types without PredicateEvaluator (TEXT_MATCH) + return new ExpressionFilterOperator(_indexSegment, _queryContext, predicate, numDocs); } } else { String column = lhs.getIdentifier(); @@ -250,9 +253,8 @@ private BaseFilterOperator constructPhysicalOperator(FilterContext filter, int n return new TextContainsFilterOperator(textIndexReader, (TextContainsPredicate) predicate, numDocs); case TEXT_MATCH: textIndexReader = dataSource.getTextIndex(); - Preconditions - .checkState(textIndexReader != null, "Cannot apply TEXT_MATCH on column: %s without text index", - column); + Preconditions.checkState(textIndexReader != null, + "Cannot apply TEXT_MATCH on column: %s without text index", column); // We could check for real time and segment Lucene reader, but easier to check the other way round if (textIndexReader instanceof NativeTextIndexReader || textIndexReader instanceof NativeMutableTextIndex) { @@ -278,13 +280,17 @@ private BaseFilterOperator constructPhysicalOperator(FilterContext filter, int n dataSource.getDataSourceMetadata().getDataType()); } _predicateEvaluators.add(Pair.of(predicate, predicateEvaluator)); - return FilterOperatorUtils.getLeafFilterOperator(predicateEvaluator, dataSource, numDocs, - _queryContext.isNullHandlingEnabled()); + return FilterOperatorUtils.getLeafFilterOperator(_queryContext, predicateEvaluator, dataSource, numDocs); case JSON_MATCH: JsonIndexReader jsonIndex = dataSource.getJsonIndex(); Preconditions.checkState(jsonIndex != null, "Cannot apply JSON_MATCH on column: %s without json index", column); return new JsonMatchFilterOperator(jsonIndex, (JsonMatchPredicate) predicate, numDocs); + case VECTOR_SIMILARITY: + VectorIndexReader vectorIndex = dataSource.getVectorIndex(); + Preconditions.checkState(vectorIndex != null, + "Cannot apply VECTOR_SIMILARITY on column: %s without vector index", column); + return new VectorSimilarityFilterOperator(vectorIndex, (VectorSimilarityPredicate) predicate, numDocs); case IS_NULL: NullValueVectorReader nullValueVector = dataSource.getNullValueVector(); if (nullValueVector != null) { @@ -301,13 +307,13 @@ private BaseFilterOperator constructPhysicalOperator(FilterContext filter, int n } default: predicateEvaluator = - PredicateEvaluatorProvider.getPredicateEvaluator(predicate, dataSource.getDictionary(), - dataSource.getDataSourceMetadata().getDataType()); + PredicateEvaluatorProvider.getPredicateEvaluator(predicate, dataSource, _queryContext); _predicateEvaluators.add(Pair.of(predicate, predicateEvaluator)); - return FilterOperatorUtils.getLeafFilterOperator(predicateEvaluator, dataSource, numDocs, - _queryContext.isNullHandlingEnabled()); + return FilterOperatorUtils.getLeafFilterOperator(_queryContext, predicateEvaluator, dataSource, numDocs); } } + case CONSTANT: + return filter.isConstantTrue() ? new MatchAllFilterOperator(numDocs) : EmptyFilterOperator.getInstance(); default: throw new IllegalStateException(); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/plan/GroupByPlanNode.java b/pinot-core/src/main/java/org/apache/pinot/core/plan/GroupByPlanNode.java index 2b5da7896bae..b44b8dc8ee4f 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/plan/GroupByPlanNode.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/plan/GroupByPlanNode.java @@ -18,79 +18,50 @@ */ package org.apache.pinot.core.plan; -import java.util.List; -import java.util.Map; -import java.util.Set; -import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.common.Operator; +import org.apache.pinot.core.operator.blocks.results.GroupByResultsBlock; import org.apache.pinot.core.operator.filter.BaseFilterOperator; +import org.apache.pinot.core.operator.query.FilteredGroupByOperator; import org.apache.pinot.core.operator.query.GroupByOperator; -import org.apache.pinot.core.operator.transform.TransformOperator; -import org.apache.pinot.core.query.aggregation.function.AggregationFunction; import org.apache.pinot.core.query.aggregation.function.AggregationFunctionUtils; import org.apache.pinot.core.query.request.context.QueryContext; -import org.apache.pinot.core.startree.CompositePredicateEvaluator; -import org.apache.pinot.core.startree.StarTreeUtils; -import org.apache.pinot.core.startree.plan.StarTreeTransformPlanNode; import org.apache.pinot.segment.spi.IndexSegment; -import org.apache.pinot.segment.spi.index.startree.AggregationFunctionColumnPair; -import org.apache.pinot.segment.spi.index.startree.StarTreeV2; +import org.apache.pinot.segment.spi.SegmentContext; /** * The GroupByPlanNode class provides the execution plan for group-by query on a single segment. */ -@SuppressWarnings("rawtypes") public class GroupByPlanNode implements PlanNode { private final IndexSegment _indexSegment; + private final SegmentContext _segmentContext; private final QueryContext _queryContext; - public GroupByPlanNode(IndexSegment indexSegment, QueryContext queryContext) { - _indexSegment = indexSegment; + public GroupByPlanNode(SegmentContext segmentContext, QueryContext queryContext) { + _indexSegment = segmentContext.getIndexSegment(); + _segmentContext = segmentContext; _queryContext = queryContext; } @Override - public GroupByOperator run() { - assert _queryContext.getAggregationFunctions() != null; - assert _queryContext.getGroupByExpressions() != null; + public Operator run() { + assert _queryContext.getAggregationFunctions() != null && _queryContext.getGroupByExpressions() != null; + return _queryContext.hasFilteredAggregations() ? buildFilteredGroupByPlan() : buildNonFilteredGroupByPlan(); + } - int numTotalDocs = _indexSegment.getSegmentMetadata().getTotalDocs(); - AggregationFunction[] aggregationFunctions = _queryContext.getAggregationFunctions(); - ExpressionContext[] groupByExpressions = _queryContext.getGroupByExpressions().toArray(new ExpressionContext[0]); + private FilteredGroupByOperator buildFilteredGroupByPlan() { + return new FilteredGroupByOperator(_queryContext, + AggregationFunctionUtils.buildFilteredAggregationInfos(_segmentContext, _queryContext), + _indexSegment.getSegmentMetadata().getTotalDocs()); + } - FilterPlanNode filterPlanNode = new FilterPlanNode(_indexSegment, _queryContext); + private GroupByOperator buildNonFilteredGroupByPlan() { + FilterPlanNode filterPlanNode = new FilterPlanNode(_segmentContext, _queryContext); BaseFilterOperator filterOperator = filterPlanNode.run(); - - // Use star-tree to solve the query if possible - List starTrees = _indexSegment.getStarTrees(); - if (starTrees != null && !_queryContext.isSkipStarTree()) { - AggregationFunctionColumnPair[] aggregationFunctionColumnPairs = - StarTreeUtils.extractAggregationFunctionPairs(aggregationFunctions); - if (aggregationFunctionColumnPairs != null) { - Map> predicateEvaluatorsMap = - StarTreeUtils.extractPredicateEvaluatorsMap(_indexSegment, _queryContext.getFilter(), - filterPlanNode.getPredicateEvaluators()); - if (predicateEvaluatorsMap != null) { - for (StarTreeV2 starTreeV2 : starTrees) { - if (StarTreeUtils.isFitForStarTree(starTreeV2.getMetadata(), aggregationFunctionColumnPairs, - groupByExpressions, predicateEvaluatorsMap.keySet())) { - TransformOperator transformOperator = - new StarTreeTransformPlanNode(_queryContext, starTreeV2, aggregationFunctionColumnPairs, - groupByExpressions, predicateEvaluatorsMap).run(); - return new GroupByOperator(aggregationFunctions, groupByExpressions, transformOperator, numTotalDocs, - _queryContext, true); - } - } - } - } - } - - Set expressionsToTransform = - AggregationFunctionUtils.collectExpressionsToTransform(aggregationFunctions, groupByExpressions); - TransformOperator transformPlanNode = - new TransformPlanNode(_indexSegment, _queryContext, expressionsToTransform, DocIdSetPlanNode.MAX_DOC_PER_CALL, - filterOperator).run(); - return new GroupByOperator(aggregationFunctions, groupByExpressions, transformPlanNode, numTotalDocs, _queryContext, - false); + AggregationFunctionUtils.AggregationInfo aggregationInfo = + AggregationFunctionUtils.buildAggregationInfo(_segmentContext, _queryContext, + _queryContext.getAggregationFunctions(), _queryContext.getFilter(), filterOperator, + filterPlanNode.getPredicateEvaluators()); + return new GroupByOperator(_queryContext, aggregationInfo, _indexSegment.getSegmentMetadata().getTotalDocs()); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/plan/InstanceResponsePlanNode.java b/pinot-core/src/main/java/org/apache/pinot/core/plan/InstanceResponsePlanNode.java index 57b0c18f9e9c..bb600d0c40d9 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/plan/InstanceResponsePlanNode.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/plan/InstanceResponsePlanNode.java @@ -22,25 +22,25 @@ import org.apache.pinot.core.operator.InstanceResponseOperator; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.FetchContext; -import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; public class InstanceResponsePlanNode implements PlanNode { protected final CombinePlanNode _combinePlanNode; - protected final List _indexSegments; + protected final List _segmentContexts; protected final List _fetchContexts; protected final QueryContext _queryContext; - public InstanceResponsePlanNode(CombinePlanNode combinePlanNode, List indexSegments, + public InstanceResponsePlanNode(CombinePlanNode combinePlanNode, List segmentContexts, List fetchContexts, QueryContext queryContext) { _combinePlanNode = combinePlanNode; - _indexSegments = indexSegments; + _segmentContexts = segmentContexts; _fetchContexts = fetchContexts; _queryContext = queryContext; } @Override public InstanceResponseOperator run() { - return new InstanceResponseOperator(_combinePlanNode.run(), _indexSegments, _fetchContexts, _queryContext); + return new InstanceResponseOperator(_combinePlanNode.run(), _segmentContexts, _fetchContexts, _queryContext); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/plan/ProjectPlanNode.java b/pinot-core/src/main/java/org/apache/pinot/core/plan/ProjectPlanNode.java new file mode 100644 index 000000000000..6bc763009084 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/plan/ProjectPlanNode.java @@ -0,0 +1,88 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.plan; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nullable; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.HashUtil; +import org.apache.pinot.core.operator.BaseProjectOperator; +import org.apache.pinot.core.operator.DocIdSetOperator; +import org.apache.pinot.core.operator.ProjectionOperator; +import org.apache.pinot.core.operator.ProjectionOperatorUtils; +import org.apache.pinot.core.operator.filter.BaseFilterOperator; +import org.apache.pinot.core.operator.transform.TransformOperator; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; +import org.apache.pinot.segment.spi.datasource.DataSource; + + +/** + * The ProjectPlanNode provides the execution plan for fetching column values on a single segment. + */ +public class ProjectPlanNode implements PlanNode { + private final IndexSegment _indexSegment; + private final SegmentContext _segmentContext; + private final QueryContext _queryContext; + private final Collection _expressions; + private final int _maxDocsPerCall; + private final BaseFilterOperator _filterOperator; + + public ProjectPlanNode(SegmentContext segmentContext, QueryContext queryContext, + Collection expressions, int maxDocsPerCall, @Nullable BaseFilterOperator filterOperator) { + _indexSegment = segmentContext.getIndexSegment(); + _segmentContext = segmentContext; + _queryContext = queryContext; + _expressions = expressions; + _maxDocsPerCall = maxDocsPerCall; + _filterOperator = filterOperator; + } + + public ProjectPlanNode(SegmentContext segmentContext, QueryContext queryContext, + Collection expressions, int maxDocsPerCall) { + this(segmentContext, queryContext, expressions, maxDocsPerCall, null); + } + + @Override + public BaseProjectOperator run() { + Set projectionColumns = new HashSet<>(); + boolean hasNonIdentifierExpression = false; + for (ExpressionContext expression : _expressions) { + expression.getColumns(projectionColumns); + if (expression.getType() != ExpressionContext.Type.IDENTIFIER) { + hasNonIdentifierExpression = true; + } + } + Map dataSourceMap = new HashMap<>(HashUtil.getHashMapCapacity(projectionColumns.size())); + projectionColumns.forEach(column -> dataSourceMap.put(column, _indexSegment.getDataSource(column))); + // NOTE: Skip creating DocIdSetOperator when maxDocsPerCall is 0 (for selection query with LIMIT 0) + DocIdSetOperator docIdSetOperator = + _maxDocsPerCall > 0 ? new DocIdSetPlanNode(_segmentContext, _queryContext, _maxDocsPerCall, + _filterOperator).run() : null; + ProjectionOperator projectionOperator = + ProjectionOperatorUtils.getProjectionOperator(dataSourceMap, docIdSetOperator); + return hasNonIdentifierExpression ? new TransformOperator(_queryContext, projectionOperator, _expressions) + : projectionOperator; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/plan/ProjectionPlanNode.java b/pinot-core/src/main/java/org/apache/pinot/core/plan/ProjectionPlanNode.java deleted file mode 100644 index 5f286b40128e..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/plan/ProjectionPlanNode.java +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.plan; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import javax.annotation.Nullable; -import org.apache.pinot.core.operator.DocIdSetOperator; -import org.apache.pinot.core.operator.ProjectionOperator; -import org.apache.pinot.core.operator.filter.BaseFilterOperator; -import org.apache.pinot.core.query.request.context.QueryContext; -import org.apache.pinot.segment.spi.IndexSegment; -import org.apache.pinot.segment.spi.datasource.DataSource; - - -/** - * The ProjectionPlanNode class provides the execution plan for fetching projection columns' data source - * on a single segment. - */ -public class ProjectionPlanNode implements PlanNode { - private final IndexSegment _indexSegment; - private final QueryContext _queryContext; - private final Set _projectionColumns; - private final int _maxDocsPerCall; - private final BaseFilterOperator _filterOperator; - - public ProjectionPlanNode(IndexSegment indexSegment, QueryContext queryContext, Set projectionColumns, - int maxDocsPerCall, @Nullable BaseFilterOperator filterOperator) { - _indexSegment = indexSegment; - _queryContext = queryContext; - _projectionColumns = projectionColumns; - _maxDocsPerCall = maxDocsPerCall; - _filterOperator = filterOperator; - } - - @Override - public ProjectionOperator run() { - Map dataSourceMap = new HashMap<>(_projectionColumns.size()); - for (String column : _projectionColumns) { - dataSourceMap.put(column, _indexSegment.getDataSource(column)); - } - // NOTE: Skip creating DocIdSetOperator when maxDocsPerCall is 0 (for selection query with LIMIT 0) - DocIdSetOperator docIdSetOperator = - _maxDocsPerCall > 0 ? new DocIdSetPlanNode(_indexSegment, _queryContext, _maxDocsPerCall, _filterOperator).run() - : null; - return new ProjectionOperator(dataSourceMap, docIdSetOperator); - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/plan/SelectionPlanNode.java b/pinot-core/src/main/java/org/apache/pinot/core/plan/SelectionPlanNode.java index cd598a7309bb..e936cd694996 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/plan/SelectionPlanNode.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/plan/SelectionPlanNode.java @@ -25,16 +25,17 @@ import org.apache.pinot.common.request.context.OrderByExpressionContext; import org.apache.pinot.common.utils.config.QueryOptionsUtils; import org.apache.pinot.core.common.Operator; +import org.apache.pinot.core.operator.BaseProjectOperator; import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; import org.apache.pinot.core.operator.query.EmptySelectionOperator; import org.apache.pinot.core.operator.query.SelectionOnlyOperator; import org.apache.pinot.core.operator.query.SelectionOrderByOperator; import org.apache.pinot.core.operator.query.SelectionPartiallyOrderedByAscOperator; import org.apache.pinot.core.operator.query.SelectionPartiallyOrderedByDescOperation; -import org.apache.pinot.core.operator.transform.TransformOperator; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.selection.SelectionOperatorUtils; import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.segment.spi.index.reader.NullValueVectorReader; @@ -44,10 +45,12 @@ */ public class SelectionPlanNode implements PlanNode { private final IndexSegment _indexSegment; + private final SegmentContext _segmentContext; private final QueryContext _queryContext; - public SelectionPlanNode(IndexSegment indexSegment, QueryContext queryContext) { - _indexSegment = indexSegment; + public SelectionPlanNode(SegmentContext segmentContext, QueryContext queryContext) { + _indexSegment = segmentContext.getIndexSegment(); + _segmentContext = segmentContext; _queryContext = queryContext; } @@ -58,8 +61,9 @@ public Operator run() { if (limit == 0) { // Empty selection (LIMIT 0) - TransformOperator transformOperator = new TransformPlanNode(_indexSegment, _queryContext, expressions, 0).run(); - return new EmptySelectionOperator(_indexSegment, expressions, transformOperator); + BaseProjectOperator projectOperator = + new ProjectPlanNode(_segmentContext, _queryContext, expressions, 0).run(); + return new EmptySelectionOperator(_indexSegment, _queryContext, expressions, projectOperator); } List orderByExpressions = _queryContext.getOrderByExpressions(); @@ -67,10 +71,9 @@ public Operator run() { // Selection only // ie: SELECT ... FROM Table WHERE ... LIMIT 10 int maxDocsPerCall = Math.min(limit, DocIdSetPlanNode.MAX_DOC_PER_CALL); - TransformPlanNode planNode = new TransformPlanNode(_indexSegment, _queryContext, expressions, maxDocsPerCall); - TransformOperator transformOperator = planNode.run(); - - return new SelectionOnlyOperator(_indexSegment, _queryContext, expressions, transformOperator); + BaseProjectOperator projectOperator = + new ProjectPlanNode(_segmentContext, _queryContext, expressions, maxDocsPerCall).run(); + return new SelectionOnlyOperator(_indexSegment, _queryContext, expressions, projectOperator); } int numOrderByExpressions = orderByExpressions.size(); // Although it is a break of abstraction, some code, specially merging, assumes that if there is an order by @@ -87,23 +90,23 @@ public Operator run() { if (sortedColumnsPrefixSize == orderByExpressions.size()) { maxDocsPerCall = Math.min(limit + _queryContext.getOffset(), DocIdSetPlanNode.MAX_DOC_PER_CALL); } - TransformPlanNode planNode = new TransformPlanNode(_indexSegment, _queryContext, expressions, maxDocsPerCall); - TransformOperator transformOperator = planNode.run(); - return new SelectionPartiallyOrderedByAscOperator(_indexSegment, _queryContext, expressions, transformOperator, + BaseProjectOperator projectOperator = + new ProjectPlanNode(_segmentContext, _queryContext, expressions, maxDocsPerCall).run(); + return new SelectionPartiallyOrderedByAscOperator(_indexSegment, _queryContext, expressions, projectOperator, sortedColumnsPrefixSize); } else { - TransformPlanNode planNode = new TransformPlanNode(_indexSegment, _queryContext, expressions, maxDocsPerCall); - TransformOperator transformOperator = planNode.run(); - return new SelectionPartiallyOrderedByDescOperation(_indexSegment, _queryContext, expressions, - transformOperator, sortedColumnsPrefixSize); + BaseProjectOperator projectOperator = + new ProjectPlanNode(_segmentContext, _queryContext, expressions, maxDocsPerCall).run(); + return new SelectionPartiallyOrderedByDescOperation(_indexSegment, _queryContext, expressions, projectOperator, + sortedColumnsPrefixSize); } } if (numOrderByExpressions == expressions.size()) { // All output expressions are ordered // ie: SELECT not_sorted1, not_sorted2 FROM Table WHERE ... ORDER BY not_sorted1, not_sorted2 LIMIT 10 OFFSET 5 - TransformOperator transformOperator = - new TransformPlanNode(_indexSegment, _queryContext, expressions, DocIdSetPlanNode.MAX_DOC_PER_CALL).run(); - return new SelectionOrderByOperator(_indexSegment, _queryContext, expressions, transformOperator); + BaseProjectOperator projectOperator = + new ProjectPlanNode(_segmentContext, _queryContext, expressions, DocIdSetPlanNode.MAX_DOC_PER_CALL).run(); + return new SelectionOrderByOperator(_indexSegment, _queryContext, expressions, projectOperator); } // Not all output expressions are ordered, only fetch the order-by expressions and docId to avoid the // unnecessary data fetch @@ -112,9 +115,9 @@ public Operator run() { for (OrderByExpressionContext orderByExpression : orderByExpressions) { expressionsToTransform.add(orderByExpression.getExpression()); } - TransformOperator transformOperator = new TransformPlanNode(_indexSegment, _queryContext, expressionsToTransform, + BaseProjectOperator projectOperator = new ProjectPlanNode(_segmentContext, _queryContext, expressionsToTransform, DocIdSetPlanNode.MAX_DOC_PER_CALL).run(); - return new SelectionOrderByOperator(_indexSegment, _queryContext, expressions, transformOperator); + return new SelectionOrderByOperator(_indexSegment, _queryContext, expressions, projectOperator); } /** diff --git a/pinot-core/src/main/java/org/apache/pinot/core/plan/StreamingInstanceResponsePlanNode.java b/pinot-core/src/main/java/org/apache/pinot/core/plan/StreamingInstanceResponsePlanNode.java index 25b20ec5b9e4..52276fa09719 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/plan/StreamingInstanceResponsePlanNode.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/plan/StreamingInstanceResponsePlanNode.java @@ -18,29 +18,27 @@ */ package org.apache.pinot.core.plan; -import io.grpc.stub.StreamObserver; import java.util.List; -import org.apache.pinot.common.proto.Server; import org.apache.pinot.core.operator.InstanceResponseOperator; import org.apache.pinot.core.operator.streaming.StreamingInstanceResponseOperator; +import org.apache.pinot.core.query.executor.ResultsBlockStreamer; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.FetchContext; -import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; public class StreamingInstanceResponsePlanNode extends InstanceResponsePlanNode { - private final StreamObserver _streamObserver; + private final ResultsBlockStreamer _streamer; - public StreamingInstanceResponsePlanNode(CombinePlanNode combinePlanNode, List indexSegments, - List fetchContexts, QueryContext queryContext, - StreamObserver streamObserver) { - super(combinePlanNode, indexSegments, fetchContexts, queryContext); - _streamObserver = streamObserver; + public StreamingInstanceResponsePlanNode(CombinePlanNode combinePlanNode, List segmentContexts, + List fetchContexts, QueryContext queryContext, ResultsBlockStreamer streamer) { + super(combinePlanNode, segmentContexts, fetchContexts, queryContext); + _streamer = streamer; } @Override public InstanceResponseOperator run() { - return new StreamingInstanceResponseOperator(_combinePlanNode.run(), _indexSegments, _fetchContexts, - _streamObserver, _queryContext); + return new StreamingInstanceResponseOperator(_combinePlanNode.run(), _segmentContexts, _fetchContexts, _streamer, + _queryContext); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/plan/StreamingSelectionPlanNode.java b/pinot-core/src/main/java/org/apache/pinot/core/plan/StreamingSelectionPlanNode.java index b21aefa85256..386dff2591ae 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/plan/StreamingSelectionPlanNode.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/plan/StreamingSelectionPlanNode.java @@ -21,11 +21,12 @@ import com.google.common.base.Preconditions; import java.util.List; import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.operator.BaseProjectOperator; import org.apache.pinot.core.operator.streaming.StreamingSelectionOnlyOperator; -import org.apache.pinot.core.operator.transform.TransformOperator; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.selection.SelectionOperatorUtils; import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; /** @@ -35,20 +36,22 @@ */ public class StreamingSelectionPlanNode implements PlanNode { private final IndexSegment _indexSegment; + private final SegmentContext _segmentContext; private final QueryContext _queryContext; - public StreamingSelectionPlanNode(IndexSegment indexSegment, QueryContext queryContext) { + public StreamingSelectionPlanNode(SegmentContext segmentContext, QueryContext queryContext) { Preconditions.checkState(queryContext.getOrderByExpressions() == null, "Selection order-by is not supported for streaming"); - _indexSegment = indexSegment; + _indexSegment = segmentContext.getIndexSegment(); + _segmentContext = segmentContext; _queryContext = queryContext; } @Override public StreamingSelectionOnlyOperator run() { List expressions = SelectionOperatorUtils.extractExpressions(_queryContext, _indexSegment); - TransformOperator transformOperator = new TransformPlanNode(_indexSegment, _queryContext, expressions, + BaseProjectOperator projectOperator = new ProjectPlanNode(_segmentContext, _queryContext, expressions, Math.min(_queryContext.getLimit(), DocIdSetPlanNode.MAX_DOC_PER_CALL)).run(); - return new StreamingSelectionOnlyOperator(_indexSegment, _queryContext, expressions, transformOperator); + return new StreamingSelectionOnlyOperator(_indexSegment, _queryContext, expressions, projectOperator); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/plan/TransformPlanNode.java b/pinot-core/src/main/java/org/apache/pinot/core/plan/TransformPlanNode.java deleted file mode 100644 index 592fc9246fd8..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/plan/TransformPlanNode.java +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.plan; - -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; -import javax.annotation.Nullable; -import org.apache.pinot.common.request.context.ExpressionContext; -import org.apache.pinot.core.operator.ProjectionOperator; -import org.apache.pinot.core.operator.filter.BaseFilterOperator; -import org.apache.pinot.core.operator.transform.PassThroughTransformOperator; -import org.apache.pinot.core.operator.transform.TransformOperator; -import org.apache.pinot.core.query.request.context.QueryContext; -import org.apache.pinot.segment.spi.IndexSegment; - - -/** - * The TransformPlanNode class provides the execution plan for transforms on a single segment. - */ -public class TransformPlanNode implements PlanNode { - private final IndexSegment _indexSegment; - private final QueryContext _queryContext; - private final Collection _expressions; - private final int _maxDocsPerCall; - private final BaseFilterOperator _filterOperator; - - public TransformPlanNode(IndexSegment indexSegment, QueryContext queryContext, - Collection expressions, int maxDocsPerCall) { - this(indexSegment, queryContext, expressions, maxDocsPerCall, null); - } - - public TransformPlanNode(IndexSegment indexSegment, QueryContext queryContext, - Collection expressions, int maxDocsPerCall, @Nullable BaseFilterOperator filterOperator) { - _indexSegment = indexSegment; - _queryContext = queryContext; - _expressions = expressions; - _maxDocsPerCall = maxDocsPerCall; - _filterOperator = filterOperator; - } - - @Override - public TransformOperator run() { - Set projectionColumns = new HashSet<>(); - boolean hasNonIdentifierExpression = false; - for (ExpressionContext expression : _expressions) { - expression.getColumns(projectionColumns); - if (expression.getType() != ExpressionContext.Type.IDENTIFIER) { - hasNonIdentifierExpression = true; - } - } - ProjectionOperator projectionOperator = - new ProjectionPlanNode(_indexSegment, _queryContext, projectionColumns, _maxDocsPerCall, _filterOperator).run(); - if (hasNonIdentifierExpression) { - return new TransformOperator(_queryContext, projectionOperator, _expressions); - } else { - return new PassThroughTransformOperator(projectionOperator, _expressions); - } - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/plan/maker/InstancePlanMakerImplV2.java b/pinot-core/src/main/java/org/apache/pinot/core/plan/maker/InstancePlanMakerImplV2.java index 391a8797f000..70b4fc8961be 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/plan/maker/InstancePlanMakerImplV2.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/plan/maker/InstancePlanMakerImplV2.java @@ -20,16 +20,14 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; -import io.grpc.stub.StreamObserver; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.collections.MapUtils; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.MapUtils; import org.apache.pinot.common.metrics.ServerMetrics; -import org.apache.pinot.common.proto.Server; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.FilterContext; import org.apache.pinot.common.request.context.OrderByExpressionContext; @@ -47,7 +45,7 @@ import org.apache.pinot.core.plan.SelectionPlanNode; import org.apache.pinot.core.plan.StreamingInstanceResponsePlanNode; import org.apache.pinot.core.plan.StreamingSelectionPlanNode; -import org.apache.pinot.core.query.config.QueryExecutorConfig; +import org.apache.pinot.core.query.executor.ResultsBlockStreamer; import org.apache.pinot.core.query.prefetch.FetchPlanner; import org.apache.pinot.core.query.prefetch.FetchPlannerRegistry; import org.apache.pinot.core.query.request.context.QueryContext; @@ -55,6 +53,7 @@ import org.apache.pinot.core.util.GroupByUtils; import org.apache.pinot.segment.spi.FetchContext; import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; import org.apache.pinot.spi.env.PinotConfiguration; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -90,30 +89,22 @@ public class InstancePlanMakerImplV2 implements PlanMaker { private static final Logger LOGGER = LoggerFactory.getLogger(InstancePlanMakerImplV2.class); - private final int _maxExecutionThreads; - private final int _maxInitialResultHolderCapacity; + private final FetchPlanner _fetchPlanner = FetchPlannerRegistry.getPlanner(); + private int _maxExecutionThreads = DEFAULT_MAX_EXECUTION_THREADS; + private int _maxInitialResultHolderCapacity = DEFAULT_MAX_INITIAL_RESULT_HOLDER_CAPACITY; // Limit on number of groups stored for each segment, beyond which no new group will be created - private final int _numGroupsLimit; + private int _numGroupsLimit = DEFAULT_NUM_GROUPS_LIMIT; // Used for SQL GROUP BY (server combine) - private final int _minSegmentGroupTrimSize; - private final int _minServerGroupTrimSize; - private final int _groupByTrimThreshold; - private final FetchPlanner _fetchPlanner = FetchPlannerRegistry.getPlanner(); + private int _minSegmentGroupTrimSize = DEFAULT_MIN_SEGMENT_GROUP_TRIM_SIZE; + private int _minServerGroupTrimSize = DEFAULT_MIN_SERVER_GROUP_TRIM_SIZE; + private int _groupByTrimThreshold = DEFAULT_GROUPBY_TRIM_THRESHOLD; - @VisibleForTesting public InstancePlanMakerImplV2() { - _maxExecutionThreads = DEFAULT_MAX_EXECUTION_THREADS; - _maxInitialResultHolderCapacity = DEFAULT_MAX_INITIAL_RESULT_HOLDER_CAPACITY; - _numGroupsLimit = DEFAULT_NUM_GROUPS_LIMIT; - _minSegmentGroupTrimSize = DEFAULT_MIN_SEGMENT_GROUP_TRIM_SIZE; - _minServerGroupTrimSize = DEFAULT_MIN_SERVER_GROUP_TRIM_SIZE; - _groupByTrimThreshold = DEFAULT_GROUPBY_TRIM_THRESHOLD; } @VisibleForTesting public InstancePlanMakerImplV2(int maxInitialResultHolderCapacity, int numGroupsLimit, int minSegmentGroupTrimSize, int minServerGroupTrimSize, int groupByTrimThreshold) { - _maxExecutionThreads = DEFAULT_MAX_EXECUTION_THREADS; _maxInitialResultHolderCapacity = maxInitialResultHolderCapacity; _numGroupsLimit = numGroupsLimit; _minSegmentGroupTrimSize = minSegmentGroupTrimSize; @@ -121,63 +112,55 @@ public InstancePlanMakerImplV2(int maxInitialResultHolderCapacity, int numGroups _groupByTrimThreshold = groupByTrimThreshold; } - /** - * Constructor for usage when client requires to pass {@link QueryExecutorConfig} to this class. - *

    - *
  • Set limit on the initial result holder capacity
  • - *
  • Set limit on number of groups returned from each segment and combined result
  • - *
- * - * @param queryExecutorConfig Query executor configuration - */ - public InstancePlanMakerImplV2(QueryExecutorConfig queryExecutorConfig) { - PinotConfiguration config = queryExecutorConfig.getConfig(); - _maxExecutionThreads = config.getProperty(MAX_EXECUTION_THREADS_KEY, DEFAULT_MAX_EXECUTION_THREADS); - _maxInitialResultHolderCapacity = - config.getProperty(MAX_INITIAL_RESULT_HOLDER_CAPACITY_KEY, DEFAULT_MAX_INITIAL_RESULT_HOLDER_CAPACITY); - _numGroupsLimit = config.getProperty(NUM_GROUPS_LIMIT_KEY, DEFAULT_NUM_GROUPS_LIMIT); + @Override + public void init(PinotConfiguration queryExecutorConfig) { + _maxExecutionThreads = queryExecutorConfig.getProperty(MAX_EXECUTION_THREADS_KEY, DEFAULT_MAX_EXECUTION_THREADS); + _maxInitialResultHolderCapacity = queryExecutorConfig.getProperty(MAX_INITIAL_RESULT_HOLDER_CAPACITY_KEY, + DEFAULT_MAX_INITIAL_RESULT_HOLDER_CAPACITY); + _numGroupsLimit = queryExecutorConfig.getProperty(NUM_GROUPS_LIMIT_KEY, DEFAULT_NUM_GROUPS_LIMIT); Preconditions.checkState(_maxInitialResultHolderCapacity <= _numGroupsLimit, "Invalid configuration: maxInitialResultHolderCapacity: %d must be smaller or equal to numGroupsLimit: %d", _maxInitialResultHolderCapacity, _numGroupsLimit); - _minSegmentGroupTrimSize = config.getProperty(MIN_SEGMENT_GROUP_TRIM_SIZE_KEY, DEFAULT_MIN_SEGMENT_GROUP_TRIM_SIZE); - _minServerGroupTrimSize = config.getProperty(MIN_SERVER_GROUP_TRIM_SIZE_KEY, DEFAULT_MIN_SERVER_GROUP_TRIM_SIZE); - _groupByTrimThreshold = config.getProperty(GROUPBY_TRIM_THRESHOLD_KEY, DEFAULT_GROUPBY_TRIM_THRESHOLD); + _minSegmentGroupTrimSize = + queryExecutorConfig.getProperty(MIN_SEGMENT_GROUP_TRIM_SIZE_KEY, DEFAULT_MIN_SEGMENT_GROUP_TRIM_SIZE); + _minServerGroupTrimSize = + queryExecutorConfig.getProperty(MIN_SERVER_GROUP_TRIM_SIZE_KEY, DEFAULT_MIN_SERVER_GROUP_TRIM_SIZE); + _groupByTrimThreshold = queryExecutorConfig.getProperty(GROUPBY_TRIM_THRESHOLD_KEY, DEFAULT_GROUPBY_TRIM_THRESHOLD); Preconditions.checkState(_groupByTrimThreshold > 0, "Invalid configurable: groupByTrimThreshold: %d must be positive", _groupByTrimThreshold); - LOGGER.info("Initializing plan maker with maxInitialResultHolderCapacity: {}, numGroupsLimit: {}, " - + "minSegmentGroupTrimSize: {}, minServerGroupTrimSize: {}", _maxInitialResultHolderCapacity, - _numGroupsLimit, - _minSegmentGroupTrimSize, _minServerGroupTrimSize); + LOGGER.info("Initialized plan maker with maxExecutionThreads: {}, maxInitialResultHolderCapacity: {}, " + + "numGroupsLimit: {}, minSegmentGroupTrimSize: {}, minServerGroupTrimSize: {}, groupByTrimThreshold: {}", + _maxExecutionThreads, _maxInitialResultHolderCapacity, _numGroupsLimit, _minSegmentGroupTrimSize, + _minServerGroupTrimSize, _groupByTrimThreshold); } - @Override - public Plan makeInstancePlan(List indexSegments, QueryContext queryContext, + public Plan makeInstancePlan(List segmentContexts, QueryContext queryContext, ExecutorService executorService, ServerMetrics serverMetrics) { applyQueryOptions(queryContext); - int numSegments = indexSegments.size(); + int numSegments = segmentContexts.size(); List planNodes = new ArrayList<>(numSegments); List fetchContexts; - if (queryContext.isEnablePrefetch()) { fetchContexts = new ArrayList<>(numSegments); - for (IndexSegment indexSegment : indexSegments) { - FetchContext fetchContext = _fetchPlanner.planFetchForProcessing(indexSegment, queryContext); + for (SegmentContext segmentContext : segmentContexts) { + FetchContext fetchContext = + _fetchPlanner.planFetchForProcessing(segmentContext.getIndexSegment(), queryContext); fetchContexts.add(fetchContext); planNodes.add( - new AcquireReleaseColumnsSegmentPlanNode(makeSegmentPlanNode(indexSegment, queryContext), indexSegment, + new AcquireReleaseColumnsSegmentPlanNode(makeSegmentPlanNode(segmentContext, queryContext), segmentContext, fetchContext)); } } else { fetchContexts = Collections.emptyList(); - for (IndexSegment indexSegment : indexSegments) { - planNodes.add(makeSegmentPlanNode(indexSegment, queryContext)); + for (SegmentContext segmentContext : segmentContexts) { + planNodes.add(makeSegmentPlanNode(segmentContext, queryContext)); } } CombinePlanNode combinePlanNode = new CombinePlanNode(planNodes, queryContext, executorService, null); return new GlobalPlanImplV0( - new InstanceResponsePlanNode(combinePlanNode, indexSegments, fetchContexts, queryContext)); + new InstanceResponsePlanNode(combinePlanNode, segmentContexts, fetchContexts, queryContext)); } private void applyQueryOptions(QueryContext queryContext) { @@ -192,6 +175,8 @@ private void applyQueryOptions(QueryContext queryContext) { // Set skipScanFilterReorder queryContext.setSkipScanFilterReorder(QueryOptionsUtils.isSkipScanFilterReorder(queryOptions)); + queryContext.setSkipIndexes(QueryOptionsUtils.getSkipIndexes(queryOptions)); + // Set maxExecutionThreads int maxExecutionThreads; Integer maxExecutionThreadsFromQuery = QueryOptionsUtils.getMaxExecutionThreads(queryOptions); @@ -209,83 +194,82 @@ private void applyQueryOptions(QueryContext queryContext) { // Set group-by query options if (QueryContextUtils.isAggregationQuery(queryContext) && queryContext.getGroupByExpressions() != null) { - // Set maxInitialResultHolderCapacity - queryContext.setMaxInitialResultHolderCapacity(_maxInitialResultHolderCapacity); - + Integer initResultCap = QueryOptionsUtils.getMaxInitialResultHolderCapacity(queryOptions); + if (initResultCap != null) { + queryContext.setMaxInitialResultHolderCapacity(initResultCap); + } else { + queryContext.setMaxInitialResultHolderCapacity(_maxInitialResultHolderCapacity); + } // Set numGroupsLimit - queryContext.setNumGroupsLimit(_numGroupsLimit); - + Integer numGroupsLimit = QueryOptionsUtils.getNumGroupsLimit(queryOptions); + if (numGroupsLimit != null) { + queryContext.setNumGroupsLimit(numGroupsLimit); + } else { + queryContext.setNumGroupsLimit(_numGroupsLimit); + } // Set minSegmentGroupTrimSize Integer minSegmentGroupTrimSizeFromQuery = QueryOptionsUtils.getMinSegmentGroupTrimSize(queryOptions); - int minSegmentGroupTrimSize = - minSegmentGroupTrimSizeFromQuery != null ? minSegmentGroupTrimSizeFromQuery : _minSegmentGroupTrimSize; - queryContext.setMinSegmentGroupTrimSize(minSegmentGroupTrimSize); - + if (minSegmentGroupTrimSizeFromQuery != null) { + queryContext.setMinSegmentGroupTrimSize(minSegmentGroupTrimSizeFromQuery); + } else { + queryContext.setMinSegmentGroupTrimSize(_minSegmentGroupTrimSize); + } // Set minServerGroupTrimSize Integer minServerGroupTrimSizeFromQuery = QueryOptionsUtils.getMinServerGroupTrimSize(queryOptions); int minServerGroupTrimSize = minServerGroupTrimSizeFromQuery != null ? minServerGroupTrimSizeFromQuery : _minServerGroupTrimSize; queryContext.setMinServerGroupTrimSize(minServerGroupTrimSize); - // Set groupTrimThreshold - queryContext.setGroupTrimThreshold(_groupByTrimThreshold); + Integer groupTrimThreshold = QueryOptionsUtils.getGroupTrimThreshold(queryOptions); + if (groupTrimThreshold != null) { + queryContext.setGroupTrimThreshold(groupTrimThreshold); + } else { + queryContext.setGroupTrimThreshold(_groupByTrimThreshold); + } } } @Override - public PlanNode makeSegmentPlanNode(IndexSegment indexSegment, QueryContext queryContext) { - rewriteQueryContextWithHints(queryContext, indexSegment); + public PlanNode makeSegmentPlanNode(SegmentContext segmentContext, QueryContext queryContext) { + rewriteQueryContextWithHints(queryContext, segmentContext.getIndexSegment()); if (QueryContextUtils.isAggregationQuery(queryContext)) { List groupByExpressions = queryContext.getGroupByExpressions(); if (groupByExpressions != null) { // Group-by query - return new GroupByPlanNode(indexSegment, queryContext); + return new GroupByPlanNode(segmentContext, queryContext); } else { // Aggregation query - return new AggregationPlanNode(indexSegment, queryContext); + return new AggregationPlanNode(segmentContext, queryContext); } } else if (QueryContextUtils.isSelectionQuery(queryContext)) { - return new SelectionPlanNode(indexSegment, queryContext); + return new SelectionPlanNode(segmentContext, queryContext); } else { assert QueryContextUtils.isDistinctQuery(queryContext); - return new DistinctPlanNode(indexSegment, queryContext); + return new DistinctPlanNode(segmentContext, queryContext); } } - @Override - public Plan makeStreamingInstancePlan(List indexSegments, QueryContext queryContext, - ExecutorService executorService, StreamObserver streamObserver, - ServerMetrics serverMetrics) { + public Plan makeStreamingInstancePlan(List segmentContexts, QueryContext queryContext, + ExecutorService executorService, ResultsBlockStreamer streamer, ServerMetrics serverMetrics) { applyQueryOptions(queryContext); - - List planNodes = new ArrayList<>(indexSegments.size()); - for (IndexSegment indexSegment : indexSegments) { - planNodes.add(makeStreamingSegmentPlanNode(indexSegment, queryContext)); - } - CombinePlanNode combinePlanNode = new CombinePlanNode(planNodes, queryContext, executorService, streamObserver); - if (QueryContextUtils.isSelectionOnlyQuery(queryContext)) { - // selection-only is streamed in StreamingSelectionPlanNode --> here only metadata block is returned. - return new GlobalPlanImplV0( - new InstanceResponsePlanNode(combinePlanNode, indexSegments, Collections.emptyList(), queryContext)); - } else { - // non-selection-only requires a StreamingInstanceResponsePlanNode to stream data block back and metadata block - // as final return. - return new GlobalPlanImplV0( - new StreamingInstanceResponsePlanNode(combinePlanNode, indexSegments, Collections.emptyList(), queryContext, - streamObserver)); + List planNodes = new ArrayList<>(segmentContexts.size()); + for (SegmentContext segmentContext : segmentContexts) { + planNodes.add(makeStreamingSegmentPlanNode(segmentContext, queryContext)); } + CombinePlanNode combinePlanNode = new CombinePlanNode(planNodes, queryContext, executorService, streamer); + return new GlobalPlanImplV0( + new StreamingInstanceResponsePlanNode(combinePlanNode, segmentContexts, Collections.emptyList(), queryContext, + streamer)); } @Override - public PlanNode makeStreamingSegmentPlanNode(IndexSegment indexSegment, QueryContext queryContext) { - if (!QueryContextUtils.isSelectionOnlyQuery(queryContext)) { - // non-selection-only query goes through normal SegmentPlan. - // it will be stream back via StreamingInstanceResponsePlanNode - return makeSegmentPlanNode(indexSegment, queryContext); + public PlanNode makeStreamingSegmentPlanNode(SegmentContext segmentContext, QueryContext queryContext) { + if (QueryContextUtils.isSelectionOnlyQuery(queryContext) && queryContext.getLimit() != 0) { + // Use streaming operator only for non-empty selection-only query + return new StreamingSelectionPlanNode(segmentContext, queryContext); } else { - // Selection-only query can be directly stream back - return new StreamingSelectionPlanNode(indexSegment, queryContext); + return makeSegmentPlanNode(segmentContext, queryContext); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/plan/maker/PlanMaker.java b/pinot-core/src/main/java/org/apache/pinot/core/plan/maker/PlanMaker.java index 70b6b14069df..36e62bd86735 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/plan/maker/PlanMaker.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/plan/maker/PlanMaker.java @@ -18,16 +18,16 @@ */ package org.apache.pinot.core.plan.maker; -import io.grpc.stub.StreamObserver; import java.util.List; import java.util.concurrent.ExecutorService; import org.apache.pinot.common.metrics.ServerMetrics; -import org.apache.pinot.common.proto.Server; import org.apache.pinot.core.plan.Plan; import org.apache.pinot.core.plan.PlanNode; +import org.apache.pinot.core.query.executor.ResultsBlockStreamer; import org.apache.pinot.core.query.request.context.QueryContext; -import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; import org.apache.pinot.spi.annotations.InterfaceAudience; +import org.apache.pinot.spi.env.PinotConfiguration; /** @@ -36,28 +36,32 @@ @InterfaceAudience.Private public interface PlanMaker { + /** + * Initializes the plan maker. + */ + void init(PinotConfiguration queryExecutorConfig); + /** * Returns an instance level {@link Plan} which contains the logical execution plan for multiple segments. */ - Plan makeInstancePlan(List indexSegments, QueryContext queryContext, ExecutorService executorService, - ServerMetrics serverMetrics); + Plan makeInstancePlan(List segmentContexts, QueryContext queryContext, + ExecutorService executorService, ServerMetrics serverMetrics); /** * Returns a segment level {@link PlanNode} which contains the logical execution plan for one segment. */ - PlanNode makeSegmentPlanNode(IndexSegment indexSegment, QueryContext queryContext); + PlanNode makeSegmentPlanNode(SegmentContext segmentContext, QueryContext queryContext); /** * Returns an instance level {@link Plan} for a streaming query which contains the logical execution plan for multiple * segments. */ - Plan makeStreamingInstancePlan(List indexSegments, QueryContext queryContext, - ExecutorService executorService, StreamObserver streamObserver, - ServerMetrics serverMetrics); + Plan makeStreamingInstancePlan(List segmentContexts, QueryContext queryContext, + ExecutorService executorService, ResultsBlockStreamer streamer, ServerMetrics serverMetrics); /** * Returns a segment level {@link PlanNode} for a streaming query which contains the logical execution plan for one * segment. */ - PlanNode makeStreamingSegmentPlanNode(IndexSegment indexSegment, QueryContext queryContext); + PlanNode makeStreamingSegmentPlanNode(SegmentContext segmentContext, QueryContext queryContext); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/AggregationExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/AggregationExecutor.java index e0def6f7f55d..620b4f7b08b9 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/AggregationExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/AggregationExecutor.java @@ -19,7 +19,7 @@ package org.apache.pinot.core.query.aggregation; import java.util.List; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; /** @@ -29,11 +29,9 @@ public interface AggregationExecutor { /** - * Performs aggregation on the given transform block. - * - * @param transformBlock Transform Block + * Performs aggregation on the given value block. */ - void aggregate(TransformBlock transformBlock); + void aggregate(ValueBlock valueBlock); /** * Returns the result of aggregation. diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/DefaultAggregationExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/DefaultAggregationExecutor.java index 706bf1de9d89..d6c64c1cfb68 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/DefaultAggregationExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/DefaultAggregationExecutor.java @@ -20,11 +20,12 @@ import java.util.ArrayList; import java.util.List; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.query.aggregation.function.AggregationFunction; import org.apache.pinot.core.query.aggregation.function.AggregationFunctionUtils; +@SuppressWarnings({"rawtypes", "unchecked"}) public class DefaultAggregationExecutor implements AggregationExecutor { protected final AggregationFunction[] _aggregationFunctions; protected final AggregationResultHolder[] _aggregationResultHolders; @@ -39,13 +40,13 @@ public DefaultAggregationExecutor(AggregationFunction[] aggregationFunctions) { } @Override - public void aggregate(TransformBlock transformBlock) { + public void aggregate(ValueBlock valueBlock) { int numAggregationFunctions = _aggregationFunctions.length; - int length = transformBlock.getNumDocs(); + int length = valueBlock.getNumDocs(); for (int i = 0; i < numAggregationFunctions; i++) { AggregationFunction aggregationFunction = _aggregationFunctions[i]; aggregationFunction.aggregate(length, _aggregationResultHolders[i], - AggregationFunctionUtils.getBlockValSetMap(aggregationFunction, transformBlock)); + AggregationFunctionUtils.getBlockValSetMap(aggregationFunction, valueBlock)); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/DoubleAggregationResultHolder.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/DoubleAggregationResultHolder.java index 7962b246d744..f0f7281f7463 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/DoubleAggregationResultHolder.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/DoubleAggregationResultHolder.java @@ -44,7 +44,7 @@ public void setValue(double value) { @Override public void setValue(int value) { - throw new RuntimeException("Method 'setValue' (with int value) not supported for class " + getClass().getName()); + throw new RuntimeException("Method 'setValue' (with int value) not supported in DoubleAggregationResultHolder"); } /** @@ -54,7 +54,7 @@ public void setValue(int value) { */ @Override public void setValue(Object value) { - throw new RuntimeException("Method 'setValue' (with object value) not supported for class " + getClass().getName()); + throw new RuntimeException("Method 'setValue' (with object value) not supported in DoubleAggregationResultHolder"); } /** @@ -73,7 +73,7 @@ public double getDoubleResult() { */ @Override public int getIntResult() { - throw new RuntimeException("Method 'getIntResult' not supported for class " + getClass().getName()); + throw new RuntimeException("Method 'getIntResult' not supported in DoubleAggregationResultHolder"); } /** @@ -83,6 +83,6 @@ public int getIntResult() { */ @Override public T getResult() { - throw new RuntimeException("Method 'getResult' not supported for class " + getClass().getName()); + throw new RuntimeException("Method 'getResult' not supported in DoubleAggregationResultHolder"); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/IntAggregateResultHolder.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/IntAggregateResultHolder.java index 78d29a204b2d..74fca7355485 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/IntAggregateResultHolder.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/IntAggregateResultHolder.java @@ -29,7 +29,7 @@ public IntAggregateResultHolder(int defaultValue) { @Override public void setValue(double value) { - throw new RuntimeException("Method 'setValue' (with double value) not supported for class " + getClass().getName()); + throw new RuntimeException("Method 'setValue' (with double value) not supported in IntAggregateResultHolder"); } @Override @@ -39,12 +39,12 @@ public void setValue(int value) { @Override public void setValue(Object value) { - throw new RuntimeException("Method 'setValue' (with object value) not supported for class " + getClass().getName()); + throw new RuntimeException("Method 'setValue' (with object value) not supported in IntAggregateResultHolder"); } @Override public double getDoubleResult() { - throw new RuntimeException("Method 'getDoubleResult' not supported for class " + getClass().getName()); + throw new RuntimeException("Method 'getDoubleResult' not supported in IntAggregateResultHolder"); } @Override @@ -54,6 +54,6 @@ public int getIntResult() { @Override public T getResult() { - throw new RuntimeException("Method 'getResult' not supported for class " + getClass().getName()); + throw new RuntimeException("Method 'getResult' not supported in IntAggregateResultHolder"); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/ObjectAggregationResultHolder.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/ObjectAggregationResultHolder.java index 817ea086f522..e058cee3e652 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/ObjectAggregationResultHolder.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/ObjectAggregationResultHolder.java @@ -54,7 +54,7 @@ public void setValue(int value) { */ @Override public double getDoubleResult() { - throw new RuntimeException("Method 'getDoubleResult' not supported for class " + getClass().getName()); + throw new RuntimeException("Method 'getDoubleResult' not supported in ObjectAggregationResultHolder"); } /** @@ -64,7 +64,7 @@ public double getDoubleResult() { */ @Override public int getIntResult() { - throw new RuntimeException("Method 'getIntResult' not supported for class " + getClass().getName()); + throw new RuntimeException("Method 'getIntResult' not supported in ObjectAggregationResultHolder"); } /** diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunction.java index 220feec5d125..c172ef5b9101 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunction.java @@ -46,11 +46,6 @@ public interface AggregationFunction 'sum_foo'. - */ - String getColumnName(); - /** * Returns the column name to be used in the data schema of results. * e.g. 'MINMAXRANGEMV( foo)' -> 'minmaxrangemv(foo)', 'PERCENTILE75(bar)' -> 'percentile75(bar)' @@ -129,6 +124,14 @@ void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder */ FinalResult extractFinalResult(IntermediateResult intermediateResult); + /** + * Merges two final results. This can be used to optimized certain functions (e.g. DISTINCT_COUNT) when data is + * partitioned on each server, where we may directly request servers to return final result and merge them on broker. + */ + default FinalResult mergeFinalResult(FinalResult finalResult1, FinalResult finalResult2) { + throw new UnsupportedOperationException("Cannot merge final results for function: " + getType()); + } + /** @return Description of this operator for Explain Plan */ String toExplainString(); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionFactory.java index 4ba341322153..96491acbd47e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionFactory.java @@ -20,12 +20,29 @@ import com.google.common.base.Preconditions; import java.util.List; -import org.apache.commons.lang3.StringUtils; +import org.apache.datasketches.tuple.aninteger.IntegerSummary; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.FunctionContext; -import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.query.aggregation.function.array.ArrayAggDistinctDoubleFunction; +import org.apache.pinot.core.query.aggregation.function.array.ArrayAggDistinctFloatFunction; +import org.apache.pinot.core.query.aggregation.function.array.ArrayAggDistinctIntFunction; +import org.apache.pinot.core.query.aggregation.function.array.ArrayAggDistinctLongFunction; +import org.apache.pinot.core.query.aggregation.function.array.ArrayAggDistinctStringFunction; +import org.apache.pinot.core.query.aggregation.function.array.ArrayAggDoubleFunction; +import org.apache.pinot.core.query.aggregation.function.array.ArrayAggFloatFunction; +import org.apache.pinot.core.query.aggregation.function.array.ArrayAggIntFunction; +import org.apache.pinot.core.query.aggregation.function.array.ArrayAggLongFunction; +import org.apache.pinot.core.query.aggregation.function.array.ArrayAggStringFunction; +import org.apache.pinot.core.query.aggregation.function.array.ListAggDistinctFunction; +import org.apache.pinot.core.query.aggregation.function.array.ListAggFunction; +import org.apache.pinot.core.query.aggregation.function.array.SumArrayDoubleAggregationFunction; +import org.apache.pinot.core.query.aggregation.function.array.SumArrayLongAggregationFunction; +import org.apache.pinot.core.query.aggregation.function.funnel.FunnelCountAggregationFunctionFactory; +import org.apache.pinot.core.query.aggregation.function.funnel.window.FunnelCompleteCountAggregationFunction; +import org.apache.pinot.core.query.aggregation.function.funnel.window.FunnelMatchStepAggregationFunction; +import org.apache.pinot.core.query.aggregation.function.funnel.window.FunnelMaxStepAggregationFunction; import org.apache.pinot.segment.spi.AggregationFunctionType; -import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.exception.BadQueryRequestException; @@ -39,44 +56,59 @@ private AggregationFunctionFactory() { /** * Given the function information, returns a new instance of the corresponding aggregation function. - *

NOTE: Underscores in the function name are ignored. - *

NOTE: We pass the query context to this method because DISTINCT is currently modeled as an aggregation function - * and requires the order-by and limit information from the query. - *

TODO: Consider modeling DISTINCT as unique selection instead of aggregation so that early-termination, limit and - * offset can be applied easier + *

NOTE: Underscores in the function name are ignored in V1. */ - public static AggregationFunction getAggregationFunction(FunctionContext function, QueryContext queryContext) { + public static AggregationFunction getAggregationFunction(FunctionContext function, boolean nullHandlingEnabled) { try { - String upperCaseFunctionName = StringUtils.remove(function.getFunctionName(), '_').toUpperCase(); + String upperCaseFunctionName = + AggregationFunctionType.getNormalizedAggregationFunctionName(function.getFunctionName()); List arguments = function.getArguments(); + int numArguments = arguments.size(); ExpressionContext firstArgument = arguments.get(0); if (upperCaseFunctionName.startsWith("PERCENTILE")) { String remainingFunctionName = upperCaseFunctionName.substring(10); if (remainingFunctionName.equals("SMARTTDIGEST")) { - return new PercentileSmartTDigestAggregationFunction(arguments); + return new PercentileSmartTDigestAggregationFunction(arguments, nullHandlingEnabled); + } + if (remainingFunctionName.equals("KLL")) { + return new PercentileKLLAggregationFunction(arguments, nullHandlingEnabled); + } + if (remainingFunctionName.equals("KLLMV")) { + return new PercentileKLLMVAggregationFunction(arguments); + } + if (remainingFunctionName.equals("RAWKLL")) { + return new PercentileRawKLLAggregationFunction(arguments, nullHandlingEnabled); + } + if (remainingFunctionName.equals("RAWKLLMV")) { + return new PercentileRawKLLMVAggregationFunction(arguments); } - int numArguments = arguments.size(); if (numArguments == 1) { // Single argument percentile (e.g. Percentile99(foo), PercentileTDigest95(bar), etc.) + // NOTE: This convention is deprecated. DO NOT add new functions here if (remainingFunctionName.matches("\\d+")) { // Percentile - return new PercentileAggregationFunction(firstArgument, parsePercentileToInt(remainingFunctionName)); + return new PercentileAggregationFunction(firstArgument, parsePercentileToInt(remainingFunctionName), + nullHandlingEnabled); } else if (remainingFunctionName.matches("EST\\d+")) { // PercentileEst String percentileString = remainingFunctionName.substring(3); - return new PercentileEstAggregationFunction(firstArgument, parsePercentileToInt(percentileString)); + return new PercentileEstAggregationFunction(firstArgument, parsePercentileToInt(percentileString), + nullHandlingEnabled); } else if (remainingFunctionName.matches("RAWEST\\d+")) { // PercentileRawEst String percentileString = remainingFunctionName.substring(6); - return new PercentileRawEstAggregationFunction(firstArgument, parsePercentileToInt(percentileString)); + return new PercentileRawEstAggregationFunction(firstArgument, parsePercentileToInt(percentileString), + nullHandlingEnabled); } else if (remainingFunctionName.matches("TDIGEST\\d+")) { // PercentileTDigest String percentileString = remainingFunctionName.substring(7); - return new PercentileTDigestAggregationFunction(firstArgument, parsePercentileToInt(percentileString)); + return new PercentileTDigestAggregationFunction(firstArgument, parsePercentileToInt(percentileString), + nullHandlingEnabled); } else if (remainingFunctionName.matches("RAWTDIGEST\\d+")) { // PercentileRawTDigest String percentileString = remainingFunctionName.substring(10); - return new PercentileRawTDigestAggregationFunction(firstArgument, parsePercentileToInt(percentileString)); + return new PercentileRawTDigestAggregationFunction(firstArgument, parsePercentileToInt(percentileString), + nullHandlingEnabled); } else if (remainingFunctionName.matches("\\d+MV")) { // PercentileMV String percentileString = remainingFunctionName.substring(0, remainingFunctionName.length() - 2); @@ -101,27 +133,27 @@ public static AggregationFunction getAggregationFunction(FunctionContext functio } else if (numArguments == 2) { // Double arguments percentile (e.g. percentile(foo, 99), percentileTDigest(bar, 95), etc.) where the // second argument is a decimal number from 0.0 to 100.0. - // Have to use literal string because we need to cast int to double here. - double percentile = parsePercentileToDouble(arguments.get(1).getLiteralString()); + double percentile = arguments.get(1).getLiteral().getDoubleValue(); + Preconditions.checkArgument(percentile >= 0 && percentile <= 100, "Invalid percentile: %s", percentile); if (remainingFunctionName.isEmpty()) { // Percentile - return new PercentileAggregationFunction(firstArgument, percentile); + return new PercentileAggregationFunction(firstArgument, percentile, nullHandlingEnabled); } if (remainingFunctionName.equals("EST")) { // PercentileEst - return new PercentileEstAggregationFunction(firstArgument, percentile); + return new PercentileEstAggregationFunction(firstArgument, percentile, nullHandlingEnabled); } if (remainingFunctionName.equals("RAWEST")) { // PercentileRawEst - return new PercentileRawEstAggregationFunction(firstArgument, percentile); + return new PercentileRawEstAggregationFunction(firstArgument, percentile, nullHandlingEnabled); } if (remainingFunctionName.equals("TDIGEST")) { // PercentileTDigest - return new PercentileTDigestAggregationFunction(firstArgument, percentile); + return new PercentileTDigestAggregationFunction(firstArgument, percentile, nullHandlingEnabled); } if (remainingFunctionName.equals("RAWTDIGEST")) { // PercentileRawTDigest - return new PercentileRawTDigestAggregationFunction(firstArgument, percentile); + return new PercentileRawTDigestAggregationFunction(firstArgument, percentile, nullHandlingEnabled); } if (remainingFunctionName.equals("MV")) { // PercentileMV @@ -143,91 +175,193 @@ public static AggregationFunction getAggregationFunction(FunctionContext functio // PercentileRawTDigestMV return new PercentileRawTDigestMVAggregationFunction(firstArgument, percentile); } + } else if (numArguments == 3) { + // Triple arguments percentile (e.g. percentileTDigest(bar, 95, 1000), etc.) where the + // second argument is a decimal number from 0.0 to 100.0 and third argument is a decimal number indicating + // the compression_factor for the TDigest. This can only be used for TDigest type percentile functions to + // pass in a custom compression_factor. If the two argument version is used the default compression_factor + // of 100.0 is used. + double percentile = arguments.get(1).getLiteral().getDoubleValue(); + Preconditions.checkArgument(percentile >= 0 && percentile <= 100, "Invalid percentile: %s", percentile); + int compressionFactor = arguments.get(2).getLiteral().getIntValue(); + Preconditions.checkArgument(compressionFactor >= 0, "Invalid compressionFactor: %d", compressionFactor); + if (remainingFunctionName.equals("TDIGEST")) { + // PercentileTDigest + return new PercentileTDigestAggregationFunction(firstArgument, percentile, compressionFactor, + nullHandlingEnabled); + } + if (remainingFunctionName.equals("RAWTDIGEST")) { + // PercentileRawTDigest + return new PercentileRawTDigestAggregationFunction(firstArgument, percentile, compressionFactor, + nullHandlingEnabled); + } + if (remainingFunctionName.equals("TDIGESTMV")) { + // PercentileTDigestMV + return new PercentileTDigestMVAggregationFunction(firstArgument, percentile, compressionFactor); + } + if (remainingFunctionName.equals("RAWTDIGESTMV")) { + // PercentileRawTDigestMV + return new PercentileRawTDigestMVAggregationFunction(firstArgument, percentile, compressionFactor); + } } throw new IllegalArgumentException("Invalid percentile function: " + function); } else { - switch (AggregationFunctionType.valueOf(upperCaseFunctionName)) { + AggregationFunctionType functionType = AggregationFunctionType.valueOf(upperCaseFunctionName); + switch (functionType) { case COUNT: - return new CountAggregationFunction(firstArgument, queryContext.isNullHandlingEnabled()); + return new CountAggregationFunction(arguments, nullHandlingEnabled); case MIN: - return new MinAggregationFunction(firstArgument, queryContext.isNullHandlingEnabled()); + return new MinAggregationFunction(arguments, nullHandlingEnabled); case MAX: - return new MaxAggregationFunction(firstArgument, queryContext.isNullHandlingEnabled()); + return new MaxAggregationFunction(arguments, nullHandlingEnabled); case SUM: - return new SumAggregationFunction(firstArgument, queryContext.isNullHandlingEnabled()); + case SUM0: + return new SumAggregationFunction(arguments, nullHandlingEnabled); case SUMPRECISION: - return new SumPrecisionAggregationFunction(arguments, queryContext.isNullHandlingEnabled()); + return new SumPrecisionAggregationFunction(arguments, nullHandlingEnabled); case AVG: - return new AvgAggregationFunction(firstArgument, queryContext.isNullHandlingEnabled()); + return new AvgAggregationFunction(arguments, nullHandlingEnabled); case MODE: - return new ModeAggregationFunction(arguments); - case FIRSTWITHTIME: - if (arguments.size() == 3) { - ExpressionContext timeCol = arguments.get(1); - ExpressionContext dataType = arguments.get(2); - if (dataType.getType() != ExpressionContext.Type.LITERAL) { - throw new IllegalArgumentException("Third argument of firstWithTime Function should be literal." - + " The function can be used as firstWithTime(dataColumn, timeColumn, 'dataType')"); - } - FieldSpec.DataType fieldDataType - = FieldSpec.DataType.valueOf(dataType.getLiteralString().toUpperCase()); - switch (fieldDataType) { - case BOOLEAN: - case INT: - return new FirstIntValueWithTimeAggregationFunction( - firstArgument, timeCol, fieldDataType == FieldSpec.DataType.BOOLEAN); - case LONG: - return new FirstLongValueWithTimeAggregationFunction(firstArgument, timeCol); - case FLOAT: - return new FirstFloatValueWithTimeAggregationFunction(firstArgument, timeCol); - case DOUBLE: - return new FirstDoubleValueWithTimeAggregationFunction(firstArgument, timeCol); - case STRING: - return new FirstStringValueWithTimeAggregationFunction(firstArgument, timeCol); - default: - throw new IllegalArgumentException("Unsupported Value Type for firstWithTime Function:" + dataType); - } - } else { - throw new IllegalArgumentException("Three arguments are required for firstWithTime Function." - + " The function can be used as firstWithTime(dataColumn, timeColumn, 'dataType')"); + return new ModeAggregationFunction(arguments, nullHandlingEnabled); + case FIRSTWITHTIME: { + Preconditions.checkArgument(numArguments == 3, + "FIRST_WITH_TIME expects 3 arguments, got: %s. The function can be used as " + + "firstWithTime(dataColumn, timeColumn, 'dataType')", numArguments); + ExpressionContext timeCol = arguments.get(1); + ExpressionContext dataTypeExp = arguments.get(2); + Preconditions.checkArgument(dataTypeExp.getType() == ExpressionContext.Type.LITERAL, + "FIRST_WITH_TIME expects the 3rd argument to be literal, got: %s. The function can be used as " + + "firstWithTime(dataColumn, timeColumn, 'dataType')", dataTypeExp.getType()); + DataType dataType = DataType.valueOf(dataTypeExp.getLiteral().getStringValue().toUpperCase()); + switch (dataType) { + case BOOLEAN: + return new FirstIntValueWithTimeAggregationFunction(firstArgument, timeCol, nullHandlingEnabled, + true); + case INT: + return new FirstIntValueWithTimeAggregationFunction(firstArgument, timeCol, nullHandlingEnabled, + false); + case LONG: + return new FirstLongValueWithTimeAggregationFunction(firstArgument, timeCol, nullHandlingEnabled); + case FLOAT: + return new FirstFloatValueWithTimeAggregationFunction(firstArgument, timeCol, nullHandlingEnabled); + case DOUBLE: + return new FirstDoubleValueWithTimeAggregationFunction(firstArgument, timeCol, nullHandlingEnabled); + case STRING: + return new FirstStringValueWithTimeAggregationFunction(firstArgument, timeCol, nullHandlingEnabled); + default: + throw new IllegalArgumentException("Unsupported data type for FIRST_WITH_TIME: " + dataType); } - case LASTWITHTIME: - if (arguments.size() == 3) { - ExpressionContext timeCol = arguments.get(1); - ExpressionContext dataType = arguments.get(2); - if (dataType.getType() != ExpressionContext.Type.LITERAL) { - throw new IllegalArgumentException("Third argument of lastWithTime Function should be literal." - + " The function can be used as lastWithTime(dataColumn, timeColumn, 'dataType')"); - } - FieldSpec.DataType fieldDataType = FieldSpec.DataType.valueOf(dataType.getLiteralString().toUpperCase()); - switch (fieldDataType) { + } + case LISTAGG: { + Preconditions.checkArgument(numArguments == 2 || numArguments == 3, + "LISTAGG expects 2 arguments, got: %s. The function can be used as " + + "listAgg([distinct] expression, 'separator')", numArguments); + ExpressionContext separatorExpression = arguments.get(1); + Preconditions.checkArgument(separatorExpression.getType() == ExpressionContext.Type.LITERAL, + "LISTAGG expects the 2nd argument to be literal, got: %s. The function can be used as " + + "listAgg([distinct] expression, 'separator')", separatorExpression.getType()); + String separator = separatorExpression.getLiteral().getStringValue(); + boolean isDistinct = false; + if (numArguments == 3) { + ExpressionContext isDistinctListAggExp = arguments.get(2); + isDistinct = isDistinctListAggExp.getLiteral().getBooleanValue(); + } + if (isDistinct) { + return new ListAggDistinctFunction(arguments.get(0), separator, nullHandlingEnabled); + } + return new ListAggFunction(arguments.get(0), separator, nullHandlingEnabled); + } + case SUMARRAYLONG: + return new SumArrayLongAggregationFunction(arguments); + case SUMARRAYDOUBLE: + return new SumArrayDoubleAggregationFunction(arguments); + case ARRAYAGG: { + Preconditions.checkArgument(numArguments >= 2, + "ARRAY_AGG expects 2 or 3 arguments, got: %s. The function can be used as " + + "arrayAgg(dataColumn, 'dataType', ['isDistinct'])", numArguments); + ExpressionContext dataTypeExp = arguments.get(1); + Preconditions.checkArgument(dataTypeExp.getType() == ExpressionContext.Type.LITERAL, + "ARRAY_AGG expects the 2nd argument to be literal, got: %s. The function can be used as " + + "arrayAgg(dataColumn, 'dataType', ['isDistinct'])", dataTypeExp.getType()); + DataType dataType = DataType.valueOf(dataTypeExp.getLiteral().getStringValue().toUpperCase()); + boolean isDistinct = false; + if (numArguments == 3) { + ExpressionContext isDistinctExp = arguments.get(2); + Preconditions.checkArgument(isDistinctExp.getType() == ExpressionContext.Type.LITERAL, + "ARRAY_AGG expects the 3rd argument to be literal, got: %s. The function can be used as " + + "arrayAgg(dataColumn, 'dataType', ['isDistinct'])", isDistinctExp.getType()); + isDistinct = isDistinctExp.getLiteral().getBooleanValue(); + } + if (isDistinct) { + switch (dataType) { case BOOLEAN: case INT: - return new LastIntValueWithTimeAggregationFunction(firstArgument, timeCol, - fieldDataType == FieldSpec.DataType.BOOLEAN); + return new ArrayAggDistinctIntFunction(firstArgument, dataType, nullHandlingEnabled); case LONG: - return new LastLongValueWithTimeAggregationFunction(firstArgument, timeCol); + case TIMESTAMP: + return new ArrayAggDistinctLongFunction(firstArgument, dataType, nullHandlingEnabled); case FLOAT: - return new LastFloatValueWithTimeAggregationFunction(firstArgument, timeCol); + return new ArrayAggDistinctFloatFunction(firstArgument, nullHandlingEnabled); case DOUBLE: - return new LastDoubleValueWithTimeAggregationFunction(firstArgument, timeCol); + return new ArrayAggDistinctDoubleFunction(firstArgument, nullHandlingEnabled); case STRING: - return new LastStringValueWithTimeAggregationFunction(firstArgument, timeCol); + return new ArrayAggDistinctStringFunction(firstArgument, nullHandlingEnabled); default: - throw new IllegalArgumentException("Unsupported Value Type for lastWithTime Function:" + dataType); + throw new IllegalArgumentException("Unsupported data type for ARRAY_AGG: " + dataType); } - } else { - throw new IllegalArgumentException("Three arguments are required for lastWithTime Function." - + " The function can be used as lastWithTime(dataColumn, timeColumn, 'dataType')"); } + switch (dataType) { + case BOOLEAN: + case INT: + return new ArrayAggIntFunction(firstArgument, dataType, nullHandlingEnabled); + case LONG: + case TIMESTAMP: + return new ArrayAggLongFunction(firstArgument, dataType, nullHandlingEnabled); + case FLOAT: + return new ArrayAggFloatFunction(firstArgument, nullHandlingEnabled); + case DOUBLE: + return new ArrayAggDoubleFunction(firstArgument, nullHandlingEnabled); + case STRING: + return new ArrayAggStringFunction(firstArgument, nullHandlingEnabled); + default: + throw new IllegalArgumentException("Unsupported data type for ARRAY_AGG: " + dataType); + } + } + case LASTWITHTIME: { + Preconditions.checkArgument(numArguments == 3, + "LAST_WITH_TIME expects 3 arguments, got: %s. The function can be used as " + + "lastWithTime(dataColumn, timeColumn, 'dataType')", numArguments); + ExpressionContext timeCol = arguments.get(1); + ExpressionContext dataTypeExp = arguments.get(2); + Preconditions.checkArgument(dataTypeExp.getType() == ExpressionContext.Type.LITERAL, + "LAST_WITH_TIME expects the 3rd argument to be literal, got: %s. The function can be used as " + + "lastWithTime(dataColumn, timeColumn, 'dataType')", dataTypeExp.getType()); + DataType dataType = DataType.valueOf(dataTypeExp.getLiteral().getStringValue().toUpperCase()); + switch (dataType) { + case BOOLEAN: + return new LastIntValueWithTimeAggregationFunction(firstArgument, timeCol, nullHandlingEnabled, true); + case INT: + return new LastIntValueWithTimeAggregationFunction(firstArgument, timeCol, nullHandlingEnabled, false); + case LONG: + return new LastLongValueWithTimeAggregationFunction(firstArgument, timeCol, nullHandlingEnabled); + case FLOAT: + return new LastFloatValueWithTimeAggregationFunction(firstArgument, timeCol, nullHandlingEnabled); + case DOUBLE: + return new LastDoubleValueWithTimeAggregationFunction(firstArgument, timeCol, nullHandlingEnabled); + case STRING: + return new LastStringValueWithTimeAggregationFunction(firstArgument, timeCol, nullHandlingEnabled); + default: + throw new IllegalArgumentException("Unsupported data type for LAST_WITH_TIME: " + dataType); + } + } case MINMAXRANGE: - return new MinMaxRangeAggregationFunction(firstArgument); + return new MinMaxRangeAggregationFunction(arguments, nullHandlingEnabled); case DISTINCTCOUNT: - return new DistinctCountAggregationFunction(firstArgument); + return new DistinctCountAggregationFunction(arguments, nullHandlingEnabled); case DISTINCTCOUNTBITMAP: - return new DistinctCountBitmapAggregationFunction(firstArgument); + return new DistinctCountBitmapAggregationFunction(arguments); case SEGMENTPARTITIONEDDISTINCTCOUNT: - return new SegmentPartitionedDistinctCountAggregationFunction(firstArgument); + return new SegmentPartitionedDistinctCountAggregationFunction(arguments); case DISTINCTCOUNTHLL: return new DistinctCountHLLAggregationFunction(arguments); case DISTINCTCOUNTRAWHLL: @@ -235,38 +369,51 @@ public static AggregationFunction getAggregationFunction(FunctionContext functio case DISTINCTCOUNTSMARTHLL: return new DistinctCountSmartHLLAggregationFunction(arguments); case FASTHLL: - return new FastHLLAggregationFunction(firstArgument); + return new FastHLLAggregationFunction(arguments); case DISTINCTCOUNTTHETASKETCH: return new DistinctCountThetaSketchAggregationFunction(arguments); case DISTINCTCOUNTRAWTHETASKETCH: return new DistinctCountRawThetaSketchAggregationFunction(arguments); + case DISTINCTSUM: + return new DistinctSumAggregationFunction(arguments, nullHandlingEnabled); + case DISTINCTAVG: + return new DistinctAvgAggregationFunction(arguments, nullHandlingEnabled); case IDSET: return new IdSetAggregationFunction(arguments); case COUNTMV: - return new CountMVAggregationFunction(firstArgument); + return new CountMVAggregationFunction(arguments); case MINMV: - return new MinMVAggregationFunction(firstArgument); + return new MinMVAggregationFunction(arguments); case MAXMV: - return new MaxMVAggregationFunction(firstArgument); + return new MaxMVAggregationFunction(arguments); case SUMMV: - return new SumMVAggregationFunction(firstArgument); + return new SumMVAggregationFunction(arguments); case AVGMV: - return new AvgMVAggregationFunction(firstArgument); + return new AvgMVAggregationFunction(arguments); case MINMAXRANGEMV: - return new MinMaxRangeMVAggregationFunction(firstArgument); + return new MinMaxRangeMVAggregationFunction(arguments); case DISTINCTCOUNTMV: - return new DistinctCountMVAggregationFunction(firstArgument); + return new DistinctCountMVAggregationFunction(arguments); case DISTINCTCOUNTBITMAPMV: - return new DistinctCountBitmapMVAggregationFunction(firstArgument); + return new DistinctCountBitmapMVAggregationFunction(arguments); case DISTINCTCOUNTHLLMV: return new DistinctCountHLLMVAggregationFunction(arguments); case DISTINCTCOUNTRAWHLLMV: return new DistinctCountRawHLLMVAggregationFunction(arguments); - case DISTINCT: - return new DistinctAggregationFunction(arguments, queryContext.getOrderByExpressions(), - queryContext.getLimit()); + case DISTINCTCOUNTHLLPLUS: + return new DistinctCountHLLPlusAggregationFunction(arguments); + case DISTINCTCOUNTRAWHLLPLUS: + return new DistinctCountRawHLLPlusAggregationFunction(arguments); + case DISTINCTCOUNTHLLPLUSMV: + return new DistinctCountHLLPlusMVAggregationFunction(arguments); + case DISTINCTCOUNTRAWHLLPLUSMV: + return new DistinctCountRawHLLPlusMVAggregationFunction(arguments); + case DISTINCTSUMMV: + return new DistinctSumMVAggregationFunction(arguments); + case DISTINCTAVGMV: + return new DistinctAvgMVAggregationFunction(arguments); case STUNION: - return new StUnionAggregationFunction(firstArgument); + return new StUnionAggregationFunction(arguments); case HISTOGRAM: return new HistogramAggregationFunction(arguments); case COVARPOP: @@ -274,28 +421,70 @@ public static AggregationFunction getAggregationFunction(FunctionContext functio case COVARSAMP: return new CovarianceAggregationFunction(arguments, true); case BOOLAND: - return new BooleanAndAggregationFunction(firstArgument, queryContext.isNullHandlingEnabled()); + return new BooleanAndAggregationFunction(arguments, nullHandlingEnabled); case BOOLOR: - return new BooleanOrAggregationFunction(firstArgument, queryContext.isNullHandlingEnabled()); + return new BooleanOrAggregationFunction(arguments, nullHandlingEnabled); case VARPOP: - return new VarianceAggregationFunction(firstArgument, false, false); + return new VarianceAggregationFunction(arguments, false, false, nullHandlingEnabled); case VARSAMP: - return new VarianceAggregationFunction(firstArgument, true, false); + return new VarianceAggregationFunction(arguments, true, false, nullHandlingEnabled); case STDDEVPOP: - return new VarianceAggregationFunction(firstArgument, false, true); + return new VarianceAggregationFunction(arguments, false, true, nullHandlingEnabled); case STDDEVSAMP: - return new VarianceAggregationFunction(firstArgument, true, true); + return new VarianceAggregationFunction(arguments, true, true, nullHandlingEnabled); case SKEWNESS: - return new FourthMomentAggregationFunction(firstArgument, FourthMomentAggregationFunction.Type.SKEWNESS); + return new FourthMomentAggregationFunction(arguments, FourthMomentAggregationFunction.Type.SKEWNESS); case KURTOSIS: - return new FourthMomentAggregationFunction(firstArgument, FourthMomentAggregationFunction.Type.KURTOSIS); + return new FourthMomentAggregationFunction(arguments, FourthMomentAggregationFunction.Type.KURTOSIS); + case FOURTHMOMENT: + return new FourthMomentAggregationFunction(arguments, FourthMomentAggregationFunction.Type.MOMENT); + case DISTINCTCOUNTTUPLESKETCH: + // mode actually doesn't matter here because we only care about keys, not values + return new DistinctCountIntegerTupleSketchAggregationFunction(arguments, IntegerSummary.Mode.Sum); + case DISTINCTCOUNTRAWINTEGERSUMTUPLESKETCH: + return new IntegerTupleSketchAggregationFunction(arguments, IntegerSummary.Mode.Sum); + case SUMVALUESINTEGERSUMTUPLESKETCH: + return new SumValuesIntegerTupleSketchAggregationFunction(arguments, IntegerSummary.Mode.Sum); + case AVGVALUEINTEGERSUMTUPLESKETCH: + return new AvgValueIntegerTupleSketchAggregationFunction(arguments, IntegerSummary.Mode.Sum); + case PINOTPARENTAGGEXPRMAX: + return new ParentExprMinMaxAggregationFunction(arguments, true); + case PINOTPARENTAGGEXPRMIN: + return new ParentExprMinMaxAggregationFunction(arguments, false); + case PINOTCHILDAGGEXPRMAX: + return new ChildExprMinMaxAggregationFunction(arguments, true); + case PINOTCHILDAGGEXPRMIN: + return new ChildExprMinMaxAggregationFunction(arguments, false); + case EXPRMAX: + case EXPRMIN: + throw new IllegalArgumentException( + "Aggregation function: " + functionType + " is only supported in selection without alias."); + case FUNNELCOUNT: + return new FunnelCountAggregationFunctionFactory(arguments).get(); + case FUNNELMAXSTEP: + return new FunnelMaxStepAggregationFunction(arguments); + case FUNNELMATCHSTEP: + return new FunnelMatchStepAggregationFunction(arguments); + case FUNNELCOMPLETECOUNT: + return new FunnelCompleteCountAggregationFunction(arguments); + case FREQUENTSTRINGSSKETCH: + return new FrequentStringsSketchAggregationFunction(arguments); + case FREQUENTLONGSSKETCH: + return new FrequentLongsSketchAggregationFunction(arguments); + case DISTINCTCOUNTCPCSKETCH: + return new DistinctCountCPCSketchAggregationFunction(arguments); + case DISTINCTCOUNTRAWCPCSKETCH: + return new DistinctCountRawCPCSketchAggregationFunction(arguments); + case DISTINCTCOUNTULL: + return new DistinctCountULLAggregationFunction(arguments); + case DISTINCTCOUNTRAWULL: + return new DistinctCountRawULLAggregationFunction(arguments); default: - throw new IllegalArgumentException(); + throw new IllegalArgumentException("Unsupported aggregation function type: " + functionType); } } } catch (Exception e) { - throw new BadQueryRequestException( - "Invalid aggregation function: " + function + "; Reason: " + e.getMessage()); + throw new BadQueryRequestException("Invalid aggregation function: " + function + "; Reason: " + e.getMessage()); } } @@ -304,10 +493,4 @@ private static int parsePercentileToInt(String percentileString) { Preconditions.checkArgument(percentile >= 0 && percentile <= 100, "Invalid percentile: %s", percentile); return percentile; } - - private static double parsePercentileToDouble(String percentileString) { - double percentile = Double.parseDouble(percentileString); - Preconditions.checkArgument(percentile >= 0d && percentile <= 100d, "Invalid percentile: %s", percentile); - return percentile; - } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionUtils.java index 8ef21fa1b460..99a21655036e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionUtils.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionUtils.java @@ -18,7 +18,13 @@ */ package org.apache.pinot.core.query.aggregation.function; -import java.util.Arrays; +import it.unimi.dsi.fastutil.doubles.DoubleArrayList; +import it.unimi.dsi.fastutil.floats.FloatArrayList; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.sql.Timestamp; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -26,13 +32,27 @@ import java.util.Map; import java.util.Set; import javax.annotation.Nullable; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.pinot.common.CustomObject; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.request.context.FilterContext; +import org.apache.pinot.common.request.context.predicate.Predicate; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.common.ObjectSerDeUtils; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.BaseProjectOperator; +import org.apache.pinot.core.operator.blocks.ValueBlock; +import org.apache.pinot.core.operator.filter.BaseFilterOperator; +import org.apache.pinot.core.operator.filter.CombinedFilterOperator; +import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; +import org.apache.pinot.core.plan.DocIdSetPlanNode; +import org.apache.pinot.core.plan.FilterPlanNode; +import org.apache.pinot.core.plan.ProjectPlanNode; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.startree.StarTreeUtils; import org.apache.pinot.segment.spi.AggregationFunctionType; +import org.apache.pinot.segment.spi.SegmentContext; import org.apache.pinot.segment.spi.index.startree.AggregationFunctionColumnPair; @@ -45,22 +65,23 @@ private AggregationFunctionUtils() { } /** - * (For Star-Tree) Creates an {@link AggregationFunctionColumnPair} from the {@link AggregationFunction}. Returns - * {@code null} if the {@link AggregationFunction} cannot be represented as an {@link AggregationFunctionColumnPair} - * (e.g. has multiple arguments, argument is not column etc.). + * (For Star-Tree) Creates an {@link AggregationFunctionColumnPair} in stored type from the + * {@link AggregationFunction}. Returns {@code null} if the {@link AggregationFunction} cannot be represented as an + * {@link AggregationFunctionColumnPair} (e.g. has multiple arguments, argument is not column etc.). + * TODO: Allow multiple arguments for aggregation functions, e.g. percentileEst */ @Nullable - public static AggregationFunctionColumnPair getAggregationFunctionColumnPair( - AggregationFunction aggregationFunction) { - AggregationFunctionType aggregationFunctionType = aggregationFunction.getType(); - if (aggregationFunctionType == AggregationFunctionType.COUNT) { + public static AggregationFunctionColumnPair getStoredFunctionColumnPair(AggregationFunction aggregationFunction) { + AggregationFunctionType functionType = aggregationFunction.getType(); + if (functionType == AggregationFunctionType.COUNT) { return AggregationFunctionColumnPair.COUNT_STAR; } List inputExpressions = aggregationFunction.getInputExpressions(); if (inputExpressions.size() == 1) { ExpressionContext inputExpression = inputExpressions.get(0); if (inputExpression.getType() == ExpressionContext.Type.IDENTIFIER) { - return new AggregationFunctionColumnPair(aggregationFunctionType, inputExpression.getIdentifier()); + return new AggregationFunctionColumnPair(AggregationFunctionColumnPair.getStoredType(functionType), + inputExpression.getIdentifier()); } } return null; @@ -72,23 +93,23 @@ public static AggregationFunctionColumnPair getAggregationFunctionColumnPair( * or group-by expressions. */ public static Set collectExpressionsToTransform(AggregationFunction[] aggregationFunctions, - @Nullable ExpressionContext[] groupByExpressions) { + @Nullable List groupByExpressions) { Set expressions = new HashSet<>(); for (AggregationFunction aggregationFunction : aggregationFunctions) { expressions.addAll(aggregationFunction.getInputExpressions()); } if (groupByExpressions != null) { - expressions.addAll(Arrays.asList(groupByExpressions)); + expressions.addAll(groupByExpressions); } return expressions; } /** * Creates a map from expression required by the {@link AggregationFunction} to {@link BlockValSet} fetched from the - * {@link TransformBlock}. + * {@link ValueBlock}. */ public static Map getBlockValSetMap(AggregationFunction aggregationFunction, - TransformBlock transformBlock) { + ValueBlock valueBlock) { //noinspection unchecked List expressions = aggregationFunction.getInputExpressions(); int numExpressions = expressions.size(); @@ -97,25 +118,25 @@ public static Map getBlockValSetMap(AggregationF } if (numExpressions == 1) { ExpressionContext expression = expressions.get(0); - return Collections.singletonMap(expression, transformBlock.getBlockValueSet(expression)); + return Collections.singletonMap(expression, valueBlock.getBlockValueSet(expression)); } Map blockValSetMap = new HashMap<>(); for (ExpressionContext expression : expressions) { - blockValSetMap.put(expression, transformBlock.getBlockValueSet(expression)); + blockValSetMap.put(expression, valueBlock.getBlockValueSet(expression)); } return blockValSetMap; } /** * (For Star-Tree) Creates a map from expression required by the {@link AggregationFunctionColumnPair} to - * {@link BlockValSet} fetched from the {@link TransformBlock}. + * {@link BlockValSet} fetched from the {@link ValueBlock}. *

NOTE: We construct the map with original column name as the key but fetch BlockValSet with the aggregation * function pair so that the aggregation result column name is consistent with or without star-tree. */ public static Map getBlockValSetMap( - AggregationFunctionColumnPair aggregationFunctionColumnPair, TransformBlock transformBlock) { + AggregationFunctionColumnPair aggregationFunctionColumnPair, ValueBlock valueBlock) { ExpressionContext expression = ExpressionContext.forIdentifier(aggregationFunctionColumnPair.getColumn()); - BlockValSet blockValSet = transformBlock.getBlockValueSet(aggregationFunctionColumnPair.toColumnName()); + BlockValSet blockValSet = valueBlock.getBlockValueSet(aggregationFunctionColumnPair.toColumnName()); return Collections.singletonMap(expression, blockValSet); } @@ -125,22 +146,58 @@ public static Map getBlockValSetMap( * TODO: Move ser/de into AggregationFunction interface */ public static Object getIntermediateResult(DataTable dataTable, ColumnDataType columnDataType, int rowId, int colId) { - switch (columnDataType) { + switch (columnDataType.getStoredType()) { + case INT: + return dataTable.getInt(rowId, colId); case LONG: return dataTable.getLong(rowId, colId); case DOUBLE: return dataTable.getDouble(rowId, colId); case OBJECT: - DataTable.CustomObject customObject = dataTable.getCustomObject(rowId, colId); + CustomObject customObject = dataTable.getCustomObject(rowId, colId); return customObject != null ? ObjectSerDeUtils.deserialize(customObject) : null; default: throw new IllegalStateException("Illegal column data type in intermediate result: " + columnDataType); } } + /** + * Reads the final result from the {@link DataTable}. + */ + public static Comparable getFinalResult(DataTable dataTable, ColumnDataType columnDataType, int rowId, int colId) { + switch (columnDataType.getStoredType()) { + case INT: + return dataTable.getInt(rowId, colId); + case LONG: + return dataTable.getLong(rowId, colId); + case FLOAT: + return dataTable.getFloat(rowId, colId); + case DOUBLE: + return dataTable.getDouble(rowId, colId); + case BIG_DECIMAL: + return dataTable.getBigDecimal(rowId, colId); + case STRING: + return dataTable.getString(rowId, colId); + case BYTES: + return dataTable.getBytes(rowId, colId); + case INT_ARRAY: + return IntArrayList.wrap(dataTable.getIntArray(rowId, colId)); + case LONG_ARRAY: + return LongArrayList.wrap(dataTable.getLongArray(rowId, colId)); + case FLOAT_ARRAY: + return FloatArrayList.wrap(dataTable.getFloatArray(rowId, colId)); + case DOUBLE_ARRAY: + return DoubleArrayList.wrap(dataTable.getDoubleArray(rowId, colId)); + case STRING_ARRAY: + return ObjectArrayList.wrap(dataTable.getStringArray(rowId, colId)); + default: + throw new IllegalStateException("Illegal column data type in final result: " + columnDataType); + } + } + /** * Reads the converted final result from the {@link DataTable}. It should be equivalent to running - * {@link AggregationFunction#extractFinalResult(Object)} and {@link ColumnDataType#convert(Object)}. + * {@link #getFinalResult} and {@link ColumnDataType#convert}. */ public static Object getConvertedFinalResult(DataTable dataTable, ColumnDataType columnDataType, int rowId, int colId) { @@ -155,14 +212,208 @@ public static Object getConvertedFinalResult(DataTable dataTable, ColumnDataType return dataTable.getDouble(rowId, colId); case BIG_DECIMAL: return dataTable.getBigDecimal(rowId, colId); + case BOOLEAN: + return dataTable.getInt(rowId, colId) == 1; + case TIMESTAMP: + return new Timestamp(dataTable.getLong(rowId, colId)); case STRING: + case JSON: return dataTable.getString(rowId, colId); case BYTES: return dataTable.getBytes(rowId, colId).getBytes(); + case INT_ARRAY: + return dataTable.getIntArray(rowId, colId); + case LONG_ARRAY: + return dataTable.getLongArray(rowId, colId); + case FLOAT_ARRAY: + return dataTable.getFloatArray(rowId, colId); case DOUBLE_ARRAY: return dataTable.getDoubleArray(rowId, colId); + case BOOLEAN_ARRAY: { + int[] intValues = dataTable.getIntArray(rowId, colId); + int numValues = intValues.length; + boolean[] booleanValues = new boolean[numValues]; + for (int i = 0; i < numValues; i++) { + booleanValues[i] = intValues[i] == 1; + } + return booleanValues; + } + case TIMESTAMP_ARRAY: { + long[] longValues = dataTable.getLongArray(rowId, colId); + int numValues = longValues.length; + Timestamp[] timestampValues = new Timestamp[numValues]; + for (int i = 0; i < numValues; i++) { + timestampValues[i] = new Timestamp(longValues[i]); + } + return timestampValues; + } + case STRING_ARRAY: + return dataTable.getStringArray(rowId, colId); default: throw new IllegalStateException("Illegal column data type in final result: " + columnDataType); } } + + public static class AggregationInfo { + private final AggregationFunction[] _functions; + private final BaseProjectOperator _projectOperator; + private final boolean _useStarTree; + + public AggregationInfo(AggregationFunction[] functions, BaseProjectOperator projectOperator, + boolean useStarTree) { + _functions = functions; + _projectOperator = projectOperator; + _useStarTree = useStarTree; + } + + public AggregationFunction[] getFunctions() { + return _functions; + } + + public BaseProjectOperator getProjectOperator() { + return _projectOperator; + } + + public boolean isUseStarTree() { + return _useStarTree; + } + } + + /** + * Builds {@link AggregationInfo} for aggregations. + */ + public static AggregationInfo buildAggregationInfo(SegmentContext segmentContext, QueryContext queryContext, + AggregationFunction[] aggregationFunctions, @Nullable FilterContext filter, BaseFilterOperator filterOperator, + List> predicateEvaluators) { + BaseProjectOperator projectOperator = null; + + // TODO: Create a short-circuit ProjectOperator when filter result is empty + if (!filterOperator.isResultEmpty()) { + projectOperator = StarTreeUtils.createStarTreeBasedProjectOperator(segmentContext.getIndexSegment(), queryContext, + aggregationFunctions, filter, predicateEvaluators); + } + + if (projectOperator != null) { + return new AggregationInfo(aggregationFunctions, projectOperator, true); + } else { + Set expressionsToTransform = + AggregationFunctionUtils.collectExpressionsToTransform(aggregationFunctions, + queryContext.getGroupByExpressions()); + projectOperator = + new ProjectPlanNode(segmentContext, queryContext, expressionsToTransform, DocIdSetPlanNode.MAX_DOC_PER_CALL, + filterOperator).run(); + return new AggregationInfo(aggregationFunctions, projectOperator, false); + } + } + + /** + * Builds swim-lanes (list of {@link AggregationInfo}) for filtered aggregations. + */ + public static List buildFilteredAggregationInfos(SegmentContext segmentContext, + QueryContext queryContext) { + assert queryContext.getAggregationFunctions() != null && queryContext.getFilteredAggregationFunctions() != null; + + FilterPlanNode mainFilterPlan = new FilterPlanNode(segmentContext, queryContext); + BaseFilterOperator mainFilterOperator = mainFilterPlan.run(); + List> mainPredicateEvaluators = mainFilterPlan.getPredicateEvaluators(); + + // No need to process sub-filters when main filter has empty result + if (mainFilterOperator.isResultEmpty()) { + AggregationFunction[] aggregationFunctions = queryContext.getAggregationFunctions(); + Set expressions = + collectExpressionsToTransform(aggregationFunctions, queryContext.getGroupByExpressions()); + BaseProjectOperator projectOperator = + new ProjectPlanNode(segmentContext, queryContext, expressions, DocIdSetPlanNode.MAX_DOC_PER_CALL, + mainFilterOperator).run(); + return Collections.singletonList(new AggregationInfo(aggregationFunctions, projectOperator, false)); + } + + // For each aggregation function, check if the aggregation function is a filtered aggregate. If so, populate the + // corresponding filter operator. + Map filteredAggregationContexts = new HashMap<>(); + List nonFilteredFunctions = new ArrayList<>(); + FilterContext mainFilter = queryContext.getFilter(); + for (Pair functionFilterPair : queryContext.getFilteredAggregationFunctions()) { + AggregationFunction aggregationFunction = functionFilterPair.getLeft(); + FilterContext filter = functionFilterPair.getRight(); + if (filter != null) { + filteredAggregationContexts.computeIfAbsent(filter, k -> { + FilterContext combinedFilter; + if (mainFilter == null) { + combinedFilter = filter; + } else { + combinedFilter = FilterContext.forAnd(List.of(mainFilter, filter)); + } + + FilterPlanNode subFilterPlan = new FilterPlanNode(segmentContext, queryContext, filter); + BaseFilterOperator subFilterOperator = subFilterPlan.run(); + BaseFilterOperator combinedFilterOperator; + if (mainFilterOperator.isResultMatchingAll() || subFilterOperator.isResultEmpty()) { + combinedFilterOperator = subFilterOperator; + } else if (subFilterOperator.isResultMatchingAll()) { + combinedFilterOperator = mainFilterOperator; + } else { + combinedFilterOperator = + new CombinedFilterOperator(mainFilterOperator, subFilterOperator, queryContext.getQueryOptions()); + } + + List> subPredicateEvaluators = subFilterPlan.getPredicateEvaluators(); + List> combinedPredicateEvaluators = + new ArrayList<>(mainPredicateEvaluators.size() + subPredicateEvaluators.size()); + combinedPredicateEvaluators.addAll(mainPredicateEvaluators); + combinedPredicateEvaluators.addAll(subPredicateEvaluators); + + return new FilteredAggregationContext(combinedFilter, combinedFilterOperator, combinedPredicateEvaluators); + })._aggregationFunctions.add(aggregationFunction); + } else { + nonFilteredFunctions.add(aggregationFunction); + } + } + + List aggregationInfos = new ArrayList<>(); + for (FilteredAggregationContext filteredAggregationContext : filteredAggregationContexts.values()) { + BaseFilterOperator filterOperator = filteredAggregationContext._filterOperator; + if (filterOperator == mainFilterOperator) { + // This can happen when the sub filter matches all documents, and we can treat the function as non-filtered + nonFilteredFunctions.addAll(filteredAggregationContext._aggregationFunctions); + } else { + AggregationFunction[] aggregationFunctions = + filteredAggregationContext._aggregationFunctions.toArray(new AggregationFunction[0]); + aggregationInfos.add( + buildAggregationInfo(segmentContext, queryContext, aggregationFunctions, filteredAggregationContext._filter, + filteredAggregationContext._filterOperator, filteredAggregationContext._predicateEvaluators)); + } + } + + if (!nonFilteredFunctions.isEmpty()) { + AggregationFunction[] aggregationFunctions = nonFilteredFunctions.toArray(new AggregationFunction[0]); + aggregationInfos.add( + buildAggregationInfo(segmentContext, queryContext, aggregationFunctions, mainFilter, mainFilterOperator, + mainPredicateEvaluators)); + } + + return aggregationInfos; + } + + private static class FilteredAggregationContext { + final FilterContext _filter; + final BaseFilterOperator _filterOperator; + final List> _predicateEvaluators; + final List _aggregationFunctions = new ArrayList<>(); + + public FilteredAggregationContext(FilterContext filter, BaseFilterOperator filterOperator, + List> predicateEvaluators) { + _filter = filter; + _filterOperator = filterOperator; + _predicateEvaluators = predicateEvaluators; + } + } + + public static String getResultColumnName(AggregationFunction aggregationFunction, @Nullable FilterContext filter) { + String columnName = aggregationFunction.getResultColumnName(); + if (filter != null) { + columnName += " FILTER(WHERE " + filter + ")"; + } + return columnName; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AvgAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AvgAggregationFunction.java index 522617626716..9dbfb6cec57b 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AvgAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AvgAggregationFunction.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.core.query.aggregation.function; +import java.util.List; import java.util.Map; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; @@ -37,11 +38,11 @@ public class AvgAggregationFunction extends BaseSingleInputAggregationFunction arguments, boolean nullHandlingEnabled) { + this(verifySingleArgument(arguments, "AVG"), nullHandlingEnabled); } - public AvgAggregationFunction(ExpressionContext expression, boolean nullHandlingEnabled) { + protected AvgAggregationFunction(ExpressionContext expression, boolean nullHandlingEnabled) { super(expression); _nullHandlingEnabled = nullHandlingEnabled; } @@ -100,13 +101,15 @@ private void aggregateNullHandlingEnabled(int length, AggregationResultHolder ag double[] doubleValues = blockValSet.getDoubleValuesSV(); if (nullBitmap.getCardinality() < length) { double sum = 0.0; + long count = 0L; // TODO: need to update the for-loop terminating condition to: i < length & i < doubleValues.length? for (int i = 0; i < length; i++) { if (!nullBitmap.contains(i)) { sum += doubleValues[i]; + count++; } } - setAggregationResult(aggregationResultHolder, sum, length); + setAggregationResult(aggregationResultHolder, sum, count); } // Note: when all input values re null (nullBitmap.getCardinality() == values.length), avg is null. As a result, // we don't call setAggregationResult. diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AvgMVAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AvgMVAggregationFunction.java index 39b429117a1f..50be99a05afe 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AvgMVAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AvgMVAggregationFunction.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.core.query.aggregation.function; +import java.util.List; import java.util.Map; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.core.common.BlockValSet; @@ -28,8 +29,8 @@ public class AvgMVAggregationFunction extends AvgAggregationFunction { - public AvgMVAggregationFunction(ExpressionContext expression) { - super(expression); + public AvgMVAggregationFunction(List arguments) { + super(verifySingleArgument(arguments, "AVG_MV"), false); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AvgValueIntegerTupleSketchAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AvgValueIntegerTupleSketchAggregationFunction.java new file mode 100644 index 000000000000..16fd6751b2d9 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/AvgValueIntegerTupleSketchAggregationFunction.java @@ -0,0 +1,68 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import java.util.List; +import org.apache.datasketches.tuple.Sketch; +import org.apache.datasketches.tuple.TupleSketchIterator; +import org.apache.datasketches.tuple.aninteger.IntegerSummary; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.segment.local.customobject.TupleIntSketchAccumulator; +import org.apache.pinot.segment.spi.AggregationFunctionType; + + +public class AvgValueIntegerTupleSketchAggregationFunction + extends IntegerTupleSketchAggregationFunction { + + public AvgValueIntegerTupleSketchAggregationFunction(List arguments, IntegerSummary.Mode mode) { + super(arguments, mode); + } + + // TODO if extra aggregation modes are supported, make this switch + // ie, if a Mode argument other than SUM is passed in, switch to the matching AggregationFunctionType + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.AVGVALUEINTEGERSUMTUPLESKETCH; + } + + @Override + public ColumnDataType getFinalResultColumnType() { + return ColumnDataType.LONG; + } + + @Override + public Comparable extractFinalResult(TupleIntSketchAccumulator accumulator) { + accumulator.setNominalEntries(_nominalEntries); + accumulator.setSetOperations(_setOps); + accumulator.setThreshold(_accumulatorThreshold); + Sketch result = accumulator.getResult(); + if (result.isEmpty() || result.getRetainedEntries() == 0) { + // there is nothing to average, return null + return null; + } + TupleSketchIterator summaries = result.iterator(); + double retainedTotal = 0L; + while (summaries.next()) { + retainedTotal += summaries.getSummary().getValue(); + } + double estimate = retainedTotal / result.getRetainedEntries(); + return Math.round(estimate); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/BaseBooleanAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/BaseBooleanAggregationFunction.java index c295d4838fab..c6b1216ca99c 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/BaseBooleanAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/BaseBooleanAggregationFunction.java @@ -21,7 +21,7 @@ import java.util.Map; import org.apache.pinot.common.request.context.ExpressionContext; -import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.query.aggregation.AggregationResultHolder; import org.apache.pinot.core.query.aggregation.IntAggregateResultHolder; @@ -232,13 +232,13 @@ public Integer merge(Integer intermediateResult1, Integer intermediateResult2) { } @Override - public DataSchema.ColumnDataType getIntermediateResultColumnType() { - return DataSchema.ColumnDataType.BOOLEAN; + public ColumnDataType getIntermediateResultColumnType() { + return ColumnDataType.INT; } @Override - public DataSchema.ColumnDataType getFinalResultColumnType() { - return DataSchema.ColumnDataType.BOOLEAN; + public ColumnDataType getFinalResultColumnType() { + return ColumnDataType.BOOLEAN; } @Override @@ -246,6 +246,11 @@ public Integer extractFinalResult(Integer intermediateResult) { return intermediateResult; } + @Override + public Integer mergeFinalResult(Integer finalResult1, Integer finalResult2) { + return merge(finalResult1, finalResult2); + } + private int getInt(Integer val) { return val == null ? _merger.getDefaultValue() : val; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/BaseDistinctAggregateAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/BaseDistinctAggregateAggregationFunction.java new file mode 100644 index 000000000000..49754b11f094 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/BaseDistinctAggregateAggregationFunction.java @@ -0,0 +1,985 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import it.unimi.dsi.fastutil.doubles.DoubleOpenHashSet; +import it.unimi.dsi.fastutil.floats.FloatOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.Map; +import java.util.Set; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.ObjectAggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.ObjectGroupByResultHolder; +import org.apache.pinot.segment.spi.AggregationFunctionType; +import org.apache.pinot.segment.spi.index.reader.Dictionary; +import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.trace.Tracing; +import org.apache.pinot.spi.utils.ByteArray; +import org.roaringbitmap.PeekableIntIterator; +import org.roaringbitmap.RoaringBitmap; + +/** + * Base class for Distinct Aggregate functions that require collecting all distinct elements in a set before + * performing the aggregate computation. This is used by DistinctSum, DistinctAvg and DistinctCount aggregation + * functions. + */ +@SuppressWarnings({"rawtypes", "unchecked"}) +public abstract class BaseDistinctAggregateAggregationFunction + extends BaseSingleInputAggregationFunction { + private final AggregationFunctionType _functionType; + private final boolean _nullHandlingEnabled; + + protected BaseDistinctAggregateAggregationFunction(ExpressionContext expression, + AggregationFunctionType aggregationFunctionType, boolean nullHandlingEnabled) { + super(expression); + _functionType = aggregationFunctionType; + _nullHandlingEnabled = nullHandlingEnabled; + } + + @Override + public AggregationFunctionType getType() { + return _functionType; + } + + @Override + public AggregationResultHolder createAggregationResultHolder() { + return new ObjectAggregationResultHolder(); + } + + @Override + public GroupByResultHolder createGroupByResultHolder(int initialCapacity, int maxCapacity) { + return new ObjectGroupByResultHolder(initialCapacity, maxCapacity); + } + + @Override + public Set extractAggregationResult(AggregationResultHolder aggregationResultHolder) { + Object result = aggregationResultHolder.getResult(); + if (result == null) { + // Use empty IntOpenHashSet as a place holder for empty result + return new IntOpenHashSet(); + } + + if (result instanceof DictIdsWrapper) { + // For dictionary-encoded expression, convert dictionary ids to values + return convertToValueSet((DictIdsWrapper) result); + } else { + // For non-dictionary-encoded expression, directly return the value set + return (Set) result; + } + } + + @Override + public Set extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { + Object result = groupByResultHolder.getResult(groupKey); + if (result == null) { + // NOTE: Return an empty IntOpenHashSet for empty result. + return new IntOpenHashSet(); + } + + if (result instanceof DictIdsWrapper) { + // For dictionary-encoded expression, convert dictionary ids to values + return convertToValueSet((DictIdsWrapper) result); + } else { + // For non-dictionary-encoded expression, directly return the value set + return (Set) result; + } + } + + @Override + public Set merge(Set intermediateResult1, Set intermediateResult2) { + if (intermediateResult1.isEmpty()) { + return intermediateResult2; + } + if (intermediateResult2.isEmpty()) { + return intermediateResult1; + } + + Tracing.ThreadAccountantOps.sampleAndCheckInterruption(); + + intermediateResult1.addAll(intermediateResult2); + return intermediateResult1; + } + + @Override + public ColumnDataType getIntermediateResultColumnType() { + return ColumnDataType.OBJECT; + } + + /** + * Returns the dictionary id bitmap from the result holder or creates a new one if it does not exist. + */ + protected static RoaringBitmap getDictIdBitmap(AggregationResultHolder aggregationResultHolder, + Dictionary dictionary) { + DictIdsWrapper dictIdsWrapper = aggregationResultHolder.getResult(); + if (dictIdsWrapper == null) { + dictIdsWrapper = new DictIdsWrapper(dictionary); + aggregationResultHolder.setValue(dictIdsWrapper); + } + return dictIdsWrapper._dictIdBitmap; + } + + /** + * Performs aggregation for a SV column + */ + protected void svAggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + RoaringBitmap nullBitmap = null; + + // For dictionary-encoded expression, store dictionary ids into the bitmap + Dictionary dictionary = blockValSet.getDictionary(); + if (dictionary != null) { + if (_nullHandlingEnabled) { + nullBitmap = blockValSet.getNullBitmap(); + } + int[] dictIds = blockValSet.getDictionaryIdsSV(); + if (nullBitmap != null && !nullBitmap.isEmpty()) { + RoaringBitmap dictIdBitmap = getDictIdBitmap(aggregationResultHolder, dictionary); + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + dictIdBitmap.add(dictIds[i]); + } + } + } else { + getDictIdBitmap(aggregationResultHolder, dictionary).addN(dictIds, 0, length); + } + return; + } + + // For non-dictionary-encoded expression, store values into the value set + DataType storedType = blockValSet.getValueType().getStoredType(); + Set valueSet = getValueSet(aggregationResultHolder, storedType); + switch (storedType) { + case INT: + IntOpenHashSet intSet = (IntOpenHashSet) valueSet; + int[] intValues = blockValSet.getIntValuesSV(); + if (_nullHandlingEnabled) { + nullBitmap = blockValSet.getNullBitmap(); + } + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + intSet.add(intValues[i]); + } + } + } else { + for (int i = 0; i < length; i++) { + intSet.add(intValues[i]); + } + } + break; + case LONG: + LongOpenHashSet longSet = (LongOpenHashSet) valueSet; + long[] longValues = blockValSet.getLongValuesSV(); + if (_nullHandlingEnabled) { + nullBitmap = blockValSet.getNullBitmap(); + } + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + longSet.add(longValues[i]); + } + } + } else { + for (int i = 0; i < length; i++) { + longSet.add(longValues[i]); + } + } + break; + case FLOAT: + FloatOpenHashSet floatSet = (FloatOpenHashSet) valueSet; + float[] floatValues = blockValSet.getFloatValuesSV(); + if (_nullHandlingEnabled) { + nullBitmap = blockValSet.getNullBitmap(); + } + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + floatSet.add(floatValues[i]); + } + } + } else { + for (int i = 0; i < length; i++) { + floatSet.add(floatValues[i]); + } + } + break; + case DOUBLE: + DoubleOpenHashSet doubleSet = (DoubleOpenHashSet) valueSet; + double[] doubleValues = blockValSet.getDoubleValuesSV(); + if (_nullHandlingEnabled) { + nullBitmap = blockValSet.getNullBitmap(); + } + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + doubleSet.add(doubleValues[i]); + } + } + } else { + for (int i = 0; i < length; i++) { + doubleSet.add(doubleValues[i]); + } + } + break; + case STRING: + ObjectOpenHashSet stringSet = (ObjectOpenHashSet) valueSet; + String[] stringValues = blockValSet.getStringValuesSV(); + //noinspection ManualArrayToCollectionCopy + if (_nullHandlingEnabled) { + nullBitmap = blockValSet.getNullBitmap(); + } + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + stringSet.add(stringValues[i]); + } + } + } else { + for (int i = 0; i < length; i++) { + stringSet.add(stringValues[i]); + } + } + break; + case BYTES: + ObjectOpenHashSet bytesSet = (ObjectOpenHashSet) valueSet; + byte[][] bytesValues = blockValSet.getBytesValuesSV(); + if (_nullHandlingEnabled) { + nullBitmap = blockValSet.getNullBitmap(); + } + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + bytesSet.add(new ByteArray(bytesValues[i])); + } + } + } else { + for (int i = 0; i < length; i++) { + bytesSet.add(new ByteArray(bytesValues[i])); + } + } + break; + default: + throw new IllegalStateException( + "Illegal data type for " + _functionType.getName() + " aggregation function: " + storedType); + } + } + + /** + * Performs aggregation for a MV column + */ + protected void mvAggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + + // For dictionary-encoded expression, store dictionary ids into the bitmap + Dictionary dictionary = blockValSet.getDictionary(); + if (dictionary != null) { + RoaringBitmap dictIdBitmap = getDictIdBitmap(aggregationResultHolder, dictionary); + int[][] dictIds = blockValSet.getDictionaryIdsMV(); + for (int i = 0; i < length; i++) { + dictIdBitmap.add(dictIds[i]); + } + return; + } + + // For non-dictionary-encoded expression, store values into the value set + DataType storedType = blockValSet.getValueType().getStoredType(); + Set valueSet = getValueSet(aggregationResultHolder, storedType); + switch (storedType) { + case INT: + IntOpenHashSet intSet = (IntOpenHashSet) valueSet; + int[][] intValues = blockValSet.getIntValuesMV(); + for (int i = 0; i < length; i++) { + for (int value : intValues[i]) { + intSet.add(value); + } + } + break; + case LONG: + LongOpenHashSet longSet = (LongOpenHashSet) valueSet; + long[][] longValues = blockValSet.getLongValuesMV(); + for (int i = 0; i < length; i++) { + for (long value : longValues[i]) { + longSet.add(value); + } + } + break; + case FLOAT: + FloatOpenHashSet floatSet = (FloatOpenHashSet) valueSet; + float[][] floatValues = blockValSet.getFloatValuesMV(); + for (int i = 0; i < length; i++) { + for (float value : floatValues[i]) { + floatSet.add(value); + } + } + break; + case DOUBLE: + DoubleOpenHashSet doubleSet = (DoubleOpenHashSet) valueSet; + double[][] doubleValues = blockValSet.getDoubleValuesMV(); + for (int i = 0; i < length; i++) { + for (double value : doubleValues[i]) { + doubleSet.add(value); + } + } + break; + case STRING: + ObjectOpenHashSet stringSet = (ObjectOpenHashSet) valueSet; + String[][] stringValues = blockValSet.getStringValuesMV(); + for (int i = 0; i < length; i++) { + //noinspection ManualArrayToCollectionCopy + for (String value : stringValues[i]) { + //noinspection UseBulkOperation + stringSet.add(value); + } + } + break; + default: + throw new IllegalStateException( + "Illegal data type for " + _functionType.getName() + " aggregation function: " + storedType); + } + } + + /** + * Performs aggregation for a SV column with group by on a SV column. + */ + protected void svAggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + RoaringBitmap nullBitmap = null; + + // For dictionary-encoded expression, store dictionary ids into the bitmap + Dictionary dictionary = blockValSet.getDictionary(); + if (dictionary != null) { + if (_nullHandlingEnabled) { + nullBitmap = blockValSet.getNullBitmap(); + } + int[] dictIds = blockValSet.getDictionaryIdsSV(); + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + getDictIdBitmap(groupByResultHolder, groupKeyArray[i], dictionary).add(dictIds[i]); + } + } + } else { + for (int i = 0; i < length; i++) { + getDictIdBitmap(groupByResultHolder, groupKeyArray[i], dictionary).add(dictIds[i]); + } + } + return; + } + + // For non-dictionary-encoded expression, store values into the value set + DataType storedType = blockValSet.getValueType().getStoredType(); + switch (storedType) { + case INT: + int[] intValues = blockValSet.getIntValuesSV(); + if (_nullHandlingEnabled) { + nullBitmap = blockValSet.getNullBitmap(); + } + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + ((IntOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.INT)).add(intValues[i]); + } + } + } else { + for (int i = 0; i < length; i++) { + ((IntOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.INT)).add(intValues[i]); + } + } + break; + case LONG: + long[] longValues = blockValSet.getLongValuesSV(); + if (_nullHandlingEnabled) { + nullBitmap = blockValSet.getNullBitmap(); + } + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + ((LongOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.LONG)).add(longValues[i]); + } + } + } else { + for (int i = 0; i < length; i++) { + ((LongOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.LONG)).add(longValues[i]); + } + } + break; + case FLOAT: + float[] floatValues = blockValSet.getFloatValuesSV(); + if (_nullHandlingEnabled) { + nullBitmap = blockValSet.getNullBitmap(); + } + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + ((FloatOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.FLOAT)).add( + floatValues[i]); + } + } + } else { + for (int i = 0; i < length; i++) { + ((FloatOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.FLOAT)).add(floatValues[i]); + } + } + break; + case DOUBLE: + double[] doubleValues = blockValSet.getDoubleValuesSV(); + if (_nullHandlingEnabled) { + nullBitmap = blockValSet.getNullBitmap(); + } + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + ((DoubleOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.DOUBLE)).add( + doubleValues[i]); + } + } + } else { + for (int i = 0; i < length; i++) { + ((DoubleOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.DOUBLE)).add( + doubleValues[i]); + } + } + break; + case STRING: + String[] stringValues = blockValSet.getStringValuesSV(); + if (_nullHandlingEnabled) { + nullBitmap = blockValSet.getNullBitmap(); + } + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + ((ObjectOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.STRING)).add( + stringValues[i]); + } + } + } else { + for (int i = 0; i < length; i++) { + ((ObjectOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.STRING)).add( + stringValues[i]); + } + } + break; + case BYTES: + byte[][] bytesValues = blockValSet.getBytesValuesSV(); + if (_nullHandlingEnabled) { + nullBitmap = blockValSet.getNullBitmap(); + } + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + ((ObjectOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.BYTES)).add( + new ByteArray(bytesValues[i])); + } + } + } else { + for (int i = 0; i < length; i++) { + ((ObjectOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.BYTES)).add( + new ByteArray(bytesValues[i])); + } + } + break; + default: + throw new IllegalStateException( + "Illegal data type for " + _functionType.getName() + " aggregation function: " + storedType); + } + } + + /** + * Performs aggregation for a MV column with group by on a SV column. + */ + protected void mvAggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + + // For dictionary-encoded expression, store dictionary ids into the bitmap + Dictionary dictionary = blockValSet.getDictionary(); + if (dictionary != null) { + int[][] dictIds = blockValSet.getDictionaryIdsMV(); + for (int i = 0; i < length; i++) { + getDictIdBitmap(groupByResultHolder, groupKeyArray[i], dictionary).add(dictIds[i]); + } + return; + } + + // For non-dictionary-encoded expression, store values into the value set + DataType storedType = blockValSet.getValueType().getStoredType(); + switch (storedType) { + case INT: + int[][] intValues = blockValSet.getIntValuesMV(); + for (int i = 0; i < length; i++) { + IntOpenHashSet intSet = (IntOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.INT); + for (int value : intValues[i]) { + intSet.add(value); + } + } + break; + case LONG: + long[][] longValues = blockValSet.getLongValuesMV(); + for (int i = 0; i < length; i++) { + LongOpenHashSet longSet = (LongOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.LONG); + for (long value : longValues[i]) { + longSet.add(value); + } + } + break; + case FLOAT: + float[][] floatValues = blockValSet.getFloatValuesMV(); + for (int i = 0; i < length; i++) { + FloatOpenHashSet floatSet = + (FloatOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.FLOAT); + for (float value : floatValues[i]) { + floatSet.add(value); + } + } + break; + case DOUBLE: + double[][] doubleValues = blockValSet.getDoubleValuesMV(); + for (int i = 0; i < length; i++) { + DoubleOpenHashSet doubleSet = + (DoubleOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.DOUBLE); + for (double value : doubleValues[i]) { + doubleSet.add(value); + } + } + break; + case STRING: + String[][] stringValues = blockValSet.getStringValuesMV(); + for (int i = 0; i < length; i++) { + ObjectOpenHashSet stringSet = + (ObjectOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.STRING); + //noinspection ManualArrayToCollectionCopy + for (String value : stringValues[i]) { + //noinspection UseBulkOperation + stringSet.add(value); + } + } + break; + default: + throw new IllegalStateException( + "Illegal data type for " + _functionType.getName() + " aggregation function: " + storedType); + } + } + + /** + * Performs aggregation for a SV column with group by on a MV column. + */ + protected void svAggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + RoaringBitmap nullBitmap = null; + + // For dictionary-encoded expression, store dictionary ids into the bitmap + Dictionary dictionary = blockValSet.getDictionary(); + if (dictionary != null) { + if (_nullHandlingEnabled) { + nullBitmap = blockValSet.getNullBitmap(); + } + int[] dictIds = blockValSet.getDictionaryIdsSV(); + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + setDictIdForGroupKeys(groupByResultHolder, groupKeysArray[i], dictionary, dictIds[i]); + } + } + } else { + for (int i = 0; i < length; i++) { + setDictIdForGroupKeys(groupByResultHolder, groupKeysArray[i], dictionary, dictIds[i]); + } + } + return; + } + + // For non-dictionary-encoded expression, store values into the value set + DataType storedType = blockValSet.getValueType().getStoredType(); + switch (storedType) { + case INT: + int[] intValues = blockValSet.getIntValuesSV(); + nullBitmap = blockValSet.getNullBitmap(); + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], intValues[i]); + } + } + } else { + for (int i = 0; i < length; i++) { + setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], intValues[i]); + } + } + break; + case LONG: + long[] longValues = blockValSet.getLongValuesSV(); + nullBitmap = blockValSet.getNullBitmap(); + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], longValues[i]); + } + } + } else { + for (int i = 0; i < length; i++) { + setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], longValues[i]); + } + } + break; + case FLOAT: + float[] floatValues = blockValSet.getFloatValuesSV(); + nullBitmap = blockValSet.getNullBitmap(); + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], floatValues[i]); + } + } + } else { + for (int i = 0; i < length; i++) { + setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], floatValues[i]); + } + } + break; + case DOUBLE: + double[] doubleValues = blockValSet.getDoubleValuesSV(); + nullBitmap = blockValSet.getNullBitmap(); + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], doubleValues[i]); + } + } + } else { + for (int i = 0; i < length; i++) { + setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], doubleValues[i]); + } + } + break; + case STRING: + String[] stringValues = blockValSet.getStringValuesSV(); + nullBitmap = blockValSet.getNullBitmap(); + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], stringValues[i]); + } + } + } else { + for (int i = 0; i < length; i++) { + setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], stringValues[i]); + } + } + break; + case BYTES: + byte[][] bytesValues = blockValSet.getBytesValuesSV(); + nullBitmap = blockValSet.getNullBitmap(); + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], new ByteArray(bytesValues[i])); + } + } + } else { + for (int i = 0; i < length; i++) { + setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], new ByteArray(bytesValues[i])); + } + } + break; + default: + throw new IllegalStateException( + "Illegal data type for " + _functionType.getName() + " aggregation function: " + storedType); + } + } + + /** + * Performs aggregation for a MV column with group by on a MV column. + */ + protected void mvAggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + + // For dictionary-encoded expression, store dictionary ids into the bitmap + Dictionary dictionary = blockValSet.getDictionary(); + if (dictionary != null) { + int[][] dictIds = blockValSet.getDictionaryIdsMV(); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + getDictIdBitmap(groupByResultHolder, groupKey, dictionary).add(dictIds[i]); + } + } + return; + } + + // For non-dictionary-encoded expression, store hash code of the values into the value set + DataType storedType = blockValSet.getValueType().getStoredType(); + switch (storedType) { + case INT: + int[][] intValues = blockValSet.getIntValuesMV(); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + IntOpenHashSet intSet = (IntOpenHashSet) getValueSet(groupByResultHolder, groupKey, DataType.INT); + for (int value : intValues[i]) { + intSet.add(value); + } + } + } + break; + case LONG: + long[][] longValues = blockValSet.getLongValuesMV(); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + LongOpenHashSet longSet = (LongOpenHashSet) getValueSet(groupByResultHolder, groupKey, DataType.LONG); + for (long value : longValues[i]) { + longSet.add(value); + } + } + } + break; + case FLOAT: + float[][] floatValues = blockValSet.getFloatValuesMV(); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + FloatOpenHashSet floatSet = (FloatOpenHashSet) getValueSet(groupByResultHolder, groupKey, DataType.FLOAT); + for (float value : floatValues[i]) { + floatSet.add(value); + } + } + } + break; + case DOUBLE: + double[][] doubleValues = blockValSet.getDoubleValuesMV(); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + DoubleOpenHashSet doubleSet = + (DoubleOpenHashSet) getValueSet(groupByResultHolder, groupKey, DataType.DOUBLE); + for (double value : doubleValues[i]) { + doubleSet.add(value); + } + } + } + break; + case STRING: + String[][] stringValues = blockValSet.getStringValuesMV(); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + ObjectOpenHashSet stringSet = + (ObjectOpenHashSet) getValueSet(groupByResultHolder, groupKey, DataType.STRING); + //noinspection ManualArrayToCollectionCopy + for (String value : stringValues[i]) { + //noinspection UseBulkOperation + stringSet.add(value); + } + } + } + break; + default: + throw new IllegalStateException( + "Illegal data type for " + _functionType.getName() + " aggregation function: " + storedType); + } + } + + /** + * Returns the value set from the result holder or creates a new one if it does not exist. + */ + protected static Set getValueSet(AggregationResultHolder aggregationResultHolder, DataType valueType) { + Set valueSet = aggregationResultHolder.getResult(); + if (valueSet == null) { + valueSet = getValueSet(valueType); + aggregationResultHolder.setValue(valueSet); + } + return valueSet; + } + + /** + * Helper method to create a value set for the given value type. + */ + private static Set getValueSet(DataType valueType) { + switch (valueType) { + case INT: + return new IntOpenHashSet(); + case LONG: + return new LongOpenHashSet(); + case FLOAT: + return new FloatOpenHashSet(); + case DOUBLE: + return new DoubleOpenHashSet(); + case STRING: + case BYTES: + return new ObjectOpenHashSet(); + default: + throw new IllegalStateException("Illegal data type for DISTINCT_AGGREGATE aggregation function valueType"); + } + } + + /** + * Returns the dictionary id bitmap for the given group key or creates a new one if it does not exist. + */ + protected static RoaringBitmap getDictIdBitmap(GroupByResultHolder groupByResultHolder, int groupKey, + Dictionary dictionary) { + DictIdsWrapper dictIdsWrapper = groupByResultHolder.getResult(groupKey); + if (dictIdsWrapper == null) { + dictIdsWrapper = new DictIdsWrapper(dictionary); + groupByResultHolder.setValueForKey(groupKey, dictIdsWrapper); + } + return dictIdsWrapper._dictIdBitmap; + } + + /** + * Returns the value set for the given group key or creates a new one if it does not exist. + */ + protected static Set getValueSet(GroupByResultHolder groupByResultHolder, int groupKey, DataType valueType) { + Set valueSet = groupByResultHolder.getResult(groupKey); + if (valueSet == null) { + valueSet = getValueSet(valueType); + groupByResultHolder.setValueForKey(groupKey, valueSet); + } + return valueSet; + } + + /** + * Helper method to set dictionary id for the given group keys into the result holder. + */ + private static void setDictIdForGroupKeys(GroupByResultHolder groupByResultHolder, int[] groupKeys, + Dictionary dictionary, int dictId) { + for (int groupKey : groupKeys) { + getDictIdBitmap(groupByResultHolder, groupKey, dictionary).add(dictId); + } + } + + /** + * Helper method to set INT value for the given group keys into the result holder. + */ + private static void setValueForGroupKeys(GroupByResultHolder groupByResultHolder, int[] groupKeys, int value) { + for (int groupKey : groupKeys) { + ((IntOpenHashSet) getValueSet(groupByResultHolder, groupKey, DataType.INT)).add(value); + } + } + + /** + * Helper method to set LONG value for the given group keys into the result holder. + */ + private static void setValueForGroupKeys(GroupByResultHolder groupByResultHolder, int[] groupKeys, long value) { + for (int groupKey : groupKeys) { + ((LongOpenHashSet) getValueSet(groupByResultHolder, groupKey, DataType.LONG)).add(value); + } + } + + /** + * Helper method to set FLOAT value for the given group keys into the result holder. + */ + private static void setValueForGroupKeys(GroupByResultHolder groupByResultHolder, int[] groupKeys, float value) { + for (int groupKey : groupKeys) { + ((FloatOpenHashSet) getValueSet(groupByResultHolder, groupKey, DataType.FLOAT)).add(value); + } + } + + /** + * Helper method to set DOUBLE value for the given group keys into the result holder. + */ + private static void setValueForGroupKeys(GroupByResultHolder groupByResultHolder, int[] groupKeys, double value) { + for (int groupKey : groupKeys) { + ((DoubleOpenHashSet) getValueSet(groupByResultHolder, groupKey, DataType.DOUBLE)).add(value); + } + } + + /** + * Helper method to set STRING value for the given group keys into the result holder. + */ + private static void setValueForGroupKeys(GroupByResultHolder groupByResultHolder, int[] groupKeys, String value) { + for (int groupKey : groupKeys) { + ((ObjectOpenHashSet) getValueSet(groupByResultHolder, groupKey, DataType.STRING)).add(value); + } + } + + /** + * Helper method to set BYTES value for the given group keys into the result holder. + */ + private static void setValueForGroupKeys(GroupByResultHolder groupByResultHolder, int[] groupKeys, ByteArray value) { + for (int groupKey : groupKeys) { + ((ObjectOpenHashSet) getValueSet(groupByResultHolder, groupKey, DataType.BYTES)).add(value); + } + } + + /** + * Helper method to read dictionary and convert dictionary ids to values for dictionary-encoded expression. + */ + private static Set convertToValueSet(DictIdsWrapper dictIdsWrapper) { + Dictionary dictionary = dictIdsWrapper._dictionary; + RoaringBitmap dictIdBitmap = dictIdsWrapper._dictIdBitmap; + int numValues = dictIdBitmap.getCardinality(); + PeekableIntIterator iterator = dictIdBitmap.getIntIterator(); + DataType storedType = dictionary.getValueType(); + switch (storedType) { + case INT: + IntOpenHashSet intSet = new IntOpenHashSet(numValues); + while (iterator.hasNext()) { + intSet.add(dictionary.getIntValue(iterator.next())); + } + return intSet; + case LONG: + LongOpenHashSet longSet = new LongOpenHashSet(numValues); + while (iterator.hasNext()) { + longSet.add(dictionary.getLongValue(iterator.next())); + } + return longSet; + case FLOAT: + FloatOpenHashSet floatSet = new FloatOpenHashSet(numValues); + while (iterator.hasNext()) { + floatSet.add(dictionary.getFloatValue(iterator.next())); + } + return floatSet; + case DOUBLE: + DoubleOpenHashSet doubleSet = new DoubleOpenHashSet(numValues); + while (iterator.hasNext()) { + doubleSet.add(dictionary.getDoubleValue(iterator.next())); + } + return doubleSet; + case STRING: + ObjectOpenHashSet stringSet = new ObjectOpenHashSet<>(numValues); + while (iterator.hasNext()) { + stringSet.add(dictionary.getStringValue(iterator.next())); + } + return stringSet; + case BYTES: + ObjectOpenHashSet bytesSet = new ObjectOpenHashSet<>(numValues); + while (iterator.hasNext()) { + bytesSet.add(new ByteArray(dictionary.getBytesValue(iterator.next()))); + } + return bytesSet; + default: + throw new IllegalStateException("Illegal data type for DISTINCT_AGGREGATE aggregation function: " + storedType); + } + } + + private static final class DictIdsWrapper { + final Dictionary _dictionary; + final RoaringBitmap _dictIdBitmap; + + private DictIdsWrapper(Dictionary dictionary) { + _dictionary = dictionary; + _dictIdBitmap = new RoaringBitmap(); + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/BaseSingleInputAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/BaseSingleInputAggregationFunction.java index 54986c9a74e5..e11ad8492a97 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/BaseSingleInputAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/BaseSingleInputAggregationFunction.java @@ -18,9 +18,13 @@ */ package org.apache.pinot.core.query.aggregation.function; +import com.google.common.base.Preconditions; import java.util.Collections; import java.util.List; +import java.util.function.Supplier; import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; /** @@ -38,11 +42,6 @@ public BaseSingleInputAggregationFunction(ExpressionContext expression) { _expression = expression; } - @Override - public String getColumnName() { - return getType().getName() + "_" + _expression; - } - @Override public String getResultColumnName() { return getType().getName().toLowerCase() + "(" + _expression + ")"; @@ -65,4 +64,28 @@ public String toExplainString() { } return stringBuilder.append(')').toString(); } + + protected static ExpressionContext verifySingleArgument(List arguments, String functionName) { + Preconditions.checkArgument(arguments.size() == 1, "%s expects 1 argument, got: %s", functionName, + arguments.size()); + return arguments.get(0); + } + + protected static E getValue(AggregationResultHolder aggregationResultHolder, Supplier orCreate) { + E result = aggregationResultHolder.getResult(); + if (result == null) { + result = orCreate.get(); + aggregationResultHolder.setValue(result); + } + return result; + } + + protected static E getValue(GroupByResultHolder groupByResultHolder, int groupKey, Supplier orCreate) { + E result = groupByResultHolder.getResult(groupKey); + if (result == null) { + result = orCreate.get(); + groupByResultHolder.setValueForKey(groupKey, result); + } + return result; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/BooleanAndAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/BooleanAndAggregationFunction.java index c385e1dcfb78..d328356a4022 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/BooleanAndAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/BooleanAndAggregationFunction.java @@ -19,18 +19,15 @@ package org.apache.pinot.core.query.aggregation.function; +import java.util.List; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.segment.spi.AggregationFunctionType; public class BooleanAndAggregationFunction extends BaseBooleanAggregationFunction { - public BooleanAndAggregationFunction(ExpressionContext expression) { - this(expression, false); - } - - public BooleanAndAggregationFunction(ExpressionContext expression, boolean nullHandlingEnabled) { - super(expression, nullHandlingEnabled, BooleanMerge.AND); + public BooleanAndAggregationFunction(List arguments, boolean nullHandlingEnabled) { + super(verifySingleArgument(arguments, "BOOL_AND"), nullHandlingEnabled, BooleanMerge.AND); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/BooleanOrAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/BooleanOrAggregationFunction.java index 6ee96fad4934..eb7582e23a86 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/BooleanOrAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/BooleanOrAggregationFunction.java @@ -19,18 +19,15 @@ package org.apache.pinot.core.query.aggregation.function; +import java.util.List; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.segment.spi.AggregationFunctionType; public class BooleanOrAggregationFunction extends BaseBooleanAggregationFunction { - public BooleanOrAggregationFunction(ExpressionContext expression) { - this(expression, false); - } - - protected BooleanOrAggregationFunction(ExpressionContext expression, boolean nullHandlingEnabled) { - super(expression, nullHandlingEnabled, BooleanMerge.OR); + public BooleanOrAggregationFunction(List arguments, boolean nullHandlingEnabled) { + super(verifySingleArgument(arguments, "BOOL_OR"), nullHandlingEnabled, BooleanMerge.OR); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/ChildAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/ChildAggregationFunction.java new file mode 100644 index 000000000000..357ebac2123b --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/ChildAggregationFunction.java @@ -0,0 +1,165 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.DummyAggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.DummyGroupByResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.spi.utils.CommonConstants; + + +/** + * Child aggregation function is used for a result placeholder during the query processing, + * It holds the position of the original aggregation function in the query + * and use its name to denote which parent aggregation function it belongs to. + * The name also serves as the key to retrieve the result from the parent aggregation function + * result holder. + * Please look at getResultColumnName() for the detailed format of the name. + * Please look at ExprMinMaxRewriter as an example of how a child aggregation function is created. + */ +public abstract class ChildAggregationFunction implements AggregationFunction { + + private static final int CHILD_AGGREGATION_FUNCTION_ID_OFFSET = 0; + private static final int CHILD_AGGREGATION_FUNCTION_COLUMN_KEY_OFFSET = 1; + private final ExpressionContext _childFunctionKeyInParent; + private final List _resultNameOperands; + private final ExpressionContext _childFunctionID; + + ChildAggregationFunction(List operands) { + _childFunctionID = operands.get(CHILD_AGGREGATION_FUNCTION_ID_OFFSET); + _childFunctionKeyInParent = operands.get(CHILD_AGGREGATION_FUNCTION_COLUMN_KEY_OFFSET); + _resultNameOperands = operands.subList(CHILD_AGGREGATION_FUNCTION_COLUMN_KEY_OFFSET + 1, operands.size()); + } + + @Override + public List getInputExpressions() { + ArrayList expressionContexts = new ArrayList<>(); + expressionContexts.add(_childFunctionID); + expressionContexts.add(_childFunctionKeyInParent); + expressionContexts.addAll(_resultNameOperands); + return expressionContexts; + } + + @Override + public final AggregationResultHolder createAggregationResultHolder() { + return new DummyAggregationResultHolder(); + } + + @Override + public final GroupByResultHolder createGroupByResultHolder(int initialCapacity, int maxCapacity) { + return new DummyGroupByResultHolder(); + } + + @Override + public final void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + } + + @Override + public final void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + } + + @Override + public final void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + } + + @Override + public final Long extractAggregationResult(AggregationResultHolder aggregationResultHolder) { + return 0L; + } + + @Override + public final Long extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { + return 0L; + } + + @Override + public final Long merge(Long intermediateResult1, Long intermediateResult2) { + return 0L; + } + + @Override + public final DataSchema.ColumnDataType getIntermediateResultColumnType() { + return DataSchema.ColumnDataType.LONG; + } + + @Override + public final DataSchema.ColumnDataType getFinalResultColumnType() { + return DataSchema.ColumnDataType.UNKNOWN; + } + + @Override + public final Long extractFinalResult(Long longValue) { + return 0L; + } + + @Override + public Long mergeFinalResult(Long finalResult1, Long finalResult2) { + return 0L; + } + + /** + * The name of the column as follows: + * CHILD_AGGREGATION_NAME_PREFIX + actual function type + operands + CHILD_AGGREGATION_SEPERATOR + * + actual function type + parent aggregation function id + CHILD_KEY_SEPERATOR + column key in parent function + * e.g. if the child aggregation function is "exprmax(0,a,b,x)", the name of the column is + * "pinotchildaggregationepxrmax(a,b,x)@exprmax0_x" + */ + @Override + public final String getResultColumnName() { + String type = getType().getName().toLowerCase(); + return CommonConstants.RewriterConstants.CHILD_AGGREGATION_NAME_PREFIX + // above is the prefix for all child aggregation functions + + + type + "(" + _resultNameOperands.stream().map(ExpressionContext::toString) + .collect(Collectors.joining(",")) + ")" + // above is the actual child aggregation function name we want to return to the user + + + CommonConstants.RewriterConstants.CHILD_AGGREGATION_SEPERATOR + + type + + _childFunctionID.getLiteral().getStringValue() + + CommonConstants.RewriterConstants.CHILD_KEY_SEPERATOR + + _childFunctionKeyInParent.toString(); + // above is the column key in the parent aggregation function + } + + @Override + public final String toExplainString() { + StringBuilder stringBuilder = new StringBuilder(CommonConstants.RewriterConstants.CHILD_AGGREGATION_NAME_PREFIX) + .append("_").append(getType().getName()).append('('); + int numArguments = getInputExpressions().size(); + if (numArguments > 0) { + stringBuilder.append(getInputExpressions().get(0).toString()); + for (int i = 1; i < numArguments; i++) { + stringBuilder.append(", ").append(getInputExpressions().get(i).toString()); + } + } + return stringBuilder.append(')').toString(); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/ChildExprMinMaxAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/ChildExprMinMaxAggregationFunction.java new file mode 100644 index 000000000000..ec2be01e1404 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/ChildExprMinMaxAggregationFunction.java @@ -0,0 +1,39 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import java.util.List; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.segment.spi.AggregationFunctionType; + + +public class ChildExprMinMaxAggregationFunction extends ChildAggregationFunction { + + private final boolean _isMax; + + public ChildExprMinMaxAggregationFunction(List operands, boolean isMax) { + super(operands); + _isMax = isMax; + } + + @Override + public AggregationFunctionType getType() { + return _isMax ? AggregationFunctionType.EXPRMAX : AggregationFunctionType.EXPRMIN; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/CountAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/CountAggregationFunction.java index 53f36f470318..b222803a4424 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/CountAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/CountAggregationFunction.java @@ -34,7 +34,6 @@ public class CountAggregationFunction extends BaseSingleInputAggregationFunction { - private static final String COUNT_STAR_COLUMN_NAME = "count_star"; private static final String COUNT_STAR_RESULT_COLUMN_NAME = "count(*)"; private static final double DEFAULT_INITIAL_VALUE = 0.0; // Special expression used by star-tree to pass in BlockValSet @@ -43,14 +42,18 @@ public class CountAggregationFunction extends BaseSingleInputAggregationFunction private final boolean _nullHandlingEnabled; - public CountAggregationFunction(ExpressionContext expression) { - this(expression, false); + public CountAggregationFunction(List arguments, boolean nullHandlingEnabled) { + this(verifySingleArgument(arguments, "COUNT"), nullHandlingEnabled); } - public CountAggregationFunction(ExpressionContext expression, boolean nullHandlingEnabled) { + protected CountAggregationFunction(ExpressionContext expression, boolean nullHandlingEnabled) { super(expression); // Consider null values only when null handling is enabled and function is not COUNT(*) - _nullHandlingEnabled = nullHandlingEnabled && !expression.getIdentifier().equals("*"); + // Note COUNT on any literal gives same result as COUNT(*) + // So allow for identifiers that are not * and functions, disable for literals and * + _nullHandlingEnabled = nullHandlingEnabled && ( + (expression.getType() == ExpressionContext.Type.IDENTIFIER && !expression.getIdentifier().equals("*")) || ( + expression.getType() == ExpressionContext.Type.FUNCTION)); } @Override @@ -58,11 +61,6 @@ public AggregationFunctionType getType() { return AggregationFunctionType.COUNT; } - @Override - public String getColumnName() { - return _nullHandlingEnabled ? super.getColumnName() : COUNT_STAR_COLUMN_NAME; - } - @Override public String getResultColumnName() { return _nullHandlingEnabled ? super.getResultColumnName() : COUNT_STAR_RESULT_COLUMN_NAME; @@ -206,6 +204,11 @@ public Long extractFinalResult(Long intermediateResult) { return intermediateResult; } + @Override + public Long mergeFinalResult(Long finalResult1, Long finalResult2) { + return finalResult1 + finalResult2; + } + @Override public String toExplainString() { StringBuilder stringBuilder = new StringBuilder(getType().getName()).append('('); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/CountMVAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/CountMVAggregationFunction.java index 061111daaf91..6ca1121770ba 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/CountMVAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/CountMVAggregationFunction.java @@ -30,14 +30,9 @@ public class CountMVAggregationFunction extends CountAggregationFunction { - /** - * Constructor for the class. - * - * @param expression Expression to aggregate on. - */ - public CountMVAggregationFunction(ExpressionContext expression) { + public CountMVAggregationFunction(List arguments) { // TODO(nhejazi): support proper null handling for aggregation functions on MV columns. - super(expression); + super(verifySingleArgument(arguments, "COUNT_MV"), false); } @Override @@ -45,11 +40,6 @@ public AggregationFunctionType getType() { return AggregationFunctionType.COUNTMV; } - @Override - public String getColumnName() { - return AggregationFunctionType.COUNTMV.getName() + "_" + _expression; - } - @Override public String getResultColumnName() { return AggregationFunctionType.COUNTMV.getName().toLowerCase() + "(" + _expression + ")"; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/CovarianceAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/CovarianceAggregationFunction.java index 1676835bae8e..7f07d0cfb898 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/CovarianceAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/CovarianceAggregationFunction.java @@ -68,11 +68,6 @@ public AggregationFunctionType getType() { return AggregationFunctionType.COVARPOP; } - @Override - public String getColumnName() { - return getType().getName() + "_" + _expression1 + "_" + _expression2; - } - @Override public String getResultColumnName() { return getType().getName().toLowerCase() + "(" + _expression1 + "," + _expression2 + ")"; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctAggregationFunction.java deleted file mode 100644 index 6f0703d72e25..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctAggregationFunction.java +++ /dev/null @@ -1,167 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.query.aggregation.function; - -import java.util.List; -import java.util.Map; -import javax.annotation.Nullable; -import org.apache.commons.lang3.StringUtils; -import org.apache.pinot.common.request.context.ExpressionContext; -import org.apache.pinot.common.request.context.OrderByExpressionContext; -import org.apache.pinot.common.utils.DataSchema.ColumnDataType; -import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.query.aggregation.AggregationResultHolder; -import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; -import org.apache.pinot.segment.spi.AggregationFunctionType; - - -/** - * The DISTINCT clause in SQL is represented as the DISTINCT aggregation function. Currently it is only used to wrap the - * information for the distinct queries. - * TODO: Use a separate way to represent DISTINCT instead of aggregation. - */ -@SuppressWarnings("rawtypes") -public class DistinctAggregationFunction implements AggregationFunction { - private final List _expressions; - private final String[] _columns; - private final List _orderByExpressions; - private final int _limit; - - /** - * Constructor for the class. - * - * @param expressions Distinct columns to return - * @param orderByExpressions Order By clause - * @param limit Limit clause - */ - public DistinctAggregationFunction(List expressions, - @Nullable List orderByExpressions, int limit) { - _expressions = expressions; - int numExpressions = expressions.size(); - _columns = new String[numExpressions]; - for (int i = 0; i < numExpressions; i++) { - _columns[i] = expressions.get(i).toString(); - } - _orderByExpressions = orderByExpressions; - _limit = limit; - } - - public String[] getColumns() { - return _columns; - } - - public List getOrderByExpressions() { - return _orderByExpressions; - } - - public int getLimit() { - return _limit; - } - - @Override - public AggregationFunctionType getType() { - return AggregationFunctionType.DISTINCT; - } - - @Override - public String getColumnName() { - return AggregationFunctionType.DISTINCT.getName() + "_" + StringUtils.join(_columns, ':'); - } - - @Override - public String getResultColumnName() { - return AggregationFunctionType.DISTINCT.getName().toLowerCase() + "(" + StringUtils.join(_columns, ':') + ")"; - } - - @Override - public List getInputExpressions() { - return _expressions; - } - - @Override - public ColumnDataType getIntermediateResultColumnType() { - return ColumnDataType.OBJECT; - } - - @Override - public AggregationResultHolder createAggregationResultHolder() { - throw new UnsupportedOperationException("Operation not supported for DISTINCT aggregation function"); - } - - @Override - public GroupByResultHolder createGroupByResultHolder(int initialCapacity, int maxCapacity) { - throw new UnsupportedOperationException("Operation not supported for DISTINCT aggregation function"); - } - - @Override - public void aggregate(int length, AggregationResultHolder aggregationResultHolder, - Map blockValSetMap) { - throw new UnsupportedOperationException("Operation not supported for DISTINCT aggregation function"); - } - - @Override - public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, - Map blockValSetMap) { - throw new UnsupportedOperationException("Operation not supported for DISTINCT aggregation function"); - } - - @Override - public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, - Map blockValSetMap) { - throw new UnsupportedOperationException("Operation not supported for DISTINCT aggregation function"); - } - - @Override - public Object extractAggregationResult(AggregationResultHolder aggregationResultHolder) { - throw new UnsupportedOperationException("Operation not supported for DISTINCT aggregation function"); - } - - @Override - public Object extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { - throw new UnsupportedOperationException("Operation not supported for DISTINCT aggregation function"); - } - - @Override - public Object merge(Object intermediateResult1, Object intermediateResult2) { - throw new UnsupportedOperationException("Operation not supported for DISTINCT aggregation function"); - } - - @Override - public ColumnDataType getFinalResultColumnType() { - throw new UnsupportedOperationException("Operation not supported for DISTINCT aggregation function"); - } - - @Override - public Comparable extractFinalResult(Object intermediateResult) { - throw new UnsupportedOperationException("Operation not supported for DISTINCT aggregation function"); - } - - @Override - public String toExplainString() { - StringBuilder stringBuilder = new StringBuilder(getType().getName()).append('('); - int numArguments = getInputExpressions().size(); - if (numArguments > 0) { - stringBuilder.append(getInputExpressions().get(0).toString()); - for (int i = 1; i < numArguments; i++) { - stringBuilder.append(", ").append(getInputExpressions().get(i).toString()); - } - } - return stringBuilder.append(')').toString(); - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctAvgAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctAvgAggregationFunction.java new file mode 100644 index 000000000000..71947839103b --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctAvgAggregationFunction.java @@ -0,0 +1,74 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.segment.spi.AggregationFunctionType; + + +/** + * Aggregation function to compute the average of distinct values for an SV column. + */ +public class DistinctAvgAggregationFunction extends BaseDistinctAggregateAggregationFunction { + + public DistinctAvgAggregationFunction(List arguments, boolean nullHandlingEnabled) { + super(verifySingleArgument(arguments, "DISTINCT_AVG"), AggregationFunctionType.DISTINCTAVG, nullHandlingEnabled); + } + + @Override + public void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + svAggregate(length, aggregationResultHolder, blockValSetMap); + } + + @Override + public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + svAggregateGroupBySV(length, groupKeyArray, groupByResultHolder, blockValSetMap); + } + + @Override + public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + svAggregateGroupByMV(length, groupKeysArray, groupByResultHolder, blockValSetMap); + } + + @Override + public DataSchema.ColumnDataType getFinalResultColumnType() { + return DataSchema.ColumnDataType.DOUBLE; + } + + @Override + public Double extractFinalResult(Set intermediateResult) { + Double distinctSum = 0.0; + + for (Object obj : intermediateResult) { + distinctSum += ((Number) obj).doubleValue(); + } + Double distinctAvg = distinctSum / intermediateResult.size(); + return distinctAvg; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctAvgMVAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctAvgMVAggregationFunction.java new file mode 100644 index 000000000000..37f877000c69 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctAvgMVAggregationFunction.java @@ -0,0 +1,74 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.segment.spi.AggregationFunctionType; + + +/** + * Aggregation function to compute the average of distinct values for an MV column. + */ +public class DistinctAvgMVAggregationFunction extends BaseDistinctAggregateAggregationFunction { + + public DistinctAvgMVAggregationFunction(List arguments) { + super(verifySingleArgument(arguments, "DISTINCT_AVG_MV"), AggregationFunctionType.DISTINCTAVGMV, false); + } + + @Override + public void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + mvAggregate(length, aggregationResultHolder, blockValSetMap); + } + + @Override + public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + mvAggregateGroupBySV(length, groupKeyArray, groupByResultHolder, blockValSetMap); + } + + @Override + public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + mvAggregateGroupByMV(length, groupKeysArray, groupByResultHolder, blockValSetMap); + } + + @Override + public DataSchema.ColumnDataType getFinalResultColumnType() { + return DataSchema.ColumnDataType.DOUBLE; + } + + @Override + public Double extractFinalResult(Set intermediateResult) { + Double distinctSum = 0.0; + + for (Object obj : intermediateResult) { + distinctSum += ((Number) obj).doubleValue(); + } + Double distinctAvg = distinctSum / intermediateResult.size(); + return distinctAvg; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountAggregationFunction.java index 315e2a5bfa3b..076bc2ccdaeb 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountAggregationFunction.java @@ -18,285 +18,43 @@ */ package org.apache.pinot.core.query.aggregation.function; -import it.unimi.dsi.fastutil.doubles.DoubleOpenHashSet; -import it.unimi.dsi.fastutil.floats.FloatOpenHashSet; -import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.List; import java.util.Map; import java.util.Set; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.query.aggregation.AggregationResultHolder; -import org.apache.pinot.core.query.aggregation.ObjectAggregationResultHolder; import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; -import org.apache.pinot.core.query.aggregation.groupby.ObjectGroupByResultHolder; import org.apache.pinot.segment.spi.AggregationFunctionType; -import org.apache.pinot.segment.spi.index.reader.Dictionary; -import org.apache.pinot.spi.data.FieldSpec.DataType; -import org.apache.pinot.spi.utils.ByteArray; -import org.roaringbitmap.PeekableIntIterator; -import org.roaringbitmap.RoaringBitmap; -@SuppressWarnings({"rawtypes", "unchecked"}) -public class DistinctCountAggregationFunction extends BaseSingleInputAggregationFunction { - - public DistinctCountAggregationFunction(ExpressionContext expression) { - super(expression); - } - - @Override - public AggregationFunctionType getType() { - return AggregationFunctionType.DISTINCTCOUNT; - } - - @Override - public AggregationResultHolder createAggregationResultHolder() { - return new ObjectAggregationResultHolder(); - } +/** + * Aggregation function to compute the average of distinct values for an SV column + */ +public class DistinctCountAggregationFunction extends BaseDistinctAggregateAggregationFunction { - @Override - public GroupByResultHolder createGroupByResultHolder(int initialCapacity, int maxCapacity) { - return new ObjectGroupByResultHolder(initialCapacity, maxCapacity); + public DistinctCountAggregationFunction(List arguments, boolean nullHandlingEnabled) { + super(verifySingleArgument(arguments, "DISTINCT_COUNT"), AggregationFunctionType.DISTINCTCOUNT, + nullHandlingEnabled); } @Override public void aggregate(int length, AggregationResultHolder aggregationResultHolder, Map blockValSetMap) { - BlockValSet blockValSet = blockValSetMap.get(_expression); - - // For dictionary-encoded expression, store dictionary ids into the bitmap - Dictionary dictionary = blockValSet.getDictionary(); - if (dictionary != null) { - int[] dictIds = blockValSet.getDictionaryIdsSV(); - getDictIdBitmap(aggregationResultHolder, dictionary).addN(dictIds, 0, length); - return; - } - - // For non-dictionary-encoded expression, store values into the value set - DataType storedType = blockValSet.getValueType().getStoredType(); - Set valueSet = getValueSet(aggregationResultHolder, storedType); - switch (storedType) { - case INT: - IntOpenHashSet intSet = (IntOpenHashSet) valueSet; - int[] intValues = blockValSet.getIntValuesSV(); - for (int i = 0; i < length; i++) { - intSet.add(intValues[i]); - } - break; - case LONG: - LongOpenHashSet longSet = (LongOpenHashSet) valueSet; - long[] longValues = blockValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - longSet.add(longValues[i]); - } - break; - case FLOAT: - FloatOpenHashSet floatSet = (FloatOpenHashSet) valueSet; - float[] floatValues = blockValSet.getFloatValuesSV(); - for (int i = 0; i < length; i++) { - floatSet.add(floatValues[i]); - } - break; - case DOUBLE: - DoubleOpenHashSet doubleSet = (DoubleOpenHashSet) valueSet; - double[] doubleValues = blockValSet.getDoubleValuesSV(); - for (int i = 0; i < length; i++) { - doubleSet.add(doubleValues[i]); - } - break; - case STRING: - ObjectOpenHashSet stringSet = (ObjectOpenHashSet) valueSet; - String[] stringValues = blockValSet.getStringValuesSV(); - //noinspection ManualArrayToCollectionCopy - for (int i = 0; i < length; i++) { - stringSet.add(stringValues[i]); - } - break; - case BYTES: - ObjectOpenHashSet bytesSet = (ObjectOpenHashSet) valueSet; - byte[][] bytesValues = blockValSet.getBytesValuesSV(); - for (int i = 0; i < length; i++) { - bytesSet.add(new ByteArray(bytesValues[i])); - } - break; - default: - throw new IllegalStateException("Illegal data type for DISTINCT_COUNT aggregation function: " + storedType); - } + svAggregate(length, aggregationResultHolder, blockValSetMap); } @Override public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, Map blockValSetMap) { - BlockValSet blockValSet = blockValSetMap.get(_expression); - - // For dictionary-encoded expression, store dictionary ids into the bitmap - Dictionary dictionary = blockValSet.getDictionary(); - if (dictionary != null) { - int[] dictIds = blockValSet.getDictionaryIdsSV(); - for (int i = 0; i < length; i++) { - getDictIdBitmap(groupByResultHolder, groupKeyArray[i], dictionary).add(dictIds[i]); - } - return; - } - - // For non-dictionary-encoded expression, store values into the value set - DataType storedType = blockValSet.getValueType().getStoredType(); - switch (storedType) { - case INT: - int[] intValues = blockValSet.getIntValuesSV(); - for (int i = 0; i < length; i++) { - ((IntOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.INT)).add(intValues[i]); - } - break; - case LONG: - long[] longValues = blockValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - ((LongOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.LONG)).add(longValues[i]); - } - break; - case FLOAT: - float[] floatValues = blockValSet.getFloatValuesSV(); - for (int i = 0; i < length; i++) { - ((FloatOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.FLOAT)).add(floatValues[i]); - } - break; - case DOUBLE: - double[] doubleValues = blockValSet.getDoubleValuesSV(); - for (int i = 0; i < length; i++) { - ((DoubleOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.DOUBLE)) - .add(doubleValues[i]); - } - break; - case STRING: - String[] stringValues = blockValSet.getStringValuesSV(); - for (int i = 0; i < length; i++) { - ((ObjectOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.STRING)) - .add(stringValues[i]); - } - break; - case BYTES: - byte[][] bytesValues = blockValSet.getBytesValuesSV(); - for (int i = 0; i < length; i++) { - ((ObjectOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.BYTES)) - .add(new ByteArray(bytesValues[i])); - } - break; - default: - throw new IllegalStateException("Illegal data type for DISTINCT_COUNT aggregation function: " + storedType); - } + svAggregateGroupBySV(length, groupKeyArray, groupByResultHolder, blockValSetMap); } @Override public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, Map blockValSetMap) { - BlockValSet blockValSet = blockValSetMap.get(_expression); - - // For dictionary-encoded expression, store dictionary ids into the bitmap - Dictionary dictionary = blockValSet.getDictionary(); - if (dictionary != null) { - int[] dictIds = blockValSet.getDictionaryIdsSV(); - for (int i = 0; i < length; i++) { - setDictIdForGroupKeys(groupByResultHolder, groupKeysArray[i], dictionary, dictIds[i]); - } - return; - } - - // For non-dictionary-encoded expression, store values into the value set - DataType storedType = blockValSet.getValueType().getStoredType(); - switch (storedType) { - case INT: - int[] intValues = blockValSet.getIntValuesSV(); - for (int i = 0; i < length; i++) { - setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], intValues[i]); - } - break; - case LONG: - long[] longValues = blockValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], longValues[i]); - } - break; - case FLOAT: - float[] floatValues = blockValSet.getFloatValuesSV(); - for (int i = 0; i < length; i++) { - setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], floatValues[i]); - } - break; - case DOUBLE: - double[] doubleValues = blockValSet.getDoubleValuesSV(); - for (int i = 0; i < length; i++) { - setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], doubleValues[i]); - } - break; - case STRING: - String[] stringValues = blockValSet.getStringValuesSV(); - for (int i = 0; i < length; i++) { - setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], stringValues[i]); - } - break; - case BYTES: - byte[][] bytesValues = blockValSet.getBytesValuesSV(); - for (int i = 0; i < length; i++) { - setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], new ByteArray(bytesValues[i])); - } - break; - default: - throw new IllegalStateException("Illegal data type for DISTINCT_COUNT aggregation function: " + storedType); - } - } - - @Override - public Set extractAggregationResult(AggregationResultHolder aggregationResultHolder) { - Object result = aggregationResultHolder.getResult(); - if (result == null) { - // Use empty IntOpenHashSet as a place holder for empty result - return new IntOpenHashSet(); - } - - if (result instanceof DictIdsWrapper) { - // For dictionary-encoded expression, convert dictionary ids to values - return convertToValueSet((DictIdsWrapper) result); - } else { - // For non-dictionary-encoded expression, directly return the value set - return (Set) result; - } - } - - @Override - public Set extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { - Object result = groupByResultHolder.getResult(groupKey); - if (result == null) { - // NOTE: Return an empty IntOpenHashSet for empty result. - return new IntOpenHashSet(); - } - - if (result instanceof DictIdsWrapper) { - // For dictionary-encoded expression, convert dictionary ids to values - return convertToValueSet((DictIdsWrapper) result); - } else { - // For non-dictionary-encoded expression, directly return the value set - return (Set) result; - } - } - - @Override - public Set merge(Set intermediateResult1, Set intermediateResult2) { - if (intermediateResult1.isEmpty()) { - return intermediateResult2; - } - if (intermediateResult2.isEmpty()) { - return intermediateResult1; - } - intermediateResult1.addAll(intermediateResult2); - return intermediateResult1; - } - - @Override - public ColumnDataType getIntermediateResultColumnType() { - return ColumnDataType.OBJECT; + svAggregateGroupByMV(length, groupKeysArray, groupByResultHolder, blockValSetMap); } @Override @@ -309,199 +67,8 @@ public Integer extractFinalResult(Set intermediateResult) { return intermediateResult.size(); } - /** - * Returns the dictionary id bitmap from the result holder or creates a new one if it does not exist. - */ - protected static RoaringBitmap getDictIdBitmap(AggregationResultHolder aggregationResultHolder, - Dictionary dictionary) { - DictIdsWrapper dictIdsWrapper = aggregationResultHolder.getResult(); - if (dictIdsWrapper == null) { - dictIdsWrapper = new DictIdsWrapper(dictionary); - aggregationResultHolder.setValue(dictIdsWrapper); - } - return dictIdsWrapper._dictIdBitmap; - } - - /** - * Returns the value set from the result holder or creates a new one if it does not exist. - */ - protected static Set getValueSet(AggregationResultHolder aggregationResultHolder, DataType valueType) { - Set valueSet = aggregationResultHolder.getResult(); - if (valueSet == null) { - valueSet = getValueSet(valueType); - aggregationResultHolder.setValue(valueSet); - } - return valueSet; - } - - /** - * Helper method to create a value set for the given value type. - */ - private static Set getValueSet(DataType valueType) { - switch (valueType) { - case INT: - return new IntOpenHashSet(); - case LONG: - return new LongOpenHashSet(); - case FLOAT: - return new FloatOpenHashSet(); - case DOUBLE: - return new DoubleOpenHashSet(); - case STRING: - case BYTES: - return new ObjectOpenHashSet(); - default: - throw new IllegalStateException("Illegal data type for DISTINCT_COUNT aggregation function: " + valueType); - } - } - - /** - * Returns the dictionary id bitmap for the given group key or creates a new one if it does not exist. - */ - protected static RoaringBitmap getDictIdBitmap(GroupByResultHolder groupByResultHolder, int groupKey, - Dictionary dictionary) { - DictIdsWrapper dictIdsWrapper = groupByResultHolder.getResult(groupKey); - if (dictIdsWrapper == null) { - dictIdsWrapper = new DictIdsWrapper(dictionary); - groupByResultHolder.setValueForKey(groupKey, dictIdsWrapper); - } - return dictIdsWrapper._dictIdBitmap; - } - - /** - * Returns the value set for the given group key or creates a new one if it does not exist. - */ - protected static Set getValueSet(GroupByResultHolder groupByResultHolder, int groupKey, DataType valueType) { - Set valueSet = groupByResultHolder.getResult(groupKey); - if (valueSet == null) { - valueSet = getValueSet(valueType); - groupByResultHolder.setValueForKey(groupKey, valueSet); - } - return valueSet; - } - - /** - * Helper method to set dictionary id for the given group keys into the result holder. - */ - private static void setDictIdForGroupKeys(GroupByResultHolder groupByResultHolder, int[] groupKeys, - Dictionary dictionary, int dictId) { - for (int groupKey : groupKeys) { - getDictIdBitmap(groupByResultHolder, groupKey, dictionary).add(dictId); - } - } - - /** - * Helper method to set INT value for the given group keys into the result holder. - */ - private static void setValueForGroupKeys(GroupByResultHolder groupByResultHolder, int[] groupKeys, int value) { - for (int groupKey : groupKeys) { - ((IntOpenHashSet) getValueSet(groupByResultHolder, groupKey, DataType.INT)).add(value); - } - } - - /** - * Helper method to set LONG value for the given group keys into the result holder. - */ - private static void setValueForGroupKeys(GroupByResultHolder groupByResultHolder, int[] groupKeys, long value) { - for (int groupKey : groupKeys) { - ((LongOpenHashSet) getValueSet(groupByResultHolder, groupKey, DataType.LONG)).add(value); - } - } - - /** - * Helper method to set FLOAT value for the given group keys into the result holder. - */ - private static void setValueForGroupKeys(GroupByResultHolder groupByResultHolder, int[] groupKeys, float value) { - for (int groupKey : groupKeys) { - ((FloatOpenHashSet) getValueSet(groupByResultHolder, groupKey, DataType.FLOAT)).add(value); - } - } - - /** - * Helper method to set DOUBLE value for the given group keys into the result holder. - */ - private static void setValueForGroupKeys(GroupByResultHolder groupByResultHolder, int[] groupKeys, double value) { - for (int groupKey : groupKeys) { - ((DoubleOpenHashSet) getValueSet(groupByResultHolder, groupKey, DataType.DOUBLE)).add(value); - } - } - - /** - * Helper method to set STRING value for the given group keys into the result holder. - */ - private static void setValueForGroupKeys(GroupByResultHolder groupByResultHolder, int[] groupKeys, String value) { - for (int groupKey : groupKeys) { - ((ObjectOpenHashSet) getValueSet(groupByResultHolder, groupKey, DataType.STRING)).add(value); - } - } - - /** - * Helper method to set BYTES value for the given group keys into the result holder. - */ - private static void setValueForGroupKeys(GroupByResultHolder groupByResultHolder, int[] groupKeys, ByteArray value) { - for (int groupKey : groupKeys) { - ((ObjectOpenHashSet) getValueSet(groupByResultHolder, groupKey, DataType.BYTES)).add(value); - } - } - - /** - * Helper method to read dictionary and convert dictionary ids to values for dictionary-encoded expression. - */ - private static Set convertToValueSet(DictIdsWrapper dictIdsWrapper) { - Dictionary dictionary = dictIdsWrapper._dictionary; - RoaringBitmap dictIdBitmap = dictIdsWrapper._dictIdBitmap; - int numValues = dictIdBitmap.getCardinality(); - PeekableIntIterator iterator = dictIdBitmap.getIntIterator(); - DataType storedType = dictionary.getValueType(); - switch (storedType) { - case INT: - IntOpenHashSet intSet = new IntOpenHashSet(numValues); - while (iterator.hasNext()) { - intSet.add(dictionary.getIntValue(iterator.next())); - } - return intSet; - case LONG: - LongOpenHashSet longSet = new LongOpenHashSet(numValues); - while (iterator.hasNext()) { - longSet.add(dictionary.getLongValue(iterator.next())); - } - return longSet; - case FLOAT: - FloatOpenHashSet floatSet = new FloatOpenHashSet(numValues); - while (iterator.hasNext()) { - floatSet.add(dictionary.getFloatValue(iterator.next())); - } - return floatSet; - case DOUBLE: - DoubleOpenHashSet doubleSet = new DoubleOpenHashSet(numValues); - while (iterator.hasNext()) { - doubleSet.add(dictionary.getDoubleValue(iterator.next())); - } - return doubleSet; - case STRING: - ObjectOpenHashSet stringSet = new ObjectOpenHashSet<>(numValues); - while (iterator.hasNext()) { - stringSet.add(dictionary.getStringValue(iterator.next())); - } - return stringSet; - case BYTES: - ObjectOpenHashSet bytesSet = new ObjectOpenHashSet<>(numValues); - while (iterator.hasNext()) { - bytesSet.add(new ByteArray(dictionary.getBytesValue(iterator.next()))); - } - return bytesSet; - default: - throw new IllegalStateException("Illegal data type for DISTINCT_COUNT aggregation function: " + storedType); - } - } - - private static final class DictIdsWrapper { - final Dictionary _dictionary; - final RoaringBitmap _dictIdBitmap; - - private DictIdsWrapper(Dictionary dictionary) { - _dictionary = dictionary; - _dictIdBitmap = new RoaringBitmap(); - } + @Override + public Integer mergeFinalResult(Integer finalResult1, Integer finalResult2) { + return finalResult1 + finalResult2; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountBitmapAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountBitmapAggregationFunction.java index a2faa1d0ea7b..d3a759333540 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountBitmapAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountBitmapAggregationFunction.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.core.query.aggregation.function; +import java.util.List; import java.util.Map; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; @@ -41,7 +42,11 @@ */ public class DistinctCountBitmapAggregationFunction extends BaseSingleInputAggregationFunction { - public DistinctCountBitmapAggregationFunction(ExpressionContext expression) { + public DistinctCountBitmapAggregationFunction(List arguments) { + this(verifySingleArgument(arguments, "DISTINCT_COUNT_BITMAP")); + } + + protected DistinctCountBitmapAggregationFunction(ExpressionContext expression) { super(expression); } @@ -324,6 +329,11 @@ public Integer extractFinalResult(RoaringBitmap intermediateResult) { return intermediateResult.getCardinality(); } + @Override + public Integer mergeFinalResult(Integer finalResult1, Integer finalResult2) { + return finalResult1 + finalResult2; + } + /** * Returns the dictionary id bitmap from the result holder or creates a new one if it does not exist. */ diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountBitmapMVAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountBitmapMVAggregationFunction.java index 14739419a3a9..cb278ec58681 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountBitmapMVAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountBitmapMVAggregationFunction.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.core.query.aggregation.function; +import java.util.List; import java.util.Map; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.core.common.BlockValSet; @@ -36,8 +37,8 @@ */ public class DistinctCountBitmapMVAggregationFunction extends DistinctCountBitmapAggregationFunction { - public DistinctCountBitmapMVAggregationFunction(ExpressionContext expression) { - super(expression); + public DistinctCountBitmapMVAggregationFunction(List arguments) { + super(verifySingleArgument(arguments, "DISTINCT_COUNT_BITMAP_MV")); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountCPCSketchAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountCPCSketchAggregationFunction.java new file mode 100644 index 000000000000..8784ec7373d8 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountCPCSketchAggregationFunction.java @@ -0,0 +1,639 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import com.google.common.base.Preconditions; +import java.util.List; +import java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.apache.datasketches.cpc.CpcSketch; +import org.apache.datasketches.memory.Memory; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.ObjectAggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.ObjectGroupByResultHolder; +import org.apache.pinot.segment.local.customobject.CpcSketchAccumulator; +import org.apache.pinot.segment.spi.AggregationFunctionType; +import org.apache.pinot.segment.spi.index.reader.Dictionary; +import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.utils.CommonConstants; +import org.roaringbitmap.PeekableIntIterator; +import org.roaringbitmap.RoaringBitmap; + + +/** + * The {@code DistinctCountCPCSketchAggregationFunction} is used for space-efficient cardinality estimation. + * The Apache Datasketches CPC sketch is a unique-counting sketch that implements the + * Compressed Probabilistic Counting (CPC, a.k.a FM85) algorithms developed by Kevin Lang in his paper + * Back to the Future: an Even More Nearly Optimal Cardinality Estimation + * Algorithm. + *

+ * The stored CPC sketch can consume about 40% less space than an HLL sketch of comparable accuracy. CPC sketches have + * been intentionally designed to offer different tradeoffs to HLL sketches so that, they complement each + * other in many ways. For more information, see the Apache Datasketches documentation. + *

+ * The aggregation function supports both pre-aggregated sketches or raw values, but no post-aggregation is supported. + * Usage examples: + *

    + *
  • + * Simple union (1 or 2 arguments): main expression to aggregate on, followed by an optional CPC sketch size + * argument. The second argument is the sketch lgK – the given log_base2 of k, and defaults to 12. + * The "raw" equivalents return serialised sketches in base64-encoded strings. + *

    DISTINCT_COUNT_CPC_SKETCH(col)

    + *

    DISTINCT_COUNT_CPC_SKETCH(col, 12)

    + *

    DISTINCT_COUNT_RAW_CPC_SKETCH(col)

    + *

    DISTINCT_COUNT_RAW_CPC_SKETCH(col, 12)

    + *
  • + * Extracting a cardinality estimate from a CPC sketch: + *

    GET_CPC_SKETCH_ESTIMATE(sketch_bytes)

    + *

    GET_CPC_SKETCH_ESTIMATE(DISTINCT_COUNT_RAW_CPC_SKETCH(col))

    + *
  • + *
  • + * Union between two sketches: + *

    + * CPC_SKETCH_UNION( + * DISTINCT_COUNT_RAW_CPC_SKETCH(col1), + * DISTINCT_COUNT_RAW_CPC_SKETCH(col2) + * ) + *

    + *
  • + *
+ */ +@SuppressWarnings({"rawtypes"}) +public class DistinctCountCPCSketchAggregationFunction + extends BaseSingleInputAggregationFunction { + private static final int DEFAULT_ACCUMULATOR_THRESHOLD = 2; + protected int _accumulatorThreshold = DEFAULT_ACCUMULATOR_THRESHOLD; + protected int _lgNominalEntries; + + public DistinctCountCPCSketchAggregationFunction(List arguments) { + super(arguments.get(0)); + int numExpressions = arguments.size(); + // This function expects 1 or 2 arguments - it is a code smell to extend the base for single + // input aggregation functions. Nevertheless, there are other functions in the base class that + // are apply here. See also: Theta sketch aggregation function. + Preconditions.checkArgument(numExpressions <= 2, "DistinctCountCPC expects 1 or 2 arguments, got: %s", + numExpressions); + if (arguments.size() == 2) { + ExpressionContext secondArgument = arguments.get(1); + Preconditions.checkArgument(secondArgument.getType() == ExpressionContext.Type.LITERAL, + "CPC Sketch Aggregation Function expects the second argument to be a literal (parameters)," + " but got: ", + secondArgument.getType()); + + if (secondArgument.getLiteral().getType() == FieldSpec.DataType.STRING) { + Parameters parameters = new Parameters(secondArgument.getLiteral().getStringValue()); + // Allows the user to trade-off memory usage for merge CPU; higher values use more memory + _accumulatorThreshold = parameters.getAccumulatorThreshold(); + // Nominal entries controls sketch accuracy and size + _lgNominalEntries = parameters.getLgNominalEntries(); + } else { + _lgNominalEntries = secondArgument.getLiteral().getIntValue(); + } + } else { + _lgNominalEntries = CommonConstants.Helix.DEFAULT_CPC_SKETCH_LGK; + } + } + + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.DISTINCTCOUNTCPCSKETCH; + } + + @Override + public AggregationResultHolder createAggregationResultHolder() { + return new ObjectAggregationResultHolder(); + } + + @Override + public GroupByResultHolder createGroupByResultHolder(int initialCapacity, int maxCapacity) { + return new ObjectGroupByResultHolder(initialCapacity, maxCapacity); + } + + @Override + public void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + + // Treat BYTES value as serialized CPC Sketch + FieldSpec.DataType storedType = blockValSet.getValueType().getStoredType(); + if (storedType == DataType.BYTES) { + byte[][] bytesValues = blockValSet.getBytesValuesSV(); + try { + CpcSketchAccumulator cpcSketchAccumulator = getAccumulator(aggregationResultHolder); + CpcSketch[] sketches = deserializeSketches(bytesValues, length); + for (CpcSketch sketch : sketches) { + cpcSketchAccumulator.apply(sketch); + } + } catch (Exception e) { + throw new RuntimeException("Caught exception while merging CPC sketches", e); + } + return; + } + + // For dictionary-encoded expression, store dictionary ids into the bitmap + Dictionary dictionary = blockValSet.getDictionary(); + if (dictionary != null) { + int[] dictIds = blockValSet.getDictionaryIdsSV(); + getDictIdBitmap(aggregationResultHolder, dictionary).addN(dictIds, 0, length); + return; + } + + // For non-dictionary-encoded expression, store values into the CpcSketch + CpcSketch cpcSketch = getCpcSketch(aggregationResultHolder); + switch (storedType) { + case INT: + int[] intValues = blockValSet.getIntValuesSV(); + for (int i = 0; i < length; i++) { + cpcSketch.update(intValues[i]); + } + break; + case LONG: + long[] longValues = blockValSet.getLongValuesSV(); + for (int i = 0; i < length; i++) { + cpcSketch.update(longValues[i]); + } + break; + case FLOAT: + float[] floatValues = blockValSet.getFloatValuesSV(); + for (int i = 0; i < length; i++) { + cpcSketch.update(floatValues[i]); + } + break; + case DOUBLE: + double[] doubleValues = blockValSet.getDoubleValuesSV(); + for (int i = 0; i < length; i++) { + cpcSketch.update(doubleValues[i]); + } + break; + case STRING: + String[] stringValues = blockValSet.getStringValuesSV(); + for (int i = 0; i < length; i++) { + cpcSketch.update(stringValues[i]); + } + break; + default: + throw new IllegalStateException("Illegal data type for DISTINCT_COUNT_CPC aggregation function: " + storedType); + } + CpcSketchAccumulator cpcSketchAccumulator = getAccumulator(aggregationResultHolder); + cpcSketchAccumulator.apply(cpcSketch); + } + + @Override + public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + + // Treat BYTES value as serialized CPC Sketch + DataType storedType = blockValSet.getValueType().getStoredType(); + if (storedType == FieldSpec.DataType.BYTES) { + byte[][] bytesValues = blockValSet.getBytesValuesSV(); + try { + CpcSketch[] sketches = deserializeSketches(bytesValues, length); + for (int i = 0; i < length; i++) { + CpcSketchAccumulator cpcSketchAccumulator = getAccumulator(groupByResultHolder, groupKeyArray[i]); + CpcSketch sketch = sketches[i]; + cpcSketchAccumulator.apply(sketch); + } + } catch (Exception e) { + throw new RuntimeException("Caught exception while aggregating CPC Sketches", e); + } + return; + } + + // For dictionary-encoded expression, store dictionary ids into the bitmap + Dictionary dictionary = blockValSet.getDictionary(); + if (dictionary != null) { + int[] dictIds = blockValSet.getDictionaryIdsSV(); + for (int i = 0; i < length; i++) { + getDictIdBitmap(groupByResultHolder, groupKeyArray[i], dictionary).add(dictIds[i]); + } + return; + } + + // For non-dictionary-encoded expression, store values into the CpcSketch + switch (storedType) { + case INT: + int[] intValues = blockValSet.getIntValuesSV(); + for (int i = 0; i < length; i++) { + getCpcSketch(groupByResultHolder, groupKeyArray[i]).update(intValues[i]); + } + break; + case LONG: + long[] longValues = blockValSet.getLongValuesSV(); + for (int i = 0; i < length; i++) { + getCpcSketch(groupByResultHolder, groupKeyArray[i]).update(longValues[i]); + } + break; + case FLOAT: + float[] floatValues = blockValSet.getFloatValuesSV(); + for (int i = 0; i < length; i++) { + getCpcSketch(groupByResultHolder, groupKeyArray[i]).update(floatValues[i]); + } + break; + case DOUBLE: + double[] doubleValues = blockValSet.getDoubleValuesSV(); + for (int i = 0; i < length; i++) { + getCpcSketch(groupByResultHolder, groupKeyArray[i]).update(doubleValues[i]); + } + break; + case STRING: + String[] stringValues = blockValSet.getStringValuesSV(); + for (int i = 0; i < length; i++) { + getCpcSketch(groupByResultHolder, groupKeyArray[i]).update(stringValues[i]); + } + break; + default: + throw new IllegalStateException("Illegal data type for DISTINCT_COUNT_CPC aggregation function: " + storedType); + } + } + + @Override + public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + + // Treat BYTES value as serialized CPC Sketch + DataType storedType = blockValSet.getValueType().getStoredType(); + boolean singleValue = blockValSet.isSingleValue(); + + if (singleValue && storedType == DataType.BYTES) { + byte[][] bytesValues = blockValSet.getBytesValuesSV(); + try { + CpcSketch[] sketches = deserializeSketches(bytesValues, length); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + getAccumulator(groupByResultHolder, groupKey).apply(sketches[i]); + } + } + } catch (Exception e) { + throw new RuntimeException("Caught exception while aggregating CPC sketches", e); + } + return; + } + + // For dictionary-encoded expression, store dictionary ids into the bitmap + Dictionary dictionary = blockValSet.getDictionary(); + if (dictionary != null) { + int[] dictIds = blockValSet.getDictionaryIdsSV(); + for (int i = 0; i < length; i++) { + setDictIdForGroupKeys(groupByResultHolder, groupKeysArray[i], dictionary, dictIds[i]); + } + return; + } + + // For non-dictionary-encoded expression, store values into the CpcSketch + switch (storedType) { + case INT: + int[] intValues = blockValSet.getIntValuesSV(); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + getCpcSketch(groupByResultHolder, groupKey).update(intValues[i]); + } + } + break; + case LONG: + long[] longValues = blockValSet.getLongValuesSV(); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + getCpcSketch(groupByResultHolder, groupKey).update(longValues[i]); + } + } + break; + case FLOAT: + float[] floatValues = blockValSet.getFloatValuesSV(); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + getCpcSketch(groupByResultHolder, groupKey).update(floatValues[i]); + } + } + break; + case DOUBLE: + double[] doubleValues = blockValSet.getDoubleValuesSV(); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + getCpcSketch(groupByResultHolder, groupKey).update(doubleValues[i]); + } + } + break; + case STRING: + String[] stringValues = blockValSet.getStringValuesSV(); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + getCpcSketch(groupByResultHolder, groupKey).update(stringValues[i]); + } + } + break; + default: + throw new IllegalStateException("Illegal data type for DISTINCT_COUNT_CPC aggregation function: " + storedType); + } + } + + @Override + public CpcSketchAccumulator extractAggregationResult(AggregationResultHolder aggregationResultHolder) { + Object result = aggregationResultHolder.getResult(); + if (result == null) { + return new CpcSketchAccumulator(_lgNominalEntries, _accumulatorThreshold); + } + + if (result instanceof CpcSketch) { + return convertSketchAccumulator(result); + } else if (result instanceof DictIdsWrapper) { + // For dictionary-encoded expression, convert dictionary ids to CpcSketch + return convertSketchAccumulator(dictionaryToCpcSketch((DictIdsWrapper) result)); + } else { + return (CpcSketchAccumulator) result; + } + } + + @Override + public CpcSketchAccumulator extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { + Object result = groupByResultHolder.getResult(groupKey); + if (result == null) { + return new CpcSketchAccumulator(_lgNominalEntries, _accumulatorThreshold); + } + + if (result instanceof CpcSketch) { + return convertSketchAccumulator(result); + } else if (result instanceof DictIdsWrapper) { + // For dictionary-encoded expression, convert dictionary ids to CpcSketch + return convertSketchAccumulator(dictionaryToCpcSketch((DictIdsWrapper) result)); + } else { + return (CpcSketchAccumulator) result; + } + } + + @Override + public CpcSketchAccumulator merge(CpcSketchAccumulator intermediateResult1, + CpcSketchAccumulator intermediateResult2) { + if (intermediateResult1 == null || intermediateResult1.isEmpty()) { + return intermediateResult2; + } + if (intermediateResult2 == null || intermediateResult2.isEmpty()) { + return intermediateResult1; + } + intermediateResult1.setLgNominalEntries(_lgNominalEntries); + intermediateResult1.setThreshold(_accumulatorThreshold); + intermediateResult1.merge(intermediateResult2); + return intermediateResult1; + } + + @Override + public DataSchema.ColumnDataType getIntermediateResultColumnType() { + return DataSchema.ColumnDataType.OBJECT; + } + + @Override + public DataSchema.ColumnDataType getFinalResultColumnType() { + return DataSchema.ColumnDataType.LONG; + } + + @Override + public Comparable extractFinalResult(CpcSketchAccumulator intermediateResult) { + intermediateResult.setLgNominalEntries(_lgNominalEntries); + intermediateResult.setThreshold(_accumulatorThreshold); + return Math.round(intermediateResult.getResult().getEstimate()); + } + + @Override + public Comparable mergeFinalResult(Comparable finalResult1, Comparable finalResult2) { + return (Long) finalResult1 + (Long) finalResult2; + } + + /** + * Returns the CpcSketch from the result holder or creates a new one if it does not exist. + */ + protected CpcSketch getCpcSketch(AggregationResultHolder aggregationResultHolder) { + CpcSketch cpcSketch = aggregationResultHolder.getResult(); + if (cpcSketch == null) { + cpcSketch = new CpcSketch(_lgNominalEntries); + aggregationResultHolder.setValue(cpcSketch); + } + return cpcSketch; + } + + /** + * Returns the dictionary id bitmap from the result holder or creates a new one if it does not exist. + */ + protected static RoaringBitmap getDictIdBitmap(AggregationResultHolder aggregationResultHolder, + Dictionary dictionary) { + DictIdsWrapper dictIdsWrapper = aggregationResultHolder.getResult(); + if (dictIdsWrapper == null) { + dictIdsWrapper = new DictIdsWrapper(dictionary); + aggregationResultHolder.setValue(dictIdsWrapper); + } + return dictIdsWrapper._dictIdBitmap; + } + + /** + * Returns the dictionary id bitmap for the given group key or creates a new one if it does not exist. + */ + protected static RoaringBitmap getDictIdBitmap(GroupByResultHolder groupByResultHolder, int groupKey, + Dictionary dictionary) { + DictIdsWrapper dictIdsWrapper = groupByResultHolder.getResult(groupKey); + if (dictIdsWrapper == null) { + dictIdsWrapper = new DictIdsWrapper(dictionary); + groupByResultHolder.setValueForKey(groupKey, dictIdsWrapper); + } + return dictIdsWrapper._dictIdBitmap; + } + + /** + * Returns the CpcSketch for the given group key or creates a new one if it does not exist. + */ + protected CpcSketch getCpcSketch(GroupByResultHolder groupByResultHolder, int groupKey) { + CpcSketch cpcSketch = groupByResultHolder.getResult(groupKey); + if (cpcSketch == null) { + cpcSketch = new CpcSketch(_lgNominalEntries); + groupByResultHolder.setValueForKey(groupKey, cpcSketch); + } + return cpcSketch; + } + + /** + * Helper method to set dictionary id for the given group keys into the result holder. + */ + private static void setDictIdForGroupKeys(GroupByResultHolder groupByResultHolder, int[] groupKeys, + Dictionary dictionary, int dictId) { + for (int groupKey : groupKeys) { + getDictIdBitmap(groupByResultHolder, groupKey, dictionary).add(dictId); + } + } + + private CpcSketch dictionaryToCpcSketch(DictIdsWrapper dictIdsWrapper) { + CpcSketch cpcSketch = new CpcSketch(_lgNominalEntries); + Dictionary dictionary = dictIdsWrapper._dictionary; + RoaringBitmap dictIdBitmap = dictIdsWrapper._dictIdBitmap; + PeekableIntIterator iterator = dictIdBitmap.getIntIterator(); + while (iterator.hasNext()) { + Object value = dictionary.get(iterator.next()); + addObjectToSketch(value, cpcSketch); + } + return cpcSketch; + } + + private void addObjectToSketch(Object rawValue, CpcSketch sketch) { + if (rawValue instanceof String) { + sketch.update((String) rawValue); + } else if (rawValue instanceof Integer) { + sketch.update((Integer) rawValue); + } else if (rawValue instanceof Long) { + sketch.update((Long) rawValue); + } else if (rawValue instanceof Double) { + sketch.update((Double) rawValue); + } else if (rawValue instanceof Float) { + sketch.update((Float) rawValue); + } else if (rawValue instanceof Object[]) { + addObjectsToSketch((Object[]) rawValue, sketch); + } else { + throw new IllegalStateException( + "Unsupported data type for CPC Sketch aggregation: " + rawValue.getClass().getSimpleName()); + } + } + + private void addObjectsToSketch(Object[] rawValues, CpcSketch sketch) { + if (rawValues instanceof String[]) { + for (String s : (String[]) rawValues) { + sketch.update(s); + } + } else if (rawValues instanceof Integer[]) { + for (Integer i : (Integer[]) rawValues) { + sketch.update(i); + } + } else if (rawValues instanceof Long[]) { + for (Long l : (Long[]) rawValues) { + sketch.update(l); + } + } else if (rawValues instanceof Double[]) { + for (Double d : (Double[]) rawValues) { + sketch.update(d); + } + } else if (rawValues instanceof Float[]) { + for (Float f : (Float[]) rawValues) { + sketch.update(f); + } + } else { + throw new IllegalStateException( + "Unsupported data type for CPC Sketch aggregation: " + rawValues.getClass().getSimpleName()); + } + } + + /** + * Returns the accumulator from the result holder or creates a new one if it does not exist. + */ + private CpcSketchAccumulator getAccumulator(AggregationResultHolder aggregationResultHolder) { + CpcSketchAccumulator accumulator = aggregationResultHolder.getResult(); + if (accumulator == null) { + accumulator = new CpcSketchAccumulator(_lgNominalEntries, _accumulatorThreshold); + aggregationResultHolder.setValue(accumulator); + } + return accumulator; + } + + /** + * Returns the accumulator for the given group key or creates a new one if it does not exist. + */ + private CpcSketchAccumulator getAccumulator(GroupByResultHolder groupByResultHolder, int groupKey) { + CpcSketchAccumulator accumulator = groupByResultHolder.getResult(groupKey); + if (accumulator == null) { + accumulator = new CpcSketchAccumulator(_lgNominalEntries, _accumulatorThreshold); + groupByResultHolder.setValueForKey(groupKey, accumulator); + } + return accumulator; + } + + /** + * Deserializes the sketches from the bytes. + */ + @SuppressWarnings({"unchecked"}) + private CpcSketch[] deserializeSketches(byte[][] serializedSketches, int length) { + CpcSketch[] sketches = new CpcSketch[length]; + for (int i = 0; i < length; i++) { + sketches[i] = CpcSketch.heapify(Memory.wrap(serializedSketches[i])); + } + return sketches; + } + + // This ensures backward compatibility with servers that still return sketches directly. + // The AggregationDataTableReducer casts intermediate results to Objects and although the code compiles, + // types might still be incompatible at runtime due to type erasure. + // Due to performance overheads of redundant casts, this should be removed at some future point. + protected CpcSketchAccumulator convertSketchAccumulator(Object result) { + if (result instanceof CpcSketch) { + CpcSketch sketch = (CpcSketch) result; + CpcSketchAccumulator accumulator = new CpcSketchAccumulator(_lgNominalEntries, _accumulatorThreshold); + accumulator.apply(sketch); + return accumulator; + } + return (CpcSketchAccumulator) result; + } + + private static final class DictIdsWrapper { + final Dictionary _dictionary; + final RoaringBitmap _dictIdBitmap; + + private DictIdsWrapper(Dictionary dictionary) { + _dictionary = dictionary; + _dictIdBitmap = new RoaringBitmap(); + } + } + + /** + * Helper class to wrap the CpcSketch parameters. The initial values for the parameters are set to the + * same defaults in the Apache Datasketches library. + */ + private static class Parameters { + private static final char PARAMETER_DELIMITER = ';'; + private static final char PARAMETER_KEY_VALUE_SEPARATOR = '='; + private static final String NOMINAL_ENTRIES_KEY = "nominalEntries"; + private static final String ACCUMULATOR_THRESHOLD_KEY = "accumulatorThreshold"; + + private int _nominalEntries = (int) Math.pow(2, CommonConstants.Helix.DEFAULT_CPC_SKETCH_LGK); + private int _accumulatorThreshold = DEFAULT_ACCUMULATOR_THRESHOLD; + + Parameters(String parametersString) { + StringUtils.deleteWhitespace(parametersString); + String[] keyValuePairs = StringUtils.split(parametersString, PARAMETER_DELIMITER); + for (String keyValuePair : keyValuePairs) { + String[] keyAndValue = StringUtils.split(keyValuePair, PARAMETER_KEY_VALUE_SEPARATOR); + Preconditions.checkArgument(keyAndValue.length == 2, "Invalid parameter: %s", keyValuePair); + String key = keyAndValue[0]; + String value = keyAndValue[1]; + if (key.equalsIgnoreCase(NOMINAL_ENTRIES_KEY)) { + _nominalEntries = Integer.parseInt(value); + } else if (key.equalsIgnoreCase(ACCUMULATOR_THRESHOLD_KEY)) { + _accumulatorThreshold = Integer.parseInt(value); + } else { + throw new IllegalArgumentException("Invalid parameter key: " + key); + } + } + } + + int getLgNominalEntries() { + return org.apache.datasketches.common.Util.exactLog2OfInt(_nominalEntries); + } + + int getAccumulatorThreshold() { + return _accumulatorThreshold; + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountHLLAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountHLLAggregationFunction.java index 914177ae8cb5..504c542f0e6e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountHLLAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountHLLAggregationFunction.java @@ -48,7 +48,7 @@ public DistinctCountHLLAggregationFunction(List arguments) { Preconditions.checkArgument(numExpressions <= 2, "DistinctCountHLL expects 1 or 2 arguments, got: %s", numExpressions); if (arguments.size() == 2) { - _log2m = Integer.parseInt(arguments.get(1).getLiteralString()); + _log2m = arguments.get(1).getLiteral().getIntValue(); } else { _log2m = CommonConstants.Helix.DEFAULT_HYPERLOGLOG_LOG2M; } @@ -363,6 +363,11 @@ public Long extractFinalResult(HyperLogLog intermediateResult) { return intermediateResult.cardinality(); } + @Override + public Long mergeFinalResult(Long finalResult1, Long finalResult2) { + return finalResult1 + finalResult2; + } + /** * Returns the dictionary id bitmap from the result holder or creates a new one if it does not exist. */ diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountHLLPlusAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountHLLPlusAggregationFunction.java new file mode 100644 index 000000000000..b27f4dd52496 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountHLLPlusAggregationFunction.java @@ -0,0 +1,476 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import com.clearspring.analytics.stream.cardinality.HyperLogLogPlus; +import com.google.common.base.Preconditions; +import java.util.List; +import java.util.Map; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.common.ObjectSerDeUtils; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.ObjectAggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.ObjectGroupByResultHolder; +import org.apache.pinot.segment.spi.AggregationFunctionType; +import org.apache.pinot.segment.spi.index.reader.Dictionary; +import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.utils.CommonConstants; +import org.roaringbitmap.PeekableIntIterator; +import org.roaringbitmap.RoaringBitmap; + + +public class DistinctCountHLLPlusAggregationFunction extends BaseSingleInputAggregationFunction { + // The parameter "p" determines the precision of the sparse list in HyperLogLogPlus. + protected final int _p; + // The "sp" parameter specifies the number of standard deviations that the sparse list's precision should be set to. + protected final int _sp; + + public DistinctCountHLLPlusAggregationFunction(List arguments) { + super(arguments.get(0)); + int numExpressions = arguments.size(); + // This function expects 1 or 2 or 3 arguments. + Preconditions.checkArgument(numExpressions <= 3, "DistinctCountHLLPlus expects 2 or 3 arguments, got: %s", + numExpressions); + if (arguments.size() == 2) { + _p = arguments.get(1).getLiteral().getIntValue(); + _sp = CommonConstants.Helix.DEFAULT_HYPERLOGLOG_PLUS_SP; + } else if (arguments.size() == 3) { + _p = arguments.get(1).getLiteral().getIntValue(); + _sp = arguments.get(2).getLiteral().getIntValue(); + } else { + _p = CommonConstants.Helix.DEFAULT_HYPERLOGLOG_PLUS_P; + _sp = CommonConstants.Helix.DEFAULT_HYPERLOGLOG_PLUS_SP; + } + } + + public int getP() { + return _p; + } + + public int getSp() { + return _sp; + } + + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.DISTINCTCOUNTHLLPLUS; + } + + @Override + public AggregationResultHolder createAggregationResultHolder() { + return new ObjectAggregationResultHolder(); + } + + @Override + public GroupByResultHolder createGroupByResultHolder(int initialCapacity, int maxCapacity) { + return new ObjectGroupByResultHolder(initialCapacity, maxCapacity); + } + + @Override + public void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + + // Treat BYTES value as serialized HyperLogLogPlus + DataType storedType = blockValSet.getValueType().getStoredType(); + if (storedType == DataType.BYTES) { + byte[][] bytesValues = blockValSet.getBytesValuesSV(); + try { + HyperLogLogPlus hyperLogLogPlus = aggregationResultHolder.getResult(); + if (hyperLogLogPlus == null) { + hyperLogLogPlus = ObjectSerDeUtils.HYPER_LOG_LOG_PLUS_SER_DE.deserialize(bytesValues[0]); + aggregationResultHolder.setValue(hyperLogLogPlus); + } else { + hyperLogLogPlus.addAll(ObjectSerDeUtils.HYPER_LOG_LOG_PLUS_SER_DE.deserialize(bytesValues[0])); + } + for (int i = 1; i < length; i++) { + hyperLogLogPlus.addAll(ObjectSerDeUtils.HYPER_LOG_LOG_PLUS_SER_DE.deserialize(bytesValues[i])); + } + } catch (Exception e) { + throw new RuntimeException("Caught exception while merging HyperLogLogPlus", e); + } + return; + } + + // For dictionary-encoded expression, store dictionary ids into the bitmap + Dictionary dictionary = blockValSet.getDictionary(); + if (dictionary != null) { + int[] dictIds = blockValSet.getDictionaryIdsSV(); + getDictIdBitmap(aggregationResultHolder, dictionary).addN(dictIds, 0, length); + return; + } + + // For non-dictionary-encoded expression, store values into the HyperLogLogPlus + HyperLogLogPlus hyperLogLogPlus = getHyperLogLogPlus(aggregationResultHolder); + switch (storedType) { + case INT: + int[] intValues = blockValSet.getIntValuesSV(); + for (int i = 0; i < length; i++) { + hyperLogLogPlus.offer(intValues[i]); + } + break; + case LONG: + long[] longValues = blockValSet.getLongValuesSV(); + for (int i = 0; i < length; i++) { + hyperLogLogPlus.offer(longValues[i]); + } + break; + case FLOAT: + float[] floatValues = blockValSet.getFloatValuesSV(); + for (int i = 0; i < length; i++) { + hyperLogLogPlus.offer(floatValues[i]); + } + break; + case DOUBLE: + double[] doubleValues = blockValSet.getDoubleValuesSV(); + for (int i = 0; i < length; i++) { + hyperLogLogPlus.offer(doubleValues[i]); + } + break; + case STRING: + String[] stringValues = blockValSet.getStringValuesSV(); + for (int i = 0; i < length; i++) { + hyperLogLogPlus.offer(stringValues[i]); + } + break; + default: + throw new IllegalStateException( + "Illegal data type for DISTINCT_COUNT_HLL_PLUS aggregation function: " + storedType); + } + } + + @Override + public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + + // Treat BYTES value as serialized HyperLogLogPlus + DataType storedType = blockValSet.getValueType().getStoredType(); + if (storedType == DataType.BYTES) { + byte[][] bytesValues = blockValSet.getBytesValuesSV(); + try { + for (int i = 0; i < length; i++) { + HyperLogLogPlus value = ObjectSerDeUtils.HYPER_LOG_LOG_PLUS_SER_DE.deserialize(bytesValues[i]); + int groupKey = groupKeyArray[i]; + HyperLogLogPlus hyperLogLogPlus = groupByResultHolder.getResult(groupKey); + if (hyperLogLogPlus != null) { + hyperLogLogPlus.addAll(value); + } else { + groupByResultHolder.setValueForKey(groupKey, value); + } + } + } catch (Exception e) { + throw new RuntimeException("Caught exception while merging HyperLogLogPlus", e); + } + return; + } + + // For dictionary-encoded expression, store dictionary ids into the bitmap + Dictionary dictionary = blockValSet.getDictionary(); + if (dictionary != null) { + int[] dictIds = blockValSet.getDictionaryIdsSV(); + for (int i = 0; i < length; i++) { + getDictIdBitmap(groupByResultHolder, groupKeyArray[i], dictionary).add(dictIds[i]); + } + return; + } + + // For non-dictionary-encoded expression, store values into the HyperLogLogPlus + switch (storedType) { + case INT: + int[] intValues = blockValSet.getIntValuesSV(); + for (int i = 0; i < length; i++) { + getHyperLogLogPlus(groupByResultHolder, groupKeyArray[i]).offer(intValues[i]); + } + break; + case LONG: + long[] longValues = blockValSet.getLongValuesSV(); + for (int i = 0; i < length; i++) { + getHyperLogLogPlus(groupByResultHolder, groupKeyArray[i]).offer(longValues[i]); + } + break; + case FLOAT: + float[] floatValues = blockValSet.getFloatValuesSV(); + for (int i = 0; i < length; i++) { + getHyperLogLogPlus(groupByResultHolder, groupKeyArray[i]).offer(floatValues[i]); + } + break; + case DOUBLE: + double[] doubleValues = blockValSet.getDoubleValuesSV(); + for (int i = 0; i < length; i++) { + getHyperLogLogPlus(groupByResultHolder, groupKeyArray[i]).offer(doubleValues[i]); + } + break; + case STRING: + String[] stringValues = blockValSet.getStringValuesSV(); + for (int i = 0; i < length; i++) { + getHyperLogLogPlus(groupByResultHolder, groupKeyArray[i]).offer(stringValues[i]); + } + break; + default: + throw new IllegalStateException( + "Illegal data type for DISTINCT_COUNT_HLL_PLUS aggregation function: " + storedType); + } + } + + @Override + public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + + // Treat BYTES value as serialized HyperLogLogPlus + DataType storedType = blockValSet.getValueType().getStoredType(); + if (storedType == DataType.BYTES) { + byte[][] bytesValues = blockValSet.getBytesValuesSV(); + try { + for (int i = 0; i < length; i++) { + HyperLogLogPlus value = ObjectSerDeUtils.HYPER_LOG_LOG_PLUS_SER_DE.deserialize(bytesValues[i]); + for (int groupKey : groupKeysArray[i]) { + HyperLogLogPlus hyperLogLogPlus = groupByResultHolder.getResult(groupKey); + if (hyperLogLogPlus != null) { + hyperLogLogPlus.addAll(value); + } else { + // Create a new HyperLogLogPlus for the group + groupByResultHolder.setValueForKey(groupKey, + ObjectSerDeUtils.HYPER_LOG_LOG_PLUS_SER_DE.deserialize(bytesValues[i])); + } + } + } + } catch (Exception e) { + throw new RuntimeException("Caught exception while merging HyperLogLogPlus", e); + } + return; + } + + // For dictionary-encoded expression, store dictionary ids into the bitmap + Dictionary dictionary = blockValSet.getDictionary(); + if (dictionary != null) { + int[] dictIds = blockValSet.getDictionaryIdsSV(); + for (int i = 0; i < length; i++) { + setDictIdForGroupKeys(groupByResultHolder, groupKeysArray[i], dictionary, dictIds[i]); + } + return; + } + + // For non-dictionary-encoded expression, store values into the HyperLogLogPlus + switch (storedType) { + case INT: + int[] intValues = blockValSet.getIntValuesSV(); + for (int i = 0; i < length; i++) { + setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], intValues[i]); + } + break; + case LONG: + long[] longValues = blockValSet.getLongValuesSV(); + for (int i = 0; i < length; i++) { + setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], longValues[i]); + } + break; + case FLOAT: + float[] floatValues = blockValSet.getFloatValuesSV(); + for (int i = 0; i < length; i++) { + setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], floatValues[i]); + } + break; + case DOUBLE: + double[] doubleValues = blockValSet.getDoubleValuesSV(); + for (int i = 0; i < length; i++) { + setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], doubleValues[i]); + } + break; + case STRING: + String[] stringValues = blockValSet.getStringValuesSV(); + for (int i = 0; i < length; i++) { + setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], stringValues[i]); + } + break; + default: + throw new IllegalStateException( + "Illegal data type for DISTINCT_COUNT_HLL_PLUS aggregation function: " + storedType); + } + } + + @Override + public HyperLogLogPlus extractAggregationResult(AggregationResultHolder aggregationResultHolder) { + Object result = aggregationResultHolder.getResult(); + if (result == null) { + return new HyperLogLogPlus(_p, _sp); + } + + if (result instanceof DictIdsWrapper) { + // For dictionary-encoded expression, convert dictionary ids to HyperLogLogPlus + return convertToHyperLogLogPlus((DictIdsWrapper) result); + } else { + // For non-dictionary-encoded expression, directly return the HyperLogLogPlus + return (HyperLogLogPlus) result; + } + } + + @Override + public HyperLogLogPlus extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { + Object result = groupByResultHolder.getResult(groupKey); + if (result == null) { + return new HyperLogLogPlus(_p, _sp); + } + + if (result instanceof DictIdsWrapper) { + // For dictionary-encoded expression, convert dictionary ids to HyperLogLogPlus + return convertToHyperLogLogPlus((DictIdsWrapper) result); + } else { + // For non-dictionary-encoded expression, directly return the HyperLogLogPlus + return (HyperLogLogPlus) result; + } + } + + @Override + public HyperLogLogPlus merge(HyperLogLogPlus intermediateResult1, HyperLogLogPlus intermediateResult2) { + // Can happen when aggregating serialized HyperLogLogPlus with non-default p, sp values + if (intermediateResult1.sizeof() != intermediateResult2.sizeof()) { + if (intermediateResult1.cardinality() == 0) { + return intermediateResult2; + } else { + Preconditions.checkState(intermediateResult2.cardinality() == 0, + "Cannot merge HyperLogLogPlus of different sizes"); + return intermediateResult1; + } + } + try { + intermediateResult1.addAll(intermediateResult2); + } catch (Exception e) { + throw new RuntimeException("Caught exception while merging HyperLogLogPlus", e); + } + return intermediateResult1; + } + + @Override + public ColumnDataType getIntermediateResultColumnType() { + return ColumnDataType.OBJECT; + } + + @Override + public ColumnDataType getFinalResultColumnType() { + return ColumnDataType.LONG; + } + + @Override + public Long extractFinalResult(HyperLogLogPlus intermediateResult) { + return intermediateResult.cardinality(); + } + + @Override + public Long mergeFinalResult(Long finalResult1, Long finalResult2) { + return finalResult1 + finalResult2; + } + + /** + * Returns the dictionary id bitmap from the result holder or creates a new one if it does not exist. + */ + protected static RoaringBitmap getDictIdBitmap(AggregationResultHolder aggregationResultHolder, + Dictionary dictionary) { + DictIdsWrapper dictIdsWrapper = aggregationResultHolder.getResult(); + if (dictIdsWrapper == null) { + dictIdsWrapper = new DictIdsWrapper(dictionary); + aggregationResultHolder.setValue(dictIdsWrapper); + } + return dictIdsWrapper._dictIdBitmap; + } + + /** + * Returns the HyperLogLogPlus from the result holder or creates a new one if it does not exist. + */ + protected HyperLogLogPlus getHyperLogLogPlus(AggregationResultHolder aggregationResultHolder) { + HyperLogLogPlus hyperLogLogPlus = aggregationResultHolder.getResult(); + if (hyperLogLogPlus == null) { + hyperLogLogPlus = new HyperLogLogPlus(_p, _sp); + aggregationResultHolder.setValue(hyperLogLogPlus); + } + return hyperLogLogPlus; + } + + /** + * Returns the dictionary id bitmap for the given group key or creates a new one if it does not exist. + */ + protected static RoaringBitmap getDictIdBitmap(GroupByResultHolder groupByResultHolder, int groupKey, + Dictionary dictionary) { + DictIdsWrapper dictIdsWrapper = groupByResultHolder.getResult(groupKey); + if (dictIdsWrapper == null) { + dictIdsWrapper = new DictIdsWrapper(dictionary); + groupByResultHolder.setValueForKey(groupKey, dictIdsWrapper); + } + return dictIdsWrapper._dictIdBitmap; + } + + /** + * Returns the HyperLogLogPlus for the given group key or creates a new one if it does not exist. + */ + protected HyperLogLogPlus getHyperLogLogPlus(GroupByResultHolder groupByResultHolder, int groupKey) { + HyperLogLogPlus hyperLogLogPlus = groupByResultHolder.getResult(groupKey); + if (hyperLogLogPlus == null) { + hyperLogLogPlus = new HyperLogLogPlus(_p, _sp); + groupByResultHolder.setValueForKey(groupKey, hyperLogLogPlus); + } + return hyperLogLogPlus; + } + + /** + * Helper method to set dictionary id for the given group keys into the result holder. + */ + private static void setDictIdForGroupKeys(GroupByResultHolder groupByResultHolder, int[] groupKeys, + Dictionary dictionary, int dictId) { + for (int groupKey : groupKeys) { + getDictIdBitmap(groupByResultHolder, groupKey, dictionary).add(dictId); + } + } + + /** + * Helper method to set value for the given group keys into the result holder. + */ + private void setValueForGroupKeys(GroupByResultHolder groupByResultHolder, int[] groupKeys, Object value) { + for (int groupKey : groupKeys) { + getHyperLogLogPlus(groupByResultHolder, groupKey).offer(value); + } + } + + /** + * Helper method to read dictionary and convert dictionary ids to HyperLogLogPlus for dictionary-encoded expression. + */ + private HyperLogLogPlus convertToHyperLogLogPlus(DictIdsWrapper dictIdsWrapper) { + HyperLogLogPlus hyperLogLogPlus = new HyperLogLogPlus(_p, _sp); + Dictionary dictionary = dictIdsWrapper._dictionary; + RoaringBitmap dictIdBitmap = dictIdsWrapper._dictIdBitmap; + PeekableIntIterator iterator = dictIdBitmap.getIntIterator(); + while (iterator.hasNext()) { + hyperLogLogPlus.offer(dictionary.get(iterator.next())); + } + return hyperLogLogPlus; + } + + private static final class DictIdsWrapper { + final Dictionary _dictionary; + final RoaringBitmap _dictIdBitmap; + + private DictIdsWrapper(Dictionary dictionary) { + _dictionary = dictionary; + _dictIdBitmap = new RoaringBitmap(); + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountHLLPlusMVAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountHLLPlusMVAggregationFunction.java new file mode 100644 index 000000000000..00abb1f5d28a --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountHLLPlusMVAggregationFunction.java @@ -0,0 +1,265 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import com.clearspring.analytics.stream.cardinality.HyperLogLogPlus; +import java.util.List; +import java.util.Map; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.segment.spi.AggregationFunctionType; +import org.apache.pinot.segment.spi.index.reader.Dictionary; +import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.roaringbitmap.RoaringBitmap; + + +public class DistinctCountHLLPlusMVAggregationFunction extends DistinctCountHLLPlusAggregationFunction { + + public DistinctCountHLLPlusMVAggregationFunction(List arguments) { + super(arguments); + } + + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.DISTINCTCOUNTHLLPLUSMV; + } + + @Override + public void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + + // For dictionary-encoded expression, store dictionary ids into the bitmap + Dictionary dictionary = blockValSet.getDictionary(); + if (dictionary != null) { + RoaringBitmap dictIdBitmap = getDictIdBitmap(aggregationResultHolder, dictionary); + int[][] dictIds = blockValSet.getDictionaryIdsMV(); + for (int i = 0; i < length; i++) { + dictIdBitmap.add(dictIds[i]); + } + return; + } + + // For non-dictionary-encoded expression, store values into the HyperLogLog + HyperLogLogPlus hyperLogLogPlus = getHyperLogLogPlus(aggregationResultHolder); + DataType storedType = blockValSet.getValueType().getStoredType(); + switch (storedType) { + case INT: + int[][] intValuesArray = blockValSet.getIntValuesMV(); + for (int i = 0; i < length; i++) { + for (int value : intValuesArray[i]) { + hyperLogLogPlus.offer(value); + } + } + break; + case LONG: + long[][] longValuesArray = blockValSet.getLongValuesMV(); + for (int i = 0; i < length; i++) { + for (long value : longValuesArray[i]) { + hyperLogLogPlus.offer(value); + } + } + break; + case FLOAT: + float[][] floatValuesArray = blockValSet.getFloatValuesMV(); + for (int i = 0; i < length; i++) { + for (float value : floatValuesArray[i]) { + hyperLogLogPlus.offer(value); + } + } + break; + case DOUBLE: + double[][] doubleValuesArray = blockValSet.getDoubleValuesMV(); + for (int i = 0; i < length; i++) { + for (double value : doubleValuesArray[i]) { + hyperLogLogPlus.offer(value); + } + } + break; + case STRING: + String[][] stringValuesArray = blockValSet.getStringValuesMV(); + for (int i = 0; i < length; i++) { + for (String value : stringValuesArray[i]) { + hyperLogLogPlus.offer(value); + } + } + break; + default: + throw new IllegalStateException( + "Illegal data type for DISTINCT_COUNT_HLL_MV aggregation function: " + storedType); + } + } + + @Override + public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + + // For dictionary-encoded expression, store dictionary ids into the bitmap + Dictionary dictionary = blockValSet.getDictionary(); + if (dictionary != null) { + int[][] dictIds = blockValSet.getDictionaryIdsMV(); + for (int i = 0; i < length; i++) { + getDictIdBitmap(groupByResultHolder, groupKeyArray[i], dictionary).add(dictIds[i]); + } + return; + } + + // For non-dictionary-encoded expression, store values into the HyperLogLog + DataType storedType = blockValSet.getValueType().getStoredType(); + switch (storedType) { + case INT: + int[][] intValuesArray = blockValSet.getIntValuesMV(); + for (int i = 0; i < length; i++) { + HyperLogLogPlus hyperLogLogPlus = getHyperLogLogPlus(groupByResultHolder, groupKeyArray[i]); + for (int value : intValuesArray[i]) { + hyperLogLogPlus.offer(value); + } + } + break; + case LONG: + long[][] longValuesArray = blockValSet.getLongValuesMV(); + for (int i = 0; i < length; i++) { + HyperLogLogPlus hyperLogLogPlus = getHyperLogLogPlus(groupByResultHolder, groupKeyArray[i]); + for (long value : longValuesArray[i]) { + hyperLogLogPlus.offer(value); + } + } + break; + case FLOAT: + float[][] floatValuesArray = blockValSet.getFloatValuesMV(); + for (int i = 0; i < length; i++) { + HyperLogLogPlus hyperLogLogPlus = getHyperLogLogPlus(groupByResultHolder, groupKeyArray[i]); + for (float value : floatValuesArray[i]) { + hyperLogLogPlus.offer(value); + } + } + break; + case DOUBLE: + double[][] doubleValuesArray = blockValSet.getDoubleValuesMV(); + for (int i = 0; i < length; i++) { + HyperLogLogPlus hyperLogLogPlus = getHyperLogLogPlus(groupByResultHolder, groupKeyArray[i]); + for (double value : doubleValuesArray[i]) { + hyperLogLogPlus.offer(value); + } + } + break; + case STRING: + String[][] stringValuesArray = blockValSet.getStringValuesMV(); + for (int i = 0; i < length; i++) { + HyperLogLogPlus hyperLogLogPlus = getHyperLogLogPlus(groupByResultHolder, groupKeyArray[i]); + for (String value : stringValuesArray[i]) { + hyperLogLogPlus.offer(value); + } + } + break; + default: + throw new IllegalStateException( + "Illegal data type for DISTINCT_COUNT_HLL_MV aggregation function: " + storedType); + } + } + + @Override + public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + + // For dictionary-encoded expression, store dictionary ids into the bitmap + Dictionary dictionary = blockValSet.getDictionary(); + if (dictionary != null) { + int[][] dictIds = blockValSet.getDictionaryIdsMV(); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + getDictIdBitmap(groupByResultHolder, groupKey, dictionary).add(dictIds[i]); + } + } + return; + } + + // For non-dictionary-encoded expression, store values into the HyperLogLog + DataType storedType = blockValSet.getValueType().getStoredType(); + switch (storedType) { + case INT: + int[][] intValuesArray = blockValSet.getIntValuesMV(); + for (int i = 0; i < length; i++) { + int[] intValues = intValuesArray[i]; + for (int groupKey : groupKeysArray[i]) { + HyperLogLogPlus hyperLogLogPlus = getHyperLogLogPlus(groupByResultHolder, groupKey); + for (int value : intValues) { + hyperLogLogPlus.offer(value); + } + } + } + break; + case LONG: + long[][] longValuesArray = blockValSet.getLongValuesMV(); + for (int i = 0; i < length; i++) { + long[] longValues = longValuesArray[i]; + for (int groupKey : groupKeysArray[i]) { + HyperLogLogPlus hyperLogLogPlus = getHyperLogLogPlus(groupByResultHolder, groupKey); + for (long value : longValues) { + hyperLogLogPlus.offer(value); + } + } + } + break; + case FLOAT: + float[][] floatValuesArray = blockValSet.getFloatValuesMV(); + for (int i = 0; i < length; i++) { + float[] floatValues = floatValuesArray[i]; + for (int groupKey : groupKeysArray[i]) { + HyperLogLogPlus hyperLogLogPlus = getHyperLogLogPlus(groupByResultHolder, groupKey); + for (float value : floatValues) { + hyperLogLogPlus.offer(value); + } + } + } + break; + case DOUBLE: + double[][] doubleValuesArray = blockValSet.getDoubleValuesMV(); + for (int i = 0; i < length; i++) { + double[] doubleValues = doubleValuesArray[i]; + for (int groupKey : groupKeysArray[i]) { + HyperLogLogPlus hyperLogLogPlus = getHyperLogLogPlus(groupByResultHolder, groupKey); + for (double value : doubleValues) { + hyperLogLogPlus.offer(value); + } + } + } + break; + case STRING: + String[][] stringValuesArray = blockValSet.getStringValuesMV(); + for (int i = 0; i < length; i++) { + String[] stringValues = stringValuesArray[i]; + for (int groupKey : groupKeysArray[i]) { + HyperLogLogPlus hyperLogLogPlus = getHyperLogLogPlus(groupByResultHolder, groupKey); + for (String value : stringValues) { + hyperLogLogPlus.offer(value); + } + } + } + break; + default: + throw new IllegalStateException( + "Illegal data type for DISTINCT_COUNT_HLL_MV aggregation function: " + storedType); + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountIntegerTupleSketchAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountIntegerTupleSketchAggregationFunction.java new file mode 100644 index 000000000000..b10797a58c18 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountIntegerTupleSketchAggregationFunction.java @@ -0,0 +1,59 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import java.util.List; +import org.apache.datasketches.tuple.aninteger.IntegerSummary; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.segment.local.customobject.TupleIntSketchAccumulator; +import org.apache.pinot.segment.spi.AggregationFunctionType; + + +public class DistinctCountIntegerTupleSketchAggregationFunction extends IntegerTupleSketchAggregationFunction { + + public DistinctCountIntegerTupleSketchAggregationFunction(List arguments, + IntegerSummary.Mode mode) { + super(arguments, mode); + } + + // TODO if extra aggregation modes are supported, make this switch + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.DISTINCTCOUNTTUPLESKETCH; + } + + @Override + public ColumnDataType getFinalResultColumnType() { + return ColumnDataType.LONG; + } + + @Override + public Comparable extractFinalResult(TupleIntSketchAccumulator accumulator) { + accumulator.setNominalEntries(_nominalEntries); + accumulator.setSetOperations(_setOps); + accumulator.setThreshold(_accumulatorThreshold); + return Double.valueOf(accumulator.getResult().getEstimate()).longValue(); + } + + @Override + public Comparable mergeFinalResult(Comparable finalResult1, Comparable finalResult2) { + return (Long) finalResult1 + (Long) finalResult2; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountMVAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountMVAggregationFunction.java index 6dc65461a99c..aa1cd6da6688 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountMVAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountMVAggregationFunction.java @@ -18,261 +18,56 @@ */ package org.apache.pinot.core.query.aggregation.function; -import it.unimi.dsi.fastutil.doubles.DoubleOpenHashSet; -import it.unimi.dsi.fastutil.floats.FloatOpenHashSet; -import it.unimi.dsi.fastutil.ints.IntOpenHashSet; -import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.List; import java.util.Map; import java.util.Set; import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.query.aggregation.AggregationResultHolder; import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; import org.apache.pinot.segment.spi.AggregationFunctionType; -import org.apache.pinot.segment.spi.index.reader.Dictionary; -import org.apache.pinot.spi.data.FieldSpec.DataType; -import org.roaringbitmap.RoaringBitmap; -@SuppressWarnings({"rawtypes", "unchecked"}) -public class DistinctCountMVAggregationFunction extends DistinctCountAggregationFunction { - - public DistinctCountMVAggregationFunction(ExpressionContext expression) { - super(expression); - } +/** + * Aggregation function to compute the average of distinct values for an MV column + */ +public class DistinctCountMVAggregationFunction extends BaseDistinctAggregateAggregationFunction { - @Override - public AggregationFunctionType getType() { - return AggregationFunctionType.DISTINCTCOUNTMV; + public DistinctCountMVAggregationFunction(List arguments) { + super(verifySingleArgument(arguments, "DISTINCT_COUNT_MV"), AggregationFunctionType.DISTINCTCOUNTMV, false); } @Override public void aggregate(int length, AggregationResultHolder aggregationResultHolder, Map blockValSetMap) { - BlockValSet blockValSet = blockValSetMap.get(_expression); - - // For dictionary-encoded expression, store dictionary ids into the bitmap - Dictionary dictionary = blockValSet.getDictionary(); - if (dictionary != null) { - RoaringBitmap dictIdBitmap = getDictIdBitmap(aggregationResultHolder, dictionary); - int[][] dictIds = blockValSet.getDictionaryIdsMV(); - for (int i = 0; i < length; i++) { - dictIdBitmap.add(dictIds[i]); - } - return; - } - - // For non-dictionary-encoded expression, store values into the value set - DataType storedType = blockValSet.getValueType().getStoredType(); - Set valueSet = getValueSet(aggregationResultHolder, storedType); - switch (storedType) { - case INT: - IntOpenHashSet intSet = (IntOpenHashSet) valueSet; - int[][] intValues = blockValSet.getIntValuesMV(); - for (int i = 0; i < length; i++) { - for (int value : intValues[i]) { - intSet.add(value); - } - } - break; - case LONG: - LongOpenHashSet longSet = (LongOpenHashSet) valueSet; - long[][] longValues = blockValSet.getLongValuesMV(); - for (int i = 0; i < length; i++) { - for (long value : longValues[i]) { - longSet.add(value); - } - } - break; - case FLOAT: - FloatOpenHashSet floatSet = (FloatOpenHashSet) valueSet; - float[][] floatValues = blockValSet.getFloatValuesMV(); - for (int i = 0; i < length; i++) { - for (float value : floatValues[i]) { - floatSet.add(value); - } - } - break; - case DOUBLE: - DoubleOpenHashSet doubleSet = (DoubleOpenHashSet) valueSet; - double[][] doubleValues = blockValSet.getDoubleValuesMV(); - for (int i = 0; i < length; i++) { - for (double value : doubleValues[i]) { - doubleSet.add(value); - } - } - break; - case STRING: - ObjectOpenHashSet stringSet = (ObjectOpenHashSet) valueSet; - String[][] stringValues = blockValSet.getStringValuesMV(); - for (int i = 0; i < length; i++) { - //noinspection ManualArrayToCollectionCopy - for (String value : stringValues[i]) { - //noinspection UseBulkOperation - stringSet.add(value); - } - } - break; - default: - throw new IllegalStateException("Illegal data type for DISTINCT_COUNT_MV aggregation function: " + storedType); - } + mvAggregate(length, aggregationResultHolder, blockValSetMap); } @Override public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, Map blockValSetMap) { - BlockValSet blockValSet = blockValSetMap.get(_expression); - - // For dictionary-encoded expression, store dictionary ids into the bitmap - Dictionary dictionary = blockValSet.getDictionary(); - if (dictionary != null) { - int[][] dictIds = blockValSet.getDictionaryIdsMV(); - for (int i = 0; i < length; i++) { - getDictIdBitmap(groupByResultHolder, groupKeyArray[i], dictionary).add(dictIds[i]); - } - return; - } - - // For non-dictionary-encoded expression, store values into the value set - DataType storedType = blockValSet.getValueType().getStoredType(); - switch (storedType) { - case INT: - int[][] intValues = blockValSet.getIntValuesMV(); - for (int i = 0; i < length; i++) { - IntOpenHashSet intSet = (IntOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.INT); - for (int value : intValues[i]) { - intSet.add(value); - } - } - break; - case LONG: - long[][] longValues = blockValSet.getLongValuesMV(); - for (int i = 0; i < length; i++) { - LongOpenHashSet longSet = (LongOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.LONG); - for (long value : longValues[i]) { - longSet.add(value); - } - } - break; - case FLOAT: - float[][] floatValues = blockValSet.getFloatValuesMV(); - for (int i = 0; i < length; i++) { - FloatOpenHashSet floatSet = - (FloatOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.FLOAT); - for (float value : floatValues[i]) { - floatSet.add(value); - } - } - break; - case DOUBLE: - double[][] doubleValues = blockValSet.getDoubleValuesMV(); - for (int i = 0; i < length; i++) { - DoubleOpenHashSet doubleSet = - (DoubleOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.DOUBLE); - for (double value : doubleValues[i]) { - doubleSet.add(value); - } - } - break; - case STRING: - String[][] stringValues = blockValSet.getStringValuesMV(); - for (int i = 0; i < length; i++) { - ObjectOpenHashSet stringSet = - (ObjectOpenHashSet) getValueSet(groupByResultHolder, groupKeyArray[i], DataType.STRING); - //noinspection ManualArrayToCollectionCopy - for (String value : stringValues[i]) { - //noinspection UseBulkOperation - stringSet.add(value); - } - } - break; - default: - throw new IllegalStateException("Illegal data type for DISTINCT_COUNT_MV aggregation function: " + storedType); - } + mvAggregateGroupBySV(length, groupKeyArray, groupByResultHolder, blockValSetMap); } @Override public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, Map blockValSetMap) { - BlockValSet blockValSet = blockValSetMap.get(_expression); + mvAggregateGroupByMV(length, groupKeysArray, groupByResultHolder, blockValSetMap); + } + + @Override + public DataSchema.ColumnDataType getFinalResultColumnType() { + return DataSchema.ColumnDataType.INT; + } - // For dictionary-encoded expression, store dictionary ids into the bitmap - Dictionary dictionary = blockValSet.getDictionary(); - if (dictionary != null) { - int[][] dictIds = blockValSet.getDictionaryIdsMV(); - for (int i = 0; i < length; i++) { - for (int groupKey : groupKeysArray[i]) { - getDictIdBitmap(groupByResultHolder, groupKey, dictionary).add(dictIds[i]); - } - } - return; - } + @Override + public Integer extractFinalResult(Set intermediateResult) { + return intermediateResult.size(); + } - // For non-dictionary-encoded expression, store hash code of the values into the value set - DataType storedType = blockValSet.getValueType().getStoredType(); - switch (storedType) { - case INT: - int[][] intValues = blockValSet.getIntValuesMV(); - for (int i = 0; i < length; i++) { - for (int groupKey : groupKeysArray[i]) { - IntOpenHashSet intSet = (IntOpenHashSet) getValueSet(groupByResultHolder, groupKey, DataType.INT); - for (int value : intValues[i]) { - intSet.add(value); - } - } - } - break; - case LONG: - long[][] longValues = blockValSet.getLongValuesMV(); - for (int i = 0; i < length; i++) { - for (int groupKey : groupKeysArray[i]) { - LongOpenHashSet longSet = (LongOpenHashSet) getValueSet(groupByResultHolder, groupKey, DataType.LONG); - for (long value : longValues[i]) { - longSet.add(value); - } - } - } - break; - case FLOAT: - float[][] floatValues = blockValSet.getFloatValuesMV(); - for (int i = 0; i < length; i++) { - for (int groupKey : groupKeysArray[i]) { - FloatOpenHashSet floatSet = (FloatOpenHashSet) getValueSet(groupByResultHolder, groupKey, DataType.FLOAT); - for (float value : floatValues[i]) { - floatSet.add(value); - } - } - } - break; - case DOUBLE: - double[][] doubleValues = blockValSet.getDoubleValuesMV(); - for (int i = 0; i < length; i++) { - for (int groupKey : groupKeysArray[i]) { - DoubleOpenHashSet doubleSet = - (DoubleOpenHashSet) getValueSet(groupByResultHolder, groupKey, DataType.DOUBLE); - for (double value : doubleValues[i]) { - doubleSet.add(value); - } - } - } - break; - case STRING: - String[][] stringValues = blockValSet.getStringValuesMV(); - for (int i = 0; i < length; i++) { - for (int groupKey : groupKeysArray[i]) { - ObjectOpenHashSet stringSet = - (ObjectOpenHashSet) getValueSet(groupByResultHolder, groupKey, DataType.STRING); - //noinspection ManualArrayToCollectionCopy - for (String value : stringValues[i]) { - //noinspection UseBulkOperation - stringSet.add(value); - } - } - } - break; - default: - throw new IllegalStateException("Illegal data type for DISTINCT_COUNT_MV aggregation function: " + storedType); - } + @Override + public Integer mergeFinalResult(Integer finalResult1, Integer finalResult2) { + return finalResult1 + finalResult2; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawCPCSketchAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawCPCSketchAggregationFunction.java new file mode 100644 index 000000000000..ff3a587881ca --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawCPCSketchAggregationFunction.java @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import java.util.List; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.segment.local.customobject.CpcSketchAccumulator; +import org.apache.pinot.segment.local.customobject.SerializedCPCSketch; +import org.apache.pinot.segment.spi.AggregationFunctionType; + + +/** + * The {@code DistinctCountRawCPCAggregationFunction} shares the same usage as the + * {@link DistinctCountCPCSketchAggregationFunction}, and returns the sketch as a base64 encoded string. + */ +public class DistinctCountRawCPCSketchAggregationFunction extends DistinctCountCPCSketchAggregationFunction { + + public DistinctCountRawCPCSketchAggregationFunction(List arguments) { + super(arguments); + } + + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.DISTINCTCOUNTRAWCPCSKETCH; + } + + @Override + public ColumnDataType getFinalResultColumnType() { + return ColumnDataType.STRING; + } + + @Override + public SerializedCPCSketch extractFinalResult(CpcSketchAccumulator intermediateResult) { + intermediateResult.setLgNominalEntries(_lgNominalEntries); + intermediateResult.setThreshold(_accumulatorThreshold); + return new SerializedCPCSketch(intermediateResult.getResult()); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawHLLPlusAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawHLLPlusAggregationFunction.java new file mode 100644 index 000000000000..facef6a22251 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawHLLPlusAggregationFunction.java @@ -0,0 +1,115 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import com.clearspring.analytics.stream.cardinality.HyperLogLogPlus; +import java.util.List; +import java.util.Map; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.segment.local.customobject.SerializedHLLPlus; +import org.apache.pinot.segment.spi.AggregationFunctionType; + + +public class DistinctCountRawHLLPlusAggregationFunction + extends BaseSingleInputAggregationFunction { + private final DistinctCountHLLPlusAggregationFunction _distinctCountHLLPlusAggregationFunction; + + public DistinctCountRawHLLPlusAggregationFunction(List arguments) { + this(arguments.get(0), new DistinctCountHLLPlusAggregationFunction(arguments)); + } + + DistinctCountRawHLLPlusAggregationFunction(ExpressionContext expression, + DistinctCountHLLPlusAggregationFunction distinctCountHLLPlusAggregationFunction) { + super(expression); + _distinctCountHLLPlusAggregationFunction = distinctCountHLLPlusAggregationFunction; + } + + public DistinctCountHLLPlusAggregationFunction getDistinctCountHLLPlusAggregationFunction() { + return _distinctCountHLLPlusAggregationFunction; + } + + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.DISTINCTCOUNTRAWHLLPLUS; + } + + @Override + public AggregationResultHolder createAggregationResultHolder() { + return _distinctCountHLLPlusAggregationFunction.createAggregationResultHolder(); + } + + @Override + public GroupByResultHolder createGroupByResultHolder(int initialCapacity, int maxCapacity) { + return _distinctCountHLLPlusAggregationFunction.createGroupByResultHolder(initialCapacity, maxCapacity); + } + + @Override + public void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + _distinctCountHLLPlusAggregationFunction.aggregate(length, aggregationResultHolder, blockValSetMap); + } + + @Override + public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + _distinctCountHLLPlusAggregationFunction.aggregateGroupBySV(length, groupKeyArray, groupByResultHolder, + blockValSetMap); + } + + @Override + public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + _distinctCountHLLPlusAggregationFunction.aggregateGroupByMV(length, groupKeysArray, groupByResultHolder, + blockValSetMap); + } + + @Override + public HyperLogLogPlus extractAggregationResult(AggregationResultHolder aggregationResultHolder) { + return _distinctCountHLLPlusAggregationFunction.extractAggregationResult(aggregationResultHolder); + } + + @Override + public HyperLogLogPlus extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { + return _distinctCountHLLPlusAggregationFunction.extractGroupByResult(groupByResultHolder, groupKey); + } + + @Override + public HyperLogLogPlus merge(HyperLogLogPlus intermediateResult1, HyperLogLogPlus intermediateResult2) { + return _distinctCountHLLPlusAggregationFunction.merge(intermediateResult1, intermediateResult2); + } + + @Override + public ColumnDataType getIntermediateResultColumnType() { + return _distinctCountHLLPlusAggregationFunction.getIntermediateResultColumnType(); + } + + @Override + public ColumnDataType getFinalResultColumnType() { + return ColumnDataType.STRING; + } + + @Override + public SerializedHLLPlus extractFinalResult(HyperLogLogPlus intermediateResult) { + return new SerializedHLLPlus(intermediateResult); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawHLLPlusMVAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawHLLPlusMVAggregationFunction.java new file mode 100644 index 000000000000..6ae2d0499626 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawHLLPlusMVAggregationFunction.java @@ -0,0 +1,36 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import java.util.List; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.segment.spi.AggregationFunctionType; + + +public class DistinctCountRawHLLPlusMVAggregationFunction extends DistinctCountRawHLLPlusAggregationFunction { + + public DistinctCountRawHLLPlusMVAggregationFunction(List arguments) { + super(arguments.get(0), new DistinctCountHLLPlusMVAggregationFunction(arguments)); + } + + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.DISTINCTCOUNTRAWHLLPLUSMV; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawThetaSketchAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawThetaSketchAggregationFunction.java index cd75fa3807c9..e25b8e0ddbbc 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawThetaSketchAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawThetaSketchAggregationFunction.java @@ -18,11 +18,13 @@ */ package org.apache.pinot.core.query.aggregation.function; +import java.util.ArrayList; import java.util.Base64; import java.util.List; import org.apache.datasketches.theta.Sketch; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.segment.local.customobject.ThetaSketchAccumulator; import org.apache.pinot.segment.spi.AggregationFunctionType; @@ -47,11 +49,18 @@ public ColumnDataType getFinalResultColumnType() { } @Override - public String extractFinalResult(List sketches) { - Sketch sketch = evaluatePostAggregationExpression(sketches); + public String extractFinalResult(List accumulators) { + int numAccumulators = accumulators.size(); + List mergedSketches = new ArrayList<>(numAccumulators); - // NOTE: Compact the sketch in unsorted, on-heap fashion for performance concern. - // See https://datasketches.apache.org/docs/Theta/ThetaSize.html for more details. - return Base64.getEncoder().encodeToString(sketch.compact(false, null).toByteArray()); + for (Object object : accumulators) { + ThetaSketchAccumulator accumulator = convertSketchAccumulator(object); + accumulator.setThreshold(_accumulatorThreshold); + accumulator.setSetOperationBuilder(_setOperationBuilder); + mergedSketches.add(accumulator.getResult()); + } + + Sketch sketch = evaluatePostAggregationExpression(mergedSketches); + return Base64.getEncoder().encodeToString(sketch.toByteArray()); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawULLAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawULLAggregationFunction.java new file mode 100644 index 000000000000..fb98636e1d1d --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountRawULLAggregationFunction.java @@ -0,0 +1,47 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import com.dynatrace.hash4j.distinctcount.UltraLogLog; +import java.util.List; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.segment.local.customobject.SerializedULL; +import org.apache.pinot.segment.spi.AggregationFunctionType; + + +public class DistinctCountRawULLAggregationFunction extends DistinctCountULLAggregationFunction { + public DistinctCountRawULLAggregationFunction(List arguments) { + super(arguments); + } + + @Override + public SerializedULL extractFinalResult(UltraLogLog intermediateResult) { + return new SerializedULL(intermediateResult); + } + + public AggregationFunctionType getType() { + return AggregationFunctionType.DISTINCTCOUNTULL; + } + + @Override + public ColumnDataType getFinalResultColumnType() { + return ColumnDataType.STRING; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountSmartHLLAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountSmartHLLAggregationFunction.java index 87e418f93528..800cc4798949 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountSmartHLLAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountSmartHLLAggregationFunction.java @@ -68,7 +68,7 @@ public DistinctCountSmartHLLAggregationFunction(List argument super(arguments.get(0)); if (arguments.size() > 1) { - Parameters parameters = new Parameters(arguments.get(1).getLiteralString()); + Parameters parameters = new Parameters(arguments.get(1).getLiteral().getStringValue()); _threshold = parameters._threshold; _log2m = parameters._log2m; } else { @@ -734,6 +734,11 @@ public Integer extractFinalResult(Object intermediateResult) { } } + @Override + public Integer mergeFinalResult(Integer finalResult1, Integer finalResult2) { + return finalResult1 + finalResult2; + } + /** * Returns the dictionary id bitmap from the result holder or creates a new one if it does not exist. */ diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountThetaSketchAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountThetaSketchAggregationFunction.java index 317cb486935a..aef397b821cb 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountThetaSketchAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountThetaSketchAggregationFunction.java @@ -25,7 +25,6 @@ import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; -import org.apache.datasketches.Util; import org.apache.datasketches.memory.Memory; import org.apache.datasketches.theta.AnotB; import org.apache.datasketches.theta.Intersection; @@ -34,6 +33,7 @@ import org.apache.datasketches.theta.Union; import org.apache.datasketches.theta.UpdateSketch; import org.apache.datasketches.theta.UpdateSketchBuilder; +import org.apache.datasketches.thetacommon.ThetaUtil; import org.apache.pinot.common.request.Expression; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.FilterContext; @@ -48,6 +48,7 @@ import org.apache.pinot.core.query.aggregation.ObjectAggregationResultHolder; import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; import org.apache.pinot.core.query.aggregation.groupby.ObjectGroupByResultHolder; +import org.apache.pinot.segment.local.customobject.ThetaSketchAccumulator; import org.apache.pinot.segment.spi.AggregationFunctionType; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.sql.parsers.CalciteSqlParser; @@ -68,29 +69,31 @@ * 'dimName=''course'' AND dimValue=''math''', 'SET_INTERSECT($1,$2)') * * - * Currently there is only 1 parameter for the function: + * Currently, there are 3 parameters to the function: *
    *
  • * nominalEntries: The nominal entries used to create the sketch. (Default 4096) + * samplingProbability: Sets the upfront uniform sampling probability, p. (Default 1.0) + * accumulatorThreshold: How many sketches should be kept in memory before merging. (Default 2) *
  • *
*

E.g. DISTINCT_COUNT_THETA_SKETCH(col, 'nominalEntries=8192') */ @SuppressWarnings({"rawtypes", "unchecked"}) public class DistinctCountThetaSketchAggregationFunction - extends BaseSingleInputAggregationFunction, Comparable> { + extends BaseSingleInputAggregationFunction, Comparable> { private static final String SET_UNION = "setunion"; private static final String SET_INTERSECT = "setintersect"; private static final String SET_DIFF = "setdiff"; private static final String DEFAULT_SKETCH_IDENTIFIER = "$0"; - private static final Sketch EMPTY_SKETCH = new UpdateSketchBuilder().build().compact(); - + private static final int DEFAULT_ACCUMULATOR_THRESHOLD = 2; private final List _inputExpressions; private final boolean _includeDefaultSketch; private final List _filterEvaluators; private final ExpressionContext _postAggregationExpression; private final UpdateSketchBuilder _updateSketchBuilder = new UpdateSketchBuilder(); - private final SetOperationBuilder _setOperationBuilder = new SetOperationBuilder(); + protected final SetOperationBuilder _setOperationBuilder = new SetOperationBuilder(); + protected int _accumulatorThreshold = DEFAULT_ACCUMULATOR_THRESHOLD; public DistinctCountThetaSketchAggregationFunction(List arguments) { super(arguments.get(0)); @@ -101,10 +104,17 @@ public DistinctCountThetaSketchAggregationFunction(List argum ExpressionContext paramsExpression = arguments.get(1); Preconditions.checkArgument(paramsExpression.getType() == ExpressionContext.Type.LITERAL, "Second argument of DISTINCT_COUNT_THETA_SKETCH aggregation function must be literal (parameters)"); - Parameters parameters = new Parameters(paramsExpression.getLiteralString()); + Parameters parameters = new Parameters(paramsExpression.getLiteral().getStringValue()); + // Allows the user to trade-off memory usage for merge CPU; higher values use more memory + _accumulatorThreshold = parameters.getAccumulatorThreshold(); + // Nominal entries controls sketch accuracy and size int nominalEntries = parameters.getNominalEntries(); _updateSketchBuilder.setNominalEntries(nominalEntries); _setOperationBuilder.setNominalEntries(nominalEntries); + // Sampling probability sets the initial value of Theta, defaults to 1.0 + float p = parameters.getSamplingProbability(); + _setOperationBuilder.setP(p); + _updateSketchBuilder.setP(p); } if (numArguments < 4) { @@ -130,8 +140,9 @@ public DistinctCountThetaSketchAggregationFunction(List argum Preconditions.checkArgument(filterExpression.getType() == ExpressionContext.Type.LITERAL, "Third to second last argument of DISTINCT_COUNT_THETA_SKETCH aggregation function must be literal " + "(filter expression)"); - FilterContext filter = - RequestContextUtils.getFilter(CalciteSqlParser.compileToExpression(filterExpression.getLiteralString())); + FilterContext filter = RequestContextUtils.getFilter( + CalciteSqlParser.compileToExpression(filterExpression.getLiteral().getStringValue())); + Preconditions.checkArgument(!filter.isConstant(), "Filter must not be constant: %s", filter); // NOTE: Collect expressions before constructing the FilterInfo so that expressionIndexMap always include the // expressions in the filter. collectExpressions(filter, _inputExpressions, expressionIndexMap); @@ -143,10 +154,9 @@ public DistinctCountThetaSketchAggregationFunction(List argum Preconditions.checkArgument(postAggregationExpression.getType() == ExpressionContext.Type.LITERAL, "Last argument of DISTINCT_COUNT_THETA_SKETCH aggregation function must be literal (post-aggregation " + "expression)"); - Expression expr = CalciteSqlParser.compileToExpression(postAggregationExpression.getLiteralString()); + Expression expr = CalciteSqlParser.compileToExpression(postAggregationExpression.getLiteral().getStringValue()); _postAggregationExpression = RequestContextUtils.getExpression(expr); - // Validate the post-aggregation expression _includeDefaultSketch = validatePostAggregationExpression(_postAggregationExpression, _filterEvaluators.size()); } @@ -401,20 +411,20 @@ public void aggregate(int length, AggregationResultHolder aggregationResultHolde } } else { // Serialized sketch - List unions = getUnions(aggregationResultHolder); + List thetaSketchAccumulators = getUnions(aggregationResultHolder); Sketch[] sketches = deserializeSketches((byte[][]) valueArrays[0], length); if (_includeDefaultSketch) { - Union defaultUnion = unions.get(0); + ThetaSketchAccumulator defaultThetaAccumulator = thetaSketchAccumulators.get(0); for (Sketch sketch : sketches) { - defaultUnion.update(sketch); + defaultThetaAccumulator.apply(sketch); } } for (int i = 0; i < numFilters; i++) { FilterEvaluator filterEvaluator = _filterEvaluators.get(i); - Union union = unions.get(i + 1); + ThetaSketchAccumulator thetaSketchAccumulator = thetaSketchAccumulators.get(i + 1); for (int j = 0; j < length; j++) { if (filterEvaluator.evaluate(singleValues, valueTypes, valueArrays, j)) { - union.update(sketches[j]); + thetaSketchAccumulator.apply(sketches[j]); } } } @@ -631,14 +641,14 @@ public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHol // Serialized sketch Sketch[] sketches = deserializeSketches((byte[][]) valueArrays[0], length); for (int i = 0; i < length; i++) { - List unions = getUnions(groupByResultHolder, groupKeyArray[i]); + List thetaSketchAccumulators = getUnions(groupByResultHolder, groupKeyArray[i]); Sketch sketch = sketches[i]; if (_includeDefaultSketch) { - unions.get(0).update(sketch); + thetaSketchAccumulators.get(0).apply(sketch); } for (int j = 0; j < numFilters; j++) { if (_filterEvaluators.get(j).evaluate(singleValues, valueTypes, valueArrays, i)) { - unions.get(j + 1).update(sketch); + thetaSketchAccumulators.get(j + 1).apply(sketch); } } } @@ -907,7 +917,7 @@ public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResult if (_includeDefaultSketch) { for (int i = 0; i < length; i++) { for (int groupKey : groupKeysArray[i]) { - getUnions(groupByResultHolder, groupKey).get(0).update(sketches[i]); + getUnions(groupByResultHolder, groupKey).get(0).apply(sketches[i]); } } } @@ -916,7 +926,7 @@ public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResult for (int j = 0; j < length; j++) { if (filterEvaluator.evaluate(singleValues, valueTypes, valueArrays, j)) { for (int groupKey : groupKeysArray[i]) { - getUnions(groupByResultHolder, groupKey).get(i + 1).update(sketches[i]); + getUnions(groupByResultHolder, groupKey).get(i + 1).apply(sketches[i]); } } } @@ -925,57 +935,74 @@ public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResult } @Override - public List extractAggregationResult(AggregationResultHolder aggregationResultHolder) { + public List extractAggregationResult(AggregationResultHolder aggregationResultHolder) { List result = aggregationResultHolder.getResult(); if (result == null) { int numSketches = _filterEvaluators.size() + 1; - List sketches = new ArrayList<>(numSketches); + List sketches = new ArrayList<>(numSketches); for (int i = 0; i < numSketches; i++) { - sketches.add(EMPTY_SKETCH); + sketches.add(new ThetaSketchAccumulator(_setOperationBuilder, _accumulatorThreshold)); } return sketches; } if (result.get(0) instanceof Sketch) { - return result; + ArrayList thetaSketchAccumulators = new ArrayList<>(result.size()); + for (Object o : result) { + ThetaSketchAccumulator thetaSketchAccumulator = convertSketchAccumulator(o); + thetaSketchAccumulators.add(thetaSketchAccumulator); + } + return thetaSketchAccumulators; } else { - return convertToSketches(result); + return result; } } @Override - public List extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { + public List extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { List result = groupByResultHolder.getResult(groupKey); + if (result == null) { + int numSketches = _filterEvaluators.size() + 1; + List thetaSketchAccumulators = new ArrayList<>(numSketches); + for (int i = 0; i < numSketches; i++) { + thetaSketchAccumulators.add(new ThetaSketchAccumulator(_setOperationBuilder, _accumulatorThreshold)); + } + return thetaSketchAccumulators; + } + if (result.get(0) instanceof Sketch) { - return result; - } else { - return convertToSketches(result); + ArrayList thetaSketchAccumulators = new ArrayList<>(result.size()); + for (Object o : result) { + ThetaSketchAccumulator thetaSketchAccumulator = convertSketchAccumulator(o); + thetaSketchAccumulators.add(thetaSketchAccumulator); + } + return thetaSketchAccumulators; } + + return result; } @Override - public List merge(List sketches1, List sketches2) { - int numSketches = sketches1.size(); - List mergedSketches = new ArrayList<>(numSketches); - for (int i = 0; i < numSketches; i++) { - Sketch sketch1 = sketches1.get(i); - Sketch sketch2 = sketches2.get(i); - if (sketch1.isEmpty()) { - mergedSketches.add(sketch2); + public List merge(List acc1, List acc2) { + int numAccumulators = acc1.size(); + List mergedAccumulators = new ArrayList<>(numAccumulators); + for (int i = 0; i < numAccumulators; i++) { + ThetaSketchAccumulator thetaSketchAccumulator1 = convertSketchAccumulator(acc1.get(i)); + ThetaSketchAccumulator thetaSketchAccumulator2 = convertSketchAccumulator(acc2.get(i)); + if (thetaSketchAccumulator1.isEmpty()) { + mergedAccumulators.add(thetaSketchAccumulator2); continue; } - if (sketch2.isEmpty()) { - mergedSketches.add(sketch1); + if (thetaSketchAccumulator2.isEmpty()) { + mergedAccumulators.add(thetaSketchAccumulator1); continue; } - Union union = _setOperationBuilder.buildUnion(); - union.update(sketch1); - union.update(sketch2); - // NOTE: Compact the sketch in unsorted, on-heap fashion for performance concern. - // See https://datasketches.apache.org/docs/Theta/ThetaSize.html for more details. - mergedSketches.add(union.getResult(false, null)); + thetaSketchAccumulator1.setSetOperationBuilder(_setOperationBuilder); + thetaSketchAccumulator1.setThreshold(_accumulatorThreshold); + thetaSketchAccumulator1.merge(thetaSketchAccumulator2); + mergedAccumulators.add(thetaSketchAccumulator1); } - return mergedSketches; + return mergedAccumulators; } @Override @@ -989,8 +1016,37 @@ public ColumnDataType getFinalResultColumnType() { } @Override - public Comparable extractFinalResult(List sketches) { - return Math.round(evaluatePostAggregationExpression(_postAggregationExpression, sketches).getEstimate()); + public Comparable extractFinalResult(List accumulators) { + int numAccumulators = accumulators.size(); + List mergedSketches = new ArrayList<>(numAccumulators); + + for (Object accumulatorObject : accumulators) { + ThetaSketchAccumulator accumulator = convertSketchAccumulator(accumulatorObject); + accumulator.setThreshold(_accumulatorThreshold); + accumulator.setSetOperationBuilder(_setOperationBuilder); + mergedSketches.add(accumulator.getResult()); + } + + return Math.round(evaluatePostAggregationExpression(_postAggregationExpression, mergedSketches).getEstimate()); + } + + @Override + public Comparable mergeFinalResult(Comparable finalResult1, Comparable finalResult2) { + return (Long) finalResult1 + (Long) finalResult2; + } + + // This ensures backward compatibility with servers that still return sketches directly. + // The AggregationDataTableReducer casts intermediate results to Objects and although the code compiles, + // types might still be incompatible at runtime due to type erasure. + // Due to performance overheads of redundant casts, this should be removed at some future point. + protected ThetaSketchAccumulator convertSketchAccumulator(Object result) { + if (result instanceof Sketch) { + Sketch sketch = (Sketch) result; + ThetaSketchAccumulator accumulator = new ThetaSketchAccumulator(_setOperationBuilder, _accumulatorThreshold); + accumulator.apply(sketch); + return accumulator; + } + return (ThetaSketchAccumulator) result; } /** @@ -1172,8 +1228,8 @@ private List getUpdateSketches(AggregationResultHolder aggregation /** * Returns the Union list from the result holder or creates a new one if it does not exist. */ - private List getUnions(AggregationResultHolder aggregationResultHolder) { - List unions = aggregationResultHolder.getResult(); + private List getUnions(AggregationResultHolder aggregationResultHolder) { + List unions = aggregationResultHolder.getResult(); if (unions == null) { unions = buildUnions(); aggregationResultHolder.setValue(unions); @@ -1196,8 +1252,8 @@ private List getUpdateSketches(GroupByResultHolder groupByResultHo /** * Returns the Union list for the given group key or creates a new one if it does not exist. */ - private List getUnions(GroupByResultHolder groupByResultHolder, int groupKey) { - List unions = groupByResultHolder.getResult(groupKey); + private List getUnions(GroupByResultHolder groupByResultHolder, int groupKey) { + List unions = groupByResultHolder.getResult(groupKey); if (unions == null) { unions = buildUnions(); groupByResultHolder.setValueForKey(groupKey, unions); @@ -1220,11 +1276,13 @@ private List buildUpdateSketches() { /** * Builds the Union list. */ - private List buildUnions() { + private List buildUnions() { int numUnions = _filterEvaluators.size() + 1; - List unions = new ArrayList<>(numUnions); + List unions = new ArrayList<>(numUnions); for (int i = 0; i < numUnions; i++) { - unions.add(_setOperationBuilder.buildUnion()); + ThetaSketchAccumulator thetaSketchAccumulator = + new ThetaSketchAccumulator(_setOperationBuilder, _accumulatorThreshold); + unions.add(thetaSketchAccumulator); } return unions; } @@ -1240,20 +1298,6 @@ private Sketch[] deserializeSketches(byte[][] serializedSketches, int length) { return sketches; } - /** - * Converts the given Unions to Sketches. - */ - private List convertToSketches(List unions) { - int numUnions = unions.size(); - List sketches = new ArrayList<>(numUnions); - for (Union union : unions) { - // NOTE: Compact the sketch in unsorted, on-heap fashion for performance concern. - // See https://datasketches.apache.org/docs/Theta/ThetaSize.html for more details. - sketches.add(union.getResult(false, null)); - } - return sketches; - } - /** * Evaluates the post-aggregation expression. */ @@ -1269,8 +1313,6 @@ private Sketch evaluatePostAggregationExpression(ExpressionContext expression, L return sketches.get(extractSketchId(expression.getIdentifier())); } - // NOTE: Compact the sketch in unsorted, on-heap fashion for performance concern. - // See https://datasketches.apache.org/docs/Theta/ThetaSize.html for more details. FunctionContext function = expression.getFunction(); String functionName = function.getFunctionName(); List arguments = function.getArguments(); @@ -1278,34 +1320,39 @@ private Sketch evaluatePostAggregationExpression(ExpressionContext expression, L case SET_UNION: Union union = _setOperationBuilder.buildUnion(); for (ExpressionContext argument : arguments) { - union.update(evaluatePostAggregationExpression(argument, sketches)); + union.union(evaluatePostAggregationExpression(argument, sketches)); } return union.getResult(false, null); case SET_INTERSECT: Intersection intersection = _setOperationBuilder.buildIntersection(); for (ExpressionContext argument : arguments) { - intersection.update(evaluatePostAggregationExpression(argument, sketches)); + intersection.intersect(evaluatePostAggregationExpression(argument, sketches)); } return intersection.getResult(false, null); case SET_DIFF: AnotB diff = _setOperationBuilder.buildANotB(); - diff.update(evaluatePostAggregationExpression(arguments.get(0), sketches), - evaluatePostAggregationExpression(arguments.get(1), sketches)); - return diff.getResult(false, null); + diff.setA(evaluatePostAggregationExpression(arguments.get(0), sketches)); + diff.notB(evaluatePostAggregationExpression(arguments.get(1), sketches)); + return diff.getResult(false, null, false); default: throw new IllegalStateException(); } } /** - * Helper class to wrap the theta-sketch parameters. + * Helper class to wrap the theta-sketch parameters. The initial values for the parameters are set to the + * same defaults in the Apache Datasketches library. */ private static class Parameters { private static final char PARAMETER_DELIMITER = ';'; private static final char PARAMETER_KEY_VALUE_SEPARATOR = '='; private static final String NOMINAL_ENTRIES_KEY = "nominalEntries"; + private static final String SAMPLING_PROBABILITY_KEY = "samplingProbability"; + private static final String ACCUMULATOR_THRESHOLD_KEY = "accumulatorThreshold"; - private int _nominalEntries = Util.DEFAULT_NOMINAL_ENTRIES; + private int _nominalEntries = ThetaUtil.DEFAULT_NOMINAL_ENTRIES; + private int _accumulatorThreshold = DEFAULT_ACCUMULATOR_THRESHOLD; + private float _samplingProbability = 1.0F; Parameters(String parametersString) { StringUtils.deleteWhitespace(parametersString); @@ -1317,6 +1364,10 @@ private static class Parameters { String value = keyAndValue[1]; if (key.equalsIgnoreCase(NOMINAL_ENTRIES_KEY)) { _nominalEntries = Integer.parseInt(value); + } else if (key.equalsIgnoreCase(SAMPLING_PROBABILITY_KEY)) { + _samplingProbability = Float.parseFloat(value); + } else if (key.equalsIgnoreCase(ACCUMULATOR_THRESHOLD_KEY)) { + _accumulatorThreshold = Integer.parseInt(value); } else { throw new IllegalArgumentException("Invalid parameter key: " + key); } @@ -1326,6 +1377,14 @@ private static class Parameters { int getNominalEntries() { return _nominalEntries; } + + float getSamplingProbability() { + return _samplingProbability; + } + + int getAccumulatorThreshold() { + return _accumulatorThreshold; + } } /** diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountULLAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountULLAggregationFunction.java new file mode 100644 index 000000000000..9e69cc9b85c3 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctCountULLAggregationFunction.java @@ -0,0 +1,461 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import com.dynatrace.hash4j.distinctcount.UltraLogLog; +import com.google.common.base.Preconditions; +import java.util.List; +import java.util.Map; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.common.ObjectSerDeUtils; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.ObjectAggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.ObjectGroupByResultHolder; +import org.apache.pinot.segment.local.utils.UltraLogLogUtils; +import org.apache.pinot.segment.spi.AggregationFunctionType; +import org.apache.pinot.segment.spi.index.reader.Dictionary; +import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.utils.CommonConstants; +import org.roaringbitmap.PeekableIntIterator; +import org.roaringbitmap.RoaringBitmap; + + +public class DistinctCountULLAggregationFunction extends BaseSingleInputAggregationFunction { + protected final int _p; + + public DistinctCountULLAggregationFunction(List arguments) { + super(arguments.get(0)); + int numExpressions = arguments.size(); + // This function expects 1 or 2 or 3 arguments. + Preconditions.checkArgument(numExpressions <= 2, "DistinctCountHLLPlus expects 1 or 2 arguments, got: %s", + numExpressions); + if (arguments.size() == 2) { + _p = arguments.get(1).getLiteral().getIntValue(); + } else { + _p = CommonConstants.Helix.DEFAULT_ULTRALOGLOG_P; + } + } + + public int getP() { + return _p; + } + + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.DISTINCTCOUNTULL; + } + + @Override + public AggregationResultHolder createAggregationResultHolder() { + return new ObjectAggregationResultHolder(); + } + + @Override + public GroupByResultHolder createGroupByResultHolder(int initialCapacity, int maxCapacity) { + return new ObjectGroupByResultHolder(initialCapacity, maxCapacity); + } + + @Override + public void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + + // Treat BYTES value as serialized HyperLogLogPlus + DataType storedType = blockValSet.getValueType().getStoredType(); + if (storedType == DataType.BYTES) { + byte[][] bytesValues = blockValSet.getBytesValuesSV(); + try { + UltraLogLog ull = aggregationResultHolder.getResult(); + if (ull == null) { + ull = ObjectSerDeUtils.ULTRA_LOG_LOG_OBJECT_SER_DE.deserialize(bytesValues[0]); + aggregationResultHolder.setValue(ull); + } else { + ull.add(ObjectSerDeUtils.ULTRA_LOG_LOG_OBJECT_SER_DE.deserialize(bytesValues[0])); + } + for (int i = 1; i < length; i++) { + ull.add(ObjectSerDeUtils.ULTRA_LOG_LOG_OBJECT_SER_DE.deserialize(bytesValues[i])); + } + } catch (Exception e) { + throw new RuntimeException("Caught exception while merging UltraLogLogs", e); + } + return; + } + + // For dictionary-encoded expression, store dictionary ids into the bitmap + Dictionary dictionary = blockValSet.getDictionary(); + if (dictionary != null) { + int[] dictIds = blockValSet.getDictionaryIdsSV(); + getDictIdBitmap(aggregationResultHolder, dictionary).addN(dictIds, 0, length); + return; + } + + // For non-dictionary-encoded expression, store values into the UltraLogLog + UltraLogLog ull = getULL(aggregationResultHolder); + switch (storedType) { + case INT: + int[] intValues = blockValSet.getIntValuesSV(); + for (int i = 0; i < length; i++) { + UltraLogLogUtils.hashObject(intValues[i]).ifPresent(ull::add); + } + break; + case LONG: + long[] longValues = blockValSet.getLongValuesSV(); + for (int i = 0; i < length; i++) { + UltraLogLogUtils.hashObject(longValues[i]).ifPresent(ull::add); + } + break; + case FLOAT: + float[] floatValues = blockValSet.getFloatValuesSV(); + for (int i = 0; i < length; i++) { + UltraLogLogUtils.hashObject(floatValues[i]).ifPresent(ull::add); + } + break; + case DOUBLE: + double[] doubleValues = blockValSet.getDoubleValuesSV(); + for (int i = 0; i < length; i++) { + UltraLogLogUtils.hashObject(doubleValues[i]).ifPresent(ull::add); + } + break; + case STRING: + String[] stringValues = blockValSet.getStringValuesSV(); + for (int i = 0; i < length; i++) { + UltraLogLogUtils.hashObject(stringValues[i]).ifPresent(ull::add); + } + break; + default: + throw new IllegalStateException( + "Illegal data type for DISTINCT_COUNT_ULL aggregation function: " + storedType); + } + } + + @Override + public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + + // Treat BYTES value as serialized UltraLogLogs + DataType storedType = blockValSet.getValueType().getStoredType(); + if (storedType == DataType.BYTES) { + byte[][] bytesValues = blockValSet.getBytesValuesSV(); + try { + for (int i = 0; i < length; i++) { + UltraLogLog value = ObjectSerDeUtils.ULTRA_LOG_LOG_OBJECT_SER_DE.deserialize(bytesValues[i]); + int groupKey = groupKeyArray[i]; + UltraLogLog ull = groupByResultHolder.getResult(groupKey); + if (ull != null) { + ull.add(value); + } else { + groupByResultHolder.setValueForKey(groupKey, value); + } + } + } catch (Exception e) { + throw new RuntimeException("Caught exception while merging UltraLogLog", e); + } + return; + } + + // For dictionary-encoded expression, store dictionary ids into the bitmap + Dictionary dictionary = blockValSet.getDictionary(); + if (dictionary != null) { + int[] dictIds = blockValSet.getDictionaryIdsSV(); + for (int i = 0; i < length; i++) { + getDictIdBitmap(groupByResultHolder, groupKeyArray[i], dictionary).add(dictIds[i]); + } + return; + } + + // For non-dictionary-encoded expression, store values into the HyperLogLogPlus + switch (storedType) { + case INT: + int[] intValues = blockValSet.getIntValuesSV(); + for (int i = 0; i < length; i++) { + UltraLogLogUtils.hashObject(intValues[i]) + .ifPresent(getULL(groupByResultHolder, groupKeyArray[i])::add); + } + break; + case LONG: + long[] longValues = blockValSet.getLongValuesSV(); + for (int i = 0; i < length; i++) { + UltraLogLogUtils.hashObject(longValues[i]) + .ifPresent(getULL(groupByResultHolder, groupKeyArray[i])::add); + } + break; + case FLOAT: + float[] floatValues = blockValSet.getFloatValuesSV(); + for (int i = 0; i < length; i++) { + UltraLogLogUtils.hashObject(floatValues[i]) + .ifPresent(getULL(groupByResultHolder, groupKeyArray[i])::add); + } + break; + case DOUBLE: + double[] doubleValues = blockValSet.getDoubleValuesSV(); + for (int i = 0; i < length; i++) { + UltraLogLogUtils.hashObject(doubleValues[i]) + .ifPresent(getULL(groupByResultHolder, groupKeyArray[i])::add); + } + break; + case STRING: + String[] stringValues = blockValSet.getStringValuesSV(); + for (int i = 0; i < length; i++) { + UltraLogLogUtils.hashObject(stringValues[i]) + .ifPresent(getULL(groupByResultHolder, groupKeyArray[i])::add); + } + break; + default: + throw new IllegalStateException( + "Illegal data type for DISTINCT_COUNT_ULL aggregation function: " + storedType); + } + } + + @Override + public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + + // Treat BYTES value as serialized HyperLogLogPlus + DataType storedType = blockValSet.getValueType().getStoredType(); + if (storedType == DataType.BYTES) { + byte[][] bytesValues = blockValSet.getBytesValuesSV(); + try { + for (int i = 0; i < length; i++) { + UltraLogLog value = ObjectSerDeUtils.ULTRA_LOG_LOG_OBJECT_SER_DE.deserialize(bytesValues[i]); + for (int groupKey : groupKeysArray[i]) { + UltraLogLog ull = groupByResultHolder.getResult(groupKey); + if (ull != null) { + ull.add(value); + } else { + // Create a new HyperLogLogPlus for the group + groupByResultHolder.setValueForKey(groupKey, + ObjectSerDeUtils.ULTRA_LOG_LOG_OBJECT_SER_DE.deserialize(bytesValues[i])); + } + } + } + } catch (Exception e) { + throw new RuntimeException("Caught exception while merging UltraLogLog", e); + } + return; + } + + // For dictionary-encoded expression, store dictionary ids into the bitmap + Dictionary dictionary = blockValSet.getDictionary(); + if (dictionary != null) { + int[] dictIds = blockValSet.getDictionaryIdsSV(); + for (int i = 0; i < length; i++) { + setDictIdForGroupKeys(groupByResultHolder, groupKeysArray[i], dictionary, dictIds[i]); + } + return; + } + + // For non-dictionary-encoded expression, store values into the UltraLogLog + switch (storedType) { + case INT: + int[] intValues = blockValSet.getIntValuesSV(); + for (int i = 0; i < length; i++) { + setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], intValues[i]); + } + break; + case LONG: + long[] longValues = blockValSet.getLongValuesSV(); + for (int i = 0; i < length; i++) { + setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], longValues[i]); + } + break; + case FLOAT: + float[] floatValues = blockValSet.getFloatValuesSV(); + for (int i = 0; i < length; i++) { + setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], floatValues[i]); + } + break; + case DOUBLE: + double[] doubleValues = blockValSet.getDoubleValuesSV(); + for (int i = 0; i < length; i++) { + setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], doubleValues[i]); + } + break; + case STRING: + String[] stringValues = blockValSet.getStringValuesSV(); + for (int i = 0; i < length; i++) { + setValueForGroupKeys(groupByResultHolder, groupKeysArray[i], stringValues[i]); + } + break; + default: + throw new IllegalStateException( + "Illegal data type for DISTINCT_COUNT_HLL_PLUS aggregation function: " + storedType); + } + } + + @Override + public UltraLogLog extractAggregationResult(AggregationResultHolder aggregationResultHolder) { + Object result = aggregationResultHolder.getResult(); + if (result == null) { + return UltraLogLog.create(_p); + } + + if (result instanceof DictIdsWrapper) { + // For dictionary-encoded expression, convert dictionary ids to ULL + return convertToULL((DictIdsWrapper) result); + } else { + // For non-dictionary-encoded expression, directly return the ULL + return (UltraLogLog) result; + } + } + + @Override + public UltraLogLog extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { + Object result = groupByResultHolder.getResult(groupKey); + if (result == null) { + return UltraLogLog.create(_p); + } + + if (result instanceof DictIdsWrapper) { + // For dictionary-encoded expression, convert dictionary ids to HyperLogLogPlus + return convertToULL((DictIdsWrapper) result); + } else { + // For non-dictionary-encoded expression, directly return the HyperLogLogPlus + return (UltraLogLog) result; + } + } + + @Override + public UltraLogLog merge(UltraLogLog intermediateResult1, UltraLogLog intermediateResult2) { + int largerP = Math.max(intermediateResult1.getP(), intermediateResult2.getP()); + UltraLogLog merged = UltraLogLog.create(largerP); + merged.add(intermediateResult1); + merged.add(intermediateResult2); + return merged; + } + + @Override + public ColumnDataType getIntermediateResultColumnType() { + return ColumnDataType.OBJECT; + } + + @Override + public ColumnDataType getFinalResultColumnType() { + return ColumnDataType.LONG; + } + + @Override + public Comparable extractFinalResult(UltraLogLog intermediateResult) { + return Math.round(intermediateResult.getDistinctCountEstimate()); + } + + @Override + public Comparable mergeFinalResult(Comparable finalResult1, Comparable finalResult2) { + return (Long) finalResult1 + (Long) finalResult2; + } + + /** + * Returns the dictionary id bitmap from the result holder or creates a new one if it does not exist. + */ + protected static RoaringBitmap getDictIdBitmap(AggregationResultHolder aggregationResultHolder, + Dictionary dictionary) { + DictIdsWrapper dictIdsWrapper = aggregationResultHolder.getResult(); + if (dictIdsWrapper == null) { + dictIdsWrapper = new DictIdsWrapper(dictionary); + aggregationResultHolder.setValue(dictIdsWrapper); + } + return dictIdsWrapper._dictIdBitmap; + } + + /** + * Returns the HyperLogLogPlus from the result holder or creates a new one if it does not exist. + */ + protected UltraLogLog getULL(AggregationResultHolder aggregationResultHolder) { + UltraLogLog ull = aggregationResultHolder.getResult(); + if (ull == null) { + ull = UltraLogLog.create(_p); + aggregationResultHolder.setValue(ull); + } + return ull; + } + + /** + * Returns the dictionary id bitmap for the given group key or creates a new one if it does not exist. + */ + protected static RoaringBitmap getDictIdBitmap(GroupByResultHolder groupByResultHolder, int groupKey, + Dictionary dictionary) { + DictIdsWrapper dictIdsWrapper = groupByResultHolder.getResult(groupKey); + if (dictIdsWrapper == null) { + dictIdsWrapper = new DictIdsWrapper(dictionary); + groupByResultHolder.setValueForKey(groupKey, dictIdsWrapper); + } + return dictIdsWrapper._dictIdBitmap; + } + + /** + * Returns the HyperLogLogPlus for the given group key or creates a new one if it does not exist. + */ + protected UltraLogLog getULL(GroupByResultHolder groupByResultHolder, int groupKey) { + UltraLogLog ull = groupByResultHolder.getResult(groupKey); + if (ull == null) { + ull = UltraLogLog.create(_p); + groupByResultHolder.setValueForKey(groupKey, ull); + } + return ull; + } + + /** + * Helper method to set dictionary id for the given group keys into the result holder. + */ + private static void setDictIdForGroupKeys(GroupByResultHolder groupByResultHolder, int[] groupKeys, + Dictionary dictionary, int dictId) { + for (int groupKey : groupKeys) { + getDictIdBitmap(groupByResultHolder, groupKey, dictionary).add(dictId); + } + } + + /** + * Helper method to set value for the given group keys into the result holder. + */ + private void setValueForGroupKeys(GroupByResultHolder groupByResultHolder, int[] groupKeys, Object value) { + for (int groupKey : groupKeys) { + UltraLogLogUtils.hashObject(value) + .ifPresent(getULL(groupByResultHolder, groupKey)::add); + } + } + + /** + * Helper method to read dictionary and convert dictionary ids to HyperLogLogPlus for dictionary-encoded expression. + */ + private UltraLogLog convertToULL(DictIdsWrapper dictIdsWrapper) { + UltraLogLog ull = UltraLogLog.create(_p); + Dictionary dictionary = dictIdsWrapper._dictionary; + RoaringBitmap dictIdBitmap = dictIdsWrapper._dictIdBitmap; + PeekableIntIterator iterator = dictIdBitmap.getIntIterator(); + while (iterator.hasNext()) { + UltraLogLogUtils.hashObject(dictionary.get(iterator.next())) + .ifPresent(ull::add); + } + return ull; + } + + private static final class DictIdsWrapper { + final Dictionary _dictionary; + final RoaringBitmap _dictIdBitmap; + + private DictIdsWrapper(Dictionary dictionary) { + _dictionary = dictionary; + _dictIdBitmap = new RoaringBitmap(); + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctSumAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctSumAggregationFunction.java new file mode 100644 index 000000000000..602017da65f3 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctSumAggregationFunction.java @@ -0,0 +1,79 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.segment.spi.AggregationFunctionType; + + +/** + * Aggregation function to compute the sum of distinct values for an SV column. + */ +public class DistinctSumAggregationFunction extends BaseDistinctAggregateAggregationFunction { + + public DistinctSumAggregationFunction(List arguments, boolean nullHandlingEnabled) { + super(verifySingleArgument(arguments, "DISTINCT_SUM"), AggregationFunctionType.DISTINCTSUM, nullHandlingEnabled); + } + + @Override + public void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + svAggregate(length, aggregationResultHolder, blockValSetMap); + } + + @Override + public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + svAggregateGroupBySV(length, groupKeyArray, groupByResultHolder, blockValSetMap); + } + + @Override + public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + svAggregateGroupByMV(length, groupKeysArray, groupByResultHolder, blockValSetMap); + } + + @Override + public DataSchema.ColumnDataType getFinalResultColumnType() { + return DataSchema.ColumnDataType.DOUBLE; + } + + @Override + public Double extractFinalResult(Set intermediateResult) { + Double distinctSum = 0.0; + + for (Object obj : intermediateResult) { + distinctSum += ((Number) obj).doubleValue(); + } + + return distinctSum; + } + + @Override + public Double mergeFinalResult(Double finalResult1, Double finalResult2) { + return finalResult1 + finalResult2; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctSumMVAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctSumMVAggregationFunction.java new file mode 100644 index 000000000000..044f95db040e --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/DistinctSumMVAggregationFunction.java @@ -0,0 +1,79 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.segment.spi.AggregationFunctionType; + + +/** + * Aggregation function to compute the sum of distinct values for an MV column. + */ +public class DistinctSumMVAggregationFunction extends BaseDistinctAggregateAggregationFunction { + + public DistinctSumMVAggregationFunction(List arguments) { + super(verifySingleArgument(arguments, "DISTINCT_SUM_MV"), AggregationFunctionType.DISTINCTSUMMV, false); + } + + @Override + public void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + mvAggregate(length, aggregationResultHolder, blockValSetMap); + } + + @Override + public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + mvAggregateGroupBySV(length, groupKeyArray, groupByResultHolder, blockValSetMap); + } + + @Override + public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + mvAggregateGroupByMV(length, groupKeysArray, groupByResultHolder, blockValSetMap); + } + + @Override + public DataSchema.ColumnDataType getFinalResultColumnType() { + return DataSchema.ColumnDataType.DOUBLE; + } + + @Override + public Double extractFinalResult(Set intermediateResult) { + Double distinctSum = 0.0; + + for (Object obj : intermediateResult) { + distinctSum += ((Number) obj).doubleValue(); + } + + return distinctSum; + } + + @Override + public Double mergeFinalResult(Double finalResult1, Double finalResult2) { + return finalResult1 + finalResult2; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FastHLLAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FastHLLAggregationFunction.java index 009ee9897aaa..a9f764352caa 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FastHLLAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FastHLLAggregationFunction.java @@ -20,6 +20,7 @@ import com.clearspring.analytics.stream.cardinality.HyperLogLog; import com.google.common.base.Preconditions; +import java.util.List; import java.util.Map; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; @@ -40,8 +41,8 @@ public class FastHLLAggregationFunction extends BaseSingleInputAggregationFuncti public static final int DEFAULT_LOG2M = 8; private static final int BYTE_TO_CHAR_OFFSET = 129; - public FastHLLAggregationFunction(ExpressionContext expression) { - super(expression); + public FastHLLAggregationFunction(List arguments) { + super(verifySingleArgument(arguments, "FAST_HLL")); } @Override @@ -150,8 +151,8 @@ public HyperLogLog merge(HyperLogLog intermediateResult1, HyperLogLog intermedia if (intermediateResult1.cardinality() == 0) { return intermediateResult2; } else { - Preconditions - .checkState(intermediateResult2.cardinality() == 0, "Cannot merge HyperLogLogs of different sizes"); + Preconditions.checkState(intermediateResult2.cardinality() == 0, + "Cannot merge HyperLogLogs of different sizes"); return intermediateResult1; } } @@ -178,6 +179,11 @@ public Long extractFinalResult(HyperLogLog intermediateResult) { return intermediateResult.cardinality(); } + @Override + public Long mergeFinalResult(Long finalResult1, Long finalResult2) { + return finalResult1 + finalResult2; + } + private static HyperLogLog convertStringToHLL(String value) { char[] chars = value.toCharArray(); int length = chars.length; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstDoubleValueWithTimeAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstDoubleValueWithTimeAggregationFunction.java index 56aff397df6e..46d61f225d9f 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstDoubleValueWithTimeAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstDoubleValueWithTimeAggregationFunction.java @@ -22,10 +22,10 @@ import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.common.ObjectSerDeUtils; -import org.apache.pinot.core.query.aggregation.AggregationResultHolder; import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; import org.apache.pinot.segment.local.customobject.DoubleLongPair; import org.apache.pinot.segment.local.customobject.ValueLongPair; +import org.roaringbitmap.IntIterator; /** @@ -38,16 +38,12 @@ * Numeric column * */ -@SuppressWarnings({"rawtypes", "unchecked"}) public class FirstDoubleValueWithTimeAggregationFunction extends FirstWithTimeAggregationFunction { + private final static ValueLongPair DEFAULT_VALUE_TIME_PAIR = new DoubleLongPair(Double.NaN, Long.MAX_VALUE); - private final static ValueLongPair DEFAULT_VALUE_TIME_PAIR - = new DoubleLongPair(Double.NaN, Long.MAX_VALUE); - - public FirstDoubleValueWithTimeAggregationFunction( - ExpressionContext dataCol, - ExpressionContext timeCol) { - super(dataCol, timeCol, ObjectSerDeUtils.DOUBLE_LONG_PAIR_SER_DE); + public FirstDoubleValueWithTimeAggregationFunction(ExpressionContext dataCol, ExpressionContext timeCol, + boolean nullHandlingEnabled) { + super(dataCol, timeCol, ObjectSerDeUtils.DOUBLE_LONG_PAIR_SER_DE, nullHandlingEnabled); } @Override @@ -61,52 +57,42 @@ public ValueLongPair getDefaultValueTimePair() { } @Override - public void aggregateResultWithRawData(int length, AggregationResultHolder aggregationResultHolder, - BlockValSet blockValSet, BlockValSet timeValSet) { - ValueLongPair defaultValueLongPair = getDefaultValueTimePair(); - Double firstData = defaultValueLongPair.getValue(); - long firstTime = defaultValueLongPair.getTime(); - double[] doubleValues = blockValSet.getDoubleValuesSV(); - long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - double data = doubleValues[i]; - long time = timeValues[i]; - if (time <= firstTime) { - firstTime = time; - firstData = data; - } - } - setAggregationResult(aggregationResultHolder, firstData, firstTime); + public Double readCell(BlockValSet block, int docId) { + return block.getDoubleValuesSV()[docId]; } @Override public void aggregateGroupResultWithRawDataSv(int length, int[] groupKeyArray, - GroupByResultHolder groupByResultHolder, - BlockValSet blockValSet, BlockValSet timeValSet) { + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, BlockValSet timeValSet) { double[] doubleValues = blockValSet.getDoubleValuesSV(); long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - double data = doubleValues[i]; - long time = timeValues[i]; - setGroupByResult(groupKeyArray[i], groupByResultHolder, data, time); - } + + IntIterator nullIdxIterator = orNullIterator(blockValSet, timeValSet); + forEachNotNull(length, nullIdxIterator, (from, to) -> { + for (int i = from; i < to; i++) { + double data = doubleValues[i]; + long time = timeValues[i]; + setGroupByResult(groupKeyArray[i], groupByResultHolder, data, time); + } + }); } @Override - public void aggregateGroupResultWithRawDataMv(int length, - int[][] groupKeysArray, - GroupByResultHolder groupByResultHolder, - BlockValSet blockValSet, - BlockValSet timeValSet) { + public void aggregateGroupResultWithRawDataMv(int length, int[][] groupKeysArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, BlockValSet timeValSet) { double[] doubleValues = blockValSet.getDoubleValuesSV(); long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - double value = doubleValues[i]; - long time = timeValues[i]; - for (int groupKey : groupKeysArray[i]) { - setGroupByResult(groupKey, groupByResultHolder, value, time); + + IntIterator nullIdxIterator = orNullIterator(blockValSet, timeValSet); + forEachNotNull(length, nullIdxIterator, (from, to) -> { + for (int i = from; i < to; i++) { + double value = doubleValues[i]; + long time = timeValues[i]; + for (int groupKey : groupKeysArray[i]) { + setGroupByResult(groupKey, groupByResultHolder, value, time); + } } - } + }); } @Override @@ -114,11 +100,6 @@ public String getResultColumnName() { return getType().getName().toLowerCase() + "(" + _expression + "," + _timeCol + ",'DOUBLE')"; } - @Override - public String getColumnName() { - return getType().getName() + "_" + _expression + "_" + _timeCol + "_DOUBLE"; - } - @Override public ColumnDataType getFinalResultColumnType() { return ColumnDataType.DOUBLE; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstFloatValueWithTimeAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstFloatValueWithTimeAggregationFunction.java index 3801ff17a378..d34fe755ecc9 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstFloatValueWithTimeAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstFloatValueWithTimeAggregationFunction.java @@ -22,10 +22,10 @@ import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.common.ObjectSerDeUtils; -import org.apache.pinot.core.query.aggregation.AggregationResultHolder; import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; import org.apache.pinot.segment.local.customobject.FloatLongPair; import org.apache.pinot.segment.local.customobject.ValueLongPair; +import org.roaringbitmap.IntIterator; /** @@ -38,15 +38,12 @@ * Numeric column * */ -@SuppressWarnings({"rawtypes", "unchecked"}) public class FirstFloatValueWithTimeAggregationFunction extends FirstWithTimeAggregationFunction { - private final static ValueLongPair DEFAULT_VALUE_TIME_PAIR = new FloatLongPair(Float.NaN, Long.MAX_VALUE); - public FirstFloatValueWithTimeAggregationFunction( - ExpressionContext dataCol, - ExpressionContext timeCol) { - super(dataCol, timeCol, ObjectSerDeUtils.FLOAT_LONG_PAIR_SER_DE); + public FirstFloatValueWithTimeAggregationFunction(ExpressionContext dataCol, ExpressionContext timeCol, + boolean nullHandlingEnabled) { + super(dataCol, timeCol, ObjectSerDeUtils.FLOAT_LONG_PAIR_SER_DE, nullHandlingEnabled); } @Override @@ -60,54 +57,42 @@ public ValueLongPair getDefaultValueTimePair() { } @Override - public void aggregateResultWithRawData(int length, AggregationResultHolder aggregationResultHolder, - BlockValSet blockValSet, BlockValSet timeValSet) { - ValueLongPair defaultValueLongPair = getDefaultValueTimePair(); - Float firstData = defaultValueLongPair.getValue(); - long firstTime = defaultValueLongPair.getTime(); - float[] floatValues = blockValSet.getFloatValuesSV(); - long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - float data = floatValues[i]; - long time = timeValues[i]; - if (time <= firstTime) { - firstTime = time; - firstData = data; - } - } - setAggregationResult(aggregationResultHolder, firstData, firstTime); + public Float readCell(BlockValSet block, int docId) { + return block.getFloatValuesSV()[docId]; } @Override - public void aggregateGroupResultWithRawDataSv(int length, - int[] groupKeyArray, - GroupByResultHolder groupByResultHolder, - BlockValSet blockValSet, - BlockValSet timeValSet) { + public void aggregateGroupResultWithRawDataSv(int length, int[] groupKeyArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, BlockValSet timeValSet) { float[] floatValues = blockValSet.getFloatValuesSV(); long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - float data = floatValues[i]; - long time = timeValues[i]; - setGroupByResult(groupKeyArray[i], groupByResultHolder, data, time); - } + + IntIterator nullIdxIterator = orNullIterator(blockValSet, timeValSet); + forEachNotNull(length, nullIdxIterator, (from, to) -> { + for (int i = from; i < to; i++) { + float data = floatValues[i]; + long time = timeValues[i]; + setGroupByResult(groupKeyArray[i], groupByResultHolder, data, time); + } + }); } @Override - public void aggregateGroupResultWithRawDataMv(int length, - int[][] groupKeysArray, - GroupByResultHolder groupByResultHolder, - BlockValSet blockValSet, - BlockValSet timeValSet) { + public void aggregateGroupResultWithRawDataMv(int length, int[][] groupKeysArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, BlockValSet timeValSet) { float[] floatValues = blockValSet.getFloatValuesSV(); long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - float value = floatValues[i]; - long time = timeValues[i]; - for (int groupKey : groupKeysArray[i]) { - setGroupByResult(groupKey, groupByResultHolder, value, time); + + IntIterator nullIdxIterator = orNullIterator(blockValSet, timeValSet); + forEachNotNull(length, nullIdxIterator, (from, to) -> { + for (int i = from; i < to; i++) { + float value = floatValues[i]; + long time = timeValues[i]; + for (int groupKey : groupKeysArray[i]) { + setGroupByResult(groupKey, groupByResultHolder, value, time); + } } - } + }); } @Override @@ -115,11 +100,6 @@ public String getResultColumnName() { return getType().getName().toLowerCase() + "(" + _expression + "," + _timeCol + ",'FLOAT')"; } - @Override - public String getColumnName() { - return getType().getName() + "_" + _expression + "_" + _timeCol + "_FLOAT"; - } - @Override public ColumnDataType getFinalResultColumnType() { return ColumnDataType.FLOAT; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstIntValueWithTimeAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstIntValueWithTimeAggregationFunction.java index be151b99942c..97b5829968a7 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstIntValueWithTimeAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstIntValueWithTimeAggregationFunction.java @@ -22,10 +22,10 @@ import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.common.ObjectSerDeUtils; -import org.apache.pinot.core.query.aggregation.AggregationResultHolder; import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; import org.apache.pinot.segment.local.customobject.IntLongPair; import org.apache.pinot.segment.local.customobject.ValueLongPair; +import org.roaringbitmap.IntIterator; /** @@ -39,18 +39,15 @@ * Numeric column * */ -@SuppressWarnings({"rawtypes", "unchecked"}) public class FirstIntValueWithTimeAggregationFunction extends FirstWithTimeAggregationFunction { + private final static ValueLongPair DEFAULT_VALUE_TIME_PAIR = + new IntLongPair(Integer.MIN_VALUE, Long.MAX_VALUE); - private final static ValueLongPair DEFAULT_VALUE_TIME_PAIR - = new IntLongPair(Integer.MIN_VALUE, Long.MAX_VALUE); private final boolean _isBoolean; - public FirstIntValueWithTimeAggregationFunction( - ExpressionContext dataCol, - ExpressionContext timeCol, - boolean isBoolean) { - super(dataCol, timeCol, ObjectSerDeUtils.INT_LONG_PAIR_SER_DE); + public FirstIntValueWithTimeAggregationFunction(ExpressionContext dataCol, ExpressionContext timeCol, + boolean nullHandlingEnabled, boolean isBoolean) { + super(dataCol, timeCol, ObjectSerDeUtils.INT_LONG_PAIR_SER_DE, nullHandlingEnabled); _isBoolean = isBoolean; } @@ -65,52 +62,42 @@ public ValueLongPair getDefaultValueTimePair() { } @Override - public void aggregateResultWithRawData(int length, AggregationResultHolder aggregationResultHolder, - BlockValSet blockValSet, BlockValSet timeValSet) { - ValueLongPair defaultValueLongPair = getDefaultValueTimePair(); - Integer firstData = defaultValueLongPair.getValue(); - long firstTime = defaultValueLongPair.getTime(); - int[] intValues = blockValSet.getIntValuesSV(); - long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - int data = intValues[i]; - long time = timeValues[i]; - if (time <= firstTime) { - firstTime = time; - firstData = data; - } - } - setAggregationResult(aggregationResultHolder, firstData, firstTime); + public Integer readCell(BlockValSet block, int docId) { + return block.getIntValuesSV()[docId]; } @Override public void aggregateGroupResultWithRawDataSv(int length, int[] groupKeyArray, - GroupByResultHolder groupByResultHolder, - BlockValSet blockValSet, BlockValSet timeValSet) { + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, BlockValSet timeValSet) { int[] intValues = blockValSet.getIntValuesSV(); long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - int data = intValues[i]; - long time = timeValues[i]; - setGroupByResult(groupKeyArray[i], groupByResultHolder, data, time); - } + + IntIterator nullIdxIterator = orNullIterator(blockValSet, timeValSet); + forEachNotNull(length, nullIdxIterator, (from, to) -> { + for (int i = from; i < to; i++) { + int data = intValues[i]; + long time = timeValues[i]; + setGroupByResult(groupKeyArray[i], groupByResultHolder, data, time); + } + }); } @Override - public void aggregateGroupResultWithRawDataMv(int length, - int[][] groupKeysArray, - GroupByResultHolder groupByResultHolder, - BlockValSet blockValSet, - BlockValSet timeValSet) { + public void aggregateGroupResultWithRawDataMv(int length, int[][] groupKeysArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, BlockValSet timeValSet) { int[] intValues = blockValSet.getIntValuesSV(); long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - int value = intValues[i]; - long time = timeValues[i]; - for (int groupKey : groupKeysArray[i]) { - setGroupByResult(groupKey, groupByResultHolder, value, time); + + IntIterator nullIdxIterator = orNullIterator(blockValSet, timeValSet); + forEachNotNull(length, nullIdxIterator, (from, to) -> { + for (int i = from; i < to; i++) { + int value = intValues[i]; + long time = timeValues[i]; + for (int groupKey : groupKeysArray[i]) { + setGroupByResult(groupKey, groupByResultHolder, value, time); + } } - } + }); } @Override @@ -122,15 +109,6 @@ public String getResultColumnName() { } } - @Override - public String getColumnName() { - if (_isBoolean) { - return getType().getName() + "_" + _expression + "_" + _timeCol + "_BOOLEAN"; - } else { - return getType().getName() + "_" + _expression + "_" + _timeCol + "_INT"; - } - } - @Override public ColumnDataType getFinalResultColumnType() { if (_isBoolean) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstLongValueWithTimeAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstLongValueWithTimeAggregationFunction.java index 960cda882098..9b1fd34d2a0f 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstLongValueWithTimeAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstLongValueWithTimeAggregationFunction.java @@ -22,10 +22,10 @@ import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.common.ObjectSerDeUtils; -import org.apache.pinot.core.query.aggregation.AggregationResultHolder; import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; import org.apache.pinot.segment.local.customobject.LongLongPair; import org.apache.pinot.segment.local.customobject.ValueLongPair; +import org.roaringbitmap.IntIterator; /** @@ -38,16 +38,12 @@ * Numeric column * */ -@SuppressWarnings({"rawtypes", "unchecked"}) public class FirstLongValueWithTimeAggregationFunction extends FirstWithTimeAggregationFunction { + private final static ValueLongPair DEFAULT_VALUE_TIME_PAIR = new LongLongPair(Long.MIN_VALUE, Long.MAX_VALUE); - private final static ValueLongPair DEFAULT_VALUE_TIME_PAIR - = new LongLongPair(Long.MIN_VALUE, Long.MAX_VALUE); - - public FirstLongValueWithTimeAggregationFunction( - ExpressionContext dataCol, - ExpressionContext timeCol) { - super(dataCol, timeCol, ObjectSerDeUtils.LONG_LONG_PAIR_SER_DE); + public FirstLongValueWithTimeAggregationFunction(ExpressionContext dataCol, ExpressionContext timeCol, + boolean nullHandlingEnabled) { + super(dataCol, timeCol, ObjectSerDeUtils.LONG_LONG_PAIR_SER_DE, nullHandlingEnabled); } @Override @@ -61,52 +57,42 @@ public ValueLongPair getDefaultValueTimePair() { } @Override - public void aggregateResultWithRawData(int length, AggregationResultHolder aggregationResultHolder, - BlockValSet blockValSet, BlockValSet timeValSet) { - ValueLongPair defaultValueLongPair = getDefaultValueTimePair(); - Long firstData = defaultValueLongPair.getValue(); - long firstTime = defaultValueLongPair.getTime(); - long[] longValues = blockValSet.getLongValuesSV(); - long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - long data = longValues[i]; - long time = timeValues[i]; - if (time <= firstTime) { - firstTime = time; - firstData = data; - } - } - setAggregationResult(aggregationResultHolder, firstData, firstTime); + public Long readCell(BlockValSet block, int docId) { + return block.getLongValuesSV()[docId]; } @Override public void aggregateGroupResultWithRawDataSv(int length, int[] groupKeyArray, - GroupByResultHolder groupByResultHolder, - BlockValSet blockValSet, BlockValSet timeValSet) { + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, BlockValSet timeValSet) { long[] longValues = blockValSet.getLongValuesSV(); long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - long data = longValues[i]; - long time = timeValues[i]; - setGroupByResult(groupKeyArray[i], groupByResultHolder, data, time); - } + + IntIterator nullIdxIterator = orNullIterator(blockValSet, timeValSet); + forEachNotNull(length, nullIdxIterator, (from, to) -> { + for (int i = from; i < to; i++) { + long data = longValues[i]; + long time = timeValues[i]; + setGroupByResult(groupKeyArray[i], groupByResultHolder, data, time); + } + }); } @Override - public void aggregateGroupResultWithRawDataMv(int length, - int[][] groupKeysArray, - GroupByResultHolder groupByResultHolder, - BlockValSet blockValSet, - BlockValSet timeValSet) { + public void aggregateGroupResultWithRawDataMv(int length, int[][] groupKeysArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, BlockValSet timeValSet) { long[] longValues = blockValSet.getLongValuesSV(); long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - long value = longValues[i]; - long time = timeValues[i]; - for (int groupKey : groupKeysArray[i]) { - setGroupByResult(groupKey, groupByResultHolder, value, time); + + IntIterator nullIdxIterator = orNullIterator(blockValSet, timeValSet); + forEachNotNull(length, nullIdxIterator, (from, to) -> { + for (int i = from; i < to; i++) { + long value = longValues[i]; + long time = timeValues[i]; + for (int groupKey : groupKeysArray[i]) { + setGroupByResult(groupKey, groupByResultHolder, value, time); + } } - } + }); } @Override @@ -114,11 +100,6 @@ public String getResultColumnName() { return getType().getName().toLowerCase() + "(" + _expression + "," + _timeCol + ",'LONG')"; } - @Override - public String getColumnName() { - return getType().getName() + "_" + _expression + "_" + _timeCol + "_LONG"; - } - @Override public ColumnDataType getFinalResultColumnType() { return ColumnDataType.LONG; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstStringValueWithTimeAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstStringValueWithTimeAggregationFunction.java index b0ace7487e07..4fe34117d244 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstStringValueWithTimeAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstStringValueWithTimeAggregationFunction.java @@ -22,10 +22,10 @@ import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.common.ObjectSerDeUtils; -import org.apache.pinot.core.query.aggregation.AggregationResultHolder; import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; import org.apache.pinot.segment.local.customobject.StringLongPair; import org.apache.pinot.segment.local.customobject.ValueLongPair; +import org.roaringbitmap.IntIterator; /** @@ -38,14 +38,12 @@ * Numeric column * */ -@SuppressWarnings({"rawtypes", "unchecked"}) public class FirstStringValueWithTimeAggregationFunction extends FirstWithTimeAggregationFunction { private final static ValueLongPair DEFAULT_VALUE_TIME_PAIR = new StringLongPair("", Long.MAX_VALUE); - public FirstStringValueWithTimeAggregationFunction( - ExpressionContext dataCol, - ExpressionContext timeCol) { - super(dataCol, timeCol, ObjectSerDeUtils.STRING_LONG_PAIR_SER_DE); + public FirstStringValueWithTimeAggregationFunction(ExpressionContext dataCol, ExpressionContext timeCol, + boolean nullHandlingEnabled) { + super(dataCol, timeCol, ObjectSerDeUtils.STRING_LONG_PAIR_SER_DE, nullHandlingEnabled); } @Override @@ -59,52 +57,42 @@ public ValueLongPair getDefaultValueTimePair() { } @Override - public void aggregateResultWithRawData(int length, AggregationResultHolder aggregationResultHolder, - BlockValSet blockValSet, BlockValSet timeValSet) { - ValueLongPair defaultValueLongPair = getDefaultValueTimePair(); - String firstData = defaultValueLongPair.getValue(); - long firstTime = defaultValueLongPair.getTime(); - String[] stringValues = blockValSet.getStringValuesSV(); - long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - String data = stringValues[i]; - long time = timeValues[i]; - if (time <= firstTime) { - firstTime = time; - firstData = data; - } - } - setAggregationResult(aggregationResultHolder, firstData, firstTime); + public String readCell(BlockValSet block, int docId) { + return block.getStringValuesSV()[docId]; } @Override public void aggregateGroupResultWithRawDataSv(int length, int[] groupKeyArray, - GroupByResultHolder groupByResultHolder, - BlockValSet blockValSet, BlockValSet timeValSet) { + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, BlockValSet timeValSet) { String[] stringValues = blockValSet.getStringValuesSV(); long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - String data = stringValues[i]; - long time = timeValues[i]; - setGroupByResult(groupKeyArray[i], groupByResultHolder, data, time); - } + + IntIterator nullIdxIterator = orNullIterator(blockValSet, timeValSet); + forEachNotNull(length, nullIdxIterator, (from, to) -> { + for (int i = from; i < to; i++) { + String data = stringValues[i]; + long time = timeValues[i]; + setGroupByResult(groupKeyArray[i], groupByResultHolder, data, time); + } + }); } @Override - public void aggregateGroupResultWithRawDataMv(int length, - int[][] groupKeysArray, - GroupByResultHolder groupByResultHolder, - BlockValSet blockValSet, - BlockValSet timeValSet) { + public void aggregateGroupResultWithRawDataMv(int length, int[][] groupKeysArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, BlockValSet timeValSet) { String[] stringValues = blockValSet.getStringValuesSV(); long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - String value = stringValues[i]; - long time = timeValues[i]; - for (int groupKey : groupKeysArray[i]) { - setGroupByResult(groupKey, groupByResultHolder, value, time); + + IntIterator nullIdxIterator = orNullIterator(blockValSet, timeValSet); + forEachNotNull(length, nullIdxIterator, (from, to) -> { + for (int i = from; i < to; i++) { + String value = stringValues[i]; + long time = timeValues[i]; + for (int groupKey : groupKeysArray[i]) { + setGroupByResult(groupKey, groupByResultHolder, value, time); + } } - } + }); } @Override @@ -112,11 +100,6 @@ public String getResultColumnName() { return getType().getName().toLowerCase() + "(" + _expression + "," + _timeCol + ",'STRING')"; } - @Override - public String getColumnName() { - return getType().getName() + "_" + _expression + "_" + _timeCol + "_STRING"; - } - @Override public ColumnDataType getFinalResultColumnType() { return ColumnDataType.STRING; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstWithTimeAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstWithTimeAggregationFunction.java index 7049d0f1db6e..3d04803fde3e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstWithTimeAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FirstWithTimeAggregationFunction.java @@ -29,9 +29,11 @@ import org.apache.pinot.core.query.aggregation.ObjectAggregationResultHolder; import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; import org.apache.pinot.core.query.aggregation.groupby.ObjectGroupByResultHolder; +import org.apache.pinot.segment.local.customobject.IntLongPair; import org.apache.pinot.segment.local.customobject.ValueLongPair; import org.apache.pinot.segment.spi.AggregationFunctionType; import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.roaringbitmap.IntIterator; /** @@ -47,14 +49,13 @@ */ @SuppressWarnings({"rawtypes", "unchecked"}) public abstract class FirstWithTimeAggregationFunction> - extends BaseSingleInputAggregationFunction, V> { + extends NullableSingleInputAggregationFunction, V> { protected final ExpressionContext _timeCol; private final ObjectSerDeUtils.ObjectSerDe> _objectSerDe; - public FirstWithTimeAggregationFunction(ExpressionContext dataCol, - ExpressionContext timeCol, - ObjectSerDeUtils.ObjectSerDe> objectSerDe) { - super(dataCol); + public FirstWithTimeAggregationFunction(ExpressionContext dataCol, ExpressionContext timeCol, + ObjectSerDeUtils.ObjectSerDe> objectSerDe, boolean nullHandlingEnabled) { + super(dataCol, nullHandlingEnabled); _timeCol = timeCol; _objectSerDe = objectSerDe; } @@ -63,8 +64,7 @@ public FirstWithTimeAggregationFunction(ExpressionContext dataCol, public abstract ValueLongPair getDefaultValueTimePair(); - public abstract void aggregateResultWithRawData(int length, AggregationResultHolder aggregationResultHolder, - BlockValSet blockValSet, BlockValSet timeValSet); + public abstract V readCell(BlockValSet block, int docId); public abstract void aggregateGroupResultWithRawDataSv(int length, int[] groupKeyArray, @@ -100,23 +100,45 @@ public void aggregate(int length, AggregationResultHolder aggregationResultHolde BlockValSet blockValSet = blockValSetMap.get(_expression); BlockValSet blockTimeSet = blockValSetMap.get(_timeCol); if (blockValSet.getValueType() != DataType.BYTES) { - aggregateResultWithRawData(length, aggregationResultHolder, blockValSet, blockTimeSet); + IntLongPair defaultPair = new IntLongPair(Integer.MIN_VALUE, Long.MAX_VALUE); + long[] timeValues = blockTimeSet.getLongValuesSV(); + + IntIterator nullIdxIterator = orNullIterator(blockValSet, blockTimeSet); + IntLongPair bestPair = foldNotNull(length, nullIdxIterator, defaultPair, (pair, from, to) -> { + IntLongPair actualPair = pair; + for (int i = from; i < to; i++) { + long time = timeValues[i]; + if (time <= actualPair.getTime()) { + actualPair = new IntLongPair(i, time); + } + } + return actualPair; + }); + V bestValue; + if (bestPair.getValue() < 0) { + bestValue = getDefaultValueTimePair().getValue(); + } else { + bestValue = readCell(blockValSet, bestPair.getValue()); + } + setAggregationResult(aggregationResultHolder, bestValue, bestPair.getTime()); } else { + // We assume bytes contain the binary serialization of FirstPair ValueLongPair defaultValueLongPair = getDefaultValueTimePair(); - V firstData = defaultValueLongPair.getValue(); - long firstTime = defaultValueLongPair.getTime(); - // Serialized FirstPair + + ValueLongPair result = constructValueLongPair(defaultValueLongPair.getValue(), defaultValueLongPair.getTime()); byte[][] bytesValues = blockValSet.getBytesValuesSV(); - for (int i = 0; i < length; i++) { - ValueLongPair firstWithTimePair = _objectSerDe.deserialize(bytesValues[i]); - V data = firstWithTimePair.getValue(); - long time = firstWithTimePair.getTime(); - if (time <= firstTime) { - firstTime = time; - firstData = data; + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + ValueLongPair firstWithTimePair = _objectSerDe.deserialize(bytesValues[i]); + long time = firstWithTimePair.getTime(); + if (time < result.getTime()) { + result.setTime(time); + result.setValue(firstWithTimePair.getValue()); + } } - } - setAggregationResult(aggregationResultHolder, firstData, firstTime); + }); + + setAggregationResult(aggregationResultHolder, result.getValue(), result.getTime()); } } @@ -138,13 +160,15 @@ public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHol } else { // Serialized FirstPair byte[][] bytesValues = blockValSet.getBytesValuesSV(); - for (int i = 0; i < length; i++) { - ValueLongPair firstWithTimePair = _objectSerDe.deserialize(bytesValues[i]); - setGroupByResult(groupKeyArray[i], - groupByResultHolder, - firstWithTimePair.getValue(), - firstWithTimePair.getTime()); - } + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + ValueLongPair firstWithTimePair = _objectSerDe.deserialize(bytesValues[i]); + setGroupByResult(groupKeyArray[i], + groupByResultHolder, + firstWithTimePair.getValue(), + firstWithTimePair.getTime()); + } + }); } } @@ -158,14 +182,16 @@ public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResult } else { // Serialized ValueTimePair byte[][] bytesValues = blockValSet.getBytesValuesSV(); - for (int i = 0; i < length; i++) { - ValueLongPair firstWithTimePair = _objectSerDe.deserialize(bytesValues[i]); - V data = firstWithTimePair.getValue(); - long time = firstWithTimePair.getTime(); - for (int groupKey : groupKeysArray[i]) { - setGroupByResult(groupKey, groupByResultHolder, data, time); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + ValueLongPair firstWithTimePair = _objectSerDe.deserialize(bytesValues[i]); + V data = firstWithTimePair.getValue(); + long time = firstWithTimePair.getTime(); + for (int groupKey : groupKeysArray[i]) { + setGroupByResult(groupKey, groupByResultHolder, data, time); + } } - } + }); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FourthMomentAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FourthMomentAggregationFunction.java index 9cb06e4eebed..81609cb79bd1 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FourthMomentAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FourthMomentAggregationFunction.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.core.query.aggregation.function; +import java.util.List; import java.util.Map; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.utils.DataSchema; @@ -36,11 +37,11 @@ public class FourthMomentAggregationFunction extends BaseSingleInputAggregationF private final Type _type; enum Type { - KURTOSIS, SKEWNESS + KURTOSIS, SKEWNESS, MOMENT } - public FourthMomentAggregationFunction(ExpressionContext expression, Type type) { - super(expression); + public FourthMomentAggregationFunction(List arguments, Type type) { + super(verifySingleArgument(arguments, type.name())); _type = type; } @@ -51,6 +52,8 @@ public AggregationFunctionType getType() { return AggregationFunctionType.KURTOSIS; case SKEWNESS: return AggregationFunctionType.SKEWNESS; + case MOMENT: + return AggregationFunctionType.FOURTHMOMENT; default: throw new IllegalArgumentException("Unexpected type " + _type); } @@ -159,6 +162,10 @@ public Double extractFinalResult(PinotFourthMoment m4) { return m4.kurtosis(); case SKEWNESS: return m4.skew(); + case MOMENT: + // this should never happen, as we're not extracting + // final result when using this method + throw new UnsupportedOperationException("Fourth moment cannot be used as aggregation function directly"); default: throw new IllegalStateException("Unexpected value: " + _type); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FrequentLongsSketchAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FrequentLongsSketchAggregationFunction.java new file mode 100644 index 000000000000..64761096df8c --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FrequentLongsSketchAggregationFunction.java @@ -0,0 +1,268 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.query.aggregation.function; + +import com.google.common.base.Preconditions; +import java.util.List; +import java.util.Map; +import org.apache.datasketches.frequencies.LongsSketch; +import org.apache.datasketches.memory.Memory; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.ObjectAggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.ObjectGroupByResultHolder; +import org.apache.pinot.segment.local.customobject.SerializedFrequentLongsSketch; +import org.apache.pinot.segment.spi.AggregationFunctionType; +import org.apache.pinot.spi.data.FieldSpec; + + +/** + *

+ * {@code FrequentLongsSketchAggregationFunction} provides an approximate FrequentItems aggregation function based on + * Apache DataSketches library. + * It is memory efficient compared to exact counting. + *

+ *

+ * The function takes an INT or LONG column as input and returns a Base64 encoded sketch object which can be + * deserialized and used to estimate the frequency of items in the dataset (how many times they appear). + *

+ *

FREQUENT_STRINGS_SKETCH(col, maxMapSize=256)

+ *

E.g.:

+ *
    + *
  • FREQUENT_LONGS_SKETCH(col)
  • + *
  • FREQUENT_LONGS_SKETCH(col, 1024)
  • + *
+ * + *

+ * If the column type is BYTES, the aggregation function will assume it is a serialized FrequentItems data sketch + * of type `LongsSketch`and will attempt to deserialize it for merging with other sketch objects. + *

+ * + *

+ * Second argument, maxMapsSize, refers to the size of the physical length of the hashmap which stores counts. It + * influences the accuracy of the sketch and should be a power of 2. + *

+ * + *

+ * There is a variation of the function (FREQUENT_STRINGS_SKETCH) which accepts STRING type input columns. + *

+ */ +public class FrequentLongsSketchAggregationFunction + extends BaseSingleInputAggregationFunction> { + protected static final int DEFAULT_MAX_MAP_SIZE = 256; + + protected int _maxMapSize; + + public FrequentLongsSketchAggregationFunction(List arguments) { + super(arguments.get(0)); + int numArguments = arguments.size(); + Preconditions.checkArgument(numArguments == 1 || numArguments == 2, + "Expecting 1 or 2 arguments for FrequentLongsSketch function: FREQUENTITEMSSKETCH(column, maxMapSize"); + _maxMapSize = numArguments == 2 ? arguments.get(1).getLiteral().getIntValue() : DEFAULT_MAX_MAP_SIZE; + } + + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.FREQUENTLONGSSKETCH; + } + + @Override + public AggregationResultHolder createAggregationResultHolder() { + return new ObjectAggregationResultHolder(); + } + + @Override + public GroupByResultHolder createGroupByResultHolder(int initialCapacity, int maxCapacity) { + return new ObjectGroupByResultHolder(initialCapacity, maxCapacity); + } + + @Override + public void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + BlockValSet valueSet = blockValSetMap.get(_expression); + FieldSpec.DataType valueType = valueSet.getValueType(); + + LongsSketch sketch = getOrCreateSketch(aggregationResultHolder); + + switch (valueType) { + case BYTES: + // Assuming the column contains serialized data sketch + LongsSketch[] deserializedSketches = deserializeSketches(blockValSetMap.get(_expression).getBytesValuesSV()); + sketch = getOrCreateSketch(aggregationResultHolder); + + for (LongsSketch colSketch : deserializedSketches) { + sketch.merge(colSketch); + } + break; + case INT: + case LONG: + for (Long val : valueSet.getLongValuesSV()) { + sketch.update(val); + } + break; + default: + throw new UnsupportedOperationException("Cannot aggregate on non int/long types"); + } + } + + @Override + public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet valueSet = blockValSetMap.get(_expression); + FieldSpec.DataType valueType = valueSet.getValueType(); + + switch (valueType) { + case BYTES: + // serialized sketch + LongsSketch[] deserializedSketches = + deserializeSketches(blockValSetMap.get(_expression).getBytesValuesSV()); + for (int i = 0; i < length; i++) { + LongsSketch sketch = getOrCreateSketch(groupByResultHolder, groupKeyArray[i]); + sketch.merge(deserializedSketches[i]); + } + break; + case INT: + case LONG: + long[] values = valueSet.getLongValuesSV(); + for (int i = 0; i < length; i++) { + LongsSketch sketch = getOrCreateSketch(groupByResultHolder, groupKeyArray[i]); + sketch.update(values[i]); + } + break; + default: + throw new UnsupportedOperationException("Cannot aggregate on non int/long types"); + } + } + + @Override + public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet valueSet = blockValSetMap.get(_expression); + FieldSpec.DataType valueType = valueSet.getValueType(); + + switch (valueType) { + case BYTES: + // serialized sketch + LongsSketch[] deserializedSketches = + deserializeSketches(blockValSetMap.get(_expression).getBytesValuesSV()); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + LongsSketch sketch = getOrCreateSketch(groupByResultHolder, groupKey); + sketch.merge(deserializedSketches[i]); + } + } + break; + case INT: + case LONG: + long[] values = valueSet.getLongValuesSV(); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + LongsSketch sketch = getOrCreateSketch(groupByResultHolder, groupKey); + sketch.update(values[i]); + } + } + break; + default: + throw new UnsupportedOperationException("Cannot aggregate on non int/long types"); + } + } + + /** + * Extracts the sketch from the result holder or creates a new one if it does not exist. + */ + protected LongsSketch getOrCreateSketch(AggregationResultHolder aggregationResultHolder) { + LongsSketch sketch = aggregationResultHolder.getResult(); + if (sketch == null) { + sketch = new LongsSketch(_maxMapSize); + aggregationResultHolder.setValue(sketch); + } + return sketch; + } + + /** + * Extracts the sketch from the group by result holder for key + * or creates a new one if it does not exist. + */ + protected LongsSketch getOrCreateSketch(GroupByResultHolder groupByResultHolder, int groupKey) { + LongsSketch sketch = groupByResultHolder.getResult(groupKey); + if (sketch == null) { + sketch = new LongsSketch(_maxMapSize); + groupByResultHolder.setValueForKey(groupKey, sketch); + } + return sketch; + } + + /** + * Deserializes the sketches from the bytes. + */ + protected LongsSketch[] deserializeSketches(byte[][] serializedSketches) { + LongsSketch[] sketches = new LongsSketch[serializedSketches.length]; + for (int i = 0; i < serializedSketches.length; i++) { + sketches[i] = LongsSketch.getInstance(Memory.wrap(serializedSketches[i])); + } + return sketches; + } + + @Override + public LongsSketch extractAggregationResult(AggregationResultHolder aggregationResultHolder) { + return aggregationResultHolder.getResult(); + } + + @Override + public LongsSketch extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { + return groupByResultHolder.getResult(groupKey); + } + + @Override + public LongsSketch merge(LongsSketch sketch1, LongsSketch sketch2) { + LongsSketch union = new LongsSketch(_maxMapSize); + if (sketch1 != null) { + union.merge(sketch1); + } + if (sketch2 != null) { + union.merge(sketch2); + } + return union; + } + + @Override + public DataSchema.ColumnDataType getIntermediateResultColumnType() { + return DataSchema.ColumnDataType.OBJECT; + } + + @Override + public DataSchema.ColumnDataType getFinalResultColumnType() { + return DataSchema.ColumnDataType.STRING; + } + + @Override + public String getResultColumnName() { + return AggregationFunctionType.FREQUENTLONGSSKETCH.getName().toLowerCase() + + "(" + _expression + ")"; + } + + @Override + public Comparable extractFinalResult(LongsSketch sketch) { + return new SerializedFrequentLongsSketch(sketch); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FrequentStringsSketchAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FrequentStringsSketchAggregationFunction.java new file mode 100644 index 000000000000..2424f223cf41 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/FrequentStringsSketchAggregationFunction.java @@ -0,0 +1,252 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.query.aggregation.function; + +import com.google.common.base.Preconditions; +import java.util.List; +import java.util.Map; +import org.apache.datasketches.common.ArrayOfStringsSerDe; +import org.apache.datasketches.frequencies.ItemsSketch; +import org.apache.datasketches.memory.Memory; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.ObjectAggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.ObjectGroupByResultHolder; +import org.apache.pinot.segment.local.customobject.SerializedFrequentStringsSketch; +import org.apache.pinot.segment.spi.AggregationFunctionType; +import org.apache.pinot.spi.data.FieldSpec; + + +/** + *

+ * {@code FrequentStringsSketchAggregationFunction} provides an approximate FrequentItems aggregation function based on + * Apache DataSketches library. + * It is memory efficient compared to exact counting. + *

+ *

+ * The function takes a STRING column as input and returns a Base64 encoded sketch object which can be + * deserialized and used to estimate the frequency of items in the dataset (how many times they appear). + *

+ *

FREQUENT_STRINGS_SKETCH(col, maxMapSize=256)

+ *

E.g.:

+ *
    + *
  • FREQUENT_STRINGS_SKETCH(col)
  • + *
  • FREQUENT_STRINGS_SKETCH(col, 1024)
  • + *
+ * + *

+ * If the column type is BYTES, the aggregation function will assume it is a serialized FrequentItems data sketch + * of type `ItemSketch`and will attempt to deserialize it for merging with other sketch objects. + *

+ * + *

+ * Second argument, maxMapsSize, refers to the size of the physical length of the hashmap which stores counts. It + * influences the accuracy of the sketch and should be a power of 2. + *

+ * + *

+ * There is a variation of the function (FREQUENT_LONGS_SKETCH) which accept INT and LONG type input columns. + *

+ */ +public class FrequentStringsSketchAggregationFunction + extends BaseSingleInputAggregationFunction, Comparable> { + protected static final int DEFAULT_MAX_MAP_SIZE = 256; + + protected int _maxMapSize; + + public FrequentStringsSketchAggregationFunction(List arguments) { + super(arguments.get(0)); + int numArguments = arguments.size(); + Preconditions.checkArgument(numArguments == 1 || numArguments == 2, + "Expecting 1 or 2 arguments for FrequentItemsSketch function: FREQUENTSTRINGSSKETCH(column, maxMapSize"); + _maxMapSize = numArguments == 2 ? arguments.get(1).getLiteral().getIntValue() : DEFAULT_MAX_MAP_SIZE; + } + + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.FREQUENTSTRINGSSKETCH; + } + + @Override + public AggregationResultHolder createAggregationResultHolder() { + return new ObjectAggregationResultHolder(); + } + + @Override + public GroupByResultHolder createGroupByResultHolder(int initialCapacity, int maxCapacity) { + return new ObjectGroupByResultHolder(initialCapacity, maxCapacity); + } + + @Override + public void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + BlockValSet valueSet = blockValSetMap.get(_expression); + FieldSpec.DataType valueType = valueSet.getValueType(); + + ItemsSketch sketch = getOrCreateSketch(aggregationResultHolder); + + if (valueType == FieldSpec.DataType.BYTES) { + // Assuming the column contains serialized data sketch + ItemsSketch[] deserializedSketches = + deserializeSketches(blockValSetMap.get(_expression).getBytesValuesSV()); + sketch = getOrCreateSketch(aggregationResultHolder); + + for (ItemsSketch colSketch : deserializedSketches) { + sketch.merge(colSketch); + } + } else { + for (String val : valueSet.getStringValuesSV()) { + sketch.update(val); + } + } + } + + @Override + public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet valueSet = blockValSetMap.get(_expression); + FieldSpec.DataType valueType = valueSet.getValueType(); + + if (valueType == FieldSpec.DataType.BYTES) { + // serialized sketch + ItemsSketch[] deserializedSketches = + deserializeSketches(blockValSetMap.get(_expression).getBytesValuesSV()); + for (int i = 0; i < length; i++) { + ItemsSketch sketch = getOrCreateSketch(groupByResultHolder, groupKeyArray[i]); + sketch.merge(deserializedSketches[i]); + } + } else { + String[] values = valueSet.getStringValuesSV(); + for (int i = 0; i < length; i++) { + ItemsSketch sketch = getOrCreateSketch(groupByResultHolder, groupKeyArray[i]); + sketch.update(values[i]); + } + } + } + + @Override + public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet valueSet = blockValSetMap.get(_expression); + FieldSpec.DataType valueType = valueSet.getValueType(); + + if (valueType == FieldSpec.DataType.BYTES) { + // serialized sketch + ItemsSketch[] deserializedSketches = + deserializeSketches(blockValSetMap.get(_expression).getBytesValuesSV()); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + ItemsSketch sketch = getOrCreateSketch(groupByResultHolder, groupKey); + sketch.merge(deserializedSketches[i]); + } + } + } else { + String[] values = valueSet.getStringValuesSV(); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + ItemsSketch sketch = getOrCreateSketch(groupByResultHolder, groupKey); + sketch.update(values[i]); + } + } + } + } + + /** + * Extracts the sketch from the result holder or creates a new one if it does not exist. + */ + protected ItemsSketch getOrCreateSketch(AggregationResultHolder aggregationResultHolder) { + ItemsSketch sketch = aggregationResultHolder.getResult(); + if (sketch == null) { + sketch = new ItemsSketch<>(_maxMapSize); + aggregationResultHolder.setValue(sketch); + } + return sketch; + } + + /** + * Extracts the sketch from the group by result holder for key + * or creates a new one if it does not exist. + */ + protected ItemsSketch getOrCreateSketch(GroupByResultHolder groupByResultHolder, int groupKey) { + ItemsSketch sketch = groupByResultHolder.getResult(groupKey); + if (sketch == null) { + sketch = new ItemsSketch<>(_maxMapSize); + groupByResultHolder.setValueForKey(groupKey, sketch); + } + return sketch; + } + + /** + * Deserializes the sketches from the bytes. + */ + protected ItemsSketch[] deserializeSketches(byte[][] serializedSketches) { + ItemsSketch[] sketches = new ItemsSketch[serializedSketches.length]; + for (int i = 0; i < serializedSketches.length; i++) { + sketches[i] = ItemsSketch.getInstance(Memory.wrap(serializedSketches[i]), new ArrayOfStringsSerDe()); + } + return sketches; + } + + @Override + public ItemsSketch extractAggregationResult(AggregationResultHolder aggregationResultHolder) { + return aggregationResultHolder.getResult(); + } + + @Override + public ItemsSketch extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { + return groupByResultHolder.getResult(groupKey); + } + + @Override + public ItemsSketch merge(ItemsSketch sketch1, ItemsSketch sketch2) { + ItemsSketch union = new ItemsSketch<>(_maxMapSize); + if (sketch1 != null) { + union.merge(sketch1); + } + if (sketch2 != null) { + union.merge(sketch2); + } + return union; + } + + @Override + public DataSchema.ColumnDataType getIntermediateResultColumnType() { + return DataSchema.ColumnDataType.OBJECT; + } + + @Override + public DataSchema.ColumnDataType getFinalResultColumnType() { + return DataSchema.ColumnDataType.STRING; + } + + @Override + public String getResultColumnName() { + return AggregationFunctionType.FREQUENTSTRINGSSKETCH.getName().toLowerCase() + + "(" + _expression + ")"; + } + + @Override + public Comparable extractFinalResult(ItemsSketch sketch) { + return new SerializedFrequentStringsSketch(sketch); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/HistogramAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/HistogramAggregationFunction.java index a2911f5ddcc2..f72e42903cad 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/HistogramAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/HistogramAggregationFunction.java @@ -31,6 +31,7 @@ import org.apache.pinot.core.query.aggregation.groupby.ObjectGroupByResultHolder; import org.apache.pinot.core.query.aggregation.utils.DoubleVectorOpUtils; import org.apache.pinot.segment.spi.AggregationFunctionType; +import org.apache.pinot.spi.utils.ArrayCopyUtils; /** @@ -59,17 +60,25 @@ public HistogramAggregationFunction(List arguments) { if (numArguments == 2) { ExpressionContext arrayExpression = arguments.get(1); Preconditions.checkArgument( - (arrayExpression.getType() == ExpressionContext.Type.FUNCTION) && (arrayExpression.getFunction() - .getFunctionName().equals(ARRAY_CONSTRUCTOR)), + // ARRAY function + (arrayExpression.getType() == ExpressionContext.Type.FUNCTION && arrayExpression.getFunction() + .getFunctionName().equals(ARRAY_CONSTRUCTOR)) || ( + arrayExpression.getType() == ExpressionContext.Type.LITERAL && !arrayExpression.getLiteral() + .isSingleValue()), "Please use the format of `Histogram(columnName, ARRAY[1,10,100])` to specify the bin edges"); - _bucketEdges = parseVector(arrayExpression.getFunction().getArguments()); + if (arrayExpression.getType() == ExpressionContext.Type.FUNCTION) { + _bucketEdges = parseVector(arrayExpression.getFunction().getArguments()); + } else { + _bucketEdges = parseVectorLiteral(arrayExpression.getLiteral().getValue()); + } _lower = _bucketEdges[0]; _upper = _bucketEdges[_bucketEdges.length - 1]; } else { _isEqualLength = true; - _lower = Double.parseDouble(arguments.get(1).getLiteralString()); - _upper = Double.parseDouble(arguments.get(2).getLiteralString()); - int numBins = Integer.parseInt(arguments.get(3).getLiteralString()); + _lower = arguments.get(1).getLiteral().getDoubleValue(); + _upper = arguments.get(2).getLiteral().getDoubleValue(); + int numBins = arguments.get(3).getLiteral().getIntValue(); + ; Preconditions.checkArgument(_upper > _lower, "The right most edge must be greater than left most edge, given %s and %s", _lower, _upper); Preconditions.checkArgument(numBins > 0, "The number of bins must be greater than zero, given %s", numBins); @@ -100,17 +109,45 @@ private double[] parseVector(List arrayStr) { if (arrayStr.get(i).getType() == ExpressionContext.Type.IDENTIFIER) { ret[i] = Double.parseDouble(arrayStr.get(i).getIdentifier()); } else { - ret[i] = Double.parseDouble(arrayStr.get(i).getLiteralString()); + ret[i] = arrayStr.get(i).getLiteral().getDoubleValue(); } if (i > 0) { - Preconditions.checkState(ret[i] > ret[i - 1], "The bin edges must be strictly increasing"); + Preconditions.checkArgument(ret[i] > ret[i - 1], "The bin edges must be strictly increasing"); } } return ret; } + private double[] parseVectorLiteral(Object array) { + Preconditions.checkArgument(array != null, "The bin edges must not be null"); + double[] ret; + if (array instanceof int[]) { + int[] intArray = (int[]) array; + ret = new double[intArray.length]; + ArrayCopyUtils.copy(intArray, ret, intArray.length); + } else if (array instanceof long[]) { + long[] longArray = (long[]) array; + ret = new double[longArray.length]; + ArrayCopyUtils.copy(longArray, ret, longArray.length); + } else if (array instanceof float[]) { + float[] floatArray = (float[]) array; + ret = new double[floatArray.length]; + ArrayCopyUtils.copy(floatArray, ret, floatArray.length); + } else if (array instanceof double[]) { + ret = (double[]) array; + } else { + throw new IllegalArgumentException("Unsupported array type: " + array.getClass()); + } + Preconditions.checkArgument(ret.length > 1, "The number of bin edges must be greater than 1"); + for (int i = 1; i < ret.length; i++) { + Preconditions.checkArgument(ret[i] > ret[i - 1], "The bin edges must be strictly increasing"); + } + return ret; + } + /** * Find the bin id for the input value. Use division for equal-length bins, and binary search otherwise. + * * @param val input value * @return bin id */ @@ -135,7 +172,7 @@ private int getBinId(double val) { i = mid; } } - id = i; + id = i; } return id; } @@ -188,12 +225,7 @@ public ColumnDataType getFinalResultColumnType() { @Override public DoubleArrayList extractFinalResult(DoubleArrayList doubleArrayList) { - int count = doubleArrayList.size(); - if (count < 1L) { - throw new IllegalStateException("histogram result shouldn't be empty!"); - } else { - return new DoubleArrayList(doubleArrayList.elements()); - } + return doubleArrayList; } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/IdSetAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/IdSetAggregationFunction.java index b4d5cb18e766..7d4f7dd3dec4 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/IdSetAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/IdSetAggregationFunction.java @@ -82,7 +82,7 @@ public IdSetAggregationFunction(List arguments) { int sizeThresholdInBytes = IdSets.DEFAULT_SIZE_THRESHOLD_IN_BYTES; int expectedInsertions = IdSets.DEFAULT_EXPECTED_INSERTIONS; double fpp = IdSets.DEFAULT_FPP; - String parametersString = parametersExpression.getLiteralString(); + String parametersString = parametersExpression.getLiteral().getStringValue(); StringUtils.deleteWhitespace(parametersString); String[] keyValuePairs = StringUtils.split(parametersString, PARAMETER_DELIMITER); for (String keyValuePair : keyValuePairs) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/IntegerTupleSketchAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/IntegerTupleSketchAggregationFunction.java new file mode 100644 index 000000000000..3941062296f1 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/IntegerTupleSketchAggregationFunction.java @@ -0,0 +1,353 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import com.google.common.base.Preconditions; +import java.util.Base64; +import java.util.List; +import java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.apache.datasketches.memory.Memory; +import org.apache.datasketches.tuple.Sketch; +import org.apache.datasketches.tuple.Sketches; +import org.apache.datasketches.tuple.aninteger.IntegerSummary; +import org.apache.datasketches.tuple.aninteger.IntegerSummaryDeserializer; +import org.apache.datasketches.tuple.aninteger.IntegerSummarySetOperations; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.ObjectAggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.ObjectGroupByResultHolder; +import org.apache.pinot.segment.local.customobject.TupleIntSketchAccumulator; +import org.apache.pinot.segment.spi.AggregationFunctionType; +import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.utils.CommonConstants; + + +/** + * The {@code IntegerTupleSketchAggregationFunction} is the base class for all integer-based Tuple Sketch aggregations. + * Apache Datasketches Tuple Sketches are an extension of the Apache Datasketches Theta Sketch. Tuple sketches store an + * additional summary value with each retained entry which makes the sketch ideal for summarizing attributes + * such as impressions or clicks. + * + * Tuple sketches are interoperable with the Theta Sketch and enable set operations over a stream of data, and can + * also be used for cardinality estimation. + * + * Note: The current implementation of this aggregation function is limited to binary columns that contain sketches + * built outside of Pinot. + * + * Usage examples: + *
    + *
  • + * Simple union (1 or 2 arguments): main expression to aggregate on, followed by an optional Tuple sketch size + * argument. The second argument is the sketch lgK – the given log_base2 of k, and defaults to 16. + * The "raw" equivalents return serialised sketches in base64-encoded strings. + *

    DISTINCT_COUNT_TUPLE_SKETCH(col)

    + *

    DISTINCT_COUNT_TUPLE_SKETCH(col, 12)

    + *

    DISTINCT_COUNT_RAW_INTEGER_SUM_TUPLE_SKETCH(col)

    + *

    DISTINCT_COUNT_RAW_INTEGER_SUM_TUPLE_SKETCH(col, 12)

    + *
  • + * Extracting a cardinality estimate from a CPC sketch: + *

    GET_INT_TUPLE_SKETCH_ESTIMATE(sketch_bytes)

    + *

    GET_INT_TUPLE_SKETCH_ESTIMATE(DISTINCT_COUNT_RAW_TUPLE_SKETCH(col))

    + *
  • + *
  • + * Union between two sketches summaries are merged using addition for hash keys in common: + *

    + * INT_SUM_TUPLE_SKETCH_UNION( + * DISTINCT_COUNT_RAW_INTEGER_SUM_TUPLE_SKETCH(col1), + * DISTINCT_COUNT_RAW_INTEGER_SUM_TUPLE_SKETCH(col2) + * ) + *

    + *
  • + *
  • + * Union between two sketches summaries are merged using maximum for hash keys in common: + *

    + * INT_MAX_TUPLE_SKETCH_UNION( + * DISTINCT_COUNT_RAW_INTEGER_SUM_TUPLE_SKETCH(col1), + * DISTINCT_COUNT_RAW_INTEGER_SUM_TUPLE_SKETCH(col2) + * ) + *

    + *
  • + *
  • + * Union between two sketches summaries are merged using minimum for hash keys in common: + *

    + * INT_MIN_TUPLE_SKETCH_UNION( + * DISTINCT_COUNT_RAW_INTEGER_SUM_TUPLE_SKETCH(col1), + * DISTINCT_COUNT_RAW_INTEGER_SUM_TUPLE_SKETCH(col2) + * ) + *

    + *
  • + *
+ */ +@SuppressWarnings({"rawtypes"}) +public class IntegerTupleSketchAggregationFunction + extends BaseSingleInputAggregationFunction { + private static final int DEFAULT_ACCUMULATOR_THRESHOLD = 2; + final ExpressionContext _expressionContext; + final IntegerSummarySetOperations _setOps; + protected int _accumulatorThreshold = DEFAULT_ACCUMULATOR_THRESHOLD; + protected int _nominalEntries; + + public IntegerTupleSketchAggregationFunction(List arguments, IntegerSummary.Mode mode) { + super(arguments.get(0)); + + Preconditions.checkArgument(arguments.size() <= 2, + "Tuple Sketch Aggregation Function expects at most 2 arguments, got: %s", arguments.size()); + _expressionContext = arguments.get(0); + _setOps = new IntegerSummarySetOperations(mode, mode); + if (arguments.size() == 2) { + ExpressionContext secondArgument = arguments.get(1); + Preconditions.checkArgument(secondArgument.getType() == ExpressionContext.Type.LITERAL, + "Tuple Sketch Aggregation Function expects the second argument to be a literal (parameters)," + " but got: ", + secondArgument.getType()); + + if (secondArgument.getLiteral().getType() == FieldSpec.DataType.STRING) { + Parameters parameters = new Parameters(secondArgument.getLiteral().getStringValue()); + // Allows the user to trade-off memory usage for merge CPU; higher values use more memory + _accumulatorThreshold = parameters.getAccumulatorThreshold(); + // Nominal entries controls sketch accuracy and size + _nominalEntries = parameters.getNominalEntries(); + } else { + _nominalEntries = secondArgument.getLiteral().getIntValue(); + } + } else { + _nominalEntries = (int) Math.pow(2, CommonConstants.Helix.DEFAULT_TUPLE_SKETCH_LGK); + } + } + + // TODO if extra aggregation modes are supported, make this switch + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.DISTINCTCOUNTRAWINTEGERSUMTUPLESKETCH; + } + + @Override + public AggregationResultHolder createAggregationResultHolder() { + return new ObjectAggregationResultHolder(); + } + + @Override + public GroupByResultHolder createGroupByResultHolder(int initialCapacity, int maxCapacity) { + return new ObjectGroupByResultHolder(initialCapacity, maxCapacity); + } + + @Override + public void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + + // Treat BYTES value as serialized Integer Tuple Sketch + FieldSpec.DataType storedType = blockValSet.getValueType().getStoredType(); + if (storedType == FieldSpec.DataType.BYTES) { + byte[][] bytesValues = blockValSet.getBytesValuesSV(); + try { + TupleIntSketchAccumulator tupleIntSketchAccumulator = getAccumulator(aggregationResultHolder); + Sketch[] sketches = deserializeSketches(bytesValues, length); + for (Sketch sketch : sketches) { + tupleIntSketchAccumulator.apply(sketch); + } + } catch (Exception e) { + throw new RuntimeException("Caught exception while aggregating Tuple Sketches", e); + } + } else { + throw new IllegalStateException("Illegal data type for " + getType() + " aggregation function: " + storedType); + } + } + + @Override + public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + + BlockValSet blockValSet = blockValSetMap.get(_expression); + + // Treat BYTES value as serialized Integer Tuple Sketch + FieldSpec.DataType storedType = blockValSet.getValueType().getStoredType(); + + if (storedType == FieldSpec.DataType.BYTES) { + byte[][] bytesValues = blockValSet.getBytesValuesSV(); + try { + Sketch[] sketches = deserializeSketches(bytesValues, length); + for (int i = 0; i < length; i++) { + TupleIntSketchAccumulator tupleIntSketchAccumulator = getAccumulator(groupByResultHolder, groupKeyArray[i]); + Sketch sketch = sketches[i]; + tupleIntSketchAccumulator.apply(sketch); + } + } catch (Exception e) { + throw new RuntimeException("Caught exception while aggregating Tuple Sketches", e); + } + } else { + throw new IllegalStateException( + "Illegal data type for INTEGER_TUPLE_SKETCH_UNION aggregation function: " + storedType); + } + } + + @Override + public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + + // Treat BYTES value as serialized Integer Tuple Sketch + FieldSpec.DataType storedType = blockValSet.getValueType().getStoredType(); + boolean singleValue = blockValSet.isSingleValue(); + + if (singleValue && storedType == FieldSpec.DataType.BYTES) { + byte[][] bytesValues = blockValSetMap.get(_expression).getBytesValuesSV(); + try { + Sketch[] sketches = deserializeSketches(bytesValues, length); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + getAccumulator(groupByResultHolder, groupKey).apply(sketches[i]); + } + } + } catch (Exception e) { + throw new RuntimeException("Caught exception while aggregating Tuple Sketches", e); + } + } else { + throw new IllegalStateException( + "Illegal data type for INTEGER_TUPLE_SKETCH_UNION aggregation function: " + storedType); + } + } + + @Override + public TupleIntSketchAccumulator extractAggregationResult(AggregationResultHolder aggregationResultHolder) { + TupleIntSketchAccumulator result = aggregationResultHolder.getResult(); + if (result == null) { + return new TupleIntSketchAccumulator(_setOps, _nominalEntries, _accumulatorThreshold); + } + return result; + } + + @Override + public TupleIntSketchAccumulator extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { + return groupByResultHolder.getResult(groupKey); + } + + @Override + public TupleIntSketchAccumulator merge(TupleIntSketchAccumulator intermediateResult1, + TupleIntSketchAccumulator intermediateResult2) { + if (intermediateResult1 == null || intermediateResult1.isEmpty()) { + return intermediateResult2; + } + if (intermediateResult2 == null || intermediateResult2.isEmpty()) { + return intermediateResult1; + } + intermediateResult1.setThreshold(_accumulatorThreshold); + intermediateResult1.setNominalEntries(_nominalEntries); + intermediateResult1.setSetOperations(_setOps); + intermediateResult1.merge(intermediateResult2); + return intermediateResult1; + } + + @Override + public ColumnDataType getIntermediateResultColumnType() { + return ColumnDataType.OBJECT; + } + + @Override + public ColumnDataType getFinalResultColumnType() { + return ColumnDataType.STRING; + } + + @Override + public Comparable extractFinalResult(TupleIntSketchAccumulator accumulator) { + accumulator.setNominalEntries(_nominalEntries); + accumulator.setSetOperations(_setOps); + accumulator.setThreshold(_accumulatorThreshold); + return Base64.getEncoder().encodeToString(accumulator.getResult().toByteArray()); + } + + /** + * Returns the accumulator from the result holder or creates a new one if it does not exist. + */ + private TupleIntSketchAccumulator getAccumulator(AggregationResultHolder aggregationResultHolder) { + TupleIntSketchAccumulator accumulator = aggregationResultHolder.getResult(); + if (accumulator == null) { + accumulator = new TupleIntSketchAccumulator(_setOps, _nominalEntries, _accumulatorThreshold); + aggregationResultHolder.setValue(accumulator); + } + return accumulator; + } + + /** + * Returns the accumulator for the given group key or creates a new one if it does not exist. + */ + private TupleIntSketchAccumulator getAccumulator(GroupByResultHolder groupByResultHolder, int groupKey) { + TupleIntSketchAccumulator accumulator = groupByResultHolder.getResult(groupKey); + if (accumulator == null) { + accumulator = new TupleIntSketchAccumulator(_setOps, _nominalEntries, _accumulatorThreshold); + groupByResultHolder.setValueForKey(groupKey, accumulator); + } + return accumulator; + } + + /** + * Deserializes the sketches from the bytes. + */ + @SuppressWarnings({"unchecked"}) + private Sketch[] deserializeSketches(byte[][] serializedSketches, int length) { + Sketch[] sketches = new Sketch[length]; + for (int i = 0; i < length; i++) { + sketches[i] = Sketches.heapifySketch(Memory.wrap(serializedSketches[i]), new IntegerSummaryDeserializer()); + } + return sketches; + } + + /** + * Helper class to wrap the tuple-sketch parameters. The initial values for the parameters are set to the + * same defaults in the Apache Datasketches library. + */ + private static class Parameters { + private static final char PARAMETER_DELIMITER = ';'; + private static final char PARAMETER_KEY_VALUE_SEPARATOR = '='; + private static final String NOMINAL_ENTRIES_KEY = "nominalEntries"; + private static final String ACCUMULATOR_THRESHOLD_KEY = "accumulatorThreshold"; + + private int _nominalEntries = (int) Math.pow(2, CommonConstants.Helix.DEFAULT_TUPLE_SKETCH_LGK); + private int _accumulatorThreshold = DEFAULT_ACCUMULATOR_THRESHOLD; + + Parameters(String parametersString) { + StringUtils.deleteWhitespace(parametersString); + String[] keyValuePairs = StringUtils.split(parametersString, PARAMETER_DELIMITER); + for (String keyValuePair : keyValuePairs) { + String[] keyAndValue = StringUtils.split(keyValuePair, PARAMETER_KEY_VALUE_SEPARATOR); + Preconditions.checkArgument(keyAndValue.length == 2, "Invalid parameter: %s", keyValuePair); + String key = keyAndValue[0]; + String value = keyAndValue[1]; + if (key.equalsIgnoreCase(NOMINAL_ENTRIES_KEY)) { + _nominalEntries = Integer.parseInt(value); + } else if (key.equalsIgnoreCase(ACCUMULATOR_THRESHOLD_KEY)) { + _accumulatorThreshold = Integer.parseInt(value); + } else { + throw new IllegalArgumentException("Invalid parameter key: " + key); + } + } + } + + int getNominalEntries() { + return _nominalEntries; + } + + int getAccumulatorThreshold() { + return _accumulatorThreshold; + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastDoubleValueWithTimeAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastDoubleValueWithTimeAggregationFunction.java index 796a0cb02ad2..398455c799a0 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastDoubleValueWithTimeAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastDoubleValueWithTimeAggregationFunction.java @@ -22,10 +22,10 @@ import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.common.ObjectSerDeUtils; -import org.apache.pinot.core.query.aggregation.AggregationResultHolder; import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; import org.apache.pinot.segment.local.customobject.DoubleLongPair; import org.apache.pinot.segment.local.customobject.ValueLongPair; +import org.roaringbitmap.IntIterator; /** @@ -38,16 +38,12 @@ * Numeric column * */ -@SuppressWarnings({"rawtypes", "unchecked"}) public class LastDoubleValueWithTimeAggregationFunction extends LastWithTimeAggregationFunction { + private final static ValueLongPair DEFAULT_VALUE_TIME_PAIR = new DoubleLongPair(Double.NaN, Long.MIN_VALUE); - private final static ValueLongPair DEFAULT_VALUE_TIME_PAIR - = new DoubleLongPair(Double.NaN, Long.MIN_VALUE); - - public LastDoubleValueWithTimeAggregationFunction( - ExpressionContext dataCol, - ExpressionContext timeCol) { - super(dataCol, timeCol, ObjectSerDeUtils.DOUBLE_LONG_PAIR_SER_DE); + public LastDoubleValueWithTimeAggregationFunction(ExpressionContext dataCol, ExpressionContext timeCol, + boolean nullHandlingEnabled) { + super(dataCol, timeCol, ObjectSerDeUtils.DOUBLE_LONG_PAIR_SER_DE, nullHandlingEnabled); } @Override @@ -61,52 +57,42 @@ public ValueLongPair getDefaultValueTimePair() { } @Override - public void aggregateResultWithRawData(int length, AggregationResultHolder aggregationResultHolder, - BlockValSet blockValSet, BlockValSet timeValSet) { - ValueLongPair defaultValueLongPair = getDefaultValueTimePair(); - Double lastData = defaultValueLongPair.getValue(); - long lastTime = defaultValueLongPair.getTime(); - double[] doubleValues = blockValSet.getDoubleValuesSV(); - long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - double data = doubleValues[i]; - long time = timeValues[i]; - if (time >= lastTime) { - lastTime = time; - lastData = data; - } - } - setAggregationResult(aggregationResultHolder, lastData, lastTime); + public Double readCell(BlockValSet block, int docId) { + return block.getDoubleValuesSV()[docId]; } @Override public void aggregateGroupResultWithRawDataSv(int length, int[] groupKeyArray, - GroupByResultHolder groupByResultHolder, - BlockValSet blockValSet, BlockValSet timeValSet) { + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, BlockValSet timeValSet) { double[] doubleValues = blockValSet.getDoubleValuesSV(); long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - double data = doubleValues[i]; - long time = timeValues[i]; - setGroupByResult(groupKeyArray[i], groupByResultHolder, data, time); - } + + IntIterator nullIdxIterator = orNullIterator(blockValSet, timeValSet); + forEachNotNull(length, nullIdxIterator, (from, to) -> { + for (int i = from; i < to; i++) { + double data = doubleValues[i]; + long time = timeValues[i]; + setGroupByResult(groupKeyArray[i], groupByResultHolder, data, time); + } + }); } @Override - public void aggregateGroupResultWithRawDataMv(int length, - int[][] groupKeysArray, - GroupByResultHolder groupByResultHolder, - BlockValSet blockValSet, - BlockValSet timeValSet) { + public void aggregateGroupResultWithRawDataMv(int length, int[][] groupKeysArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, BlockValSet timeValSet) { double[] doubleValues = blockValSet.getDoubleValuesSV(); long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - double value = doubleValues[i]; - long time = timeValues[i]; - for (int groupKey : groupKeysArray[i]) { - setGroupByResult(groupKey, groupByResultHolder, value, time); + + IntIterator nullIdxIterator = orNullIterator(blockValSet, timeValSet); + forEachNotNull(length, nullIdxIterator, (from, to) -> { + for (int i = from; i < to; i++) { + double value = doubleValues[i]; + long time = timeValues[i]; + for (int groupKey : groupKeysArray[i]) { + setGroupByResult(groupKey, groupByResultHolder, value, time); + } } - } + }); } @Override @@ -114,11 +100,6 @@ public String getResultColumnName() { return getType().getName().toLowerCase() + "(" + _expression + "," + _timeCol + ",'DOUBLE')"; } - @Override - public String getColumnName() { - return getType().getName() + "_" + _expression + "_" + _timeCol + "_DOUBLE"; - } - @Override public ColumnDataType getFinalResultColumnType() { return ColumnDataType.DOUBLE; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastFloatValueWithTimeAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastFloatValueWithTimeAggregationFunction.java index 6061a839bb6e..6d7eaaa1722c 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastFloatValueWithTimeAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastFloatValueWithTimeAggregationFunction.java @@ -22,10 +22,10 @@ import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.common.ObjectSerDeUtils; -import org.apache.pinot.core.query.aggregation.AggregationResultHolder; import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; import org.apache.pinot.segment.local.customobject.FloatLongPair; import org.apache.pinot.segment.local.customobject.ValueLongPair; +import org.roaringbitmap.IntIterator; /** @@ -38,15 +38,12 @@ * Numeric column * */ -@SuppressWarnings({"rawtypes", "unchecked"}) public class LastFloatValueWithTimeAggregationFunction extends LastWithTimeAggregationFunction { - private final static ValueLongPair DEFAULT_VALUE_TIME_PAIR = new FloatLongPair(Float.NaN, Long.MIN_VALUE); - public LastFloatValueWithTimeAggregationFunction( - ExpressionContext dataCol, - ExpressionContext timeCol) { - super(dataCol, timeCol, ObjectSerDeUtils.FLOAT_LONG_PAIR_SER_DE); + public LastFloatValueWithTimeAggregationFunction(ExpressionContext dataCol, ExpressionContext timeCol, + boolean nullHandlingEnabled) { + super(dataCol, timeCol, ObjectSerDeUtils.FLOAT_LONG_PAIR_SER_DE, nullHandlingEnabled); } @Override @@ -60,54 +57,42 @@ public ValueLongPair getDefaultValueTimePair() { } @Override - public void aggregateResultWithRawData(int length, AggregationResultHolder aggregationResultHolder, - BlockValSet blockValSet, BlockValSet timeValSet) { - ValueLongPair defaultValueLongPair = getDefaultValueTimePair(); - Float lastData = defaultValueLongPair.getValue(); - long lastTime = defaultValueLongPair.getTime(); - float[] floatValues = blockValSet.getFloatValuesSV(); - long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - float data = floatValues[i]; - long time = timeValues[i]; - if (time >= lastTime) { - lastTime = time; - lastData = data; - } - } - setAggregationResult(aggregationResultHolder, lastData, lastTime); + public Float readCell(BlockValSet block, int docId) { + return block.getFloatValuesSV()[docId]; } @Override - public void aggregateGroupResultWithRawDataSv(int length, - int[] groupKeyArray, - GroupByResultHolder groupByResultHolder, - BlockValSet blockValSet, - BlockValSet timeValSet) { + public void aggregateGroupResultWithRawDataSv(int length, int[] groupKeyArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, BlockValSet timeValSet) { float[] floatValues = blockValSet.getFloatValuesSV(); long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - float data = floatValues[i]; - long time = timeValues[i]; - setGroupByResult(groupKeyArray[i], groupByResultHolder, data, time); - } + + IntIterator nullIdxIterator = orNullIterator(blockValSet, timeValSet); + forEachNotNull(length, nullIdxIterator, (from, to) -> { + for (int i = from; i < to; i++) { + float data = floatValues[i]; + long time = timeValues[i]; + setGroupByResult(groupKeyArray[i], groupByResultHolder, data, time); + } + }); } @Override - public void aggregateGroupResultWithRawDataMv(int length, - int[][] groupKeysArray, - GroupByResultHolder groupByResultHolder, - BlockValSet blockValSet, - BlockValSet timeValSet) { + public void aggregateGroupResultWithRawDataMv(int length, int[][] groupKeysArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, BlockValSet timeValSet) { float[] floatValues = blockValSet.getFloatValuesSV(); long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - float value = floatValues[i]; - long time = timeValues[i]; - for (int groupKey : groupKeysArray[i]) { - setGroupByResult(groupKey, groupByResultHolder, value, time); + + IntIterator nullIdxIterator = orNullIterator(blockValSet, timeValSet); + forEachNotNull(length, nullIdxIterator, (from, to) -> { + for (int i = from; i < to; i++) { + float value = floatValues[i]; + long time = timeValues[i]; + for (int groupKey : groupKeysArray[i]) { + setGroupByResult(groupKey, groupByResultHolder, value, time); + } } - } + }); } @Override @@ -115,11 +100,6 @@ public String getResultColumnName() { return getType().getName().toLowerCase() + "(" + _expression + "," + _timeCol + ",'FLOAT')"; } - @Override - public String getColumnName() { - return getType().getName() + "_" + _expression + "_" + _timeCol + "_FLOAT"; - } - @Override public ColumnDataType getFinalResultColumnType() { return ColumnDataType.FLOAT; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastIntValueWithTimeAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastIntValueWithTimeAggregationFunction.java index ff9fdee5e2b1..e124f58d0e2c 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastIntValueWithTimeAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastIntValueWithTimeAggregationFunction.java @@ -22,10 +22,10 @@ import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.common.ObjectSerDeUtils; -import org.apache.pinot.core.query.aggregation.AggregationResultHolder; import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; import org.apache.pinot.segment.local.customobject.IntLongPair; import org.apache.pinot.segment.local.customobject.ValueLongPair; +import org.roaringbitmap.IntIterator; /** @@ -39,18 +39,15 @@ * Numeric column * */ -@SuppressWarnings({"rawtypes", "unchecked"}) public class LastIntValueWithTimeAggregationFunction extends LastWithTimeAggregationFunction { + private final static ValueLongPair DEFAULT_VALUE_TIME_PAIR = + new IntLongPair(Integer.MIN_VALUE, Long.MIN_VALUE); - private final static ValueLongPair DEFAULT_VALUE_TIME_PAIR - = new IntLongPair(Integer.MIN_VALUE, Long.MIN_VALUE); private final boolean _isBoolean; - public LastIntValueWithTimeAggregationFunction( - ExpressionContext dataCol, - ExpressionContext timeCol, - boolean isBoolean) { - super(dataCol, timeCol, ObjectSerDeUtils.INT_LONG_PAIR_SER_DE); + public LastIntValueWithTimeAggregationFunction(ExpressionContext dataCol, ExpressionContext timeCol, + boolean nullHandlingEnabled, boolean isBoolean) { + super(dataCol, timeCol, ObjectSerDeUtils.INT_LONG_PAIR_SER_DE, nullHandlingEnabled); _isBoolean = isBoolean; } @@ -65,52 +62,42 @@ public ValueLongPair getDefaultValueTimePair() { } @Override - public void aggregateResultWithRawData(int length, AggregationResultHolder aggregationResultHolder, - BlockValSet blockValSet, BlockValSet timeValSet) { - ValueLongPair defaultValueLongPair = getDefaultValueTimePair(); - Integer lastData = defaultValueLongPair.getValue(); - long lastTime = defaultValueLongPair.getTime(); - int[] intValues = blockValSet.getIntValuesSV(); - long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - int data = intValues[i]; - long time = timeValues[i]; - if (time >= lastTime) { - lastTime = time; - lastData = data; - } - } - setAggregationResult(aggregationResultHolder, lastData, lastTime); + public Integer readCell(BlockValSet block, int docId) { + return block.getIntValuesSV()[docId]; } @Override public void aggregateGroupResultWithRawDataSv(int length, int[] groupKeyArray, - GroupByResultHolder groupByResultHolder, - BlockValSet blockValSet, BlockValSet timeValSet) { + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, BlockValSet timeValSet) { int[] intValues = blockValSet.getIntValuesSV(); long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - int data = intValues[i]; - long time = timeValues[i]; - setGroupByResult(groupKeyArray[i], groupByResultHolder, data, time); - } + + IntIterator nullIdxIterator = orNullIterator(blockValSet, timeValSet); + forEachNotNull(length, nullIdxIterator, (from, to) -> { + for (int i = from; i < to; i++) { + int data = intValues[i]; + long time = timeValues[i]; + setGroupByResult(groupKeyArray[i], groupByResultHolder, data, time); + } + }); } @Override - public void aggregateGroupResultWithRawDataMv(int length, - int[][] groupKeysArray, - GroupByResultHolder groupByResultHolder, - BlockValSet blockValSet, - BlockValSet timeValSet) { + public void aggregateGroupResultWithRawDataMv(int length, int[][] groupKeysArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, BlockValSet timeValSet) { int[] intValues = blockValSet.getIntValuesSV(); long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - int value = intValues[i]; - long time = timeValues[i]; - for (int groupKey : groupKeysArray[i]) { - setGroupByResult(groupKey, groupByResultHolder, value, time); + + IntIterator nullIdxIterator = orNullIterator(blockValSet, timeValSet); + forEachNotNull(length, nullIdxIterator, (from, to) -> { + for (int i = from; i < to; i++) { + int value = intValues[i]; + long time = timeValues[i]; + for (int groupKey : groupKeysArray[i]) { + setGroupByResult(groupKey, groupByResultHolder, value, time); + } } - } + }); } @Override @@ -122,15 +109,6 @@ public String getResultColumnName() { } } - @Override - public String getColumnName() { - if (_isBoolean) { - return getType().getName() + "_" + _expression + "_" + _timeCol + "_BOOLEAN"; - } else { - return getType().getName() + "_" + _expression + "_" + _timeCol + "_INT"; - } - } - @Override public ColumnDataType getFinalResultColumnType() { if (_isBoolean) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastLongValueWithTimeAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastLongValueWithTimeAggregationFunction.java index fa3c15fb3a79..6c0e08e761a9 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastLongValueWithTimeAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastLongValueWithTimeAggregationFunction.java @@ -22,10 +22,10 @@ import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.common.ObjectSerDeUtils; -import org.apache.pinot.core.query.aggregation.AggregationResultHolder; import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; import org.apache.pinot.segment.local.customobject.LongLongPair; import org.apache.pinot.segment.local.customobject.ValueLongPair; +import org.roaringbitmap.IntIterator; /** @@ -38,16 +38,12 @@ * Numeric column * */ -@SuppressWarnings({"rawtypes", "unchecked"}) public class LastLongValueWithTimeAggregationFunction extends LastWithTimeAggregationFunction { + private final static ValueLongPair DEFAULT_VALUE_TIME_PAIR = new LongLongPair(Long.MIN_VALUE, Long.MIN_VALUE); - private final static ValueLongPair DEFAULT_VALUE_TIME_PAIR - = new LongLongPair(Long.MIN_VALUE, Long.MIN_VALUE); - - public LastLongValueWithTimeAggregationFunction( - ExpressionContext dataCol, - ExpressionContext timeCol) { - super(dataCol, timeCol, ObjectSerDeUtils.LONG_LONG_PAIR_SER_DE); + public LastLongValueWithTimeAggregationFunction(ExpressionContext dataCol, ExpressionContext timeCol, + boolean nullHandlingEnabled) { + super(dataCol, timeCol, ObjectSerDeUtils.LONG_LONG_PAIR_SER_DE, nullHandlingEnabled); } @Override @@ -61,52 +57,42 @@ public ValueLongPair getDefaultValueTimePair() { } @Override - public void aggregateResultWithRawData(int length, AggregationResultHolder aggregationResultHolder, - BlockValSet blockValSet, BlockValSet timeValSet) { - ValueLongPair defaultValueLongPair = getDefaultValueTimePair(); - Long lastData = defaultValueLongPair.getValue(); - long lastTime = defaultValueLongPair.getTime(); - long[] longValues = blockValSet.getLongValuesSV(); - long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - long data = longValues[i]; - long time = timeValues[i]; - if (time >= lastTime) { - lastTime = time; - lastData = data; - } - } - setAggregationResult(aggregationResultHolder, lastData, lastTime); + public Long readCell(BlockValSet block, int docId) { + return block.getLongValuesSV()[docId]; } @Override public void aggregateGroupResultWithRawDataSv(int length, int[] groupKeyArray, - GroupByResultHolder groupByResultHolder, - BlockValSet blockValSet, BlockValSet timeValSet) { + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, BlockValSet timeValSet) { long[] longValues = blockValSet.getLongValuesSV(); long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - long data = longValues[i]; - long time = timeValues[i]; - setGroupByResult(groupKeyArray[i], groupByResultHolder, data, time); - } + + IntIterator nullIdxIterator = orNullIterator(blockValSet, timeValSet); + forEachNotNull(length, nullIdxIterator, (from, to) -> { + for (int i = from; i < to; i++) { + long data = longValues[i]; + long time = timeValues[i]; + setGroupByResult(groupKeyArray[i], groupByResultHolder, data, time); + } + }); } @Override - public void aggregateGroupResultWithRawDataMv(int length, - int[][] groupKeysArray, - GroupByResultHolder groupByResultHolder, - BlockValSet blockValSet, - BlockValSet timeValSet) { + public void aggregateGroupResultWithRawDataMv(int length, int[][] groupKeysArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, BlockValSet timeValSet) { long[] longValues = blockValSet.getLongValuesSV(); long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - long value = longValues[i]; - long time = timeValues[i]; - for (int groupKey : groupKeysArray[i]) { - setGroupByResult(groupKey, groupByResultHolder, value, time); + + IntIterator nullIdxIterator = orNullIterator(blockValSet, timeValSet); + forEachNotNull(length, nullIdxIterator, (from, to) -> { + for (int i = from; i < to; i++) { + long value = longValues[i]; + long time = timeValues[i]; + for (int groupKey : groupKeysArray[i]) { + setGroupByResult(groupKey, groupByResultHolder, value, time); + } } - } + }); } @Override @@ -114,11 +100,6 @@ public String getResultColumnName() { return getType().getName().toLowerCase() + "(" + _expression + "," + _timeCol + ",'LONG')"; } - @Override - public String getColumnName() { - return getType().getName() + "_" + _expression + "_" + _timeCol + "_LONG"; - } - @Override public ColumnDataType getFinalResultColumnType() { return ColumnDataType.LONG; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastStringValueWithTimeAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastStringValueWithTimeAggregationFunction.java index cb3caa886bd4..2b200b157a18 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastStringValueWithTimeAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastStringValueWithTimeAggregationFunction.java @@ -22,10 +22,10 @@ import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.common.ObjectSerDeUtils; -import org.apache.pinot.core.query.aggregation.AggregationResultHolder; import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; import org.apache.pinot.segment.local.customobject.StringLongPair; import org.apache.pinot.segment.local.customobject.ValueLongPair; +import org.roaringbitmap.IntIterator; /** @@ -38,14 +38,12 @@ * Numeric column * */ -@SuppressWarnings({"rawtypes", "unchecked"}) public class LastStringValueWithTimeAggregationFunction extends LastWithTimeAggregationFunction { private final static ValueLongPair DEFAULT_VALUE_TIME_PAIR = new StringLongPair("", Long.MIN_VALUE); - public LastStringValueWithTimeAggregationFunction( - ExpressionContext dataCol, - ExpressionContext timeCol) { - super(dataCol, timeCol, ObjectSerDeUtils.STRING_LONG_PAIR_SER_DE); + public LastStringValueWithTimeAggregationFunction(ExpressionContext dataCol, ExpressionContext timeCol, + boolean nullHandlingEnabled) { + super(dataCol, timeCol, ObjectSerDeUtils.STRING_LONG_PAIR_SER_DE, nullHandlingEnabled); } @Override @@ -59,52 +57,42 @@ public ValueLongPair getDefaultValueTimePair() { } @Override - public void aggregateResultWithRawData(int length, AggregationResultHolder aggregationResultHolder, - BlockValSet blockValSet, BlockValSet timeValSet) { - ValueLongPair defaultValueLongPair = getDefaultValueTimePair(); - String lastData = defaultValueLongPair.getValue(); - long lastTime = defaultValueLongPair.getTime(); - String[] stringValues = blockValSet.getStringValuesSV(); - long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - String data = stringValues[i]; - long time = timeValues[i]; - if (time >= lastTime) { - lastTime = time; - lastData = data; - } - } - setAggregationResult(aggregationResultHolder, lastData, lastTime); + public String readCell(BlockValSet block, int docId) { + return block.getStringValuesSV()[docId]; } @Override public void aggregateGroupResultWithRawDataSv(int length, int[] groupKeyArray, - GroupByResultHolder groupByResultHolder, - BlockValSet blockValSet, BlockValSet timeValSet) { + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, BlockValSet timeValSet) { String[] stringValues = blockValSet.getStringValuesSV(); long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - String data = stringValues[i]; - long time = timeValues[i]; - setGroupByResult(groupKeyArray[i], groupByResultHolder, data, time); - } + + IntIterator nullIdxIterator = orNullIterator(blockValSet, timeValSet); + forEachNotNull(length, nullIdxIterator, (from, to) -> { + for (int i = from; i < to; i++) { + String data = stringValues[i]; + long time = timeValues[i]; + setGroupByResult(groupKeyArray[i], groupByResultHolder, data, time); + } + }); } @Override - public void aggregateGroupResultWithRawDataMv(int length, - int[][] groupKeysArray, - GroupByResultHolder groupByResultHolder, - BlockValSet blockValSet, - BlockValSet timeValSet) { + public void aggregateGroupResultWithRawDataMv(int length, int[][] groupKeysArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, BlockValSet timeValSet) { String[] stringValues = blockValSet.getStringValuesSV(); long[] timeValues = timeValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - String value = stringValues[i]; - long time = timeValues[i]; - for (int groupKey : groupKeysArray[i]) { - setGroupByResult(groupKey, groupByResultHolder, value, time); + + IntIterator nullIdxIterator = orNullIterator(blockValSet, timeValSet); + forEachNotNull(length, nullIdxIterator, (from, to) -> { + for (int i = from; i < to; i++) { + String value = stringValues[i]; + long time = timeValues[i]; + for (int groupKey : groupKeysArray[i]) { + setGroupByResult(groupKey, groupByResultHolder, value, time); + } } - } + }); } @Override @@ -112,11 +100,6 @@ public String getResultColumnName() { return getType().getName().toLowerCase() + "(" + _expression + "," + _timeCol + ",'STRING')"; } - @Override - public String getColumnName() { - return getType().getName() + "_" + _expression + "_" + _timeCol + "_STRING"; - } - @Override public ColumnDataType getFinalResultColumnType() { return ColumnDataType.STRING; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastWithTimeAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastWithTimeAggregationFunction.java index cd4b0576a6d6..565445ae6f53 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastWithTimeAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/LastWithTimeAggregationFunction.java @@ -29,9 +29,11 @@ import org.apache.pinot.core.query.aggregation.ObjectAggregationResultHolder; import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; import org.apache.pinot.core.query.aggregation.groupby.ObjectGroupByResultHolder; +import org.apache.pinot.segment.local.customobject.IntLongPair; import org.apache.pinot.segment.local.customobject.ValueLongPair; import org.apache.pinot.segment.spi.AggregationFunctionType; import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.roaringbitmap.IntIterator; /** @@ -47,14 +49,14 @@ */ @SuppressWarnings({"rawtypes", "unchecked"}) public abstract class LastWithTimeAggregationFunction> - extends BaseSingleInputAggregationFunction, V> { + extends NullableSingleInputAggregationFunction, V> { protected final ExpressionContext _timeCol; private final ObjectSerDeUtils.ObjectSerDe> _objectSerDe; public LastWithTimeAggregationFunction(ExpressionContext dataCol, ExpressionContext timeCol, - ObjectSerDeUtils.ObjectSerDe> objectSerDe) { - super(dataCol); + ObjectSerDeUtils.ObjectSerDe> objectSerDe, boolean nullHandlingEnabled) { + super(dataCol, nullHandlingEnabled); _timeCol = timeCol; _objectSerDe = objectSerDe; } @@ -63,8 +65,7 @@ public LastWithTimeAggregationFunction(ExpressionContext dataCol, public abstract ValueLongPair getDefaultValueTimePair(); - public abstract void aggregateResultWithRawData(int length, AggregationResultHolder aggregationResultHolder, - BlockValSet blockValSet, BlockValSet timeValSet); + public abstract V readCell(BlockValSet block, int docId); public abstract void aggregateGroupResultWithRawDataSv(int length, int[] groupKeyArray, @@ -100,8 +101,29 @@ public void aggregate(int length, AggregationResultHolder aggregationResultHolde BlockValSet blockValSet = blockValSetMap.get(_expression); BlockValSet blockTimeSet = blockValSetMap.get(_timeCol); if (blockValSet.getValueType() != DataType.BYTES) { - aggregateResultWithRawData(length, aggregationResultHolder, blockValSet, blockTimeSet); + IntLongPair defaultPair = new IntLongPair(Integer.MIN_VALUE, Long.MIN_VALUE); + long[] timeValues = blockTimeSet.getLongValuesSV(); + + IntIterator nullIdxIterator = orNullIterator(blockValSet, blockTimeSet); + IntLongPair bestPair = foldNotNull(length, nullIdxIterator, defaultPair, (pair, from, to) -> { + IntLongPair actualPair = pair; + for (int i = from; i < to; i++) { + long time = timeValues[i]; + if (time >= actualPair.getTime()) { + actualPair = new IntLongPair(i, time); + } + } + return actualPair; + }); + V bestValue; + if (bestPair.getValue() < 0) { + bestValue = getDefaultValueTimePair().getValue(); + } else { + bestValue = readCell(blockValSet, bestPair.getValue()); + } + setAggregationResult(aggregationResultHolder, bestValue, bestPair.getTime()); } else { + // We assume bytes contain the binary serialization of FirstPair ValueLongPair defaultValueLongPair = getDefaultValueTimePair(); V lastData = defaultValueLongPair.getValue(); long lastTime = defaultValueLongPair.getTime(); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MaxAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MaxAggregationFunction.java index 79c08bd2bec3..c2d37d35d9a1 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MaxAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MaxAggregationFunction.java @@ -19,6 +19,7 @@ package org.apache.pinot.core.query.aggregation.function; import java.math.BigDecimal; +import java.util.List; import java.util.Map; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; @@ -37,11 +38,11 @@ public class MaxAggregationFunction extends BaseSingleInputAggregationFunction arguments, boolean nullHandlingEnabled) { + this(verifySingleArgument(arguments, "MAX"), nullHandlingEnabled); } - public MaxAggregationFunction(ExpressionContext expression, boolean nullHandlingEnabled) { + protected MaxAggregationFunction(ExpressionContext expression, boolean nullHandlingEnabled) { super(expression); _nullHandlingEnabled = nullHandlingEnabled; } @@ -309,4 +310,9 @@ public ColumnDataType getFinalResultColumnType() { public Double extractFinalResult(Double intermediateResult) { return intermediateResult; } + + @Override + public Double mergeFinalResult(Double finalResult1, Double finalResult2) { + return merge(finalResult1, finalResult2); + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MaxMVAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MaxMVAggregationFunction.java index 81f99775b530..436077c375e7 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MaxMVAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MaxMVAggregationFunction.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.core.query.aggregation.function; +import java.util.List; import java.util.Map; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.core.common.BlockValSet; @@ -28,8 +29,8 @@ public class MaxMVAggregationFunction extends MaxAggregationFunction { - public MaxMVAggregationFunction(ExpressionContext expression) { - super(expression); + public MaxMVAggregationFunction(List arguments) { + super(verifySingleArgument(arguments, "MAX_MV"), false); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MinAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MinAggregationFunction.java index b2ed9390d330..a74b7a53ee06 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MinAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MinAggregationFunction.java @@ -19,6 +19,7 @@ package org.apache.pinot.core.query.aggregation.function; import java.math.BigDecimal; +import java.util.List; import java.util.Map; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; @@ -37,11 +38,11 @@ public class MinAggregationFunction extends BaseSingleInputAggregationFunction arguments, boolean nullHandlingEnabled) { + this(verifySingleArgument(arguments, "MIN"), nullHandlingEnabled); } - public MinAggregationFunction(ExpressionContext expression, boolean nullHandlingEnabled) { + protected MinAggregationFunction(ExpressionContext expression, boolean nullHandlingEnabled) { super(expression); _nullHandlingEnabled = nullHandlingEnabled; } @@ -308,4 +309,9 @@ public ColumnDataType getFinalResultColumnType() { public Double extractFinalResult(Double intermediateResult) { return intermediateResult; } + + @Override + public Double mergeFinalResult(Double finalResult1, Double finalResult2) { + return merge(finalResult1, finalResult2); + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MinMVAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MinMVAggregationFunction.java index f6a4edcfa992..ec231407d45d 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MinMVAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MinMVAggregationFunction.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.core.query.aggregation.function; +import java.util.List; import java.util.Map; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.core.common.BlockValSet; @@ -28,8 +29,8 @@ public class MinMVAggregationFunction extends MinAggregationFunction { - public MinMVAggregationFunction(ExpressionContext expression) { - super(expression); + public MinMVAggregationFunction(List arguments) { + super(verifySingleArgument(arguments, "MIN_MV"), false); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MinMaxRangeAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MinMaxRangeAggregationFunction.java index f0bcf93ff347..e6b5b0ad8413 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MinMaxRangeAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MinMaxRangeAggregationFunction.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.core.query.aggregation.function; +import java.util.List; import java.util.Map; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; @@ -32,10 +33,14 @@ import org.apache.pinot.spi.data.FieldSpec.DataType; -public class MinMaxRangeAggregationFunction extends BaseSingleInputAggregationFunction { +public class MinMaxRangeAggregationFunction extends NullableSingleInputAggregationFunction { - public MinMaxRangeAggregationFunction(ExpressionContext expression) { - super(expression); + public MinMaxRangeAggregationFunction(List arguments, boolean nullHandlingEnabled) { + super(verifySingleArgument(arguments, "MIN_MAX_RANGE"), nullHandlingEnabled); + } + + protected MinMaxRangeAggregationFunction(ExpressionContext expression, boolean nullHandlingEnabled) { + super(expression, nullHandlingEnabled); } @Override @@ -56,37 +61,29 @@ public GroupByResultHolder createGroupByResultHolder(int initialCapacity, int ma @Override public void aggregate(int length, AggregationResultHolder aggregationResultHolder, Map blockValSetMap) { - double min = Double.POSITIVE_INFINITY; - double max = Double.NEGATIVE_INFINITY; BlockValSet blockValSet = blockValSetMap.get(_expression); + MinMaxRangePair minMax = new MinMaxRangePair(); + if (blockValSet.getValueType() != DataType.BYTES) { double[] doubleValues = blockValSet.getDoubleValuesSV(); - for (int i = 0; i < length; i++) { - double value = doubleValues[i]; - if (value < min) { - min = value; + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + double value = doubleValues[i]; + minMax.apply(value); } - if (value > max) { - max = value; - } - } + }); } else { // Serialized MinMaxRangePair byte[][] bytesValues = blockValSet.getBytesValuesSV(); - for (int i = 0; i < length; i++) { - MinMaxRangePair minMaxRangePair = ObjectSerDeUtils.MIN_MAX_RANGE_PAIR_SER_DE.deserialize(bytesValues[i]); - double minValue = minMaxRangePair.getMin(); - double maxValue = minMaxRangePair.getMax(); - if (minValue < min) { - min = minValue; + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + MinMaxRangePair minMaxRangePair = ObjectSerDeUtils.MIN_MAX_RANGE_PAIR_SER_DE.deserialize(bytesValues[i]); + minMax.apply(minMaxRangePair); } - if (maxValue > max) { - max = maxValue; - } - } + }); } - setAggregationResult(aggregationResultHolder, min, max); + setAggregationResult(aggregationResultHolder, minMax.getMin(), minMax.getMax()); } protected void setAggregationResult(AggregationResultHolder aggregationResultHolder, double min, double max) { @@ -104,17 +101,21 @@ public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHol BlockValSet blockValSet = blockValSetMap.get(_expression); if (blockValSet.getValueType() != DataType.BYTES) { double[] doubleValues = blockValSet.getDoubleValuesSV(); - for (int i = 0; i < length; i++) { - double value = doubleValues[i]; - setGroupByResult(groupKeyArray[i], groupByResultHolder, value, value); - } + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + double value = doubleValues[i]; + setGroupByResult(groupKeyArray[i], groupByResultHolder, value, value); + } + }); } else { // Serialized MinMaxRangePair byte[][] bytesValues = blockValSet.getBytesValuesSV(); - for (int i = 0; i < length; i++) { - MinMaxRangePair minMaxRangePair = ObjectSerDeUtils.MIN_MAX_RANGE_PAIR_SER_DE.deserialize(bytesValues[i]); - setGroupByResult(groupKeyArray[i], groupByResultHolder, minMaxRangePair.getMin(), minMaxRangePair.getMax()); - } + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + MinMaxRangePair minMaxRangePair = ObjectSerDeUtils.MIN_MAX_RANGE_PAIR_SER_DE.deserialize(bytesValues[i]); + setGroupByResult(groupKeyArray[i], groupByResultHolder, minMaxRangePair.getMin(), minMaxRangePair.getMax()); + } + }); } } @@ -124,23 +125,27 @@ public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResult BlockValSet blockValSet = blockValSetMap.get(_expression); if (blockValSet.getValueType() != DataType.BYTES) { double[] doubleValues = blockValSet.getDoubleValuesSV(); - for (int i = 0; i < length; i++) { - double value = doubleValues[i]; - for (int groupKey : groupKeysArray[i]) { - setGroupByResult(groupKey, groupByResultHolder, value, value); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + double value = doubleValues[i]; + for (int groupKey : groupKeysArray[i]) { + setGroupByResult(groupKey, groupByResultHolder, value, value); + } } - } + }); } else { // Serialized MinMaxRangePair byte[][] bytesValues = blockValSet.getBytesValuesSV(); - for (int i = 0; i < length; i++) { - MinMaxRangePair minMaxRangePair = ObjectSerDeUtils.MIN_MAX_RANGE_PAIR_SER_DE.deserialize(bytesValues[i]); - double min = minMaxRangePair.getMin(); - double max = minMaxRangePair.getMax(); - for (int groupKey : groupKeysArray[i]) { - setGroupByResult(groupKey, groupByResultHolder, min, max); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + MinMaxRangePair minMaxRangePair = ObjectSerDeUtils.MIN_MAX_RANGE_PAIR_SER_DE.deserialize(bytesValues[i]); + double min = minMaxRangePair.getMin(); + double max = minMaxRangePair.getMax(); + for (int groupKey : groupKeysArray[i]) { + setGroupByResult(groupKey, groupByResultHolder, min, max); + } } - } + }); } } @@ -156,8 +161,8 @@ protected void setGroupByResult(int groupKey, GroupByResultHolder groupByResultH @Override public MinMaxRangePair extractAggregationResult(AggregationResultHolder aggregationResultHolder) { MinMaxRangePair minMaxRangePair = aggregationResultHolder.getResult(); - if (minMaxRangePair == null) { - return new MinMaxRangePair(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY); + if (minMaxRangePair == null && !_nullHandlingEnabled) { + return new MinMaxRangePair(); } else { return minMaxRangePair; } @@ -166,8 +171,8 @@ public MinMaxRangePair extractAggregationResult(AggregationResultHolder aggregat @Override public MinMaxRangePair extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { MinMaxRangePair minMaxRangePair = groupByResultHolder.getResult(groupKey); - if (minMaxRangePair == null) { - return new MinMaxRangePair(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY); + if (minMaxRangePair == null && !_nullHandlingEnabled) { + return new MinMaxRangePair(); } else { return minMaxRangePair; } @@ -175,6 +180,14 @@ public MinMaxRangePair extractGroupByResult(GroupByResultHolder groupByResultHol @Override public MinMaxRangePair merge(MinMaxRangePair intermediateResult1, MinMaxRangePair intermediateResult2) { + if (_nullHandlingEnabled) { + if (intermediateResult1 == null) { + return intermediateResult2; + } + if (intermediateResult2 == null) { + return intermediateResult1; + } + } intermediateResult1.apply(intermediateResult2); return intermediateResult1; } @@ -191,6 +204,9 @@ public ColumnDataType getFinalResultColumnType() { @Override public Double extractFinalResult(MinMaxRangePair intermediateResult) { + if (intermediateResult == null) { + return null; + } return intermediateResult.getMax() - intermediateResult.getMin(); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MinMaxRangeMVAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MinMaxRangeMVAggregationFunction.java index a39c0dc1fee3..534bdb41f226 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MinMaxRangeMVAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/MinMaxRangeMVAggregationFunction.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.core.query.aggregation.function; +import java.util.List; import java.util.Map; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.core.common.BlockValSet; @@ -28,8 +29,8 @@ public class MinMaxRangeMVAggregationFunction extends MinMaxRangeAggregationFunction { - public MinMaxRangeMVAggregationFunction(ExpressionContext expression) { - super(expression); + public MinMaxRangeMVAggregationFunction(List arguments) { + super(verifySingleArgument(arguments, "MIN_MAX_RANGE_MV"), false); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/ModeAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/ModeAggregationFunction.java index fb394ad5919a..dbef6d8dad9c 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/ModeAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/ModeAggregationFunction.java @@ -56,19 +56,20 @@ * */ @SuppressWarnings({"rawtypes", "unchecked"}) -public class ModeAggregationFunction extends BaseSingleInputAggregationFunction, Double> { +public class ModeAggregationFunction + extends NullableSingleInputAggregationFunction, Double> { private static final double DEFAULT_FINAL_RESULT = Double.NEGATIVE_INFINITY; private final MultiModeReducerType _multiModeReducerType; - public ModeAggregationFunction(List arguments) { - super(arguments.get(0)); + public ModeAggregationFunction(List arguments, boolean nullHandlingEnabled) { + super(arguments.get(0), nullHandlingEnabled); int numArguments = arguments.size(); Preconditions.checkArgument(numArguments <= 2, "Mode expects at most 2 arguments, got: %s", numArguments); if (numArguments > 1) { - _multiModeReducerType = MultiModeReducerType.valueOf(arguments.get(1).getLiteralString()); + _multiModeReducerType = MultiModeReducerType.valueOf(arguments.get(1).getLiteral().getStringValue()); } else { _multiModeReducerType = MultiModeReducerType.MIN; } @@ -263,11 +264,14 @@ public void aggregate(int length, AggregationResultHolder aggregationResultHolde // For dictionary-encoded expression, store dictionary ids into the dictId map Dictionary dictionary = blockValSet.getDictionary(); if (dictionary != null) { - int[] dictIds = blockValSet.getDictionaryIdsSV(); + Int2IntOpenHashMap dictIdValueMap = getDictIdCountMap(aggregationResultHolder, dictionary); - for (int i = 0; i < length; i++) { - dictIdValueMap.merge(dictIds[i], 1, Integer::sum); - } + int[] dictIds = blockValSet.getDictionaryIdsSV(); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + dictIdValueMap.merge(dictIds[i], 1, Integer::sum); + } + }); return; } @@ -278,30 +282,38 @@ public void aggregate(int length, AggregationResultHolder aggregationResultHolde case INT: Int2LongOpenHashMap intMap = (Int2LongOpenHashMap) valueMap; int[] intValues = blockValSet.getIntValuesSV(); - for (int i = 0; i < length; i++) { - intMap.merge(intValues[i], 1, Long::sum); - } + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + intMap.merge(intValues[i], 1, Long::sum); + } + }); break; case LONG: Long2LongOpenHashMap longMap = (Long2LongOpenHashMap) valueMap; long[] longValues = blockValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - longMap.merge(longValues[i], 1, Long::sum); - } + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + longMap.merge(longValues[i], 1, Long::sum); + } + }); break; case FLOAT: Float2LongOpenHashMap floatMap = (Float2LongOpenHashMap) valueMap; float[] floatValues = blockValSet.getFloatValuesSV(); - for (int i = 0; i < length; i++) { - floatMap.merge(floatValues[i], 1, Long::sum); - } + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + floatMap.merge(floatValues[i], 1, Long::sum); + } + }); break; case DOUBLE: Double2LongOpenHashMap doubleMap = (Double2LongOpenHashMap) valueMap; double[] doubleValues = blockValSet.getDoubleValuesSV(); - for (int i = 0; i < length; i++) { - doubleMap.merge(doubleValues[i], 1, Long::sum); - } + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + doubleMap.merge(doubleValues[i], 1, Long::sum); + } + }); break; default: throw new IllegalStateException("Illegal data type for MODE aggregation function: " + storedType); @@ -317,9 +329,12 @@ public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHol Dictionary dictionary = blockValSet.getDictionary(); if (dictionary != null) { int[] dictIds = blockValSet.getDictionaryIdsSV(); - for (int i = 0; i < length; i++) { - getDictIdCountMap(groupByResultHolder, groupKeyArray[i], dictionary).merge(dictIds[i], 1, Integer::sum); - } + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + Int2IntOpenHashMap dictIdCountMap = getDictIdCountMap(groupByResultHolder, groupKeyArray[i], dictionary); + dictIdCountMap.merge(dictIds[i], 1, Integer::sum); + } + }); return; } @@ -328,27 +343,35 @@ public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHol switch (storedType) { case INT: int[] intValues = blockValSet.getIntValuesSV(); - for (int i = 0; i < length; i++) { - setValueForGroupKeys(groupByResultHolder, groupKeyArray[i], intValues[i]); - } + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + setValueForGroupKeys(groupByResultHolder, groupKeyArray[i], intValues[i]); + } + }); break; case LONG: long[] longValues = blockValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - setValueForGroupKeys(groupByResultHolder, groupKeyArray[i], longValues[i]); - } + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + setValueForGroupKeys(groupByResultHolder, groupKeyArray[i], longValues[i]); + } + }); break; case FLOAT: float[] floatValues = blockValSet.getFloatValuesSV(); - for (int i = 0; i < length; i++) { - setValueForGroupKeys(groupByResultHolder, groupKeyArray[i], floatValues[i]); - } + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + setValueForGroupKeys(groupByResultHolder, groupKeyArray[i], floatValues[i]); + } + }); break; case DOUBLE: double[] doubleValues = blockValSet.getDoubleValuesSV(); - for (int i = 0; i < length; i++) { - setValueForGroupKeys(groupByResultHolder, groupKeyArray[i], doubleValues[i]); - } + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + setValueForGroupKeys(groupByResultHolder, groupKeyArray[i], doubleValues[i]); + } + }); break; default: throw new IllegalStateException("Illegal data type for MODE aggregation function: " + storedType); @@ -364,11 +387,13 @@ public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResult Dictionary dictionary = blockValSet.getDictionary(); if (dictionary != null) { int[] dictIds = blockValSet.getDictionaryIdsSV(); - for (int i = 0; i < length; i++) { - for (int groupKey : groupKeysArray[i]) { - getDictIdCountMap(groupByResultHolder, groupKey, dictionary).merge(dictIds[i], 1, Integer::sum); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + for (int groupKey : groupKeysArray[i]) { + getDictIdCountMap(groupByResultHolder, groupKey, dictionary).merge(dictIds[i], 1, Integer::sum); + } } - } + }); return; } @@ -377,35 +402,43 @@ public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResult switch (storedType) { case INT: int[] intValues = blockValSet.getIntValuesSV(); - for (int i = 0; i < length; i++) { - for (int groupKey : groupKeysArray[i]) { - setValueForGroupKeys(groupByResultHolder, groupKey, intValues[i]); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + for (int groupKey : groupKeysArray[i]) { + setValueForGroupKeys(groupByResultHolder, groupKey, intValues[i]); + } } - } + }); break; case LONG: long[] longValues = blockValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - for (int groupKey : groupKeysArray[i]) { - setValueForGroupKeys(groupByResultHolder, groupKey, longValues[i]); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + for (int groupKey : groupKeysArray[i]) { + setValueForGroupKeys(groupByResultHolder, groupKey, longValues[i]); + } } - } + }); break; case FLOAT: float[] floatValues = blockValSet.getFloatValuesSV(); - for (int i = 0; i < length; i++) { - for (int groupKey : groupKeysArray[i]) { - setValueForGroupKeys(groupByResultHolder, groupKey, floatValues[i]); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + for (int groupKey : groupKeysArray[i]) { + setValueForGroupKeys(groupByResultHolder, groupKey, floatValues[i]); + } } - } + }); break; case DOUBLE: double[] doubleValues = blockValSet.getDoubleValuesSV(); - for (int i = 0; i < length; i++) { - for (int groupKey : groupKeysArray[i]) { - setValueForGroupKeys(groupByResultHolder, groupKey, doubleValues[i]); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + for (int groupKey : groupKeysArray[i]) { + setValueForGroupKeys(groupByResultHolder, groupKey, doubleValues[i]); + } } - } + }); break; default: throw new IllegalStateException("Illegal data type for MODE aggregation function: " + storedType); @@ -467,7 +500,11 @@ public ColumnDataType getFinalResultColumnType() { @Override public Double extractFinalResult(Map intermediateResult) { if (intermediateResult.isEmpty()) { - return DEFAULT_FINAL_RESULT; + if (_nullHandlingEnabled) { + return null; + } else { + return DEFAULT_FINAL_RESULT; + } } else if (intermediateResult instanceof Int2LongOpenHashMap) { return extractFinalResult((Int2LongOpenHashMap) intermediateResult); } else if (intermediateResult instanceof Long2LongOpenHashMap) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/NullableSingleInputAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/NullableSingleInputAggregationFunction.java new file mode 100644 index 000000000000..907f0139d2a9 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/NullableSingleInputAggregationFunction.java @@ -0,0 +1,263 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import java.util.NoSuchElementException; +import javax.annotation.Nullable; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.common.BlockValSet; +import org.roaringbitmap.IntIterator; +import org.roaringbitmap.RoaringBitmap; + + +public abstract class NullableSingleInputAggregationFunction + extends BaseSingleInputAggregationFunction { + protected final boolean _nullHandlingEnabled; + + public NullableSingleInputAggregationFunction(ExpressionContext expression, boolean nullHandlingEnabled) { + super(expression); + _nullHandlingEnabled = nullHandlingEnabled; + } + + /** + * A consumer that is being used to consume batch of indexes. + */ + @FunctionalInterface + public interface BatchConsumer { + /** + * Consumes a batch of indexes. + * @param fromInclusive the start index (inclusive) + * @param toExclusive the end index (exclusive) + */ + void consume(int fromInclusive, int toExclusive); + } + + /** + * A reducer that is being used to fold over consecutive indexes. + * @param + */ + @FunctionalInterface + public interface Reducer { + /** + * Applies the reducer to the range of indexes. + * @param acum the initial value of the accumulator + * @param fromInclusive the start index (inclusive) + * @param toExclusive the end index (exclusive) + * @return the next value of the accumulator (maybe the same as the input) + */ + A apply(A acum, int fromInclusive, int toExclusive); + } + + /** + * Iterates over the non-null ranges of the blockValSet and calls the consumer for each range. + * @param blockValSet the blockValSet to iterate over + * @param consumer the consumer to call for each non-null range + */ + public void forEachNotNull(int length, BlockValSet blockValSet, BatchConsumer consumer) { + if (!_nullHandlingEnabled) { + consumer.consume(0, length); + return; + } + + RoaringBitmap roaringBitmap = blockValSet.getNullBitmap(); + if (roaringBitmap == null) { + consumer.consume(0, length); + return; + } + + forEachNotNull(length, roaringBitmap.getIntIterator(), consumer); + } + + /** + * Iterates over the non-null ranges of the nullIndexIterator and calls the consumer for each range. + * @param nullIndexIterator an int iterator that returns values in ascending order whose min value is 0. + * Rows are considered null if and only if their index is emitted. + */ + public void forEachNotNull(int length, IntIterator nullIndexIterator, BatchConsumer consumer) { + int prev = 0; + while (nullIndexIterator.hasNext() && prev < length) { + int nextNull = Math.min(nullIndexIterator.next(), length); + if (nextNull > prev) { + consumer.consume(prev, nextNull); + } + prev = nextNull + 1; + } + if (prev < length) { + consumer.consume(prev, length); + } + } + + /** + * Folds over the non-null ranges of the blockValSet using the reducer. + * @param initialAcum the initial value of the accumulator + * @param The type of the accumulator + */ + public A foldNotNull(int length, BlockValSet blockValSet, A initialAcum, Reducer reducer) { + return foldNotNull(length, blockValSet.getNullBitmap(), initialAcum, reducer); + } + + /** + * Folds over the non-null ranges of the blockValSet using the reducer. + * @param initialAcum the initial value of the accumulator + * @param The type of the accumulator + */ + public A foldNotNull(int length, @Nullable RoaringBitmap roaringBitmap, A initialAcum, Reducer reducer) { + IntIterator intIterator = roaringBitmap == null ? null : roaringBitmap.getIntIterator(); + return foldNotNull(length, intIterator, initialAcum, reducer); + } + + /** + * Folds over the non-null ranges of the nullIndexIterator using the reducer. + * @param nullIndexIterator an int iterator that returns values in ascending order whose min value is 0. + * Rows are considered null if and only if their index is emitted. + * @param initialAcum the initial value of the accumulator + * @param The type of the accumulator + */ + public A foldNotNull(int length, @Nullable IntIterator nullIndexIterator, A initialAcum, Reducer reducer) { + A acum = initialAcum; + if (!_nullHandlingEnabled || nullIndexIterator == null || !nullIndexIterator.hasNext()) { + return reducer.apply(initialAcum, 0, length); + } + + int prev = 0; + while (nullIndexIterator.hasNext() && prev < length) { + int nextNull = Math.min(nullIndexIterator.next(), length); + if (nextNull > prev) { + acum = reducer.apply(acum, prev, nextNull); + } + prev = nextNull + 1; + } + if (prev < length) { + acum = reducer.apply(acum, prev, length); + } + return acum; + } + + public IntIterator orNullIterator(BlockValSet valSet1, BlockValSet valSet2) { + if (!_nullHandlingEnabled) { + return EmptyIntIterator.INSTANCE; + } else { + RoaringBitmap nullBlock1 = valSet1.getNullBitmap(); + RoaringBitmap nullBlock2 = valSet2.getNullBitmap(); + if (nullBlock1 == null) { + return nullBlock2 == null ? EmptyIntIterator.INSTANCE : nullBlock2.getIntIterator(); + } else if (nullBlock2 == null) { + return nullBlock1.getIntIterator(); + } else { + return new MinIntIterator(nullBlock1.getIntIterator(), nullBlock2.getIntIterator()); + } + } + } + + public static class EmptyIntIterator implements IntIterator { + + public static final EmptyIntIterator INSTANCE = new EmptyIntIterator(); + + private EmptyIntIterator() { + } + + @Override + public IntIterator clone() { + return this; + } + + @Override + public boolean hasNext() { + return false; + } + + @Override + public int next() { + throw new NoSuchElementException(); + } + } + + public static class MinIntIterator implements IntIterator { + private final IntIterator _it1; + private final IntIterator _it2; + private int _next1 = -1; + private int _next2 = -1; + + /** + * @param it1 it has to iterate in ascending order and the min value is 0 + * @param it2 it has to iterate in ascending order and the min value is 0 + */ + public MinIntIterator(IntIterator it1, IntIterator it2) { + _it1 = it1; + _it2 = it2; + } + + @Override + public IntIterator clone() { + return new MinIntIterator(_it1.clone(), _it2.clone()); + } + + @Override + public boolean hasNext() { + return _next1 > 0 || _next2 > 0 || _it1.hasNext() || _it2.hasNext(); + } + + @Override + public int next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + if (_next1 < 0) { + if (_it1.hasNext()) { + _next1 = _it1.next(); + } else { //it1 is completely consumed + if (_next2 >= 0) { // consume the last cached value + return consume2(); + } else { // after that, return all values from it2 + return _it2.next(); + } + } + } + if (_next2 < 0) { + if (_it2.hasNext()) { + _next2 = _it2.next(); + } else { //it2 is completely consumed + if (_next1 >= 0) { // consume the last cached value + return consume1(); + } else { // after that, return all values from it1 + return _it1.next(); + } + } + } + assert _next1 >= 0 && _next2 >= 0; + if (_next1 <= _next2) { + return consume1(); + } else { + return consume2(); + } + } + + private int consume1() { + int nextVal = _next1; + _next1 = -1; + return nextVal; + } + + private int consume2() { + int nextVal = _next2; + _next2 = -1; + return nextVal; + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/ParentAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/ParentAggregationFunction.java new file mode 100644 index 000000000000..9fb18bd347c1 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/ParentAggregationFunction.java @@ -0,0 +1,71 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import java.util.List; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.query.aggregation.utils.ParentAggregationFunctionResultObject; +import org.apache.pinot.spi.utils.CommonConstants; + + +/** + * Base class for parent aggregation functions. A parent aggregation function is an aggregation function + * whose result is a nested data block containing multiple columns, each of which corresponds to a child + * aggregation function's result. + */ +public abstract class ParentAggregationFunction + implements AggregationFunction { + + protected static final int PARENT_AGGREGATION_FUNCTION_ID_OFFSET = 0; + protected List _arguments; + + ParentAggregationFunction(List arguments) { + _arguments = arguments; + } + + @Override + public final DataSchema.ColumnDataType getFinalResultColumnType() { + return DataSchema.ColumnDataType.OBJECT; + } + + // The name of the column is the prefix of the parent aggregation function + the name of the + // aggregation function + the id of the parent aggregation function + // e.g. if the parent aggregation function is "exprmax(0,3,a,b,c,x,y,z)", the name of the column is + // "pinotparentaggregationexprmax0" + @Override + public final String getResultColumnName() { + return CommonConstants.RewriterConstants.PARENT_AGGREGATION_NAME_PREFIX + + getType().getName().toLowerCase() + + _arguments.get(PARENT_AGGREGATION_FUNCTION_ID_OFFSET).getLiteral().getIntValue(); + } + + public final String toExplainString() { + StringBuilder stringBuilder = new StringBuilder(CommonConstants.RewriterConstants.PARENT_AGGREGATION_NAME_PREFIX) + .append("_").append(getType().getName()).append('('); + int numArguments = _arguments.size(); + if (numArguments > 0) { + stringBuilder.append(_arguments.get(0).toString()); + for (int i = 1; i < numArguments; i++) { + stringBuilder.append(", ").append(_arguments.get(i).toString()); + } + } + return stringBuilder.append(')').toString(); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/ParentExprMinMaxAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/ParentExprMinMaxAggregationFunction.java new file mode 100644 index 000000000000..6836a596a564 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/ParentExprMinMaxAggregationFunction.java @@ -0,0 +1,432 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import com.google.common.base.Preconditions; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.ObjectAggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.ObjectGroupByResultHolder; +import org.apache.pinot.core.query.aggregation.utils.exprminmax.ExprMinMaxMeasuringValSetWrapper; +import org.apache.pinot.core.query.aggregation.utils.exprminmax.ExprMinMaxObject; +import org.apache.pinot.core.query.aggregation.utils.exprminmax.ExprMinMaxProjectionValSetWrapper; +import org.apache.pinot.segment.spi.AggregationFunctionType; + + +public class ParentExprMinMaxAggregationFunction extends ParentAggregationFunction { + + // list of columns that we do min/max on + private final List _measuringColumns; + // list of columns that we project based on the min/max value + private final List _projectionColumns; + // true if we are doing exprmax, false if we are doing exprmin + private final boolean _isMax; + // the id of the function, this is to associate the result of the parent aggregation function with the + // child aggregation functions having the same type(exprmin/exprmax) and measuring columns + private final ExpressionContext _functionIdContext; + private final ExpressionContext _numMeasuringColumnContext; + // number of columns that we do min/max on + private final int _numMeasuringColumns; + // number of columns that we project based on the min/max value + private final int _numProjectionColumns; + + // The following variable need to be initialized + + // The wrapper classes for the block value sets + private final ThreadLocal> _exprMinMaxWrapperMeasuringColumnSets = + ThreadLocal.withInitial(ArrayList::new); + private final ThreadLocal> _exprMinMaxWrapperProjectionColumnSets = + ThreadLocal.withInitial(ArrayList::new); + // The schema for the measuring columns and projection columns + private final ThreadLocal _measuringColumnSchema = new ThreadLocal<>(); + private final ThreadLocal _projectionColumnSchema = new ThreadLocal<>(); + // If the schemas are initialized + private final ThreadLocal _schemaInitialized = ThreadLocal.withInitial(() -> false); + + public ParentExprMinMaxAggregationFunction(List arguments, boolean isMax) { + + super(arguments); + _isMax = isMax; + _functionIdContext = arguments.get(0); + + _numMeasuringColumnContext = arguments.get(1); + _numMeasuringColumns = _numMeasuringColumnContext.getLiteral().getIntValue(); + + _measuringColumns = arguments.subList(2, 2 + _numMeasuringColumns); + _projectionColumns = arguments.subList(2 + _numMeasuringColumns, arguments.size()); + _numProjectionColumns = _projectionColumns.size(); + } + + @Override + public AggregationFunctionType getType() { + return _isMax ? AggregationFunctionType.EXPRMAX : AggregationFunctionType.EXPRMIN; + } + + @Override + public List getInputExpressions() { + ArrayList expressionContexts = new ArrayList<>(); + expressionContexts.add(_functionIdContext); + expressionContexts.add(_numMeasuringColumnContext); + expressionContexts.addAll(_measuringColumns); + expressionContexts.addAll(_projectionColumns); + return expressionContexts; + } + + @Override + public AggregationResultHolder createAggregationResultHolder() { + return new ObjectAggregationResultHolder(); + } + + @Override + public GroupByResultHolder createGroupByResultHolder(int initialCapacity, int maxCapacity) { + return new ObjectGroupByResultHolder(initialCapacity, maxCapacity); + } + + @SuppressWarnings("LoopStatementThatDoesntLoop") + @Override + public void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + + ExprMinMaxObject exprMinMaxObject = aggregationResultHolder.getResult(); + + if (exprMinMaxObject == null) { + initializeWithNewDataBlocks(blockValSetMap); + exprMinMaxObject = new ExprMinMaxObject(_measuringColumnSchema.get(), _projectionColumnSchema.get()); + } + + List rowIds = new ArrayList<>(); + for (int i = 0; i < length; i++) { + int compareResult = exprMinMaxObject.compareAndSetKey(_exprMinMaxWrapperMeasuringColumnSets.get(), i, _isMax); + if (compareResult == 0) { + // same key, add the rowId to the list + rowIds.add(i); + } else if (compareResult > 0) { + // new key is set, clear the list and add the new rowId + rowIds.clear(); + rowIds.add(i); + } + } + + // for all the rows that are associated with the extremum key, add the projection columns + for (Integer rowId : rowIds) { + exprMinMaxObject.addVal(_exprMinMaxWrapperProjectionColumnSets.get(), rowId); + } + + aggregationResultHolder.setValue(exprMinMaxObject); + } + + // this method is called to initialize the schemas if they are not initialized + // and to set the new block value sets for the wrapper classes + private void initializeWithNewDataBlocks(Map blockValSetMap) { + if (blockValSetMap == null) { + initializeForEmptyDocSet(); + return; + } + + // if the schema is already initialized, just update with the new block value sets + if (_schemaInitialized.get()) { + for (int i = 0; i < _numMeasuringColumns; i++) { + _exprMinMaxWrapperMeasuringColumnSets.get().get(i).setNewBlock(blockValSetMap.get(_measuringColumns.get(i))); + } + for (int i = 0; i < _numProjectionColumns; i++) { + _exprMinMaxWrapperProjectionColumnSets.get().get(i).setNewBlock(blockValSetMap.get(_projectionColumns.get(i))); + } + return; + } + // the schema is initialized only once + _schemaInitialized.set(true); + // setup measuring column names and types + initializeMeasuringColumnValSet(blockValSetMap); + // setup projection column names and types + initializeProjectionColumnValSet(blockValSetMap); + } + + private void initializeProjectionColumnValSet(Map blockValSetMap) { + List exprMinMaxWrapperProjectionColumnSets = + _exprMinMaxWrapperProjectionColumnSets.get(); + String[] projectionColNames = new String[_projectionColumns.size()]; + DataSchema.ColumnDataType[] projectionColTypes = new DataSchema.ColumnDataType[_projectionColumns.size()]; + for (int i = 0; i < _projectionColumns.size(); i++) { + projectionColNames[i] = _projectionColumns.get(i).toString(); + ExpressionContext projectionColumn = _projectionColumns.get(i); + BlockValSet blockValSet = blockValSetMap.get(projectionColumn); + if (blockValSet.isSingleValue()) { + switch (blockValSet.getValueType()) { + case INT: + exprMinMaxWrapperProjectionColumnSets.add( + new ExprMinMaxProjectionValSetWrapper(true, DataSchema.ColumnDataType.INT, blockValSet)); + projectionColTypes[i] = DataSchema.ColumnDataType.INT; + break; + case BOOLEAN: + exprMinMaxWrapperProjectionColumnSets.add( + new ExprMinMaxProjectionValSetWrapper(true, DataSchema.ColumnDataType.BOOLEAN, blockValSet)); + projectionColTypes[i] = DataSchema.ColumnDataType.INT; + break; + case LONG: + exprMinMaxWrapperProjectionColumnSets.add( + new ExprMinMaxProjectionValSetWrapper(true, DataSchema.ColumnDataType.LONG, blockValSet)); + projectionColTypes[i] = DataSchema.ColumnDataType.LONG; + break; + case TIMESTAMP: + exprMinMaxWrapperProjectionColumnSets.add( + new ExprMinMaxProjectionValSetWrapper(true, DataSchema.ColumnDataType.TIMESTAMP, blockValSet)); + projectionColTypes[i] = DataSchema.ColumnDataType.LONG; + break; + case FLOAT: + exprMinMaxWrapperProjectionColumnSets.add( + new ExprMinMaxProjectionValSetWrapper(true, DataSchema.ColumnDataType.FLOAT, blockValSet)); + projectionColTypes[i] = DataSchema.ColumnDataType.FLOAT; + break; + case DOUBLE: + exprMinMaxWrapperProjectionColumnSets.add( + new ExprMinMaxProjectionValSetWrapper(true, DataSchema.ColumnDataType.DOUBLE, blockValSet)); + projectionColTypes[i] = DataSchema.ColumnDataType.DOUBLE; + break; + case STRING: + exprMinMaxWrapperProjectionColumnSets.add( + new ExprMinMaxProjectionValSetWrapper(true, DataSchema.ColumnDataType.STRING, blockValSet)); + projectionColTypes[i] = DataSchema.ColumnDataType.STRING; + break; + case JSON: + exprMinMaxWrapperProjectionColumnSets.add( + new ExprMinMaxProjectionValSetWrapper(true, DataSchema.ColumnDataType.JSON, blockValSet)); + projectionColTypes[i] = DataSchema.ColumnDataType.STRING; + break; + case BYTES: + exprMinMaxWrapperProjectionColumnSets.add( + new ExprMinMaxProjectionValSetWrapper(true, DataSchema.ColumnDataType.BYTES, blockValSet)); + projectionColTypes[i] = DataSchema.ColumnDataType.BYTES; + break; + case BIG_DECIMAL: + exprMinMaxWrapperProjectionColumnSets.add( + new ExprMinMaxProjectionValSetWrapper(true, DataSchema.ColumnDataType.BIG_DECIMAL, blockValSet)); + projectionColTypes[i] = DataSchema.ColumnDataType.BIG_DECIMAL; + break; + default: + throw new IllegalStateException( + "Cannot compute exprminMax projection on non-comparable type: " + blockValSet.getValueType()); + } + } else { + switch (blockValSet.getValueType()) { + case INT: + exprMinMaxWrapperProjectionColumnSets.add( + new ExprMinMaxProjectionValSetWrapper(false, DataSchema.ColumnDataType.INT_ARRAY, blockValSet)); + projectionColTypes[i] = DataSchema.ColumnDataType.INT_ARRAY; + break; + case BOOLEAN: + exprMinMaxWrapperProjectionColumnSets.add( + new ExprMinMaxProjectionValSetWrapper(false, DataSchema.ColumnDataType.BOOLEAN_ARRAY, blockValSet)); + projectionColTypes[i] = DataSchema.ColumnDataType.INT_ARRAY; + break; + case LONG: + exprMinMaxWrapperProjectionColumnSets.add( + new ExprMinMaxProjectionValSetWrapper(false, DataSchema.ColumnDataType.LONG_ARRAY, blockValSet)); + projectionColTypes[i] = DataSchema.ColumnDataType.LONG_ARRAY; + break; + case TIMESTAMP: + exprMinMaxWrapperProjectionColumnSets.add( + new ExprMinMaxProjectionValSetWrapper(false, DataSchema.ColumnDataType.TIMESTAMP_ARRAY, blockValSet)); + projectionColTypes[i] = DataSchema.ColumnDataType.LONG_ARRAY; + break; + case FLOAT: + exprMinMaxWrapperProjectionColumnSets.add( + new ExprMinMaxProjectionValSetWrapper(false, DataSchema.ColumnDataType.FLOAT_ARRAY, blockValSet)); + projectionColTypes[i] = DataSchema.ColumnDataType.FLOAT_ARRAY; + break; + case DOUBLE: + exprMinMaxWrapperProjectionColumnSets.add( + new ExprMinMaxProjectionValSetWrapper(false, DataSchema.ColumnDataType.DOUBLE_ARRAY, blockValSet)); + projectionColTypes[i] = DataSchema.ColumnDataType.DOUBLE_ARRAY; + break; + case STRING: + exprMinMaxWrapperProjectionColumnSets.add( + new ExprMinMaxProjectionValSetWrapper(false, DataSchema.ColumnDataType.STRING_ARRAY, blockValSet)); + projectionColTypes[i] = DataSchema.ColumnDataType.STRING_ARRAY; + break; + case BYTES: + exprMinMaxWrapperProjectionColumnSets.add( + new ExprMinMaxProjectionValSetWrapper(false, DataSchema.ColumnDataType.BYTES_ARRAY, blockValSet)); + projectionColTypes[i] = DataSchema.ColumnDataType.BYTES_ARRAY; + break; + default: + throw new IllegalStateException( + "Cannot compute exprminMax projection on non-comparable type: " + blockValSet.getValueType()); + } + } + } + // setup measuring column schema + _projectionColumnSchema.set(new DataSchema(projectionColNames, projectionColTypes)); + } + + private void initializeMeasuringColumnValSet(Map blockValSetMap) { + List exprMinMaxWrapperMeasuringColumnSets = + _exprMinMaxWrapperMeasuringColumnSets.get(); + String[] measuringColNames = new String[_numMeasuringColumns]; + DataSchema.ColumnDataType[] measuringColTypes = new DataSchema.ColumnDataType[_numMeasuringColumns]; + for (int i = 0; i < _numMeasuringColumns; i++) { + measuringColNames[i] = _measuringColumns.get(i).toString(); + ExpressionContext measuringColumn = _measuringColumns.get(i); + BlockValSet blockValSet = blockValSetMap.get(measuringColumn); + Preconditions.checkState(blockValSet.isSingleValue(), "ExprMinMax only supports single-valued" + + " measuring columns"); + switch (blockValSet.getValueType()) { + case INT: + exprMinMaxWrapperMeasuringColumnSets.add( + new ExprMinMaxMeasuringValSetWrapper(true, DataSchema.ColumnDataType.INT, blockValSet)); + measuringColTypes[i] = DataSchema.ColumnDataType.INT; + break; + case BOOLEAN: + exprMinMaxWrapperMeasuringColumnSets.add( + new ExprMinMaxMeasuringValSetWrapper(true, DataSchema.ColumnDataType.BOOLEAN, blockValSet)); + measuringColTypes[i] = DataSchema.ColumnDataType.INT; + break; + case LONG: + exprMinMaxWrapperMeasuringColumnSets.add( + new ExprMinMaxMeasuringValSetWrapper(true, DataSchema.ColumnDataType.LONG, blockValSet)); + measuringColTypes[i] = DataSchema.ColumnDataType.LONG; + break; + case TIMESTAMP: + exprMinMaxWrapperMeasuringColumnSets.add( + new ExprMinMaxMeasuringValSetWrapper(true, DataSchema.ColumnDataType.TIMESTAMP, blockValSet)); + measuringColTypes[i] = DataSchema.ColumnDataType.LONG; + break; + case FLOAT: + exprMinMaxWrapperMeasuringColumnSets.add( + new ExprMinMaxMeasuringValSetWrapper(true, DataSchema.ColumnDataType.FLOAT, blockValSet)); + measuringColTypes[i] = DataSchema.ColumnDataType.FLOAT; + break; + case DOUBLE: + exprMinMaxWrapperMeasuringColumnSets.add( + new ExprMinMaxMeasuringValSetWrapper(true, DataSchema.ColumnDataType.DOUBLE, blockValSet)); + measuringColTypes[i] = DataSchema.ColumnDataType.DOUBLE; + break; + case STRING: + exprMinMaxWrapperMeasuringColumnSets.add( + new ExprMinMaxMeasuringValSetWrapper(true, DataSchema.ColumnDataType.STRING, blockValSet)); + measuringColTypes[i] = DataSchema.ColumnDataType.STRING; + break; + case BIG_DECIMAL: + exprMinMaxWrapperMeasuringColumnSets.add( + new ExprMinMaxMeasuringValSetWrapper(true, DataSchema.ColumnDataType.BIG_DECIMAL, blockValSet)); + measuringColTypes[i] = DataSchema.ColumnDataType.BIG_DECIMAL; + break; + default: + throw new IllegalStateException( + "Cannot compute exprminMax measuring on non-comparable type: " + blockValSet.getValueType()); + } + } + // setup measuring column schema + _measuringColumnSchema.set(new DataSchema(measuringColNames, measuringColTypes)); + } + + // This method is called when the docIdSet is empty meaning that there are no rows that match the filter. + private void initializeForEmptyDocSet() { + if (_schemaInitialized.get()) { + return; + } + _schemaInitialized.set(true); + String[] measuringColNames = new String[_numMeasuringColumns]; + DataSchema.ColumnDataType[] measuringColTypes = new DataSchema.ColumnDataType[_numMeasuringColumns]; + for (int i = 0; i < _numMeasuringColumns; i++) { + measuringColNames[i] = _measuringColumns.get(i).toString(); + measuringColTypes[i] = DataSchema.ColumnDataType.STRING; + } + + String[] projectionColNames = new String[_numProjectionColumns]; + DataSchema.ColumnDataType[] projectionColTypes = new DataSchema.ColumnDataType[_numProjectionColumns]; + for (int i = 0; i < _numProjectionColumns; i++) { + projectionColNames[i] = _projectionColumns.get(i).toString(); + projectionColTypes[i] = DataSchema.ColumnDataType.STRING; + } + _measuringColumnSchema.set(new DataSchema(measuringColNames, measuringColTypes)); + _projectionColumnSchema.set(new DataSchema(projectionColNames, projectionColTypes)); + } + + @Override + public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + initializeWithNewDataBlocks(blockValSetMap); + for (int i = 0; i < length; i++) { + int groupKey = groupKeyArray[i]; + updateGroupByResult(groupByResultHolder, i, groupKey); + } + } + + private void updateGroupByResult(GroupByResultHolder groupByResultHolder, int i, int groupKey) { + ExprMinMaxObject exprMinMaxObject = groupByResultHolder.getResult(groupKey); + if (exprMinMaxObject == null) { + exprMinMaxObject = new ExprMinMaxObject(_measuringColumnSchema.get(), _projectionColumnSchema.get()); + groupByResultHolder.setValueForKey(groupKey, exprMinMaxObject); + } + int compareResult = exprMinMaxObject.compareAndSetKey(_exprMinMaxWrapperMeasuringColumnSets.get(), i, _isMax); + if (compareResult == 0) { + exprMinMaxObject.addVal(_exprMinMaxWrapperProjectionColumnSets.get(), i); + } else if (compareResult > 0) { + exprMinMaxObject.setToNewVal(_exprMinMaxWrapperProjectionColumnSets.get(), i); + } + } + + @Override + public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + initializeWithNewDataBlocks(blockValSetMap); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + updateGroupByResult(groupByResultHolder, i, groupKey); + } + } + } + + @Override + public ExprMinMaxObject extractAggregationResult(AggregationResultHolder aggregationResultHolder) { + ExprMinMaxObject exprMinMaxObject = aggregationResultHolder.getResult(); + if (exprMinMaxObject == null) { + initializeWithNewDataBlocks(null); + return new ExprMinMaxObject(_measuringColumnSchema.get(), _projectionColumnSchema.get()); + } else { + return exprMinMaxObject; + } + } + + @Override + public ExprMinMaxObject extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { + return groupByResultHolder.getResult(groupKey); + } + + @Override + public ExprMinMaxObject merge(ExprMinMaxObject intermediateResult1, ExprMinMaxObject intermediateResult2) { + return intermediateResult1.merge(intermediateResult2, _isMax); + } + + @Override + public DataSchema.ColumnDataType getIntermediateResultColumnType() { + return DataSchema.ColumnDataType.OBJECT; + } + + @Override + public ExprMinMaxObject extractFinalResult(ExprMinMaxObject exprMinMaxObject) { + return exprMinMaxObject; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileAggregationFunction.java index 8bfca2eefeb2..c9c71744d267 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileAggregationFunction.java @@ -31,7 +31,7 @@ import org.apache.pinot.segment.spi.AggregationFunctionType; -public class PercentileAggregationFunction extends BaseSingleInputAggregationFunction { +public class PercentileAggregationFunction extends NullableSingleInputAggregationFunction { private static final double DEFAULT_FINAL_RESULT = Double.NEGATIVE_INFINITY; //version 0 functions specified in the of form PERCENTILE<2-digits>(column) @@ -39,14 +39,14 @@ public class PercentileAggregationFunction extends BaseSingleInputAggregationFun protected final int _version; protected final double _percentile; - public PercentileAggregationFunction(ExpressionContext expression, int percentile) { - super(expression); + public PercentileAggregationFunction(ExpressionContext expression, int percentile, boolean nullHandlingEnabled) { + super(expression, nullHandlingEnabled); _version = 0; _percentile = percentile; } - public PercentileAggregationFunction(ExpressionContext expression, double percentile) { - super(expression); + public PercentileAggregationFunction(ExpressionContext expression, double percentile, boolean nullHandlingEnabled) { + super(expression, nullHandlingEnabled); _version = 1; _percentile = percentile; } @@ -56,12 +56,6 @@ public AggregationFunctionType getType() { return AggregationFunctionType.PERCENTILE; } - @Override - public String getColumnName() { - return _version == 0 ? AggregationFunctionType.PERCENTILE.getName() + (int) _percentile + "_" + _expression - : AggregationFunctionType.PERCENTILE.getName() + _percentile + "_" + _expression; - } - @Override public String getResultColumnName() { return _version == 0 ? AggregationFunctionType.PERCENTILE.getName().toLowerCase() + (int) _percentile + "(" @@ -83,33 +77,42 @@ public GroupByResultHolder createGroupByResultHolder(int initialCapacity, int ma public void aggregate(int length, AggregationResultHolder aggregationResultHolder, Map blockValSetMap) { DoubleArrayList valueList = getValueList(aggregationResultHolder); - double[] valueArray = blockValSetMap.get(_expression).getDoubleValuesSV(); - for (int i = 0; i < length; i++) { - valueList.add(valueArray[i]); - } + BlockValSet blockValSet = blockValSetMap.get(_expression); + double[] valueArray = blockValSet.getDoubleValuesSV(); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + valueList.add(valueArray[i]); + } + }); } @Override public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, Map blockValSetMap) { - double[] valueArray = blockValSetMap.get(_expression).getDoubleValuesSV(); - for (int i = 0; i < length; i++) { - DoubleArrayList valueList = getValueList(groupByResultHolder, groupKeyArray[i]); - valueList.add(valueArray[i]); - } + BlockValSet blockValSet = blockValSetMap.get(_expression); + double[] valueArray = blockValSet.getDoubleValuesSV(); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + DoubleArrayList valueList = getValueList(groupByResultHolder, groupKeyArray[i]); + valueList.add(valueArray[i]); + } + }); } @Override public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, Map blockValSetMap) { - double[] valueArray = blockValSetMap.get(_expression).getDoubleValuesSV(); - for (int i = 0; i < length; i++) { - double value = valueArray[i]; - for (int groupKey : groupKeysArray[i]) { - DoubleArrayList valueList = getValueList(groupByResultHolder, groupKey); - valueList.add(value); + BlockValSet blockValSet = blockValSetMap.get(_expression); + double[] valueArray = blockValSet.getDoubleValuesSV(); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + double value = valueArray[i]; + for (int groupKey : groupKeysArray[i]) { + DoubleArrayList valueList = getValueList(groupByResultHolder, groupKey); + valueList.add(value); + } } - } + }); } @Override @@ -152,7 +155,11 @@ public ColumnDataType getFinalResultColumnType() { public Double extractFinalResult(DoubleArrayList intermediateResult) { int size = intermediateResult.size(); if (size == 0) { - return DEFAULT_FINAL_RESULT; + if (_nullHandlingEnabled) { + return null; + } else { + return DEFAULT_FINAL_RESULT; + } } else { double[] values = intermediateResult.elements(); Arrays.sort(values, 0, size); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileEstAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileEstAggregationFunction.java index ba815c47b8bd..e67a3f7d6500 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileEstAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileEstAggregationFunction.java @@ -32,7 +32,7 @@ import org.apache.pinot.spi.data.FieldSpec.DataType; -public class PercentileEstAggregationFunction extends BaseSingleInputAggregationFunction { +public class PercentileEstAggregationFunction extends NullableSingleInputAggregationFunction { public static final double DEFAULT_MAX_ERROR = 0.05; //version 0 functions specified in the of form PERCENTILEEST<2-digits>(column) @@ -40,14 +40,15 @@ public class PercentileEstAggregationFunction extends BaseSingleInputAggregation protected final int _version; protected final double _percentile; - public PercentileEstAggregationFunction(ExpressionContext expression, int percentile) { - super(expression); + public PercentileEstAggregationFunction(ExpressionContext expression, int percentile, boolean nullHandlingEnabled) { + super(expression, nullHandlingEnabled); _version = 0; _percentile = percentile; } - public PercentileEstAggregationFunction(ExpressionContext expression, double percentile) { - super(expression); + public PercentileEstAggregationFunction(ExpressionContext expression, double percentile, + boolean nullHandlingEnabled) { + super(expression, nullHandlingEnabled); _version = 1; _percentile = percentile; } @@ -57,12 +58,6 @@ public AggregationFunctionType getType() { return AggregationFunctionType.PERCENTILEEST; } - @Override - public String getColumnName() { - return _version == 0 ? AggregationFunctionType.PERCENTILEEST.getName() + (int) _percentile + "_" + _expression - : AggregationFunctionType.PERCENTILEEST.getName() + _percentile + "_" + _expression; - } - @Override public String getResultColumnName() { return _version == 0 ? AggregationFunctionType.PERCENTILEEST.getName().toLowerCase() + (int) _percentile + "(" @@ -87,24 +82,30 @@ public void aggregate(int length, AggregationResultHolder aggregationResultHolde if (blockValSet.getValueType() != DataType.BYTES) { long[] longValues = blockValSet.getLongValuesSV(); QuantileDigest quantileDigest = getDefaultQuantileDigest(aggregationResultHolder); - for (int i = 0; i < length; i++) { - quantileDigest.add(longValues[i]); - } + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + quantileDigest.add(longValues[i]); + } + }); } else { // Serialized QuantileDigest byte[][] bytesValues = blockValSet.getBytesValuesSV(); - QuantileDigest quantileDigest = aggregationResultHolder.getResult(); - if (quantileDigest != null) { - for (int i = 0; i < length; i++) { - quantileDigest.merge(ObjectSerDeUtils.QUANTILE_DIGEST_SER_DE.deserialize(bytesValues[i])); + foldNotNull(length, blockValSet, (QuantileDigest) aggregationResultHolder.getResult(), (quantile, from, toEx) -> { + int start; + QuantileDigest quantileDigest; + if (quantile != null) { + start = from; + quantileDigest = quantile; + } else { + start = from + 1; + quantileDigest = ObjectSerDeUtils.QUANTILE_DIGEST_SER_DE.deserialize(bytesValues[from]); + aggregationResultHolder.setValue(quantileDigest); } - } else { - quantileDigest = ObjectSerDeUtils.QUANTILE_DIGEST_SER_DE.deserialize(bytesValues[0]); - aggregationResultHolder.setValue(quantileDigest); - for (int i = 1; i < length; i++) { + for (int i = start; i < toEx; i++) { quantileDigest.merge(ObjectSerDeUtils.QUANTILE_DIGEST_SER_DE.deserialize(bytesValues[i])); } - } + return quantileDigest; + }); } } @@ -114,22 +115,26 @@ public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHol BlockValSet blockValSet = blockValSetMap.get(_expression); if (blockValSet.getValueType() != DataType.BYTES) { long[] longValues = blockValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - getDefaultQuantileDigest(groupByResultHolder, groupKeyArray[i]).add(longValues[i]); - } + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + getDefaultQuantileDigest(groupByResultHolder, groupKeyArray[i]).add(longValues[i]); + } + }); } else { // Serialized QuantileDigest byte[][] bytesValues = blockValSet.getBytesValuesSV(); - for (int i = 0; i < length; i++) { - QuantileDigest value = ObjectSerDeUtils.QUANTILE_DIGEST_SER_DE.deserialize(bytesValues[i]); - int groupKey = groupKeyArray[i]; - QuantileDigest quantileDigest = groupByResultHolder.getResult(groupKey); - if (quantileDigest != null) { - quantileDigest.merge(value); - } else { - groupByResultHolder.setValueForKey(groupKey, value); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + QuantileDigest value = ObjectSerDeUtils.QUANTILE_DIGEST_SER_DE.deserialize(bytesValues[i]); + int groupKey = groupKeyArray[i]; + QuantileDigest quantileDigest = groupByResultHolder.getResult(groupKey); + if (quantileDigest != null) { + quantileDigest.merge(value); + } else { + groupByResultHolder.setValueForKey(groupKey, value); + } } - } + }); } } @@ -139,28 +144,32 @@ public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResult BlockValSet blockValSet = blockValSetMap.get(_expression); if (blockValSet.getValueType() != DataType.BYTES) { long[] longValues = blockValSet.getLongValuesSV(); - for (int i = 0; i < length; i++) { - long value = longValues[i]; - for (int groupKey : groupKeysArray[i]) { - getDefaultQuantileDigest(groupByResultHolder, groupKey).add(value); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + long value = longValues[i]; + for (int groupKey : groupKeysArray[i]) { + getDefaultQuantileDigest(groupByResultHolder, groupKey).add(value); + } } - } + }); } else { // Serialized QuantileDigest byte[][] bytesValues = blockValSet.getBytesValuesSV(); - for (int i = 0; i < length; i++) { - QuantileDigest value = ObjectSerDeUtils.QUANTILE_DIGEST_SER_DE.deserialize(bytesValues[i]); - for (int groupKey : groupKeysArray[i]) { - QuantileDigest quantileDigest = groupByResultHolder.getResult(groupKey); - if (quantileDigest != null) { - quantileDigest.merge(value); - } else { - // Create a new QuantileDigest for the group - groupByResultHolder - .setValueForKey(groupKey, ObjectSerDeUtils.QUANTILE_DIGEST_SER_DE.deserialize(bytesValues[i])); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + QuantileDigest value = ObjectSerDeUtils.QUANTILE_DIGEST_SER_DE.deserialize(bytesValues[i]); + for (int groupKey : groupKeysArray[i]) { + QuantileDigest quantileDigest = groupByResultHolder.getResult(groupKey); + if (quantileDigest != null) { + quantileDigest.merge(value); + } else { + // Create a new QuantileDigest for the group + groupByResultHolder.setValueForKey(groupKey, + ObjectSerDeUtils.QUANTILE_DIGEST_SER_DE.deserialize(bytesValues[i])); + } } } - } + }); } } @@ -208,6 +217,9 @@ public ColumnDataType getFinalResultColumnType() { @Override public Long extractFinalResult(QuantileDigest intermediateResult) { + if (intermediateResult.getCount() == 0 && _nullHandlingEnabled) { + return null; + } return intermediateResult.getQuantile(_percentile / 100.0); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileEstMVAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileEstMVAggregationFunction.java index de206a94e8f1..5a861714620e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileEstMVAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileEstMVAggregationFunction.java @@ -30,11 +30,11 @@ public class PercentileEstMVAggregationFunction extends PercentileEstAggregationFunction { public PercentileEstMVAggregationFunction(ExpressionContext expression, int percentile) { - super(expression, percentile); + super(expression, percentile, false); } public PercentileEstMVAggregationFunction(ExpressionContext expression, double percentile) { - super(expression, percentile); + super(expression, percentile, false); } @Override @@ -42,12 +42,6 @@ public AggregationFunctionType getType() { return AggregationFunctionType.PERCENTILEESTMV; } - @Override - public String getColumnName() { - return _version == 0 ? AggregationFunctionType.PERCENTILEEST.getName() + (int) _percentile + "MV_" + _expression - : AggregationFunctionType.PERCENTILEEST.getName() + _percentile + "MV_" + _expression; - } - @Override public String getResultColumnName() { return _version == 0 ? AggregationFunctionType.PERCENTILEEST.getName().toLowerCase() + (int) _percentile + "mv(" diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileKLLAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileKLLAggregationFunction.java new file mode 100644 index 000000000000..bcf025a80149 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileKLLAggregationFunction.java @@ -0,0 +1,261 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import com.google.common.base.Preconditions; +import java.util.List; +import java.util.Map; +import org.apache.datasketches.kll.KllDoublesSketch; +import org.apache.datasketches.memory.Memory; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.ObjectAggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.ObjectGroupByResultHolder; +import org.apache.pinot.segment.spi.AggregationFunctionType; +import org.apache.pinot.spi.data.FieldSpec.DataType; + + +/** + *

+ * {@code PercentileKLLAggregationFunction} provides an approximate percentile calculator using the KLL algorithm + * from Apache DataSketches library. + *

+ *

+ * The interface is similar to plain 'Percentile' function except for the optional K value which determines + * the size, hence the accuracy of the sketch. + *

+ *

PERCENTILE_KLL(col, percentile, kValue)

+ *

E.g.:

+ *
    + *
  • PERCENTILE_KLL(col, 90)
  • + *
  • PERCENTILE_KLL(col, 99.9, 800)
  • + *
+ * + *

+ * If the column type is BYTES, the aggregation function will assume it is a serialized KllDoubleSketch and will + * attempt to deserialize it for further processing. + *

+ * + *

+ * There is a variation of the function (PERCENTILE_RAW_KLL) that returns the Base64 encoded + * sketch object to be used externally. + *

+ */ +public class PercentileKLLAggregationFunction + extends NullableSingleInputAggregationFunction> { + protected static final int DEFAULT_K_VALUE = 200; + + protected final double _percentile; + protected int _kValue; + + public PercentileKLLAggregationFunction(List arguments, boolean nullHandlingEnabled) { + super(arguments.get(0), nullHandlingEnabled); + + // Check that there are correct number of arguments + int numArguments = arguments.size(); + Preconditions.checkArgument(numArguments == 2 || numArguments == 3, + "Expecting 2 or 3 arguments for PercentileKLL function: PERCENTILE_KLL(column, percentile, k=200"); + + _percentile = arguments.get(1).getLiteral().getDoubleValue(); + Preconditions.checkArgument(_percentile >= 0 && _percentile <= 100, + "Percentile value needs to be in range 0-100, inclusive"); + + _kValue = numArguments == 3 ? arguments.get(2).getLiteral().getIntValue() : DEFAULT_K_VALUE; + } + + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.PERCENTILEKLL; + } + + @Override + public AggregationResultHolder createAggregationResultHolder() { + return new ObjectAggregationResultHolder(); + } + + @Override + public GroupByResultHolder createGroupByResultHolder(int initialCapacity, int maxCapacity) { + return new ObjectGroupByResultHolder(initialCapacity, maxCapacity); + } + + @Override + public void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + BlockValSet valueSet = blockValSetMap.get(_expression); + DataType valueType = valueSet.getValueType(); + KllDoublesSketch sketch = getOrCreateSketch(aggregationResultHolder); + + if (valueType == DataType.BYTES) { + // Assuming the column contains serialized data sketch + KllDoublesSketch[] deserializedSketches = deserializeSketches(blockValSetMap.get(_expression).getBytesValuesSV()); + forEachNotNull(length, valueSet, (from, to) -> { + for (int i = from; i < to; i++) { + sketch.merge(deserializedSketches[i]); + } + }); + } else { + double[] values = valueSet.getDoubleValuesSV(); + forEachNotNull(length, valueSet, (from, to) -> { + for (int i = from; i < to; i++) { + sketch.update(values[i]); + } + }); + } + } + + @Override + public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet valueSet = blockValSetMap.get(_expression); + DataType valueType = valueSet.getValueType(); + + if (valueType == DataType.BYTES) { + // serialized sketch + KllDoublesSketch[] deserializedSketches = deserializeSketches(blockValSetMap.get(_expression).getBytesValuesSV()); + forEachNotNull(length, valueSet, (from, to) -> { + for (int i = from; i < to; i++) { + KllDoublesSketch sketch = getOrCreateSketch(groupByResultHolder, groupKeyArray[i]); + sketch.merge(deserializedSketches[i]); + } + }); + } else { + double[] values = valueSet.getDoubleValuesSV(); + forEachNotNull(length, valueSet, (from, to) -> { + for (int i = from; i < to; i++) { + KllDoublesSketch sketch = getOrCreateSketch(groupByResultHolder, groupKeyArray[i]); + sketch.update(values[i]); + } + }); + } + } + + @Override + public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet valueSet = blockValSetMap.get(_expression); + DataType valueType = valueSet.getValueType(); + + if (valueType == DataType.BYTES) { + // serialized sketch + KllDoublesSketch[] deserializedSketches = deserializeSketches(blockValSetMap.get(_expression).getBytesValuesSV()); + forEachNotNull(length, valueSet, (from, to) -> { + for (int i = from; i < to; i++) { + for (int groupKey : groupKeysArray[i]) { + KllDoublesSketch sketch = getOrCreateSketch(groupByResultHolder, groupKey); + sketch.merge(deserializedSketches[i]); + } + } + }); + } else { + double[] values = valueSet.getDoubleValuesSV(); + forEachNotNull(length, valueSet, (from, to) -> { + for (int i = from; i < to; i++) { + for (int groupKey : groupKeysArray[i]) { + KllDoublesSketch sketch = getOrCreateSketch(groupByResultHolder, groupKey); + sketch.update(values[i]); + } + } + }); + } + } + + /** + * Extracts the sketch from the result holder or creates a new one if it does not exist. + */ + protected KllDoublesSketch getOrCreateSketch(AggregationResultHolder aggregationResultHolder) { + KllDoublesSketch sketch = aggregationResultHolder.getResult(); + if (sketch == null) { + sketch = KllDoublesSketch.newHeapInstance(_kValue); + aggregationResultHolder.setValue(sketch); + } + return sketch; + } + + /** + * Extracts the sketch from the group by result holder for key + * or creates a new one if it does not exist. + */ + protected KllDoublesSketch getOrCreateSketch(GroupByResultHolder groupByResultHolder, int groupKey) { + KllDoublesSketch sketch = groupByResultHolder.getResult(groupKey); + if (sketch == null) { + sketch = KllDoublesSketch.newHeapInstance(_kValue); + groupByResultHolder.setValueForKey(groupKey, sketch); + } + return sketch; + } + + /** + * Deserializes the sketches from the bytes. + */ + protected KllDoublesSketch[] deserializeSketches(byte[][] serializedSketches) { + KllDoublesSketch[] sketches = new KllDoublesSketch[serializedSketches.length]; + for (int i = 0; i < serializedSketches.length; i++) { + sketches[i] = KllDoublesSketch.wrap(Memory.wrap(serializedSketches[i])); + } + return sketches; + } + + @Override + public KllDoublesSketch extractAggregationResult(AggregationResultHolder aggregationResultHolder) { + return aggregationResultHolder.getResult(); + } + + @Override + public KllDoublesSketch extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { + return groupByResultHolder.getResult(groupKey); + } + + @Override + public KllDoublesSketch merge(KllDoublesSketch sketch1, KllDoublesSketch sketch2) { + KllDoublesSketch union = KllDoublesSketch.newHeapInstance(_kValue); + if (sketch1 != null) { + union.merge(sketch1); + } + if (sketch2 != null) { + union.merge(sketch2); + } + return union; + } + + @Override + public ColumnDataType getIntermediateResultColumnType() { + return ColumnDataType.OBJECT; + } + + @Override + public ColumnDataType getFinalResultColumnType() { + return ColumnDataType.DOUBLE; + } + + @Override + public String getResultColumnName() { + return AggregationFunctionType.PERCENTILEKLL.getName().toLowerCase() + "(" + _expression + ", " + _percentile + ")"; + } + + @Override + public Comparable extractFinalResult(KllDoublesSketch sketch) { + if (sketch.isEmpty() && _nullHandlingEnabled) { + return null; + } + return sketch.getQuantile(_percentile / 100); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileKLLMVAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileKLLMVAggregationFunction.java new file mode 100644 index 000000000000..26af8dea447d --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileKLLMVAggregationFunction.java @@ -0,0 +1,123 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import java.util.List; +import java.util.Map; +import org.apache.datasketches.kll.KllDoublesSketch; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.segment.spi.AggregationFunctionType; +import org.apache.pinot.spi.data.FieldSpec.DataType; + + +public class PercentileKLLMVAggregationFunction extends PercentileKLLAggregationFunction { + + public PercentileKLLMVAggregationFunction(List arguments) { + super(arguments, false); + } + + @Override + public void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + BlockValSet valueSet = blockValSetMap.get(_expression); + DataType valueType = valueSet.getValueType(); + KllDoublesSketch sketch = getOrCreateSketch(aggregationResultHolder); + + if (valueType == DataType.BYTES) { + // Assuming the column contains serialized data sketches + KllDoublesSketch[] deserializedSketches = deserializeSketches(blockValSetMap.get(_expression).getBytesValuesSV()); + for (int i = 0; i < length; i++) { + sketch.merge(deserializedSketches[i]); + } + } else { + double[][] values = valueSet.getDoubleValuesMV(); + for (int i = 0; i < length; i++) { + for (double val : values[i]) { + sketch.update(val); + } + } + } + } + + @Override + public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet valueSet = blockValSetMap.get(_expression); + DataType valueType = valueSet.getValueType(); + + if (valueType == DataType.BYTES) { + // serialized sketch + KllDoublesSketch[] deserializedSketches = deserializeSketches(blockValSetMap.get(_expression).getBytesValuesSV()); + for (int i = 0; i < length; i++) { + KllDoublesSketch sketch = getOrCreateSketch(groupByResultHolder, groupKeyArray[i]); + sketch.merge(deserializedSketches[i]); + } + } else { + double[][] values = valueSet.getDoubleValuesMV(); + for (int i = 0; i < length; i++) { + KllDoublesSketch sketch = getOrCreateSketch(groupByResultHolder, groupKeyArray[i]); + for (double val : values[i]) { + sketch.update(val); + } + } + } + } + + @Override + public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet valueSet = blockValSetMap.get(_expression); + DataType valueType = valueSet.getValueType(); + + if (valueType == DataType.BYTES) { + // serialized sketch + KllDoublesSketch[] deserializedSketches = deserializeSketches(blockValSetMap.get(_expression).getBytesValuesSV()); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + KllDoublesSketch sketch = this.getOrCreateSketch(groupByResultHolder, groupKey); + sketch.merge(deserializedSketches[i]); + } + } + } else { + double[][] values = valueSet.getDoubleValuesMV(); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + KllDoublesSketch sketch = getOrCreateSketch(groupByResultHolder, groupKey); + for (double val : values[i]) { + sketch.update(val); + } + } + } + } + } + + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.PERCENTILEKLLMV; + } + + @Override + public String getResultColumnName() { + return AggregationFunctionType.PERCENTILEKLLMV.getName().toLowerCase() + "(" + _expression + ", " + _percentile + + ")"; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileMVAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileMVAggregationFunction.java index 33d1ff945ad8..620763ea7599 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileMVAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileMVAggregationFunction.java @@ -30,11 +30,11 @@ public class PercentileMVAggregationFunction extends PercentileAggregationFunction { public PercentileMVAggregationFunction(ExpressionContext expression, int percentile) { - super(expression, percentile); + super(expression, percentile, false); } public PercentileMVAggregationFunction(ExpressionContext expression, double percentile) { - super(expression, percentile); + super(expression, percentile, false); } @Override @@ -42,12 +42,6 @@ public AggregationFunctionType getType() { return AggregationFunctionType.PERCENTILEMV; } - @Override - public String getColumnName() { - return _version == 0 ? AggregationFunctionType.PERCENTILE.getName() + (int) _percentile + "MV_" + _expression - : AggregationFunctionType.PERCENTILE.getName() + _percentile + "MV_" + _expression; - } - @Override public String getResultColumnName() { return _version == 0 ? AggregationFunctionType.PERCENTILE.getName().toLowerCase() + (int) _percentile + "mv(" diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileRawEstAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileRawEstAggregationFunction.java index 1922d74c515b..04787e7d559b 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileRawEstAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileRawEstAggregationFunction.java @@ -37,12 +37,14 @@ public class PercentileRawEstAggregationFunction extends BaseSingleInputAggregationFunction { private final PercentileEstAggregationFunction _percentileEstAggregationFunction; - public PercentileRawEstAggregationFunction(ExpressionContext expressionContext, double percentile) { - this(expressionContext, new PercentileEstAggregationFunction(expressionContext, percentile)); + public PercentileRawEstAggregationFunction(ExpressionContext expressionContext, double percentile, + boolean nullHandlingEnabled) { + this(expressionContext, new PercentileEstAggregationFunction(expressionContext, percentile, nullHandlingEnabled)); } - public PercentileRawEstAggregationFunction(ExpressionContext expressionContext, int percentile) { - this(expressionContext, new PercentileEstAggregationFunction(expressionContext, percentile)); + public PercentileRawEstAggregationFunction(ExpressionContext expressionContext, int percentile, + boolean nullHandlingEnabled) { + this(expressionContext, new PercentileEstAggregationFunction(expressionContext, percentile, nullHandlingEnabled)); } protected PercentileRawEstAggregationFunction(ExpressionContext expression, @@ -56,15 +58,6 @@ public AggregationFunctionType getType() { return AggregationFunctionType.PERCENTILERAWEST; } - @Override - public String getColumnName() { - final double percentile = _percentileEstAggregationFunction._percentile; - final int version = _percentileEstAggregationFunction._version; - final String type = getType().getName(); - - return version == 0 ? type + (int) percentile + "_" + _expression : type + percentile + "_" + _expression; - } - @Override public String getResultColumnName() { final double percentile = _percentileEstAggregationFunction._percentile; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileRawKLLAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileRawKLLAggregationFunction.java new file mode 100644 index 000000000000..7e88cf009d88 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileRawKLLAggregationFunction.java @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import java.util.List; +import org.apache.datasketches.kll.KllDoublesSketch; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.segment.local.customobject.SerializedKLL; +import org.apache.pinot.segment.spi.AggregationFunctionType; + + +public class PercentileRawKLLAggregationFunction extends PercentileKLLAggregationFunction { + + public PercentileRawKLLAggregationFunction(List arguments, boolean nullHandlingEnabled) { + super(arguments, nullHandlingEnabled); + } + + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.PERCENTILERAWKLL; + } + + @Override + public ColumnDataType getFinalResultColumnType() { + return ColumnDataType.STRING; + } + + @Override + public String getResultColumnName() { + return AggregationFunctionType.PERCENTILERAWKLL.getName().toLowerCase() + "(" + _expression + ", " + _percentile + + ")"; + } + + @Override + public SerializedKLL extractFinalResult(KllDoublesSketch sketch) { + return new SerializedKLL(sketch, _percentile); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileRawKLLMVAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileRawKLLMVAggregationFunction.java new file mode 100644 index 000000000000..80a6a3ac6b0b --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileRawKLLMVAggregationFunction.java @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import java.util.List; +import org.apache.datasketches.kll.KllDoublesSketch; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.segment.local.customobject.SerializedKLL; +import org.apache.pinot.segment.spi.AggregationFunctionType; + + +public class PercentileRawKLLMVAggregationFunction extends PercentileKLLMVAggregationFunction { + + public PercentileRawKLLMVAggregationFunction(List arguments) { + super(arguments); + } + + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.PERCENTILERAWKLLMV; + } + + @Override + public ColumnDataType getFinalResultColumnType() { + return ColumnDataType.STRING; + } + + @Override + public String getResultColumnName() { + return AggregationFunctionType.PERCENTILERAWKLLMV.getName().toLowerCase() + "(" + _expression + ", " + _percentile + + ")"; + } + + @Override + public SerializedKLL extractFinalResult(KllDoublesSketch sketch) { + return new SerializedKLL(sketch, _percentile); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileRawTDigestAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileRawTDigestAggregationFunction.java index 0efbef2b4780..fc618027a5fa 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileRawTDigestAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileRawTDigestAggregationFunction.java @@ -37,12 +37,22 @@ public class PercentileRawTDigestAggregationFunction extends BaseSingleInputAggregationFunction { private final PercentileTDigestAggregationFunction _percentileTDigestAggregationFunction; - public PercentileRawTDigestAggregationFunction(ExpressionContext expressionContext, int percentile) { - this(expressionContext, new PercentileTDigestAggregationFunction(expressionContext, percentile)); + public PercentileRawTDigestAggregationFunction(ExpressionContext expressionContext, int percentile, + boolean nullHandlingEnabled) { + this(expressionContext, new PercentileTDigestAggregationFunction(expressionContext, percentile, + nullHandlingEnabled)); } - public PercentileRawTDigestAggregationFunction(ExpressionContext expressionContext, double percentile) { - this(expressionContext, new PercentileTDigestAggregationFunction(expressionContext, percentile)); + public PercentileRawTDigestAggregationFunction(ExpressionContext expressionContext, double percentile, + boolean nullHandlingEnabled) { + this(expressionContext, new PercentileTDigestAggregationFunction(expressionContext, percentile, + nullHandlingEnabled)); + } + + public PercentileRawTDigestAggregationFunction(ExpressionContext expressionContext, double percentile, + int compressionFactor, boolean nullHandlingEnabled) { + this(expressionContext, new PercentileTDigestAggregationFunction(expressionContext, percentile, compressionFactor, + nullHandlingEnabled)); } protected PercentileRawTDigestAggregationFunction(ExpressionContext expression, @@ -56,23 +66,17 @@ public AggregationFunctionType getType() { return AggregationFunctionType.PERCENTILERAWTDIGEST; } - @Override - public String getColumnName() { - final double percentile = _percentileTDigestAggregationFunction._percentile; - final int version = _percentileTDigestAggregationFunction._version; - final String type = getType().getName(); - - return version == 0 ? type + (int) percentile + "_" + _expression : type + percentile + "_" + _expression; - } - @Override public String getResultColumnName() { final double percentile = _percentileTDigestAggregationFunction._percentile; + final int compressionFactor = _percentileTDigestAggregationFunction._compressionFactor; final int version = _percentileTDigestAggregationFunction._version; final String type = getType().getName().toLowerCase(); return version == 0 ? type + (int) percentile + "(" + _expression + ")" - : type + "(" + _expression + ", " + percentile + ")"; + : (((compressionFactor == PercentileTDigestAggregationFunction.DEFAULT_TDIGEST_COMPRESSION)) + ? (type + "(" + _expression + ", " + percentile + ")") + : (type + "(" + _expression + ", " + percentile + ", " + compressionFactor + ")")); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileRawTDigestMVAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileRawTDigestMVAggregationFunction.java index 4cf5cbbb8faa..e6581ce478d4 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileRawTDigestMVAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileRawTDigestMVAggregationFunction.java @@ -36,6 +36,12 @@ public PercentileRawTDigestMVAggregationFunction(ExpressionContext expressionCon super(expressionContext, new PercentileTDigestMVAggregationFunction(expressionContext, percentile)); } + public PercentileRawTDigestMVAggregationFunction(ExpressionContext expressionContext, double percentile, + int compressionFactor) { + super(expressionContext, + new PercentileTDigestMVAggregationFunction(expressionContext, percentile, compressionFactor)); + } + @Override public AggregationFunctionType getType() { return AggregationFunctionType.PERCENTILERAWTDIGESTMV; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileSmartTDigestAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileSmartTDigestAggregationFunction.java index 2697690a60ed..20d5372ca56f 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileSmartTDigestAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileSmartTDigestAggregationFunction.java @@ -50,24 +50,24 @@ * - compression: Compression for the converted TDigest, 100 by default. * Example of third argument: 'threshold=10000;compression=50' */ -public class PercentileSmartTDigestAggregationFunction extends BaseSingleInputAggregationFunction { +public class PercentileSmartTDigestAggregationFunction extends NullableSingleInputAggregationFunction { private static final double DEFAULT_FINAL_RESULT = Double.NEGATIVE_INFINITY; private final double _percentile; private final int _threshold; private final int _compression; - public PercentileSmartTDigestAggregationFunction(List arguments) { - super(arguments.get(0)); + public PercentileSmartTDigestAggregationFunction(List arguments, boolean nullHandlingEnabled) { + super(arguments.get(0), nullHandlingEnabled); try { - _percentile = Double.parseDouble(arguments.get(1).getLiteralString()); + _percentile = arguments.get(1).getLiteral().getDoubleValue(); } catch (Exception e) { throw new IllegalArgumentException( "Second argument of PERCENTILE_SMART_TDIGEST aggregation function must be a double literal (percentile)"); } Preconditions.checkArgument(_percentile >= 0 && _percentile <= 100, "Invalid percentile: %s", _percentile); if (arguments.size() > 2) { - Parameters parameters = new Parameters(arguments.get(2).getLiteralString()); + Parameters parameters = new Parameters(arguments.get(2).getLiteral().getStringValue()); _compression = parameters._compression; _threshold = parameters._threshold; } else { @@ -93,11 +93,6 @@ public AggregationFunctionType getType() { return AggregationFunctionType.PERCENTILESMARTTDIGEST; } - @Override - public String getColumnName() { - return AggregationFunctionType.PERCENTILESMARTTDIGEST.getName() + _percentile + "_" + _expression; - } - @Override public String getResultColumnName() { return AggregationFunctionType.PERCENTILESMARTTDIGEST.getName().toLowerCase() + "(" + _expression + ", " @@ -133,39 +128,53 @@ private static void validateValueType(BlockValSet blockValSet) { blockValSet.isSingleValue() ? "" : "_MV"); } - private static void aggregateIntoTDigest(int length, AggregationResultHolder aggregationResultHolder, + private void aggregateIntoTDigest(int length, AggregationResultHolder aggregationResultHolder, BlockValSet blockValSet) { TDigest tDigest = aggregationResultHolder.getResult(); if (blockValSet.isSingleValue()) { double[] doubleValues = blockValSet.getDoubleValuesSV(); - for (int i = 0; i < length; i++) { - tDigest.add(doubleValues[i]); - } + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + tDigest.add(doubleValues[i]); + } + }); } else { double[][] doubleValues = blockValSet.getDoubleValuesMV(); - for (int i = 0; i < length; i++) { - for (double value : doubleValues[i]) { - tDigest.add(value); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + for (double value : doubleValues[i]) { + tDigest.add(value); + } } - } + }); } } - private void aggregateIntoValueList(int length, AggregationResultHolder aggregationResultHolder, - BlockValSet blockValSet) { + private DoubleArrayList getOrCreateList(int length, AggregationResultHolder aggregationResultHolder) { DoubleArrayList valueList = aggregationResultHolder.getResult(); if (valueList == null) { valueList = new DoubleArrayList(length); aggregationResultHolder.setValue(valueList); } + return valueList; + } + + private void aggregateIntoValueList(int length, AggregationResultHolder aggregationResultHolder, + BlockValSet blockValSet) { + DoubleArrayList valueList = getOrCreateList(length, aggregationResultHolder); if (blockValSet.isSingleValue()) { double[] doubleValues = blockValSet.getDoubleValuesSV(); - valueList.addElements(valueList.size(), doubleValues, 0, length); + forEachNotNull(length, blockValSet, (from, toEx) -> + valueList.addElements(valueList.size(), doubleValues, from, toEx - from) + ); } else { double[][] doubleValues = blockValSet.getDoubleValuesMV(); - for (int i = 0; i < length; i++) { - valueList.addElements(valueList.size(), doubleValues[i]); - } + forEachNotNull(length, blockValSet, (from, toEx) -> { + for (int i = 0; i < length; i++) { + valueList.addElements(valueList.size(), doubleValues[i]); + } + } + ); } if (valueList.size() > _threshold) { aggregationResultHolder.setValue(convertValueListToTDigest(valueList)); @@ -188,16 +197,20 @@ public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHol validateValueType(blockValSet); if (blockValSet.isSingleValue()) { double[] doubleValues = blockValSet.getDoubleValuesSV(); - for (int i = 0; i < length; i++) { - DoubleArrayList valueList = getValueList(groupByResultHolder, groupKeyArray[i]); - valueList.add(doubleValues[i]); - } + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + DoubleArrayList valueList = getValueList(groupByResultHolder, groupKeyArray[i]); + valueList.add(doubleValues[i]); + } + }); } else { double[][] doubleValues = blockValSet.getDoubleValuesMV(); - for (int i = 0; i < length; i++) { - DoubleArrayList valueList = getValueList(groupByResultHolder, groupKeyArray[i]); - valueList.addElements(valueList.size(), doubleValues[i]); - } + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + DoubleArrayList valueList = getValueList(groupByResultHolder, groupKeyArray[i]); + valueList.addElements(valueList.size(), doubleValues[i]); + } + }); } } @@ -217,19 +230,23 @@ public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResult validateValueType(blockValSet); if (blockValSet.isSingleValue()) { double[] doubleValues = blockValSet.getDoubleValuesSV(); - for (int i = 0; i < length; i++) { - for (int groupKey : groupKeysArray[i]) { - getValueList(groupByResultHolder, groupKey).add(doubleValues[i]); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + for (int groupKey : groupKeysArray[i]) { + getValueList(groupByResultHolder, groupKey).add(doubleValues[i]); + } } - } + }); } else { double[][] doubleValues = blockValSet.getDoubleValuesMV(); - for (int i = 0; i < length; i++) { - for (int groupKey : groupKeysArray[i]) { - DoubleArrayList valueList = getValueList(groupByResultHolder, groupKey); - valueList.addElements(valueList.size(), doubleValues[i]); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + for (int groupKey : groupKeysArray[i]) { + DoubleArrayList valueList = getValueList(groupByResultHolder, groupKey); + valueList.addElements(valueList.size(), doubleValues[i]); + } } - } + }); } } @@ -290,7 +307,11 @@ public Double extractFinalResult(Object intermediateResult) { DoubleArrayList valueList = (DoubleArrayList) intermediateResult; int size = valueList.size(); if (size == 0) { - return DEFAULT_FINAL_RESULT; + if (_nullHandlingEnabled) { + return null; + } else { + return DEFAULT_FINAL_RESULT; + } } else { double[] values = valueList.elements(); Arrays.sort(values, 0, size); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileTDigestAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileTDigestAggregationFunction.java index 9ba510133939..c831e52d2248 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileTDigestAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileTDigestAggregationFunction.java @@ -34,44 +34,59 @@ /** * TDigest based Percentile aggregation function. + * + * TODO: Decided not to support custom compression for version 0 queries as it seems to be the older syntax and requires + * extra handling for two argument PERCENTILE functions to assess if v0 or v1. This can be revisited later if the + * need arises */ -public class PercentileTDigestAggregationFunction extends BaseSingleInputAggregationFunction { +public class PercentileTDigestAggregationFunction extends NullableSingleInputAggregationFunction { public static final int DEFAULT_TDIGEST_COMPRESSION = 100; - //version 0 functions specified in the of form PERCENTILETDIGEST<2-digits>(column) - //version 1 functions of form PERCENTILETDIGEST(column, <2-digits>.<16-digits>) + // version 0 functions specified in the of form PERCENTILETDIGEST<2-digits>(column). Uses default compression of 100 + // version 1 functions of form PERCENTILETDIGEST(column, <2-digits>.<16-digits>, [optional]) protected final int _version; protected final double _percentile; + protected final int _compressionFactor; - public PercentileTDigestAggregationFunction(ExpressionContext expression, int percentile) { - super(expression); + public PercentileTDigestAggregationFunction(ExpressionContext expression, int percentile, + boolean nullHandlingEnabled) { + super(expression, nullHandlingEnabled); _version = 0; _percentile = percentile; + _compressionFactor = DEFAULT_TDIGEST_COMPRESSION; } - public PercentileTDigestAggregationFunction(ExpressionContext expression, double percentile) { - super(expression); + public PercentileTDigestAggregationFunction(ExpressionContext expression, double percentile, + boolean nullHandlingEnabled) { + super(expression, nullHandlingEnabled); _version = 1; _percentile = percentile; + _compressionFactor = DEFAULT_TDIGEST_COMPRESSION; } - @Override - public AggregationFunctionType getType() { - return AggregationFunctionType.PERCENTILETDIGEST; + public PercentileTDigestAggregationFunction(ExpressionContext expression, double percentile, + int compressionFactor, boolean nullHandlingEnabled) { + super(expression, nullHandlingEnabled); + _version = 1; + _percentile = percentile; + _compressionFactor = compressionFactor; } @Override - public String getColumnName() { - return _version == 0 ? AggregationFunctionType.PERCENTILETDIGEST.getName() + (int) _percentile + "_" + _expression - : AggregationFunctionType.PERCENTILETDIGEST.getName() + _percentile + "_" + _expression; + public AggregationFunctionType getType() { + return AggregationFunctionType.PERCENTILETDIGEST; } @Override public String getResultColumnName() { return _version == 0 ? AggregationFunctionType.PERCENTILETDIGEST.getName().toLowerCase() + (int) _percentile + "(" + _expression + ")" - : AggregationFunctionType.PERCENTILETDIGEST.getName().toLowerCase() + "(" + _expression + ", " + _percentile - + ")"; + : ((_compressionFactor == DEFAULT_TDIGEST_COMPRESSION) + ? (AggregationFunctionType.PERCENTILETDIGEST.getName().toLowerCase() + "(" + _expression + ", " + + _percentile + ")") + : (AggregationFunctionType.PERCENTILETDIGEST.getName().toLowerCase() + "(" + _expression + ", " + + _percentile + ", " + _compressionFactor + ")") + ); } @Override @@ -90,25 +105,29 @@ public void aggregate(int length, AggregationResultHolder aggregationResultHolde BlockValSet blockValSet = blockValSetMap.get(_expression); if (blockValSet.getValueType() != DataType.BYTES) { double[] doubleValues = blockValSet.getDoubleValuesSV(); - TDigest tDigest = getDefaultTDigest(aggregationResultHolder); - for (int i = 0; i < length; i++) { - tDigest.add(doubleValues[i]); - } + TDigest tDigest = getDefaultTDigest(aggregationResultHolder, _compressionFactor); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + tDigest.add(doubleValues[i]); + } + }); } else { // Serialized TDigest byte[][] bytesValues = blockValSet.getBytesValuesSV(); - TDigest tDigest = aggregationResultHolder.getResult(); - if (tDigest != null) { - for (int i = 0; i < length; i++) { - tDigest.add(ObjectSerDeUtils.TDIGEST_SER_DE.deserialize(bytesValues[i])); - } - } else { - tDigest = ObjectSerDeUtils.TDIGEST_SER_DE.deserialize(bytesValues[0]); - aggregationResultHolder.setValue(tDigest); - for (int i = 1; i < length; i++) { - tDigest.add(ObjectSerDeUtils.TDIGEST_SER_DE.deserialize(bytesValues[i])); + foldNotNull(length, blockValSet, (TDigest) aggregationResultHolder.getResult(), (tDigest, from, toEx) -> { + if (tDigest != null) { + for (int i = from; i < toEx; i++) { + tDigest.add(ObjectSerDeUtils.TDIGEST_SER_DE.deserialize(bytesValues[i])); + } + } else { + tDigest = ObjectSerDeUtils.TDIGEST_SER_DE.deserialize(bytesValues[0]); + aggregationResultHolder.setValue(tDigest); + for (int i = 1; i < length; i++) { + tDigest.add(ObjectSerDeUtils.TDIGEST_SER_DE.deserialize(bytesValues[i])); + } } - } + return tDigest; + }); } } @@ -118,22 +137,26 @@ public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHol BlockValSet blockValSet = blockValSetMap.get(_expression); if (blockValSet.getValueType() != DataType.BYTES) { double[] doubleValues = blockValSet.getDoubleValuesSV(); - for (int i = 0; i < length; i++) { - getDefaultTDigest(groupByResultHolder, groupKeyArray[i]).add(doubleValues[i]); - } + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + getDefaultTDigest(groupByResultHolder, groupKeyArray[i], _compressionFactor).add(doubleValues[i]); + } + }); } else { // Serialized TDigest byte[][] bytesValues = blockValSet.getBytesValuesSV(); - for (int i = 0; i < length; i++) { - TDigest value = ObjectSerDeUtils.TDIGEST_SER_DE.deserialize(bytesValues[i]); - int groupKey = groupKeyArray[i]; - TDigest tDigest = groupByResultHolder.getResult(groupKey); - if (tDigest != null) { - tDigest.add(value); - } else { - groupByResultHolder.setValueForKey(groupKey, value); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + TDigest value = ObjectSerDeUtils.TDIGEST_SER_DE.deserialize(bytesValues[i]); + int groupKey = groupKeyArray[i]; + TDigest tDigest = groupByResultHolder.getResult(groupKey); + if (tDigest != null) { + tDigest.add(value); + } else { + groupByResultHolder.setValueForKey(groupKey, value); + } } - } + }); } } @@ -143,27 +166,31 @@ public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResult BlockValSet blockValSet = blockValSetMap.get(_expression); if (blockValSet.getValueType() != DataType.BYTES) { double[] doubleValues = blockValSet.getDoubleValuesSV(); - for (int i = 0; i < length; i++) { - double value = doubleValues[i]; - for (int groupKey : groupKeysArray[i]) { - getDefaultTDigest(groupByResultHolder, groupKey).add(value); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + double value = doubleValues[i]; + for (int groupKey : groupKeysArray[i]) { + getDefaultTDigest(groupByResultHolder, groupKey, _compressionFactor).add(value); + } } - } + }); } else { // Serialized QuantileDigest byte[][] bytesValues = blockValSet.getBytesValuesSV(); - for (int i = 0; i < length; i++) { - TDigest value = ObjectSerDeUtils.TDIGEST_SER_DE.deserialize(bytesValues[i]); - for (int groupKey : groupKeysArray[i]) { - TDigest tDigest = groupByResultHolder.getResult(groupKey); - if (tDigest != null) { - tDigest.add(value); - } else { - // Create a new TDigest for the group - groupByResultHolder.setValueForKey(groupKey, ObjectSerDeUtils.TDIGEST_SER_DE.deserialize(bytesValues[i])); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + TDigest value = ObjectSerDeUtils.TDIGEST_SER_DE.deserialize(bytesValues[i]); + for (int groupKey : groupKeysArray[i]) { + TDigest tDigest = groupByResultHolder.getResult(groupKey); + if (tDigest != null) { + tDigest.add(value); + } else { + // Create a new TDigest for the group + groupByResultHolder.setValueForKey(groupKey, ObjectSerDeUtils.TDIGEST_SER_DE.deserialize(bytesValues[i])); + } } } - } + }); } } @@ -171,7 +198,7 @@ public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResult public TDigest extractAggregationResult(AggregationResultHolder aggregationResultHolder) { TDigest tDigest = aggregationResultHolder.getResult(); if (tDigest == null) { - return TDigest.createMergingDigest(DEFAULT_TDIGEST_COMPRESSION); + return TDigest.createMergingDigest(_compressionFactor); } else { return tDigest; } @@ -181,7 +208,7 @@ public TDigest extractAggregationResult(AggregationResultHolder aggregationResul public TDigest extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { TDigest tDigest = groupByResultHolder.getResult(groupKey); if (tDigest == null) { - return TDigest.createMergingDigest(DEFAULT_TDIGEST_COMPRESSION); + return TDigest.createMergingDigest(_compressionFactor); } else { return tDigest; } @@ -218,12 +245,13 @@ public Double extractFinalResult(TDigest intermediateResult) { * Returns the TDigest from the result holder or creates a new one with default compression if it does not exist. * * @param aggregationResultHolder Result holder + * @param compressionFactor Compression factor to use for the TDigest * @return TDigest from the result holder */ - protected static TDigest getDefaultTDigest(AggregationResultHolder aggregationResultHolder) { + protected static TDigest getDefaultTDigest(AggregationResultHolder aggregationResultHolder, int compressionFactor) { TDigest tDigest = aggregationResultHolder.getResult(); if (tDigest == null) { - tDigest = TDigest.createMergingDigest(DEFAULT_TDIGEST_COMPRESSION); + tDigest = TDigest.createMergingDigest(compressionFactor); aggregationResultHolder.setValue(tDigest); } return tDigest; @@ -234,12 +262,14 @@ protected static TDigest getDefaultTDigest(AggregationResultHolder aggregationRe * * @param groupByResultHolder Result holder * @param groupKey Group key for which to return the TDigest + * @param compressionFactor Compression factor to use for the TDigest * @return TDigest for the group key */ - protected static TDigest getDefaultTDigest(GroupByResultHolder groupByResultHolder, int groupKey) { + protected static TDigest getDefaultTDigest(GroupByResultHolder groupByResultHolder, int groupKey, + int compressionFactor) { TDigest tDigest = groupByResultHolder.getResult(groupKey); if (tDigest == null) { - tDigest = TDigest.createMergingDigest(DEFAULT_TDIGEST_COMPRESSION); + tDigest = TDigest.createMergingDigest(compressionFactor); groupByResultHolder.setValueForKey(groupKey, tDigest); } return tDigest; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileTDigestMVAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileTDigestMVAggregationFunction.java index 122748a2e525..a6b7884e6e87 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileTDigestMVAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/PercentileTDigestMVAggregationFunction.java @@ -30,37 +30,39 @@ public class PercentileTDigestMVAggregationFunction extends PercentileTDigestAggregationFunction { public PercentileTDigestMVAggregationFunction(ExpressionContext expression, int percentile) { - super(expression, percentile); + super(expression, percentile, false); } public PercentileTDigestMVAggregationFunction(ExpressionContext expression, double percentile) { - super(expression, percentile); + super(expression, percentile, false); } - @Override - public AggregationFunctionType getType() { - return AggregationFunctionType.PERCENTILETDIGESTMV; + public PercentileTDigestMVAggregationFunction(ExpressionContext expression, double percentile, + int compressionFactor) { + super(expression, percentile, compressionFactor, false); } @Override - public String getColumnName() { - return _version == 0 ? AggregationFunctionType.PERCENTILETDIGEST.getName() + (int) _percentile + "MV_" + _expression - : AggregationFunctionType.PERCENTILETDIGEST.getName() + _percentile + "MV_" + _expression; + public AggregationFunctionType getType() { + return AggregationFunctionType.PERCENTILETDIGESTMV; } @Override public String getResultColumnName() { return _version == 0 ? AggregationFunctionType.PERCENTILETDIGEST.getName().toLowerCase() + (int) _percentile + "mv(" + _expression + ")" - : AggregationFunctionType.PERCENTILETDIGEST.getName().toLowerCase() + "mv(" + _expression + ", " + _percentile - + ")"; + : ((_compressionFactor == PercentileTDigestAggregationFunction.DEFAULT_TDIGEST_COMPRESSION) + ? (AggregationFunctionType.PERCENTILETDIGEST.getName().toLowerCase() + "mv(" + _expression + ", " + + _percentile + ")") + : (AggregationFunctionType.PERCENTILETDIGEST.getName().toLowerCase() + "mv(" + _expression + ", " + + _percentile + ", " + _compressionFactor + ")")); } @Override public void aggregate(int length, AggregationResultHolder aggregationResultHolder, Map blockValSetMap) { double[][] valuesArray = blockValSetMap.get(_expression).getDoubleValuesMV(); - TDigest tDigest = getDefaultTDigest(aggregationResultHolder); + TDigest tDigest = getDefaultTDigest(aggregationResultHolder, _compressionFactor); for (int i = 0; i < length; i++) { for (double value : valuesArray[i]) { tDigest.add(value); @@ -73,7 +75,7 @@ public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHol Map blockValSetMap) { double[][] valuesArray = blockValSetMap.get(_expression).getDoubleValuesMV(); for (int i = 0; i < length; i++) { - TDigest tDigest = getDefaultTDigest(groupByResultHolder, groupKeyArray[i]); + TDigest tDigest = getDefaultTDigest(groupByResultHolder, groupKeyArray[i], _compressionFactor); for (double value : valuesArray[i]) { tDigest.add(value); } @@ -87,7 +89,7 @@ public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResult for (int i = 0; i < length; i++) { double[] values = valuesArray[i]; for (int groupKey : groupKeysArray[i]) { - TDigest tDigest = getDefaultTDigest(groupByResultHolder, groupKey); + TDigest tDigest = getDefaultTDigest(groupByResultHolder, groupKey, _compressionFactor); for (double value : values) { tDigest.add(value); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SegmentPartitionedDistinctCountAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SegmentPartitionedDistinctCountAggregationFunction.java index f273fa8b615a..dcf05cc2edf1 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SegmentPartitionedDistinctCountAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SegmentPartitionedDistinctCountAggregationFunction.java @@ -23,6 +23,7 @@ import it.unimi.dsi.fastutil.longs.LongOpenHashSet; import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; import java.util.Collection; +import java.util.List; import java.util.Map; import javax.annotation.Nullable; import org.apache.pinot.common.request.context.ExpressionContext; @@ -48,8 +49,8 @@ */ public class SegmentPartitionedDistinctCountAggregationFunction extends BaseSingleInputAggregationFunction { - public SegmentPartitionedDistinctCountAggregationFunction(ExpressionContext expression) { - super(expression); + public SegmentPartitionedDistinctCountAggregationFunction(List arguments) { + super(verifySingleArgument(arguments, "SEGMENT_PARTITIONED_DISTINCT_COUNT")); } @Override @@ -327,6 +328,11 @@ public Long extractFinalResult(Long intermediateResult) { return intermediateResult; } + @Override + public Long mergeFinalResult(Long finalResult1, Long finalResult2) { + return finalResult1 + finalResult2; + } + /** * Helper method to set an INT value for the given group key into the result holder. */ diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/StUnionAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/StUnionAggregationFunction.java index 0e0ff6f4934e..8b6a6ed45a6e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/StUnionAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/StUnionAggregationFunction.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.core.query.aggregation.function; +import java.util.List; import java.util.Map; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.utils.DataSchema; @@ -35,13 +36,8 @@ public class StUnionAggregationFunction extends BaseSingleInputAggregationFunction { - /** - * Constructor for the class. - * - * @param expression Expression to aggregate on. - */ - public StUnionAggregationFunction(ExpressionContext expression) { - super(expression); + public StUnionAggregationFunction(List arguments) { + super(verifySingleArgument(arguments, "ST_UNION")); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SumAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SumAggregationFunction.java index 04f63bd4e274..b90dcc205105 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SumAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SumAggregationFunction.java @@ -19,6 +19,7 @@ package org.apache.pinot.core.query.aggregation.function; import java.math.BigDecimal; +import java.util.List; import java.util.Map; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; @@ -37,11 +38,11 @@ public class SumAggregationFunction extends BaseSingleInputAggregationFunction arguments, boolean nullHandlingEnabled) { + this(verifySingleArgument(arguments, "SUM"), nullHandlingEnabled); } - public SumAggregationFunction(ExpressionContext expression, boolean nullHandlingEnabled) { + protected SumAggregationFunction(ExpressionContext expression, boolean nullHandlingEnabled) { super(expression); _nullHandlingEnabled = nullHandlingEnabled; } @@ -293,4 +294,9 @@ public ColumnDataType getFinalResultColumnType() { public Double extractFinalResult(Double intermediateResult) { return intermediateResult; } + + @Override + public Double mergeFinalResult(Double finalResult1, Double finalResult2) { + return merge(finalResult1, finalResult2); + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SumMVAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SumMVAggregationFunction.java index eb39d3adf0ec..4dfdf5020222 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SumMVAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SumMVAggregationFunction.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.core.query.aggregation.function; +import java.util.List; import java.util.Map; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.core.common.BlockValSet; @@ -28,8 +29,8 @@ public class SumMVAggregationFunction extends SumAggregationFunction { - public SumMVAggregationFunction(ExpressionContext expression) { - super(expression); + public SumMVAggregationFunction(List arguments) { + super(verifySingleArgument(arguments, "SUM_MV"), false); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SumPrecisionAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SumPrecisionAggregationFunction.java index 2656389822f2..2bad736974ca 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SumPrecisionAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SumPrecisionAggregationFunction.java @@ -57,9 +57,9 @@ public SumPrecisionAggregationFunction(List arguments, boolea int numArguments = arguments.size(); Preconditions.checkArgument(numArguments <= 3, "SumPrecision expects at most 3 arguments, got: %s", numArguments); if (numArguments > 1) { - _precision = Integer.valueOf(arguments.get(1).getLiteralString()); + _precision = arguments.get(1).getLiteral().getIntValue(); if (numArguments > 2) { - _scale = Integer.valueOf(arguments.get(2).getLiteralString()); + _scale = arguments.get(2).getLiteral().getIntValue(); } else { _scale = null; } @@ -489,6 +489,11 @@ public BigDecimal extractFinalResult(BigDecimal intermediateResult) { return _scale == null ? result : result.setScale(_scale, RoundingMode.HALF_EVEN); } + @Override + public BigDecimal mergeFinalResult(BigDecimal finalResult1, BigDecimal finalResult2) { + return merge(finalResult1, finalResult2); + } + public BigDecimal getDefaultResult(AggregationResultHolder aggregationResultHolder) { BigDecimal result = aggregationResultHolder.getResult(); return result != null ? result : BigDecimal.ZERO; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SumValuesIntegerTupleSketchAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SumValuesIntegerTupleSketchAggregationFunction.java new file mode 100644 index 000000000000..fa4ac2d68dbf --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/SumValuesIntegerTupleSketchAggregationFunction.java @@ -0,0 +1,67 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import java.util.List; +import org.apache.datasketches.tuple.Sketch; +import org.apache.datasketches.tuple.TupleSketchIterator; +import org.apache.datasketches.tuple.aninteger.IntegerSummary; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.segment.local.customobject.TupleIntSketchAccumulator; +import org.apache.pinot.segment.spi.AggregationFunctionType; + + +public class SumValuesIntegerTupleSketchAggregationFunction extends IntegerTupleSketchAggregationFunction { + + public SumValuesIntegerTupleSketchAggregationFunction(List arguments, IntegerSummary.Mode mode) { + super(arguments, mode); + } + + // TODO if extra aggregation modes are supported, make this switch + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.SUMVALUESINTEGERSUMTUPLESKETCH; + } + + @Override + public ColumnDataType getFinalResultColumnType() { + return ColumnDataType.LONG; + } + + @Override + public Comparable extractFinalResult(TupleIntSketchAccumulator accumulator) { + double retainedTotal = 0L; + accumulator.setNominalEntries(_nominalEntries); + accumulator.setSetOperations(_setOps); + accumulator.setThreshold(_accumulatorThreshold); + Sketch result = accumulator.getResult(); + TupleSketchIterator summaries = result.iterator(); + while (summaries.next()) { + retainedTotal += summaries.getSummary().getValue(); + } + double estimate = retainedTotal / result.getTheta(); + return Math.round(estimate); + } + + @Override + public Comparable mergeFinalResult(Comparable finalResult1, Comparable finalResult2) { + return (Long) finalResult1 + (Long) finalResult2; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/VarianceAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/VarianceAggregationFunction.java index c86269b7ce4f..6c49dfaa7a0a 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/VarianceAggregationFunction.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/VarianceAggregationFunction.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.core.query.aggregation.function; +import java.util.List; import java.util.Map; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.utils.DataSchema; @@ -29,6 +30,7 @@ import org.apache.pinot.core.query.aggregation.utils.StatisticalAggregationFunctionUtils; import org.apache.pinot.segment.local.customobject.VarianceTuple; import org.apache.pinot.segment.spi.AggregationFunctionType; +import org.roaringbitmap.RoaringBitmap; /** @@ -41,13 +43,19 @@ public class VarianceAggregationFunction extends BaseSingleInputAggregationFunction { private static final double DEFAULT_FINAL_RESULT = Double.NEGATIVE_INFINITY; protected final boolean _isSample; - protected final boolean _isStdDev; + protected final boolean _nullHandlingEnabled; - public VarianceAggregationFunction(ExpressionContext expression, boolean isSample, boolean isStdDev) { - super(expression); + public VarianceAggregationFunction(List arguments, boolean isSample, boolean isStdDev, + boolean nullHandlingEnabled) { + super(verifySingleArgument(arguments, getFunctionName(isSample, isStdDev))); _isSample = isSample; _isStdDev = isStdDev; + _nullHandlingEnabled = nullHandlingEnabled; + } + + private static String getFunctionName(boolean isSample, boolean isStdDev) { + return isSample ? (isStdDev ? "STD_DEV_SAMP" : "VAR_SAMP") : (isStdDev ? "STD_DEV_POP" : "VAR_POP"); } @Override @@ -72,18 +80,38 @@ public GroupByResultHolder createGroupByResultHolder(int initialCapacity, int ma public void aggregate(int length, AggregationResultHolder aggregationResultHolder, Map blockValSetMap) { double[] values = StatisticalAggregationFunctionUtils.getValSet(blockValSetMap, _expression); + RoaringBitmap nullBitmap = null; + if (_nullHandlingEnabled) { + nullBitmap = blockValSetMap.get(_expression).getNullBitmap(); + } long count = 0; double sum = 0.0; double variance = 0.0; - for (int i = 0; i < length; i++) { - count++; - sum += values[i]; - if (count > 1) { - variance = computeIntermediateVariance(count, sum, variance, values[i]); + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + count++; + sum += values[i]; + if (count > 1) { + variance = computeIntermediateVariance(count, sum, variance, values[i]); + } + } + } + } else { + for (int i = 0; i < length; i++) { + count++; + sum += values[i]; + if (count > 1) { + variance = computeIntermediateVariance(count, sum, variance, values[i]); + } } } - setAggregationResult(aggregationResultHolder, length, sum, variance); + + if (_nullHandlingEnabled && count == 0) { + return; + } + setAggregationResult(aggregationResultHolder, count, sum, variance); } private double computeIntermediateVariance(long count, double sum, double m2, double value) { @@ -116,8 +144,20 @@ protected void setGroupByResult(int groupKey, GroupByResultHolder groupByResultH public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, Map blockValSetMap) { double[] values = StatisticalAggregationFunctionUtils.getValSet(blockValSetMap, _expression); - for (int i = 0; i < length; i++) { - setGroupByResult(groupKeyArray[i], groupByResultHolder, 1L, values[i], 0.0); + RoaringBitmap nullBitmap = null; + if (_nullHandlingEnabled) { + nullBitmap = blockValSetMap.get(_expression).getNullBitmap(); + } + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + setGroupByResult(groupKeyArray[i], groupByResultHolder, 1L, values[i], 0.0); + } + } + } else { + for (int i = 0; i < length; i++) { + setGroupByResult(groupKeyArray[i], groupByResultHolder, 1L, values[i], 0.0); + } } } @@ -125,9 +165,23 @@ public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHol public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, Map blockValSetMap) { double[] values = StatisticalAggregationFunctionUtils.getValSet(blockValSetMap, _expression); - for (int i = 0; i < length; i++) { - for (int groupKey : groupKeysArray[i]) { - setGroupByResult(groupKey, groupByResultHolder, 1L, values[i], 0.0); + RoaringBitmap nullBitmap = null; + if (_nullHandlingEnabled) { + nullBitmap = blockValSetMap.get(_expression).getNullBitmap(); + } + if (nullBitmap != null && !nullBitmap.isEmpty()) { + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + for (int groupKey : groupKeysArray[i]) { + setGroupByResult(groupKey, groupByResultHolder, 1L, values[i], 0.0); + } + } + } + } else { + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + setGroupByResult(groupKey, groupByResultHolder, 1L, values[i], 0.0); + } } } } @@ -136,7 +190,7 @@ public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResult public VarianceTuple extractAggregationResult(AggregationResultHolder aggregationResultHolder) { VarianceTuple varianceTuple = aggregationResultHolder.getResult(); if (varianceTuple == null) { - return new VarianceTuple(0L, 0.0, 0.0); + return _nullHandlingEnabled ? null : new VarianceTuple(0L, 0.0, 0.0); } else { return varianceTuple; } @@ -149,6 +203,13 @@ public VarianceTuple extractGroupByResult(GroupByResultHolder groupByResultHolde @Override public VarianceTuple merge(VarianceTuple intermediateResult1, VarianceTuple intermediateResult2) { + if (_nullHandlingEnabled) { + if (intermediateResult1 == null) { + return intermediateResult2; + } else if (intermediateResult2 == null) { + return intermediateResult1; + } + } intermediateResult1.apply(intermediateResult2); return intermediateResult1; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggDistinctDoubleFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggDistinctDoubleFunction.java new file mode 100644 index 000000000000..f5dacc3f4b24 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggDistinctDoubleFunction.java @@ -0,0 +1,66 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.array; + +import it.unimi.dsi.fastutil.doubles.DoubleOpenHashSet; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.roaringbitmap.RoaringBitmap; + + +public class ArrayAggDistinctDoubleFunction extends BaseArrayAggDoubleFunction { + public ArrayAggDistinctDoubleFunction(ExpressionContext expression, boolean nullHandlingEnabled) { + super(expression, nullHandlingEnabled); + } + + @Override + protected void aggregateArray(int length, AggregationResultHolder aggregationResultHolder, BlockValSet blockValSet) { + DoubleOpenHashSet valueArray = new DoubleOpenHashSet(length); + double[] value = blockValSet.getDoubleValuesSV(); + for (int i = 0; i < length; i++) { + valueArray.add(value[i]); + } + aggregationResultHolder.setValue(valueArray); + } + + @Override + protected void aggregateArrayWithNull(int length, AggregationResultHolder aggregationResultHolder, + BlockValSet blockValSet, RoaringBitmap nullBitmap) { + DoubleOpenHashSet valueArray = new DoubleOpenHashSet(length); + double[] value = blockValSet.getDoubleValuesSV(); + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + valueArray.add(value[i]); + } + } + aggregationResultHolder.setValue(valueArray); + } + + @Override + protected void setGroupByResult(GroupByResultHolder resultHolder, int groupKey, double value) { + DoubleOpenHashSet valueSet = resultHolder.getResult(groupKey); + if (valueSet == null) { + valueSet = new DoubleOpenHashSet(); + resultHolder.setValueForKey(groupKey, valueSet); + } + valueSet.add(value); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggDistinctFloatFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggDistinctFloatFunction.java new file mode 100644 index 000000000000..cf3e21eeb127 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggDistinctFloatFunction.java @@ -0,0 +1,66 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.array; + +import it.unimi.dsi.fastutil.floats.FloatOpenHashSet; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.roaringbitmap.RoaringBitmap; + + +public class ArrayAggDistinctFloatFunction extends BaseArrayAggFloatFunction { + public ArrayAggDistinctFloatFunction(ExpressionContext expression, boolean nullHandlingEnabled) { + super(expression, nullHandlingEnabled); + } + + @Override + protected void aggregateArray(int length, AggregationResultHolder aggregationResultHolder, BlockValSet blockValSet) { + FloatOpenHashSet valueArray = new FloatOpenHashSet(length); + float[] value = blockValSet.getFloatValuesSV(); + for (int i = 0; i < length; i++) { + valueArray.add(value[i]); + } + aggregationResultHolder.setValue(valueArray); + } + + @Override + protected void aggregateArrayWithNull(int length, AggregationResultHolder aggregationResultHolder, + BlockValSet blockValSet, RoaringBitmap nullBitmap) { + FloatOpenHashSet valueArray = new FloatOpenHashSet(length); + float[] value = blockValSet.getFloatValuesSV(); + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + valueArray.add(value[i]); + } + } + aggregationResultHolder.setValue(valueArray); + } + + @Override + protected void setGroupByResult(GroupByResultHolder resultHolder, int groupKey, float value) { + FloatOpenHashSet valueSet = resultHolder.getResult(groupKey); + if (valueSet == null) { + valueSet = new FloatOpenHashSet(); + resultHolder.setValueForKey(groupKey, valueSet); + } + valueSet.add(value); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggDistinctIntFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggDistinctIntFunction.java new file mode 100644 index 000000000000..1ccbb27b0ad3 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggDistinctIntFunction.java @@ -0,0 +1,69 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.array; + +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.spi.data.FieldSpec; +import org.roaringbitmap.RoaringBitmap; + + +public class ArrayAggDistinctIntFunction extends BaseArrayAggIntFunction { + public ArrayAggDistinctIntFunction(ExpressionContext expression, FieldSpec.DataType dataType, + boolean nullHandlingEnabled) { + super(expression, dataType, nullHandlingEnabled); + } + + @Override + protected void aggregateArray(int length, AggregationResultHolder aggregationResultHolder, + BlockValSet blockValSet) { + int[] value = blockValSet.getIntValuesSV(); + IntOpenHashSet valueArray = new IntOpenHashSet(length); + for (int i = 0; i < length; i++) { + valueArray.add(value[i]); + } + aggregationResultHolder.setValue(valueArray); + } + + @Override + protected void aggregateArrayWithNull(int length, AggregationResultHolder aggregationResultHolder, + BlockValSet blockValSet, RoaringBitmap nullBitmap) { + int[] value = blockValSet.getIntValuesSV(); + IntOpenHashSet valueArray = new IntOpenHashSet(length); + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + valueArray.add(value[i]); + } + } + aggregationResultHolder.setValue(valueArray); + } + + @Override + protected void setGroupByResult(GroupByResultHolder resultHolder, int groupKey, int value) { + IntOpenHashSet valueSet = resultHolder.getResult(groupKey); + if (valueSet == null) { + valueSet = new IntOpenHashSet(); + resultHolder.setValueForKey(groupKey, valueSet); + } + valueSet.add(value); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggDistinctLongFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggDistinctLongFunction.java new file mode 100644 index 000000000000..bc35b9cb3605 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggDistinctLongFunction.java @@ -0,0 +1,69 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.array; + +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.spi.data.FieldSpec; +import org.roaringbitmap.RoaringBitmap; + + +public class ArrayAggDistinctLongFunction extends BaseArrayAggLongFunction { + public ArrayAggDistinctLongFunction(ExpressionContext expression, FieldSpec.DataType dataType, + boolean nullHandlingEnabled) { + super(expression, dataType, nullHandlingEnabled); + } + + @Override + protected void aggregateArray(int length, AggregationResultHolder aggregationResultHolder, + BlockValSet blockValSet) { + long[] value = blockValSet.getLongValuesSV(); + LongOpenHashSet valueArray = new LongOpenHashSet(length); + for (int i = 0; i < length; i++) { + valueArray.add(value[i]); + } + aggregationResultHolder.setValue(valueArray); + } + + @Override + protected void aggregateArrayWithNull(int length, AggregationResultHolder aggregationResultHolder, + BlockValSet blockValSet, RoaringBitmap nullBitmap) { + long[] value = blockValSet.getLongValuesSV(); + LongOpenHashSet valueArray = new LongOpenHashSet(length); + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + valueArray.add(value[i]); + } + } + aggregationResultHolder.setValue(valueArray); + } + + @Override + protected void setGroupByResult(GroupByResultHolder resultHolder, int groupKey, long value) { + LongOpenHashSet valueSet = resultHolder.getResult(groupKey); + if (valueSet == null) { + valueSet = new LongOpenHashSet(); + resultHolder.setValueForKey(groupKey, valueSet); + } + valueSet.add(value); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggDistinctStringFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggDistinctStringFunction.java new file mode 100644 index 000000000000..0a964e3f10b5 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggDistinctStringFunction.java @@ -0,0 +1,65 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.array; + +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.Arrays; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.roaringbitmap.RoaringBitmap; + + +public class ArrayAggDistinctStringFunction extends BaseArrayAggStringFunction> { + public ArrayAggDistinctStringFunction(ExpressionContext expression, boolean nullHandlingEnabled) { + super(expression, nullHandlingEnabled); + } + + @Override + protected void aggregateArray(int length, AggregationResultHolder aggregationResultHolder, BlockValSet blockValSet) { + ObjectOpenHashSet valueArray = new ObjectOpenHashSet<>(length); + String[] value = blockValSet.getStringValuesSV(); + valueArray.addAll(Arrays.asList(value)); + aggregationResultHolder.setValue(valueArray); + } + + @Override + protected void aggregateArrayWithNull(int length, AggregationResultHolder aggregationResultHolder, + BlockValSet blockValSet, RoaringBitmap nullBitmap) { + ObjectOpenHashSet valueArray = new ObjectOpenHashSet<>(length); + String[] value = blockValSet.getStringValuesSV(); + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + valueArray.add(value[i]); + } + } + aggregationResultHolder.setValue(valueArray); + } + + @Override + protected void setGroupByResult(GroupByResultHolder resultHolder, int groupKey, String value) { + ObjectOpenHashSet valueSet = resultHolder.getResult(groupKey); + if (valueSet == null) { + valueSet = new ObjectOpenHashSet<>(); + resultHolder.setValueForKey(groupKey, valueSet); + } + valueSet.add(value); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggDoubleFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggDoubleFunction.java new file mode 100644 index 000000000000..78a29697bee3 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggDoubleFunction.java @@ -0,0 +1,66 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.array; + +import it.unimi.dsi.fastutil.doubles.DoubleArrayList; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.roaringbitmap.RoaringBitmap; + + +public class ArrayAggDoubleFunction extends BaseArrayAggDoubleFunction { + public ArrayAggDoubleFunction(ExpressionContext expression, boolean nullHandlingEnabled) { + super(expression, nullHandlingEnabled); + } + + @Override + protected void aggregateArray(int length, AggregationResultHolder aggregationResultHolder, BlockValSet blockValSet) { + DoubleArrayList valueArray = new DoubleArrayList(length); + double[] value = blockValSet.getDoubleValuesSV(); + for (int i = 0; i < length; i++) { + valueArray.add(value[i]); + } + aggregationResultHolder.setValue(valueArray); + } + + @Override + protected void aggregateArrayWithNull(int length, AggregationResultHolder aggregationResultHolder, + BlockValSet blockValSet, RoaringBitmap nullBitmap) { + DoubleArrayList valueArray = new DoubleArrayList(length); + double[] value = blockValSet.getDoubleValuesSV(); + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + valueArray.add(value[i]); + } + } + aggregationResultHolder.setValue(valueArray); + } + + @Override + protected void setGroupByResult(GroupByResultHolder resultHolder, int groupKey, double value) { + DoubleArrayList valueArray = resultHolder.getResult(groupKey); + if (valueArray == null) { + valueArray = new DoubleArrayList(); + resultHolder.setValueForKey(groupKey, valueArray); + } + valueArray.add(value); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggFloatFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggFloatFunction.java new file mode 100644 index 000000000000..50cb800801a7 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggFloatFunction.java @@ -0,0 +1,67 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.array; + +import it.unimi.dsi.fastutil.floats.FloatArrayList; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.roaringbitmap.RoaringBitmap; + + +public class ArrayAggFloatFunction extends BaseArrayAggFloatFunction { + public ArrayAggFloatFunction(ExpressionContext expression, boolean nullHandlingEnabled) { + super(expression, nullHandlingEnabled); + } + + @Override + protected void aggregateArray(int length, AggregationResultHolder aggregationResultHolder, BlockValSet blockValSet) { + FloatArrayList valueArray = new FloatArrayList(length); + float[] value = blockValSet.getFloatValuesSV(); + for (int i = 0; i < length; i++) { + valueArray.add(value[i]); + } + aggregationResultHolder.setValue(valueArray); + } + + @Override + protected void aggregateArrayWithNull(int length, AggregationResultHolder aggregationResultHolder, + BlockValSet blockValSet, RoaringBitmap nullBitmap) { + FloatArrayList valueArray = new FloatArrayList(length); + float[] value = blockValSet.getFloatValuesSV(); + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + valueArray.add(value[i]); + } + } + aggregationResultHolder.setValue(valueArray); + } + + @Override + + protected void setGroupByResult(GroupByResultHolder resultHolder, int groupKey, float value) { + FloatArrayList valueArray = resultHolder.getResult(groupKey); + if (valueArray == null) { + valueArray = new FloatArrayList(); + resultHolder.setValueForKey(groupKey, valueArray); + } + valueArray.add(value); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggIntFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggIntFunction.java new file mode 100644 index 000000000000..45589ab739b9 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggIntFunction.java @@ -0,0 +1,67 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.array; + +import it.unimi.dsi.fastutil.ints.IntArrayList; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.spi.data.FieldSpec; +import org.roaringbitmap.RoaringBitmap; + + +public class ArrayAggIntFunction extends BaseArrayAggIntFunction { + public ArrayAggIntFunction(ExpressionContext expression, FieldSpec.DataType dataType, boolean nullHandlingEnabled) { + super(expression, dataType, nullHandlingEnabled); + } + + @Override + protected void aggregateArray(int length, AggregationResultHolder aggregationResultHolder, + BlockValSet blockValSet) { + int[] value = blockValSet.getIntValuesSV(); + IntArrayList valueArray = new IntArrayList(length); + for (int i = 0; i < length; i++) { + valueArray.add(value[i]); + } + aggregationResultHolder.setValue(valueArray); + } + + @Override + protected void aggregateArrayWithNull(int length, AggregationResultHolder aggregationResultHolder, + BlockValSet blockValSet, RoaringBitmap nullBitmap) { + int[] value = blockValSet.getIntValuesSV(); + IntArrayList valueArray = new IntArrayList(length); + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + valueArray.add(value[i]); + } + } + aggregationResultHolder.setValue(valueArray); + } + + protected void setGroupByResult(GroupByResultHolder resultHolder, int groupKey, int value) { + IntArrayList valueArray = resultHolder.getResult(groupKey); + if (valueArray == null) { + valueArray = new IntArrayList(); + resultHolder.setValueForKey(groupKey, valueArray); + } + valueArray.add(value); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggLongFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggLongFunction.java new file mode 100644 index 000000000000..61643a5fda0d --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggLongFunction.java @@ -0,0 +1,68 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.array; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.spi.data.FieldSpec; +import org.roaringbitmap.RoaringBitmap; + + +public class ArrayAggLongFunction extends BaseArrayAggLongFunction { + public ArrayAggLongFunction(ExpressionContext expression, FieldSpec.DataType dataType, boolean nullHandlingEnabled) { + super(expression, dataType, nullHandlingEnabled); + } + + @Override + protected void aggregateArray(int length, AggregationResultHolder aggregationResultHolder, + BlockValSet blockValSet) { + long[] value = blockValSet.getLongValuesSV(); + LongArrayList valueArray = new LongArrayList(length); + for (int i = 0; i < length; i++) { + valueArray.add(value[i]); + } + aggregationResultHolder.setValue(valueArray); + } + + @Override + protected void aggregateArrayWithNull(int length, AggregationResultHolder aggregationResultHolder, + BlockValSet blockValSet, RoaringBitmap nullBitmap) { + long[] value = blockValSet.getLongValuesSV(); + LongArrayList valueArray = new LongArrayList(length); + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + valueArray.add(value[i]); + } + } + aggregationResultHolder.setValue(valueArray); + } + + @Override + protected void setGroupByResult(GroupByResultHolder resultHolder, int groupKey, long value) { + LongArrayList valueArray = resultHolder.getResult(groupKey); + if (valueArray == null) { + valueArray = new LongArrayList(); + resultHolder.setValueForKey(groupKey, valueArray); + } + valueArray.add(value); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggStringFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggStringFunction.java new file mode 100644 index 000000000000..5abfe51dd294 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ArrayAggStringFunction.java @@ -0,0 +1,65 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.array; + +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Arrays; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.roaringbitmap.RoaringBitmap; + + +public class ArrayAggStringFunction extends BaseArrayAggStringFunction> { + public ArrayAggStringFunction(ExpressionContext expression, boolean nullHandlingEnabled) { + super(expression, nullHandlingEnabled); + } + + @Override + protected void aggregateArray(int length, AggregationResultHolder aggregationResultHolder, BlockValSet blockValSet) { + ObjectArrayList valueArray = new ObjectArrayList<>(length); + String[] value = blockValSet.getStringValuesSV(); + valueArray.addAll(Arrays.asList(value)); + aggregationResultHolder.setValue(valueArray); + } + + @Override + protected void aggregateArrayWithNull(int length, AggregationResultHolder aggregationResultHolder, + BlockValSet blockValSet, RoaringBitmap nullBitmap) { + ObjectArrayList valueArray = new ObjectArrayList<>(length); + String[] value = blockValSet.getStringValuesSV(); + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + valueArray.add(value[i]); + } + } + aggregationResultHolder.setValue(valueArray); + } + + @Override + protected void setGroupByResult(GroupByResultHolder resultHolder, int groupKey, String value) { + ObjectArrayList valueArray = resultHolder.getResult(groupKey); + if (valueArray == null) { + valueArray = new ObjectArrayList<>(); + resultHolder.setValueForKey(groupKey, valueArray); + } + valueArray.add(value); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/BaseArrayAggDoubleFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/BaseArrayAggDoubleFunction.java new file mode 100644 index 000000000000..de3e52b80e51 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/BaseArrayAggDoubleFunction.java @@ -0,0 +1,101 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.array; + +import it.unimi.dsi.fastutil.doubles.AbstractDoubleCollection; +import it.unimi.dsi.fastutil.doubles.DoubleArrayList; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.spi.data.FieldSpec; +import org.roaringbitmap.RoaringBitmap; + + +public abstract class BaseArrayAggDoubleFunction + extends BaseArrayAggFunction { + public BaseArrayAggDoubleFunction(ExpressionContext expression, boolean nullHandlingEnabled) { + super(expression, FieldSpec.DataType.DOUBLE, nullHandlingEnabled); + } + + abstract void setGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey, double value); + + @Override + protected void aggregateArrayGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + BlockValSet blockValSet) { + double[] values = blockValSet.getDoubleValuesSV(); + for (int i = 0; i < length; i++) { + setGroupByResult(groupByResultHolder, groupKeyArray[i], values[i]); + } + } + + @Override + protected void aggregateArrayGroupBySVWithNull(int length, int[] groupKeyArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, RoaringBitmap nullBitmap) { + double[] values = blockValSet.getDoubleValuesSV(); + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + setGroupByResult(groupByResultHolder, groupKeyArray[i], values[i]); + } + } + } + + @Override + protected void aggregateArrayGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + BlockValSet blockValSet) { + double[] values = blockValSet.getDoubleValuesSV(); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + setGroupByResult(groupByResultHolder, groupKey, values[i]); + } + } + } + + @Override + protected void aggregateArrayGroupByMVWithNull(int length, int[][] groupKeysArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, RoaringBitmap nullBitmap) { + double[] values = blockValSet.getDoubleValuesSV(); + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + for (int groupKey : groupKeysArray[i]) { + setGroupByResult(groupByResultHolder, groupKey, values[i]); + } + } + } + } + + @Override + public I merge(I intermediateResult1, I intermediateResult2) { + if (intermediateResult1 == null) { + return intermediateResult2; + } + if (intermediateResult2 == null) { + return intermediateResult1; + } + intermediateResult1.addAll(intermediateResult2); + return intermediateResult1; + } + + @Override + public DoubleArrayList extractFinalResult(I doubleArrayList) { + if (doubleArrayList == null) { + return new DoubleArrayList(); + } + return new DoubleArrayList(doubleArrayList); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/BaseArrayAggFloatFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/BaseArrayAggFloatFunction.java new file mode 100644 index 000000000000..a6f4078dc54f --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/BaseArrayAggFloatFunction.java @@ -0,0 +1,101 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.array; + +import it.unimi.dsi.fastutil.floats.AbstractFloatCollection; +import it.unimi.dsi.fastutil.floats.FloatArrayList; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.spi.data.FieldSpec; +import org.roaringbitmap.RoaringBitmap; + + +public abstract class BaseArrayAggFloatFunction + extends BaseArrayAggFunction { + public BaseArrayAggFloatFunction(ExpressionContext expression, boolean nullHandlingEnabled) { + super(expression, FieldSpec.DataType.FLOAT, nullHandlingEnabled); + } + + abstract void setGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey, float value); + + @Override + protected void aggregateArrayGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + BlockValSet blockValSet) { + float[] values = blockValSet.getFloatValuesSV(); + for (int i = 0; i < length; i++) { + setGroupByResult(groupByResultHolder, groupKeyArray[i], values[i]); + } + } + + @Override + protected void aggregateArrayGroupBySVWithNull(int length, int[] groupKeyArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, RoaringBitmap nullBitmap) { + float[] values = blockValSet.getFloatValuesSV(); + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + setGroupByResult(groupByResultHolder, groupKeyArray[i], values[i]); + } + } + } + + @Override + protected void aggregateArrayGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + BlockValSet blockValSet) { + float[] values = blockValSet.getFloatValuesSV(); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + setGroupByResult(groupByResultHolder, groupKey, values[i]); + } + } + } + + @Override + protected void aggregateArrayGroupByMVWithNull(int length, int[][] groupKeysArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, RoaringBitmap nullBitmap) { + float[] values = blockValSet.getFloatValuesSV(); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + if (!nullBitmap.contains(i)) { + setGroupByResult(groupByResultHolder, groupKey, values[i]); + } + } + } + } + + @Override + public I merge(I intermediateResult1, I intermediateResult2) { + if (intermediateResult1 == null) { + return intermediateResult2; + } + if (intermediateResult2 == null) { + return intermediateResult1; + } + intermediateResult1.addAll(intermediateResult2); + return intermediateResult1; + } + + @Override + public FloatArrayList extractFinalResult(I floatArrayList) { + if (floatArrayList == null) { + return new FloatArrayList(); + } + return new FloatArrayList(floatArrayList); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/BaseArrayAggFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/BaseArrayAggFunction.java new file mode 100644 index 000000000000..5f17c16a197f --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/BaseArrayAggFunction.java @@ -0,0 +1,140 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.array; + +import java.util.Map; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.ObjectAggregationResultHolder; +import org.apache.pinot.core.query.aggregation.function.BaseSingleInputAggregationFunction; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.ObjectGroupByResultHolder; +import org.apache.pinot.segment.spi.AggregationFunctionType; +import org.apache.pinot.spi.data.FieldSpec; +import org.roaringbitmap.RoaringBitmap; + + +public abstract class BaseArrayAggFunction extends BaseSingleInputAggregationFunction { + + protected final boolean _nullHandlingEnabled; + private final DataSchema.ColumnDataType _resultColumnType; + + public BaseArrayAggFunction(ExpressionContext expression, FieldSpec.DataType dataType, boolean nullHandlingEnabled) { + super(expression); + _nullHandlingEnabled = nullHandlingEnabled; + _resultColumnType = DataSchema.ColumnDataType.fromDataTypeMV(dataType); + } + + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.ARRAYAGG; + } + + @Override + public AggregationResultHolder createAggregationResultHolder() { + return new ObjectAggregationResultHolder(); + } + + @Override + public GroupByResultHolder createGroupByResultHolder(int initialCapacity, int maxCapacity) { + return new ObjectGroupByResultHolder(initialCapacity, maxCapacity); + } + + @Override + public DataSchema.ColumnDataType getIntermediateResultColumnType() { + return DataSchema.ColumnDataType.OBJECT; + } + + @Override + public DataSchema.ColumnDataType getFinalResultColumnType() { + return _resultColumnType; + } + + @Override + public void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + if (_nullHandlingEnabled) { + RoaringBitmap nullBitmap = blockValSet.getNullBitmap(); + if (nullBitmap != null && !nullBitmap.isEmpty()) { + aggregateArrayWithNull(length, aggregationResultHolder, blockValSet, nullBitmap); + return; + } + } + aggregateArray(length, aggregationResultHolder, blockValSet); + } + + protected abstract void aggregateArray(int length, AggregationResultHolder aggregationResultHolder, + BlockValSet blockValSet); + + protected abstract void aggregateArrayWithNull(int length, AggregationResultHolder aggregationResultHolder, + BlockValSet blockValSet, RoaringBitmap nullBitmap); + + @Override + public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + if (_nullHandlingEnabled) { + RoaringBitmap nullBitmap = blockValSet.getNullBitmap(); + if (nullBitmap != null && !nullBitmap.isEmpty()) { + aggregateArrayGroupBySVWithNull(length, groupKeyArray, groupByResultHolder, blockValSet, nullBitmap); + return; + } + } + aggregateArrayGroupBySV(length, groupKeyArray, groupByResultHolder, blockValSet); + } + + protected abstract void aggregateArrayGroupBySV(int length, int[] groupKeyArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet); + + protected abstract void aggregateArrayGroupBySVWithNull(int length, int[] groupKeyArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, RoaringBitmap nullBitmap); + + @Override + public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + if (_nullHandlingEnabled) { + RoaringBitmap nullBitmap = blockValSet.getNullBitmap(); + if (nullBitmap != null && !nullBitmap.isEmpty()) { + aggregateArrayGroupByMVWithNull(length, groupKeysArray, groupByResultHolder, blockValSet, nullBitmap); + return; + } + } + aggregateArrayGroupByMV(length, groupKeysArray, groupByResultHolder, blockValSet); + } + + protected abstract void aggregateArrayGroupByMV(int length, int[][] groupKeysArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet); + + protected abstract void aggregateArrayGroupByMVWithNull(int length, int[][] groupKeysArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, RoaringBitmap nullBitmap); + + @Override + public I extractAggregationResult(AggregationResultHolder aggregationResultHolder) { + return aggregationResultHolder.getResult(); + } + + @Override + public I extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { + return groupByResultHolder.getResult(groupKey); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/BaseArrayAggIntFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/BaseArrayAggIntFunction.java new file mode 100644 index 000000000000..7f05d1585789 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/BaseArrayAggIntFunction.java @@ -0,0 +1,106 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.array; + +import it.unimi.dsi.fastutil.ints.AbstractIntCollection; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.spi.data.FieldSpec; +import org.roaringbitmap.RoaringBitmap; + + +public abstract class BaseArrayAggIntFunction + extends BaseArrayAggFunction { + public BaseArrayAggIntFunction(ExpressionContext expression, FieldSpec.DataType dataType, + boolean nullHandlingEnabled) { + super(expression, dataType, nullHandlingEnabled); + } + + abstract void setGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey, int value); + + @Override + protected void aggregateArrayGroupBySV(int length, int[] groupKeyArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet) { + int[] values = blockValSet.getIntValuesSV(); + for (int i = 0; i < length; i++) { + setGroupByResult(groupByResultHolder, groupKeyArray[i], values[i]); + } + } + + @Override + protected void aggregateArrayGroupBySVWithNull(int length, int[] groupKeyArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, RoaringBitmap nullBitmap) { + int[] values = blockValSet.getIntValuesSV(); + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + setGroupByResult(groupByResultHolder, groupKeyArray[i], values[i]); + } + } + } + + @Override + protected void aggregateArrayGroupByMV(int length, int[][] groupKeysArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet) { + int[] values = blockValSet.getIntValuesSV(); + for (int i = 0; i < length; i++) { + int[] groupKeys = groupKeysArray[i]; + int value = values[i]; + for (int groupKey : groupKeys) { + setGroupByResult(groupByResultHolder, groupKey, value); + } + } + } + + @Override + protected void aggregateArrayGroupByMVWithNull(int length, int[][] groupKeysArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, RoaringBitmap nullBitmap) { + int[] values = blockValSet.getIntValuesSV(); + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + int[] groupKeys = groupKeysArray[i]; + int value = values[i]; + for (int groupKey : groupKeys) { + setGroupByResult(groupByResultHolder, groupKey, value); + } + } + } + } + + @Override + public I merge(I intermediateResult1, I intermediateResult2) { + if (intermediateResult1 == null || intermediateResult1.isEmpty()) { + return intermediateResult2; + } + if (intermediateResult2 == null || intermediateResult2.isEmpty()) { + return intermediateResult1; + } + intermediateResult1.addAll(intermediateResult2); + return intermediateResult1; + } + + @Override + public IntArrayList extractFinalResult(I intArrayList) { + if (intArrayList == null) { + return new IntArrayList(); + } + return new IntArrayList(intArrayList); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/BaseArrayAggLongFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/BaseArrayAggLongFunction.java new file mode 100644 index 000000000000..76e705945afb --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/BaseArrayAggLongFunction.java @@ -0,0 +1,104 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.array; + +import it.unimi.dsi.fastutil.longs.AbstractLongCollection; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.spi.data.FieldSpec; +import org.roaringbitmap.RoaringBitmap; + + +public abstract class BaseArrayAggLongFunction + extends BaseArrayAggFunction { + public BaseArrayAggLongFunction(ExpressionContext expression, FieldSpec.DataType dataType, + boolean nullHandlingEnabled) { + super(expression, dataType, nullHandlingEnabled); + } + + abstract void setGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey, long value); + + @Override + protected void aggregateArrayGroupBySV(int length, int[] groupKeyArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet) { + long[] values = blockValSet.getLongValuesSV(); + for (int i = 0; i < length; i++) { + setGroupByResult(groupByResultHolder, groupKeyArray[i], values[i]); + } + } + + @Override + protected void aggregateArrayGroupBySVWithNull(int length, int[] groupKeyArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, RoaringBitmap nullBitmap) { + long[] values = blockValSet.getLongValuesSV(); + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + setGroupByResult(groupByResultHolder, groupKeyArray[i], values[i]); + } + } + } + + @Override + protected void aggregateArrayGroupByMV(int length, int[][] groupKeysArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet) { + long[] values = blockValSet.getLongValuesSV(); + for (int i = 0; i < length; i++) { + int[] groupKeys = groupKeysArray[i]; + for (int groupKey : groupKeys) { + setGroupByResult(groupByResultHolder, groupKey, values[i]); + } + } + } + + @Override + protected void aggregateArrayGroupByMVWithNull(int length, int[][] groupKeysArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, RoaringBitmap nullBitmap) { + long[] values = blockValSet.getLongValuesSV(); + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + int[] groupKeys = groupKeysArray[i]; + for (int groupKey : groupKeys) { + setGroupByResult(groupByResultHolder, groupKey, values[i]); + } + } + } + } + + @Override + public I merge(I intermediateResult1, I intermediateResult2) { + if (intermediateResult1 == null || intermediateResult1.isEmpty()) { + return intermediateResult2; + } + if (intermediateResult2 == null || intermediateResult2.isEmpty()) { + return intermediateResult1; + } + intermediateResult1.addAll(intermediateResult2); + return intermediateResult1; + } + + @Override + public LongArrayList extractFinalResult(I arrayList) { + if (arrayList == null) { + return new LongArrayList(); + } + return new LongArrayList(arrayList); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/BaseArrayAggStringFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/BaseArrayAggStringFunction.java new file mode 100644 index 000000000000..2e80eb6040a0 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/BaseArrayAggStringFunction.java @@ -0,0 +1,105 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.array; + +import it.unimi.dsi.fastutil.objects.AbstractObjectCollection; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectIterators; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.spi.data.FieldSpec; +import org.roaringbitmap.RoaringBitmap; + + +public abstract class BaseArrayAggStringFunction> + extends BaseArrayAggFunction> { + public BaseArrayAggStringFunction(ExpressionContext expression, boolean nullHandlingEnabled) { + super(expression, FieldSpec.DataType.STRING, nullHandlingEnabled); + } + + abstract void setGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey, String value); + + @Override + protected void aggregateArrayGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + BlockValSet blockValSet) { + String[] values = blockValSet.getStringValuesSV(); + for (int i = 0; i < length; i++) { + setGroupByResult(groupByResultHolder, groupKeyArray[i], values[i]); + } + } + + @Override + protected void aggregateArrayGroupBySVWithNull(int length, int[] groupKeyArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, RoaringBitmap nullBitmap) { + String[] values = blockValSet.getStringValuesSV(); + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + setGroupByResult(groupByResultHolder, groupKeyArray[i], values[i]); + } + } + } + + @Override + protected void aggregateArrayGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + BlockValSet blockValSet) { + String[] values = blockValSet.getStringValuesSV(); + for (int i = 0; i < length; i++) { + for (int groupKey : groupKeysArray[i]) { + setGroupByResult(groupByResultHolder, groupKey, values[i]); + } + } + } + + @Override + protected void aggregateArrayGroupByMVWithNull(int length, int[][] groupKeysArray, + GroupByResultHolder groupByResultHolder, BlockValSet blockValSet, RoaringBitmap nullBitmap) { + String[] values = blockValSet.getStringValuesSV(); + for (int i = 0; i < length; i++) { + if (!nullBitmap.contains(i)) { + for (int groupKey : groupKeysArray[i]) { + setGroupByResult(groupByResultHolder, groupKey, values[i]); + } + } + } + } + + @Override + public I merge(I intermediateResult1, I intermediateResult2) { + if (intermediateResult1 == null) { + return intermediateResult2; + } + if (intermediateResult2 == null) { + return intermediateResult1; + } + intermediateResult1.addAll(intermediateResult2); + return intermediateResult1; + } + + @Override + public ObjectArrayList extractFinalResult(I stringArrayList) { + if (stringArrayList == null) { + return new ObjectArrayList<>(); + } + // NOTE: Wrap a String[] to work around the bug of ObjectArrayList constructor creating Object[] internally. + String[] stringArray = new String[stringArrayList.size()]; + ObjectIterators.unwrap(stringArrayList.iterator(), stringArray); + return ObjectArrayList.wrap(stringArray); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ListAggDistinctFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ListAggDistinctFunction.java new file mode 100644 index 000000000000..ef02640ad6a6 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ListAggDistinctFunction.java @@ -0,0 +1,58 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.array; + +import it.unimi.dsi.fastutil.objects.AbstractObjectCollection; +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; + + +/** + * The {@code ListAggDistinctFunction} extends the {@link ListAggFunction} to use {@link ObjectLinkedOpenHashSet} as + * the intermediate result to hold distinct values for aggregation. + */ +public class ListAggDistinctFunction extends ListAggFunction { + + public ListAggDistinctFunction(ExpressionContext expression, String separator, boolean nullHandlingEnabled) { + super(expression, separator, nullHandlingEnabled); + } + + @Override + protected AbstractObjectCollection getObjectCollection(AggregationResultHolder aggregationResultHolder) { + ObjectLinkedOpenHashSet valueSet = aggregationResultHolder.getResult(); + if (valueSet == null) { + valueSet = new ObjectLinkedOpenHashSet<>(); + aggregationResultHolder.setValue(valueSet); + } + return valueSet; + } + + @Override + protected AbstractObjectCollection getObjectCollection(GroupByResultHolder groupByResultHolder, + int groupKey) { + ObjectLinkedOpenHashSet valueSet = groupByResultHolder.getResult(groupKey); + if (valueSet == null) { + valueSet = new ObjectLinkedOpenHashSet<>(); + groupByResultHolder.setValueForKey(groupKey, valueSet); + } + return valueSet; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ListAggFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ListAggFunction.java new file mode 100644 index 000000000000..f433df81f474 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/ListAggFunction.java @@ -0,0 +1,160 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.array; + +import it.unimi.dsi.fastutil.objects.AbstractObjectCollection; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import java.util.Arrays; +import java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.ObjectAggregationResultHolder; +import org.apache.pinot.core.query.aggregation.function.NullableSingleInputAggregationFunction; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.ObjectGroupByResultHolder; +import org.apache.pinot.segment.spi.AggregationFunctionType; + + +public class ListAggFunction + extends NullableSingleInputAggregationFunction, String> { + + private final String _separator; + + public ListAggFunction(ExpressionContext expression, String separator, boolean nullHandlingEnabled) { + super(expression, nullHandlingEnabled); + _separator = separator; + } + + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.LISTAGG; + } + + @Override + public AggregationResultHolder createAggregationResultHolder() { + return new ObjectAggregationResultHolder(); + } + + @Override + public GroupByResultHolder createGroupByResultHolder(int initialCapacity, int maxCapacity) { + return new ObjectGroupByResultHolder(initialCapacity, maxCapacity); + } + + @Override + public void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + AbstractObjectCollection valueSet = getObjectCollection(aggregationResultHolder); + BlockValSet blockValSet = blockValSetMap.get(_expression); + String[] values = blockValSet.getStringValuesSV(); + forEachNotNull(length, blockValSet, (from, to) -> { + valueSet.addAll(Arrays.asList(values).subList(from, to)); + }); + } + + protected AbstractObjectCollection getObjectCollection(AggregationResultHolder aggregationResultHolder) { + ObjectArrayList valueSet = aggregationResultHolder.getResult(); + if (valueSet == null) { + valueSet = new ObjectArrayList<>(); + aggregationResultHolder.setValue(valueSet); + } + return valueSet; + } + + protected AbstractObjectCollection getObjectCollection(GroupByResultHolder groupByResultHolder, + int groupKey) { + ObjectArrayList valueSet = groupByResultHolder.getResult(groupKey); + if (valueSet == null) { + valueSet = new ObjectArrayList<>(); + groupByResultHolder.setValueForKey(groupKey, valueSet); + } + return valueSet; + } + + @Override + public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + String[] values = blockValSet.getStringValuesSV(); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + AbstractObjectCollection groupValueList = getObjectCollection(groupByResultHolder, groupKeyArray[i]); + groupValueList.add(values[i]); + } + }); + } + + @Override + public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + BlockValSet blockValSet = blockValSetMap.get(_expression); + String[] values = blockValSet.getStringValuesSV(); + forEachNotNull(length, blockValSet, (from, to) -> { + for (int i = from; i < to; i++) { + for (int groupKey : groupKeysArray[i]) { + AbstractObjectCollection groupValueList = getObjectCollection(groupByResultHolder, groupKey); + groupValueList.add(values[i]); + } + } + }); + } + + @Override + public AbstractObjectCollection extractAggregationResult(AggregationResultHolder aggregationResultHolder) { + return aggregationResultHolder.getResult(); + } + + @Override + public AbstractObjectCollection extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { + return groupByResultHolder.getResult(groupKey); + } + + @Override + public AbstractObjectCollection merge(AbstractObjectCollection intermediateResult1, + AbstractObjectCollection intermediateResult2) { + if (intermediateResult1 == null) { + return intermediateResult2; + } + if (intermediateResult2 == null) { + return intermediateResult1; + } + intermediateResult1.addAll(intermediateResult2); + return intermediateResult1; + } + + @Override + public DataSchema.ColumnDataType getIntermediateResultColumnType() { + return DataSchema.ColumnDataType.OBJECT; + } + + @Override + public DataSchema.ColumnDataType getFinalResultColumnType() { + return DataSchema.ColumnDataType.STRING; + } + + @Override + public String extractFinalResult(AbstractObjectCollection strings) { + if (strings == null) { + return null; + } + return StringUtils.join(strings, _separator); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/SumArrayDoubleAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/SumArrayDoubleAggregationFunction.java new file mode 100644 index 000000000000..a29a79eb7b0d --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/SumArrayDoubleAggregationFunction.java @@ -0,0 +1,151 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.array; + +import it.unimi.dsi.fastutil.doubles.DoubleArrayList; +import java.util.List; +import java.util.Map; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.ObjectAggregationResultHolder; +import org.apache.pinot.core.query.aggregation.function.BaseSingleInputAggregationFunction; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.ObjectGroupByResultHolder; +import org.apache.pinot.segment.spi.AggregationFunctionType; + + +public class SumArrayDoubleAggregationFunction + extends BaseSingleInputAggregationFunction { + + public SumArrayDoubleAggregationFunction(List arguments) { + super(verifySingleArgument(arguments, "SUM_ARRAY")); + } + + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.SUMARRAYDOUBLE; + } + + @Override + public AggregationResultHolder createAggregationResultHolder() { + return new ObjectAggregationResultHolder(); + } + + @Override + public GroupByResultHolder createGroupByResultHolder(int initialCapacity, int maxCapacity) { + return new ObjectGroupByResultHolder(initialCapacity, maxCapacity); + } + + @Override + public void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + double[][] values = blockValSetMap.get(_expression).getDoubleValuesMV(); + if (aggregationResultHolder.getResult() == null) { + aggregationResultHolder.setValue(new DoubleArrayList()); + } + DoubleArrayList result = aggregationResultHolder.getResult(); + for (int i = 0; i < length; i++) { + double[] value = values[i]; + aggregateMerge(value, result); + } + } + + @Override + public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + double[][] valuesArray = blockValSetMap.get(_expression).getDoubleValuesMV(); + for (int i = 0; i < length; i++) { + double[] values = valuesArray[i]; + int groupKey = groupKeyArray[i]; + setGroupByResult(groupByResultHolder, values, groupKey); + } + } + + @Override + public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + double[][] valuesArray = blockValSetMap.get(_expression).getDoubleValuesMV(); + for (int i = 0; i < length; i++) { + double[] values = valuesArray[i]; + int[] groupKeys = groupKeysArray[i]; + for (int groupKey : groupKeys) { + setGroupByResult(groupByResultHolder, values, groupKey); + } + } + } + + private void setGroupByResult(GroupByResultHolder groupByResultHolder, double[] values, int groupKey) { + DoubleArrayList sumList = groupByResultHolder.getResult(groupKey); + if (sumList == null) { + sumList = new DoubleArrayList(); + groupByResultHolder.setValueForKey(groupKey, sumList); + } + aggregateMerge(values, sumList); + } + + private void aggregateMerge(double[] values, DoubleArrayList sumList) { + for (int j = sumList.size(); j < values.length; j++) { + sumList.add(0L); + } + for (int j = 0; j < values.length; j++) { + sumList.set(j, sumList.getDouble(j) + values[j]); + } + } + + @Override + public DoubleArrayList extractAggregationResult(AggregationResultHolder aggregationResultHolder) { + return aggregationResultHolder.getResult(); + } + + @Override + public DoubleArrayList extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { + return groupByResultHolder.getResult(groupKey); + } + + @Override + public DoubleArrayList merge(DoubleArrayList intermediateResult1, DoubleArrayList intermediateResult2) { + if (intermediateResult1.size() < intermediateResult2.size()) { + for (int i = 0; i < intermediateResult1.size(); i++) { + intermediateResult2.set(i, intermediateResult1.getDouble(i) + intermediateResult2.getDouble(i)); + } + return intermediateResult2; + } + for (int i = 0; i < intermediateResult2.size(); i++) { + intermediateResult1.set(i, intermediateResult1.getDouble(i) + intermediateResult2.getDouble(i)); + } + return intermediateResult1; + } + + @Override + public DataSchema.ColumnDataType getIntermediateResultColumnType() { + return DataSchema.ColumnDataType.DOUBLE_ARRAY; + } + + @Override + public DataSchema.ColumnDataType getFinalResultColumnType() { + return DataSchema.ColumnDataType.DOUBLE_ARRAY; + } + + @Override + public DoubleArrayList extractFinalResult(DoubleArrayList result) { + return result; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/SumArrayLongAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/SumArrayLongAggregationFunction.java new file mode 100644 index 000000000000..a552a2a55bf5 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/array/SumArrayLongAggregationFunction.java @@ -0,0 +1,150 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.array; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import java.util.List; +import java.util.Map; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.ObjectAggregationResultHolder; +import org.apache.pinot.core.query.aggregation.function.BaseSingleInputAggregationFunction; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.ObjectGroupByResultHolder; +import org.apache.pinot.segment.spi.AggregationFunctionType; + + +public class SumArrayLongAggregationFunction extends BaseSingleInputAggregationFunction { + + public SumArrayLongAggregationFunction(List arguments) { + super(verifySingleArgument(arguments, "SUM_ARRAY")); + } + + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.SUMARRAYLONG; + } + + @Override + public AggregationResultHolder createAggregationResultHolder() { + return new ObjectAggregationResultHolder(); + } + + @Override + public GroupByResultHolder createGroupByResultHolder(int initialCapacity, int maxCapacity) { + return new ObjectGroupByResultHolder(initialCapacity, maxCapacity); + } + + @Override + public void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + long[][] values = blockValSetMap.get(_expression).getLongValuesMV(); + if (aggregationResultHolder.getResult() == null) { + aggregationResultHolder.setValue(new LongArrayList()); + } + LongArrayList result = aggregationResultHolder.getResult(); + for (int i = 0; i < length; i++) { + long[] value = values[i]; + aggregateMerge(value, result); + } + } + + @Override + public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + long[][] valuesArray = blockValSetMap.get(_expression).getLongValuesMV(); + for (int i = 0; i < length; i++) { + long[] values = valuesArray[i]; + int groupKey = groupKeyArray[i]; + setGroupByResult(groupByResultHolder, values, groupKey); + } + } + + @Override + public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + long[][] valuesArray = blockValSetMap.get(_expression).getLongValuesMV(); + for (int i = 0; i < length; i++) { + long[] values = valuesArray[i]; + int[] groupKeys = groupKeysArray[i]; + for (int groupKey : groupKeys) { + setGroupByResult(groupByResultHolder, values, groupKey); + } + } + } + + private void setGroupByResult(GroupByResultHolder groupByResultHolder, long[] values, int groupKey) { + LongArrayList sumList = groupByResultHolder.getResult(groupKey); + if (sumList == null) { + sumList = new LongArrayList(); + groupByResultHolder.setValueForKey(groupKey, sumList); + } + aggregateMerge(values, sumList); + } + + private void aggregateMerge(long[] values, LongArrayList sumList) { + for (int j = sumList.size(); j < values.length; j++) { + sumList.add(0L); + } + for (int j = 0; j < values.length; j++) { + sumList.set(j, sumList.getLong(j) + values[j]); + } + } + + @Override + public LongArrayList extractAggregationResult(AggregationResultHolder aggregationResultHolder) { + return aggregationResultHolder.getResult(); + } + + @Override + public LongArrayList extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { + return groupByResultHolder.getResult(groupKey); + } + + @Override + public LongArrayList merge(LongArrayList intermediateResult1, LongArrayList intermediateResult2) { + if (intermediateResult1.size() < intermediateResult2.size()) { + for (int i = 0; i < intermediateResult1.size(); i++) { + intermediateResult2.set(i, intermediateResult1.getLong(i) + intermediateResult2.getLong(i)); + } + return intermediateResult2; + } + for (int i = 0; i < intermediateResult2.size(); i++) { + intermediateResult1.set(i, intermediateResult1.getLong(i) + intermediateResult2.getLong(i)); + } + return intermediateResult1; + } + + @Override + public DataSchema.ColumnDataType getIntermediateResultColumnType() { + return DataSchema.ColumnDataType.LONG_ARRAY; + } + + @Override + public DataSchema.ColumnDataType getFinalResultColumnType() { + return DataSchema.ColumnDataType.LONG_ARRAY; + } + + @Override + public LongArrayList extractFinalResult(LongArrayList result) { + return result; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/AggregationStrategy.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/AggregationStrategy.java new file mode 100644 index 000000000000..298fd4a80523 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/AggregationStrategy.java @@ -0,0 +1,167 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.funnel; + +import com.google.common.base.Preconditions; +import java.util.List; +import java.util.Map; +import javax.annotation.concurrent.ThreadSafe; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.segment.spi.index.reader.Dictionary; + + +/** + * Interface for within segment aggregation strategy. + * + *

The implementation should be stateless, and can be shared among multiple segments in multiple threads. The + * result for each segment should be stored and passed in via the result holder. + * There should be no assumptions beyond segment boundaries, different aggregation strategies may be utilized + * across different segments for a given query. + * + * @param Aggregation result accumulated across blocks within segment, kept by result holder. + */ +@ThreadSafe +public abstract class AggregationStrategy { + + protected final int _numSteps; + private final List _stepExpressions; + private final List _correlateByExpressions; + private final ExpressionContext _primaryCorrelationCol; + + public AggregationStrategy(List stepExpressions, List correlateByExpressions) { + _stepExpressions = stepExpressions; + _correlateByExpressions = correlateByExpressions; + _primaryCorrelationCol = _correlateByExpressions.get(0); + _numSteps = _stepExpressions.size(); + } + + /** + * Returns an aggregation result for this aggregation strategy to be kept in a result holder (aggregation only). + */ + abstract A createAggregationResult(Dictionary dictionary); + + public A getAggregationResultGroupBy(Dictionary dictionary, GroupByResultHolder groupByResultHolder, int groupKey) { + A aggResult = groupByResultHolder.getResult(groupKey); + if (aggResult == null) { + aggResult = createAggregationResult(dictionary); + groupByResultHolder.setValueForKey(groupKey, aggResult); + } + return aggResult; + } + + public A getAggregationResult(Dictionary dictionary, AggregationResultHolder aggregationResultHolder) { + A aggResult = aggregationResultHolder.getResult(); + if (aggResult == null) { + aggResult = createAggregationResult(dictionary); + aggregationResultHolder.setValue(aggResult); + } + return aggResult; + } + + /** + * Performs aggregation on the given block value sets (aggregation only). + */ + public void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + final Dictionary dictionary = getDictionary(blockValSetMap); + final int[] correlationIds = getCorrelationIds(blockValSetMap); + final int[][] steps = getSteps(blockValSetMap); + + final A aggResult = getAggregationResult(dictionary, aggregationResultHolder); + for (int i = 0; i < length; i++) { + for (int n = 0; n < _numSteps; n++) { + if (steps[n][i] > 0) { + add(dictionary, aggResult, n, correlationIds[i]); + } + } + } + } + + /** + * Performs aggregation on the given group key array and block value sets (aggregation group-by on single-value + * columns). + */ + public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + final Dictionary dictionary = getDictionary(blockValSetMap); + final int[] correlationIds = getCorrelationIds(blockValSetMap); + final int[][] steps = getSteps(blockValSetMap); + + for (int i = 0; i < length; i++) { + for (int n = 0; n < _numSteps; n++) { + final int groupKey = groupKeyArray[i]; + final A aggResult = getAggregationResultGroupBy(dictionary, groupByResultHolder, groupKey); + if (steps[n][i] > 0) { + add(dictionary, aggResult, n, correlationIds[i]); + } + } + } + } + + /** + * Performs aggregation on the given group keys array and block value sets (aggregation group-by on multi-value + * columns). + */ + public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + final Dictionary dictionary = getDictionary(blockValSetMap); + final int[] correlationIds = getCorrelationIds(blockValSetMap); + final int[][] steps = getSteps(blockValSetMap); + + for (int i = 0; i < length; i++) { + for (int n = 0; n < _numSteps; n++) { + for (int groupKey : groupKeysArray[i]) { + final A aggResult = getAggregationResultGroupBy(dictionary, groupByResultHolder, groupKey); + if (steps[n][i] > 0) { + add(dictionary, aggResult, n, correlationIds[i]); + } + } + } + } + } + + /** + * Adds a correlation id to the aggregation counter for a given step in the funnel. + */ + abstract void add(Dictionary dictionary, A aggResult, int step, int correlationId); + + private Dictionary getDictionary(Map blockValSetMap) { + final Dictionary primaryCorrelationDictionary = blockValSetMap.get(_primaryCorrelationCol).getDictionary(); + Preconditions.checkArgument(primaryCorrelationDictionary != null, + "CORRELATE_BY column in FUNNELCOUNT aggregation function not supported, please use a dictionary encoded " + + "column."); + return primaryCorrelationDictionary; + } + + private int[] getCorrelationIds(Map blockValSetMap) { + return blockValSetMap.get(_primaryCorrelationCol).getDictionaryIdsSV(); + } + + private int[][] getSteps(Map blockValSetMap) { + final int[][] steps = new int[_numSteps][]; + for (int n = 0; n < _numSteps; n++) { + final BlockValSet stepBlockValSet = blockValSetMap.get(_stepExpressions.get(n)); + steps[n] = stepBlockValSet.getIntValuesSV(); + } + return steps; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/BitmapAggregationStrategy.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/BitmapAggregationStrategy.java new file mode 100644 index 000000000000..f726d936205f --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/BitmapAggregationStrategy.java @@ -0,0 +1,44 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.funnel; + +import java.util.List; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.segment.spi.index.reader.Dictionary; + + +/** + * Aggregation strategy leveraging roaring bitmap algebra (unions/intersections). + */ +class BitmapAggregationStrategy extends AggregationStrategy { + public BitmapAggregationStrategy(List stepExpressions, + List correlateByExpressions) { + super(stepExpressions, correlateByExpressions); + } + + @Override + public DictIdsWrapper createAggregationResult(Dictionary dictionary) { + return new DictIdsWrapper(_numSteps, dictionary); + } + + @Override + protected void add(Dictionary dictionary, DictIdsWrapper dictIdsWrapper, int step, int correlationId) { + dictIdsWrapper._stepsBitmaps[step].add(correlationId); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/BitmapMergeStrategy.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/BitmapMergeStrategy.java new file mode 100644 index 000000000000..0be8bbadbe0a --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/BitmapMergeStrategy.java @@ -0,0 +1,52 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.funnel; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import java.util.List; +import org.roaringbitmap.RoaringBitmap; + + +class BitmapMergeStrategy implements MergeStrategy> { + protected final int _numSteps; + + BitmapMergeStrategy(int numSteps) { + _numSteps = numSteps; + } + + @Override + public List merge(List intermediateResult1, List intermediateResult2) { + for (int i = 0; i < _numSteps; i++) { + intermediateResult1.get(i).or(intermediateResult2.get(i)); + } + return intermediateResult1; + } + + @Override + public LongArrayList extractFinalResult(List stepsBitmaps) { + long[] result = new long[_numSteps]; + result[0] = stepsBitmaps.get(0).getCardinality(); + for (int i = 1; i < _numSteps; i++) { + // intersect this step with previous step + stepsBitmaps.get(i).and(stepsBitmaps.get(i - 1)); + result[i] = stepsBitmaps.get(i).getCardinality(); + } + return LongArrayList.wrap(result); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/BitmapResultExtractionStrategy.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/BitmapResultExtractionStrategy.java new file mode 100644 index 000000000000..b60bad7e0c88 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/BitmapResultExtractionStrategy.java @@ -0,0 +1,85 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.funnel; + +import java.util.ArrayList; +import java.util.List; +import org.apache.pinot.segment.spi.index.reader.Dictionary; +import org.apache.pinot.spi.data.FieldSpec; +import org.roaringbitmap.PeekableIntIterator; +import org.roaringbitmap.RoaringBitmap; + + +class BitmapResultExtractionStrategy implements ResultExtractionStrategy> { + protected final int _numSteps; + + BitmapResultExtractionStrategy(int numSteps) { + _numSteps = numSteps; + } + + @Override + public List extractIntermediateResult(DictIdsWrapper dictIdsWrapper) { + Dictionary dictionary = dictIdsWrapper._dictionary; + List result = new ArrayList<>(_numSteps); + for (RoaringBitmap dictIdBitmap : dictIdsWrapper._stepsBitmaps) { + result.add(convertToValueBitmap(dictionary, dictIdBitmap)); + } + return result; + } + + /** + * Helper method to read dictionary and convert dictionary ids to hash code of the values for dictionary-encoded + * expression. + */ + private RoaringBitmap convertToValueBitmap(Dictionary dictionary, RoaringBitmap dictIdBitmap) { + RoaringBitmap valueBitmap = new RoaringBitmap(); + PeekableIntIterator iterator = dictIdBitmap.getIntIterator(); + FieldSpec.DataType storedType = dictionary.getValueType(); + switch (storedType) { + case INT: + while (iterator.hasNext()) { + valueBitmap.add(dictionary.getIntValue(iterator.next())); + } + break; + case LONG: + while (iterator.hasNext()) { + valueBitmap.add(Long.hashCode(dictionary.getLongValue(iterator.next()))); + } + break; + case FLOAT: + while (iterator.hasNext()) { + valueBitmap.add(Float.hashCode(dictionary.getFloatValue(iterator.next()))); + } + break; + case DOUBLE: + while (iterator.hasNext()) { + valueBitmap.add(Double.hashCode(dictionary.getDoubleValue(iterator.next()))); + } + break; + case STRING: + while (iterator.hasNext()) { + valueBitmap.add(dictionary.getStringValue(iterator.next()).hashCode()); + } + break; + default: + throw new IllegalArgumentException("Illegal data type for FUNNEL_COUNT aggregation function: " + storedType); + } + return valueBitmap; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/DictIdsWrapper.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/DictIdsWrapper.java new file mode 100644 index 000000000000..c09d0128f297 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/DictIdsWrapper.java @@ -0,0 +1,36 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.funnel; + +import org.apache.pinot.segment.spi.index.reader.Dictionary; +import org.roaringbitmap.RoaringBitmap; + + +final class DictIdsWrapper { + final Dictionary _dictionary; + final RoaringBitmap[] _stepsBitmaps; + + DictIdsWrapper(int numSteps, Dictionary dictionary) { + _dictionary = dictionary; + _stepsBitmaps = new RoaringBitmap[numSteps]; + for (int n = 0; n < numSteps; n++) { + _stepsBitmaps[n] = new RoaringBitmap(); + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/FunnelCountAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/FunnelCountAggregationFunction.java new file mode 100644 index 000000000000..29b18078fc94 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/FunnelCountAggregationFunction.java @@ -0,0 +1,198 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.funnel; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.ObjectAggregationResultHolder; +import org.apache.pinot.core.query.aggregation.function.AggregationFunction; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.ObjectGroupByResultHolder; +import org.apache.pinot.segment.spi.AggregationFunctionType; + + +/** + * The {@code FunnelCountAggregationFunction} calculates the number of conversions for a given correlation column and + * a list of steps as boolean expressions. + * + * @param Aggregation result accumulated across blocks within segment, kept by result holder. + * @param Intermediate result at segment level (extracted from aforementioned aggregation result). + * + * Example: + * SELECT + * dateTrunc('day', timestamp) AS ts, + * FUNNEL_COUNT( + * STEPS(url = '/addToCart', url = '/checkout', url = '/orderConfirmation'), + * CORRELATE_BY(user_id) + * ) as step_counts + * FROM user_log + * WHERE url in ('/addToCart', '/checkout', '/orderConfirmation') + * GROUP BY 1 + * + * Counting strategies can be controlled via optional SETTINGS options, for example: + * + * FUNNEL_COUNT( + * STEPS(url = '/addToCart', url = '/checkout', url = '/orderConfirmation'), + * CORRELATE_BY(user_id), + * SETTINGS('theta_sketch','nominalEntries=4096') + * ) + * + * Please refer to {@link FunnelCountAggregationFunctionFactory} to learn about counting strategies available. + * + * @see FunnelCountAggregationFunctionFactory + * @see FunnelCountSortedAggregationFunction + */ +public class FunnelCountAggregationFunction implements AggregationFunction { + private final List _expressions; + private final List _stepExpressions; + private final List _correlateByExpressions; + private final int _numSteps; + + private final AggregationStrategy _aggregationStrategy; + private final ResultExtractionStrategy _resultExtractionStrategy; + private final MergeStrategy _mergeStrategy; + + public FunnelCountAggregationFunction(List expressions, List stepExpressions, + List correlateByExpressions, AggregationStrategy aggregationStrategy, + ResultExtractionStrategy resultExtractionStrategy, MergeStrategy mergeStrategy) { + _expressions = expressions; + _stepExpressions = stepExpressions; + _correlateByExpressions = correlateByExpressions; + _aggregationStrategy = aggregationStrategy; + _resultExtractionStrategy = resultExtractionStrategy; + _mergeStrategy = mergeStrategy; + _numSteps = _stepExpressions.size(); + } + + @Override + public String getResultColumnName() { + return getType().getName().toLowerCase() + "(" + _expressions.stream().map(ExpressionContext::toString) + .collect(Collectors.joining(",")) + ")"; + } + + @Override + public List getInputExpressions() { + final List inputs = new ArrayList<>(); + inputs.addAll(_correlateByExpressions); + inputs.addAll(_stepExpressions); + return inputs; + } + + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.FUNNELCOUNT; + } + + @Override + public AggregationResultHolder createAggregationResultHolder() { + return new ObjectAggregationResultHolder(); + } + + @Override + public GroupByResultHolder createGroupByResultHolder(int initialCapacity, int maxCapacity) { + return new ObjectGroupByResultHolder(initialCapacity, maxCapacity); + } + + @Override + public void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + _aggregationStrategy.aggregate(length, aggregationResultHolder, blockValSetMap); + } + + @Override + public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + _aggregationStrategy.aggregateGroupBySV(length, groupKeyArray, groupByResultHolder, blockValSetMap); + } + + @Override + public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + _aggregationStrategy.aggregateGroupByMV(length, groupKeysArray, groupByResultHolder, blockValSetMap); + } + + @Override + public I extractAggregationResult(AggregationResultHolder aggregationResultHolder) { + return _resultExtractionStrategy.extractAggregationResult(aggregationResultHolder); + } + + @Override + public I extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { + return _resultExtractionStrategy.extractGroupByResult(groupByResultHolder, groupKey); + } + + @Override + public I merge(I a, I b) { + if (a == null) { + return b; + } + if (b == null) { + return a; + } + return _mergeStrategy.merge(a, b); + } + + @Override + public ColumnDataType getIntermediateResultColumnType() { + return ColumnDataType.OBJECT; + } + + @Override + public ColumnDataType getFinalResultColumnType() { + return ColumnDataType.LONG_ARRAY; + } + + @Override + public LongArrayList extractFinalResult(I intermediateResult) { + if (intermediateResult == null) { + return new LongArrayList(_numSteps); + } + return _mergeStrategy.extractFinalResult(intermediateResult); + } + + @Override + public LongArrayList mergeFinalResult(LongArrayList finalResult1, LongArrayList finalResult2) { + long[] elements1 = finalResult1.elements(); + long[] elements2 = finalResult2.elements(); + for (int i = 0; i < _numSteps; i++) { + elements1[i] += elements2[i]; + } + return finalResult1; + } + + @Override + public String toExplainString() { + StringBuilder stringBuilder = new StringBuilder(getType().getName()).append('('); + int numArguments = getInputExpressions().size(); + if (numArguments > 0) { + stringBuilder.append(getInputExpressions().get(0).toString()); + for (int i = 1; i < numArguments; i++) { + stringBuilder.append(", ").append(getInputExpressions().get(i).toString()); + } + } + return stringBuilder.append(')').toString(); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/FunnelCountAggregationFunctionFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/FunnelCountAggregationFunctionFactory.java new file mode 100644 index 000000000000..dc9c14b3d4ba --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/FunnelCountAggregationFunctionFactory.java @@ -0,0 +1,272 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.funnel; + +import com.google.common.base.Preconditions; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; +import org.apache.datasketches.theta.Sketch; +import org.apache.datasketches.theta.UpdateSketch; +import org.apache.datasketches.thetacommon.ThetaUtil; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.query.aggregation.function.AggregationFunction; +import org.apache.pinot.core.query.aggregation.function.DistinctCountAggregationFunction; +import org.apache.pinot.core.query.aggregation.function.DistinctCountBitmapAggregationFunction; +import org.apache.pinot.core.query.aggregation.function.DistinctCountThetaSketchAggregationFunction; +import org.apache.pinot.core.query.aggregation.function.SegmentPartitionedDistinctCountAggregationFunction; +import org.roaringbitmap.RoaringBitmap; + + +/** + * The {@code FunnelCountAggregationFunctionFactory} builds a {@code FunnelCountAggregationFunction}. + * Primary role is to validate inputs and select the appropriate aggregation strategy to use based on settings. + * + * There are 5 strategies available, mirroring the corresponding distinct count implementations as per below. + *

+ */ +public class FunnelCountAggregationFunctionFactory implements Supplier { + final List _expressions; + final List _stepExpressions; + final List _correlateByExpressions; + final ExpressionContext _primaryCorrelationCol; + final int _numSteps; + final int _nominalEntries; + final boolean _partitionSetting; + final boolean _sortingSetting; + final boolean _thetaSketchSetting; + final boolean _setSetting; + + public FunnelCountAggregationFunctionFactory(List expressions) { + _expressions = expressions; + Option.validate(expressions); + _correlateByExpressions = Option.CORRELATE_BY.getInputExpressions(expressions); + _primaryCorrelationCol = _correlateByExpressions.get(0); + _stepExpressions = Option.STEPS.getInputExpressions(expressions); + _numSteps = _stepExpressions.size(); + + final List settings = Option.SETTINGS.getLiterals(expressions); + Setting.validate(settings); + _setSetting = Setting.SET.isSet(settings); + _partitionSetting = Setting.PARTITIONED.isSet(settings); + _sortingSetting = Setting.SORTED.isSet(settings); + _thetaSketchSetting = Setting.THETA_SKETCH.isSet(settings); + _nominalEntries = Setting.NOMINAL_ENTRIES.getInteger(settings).orElse(ThetaUtil.DEFAULT_NOMINAL_ENTRIES); + } + + public AggregationFunction get() { + if (_partitionSetting) { + if (_thetaSketchSetting) { + // theta_sketch && partitioned + return createPartionedFunnelCountAggregationFunction(thetaSketchAggregationStrategy(), + thetaSketchPartitionedResultExtractionStrategy(), partitionedMergeStrategy()); + } else { + // partitioned && !theta_sketch + return createPartionedFunnelCountAggregationFunction(bitmapAggregationStrategy(), + bitmapPartitionedResultExtractionStrategy(), partitionedMergeStrategy()); + } + } else { + if (_thetaSketchSetting) { + // theta_sketch && !partitioned + return createFunnelCountAggregationFunction(thetaSketchAggregationStrategy(), + thetaSketchResultExtractionStrategy(), thetaSketchMergeStrategy()); + } else if (_setSetting) { + // set && !partitioned && !theta_sketch + return createFunnelCountAggregationFunction(bitmapAggregationStrategy(), setResultExtractionStrategy(), + setMergeStrategy()); + } else { + // default (bitmap) + // !partitioned && !theta_sketch && !set + return createFunnelCountAggregationFunction(bitmapAggregationStrategy(), bitmapResultExtractionStrategy(), + bitmapMergeStrategy()); + } + } + } + + private FunnelCountAggregationFunction createFunnelCountAggregationFunction( + AggregationStrategy aggregationStrategy, ResultExtractionStrategy resultExtractionStrategy, + MergeStrategy mergeStrategy) { + return new FunnelCountAggregationFunction<>(_expressions, _stepExpressions, _correlateByExpressions, + aggregationStrategy, resultExtractionStrategy, mergeStrategy); + } + + private FunnelCountAggregationFunction> createPartionedFunnelCountAggregationFunction( + AggregationStrategy aggregationStrategy, ResultExtractionStrategy> resultExtractionStrategy, + MergeStrategy> mergeStrategy) { + if (_sortingSetting) { + return new FunnelCountSortedAggregationFunction<>(_expressions, _stepExpressions, _correlateByExpressions, + aggregationStrategy, resultExtractionStrategy, mergeStrategy); + } else { + return new FunnelCountAggregationFunction<>(_expressions, _stepExpressions, _correlateByExpressions, + aggregationStrategy, resultExtractionStrategy, mergeStrategy); + } + } + + AggregationStrategy thetaSketchAggregationStrategy() { + return new ThetaSketchAggregationStrategy(_stepExpressions, _correlateByExpressions, _nominalEntries); + } + + AggregationStrategy bitmapAggregationStrategy() { + return new BitmapAggregationStrategy(_stepExpressions, _correlateByExpressions); + } + + MergeStrategy> thetaSketchMergeStrategy() { + return new ThetaSketchMergeStrategy(_numSteps, _nominalEntries); + } + + MergeStrategy> setMergeStrategy() { + return new SetMergeStrategy(_numSteps); + } + + MergeStrategy> bitmapMergeStrategy() { + return new BitmapMergeStrategy(_numSteps); + } + + MergeStrategy> partitionedMergeStrategy() { + return new PartitionedMergeStrategy(_numSteps); + } + + ResultExtractionStrategy> thetaSketchResultExtractionStrategy() { + return new ThetaSketchResultExtractionStrategy(_numSteps); + } + + ResultExtractionStrategy> setResultExtractionStrategy() { + return new SetResultExtractionStrategy(_numSteps); + } + + ResultExtractionStrategy> bitmapResultExtractionStrategy() { + return new BitmapResultExtractionStrategy(_numSteps); + } + + ResultExtractionStrategy> bitmapPartitionedResultExtractionStrategy() { + final MergeStrategy> bitmapMergeStrategy = bitmapMergeStrategy(); + return dictIdsWrapper -> bitmapMergeStrategy.extractFinalResult(Arrays.asList(dictIdsWrapper._stepsBitmaps)); + } + + ResultExtractionStrategy> thetaSketchPartitionedResultExtractionStrategy() { + final MergeStrategy> thetaSketchMergeStrategy = thetaSketchMergeStrategy(); + return sketches -> thetaSketchMergeStrategy.extractFinalResult(Arrays.asList(sketches)); + } + + enum Option { + STEPS("steps"), CORRELATE_BY("correlateby"), SETTINGS("settings"); + + final String _name; + + Option(String name) { + _name = name; + } + + public static void validate(List expressions) { + final List invalidOptions = expressions.stream() + .filter(expression -> !Arrays.stream(Option.values()).anyMatch(option -> option.matches(expression))) + .map(ExpressionContext::toString).collect(Collectors.toList()); + + if (!invalidOptions.isEmpty()) { + throw new IllegalArgumentException("Invalid FUNNELCOUNT options: " + String.join(", ", invalidOptions)); + } + } + + boolean matches(ExpressionContext expression) { + if (expression.getType() != ExpressionContext.Type.FUNCTION) { + return false; + } + return _name.equals(expression.getFunction().getFunctionName()); + } + + Optional find(List expressions) { + return expressions.stream().filter(this::matches).findFirst(); + } + + public List getInputExpressions(List expressions) { + final List inputExpressions = + this.find(expressions).map(exp -> exp.getFunction().getArguments()) + .orElseThrow(() -> new IllegalArgumentException("FUNNELCOUNT requires " + _name)); + Preconditions.checkArgument(!inputExpressions.isEmpty(), "FUNNELCOUNT: " + _name + " requires an argument."); + return inputExpressions; + } + + public List getLiterals(List expressions) { + List inputExpressions = + find(expressions).map(exp -> exp.getFunction().getArguments()).orElseGet(Collections::emptyList); + Preconditions.checkArgument( + inputExpressions.stream().allMatch(exp -> exp.getType() == ExpressionContext.Type.LITERAL), + "FUNNELCOUNT: " + _name + " parameters must be literals"); + return inputExpressions.stream().map(exp -> exp.getLiteral().getStringValue()).collect(Collectors.toList()); + } + } + + enum Setting { + SET("set"), + BITMAP("bitmap"), + PARTITIONED("partitioned"), + SORTED("sorted"), + THETA_SKETCH("theta_sketch"), + NOMINAL_ENTRIES("nominalEntries"); + + private static final char KEY_VALUE_SEPARATOR = '='; + final String _name; + + Setting(String name) { + _name = name.toLowerCase(); + } + + public static void validate(List settings) { + final List invalidSettings = settings.stream().filter(param -> !Arrays.stream(Setting.values()) + .anyMatch(setting -> setting.matchesKV(param) || setting.matches(param))).collect(Collectors.toList()); + + if (!invalidSettings.isEmpty()) { + throw new IllegalArgumentException("Invalid FUNNELCOUNT SETTINGS: " + String.join(", ", invalidSettings)); + } + } + + boolean matchesKV(String setting) { + return StringUtils.deleteWhitespace(setting).toLowerCase().startsWith(_name + KEY_VALUE_SEPARATOR); + } + + boolean matches(String setting) { + return StringUtils.deleteWhitespace(setting).toLowerCase().equals(_name); + } + + public Optional getString(List settings) { + return settings.stream().filter(this::matchesKV).findFirst() + .map(setting -> setting.substring(_name.length() + 1)); + } + + public Optional getInteger(List settings) { + return getString(settings).map(Integer::parseInt); + } + + public boolean isSet(List settings) { + return settings.stream().anyMatch(this::matches) || getString(settings).map(Boolean::parseBoolean).orElse(false); + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/FunnelCountSortedAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/FunnelCountSortedAggregationFunction.java new file mode 100644 index 000000000000..ac39461cef54 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/FunnelCountSortedAggregationFunction.java @@ -0,0 +1,129 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.funnel; + +import com.google.common.base.Preconditions; +import java.util.List; +import java.util.Map; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.segment.spi.index.reader.Dictionary; + + +/** + * The {@code FunnelCountSortedAggregationFunction} calculates the number of conversions for a given correlation column + * and a list of steps as boolean expressions. + * It leverages a more efficient counting strategy for segments sorted by correlate_by column, falls back to a regular + * counting strategy for unsorted segments (e.g. uncommitted segments). + * + * Example: + * SELECT + * dateTrunc('day', timestamp) AS ts, + * FUNNEL_COUNT( + * STEPS(url = '/addToCart', url = '/checkout', url = '/orderConfirmation'), + * CORRELATE_BY(user_id), + * SETTINGS('partitioned','sorted') + * ) as step_counts + * FROM user_log + * WHERE url in ('/addToCart', '/checkout', '/orderConfirmation') + * GROUP BY 1 + * + */ +public class FunnelCountSortedAggregationFunction extends FunnelCountAggregationFunction> { + private final ExpressionContext _primaryCorrelationCol; + private final AggregationStrategy _sortedAggregationStrategy; + private final ResultExtractionStrategy> _sortedResultExtractionStrategy; + + public FunnelCountSortedAggregationFunction(List expressions, + List stepExpressions, List correlateByExpressions, + AggregationStrategy aggregationStrategy, ResultExtractionStrategy> resultExtractionStrategy, + MergeStrategy> mergeStrategy) { + super(expressions, stepExpressions, correlateByExpressions, aggregationStrategy, resultExtractionStrategy, + mergeStrategy); + _sortedAggregationStrategy = new SortedAggregationStrategy(stepExpressions, correlateByExpressions); + _sortedResultExtractionStrategy = SortedAggregationResult::extractResult;; + _primaryCorrelationCol = correlateByExpressions.get(0); + } + + @Override + public void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + if (isSortedDictionary(blockValSetMap)) { + _sortedAggregationStrategy.aggregate(length, aggregationResultHolder, blockValSetMap); + } else { + super.aggregate(length, aggregationResultHolder, blockValSetMap); + } + } + + @Override + public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + if (isSortedDictionary(blockValSetMap)) { + _sortedAggregationStrategy.aggregateGroupBySV(length, groupKeyArray, groupByResultHolder, blockValSetMap); + } else { + super.aggregateGroupBySV(length, groupKeyArray, groupByResultHolder, blockValSetMap); + } + } + + @Override + public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + if (isSortedDictionary(blockValSetMap)) { + _sortedAggregationStrategy.aggregateGroupByMV(length, groupKeysArray, groupByResultHolder, blockValSetMap); + } else { + super.aggregateGroupByMV(length, groupKeysArray, groupByResultHolder, blockValSetMap); + } + } + + @Override + public List extractAggregationResult(AggregationResultHolder aggregationResultHolder) { + if (isSortedAggResult(aggregationResultHolder.getResult())) { + return _sortedResultExtractionStrategy.extractAggregationResult(aggregationResultHolder); + } else { + return super.extractAggregationResult(aggregationResultHolder); + } + } + + @Override + public List extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { + if (isSortedAggResult(groupByResultHolder.getResult(groupKey))) { + return _sortedResultExtractionStrategy.extractGroupByResult(groupByResultHolder, groupKey); + } else { + return super.extractGroupByResult(groupByResultHolder, groupKey); + } + } + + private boolean isSortedDictionary(Map blockValSetMap) { + return getDictionary(blockValSetMap).isSorted(); + } + + private boolean isSortedAggResult(Object aggResult) { + return aggResult instanceof SortedAggregationResult; + } + + private Dictionary getDictionary(Map blockValSetMap) { + final Dictionary primaryCorrelationDictionary = blockValSetMap.get(_primaryCorrelationCol).getDictionary(); + Preconditions.checkArgument(primaryCorrelationDictionary != null, + "CORRELATE_BY column in FUNNELCOUNT aggregation function not supported for sorted setting, " + + "please use a dictionary encoded column."); + return primaryCorrelationDictionary; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/FunnelStepEvent.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/FunnelStepEvent.java new file mode 100644 index 000000000000..6518dc112bed --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/FunnelStepEvent.java @@ -0,0 +1,107 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.funnel; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; + + +public class FunnelStepEvent implements Comparable { + public final static int SIZE_IN_BYTES = Long.BYTES + Integer.BYTES; + + private final long _timestamp; + private final int _step; + + public FunnelStepEvent(long timestamp, int step) { + _timestamp = timestamp; + _step = step; + } + + public FunnelStepEvent(byte[] bytes) { + try (DataInputStream dataInputStream = new DataInputStream(new ByteArrayInputStream(bytes))) { + _timestamp = dataInputStream.readLong(); + _step = dataInputStream.readInt(); + } catch (Exception e) { + throw new RuntimeException("Caught exception while converting byte[] to FunnelStepEvent", e); + } + } + + public long getTimestamp() { + return _timestamp; + } + + public int getStep() { + return _step; + } + + @Override + public String toString() { + return "StepEvent{" + "timestamp=" + _timestamp + ", step=" + _step + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + FunnelStepEvent stepEvent = (FunnelStepEvent) o; + + if (_timestamp != stepEvent._timestamp) { + return false; + } + return _step == stepEvent._step; + } + + @Override + public int hashCode() { + int result = Long.hashCode(_timestamp); + result = 31 * result + _step; + return result; + } + + @Override + public int compareTo(FunnelStepEvent o) { + if (_timestamp < o._timestamp) { + return -1; + } else if (_timestamp > o._timestamp) { + return 1; + } else { + return Integer.compare(_step, o._step); + } + } + + public byte[] getBytes() { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream); + try { + dataOutputStream.writeLong(_timestamp); + dataOutputStream.writeInt(_step); + dataOutputStream.close(); + } catch (Exception e) { + throw new RuntimeException("Caught exception while converting FunnelStepEvent to byte[]", e); + } + return byteArrayOutputStream.toByteArray(); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/MergeStrategy.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/MergeStrategy.java new file mode 100644 index 000000000000..47eaa57e6ff1 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/MergeStrategy.java @@ -0,0 +1,37 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.funnel; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import javax.annotation.concurrent.ThreadSafe; + + +/** + * Interface for cross-segment merge strategy. + * + *

The implementation should be stateless, and can be shared among multiple segments in multiple threads. + * + * @param Intermediate result at segment level (extracted from aggregation strategy result). + */ +@ThreadSafe +interface MergeStrategy { + I merge(I intermediateResult1, I intermediateResult2); + + LongArrayList extractFinalResult(I intermediateResult); +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/PartitionedMergeStrategy.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/PartitionedMergeStrategy.java new file mode 100644 index 000000000000..15ea02e09038 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/PartitionedMergeStrategy.java @@ -0,0 +1,50 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.funnel; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import java.util.List; + + +class PartitionedMergeStrategy implements MergeStrategy> { + protected final int _numSteps; + + PartitionedMergeStrategy(int numSteps) { + _numSteps = numSteps; + } + + @Override + public List merge(List a, List b) { + LongArrayList result = toLongArrayList(a); + long[] elements = result.elements(); + for (int i = 0; i < _numSteps; i++) { + elements[i] += b.get(i); + } + return result; + } + + @Override + public LongArrayList extractFinalResult(List intermediateResult) { + return toLongArrayList(intermediateResult); + } + + private LongArrayList toLongArrayList(List longList) { + return longList instanceof LongArrayList ? ((LongArrayList) longList).clone() : new LongArrayList(longList); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/ResultExtractionStrategy.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/ResultExtractionStrategy.java new file mode 100644 index 000000000000..7c35251640c8 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/ResultExtractionStrategy.java @@ -0,0 +1,52 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.funnel; + +import javax.annotation.concurrent.ThreadSafe; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; + + +/** + * Interface for segment aggregation result extraction strategy. + * + *

The implementation should be stateless, and can be shared among multiple segments in multiple threads. + * + * @param Aggregation result accumulated across blocks within segment, kept by result holder. + * @param Intermediate result at segment level (extracted from aforementioned aggregation result). + */ +@ThreadSafe +interface ResultExtractionStrategy { + + /** + * Extracts the intermediate result from the aggregation result holder (aggregation only). + */ + default I extractAggregationResult(AggregationResultHolder aggregationResultHolder) { + return extractIntermediateResult(aggregationResultHolder.getResult()); + } + + /** + * Extracts the intermediate result from the group-by result holder for the given group key (aggregation group-by). + */ + default I extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { + return extractIntermediateResult(groupByResultHolder.getResult(groupKey)); + } + + I extractIntermediateResult(A aggregationResult); +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/SetMergeStrategy.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/SetMergeStrategy.java new file mode 100644 index 000000000000..d5ae4bc987f7 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/SetMergeStrategy.java @@ -0,0 +1,52 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.funnel; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import java.util.List; +import java.util.Set; + + +class SetMergeStrategy implements MergeStrategy> { + protected final int _numSteps; + + SetMergeStrategy(int numSteps) { + _numSteps = numSteps; + } + + @Override + public List merge(List intermediateResult1, List intermediateResult2) { + for (int i = 0; i < _numSteps; i++) { + intermediateResult1.get(i).addAll(intermediateResult2.get(i)); + } + return intermediateResult1; + } + + @Override + public LongArrayList extractFinalResult(List stepsSets) { + long[] result = new long[_numSteps]; + result[0] = stepsSets.get(0).size(); + for (int i = 1; i < _numSteps; i++) { + // intersect this step with previous step + stepsSets.get(i).retainAll(stepsSets.get(i - 1)); + result[i] = stepsSets.get(i).size(); + } + return LongArrayList.wrap(result); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/SetResultExtractionStrategy.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/SetResultExtractionStrategy.java new file mode 100644 index 000000000000..09ad7adad803 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/SetResultExtractionStrategy.java @@ -0,0 +1,94 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.funnel; + +import it.unimi.dsi.fastutil.doubles.DoubleOpenHashSet; +import it.unimi.dsi.fastutil.floats.FloatOpenHashSet; +import it.unimi.dsi.fastutil.ints.IntOpenHashSet; +import it.unimi.dsi.fastutil.longs.LongOpenHashSet; +import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import org.apache.pinot.segment.spi.index.reader.Dictionary; +import org.apache.pinot.spi.data.FieldSpec; +import org.roaringbitmap.PeekableIntIterator; +import org.roaringbitmap.RoaringBitmap; + + +/** + * Aggregation strategy leveraging set algebra (unions/intersections). + */ +class SetResultExtractionStrategy implements ResultExtractionStrategy> { + protected final int _numSteps; + + SetResultExtractionStrategy(int numSteps) { + _numSteps = numSteps; + } + + @Override + public List extractIntermediateResult(DictIdsWrapper dictIdsWrapper) { + Dictionary dictionary = dictIdsWrapper._dictionary; + List result = new ArrayList<>(_numSteps); + for (RoaringBitmap dictIdBitmap : dictIdsWrapper._stepsBitmaps) { + result.add(convertToValueSet(dictionary, dictIdBitmap)); + } + return result; + } + + private Set convertToValueSet(Dictionary dictionary, RoaringBitmap dictIdBitmap) { + int numValues = dictIdBitmap.getCardinality(); + PeekableIntIterator iterator = dictIdBitmap.getIntIterator(); + FieldSpec.DataType storedType = dictionary.getValueType(); + switch (storedType) { + case INT: + IntOpenHashSet intSet = new IntOpenHashSet(numValues); + while (iterator.hasNext()) { + intSet.add(dictionary.getIntValue(iterator.next())); + } + return intSet; + case LONG: + LongOpenHashSet longSet = new LongOpenHashSet(numValues); + while (iterator.hasNext()) { + longSet.add(dictionary.getLongValue(iterator.next())); + } + return longSet; + case FLOAT: + FloatOpenHashSet floatSet = new FloatOpenHashSet(numValues); + while (iterator.hasNext()) { + floatSet.add(dictionary.getFloatValue(iterator.next())); + } + return floatSet; + case DOUBLE: + DoubleOpenHashSet doubleSet = new DoubleOpenHashSet(numValues); + while (iterator.hasNext()) { + doubleSet.add(dictionary.getDoubleValue(iterator.next())); + } + return doubleSet; + case STRING: + ObjectOpenHashSet stringSet = new ObjectOpenHashSet<>(numValues); + while (iterator.hasNext()) { + stringSet.add(dictionary.getStringValue(iterator.next())); + } + return stringSet; + default: + throw new IllegalArgumentException("Illegal data type for FUNNEL_COUNT aggregation function: " + storedType); + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/SortedAggregationResult.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/SortedAggregationResult.java new file mode 100644 index 000000000000..eb773eac7ed0 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/SortedAggregationResult.java @@ -0,0 +1,67 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.funnel; + +import it.unimi.dsi.fastutil.longs.LongArrayList; + + +/** + * Aggregation result data structure leveraged by sorted aggregation strategy. + */ +class SortedAggregationResult { + final int _numSteps; + final long[] _stepCounters; + final boolean[] _correlatedSteps; + int _lastCorrelationId = Integer.MIN_VALUE; + + SortedAggregationResult(int numSteps) { + _numSteps = numSteps; + _stepCounters = new long[_numSteps]; + _correlatedSteps = new boolean[_numSteps]; + } + + public void add(int step, int correlationId) { + if (correlationId != _lastCorrelationId) { + // End of correlation group, calculate funnel conversion counts + incrStepCounters(); + + // initialize next correlation group + for (int n = 0; n < _numSteps; n++) { + _correlatedSteps[n] = false; + } + _lastCorrelationId = correlationId; + } + _correlatedSteps[step] = true; + } + + void incrStepCounters() { + for (int n = 0; n < _numSteps; n++) { + if (!_correlatedSteps[n]) { + break; + } + _stepCounters[n]++; + } + } + + public LongArrayList extractResult() { + // count last correlation id left open + incrStepCounters(); + return LongArrayList.wrap(_stepCounters); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/SortedAggregationStrategy.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/SortedAggregationStrategy.java new file mode 100644 index 000000000000..533d8723a74b --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/SortedAggregationStrategy.java @@ -0,0 +1,44 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.funnel; + +import java.util.List; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.segment.spi.index.reader.Dictionary; + + +/** + * Aggregation strategy for segments partitioned and sorted by the main correlation column. + */ +class SortedAggregationStrategy extends AggregationStrategy { + public SortedAggregationStrategy(List stepExpressions, + List correlateByExpressions) { + super(stepExpressions, correlateByExpressions); + } + + @Override + public SortedAggregationResult createAggregationResult(Dictionary dictionary) { + return new SortedAggregationResult(_numSteps); + } + + @Override + void add(Dictionary dictionary, SortedAggregationResult aggResult, int step, int correlationId) { + aggResult.add(step, correlationId); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/ThetaSketchAggregationStrategy.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/ThetaSketchAggregationStrategy.java new file mode 100644 index 000000000000..a2ac25f86775 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/ThetaSketchAggregationStrategy.java @@ -0,0 +1,73 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.funnel; + +import java.util.List; +import org.apache.datasketches.theta.UpdateSketch; +import org.apache.datasketches.theta.UpdateSketchBuilder; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.segment.spi.index.reader.Dictionary; + + +/** + * Aggregation strategy leveraging theta sketch algebra (unions/intersections). + */ +class ThetaSketchAggregationStrategy extends AggregationStrategy { + final UpdateSketchBuilder _updateSketchBuilder; + + public ThetaSketchAggregationStrategy(List stepExpressions, + List correlateByExpressions, int nominalEntries) { + super(stepExpressions, correlateByExpressions); + _updateSketchBuilder = new UpdateSketchBuilder().setNominalEntries(nominalEntries); + } + + @Override + public UpdateSketch[] createAggregationResult(Dictionary dictionary) { + final UpdateSketch[] stepsSketches = new UpdateSketch[_numSteps]; + for (int n = 0; n < _numSteps; n++) { + stepsSketches[n] = _updateSketchBuilder.build(); + } + return stepsSketches; + } + + @Override + void add(Dictionary dictionary, UpdateSketch[] stepsSketches, int step, int correlationId) { + final UpdateSketch sketch = stepsSketches[step]; + switch (dictionary.getValueType()) { + case INT: + sketch.update(dictionary.getIntValue(correlationId)); + break; + case LONG: + sketch.update(dictionary.getLongValue(correlationId)); + break; + case FLOAT: + sketch.update(dictionary.getFloatValue(correlationId)); + break; + case DOUBLE: + sketch.update(dictionary.getDoubleValue(correlationId)); + break; + case STRING: + sketch.update(dictionary.getStringValue(correlationId)); + break; + default: + throw new IllegalStateException("Illegal CORRELATED_BY column data type for FUNNEL_COUNT aggregation function: " + + dictionary.getValueType()); + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/ThetaSketchMergeStrategy.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/ThetaSketchMergeStrategy.java new file mode 100644 index 000000000000..80378d7078d7 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/ThetaSketchMergeStrategy.java @@ -0,0 +1,62 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.funnel; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import java.util.ArrayList; +import java.util.List; +import org.apache.datasketches.theta.Intersection; +import org.apache.datasketches.theta.SetOperationBuilder; +import org.apache.datasketches.theta.Sketch; + + +class ThetaSketchMergeStrategy implements MergeStrategy> { + protected final int _numSteps; + final SetOperationBuilder _setOperationBuilder; + + ThetaSketchMergeStrategy(int numSteps, int nominalEntries) { + _numSteps = numSteps; + _setOperationBuilder = new SetOperationBuilder().setNominalEntries(nominalEntries); + } + + @Override + public List merge(List sketches1, List sketches2) { + final List mergedSketches = new ArrayList<>(_numSteps); + for (int i = 0; i < _numSteps; i++) { + // NOTE: Compact the sketch in unsorted, on-heap fashion for performance concern. + // See https://datasketches.apache.org/docs/Theta/ThetaSize.html for more details. + mergedSketches.add(_setOperationBuilder.buildUnion().union(sketches1.get(i), sketches2.get(i), false, null)); + } + return mergedSketches; + } + + @Override + public LongArrayList extractFinalResult(List sketches) { + long[] result = new long[_numSteps]; + + Sketch sketch = sketches.get(0); + result[0] = Math.round(sketch.getEstimate()); + for (int i = 1; i < _numSteps; i++) { + Intersection intersection = _setOperationBuilder.buildIntersection(); + sketch = intersection.intersect(sketch, sketches.get(i)); + result[i] = Math.round(sketch.getEstimate()); + } + return LongArrayList.wrap(result); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/ThetaSketchResultExtractionStrategy.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/ThetaSketchResultExtractionStrategy.java new file mode 100644 index 000000000000..2c794271bf21 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/ThetaSketchResultExtractionStrategy.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.funnel; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.apache.datasketches.theta.Sketch; +import org.apache.datasketches.theta.UpdateSketch; +import org.apache.datasketches.theta.UpdateSketchBuilder; + + +class ThetaSketchResultExtractionStrategy implements ResultExtractionStrategy> { + private static final Sketch EMPTY_SKETCH = new UpdateSketchBuilder().build().compact(); + + protected final int _numSteps; + + ThetaSketchResultExtractionStrategy(int numSteps) { + _numSteps = numSteps; + } + + @Override + public List extractIntermediateResult(UpdateSketch[] stepsSketches) { + if (stepsSketches == null) { + return Collections.nCopies(_numSteps, EMPTY_SKETCH); + } + return Arrays.asList(stepsSketches); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/window/FunnelBaseAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/window/FunnelBaseAggregationFunction.java new file mode 100644 index 000000000000..502a49cdbecb --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/window/FunnelBaseAggregationFunction.java @@ -0,0 +1,304 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.funnel.window; + +import com.google.common.base.Preconditions; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.PriorityQueue; +import java.util.stream.Collectors; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; +import org.apache.pinot.core.query.aggregation.ObjectAggregationResultHolder; +import org.apache.pinot.core.query.aggregation.function.AggregationFunction; +import org.apache.pinot.core.query.aggregation.function.funnel.FunnelStepEvent; +import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.ObjectGroupByResultHolder; + + +public abstract class FunnelBaseAggregationFunction + implements AggregationFunction, F> { + protected final ExpressionContext _timestampExpression; + protected final long _windowSize; + protected final List _stepExpressions; + protected final FunnelModes _modes = new FunnelModes(); + protected final int _numSteps; + + public FunnelBaseAggregationFunction(List arguments) { + int numArguments = arguments.size(); + Preconditions.checkArgument(numArguments > 3, + "FUNNEL_AGG_FUNC expects >= 4 arguments, got: %s. The function can be used as " + + getType().getName() + "(timestampExpression, windowSize, numberSteps, stepExpression, " + + "[stepExpression, ..], [mode, [mode, ... ]])", + numArguments); + _timestampExpression = arguments.get(0); + _windowSize = arguments.get(1).getLiteral().getLongValue(); + Preconditions.checkArgument(_windowSize > 0, "Window size must be > 0"); + _numSteps = arguments.get(2).getLiteral().getIntValue(); + Preconditions.checkArgument(numArguments >= 3 + _numSteps, + "FUNNEL_AGG_FUNC expects >= " + (3 + _numSteps) + " arguments, got: %s. The function can be used as " + + getType().getName() + "(timestampExpression, windowSize, numberSteps, stepExpression, " + + "[stepExpression, ..], [mode, [mode, ... ]])", + numArguments); + _stepExpressions = arguments.subList(3, 3 + _numSteps); + if (numArguments > 3 + _numSteps) { + arguments.subList(3 + _numSteps, numArguments) + .forEach(arg -> _modes.add(Mode.valueOf(arg.getLiteral().getStringValue().toUpperCase()))); + } + } + + @Override + public String getResultColumnName() { + return getType().getName().toLowerCase() + "(" + _windowSize + ") (" + _timestampExpression.toString() + ", " + + _stepExpressions.stream().map(ExpressionContext::toString).collect(Collectors.joining(",")) + ")"; + } + + @Override + public List getInputExpressions() { + List inputs = new ArrayList<>(1 + _numSteps); + inputs.add(_timestampExpression); + inputs.addAll(_stepExpressions); + return inputs; + } + + @Override + public AggregationResultHolder createAggregationResultHolder() { + return new ObjectAggregationResultHolder(); + } + + @Override + public GroupByResultHolder createGroupByResultHolder(int initialCapacity, int maxCapacity) { + return new ObjectGroupByResultHolder(initialCapacity, maxCapacity); + } + + @Override + public void aggregate(int length, AggregationResultHolder aggregationResultHolder, + Map blockValSetMap) { + long[] timestampBlock = blockValSetMap.get(_timestampExpression).getLongValuesSV(); + List stepBlocks = new ArrayList<>(_numSteps); + for (ExpressionContext stepExpression : _stepExpressions) { + stepBlocks.add(blockValSetMap.get(stepExpression).getIntValuesSV()); + } + PriorityQueue stepEvents = aggregationResultHolder.getResult(); + if (stepEvents == null) { + stepEvents = new PriorityQueue<>(); + aggregationResultHolder.setValue(stepEvents); + } + for (int i = 0; i < length; i++) { + boolean stepFound = false; + for (int j = 0; j < _numSteps; j++) { + if (stepBlocks.get(j)[i] == 1) { + stepEvents.add(new FunnelStepEvent(timestampBlock[i], j)); + stepFound = true; + break; + } + } + // If the mode is KEEP_ALL and no step is found, add a dummy step event with step -1 + if (_modes.hasKeepAll() && !stepFound) { + stepEvents.add(new FunnelStepEvent(timestampBlock[i], -1)); + } + } + } + + @Override + public void aggregateGroupBySV(int length, int[] groupKeyArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + long[] timestampBlock = blockValSetMap.get(_timestampExpression).getLongValuesSV(); + List stepBlocks = new ArrayList<>(_numSteps); + for (ExpressionContext stepExpression : _stepExpressions) { + stepBlocks.add(blockValSetMap.get(stepExpression).getIntValuesSV()); + } + for (int i = 0; i < length; i++) { + int groupKey = groupKeyArray[i]; + boolean stepFound = false; + for (int j = 0; j < _numSteps; j++) { + if (stepBlocks.get(j)[i] == 1) { + PriorityQueue stepEvents = getFunnelStepEvents(groupByResultHolder, groupKey); + stepEvents.add(new FunnelStepEvent(timestampBlock[i], j)); + stepFound = true; + break; + } + } + // If the mode is KEEP_ALL and no step is found, add a dummy step event with step -1 + if (_modes.hasKeepAll() && !stepFound) { + PriorityQueue stepEvents = getFunnelStepEvents(groupByResultHolder, groupKey); + stepEvents.add(new FunnelStepEvent(timestampBlock[i], -1)); + } + } + } + + @Override + public void aggregateGroupByMV(int length, int[][] groupKeysArray, GroupByResultHolder groupByResultHolder, + Map blockValSetMap) { + long[] timestampBlock = blockValSetMap.get(_timestampExpression).getLongValuesSV(); + List stepBlocks = new ArrayList<>(_numSteps); + for (ExpressionContext stepExpression : _stepExpressions) { + stepBlocks.add(blockValSetMap.get(stepExpression).getIntValuesSV()); + } + for (int i = 0; i < length; i++) { + int[] groupKeys = groupKeysArray[i]; + boolean stepFound = false; + for (int j = 0; j < _numSteps; j++) { + if (stepBlocks.get(j)[i] == 1) { + for (int groupKey : groupKeys) { + PriorityQueue stepEvents = getFunnelStepEvents(groupByResultHolder, groupKey); + stepEvents.add(new FunnelStepEvent(timestampBlock[i], j)); + } + stepFound = true; + break; + } + } + // If the mode is KEEP_ALL and no step is found, add a dummy step event with step -1 + if (_modes.hasKeepAll() && !stepFound) { + for (int groupKey : groupKeys) { + PriorityQueue stepEvents = getFunnelStepEvents(groupByResultHolder, groupKey); + stepEvents.add(new FunnelStepEvent(timestampBlock[i], -1)); + } + } + } + } + + private static PriorityQueue getFunnelStepEvents(GroupByResultHolder groupByResultHolder, + int groupKey) { + PriorityQueue stepEvents = groupByResultHolder.getResult(groupKey); + if (stepEvents == null) { + stepEvents = new PriorityQueue<>(); + groupByResultHolder.setValueForKey(groupKey, stepEvents); + } + return stepEvents; + } + + @Override + public PriorityQueue extractAggregationResult(AggregationResultHolder aggregationResultHolder) { + return aggregationResultHolder.getResult(); + } + + @Override + public PriorityQueue extractGroupByResult(GroupByResultHolder groupByResultHolder, int groupKey) { + return groupByResultHolder.getResult(groupKey); + } + + @Override + public PriorityQueue merge(PriorityQueue intermediateResult1, + PriorityQueue intermediateResult2) { + if (intermediateResult1 == null) { + return intermediateResult2; + } + if (intermediateResult2 == null) { + return intermediateResult1; + } + intermediateResult1.addAll(intermediateResult2); + return intermediateResult1; + } + + @Override + public DataSchema.ColumnDataType getIntermediateResultColumnType() { + return DataSchema.ColumnDataType.OBJECT; + } + + /** + * Fill the sliding window with the events that fall into the window. + * Note that the events from stepEvents are dequeued and added to the sliding window. + * This method ensure the first event from the sliding window is the first step event. + * @param stepEvents The priority queue of step events + * @param slidingWindow The sliding window with events that fall into the window + */ + protected void fillWindow(PriorityQueue stepEvents, ArrayDeque slidingWindow) { + // Ensure for the sliding window, the first event is the first step + while ((!slidingWindow.isEmpty()) && slidingWindow.peek().getStep() != 0) { + slidingWindow.pollFirst(); + } + if (slidingWindow.isEmpty()) { + while (!stepEvents.isEmpty() && stepEvents.peek().getStep() != 0) { + stepEvents.poll(); + } + if (stepEvents.isEmpty()) { + return; + } + slidingWindow.addLast(stepEvents.poll()); + } + // SlidingWindow is not empty + long windowStart = slidingWindow.peek().getTimestamp(); + long windowEnd = windowStart + _windowSize; + while (!stepEvents.isEmpty() && (stepEvents.peek().getTimestamp() < windowEnd)) { + slidingWindow.addLast(stepEvents.poll()); + } + } + + @Override + public String toExplainString() { + //@formatter:off + return getType().getName() + "{" + + "timestampExpression=" + _timestampExpression + + ", windowSize=" + _windowSize + + ", stepExpressions=" + _stepExpressions + + '}'; + //@formatter:on + } + + protected enum Mode { + STRICT_DEDUPLICATION(1), STRICT_ORDER(2), STRICT_INCREASE(4), KEEP_ALL(8); + + private final int _value; + + Mode(int value) { + _value = value; + } + + public int getValue() { + return _value; + } + } + + protected static class FunnelModes { + private int _bitmask = 0; + + public void add(Mode mode) { + _bitmask |= mode.getValue(); + } + + public void remove(Mode mode) { + _bitmask &= ~mode.getValue(); + } + + public boolean contains(Mode mode) { + return (_bitmask & mode.getValue()) != 0; + } + + public boolean hasStrictDeduplication() { + return contains(Mode.STRICT_DEDUPLICATION); + } + + public boolean hasStrictOrder() { + return contains(Mode.STRICT_ORDER); + } + + public boolean hasStrictIncrease() { + return contains(Mode.STRICT_INCREASE); + } + + public boolean hasKeepAll() { + return contains(Mode.KEEP_ALL); + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/window/FunnelCompleteCountAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/window/FunnelCompleteCountAggregationFunction.java new file mode 100644 index 000000000000..0f1d12e269ac --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/window/FunnelCompleteCountAggregationFunction.java @@ -0,0 +1,110 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.funnel.window; + +import java.util.ArrayDeque; +import java.util.List; +import java.util.PriorityQueue; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.query.aggregation.function.funnel.FunnelStepEvent; +import org.apache.pinot.segment.spi.AggregationFunctionType; + + +public class FunnelCompleteCountAggregationFunction extends FunnelBaseAggregationFunction { + + public FunnelCompleteCountAggregationFunction(List arguments) { + super(arguments); + } + + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.FUNNELCOMPLETECOUNT; + } + + @Override + public DataSchema.ColumnDataType getFinalResultColumnType() { + return DataSchema.ColumnDataType.INT; + } + + @Override + public Integer extractFinalResult(PriorityQueue stepEvents) { + int totalCompletedRounds = 0; + if (stepEvents == null || stepEvents.isEmpty()) { + return totalCompletedRounds; + } + ArrayDeque slidingWindow = new ArrayDeque<>(); + while (!stepEvents.isEmpty()) { + fillWindow(stepEvents, slidingWindow); + if (slidingWindow.isEmpty()) { + break; + } + + long windowStart = slidingWindow.peek().getTimestamp(); + + int maxStep = 0; + long previousTimestamp = -1; + for (FunnelStepEvent event : slidingWindow) { + int currentEventStep = event.getStep(); + // If the same condition holds for the sequence of events, then such repeating event interrupts further + // processing. + if (_modes.hasStrictDeduplication()) { + if (currentEventStep == maxStep - 1) { + maxStep = 0; + } + } + // Don't allow interventions of other events. E.g. in the case of A->B->D->C, it stops finding A->B->C at the D + // and the max event level is 2. + if (_modes.hasStrictOrder()) { + if (currentEventStep != maxStep) { + maxStep = 0; + } + } + // Apply conditions only to events with strictly increasing timestamps. + if (_modes.hasStrictIncrease()) { + if (previousTimestamp == event.getTimestamp()) { + continue; + } + } + previousTimestamp = event.getTimestamp(); + if (maxStep == currentEventStep) { + maxStep++; + } + if (maxStep == _numSteps) { + totalCompletedRounds++; + maxStep = 0; + windowStart = event.getTimestamp(); + } + } + if (!slidingWindow.isEmpty()) { + slidingWindow.pollFirst(); + } + // sliding window should pop until current event: + while (!slidingWindow.isEmpty() && slidingWindow.peek().getTimestamp() < windowStart) { + slidingWindow.pollFirst(); + } + } + return totalCompletedRounds; + } + + @Override + public Integer mergeFinalResult(Integer finalResult1, Integer finalResult2) { + return finalResult1 + finalResult2; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/window/FunnelMatchStepAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/window/FunnelMatchStepAggregationFunction.java new file mode 100644 index 000000000000..044157fa2123 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/window/FunnelMatchStepAggregationFunction.java @@ -0,0 +1,127 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.funnel.window; + +import it.unimi.dsi.fastutil.ints.IntArrayList; +import java.util.ArrayDeque; +import java.util.List; +import java.util.PriorityQueue; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.query.aggregation.function.funnel.FunnelStepEvent; +import org.apache.pinot.segment.spi.AggregationFunctionType; + + +public class FunnelMatchStepAggregationFunction extends FunnelBaseAggregationFunction { + + public FunnelMatchStepAggregationFunction(List arguments) { + super(arguments); + } + + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.FUNNELMATCHSTEP; + } + + @Override + public DataSchema.ColumnDataType getFinalResultColumnType() { + return DataSchema.ColumnDataType.INT_ARRAY; + } + + @Override + public IntArrayList extractFinalResult(PriorityQueue stepEvents) { + int finalMaxStep = 0; + IntArrayList result = new IntArrayList(_numSteps); + for (int i = 0; i < _numSteps; i++) { + result.add(0); + } + if (stepEvents == null || stepEvents.isEmpty()) { + return result; + } + ArrayDeque slidingWindow = new ArrayDeque<>(); + while (!stepEvents.isEmpty()) { + fillWindow(stepEvents, slidingWindow); + if (slidingWindow.isEmpty()) { + break; + } + int maxSteps = processWindow(slidingWindow); + finalMaxStep = Math.max(finalMaxStep, maxSteps); + if (finalMaxStep == _numSteps) { + break; + } + if (!slidingWindow.isEmpty()) { + slidingWindow.pollFirst(); + } + } + for (int i = 0; i < finalMaxStep; i++) { + result.set(i, 1); + } + return result; + } + + protected Integer processWindow(ArrayDeque slidingWindow) { + int maxStep = 0; + long previousTimestamp = -1; + for (FunnelStepEvent event : slidingWindow) { + int currentEventStep = event.getStep(); + // If the same condition holds for the sequence of events, then such repeating event interrupts further + // processing. + if (_modes.hasStrictDeduplication()) { + if (currentEventStep == maxStep - 1) { + return maxStep; + } + } + // Don't allow interventions of other events. E.g. in the case of A->B->D->C, it stops finding A->B->C at the D + // and the max event level is 2. + if (_modes.hasStrictOrder()) { + if (currentEventStep != maxStep) { + return maxStep; + } + } + // Apply conditions only to events with strictly increasing timestamps. + if (_modes.hasStrictIncrease()) { + if (previousTimestamp == event.getTimestamp()) { + continue; + } + } + if (maxStep == currentEventStep) { + maxStep++; + previousTimestamp = event.getTimestamp(); + } + if (maxStep == _numSteps) { + break; + } + } + return maxStep; + } + + @Override + public IntArrayList mergeFinalResult(IntArrayList finalResult1, IntArrayList finalResult2) { + // Return the longest 1s sequence from both results + for (int i = 0; i < _numSteps; i++) { + if (finalResult1.getInt(i) == 0) { + return finalResult2; + } + if (finalResult2.getInt(i) == 0) { + return finalResult1; + } + } + return finalResult1; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/window/FunnelMaxStepAggregationFunction.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/window/FunnelMaxStepAggregationFunction.java new file mode 100644 index 000000000000..73684ad4605e --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/function/funnel/window/FunnelMaxStepAggregationFunction.java @@ -0,0 +1,110 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function.funnel.window; + +import java.util.ArrayDeque; +import java.util.List; +import java.util.PriorityQueue; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.query.aggregation.function.funnel.FunnelStepEvent; +import org.apache.pinot.segment.spi.AggregationFunctionType; + + +public class FunnelMaxStepAggregationFunction extends FunnelBaseAggregationFunction { + + public FunnelMaxStepAggregationFunction(List arguments) { + super(arguments); + } + + @Override + public AggregationFunctionType getType() { + return AggregationFunctionType.FUNNELMAXSTEP; + } + + @Override + public DataSchema.ColumnDataType getFinalResultColumnType() { + return DataSchema.ColumnDataType.INT; + } + + @Override + public Integer extractFinalResult(PriorityQueue stepEvents) { + int finalMaxStep = 0; + if (stepEvents == null || stepEvents.isEmpty()) { + return finalMaxStep; + } + ArrayDeque slidingWindow = new ArrayDeque<>(); + while (!stepEvents.isEmpty()) { + fillWindow(stepEvents, slidingWindow); + if (slidingWindow.isEmpty()) { + break; + } + int maxSteps = processWindow(slidingWindow); + finalMaxStep = Math.max(finalMaxStep, maxSteps); + if (finalMaxStep == _numSteps) { + break; + } + if (!slidingWindow.isEmpty()) { + slidingWindow.pollFirst(); + } + } + return finalMaxStep; + } + + protected Integer processWindow(ArrayDeque slidingWindow) { + int maxStep = 0; + long previousTimestamp = -1; + for (FunnelStepEvent event : slidingWindow) { + int currentEventStep = event.getStep(); + // If the same condition holds for the sequence of events, then such repeating event interrupts further + // processing. + if (_modes.hasStrictDeduplication()) { + if (currentEventStep == maxStep - 1) { + return maxStep; + } + } + // Don't allow interventions of other events. E.g. in the case of A->B->D->C, it stops finding A->B->C at the D + // and the max event level is 2. + if (_modes.hasStrictOrder()) { + if (currentEventStep != maxStep) { + return maxStep; + } + } + // Apply conditions only to events with strictly increasing timestamps. + if (_modes.hasStrictIncrease()) { + if (previousTimestamp == event.getTimestamp()) { + continue; + } + } + if (maxStep == currentEventStep) { + maxStep++; + previousTimestamp = event.getTimestamp(); + } + if (maxStep == _numSteps) { + break; + } + } + return maxStep; + } + + @Override + public Integer mergeFinalResult(Integer finalResult1, Integer finalResult2) { + return Math.max(finalResult1, finalResult2); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/DefaultGroupByExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/DefaultGroupByExecutor.java index e0af94070c82..133385a52456 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/DefaultGroupByExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/DefaultGroupByExecutor.java @@ -20,13 +20,14 @@ import java.util.Collection; import java.util.Map; +import javax.annotation.Nullable; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.data.table.IntermediateRecord; import org.apache.pinot.core.data.table.TableResizer; -import org.apache.pinot.core.operator.blocks.TransformBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; -import org.apache.pinot.core.operator.transform.TransformResultMetadata; +import org.apache.pinot.core.operator.BaseProjectOperator; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.plan.DocIdSetPlanNode; import org.apache.pinot.core.query.aggregation.function.AggregationFunction; import org.apache.pinot.core.query.aggregation.function.AggregationFunctionUtils; @@ -58,44 +59,53 @@ public class DefaultGroupByExecutor implements GroupByExecutor { protected final int[] _svGroupKeys; protected final int[][] _mvGroupKeys; - /** - * Constructor for the class. - * - * @param queryContext Query context - * @param groupByExpressions Array of group-by expressions - * @param transformOperator Transform operator - */ public DefaultGroupByExecutor(QueryContext queryContext, ExpressionContext[] groupByExpressions, - TransformOperator transformOperator) { - _aggregationFunctions = queryContext.getAggregationFunctions(); + BaseProjectOperator projectOperator) { + this(queryContext, queryContext.getAggregationFunctions(), groupByExpressions, projectOperator, null); + } + + public DefaultGroupByExecutor(QueryContext queryContext, AggregationFunction[] aggregationFunctions, + ExpressionContext[] groupByExpressions, BaseProjectOperator projectOperator) { + this(queryContext, aggregationFunctions, groupByExpressions, projectOperator, null); + } + + public DefaultGroupByExecutor(QueryContext queryContext, AggregationFunction[] aggregationFunctions, + ExpressionContext[] groupByExpressions, BaseProjectOperator projectOperator, + @Nullable GroupKeyGenerator groupKeyGenerator) { + _aggregationFunctions = aggregationFunctions; assert _aggregationFunctions != null; _nullHandlingEnabled = queryContext.isNullHandlingEnabled(); boolean hasMVGroupByExpression = false; boolean hasNoDictionaryGroupByExpression = false; for (ExpressionContext groupByExpression : groupByExpressions) { - TransformResultMetadata transformResultMetadata = transformOperator.getResultMetadata(groupByExpression); - hasMVGroupByExpression |= !transformResultMetadata.isSingleValue(); - hasNoDictionaryGroupByExpression |= !transformResultMetadata.hasDictionary(); + ColumnContext columnContext = projectOperator.getResultColumnContext(groupByExpression); + hasMVGroupByExpression |= !columnContext.isSingleValue(); + hasNoDictionaryGroupByExpression |= columnContext.getDictionary() == null; } _hasMVGroupByExpression = hasMVGroupByExpression; // Initialize group key generator int numGroupsLimit = queryContext.getNumGroupsLimit(); int maxInitialResultHolderCapacity = queryContext.getMaxInitialResultHolderCapacity(); - if (hasNoDictionaryGroupByExpression || _nullHandlingEnabled) { - if (groupByExpressions.length == 1) { - // TODO(nhejazi): support MV and dictionary based when null handling is enabled. - _groupKeyGenerator = - new NoDictionarySingleColumnGroupKeyGenerator(transformOperator, groupByExpressions[0], numGroupsLimit, - _nullHandlingEnabled); + if (groupKeyGenerator != null) { + _groupKeyGenerator = groupKeyGenerator; + } else { + if (hasNoDictionaryGroupByExpression || _nullHandlingEnabled) { + if (groupByExpressions.length == 1) { + // TODO(nhejazi): support MV and dictionary based when null handling is enabled. + _groupKeyGenerator = + new NoDictionarySingleColumnGroupKeyGenerator(projectOperator, groupByExpressions[0], numGroupsLimit, + _nullHandlingEnabled); + } else { + _groupKeyGenerator = + new NoDictionaryMultiColumnGroupKeyGenerator(projectOperator, groupByExpressions, numGroupsLimit, + _nullHandlingEnabled); + } } else { - _groupKeyGenerator = - new NoDictionaryMultiColumnGroupKeyGenerator(transformOperator, groupByExpressions, numGroupsLimit); + _groupKeyGenerator = new DictionaryBasedGroupKeyGenerator(projectOperator, groupByExpressions, numGroupsLimit, + maxInitialResultHolderCapacity); } - } else { - _groupKeyGenerator = new DictionaryBasedGroupKeyGenerator(transformOperator, groupByExpressions, numGroupsLimit, - maxInitialResultHolderCapacity); } // Initialize result holders @@ -118,30 +128,29 @@ public DefaultGroupByExecutor(QueryContext queryContext, ExpressionContext[] gro } @Override - public void process(TransformBlock transformBlock) { + public void process(ValueBlock valueBlock) { // Generate group keys // NOTE: groupKeyGenerator will limit the number of groups. Once reaching limit, no new group will be generated if (_hasMVGroupByExpression) { - _groupKeyGenerator.generateKeysForBlock(transformBlock, _mvGroupKeys); + _groupKeyGenerator.generateKeysForBlock(valueBlock, _mvGroupKeys); } else { - _groupKeyGenerator.generateKeysForBlock(transformBlock, _svGroupKeys); + _groupKeyGenerator.generateKeysForBlock(valueBlock, _svGroupKeys); } int capacityNeeded = _groupKeyGenerator.getCurrentGroupKeyUpperBound(); - int length = transformBlock.getNumDocs(); + int length = valueBlock.getNumDocs(); int numAggregationFunctions = _aggregationFunctions.length; for (int i = 0; i < numAggregationFunctions; i++) { GroupByResultHolder groupByResultHolder = _groupByResultHolders[i]; groupByResultHolder.ensureCapacity(capacityNeeded); - aggregate(transformBlock, length, i); + aggregate(valueBlock, length, i); } } - protected void aggregate(TransformBlock transformBlock, int length, int functionIndex) { + protected void aggregate(ValueBlock valueBlock, int length, int functionIndex) { AggregationFunction aggregationFunction = _aggregationFunctions[functionIndex]; Map blockValSetMap = - AggregationFunctionUtils.getBlockValSetMap(aggregationFunction, transformBlock); - + AggregationFunctionUtils.getBlockValSetMap(aggregationFunction, valueBlock); GroupByResultHolder groupByResultHolder = _groupByResultHolders[functionIndex]; if (_hasMVGroupByExpression) { aggregationFunction.aggregateGroupByMV(length, _mvGroupKeys, groupByResultHolder, blockValSetMap); @@ -164,4 +173,14 @@ public int getNumGroups() { public Collection trimGroupByResult(int trimSize, TableResizer tableResizer) { return tableResizer.trimInSegmentResults(_groupKeyGenerator, _groupByResultHolders, trimSize); } + + @Override + public GroupKeyGenerator getGroupKeyGenerator() { + return _groupKeyGenerator; + } + + @Override + public GroupByResultHolder[] getGroupByResultHolders() { + return _groupByResultHolders; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/DictionaryBasedGroupKeyGenerator.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/DictionaryBasedGroupKeyGenerator.java index ca431545343c..8650ccad9bad 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/DictionaryBasedGroupKeyGenerator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/DictionaryBasedGroupKeyGenerator.java @@ -29,8 +29,9 @@ import java.util.Iterator; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.TransformBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.segment.spi.index.reader.Dictionary; @@ -97,8 +98,8 @@ public class DictionaryBasedGroupKeyGenerator implements GroupKeyGenerator { private final int _globalGroupIdUpperBound; private final RawKeyHolder _rawKeyHolder; - public DictionaryBasedGroupKeyGenerator(TransformOperator transformOperator, ExpressionContext[] groupByExpressions, - int numGroupsLimit, int arrayBasedThreshold) { + public DictionaryBasedGroupKeyGenerator(BaseProjectOperator projectOperator, + ExpressionContext[] groupByExpressions, int numGroupsLimit, int arrayBasedThreshold) { assert numGroupsLimit >= arrayBasedThreshold; _groupByExpressions = groupByExpressions; @@ -117,7 +118,9 @@ public DictionaryBasedGroupKeyGenerator(TransformOperator transformOperator, Exp boolean longOverflow = false; for (int i = 0; i < _numGroupByExpressions; i++) { ExpressionContext groupByExpression = groupByExpressions[i]; - _dictionaries[i] = transformOperator.getDictionary(groupByExpression); + ColumnContext columnContext = projectOperator.getResultColumnContext(groupByExpression); + _dictionaries[i] = columnContext.getDictionary(); + assert _dictionaries[i] != null; int cardinality = _dictionaries[i].length(); _cardinalities[i] = cardinality; if (_internedDictionaryValues != null && cardinality < MAX_DICTIONARY_INTERN_TABLE_SIZE) { @@ -130,8 +133,7 @@ public DictionaryBasedGroupKeyGenerator(TransformOperator transformOperator, Exp cardinalityProduct *= cardinality; } } - - _isSingleValueColumn[i] = transformOperator.getResultMetadata(groupByExpression).isSingleValue(); + _isSingleValueColumn[i] = columnContext.isSingleValue(); } // TODO: Clear the holder after processing the query instead of before if (longOverflow) { @@ -175,28 +177,27 @@ public int getGlobalGroupKeyUpperBound() { } @Override - public void generateKeysForBlock(TransformBlock transformBlock, int[] groupKeys) { + public void generateKeysForBlock(ValueBlock valueBlock, int[] groupKeys) { // Fetch dictionary ids in the given block for all group-by columns for (int i = 0; i < _numGroupByExpressions; i++) { - BlockValSet blockValueSet = transformBlock.getBlockValueSet(_groupByExpressions[i]); + BlockValSet blockValueSet = valueBlock.getBlockValueSet(_groupByExpressions[i]); _singleValueDictIds[i] = blockValueSet.getDictionaryIdsSV(); } - _rawKeyHolder.processSingleValue(transformBlock.getNumDocs(), groupKeys); + _rawKeyHolder.processSingleValue(valueBlock.getNumDocs(), groupKeys); } @Override - public void generateKeysForBlock(TransformBlock transformBlock, int[][] groupKeys) { + public void generateKeysForBlock(ValueBlock valueBlock, int[][] groupKeys) { // Fetch dictionary ids in the given block for all group-by columns for (int i = 0; i < _numGroupByExpressions; i++) { - BlockValSet blockValueSet = transformBlock.getBlockValueSet(_groupByExpressions[i]); + BlockValSet blockValueSet = valueBlock.getBlockValueSet(_groupByExpressions[i]); if (_isSingleValueColumn[i]) { _singleValueDictIds[i] = blockValueSet.getDictionaryIdsSV(); } else { _multiValueDictIds[i] = blockValueSet.getDictionaryIdsMV(); } } - - _rawKeyHolder.processMultiValue(transformBlock.getNumDocs(), groupKeys); + _rawKeyHolder.processMultiValue(valueBlock.getNumDocs(), groupKeys); } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/DummyAggregationResultHolder.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/DummyAggregationResultHolder.java new file mode 100644 index 000000000000..545cd7973cc0 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/DummyAggregationResultHolder.java @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.groupby; + +import org.apache.pinot.core.query.aggregation.AggregationResultHolder; + + +/** + * Placeholder AggregationResultHolder that does noop + * This is used for ChildAggregationFunction + */ +public class DummyAggregationResultHolder implements AggregationResultHolder { + @Override + public void setValue(double value) { + } + + @Override + public void setValue(int value) { + } + + @Override + public void setValue(Object value) { + } + + @Override + public double getDoubleResult() { + return 0; + } + + @Override + public int getIntResult() { + return 0; + } + + @Override + public T getResult() { + return null; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/DummyGroupByResultHolder.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/DummyGroupByResultHolder.java new file mode 100644 index 000000000000..f76e8967cfd9 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/DummyGroupByResultHolder.java @@ -0,0 +1,56 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.groupby; + +/** + * Placeholder GroupByResultHolder that does noop + * This is used for ChildAggregationFunction + */ +public class DummyGroupByResultHolder implements GroupByResultHolder { + @Override + public void setValueForKey(int groupKey, double value) { + } + + @Override + public void setValueForKey(int groupKey, int value) { + } + + @Override + public void setValueForKey(int groupKey, Object value) { + } + + @Override + public double getDoubleResult(int groupKey) { + return 0; + } + + @Override + public int getIntResult(int groupKey) { + return 0; + } + + @Override + public T getResult(int groupKey) { + return null; + } + + @Override + public void ensureCapacity(int capacity) { + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/GroupByExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/GroupByExecutor.java index 869ef5dbe9a7..8c5524ec4574 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/GroupByExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/GroupByExecutor.java @@ -21,7 +21,7 @@ import java.util.Collection; import org.apache.pinot.core.data.table.IntermediateRecord; import org.apache.pinot.core.data.table.TableResizer; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; /** @@ -30,11 +30,9 @@ public interface GroupByExecutor { /** - * Performs the group-by aggregation on the given transform block. - * - * @param transformBlock Transform block + * Performs the group-by aggregation on the given value block. */ - void process(TransformBlock transformBlock); + void process(ValueBlock valueBlock); /** * Returns the result of group-by aggregation. @@ -58,4 +56,8 @@ public interface GroupByExecutor { * */ Collection trimGroupByResult(int trimSize, TableResizer tableResizer); + + GroupKeyGenerator getGroupKeyGenerator(); + + GroupByResultHolder[] getGroupByResultHolders(); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/GroupKeyGenerator.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/GroupKeyGenerator.java index 66332b5ad00d..393610b4a38a 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/GroupKeyGenerator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/GroupKeyGenerator.java @@ -19,7 +19,7 @@ package org.apache.pinot.core.query.aggregation.groupby; import java.util.Iterator; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; /** @@ -39,22 +39,22 @@ public interface GroupKeyGenerator { int getGlobalGroupKeyUpperBound(); /** - * Generates group keys on the given transform block and returns the result to the given buffer. + * Generates group keys on the given value block and returns the result to the given buffer. *

This method is for situation where all the group-by columns are single-valued. * - * @param transformBlock Transform block - * @param groupKeys Buffer to return the results + * @param valueBlock Value block + * @param groupKeys Buffer to return the results */ - void generateKeysForBlock(TransformBlock transformBlock, int[] groupKeys); + void generateKeysForBlock(ValueBlock valueBlock, int[] groupKeys); /** - * Generate group keys on the given transform block and returns the result to the given buffer. + * Generate group keys on the given value block and returns the result to the given buffer. *

This method is for situation where at least one group-by columns are multi-valued. * - * @param transformBlock Transform block - * @param groupKeys Buffer to return the results + * @param valueBlock Value block + * @param groupKeys Buffer to return the results */ - void generateKeysForBlock(TransformBlock transformBlock, int[][] groupKeys); + void generateKeysForBlock(ValueBlock valueBlock, int[][] groupKeys); /** * Get the current upper bound of the group key. All group keys already generated should be less than this value. This diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/NoDictionaryMultiColumnGroupKeyGenerator.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/NoDictionaryMultiColumnGroupKeyGenerator.java index e209eab404a3..9c7cf193a662 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/NoDictionaryMultiColumnGroupKeyGenerator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/NoDictionaryMultiColumnGroupKeyGenerator.java @@ -26,15 +26,16 @@ import java.util.Iterator; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.TransformBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; -import org.apache.pinot.core.operator.transform.TransformResultMetadata; +import org.apache.pinot.core.operator.BaseProjectOperator; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.query.aggregation.groupby.utils.ValueToIdMap; import org.apache.pinot.core.query.aggregation.groupby.utils.ValueToIdMapFactory; import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.utils.ByteArray; import org.apache.pinot.spi.utils.FixedIntArray; +import org.roaringbitmap.RoaringBitmap; /** @@ -47,6 +48,8 @@ * 2. Add support for trimming group-by results. */ public class NoDictionaryMultiColumnGroupKeyGenerator implements GroupKeyGenerator { + private static final int ID_FOR_NULL = INVALID_ID - 1; + private final ExpressionContext[] _groupByExpressions; private final int _numGroupByExpressions; private final DataType[] _storedTypes; @@ -54,47 +57,48 @@ public class NoDictionaryMultiColumnGroupKeyGenerator implements GroupKeyGenerat private final ValueToIdMap[] _onTheFlyDictionaries; private final Object2IntOpenHashMap _groupKeyMap; private final boolean[] _isSingleValueExpressions; - private final int _globalGroupIdUpperBound; - - private int _numGroups = 0; + private final int _numGroupsLimit; + private final boolean _nullHandlingEnabled; - public NoDictionaryMultiColumnGroupKeyGenerator(TransformOperator transformOperator, - ExpressionContext[] groupByExpressions, int numGroupsLimit) { + public NoDictionaryMultiColumnGroupKeyGenerator(BaseProjectOperator projectOperator, + ExpressionContext[] groupByExpressions, int numGroupsLimit, boolean nullHandlingEnabled) { _groupByExpressions = groupByExpressions; _numGroupByExpressions = groupByExpressions.length; _storedTypes = new DataType[_numGroupByExpressions]; _dictionaries = new Dictionary[_numGroupByExpressions]; _onTheFlyDictionaries = new ValueToIdMap[_numGroupByExpressions]; _isSingleValueExpressions = new boolean[_numGroupByExpressions]; + _nullHandlingEnabled = nullHandlingEnabled; for (int i = 0; i < _numGroupByExpressions; i++) { ExpressionContext groupByExpression = groupByExpressions[i]; - TransformResultMetadata transformResultMetadata = transformOperator.getResultMetadata(groupByExpression); - _storedTypes[i] = transformResultMetadata.getDataType().getStoredType(); - if (transformResultMetadata.hasDictionary()) { - _dictionaries[i] = transformOperator.getDictionary(groupByExpression); + ColumnContext columnContext = projectOperator.getResultColumnContext(groupByExpression); + _storedTypes[i] = columnContext.getDataType().getStoredType(); + Dictionary dictionary = _nullHandlingEnabled ? null : columnContext.getDictionary(); + if (dictionary != null) { + _dictionaries[i] = dictionary; } else { _onTheFlyDictionaries[i] = ValueToIdMapFactory.get(_storedTypes[i]); } - _isSingleValueExpressions[i] = transformResultMetadata.isSingleValue(); + _isSingleValueExpressions[i] = columnContext.isSingleValue(); } _groupKeyMap = new Object2IntOpenHashMap<>(); _groupKeyMap.defaultReturnValue(INVALID_ID); - _globalGroupIdUpperBound = numGroupsLimit; + _numGroupsLimit = numGroupsLimit; } @Override public int getGlobalGroupKeyUpperBound() { - return _globalGroupIdUpperBound; + return _numGroupsLimit; } @Override - public void generateKeysForBlock(TransformBlock transformBlock, int[] groupKeys) { - int numDocs = transformBlock.getNumDocs(); + public void generateKeysForBlock(ValueBlock valueBlock, int[] groupKeys) { + int numDocs = valueBlock.getNumDocs(); Object[] values = new Object[_numGroupByExpressions]; for (int i = 0; i < _numGroupByExpressions; i++) { - BlockValSet blockValSet = transformBlock.getBlockValueSet(_groupByExpressions[i]); + BlockValSet blockValSet = valueBlock.getBlockValueSet(_groupByExpressions[i]); if (_dictionaries[i] != null) { values[i] = blockValSet.getDictionaryIdsSV(); } else { @@ -111,6 +115,9 @@ public void generateKeysForBlock(TransformBlock transformBlock, int[] groupKeys) case DOUBLE: values[i] = blockValSet.getDoubleValuesSV(); break; + case BIG_DECIMAL: + values[i] = blockValSet.getBigDecimalValuesSV(); + break; case STRING: values[i] = blockValSet.getStringValuesSV(); break; @@ -125,38 +132,150 @@ public void generateKeysForBlock(TransformBlock transformBlock, int[] groupKeys) int[] keyValues = new int[_numGroupByExpressions]; // note that we are mutating its backing array for memory efficiency FixedIntArray flyweightKey = new FixedIntArray(keyValues); - for (int row = 0; row < numDocs; row++) { - for (int col = 0; col < _numGroupByExpressions; col++) { - Object columnValues = values[col]; - ValueToIdMap onTheFlyDictionary = _onTheFlyDictionaries[col]; - if (columnValues instanceof int[]) { - if (onTheFlyDictionary == null) { - keyValues[col] = ((int[]) columnValues)[row]; - } else { - keyValues[col] = onTheFlyDictionary.put(((int[]) columnValues)[row]); + if (_nullHandlingEnabled) { + RoaringBitmap[] nullBitmaps = new RoaringBitmap[_numGroupByExpressions]; + for (int i = 0; i < _numGroupByExpressions; i++) { + nullBitmaps[i] = valueBlock.getBlockValueSet(_groupByExpressions[i]).getNullBitmap(); + } + for (int row = 0; row < numDocs; row++) { + int numGroups = _groupKeyMap.size(); + boolean hasInvalidKeyValue = false; + if (numGroups < _numGroupsLimit) { + for (int col = 0; col < _numGroupByExpressions; col++) { + if (nullBitmaps[col] != null && nullBitmaps[col].contains(row)) { + keyValues[col] = ID_FOR_NULL; + } else { + Object columnValues = values[col]; + ValueToIdMap onTheFlyDictionary = _onTheFlyDictionaries[col]; + int keyValue; + if (columnValues instanceof int[]) { + keyValue = onTheFlyDictionary.put(((int[]) columnValues)[row]); + } else if (columnValues instanceof long[]) { + keyValue = onTheFlyDictionary.put(((long[]) columnValues)[row]); + } else if (columnValues instanceof float[]) { + keyValue = onTheFlyDictionary.put(((float[]) columnValues)[row]); + } else if (columnValues instanceof double[]) { + keyValue = onTheFlyDictionary.put(((double[]) columnValues)[row]); + } else if (columnValues instanceof byte[][]) { + keyValue = onTheFlyDictionary.put(new ByteArray(((byte[][]) columnValues)[row])); + } else { + keyValue = onTheFlyDictionary.put(((Object[]) columnValues)[row]); + } + keyValues[col] = keyValue; + } + } + } else { + for (int col = 0; col < _numGroupByExpressions; col++) { + if (nullBitmaps[col] != null && nullBitmaps[col].contains(row)) { + keyValues[col] = ID_FOR_NULL; + } else { + Object columnValues = values[col]; + ValueToIdMap onTheFlyDictionary = _onTheFlyDictionaries[col]; + int keyValue; + if (columnValues instanceof int[]) { + keyValue = onTheFlyDictionary.getId(((int[]) columnValues)[row]); + } else if (columnValues instanceof long[]) { + keyValue = onTheFlyDictionary.getId(((long[]) columnValues)[row]); + } else if (columnValues instanceof float[]) { + keyValue = onTheFlyDictionary.getId(((float[]) columnValues)[row]); + } else if (columnValues instanceof double[]) { + keyValue = onTheFlyDictionary.getId(((double[]) columnValues)[row]); + } else if (columnValues instanceof byte[][]) { + keyValue = onTheFlyDictionary.getId(new ByteArray(((byte[][]) columnValues)[row])); + } else { + keyValue = onTheFlyDictionary.getId(((Object[]) columnValues)[row]); + } + if (keyValue == INVALID_ID) { + hasInvalidKeyValue = true; + break; + } + } + } + } + if (hasInvalidKeyValue) { + groupKeys[row] = INVALID_ID; + } else { + int groupId = getGroupIdForKey(flyweightKey); + if (groupId == numGroups) { + // When a new group is added, create a new FixedIntArray + keyValues = new int[_numGroupByExpressions]; + flyweightKey = new FixedIntArray(keyValues); + } + groupKeys[row] = groupId; + } + } + } else { + for (int row = 0; row < numDocs; row++) { + int numGroups = _groupKeyMap.size(); + boolean hasInvalidKeyValue = false; + if (numGroups < _numGroupsLimit) { + for (int col = 0; col < _numGroupByExpressions; col++) { + Object columnValues = values[col]; + ValueToIdMap onTheFlyDictionary = _onTheFlyDictionaries[col]; + int keyValue; + if (columnValues instanceof int[]) { + int columnValue = ((int[]) columnValues)[row]; + keyValue = onTheFlyDictionary != null ? onTheFlyDictionary.put(columnValue) : columnValue; + } else if (columnValues instanceof long[]) { + keyValue = onTheFlyDictionary.put(((long[]) columnValues)[row]); + } else if (columnValues instanceof float[]) { + keyValue = onTheFlyDictionary.put(((float[]) columnValues)[row]); + } else if (columnValues instanceof double[]) { + keyValue = onTheFlyDictionary.put(((double[]) columnValues)[row]); + } else if (columnValues instanceof byte[][]) { + keyValue = onTheFlyDictionary.put(new ByteArray(((byte[][]) columnValues)[row])); + } else { + keyValue = onTheFlyDictionary.put(((Object[]) columnValues)[row]); + } + keyValues[col] = keyValue; + } + } else { + for (int col = 0; col < _numGroupByExpressions; col++) { + Object columnValues = values[col]; + ValueToIdMap onTheFlyDictionary = _onTheFlyDictionaries[col]; + int keyValue; + if (columnValues instanceof int[]) { + int columnValue = ((int[]) columnValues)[row]; + keyValue = onTheFlyDictionary != null ? onTheFlyDictionary.getId(columnValue) : columnValue; + } else if (columnValues instanceof long[]) { + keyValue = onTheFlyDictionary.getId(((long[]) columnValues)[row]); + } else if (columnValues instanceof float[]) { + keyValue = onTheFlyDictionary.getId(((float[]) columnValues)[row]); + } else if (columnValues instanceof double[]) { + keyValue = onTheFlyDictionary.getId(((double[]) columnValues)[row]); + } else if (columnValues instanceof byte[][]) { + keyValue = onTheFlyDictionary.getId(new ByteArray(((byte[][]) columnValues)[row])); + } else { + keyValue = onTheFlyDictionary.getId(((Object[]) columnValues)[row]); + } + if (keyValue == INVALID_ID) { + hasInvalidKeyValue = true; + break; + } + keyValues[col] = keyValue; } - } else if (columnValues instanceof long[]) { - keyValues[col] = onTheFlyDictionary.put(((long[]) columnValues)[row]); - } else if (columnValues instanceof float[]) { - keyValues[col] = onTheFlyDictionary.put(((float[]) columnValues)[row]); - } else if (columnValues instanceof double[]) { - keyValues[col] = onTheFlyDictionary.put(((double[]) columnValues)[row]); - } else if (columnValues instanceof String[]) { - keyValues[col] = onTheFlyDictionary.put(((String[]) columnValues)[row]); - } else if (columnValues instanceof byte[][]) { - keyValues[col] = onTheFlyDictionary.put(new ByteArray(((byte[][]) columnValues)[row])); + } + if (hasInvalidKeyValue) { + groupKeys[row] = INVALID_ID; + } else { + int groupId = getGroupIdForKey(flyweightKey); + if (groupId == numGroups) { + // When a new group is added, create a new FixedIntArray + keyValues = new int[_numGroupByExpressions]; + flyweightKey = new FixedIntArray(keyValues); + } + groupKeys[row] = groupId; } } - groupKeys[row] = getGroupIdForFlyweightKey(flyweightKey); } } @Override - public void generateKeysForBlock(TransformBlock transformBlock, int[][] groupKeys) { - int numDocs = transformBlock.getNumDocs(); + public void generateKeysForBlock(ValueBlock valueBlock, int[][] groupKeys) { + int numDocs = valueBlock.getNumDocs(); int[][][] keys = new int[numDocs][_numGroupByExpressions][]; for (int i = 0; i < _numGroupByExpressions; i++) { - BlockValSet blockValSet = transformBlock.getBlockValueSet(_groupByExpressions[i]); + BlockValSet blockValSet = valueBlock.getBlockValueSet(_groupByExpressions[i]); if (_dictionaries[i] != null) { if (_isSingleValueExpressions[i]) { int[] dictIds = blockValSet.getDictionaryIdsSV(); @@ -292,23 +411,6 @@ public Iterator getGroupKeys() { return new GroupKeyIterator(); } - /** - * Helper method to get or create group-id for a group key. - * - * @param flyweight Group key, that is a list of objects to be grouped, will be cloned on first occurrence - * @return Group id - */ - private int getGroupIdForFlyweightKey(FixedIntArray flyweight) { - int groupId = _groupKeyMap.getInt(flyweight); - if (groupId == INVALID_ID) { - if (_numGroups < _globalGroupIdUpperBound) { - groupId = _numGroups; - _groupKeyMap.put(flyweight.clone(), _numGroups++); - } - } - return groupId; - } - /** * Helper method to get or create group-id for a group key. * @@ -316,14 +418,12 @@ private int getGroupIdForFlyweightKey(FixedIntArray flyweight) { * @return Group id */ private int getGroupIdForKey(FixedIntArray keyList) { - int groupId = _groupKeyMap.getInt(keyList); - if (groupId == INVALID_ID) { - if (_numGroups < _globalGroupIdUpperBound) { - groupId = _numGroups; - _groupKeyMap.put(keyList, _numGroups++); - } + int numGroups = _groupKeyMap.size(); + if (numGroups < _numGroupsLimit) { + return _groupKeyMap.computeIfAbsent(keyList, k -> numGroups); + } else { + return _groupKeyMap.getInt(keyList); } - return groupId; } /** @@ -391,10 +491,14 @@ private Object[] buildKeysFromIds(FixedIntArray keyList) { Object[] keys = new Object[_numGroupByExpressions]; int[] dictIds = keyList.elements(); for (int i = 0; i < _numGroupByExpressions; i++) { - if (_dictionaries[i] != null) { - keys[i] = _dictionaries[i].getInternal(dictIds[i]); + if (dictIds[i] == ID_FOR_NULL) { + keys[i] = null; } else { - keys[i] = _onTheFlyDictionaries[i].get(dictIds[i]); + if (_dictionaries[i] != null) { + keys[i] = _dictionaries[i].getInternal(dictIds[i]); + } else { + keys[i] = _onTheFlyDictionaries[i].get(dictIds[i]); + } } } return keys; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/NoDictionarySingleColumnGroupKeyGenerator.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/NoDictionarySingleColumnGroupKeyGenerator.java index f6716ebb2dca..768bfec1bebb 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/NoDictionarySingleColumnGroupKeyGenerator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/NoDictionarySingleColumnGroupKeyGenerator.java @@ -35,8 +35,9 @@ import java.util.Map; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.TransformBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.utils.ByteArray; import org.roaringbitmap.RoaringBitmap; @@ -62,14 +63,15 @@ public class NoDictionarySingleColumnGroupKeyGenerator implements GroupKeyGenera private int _numGroups = 0; - public NoDictionarySingleColumnGroupKeyGenerator(TransformOperator transformOperator, + public NoDictionarySingleColumnGroupKeyGenerator(BaseProjectOperator projectOperator, ExpressionContext groupByExpression, int numGroupsLimit, boolean nullHandlingEnabled) { _groupByExpression = groupByExpression; - _storedType = transformOperator.getResultMetadata(_groupByExpression).getDataType().getStoredType(); + ColumnContext columnContext = projectOperator.getResultColumnContext(groupByExpression); + _storedType = columnContext.getDataType().getStoredType(); _groupKeyMap = createGroupKeyMap(_storedType); _globalGroupIdUpperBound = numGroupsLimit; _nullHandlingEnabled = nullHandlingEnabled; - _isSingleValueExpression = transformOperator.getResultMetadata(groupByExpression).isSingleValue(); + _isSingleValueExpression = columnContext.isSingleValue(); } @Override @@ -78,16 +80,16 @@ public int getGlobalGroupKeyUpperBound() { } @Override - public void generateKeysForBlock(TransformBlock transformBlock, int[] groupKeys) { - BlockValSet blockValSet = transformBlock.getBlockValueSet(_groupByExpression); + public void generateKeysForBlock(ValueBlock valueBlock, int[] groupKeys) { + BlockValSet blockValSet = valueBlock.getBlockValueSet(_groupByExpression); if (_nullHandlingEnabled) { RoaringBitmap nullBitmap = blockValSet.getNullBitmap(); if (nullBitmap != null && !nullBitmap.isEmpty()) { - generateKeysForBlockNullHandlingEnabled(transformBlock, groupKeys, nullBitmap); + generateKeysForBlockNullHandlingEnabled(valueBlock, groupKeys, nullBitmap); return; } } - int numDocs = transformBlock.getNumDocs(); + int numDocs = valueBlock.getNumDocs(); switch (_storedType) { case INT: @@ -137,11 +139,11 @@ public void generateKeysForBlock(TransformBlock transformBlock, int[] groupKeys) } } - public void generateKeysForBlockNullHandlingEnabled(TransformBlock transformBlock, int[] groupKeys, + public void generateKeysForBlockNullHandlingEnabled(ValueBlock valueBlock, int[] groupKeys, RoaringBitmap nullBitmap) { assert nullBitmap != null; - BlockValSet blockValSet = transformBlock.getBlockValueSet(_groupByExpression); - int numDocs = transformBlock.getNumDocs(); + BlockValSet blockValSet = valueBlock.getBlockValueSet(_groupByExpression); + int numDocs = valueBlock.getNumDocs(); switch (_storedType) { case INT: @@ -262,9 +264,9 @@ private Map createGroupKeyMap(DataType keyType) { } @Override - public void generateKeysForBlock(TransformBlock transformBlock, int[][] groupKeys) { - int numDocs = transformBlock.getNumDocs(); - BlockValSet blockValSet = transformBlock.getBlockValueSet(_groupByExpression); + public void generateKeysForBlock(ValueBlock valueBlock, int[][] groupKeys) { + int numDocs = valueBlock.getNumDocs(); + BlockValSet blockValSet = valueBlock.getBlockValueSet(_groupByExpression); if (_isSingleValueExpression) { switch (_storedType) { @@ -305,8 +307,7 @@ public void generateKeysForBlock(TransformBlock transformBlock, int[][] groupKey } break; default: - throw new IllegalArgumentException( - "Illegal data type for no-dictionary key generator: " + _storedType); + throw new IllegalArgumentException("Illegal data type for no-dictionary key generator: " + _storedType); } } else { switch (_storedType) { @@ -366,8 +367,7 @@ public void generateKeysForBlock(TransformBlock transformBlock, int[][] groupKey } break; default: - throw new IllegalArgumentException( - "Illegal data type for no-dictionary key generator: " + _storedType); + throw new IllegalArgumentException("Illegal data type for no-dictionary key generator: " + _storedType); } } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/BaseValueToIdMap.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/BaseValueToIdMap.java deleted file mode 100644 index 6d889d3442d3..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/BaseValueToIdMap.java +++ /dev/null @@ -1,87 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.query.aggregation.groupby.utils; - -import org.apache.pinot.spi.utils.ByteArray; - - -/** - * Abstract base class for {@link ValueToIdMap} interface. - */ -public abstract class BaseValueToIdMap implements ValueToIdMap { - @Override - public int put(int value) { - throw new UnsupportedOperationException(); - } - - @Override - public int put(long value) { - throw new UnsupportedOperationException(); - } - - @Override - public int put(float value) { - throw new UnsupportedOperationException(); - } - - @Override - public int put(double value) { - throw new UnsupportedOperationException(); - } - - @Override - public int put(String value) { - throw new UnsupportedOperationException(); - } - - @Override - public int put(ByteArray value) { - throw new UnsupportedOperationException(); - } - - @Override - public int getInt(int id) { - throw new UnsupportedOperationException(); - } - - @Override - public long getLong(int id) { - throw new UnsupportedOperationException(); - } - - @Override - public float getFloat(int id) { - throw new UnsupportedOperationException(); - } - - @Override - public double getDouble(int id) { - throw new UnsupportedOperationException(); - } - - @Override - public String getString(int id) { - throw new UnsupportedOperationException(); - } - - @Override - public ByteArray getBytes(int id) { - throw new UnsupportedOperationException(); - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/BytesToIdMap.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/BytesToIdMap.java deleted file mode 100644 index 2f2fe6ec2e15..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/BytesToIdMap.java +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.query.aggregation.groupby.utils; - -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import it.unimi.dsi.fastutil.objects.ObjectList; -import org.apache.pinot.spi.utils.ByteArray; - - -/** - * Implementation of {@link ValueToIdMap} for ByteArray. - */ -public class BytesToIdMap extends BaseValueToIdMap { - Object2IntMap _valueToIdMap; - ObjectList _idToValueMap; - - public BytesToIdMap() { - _valueToIdMap = new Object2IntOpenHashMap<>(); - _valueToIdMap.defaultReturnValue(INVALID_KEY); - _idToValueMap = new ObjectArrayList<>(); - } - - @Override - public int put(ByteArray value) { - int id = _valueToIdMap.getInt(value); - if (id == INVALID_KEY) { - id = _idToValueMap.size(); - _valueToIdMap.put(value, id); - _idToValueMap.add(value); - } - return id; - } - - @Override - public String getString(int id) { - return getBytes(id).toHexString(); - } - - @Override - public ByteArray getBytes(int id) { - assert id < _idToValueMap.size(); - return _idToValueMap.get(id); - } - - @Override - public Object get(int id) { - return getBytes(id); - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/DoubleToIdMap.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/DoubleToIdMap.java index 4bc5a1d8d13b..0b3754ee4fe5 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/DoubleToIdMap.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/DoubleToIdMap.java @@ -18,18 +18,16 @@ */ package org.apache.pinot.core.query.aggregation.groupby.utils; -import it.unimi.dsi.fastutil.doubles.Double2IntMap; import it.unimi.dsi.fastutil.doubles.Double2IntOpenHashMap; import it.unimi.dsi.fastutil.doubles.DoubleArrayList; -import it.unimi.dsi.fastutil.doubles.DoubleList; /** * Implementation of {@link ValueToIdMap} for double. */ -public class DoubleToIdMap extends BaseValueToIdMap { - Double2IntMap _valueToIdMap; - DoubleList _idToValueMap; +public class DoubleToIdMap implements ValueToIdMap { + private final Double2IntOpenHashMap _valueToIdMap; + private final DoubleArrayList _idToValueMap; public DoubleToIdMap() { _valueToIdMap = new Double2IntOpenHashMap(); @@ -39,28 +37,31 @@ public DoubleToIdMap() { @Override public int put(double value) { - int id = _valueToIdMap.get(value); - if (id == INVALID_KEY) { - id = _idToValueMap.size(); - _valueToIdMap.put(value, id); + int numValues = _valueToIdMap.size(); + int id = _valueToIdMap.computeIfAbsent(value, k -> numValues); + if (id == numValues) { _idToValueMap.add(value); } return id; } @Override - public double getDouble(int id) { - assert id < _idToValueMap.size(); - return _idToValueMap.getDouble(id); + public int put(Object value) { + return put((double) value); } @Override - public String getString(int id) { - return Double.toString(getDouble(id)); + public int getId(double value) { + return _valueToIdMap.get(value); } @Override - public Object get(int id) { - return getDouble(id); + public int getId(Object value) { + return getId((double) value); + } + + @Override + public Double get(int id) { + return _idToValueMap.getDouble(id); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/FloatToIdMap.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/FloatToIdMap.java index a928c2de0b1f..8b4e41aba3a5 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/FloatToIdMap.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/FloatToIdMap.java @@ -18,18 +18,16 @@ */ package org.apache.pinot.core.query.aggregation.groupby.utils; -import it.unimi.dsi.fastutil.floats.Float2IntMap; import it.unimi.dsi.fastutil.floats.Float2IntOpenHashMap; import it.unimi.dsi.fastutil.floats.FloatArrayList; -import it.unimi.dsi.fastutil.floats.FloatList; /** * Implementation of {@link ValueToIdMap} for float. */ -public class FloatToIdMap extends BaseValueToIdMap { - Float2IntMap _valueToIdMap; - FloatList _idToValueMap; +public class FloatToIdMap implements ValueToIdMap { + private final Float2IntOpenHashMap _valueToIdMap; + private final FloatArrayList _idToValueMap; public FloatToIdMap() { _valueToIdMap = new Float2IntOpenHashMap(); @@ -39,28 +37,31 @@ public FloatToIdMap() { @Override public int put(float value) { - int id = _valueToIdMap.get(value); - if (id == INVALID_KEY) { - id = _idToValueMap.size(); - _valueToIdMap.put(value, id); + int numValues = _valueToIdMap.size(); + int id = _valueToIdMap.computeIfAbsent(value, k -> numValues); + if (id == numValues) { _idToValueMap.add(value); } return id; } @Override - public float getFloat(int id) { - assert id < _idToValueMap.size(); - return _idToValueMap.getFloat(id); + public int put(Object value) { + return put((float) value); } @Override - public String getString(int id) { - return Float.toString(getFloat(id)); + public int getId(float value) { + return _valueToIdMap.get(value); } @Override - public Object get(int id) { - return getFloat(id); + public int getId(Object value) { + return getId((float) value); + } + + @Override + public Float get(int id) { + return _idToValueMap.getFloat(id); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/IntToIdMap.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/IntToIdMap.java index d07e4d62dbf6..2a05d8a2a534 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/IntToIdMap.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/IntToIdMap.java @@ -18,18 +18,16 @@ */ package org.apache.pinot.core.query.aggregation.groupby.utils; -import it.unimi.dsi.fastutil.ints.Int2IntMap; import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap; import it.unimi.dsi.fastutil.ints.IntArrayList; -import it.unimi.dsi.fastutil.ints.IntList; /** * Implementation of {@link ValueToIdMap} for int. */ -public class IntToIdMap extends BaseValueToIdMap { - Int2IntMap _valueToIdMap; - IntList _idToValueMap; +public class IntToIdMap implements ValueToIdMap { + private final Int2IntOpenHashMap _valueToIdMap; + private final IntArrayList _idToValueMap; public IntToIdMap() { _valueToIdMap = new Int2IntOpenHashMap(); @@ -39,28 +37,31 @@ public IntToIdMap() { @Override public int put(int value) { - int id = _valueToIdMap.get(value); - if (id == INVALID_KEY) { - id = _idToValueMap.size(); - _valueToIdMap.put(value, id); + int numValues = _valueToIdMap.size(); + int id = _valueToIdMap.computeIfAbsent(value, k -> numValues); + if (id == numValues) { _idToValueMap.add(value); } return id; } @Override - public int getInt(int id) { - assert id < _idToValueMap.size(); - return _idToValueMap.getInt(id); + public int put(Object value) { + return put((int) value); } @Override - public String getString(int id) { - return Integer.toString(getInt(id)); + public int getId(int value) { + return _valueToIdMap.get(value); } @Override - public Object get(int id) { - return getInt(id); + public int getId(Object value) { + return getId((int) value); + } + + @Override + public Integer get(int id) { + return _idToValueMap.getInt(id); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/LongToIdMap.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/LongToIdMap.java index 06df55c2cb1d..cc2259d27892 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/LongToIdMap.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/LongToIdMap.java @@ -18,18 +18,16 @@ */ package org.apache.pinot.core.query.aggregation.groupby.utils; -import it.unimi.dsi.fastutil.longs.Long2IntMap; import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; import it.unimi.dsi.fastutil.longs.LongArrayList; -import it.unimi.dsi.fastutil.longs.LongList; /** * Implementation of {@link ValueToIdMap} for long. */ -public class LongToIdMap extends BaseValueToIdMap { - Long2IntMap _valueToIdMap; - LongList _idToValueMap; +public class LongToIdMap implements ValueToIdMap { + private final Long2IntOpenHashMap _valueToIdMap; + private final LongArrayList _idToValueMap; public LongToIdMap() { _valueToIdMap = new Long2IntOpenHashMap(); @@ -39,28 +37,31 @@ public LongToIdMap() { @Override public int put(long value) { - int id = _valueToIdMap.get(value); - if (id == INVALID_KEY) { - id = _idToValueMap.size(); - _valueToIdMap.put(value, id); + int numValues = _valueToIdMap.size(); + int id = _valueToIdMap.computeIfAbsent(value, k -> numValues); + if (id == numValues) { _idToValueMap.add(value); } return id; } @Override - public long getLong(int id) { - assert id < _idToValueMap.size(); - return _idToValueMap.getLong(id); + public int put(Object value) { + return put((long) value); } @Override - public String getString(int id) { - return Long.toString(getLong(id)); + public int getId(long value) { + return _valueToIdMap.get(value); } @Override - public Object get(int id) { - return getLong(id); + public int getId(Object value) { + return getId((long) value); + } + + @Override + public Long get(int id) { + return _idToValueMap.getLong(id); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/ObjectToIdMap.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/ObjectToIdMap.java new file mode 100644 index 000000000000..9a0734362dbc --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/ObjectToIdMap.java @@ -0,0 +1,57 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.groupby.utils; + +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import java.util.ArrayList; + + +/** + * Implementation of {@link ValueToIdMap} for Object. + */ +public class ObjectToIdMap implements ValueToIdMap { + private final Object2IntOpenHashMap _valueToIdMap; + private final ArrayList _idToValueMap; + + public ObjectToIdMap() { + _valueToIdMap = new Object2IntOpenHashMap<>(); + _valueToIdMap.defaultReturnValue(INVALID_KEY); + _idToValueMap = new ArrayList<>(); + } + + @Override + public int put(Object value) { + int numValues = _valueToIdMap.size(); + int id = _valueToIdMap.computeIntIfAbsent(value, k -> numValues); + if (id == numValues) { + _idToValueMap.add(value); + } + return id; + } + + @Override + public int getId(Object value) { + return _valueToIdMap.getInt(value); + } + + @Override + public Object get(int id) { + return _idToValueMap.get(id); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/StringToIdMap.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/StringToIdMap.java deleted file mode 100644 index 290eaddaf5a5..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/StringToIdMap.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.query.aggregation.groupby.utils; - -import it.unimi.dsi.fastutil.objects.Object2IntMap; -import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; -import it.unimi.dsi.fastutil.objects.ObjectArrayList; -import it.unimi.dsi.fastutil.objects.ObjectList; - - -/** - * Implementation of {@link ValueToIdMap} for String. - */ -public class StringToIdMap extends BaseValueToIdMap { - Object2IntMap _valueToIdMap; - ObjectList _idToValueMap; - - public StringToIdMap() { - _valueToIdMap = new Object2IntOpenHashMap<>(); - _valueToIdMap.defaultReturnValue(INVALID_KEY); - _idToValueMap = new ObjectArrayList<>(); - } - - @Override - public int put(String value) { - int id = _valueToIdMap.getInt(value); - if (id == INVALID_KEY) { - id = _idToValueMap.size(); - _valueToIdMap.put(value, id); - _idToValueMap.add(value); - } - return id; - } - - @Override - public String getString(int id) { - assert id < _idToValueMap.size(); - return _idToValueMap.get(id); - } - - @Override - public Object get(int id) { - return getString(id); - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/ValueToIdMap.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/ValueToIdMap.java index 858e814c5197..93d65383e7f4 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/ValueToIdMap.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/ValueToIdMap.java @@ -18,38 +18,47 @@ */ package org.apache.pinot.core.query.aggregation.groupby.utils; -import org.apache.pinot.spi.utils.ByteArray; - - /** * Interface for mapping primitive values to contiguous id's. */ public interface ValueToIdMap { int INVALID_KEY = -1; - int put(int value); - - int put(long value); - - int put(float value); + default int put(int value) { + throw new UnsupportedOperationException(); + } - int put(double value); + default int put(long value) { + throw new UnsupportedOperationException(); + } - int put(String value); + default int put(float value) { + throw new UnsupportedOperationException(); + } - int put(ByteArray value); + default int put(double value) { + throw new UnsupportedOperationException(); + } - int getInt(int id); + int put(Object value); - long getLong(int id); + default int getId(int value) { + throw new UnsupportedOperationException(); + } - float getFloat(int id); + default int getId(long value) { + throw new UnsupportedOperationException(); + } - double getDouble(int id); + default int getId(float value) { + throw new UnsupportedOperationException(); + } - String getString(int id); + default int getId(double value) { + throw new UnsupportedOperationException(); + } - ByteArray getBytes(int id); + int getId(Object value); /** * Returns the value for the given id. @@ -59,6 +68,7 @@ public interface ValueToIdMap { *
  • LONG -> Long
  • *
  • FLOAT -> Float
  • *
  • DOUBLE -> Double
  • + *
  • BIG_DECIMAL -> BigDecimal
  • *
  • STRING -> String
  • *
  • BYTES -> ByteArray
  • * diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/ValueToIdMapFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/ValueToIdMapFactory.java index 444899e4b73a..ee6222566020 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/ValueToIdMapFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/groupby/utils/ValueToIdMapFactory.java @@ -18,18 +18,17 @@ */ package org.apache.pinot.core.query.aggregation.groupby.utils; -import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.data.FieldSpec.DataType; /** * Factory for various implementations for {@link ValueToIdMap} */ public class ValueToIdMapFactory { - // Private constructor to prevent instantiating the class. private ValueToIdMapFactory() { } - public static ValueToIdMap get(FieldSpec.DataType dataType) { + public static ValueToIdMap get(DataType dataType) { switch (dataType) { case INT: return new IntToIdMap(); @@ -39,12 +38,8 @@ public static ValueToIdMap get(FieldSpec.DataType dataType) { return new FloatToIdMap(); case DOUBLE: return new DoubleToIdMap(); - case STRING: - return new StringToIdMap(); - case BYTES: - return new BytesToIdMap(); default: - throw new IllegalArgumentException("Illegal data type for ValueToIdMapFactory: " + dataType); + return new ObjectToIdMap(); } } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/utils/ParentAggregationFunctionResultObject.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/utils/ParentAggregationFunctionResultObject.java new file mode 100644 index 000000000000..ee441b74e58c --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/utils/ParentAggregationFunctionResultObject.java @@ -0,0 +1,42 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.utils; + +import java.io.Serializable; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.query.utils.rewriter.ParentAggregationResultRewriter; + + +/** + * Interface for the result of a parent aggregation function, as can be used to populate the results of corresponding + * of child aggregation functions. Each child aggregation function will have a corresponding column in the result + * schema, please see {@link ParentAggregationResultRewriter} for more details. + */ +public interface ParentAggregationFunctionResultObject + extends Comparable, Serializable { + + // get the nested value of the field at the given row, column + Object getField(int rowId, int colId); + + // get total number of rows + int getNumberOfRows(); + + // get the nested schema of the result + DataSchema getSchema(); +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/utils/exprminmax/ExprMinMaxMeasuringValSetWrapper.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/utils/exprminmax/ExprMinMaxMeasuringValSetWrapper.java new file mode 100644 index 000000000000..b88e7b2e1ec9 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/utils/exprminmax/ExprMinMaxMeasuringValSetWrapper.java @@ -0,0 +1,77 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.utils.exprminmax; + +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.common.BlockValSet; + + +/** + * Wrapper class for measuring columns in exprmin/max aggregation function. + * Meanly used to do comparison without boxing primitive types. + */ +public class ExprMinMaxMeasuringValSetWrapper extends ExprMinMaxWrapperValSet { + + public ExprMinMaxMeasuringValSetWrapper(boolean isSingleValue, DataSchema.ColumnDataType dataType, + BlockValSet blockValSet) { + super(dataType, isSingleValue); + setNewBlock(blockValSet); + } + + public Comparable getComparable(int i) { + switch (_dataType) { + case INT: + case BOOLEAN: + return _intValues[i]; + case LONG: + case TIMESTAMP: + return _longValues[i]; + case FLOAT: + return _floatValues[i]; + case DOUBLE: + return _doublesValues[i]; + case STRING: + case BIG_DECIMAL: + return (Comparable) _objectsValues[i]; + default: + throw new IllegalStateException("Unsupported data type: " + _dataType); + } + } + + public int compare(int i, Object o) { + switch (_dataType) { + case INT: + case BOOLEAN: + return Integer.compare((Integer) o, _intValues[i]); + case LONG: + case TIMESTAMP: + return Long.compare((Long) o, _longValues[i]); + case FLOAT: + return Float.compare((Float) o, _floatValues[i]); + case DOUBLE: + return Double.compare((Double) o, _doublesValues[i]); + case STRING: + return ((String) o).compareTo((String) _objectsValues[i]); + case BIG_DECIMAL: + return ((java.math.BigDecimal) o).compareTo((java.math.BigDecimal) _objectsValues[i]); + default: + throw new IllegalStateException("Unsupported data type in comparison" + _dataType); + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/utils/exprminmax/ExprMinMaxObject.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/utils/exprminmax/ExprMinMaxObject.java new file mode 100644 index 000000000000..a169e7d136bd --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/utils/exprminmax/ExprMinMaxObject.java @@ -0,0 +1,353 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.query.aggregation.utils.exprminmax; + +import com.google.common.base.Preconditions; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.annotation.Nonnull; +import org.apache.pinot.common.datablock.DataBlock; +import org.apache.pinot.common.datablock.DataBlockUtils; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.common.datablock.DataBlockBuilder; +import org.apache.pinot.core.query.aggregation.utils.ParentAggregationFunctionResultObject; + + +public class ExprMinMaxObject implements ParentAggregationFunctionResultObject { + + // if the object is created but not yet populated, this happens e.g. when a server has no data for + // the query and returns a default value + enum ObjectNullState { + NULL(0), + NON_NULL(1); + + final int _state; + + ObjectNullState(int i) { + _state = i; + } + + int getState() { + return _state; + } + } + + // if the object contains non null values + private boolean _isNull; + + // if the value is stored in a mutable list, this is false only when the Object is deserialized from a byte buffer + // if the object is mutable, it means that the object is read only and the values are stored in + // _immutableMeasuringKeys and _immutableProjectionVals, otherwise we read and write from _extremumMeasuringKeys + // and _extremumProjectionValues + private boolean _mutable; + + // the schema of the measuring columns + private final DataSchema _measuringSchema; + // the schema of the projection columns + private final DataSchema _projectionSchema; + + // the size of the extremum key cols and value cols + private final int _sizeOfExtremumMeasuringKeys; + private final int _sizeOfExtremumProjectionVals; + + // the current extremum keys, keys are the extremum values of the measuring columns, + // used for comparison + private Comparable[] _extremumMeasuringKeys = null; + // the current extremum values, values are the values of the projection columns + // associated with the minimum measuring column, used for projection + private final List _extremumProjectionValues = new ArrayList<>(); + + // used for ser/de + private DataBlock _immutableMeasuringKeys; + private DataBlock _immutableProjectionVals; + + public ExprMinMaxObject(DataSchema measuringSchema, DataSchema projectionSchema) { + _isNull = true; + _mutable = true; + + _measuringSchema = measuringSchema; + _projectionSchema = projectionSchema; + + _sizeOfExtremumMeasuringKeys = _measuringSchema.size(); + _sizeOfExtremumProjectionVals = _projectionSchema.size(); + } + + public ExprMinMaxObject(ByteBuffer byteBuffer) + throws IOException { + _mutable = false; + _isNull = byteBuffer.getInt() == ObjectNullState.NULL.getState(); + byteBuffer = byteBuffer.slice(); + _immutableMeasuringKeys = DataBlockUtils.getDataBlock(byteBuffer); + byteBuffer = byteBuffer.slice(); + _immutableProjectionVals = DataBlockUtils.getDataBlock(byteBuffer); + + _measuringSchema = _immutableMeasuringKeys.getDataSchema(); + _projectionSchema = _immutableProjectionVals.getDataSchema(); + + _sizeOfExtremumMeasuringKeys = _measuringSchema.size(); + _sizeOfExtremumProjectionVals = _projectionSchema.size(); + } + + public static ExprMinMaxObject fromBytes(byte[] bytes) + throws IOException { + return fromByteBuffer(ByteBuffer.wrap(bytes)); + } + + public static ExprMinMaxObject fromByteBuffer(ByteBuffer byteBuffer) + throws IOException { + return new ExprMinMaxObject(byteBuffer); + } + + // used for result serialization + @Nonnull + public byte[] toBytes() + throws IOException { + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + DataOutputStream dataOutputStream = new DataOutputStream(byteArrayOutputStream); + if (_isNull) { + // serialize the null object with schemas + dataOutputStream.writeInt(ObjectNullState.NULL.getState()); + _immutableMeasuringKeys = DataBlockBuilder.buildFromRows(Collections.emptyList(), _measuringSchema); + _immutableProjectionVals = DataBlockBuilder.buildFromRows(Collections.emptyList(), _projectionSchema); + } else { + dataOutputStream.writeInt(ObjectNullState.NON_NULL.getState()); + _immutableMeasuringKeys = + DataBlockBuilder.buildFromRows(Collections.singletonList(_extremumMeasuringKeys), _measuringSchema); + _immutableProjectionVals = DataBlockBuilder.buildFromRows(_extremumProjectionValues, _projectionSchema); + } + dataOutputStream.write(_immutableMeasuringKeys.toBytes()); + dataOutputStream.write(_immutableProjectionVals.toBytes()); + return byteArrayOutputStream.toByteArray(); + } + + /** + * Used during segment processing + * Compare the current key with the new key, and return the comparison result. + * > 0: the key is replaced because the new key is the new extremum + * = 0: new key is the same as the current extremum + * < 0: current key is still the extremum + */ + public int compareAndSetKey(List exprMinMaxWrapperValSets, int offset, + boolean isMax) { + Preconditions.checkState(_mutable, "Cannot compare and set key after the object is serialized"); + if (!_isNull) { + for (int i = 0; i < _sizeOfExtremumMeasuringKeys; i++) { + ExprMinMaxMeasuringValSetWrapper exprMinMaxWrapperValSet = exprMinMaxWrapperValSets.get(i); + int result = exprMinMaxWrapperValSet.compare(offset, _extremumMeasuringKeys[i]); + if (result != 0) { + if (isMax ? result < 0 : result > 0) { + for (int j = 0; j < _sizeOfExtremumMeasuringKeys; j++) { + _extremumMeasuringKeys[j] = exprMinMaxWrapperValSets.get(j).getComparable(offset); + } + return 1; + } + return -1; + } + } + } else { + _isNull = false; + _extremumMeasuringKeys = new Comparable[_sizeOfExtremumMeasuringKeys]; + for (int i = 0; i < _sizeOfExtremumMeasuringKeys; i++) { + _extremumMeasuringKeys[i] = exprMinMaxWrapperValSets.get(i).getComparable(offset); + } + } + return 0; + } + + /** + * Used during segment processing with compareAndSetKey + * Set the vals to the new vals if the key is replaced. + */ + public void setToNewVal(List exprMinMaxProjectionValSetWrappers, int offset) { + _extremumProjectionValues.clear(); + addVal(exprMinMaxProjectionValSetWrappers, offset); + } + + /** + * Used during segment processing with compareAndSetKey + * Add the vals to the list of vals if the key is the same. + */ + public void addVal(List exprMinMaxProjectionValSetWrappers, int offset) { + Object[] val = new Object[_projectionSchema.size()]; + for (int i = 0; i < _projectionSchema.size(); i++) { + val[i] = exprMinMaxProjectionValSetWrappers.get(i).getValue(offset); + } + _extremumProjectionValues.add(val); + } + + public Comparable[] getExtremumKey() { + if (_mutable) { + return _extremumMeasuringKeys; + } else { + Comparable[] extremumKeys = new Comparable[_sizeOfExtremumMeasuringKeys]; + for (int i = 0; i < _sizeOfExtremumMeasuringKeys; i++) { + switch (_measuringSchema.getColumnDataType(i)) { + case INT: + case BOOLEAN: + extremumKeys[i] = _immutableMeasuringKeys.getInt(0, i); + break; + case LONG: + case TIMESTAMP: + extremumKeys[i] = _immutableMeasuringKeys.getLong(0, i); + break; + case FLOAT: + extremumKeys[i] = _immutableMeasuringKeys.getFloat(0, i); + break; + case DOUBLE: + extremumKeys[i] = _immutableMeasuringKeys.getDouble(0, i); + break; + case STRING: + extremumKeys[i] = _immutableMeasuringKeys.getString(0, i); + break; + case BIG_DECIMAL: + extremumKeys[i] = _immutableMeasuringKeys.getBigDecimal(0, i); + break; + default: + throw new IllegalStateException("Unsupported data type: " + _measuringSchema.getColumnDataType(i)); + } + } + return extremumKeys; + } + } + + /** + * Get the field from a projection column + */ + @Override + public Object getField(int rowId, int colId) { + if (_mutable) { + return _extremumProjectionValues.get(rowId)[colId]; + } else { + switch (_projectionSchema.getColumnDataType(colId)) { + case BOOLEAN: + case INT: + return _immutableProjectionVals.getInt(rowId, colId); + case TIMESTAMP: + case LONG: + return _immutableProjectionVals.getLong(rowId, colId); + case FLOAT: + return _immutableProjectionVals.getFloat(rowId, colId); + case DOUBLE: + return _immutableProjectionVals.getDouble(rowId, colId); + case JSON: + case STRING: + return _immutableProjectionVals.getString(rowId, colId); + case BYTES: + return _immutableProjectionVals.getBytes(rowId, colId); + case BIG_DECIMAL: + return _immutableProjectionVals.getBigDecimal(rowId, colId); + case BOOLEAN_ARRAY: + case INT_ARRAY: + return _immutableProjectionVals.getIntArray(rowId, colId); + case TIMESTAMP_ARRAY: + case LONG_ARRAY: + return _immutableProjectionVals.getLongArray(rowId, colId); + case FLOAT_ARRAY: + return _immutableProjectionVals.getFloatArray(rowId, colId); + case DOUBLE_ARRAY: + return _immutableProjectionVals.getDoubleArray(rowId, colId); + case STRING_ARRAY: + case BYTES_ARRAY: + return _immutableProjectionVals.getStringArray(rowId, colId); + default: + throw new IllegalStateException("Unsupported data type: " + _projectionSchema.getColumnDataType(colId)); + } + } + } + + /** + * Merge two exprminMaxObjects + */ + public ExprMinMaxObject merge(ExprMinMaxObject other, boolean isMax) { + if (_isNull && other._isNull) { + return this; + } else if (_isNull) { + return other; + } else if (other._isNull) { + return this; + } else { + int result; + Comparable[] key = getExtremumKey(); + Comparable[] otherKey = other.getExtremumKey(); + for (int i = 0; i < _sizeOfExtremumMeasuringKeys; i++) { + result = key[i].compareTo(otherKey[i]); + if (result != 0) { + // If the keys are not equal, return the object with the extremum key + if (isMax) { + return result > 0 ? this : other; + } else { + return result < 0 ? this : other; + } + } + } + // If the keys are equal, add the values of the other object to this object + if (!_mutable) { + // If the result is immutable, we need to copy the values from the serialized result to the mutable result + _mutable = true; + for (int i = 0; i < getNumberOfRows(); i++) { + Object[] val = new Object[_sizeOfExtremumProjectionVals]; + for (int j = 0; j < _sizeOfExtremumProjectionVals; j++) { + val[j] = getField(i, j); + } + _extremumProjectionValues.add(val); + } + } + for (int i = 0; i < other.getNumberOfRows(); i++) { + Object[] val = new Object[_sizeOfExtremumProjectionVals]; + for (int j = 0; j < _sizeOfExtremumProjectionVals; j++) { + val[j] = other.getField(i, j); + } + _extremumProjectionValues.add(val); + } + return this; + } + } + + /** + * get the number of rows in the projection data + */ + @Override + public int getNumberOfRows() { + if (_mutable) { + return _extremumProjectionValues.size(); + } else { + return _immutableProjectionVals.getNumberOfRows(); + } + } + + /** + * return the schema of the projection data + */ + @Override + public DataSchema getSchema() { + // the final parent aggregation result only cares about the projection columns + return _projectionSchema; + } + + @Override + public int compareTo(ParentAggregationFunctionResultObject o) { + return this.getNumberOfRows() - o.getNumberOfRows(); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/utils/exprminmax/ExprMinMaxProjectionValSetWrapper.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/utils/exprminmax/ExprMinMaxProjectionValSetWrapper.java new file mode 100644 index 000000000000..e855c55c9d73 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/utils/exprminmax/ExprMinMaxProjectionValSetWrapper.java @@ -0,0 +1,70 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.utils.exprminmax; + +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.common.BlockValSet; + + +/** + * Wrapper class for projection block value set for exprmin/max aggregation function. + * Used to get the value from val set of different data types. + */ +public class ExprMinMaxProjectionValSetWrapper extends ExprMinMaxWrapperValSet { + + public ExprMinMaxProjectionValSetWrapper(boolean isSingleValue, DataSchema.ColumnDataType dataType, + BlockValSet blockValSet) { + super(dataType, isSingleValue); + setNewBlock(blockValSet); + } + + public Object getValue(int i) { + switch (_dataType) { + case INT: + case BOOLEAN: + return _intValues[i]; + case LONG: + case TIMESTAMP: + return _longValues[i]; + case FLOAT: + return _floatValues[i]; + case DOUBLE: + return _doublesValues[i]; + case STRING: + case BIG_DECIMAL: + case BYTES: + case JSON: + return _objectsValues[i]; + case INT_ARRAY: + return _intValuesMV[i].length == 0 ? null : _intValuesMV[i]; + case LONG_ARRAY: + case TIMESTAMP_ARRAY: + return _longValuesMV[i].length == 0 ? null : _longValuesMV[i]; + case FLOAT_ARRAY: + return _floatValuesMV[i].length == 0 ? null : _floatValuesMV[i]; + case DOUBLE_ARRAY: + return _doublesValuesMV[i].length == 0 ? null : _doublesValuesMV[i]; + case STRING_ARRAY: + case BYTES_ARRAY: + return _objectsValuesMV[i].length == 0 ? null : _objectsValuesMV[i]; + default: + throw new IllegalStateException("Unsupported data type: " + _dataType); + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/utils/exprminmax/ExprMinMaxWrapperValSet.java b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/utils/exprminmax/ExprMinMaxWrapperValSet.java new file mode 100644 index 000000000000..7090e4e9e07c --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/aggregation/utils/exprminmax/ExprMinMaxWrapperValSet.java @@ -0,0 +1,106 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.utils.exprminmax; + +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.common.BlockValSet; + + +/** + * Wrapper class for the value sets of the column to do exprmin/max on. + * This class is used for type-generic implementation of exprmin/max. + */ +public class ExprMinMaxWrapperValSet { + protected final DataSchema.ColumnDataType _dataType; + boolean _isSingleValue; + int[] _intValues; + long[] _longValues; + float[] _floatValues; + double[] _doublesValues; + Object[] _objectsValues; + int[][] _intValuesMV; + long[][] _longValuesMV; + float[][] _floatValuesMV; + double[][] _doublesValuesMV; + Object[][] _objectsValuesMV; + + public ExprMinMaxWrapperValSet( + DataSchema.ColumnDataType dataType, boolean isSingleValue) { + _dataType = dataType; + _isSingleValue = isSingleValue; + } + + public void setNewBlock(BlockValSet blockValSet) { + if (_isSingleValue) { + switch (_dataType) { + case INT: + case BOOLEAN: + _intValues = blockValSet.getIntValuesSV(); + break; + case LONG: + case TIMESTAMP: + _longValues = blockValSet.getLongValuesSV(); + break; + case FLOAT: + _floatValues = blockValSet.getFloatValuesSV(); + break; + case DOUBLE: + _doublesValues = blockValSet.getDoubleValuesSV(); + break; + case STRING: + case JSON: + _objectsValues = blockValSet.getStringValuesSV(); + break; + case BIG_DECIMAL: + _objectsValues = blockValSet.getBigDecimalValuesSV(); + break; + case BYTES: + _objectsValues = blockValSet.getBytesValuesSV(); + break; + default: + throw new IllegalStateException("Unsupported data type: " + _dataType); + } + } else { + switch (_dataType) { + case INT_ARRAY: + case BOOLEAN_ARRAY: + _intValuesMV = blockValSet.getIntValuesMV(); + break; + case LONG_ARRAY: + case TIMESTAMP_ARRAY: + _longValuesMV = blockValSet.getLongValuesMV(); + break; + case FLOAT_ARRAY: + _floatValuesMV = blockValSet.getFloatValuesMV(); + break; + case DOUBLE_ARRAY: + _doublesValuesMV = blockValSet.getDoubleValuesMV(); + break; + case STRING_ARRAY: + _objectsValuesMV = blockValSet.getStringValuesMV(); + break; + case BYTES_ARRAY: + _objectsValuesMV = blockValSet.getBytesValuesMV(); + break; + default: + throw new IllegalStateException("Unsupported data type: " + _dataType); + } + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/config/QueryExecutorConfig.java b/pinot-core/src/main/java/org/apache/pinot/core/query/config/QueryExecutorConfig.java index 1d5306f01d3c..5c24536d9ada 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/config/QueryExecutorConfig.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/config/QueryExecutorConfig.java @@ -18,62 +18,39 @@ */ package org.apache.pinot.core.query.config; -import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration2.ex.ConfigurationException; import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.utils.CommonConstants.Server; /** * Config for QueryExecutor. - * - * */ public class QueryExecutorConfig { - - // Prefix key of Query Pruner public static final String QUERY_PRUNER = "pruner"; - // Prefix key of Query Planner - public static final String QUERY_PLANNER = "queryPlanner"; - // Prefix key of TimeOut + public static final String PLAN_MAKER_CLASS = "plan.maker.class"; public static final String TIME_OUT = "timeout"; - private static final String[] REQUIRED_KEYS = {}; - - private PinotConfiguration _queryExecutorConfig = null; - private SegmentPrunerConfig _segmentPrunerConfig; - private QueryPlannerConfig _queryPlannerConfig; - private final long _timeOutMs; + private final PinotConfiguration _config; public QueryExecutorConfig(PinotConfiguration config) throws ConfigurationException { - _queryExecutorConfig = config; - checkRequiredKeys(); - _segmentPrunerConfig = new SegmentPrunerConfig(_queryExecutorConfig.subset(QUERY_PRUNER)); - _queryPlannerConfig = new QueryPlannerConfig(_queryExecutorConfig.subset(QUERY_PLANNER)); - _timeOutMs = _queryExecutorConfig.getProperty(TIME_OUT, -1); - } - - private void checkRequiredKeys() - throws ConfigurationException { - for (String keyString : REQUIRED_KEYS) { - if (!_queryExecutorConfig.containsKey(keyString)) { - throw new ConfigurationException("Cannot find required key : " + keyString); - } - } + _config = config; } public PinotConfiguration getConfig() { - return _queryExecutorConfig; + return _config; } public SegmentPrunerConfig getPrunerConfig() { - return _segmentPrunerConfig; + return new SegmentPrunerConfig(_config.subset(QUERY_PRUNER)); } - public QueryPlannerConfig getQueryPlannerConfig() { - return _queryPlannerConfig; + public String getPlanMakerClass() { + return _config.getProperty(PLAN_MAKER_CLASS, Server.DEFAULT_QUERY_EXECUTOR_PLAN_MAKER_CLASS); } public long getTimeOut() { - return _timeOutMs; + return _config.getProperty(TIME_OUT, Server.DEFAULT_QUERY_EXECUTOR_TIMEOUT_MS); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/config/QueryPlannerConfig.java b/pinot-core/src/main/java/org/apache/pinot/core/query/config/QueryPlannerConfig.java deleted file mode 100644 index 7c9091720867..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/config/QueryPlannerConfig.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.query.config; - -import org.apache.commons.configuration.ConfigurationException; -import org.apache.pinot.spi.env.PinotConfiguration; - - -/** - * Config for QueryPlanner. - * - * - */ -public class QueryPlannerConfig { - - private PinotConfiguration _queryPlannerConfig; - private static final String[] REQUIRED_KEYS = {}; - - public QueryPlannerConfig(PinotConfiguration queryPlannerConfig) - throws ConfigurationException { - _queryPlannerConfig = queryPlannerConfig; - checkRequiredKeys(); - } - - private void checkRequiredKeys() - throws ConfigurationException { - for (String keyString : REQUIRED_KEYS) { - if (!_queryPlannerConfig.containsKey(keyString)) { - throw new ConfigurationException("Cannot find required key : " + keyString); - } - } - } - - public String getQueryPlannerType() { - return _queryPlannerConfig.getProperty("type"); - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/config/SegmentPrunerConfig.java b/pinot-core/src/main/java/org/apache/pinot/core/query/config/SegmentPrunerConfig.java index 604bf1080115..20ed9730c6c6 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/config/SegmentPrunerConfig.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/config/SegmentPrunerConfig.java @@ -19,9 +19,9 @@ package org.apache.pinot.core.query.config; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.utils.CommonConstants.Server; /** @@ -35,7 +35,8 @@ public class SegmentPrunerConfig { private final List _segmentPrunerConfigs; public SegmentPrunerConfig(PinotConfiguration segmentPrunerConfig) { - List segmentPrunerNames = segmentPrunerConfig.getProperty(SEGMENT_PRUNER_NAMES_KEY, Arrays.asList()); + List segmentPrunerNames = + segmentPrunerConfig.getProperty(SEGMENT_PRUNER_NAMES_KEY, Server.DEFAULT_QUERY_EXECUTOR_PRUNER_CLASS); _numSegmentPruners = segmentPrunerNames.size(); _segmentPrunerNames = new ArrayList<>(_numSegmentPruners); _segmentPrunerConfigs = new ArrayList<>(_numSegmentPruners); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/DistinctExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/DistinctExecutor.java index 1a51e11aebf7..053a9d558073 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/DistinctExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/DistinctExecutor.java @@ -18,7 +18,7 @@ */ package org.apache.pinot.core.query.distinct; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; /** @@ -29,10 +29,10 @@ public interface DistinctExecutor { int MAX_INITIAL_CAPACITY = 10000; /** - * Processes the given transform block, returns {@code true} if the query is already satisfied, {@code false} + * Processes the given value block, returns {@code true} if the query is already satisfied, {@code false} * otherwise. No more calls should be made after it returns {@code true}. */ - boolean process(TransformBlock transformBlock); + boolean process(ValueBlock valueBlock); /** * Returns the distinct result. Note that the returned DistinctTable might not be a main DistinctTable, thus cannot be diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/DistinctExecutorFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/DistinctExecutorFactory.java index 6f988cf6a22e..5a3e052c157d 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/DistinctExecutorFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/DistinctExecutorFactory.java @@ -22,9 +22,8 @@ import java.util.List; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.OrderByExpressionContext; -import org.apache.pinot.core.operator.transform.TransformOperator; -import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.core.query.aggregation.function.DistinctAggregationFunction; +import org.apache.pinot.core.operator.BaseProjectOperator; +import org.apache.pinot.core.operator.ColumnContext; import org.apache.pinot.core.query.distinct.dictionary.DictionaryBasedMultiColumnDistinctOnlyExecutor; import org.apache.pinot.core.query.distinct.dictionary.DictionaryBasedMultiColumnDistinctOrderByExecutor; import org.apache.pinot.core.query.distinct.dictionary.DictionaryBasedSingleColumnDistinctOnlyExecutor; @@ -44,6 +43,7 @@ import org.apache.pinot.core.query.distinct.raw.RawMultiColumnDistinctExecutor; import org.apache.pinot.core.query.distinct.raw.RawStringSingleColumnDistinctOnlyExecutor; import org.apache.pinot.core.query.distinct.raw.RawStringSingleColumnDistinctOrderByExecutor; +import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.spi.data.FieldSpec.DataType; @@ -58,28 +58,28 @@ private DistinctExecutorFactory() { /** * Returns the {@link DistinctExecutor} for the given distinct query. */ - public static DistinctExecutor getDistinctExecutor(DistinctAggregationFunction distinctAggregationFunction, - TransformOperator transformOperator, boolean nullHandlingEnabled) { - List expressions = distinctAggregationFunction.getInputExpressions(); - List orderByExpressions = distinctAggregationFunction.getOrderByExpressions(); - int limit = distinctAggregationFunction.getLimit(); + public static DistinctExecutor getDistinctExecutor(BaseProjectOperator projectOperator, + QueryContext queryContext) { + List expressions = queryContext.getSelectExpressions(); + List orderByExpressions = queryContext.getOrderByExpressions(); + int limit = queryContext.getLimit(); if (orderByExpressions == null) { - return getDistinctOnlyExecutor(expressions, limit, transformOperator, nullHandlingEnabled); + return getDistinctOnlyExecutor(expressions, limit, projectOperator, queryContext.isNullHandlingEnabled()); } else { - return getDistinctOrderByExecutor( - expressions, orderByExpressions, limit, transformOperator, nullHandlingEnabled); + return getDistinctOrderByExecutor(expressions, orderByExpressions, limit, projectOperator, + queryContext.isNullHandlingEnabled()); } } private static DistinctExecutor getDistinctOnlyExecutor(List expressions, int limit, - TransformOperator transformOperator, boolean nullHandlingEnabled) { + BaseProjectOperator projectOperator, boolean nullHandlingEnabled) { int numExpressions = expressions.size(); if (numExpressions == 1) { // Single column ExpressionContext expression = expressions.get(0); - TransformResultMetadata expressionMetadata = transformOperator.getResultMetadata(expression); - DataType dataType = expressionMetadata.getDataType(); - Dictionary dictionary = transformOperator.getDictionary(expression); + ColumnContext columnContext = projectOperator.getResultColumnContext(expression); + DataType dataType = columnContext.getDataType(); + Dictionary dictionary = columnContext.getDictionary(); if (dictionary != null && !nullHandlingEnabled) { // Dictionary based return new DictionaryBasedSingleColumnDistinctOnlyExecutor(expression, dictionary, dataType, limit); @@ -108,22 +108,21 @@ private static DistinctExecutor getDistinctOnlyExecutor(List // Multiple columns boolean hasMVExpression = false; List dataTypes = new ArrayList<>(numExpressions); - for (ExpressionContext expression : expressions) { - TransformResultMetadata expressionMetadata = transformOperator.getResultMetadata(expression); - if (!expressionMetadata.isSingleValue()) { - hasMVExpression = true; - } - dataTypes.add(expressionMetadata.getDataType()); - } List dictionaries = new ArrayList<>(numExpressions); boolean dictionaryBased = true; for (ExpressionContext expression : expressions) { - Dictionary dictionary = transformOperator.getDictionary(expression); - if (dictionary != null) { - dictionaries.add(dictionary); - } else { - dictionaryBased = false; - break; + ColumnContext columnContext = projectOperator.getResultColumnContext(expression); + if (!columnContext.isSingleValue()) { + hasMVExpression = true; + } + dataTypes.add(columnContext.getDataType()); + if (dictionaryBased) { + Dictionary dictionary = columnContext.getDictionary(); + if (dictionary != null) { + dictionaries.add(dictionary); + } else { + dictionaryBased = false; + } } } if (dictionaryBased) { @@ -132,23 +131,24 @@ private static DistinctExecutor getDistinctOnlyExecutor(List limit); } else { // Raw value based - return new RawMultiColumnDistinctExecutor(expressions, hasMVExpression, dataTypes, null, limit); + return new RawMultiColumnDistinctExecutor(expressions, hasMVExpression, dataTypes, null, nullHandlingEnabled, + limit); } } } private static DistinctExecutor getDistinctOrderByExecutor(List expressions, - List orderByExpressions, int limit, TransformOperator transformOperator, + List orderByExpressions, int limit, BaseProjectOperator projectOperator, boolean nullHandlingEnabled) { int numExpressions = expressions.size(); if (numExpressions == 1) { // Single column ExpressionContext expression = expressions.get(0); - TransformResultMetadata expressionMetadata = transformOperator.getResultMetadata(expression); - DataType dataType = expressionMetadata.getDataType(); + ColumnContext columnContext = projectOperator.getResultColumnContext(expression); + DataType dataType = columnContext.getDataType(); assert orderByExpressions.size() == 1; OrderByExpressionContext orderByExpression = orderByExpressions.get(0); - Dictionary dictionary = transformOperator.getDictionary(expression); + Dictionary dictionary = columnContext.getDictionary(); // Note: Use raw value based when dictionary is not sorted (consuming segments). if (dictionary != null && dictionary.isSorted() && !nullHandlingEnabled) { // Dictionary based @@ -186,32 +186,32 @@ private static DistinctExecutor getDistinctOrderByExecutor(List dataTypes = new ArrayList<>(numExpressions); - for (ExpressionContext expression : expressions) { - TransformResultMetadata expressionMetadata = transformOperator.getResultMetadata(expression); - if (!expressionMetadata.isSingleValue()) { - hasMVExpression = true; - } - dataTypes.add(expressionMetadata.getDataType()); - } List dictionaries = new ArrayList<>(numExpressions); boolean dictionaryBased = true; for (ExpressionContext expression : expressions) { - Dictionary dictionary = transformOperator.getDictionary(expression); - // Note: Use raw value based when dictionary is not sorted (consuming segments). - if (dictionary != null && dictionary.isSorted()) { - dictionaries.add(dictionary); - } else { - dictionaryBased = false; - break; + ColumnContext columnContext = projectOperator.getResultColumnContext(expression); + if (!columnContext.isSingleValue()) { + hasMVExpression = true; + } + dataTypes.add(columnContext.getDataType()); + if (dictionaryBased) { + Dictionary dictionary = columnContext.getDictionary(); + // Note: Use raw value based when dictionary is not sorted (consuming segments). + if (dictionary != null && dictionary.isSorted()) { + dictionaries.add(dictionary); + } else { + dictionaryBased = false; + } } } - if (dictionaryBased) { + if (dictionaryBased && !nullHandlingEnabled) { // Dictionary based return new DictionaryBasedMultiColumnDistinctOrderByExecutor(expressions, hasMVExpression, dictionaries, dataTypes, orderByExpressions, limit); } else { // Raw value based - return new RawMultiColumnDistinctExecutor(expressions, hasMVExpression, dataTypes, orderByExpressions, limit); + return new RawMultiColumnDistinctExecutor(expressions, hasMVExpression, dataTypes, orderByExpressions, + nullHandlingEnabled, limit); } } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/DistinctTable.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/DistinctTable.java index 0affc272f4da..1ba933be3d0f 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/DistinctTable.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/DistinctTable.java @@ -40,8 +40,8 @@ import org.apache.pinot.core.common.datatable.DataTableBuilder; import org.apache.pinot.core.common.datatable.DataTableBuilderFactory; import org.apache.pinot.core.data.table.Record; +import org.apache.pinot.spi.trace.Tracing; import org.apache.pinot.spi.utils.ByteArray; -import org.apache.pinot.spi.utils.LoopUtils; import org.roaringbitmap.RoaringBitmap; @@ -93,10 +93,12 @@ public DistinctTable(DataSchema dataSchema, @Nullable List(initialCapacity, (r1, r2) -> { @@ -110,9 +112,9 @@ public DistinctTable(DataSchema dataSchema, @Nullable List ex } @Override - public boolean process(TransformBlock transformBlock) { - int numDocs = transformBlock.getNumDocs(); + public boolean process(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); int numExpressions = _expressions.size(); if (!_hasMVExpression) { int[][] dictIdsArray = new int[numDocs][numExpressions]; for (int i = 0; i < numExpressions; i++) { - BlockValSet blockValueSet = transformBlock.getBlockValueSet(_expressions.get(i)); + BlockValSet blockValueSet = valueBlock.getBlockValueSet(_expressions.get(i)); int[] dictIdsForExpression = blockValueSet.getDictionaryIdsSV(); for (int j = 0; j < numDocs; j++) { dictIdsArray[j][i] = dictIdsForExpression[j]; @@ -63,7 +63,7 @@ public boolean process(TransformBlock transformBlock) { int[][] svDictIds = new int[numExpressions][]; int[][][] mvDictIds = new int[numExpressions][][]; for (int i = 0; i < numExpressions; i++) { - BlockValSet blockValueSet = transformBlock.getBlockValueSet(_expressions.get(i)); + BlockValSet blockValueSet = valueBlock.getBlockValueSet(_expressions.get(i)); if (blockValueSet.isSingleValue()) { svDictIds[i] = blockValueSet.getDictionaryIdsSV(); } else { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/dictionary/DictionaryBasedMultiColumnDistinctOrderByExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/dictionary/DictionaryBasedMultiColumnDistinctOrderByExecutor.java index 1d3a40d67ab5..b5fc60f8e6ca 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/dictionary/DictionaryBasedMultiColumnDistinctOrderByExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/dictionary/DictionaryBasedMultiColumnDistinctOrderByExecutor.java @@ -24,7 +24,7 @@ import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.OrderByExpressionContext; import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.core.query.distinct.DistinctExecutorUtils; import org.apache.pinot.segment.spi.index.reader.Dictionary; @@ -67,13 +67,13 @@ public DictionaryBasedMultiColumnDistinctOrderByExecutor(List } @Override - public boolean process(TransformBlock transformBlock) { - int numDocs = transformBlock.getNumDocs(); + public boolean process(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); int numExpressions = _expressions.size(); if (!_hasMVExpression) { int[][] dictIdsArray = new int[numDocs][numExpressions]; for (int i = 0; i < numExpressions; i++) { - BlockValSet blockValueSet = transformBlock.getBlockValueSet(_expressions.get(i)); + BlockValSet blockValueSet = valueBlock.getBlockValueSet(_expressions.get(i)); int[] dictIdsForExpression = blockValueSet.getDictionaryIdsSV(); for (int j = 0; j < numDocs; j++) { dictIdsArray[j][i] = dictIdsForExpression[j]; @@ -86,7 +86,7 @@ public boolean process(TransformBlock transformBlock) { int[][] svDictIds = new int[numExpressions][]; int[][][] mvDictIds = new int[numExpressions][][]; for (int i = 0; i < numExpressions; i++) { - BlockValSet blockValueSet = transformBlock.getBlockValueSet(_expressions.get(i)); + BlockValSet blockValueSet = valueBlock.getBlockValueSet(_expressions.get(i)); if (blockValueSet.isSingleValue()) { svDictIds[i] = blockValueSet.getDictionaryIdsSV(); } else { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/dictionary/DictionaryBasedSingleColumnDistinctOnlyExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/dictionary/DictionaryBasedSingleColumnDistinctOnlyExecutor.java index 150d8d8168c8..d7f25a334dc0 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/dictionary/DictionaryBasedSingleColumnDistinctOnlyExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/dictionary/DictionaryBasedSingleColumnDistinctOnlyExecutor.java @@ -20,7 +20,7 @@ import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.spi.data.FieldSpec.DataType; @@ -37,9 +37,9 @@ public DictionaryBasedSingleColumnDistinctOnlyExecutor(ExpressionContext express } @Override - public boolean process(TransformBlock transformBlock) { - BlockValSet blockValueSet = transformBlock.getBlockValueSet(_expression); - int numDocs = transformBlock.getNumDocs(); + public boolean process(ValueBlock valueBlock) { + BlockValSet blockValueSet = valueBlock.getBlockValueSet(_expression); + int numDocs = valueBlock.getNumDocs(); if (blockValueSet.isSingleValue()) { int[] dictIds = blockValueSet.getDictionaryIdsSV(); for (int i = 0; i < numDocs; i++) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/dictionary/DictionaryBasedSingleColumnDistinctOrderByExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/dictionary/DictionaryBasedSingleColumnDistinctOrderByExecutor.java index 329c7309152c..51aad6ae1eeb 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/dictionary/DictionaryBasedSingleColumnDistinctOrderByExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/dictionary/DictionaryBasedSingleColumnDistinctOrderByExecutor.java @@ -23,7 +23,7 @@ import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.OrderByExpressionContext; import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.spi.data.FieldSpec.DataType; @@ -47,9 +47,9 @@ public DictionaryBasedSingleColumnDistinctOrderByExecutor(ExpressionContext expr } @Override - public boolean process(TransformBlock transformBlock) { - BlockValSet blockValueSet = transformBlock.getBlockValueSet(_expression); - int numDocs = transformBlock.getNumDocs(); + public boolean process(ValueBlock valueBlock) { + BlockValSet blockValueSet = valueBlock.getBlockValueSet(_expression); + int numDocs = valueBlock.getNumDocs(); if (blockValueSet.isSingleValue()) { int[] dictIds = blockValueSet.getDictionaryIdsSV(); for (int i = 0; i < numDocs; i++) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawBigDecimalSingleColumnDistinctExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawBigDecimalSingleColumnDistinctExecutor.java index 3dcf78e6744e..ed60dcbf3153 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawBigDecimalSingleColumnDistinctExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawBigDecimalSingleColumnDistinctExecutor.java @@ -26,10 +26,13 @@ import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.data.table.Record; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.core.query.distinct.DistinctTable; import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.roaringbitmap.RoaringBitmap; /** @@ -42,6 +45,7 @@ public abstract class BaseRawBigDecimalSingleColumnDistinctExecutor implements D final boolean _nullHandlingEnabled; final ObjectSet _valueSet; + private boolean _hasNull; BaseRawBigDecimalSingleColumnDistinctExecutor(ExpressionContext expression, DataType dataType, int limit, boolean nullHandlingEnabled) { @@ -61,6 +65,36 @@ public DistinctTable getResult() { for (BigDecimal value : _valueSet) { records.add(new Record(new Object[]{value})); } + if (_hasNull) { + records.add(new Record(new Object[]{null})); + } + assert records.size() - (_hasNull ? 1 : 0) <= _limit; return new DistinctTable(dataSchema, records, _nullHandlingEnabled); } + + @Override + public boolean process(ValueBlock valueBlock) { + BlockValSet blockValueSet = valueBlock.getBlockValueSet(_expression); + BigDecimal[] values = blockValueSet.getBigDecimalValuesSV(); + int numDocs = valueBlock.getNumDocs(); + if (_nullHandlingEnabled) { + RoaringBitmap nullBitmap = blockValueSet.getNullBitmap(); + for (int i = 0; i < numDocs; i++) { + if (nullBitmap != null && nullBitmap.contains(i)) { + _hasNull = true; + } else if (add(values[i])) { + return true; + } + } + } else { + for (int i = 0; i < numDocs; i++) { + if (add(values[i])) { + return true; + } + } + } + return false; + } + + protected abstract boolean add(BigDecimal value); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawBytesSingleColumnDistinctExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawBytesSingleColumnDistinctExecutor.java index 870155bc3dab..73ad83e6726d 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawBytesSingleColumnDistinctExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawBytesSingleColumnDistinctExecutor.java @@ -25,11 +25,14 @@ import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.data.table.Record; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.core.query.distinct.DistinctTable; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.utils.ByteArray; +import org.roaringbitmap.RoaringBitmap; /** @@ -42,6 +45,7 @@ abstract class BaseRawBytesSingleColumnDistinctExecutor implements DistinctExecu final boolean _nullHandlingEnabled; final ObjectSet _valueSet; + private boolean _hasNull; BaseRawBytesSingleColumnDistinctExecutor(ExpressionContext expression, DataType dataType, int limit, boolean nullHandlingEnabled) { @@ -61,6 +65,36 @@ public DistinctTable getResult() { for (ByteArray value : _valueSet) { records.add(new Record(new Object[]{value})); } + if (_hasNull) { + records.add(new Record(new Object[]{null})); + } + assert records.size() - (_hasNull ? 1 : 0) <= _limit; return new DistinctTable(dataSchema, records, _nullHandlingEnabled); } + + @Override + public boolean process(ValueBlock valueBlock) { + BlockValSet blockValueSet = valueBlock.getBlockValueSet(_expression); + byte[][] values = blockValueSet.getBytesValuesSV(); + int numDocs = valueBlock.getNumDocs(); + if (_nullHandlingEnabled) { + RoaringBitmap nullBitmap = blockValueSet.getNullBitmap(); + for (int i = 0; i < numDocs; i++) { + if (nullBitmap != null && nullBitmap.contains(i)) { + _hasNull = true; + } else if (add(new ByteArray(values[i]))) { + return true; + } + } + } else { + for (int i = 0; i < numDocs; i++) { + if (add(new ByteArray(values[i]))) { + return true; + } + } + } + return false; + } + + protected abstract boolean add(ByteArray byteArray); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawDoubleSingleColumnDistinctExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawDoubleSingleColumnDistinctExecutor.java index 907b5c3b93ee..452eefb1709a 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawDoubleSingleColumnDistinctExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawDoubleSingleColumnDistinctExecutor.java @@ -26,10 +26,13 @@ import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.data.table.Record; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.core.query.distinct.DistinctTable; import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.roaringbitmap.RoaringBitmap; /** @@ -58,7 +61,7 @@ abstract class BaseRawDoubleSingleColumnDistinctExecutor implements DistinctExec public DistinctTable getResult() { DataSchema dataSchema = new DataSchema(new String[]{_expression.toString()}, new ColumnDataType[]{ColumnDataType.fromDataTypeSV(_dataType)}); - List records = new ArrayList<>(_valueSet.size()); + List records = new ArrayList<>(_valueSet.size() + (_hasNull ? 1 : 0)); DoubleIterator valueIterator = _valueSet.iterator(); while (valueIterator.hasNext()) { records.add(new Record(new Object[]{valueIterator.nextDouble()})); @@ -66,6 +69,44 @@ public DistinctTable getResult() { if (_hasNull) { records.add(new Record(new Object[]{null})); } + assert records.size() - (_hasNull ? 1 : 0) <= _limit; return new DistinctTable(dataSchema, records, _nullHandlingEnabled); } + + @Override + public boolean process(ValueBlock valueBlock) { + BlockValSet blockValueSet = valueBlock.getBlockValueSet(_expression); + int numDocs = valueBlock.getNumDocs(); + if (blockValueSet.isSingleValue()) { + double[] values = blockValueSet.getDoubleValuesSV(); + if (_nullHandlingEnabled) { + RoaringBitmap nullBitmap = blockValueSet.getNullBitmap(); + for (int i = 0; i < numDocs; i++) { + if (nullBitmap != null && nullBitmap.contains(i)) { + _hasNull = true; + } else if (add(values[i])) { + return true; + } + } + } else { + for (int i = 0; i < numDocs; i++) { + if (add(values[i])) { + return true; + } + } + } + } else { + int[][] values = blockValueSet.getIntValuesMV(); + for (int i = 0; i < numDocs; i++) { + for (double value : values[i]) { + if (add(value)) { + return true; + } + } + } + } + return false; + } + + protected abstract boolean add(double val); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawFloatSingleColumnDistinctExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawFloatSingleColumnDistinctExecutor.java index 9648b26a87ba..dd772a1e122c 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawFloatSingleColumnDistinctExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawFloatSingleColumnDistinctExecutor.java @@ -26,10 +26,13 @@ import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.data.table.Record; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.core.query.distinct.DistinctTable; import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.roaringbitmap.RoaringBitmap; /** @@ -58,7 +61,7 @@ abstract class BaseRawFloatSingleColumnDistinctExecutor implements DistinctExecu public DistinctTable getResult() { DataSchema dataSchema = new DataSchema(new String[]{_expression.toString()}, new ColumnDataType[]{ColumnDataType.fromDataTypeSV(_dataType)}); - List records = new ArrayList<>(_valueSet.size()); + List records = new ArrayList<>(_valueSet.size() + (_hasNull ? 1 : 0)); FloatIterator valueIterator = _valueSet.iterator(); while (valueIterator.hasNext()) { records.add(new Record(new Object[]{valueIterator.nextFloat()})); @@ -66,7 +69,44 @@ public DistinctTable getResult() { if (_hasNull) { records.add(new Record(new Object[]{null})); } - assert records.size() <= _limit; + assert records.size() - (_hasNull ? 1 : 0) <= _limit; return new DistinctTable(dataSchema, records, _nullHandlingEnabled); } + + @Override + public boolean process(ValueBlock valueBlock) { + BlockValSet blockValueSet = valueBlock.getBlockValueSet(_expression); + int numDocs = valueBlock.getNumDocs(); + if (blockValueSet.isSingleValue()) { + float[] values = blockValueSet.getFloatValuesSV(); + if (_nullHandlingEnabled) { + RoaringBitmap nullBitmap = blockValueSet.getNullBitmap(); + for (int i = 0; i < numDocs; i++) { + if (nullBitmap != null && nullBitmap.contains(i)) { + _hasNull = true; + } else if (add(values[i])) { + return true; + } + } + } else { + for (int i = 0; i < numDocs; i++) { + if (add(values[i])) { + return true; + } + } + } + } else { + float[][] values = blockValueSet.getFloatValuesMV(); + for (int i = 0; i < numDocs; i++) { + for (float value : values[i]) { + if (add(value)) { + return true; + } + } + } + } + return false; + } + + protected abstract boolean add(float val); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawIntSingleColumnDistinctExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawIntSingleColumnDistinctExecutor.java index c3b8413d3397..b8f87b8d5666 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawIntSingleColumnDistinctExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawIntSingleColumnDistinctExecutor.java @@ -26,10 +26,13 @@ import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.data.table.Record; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.core.query.distinct.DistinctTable; import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.roaringbitmap.RoaringBitmap; /** @@ -59,7 +62,7 @@ abstract class BaseRawIntSingleColumnDistinctExecutor implements DistinctExecuto public DistinctTable getResult() { DataSchema dataSchema = new DataSchema(new String[]{_expression.toString()}, new ColumnDataType[]{ColumnDataType.fromDataTypeSV(_dataType)}); - List records = new ArrayList<>(_valueSet.size()); + List records = new ArrayList<>(_valueSet.size() + (_hasNull ? 1 : 0)); IntIterator valueIterator = _valueSet.iterator(); while (valueIterator.hasNext()) { records.add(new Record(new Object[]{valueIterator.nextInt()})); @@ -67,7 +70,44 @@ public DistinctTable getResult() { if (_hasNull) { records.add(new Record(new Object[]{null})); } - assert records.size() <= _limit; + assert records.size() - (_hasNull ? 1 : 0) <= _limit; return new DistinctTable(dataSchema, records, _nullHandlingEnabled); } + + @Override + public boolean process(ValueBlock valueBlock) { + BlockValSet blockValueSet = valueBlock.getBlockValueSet(_expression); + int numDocs = valueBlock.getNumDocs(); + if (blockValueSet.isSingleValue()) { + int[] values = blockValueSet.getIntValuesSV(); + if (_nullHandlingEnabled) { + RoaringBitmap nullBitmap = blockValueSet.getNullBitmap(); + for (int i = 0; i < numDocs; i++) { + if (nullBitmap != null && nullBitmap.contains(i)) { + _hasNull = true; + } else if (add(values[i])) { + return true; + } + } + } else { + for (int i = 0; i < numDocs; i++) { + if (add(values[i])) { + return true; + } + } + } + } else { + int[][] values = blockValueSet.getIntValuesMV(); + for (int i = 0; i < numDocs; i++) { + for (int value : values[i]) { + if (add(value)) { + return true; + } + } + } + } + return false; + } + + protected abstract boolean add(int val); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawLongSingleColumnDistinctExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawLongSingleColumnDistinctExecutor.java index a75d733c38c0..eb627c74c211 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawLongSingleColumnDistinctExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawLongSingleColumnDistinctExecutor.java @@ -26,10 +26,13 @@ import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.data.table.Record; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.core.query.distinct.DistinctTable; import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.roaringbitmap.RoaringBitmap; /** @@ -58,7 +61,7 @@ abstract class BaseRawLongSingleColumnDistinctExecutor implements DistinctExecut public DistinctTable getResult() { DataSchema dataSchema = new DataSchema(new String[]{_expression.toString()}, new ColumnDataType[]{ColumnDataType.fromDataTypeSV(_dataType)}); - List records = new ArrayList<>(_valueSet.size()); + List records = new ArrayList<>(_valueSet.size() + (_hasNull ? 1 : 0)); LongIterator valueIterator = _valueSet.iterator(); while (valueIterator.hasNext()) { records.add(new Record(new Object[]{valueIterator.nextLong()})); @@ -66,7 +69,44 @@ public DistinctTable getResult() { if (_hasNull) { records.add(new Record(new Object[]{null})); } - assert records.size() <= _limit; + assert records.size() - (_hasNull ? 1 : 0) <= _limit; return new DistinctTable(dataSchema, records, _nullHandlingEnabled); } + + @Override + public boolean process(ValueBlock valueBlock) { + BlockValSet blockValueSet = valueBlock.getBlockValueSet(_expression); + int numDocs = valueBlock.getNumDocs(); + if (blockValueSet.isSingleValue()) { + long[] values = blockValueSet.getLongValuesSV(); + if (_nullHandlingEnabled) { + RoaringBitmap nullBitmap = blockValueSet.getNullBitmap(); + for (int i = 0; i < numDocs; i++) { + if (nullBitmap != null && nullBitmap.contains(i)) { + _hasNull = true; + } else if (add(values[i])) { + return true; + } + } + } else { + for (int i = 0; i < numDocs; i++) { + if (add(values[i])) { + return true; + } + } + } + } else { + long[][] values = blockValueSet.getLongValuesMV(); + for (int i = 0; i < numDocs; i++) { + for (long value : values[i]) { + if (add(value)) { + return true; + } + } + } + } + return false; + } + + protected abstract boolean add(long val); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawStringSingleColumnDistinctExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawStringSingleColumnDistinctExecutor.java index 564a9c3e802a..2a939862ea3b 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawStringSingleColumnDistinctExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/BaseRawStringSingleColumnDistinctExecutor.java @@ -25,10 +25,13 @@ import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.data.table.Record; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.core.query.distinct.DistinctTable; import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.roaringbitmap.RoaringBitmap; /** @@ -41,6 +44,7 @@ abstract class BaseRawStringSingleColumnDistinctExecutor implements DistinctExec final boolean _nullHandlingEnabled; final ObjectSet _valueSet; + private boolean _hasNull; BaseRawStringSingleColumnDistinctExecutor(ExpressionContext expression, DataType dataType, int limit, boolean nullHandlingEnabled) { @@ -60,6 +64,47 @@ public DistinctTable getResult() { for (String value : _valueSet) { records.add(new Record(new Object[]{value})); } + if (_hasNull) { + records.add(new Record(new Object[]{null})); + } + assert records.size() - (_hasNull ? 1 : 0) <= _limit; return new DistinctTable(dataSchema, records, _nullHandlingEnabled); } + + @Override + public boolean process(ValueBlock valueBlock) { + BlockValSet blockValueSet = valueBlock.getBlockValueSet(_expression); + int numDocs = valueBlock.getNumDocs(); + if (blockValueSet.isSingleValue()) { + String[] values = blockValueSet.getStringValuesSV(); + if (_nullHandlingEnabled) { + RoaringBitmap nullBitmap = blockValueSet.getNullBitmap(); + for (int i = 0; i < numDocs; i++) { + if (nullBitmap != null && nullBitmap.contains(i)) { + _hasNull = true; + } else if (add(values[i])) { + return true; + } + } + } else { + for (int i = 0; i < numDocs; i++) { + if (add(values[i])) { + return true; + } + } + } + } else { + String[][] values = blockValueSet.getStringValuesMV(); + for (int i = 0; i < numDocs; i++) { + for (String value : values[i]) { + if (add(value)) { + return true; + } + } + } + } + return false; + } + + protected abstract boolean add(String val); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawBigDecimalSingleColumnDistinctOnlyExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawBigDecimalSingleColumnDistinctOnlyExecutor.java index a44ee36ef828..6f5bd46c83fc 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawBigDecimalSingleColumnDistinctOnlyExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawBigDecimalSingleColumnDistinctOnlyExecutor.java @@ -20,11 +20,8 @@ import java.math.BigDecimal; import org.apache.pinot.common.request.context.ExpressionContext; -import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.TransformBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.spi.data.FieldSpec.DataType; -import org.roaringbitmap.RoaringBitmap; /** @@ -38,29 +35,8 @@ public RawBigDecimalSingleColumnDistinctOnlyExecutor(ExpressionContext expressio } @Override - public boolean process(TransformBlock transformBlock) { - BlockValSet blockValueSet = transformBlock.getBlockValueSet(_expression); - BigDecimal[] values = blockValueSet.getBigDecimalValuesSV(); - int numDocs = transformBlock.getNumDocs(); - if (_nullHandlingEnabled) { - RoaringBitmap nullBitmap = blockValueSet.getNullBitmap(); - for (int i = 0; i < numDocs; i++) { - if (nullBitmap != null && nullBitmap.contains(i)) { - values[i] = null; - } - _valueSet.add(values[i]); - if (_valueSet.size() >= _limit) { - return true; - } - } - } else { - for (int i = 0; i < numDocs; i++) { - _valueSet.add(values[i]); - if (_valueSet.size() >= _limit) { - return true; - } - } - } - return false; + protected boolean add(BigDecimal value) { + _valueSet.add(value); + return _valueSet.size() >= _limit; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawBigDecimalSingleColumnDistinctOrderByExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawBigDecimalSingleColumnDistinctOrderByExecutor.java index db1d87fa66a4..e0673f068a9b 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawBigDecimalSingleColumnDistinctOrderByExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawBigDecimalSingleColumnDistinctOrderByExecutor.java @@ -23,11 +23,8 @@ import java.math.BigDecimal; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.OrderByExpressionContext; -import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.TransformBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.spi.data.FieldSpec.DataType; -import org.roaringbitmap.RoaringBitmap; /** @@ -42,35 +39,12 @@ public RawBigDecimalSingleColumnDistinctOrderByExecutor(ExpressionContext expres assert orderByExpression.getExpression().equals(expression); int comparisonFactor = orderByExpression.isAsc() ? -1 : 1; - if (nullHandlingEnabled) { - _priorityQueue = new ObjectHeapPriorityQueue<>(Math.min(limit, MAX_INITIAL_CAPACITY), - (b1, b2) -> b1 == null ? (b2 == null ? 0 : 1) : (b2 == null ? -1 : b1.compareTo(b2)) * comparisonFactor); - } else { - _priorityQueue = new ObjectHeapPriorityQueue<>(Math.min(limit, MAX_INITIAL_CAPACITY), - (b1, b2) -> b1.compareTo(b2) * comparisonFactor); - } + _priorityQueue = new ObjectHeapPriorityQueue<>(Math.min(limit, MAX_INITIAL_CAPACITY), + (b1, b2) -> b1.compareTo(b2) * comparisonFactor); } @Override - public boolean process(TransformBlock transformBlock) { - BlockValSet blockValueSet = transformBlock.getBlockValueSet(_expression); - BigDecimal[] values = blockValueSet.getBigDecimalValuesSV(); - int numDocs = transformBlock.getNumDocs(); - if (_nullHandlingEnabled) { - RoaringBitmap nullBitmap = blockValueSet.getNullBitmap(); - for (int i = 0; i < numDocs; i++) { - BigDecimal value = nullBitmap != null && nullBitmap.contains(i) ? null : values[i]; - processInternal(value); - } - } else { - for (int i = 0; i < numDocs; i++) { - processInternal(values[i]); - } - } - return false; - } - - private void processInternal(BigDecimal value) { + protected boolean add(BigDecimal value) { if (!_valueSet.contains(value)) { if (_valueSet.size() < _limit) { _valueSet.add(value); @@ -85,5 +59,6 @@ private void processInternal(BigDecimal value) { } } } + return false; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawBytesSingleColumnDistinctOnlyExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawBytesSingleColumnDistinctOnlyExecutor.java index 3340dbb09b3e..fa6667988250 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawBytesSingleColumnDistinctOnlyExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawBytesSingleColumnDistinctOnlyExecutor.java @@ -19,12 +19,9 @@ package org.apache.pinot.core.query.distinct.raw; import org.apache.pinot.common.request.context.ExpressionContext; -import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.TransformBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.utils.ByteArray; -import org.roaringbitmap.RoaringBitmap; /** @@ -38,29 +35,8 @@ public RawBytesSingleColumnDistinctOnlyExecutor(ExpressionContext expression, Da } @Override - public boolean process(TransformBlock transformBlock) { - BlockValSet blockValueSet = transformBlock.getBlockValueSet(_expression); - byte[][] values = blockValueSet.getBytesValuesSV(); - int numDocs = transformBlock.getNumDocs(); - if (_nullHandlingEnabled) { - RoaringBitmap nullBitmap = blockValueSet.getNullBitmap(); - for (int i = 0; i < numDocs; i++) { - if (nullBitmap != null && nullBitmap.contains(i)) { - values[i] = null; - } - _valueSet.add(new ByteArray(values[i])); - if (_valueSet.size() >= _limit) { - return true; - } - } - } else { - for (int i = 0; i < numDocs; i++) { - _valueSet.add(new ByteArray(values[i])); - if (_valueSet.size() >= _limit) { - return true; - } - } - } - return false; + protected boolean add(ByteArray byteArray) { + _valueSet.add(byteArray); + return _valueSet.size() >= _limit; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawBytesSingleColumnDistinctOrderByExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawBytesSingleColumnDistinctOrderByExecutor.java index b14cad6189a6..03e3b26b3f64 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawBytesSingleColumnDistinctOrderByExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawBytesSingleColumnDistinctOrderByExecutor.java @@ -22,12 +22,9 @@ import it.unimi.dsi.fastutil.objects.ObjectHeapPriorityQueue; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.OrderByExpressionContext; -import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.TransformBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.utils.ByteArray; -import org.roaringbitmap.RoaringBitmap; /** @@ -42,48 +39,26 @@ public RawBytesSingleColumnDistinctOrderByExecutor(ExpressionContext expression, assert orderByExpression.getExpression().equals(expression); int comparisonFactor = orderByExpression.isAsc() ? -1 : 1; - if (nullHandlingEnabled) { - _priorityQueue = new ObjectHeapPriorityQueue<>(Math.min(limit, MAX_INITIAL_CAPACITY), - (b1, b2) -> b1 == null ? (b2 == null ? 0 : 1) : (b2 == null ? -1 : b1.compareTo(b2)) * comparisonFactor); - } else { - _priorityQueue = new ObjectHeapPriorityQueue<>(Math.min(limit, MAX_INITIAL_CAPACITY), - (b1, b2) -> b1.compareTo(b2) * comparisonFactor); - } + _priorityQueue = new ObjectHeapPriorityQueue<>(Math.min(limit, MAX_INITIAL_CAPACITY), + (b1, b2) -> b1.compareTo(b2) * comparisonFactor); } @Override - public boolean process(TransformBlock transformBlock) { - BlockValSet blockValueSet = transformBlock.getBlockValueSet(_expression); - byte[][] values = blockValueSet.getBytesValuesSV(); - int numDocs = transformBlock.getNumDocs(); - if (_nullHandlingEnabled) { - RoaringBitmap nullBitmap = blockValueSet.getNullBitmap(); - for (int i = 0; i < numDocs; i++) { - ByteArray value = nullBitmap != null && nullBitmap.contains(i) ? null : new ByteArray(values[i]); - processInternal(value); - } - } else { - for (int i = 0; i < numDocs; i++) { - processInternal(new ByteArray(values[i])); - } - } - return false; - } - - private void processInternal(ByteArray value) { - if (!_valueSet.contains(value)) { + protected boolean add(ByteArray byteArray) { + if (!_valueSet.contains(byteArray)) { if (_valueSet.size() < _limit) { - _valueSet.add(value); - _priorityQueue.enqueue(value); + _valueSet.add(byteArray); + _priorityQueue.enqueue(byteArray); } else { ByteArray firstValue = _priorityQueue.first(); - if (_priorityQueue.comparator().compare(value, firstValue) > 0) { + if (_priorityQueue.comparator().compare(byteArray, firstValue) > 0) { _valueSet.remove(firstValue); - _valueSet.add(value); + _valueSet.add(byteArray); _priorityQueue.dequeue(); - _priorityQueue.enqueue(value); + _priorityQueue.enqueue(byteArray); } } } + return false; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawDoubleSingleColumnDistinctOnlyExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawDoubleSingleColumnDistinctOnlyExecutor.java index 7cb926d221f2..ded36ea9a354 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawDoubleSingleColumnDistinctOnlyExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawDoubleSingleColumnDistinctOnlyExecutor.java @@ -19,11 +19,8 @@ package org.apache.pinot.core.query.distinct.raw; import org.apache.pinot.common.request.context.ExpressionContext; -import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.TransformBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.spi.data.FieldSpec.DataType; -import org.roaringbitmap.RoaringBitmap; /** @@ -37,44 +34,8 @@ public RawDoubleSingleColumnDistinctOnlyExecutor(ExpressionContext expression, D } @Override - public boolean process(TransformBlock transformBlock) { - BlockValSet blockValueSet = transformBlock.getBlockValueSet(_expression); - int numDocs = transformBlock.getNumDocs(); - if (blockValueSet.isSingleValue()) { - double[] values = blockValueSet.getDoubleValuesSV(); - if (_nullHandlingEnabled) { - // TODO(nhejazi): consider having a separate set of classes to handle the case with null handling enabled. - RoaringBitmap nullBitmap = blockValueSet.getNullBitmap(); - for (int i = 0; i < numDocs; i++) { - if (nullBitmap != null && nullBitmap.contains(i)) { - _hasNull = true; - } else { - _valueSet.add(values[i]); - if (_valueSet.size() >= _limit - (_hasNull ? 1 : 0)) { - return true; - } - } - } - } else { - for (int i = 0; i < numDocs; i++) { - _valueSet.add(values[i]); - if (_valueSet.size() >= _limit) { - return true; - } - } - } - } else { - // TODO(nhejazi): support proper null handling in multi-valued columns. - double[][] values = blockValueSet.getDoubleValuesMV(); - for (int i = 0; i < numDocs; i++) { - for (double value : values[i]) { - _valueSet.add(value); - if (_valueSet.size() >= _limit) { - return true; - } - } - } - } - return false; + protected boolean add(double value) { + _valueSet.add(value); + return _valueSet.size() >= _limit; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawDoubleSingleColumnDistinctOrderByExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawDoubleSingleColumnDistinctOrderByExecutor.java index 5dbfec9b41ca..6ddf633e4e29 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawDoubleSingleColumnDistinctOrderByExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawDoubleSingleColumnDistinctOrderByExecutor.java @@ -22,11 +22,8 @@ import it.unimi.dsi.fastutil.doubles.DoublePriorityQueue; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.OrderByExpressionContext; -import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.TransformBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.spi.data.FieldSpec.DataType; -import org.roaringbitmap.RoaringBitmap; /** @@ -46,39 +43,9 @@ public RawDoubleSingleColumnDistinctOrderByExecutor(ExpressionContext expression } @Override - public boolean process(TransformBlock transformBlock) { - BlockValSet blockValueSet = transformBlock.getBlockValueSet(_expression); - int numDocs = transformBlock.getNumDocs(); - if (blockValueSet.isSingleValue()) { - double[] values = blockValueSet.getDoubleValuesSV(); - if (_nullHandlingEnabled) { - RoaringBitmap nullBitmap = blockValueSet.getNullBitmap(); - for (int i = 0; i < numDocs; i++) { - if (nullBitmap != null && nullBitmap.contains(i)) { - _hasNull = true; - } else { - add(values[i]); - } - } - } else { - for (int i = 0; i < numDocs; i++) { - add(values[i]); - } - } - } else { - double[][] values = blockValueSet.getDoubleValuesMV(); - for (int i = 0; i < numDocs; i++) { - for (double value : values[i]) { - add(value); - } - } - } - return false; - } - - private void add(double value) { + protected boolean add(double value) { if (!_valueSet.contains(value)) { - if (_valueSet.size() < _limit - (_hasNull ? 1 : 0)) { + if (_valueSet.size() < _limit) { _valueSet.add(value); _priorityQueue.enqueue(value); } else { @@ -91,5 +58,6 @@ private void add(double value) { } } } + return false; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawFloatSingleColumnDistinctOnlyExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawFloatSingleColumnDistinctOnlyExecutor.java index 2bd32ba19207..d37ceb730ccf 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawFloatSingleColumnDistinctOnlyExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawFloatSingleColumnDistinctOnlyExecutor.java @@ -19,11 +19,8 @@ package org.apache.pinot.core.query.distinct.raw; import org.apache.pinot.common.request.context.ExpressionContext; -import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.TransformBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.spi.data.FieldSpec.DataType; -import org.roaringbitmap.RoaringBitmap; /** @@ -37,42 +34,8 @@ public RawFloatSingleColumnDistinctOnlyExecutor(ExpressionContext expression, Da } @Override - public boolean process(TransformBlock transformBlock) { - BlockValSet blockValueSet = transformBlock.getBlockValueSet(_expression); - int numDocs = transformBlock.getNumDocs(); - if (blockValueSet.isSingleValue()) { - float[] values = blockValueSet.getFloatValuesSV(); - if (_nullHandlingEnabled) { - RoaringBitmap nullBitmap = blockValueSet.getNullBitmap(); - for (int i = 0; i < numDocs; i++) { - if (nullBitmap != null && nullBitmap.contains(i)) { - _hasNull = true; - } else { - _valueSet.add(values[i]); - if (_valueSet.size() >= _limit - (_hasNull ? 1 : 0)) { - return true; - } - } - } - } else { - for (int i = 0; i < numDocs; i++) { - _valueSet.add(values[i]); - if (_valueSet.size() >= _limit) { - return true; - } - } - } - } else { - float[][] values = blockValueSet.getFloatValuesMV(); - for (int i = 0; i < numDocs; i++) { - for (float value : values[i]) { - _valueSet.add(value); - if (_valueSet.size() >= _limit) { - return true; - } - } - } - } - return false; + protected boolean add(float value) { + _valueSet.add(value); + return _valueSet.size() >= _limit; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawFloatSingleColumnDistinctOrderByExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawFloatSingleColumnDistinctOrderByExecutor.java index 50206647a4fc..9ecc59a9db00 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawFloatSingleColumnDistinctOrderByExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawFloatSingleColumnDistinctOrderByExecutor.java @@ -22,11 +22,8 @@ import it.unimi.dsi.fastutil.floats.FloatPriorityQueue; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.OrderByExpressionContext; -import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.TransformBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.spi.data.FieldSpec.DataType; -import org.roaringbitmap.RoaringBitmap; /** @@ -46,39 +43,9 @@ public RawFloatSingleColumnDistinctOrderByExecutor(ExpressionContext expression, } @Override - public boolean process(TransformBlock transformBlock) { - BlockValSet blockValueSet = transformBlock.getBlockValueSet(_expression); - int numDocs = transformBlock.getNumDocs(); - if (blockValueSet.isSingleValue()) { - float[] values = blockValueSet.getFloatValuesSV(); - if (_nullHandlingEnabled) { - RoaringBitmap nullBitmap = blockValueSet.getNullBitmap(); - for (int i = 0; i < numDocs; i++) { - if (nullBitmap != null && nullBitmap.contains(i)) { - _hasNull = true; - } else { - add(values[i]); - } - } - } else { - for (int i = 0; i < numDocs; i++) { - add(values[i]); - } - } - } else { - float[][] values = blockValueSet.getFloatValuesMV(); - for (int i = 0; i < numDocs; i++) { - for (float value : values[i]) { - add(value); - } - } - } - return false; - } - - private void add(float value) { + protected boolean add(float value) { if (!_valueSet.contains(value)) { - if (_valueSet.size() < _limit - (_hasNull ? 1 : 0)) { + if (_valueSet.size() < _limit) { _valueSet.add(value); _priorityQueue.enqueue(value); } else { @@ -91,5 +58,6 @@ private void add(float value) { } } } + return false; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawIntSingleColumnDistinctOnlyExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawIntSingleColumnDistinctOnlyExecutor.java index ba4ba8fe8ea3..c585d77c5d96 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawIntSingleColumnDistinctOnlyExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawIntSingleColumnDistinctOnlyExecutor.java @@ -19,11 +19,8 @@ package org.apache.pinot.core.query.distinct.raw; import org.apache.pinot.common.request.context.ExpressionContext; -import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.TransformBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.spi.data.FieldSpec.DataType; -import org.roaringbitmap.RoaringBitmap; /** @@ -37,42 +34,8 @@ public RawIntSingleColumnDistinctOnlyExecutor(ExpressionContext expression, Data } @Override - public boolean process(TransformBlock transformBlock) { - BlockValSet blockValueSet = transformBlock.getBlockValueSet(_expression); - int numDocs = transformBlock.getNumDocs(); - if (blockValueSet.isSingleValue()) { - int[] values = blockValueSet.getIntValuesSV(); - if (_nullHandlingEnabled) { - RoaringBitmap nullBitmap = blockValueSet.getNullBitmap(); - for (int i = 0; i < numDocs; i++) { - if (nullBitmap != null && nullBitmap.contains(i)) { - _hasNull = true; - } else { - _valueSet.add(values[i]); - if (_valueSet.size() >= _limit - (_hasNull ? 1 : 0)) { - return true; - } - } - } - } else { - for (int i = 0; i < numDocs; i++) { - _valueSet.add(values[i]); - if (_valueSet.size() >= _limit) { - return true; - } - } - } - } else { - int[][] values = blockValueSet.getIntValuesMV(); - for (int i = 0; i < numDocs; i++) { - for (int value : values[i]) { - _valueSet.add(value); - if (_valueSet.size() >= _limit) { - return true; - } - } - } - } - return false; + protected boolean add(int val) { + _valueSet.add(val); + return _valueSet.size() >= _limit; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawIntSingleColumnDistinctOrderByExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawIntSingleColumnDistinctOrderByExecutor.java index 4d32dbfca243..313b2722c979 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawIntSingleColumnDistinctOrderByExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawIntSingleColumnDistinctOrderByExecutor.java @@ -22,11 +22,8 @@ import it.unimi.dsi.fastutil.ints.IntPriorityQueue; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.OrderByExpressionContext; -import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.TransformBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.spi.data.FieldSpec.DataType; -import org.roaringbitmap.RoaringBitmap; /** @@ -46,39 +43,9 @@ public RawIntSingleColumnDistinctOrderByExecutor(ExpressionContext expression, D } @Override - public boolean process(TransformBlock transformBlock) { - BlockValSet blockValueSet = transformBlock.getBlockValueSet(_expression); - int numDocs = transformBlock.getNumDocs(); - if (blockValueSet.isSingleValue()) { - int[] values = blockValueSet.getIntValuesSV(); - if (_nullHandlingEnabled) { - RoaringBitmap nullBitmap = blockValueSet.getNullBitmap(); - for (int i = 0; i < numDocs; i++) { - if (nullBitmap != null && nullBitmap.contains(i)) { - _hasNull = true; - } else { - add(values[i]); - } - } - } else { - for (int i = 0; i < numDocs; i++) { - add(values[i]); - } - } - } else { - int[][] values = blockValueSet.getIntValuesMV(); - for (int i = 0; i < numDocs; i++) { - for (int value : values[i]) { - add(value); - } - } - } - return false; - } - - private void add(int value) { + protected boolean add(int value) { if (!_valueSet.contains(value)) { - if (_valueSet.size() < _limit - (_hasNull ? 1 : 0)) { + if (_valueSet.size() < _limit) { _valueSet.add(value); _priorityQueue.enqueue(value); } else { @@ -91,5 +58,6 @@ private void add(int value) { } } } + return false; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawLongSingleColumnDistinctOnlyExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawLongSingleColumnDistinctOnlyExecutor.java index 628b9bc29795..72bff91bd7b7 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawLongSingleColumnDistinctOnlyExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawLongSingleColumnDistinctOnlyExecutor.java @@ -19,11 +19,8 @@ package org.apache.pinot.core.query.distinct.raw; import org.apache.pinot.common.request.context.ExpressionContext; -import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.TransformBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.spi.data.FieldSpec.DataType; -import org.roaringbitmap.RoaringBitmap; /** @@ -37,42 +34,8 @@ public RawLongSingleColumnDistinctOnlyExecutor(ExpressionContext expression, Dat } @Override - public boolean process(TransformBlock transformBlock) { - BlockValSet blockValueSet = transformBlock.getBlockValueSet(_expression); - int numDocs = transformBlock.getNumDocs(); - if (blockValueSet.isSingleValue()) { - long[] values = blockValueSet.getLongValuesSV(); - if (_nullHandlingEnabled) { - RoaringBitmap nullBitmap = blockValueSet.getNullBitmap(); - for (int i = 0; i < numDocs; i++) { - if (nullBitmap != null && nullBitmap.contains(i)) { - _hasNull = true; - } else { - _valueSet.add(values[i]); - if (_valueSet.size() >= _limit - (_hasNull ? 1 : 0)) { - return true; - } - } - } - } else { - for (int i = 0; i < numDocs; i++) { - _valueSet.add(values[i]); - if (_valueSet.size() >= _limit) { - return true; - } - } - } - } else { - long[][] values = blockValueSet.getLongValuesMV(); - for (int i = 0; i < numDocs; i++) { - for (long value : values[i]) { - _valueSet.add(value); - if (_valueSet.size() >= _limit) { - return true; - } - } - } - } - return false; + protected boolean add(long val) { + _valueSet.add(val); + return _valueSet.size() >= _limit; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawLongSingleColumnDistinctOrderByExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawLongSingleColumnDistinctOrderByExecutor.java index 6dc74e69e03f..77dd3330c99c 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawLongSingleColumnDistinctOrderByExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawLongSingleColumnDistinctOrderByExecutor.java @@ -22,11 +22,8 @@ import it.unimi.dsi.fastutil.longs.LongPriorityQueue; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.OrderByExpressionContext; -import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.TransformBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.spi.data.FieldSpec.DataType; -import org.roaringbitmap.RoaringBitmap; /** @@ -46,39 +43,9 @@ public RawLongSingleColumnDistinctOrderByExecutor(ExpressionContext expression, } @Override - public boolean process(TransformBlock transformBlock) { - BlockValSet blockValueSet = transformBlock.getBlockValueSet(_expression); - int numDocs = transformBlock.getNumDocs(); - if (blockValueSet.isSingleValue()) { - long[] values = blockValueSet.getLongValuesSV(); - if (_nullHandlingEnabled) { - RoaringBitmap nullBitmap = blockValueSet.getNullBitmap(); - for (int i = 0; i < numDocs; i++) { - if (nullBitmap != null && nullBitmap.contains(i)) { - _hasNull = true; - } else { - add(values[i]); - } - } - } else { - for (int i = 0; i < numDocs; i++) { - add(values[i]); - } - } - } else { - long[][] values = blockValueSet.getLongValuesMV(); - for (int i = 0; i < numDocs; i++) { - for (long value : values[i]) { - add(value); - } - } - } - return false; - } - - private void add(long value) { + protected boolean add(long value) { if (!_valueSet.contains(value)) { - if (_valueSet.size() < _limit - (_hasNull ? 1 : 0)) { + if (_valueSet.size() < _limit) { _valueSet.add(value); _priorityQueue.enqueue(value); } else { @@ -91,5 +58,6 @@ private void add(long value) { } } } + return false; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawMultiColumnDistinctExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawMultiColumnDistinctExecutor.java index 9f01dad4b0c6..51ad0f950842 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawMultiColumnDistinctExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawMultiColumnDistinctExecutor.java @@ -28,12 +28,13 @@ import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.common.RowBasedBlockValueFetcher; import org.apache.pinot.core.data.table.Record; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.core.query.distinct.DistinctExecutorUtils; import org.apache.pinot.core.query.distinct.DistinctTable; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.utils.ByteArray; +import org.roaringbitmap.RoaringBitmap; /** @@ -43,11 +44,14 @@ public class RawMultiColumnDistinctExecutor implements DistinctExecutor { private final List _expressions; private final boolean _hasMVExpression; private final DistinctTable _distinctTable; + private final boolean _nullHandlingEnabled; public RawMultiColumnDistinctExecutor(List expressions, boolean hasMVExpression, - List dataTypes, @Nullable List orderByExpressions, int limit) { + List dataTypes, @Nullable List orderByExpressions, + boolean nullHandlingEnabled, int limit) { _expressions = expressions; _hasMVExpression = hasMVExpression; + _nullHandlingEnabled = nullHandlingEnabled; int numExpressions = expressions.size(); String[] columnNames = new String[numExpressions]; @@ -57,37 +61,48 @@ public RawMultiColumnDistinctExecutor(List expressions, boole columnDataTypes[i] = ColumnDataType.fromDataTypeSV(dataTypes.get(i)); } DataSchema dataSchema = new DataSchema(columnNames, columnDataTypes); - _distinctTable = new DistinctTable(dataSchema, orderByExpressions, limit, false); + _distinctTable = new DistinctTable(dataSchema, orderByExpressions, limit, _nullHandlingEnabled); } @Override - public boolean process(TransformBlock transformBlock) { - int numDocs = transformBlock.getNumDocs(); + public boolean process(ValueBlock valueBlock) { + int numDocs = valueBlock.getNumDocs(); int numExpressions = _expressions.size(); if (!_hasMVExpression) { BlockValSet[] blockValSets = new BlockValSet[numExpressions]; for (int i = 0; i < numExpressions; i++) { - blockValSets[i] = transformBlock.getBlockValueSet(_expressions.get(i)); + blockValSets[i] = valueBlock.getBlockValueSet(_expressions.get(i)); + } + RoaringBitmap[] nullBitmaps = new RoaringBitmap[numExpressions]; + if (_nullHandlingEnabled) { + for (int i = 0; i < numExpressions; i++) { + nullBitmaps[i] = blockValSets[i].getNullBitmap(); + } } RowBasedBlockValueFetcher valueFetcher = new RowBasedBlockValueFetcher(blockValSets); - if (_distinctTable.hasOrderBy()) { - for (int i = 0; i < numDocs; i++) { - Record record = new Record(valueFetcher.getRow(i)); - _distinctTable.addWithOrderBy(record); + for (int docId = 0; docId < numDocs; docId++) { + Record record = new Record(valueFetcher.getRow(docId)); + if (_nullHandlingEnabled) { + for (int i = 0; i < numExpressions; i++) { + if (nullBitmaps[i] != null && nullBitmaps[i].contains(docId)) { + record.getValues()[i] = null; + } + } } - } else { - for (int i = 0; i < numDocs; i++) { - Record record = new Record(valueFetcher.getRow(i)); + if (_distinctTable.hasOrderBy()) { + _distinctTable.addWithOrderBy(record); + } else { if (_distinctTable.addWithoutOrderBy(record)) { return true; } } } } else { + // TODO(https://github.com/apache/pinot/issues/10882): support NULL for multi-value Object[][] svValues = new Object[numExpressions][]; Object[][][] mvValues = new Object[numExpressions][][]; for (int i = 0; i < numExpressions; i++) { - BlockValSet blockValueSet = transformBlock.getBlockValueSet(_expressions.get(i)); + BlockValSet blockValueSet = valueBlock.getBlockValueSet(_expressions.get(i)); if (blockValueSet.isSingleValue()) { svValues[i] = getSVValues(blockValueSet, numDocs); } else { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawStringSingleColumnDistinctOnlyExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawStringSingleColumnDistinctOnlyExecutor.java index 4aa90a5b41de..97d57f0845d3 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawStringSingleColumnDistinctOnlyExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawStringSingleColumnDistinctOnlyExecutor.java @@ -19,11 +19,8 @@ package org.apache.pinot.core.query.distinct.raw; import org.apache.pinot.common.request.context.ExpressionContext; -import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.TransformBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.spi.data.FieldSpec.DataType; -import org.roaringbitmap.RoaringBitmap; /** @@ -37,41 +34,8 @@ public RawStringSingleColumnDistinctOnlyExecutor(ExpressionContext expression, D } @Override - public boolean process(TransformBlock transformBlock) { - BlockValSet blockValueSet = transformBlock.getBlockValueSet(_expression); - int numDocs = transformBlock.getNumDocs(); - if (blockValueSet.isSingleValue()) { - String[] values = blockValueSet.getStringValuesSV(); - if (_nullHandlingEnabled) { - RoaringBitmap nullBitmap = blockValueSet.getNullBitmap(); - for (int i = 0; i < numDocs; i++) { - if (nullBitmap != null && nullBitmap.contains(i)) { - values[i] = null; - } - _valueSet.add(values[i]); - if (_valueSet.size() >= _limit) { - return true; - } - } - } else { - for (int i = 0; i < numDocs; i++) { - _valueSet.add(values[i]); - if (_valueSet.size() >= _limit) { - return true; - } - } - } - } else { - String[][] values = blockValueSet.getStringValuesMV(); - for (int i = 0; i < numDocs; i++) { - for (String value : values[i]) { - _valueSet.add(value); - if (_valueSet.size() >= _limit) { - return true; - } - } - } - } - return false; + protected boolean add(String value) { + _valueSet.add(value); + return _valueSet.size() >= _limit; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawStringSingleColumnDistinctOrderByExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawStringSingleColumnDistinctOrderByExecutor.java index 6ecd8ab93253..d86bad4e903d 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawStringSingleColumnDistinctOrderByExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/distinct/raw/RawStringSingleColumnDistinctOrderByExecutor.java @@ -22,11 +22,8 @@ import it.unimi.dsi.fastutil.objects.ObjectHeapPriorityQueue; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.OrderByExpressionContext; -import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.TransformBlock; import org.apache.pinot.core.query.distinct.DistinctExecutor; import org.apache.pinot.spi.data.FieldSpec.DataType; -import org.roaringbitmap.RoaringBitmap; /** @@ -41,43 +38,12 @@ public RawStringSingleColumnDistinctOrderByExecutor(ExpressionContext expression assert orderByExpression.getExpression().equals(expression); int comparisonFactor = orderByExpression.isAsc() ? -1 : 1; - if (nullHandlingEnabled) { - _priorityQueue = new ObjectHeapPriorityQueue<>(Math.min(limit, MAX_INITIAL_CAPACITY), - (s1, s2) -> s1 == null ? (s2 == null ? 0 : 1) : (s2 == null ? -1 : s1.compareTo(s2)) * comparisonFactor); - } else { - _priorityQueue = new ObjectHeapPriorityQueue<>(Math.min(limit, MAX_INITIAL_CAPACITY), - (s1, s2) -> s1.compareTo(s2) * comparisonFactor); - } + _priorityQueue = new ObjectHeapPriorityQueue<>(Math.min(limit, MAX_INITIAL_CAPACITY), + (s1, s2) -> s1.compareTo(s2) * comparisonFactor); } @Override - public boolean process(TransformBlock transformBlock) { - BlockValSet blockValueSet = transformBlock.getBlockValueSet(_expression); - int numDocs = transformBlock.getNumDocs(); - if (blockValueSet.isSingleValue()) { - String[] values = blockValueSet.getStringValuesSV(); - if (_nullHandlingEnabled) { - RoaringBitmap nullBitmap = blockValueSet.getNullBitmap(); - for (int i = 0; i < numDocs; i++) { - add(nullBitmap != null && nullBitmap.contains(i) ? null : values[i]); - } - } else { - for (int i = 0; i < numDocs; i++) { - add(values[i]); - } - } - } else { - String[][] values = blockValueSet.getStringValuesMV(); - for (int i = 0; i < numDocs; i++) { - for (String value : values[i]) { - add(value); - } - } - } - return false; - } - - private void add(String value) { + protected boolean add(String value) { if (!_valueSet.contains(value)) { if (_valueSet.size() < _limit) { _valueSet.add(value); @@ -92,5 +58,6 @@ private void add(String value) { } } } + return false; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/executor/QueryExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/executor/QueryExecutor.java index b7ac47274318..60727da79fb1 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/executor/QueryExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/executor/QueryExecutor.java @@ -18,15 +18,11 @@ */ package org.apache.pinot.core.query.executor; -import io.grpc.stub.StreamObserver; import java.util.concurrent.ExecutorService; import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; -import org.apache.commons.configuration.ConfigurationException; -import org.apache.pinot.common.datatable.DataTable; -import org.apache.pinot.common.exception.QueryException; +import org.apache.commons.configuration2.ex.ConfigurationException; import org.apache.pinot.common.metrics.ServerMetrics; -import org.apache.pinot.common.proto.Server; import org.apache.pinot.core.data.manager.InstanceDataManager; import org.apache.pinot.core.operator.blocks.InstanceResponseBlock; import org.apache.pinot.core.query.request.ServerQueryRequest; @@ -39,6 +35,7 @@ public interface QueryExecutor { /** * Initializes the query executor. *

    Should be called only once and before calling any other method. + *

    NOTE: The config is the subset of server config with prefix 'pinot.server.query.executor' */ void init(PinotConfiguration config, InstanceDataManager instanceDataManager, ServerMetrics serverMetrics) throws ConfigurationException; @@ -55,43 +52,6 @@ void init(PinotConfiguration config, InstanceDataManager instanceDataManager, Se */ void shutDown(); - /** - * Processes the non-streaming query with the given executor service. - * - * Deprecated: use execute() instead. - */ - @Deprecated - default DataTable processQuery(ServerQueryRequest queryRequest, ExecutorService executorService) { - return processQuery(queryRequest, executorService, null); - } - - /** - * Processes the query (streaming or non-streaming) with the given executor service. - *

      - *
    • - * For streaming request, the returned {@link DataTable} contains only the metadata. The response is streamed back - * via the observer. - *
    • - *
    • - * For non-streaming request, the returned {@link DataTable} contains both data and metadata. - *
    • - *
    - * - * Deprecated: use execute() instead. - */ - @Deprecated - default DataTable processQuery(ServerQueryRequest queryRequest, ExecutorService executorService, - @Nullable StreamObserver responseObserver) { - InstanceResponseBlock instanceResponse = execute(queryRequest, executorService, responseObserver); - try { - return instanceResponse.toDataTable(); - } catch (Exception e) { - DataTable metadataOnlyDataTable = instanceResponse.toMetadataOnlyDataTable(); - metadataOnlyDataTable.addException(QueryException.getException(QueryException.DATA_TABLE_SERIALIZATION_ERROR, e)); - return metadataOnlyDataTable; - } - } - /** * Executes the non-streaming query with the given executor service. */ @@ -112,5 +72,5 @@ default InstanceResponseBlock execute(ServerQueryRequest queryRequest, ExecutorS * */ InstanceResponseBlock execute(ServerQueryRequest queryRequest, ExecutorService executorService, - @Nullable StreamObserver responseObserver); + @Nullable ResultsBlockStreamer streamer); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/executor/ResultsBlockStreamer.java b/pinot-core/src/main/java/org/apache/pinot/core/query/executor/ResultsBlockStreamer.java new file mode 100644 index 000000000000..ff42ba1c458d --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/executor/ResultsBlockStreamer.java @@ -0,0 +1,34 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.executor; + +import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock; + + +/** + * Streamer for results blocks. + */ +public interface ResultsBlockStreamer { + + /** + * Sends the results block. + */ + void send(BaseResultsBlock block) + throws Exception; +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/executor/ServerQueryExecutorV1Impl.java b/pinot-core/src/main/java/org/apache/pinot/core/query/executor/ServerQueryExecutorV1Impl.java index 92467ba31649..8c2906db541e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/executor/ServerQueryExecutorV1Impl.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/executor/ServerQueryExecutorV1Impl.java @@ -19,8 +19,8 @@ package org.apache.pinot.core.query.executor; import com.google.common.base.Preconditions; -import io.grpc.stub.StreamObserver; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -29,33 +29,31 @@ import java.util.stream.Collectors; import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; -import org.apache.commons.configuration.ConfigurationException; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.configuration2.ex.ConfigurationException; +import org.apache.commons.lang3.StringUtils; import org.apache.pinot.common.datatable.DataTable.MetadataKey; import org.apache.pinot.common.exception.QueryException; import org.apache.pinot.common.function.TransformFunctionType; import org.apache.pinot.common.metrics.ServerMeter; import org.apache.pinot.common.metrics.ServerMetrics; import org.apache.pinot.common.metrics.ServerQueryPhase; -import org.apache.pinot.common.proto.Server; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.FilterContext; import org.apache.pinot.common.request.context.FunctionContext; import org.apache.pinot.common.utils.config.QueryOptionsUtils; +import org.apache.pinot.common.utils.request.RequestUtils; import org.apache.pinot.core.common.ExplainPlanRowData; import org.apache.pinot.core.common.ExplainPlanRows; import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.data.manager.InstanceDataManager; import org.apache.pinot.core.data.manager.realtime.RealtimeTableDataManager; +import org.apache.pinot.core.operator.InstanceResponseOperator; import org.apache.pinot.core.operator.blocks.InstanceResponseBlock; import org.apache.pinot.core.operator.blocks.results.AggregationResultsBlock; import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock; import org.apache.pinot.core.operator.blocks.results.ExplainResultsBlock; import org.apache.pinot.core.operator.blocks.results.ResultsBlockUtils; -import org.apache.pinot.core.operator.filter.EmptyFilterOperator; -import org.apache.pinot.core.operator.filter.MatchAllFilterOperator; import org.apache.pinot.core.plan.Plan; -import org.apache.pinot.core.plan.maker.InstancePlanMakerImplV2; import org.apache.pinot.core.plan.maker.PlanMaker; import org.apache.pinot.core.query.aggregation.function.AggregationFunction; import org.apache.pinot.core.query.config.QueryExecutorConfig; @@ -73,13 +71,13 @@ import org.apache.pinot.segment.spi.ImmutableSegment; import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.segment.spi.MutableSegment; +import org.apache.pinot.segment.spi.SegmentContext; import org.apache.pinot.segment.spi.SegmentMetadata; -import org.apache.pinot.spi.data.FieldSpec; import org.apache.pinot.spi.env.PinotConfiguration; import org.apache.pinot.spi.exception.BadQueryRequestException; import org.apache.pinot.spi.exception.QueryCancelledException; +import org.apache.pinot.spi.plugin.PluginManager; import org.apache.pinot.spi.trace.Tracing; -import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -96,7 +94,7 @@ public class ServerQueryExecutorV1Impl implements QueryExecutor { private ServerMetrics _serverMetrics; private SegmentPrunerService _segmentPrunerService; private PlanMaker _planMaker; - private long _defaultTimeoutMs = CommonConstants.Server.DEFAULT_QUERY_EXECUTOR_TIMEOUT_MS; + private long _defaultTimeoutMs; private boolean _enablePrefetch; @Override @@ -108,11 +106,15 @@ public synchronized void init(PinotConfiguration config, InstanceDataManager ins QueryExecutorConfig queryExecutorConfig = new QueryExecutorConfig(config); LOGGER.info("Trying to build SegmentPrunerService"); _segmentPrunerService = new SegmentPrunerService(queryExecutorConfig.getPrunerConfig()); - LOGGER.info("Trying to build QueryPlanMaker"); - _planMaker = new InstancePlanMakerImplV2(queryExecutorConfig); - if (queryExecutorConfig.getTimeOut() > 0) { - _defaultTimeoutMs = queryExecutorConfig.getTimeOut(); + String planMakerClass = queryExecutorConfig.getPlanMakerClass(); + LOGGER.info("Trying to build PlanMaker with class: {}", planMakerClass); + try { + _planMaker = PluginManager.get().createInstance(planMakerClass); + } catch (Exception e) { + throw new RuntimeException("Caught exception while creating PlanMaker with class: " + planMakerClass); } + _planMaker.init(config); + _defaultTimeoutMs = queryExecutorConfig.getTimeOut(); _enablePrefetch = Boolean.parseBoolean(config.getProperty(ENABLE_PREFETCH)); LOGGER.info("Initialized query executor with defaultTimeoutMs: {}, enablePrefetch: {}", _defaultTimeoutMs, _enablePrefetch); @@ -130,9 +132,9 @@ public synchronized void shutDown() { @Override public InstanceResponseBlock execute(ServerQueryRequest queryRequest, ExecutorService executorService, - @Nullable StreamObserver responseObserver) { + @Nullable ResultsBlockStreamer streamer) { if (!queryRequest.isEnableTrace()) { - return executeInternal(queryRequest, executorService, responseObserver); + return executeInternal(queryRequest, executorService, streamer); } try { long requestId = queryRequest.getRequestId(); @@ -141,14 +143,14 @@ public InstanceResponseBlock execute(ServerQueryRequest queryRequest, ExecutorSe long traceId = TableNameBuilder.isRealtimeTableResource(queryRequest.getTableNameWithType()) ? -requestId : requestId; Tracing.getTracer().register(traceId); - return executeInternal(queryRequest, executorService, responseObserver); + return executeInternal(queryRequest, executorService, streamer); } finally { Tracing.getTracer().unregister(); } } private InstanceResponseBlock executeInternal(ServerQueryRequest queryRequest, ExecutorService executorService, - @Nullable StreamObserver responseObserver) { + @Nullable ResultsBlockStreamer streamer) { TimerContext timerContext = queryRequest.getTimerContext(); TimerContext.Timer schedulerWaitTimer = timerContext.getPhaseTimer(ServerQueryPhase.SCHEDULER_WAIT); if (schedulerWaitTimer != null) { @@ -199,10 +201,16 @@ private InstanceResponseBlock executeInternal(ServerQueryRequest queryRequest, E } List segmentsToQuery = queryRequest.getSegmentsToQuery(); + List optionalSegments = queryRequest.getOptionalSegments(); List notAcquiredSegments = new ArrayList<>(); List segmentDataManagers = - tableDataManager.acquireSegments(segmentsToQuery, notAcquiredSegments); + tableDataManager.acquireSegments(segmentsToQuery, optionalSegments, notAcquiredSegments); int numSegmentsAcquired = segmentDataManagers.size(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Processing requestId: {} with segmentsToQuery: {}, optionalSegments: {} and acquiredSegments: {}", + requestId, segmentsToQuery, optionalSegments, + segmentDataManagers.stream().map(SegmentDataManager::getSegmentName).collect(Collectors.toList())); + } List indexSegments = new ArrayList<>(numSegmentsAcquired); for (SegmentDataManager segmentDataManager : segmentDataManagers) { indexSegments.add(segmentDataManager.getSegment()); @@ -228,7 +236,8 @@ private InstanceResponseBlock executeInternal(ServerQueryRequest queryRequest, E if (indexTimeMs > 0) { minIndexTimeMs = Math.min(minIndexTimeMs, indexTimeMs); } - long ingestionTimeMs = segmentMetadata.getLatestIngestionTimestamp(); + long ingestionTimeMs = + ((RealtimeTableDataManager) tableDataManager).getPartitionIngestionTimeMs(indexSegment.getSegmentName()); if (ingestionTimeMs > 0) { minIngestionTimeMs = Math.min(minIngestionTimeMs, ingestionTimeMs); } @@ -249,8 +258,9 @@ private InstanceResponseBlock executeInternal(ServerQueryRequest queryRequest, E InstanceResponseBlock instanceResponse = null; try { - instanceResponse = executeInternal(indexSegments, queryContext, timerContext, executorService, responseObserver, - queryRequest.isEnableStreaming()); + instanceResponse = + executeInternal(tableDataManager, indexSegments, queryContext, timerContext, executorService, streamer, + queryRequest.isEnableStreaming()); } catch (Exception e) { _serverMetrics.addMeteredTableValue(tableNameWithType, ServerMeter.QUERY_EXECUTION_EXCEPTIONS, 1); instanceResponse = new InstanceResponseBlock(); @@ -268,10 +278,11 @@ private InstanceResponseBlock executeInternal(ServerQueryRequest queryRequest, E // return the error table to broker sooner than here. But in case of race condition, we construct the error // table here too. instanceResponse.addException(QueryException.getException(QueryException.QUERY_CANCELLATION_ERROR, - "Query cancelled on: " + _instanceDataManager.getInstanceId() + e)); + "Query cancelled on: " + _instanceDataManager.getInstanceId() + " " + e)); } else { LOGGER.error("Exception processing requestId {}", requestId, e); - instanceResponse.addException(QueryException.getException(QueryException.QUERY_EXECUTION_ERROR, e)); + instanceResponse.addException(QueryException.getException(QueryException.QUERY_EXECUTION_ERROR, + "Query execution error on: " + _instanceDataManager.getInstanceId() + " " + e)); } } finally { for (SegmentDataManager segmentDataManager : segmentDataManagers) { @@ -317,7 +328,7 @@ private InstanceResponseBlock executeInternal(ServerQueryRequest queryRequest, E } long minConsumingFreshnessTimeMs = 0; if (minIngestionTimeMs != Long.MAX_VALUE) { - minConsumingFreshnessTimeMs = minIndexTimeMs; + minConsumingFreshnessTimeMs = minIngestionTimeMs; } else if (minIndexTimeMs != Long.MAX_VALUE) { minConsumingFreshnessTimeMs = minIndexTimeMs; } else if (maxEndTimeMs != Long.MIN_VALUE) { @@ -336,11 +347,11 @@ private InstanceResponseBlock executeInternal(ServerQueryRequest queryRequest, E } // NOTE: This method might change indexSegments. Do not use it after calling this method. - private InstanceResponseBlock executeInternal(List indexSegments, QueryContext queryContext, - TimerContext timerContext, ExecutorService executorService, - @Nullable StreamObserver responseObserver, boolean enableStreaming) + private InstanceResponseBlock executeInternal(TableDataManager tableDataManager, List indexSegments, + QueryContext queryContext, TimerContext timerContext, ExecutorService executorService, + @Nullable ResultsBlockStreamer streamer, boolean enableStreaming) throws Exception { - handleSubquery(queryContext, indexSegments, timerContext, executorService); + handleSubquery(queryContext, tableDataManager, indexSegments, timerContext, executorService); // Compute total docs for the table before pruning the segments long numTotalDocs = 0; @@ -348,11 +359,18 @@ private InstanceResponseBlock executeInternal(List indexSegments, numTotalDocs += indexSegment.getSegmentMetadata().getTotalDocs(); } - TimerContext.Timer segmentPruneTimer = timerContext.startNewPhaseTimer(ServerQueryPhase.SEGMENT_PRUNING); + List selectedSegments; + SegmentPrunerStatistics prunerStats = null; + if ((queryContext.getFilter() != null && queryContext.getFilter().isConstantFalse()) || ( + queryContext.getHavingFilter() != null && queryContext.getHavingFilter().isConstantFalse())) { + selectedSegments = Collections.emptyList(); + } else { + TimerContext.Timer segmentPruneTimer = timerContext.startNewPhaseTimer(ServerQueryPhase.SEGMENT_PRUNING); + prunerStats = new SegmentPrunerStatistics(); + selectedSegments = _segmentPrunerService.prune(indexSegments, queryContext, prunerStats, executorService); + segmentPruneTimer.stopAndRecord(); + } int numTotalSegments = indexSegments.size(); - SegmentPrunerStatistics prunerStats = new SegmentPrunerStatistics(); - List selectedSegments = _segmentPrunerService.prune(indexSegments, queryContext, prunerStats); - segmentPruneTimer.stopAndRecord(); int numSelectedSegments = selectedSegments.size(); LOGGER.debug("Matched {} segments after pruning", numSelectedSegments); InstanceResponseBlock instanceResponse; @@ -360,15 +378,16 @@ private InstanceResponseBlock executeInternal(List indexSegments, if (queryContext.isExplain()) { instanceResponse = getExplainResponseForNoMatchingSegment(numTotalSegments, queryContext); } else { - instanceResponse = - new InstanceResponseBlock(ResultsBlockUtils.buildEmptyQueryResults(queryContext), queryContext); + instanceResponse = new InstanceResponseBlock(ResultsBlockUtils.buildEmptyQueryResults(queryContext)); } } else { TimerContext.Timer planBuildTimer = timerContext.startNewPhaseTimer(ServerQueryPhase.BUILD_QUERY_PLAN); + List selectedSegmentContexts = + tableDataManager.getSegmentContexts(selectedSegments, queryContext.getQueryOptions()); Plan queryPlan = - enableStreaming ? _planMaker.makeStreamingInstancePlan(selectedSegments, queryContext, executorService, - responseObserver, _serverMetrics) - : _planMaker.makeInstancePlan(selectedSegments, queryContext, executorService, _serverMetrics); + enableStreaming ? _planMaker.makeStreamingInstancePlan(selectedSegmentContexts, queryContext, executorService, + streamer, _serverMetrics) + : _planMaker.makeInstancePlan(selectedSegmentContexts, queryContext, executorService, _serverMetrics); planBuildTimer.stopAndRecord(); TimerContext.Timer planExecTimer = timerContext.startNewPhaseTimer(ServerQueryPhase.QUERY_PLAN_EXECUTION); @@ -382,18 +401,20 @@ private InstanceResponseBlock executeInternal(List indexSegments, // Set the number of pruned segments. This count does not include the segments which returned empty filters int prunedSegments = numTotalSegments - numSelectedSegments; instanceResponse.addMetadata(MetadataKey.NUM_SEGMENTS_PRUNED_BY_SERVER.getName(), String.valueOf(prunedSegments)); - addPrunerStats(instanceResponse, prunerStats); + if (prunerStats != null) { + addPrunerStats(instanceResponse, prunerStats); + } return instanceResponse; } private static InstanceResponseBlock getExplainResponseForNoMatchingSegment(int numTotalSegments, QueryContext queryContext) { - ExplainResultsBlock explainResults = new ExplainResultsBlock(); + ExplainResultsBlock explainResults = new ExplainResultsBlock(queryContext); explainResults.addOperator(String.format(ExplainPlanRows.PLAN_START_FORMAT, numTotalSegments), ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS); explainResults.addOperator(ExplainPlanRows.ALL_SEGMENTS_PRUNED_ON_SERVER, 3, 2); - return new InstanceResponseBlock(explainResults, queryContext); + return new InstanceResponseBlock(explainResults); } /** @@ -408,12 +429,14 @@ private static Map> getAllSegmentsUniqueExplainPl Map> uniquePlanNodeHashCodes = new HashMap<>(); // Obtain the list of all possible segment plans after the combine root node - List children = root.getChildOperators(); + List children = root.getChildOperators(); for (Operator child : children) { int[] operatorId = {3}; ExplainPlanRows explainPlanRows = new ExplainPlanRows(); // Get the segment explain plan for a single segment - getSegmentExplainPlanRowData(child, explainPlanRows, operatorId, 2); + if (child != null) { + child.explainPlan(explainPlanRows, operatorId, 2); + } int numRows = explainPlanRows.getExplainPlanRowData().size(); if (numRows > 0) { operatorDepthToRowDataMap.putIfAbsent(numRows, new ArrayList<>()); @@ -453,84 +476,65 @@ private static Map> getAllSegmentsUniqueExplainPl return operatorDepthToRowDataMap; } - /** - * Get the list of Explain Plan rows for a single segment - */ - private static void getSegmentExplainPlanRowData(Operator node, ExplainPlanRows explainPlanRows, int[] globalId, - int parentId) { - if (node == null) { - return; - } + public static InstanceResponseBlock executeExplainQuery(Plan queryPlan, QueryContext queryContext) { + ExplainResultsBlock explainResults = new ExplainResultsBlock(queryContext); + InstanceResponseOperator responseOperator = (InstanceResponseOperator) queryPlan.getPlanNode().run(); - String explainPlanString = node.toExplainString(); - if (explainPlanString != null) { - ExplainPlanRowData explainPlanRowData = new ExplainPlanRowData(explainPlanString, globalId[0], parentId); - parentId = globalId[0]++; - explainPlanRows.appendExplainPlanRowData(explainPlanRowData); - if (node instanceof EmptyFilterOperator) { - explainPlanRows.setHasEmptyFilter(true); - } - if (node instanceof MatchAllFilterOperator) { - explainPlanRows.setHasMatchAllFilter(true); + try { + responseOperator.prefetchAll(); + + List childOperators = queryPlan.getPlanNode().run().getChildOperators(); + assert childOperators.size() == 1; + Operator root = childOperators.get(0); + Map> operatorDepthToRowDataMap; + int numEmptyFilterSegments = 0; + int numMatchAllFilterSegments = 0; + + // Get the list of unique explain plans + operatorDepthToRowDataMap = getAllSegmentsUniqueExplainPlanRowData(root); + List listOfExplainPlans = new ArrayList<>(); + operatorDepthToRowDataMap.forEach((key, value) -> listOfExplainPlans.addAll(value)); + + // Setup the combine root's explain string + explainResults.addOperator(root.toExplainString(), 2, 1); + + // Walk through all the explain plans and create the entries in the explain plan output for each plan + for (ExplainPlanRows explainPlanRows : listOfExplainPlans) { + numEmptyFilterSegments += + explainPlanRows.isHasEmptyFilter() ? explainPlanRows.getNumSegmentsMatchingThisPlan() : 0; + numMatchAllFilterSegments += + explainPlanRows.isHasMatchAllFilter() ? explainPlanRows.getNumSegmentsMatchingThisPlan() : 0; + explainResults.addOperator( + String.format(ExplainPlanRows.PLAN_START_FORMAT, explainPlanRows.getNumSegmentsMatchingThisPlan()), + ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS); + for (ExplainPlanRowData explainPlanRowData : explainPlanRows.getExplainPlanRowData()) { + explainResults.addOperator(explainPlanRowData.getExplainPlanString(), explainPlanRowData.getOperatorId(), + explainPlanRowData.getParentId()); + } } - } - - List children = node.getChildOperators(); - for (Operator child : children) { - getSegmentExplainPlanRowData(child, explainPlanRows, globalId, parentId); - } - } - public static InstanceResponseBlock executeExplainQuery(Plan queryPlan, QueryContext queryContext) { - ExplainResultsBlock explainResults = new ExplainResultsBlock(); - List childOperators = queryPlan.getPlanNode().run().getChildOperators(); - assert childOperators.size() == 1; - Operator root = childOperators.get(0); - Map> operatorDepthToRowDataMap; - int numEmptyFilterSegments = 0; - int numMatchAllFilterSegments = 0; - - // Get the list of unique explain plans - operatorDepthToRowDataMap = getAllSegmentsUniqueExplainPlanRowData(root); - List listOfExplainPlans = new ArrayList<>(); - operatorDepthToRowDataMap.forEach((key, value) -> listOfExplainPlans.addAll(value)); - - // Setup the combine root's explain string - explainResults.addOperator(root.toExplainString(), 2, 1); - - // Walk through all the explain plans and create the entries in the explain plan output for each plan - for (ExplainPlanRows explainPlanRows : listOfExplainPlans) { - numEmptyFilterSegments += - explainPlanRows.isHasEmptyFilter() ? explainPlanRows.getNumSegmentsMatchingThisPlan() : 0; - numMatchAllFilterSegments += - explainPlanRows.isHasMatchAllFilter() ? explainPlanRows.getNumSegmentsMatchingThisPlan() : 0; - explainResults.addOperator( - String.format(ExplainPlanRows.PLAN_START_FORMAT, explainPlanRows.getNumSegmentsMatchingThisPlan()), - ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS); - for (ExplainPlanRowData explainPlanRowData : explainPlanRows.getExplainPlanRowData()) { - explainResults.addOperator(explainPlanRowData.getExplainPlanString(), explainPlanRowData.getOperatorId(), - explainPlanRowData.getParentId()); - } + InstanceResponseBlock instanceResponse = new InstanceResponseBlock(explainResults); + instanceResponse.addMetadata(MetadataKey.EXPLAIN_PLAN_NUM_EMPTY_FILTER_SEGMENTS.getName(), + String.valueOf(numEmptyFilterSegments)); + instanceResponse.addMetadata(MetadataKey.EXPLAIN_PLAN_NUM_MATCH_ALL_FILTER_SEGMENTS.getName(), + String.valueOf(numMatchAllFilterSegments)); + return instanceResponse; + } finally { + responseOperator.releaseAll(); } - - InstanceResponseBlock instanceResponse = new InstanceResponseBlock(explainResults, queryContext); - instanceResponse.addMetadata(MetadataKey.EXPLAIN_PLAN_NUM_EMPTY_FILTER_SEGMENTS.getName(), - String.valueOf(numEmptyFilterSegments)); - instanceResponse.addMetadata(MetadataKey.EXPLAIN_PLAN_NUM_MATCH_ALL_FILTER_SEGMENTS.getName(), - String.valueOf(numMatchAllFilterSegments)); - return instanceResponse; } /** * Handles the subquery in the given query. *

    Currently only supports subquery within the filter. */ - private void handleSubquery(QueryContext queryContext, List indexSegments, TimerContext timerContext, - ExecutorService executorService) + private void handleSubquery(QueryContext queryContext, TableDataManager tableDataManager, + List indexSegments, TimerContext timerContext, ExecutorService executorService) throws Exception { FilterContext filter = queryContext.getFilter(); - if (filter != null) { - handleSubquery(filter, indexSegments, timerContext, executorService, queryContext.getEndTimeMs()); + if (filter != null && !filter.isConstant()) { + handleSubquery(filter, tableDataManager, indexSegments, timerContext, executorService, + queryContext.getEndTimeMs()); } } @@ -538,16 +542,17 @@ private void handleSubquery(QueryContext queryContext, List indexS * Handles the subquery in the given filter. *

    Currently only supports subquery within the lhs of the predicate. */ - private void handleSubquery(FilterContext filter, List indexSegments, TimerContext timerContext, - ExecutorService executorService, long endTimeMs) + private void handleSubquery(FilterContext filter, TableDataManager tableDataManager, List indexSegments, + TimerContext timerContext, ExecutorService executorService, long endTimeMs) throws Exception { List children = filter.getChildren(); if (children != null) { for (FilterContext child : children) { - handleSubquery(child, indexSegments, timerContext, executorService, endTimeMs); + handleSubquery(child, tableDataManager, indexSegments, timerContext, executorService, endTimeMs); } } else { - handleSubquery(filter.getPredicate().getLhs(), indexSegments, timerContext, executorService, endTimeMs); + handleSubquery(filter.getPredicate().getLhs(), tableDataManager, indexSegments, timerContext, executorService, + endTimeMs); } } @@ -558,8 +563,8 @@ private void handleSubquery(FilterContext filter, List indexSegmen *

    Currently only supports ID_SET subquery within the IN_PARTITIONED_SUBQUERY transform function, which will be * rewritten to an IN_ID_SET transform function. */ - private void handleSubquery(ExpressionContext expression, List indexSegments, TimerContext timerContext, - ExecutorService executorService, long endTimeMs) + private void handleSubquery(ExpressionContext expression, TableDataManager tableDataManager, + List indexSegments, TimerContext timerContext, ExecutorService executorService, long endTimeMs) throws Exception { FunctionContext function = expression.getFunction(); if (function == null) { @@ -572,7 +577,8 @@ private void handleSubquery(ExpressionContext expression, List ind ExpressionContext subqueryExpression = arguments.get(1); Preconditions.checkState(subqueryExpression.getType() == ExpressionContext.Type.LITERAL, "Second argument of IN_PARTITIONED_SUBQUERY must be a literal (subquery)"); - QueryContext subquery = QueryContextConverterUtils.getQueryContext(subqueryExpression.getLiteralString()); + QueryContext subquery = + QueryContextConverterUtils.getQueryContext(subqueryExpression.getLiteral().getStringValue()); // Subquery should be an ID_SET aggregation only query //noinspection rawtypes AggregationFunction[] aggregationFunctions = subquery.getAggregationFunctions(); @@ -585,7 +591,8 @@ private void handleSubquery(ExpressionContext expression, List ind subquery.setEndTimeMs(endTimeMs); // Make a clone of indexSegments because the method might modify the list InstanceResponseBlock instanceResponse = - executeInternal(new ArrayList<>(indexSegments), subquery, timerContext, executorService, null, false); + executeInternal(tableDataManager, new ArrayList<>(indexSegments), subquery, timerContext, executorService, + null, false); BaseResultsBlock resultsBlock = instanceResponse.getResultsBlock(); Preconditions.checkState(resultsBlock instanceof AggregationResultsBlock, "Got unexpected results block type: %s, expecting aggregation results", @@ -594,12 +601,11 @@ private void handleSubquery(ExpressionContext expression, List ind Preconditions.checkState(result instanceof IdSet, "Got unexpected result type: %s, expecting IdSet", result != null ? result.getClass().getSimpleName() : null); // Rewrite the expression - function.setFunctionName(TransformFunctionType.INIDSET.name()); - arguments.set(1, - ExpressionContext.forLiteralContext(FieldSpec.DataType.STRING, ((IdSet) result).toBase64String())); + function.setFunctionName(TransformFunctionType.IN_ID_SET.name()); + arguments.set(1, ExpressionContext.forLiteral(RequestUtils.getLiteral(((IdSet) result).toBase64String()))); } else { for (ExpressionContext argument : arguments) { - handleSubquery(argument, indexSegments, timerContext, executorService, endTimeMs); + handleSubquery(argument, tableDataManager, indexSegments, timerContext, executorService, endTimeMs); } } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/executor/sql/SqlQueryExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/executor/sql/SqlQueryExecutor.java index 9d7239203a62..c929716f6e42 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/executor/sql/SqlQueryExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/executor/sql/SqlQueryExecutor.java @@ -18,8 +18,6 @@ */ package org.apache.pinot.core.query.executor.sql; -import com.google.common.collect.ImmutableList; -import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -103,20 +101,20 @@ public BrokerResponse executeDMLStatement(SqlNodeAndOptions sqlNodeAndOptions, List rows = new ArrayList<>(); tableToTaskIdMap.forEach((key, value) -> rows.add(new Object[]{key, value})); result.setResultTable(new ResultTable(statement.getResultSchema(), rows)); - } catch (IOException e) { - result.setExceptions(ImmutableList.of(QueryException.getException(QueryException.QUERY_EXECUTION_ERROR, e))); + } catch (Exception e) { + result.addException(QueryException.getException(QueryException.QUERY_EXECUTION_ERROR, e)); } break; case HTTP: try { result.setResultTable(new ResultTable(statement.getResultSchema(), statement.execute())); } catch (Exception e) { - result.setExceptions(ImmutableList.of(QueryException.getException(QueryException.QUERY_EXECUTION_ERROR, e))); + result.addException(QueryException.getException(QueryException.QUERY_EXECUTION_ERROR, e)); } break; default: - result.setExceptions(ImmutableList.of(QueryException.getException(QueryException.QUERY_EXECUTION_ERROR, - new UnsupportedOperationException("Unsupported statement - " + statement)))); + result.addException( + QueryException.getException(QueryException.QUERY_EXECUTION_ERROR, "Unsupported statement: " + statement)); break; } return result; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/logger/ServerQueryLogger.java b/pinot-core/src/main/java/org/apache/pinot/core/query/logger/ServerQueryLogger.java new file mode 100644 index 000000000000..a9908a87b153 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/logger/ServerQueryLogger.java @@ -0,0 +1,227 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.logger; + +import com.google.common.util.concurrent.RateLimiter; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Nullable; +import org.apache.pinot.common.datatable.DataTable.MetadataKey; +import org.apache.pinot.common.metrics.ServerMeter; +import org.apache.pinot.common.metrics.ServerMetrics; +import org.apache.pinot.common.metrics.ServerQueryPhase; +import org.apache.pinot.common.metrics.ServerTimer; +import org.apache.pinot.core.operator.blocks.InstanceResponseBlock; +import org.apache.pinot.core.query.request.ServerQueryRequest; +import org.apache.pinot.core.query.request.context.TimerContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +@SuppressWarnings("UnstableApiUsage") +public class ServerQueryLogger { + private static final Logger LOGGER = LoggerFactory.getLogger(ServerQueryLogger.class); + private static final AtomicReference INSTANCE = new AtomicReference<>(); + + private final ServerMetrics _serverMetrics; + private final RateLimiter _queryLogRateLimiter; + private final RateLimiter _droppedReportRateLimiter; + private final AtomicInteger _numDroppedLogs = new AtomicInteger(); + + public static void init(double queryLogMaxRate, double droppedReportMaxRate, ServerMetrics serverMetrics) { + if (INSTANCE.compareAndSet(null, new ServerQueryLogger(queryLogMaxRate, droppedReportMaxRate, serverMetrics))) { + LOGGER.info("Initialized ServerQueryLogger with query log max rate: {}, dropped report max rate: {}", + queryLogMaxRate, droppedReportMaxRate); + } else { + LOGGER.error("ServerQueryLogger is already initialized, not initializing it again"); + } + } + + @Nullable + public static ServerQueryLogger getInstance() { + // NOTE: In some tests, ServerQueryLogger might not be initialized. Returns null when it is not initialized. + return INSTANCE.get(); + } + + private ServerQueryLogger(double queryLogMaxRate, double droppedReportMaxRate, ServerMetrics serverMetrics) { + _serverMetrics = serverMetrics; + _queryLogRateLimiter = RateLimiter.create(queryLogMaxRate); + _droppedReportRateLimiter = RateLimiter.create(droppedReportMaxRate); + } + + public void logQuery(ServerQueryRequest request, InstanceResponseBlock response, String schedulerType) { + String tableNameWithType = request.getTableNameWithType(); + Map responseMetadata = response.getResponseMetadata(); + + long numDocsScanned = getLongValue(responseMetadata, MetadataKey.NUM_DOCS_SCANNED.getName(), -1); + addToTableMeter(tableNameWithType, ServerMeter.NUM_DOCS_SCANNED, numDocsScanned); + + long numEntriesScannedInFilter = + getLongValue(responseMetadata, MetadataKey.NUM_ENTRIES_SCANNED_IN_FILTER.getName(), -1); + addToTableMeter(tableNameWithType, ServerMeter.NUM_ENTRIES_SCANNED_IN_FILTER, numEntriesScannedInFilter); + + long numEntriesScannedPostFilter = + getLongValue(responseMetadata, MetadataKey.NUM_ENTRIES_SCANNED_POST_FILTER.getName(), -1); + addToTableMeter(tableNameWithType, ServerMeter.NUM_ENTRIES_SCANNED_POST_FILTER, numEntriesScannedPostFilter); + + long numSegmentsQueried = getLongValue(responseMetadata, MetadataKey.NUM_SEGMENTS_QUERIED.getName(), -1); + addToTableMeter(tableNameWithType, ServerMeter.NUM_SEGMENTS_QUERIED, numSegmentsQueried); + + long numSegmentsProcessed = getLongValue(responseMetadata, MetadataKey.NUM_SEGMENTS_PROCESSED.getName(), -1); + addToTableMeter(tableNameWithType, ServerMeter.NUM_SEGMENTS_PROCESSED, numSegmentsProcessed); + + long numSegmentsMatched = getLongValue(responseMetadata, MetadataKey.NUM_SEGMENTS_MATCHED.getName(), -1); + addToTableMeter(tableNameWithType, ServerMeter.NUM_SEGMENTS_MATCHED, numSegmentsMatched); + + long numSegmentsPrunedInvalid = + getLongValue(responseMetadata, MetadataKey.NUM_SEGMENTS_PRUNED_INVALID.getName(), -1); + addToTableMeter(tableNameWithType, ServerMeter.NUM_SEGMENTS_PRUNED_INVALID, numSegmentsPrunedInvalid); + + long numSegmentsPrunedByLimit = + getLongValue(responseMetadata, MetadataKey.NUM_SEGMENTS_PRUNED_BY_LIMIT.getName(), -1); + addToTableMeter(tableNameWithType, ServerMeter.NUM_SEGMENTS_PRUNED_BY_LIMIT, numSegmentsPrunedByLimit); + + long numSegmentsPrunedByValue = + getLongValue(responseMetadata, MetadataKey.NUM_SEGMENTS_PRUNED_BY_VALUE.getName(), -1); + addToTableMeter(tableNameWithType, ServerMeter.NUM_SEGMENTS_PRUNED_BY_VALUE, numSegmentsPrunedByValue); + + long numConsumingSegmentsQueried = + getLongValue(responseMetadata, MetadataKey.NUM_CONSUMING_SEGMENTS_QUERIED.getName(), -1); + long numConsumingSegmentsProcessed = + getLongValue(responseMetadata, MetadataKey.NUM_CONSUMING_SEGMENTS_PROCESSED.getName(), -1); + long numConsumingSegmentsMatched = + getLongValue(responseMetadata, MetadataKey.NUM_CONSUMING_SEGMENTS_MATCHED.getName(), -1); + + long minConsumingFreshnessMs = + getLongValue(responseMetadata, MetadataKey.MIN_CONSUMING_FRESHNESS_TIME_MS.getName(), -1); + if (minConsumingFreshnessMs > 0 && minConsumingFreshnessMs != Long.MAX_VALUE) { + _serverMetrics.addTimedTableValue(tableNameWithType, ServerTimer.FRESHNESS_LAG_MS, + (System.currentTimeMillis() - minConsumingFreshnessMs), TimeUnit.MILLISECONDS); + } + + long numResizes = getLongValue(responseMetadata, MetadataKey.NUM_RESIZES.getName(), -1); + addToTableMeter(tableNameWithType, ServerMeter.NUM_RESIZES, numResizes); + + long resizeTimeMs = getLongValue(responseMetadata, MetadataKey.RESIZE_TIME_MS.getName(), -1); + addToTableMeter(tableNameWithType, ServerMeter.RESIZE_TIME_MS, resizeTimeMs); + + long threadCpuTimeNs = getLongValue(responseMetadata, MetadataKey.THREAD_CPU_TIME_NS.getName(), 0); + if (threadCpuTimeNs > 0) { + _serverMetrics.addTimedTableValue(tableNameWithType, ServerTimer.EXECUTION_THREAD_CPU_TIME_NS, threadCpuTimeNs, + TimeUnit.NANOSECONDS); + _serverMetrics.addMeteredTableValue(tableNameWithType, ServerMeter.TOTAL_THREAD_CPU_TIME_MILLIS, + TimeUnit.MILLISECONDS.convert(threadCpuTimeNs, TimeUnit.NANOSECONDS)); + } + + long systemActivitiesCpuTimeNs = + getLongValue(responseMetadata, MetadataKey.SYSTEM_ACTIVITIES_CPU_TIME_NS.getName(), 0); + if (systemActivitiesCpuTimeNs > 0) { + _serverMetrics.addTimedTableValue(tableNameWithType, ServerTimer.SYSTEM_ACTIVITIES_CPU_TIME_NS, + systemActivitiesCpuTimeNs, TimeUnit.NANOSECONDS); + } + + long responseSerializationCpuTimeNs = + getLongValue(responseMetadata, MetadataKey.RESPONSE_SER_CPU_TIME_NS.getName(), 0); + if (responseSerializationCpuTimeNs > 0) { + _serverMetrics.addTimedTableValue(tableNameWithType, ServerTimer.RESPONSE_SER_CPU_TIME_NS, + responseSerializationCpuTimeNs, TimeUnit.NANOSECONDS); + } + + long totalCpuTimeNs = threadCpuTimeNs + systemActivitiesCpuTimeNs + responseSerializationCpuTimeNs; + if (totalCpuTimeNs > 0) { + _serverMetrics.addTimedTableValue(tableNameWithType, ServerTimer.TOTAL_CPU_TIME_NS, totalCpuTimeNs, + TimeUnit.NANOSECONDS); + } + + TimerContext timerContext = request.getTimerContext(); + long schedulerWaitMs = timerContext.getPhaseDurationMs(ServerQueryPhase.SCHEDULER_WAIT); + + // Please keep the format as name=value comma-separated with no spaces + // Please add new entries at the end + if (_queryLogRateLimiter.tryAcquire() || forceLog(schedulerWaitMs, numDocsScanned, numSegmentsPrunedInvalid)) { + LOGGER.info("Processed requestId={},table={}," + + "segments(queried/processed/matched/consumingQueried/consumingProcessed/consumingMatched/" + + "invalid/limit/value)={}/{}/{}/{}/{}/{}/{}/{}/{}," + + "schedulerWaitMs={},reqDeserMs={},totalExecMs={},resSerMs={},totalTimeMs={}," + + "minConsumingFreshnessMs={},broker={},numDocsScanned={},scanInFilter={},scanPostFilter={},sched={}," + + "threadCpuTimeNs(total/thread/sysActivity/resSer)={}/{}/{}/{}", request.getRequestId(), + tableNameWithType, + numSegmentsQueried, numSegmentsProcessed, numSegmentsMatched, numConsumingSegmentsQueried, + numConsumingSegmentsProcessed, numConsumingSegmentsMatched, numSegmentsPrunedInvalid, + numSegmentsPrunedByLimit, numSegmentsPrunedByValue, schedulerWaitMs, + timerContext.getPhaseDurationMs(ServerQueryPhase.REQUEST_DESERIALIZATION), + timerContext.getPhaseDurationMs(ServerQueryPhase.QUERY_PROCESSING), + timerContext.getPhaseDurationMs(ServerQueryPhase.RESPONSE_SERIALIZATION), + timerContext.getPhaseDurationMs(ServerQueryPhase.TOTAL_QUERY_TIME), minConsumingFreshnessMs, + request.getBrokerId(), numDocsScanned, numEntriesScannedInFilter, numEntriesScannedPostFilter, schedulerType, + totalCpuTimeNs, threadCpuTimeNs, systemActivitiesCpuTimeNs, responseSerializationCpuTimeNs); + + // Limit the dropping log message at most once per second. + if (_droppedReportRateLimiter.tryAcquire()) { + // NOTE: the reported number may not be accurate since we will be missing some increments happened between + // get() and set(). + int numDroppedLogs = _numDroppedLogs.get(); + if (numDroppedLogs > 0) { + LOGGER.info("{} logs were dropped. (log max rate per second: {})", numDroppedLogs, + _queryLogRateLimiter.getRate()); + _numDroppedLogs.set(0); + } + } + } else { + _numDroppedLogs.getAndIncrement(); + } + } + + private static long getLongValue(Map metadata, String key, long defaultValue) { + String value = metadata.get(key); + return value != null ? Long.parseLong(value) : defaultValue; + } + + private void addToTableMeter(String tableNameWithType, ServerMeter meter, long value) { + if (value > 0) { + _serverMetrics.addMeteredTableValue(tableNameWithType, meter, value); + } + } + + /** + * Returns {@code true} when the query should be logged even if the query log rate is reached. + * + * TODO: come up with other criteria for forcing a log and come up with better numbers. + */ + private static boolean forceLog(long schedulerWaitMs, long numDocsScanned, long numSegmentsPrunedInvalid) { + // If scheduler wait time is larger than 100ms, force the log. + if (schedulerWaitMs > 100) { + return true; + } + + // If the number of document scanned is larger than 1 million rows, force the log. + if (numDocsScanned > 1_000_000) { + return true; + } + + // If there are invalid segments, force the log. + if (numSegmentsPrunedInvalid > 0) { + return true; + } + + return false; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/QueryOptimizer.java b/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/QueryOptimizer.java index d0b0c549c34a..dbd9b5c70c6f 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/QueryOptimizer.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/QueryOptimizer.java @@ -26,9 +26,11 @@ import org.apache.pinot.common.request.PinotQuery; import org.apache.pinot.core.query.optimizer.filter.FilterOptimizer; import org.apache.pinot.core.query.optimizer.filter.FlattenAndOrFilterOptimizer; +import org.apache.pinot.core.query.optimizer.filter.IdenticalPredicateFilterOptimizer; import org.apache.pinot.core.query.optimizer.filter.MergeEqInFilterOptimizer; import org.apache.pinot.core.query.optimizer.filter.MergeRangeFilterOptimizer; import org.apache.pinot.core.query.optimizer.filter.NumericalFilterOptimizer; +import org.apache.pinot.core.query.optimizer.filter.TextMatchFilterOptimizer; import org.apache.pinot.core.query.optimizer.filter.TimePredicateFilterOptimizer; import org.apache.pinot.core.query.optimizer.statement.StatementOptimizer; import org.apache.pinot.core.query.optimizer.statement.StringPredicateFilterOptimizer; @@ -38,13 +40,15 @@ public class QueryOptimizer { // DO NOT change the order of these optimizers. - // - MergeEqInFilterOptimizer and MergeRangeFilterOptimizer relies on FlattenAndOrFilterOptimizer to flatten the - // AND/OR predicate so that the children are on the same level to be merged + // - MergeEqInFilterOptimizer, MergeRangeFilterOptimizer, and TextMatchFilterOptimizer each rely on + // FlattenAndOrFilterOptimizer to flatten the AND/OR predicate so that the children are on the same level to + // be merged // - TimePredicateFilterOptimizer and MergeRangeFilterOptimizer relies on NumericalFilterOptimizer to convert the // values to the proper format so that they can be properly parsed private static final List FILTER_OPTIMIZERS = - Arrays.asList(new FlattenAndOrFilterOptimizer(), new MergeEqInFilterOptimizer(), new NumericalFilterOptimizer(), - new TimePredicateFilterOptimizer(), new MergeRangeFilterOptimizer()); + Arrays.asList(new FlattenAndOrFilterOptimizer(), new IdenticalPredicateFilterOptimizer(), + new MergeEqInFilterOptimizer(), new NumericalFilterOptimizer(), new TimePredicateFilterOptimizer(), + new MergeRangeFilterOptimizer(), new TextMatchFilterOptimizer()); private static final List STATEMENT_OPTIMIZERS = Collections.singletonList(new StringPredicateFilterOptimizer()); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/BaseAndOrBooleanFilterOptimizer.java b/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/BaseAndOrBooleanFilterOptimizer.java new file mode 100644 index 000000000000..5ef10d09721e --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/BaseAndOrBooleanFilterOptimizer.java @@ -0,0 +1,129 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.optimizer.filter; + +import java.util.List; +import javax.annotation.Nullable; +import org.apache.pinot.common.request.Expression; +import org.apache.pinot.common.request.Function; +import org.apache.pinot.common.utils.request.RequestUtils; +import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.sql.FilterKind; + + +/** + * This base class acts as a helper for any optimizer that is effectively removing filter conditions. + * It provides TRUE/FALSE literal classes that can be used to replace filter expressions that are always true/false. + * It provides an optimization implementation for AND/OR/NOT expressions. + */ +public abstract class BaseAndOrBooleanFilterOptimizer implements FilterOptimizer { + + protected static final Expression TRUE = RequestUtils.getLiteralExpression(true); + protected static final Expression FALSE = RequestUtils.getLiteralExpression(false); + + /** + * This recursively optimizes each part of the filter expression. For any AND/OR/NOT, + * we optimize each child, then we optimize the remaining statement. If there is only + * a child statement, we optimize that. + */ + @Override + public Expression optimize(Expression filterExpression, @Nullable Schema schema) { + if (!canBeOptimized(filterExpression, schema)) { + return filterExpression; + } + + Function function = filterExpression.getFunctionCall(); + List operands = function.getOperands(); + FilterKind kind = FilterKind.valueOf(function.getOperator()); + switch (kind) { + case AND: + case OR: + case NOT: + // Recursively traverse the expression tree to find an operator node that can be rewritten. + operands.replaceAll(operand -> optimize(operand, schema)); + + // We have rewritten the child operands, so rewrite the parent if needed. + return optimizeCurrent(filterExpression); + default: + return optimizeChild(filterExpression, schema); + } + } + + abstract boolean canBeOptimized(Expression filterExpression, @Nullable Schema schema); + + /** + * Optimize any cases that are not AND/OR/NOT. This should be done by converting any cases + * that are always true to TRUE or always false to FALSE. + */ + abstract Expression optimizeChild(Expression filterExpression, @Nullable Schema schema); + + /** + * If any of the operands of AND function is "false", then the AND function itself is false and can be replaced with + * "false" literal. Otherwise, remove all the "true" operands of the AND function. Similarly, if any of the operands + * of OR function is "true", then the OR function itself is true and can be replaced with "true" literal. Otherwise, + * remove all the "false" operands of the OR function. + */ + protected Expression optimizeCurrent(Expression expression) { + Function function = expression.getFunctionCall(); + String operator = function.getOperator(); + List operands = function.getOperands(); + if (operator.equals(FilterKind.AND.name())) { + // If any of the literal operands are always false, then replace AND function with FALSE. + for (Expression operand : operands) { + if (operand.equals(FALSE)) { + return FALSE; + } + } + + // Remove all Literal operands that are always true. + operands.removeIf(operand -> operand.equals(TRUE)); + if (operands.isEmpty()) { + return TRUE; + } + } else if (operator.equals(FilterKind.OR.name())) { + // If any of the literal operands are always true, then replace OR function with TRUE + for (Expression operand : operands) { + if (operand.equals(TRUE)) { + return TRUE; + } + } + + // Remove all Literal operands that are always false. + operands.removeIf(operand -> operand.equals(FALSE)); + if (operands.isEmpty()) { + return FALSE; + } + } else if (operator.equals(FilterKind.NOT.name())) { + assert operands.size() == 1; + Expression operand = operands.get(0); + if (operand.equals(TRUE)) { + return FALSE; + } + if (operand.equals(FALSE)) { + return TRUE; + } + } + return expression; + } + + /** Change the expression value to boolean literal with given value. */ + protected static Expression getExpressionFromBoolean(boolean value) { + return value ? TRUE : FALSE; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/FlattenAndOrFilterOptimizer.java b/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/FlattenAndOrFilterOptimizer.java index 1b04f472de99..a1de7bd7c6e8 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/FlattenAndOrFilterOptimizer.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/FlattenAndOrFilterOptimizer.java @@ -40,6 +40,9 @@ public Expression optimize(Expression filterExpression, @Nullable Schema schema) private Expression optimize(Expression filterExpression) { Function function = filterExpression.getFunctionCall(); + if (function == null) { + return filterExpression; + } String operator = function.getOperator(); if (!operator.equals(FilterKind.AND.name()) && !operator.equals(FilterKind.OR.name())) { return filterExpression; @@ -50,7 +53,7 @@ private Expression optimize(Expression filterExpression) { for (Expression child : children) { Expression optimizedChild = optimize(child); Function childFunction = optimizedChild.getFunctionCall(); - if (childFunction.getOperator().equals(operator)) { + if (childFunction != null && childFunction.getOperator().equals(operator)) { newChildren.addAll(childFunction.getOperands()); } else { newChildren.add(optimizedChild); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/IdenticalPredicateFilterOptimizer.java b/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/IdenticalPredicateFilterOptimizer.java new file mode 100644 index 000000000000..ba6401799bab --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/IdenticalPredicateFilterOptimizer.java @@ -0,0 +1,104 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.optimizer.filter; + +import java.util.List; +import javax.annotation.Nullable; +import org.apache.pinot.common.request.Expression; +import org.apache.pinot.common.request.Function; +import org.apache.pinot.common.request.Literal; +import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.sql.FilterKind; + + +/** + * This optimizer converts all predicates where the left hand side == right hand side to + * a simple TRUE/FALSE literal value. While filters like, WHERE 1=1 OR "col1"="col1" are not + * typical, they end up expensive in Pinot because they are rewritten as A-A==0. + */ +public class IdenticalPredicateFilterOptimizer extends BaseAndOrBooleanFilterOptimizer { + + @Override + boolean canBeOptimized(Expression filterExpression, @Nullable Schema schema) { + // if there's no function call, there's no lhs or rhs + return filterExpression.getFunctionCall() != null; + } + + @Override + Expression optimizeChild(Expression filterExpression, @Nullable Schema schema) { + Function function = filterExpression.getFunctionCall(); + FilterKind kind = FilterKind.valueOf(function.getOperator()); + switch (kind) { + case EQUALS: + if (hasIdenticalLhsAndRhs(function.getOperands())) { + return TRUE; + } + break; + case NOT_EQUALS: + if (hasIdenticalLhsAndRhs(function.getOperands())) { + return FALSE; + } + break; + default: + break; + } + return filterExpression; + } + + /** + * Pinot queries of the WHERE 1 != 1 AND "col1" = "col2" variety are rewritten as + * 1-1 != 0 AND "col1"-"col2" = 0. Therefore, we check specifically for the case where + * the operand is set up in this fashion. + * + * We return false specifically after every check to ensure we're only continuing when + * the input looks as expected. Otherwise, it's easy to for one of the operand functions + * to return null and fail the query. + * + * TODO: The rewrite is already happening in PredicateComparisonRewriter.updateFunctionExpression(), + * so we might just compare the lhs and rhs there. + */ + private boolean hasIdenticalLhsAndRhs(List operands) { + boolean hasTwoChildren = operands.size() == 2; + Expression firstChild = operands.get(0); + if (firstChild.getFunctionCall() == null || !hasTwoChildren) { + return false; + } + boolean firstChildIsMinusOperator = firstChild.getFunctionCall().getOperator().equals("minus"); + if (!firstChildIsMinusOperator) { + return false; + } + boolean firstChildHasTwoOperands = firstChild.getFunctionCall().getOperandsSize() == 2; + if (!firstChildHasTwoOperands) { + return false; + } + Expression minusOperandFirstChild = firstChild.getFunctionCall().getOperands().get(0); + Expression minusOperandSecondChild = firstChild.getFunctionCall().getOperands().get(1); + if (minusOperandFirstChild == null || minusOperandSecondChild == null || !minusOperandFirstChild.equals( + minusOperandSecondChild)) { + return false; + } + Expression secondChild = operands.get(1); + return isLiteralZero(secondChild); + } + + private boolean isLiteralZero(Expression expression) { + Literal literal = expression.getLiteral(); + return literal != null && literal.isSetIntValue() && literal.getIntValue() == 0; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/MergeEqInFilterOptimizer.java b/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/MergeEqInFilterOptimizer.java index 21fbf324bdeb..6836f8022617 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/MergeEqInFilterOptimizer.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/MergeEqInFilterOptimizer.java @@ -19,7 +19,6 @@ package org.apache.pinot.core.query.optimizer.filter; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -56,6 +55,9 @@ public Expression optimize(Expression filterExpression, @Nullable Schema schema) private Expression optimize(Expression filterExpression) { Function function = filterExpression.getFunctionCall(); + if (function == null) { + return filterExpression; + } String operator = function.getOperator(); if (operator.equals(FilterKind.OR.name())) { List children = function.getOperands(); @@ -66,49 +68,53 @@ private Expression optimize(Expression filterExpression) { // Iterate over all the child filters to merge EQ and IN predicates for (Expression child : children) { Function childFunction = child.getFunctionCall(); - String childOperator = childFunction.getOperator(); - assert !childOperator.equals(FilterKind.OR.name()); - if (childOperator.equals(FilterKind.AND.name()) || childOperator.equals(FilterKind.NOT.name())) { - childFunction.getOperands().replaceAll(this::optimize); + if (childFunction == null) { newChildren.add(child); - } else if (childOperator.equals(FilterKind.EQUALS.name())) { - List operands = childFunction.getOperands(); - Expression lhs = operands.get(0); - Expression value = operands.get(1); - Set values = valuesMap.get(lhs); - if (values == null) { - values = new HashSet<>(); - values.add(value); - valuesMap.put(lhs, values); - } else { - values.add(value); - // Recreate filter when multiple predicates can be merged - recreateFilter = true; - } - } else if (childOperator.equals(FilterKind.IN.name())) { - List operands = childFunction.getOperands(); - Expression lhs = operands.get(0); - Set inPredicateValuesSet = new HashSet<>(); - int numOperands = operands.size(); - for (int i = 1; i < numOperands; i++) { - inPredicateValuesSet.add(operands.get(i)); - } - int numUniqueValues = inPredicateValuesSet.size(); - if (numUniqueValues == 1 || numUniqueValues != numOperands - 1) { - // Recreate filter when the IN predicate contains only 1 value (can be rewritten to EQ predicate), or values - // can be de-duplicated - recreateFilter = true; - } - Set values = valuesMap.get(lhs); - if (values == null) { - valuesMap.put(lhs, inPredicateValuesSet); + } else { + String childOperator = childFunction.getOperator(); + assert !childOperator.equals(FilterKind.OR.name()); + if (childOperator.equals(FilterKind.AND.name()) || childOperator.equals(FilterKind.NOT.name())) { + childFunction.getOperands().replaceAll(this::optimize); + newChildren.add(child); + } else if (childOperator.equals(FilterKind.EQUALS.name())) { + List operands = childFunction.getOperands(); + Expression lhs = operands.get(0); + Expression value = operands.get(1); + Set values = valuesMap.get(lhs); + if (values == null) { + values = new HashSet<>(); + values.add(value); + valuesMap.put(lhs, values); + } else { + values.add(value); + // Recreate filter when multiple predicates can be merged + recreateFilter = true; + } + } else if (childOperator.equals(FilterKind.IN.name())) { + List operands = childFunction.getOperands(); + Expression lhs = operands.get(0); + Set inPredicateValuesSet = new HashSet<>(); + int numOperands = operands.size(); + for (int i = 1; i < numOperands; i++) { + inPredicateValuesSet.add(operands.get(i)); + } + int numUniqueValues = inPredicateValuesSet.size(); + if (numUniqueValues == 1 || numUniqueValues != numOperands - 1) { + // Recreate filter when the IN predicate contains only 1 value (can be rewritten to EQ predicate), + // or values can be de-duplicated + recreateFilter = true; + } + Set values = valuesMap.get(lhs); + if (values == null) { + valuesMap.put(lhs, inPredicateValuesSet); + } else { + values.addAll(inPredicateValuesSet); + // Recreate filter when multiple predicates can be merged + recreateFilter = true; + } } else { - values.addAll(inPredicateValuesSet); - // Recreate filter when multiple predicates can be merged - recreateFilter = true; + newChildren.add(child); } - } else { - newChildren.add(child); } } @@ -157,16 +163,12 @@ private Expression optimize(Expression filterExpression) { private static Expression getFilterExpression(Expression lhs, Set values) { int numValues = values.size(); if (numValues == 1) { - Expression eqFilter = RequestUtils.getFunctionExpression(FilterKind.EQUALS.name()); - eqFilter.getFunctionCall().setOperands(Arrays.asList(lhs, values.iterator().next())); - return eqFilter; + return RequestUtils.getFunctionExpression(FilterKind.EQUALS.name(), lhs, values.iterator().next()); } else { - Expression inFilter = RequestUtils.getFunctionExpression(FilterKind.IN.name()); List operands = new ArrayList<>(numValues + 1); operands.add(lhs); operands.addAll(values); - inFilter.getFunctionCall().setOperands(operands); - return inFilter; + return RequestUtils.getFunctionExpression(FilterKind.IN.name(), operands); } } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/MergeRangeFilterOptimizer.java b/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/MergeRangeFilterOptimizer.java index 1d0b91cc8809..29eff505619e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/MergeRangeFilterOptimizer.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/MergeRangeFilterOptimizer.java @@ -19,7 +19,6 @@ package org.apache.pinot.core.query.optimizer.filter; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -147,16 +146,14 @@ private static Range getRange(FilterKind filterKind, List operands, */ @SuppressWarnings("rawtypes") private static Comparable getComparable(Expression literalExpression, DataType dataType) { - return dataType.convertInternal(literalExpression.getLiteral().getFieldValue().toString()); + return dataType.convertInternal(RequestUtils.getLiteralString(literalExpression)); } /** * Helper method to construct a RANGE predicate filter Expression from the given column and range. */ private static Expression getRangeFilterExpression(String column, Range range) { - Expression rangeFilter = RequestUtils.getFunctionExpression(FilterKind.RANGE.name()); - rangeFilter.getFunctionCall().setOperands(Arrays.asList(RequestUtils.getIdentifierExpression(column), - RequestUtils.getLiteralExpression(range.getRangeString()))); - return rangeFilter; + return RequestUtils.getFunctionExpression(FilterKind.RANGE.name(), RequestUtils.getIdentifierExpression(column), + RequestUtils.getLiteralExpression(range.getRangeString())); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/NumericalFilterOptimizer.java b/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/NumericalFilterOptimizer.java index a50484889fa0..b0bcf424717f 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/NumericalFilterOptimizer.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/NumericalFilterOptimizer.java @@ -25,13 +25,12 @@ import org.apache.pinot.common.request.ExpressionType; import org.apache.pinot.common.request.Function; import org.apache.pinot.common.request.Literal; -import org.apache.pinot.common.utils.request.RequestUtils; import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.data.Schema; import org.apache.pinot.sql.FilterKind; - /** * Numerical expressions of form "column literal", where operator can be '=', '!=', '>', '>=', '<', or '<=', * can compare a column of one datatype (say INT) with a literal of different datatype (say DOUBLE). These expressions @@ -62,50 +61,41 @@ * * TODO: Add support for BETWEEN, IN, and NOT IN operators. */ -public class NumericalFilterOptimizer implements FilterOptimizer { - private static final Expression TRUE = RequestUtils.getLiteralExpression(true); - private static final Expression FALSE = RequestUtils.getLiteralExpression(false); +public class NumericalFilterOptimizer extends BaseAndOrBooleanFilterOptimizer { @Override - public Expression optimize(Expression expression, @Nullable Schema schema) { - ExpressionType type = expression.getType(); - if (type != ExpressionType.FUNCTION || schema == null) { - // We have nothing to rewrite if expression is not a function or schema is null - return expression; - } + boolean canBeOptimized(Expression filterExpression, @Nullable Schema schema) { + ExpressionType type = filterExpression.getType(); + // We have nothing to rewrite if expression is not a function or schema is null + return type == ExpressionType.FUNCTION && schema != null; + } - Function function = expression.getFunctionCall(); - List operands = function.getOperands(); + @Override + Expression optimizeChild(Expression filterExpression, @Nullable Schema schema) { + Function function = filterExpression.getFunctionCall(); FilterKind kind = FilterKind.valueOf(function.getOperator()); switch (kind) { - case AND: - case OR: - case NOT: - // Recursively traverse the expression tree to find an operator node that can be rewritten. - operands.forEach(operand -> optimize(operand, schema)); - - // We have rewritten the child operands, so rewrite the parent if needed. - return optimizeCurrent(expression); case IS_NULL: case IS_NOT_NULL: // No need to try to optimize IS_NULL and IS_NOT_NULL operations on numerical columns. break; default: + List operands = function.getOperands(); // Verify that LHS is a numeric column and RHS is a numeric literal before rewriting. Expression lhs = operands.get(0); Expression rhs = operands.get(1); if (isNumericLiteral(rhs)) { - FieldSpec.DataType dataType = getDataType(lhs, schema); + DataType dataType = getDataType(lhs, schema); if (dataType != null && dataType.isNumeric()) { switch (kind) { case EQUALS: case NOT_EQUALS: - return rewriteEqualsExpression(expression, kind, dataType, rhs); + return rewriteEqualsExpression(filterExpression, kind, dataType, rhs); case GREATER_THAN: case GREATER_THAN_OR_EQUAL: case LESS_THAN: case LESS_THAN_OR_EQUAL: - return rewriteRangeExpression(expression, kind, lhs, rhs, schema); + return rewriteRangeExpression(filterExpression, kind, dataType, rhs); default: break; } @@ -113,72 +103,21 @@ public Expression optimize(Expression expression, @Nullable Schema schema) { } break; } - return expression; - } - - /** - * If any of the operands of AND function is "false", then the AND function itself is false and can be replaced with - * "false" literal. Otherwise, remove all the "true" operands of the AND function. Similarly, if any of the operands - * of OR function is "true", then the OR function itself is true and can be replaced with "true" literal. Otherwise, - * remove all the "false" operands of the OR function. - */ - private static Expression optimizeCurrent(Expression expression) { - Function function = expression.getFunctionCall(); - String operator = function.getOperator(); - List operands = function.getOperands(); - if (operator.equals(FilterKind.AND.name())) { - // If any of the literal operands are FALSE, then replace AND function with FALSE. - for (Expression operand : operands) { - if (operand.equals(FALSE)) { - return FALSE; - } - } - - // Remove all Literal operands that are TRUE. - operands.removeIf(x -> x.equals(TRUE)); - if (operands.isEmpty()) { - return TRUE; - } - } else if (operator.equals(FilterKind.OR.name())) { - // If any of the literal operands are TRUE, then replace OR function with TRUE - for (Expression operand : operands) { - if (operand.equals(TRUE)) { - return TRUE; - } - } - - // Remove all Literal operands that are FALSE. - operands.removeIf(x -> x.equals(FALSE)); - if (operands.isEmpty()) { - return FALSE; - } - } else if (operator.equals(FilterKind.NOT.name())) { - assert operands.size() == 1; - Expression operand = operands.get(0); - if (operand.equals(TRUE)) { - return FALSE; - } - if (operand.equals(FALSE)) { - return TRUE; - } - } - return expression; + return filterExpression; } /** * Rewrite expressions of form "column = literal" or "column != literal" to ensure that RHS literal is the same * datatype as LHS column. */ - private static Expression rewriteEqualsExpression(Expression equals, FilterKind kind, FieldSpec.DataType dataType, + private static Expression rewriteEqualsExpression(Expression equals, FilterKind kind, DataType dataType, Expression rhs) { // Get expression operator boolean result = kind == FilterKind.NOT_EQUALS; switch (rhs.getLiteral().getSetField()) { - case SHORT_VALUE: case INT_VALUE: - // No rewrites needed since SHORT and INT conversion to numeric column types (INT, LONG, FLOAT, and DOUBLE) is - // lossless and will be implicitly handled on the server side. + // No rewrites needed since INT conversion to numeric column types can be handled on the server side. break; case LONG_VALUE: { long actual = rhs.getLiteral().getLongValue(); @@ -187,7 +126,7 @@ private static Expression rewriteEqualsExpression(Expression equals, FilterKind int converted = (int) actual; if (converted != actual) { // Long value does not fall within the bounds of INT column. - setExpressionToBoolean(equals, result); + return getExpressionFromBoolean(result); } else { // Replace long value with converted int value. rhs.getLiteral().setLongValue(converted); @@ -198,7 +137,7 @@ private static Expression rewriteEqualsExpression(Expression equals, FilterKind float converted = (float) actual; if (BigDecimal.valueOf(actual).compareTo(BigDecimal.valueOf(converted)) != 0) { // Long to float conversion is lossy. - setExpressionToBoolean(equals, result); + return getExpressionFromBoolean(result); } else { // Replace long value with converted float value. rhs.getLiteral().setDoubleValue(converted); @@ -209,7 +148,7 @@ private static Expression rewriteEqualsExpression(Expression equals, FilterKind double converted = (double) actual; if (BigDecimal.valueOf(actual).compareTo(BigDecimal.valueOf(converted)) != 0) { // Long to double conversion is lossy. - setExpressionToBoolean(equals, result); + return getExpressionFromBoolean(result); } else { // Replace long value with converted double value. rhs.getLiteral().setDoubleValue(converted); @@ -221,6 +160,11 @@ private static Expression rewriteEqualsExpression(Expression equals, FilterKind } break; } + case FLOAT_VALUE: { + float actual = Float.intBitsToFloat(rhs.getLiteral().getFloatValue()); + System.out.println(actual); + break; + } case DOUBLE_VALUE: { double actual = rhs.getLiteral().getDoubleValue(); switch (dataType) { @@ -228,7 +172,7 @@ private static Expression rewriteEqualsExpression(Expression equals, FilterKind int converted = (int) actual; if (converted != actual) { // Double value does not fall within the bounds of INT column. - setExpressionToBoolean(equals, result); + return getExpressionFromBoolean(result); } else { // Replace double value with converted int value. rhs.getLiteral().setLongValue(converted); @@ -239,29 +183,18 @@ private static Expression rewriteEqualsExpression(Expression equals, FilterKind long converted = (long) actual; if (BigDecimal.valueOf(actual).compareTo(BigDecimal.valueOf(converted)) != 0) { // Double to long conversion is lossy. - setExpressionToBoolean(equals, result); + return getExpressionFromBoolean(result); } else { // Replace double value with converted long value. rhs.getLiteral().setLongValue(converted); } break; } - case FLOAT: { - float converted = (float) actual; - if (converted != actual) { - // Double to float conversion is lossy. - setExpressionToBoolean(equals, result); - } else { - // Replace double value with converted float value. - rhs.getLiteral().setDoubleValue(converted); - } - break; - } default: break; } + break; } - break; default: break; } @@ -272,16 +205,11 @@ private static Expression rewriteEqualsExpression(Expression equals, FilterKind * Rewrite expressions of form "column > literal", "column >= literal", "column < literal", and "column <= literal" * to ensure that RHS literal is the same datatype as LHS column. */ - private static Expression rewriteRangeExpression(Expression range, FilterKind kind, Expression lhs, Expression rhs, - Schema schema) { - // Get column data type. - FieldSpec.DataType dataType = schema.getFieldSpecFor(lhs.getIdentifier().getName()).getDataType(); - + private static Expression rewriteRangeExpression(Expression range, FilterKind kind, DataType dataType, + Expression rhs) { switch (rhs.getLiteral().getSetField()) { - case SHORT_VALUE: case INT_VALUE: - // No rewrites needed since SHORT and INT conversion to numeric column types (INT, LONG, FLOAT, and DOUBLE) is - // lossless and will be implicitly handled on the server side. + // No rewrites needed since INT conversion to numeric column types can be handled on the server side. break; case LONG_VALUE: { long actual = rhs.getLiteral().getLongValue(); @@ -294,12 +222,12 @@ private static Expression rewriteRangeExpression(Expression range, FilterKind ki // INT column can never have a value greater than Integer.MAX_VALUE. < and <= expressions will always be // true, because an INT column will always have values greater than or equal to Integer.MIN_VALUE and less // than or equal to Integer.MAX_VALUE. - setExpressionToBoolean(range, kind == FilterKind.LESS_THAN || kind == FilterKind.LESS_THAN_OR_EQUAL); + return getExpressionFromBoolean(kind == FilterKind.LESS_THAN || kind == FilterKind.LESS_THAN_OR_EQUAL); } else if (comparison < 0) { // Literal value is less than the bounds of INT. > and >= expressions will always be true because an // INT column will always have a value greater than or equal to Integer.MIN_VALUE. < and <= expressions // will always be false, because an INT column will never have values less than Integer.MIN_VALUE. - setExpressionToBoolean(range, + return getExpressionFromBoolean( kind == FilterKind.GREATER_THAN || kind == FilterKind.GREATER_THAN_OR_EQUAL); } else { // Long literal value falls within the bounds of INT column, server will successfully convert the literal @@ -343,6 +271,11 @@ private static Expression rewriteRangeExpression(Expression range, FilterKind ki } break; } + case FLOAT_VALUE: { + float actual = Float.intBitsToFloat(rhs.getLiteral().getFloatValue()); + System.out.println(actual); + break; + } case DOUBLE_VALUE: { double actual = rhs.getLiteral().getDoubleValue(); switch (dataType) { @@ -351,10 +284,10 @@ private static Expression rewriteRangeExpression(Expression range, FilterKind ki int comparison = Double.compare(actual, converted); if (comparison > 0 && converted == Integer.MAX_VALUE) { // Literal value is greater than the bounds of INT. - setExpressionToBoolean(range, kind == FilterKind.LESS_THAN || kind == FilterKind.LESS_THAN_OR_EQUAL); + return getExpressionFromBoolean(kind == FilterKind.LESS_THAN || kind == FilterKind.LESS_THAN_OR_EQUAL); } else if (comparison < 0 && converted == Integer.MIN_VALUE) { // Literal value is less than the bounds of INT. - setExpressionToBoolean(range, + return getExpressionFromBoolean( kind == FilterKind.GREATER_THAN || kind == FilterKind.GREATER_THAN_OR_EQUAL); } else { // Literal value falls within the bounds of INT. @@ -371,10 +304,10 @@ private static Expression rewriteRangeExpression(Expression range, FilterKind ki if (comparison > 0 && converted == Long.MAX_VALUE) { // Literal value is greater than the bounds of LONG. - setExpressionToBoolean(range, kind == FilterKind.LESS_THAN || kind == FilterKind.LESS_THAN_OR_EQUAL); + return getExpressionFromBoolean(kind == FilterKind.LESS_THAN || kind == FilterKind.LESS_THAN_OR_EQUAL); } else if (comparison < 0 && converted == Long.MIN_VALUE) { // Literal value is less than the bounds of LONG. - setExpressionToBoolean(range, + return getExpressionFromBoolean( kind == FilterKind.GREATER_THAN || kind == FilterKind.GREATER_THAN_OR_EQUAL); } else { // Rewrite range operator @@ -389,26 +322,24 @@ private static Expression rewriteRangeExpression(Expression range, FilterKind ki float converted = (float) actual; if (converted == Float.POSITIVE_INFINITY) { // Literal value is greater than the bounds of FLOAT - setExpressionToBoolean(range, kind == FilterKind.LESS_THAN || kind == FilterKind.LESS_THAN_OR_EQUAL); + return getExpressionFromBoolean(kind == FilterKind.LESS_THAN || kind == FilterKind.LESS_THAN_OR_EQUAL); } else if (converted == Float.NEGATIVE_INFINITY) { // Literal value is less than the bounds of LONG. - setExpressionToBoolean(range, + return getExpressionFromBoolean( kind == FilterKind.GREATER_THAN || kind == FilterKind.GREATER_THAN_OR_EQUAL); - } else { - int comparison = Double.compare(actual, converted); - // Rewrite range operator - rewriteRangeOperator(range, kind, comparison); - - // Rewrite range literal - rhs.getLiteral().setDoubleValue(converted); } + // Do not rewrite range operator since double has higher precision than float + // If we do, we may introduce problems. + // For example, in the previous logic, "> 0.05" will be converted into ">= 0.05000000074505806". When the + // query reaches a server, the server will convert it to ">= 0.05" in + // ColumnValueSegmentPruner#pruneRangePredicate, which is incorrect. break; } default: break; } + break; } - break; default: break; } @@ -451,25 +382,32 @@ private static void rewriteRangeOperator(Expression range, FilterKind kind, int /** @return field data type extracted from the expression. null if we can't determine the type. */ @Nullable - private static FieldSpec.DataType getDataType(Expression expression, Schema schema) { + private static DataType getDataType(Expression expression, Schema schema) { if (expression.getType() == ExpressionType.IDENTIFIER) { String column = expression.getIdentifier().getName(); FieldSpec fieldSpec = schema.getFieldSpecFor(column); if (fieldSpec != null && fieldSpec.isSingleValueField()) { return fieldSpec.getDataType(); } - } else if (expression.getType() == ExpressionType.FUNCTION - && "cast".equalsIgnoreCase(expression.getFunctionCall().getOperator())) { + } else if (expression.getType() == ExpressionType.FUNCTION && "cast".equalsIgnoreCase( + expression.getFunctionCall().getOperator())) { // expression is not identifier but we can also determine the data type. - String targetTypeLiteral = expression.getFunctionCall().getOperands().get(1).getLiteral().getStringValue() - .toUpperCase(); - FieldSpec.DataType dataType; + String targetTypeLiteral = + expression.getFunctionCall().getOperands().get(1).getLiteral().getStringValue().toUpperCase(); + DataType dataType; + + // Strip out _ARRAY suffix that can be used to represent an MV field type since the semantics here will be the + // same as that for the equivalent SV field of the same type + if (targetTypeLiteral.endsWith("_ARRAY")) { + targetTypeLiteral = targetTypeLiteral.substring(0, targetTypeLiteral.length() - 6); + } + if ("INTEGER".equals(targetTypeLiteral)) { - dataType = FieldSpec.DataType.INT; + dataType = DataType.INT; } else if ("VARCHAR".equals(targetTypeLiteral)) { - dataType = FieldSpec.DataType.STRING; + dataType = DataType.STRING; } else { - dataType = FieldSpec.DataType.valueOf(targetTypeLiteral); + dataType = DataType.valueOf(targetTypeLiteral); } return dataType; } @@ -481,9 +419,9 @@ private static boolean isNumericLiteral(Expression expression) { if (expression.getType() == ExpressionType.LITERAL) { Literal._Fields type = expression.getLiteral().getSetField(); switch (type) { - case SHORT_VALUE: case INT_VALUE: case LONG_VALUE: + case FLOAT_VALUE: case DOUBLE_VALUE: return true; default: @@ -492,11 +430,4 @@ private static boolean isNumericLiteral(Expression expression) { } return false; } - - /** Change the expression value to boolean literal with given value. */ - private static void setExpressionToBoolean(Expression expression, boolean value) { - expression.unsetFunctionCall(); - expression.setType(ExpressionType.LITERAL); - expression.setLiteral(Literal.boolValue(value)); - } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/TextMatchFilterOptimizer.java b/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/TextMatchFilterOptimizer.java new file mode 100644 index 000000000000..c6b2e29838f6 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/TextMatchFilterOptimizer.java @@ -0,0 +1,178 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.optimizer.filter; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.annotation.Nullable; +import org.apache.pinot.common.request.Expression; +import org.apache.pinot.common.request.ExpressionType; +import org.apache.pinot.common.request.Function; +import org.apache.pinot.common.utils.request.RequestUtils; +import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.sql.FilterKind; + + +/** + * The {@code TextMatchFilterOptimizer} merges `TEXT_MATCH` predicates on the same column within an `OR` or `AND`, + * maximizing the amount of the query that can be pushed down to Lucene + * + * NOTE: This optimizer follows the {@link FlattenAndOrFilterOptimizer}, so all the AND/OR filters are already + * flattened. + */ +public class TextMatchFilterOptimizer implements FilterOptimizer { + private static final String SPACE = " "; + + @Override + public Expression optimize(Expression filterExpression, @Nullable Schema schema) { + return filterExpression.getType() == ExpressionType.FUNCTION ? optimize(filterExpression) : filterExpression; + } + + private Expression optimize(Expression filterExpression) { + Function function = filterExpression.getFunctionCall(); + if (function == null) { + return filterExpression; + } + + // no optimization can be performed unless the function is an OR, AND, or NOT + String operator = function.getOperator(); + if (!operator.equals(FilterKind.OR.name()) && !operator.equals(FilterKind.AND.name()) && !operator.equals( + FilterKind.NOT.name())) { + return filterExpression; + } + + List children = function.getOperands(); + children.replaceAll(this::optimize); + + List newChildren = new ArrayList<>(); + Map> textMatchMap = new HashMap<>(); + boolean recreateFilter = false; + + // iterate over all child expressions to collect TEXT_MATCH filters for each identifier + for (Expression child : children) { + Function childFunction = child.getFunctionCall(); + if (childFunction == null) { + newChildren.add(child); + } else { + String childOperator = childFunction.getOperator(); + if (childOperator.equals(FilterKind.TEXT_MATCH.name())) { + List operands = childFunction.getOperands(); + Expression identifier = operands.get(0); + textMatchMap.computeIfAbsent(identifier, k -> new ArrayList<>()).add(child); + } else if (childOperator.equals(FilterKind.NOT.name())) { + assert childFunction.getOperands().size() == 1; + Expression operand = childFunction.getOperands().get(0); + Function notChildFunction = operand.getFunctionCall(); + if (notChildFunction == null) { + newChildren.add(child); + continue; + } + if (notChildFunction.getOperator().equals(FilterKind.TEXT_MATCH.name())) { + List operands = notChildFunction.getOperands(); + Expression identifier = operands.get(0); + textMatchMap.computeIfAbsent(identifier, k -> new ArrayList<>()).add(child); + continue; + } + newChildren.add(child); + } else { + Expression newChild = optimize(child); + if (!newChild.equals(child)) { + recreateFilter = true; + } + newChildren.add(optimize(child)); + } + } + } + + for (List values : textMatchMap.values()) { + if (values.size() > 1) { + recreateFilter = true; + break; + } + } + if (recreateFilter) { + return getNewFilter(operator, newChildren, textMatchMap); + } + return filterExpression; + } + + private Expression getNewFilter(String operator, List newChildren, + Map> textMatchMap) { + // for each key in textMatchMap, build a TEXT_MATCH expression (merge list of filters) + for (Map.Entry> entry : textMatchMap.entrySet()) { + // special case: if all expressions are NOT, then wrap the merged text match inside a NOT. otherwise, push the + // NOT down into the text match expression + boolean allNot = true; + for (Expression expression : entry.getValue()) { + if (!expression.getFunctionCall().getOperator().equals(FilterKind.NOT.name())) { + allNot = false; + break; + } + } + + List literals = new ArrayList<>(); + if (allNot) { + for (Expression expression : entry.getValue()) { + Expression operand = expression.getFunctionCall().getOperands().get(0); + literals.add(operand.getFunctionCall().getOperands().get(1).getLiteral().getStringValue()); + } + } else { + for (Expression expression : entry.getValue()) { + if (expression.getFunctionCall().getOperator().equals(FilterKind.NOT.name())) { + Expression operand = expression.getFunctionCall().getOperands().get(0); + literals.add(FilterKind.NOT.name() + SPACE + operand.getFunctionCall().getOperands().get(1).getLiteral() + .getStringValue()); + continue; + } + assert expression.getFunctionCall().getOperator().equals(FilterKind.TEXT_MATCH.name()); + literals.add(expression.getFunctionCall().getOperands().get(1).getLiteral().getStringValue()); + } + } + + // build the merged TEXT_MATCH expression + String mergedTextMatchFilter; + if (allNot) { + assert operator.equals(FilterKind.AND.name()) || operator.equals(FilterKind.OR.name()); + if (operator.equals(FilterKind.AND.name())) { + mergedTextMatchFilter = String.join(SPACE + FilterKind.OR.name() + SPACE, literals); + } else { + mergedTextMatchFilter = String.join(SPACE + FilterKind.AND.name() + SPACE, literals); + } + } else { + mergedTextMatchFilter = String.join(SPACE + operator + SPACE, literals); + } + Expression mergedTextMatchExpression = + RequestUtils.getFunctionExpression(FilterKind.TEXT_MATCH.name(), entry.getKey(), + RequestUtils.getLiteralExpression("(" + mergedTextMatchFilter + ")")); + if (allNot) { + newChildren.add(RequestUtils.getFunctionExpression(FilterKind.NOT.name(), mergedTextMatchExpression)); + } else { + newChildren.add(mergedTextMatchExpression); + } + } + + if (newChildren.size() == 1) { + return newChildren.get(0); + } + assert operator.equals(FilterKind.OR.name()) || operator.equals(FilterKind.AND.name()); + return RequestUtils.getFunctionExpression(operator, newChildren); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/TimePredicateFilterOptimizer.java b/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/TimePredicateFilterOptimizer.java index 7a2603ce9089..792b6ec4d17d 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/TimePredicateFilterOptimizer.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/optimizer/filter/TimePredicateFilterOptimizer.java @@ -20,7 +20,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; -import java.util.Arrays; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; @@ -28,6 +28,7 @@ import org.apache.pinot.common.request.Expression; import org.apache.pinot.common.request.ExpressionType; import org.apache.pinot.common.request.Function; +import org.apache.pinot.common.request.Literal; import org.apache.pinot.common.utils.request.RequestUtils; import org.apache.pinot.core.operator.transform.function.DateTimeConversionTransformFunction; import org.apache.pinot.core.operator.transform.function.TimeConversionTransformFunction; @@ -128,7 +129,7 @@ private void optimizeTimeConvert(Function filterFunction, FilterKind filterKind) // -> millis >= 1000 // // Note that 'millisToSeconds(millis) > 0' is not equivalent to 'millis > 0' - long lowerValue = Long.parseLong(filterOperands.get(1).getLiteral().getFieldValue().toString()); + long lowerValue = getLongValue(filterOperands.get(1)); lowerMillis = outputTimeUnit.toMillis(lowerValue + 1); Preconditions.checkState(TimeUtils.timeValueInValidRange(lowerMillis), "Invalid lower bound in millis: %s", lowerMillis); @@ -137,7 +138,7 @@ private void optimizeTimeConvert(Function filterFunction, FilterKind filterKind) case GREATER_THAN_OR_EQUAL: { // millisToFormat(millis) >= n // -> millis >= formatToMillis(n) - long lowerValue = Long.parseLong(filterOperands.get(1).getLiteral().getFieldValue().toString()); + long lowerValue = getLongValue(filterOperands.get(1)); lowerMillis = outputTimeUnit.toMillis(lowerValue); Preconditions.checkState(TimeUtils.timeValueInValidRange(lowerMillis), "Invalid lower bound in millis: %s", lowerMillis); @@ -146,7 +147,7 @@ private void optimizeTimeConvert(Function filterFunction, FilterKind filterKind) case LESS_THAN: { // millisToFormat(millis) < n // -> millis < formatToMillis(n) - long upperValue = Long.parseLong(filterOperands.get(1).getLiteral().getFieldValue().toString()); + long upperValue = getLongValue(filterOperands.get(1)); upperMillis = outputTimeUnit.toMillis(upperValue); Preconditions.checkState(TimeUtils.timeValueInValidRange(upperMillis), "Invalid upper bound in millis: %s", upperMillis); @@ -163,7 +164,7 @@ private void optimizeTimeConvert(Function filterFunction, FilterKind filterKind) // -> millis < 1000 // // Note that 'millisToSeconds(millis) <= 0' is not equivalent to 'millis <= 0' - long upperValue = Long.parseLong(filterOperands.get(1).getLiteral().getFieldValue().toString()); + long upperValue = getLongValue(filterOperands.get(1)); upperMillis = outputTimeUnit.toMillis(upperValue + 1); Preconditions.checkState(TimeUtils.timeValueInValidRange(upperMillis), "Invalid upper bound in millis: %s", upperMillis); @@ -171,11 +172,11 @@ private void optimizeTimeConvert(Function filterFunction, FilterKind filterKind) } case BETWEEN: { // Combine GREATER_THAN_OR_EQUAL and LESS_THAN_OR_EQUAL - long lowerValue = Long.parseLong(filterOperands.get(1).getLiteral().getFieldValue().toString()); + long lowerValue = getLongValue(filterOperands.get(1)); lowerMillis = outputTimeUnit.toMillis(lowerValue); Preconditions.checkState(TimeUtils.timeValueInValidRange(lowerMillis), "Invalid lower bound in millis: %s", lowerMillis); - long upperValue = Long.parseLong(filterOperands.get(2).getLiteral().getFieldValue().toString()); + long upperValue = getLongValue(filterOperands.get(2)); upperMillis = outputTimeUnit.toMillis(upperValue + 1); Preconditions.checkState(TimeUtils.timeValueInValidRange(upperMillis), "Invalid upper bound in millis: %s", upperMillis); @@ -183,7 +184,7 @@ private void optimizeTimeConvert(Function filterFunction, FilterKind filterKind) } case EQUALS: { // Combine GREATER_THAN_OR_EQUAL and LESS_THAN_OR_EQUAL - long value = Long.parseLong(filterOperands.get(1).getLiteral().getFieldValue().toString()); + long value = getLongValue(filterOperands.get(1)); lowerMillis = outputTimeUnit.toMillis(value); Preconditions.checkState(TimeUtils.timeValueInValidRange(lowerMillis), "Invalid lower bound in millis: %s", lowerMillis); @@ -234,9 +235,7 @@ private void optimizeTimeConvert(Function filterFunction, FilterKind filterKind) // Step 3: Rewrite the filter function String rangeString = new Range(lowerValue, lowerInclusive, upperValue, upperInclusive).getRangeString(); - filterFunction.setOperator(FilterKind.RANGE.name()); - filterFunction.setOperands( - Arrays.asList(timeConvertOperands.get(0), RequestUtils.getLiteralExpression(rangeString))); + rewriteToRange(filterFunction, timeConvertOperands.get(0), rangeString); } catch (Exception e) { LOGGER.warn("Caught exception while optimizing TIME_CONVERT predicate: {}, skipping the optimization", filterFunction, e); @@ -289,8 +288,8 @@ && isStringLiteral(dateTimeConvertOperands.get(3)), // -> millis >= 60000 // // Note that 'millisToSeconds(floor(millis, 1 minute)) > 0' is not equivalent to 'millis > 0' - long lowerValue = Long.parseLong(filterOperands.get(1).getLiteral().getFieldValue().toString()); - lowerMillis = ceil(outputFormat.fromFormatToMillis(Long.toString(lowerValue + 1)), granularityMillis); + long lowerValue = getLongValue(filterOperands.get(1)); + lowerMillis = ceil(outputFormat.fromFormatToMillis(lowerValue + 1), granularityMillis); Preconditions.checkState(TimeUtils.timeValueInValidRange(lowerMillis), "Invalid lower bound in millis: %s", lowerMillis); break; @@ -299,7 +298,7 @@ && isStringLiteral(dateTimeConvertOperands.get(3)), // millisToFormat(floor(millis, granularity)) >= n // -> floor(millis, granularity) >= formatToMillis(n) // -> millis >= ceil(formatToMillis(n), granularity) - String lowerValue = filterOperands.get(1).getLiteral().getFieldValue().toString(); + long lowerValue = getLongValue(filterOperands.get(1)); lowerMillis = ceil(outputFormat.fromFormatToMillis(lowerValue), granularityMillis); Preconditions.checkState(TimeUtils.timeValueInValidRange(lowerMillis), "Invalid lower bound in millis: %s", lowerMillis); @@ -309,7 +308,7 @@ && isStringLiteral(dateTimeConvertOperands.get(3)), // millisToFormat(floor(millis, granularity)) < n // -> floor(millis, granularity) < formatToMillis(n) // -> millis < ceil(formatToMillis(n), granularity) - String upperValue = filterOperands.get(1).getLiteral().getFieldValue().toString(); + long upperValue = getLongValue(filterOperands.get(1)); upperMillis = ceil(outputFormat.fromFormatToMillis(upperValue), granularityMillis); Preconditions.checkState(TimeUtils.timeValueInValidRange(upperMillis), "Invalid upper bound in millis: %s", upperMillis); @@ -328,32 +327,31 @@ && isStringLiteral(dateTimeConvertOperands.get(3)), // -> millis < 60000 // // Note that 'millisToSeconds(floor(millis, 1 minute)) <= 0' is not equivalent to 'millis <= 0' - long upperValue = Long.parseLong(filterOperands.get(1).getLiteral().getFieldValue().toString()); - upperMillis = ceil(outputFormat.fromFormatToMillis(Long.toString(upperValue + 1)), granularityMillis); + long upperValue = getLongValue(filterOperands.get(1)); + upperMillis = ceil(outputFormat.fromFormatToMillis(upperValue + 1), granularityMillis); Preconditions.checkState(TimeUtils.timeValueInValidRange(upperMillis), "Invalid upper bound in millis: %s", upperMillis); break; } case BETWEEN: { // Combine GREATER_THAN_OR_EQUAL and LESS_THAN_OR_EQUAL - String lowerValue = filterOperands.get(1).getLiteral().getFieldValue().toString(); + long lowerValue = getLongValue(filterOperands.get(1)); lowerMillis = ceil(outputFormat.fromFormatToMillis(lowerValue), granularityMillis); Preconditions.checkState(TimeUtils.timeValueInValidRange(lowerMillis), "Invalid lower bound in millis: %s", lowerMillis); - long upperValue = Long.parseLong(filterOperands.get(2).getLiteral().getFieldValue().toString()); - upperMillis = ceil(outputFormat.fromFormatToMillis(Long.toString(upperValue + 1)), granularityMillis); + long upperValue = getLongValue(filterOperands.get(2)); + upperMillis = ceil(outputFormat.fromFormatToMillis(upperValue + 1), granularityMillis); Preconditions.checkState(TimeUtils.timeValueInValidRange(upperMillis), "Invalid upper bound in millis: %s", upperMillis); break; } case EQUALS: { // Combine GREATER_THAN_OR_EQUAL and LESS_THAN_OR_EQUAL - String value = filterOperands.get(1).getLiteral().getFieldValue().toString(); + long value = getLongValue(filterOperands.get(1)); lowerMillis = ceil(outputFormat.fromFormatToMillis(value), granularityMillis); Preconditions.checkState(TimeUtils.timeValueInValidRange(lowerMillis), "Invalid lower bound in millis: %s", lowerMillis); - upperMillis = - ceil(outputFormat.fromFormatToMillis(Long.toString(Long.parseLong(value) + 1)), granularityMillis); + upperMillis = ceil(outputFormat.fromFormatToMillis(value + 1), granularityMillis); Preconditions.checkState(TimeUtils.timeValueInValidRange(upperMillis), "Invalid upper bound in millis: %s", upperMillis); break; @@ -400,9 +398,7 @@ && isStringLiteral(dateTimeConvertOperands.get(3)), // Step 3: Rewrite the filter function String rangeString = new Range(lowerValue, lowerInclusive, upperValue, upperInclusive).getRangeString(); - filterFunction.setOperator(FilterKind.RANGE.name()); - filterFunction.setOperands( - Arrays.asList(dateTimeConvertOperands.get(0), RequestUtils.getLiteralExpression(rangeString))); + rewriteToRange(filterFunction, dateTimeConvertOperands.get(0), rangeString); } catch (Exception e) { LOGGER.warn("Caught exception while optimizing DATE_TIME_CONVERT predicate: {}, skipping the optimization", filterFunction, e); @@ -410,7 +406,23 @@ && isStringLiteral(dateTimeConvertOperands.get(3)), } private boolean isStringLiteral(Expression expression) { - return expression.getType() == ExpressionType.LITERAL && expression.getLiteral().isSetStringValue(); + Literal literal = expression.getLiteral(); + return literal != null && literal.isSetStringValue(); + } + + private long getLongValue(Expression expression) { + Literal literal = expression.getLiteral(); + Preconditions.checkArgument(literal != null, "Got non-literal expression: %s", expression); + switch (literal.getSetField()) { + case INT_VALUE: + return literal.getIntValue(); + case LONG_VALUE: + return literal.getLongValue(); + case STRING_VALUE: + return Long.parseLong(literal.getStringValue()); + default: + throw new IllegalStateException("Unsupported literal type: " + literal.getSetField() + " as long value"); + } } /** @@ -419,4 +431,13 @@ private boolean isStringLiteral(Expression expression) { private long ceil(long millisValue, long granularityMillis) { return (millisValue + granularityMillis - 1) / granularityMillis * granularityMillis; } + + private static void rewriteToRange(Function filterFunction, Expression expression, String rangeString) { + filterFunction.setOperator(FilterKind.RANGE.name()); + // NOTE: Create an ArrayList because we might need to modify the list later + List newOperands = new ArrayList<>(2); + newOperands.add(expression); + newOperands.add(RequestUtils.getLiteralExpression(rangeString)); + filterFunction.setOperands(newOperands); + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/prefetch/DefaultFetchPlanner.java b/pinot-core/src/main/java/org/apache/pinot/core/query/prefetch/DefaultFetchPlanner.java index fcfb07aa22f1..d198fb97e07b 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/prefetch/DefaultFetchPlanner.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/prefetch/DefaultFetchPlanner.java @@ -33,7 +33,8 @@ import org.apache.pinot.segment.spi.FetchContext; import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.segment.spi.datasource.DataSource; -import org.apache.pinot.segment.spi.store.ColumnIndexType; +import org.apache.pinot.segment.spi.index.IndexType; +import org.apache.pinot.segment.spi.index.StandardIndexes; public class DefaultFetchPlanner implements FetchPlanner { @@ -45,11 +46,11 @@ public FetchContext planFetchForPruning(IndexSegment indexSegment, QueryContext // Extract columns in EQ/IN predicates. Set eqInColumns = new HashSet<>(); extractEqInColumns(Objects.requireNonNull(queryContext.getFilter()), eqInColumns); - Map> columnToIndexList = new HashMap<>(); + Map>> columnToIndexList = new HashMap<>(); for (String column : eqInColumns) { DataSource dataSource = indexSegment.getDataSource(column); if (dataSource.getBloomFilter() != null) { - columnToIndexList.put(column, Collections.singletonList(ColumnIndexType.BLOOM_FILTER)); + columnToIndexList.put(column, Collections.singletonList(StandardIndexes.bloomFilter())); } } return new FetchContext(UUID.randomUUID(), indexSegment.getSegmentName(), columnToIndexList); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/pruner/BloomFilterSegmentPruner.java b/pinot-core/src/main/java/org/apache/pinot/core/query/pruner/BloomFilterSegmentPruner.java new file mode 100644 index 000000000000..6277aac8eb2b --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/pruner/BloomFilterSegmentPruner.java @@ -0,0 +1,264 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.pruner; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ExecutorService; +import java.util.function.Function; +import javax.annotation.Nullable; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.request.context.FilterContext; +import org.apache.pinot.common.request.context.predicate.EqPredicate; +import org.apache.pinot.common.request.context.predicate.InPredicate; +import org.apache.pinot.common.request.context.predicate.Predicate; +import org.apache.pinot.core.query.prefetch.FetchPlanner; +import org.apache.pinot.core.query.prefetch.FetchPlannerRegistry; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.util.QueryMultiThreadingUtils; +import org.apache.pinot.segment.spi.FetchContext; +import org.apache.pinot.segment.spi.ImmutableSegment; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.datasource.DataSource; +import org.apache.pinot.segment.spi.datasource.DataSourceMetadata; +import org.apache.pinot.segment.spi.index.reader.BloomFilterReader; +import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.exception.QueryCancelledException; + + +/** + * The {@code BloomFilterSegmentPruner} prunes segments based on bloom filter for EQUALITY filter. Because the access + * to bloom filter data is required, segment pruning is done in parallel when the number of segments is large. + */ +@SuppressWarnings({"rawtypes", "unchecked", "RedundantIfStatement"}) +public class BloomFilterSegmentPruner extends ValueBasedSegmentPruner { + // Try to schedule 10 segments for each thread, or evenly distribute them to all MAX_NUM_THREADS_PER_QUERY threads. + // TODO: make this threshold configurable? threshold 10 is also used in CombinePlanNode, which accesses the + // dictionary data to do query planning and if segments are more than 10, planning is done in parallel. + private static final int TARGET_NUM_SEGMENTS_PER_THREAD = 10; + + private FetchPlanner _fetchPlanner; + + @Override + public void init(PinotConfiguration config) { + super.init(config); + _fetchPlanner = FetchPlannerRegistry.getPlanner(); + } + + @Override + protected boolean isApplicableToPredicate(Predicate predicate) { + // Only prune columns + if (predicate.getLhs().getType() != ExpressionContext.Type.IDENTIFIER) { + return false; + } + Predicate.Type predicateType = predicate.getType(); + if (predicateType == Predicate.Type.EQ) { + return true; + } + if (predicateType == Predicate.Type.IN) { + List values = ((InPredicate) predicate).getValues(); + // Skip pruning when there are too many values in the IN predicate + if (values.size() <= _inPredicateThreshold) { + return true; + } + } + return false; + } + + @Override + public List prune(List segments, QueryContext query) { + if (segments.isEmpty()) { + return segments; + } + if (!query.isEnablePrefetch()) { + return super.prune(segments, query); + } + return prefetch(segments, query, fetchContexts -> { + int numSegments = segments.size(); + FilterContext filter = Objects.requireNonNull(query.getFilter()); + ValueCache cachedValues = new ValueCache(); + Map dataSourceCache = new HashMap<>(); + List selectedSegments = new ArrayList<>(numSegments); + for (int i = 0; i < numSegments; i++) { + dataSourceCache.clear(); + IndexSegment segment = segments.get(i); + if (!pruneSegmentWithFetchContext(segment, fetchContexts[i], filter, dataSourceCache, cachedValues)) { + selectedSegments.add(segment); + } + } + return selectedSegments; + }); + } + + @Override + public List prune(List segments, QueryContext query, + @Nullable ExecutorService executorService) { + if (segments.isEmpty()) { + return segments; + } + if (executorService == null || segments.size() <= TARGET_NUM_SEGMENTS_PER_THREAD) { + // If executor is not provided, or the number of segments is small, prune them sequentially + return prune(segments, query); + } + // With executor service and large number of segments, prune them in parallel. + // NOTE: Even if numTasks=1 i.e. we get a single executor thread, still run it using a separate thread so that + // the timeout can be honored. For example, this may happen when there is only one processor. + int numTasks = QueryMultiThreadingUtils.getNumTasks(segments.size(), TARGET_NUM_SEGMENTS_PER_THREAD, + query.getMaxExecutionThreads()); + if (!query.isEnablePrefetch()) { + return pruneInParallel(numTasks, segments, query, executorService, null); + } + return prefetch(segments, query, + fetchContexts -> pruneInParallel(numTasks, segments, query, executorService, fetchContexts)); + } + + private List pruneInParallel(int numTasks, List segments, QueryContext queryContext, + ExecutorService executorService, FetchContext[] fetchContexts) { + int numSegments = segments.size(); + List allSelectedSegments = new ArrayList<>(); + QueryMultiThreadingUtils.runTasksWithDeadline(numTasks, index -> { + FilterContext filter = Objects.requireNonNull(queryContext.getFilter()); + ValueCache cachedValues = new ValueCache(); + Map dataSourceCache = new HashMap<>(); + List selectedSegments = new ArrayList<>(); + for (int i = index; i < numSegments; i += numTasks) { + dataSourceCache.clear(); + IndexSegment segment = segments.get(i); + FetchContext fetchContext = fetchContexts == null ? null : fetchContexts[i]; + if (!pruneSegmentWithFetchContext(segment, fetchContext, filter, dataSourceCache, cachedValues)) { + selectedSegments.add(segment); + } + } + return selectedSegments; + }, taskRes -> { + if (taskRes != null) { + allSelectedSegments.addAll(taskRes); + } + }, e -> { + if (e instanceof InterruptedException) { + throw new QueryCancelledException("Cancelled while running BloomFilterSegmentPruner", e); + } + throw new RuntimeException("Caught exception while running BloomFilterSegmentPruner", e); + }, executorService, queryContext.getEndTimeMs()); + return allSelectedSegments; + } + + private List prefetch(List segments, QueryContext query, + Function> pruneFunc) { + int numSegments = segments.size(); + FetchContext[] fetchContexts = new FetchContext[numSegments]; + try { + // Prefetch bloom filter for columns within the EQ/IN predicate if exists + for (int i = 0; i < numSegments; i++) { + IndexSegment segment = segments.get(i); + FetchContext fetchContext = _fetchPlanner.planFetchForPruning(segment, query); + if (!fetchContext.isEmpty()) { + segment.prefetch(fetchContext); + fetchContexts[i] = fetchContext; + } + } + return pruneFunc.apply(fetchContexts); + } finally { + // Release the prefetched bloom filters + for (int i = 0; i < numSegments; i++) { + FetchContext fetchContext = fetchContexts[i]; + if (fetchContext != null) { + segments.get(i).release(fetchContext); + } + } + } + } + + private boolean pruneSegmentWithFetchContext(IndexSegment segment, FetchContext fetchContext, FilterContext filter, + Map dataSourceCache, ValueCache cachedValues) { + if (fetchContext == null) { + return pruneSegment(segment, filter, dataSourceCache, cachedValues); + } + try { + segment.acquire(fetchContext); + return pruneSegment(segment, filter, dataSourceCache, cachedValues); + } finally { + segment.release(fetchContext); + } + } + + @Override + boolean pruneSegmentWithPredicate(IndexSegment segment, Predicate predicate, Map dataSourceCache, + ValueCache cachedValues) { + Predicate.Type predicateType = predicate.getType(); + if (predicateType == Predicate.Type.EQ) { + return pruneEqPredicate(segment, (EqPredicate) predicate, dataSourceCache, cachedValues); + } else if (predicateType == Predicate.Type.IN) { + return pruneInPredicate(segment, (InPredicate) predicate, dataSourceCache, cachedValues); + } else { + return false; + } + } + + /** + * For EQ predicate, prune the segments based on column bloom filter. + */ + private boolean pruneEqPredicate(IndexSegment segment, EqPredicate eqPredicate, + Map dataSourceCache, ValueCache valueCache) { + String column = eqPredicate.getLhs().getIdentifier(); + DataSource dataSource = segment instanceof ImmutableSegment ? segment.getDataSource(column) + : dataSourceCache.computeIfAbsent(column, segment::getDataSource); + // NOTE: Column must exist after DataSchemaSegmentPruner + assert dataSource != null; + DataSourceMetadata dataSourceMetadata = dataSource.getDataSourceMetadata(); + ValueCache.CachedValue cachedValue = valueCache.get(eqPredicate, dataSourceMetadata.getDataType()); + // Check bloom filter + BloomFilterReader bloomFilter = dataSource.getBloomFilter(); + return bloomFilter != null && !cachedValue.mightBeContained(bloomFilter); + } + + /** + * For IN predicate, prune the segments based on column bloom filter. + * NOTE: segments will not be pruned if the number of values is greater than the threshold. + */ + private boolean pruneInPredicate(IndexSegment segment, InPredicate inPredicate, + Map dataSourceCache, ValueCache valueCache) { + List values = inPredicate.getValues(); + // Skip pruning when there are too many values in the IN predicate + if (values.size() > _inPredicateThreshold) { + return false; + } + String column = inPredicate.getLhs().getIdentifier(); + DataSource dataSource = segment instanceof ImmutableSegment ? segment.getDataSource(column) + : dataSourceCache.computeIfAbsent(column, segment::getDataSource); + // NOTE: Column must exist after DataSchemaSegmentPruner + assert dataSource != null; + DataSourceMetadata dataSourceMetadata = dataSource.getDataSourceMetadata(); + List cachedValues = valueCache.get(inPredicate, dataSourceMetadata.getDataType()); + // Check bloom filter + BloomFilterReader bloomFilter = dataSource.getBloomFilter(); + if (bloomFilter == null) { + return false; + } + for (ValueCache.CachedValue value : cachedValues) { + if (value.mightBeContained(bloomFilter)) { + return false; + } + } + return true; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/pruner/ColumnValueSegmentPruner.java b/pinot-core/src/main/java/org/apache/pinot/core/query/pruner/ColumnValueSegmentPruner.java index 6ee9ba5c05c2..8eead9f552fb 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/pruner/ColumnValueSegmentPruner.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/pruner/ColumnValueSegmentPruner.java @@ -18,45 +18,32 @@ */ package org.apache.pinot.core.query.pruner; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.IdentityHashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import org.apache.pinot.common.request.context.ExpressionContext; -import org.apache.pinot.common.request.context.FilterContext; import org.apache.pinot.common.request.context.predicate.EqPredicate; import org.apache.pinot.common.request.context.predicate.InPredicate; import org.apache.pinot.common.request.context.predicate.Predicate; import org.apache.pinot.common.request.context.predicate.RangePredicate; -import org.apache.pinot.core.query.prefetch.FetchPlanner; -import org.apache.pinot.core.query.prefetch.FetchPlannerRegistry; -import org.apache.pinot.core.query.request.context.QueryContext; -import org.apache.pinot.segment.local.segment.index.readers.bloom.GuavaBloomFilterReaderUtils; -import org.apache.pinot.segment.spi.FetchContext; import org.apache.pinot.segment.spi.ImmutableSegment; import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.segment.spi.datasource.DataSourceMetadata; -import org.apache.pinot.segment.spi.index.reader.BloomFilterReader; import org.apache.pinot.segment.spi.partition.PartitionFunction; import org.apache.pinot.spi.data.FieldSpec.DataType; -import org.apache.pinot.spi.env.PinotConfiguration; -import org.apache.pinot.spi.exception.BadQueryRequestException; -import org.apache.pinot.spi.utils.CommonConstants.Server; /** * The {@code ColumnValueSegmentPruner} is the segment pruner that prunes segments based on the value inside the filter. + * This pruner is supposed to use segment metadata like min/max or partition id only. Pruners that need to access + * segment data like bloom filter is implemented separately and called after this one to reduce required data access. *

      *
    • * For EQUALITY filter, prune the segment based on: *
        *
      • Column min/max value
      • *
      • Column partition
      • - *
      • Column bloom filter
      • *
      *
    • *
    • @@ -68,126 +55,39 @@ *
    */ @SuppressWarnings({"rawtypes", "unchecked", "RedundantIfStatement"}) -public class ColumnValueSegmentPruner implements SegmentPruner { - - public static final String IN_PREDICATE_THRESHOLD = "inpredicate.threshold"; - - private int _inPredicateThreshold; - private FetchPlanner _fetchPlanner; - - @Override - public void init(PinotConfiguration config) { - _inPredicateThreshold = - config.getProperty(IN_PREDICATE_THRESHOLD, Server.DEFAULT_VALUE_PRUNER_IN_PREDICATE_THRESHOLD); - _fetchPlanner = FetchPlannerRegistry.getPlanner(); - } - - @Override - public boolean isApplicableTo(QueryContext query) { - return query.getFilter() != null; - } - +public class ColumnValueSegmentPruner extends ValueBasedSegmentPruner { @Override - public List prune(List segments, QueryContext query) { - if (segments.isEmpty()) { - return segments; + protected boolean isApplicableToPredicate(Predicate predicate) { + // Only prune columns + if (predicate.getLhs().getType() != ExpressionContext.Type.IDENTIFIER) { + return false; } - FilterContext filter = Objects.requireNonNull(query.getFilter()); - ValueCache cachedValues = new ValueCache(); - int numSegments = segments.size(); - List selectedSegments = new ArrayList<>(numSegments); - if (query.isEnablePrefetch()) { - FetchContext[] fetchContexts = new FetchContext[numSegments]; - try { - // Prefetch bloom filter for columns within the EQ/IN predicate if exists - for (int i = 0; i < numSegments; i++) { - IndexSegment segment = segments.get(i); - FetchContext fetchContext = _fetchPlanner.planFetchForPruning(segment, query); - if (!fetchContext.isEmpty()) { - segment.prefetch(fetchContext); - fetchContexts[i] = fetchContext; - } - } - // Prune segments - Map[] dataSourceCaches = new Map[numSegments]; - for (int i = 0; i < numSegments; i++) { - dataSourceCaches[i] = new HashMap<>(); - IndexSegment segment = segments.get(i); - FetchContext fetchContext = fetchContexts[i]; - if (fetchContext != null) { - segment.acquire(fetchContext); - try { - if (!pruneSegment(segment, filter, dataSourceCaches[i], cachedValues)) { - selectedSegments.add(segment); - } - } finally { - segment.release(fetchContext); - } - } else { - if (!pruneSegment(segment, filter, dataSourceCaches[i], cachedValues)) { - selectedSegments.add(segment); - } - } - } - } finally { - // Release the prefetched bloom filters - for (int i = 0; i < numSegments; i++) { - FetchContext fetchContext = fetchContexts[i]; - if (fetchContext != null) { - segments.get(i).release(fetchContext); - } - } - } - } else { - Map dataSourceCache = new HashMap<>(); - for (IndexSegment segment : segments) { - dataSourceCache.clear(); - if (!pruneSegment(segment, filter, dataSourceCache, cachedValues)) { - selectedSegments.add(segment); - } + Predicate.Type predicateType = predicate.getType(); + if (predicateType == Predicate.Type.EQ || predicateType == Predicate.Type.RANGE) { + return true; + } + if (predicateType == Predicate.Type.IN) { + List values = ((InPredicate) predicate).getValues(); + // Skip pruning when there are too many values in the IN predicate + if (values.size() <= _inPredicateThreshold) { + return true; } } - return selectedSegments; + return false; } - private boolean pruneSegment(IndexSegment segment, FilterContext filter, Map dataSourceCache, + @Override + boolean pruneSegmentWithPredicate(IndexSegment segment, Predicate predicate, Map dataSourceCache, ValueCache cachedValues) { - switch (filter.getType()) { - case AND: - for (FilterContext child : filter.getChildren()) { - if (pruneSegment(segment, child, dataSourceCache, cachedValues)) { - return true; - } - } - return false; - case OR: - for (FilterContext child : filter.getChildren()) { - if (!pruneSegment(segment, child, dataSourceCache, cachedValues)) { - return false; - } - } - return true; - case NOT: - // Do not prune NOT filter - return false; - case PREDICATE: - Predicate predicate = filter.getPredicate(); - // Only prune columns - if (predicate.getLhs().getType() != ExpressionContext.Type.IDENTIFIER) { - return false; - } - Predicate.Type predicateType = predicate.getType(); - if (predicateType == Predicate.Type.EQ) { - return pruneEqPredicate(segment, (EqPredicate) predicate, dataSourceCache, cachedValues); - } else if (predicateType == Predicate.Type.IN) { - return pruneInPredicate(segment, (InPredicate) predicate, dataSourceCache, cachedValues); - } else if (predicateType == Predicate.Type.RANGE) { - return pruneRangePredicate(segment, (RangePredicate) predicate, dataSourceCache); - } else { - return false; - } - default: - throw new IllegalStateException(); + Predicate.Type predicateType = predicate.getType(); + if (predicateType == Predicate.Type.EQ) { + return pruneEqPredicate(segment, (EqPredicate) predicate, dataSourceCache, cachedValues); + } else if (predicateType == Predicate.Type.IN) { + return pruneInPredicate(segment, (InPredicate) predicate, dataSourceCache, cachedValues); + } else if (predicateType == Predicate.Type.RANGE) { + return pruneRangePredicate(segment, (RangePredicate) predicate, dataSourceCache); + } else { + return false; } } @@ -196,45 +96,30 @@ private boolean pruneSegment(IndexSegment segment, FilterContext filter, Map *
  • Column min/max value
  • *
  • Column partition
  • - *
  • Column bloom filter
  • * */ private boolean pruneEqPredicate(IndexSegment segment, EqPredicate eqPredicate, Map dataSourceCache, ValueCache valueCache) { String column = eqPredicate.getLhs().getIdentifier(); - DataSource dataSource = segment instanceof ImmutableSegment - ? segment.getDataSource(column) + DataSource dataSource = segment instanceof ImmutableSegment ? segment.getDataSource(column) : dataSourceCache.computeIfAbsent(column, segment::getDataSource); // NOTE: Column must exist after DataSchemaSegmentPruner assert dataSource != null; DataSourceMetadata dataSourceMetadata = dataSource.getDataSourceMetadata(); ValueCache.CachedValue cachedValue = valueCache.get(eqPredicate, dataSourceMetadata.getDataType()); - - Comparable value = cachedValue.getComparableValue(); - // Check min/max value - if (!checkMinMaxRange(dataSourceMetadata, value)) { + if (!checkMinMaxRange(dataSourceMetadata, cachedValue.getComparableValue())) { return true; } - // Check column partition PartitionFunction partitionFunction = dataSourceMetadata.getPartitionFunction(); if (partitionFunction != null) { Set partitions = dataSourceMetadata.getPartitions(); assert partitions != null; - if (!partitions.contains(partitionFunction.getPartition(value))) { + if (!partitions.contains(partitionFunction.getPartition(cachedValue.getValue()))) { return true; } } - - // Check bloom filter - BloomFilterReader bloomFilter = dataSource.getBloomFilter(); - if (bloomFilter != null) { - if (!cachedValue.mightBeContained(bloomFilter)) { - return true; - } - } - return false; } @@ -242,66 +127,26 @@ private boolean pruneEqPredicate(IndexSegment segment, EqPredicate eqPredicate, * For IN predicate, prune the segments based on: *
      *
    • Column min/max value
    • - *
    • Column bloom filter
    • *
    *

    NOTE: segments will not be pruned if the number of values is greater than the threshold. */ private boolean pruneInPredicate(IndexSegment segment, InPredicate inPredicate, Map dataSourceCache, ValueCache valueCache) { - String column = inPredicate.getLhs().getIdentifier(); - DataSource dataSource = segment instanceof ImmutableSegment - ? segment.getDataSource(column) - : dataSourceCache.computeIfAbsent(column, segment::getDataSource); - // NOTE: Column must exist after DataSchemaSegmentPruner - assert dataSource != null; - DataSourceMetadata dataSourceMetadata = dataSource.getDataSourceMetadata(); List values = inPredicate.getValues(); - // Skip pruning when there are too many values in the IN predicate if (values.size() > _inPredicateThreshold) { return false; } - + String column = inPredicate.getLhs().getIdentifier(); + DataSource dataSource = segment instanceof ImmutableSegment ? segment.getDataSource(column) + : dataSourceCache.computeIfAbsent(column, segment::getDataSource); + // NOTE: Column must exist after DataSchemaSegmentPruner + assert dataSource != null; + DataSourceMetadata dataSourceMetadata = dataSource.getDataSourceMetadata(); List cachedValues = valueCache.get(inPredicate, dataSourceMetadata.getDataType()); - // Check min/max value - boolean someInRange = false; for (ValueCache.CachedValue value : cachedValues) { if (checkMinMaxRange(dataSourceMetadata, value.getComparableValue())) { - someInRange = true; - break; - } - } - if (!someInRange) { - return true; - } - - // Check bloom filter - BloomFilterReader bloomFilter = dataSource.getBloomFilter(); - if (bloomFilter == null) { - return false; - } - for (ValueCache.CachedValue value : cachedValues) { - if (value.mightBeContained(bloomFilter)) { - return false; - } - } - return true; - } - - /** - * Returns {@code true} if the value is within the column's min/max value range, {@code false} otherwise. - */ - private boolean checkMinMaxRange(DataSourceMetadata dataSourceMetadata, Comparable value) { - Comparable minValue = dataSourceMetadata.getMinValue(); - if (minValue != null) { - if (value.compareTo(minValue) < 0) { - return false; - } - } - Comparable maxValue = dataSourceMetadata.getMaxValue(); - if (maxValue != null) { - if (value.compareTo(maxValue) > 0) { return false; } } @@ -317,8 +162,7 @@ private boolean checkMinMaxRange(DataSourceMetadata dataSourceMetadata, Comparab private boolean pruneRangePredicate(IndexSegment segment, RangePredicate rangePredicate, Map dataSourceCache) { String column = rangePredicate.getLhs().getIdentifier(); - DataSource dataSource = segment instanceof ImmutableSegment - ? segment.getDataSource(column) + DataSource dataSource = segment instanceof ImmutableSegment ? segment.getDataSource(column) : dataSourceCache.computeIfAbsent(column, segment::getDataSource); // NOTE: Column must exist after DataSchemaSegmentPruner assert dataSource != null; @@ -382,95 +226,25 @@ private boolean pruneRangePredicate(IndexSegment segment, RangePredicate rangePr } } } - return false; } - private static Comparable convertValue(String stringValue, DataType dataType) { - try { - return dataType.convertInternal(stringValue); - } catch (Exception e) { - throw new BadQueryRequestException(e); - } - } - - private static class ValueCache { - // As Predicates are recursive structures, their hashCode is quite expensive. - // By using an IdentityHashMap here we don't need to iterate over the recursive - // structure. This is specially useful in the IN expression. - private final Map _cache = new IdentityHashMap<>(); - - private CachedValue add(EqPredicate pred) { - CachedValue val = new CachedValue(pred.getValue()); - _cache.put(pred, val); - return val; - } - - private List add(InPredicate pred) { - List vals = new ArrayList<>(pred.getValues().size()); - for (String value : pred.getValues()) { - vals.add(new CachedValue(value)); - } - _cache.put(pred, vals); - return vals; - } - - public CachedValue get(EqPredicate pred, DataType dt) { - CachedValue cachedValue = (CachedValue) _cache.get(pred); - if (cachedValue == null) { - cachedValue = add(pred); - } - cachedValue.ensureDataType(dt); - return cachedValue; - } - - public List get(InPredicate pred, DataType dt) { - List cachedValues = (List) _cache.get(pred); - if (cachedValues == null) { - cachedValues = add(pred); - } - for (CachedValue cachedValue : cachedValues) { - cachedValue.ensureDataType(dt); + /** + * Returns {@code true} if the value is within the column's min/max value range, {@code false} otherwise. + */ + private boolean checkMinMaxRange(DataSourceMetadata dataSourceMetadata, Comparable value) { + Comparable minValue = dataSourceMetadata.getMinValue(); + if (minValue != null) { + if (value.compareTo(minValue) < 0) { + return false; } - return cachedValues; } - - public static class CachedValue { - private final Object _value; - private boolean _hashed = false; - private long _hash1; - private long _hash2; - private DataType _dt; - private Comparable _comparableValue; - - private CachedValue(Object value) { - _value = value; - } - - private Comparable getComparableValue() { - assert _dt != null; - return _comparableValue; - } - - private void ensureDataType(DataType dt) { - if (dt != _dt) { - String strValue = _value.toString(); - _dt = dt; - _comparableValue = convertValue(strValue, dt); - _hashed = false; - } - } - - private boolean mightBeContained(BloomFilterReader bloomFilter) { - if (!_hashed) { - GuavaBloomFilterReaderUtils.Hash128AsLongs hash128AsLongs = - GuavaBloomFilterReaderUtils.hashAsLongs(_comparableValue.toString()); - _hash1 = hash128AsLongs.getHash1(); - _hash2 = hash128AsLongs.getHash2(); - _hashed = true; - } - return bloomFilter.mightContain(_hash1, _hash2); + Comparable maxValue = dataSourceMetadata.getMaxValue(); + if (maxValue != null) { + if (value.compareTo(maxValue) > 0) { + return false; } } + return true; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/pruner/SegmentPruner.java b/pinot-core/src/main/java/org/apache/pinot/core/query/pruner/SegmentPruner.java index 39d19c375d30..db848fc9159d 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/pruner/SegmentPruner.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/pruner/SegmentPruner.java @@ -19,6 +19,8 @@ package org.apache.pinot.core.query.pruner; import java.util.List; +import java.util.concurrent.ExecutorService; +import javax.annotation.Nullable; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.spi.env.PinotConfiguration; @@ -45,4 +47,9 @@ public interface SegmentPruner { * TODO: Revisit this because the caller doesn't require not changing the input segments */ List prune(List segments, QueryContext query); + + default List prune(List segments, QueryContext query, + @Nullable ExecutorService executorService) { + return prune(segments, query); + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/pruner/SegmentPrunerProvider.java b/pinot-core/src/main/java/org/apache/pinot/core/query/pruner/SegmentPrunerProvider.java index e09d54590d7e..e9acb210ab78 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/pruner/SegmentPrunerProvider.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/pruner/SegmentPrunerProvider.java @@ -26,8 +26,6 @@ /** * A static SegmentPrunerProvider will give SegmentPruner instance based on prunerClassName and configuration. - * - * */ public class SegmentPrunerProvider { private SegmentPrunerProvider() { @@ -36,10 +34,12 @@ private SegmentPrunerProvider() { private static final Map> PRUNER_MAP = new HashMap<>(); public static final String COLUMN_VALUE_SEGMENT_PRUNER_NAME = "columnvaluesegmentpruner"; + public static final String BLOOM_FILTER_SEGMENT_PRUNER_NAME = "bloomfiltersegmentpruner"; public static final String SELECTION_QUERY_SEGMENT_PRUNER_NAME = "selectionquerysegmentpruner"; static { PRUNER_MAP.put(COLUMN_VALUE_SEGMENT_PRUNER_NAME, ColumnValueSegmentPruner.class); + PRUNER_MAP.put(BLOOM_FILTER_SEGMENT_PRUNER_NAME, BloomFilterSegmentPruner.class); PRUNER_MAP.put(SELECTION_QUERY_SEGMENT_PRUNER_NAME, SelectionQuerySegmentPruner.class); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/pruner/SegmentPrunerService.java b/pinot-core/src/main/java/org/apache/pinot/core/query/pruner/SegmentPrunerService.java index 5c79ca1c2db3..4b775f67bd6f 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/pruner/SegmentPrunerService.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/pruner/SegmentPrunerService.java @@ -22,7 +22,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.ExecutorService; import java.util.function.BiConsumer; +import javax.annotation.Nullable; import org.apache.pinot.core.query.config.SegmentPrunerConfig; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.IndexSegment; @@ -59,6 +61,7 @@ public SegmentPrunerService(SegmentPrunerConfig config) { _prunerStatsUpdaters.put(pruner, SegmentPrunerStatistics::setLimitPruned); break; case SegmentPrunerProvider.COLUMN_VALUE_SEGMENT_PRUNER_NAME: + case SegmentPrunerProvider.BLOOM_FILTER_SEGMENT_PRUNER_NAME: _prunerStatsUpdaters.put(pruner, SegmentPrunerStatistics::setValuePruned); break; default: @@ -96,6 +99,11 @@ public List prune(List segments, QueryContext query) * undefined way. Therefore, this list should not be used after calling this method. */ public List prune(List segments, QueryContext query, SegmentPrunerStatistics stats) { + return prune(segments, query, stats, null); + } + + public List prune(List segments, QueryContext query, SegmentPrunerStatistics stats, + @Nullable ExecutorService executorService) { try (InvocationScope scope = Tracing.getTracer().createScope(SegmentPrunerService.class)) { segments = removeInvalidSegments(segments, query, stats); int invokedPrunersCount = 0; @@ -105,7 +113,7 @@ public List prune(List segments, QueryContext query, try (InvocationScope prunerScope = Tracing.getTracer().createScope(segmentPruner.getClass())) { int originalSegmentsSize = segments.size(); prunerScope.setNumSegments(originalSegmentsSize); - segments = segmentPruner.prune(segments, query); + segments = segmentPruner.prune(segments, query, executorService); _prunerStatsUpdaters.get(segmentPruner).accept(stats, originalSegmentsSize - segments.size()); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/pruner/ValueBasedSegmentPruner.java b/pinot-core/src/main/java/org/apache/pinot/core/query/pruner/ValueBasedSegmentPruner.java new file mode 100644 index 000000000000..2e3214c8c211 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/pruner/ValueBasedSegmentPruner.java @@ -0,0 +1,243 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.pruner; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.request.context.FilterContext; +import org.apache.pinot.common.request.context.predicate.EqPredicate; +import org.apache.pinot.common.request.context.predicate.InPredicate; +import org.apache.pinot.common.request.context.predicate.Predicate; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.segment.local.segment.index.readers.bloom.GuavaBloomFilterReaderUtils; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.datasource.DataSource; +import org.apache.pinot.segment.spi.index.reader.BloomFilterReader; +import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.exception.BadQueryRequestException; +import org.apache.pinot.spi.utils.CommonConstants.Server; + + +/** + * The {@code ValueBasedSegmentPruner} prunes segments based on values inside the filter and segment metadata and data. + */ +@SuppressWarnings({"rawtypes", "unchecked", "RedundantIfStatement"}) +abstract public class ValueBasedSegmentPruner implements SegmentPruner { + public static final String IN_PREDICATE_THRESHOLD = "inpredicate.threshold"; + protected int _inPredicateThreshold; + + @Override + public void init(PinotConfiguration config) { + _inPredicateThreshold = + config.getProperty(IN_PREDICATE_THRESHOLD, Server.DEFAULT_VALUE_PRUNER_IN_PREDICATE_THRESHOLD); + } + + @Override + public boolean isApplicableTo(QueryContext query) { + if (query.getFilter() == null) { + return false; + } + return isApplicableToFilter(query.getFilter()); + } + + /** + * 1. NOT is not applicable for segment pruning; + * 2. For OR, if one of the child filter is not applicable for pruning, the parent filter is not applicable; + * 3. For AND, if one of the child filter is applicable for pruning, the parent filter is applicable, but it + * doesn't mean this child filter can prune the segment. + * 4. The specific pruners decide their own applicable predicate types. + */ + private boolean isApplicableToFilter(FilterContext filter) { + switch (filter.getType()) { + case AND: + for (FilterContext child : filter.getChildren()) { + if (isApplicableToFilter(child)) { + return true; + } + } + return false; + case OR: + for (FilterContext child : filter.getChildren()) { + if (!isApplicableToFilter(child)) { + return false; + } + } + return true; + case NOT: + // Do not prune NOT filter + return false; + case PREDICATE: + return isApplicableToPredicate(filter.getPredicate()); + default: + throw new IllegalStateException(); + } + } + + abstract boolean isApplicableToPredicate(Predicate predicate); + + @Override + public List prune(List segments, QueryContext query) { + if (segments.isEmpty()) { + return segments; + } + FilterContext filter = Objects.requireNonNull(query.getFilter()); + ValueCache cachedValues = new ValueCache(); + Map dataSourceCache = new HashMap<>(); + List selectedSegments = new ArrayList<>(segments.size()); + for (IndexSegment segment : segments) { + dataSourceCache.clear(); + if (!pruneSegment(segment, filter, dataSourceCache, cachedValues)) { + selectedSegments.add(segment); + } + } + return selectedSegments; + } + + protected boolean pruneSegment(IndexSegment segment, FilterContext filter, Map dataSourceCache, + ValueCache cachedValues) { + switch (filter.getType()) { + case AND: + for (FilterContext child : filter.getChildren()) { + if (pruneSegment(segment, child, dataSourceCache, cachedValues)) { + return true; + } + } + return false; + case OR: + for (FilterContext child : filter.getChildren()) { + if (!pruneSegment(segment, child, dataSourceCache, cachedValues)) { + return false; + } + } + return true; + case NOT: + // Do not prune NOT filter + return false; + case PREDICATE: + Predicate predicate = filter.getPredicate(); + // Only prune columns + if (predicate.getLhs().getType() != ExpressionContext.Type.IDENTIFIER) { + return false; + } + return pruneSegmentWithPredicate(segment, predicate, dataSourceCache, cachedValues); + default: + throw new IllegalStateException(); + } + } + + abstract boolean pruneSegmentWithPredicate(IndexSegment segment, Predicate predicate, + Map dataSourceCache, ValueCache cachedValues); + + protected static Comparable convertValue(String stringValue, DataType dataType) { + try { + return dataType.convertInternal(stringValue); + } catch (Exception e) { + throw new BadQueryRequestException(e); + } + } + + protected static class ValueCache { + // As Predicates are recursive structures, their hashCode is quite expensive. + // By using an IdentityHashMap here we don't need to iterate over the recursive + // structure. This is specially useful in the IN expression. + private final Map _cache = new IdentityHashMap<>(); + + private CachedValue add(EqPredicate pred) { + CachedValue val = new CachedValue(pred.getValue()); + _cache.put(pred, val); + return val; + } + + private List add(InPredicate pred) { + List vals = new ArrayList<>(pred.getValues().size()); + for (String value : pred.getValues()) { + vals.add(new CachedValue(value)); + } + _cache.put(pred, vals); + return vals; + } + + public CachedValue get(EqPredicate pred, DataType dt) { + CachedValue cachedValue = (CachedValue) _cache.get(pred); + if (cachedValue == null) { + cachedValue = add(pred); + } + cachedValue.ensureDataType(dt); + return cachedValue; + } + + public List get(InPredicate pred, DataType dt) { + List cachedValues = (List) _cache.get(pred); + if (cachedValues == null) { + cachedValues = add(pred); + } + for (CachedValue cachedValue : cachedValues) { + cachedValue.ensureDataType(dt); + } + return cachedValues; + } + + public static class CachedValue { + private final String _value; + private boolean _hashed = false; + private long _hash1; + private long _hash2; + private DataType _dt; + private Comparable _comparableValue; + + private CachedValue(String value) { + _value = value; + } + + public String getValue() { + return _value; + } + + public Comparable getComparableValue() { + assert _dt != null; + return _comparableValue; + } + + public void ensureDataType(DataType dt) { + if (dt != _dt) { + _dt = dt; + _comparableValue = convertValue(_value, dt); + _hashed = false; + } + } + + public boolean mightBeContained(BloomFilterReader bloomFilter) { + if (!_hashed) { + GuavaBloomFilterReaderUtils.Hash128AsLongs hash128AsLongs = + GuavaBloomFilterReaderUtils.hashAsLongs(_comparableValue.toString()); + _hash1 = hash128AsLongs.getHash1(); + _hash2 = hash128AsLongs.getHash2(); + _hashed = true; + } + return bloomFilter.mightContain(_hash1, _hash2); + } + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/AggregationDataTableReducer.java b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/AggregationDataTableReducer.java index b727df9c30bb..1c39b6971bfa 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/AggregationDataTableReducer.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/AggregationDataTableReducer.java @@ -18,9 +18,9 @@ */ package org.apache.pinot.core.query.reduce; -import com.google.common.base.Preconditions; import java.util.Collection; import java.util.Collections; +import java.util.List; import java.util.Map; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.metrics.BrokerMetrics; @@ -31,7 +31,10 @@ import org.apache.pinot.core.query.aggregation.function.AggregationFunction; import org.apache.pinot.core.query.aggregation.function.AggregationFunctionUtils; import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.query.utils.rewriter.ResultRewriteUtils; +import org.apache.pinot.core.query.utils.rewriter.RewriterResult; import org.apache.pinot.core.transport.ServerRoutingInstance; +import org.apache.pinot.spi.trace.Tracing; import org.roaringbitmap.RoaringBitmap; @@ -43,9 +46,10 @@ public class AggregationDataTableReducer implements DataTableReducer { private final QueryContext _queryContext; private final AggregationFunction[] _aggregationFunctions; - AggregationDataTableReducer(QueryContext queryContext) { + public AggregationDataTableReducer(QueryContext queryContext) { _queryContext = queryContext; - _aggregationFunctions = queryContext.getAggregationFunctions(); + _aggregationFunctions = _queryContext.getAggregationFunctions(); + assert _aggregationFunctions != null; } /** @@ -55,20 +59,24 @@ public class AggregationDataTableReducer implements DataTableReducer { public void reduceAndSetResults(String tableName, DataSchema dataSchema, Map dataTableMap, BrokerResponseNative brokerResponseNative, DataTableReducerContext reducerContext, BrokerMetrics brokerMetrics) { - assert dataSchema != null; + dataSchema = ReducerDataSchemaUtils.canonicalizeDataSchemaForAggregation(_queryContext, dataSchema); if (dataTableMap.isEmpty()) { DataSchema resultTableSchema = - new PostAggregationHandler(_queryContext, getPrePostAggregationDataSchema()).getResultDataSchema(); + new PostAggregationHandler(_queryContext, getPrePostAggregationDataSchema(dataSchema)).getResultDataSchema(); brokerResponseNative.setResultTable(new ResultTable(resultTableSchema, Collections.emptyList())); return; } - if (!_queryContext.isServerReturnFinalResult()) { - reduceWithIntermediateResult(dataSchema, dataTableMap.values(), brokerResponseNative); + Collection dataTables = dataTableMap.values(); + if (_queryContext.isServerReturnFinalResult()) { + if (dataTables.size() == 1) { + processSingleFinalResult(dataSchema, dataTables.iterator().next(), brokerResponseNative); + } else { + reduceWithFinalResult(dataSchema, dataTables, brokerResponseNative); + } } else { - Preconditions.checkState(dataTableMap.size() == 1, "Cannot merge final results from multiple servers"); - reduceWithFinalResult(dataSchema, dataTableMap.values().iterator().next(), brokerResponseNative); + reduceWithIntermediateResult(dataSchema, dataTables, brokerResponseNative); } } @@ -77,6 +85,7 @@ private void reduceWithIntermediateResult(DataSchema dataSchema, Collection dataTables, + BrokerResponseNative brokerResponseNative) { + int numAggregationFunctions = _aggregationFunctions.length; + Comparable[] finalResults = new Comparable[numAggregationFunctions]; + for (DataTable dataTable : dataTables) { + for (int i = 0; i < numAggregationFunctions; i++) { + Tracing.ThreadAccountantOps.sampleAndCheckInterruption(); + Comparable finalResultToMerge; + ColumnDataType columnDataType = dataSchema.getColumnDataType(i); + if (_queryContext.isNullHandlingEnabled()) { + RoaringBitmap nullBitmap = dataTable.getNullRowIds(i); + if (nullBitmap != null && nullBitmap.contains(0)) { + finalResultToMerge = null; + } else { + finalResultToMerge = AggregationFunctionUtils.getFinalResult(dataTable, columnDataType, 0, i); + } + } else { + finalResultToMerge = AggregationFunctionUtils.getFinalResult(dataTable, columnDataType, 0, i); + } + Comparable mergedFinalResult = finalResults[i]; + if (mergedFinalResult == null) { + finalResults[i] = finalResultToMerge; + } else { + finalResults[i] = _aggregationFunctions[i].mergeFinalResult(mergedFinalResult, finalResultToMerge); + } + } + } + Object[] convertedFinalResults = new Object[numAggregationFunctions]; + for (int i = 0; i < numAggregationFunctions; i++) { + AggregationFunction aggregationFunction = _aggregationFunctions[i]; + Comparable result = finalResults[i]; + convertedFinalResults[i] = result != null ? aggregationFunction.getFinalResultColumnType().convert(result) : null; + } + brokerResponseNative.setResultTable( + reduceToResultTable(getPrePostAggregationDataSchema(dataSchema), convertedFinalResults)); } /** * Sets aggregation results into ResultsTable */ - private ResultTable reduceToResultTable(Object[] finalResults) { - PostAggregationHandler postAggregationHandler = - new PostAggregationHandler(_queryContext, getPrePostAggregationDataSchema()); - DataSchema dataSchema = postAggregationHandler.getResultDataSchema(); + private ResultTable reduceToResultTable(DataSchema dataSchema, Object[] finalResults) { + PostAggregationHandler postAggregationHandler = new PostAggregationHandler(_queryContext, dataSchema); + DataSchema resultDataSchema = postAggregationHandler.getResultDataSchema(); Object[] row = postAggregationHandler.getResult(finalResults); - ColumnDataType[] columnDataTypes = dataSchema.getColumnDataTypes(); + + RewriterResult resultRewriterResult = + ResultRewriteUtils.rewriteResult(resultDataSchema, Collections.singletonList(row)); + resultDataSchema = resultRewriterResult.getDataSchema(); + List rows = resultRewriterResult.getRows(); + + ColumnDataType[] columnDataTypes = resultDataSchema.getColumnDataTypes(); int numColumns = columnDataTypes.length; - for (int i = 0; i < numColumns; i++) { - row[i] = columnDataTypes[i].format(row[i]); + for (Object[] rewrittenRow : rows) { + for (int j = 0; j < numColumns; j++) { + rewrittenRow[j] = columnDataTypes[j].format(rewrittenRow[j]); + } } - return new ResultTable(dataSchema, Collections.singletonList(row)); + + return new ResultTable(resultDataSchema, rows); } /** * Constructs the DataSchema for the rows before the post-aggregation (SQL mode). */ - private DataSchema getPrePostAggregationDataSchema() { + private DataSchema getPrePostAggregationDataSchema(DataSchema dataSchema) { int numAggregationFunctions = _aggregationFunctions.length; - String[] columnNames = new String[numAggregationFunctions]; ColumnDataType[] columnDataTypes = new ColumnDataType[numAggregationFunctions]; for (int i = 0; i < numAggregationFunctions; i++) { - AggregationFunction aggregationFunction = _aggregationFunctions[i]; - columnNames[i] = aggregationFunction.getResultColumnName(); - columnDataTypes[i] = aggregationFunction.getFinalResultColumnType(); + columnDataTypes[i] = _aggregationFunctions[i].getFinalResultColumnType(); } - return new DataSchema(columnNames, columnDataTypes); + return new DataSchema(dataSchema.getColumnNames(), columnDataTypes); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/BaseGapfillProcessor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/BaseGapfillProcessor.java index 0c37e7f76306..25106858ea48 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/BaseGapfillProcessor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/BaseGapfillProcessor.java @@ -83,16 +83,16 @@ abstract class BaseGapfillProcessor { List args = _gapFillSelection.getFunction().getArguments(); - _dateTimeFormatter = new DateTimeFormatSpec(args.get(1).getLiteralString()); - _gapfillDateTimeGranularity = new DateTimeGranularitySpec(args.get(4).getLiteralString()); - if (args.get(5).getLiteralString() == null) { + _dateTimeFormatter = new DateTimeFormatSpec(args.get(1).getLiteral().getStringValue()); + _gapfillDateTimeGranularity = new DateTimeGranularitySpec(args.get(4).getLiteral().getStringValue()); + if (args.get(5).getLiteral() == null) { _postGapfillDateTimeGranularity = _gapfillDateTimeGranularity; } else { - _postGapfillDateTimeGranularity = new DateTimeGranularitySpec(args.get(5).getLiteralString()); + _postGapfillDateTimeGranularity = new DateTimeGranularitySpec(args.get(5).getLiteral().getStringValue()); } - String start = args.get(2).getLiteralString(); + String start = args.get(2).getLiteral().getStringValue(); _startMs = truncate(_dateTimeFormatter.fromFormatToMillis(start)); - String end = args.get(3).getLiteralString(); + String end = args.get(3).getLiteral().getStringValue(); _endMs = truncate(_dateTimeFormatter.fromFormatToMillis(end)); _gapfillTimeBucketSize = _gapfillDateTimeGranularity.granularityToMillis(); _postGapfillTimeBucketSize = _postGapfillDateTimeGranularity.granularityToMillis(); @@ -201,7 +201,7 @@ protected DataSchema getResultTableDataSchema(DataSchema dataSchema) { } else { FunctionContext functionContext = expressionContext.getFunction(); AggregationFunction aggregationFunction = - AggregationFunctionFactory.getAggregationFunction(functionContext, _queryContext); + AggregationFunctionFactory.getAggregationFunction(functionContext, _queryContext.isNullHandlingEnabled()); columnDataTypes[i] = aggregationFunction.getFinalResultColumnType(); columnNames[i] = functionContext.toString(); } @@ -222,8 +222,8 @@ protected long truncate(long epoch) { return epoch / sz * sz; } - protected List gapFillAndAggregate( - List rows, DataSchema dataSchema, DataSchema resultTableSchema) { + protected List gapFillAndAggregate(List rows, DataSchema dataSchema, + DataSchema resultTableSchema) { throw new UnsupportedOperationException("Not supported"); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/BaseReduceService.java b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/BaseReduceService.java index 9efac85c937d..9b44e0c40598 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/BaseReduceService.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/BaseReduceService.java @@ -19,28 +19,15 @@ package org.apache.pinot.core.query.reduce; import com.google.common.util.concurrent.ThreadFactoryBuilder; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; -import java.util.function.LongConsumer; import javax.annotation.concurrent.ThreadSafe; -import org.apache.pinot.common.datatable.DataTable; -import org.apache.pinot.common.datatable.DataTable.MetadataKey; -import org.apache.pinot.common.metrics.BrokerMeter; -import org.apache.pinot.common.metrics.BrokerMetrics; -import org.apache.pinot.common.metrics.BrokerTimer; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.response.broker.BrokerResponseNative; -import org.apache.pinot.common.response.broker.QueryProcessingException; import org.apache.pinot.common.response.broker.ResultTable; import org.apache.pinot.core.query.request.context.QueryContext; -import org.apache.pinot.core.transport.ServerRoutingInstance; -import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.env.PinotConfiguration; import org.apache.pinot.spi.utils.CommonConstants; import org.slf4j.Logger; @@ -64,12 +51,15 @@ public abstract class BaseReduceService { protected final ExecutorService _reduceExecutorService; protected final int _maxReduceThreadsPerQuery; protected final int _groupByTrimThreshold; + protected final int _minGroupTrimSize; public BaseReduceService(PinotConfiguration config) { _maxReduceThreadsPerQuery = config.getProperty(CommonConstants.Broker.CONFIG_OF_MAX_REDUCE_THREADS_PER_QUERY, CommonConstants.Broker.DEFAULT_MAX_REDUCE_THREADS_PER_QUERY); _groupByTrimThreshold = config.getProperty(CommonConstants.Broker.CONFIG_OF_BROKER_GROUPBY_TRIM_THRESHOLD, CommonConstants.Broker.DEFAULT_BROKER_GROUPBY_TRIM_THRESHOLD); + _minGroupTrimSize = config.getProperty(CommonConstants.Broker.CONFIG_OF_BROKER_MIN_GROUP_TRIM_SIZE, + CommonConstants.Broker.DEFAULT_BROKER_MIN_GROUP_TRIM_SIZE); int numThreadsInExecutorService = Runtime.getRuntime().availableProcessors(); LOGGER.info("Initializing BrokerReduceService with {} threads, and {} max reduce threads.", @@ -120,240 +110,4 @@ protected static List getSelectExpressions(List _processingExceptions = new ArrayList<>(); - private final Map _traceInfo = new HashMap<>(); - private final boolean _enableTrace; - - private long _numDocsScanned = 0L; - private long _numEntriesScannedInFilter = 0L; - private long _numEntriesScannedPostFilter = 0L; - private long _numSegmentsQueried = 0L; - private long _numSegmentsProcessed = 0L; - private long _numSegmentsMatched = 0L; - private long _numConsumingSegmentsQueried = 0L; - private long _numConsumingSegmentsProcessed = 0L; - private long _numConsumingSegmentsMatched = 0L; - private long _minConsumingFreshnessTimeMs = Long.MAX_VALUE; - private long _numTotalDocs = 0L; - private long _offlineThreadCpuTimeNs = 0L; - private long _realtimeThreadCpuTimeNs = 0L; - private long _offlineSystemActivitiesCpuTimeNs = 0L; - private long _realtimeSystemActivitiesCpuTimeNs = 0L; - private long _offlineResponseSerializationCpuTimeNs = 0L; - private long _realtimeResponseSerializationCpuTimeNs = 0L; - private long _offlineTotalCpuTimeNs = 0L; - private long _realtimeTotalCpuTimeNs = 0L; - private long _numSegmentsPrunedByServer = 0L; - private long _numSegmentsPrunedInvalid = 0L; - private long _numSegmentsPrunedByLimit = 0L; - private long _numSegmentsPrunedByValue = 0L; - private long _explainPlanNumEmptyFilterSegments = 0L; - private long _explainPlanNumMatchAllFilterSegments = 0L; - private boolean _numGroupsLimitReached = false; - - protected ExecutionStatsAggregator(boolean enableTrace) { - _enableTrace = enableTrace; - } - - protected synchronized void aggregate(ServerRoutingInstance routingInstance, DataTable dataTable) { - Map metadata = dataTable.getMetadata(); - // Reduce on trace info. - if (_enableTrace) { - _traceInfo.put(routingInstance.getShortName(), metadata.get(MetadataKey.TRACE_INFO.getName())); - } - - // Reduce on exceptions. - Map exceptions = dataTable.getExceptions(); - for (int key : exceptions.keySet()) { - _processingExceptions.add(new QueryProcessingException(key, exceptions.get(key))); - } - - // Reduce on execution statistics. - String numDocsScannedString = metadata.get(MetadataKey.NUM_DOCS_SCANNED.getName()); - if (numDocsScannedString != null) { - _numDocsScanned += Long.parseLong(numDocsScannedString); - } - String numEntriesScannedInFilterString = metadata.get(MetadataKey.NUM_ENTRIES_SCANNED_IN_FILTER.getName()); - if (numEntriesScannedInFilterString != null) { - _numEntriesScannedInFilter += Long.parseLong(numEntriesScannedInFilterString); - } - String numEntriesScannedPostFilterString = metadata.get(MetadataKey.NUM_ENTRIES_SCANNED_POST_FILTER.getName()); - if (numEntriesScannedPostFilterString != null) { - _numEntriesScannedPostFilter += Long.parseLong(numEntriesScannedPostFilterString); - } - String numSegmentsQueriedString = metadata.get(MetadataKey.NUM_SEGMENTS_QUERIED.getName()); - if (numSegmentsQueriedString != null) { - _numSegmentsQueried += Long.parseLong(numSegmentsQueriedString); - } - - String numSegmentsProcessedString = metadata.get(MetadataKey.NUM_SEGMENTS_PROCESSED.getName()); - if (numSegmentsProcessedString != null) { - _numSegmentsProcessed += Long.parseLong(numSegmentsProcessedString); - } - String numSegmentsMatchedString = metadata.get(MetadataKey.NUM_SEGMENTS_MATCHED.getName()); - if (numSegmentsMatchedString != null) { - _numSegmentsMatched += Long.parseLong(numSegmentsMatchedString); - } - - String numConsumingSegmentsQueriedString = metadata.get(MetadataKey.NUM_CONSUMING_SEGMENTS_QUERIED.getName()); - if (numConsumingSegmentsQueriedString != null) { - _numConsumingSegmentsQueried += Long.parseLong(numConsumingSegmentsQueriedString); - } - - String numConsumingSegmentsProcessed = metadata.get(MetadataKey.NUM_CONSUMING_SEGMENTS_PROCESSED.getName()); - if (numConsumingSegmentsProcessed != null) { - _numConsumingSegmentsProcessed += Long.parseLong(numConsumingSegmentsProcessed); - } - - String numConsumingSegmentsMatched = metadata.get(MetadataKey.NUM_CONSUMING_SEGMENTS_MATCHED.getName()); - if (numConsumingSegmentsMatched != null) { - _numConsumingSegmentsMatched += Long.parseLong(numConsumingSegmentsMatched); - } - - String minConsumingFreshnessTimeMsString = metadata.get(MetadataKey.MIN_CONSUMING_FRESHNESS_TIME_MS.getName()); - if (minConsumingFreshnessTimeMsString != null) { - _minConsumingFreshnessTimeMs = - Math.min(Long.parseLong(minConsumingFreshnessTimeMsString), _minConsumingFreshnessTimeMs); - } - - String threadCpuTimeNsString = metadata.get(MetadataKey.THREAD_CPU_TIME_NS.getName()); - if (threadCpuTimeNsString != null) { - if (routingInstance.getTableType() == TableType.OFFLINE) { - _offlineThreadCpuTimeNs += Long.parseLong(threadCpuTimeNsString); - } else { - _realtimeThreadCpuTimeNs += Long.parseLong(threadCpuTimeNsString); - } - } - - String systemActivitiesCpuTimeNsString = metadata.get(MetadataKey.SYSTEM_ACTIVITIES_CPU_TIME_NS.getName()); - if (systemActivitiesCpuTimeNsString != null) { - if (routingInstance.getTableType() == TableType.OFFLINE) { - _offlineSystemActivitiesCpuTimeNs += Long.parseLong(systemActivitiesCpuTimeNsString); - } else { - _realtimeSystemActivitiesCpuTimeNs += Long.parseLong(systemActivitiesCpuTimeNsString); - } - } - - String responseSerializationCpuTimeNsString = metadata.get(MetadataKey.RESPONSE_SER_CPU_TIME_NS.getName()); - if (responseSerializationCpuTimeNsString != null) { - if (routingInstance.getTableType() == TableType.OFFLINE) { - _offlineResponseSerializationCpuTimeNs += Long.parseLong(responseSerializationCpuTimeNsString); - } else { - _realtimeResponseSerializationCpuTimeNs += Long.parseLong(responseSerializationCpuTimeNsString); - } - } - _offlineTotalCpuTimeNs = - _offlineThreadCpuTimeNs + _offlineSystemActivitiesCpuTimeNs + _offlineResponseSerializationCpuTimeNs; - _realtimeTotalCpuTimeNs = - _realtimeThreadCpuTimeNs + _realtimeSystemActivitiesCpuTimeNs + _realtimeResponseSerializationCpuTimeNs; - - withNotNullLongMetadata(metadata, MetadataKey.NUM_SEGMENTS_PRUNED_BY_SERVER, - l -> _numSegmentsPrunedByServer += l); - withNotNullLongMetadata(metadata, MetadataKey.NUM_SEGMENTS_PRUNED_INVALID, l -> _numSegmentsPrunedInvalid += l); - withNotNullLongMetadata(metadata, MetadataKey.NUM_SEGMENTS_PRUNED_BY_LIMIT, l -> _numSegmentsPrunedByLimit += l); - withNotNullLongMetadata(metadata, MetadataKey.NUM_SEGMENTS_PRUNED_BY_VALUE, l -> _numSegmentsPrunedByValue += l); - - String explainPlanNumEmptyFilterSegments = - metadata.get(MetadataKey.EXPLAIN_PLAN_NUM_EMPTY_FILTER_SEGMENTS.getName()); - if (explainPlanNumEmptyFilterSegments != null) { - _explainPlanNumEmptyFilterSegments += Long.parseLong(explainPlanNumEmptyFilterSegments); - } - - String explainPlanNumMatchAllFilterSegments = - metadata.get(MetadataKey.EXPLAIN_PLAN_NUM_MATCH_ALL_FILTER_SEGMENTS.getName()); - if (explainPlanNumMatchAllFilterSegments != null) { - _explainPlanNumMatchAllFilterSegments += Long.parseLong(explainPlanNumMatchAllFilterSegments); - } - - String numTotalDocsString = metadata.get(MetadataKey.TOTAL_DOCS.getName()); - if (numTotalDocsString != null) { - _numTotalDocs += Long.parseLong(numTotalDocsString); - } - _numGroupsLimitReached |= Boolean.parseBoolean(metadata.get(MetadataKey.NUM_GROUPS_LIMIT_REACHED.getName())); - } - - protected void setStats(String rawTableName, BrokerResponseNative brokerResponseNative, - BrokerMetrics brokerMetrics) { - // set exception - List processingExceptions = brokerResponseNative.getProcessingExceptions(); - processingExceptions.addAll(_processingExceptions); - - // add all trace. - if (_enableTrace) { - brokerResponseNative.getTraceInfo().putAll(_traceInfo); - } - - // Set execution statistics. - brokerResponseNative.setNumDocsScanned(_numDocsScanned); - brokerResponseNative.setNumEntriesScannedInFilter(_numEntriesScannedInFilter); - brokerResponseNative.setNumEntriesScannedPostFilter(_numEntriesScannedPostFilter); - brokerResponseNative.setNumSegmentsQueried(_numSegmentsQueried); - brokerResponseNative.setNumSegmentsProcessed(_numSegmentsProcessed); - brokerResponseNative.setNumSegmentsMatched(_numSegmentsMatched); - brokerResponseNative.setTotalDocs(_numTotalDocs); - brokerResponseNative.setNumGroupsLimitReached(_numGroupsLimitReached); - brokerResponseNative.setOfflineThreadCpuTimeNs(_offlineThreadCpuTimeNs); - brokerResponseNative.setRealtimeThreadCpuTimeNs(_realtimeThreadCpuTimeNs); - brokerResponseNative.setOfflineSystemActivitiesCpuTimeNs(_offlineSystemActivitiesCpuTimeNs); - brokerResponseNative.setRealtimeSystemActivitiesCpuTimeNs(_realtimeSystemActivitiesCpuTimeNs); - brokerResponseNative.setOfflineResponseSerializationCpuTimeNs(_offlineResponseSerializationCpuTimeNs); - brokerResponseNative.setRealtimeResponseSerializationCpuTimeNs(_realtimeResponseSerializationCpuTimeNs); - brokerResponseNative.setOfflineTotalCpuTimeNs(_offlineTotalCpuTimeNs); - brokerResponseNative.setRealtimeTotalCpuTimeNs(_realtimeTotalCpuTimeNs); - brokerResponseNative.setNumSegmentsPrunedByServer(_numSegmentsPrunedByServer); - brokerResponseNative.setNumSegmentsPrunedInvalid(_numSegmentsPrunedInvalid); - brokerResponseNative.setNumSegmentsPrunedByLimit(_numSegmentsPrunedByLimit); - brokerResponseNative.setNumSegmentsPrunedByValue(_numSegmentsPrunedByValue); - brokerResponseNative.setExplainPlanNumEmptyFilterSegments(_explainPlanNumEmptyFilterSegments); - brokerResponseNative.setExplainPlanNumMatchAllFilterSegments(_explainPlanNumMatchAllFilterSegments); - if (_numConsumingSegmentsQueried > 0) { - brokerResponseNative.setNumConsumingSegmentsQueried(_numConsumingSegmentsQueried); - } - if (_minConsumingFreshnessTimeMs != Long.MAX_VALUE) { - brokerResponseNative.setMinConsumingFreshnessTimeMs(_minConsumingFreshnessTimeMs); - } - brokerResponseNative.setNumConsumingSegmentsProcessed(_numConsumingSegmentsProcessed); - brokerResponseNative.setNumConsumingSegmentsMatched(_numConsumingSegmentsMatched); - - // Update broker metrics. - if (brokerMetrics != null) { - brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.DOCUMENTS_SCANNED, _numDocsScanned); - brokerMetrics - .addMeteredTableValue(rawTableName, BrokerMeter.ENTRIES_SCANNED_IN_FILTER, _numEntriesScannedInFilter); - brokerMetrics - .addMeteredTableValue(rawTableName, BrokerMeter.ENTRIES_SCANNED_POST_FILTER, _numEntriesScannedPostFilter); - brokerMetrics.addTimedTableValue(rawTableName, BrokerTimer.OFFLINE_THREAD_CPU_TIME_NS, _offlineThreadCpuTimeNs, - TimeUnit.NANOSECONDS); - brokerMetrics.addTimedTableValue(rawTableName, BrokerTimer.REALTIME_THREAD_CPU_TIME_NS, - _realtimeThreadCpuTimeNs, - TimeUnit.NANOSECONDS); - brokerMetrics.addTimedTableValue(rawTableName, BrokerTimer.OFFLINE_SYSTEM_ACTIVITIES_CPU_TIME_NS, - _offlineSystemActivitiesCpuTimeNs, TimeUnit.NANOSECONDS); - brokerMetrics.addTimedTableValue(rawTableName, BrokerTimer.REALTIME_SYSTEM_ACTIVITIES_CPU_TIME_NS, - _realtimeSystemActivitiesCpuTimeNs, TimeUnit.NANOSECONDS); - brokerMetrics.addTimedTableValue(rawTableName, BrokerTimer.OFFLINE_RESPONSE_SER_CPU_TIME_NS, - _offlineResponseSerializationCpuTimeNs, TimeUnit.NANOSECONDS); - brokerMetrics.addTimedTableValue(rawTableName, BrokerTimer.REALTIME_RESPONSE_SER_CPU_TIME_NS, - _realtimeResponseSerializationCpuTimeNs, TimeUnit.NANOSECONDS); - brokerMetrics.addTimedTableValue(rawTableName, BrokerTimer.OFFLINE_TOTAL_CPU_TIME_NS, _offlineTotalCpuTimeNs, - TimeUnit.NANOSECONDS); - brokerMetrics.addTimedTableValue(rawTableName, BrokerTimer.REALTIME_TOTAL_CPU_TIME_NS, _realtimeTotalCpuTimeNs, - TimeUnit.NANOSECONDS); - - if (_minConsumingFreshnessTimeMs != Long.MAX_VALUE) { - brokerMetrics.addTimedTableValue(rawTableName, BrokerTimer.FRESHNESS_LAG_MS, - System.currentTimeMillis() - _minConsumingFreshnessTimeMs, TimeUnit.MILLISECONDS); - } - } - } - - private void withNotNullLongMetadata(Map metadata, MetadataKey key, LongConsumer consumer) { - String strValue = metadata.get(key.getName()); - if (strValue != null) { - consumer.accept(Long.parseLong(strValue)); - } - } - } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/BrokerReduceService.java b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/BrokerReduceService.java index 74156f5503b9..03d801941e31 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/BrokerReduceService.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/BrokerReduceService.java @@ -18,22 +18,32 @@ */ package org.apache.pinot.core.query.reduce; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; +import java.util.List; import java.util.Map; -import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; import org.apache.pinot.common.datatable.DataTable; +import org.apache.pinot.common.exception.QueryException; +import org.apache.pinot.common.metrics.BrokerMeter; import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.common.response.broker.BrokerResponseNative; +import org.apache.pinot.common.response.broker.QueryProcessingException; import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.common.utils.config.QueryOptionsUtils; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.request.context.utils.QueryContextConverterUtils; import org.apache.pinot.core.transport.ServerRoutingInstance; import org.apache.pinot.core.util.GapfillUtils; import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.exception.BadQueryRequestException; +import org.apache.pinot.spi.exception.EarlyTerminationException; import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.builder.TableNameBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** @@ -42,12 +52,14 @@ */ @ThreadSafe public class BrokerReduceService extends BaseReduceService { + private static final Logger LOGGER = LoggerFactory.getLogger(BrokerReduceService.class); + public BrokerReduceService(PinotConfiguration config) { super(config); } public BrokerResponseNative reduceOnDataTable(BrokerRequest brokerRequest, BrokerRequest serverBrokerRequest, - Map dataTableMap, long reduceTimeOutMs, @Nullable BrokerMetrics brokerMetrics) { + Map dataTableMap, long reduceTimeOutMs, BrokerMetrics brokerMetrics) { if (dataTableMap.isEmpty()) { // Empty response. return BrokerResponseNative.empty(); @@ -61,7 +73,9 @@ public BrokerResponseNative reduceOnDataTable(BrokerRequest brokerRequest, Broke BrokerResponseNative brokerResponseNative = new BrokerResponseNative(); // Cache a data schema from data tables (try to cache one with data rows associated with it). - DataSchema cachedDataSchema = null; + DataSchema dataSchemaFromEmptyDataTable = null; + DataSchema dataSchemaFromNonEmptyDataTable = null; + List serversWithConflictingDataSchema = new ArrayList<>(); // Process server response metadata. Iterator> iterator = dataTableMap.entrySet().iterator(); @@ -79,12 +93,22 @@ public BrokerResponseNative reduceOnDataTable(BrokerRequest brokerRequest, Broke } else { // Try to cache a data table with data rows inside, or cache one with data schema inside. if (dataTable.getNumberOfRows() == 0) { - if (cachedDataSchema == null) { - cachedDataSchema = dataSchema; + if (dataSchemaFromEmptyDataTable == null) { + dataSchemaFromEmptyDataTable = dataSchema; } iterator.remove(); } else { - cachedDataSchema = dataSchema; + if (dataSchemaFromNonEmptyDataTable == null) { + dataSchemaFromNonEmptyDataTable = dataSchema; + } else { + // Remove data tables with conflicting data schema. + // NOTE: Only compare the column data types, since the column names (string representation of expression) + // can change across different versions. + if (!Arrays.equals(dataSchema.getColumnDataTypes(), dataSchemaFromNonEmptyDataTable.getColumnDataTypes())) { + serversWithConflictingDataSchema.add(entry.getKey()); + iterator.remove(); + } + } } } } @@ -95,25 +119,56 @@ public BrokerResponseNative reduceOnDataTable(BrokerRequest brokerRequest, Broke // Set execution statistics and Update broker metrics. aggregator.setStats(rawTableName, brokerResponseNative, brokerMetrics); + // Report the servers with conflicting data schema. + if (!serversWithConflictingDataSchema.isEmpty()) { + String errorMessage = + String.format("%s: responses for table: %s from servers: %s got dropped due to data schema inconsistency.", + QueryException.MERGE_RESPONSE_ERROR.getMessage(), tableName, serversWithConflictingDataSchema); + LOGGER.warn(errorMessage); + brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.RESPONSE_MERGE_EXCEPTIONS, 1); + brokerResponseNative.addException( + new QueryProcessingException(QueryException.MERGE_RESPONSE_ERROR_CODE, errorMessage)); + } + // NOTE: When there is no cached data schema, that means all servers encountered exception. In such case, return the // response with metadata only. + DataSchema cachedDataSchema = + dataSchemaFromNonEmptyDataTable != null ? dataSchemaFromNonEmptyDataTable : dataSchemaFromEmptyDataTable; if (cachedDataSchema == null) { return brokerResponseNative; } QueryContext serverQueryContext = QueryContextConverterUtils.getQueryContext(serverBrokerRequest.getPinotQuery()); DataTableReducer dataTableReducer = ResultReducerFactory.getResultReducer(serverQueryContext); - dataTableReducer.reduceAndSetResults(rawTableName, cachedDataSchema, dataTableMap, brokerResponseNative, - new DataTableReducerContext(_reduceExecutorService, _maxReduceThreadsPerQuery, reduceTimeOutMs, - _groupByTrimThreshold), brokerMetrics); + + Integer minGroupTrimSizeQueryOption = null; + Integer groupTrimThresholdQueryOption = null; + if (queryOptions != null) { + minGroupTrimSizeQueryOption = QueryOptionsUtils.getMinBrokerGroupTrimSize(queryOptions); + groupTrimThresholdQueryOption = QueryOptionsUtils.getGroupTrimThreshold(queryOptions); + } + int minGroupTrimSize = minGroupTrimSizeQueryOption != null ? minGroupTrimSizeQueryOption : _minGroupTrimSize; + int groupTrimThreshold = + groupTrimThresholdQueryOption != null ? groupTrimThresholdQueryOption : _groupByTrimThreshold; + + try { + dataTableReducer.reduceAndSetResults(rawTableName, cachedDataSchema, dataTableMap, brokerResponseNative, + new DataTableReducerContext(_reduceExecutorService, _maxReduceThreadsPerQuery, reduceTimeOutMs, + groupTrimThreshold, minGroupTrimSize), brokerMetrics); + } catch (EarlyTerminationException e) { + brokerResponseNative.addException( + new QueryProcessingException(QueryException.QUERY_CANCELLATION_ERROR_CODE, e.toString())); + } QueryContext queryContext; if (brokerRequest == serverBrokerRequest) { queryContext = serverQueryContext; } else { queryContext = QueryContextConverterUtils.getQueryContext(brokerRequest.getPinotQuery()); GapfillUtils.GapfillType gapfillType = GapfillUtils.getGapfillType(queryContext); - BaseGapfillProcessor gapfillProcessor = - GapfillProcessorFactory.getGapfillProcessor(queryContext, gapfillType); + if (gapfillType == null) { + throw new BadQueryRequestException("Nested query is not supported without gapfill"); + } + BaseGapfillProcessor gapfillProcessor = GapfillProcessorFactory.getGapfillProcessor(queryContext, gapfillType); gapfillProcessor.process(brokerResponseNative); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/DataTableReducerContext.java b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/DataTableReducerContext.java index d4946df81a16..d4b69e6c2132 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/DataTableReducerContext.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/DataTableReducerContext.java @@ -31,6 +31,7 @@ public class DataTableReducerContext { private final long _reduceTimeOutMs; // used for SQL GROUP BY private final int _groupByTrimThreshold; + private final int _minGroupTrimSize; /** * Constructor for the class. @@ -41,11 +42,12 @@ public class DataTableReducerContext { * @param groupByTrimThreshold trim threshold for SQL group by */ public DataTableReducerContext(ExecutorService executorService, int maxReduceThreadsPerQuery, long reduceTimeOutMs, - int groupByTrimThreshold) { + int groupByTrimThreshold, int minGroupTrimSize) { _executorService = executorService; _maxReduceThreadsPerQuery = maxReduceThreadsPerQuery; _reduceTimeOutMs = reduceTimeOutMs; _groupByTrimThreshold = groupByTrimThreshold; + _minGroupTrimSize = minGroupTrimSize; } public ExecutorService getExecutorService() { @@ -63,4 +65,8 @@ public long getReduceTimeOutMs() { public int getGroupByTrimThreshold() { return _groupByTrimThreshold; } + + public int getMinGroupTrimSize() { + return _minGroupTrimSize; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/DistinctDataTableReducer.java b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/DistinctDataTableReducer.java index af25afe24ef6..4553776963ee 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/DistinctDataTableReducer.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/DistinctDataTableReducer.java @@ -19,8 +19,6 @@ package org.apache.pinot.core.query.reduce; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -30,13 +28,12 @@ import org.apache.pinot.common.response.broker.ResultTable; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; -import org.apache.pinot.core.common.ObjectSerDeUtils; import org.apache.pinot.core.data.table.Record; -import org.apache.pinot.core.query.aggregation.function.DistinctAggregationFunction; import org.apache.pinot.core.query.distinct.DistinctTable; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.selection.SelectionOperatorUtils; import org.apache.pinot.core.transport.ServerRoutingInstance; +import org.apache.pinot.spi.trace.Tracing; import org.roaringbitmap.RoaringBitmap; @@ -44,89 +41,76 @@ * Helper class to reduce data tables and set results of distinct query into the BrokerResponseNative */ public class DistinctDataTableReducer implements DataTableReducer { - private final DistinctAggregationFunction _distinctAggregationFunction; private final QueryContext _queryContext; - DistinctDataTableReducer(DistinctAggregationFunction distinctAggregationFunction, QueryContext queryContext) { - _distinctAggregationFunction = distinctAggregationFunction; + public DistinctDataTableReducer(QueryContext queryContext) { _queryContext = queryContext; } - /** - * Reduces and sets results of distinct into ResultTable. - */ @Override public void reduceAndSetResults(String tableName, DataSchema dataSchema, Map dataTableMap, BrokerResponseNative brokerResponseNative, DataTableReducerContext reducerContext, BrokerMetrics brokerMetrics) { - // DISTINCT is implemented as an aggregation function in the execution engine. Just like - // other aggregation functions, DISTINCT returns its result as a single object - // (of type DistinctTable) serialized by the server into the DataTable and deserialized - // by the broker from the DataTable. So there should be exactly 1 row and 1 column and that - // column value should be the serialized DistinctTable -- so essentially it is a DataTable - // inside a DataTable + dataSchema = ReducerDataSchemaUtils.canonicalizeDataSchemaForDistinct(_queryContext, dataSchema); + DistinctTable distinctTable = + new DistinctTable(dataSchema, _queryContext.getOrderByExpressions(), _queryContext.getLimit(), + _queryContext.isNullHandlingEnabled()); + if (distinctTable.hasOrderBy()) { + addToOrderByDistinctTable(dataSchema, dataTableMap, distinctTable); + } else { + addToNonOrderByDistinctTable(dataSchema, dataTableMap, distinctTable); + } + brokerResponseNative.setResultTable(reduceToResultTable(distinctTable)); + } - // Gather all non-empty DistinctTables - // TODO: until we upgrade to newer version of pinot, we have to keep both code path. remove after 0.12.0 release. - // This is to work with server rolling upgrade when partially returned as DistinctTable Obj and partially regular - // DataTable; if all returns are DataTable we can directly merge with priority queue (with dedup). - List nonEmptyDistinctTables = new ArrayList<>(dataTableMap.size()); + private void addToOrderByDistinctTable(DataSchema dataSchema, Map dataTableMap, + DistinctTable distinctTable) { for (DataTable dataTable : dataTableMap.values()) { - // Do not use the cached data schema because it might be either single object (legacy) or normal data table - dataSchema = dataTable.getDataSchema(); + Tracing.ThreadAccountantOps.sampleAndCheckInterruption(); int numColumns = dataSchema.size(); - if (numColumns == 1 && dataSchema.getColumnDataType(0) == ColumnDataType.OBJECT) { - // DistinctTable is still being returned as a single object - DataTable.CustomObject customObject = dataTable.getCustomObject(0, 0); - assert customObject != null; - DistinctTable distinctTable = ObjectSerDeUtils.deserialize(customObject); - if (!distinctTable.isEmpty()) { - nonEmptyDistinctTables.add(distinctTable); + int numRows = dataTable.getNumberOfRows(); + if (_queryContext.isNullHandlingEnabled()) { + RoaringBitmap[] nullBitmaps = new RoaringBitmap[numColumns]; + for (int coldId = 0; coldId < numColumns; coldId++) { + nullBitmaps[coldId] = dataTable.getNullRowIds(coldId); + } + for (int rowId = 0; rowId < numRows; rowId++) { + distinctTable.addWithOrderBy(new Record( + SelectionOperatorUtils.extractRowFromDataTableWithNullHandling(dataTable, rowId, nullBitmaps))); } } else { - // DistinctTable is being returned as normal data table - int numRows = dataTable.getNumberOfRows(); - if (numRows > 0) { - List records = new ArrayList<>(numRows); - if (_queryContext.isNullHandlingEnabled()) { - RoaringBitmap[] nullBitmaps = new RoaringBitmap[numColumns]; - for (int coldId = 0; coldId < numColumns; coldId++) { - nullBitmaps[coldId] = dataTable.getNullRowIds(coldId); - } - for (int rowId = 0; rowId < numRows; rowId++) { - records.add(new Record( - SelectionOperatorUtils.extractRowFromDataTableWithNullHandling(dataTable, rowId, nullBitmaps))); - } - } else { - for (int rowId = 0; rowId < numRows; rowId++) { - records.add(new Record(SelectionOperatorUtils.extractRowFromDataTable(dataTable, rowId))); - } - } - nonEmptyDistinctTables.add(new DistinctTable(dataSchema, records)); + for (int rowId = 0; rowId < numRows; rowId++) { + distinctTable.addWithOrderBy(new Record(SelectionOperatorUtils.extractRowFromDataTable(dataTable, rowId))); } } } + } - if (nonEmptyDistinctTables.isEmpty()) { - // All the DistinctTables are empty, construct an empty response - // TODO: This returns schema with all STRING data types. - // There's no way currently to get the data types of the distinct columns for empty results - String[] columns = _distinctAggregationFunction.getColumns(); - - int numColumns = columns.length; - ColumnDataType[] columnDataTypes = new ColumnDataType[numColumns]; - Arrays.fill(columnDataTypes, ColumnDataType.STRING); - brokerResponseNative.setResultTable( - new ResultTable(new DataSchema(columns, columnDataTypes), Collections.emptyList())); - } else { - // Construct a main DistinctTable and merge all non-empty DistinctTables into it - DistinctTable mainDistinctTable = new DistinctTable(nonEmptyDistinctTables.get(0).getDataSchema(), - _distinctAggregationFunction.getOrderByExpressions(), _distinctAggregationFunction.getLimit(), - _queryContext.isNullHandlingEnabled()); - for (DistinctTable distinctTable : nonEmptyDistinctTables) { - mainDistinctTable.mergeTable(distinctTable); + private void addToNonOrderByDistinctTable(DataSchema dataSchema, Map dataTableMap, + DistinctTable distinctTable) { + for (DataTable dataTable : dataTableMap.values()) { + Tracing.ThreadAccountantOps.sampleAndCheckInterruption(); + int numColumns = dataSchema.size(); + int numRows = dataTable.getNumberOfRows(); + if (_queryContext.isNullHandlingEnabled()) { + RoaringBitmap[] nullBitmaps = new RoaringBitmap[numColumns]; + for (int coldId = 0; coldId < numColumns; coldId++) { + nullBitmaps[coldId] = dataTable.getNullRowIds(coldId); + } + for (int rowId = 0; rowId < numRows; rowId++) { + if (distinctTable.addWithoutOrderBy(new Record( + SelectionOperatorUtils.extractRowFromDataTableWithNullHandling(dataTable, rowId, nullBitmaps)))) { + return; + } + } + } else { + for (int rowId = 0; rowId < numRows; rowId++) { + if (distinctTable.addWithoutOrderBy( + new Record(SelectionOperatorUtils.extractRowFromDataTable(dataTable, rowId)))) { + return; + } + } } - brokerResponseNative.setResultTable(reduceToResultTable(mainDistinctTable)); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/ExecutionStatsAggregator.java b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/ExecutionStatsAggregator.java new file mode 100644 index 000000000000..322af48d7bc8 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/ExecutionStatsAggregator.java @@ -0,0 +1,279 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.reduce; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.LongConsumer; +import org.apache.pinot.common.datatable.DataTable; +import org.apache.pinot.common.metrics.BrokerMeter; +import org.apache.pinot.common.metrics.BrokerMetrics; +import org.apache.pinot.common.metrics.BrokerTimer; +import org.apache.pinot.common.response.broker.BrokerResponseNative; +import org.apache.pinot.common.response.broker.QueryProcessingException; +import org.apache.pinot.core.transport.ServerRoutingInstance; +import org.apache.pinot.spi.config.table.TableType; + + +public class ExecutionStatsAggregator { + private final List _processingExceptions = new ArrayList<>(); + private final Map _traceInfo = new HashMap<>(); + private final boolean _enableTrace; + + private long _numDocsScanned = 0L; + private long _numEntriesScannedInFilter = 0L; + private long _numEntriesScannedPostFilter = 0L; + private long _numSegmentsQueried = 0L; + private long _numSegmentsProcessed = 0L; + private long _numSegmentsMatched = 0L; + private long _numConsumingSegmentsQueried = 0L; + private long _numConsumingSegmentsProcessed = 0L; + private long _numConsumingSegmentsMatched = 0L; + private long _minConsumingFreshnessTimeMs = Long.MAX_VALUE; + private long _numTotalDocs = 0L; + private long _offlineThreadCpuTimeNs = 0L; + private long _realtimeThreadCpuTimeNs = 0L; + private long _offlineSystemActivitiesCpuTimeNs = 0L; + private long _realtimeSystemActivitiesCpuTimeNs = 0L; + private long _offlineResponseSerializationCpuTimeNs = 0L; + private long _realtimeResponseSerializationCpuTimeNs = 0L; + private long _offlineTotalCpuTimeNs = 0L; + private long _realtimeTotalCpuTimeNs = 0L; + private long _numSegmentsPrunedByServer = 0L; + private long _numSegmentsPrunedInvalid = 0L; + private long _numSegmentsPrunedByLimit = 0L; + private long _numSegmentsPrunedByValue = 0L; + private long _explainPlanNumEmptyFilterSegments = 0L; + private long _explainPlanNumMatchAllFilterSegments = 0L; + private boolean _numGroupsLimitReached = false; + + public ExecutionStatsAggregator(boolean enableTrace) { + _enableTrace = enableTrace; + } + + public void aggregate(ServerRoutingInstance routingInstance, DataTable dataTable) { + TableType tableType = routingInstance.getTableType(); + String instanceName = routingInstance.getShortName(); + Map metadata = dataTable.getMetadata(); + + // Reduce on trace info. + if (_enableTrace && metadata.containsKey(DataTable.MetadataKey.TRACE_INFO.getName())) { + _traceInfo.put(instanceName, metadata.get(DataTable.MetadataKey.TRACE_INFO.getName())); + } + + // Reduce on exceptions. + Map exceptions = dataTable.getExceptions(); + for (Map.Entry entry : exceptions.entrySet()) { + _processingExceptions.add(new QueryProcessingException(entry.getKey(), entry.getValue())); + } + + // Reduce on execution statistics. + String numDocsScannedString = metadata.get(DataTable.MetadataKey.NUM_DOCS_SCANNED.getName()); + if (numDocsScannedString != null) { + _numDocsScanned += Long.parseLong(numDocsScannedString); + } + String numEntriesScannedInFilterString = + metadata.get(DataTable.MetadataKey.NUM_ENTRIES_SCANNED_IN_FILTER.getName()); + if (numEntriesScannedInFilterString != null) { + _numEntriesScannedInFilter += Long.parseLong(numEntriesScannedInFilterString); + } + String numEntriesScannedPostFilterString = + metadata.get(DataTable.MetadataKey.NUM_ENTRIES_SCANNED_POST_FILTER.getName()); + if (numEntriesScannedPostFilterString != null) { + _numEntriesScannedPostFilter += Long.parseLong(numEntriesScannedPostFilterString); + } + String numSegmentsQueriedString = metadata.get(DataTable.MetadataKey.NUM_SEGMENTS_QUERIED.getName()); + if (numSegmentsQueriedString != null) { + _numSegmentsQueried += Long.parseLong(numSegmentsQueriedString); + } + + String numSegmentsProcessedString = metadata.get(DataTable.MetadataKey.NUM_SEGMENTS_PROCESSED.getName()); + if (numSegmentsProcessedString != null) { + _numSegmentsProcessed += Long.parseLong(numSegmentsProcessedString); + } + String numSegmentsMatchedString = metadata.get(DataTable.MetadataKey.NUM_SEGMENTS_MATCHED.getName()); + if (numSegmentsMatchedString != null) { + _numSegmentsMatched += Long.parseLong(numSegmentsMatchedString); + } + + String numConsumingSegmentsQueriedString = + metadata.get(DataTable.MetadataKey.NUM_CONSUMING_SEGMENTS_QUERIED.getName()); + if (numConsumingSegmentsQueriedString != null) { + _numConsumingSegmentsQueried += Long.parseLong(numConsumingSegmentsQueriedString); + } + + String numConsumingSegmentsProcessed = + metadata.get(DataTable.MetadataKey.NUM_CONSUMING_SEGMENTS_PROCESSED.getName()); + if (numConsumingSegmentsProcessed != null) { + _numConsumingSegmentsProcessed += Long.parseLong(numConsumingSegmentsProcessed); + } + + String numConsumingSegmentsMatched = metadata.get(DataTable.MetadataKey.NUM_CONSUMING_SEGMENTS_MATCHED.getName()); + if (numConsumingSegmentsMatched != null) { + _numConsumingSegmentsMatched += Long.parseLong(numConsumingSegmentsMatched); + } + + String minConsumingFreshnessTimeMsString = + metadata.get(DataTable.MetadataKey.MIN_CONSUMING_FRESHNESS_TIME_MS.getName()); + if (minConsumingFreshnessTimeMsString != null) { + _minConsumingFreshnessTimeMs = + Math.min(Long.parseLong(minConsumingFreshnessTimeMsString), _minConsumingFreshnessTimeMs); + } + + String threadCpuTimeNsString = metadata.get(DataTable.MetadataKey.THREAD_CPU_TIME_NS.getName()); + if (tableType != null && threadCpuTimeNsString != null) { + if (tableType == TableType.OFFLINE) { + _offlineThreadCpuTimeNs += Long.parseLong(threadCpuTimeNsString); + } else { + _realtimeThreadCpuTimeNs += Long.parseLong(threadCpuTimeNsString); + } + } + + String systemActivitiesCpuTimeNsString = + metadata.get(DataTable.MetadataKey.SYSTEM_ACTIVITIES_CPU_TIME_NS.getName()); + if (tableType != null && systemActivitiesCpuTimeNsString != null) { + if (tableType == TableType.OFFLINE) { + _offlineSystemActivitiesCpuTimeNs += Long.parseLong(systemActivitiesCpuTimeNsString); + } else { + _realtimeSystemActivitiesCpuTimeNs += Long.parseLong(systemActivitiesCpuTimeNsString); + } + } + + String responseSerializationCpuTimeNsString = + metadata.get(DataTable.MetadataKey.RESPONSE_SER_CPU_TIME_NS.getName()); + if (tableType != null && responseSerializationCpuTimeNsString != null) { + if (tableType == TableType.OFFLINE) { + _offlineResponseSerializationCpuTimeNs += Long.parseLong(responseSerializationCpuTimeNsString); + } else { + _realtimeResponseSerializationCpuTimeNs += Long.parseLong(responseSerializationCpuTimeNsString); + } + } + _offlineTotalCpuTimeNs = + _offlineThreadCpuTimeNs + _offlineSystemActivitiesCpuTimeNs + _offlineResponseSerializationCpuTimeNs; + _realtimeTotalCpuTimeNs = + _realtimeThreadCpuTimeNs + _realtimeSystemActivitiesCpuTimeNs + _realtimeResponseSerializationCpuTimeNs; + + withNotNullLongMetadata(metadata, DataTable.MetadataKey.NUM_SEGMENTS_PRUNED_BY_SERVER, + l -> _numSegmentsPrunedByServer += l); + withNotNullLongMetadata(metadata, DataTable.MetadataKey.NUM_SEGMENTS_PRUNED_INVALID, + l -> _numSegmentsPrunedInvalid += l); + withNotNullLongMetadata(metadata, DataTable.MetadataKey.NUM_SEGMENTS_PRUNED_BY_LIMIT, + l -> _numSegmentsPrunedByLimit += l); + withNotNullLongMetadata(metadata, DataTable.MetadataKey.NUM_SEGMENTS_PRUNED_BY_VALUE, + l -> _numSegmentsPrunedByValue += l); + + String explainPlanNumEmptyFilterSegments = + metadata.get(DataTable.MetadataKey.EXPLAIN_PLAN_NUM_EMPTY_FILTER_SEGMENTS.getName()); + if (explainPlanNumEmptyFilterSegments != null) { + _explainPlanNumEmptyFilterSegments += Long.parseLong(explainPlanNumEmptyFilterSegments); + } + + String explainPlanNumMatchAllFilterSegments = + metadata.get(DataTable.MetadataKey.EXPLAIN_PLAN_NUM_MATCH_ALL_FILTER_SEGMENTS.getName()); + if (explainPlanNumMatchAllFilterSegments != null) { + _explainPlanNumMatchAllFilterSegments += Long.parseLong(explainPlanNumMatchAllFilterSegments); + } + + String numTotalDocsString = metadata.get(DataTable.MetadataKey.TOTAL_DOCS.getName()); + if (numTotalDocsString != null) { + _numTotalDocs += Long.parseLong(numTotalDocsString); + } + + _numGroupsLimitReached |= + Boolean.parseBoolean(metadata.get(DataTable.MetadataKey.NUM_GROUPS_LIMIT_REACHED.getName())); + } + + public void setStats(String rawTableName, BrokerResponseNative brokerResponseNative, BrokerMetrics brokerMetrics) { + // set exception + List processingExceptions = brokerResponseNative.getExceptions(); + processingExceptions.addAll(_processingExceptions); + + // add all trace. + if (_enableTrace) { + brokerResponseNative.getTraceInfo().putAll(_traceInfo); + } + + // Set execution statistics. + brokerResponseNative.setNumDocsScanned(_numDocsScanned); + brokerResponseNative.setNumEntriesScannedInFilter(_numEntriesScannedInFilter); + brokerResponseNative.setNumEntriesScannedPostFilter(_numEntriesScannedPostFilter); + brokerResponseNative.setNumSegmentsQueried(_numSegmentsQueried); + brokerResponseNative.setNumSegmentsProcessed(_numSegmentsProcessed); + brokerResponseNative.setNumSegmentsMatched(_numSegmentsMatched); + brokerResponseNative.setTotalDocs(_numTotalDocs); + brokerResponseNative.setNumGroupsLimitReached(_numGroupsLimitReached); + brokerResponseNative.setOfflineThreadCpuTimeNs(_offlineThreadCpuTimeNs); + brokerResponseNative.setRealtimeThreadCpuTimeNs(_realtimeThreadCpuTimeNs); + brokerResponseNative.setOfflineSystemActivitiesCpuTimeNs(_offlineSystemActivitiesCpuTimeNs); + brokerResponseNative.setRealtimeSystemActivitiesCpuTimeNs(_realtimeSystemActivitiesCpuTimeNs); + brokerResponseNative.setOfflineResponseSerializationCpuTimeNs(_offlineResponseSerializationCpuTimeNs); + brokerResponseNative.setRealtimeResponseSerializationCpuTimeNs(_realtimeResponseSerializationCpuTimeNs); + brokerResponseNative.setNumSegmentsPrunedByServer(_numSegmentsPrunedByServer); + brokerResponseNative.setNumSegmentsPrunedInvalid(_numSegmentsPrunedInvalid); + brokerResponseNative.setNumSegmentsPrunedByLimit(_numSegmentsPrunedByLimit); + brokerResponseNative.setNumSegmentsPrunedByValue(_numSegmentsPrunedByValue); + brokerResponseNative.setExplainPlanNumEmptyFilterSegments(_explainPlanNumEmptyFilterSegments); + brokerResponseNative.setExplainPlanNumMatchAllFilterSegments(_explainPlanNumMatchAllFilterSegments); + if (_numConsumingSegmentsQueried > 0) { + brokerResponseNative.setNumConsumingSegmentsQueried(_numConsumingSegmentsQueried); + } + if (_minConsumingFreshnessTimeMs != Long.MAX_VALUE) { + brokerResponseNative.setMinConsumingFreshnessTimeMs(_minConsumingFreshnessTimeMs); + } + brokerResponseNative.setNumConsumingSegmentsProcessed(_numConsumingSegmentsProcessed); + brokerResponseNative.setNumConsumingSegmentsMatched(_numConsumingSegmentsMatched); + + // Update broker metrics. + brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.DOCUMENTS_SCANNED, _numDocsScanned); + brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.ENTRIES_SCANNED_IN_FILTER, _numEntriesScannedInFilter); + brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.ENTRIES_SCANNED_POST_FILTER, + _numEntriesScannedPostFilter); + brokerMetrics.addTimedTableValue(rawTableName, BrokerTimer.OFFLINE_THREAD_CPU_TIME_NS, _offlineThreadCpuTimeNs, + TimeUnit.NANOSECONDS); + brokerMetrics.addTimedTableValue(rawTableName, BrokerTimer.REALTIME_THREAD_CPU_TIME_NS, _realtimeThreadCpuTimeNs, + TimeUnit.NANOSECONDS); + brokerMetrics.addTimedTableValue(rawTableName, BrokerTimer.OFFLINE_SYSTEM_ACTIVITIES_CPU_TIME_NS, + _offlineSystemActivitiesCpuTimeNs, TimeUnit.NANOSECONDS); + brokerMetrics.addTimedTableValue(rawTableName, BrokerTimer.REALTIME_SYSTEM_ACTIVITIES_CPU_TIME_NS, + _realtimeSystemActivitiesCpuTimeNs, TimeUnit.NANOSECONDS); + brokerMetrics.addTimedTableValue(rawTableName, BrokerTimer.OFFLINE_RESPONSE_SER_CPU_TIME_NS, + _offlineResponseSerializationCpuTimeNs, TimeUnit.NANOSECONDS); + brokerMetrics.addTimedTableValue(rawTableName, BrokerTimer.REALTIME_RESPONSE_SER_CPU_TIME_NS, + _realtimeResponseSerializationCpuTimeNs, TimeUnit.NANOSECONDS); + brokerMetrics.addTimedTableValue(rawTableName, BrokerTimer.OFFLINE_TOTAL_CPU_TIME_NS, _offlineTotalCpuTimeNs, + TimeUnit.NANOSECONDS); + brokerMetrics.addTimedTableValue(rawTableName, BrokerTimer.REALTIME_TOTAL_CPU_TIME_NS, _realtimeTotalCpuTimeNs, + TimeUnit.NANOSECONDS); + + if (_minConsumingFreshnessTimeMs != Long.MAX_VALUE) { + brokerMetrics.addTimedTableValue(rawTableName, BrokerTimer.FRESHNESS_LAG_MS, + System.currentTimeMillis() - _minConsumingFreshnessTimeMs, TimeUnit.MILLISECONDS); + } + } + + private void withNotNullLongMetadata(Map metadata, DataTable.MetadataKey key, LongConsumer consumer) { + String strValue = metadata.get(key.getName()); + if (strValue != null) { + consumer.accept(Long.parseLong(strValue)); + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GapfillFilterHandler.java b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GapfillFilterHandler.java index 780de965cd0d..6f694cc04e31 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GapfillFilterHandler.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GapfillFilterHandler.java @@ -67,7 +67,7 @@ public ValueExtractor getValueExtractor(ExpressionContext expression) { expression = GapfillUtils.stripGapfill(expression); if (expression.getType() == ExpressionContext.Type.LITERAL) { // Literal - return new LiteralValueExtractor(expression.getLiteralString()); + return new LiteralValueExtractor(expression.getLiteral().getStringValue()); } if (expression.getType() == ExpressionContext.Type.IDENTIFIER) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GapfillProcessor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GapfillProcessor.java index 22b1bd91631a..fbe10c8a86be 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GapfillProcessor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GapfillProcessor.java @@ -34,6 +34,7 @@ import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.common.BlockValSet; import org.apache.pinot.core.data.table.Key; +import org.apache.pinot.core.operator.docvalsets.RowBasedBlockValSet; import org.apache.pinot.core.query.aggregation.function.AggregationFunction; import org.apache.pinot.core.query.aggregation.function.AggregationFunctionFactory; import org.apache.pinot.core.query.aggregation.function.CountAggregationFunction; @@ -272,7 +273,8 @@ private List aggregateGapfilledData(Object timeCol, List buc Map blockValSetMap = new HashMap<>(); for (int i = 1; i < dataSchema.getColumnNames().length; i++) { blockValSetMap.put(ExpressionContext.forIdentifier(dataSchema.getColumnName(i)), - new RowBasedBlockValSet(dataSchema.getColumnDataType(i), bucketedRows, i)); + new RowBasedBlockValSet(dataSchema.getColumnDataType(i), bucketedRows, i, + _queryContext.isNullHandlingEnabled())); } for (int i = 0; i < _queryContext.getSelectExpressions().size(); i++) { @@ -280,7 +282,7 @@ private List aggregateGapfilledData(Object timeCol, List buc if (expressionContext.getType() == ExpressionContext.Type.FUNCTION) { FunctionContext functionContext = expressionContext.getFunction(); AggregationFunction aggregationFunction = - AggregationFunctionFactory.getAggregationFunction(functionContext, _queryContext); + AggregationFunctionFactory.getAggregationFunction(functionContext, _queryContext.isNullHandlingEnabled()); GroupByResultHolder groupByResultHolder = aggregationFunction.createGroupByResultHolder(groupKeyIndexes.size(), groupKeyIndexes.size()); if (aggregationFunction instanceof CountAggregationFunction) { @@ -305,10 +307,10 @@ private Object getFillValue(int columnIndex, String columnName, Object key, Colu if (expressionContext != null && expressionContext.getFunction() != null && GapfillUtils .isFill(expressionContext)) { List args = expressionContext.getFunction().getArguments(); - if (args.get(1).getLiteralString() == null) { + if (args.get(1).getLiteral() == null) { throw new UnsupportedOperationException("Wrong Sql."); } - GapfillUtils.FillType fillType = GapfillUtils.FillType.valueOf(args.get(1).getLiteralString()); + GapfillUtils.FillType fillType = GapfillUtils.FillType.valueOf(args.get(1).getLiteral().getStringValue()); if (fillType == GapfillUtils.FillType.FILL_DEFAULT_VALUE) { // TODO: may fill the default value from sql in the future. return GapfillUtils.getDefaultValue(dataType); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GroupByDataTableReducer.java b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GroupByDataTableReducer.java index 960cf1cb070e..09b4d6a15615 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GroupByDataTableReducer.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/GroupByDataTableReducer.java @@ -18,7 +18,11 @@ */ package org.apache.pinot.core.query.reduce; -import com.google.common.base.Preconditions; +import it.unimi.dsi.fastutil.doubles.DoubleArrayList; +import it.unimi.dsi.fastutil.floats.FloatArrayList; +import it.unimi.dsi.fastutil.ints.IntArrayList; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collection; @@ -30,6 +34,10 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; +import org.apache.commons.lang3.StringUtils; +import org.apache.pinot.common.CustomObject; +import org.apache.pinot.common.Utils; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.exception.QueryException; import org.apache.pinot.common.metrics.BrokerGauge; @@ -52,36 +60,39 @@ import org.apache.pinot.core.query.aggregation.function.AggregationFunction; import org.apache.pinot.core.query.aggregation.function.AggregationFunctionUtils; import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.query.utils.rewriter.ResultRewriteUtils; +import org.apache.pinot.core.query.utils.rewriter.RewriterResult; import org.apache.pinot.core.transport.ServerRoutingInstance; import org.apache.pinot.core.util.GroupByUtils; import org.apache.pinot.core.util.trace.TraceRunnable; -import org.apache.pinot.spi.utils.LoopUtils; +import org.apache.pinot.spi.accounting.ThreadExecutionContext; +import org.apache.pinot.spi.accounting.ThreadResourceUsageProvider; +import org.apache.pinot.spi.exception.EarlyTerminationException; +import org.apache.pinot.spi.trace.Tracing; import org.roaringbitmap.RoaringBitmap; /** * Helper class to reduce data tables and set group by results into the BrokerResponseNative */ -@SuppressWarnings({"rawtypes", "unchecked"}) +@SuppressWarnings("rawtypes") public class GroupByDataTableReducer implements DataTableReducer { private static final int MIN_DATA_TABLES_FOR_CONCURRENT_REDUCE = 2; // TBD, find a better value. - private static final int MAX_ROWS_UPSERT_PER_INTERRUPTION_CHECK = 10_000; private final QueryContext _queryContext; private final AggregationFunction[] _aggregationFunctions; private final int _numAggregationFunctions; - private final List _groupByExpressions; private final int _numGroupByExpressions; private final int _numColumns; - GroupByDataTableReducer(QueryContext queryContext) { + public GroupByDataTableReducer(QueryContext queryContext) { _queryContext = queryContext; _aggregationFunctions = queryContext.getAggregationFunctions(); assert _aggregationFunctions != null; _numAggregationFunctions = _aggregationFunctions.length; - _groupByExpressions = queryContext.getGroupByExpressions(); - assert _groupByExpressions != null; - _numGroupByExpressions = _groupByExpressions.size(); + List groupByExpressions = queryContext.getGroupByExpressions(); + assert groupByExpressions != null; + _numGroupByExpressions = groupByExpressions.size(); _numColumns = _numAggregationFunctions + _numGroupByExpressions; } @@ -92,28 +103,29 @@ public class GroupByDataTableReducer implements DataTableReducer { public void reduceAndSetResults(String tableName, DataSchema dataSchema, Map dataTableMap, BrokerResponseNative brokerResponse, DataTableReducerContext reducerContext, BrokerMetrics brokerMetrics) { - assert dataSchema != null; + dataSchema = ReducerDataSchemaUtils.canonicalizeDataSchemaForGroupBy(_queryContext, dataSchema); if (dataTableMap.isEmpty()) { PostAggregationHandler postAggregationHandler = new PostAggregationHandler(_queryContext, getPrePostAggregationDataSchema(dataSchema)); DataSchema resultDataSchema = postAggregationHandler.getResultDataSchema(); - brokerResponse.setResultTable(new ResultTable(resultDataSchema, Collections.emptyList())); + RewriterResult rewriterResult = ResultRewriteUtils.rewriteResult(resultDataSchema, Collections.emptyList()); + brokerResponse.setResultTable(new ResultTable(rewriterResult.getDataSchema(), rewriterResult.getRows())); return; } - if (!_queryContext.isServerReturnFinalResult()) { + Collection dataTables = dataTableMap.values(); + // NOTE: Use regular reduce when group keys are not partitioned even if there are only one data table because the + // records are not sorted yet. + if (_queryContext.isServerReturnFinalResult() && dataTables.size() == 1) { + processSingleFinalResult(dataSchema, dataTables.iterator().next(), brokerResponse); + } else { try { - reduceWithIntermediateResult(brokerResponse, dataSchema, dataTableMap.values(), reducerContext, tableName, - brokerMetrics); + reduceResult(brokerResponse, dataSchema, dataTables, reducerContext, tableName, brokerMetrics); } catch (TimeoutException e) { - brokerResponse.getProcessingExceptions() + brokerResponse.getExceptions() .add(new QueryProcessingException(QueryException.BROKER_TIMEOUT_ERROR_CODE, e.getMessage())); } - } else { - // TODO: Support merging results from multiple servers when the data is partitioned on the group-by column - Preconditions.checkState(dataTableMap.size() == 1, "Cannot merge final results from multiple servers"); - reduceWithFinalResult(dataSchema, dataTableMap.values().iterator().next(), brokerResponse); } if (brokerMetrics != null && brokerResponse.getResultTable() != null) { @@ -132,10 +144,11 @@ public void reduceAndSetResults(String tableName, DataSchema dataSchema, * @param brokerMetrics broker metrics (meters) * @throws TimeoutException If unable complete within timeout. */ - private void reduceWithIntermediateResult(BrokerResponseNative brokerResponseNative, DataSchema dataSchema, + private void reduceResult(BrokerResponseNative brokerResponseNative, DataSchema dataSchema, Collection dataTables, DataTableReducerContext reducerContext, String rawTableName, BrokerMetrics brokerMetrics) throws TimeoutException { + // NOTE: This step will modify the data schema and also return final aggregate results. IndexedTable indexedTable = getIndexedTable(dataSchema, dataTables, reducerContext); if (brokerMetrics != null) { brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.NUM_RESIZES, indexedTable.getNumResizes()); @@ -144,9 +157,7 @@ private void reduceWithIntermediateResult(BrokerResponseNative brokerResponseNat int numRecords = indexedTable.size(); Iterator sortedIterator = indexedTable.iterator(); - DataSchema prePostAggregationDataSchema = getPrePostAggregationDataSchema(dataSchema); - PostAggregationHandler postAggregationHandler = - new PostAggregationHandler(_queryContext, prePostAggregationDataSchema); + PostAggregationHandler postAggregationHandler = new PostAggregationHandler(_queryContext, dataSchema); DataSchema resultDataSchema = postAggregationHandler.getResultDataSchema(); // Directly return when there is no record returned, or limit is 0 @@ -158,16 +169,16 @@ private void reduceWithIntermediateResult(BrokerResponseNative brokerResponseNat // Calculate rows before post-aggregation List rows; - ColumnDataType[] columnDataTypes = prePostAggregationDataSchema.getColumnDataTypes(); + ColumnDataType[] columnDataTypes = dataSchema.getColumnDataTypes(); int numColumns = columnDataTypes.length; FilterContext havingFilter = _queryContext.getHavingFilter(); if (havingFilter != null) { rows = new ArrayList<>(); HavingFilterHandler havingFilterHandler = new HavingFilterHandler(havingFilter, postAggregationHandler, _queryContext.isNullHandlingEnabled()); + int processedRows = 0; while (rows.size() < limit && sortedIterator.hasNext()) { Object[] row = sortedIterator.next().getValues(); - extractFinalAggregationResults(row); for (int i = 0; i < numColumns; i++) { Object value = row[i]; if (value != null) { @@ -177,13 +188,14 @@ private void reduceWithIntermediateResult(BrokerResponseNative brokerResponseNat if (havingFilterHandler.isMatch(row)) { rows.add(row); } + Tracing.ThreadAccountantOps.sampleAndCheckInterruptionPeriodically(processedRows); + processedRows++; } } else { int numRows = Math.min(numRecords, limit); rows = new ArrayList<>(numRows); for (int i = 0; i < numRows; i++) { Object[] row = sortedIterator.next().getValues(); - extractFinalAggregationResults(row); for (int j = 0; j < numColumns; j++) { Object value = row[j]; if (value != null) { @@ -191,23 +203,16 @@ private void reduceWithIntermediateResult(BrokerResponseNative brokerResponseNat } } rows.add(row); + Tracing.ThreadAccountantOps.sampleAndCheckInterruptionPeriodically(i); } } // Calculate final result rows after post aggregation List resultRows = calculateFinalResultRows(postAggregationHandler, rows); - brokerResponseNative.setResultTable(new ResultTable(resultDataSchema, resultRows)); - } - - /** - * Helper method to extract the final aggregation results for the given row (in-place). - */ - private void extractFinalAggregationResults(Object[] row) { - for (int i = 0; i < _numAggregationFunctions; i++) { - int valueIndex = i + _numGroupByExpressions; - row[valueIndex] = _aggregationFunctions[i].extractFinalResult(row[valueIndex]); - } + // Rewrite and set result table + RewriterResult rewriterResult = ResultRewriteUtils.rewriteResult(resultDataSchema, resultRows); + brokerResponseNative.setResultTable(new ResultTable(rewriterResult.getDataSchema(), rewriterResult.getRows())); } /** @@ -232,25 +237,28 @@ private IndexedTable getIndexedTable(DataSchema dataSchema, Collection= GroupByCombineOperator.MAX_TRIM_THRESHOLD) { // special case of trim threshold where it is set to max value. // there won't be any trimming during upsert in this case. // thus we can avoid the overhead of read-lock and write-lock // in the upsert method. - indexedTable = new UnboundedConcurrentIndexedTable(dataSchema, _queryContext, resultSize); + indexedTable = new UnboundedConcurrentIndexedTable(dataSchema, hasFinalInput, _queryContext, resultSize); } else { - indexedTable = new ConcurrentIndexedTable(dataSchema, _queryContext, resultSize, trimSize, trimThreshold); + indexedTable = + new ConcurrentIndexedTable(dataSchema, hasFinalInput, _queryContext, resultSize, trimSize, trimThreshold); } } @@ -267,19 +275,19 @@ private IndexedTable getIndexedTable(DataSchema dataSchema, Collection exception = new AtomicReference<>(); ColumnDataType[] storedColumnDataTypes = dataSchema.getStoredColumnDataTypes(); for (int i = 0; i < numReduceThreadsToUse; i++) { List reduceGroup = reduceGroups.get(i); + int taskId = i; + ThreadExecutionContext parentContext = Tracing.getThreadAccountant().getThreadExecutionContext(); futures[i] = reducerContext.getExecutorService().submit(new TraceRunnable() { @Override public void runJob() { - for (DataTable dataTable : reduceGroup) { - // Terminate when thread is interrupted. This is expected when the query already fails in the main thread. - if (Thread.interrupted()) { - return; - } - try { + Tracing.ThreadAccountantOps.setupWorker(taskId, new ThreadResourceUsageProvider(), parentContext); + try { + for (DataTable dataTable : reduceGroup) { boolean nullHandlingEnabled = _queryContext.isNullHandlingEnabled(); RoaringBitmap[] nullBitmaps = null; if (nullHandlingEnabled) { @@ -291,9 +299,13 @@ public void runJob() { int numRows = dataTable.getNumberOfRows(); for (int rowId = 0; rowId < numRows; rowId++) { - LoopUtils.checkMergePhaseInterruption(rowId); + // Terminate when thread is interrupted. + // This is expected when the query already fails in the main thread. + // The first check will always be performed when rowId = 0 + Tracing.ThreadAccountantOps.sampleAndCheckInterruptionPeriodically(rowId); Object[] values = new Object[_numColumns]; for (int colId = 0; colId < _numColumns; colId++) { + // NOTE: We need to handle data types for group key, intermediate and final aggregate result. switch (storedColumnDataTypes[colId]) { case INT: values[colId] = dataTable.getInt(rowId, colId); @@ -316,9 +328,24 @@ public void runJob() { case BYTES: values[colId] = dataTable.getBytes(rowId, colId); break; + case INT_ARRAY: + values[colId] = IntArrayList.wrap(dataTable.getIntArray(rowId, colId)); + break; + case LONG_ARRAY: + values[colId] = LongArrayList.wrap(dataTable.getLongArray(rowId, colId)); + break; + case FLOAT_ARRAY: + values[colId] = FloatArrayList.wrap(dataTable.getFloatArray(rowId, colId)); + break; + case DOUBLE_ARRAY: + values[colId] = DoubleArrayList.wrap(dataTable.getDoubleArray(rowId, colId)); + break; + case STRING_ARRAY: + values[colId] = ObjectArrayList.wrap(dataTable.getStringArray(rowId, colId)); + break; case OBJECT: // TODO: Move ser/de into AggregationFunction interface - DataTable.CustomObject customObject = dataTable.getCustomObject(rowId, colId); + CustomObject customObject = dataTable.getCustomObject(rowId, colId); if (customObject != null) { values[colId] = ObjectSerDeUtils.deserialize(customObject); } @@ -337,9 +364,12 @@ public void runJob() { } indexedTable.upsert(new Record(values)); } - } finally { - countDownLatch.countDown(); } + } catch (Throwable t) { + exception.compareAndSet(null, t); + } finally { + countDownLatch.countDown(); + Tracing.ThreadAccountantOps.clear(); } } }); @@ -350,8 +380,15 @@ public void runJob() { if (!countDownLatch.await(timeOutMs, TimeUnit.MILLISECONDS)) { throw new TimeoutException("Timed out in broker reduce phase"); } + Throwable t = exception.get(); + if (t != null) { + Utils.rethrowException(t); + } } catch (InterruptedException e) { - throw new RuntimeException("Interrupted in broker reduce phase", e); + Exception killedErrorMsg = Tracing.getThreadAccountant().getErrorStatus(); + throw new EarlyTerminationException( + "Interrupted in broker reduce phase" + (killedErrorMsg == null ? StringUtils.EMPTY : " " + killedErrorMsg), + e); } finally { for (Future future : futures) { if (!future.isDone()) { @@ -360,7 +397,7 @@ public void runJob() { } } - indexedTable.finish(true); + indexedTable.finish(true, true); return indexedTable; } @@ -385,7 +422,7 @@ private int getNumReduceThreadsToUse(int numDataTables, int maxReduceThreadsPerQ } } - private void reduceWithFinalResult(DataSchema dataSchema, DataTable dataTable, + private void processSingleFinalResult(DataSchema dataSchema, DataTable dataTable, BrokerResponseNative brokerResponseNative) { PostAggregationHandler postAggregationHandler = new PostAggregationHandler(_queryContext, dataSchema); DataSchema resultDataSchema = postAggregationHandler.getResultDataSchema(); @@ -425,7 +462,9 @@ private void reduceWithFinalResult(DataSchema dataSchema, DataTable dataTable, // Calculate final result rows after post aggregation List resultRows = calculateFinalResultRows(postAggregationHandler, rows); - brokerResponseNative.setResultTable(new ResultTable(resultDataSchema, resultRows)); + // Rewrite and set result table + RewriterResult rewriterResult = ResultRewriteUtils.rewriteResult(resultDataSchema, resultRows); + brokerResponseNative.setResultTable(new ResultTable(rewriterResult.getDataSchema(), rewriterResult.getRows())); } private List calculateFinalResultRows(PostAggregationHandler postAggregationHandler, List rows) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/PostAggregationHandler.java b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/PostAggregationHandler.java index 516ce76f9dc7..efa4445872fb 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/PostAggregationHandler.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/PostAggregationHandler.java @@ -108,7 +108,7 @@ public Object[] getResult(Object[] row) { public ValueExtractor getValueExtractor(ExpressionContext expression) { if (expression.getType() == ExpressionContext.Type.LITERAL) { // Literal - return new LiteralValueExtractor(expression.getLiteralString()); + return new LiteralValueExtractor(expression.getLiteral().getStringValue()); } if (_numGroupByExpressions > 0) { Integer groupByExpressionIndex = _groupByExpressionIndexMap.get(expression); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/ReducerDataSchemaUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/ReducerDataSchemaUtils.java new file mode 100644 index 000000000000..7bd751877dc7 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/ReducerDataSchemaUtils.java @@ -0,0 +1,108 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.reduce; + +import com.google.common.base.Preconditions; +import java.util.List; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.request.context.FilterContext; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.query.aggregation.function.AggregationFunction; +import org.apache.pinot.core.query.aggregation.function.AggregationFunctionUtils; +import org.apache.pinot.core.query.request.context.QueryContext; + + +@SuppressWarnings("rawtypes") +public class ReducerDataSchemaUtils { + private ReducerDataSchemaUtils() { + } + + /** + * Returns the canonical data schema of the aggregation result based on the query and the data schema returned from + * the server. + *

    Column names are re-generated in the canonical data schema to avoid the backward incompatibility caused by + * changing the string representation of the expression. + */ + public static DataSchema canonicalizeDataSchemaForAggregation(QueryContext queryContext, DataSchema dataSchema) { + List> filteredAggregationFunctions = + queryContext.getFilteredAggregationFunctions(); + assert filteredAggregationFunctions != null; + int numAggregations = filteredAggregationFunctions.size(); + Preconditions.checkState(dataSchema.size() == numAggregations, + "BUG: Expect same number of aggregations and columns in data schema, got %s aggregations, %s columns in data " + + "schema", numAggregations, dataSchema.size()); + String[] columnNames = new String[numAggregations]; + for (int i = 0; i < numAggregations; i++) { + Pair pair = filteredAggregationFunctions.get(i); + AggregationFunction aggregationFunction = pair.getLeft(); + columnNames[i] = AggregationFunctionUtils.getResultColumnName(aggregationFunction, pair.getRight()); + } + return new DataSchema(columnNames, dataSchema.getColumnDataTypes()); + } + + /** + * Returns the canonical data schema of the group-by result based on the query and the data schema returned from the + * server. Group-by expressions are always at the beginning of the data schema, followed by the aggregations. + *

    Column names are re-generated in the canonical data schema to avoid the backward incompatibility caused by + * changing the string representation of the expression. + */ + public static DataSchema canonicalizeDataSchemaForGroupBy(QueryContext queryContext, DataSchema dataSchema) { + List groupByExpressions = queryContext.getGroupByExpressions(); + List> filteredAggregationFunctions = + queryContext.getFilteredAggregationFunctions(); + assert groupByExpressions != null && filteredAggregationFunctions != null; + int numGroupByExpression = groupByExpressions.size(); + int numAggregations = filteredAggregationFunctions.size(); + int numColumns = numGroupByExpression + numAggregations; + String[] columnNames = new String[numColumns]; + Preconditions.checkState(dataSchema.size() == numColumns, + "BUG: Expect same number of group-by expressions, aggregations and columns in data schema, got %s group-by " + + "expressions, %s aggregations, %s columns in data schema", numGroupByExpression, numAggregations, + dataSchema.size()); + for (int i = 0; i < numGroupByExpression; i++) { + columnNames[i] = groupByExpressions.get(i).toString(); + } + for (int i = 0; i < numAggregations; i++) { + Pair pair = filteredAggregationFunctions.get(i); + columnNames[numGroupByExpression + i] = + AggregationFunctionUtils.getResultColumnName(pair.getLeft(), pair.getRight()); + } + return new DataSchema(columnNames, dataSchema.getColumnDataTypes()); + } + + /** + * Returns the canonical data schema of the distinct result based on the query and the data schema returned from the + * server. + *

    Column names are re-generated in the canonical data schema to avoid the backward incompatibility caused by + * changing the string representation of the expression. + */ + public static DataSchema canonicalizeDataSchemaForDistinct(QueryContext queryContext, DataSchema dataSchema) { + List selectExpressions = queryContext.getSelectExpressions(); + int numSelectExpressions = selectExpressions.size(); + Preconditions.checkState(dataSchema.size() == numSelectExpressions, + "BUG: Expect same number of columns in SELECT clause and data schema, got %s in SELECT clause, %s in data " + + "schema", numSelectExpressions, dataSchema.size()); + String[] columnNames = new String[numSelectExpressions]; + for (int i = 0; i < numSelectExpressions; i++) { + columnNames[i] = selectExpressions.get(i).toString(); + } + return new DataSchema(columnNames, dataSchema.getColumnDataTypes()); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/ResultReducerFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/ResultReducerFactory.java index 529cad24dbc6..24d991d4b4c6 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/ResultReducerFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/ResultReducerFactory.java @@ -18,17 +18,13 @@ */ package org.apache.pinot.core.query.reduce; -import org.apache.pinot.core.query.aggregation.function.AggregationFunction; -import org.apache.pinot.core.query.aggregation.function.DistinctAggregationFunction; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.request.context.utils.QueryContextUtils; -import org.apache.pinot.segment.spi.AggregationFunctionType; /** * Factory class to construct the right result reducer based on the query context. */ -@SuppressWarnings("rawtypes") public final class ResultReducerFactory { private ResultReducerFactory() { } @@ -40,26 +36,18 @@ public static DataTableReducer getResultReducer(QueryContext queryContext) { if (queryContext.isExplain()) { return new ExplainPlanDataTableReducer(queryContext); } - - AggregationFunction[] aggregationFunctions = queryContext.getAggregationFunctions(); - if (aggregationFunctions == null) { - // Selection query + if (QueryContextUtils.isSelectionQuery(queryContext)) { return new SelectionDataTableReducer(queryContext); - } else { - // Aggregation query + } + if (QueryContextUtils.isAggregationQuery(queryContext)) { if (queryContext.getGroupByExpressions() == null) { - // Aggregation only query - if (aggregationFunctions.length == 1 && aggregationFunctions[0].getType() == AggregationFunctionType.DISTINCT) { - // Distinct query - return new DistinctDataTableReducer((DistinctAggregationFunction) aggregationFunctions[0], queryContext); - } else { - return new AggregationDataTableReducer(queryContext); - } + return new AggregationDataTableReducer(queryContext); } else { - // Aggregation group-by query return new GroupByDataTableReducer(queryContext); } } + assert QueryContextUtils.isDistinctQuery(queryContext); + return new DistinctDataTableReducer(queryContext); } public static StreamingReducer getStreamingReducer(QueryContext queryContext) { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/RowBasedBlockValSet.java b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/RowBasedBlockValSet.java deleted file mode 100644 index 1e05ab9c9e92..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/RowBasedBlockValSet.java +++ /dev/null @@ -1,220 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.query.reduce; - -import java.math.BigDecimal; -import java.util.List; -import javax.annotation.Nullable; -import org.apache.pinot.common.utils.DataSchema; -import org.apache.pinot.common.utils.PinotDataType; -import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.segment.spi.index.reader.Dictionary; -import org.apache.pinot.spi.data.FieldSpec; -import org.roaringbitmap.RoaringBitmap; - - -/** - * When the data is retrieved from pinot servers and merged on the broker, it - * will become row based data. If we want to apply Transformation or Aggregation, - * we need {@link BlockValSet} format data. This class is used to provide - * {@link BlockValSet} interface wrapping around row based data. - * - * TODO: We need add support for BYTES and MV - */ -public class RowBasedBlockValSet implements BlockValSet { - - private final FieldSpec.DataType _dataType; - private final PinotDataType _pinotDataType; - private final List _rows; - private final int _columnIndex; - - public RowBasedBlockValSet(DataSchema.ColumnDataType columnDataType, List rows, - int columnIndex) { - _dataType = columnDataType.toDataType(); - _pinotDataType = PinotDataType.getPinotDataTypeForExecution(columnDataType); - _rows = rows; - _columnIndex = columnIndex; - } - - @Nullable - @Override - public RoaringBitmap getNullBitmap() { - // TODO: The assumption for now is that the rows in RowBasedBlockValSet contain non-null values. - // Update to pass nullBitmap in constructor if rows have null values. Alternatively, compute nullBitmap on the fly. - return null; - } - - @Override - public FieldSpec.DataType getValueType() { - return _dataType; - } - - @Override - public boolean isSingleValue() { - return true; - } - - @Nullable - @Override - public Dictionary getDictionary() { - return null; - } - - @Override - public int[] getDictionaryIdsSV() { - throw new UnsupportedOperationException(); - } - - @Override - public int[] getIntValuesSV() { - int length = _rows.size(); - int[] values = new int[length]; - if (_dataType.isNumeric()) { - for (int i = 0; i < length; i++) { - values[i] = ((Number) _rows.get(i)[_columnIndex]).intValue(); - } - } else if (_dataType == FieldSpec.DataType.STRING) { - for (int i = 0; i < length; i++) { - values[i] = Integer.parseInt((String) _rows.get(i)[_columnIndex]); - } - } else { - throw new IllegalStateException("Cannot read int values from data type: " + _dataType); - } - return values; - } - - @Override - public long[] getLongValuesSV() { - int length = _rows.size(); - long[] values = new long[length]; - if (_dataType.isNumeric()) { - for (int i = 0; i < length; i++) { - values[i] = ((Number) _rows.get(i)[_columnIndex]).longValue(); - } - } else if (_dataType == FieldSpec.DataType.STRING) { - for (int i = 0; i < length; i++) { - values[i] = Long.parseLong((String) _rows.get(i)[_columnIndex]); - } - } else { - throw new IllegalStateException("Cannot read long values from data type: " + _dataType); - } - return values; - } - - @Override - public float[] getFloatValuesSV() { - int length = _rows.size(); - float[] values = new float[length]; - if (_dataType.isNumeric()) { - for (int i = 0; i < length; i++) { - values[i] = ((Number) _rows.get(i)[_columnIndex]).floatValue(); - } - } else if (_dataType == FieldSpec.DataType.STRING) { - for (int i = 0; i < length; i++) { - values[i] = Float.parseFloat((String) _rows.get(i)[_columnIndex]); - } - } else { - throw new IllegalStateException("Cannot read float values from data type: " + _dataType); - } - return values; - } - - @Override - public double[] getDoubleValuesSV() { - int length = _rows.size(); - double[] values = new double[length]; - if (_dataType.isNumeric()) { - for (int i = 0; i < length; i++) { - values[i] = ((Number) _rows.get(i)[_columnIndex]).doubleValue(); - } - } else if (_dataType == FieldSpec.DataType.STRING) { - for (int i = 0; i < length; i++) { - values[i] = Double.parseDouble((String) _rows.get(i)[_columnIndex]); - } - } else { - throw new IllegalStateException("Cannot read double values from data type: " + _dataType); - } - return values; - } - - @Override - public BigDecimal[] getBigDecimalValuesSV() { - int length = _rows.size(); - BigDecimal[] values = new BigDecimal[length]; - for (int i = 0; i < length; i++) { - values[i] = _pinotDataType.toBigDecimal(_rows.get(i)[_columnIndex]); - } - return values; - } - - @Override - public String[] getStringValuesSV() { - int length = _rows.size(); - String[] values = new String[length]; - for (int i = 0; i < length; i++) { - values[i] = _rows.get(i)[_columnIndex].toString(); - } - return values; - } - - @Override - public byte[][] getBytesValuesSV() { - throw new UnsupportedOperationException(); - } - - @Override - public int[][] getDictionaryIdsMV() { - throw new UnsupportedOperationException(); - } - - @Override - public int[][] getIntValuesMV() { - throw new UnsupportedOperationException(); - } - - @Override - public long[][] getLongValuesMV() { - throw new UnsupportedOperationException(); - } - - @Override - public float[][] getFloatValuesMV() { - throw new UnsupportedOperationException(); - } - - @Override - public double[][] getDoubleValuesMV() { - throw new UnsupportedOperationException(); - } - - @Override - public String[][] getStringValuesMV() { - throw new UnsupportedOperationException(); - } - - @Override - public byte[][][] getBytesValuesMV() { - throw new UnsupportedOperationException(); - } - - @Override - public int[] getNumMVEntries() { - throw new UnsupportedOperationException(); - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/SelectionDataTableReducer.java b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/SelectionDataTableReducer.java index 4d30f549358b..a8226aee5e27 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/SelectionDataTableReducer.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/SelectionDataTableReducer.java @@ -18,37 +18,28 @@ */ package org.apache.pinot.core.query.reduce; -import java.util.ArrayList; import java.util.Collections; -import java.util.Iterator; import java.util.List; import java.util.Map; +import org.apache.commons.lang3.tuple.Pair; import org.apache.pinot.common.datatable.DataTable; -import org.apache.pinot.common.exception.QueryException; -import org.apache.pinot.common.metrics.BrokerMeter; import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.response.broker.BrokerResponseNative; -import org.apache.pinot.common.response.broker.QueryProcessingException; import org.apache.pinot.common.response.broker.ResultTable; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.selection.SelectionOperatorService; import org.apache.pinot.core.query.selection.SelectionOperatorUtils; import org.apache.pinot.core.transport.ServerRoutingInstance; -import org.apache.pinot.spi.utils.builder.TableNameBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Helper class to reduce and set Selection results into the BrokerResponseNative */ public class SelectionDataTableReducer implements DataTableReducer { - private static final Logger LOGGER = LoggerFactory.getLogger(SelectionDataTableReducer.class); - private final QueryContext _queryContext; - SelectionDataTableReducer(QueryContext queryContext) { + public SelectionDataTableReducer(QueryContext queryContext) { _queryContext = queryContext; } @@ -59,68 +50,25 @@ public class SelectionDataTableReducer implements DataTableReducer { public void reduceAndSetResults(String tableName, DataSchema dataSchema, Map dataTableMap, BrokerResponseNative brokerResponseNative, DataTableReducerContext reducerContext, BrokerMetrics brokerMetrics) { - if (dataTableMap.isEmpty()) { - // For empty data table map, construct empty result using the cached data schema for selection query - List selectionColumns = SelectionOperatorUtils.getSelectionColumns(_queryContext, dataSchema); - DataSchema selectionDataSchema = SelectionOperatorUtils.getResultTableDataSchema(dataSchema, selectionColumns); - brokerResponseNative.setResultTable(new ResultTable(selectionDataSchema, Collections.emptyList())); - } else { - // For data table map with more than one data tables, remove conflicting data tables - if (dataTableMap.size() > 1) { - List droppedServers = removeConflictingResponses(dataSchema, dataTableMap); - if (!droppedServers.isEmpty()) { - String errorMessage = QueryException.MERGE_RESPONSE_ERROR.getMessage() + ": responses for table: " + tableName - + " from servers: " + droppedServers + " got dropped due to data schema inconsistency."; - LOGGER.warn(errorMessage); - if (brokerMetrics != null) { - brokerMetrics.addMeteredTableValue(TableNameBuilder.extractRawTableName(tableName), - BrokerMeter.RESPONSE_MERGE_EXCEPTIONS, 1L); - } - brokerResponseNative.addToExceptions( - new QueryProcessingException(QueryException.MERGE_RESPONSE_ERROR_CODE, errorMessage)); - } - } - - int limit = _queryContext.getLimit(); - if (limit > 0 && _queryContext.getOrderByExpressions() != null) { - // Selection order-by - SelectionOperatorService selectionService = new SelectionOperatorService(_queryContext, dataSchema); - selectionService.reduceWithOrdering(dataTableMap.values(), _queryContext.isNullHandlingEnabled()); - brokerResponseNative.setResultTable(selectionService.renderResultTableWithOrdering()); - } else { - // Selection only - List selectionColumns = SelectionOperatorUtils.getSelectionColumns(_queryContext, dataSchema); - List reducedRows = SelectionOperatorUtils.reduceWithoutOrdering(dataTableMap.values(), limit, - _queryContext.isNullHandlingEnabled()); - brokerResponseNative.setResultTable( - SelectionOperatorUtils.renderResultTableWithoutOrdering(reducedRows, dataSchema, selectionColumns)); - } + Pair pair = + SelectionOperatorUtils.getResultTableDataSchemaAndColumnIndices(_queryContext, dataSchema); + int limit = _queryContext.getLimit(); + if (dataTableMap.isEmpty() || limit == 0) { + brokerResponseNative.setResultTable(new ResultTable(pair.getLeft(), Collections.emptyList())); + return; } - } - - /** - * Given a data schema, remove data tables that are not compatible with this data schema. - *

    Upgrade the data schema passed in to cover all remaining data schemas. - * - * @param dataSchema data schema. - * @param dataTableMap map from server to data table. - * @return list of server names where the data table got removed. - */ - private List removeConflictingResponses(DataSchema dataSchema, - Map dataTableMap) { - List droppedServers = new ArrayList<>(); - Iterator> iterator = dataTableMap.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - DataSchema dataSchemaToCompare = entry.getValue().getDataSchema(); - assert dataSchemaToCompare != null; - if (!dataSchema.isTypeCompatibleWith(dataSchemaToCompare)) { - droppedServers.add(entry.getKey()); - iterator.remove(); - } else { - dataSchema = DataSchema.upgradeToCover(dataSchema, dataSchemaToCompare); - } + if (_queryContext.getOrderByExpressions() == null) { + // Selection only + List reducedRows = SelectionOperatorUtils.reduceWithoutOrdering(dataTableMap.values(), limit, + _queryContext.isNullHandlingEnabled()); + brokerResponseNative.setResultTable( + SelectionOperatorUtils.renderResultTableWithoutOrdering(reducedRows, pair.getLeft(), pair.getRight())); + } else { + // Selection order-by + SelectionOperatorService selectionService = + new SelectionOperatorService(_queryContext, pair.getLeft(), pair.getRight()); + selectionService.reduceWithOrdering(dataTableMap.values()); + brokerResponseNative.setResultTable(selectionService.renderResultTableWithOrdering()); } - return droppedServers; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/SelectionOnlyStreamingReducer.java b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/SelectionOnlyStreamingReducer.java index cda1ad2b8782..ffa0ffdbd084 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/SelectionOnlyStreamingReducer.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/SelectionOnlyStreamingReducer.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.apache.commons.lang3.tuple.Pair; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.response.broker.BrokerResponseNative; import org.apache.pinot.common.response.broker.ResultTable; @@ -58,7 +59,7 @@ private void reduceWithoutOrdering(DataTable dataTable, int limit, boolean nullH int numColumns = dataTable.getDataSchema().size(); int numRows = dataTable.getNumberOfRows(); if (nullHandlingEnabled) { - RoaringBitmap[] nullBitmaps = new RoaringBitmap[numColumns];; + RoaringBitmap[] nullBitmaps = new RoaringBitmap[numColumns]; for (int coldId = 0; coldId < numColumns; coldId++) { nullBitmaps[coldId] = dataTable.getNullRowIds(coldId); } @@ -88,16 +89,19 @@ private void reduceWithoutOrdering(DataTable dataTable, int limit, boolean nullH @Override public BrokerResponseNative seal() { - BrokerResponseNative brokerResponseNative = new BrokerResponseNative(); - List selectionColumns = SelectionOperatorUtils.getSelectionColumns(_queryContext, _dataSchema); - if (_dataSchema != null && _rows.size() > 0) { - brokerResponseNative.setResultTable( - SelectionOperatorUtils.renderResultTableWithoutOrdering(_rows, _dataSchema, selectionColumns)); + if (_dataSchema == null) { + return BrokerResponseNative.empty(); + } + Pair pair = + SelectionOperatorUtils.getResultTableDataSchemaAndColumnIndices(_queryContext, _dataSchema); + ResultTable resultTable; + if (_rows.isEmpty()) { + resultTable = new ResultTable(pair.getLeft(), Collections.emptyList()); } else { - // For empty data table map, construct empty result using the cached data schema for selection query - DataSchema selectionDataSchema = SelectionOperatorUtils.getResultTableDataSchema(_dataSchema, selectionColumns); - brokerResponseNative.setResultTable(new ResultTable(selectionDataSchema, Collections.emptyList())); + resultTable = SelectionOperatorUtils.renderResultTableWithoutOrdering(_rows, pair.getLeft(), pair.getRight()); } - return brokerResponseNative; + BrokerResponseNative brokerResponse = new BrokerResponseNative(); + brokerResponse.setResultTable(resultTable); + return brokerResponse; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/StreamingReduceService.java b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/StreamingReduceService.java index 3a01dd05d342..653498aadeff 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/StreamingReduceService.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/StreamingReduceService.java @@ -25,7 +25,6 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; -import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.datatable.DataTableFactory; @@ -34,6 +33,7 @@ import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.common.request.PinotQuery; import org.apache.pinot.common.response.broker.BrokerResponseNative; +import org.apache.pinot.common.utils.config.QueryOptionsUtils; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.request.context.utils.QueryContextConverterUtils; import org.apache.pinot.core.transport.ServerRoutingInstance; @@ -58,7 +58,7 @@ public StreamingReduceService(PinotConfiguration config) { public BrokerResponseNative reduceOnStreamResponse(BrokerRequest brokerRequest, Map> serverResponseMap, long reduceTimeOutMs, - @Nullable BrokerMetrics brokerMetrics) + BrokerMetrics brokerMetrics) throws IOException { if (serverResponseMap.isEmpty()) { // Empty response. @@ -78,10 +78,20 @@ public BrokerResponseNative reduceOnStreamResponse(BrokerRequest brokerRequest, // initialize empty response. ExecutionStatsAggregator aggregator = new ExecutionStatsAggregator(enableTrace); + Integer minGroupTrimSizeQueryOption = null; + Integer groupTrimThresholdQueryOption = null; + if (queryOptions != null) { + minGroupTrimSizeQueryOption = QueryOptionsUtils.getMinBrokerGroupTrimSize(queryOptions); + groupTrimThresholdQueryOption = QueryOptionsUtils.getGroupTrimThreshold(queryOptions); + } + int minGroupTrimSize = minGroupTrimSizeQueryOption != null ? minGroupTrimSizeQueryOption : _minGroupTrimSize; + int groupTrimThreshold = + groupTrimThresholdQueryOption != null ? groupTrimThresholdQueryOption : _groupByTrimThreshold; + // Process server response. DataTableReducerContext dataTableReducerContext = new DataTableReducerContext(_reduceExecutorService, _maxReduceThreadsPerQuery, reduceTimeOutMs, - _groupByTrimThreshold); + groupTrimThreshold, minGroupTrimSize); StreamingReducer streamingReducer = ResultReducerFactory.getStreamingReducer(queryContext); streamingReducer.init(dataTableReducerContext); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/SumAvgGapfillProcessor.java b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/SumAvgGapfillProcessor.java index 835a2c753c63..6e0091262521 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/SumAvgGapfillProcessor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/SumAvgGapfillProcessor.java @@ -46,6 +46,7 @@ class SumAvgGapfillProcessor extends BaseGapfillProcessor { private final static int COLUMN_TYPE_AVG = 2; protected Map _filteredMap; protected final Map _groupByKeys; + protected final Map _previousIndexByGroupKey; SumAvgGapfillProcessor(QueryContext queryContext, GapfillUtils.GapfillType gapfillType) { super(queryContext, gapfillType); @@ -53,6 +54,7 @@ class SumAvgGapfillProcessor extends BaseGapfillProcessor { _columnTypes = new int[_queryContext.getSelectExpressions().size()]; _sumArgIndexes = new int[_columnTypes.length]; _sumes = new double[_columnTypes.length]; + _previousIndexByGroupKey = new HashMap<>(); } protected void initializeAggregationValues(List rows, DataSchema dataSchema) { @@ -75,13 +77,14 @@ protected void initializeAggregationValues(List rows, DataSchema dataS } for (Map.Entry entry : _groupByKeys.entrySet()) { - if (_previousByGroupKey.containsKey(entry.getKey())) { + if (_previousIndexByGroupKey.containsKey(entry.getKey())) { if (_postGapfillFilterHandler == null - || _postGapfillFilterHandler.isMatch(_previousByGroupKey.get(entry.getKey()))) { - _filteredMap.put(entry.getValue(), entry.getValue()); + || _postGapfillFilterHandler.isMatch(rows.get(_previousIndexByGroupKey.get(entry.getKey())))) { + _filteredMap.put(entry.getValue(), _previousIndexByGroupKey.get(entry.getKey())); for (int i = 0; i < _columnTypes.length; i++) { if (_columnTypes[i] != 0) { - _sumes[i] += ((Number) rows.get(entry.getValue())[_sumArgIndexes[i]]).doubleValue(); + _sumes[i] += + ((Number) rows.get(_previousIndexByGroupKey.get(entry.getKey()))[_sumArgIndexes[i]]).doubleValue(); } } _count++; @@ -95,11 +98,13 @@ protected List gapFillAndAggregate( List rows, DataSchema dataSchema, DataSchema resultTableSchema) { int [] timeBucketedRawRows = new int[_numOfTimeBuckets + 1]; int timeBucketedRawRowsIndex = 0; + int lastIndex = rows.size(); for (int i = 0; i < rows.size(); i++) { Object[] row = rows.get(i); long time = _dateTimeFormatter.fromFormatToMillis(String.valueOf(row[_timeBucketColumnIndex])); int index = findGapfillBucketIndex(time); if (index >= _numOfTimeBuckets) { + lastIndex = index; timeBucketedRawRows[timeBucketedRawRowsIndex++] = i; break; } @@ -107,14 +112,15 @@ protected List gapFillAndAggregate( _groupByKeys.putIfAbsent(key, _groupByKeys.size()); if (index < 0) { // the data can potentially be used for previous value - _previousByGroupKey.compute(key, (k, previousRow) -> { - if (previousRow == null) { - return row; + final int currentRowIndex = i; + _previousIndexByGroupKey.compute(key, (k, previousRowIndex) -> { + if (previousRowIndex == null) { + return currentRowIndex; } else { - if ((Long) row[_timeBucketColumnIndex] > (Long) previousRow[_timeBucketColumnIndex]) { - return row; + if ((Long) row[_timeBucketColumnIndex] > (Long) rows.get(previousRowIndex)[_timeBucketColumnIndex]) { + return currentRowIndex; } else { - return previousRow; + return previousRowIndex; } } }); @@ -125,7 +131,7 @@ protected List gapFillAndAggregate( } } while (timeBucketedRawRowsIndex < _numOfTimeBuckets + 1) { - timeBucketedRawRows[timeBucketedRawRowsIndex++] = rows.size(); + timeBucketedRawRows[timeBucketedRawRowsIndex++] = lastIndex; } _filteredMap = new HashMap<>(); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/PredicateRowMatcher.java b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/PredicateRowMatcher.java index edd5c6caeb7b..4c108b70e72d 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/PredicateRowMatcher.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/filter/PredicateRowMatcher.java @@ -20,6 +20,7 @@ import java.math.BigDecimal; import java.sql.Timestamp; +import javax.annotation.Nullable; import org.apache.pinot.common.request.context.predicate.Predicate; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluatorProvider; @@ -32,19 +33,31 @@ public class PredicateRowMatcher implements RowMatcher { private final ValueExtractor _valueExtractor; private final DataType _valueType; - private final PredicateEvaluator _predicateEvaluator; + private final Predicate.Type _predicateType; + private final @Nullable PredicateEvaluator _predicateEvaluator; private final boolean _nullHandlingEnabled; public PredicateRowMatcher(Predicate predicate, ValueExtractor valueExtractor, boolean nullHandlingEnabled) { _valueExtractor = valueExtractor; _valueType = _valueExtractor.getColumnDataType().toDataType(); - _predicateEvaluator = PredicateEvaluatorProvider.getPredicateEvaluator(predicate, null, _valueType); + _predicateType = predicate.getType(); + if (_predicateType == Predicate.Type.IS_NULL || _predicateType == Predicate.Type.IS_NOT_NULL) { + _predicateEvaluator = null; + } else { + _predicateEvaluator = PredicateEvaluatorProvider.getPredicateEvaluator(predicate, null, _valueType); + } _nullHandlingEnabled = nullHandlingEnabled; } @Override public boolean isMatch(Object[] row) { Object value = _valueExtractor.extract(row); + if (_predicateType == Predicate.Type.IS_NULL) { + return value == null; + } else if (_predicateType == Predicate.Type.IS_NOT_NULL) { + return value != null; + } + assert (_predicateEvaluator != null); if (_nullHandlingEnabled && value == null) { return false; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/function/InternalReduceFunctions.java b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/function/InternalReduceFunctions.java new file mode 100644 index 000000000000..ab0e8cfd209d --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/reduce/function/InternalReduceFunctions.java @@ -0,0 +1,42 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.query.reduce.function; + +import org.apache.pinot.spi.annotations.ScalarFunction; + + +/** + * This class contains functions that are necessary for the multistage engine + * aggregations that need to be reduced after the initial aggregation to get + * the final result. + */ +public class InternalReduceFunctions { + + private InternalReduceFunctions() { + } + + @ScalarFunction(nullableParameters = true) + public static Double avgReduce(Double intermediateResultSum, Long intermediateResultCount) { + if (intermediateResultCount == null || intermediateResultCount == 0L || intermediateResultSum == null) { + return null; + } + return intermediateResultSum / intermediateResultCount; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/request/ServerQueryRequest.java b/pinot-core/src/main/java/org/apache/pinot/core/query/request/ServerQueryRequest.java index dc62b3c884e7..d4ce7857a5ff 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/request/ServerQueryRequest.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/request/ServerQueryRequest.java @@ -51,6 +51,7 @@ public class ServerQueryRequest { private final boolean _enableTrace; private final boolean _enableStreaming; private final List _segmentsToQuery; + private final List _optionalSegments; private final QueryContext _queryContext; // Request id might not be unique across brokers or for request hitting a hybrid table. To solve that we may construct @@ -61,11 +62,17 @@ public class ServerQueryRequest { private final TimerContext _timerContext; public ServerQueryRequest(InstanceRequest instanceRequest, ServerMetrics serverMetrics, long queryArrivalTimeMs) { + this(instanceRequest, serverMetrics, queryArrivalTimeMs, false); + } + + public ServerQueryRequest(InstanceRequest instanceRequest, ServerMetrics serverMetrics, long queryArrivalTimeMs, + boolean enableStreaming) { _requestId = instanceRequest.getRequestId(); _brokerId = instanceRequest.getBrokerId() != null ? instanceRequest.getBrokerId() : "unknown"; _enableTrace = instanceRequest.isEnableTrace(); - _enableStreaming = false; + _enableStreaming = enableStreaming; _segmentsToQuery = instanceRequest.getSearchSegments(); + _optionalSegments = instanceRequest.getOptionalSegments(); _queryContext = getQueryContext(instanceRequest.getQuery().getPinotQuery()); _queryId = QueryIdUtils.getQueryId(_brokerId, _requestId, TableNameBuilder.getTableTypeFromTableName(_queryContext.getTableName())); @@ -83,6 +90,8 @@ public ServerQueryRequest(Server.ServerRequest serverRequest, ServerMetrics serv _enableStreaming = Boolean.parseBoolean(metadata.get(Request.MetadataKeys.ENABLE_STREAMING)); _segmentsToQuery = serverRequest.getSegmentsList(); + // TODO: support optional segments for GrpcQueryServer + _optionalSegments = null; BrokerRequest brokerRequest; String payloadType = metadata.getOrDefault(Request.MetadataKeys.PAYLOAD_TYPE, Request.PayloadType.SQL); @@ -134,6 +143,10 @@ public List getSegmentsToQuery() { return _segmentsToQuery; } + public List getOptionalSegments() { + return _optionalSegments; + } + public QueryContext getQueryContext() { return _queryContext; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/QueryContext.java b/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/QueryContext.java index 5c1bd2fe846e..aee9261d9480 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/QueryContext.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/QueryContext.java @@ -40,6 +40,8 @@ import org.apache.pinot.core.query.aggregation.function.AggregationFunction; import org.apache.pinot.core.query.aggregation.function.AggregationFunctionFactory; import org.apache.pinot.core.util.MemoizedClassAssociation; +import org.apache.pinot.segment.spi.datasource.DataSource; +import org.apache.pinot.spi.config.table.FieldConfig; /** @@ -73,6 +75,7 @@ public class QueryContext { private final String _tableName; private final QueryContext _subquery; private final List _selectExpressions; + private final boolean _distinct; private final List _aliasList; private final FilterContext _filter; private final List _groupByExpressions; @@ -88,10 +91,9 @@ public class QueryContext { // Pre-calculate the aggregation functions and columns for the query so that it can be shared across all the segments private AggregationFunction[] _aggregationFunctions; - private Map _aggregationFunctionIndexMap; - private boolean _hasFilteredAggregations; private List> _filteredAggregationFunctions; private Map, Integer> _filteredAggregationsIndexMap; + private boolean _hasFilteredAggregations; private Set _columns; // Other properties to be shared across all the segments @@ -122,16 +124,21 @@ public class QueryContext { private boolean _nullHandlingEnabled; // Whether server returns the final result private boolean _serverReturnFinalResult; + // Whether server returns the final result with unpartitioned group key + private boolean _serverReturnFinalResultKeyUnpartitioned; + // Collection of index types to skip per column + private Map> _skipIndexes; private QueryContext(@Nullable String tableName, @Nullable QueryContext subquery, - List selectExpressions, List aliasList, @Nullable FilterContext filter, - @Nullable List groupByExpressions, @Nullable FilterContext havingFilter, - @Nullable List orderByExpressions, int limit, int offset, - Map queryOptions, @Nullable Map expressionOverrideHints, - boolean explain) { + List selectExpressions, boolean distinct, List aliasList, + @Nullable FilterContext filter, @Nullable List groupByExpressions, + @Nullable FilterContext havingFilter, @Nullable List orderByExpressions, int limit, + int offset, Map queryOptions, + @Nullable Map expressionOverrideHints, boolean explain) { _tableName = tableName; _subquery = subquery; _selectExpressions = selectExpressions; + _distinct = distinct; _aliasList = Collections.unmodifiableList(aliasList); _filter = filter; _groupByExpressions = groupByExpressions; @@ -167,6 +174,13 @@ public List getSelectExpressions() { return _selectExpressions; } + /** + * Returns whether the query is a DISTINCT query. + */ + public boolean isDistinct() { + return _distinct; + } + /** * Returns an unmodifiable list from the expression to its alias. */ @@ -257,22 +271,6 @@ public List> getFilteredAggregationFunc return _filteredAggregationFunctions; } - /** - * Returns the filtered aggregation expressions for the query. - */ - public boolean isHasFilteredAggregations() { - return _hasFilteredAggregations; - } - - /** - * Returns a map from the AGGREGATION FunctionContext to the index of the corresponding AggregationFunction in the - * aggregation functions array. - */ - @Nullable - public Map getAggregationFunctionIndexMap() { - return _aggregationFunctionIndexMap; - } - /** * Returns a map from the filtered aggregation (pair of AGGREGATION FunctionContext and FILTER FilterContext) to the * index of corresponding AggregationFunction in the aggregation functions array. @@ -282,6 +280,13 @@ public Map, Integer> getFilteredAggregation return _filteredAggregationsIndexMap; } + /** + * Returns the filtered aggregation expressions for the query. + */ + public boolean hasFilteredAggregations() { + return _hasFilteredAggregations; + } + /** * Returns the columns (IDENTIFIER expressions) in the query. */ @@ -393,6 +398,14 @@ public void setServerReturnFinalResult(boolean serverReturnFinalResult) { _serverReturnFinalResult = serverReturnFinalResult; } + public boolean isServerReturnFinalResultKeyUnpartitioned() { + return _serverReturnFinalResultKeyUnpartitioned; + } + + public void setServerReturnFinalResultKeyUnpartitioned(boolean serverReturnFinalResultKeyUnpartitioned) { + _serverReturnFinalResultKeyUnpartitioned = serverReturnFinalResultKeyUnpartitioned; + } + /** * Gets or computes a value of type {@code V} associated with a key of type {@code K} so that it can be shared * within the scope of a query. @@ -413,16 +426,32 @@ public V getOrComputeSharedValue(Class type, K key, Function map @Override public String toString() { return "QueryContext{" + "_tableName='" + _tableName + '\'' + ", _subquery=" + _subquery + ", _selectExpressions=" - + _selectExpressions + ", _aliasList=" + _aliasList + ", _filter=" + _filter + ", _groupByExpressions=" - + _groupByExpressions + ", _havingFilter=" + _havingFilter + ", _orderByExpressions=" + _orderByExpressions - + ", _limit=" + _limit + ", _offset=" + _offset + ", _queryOptions=" + _queryOptions + + _selectExpressions + ", _distinct=" + _distinct + ", _aliasList=" + _aliasList + ", _filter=" + _filter + + ", _groupByExpressions=" + _groupByExpressions + ", _havingFilter=" + _havingFilter + ", _orderByExpressions=" + + _orderByExpressions + ", _limit=" + _limit + ", _offset=" + _offset + ", _queryOptions=" + _queryOptions + ", _expressionOverrideHints=" + _expressionOverrideHints + ", _explain=" + _explain + '}'; } + public void setSkipIndexes(Map> skipIndexes) { + _skipIndexes = skipIndexes; + } + + public boolean isIndexUseAllowed(String columnName, FieldConfig.IndexType indexType) { + if (_skipIndexes == null) { + return true; + } + return !_skipIndexes.getOrDefault(columnName, Collections.EMPTY_SET).contains(indexType); + } + + public boolean isIndexUseAllowed(DataSource dataSource, FieldConfig.IndexType indexType) { + return isIndexUseAllowed(dataSource.getColumnName(), indexType); + } + public static class Builder { private String _tableName; private QueryContext _subquery; private List _selectExpressions; + private boolean _distinct; private List _aliasList; private FilterContext _filter; private List _groupByExpressions; @@ -431,7 +460,6 @@ public static class Builder { private int _limit; private int _offset; private Map _queryOptions; - private Map _debugOptions; private Map _expressionOverrideHints; private boolean _explain; @@ -450,6 +478,11 @@ public Builder setSelectExpressions(List selectExpressions) { return this; } + public Builder setDistinct(boolean distinct) { + _distinct = distinct; + return this; + } + public Builder setAliasList(List aliasList) { _aliasList = aliasList; return this; @@ -507,10 +540,13 @@ public QueryContext build() { _queryOptions = Collections.emptyMap(); } QueryContext queryContext = - new QueryContext(_tableName, _subquery, _selectExpressions, _aliasList, _filter, _groupByExpressions, - _havingFilter, _orderByExpressions, _limit, _offset, _queryOptions, _expressionOverrideHints, _explain); + new QueryContext(_tableName, _subquery, _selectExpressions, _distinct, _aliasList, _filter, + _groupByExpressions, _havingFilter, _orderByExpressions, _limit, _offset, _queryOptions, + _expressionOverrideHints, _explain); queryContext.setNullHandlingEnabled(QueryOptionsUtils.isNullHandlingEnabled(_queryOptions)); queryContext.setServerReturnFinalResult(QueryOptionsUtils.isServerReturnFinalResult(_queryOptions)); + queryContext.setServerReturnFinalResultKeyUnpartitioned( + QueryOptionsUtils.isServerReturnFinalResultKeyUnpartitioned(_queryOptions)); // Pre-calculate the aggregation functions and columns for the query generateAggregationFunctions(queryContext); @@ -536,15 +572,11 @@ private void generateAggregationFunctions(QueryContext queryContext) { FunctionContext aggregation = pair.getLeft(); FilterContext filter = pair.getRight(); if (filter != null) { - // Filtered aggregation - if (_groupByExpressions != null) { - throw new IllegalStateException("GROUP BY with FILTER clauses is not supported"); - } queryContext._hasFilteredAggregations = true; } int functionIndex = filteredAggregationFunctions.size(); AggregationFunction aggregationFunction = - AggregationFunctionFactory.getAggregationFunction(aggregation, queryContext); + AggregationFunctionFactory.getAggregationFunction(aggregation, queryContext._nullHandlingEnabled); filteredAggregationFunctions.add(Pair.of(aggregationFunction, filter)); filteredAggregationsIndexMap.put(Pair.of(aggregation, filter), functionIndex); } @@ -565,7 +597,7 @@ private void generateAggregationFunctions(QueryContext queryContext) { FilterContext filter = pair.getRight(); int functionIndex = filteredAggregationFunctions.size(); AggregationFunction aggregationFunction = - AggregationFunctionFactory.getAggregationFunction(aggregation, queryContext); + AggregationFunctionFactory.getAggregationFunction(aggregation, queryContext._nullHandlingEnabled); filteredAggregationFunctions.add(Pair.of(aggregationFunction, filter)); filteredAggregationsIndexMap.put(Pair.of(aggregation, filter), functionIndex); } @@ -577,12 +609,7 @@ private void generateAggregationFunctions(QueryContext queryContext) { for (int i = 0; i < numAggregations; i++) { aggregationFunctions[i] = filteredAggregationFunctions.get(i).getLeft(); } - Map aggregationFunctionIndexMap = new HashMap<>(); - for (Map.Entry, Integer> entry : filteredAggregationsIndexMap.entrySet()) { - aggregationFunctionIndexMap.put(entry.getKey().getLeft(), entry.getValue()); - } queryContext._aggregationFunctions = aggregationFunctions; - queryContext._aggregationFunctionIndexMap = aggregationFunctionIndexMap; queryContext._filteredAggregationFunctions = filteredAggregationFunctions; queryContext._filteredAggregationsIndexMap = filteredAggregationsIndexMap; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/TimerContext.java b/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/TimerContext.java index 175da9258ae3..be6fbbd7f192 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/TimerContext.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/TimerContext.java @@ -25,10 +25,11 @@ public class TimerContext { + private static final int SERVER_QUERY_PHASE_COUNT = ServerQueryPhase.values().length; private final String _tableNameWithType; private final ServerMetrics _serverMetrics; private final long _queryArrivalTimeMs; - private final Timer[] _phaseTimers = new Timer[ServerQueryPhase.values().length]; + private final Timer[] _phaseTimers = new Timer[SERVER_QUERY_PHASE_COUNT]; public class Timer { private final ServerQueryPhase _queryPhase; diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/utils/QueryContextConverterUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/utils/QueryContextConverterUtils.java index c04e0f7b42bd..ce4219812cf8 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/utils/QueryContextConverterUtils.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/utils/QueryContextConverterUtils.java @@ -24,10 +24,9 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections4.CollectionUtils; import org.apache.pinot.common.request.DataSource; import org.apache.pinot.common.request.Expression; -import org.apache.pinot.common.request.ExpressionType; import org.apache.pinot.common.request.Function; import org.apache.pinot.common.request.PinotQuery; import org.apache.pinot.common.request.context.ExpressionContext; @@ -64,40 +63,29 @@ public static QueryContext getQueryContext(PinotQuery pinotQuery) { // SELECT List selectExpressions; + boolean distinct = false; List selectList = pinotQuery.getSelectList(); + // Handle DISTINCT + if (selectList.size() == 1) { + Function function = selectList.get(0).getFunctionCall(); + if (function != null && function.getOperator().equals("distinct")) { + distinct = true; + selectList = function.getOperands(); + } + } List aliasList = new ArrayList<>(selectList.size()); selectExpressions = new ArrayList<>(selectList.size()); for (Expression thriftExpression : selectList) { // Handle alias - Expression expressionWithoutAlias = thriftExpression; - if (thriftExpression.getType() == ExpressionType.FUNCTION) { - Function function = thriftExpression.getFunctionCall(); + Function function = thriftExpression.getFunctionCall(); + Expression expressionWithoutAlias; + if (function != null && function.getOperator().equals("as")) { List operands = function.getOperands(); - switch (function.getOperator().toUpperCase()) { - case "AS": - expressionWithoutAlias = operands.get(0); - aliasList.add(operands.get(1).getIdentifier().getName()); - break; - case "DISTINCT": - int numOperands = operands.size(); - for (int i = 0; i < numOperands; i++) { - Expression operand = operands.get(i); - Function operandFunction = operand.getFunctionCall(); - if (operandFunction != null && operandFunction.getOperator().equalsIgnoreCase("AS")) { - operands.set(i, operandFunction.getOperands().get(0)); - aliasList.add(operandFunction.getOperands().get(1).getIdentifier().getName()); - } else { - aliasList.add(null); - } - } - break; - default: - // Add null as a placeholder for alias. - aliasList.add(null); - break; - } + expressionWithoutAlias = operands.get(0); + aliasList.add(operands.get(1).getIdentifier().getName()); } else { - // Add null as a placeholder for alias. + expressionWithoutAlias = thriftExpression; + // Add null as a placeholder for alias aliasList.add(null); } selectExpressions.add(RequestContextUtils.getExpression(expressionWithoutAlias)); @@ -108,6 +96,10 @@ public static QueryContext getQueryContext(PinotQuery pinotQuery) { Expression filterExpression = pinotQuery.getFilterExpression(); if (filterExpression != null) { filter = RequestContextUtils.getFilter(filterExpression); + // Remove the filter if it is always true + if (filter.isConstantTrue()) { + filter = null; + } } // GROUP BY @@ -124,16 +116,20 @@ public static QueryContext getQueryContext(PinotQuery pinotQuery) { List orderByExpressions = null; List orderByList = pinotQuery.getOrderByList(); if (CollectionUtils.isNotEmpty(orderByList)) { - // Deduplicate the order-by expressions orderByExpressions = new ArrayList<>(orderByList.size()); - Set expressionSet = new HashSet<>(); + Set seen = new HashSet<>(); for (Expression orderBy : orderByList) { - // NOTE: Order-by is always a Function with the ordering of the Expression - Function thriftFunction = orderBy.getFunctionCall(); - ExpressionContext expression = RequestContextUtils.getExpression(thriftFunction.getOperands().get(0)); - if (expressionSet.add(expression)) { - boolean isAsc = thriftFunction.getOperator().equalsIgnoreCase("ASC"); - orderByExpressions.add(new OrderByExpressionContext(expression, isAsc)); + Boolean isNullsLast = CalciteSqlParser.isNullsLast(orderBy); + boolean isAsc = CalciteSqlParser.isAsc(orderBy, isNullsLast); + Expression orderByFunctionsRemoved = CalciteSqlParser.removeOrderByFunctions(orderBy); + // Deduplicate the order-by expressions + if (seen.add(orderByFunctionsRemoved)) { + ExpressionContext expressionContext = RequestContextUtils.getExpression(orderByFunctionsRemoved); + if (isNullsLast != null) { + orderByExpressions.add(new OrderByExpressionContext(expressionContext, isAsc, isNullsLast)); + } else { + orderByExpressions.add(new OrderByExpressionContext(expressionContext, isAsc)); + } } } } @@ -143,6 +139,10 @@ public static QueryContext getQueryContext(PinotQuery pinotQuery) { Expression havingExpression = pinotQuery.getHavingExpression(); if (havingExpression != null) { havingFilter = RequestContextUtils.getFilter(havingExpression); + // Remove the filter if it is always true + if (havingFilter.isConstantTrue()) { + havingFilter = null; + } } // EXPRESSION OVERRIDE HINTS @@ -156,7 +156,7 @@ public static QueryContext getQueryContext(PinotQuery pinotQuery) { } return new QueryContext.Builder().setTableName(tableName).setSubquery(subquery) - .setSelectExpressions(selectExpressions).setAliasList(aliasList).setFilter(filter) + .setSelectExpressions(selectExpressions).setDistinct(distinct).setAliasList(aliasList).setFilter(filter) .setGroupByExpressions(groupByExpressions).setOrderByExpressions(orderByExpressions) .setHavingFilter(havingFilter).setLimit(pinotQuery.getLimit()).setOffset(pinotQuery.getOffset()) .setQueryOptions(pinotQuery.getQueryOptions()).setExpressionOverrideHints(expressionContextOverrideHints) diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/utils/QueryContextUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/utils/QueryContextUtils.java index 7a468c438bf3..b5beee1a4013 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/utils/QueryContextUtils.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/request/context/utils/QueryContextUtils.java @@ -24,12 +24,9 @@ import org.apache.pinot.common.request.context.FilterContext; import org.apache.pinot.common.request.context.FunctionContext; import org.apache.pinot.common.request.context.OrderByExpressionContext; -import org.apache.pinot.core.query.aggregation.function.AggregationFunction; -import org.apache.pinot.core.query.aggregation.function.DistinctAggregationFunction; import org.apache.pinot.core.query.request.context.QueryContext; -@SuppressWarnings("rawtypes") public class QueryContextUtils { private QueryContextUtils() { } @@ -38,7 +35,7 @@ private QueryContextUtils() { * Returns {@code true} if the given query is a selection query, {@code false} otherwise. */ public static boolean isSelectionQuery(QueryContext query) { - return query.getAggregationFunctions() == null; + return !query.isDistinct() && query.getAggregationFunctions() == null; } /** @@ -47,25 +44,21 @@ public static boolean isSelectionQuery(QueryContext query) { * Selection-only query at this moment means selection query without order-by. */ public static boolean isSelectionOnlyQuery(QueryContext query) { - return query.getAggregationFunctions() == null && query.getOrderByExpressions() == null; + return isSelectionQuery(query) && query.getOrderByExpressions() == null; } /** * Returns {@code true} if the given query is an aggregation query, {@code false} otherwise. */ public static boolean isAggregationQuery(QueryContext query) { - AggregationFunction[] aggregationFunctions = query.getAggregationFunctions(); - return aggregationFunctions != null && (aggregationFunctions.length != 1 - || !(aggregationFunctions[0] instanceof DistinctAggregationFunction)); + return query.getAggregationFunctions() != null; } /** * Returns {@code true} if the given query is a distinct query, {@code false} otherwise. */ public static boolean isDistinctQuery(QueryContext query) { - AggregationFunction[] aggregationFunctions = query.getAggregationFunctions(); - return aggregationFunctions != null && aggregationFunctions.length == 1 - && aggregationFunctions[0] instanceof DistinctAggregationFunction; + return query.isDistinct(); } /** Collect aggregation functions (except for the ones in filter). */ @@ -96,7 +89,6 @@ public static void collectPostAggregations(QueryContext queryContext, Set postAggregations) { FunctionContext function = expression.getFunction(); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/scheduler/QueryScheduler.java b/pinot-core/src/main/java/org/apache/pinot/core/query/scheduler/QueryScheduler.java index e14a6180a3af..f4c2a241e769 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/scheduler/QueryScheduler.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/scheduler/QueryScheduler.java @@ -22,27 +22,28 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFutureTask; -import com.google.common.util.concurrent.RateLimiter; import java.util.Map; import java.util.concurrent.ExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.LongAccumulator; import javax.annotation.Nullable; +import org.apache.commons.lang3.StringUtils; import org.apache.pinot.common.datatable.DataTable.MetadataKey; import org.apache.pinot.common.exception.QueryException; -import org.apache.pinot.common.metrics.ServerGauge; import org.apache.pinot.common.metrics.ServerMeter; import org.apache.pinot.common.metrics.ServerMetrics; import org.apache.pinot.common.metrics.ServerQueryPhase; -import org.apache.pinot.common.metrics.ServerTimer; import org.apache.pinot.common.response.ProcessingException; +import org.apache.pinot.common.utils.config.QueryOptionsUtils; import org.apache.pinot.core.operator.blocks.InstanceResponseBlock; +import org.apache.pinot.core.operator.blocks.results.ExceptionResultsBlock; import org.apache.pinot.core.query.executor.QueryExecutor; +import org.apache.pinot.core.query.logger.ServerQueryLogger; import org.apache.pinot.core.query.request.ServerQueryRequest; import org.apache.pinot.core.query.request.context.TimerContext; import org.apache.pinot.core.query.scheduler.resources.ResourceManager; import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.exception.EarlyTerminationException; +import org.apache.pinot.spi.exception.QueryCancelledException; import org.apache.pinot.spi.trace.Tracing; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,24 +53,15 @@ * Abstract class providing common scheduler functionality * including query runner and query worker pool */ -@SuppressWarnings("UnstableApiUsage") public abstract class QueryScheduler { private static final Logger LOGGER = LoggerFactory.getLogger(QueryScheduler.class); - private static final String INVALID_NUM_SCANNED = "-1"; - private static final String INVALID_SEGMENTS_COUNT = "-1"; - private static final String INVALID_FRESHNESS_MS = "-1"; - private static final String INVALID_NUM_RESIZES = "-1"; - private static final String INVALID_RESIZE_TIME_MS = "-1"; - private static final String QUERY_LOG_MAX_RATE_KEY = "query.log.maxRatePerSecond"; - private static final double DEFAULT_QUERY_LOG_MAX_RATE = 10_000d; protected final ServerMetrics _serverMetrics; protected final QueryExecutor _queryExecutor; protected final ResourceManager _resourceManager; protected final LongAccumulator _latestQueryTime; - private final RateLimiter _queryLogRateLimiter; - private final RateLimiter _numDroppedLogRateLimiter; - private final AtomicInteger _numDroppedLogCounter; + protected final ServerQueryLogger _queryLogger = ServerQueryLogger.getInstance(); + protected volatile boolean _isRunning = false; /** @@ -90,10 +82,6 @@ public QueryScheduler(PinotConfiguration config, QueryExecutor queryExecutor, Re _resourceManager = resourceManager; _queryExecutor = queryExecutor; _latestQueryTime = latestQueryTime; - _queryLogRateLimiter = RateLimiter.create(config.getProperty(QUERY_LOG_MAX_RATE_KEY, DEFAULT_QUERY_LOG_MAX_RATE)); - _numDroppedLogRateLimiter = RateLimiter.create(1.0d); - _numDroppedLogCounter = new AtomicInteger(0); - LOGGER.info("Query log max rate: {}", _queryLogRateLimiter.getRate()); } /** @@ -145,6 +133,7 @@ protected ListenableFutureTask createQueryFutureTask(ServerQueryRequest @Nullable protected byte[] processQueryAndSerialize(ServerQueryRequest queryRequest, ExecutorService executorService) { + //Start instrumentation context. This must not be moved further below interspersed into the code. Tracing.ThreadAccountantOps.setupRunner(queryRequest.getQueryId()); _latestQueryTime.accumulate(System.currentTimeMillis()); @@ -164,133 +153,29 @@ protected byte[] processQueryAndSerialize(ServerQueryRequest queryRequest, Execu long requestId = queryRequest.getRequestId(); Map responseMetadata = instanceResponse.getResponseMetadata(); responseMetadata.put(MetadataKey.REQUEST_ID.getName(), Long.toString(requestId)); - byte[] responseBytes = serializeResponse(queryRequest, instanceResponse); // Log the statistics - String tableNameWithType = queryRequest.getTableNameWithType(); - long numDocsScanned = - Long.parseLong(responseMetadata.getOrDefault(MetadataKey.NUM_DOCS_SCANNED.getName(), INVALID_NUM_SCANNED)); - long numEntriesScannedInFilter = Long.parseLong( - responseMetadata.getOrDefault(MetadataKey.NUM_ENTRIES_SCANNED_IN_FILTER.getName(), INVALID_NUM_SCANNED)); - long numEntriesScannedPostFilter = Long.parseLong( - responseMetadata.getOrDefault(MetadataKey.NUM_ENTRIES_SCANNED_POST_FILTER.getName(), INVALID_NUM_SCANNED)); - long numSegmentsProcessed = Long.parseLong( - responseMetadata.getOrDefault(MetadataKey.NUM_SEGMENTS_PROCESSED.getName(), INVALID_SEGMENTS_COUNT)); - long numSegmentsMatched = Long.parseLong( - responseMetadata.getOrDefault(MetadataKey.NUM_SEGMENTS_MATCHED.getName(), INVALID_SEGMENTS_COUNT)); - long numSegmentsPrunedInvalid = Long.parseLong( - responseMetadata.getOrDefault(MetadataKey.NUM_SEGMENTS_PRUNED_INVALID.getName(), INVALID_SEGMENTS_COUNT)); - long numSegmentsPrunedByLimit = Long.parseLong( - responseMetadata.getOrDefault(MetadataKey.NUM_SEGMENTS_PRUNED_BY_LIMIT.getName(), INVALID_SEGMENTS_COUNT)); - long numSegmentsPrunedByValue = Long.parseLong( - responseMetadata.getOrDefault(MetadataKey.NUM_SEGMENTS_PRUNED_BY_VALUE.getName(), INVALID_SEGMENTS_COUNT)); - long numSegmentsConsuming = Long.parseLong( - responseMetadata.getOrDefault(MetadataKey.NUM_CONSUMING_SEGMENTS_QUERIED.getName(), INVALID_SEGMENTS_COUNT)); - long numConsumingSegmentsProcessed = Long.parseLong( - responseMetadata.getOrDefault(MetadataKey.NUM_CONSUMING_SEGMENTS_PROCESSED.getName(), - INVALID_SEGMENTS_COUNT)); - long numConsumingSegmentsMatched = Long.parseLong( - responseMetadata.getOrDefault(MetadataKey.NUM_CONSUMING_SEGMENTS_MATCHED.getName(), INVALID_SEGMENTS_COUNT)); - long minConsumingFreshnessMs = Long.parseLong( - responseMetadata.getOrDefault(MetadataKey.MIN_CONSUMING_FRESHNESS_TIME_MS.getName(), INVALID_FRESHNESS_MS)); - int numResizes = - Integer.parseInt(responseMetadata.getOrDefault(MetadataKey.NUM_RESIZES.getName(), INVALID_NUM_RESIZES)); - long resizeTimeMs = - Long.parseLong(responseMetadata.getOrDefault(MetadataKey.RESIZE_TIME_MS.getName(), INVALID_RESIZE_TIME_MS)); - long threadCpuTimeNs = - Long.parseLong(responseMetadata.getOrDefault(MetadataKey.THREAD_CPU_TIME_NS.getName(), "0")); - long systemActivitiesCpuTimeNs = - Long.parseLong(responseMetadata.getOrDefault(MetadataKey.SYSTEM_ACTIVITIES_CPU_TIME_NS.getName(), "0")); - long responseSerializationCpuTimeNs = - Long.parseLong(responseMetadata.getOrDefault(MetadataKey.RESPONSE_SER_CPU_TIME_NS.getName(), "0")); - long totalCpuTimeNs = threadCpuTimeNs + systemActivitiesCpuTimeNs + responseSerializationCpuTimeNs; - - if (numDocsScanned > 0) { - _serverMetrics.addMeteredTableValue(tableNameWithType, ServerMeter.NUM_DOCS_SCANNED, numDocsScanned); - } - if (numEntriesScannedInFilter > 0) { - _serverMetrics.addMeteredTableValue(tableNameWithType, ServerMeter.NUM_ENTRIES_SCANNED_IN_FILTER, - numEntriesScannedInFilter); - } - if (numEntriesScannedPostFilter > 0) { - _serverMetrics.addMeteredTableValue(tableNameWithType, ServerMeter.NUM_ENTRIES_SCANNED_POST_FILTER, - numEntriesScannedPostFilter); - } - if (numResizes > 0) { - _serverMetrics.addMeteredTableValue(tableNameWithType, ServerMeter.NUM_RESIZES, numResizes); - } - if (resizeTimeMs > 0) { - _serverMetrics.addValueToTableGauge(tableNameWithType, ServerGauge.RESIZE_TIME_MS, resizeTimeMs); - } - if (threadCpuTimeNs > 0) { - _serverMetrics.addTimedTableValue(tableNameWithType, ServerTimer.EXECUTION_THREAD_CPU_TIME_NS, threadCpuTimeNs, - TimeUnit.NANOSECONDS); - } - if (systemActivitiesCpuTimeNs > 0) { - _serverMetrics.addTimedTableValue(tableNameWithType, ServerTimer.SYSTEM_ACTIVITIES_CPU_TIME_NS, - systemActivitiesCpuTimeNs, TimeUnit.NANOSECONDS); - } - if (responseSerializationCpuTimeNs > 0) { - _serverMetrics.addTimedTableValue(tableNameWithType, ServerTimer.RESPONSE_SER_CPU_TIME_NS, - responseSerializationCpuTimeNs, TimeUnit.NANOSECONDS); - } - if (totalCpuTimeNs > 0) { - _serverMetrics.addTimedTableValue(tableNameWithType, ServerTimer.TOTAL_CPU_TIME_NS, totalCpuTimeNs, - TimeUnit.NANOSECONDS); - } - - TimerContext timerContext = queryRequest.getTimerContext(); - int numSegmentsQueried = queryRequest.getSegmentsToQuery().size(); - long schedulerWaitMs = timerContext.getPhaseDurationMs(ServerQueryPhase.SCHEDULER_WAIT); - - // Please keep the format as name=value comma-separated with no spaces - // Please add new entries at the end - if (_queryLogRateLimiter.tryAcquire() || forceLog(schedulerWaitMs, numDocsScanned, numSegmentsPrunedInvalid)) { - LOGGER.info("Processed requestId={},table={}," - + "segments(queried/processed/matched/consumingQueried/consumingProcessed/consumingMatched/" - + "invalid/limit/value)={}/{}/{}/{}/{}/{}/{}/{}/{}," - + "schedulerWaitMs={},reqDeserMs={},totalExecMs={},resSerMs={},totalTimeMs={}," - + "minConsumingFreshnessMs={},broker={},numDocsScanned={},scanInFilter={},scanPostFilter={},sched={}," - + "threadCpuTimeNs(total/thread/sysActivity/resSer)={}/{}/{}/{}", requestId, tableNameWithType, - numSegmentsQueried, numSegmentsProcessed, numSegmentsMatched, numSegmentsConsuming, - numConsumingSegmentsProcessed, numConsumingSegmentsMatched, numSegmentsPrunedInvalid, - numSegmentsPrunedByLimit, numSegmentsPrunedByValue, schedulerWaitMs, - timerContext.getPhaseDurationMs(ServerQueryPhase.REQUEST_DESERIALIZATION), - timerContext.getPhaseDurationMs(ServerQueryPhase.QUERY_PROCESSING), - timerContext.getPhaseDurationMs(ServerQueryPhase.RESPONSE_SERIALIZATION), - timerContext.getPhaseDurationMs(ServerQueryPhase.TOTAL_QUERY_TIME), minConsumingFreshnessMs, - queryRequest.getBrokerId(), numDocsScanned, numEntriesScannedInFilter, numEntriesScannedPostFilter, name(), - totalCpuTimeNs, threadCpuTimeNs, systemActivitiesCpuTimeNs, responseSerializationCpuTimeNs); - - // Limit the dropping log message at most once per second. - if (_numDroppedLogRateLimiter.tryAcquire()) { - // NOTE: the reported number may not be accurate since we will be missing some increments happened between - // get() and set(). - int numDroppedLog = _numDroppedLogCounter.get(); - if (numDroppedLog > 0) { - LOGGER.info("{} logs were dropped. (log max rate per second: {})", numDroppedLog, - _queryLogRateLimiter.getRate()); - _numDroppedLogCounter.set(0); - } - } - } else { - _numDroppedLogCounter.incrementAndGet(); + if (_queryLogger != null) { + _queryLogger.logQuery(queryRequest, instanceResponse, name()); } - if (minConsumingFreshnessMs > -1) { - _serverMetrics.addTimedTableValue(tableNameWithType, ServerTimer.FRESHNESS_LAG_MS, - (System.currentTimeMillis() - minConsumingFreshnessMs), TimeUnit.MILLISECONDS); + // TODO: Perform this check sooner during the serialization of DataTable. + Map queryOptions = queryRequest.getQueryContext().getQueryOptions(); + Long maxResponseSizeBytes = QueryOptionsUtils.getMaxServerResponseSizeBytes(queryOptions); + if (maxResponseSizeBytes != null && responseBytes != null && responseBytes.length > maxResponseSizeBytes) { + String errMsg = + String.format("Serialized query response size %d exceeds threshold %d for requestId %d from broker %s", + responseBytes.length, maxResponseSizeBytes, queryRequest.getRequestId(), queryRequest.getBrokerId()); + LOGGER.error(errMsg); + _serverMetrics.addMeteredTableValue(queryRequest.getTableNameWithType(), + ServerMeter.LARGE_QUERY_RESPONSE_SIZE_EXCEPTIONS, 1); + + instanceResponse = new InstanceResponseBlock(); + instanceResponse.addException(QueryException.getException(QueryException.QUERY_CANCELLATION_ERROR, errMsg)); + instanceResponse.addMetadata(MetadataKey.REQUEST_ID.getName(), Long.toString(requestId)); + responseBytes = serializeResponse(queryRequest, instanceResponse); } - _serverMetrics.addMeteredTableValue(tableNameWithType, ServerMeter.NUM_SEGMENTS_QUERIED, numSegmentsQueried); - _serverMetrics.addMeteredTableValue(tableNameWithType, ServerMeter.NUM_SEGMENTS_PROCESSED, numSegmentsProcessed); - _serverMetrics.addMeteredTableValue(tableNameWithType, ServerMeter.NUM_SEGMENTS_MATCHED, numSegmentsMatched); - _serverMetrics.addMeteredTableValue(tableNameWithType, ServerMeter.NUM_SEGMENTS_PRUNED_INVALID, - numSegmentsPrunedInvalid); - _serverMetrics.addMeteredTableValue(tableNameWithType, ServerMeter.NUM_SEGMENTS_PRUNED_BY_LIMIT, - numSegmentsPrunedByLimit); - _serverMetrics.addMeteredTableValue(tableNameWithType, ServerMeter.NUM_SEGMENTS_PRUNED_BY_VALUE, - numSegmentsPrunedByValue); return responseBytes; } finally { @@ -334,6 +219,14 @@ private byte[] serializeResponse(ServerQueryRequest queryRequest, InstanceRespon byte[] responseByte = null; try { responseByte = instanceResponse.toDataTable().toBytes(); + } catch (EarlyTerminationException e) { + Exception killedErrorMsg = Tracing.getThreadAccountant().getErrorStatus(); + String errMsg = + "Cancelled while building data table" + (killedErrorMsg == null ? StringUtils.EMPTY : " " + killedErrorMsg); + LOGGER.error(errMsg); + instanceResponse = new InstanceResponseBlock(new ExceptionResultsBlock(new QueryCancelledException(errMsg, e))); + instanceResponse.addMetadata(MetadataKey.REQUEST_ID.getName(), Long.toString(queryRequest.getRequestId())); + return serializeResponse(queryRequest, instanceResponse); } catch (Exception e) { _serverMetrics.addMeteredGlobalValue(ServerMeter.RESPONSE_SERIALIZATION_EXCEPTIONS, 1); LOGGER.error("Caught exception while serializing response for requestId: {}, brokerId: {}", diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/scheduler/resources/ResourceLimitPolicy.java b/pinot-core/src/main/java/org/apache/pinot/core/query/scheduler/resources/ResourceLimitPolicy.java index 2aa9785891bb..417131fe59d7 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/scheduler/resources/ResourceLimitPolicy.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/scheduler/resources/ResourceLimitPolicy.java @@ -42,7 +42,7 @@ public class ResourceLimitPolicy { private final int _tableThreadsSoftLimit; private final int _tableThreadsHardLimit; - ResourceLimitPolicy(PinotConfiguration config, int numWorkerThreads) { + public ResourceLimitPolicy(PinotConfiguration config, int numWorkerThreads) { int softLimit = checkGetOrDefaultPct(config, TABLE_THREADS_SOFT_LIMIT, DEFAULT_TABLE_THREADS_SOFT_LIMIT); _tableThreadsSoftLimit = Math.min(numWorkerThreads, Math.max(1, numWorkerThreads * softLimit / 100)); int hardLimit = checkGetOrDefaultPct(config, TABLE_THREADS_HARD_LIMIT, DEFAULT_TABLE_THREADS_HARD_LIMIT); @@ -68,15 +68,15 @@ private int checkGetOrDefaultPct(PinotConfiguration schedulerConfig, String key, return pct; } - int getMaxThreadsPerQuery() { + public int getMaxThreadsPerQuery() { return _maxThreadsPerQuery; } - int getTableThreadsSoftLimit() { + public int getTableThreadsSoftLimit() { return _tableThreadsSoftLimit; } - int getTableThreadsHardLimit() { + public int getTableThreadsHardLimit() { return _tableThreadsHardLimit; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/scheduler/resources/ResourceManager.java b/pinot-core/src/main/java/org/apache/pinot/core/query/scheduler/resources/ResourceManager.java index 0216ca3f291f..9414dc11edde 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/scheduler/resources/ResourceManager.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/scheduler/resources/ResourceManager.java @@ -28,7 +28,6 @@ import org.apache.pinot.core.query.scheduler.SchedulerGroupAccountant; import org.apache.pinot.core.util.trace.TracedThreadFactory; import org.apache.pinot.spi.env.PinotConfiguration; -import org.apache.pinot.spi.trace.Tracing; import org.apache.pinot.spi.utils.CommonConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -94,8 +93,6 @@ public ResourceManager(PinotConfiguration config) { CommonConstants.ExecutorService.PINOT_QUERY_WORKER_NAME_FORMAT); _queryWorkers = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(_numQueryWorkerThreads, queryWorkersFactory)); - - Tracing.ThreadAccountantOps.initializeThreadAccountant(_numQueryRunnerThreads, _numQueryWorkerThreads, config); } public void stop() { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/selection/SelectionOperatorService.java b/pinot-core/src/main/java/org/apache/pinot/core/query/selection/SelectionOperatorService.java index 525ab7af9ec0..7db96f522431 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/selection/SelectionOperatorService.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/selection/SelectionOperatorService.java @@ -20,13 +20,13 @@ import java.util.Collection; import java.util.LinkedList; -import java.util.List; import java.util.PriorityQueue; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.response.broker.ResultTable; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.query.request.context.QueryContext; -import org.apache.pinot.spi.utils.LoopUtils; +import org.apache.pinot.core.query.utils.OrderByComparatorFactory; +import org.apache.pinot.spi.trace.Tracing; import org.roaringbitmap.RoaringBitmap; @@ -55,57 +55,41 @@ * * */ -@SuppressWarnings("rawtypes") public class SelectionOperatorService { private final QueryContext _queryContext; - private final List _selectionColumns; private final DataSchema _dataSchema; + private final int[] _columnIndices; private final int _offset; private final int _numRowsToKeep; private final PriorityQueue _rows; - /** - * Constructor for SelectionOperatorService with {@link DataSchema}. (Inter segment) - * - * @param queryContext Selection order-by query - * @param dataSchema data schema. - */ - public SelectionOperatorService(QueryContext queryContext, DataSchema dataSchema) { + public SelectionOperatorService(QueryContext queryContext, DataSchema dataSchema, int[] columnIndices) { _queryContext = queryContext; - _selectionColumns = SelectionOperatorUtils.getSelectionColumns(queryContext, dataSchema); _dataSchema = dataSchema; + _columnIndices = columnIndices; // Select rows from offset to offset + limit. _offset = queryContext.getOffset(); _numRowsToKeep = _offset + queryContext.getLimit(); assert queryContext.getOrderByExpressions() != null; _rows = new PriorityQueue<>(Math.min(_numRowsToKeep, SelectionOperatorUtils.MAX_ROW_HOLDER_INITIAL_CAPACITY), - SelectionOperatorUtils.getTypeCompatibleComparator(queryContext.getOrderByExpressions(), _dataSchema, - _queryContext.isNullHandlingEnabled())); - } - - /** - * Get the selection results. - * - * @return selection results. - */ - public PriorityQueue getRows() { - return _rows; + OrderByComparatorFactory.getComparator(queryContext.getOrderByExpressions(), + _queryContext.isNullHandlingEnabled()).reversed()); } /** * Reduces a collection of {@link DataTable}s to selection rows for selection queries with ORDER BY. - * (Broker side) + * TODO: Do merge sort after releasing 0.13.0 when server side results are sorted + * Can also consider adding a data table metadata to indicate whether the server side results are sorted */ - public void reduceWithOrdering(Collection dataTables, boolean nullHandlingEnabled) { + public void reduceWithOrdering(Collection dataTables) { for (DataTable dataTable : dataTables) { int numRows = dataTable.getNumberOfRows(); - if (nullHandlingEnabled) { + if (_queryContext.isNullHandlingEnabled()) { RoaringBitmap[] nullBitmaps = new RoaringBitmap[dataTable.getDataSchema().size()]; for (int colId = 0; colId < nullBitmaps.length; colId++) { nullBitmaps[colId] = dataTable.getNullRowIds(colId); } for (int rowId = 0; rowId < numRows; rowId++) { - LoopUtils.checkMergePhaseInterruption(rowId); Object[] row = SelectionOperatorUtils.extractRowFromDataTable(dataTable, rowId); for (int colId = 0; colId < nullBitmaps.length; colId++) { if (nullBitmaps[colId] != null && nullBitmaps[colId].contains(rowId)) { @@ -113,44 +97,37 @@ public void reduceWithOrdering(Collection dataTables, boolean nullHan } } SelectionOperatorUtils.addToPriorityQueue(row, _rows, _numRowsToKeep); + Tracing.ThreadAccountantOps.sampleAndCheckInterruptionPeriodically(rowId); } } else { for (int rowId = 0; rowId < numRows; rowId++) { - LoopUtils.checkMergePhaseInterruption(rowId); Object[] row = SelectionOperatorUtils.extractRowFromDataTable(dataTable, rowId); SelectionOperatorUtils.addToPriorityQueue(row, _rows, _numRowsToKeep); + Tracing.ThreadAccountantOps.sampleAndCheckInterruptionPeriodically(rowId); } } } } /** - * Render the selection rows to a {@link ResultTable} object for selection queries with ORDER BY. - * (Broker side) - *

    {@link ResultTable} object will be used to build the broker response. - *

    Should be called after method "reduceWithOrdering()". + * Renders the selection rows to a {@link ResultTable} object for selection queries with ORDER BY. */ public ResultTable renderResultTableWithOrdering() { - int[] columnIndices = SelectionOperatorUtils.getColumnIndices(_selectionColumns, _dataSchema); - int numColumns = columnIndices.length; - DataSchema resultDataSchema = SelectionOperatorUtils.getSchemaForProjection(_dataSchema, columnIndices); - - // Extract the result rows - LinkedList rowsInSelectionResults = new LinkedList<>(); + LinkedList resultRows = new LinkedList<>(); + DataSchema.ColumnDataType[] columnDataTypes = _dataSchema.getColumnDataTypes(); + int numColumns = columnDataTypes.length; while (_rows.size() > _offset) { Object[] row = _rows.poll(); assert row != null; - Object[] extractedRow = new Object[numColumns]; + Object[] resultRow = new Object[numColumns]; for (int i = 0; i < numColumns; i++) { - Object value = row[columnIndices[i]]; + Object value = row[_columnIndices[i]]; if (value != null) { - extractedRow[i] = resultDataSchema.getColumnDataType(i).convertAndFormat(value); + resultRow[i] = columnDataTypes[i].convertAndFormat(value); } } - - rowsInSelectionResults.addFirst(extractedRow); + resultRows.addFirst(resultRow); } - - return new ResultTable(resultDataSchema, rowsInSelectionResults); + return new ResultTable(_dataSchema, resultRows); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/selection/SelectionOperatorUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/query/selection/SelectionOperatorUtils.java index bda3efab0b64..9d4bc6dd8bd5 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/selection/SelectionOperatorUtils.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/selection/SelectionOperatorUtils.java @@ -18,6 +18,8 @@ */ package org.apache.pinot.core.query.selection; +import com.google.common.base.Preconditions; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; import java.io.IOException; import java.math.BigDecimal; import java.util.ArrayList; @@ -25,13 +27,11 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; import java.util.HashSet; -import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.PriorityQueue; import java.util.Set; +import org.apache.commons.lang3.tuple.Pair; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.OrderByExpressionContext; @@ -40,11 +40,11 @@ import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.common.datatable.DataTableBuilder; import org.apache.pinot.core.common.datatable.DataTableBuilderFactory; +import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.IndexSegment; -import org.apache.pinot.spi.utils.ArrayCopyUtils; +import org.apache.pinot.spi.trace.Tracing; import org.apache.pinot.spi.utils.ByteArray; -import org.apache.pinot.spi.utils.LoopUtils; import org.roaringbitmap.RoaringBitmap; @@ -167,79 +167,195 @@ public static List getSelectionColumns(QueryContext queryContext, DataSc } /** - * Constructs the final selection DataSchema based on the order of selection columns (data schema can have a - * different order, depending on order by clause) - * @param dataSchema data schema used for execution and ordering - * @param selectionColumns the selection order - * @return data schema for final results + * Returns the data schema and column indices of the final selection results based on the query and the data schema of + * the server response. See {@link #extractExpressions} for the column orders on the server side. + * NOTE: DO NOT rely on column name lookup across query context and data schema because the string representation of + * expression can change, which will cause backward incompatibility. */ - public static DataSchema getResultTableDataSchema(DataSchema dataSchema, List selectionColumns) { - Map columnNameToDataType = new HashMap<>(); - String[] columnNames = dataSchema.getColumnNames(); - ColumnDataType[] columnDataTypes = dataSchema.getColumnDataTypes(); - int numColumns = columnNames.length; - for (int i = 0; i < numColumns; i++) { - columnNameToDataType.put(columnNames[i], columnDataTypes[i]); + public static Pair getResultTableDataSchemaAndColumnIndices(QueryContext queryContext, + DataSchema dataSchema) { + List selectExpressions = queryContext.getSelectExpressions(); + int numSelectExpressions = selectExpressions.size(); + ColumnDataType[] columnDataTypesInDataSchema = dataSchema.getColumnDataTypes(); + int numColumnsInDataSchema = columnDataTypesInDataSchema.length; + + // No order-by expression + // NOTE: Order-by expressions are ignored for queries with LIMIT 0. + List orderByExpressions = queryContext.getOrderByExpressions(); + if (orderByExpressions == null || queryContext.getLimit() == 0) { + // For 'SELECT *', use the server response data schema as the final results data schema. + if ((numSelectExpressions == 1 && selectExpressions.get(0).equals(IDENTIFIER_STAR))) { + int[] columnIndices = new int[numColumnsInDataSchema]; + for (int i = 0; i < numColumnsInDataSchema; i++) { + columnIndices[i] = i; + } + return Pair.of(dataSchema, columnIndices); + } + + // For select without duplicate columns, the order of the final selection columns is the same as the order of the + // columns in the data schema. + if (numSelectExpressions == numColumnsInDataSchema) { + String[] columnNames = new String[numSelectExpressions]; + int[] columnIndices = new int[numSelectExpressions]; + for (int i = 0; i < numSelectExpressions; i++) { + columnNames[i] = selectExpressions.get(i).toString(); + columnIndices[i] = i; + } + return Pair.of(new DataSchema(columnNames, columnDataTypesInDataSchema), columnIndices); + } + + // For select with duplicate columns, construct a map from expression to index with the same order as the data + // schema, then look up the selection expressions. + Object2IntOpenHashMap expressionIndexMap = new Object2IntOpenHashMap<>(numColumnsInDataSchema); + for (ExpressionContext selectExpression : selectExpressions) { + expressionIndexMap.putIfAbsent(selectExpression, expressionIndexMap.size()); + } + Preconditions.checkState(expressionIndexMap.size() == numColumnsInDataSchema, + "BUG: Expect same number of deduped columns in SELECT clause and in data schema, got %s before dedup and %s" + + " after dedup in SELECT clause, %s in data schema", numSelectExpressions, expressionIndexMap.size(), + numColumnsInDataSchema); + String[] columnNames = new String[numSelectExpressions]; + ColumnDataType[] columnDataTypes = new ColumnDataType[numSelectExpressions]; + int[] columnIndices = new int[numSelectExpressions]; + for (int i = 0; i < numSelectExpressions; i++) { + ExpressionContext selectExpression = selectExpressions.get(i); + int columnIndex = expressionIndexMap.getInt(selectExpression); + columnNames[i] = selectExpression.toString(); + columnDataTypes[i] = columnDataTypesInDataSchema[columnIndex]; + columnIndices[i] = columnIndex; + } + return Pair.of(new DataSchema(columnNames, columnDataTypes), columnIndices); } - int numResultColumns = selectionColumns.size(); - ColumnDataType[] finalColumnDataTypes = new ColumnDataType[numResultColumns]; - for (int i = 0; i < numResultColumns; i++) { - finalColumnDataTypes[i] = columnNameToDataType.get(selectionColumns.get(i)); + + // For 'SELECT *' with order-by, exclude transform functions from the returned columns and sort. + if (numSelectExpressions == 1 && selectExpressions.get(0).equals(IDENTIFIER_STAR)) { + String[] columnNamesInDataSchema = dataSchema.getColumnNames(); + List columnIndexList = new ArrayList<>(columnNamesInDataSchema.length); + for (int i = 0; i < columnNamesInDataSchema.length; i++) { + if (columnNamesInDataSchema[i].indexOf('(') == -1) { + columnIndexList.add(i); + } + } + columnIndexList.sort(Comparator.comparing(o -> columnNamesInDataSchema[o])); + int numColumns = columnIndexList.size(); + String[] columnNames = new String[numColumns]; + ColumnDataType[] columnDataTypes = new ColumnDataType[numColumns]; + int[] columnIndices = new int[numColumns]; + for (int i = 0; i < numColumns; i++) { + int columnIndex = columnIndexList.get(i); + columnNames[i] = columnNamesInDataSchema[columnIndex]; + columnDataTypes[i] = columnDataTypesInDataSchema[columnIndex]; + columnIndices[i] = columnIndex; + } + return Pair.of(new DataSchema(columnNames, columnDataTypes), columnIndices); + } + + // For other order-by queries, construct a map from expression to index with the same order as the data schema, + // then look up the selection expressions. + Object2IntOpenHashMap expressionIndexMap = new Object2IntOpenHashMap<>(numColumnsInDataSchema); + // NOTE: Order-by expressions are already deduped in QueryContext. + for (OrderByExpressionContext orderByExpression : orderByExpressions) { + expressionIndexMap.put(orderByExpression.getExpression(), expressionIndexMap.size()); } - return new DataSchema(selectionColumns.toArray(new String[0]), finalColumnDataTypes); + for (ExpressionContext selectExpression : selectExpressions) { + expressionIndexMap.putIfAbsent(selectExpression, expressionIndexMap.size()); + } + String[] columnNames = new String[numSelectExpressions]; + ColumnDataType[] columnDataTypes = new ColumnDataType[numSelectExpressions]; + int[] columnIndices = new int[numSelectExpressions]; + if (expressionIndexMap.size() == numColumnsInDataSchema) { + for (int i = 0; i < numSelectExpressions; i++) { + ExpressionContext selectExpression = selectExpressions.get(i); + int columnIndex = expressionIndexMap.getInt(selectExpression); + columnNames[i] = selectExpression.toString(); + columnDataTypes[i] = columnDataTypesInDataSchema[columnIndex]; + columnIndices[i] = columnIndex; + } + } else { + // When all segments are pruned on the server side, the data schema will only contain the columns in the SELECT + // clause, and data type for all columns are set to STRING. See ResultBlocksUtils for details. + for (int i = 0; i < numSelectExpressions; i++) { + columnNames[i] = selectExpressions.get(i).toString(); + columnDataTypes[i] = ColumnDataType.STRING; + columnIndices[i] = i; + } + } + return Pair.of(new DataSchema(columnNames, columnDataTypes), columnIndices); } /** * Merge two partial results for selection queries without ORDER BY. (Server side) * - * @param mergedRows partial results 1. - * @param rowsToMerge partial results 2. + * @param mergedBlock partial results 1. + * @param blockToMerge partial results 2. * @param selectionSize size of the selection. */ - public static void mergeWithoutOrdering(Collection mergedRows, Collection rowsToMerge, + public static void mergeWithoutOrdering(SelectionResultsBlock mergedBlock, SelectionResultsBlock blockToMerge, int selectionSize) { - Iterator iterator = rowsToMerge.iterator(); - int numMergedRows = 0; - while (mergedRows.size() < selectionSize && iterator.hasNext()) { - LoopUtils.checkMergePhaseInterruption(numMergedRows); - mergedRows.add(iterator.next()); - numMergedRows++; + List mergedRows = mergedBlock.getRows(); + List rowsToMerge = blockToMerge.getRows(); + int numRowsToMerge = Math.min(selectionSize - mergedRows.size(), rowsToMerge.size()); + if (numRowsToMerge > 0) { + mergedRows.addAll(rowsToMerge.subList(0, numRowsToMerge)); } } /** * Merge two partial results for selection queries with ORDER BY. (Server side) - * TODO: Should use type compatible comparator to compare the rows * - * @param mergedRows partial results 1. - * @param rowsToMerge partial results 2. + * @param mergedBlock partial results 1 (sorted). + * @param blockToMerge partial results 2 (sorted). * @param maxNumRows maximum number of rows need to be stored. */ - public static void mergeWithOrdering(PriorityQueue mergedRows, Collection rowsToMerge, + public static void mergeWithOrdering(SelectionResultsBlock mergedBlock, SelectionResultsBlock blockToMerge, int maxNumRows) { + List sortedRows1 = mergedBlock.getRows(); + List sortedRows2 = blockToMerge.getRows(); + Comparator comparator = mergedBlock.getComparator(); + assert comparator != null; + int numSortedRows1 = sortedRows1.size(); + int numSortedRows2 = sortedRows2.size(); + if (numSortedRows1 == 0) { + mergedBlock.setRows(sortedRows2); + return; + } + if (numSortedRows2 == 0 || (numSortedRows1 == maxNumRows + && comparator.compare(sortedRows1.get(numSortedRows1 - 1), sortedRows2.get(0)) <= 0)) { + return; + } + int numRowsToMerge = Math.min(numSortedRows1 + numSortedRows2, maxNumRows); + List mergedRows = new ArrayList<>(numRowsToMerge); + int i1 = 0; + int i2 = 0; int numMergedRows = 0; - for (Object[] row : rowsToMerge) { - LoopUtils.checkMergePhaseInterruption(numMergedRows); - addToPriorityQueue(row, mergedRows, maxNumRows); - numMergedRows++; + while (i1 < numSortedRows1 && i2 < numSortedRows2 && numMergedRows < numRowsToMerge) { + Object[] row1 = sortedRows1.get(i1); + Object[] row2 = sortedRows2.get(i2); + if (comparator.compare(row1, row2) <= 0) { + mergedRows.add(row1); + i1++; + } else { + mergedRows.add(row2); + i2++; + } + Tracing.ThreadAccountantOps.sampleAndCheckInterruptionPeriodically(numMergedRows++); + } + if (numMergedRows < numRowsToMerge) { + if (i1 < numSortedRows1) { + assert i2 == numSortedRows2; + mergedRows.addAll(sortedRows1.subList(i1, i1 + numRowsToMerge - numMergedRows)); + } else { + assert i1 == numSortedRows1; + mergedRows.addAll(sortedRows2.subList(i2, i2 + numRowsToMerge - numMergedRows)); + } } + mergedBlock.setRows(mergedRows); } /** * Build a {@link DataTable} from a {@link Collection} of selection rows with {@link DataSchema}. (Server side) - *

    The passed in data schema stored the column data type that can cover all actual data types for that column. - *

    The actual data types for each column in rows can be different but must be compatible with each other. - *

    Before write each row into the data table, first convert it to match the data types in data schema. - * - * TODO: Type compatibility is not supported for selection order-by because all segments on the same server shared the - * same comparator. Another solution is to always use the table schema to execute the query (preferable because - * type compatible checks are expensive). * - * @param rows {@link Collection} of selection rows. - * @param dataSchema data schema. - * @param nullHandlingEnabled whether null handling is enabled. - * @return data table. - * @throws IOException + * This method is allowed to modify the given rows. Specifically, it may remove nulls cells from it. */ public static DataTable getDataTableFromRows(Collection rows, DataSchema dataSchema, boolean nullHandlingEnabled) @@ -269,76 +385,51 @@ public static DataTable getDataTableFromRows(Collection rows, DataSche } } + int numRowsAdded = 0; for (Object[] row : rows) { + Tracing.ThreadAccountantOps.sampleAndCheckInterruptionPeriodically(numRowsAdded); dataTableBuilder.startRow(); for (int i = 0; i < numColumns; i++) { Object columnValue = row[i]; switch (storedColumnDataTypes[i]) { // Single-value column case INT: - dataTableBuilder.setColumn(i, ((Number) columnValue).intValue()); + dataTableBuilder.setColumn(i, (int) columnValue); break; case LONG: - dataTableBuilder.setColumn(i, ((Number) columnValue).longValue()); + dataTableBuilder.setColumn(i, (long) columnValue); break; case FLOAT: - dataTableBuilder.setColumn(i, ((Number) columnValue).floatValue()); + dataTableBuilder.setColumn(i, (float) columnValue); break; case DOUBLE: - dataTableBuilder.setColumn(i, ((Number) columnValue).doubleValue()); + dataTableBuilder.setColumn(i, (double) columnValue); break; case BIG_DECIMAL: dataTableBuilder.setColumn(i, (BigDecimal) columnValue); break; case STRING: - dataTableBuilder.setColumn(i, ((String) columnValue)); + dataTableBuilder.setColumn(i, (String) columnValue); break; case BYTES: dataTableBuilder.setColumn(i, (ByteArray) columnValue); break; + case UNKNOWN: + dataTableBuilder.setColumn(i, (Object) null); + break; // Multi-value column case INT_ARRAY: dataTableBuilder.setColumn(i, (int[]) columnValue); break; case LONG_ARRAY: - // LONG_ARRAY type covers INT_ARRAY and LONG_ARRAY - if (columnValue instanceof int[]) { - int[] ints = (int[]) columnValue; - int length = ints.length; - long[] longs = new long[length]; - ArrayCopyUtils.copy(ints, longs, length); - dataTableBuilder.setColumn(i, longs); - } else { - dataTableBuilder.setColumn(i, (long[]) columnValue); - } + dataTableBuilder.setColumn(i, (long[]) columnValue); break; case FLOAT_ARRAY: dataTableBuilder.setColumn(i, (float[]) columnValue); break; case DOUBLE_ARRAY: - // DOUBLE_ARRAY type covers INT_ARRAY, LONG_ARRAY, FLOAT_ARRAY and DOUBLE_ARRAY - if (columnValue instanceof int[]) { - int[] ints = (int[]) columnValue; - int length = ints.length; - double[] doubles = new double[length]; - ArrayCopyUtils.copy(ints, doubles, length); - dataTableBuilder.setColumn(i, doubles); - } else if (columnValue instanceof long[]) { - long[] longs = (long[]) columnValue; - int length = longs.length; - double[] doubles = new double[length]; - ArrayCopyUtils.copy(longs, doubles, length); - dataTableBuilder.setColumn(i, doubles); - } else if (columnValue instanceof float[]) { - float[] floats = (float[]) columnValue; - int length = floats.length; - double[] doubles = new double[length]; - ArrayCopyUtils.copy(floats, doubles, length); - dataTableBuilder.setColumn(i, doubles); - } else { - dataTableBuilder.setColumn(i, (double[]) columnValue); - } + dataTableBuilder.setColumn(i, (double[]) columnValue); break; case STRING_ARRAY: dataTableBuilder.setColumn(i, (String[]) columnValue); @@ -351,6 +442,7 @@ public static DataTable getDataTableFromRows(Collection rows, DataSche } } dataTableBuilder.finishRow(); + numRowsAdded++; } if (nullHandlingEnabled) { @@ -398,6 +490,9 @@ public static Object[] extractRowFromDataTable(DataTable dataTable, int rowId) { case BYTES: row[i] = dataTable.getBytes(rowId, i); break; + case UNKNOWN: + row[i] = null; + break; // Multi-value column case INT_ARRAY: @@ -460,21 +555,21 @@ public static List reduceWithoutOrdering(Collection dataTab nullBitmaps[coldId] = dataTable.getNullRowIds(coldId); } for (int rowId = 0; rowId < numRows; rowId++) { - LoopUtils.checkMergePhaseInterruption(rowId); if (rows.size() < limit) { rows.add(extractRowFromDataTableWithNullHandling(dataTable, rowId, nullBitmaps)); } else { break; } + Tracing.ThreadAccountantOps.sampleAndCheckInterruptionPeriodically(rowId); } } else { for (int rowId = 0; rowId < numRows; rowId++) { - LoopUtils.checkMergePhaseInterruption(rowId); if (rows.size() < limit) { rows.add(extractRowFromDataTable(dataTable, rowId)); } else { break; } + Tracing.ThreadAccountantOps.sampleAndCheckInterruptionPeriodically(rowId); } } } @@ -482,162 +577,26 @@ public static List reduceWithoutOrdering(Collection dataTab } /** - * Render the selection rows to a {@link ResultTable} object - * for selection queries without ORDER BY - *

    {@link ResultTable} object will be used to set in the broker response. - *

    Should be called after method "reduceWithoutOrdering()". - * - * @param rows selection rows. - * @param dataSchema data schema. - * @param selectionColumns selection columns. - * @return {@link ResultTable} object results. + * Renders the selection rows to a {@link ResultTable} object for selection queries without ORDER BY. + * (Broker side) */ public static ResultTable renderResultTableWithoutOrdering(List rows, DataSchema dataSchema, - List selectionColumns) { + int[] columnIndices) { int numRows = rows.size(); List resultRows = new ArrayList<>(numRows); - - DataSchema resultDataSchema = dataSchema; - Map columnNameToIndexMap = null; - if (dataSchema.getColumnNames().length != selectionColumns.size()) { - // Create updated data schema since one column can be selected multiple times. - columnNameToIndexMap = new HashMap<>(dataSchema.getColumnNames().length); - String[] columnNames = dataSchema.getColumnNames(); - ColumnDataType[] columnDataTypes = dataSchema.getColumnDataTypes(); - for (int i = 0; i < columnNames.length; i++) { - columnNameToIndexMap.put(columnNames[i], i); - } - - ColumnDataType[] newColumnDataTypes = new ColumnDataType[selectionColumns.size()]; - for (int i = 0; i < newColumnDataTypes.length; i++) { - int index = columnNameToIndexMap.get(selectionColumns.get(i)); - newColumnDataTypes[i] = columnDataTypes[index]; - } - - resultDataSchema = new DataSchema(selectionColumns.toArray(new String[0]), newColumnDataTypes); - } - - int numColumns = resultDataSchema.getColumnNames().length; - ColumnDataType[] resultColumnDataTypes = resultDataSchema.getColumnDataTypes(); + ColumnDataType[] columnDataTypes = dataSchema.getColumnDataTypes(); + int numColumns = columnDataTypes.length; for (Object[] row : rows) { Object[] resultRow = new Object[numColumns]; for (int i = 0; i < numColumns; i++) { - int index = (columnNameToIndexMap != null) ? columnNameToIndexMap.get(selectionColumns.get(i)) : i; - Object value = row[index]; + Object value = row[columnIndices[i]]; if (value != null) { - resultRow[i] = resultColumnDataTypes[i].convertAndFormat(value); + resultRow[i] = columnDataTypes[i].convertAndFormat(value); } } resultRows.add(resultRow); } - - return new ResultTable(resultDataSchema, resultRows); - } - - /** - * Helper method to compute column indices from selection columns and the data schema for selection queries - * @param selectionColumns selection columns. - * @param dataSchema data schema. - * @return column indices - */ - public static int[] getColumnIndices(List selectionColumns, DataSchema dataSchema) { - String[] columnNames = dataSchema.getColumnNames(); - Map columnToIndexMap = getColumnToIndexMap(columnNames); - int numSelectionColumns = selectionColumns.size(); - int[] columnIndices = new int[numSelectionColumns]; - for (int i = 0; i < numSelectionColumns; i++) { - columnIndices[i] = columnToIndexMap.get(selectionColumns.get(i)); - } - return columnIndices; - } - - public static Map getColumnToIndexMap(String[] columns) { - Map columnToIndexMap = new HashMap<>(); - int numColumns = columns.length; - for (int i = 0; i < numColumns; i++) { - columnToIndexMap.put(columns[i], i); - } - return columnToIndexMap; - } - - /** - * Helper method to get the type-compatible {@link Comparator} for selection rows. (Inter segment) - *

    Type-compatible comparator allows compatible types to compare with each other. - * - * @return flexible {@link Comparator} for selection rows. - */ - public static Comparator getTypeCompatibleComparator(List orderByExpressions, - DataSchema dataSchema, boolean isNullHandlingEnabled) { - ColumnDataType[] columnDataTypes = dataSchema.getColumnDataTypes(); - - // Compare all single-value columns - int numOrderByExpressions = orderByExpressions.size(); - List valueIndexList = new ArrayList<>(numOrderByExpressions); - for (int i = 0; i < numOrderByExpressions; i++) { - if (!columnDataTypes[i].isArray()) { - valueIndexList.add(i); - } - } - - int numValuesToCompare = valueIndexList.size(); - int[] valueIndices = new int[numValuesToCompare]; - boolean[] useDoubleComparison = new boolean[numValuesToCompare]; - // Use multiplier -1 or 1 to control ascending/descending order - int[] multipliers = new int[numValuesToCompare]; - for (int i = 0; i < numValuesToCompare; i++) { - int valueIndex = valueIndexList.get(i); - valueIndices[i] = valueIndex; - if (columnDataTypes[valueIndex].isNumber()) { - useDoubleComparison[i] = true; - } - multipliers[i] = orderByExpressions.get(valueIndex).isAsc() ? -1 : 1; - } - - if (isNullHandlingEnabled) { - return (o1, o2) -> { - for (int i = 0; i < numValuesToCompare; i++) { - int index = valueIndices[i]; - Object v1 = o1[index]; - Object v2 = o2[index]; - if (v1 == null) { - // The default null ordering is: 'NULLS LAST'. - return v2 == null ? 0 : -multipliers[i]; - } else if (v2 == null) { - return multipliers[i]; - } - int result; - if (useDoubleComparison[i]) { - result = Double.compare(((Number) v1).doubleValue(), ((Number) v2).doubleValue()); - } else { - //noinspection unchecked - result = ((Comparable) v1).compareTo(v2); - } - if (result != 0) { - return result * multipliers[i]; - } - } - return 0; - }; - } else { - return (o1, o2) -> { - for (int i = 0; i < numValuesToCompare; i++) { - int index = valueIndices[i]; - Object v1 = o1[index]; - Object v2 = o2[index]; - int result; - if (useDoubleComparison[i]) { - result = Double.compare(((Number) v1).doubleValue(), ((Number) v2).doubleValue()); - } else { - //noinspection unchecked - result = ((Comparable) v1).compareTo(v2); - } - if (result != 0) { - return result * multipliers[i]; - } - } - return 0; - }; - } + return new ResultTable(dataSchema, resultRows); } /** @@ -656,19 +615,4 @@ public static void addToPriorityQueue(T value, PriorityQueue queue, int m queue.offer(value); } } - - public static DataSchema getSchemaForProjection(DataSchema dataSchema, int[] columnIndices) { - int numColumns = columnIndices.length; - - String[] columnNames = dataSchema.getColumnNames(); - ColumnDataType[] columnDataTypes = dataSchema.getColumnDataTypes(); - String[] resultColumnNames = new String[numColumns]; - ColumnDataType[] resultColumnDataTypes = new ColumnDataType[numColumns]; - for (int i = 0; i < numColumns; i++) { - int columnIndex = columnIndices[i]; - resultColumnNames[i] = columnNames[columnIndex]; - resultColumnDataTypes[i] = columnDataTypes[columnIndex]; - } - return new DataSchema(resultColumnNames, resultColumnDataTypes); - } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/utils/OrderByComparatorFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/query/utils/OrderByComparatorFactory.java index 4258317e76e9..c9d063ead320 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/utils/OrderByComparatorFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/utils/OrderByComparatorFactory.java @@ -18,16 +18,11 @@ */ package org.apache.pinot.core.query.utils; -import com.google.common.base.Preconditions; -import java.math.BigDecimal; -import java.util.ArrayList; import java.util.Comparator; import java.util.List; import org.apache.pinot.common.request.context.OrderByExpressionContext; -import org.apache.pinot.core.operator.transform.TransformResultMetadata; -import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.core.operator.ColumnContext; import org.apache.pinot.spi.exception.BadQueryRequestException; -import org.apache.pinot.spi.utils.ByteArray; /** @@ -38,118 +33,77 @@ private OrderByComparatorFactory() { } public static Comparator getComparator(List orderByExpressions, - TransformResultMetadata[] orderByExpressionMetadata, boolean reverse, boolean nullHandlingEnabled) { - return getComparator(orderByExpressions, orderByExpressionMetadata, reverse, nullHandlingEnabled, 0, - orderByExpressions.size()); + ColumnContext[] orderByColumnContexts, boolean nullHandlingEnabled) { + return getComparator(orderByExpressions, orderByColumnContexts, nullHandlingEnabled, 0, orderByExpressions.size()); } - /** - * @param reverse if false, the comparator will order in the direction indicated by the - * {@link OrderByExpressionContext#isAsc()}. Otherwise, it will be in the opposite direction. - */ public static Comparator getComparator(List orderByExpressions, - TransformResultMetadata[] orderByExpressionMetadata, boolean reverse, boolean nullHandlingEnabled, int from, - int to) { - Preconditions.checkArgument(to <= orderByExpressions.size(), - "Trying to access %sth position of orderByExpressions with size %s", to, orderByExpressions.size()); - Preconditions.checkArgument(to <= orderByExpressionMetadata.length, - "Trying to access %sth position of orderByExpressionMetadata with size %s", to, - orderByExpressionMetadata.length); - Preconditions.checkArgument(from < to, "FROM (%s) must be lower than TO (%s)", from, to); + ColumnContext[] orderByColumnContexts, boolean nullHandlingEnabled, int from, int to) { + assert 0 <= from && from < to && to <= orderByExpressions.size(); - // Compare all single-value columns - int numOrderByExpressions = to - from; - List valueIndexList = new ArrayList<>(numOrderByExpressions); + // Check if all expressions are single-valued for (int i = from; i < to; i++) { - if (orderByExpressionMetadata[i].isSingleValue()) { - valueIndexList.add(i); - } else { - // MV columns should not be part of the selection order by only list + if (!orderByColumnContexts[i].isSingleValue()) { + // MV columns should not be part of the selection order-by list throw new BadQueryRequestException( String.format("MV expression: %s should not be included in the ORDER-BY clause", orderByExpressions.get(i))); } } - int numValuesToCompare = valueIndexList.size(); - int[] valueIndices = new int[numValuesToCompare]; - FieldSpec.DataType[] storedTypes = new FieldSpec.DataType[numValuesToCompare]; + return getComparator(orderByExpressions, nullHandlingEnabled, from, to); + } + + public static Comparator getComparator(List orderByExpressions, + boolean nullHandlingEnabled) { + return getComparator(orderByExpressions, nullHandlingEnabled, 0, orderByExpressions.size()); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + public static Comparator getComparator(List orderByExpressions, + boolean nullHandlingEnabled, int from, int to) { + assert 0 <= from && from < to && to <= orderByExpressions.size(); + // Use multiplier -1 or 1 to control ascending/descending order - int[] multipliers = new int[numValuesToCompare]; - int ascMult = reverse ? -1 : 1; - int descMult = reverse ? 1 : -1; - for (int i = 0; i < numValuesToCompare; i++) { - int valueIndex = valueIndexList.get(i); - valueIndices[i] = valueIndex; - storedTypes[i] = orderByExpressionMetadata[valueIndex].getDataType().getStoredType(); - multipliers[i] = orderByExpressions.get(valueIndex).isAsc() ? ascMult : descMult; + int[] multipliers = new int[to]; + // Use nulls multiplier -1 or 1 to control nulls last/first order + int[] nullsMultipliers = new int[to]; + for (int i = from; i < to; i++) { + multipliers[i] = orderByExpressions.get(i).isAsc() ? 1 : -1; + nullsMultipliers[i] = orderByExpressions.get(i).isNullsLast() ? 1 : -1; } if (nullHandlingEnabled) { return (Object[] o1, Object[] o2) -> { - for (int i = 0; i < numValuesToCompare; i++) { - int index = valueIndices[i]; - // TODO: Evaluate the performance of casting to Comparable and avoid the switch - Object v1 = o1[index]; - Object v2 = o2[index]; - if (v1 == null) { - // The default null ordering is: 'NULLS LAST', regardless of the ordering direction. - return v2 == null ? 0 : -multipliers[i]; + for (int i = from; i < to; i++) { + Comparable v1 = (Comparable) o1[i]; + Comparable v2 = (Comparable) o2[i]; + if (v1 == null && v2 == null) { + continue; + } else if (v1 == null) { + return nullsMultipliers[i]; } else if (v2 == null) { - return multipliers[i]; + return -nullsMultipliers[i]; } - int result = compareCols(v1, v2, storedTypes[i], multipliers[i]); + int result = v1.compareTo(v2); if (result != 0) { - return result; + return result * multipliers[i]; } } return 0; }; } else { return (Object[] o1, Object[] o2) -> { - for (int i = 0; i < numValuesToCompare; i++) { - int index = valueIndices[i]; - // TODO: Evaluate the performance of casting to Comparable and avoid the switch - int result = compareCols(o1[index], o2[index], storedTypes[i], multipliers[i]); + for (int i = from; i < to; i++) { + Comparable v1 = (Comparable) o1[i]; + Comparable v2 = (Comparable) o2[i]; + int result = v1.compareTo(v2); if (result != 0) { - return result; + return result * multipliers[i]; } } return 0; }; } } - - private static int compareCols(Object v1, Object v2, FieldSpec.DataType type, int multiplier) { - - // TODO: Evaluate the performance of casting to Comparable and avoid the switch - int result; - switch (type) { - case INT: - result = ((Integer) v1).compareTo((Integer) v2); - break; - case LONG: - result = ((Long) v1).compareTo((Long) v2); - break; - case FLOAT: - result = ((Float) v1).compareTo((Float) v2); - break; - case DOUBLE: - result = ((Double) v1).compareTo((Double) v2); - break; - case BIG_DECIMAL: - result = ((BigDecimal) v1).compareTo((BigDecimal) v2); - break; - case STRING: - result = ((String) v1).compareTo((String) v2); - break; - case BYTES: - result = ((ByteArray) v1).compareTo((ByteArray) v2); - break; - // NOTE: Multi-value columns are not comparable, so we should not reach here - default: - throw new IllegalStateException(); - } - return result * multiplier; - } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/utils/QueryIdUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/query/utils/QueryIdUtils.java index e45e32d366e6..48c0dccac58f 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/query/utils/QueryIdUtils.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/utils/QueryIdUtils.java @@ -30,8 +30,8 @@ public class QueryIdUtils { private QueryIdUtils() { } - private static final String OFFLINE_SUFFIX = "_O"; - private static final String REALTIME_SUFFIX = "_R"; + public static final String OFFLINE_SUFFIX = "_O"; + public static final String REALTIME_SUFFIX = "_R"; public static String getQueryId(String brokerId, long requestId, TableType tableType) { return brokerId + "_" + requestId + (tableType == TableType.OFFLINE ? OFFLINE_SUFFIX : REALTIME_SUFFIX); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/utils/rewriter/ParentAggregationResultRewriter.java b/pinot-core/src/main/java/org/apache/pinot/core/query/utils/rewriter/ParentAggregationResultRewriter.java new file mode 100644 index 000000000000..d27de4175273 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/utils/rewriter/ParentAggregationResultRewriter.java @@ -0,0 +1,222 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.utils.rewriter; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.query.aggregation.utils.ParentAggregationFunctionResultObject; +import org.apache.pinot.spi.utils.CommonConstants; + + +/** + * Used in aggregation and group-by queries with aggregation functions. + * Use the result of parent aggregation functions to populate the result of child aggregation functions. + * This implementation is based on the column names of the result schema. + * The result column name of a parent aggregation function has the following format: + * CommonConstants.RewriterConstants.PARENT_AGGREGATION_NAME_PREFIX + aggregationFunctionType + FunctionID + * The result column name of corresponding child aggregation function has the following format: + * CHILD_AGGREGATION_NAME_PREFIX + aggregationFunctionType + operands + CHILD_AGGREGATION_SEPERATOR + * + aggregationFunctionType + parent FunctionID + CHILD_KEY_SEPERATOR + column key in parent function + * This approach will not work with `AS` clauses as they alter the column names. + * TODO: Add support for `AS` clauses. + */ +public class ParentAggregationResultRewriter implements ResultRewriter { + public ParentAggregationResultRewriter() { + } + + private static Map createChildFunctionMapping(DataSchema schema, Object[] row) { + Map childFunctionMapping = new HashMap<>(); + for (int i = 0; i < schema.size(); i++) { + String columnName = schema.getColumnName(i); + if (columnName.startsWith(CommonConstants.RewriterConstants.PARENT_AGGREGATION_NAME_PREFIX)) { + ParentAggregationFunctionResultObject parent = (ParentAggregationFunctionResultObject) row[i]; + + DataSchema nestedSchema = parent.getSchema(); + for (int j = 0; j < nestedSchema.size(); j++) { + String childColumnKey = nestedSchema.getColumnName(j); + String originalChildFunctionKey = + columnName.substring(CommonConstants.RewriterConstants.PARENT_AGGREGATION_NAME_PREFIX.length()) + + CommonConstants.RewriterConstants.CHILD_KEY_SEPERATOR + childColumnKey; + // aggregationFunctionType + childFunctionID + CHILD_KEY_SEPERATOR + childFunctionKeyInParent + childFunctionMapping.put(originalChildFunctionKey, new ChildFunctionMapping(parent, j, i)); + } + } + } + return childFunctionMapping; + } + + public RewriterResult rewrite(DataSchema dataSchema, List rows) { + + int numParentAggregationFunctions = 0; + // Count the number of parent aggregation functions + for (int i = 0; i < dataSchema.size(); i++) { + if (dataSchema.getColumnName(i).startsWith(CommonConstants.RewriterConstants.PARENT_AGGREGATION_NAME_PREFIX)) { + numParentAggregationFunctions++; + } + } + + if (numParentAggregationFunctions == 0) { + // no change to the result + return new RewriterResult(dataSchema, rows); + } + + Map childFunctionMapping = null; + if (!rows.isEmpty()) { + // Create a mapping from the child aggregation function name to the child aggregation function + childFunctionMapping = createChildFunctionMapping(dataSchema, rows.get(0)); + } + + String[] newColumnNames = new String[dataSchema.size() - numParentAggregationFunctions]; + DataSchema.ColumnDataType[] newColumnDataTypes + = new DataSchema.ColumnDataType[dataSchema.size() - numParentAggregationFunctions]; + + // Create a mapping from the function offset in the final aggregation result + // to its own/parent function offset in the original aggregation result + Map aggregationFunctionIndexMapping = new HashMap<>(); + // Create a set of the result indices of the child aggregation functions + Set childAggregationFunctionIndices = new HashSet<>(); + // Create a mapping from the result aggregation function index to the nested index of the + // child aggregation function in the parent aggregation function + Map childAggregationFunctionNestedIndexMapping = new HashMap<>(); + // Create a set of the result indices of the parent aggregation functions + Set parentAggregationFunctionIndices = new HashSet<>(); + + for (int i = 0, j = 0; i < dataSchema.size(); i++) { + String columnName = dataSchema.getColumnName(i); + // Skip the parent aggregation functions + if (columnName.startsWith(CommonConstants.RewriterConstants.PARENT_AGGREGATION_NAME_PREFIX)) { + parentAggregationFunctionIndices.add(i); + continue; + } + + // for child aggregation functions and regular columns in the result + // create a new schema and populate the new column names and data types + // also populate the offset mappings used to rewrite the result + if (columnName.startsWith(CommonConstants.RewriterConstants.CHILD_AGGREGATION_NAME_PREFIX)) { + // This is a child column of a parent aggregation function + String childAggregationFunctionNameWithKey = + columnName.substring(CommonConstants.RewriterConstants.CHILD_AGGREGATION_NAME_PREFIX.length()); + String[] s = childAggregationFunctionNameWithKey + .split(CommonConstants.RewriterConstants.CHILD_AGGREGATION_SEPERATOR); + newColumnNames[j] = s[0]; + + if (childFunctionMapping == null) { + newColumnDataTypes[j] = DataSchema.ColumnDataType.STRING; + j++; + continue; + } + ChildFunctionMapping childFunction = childFunctionMapping.get(s[1]); + newColumnDataTypes[j] = childFunction.getParent().getSchema() + .getColumnDataType(childFunction.getNestedOffset()); + + childAggregationFunctionNestedIndexMapping.put(j, childFunction.getNestedOffset()); + childAggregationFunctionIndices.add(j); + aggregationFunctionIndexMapping.put(j, childFunction.getOffset()); + } else { + // This is a regular column + newColumnNames[j] = columnName; + newColumnDataTypes[j] = dataSchema.getColumnDataType(i); + + aggregationFunctionIndexMapping.put(j, i); + } + j++; + } + + DataSchema newDataSchema = new DataSchema(newColumnNames, newColumnDataTypes); + List newRows = new ArrayList<>(); + + for (Object[] row : rows) { + int maxRows = parentAggregationFunctionIndices.stream().map(k -> { + ParentAggregationFunctionResultObject parentAggregationFunctionResultObject = + (ParentAggregationFunctionResultObject) row[k]; + return parentAggregationFunctionResultObject.getNumberOfRows(); + }).max(Integer::compareTo).orElse(0); + maxRows = maxRows == 0 ? 1 : maxRows; + + List newRowsBuffer = new ArrayList<>(); + for (int rowIter = 0; rowIter < maxRows; rowIter++) { + Object[] newRow = new Object[newDataSchema.size()]; + for (int fieldIter = 0; fieldIter < newDataSchema.size(); fieldIter++) { + // If the field is a child aggregation function, extract the value from the parent result + if (childAggregationFunctionIndices.contains(fieldIter)) { + int offset = aggregationFunctionIndexMapping.get(fieldIter); + int nestedOffset = childAggregationFunctionNestedIndexMapping.get(fieldIter); + ParentAggregationFunctionResultObject parentAggregationFunctionResultObject = + (ParentAggregationFunctionResultObject) row[offset]; + // If the parent result has more rows than the current row, extract the value from the row + if (rowIter < parentAggregationFunctionResultObject.getNumberOfRows()) { + newRow[fieldIter] = parentAggregationFunctionResultObject.getField(rowIter, nestedOffset); + } else { + newRow[fieldIter] = null; + } + } else { // If the field is a regular column, extract the value from the row, only the first row has value + newRow[fieldIter] = row[aggregationFunctionIndexMapping.get(fieldIter)]; + } + } + newRowsBuffer.add(newRow); + } + newRows.addAll(newRowsBuffer); + } + return new RewriterResult(newDataSchema, newRows); + } + + /** + * Mapping from child function key to + * 1. the parent result object, + * 2. offset of the parent result column in original result row, + * 3. the nested offset of the child function result in the parent data block + * + * For example, for a list of aggregation functions result: + * 0 1 2 3 + * | | | | + * "child_exprmin(a, b, x) ,child_exprmin(a, b, y), child_exprmin(a, b, z), parent_exprmin(a, b, x, y, z)" + * | | | + * 0 1 2 + * offset of the parent of child_exprmin(a, b, y) is 3 + * nested offset is child_exprmin(a, b, y) is 1 + */ + private static class ChildFunctionMapping { + private final ParentAggregationFunctionResultObject _parent; + private final int _nestedOffset; + private final int _offset; + + public ChildFunctionMapping(ParentAggregationFunctionResultObject parent, int nestedOffset, int offset) { + _parent = parent; + _nestedOffset = nestedOffset; + _offset = offset; + } + + public int getOffset() { + return _offset; + } + + public ParentAggregationFunctionResultObject getParent() { + return _parent; + } + + public int getNestedOffset() { + return _nestedOffset; + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/utils/rewriter/ResultRewriteUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/query/utils/rewriter/ResultRewriteUtils.java new file mode 100644 index 000000000000..3ae7f594e64e --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/utils/rewriter/ResultRewriteUtils.java @@ -0,0 +1,38 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.utils.rewriter; + +import java.util.List; +import org.apache.pinot.common.utils.DataSchema; + + +public class ResultRewriteUtils { + + private ResultRewriteUtils() { + } + + public static RewriterResult rewriteResult(DataSchema dataSchema, List rows) { + for (ResultRewriter resultRewriter : ResultRewriterFactory.getResultRewriter()) { + RewriterResult result = resultRewriter.rewrite(dataSchema, rows); + dataSchema = result.getDataSchema(); + rows = result.getRows(); + } + return new RewriterResult(dataSchema, rows); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/utils/rewriter/ResultRewriter.java b/pinot-core/src/main/java/org/apache/pinot/core/query/utils/rewriter/ResultRewriter.java new file mode 100644 index 000000000000..80975e8f1c63 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/utils/rewriter/ResultRewriter.java @@ -0,0 +1,30 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.utils.rewriter; + +import java.util.List; +import org.apache.pinot.common.utils.DataSchema; + + +/** + * Interface for rewriting the result of a query + */ +public interface ResultRewriter { + RewriterResult rewrite(DataSchema dataSchema, List rows); +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/utils/rewriter/ResultRewriterFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/query/utils/rewriter/ResultRewriterFactory.java new file mode 100644 index 000000000000..9d3d0e45da70 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/utils/rewriter/ResultRewriterFactory.java @@ -0,0 +1,69 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.utils.rewriter; + +import com.google.common.collect.ImmutableList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class ResultRewriterFactory { + + private ResultRewriterFactory() { + } + + private static final Logger LOGGER = LoggerFactory.getLogger(ResultRewriterFactory.class); + // left blank intentionally to not load any result rewriter by default + static final List DEFAULT_RESULT_REWRITERS_CLASS_NAMES = ImmutableList.of(); + + static AtomicReference> _resultRewriters + = new AtomicReference<>(getResultRewriter(DEFAULT_RESULT_REWRITERS_CLASS_NAMES)); + + public static void init(String resultRewritersClassNamesStr) { + List resultRewritersClassNames = + (resultRewritersClassNamesStr != null) ? Arrays.asList(resultRewritersClassNamesStr.split(",")) + : DEFAULT_RESULT_REWRITERS_CLASS_NAMES; + _resultRewriters.set(getResultRewriter(resultRewritersClassNames)); + } + + public static List getResultRewriter() { + return _resultRewriters.get(); + } + + private static List getResultRewriter(List resultRewriterClasses) { + final ImmutableList.Builder builder = ImmutableList.builder(); + for (String resultRewriterClassName : resultRewriterClasses) { + try { + builder.add(getResultRewriter(resultRewriterClassName)); + } catch (Exception e) { + LOGGER.error("Failed to load resultRewriter: {}", resultRewriterClassName, e); + } + } + return builder.build(); + } + + private static ResultRewriter getResultRewriter(String resultRewriterClassName) + throws Exception { + final Class resultRewriterClass = (Class) Class.forName(resultRewriterClassName); + return (ResultRewriter) resultRewriterClass.getDeclaredConstructors()[0].newInstance(); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/query/utils/rewriter/RewriterResult.java b/pinot-core/src/main/java/org/apache/pinot/core/query/utils/rewriter/RewriterResult.java new file mode 100644 index 000000000000..f62b4a35f9b0 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/query/utils/rewriter/RewriterResult.java @@ -0,0 +1,41 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.utils.rewriter; + +import java.util.List; +import org.apache.pinot.common.utils.DataSchema; + + +public class RewriterResult { + DataSchema _dataSchema; + List _rows; + + public RewriterResult(DataSchema dataSchema, List rows) { + _dataSchema = dataSchema; + _rows = rows; + } + + public DataSchema getDataSchema() { + return _dataSchema; + } + + public List getRows() { + return _rows; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/routing/RoutingManager.java b/pinot-core/src/main/java/org/apache/pinot/core/routing/RoutingManager.java index 857f0207dabf..51635691eadf 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/routing/RoutingManager.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/routing/RoutingManager.java @@ -19,6 +19,8 @@ package org.apache.pinot.core.routing; import java.util.Map; +import java.util.Set; +import javax.annotation.Nullable; import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.core.transport.ServerInstance; import org.apache.pinot.spi.annotations.InterfaceAudience; @@ -38,7 +40,7 @@ public interface RoutingManager { /** - * Get all enabled server instances that are available for routing. + * Get all enabled server instances in the cluster. * * @return all currently enabled server instances. */ @@ -50,6 +52,7 @@ public interface RoutingManager { * @param brokerRequest the broker request constructed from a query. * @return the route table. */ + @Nullable RoutingTable getRoutingTable(BrokerRequest brokerRequest, long requestId); /** @@ -66,5 +69,18 @@ public interface RoutingManager { * @param offlineTableName offline table name * @return time boundary info. */ + @Nullable TimeBoundaryInfo getTimeBoundaryInfo(String offlineTableName); + + /** + * Returns the {@link TablePartitionInfo} for a given table. + */ + @Nullable + TablePartitionInfo getTablePartitionInfo(String tableNameWithType); + + /** + * Returns the enabled server instances currently serving the given table. + */ + @Nullable + Set getServingInstances(String tableNameWithType); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/routing/RoutingTable.java b/pinot-core/src/main/java/org/apache/pinot/core/routing/RoutingTable.java index 3491dcd82158..ccc6aedb81d1 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/routing/RoutingTable.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/routing/RoutingTable.java @@ -20,22 +20,27 @@ import java.util.List; import java.util.Map; +import org.apache.commons.lang3.tuple.Pair; import org.apache.pinot.core.transport.ServerInstance; public class RoutingTable { - private final Map> _serverInstanceToSegmentsMap; + // Optional segments are those not online as in ExternalView but might have been online on servers already, e.g. + // the newly created consuming segments. Such segments were simply skipped by brokers at query routing time, but that + // had caused wrong query results, particularly for upsert tables. Instead, we should pass such segments to servers + // and let them decide how to handle them, e.g. skip them upon issues or include them for better query results. + private final Map, List/*optional segments*/>> _serverInstanceToSegmentsMap; private final List _unavailableSegments; private final int _numPrunedSegments; - public RoutingTable(Map> serverInstanceToSegmentsMap, List unavailableSegments, - int numPrunedSegments) { + public RoutingTable(Map, List>> serverInstanceToSegmentsMap, + List unavailableSegments, int numPrunedSegments) { _serverInstanceToSegmentsMap = serverInstanceToSegmentsMap; _unavailableSegments = unavailableSegments; _numPrunedSegments = numPrunedSegments; } - public Map> getServerInstanceToSegmentsMap() { + public Map, List>> getServerInstanceToSegmentsMap() { return _serverInstanceToSegmentsMap; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/routing/TablePartitionInfo.java b/pinot-core/src/main/java/org/apache/pinot/core/routing/TablePartitionInfo.java new file mode 100644 index 000000000000..3ff464bed079 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/routing/TablePartitionInfo.java @@ -0,0 +1,76 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.routing; + +import java.util.List; +import java.util.Set; + + +public class TablePartitionInfo { + private final String _tableNameWithType; + private final String _partitionColumn; + private final String _partitionFunctionName; + private final int _numPartitions; + private final PartitionInfo[] _partitionInfoMap; + private final List _segmentsWithInvalidPartition; + + public TablePartitionInfo(String tableNameWithType, String partitionColumn, String partitionFunctionName, + int numPartitions, PartitionInfo[] partitionInfoMap, List segmentsWithInvalidPartition) { + _tableNameWithType = tableNameWithType; + _partitionColumn = partitionColumn; + _partitionFunctionName = partitionFunctionName; + _numPartitions = numPartitions; + _partitionInfoMap = partitionInfoMap; + _segmentsWithInvalidPartition = segmentsWithInvalidPartition; + } + + public String getTableNameWithType() { + return _tableNameWithType; + } + + public String getPartitionColumn() { + return _partitionColumn; + } + + public String getPartitionFunctionName() { + return _partitionFunctionName; + } + + public int getNumPartitions() { + return _numPartitions; + } + + public PartitionInfo[] getPartitionInfoMap() { + return _partitionInfoMap; + } + + public List getSegmentsWithInvalidPartition() { + return _segmentsWithInvalidPartition; + } + + public static class PartitionInfo { + public final Set _fullyReplicatedServers; + public final List _segments; + + public PartitionInfo(Set fullyReplicatedServers, List segments) { + _fullyReplicatedServers = fullyReplicatedServers; + _segments = segments; + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/aggregator/DistinctCountCPCSketchAggregator.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/aggregator/DistinctCountCPCSketchAggregator.java new file mode 100644 index 000000000000..82e9a7416162 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/aggregator/DistinctCountCPCSketchAggregator.java @@ -0,0 +1,51 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.segment.processing.aggregator; + +import org.apache.datasketches.cpc.CpcSketch; +import org.apache.datasketches.cpc.CpcUnion; +import org.apache.pinot.core.common.ObjectSerDeUtils; +import org.apache.pinot.spi.utils.CommonConstants; + + +public class DistinctCountCPCSketchAggregator implements ValueAggregator { + + public DistinctCountCPCSketchAggregator() { + } + + @Override + public Object aggregate(Object value1, Object value2) { + CpcSketch first = ObjectSerDeUtils.DATA_SKETCH_CPC_SER_DE.deserialize((byte[]) value1); + CpcSketch second = ObjectSerDeUtils.DATA_SKETCH_CPC_SER_DE.deserialize((byte[]) value2); + CpcSketch result; + if (first == null && second == null) { + result = new CpcSketch(CommonConstants.Helix.DEFAULT_CPC_SKETCH_LGK); + } else if (second == null) { + result = first; + } else if (first == null) { + result = second; + } else { + CpcUnion union = new CpcUnion(CommonConstants.Helix.DEFAULT_CPC_SKETCH_LGK); + union.update(first); + union.update(second); + result = union.getResult(); + } + return ObjectSerDeUtils.DATA_SKETCH_CPC_SER_DE.serialize(result); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/aggregator/DistinctCountHLLAggregator.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/aggregator/DistinctCountHLLAggregator.java new file mode 100644 index 000000000000..4eecbe3696c3 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/aggregator/DistinctCountHLLAggregator.java @@ -0,0 +1,38 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.segment.processing.aggregator; + +import com.clearspring.analytics.stream.cardinality.CardinalityMergeException; +import com.clearspring.analytics.stream.cardinality.HyperLogLog; +import org.apache.pinot.core.common.ObjectSerDeUtils; + + +public class DistinctCountHLLAggregator implements ValueAggregator { + @Override + public Object aggregate(Object value1, Object value2) { + try { + HyperLogLog first = ObjectSerDeUtils.HYPER_LOG_LOG_SER_DE.deserialize((byte[]) value1); + HyperLogLog second = ObjectSerDeUtils.HYPER_LOG_LOG_SER_DE.deserialize((byte[]) value2); + first.addAll(second); + return ObjectSerDeUtils.HYPER_LOG_LOG_SER_DE.serialize(first); + } catch (CardinalityMergeException e) { + throw new RuntimeException(e); + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/aggregator/DistinctCountThetaSketchAggregator.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/aggregator/DistinctCountThetaSketchAggregator.java new file mode 100644 index 000000000000..b11f7d7b0034 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/aggregator/DistinctCountThetaSketchAggregator.java @@ -0,0 +1,43 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.segment.processing.aggregator; + +import org.apache.datasketches.theta.Sketch; +import org.apache.datasketches.theta.Union; +import org.apache.pinot.core.common.ObjectSerDeUtils; +import org.apache.pinot.spi.utils.CommonConstants; + + +public class DistinctCountThetaSketchAggregator implements ValueAggregator { + + private final Union _union; + + public DistinctCountThetaSketchAggregator() { + // TODO: Handle configurable nominal entries + _union = Union.builder().setNominalEntries(CommonConstants.Helix.DEFAULT_THETA_SKETCH_NOMINAL_ENTRIES).buildUnion(); + } + + @Override + public Object aggregate(Object value1, Object value2) { + Sketch first = ObjectSerDeUtils.DATA_SKETCH_THETA_SER_DE.deserialize((byte[]) value1); + Sketch second = ObjectSerDeUtils.DATA_SKETCH_THETA_SER_DE.deserialize((byte[]) value2); + Sketch result = _union.union(first, second); + return ObjectSerDeUtils.DATA_SKETCH_THETA_SER_DE.serialize(result); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/aggregator/DistinctCountULLAggregator.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/aggregator/DistinctCountULLAggregator.java new file mode 100644 index 000000000000..2a51ac052b6f --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/aggregator/DistinctCountULLAggregator.java @@ -0,0 +1,39 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.segment.processing.aggregator; + +import com.dynatrace.hash4j.distinctcount.UltraLogLog; +import org.apache.pinot.core.common.ObjectSerDeUtils; + + +public class DistinctCountULLAggregator implements ValueAggregator { + @Override + public Object aggregate(Object value1, Object value2) { + UltraLogLog first = ObjectSerDeUtils.ULTRA_LOG_LOG_OBJECT_SER_DE.deserialize((byte[]) value1); + UltraLogLog second = ObjectSerDeUtils.ULTRA_LOG_LOG_OBJECT_SER_DE.deserialize((byte[]) value2); + // add to the one with a larger P and return that + if (first.getP() >= second.getP()) { + first.add(second); + return ObjectSerDeUtils.ULTRA_LOG_LOG_OBJECT_SER_DE.serialize(first); + } else { + second.add(first); + return ObjectSerDeUtils.ULTRA_LOG_LOG_OBJECT_SER_DE.serialize(second); + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/aggregator/IntegerTupleSketchAggregator.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/aggregator/IntegerTupleSketchAggregator.java new file mode 100644 index 000000000000..8bdf7f8a86fa --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/aggregator/IntegerTupleSketchAggregator.java @@ -0,0 +1,42 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.segment.processing.aggregator; + +import org.apache.datasketches.tuple.Sketch; +import org.apache.datasketches.tuple.Union; +import org.apache.datasketches.tuple.aninteger.IntegerSummary; +import org.apache.datasketches.tuple.aninteger.IntegerSummarySetOperations; +import org.apache.pinot.core.common.ObjectSerDeUtils; + + +public class IntegerTupleSketchAggregator implements ValueAggregator { + IntegerSummary.Mode _mode; + + public IntegerTupleSketchAggregator(IntegerSummary.Mode mode) { + _mode = mode; + } + + @Override + public Object aggregate(Object value1, Object value2) { + Sketch first = ObjectSerDeUtils.DATA_SKETCH_INT_TUPLE_SER_DE.deserialize((byte[]) value1); + Sketch second = ObjectSerDeUtils.DATA_SKETCH_INT_TUPLE_SER_DE.deserialize((byte[]) value2); + Sketch result = new Union<>(new IntegerSummarySetOperations(_mode, _mode)).union(first, second); + return ObjectSerDeUtils.DATA_SKETCH_INT_TUPLE_SER_DE.serialize(result); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/aggregator/ValueAggregatorFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/aggregator/ValueAggregatorFactory.java index 68578d1f8dae..3b51f417871b 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/aggregator/ValueAggregatorFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/aggregator/ValueAggregatorFactory.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.core.segment.processing.aggregator; +import org.apache.datasketches.tuple.aninteger.IntegerSummary; import org.apache.pinot.segment.spi.AggregationFunctionType; import org.apache.pinot.spi.data.FieldSpec.DataType; @@ -31,6 +32,9 @@ private ValueAggregatorFactory() { /** * Constructs a ValueAggregator from the given aggregation type. + * + * When adding entries to this please add them to the Set in org.apache.pinot.segment.local.utils.TableConfigUtils + * named AVAILABLE_CORE_VALUE_AGGREGATORS so that they can be used in RealtimeToOfflineTask */ public static ValueAggregator getValueAggregator(AggregationFunctionType aggregationType, DataType dataType) { switch (aggregationType) { @@ -40,6 +44,23 @@ public static ValueAggregator getValueAggregator(AggregationFunctionType aggrega return new MaxValueAggregator(dataType); case SUM: return new SumValueAggregator(dataType); + case DISTINCTCOUNTHLL: + case DISTINCTCOUNTRAWHLL: + return new DistinctCountHLLAggregator(); + case DISTINCTCOUNTTHETASKETCH: + case DISTINCTCOUNTRAWTHETASKETCH: + return new DistinctCountThetaSketchAggregator(); + case DISTINCTCOUNTTUPLESKETCH: + case DISTINCTCOUNTRAWINTEGERSUMTUPLESKETCH: + case SUMVALUESINTEGERSUMTUPLESKETCH: + case AVGVALUEINTEGERSUMTUPLESKETCH: + return new IntegerTupleSketchAggregator(IntegerSummary.Mode.Sum); + case DISTINCTCOUNTCPCSKETCH: + case DISTINCTCOUNTRAWCPCSKETCH: + return new DistinctCountCPCSketchAggregator(); + case DISTINCTCOUNTULL: + case DISTINCTCOUNTRAWULL: + return new DistinctCountULLAggregator(); default: throw new IllegalStateException("Unsupported aggregation type: " + aggregationType); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/framework/DefaultSegmentNumRowProvider.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/framework/DefaultSegmentNumRowProvider.java new file mode 100644 index 000000000000..740e56c13756 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/framework/DefaultSegmentNumRowProvider.java @@ -0,0 +1,39 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.segment.processing.framework; + +/** + * Provides the configured maxRecordsPerSegment + */ +public class DefaultSegmentNumRowProvider implements SegmentNumRowProvider { + private int _maxRecordsPerSegment; + + public DefaultSegmentNumRowProvider(int maxRecordsPerSegment) { + _maxRecordsPerSegment = maxRecordsPerSegment; + } + + @Override + public int getNumRows() { + return _maxRecordsPerSegment; + } + + @Override + public void updateSegmentInfo(int numRows, long segmentSize) { + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/framework/SegmentConfig.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/framework/SegmentConfig.java index b3302fafc02c..95191b658b7d 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/framework/SegmentConfig.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/framework/SegmentConfig.java @@ -31,22 +31,28 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class SegmentConfig { public static final int DEFAULT_MAX_NUM_RECORDS_PER_SEGMENT = 5_000_000; + public static final long DEFAULT_SEGMENT_MAPPER_FILE_SIZE_IN_BYTES = Long.MAX_VALUE; private final int _maxNumRecordsPerSegment; private final String _segmentNamePrefix; private final String _segmentNamePostfix; private final String _fixedSegmentName; + private final long _segmentMapperFileSizeThresholdInBytes; @JsonCreator private SegmentConfig(@JsonProperty(value = "maxNumRecordsPerSegment", required = true) int maxNumRecordsPerSegment, @JsonProperty("segmentNamePrefix") @Nullable String segmentNamePrefix, @JsonProperty("segmentNamePostfix") @Nullable String segmentNamePostfix, - @JsonProperty("fixedSegmentName") @Nullable String fixedSegmentName) { + @JsonProperty("fixedSegmentName") @Nullable String fixedSegmentName, + @JsonProperty(value = "segmentMapperFileSizeThresholdInBytes", required = true) + long segmentMapperFileSizeThresholdInBytes) { Preconditions.checkState(maxNumRecordsPerSegment > 0, "Max num records per segment must be > 0"); + Preconditions.checkState(segmentMapperFileSizeThresholdInBytes > 0, "Intermediate file size threshold must be > 0"); _maxNumRecordsPerSegment = maxNumRecordsPerSegment; _segmentNamePrefix = segmentNamePrefix; _segmentNamePostfix = segmentNamePostfix; _fixedSegmentName = fixedSegmentName; + _segmentMapperFileSizeThresholdInBytes = segmentMapperFileSizeThresholdInBytes; } /** @@ -71,15 +77,21 @@ public String getFixedSegmentName() { return _fixedSegmentName; } + public long getIntermediateFileSizeThreshold() { + return _segmentMapperFileSizeThresholdInBytes; + } + /** * Builder for SegmentConfig */ public static class Builder { private int _maxNumRecordsPerSegment = DEFAULT_MAX_NUM_RECORDS_PER_SEGMENT; + private long _segmentMapperFileSizeThresholdInBytes = DEFAULT_SEGMENT_MAPPER_FILE_SIZE_IN_BYTES; private String _segmentNamePrefix; private String _segmentNamePostfix; private String _fixedSegmentName; + public Builder setMaxNumRecordsPerSegment(int maxNumRecordsPerSegment) { _maxNumRecordsPerSegment = maxNumRecordsPerSegment; return this; @@ -99,17 +111,25 @@ public Builder setFixedSegmentName(String fixedSegmentName) { _fixedSegmentName = fixedSegmentName; return this; } + public Builder setIntermediateFileSizeThreshold(long segmentMapperFileSizeThresholdInBytes) { + _segmentMapperFileSizeThresholdInBytes = segmentMapperFileSizeThresholdInBytes; + return this; + } public SegmentConfig build() { Preconditions.checkState(_maxNumRecordsPerSegment > 0, "Max num records per segment must be > 0"); - return new SegmentConfig(_maxNumRecordsPerSegment, _segmentNamePrefix, _segmentNamePostfix, _fixedSegmentName); + Preconditions.checkState(_segmentMapperFileSizeThresholdInBytes > 0, + "Intermediate file size threshold must be > 0"); + return new SegmentConfig(_maxNumRecordsPerSegment, _segmentNamePrefix, _segmentNamePostfix, _fixedSegmentName, + _segmentMapperFileSizeThresholdInBytes); } } @Override public String toString() { - return "SegmentConfig{" + "_maxNumRecordsPerSegment=" + _maxNumRecordsPerSegment + ", _segmentNamePrefix='" - + _segmentNamePrefix + '\'' + ", _segmentNamePostfix='" + _segmentNamePostfix + '\'' + ", _fixedSegmentName='" - + _fixedSegmentName + '\'' + '}'; + return "SegmentConfig{" + "_maxNumRecordsPerSegment=" + _maxNumRecordsPerSegment + + ", _segmentMapperFileSizeThresholdInBytes=" + _segmentMapperFileSizeThresholdInBytes + + ", _segmentNamePrefix='" + _segmentNamePrefix + '\'' + ", _segmentNamePostfix='" + _segmentNamePostfix + '\'' + + ", _fixedSegmentName='" + _fixedSegmentName + '\'' + '}'; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/framework/SegmentNumRowProvider.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/framework/SegmentNumRowProvider.java new file mode 100644 index 000000000000..04e7312a3333 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/framework/SegmentNumRowProvider.java @@ -0,0 +1,28 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.segment.processing.framework; + +/** + * Interface to compute rows for a segment, using past segment size + * and number of rows. This will be used by SegmentProcessorFramework. + */ +public interface SegmentNumRowProvider { + int getNumRows(); + void updateSegmentInfo(int numRows, long segmentSize); +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/framework/SegmentProcessorFramework.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/framework/SegmentProcessorFramework.java index 25f2b82eba06..4b166b934a90 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/framework/SegmentProcessorFramework.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/framework/SegmentProcessorFramework.java @@ -33,6 +33,7 @@ import org.apache.pinot.core.segment.processing.mapper.SegmentMapper; import org.apache.pinot.core.segment.processing.reducer.Reducer; import org.apache.pinot.core.segment.processing.reducer.ReducerFactory; +import org.apache.pinot.segment.local.recordtransformer.RecordTransformer; import org.apache.pinot.segment.local.segment.creator.RecordReaderSegmentCreationDataSource; import org.apache.pinot.segment.local.segment.creator.TransformPipeline; import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; @@ -41,6 +42,8 @@ import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.data.Schema; import org.apache.pinot.spi.data.readers.RecordReader; +import org.apache.pinot.spi.data.readers.RecordReaderFileConfig; +import org.apache.pinot.spi.recordenricher.RecordEnricherPipeline; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,24 +61,39 @@ public class SegmentProcessorFramework { private static final Logger LOGGER = LoggerFactory.getLogger(SegmentProcessorFramework.class); - private final List _recordReaders; + private final List _recordReaderFileConfigs; + private final List _customRecordTransformers; private final SegmentProcessorConfig _segmentProcessorConfig; private final File _mapperOutputDir; private final File _reducerOutputDir; private final File _segmentsOutputDir; + private final SegmentNumRowProvider _segmentNumRowProvider; + private int _segmentSequenceId = 0; /** - * Initializes the SegmentProcessorFramework with record readers, config and working directory. + * Initializes the SegmentProcessorFramework with record readers, config and working directory. We will now rely on + * users passing RecordReaderFileConfig, since that also allows us to do lazy initialization of RecordReaders. + * Please use the other constructor that uses RecordReaderFileConfig. */ + @Deprecated public SegmentProcessorFramework(List recordReaders, SegmentProcessorConfig segmentProcessorConfig, File workingDir) throws IOException { - Preconditions.checkState(!recordReaders.isEmpty(), "No record reader is provided"); + this(segmentProcessorConfig, workingDir, convertRecordReadersToRecordReaderFileConfig(recordReaders), + Collections.emptyList(), null); + } + + public SegmentProcessorFramework(SegmentProcessorConfig segmentProcessorConfig, File workingDir, + List recordReaderFileConfigs, List customRecordTransformers, + SegmentNumRowProvider segmentNumRowProvider) + throws IOException { + Preconditions.checkState(!recordReaderFileConfigs.isEmpty(), "No recordReaderFileConfigs provided"); LOGGER.info("Initializing SegmentProcessorFramework with {} record readers, config: {}, working dir: {}", - recordReaders.size(), segmentProcessorConfig, workingDir.getAbsolutePath()); + recordReaderFileConfigs.size(), segmentProcessorConfig, workingDir.getAbsolutePath()); + _recordReaderFileConfigs = recordReaderFileConfigs; + _customRecordTransformers = customRecordTransformers; - _recordReaders = recordReaders; _segmentProcessorConfig = segmentProcessorConfig; _mapperOutputDir = new File(workingDir, "mapper_output"); @@ -84,6 +102,19 @@ public SegmentProcessorFramework(List recordReaders, SegmentProces FileUtils.forceMkdir(_reducerOutputDir); _segmentsOutputDir = new File(workingDir, "segments_output"); FileUtils.forceMkdir(_segmentsOutputDir); + + _segmentNumRowProvider = (segmentNumRowProvider == null) ? new DefaultSegmentNumRowProvider( + segmentProcessorConfig.getSegmentConfig().getMaxNumRecordsPerSegment()) : segmentNumRowProvider; + } + + private static List convertRecordReadersToRecordReaderFileConfig( + List recordReaders) { + Preconditions.checkState(!recordReaders.isEmpty(), "No record reader is provided"); + List recordReaderFileConfigs = new ArrayList<>(); + for (RecordReader recordReader : recordReaders) { + recordReaderFileConfigs.add(new RecordReaderFileConfig(recordReader)); + } + return recordReaderFileConfigs; } /** @@ -91,32 +122,130 @@ public SegmentProcessorFramework(List recordReaders, SegmentProces */ public List process() throws Exception { - // Map phase - LOGGER.info("Beginning map phase on {} record readers", _recordReaders.size()); - SegmentMapper mapper = new SegmentMapper(_recordReaders, _segmentProcessorConfig, _mapperOutputDir); - Map partitionToFileManagerMap = mapper.map(); - - // Check for mapper output files - if (partitionToFileManagerMap.isEmpty()) { - LOGGER.info("No partition generated from mapper phase, skipping the reducer phase"); - return Collections.emptyList(); + try { + return doProcess(); + } catch (Exception e) { + // Cleaning up output dir as processing has failed. file managers left from map or reduce phase will be cleaned + // up in the respective phases. + FileUtils.deleteQuietly(_segmentsOutputDir); + throw e; + } finally { + FileUtils.deleteDirectory(_mapperOutputDir); + FileUtils.deleteDirectory(_reducerOutputDir); } + } + + private List doProcess() + throws Exception { + List outputSegmentDirs = new ArrayList<>(); + int numRecordReaders = _recordReaderFileConfigs.size(); + int nextRecordReaderIndexToBeProcessed = 0; + int iterationCount = 1; + Consumer observer = _segmentProcessorConfig.getProgressObserver(); + boolean isMapperOutputSizeThresholdEnabled = + _segmentProcessorConfig.getSegmentConfig().getIntermediateFileSizeThreshold() != Long.MAX_VALUE; + + while (nextRecordReaderIndexToBeProcessed < numRecordReaders) { + // Initialise the mapper. Eliminate the record readers that have been processed in the previous iterations. + SegmentMapper mapper = + new SegmentMapper(_recordReaderFileConfigs.subList(nextRecordReaderIndexToBeProcessed, numRecordReaders), + _customRecordTransformers, _segmentProcessorConfig, _mapperOutputDir); + + // Log start of iteration details only if intermediate file size threshold is set. + if (isMapperOutputSizeThresholdEnabled) { + String logMessage = + String.format("Starting iteration %d with %d record readers. Starting index = %d, end index = %d", + iterationCount, + _recordReaderFileConfigs.subList(nextRecordReaderIndexToBeProcessed, numRecordReaders).size(), + nextRecordReaderIndexToBeProcessed + 1, numRecordReaders); + LOGGER.info(logMessage); + observer.accept(logMessage); + } + + // Map phase. + long mapStartTimeInMs = System.currentTimeMillis(); + Map partitionToFileManagerMap = mapper.map(); + + // Log the time taken to map. + LOGGER.info("Finished iteration {} in {}ms", iterationCount, System.currentTimeMillis() - mapStartTimeInMs); + + // Check for mapper output files, if no files are generated, skip the reducer phase and move on to the next + // iteration. + if (partitionToFileManagerMap.isEmpty()) { + LOGGER.info("No mapper output files generated, skipping reduce phase"); + nextRecordReaderIndexToBeProcessed = getNextRecordReaderIndexToBeProcessed(nextRecordReaderIndexToBeProcessed); + continue; + } + + // Reduce phase. + doReduce(partitionToFileManagerMap); - // Reduce phase + // Segment creation phase. Add the created segments to the final list. + outputSegmentDirs.addAll(generateSegment(partitionToFileManagerMap)); + + // Store the starting index of the record readers that were processed in this iteration for logging purposes. + int startingProcessedRecordReaderIndex = nextRecordReaderIndexToBeProcessed; + + // Update next record reader index to be processed. + nextRecordReaderIndexToBeProcessed = getNextRecordReaderIndexToBeProcessed(nextRecordReaderIndexToBeProcessed); + + // Log the details between iteration only if intermediate file size threshold is set. + if (isMapperOutputSizeThresholdEnabled) { + // Take care of logging the proper RecordReader index in case of the last iteration. + int boundaryIndexToLog = + nextRecordReaderIndexToBeProcessed == numRecordReaders ? nextRecordReaderIndexToBeProcessed + : nextRecordReaderIndexToBeProcessed + 1; + + // We are sure that the last RecordReader is completely processed in the last iteration else it may or may not + // have completed processing. Log it accordingly. + String logMessage; + if (nextRecordReaderIndexToBeProcessed == numRecordReaders) { + logMessage = String.format("Finished processing all of %d RecordReaders", numRecordReaders); + } else { + logMessage = String.format( + "Finished processing RecordReaders %d to %d (RecordReader %d might be partially processed) out of %d in " + + "iteration %d", startingProcessedRecordReaderIndex + 1, boundaryIndexToLog, + nextRecordReaderIndexToBeProcessed + 1, numRecordReaders, iterationCount); + } + + observer.accept(logMessage); + LOGGER.info(logMessage); + } + + iterationCount++; + } + return outputSegmentDirs; + } + + private int getNextRecordReaderIndexToBeProcessed(int currentRecordIndex) { + for (int i = currentRecordIndex; i < _recordReaderFileConfigs.size(); i++) { + RecordReaderFileConfig recordReaderFileConfig = _recordReaderFileConfigs.get(i); + if (!recordReaderFileConfig.isRecordReaderDone()) { + return i; + } + } + return _recordReaderFileConfigs.size(); + } + + private void doReduce(Map partitionToFileManagerMap) + throws Exception { LOGGER.info("Beginning reduce phase on partitions: {}", partitionToFileManagerMap.keySet()); Consumer observer = _segmentProcessorConfig.getProgressObserver(); int totalCount = partitionToFileManagerMap.keySet().size(); int count = 1; for (Map.Entry entry : partitionToFileManagerMap.entrySet()) { String partitionId = entry.getKey(); - observer.accept(String - .format("Doing reduce phase on data from partition: %s (%d out of %d)", partitionId, count++, totalCount)); + observer.accept( + String.format("Doing reduce phase on data from partition: %s (%d out of %d)", partitionId, count++, + totalCount)); GenericRowFileManager fileManager = entry.getValue(); Reducer reducer = ReducerFactory.getReducer(partitionId, fileManager, _segmentProcessorConfig, _reducerOutputDir); entry.setValue(reducer.reduce()); } + } - // Segment creation phase + private List generateSegment(Map partitionToFileManagerMap) + throws Exception { LOGGER.info("Beginning segment creation phase on partitions: {}", partitionToFileManagerMap.keySet()); List outputSegmentDirs = new ArrayList<>(); TableConfig tableConfig = _segmentProcessorConfig.getTableConfig(); @@ -126,19 +255,19 @@ public List process() String fixedSegmentName = _segmentProcessorConfig.getSegmentConfig().getFixedSegmentName(); SegmentGeneratorConfig generatorConfig = new SegmentGeneratorConfig(tableConfig, schema); generatorConfig.setOutDir(_segmentsOutputDir.getPath()); + Consumer observer = _segmentProcessorConfig.getProgressObserver(); if (tableConfig.getIndexingConfig().getSegmentNameGeneratorType() != null) { - generatorConfig.setSegmentNameGenerator(SegmentNameGeneratorFactory - .createSegmentNameGenerator(tableConfig, schema, segmentNamePrefix, segmentNamePostfix, fixedSegmentName, - false)); + generatorConfig.setSegmentNameGenerator( + SegmentNameGeneratorFactory.createSegmentNameGenerator(tableConfig, schema, segmentNamePrefix, + segmentNamePostfix, fixedSegmentName, false)); } else { // SegmentNameGenerator will be inferred by the SegmentGeneratorConfig. generatorConfig.setSegmentNamePrefix(segmentNamePrefix); generatorConfig.setSegmentNamePostfix(segmentNamePostfix); + generatorConfig.setSegmentName(fixedSegmentName); } - int maxNumRecordsPerSegment = _segmentProcessorConfig.getSegmentConfig().getMaxNumRecordsPerSegment(); - int sequenceId = 0; for (Map.Entry entry : partitionToFileManagerMap.entrySet()) { String partitionId = entry.getKey(); GenericRowFileManager fileManager = entry.getValue(); @@ -149,28 +278,30 @@ public List process() LOGGER.info("Start creating segments on partition: {}, numRows: {}, numSortFields: {}", partitionId, numRows, numSortFields); GenericRowFileRecordReader recordReader = fileReader.getRecordReader(); - for (int startRowId = 0; startRowId < numRows; startRowId += maxNumRecordsPerSegment, sequenceId++) { + int maxNumRecordsPerSegment; + for (int startRowId = 0; startRowId < numRows; startRowId += maxNumRecordsPerSegment, _segmentSequenceId++) { + maxNumRecordsPerSegment = _segmentNumRowProvider.getNumRows(); int endRowId = Math.min(startRowId + maxNumRecordsPerSegment, numRows); - LOGGER.info("Start creating segment of sequenceId: {} with row range: {} to {}", sequenceId, startRowId, - endRowId); + LOGGER.info("Start creating segment of sequenceId: {} with row range: {} to {}", _segmentSequenceId, + startRowId, endRowId); observer.accept(String.format( "Creating segment of sequentId: %d with data from partition: %s and row range: [%d, %d) out of [0, %d)", - sequenceId, partitionId, startRowId, endRowId, numRows)); - generatorConfig.setSequenceId(sequenceId); + _segmentSequenceId, partitionId, startRowId, endRowId, numRows)); + generatorConfig.setSequenceId(_segmentSequenceId); GenericRowFileRecordReader recordReaderForRange = recordReader.getRecordReaderForRange(startRowId, endRowId); SegmentIndexCreationDriverImpl driver = new SegmentIndexCreationDriverImpl(); driver.init(generatorConfig, new RecordReaderSegmentCreationDataSource(recordReaderForRange), + RecordEnricherPipeline.getPassThroughPipeline(), TransformPipeline.getPassThroughPipeline()); driver.build(); outputSegmentDirs.add(driver.getOutputDirectory()); + _segmentNumRowProvider.updateSegmentInfo(driver.getSegmentStats().getTotalDocCount(), + FileUtils.sizeOfDirectory(driver.getOutputDirectory())); } } finally { fileManager.cleanUp(); } } - FileUtils.deleteDirectory(_mapperOutputDir); - FileUtils.deleteDirectory(_reducerOutputDir); - LOGGER.info("Successfully created segments: {}", outputSegmentDirs); return outputSegmentDirs; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/genericrow/AdaptiveConstraintsWriter.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/genericrow/AdaptiveConstraintsWriter.java new file mode 100644 index 000000000000..d0a3daffa0fc --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/genericrow/AdaptiveConstraintsWriter.java @@ -0,0 +1,33 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.segment.processing.genericrow; + +import java.io.IOException; + + +/** + * Interface for a writer which can track constraints. This will be used by SegmentProcessorFramework. + * */ + +public interface AdaptiveConstraintsWriter { + boolean canWrite(); + + void write(W writer, D dataUnit) + throws IOException; +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/genericrow/AdaptiveSizeBasedWriter.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/genericrow/AdaptiveSizeBasedWriter.java new file mode 100644 index 000000000000..541bd14e269b --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/genericrow/AdaptiveSizeBasedWriter.java @@ -0,0 +1,51 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.segment.processing.genericrow; + +import java.io.IOException; +import org.apache.pinot.spi.data.readers.GenericRow; + + +public class AdaptiveSizeBasedWriter implements AdaptiveConstraintsWriter { + + private final long _bytesLimit; + private long _numBytesWritten; + + public AdaptiveSizeBasedWriter(long bytesLimit) { + _bytesLimit = bytesLimit; + _numBytesWritten = 0; + } + + public long getBytesLimit() { + return _bytesLimit; + } + public long getNumBytesWritten() { + return _numBytesWritten; + } + + @Override + public boolean canWrite() { + return _numBytesWritten < _bytesLimit; + } + + @Override + public void write(GenericRowFileWriter writer, GenericRow row) throws IOException { + _numBytesWritten += writer.writeData(row); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/genericrow/FileWriter.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/genericrow/FileWriter.java new file mode 100644 index 000000000000..8dd0a7d0a15f --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/genericrow/FileWriter.java @@ -0,0 +1,30 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.segment.processing.genericrow; + +import java.io.IOException; + +/** + * Abstraction for writing data units to a file. + * */ + +public interface FileWriter { + void close() throws IOException; + long writeData(T dataUnit) throws IOException; +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/genericrow/GenericRowFileWriter.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/genericrow/GenericRowFileWriter.java index 4f7957c349cb..281838a0b5e5 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/genericrow/GenericRowFileWriter.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/genericrow/GenericRowFileWriter.java @@ -38,7 +38,7 @@ * * TODO: Consider using ByteBuffer instead of OutputStream. */ -public class GenericRowFileWriter implements Closeable { +public class GenericRowFileWriter implements Closeable, FileWriter { private final DataOutputStream _offsetStream; private final BufferedOutputStream _dataStream; private final GenericRowSerializer _serializer; @@ -63,10 +63,23 @@ public void write(GenericRow genericRow) _nextOffset += bytes.length; } + public long writeData(GenericRow genericRow) + throws IOException { + _offsetStream.writeLong(_nextOffset); + byte[] bytes = _serializer.serialize(genericRow); + _dataStream.write(bytes); + _nextOffset += bytes.length; + return bytes.length; + } + @Override public void close() throws IOException { - _offsetStream.close(); - _dataStream.close(); + try { + // Wrapping around try block to make sure dataStream is closed, despite failures while closing offsetStream. + _offsetStream.close(); + } finally { + _dataStream.close(); + } } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/lifecycle/PinotSegmentLifecycleEventListener.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/lifecycle/PinotSegmentLifecycleEventListener.java new file mode 100644 index 000000000000..c7c510ae2d08 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/lifecycle/PinotSegmentLifecycleEventListener.java @@ -0,0 +1,30 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.segment.processing.lifecycle; + +import org.apache.helix.HelixManager; + + +public interface PinotSegmentLifecycleEventListener { + SegmentLifecycleEventType getType(); + + void init(HelixManager helixManager); + + void onEvent(SegmentLifecycleEventDetails event); +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/lifecycle/PinotSegmentLifecycleEventListenerManager.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/lifecycle/PinotSegmentLifecycleEventListenerManager.java new file mode 100644 index 000000000000..5269c3f79765 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/lifecycle/PinotSegmentLifecycleEventListenerManager.java @@ -0,0 +1,96 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.segment.processing.lifecycle; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.helix.HelixManager; +import org.apache.pinot.spi.utils.PinotReflectionUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public class PinotSegmentLifecycleEventListenerManager { + private static final Logger LOGGER = LoggerFactory.getLogger(PinotSegmentLifecycleEventListenerManager.class); + private static final PinotSegmentLifecycleEventListenerManager INSTANCE = + new PinotSegmentLifecycleEventListenerManager(); + private Map> _eventTypeToListenersMap; + private boolean _initialized = false; + + private PinotSegmentLifecycleEventListenerManager() { + } + + public static PinotSegmentLifecycleEventListenerManager getInstance() { + return INSTANCE; + } + + public synchronized void init(HelixManager helixZkManager) { + if (_initialized) { + LOGGER.warn("Segment lifecycle event listener manager already initialized, skipping it"); + return; + } + _eventTypeToListenersMap = new HashMap<>(); + Set> classes = + PinotReflectionUtils.getClassesThroughReflection(".*\\.plugin\\.segment\\.lifecycle\\.listener\\..*", + SegmentLifecycleEventListener.class); + for (Class clazz : classes) { + SegmentLifecycleEventListener annotation = clazz.getAnnotation(SegmentLifecycleEventListener.class); + if (annotation.enabled()) { + try { + PinotSegmentLifecycleEventListener pinotSegmentLifecycleEventListener = + (PinotSegmentLifecycleEventListener) clazz.newInstance(); + pinotSegmentLifecycleEventListener.init(helixZkManager); + _eventTypeToListenersMap.compute(pinotSegmentLifecycleEventListener.getType(), (key, list) -> { + if (list == null) { + list = new ArrayList<>(); + } + list.add(pinotSegmentLifecycleEventListener); + return list; + }); + } catch (Exception e) { + LOGGER.error("Caught exception while initializing segment lifecyle event listener : {}, skipping it", clazz, + e); + } + } + } + + _initialized = true; + } + + public void notifyListeners(SegmentLifecycleEventDetails event) { + if (!_initialized) { + LOGGER.warn("Segment lifecycle event listener manager not initialized, skipping it"); + return; + } + + List listeners = _eventTypeToListenersMap.get(event.getType()); + if (listeners != null) { + for (PinotSegmentLifecycleEventListener listener : listeners) { + try { + listener.onEvent(event); + } catch (Exception e) { + LOGGER.error("Segment lifecycle listener call failed : ", e); + } + } + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/lifecycle/SegmentLifecycleEventDetails.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/lifecycle/SegmentLifecycleEventDetails.java new file mode 100644 index 000000000000..c59435455cd4 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/lifecycle/SegmentLifecycleEventDetails.java @@ -0,0 +1,29 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.segment.processing.lifecycle; + +import java.util.List; + +public interface SegmentLifecycleEventDetails { + SegmentLifecycleEventType getType(); + + List getSegments(); + + String getTableNameWithType(); +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/lifecycle/SegmentLifecycleEventListener.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/lifecycle/SegmentLifecycleEventListener.java new file mode 100644 index 000000000000..0a4225e5294b --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/lifecycle/SegmentLifecycleEventListener.java @@ -0,0 +1,30 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.segment.processing.lifecycle; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface SegmentLifecycleEventListener { + boolean enabled() default true; +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/lifecycle/SegmentLifecycleEventType.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/lifecycle/SegmentLifecycleEventType.java new file mode 100644 index 000000000000..9559b9547fa8 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/lifecycle/SegmentLifecycleEventType.java @@ -0,0 +1,23 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.segment.processing.lifecycle; + +public enum SegmentLifecycleEventType { + DELETION +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/lifecycle/impl/SegmentDeletionEventDetails.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/lifecycle/impl/SegmentDeletionEventDetails.java new file mode 100644 index 000000000000..eb839a26a691 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/lifecycle/impl/SegmentDeletionEventDetails.java @@ -0,0 +1,48 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.segment.processing.lifecycle.impl; + +import java.util.List; +import org.apache.pinot.core.segment.processing.lifecycle.SegmentLifecycleEventDetails; +import org.apache.pinot.core.segment.processing.lifecycle.SegmentLifecycleEventType; + +public class SegmentDeletionEventDetails implements SegmentLifecycleEventDetails { + private final List _segmentsDeleted; + private final String _tableName; + + public SegmentDeletionEventDetails(String tableName, List segmentsDeleted) { + _tableName = tableName; + _segmentsDeleted = segmentsDeleted; + } + + @Override + public SegmentLifecycleEventType getType() { + return SegmentLifecycleEventType.DELETION; + } + + @Override + public List getSegments() { + return _segmentsDeleted; + } + + @Override + public String getTableNameWithType() { + return _tableName; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/mapper/SegmentMapper.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/mapper/SegmentMapper.java index 2d8118ff82a8..8d36b72b92c3 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/mapper/SegmentMapper.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/mapper/SegmentMapper.java @@ -30,20 +30,26 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.pinot.core.segment.processing.framework.SegmentProcessorConfig; +import org.apache.pinot.core.segment.processing.genericrow.AdaptiveSizeBasedWriter; import org.apache.pinot.core.segment.processing.genericrow.GenericRowFileManager; +import org.apache.pinot.core.segment.processing.genericrow.GenericRowFileWriter; import org.apache.pinot.core.segment.processing.partitioner.Partitioner; import org.apache.pinot.core.segment.processing.partitioner.PartitionerConfig; import org.apache.pinot.core.segment.processing.partitioner.PartitionerFactory; import org.apache.pinot.core.segment.processing.timehandler.TimeHandler; import org.apache.pinot.core.segment.processing.timehandler.TimeHandlerFactory; import org.apache.pinot.core.segment.processing.utils.SegmentProcessorUtils; +import org.apache.pinot.segment.local.recordtransformer.ComplexTypeTransformer; import org.apache.pinot.segment.local.recordtransformer.CompositeTransformer; +import org.apache.pinot.segment.local.recordtransformer.RecordTransformer; import org.apache.pinot.segment.local.utils.IngestionUtils; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.data.FieldSpec; import org.apache.pinot.spi.data.Schema; import org.apache.pinot.spi.data.readers.GenericRow; import org.apache.pinot.spi.data.readers.RecordReader; +import org.apache.pinot.spi.data.readers.RecordReaderFileConfig; +import org.apache.pinot.spi.recordenricher.RecordEnricherPipeline; import org.apache.pinot.spi.utils.StringUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,24 +65,27 @@ */ public class SegmentMapper { private static final Logger LOGGER = LoggerFactory.getLogger(SegmentMapper.class); - - private final List _recordReaders; private final SegmentProcessorConfig _processorConfig; private final File _mapperOutputDir; - private final List _fieldSpecs; private final boolean _includeNullFields; private final int _numSortFields; - + private final RecordEnricherPipeline _recordEnricherPipeline; private final CompositeTransformer _recordTransformer; + private final ComplexTypeTransformer _complexTypeTransformer; private final TimeHandler _timeHandler; private final Partitioner[] _partitioners; private final String[] _partitionsBuffer; // NOTE: Use TreeMap so that the order is deterministic private final Map _partitionToFileManagerMap = new TreeMap<>(); - - public SegmentMapper(List recordReaders, SegmentProcessorConfig processorConfig, File mapperOutputDir) { - _recordReaders = recordReaders; + private AdaptiveSizeBasedWriter _adaptiveSizeBasedWriter; + private List _recordReaderFileConfigs; + private List _customRecordTransformers; + + public SegmentMapper(List recordReaderFileConfigs, + List customRecordTransformers, SegmentProcessorConfig processorConfig, File mapperOutputDir) { + _recordReaderFileConfigs = recordReaderFileConfigs; + _customRecordTransformers = customRecordTransformers; _processorConfig = processorConfig; _mapperOutputDir = mapperOutputDir; @@ -87,7 +96,9 @@ public SegmentMapper(List recordReaders, SegmentProcessorConfig pr _fieldSpecs = pair.getLeft(); _numSortFields = pair.getRight(); _includeNullFields = tableConfig.getIndexingConfig().isNullHandlingEnabled(); - _recordTransformer = CompositeTransformer.getDefaultTransformer(tableConfig, schema); + _recordEnricherPipeline = RecordEnricherPipeline.fromTableConfig(tableConfig); + _recordTransformer = CompositeTransformer.composeAllTransformers(_customRecordTransformers, tableConfig, schema); + _complexTypeTransformer = ComplexTypeTransformer.getComplexTypeTransformer(tableConfig); _timeHandler = TimeHandlerFactory.getTimeHandler(processorConfig); List partitionerConfigs = processorConfig.getPartitionerConfigs(); int numPartitioners = partitionerConfigs.size(); @@ -97,9 +108,14 @@ public SegmentMapper(List recordReaders, SegmentProcessorConfig pr } // Time partition + partition from partitioners _partitionsBuffer = new String[numPartitioners + 1]; + LOGGER.info("Initialized mapper with {} record readers, output dir: {}, timeHandler: {}, partitioners: {}", - _recordReaders.size(), _mapperOutputDir, _timeHandler.getClass(), + _recordReaderFileConfigs.size(), _mapperOutputDir, _timeHandler.getClass(), Arrays.stream(_partitioners).map(p -> p.getClass().toString()).collect(Collectors.joining(","))); + + // initialize adaptive writer. + _adaptiveSizeBasedWriter = + new AdaptiveSizeBasedWriter(processorConfig.getSegmentConfig().getIntermediateFileSizeThreshold()); } /** @@ -108,43 +124,100 @@ public SegmentMapper(List recordReaders, SegmentProcessorConfig pr */ public Map map() throws Exception { + try { + return doMap(); + } catch (Exception e) { + // Cleaning up resources created by the mapper. + for (GenericRowFileManager fileManager : _partitionToFileManagerMap.values()) { + fileManager.cleanUp(); + } + throw e; + } + } + + private Map doMap() + throws Exception { Consumer observer = _processorConfig.getProgressObserver(); - int totalCount = _recordReaders.size(); int count = 1; + int totalNumRecordReaders = _recordReaderFileConfigs.size(); GenericRow reuse = new GenericRow(); - for (RecordReader recordReader : _recordReaders) { - observer.accept(String.format("Doing map phase on data from RecordReader (%d out of %d)", count++, totalCount)); - while (recordReader.hasNext()) { - reuse = recordReader.next(reuse); - - // TODO: Add ComplexTypeTransformer here. Currently it is not idempotent so cannot add it - - if (reuse.getValue(GenericRow.MULTIPLE_RECORDS_KEY) != null) { - //noinspection unchecked - for (GenericRow row : (Collection) reuse.getValue(GenericRow.MULTIPLE_RECORDS_KEY)) { - GenericRow transformedRow = _recordTransformer.transform(row); - if (transformedRow != null && IngestionUtils.shouldIngestRow(transformedRow)) { - writeRecord(transformedRow); - } - } - } else { - GenericRow transformedRow = _recordTransformer.transform(reuse); - if (transformedRow != null && IngestionUtils.shouldIngestRow(transformedRow)) { - writeRecord(transformedRow); - } - } - - reuse.clear(); + for (RecordReaderFileConfig recordReaderFileConfig : _recordReaderFileConfigs) { + RecordReader recordReader = recordReaderFileConfig.getRecordReader(); + + // Mapper can terminate midway of reading a file if the intermediate file size has crossed the configured + // threshold. Map phase will continue in the next iteration right where we are leaving off in the current + // iteration. + boolean shouldMapperTerminate = + !completeMapAndTransformRow(recordReader, reuse, observer, count, totalNumRecordReaders); + + // Terminate the map phase if intermediate file size has crossed the threshold. + if (shouldMapperTerminate) { + break; } + recordReaderFileConfig.closeRecordReader(); + count++; } for (GenericRowFileManager fileManager : _partitionToFileManagerMap.values()) { fileManager.closeFileWriter(); } - return _partitionToFileManagerMap; } + +// Returns true if the map phase can continue, false if it should terminate based on the configured threshold for +// intermediate file size during map phase. + private boolean completeMapAndTransformRow(RecordReader recordReader, GenericRow reuse, + Consumer observer, int count, int totalCount) throws Exception { + observer.accept(String.format("Doing map phase on data from RecordReader (%d out of %d)", count, totalCount)); + while (recordReader.hasNext() && (_adaptiveSizeBasedWriter.canWrite())) { + reuse = recordReader.next(reuse); + _recordEnricherPipeline.run(reuse); + + if (reuse.getValue(GenericRow.MULTIPLE_RECORDS_KEY) != null) { + //noinspection unchecked + for (GenericRow row : (Collection) reuse.getValue(GenericRow.MULTIPLE_RECORDS_KEY)) { + transformAndWrite(row); + } + } else { + transformAndWrite(reuse); + } + reuse.clear(); + } + if (recordReader.hasNext() && !_adaptiveSizeBasedWriter.canWrite()) { + String logMessage = String.format( + "Stopping record readers at index: %d out of %d passed to mapper as size limit reached, bytes written = %d," + + " bytes " + "limit = %d", count, totalCount, _adaptiveSizeBasedWriter.getNumBytesWritten(), + _adaptiveSizeBasedWriter.getBytesLimit()); + observer.accept(logMessage); + LOGGER.info(logMessage); + return false; + } + return true; + } + + private void transformAndWrite(GenericRow row) + throws IOException { + GenericRow decodedRow = row; + if (_complexTypeTransformer != null) { + decodedRow = _complexTypeTransformer.transform(row); + } + Collection rows = (Collection) decodedRow.getValue(GenericRow.MULTIPLE_RECORDS_KEY); + if (rows != null) { + for (GenericRow unrappedRow : rows) { + GenericRow transformedRow = _recordTransformer.transform(unrappedRow); + if (transformedRow != null && IngestionUtils.shouldIngestRow(transformedRow)) { + writeRecord(transformedRow); + } + } + } else { + GenericRow transformedRow = _recordTransformer.transform(row); + if (transformedRow != null && IngestionUtils.shouldIngestRow(transformedRow)) { + writeRecord(transformedRow); + } + } + } + private void writeRecord(GenericRow row) throws IOException { String timePartition = _timeHandler.handleTime(row); @@ -170,6 +243,10 @@ private void writeRecord(GenericRow row) _partitionToFileManagerMap.put(partition, fileManager); } - fileManager.getFileWriter().write(row); + // Get the file writer. + GenericRowFileWriter fileWriter = fileManager.getFileWriter(); + + // Write the row. + _adaptiveSizeBasedWriter.write(fileWriter, row); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/partitioner/TableConfigPartitioner.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/partitioner/TableConfigPartitioner.java index 4a088f129d1b..cff7f3b74567 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/partitioner/TableConfigPartitioner.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/partitioner/TableConfigPartitioner.java @@ -21,6 +21,7 @@ import org.apache.pinot.segment.spi.partition.PartitionFunction; import org.apache.pinot.segment.spi.partition.PartitionFunctionFactory; import org.apache.pinot.spi.config.table.ColumnPartitionConfig; +import org.apache.pinot.spi.data.FieldSpec; import org.apache.pinot.spi.data.readers.GenericRow; @@ -41,6 +42,6 @@ public TableConfigPartitioner(String columnName, ColumnPartitionConfig columnPar @Override public String getPartition(GenericRow genericRow) { - return String.valueOf(_partitionFunction.getPartition(genericRow.getValue(_column))); + return String.valueOf(_partitionFunction.getPartition(FieldSpec.getStringValue(genericRow.getValue(_column)))); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/reducer/DedupReducer.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/reducer/DedupReducer.java index 33bdd8b544d0..a54f00d5eb35 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/reducer/DedupReducer.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/reducer/DedupReducer.java @@ -38,6 +38,7 @@ public class DedupReducer implements Reducer { private final String _partitionId; private final GenericRowFileManager _fileManager; private final File _reducerOutputDir; + private GenericRowFileManager _dedupFileManager; public DedupReducer(String partitionId, GenericRowFileManager fileManager, File reducerOutputDir) { _partitionId = partitionId; @@ -48,6 +49,19 @@ public DedupReducer(String partitionId, GenericRowFileManager fileManager, File @Override public GenericRowFileManager reduce() throws Exception { + try { + return doReduce(); + } catch (Exception e) { + // Cleaning up resources created by the reducer, leaving others to the caller like the input _fileManager. + if (_dedupFileManager != null) { + _dedupFileManager.cleanUp(); + } + throw e; + } + } + + private GenericRowFileManager doReduce() + throws Exception { LOGGER.info("Start reducing on partition: {}", _partitionId); long reduceStartTimeMs = System.currentTimeMillis(); @@ -63,10 +77,10 @@ public GenericRowFileManager reduce() FileUtils.forceMkdir(partitionOutputDir); LOGGER.info("Start creating dedup file under dir: {}", partitionOutputDir); long dedupFileCreationStartTimeMs = System.currentTimeMillis(); - GenericRowFileManager dedupFileManager = + _dedupFileManager = new GenericRowFileManager(partitionOutputDir, _fileManager.getFieldSpecs(), _fileManager.isIncludeNullFields(), 0); - GenericRowFileWriter dedupFileWriter = dedupFileManager.getFileWriter(); + GenericRowFileWriter dedupFileWriter = _dedupFileManager.getFileWriter(); GenericRow previousRow = new GenericRow(); recordReader.read(0, previousRow); int previousRowId = 0; @@ -79,11 +93,11 @@ public GenericRowFileManager reduce() dedupFileWriter.write(previousRow); } } - dedupFileManager.closeFileWriter(); + _dedupFileManager.closeFileWriter(); LOGGER.info("Finish creating dedup file in {}ms", System.currentTimeMillis() - dedupFileCreationStartTimeMs); _fileManager.cleanUp(); LOGGER.info("Finish reducing in {}ms", System.currentTimeMillis() - reduceStartTimeMs); - return dedupFileManager; + return _dedupFileManager; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/reducer/RollupReducer.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/reducer/RollupReducer.java index 5b11c46def89..ae88120f204b 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/reducer/RollupReducer.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/reducer/RollupReducer.java @@ -48,6 +48,7 @@ public class RollupReducer implements Reducer { private final GenericRowFileManager _fileManager; private final Map _aggregationTypes; private final File _reducerOutputDir; + private GenericRowFileManager _rollupFileManager; public RollupReducer(String partitionId, GenericRowFileManager fileManager, Map aggregationTypes, File reducerOutputDir) { @@ -60,6 +61,19 @@ public RollupReducer(String partitionId, GenericRowFileManager fileManager, @Override public GenericRowFileManager reduce() throws Exception { + try { + return doReduce(); + } catch (Exception e) { + // Cleaning up resources created by the reducer, leaving others to the caller like the input _fileManager. + if (_rollupFileManager != null) { + _rollupFileManager.cleanUp(); + } + throw e; + } + } + + private GenericRowFileManager doReduce() + throws Exception { LOGGER.info("Start reducing on partition: {}", _partitionId); long reduceStartTimeMs = System.currentTimeMillis(); @@ -85,9 +99,8 @@ public GenericRowFileManager reduce() FileUtils.forceMkdir(partitionOutputDir); LOGGER.info("Start creating rollup file under dir: {}", partitionOutputDir); long rollupFileCreationStartTimeMs = System.currentTimeMillis(); - GenericRowFileManager rollupFileManager = - new GenericRowFileManager(partitionOutputDir, fieldSpecs, includeNullFields, 0); - GenericRowFileWriter rollupFileWriter = rollupFileManager.getFileWriter(); + _rollupFileManager = new GenericRowFileManager(partitionOutputDir, fieldSpecs, includeNullFields, 0); + GenericRowFileWriter rollupFileWriter = _rollupFileManager.getFileWriter(); GenericRow previousRow = new GenericRow(); recordReader.read(0, previousRow); int previousRowId = 0; @@ -122,12 +135,12 @@ public GenericRowFileManager reduce() } } rollupFileWriter.write(previousRow); - rollupFileManager.closeFileWriter(); + _rollupFileManager.closeFileWriter(); LOGGER.info("Finish creating rollup file in {}ms", System.currentTimeMillis() - rollupFileCreationStartTimeMs); _fileManager.cleanUp(); LOGGER.info("Finish reducing in {}ms", System.currentTimeMillis() - reduceStartTimeMs); - return rollupFileManager; + return _rollupFileManager; } private static void aggregateWithNullFields(GenericRow aggregatedRow, GenericRow rowToAggregate, diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/transformer/NoOpRecordTransformer.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/transformer/NoOpRecordTransformer.java deleted file mode 100644 index 06578f0e16b8..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/transformer/NoOpRecordTransformer.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.segment.processing.transformer; - -import org.apache.pinot.spi.data.readers.GenericRow; - - -/** - * Record transformer which does no transformation - */ -public class NoOpRecordTransformer implements RecordTransformer { - @Override - public GenericRow transformRecord(GenericRow row) { - return row; - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/transformer/RecordTransformer.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/transformer/RecordTransformer.java deleted file mode 100644 index a60620fccddd..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/transformer/RecordTransformer.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.segment.processing.transformer; - -import org.apache.pinot.spi.data.readers.GenericRow; - - -/** - * Interface for record transformer - */ -public interface RecordTransformer { - - /** - * Transform the given row to another row - * - * @param row an original row - * @return a transformed row - */ - GenericRow transformRecord(GenericRow row); -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/transformer/RecordTransformerConfig.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/transformer/RecordTransformerConfig.java deleted file mode 100644 index 1cca911ff974..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/transformer/RecordTransformerConfig.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.segment.processing.transformer; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.Map; -import javax.annotation.Nullable; - - -/** - * Config for record transformer - */ -@JsonIgnoreProperties(ignoreUnknown = true) -public class RecordTransformerConfig { - - private final Map _transformFunctionsMap; - - @JsonCreator - private RecordTransformerConfig( - @JsonProperty(value = "transformFunctionsMap") Map transformFunctionsMap) { - _transformFunctionsMap = transformFunctionsMap; - } - - /** - * Map containing transform functions for column transformation of a record - */ - @JsonProperty - @Nullable - public Map getTransformFunctionsMap() { - return _transformFunctionsMap; - } - - /** - * Builder for Record Transformer Config - */ - public static class Builder { - private Map _transformFunctionsMap; - - public Builder setTransformFunctionsMap(Map transformFunctionsMap) { - _transformFunctionsMap = transformFunctionsMap; - return this; - } - - public RecordTransformerConfig build() { - return new RecordTransformerConfig(_transformFunctionsMap); - } - } - - @Override - public String toString() { - return "RecordTransformerConfig{" + "_transformFunctionsMap=" + _transformFunctionsMap + '}'; - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/transformer/TransformFunctionRecordTransformer.java b/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/transformer/TransformFunctionRecordTransformer.java deleted file mode 100644 index 295338fac4bc..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/segment/processing/transformer/TransformFunctionRecordTransformer.java +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.segment.processing.transformer; - -import java.util.HashMap; -import java.util.Map; -import org.apache.pinot.segment.local.function.FunctionEvaluator; -import org.apache.pinot.segment.local.function.FunctionEvaluatorFactory; -import org.apache.pinot.spi.data.readers.GenericRow; - - -/** - * RecordTransformer which executes transform functions to transform columns of record - * Does not follow any particular order, and hence cannot support transformations where strict order of execution is - * needed - */ -public class TransformFunctionRecordTransformer implements RecordTransformer { - - private final Map _functionEvaluatorMap = new HashMap<>(); - - public TransformFunctionRecordTransformer(Map transformFunctionMap) { - transformFunctionMap.forEach( - (key, value) -> _functionEvaluatorMap.put(key, FunctionEvaluatorFactory.getExpressionEvaluator(value))); - } - - @Override - public GenericRow transformRecord(GenericRow row) { - for (Map.Entry entry : _functionEvaluatorMap.entrySet()) { - Object result = entry.getValue().evaluate(row); - row.putValue(entry.getKey(), result); - } - return row; - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/startree/CompositePredicateEvaluator.java b/pinot-core/src/main/java/org/apache/pinot/core/startree/CompositePredicateEvaluator.java index 9424bc0cdbd1..1725364aeeec 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/startree/CompositePredicateEvaluator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/startree/CompositePredicateEvaluator.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.core.startree; +import it.unimi.dsi.fastutil.objects.ObjectBooleanPair; import java.util.List; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; @@ -26,19 +27,19 @@ * Represents a composite predicate. * * A composite predicate evaluator represents a single predicate evaluator or multiple predicate evaluators conjoined - * with OR. - * Consider the given predicate: (d1 > 10 OR d1 < 50). A composite predicate will represent two predicates -- (d1 > 10) - * and (d1 < 50) and represent that they are related by the operator OR. + * with OR. Each predicate evaluator is associated with a boolean value indicating whether the predicate is negated. + * Consider the given predicate: (d1 > 10 OR NOT d1 > 50). A composite predicate will represent two predicates: + * (d1 > 10) and NOT(d1 > 50) and represent that they are related by the operator OR. */ public class CompositePredicateEvaluator { - private final List _predicateEvaluators; + private final List> _predicateEvaluators; - public CompositePredicateEvaluator(List predicateEvaluators) { + public CompositePredicateEvaluator(List> predicateEvaluators) { assert !predicateEvaluators.isEmpty(); _predicateEvaluators = predicateEvaluators; } - public List getPredicateEvaluators() { + public List> getPredicateEvaluators() { return _predicateEvaluators; } @@ -47,8 +48,8 @@ public List getPredicateEvaluators() { * predicate evaluator, {@code false} otherwise. */ public boolean apply(int dictId) { - for (PredicateEvaluator predicateEvaluator : _predicateEvaluators) { - if (predicateEvaluator.applySV(dictId)) { + for (ObjectBooleanPair predicateEvaluator : _predicateEvaluators) { + if (predicateEvaluator.left().applySV(dictId) != predicateEvaluator.rightBoolean()) { return true; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/startree/StarTreeUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/startree/StarTreeUtils.java index 5892e6312b3a..68bd26e7801a 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/startree/StarTreeUtils.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/startree/StarTreeUtils.java @@ -18,6 +18,7 @@ */ package org.apache.pinot.core.startree; +import it.unimi.dsi.fastutil.objects.ObjectBooleanPair; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; @@ -32,13 +33,17 @@ import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.FilterContext; import org.apache.pinot.common.request.context.predicate.Predicate; +import org.apache.pinot.core.operator.BaseProjectOperator; import org.apache.pinot.core.operator.filter.predicate.PredicateEvaluator; import org.apache.pinot.core.query.aggregation.function.AggregationFunction; import org.apache.pinot.core.query.aggregation.function.AggregationFunctionUtils; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.startree.plan.StarTreeProjectPlanNode; import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.segment.spi.index.reader.Dictionary; import org.apache.pinot.segment.spi.index.startree.AggregationFunctionColumnPair; +import org.apache.pinot.segment.spi.index.startree.StarTreeV2; import org.apache.pinot.segment.spi.index.startree.StarTreeV2Metadata; @@ -60,7 +65,7 @@ public static AggregationFunctionColumnPair[] extractAggregationFunctionPairs( new AggregationFunctionColumnPair[numAggregationFunctions]; for (int i = 0; i < numAggregationFunctions; i++) { AggregationFunctionColumnPair aggregationFunctionColumnPair = - AggregationFunctionUtils.getAggregationFunctionColumnPair(aggregationFunctions[i]); + AggregationFunctionUtils.getStoredFunctionColumnPair(aggregationFunctions[i]); if (aggregationFunctionColumnPair != null) { aggregationFunctionColumnPairs[i] = aggregationFunctionColumnPair; } else { @@ -71,13 +76,13 @@ public static AggregationFunctionColumnPair[] extractAggregationFunctionPairs( } /** - * Extracts a map from the column to a list of {@link PredicateEvaluator}s for it. Returns {@code null} if the filter - * cannot be solved by the star-tree. + * Extracts a map from the column to a list of {@link CompositePredicateEvaluator}s for it. Returns {@code null} if + * the filter cannot be solved by the star-tree. * * A predicate can be simple (d1 > 10) or composite (d1 > 10 AND d2 < 50) or multi levelled - * (d1 > 50 AND (d2 > 10 OR d2 < 35)). + * (d1 > 50 AND (d2 > 10 OR NOT d2 > 35)). * This method represents a list of CompositePredicates per dimension. For each dimension, all CompositePredicates in - * the list are implicitly ANDed together. Any OR predicates are nested within a CompositePredicate. + * the list are implicitly ANDed together. Any OR and NOT predicates are nested within a CompositePredicate. * * A map from predicates to their evaluators is passed in to accelerate the computation. */ @@ -98,32 +103,61 @@ public static Map> extractPredicateEva queue.addAll(filterNode.getChildren()); break; case OR: - Pair> pair = + Pair pair = isOrClauseValidForStarTree(indexSegment, filterNode, predicateEvaluatorMapping); if (pair == null) { return null; } - List predicateEvaluators = pair.getRight(); - // NOTE: Empty list means always true - if (!predicateEvaluators.isEmpty()) { - predicateEvaluatorsMap.computeIfAbsent(pair.getLeft(), k -> new ArrayList<>()) - .add(new CompositePredicateEvaluator(predicateEvaluators)); + // NOTE: Null identifier means always true + if (pair.getLeft() != null) { + predicateEvaluatorsMap.computeIfAbsent(pair.getLeft(), k -> new ArrayList<>()).add(pair.getRight()); } break; case NOT: - // TODO: Support NOT in star-tree - return null; + boolean negated = true; + FilterContext negatedChild = filterNode.getChildren().get(0); + while (true) { + FilterContext.Type type = negatedChild.getType(); + if (type == FilterContext.Type.PREDICATE) { + Predicate predicate = negatedChild.getPredicate(); + PredicateEvaluator predicateEvaluator = + getPredicateEvaluator(indexSegment, predicate, predicateEvaluatorMapping); + // Do not use star-tree when the predicate cannot be solved with star-tree + if (predicateEvaluator == null) { + return null; + } + // Do not use star-tree when the predicate is always false + if ((predicateEvaluator.isAlwaysTrue() && negated) || (predicateEvaluator.isAlwaysFalse() && !negated)) { + return null; + } + // Skip adding always true predicate + if ((predicateEvaluator.isAlwaysTrue() && !negated) || (predicateEvaluator.isAlwaysFalse() && negated)) { + break; + } + predicateEvaluatorsMap.computeIfAbsent(predicate.getLhs().getIdentifier(), k -> new ArrayList<>()) + .add(new CompositePredicateEvaluator(List.of(ObjectBooleanPair.of(predicateEvaluator, negated)))); + break; + } + if (type == FilterContext.Type.NOT) { + negated = !negated; + negatedChild = negatedChild.getChildren().get(0); + continue; + } + // Do not allow nested AND/OR under NOT + return null; + } + break; case PREDICATE: Predicate predicate = filterNode.getPredicate(); - PredicateEvaluator predicateEvaluator = getPredicateEvaluator(indexSegment, predicate, - predicateEvaluatorMapping); - if (predicateEvaluator == null) { - // The predicate cannot be solved with star-tree + PredicateEvaluator predicateEvaluator = + getPredicateEvaluator(indexSegment, predicate, predicateEvaluatorMapping); + // Do not use star-tree when the predicate cannot be solved with star-tree or is always false + if (predicateEvaluator == null || predicateEvaluator.isAlwaysFalse()) { return null; } if (!predicateEvaluator.isAlwaysTrue()) { predicateEvaluatorsMap.computeIfAbsent(predicate.getLhs().getIdentifier(), k -> new ArrayList<>()) - .add(new CompositePredicateEvaluator(Collections.singletonList(predicateEvaluator))); + .add(new CompositePredicateEvaluator(List.of(ObjectBooleanPair.of(predicateEvaluator, false)))); } break; default: @@ -173,67 +207,91 @@ public static boolean isFitForStarTree(StarTreeV2Metadata starTreeV2Metadata, * StarTree supports OR predicates on a single dimension only (d1 < 10 OR d1 > 50). * * @return The pair of single identifier and predicate evaluators applied to it if true; {@code null} if the OR clause - * cannot be solved with star-tree; empty predicate evaluator list if the OR clause always evaluates to true. + * cannot be solved with star-tree; a pair of nulls if the OR clause always evaluates to true. */ @Nullable - private static Pair> isOrClauseValidForStarTree(IndexSegment indexSegment, + private static Pair isOrClauseValidForStarTree(IndexSegment indexSegment, FilterContext filter, List> predicateEvaluatorMapping) { assert filter.getType() == FilterContext.Type.OR; - List predicates = new ArrayList<>(); + List> predicates = new ArrayList<>(); if (!extractOrClausePredicates(filter, predicates)) { return null; } String identifier = null; - List predicateEvaluators = new ArrayList<>(); - for (Predicate predicate : predicates) { - PredicateEvaluator predicateEvaluator = getPredicateEvaluator(indexSegment, predicate, predicateEvaluatorMapping); + List> predicateEvaluators = new ArrayList<>(); + for (ObjectBooleanPair predicate : predicates) { + PredicateEvaluator predicateEvaluator = + getPredicateEvaluator(indexSegment, predicate.left(), predicateEvaluatorMapping); if (predicateEvaluator == null) { // The predicate cannot be solved with star-tree return null; } - if (predicateEvaluator.isAlwaysTrue()) { - // Use empty predicate evaluators to represent always true - return Pair.of(null, Collections.emptyList()); + boolean negated = predicate.rightBoolean(); + // Use a pair of null values to represent always true + if ((predicateEvaluator.isAlwaysTrue() && !negated) || (predicateEvaluator.isAlwaysFalse() && negated)) { + return Pair.of(null, null); } - if (!predicateEvaluator.isAlwaysFalse()) { - String predicateIdentifier = predicate.getLhs().getIdentifier(); - if (identifier == null) { - identifier = predicateIdentifier; - } else { - if (!identifier.equals(predicateIdentifier)) { - // The predicates are applied to multiple columns - return null; - } + // Skip the always false predicate + if ((predicateEvaluator.isAlwaysTrue() && negated) || (predicateEvaluator.isAlwaysFalse() && !negated)) { + continue; + } + String predicateIdentifier = predicate.left().getLhs().getIdentifier(); + if (identifier == null) { + identifier = predicateIdentifier; + } else { + if (!identifier.equals(predicateIdentifier)) { + // The predicates are applied to multiple columns + return null; } - predicateEvaluators.add(predicateEvaluator); } + predicateEvaluators.add(ObjectBooleanPair.of(predicateEvaluator, negated)); + } + // When all predicates are always false, do not use star-tree + if (predicateEvaluators.isEmpty()) { + return null; } - return Pair.of(identifier, predicateEvaluators); + return Pair.of(identifier, new CompositePredicateEvaluator(predicateEvaluators)); } /** * Extracts the predicates under the given OR clause, returns {@code false} if there is nested AND or NOT under OR * clause. - * TODO: Support NOT in star-tree */ - private static boolean extractOrClausePredicates(FilterContext filter, List predicates) { + private static boolean extractOrClausePredicates(FilterContext filter, + List> predicates) { assert filter.getType() == FilterContext.Type.OR; for (FilterContext child : filter.getChildren()) { switch (child.getType()) { case AND: - case NOT: return false; case OR: if (!extractOrClausePredicates(child, predicates)) { return false; } - predicates.add(child.getPredicate()); + break; + case NOT: + boolean negated = true; + FilterContext negatedChild = child.getChildren().get(0); + while (true) { + FilterContext.Type type = negatedChild.getType(); + if (type == FilterContext.Type.PREDICATE) { + predicates.add(ObjectBooleanPair.of(negatedChild.getPredicate(), negated)); + break; + } + if (type == FilterContext.Type.NOT) { + negated = !negated; + negatedChild = negatedChild.getChildren().get(0); + continue; + } + // Do not allow nested AND/OR under NOT + return false; + } break; case PREDICATE: - predicates.add(child.getPredicate()); + predicates.add(ObjectBooleanPair.of(child.getPredicate(), false)); break; default: throw new IllegalStateException(); @@ -274,10 +332,44 @@ private static PredicateEvaluator getPredicateEvaluator(IndexSegment indexSegmen break; } for (Pair pair : predicatesEvaluatorMapping) { - if (pair.getKey().equals(predicate)) { + if (pair.getKey() == predicate) { return pair.getValue(); } } return null; } + + /** + * Returns a {@link BaseProjectOperator} when the filter can be solved with star-tree, or {@code null} otherwise. + */ + @Nullable + public static BaseProjectOperator createStarTreeBasedProjectOperator(IndexSegment indexSegment, + QueryContext queryContext, AggregationFunction[] aggregationFunctions, @Nullable FilterContext filter, + List> predicateEvaluators) { + List starTrees = indexSegment.getStarTrees(); + if (starTrees == null || queryContext.isSkipStarTree() || queryContext.isNullHandlingEnabled()) { + return null; + } + AggregationFunctionColumnPair[] aggregationFunctionColumnPairs = + extractAggregationFunctionPairs(aggregationFunctions); + if (aggregationFunctionColumnPairs == null) { + return null; + } + Map> predicateEvaluatorsMap = + extractPredicateEvaluatorsMap(indexSegment, filter, predicateEvaluators); + if (predicateEvaluatorsMap == null) { + return null; + } + ExpressionContext[] groupByExpressions = + queryContext.getGroupByExpressions() != null ? queryContext.getGroupByExpressions() + .toArray(new ExpressionContext[0]) : null; + for (StarTreeV2 starTreeV2 : starTrees) { + if (isFitForStarTree(starTreeV2.getMetadata(), aggregationFunctionColumnPairs, groupByExpressions, + predicateEvaluatorsMap.keySet())) { + return new StarTreeProjectPlanNode(queryContext, starTreeV2, aggregationFunctionColumnPairs, groupByExpressions, + predicateEvaluatorsMap).run(); + } + } + return null; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/startree/executor/StarTreeAggregationExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/startree/executor/StarTreeAggregationExecutor.java index 4067d53e6fc1..6953aabddf5d 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/startree/executor/StarTreeAggregationExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/startree/executor/StarTreeAggregationExecutor.java @@ -18,7 +18,7 @@ */ package org.apache.pinot.core.startree.executor; -import org.apache.pinot.core.operator.blocks.TransformBlock; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.query.aggregation.DefaultAggregationExecutor; import org.apache.pinot.core.query.aggregation.function.AggregationFunction; import org.apache.pinot.core.query.aggregation.function.AggregationFunctionUtils; @@ -43,17 +43,17 @@ public StarTreeAggregationExecutor(AggregationFunction[] aggregationFunctions) { _aggregationFunctionColumnPairs = new AggregationFunctionColumnPair[numAggregationFunctions]; for (int i = 0; i < numAggregationFunctions; i++) { _aggregationFunctionColumnPairs[i] = - AggregationFunctionUtils.getAggregationFunctionColumnPair(aggregationFunctions[i]); + AggregationFunctionUtils.getStoredFunctionColumnPair(aggregationFunctions[i]); } } @Override - public void aggregate(TransformBlock transformBlock) { + public void aggregate(ValueBlock valueBlock) { int numAggregationFunctions = _aggregationFunctions.length; - int length = transformBlock.getNumDocs(); + int length = valueBlock.getNumDocs(); for (int i = 0; i < numAggregationFunctions; i++) { _aggregationFunctions[i].aggregate(length, _aggregationResultHolders[i], - AggregationFunctionUtils.getBlockValSetMap(_aggregationFunctionColumnPairs[i], transformBlock)); + AggregationFunctionUtils.getBlockValSetMap(_aggregationFunctionColumnPairs[i], valueBlock)); } } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/startree/executor/StarTreeGroupByExecutor.java b/pinot-core/src/main/java/org/apache/pinot/core/startree/executor/StarTreeGroupByExecutor.java index 0960dd15e534..8d7d3dd65fb7 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/startree/executor/StarTreeGroupByExecutor.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/startree/executor/StarTreeGroupByExecutor.java @@ -19,14 +19,16 @@ package org.apache.pinot.core.startree.executor; import java.util.Map; +import javax.annotation.Nullable; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.core.common.BlockValSet; -import org.apache.pinot.core.operator.blocks.TransformBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.query.aggregation.function.AggregationFunction; import org.apache.pinot.core.query.aggregation.function.AggregationFunctionUtils; import org.apache.pinot.core.query.aggregation.groupby.DefaultGroupByExecutor; import org.apache.pinot.core.query.aggregation.groupby.GroupByResultHolder; +import org.apache.pinot.core.query.aggregation.groupby.GroupKeyGenerator; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.segment.spi.index.startree.AggregationFunctionColumnPair; @@ -44,25 +46,35 @@ public class StarTreeGroupByExecutor extends DefaultGroupByExecutor { private final AggregationFunctionColumnPair[] _aggregationFunctionColumnPairs; public StarTreeGroupByExecutor(QueryContext queryContext, ExpressionContext[] groupByExpressions, - TransformOperator transformOperator) { - super(queryContext, groupByExpressions, transformOperator); + BaseProjectOperator projectOperator) { + this(queryContext, queryContext.getAggregationFunctions(), groupByExpressions, projectOperator, null); + } + + public StarTreeGroupByExecutor(QueryContext queryContext, AggregationFunction[] aggregationFunctions, + ExpressionContext[] groupByExpressions, BaseProjectOperator projectOperator) { + this(queryContext, aggregationFunctions, groupByExpressions, projectOperator, null); + } + + public StarTreeGroupByExecutor(QueryContext queryContext, AggregationFunction[] aggregationFunctions, + ExpressionContext[] groupByExpressions, BaseProjectOperator projectOperator, + @Nullable GroupKeyGenerator groupKeyGenerator) { + super(queryContext, aggregationFunctions, groupByExpressions, projectOperator, groupKeyGenerator); - AggregationFunction[] aggregationFunctions = queryContext.getAggregationFunctions(); assert aggregationFunctions != null; int numAggregationFunctions = aggregationFunctions.length; _aggregationFunctionColumnPairs = new AggregationFunctionColumnPair[numAggregationFunctions]; for (int i = 0; i < numAggregationFunctions; i++) { _aggregationFunctionColumnPairs[i] = - AggregationFunctionUtils.getAggregationFunctionColumnPair(aggregationFunctions[i]); + AggregationFunctionUtils.getStoredFunctionColumnPair(aggregationFunctions[i]); } } @Override - protected void aggregate(TransformBlock transformBlock, int length, int functionIndex) { + protected void aggregate(ValueBlock valueBlock, int length, int functionIndex) { AggregationFunction aggregationFunction = _aggregationFunctions[functionIndex]; GroupByResultHolder groupByResultHolder = _groupByResultHolders[functionIndex]; Map blockValSetMap = - AggregationFunctionUtils.getBlockValSetMap(_aggregationFunctionColumnPairs[functionIndex], transformBlock); + AggregationFunctionUtils.getBlockValSetMap(_aggregationFunctionColumnPairs[functionIndex], valueBlock); if (_hasMVGroupByExpression) { aggregationFunction.aggregateGroupByMV(length, _mvGroupKeys, groupByResultHolder, blockValSetMap); } else { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/startree/operator/StarTreeFilterOperator.java b/pinot-core/src/main/java/org/apache/pinot/core/startree/operator/StarTreeFilterOperator.java index 3023704f3e9e..107390fecd60 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/startree/operator/StarTreeFilterOperator.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/startree/operator/StarTreeFilterOperator.java @@ -18,9 +18,11 @@ */ package org.apache.pinot.core.startree.operator; +import it.unimi.dsi.fastutil.ints.IntImmutableList; import it.unimi.dsi.fastutil.ints.IntIterator; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; +import it.unimi.dsi.fastutil.objects.ObjectBooleanPair; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; @@ -32,9 +34,10 @@ import java.util.Queue; import java.util.Set; import javax.annotation.Nullable; +import org.apache.pinot.common.utils.config.QueryOptionsUtils; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.blocks.EmptyFilterBlock; -import org.apache.pinot.core.operator.blocks.FilterBlock; +import org.apache.pinot.core.operator.docidsets.EmptyDocIdSet; import org.apache.pinot.core.operator.filter.BaseFilterOperator; import org.apache.pinot.core.operator.filter.BitmapBasedFilterOperator; import org.apache.pinot.core.operator.filter.EmptyFilterOperator; @@ -108,23 +111,27 @@ private static class StarTreeResult { private final StarTreeV2 _starTreeV2; private final Map> _predicateEvaluatorsMap; private final Set _groupByColumns; + private final boolean _scanStarTreeNodes; boolean _resultEmpty = false; public StarTreeFilterOperator(QueryContext queryContext, StarTreeV2 starTreeV2, Map> predicateEvaluatorsMap, @Nullable Set groupByColumns) { + // This filter operator does not support AND/OR/NOT operations. + super(0, false); _queryContext = queryContext; _starTreeV2 = starTreeV2; _predicateEvaluatorsMap = predicateEvaluatorsMap; _groupByColumns = groupByColumns != null ? groupByColumns : Collections.emptySet(); + _scanStarTreeNodes = QueryOptionsUtils.isScanStarTreeNodes(_queryContext.getQueryOptions()); } @Override - public FilterBlock getNextBlock() { + protected BlockDocIdSet getTrues() { if (_resultEmpty) { - return EmptyFilterBlock.getInstance(); + return EmptyDocIdSet.getInstance(); } - return getFilterOperator().nextBlock(); + return getFilterOperator().nextBlock().getBlockDocIdSet(); } @Override @@ -170,18 +177,17 @@ private BaseFilterOperator getFilterOperator() { _predicateEvaluatorsMap.get(remainingPredicateColumn); DataSource dataSource = _starTreeV2.getDataSource(remainingPredicateColumn); for (CompositePredicateEvaluator compositePredicateEvaluator : compositePredicateEvaluators) { - List predicateEvaluators = compositePredicateEvaluator.getPredicateEvaluators(); + List> predicateEvaluators = + compositePredicateEvaluator.getPredicateEvaluators(); int numPredicateEvaluators = predicateEvaluators.size(); if (numPredicateEvaluators == 1) { // Single predicate evaluator - childFilterOperators.add( - FilterOperatorUtils.getLeafFilterOperator(predicateEvaluators.get(0), dataSource, numDocs)); + childFilterOperators.add(getFilterOperator(predicateEvaluators.get(0), dataSource, numDocs)); } else { // Predicate evaluators conjoined with OR List orChildFilterOperators = new ArrayList<>(numPredicateEvaluators); - for (PredicateEvaluator childPredicateEvaluator : predicateEvaluators) { - orChildFilterOperators.add( - FilterOperatorUtils.getLeafFilterOperator(childPredicateEvaluator, dataSource, numDocs)); + for (ObjectBooleanPair childPredicateEvaluator : predicateEvaluators) { + orChildFilterOperators.add(getFilterOperator(childPredicateEvaluator, dataSource, numDocs)); } childFilterOperators.add( FilterOperatorUtils.getOrFilterOperator(_queryContext, orChildFilterOperators, numDocs)); @@ -192,6 +198,17 @@ private BaseFilterOperator getFilterOperator() { return FilterOperatorUtils.getAndFilterOperator(_queryContext, childFilterOperators, numDocs); } + private BaseFilterOperator getFilterOperator(ObjectBooleanPair predicateEvaluator, + DataSource dataSource, int numDocs) { + BaseFilterOperator leafFilterOperator = + FilterOperatorUtils.getLeafFilterOperator(_queryContext, predicateEvaluator.left(), dataSource, numDocs); + if (predicateEvaluator.rightBoolean()) { + return FilterOperatorUtils.getNotFilterOperator(_queryContext, leafFilterOperator, numDocs); + } else { + return leafFilterOperator; + } + } + /** * Helper method to traverse the star tree, get matching documents and keep track of all the predicate columns that * are not matched. Returns {@code null} if no matching dictionary id found for a column (i.e. the result for the @@ -200,19 +217,26 @@ private BaseFilterOperator getFilterOperator() { @Nullable private StarTreeResult traverseStarTree() { MutableRoaringBitmap matchingDocIds = new MutableRoaringBitmap(); - Set globalRemainingPredicateColumns = Collections.emptySet(); - boolean globalRemainingPredicateColumnsSet = false; + Set globalRemainingPredicateColumns = null; StarTree starTree = _starTreeV2.getStarTree(); List dimensionNames = starTree.getDimensionNames(); StarTreeNode starTreeRootNode = starTree.getRoot(); + // Track whether we have found a leaf node added to the queue. If we have found a leaf node, and traversed to the + // level of the leave node, we can set globalRemainingPredicateColumns if not already set because we know the leaf + // node won't split further on other predicate columns. + boolean foundLeafNode = starTreeRootNode.isLeaf(); + // Use BFS to traverse the star tree Queue queue = new ArrayDeque<>(); queue.add(starTreeRootNode); int currentDimensionId = -1; Set remainingPredicateColumns = new HashSet<>(_predicateEvaluatorsMap.keySet()); Set remainingGroupByColumns = new HashSet<>(_groupByColumns); + if (foundLeafNode) { + globalRemainingPredicateColumns = new HashSet<>(remainingPredicateColumns); + } IntSet matchingDictIds = null; StarTreeNode starTreeNode; while ((starTreeNode = queue.poll()) != null) { @@ -222,6 +246,9 @@ private StarTreeResult traverseStarTree() { String dimension = dimensionNames.get(dimensionId); remainingPredicateColumns.remove(dimension); remainingGroupByColumns.remove(dimension); + if (foundLeafNode && globalRemainingPredicateColumns == null) { + globalRemainingPredicateColumns = new HashSet<>(remainingPredicateColumns); + } matchingDictIds = null; currentDimensionId = dimensionId; } @@ -237,14 +264,6 @@ private StarTreeResult traverseStarTree() { // remaining predicate columns for this node if (starTreeNode.isLeaf()) { matchingDocIds.add((long) starTreeNode.getStartDocId(), starTreeNode.getEndDocId()); - // Only set the global remaining predicate columns once because we traverse the tree with BFS, so the first leaf - // node always have all the remaining predicate columns - if (!globalRemainingPredicateColumnsSet) { - if (!remainingPredicateColumns.isEmpty()) { - globalRemainingPredicateColumns = new HashSet<>(remainingPredicateColumns); - } - globalRemainingPredicateColumnsSet = true; - } continue; } @@ -254,8 +273,8 @@ private StarTreeResult traverseStarTree() { // Only read star-node when the dimension is not in the global remaining predicate columns or group-by columns // because we cannot use star-node in such cases StarTreeNode starNode = null; - if (!globalRemainingPredicateColumns.contains(childDimension) && !remainingGroupByColumns.contains( - childDimension)) { + if ((globalRemainingPredicateColumns == null || !globalRemainingPredicateColumns.contains(childDimension)) + && !remainingGroupByColumns.contains(childDimension)) { starNode = starTreeNode.getChildForDimensionValue(StarTreeNode.ALL); } @@ -276,7 +295,7 @@ private StarTreeResult traverseStarTree() { int numChildren = starTreeNode.getNumChildren(); // If number of matching dictionary ids is large, use scan instead of binary search - if (numMatchingDictIds * USE_SCAN_TO_TRAVERSE_NODES_THRESHOLD > numChildren) { + if (numMatchingDictIds * USE_SCAN_TO_TRAVERSE_NODES_THRESHOLD > numChildren || _scanStarTreeNodes) { Iterator childrenIterator = starTreeNode.getChildrenIterator(); // When the star-node exists, and the number of matching dictionary ids is more than or equal to the @@ -284,18 +303,22 @@ private StarTreeResult traverseStarTree() { // star-node if so if (starNode != null && numMatchingDictIds >= numChildren - 1) { List matchingChildNodes = new ArrayList<>(); + boolean findLeafChildNode = false; while (childrenIterator.hasNext()) { StarTreeNode childNode = childrenIterator.next(); if (matchingDictIds.contains(childNode.getDimensionValue())) { matchingChildNodes.add(childNode); + findLeafChildNode |= childNode.isLeaf(); } } if (matchingChildNodes.size() == numChildren - 1) { // All the child nodes (except for the star-node) match the predicate, use the star-node queue.add(starNode); + foundLeafNode |= starNode.isLeaf(); } else { // Some child nodes do not match the predicate, use the matching child nodes queue.addAll(matchingChildNodes); + foundLeafNode |= findLeafChildNode; } } else { // Cannot use the star-node, use the matching child nodes @@ -303,6 +326,7 @@ private StarTreeResult traverseStarTree() { StarTreeNode childNode = childrenIterator.next(); if (matchingDictIds.contains(childNode.getDimensionValue())) { queue.add(childNode); + foundLeafNode |= childNode.isLeaf(); } } } @@ -315,6 +339,7 @@ private StarTreeResult traverseStarTree() { // Child node might be null because the matching dictionary id might not exist under this branch if (childNode != null) { queue.add(childNode); + foundLeafNode |= childNode.isLeaf(); } } } @@ -324,6 +349,7 @@ private StarTreeResult traverseStarTree() { if (starNode != null) { // Star-node exists, use it queue.add(starNode); + foundLeafNode |= starNode.isLeaf(); } else { // Star-node does not exist or cannot be used, add all non-star nodes to the queue Iterator childrenIterator = starTreeNode.getChildrenIterator(); @@ -331,13 +357,15 @@ private StarTreeResult traverseStarTree() { StarTreeNode childNode = childrenIterator.next(); if (childNode.getDimensionValue() != StarTreeNode.ALL) { queue.add(childNode); + foundLeafNode |= childNode.isLeaf(); } } } } } - return new StarTreeResult(matchingDocIds, globalRemainingPredicateColumns); + return new StarTreeResult(matchingDocIds, + globalRemainingPredicateColumns != null ? globalRemainingPredicateColumns : Collections.emptySet()); } /** @@ -369,24 +397,28 @@ public int compare(CompositePredicateEvaluator o1, CompositePredicateEvaluator o } int getPriority(CompositePredicateEvaluator compositePredicateEvaluator) { - List predicateEvaluators = compositePredicateEvaluator.getPredicateEvaluators(); + List> predicateEvaluators = + compositePredicateEvaluator.getPredicateEvaluators(); if (predicateEvaluators.size() == 1) { - switch (predicateEvaluators.get(0).getPredicateType()) { + ObjectBooleanPair predicateEvaluator = predicateEvaluators.get(0); + boolean negated = predicateEvaluator.rightBoolean(); + switch (predicateEvaluator.left().getPredicateType()) { case EQ: - return 1; + return negated ? 5 : 1; case IN: - return 2; + return negated ? 4 : 2; case RANGE: return 3; - case NOT_EQ: case NOT_IN: - return 4; + return negated ? 2 : 4; + case NOT_EQ: + return negated ? 1 : 5; default: - throw new UnsupportedOperationException(); + throw new IllegalStateException(); } } else { // Process OR at last - return 5; + return 6; } } }); @@ -416,12 +448,25 @@ int getPriority(CompositePredicateEvaluator compositePredicateEvaluator) { * Returns the matching dictionary ids for the given composite predicate evaluator. */ private IntSet getMatchingDictIds(CompositePredicateEvaluator compositePredicateEvaluator) { - IntSet matchingDictIds = new IntOpenHashSet(); - for (PredicateEvaluator predicateEvaluator : compositePredicateEvaluator.getPredicateEvaluators()) { - for (int matchingDictId : predicateEvaluator.getMatchingDictIds()) { - matchingDictIds.add(matchingDictId); + List> predicateEvaluators = + compositePredicateEvaluator.getPredicateEvaluators(); + if (predicateEvaluators.size() == 1) { + ObjectBooleanPair predicateEvaluator = predicateEvaluators.get(0); + if (predicateEvaluator.rightBoolean()) { + return new IntOpenHashSet(predicateEvaluator.left().getNonMatchingDictIds()); + } else { + return new IntOpenHashSet(predicateEvaluator.left().getMatchingDictIds()); } + } else { + IntSet matchingDictIds = new IntOpenHashSet(); + for (ObjectBooleanPair predicateEvaluator : predicateEvaluators) { + if (predicateEvaluator.rightBoolean()) { + matchingDictIds.addAll(new IntImmutableList(predicateEvaluator.left().getNonMatchingDictIds())); + } else { + matchingDictIds.addAll(new IntImmutableList(predicateEvaluator.left().getMatchingDictIds())); + } + } + return matchingDictIds; } - return matchingDictIds; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/startree/plan/StarTreeProjectPlanNode.java b/pinot-core/src/main/java/org/apache/pinot/core/startree/plan/StarTreeProjectPlanNode.java new file mode 100644 index 000000000000..34d831cb25ba --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/startree/plan/StarTreeProjectPlanNode.java @@ -0,0 +1,92 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.startree.plan; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nullable; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.utils.HashUtil; +import org.apache.pinot.core.operator.BaseProjectOperator; +import org.apache.pinot.core.operator.DocIdSetOperator; +import org.apache.pinot.core.operator.ProjectionOperator; +import org.apache.pinot.core.operator.ProjectionOperatorUtils; +import org.apache.pinot.core.operator.transform.TransformOperator; +import org.apache.pinot.core.plan.PlanNode; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.startree.CompositePredicateEvaluator; +import org.apache.pinot.segment.spi.datasource.DataSource; +import org.apache.pinot.segment.spi.index.startree.AggregationFunctionColumnPair; +import org.apache.pinot.segment.spi.index.startree.StarTreeV2; + + +public class StarTreeProjectPlanNode implements PlanNode { + private final QueryContext _queryContext; + private final StarTreeV2 _starTreeV2; + private final AggregationFunctionColumnPair[] _aggregationFunctionColumnPairs; + private final ExpressionContext[] _groupByExpressions; + private final Map> _predicateEvaluatorsMap; + + public StarTreeProjectPlanNode(QueryContext queryContext, StarTreeV2 starTreeV2, + AggregationFunctionColumnPair[] aggregationFunctionColumnPairs, @Nullable ExpressionContext[] groupByExpressions, + Map> predicateEvaluatorsMap) { + _queryContext = queryContext; + _starTreeV2 = starTreeV2; + _aggregationFunctionColumnPairs = aggregationFunctionColumnPairs; + _groupByExpressions = groupByExpressions; + _predicateEvaluatorsMap = predicateEvaluatorsMap; + } + + @Override + public BaseProjectOperator run() { + Set projectionColumns = new HashSet<>(); + boolean hasNonIdentifierExpression = false; + for (AggregationFunctionColumnPair aggregationFunctionColumnPair : _aggregationFunctionColumnPairs) { + projectionColumns.add(aggregationFunctionColumnPair.toColumnName()); + } + Set groupByColumns; + if (_groupByExpressions != null) { + groupByColumns = new HashSet<>(); + for (ExpressionContext expression : _groupByExpressions) { + expression.getColumns(groupByColumns); + if (expression.getType() != ExpressionContext.Type.IDENTIFIER) { + hasNonIdentifierExpression = true; + } + } + projectionColumns.addAll(groupByColumns); + } else { + groupByColumns = null; + } + DocIdSetOperator docIdSetOperator = + new StarTreeDocIdSetPlanNode(_queryContext, _starTreeV2, _predicateEvaluatorsMap, groupByColumns).run(); + Map dataSourceMap = new HashMap<>(HashUtil.getHashMapCapacity(projectionColumns.size())); + projectionColumns.forEach(column -> dataSourceMap.put(column, _starTreeV2.getDataSource(column))); + ProjectionOperator projectionOperator = + ProjectionOperatorUtils.getProjectionOperator(dataSourceMap, docIdSetOperator); + // NOTE: Here we do not put aggregation expressions into TransformOperator based on the following assumptions: + // - They are all columns (not functions or constants), where no transform is required + // - We never call TransformOperator.getResultColumnContext() on them + return hasNonIdentifierExpression ? new TransformOperator(_queryContext, projectionOperator, + Arrays.asList(_groupByExpressions)) : projectionOperator; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/startree/plan/StarTreeProjectionPlanNode.java b/pinot-core/src/main/java/org/apache/pinot/core/startree/plan/StarTreeProjectionPlanNode.java deleted file mode 100644 index 2238193050eb..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/startree/plan/StarTreeProjectionPlanNode.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.startree.plan; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.annotation.Nullable; -import org.apache.pinot.core.operator.ProjectionOperator; -import org.apache.pinot.core.plan.PlanNode; -import org.apache.pinot.core.query.request.context.QueryContext; -import org.apache.pinot.core.startree.CompositePredicateEvaluator; -import org.apache.pinot.segment.spi.datasource.DataSource; -import org.apache.pinot.segment.spi.index.startree.StarTreeV2; - - -public class StarTreeProjectionPlanNode implements PlanNode { - private final Map _dataSourceMap; - private final StarTreeDocIdSetPlanNode _starTreeDocIdSetPlanNode; - - public StarTreeProjectionPlanNode(QueryContext queryContext, StarTreeV2 starTreeV2, Set projectionColumns, - Map> predicateEvaluatorsMap, @Nullable Set groupByColumns) { - _dataSourceMap = new HashMap<>(); - for (String projectionColumn : projectionColumns) { - _dataSourceMap.put(projectionColumn, starTreeV2.getDataSource(projectionColumn)); - } - _starTreeDocIdSetPlanNode = - new StarTreeDocIdSetPlanNode(queryContext, starTreeV2, predicateEvaluatorsMap, groupByColumns); - } - - @Override - public ProjectionOperator run() { - return new ProjectionOperator(_dataSourceMap, _starTreeDocIdSetPlanNode.run()); - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/startree/plan/StarTreeTransformPlanNode.java b/pinot-core/src/main/java/org/apache/pinot/core/startree/plan/StarTreeTransformPlanNode.java deleted file mode 100644 index 283891dcbc29..000000000000 --- a/pinot-core/src/main/java/org/apache/pinot/core/startree/plan/StarTreeTransformPlanNode.java +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.startree.plan; - -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.annotation.Nullable; -import org.apache.pinot.common.request.context.ExpressionContext; -import org.apache.pinot.core.operator.transform.TransformOperator; -import org.apache.pinot.core.plan.PlanNode; -import org.apache.pinot.core.query.request.context.QueryContext; -import org.apache.pinot.core.startree.CompositePredicateEvaluator; -import org.apache.pinot.segment.spi.index.startree.AggregationFunctionColumnPair; -import org.apache.pinot.segment.spi.index.startree.StarTreeV2; - - -public class StarTreeTransformPlanNode implements PlanNode { - private final List _groupByExpressions; - private final StarTreeProjectionPlanNode _starTreeProjectionPlanNode; - - public StarTreeTransformPlanNode(QueryContext queryContext, StarTreeV2 starTreeV2, - AggregationFunctionColumnPair[] aggregationFunctionColumnPairs, @Nullable ExpressionContext[] groupByExpressions, - Map> predicateEvaluatorsMap) { - Set projectionColumns = new HashSet<>(); - for (AggregationFunctionColumnPair aggregationFunctionColumnPair : aggregationFunctionColumnPairs) { - projectionColumns.add(aggregationFunctionColumnPair.toColumnName()); - } - Set groupByColumns; - if (groupByExpressions != null) { - _groupByExpressions = Arrays.asList(groupByExpressions); - groupByColumns = new HashSet<>(); - for (ExpressionContext groupByExpression : groupByExpressions) { - groupByExpression.getColumns(groupByColumns); - } - projectionColumns.addAll(groupByColumns); - } else { - _groupByExpressions = Collections.emptyList(); - groupByColumns = null; - } - _starTreeProjectionPlanNode = - new StarTreeProjectionPlanNode(queryContext, starTreeV2, projectionColumns, predicateEvaluatorsMap, - groupByColumns); - } - - @Override - public TransformOperator run() { - // NOTE: Here we do not put aggregation expressions into TransformOperator based on the following assumptions: - // - They are all columns (not functions or constants), where no transform is required - // - We never call TransformOperator.getResultMetadata() or TransformOperator.getDictionary() on them - return new TransformOperator(_starTreeProjectionPlanNode.run(), _groupByExpressions); - } -} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/transport/AsyncQueryResponse.java b/pinot-core/src/main/java/org/apache/pinot/core/transport/AsyncQueryResponse.java index 3aecb184beac..2ec90ab3b905 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/transport/AsyncQueryResponse.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/transport/AsyncQueryResponse.java @@ -28,6 +28,7 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; import org.apache.pinot.common.datatable.DataTable; +import org.apache.pinot.common.utils.HashUtil; import org.apache.pinot.core.transport.server.routing.stats.ServerRoutingStatsManager; @@ -54,14 +55,18 @@ public AsyncQueryResponse(QueryRouter queryRouter, long requestId, Set(numServersQueried); + _responseMap = new ConcurrentHashMap<>(HashUtil.getHashMapCapacity(numServersQueried)); + _serverRoutingStatsManager = serverRoutingStatsManager; for (ServerRoutingInstance serverRoutingInstance : serversQueried) { + // Record stats related to query submission just before sending the request. Otherwise, if the response is + // received immediately, there's a possibility of updating query response stats before updating query + // submission stats. + _serverRoutingStatsManager.recordStatsForQuerySubmission(requestId, serverRoutingInstance.getInstanceId()); _responseMap.put(serverRoutingInstance, new ServerResponse(startTimeMs)); } _countDownLatch = new CountDownLatch(numServersQueried); _timeoutMs = timeoutMs; _maxEndTimeMs = startTimeMs + timeoutMs; - _serverRoutingStatsManager = serverRoutingStatsManager; } @Override @@ -87,15 +92,16 @@ public Map getFinalResponses() _status.compareAndSet(Status.IN_PROGRESS, finish ? Status.COMPLETED : Status.TIMED_OUT); return _responseMap; } finally { - // Update ServerRoutingStats. + // Update ServerRoutingStats for query completion. This is done here to ensure that the stats are updated for + // servers even if the query times out or if servers have not responded. for (Map.Entry entry : _responseMap.entrySet()) { ServerResponse response = entry.getValue(); - if (response == null || response.getDataTable() == null) { - // These are servers from which a response was not received. So update query response stats for such - // servers with maximum latency i.e timeout value. - _serverRoutingStatsManager.recordStatsUponResponseArrival(_requestId, entry.getKey().getInstanceId(), - _timeoutMs); - } + + // ServerResponse returns -1 if responseDelayMs is not set. This indicates that a response was not received + // from the server. Hence we set the latency to the timeout value. + long latency = + (response != null && response.getResponseDelayMs() >= 0) ? response.getResponseDelayMs() : _timeoutMs; + _serverRoutingStatsManager.recordStatsUponResponseArrival(_requestId, entry.getKey().getInstanceId(), latency); } _queryRouter.markQueryDone(_requestId); @@ -151,12 +157,6 @@ void receiveDataTable(ServerRoutingInstance serverRoutingInstance, DataTable dat ServerResponse response = _responseMap.get(serverRoutingInstance); response.receiveDataTable(dataTable, responseSize, deserializationTimeMs); - // Record query completion stats immediately after receiving the response from the server instead of waiting - // for all servers to respond. This helps to keep the stats up-to-date. - long latencyMs = response.getResponseDelayMs(); - _serverRoutingStatsManager.recordStatsUponResponseArrival(_requestId, serverRoutingInstance.getInstanceId(), - latencyMs); - _numServersResponded.getAndIncrement(); _countDownLatch.countDown(); } @@ -181,4 +181,12 @@ void markServerDown(ServerRoutingInstance serverRoutingInstance, Exception excep markQueryFailed(serverRoutingInstance, exception); } } + + /** + * Wait for one less server response. This is used when the server is skipped, as + * query submission will have failed we do not want to wait for the response. + */ + void skipServerResponse() { + _countDownLatch.countDown(); + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/transport/ChannelHandlerFactory.java b/pinot-core/src/main/java/org/apache/pinot/core/transport/ChannelHandlerFactory.java index 94b1c3c1d93b..00545f2607f3 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/transport/ChannelHandlerFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/transport/ChannelHandlerFactory.java @@ -22,10 +22,13 @@ import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; +import io.netty.handler.ssl.SslContext; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import org.apache.pinot.common.config.TlsConfig; import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.metrics.ServerMetrics; -import org.apache.pinot.common.utils.TlsUtils; +import org.apache.pinot.common.utils.tls.TlsUtils; import org.apache.pinot.core.query.scheduler.QueryScheduler; import org.apache.pinot.server.access.AccessControl; import org.apache.pinot.spi.env.PinotConfiguration; @@ -35,8 +38,15 @@ * The {@code ChannelHandlerFactory} provides all kinds of Netty ChannelHandlers */ public class ChannelHandlerFactory { - public static final String SSL = "ssl"; + // The key is the hashCode of the TlsConfig, the value is the SslContext + // We don't use TlsConfig as the map key because the TlsConfig is mutable, which means the hashCode can change. If the + // hashCode changes and the map is resized, the SslContext of the old hashCode will be lost. + private static final Map CLIENT_SSL_CONTEXTS_CACHE = new ConcurrentHashMap<>(); + // the key is the hashCode of the TlsConfig, the value is the SslContext + // We don't use TlsConfig as the map key because the TlsConfig is mutable, which means the hashCode can change. If the + // hashCode changes and the map is resized, the SslContext of the old hashCode will be lost. + private static final Map SERVER_SSL_CONTEXTS_CACHE = new ConcurrentHashMap<>(); private ChannelHandlerFactory() { } @@ -60,14 +70,18 @@ public static ChannelHandler getLengthFieldPrepender() { * The {@code getClientTlsHandler} return a Client side Tls handler that encrypt and decrypt everything. */ public static ChannelHandler getClientTlsHandler(TlsConfig tlsConfig, SocketChannel ch) { - return TlsUtils.buildClientContext(tlsConfig).newHandler(ch.alloc()); + SslContext sslContext = CLIENT_SSL_CONTEXTS_CACHE + .computeIfAbsent(tlsConfig.hashCode(), tlsConfigHashCode -> TlsUtils.buildClientContext(tlsConfig)); + return sslContext.newHandler(ch.alloc()); } /** * The {@code getServerTlsHandler} return a Server side Tls handler that encrypt and decrypt everything. */ public static ChannelHandler getServerTlsHandler(TlsConfig tlsConfig, SocketChannel ch) { - return TlsUtils.buildServerContext(tlsConfig).newHandler(ch.alloc()); + SslContext sslContext = SERVER_SSL_CONTEXTS_CACHE.computeIfAbsent( + tlsConfig.hashCode(), tlsConfigHashCode -> TlsUtils.buildServerContext(tlsConfig)); + return sslContext.newHandler(ch.alloc()); } /** @@ -87,4 +101,9 @@ public static ChannelHandler getInstanceRequestHandler(String instanceName, Pino QueryScheduler queryScheduler, ServerMetrics serverMetrics, AccessControl accessControl) { return new InstanceRequestHandler(instanceName, config, queryScheduler, serverMetrics, accessControl); } + + public static ChannelHandler getDirectOOMHandler(QueryRouter queryRouter, ServerRoutingInstance serverRoutingInstance, + ConcurrentHashMap serverToChannelMap) { + return new DirectOOMHandler(queryRouter, serverRoutingInstance, serverToChannelMap); + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/transport/DataTableHandler.java b/pinot-core/src/main/java/org/apache/pinot/core/transport/DataTableHandler.java index a5ef07b2e531..3a117a49912a 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/transport/DataTableHandler.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/transport/DataTableHandler.java @@ -25,6 +25,7 @@ import org.apache.pinot.common.datatable.DataTableFactory; import org.apache.pinot.common.metrics.BrokerMeter; import org.apache.pinot.common.metrics.BrokerMetrics; +import org.apache.pinot.spi.trace.Tracing; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -61,6 +62,7 @@ public void channelInactive(ChannelHandlerContext ctx) { @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) { + Tracing.ThreadAccountantOps.setThreadResourceUsageProvider(); int responseSize = msg.readableBytes(); _brokerMetrics.addMeteredGlobalValue(BrokerMeter.NETTY_CONNECTION_BYTES_RECEIVED, responseSize); try { @@ -68,6 +70,8 @@ protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) { DataTable dataTable = DataTableFactory.getDataTable(msg.nioBuffer()); _queryRouter.receiveDataTable(_serverRoutingInstance, dataTable, responseSize, (int) (System.currentTimeMillis() - deserializationStartTimeMs)); + long requestID = Long.parseLong(dataTable.getMetadata().get(DataTable.MetadataKey.REQUEST_ID.getName())); + Tracing.ThreadAccountantOps.updateQueryUsageConcurrently(String.valueOf(requestID)); } catch (Exception e) { LOGGER.error("Caught exception while deserializing data table of size: {} from server: {}", responseSize, _serverRoutingInstance, e); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/transport/DirectOOMHandler.java b/pinot-core/src/main/java/org/apache/pinot/core/transport/DirectOOMHandler.java new file mode 100644 index 000000000000..0ba00327f637 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/transport/DirectOOMHandler.java @@ -0,0 +1,97 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.transport; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.commons.lang3.StringUtils; +import org.apache.pinot.common.metrics.BrokerMeter; +import org.apache.pinot.common.metrics.BrokerMetrics; +import org.apache.pinot.spi.exception.QueryCancelledException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Handling netty direct memory OOM. In this case there is a great chance that multiple channels are receiving + * large data tables from servers concurrently. We want to close all channels to servers to proactively release + * the direct memory, because the execution of netty threads can all block in allocating direct memory, in which case + * no one will reach channelRead0. + */ +public class DirectOOMHandler extends ChannelInboundHandlerAdapter { + private static final Logger LOGGER = LoggerFactory.getLogger(DirectOOMHandler.class); + private static final AtomicBoolean DIRECT_OOM_SHUTTING_DOWN = new AtomicBoolean(false); + private final QueryRouter _queryRouter; + private final ServerRoutingInstance _serverRoutingInstance; + private final ConcurrentHashMap _serverToChannelMap; + private volatile boolean _silentShutDown = false; + + public DirectOOMHandler(QueryRouter queryRouter, ServerRoutingInstance serverRoutingInstance, + ConcurrentHashMap serverToChannelMap) { + _queryRouter = queryRouter; + _serverRoutingInstance = serverRoutingInstance; + _serverToChannelMap = serverToChannelMap; + } + + public void setSilentShutDown() { + _silentShutDown = true; + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + // if we are shutting down channels due to direct memory OOM, we short circuit the channel inactive + if (_silentShutDown) { + return; + } + ctx.fireChannelInactive(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + // catch direct memory oom here + if (cause instanceof OutOfMemoryError + && StringUtils.containsIgnoreCase(cause.getMessage(), "direct buffer")) { + BrokerMetrics.get().addMeteredGlobalValue(BrokerMeter.DIRECT_MEMORY_OOM, 1L); + // only one thread can get here and do the shutdown + if (DIRECT_OOM_SHUTTING_DOWN.compareAndSet(false, true)) { + try { + LOGGER.error("Closing ALL channels to servers, as we are running out of direct memory " + + "while receiving response from {}", _serverRoutingInstance, cause); + // close all channels to servers + _serverToChannelMap.keySet().forEach(serverRoutingInstance -> { + ServerChannels.ServerChannel removed = _serverToChannelMap.remove(serverRoutingInstance); + removed.closeChannel(); + removed.setSilentShutdown(); + }); + _queryRouter.markServerDown(_serverRoutingInstance, + new QueryCancelledException("Query cancelled as broker is out of direct memory")); + } catch (Exception e) { + LOGGER.error("Caught exception while handling direct memory OOM", e); + } finally { + DIRECT_OOM_SHUTTING_DOWN.set(false); + } + } else { + LOGGER.warn("Caught direct memory OOM, but another thread is already handling it", cause); + } + } else { + ctx.fireExceptionCaught(cause); + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/transport/HttpServerThreadPoolConfig.java b/pinot-core/src/main/java/org/apache/pinot/core/transport/HttpServerThreadPoolConfig.java new file mode 100644 index 000000000000..c4bf6d510932 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/transport/HttpServerThreadPoolConfig.java @@ -0,0 +1,59 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.transport; + +/** + * This configures the thread pool configs for the Http servers in Pinot server, controller, broker and minion. + */ +public class HttpServerThreadPoolConfig { + private static final HttpServerThreadPoolConfig DEFAULT = + new HttpServerThreadPoolConfig(Runtime.getRuntime().availableProcessors() * 2, + Runtime.getRuntime().availableProcessors() * 2); + private int _maxPoolSize; + private int _corePoolSize; + + public HttpServerThreadPoolConfig(int corePoolSize, int maxPoolSize) { + _maxPoolSize = maxPoolSize; + _corePoolSize = corePoolSize; + } + + public static HttpServerThreadPoolConfig defaultInstance() { + return DEFAULT.copy(); + } + + public int getMaxPoolSize() { + return _maxPoolSize; + } + + public void setMaxPoolSize(int maxPoolSize) { + _maxPoolSize = maxPoolSize; + } + + public int getCorePoolSize() { + return _corePoolSize; + } + + public void setCorePoolSize(int corePoolSize) { + _corePoolSize = corePoolSize; + } + + public HttpServerThreadPoolConfig copy() { + return new HttpServerThreadPoolConfig(_corePoolSize, _maxPoolSize); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/transport/InstanceRequestHandler.java b/pinot-core/src/main/java/org/apache/pinot/core/transport/InstanceRequestHandler.java index 480b9b042758..4940823b741e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/transport/InstanceRequestHandler.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/transport/InstanceRequestHandler.java @@ -71,6 +71,7 @@ public class InstanceRequestHandler extends SimpleChannelInboundHandler // TODO: make it configurable private static final int SLOW_QUERY_LATENCY_THRESHOLD_MS = 100; + private static final int LARGE_RESPONSE_SIZE_THRESHOLD_BYTES = 100 * 1024 * 1024; // 100 MB private final String _instanceName; private final ThreadLocal _deserializer; @@ -283,9 +284,10 @@ private void sendErrorResponse(ChannelHandlerContext ctx, long requestId, String dataTableMetadata.put(MetadataKey.REQUEST_ID.getName(), Long.toString(requestId)); if (cancelled) { dataTable.addException(QueryException.getException(QueryException.QUERY_CANCELLATION_ERROR, - "Query cancelled on: " + _instanceName + e)); + "Query cancelled on: " + _instanceName + " " + e)); } else { - dataTable.addException(QueryException.getException(QueryException.QUERY_EXECUTION_ERROR, e)); + dataTable.addException(QueryException.getException(QueryException.QUERY_EXECUTION_ERROR, + "Query execution error on: " + _instanceName + " " + e)); } byte[] serializedDataTable = dataTable.toBytes(); sendResponse(ctx, tableNameWithType, queryArrivalTimeMs, serializedDataTable); @@ -320,6 +322,11 @@ private void sendResponse(ChannelHandlerContext ctx, String tableNameWithType, l LOGGER.info("Slow query: request handler processing time: {}, send response latency: {}, total time to handle " + "request: {}", queryProcessingTimeMs, sendResponseLatencyMs, totalQueryTimeMs); } + if (serializedDataTable.length > LARGE_RESPONSE_SIZE_THRESHOLD_BYTES) { + LOGGER.warn("Large query: response size in bytes: {}, table name {}", + serializedDataTable.length, tableNameWithType); + ServerMetrics.get().addMeteredTableValue(tableNameWithType, ServerMeter.LARGE_QUERY_RESPONSES_SENT, 1); + } }); } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/transport/ListenerConfig.java b/pinot-core/src/main/java/org/apache/pinot/core/transport/ListenerConfig.java index 7c47d38b5593..172efe02bb96 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/transport/ListenerConfig.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/transport/ListenerConfig.java @@ -31,13 +31,16 @@ public class ListenerConfig { private final int _port; private final String _protocol; private final TlsConfig _tlsConfig; + private final HttpServerThreadPoolConfig _threadPoolConfig; - public ListenerConfig(String name, String host, int port, String protocol, TlsConfig tlsConfig) { + public ListenerConfig(String name, String host, int port, String protocol, TlsConfig tlsConfig, + HttpServerThreadPoolConfig threadPoolConfig) { _name = name; _host = host; _port = port; _protocol = protocol; _tlsConfig = tlsConfig; + _threadPoolConfig = threadPoolConfig; } public String getName() { @@ -59,4 +62,8 @@ public String getProtocol() { public TlsConfig getTlsConfig() { return _tlsConfig; } + + public HttpServerThreadPoolConfig getThreadPoolConfig() { + return _threadPoolConfig; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/transport/QueryRouter.java b/pinot-core/src/main/java/org/apache/pinot/core/transport/QueryRouter.java index e0ff080e1169..2b7561186524 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/transport/QueryRouter.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/transport/QueryRouter.java @@ -25,6 +25,8 @@ import java.util.concurrent.TimeoutException; import javax.annotation.Nullable; import javax.annotation.concurrent.ThreadSafe; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.lang3.tuple.Pair; import org.apache.pinot.common.config.NettyConfig; import org.apache.pinot.common.config.TlsConfig; import org.apache.pinot.common.datatable.DataTable; @@ -33,6 +35,7 @@ import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.common.request.InstanceRequest; +import org.apache.pinot.common.utils.config.QueryOptionsUtils; import org.apache.pinot.core.transport.server.routing.stats.ServerRoutingStatsManager; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.utils.CommonConstants; @@ -85,19 +88,23 @@ public QueryRouter(String brokerId, BrokerMetrics brokerMetrics, @Nullable Netty } public AsyncQueryResponse submitQuery(long requestId, String rawTableName, - @Nullable BrokerRequest offlineBrokerRequest, @Nullable Map> offlineRoutingTable, - @Nullable BrokerRequest realtimeBrokerRequest, @Nullable Map> realtimeRoutingTable, - long timeoutMs) { + @Nullable BrokerRequest offlineBrokerRequest, + @Nullable Map, List>> offlineRoutingTable, + @Nullable BrokerRequest realtimeBrokerRequest, + @Nullable Map, List>> realtimeRoutingTable, long timeoutMs) { assert offlineBrokerRequest != null || realtimeBrokerRequest != null; // can prefer but not require TLS until all servers guaranteed to be on TLS boolean preferTls = _serverChannelsTls != null; + // skip unavailable servers if the query option is set + boolean skipUnavailableServers = isSkipUnavailableServers(offlineBrokerRequest, realtimeBrokerRequest); + // Build map from server to request based on the routing table Map requestMap = new HashMap<>(); if (offlineBrokerRequest != null) { assert offlineRoutingTable != null; - for (Map.Entry> entry : offlineRoutingTable.entrySet()) { + for (Map.Entry, List>> entry : offlineRoutingTable.entrySet()) { ServerRoutingInstance serverRoutingInstance = entry.getKey().toServerRoutingInstance(TableType.OFFLINE, preferTls); InstanceRequest instanceRequest = getInstanceRequest(requestId, offlineBrokerRequest, entry.getValue()); @@ -106,7 +113,7 @@ public AsyncQueryResponse submitQuery(long requestId, String rawTableName, } if (realtimeBrokerRequest != null) { assert realtimeRoutingTable != null; - for (Map.Entry> entry : realtimeRoutingTable.entrySet()) { + for (Map.Entry, List>> entry : realtimeRoutingTable.entrySet()) { ServerRoutingInstance serverRoutingInstance = entry.getKey().toServerRoutingInstance(TableType.REALTIME, preferTls); InstanceRequest instanceRequest = getInstanceRequest(requestId, realtimeBrokerRequest, entry.getValue()); @@ -123,10 +130,6 @@ public AsyncQueryResponse submitQuery(long requestId, String rawTableName, ServerRoutingInstance serverRoutingInstance = entry.getKey(); ServerChannels serverChannels = serverRoutingInstance.isTlsEnabled() ? _serverChannelsTls : _serverChannels; try { - // Record stats related to query submission just before sending the request. Otherwise, if the response is - // received immediately, there's a possibility of updating query response stats before updating query - // submission stats. - _serverRoutingStatsManager.recordStatsAfterQuerySubmission(requestId, serverRoutingInstance.getInstanceId()); serverChannels.sendRequest(rawTableName, asyncQueryResponse, serverRoutingInstance, entry.getValue(), timeoutMs); asyncQueryResponse.markRequestSubmitted(serverRoutingInstance); @@ -138,14 +141,28 @@ public AsyncQueryResponse submitQuery(long requestId, String rawTableName, break; } catch (Exception e) { _brokerMetrics.addMeteredTableValue(rawTableName, BrokerMeter.REQUEST_SEND_EXCEPTIONS, 1); - markQueryFailed(requestId, serverRoutingInstance, asyncQueryResponse, e); - break; + if (skipUnavailableServers) { + asyncQueryResponse.skipServerResponse(); + } else { + markQueryFailed(requestId, serverRoutingInstance, asyncQueryResponse, e); + break; + } } } return asyncQueryResponse; } + private boolean isSkipUnavailableServers(@Nullable BrokerRequest offlineBrokerRequest, + @Nullable BrokerRequest realtimeBrokerRequest) { + if (offlineBrokerRequest != null && QueryOptionsUtils.isSkipUnavailableServers( + offlineBrokerRequest.getPinotQuery().getQueryOptions())) { + return true; + } + return realtimeBrokerRequest != null && QueryOptionsUtils.isSkipUnavailableServers( + realtimeBrokerRequest.getPinotQuery().getQueryOptions()); + } + private void markQueryFailed(long requestId, ServerRoutingInstance serverRoutingInstance, AsyncQueryResponse asyncQueryResponse, Exception e) { LOGGER.error("Caught exception while sending request {} to server: {}, marking query failed", requestId, @@ -195,7 +212,8 @@ void markQueryDone(long requestId) { _asyncQueryResponseMap.remove(requestId); } - private InstanceRequest getInstanceRequest(long requestId, BrokerRequest brokerRequest, List segments) { + private InstanceRequest getInstanceRequest(long requestId, BrokerRequest brokerRequest, + Pair, List> segments) { InstanceRequest instanceRequest = new InstanceRequest(); instanceRequest.setRequestId(requestId); instanceRequest.setQuery(brokerRequest); @@ -203,8 +221,14 @@ private InstanceRequest getInstanceRequest(long requestId, BrokerRequest brokerR if (queryOptions != null) { instanceRequest.setEnableTrace(Boolean.parseBoolean(queryOptions.get(CommonConstants.Broker.Request.TRACE))); } - instanceRequest.setSearchSegments(segments); + instanceRequest.setSearchSegments(segments.getLeft()); instanceRequest.setBrokerId(_brokerId); + if (CollectionUtils.isNotEmpty(segments.getRight())) { + // Don't set this field, i.e. leave it as null, if there is no optional segment at all, to be more backward + // compatible, as there are places like in multi-stage query engine where this field is not set today when + // creating the InstanceRequest. + instanceRequest.setOptionalSegments(segments.getRight()); + } return instanceRequest; } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/transport/QueryServer.java b/pinot-core/src/main/java/org/apache/pinot/core/transport/QueryServer.java index 9bca2a6ca108..0e1c7d1aa07d 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/transport/QueryServer.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/transport/QueryServer.java @@ -18,8 +18,10 @@ */ package org.apache.pinot.core.transport; +import com.google.common.annotations.VisibleForTesting; import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.Channel; +import io.netty.buffer.PooledByteBufAllocator; +import io.netty.buffer.PooledByteBufAllocatorMetric; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; @@ -37,6 +39,8 @@ import java.util.concurrent.TimeUnit; import org.apache.pinot.common.config.NettyConfig; import org.apache.pinot.common.config.TlsConfig; +import org.apache.pinot.common.metrics.ServerGauge; +import org.apache.pinot.common.metrics.ServerMetrics; import org.apache.pinot.core.util.OsCheck; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -55,7 +59,7 @@ public class QueryServer { private final EventLoopGroup _workerGroup; private final Class _channelClass; private final ChannelHandler _instanceRequestHandler; - private Channel _channel; + private ServerSocketChannel _channel; /** * Create an unsecured server instance @@ -81,16 +85,12 @@ public QueryServer(int port, NettyConfig nettyConfig, TlsConfig tlsConfig, Chann boolean enableNativeTransports = nettyConfig != null && nettyConfig.isNativeTransportsEnabled(); OsCheck.OSType operatingSystemType = OsCheck.getOperatingSystemType(); - if (enableNativeTransports - && operatingSystemType == OsCheck.OSType.Linux - && Epoll.isAvailable()) { + if (enableNativeTransports && operatingSystemType == OsCheck.OSType.Linux && Epoll.isAvailable()) { _bossGroup = new EpollEventLoopGroup(); _workerGroup = new EpollEventLoopGroup(); _channelClass = EpollServerSocketChannel.class; LOGGER.info("Using Epoll event loop"); - } else if (enableNativeTransports - && operatingSystemType == OsCheck.OSType.MacOS - && KQueue.isAvailable()) { + } else if (enableNativeTransports && operatingSystemType == OsCheck.OSType.MacOS && KQueue.isAvailable()) { _bossGroup = new KQueueEventLoopGroup(); _workerGroup = new KQueueEventLoopGroup(); _channelClass = KQueueServerSocketChannel.class; @@ -100,11 +100,9 @@ public QueryServer(int port, NettyConfig nettyConfig, TlsConfig tlsConfig, Chann _workerGroup = new NioEventLoopGroup(); _channelClass = NioServerSocketChannel.class; StringBuilder log = new StringBuilder("Using NIO event loop"); - if (operatingSystemType == OsCheck.OSType.Linux - && enableNativeTransports) { + if (operatingSystemType == OsCheck.OSType.Linux && enableNativeTransports) { log.append(", as Epoll is not available: ").append(Epoll.unavailabilityCause()); - } else if (operatingSystemType == OsCheck.OSType.MacOS - && enableNativeTransports) { + } else if (operatingSystemType == OsCheck.OSType.MacOS && enableNativeTransports) { log.append(", as KQueue is not available: ").append(KQueue.unavailabilityCause()); } LOGGER.info(log.toString()); @@ -114,9 +112,21 @@ public QueryServer(int port, NettyConfig nettyConfig, TlsConfig tlsConfig, Chann public void start() { try { ServerBootstrap serverBootstrap = new ServerBootstrap(); - _channel = serverBootstrap.group(_bossGroup, _workerGroup).channel(_channelClass) + + PooledByteBufAllocator bufAllocator = PooledByteBufAllocator.DEFAULT; + PooledByteBufAllocatorMetric metric = bufAllocator.metric(); + ServerMetrics metrics = ServerMetrics.get(); + metrics.setOrUpdateGlobalGauge(ServerGauge.NETTY_POOLED_USED_DIRECT_MEMORY, metric::usedDirectMemory); + metrics.setOrUpdateGlobalGauge(ServerGauge.NETTY_POOLED_USED_HEAP_MEMORY, metric::usedHeapMemory); + metrics.setOrUpdateGlobalGauge(ServerGauge.NETTY_POOLED_ARENAS_DIRECT, metric::numDirectArenas); + metrics.setOrUpdateGlobalGauge(ServerGauge.NETTY_POOLED_ARENAS_HEAP, metric::numHeapArenas); + metrics.setOrUpdateGlobalGauge(ServerGauge.NETTY_POOLED_CACHE_SIZE_SMALL, metric::smallCacheSize); + metrics.setOrUpdateGlobalGauge(ServerGauge.NETTY_POOLED_CACHE_SIZE_NORMAL, metric::normalCacheSize); + metrics.setOrUpdateGlobalGauge(ServerGauge.NETTY_POOLED_THREADLOCALCACHE, metric::numThreadLocalCaches); + metrics.setOrUpdateGlobalGauge(ServerGauge.NETTY_POOLED_CHUNK_SIZE, metric::chunkSize); + _channel = (ServerSocketChannel) serverBootstrap.group(_bossGroup, _workerGroup).channel(_channelClass) .option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true) - .childHandler(new ChannelInitializer() { + .option(ChannelOption.ALLOCATOR, bufAllocator).childHandler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel ch) { if (_tlsConfig != null) { @@ -148,4 +158,9 @@ public void shutDown() { _bossGroup.shutdownGracefully(0, 0, TimeUnit.SECONDS); } } + + @VisibleForTesting + ServerSocketChannel getChannel() { + return _channel; + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/transport/ServerChannels.java b/pinot-core/src/main/java/org/apache/pinot/core/transport/ServerChannels.java index f9ed68b08f97..c9fe068ed44b 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/transport/ServerChannels.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/transport/ServerChannels.java @@ -19,6 +19,8 @@ package org.apache.pinot.core.transport; import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.PooledByteBufAllocator; +import io.netty.buffer.PooledByteBufAllocatorMetric; import io.netty.buffer.Unpooled; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; @@ -142,7 +144,7 @@ public void shutDown() { } @ThreadSafe - private class ServerChannel { + class ServerChannel { final ServerRoutingInstance _serverRoutingInstance; final Bootstrap _bootstrap; // lock to protect channel as requests must be written into channel sequentially @@ -151,7 +153,19 @@ private class ServerChannel { ServerChannel(ServerRoutingInstance serverRoutingInstance) { _serverRoutingInstance = serverRoutingInstance; + PooledByteBufAllocator bufAllocator = PooledByteBufAllocator.DEFAULT; + PooledByteBufAllocatorMetric metric = bufAllocator.metric(); + _brokerMetrics.setOrUpdateGlobalGauge(BrokerGauge.NETTY_POOLED_USED_DIRECT_MEMORY, metric::usedDirectMemory); + _brokerMetrics.setOrUpdateGlobalGauge(BrokerGauge.NETTY_POOLED_USED_HEAP_MEMORY, metric::usedHeapMemory); + _brokerMetrics.setOrUpdateGlobalGauge(BrokerGauge.NETTY_POOLED_ARENAS_DIRECT, metric::numDirectArenas); + _brokerMetrics.setOrUpdateGlobalGauge(BrokerGauge.NETTY_POOLED_ARENAS_HEAP, metric::numHeapArenas); + _brokerMetrics.setOrUpdateGlobalGauge(BrokerGauge.NETTY_POOLED_CACHE_SIZE_SMALL, metric::smallCacheSize); + _brokerMetrics.setOrUpdateGlobalGauge(BrokerGauge.NETTY_POOLED_CACHE_SIZE_NORMAL, metric::normalCacheSize); + _brokerMetrics.setOrUpdateGlobalGauge(BrokerGauge.NETTY_POOLED_THREADLOCALCACHE, metric::numThreadLocalCaches); + _brokerMetrics.setOrUpdateGlobalGauge(BrokerGauge.NETTY_POOLED_CHUNK_SIZE, metric::chunkSize); + _bootstrap = new Bootstrap().remoteAddress(serverRoutingInstance.getHostname(), serverRoutingInstance.getPort()) + .option(ChannelOption.ALLOCATOR, bufAllocator) .group(_eventLoopGroup).channel(_channelClass).option(ChannelOption.SO_KEEPALIVE, true) .handler(new ChannelInitializer() { @Override @@ -164,14 +178,32 @@ protected void initChannel(SocketChannel ch) { ch.pipeline().addLast(ChannelHandlerFactory.getLengthFieldBasedFrameDecoder()); ch.pipeline().addLast(ChannelHandlerFactory.getLengthFieldPrepender()); + ch.pipeline().addLast( + ChannelHandlerFactory.getDirectOOMHandler(_queryRouter, _serverRoutingInstance, _serverToChannelMap) + ); // NOTE: data table de-serialization happens inside this handler // Revisit if this becomes a bottleneck - ch.pipeline().addLast( - ChannelHandlerFactory.getDataTableHandler(_queryRouter, _serverRoutingInstance, _brokerMetrics)); + ch.pipeline().addLast(ChannelHandlerFactory + .getDataTableHandler(_queryRouter, _serverRoutingInstance, _brokerMetrics)); } }); } + void closeChannel() { + if (_channel != null) { + _channel.close(); + } + } + + void setSilentShutdown() { + if (_channel != null) { + DirectOOMHandler directOOMHandler = _channel.pipeline().get(DirectOOMHandler.class); + if (directOOMHandler != null) { + directOOMHandler.setSilentShutDown(); + } + } + } + void sendRequest(String rawTableName, AsyncQueryResponse asyncQueryResponse, ServerRoutingInstance serverRoutingInstance, byte[] requestBytes, long timeoutMs) throws InterruptedException, TimeoutException { diff --git a/pinot-core/src/main/java/org/apache/pinot/core/transport/ServerInstance.java b/pinot-core/src/main/java/org/apache/pinot/core/transport/ServerInstance.java index 8809e4a015d2..61a0b73d8a96 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/transport/ServerInstance.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/transport/ServerInstance.java @@ -20,7 +20,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.helix.model.InstanceConfig; import org.apache.pinot.common.utils.config.InstanceUtils; import org.apache.pinot.spi.config.table.TableType; @@ -28,7 +28,7 @@ import org.apache.pinot.spi.utils.CommonConstants.Helix; -public class ServerInstance { +public final class ServerInstance { public enum RoutingType { NETTY, GRPC, NETTY_TLS diff --git a/pinot-core/src/main/java/org/apache/pinot/core/transport/ServerRoutingInstance.java b/pinot-core/src/main/java/org/apache/pinot/core/transport/ServerRoutingInstance.java index 9b4f9926fb5b..faa2cd91a341 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/transport/ServerRoutingInstance.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/transport/ServerRoutingInstance.java @@ -34,7 +34,7 @@ * might be treated as two different routing target instances based on the types of table it serves. */ @ThreadSafe -public class ServerRoutingInstance { +public final class ServerRoutingInstance { private static final String SHORT_OFFLINE_SUFFIX = "_O"; private static final String SHORT_REALTIME_SUFFIX = "_R"; private static final Map SHORT_HOSTNAME_MAP = new ConcurrentHashMap<>(); diff --git a/pinot-core/src/main/java/org/apache/pinot/core/transport/grpc/GrpcQueryServer.java b/pinot-core/src/main/java/org/apache/pinot/core/transport/grpc/GrpcQueryServer.java index f92a5e23f07a..daae6d74cca0 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/transport/grpc/GrpcQueryServer.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/transport/grpc/GrpcQueryServer.java @@ -18,8 +18,11 @@ */ package org.apache.pinot.core.transport.grpc; +import io.grpc.Attributes; +import io.grpc.Grpc; import io.grpc.Server; import io.grpc.ServerBuilder; +import io.grpc.ServerTransportFilter; import io.grpc.Status; import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts; import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder; @@ -29,20 +32,27 @@ import io.grpc.netty.shaded.io.netty.handler.ssl.SslProvider; import io.grpc.stub.StreamObserver; import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import nl.altindag.ssl.SSLFactory; import org.apache.pinot.common.config.GrpcConfig; import org.apache.pinot.common.config.TlsConfig; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.metrics.ServerMeter; import org.apache.pinot.common.metrics.ServerMetrics; +import org.apache.pinot.common.metrics.ServerTimer; import org.apache.pinot.common.proto.PinotQueryServerGrpc; import org.apache.pinot.common.proto.Server.ServerRequest; import org.apache.pinot.common.proto.Server.ServerResponse; -import org.apache.pinot.common.utils.TlsUtils; +import org.apache.pinot.common.utils.tls.PinotInsecureMode; +import org.apache.pinot.common.utils.tls.RenewableTlsUtils; import org.apache.pinot.core.operator.blocks.InstanceResponseBlock; import org.apache.pinot.core.operator.streaming.StreamingResponseUtils; import org.apache.pinot.core.query.executor.QueryExecutor; +import org.apache.pinot.core.query.logger.ServerQueryLogger; import org.apache.pinot.core.query.request.ServerQueryRequest; import org.apache.pinot.core.query.scheduler.resources.ResourceManager; import org.apache.pinot.server.access.AccessControl; @@ -54,27 +64,55 @@ // TODO: Plug in QueryScheduler public class GrpcQueryServer extends PinotQueryServerGrpc.PinotQueryServerImplBase { private static final Logger LOGGER = LoggerFactory.getLogger(GrpcQueryServer.class); + // the key is the hashCode of the TlsConfig, the value is the SslContext + // We don't use TlsConfig as the map key because the TlsConfig is mutable, which means the hashCode can change. If the + // hashCode changes and the map is resized, the SslContext of the old hashCode will be lost. + private static final Map SERVER_SSL_CONTEXTS_CACHE = new ConcurrentHashMap<>(); private final QueryExecutor _queryExecutor; private final ServerMetrics _serverMetrics; private final Server _server; - private final ExecutorService _executorService = - Executors.newFixedThreadPool(ResourceManager.DEFAULT_QUERY_WORKER_THREADS); + private final ExecutorService _executorService; private final AccessControl _accessControl; + private final ServerQueryLogger _queryLogger = ServerQueryLogger.getInstance(); + + // Filter to keep track of gRPC connections. + private class GrpcQueryTransportFilter extends ServerTransportFilter { + @Override + public Attributes transportReady(Attributes transportAttrs) { + LOGGER.info("gRPC transportReady: REMOTE_ADDR {}", + transportAttrs != null ? transportAttrs.get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR) : "null"); + _serverMetrics.addMeteredGlobalValue(ServerMeter.GRPC_TRANSPORT_READY, 1); + return super.transportReady(transportAttrs); + } + + @Override + public void transportTerminated(Attributes transportAttrs) { + // transportTerminated can be called without transportReady before it, e.g. handshake fails + // So, don't emit metrics if transportAttrs is null + if (transportAttrs != null) { + LOGGER.info("gRPC transportTerminated: REMOTE_ADDR {}", transportAttrs.get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR)); + _serverMetrics.addMeteredGlobalValue(ServerMeter.GRPC_TRANSPORT_TERMINATED, 1); + } + } + } public GrpcQueryServer(int port, GrpcConfig config, TlsConfig tlsConfig, QueryExecutor queryExecutor, ServerMetrics serverMetrics, AccessControl accessControl) { + _executorService = Executors.newFixedThreadPool(config.isQueryWorkerThreadsSet() ? config.getQueryWorkerThreads() + : ResourceManager.DEFAULT_QUERY_WORKER_THREADS); _queryExecutor = queryExecutor; _serverMetrics = serverMetrics; if (tlsConfig != null) { try { _server = NettyServerBuilder.forPort(port).sslContext(buildGRpcSslContext(tlsConfig)) - .maxInboundMessageSize(config.getMaxInboundMessageSizeBytes()).addService(this).build(); + .maxInboundMessageSize(config.getMaxInboundMessageSizeBytes()).addService(this) + .addTransportFilter(new GrpcQueryTransportFilter()).build(); } catch (Exception e) { throw new RuntimeException("Failed to start secure grpcQueryServer", e); } } else { - _server = ServerBuilder.forPort(port).addService(this).build(); + _server = ServerBuilder.forPort(port).addService(this).addTransportFilter(new GrpcQueryTransportFilter()).build(); } _accessControl = accessControl; LOGGER.info("Initialized GrpcQueryServer on port: {} with numWorkerThreads: {}", port, @@ -82,20 +120,28 @@ public GrpcQueryServer(int port, GrpcConfig config, TlsConfig tlsConfig, QueryEx } private SslContext buildGRpcSslContext(TlsConfig tlsConfig) - throws Exception { + throws IllegalArgumentException { LOGGER.info("Building gRPC SSL context"); if (tlsConfig.getKeyStorePath() == null) { throw new IllegalArgumentException("Must provide key store path for secured gRpc server"); } - SslContextBuilder sslContextBuilder = SslContextBuilder.forServer(TlsUtils.createKeyManagerFactory(tlsConfig)) - .sslProvider(SslProvider.valueOf(tlsConfig.getSslProvider())); - if (tlsConfig.getTrustStorePath() != null) { - sslContextBuilder.trustManager(TlsUtils.createTrustManagerFactory(tlsConfig)); - } - if (tlsConfig.isClientAuthEnabled()) { - sslContextBuilder.clientAuth(ClientAuth.REQUIRE); - } - return GrpcSslContexts.configure(sslContextBuilder).build(); + SslContext sslContext = SERVER_SSL_CONTEXTS_CACHE.computeIfAbsent(tlsConfig.hashCode(), tlsConfigHashCode -> { + try { + SSLFactory sslFactory = + RenewableTlsUtils.createSSLFactoryAndEnableAutoRenewalWhenUsingFileStores( + tlsConfig, PinotInsecureMode::isPinotInInsecureMode); + SslContextBuilder sslContextBuilder = SslContextBuilder.forServer(sslFactory.getKeyManagerFactory().get()) + .sslProvider(SslProvider.valueOf(tlsConfig.getSslProvider())); + sslFactory.getTrustManagerFactory().ifPresent(sslContextBuilder::trustManager); + if (tlsConfig.isClientAuthEnabled()) { + sslContextBuilder.clientAuth(ClientAuth.REQUIRE); + } + return GrpcSslContexts.configure(sslContextBuilder).build(); + } catch (Exception e) { + throw new RuntimeException("Failed to build gRPC SSL context", e); + } + }); + return sslContext; } public void start() { @@ -118,6 +164,10 @@ public void shutdown() { @Override public void submit(ServerRequest request, StreamObserver responseObserver) { + long startTime = System.nanoTime(); + _serverMetrics.addMeteredGlobalValue(ServerMeter.GRPC_QUERIES, 1); + _serverMetrics.addMeteredGlobalValue(ServerMeter.GRPC_BYTES_RECEIVED, request.getSerializedSize()); + // Deserialize the request ServerQueryRequest queryRequest; try { @@ -141,12 +191,16 @@ public void submit(ServerRequest request, StreamObserver respons _serverMetrics.addMeteredGlobalValue(ServerMeter.NO_TABLE_ACCESS, 1); responseObserver.onError( Status.NOT_FOUND.withDescription(exceptionMsg).withCause(unsupportedOperationException).asException()); + return; } // Process the query InstanceResponseBlock instanceResponse; try { - instanceResponse = _queryExecutor.execute(queryRequest, _executorService, responseObserver); + LOGGER.info("Executing gRPC query request {}: {} received from broker: {}", queryRequest.getRequestId(), + queryRequest.getQueryContext(), queryRequest.getBrokerId()); + instanceResponse = _queryExecutor.execute(queryRequest, _executorService, + new GrpcResultsBlockStreamer(responseObserver, _serverMetrics)); } catch (Exception e) { LOGGER.error("Caught exception while processing request {}: {} from broker: {}", queryRequest.getRequestId(), queryRequest.getQueryContext(), queryRequest.getBrokerId(), e); @@ -168,6 +222,14 @@ public void submit(ServerRequest request, StreamObserver respons return; } responseObserver.onNext(serverResponse); + _serverMetrics.addMeteredGlobalValue(ServerMeter.GRPC_BYTES_SENT, serverResponse.getSerializedSize()); responseObserver.onCompleted(); + _serverMetrics.addTimedTableValue(queryRequest.getTableNameWithType(), ServerTimer.GRPC_QUERY_EXECUTION_MS, + System.nanoTime() - startTime, TimeUnit.NANOSECONDS); + + // Log the query + if (_queryLogger != null) { + _queryLogger.logQuery(queryRequest, instanceResponse, "GrpcQueryServer"); + } } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/transport/grpc/GrpcResultsBlockStreamer.java b/pinot-core/src/main/java/org/apache/pinot/core/transport/grpc/GrpcResultsBlockStreamer.java new file mode 100644 index 000000000000..8a13a3b798d2 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/transport/grpc/GrpcResultsBlockStreamer.java @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.transport.grpc; + +import com.google.common.base.Preconditions; +import io.grpc.stub.StreamObserver; +import java.io.IOException; +import java.util.Collection; +import org.apache.pinot.common.datatable.DataTable; +import org.apache.pinot.common.metrics.ServerMeter; +import org.apache.pinot.common.metrics.ServerMetrics; +import org.apache.pinot.common.proto.Server; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.operator.blocks.results.BaseResultsBlock; +import org.apache.pinot.core.operator.streaming.StreamingResponseUtils; +import org.apache.pinot.core.query.executor.ResultsBlockStreamer; + + +public class GrpcResultsBlockStreamer implements ResultsBlockStreamer { + private final StreamObserver _streamObserver; + private final ServerMetrics _serverMetrics; + + public GrpcResultsBlockStreamer(StreamObserver streamObserver, ServerMetrics serverMetrics) { + _streamObserver = streamObserver; + _serverMetrics = serverMetrics; + } + + @Override + public void send(BaseResultsBlock block) + throws IOException { + DataSchema dataSchema = block.getDataSchema(); + Collection rows = block.getRows(); + Preconditions.checkState(dataSchema != null && rows != null, "Malformed data block"); + DataTable dataTable = block.getDataTable(); + Server.ServerResponse response = StreamingResponseUtils.getDataResponse(dataTable); + _streamObserver.onNext(response); + _serverMetrics.addMeteredGlobalValue(ServerMeter.GRPC_BYTES_SENT, response.getSerializedSize()); + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/transport/server/routing/stats/ServerRoutingStatsManager.java b/pinot-core/src/main/java/org/apache/pinot/core/transport/server/routing/stats/ServerRoutingStatsManager.java index 87c80566c128..a21906d23b82 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/transport/server/routing/stats/ServerRoutingStatsManager.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/transport/server/routing/stats/ServerRoutingStatsManager.java @@ -30,6 +30,8 @@ import java.util.concurrent.ThreadPoolExecutor; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; +import org.apache.pinot.common.metrics.BrokerGauge; +import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.spi.env.PinotConfiguration; import org.apache.pinot.spi.utils.CommonConstants.Broker.AdaptiveServerSelector; import org.slf4j.Logger; @@ -46,6 +48,7 @@ public class ServerRoutingStatsManager { private static final Logger LOGGER = LoggerFactory.getLogger(ServerRoutingStatsManager.class); private final PinotConfiguration _config; + private final BrokerMetrics _brokerMetrics; private volatile boolean _isEnabled; private ConcurrentHashMap _serverQueryStatsMap; @@ -61,8 +64,9 @@ public class ServerRoutingStatsManager { private double _avgInitializationVal; private int _hybridScoreExponent; - public ServerRoutingStatsManager(PinotConfiguration pinotConfig) { + public ServerRoutingStatsManager(PinotConfiguration pinotConfig, BrokerMetrics brokerMetrics) { _config = pinotConfig; + _brokerMetrics = brokerMetrics; } public void init() { @@ -131,16 +135,16 @@ public long getCompletedTaskCount() { } /** - * Called when a query is submitted to a server. Updates stats corresponding to query submission. + * Called just before submitting a query to a server. Updates stats corresponding to query submission. */ - public void recordStatsAfterQuerySubmission(long requestId, String serverInstanceId) { + public void recordStatsForQuerySubmission(long requestId, String serverInstanceId) { if (!_isEnabled) { return; } - // TODO: Track Executor qSize and alert if it crosses a threshold. _executorService.execute(() -> { try { + recordQueueSizeMetrics(); updateStatsAfterQuerySubmission(serverInstanceId); } catch (Exception e) { LOGGER.error("Exception caught while updating stats. requestId={}, exception={}", requestId, e); @@ -377,4 +381,9 @@ public Double fetchHybridScoreForServer(String server) { stats.getServerReadLock().unlock(); } } + + private void recordQueueSizeMetrics() { + int queueSize = getQueueSize(); + _brokerMetrics.setValueOfGlobalGauge(BrokerGauge.ROUTING_STATS_MANAGER_QUEUE_SIZE, queueSize); + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/util/DataBlockExtractUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/util/DataBlockExtractUtils.java new file mode 100644 index 000000000000..445881527788 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/util/DataBlockExtractUtils.java @@ -0,0 +1,909 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.util; + +import com.google.common.base.Preconditions; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.annotation.Nullable; +import org.apache.pinot.common.datablock.DataBlock; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.core.common.ObjectSerDeUtils; +import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.utils.CommonConstants.NullValuePlaceHolder; +import org.roaringbitmap.PeekableIntIterator; +import org.roaringbitmap.RoaringBitmap; + + +/** + * Utils to extract values from {@link DataBlock}. + */ +public final class DataBlockExtractUtils { + private DataBlockExtractUtils() { + } + + public static List extractRows(DataBlock dataBlock) { + DataSchema dataSchema = dataBlock.getDataSchema(); + ColumnDataType[] storedTypes = dataSchema.getStoredColumnDataTypes(); + int numColumns = storedTypes.length; + RoaringBitmap[] nullBitmaps = new RoaringBitmap[numColumns]; + for (int colId = 0; colId < numColumns; colId++) { + nullBitmaps[colId] = dataBlock.getNullRowIds(colId); + } + int numRows = dataBlock.getNumberOfRows(); + List rows = new ArrayList<>(numRows); + for (int rowId = 0; rowId < numRows; rowId++) { + Object[] row = new Object[numColumns]; + for (int colId = 0; colId < numColumns; colId++) { + RoaringBitmap nullBitmap = nullBitmaps[colId]; + if (nullBitmap == null || !nullBitmap.contains(rowId)) { + row[colId] = extractValue(dataBlock, storedTypes[colId], rowId, colId); + } + } + rows.add(row); + } + return rows; + } + + private static Object extractValue(DataBlock dataBlock, ColumnDataType storedType, int rowId, int colId) { + switch (storedType) { + // Single-value column + case INT: + return dataBlock.getInt(rowId, colId); + case LONG: + return dataBlock.getLong(rowId, colId); + case FLOAT: + return dataBlock.getFloat(rowId, colId); + case DOUBLE: + return dataBlock.getDouble(rowId, colId); + case BIG_DECIMAL: + return dataBlock.getBigDecimal(rowId, colId); + case STRING: + return dataBlock.getString(rowId, colId); + case BYTES: + return dataBlock.getBytes(rowId, colId); + + // Multi-value column + case INT_ARRAY: + return dataBlock.getIntArray(rowId, colId); + case LONG_ARRAY: + return dataBlock.getLongArray(rowId, colId); + case FLOAT_ARRAY: + return dataBlock.getFloatArray(rowId, colId); + case DOUBLE_ARRAY: + return dataBlock.getDoubleArray(rowId, colId); + case STRING_ARRAY: + return dataBlock.getStringArray(rowId, colId); + + // Special intermediate result for aggregation function + case OBJECT: + return ObjectSerDeUtils.deserialize(dataBlock.getCustomObject(rowId, colId)); + + default: + throw new IllegalStateException(String.format("Unsupported stored type: %s for column: %s", storedType, + dataBlock.getDataSchema().getColumnName(colId))); + } + } + + public static Object[][] extractKeys(DataBlock dataBlock, int[] keyIds) { + DataSchema dataSchema = dataBlock.getDataSchema(); + int numKeys = keyIds.length; + ColumnDataType[] storedTypes = new ColumnDataType[numKeys]; + RoaringBitmap[] nullBitmaps = new RoaringBitmap[numKeys]; + for (int colId = 0; colId < numKeys; colId++) { + storedTypes[colId] = dataSchema.getColumnDataType(keyIds[colId]).getStoredType(); + nullBitmaps[colId] = dataBlock.getNullRowIds(keyIds[colId]); + } + int numRows = dataBlock.getNumberOfRows(); + Object[][] keys = new Object[numRows][]; + for (int rowId = 0; rowId < numRows; rowId++) { + Object[] values = new Object[numKeys]; + for (int colId = 0; colId < numKeys; colId++) { + RoaringBitmap nullBitmap = nullBitmaps[colId]; + if (nullBitmap == null || !nullBitmap.contains(rowId)) { + values[colId] = extractValue(dataBlock, storedTypes[colId], rowId, keyIds[colId]); + } + } + keys[rowId] = values; + } + return keys; + } + + public static Object[][] extractKeys(DataBlock dataBlock, int[] keyIds, int numMatchedRows, + RoaringBitmap matchedBitmap) { + DataSchema dataSchema = dataBlock.getDataSchema(); + int numKeys = keyIds.length; + ColumnDataType[] storedTypes = new ColumnDataType[numKeys]; + RoaringBitmap[] nullBitmaps = new RoaringBitmap[numKeys]; + for (int colId = 0; colId < numKeys; colId++) { + storedTypes[colId] = dataSchema.getColumnDataType(keyIds[colId]).getStoredType(); + nullBitmaps[colId] = dataBlock.getNullRowIds(keyIds[colId]); + } + Object[][] keys = new Object[numMatchedRows][]; + PeekableIntIterator iterator = matchedBitmap.getIntIterator(); + for (int matchedRowId = 0; matchedRowId < numMatchedRows; matchedRowId++) { + int rowId = iterator.next(); + Object[] values = new Object[numKeys]; + for (int colId = 0; colId < numKeys; colId++) { + RoaringBitmap nullBitmap = nullBitmaps[colId]; + if (nullBitmap == null || !nullBitmap.contains(rowId)) { + values[colId] = extractValue(dataBlock, storedTypes[colId], rowId, keyIds[colId]); + } + } + keys[matchedRowId] = values; + } + return keys; + } + + public static Object[] extractColumn(DataBlock dataBlock, int colId) { + DataSchema dataSchema = dataBlock.getDataSchema(); + ColumnDataType storedType = dataSchema.getColumnDataType(colId).getStoredType(); + RoaringBitmap nullBitmap = dataBlock.getNullRowIds(colId); + int numRows = dataBlock.getNumberOfRows(); + Object[] values = new Object[numRows]; + if (nullBitmap == null) { + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = extractValue(dataBlock, storedType, rowId, colId); + } + } else { + for (int rowId = 0; rowId < numRows; rowId++) { + if (!nullBitmap.contains(rowId)) { + values[rowId] = extractValue(dataBlock, storedType, rowId, colId); + } + } + } + return values; + } + + public static Object[] extractColumn(DataBlock dataBlock, int colId, int numMatchedRows, + RoaringBitmap matchedBitmap) { + DataSchema dataSchema = dataBlock.getDataSchema(); + ColumnDataType storedType = dataSchema.getColumnDataType(colId).getStoredType(); + RoaringBitmap nullBitmap = dataBlock.getNullRowIds(colId); + Object[] values = new Object[numMatchedRows]; + PeekableIntIterator iterator = matchedBitmap.getIntIterator(); + if (nullBitmap == null) { + for (int matchedRowId = 0; matchedRowId < numMatchedRows; matchedRowId++) { + int rowId = iterator.next(); + values[matchedRowId] = extractValue(dataBlock, storedType, rowId, colId); + } + } else { + for (int matchedRowId = 0; matchedRowId < numMatchedRows; matchedRowId++) { + int rowId = iterator.next(); + if (!nullBitmap.contains(rowId)) { + values[matchedRowId] = extractValue(dataBlock, storedType, rowId, colId); + } + } + } + return values; + } + + public static int[] extractIntColumn(DataType storedType, DataBlock dataBlock, int colId, + @Nullable RoaringBitmap nullBitmap) { + int numRows = dataBlock.getNumberOfRows(); + int[] values = new int[numRows]; + if (numRows == 0 || storedType == DataType.UNKNOWN) { + return values; + } + if (nullBitmap == null) { + switch (storedType) { + case INT: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = dataBlock.getInt(rowId, colId); + } + break; + case LONG: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = (int) dataBlock.getLong(rowId, colId); + } + break; + case FLOAT: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = (int) dataBlock.getFloat(rowId, colId); + } + break; + case DOUBLE: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = (int) dataBlock.getDouble(rowId, colId); + } + break; + case BIG_DECIMAL: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = dataBlock.getBigDecimal(rowId, colId).intValue(); + } + break; + default: + throw new IllegalStateException(String.format("Cannot extract int values for column: %s with stored type: %s", + dataBlock.getDataSchema().getColumnName(colId), storedType)); + } + } else { + switch (storedType) { + case INT: + for (int rowId = 0; rowId < numRows; rowId++) { + if (!nullBitmap.contains(rowId)) { + values[rowId] = dataBlock.getInt(rowId, colId); + } + } + break; + case LONG: + for (int rowId = 0; rowId < numRows; rowId++) { + if (!nullBitmap.contains(rowId)) { + values[rowId] = (int) dataBlock.getLong(rowId, colId); + } + } + break; + case FLOAT: + for (int rowId = 0; rowId < numRows; rowId++) { + if (!nullBitmap.contains(rowId)) { + values[rowId] = (int) dataBlock.getFloat(rowId, colId); + } + } + break; + case DOUBLE: + for (int rowId = 0; rowId < numRows; rowId++) { + if (!nullBitmap.contains(rowId)) { + values[rowId] = (int) dataBlock.getDouble(rowId, colId); + } + } + break; + case BIG_DECIMAL: + for (int rowId = 0; rowId < numRows; rowId++) { + if (!nullBitmap.contains(rowId)) { + values[rowId] = dataBlock.getBigDecimal(rowId, colId).intValue(); + } + } + break; + default: + throw new IllegalStateException(String.format("Cannot extract int values for column: %s with stored type: %s", + dataBlock.getDataSchema().getColumnName(colId), storedType)); + } + } + return values; + } + + public static long[] extractLongColumn(DataType storedType, DataBlock dataBlock, int colId, + @Nullable RoaringBitmap nullBitmap) { + int numRows = dataBlock.getNumberOfRows(); + long[] values = new long[numRows]; + if (numRows == 0 || storedType == DataType.UNKNOWN) { + return values; + } + if (nullBitmap == null) { + switch (storedType) { + case INT: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = dataBlock.getInt(rowId, colId); + } + break; + case LONG: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = dataBlock.getLong(rowId, colId); + } + break; + case FLOAT: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = (long) dataBlock.getFloat(rowId, colId); + } + break; + case DOUBLE: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = (long) dataBlock.getDouble(rowId, colId); + } + break; + case BIG_DECIMAL: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = dataBlock.getBigDecimal(rowId, colId).longValue(); + } + break; + default: + throw new IllegalStateException( + String.format("Cannot extract long values for column: %s with stored type: %s", + dataBlock.getDataSchema().getColumnName(colId), storedType)); + } + } else { + switch (storedType) { + case INT: + for (int rowId = 0; rowId < numRows; rowId++) { + if (!nullBitmap.contains(rowId)) { + values[rowId] = dataBlock.getInt(rowId, colId); + } + } + break; + case LONG: + for (int rowId = 0; rowId < numRows; rowId++) { + if (!nullBitmap.contains(rowId)) { + values[rowId] = dataBlock.getLong(rowId, colId); + } + } + break; + case FLOAT: + for (int rowId = 0; rowId < numRows; rowId++) { + if (!nullBitmap.contains(rowId)) { + values[rowId] = (long) dataBlock.getFloat(rowId, colId); + } + } + break; + case DOUBLE: + for (int rowId = 0; rowId < numRows; rowId++) { + if (!nullBitmap.contains(rowId)) { + values[rowId] = (long) dataBlock.getDouble(rowId, colId); + } + } + break; + case BIG_DECIMAL: + for (int rowId = 0; rowId < numRows; rowId++) { + if (!nullBitmap.contains(rowId)) { + values[rowId] = dataBlock.getBigDecimal(rowId, colId).longValue(); + } + } + break; + default: + throw new IllegalStateException( + String.format("Cannot extract long values for column: %s with stored type: %s", + dataBlock.getDataSchema().getColumnName(colId), storedType)); + } + } + return values; + } + + public static float[] extractFloatColumn(DataType storedType, DataBlock dataBlock, int colId, + @Nullable RoaringBitmap nullBitmap) { + int numRows = dataBlock.getNumberOfRows(); + float[] values = new float[numRows]; + if (numRows == 0 || storedType == DataType.UNKNOWN) { + return values; + } + if (nullBitmap == null) { + switch (storedType) { + case INT: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = dataBlock.getInt(rowId, colId); + } + break; + case LONG: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = dataBlock.getLong(rowId, colId); + } + break; + case FLOAT: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = dataBlock.getFloat(rowId, colId); + } + break; + case DOUBLE: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = (float) dataBlock.getDouble(rowId, colId); + } + break; + case BIG_DECIMAL: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = dataBlock.getBigDecimal(rowId, colId).floatValue(); + } + break; + default: + throw new IllegalStateException( + String.format("Cannot extract float values for column: %s with stored type: %s", + dataBlock.getDataSchema().getColumnName(colId), storedType)); + } + } else { + switch (storedType) { + case INT: + for (int rowId = 0; rowId < numRows; rowId++) { + if (!nullBitmap.contains(rowId)) { + values[rowId] = dataBlock.getInt(rowId, colId); + } + } + break; + case LONG: + for (int rowId = 0; rowId < numRows; rowId++) { + if (!nullBitmap.contains(rowId)) { + values[rowId] = dataBlock.getLong(rowId, colId); + } + } + break; + case FLOAT: + for (int rowId = 0; rowId < numRows; rowId++) { + if (!nullBitmap.contains(rowId)) { + values[rowId] = dataBlock.getFloat(rowId, colId); + } + } + break; + case DOUBLE: + for (int rowId = 0; rowId < numRows; rowId++) { + if (!nullBitmap.contains(rowId)) { + values[rowId] = (float) dataBlock.getDouble(rowId, colId); + } + } + break; + case BIG_DECIMAL: + for (int rowId = 0; rowId < numRows; rowId++) { + if (!nullBitmap.contains(rowId)) { + values[rowId] = dataBlock.getBigDecimal(rowId, colId).floatValue(); + } + } + break; + default: + throw new IllegalStateException( + String.format("Cannot extract float values for column: %s with stored type: %s", + dataBlock.getDataSchema().getColumnName(colId), storedType)); + } + } + return values; + } + + public static double[] extractDoubleColumn(DataType storedType, DataBlock dataBlock, int colId, + @Nullable RoaringBitmap nullBitmap) { + int numRows = dataBlock.getNumberOfRows(); + double[] values = new double[numRows]; + if (numRows == 0 || storedType == DataType.UNKNOWN) { + return values; + } + if (nullBitmap == null) { + switch (storedType) { + case INT: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = dataBlock.getInt(rowId, colId); + } + break; + case LONG: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = dataBlock.getLong(rowId, colId); + } + break; + case FLOAT: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = dataBlock.getFloat(rowId, colId); + } + break; + case DOUBLE: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = dataBlock.getDouble(rowId, colId); + } + break; + case BIG_DECIMAL: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = dataBlock.getBigDecimal(rowId, colId).doubleValue(); + } + break; + default: + throw new IllegalStateException( + String.format("Cannot extract double values for column: %s with stored type: %s", + dataBlock.getDataSchema().getColumnName(colId), storedType)); + } + } else { + switch (storedType) { + case INT: + for (int rowId = 0; rowId < numRows; rowId++) { + if (!nullBitmap.contains(rowId)) { + values[rowId] = dataBlock.getInt(rowId, colId); + } + } + break; + case LONG: + for (int rowId = 0; rowId < numRows; rowId++) { + if (!nullBitmap.contains(rowId)) { + values[rowId] = dataBlock.getLong(rowId, colId); + } + } + break; + case FLOAT: + for (int rowId = 0; rowId < numRows; rowId++) { + if (!nullBitmap.contains(rowId)) { + values[rowId] = dataBlock.getFloat(rowId, colId); + } + } + break; + case DOUBLE: + for (int rowId = 0; rowId < numRows; rowId++) { + if (!nullBitmap.contains(rowId)) { + values[rowId] = dataBlock.getDouble(rowId, colId); + } + } + break; + case BIG_DECIMAL: + for (int rowId = 0; rowId < numRows; rowId++) { + if (!nullBitmap.contains(rowId)) { + values[rowId] = dataBlock.getBigDecimal(rowId, colId).doubleValue(); + } + } + break; + default: + throw new IllegalStateException( + String.format("Cannot extract double values for column: %s with stored type: %s", + dataBlock.getDataSchema().getColumnName(colId), storedType)); + } + } + return values; + } + + public static BigDecimal[] extractBigDecimalColumn(DataType storedType, DataBlock dataBlock, int colId, + @Nullable RoaringBitmap nullBitmap) { + int numRows = dataBlock.getNumberOfRows(); + BigDecimal[] values = new BigDecimal[numRows]; + if (numRows == 0) { + return values; + } + if (storedType == DataType.UNKNOWN) { + Arrays.fill(values, NullValuePlaceHolder.BIG_DECIMAL); + return values; + } + if (nullBitmap == null) { + switch (storedType) { + case INT: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = BigDecimal.valueOf(dataBlock.getInt(rowId, colId)); + } + break; + case LONG: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = BigDecimal.valueOf(dataBlock.getLong(rowId, colId)); + } + break; + case FLOAT: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = BigDecimal.valueOf(dataBlock.getFloat(rowId, colId)); + } + break; + case DOUBLE: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = BigDecimal.valueOf(dataBlock.getDouble(rowId, colId)); + } + break; + case BIG_DECIMAL: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = dataBlock.getBigDecimal(rowId, colId); + } + break; + default: + throw new IllegalStateException( + String.format("Cannot extract BigDecimal values for column: %s with stored type: %s", + dataBlock.getDataSchema().getColumnName(colId), storedType)); + } + } else { + switch (storedType) { + case INT: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = !nullBitmap.contains(rowId) ? BigDecimal.valueOf(dataBlock.getInt(rowId, colId)) + : NullValuePlaceHolder.BIG_DECIMAL; + } + break; + case LONG: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = !nullBitmap.contains(rowId) ? BigDecimal.valueOf(dataBlock.getLong(rowId, colId)) + : NullValuePlaceHolder.BIG_DECIMAL; + } + break; + case FLOAT: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = + !nullBitmap.contains(rowId) ? new BigDecimal(Float.toString(dataBlock.getFloat(rowId, colId))) + : NullValuePlaceHolder.BIG_DECIMAL; + } + break; + case DOUBLE: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = + !nullBitmap.contains(rowId) ? new BigDecimal(Double.toString(dataBlock.getDouble(rowId, colId))) + : NullValuePlaceHolder.BIG_DECIMAL; + } + break; + case BIG_DECIMAL: + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = + !nullBitmap.contains(rowId) ? dataBlock.getBigDecimal(rowId, colId) : NullValuePlaceHolder.BIG_DECIMAL; + } + break; + default: + throw new IllegalStateException( + String.format("Cannot extract BigDecimal values for column: %s with stored type: %s", + dataBlock.getDataSchema().getColumnName(colId), storedType)); + } + } + return values; + } + + public static String[] extractStringColumn(DataType storedType, DataBlock dataBlock, int colId, + @Nullable RoaringBitmap nullBitmap) { + int numRows = dataBlock.getNumberOfRows(); + String[] values = new String[numRows]; + if (numRows == 0) { + return values; + } + if (storedType == DataType.UNKNOWN) { + Arrays.fill(values, NullValuePlaceHolder.STRING); + return values; + } + Preconditions.checkState(storedType == DataType.STRING, + "Cannot extract String values for column: %s with stored type: %s", + dataBlock.getDataSchema().getColumnName(colId), storedType); + if (nullBitmap == null) { + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = dataBlock.getString(rowId, colId); + } + } else { + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = !nullBitmap.contains(rowId) ? dataBlock.getString(rowId, colId) : NullValuePlaceHolder.STRING; + } + } + return values; + } + + public static byte[][] extractBytesColumn(DataType storedType, DataBlock dataBlock, int colId, + @Nullable RoaringBitmap nullBitmap) { + int numRows = dataBlock.getNumberOfRows(); + byte[][] values = new byte[numRows][]; + if (numRows == 0) { + return values; + } + if (storedType == DataType.UNKNOWN) { + Arrays.fill(values, NullValuePlaceHolder.BYTES); + return values; + } + Preconditions.checkState(storedType == DataType.BYTES, + "Cannot extract byte[] values for column: %s with stored type: %s", + dataBlock.getDataSchema().getColumnName(colId), storedType); + if (nullBitmap == null) { + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = dataBlock.getBytes(rowId, colId).getBytes(); + } + } else { + for (int rowId = 0; rowId < numRows; rowId++) { + values[rowId] = + !nullBitmap.contains(rowId) ? dataBlock.getBytes(rowId, colId).getBytes() : NullValuePlaceHolder.BYTES; + } + } + return values; + } + + public static int[] extractIntColumn(DataType storedType, DataBlock dataBlock, int colId, int numMatchedRows, + RoaringBitmap matchedBitmap, @Nullable RoaringBitmap matchedNullBitmap) { + int[] values = new int[numMatchedRows]; + if (numMatchedRows == 0 || storedType == DataType.UNKNOWN) { + return values; + } + PeekableIntIterator iterator = matchedBitmap.getIntIterator(); + for (int matchedRowId = 0; matchedRowId < numMatchedRows; matchedRowId++) { + int rowId = iterator.next(); + if (matchedNullBitmap != null && matchedNullBitmap.contains(matchedRowId)) { + continue; + } + switch (storedType) { + case INT: + values[matchedRowId] = dataBlock.getInt(rowId, colId); + break; + case LONG: + values[matchedRowId] = (int) dataBlock.getLong(rowId, colId); + break; + case FLOAT: + values[matchedRowId] = (int) dataBlock.getFloat(rowId, colId); + break; + case DOUBLE: + values[matchedRowId] = (int) dataBlock.getDouble(rowId, colId); + break; + case BIG_DECIMAL: + values[matchedRowId] = dataBlock.getBigDecimal(rowId, colId).intValue(); + break; + default: + throw new IllegalStateException(String.format("Cannot extract int values for column: %s with stored type: %s", + dataBlock.getDataSchema().getColumnName(colId), storedType)); + } + } + return values; + } + + public static long[] extractLongColumn(DataType storedType, DataBlock dataBlock, int colId, int numMatchedRows, + RoaringBitmap matchedBitmap, @Nullable RoaringBitmap matchedNullBitmap) { + long[] values = new long[numMatchedRows]; + if (numMatchedRows == 0 || storedType == DataType.UNKNOWN) { + return values; + } + PeekableIntIterator iterator = matchedBitmap.getIntIterator(); + for (int matchedRowId = 0; matchedRowId < numMatchedRows; matchedRowId++) { + int rowId = iterator.next(); + if (matchedNullBitmap != null && matchedNullBitmap.contains(matchedRowId)) { + continue; + } + switch (storedType) { + case INT: + values[matchedRowId] = dataBlock.getInt(rowId, colId); + break; + case LONG: + values[matchedRowId] = dataBlock.getLong(rowId, colId); + break; + case FLOAT: + values[matchedRowId] = (long) dataBlock.getFloat(rowId, colId); + break; + case DOUBLE: + values[matchedRowId] = (long) dataBlock.getDouble(rowId, colId); + break; + case BIG_DECIMAL: + values[matchedRowId] = dataBlock.getBigDecimal(rowId, colId).longValue(); + break; + default: + throw new IllegalStateException( + String.format("Cannot extract long values for column: %s with stored type: %s", + dataBlock.getDataSchema().getColumnName(colId), storedType)); + } + } + return values; + } + + public static float[] extractFloatColumn(DataType storedType, DataBlock dataBlock, int colId, int numMatchedRows, + RoaringBitmap matchedBitmap, @Nullable RoaringBitmap matchedNullBitmap) { + float[] values = new float[numMatchedRows]; + if (numMatchedRows == 0 || storedType == DataType.UNKNOWN) { + return values; + } + PeekableIntIterator iterator = matchedBitmap.getIntIterator(); + for (int matchedRowId = 0; matchedRowId < numMatchedRows; matchedRowId++) { + int rowId = iterator.next(); + if (matchedNullBitmap != null && matchedNullBitmap.contains(matchedRowId)) { + continue; + } + switch (storedType) { + case INT: + values[matchedRowId] = dataBlock.getInt(rowId, colId); + break; + case LONG: + values[matchedRowId] = dataBlock.getLong(rowId, colId); + break; + case FLOAT: + values[matchedRowId] = dataBlock.getFloat(rowId, colId); + break; + case DOUBLE: + values[matchedRowId] = (float) dataBlock.getDouble(rowId, colId); + break; + case BIG_DECIMAL: + values[matchedRowId] = dataBlock.getBigDecimal(rowId, colId).floatValue(); + break; + default: + throw new IllegalStateException( + String.format("Cannot extract float values for column: %s with stored type: %s", + dataBlock.getDataSchema().getColumnName(colId), storedType)); + } + } + return values; + } + + public static double[] extractDoubleColumn(DataType storedType, DataBlock dataBlock, int colId, int numMatchedRows, + RoaringBitmap matchedBitmap, @Nullable RoaringBitmap matchedNullBitmap) { + double[] values = new double[numMatchedRows]; + if (numMatchedRows == 0 || storedType == DataType.UNKNOWN) { + return values; + } + PeekableIntIterator iterator = matchedBitmap.getIntIterator(); + for (int matchedRowId = 0; matchedRowId < numMatchedRows; matchedRowId++) { + int rowId = iterator.next(); + if (matchedNullBitmap != null && matchedNullBitmap.contains(matchedRowId)) { + continue; + } + switch (storedType) { + case INT: + values[matchedRowId] = dataBlock.getInt(rowId, colId); + break; + case LONG: + values[matchedRowId] = dataBlock.getLong(rowId, colId); + break; + case FLOAT: + values[matchedRowId] = dataBlock.getFloat(rowId, colId); + break; + case DOUBLE: + values[matchedRowId] = dataBlock.getDouble(rowId, colId); + break; + case BIG_DECIMAL: + values[matchedRowId] = dataBlock.getBigDecimal(rowId, colId).doubleValue(); + break; + default: + throw new IllegalStateException( + String.format("Cannot extract double values for column: %s with stored type: %s", + dataBlock.getDataSchema().getColumnName(colId), storedType)); + } + } + return values; + } + + public static BigDecimal[] extractBigDecimalColumn(DataType storedType, DataBlock dataBlock, int colId, + int numMatchedRows, RoaringBitmap matchedBitmap, @Nullable RoaringBitmap matchedNullBitmap) { + BigDecimal[] values = new BigDecimal[numMatchedRows]; + if (numMatchedRows == 0) { + return values; + } + if (storedType == DataType.UNKNOWN) { + Arrays.fill(values, NullValuePlaceHolder.BIG_DECIMAL); + return values; + } + PeekableIntIterator iterator = matchedBitmap.getIntIterator(); + for (int matchedRowId = 0; matchedRowId < numMatchedRows; matchedRowId++) { + int rowId = iterator.next(); + if (matchedNullBitmap != null && matchedNullBitmap.contains(matchedRowId)) { + values[matchedRowId] = NullValuePlaceHolder.BIG_DECIMAL; + continue; + } + switch (storedType) { + case INT: + values[matchedRowId] = BigDecimal.valueOf(dataBlock.getInt(rowId, colId)); + break; + case LONG: + values[matchedRowId] = BigDecimal.valueOf(dataBlock.getLong(rowId, colId)); + break; + case FLOAT: + values[matchedRowId] = BigDecimal.valueOf(dataBlock.getFloat(rowId, colId)); + break; + case DOUBLE: + values[matchedRowId] = BigDecimal.valueOf(dataBlock.getDouble(rowId, colId)); + break; + case BIG_DECIMAL: + values[matchedRowId] = dataBlock.getBigDecimal(rowId, colId); + break; + default: + throw new IllegalStateException( + String.format("Cannot extract BigDecimal values for column: %s with stored type: %s", + dataBlock.getDataSchema().getColumnName(colId), storedType)); + } + } + return values; + } + + public static String[] extractStringColumn(DataType storedType, DataBlock dataBlock, int colId, int numMatchedRows, + RoaringBitmap matchedBitmap, @Nullable RoaringBitmap matchedNullBitmap) { + String[] values = new String[numMatchedRows]; + if (numMatchedRows == 0) { + return values; + } + if (storedType == DataType.UNKNOWN) { + Arrays.fill(values, NullValuePlaceHolder.STRING); + return values; + } + Preconditions.checkState(storedType == DataType.STRING, + "Cannot extract String values for column: %s with stored type: %s", + dataBlock.getDataSchema().getColumnName(colId), storedType); + PeekableIntIterator iterator = matchedBitmap.getIntIterator(); + for (int matchedRowId = 0; matchedRowId < numMatchedRows; matchedRowId++) { + int rowId = iterator.next(); + boolean isNull = matchedNullBitmap != null && matchedNullBitmap.contains(matchedRowId); + values[matchedRowId] = !isNull ? dataBlock.getString(rowId, colId) : NullValuePlaceHolder.STRING; + } + return values; + } + + public static byte[][] extractBytesColumn(DataType storedType, DataBlock dataBlock, int colId, int numMatchedRows, + RoaringBitmap matchedBitmap, @Nullable RoaringBitmap matchedNullBitmap) { + byte[][] values = new byte[numMatchedRows][]; + if (numMatchedRows == 0) { + return values; + } + if (storedType == DataType.UNKNOWN) { + Arrays.fill(values, NullValuePlaceHolder.BYTES); + return values; + } + Preconditions.checkState(storedType == DataType.BYTES, + "Cannot extract byte[] values for column: %s with stored type: %s", + dataBlock.getDataSchema().getColumnName(colId), storedType); + PeekableIntIterator iterator = matchedBitmap.getIntIterator(); + for (int matchedRowId = 0; matchedRowId < numMatchedRows; matchedRowId++) { + int rowId = iterator.next(); + boolean isNull = matchedNullBitmap != null && matchedNullBitmap.contains(matchedRowId); + values[matchedRowId] = !isNull ? dataBlock.getBytes(rowId, colId).getBytes() : NullValuePlaceHolder.BYTES; + } + return values; + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/util/GapfillUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/util/GapfillUtils.java index bd39d3472069..c353115dcf5b 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/util/GapfillUtils.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/util/GapfillUtils.java @@ -277,9 +277,8 @@ public static PinotQuery stripGapfill(PinotQuery pinotQuery) { return pinotQuery; } - // Carry over the query and debug options from the original query + // Carry over the query options from the original query Map queryOptions = pinotQuery.getQueryOptions(); - Map debugOptions = pinotQuery.getDebugOptions(); while (pinotQuery.getDataSource().getSubquery() != null) { pinotQuery = pinotQuery.getDataSource().getSubquery(); @@ -321,7 +320,6 @@ public static PinotQuery stripGapfill(PinotQuery pinotQuery) { } strippedPinotQuery.setQueryOptions(queryOptions); - strippedPinotQuery.setDebugOptions(debugOptions); return strippedPinotQuery; } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/util/ListenerConfigUtil.java b/pinot-core/src/main/java/org/apache/pinot/core/util/ListenerConfigUtil.java index b5e4eae1663d..9a5969c23864 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/util/ListenerConfigUtil.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/util/ListenerConfigUtil.java @@ -34,16 +34,19 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import nl.altindag.ssl.SSLFactory; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.pinot.common.config.TlsConfig; -import org.apache.pinot.common.utils.TlsUtils; +import org.apache.pinot.common.utils.tls.PinotInsecureMode; +import org.apache.pinot.common.utils.tls.RenewableTlsUtils; +import org.apache.pinot.common.utils.tls.TlsUtils; +import org.apache.pinot.core.transport.HttpServerThreadPoolConfig; import org.apache.pinot.core.transport.ListenerConfig; import org.apache.pinot.spi.env.PinotConfiguration; import org.apache.pinot.spi.utils.CommonConstants; import org.glassfish.grizzly.http.server.HttpServer; import org.glassfish.grizzly.http.server.NetworkListener; -import org.glassfish.grizzly.ssl.SSLContextConfigurator; import org.glassfish.grizzly.ssl.SSLEngineConfigurator; import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory; import org.glassfish.jersey.internal.guava.ThreadFactoryBuilder; @@ -60,6 +63,7 @@ public final class ListenerConfigUtil { private static final String DEFAULT_HOST = "0.0.0.0"; private static final String DOT_ACCESS_PROTOCOLS = ".access.protocols"; + private static final String DOT_ACCESS_THREAD_POOL = ".http.server.thread.pool"; private ListenerConfigUtil() { // left blank @@ -96,7 +100,8 @@ public static List buildControllerConfigs(PinotConfiguration con String portString = controllerConf.getProperty("controller.port"); if (portString != null) { listeners.add(new ListenerConfig(CommonConstants.HTTP_PROTOCOL, DEFAULT_HOST, Integer.parseInt(portString), - CommonConstants.HTTP_PROTOCOL, new TlsConfig())); + CommonConstants.HTTP_PROTOCOL, new TlsConfig(), buildServerThreadPoolConfig(controllerConf, + "pinot.controller"))); } TlsConfig tlsDefaults = TlsUtils.extractTlsConfig(controllerConf, "controller.tls"); @@ -113,7 +118,7 @@ public static List buildBrokerConfigs(PinotConfiguration brokerC String queryPortString = brokerConf.getProperty(CommonConstants.Helix.KEY_OF_BROKER_QUERY_PORT); if (queryPortString != null) { listeners.add(new ListenerConfig(CommonConstants.HTTP_PROTOCOL, DEFAULT_HOST, Integer.parseInt(queryPortString), - CommonConstants.HTTP_PROTOCOL, new TlsConfig())); + CommonConstants.HTTP_PROTOCOL, new TlsConfig(), buildServerThreadPoolConfig(brokerConf, "pinot.broker"))); } TlsConfig tlsDefaults = TlsUtils.extractTlsConfig(brokerConf, CommonConstants.Broker.BROKER_TLS_PREFIX); @@ -123,7 +128,8 @@ public static List buildBrokerConfigs(PinotConfiguration brokerC // support legacy behavior < 0.7.0 if (listeners.isEmpty()) { listeners.add(new ListenerConfig(CommonConstants.HTTP_PROTOCOL, DEFAULT_HOST, - CommonConstants.Helix.DEFAULT_BROKER_QUERY_PORT, CommonConstants.HTTP_PROTOCOL, new TlsConfig())); + CommonConstants.Helix.DEFAULT_BROKER_QUERY_PORT, CommonConstants.HTTP_PROTOCOL, new TlsConfig(), + buildServerThreadPoolConfig(brokerConf, "pinot.broker"))); } return listeners; @@ -136,7 +142,7 @@ public static List buildServerAdminConfigs(PinotConfiguration se if (adminApiPortString != null) { listeners.add( new ListenerConfig(CommonConstants.HTTP_PROTOCOL, DEFAULT_HOST, Integer.parseInt(adminApiPortString), - CommonConstants.HTTP_PROTOCOL, new TlsConfig())); + CommonConstants.HTTP_PROTOCOL, new TlsConfig(), buildServerThreadPoolConfig(serverConf, "pinot.server"))); } TlsConfig tlsDefaults = TlsUtils.extractTlsConfig(serverConf, CommonConstants.Server.SERVER_TLS_PREFIX); @@ -147,7 +153,7 @@ public static List buildServerAdminConfigs(PinotConfiguration se if (listeners.isEmpty()) { listeners.add( new ListenerConfig(CommonConstants.HTTP_PROTOCOL, DEFAULT_HOST, CommonConstants.Server.DEFAULT_ADMIN_API_PORT, - CommonConstants.HTTP_PROTOCOL, new TlsConfig())); + CommonConstants.HTTP_PROTOCOL, new TlsConfig(), buildServerThreadPoolConfig(serverConf, "pinot.server"))); } return listeners; @@ -159,7 +165,7 @@ public static List buildMinionConfigs(PinotConfiguration minionC String portString = minionConf.getProperty(CommonConstants.Helix.KEY_OF_MINION_PORT); if (portString != null) { listeners.add(new ListenerConfig(CommonConstants.HTTP_PROTOCOL, DEFAULT_HOST, Integer.parseInt(portString), - CommonConstants.HTTP_PROTOCOL, new TlsConfig())); + CommonConstants.HTTP_PROTOCOL, new TlsConfig(), buildServerThreadPoolConfig(minionConf, "pinot.minion"))); } TlsConfig tlsDefaults = TlsUtils.extractTlsConfig(minionConf, CommonConstants.Minion.MINION_TLS_PREFIX); @@ -169,7 +175,7 @@ public static List buildMinionConfigs(PinotConfiguration minionC if (listeners.isEmpty()) { listeners.add( new ListenerConfig(CommonConstants.HTTP_PROTOCOL, DEFAULT_HOST, CommonConstants.Minion.DEFAULT_HELIX_PORT, - CommonConstants.HTTP_PROTOCOL, new TlsConfig())); + CommonConstants.HTTP_PROTOCOL, new TlsConfig(), buildServerThreadPoolConfig(minionConf, "pinot.minion"))); } return listeners; @@ -182,7 +188,8 @@ private static ListenerConfig buildListenerConfig(PinotConfiguration config, Str return new ListenerConfig(name, getHost(config.getProperty(protocolNamespace + ".host", DEFAULT_HOST)), getPort(config.getProperty(protocolNamespace + ".port")), getProtocol(config.getProperty(protocolNamespace + ".protocol"), name), - TlsUtils.extractTlsConfig(config, protocolNamespace + ".tls", tlsConfig)); + TlsUtils.extractTlsConfig(config, protocolNamespace + ".tls", tlsConfig), + buildServerThreadPoolConfig(config, namespace)); } private static String getHost(String configuredHost) { @@ -231,7 +238,9 @@ public static void configureListener(HttpServer httpServer, ListenerConfig liste listener.getTransport().getWorkerThreadPoolConfig().setThreadFactory( new ThreadFactoryBuilder().setNameFormat("grizzly-http-server-%d") - .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler()).build()); + .setUncaughtExceptionHandler(new JerseyProcessingUncaughtExceptionHandler()).build()) + .setCorePoolSize(listenerConfig.getThreadPoolConfig().getCorePoolSize()) + .setMaxPoolSize(listenerConfig.getThreadPoolConfig().getMaxPoolSize()); if (CommonConstants.HTTPS_PROTOCOL.equals(listenerConfig.getProtocol())) { listener.setSecure(true); @@ -256,22 +265,26 @@ public static int findLastTlsPort(List configs, int defaultValue } private static SSLEngineConfigurator buildSSLEngineConfigurator(TlsConfig tlsConfig) { - SSLContextConfigurator sslContextConfigurator = new SSLContextConfigurator(); + SSLFactory sslFactory = + RenewableTlsUtils.createSSLFactoryAndEnableAutoRenewalWhenUsingFileStores( + tlsConfig, PinotInsecureMode::isPinotInInsecureMode); + return new SSLEngineConfigurator(sslFactory.getSslContext()).setClientMode(false) + .setNeedClientAuth(tlsConfig.isClientAuthEnabled()).setEnabledProtocols(new String[]{"TLSv1.2"}); + } - if (tlsConfig.getKeyStorePath() != null) { - Preconditions.checkNotNull(tlsConfig.getKeyStorePassword(), "key store password required"); - sslContextConfigurator.setKeyStoreFile(cacheInTempFile(tlsConfig.getKeyStorePath()).getAbsolutePath()); - sslContextConfigurator.setKeyStorePass(tlsConfig.getKeyStorePassword()); - } + private static HttpServerThreadPoolConfig buildServerThreadPoolConfig(PinotConfiguration config, String namespace) { + String threadPoolNamespace = namespace + DOT_ACCESS_THREAD_POOL; - if (tlsConfig.getTrustStorePath() != null) { - Preconditions.checkNotNull(tlsConfig.getKeyStorePassword(), "trust store password required"); - sslContextConfigurator.setTrustStoreFile(cacheInTempFile(tlsConfig.getTrustStorePath()).getAbsolutePath()); - sslContextConfigurator.setTrustStorePass(tlsConfig.getTrustStorePassword()); + HttpServerThreadPoolConfig threadPoolConfig = HttpServerThreadPoolConfig.defaultInstance(); + int corePoolSize = config.getProperty(threadPoolNamespace + "." + "corePoolSize", -1); + int maxPoolSize = config.getProperty(threadPoolNamespace + "." + "maxPoolSize", -1); + if (corePoolSize > 0) { + threadPoolConfig.setCorePoolSize(corePoolSize); } - - return new SSLEngineConfigurator(sslContextConfigurator).setClientMode(false) - .setNeedClientAuth(tlsConfig.isClientAuthEnabled()).setEnabledProtocols(new String[]{"TLSv1.2"}); + if (maxPoolSize > 0) { + threadPoolConfig.setMaxPoolSize(maxPoolSize); + } + return threadPoolConfig; } public static String toString(Collection listenerConfigs) { @@ -282,7 +295,7 @@ public static String toString(Collection listenerConfi private static File cacheInTempFile(String sourceUrl) { try { - URL url = TlsUtils.makeKeyStoreUrl(sourceUrl); + URL url = TlsUtils.makeKeyOrTrustStoreUrl(sourceUrl); if ("file".equals(url.getProtocol())) { return new File(url.getPath()); } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/util/OsCheck.java b/pinot-core/src/main/java/org/apache/pinot/core/util/OsCheck.java index a8ba40da5f7a..c3a52d1a129a 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/util/OsCheck.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/util/OsCheck.java @@ -19,12 +19,14 @@ package org.apache.pinot.core.util; import java.util.Locale; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public final class OsCheck { // cached result of OS detection - protected static final OSType _detectedOS; + private static final OSType DETECTED_OS; private OsCheck() { } @@ -36,7 +38,7 @@ private OsCheck() { * @return - the operating system detected */ public static OSType getOperatingSystemType() { - return _detectedOS; + return DETECTED_OS; } /** @@ -46,17 +48,19 @@ public enum OSType { Windows, MacOS, Linux, Other } + private static final Logger LOGGER = LoggerFactory.getLogger(OsCheck.class); + static { String os = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH); - System.out.println(os); + LOGGER.info("System property \"os.name\" is: {}", os); if ((os.contains("mac")) || (os.contains("darwin"))) { - _detectedOS = OSType.MacOS; + DETECTED_OS = OSType.MacOS; } else if (os.contains("win")) { - _detectedOS = OSType.Windows; + DETECTED_OS = OSType.Windows; } else if (os.contains("linux")) { - _detectedOS = OSType.Linux; + DETECTED_OS = OSType.Linux; } else { - _detectedOS = OSType.Other; + DETECTED_OS = OSType.Other; } } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/util/PeerServerSegmentFinder.java b/pinot-core/src/main/java/org/apache/pinot/core/util/PeerServerSegmentFinder.java index 80bd31fa3472..7f26d759352d 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/util/PeerServerSegmentFinder.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/util/PeerServerSegmentFinder.java @@ -19,21 +19,19 @@ package org.apache.pinot.core.util; import java.net.URI; -import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import java.util.Map; -import org.apache.commons.collections.ListUtils; import org.apache.helix.HelixAdmin; import org.apache.helix.HelixManager; import org.apache.helix.model.ExternalView; import org.apache.helix.model.InstanceConfig; -import org.apache.pinot.common.utils.LLCSegmentName; -import org.apache.pinot.common.utils.helix.HelixHelper; -import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.utils.CommonConstants; +import org.apache.pinot.spi.utils.CommonConstants.Helix.Instance; +import org.apache.pinot.spi.utils.CommonConstants.Helix.StateModel.SegmentStateModel; +import org.apache.pinot.spi.utils.CommonConstants.Server; import org.apache.pinot.spi.utils.StringUtil; -import org.apache.pinot.spi.utils.builder.TableNameBuilder; +import org.apache.pinot.spi.utils.retry.AttemptsExceededException; import org.apache.pinot.spi.utils.retry.RetryPolicies; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,82 +45,74 @@ public class PeerServerSegmentFinder { private PeerServerSegmentFinder() { } - private static final Logger _logger = LoggerFactory.getLogger(PeerServerSegmentFinder.class); + private static final Logger LOGGER = LoggerFactory.getLogger(PeerServerSegmentFinder.class); private static final int MAX_NUM_ATTEMPTS = 5; private static final int INITIAL_DELAY_MS = 500; private static final double DELAY_SCALE_FACTOR = 2; /** - * - * @param segmentName - * @param downloadScheme Can be either http or https. - * @param helixManager - * @return a list of uri strings of the form http(s)://hostname:port/segments/tablenameWithType/segmentName - * for the servers hosting ONLINE segments; empty list if no such server found. + * Returns a list of URIs of the form 'http(s)://hostname:port/segments/tableNameWithType/segmentName' for the servers + * hosting ONLINE segments; empty list if no such server found. The download scheme can be either 'http' or 'https'. */ - public static List getPeerServerURIs(String segmentName, String downloadScheme, HelixManager helixManager) { - LLCSegmentName llcSegmentName = new LLCSegmentName(segmentName); - String tableNameWithType = - TableNameBuilder.forType(TableType.REALTIME).tableNameWithType(llcSegmentName.getTableName()); - return getPeerServerURIs(segmentName, downloadScheme, helixManager, tableNameWithType); - } - - public static List getPeerServerURIs(String segmentName, String downloadScheme, - HelixManager helixManager, String tableNameWithType) { + public static List getPeerServerURIs(HelixManager helixManager, String tableNameWithType, String segmentName, + String downloadScheme) { HelixAdmin helixAdmin = helixManager.getClusterManagmentTool(); String clusterName = helixManager.getClusterName(); - if (clusterName == null) { - _logger.error("ClusterName not found"); - return ListUtils.EMPTY_LIST; - } - final List onlineServerURIs = new ArrayList<>(); + List onlineServerURIs = new ArrayList<>(); try { RetryPolicies.exponentialBackoffRetryPolicy(MAX_NUM_ATTEMPTS, INITIAL_DELAY_MS, DELAY_SCALE_FACTOR) .attempt(() -> { - getOnlineServersFromExternalView(segmentName, downloadScheme, tableNameWithType, helixAdmin, clusterName, + getOnlineServersFromExternalView(helixAdmin, clusterName, tableNameWithType, segmentName, downloadScheme, onlineServerURIs); return !onlineServerURIs.isEmpty(); }); + } catch (AttemptsExceededException e) { + LOGGER.error("Failed to find ONLINE servers for segment: {} in table: {} after {} attempts", segmentName, + tableNameWithType, MAX_NUM_ATTEMPTS); } catch (Exception e) { - _logger.error("Failure in getting online servers for segment {}", segmentName, e); + LOGGER.error("Caught exception while getting peer server URIs for segment: {} in table: {}", segmentName, + tableNameWithType, e); } return onlineServerURIs; } - private static void getOnlineServersFromExternalView(String segmentName, String downloadScheme, - String tableNameWithType, HelixAdmin helixAdmin, String clusterName, List onlineServerURIs) { - ExternalView externalViewForResource = - HelixHelper.getExternalViewForResource(helixAdmin, clusterName, tableNameWithType); - if (externalViewForResource == null) { - _logger.warn("External View not found for table {}", tableNameWithType); + private static void getOnlineServersFromExternalView(HelixAdmin helixAdmin, String clusterName, + String tableNameWithType, String segmentName, String downloadScheme, List onlineServerURIs) + throws Exception { + ExternalView externalView = helixAdmin.getResourceExternalView(clusterName, tableNameWithType); + if (externalView == null) { + LOGGER.warn("Failed to find external view for table: {}", tableNameWithType); return; } // Find out the ONLINE servers serving the segment. - Map instanceToStateMap = externalViewForResource.getStateMap(segmentName); - for (Map.Entry instanceState : instanceToStateMap.entrySet()) { - if ("ONLINE".equals(instanceState.getValue())) { + Map instanceStateMap = externalView.getStateMap(segmentName); + if (instanceStateMap == null) { + LOGGER.warn("Failed to find segment: {} in table: {}", segmentName, tableNameWithType); + return; + } + for (Map.Entry instanceState : instanceStateMap.entrySet()) { + if (SegmentStateModel.ONLINE.equals(instanceState.getValue())) { String instanceId = instanceState.getKey(); - _logger.info("Found ONLINE server {} for segment {}.", instanceId, segmentName); + LOGGER.info("Found ONLINE server: {} for segment: {} in table: {}", instanceId, segmentName, tableNameWithType); InstanceConfig instanceConfig = helixAdmin.getInstanceConfig(clusterName, instanceId); String hostName = instanceConfig.getHostName(); - int port = getServerAdminPort(helixAdmin, clusterName, instanceId); - try { - onlineServerURIs.add(new URI(StringUtil - .join("/", downloadScheme + "://" + hostName + ":" + port, "segments", tableNameWithType, segmentName))); - } catch (URISyntaxException e) { - _logger.warn("Error in uri syntax: ", e); - } + String adminPortKey = getAdminPortKey(downloadScheme); + int port = instanceConfig.getRecord().getIntField(adminPortKey, Server.DEFAULT_ADMIN_API_PORT); + onlineServerURIs.add(new URI( + StringUtil.join("/", downloadScheme + "://" + hostName + ":" + port, "segments", tableNameWithType, + segmentName))); } } } - private static int getServerAdminPort(HelixAdmin helixAdmin, String clusterName, String instanceId) { - try { - return Integer.parseInt(HelixHelper.getInstanceConfigsMapFor(instanceId, clusterName, helixAdmin) - .get(CommonConstants.Helix.Instance.ADMIN_PORT_KEY)); - } catch (Exception e) { - _logger.warn("Failed to retrieve ADMIN PORT for instanceId {} in the cluster {} ", instanceId, clusterName, e); - return CommonConstants.Helix.DEFAULT_SERVER_NETTY_PORT; + private static String getAdminPortKey(String downloadScheme) { + switch (downloadScheme) { + case CommonConstants.HTTP_PROTOCOL: + return Instance.ADMIN_PORT_KEY; + case CommonConstants.HTTPS_PROTOCOL: + return Instance.ADMIN_HTTPS_PORT_KEY; + default: + throw new IllegalArgumentException("Unsupported download scheme: " + downloadScheme); } } } diff --git a/pinot-core/src/main/java/org/apache/pinot/core/util/QueryMultiThreadingUtils.java b/pinot-core/src/main/java/org/apache/pinot/core/util/QueryMultiThreadingUtils.java new file mode 100644 index 000000000000..bea9d0110c51 --- /dev/null +++ b/pinot-core/src/main/java/org/apache/pinot/core/util/QueryMultiThreadingUtils.java @@ -0,0 +1,113 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.Phaser; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.function.Function; +import org.apache.pinot.core.util.trace.TraceCallable; + + +/** + * The term `task` and `thread` are used interchangeably in the logic to parallelize CombinePlanNode and + * BaseCombineOperator. This class provides common methods used to set up the parallel processing. + */ +public class QueryMultiThreadingUtils { + private QueryMultiThreadingUtils() { + } + + /** + * Use at most 10 or half of the processors threads for each query. If there are less than 2 processors, use 1 + * thread. + *

    NOTE: Runtime.getRuntime().availableProcessors() may return value < 2 in container based environment, e.g. + * Kubernetes. + */ + public static final int MAX_NUM_THREADS_PER_QUERY = + Math.max(1, Math.min(10, Runtime.getRuntime().availableProcessors() / 2)); + + /** + * Returns the number of tasks for the query execution. The tasks can be assigned to multiple execution threads so + * that they can run in parallel. The parallelism is bounded by the task count. + */ + public static int getNumTasksForQuery(int numOperators, int maxExecutionThreads) { + return getNumTasks(numOperators, 1, maxExecutionThreads); + } + + public static int getNumTasks(int numWorkUnits, int minUnitsPerThread, int maxExecutionThreads) { + if (numWorkUnits <= minUnitsPerThread) { + return 1; + } + if (maxExecutionThreads <= 0) { + maxExecutionThreads = MAX_NUM_THREADS_PER_QUERY; + } + return Math.min((numWorkUnits + minUnitsPerThread - 1) / minUnitsPerThread, maxExecutionThreads); + } + + /** + * Use a Phaser to ensure all the Futures are done (not scheduled, finished or interrupted) before the main thread + * returns. We need to ensure no execution left before the main thread returning because the main thread holds the + * reference to the segments, and if the segments are deleted/refreshed, the segments can be released after the main + * thread returns, which would lead to undefined behavior (even JVM crash) when executing queries against them. + */ + public static void runTasksWithDeadline(int numTasks, Function taskFunc, Consumer resCollector, + Consumer errHandler, ExecutorService executorService, long deadlineInMs) { + Phaser phaser = new Phaser(1); + List> futures = new ArrayList<>(numTasks); + for (int i = 0; i < numTasks; i++) { + int index = i; + futures.add(executorService.submit(new TraceCallable() { + @Override + public T callJob() { + try { + // Register the thread to the phaser for main thread to wait for it to complete. + if (phaser.register() < 0) { + return null; + } + return taskFunc.apply(index); + } finally { + phaser.arriveAndDeregister(); + } + } + })); + } + try { + // Check deadline while waiting for the task results. + for (Future future : futures) { + T taskResult = future.get(deadlineInMs - System.currentTimeMillis(), TimeUnit.MILLISECONDS); + resCollector.accept(taskResult); + } + } catch (Exception e) { + errHandler.accept(e); + } finally { + // Cancel all ongoing jobs + for (Future future : futures) { + if (!future.isDone()) { + future.cancel(true); + } + } + // Deregister the main thread and wait for all threads done + phaser.awaitAdvance(phaser.arriveAndDeregister()); + } + } +} diff --git a/pinot-core/src/main/java/org/apache/pinot/core/util/trace/BuiltInTracer.java b/pinot-core/src/main/java/org/apache/pinot/core/util/trace/BuiltInTracer.java index 7bd93b176311..bbd5b93bff89 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/util/trace/BuiltInTracer.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/util/trace/BuiltInTracer.java @@ -50,6 +50,15 @@ public void close() { } TraceContext.logTime(operatorName, duration); } + + @Override + public void close(Object context) { + String operatorName = _operator.getSimpleName(); + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Context collected for {}: {}", operatorName, context); + } + TraceContext.logInfo(operatorName, context); + } } @Override diff --git a/pinot-core/src/main/java/org/apache/pinot/core/util/trace/TraceContext.java b/pinot-core/src/main/java/org/apache/pinot/core/util/trace/TraceContext.java index 4d430434e562..0bdaf0da626e 100644 --- a/pinot-core/src/main/java/org/apache/pinot/core/util/trace/TraceContext.java +++ b/pinot-core/src/main/java/org/apache/pinot/core/util/trace/TraceContext.java @@ -189,8 +189,11 @@ public static void logInfo(String key, Object value) { */ public static String getTraceInfo() { ArrayNode jsonTraces = JsonUtils.newArrayNode(); - for (Trace trace : REQUEST_TO_TRACES_MAP.get(TRACE_ENTRY_THREAD_LOCAL.get()._requestId)) { - jsonTraces.add(trace.toJson()); + Queue traces = REQUEST_TO_TRACES_MAP.get(TRACE_ENTRY_THREAD_LOCAL.get()._requestId); + if (traces != null && !traces.isEmpty()) { + for (Trace trace : traces) { + jsonTraces.add(trace.toJson()); + } } return jsonTraces.toString(); } diff --git a/pinot-core/src/main/java/org/apache/pinot/server/access/BasicAuthAccessFactory.java b/pinot-core/src/main/java/org/apache/pinot/server/access/BasicAuthAccessFactory.java index 09187ece7bd4..9747151c8025 100644 --- a/pinot-core/src/main/java/org/apache/pinot/server/access/BasicAuthAccessFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/server/access/BasicAuthAccessFactory.java @@ -23,7 +23,7 @@ import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.pinot.core.auth.BasicAuthPrincipal; import org.apache.pinot.core.auth.BasicAuthUtils; import org.apache.pinot.spi.env.PinotConfiguration; @@ -65,7 +65,7 @@ public boolean isAuthorizedChannel(ChannelHandlerContext channelHandlerContext) public boolean hasDataAccess(RequesterIdentity requesterIdentity, String tableName) { Collection tokens = getTokens(requesterIdentity); return tokens.stream() - .map(BasicAuthUtils::normalizeBase64Token) + .map(org.apache.pinot.common.auth.BasicAuthUtils::normalizeBase64Token) .map(_token2principal::get) .filter(Objects::nonNull) .findFirst() diff --git a/pinot-core/src/main/java/org/apache/pinot/server/access/ZkBasicAuthAccessFactory.java b/pinot-core/src/main/java/org/apache/pinot/server/access/ZkBasicAuthAccessFactory.java index 6606a2b026af..b5de5db93c86 100644 --- a/pinot-core/src/main/java/org/apache/pinot/server/access/ZkBasicAuthAccessFactory.java +++ b/pinot-core/src/main/java/org/apache/pinot/server/access/ZkBasicAuthAccessFactory.java @@ -23,7 +23,7 @@ import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.helix.HelixManager; import org.apache.pinot.common.config.provider.AccessControlUserCache; import org.apache.pinot.common.utils.BcryptUtils; @@ -32,6 +32,7 @@ import org.apache.pinot.spi.env.PinotConfiguration; import org.apache.pinot.spi.utils.builder.TableNameBuilder; + /** * Zookeeper Basic Authentication based on Pinot Controller UI. * The user role has been distinguished by user and admin. Only admin can have access to the @@ -42,80 +43,78 @@ * */ public class ZkBasicAuthAccessFactory implements AccessControlFactory { - private static final String AUTHORIZATION_KEY = "authorization"; + private static final String AUTHORIZATION_KEY = "authorization"; - private HelixManager _helixManager; - private AccessControl _accessControl; + private HelixManager _helixManager; + private AccessControl _accessControl; - @Override - public void init(PinotConfiguration configuration, HelixManager helixManager) { - _helixManager = helixManager; - } + @Override + public void init(PinotConfiguration configuration, HelixManager helixManager) { + _helixManager = helixManager; + } - @Override - public AccessControl create() { - if (_accessControl == null) { - _accessControl = new BasicAuthAccessControl(_helixManager); - } - return _accessControl; + @Override + public AccessControl create() { + if (_accessControl == null) { + _accessControl = new BasicAuthAccessControl(_helixManager); } + return _accessControl; + } - /** - * Access Control using metadata-based basic grpc authentication - */ - private static class BasicAuthAccessControl implements AccessControl { - private Map _name2principal; - private AccessControlUserCache _userCache; - private HelixManager _innerHelixManager; + /** + * Access Control using metadata-based basic grpc authentication + */ + private static class BasicAuthAccessControl implements AccessControl { + private Map _name2principal; + private AccessControlUserCache _userCache; + private HelixManager _innerHelixManager; - public BasicAuthAccessControl(HelixManager helixManager) { - _innerHelixManager = helixManager; - } + public BasicAuthAccessControl(HelixManager helixManager) { + _innerHelixManager = helixManager; + } - public void initUserCache() { - if (_userCache == null) { - _userCache = new AccessControlUserCache(_innerHelixManager.getHelixPropertyStore()); - } - } + public void initUserCache() { + if (_userCache == null) { + _userCache = new AccessControlUserCache(_innerHelixManager.getHelixPropertyStore()); + } + } - @Override - public boolean isAuthorizedChannel(ChannelHandlerContext channelHandlerContext) { - return true; - } + @Override + public boolean isAuthorizedChannel(ChannelHandlerContext channelHandlerContext) { + return true; + } - @Override - public boolean hasDataAccess(RequesterIdentity requesterIdentity, String tableName) { - if (_userCache == null) { - initUserCache(); - } - Collection tokens = getTokens(requesterIdentity); - _name2principal = BasicAuthUtils.extractBasicAuthPrincipals(_userCache.getAllServerUserConfig()) - .stream().collect(Collectors.toMap(ZkBasicAuthPrincipal::getName, p -> p)); + @Override + public boolean hasDataAccess(RequesterIdentity requesterIdentity, String tableName) { + if (_userCache == null) { + initUserCache(); + } + Collection tokens = getTokens(requesterIdentity); + _name2principal = BasicAuthUtils.extractBasicAuthPrincipals(_userCache.getAllServerUserConfig()).stream() + .collect(Collectors.toMap(ZkBasicAuthPrincipal::getName, p -> p)); - Map name2password = tokens.stream().collect(Collectors - .toMap(BasicAuthUtils::extractUsername, BasicAuthUtils::extractPassword)); - Map password2principal = name2password.keySet().stream() - .collect(Collectors.toMap(name2password::get, _name2principal::get)); - return password2principal.entrySet().stream() - .filter(entry -> BcryptUtils.checkpw(entry.getKey(), entry.getValue().getPassword())) - .map(u -> u.getValue()) - .filter(Objects::nonNull) - .findFirst() - .map(zkprincipal -> StringUtils.isEmpty(tableName) || zkprincipal.hasTable( - TableNameBuilder.extractRawTableName(tableName))) - .orElse(false); - } + Map name2password = tokens.stream().collect( + Collectors.toMap(org.apache.pinot.common.auth.BasicAuthUtils::extractUsername, + org.apache.pinot.common.auth.BasicAuthUtils::extractPassword)); + Map password2principal = + name2password.keySet().stream().collect(Collectors.toMap(name2password::get, _name2principal::get)); + return password2principal.entrySet().stream().filter( + entry -> BcryptUtils.checkpwWithCache(entry.getKey(), entry.getValue().getPassword(), + _userCache.getUserPasswordAuthCache())).map(u -> u.getValue()).filter(Objects::nonNull).findFirst().map( + zkprincipal -> StringUtils.isEmpty(tableName) || zkprincipal.hasTable( + TableNameBuilder.extractRawTableName(tableName))).orElse(false); + } - private Collection getTokens(RequesterIdentity requesterIdentity) { - if (requesterIdentity instanceof GrpcRequesterIdentity) { - GrpcRequesterIdentity identity = (GrpcRequesterIdentity) requesterIdentity; - return identity.getGrpcMetadata().get(AUTHORIZATION_KEY); - } - if (requesterIdentity instanceof HttpRequesterIdentity) { - HttpRequesterIdentity identity = (HttpRequesterIdentity) requesterIdentity; - return identity.getHttpHeaders().get(AUTHORIZATION_KEY); - } - throw new UnsupportedOperationException("GrpcRequesterIdentity or HttpRequesterIdentity is required"); - } + private Collection getTokens(RequesterIdentity requesterIdentity) { + if (requesterIdentity instanceof GrpcRequesterIdentity) { + GrpcRequesterIdentity identity = (GrpcRequesterIdentity) requesterIdentity; + return identity.getGrpcMetadata().get(AUTHORIZATION_KEY); + } + if (requesterIdentity instanceof HttpRequesterIdentity) { + HttpRequesterIdentity identity = (HttpRequesterIdentity) requesterIdentity; + return identity.getHttpHeaders().get(AUTHORIZATION_KEY); + } + throw new UnsupportedOperationException("GrpcRequesterIdentity or HttpRequesterIdentity is required"); } + } } diff --git a/pinot-core/src/main/java/org/apache/pinot/server/realtime/ServerSegmentCompletionProtocolHandler.java b/pinot-core/src/main/java/org/apache/pinot/server/realtime/ServerSegmentCompletionProtocolHandler.java index a7547e9ab1eb..6e1e24a688be 100644 --- a/pinot-core/src/main/java/org/apache/pinot/server/realtime/ServerSegmentCompletionProtocolHandler.java +++ b/pinot-core/src/main/java/org/apache/pinot/server/realtime/ServerSegmentCompletionProtocolHandler.java @@ -29,6 +29,7 @@ import org.apache.pinot.common.protocols.SegmentCompletionProtocol; import org.apache.pinot.common.utils.ClientSSLContextGenerator; import org.apache.pinot.common.utils.FileUploadDownloadClient; +import org.apache.pinot.common.utils.http.HttpClientConfig; import org.apache.pinot.core.data.manager.realtime.Server2ControllerSegmentUploader; import org.apache.pinot.core.util.SegmentCompletionProtocolUtils; import org.apache.pinot.spi.auth.AuthProvider; @@ -52,6 +53,7 @@ public class ServerSegmentCompletionProtocolHandler { private static final String HTTP_PROTOCOL = CommonConstants.HTTP_PROTOCOL; private static SSLContext _sslContext; + private static HttpClientConfig _httpClientConfig = HttpClientConfig.DEFAULT_HTTP_CLIENT_CONFIG; private static Integer _controllerHttpsPort; private static int _segmentUploadRequestTimeoutMs; private static AuthProvider _authProvider; @@ -76,10 +78,12 @@ public static void init(PinotConfiguration uploaderConfig) { .getProperty(CONFIG_OF_SEGMENT_UPLOAD_REQUEST_TIMEOUT_MS, DEFAULT_SEGMENT_UPLOAD_REQUEST_TIMEOUT_MS); _authProvider = AuthProviderUtils.extractAuthProvider(uploaderConfig, CONFIG_OF_SEGMENT_UPLOADER_AUTH); + + _httpClientConfig = HttpClientConfig.newBuilder(uploaderConfig).build(); } public ServerSegmentCompletionProtocolHandler(ServerMetrics serverMetrics, String tableNameWithType) { - _fileUploadDownloadClient = new FileUploadDownloadClient(_sslContext); + _fileUploadDownloadClient = new FileUploadDownloadClient(_httpClientConfig, _sslContext); _serverMetrics = serverMetrics; _rawTableName = TableNameBuilder.extractRawTableName(tableNameWithType); } @@ -124,18 +128,6 @@ public String getSegmentCommitUploadURL(SegmentCompletionProtocol.Request.Params return request.getUrl(hostPort, protocol); } - // Replaced by segmentCommitEndWithMetadata(). - @Deprecated - public SegmentCompletionProtocol.Response segmentCommitEnd(SegmentCompletionProtocol.Request.Params params) { - SegmentCompletionProtocol.SegmentCommitEndRequest request = - new SegmentCompletionProtocol.SegmentCommitEndRequest(params); - String url = createSegmentCompletionUrl(request); - if (url == null) { - return SegmentCompletionProtocol.RESP_NOT_SENT; - } - return sendRequest(url); - } - public SegmentCompletionProtocol.Response segmentCommitEndWithMetadata( SegmentCompletionProtocol.Request.Params params, final Map metadataFiles) { SegmentCompletionProtocol.SegmentCommitEndWithMetadataRequest request = @@ -159,7 +151,7 @@ public SegmentCompletionProtocol.Response segmentCommit(SegmentCompletionProtoco try { segmentUploader = new Server2ControllerSegmentUploader(LOGGER, _fileUploadDownloadClient, url, params.getSegmentName(), - _segmentUploadRequestTimeoutMs, _serverMetrics, _authProvider); + _segmentUploadRequestTimeoutMs, _serverMetrics, _authProvider, _rawTableName); } catch (URISyntaxException e) { LOGGER.error("Segment commit upload url error: ", e); return SegmentCompletionProtocol.RESP_NOT_SENT; diff --git a/pinot-core/src/test/java/org/apache/pinot/core/NettyTest.java b/pinot-core/src/test/java/org/apache/pinot/core/NettyTest.java new file mode 100644 index 000000000000..61fb32398f21 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/NettyTest.java @@ -0,0 +1,59 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core; + +import io.netty.channel.epoll.Epoll; +import io.netty.channel.kqueue.KQueue; +import io.netty.handler.ssl.OpenSsl; +import org.apache.pinot.core.util.OsCheck; +import org.testng.SkipException; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + + +public class NettyTest { + private static void requiredOs(final OsCheck.OSType requiredOsType) { + if (OsCheck.getOperatingSystemType() != requiredOsType) { + throw new SkipException("skipping test: " + requiredOsType + " != " + OsCheck.getOperatingSystemType()); + } + } + + @Test + public void epollIsAvailableOnLinux() { + requiredOs(OsCheck.OSType.Linux); + Epoll.ensureAvailability(); + assertTrue(Epoll.isAvailable()); + } + + @Test + public void kqueueIsAvailableOnMac() { + requiredOs(OsCheck.OSType.MacOS); + KQueue.ensureAvailability(); + assertTrue(KQueue.isAvailable()); + } + + @Test + public void boringSslIsAvailable() { + OpenSsl.ensureAvailability(); + assertTrue(OpenSsl.isAvailable()); + assertEquals(OpenSsl.versionString(), "BoringSSL"); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/accounting/ResourceManagerAccountingTest.java b/pinot-core/src/test/java/org/apache/pinot/core/accounting/ResourceManagerAccountingTest.java index acc0a4acd67a..f8c0e4562df7 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/accounting/ResourceManagerAccountingTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/accounting/ResourceManagerAccountingTest.java @@ -18,27 +18,48 @@ */ package org.apache.pinot.core.accounting; +import java.io.File; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; +import org.apache.commons.io.FileUtils; import org.apache.log4j.Level; import org.apache.log4j.LogManager; +import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.metrics.ServerMetrics; +import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.accounting.PerQueryCPUMemAccountantFactory.PerQueryCPUMemResourceUsageAccountant; -import org.apache.pinot.core.accounting.utils.RunnerWorkerThreadOffsetProvider; +import org.apache.pinot.core.common.datablock.DataBlockTestUtils; +import org.apache.pinot.core.data.table.IndexedTable; +import org.apache.pinot.core.data.table.Record; +import org.apache.pinot.core.data.table.SimpleIndexedTable; +import org.apache.pinot.core.operator.blocks.results.GroupByResultsBlock; import org.apache.pinot.core.query.request.ServerQueryRequest; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.query.request.context.utils.QueryContextConverterUtils; import org.apache.pinot.core.query.scheduler.SchedulerGroupAccountant; import org.apache.pinot.core.query.scheduler.resources.QueryExecutorService; import org.apache.pinot.core.query.scheduler.resources.ResourceManager; +import org.apache.pinot.core.query.selection.SelectionOperatorUtils; +import org.apache.pinot.segment.local.realtime.impl.json.MutableJsonIndexImpl; +import org.apache.pinot.segment.local.segment.creator.impl.inv.json.OffHeapJsonIndexCreator; +import org.apache.pinot.segment.local.segment.index.readers.json.ImmutableJsonIndexReader; +import org.apache.pinot.segment.spi.V1Constants; +import org.apache.pinot.segment.spi.index.creator.JsonIndexCreator; +import org.apache.pinot.segment.spi.memory.PinotDataBuffer; import org.apache.pinot.spi.accounting.ThreadExecutionContext; import org.apache.pinot.spi.accounting.ThreadResourceUsageProvider; +import org.apache.pinot.spi.config.table.JsonIndexConfig; import org.apache.pinot.spi.env.PinotConfiguration; import org.apache.pinot.spi.exception.EarlyTerminationException; import org.apache.pinot.spi.trace.Tracing; @@ -53,45 +74,7 @@ public class ResourceManagerAccountingTest { public static final Logger LOGGER = LoggerFactory.getLogger(ResourceManagerAccountingTest.class); - - @Test - public void testThreadIDProvider() - throws Exception { - ResourceManager rm = getResourceManager(2, 5, 1, 3, Collections.emptyMap()); - Future[] futures = new Future[2001]; - Set threadIds = ConcurrentHashMap.newKeySet(); - - RunnerWorkerThreadOffsetProvider runnerWorkerThreadOffsetProvider = new RunnerWorkerThreadOffsetProvider(); - - for (int i = 0; i < 1000; i++) { - int finalI = i; - futures[i + 1000] = rm.getQueryWorkers().submit(() -> { - int id = runnerWorkerThreadOffsetProvider.get(); - threadIds.add(id); - futures[2000].cancel(true); - }); - futures[i] = rm.getQueryRunners().submit(() -> { - int id = runnerWorkerThreadOffsetProvider.get(); - threadIds.add(id); - futures[2500 - finalI] = null; - }); - } - for (int i = 0; i < 2000; i++) { - try { - futures[i].get(); - } catch (Exception ignored) { - } - } - Assert.assertEquals(threadIds.size(), 7); - - Assert.assertTrue(threadIds.contains(0)); - Assert.assertTrue(threadIds.contains(1)); - Assert.assertTrue(threadIds.contains(2)); - Assert.assertTrue(threadIds.contains(3)); - Assert.assertTrue(threadIds.contains(4)); - Assert.assertTrue(threadIds.contains(5)); - Assert.assertTrue(threadIds.contains(6)); - } + private static final int NUM_ROWS = 1_000_000; /** * Test thread cpu usage tracking in multithread environment, add @Test to run. @@ -265,6 +248,236 @@ public void testWorkerThreadInterruption() Assert.fail("Expected EarlyTerminationException to be thrown"); } + /** + * Test instrumentation during {@link DataTable} creation + */ + @Test + public void testGetDataTableOOMSelect() + throws Exception { + + // generate random rows + String[] columnNames = { + "int", "long", "float", "double", "big_decimal", "string", "bytes", "int_array", "long_array", "float_array", + "double_array", "string_array" + }; + DataSchema.ColumnDataType[] columnDataTypes = { + DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.LONG, DataSchema.ColumnDataType.FLOAT, + DataSchema.ColumnDataType.DOUBLE, DataSchema.ColumnDataType.BIG_DECIMAL, DataSchema.ColumnDataType.STRING, + DataSchema.ColumnDataType.BYTES, DataSchema.ColumnDataType.INT_ARRAY, DataSchema.ColumnDataType.LONG_ARRAY, + DataSchema.ColumnDataType.FLOAT_ARRAY, DataSchema.ColumnDataType.DOUBLE_ARRAY, + DataSchema.ColumnDataType.STRING_ARRAY + }; + DataSchema dataSchema = new DataSchema(columnNames, columnDataTypes); + List rows = DataBlockTestUtils.getRandomRows(dataSchema, NUM_ROWS, 0); + + // set up logging and configs + LogManager.getLogger(PerQueryCPUMemResourceUsageAccountant.class).setLevel(Level.OFF); + LogManager.getLogger(ThreadResourceUsageProvider.class).setLevel(Level.OFF); + ThreadResourceUsageProvider.setThreadMemoryMeasurementEnabled(true); + HashMap configs = new HashMap<>(); + ServerMetrics.register(Mockito.mock(ServerMetrics.class)); + configs.put(CommonConstants.Accounting.CONFIG_OF_ALARMING_LEVEL_HEAP_USAGE_RATIO, 0.00f); + configs.put(CommonConstants.Accounting.CONFIG_OF_CRITICAL_LEVEL_HEAP_USAGE_RATIO, 0.00f); + configs.put(CommonConstants.Accounting.CONFIG_OF_FACTORY_NAME, + "org.apache.pinot.core.accounting.PerQueryCPUMemAccountantFactory"); + configs.put(CommonConstants.Accounting.CONFIG_OF_ENABLE_THREAD_MEMORY_SAMPLING, true); + configs.put(CommonConstants.Accounting.CONFIG_OF_ENABLE_THREAD_CPU_SAMPLING, false); + configs.put(CommonConstants.Accounting.CONFIG_OF_OOM_PROTECTION_KILLING_QUERY, true); + PinotConfiguration config = getConfig(20, 2, configs); + ResourceManager rm = getResourceManager(20, 2, 1, 1, configs); + // init accountant and start watcher task + Tracing.ThreadAccountantOps.initializeThreadAccountant(config, "testSelect"); + + CountDownLatch latch = new CountDownLatch(100); + AtomicBoolean earlyTerminationOccurred = new AtomicBoolean(false); + + for (int i = 0; i < 100; i++) { + int finalI = i; + rm.getQueryRunners().submit(() -> { + Tracing.ThreadAccountantOps.setupRunner("testSelectQueryId" + finalI); + try { + SelectionOperatorUtils.getDataTableFromRows(rows, dataSchema, false).toBytes(); + } catch (EarlyTerminationException e) { + earlyTerminationOccurred.set(true); + Tracing.ThreadAccountantOps.clear(); + } catch (IOException e) { + Assert.fail(e.getMessage()); + } finally { + latch.countDown(); + } + }); + } + latch.await(); + // assert that EarlyTerminationException was thrown in at least one runner thread + Assert.assertTrue(earlyTerminationOccurred.get()); + } + + /** + * Test instrumentation during {@link DataTable} creation + */ + @Test + public void testGetDataTableOOMGroupBy() + throws Exception { + + // generate random indexedTable + QueryContext queryContext = + QueryContextConverterUtils.getQueryContext("SELECT SUM(m1), MAX(m2) FROM testTable GROUP BY d1, d2, d3, d4"); + DataSchema dataSchema = + new DataSchema(new String[]{"d1", "d2", "d3", "d4", "sum(m1)", "max(m2)"}, new DataSchema.ColumnDataType[]{ + DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.DOUBLE, + DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.DOUBLE, DataSchema.ColumnDataType.DOUBLE + }); + List rows = DataBlockTestUtils.getRandomRows(dataSchema, NUM_ROWS, 0); + IndexedTable indexedTable = + new SimpleIndexedTable(dataSchema, queryContext, NUM_ROWS, Integer.MAX_VALUE, Integer.MAX_VALUE); + for (Object[] row : rows) { + indexedTable.upsert(new Record(row)); + } + indexedTable.finish(false); + // set up GroupByResultsBlock + GroupByResultsBlock groupByResultsBlock = new GroupByResultsBlock(indexedTable, queryContext); + + // set up logging and configs + LogManager.getLogger(PerQueryCPUMemResourceUsageAccountant.class).setLevel(Level.OFF); + LogManager.getLogger(ThreadResourceUsageProvider.class).setLevel(Level.OFF); + ThreadResourceUsageProvider.setThreadMemoryMeasurementEnabled(true); + HashMap configs = new HashMap<>(); + ServerMetrics.register(Mockito.mock(ServerMetrics.class)); + configs.put(CommonConstants.Accounting.CONFIG_OF_ALARMING_LEVEL_HEAP_USAGE_RATIO, 0.00f); + configs.put(CommonConstants.Accounting.CONFIG_OF_CRITICAL_LEVEL_HEAP_USAGE_RATIO, 0.00f); + configs.put(CommonConstants.Accounting.CONFIG_OF_FACTORY_NAME, + "org.apache.pinot.core.accounting.PerQueryCPUMemAccountantFactory"); + configs.put(CommonConstants.Accounting.CONFIG_OF_ENABLE_THREAD_MEMORY_SAMPLING, true); + configs.put(CommonConstants.Accounting.CONFIG_OF_ENABLE_THREAD_CPU_SAMPLING, false); + configs.put(CommonConstants.Accounting.CONFIG_OF_OOM_PROTECTION_KILLING_QUERY, true); + PinotConfiguration config = getConfig(20, 2, configs); + ResourceManager rm = getResourceManager(20, 2, 1, 1, configs); + // init accountant and start watcher task + Tracing.ThreadAccountantOps.initializeThreadAccountant(config, "testGroupBy"); + + CountDownLatch latch = new CountDownLatch(100); + AtomicBoolean earlyTerminationOccurred = new AtomicBoolean(false); + + for (int i = 0; i < 100; i++) { + int finalI = i; + rm.getQueryRunners().submit(() -> { + Tracing.ThreadAccountantOps.setupRunner("testGroupByQueryId" + finalI); + try { + groupByResultsBlock.getDataTable().toBytes(); + } catch (EarlyTerminationException e) { + earlyTerminationOccurred.set(true); + Tracing.ThreadAccountantOps.clear(); + } catch (IOException e) { + Assert.fail(e.getMessage()); + } finally { + latch.countDown(); + } + }); + } + latch.await(); + // assert that EarlyTerminationException was thrown in at least one runner thread + Assert.assertTrue(earlyTerminationOccurred.get()); + } + + /** + * Test instrumentation in getMatchingFlattenedDocsMap() from + * {@link org.apache.pinot.segment.spi.index.reader.JsonIndexReader} + * + * Since getMatchingFlattenedDocsMap() can collect a large map before processing any blocks, it is required to + * check for OOM during map generation. This test generates a mutable and immutable json index, and generates a map + * as would happen in json_extract_index execution. + * + * It is roughly equivalent to running json_extract_index(col, '$.key', 'STRING'). + */ + @Test + public void testJsonIndexExtractMapOOM() + throws Exception { + HashMap configs = new HashMap<>(); + ServerMetrics.register(Mockito.mock(ServerMetrics.class)); + ThreadResourceUsageProvider.setThreadMemoryMeasurementEnabled(true); + LogManager.getLogger(PerQueryCPUMemResourceUsageAccountant.class).setLevel(Level.OFF); + LogManager.getLogger(ThreadResourceUsageProvider.class).setLevel(Level.OFF); + configs.put(CommonConstants.Accounting.CONFIG_OF_ALARMING_LEVEL_HEAP_USAGE_RATIO, 0.00f); + configs.put(CommonConstants.Accounting.CONFIG_OF_CRITICAL_LEVEL_HEAP_USAGE_RATIO, 0.00f); + configs.put(CommonConstants.Accounting.CONFIG_OF_FACTORY_NAME, + "org.apache.pinot.core.accounting.PerQueryCPUMemAccountantFactory"); + configs.put(CommonConstants.Accounting.CONFIG_OF_ENABLE_THREAD_MEMORY_SAMPLING, true); + configs.put(CommonConstants.Accounting.CONFIG_OF_ENABLE_THREAD_CPU_SAMPLING, false); + configs.put(CommonConstants.Accounting.CONFIG_OF_OOM_PROTECTION_KILLING_QUERY, true); + configs.put(CommonConstants.Accounting.CONFIG_OF_MIN_MEMORY_FOOTPRINT_TO_KILL_RATIO, 0.00f); + + PinotConfiguration config = getConfig(2, 2, configs); + ResourceManager rm = getResourceManager(2, 2, 1, 1, configs); + // init accountant and start watcher task + Tracing.ThreadAccountantOps.initializeThreadAccountant(config, "testJsonIndexExtractMapOOM"); + + Supplier randomJsonValue = () -> { + Random random = new Random(); + int length = random.nextInt(1000); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) { + sb.append((char) (random.nextInt(26) + 'a')); + } + return "{\"key\":\"" + sb + "\"}"; + }; + + File indexDir = new File(FileUtils.getTempDirectory(), "testJsonIndexExtractMapOOM"); + FileUtils.forceMkdir(indexDir); + String colName = "col"; + try (JsonIndexCreator offHeapIndexCreator = new OffHeapJsonIndexCreator(indexDir, colName, new JsonIndexConfig()); + MutableJsonIndexImpl mutableJsonIndex = new MutableJsonIndexImpl(new JsonIndexConfig())) { + // build json indexes + for (int i = 0; i < 1000000; i++) { + String val = randomJsonValue.get(); + offHeapIndexCreator.add(val); + mutableJsonIndex.add(val); + } + offHeapIndexCreator.seal(); + + CountDownLatch latch = new CountDownLatch(2); + AtomicBoolean mutableEarlyTerminationOccurred = new AtomicBoolean(false); + + // test mutable json index .getMatchingFlattenedDocsMap() + rm.getQueryRunners().submit(() -> { + Tracing.ThreadAccountantOps.setupRunner("testJsonExtractIndexId1"); + try { + mutableJsonIndex.getMatchingFlattenedDocsMap("key", null); + } catch (EarlyTerminationException e) { + mutableEarlyTerminationOccurred.set(true); + Tracing.ThreadAccountantOps.clear(); + } finally { + latch.countDown(); + } + }); + + // test immutable json index .getMatchingFlattenedDocsMap() + File indexFile = new File(indexDir, colName + V1Constants.Indexes.JSON_INDEX_FILE_EXTENSION); + AtomicBoolean immutableEarlyTerminationOccurred = new AtomicBoolean(false); + rm.getQueryRunners().submit(() -> { + Tracing.ThreadAccountantOps.setupRunner("testJsonExtractIndexId2"); + try { + try (PinotDataBuffer offHeapDataBuffer = PinotDataBuffer.mapReadOnlyBigEndianFile(indexFile); + ImmutableJsonIndexReader offHeapIndexReader = new ImmutableJsonIndexReader(offHeapDataBuffer, 1000000)) { + offHeapIndexReader.getMatchingFlattenedDocsMap("key", null); + } catch (IOException e) { + Assert.fail("failed .getMatchingFlattenedDocsMap for the immutable json index"); + } + } catch (EarlyTerminationException e) { + immutableEarlyTerminationOccurred.set(true); + Tracing.ThreadAccountantOps.clear(); + } finally { + latch.countDown(); + } + }); + + latch.await(); + Assert.assertTrue(mutableEarlyTerminationOccurred.get(), + "Expected early termination reading the mutable index"); + Assert.assertTrue(immutableEarlyTerminationOccurred.get(), + "Expected early termination reading the immutable index"); + } + } + /** * Test thread memory usage tracking and query killing in multi-thread environment, add @Test to run. */ @@ -307,7 +520,7 @@ public void testThreadMemory() Tracing.ThreadAccountantOps.sample(); if (Thread.interrupted() || thread.isInterrupted()) { Tracing.ThreadAccountantOps.clear(); - LOGGER.error("KilledWorker " + queryId + " " + finalJ); + LOGGER.error("KilledWorker {} {}", queryId, finalJ); return; } a[i] = new long[200000]; @@ -326,7 +539,7 @@ public void testThreadMemory() for (int i = 0; i < 10; i++) { futuresThread[i].cancel(true); } - LOGGER.error("Killed " + queryId); + LOGGER.error("Killed {}", queryId); } Tracing.ThreadAccountantOps.clear(); }); diff --git a/pinot-core/src/test/java/org/apache/pinot/core/auth/FineGrainedAuthUtilsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/auth/FineGrainedAuthUtilsTest.java new file mode 100644 index 000000000000..c18f30ecf64d --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/auth/FineGrainedAuthUtilsTest.java @@ -0,0 +1,94 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.auth; + +import java.lang.reflect.Method; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import org.mockito.Mockito; +import org.testng.Assert; +import org.testng.annotations.Test; + + +public class FineGrainedAuthUtilsTest { + + @Test + public void testValidateFineGrainedAuthAllowed() { + FineGrainedAccessControl ac = Mockito.mock(FineGrainedAccessControl.class); + Mockito.when(ac.hasAccess(Mockito.any(HttpHeaders.class), Mockito.any(), Mockito.any(), Mockito.any())) + .thenReturn(true); + + UriInfo mockUriInfo = Mockito.mock(UriInfo.class); + HttpHeaders mockHttpHeaders = Mockito.mock(HttpHeaders.class); + + FineGrainedAuthUtils.validateFineGrainedAuth(getAnnotatedMethod(), mockUriInfo, mockHttpHeaders, ac); + } + + @Test + public void testValidateFineGrainedAuthDenied() { + FineGrainedAccessControl ac = Mockito.mock(FineGrainedAccessControl.class); + Mockito.when(ac.hasAccess(Mockito.any(HttpHeaders.class), Mockito.any(), Mockito.any(), Mockito.any())) + .thenReturn(false); + + UriInfo mockUriInfo = Mockito.mock(UriInfo.class); + HttpHeaders mockHttpHeaders = Mockito.mock(HttpHeaders.class); + + try { + FineGrainedAuthUtils.validateFineGrainedAuth(getAnnotatedMethod(), mockUriInfo, mockHttpHeaders, ac); + Assert.fail("Expected WebApplicationException"); + } catch (WebApplicationException e) { + Assert.assertTrue(e.getMessage().contains("Access denied to getCluster in the cluster")); + Assert.assertEquals(e.getResponse().getStatus(), Response.Status.FORBIDDEN.getStatusCode()); + } + } + + @Test + public void testValidateFineGrainedAuthWithNoSuchMethodError() { + FineGrainedAccessControl ac = Mockito.mock(FineGrainedAccessControl.class); + Mockito.when(ac.hasAccess(Mockito.any(HttpHeaders.class), Mockito.any(), Mockito.any(), Mockito.any())) + .thenThrow(new NoSuchMethodError("Method not found")); + + UriInfo mockUriInfo = Mockito.mock(UriInfo.class); + HttpHeaders mockHttpHeaders = Mockito.mock(HttpHeaders.class); + + try { + FineGrainedAuthUtils.validateFineGrainedAuth(getAnnotatedMethod(), mockUriInfo, mockHttpHeaders, ac); + Assert.fail("Expected WebApplicationException"); + } catch (WebApplicationException e) { + Assert.assertTrue(e.getMessage().contains("Failed to check for access")); + Assert.assertEquals(e.getResponse().getStatus(), Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()); + } + } + + static class TestResource { + @Authorize(targetType = TargetType.CLUSTER, action = "getCluster") + void getCluster() { + } + } + + private Method getAnnotatedMethod() { + try { + return TestResource.class.getDeclaredMethod("getCluster"); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/common/ObjectSerDeUtilsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/common/ObjectSerDeUtilsTest.java index f22dbb73d69a..882348a68cf5 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/common/ObjectSerDeUtilsTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/common/ObjectSerDeUtilsTest.java @@ -19,22 +19,40 @@ package org.apache.pinot.core.common; import com.clearspring.analytics.stream.cardinality.HyperLogLog; +import com.dynatrace.hash4j.distinctcount.UltraLogLog; import com.tdunning.math.stats.TDigest; import it.unimi.dsi.fastutil.doubles.Double2LongOpenHashMap; import it.unimi.dsi.fastutil.doubles.DoubleArrayList; import it.unimi.dsi.fastutil.floats.Float2LongOpenHashMap; +import it.unimi.dsi.fastutil.floats.FloatArrayList; import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap; +import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.longs.Long2LongOpenHashMap; +import it.unimi.dsi.fastutil.longs.LongArrayList; +import it.unimi.dsi.fastutil.objects.ObjectArrayList; +import it.unimi.dsi.fastutil.objects.ObjectLinkedOpenHashSet; import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.PriorityQueue; import java.util.Random; -import org.apache.commons.lang.RandomStringUtils; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.datasketches.cpc.CpcSketch; +import org.apache.datasketches.theta.SetOperationBuilder; +import org.apache.datasketches.theta.Sketch; +import org.apache.datasketches.theta.Sketches; +import org.apache.datasketches.theta.UpdateSketch; +import org.apache.datasketches.tuple.aninteger.IntegerSketch; +import org.apache.datasketches.tuple.aninteger.IntegerSummary; +import org.apache.datasketches.tuple.aninteger.IntegerSummarySetOperations; import org.apache.pinot.core.query.aggregation.function.PercentileEstAggregationFunction; import org.apache.pinot.core.query.aggregation.function.PercentileTDigestAggregationFunction; +import org.apache.pinot.core.query.aggregation.function.funnel.FunnelStepEvent; import org.apache.pinot.segment.local.customobject.AvgPair; +import org.apache.pinot.segment.local.customobject.CpcSketchAccumulator; import org.apache.pinot.segment.local.customobject.DoubleLongPair; import org.apache.pinot.segment.local.customobject.FloatLongPair; import org.apache.pinot.segment.local.customobject.IntLongPair; @@ -42,11 +60,15 @@ import org.apache.pinot.segment.local.customobject.MinMaxRangePair; import org.apache.pinot.segment.local.customobject.QuantileDigest; import org.apache.pinot.segment.local.customobject.StringLongPair; +import org.apache.pinot.segment.local.customobject.ThetaSketchAccumulator; +import org.apache.pinot.segment.local.customobject.TupleIntSketchAccumulator; import org.apache.pinot.segment.local.customobject.ValueLongPair; +import org.apache.pinot.segment.local.utils.UltraLogLogUtils; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertThrows; +import static org.testng.Assert.assertTrue; public class ObjectSerDeUtilsTest { @@ -289,6 +311,25 @@ public void testTDigest() { assertEquals(actual.quantile(j / 100.0), expected.quantile(j / 100.0), 1e-5); } } + + // Try some custom compression values + List compressionFactorsToTest = Arrays.asList(10d, 200d, 500d, 1000d, 10000d); + for (double compressionFactor : compressionFactorsToTest) { + for (int i = 0; i < NUM_ITERATIONS; i++) { + TDigest expected = TDigest.createMergingDigest(compressionFactor); + int size = RANDOM.nextInt(100) + 1; + for (int j = 0; j < size; j++) { + expected.add(RANDOM.nextDouble()); + } + + byte[] bytes = ObjectSerDeUtils.serialize(expected); + TDigest actual = ObjectSerDeUtils.deserialize(bytes, ObjectSerDeUtils.ObjectType.TDigest); + + for (int j = 0; j <= 100; j++) { + assertEquals(actual.quantile(j / 100.0), expected.quantile(j / 100.0), 1e-5); + } + } + } } @Test @@ -354,4 +395,228 @@ public void testDouble2LongMap() { assertEquals(actual, expected, ERROR_MESSAGE); } } + + @Test + public void testCpcSketch() { + for (int i = 0; i < NUM_ITERATIONS; i++) { + CpcSketch sketch = new CpcSketch(); + int size = RANDOM.nextInt(100) + 1; + for (int j = 0; j < size; j++) { + sketch.update(RANDOM.nextLong()); + } + + byte[] bytes = ObjectSerDeUtils.serialize(sketch); + CpcSketch actual = + ObjectSerDeUtils.deserialize(bytes, ObjectSerDeUtils.ObjectType.CompressedProbabilisticCounting); + + assertEquals(actual.getEstimate(), sketch.getEstimate(), ERROR_MESSAGE); + } + } + + @Test + public void testIntArrayList() { + for (int i = 0; i < NUM_ITERATIONS; i++) { + int size = RANDOM.nextInt(100); + IntArrayList expected = new IntArrayList(size); + for (int j = 0; j < size; j++) { + expected.add(RANDOM.nextInt()); + } + byte[] bytes = ObjectSerDeUtils.serialize(expected); + IntArrayList actual = ObjectSerDeUtils.deserialize(bytes, ObjectSerDeUtils.ObjectType.IntArrayList); + assertEquals(actual, expected, ERROR_MESSAGE); + } + } + + @Test + public void testLongArrayList() { + for (int i = 0; i < NUM_ITERATIONS; i++) { + int size = RANDOM.nextInt(100); + LongArrayList expected = new LongArrayList(size); + for (int j = 0; j < size; j++) { + expected.add(RANDOM.nextLong()); + } + byte[] bytes = ObjectSerDeUtils.serialize(expected); + LongArrayList actual = ObjectSerDeUtils.deserialize(bytes, ObjectSerDeUtils.ObjectType.LongArrayList); + assertEquals(actual, expected, ERROR_MESSAGE); + } + } + + @Test + public void testFloatArrayList() { + for (int i = 0; i < NUM_ITERATIONS; i++) { + int size = RANDOM.nextInt(100); + FloatArrayList expected = new FloatArrayList(size); + for (int j = 0; j < size; j++) { + expected.add(RANDOM.nextFloat()); + } + byte[] bytes = ObjectSerDeUtils.serialize(expected); + FloatArrayList actual = ObjectSerDeUtils.deserialize(bytes, ObjectSerDeUtils.ObjectType.FloatArrayList); + assertEquals(actual, expected, ERROR_MESSAGE); + } + } + + @Test + public void testStringArrayList() { + for (int i = 0; i < NUM_ITERATIONS; i++) { + int size = RANDOM.nextInt(100); + ObjectArrayList expected = new ObjectArrayList<>(size); + for (int j = 0; j < size; j++) { + expected.add(RandomStringUtils.random(RANDOM.nextInt(20))); + } + byte[] bytes = ObjectSerDeUtils.serialize(expected); + ObjectArrayList actual = ObjectSerDeUtils.deserialize(bytes, ObjectSerDeUtils.ObjectType.StringArrayList); + assertEquals(actual, expected, ERROR_MESSAGE); + } + } + + @Test + public void testULL() { + for (int i = 0; i < NUM_ITERATIONS; i++) { + UltraLogLog ull = UltraLogLog.create(12); + int size = RANDOM.nextInt(100) + 1; + for (int j = 0; j < size; j++) { + UltraLogLogUtils.hashObject(RANDOM.nextLong()).ifPresent(ull::add); + } + + byte[] bytes = ObjectSerDeUtils.serialize(ull); + UltraLogLog actual = ObjectSerDeUtils.deserialize(bytes, ObjectSerDeUtils.ObjectType.UltraLogLog); + + assertEquals(actual.getDistinctCountEstimate(), ull.getDistinctCountEstimate(), ERROR_MESSAGE); + assertEquals(actual.getState(), ull.getState(), ERROR_MESSAGE); + } + } + + @Test + public void testThetaSketch() { + for (int i = 0; i < NUM_ITERATIONS; i++) { + UpdateSketch input = Sketches.updateSketchBuilder().build(); + int size = RANDOM.nextInt(100) + 10; + boolean shouldOrder = RANDOM.nextBoolean(); + + for (int j = 0; j < size; j++) { + input.update(j); + } + + Sketch sketch = input.compact(shouldOrder, null); + + byte[] bytes = ObjectSerDeUtils.serialize(sketch); + Sketch actual = ObjectSerDeUtils.deserialize(bytes, ObjectSerDeUtils.ObjectType.DataSketch); + + assertEquals(actual.getEstimate(), sketch.getEstimate(), ERROR_MESSAGE); + assertEquals(actual.toByteArray(), sketch.toByteArray(), ERROR_MESSAGE); + assertEquals(actual.isOrdered(), shouldOrder, ERROR_MESSAGE); + } + } + + @Test + public void testThetaSketchAccumulator() { + for (int i = 0; i < NUM_ITERATIONS; i++) { + UpdateSketch input = Sketches.updateSketchBuilder().build(); + int size = RANDOM.nextInt(100) + 10; + + for (int j = 0; j < size; j++) { + input.update(j); + } + + SetOperationBuilder setOperationBuilder = new SetOperationBuilder(); + ThetaSketchAccumulator accumulator = new ThetaSketchAccumulator(setOperationBuilder, 2); + Sketch sketch = input.compact(false, null); + accumulator.apply(sketch); + + byte[] bytes = ObjectSerDeUtils.serialize(accumulator); + ThetaSketchAccumulator actual = + ObjectSerDeUtils.deserialize(bytes, ObjectSerDeUtils.ObjectType.ThetaSketchAccumulator); + + assertEquals(actual.getResult().getEstimate(), sketch.getEstimate(), ERROR_MESSAGE); + assertEquals(actual.getResult().toByteArray(), sketch.toByteArray(), ERROR_MESSAGE); + } + } + + @Test + public void testTupleIntSketchAccumulator() { + for (int i = 0; i < NUM_ITERATIONS; i++) { + int lgK = 4; + int size = RANDOM.nextInt(100) + 10; + IntegerSketch input = new IntegerSketch(lgK, IntegerSummary.Mode.Sum); + + for (int j = 0; j < size; j++) { + input.update(j, RANDOM.nextInt(100)); + } + + IntegerSummarySetOperations setOps = + new IntegerSummarySetOperations(IntegerSummary.Mode.Sum, IntegerSummary.Mode.Sum); + TupleIntSketchAccumulator accumulator = new TupleIntSketchAccumulator(setOps, (int) Math.pow(2, lgK), 2); + org.apache.datasketches.tuple.Sketch sketch = input.compact(); + accumulator.apply(sketch); + + byte[] bytes = ObjectSerDeUtils.serialize(accumulator); + TupleIntSketchAccumulator actual = + ObjectSerDeUtils.deserialize(bytes, ObjectSerDeUtils.ObjectType.TupleIntSketchAccumulator); + + assertEquals(actual.getResult().getEstimate(), sketch.getEstimate(), ERROR_MESSAGE); + assertEquals(actual.getResult().toByteArray(), sketch.toByteArray(), ERROR_MESSAGE); + } + } + + @Test + public void testCpcSketchAccumulator() { + int lgK = 4; + for (int i = 0; i < NUM_ITERATIONS; i++) { + int size = RANDOM.nextInt(100) + 10; + CpcSketch sketch = new CpcSketch(lgK); + + for (int j = 0; j < size; j++) { + sketch.update(j); + } + + CpcSketchAccumulator accumulator = new CpcSketchAccumulator(lgK, 2); + accumulator.apply(sketch); + + byte[] bytes = ObjectSerDeUtils.serialize(accumulator); + CpcSketchAccumulator actual = + ObjectSerDeUtils.deserialize(bytes, ObjectSerDeUtils.ObjectType.CpcSketchAccumulator); + + assertEquals(actual.getResult().getEstimate(), sketch.getEstimate(), ERROR_MESSAGE); + assertEquals(actual.getResult().toByteArray(), sketch.toByteArray(), ERROR_MESSAGE); + } + } + + @Test + public void testOrderedStringSet() { + for (int i = 0; i < NUM_ITERATIONS; i++) { + int size = RANDOM.nextInt(100); + ObjectLinkedOpenHashSet expected = new ObjectLinkedOpenHashSet<>(size); + for (int j = 0; j < size; j++) { + expected.add(RandomStringUtils.random(RANDOM.nextInt(20))); + } + byte[] bytes = ObjectSerDeUtils.serialize(expected); + ObjectLinkedOpenHashSet actual = + ObjectSerDeUtils.deserialize(bytes, ObjectSerDeUtils.ObjectType.OrderedStringSet); + for (int j = 0; j < size; j++) { + assertEquals(actual.get(j), expected.get(j), ERROR_MESSAGE); + } + } + } + + @Test + public void testFunnelStepEventAccumulator() { + for (int i = 0; i < NUM_ITERATIONS; i++) { + int size = RANDOM.nextInt(1000); + PriorityQueue expected = new PriorityQueue<>(); + for (int j = 0; j < size; j++) { + expected.add(new FunnelStepEvent(RANDOM.nextLong(), RANDOM.nextInt())); + } + byte[] bytes = ObjectSerDeUtils.serialize(expected); + PriorityQueue actual = + ObjectSerDeUtils.deserialize(bytes, ObjectSerDeUtils.ObjectType.FunnelStepEventAccumulator); + while (!actual.isEmpty()) { + assertEquals(actual.poll(), expected.poll(), ERROR_MESSAGE); + } + } + // Test empty queue + PriorityQueue empty = new PriorityQueue<>(); + PriorityQueue deserialized = ObjectSerDeUtils.deserialize(ObjectSerDeUtils.serialize(empty), + ObjectSerDeUtils.ObjectType.FunnelStepEventAccumulator); + assertTrue(deserialized.isEmpty()); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/common/datablock/DataBlockTest.java b/pinot-core/src/test/java/org/apache/pinot/core/common/datablock/DataBlockTest.java index d71260aa8bfb..db3945a10a4b 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/common/datablock/DataBlockTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/common/datablock/DataBlockTest.java @@ -20,6 +20,7 @@ import com.google.common.collect.ImmutableList; import java.io.IOException; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import org.apache.pinot.common.datablock.ColumnarDataBlock; @@ -29,15 +30,15 @@ import org.apache.pinot.common.exception.QueryException; import org.apache.pinot.common.response.ProcessingException; import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class DataBlockTest { - private static final List EXCLUDE_DATA_TYPES = ImmutableList.of( - DataSchema.ColumnDataType.OBJECT, DataSchema.ColumnDataType.JSON, DataSchema.ColumnDataType.BYTES, - DataSchema.ColumnDataType.BYTES_ARRAY); + private static final List EXCLUDE_DATA_TYPES = + ImmutableList.of(ColumnDataType.OBJECT, ColumnDataType.JSON, ColumnDataType.BYTES, ColumnDataType.BYTES_ARRAY); private static final int TEST_ROW_COUNT = 5; @Test @@ -55,16 +56,15 @@ public void testException() // Assert processing exception and original exception both matches. String actual = dataBlock.getExceptions().get(QueryException.QUERY_EXECUTION_ERROR.getErrorCode()); Assert.assertEquals(actual, expected); - Assert.assertTrue(dataBlock.getExceptions().get(QueryException.UNKNOWN_ERROR_CODE) - .contains(originalException.getMessage())); + Assert.assertTrue( + dataBlock.getExceptions().get(QueryException.UNKNOWN_ERROR_CODE).contains(originalException.getMessage())); } @Test(dataProvider = "testTypeNullPercentile") public void testAllDataTypes(int nullPercentile) throws Exception { - - DataSchema.ColumnDataType[] allDataTypes = DataSchema.ColumnDataType.values(); - List columnDataTypes = new ArrayList(); + ColumnDataType[] allDataTypes = ColumnDataType.values(); + List columnDataTypes = new ArrayList(); List columnNames = new ArrayList(); for (int i = 0; i < allDataTypes.length; i++) { if (!EXCLUDE_DATA_TYPES.contains(allDataTypes[i])) { @@ -73,20 +73,21 @@ public void testAllDataTypes(int nullPercentile) } } - DataSchema dataSchema = new DataSchema(columnNames.toArray(new String[]{}), - columnDataTypes.toArray(new DataSchema.ColumnDataType[]{})); + DataSchema dataSchema = + new DataSchema(columnNames.toArray(new String[]{}), columnDataTypes.toArray(new ColumnDataType[]{})); List rows = DataBlockTestUtils.getRandomRows(dataSchema, TEST_ROW_COUNT, nullPercentile); List columnars = DataBlockTestUtils.convertColumnar(dataSchema, rows); RowDataBlock rowBlock = DataBlockBuilder.buildFromRows(rows, dataSchema); ColumnarDataBlock columnarBlock = DataBlockBuilder.buildFromColumns(columnars, dataSchema); for (int colId = 0; colId < dataSchema.getColumnNames().length; colId++) { - DataSchema.ColumnDataType columnDataType = dataSchema.getColumnDataType(colId); + ColumnDataType columnDataType = dataSchema.getColumnDataType(colId); for (int rowId = 0; rowId < TEST_ROW_COUNT; rowId++) { Object rowVal = DataBlockTestUtils.getElement(rowBlock, rowId, colId, columnDataType); Object colVal = DataBlockTestUtils.getElement(columnarBlock, rowId, colId, columnDataType); - Assert.assertEquals(rowVal, colVal, "Error comparing Row/Column Block at (" + rowId + "," + colId + ")" - + " of Type: " + columnDataType + "! rowValue: [" + rowVal + "], columnarValue: [" + colVal + "]"); + Assert.assertEquals(rowVal, colVal, + "Error comparing Row/Column Block at (" + rowId + "," + colId + ")" + " of Type: " + columnDataType + + "! rowValue: [" + rowVal + "], columnarValue: [" + colVal + "]"); } } } @@ -95,4 +96,32 @@ public void testAllDataTypes(int nullPercentile) public Object[][] provideTestTypeNullPercentile() { return new Object[][]{new Object[]{0}, new Object[]{10}, new Object[]{100}}; } + + /** + * TODO: bytes array serialization probably needs fixing. + */ + @Test(expectedExceptions = IllegalStateException.class, expectedExceptionsMessageRegExp = "Unsupported stored type:" + + " BYTES_ARRAY.*") + public void bytesArraySerDe() + throws Exception { + Object[] row = new Object[1]; + row[0] = new byte[][]{new byte[]{0xD, 0xA}, new byte[]{0xD, 0xA}}; + List rows = new ArrayList<>(); + rows.add(row); + DataSchema dataSchema = new DataSchema(new String[]{"byteArray"}, new ColumnDataType[]{ColumnDataType.BYTES_ARRAY}); + DataBlockBuilder.buildFromRows(rows, dataSchema); + } + + @Test + public void intArraySerDe() + throws IOException { + Object[] row = new Object[1]; + row[0] = new int[0]; + List rows = new ArrayList<>(); + rows.add(row); + DataSchema dataSchema = new DataSchema(new String[]{"intArray"}, new ColumnDataType[]{ColumnDataType.INT_ARRAY}); + DataBlock dataBlock = DataBlockBuilder.buildFromRows(rows, dataSchema); + int[] intArray = DataBlockUtils.getDataBlock(ByteBuffer.wrap(dataBlock.toBytes())).getIntArray(0, 0); + Assert.assertEquals(intArray.length, 0); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/common/datablock/DataBlockTestUtils.java b/pinot-core/src/test/java/org/apache/pinot/core/common/datablock/DataBlockTestUtils.java index 67cc82cba6d0..a42a3ff4a396 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/common/datablock/DataBlockTestUtils.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/common/datablock/DataBlockTestUtils.java @@ -19,13 +19,13 @@ package org.apache.pinot.core.common.datablock; import java.math.BigDecimal; -import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; import java.util.Random; -import org.apache.commons.lang.RandomStringUtils; +import org.apache.commons.lang3.RandomStringUtils; import org.apache.pinot.common.datablock.DataBlock; import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.spi.utils.ByteArray; import org.roaringbitmap.RoaringBitmap; @@ -40,7 +40,7 @@ private DataBlockTestUtils() { public static Object[] getRandomRow(DataSchema dataSchema, int nullPercentile) { final int numColumns = dataSchema.getColumnNames().length; - DataSchema.ColumnDataType[] columnDataTypes = dataSchema.getColumnDataTypes(); + ColumnDataType[] columnDataTypes = dataSchema.getColumnDataTypes(); Object[] row = new Object[numColumns]; for (int colId = 0; colId < numColumns; colId++) { switch (columnDataTypes[colId]) { @@ -60,10 +60,10 @@ public static Object[] getRandomRow(DataSchema dataSchema, int nullPercentile) { row[colId] = BigDecimal.valueOf(RANDOM.nextDouble()); break; case BOOLEAN: - row[colId] = RANDOM.nextBoolean(); + row[colId] = RANDOM.nextBoolean() ? 1 : 0; break; case TIMESTAMP: - row[colId] = new Timestamp(RANDOM.nextLong()); + row[colId] = RANDOM.nextLong(); break; case STRING: row[colId] = RandomStringUtils.random(RANDOM.nextInt(20)); @@ -113,33 +113,35 @@ public static Object[] getRandomRow(DataSchema dataSchema, int nullPercentile) { break; case BOOLEAN_ARRAY: length = RANDOM.nextInt(ARRAY_SIZE); - boolean[] booleanArray = new boolean[length]; + int[] booleanArray = new int[length]; for (int i = 0; i < length; i++) { - booleanArray[i] = RANDOM.nextBoolean(); + booleanArray[i] = RANDOM.nextBoolean() ? 1 : 0; } row[colId] = booleanArray; break; case TIMESTAMP_ARRAY: length = RANDOM.nextInt(ARRAY_SIZE); - Timestamp[] timestampArray = new Timestamp[length]; + long[] timestampArray = new long[length]; for (int i = 0; i < length; i++) { - timestampArray[i] = new Timestamp(RANDOM.nextLong()); + timestampArray[i] = RANDOM.nextLong(); } row[colId] = timestampArray; break; + case UNKNOWN: + row[colId] = null; + break; default: throw new UnsupportedOperationException("Can't fill random data for column type: " + columnDataTypes[colId]); } // randomly set some entry to null - if (columnDataTypes[colId].getStoredType() != DataSchema.ColumnDataType.OBJECT) { + if (columnDataTypes[colId].getStoredType() != ColumnDataType.OBJECT) { row[colId] = randomlySettingNull(nullPercentile) ? null : row[colId]; } } return row; } - public static Object getElement(DataBlock dataBlock, int rowId, int colId, - DataSchema.ColumnDataType columnDataType) { + public static Object getElement(DataBlock dataBlock, int rowId, int colId, ColumnDataType columnDataType) { RoaringBitmap nullBitmap = dataBlock.getNullRowIds(colId); if (nullBitmap != null) { if (nullBitmap.contains(rowId)) { @@ -161,10 +163,8 @@ public static Object getElement(DataBlock dataBlock, int rowId, int colId, return dataBlock.getString(rowId, colId); case BYTES: return dataBlock.getBytes(rowId, colId); - case BOOLEAN_ARRAY: case INT_ARRAY: return dataBlock.getIntArray(rowId, colId); - case TIMESTAMP_ARRAY: case LONG_ARRAY: return dataBlock.getLongArray(rowId, colId); case FLOAT_ARRAY: @@ -173,6 +173,8 @@ public static Object getElement(DataBlock dataBlock, int rowId, int colId, return dataBlock.getDoubleArray(rowId, colId); case STRING_ARRAY: return dataBlock.getStringArray(rowId, colId); + case UNKNOWN: + return null; default: throw new UnsupportedOperationException("Can't retrieve data for column type: " + columnDataType); } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/common/datatable/DataTableSerDeTest.java b/pinot-core/src/test/java/org/apache/pinot/core/common/datatable/DataTableSerDeTest.java index 22b0e9263034..91f1df4fa771 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/common/datatable/DataTableSerDeTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/common/datatable/DataTableSerDeTest.java @@ -29,8 +29,9 @@ import java.util.Arrays; import java.util.Map; import java.util.Random; -import org.apache.commons.lang.RandomStringUtils; +import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.pinot.common.CustomObject; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.datatable.DataTable.MetadataKey; import org.apache.pinot.common.datatable.DataTableFactory; @@ -122,36 +123,143 @@ public void testException(int dataTableVersion) } @Test(dataProvider = "versionProvider") - public void testEmptyStrings(int dataTableVersion) + public void testEmptyValues(int dataTableVersion) throws IOException { DataTableBuilderFactory.setDataTableVersion(dataTableVersion); String emptyString = StringUtils.EMPTY; String[] emptyStringArray = {StringUtils.EMPTY}; + ByteArray emptyBytes = new ByteArray(new byte[0]); + for (int numRows = 0; numRows < NUM_ROWS; numRows++) { + testEmptyValues(new DataSchema(new String[]{"STR_SV", "STR_MV"}, new DataSchema.ColumnDataType[]{ + DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.STRING_ARRAY + }), numRows, new Object[]{emptyString, emptyStringArray}); + + testEmptyValues( + new DataSchema(new String[]{"STR_SV"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.STRING}), + numRows, new Object[]{emptyString}); + + testEmptyValues(new DataSchema(new String[]{"STR_MV"}, + new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.STRING_ARRAY}), numRows, + new Object[]{emptyStringArray}); + + testEmptyValues( + new DataSchema(new String[]{"BYTES"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.BYTES}), + numRows, new Object[]{emptyBytes}); + + testEmptyValues( + new DataSchema(new String[]{"BOOL_ARR"}, + new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.BOOLEAN_ARRAY}), + numRows, new Object[]{new int[]{}}); + + testEmptyValues( + new DataSchema(new String[]{"BOOL_ARR"}, + new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.BOOLEAN_ARRAY}), + numRows, new Object[]{new int[]{0}}); + + testEmptyValues( + new DataSchema(new String[]{"INT_ARR"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT_ARRAY}), + numRows, new Object[]{new int[]{}}); + + testEmptyValues( + new DataSchema(new String[]{"INT_ARR"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT_ARRAY}), + numRows, new Object[]{new int[]{0}}); + + testEmptyValues(new DataSchema(new String[]{"LONG_ARR"}, + new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.LONG_ARRAY}), numRows, new Object[]{new long[]{}}); + + testEmptyValues(new DataSchema(new String[]{"LONG_ARR"}, + new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.LONG_ARRAY}), numRows, new Object[]{new long[]{0}}); + + testEmptyValues(new DataSchema(new String[]{"FLOAT_ARR"}, + new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.FLOAT_ARRAY}), numRows, + new Object[]{new float[]{}}); + + testEmptyValues(new DataSchema(new String[]{"FLOAT_ARR"}, + new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.FLOAT_ARRAY}), numRows, + new Object[]{new float[]{0}}); + + testEmptyValues(new DataSchema(new String[]{"DOUBLE_ARR"}, + new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.DOUBLE_ARRAY}), numRows, + new Object[]{new double[]{}}); + + testEmptyValues(new DataSchema(new String[]{"DOUBLE_ARR"}, + new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.DOUBLE_ARRAY}), numRows, + new Object[]{new double[]{0}}); + } + DataTableBuilderFactory.setDataTableVersion(DataTableBuilderFactory.DEFAULT_VERSION); + } + + private void testEmptyValues(DataSchema dataSchema, int numRows, Object[] emptyValues) + throws IOException { - DataSchema dataSchema = new DataSchema(new String[]{"SV", "MV"}, - new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.STRING_ARRAY}); DataTableBuilder dataTableBuilder = DataTableBuilderFactory.getDataTableBuilder(dataSchema); - for (int rowId = 0; rowId < NUM_ROWS; rowId++) { + for (int rowId = 0; rowId < numRows; rowId++) { dataTableBuilder.startRow(); - dataTableBuilder.setColumn(0, emptyString); - dataTableBuilder.setColumn(1, emptyStringArray); + for (int columnId = 0; columnId < dataSchema.size(); columnId++) { + Object emptyValue = emptyValues[columnId]; + if (emptyValue instanceof int[]) { + dataTableBuilder.setColumn(columnId, (int[]) emptyValue); + } else if (emptyValue instanceof long[]) { + dataTableBuilder.setColumn(columnId, (long[]) emptyValue); + } else if (emptyValue instanceof float[]) { + dataTableBuilder.setColumn(columnId, (float[]) emptyValue); + } else if (emptyValue instanceof double[]) { + dataTableBuilder.setColumn(columnId, (double[]) emptyValue); + } else if (emptyValue instanceof String[]) { + dataTableBuilder.setColumn(columnId, (String[]) emptyValue); + } else if (emptyValue instanceof String) { + dataTableBuilder.setColumn(columnId, (String) emptyValue); + } else if (emptyValue instanceof ByteArray) { + dataTableBuilder.setColumn(columnId, (ByteArray) emptyValue); + } else { + dataTableBuilder.setColumn(columnId, emptyValue); + } + } dataTableBuilder.finishRow(); } DataTable dataTable = dataTableBuilder.build(); DataTable newDataTable = DataTableFactory.getDataTable(dataTable.toBytes()); Assert.assertEquals(newDataTable.getDataSchema(), dataSchema); - Assert.assertEquals(newDataTable.getNumberOfRows(), NUM_ROWS); + Assert.assertEquals(newDataTable.getNumberOfRows(), numRows); - for (int rowId = 0; rowId < NUM_ROWS; rowId++) { - Assert.assertEquals(newDataTable.getString(rowId, 0), emptyString); - Assert.assertEquals(newDataTable.getStringArray(rowId, 1), emptyStringArray); + for (int rowId = 0; rowId < numRows; rowId++) { + for (int columnId = 0; columnId < dataSchema.size(); columnId++) { + Object entry; + switch (dataSchema.getColumnDataType(columnId)) { + case BOOLEAN_ARRAY: + case INT_ARRAY: + entry = newDataTable.getIntArray(rowId, columnId); + break; + case LONG_ARRAY: + entry = newDataTable.getLongArray(rowId, columnId); + break; + case FLOAT_ARRAY: + entry = newDataTable.getFloatArray(rowId, columnId); + break; + case DOUBLE_ARRAY: + entry = newDataTable.getDoubleArray(rowId, columnId); + break; + case STRING_ARRAY: + entry = newDataTable.getStringArray(rowId, columnId); + break; + case STRING: + entry = newDataTable.getString(rowId, columnId); + break; + case BYTES: + entry = newDataTable.getBytes(rowId, columnId); + break; + default: + entry = newDataTable.getCustomObject(rowId, columnId); + break; + } + Assert.assertEquals(entry, emptyValues[columnId]); + } } - DataTableBuilderFactory.setDataTableVersion(DataTableBuilderFactory.DEFAULT_VERSION); } @Test(dataProvider = "versionProvider") - public void testAllDataTypes(int dataTableVersion) + public void testAllDataTypesInOneSchema(int dataTableVersion) throws IOException { DataTableBuilderFactory.setDataTableVersion(dataTableVersion); DataSchema.ColumnDataType[] columnDataTypes = DataSchema.ColumnDataType.values(); @@ -173,6 +281,29 @@ public void testAllDataTypes(int dataTableVersion) DataTableBuilderFactory.setDataTableVersion(DataTableBuilderFactory.DEFAULT_VERSION); } + @Test(dataProvider = "versionProvider") + public void testAllDataTypes(int dataTableVersion) + throws IOException { + DataTableBuilderFactory.setDataTableVersion(dataTableVersion); + DataSchema.ColumnDataType[] columnDataTypes = DataSchema.ColumnDataType.values(); + int numColumns = columnDataTypes.length; + for (int i = 0; i < numColumns; i++) { + String[] columnName = new String[]{columnDataTypes[i].name()}; + DataSchema.ColumnDataType[] columnDataType = new DataSchema.ColumnDataType[]{columnDataTypes[i]}; + DataSchema dataSchema = new DataSchema(columnName, columnDataType); + for (int numRows = 0; numRows < NUM_ROWS; numRows++) { + DataTableBuilder dataTableBuilder = DataTableBuilderFactory.getDataTableBuilder(dataSchema); + fillDataTableWithRandomData(dataTableBuilder, columnDataType, 1, numRows); + DataTable dataTable = dataTableBuilder.build(); + DataTable newDataTable = DataTableFactory.getDataTable(dataTable.toBytes()); + Assert.assertEquals(newDataTable.getDataSchema(), dataSchema, ERROR_MESSAGE); + Assert.assertEquals(newDataTable.getNumberOfRows(), numRows, ERROR_MESSAGE); + verifyDataIsSame(newDataTable, columnDataType, 1, numRows); + } + } + DataTableBuilderFactory.setDataTableVersion(DataTableBuilderFactory.DEFAULT_VERSION); + } + @Test(dataProvider = "versionProvider") public void testExecutionThreadCpuTimeNs(int dataTableVersion) throws IOException { @@ -552,6 +683,12 @@ public void testDataTableVer3MetadataBytesLayout() private void fillDataTableWithRandomData(DataTableBuilder dataTableBuilder, DataSchema.ColumnDataType[] columnDataTypes, int numColumns) throws IOException { + fillDataTableWithRandomData(dataTableBuilder, columnDataTypes, numColumns, NUM_ROWS); + } + + private void fillDataTableWithRandomData(DataTableBuilder dataTableBuilder, + DataSchema.ColumnDataType[] columnDataTypes, int numColumns, int numRows) + throws IOException { RoaringBitmap[] nullBitmaps = null; if (DataTableBuilderFactory.getDataTableVersion() >= DataTableFactory.VERSION_4) { nullBitmaps = new RoaringBitmap[numColumns]; @@ -559,7 +696,7 @@ private void fillDataTableWithRandomData(DataTableBuilder dataTableBuilder, nullBitmaps[colId] = new RoaringBitmap(); } } - for (int rowId = 0; rowId < NUM_ROWS; rowId++) { + for (int rowId = 0; rowId < numRows; rowId++) { dataTableBuilder.startRow(); for (int colId = 0; colId < numColumns; colId++) { // Note: isNull is handled for SV columns only for now. @@ -679,6 +816,9 @@ private void fillDataTableWithRandomData(DataTableBuilder dataTableBuilder, STRING_ARRAYS[rowId] = stringArray; dataTableBuilder.setColumn(colId, stringArray); break; + case UNKNOWN: + dataTableBuilder.setColumn(colId, (Object) null); + break; default: throw new UnsupportedOperationException("Unable to generate random data for: " + columnDataTypes[colId]); } @@ -693,11 +833,16 @@ private void fillDataTableWithRandomData(DataTableBuilder dataTableBuilder, } private void verifyDataIsSame(DataTable newDataTable, DataSchema.ColumnDataType[] columnDataTypes, int numColumns) { + verifyDataIsSame(newDataTable, columnDataTypes, numColumns, NUM_ROWS); + } + + private void verifyDataIsSame(DataTable newDataTable, DataSchema.ColumnDataType[] columnDataTypes, int numColumns, + int numRows) { RoaringBitmap[] nullBitmaps = new RoaringBitmap[numColumns]; for (int colId = 0; colId < numColumns; colId++) { nullBitmaps[colId] = newDataTable.getNullRowIds(colId); } - for (int rowId = 0; rowId < NUM_ROWS; rowId++) { + for (int rowId = 0; rowId < numRows; rowId++) { for (int colId = 0; colId < numColumns; colId++) { boolean isNull = nullBitmaps[colId] != null && nullBitmaps[colId].contains(rowId); switch (columnDataTypes[colId]) { @@ -734,7 +879,7 @@ private void verifyDataIsSame(DataTable newDataTable, DataSchema.ColumnDataType[ ERROR_MESSAGE); break; case OBJECT: - DataTable.CustomObject customObject = newDataTable.getCustomObject(rowId, colId); + CustomObject customObject = newDataTable.getCustomObject(rowId, colId); if (isNull) { Assert.assertNull(customObject, ERROR_MESSAGE); } else { @@ -772,6 +917,10 @@ private void verifyDataIsSame(DataTable newDataTable, DataSchema.ColumnDataType[ Assert.assertTrue(Arrays.equals(newDataTable.getStringArray(rowId, colId), STRING_ARRAYS[rowId]), ERROR_MESSAGE); break; + case UNKNOWN: + Object nulValue = newDataTable.getCustomObject(rowId, colId); + Assert.assertNull(nulValue, ERROR_MESSAGE); + break; default: throw new UnsupportedOperationException("Unable to generate random data for: " + columnDataTypes[colId]); } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/common/evaluators/DefaultJsonPathEvaluatorTest.java b/pinot-core/src/test/java/org/apache/pinot/core/common/evaluators/DefaultJsonPathEvaluatorTest.java new file mode 100644 index 000000000000..c7b304667c3b --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/common/evaluators/DefaultJsonPathEvaluatorTest.java @@ -0,0 +1,146 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.common.evaluators; + +import java.nio.charset.StandardCharsets; +import org.apache.pinot.segment.spi.evaluator.json.JsonPathEvaluator; +import org.apache.pinot.segment.spi.index.reader.ForwardIndexReader; +import org.apache.pinot.segment.spi.index.reader.ForwardIndexReaderContext; +import org.apache.pinot.spi.data.FieldSpec; +import org.testng.annotations.Test; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals; + + +public class DefaultJsonPathEvaluatorTest { + @Test + public void testNonDictIntegerArray() { + String json = "{\"values\": [1, 2, 3, 4, 5]}"; + String path = "$.values[0:3]"; + JsonPathEvaluator evaluator = DefaultJsonPathEvaluator.create(path, new int[]{}); + ForwardIndexReader reader = mock(ForwardIndexReader.class); + when(reader.isDictionaryEncoded()).thenReturn(false); + when(reader.getBytes(eq(0), any())).thenReturn(json.getBytes(StandardCharsets.UTF_8)); + when(reader.getStoredType()).thenReturn(FieldSpec.DataType.STRING); + when(reader.createContext()).thenReturn(null); + + // Read as ints + int[][] buffer = new int[1][3]; + evaluator.evaluateBlock(new int[]{0}, 1, reader, null, null, null, buffer); + assertArrayEquals(buffer, new int[][]{{1, 2, 3}}); + + // Read as longs + long[][] longBuffer = new long[1][3]; + evaluator.evaluateBlock(new int[]{0}, 1, reader, null, null, null, longBuffer); + assertArrayEquals(longBuffer, new long[][]{{1, 2, 3}}); + + // Read as floats + float[][] floatBuffer = new float[1][3]; + evaluator.evaluateBlock(new int[]{0}, 1, reader, null, null, null, floatBuffer); + assertArrayEquals(floatBuffer, new float[][]{{1.0f, 2.0f, 3.0f}}); + + // Read as doubles + double[][] doubleBuffer = new double[1][3]; + evaluator.evaluateBlock(new int[]{0}, 1, reader, null, null, null, doubleBuffer); + assertArrayEquals(doubleBuffer, new double[][]{{1.0, 2.0, 3.0}}); + + // Read as strings + String[][] stringBuffer = new String[1][3]; + evaluator.evaluateBlock(new int[]{0}, 1, reader, null, null, null, stringBuffer); + assertArrayEquals(stringBuffer, new String[][]{{"1", "2", "3"}}); + } + + @Test + public void testNonDictStringArray() { + String json = "{\"values\": [\"1\", \"2\", \"3\", \"4\", \"5\"]}"; + String path = "$.values[0:3]"; + JsonPathEvaluator evaluator = DefaultJsonPathEvaluator.create(path, new int[]{}); + ForwardIndexReader reader = mock(ForwardIndexReader.class); + when(reader.isDictionaryEncoded()).thenReturn(false); + when(reader.getBytes(eq(0), any())).thenReturn(json.getBytes(StandardCharsets.UTF_8)); + when(reader.getStoredType()).thenReturn(FieldSpec.DataType.STRING); + when(reader.createContext()).thenReturn(null); + + // Read as ints + int[][] buffer = new int[1][3]; + evaluator.evaluateBlock(new int[]{0}, 1, reader, null, null, null, buffer); + assertArrayEquals(buffer, new int[][]{{1, 2, 3}}); + + // Read as longs + long[][] longBuffer = new long[1][3]; + evaluator.evaluateBlock(new int[]{0}, 1, reader, null, null, null, longBuffer); + assertArrayEquals(longBuffer, new long[][]{{1, 2, 3}}); + + // Read as floats + float[][] floatBuffer = new float[1][3]; + evaluator.evaluateBlock(new int[]{0}, 1, reader, null, null, null, floatBuffer); + assertArrayEquals(floatBuffer, new float[][]{{1.0f, 2.0f, 3.0f}}); + + // Read as doubles + double[][] doubleBuffer = new double[1][3]; + evaluator.evaluateBlock(new int[]{0}, 1, reader, null, null, null, doubleBuffer); + assertArrayEquals(doubleBuffer, new double[][]{{1.0, 2.0, 3.0}}); + + // Read as strings + String[][] stringBuffer = new String[1][3]; + evaluator.evaluateBlock(new int[]{0}, 1, reader, null, null, null, stringBuffer); + assertArrayEquals(stringBuffer, new String[][]{{"1", "2", "3"}}); + } + + @Test + public void testNonDictDoubleArray() { + String json = "{\"values\": [1.0, 2.0, 3.0, 4.0, 5.0]}"; + String path = "$.values[0:3]"; + JsonPathEvaluator evaluator = DefaultJsonPathEvaluator.create(path, new int[]{}); + ForwardIndexReader reader = mock(ForwardIndexReader.class); + when(reader.isDictionaryEncoded()).thenReturn(false); + when(reader.getBytes(eq(0), any())).thenReturn(json.getBytes(StandardCharsets.UTF_8)); + when(reader.getStoredType()).thenReturn(FieldSpec.DataType.STRING); + when(reader.createContext()).thenReturn(null); + + // Read as ints + int[][] buffer = new int[1][3]; + evaluator.evaluateBlock(new int[]{0}, 1, reader, null, null, null, buffer); + assertArrayEquals(buffer, new int[][]{{1, 2, 3}}); + + // Read as longs + long[][] longBuffer = new long[1][3]; + evaluator.evaluateBlock(new int[]{0}, 1, reader, null, null, null, longBuffer); + assertArrayEquals(longBuffer, new long[][]{{1, 2, 3}}); + + // Read as floats + float[][] floatBuffer = new float[1][3]; + evaluator.evaluateBlock(new int[]{0}, 1, reader, null, null, null, floatBuffer); + assertArrayEquals(floatBuffer, new float[][]{{1.0f, 2.0f, 3.0f}}); + + // Read as doubles + double[][] doubleBuffer = new double[1][3]; + evaluator.evaluateBlock(new int[]{0}, 1, reader, null, null, null, doubleBuffer); + assertArrayEquals(doubleBuffer, new double[][]{{1.0, 2.0, 3.0}}); + + // Read as strings + String[][] stringBuffer = new String[1][3]; + evaluator.evaluateBlock(new int[]{0}, 1, reader, null, null, null, stringBuffer); + assertArrayEquals(stringBuffer, new String[][]{{"1.0", "2.0", "3.0"}}); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/data/function/DateTimeFunctionsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/data/function/DateTimeFunctionsTest.java index ad24fefcc957..a5f805ed5c29 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/data/function/DateTimeFunctionsTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/data/function/DateTimeFunctionsTest.java @@ -19,11 +19,13 @@ package org.apache.pinot.core.data.function; import com.google.common.collect.Lists; +import java.sql.Timestamp; import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import org.apache.pinot.common.function.scalar.DateTimeFunctions; import org.apache.pinot.segment.local.function.InbuiltFunctionEvaluator; import org.apache.pinot.spi.data.readers.GenericRow; import org.joda.time.DateTime; @@ -58,6 +60,7 @@ public void testDateTimeFunctions(String functionExpression, List expect testFunction(functionExpression, expectedArguments, row, expectedResult); } + @DataProvider(name = "dateTimeFunctionsDataProvider") public Object[][] dateTimeFunctionsDataProvider() { List inputs = new ArrayList<>(); @@ -241,6 +244,14 @@ public Object[][] dateTimeFunctionsDataProvider() { "fromDateTime(dateTime, 'yyyy-MM-dd''T''HH:mm:ss.SSS''Z''')", Lists.newArrayList("dateTime"), row113, null }); + // fromDateTime with malformed dateTime and default Value should return -1 + GenericRow row114 = new GenericRow(); + row114.putValue("dateTime", "malformed_string"); + inputs.add(new Object[]{ + "fromDateTime(dateTime, 'yyyy-MM-dd''T''HH:mm:ss.SSS''Z''', 'UTC', -1)", Lists.newArrayList("dateTime"), + row114, -1L + }); + // timezone_hour and timezone_minute List expectedArguments = Collections.singletonList("tz"); GenericRow row120 = new GenericRow(); @@ -346,10 +357,14 @@ public void testDateTrunc() { row.putValue("epochMillis", 1612296732123L); List arguments = Lists.newArrayList("epochMillis"); - // name variations + // Standard SQL + testFunction("date_trunc(second, epochMillis)", arguments, row, 1612296732000L); + testFunction("DATE_TRUNC(MINUTE, epochMillis)", arguments, row, 1612296720000L); + + // Name variations testFunction("datetrunc('millisecond', epochMillis, 'MILLISECONDS')", arguments, row, 1612296732123L); testFunction("date_trunc('MILLISECOND', epochMillis, 'MILLISECONDS')", arguments, row, 1612296732123L); - testFunction("dateTrunc('millisecond', epochMillis, 'MILLISECONDS')", arguments, row, 1612296732123L); + testFunction("dateTrunc('millisecond', epochMillis, 'milliseconds')", arguments, row, 1612296732123L); testFunction("DATE_TRUNC('SECOND', epochMillis, 'MILLISECONDS')", arguments, row, 1612296732000L); // MILLISECONDS to various @@ -399,6 +414,8 @@ public void testDateTrunc() { DateTime result = WEIRD_TIMESTAMP; testFunction("datetrunc('millisecond', epochMillis, 'MILLISECONDS', '" + weirdDateTimeZoneid + "')", arguments, row, result.getMillis()); + testFunction("datetrunc('millisecond', epochMillis, 'MILLISECONDS', '" + weirdDateTimeZoneid + "', 'milliseconds')", + arguments, row, result.getMillis()); result = result.withMillisOfSecond(0); testFunction("datetrunc('second', epochMillis, 'MILLISECONDS', '" + weirdDateTimeZoneid + "')", arguments, row, result.getMillis()); @@ -418,6 +435,48 @@ private static long iso8601ToUtcEpochMillis(String iso8601) { return formatter.parseDateTime(iso8601).getMillis(); } + @Test + public void testDateBin() { + assertEquals(DateTimeFunctions.dateBin("2s", Timestamp.valueOf("2024-02-10 23:29:55.0"), + Timestamp.valueOf("2024-01-01 00:00:00.0")), Timestamp.valueOf("2024-02-10 23:29:54.0")); + + assertEquals(DateTimeFunctions.dateBin("10s", Timestamp.valueOf("2024-02-10 23:29:55.0"), + Timestamp.valueOf("2024-01-01 00:00:00.0")), Timestamp.valueOf("2024-02-10 23:29:50.0")); + + assertEquals(DateTimeFunctions.dateBin("10m", Timestamp.valueOf("2024-02-10 23:29:55.0"), + Timestamp.valueOf("2024-01-01 00:00:00.0")), Timestamp.valueOf("2024-02-10 23:20:00.0")); + + assertEquals(DateTimeFunctions.dateBin("15m", Timestamp.valueOf("2024-02-10 23:29:55.0"), + Timestamp.valueOf("2024-01-01 00:00:00.0")), Timestamp.valueOf("2024-02-10 23:15:00.0")); + + assertEquals(DateTimeFunctions.dateBin("20m", Timestamp.valueOf("2024-02-10 23:29:55.0"), + Timestamp.valueOf("2024-01-01 00:00:00.0")), Timestamp.valueOf("2024-02-10 23:20:00.0")); + + assertEquals(DateTimeFunctions.dateBin("30m", Timestamp.valueOf("2024-02-10 23:00:55.0"), + Timestamp.valueOf("2024-01-01 00:00:00.0")), Timestamp.valueOf("2024-02-10 23:00:00.0")); + + assertEquals(DateTimeFunctions.dateBin("1h", Timestamp.valueOf("2024-02-10 23:00:55.0"), + Timestamp.valueOf("2024-01-01 00:00:00.0")), Timestamp.valueOf("2024-02-10 23:00:00.0")); + + assertEquals(DateTimeFunctions.dateBin("1h15m", Timestamp.valueOf("2024-02-10 23:00:55.0"), + Timestamp.valueOf("2024-01-01 00:00:00.0")), Timestamp.valueOf("2024-02-10 22:30:00.0")); + + assertEquals(DateTimeFunctions.dateBin("2h", Timestamp.valueOf("2024-02-10 23:00:55.0"), + Timestamp.valueOf("2024-01-01 00:00:00.0")), Timestamp.valueOf("2024-02-10 22:00:00.0")); + + assertEquals(DateTimeFunctions.dateBin("24h", Timestamp.valueOf("2024-02-10 23:00:55.0"), + Timestamp.valueOf("2024-01-01 00:00:00.0")), Timestamp.valueOf("2024-02-10 00:00:00.0")); + + assertEquals(DateTimeFunctions.dateBin("1d", Timestamp.valueOf("2024-02-10 23:00:55.0"), + Timestamp.valueOf("2024-01-01 00:00:00.0")), Timestamp.valueOf("2024-02-10 00:00:00.0")); + + assertEquals(DateTimeFunctions.dateBin("2d", Timestamp.valueOf("2024-02-09 23:00:55.0"), + Timestamp.valueOf("2024-01-01 00:00:00.0")), Timestamp.valueOf("2024-02-08 00:00:00.0")); + + assertEquals(DateTimeFunctions.dateBin("10d10m", Timestamp.valueOf("2024-02-09 23:00:55.0"), + Timestamp.valueOf("2024-01-01 00:00:00.0")), Timestamp.valueOf("2024-01-31 00:30:00.0")); + } + @Test public void testDateTimeConvert() { // EPOCH to EPOCH @@ -529,6 +588,9 @@ public void testDateTimeConvert() { testDateTimeConvert("20180418T01:00:00", "1:HOURS:SIMPLE_DATE_FORMAT:yyyyMMdd''T''HH:mm:ss", "1:MILLISECONDS:SIMPLE_DATE_FORMAT:yyyy-MM-dd HH:mm:ss.SSS tz(America/Los_Angeles)", "1:DAYS", "2018-04-17 00:00:00.000"); + // Test time value with scientific number + testDateTimeConvert(1.50598536E12/* 20170921T02:16:00 */, "1:MILLISECONDS:EPOCH", + "1:DAYS:SIMPLE_DATE_FORMAT:yyyyMMdd", "1:DAYS", "20170921"); } @Test @@ -579,7 +641,7 @@ private void testDateTimeConvert(Object timeValue, String inputFormatStr, String row.putValue("timeCol", timeValue); List arguments = Collections.singletonList("timeCol"); testFunction(String.format("dateTimeConvert(timeCol, '%s', '%s', '%s')", inputFormatStr, outputFormatStr, - outputGranularityStr), arguments, row, expectedResult == null ? null : expectedResult.toString()); + outputGranularityStr), arguments, row, expectedResult == null ? null : expectedResult); } private void testMultipleInvocations(String functionExpression, List rows, List expectedResults) { @@ -587,7 +649,7 @@ private void testMultipleInvocations(String functionExpression, List int numInvocations = rows.size(); assertEquals(expectedResults.size(), numInvocations); for (int i = 0; i < numInvocations; i++) { - assertEquals(evaluator.evaluate(rows.get(i)), expectedResults.get(i).toString()); + assertEquals(evaluator.evaluate(rows.get(i)), expectedResults.get(i)); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/data/function/InbuiltFunctionEvaluatorTest.java b/pinot-core/src/test/java/org/apache/pinot/core/data/function/InbuiltFunctionEvaluatorTest.java index 5c6835e293f6..07ba2e00c72d 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/data/function/InbuiltFunctionEvaluatorTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/data/function/InbuiltFunctionEvaluatorTest.java @@ -30,6 +30,7 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; public class InbuiltFunctionEvaluatorTest { @@ -130,7 +131,7 @@ public void testStateSharedBetweenRowsForExecution() throws Exception { MyFunc myFunc = new MyFunc(); Method method = myFunc.getClass().getDeclaredMethod("appendToStringAndReturn", String.class); - FunctionRegistry.registerFunction(method, false); + FunctionRegistry.registerFunction(method, false, false, false); String expression = "appendToStringAndReturn('test ')"; InbuiltFunctionEvaluator evaluator = new InbuiltFunctionEvaluator(expression); assertTrue(evaluator.getArguments().isEmpty()); @@ -157,6 +158,21 @@ public void testNullReturnedByInbuiltFunctionEvaluatorThatCannotTakeNull() { } } + @Test + public void testPlaceholderFunctionShouldNotBeRegistered() + throws Exception { + GenericRow row = new GenericRow(); + row.putValue("testColumn", "testValue"); + String expression = "text_match(testColumn, 'pattern')"; + try { + InbuiltFunctionEvaluator evaluator = new InbuiltFunctionEvaluator(expression); + evaluator.evaluate(row); + fail(); + } catch (Throwable t) { + assertTrue(t.getMessage().contains("text_match")); + } + } + public static class MyFunc { String _baseString = ""; diff --git a/pinot-core/src/test/java/org/apache/pinot/core/data/function/JsonFunctionsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/data/function/JsonFunctionsTest.java index fa9f6dfced2b..6aa8bcbe3d8f 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/data/function/JsonFunctionsTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/data/function/JsonFunctionsTest.java @@ -137,4 +137,19 @@ public Object[][] jsonFunctionsDataProvider() }); return inputs.toArray(new Object[0][]); } + + @Test(description = "jsonFormat(Java null) should return Java null") + public void jsonFormatWithJavaNullReturnsJavaNull() { + GenericRow row = new GenericRow(); + row.putValue("jsonMap", null); + testFunction("json_format(jsonMap)", Lists.newArrayList("jsonMap"), row, null); + } + + @Test(description = "jsonFormat(JSON null) should return \"null\"") + public void jsonFormatWithJsonNullReturnsStringNull() + throws IOException { + GenericRow row = new GenericRow(); + row.putValue("jsonMap", JsonUtils.stringToJsonNode("null")); + testFunction("json_format(jsonMap)", Lists.newArrayList("jsonMap"), row, "null"); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/data/function/ObjectFunctionsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/data/function/ObjectFunctionsTest.java index e324352a53b5..231b6078575c 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/data/function/ObjectFunctionsTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/data/function/ObjectFunctionsTest.java @@ -113,6 +113,49 @@ public Object[][] objectFunctionsDataProvider() { "coalesce(value1,value2,value3,value4,value5)", Lists.newArrayList("value1", "value2", "value3", "value4", "value5"), allValues, "1" }); + + // Adding a test for case when + GenericRow caseWhenCaseValueOne = new GenericRow(); + caseWhenCaseValueOne.putValue("value1", 1); + inputs.add(new Object[]{ + "CASEWHEN(value1 = 1, 'one', value1 = 2, 'two', 'other')", Lists.newArrayList("value1", + "value1"), caseWhenCaseValueOne, "one" + }); + + GenericRow caseWhenCaseValueTwo = new GenericRow(); + caseWhenCaseValueTwo.putValue("value1", 2); + inputs.add(new Object[]{ + "CASEWHEN(value1 = 1, 'one', value1 = 2, 'two', 'other')", Lists.newArrayList("value1", + "value1"), caseWhenCaseValueTwo, "two" + }); + + GenericRow caseWhenCaseValueThree = new GenericRow(); + caseWhenCaseValueThree.putValue("value1", 3); + inputs.add(new Object[]{ + "CASEWHEN(value1 = 1, 'one', value1 = 2, 'two', 'other')", Lists.newArrayList("value1", + "value1"), caseWhenCaseValueThree, "other" + }); + + GenericRow caseWhenCaseMultipleExpression = new GenericRow(); + caseWhenCaseMultipleExpression.putValue("value1", 10); + inputs.add(new Object[]{ + "CASEWHEN(value1 = 1, 'one', value1 = 2, 'two', value1 = 3, 'three', value1 = 4, 'four', value1 = 5, 'five', " + + "value1 = 6, 'six', value1 = 7, 'seven', value1 = 8, 'eight', value1 = 9, 'nine', value1 = 10, 'ten', " + + "'other')", Lists.newArrayList("value1", "value1", "value1", "value1", "value1", "value1", "value1", + "value1", "value1", "value1"), caseWhenCaseMultipleExpression, "ten" + }); + + GenericRow caseWhenCaseMultipleExpression2 = new GenericRow(); + caseWhenCaseMultipleExpression2.putValue("value1", 15); + inputs.add(new Object[]{ + "CASEWHEN(value1 = 1, 'one', value1 = 2, 'two', value1 = 3, 'three', value1 = 4, 'four', value1 = 5, 'five', " + + "value1 = 6, 'six', value1 = 7, 'seven', value1 = 8, 'eight', value1 = 9, 'nine', value1 = 10, 'ten', " + + "value1 = 11, 'eleven', value1 = 12, 'twelve', value1 = 13, 'thirteen', value1 = 14, 'fourteen', value1" + + " = 15, 'fifteen'," + "'other')", Lists.newArrayList("value1", "value1", "value1", "value1", "value1", + "value1", "value1", "value1", "value1", "value1", "value1", "value1", "value1", "value1", + "value1"), caseWhenCaseMultipleExpression2, "fifteen" + }); + return inputs.toArray(new Object[0][]); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/data/function/VectorFunctionsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/data/function/VectorFunctionsTest.java new file mode 100644 index 000000000000..6600b5c10ff1 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/data/function/VectorFunctionsTest.java @@ -0,0 +1,132 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.data.function; + +import com.google.common.collect.Lists; +import java.util.ArrayList; +import java.util.List; +import org.apache.pinot.segment.local.function.InbuiltFunctionEvaluator; +import org.apache.pinot.spi.data.readers.GenericRow; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + + +/** + * Tests the vector scalar functions + */ +public class VectorFunctionsTest { + + private void testFunction(String functionExpression, List expectedArguments, GenericRow row, + Object expectedResult) { + InbuiltFunctionEvaluator evaluator = new InbuiltFunctionEvaluator(functionExpression); + Assert.assertEquals(evaluator.getArguments(), expectedArguments); + Assert.assertEquals(evaluator.evaluate(row), expectedResult); + } + + @Test(dataProvider = "vectorFunctionsDataProvider") + public void testVectorFunctions(String functionExpression, List expectedArguments, GenericRow row, + Object expectedResult) { + testFunction(functionExpression, expectedArguments, row, expectedResult); + } + + @DataProvider(name = "vectorFunctionsDataProvider") + public Object[][] vectorFunctionsDataProvider() { + List inputs = new ArrayList<>(); + + GenericRow row = new GenericRow(); + row.putValue("vector1", new float[]{0.1f, 0.2f, 0.3f, 0.4f, 0.5f}); + row.putValue("vector2", new float[]{0.6f, 0.7f, 0.8f, 0.9f, 1.0f}); + inputs.add(new Object[]{ + "cosineDistance(vector1, vector2)", Lists.newArrayList("vector1", "vector2"), row, 0.03504950750101454 + }); + inputs.add(new Object[]{ + "innerProduct(vector1, vector2)", Lists.newArrayList("vector1", "vector2"), row, 1.2999999970197678 + }); + inputs.add(new Object[]{ + "l2Distance(vector1, vector2)", Lists.newArrayList("vector1", "vector2"), row, 1.1180339754218913 + }); + inputs.add(new Object[]{ + "l1Distance(vector1, vector2)", Lists.newArrayList("vector1", "vector2"), row, 2.4999999701976776 + }); + inputs.add(new Object[]{"vectorDims(vector1)", Lists.newArrayList("vector1"), row, 5}); + inputs.add(new Object[]{"vectorDims(vector2)", Lists.newArrayList("vector2"), row, 5}); + inputs.add(new Object[]{"vectorNorm(vector1)", Lists.newArrayList("vector1"), row, 0.741619857751291}); + inputs.add(new Object[]{"vectorNorm(vector2)", Lists.newArrayList("vector2"), row, 1.8165902091773676}); + return inputs.toArray(new Object[0][]); + } + + @Test(dataProvider = "vectorFunctionsZeroDataProvider") + public void testVectorFunctionsWithZeroVector(String functionExpression, List expectedArguments, + GenericRow row, + Object expectedResult) { + testFunction(functionExpression, expectedArguments, row, expectedResult); + } + + @DataProvider(name = "vectorFunctionsZeroDataProvider") + public Object[][] vectorFunctionsZeroDataProvider() { + List inputs = new ArrayList<>(); + + GenericRow row = new GenericRow(); + row.putValue("vector1", new float[]{0.1f, 0.2f, 0.3f, 0.4f, 0.5f}); + row.putValue("vector2", new float[]{0f, 0f, 0f, 0f, 0f}); + inputs.add(new Object[]{ + "cosineDistance(vector1, vector2)", Lists.newArrayList("vector1", "vector2"), row, Double.NaN + }); + inputs.add(new Object[]{ + "cosineDistance(vector1, vector2, 0.0)", Lists.newArrayList("vector1", "vector2"), row, 0.0 + }); + inputs.add(new Object[]{ + "cosineDistance(vector1, vector2, 1.0)", Lists.newArrayList("vector1", "vector2"), row, 1.0 + }); + inputs.add(new Object[]{ + "innerProduct(vector1, vector2)", Lists.newArrayList("vector1", "vector2"), row, 0.0 + }); + inputs.add(new Object[]{ + "l2Distance(vector1, vector2)", Lists.newArrayList("vector1", "vector2"), row, 0.741619857751291 + }); + inputs.add(new Object[]{ + "l1Distance(vector1, vector2)", Lists.newArrayList("vector1", "vector2"), row, 1.5000000223517418 + }); + inputs.add(new Object[]{"vectorDims(vector1)", Lists.newArrayList("vector1"), row, 5}); + inputs.add(new Object[]{"vectorDims(vector2)", Lists.newArrayList("vector2"), row, 5}); + inputs.add(new Object[]{"vectorNorm(vector1)", Lists.newArrayList("vector1"), row, 0.741619857751291}); + inputs.add(new Object[]{"vectorNorm(vector2)", Lists.newArrayList("vector2"), row, 0.0}); + + inputs.add(new Object[]{ + "cosineDistance(vector1, ARRAY[0.0,0.0,0.0,0.0,0.0])", Lists.newArrayList("vector1"), row, Double.NaN + }); + inputs.add(new Object[]{ + "cosineDistance(vector1, ARRAY[0.0,0.0,0.0,0.0,0.0], 0.0)", Lists.newArrayList("vector1"), row, 0.0 + }); + inputs.add(new Object[]{ + "cosineDistance(vector1, ARRAY[0.0,0.0,0.0,0.0,0.0], 1.0)", Lists.newArrayList("vector1"), row, 1.0 + }); + inputs.add(new Object[]{ + "innerProduct(vector1, ARRAY[0.0,0.0,0.0,0.0,0.0])", Lists.newArrayList("vector1"), row, 0.0 + }); + inputs.add(new Object[]{ + "l2Distance(vector1, ARRAY[0.0,0.0,0.0,0.0,0.0])", Lists.newArrayList("vector1"), row, 0.741619857751291 + }); + inputs.add(new Object[]{ + "l1Distance(vector1, ARRAY[0.0,0.0,0.0,0.0,0.0])", Lists.newArrayList("vector1"), row, 1.5000000223517418 + }); + return inputs.toArray(new Object[0][]); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/BaseTableDataManagerAcquireSegmentTest.java b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/BaseTableDataManagerAcquireSegmentTest.java index c679a6d2fee8..50546f57b19f 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/BaseTableDataManagerAcquireSegmentTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/BaseTableDataManagerAcquireSegmentTest.java @@ -22,35 +22,31 @@ import java.io.File; import java.lang.reflect.Field; import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; -import org.apache.commons.configuration.MapConfiguration; import org.apache.commons.io.FileUtils; import org.apache.helix.HelixManager; -import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.pinot.common.metrics.ServerMetrics; import org.apache.pinot.core.data.manager.offline.ImmutableSegmentDataManager; import org.apache.pinot.core.data.manager.offline.OfflineTableDataManager; import org.apache.pinot.segment.local.data.manager.SegmentDataManager; import org.apache.pinot.segment.local.data.manager.TableDataManager; -import org.apache.pinot.segment.local.data.manager.TableDataManagerConfig; -import org.apache.pinot.segment.local.data.manager.TableDataManagerParams; +import org.apache.pinot.segment.local.utils.SegmentLocks; import org.apache.pinot.segment.spi.ImmutableSegment; import org.apache.pinot.segment.spi.SegmentMetadata; -import org.apache.pinot.spi.metrics.PinotMetricUtils; -import org.apache.pinot.spi.utils.builder.TableNameBuilder; +import org.apache.pinot.spi.config.instance.InstanceDataManagerConfig; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.apache.pinot.util.TestUtils; import org.testng.Assert; -import org.testng.annotations.AfterSuite; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; -import org.testng.annotations.BeforeSuite; import org.testng.annotations.Test; import static org.mockito.Mockito.*; @@ -58,7 +54,6 @@ public class BaseTableDataManagerAcquireSegmentTest { private static final String RAW_TABLE_NAME = "testTable"; - private static final String OFFLINE_TABLE_NAME = TableNameBuilder.OFFLINE.tableNameWithType(RAW_TABLE_NAME); private static final String SEGMENT_PREFIX = "segment"; private static final int DELETED_SEGMENTS_CACHE_SIZE = 100; private static final int DELETED_SEGMENTS_TTL_MINUTES = 2; @@ -70,12 +65,9 @@ public class BaseTableDataManagerAcquireSegmentTest { // Set once for every test private volatile int _nDestroys; private volatile boolean _closing; - private Set _allSegments = new HashSet<>(); - private Set _accessedSegManagers = - Collections.newSetFromMap(new ConcurrentHashMap()); - private Set _allSegManagers = - Collections.newSetFromMap(new ConcurrentHashMap()); - private AtomicInteger _numQueries = new AtomicInteger(0); + private final Set _allSegments = new HashSet<>(); + private final Set _accessedSegManagers = ConcurrentHashMap.newKeySet(); + private final Set _allSegManagers = ConcurrentHashMap.newKeySet(); private Map _internalSegMap; private Throwable _exception; private Thread _masterThread; @@ -86,9 +78,11 @@ public class BaseTableDataManagerAcquireSegmentTest { private volatile int _lo; private volatile int _hi; - @BeforeSuite + @BeforeClass public void setUp() throws Exception { + ServerMetrics.register(mock(ServerMetrics.class)); + _tmpDir = new File(FileUtils.getTempDirectory(), "OfflineTableDataManagerTest"); TestUtils.ensureDirectoriesExistAndEmpty(_tmpDir); _tmpDir.deleteOnExit(); @@ -98,7 +92,7 @@ public void setUp() System.out.printf("Record random seed: %d to reproduce test results upon failure\n", seed); } - @AfterSuite + @AfterClass public void tearDown() { if (_tmpDir != null) { org.apache.commons.io.FileUtils.deleteQuietly(_tmpDir); @@ -112,26 +106,20 @@ public void beforeMethod() { _allSegments.clear(); _accessedSegManagers.clear(); _allSegManagers.clear(); - _numQueries.set(0); _exception = null; _masterThread = null; } private TableDataManager makeTestableManager() throws Exception { + InstanceDataManagerConfig instanceDataManagerConfig = mock(InstanceDataManagerConfig.class); + when(instanceDataManagerConfig.getInstanceDataDir()).thenReturn(_tmpDir.getAbsolutePath()); + when(instanceDataManagerConfig.getDeletedSegmentsCacheSize()).thenReturn(DELETED_SEGMENTS_CACHE_SIZE); + when(instanceDataManagerConfig.getDeletedSegmentsCacheTtlMinutes()).thenReturn(DELETED_SEGMENTS_TTL_MINUTES); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); TableDataManager tableDataManager = new OfflineTableDataManager(); - TableDataManagerConfig config; - { - config = mock(TableDataManagerConfig.class); - when(config.getTableName()).thenReturn(OFFLINE_TABLE_NAME); - when(config.getDataDir()).thenReturn(_tmpDir.getAbsolutePath()); - when(config.getAuthConfig()).thenReturn(new MapConfiguration(new HashMap<>())); - when(config.getTableDeletedSegmentsCacheSize()).thenReturn(DELETED_SEGMENTS_CACHE_SIZE); - when(config.getTableDeletedSegmentsCacheTtlMinutes()).thenReturn(DELETED_SEGMENTS_TTL_MINUTES); - } - tableDataManager.init(config, "dummyInstance", mock(ZkHelixPropertyStore.class), - new ServerMetrics(PinotMetricUtils.getPinotMetricsRegistry()), mock(HelixManager.class), null, - new TableDataManagerParams(0, false, -1)); + tableDataManager.init(instanceDataManagerConfig, mock(HelixManager.class), new SegmentLocks(), tableConfig, null, + null); tableDataManager.start(); Field segsMapField = BaseTableDataManager.class.getDeclaredField("_segmentDataManagerMap"); segsMapField.setAccessible(true); @@ -167,7 +155,7 @@ public void basicTest() Assert.assertEquals(tableDataManager.getNumSegments(), 1); SegmentDataManager segmentDataManager = tableDataManager.acquireSegment(segmentName); Assert.assertEquals(segmentDataManager.getReferenceCount(), 2); - tableDataManager.removeSegment(segmentName); + tableDataManager.offloadSegment(segmentName); Assert.assertEquals(tableDataManager.getNumSegments(), 0); Assert.assertEquals(segmentDataManager.getReferenceCount(), 1); Assert.assertEquals(_nDestroys, 0); @@ -192,10 +180,10 @@ public void basicTest() // return false. tableDataManager.addSegment(immutableSegment); Assert.assertFalse(tableDataManager.isSegmentDeletedRecently(segmentName)); - tableDataManager.removeSegment(segmentName); + tableDataManager.offloadSegment(segmentName); // Removing the segment again is fine. - tableDataManager.removeSegment(segmentName); + tableDataManager.offloadSegment(segmentName); Assert.assertEquals(tableDataManager.getNumSegments(), 0); // Add a new segment and remove it in order this time. @@ -227,7 +215,7 @@ public void basicTest() // Delete ix2 without accessing it. SegmentDataManager sdm2 = _internalSegMap.get(anotherSeg); Assert.assertEquals(sdm2.getReferenceCount(), 1); - tableDataManager.removeSegment(anotherSeg); + tableDataManager.offloadSegment(anotherSeg); Assert.assertEquals(tableDataManager.getNumSegments(), 0); Assert.assertEquals(sdm2.getReferenceCount(), 0); verify(ix2, times(1)).destroy(); @@ -461,10 +449,11 @@ private void replaceSegment() { } // Remove the segment _lo and then bump _lo - private void removeSegment() { + private void removeSegment() + throws Exception { // Keep at least one segment in place. if (_hi > _lo) { - _tableDataManager.removeSegment(SEGMENT_PREFIX + _lo); + _tableDataManager.offloadSegment(SEGMENT_PREFIX + _lo); _lo++; } else { addSegment(); diff --git a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/BaseTableDataManagerTest.java b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/BaseTableDataManagerTest.java index 6437a54a702b..1282e493fd95 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/BaseTableDataManagerTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/BaseTableDataManagerTest.java @@ -18,408 +18,391 @@ */ package org.apache.pinot.core.data.manager; +import java.io.DataInputStream; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; -import org.apache.commons.configuration.MapConfiguration; import org.apache.commons.io.FileUtils; import org.apache.helix.HelixManager; -import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; import org.apache.pinot.common.metrics.ServerMetrics; import org.apache.pinot.common.tier.TierFactory; import org.apache.pinot.common.utils.TarGzCompressionUtils; import org.apache.pinot.common.utils.fetcher.BaseSegmentFetcher; import org.apache.pinot.common.utils.fetcher.SegmentFetcherFactory; +import org.apache.pinot.core.data.manager.offline.ImmutableSegmentDataManager; import org.apache.pinot.core.data.manager.offline.OfflineTableDataManager; -import org.apache.pinot.core.util.PeerServerSegmentFinder; -import org.apache.pinot.segment.local.data.manager.TableDataManagerConfig; -import org.apache.pinot.segment.local.data.manager.TableDataManagerParams; import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; import org.apache.pinot.segment.local.segment.index.loader.IndexLoadingConfig; import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; +import org.apache.pinot.segment.local.utils.SegmentLocks; +import org.apache.pinot.segment.spi.ImmutableSegment; import org.apache.pinot.segment.spi.SegmentMetadata; import org.apache.pinot.segment.spi.V1Constants; import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; import org.apache.pinot.segment.spi.creator.SegmentVersion; import org.apache.pinot.segment.spi.index.metadata.SegmentMetadataImpl; +import org.apache.pinot.segment.spi.store.SegmentDirectoryPaths; +import org.apache.pinot.spi.config.instance.InstanceDataManagerConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.config.table.TierConfig; import org.apache.pinot.spi.crypt.PinotCrypter; import org.apache.pinot.spi.crypt.PinotCrypterFactory; -import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.data.Schema; import org.apache.pinot.spi.data.readers.GenericRow; import org.apache.pinot.spi.env.PinotConfiguration; -import org.apache.pinot.spi.metrics.PinotMetricUtils; import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; import org.apache.pinot.spi.utils.retry.AttemptsExceededException; import org.apache.pinot.util.TestUtils; -import org.mockito.MockedStatic; -import org.mockito.Mockito; import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeClass; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.fail; +import static org.testng.Assert.*; public class BaseTableDataManagerTest { private static final File TEMP_DIR = new File(FileUtils.getTempDirectory(), "BaseTableDataManagerTest"); - private static final String TABLE_NAME = "table01"; - private static final String TABLE_NAME_WITH_TYPE = "table01_OFFLINE"; - private static final File TABLE_DATA_DIR = new File(TEMP_DIR, TABLE_NAME_WITH_TYPE); + private static final String RAW_TABLE_NAME = "testTable"; + private static final String OFFLINE_TABLE_NAME = TableNameBuilder.OFFLINE.tableNameWithType(RAW_TABLE_NAME); + private static final File TABLE_DATA_DIR = new File(TEMP_DIR, OFFLINE_TABLE_NAME); + private static final String SEGMENT_NAME = "testSegment"; + private static final String TIER_SEGMENT_DIRECTORY_LOADER = "tierBased"; + private static final String TIER_NAME = "coolTier"; private static final String STRING_COLUMN = "col1"; private static final String[] STRING_VALUES = {"A", "D", "E", "B", "C"}; private static final String LONG_COLUMN = "col2"; private static final long[] LONG_VALUES = {10000L, 20000L, 50000L, 40000L, 30000L}; - @BeforeMethod + private static final TableConfig DEFAULT_TABLE_CONFIG = + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + private static final TableConfig TIER_TABLE_CONFIG = + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setTierConfigList(List.of( + new TierConfig(TIER_NAME, TierFactory.TIME_SEGMENT_SELECTOR_TYPE, "3d", null, + TierFactory.PINOT_SERVER_STORAGE_TYPE, "tag_OFFLINE", null, + Map.of("dataDir", new File(TEMP_DIR, TIER_NAME).getAbsolutePath())))).build(); + private static final Schema SCHEMA = + new Schema.SchemaBuilder().setSchemaName(RAW_TABLE_NAME).addSingleValueDimension(STRING_COLUMN, DataType.STRING) + .addMetric(LONG_COLUMN, DataType.LONG).build(); + + @BeforeClass public void setUp() throws Exception { + ServerMetrics.register(mock(ServerMetrics.class)); + } + + @BeforeMethod + public void setUpMethod() + throws Exception { TestUtils.ensureDirectoriesExistAndEmpty(TEMP_DIR); - TableDataManagerTestUtils.initSegmentFetcher(); + initSegmentFetcher(); } @AfterMethod - public void tearDown() + public void tearDownMethod() throws Exception { FileUtils.deleteDirectory(TEMP_DIR); } + public static void initSegmentFetcher() + throws Exception { + Map properties = new HashMap<>(); + properties.put(BaseSegmentFetcher.RETRY_COUNT_CONFIG_KEY, 3); + properties.put(BaseSegmentFetcher.RETRY_WAIT_MS_CONFIG_KEY, 100); + properties.put(BaseSegmentFetcher.RETRY_DELAY_SCALE_FACTOR_CONFIG_KEY, 5); + SegmentFetcherFactory.init(new PinotConfiguration(properties)); + + // Setup crypter + properties.put("class.fakePinotCrypter", BaseTableDataManagerTest.FakePinotCrypter.class.getName()); + PinotCrypterFactory.init(new PinotConfiguration(properties)); + } + @Test public void testReloadSegmentNewData() throws Exception { - TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build(); - String segName = "seg01"; - SegmentZKMetadata zkmd = createRawSegment(tableConfig, segName, SegmentVersion.v3, 5); + SegmentZKMetadata zkMetadata = createRawSegment(SegmentVersion.v3, 5); // Mock the case where segment is loaded but its CRC is different from // the one in zk, thus raw segment is downloaded and loaded. - SegmentMetadata llmd = mock(SegmentMetadata.class); - when(llmd.getCrc()).thenReturn("0"); + SegmentMetadata localMetadata = mock(SegmentMetadata.class); + when(localMetadata.getCrc()).thenReturn("0"); - BaseTableDataManager tmgr = createTableManager(); - assertFalse(tmgr.getSegmentDataDir(segName).exists()); - tmgr.reloadSegment(segName, TableDataManagerTestUtils.createIndexLoadingConfig(), zkmd, llmd, null, false); - assertTrue(tmgr.getSegmentDataDir(segName).exists()); - llmd = new SegmentMetadataImpl(tmgr.getSegmentDataDir(segName)); - assertEquals(llmd.getTotalDocs(), 5); + BaseTableDataManager tableDataManager = createTableManager(); + File dataDir = tableDataManager.getSegmentDataDir(SEGMENT_NAME); + assertFalse(dataDir.exists()); + tableDataManager.reloadSegment(SEGMENT_NAME, new IndexLoadingConfig(), zkMetadata, localMetadata, null, false); + assertTrue(dataDir.exists()); + assertEquals(new SegmentMetadataImpl(dataDir).getTotalDocs(), 5); } @Test public void testReloadSegmentNewDataNewTier() throws Exception { - TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build(); - String segName = "seg01"; - SegmentZKMetadata zkmd = createRawSegment(tableConfig, segName, SegmentVersion.v3, 5); - String tierName = "coolTier"; - zkmd.setTier(tierName); + SegmentZKMetadata zkMetadata = createRawSegment(SegmentVersion.v3, 5); + zkMetadata.setTier(TIER_NAME); // Mock the case where segment is loaded but its CRC is different from // the one in zk, thus raw segment is downloaded and loaded. - SegmentMetadata llmd = mock(SegmentMetadata.class); - when(llmd.getCrc()).thenReturn("0"); + SegmentMetadata localMetadata = mock(SegmentMetadata.class); + when(localMetadata.getCrc()).thenReturn("0"); // No dataDir for coolTier, thus stay on default tier. - BaseTableDataManager tmgr = createTableManager(); - File defaultSegDir = tmgr.getSegmentDataDir(segName); - assertFalse(defaultSegDir.exists()); - tmgr.reloadSegment(segName, TableDataManagerTestUtils.createIndexLoadingConfig("tierBased", tableConfig, null), - zkmd, llmd, null, false); - assertTrue(defaultSegDir.exists()); - llmd = new SegmentMetadataImpl(defaultSegDir); - assertEquals(llmd.getTotalDocs(), 5); + BaseTableDataManager tableDataManager = createTableManager(); + File defaultDataDir = tableDataManager.getSegmentDataDir(SEGMENT_NAME); + assertFalse(defaultDataDir.exists()); + tableDataManager.reloadSegment(SEGMENT_NAME, createTierIndexLoadingConfig(DEFAULT_TABLE_CONFIG), zkMetadata, + localMetadata, null, false); + assertTrue(defaultDataDir.exists()); + assertEquals(new SegmentMetadataImpl(defaultDataDir).getTotalDocs(), 5); // Configured dataDir for coolTier, thus move to new dir. - llmd = mock(SegmentMetadata.class); - when(llmd.getCrc()).thenReturn("0"); - tableConfig = createTableConfigWithTier(tierName, new File(TEMP_DIR, tierName)); - tmgr = createTableManager(); - IndexLoadingConfig loadingCfg = TableDataManagerTestUtils.createIndexLoadingConfig("tierBased", tableConfig, null); - tmgr.reloadSegment(segName, loadingCfg, zkmd, llmd, null, false); - File segDirOnTier = tmgr.getSegmentDataDir(segName, tierName, loadingCfg.getTableConfig()); - assertTrue(segDirOnTier.exists()); - assertFalse(defaultSegDir.exists()); - llmd = new SegmentMetadataImpl(segDirOnTier); - assertEquals(llmd.getTotalDocs(), 5); - assertEquals(llmd.getIndexDir(), segDirOnTier); + tableDataManager = createTableManager(); + tableDataManager.reloadSegment(SEGMENT_NAME, createTierIndexLoadingConfig(TIER_TABLE_CONFIG), zkMetadata, + localMetadata, null, false); + File tierDataDir = tableDataManager.getSegmentDataDir(SEGMENT_NAME, TIER_NAME, TIER_TABLE_CONFIG); + assertTrue(tierDataDir.exists()); + assertFalse(defaultDataDir.exists()); + assertEquals(new SegmentMetadataImpl(tierDataDir).getTotalDocs(), 5); } @Test public void testReloadSegmentUseLocalCopy() throws Exception { - TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build(); - String segName = "seg01"; - File localSegDir = createSegment(tableConfig, segName, SegmentVersion.v1, 5); - long segCrc = TableDataManagerTestUtils.getCRC(localSegDir, SegmentVersion.v1); + File indexDir = createSegment(SegmentVersion.v1, 5); + long crc = getCRC(indexDir); // Same CRCs so load the local segment directory directly. - SegmentZKMetadata zkmd = mock(SegmentZKMetadata.class); - when(zkmd.getCrc()).thenReturn(segCrc); - SegmentMetadata llmd = mock(SegmentMetadata.class); - when(llmd.getCrc()).thenReturn(Long.toString(segCrc)); - - BaseTableDataManager tmgr = createTableManager(); - tmgr.reloadSegment(segName, TableDataManagerTestUtils.createIndexLoadingConfig(), zkmd, llmd, null, false); - assertTrue(tmgr.getSegmentDataDir(segName).exists()); - llmd = new SegmentMetadataImpl(tmgr.getSegmentDataDir(segName)); - assertEquals(llmd.getTotalDocs(), 5); - - FileUtils.deleteQuietly(localSegDir); + SegmentZKMetadata zkMetadata = mock(SegmentZKMetadata.class); + when(zkMetadata.getCrc()).thenReturn(crc); + SegmentMetadata localMetadata = mock(SegmentMetadata.class); + when(localMetadata.getCrc()).thenReturn(Long.toString(crc)); + + BaseTableDataManager tableDataManager = createTableManager(); + tableDataManager.reloadSegment(SEGMENT_NAME, new IndexLoadingConfig(), zkMetadata, localMetadata, null, false); + assertEquals(tableDataManager.getSegmentDataDir(SEGMENT_NAME), indexDir); + assertTrue(indexDir.exists()); + assertEquals(new SegmentMetadataImpl(indexDir).getTotalDocs(), 5); + + FileUtils.deleteQuietly(indexDir); try { - tmgr.reloadSegment(segName, TableDataManagerTestUtils.createIndexLoadingConfig(), zkmd, llmd, null, false); + tableDataManager.reloadSegment(SEGMENT_NAME, new IndexLoadingConfig(), zkMetadata, localMetadata, null, false); fail(); } catch (Exception e) { // As expected, segment reloading fails due to missing the local segment dir. - assertTrue(e.getMessage().contains("does not exist or is not a directory")); } } @Test public void testReloadSegmentUseLocalCopyNewTier() throws Exception { - TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build(); - String segName = "seg01"; - File localSegDir = createSegment(tableConfig, segName, SegmentVersion.v1, 5); - long segCrc = TableDataManagerTestUtils.getCRC(localSegDir, SegmentVersion.v1); + File indexDir = createSegment(SegmentVersion.v1, 5); + long crc = getCRC(indexDir); // Same CRCs so load the local segment directory directly. - SegmentZKMetadata zkmd = mock(SegmentZKMetadata.class); - when(zkmd.getCrc()).thenReturn(segCrc); - String tierName = "coolTier"; - when(zkmd.getTier()).thenReturn(tierName); - SegmentMetadata llmd = mock(SegmentMetadata.class); - when(llmd.getCrc()).thenReturn(Long.toString(segCrc)); + SegmentZKMetadata zkMetadata = mock(SegmentZKMetadata.class); + when(zkMetadata.getCrc()).thenReturn(crc); + when(zkMetadata.getTier()).thenReturn(TIER_NAME); + SegmentMetadata localMetadata = mock(SegmentMetadata.class); + when(localMetadata.getCrc()).thenReturn(Long.toString(crc)); // No dataDir for coolTier, thus stay on default tier. - BaseTableDataManager tmgr = createTableManager(); - tmgr.reloadSegment(segName, TableDataManagerTestUtils.createIndexLoadingConfig("tierBased", tableConfig, null), - zkmd, llmd, null, false); - assertTrue(tmgr.getSegmentDataDir(segName).exists()); - llmd = new SegmentMetadataImpl(tmgr.getSegmentDataDir(segName)); - assertEquals(llmd.getTotalDocs(), 5); - assertEquals(llmd.getIndexDir(), localSegDir); + BaseTableDataManager tableDataManager = createTableManager(); + tableDataManager.reloadSegment(SEGMENT_NAME, createTierIndexLoadingConfig(DEFAULT_TABLE_CONFIG), zkMetadata, + localMetadata, null, false); + assertEquals(tableDataManager.getSegmentDataDir(SEGMENT_NAME), indexDir); + assertTrue(indexDir.exists()); + assertEquals(new SegmentMetadataImpl(indexDir).getTotalDocs(), 5); // Configured dataDir for coolTier, thus move to new dir. - llmd = mock(SegmentMetadata.class); - when(llmd.getCrc()).thenReturn(Long.toString(segCrc)); - tableConfig = createTableConfigWithTier(tierName, new File(TEMP_DIR, tierName)); - tmgr = createTableManager(); - IndexLoadingConfig loadingCfg = TableDataManagerTestUtils.createIndexLoadingConfig("tierBased", tableConfig, null); - tmgr.reloadSegment(segName, loadingCfg, zkmd, llmd, null, false); - File segDirOnTier = tmgr.getSegmentDataDir(segName, tierName, loadingCfg.getTableConfig()); - assertTrue(segDirOnTier.exists()); - assertFalse(localSegDir.exists()); - llmd = new SegmentMetadataImpl(segDirOnTier); - assertEquals(llmd.getTotalDocs(), 5); - assertEquals(llmd.getIndexDir(), segDirOnTier); + tableDataManager = createTableManager(); + tableDataManager.reloadSegment(SEGMENT_NAME, createTierIndexLoadingConfig(TIER_TABLE_CONFIG), zkMetadata, + localMetadata, null, false); + File tierDataDir = tableDataManager.getSegmentDataDir(SEGMENT_NAME, TIER_NAME, TIER_TABLE_CONFIG); + assertTrue(tierDataDir.exists()); + assertFalse(indexDir.exists()); + assertEquals(new SegmentMetadataImpl(tierDataDir).getTotalDocs(), 5); } @Test public void testReloadSegmentConvertVersion() throws Exception { - TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build(); - String segName = "seg01"; - File localSegDir = createSegment(tableConfig, segName, SegmentVersion.v1, 5); - long segCrc = TableDataManagerTestUtils.getCRC(localSegDir, SegmentVersion.v1); + File indexDir = createSegment(SegmentVersion.v1, 5); + long crc = getCRC(indexDir); // Same CRCs so load the local segment directory directly. - SegmentZKMetadata zkmd = mock(SegmentZKMetadata.class); - when(zkmd.getCrc()).thenReturn(segCrc); - SegmentMetadata llmd = mock(SegmentMetadata.class); - when(llmd.getCrc()).thenReturn(Long.toString(segCrc)); + SegmentZKMetadata zkMetadata = mock(SegmentZKMetadata.class); + when(zkMetadata.getCrc()).thenReturn(crc); + SegmentMetadata localMetadata = mock(SegmentMetadata.class); + when(localMetadata.getCrc()).thenReturn(Long.toString(crc)); // Require to use v3 format. - IndexLoadingConfig idxCfg = TableDataManagerTestUtils.createIndexLoadingConfig(); - idxCfg.setSegmentVersion(SegmentVersion.v3); + IndexLoadingConfig indexLoadingConfig = new IndexLoadingConfig(); + indexLoadingConfig.setSegmentVersion(SegmentVersion.v3); - BaseTableDataManager tmgr = createTableManager(); - tmgr.reloadSegment(segName, idxCfg, zkmd, llmd, null, false); - assertTrue(tmgr.getSegmentDataDir(segName).exists()); - llmd = new SegmentMetadataImpl(tmgr.getSegmentDataDir(segName)); - assertEquals(llmd.getVersion(), SegmentVersion.v3); - assertEquals(llmd.getTotalDocs(), 5); + BaseTableDataManager tableDataManager = createTableManager(); + tableDataManager.reloadSegment(SEGMENT_NAME, indexLoadingConfig, zkMetadata, localMetadata, null, false); + assertEquals(tableDataManager.getSegmentDataDir(SEGMENT_NAME), indexDir); + assertTrue(indexDir.exists()); + SegmentMetadata segmentMetadata = new SegmentMetadataImpl(indexDir); + assertEquals(segmentMetadata.getTotalDocs(), 5); + assertEquals(segmentMetadata.getVersion(), SegmentVersion.v3); } @Test public void testReloadSegmentAddIndex() throws Exception { - TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build(); - String segName = "seg01"; - File localSegDir = createSegment(tableConfig, segName, SegmentVersion.v3, 5); - long segCrc = TableDataManagerTestUtils.getCRC(localSegDir, SegmentVersion.v3); - assertFalse(hasInvertedIndex(localSegDir, STRING_COLUMN, SegmentVersion.v3)); - assertFalse(hasInvertedIndex(localSegDir, LONG_COLUMN, SegmentVersion.v3)); + File indexDir = createSegment(SegmentVersion.v3, 5); + long crc = getCRC(indexDir); + assertFalse(hasInvertedIndex(indexDir, STRING_COLUMN)); + assertFalse(hasInvertedIndex(indexDir, LONG_COLUMN)); // Same CRCs so load the local segment directory directly. - SegmentZKMetadata zkmd = mock(SegmentZKMetadata.class); - when(zkmd.getCrc()).thenReturn(segCrc); - SegmentMetadata llmd = mock(SegmentMetadata.class); - when(llmd.getCrc()).thenReturn(Long.toString(segCrc)); + SegmentZKMetadata zkMetadata = mock(SegmentZKMetadata.class); + when(zkMetadata.getCrc()).thenReturn(crc); + SegmentMetadata localMetadata = mock(SegmentMetadata.class); + when(localMetadata.getCrc()).thenReturn(Long.toString(crc)); // Require to add indices. - IndexLoadingConfig idxCfg = TableDataManagerTestUtils.createIndexLoadingConfig(); - idxCfg.setSegmentVersion(SegmentVersion.v3); - idxCfg.setInvertedIndexColumns(new HashSet<>(Arrays.asList(STRING_COLUMN, LONG_COLUMN))); + IndexLoadingConfig indexLoadingConfig = new IndexLoadingConfig(); + indexLoadingConfig.setInvertedIndexColumns(new HashSet<>(Arrays.asList(STRING_COLUMN, LONG_COLUMN))); - BaseTableDataManager tmgr = createTableManager(); - tmgr.reloadSegment(segName, idxCfg, zkmd, llmd, null, false); - assertTrue(tmgr.getSegmentDataDir(segName).exists()); - llmd = new SegmentMetadataImpl(tmgr.getSegmentDataDir(segName)); - assertEquals(llmd.getTotalDocs(), 5); - assertTrue(hasInvertedIndex(tmgr.getSegmentDataDir(segName), STRING_COLUMN, SegmentVersion.v3)); - assertTrue(hasInvertedIndex(tmgr.getSegmentDataDir(segName), LONG_COLUMN, SegmentVersion.v3)); + BaseTableDataManager tableDataManager = createTableManager(); + tableDataManager.reloadSegment(SEGMENT_NAME, indexLoadingConfig, zkMetadata, localMetadata, null, false); + assertEquals(tableDataManager.getSegmentDataDir(SEGMENT_NAME), indexDir); + assertTrue(indexDir.exists()); + assertEquals(new SegmentMetadataImpl(indexDir).getTotalDocs(), 5); + assertTrue(hasInvertedIndex(indexDir, STRING_COLUMN)); + assertTrue(hasInvertedIndex(indexDir, LONG_COLUMN)); } @Test public void testReloadSegmentForceDownload() throws Exception { - TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build(); - String segName = "seg01"; - File localSegDir = createSegment(tableConfig, segName, SegmentVersion.v3, 5); - SegmentZKMetadata zkmd = TableDataManagerTestUtils.makeRawSegment(segName, localSegDir, - new File(TEMP_DIR, segName + TarGzCompressionUtils.TAR_GZ_FILE_EXTENSION), false); + File indexDir = createSegment(SegmentVersion.v3, 5); + SegmentZKMetadata zkMetadata = + makeRawSegment(indexDir, new File(TEMP_DIR, SEGMENT_NAME + TarGzCompressionUtils.TAR_GZ_FILE_EXTENSION), false); // Same CRC but force to download. - BaseTableDataManager tmgr = createTableManager(); - SegmentMetadataImpl llmd = new SegmentMetadataImpl(tmgr.getSegmentDataDir(segName)); - assertEquals(llmd.getCrc(), zkmd.getCrc() + ""); + BaseTableDataManager tableDataManager = createTableManager(); + SegmentMetadataImpl segmentMetadata = new SegmentMetadataImpl(indexDir); + assertEquals(Long.parseLong(segmentMetadata.getCrc()), zkMetadata.getCrc()); // Remove the local segment dir. Segment reloading fails unless force to download. - FileUtils.deleteQuietly(localSegDir); + FileUtils.deleteQuietly(indexDir); try { - tmgr.reloadSegment(segName, TableDataManagerTestUtils.createIndexLoadingConfig(), zkmd, llmd, null, false); + tableDataManager.reloadSegment(SEGMENT_NAME, new IndexLoadingConfig(), zkMetadata, segmentMetadata, null, false); fail(); } catch (Exception e) { // As expected, segment reloading fails due to missing the local segment dir. - assertTrue(e.getMessage().contains("does not exist or is not a directory")); } - tmgr.reloadSegment(segName, TableDataManagerTestUtils.createIndexLoadingConfig(), zkmd, llmd, null, true); - assertTrue(tmgr.getSegmentDataDir(segName).exists()); - - llmd = new SegmentMetadataImpl(tmgr.getSegmentDataDir(segName)); - assertEquals(llmd.getCrc(), zkmd.getCrc() + ""); - assertEquals(llmd.getTotalDocs(), 5); + tableDataManager.reloadSegment(SEGMENT_NAME, new IndexLoadingConfig(), zkMetadata, segmentMetadata, null, true); + assertTrue(indexDir.exists()); + segmentMetadata = new SegmentMetadataImpl(indexDir); + assertEquals(Long.parseLong(segmentMetadata.getCrc()), zkMetadata.getCrc()); + assertEquals(segmentMetadata.getTotalDocs(), 5); } @Test - public void testAddOrReplaceSegmentNewData() + public void testReplaceSegmentNewData() throws Exception { - TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build(); - String segName = "seg01"; - SegmentZKMetadata zkmd = createRawSegment(tableConfig, segName, SegmentVersion.v3, 5); + SegmentZKMetadata zkMetadata = createRawSegment(SegmentVersion.v3, 5); // Mock the case where segment is loaded but its CRC is different from // the one in zk, thus raw segment is downloaded and loaded. - SegmentMetadata llmd = mock(SegmentMetadata.class); - when(llmd.getCrc()).thenReturn("0"); + ImmutableSegmentDataManager segmentDataManager = createImmutableSegmentDataManager(SEGMENT_NAME, 0); - BaseTableDataManager tmgr = createTableManager(); - assertFalse(tmgr.getSegmentDataDir(segName).exists()); - tmgr.addOrReplaceSegment(segName, TableDataManagerTestUtils.createIndexLoadingConfig(), zkmd, llmd); - assertTrue(tmgr.getSegmentDataDir(segName).exists()); - llmd = new SegmentMetadataImpl(tmgr.getSegmentDataDir(segName)); - assertEquals(llmd.getTotalDocs(), 5); + BaseTableDataManager tableDataManager = createTableManager(); + File dataDir = tableDataManager.getSegmentDataDir(SEGMENT_NAME); + assertFalse(dataDir.exists()); + tableDataManager.replaceSegmentIfCrcMismatch(segmentDataManager, zkMetadata, new IndexLoadingConfig()); + assertTrue(dataDir.exists()); + assertEquals(new SegmentMetadataImpl(dataDir).getTotalDocs(), 5); } @Test - public void testAddOrReplaceSegmentNewDataNewTier() + public void testReplaceSegmentNewDataNewTier() throws Exception { - TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build(); - String segName = "seg01"; - SegmentZKMetadata zkmd = createRawSegment(tableConfig, segName, SegmentVersion.v3, 5); - String tierName = "coolTier"; - zkmd.setTier(tierName); + SegmentZKMetadata zkMetadata = createRawSegment(SegmentVersion.v3, 5); + zkMetadata.setTier(TIER_NAME); // Mock the case where segment is loaded but its CRC is different from // the one in zk, thus raw segment is downloaded and loaded. - SegmentMetadata llmd = mock(SegmentMetadata.class); - when(llmd.getCrc()).thenReturn("0"); + ImmutableSegmentDataManager segmentDataManager = createImmutableSegmentDataManager(SEGMENT_NAME, 0); // No dataDir for coolTier, thus stay on default tier. - BaseTableDataManager tmgr = createTableManager(); - File defaultSegDir = tmgr.getSegmentDataDir(segName); - assertFalse(defaultSegDir.exists()); - tmgr.addOrReplaceSegment(segName, - TableDataManagerTestUtils.createIndexLoadingConfig("tierBased", tableConfig, null), zkmd, llmd); - assertTrue(defaultSegDir.exists()); - llmd = new SegmentMetadataImpl(defaultSegDir); - assertEquals(llmd.getTotalDocs(), 5); + BaseTableDataManager tableDataManager = createTableManager(); + File defaultDataDir = tableDataManager.getSegmentDataDir(SEGMENT_NAME); + assertFalse(defaultDataDir.exists()); + tableDataManager.replaceSegmentIfCrcMismatch(segmentDataManager, zkMetadata, + createTierIndexLoadingConfig(DEFAULT_TABLE_CONFIG)); + assertTrue(defaultDataDir.exists()); + assertEquals(new SegmentMetadataImpl(defaultDataDir).getTotalDocs(), 5); // Configured dataDir for coolTier, thus move to new dir. - llmd = mock(SegmentMetadata.class); - when(llmd.getCrc()).thenReturn("0"); - tableConfig = createTableConfigWithTier(tierName, new File(TEMP_DIR, tierName)); - tmgr = createTableManager(); - IndexLoadingConfig loadingCfg = TableDataManagerTestUtils.createIndexLoadingConfig("tierBased", tableConfig, null); - tmgr.addOrReplaceSegment(segName, loadingCfg, zkmd, llmd); - File segDirOnTier = tmgr.getSegmentDataDir(segName, tierName, loadingCfg.getTableConfig()); - assertTrue(segDirOnTier.exists()); - assertFalse(defaultSegDir.exists()); - llmd = new SegmentMetadataImpl(segDirOnTier); - assertEquals(llmd.getTotalDocs(), 5); - assertEquals(llmd.getIndexDir(), segDirOnTier); + tableDataManager = createTableManager(); + tableDataManager.replaceSegmentIfCrcMismatch(segmentDataManager, zkMetadata, + createTierIndexLoadingConfig(TIER_TABLE_CONFIG)); + File tierDataDir = tableDataManager.getSegmentDataDir(SEGMENT_NAME, TIER_NAME, TIER_TABLE_CONFIG); + assertTrue(tierDataDir.exists()); + assertFalse(defaultDataDir.exists()); + SegmentMetadata segmentMetadata = new SegmentMetadataImpl(tierDataDir); + assertEquals(segmentMetadata.getTotalDocs(), 5); + assertEquals(segmentMetadata.getIndexDir(), tierDataDir); } @Test - public void testAddOrReplaceSegmentNoop() + public void testReplaceSegmentNoop() throws Exception { - SegmentZKMetadata zkmd = mock(SegmentZKMetadata.class); - when(zkmd.getCrc()).thenReturn(Long.valueOf(1024)); + String segmentName = "seg01"; + SegmentZKMetadata zkMetadata = mock(SegmentZKMetadata.class); + when(zkMetadata.getSegmentName()).thenReturn(segmentName); + when(zkMetadata.getCrc()).thenReturn(1024L); - SegmentMetadata llmd = mock(SegmentMetadata.class); - when(llmd.getCrc()).thenReturn("1024"); + ImmutableSegmentDataManager segmentDataManager = createImmutableSegmentDataManager(segmentName, 1024L); - BaseTableDataManager tmgr = createTableManager(); - assertFalse(tmgr.getSegmentDataDir("seg01").exists()); - tmgr.addOrReplaceSegment("seg01", TableDataManagerTestUtils.createIndexLoadingConfig(), zkmd, llmd); + BaseTableDataManager tableDataManager = createTableManager(); + assertFalse(tableDataManager.getSegmentDataDir(segmentName).exists()); + tableDataManager.replaceSegmentIfCrcMismatch(segmentDataManager, zkMetadata, new IndexLoadingConfig()); // As CRC is same, the index dir is left as is, so not get created by the test. - assertFalse(tmgr.getSegmentDataDir("seg01").exists()); + assertFalse(tableDataManager.getSegmentDataDir(segmentName).exists()); } @Test - public void testAddOrReplaceSegmentUseLocalCopy() + public void testAddNewSegmentUseLocalCopy() throws Exception { - TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build(); - String segName = "seg01"; - File localSegDir = createSegment(tableConfig, segName, SegmentVersion.v3, 5); - long segCrc = TableDataManagerTestUtils.getCRC(localSegDir, SegmentVersion.v3); + File indexDir = createSegment(SegmentVersion.v3, 5); + long crc = getCRC(indexDir); // Make local and remote CRC same to skip downloading raw segment. - SegmentZKMetadata zkmd = mock(SegmentZKMetadata.class); - when(zkmd.getCrc()).thenReturn(segCrc); - when(zkmd.getDownloadUrl()).thenReturn("file://somewhere"); - - BaseTableDataManager tmgr = createTableManager(); - tmgr.addOrReplaceSegment(segName, TableDataManagerTestUtils.createIndexLoadingConfig(), zkmd, null); - assertTrue(tmgr.getSegmentDataDir(segName).exists()); - SegmentMetadataImpl llmd = new SegmentMetadataImpl(tmgr.getSegmentDataDir(segName)); - assertEquals(llmd.getTotalDocs(), 5); - - FileUtils.deleteQuietly(localSegDir); + SegmentZKMetadata zkMetadata = mock(SegmentZKMetadata.class); + when(zkMetadata.getSegmentName()).thenReturn(SEGMENT_NAME); + when(zkMetadata.getCrc()).thenReturn(crc); + when(zkMetadata.getDownloadUrl()).thenReturn("file://somewhere"); + + BaseTableDataManager tableDataManager = createTableManager(); + tableDataManager.addNewOnlineSegment(zkMetadata, new IndexLoadingConfig()); + assertEquals(tableDataManager.getSegmentDataDir(SEGMENT_NAME), indexDir); + assertTrue(indexDir.exists()); + assertEquals(new SegmentMetadataImpl(indexDir).getTotalDocs(), 5); + + FileUtils.deleteQuietly(indexDir); try { - tmgr.addOrReplaceSegment(segName, TableDataManagerTestUtils.createIndexLoadingConfig(), zkmd, null); + tableDataManager.addNewOnlineSegment(zkMetadata, new IndexLoadingConfig()); fail(); } catch (Exception e) { // As expected, when local segment dir is missing, it tries to download @@ -429,267 +412,214 @@ public void testAddOrReplaceSegmentUseLocalCopy() } @Test - public void testAddOrReplaceSegmentUseLocalCopyNewTier() + public void testAddSegmentUseLocalCopyNewTier() throws Exception { - TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build(); - String segName = "seg01"; - File localSegDir = createSegment(tableConfig, segName, SegmentVersion.v3, 5); - long segCrc = TableDataManagerTestUtils.getCRC(localSegDir, SegmentVersion.v3); + File indexDir = createSegment(SegmentVersion.v3, 5); + long crc = getCRC(indexDir); // Make local and remote CRC same to skip downloading raw segment. - SegmentZKMetadata zkmd = mock(SegmentZKMetadata.class); - when(zkmd.getCrc()).thenReturn(segCrc); - when(zkmd.getDownloadUrl()).thenReturn("file://somewhere"); - String tierName = "coolTier"; - when(zkmd.getTier()).thenReturn(tierName); + SegmentZKMetadata zkMetadata = mock(SegmentZKMetadata.class); + when(zkMetadata.getSegmentName()).thenReturn(SEGMENT_NAME); + when(zkMetadata.getCrc()).thenReturn(crc); + when(zkMetadata.getDownloadUrl()).thenReturn("file://somewhere"); + when(zkMetadata.getTier()).thenReturn(TIER_NAME); // No dataDir for coolTier, thus stay on default tier. - BaseTableDataManager tmgr = createTableManager(); - tmgr.addOrReplaceSegment(segName, - TableDataManagerTestUtils.createIndexLoadingConfig("tierBased", tableConfig, null), zkmd, null); - assertTrue(tmgr.getSegmentDataDir(segName).exists()); - SegmentMetadataImpl llmd = new SegmentMetadataImpl(tmgr.getSegmentDataDir(segName)); - assertEquals(llmd.getTotalDocs(), 5); - assertEquals(llmd.getIndexDir(), localSegDir); + BaseTableDataManager tableDataManager = createTableManager(); + tableDataManager.addNewOnlineSegment(zkMetadata, createTierIndexLoadingConfig(DEFAULT_TABLE_CONFIG)); + assertEquals(tableDataManager.getSegmentDataDir(SEGMENT_NAME), indexDir); + assertTrue(indexDir.exists()); + assertEquals(new SegmentMetadataImpl(indexDir).getTotalDocs(), 5); // Configured dataDir for coolTier, thus move to new dir. - tableConfig = createTableConfigWithTier(tierName, new File(TEMP_DIR, tierName)); - IndexLoadingConfig loadingCfg = TableDataManagerTestUtils.createIndexLoadingConfig("tierBased", tableConfig, null); - File segDirOnTier = tmgr.getSegmentDataDir(segName, tierName, loadingCfg.getTableConfig()); - assertFalse(segDirOnTier.exists()); - // Move segDir to new tier to see if addOrReplaceSegment() can load segDir from new tier directly. - FileUtils.moveDirectory(localSegDir, segDirOnTier); - tmgr = createTableManager(); - tmgr.addOrReplaceSegment(segName, loadingCfg, zkmd, null); - llmd = new SegmentMetadataImpl(segDirOnTier); - assertEquals(llmd.getTotalDocs(), 5); - assertEquals(llmd.getIndexDir(), segDirOnTier); + File tierDataDir = tableDataManager.getSegmentDataDir(SEGMENT_NAME, TIER_NAME, TIER_TABLE_CONFIG); + assertFalse(tierDataDir.exists()); + // Move segDir to new tier to see if addNewOnlineSegment() can load segDir from new tier directly. + FileUtils.moveDirectory(indexDir, tierDataDir); + tableDataManager = createTableManager(); + tableDataManager.addNewOnlineSegment(zkMetadata, createTierIndexLoadingConfig(TIER_TABLE_CONFIG)); + assertEquals(new SegmentMetadataImpl(tierDataDir).getTotalDocs(), 5); } @Test - public void testAddOrReplaceSegmentUseBackupCopy() + public void testAddSegmentUseBackupCopy() throws Exception { - TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build(); - String segName = "seg01"; - File localSegDir = createSegment(tableConfig, segName, SegmentVersion.v3, 5); - long segCrc = TableDataManagerTestUtils.getCRC(localSegDir, SegmentVersion.v3); + File indexDir = createSegment(SegmentVersion.v3, 5); + long crc = getCRC(indexDir); // Make local and remote CRC same to skip downloading raw segment. - SegmentZKMetadata zkmd = mock(SegmentZKMetadata.class); - when(zkmd.getCrc()).thenReturn(segCrc); + SegmentZKMetadata zkMetadata = mock(SegmentZKMetadata.class); + when(zkMetadata.getSegmentName()).thenReturn(SEGMENT_NAME); + when(zkMetadata.getCrc()).thenReturn(crc); - BaseTableDataManager tmgr = createTableManager(); - File backup = tmgr.getSegmentDataDir(segName + CommonConstants.Segment.SEGMENT_BACKUP_DIR_SUFFIX); - localSegDir.renameTo(backup); + BaseTableDataManager tableDataManager = createTableManager(); + File backupDir = + tableDataManager.getSegmentDataDir(SEGMENT_NAME + CommonConstants.Segment.SEGMENT_BACKUP_DIR_SUFFIX); + assertTrue(indexDir.renameTo(backupDir)); - assertFalse(tmgr.getSegmentDataDir(segName).exists()); - tmgr.addOrReplaceSegment(segName, TableDataManagerTestUtils.createIndexLoadingConfig(), zkmd, null); - assertTrue(tmgr.getSegmentDataDir(segName).exists()); - SegmentMetadataImpl llmd = new SegmentMetadataImpl(tmgr.getSegmentDataDir(segName)); - assertEquals(llmd.getTotalDocs(), 5); + assertFalse(indexDir.exists()); + tableDataManager.addNewOnlineSegment(zkMetadata, new IndexLoadingConfig()); + assertEquals(tableDataManager.getSegmentDataDir(SEGMENT_NAME), indexDir); + assertTrue(indexDir.exists()); + assertEquals(new SegmentMetadataImpl(indexDir).getTotalDocs(), 5); } @Test - public void testAddOrReplaceSegmentStaleBackupCopy() + public void testAddSegmentStaleBackupCopy() throws Exception { - TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build(); - String segName = "seg01"; - SegmentZKMetadata zkmd = createRawSegment(tableConfig, segName, SegmentVersion.v3, 5); + SegmentZKMetadata zkMetadata = createRawSegment(SegmentVersion.v3, 5); - BaseTableDataManager tmgr = createTableManager(); // Create a local segment with fewer rows, making its CRC different from the raw segment. // So that the raw segment is downloaded and loaded in the end. - File localSegDir = createSegment(tableConfig, segName, SegmentVersion.v3, 3); - File backup = tmgr.getSegmentDataDir(segName + CommonConstants.Segment.SEGMENT_BACKUP_DIR_SUFFIX); - localSegDir.renameTo(backup); + BaseTableDataManager tableDataManager = createTableManager(); + File indexDir = createSegment(SegmentVersion.v3, 3); + File backupDir = + tableDataManager.getSegmentDataDir(SEGMENT_NAME + CommonConstants.Segment.SEGMENT_BACKUP_DIR_SUFFIX); + assertTrue(indexDir.renameTo(backupDir)); - assertFalse(tmgr.getSegmentDataDir(segName).exists()); - tmgr.addOrReplaceSegment(segName, TableDataManagerTestUtils.createIndexLoadingConfig(), zkmd, null); - assertTrue(tmgr.getSegmentDataDir(segName).exists()); - SegmentMetadataImpl llmd = new SegmentMetadataImpl(tmgr.getSegmentDataDir(segName)); - assertEquals(llmd.getTotalDocs(), 5); + assertFalse(indexDir.exists()); + tableDataManager.addNewOnlineSegment(zkMetadata, new IndexLoadingConfig()); + assertEquals(tableDataManager.getSegmentDataDir(SEGMENT_NAME), indexDir); + assertTrue(indexDir.exists()); + assertEquals(new SegmentMetadataImpl(indexDir).getTotalDocs(), 5); } @Test - public void testAddOrReplaceSegmentUpConvertVersion() + public void testAddSegmentUpConvertVersion() throws Exception { - TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build(); - String segName = "seg01"; - File localSegDir = createSegment(tableConfig, segName, SegmentVersion.v1, 5); - long segCrc = TableDataManagerTestUtils.getCRC(localSegDir, SegmentVersion.v1); + File indexDir = createSegment(SegmentVersion.v1, 5); + long crc = getCRC(indexDir); // Make local and remote CRC same to skip downloading raw segment. - SegmentZKMetadata zkmd = mock(SegmentZKMetadata.class); - when(zkmd.getCrc()).thenReturn(segCrc); + SegmentZKMetadata zkMetadata = mock(SegmentZKMetadata.class); + when(zkMetadata.getSegmentName()).thenReturn(SEGMENT_NAME); + when(zkMetadata.getCrc()).thenReturn(crc); // Require to use v3 format. - IndexLoadingConfig idxCfg = TableDataManagerTestUtils.createIndexLoadingConfig(); - idxCfg.setSegmentVersion(SegmentVersion.v3); + IndexLoadingConfig indexLoadingConfig = new IndexLoadingConfig(); + indexLoadingConfig.setSegmentVersion(SegmentVersion.v3); - BaseTableDataManager tmgr = createTableManager(); - tmgr.addOrReplaceSegment(segName, idxCfg, zkmd, null); - assertTrue(tmgr.getSegmentDataDir(segName).exists()); - SegmentMetadataImpl llmd = new SegmentMetadataImpl(tmgr.getSegmentDataDir(segName)); - assertEquals(llmd.getVersion(), SegmentVersion.v3); - assertEquals(llmd.getTotalDocs(), 5); + BaseTableDataManager tableDataManager = createTableManager(); + tableDataManager.addNewOnlineSegment(zkMetadata, indexLoadingConfig); + assertEquals(tableDataManager.getSegmentDataDir(SEGMENT_NAME), indexDir); + assertTrue(indexDir.exists()); + SegmentMetadata segmentMetadata = new SegmentMetadataImpl(indexDir); + assertEquals(segmentMetadata.getTotalDocs(), 5); + assertEquals(segmentMetadata.getVersion(), SegmentVersion.v3); } @Test - public void testAddOrReplaceSegmentDownConvertVersion() + public void testAddSegmentDownConvertVersion() throws Exception { - TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build(); - String segName = "seg01"; - File localSegDir = createSegment(tableConfig, segName, SegmentVersion.v3, 5); - long segCrc = TableDataManagerTestUtils.getCRC(localSegDir, SegmentVersion.v3); + File indexDir = createSegment(SegmentVersion.v3, 5); + long crc = getCRC(indexDir); // Make local and remote CRC same to skip downloading raw segment. - SegmentZKMetadata zkmd = mock(SegmentZKMetadata.class); - when(zkmd.getCrc()).thenReturn(segCrc); + SegmentZKMetadata zkMetadata = mock(SegmentZKMetadata.class); + when(zkMetadata.getSegmentName()).thenReturn(SEGMENT_NAME); + when(zkMetadata.getCrc()).thenReturn(crc); // Require to use v1 format. - IndexLoadingConfig idxCfg = TableDataManagerTestUtils.createIndexLoadingConfig(); - idxCfg.setSegmentVersion(SegmentVersion.v1); + IndexLoadingConfig indexLoadingConfig = new IndexLoadingConfig(); + indexLoadingConfig.setSegmentVersion(SegmentVersion.v1); - BaseTableDataManager tmgr = createTableManager(); - tmgr.addOrReplaceSegment(segName, idxCfg, zkmd, null); - assertTrue(tmgr.getSegmentDataDir(segName).exists()); - SegmentMetadataImpl llmd = new SegmentMetadataImpl(tmgr.getSegmentDataDir(segName)); + BaseTableDataManager tableDataManager = createTableManager(); + tableDataManager.addNewOnlineSegment(zkMetadata, indexLoadingConfig); + assertEquals(tableDataManager.getSegmentDataDir(SEGMENT_NAME), indexDir); + assertTrue(indexDir.exists()); // The existing segment preprocessing logic doesn't down convert segment format. - assertEquals(llmd.getVersion(), SegmentVersion.v3); - assertEquals(llmd.getTotalDocs(), 5); + SegmentMetadata segmentMetadata = new SegmentMetadataImpl(indexDir); + assertEquals(segmentMetadata.getTotalDocs(), 5); + assertEquals(segmentMetadata.getVersion(), SegmentVersion.v3); } @Test - public void testAddOrReplaceSegmentAddIndex() + public void testAddSegmentAddIndex() throws Exception { - TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build(); - String segName = "seg01"; - File localSegDir = createSegment(tableConfig, segName, SegmentVersion.v3, 5); - long segCrc = TableDataManagerTestUtils.getCRC(localSegDir, SegmentVersion.v3); - assertFalse(hasInvertedIndex(localSegDir, STRING_COLUMN, SegmentVersion.v3)); - assertFalse(hasInvertedIndex(localSegDir, LONG_COLUMN, SegmentVersion.v3)); + File indexDir = createSegment(SegmentVersion.v3, 5); + long crc = getCRC(indexDir); + assertFalse(hasInvertedIndex(indexDir, STRING_COLUMN)); + assertFalse(hasInvertedIndex(indexDir, LONG_COLUMN)); // Make local and remote CRC same to skip downloading raw segment. - SegmentZKMetadata zkmd = mock(SegmentZKMetadata.class); - when(zkmd.getCrc()).thenReturn(segCrc); + SegmentZKMetadata zkMetadata = mock(SegmentZKMetadata.class); + when(zkMetadata.getSegmentName()).thenReturn(SEGMENT_NAME); + when(zkMetadata.getCrc()).thenReturn(crc); // Require to add indices. - IndexLoadingConfig idxCfg = TableDataManagerTestUtils.createIndexLoadingConfig(); - idxCfg.setSegmentVersion(SegmentVersion.v3); - idxCfg.setInvertedIndexColumns(new HashSet<>(Arrays.asList(STRING_COLUMN, LONG_COLUMN))); + IndexLoadingConfig indexLoadingConfig = new IndexLoadingConfig(); + indexLoadingConfig.setSegmentVersion(SegmentVersion.v3); + indexLoadingConfig.setInvertedIndexColumns(new HashSet<>(Arrays.asList(STRING_COLUMN, LONG_COLUMN))); - BaseTableDataManager tmgr = createTableManager(); - tmgr.addOrReplaceSegment(segName, idxCfg, zkmd, null); - assertTrue(tmgr.getSegmentDataDir(segName).exists()); - SegmentMetadataImpl llmd = new SegmentMetadataImpl(tmgr.getSegmentDataDir(segName)); - assertEquals(llmd.getTotalDocs(), 5); - assertTrue(hasInvertedIndex(tmgr.getSegmentDataDir(segName), STRING_COLUMN, SegmentVersion.v3)); - assertTrue(hasInvertedIndex(tmgr.getSegmentDataDir(segName), LONG_COLUMN, SegmentVersion.v3)); + BaseTableDataManager tableDataManager = createTableManager(); + tableDataManager.addNewOnlineSegment(zkMetadata, indexLoadingConfig); + assertEquals(tableDataManager.getSegmentDataDir(SEGMENT_NAME), indexDir); + assertTrue(indexDir.exists()); + SegmentMetadataImpl segmentMetadata = new SegmentMetadataImpl(indexDir); + assertEquals(segmentMetadata.getTotalDocs(), 5); + assertTrue(hasInvertedIndex(indexDir, STRING_COLUMN)); + assertTrue(hasInvertedIndex(indexDir, LONG_COLUMN)); } @Test public void testDownloadAndDecrypt() throws Exception { - File tempInput = new File(TEMP_DIR, "tmp.txt"); - FileUtils.write(tempInput, "this is from somewhere remote"); - - SegmentZKMetadata zkmd = mock(SegmentZKMetadata.class); - when(zkmd.getDownloadUrl()).thenReturn("file://" + tempInput.getAbsolutePath()); - - BaseTableDataManager tmgr = createTableManager(); - File tempRootDir = tmgr.getTmpSegmentDataDir("test-download-decrypt"); - - File tarFile = tmgr.downloadAndDecrypt("seg01", zkmd, tempRootDir); - assertEquals(FileUtils.readFileToString(tarFile), "this is from somewhere remote"); - - when(zkmd.getCrypterName()).thenReturn("fakePinotCrypter"); - tarFile = tmgr.downloadAndDecrypt("seg01", zkmd, tempRootDir); - assertEquals(FileUtils.readFileToString(tarFile), "this is from somewhere remote"); + File tempDir = new File(TEMP_DIR, "testDownloadAndDecrypt"); + String fileName = "tmp.txt"; + FileUtils.write(new File(tempDir, fileName), "this is from somewhere remote"); + String tarFileName = SEGMENT_NAME + TarGzCompressionUtils.TAR_GZ_FILE_EXTENSION; + File tempTarFile = new File(TEMP_DIR, tarFileName); + TarGzCompressionUtils.createTarGzFile(tempDir, tempTarFile); + + SegmentZKMetadata zkMetadata = mock(SegmentZKMetadata.class); + when(zkMetadata.getSegmentName()).thenReturn(SEGMENT_NAME); + when(zkMetadata.getDownloadUrl()).thenReturn("file://" + tempTarFile.getAbsolutePath()); + + BaseTableDataManager tableDataManager = createTableManager(); + File indexDir = tableDataManager.downloadSegment(zkMetadata); + assertEquals(FileUtils.readFileToString(new File(indexDir, fileName)), "this is from somewhere remote"); + + FileUtils.deleteDirectory(indexDir); + when(zkMetadata.getCrypterName()).thenReturn("fakePinotCrypter"); + indexDir = tableDataManager.downloadSegment(zkMetadata); + assertEquals(FileUtils.readFileToString(new File(indexDir, fileName)), "this is from somewhere remote"); FakePinotCrypter fakeCrypter = (FakePinotCrypter) PinotCrypterFactory.create("fakePinotCrypter"); - String parentDir = TABLE_NAME_WITH_TYPE + "/tmp/test-download-decrypt/"; - assertTrue(fakeCrypter._origFile.getAbsolutePath().endsWith(parentDir + "seg01.tar.gz.enc")); - assertTrue(fakeCrypter._decFile.getAbsolutePath().endsWith(parentDir + "seg01.tar.gz")); + assertTrue(fakeCrypter._origFile.getAbsolutePath().endsWith(tarFileName + ".enc")); + assertTrue(fakeCrypter._decFile.getAbsolutePath().endsWith(tarFileName)); try { // Set maxRetry to 0 to cause retry failure immediately. Map properties = new HashMap<>(); properties.put(BaseSegmentFetcher.RETRY_COUNT_CONFIG_KEY, 0); SegmentFetcherFactory.init(new PinotConfiguration(properties)); - tmgr.downloadAndDecrypt("seg01", zkmd, tempRootDir); + tableDataManager.downloadSegment(zkMetadata); fail(); } catch (AttemptsExceededException e) { assertEquals(e.getMessage(), "Operation failed after 0 attempts"); } } - // case 2: if the attempt to download from deep storage exceeds, invoke downloadFromPeers. - @Test - public void testDownloadAndDecryptPeerDownload() throws Exception { - String backupCopyURI = mockRemoteCopy().toString(); - SegmentZKMetadata zkmd = mock(SegmentZKMetadata.class); - when(zkmd.getDownloadUrl()).thenReturn(backupCopyURI); - - TableDataManagerConfig config = createDefaultTableDataManagerConfig(); - when(config.getTablePeerDownloadScheme()).thenReturn("http"); - BaseTableDataManager tmgr = createSpyOfflineTableManager(config); - File tempRootDir = tmgr.getTmpSegmentDataDir("test-download-decrypt-peer"); - - // As the case 2 description says, we need to mock the static method fetchAndDecryptSegmentToLocal to - // throw the AttemptExceed exception; Due to the constraint that mockito static cannot do argument matching, - // e.g., any(), we have to pass exact argument value when mocking fetchAndDecryptSegmentToLocal. - // However, the second argument of File is internally created, which cannot be mocked. - // Luckily, the File class's equal method only compares the path. Thus, we can create a file with identical path - // and use it to mock the fetchAndDecryptSegmentToLocal - File destFile = new File(tempRootDir, "seg01" + TarGzCompressionUtils.TAR_GZ_FILE_EXTENSION); - doNothing().when(tmgr).downloadFromPeersWithoutStreaming("seg01", zkmd, destFile); - try (MockedStatic mockSegFactory = mockStatic(SegmentFetcherFactory.class)) { - mockSegFactory.when(() -> SegmentFetcherFactory.fetchAndDecryptSegmentToLocal(backupCopyURI, destFile, null)) - .thenThrow(new AttemptsExceededException("fake attempt exceeds exception")); - tmgr.downloadAndDecrypt("seg01", zkmd, tempRootDir); - } - verify(tmgr, times(1)).downloadFromPeersWithoutStreaming("seg01", zkmd, destFile); - } - - // happy case: download from peers - @Test - public void testDownloadFromPeersWithoutStreaming() throws Exception { - URI uri = mockRemoteCopy(); - TableDataManagerConfig config = createDefaultTableDataManagerConfig(); - when(config.getTablePeerDownloadScheme()).thenReturn("http"); - HelixManager mockedHelix = mock(HelixManager.class); - BaseTableDataManager tmgr = createTableManager(config, mockedHelix); - File tempRootDir = tmgr.getTmpSegmentDataDir("test-download-peer-without-streaming"); - File destFile = new File(tempRootDir, "seg01" + TarGzCompressionUtils.TAR_GZ_FILE_EXTENSION); - try (MockedStatic mockPeerSegFinder = mockStatic(PeerServerSegmentFinder.class)) { - mockPeerSegFinder.when(() -> PeerServerSegmentFinder.getPeerServerURIs( - "seg01", "http", mockedHelix, TABLE_NAME_WITH_TYPE)) - .thenReturn(Collections.singletonList(uri)); - tmgr.downloadFromPeersWithoutStreaming("seg01", mock(SegmentZKMetadata.class), destFile); - } - assertEquals(FileUtils.readFileToString(destFile), "this is from somewhere remote"); - } - @Test public void testUntarAndMoveSegment() throws IOException { - BaseTableDataManager tmgr = createTableManager(); - File tempRootDir = tmgr.getTmpSegmentDataDir("test-untar-move"); + BaseTableDataManager tableDataManager = createTableManager(); + File tempRootDir = tableDataManager.getTmpSegmentDataDir("test-untar-move"); // All input and intermediate files are put in the tempRootDir. - File tempTar = new File(tempRootDir, "seg01" + TarGzCompressionUtils.TAR_GZ_FILE_EXTENSION); - File tempInputDir = new File(tempRootDir, "seg01_input"); + File tempTar = new File(tempRootDir, SEGMENT_NAME + TarGzCompressionUtils.TAR_GZ_FILE_EXTENSION); + File tempInputDir = new File(tempRootDir, "input"); FileUtils.write(new File(tempInputDir, "tmp.txt"), "this is in segment dir"); TarGzCompressionUtils.createTarGzFile(tempInputDir, tempTar); FileUtils.deleteQuietly(tempInputDir); // The destination is the segment directory at the same level of tempRootDir. - File indexDir = tmgr.untarAndMoveSegment("seg01", tempTar, tempRootDir); - assertEquals(indexDir, tmgr.getSegmentDataDir("seg01")); - assertEquals(FileUtils.readFileToString(new File(indexDir, "tmp.txt")), "this is in segment dir"); + File untarredFile = tableDataManager.untarAndMoveSegment(SEGMENT_NAME, tempTar, tempRootDir); + assertEquals(untarredFile, tableDataManager.getSegmentDataDir(SEGMENT_NAME)); + assertEquals(FileUtils.readFileToString(new File(untarredFile, "tmp.txt")), "this is in segment dir"); try { - tmgr.untarAndMoveSegment("seg01", new File(tempRootDir, "unknown.txt"), TEMP_DIR); + tableDataManager.untarAndMoveSegment(SEGMENT_NAME, new File(tempRootDir, "unknown.txt"), TEMP_DIR); fail(); } catch (Exception e) { // expected. @@ -713,64 +643,36 @@ public void encrypt(File origFile, File encFile) { public void decrypt(File origFile, File decFile) { _origFile = origFile; _decFile = decFile; + origFile.renameTo(decFile); } } - private static BaseTableDataManager createTableManager() { - TableDataManagerConfig config = createDefaultTableDataManagerConfig(); - - OfflineTableDataManager tableDataManager = new OfflineTableDataManager(); - tableDataManager.init(config, "dummyInstance", mock(ZkHelixPropertyStore.class), - new ServerMetrics(PinotMetricUtils.getPinotMetricsRegistry()), mock(HelixManager.class), null, - new TableDataManagerParams(0, false, -1)); - tableDataManager.start(); - return tableDataManager; + private static OfflineTableDataManager createTableManager() { + return createTableManager(createDefaultInstanceDataManagerConfig()); } - private static BaseTableDataManager createTableManager(TableDataManagerConfig config, HelixManager helixManager) { + private static OfflineTableDataManager createTableManager(InstanceDataManagerConfig instanceDataManagerConfig) { + HelixManager helixManager = mock(HelixManager.class); + SegmentLocks segmentLocks = new SegmentLocks(); OfflineTableDataManager tableDataManager = new OfflineTableDataManager(); - tableDataManager.init(config, "dummyInstance", mock(ZkHelixPropertyStore.class), - new ServerMetrics(PinotMetricUtils.getPinotMetricsRegistry()), helixManager, null, - new TableDataManagerParams(0, false, -1)); - tableDataManager.start(); + tableDataManager.init(instanceDataManagerConfig, helixManager, segmentLocks, DEFAULT_TABLE_CONFIG, null, null); return tableDataManager; } - private static OfflineTableDataManager createSpyOfflineTableManager(TableDataManagerConfig tableDataManagerConfig) { - OfflineTableDataManager tableDataManager = new OfflineTableDataManager(); - tableDataManager.init(tableDataManagerConfig, "dummyInstance", mock(ZkHelixPropertyStore.class), - new ServerMetrics(PinotMetricUtils.getPinotMetricsRegistry()), mock(HelixManager.class), null, - new TableDataManagerParams(0, false, -1)); - tableDataManager.start(); - return Mockito.spy(tableDataManager); - } - - private static TableDataManagerConfig createDefaultTableDataManagerConfig() { - TableDataManagerConfig config = mock(TableDataManagerConfig.class); - when(config.getTableName()).thenReturn(TABLE_NAME_WITH_TYPE); - when(config.getDataDir()).thenReturn(TABLE_DATA_DIR.getAbsolutePath()); - when(config.getAuthConfig()).thenReturn(new MapConfiguration(Collections.emptyMap())); + private static InstanceDataManagerConfig createDefaultInstanceDataManagerConfig() { + InstanceDataManagerConfig config = mock(InstanceDataManagerConfig.class); + when(config.getInstanceDataDir()).thenReturn(TEMP_DIR.getAbsolutePath()); return config; } - private static SegmentZKMetadata createRawSegment(TableConfig tableConfig, String segName, SegmentVersion segVer, - int rowCnt) - throws Exception { - File localSegDir = createSegment(tableConfig, segName, segVer, rowCnt); - return TableDataManagerTestUtils.makeRawSegment(segName, localSegDir, - new File(TEMP_DIR, segName + TarGzCompressionUtils.TAR_GZ_FILE_EXTENSION), true); - } - - private static File createSegment(TableConfig tableConfig, String segName, SegmentVersion segVer, int rowCnt) + private static File createSegment(SegmentVersion segmentVersion, int numRows) throws Exception { - Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(STRING_COLUMN, FieldSpec.DataType.STRING) - .addMetric(LONG_COLUMN, FieldSpec.DataType.LONG).build(); - SegmentGeneratorConfig config = new SegmentGeneratorConfig(tableConfig, schema); + SegmentGeneratorConfig config = new SegmentGeneratorConfig(DEFAULT_TABLE_CONFIG, SCHEMA); config.setOutDir(TABLE_DATA_DIR.getAbsolutePath()); - config.setSegmentName(segName); - config.setSegmentVersion(segVer); + config.setSegmentName(SEGMENT_NAME); + config.setSegmentVersion(segmentVersion); List rows = new ArrayList<>(3); - for (int i = 0; i < rowCnt; i++) { + for (int i = 0; i < numRows; i++) { GenericRow row = new GenericRow(); row.putValue(STRING_COLUMN, STRING_VALUES[i]); row.putValue(LONG_COLUMN, LONG_VALUES[i]); @@ -779,32 +681,64 @@ private static File createSegment(TableConfig tableConfig, String segName, Segme SegmentIndexCreationDriverImpl driver = new SegmentIndexCreationDriverImpl(); driver.init(config, new GenericRowRecordReader(rows)); driver.build(); - return new File(TABLE_DATA_DIR, segName); + return new File(TABLE_DATA_DIR, SEGMENT_NAME); + } + + private static SegmentZKMetadata createRawSegment(SegmentVersion segmentVersion, int numRows) + throws Exception { + File indexDir = createSegment(segmentVersion, numRows); + return makeRawSegment(indexDir, new File(TEMP_DIR, SEGMENT_NAME + TarGzCompressionUtils.TAR_GZ_FILE_EXTENSION), + true); + } + + private static SegmentZKMetadata makeRawSegment(File indexDir, File rawSegmentFile, boolean deleteIndexDir) + throws Exception { + long crc = getCRC(indexDir); + SegmentZKMetadata zkMetadata = new SegmentZKMetadata(SEGMENT_NAME); + TarGzCompressionUtils.createTarGzFile(indexDir, rawSegmentFile); + zkMetadata.setDownloadUrl("file://" + rawSegmentFile.getAbsolutePath()); + zkMetadata.setCrc(crc); + if (deleteIndexDir) { + FileUtils.deleteQuietly(indexDir); + } + return zkMetadata; } - private static boolean hasInvertedIndex(File segDir, String colName, SegmentVersion segVer) + private static long getCRC(File indexDir) throws IOException { - File parentDir = segDir; - if (segVer == SegmentVersion.v3) { - parentDir = new File(segDir, "v3"); + File creationMetaFile = SegmentDirectoryPaths.findCreationMetaFile(indexDir); + assertNotNull(creationMetaFile); + try (DataInputStream in = new DataInputStream(new FileInputStream(creationMetaFile))) { + return in.readLong(); } - File idxMapFile = new File(parentDir, V1Constants.INDEX_MAP_FILE_NAME); - return FileUtils.readFileToString(idxMapFile).contains(colName + ".inverted_index"); } - private TableConfig createTableConfigWithTier(String tierName, File dataDir) { - return new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).setTierConfigList( - Collections.singletonList(new TierConfig(tierName, TierFactory.TIME_SEGMENT_SELECTOR_TYPE, "3d", null, - TierFactory.PINOT_SERVER_STORAGE_TYPE, "tag_OFFLINE", null, - Collections.singletonMap("dataDir", dataDir.getAbsolutePath())))).build(); + private IndexLoadingConfig createTierIndexLoadingConfig(TableConfig tableConfig) { + InstanceDataManagerConfig instanceDataManagerConfig = mock(InstanceDataManagerConfig.class); + when(instanceDataManagerConfig.getSegmentDirectoryLoader()).thenReturn(TIER_SEGMENT_DIRECTORY_LOADER); + when(instanceDataManagerConfig.getConfig()).thenReturn(new PinotConfiguration()); + IndexLoadingConfig indexLoadingConfig = new IndexLoadingConfig(instanceDataManagerConfig, tableConfig, null); + indexLoadingConfig.setTableDataDir(TEMP_DIR.getAbsolutePath() + File.separator + tableConfig.getTableName()); + indexLoadingConfig.setInstanceTierConfigs(Map.of()); + indexLoadingConfig.setSegmentTier(TIER_NAME); + return indexLoadingConfig; } - private static URI mockRemoteCopy() throws IOException, URISyntaxException { - File tempInput = new File(TEMP_DIR, "tmp.txt"); - FileUtils.write(tempInput, "this is from somewhere remote"); + private ImmutableSegmentDataManager createImmutableSegmentDataManager(String segmentName, long crc) { + ImmutableSegmentDataManager segmentDataManager = mock(ImmutableSegmentDataManager.class); + when(segmentDataManager.getSegmentName()).thenReturn(segmentName); + ImmutableSegment immutableSegment = mock(ImmutableSegment.class); + when(segmentDataManager.getSegment()).thenReturn(immutableSegment); + SegmentMetadata segmentMetadata = mock(SegmentMetadata.class); + when(immutableSegment.getSegmentMetadata()).thenReturn(segmentMetadata); + when(segmentMetadata.getCrc()).thenReturn(Long.toString(crc)); + return segmentDataManager; + } - String backupCopyURI = "file://" + tempInput.getAbsolutePath(); - URI uri = new URI(backupCopyURI); - return uri; + private static boolean hasInvertedIndex(File indexDir, String columnName) + throws IOException { + File indexMapFile = + new File(new File(indexDir, SegmentDirectoryPaths.V3_SUBDIRECTORY_NAME), V1Constants.INDEX_MAP_FILE_NAME); + return FileUtils.readFileToString(indexMapFile, StandardCharsets.UTF_8).contains(columnName + ".inverted_index"); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/TableDataManagerTestUtils.java b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/TableDataManagerTestUtils.java deleted file mode 100644 index 510e6c1232b0..000000000000 --- a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/TableDataManagerTestUtils.java +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.data.manager; - -import java.io.DataInputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import javax.annotation.Nullable; -import org.apache.commons.io.FileUtils; -import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; -import org.apache.pinot.common.utils.TarGzCompressionUtils; -import org.apache.pinot.common.utils.fetcher.BaseSegmentFetcher; -import org.apache.pinot.common.utils.fetcher.SegmentFetcherFactory; -import org.apache.pinot.segment.local.segment.index.loader.IndexLoadingConfig; -import org.apache.pinot.segment.spi.V1Constants; -import org.apache.pinot.segment.spi.creator.SegmentVersion; -import org.apache.pinot.spi.config.instance.InstanceDataManagerConfig; -import org.apache.pinot.spi.config.table.TableConfig; -import org.apache.pinot.spi.crypt.PinotCrypterFactory; -import org.apache.pinot.spi.data.Schema; -import org.apache.pinot.spi.env.PinotConfiguration; -import org.apache.pinot.spi.utils.ReadMode; - -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - - -public class TableDataManagerTestUtils { - private TableDataManagerTestUtils() { - } - - public static long getCRC(File segDir, SegmentVersion segVer) - throws IOException { - File parentDir = segDir; - if (segVer == SegmentVersion.v3) { - parentDir = new File(segDir, "v3"); - } - File crcFile = new File(parentDir, V1Constants.SEGMENT_CREATION_META); - try (DataInputStream ds = new DataInputStream(new FileInputStream(crcFile))) { - return ds.readLong(); - } - } - - public static SegmentZKMetadata makeRawSegment(String segName, File localSegDir, File rawSegDir, - boolean deleteLocalSegDir) - throws Exception { - long segCrc = TableDataManagerTestUtils.getCRC(localSegDir, SegmentVersion.v3); - SegmentZKMetadata zkmd = new SegmentZKMetadata(segName); - TarGzCompressionUtils.createTarGzFile(localSegDir, rawSegDir); - zkmd.setDownloadUrl("file://" + rawSegDir.getAbsolutePath()); - zkmd.setCrc(segCrc); - if (deleteLocalSegDir) { - FileUtils.deleteQuietly(localSegDir); - } - return zkmd; - } - - public static void initSegmentFetcher() - throws Exception { - Map properties = new HashMap<>(); - properties.put(BaseSegmentFetcher.RETRY_COUNT_CONFIG_KEY, 3); - properties.put(BaseSegmentFetcher.RETRY_WAIT_MS_CONFIG_KEY, 100); - properties.put(BaseSegmentFetcher.RETRY_DELAY_SCALE_FACTOR_CONFIG_KEY, 5); - SegmentFetcherFactory.init(new PinotConfiguration(properties)); - - // Setup crypter - properties.put("class.fakePinotCrypter", BaseTableDataManagerTest.FakePinotCrypter.class.getName()); - PinotCrypterFactory.init(new PinotConfiguration(properties)); - } - - public static IndexLoadingConfig createIndexLoadingConfig(String segDirLoader, TableConfig tableConfig, - @Nullable Schema schema) { - InstanceDataManagerConfig idmc = mock(InstanceDataManagerConfig.class); - when(idmc.getSegmentDirectoryLoader()).thenReturn(segDirLoader); - when(idmc.getConfig()).thenReturn(new PinotConfiguration()); - IndexLoadingConfig indexLoadingConfig = new IndexLoadingConfig(idmc, tableConfig, schema); - indexLoadingConfig.setSegmentVersion(SegmentVersion.v3); - indexLoadingConfig.setReadMode(ReadMode.mmap); - return indexLoadingConfig; - } - - public static IndexLoadingConfig createIndexLoadingConfig() { - IndexLoadingConfig indexLoadingConfig = new IndexLoadingConfig(); - indexLoadingConfig.setSegmentVersion(SegmentVersion.v3); - indexLoadingConfig.setReadMode(ReadMode.mmap); - return indexLoadingConfig; - } -} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/offline/DimensionTableDataManagerTest.java b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/offline/DimensionTableDataManagerTest.java index 132b94e72a70..fd6769a0ad99 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/offline/DimensionTableDataManagerTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/offline/DimensionTableDataManagerTest.java @@ -19,6 +19,9 @@ package org.apache.pinot.core.data.manager.offline; import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; import java.net.URL; import java.util.Collections; import java.util.List; @@ -32,27 +35,30 @@ import org.apache.pinot.common.utils.SchemaUtils; import org.apache.pinot.common.utils.config.TableConfigUtils; import org.apache.pinot.segment.local.data.manager.SegmentDataManager; -import org.apache.pinot.segment.local.data.manager.TableDataManagerConfig; -import org.apache.pinot.segment.local.data.manager.TableDataManagerParams; +import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; import org.apache.pinot.segment.local.segment.creator.SegmentTestUtils; import org.apache.pinot.segment.local.segment.creator.impl.SegmentCreationDriverFactory; import org.apache.pinot.segment.local.segment.index.loader.IndexLoadingConfig; import org.apache.pinot.segment.local.segment.index.loader.LoaderTest; +import org.apache.pinot.segment.local.utils.SegmentLocks; import org.apache.pinot.segment.spi.SegmentMetadata; import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; import org.apache.pinot.segment.spi.creator.SegmentIndexCreationDriver; import org.apache.pinot.segment.spi.index.metadata.SegmentMetadataImpl; +import org.apache.pinot.spi.config.instance.InstanceDataManagerConfig; import org.apache.pinot.spi.config.table.DimensionTableConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.data.FieldSpec; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.spi.data.readers.FileFormat; import org.apache.pinot.spi.data.readers.GenericRow; import org.apache.pinot.spi.data.readers.PrimaryKey; -import org.apache.pinot.spi.metrics.PinotMetricUtils; +import org.apache.pinot.spi.utils.JsonUtils; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.apache.pinot.spi.utils.builder.TableNameBuilder; +import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -62,6 +68,7 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; +import static org.testng.Assert.fail; @SuppressWarnings("unchecked") @@ -69,7 +76,9 @@ public class DimensionTableDataManagerTest { private static final File TEMP_DIR = new File(FileUtils.getTempDirectory(), LoaderTest.class.getName()); private static final String RAW_TABLE_NAME = "dimBaseballTeams"; private static final String OFFLINE_TABLE_NAME = TableNameBuilder.OFFLINE.tableNameWithType(RAW_TABLE_NAME); - private static final String AVRO_DATA_PATH = "data/dimBaseballTeams.avro"; + private static final String CSV_DATA_PATH = "data/dimBaseballTeams.csv"; + private static final String SCHEMA_PATH = "data/dimBaseballTeams_schema.json"; + private static final String TABLE_CONFIG_PATH = "data/dimBaseballTeams_config.json"; private File _indexDir; private IndexLoadingConfig _indexLoadingConfig; @@ -79,21 +88,32 @@ public class DimensionTableDataManagerTest { @BeforeClass public void setUp() throws Exception { + ServerMetrics.register(mock(ServerMetrics.class)); + // prepare segment data - URL resourceUrl = getClass().getClassLoader().getResource(AVRO_DATA_PATH); - assertNotNull(resourceUrl); - File avroFile = new File(resourceUrl.getFile()); + URL dataPathUrl = getClass().getClassLoader().getResource(CSV_DATA_PATH); + URL schemaPathUrl = getClass().getClassLoader().getResource(SCHEMA_PATH); + URL configPathUrl = getClass().getClassLoader().getResource(TABLE_CONFIG_PATH); + assertNotNull(dataPathUrl); + assertNotNull(schemaPathUrl); + assertNotNull(configPathUrl); + File csvFile = new File(dataPathUrl.getFile()); + TableConfig tableConfig = createTableConfig(new File(configPathUrl.getFile())); + Schema schema = createSchema(new File(schemaPathUrl.getFile())); // create segment + File tableDataDir = new File(TEMP_DIR, OFFLINE_TABLE_NAME); + SegmentGeneratorConfig segmentGeneratorConfig = - SegmentTestUtils.getSegmentGeneratorConfigWithoutTimeColumn(avroFile, TEMP_DIR, RAW_TABLE_NAME); + SegmentTestUtils.getSegmentGeneratorConfig(csvFile, FileFormat.CSV, tableDataDir, RAW_TABLE_NAME, tableConfig, + schema); SegmentIndexCreationDriver driver = SegmentCreationDriverFactory.get(null); driver.init(segmentGeneratorConfig); driver.build(); String segmentName = driver.getSegmentName(); - _indexDir = new File(TEMP_DIR, segmentName); - _indexLoadingConfig = new IndexLoadingConfig(); + _indexDir = new File(tableDataDir, segmentName); + _indexLoadingConfig = new IndexLoadingConfig(tableConfig, schema); _segmentMetadata = new SegmentMetadataImpl(_indexDir); _segmentZKMetadata = new SegmentZKMetadata(segmentName); _segmentZKMetadata.setCrc(Long.parseLong(_segmentMetadata.getCrc())); @@ -110,9 +130,9 @@ private Schema getSchema() { .setPrimaryKeyColumns(Collections.singletonList("teamID")).build(); } - private TableConfig getTableConfig(boolean disablePreload) { - DimensionTableConfig dimensionTableConfig = new DimensionTableConfig(disablePreload); - return new TableConfigBuilder(TableType.OFFLINE).setTableName("dimBaseballTeams").setSchemaName("dimBaseballTeams") + private TableConfig getTableConfig(boolean disablePreload, boolean errorOnDuplicatePrimaryKey) { + DimensionTableConfig dimensionTableConfig = new DimensionTableConfig(disablePreload, errorOnDuplicatePrimaryKey); + return new TableConfigBuilder(TableType.OFFLINE).setTableName("dimBaseballTeams") .setDimensionTableConfig(dimensionTableConfig).build(); } @@ -124,17 +144,12 @@ private Schema getSchemaWithExtraColumn() { } private DimensionTableDataManager makeTableDataManager(HelixManager helixManager) { + InstanceDataManagerConfig instanceDataManagerConfig = mock(InstanceDataManagerConfig.class); + when(instanceDataManagerConfig.getInstanceDataDir()).thenReturn(TEMP_DIR.getAbsolutePath()); + TableConfig tableConfig = getTableConfig(false, false); DimensionTableDataManager tableDataManager = DimensionTableDataManager.createInstanceByTableName(OFFLINE_TABLE_NAME); - TableDataManagerConfig config; - { - config = mock(TableDataManagerConfig.class); - when(config.getTableName()).thenReturn(OFFLINE_TABLE_NAME); - when(config.getDataDir()).thenReturn(TEMP_DIR.getAbsolutePath()); - } - tableDataManager.init(config, "dummyInstance", helixManager.getHelixPropertyStore(), - new ServerMetrics(PinotMetricUtils.getPinotMetricsRegistry()), helixManager, null, - new TableDataManagerParams(0, false, -1)); + tableDataManager.init(instanceDataManagerConfig, helixManager, new SegmentLocks(), tableConfig, null, null); tableDataManager.start(); return tableDataManager; } @@ -156,13 +171,13 @@ public void testInstantiation() assertEquals(tableDataManager, returnedManager, "Manager should return already created instance"); // assert that segments are released after loading data - tableDataManager.addSegment(_indexDir, _indexLoadingConfig); + tableDataManager.addSegment(ImmutableSegmentLoader.load(_indexDir, _indexLoadingConfig)); for (SegmentDataManager segmentManager : returnedManager.acquireAllSegments()) { assertEquals(segmentManager.getReferenceCount() - 1, // Subtract this acquisition 1, // Default ref count "Reference counts should be same before and after segment loading."); returnedManager.releaseSegment(segmentManager); - returnedManager.removeSegment(segmentManager.getSegmentName()); + returnedManager.offloadSegment(segmentManager.getSegmentName()); } // try fetching non-existent table @@ -184,7 +199,7 @@ public void testLookup() GenericRow resp = tableDataManager.lookupRowByPrimaryKey(new PrimaryKey(new String[]{"SF"})); assertNull(resp, "Response should be null if no segment is loaded"); - tableDataManager.addSegment(_indexDir, _indexLoadingConfig); + tableDataManager.addSegment(ImmutableSegmentLoader.load(_indexDir, _indexLoadingConfig)); // Confirm table is loaded and available for lookup resp = tableDataManager.lookupRowByPrimaryKey(new PrimaryKey(new String[]{"SF"})); @@ -207,7 +222,7 @@ public void testLookup() assertEquals(segmentManagers.size(), 1, "Should have exactly one segment manager"); SegmentDataManager segMgr = segmentManagers.get(0); String segmentName = segMgr.getSegmentName(); - tableDataManager.removeSegment(segmentName); + tableDataManager.offloadSegment(segmentName); // confirm table is cleaned up resp = tableDataManager.lookupRowByPrimaryKey(new PrimaryKey(new String[]{"SF"})); assertNull(resp, "Response should be null if no segment is loaded"); @@ -222,8 +237,7 @@ public void testReloadTable() SchemaUtils.toZNRecord(getSchema())); when(helixManager.getHelixPropertyStore()).thenReturn(propertyStore); DimensionTableDataManager tableDataManager = makeTableDataManager(helixManager); - - tableDataManager.addSegment(_indexDir, _indexLoadingConfig); + tableDataManager.addSegment(ImmutableSegmentLoader.load(_indexDir, _indexLoadingConfig)); // Confirm table is loaded and available for lookup GenericRow resp = tableDataManager.lookupRowByPrimaryKey(new PrimaryKey(new String[]{"SF"})); @@ -261,8 +275,8 @@ public void testLookupWithoutPreLoad() ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); when(propertyStore.get("/SCHEMAS/dimBaseballTeams", null, AccessOption.PERSISTENT)).thenReturn( SchemaUtils.toZNRecord(getSchema())); - when(propertyStore.get("/CONFIGS/TABLE/dimBaseballTeams", null, AccessOption.PERSISTENT)).thenReturn( - TableConfigUtils.toZNRecord(getTableConfig(true))); + when(propertyStore.get("/CONFIGS/TABLE/dimBaseballTeams_OFFLINE", null, AccessOption.PERSISTENT)).thenReturn( + TableConfigUtils.toZNRecord(getTableConfig(true, false))); when(helixManager.getHelixPropertyStore()).thenReturn(propertyStore); DimensionTableDataManager tableDataManager = makeTableDataManager(helixManager); @@ -270,7 +284,7 @@ public void testLookupWithoutPreLoad() GenericRow resp = tableDataManager.lookupRowByPrimaryKey(new PrimaryKey(new String[]{"SF"})); assertNull(resp, "Response should be null if no segment is loaded"); - tableDataManager.addSegment(_indexDir, _indexLoadingConfig); + tableDataManager.addSegment(ImmutableSegmentLoader.load(_indexDir, _indexLoadingConfig)); // Confirm table is loaded and available for lookup resp = tableDataManager.lookupRowByPrimaryKey(new PrimaryKey(new String[]{"SF"})); @@ -293,9 +307,47 @@ public void testLookupWithoutPreLoad() assertEquals(segmentManagers.size(), 1, "Should have exactly one segment manager"); SegmentDataManager segMgr = segmentManagers.get(0); String segmentName = segMgr.getSegmentName(); - tableDataManager.removeSegment(segmentName); + tableDataManager.offloadSegment(segmentName); // confirm table is cleaned up resp = tableDataManager.lookupRowByPrimaryKey(new PrimaryKey(new String[]{"SF"})); assertNull(resp, "Response should be null if no segment is loaded"); } + + @Test + public void testLookupErrorOnDuplicatePrimaryKey() + throws Exception { + HelixManager helixManager = mock(HelixManager.class); + ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); + when(propertyStore.get("/SCHEMAS/dimBaseballTeams", null, AccessOption.PERSISTENT)).thenReturn( + SchemaUtils.toZNRecord(getSchema())); + when(propertyStore.get("/CONFIGS/TABLE/dimBaseballTeams_OFFLINE", null, AccessOption.PERSISTENT)).thenReturn( + TableConfigUtils.toZNRecord(getTableConfig(false, true))); + when(helixManager.getHelixPropertyStore()).thenReturn(propertyStore); + DimensionTableDataManager tableDataManager = makeTableDataManager(helixManager); + + // try fetching data BEFORE loading segment + GenericRow resp = tableDataManager.lookupRowByPrimaryKey(new PrimaryKey(new String[]{"SF"})); + assertNull(resp, "Response should be null if no segment is loaded"); + + try { + tableDataManager.addSegment(ImmutableSegmentLoader.load(_indexDir, _indexLoadingConfig)); + fail("Should error out when ErrorOnDuplicatePrimaryKey is configured to true"); + } catch (Exception e) { + // expected; + } + } + + protected static Schema createSchema(File schemaFile) + throws IOException { + InputStream inputStream = new FileInputStream(schemaFile); + Assert.assertNotNull(inputStream); + return JsonUtils.inputStreamToObject(inputStream, Schema.class); + } + + protected static TableConfig createTableConfig(File tableConfigFile) + throws IOException { + InputStream inputStream = new FileInputStream(tableConfigFile); + Assert.assertNotNull(inputStream); + return JsonUtils.inputStreamToObject(inputStream, TableConfig.class); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/IdleTimerTest.java b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/IdleTimerTest.java new file mode 100644 index 000000000000..e0adf5bfb14b --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/IdleTimerTest.java @@ -0,0 +1,123 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.data.manager.realtime; + +import org.testng.Assert; +import org.testng.annotations.Test; + + +public class IdleTimerTest { + + private static class StaticIdleTimer extends IdleTimer { + + private long _nowTimeMs = 0; + + public StaticIdleTimer() { + super(); + } + + @Override + protected long now() { + return _nowTimeMs; + } + + public void setNowTimeMs(long nowTimeMs) { + _nowTimeMs = nowTimeMs; + } + } + + @Test + public void testIdleTimerResetNoIdle() { + StaticIdleTimer timer = new StaticIdleTimer(); + // idle times are all 0 before init + Assert.assertEquals(timer.getTimeSinceStreamLastCreatedOrConsumedMs(), 0); + Assert.assertEquals(timer.getTimeSinceEventLastConsumedMs(), 0); + // start times are all 1000L + timer.setNowTimeMs(1000L); + timer.init(); + Assert.assertEquals(timer.getTimeSinceStreamLastCreatedOrConsumedMs(), 0); + Assert.assertEquals(timer.getTimeSinceEventLastConsumedMs(), 0); + // new now time should affect idle time + timer.setNowTimeMs(2000L); + Assert.assertEquals(timer.getTimeSinceStreamLastCreatedOrConsumedMs(), 1000); + Assert.assertEquals(timer.getTimeSinceEventLastConsumedMs(), 1000); + // everything resets to 2000 + timer.init(); + Assert.assertEquals(timer.getTimeSinceStreamLastCreatedOrConsumedMs(), 0); + Assert.assertEquals(timer.getTimeSinceEventLastConsumedMs(), 0); + } + + @Test + public void testOnlyResetStreamIdleTime() { + StaticIdleTimer timer = new StaticIdleTimer(); + timer.setNowTimeMs(1000L); + timer.init(); + Assert.assertEquals(timer.getTimeSinceStreamLastCreatedOrConsumedMs(), 0); + Assert.assertEquals(timer.getTimeSinceEventLastConsumedMs(), 0); + // only stream idle time resets + timer.setNowTimeMs(2000L); + timer.markStreamCreated(); + Assert.assertEquals(timer.getTimeSinceStreamLastCreatedOrConsumedMs(), 0); + Assert.assertEquals(timer.getTimeSinceEventLastConsumedMs(), 1000); + // everything resets to 0 + timer.init(); + Assert.assertEquals(timer.getTimeSinceStreamLastCreatedOrConsumedMs(), 0); + Assert.assertEquals(timer.getTimeSinceEventLastConsumedMs(), 0); + } + + @Test + public void testMultipleIdleResets() { + StaticIdleTimer timer = new StaticIdleTimer(); + timer.setNowTimeMs(1000L); + timer.init(); + Assert.assertEquals(timer.getTimeSinceStreamLastCreatedOrConsumedMs(), 0); + Assert.assertEquals(timer.getTimeSinceEventLastConsumedMs(), 0); + // new now time should affect idle time + timer.setNowTimeMs(2000L); + Assert.assertEquals(timer.getTimeSinceStreamLastCreatedOrConsumedMs(), 1000); + Assert.assertEquals(timer.getTimeSinceEventLastConsumedMs(), 1000); + // only stream idle time resets + timer.markStreamCreated(); + Assert.assertEquals(timer.getTimeSinceStreamLastCreatedOrConsumedMs(), 0); + Assert.assertEquals(timer.getTimeSinceEventLastConsumedMs(), 1000); + // everything resets to 0 + timer.setNowTimeMs(3000L); + timer.markEventConsumed(); + Assert.assertEquals(timer.getTimeSinceStreamLastCreatedOrConsumedMs(), 0); + Assert.assertEquals(timer.getTimeSinceEventLastConsumedMs(), 0); + // later now times should affect idle time + timer.setNowTimeMs(4000L); + Assert.assertEquals(timer.getTimeSinceStreamLastCreatedOrConsumedMs(), 1000); + Assert.assertEquals(timer.getTimeSinceEventLastConsumedMs(), 1000); + // only stream idle time resets + timer.setNowTimeMs(5000L); + timer.markStreamCreated(); + Assert.assertEquals(timer.getTimeSinceStreamLastCreatedOrConsumedMs(), 0); + Assert.assertEquals(timer.getTimeSinceEventLastConsumedMs(), 2000); + // later now time should only increase both idle times + timer.setNowTimeMs(6000L); + Assert.assertEquals(timer.getTimeSinceStreamLastCreatedOrConsumedMs(), 1000); + Assert.assertEquals(timer.getTimeSinceEventLastConsumedMs(), 3000); + // everything resets to 0 + timer.init(); + Assert.assertEquals(timer.getTimeSinceStreamLastCreatedOrConsumedMs(), 0); + Assert.assertEquals(timer.getTimeSinceEventLastConsumedMs(), 0); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/IngestionDelayTrackerTest.java b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/IngestionDelayTrackerTest.java new file mode 100644 index 000000000000..807273158380 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/IngestionDelayTrackerTest.java @@ -0,0 +1,321 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.data.manager.realtime; + +import java.time.Clock; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; +import org.apache.pinot.common.metrics.ServerMetrics; +import org.apache.pinot.spi.stream.LongMsgOffset; +import org.apache.pinot.spi.stream.StreamPartitionMsgOffset; +import org.testng.Assert; +import org.testng.annotations.Test; + +import static org.mockito.Mockito.mock; + + +public class IngestionDelayTrackerTest { + private static final String REALTIME_TABLE_NAME = "testTable_REALTIME"; + private static final int TIMER_THREAD_TICK_INTERVAL_MS = 100; + + private final ServerMetrics _serverMetrics = mock(ServerMetrics.class); + private final RealtimeTableDataManager _realtimeTableDataManager = mock(RealtimeTableDataManager.class); + + private IngestionDelayTracker createTracker() { + IngestionDelayTracker ingestionDelayTracker = + new IngestionDelayTracker(_serverMetrics, REALTIME_TABLE_NAME, _realtimeTableDataManager, () -> true); + // With no samples, the time reported must be zero + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionDelayMs(0), 0); + return ingestionDelayTracker; + } + + @Test + public void testTrackerConstructors() { + // Test regular constructor + IngestionDelayTracker ingestionDelayTracker = + new IngestionDelayTracker(_serverMetrics, REALTIME_TABLE_NAME, _realtimeTableDataManager, () -> true); + + Clock clock = Clock.systemUTC(); + ingestionDelayTracker.setClock(clock); + + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionDelayMs(0), 0); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionTimeMs(0), Long.MIN_VALUE); + ingestionDelayTracker.shutdown(); + // Test constructor with timer arguments + ingestionDelayTracker = new IngestionDelayTracker(_serverMetrics, REALTIME_TABLE_NAME, _realtimeTableDataManager, + TIMER_THREAD_TICK_INTERVAL_MS, () -> true); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionDelayMs(0), 0); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionTimeMs(0), Long.MIN_VALUE); + // Test bad timer args to the constructor + try { + new IngestionDelayTracker(_serverMetrics, REALTIME_TABLE_NAME, _realtimeTableDataManager, + 0, () -> true); + Assert.fail("Must have asserted due to invalid arguments"); // Constructor must assert + } catch (Exception e) { + if ((e instanceof NullPointerException) || !(e instanceof RuntimeException)) { + Assert.fail(String.format("Unexpected exception: %s:%s", e.getClass(), e.getMessage())); + } + } + } + + @Test + public void testRecordIngestionDelayWithNoAging() { + final long maxTestDelay = 100; + final int partition0 = 0; + final int partition1 = 1; + + IngestionDelayTracker ingestionDelayTracker = createTracker(); + // Use fixed clock so samples dont age + Instant now = Instant.now(); + ZoneId zoneId = ZoneId.systemDefault(); + Clock clock = Clock.fixed(now, zoneId); + ingestionDelayTracker.setClock(clock); + + // Test we follow a single partition up and down + for (long i = 0; i <= maxTestDelay; i++) { + long firstStreamIngestionTimeMs = i + 1; + ingestionDelayTracker.updateIngestionDelay(i, firstStreamIngestionTimeMs, partition0); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionDelayMs(partition0), clock.millis() - i); + Assert.assertEquals(ingestionDelayTracker.getPartitionEndToEndIngestionDelayMs(partition0), + clock.millis() - firstStreamIngestionTimeMs); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionTimeMs(partition0), i); + } + + // Test tracking down a measure for a given partition + for (long i = maxTestDelay; i >= 0; i--) { + ingestionDelayTracker.updateIngestionDelay(i, (i + 1), partition0); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionDelayMs(partition0), clock.millis() - i); + Assert.assertEquals(ingestionDelayTracker.getPartitionEndToEndIngestionDelayMs(partition0), + clock.millis() - (i + 1)); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionTimeMs(partition0), i); + } + + // Make the current partition maximum + ingestionDelayTracker.updateIngestionDelay(maxTestDelay, maxTestDelay, partition0); + + // Bring up partition1 delay up and verify values + for (long i = 0; i <= 2 * maxTestDelay; i++) { + ingestionDelayTracker.updateIngestionDelay(i, (i + 1), partition1); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionDelayMs(partition1), clock.millis() - i); + Assert.assertEquals(ingestionDelayTracker.getPartitionEndToEndIngestionDelayMs(partition1), + clock.millis() - (i + 1)); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionTimeMs(partition1), i); + } + + // Bring down values of partition1 and verify values + for (long i = 2 * maxTestDelay; i >= 0; i--) { + ingestionDelayTracker.updateIngestionDelay(i, (i + 1), partition1); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionDelayMs(partition1), clock.millis() - i); + Assert.assertEquals(ingestionDelayTracker.getPartitionEndToEndIngestionDelayMs(partition1), + clock.millis() - (i + 1)); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionTimeMs(partition1), i); + } + + ingestionDelayTracker.shutdown(); + Assert.assertTrue(true); + } + + @Test + public void testRecordIngestionDelayWithAging() { + final int partition0 = 0; + final long partition0Delay0 = 1000; + final long partition0Delay1 = 10; // record lower delay to make sure max gets reduced + final long partition0Offset0Ms = 300; + final long partition0Offset1Ms = 1000; + final int partition1 = 1; + final long partition1Delay0 = 11; + final long partition1Offset0Ms = 150; + + IngestionDelayTracker ingestionDelayTracker = createTracker(); + + // With samples for a single partition, test that sample is aged as expected + Instant now = Instant.now(); + ZoneId zoneId = ZoneId.systemDefault(); + Clock clock = Clock.fixed(now, zoneId); + ingestionDelayTracker.setClock(clock); + long ingestionTimeMs = (clock.millis() - partition0Delay0); + ingestionDelayTracker.updateIngestionDelay(ingestionTimeMs, + (clock.millis() - partition0Delay0), partition0); + ingestionDelayTracker.updateIngestionDelay((clock.millis() - partition0Delay0), (clock.millis() - partition0Delay0), + partition0); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionDelayMs(partition0), partition0Delay0); + Assert.assertEquals(ingestionDelayTracker.getPartitionEndToEndIngestionDelayMs(partition0), partition0Delay0); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionTimeMs(partition0), ingestionTimeMs); + // Advance clock and test aging + Clock offsetClock = Clock.offset(clock, Duration.ofMillis(partition0Offset0Ms)); + ingestionDelayTracker.setClock(offsetClock); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionDelayMs(partition0), + (partition0Delay0 + partition0Offset0Ms)); + Assert.assertEquals(ingestionDelayTracker.getPartitionEndToEndIngestionDelayMs(partition0), + (partition0Delay0 + partition0Offset0Ms)); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionTimeMs(partition0), ingestionTimeMs); + + ingestionTimeMs = (offsetClock.millis() - partition0Delay1); + ingestionDelayTracker.updateIngestionDelay(ingestionTimeMs, + (offsetClock.millis() - partition0Delay1), partition0); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionDelayMs(partition0), partition0Delay1); + Assert.assertEquals(ingestionDelayTracker.getPartitionEndToEndIngestionDelayMs(partition0), partition0Delay1); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionTimeMs(partition0), ingestionTimeMs); + + // Add some offset to the last sample and make sure we age that measure properly + offsetClock = Clock.offset(offsetClock, Duration.ofMillis(partition0Offset1Ms)); + ingestionDelayTracker.setClock(offsetClock); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionDelayMs(partition0), + (partition0Delay1 + partition0Offset1Ms)); + + ingestionTimeMs = (offsetClock.millis() - partition1Delay0); + ingestionDelayTracker.updateIngestionDelay(ingestionTimeMs, + (offsetClock.millis() - partition1Delay0), partition1); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionDelayMs(partition1), partition1Delay0); + Assert.assertEquals(ingestionDelayTracker.getPartitionEndToEndIngestionDelayMs(partition1), partition1Delay0); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionTimeMs(partition1), ingestionTimeMs); + + // Add some offset to the last sample and make sure we age that measure properly + offsetClock = Clock.offset(offsetClock, Duration.ofMillis(partition1Offset0Ms)); + ingestionDelayTracker.setClock(offsetClock); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionDelayMs(partition1), + (partition1Delay0 + partition1Offset0Ms)); + + ingestionDelayTracker.shutdown(); + } + + @Test + public void testStopTrackingIngestionDelay() { + final long maxTestDelay = 100; + final int maxPartition = 100; + + IngestionDelayTracker ingestionDelayTracker = createTracker(); + // Use fixed clock so samples don't age + Instant now = Instant.now(); + ZoneId zoneId = ZoneId.systemDefault(); + Clock clock = Clock.fixed(now, zoneId); + ingestionDelayTracker.setClock(clock); + + // Record a number of partitions with delay equal to partition id + for (int partitionGroupId = 0; partitionGroupId <= maxTestDelay; partitionGroupId++) { + long ingestionTimeMs = (clock.millis() - partitionGroupId); + ingestionDelayTracker.updateIngestionDelay(ingestionTimeMs, ingestionTimeMs, partitionGroupId); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionDelayMs(partitionGroupId), partitionGroupId); + Assert.assertEquals(ingestionDelayTracker.getPartitionEndToEndIngestionDelayMs(partitionGroupId), + partitionGroupId); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionTimeMs(partitionGroupId), ingestionTimeMs); + } + for (int partitionGroupId = maxPartition; partitionGroupId >= 0; partitionGroupId--) { + ingestionDelayTracker.stopTrackingPartitionIngestionDelay(partitionGroupId); + } + for (int partitionGroupId = 0; partitionGroupId <= maxTestDelay; partitionGroupId++) { + // Untracked partitions must return 0 + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionDelayMs(partitionGroupId), 0); + Assert.assertEquals(ingestionDelayTracker.getPartitionEndToEndIngestionDelayMs(partitionGroupId), 0); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionTimeMs( + partitionGroupId), Long.MIN_VALUE); + } + } + + @Test + public void testShutdown() { + final long maxTestDelay = 100; + + IngestionDelayTracker ingestionDelayTracker = createTracker(); + // Use fixed clock so samples don't age + Instant now = Instant.now(); + ZoneId zoneId = ZoneId.systemDefault(); + Clock clock = Clock.fixed(now, zoneId); + ingestionDelayTracker.setClock(clock); + + // Test Shutdown with partitions active + for (int partitionGroupId = 0; partitionGroupId <= maxTestDelay; partitionGroupId++) { + ingestionDelayTracker.updateIngestionDelay((clock.millis() - partitionGroupId), + (clock.millis() - partitionGroupId), partitionGroupId); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionDelayMs(partitionGroupId), partitionGroupId); + Assert.assertEquals(ingestionDelayTracker.getPartitionEndToEndIngestionDelayMs(partitionGroupId), + partitionGroupId); + } + ingestionDelayTracker.shutdown(); + + // Test shutdown with no partitions + ingestionDelayTracker = createTracker(); + ingestionDelayTracker.shutdown(); + } + + @Test + public void testRecordIngestionDelayOffsetWithNoAging() { + final int partition0 = 0; + final int partition1 = 1; + + IngestionDelayTracker ingestionDelayTracker = createTracker(); + // Use fixed clock so samples don't age + Instant now = Instant.now(); + ZoneId zoneId = ZoneId.systemDefault(); + Clock clock = Clock.fixed(now, zoneId); + ingestionDelayTracker.setClock(clock); + + // Test tracking offset lag for a single partition + StreamPartitionMsgOffset msgOffset0 = new LongMsgOffset(100); + StreamPartitionMsgOffset latestOffset0 = new LongMsgOffset(200); + ingestionDelayTracker.updateIngestionOffsets(msgOffset0, latestOffset0, partition0); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionOffsetLag(partition0), 100); + + // Test tracking offset lag for another partition + StreamPartitionMsgOffset msgOffset1 = new LongMsgOffset(50); + StreamPartitionMsgOffset latestOffset1 = new LongMsgOffset(150); + ingestionDelayTracker.updateIngestionOffsets(msgOffset1, latestOffset1, partition1); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionOffsetLag(partition1), 100); + + // Update offset lag for partition0 + msgOffset0 = new LongMsgOffset(150); + latestOffset0 = new LongMsgOffset(200); + ingestionDelayTracker.updateIngestionOffsets(msgOffset0, latestOffset0, partition0); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionOffsetLag(partition0), 50); + + ingestionDelayTracker.shutdown(); + } + + @Test + public void testRecordIngestionDelayOffsetWithAging() { + final int partition0 = 0; + final long partition0OffsetLag0 = 100; + final long partition0OffsetLag1 = 50; + + IngestionDelayTracker ingestionDelayTracker = createTracker(); + + // With samples for a single partition, test that sample is aged as expected + Instant now = Instant.now(); + ZoneId zoneId = ZoneId.systemDefault(); + Clock clock = Clock.fixed(now, zoneId); + ingestionDelayTracker.setClock(clock); + + StreamPartitionMsgOffset msgOffset0 = new LongMsgOffset(100); + StreamPartitionMsgOffset latestOffset0 = new LongMsgOffset(200); + ingestionDelayTracker.updateIngestionOffsets(msgOffset0, latestOffset0, partition0); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionOffsetLag(partition0), partition0OffsetLag0); + + // Update offset lag and test aging + msgOffset0 = new LongMsgOffset(150); + latestOffset0 = new LongMsgOffset(200); + ingestionDelayTracker.updateIngestionOffsets(msgOffset0, latestOffset0, partition0); + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionOffsetLag(partition0), partition0OffsetLag1); + + + Assert.assertEquals(ingestionDelayTracker.getPartitionIngestionOffsetLag(partition0), partition0OffsetLag1); + ingestionDelayTracker.shutdown(); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/LLRealtimeSegmentDataManagerTest.java b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/LLRealtimeSegmentDataManagerTest.java deleted file mode 100644 index b6c290cde645..000000000000 --- a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/LLRealtimeSegmentDataManagerTest.java +++ /dev/null @@ -1,1231 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.data.manager.realtime; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.time.Instant; -import java.util.LinkedList; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Supplier; -import javax.annotation.Nullable; -import org.apache.commons.io.FileUtils; -import org.apache.helix.HelixManager; -import org.apache.helix.store.zk.ZkHelixPropertyStore; -import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; -import org.apache.pinot.common.metrics.ServerMetrics; -import org.apache.pinot.common.protocols.SegmentCompletionProtocol; -import org.apache.pinot.common.utils.LLCSegmentName; -import org.apache.pinot.common.utils.config.TableConfigUtils; -import org.apache.pinot.core.data.manager.offline.TableDataManagerProvider; -import org.apache.pinot.core.realtime.impl.fakestream.FakeStreamConfigUtils; -import org.apache.pinot.core.realtime.impl.fakestream.FakeStreamConsumerFactory; -import org.apache.pinot.core.realtime.impl.fakestream.FakeStreamMessageDecoder; -import org.apache.pinot.segment.local.data.manager.TableDataManager; -import org.apache.pinot.segment.local.data.manager.TableDataManagerConfig; -import org.apache.pinot.segment.local.realtime.impl.RealtimeSegmentStatsHistory; -import org.apache.pinot.segment.local.segment.creator.Fixtures; -import org.apache.pinot.segment.local.segment.index.loader.IndexLoadingConfig; -import org.apache.pinot.spi.config.instance.InstanceDataManagerConfig; -import org.apache.pinot.spi.config.table.TableConfig; -import org.apache.pinot.spi.config.table.TableType; -import org.apache.pinot.spi.data.Schema; -import org.apache.pinot.spi.env.PinotConfiguration; -import org.apache.pinot.spi.metrics.PinotMetricUtils; -import org.apache.pinot.spi.stream.LongMsgOffset; -import org.apache.pinot.spi.stream.LongMsgOffsetFactory; -import org.apache.pinot.spi.stream.PermanentConsumerException; -import org.apache.pinot.spi.stream.StreamConfigProperties; -import org.apache.pinot.spi.stream.StreamPartitionMsgOffset; -import org.apache.pinot.spi.utils.CommonConstants; -import org.apache.pinot.spi.utils.builder.TableNameBuilder; -import org.apache.pinot.util.TestUtils; -import org.testng.Assert; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - - -// TODO Re-write this test using the stream abstraction -public class LLRealtimeSegmentDataManagerTest { - private static final String SEGMENT_DIR = "/tmp/" + LLRealtimeSegmentDataManagerTest.class.getSimpleName(); - private static final File SEGMENT_DIR_FILE = new File(SEGMENT_DIR); - private static final String RAW_TABLE_NAME = "testTable"; - private static final String REALTIME_TABLE_NAME = TableNameBuilder.REALTIME.tableNameWithType(RAW_TABLE_NAME); - private static final int PARTITION_GROUP_ID = 0; - private static final int SEQUENCE_ID = 945; - private static final long SEG_TIME_MS = 98347869999L; - private static final LLCSegmentName SEGMENT_NAME = - new LLCSegmentName(RAW_TABLE_NAME, PARTITION_GROUP_ID, SEQUENCE_ID, SEG_TIME_MS); - private static final String SEGMENT_NAME_STR = SEGMENT_NAME.getSegmentName(); - private static final long START_OFFSET_VALUE = 198L; - private static final LongMsgOffset START_OFFSET = new LongMsgOffset(START_OFFSET_VALUE); - - private final Map _partitionGroupIdToSemaphoreMap = new ConcurrentHashMap<>(); - - private static TableConfig createTableConfig() - throws Exception { - return Fixtures.createTableConfig(FakeStreamConsumerFactory.class.getName(), - FakeStreamMessageDecoder.class.getName()); - } - - private RealtimeTableDataManager createTableDataManager(TableConfig tableConfig) { - final String instanceId = "server-1"; - SegmentBuildTimeLeaseExtender.getOrCreate(instanceId, new ServerMetrics(PinotMetricUtils.getPinotMetricsRegistry()), - tableConfig.getTableName()); - RealtimeTableDataManager tableDataManager = mock(RealtimeTableDataManager.class); - when(tableDataManager.getServerInstance()).thenReturn(instanceId); - RealtimeSegmentStatsHistory statsHistory = mock(RealtimeSegmentStatsHistory.class); - when(statsHistory.getEstimatedCardinality(anyString())).thenReturn(200); - when(statsHistory.getEstimatedAvgColSize(anyString())).thenReturn(32); - when(tableDataManager.getStatsHistory()).thenReturn(statsHistory); - return tableDataManager; - } - - private SegmentZKMetadata createZkMetadata() { - SegmentZKMetadata segmentZKMetadata = new SegmentZKMetadata(SEGMENT_NAME_STR); - segmentZKMetadata.setStartOffset(START_OFFSET.toString()); - segmentZKMetadata.setCreationTime(System.currentTimeMillis()); - segmentZKMetadata.setStatus(CommonConstants.Segment.Realtime.Status.IN_PROGRESS); - return segmentZKMetadata; - } - - private FakeLLRealtimeSegmentDataManager createFakeSegmentManager() - throws Exception { - return createFakeSegmentManager(false, new TimeSupplier(), null, null, null); - } - - private FakeLLRealtimeSegmentDataManager createFakeSegmentManager(boolean noUpsert, TimeSupplier timeSupplier, - @Nullable String maxRows, @Nullable String maxDuration, @Nullable TableConfig tableConfig) - throws Exception { - SegmentZKMetadata segmentZKMetadata = createZkMetadata(); - if (tableConfig == null) { - tableConfig = createTableConfig(); - } - if (noUpsert) { - tableConfig.setUpsertConfig(null); - } - if (maxRows != null) { - tableConfig.getIndexingConfig().getStreamConfigs() - .put(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_ROWS, maxRows); - } - if (maxDuration != null) { - tableConfig.getIndexingConfig().getStreamConfigs() - .put(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_TIME, maxDuration); - } - RealtimeTableDataManager tableDataManager = createTableDataManager(tableConfig); - LLCSegmentName llcSegmentName = new LLCSegmentName(SEGMENT_NAME_STR); - _partitionGroupIdToSemaphoreMap.putIfAbsent(PARTITION_GROUP_ID, new Semaphore(1)); - Schema schema = Fixtures.createSchema(); - ServerMetrics serverMetrics = new ServerMetrics(PinotMetricUtils.getPinotMetricsRegistry()); - return new FakeLLRealtimeSegmentDataManager(segmentZKMetadata, tableConfig, tableDataManager, SEGMENT_DIR, schema, - llcSegmentName, _partitionGroupIdToSemaphoreMap, serverMetrics, timeSupplier); - } - - @BeforeClass - public void setUp() { - SEGMENT_DIR_FILE.deleteOnExit(); - SegmentBuildTimeLeaseExtender.initExecutor(); - } - - @AfterClass - public void tearDown() { - FileUtils.deleteQuietly(SEGMENT_DIR_FILE); - SegmentBuildTimeLeaseExtender.shutdownExecutor(); - } - - @Test - public void testOffsetParsing() - throws Exception { - final String offset = "34"; - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - { - // Controller sends catchup response with both offset and streamPartitionMsgOffset - String responseStr = - "{" + " \"streamPartitionMsgOffset\" : \"" + offset + "\"," + " \"offset\" : " + offset + "," - + " \"buildTimeSec\" : -1," + " \"isSplitCommitType\" : false," - + " \"segmentLocation\" : \"file:///a/b\"," + " \"status\" : \"CATCH_UP\"" + "}"; - SegmentCompletionProtocol.Response response = SegmentCompletionProtocol.Response.fromJsonString(responseStr); - StreamPartitionMsgOffset extractedOffset = segmentDataManager.extractOffset(response); - Assert.assertEquals(extractedOffset.compareTo(new LongMsgOffset(offset)), 0); - } - { - // Controller sends catchup response with offset only - String responseStr = - "{" + " \"offset\" : " + offset + "," + " \"buildTimeSec\" : -1," + " \"isSplitCommitType\" : false," - + " \"segmentLocation\" : \"file:///a/b\"," + " \"status\" : \"CATCH_UP\"" + "}"; - SegmentCompletionProtocol.Response response = SegmentCompletionProtocol.Response.fromJsonString(responseStr); - StreamPartitionMsgOffset extractedOffset = segmentDataManager.extractOffset(response); - Assert.assertEquals(extractedOffset.compareTo(new LongMsgOffset(offset)), 0); - } - { - // Controller sends catchup response streamPartitionMsgOffset only - String responseStr = "{" + " \"streamPartitionMsgOffset\" : \"" + offset + "\"," + " \"buildTimeSec\" : -1," - + " \"isSplitCommitType\" : false," + " \"segmentLocation\" : \"file:///a/b\"," - + " \"status\" : \"CATCH_UP\"" + "}"; - SegmentCompletionProtocol.Response response = SegmentCompletionProtocol.Response.fromJsonString(responseStr); - StreamPartitionMsgOffset extractedOffset = segmentDataManager.extractOffset(response); - Assert.assertEquals(extractedOffset.compareTo(new LongMsgOffset(offset)), 0); - } - segmentDataManager.destroy(); - } - - // Test that we are in HOLDING state as long as the controller responds HOLD to our segmentConsumed() message. - // we should not consume when holding. - @Test - public void testHolding() - throws Exception { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - LLRealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); - final LongMsgOffset endOffset = new LongMsgOffset(START_OFFSET_VALUE + 500); - // We should consume initially... - segmentDataManager._consumeOffsets.add(endOffset); - final SegmentCompletionProtocol.Response response = new SegmentCompletionProtocol.Response( - new SegmentCompletionProtocol.Response.Params().withStatus( - SegmentCompletionProtocol.ControllerResponseStatus.HOLD) - .withStreamPartitionMsgOffset(endOffset.toString())); - // And then never consume as long as we get a hold response, 100 times. - for (int i = 0; i < 100; i++) { - segmentDataManager._responses.add(response); - } - - consumer.run(); - - Assert.assertTrue(segmentDataManager._responses.isEmpty()); - Assert.assertTrue(segmentDataManager._consumeOffsets.isEmpty()); - Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); - Assert.assertFalse(segmentDataManager._buildSegmentCalled); - Assert.assertFalse(segmentDataManager._commitSegmentCalled); - Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled); - Assert.assertEquals(segmentDataManager._state.get(segmentDataManager), LLRealtimeSegmentDataManager.State.HOLDING); - segmentDataManager.destroy(); - } - - // Test that we go to commit when the controller responds commit after 2 holds. - @Test - public void testCommitAfterHold() - throws Exception { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - LLRealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); - final LongMsgOffset endOffset = new LongMsgOffset(START_OFFSET_VALUE + 500); - // We should consume initially... - segmentDataManager._consumeOffsets.add(endOffset); - final SegmentCompletionProtocol.Response holdResponse = new SegmentCompletionProtocol.Response( - new SegmentCompletionProtocol.Response.Params().withStreamPartitionMsgOffset(endOffset.toString()) - .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.HOLD)); - final SegmentCompletionProtocol.Response commitResponse = new SegmentCompletionProtocol.Response( - new SegmentCompletionProtocol.Response.Params().withStreamPartitionMsgOffset(endOffset.toString()) - .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.COMMIT)); - // And then never consume as long as we get a hold response, 100 times. - segmentDataManager._responses.add(holdResponse); - segmentDataManager._responses.add(commitResponse); - - consumer.run(); - - Assert.assertTrue(segmentDataManager._responses.isEmpty()); - Assert.assertTrue(segmentDataManager._consumeOffsets.isEmpty()); - Assert.assertTrue(segmentDataManager._buildSegmentCalled); - Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); - Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled); - Assert.assertTrue(segmentDataManager._commitSegmentCalled); - Assert.assertEquals(segmentDataManager._state.get(segmentDataManager), - LLRealtimeSegmentDataManager.State.COMMITTED); - segmentDataManager.destroy(); - } - - @Test - public void testSegmentBuildException() - throws Exception { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - LLRealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); - final LongMsgOffset endOffset = new LongMsgOffset(START_OFFSET_VALUE + 500); - // We should consume initially... - segmentDataManager._consumeOffsets.add(endOffset); - final SegmentCompletionProtocol.Response commitResponse = new SegmentCompletionProtocol.Response( - new SegmentCompletionProtocol.Response.Params().withStreamPartitionMsgOffset(endOffset.toString()) - .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.COMMIT)); - segmentDataManager._responses.add(commitResponse); - segmentDataManager._failSegmentBuild = true; - - consumer.run(); - Assert.assertTrue(segmentDataManager._buildSegmentCalled); - Assert.assertEquals(segmentDataManager._state.get(segmentDataManager), LLRealtimeSegmentDataManager.State.ERROR); - segmentDataManager.destroy(); - } - - // Test hold, catchup. hold, commit - @Test - public void testCommitAfterCatchup() - throws Exception { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - LLRealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); - final LongMsgOffset firstOffset = new LongMsgOffset(START_OFFSET_VALUE + 500); - final LongMsgOffset catchupOffset = new LongMsgOffset(firstOffset.getOffset() + 10); - // We should consume initially... - segmentDataManager._consumeOffsets.add(firstOffset); - segmentDataManager._consumeOffsets.add(catchupOffset); // Offset after catchup - final SegmentCompletionProtocol.Response holdResponse1 = new SegmentCompletionProtocol.Response( - new SegmentCompletionProtocol.Response.Params().withStatus( - SegmentCompletionProtocol.ControllerResponseStatus.HOLD) - .withStreamPartitionMsgOffset(firstOffset.toString())); - final SegmentCompletionProtocol.Response catchupResponse = new SegmentCompletionProtocol.Response( - new SegmentCompletionProtocol.Response.Params().withStatus( - SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP) - .withStreamPartitionMsgOffset(catchupOffset.toString())); - final SegmentCompletionProtocol.Response holdResponse2 = new SegmentCompletionProtocol.Response( - new SegmentCompletionProtocol.Response.Params().withStreamPartitionMsgOffset(catchupOffset.toString()) - .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.HOLD)); - final SegmentCompletionProtocol.Response commitResponse = new SegmentCompletionProtocol.Response( - new SegmentCompletionProtocol.Response.Params().withStreamPartitionMsgOffset(catchupOffset.toString()) - .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.COMMIT)); - // And then never consume as long as we get a hold response, 100 times. - segmentDataManager._responses.add(holdResponse1); - segmentDataManager._responses.add(catchupResponse); - segmentDataManager._responses.add(holdResponse2); - segmentDataManager._responses.add(commitResponse); - - consumer.run(); - - Assert.assertTrue(segmentDataManager._responses.isEmpty()); - Assert.assertTrue(segmentDataManager._consumeOffsets.isEmpty()); - Assert.assertTrue(segmentDataManager._buildSegmentCalled); - Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); - Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled); - Assert.assertTrue(segmentDataManager._commitSegmentCalled); - Assert.assertEquals(segmentDataManager._state.get(segmentDataManager), - LLRealtimeSegmentDataManager.State.COMMITTED); - segmentDataManager.destroy(); - } - - @Test - public void testCommitAfterCatchupWithPeriodOffset() throws Exception { - TableConfig tableConfig = createTableConfig(); - tableConfig.getIndexingConfig().getStreamConfigs() - .put(StreamConfigProperties.constructStreamProperty( - StreamConfigProperties.STREAM_CONSUMER_OFFSET_CRITERIA, "fakeStream"), "2d"); - FakeLLRealtimeSegmentDataManager segmentDataManager = - createFakeSegmentManager(false, new TimeSupplier(), null, null, tableConfig); - LLRealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); - final LongMsgOffset firstOffset = new LongMsgOffset(START_OFFSET_VALUE + 500); - final LongMsgOffset catchupOffset = new LongMsgOffset(firstOffset.getOffset() + 10); - // We should consume initially... - segmentDataManager._consumeOffsets.add(firstOffset); - segmentDataManager._consumeOffsets.add(catchupOffset); // Offset after catchup - final SegmentCompletionProtocol.Response holdResponse1 = new SegmentCompletionProtocol.Response( - new SegmentCompletionProtocol.Response.Params().withStatus( - SegmentCompletionProtocol.ControllerResponseStatus.HOLD) - .withStreamPartitionMsgOffset(firstOffset.toString())); - final SegmentCompletionProtocol.Response catchupResponse = new SegmentCompletionProtocol.Response( - new SegmentCompletionProtocol.Response.Params().withStatus( - SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP) - .withStreamPartitionMsgOffset(catchupOffset.toString())); - final SegmentCompletionProtocol.Response holdResponse2 = new SegmentCompletionProtocol.Response( - new SegmentCompletionProtocol.Response.Params().withStreamPartitionMsgOffset(catchupOffset.toString()) - .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.HOLD)); - final SegmentCompletionProtocol.Response commitResponse = new SegmentCompletionProtocol.Response( - new SegmentCompletionProtocol.Response.Params().withStreamPartitionMsgOffset(catchupOffset.toString()) - .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.COMMIT)); - // And then never consume as long as we get a hold response, 100 times. - segmentDataManager._responses.add(holdResponse1); - segmentDataManager._responses.add(catchupResponse); - segmentDataManager._responses.add(holdResponse2); - segmentDataManager._responses.add(commitResponse); - - consumer.run(); - - Assert.assertTrue(segmentDataManager._responses.isEmpty()); - Assert.assertTrue(segmentDataManager._consumeOffsets.isEmpty()); - Assert.assertTrue(segmentDataManager._buildSegmentCalled); - Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); - Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled); - Assert.assertTrue(segmentDataManager._commitSegmentCalled); - Assert.assertEquals(segmentDataManager._state.get(segmentDataManager), - LLRealtimeSegmentDataManager.State.COMMITTED); - segmentDataManager.destroy(); - } - - @Test - public void testCommitAfterCatchupWithTimestampOffset() throws Exception { - TableConfig tableConfig = createTableConfig(); - tableConfig.getIndexingConfig().getStreamConfigs() - .put(StreamConfigProperties.constructStreamProperty( - StreamConfigProperties.STREAM_CONSUMER_OFFSET_CRITERIA, "fakeStream"), Instant.now().toString()); - FakeLLRealtimeSegmentDataManager segmentDataManager = - createFakeSegmentManager(false, new TimeSupplier(), null, null, tableConfig); - LLRealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); - final LongMsgOffset firstOffset = new LongMsgOffset(START_OFFSET_VALUE + 500); - final LongMsgOffset catchupOffset = new LongMsgOffset(firstOffset.getOffset() + 10); - // We should consume initially... - segmentDataManager._consumeOffsets.add(firstOffset); - segmentDataManager._consumeOffsets.add(catchupOffset); // Offset after catchup - final SegmentCompletionProtocol.Response holdResponse1 = new SegmentCompletionProtocol.Response( - new SegmentCompletionProtocol.Response.Params().withStatus( - SegmentCompletionProtocol.ControllerResponseStatus.HOLD) - .withStreamPartitionMsgOffset(firstOffset.toString())); - final SegmentCompletionProtocol.Response catchupResponse = new SegmentCompletionProtocol.Response( - new SegmentCompletionProtocol.Response.Params().withStatus( - SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP) - .withStreamPartitionMsgOffset(catchupOffset.toString())); - final SegmentCompletionProtocol.Response holdResponse2 = new SegmentCompletionProtocol.Response( - new SegmentCompletionProtocol.Response.Params().withStreamPartitionMsgOffset(catchupOffset.toString()) - .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.HOLD)); - final SegmentCompletionProtocol.Response commitResponse = new SegmentCompletionProtocol.Response( - new SegmentCompletionProtocol.Response.Params().withStreamPartitionMsgOffset(catchupOffset.toString()) - .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.COMMIT)); - // And then never consume as long as we get a hold response, 100 times. - segmentDataManager._responses.add(holdResponse1); - segmentDataManager._responses.add(catchupResponse); - segmentDataManager._responses.add(holdResponse2); - segmentDataManager._responses.add(commitResponse); - - consumer.run(); - - Assert.assertTrue(segmentDataManager._responses.isEmpty()); - Assert.assertTrue(segmentDataManager._consumeOffsets.isEmpty()); - Assert.assertTrue(segmentDataManager._buildSegmentCalled); - Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); - Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled); - Assert.assertTrue(segmentDataManager._commitSegmentCalled); - Assert.assertEquals(segmentDataManager._state.get(segmentDataManager), - LLRealtimeSegmentDataManager.State.COMMITTED); - segmentDataManager.destroy(); - } - - @Test - public void testDiscarded() - throws Exception { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - LLRealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); - final LongMsgOffset endOffset = new LongMsgOffset(START_OFFSET_VALUE + 500); - segmentDataManager._consumeOffsets.add(endOffset); - final SegmentCompletionProtocol.Response discardResponse = new SegmentCompletionProtocol.Response( - new SegmentCompletionProtocol.Response.Params().withStreamPartitionMsgOffset(endOffset.toString()) - .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.DISCARD)); - segmentDataManager._responses.add(discardResponse); - - consumer.run(); - - Assert.assertTrue(segmentDataManager._responses.isEmpty()); - Assert.assertTrue(segmentDataManager._consumeOffsets.isEmpty()); - Assert.assertFalse(segmentDataManager._buildSegmentCalled); - Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); - Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled); - Assert.assertFalse(segmentDataManager._commitSegmentCalled); - Assert.assertEquals(segmentDataManager._state.get(segmentDataManager), - LLRealtimeSegmentDataManager.State.DISCARDED); - segmentDataManager.destroy(); - } - - @Test - public void testRetained() - throws Exception { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - LLRealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); - final LongMsgOffset endOffset = new LongMsgOffset(START_OFFSET_VALUE + 500); - segmentDataManager._consumeOffsets.add(endOffset); - SegmentCompletionProtocol.Response.Params params = new SegmentCompletionProtocol.Response.Params(); - params.withStreamPartitionMsgOffset(endOffset.toString()) - .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.KEEP); - final SegmentCompletionProtocol.Response keepResponse = new SegmentCompletionProtocol.Response(params); - segmentDataManager._responses.add(keepResponse); - - consumer.run(); - - Assert.assertTrue(segmentDataManager._responses.isEmpty()); - Assert.assertTrue(segmentDataManager._consumeOffsets.isEmpty()); - Assert.assertFalse(segmentDataManager._buildSegmentCalled); - Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled); - Assert.assertTrue(segmentDataManager._buildAndReplaceCalled); - Assert.assertFalse(segmentDataManager._commitSegmentCalled); - Assert.assertEquals(segmentDataManager._state.get(segmentDataManager), LLRealtimeSegmentDataManager.State.RETAINED); - segmentDataManager.destroy(); - } - - @Test - public void testNotLeader() - throws Exception { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - LLRealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); - final LongMsgOffset endOffset = new LongMsgOffset(START_OFFSET_VALUE + 500); - // We should consume initially... - segmentDataManager._consumeOffsets.add(endOffset); - final SegmentCompletionProtocol.Response response = new SegmentCompletionProtocol.Response( - new SegmentCompletionProtocol.Response.Params().withStreamPartitionMsgOffset(endOffset.toString()) - .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.NOT_LEADER)); - // And then never consume as long as we get a Not leader response, 100 times. - for (int i = 0; i < 100; i++) { - segmentDataManager._responses.add(response); - } - - consumer.run(); - - Assert.assertTrue(segmentDataManager._responses.isEmpty()); - Assert.assertTrue(segmentDataManager._consumeOffsets.isEmpty()); - Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); - Assert.assertFalse(segmentDataManager._buildSegmentCalled); - Assert.assertFalse(segmentDataManager._commitSegmentCalled); - Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled); - Assert.assertEquals(segmentDataManager._state.get(segmentDataManager), LLRealtimeSegmentDataManager.State.HOLDING); - segmentDataManager.destroy(); - } - - @Test - public void testConsumingException() - throws Exception { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - LLRealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); - - segmentDataManager._throwExceptionFromConsume = true; - segmentDataManager._postConsumeStoppedCalled = false; - consumer.run(); - Assert.assertTrue(segmentDataManager._postConsumeStoppedCalled); - segmentDataManager.destroy(); - } - - // Tests to go online from consuming state - - // If the state is is COMMITTED or RETAINED, nothing to do - // If discarded or error state, then downloadAndReplace the segment - @Test - public void testOnlineTransitionAfterStop() - throws Exception { - SegmentZKMetadata metadata = new SegmentZKMetadata(SEGMENT_NAME_STR); - final long finalOffsetValue = START_OFFSET_VALUE + 600; - final LongMsgOffset finalOffset = new LongMsgOffset(finalOffsetValue); - metadata.setEndOffset(finalOffset.toString()); - - { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - segmentDataManager._stopWaitTimeMs = 0; - segmentDataManager._state.set(segmentDataManager, LLRealtimeSegmentDataManager.State.COMMITTED); - segmentDataManager.goOnlineFromConsuming(metadata); - Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled); - Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); - segmentDataManager.destroy(); - } - - { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - segmentDataManager._stopWaitTimeMs = 0; - segmentDataManager._state.set(segmentDataManager, LLRealtimeSegmentDataManager.State.RETAINED); - segmentDataManager.goOnlineFromConsuming(metadata); - Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled); - Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); - segmentDataManager.destroy(); - } - - { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - segmentDataManager._stopWaitTimeMs = 0; - segmentDataManager._state.set(segmentDataManager, LLRealtimeSegmentDataManager.State.DISCARDED); - segmentDataManager.goOnlineFromConsuming(metadata); - Assert.assertTrue(segmentDataManager._downloadAndReplaceCalled); - Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); - segmentDataManager.destroy(); - } - - { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - segmentDataManager._stopWaitTimeMs = 0; - segmentDataManager._state.set(segmentDataManager, LLRealtimeSegmentDataManager.State.ERROR); - segmentDataManager.goOnlineFromConsuming(metadata); - Assert.assertTrue(segmentDataManager._downloadAndReplaceCalled); - Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); - segmentDataManager.destroy(); - } - - // If holding, but we have overshot the expected final offset, the download and replace - { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - segmentDataManager._stopWaitTimeMs = 0; - segmentDataManager._state.set(segmentDataManager, LLRealtimeSegmentDataManager.State.HOLDING); - segmentDataManager.setCurrentOffset(finalOffsetValue + 1); - segmentDataManager.goOnlineFromConsuming(metadata); - Assert.assertTrue(segmentDataManager._downloadAndReplaceCalled); - Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); - segmentDataManager.destroy(); - } - - // If catching up, but we have overshot the expected final offset, the download and replace - { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - segmentDataManager._stopWaitTimeMs = 0; - segmentDataManager._state.set(segmentDataManager, LLRealtimeSegmentDataManager.State.CATCHING_UP); - segmentDataManager.setCurrentOffset(finalOffsetValue + 1); - segmentDataManager.goOnlineFromConsuming(metadata); - Assert.assertTrue(segmentDataManager._downloadAndReplaceCalled); - Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); - segmentDataManager.destroy(); - } - - // If catching up, but we did not get to the final offset, then download and replace - { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - segmentDataManager._stopWaitTimeMs = 0; - segmentDataManager._state.set(segmentDataManager, LLRealtimeSegmentDataManager.State.CATCHING_UP); - segmentDataManager._consumeOffsets.add(new LongMsgOffset(finalOffsetValue - 1)); - segmentDataManager.goOnlineFromConsuming(metadata); - Assert.assertTrue(segmentDataManager._downloadAndReplaceCalled); - Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); - segmentDataManager.destroy(); - } - - // But then if we get to the exact offset, we get to build and replace, not download - { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - segmentDataManager._stopWaitTimeMs = 0; - segmentDataManager._state.set(segmentDataManager, LLRealtimeSegmentDataManager.State.CATCHING_UP); - segmentDataManager._consumeOffsets.add(finalOffset); - segmentDataManager.goOnlineFromConsuming(metadata); - Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled); - Assert.assertTrue(segmentDataManager._buildAndReplaceCalled); - segmentDataManager.destroy(); - } - } - - @Test - public void testEndCriteriaChecking() - throws Exception { - // test reaching max row limit - { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - segmentDataManager._state.set(segmentDataManager, LLRealtimeSegmentDataManager.State.INITIAL_CONSUMING); - Assert.assertFalse(segmentDataManager.invokeEndCriteriaReached()); - segmentDataManager.setNumRowsIndexed(Fixtures.MAX_ROWS_IN_SEGMENT - 1); - Assert.assertFalse(segmentDataManager.invokeEndCriteriaReached()); - segmentDataManager.setNumRowsIndexed(Fixtures.MAX_ROWS_IN_SEGMENT); - Assert.assertTrue(segmentDataManager.invokeEndCriteriaReached()); - Assert.assertEquals(segmentDataManager.getStopReason(), SegmentCompletionProtocol.REASON_ROW_LIMIT); - segmentDataManager.destroy(); - } - // test reaching max time limit - { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - segmentDataManager._state.set(segmentDataManager, LLRealtimeSegmentDataManager.State.INITIAL_CONSUMING); - Assert.assertFalse(segmentDataManager.invokeEndCriteriaReached()); - // We should still get false because there is no messages fetched - segmentDataManager._timeSupplier.add(Fixtures.MAX_TIME_FOR_SEGMENT_CLOSE_MS + 1); - Assert.assertFalse(segmentDataManager.invokeEndCriteriaReached()); - // Once there are messages fetched, and the time exceeds the extended hour, we should get true - setHasMessagesFetched(segmentDataManager, true); - segmentDataManager._timeSupplier.add(TimeUnit.HOURS.toMillis(1)); - Assert.assertTrue(segmentDataManager.invokeEndCriteriaReached()); - Assert.assertEquals(segmentDataManager.getStopReason(), SegmentCompletionProtocol.REASON_TIME_LIMIT); - segmentDataManager.destroy(); - } - // In catching up state, test reaching final offset - { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - segmentDataManager._state.set(segmentDataManager, LLRealtimeSegmentDataManager.State.CATCHING_UP); - final long finalOffset = START_OFFSET_VALUE + 100; - segmentDataManager.setFinalOffset(finalOffset); - segmentDataManager.setCurrentOffset(finalOffset - 1); - Assert.assertFalse(segmentDataManager.invokeEndCriteriaReached()); - segmentDataManager.setCurrentOffset(finalOffset); - Assert.assertTrue(segmentDataManager.invokeEndCriteriaReached()); - segmentDataManager.destroy(); - } - // In catching up state, test reaching final offset ignoring time - { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - segmentDataManager._timeSupplier.add(Fixtures.MAX_TIME_FOR_SEGMENT_CLOSE_MS); - segmentDataManager._state.set(segmentDataManager, LLRealtimeSegmentDataManager.State.CATCHING_UP); - final long finalOffset = START_OFFSET_VALUE + 100; - segmentDataManager.setFinalOffset(finalOffset); - segmentDataManager.setCurrentOffset(finalOffset - 1); - Assert.assertFalse(segmentDataManager.invokeEndCriteriaReached()); - segmentDataManager.setCurrentOffset(finalOffset); - Assert.assertTrue(segmentDataManager.invokeEndCriteriaReached()); - segmentDataManager.destroy(); - } - // When we go from consuming to online state, time and final offset matter. - // Case 1. We have reached final offset. - { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - segmentDataManager._timeSupplier.add(1); - segmentDataManager._state.set(segmentDataManager, LLRealtimeSegmentDataManager.State.CONSUMING_TO_ONLINE); - segmentDataManager.setConsumeEndTime(segmentDataManager._timeSupplier.get() + 10); - final long finalOffset = START_OFFSET_VALUE + 100; - segmentDataManager.setFinalOffset(finalOffset); - segmentDataManager.setCurrentOffset(finalOffset - 1); - Assert.assertFalse(segmentDataManager.invokeEndCriteriaReached()); - segmentDataManager.setCurrentOffset(finalOffset); - Assert.assertTrue(segmentDataManager.invokeEndCriteriaReached()); - segmentDataManager.destroy(); - } - // Case 2. We have reached time limit. - { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - segmentDataManager._state.set(segmentDataManager, LLRealtimeSegmentDataManager.State.CONSUMING_TO_ONLINE); - final long endTime = segmentDataManager._timeSupplier.get() + 10; - segmentDataManager.setConsumeEndTime(endTime); - final long finalOffset = START_OFFSET_VALUE + 100; - segmentDataManager.setFinalOffset(finalOffset); - segmentDataManager.setCurrentOffset(finalOffset - 1); - segmentDataManager._timeSupplier.set(endTime - 1); - Assert.assertFalse(segmentDataManager.invokeEndCriteriaReached()); - segmentDataManager._timeSupplier.set(endTime); - Assert.assertTrue(segmentDataManager.invokeEndCriteriaReached()); - segmentDataManager.destroy(); - } - } - - private void setHasMessagesFetched(FakeLLRealtimeSegmentDataManager segmentDataManager, boolean hasMessagesFetched) - throws Exception { - Field field = LLRealtimeSegmentDataManager.class.getDeclaredField("_hasMessagesFetched"); - field.setAccessible(true); - field.set(segmentDataManager, hasMessagesFetched); - } - - // If commit fails, make sure that we do not re-build the segment when we try to commit again. - @Test - public void testReuseOfBuiltSegment() - throws Exception { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - - SegmentCompletionProtocol.Response.Params params = new SegmentCompletionProtocol.Response.Params(); - params.withStatus(SegmentCompletionProtocol.ControllerResponseStatus.COMMIT_SUCCESS); - SegmentCompletionProtocol.Response commitSuccess = new SegmentCompletionProtocol.Response(params); - params.withStatus(SegmentCompletionProtocol.ControllerResponseStatus.FAILED); - SegmentCompletionProtocol.Response commitFailed = new SegmentCompletionProtocol.Response(params); - - // Set up the responses so that we get a failed respnse first and then a success response. - segmentDataManager._responses.add(commitFailed); - segmentDataManager._responses.add(commitSuccess); - final long leaseTime = 50000L; - - // The first time we invoke build, it should go ahead and build the segment. - File segmentTarFile = segmentDataManager.invokeBuildForCommit(leaseTime).getSegmentTarFile(); - Assert.assertNotNull(segmentTarFile); - Assert.assertTrue(segmentDataManager._buildSegmentCalled); - Assert.assertFalse(segmentDataManager.invokeCommit()); - Assert.assertTrue(segmentTarFile.exists()); - - segmentDataManager._buildSegmentCalled = false; - - // This time around it should not build the segment. - File segmentTarFile1 = segmentDataManager.invokeBuildForCommit(leaseTime).getSegmentTarFile(); - Assert.assertFalse(segmentDataManager._buildSegmentCalled); - Assert.assertEquals(segmentTarFile1, segmentTarFile); - Assert.assertTrue(segmentTarFile.exists()); - Assert.assertTrue(segmentDataManager.invokeCommit()); - Assert.assertFalse(segmentTarFile.exists()); - segmentDataManager.destroy(); - } - - // If commit fails, and we still have the file, make sure that we remove the file when we go - // online. - @Test - public void testFileRemovedDuringOnlineTransition() - throws Exception { - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); - - SegmentCompletionProtocol.Response.Params params = new SegmentCompletionProtocol.Response.Params(); - params.withStatus(SegmentCompletionProtocol.ControllerResponseStatus.FAILED); - SegmentCompletionProtocol.Response commitFailed = new SegmentCompletionProtocol.Response(params); - - // Set up the responses so that we get a failed response first and then a success response. - segmentDataManager._responses.add(commitFailed); - final long leaseTime = 50000L; - final long finalOffset = START_OFFSET_VALUE + 600; - segmentDataManager.setCurrentOffset(finalOffset); - - // We have set up commit to fail, so we should carry over the segment file. - File segmentTarFile = segmentDataManager.invokeBuildForCommit(leaseTime).getSegmentTarFile(); - Assert.assertNotNull(segmentTarFile); - Assert.assertTrue(segmentDataManager._buildSegmentCalled); - Assert.assertFalse(segmentDataManager.invokeCommit()); - Assert.assertTrue(segmentTarFile.exists()); - - // Now let the segment go ONLINE from CONSUMING, and ensure that the file is removed. - SegmentZKMetadata metadata = new SegmentZKMetadata(SEGMENT_NAME_STR); - metadata.setEndOffset(new LongMsgOffset(finalOffset).toString()); - segmentDataManager._stopWaitTimeMs = 0; - segmentDataManager._state.set(segmentDataManager, LLRealtimeSegmentDataManager.State.HOLDING); - segmentDataManager.goOnlineFromConsuming(metadata); - Assert.assertFalse(segmentTarFile.exists()); - segmentDataManager.destroy(); - } - - @Test - public void testOnlyOneSegmentHoldingTheSemaphoreForParticularPartition() - throws Exception { - long timeout = 10_000L; - FakeLLRealtimeSegmentDataManager firstSegmentDataManager = createFakeSegmentManager(); - Assert.assertTrue(firstSegmentDataManager.getAcquiredConsumerSemaphore().get()); - Semaphore firstSemaphore = firstSegmentDataManager.getPartitionGroupConsumerSemaphore(); - Assert.assertEquals(firstSemaphore.availablePermits(), 0); - Assert.assertFalse(firstSemaphore.hasQueuedThreads()); - - AtomicReference secondSegmentDataManager = new AtomicReference<>(null); - - // Construct the second segment manager, which will be blocked on the semaphore. - Thread constructSecondSegmentManager = new Thread(() -> { - try { - secondSegmentDataManager.set(createFakeSegmentManager()); - } catch (Exception e) { - throw new RuntimeException("Exception when sleeping for " + timeout + "ms", e); - } - }); - constructSecondSegmentManager.start(); - - // Wait until the second segment manager gets blocked on the semaphore. - TestUtils.waitForCondition(aVoid -> { - if (firstSemaphore.hasQueuedThreads()) { - // Once verified the second segment gets blocked, release the semaphore. - firstSegmentDataManager.destroy(); - return true; - } else { - return false; - } - }, timeout, "Failed to wait for the second segment blocked on semaphore"); - - // Wait for the second segment manager finished the construction. - TestUtils.waitForCondition(aVoid -> secondSegmentDataManager.get() != null, timeout, - "Failed to acquire the semaphore for the second segment manager in " + timeout + "ms"); - - Assert.assertTrue(secondSegmentDataManager.get().getAcquiredConsumerSemaphore().get()); - Semaphore secondSemaphore = secondSegmentDataManager.get().getPartitionGroupConsumerSemaphore(); - Assert.assertEquals(firstSemaphore, secondSemaphore); - Assert.assertEquals(secondSemaphore.availablePermits(), 0); - Assert.assertFalse(secondSemaphore.hasQueuedThreads()); - - // Call destroy method the 2nd time on the first segment manager, the permits in semaphore won't increase. - firstSegmentDataManager.destroy(); - Assert.assertEquals(firstSegmentDataManager.getPartitionGroupConsumerSemaphore().availablePermits(), 0); - - // The permit finally gets released in the Semaphore. - secondSegmentDataManager.get().destroy(); - Assert.assertEquals(secondSegmentDataManager.get().getPartitionGroupConsumerSemaphore().availablePermits(), 1); - } - - @Test - public void testShutdownTableDataManagerWillNotShutdownLeaseExtenderExecutor() - throws Exception { - TableConfig tableConfig = createTableConfig(); - tableConfig.setUpsertConfig(null); - ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); - when(propertyStore.get(anyString(), any(), anyInt())).thenReturn(TableConfigUtils.toZNRecord(tableConfig)); - - TableDataManagerConfig tableDataManagerConfig = mock(TableDataManagerConfig.class); - when(tableDataManagerConfig.getTableName()).thenReturn(REALTIME_TABLE_NAME); - when(tableDataManagerConfig.getTableType()).thenReturn(TableType.REALTIME); - when(tableDataManagerConfig.getDataDir()).thenReturn(FileUtils.getTempDirectoryPath()); - InstanceDataManagerConfig instanceDataManagerConfig = mock(InstanceDataManagerConfig.class); - when(instanceDataManagerConfig.getMaxParallelSegmentBuilds()).thenReturn(4); - when(instanceDataManagerConfig.getStreamSegmentDownloadUntarRateLimit()).thenReturn(-1L); - when(instanceDataManagerConfig.getMaxParallelSegmentDownloads()).thenReturn(-1); - when(instanceDataManagerConfig.isStreamSegmentDownloadUntar()).thenReturn(false); - TableDataManagerProvider.init(instanceDataManagerConfig); - - TableDataManager tableDataManager = - TableDataManagerProvider.getTableDataManager(tableDataManagerConfig, "testInstance", propertyStore, - mock(ServerMetrics.class), mock(HelixManager.class), null); - tableDataManager.start(); - tableDataManager.shutDown(); - Assert.assertFalse(SegmentBuildTimeLeaseExtender.isExecutorShutdown()); - } - - @Test - public void testShouldNotSkipUnfilteredMessagesIfNotIndexedAndTimeThresholdIsReached() - throws Exception { - final int segmentTimeThresholdMins = 10; - TimeSupplier timeSupplier = new TimeSupplier() { - @Override - public Long get() { - long now = System.currentTimeMillis(); - // now() is called once in the run() method, once before each batch reading and once for every row indexation - if (_timeCheckCounter.incrementAndGet() <= FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS + 4) { - return now; - } - // Exceed segment time threshold - return now + TimeUnit.MINUTES.toMillis(segmentTimeThresholdMins + 1); - } - }; - FakeLLRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(true, timeSupplier, - String.valueOf(FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS * 2), - segmentTimeThresholdMins + "m", null); - segmentDataManager._stubConsumeLoop = false; - segmentDataManager._state.set(segmentDataManager, LLRealtimeSegmentDataManager.State.INITIAL_CONSUMING); - - LLRealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); - final LongMsgOffset endOffset = - new LongMsgOffset(START_OFFSET_VALUE + FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS); - segmentDataManager._consumeOffsets.add(endOffset); - final SegmentCompletionProtocol.Response response = new SegmentCompletionProtocol.Response( - new SegmentCompletionProtocol.Response.Params().withStatus( - SegmentCompletionProtocol.ControllerResponseStatus.COMMIT) - .withStreamPartitionMsgOffset(endOffset.toString())); - segmentDataManager._responses.add(response); - - consumer.run(); - - try { - // millis() is called first in run before consumption, then once for each batch and once for each message in - // the batch, then once more when metrics are updated after each batch is processed and then 4 more times in - // run() after consume loop - Assert.assertEquals(timeSupplier._timeCheckCounter.get(), FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS + 8); - Assert.assertEquals(((LongMsgOffset) segmentDataManager.getCurrentOffset()).getOffset(), - START_OFFSET_VALUE + FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS); - Assert.assertEquals(segmentDataManager.getSegment().getNumDocsIndexed(), - FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS); - Assert.assertEquals(segmentDataManager.getSegment().getSegmentMetadata().getTotalDocs(), - FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS); - } finally { - segmentDataManager.destroy(); - } - } - - @Test - public void testShouldNotSkipUnfilteredMessagesIfNotIndexedAndRowCountThresholdIsReached() - throws Exception { - final int segmentTimeThresholdMins = 10; - TimeSupplier timeSupplier = new TimeSupplier(); - FakeLLRealtimeSegmentDataManager segmentDataManager = - createFakeSegmentManager(true, timeSupplier, String.valueOf(FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS), - segmentTimeThresholdMins + "m", null); - segmentDataManager._stubConsumeLoop = false; - segmentDataManager._state.set(segmentDataManager, LLRealtimeSegmentDataManager.State.INITIAL_CONSUMING); - - LLRealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); - final LongMsgOffset endOffset = - new LongMsgOffset(START_OFFSET_VALUE + FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS); - segmentDataManager._consumeOffsets.add(endOffset); - final SegmentCompletionProtocol.Response response = new SegmentCompletionProtocol.Response( - new SegmentCompletionProtocol.Response.Params().withStatus( - SegmentCompletionProtocol.ControllerResponseStatus.COMMIT) - .withStreamPartitionMsgOffset(endOffset.toString())); - segmentDataManager._responses.add(response); - - consumer.run(); - - try { - // millis() is called first in run before consumption, then once for each batch and once for each message in - // the batch, then once for metrics updates and then 4 more times in run() after consume loop - Assert.assertEquals(timeSupplier._timeCheckCounter.get(), FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS + 6); - Assert.assertEquals(((LongMsgOffset) segmentDataManager.getCurrentOffset()).getOffset(), - START_OFFSET_VALUE + FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS); - Assert.assertEquals(segmentDataManager.getSegment().getNumDocsIndexed(), - FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS); - Assert.assertEquals(segmentDataManager.getSegment().getSegmentMetadata().getTotalDocs(), - FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS); - } finally { - segmentDataManager.destroy(); - } - } - - private static class TimeSupplier implements Supplier { - protected final AtomicInteger _timeCheckCounter = new AtomicInteger(); - protected long _timeNow = System.currentTimeMillis(); - - @Override - public Long get() { - _timeCheckCounter.incrementAndGet(); - return _timeNow; - } - - public void set(long millis) { - _timeNow = millis; - } - - public void add(long millis) { - _timeNow += millis; - } - } - - public static class FakeLLRealtimeSegmentDataManager extends LLRealtimeSegmentDataManager { - - public Field _state; - public Field _shouldStop; - public Field _stopReason; - private Field _streamMsgOffsetFactory; - public LinkedList _consumeOffsets = new LinkedList<>(); - public LinkedList _responses = new LinkedList<>(); - public boolean _commitSegmentCalled = false; - public boolean _buildSegmentCalled = false; - public boolean _failSegmentBuild = false; - public boolean _buildAndReplaceCalled = false; - public int _stopWaitTimeMs = 100; - private boolean _downloadAndReplaceCalled = false; - public boolean _throwExceptionFromConsume = false; - public boolean _postConsumeStoppedCalled = false; - public Map _semaphoreMap; - public boolean _stubConsumeLoop = true; - private TimeSupplier _timeSupplier; - - private static InstanceDataManagerConfig makeInstanceDataManagerConfig() { - InstanceDataManagerConfig dataManagerConfig = mock(InstanceDataManagerConfig.class); - when(dataManagerConfig.getReadMode()).thenReturn(null); - when(dataManagerConfig.getAvgMultiValueCount()).thenReturn(null); - when(dataManagerConfig.getSegmentFormatVersion()).thenReturn(null); - when(dataManagerConfig.isEnableSplitCommit()).thenReturn(false); - when(dataManagerConfig.isRealtimeOffHeapAllocation()).thenReturn(false); - when(dataManagerConfig.getConfig()).thenReturn(new PinotConfiguration()); - return dataManagerConfig; - } - - public FakeLLRealtimeSegmentDataManager(SegmentZKMetadata segmentZKMetadata, TableConfig tableConfig, - RealtimeTableDataManager realtimeTableDataManager, String resourceDataDir, Schema schema, - LLCSegmentName llcSegmentName, Map semaphoreMap, ServerMetrics serverMetrics, - TimeSupplier timeSupplier) - throws Exception { - super(segmentZKMetadata, tableConfig, realtimeTableDataManager, resourceDataDir, - new IndexLoadingConfig(makeInstanceDataManagerConfig(), tableConfig), schema, llcSegmentName, - semaphoreMap.get(llcSegmentName.getPartitionGroupId()), serverMetrics, null, null); - _state = LLRealtimeSegmentDataManager.class.getDeclaredField("_state"); - _state.setAccessible(true); - _shouldStop = LLRealtimeSegmentDataManager.class.getDeclaredField("_shouldStop"); - _shouldStop.setAccessible(true); - _stopReason = LLRealtimeSegmentDataManager.class.getDeclaredField("_stopReason"); - _stopReason.setAccessible(true); - _semaphoreMap = semaphoreMap; - _streamMsgOffsetFactory = LLRealtimeSegmentDataManager.class.getDeclaredField("_streamPartitionMsgOffsetFactory"); - _streamMsgOffsetFactory.setAccessible(true); - _streamMsgOffsetFactory.set(this, new LongMsgOffsetFactory()); - _timeSupplier = timeSupplier; - } - - public String getStopReason() { - try { - return (String) _stopReason.get(this); - } catch (Exception e) { - Assert.fail(); - } - return null; - } - - public PartitionConsumer createPartitionConsumer() { - PartitionConsumer consumer = new PartitionConsumer(); - return consumer; - } - - public SegmentBuildDescriptor invokeBuildForCommit(long leaseTime) { - super.buildSegmentForCommit(leaseTime); - return getSegmentBuildDescriptor(); - } - - public boolean invokeCommit() { - SegmentCompletionProtocol.Response response = mock(SegmentCompletionProtocol.Response.class); - when(response.isSplitCommit()).thenReturn(false); - return super.commitSegment(response.getControllerVipUrl(), false); - } - - private void terminateLoopIfNecessary() { - if (_consumeOffsets.isEmpty() && _responses.isEmpty()) { - try { - _shouldStop.set(this, true); - } catch (Exception e) { - Assert.fail(); - } - } - } - - @Override - protected void startConsumerThread() { - // Do nothing. - } - - @Override - protected boolean consumeLoop() - throws Exception { - if (_stubConsumeLoop) { - if (_throwExceptionFromConsume) { - throw new PermanentConsumerException(new Throwable("Offset out of range")); - } - setCurrentOffset(_consumeOffsets.remove().getOffset()); - terminateLoopIfNecessary(); - return true; - } - return super.consumeLoop(); - } - - @Override - protected SegmentCompletionProtocol.Response postSegmentConsumedMsg() { - SegmentCompletionProtocol.Response response = _responses.remove(); - terminateLoopIfNecessary(); - return response; - } - - @Override - protected SegmentCompletionProtocol.Response commit(String controllerVipUrl, boolean isSplitCommit) { - SegmentCompletionProtocol.Response response = _responses.remove(); - return response; - } - - @Override - protected void postStopConsumedMsg(String reason) { - _postConsumeStoppedCalled = true; - } - - // TODO: Some of the tests rely on specific number of calls to the `now()` method in the SegmentDataManager. - // This is not a good coding practice and makes the code very fragile. This needs to be fixed. - // Invoking now() in any part of LLRealtimeSegmentDataManager code will break the following tests: - // 1. LLRealtimeSegmentDataManagerTest.testShouldNotSkipUnfilteredMessagesIfNotIndexedAndRowCountThresholdIsReached - // 2. LLRealtimeSegmentDataManagerTest.testShouldNotSkipUnfilteredMessagesIfNotIndexedAndTimeThresholdIsReached - @Override - protected long now() { - // now() is called in the constructor before _timeSupplier is set - if (_timeSupplier == null) { - return System.currentTimeMillis(); - } - return _timeSupplier.get(); - } - - @Override - protected void hold() { - _timeSupplier.add(5000L); - } - - @Override - protected boolean buildSegmentAndReplace() { - _buildAndReplaceCalled = true; - return true; - } - - @Override - protected SegmentBuildDescriptor buildSegmentInternal(boolean forCommit) { - _buildSegmentCalled = true; - if (_failSegmentBuild) { - return null; - } - if (!forCommit) { - return new SegmentBuildDescriptor(null, null, getCurrentOffset(), 0, 0, -1); - } - File segmentTarFile = new File(SEGMENT_DIR, "segmentFile"); - try { - segmentTarFile.createNewFile(); - } catch (IOException e) { - Assert.fail("Could not create file " + segmentTarFile); - } - return new SegmentBuildDescriptor(segmentTarFile, null, getCurrentOffset(), 0, 0, -1); - } - - @Override - protected boolean commitSegment(String controllerVipUrl, boolean isSplitCommit) { - _commitSegmentCalled = true; - return true; - } - - @Override - protected void downloadSegmentAndReplace(SegmentZKMetadata metadata) { - _downloadAndReplaceCalled = true; - } - - @Override - public void stop() { - _timeSupplier.add(_stopWaitTimeMs); - } - - public void setCurrentOffset(long offset) { - setOffset(offset, "_currentOffset"); - } - - public void setConsumeEndTime(long endTime) { - setLong(endTime, "_consumeEndTime"); - } - - public void setNumRowsConsumed(int numRows) { - setInt(numRows, "_numRowsConsumed"); - } - - public void setNumRowsIndexed(int numRows) { - setInt(numRows, "_numRowsIndexed"); - } - - public void setFinalOffset(long offset) { - setOffset(offset, "_finalOffset"); - } - - public boolean invokeEndCriteriaReached() { - Method endCriteriaReached = null; - try { - endCriteriaReached = LLRealtimeSegmentDataManager.class.getDeclaredMethod("endCriteriaReached"); - endCriteriaReached.setAccessible(true); - Boolean result = (Boolean) endCriteriaReached.invoke(this); - return result; - } catch (NoSuchMethodException e) { - Assert.fail(); - } catch (InvocationTargetException e) { - Assert.fail(); - } catch (IllegalAccessException e) { - Assert.fail(); - } - throw new RuntimeException("Cannot get here"); - } - - public void setSegmentMaxRowCount(int numRows) { - setInt(numRows, "_segmentMaxRowCount"); - } - - private void setLong(long value, String fieldName) { - try { - Field field = LLRealtimeSegmentDataManager.class.getDeclaredField(fieldName); - field.setAccessible(true); - field.setLong(this, value); - } catch (NoSuchFieldException e) { - Assert.fail(); - } catch (IllegalAccessException e) { - Assert.fail(); - } - } - - private void setOffset(long value, String fieldName) { - try { - Field field = LLRealtimeSegmentDataManager.class.getDeclaredField(fieldName); - field.setAccessible(true); - StreamPartitionMsgOffset offset = (StreamPartitionMsgOffset) field.get(this); -// if (offset == null) { - field.set(this, new LongMsgOffset(value)); -// } else { -// offset.setOffset(value); -// } - } catch (NoSuchFieldException e) { - Assert.fail(); - } catch (IllegalAccessException e) { - Assert.fail(); - } - } - - private void setInt(int value, String fieldName) { - try { - Field field = LLRealtimeSegmentDataManager.class.getDeclaredField(fieldName); - field.setAccessible(true); - field.setInt(this, value); - } catch (NoSuchFieldException e) { - Assert.fail(); - } catch (IllegalAccessException e) { - Assert.fail(); - } - } - } -} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/PinotFSSegmentUploaderTest.java b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/PinotFSSegmentUploaderTest.java index 62fa1a44efa9..df892bec95fb 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/PinotFSSegmentUploaderTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/PinotFSSegmentUploaderTest.java @@ -28,20 +28,23 @@ import java.util.UUID; import org.apache.commons.io.FileUtils; import org.apache.pinot.common.exception.HttpErrorStatusException; +import org.apache.pinot.common.metrics.ServerMetrics; import org.apache.pinot.common.utils.LLCSegmentName; import org.apache.pinot.spi.env.PinotConfiguration; import org.apache.pinot.spi.filesystem.BasePinotFS; import org.apache.pinot.spi.filesystem.PinotFSFactory; import org.apache.pinot.spi.utils.StringUtil; +import org.mockito.Mockito; import org.testng.Assert; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; public class PinotFSSegmentUploaderTest { - private static final int TIMEOUT_IN_MS = 100; + private static final int TIMEOUT_IN_MS = 1000; private File _file; private LLCSegmentName _llcSegmentName; + private ServerMetrics _serverMetrics = Mockito.mock(ServerMetrics.class); @BeforeClass public void setUp() @@ -61,7 +64,7 @@ public void setUp() @Test public void testSuccessfulUpload() { - SegmentUploader segmentUploader = new PinotFSSegmentUploader("hdfs://root", TIMEOUT_IN_MS); + SegmentUploader segmentUploader = new PinotFSSegmentUploader("hdfs://root", TIMEOUT_IN_MS, _serverMetrics); URI segmentURI = segmentUploader.uploadSegment(_file, _llcSegmentName); Assert.assertTrue(segmentURI.toString().startsWith(StringUtil .join(File.separator, "hdfs://root", _llcSegmentName.getTableName(), _llcSegmentName.getSegmentName()))); @@ -69,7 +72,7 @@ public void testSuccessfulUpload() { @Test public void testSegmentAlreadyExist() { - SegmentUploader segmentUploader = new PinotFSSegmentUploader("existing://root", TIMEOUT_IN_MS); + SegmentUploader segmentUploader = new PinotFSSegmentUploader("existing://root", TIMEOUT_IN_MS, _serverMetrics); URI segmentURI = segmentUploader.uploadSegment(_file, _llcSegmentName); Assert.assertTrue(segmentURI.toString().startsWith(StringUtil .join(File.separator, "existing://root", _llcSegmentName.getTableName(), _llcSegmentName.getSegmentName()))); @@ -77,14 +80,14 @@ public void testSegmentAlreadyExist() { @Test public void testUploadTimeOut() { - SegmentUploader segmentUploader = new PinotFSSegmentUploader("timeout://root", TIMEOUT_IN_MS); + SegmentUploader segmentUploader = new PinotFSSegmentUploader("timeout://root", TIMEOUT_IN_MS, _serverMetrics); URI segmentURI = segmentUploader.uploadSegment(_file, _llcSegmentName); Assert.assertNull(segmentURI); } @Test public void testNoSegmentStoreConfigured() { - SegmentUploader segmentUploader = new PinotFSSegmentUploader("", TIMEOUT_IN_MS); + SegmentUploader segmentUploader = new PinotFSSegmentUploader("", TIMEOUT_IN_MS, _serverMetrics); URI segmentURI = segmentUploader.uploadSegment(_file, _llcSegmentName); Assert.assertNull(segmentURI); } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/RealtimeConsumptionRateManagerTest.java b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/RealtimeConsumptionRateManagerTest.java index 21c58f9afa2e..325066fa1622 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/RealtimeConsumptionRateManagerTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/RealtimeConsumptionRateManagerTest.java @@ -27,14 +27,12 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import org.apache.pinot.common.metrics.ServerMetrics; +import org.apache.pinot.spi.env.PinotConfiguration; import org.apache.pinot.spi.stream.StreamConfig; +import org.apache.pinot.spi.utils.CommonConstants; import org.testng.annotations.Test; import static org.apache.pinot.core.data.manager.realtime.RealtimeConsumptionRateManager.*; -import static org.apache.pinot.core.data.manager.realtime.RealtimeConsumptionRateManager.ConsumptionRateLimiter; -import static org.apache.pinot.core.data.manager.realtime.RealtimeConsumptionRateManager.MetricEmitter; -import static org.apache.pinot.core.data.manager.realtime.RealtimeConsumptionRateManager.NOOP_RATE_LIMITER; -import static org.apache.pinot.core.data.manager.realtime.RealtimeConsumptionRateManager.RateLimiterImpl; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -51,6 +49,10 @@ public class RealtimeConsumptionRateManagerTest { private static final StreamConfig STREAM_CONFIG_A = mock(StreamConfig.class); private static final StreamConfig STREAM_CONFIG_B = mock(StreamConfig.class); private static final StreamConfig STREAM_CONFIG_C = mock(StreamConfig.class); + private static final PinotConfiguration SERVER_CONFIG_1 = mock(PinotConfiguration.class); + private static final PinotConfiguration SERVER_CONFIG_2 = mock(PinotConfiguration.class); + private static final PinotConfiguration SERVER_CONFIG_3 = mock(PinotConfiguration.class); + private static final PinotConfiguration SERVER_CONFIG_4 = mock(PinotConfiguration.class); private static RealtimeConsumptionRateManager _consumptionRateManager; static { @@ -65,6 +67,15 @@ public class RealtimeConsumptionRateManagerTest { when(STREAM_CONFIG_B.getTopicConsumptionRateLimit()).thenReturn(Optional.of(RATE_LIMIT_FOR_ENTIRE_TOPIC)); when(STREAM_CONFIG_C.getTopicConsumptionRateLimit()).thenReturn(Optional.empty()); _consumptionRateManager = new RealtimeConsumptionRateManager(cache); + + when(SERVER_CONFIG_1.getProperty(CommonConstants.Server.CONFIG_OF_SERVER_CONSUMPTION_RATE_LIMIT, + CommonConstants.Server.DEFAULT_SERVER_CONSUMPTION_RATE_LIMIT)).thenReturn(5.0); + when(SERVER_CONFIG_2.getProperty(CommonConstants.Server.CONFIG_OF_SERVER_CONSUMPTION_RATE_LIMIT, + CommonConstants.Server.DEFAULT_SERVER_CONSUMPTION_RATE_LIMIT)).thenReturn(2.5); + when(SERVER_CONFIG_3.getProperty(CommonConstants.Server.CONFIG_OF_SERVER_CONSUMPTION_RATE_LIMIT, + CommonConstants.Server.DEFAULT_SERVER_CONSUMPTION_RATE_LIMIT)).thenReturn(0.0); + when(SERVER_CONFIG_4.getProperty(CommonConstants.Server.CONFIG_OF_SERVER_CONSUMPTION_RATE_LIMIT, + CommonConstants.Server.DEFAULT_SERVER_CONSUMPTION_RATE_LIMIT)).thenReturn(-1.0); } @Test @@ -83,7 +94,27 @@ public void testCreateRateLimiter() { } @Test - public void testBuildCache() throws Exception { + public void testCreateServerRateLimiter() { + // Server config 1 + ConsumptionRateLimiter rateLimiter = _consumptionRateManager.createServerRateLimiter(SERVER_CONFIG_1, null); + assertEquals(5.0, ((RateLimiterImpl) rateLimiter).getRate(), DELTA); + + // Server config 2 + rateLimiter = _consumptionRateManager.createServerRateLimiter(SERVER_CONFIG_2, null); + assertEquals(2.5, ((RateLimiterImpl) rateLimiter).getRate(), DELTA); + + // Server config 3 + rateLimiter = _consumptionRateManager.createServerRateLimiter(SERVER_CONFIG_3, null); + assertEquals(rateLimiter, NOOP_RATE_LIMITER); + + // Server config 4 + rateLimiter = _consumptionRateManager.createServerRateLimiter(SERVER_CONFIG_4, null); + assertEquals(rateLimiter, NOOP_RATE_LIMITER); + } + + @Test + public void testBuildCache() + throws Exception { PartitionCountFetcher partitionCountFetcher = mock(PartitionCountFetcher.class); LoadingCache cache = buildCache(partitionCountFetcher, 500, TimeUnit.MILLISECONDS); when(partitionCountFetcher.fetch(STREAM_CONFIG_A)).thenReturn(10); @@ -150,21 +181,21 @@ public void testMetricEmitter() { now = Clock.fixed(Instant.parse("2022-08-10T12:01:05Z"), ZoneOffset.UTC).instant(); int sumOfMsgsInPrevMinute = sum(numMsgs); int expectedRatio = calcExpectedRatio(rateLimitInMinutes, sumOfMsgsInPrevMinute); - numMsgs = new int[] {35}; + numMsgs = new int[]{35}; assertEquals(metricEmitter.emitMetric(numMsgs[0], rateLimit, now), expectedRatio); // 3rd minute now = Clock.fixed(Instant.parse("2022-08-10T12:02:25Z"), ZoneOffset.UTC).instant(); sumOfMsgsInPrevMinute = sum(numMsgs); expectedRatio = calcExpectedRatio(rateLimitInMinutes, sumOfMsgsInPrevMinute); - numMsgs = new int[] {0}; + numMsgs = new int[]{0}; assertEquals(metricEmitter.emitMetric(numMsgs[0], rateLimit, now), expectedRatio); // 4th minute now = Clock.fixed(Instant.parse("2022-08-10T12:03:15Z"), ZoneOffset.UTC).instant(); sumOfMsgsInPrevMinute = sum(numMsgs); expectedRatio = calcExpectedRatio(rateLimitInMinutes, sumOfMsgsInPrevMinute); - numMsgs = new int[] {10, 20}; + numMsgs = new int[]{10, 20}; assertEquals(metricEmitter.emitMetric(numMsgs[0], rateLimit, now), expectedRatio); now = Clock.fixed(Instant.parse("2022-08-10T12:03:20Z"), ZoneOffset.UTC).instant(); assertEquals(metricEmitter.emitMetric(numMsgs[1], rateLimit, now), expectedRatio); @@ -173,7 +204,7 @@ public void testMetricEmitter() { now = Clock.fixed(Instant.parse("2022-08-10T12:04:30Z"), ZoneOffset.UTC).instant(); sumOfMsgsInPrevMinute = sum(numMsgs); expectedRatio = calcExpectedRatio(rateLimitInMinutes, sumOfMsgsInPrevMinute); - numMsgs = new int[] {5}; + numMsgs = new int[]{5}; assertEquals(metricEmitter.emitMetric(numMsgs[0], rateLimit, now), expectedRatio); } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/RealtimeSegmentDataManagerTest.java b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/RealtimeSegmentDataManagerTest.java new file mode 100644 index 000000000000..910edf7d9bc8 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/RealtimeSegmentDataManagerTest.java @@ -0,0 +1,1157 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.data.manager.realtime; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.time.Instant; +import java.util.LinkedList; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Lock; +import java.util.function.Supplier; +import javax.annotation.Nullable; +import org.apache.commons.io.FileUtils; +import org.apache.helix.HelixManager; +import org.apache.helix.store.zk.ZkHelixPropertyStore; +import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; +import org.apache.pinot.common.metrics.ServerMetrics; +import org.apache.pinot.common.protocols.SegmentCompletionProtocol; +import org.apache.pinot.common.utils.LLCSegmentName; +import org.apache.pinot.common.utils.config.TableConfigUtils; +import org.apache.pinot.core.data.manager.offline.TableDataManagerProvider; +import org.apache.pinot.core.realtime.impl.fakestream.FakeStreamConfigUtils; +import org.apache.pinot.core.realtime.impl.fakestream.FakeStreamConsumerFactory; +import org.apache.pinot.core.realtime.impl.fakestream.FakeStreamMessageDecoder; +import org.apache.pinot.segment.local.data.manager.TableDataManager; +import org.apache.pinot.segment.local.realtime.impl.RealtimeSegmentStatsHistory; +import org.apache.pinot.segment.local.segment.creator.Fixtures; +import org.apache.pinot.segment.local.segment.index.loader.IndexLoadingConfig; +import org.apache.pinot.segment.local.utils.SegmentLocks; +import org.apache.pinot.spi.config.instance.InstanceDataManagerConfig; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.metrics.PinotMetricUtils; +import org.apache.pinot.spi.stream.LongMsgOffset; +import org.apache.pinot.spi.stream.LongMsgOffsetFactory; +import org.apache.pinot.spi.stream.PermanentConsumerException; +import org.apache.pinot.spi.stream.StreamConfigProperties; +import org.apache.pinot.spi.stream.StreamPartitionMsgOffset; +import org.apache.pinot.spi.utils.CommonConstants; +import org.apache.pinot.spi.utils.builder.TableNameBuilder; +import org.apache.pinot.util.TestUtils; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + + +// TODO Re-write this test using the stream abstraction +public class RealtimeSegmentDataManagerTest { + private static final File TEMP_DIR = new File(FileUtils.getTempDirectory(), "RealtimeSegmentDataManagerTest"); + private static final String RAW_TABLE_NAME = "testTable"; + private static final String REALTIME_TABLE_NAME = TableNameBuilder.REALTIME.tableNameWithType(RAW_TABLE_NAME); + private static final int PARTITION_GROUP_ID = 0; + private static final int SEQUENCE_ID = 945; + private static final long SEG_TIME_MS = 98347869999L; + private static final LLCSegmentName SEGMENT_NAME = + new LLCSegmentName(RAW_TABLE_NAME, PARTITION_GROUP_ID, SEQUENCE_ID, SEG_TIME_MS); + private static final String SEGMENT_NAME_STR = SEGMENT_NAME.getSegmentName(); + private static final long START_OFFSET_VALUE = 198L; + private static final LongMsgOffset START_OFFSET = new LongMsgOffset(START_OFFSET_VALUE); + + private final Map _partitionGroupIdToSemaphoreMap = new ConcurrentHashMap<>(); + + private static TableConfig createTableConfig() + throws Exception { + return Fixtures.createTableConfig(FakeStreamConsumerFactory.class.getName(), + FakeStreamMessageDecoder.class.getName()); + } + + private RealtimeTableDataManager createTableDataManager(TableConfig tableConfig) { + final String instanceId = "server-1"; + SegmentBuildTimeLeaseExtender.getOrCreate(instanceId, new ServerMetrics(PinotMetricUtils.getPinotMetricsRegistry()), + tableConfig.getTableName()); + RealtimeTableDataManager tableDataManager = mock(RealtimeTableDataManager.class); + when(tableDataManager.getInstanceId()).thenReturn(instanceId); + when(tableDataManager.getSegmentLock(any())).thenReturn(mock(Lock.class)); + RealtimeSegmentStatsHistory statsHistory = mock(RealtimeSegmentStatsHistory.class); + when(statsHistory.getEstimatedCardinality(anyString())).thenReturn(200); + when(statsHistory.getEstimatedAvgColSize(anyString())).thenReturn(32); + when(tableDataManager.getStatsHistory()).thenReturn(statsHistory); + when(tableDataManager.getConsumerDir()).thenReturn(TEMP_DIR.getAbsolutePath() + "/consumerDir"); + return tableDataManager; + } + + private SegmentZKMetadata createZkMetadata() { + SegmentZKMetadata segmentZKMetadata = new SegmentZKMetadata(SEGMENT_NAME_STR); + segmentZKMetadata.setStartOffset(START_OFFSET.toString()); + segmentZKMetadata.setCreationTime(System.currentTimeMillis()); + segmentZKMetadata.setStatus(CommonConstants.Segment.Realtime.Status.IN_PROGRESS); + return segmentZKMetadata; + } + + private FakeRealtimeSegmentDataManager createFakeSegmentManager() + throws Exception { + return createFakeSegmentManager(false, new TimeSupplier(), null, null, null); + } + + private FakeRealtimeSegmentDataManager createFakeSegmentManager(boolean noUpsert, TimeSupplier timeSupplier, + @Nullable String maxRows, @Nullable String maxDuration, @Nullable TableConfig tableConfig) + throws Exception { + SegmentZKMetadata segmentZKMetadata = createZkMetadata(); + if (tableConfig == null) { + tableConfig = createTableConfig(); + } + if (noUpsert) { + tableConfig.setUpsertConfig(null); + } + if (maxRows != null) { + tableConfig.getIndexingConfig().getStreamConfigs() + .put(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_ROWS, maxRows); + } + if (maxDuration != null) { + tableConfig.getIndexingConfig().getStreamConfigs() + .put(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_TIME, maxDuration); + } + RealtimeTableDataManager tableDataManager = createTableDataManager(tableConfig); + LLCSegmentName llcSegmentName = new LLCSegmentName(SEGMENT_NAME_STR); + _partitionGroupIdToSemaphoreMap.putIfAbsent(PARTITION_GROUP_ID, new Semaphore(1)); + Schema schema = Fixtures.createSchema(); + ServerMetrics serverMetrics = new ServerMetrics(PinotMetricUtils.getPinotMetricsRegistry()); + return new FakeRealtimeSegmentDataManager(segmentZKMetadata, tableConfig, tableDataManager, + new File(TEMP_DIR, REALTIME_TABLE_NAME).getAbsolutePath(), schema, llcSegmentName, + _partitionGroupIdToSemaphoreMap, serverMetrics, timeSupplier); + } + + @BeforeClass + public void setUp() { + ServerMetrics.register(mock(ServerMetrics.class)); + + FileUtils.deleteQuietly(TEMP_DIR); + SegmentBuildTimeLeaseExtender.initExecutor(); + } + + @AfterClass + public void tearDown() { + FileUtils.deleteQuietly(TEMP_DIR); + SegmentBuildTimeLeaseExtender.shutdownExecutor(); + } + + // Test that we are in HOLDING state as long as the controller responds HOLD to our segmentConsumed() message. + // we should not consume when holding. + @Test + public void testHolding() + throws Exception { + FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); + RealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); + final LongMsgOffset endOffset = new LongMsgOffset(START_OFFSET_VALUE + 500); + // We should consume initially... + segmentDataManager._consumeOffsets.add(endOffset); + final SegmentCompletionProtocol.Response response = new SegmentCompletionProtocol.Response( + new SegmentCompletionProtocol.Response.Params().withStatus( + SegmentCompletionProtocol.ControllerResponseStatus.HOLD) + .withStreamPartitionMsgOffset(endOffset.toString())); + // And then never consume as long as we get a hold response, 100 times. + for (int i = 0; i < 100; i++) { + segmentDataManager._responses.add(response); + } + + consumer.run(); + + Assert.assertTrue(segmentDataManager._responses.isEmpty()); + Assert.assertTrue(segmentDataManager._consumeOffsets.isEmpty()); + Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); + Assert.assertFalse(segmentDataManager._buildSegmentCalled); + Assert.assertFalse(segmentDataManager._commitSegmentCalled); + Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled); + Assert.assertEquals(segmentDataManager._state.get(segmentDataManager), RealtimeSegmentDataManager.State.HOLDING); + segmentDataManager.close(); + } + + // Test that we go to commit when the controller responds commit after 2 holds. + @Test + public void testCommitAfterHold() + throws Exception { + FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); + RealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); + final LongMsgOffset endOffset = new LongMsgOffset(START_OFFSET_VALUE + 500); + // We should consume initially... + segmentDataManager._consumeOffsets.add(endOffset); + final SegmentCompletionProtocol.Response holdResponse = new SegmentCompletionProtocol.Response( + new SegmentCompletionProtocol.Response.Params().withStreamPartitionMsgOffset(endOffset.toString()) + .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.HOLD)); + final SegmentCompletionProtocol.Response commitResponse = new SegmentCompletionProtocol.Response( + new SegmentCompletionProtocol.Response.Params().withStreamPartitionMsgOffset(endOffset.toString()) + .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.COMMIT)); + // And then never consume as long as we get a hold response, 100 times. + segmentDataManager._responses.add(holdResponse); + segmentDataManager._responses.add(commitResponse); + + consumer.run(); + + Assert.assertTrue(segmentDataManager._responses.isEmpty()); + Assert.assertTrue(segmentDataManager._consumeOffsets.isEmpty()); + Assert.assertTrue(segmentDataManager._buildSegmentCalled); + Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); + Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled); + Assert.assertTrue(segmentDataManager._commitSegmentCalled); + Assert.assertEquals(segmentDataManager._state.get(segmentDataManager), RealtimeSegmentDataManager.State.COMMITTED); + segmentDataManager.close(); + } + + @Test + public void testSegmentBuildException() + throws Exception { + FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); + RealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); + final LongMsgOffset endOffset = new LongMsgOffset(START_OFFSET_VALUE + 500); + // We should consume initially... + segmentDataManager._consumeOffsets.add(endOffset); + final SegmentCompletionProtocol.Response commitResponse = new SegmentCompletionProtocol.Response( + new SegmentCompletionProtocol.Response.Params().withStreamPartitionMsgOffset(endOffset.toString()) + .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.COMMIT)); + segmentDataManager._responses.add(commitResponse); + segmentDataManager._failSegmentBuild = true; + + consumer.run(); + Assert.assertTrue(segmentDataManager._buildSegmentCalled); + Assert.assertEquals(segmentDataManager._state.get(segmentDataManager), RealtimeSegmentDataManager.State.ERROR); + segmentDataManager.close(); + } + + // Test hold, catchup. hold, commit + @Test + public void testCommitAfterCatchup() + throws Exception { + FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); + RealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); + final LongMsgOffset firstOffset = new LongMsgOffset(START_OFFSET_VALUE + 500); + final LongMsgOffset catchupOffset = new LongMsgOffset(firstOffset.getOffset() + 10); + // We should consume initially... + segmentDataManager._consumeOffsets.add(firstOffset); + segmentDataManager._consumeOffsets.add(catchupOffset); // Offset after catchup + final SegmentCompletionProtocol.Response holdResponse1 = new SegmentCompletionProtocol.Response( + new SegmentCompletionProtocol.Response.Params().withStatus( + SegmentCompletionProtocol.ControllerResponseStatus.HOLD) + .withStreamPartitionMsgOffset(firstOffset.toString())); + final SegmentCompletionProtocol.Response catchupResponse = new SegmentCompletionProtocol.Response( + new SegmentCompletionProtocol.Response.Params().withStatus( + SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP) + .withStreamPartitionMsgOffset(catchupOffset.toString())); + final SegmentCompletionProtocol.Response holdResponse2 = new SegmentCompletionProtocol.Response( + new SegmentCompletionProtocol.Response.Params().withStreamPartitionMsgOffset(catchupOffset.toString()) + .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.HOLD)); + final SegmentCompletionProtocol.Response commitResponse = new SegmentCompletionProtocol.Response( + new SegmentCompletionProtocol.Response.Params().withStreamPartitionMsgOffset(catchupOffset.toString()) + .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.COMMIT)); + // And then never consume as long as we get a hold response, 100 times. + segmentDataManager._responses.add(holdResponse1); + segmentDataManager._responses.add(catchupResponse); + segmentDataManager._responses.add(holdResponse2); + segmentDataManager._responses.add(commitResponse); + + consumer.run(); + + Assert.assertTrue(segmentDataManager._responses.isEmpty()); + Assert.assertTrue(segmentDataManager._consumeOffsets.isEmpty()); + Assert.assertTrue(segmentDataManager._buildSegmentCalled); + Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); + Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled); + Assert.assertTrue(segmentDataManager._commitSegmentCalled); + Assert.assertEquals(segmentDataManager._state.get(segmentDataManager), RealtimeSegmentDataManager.State.COMMITTED); + segmentDataManager.close(); + } + + @Test + public void testCommitAfterCatchupWithPeriodOffset() + throws Exception { + TableConfig tableConfig = createTableConfig(); + tableConfig.getIndexingConfig().getStreamConfigs().put( + StreamConfigProperties.constructStreamProperty(StreamConfigProperties.STREAM_CONSUMER_OFFSET_CRITERIA, + "fakeStream"), "2d"); + FakeRealtimeSegmentDataManager segmentDataManager = + createFakeSegmentManager(false, new TimeSupplier(), null, null, tableConfig); + RealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); + final LongMsgOffset firstOffset = new LongMsgOffset(START_OFFSET_VALUE + 500); + final LongMsgOffset catchupOffset = new LongMsgOffset(firstOffset.getOffset() + 10); + // We should consume initially... + segmentDataManager._consumeOffsets.add(firstOffset); + segmentDataManager._consumeOffsets.add(catchupOffset); // Offset after catchup + final SegmentCompletionProtocol.Response holdResponse1 = new SegmentCompletionProtocol.Response( + new SegmentCompletionProtocol.Response.Params().withStatus( + SegmentCompletionProtocol.ControllerResponseStatus.HOLD) + .withStreamPartitionMsgOffset(firstOffset.toString())); + final SegmentCompletionProtocol.Response catchupResponse = new SegmentCompletionProtocol.Response( + new SegmentCompletionProtocol.Response.Params().withStatus( + SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP) + .withStreamPartitionMsgOffset(catchupOffset.toString())); + final SegmentCompletionProtocol.Response holdResponse2 = new SegmentCompletionProtocol.Response( + new SegmentCompletionProtocol.Response.Params().withStreamPartitionMsgOffset(catchupOffset.toString()) + .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.HOLD)); + final SegmentCompletionProtocol.Response commitResponse = new SegmentCompletionProtocol.Response( + new SegmentCompletionProtocol.Response.Params().withStreamPartitionMsgOffset(catchupOffset.toString()) + .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.COMMIT)); + // And then never consume as long as we get a hold response, 100 times. + segmentDataManager._responses.add(holdResponse1); + segmentDataManager._responses.add(catchupResponse); + segmentDataManager._responses.add(holdResponse2); + segmentDataManager._responses.add(commitResponse); + + consumer.run(); + + Assert.assertTrue(segmentDataManager._responses.isEmpty()); + Assert.assertTrue(segmentDataManager._consumeOffsets.isEmpty()); + Assert.assertTrue(segmentDataManager._buildSegmentCalled); + Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); + Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled); + Assert.assertTrue(segmentDataManager._commitSegmentCalled); + Assert.assertEquals(segmentDataManager._state.get(segmentDataManager), RealtimeSegmentDataManager.State.COMMITTED); + segmentDataManager.close(); + } + + @Test + public void testCommitAfterCatchupWithTimestampOffset() + throws Exception { + TableConfig tableConfig = createTableConfig(); + tableConfig.getIndexingConfig().getStreamConfigs().put( + StreamConfigProperties.constructStreamProperty(StreamConfigProperties.STREAM_CONSUMER_OFFSET_CRITERIA, + "fakeStream"), Instant.now().toString()); + FakeRealtimeSegmentDataManager segmentDataManager = + createFakeSegmentManager(false, new TimeSupplier(), null, null, tableConfig); + RealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); + final LongMsgOffset firstOffset = new LongMsgOffset(START_OFFSET_VALUE + 500); + final LongMsgOffset catchupOffset = new LongMsgOffset(firstOffset.getOffset() + 10); + // We should consume initially... + segmentDataManager._consumeOffsets.add(firstOffset); + segmentDataManager._consumeOffsets.add(catchupOffset); // Offset after catchup + final SegmentCompletionProtocol.Response holdResponse1 = new SegmentCompletionProtocol.Response( + new SegmentCompletionProtocol.Response.Params().withStatus( + SegmentCompletionProtocol.ControllerResponseStatus.HOLD) + .withStreamPartitionMsgOffset(firstOffset.toString())); + final SegmentCompletionProtocol.Response catchupResponse = new SegmentCompletionProtocol.Response( + new SegmentCompletionProtocol.Response.Params().withStatus( + SegmentCompletionProtocol.ControllerResponseStatus.CATCH_UP) + .withStreamPartitionMsgOffset(catchupOffset.toString())); + final SegmentCompletionProtocol.Response holdResponse2 = new SegmentCompletionProtocol.Response( + new SegmentCompletionProtocol.Response.Params().withStreamPartitionMsgOffset(catchupOffset.toString()) + .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.HOLD)); + final SegmentCompletionProtocol.Response commitResponse = new SegmentCompletionProtocol.Response( + new SegmentCompletionProtocol.Response.Params().withStreamPartitionMsgOffset(catchupOffset.toString()) + .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.COMMIT)); + // And then never consume as long as we get a hold response, 100 times. + segmentDataManager._responses.add(holdResponse1); + segmentDataManager._responses.add(catchupResponse); + segmentDataManager._responses.add(holdResponse2); + segmentDataManager._responses.add(commitResponse); + + consumer.run(); + + Assert.assertTrue(segmentDataManager._responses.isEmpty()); + Assert.assertTrue(segmentDataManager._consumeOffsets.isEmpty()); + Assert.assertTrue(segmentDataManager._buildSegmentCalled); + Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); + Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled); + Assert.assertTrue(segmentDataManager._commitSegmentCalled); + Assert.assertEquals(segmentDataManager._state.get(segmentDataManager), RealtimeSegmentDataManager.State.COMMITTED); + segmentDataManager.close(); + } + + @Test + public void testDiscarded() + throws Exception { + FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); + RealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); + final LongMsgOffset endOffset = new LongMsgOffset(START_OFFSET_VALUE + 500); + segmentDataManager._consumeOffsets.add(endOffset); + final SegmentCompletionProtocol.Response discardResponse = new SegmentCompletionProtocol.Response( + new SegmentCompletionProtocol.Response.Params().withStreamPartitionMsgOffset(endOffset.toString()) + .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.DISCARD)); + segmentDataManager._responses.add(discardResponse); + + consumer.run(); + + Assert.assertTrue(segmentDataManager._responses.isEmpty()); + Assert.assertTrue(segmentDataManager._consumeOffsets.isEmpty()); + Assert.assertFalse(segmentDataManager._buildSegmentCalled); + Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); + Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled); + Assert.assertFalse(segmentDataManager._commitSegmentCalled); + Assert.assertEquals(segmentDataManager._state.get(segmentDataManager), RealtimeSegmentDataManager.State.DISCARDED); + segmentDataManager.close(); + } + + @Test + public void testRetained() + throws Exception { + FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); + RealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); + final LongMsgOffset endOffset = new LongMsgOffset(START_OFFSET_VALUE + 500); + segmentDataManager._consumeOffsets.add(endOffset); + SegmentCompletionProtocol.Response.Params params = new SegmentCompletionProtocol.Response.Params(); + params.withStreamPartitionMsgOffset(endOffset.toString()) + .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.KEEP); + final SegmentCompletionProtocol.Response keepResponse = new SegmentCompletionProtocol.Response(params); + segmentDataManager._responses.add(keepResponse); + + consumer.run(); + + Assert.assertTrue(segmentDataManager._responses.isEmpty()); + Assert.assertTrue(segmentDataManager._consumeOffsets.isEmpty()); + Assert.assertFalse(segmentDataManager._buildSegmentCalled); + Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled); + Assert.assertTrue(segmentDataManager._buildAndReplaceCalled); + Assert.assertFalse(segmentDataManager._commitSegmentCalled); + Assert.assertEquals(segmentDataManager._state.get(segmentDataManager), RealtimeSegmentDataManager.State.RETAINED); + segmentDataManager.close(); + } + + @Test + public void testNotLeader() + throws Exception { + FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); + RealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); + final LongMsgOffset endOffset = new LongMsgOffset(START_OFFSET_VALUE + 500); + // We should consume initially... + segmentDataManager._consumeOffsets.add(endOffset); + final SegmentCompletionProtocol.Response response = new SegmentCompletionProtocol.Response( + new SegmentCompletionProtocol.Response.Params().withStreamPartitionMsgOffset(endOffset.toString()) + .withStatus(SegmentCompletionProtocol.ControllerResponseStatus.NOT_LEADER)); + // And then never consume as long as we get a Not leader response, 100 times. + for (int i = 0; i < 100; i++) { + segmentDataManager._responses.add(response); + } + + consumer.run(); + + Assert.assertTrue(segmentDataManager._responses.isEmpty()); + Assert.assertTrue(segmentDataManager._consumeOffsets.isEmpty()); + Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); + Assert.assertFalse(segmentDataManager._buildSegmentCalled); + Assert.assertFalse(segmentDataManager._commitSegmentCalled); + Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled); + Assert.assertEquals(segmentDataManager._state.get(segmentDataManager), RealtimeSegmentDataManager.State.HOLDING); + segmentDataManager.close(); + } + + @Test + public void testConsumingException() + throws Exception { + FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); + RealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); + + segmentDataManager._throwExceptionFromConsume = true; + segmentDataManager._postConsumeStoppedCalled = false; + consumer.run(); + Assert.assertTrue(segmentDataManager._postConsumeStoppedCalled); + segmentDataManager.close(); + } + + // Tests to go online from consuming state + + // If the state is is COMMITTED or RETAINED, nothing to do + // If discarded or error state, then downloadAndReplace the segment + @Test + public void testOnlineTransitionAfterStop() + throws Exception { + SegmentZKMetadata metadata = new SegmentZKMetadata(SEGMENT_NAME_STR); + final long finalOffsetValue = START_OFFSET_VALUE + 600; + final LongMsgOffset finalOffset = new LongMsgOffset(finalOffsetValue); + metadata.setEndOffset(finalOffset.toString()); + + try (FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager()) { + segmentDataManager._stopWaitTimeMs = 0; + segmentDataManager._state.set(segmentDataManager, RealtimeSegmentDataManager.State.COMMITTED); + segmentDataManager.goOnlineFromConsuming(metadata); + Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled); + Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); + } + + try (FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager()) { + segmentDataManager._stopWaitTimeMs = 0; + segmentDataManager._state.set(segmentDataManager, RealtimeSegmentDataManager.State.RETAINED); + segmentDataManager.goOnlineFromConsuming(metadata); + Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled); + Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); + } + + try (FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager()) { + segmentDataManager._stopWaitTimeMs = 0; + segmentDataManager._state.set(segmentDataManager, RealtimeSegmentDataManager.State.DISCARDED); + segmentDataManager.goOnlineFromConsuming(metadata); + Assert.assertTrue(segmentDataManager._downloadAndReplaceCalled); + Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); + } + + try (FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager()) { + segmentDataManager._stopWaitTimeMs = 0; + segmentDataManager._state.set(segmentDataManager, RealtimeSegmentDataManager.State.ERROR); + segmentDataManager.goOnlineFromConsuming(metadata); + Assert.assertTrue(segmentDataManager._downloadAndReplaceCalled); + Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); + } + + // If holding, but we have overshot the expected final offset, the download and replace + try (FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager()) { + segmentDataManager._stopWaitTimeMs = 0; + segmentDataManager._state.set(segmentDataManager, RealtimeSegmentDataManager.State.HOLDING); + segmentDataManager.setCurrentOffset(finalOffsetValue + 1); + segmentDataManager.goOnlineFromConsuming(metadata); + Assert.assertTrue(segmentDataManager._downloadAndReplaceCalled); + Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); + } + + // If catching up, but we have overshot the expected final offset, the download and replace + try (FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager()) { + segmentDataManager._stopWaitTimeMs = 0; + segmentDataManager._state.set(segmentDataManager, RealtimeSegmentDataManager.State.CATCHING_UP); + segmentDataManager.setCurrentOffset(finalOffsetValue + 1); + segmentDataManager.goOnlineFromConsuming(metadata); + Assert.assertTrue(segmentDataManager._downloadAndReplaceCalled); + Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); + } + + // If catching up, but we did not get to the final offset, then download and replace + try (FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager()) { + segmentDataManager._stopWaitTimeMs = 0; + segmentDataManager._state.set(segmentDataManager, RealtimeSegmentDataManager.State.CATCHING_UP); + segmentDataManager._consumeOffsets.add(new LongMsgOffset(finalOffsetValue - 1)); + segmentDataManager.goOnlineFromConsuming(metadata); + Assert.assertTrue(segmentDataManager._downloadAndReplaceCalled); + Assert.assertFalse(segmentDataManager._buildAndReplaceCalled); + } + + // But then if we get to the exact offset, we get to build and replace, not download + try (FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager()) { + segmentDataManager._stopWaitTimeMs = 0; + segmentDataManager._state.set(segmentDataManager, RealtimeSegmentDataManager.State.CATCHING_UP); + segmentDataManager._consumeOffsets.add(finalOffset); + segmentDataManager.goOnlineFromConsuming(metadata); + Assert.assertFalse(segmentDataManager._downloadAndReplaceCalled); + Assert.assertTrue(segmentDataManager._buildAndReplaceCalled); + } + } + + @Test + public void testEndCriteriaChecking() + throws Exception { + // test reaching max row limit + try (FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager()) { + segmentDataManager._state.set(segmentDataManager, RealtimeSegmentDataManager.State.INITIAL_CONSUMING); + Assert.assertFalse(segmentDataManager.invokeEndCriteriaReached()); + segmentDataManager.setNumRowsIndexed(Fixtures.MAX_ROWS_IN_SEGMENT - 1); + Assert.assertFalse(segmentDataManager.invokeEndCriteriaReached()); + segmentDataManager.setNumRowsIndexed(Fixtures.MAX_ROWS_IN_SEGMENT); + Assert.assertTrue(segmentDataManager.invokeEndCriteriaReached()); + Assert.assertEquals(segmentDataManager.getStopReason(), SegmentCompletionProtocol.REASON_ROW_LIMIT); + } + // test reaching max time limit + try (FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager()) { + segmentDataManager._state.set(segmentDataManager, RealtimeSegmentDataManager.State.INITIAL_CONSUMING); + Assert.assertFalse(segmentDataManager.invokeEndCriteriaReached()); + // We should still get false because there is no messages fetched + segmentDataManager._timeSupplier.add(Fixtures.MAX_TIME_FOR_SEGMENT_CLOSE_MS + 1); + Assert.assertFalse(segmentDataManager.invokeEndCriteriaReached()); + // Once there are messages fetched, and the time exceeds the extended hour, we should get true + setHasMessagesFetched(segmentDataManager, true); + segmentDataManager._timeSupplier.add(TimeUnit.HOURS.toMillis(1)); + Assert.assertTrue(segmentDataManager.invokeEndCriteriaReached()); + Assert.assertEquals(segmentDataManager.getStopReason(), SegmentCompletionProtocol.REASON_TIME_LIMIT); + } + // In catching up state, test reaching final offset + try (FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager()) { + segmentDataManager._state.set(segmentDataManager, RealtimeSegmentDataManager.State.CATCHING_UP); + final long finalOffset = START_OFFSET_VALUE + 100; + segmentDataManager.setFinalOffset(finalOffset); + segmentDataManager.setCurrentOffset(finalOffset - 1); + Assert.assertFalse(segmentDataManager.invokeEndCriteriaReached()); + segmentDataManager.setCurrentOffset(finalOffset); + Assert.assertTrue(segmentDataManager.invokeEndCriteriaReached()); + } + // In catching up state, test reaching final offset ignoring time + try (FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager()) { + segmentDataManager._timeSupplier.add(Fixtures.MAX_TIME_FOR_SEGMENT_CLOSE_MS); + segmentDataManager._state.set(segmentDataManager, RealtimeSegmentDataManager.State.CATCHING_UP); + final long finalOffset = START_OFFSET_VALUE + 100; + segmentDataManager.setFinalOffset(finalOffset); + segmentDataManager.setCurrentOffset(finalOffset - 1); + Assert.assertFalse(segmentDataManager.invokeEndCriteriaReached()); + segmentDataManager.setCurrentOffset(finalOffset); + Assert.assertTrue(segmentDataManager.invokeEndCriteriaReached()); + } + // When we go from consuming to online state, time and final offset matter. + // Case 1. We have reached final offset. + try (FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager()) { + segmentDataManager._timeSupplier.add(1); + segmentDataManager._state.set(segmentDataManager, RealtimeSegmentDataManager.State.CONSUMING_TO_ONLINE); + segmentDataManager.setConsumeEndTime(segmentDataManager._timeSupplier.get() + 10); + final long finalOffset = START_OFFSET_VALUE + 100; + segmentDataManager.setFinalOffset(finalOffset); + segmentDataManager.setCurrentOffset(finalOffset - 1); + Assert.assertFalse(segmentDataManager.invokeEndCriteriaReached()); + segmentDataManager.setCurrentOffset(finalOffset); + Assert.assertTrue(segmentDataManager.invokeEndCriteriaReached()); + } + // Case 2. We have reached time limit. + try (FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager()) { + segmentDataManager._state.set(segmentDataManager, RealtimeSegmentDataManager.State.CONSUMING_TO_ONLINE); + final long endTime = segmentDataManager._timeSupplier.get() + 10; + segmentDataManager.setConsumeEndTime(endTime); + final long finalOffset = START_OFFSET_VALUE + 100; + segmentDataManager.setFinalOffset(finalOffset); + segmentDataManager.setCurrentOffset(finalOffset - 1); + segmentDataManager._timeSupplier.set(endTime - 1); + Assert.assertFalse(segmentDataManager.invokeEndCriteriaReached()); + segmentDataManager._timeSupplier.set(endTime); + Assert.assertTrue(segmentDataManager.invokeEndCriteriaReached()); + } + } + + private void setHasMessagesFetched(FakeRealtimeSegmentDataManager segmentDataManager, boolean hasMessagesFetched) + throws Exception { + Field field = RealtimeSegmentDataManager.class.getDeclaredField("_hasMessagesFetched"); + field.setAccessible(true); + field.set(segmentDataManager, hasMessagesFetched); + } + + // If commit fails, make sure that we do not re-build the segment when we try to commit again. + @Test + public void testReuseOfBuiltSegment() + throws Exception { + FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); + + SegmentCompletionProtocol.Response.Params params = new SegmentCompletionProtocol.Response.Params(); + params.withStatus(SegmentCompletionProtocol.ControllerResponseStatus.COMMIT_SUCCESS); + SegmentCompletionProtocol.Response commitSuccess = new SegmentCompletionProtocol.Response(params); + params.withStatus(SegmentCompletionProtocol.ControllerResponseStatus.FAILED); + SegmentCompletionProtocol.Response commitFailed = new SegmentCompletionProtocol.Response(params); + + // Set up the responses so that we get a failed respnse first and then a success response. + segmentDataManager._responses.add(commitFailed); + segmentDataManager._responses.add(commitSuccess); + final long leaseTime = 50000L; + + // The first time we invoke build, it should go ahead and build the segment. + File segmentTarFile = segmentDataManager.invokeBuildForCommit(leaseTime).getSegmentTarFile(); + Assert.assertNotNull(segmentTarFile); + Assert.assertTrue(segmentDataManager._buildSegmentCalled); + Assert.assertFalse(segmentDataManager.invokeCommit()); + Assert.assertTrue(segmentTarFile.exists()); + + segmentDataManager._buildSegmentCalled = false; + + // This time around it should not build the segment. + File segmentTarFile1 = segmentDataManager.invokeBuildForCommit(leaseTime).getSegmentTarFile(); + Assert.assertFalse(segmentDataManager._buildSegmentCalled); + Assert.assertEquals(segmentTarFile1, segmentTarFile); + Assert.assertTrue(segmentTarFile.exists()); + Assert.assertTrue(segmentDataManager.invokeCommit()); + Assert.assertFalse(segmentTarFile.exists()); + segmentDataManager.close(); + } + + // If commit fails, and we still have the file, make sure that we remove the file when we go + // online. + @Test + public void testFileRemovedDuringOnlineTransition() + throws Exception { + FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(); + + SegmentCompletionProtocol.Response.Params params = new SegmentCompletionProtocol.Response.Params(); + params.withStatus(SegmentCompletionProtocol.ControllerResponseStatus.FAILED); + SegmentCompletionProtocol.Response commitFailed = new SegmentCompletionProtocol.Response(params); + + // Set up the responses so that we get a failed response first and then a success response. + segmentDataManager._responses.add(commitFailed); + final long leaseTime = 50000L; + final long finalOffset = START_OFFSET_VALUE + 600; + segmentDataManager.setCurrentOffset(finalOffset); + + // We have set up commit to fail, so we should carry over the segment file. + File segmentTarFile = segmentDataManager.invokeBuildForCommit(leaseTime).getSegmentTarFile(); + Assert.assertNotNull(segmentTarFile); + Assert.assertTrue(segmentDataManager._buildSegmentCalled); + Assert.assertFalse(segmentDataManager.invokeCommit()); + Assert.assertTrue(segmentTarFile.exists()); + + // Now let the segment go ONLINE from CONSUMING, and ensure that the file is removed. + SegmentZKMetadata metadata = new SegmentZKMetadata(SEGMENT_NAME_STR); + metadata.setEndOffset(new LongMsgOffset(finalOffset).toString()); + segmentDataManager._stopWaitTimeMs = 0; + segmentDataManager._state.set(segmentDataManager, RealtimeSegmentDataManager.State.HOLDING); + segmentDataManager.goOnlineFromConsuming(metadata); + Assert.assertFalse(segmentTarFile.exists()); + segmentDataManager.close(); + } + + @Test + public void testOnlyOneSegmentHoldingTheSemaphoreForParticularPartition() + throws Exception { + long timeout = 10_000L; + FakeRealtimeSegmentDataManager firstSegmentDataManager = createFakeSegmentManager(); + Assert.assertTrue(firstSegmentDataManager.getAcquiredConsumerSemaphore().get()); + Semaphore firstSemaphore = firstSegmentDataManager.getPartitionGroupConsumerSemaphore(); + Assert.assertEquals(firstSemaphore.availablePermits(), 0); + Assert.assertFalse(firstSemaphore.hasQueuedThreads()); + + AtomicReference secondSegmentDataManager = new AtomicReference<>(null); + + // Construct the second segment manager, which will be blocked on the semaphore. + Thread constructSecondSegmentManager = new Thread(() -> { + try { + secondSegmentDataManager.set(createFakeSegmentManager()); + } catch (Exception e) { + throw new RuntimeException("Exception when sleeping for " + timeout + "ms", e); + } + }); + constructSecondSegmentManager.start(); + + // Wait until the second segment manager gets blocked on the semaphore. + TestUtils.waitForCondition(aVoid -> { + if (firstSemaphore.hasQueuedThreads()) { + // Once verified the second segment gets blocked, release the semaphore. + firstSegmentDataManager.close(); + return true; + } else { + return false; + } + }, timeout, "Failed to wait for the second segment blocked on semaphore"); + + // Wait for the second segment manager finished the construction. + TestUtils.waitForCondition(aVoid -> secondSegmentDataManager.get() != null, timeout, + "Failed to acquire the semaphore for the second segment manager in " + timeout + "ms"); + + Assert.assertTrue(secondSegmentDataManager.get().getAcquiredConsumerSemaphore().get()); + Semaphore secondSemaphore = secondSegmentDataManager.get().getPartitionGroupConsumerSemaphore(); + Assert.assertEquals(firstSemaphore, secondSemaphore); + Assert.assertEquals(secondSemaphore.availablePermits(), 0); + Assert.assertFalse(secondSemaphore.hasQueuedThreads()); + + // Call offload method the 2nd time on the first segment manager, the permits in semaphore won't increase. + firstSegmentDataManager.close(); + Assert.assertEquals(firstSegmentDataManager.getPartitionGroupConsumerSemaphore().availablePermits(), 0); + + // The permit finally gets released in the Semaphore. + secondSegmentDataManager.get().close(); + Assert.assertEquals(secondSegmentDataManager.get().getPartitionGroupConsumerSemaphore().availablePermits(), 1); + } + + @Test + public void testShutdownTableDataManagerWillNotShutdownLeaseExtenderExecutor() + throws Exception { + TableConfig tableConfig = createTableConfig(); + tableConfig.setUpsertConfig(null); + ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); + when(propertyStore.get(anyString(), any(), anyInt())).thenReturn(TableConfigUtils.toZNRecord(tableConfig)); + HelixManager helixManager = mock(HelixManager.class); + when(helixManager.getHelixPropertyStore()).thenReturn(propertyStore); + + InstanceDataManagerConfig instanceDataManagerConfig = mock(InstanceDataManagerConfig.class); + when(instanceDataManagerConfig.getInstanceDataDir()).thenReturn(TEMP_DIR.getAbsolutePath()); + TableDataManager tableDataManager = + new TableDataManagerProvider(instanceDataManagerConfig, helixManager, new SegmentLocks()).getTableDataManager( + tableConfig); + tableDataManager.start(); + tableDataManager.shutDown(); + Assert.assertFalse(SegmentBuildTimeLeaseExtender.isExecutorShutdown()); + } + + @Test + public void testShouldNotSkipUnfilteredMessagesIfNotIndexedAndTimeThresholdIsReached() + throws Exception { + final int segmentTimeThresholdMins = 10; + TimeSupplier timeSupplier = new TimeSupplier() { + @Override + public Long get() { + long now = System.currentTimeMillis(); + // now() is called once in the run() method, once before each batch reading and once for every row indexation + if (_timeCheckCounter.incrementAndGet() <= FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS + 4) { + return now; + } + // Exceed segment time threshold + return now + TimeUnit.MINUTES.toMillis(segmentTimeThresholdMins + 1); + } + }; + try (FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(true, timeSupplier, + String.valueOf(FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS * 2), segmentTimeThresholdMins + "m", null)) { + segmentDataManager._stubConsumeLoop = false; + segmentDataManager._state.set(segmentDataManager, RealtimeSegmentDataManager.State.INITIAL_CONSUMING); + + RealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); + final LongMsgOffset endOffset = + new LongMsgOffset(START_OFFSET_VALUE + FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS); + segmentDataManager._consumeOffsets.add(endOffset); + final SegmentCompletionProtocol.Response response = new SegmentCompletionProtocol.Response( + new SegmentCompletionProtocol.Response.Params().withStatus( + SegmentCompletionProtocol.ControllerResponseStatus.COMMIT) + .withStreamPartitionMsgOffset(endOffset.toString())); + segmentDataManager._responses.add(response); + + consumer.run(); + + // millis() is called first in run before consumption, then once for each batch and once for each message in + // the batch, then once more when metrics are updated after each batch is processed and then 4 more times in + // run() after consume loop + Assert.assertEquals(timeSupplier._timeCheckCounter.get(), FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS + 8); + Assert.assertEquals(((LongMsgOffset) segmentDataManager.getCurrentOffset()).getOffset(), + START_OFFSET_VALUE + FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS); + Assert.assertEquals(segmentDataManager.getSegment().getNumDocsIndexed(), + FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS); + Assert.assertEquals(segmentDataManager.getSegment().getSegmentMetadata().getTotalDocs(), + FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS); + } + } + + @Test + public void testShouldNotSkipUnfilteredMessagesIfNotIndexedAndRowCountThresholdIsReached() + throws Exception { + final int segmentTimeThresholdMins = 10; + TimeSupplier timeSupplier = new TimeSupplier(); + try (FakeRealtimeSegmentDataManager segmentDataManager = createFakeSegmentManager(true, timeSupplier, + String.valueOf(FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS), segmentTimeThresholdMins + "m", null)) { + segmentDataManager._stubConsumeLoop = false; + segmentDataManager._state.set(segmentDataManager, RealtimeSegmentDataManager.State.INITIAL_CONSUMING); + + RealtimeSegmentDataManager.PartitionConsumer consumer = segmentDataManager.createPartitionConsumer(); + final LongMsgOffset endOffset = + new LongMsgOffset(START_OFFSET_VALUE + FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS); + segmentDataManager._consumeOffsets.add(endOffset); + final SegmentCompletionProtocol.Response response = new SegmentCompletionProtocol.Response( + new SegmentCompletionProtocol.Response.Params().withStatus( + SegmentCompletionProtocol.ControllerResponseStatus.COMMIT) + .withStreamPartitionMsgOffset(endOffset.toString())); + segmentDataManager._responses.add(response); + + consumer.run(); + + // millis() is called first in run before consumption, then once for each batch and once for each message in + // the batch, then once for metrics updates and then 4 more times in run() after consume loop + Assert.assertEquals(timeSupplier._timeCheckCounter.get(), FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS + 6); + Assert.assertEquals(((LongMsgOffset) segmentDataManager.getCurrentOffset()).getOffset(), + START_OFFSET_VALUE + FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS); + Assert.assertEquals(segmentDataManager.getSegment().getNumDocsIndexed(), + FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS); + Assert.assertEquals(segmentDataManager.getSegment().getSegmentMetadata().getTotalDocs(), + FakeStreamConfigUtils.SEGMENT_FLUSH_THRESHOLD_ROWS); + } + } + + private static class TimeSupplier implements Supplier { + protected final AtomicInteger _timeCheckCounter = new AtomicInteger(); + protected long _timeNow = System.currentTimeMillis(); + + @Override + public Long get() { + _timeCheckCounter.incrementAndGet(); + return _timeNow; + } + + public void set(long millis) { + _timeNow = millis; + } + + public void add(long millis) { + _timeNow += millis; + } + } + + // Implementing Closeable to make it easier to use in try-with-resources + public static class FakeRealtimeSegmentDataManager extends RealtimeSegmentDataManager implements Closeable { + + public Field _state; + public Field _shouldStop; + public Field _stopReason; + private Field _streamMsgOffsetFactory; + public LinkedList _consumeOffsets = new LinkedList<>(); + public LinkedList _responses = new LinkedList<>(); + public boolean _commitSegmentCalled = false; + public boolean _buildSegmentCalled = false; + public boolean _failSegmentBuild = false; + public boolean _buildAndReplaceCalled = false; + public int _stopWaitTimeMs = 100; + private boolean _downloadAndReplaceCalled = false; + public boolean _throwExceptionFromConsume = false; + public boolean _postConsumeStoppedCalled = false; + public Map _semaphoreMap; + public boolean _stubConsumeLoop = true; + private TimeSupplier _timeSupplier; + + private static InstanceDataManagerConfig makeInstanceDataManagerConfig() { + InstanceDataManagerConfig dataManagerConfig = mock(InstanceDataManagerConfig.class); + when(dataManagerConfig.getReadMode()).thenReturn(null); + when(dataManagerConfig.getAvgMultiValueCount()).thenReturn(null); + when(dataManagerConfig.getSegmentFormatVersion()).thenReturn(null); + when(dataManagerConfig.isRealtimeOffHeapAllocation()).thenReturn(false); + when(dataManagerConfig.getConfig()).thenReturn(new PinotConfiguration()); + return dataManagerConfig; + } + + public FakeRealtimeSegmentDataManager(SegmentZKMetadata segmentZKMetadata, TableConfig tableConfig, + RealtimeTableDataManager realtimeTableDataManager, String resourceDataDir, Schema schema, + LLCSegmentName llcSegmentName, Map semaphoreMap, ServerMetrics serverMetrics, + TimeSupplier timeSupplier) + throws Exception { + super(segmentZKMetadata, tableConfig, realtimeTableDataManager, resourceDataDir, + new IndexLoadingConfig(makeInstanceDataManagerConfig(), tableConfig), schema, llcSegmentName, + semaphoreMap.get(llcSegmentName.getPartitionGroupId()), serverMetrics, null, null, () -> true); + _state = RealtimeSegmentDataManager.class.getDeclaredField("_state"); + _state.setAccessible(true); + _shouldStop = RealtimeSegmentDataManager.class.getDeclaredField("_shouldStop"); + _shouldStop.setAccessible(true); + _stopReason = RealtimeSegmentDataManager.class.getDeclaredField("_stopReason"); + _stopReason.setAccessible(true); + _semaphoreMap = semaphoreMap; + _streamMsgOffsetFactory = RealtimeSegmentDataManager.class.getDeclaredField("_streamPartitionMsgOffsetFactory"); + _streamMsgOffsetFactory.setAccessible(true); + _streamMsgOffsetFactory.set(this, new LongMsgOffsetFactory()); + _timeSupplier = timeSupplier; + } + + public String getStopReason() { + try { + return (String) _stopReason.get(this); + } catch (Exception e) { + Assert.fail(); + } + return null; + } + + public PartitionConsumer createPartitionConsumer() { + return new PartitionConsumer(); + } + + public SegmentBuildDescriptor invokeBuildForCommit(long leaseTime) { + super.buildSegmentForCommit(leaseTime); + return getSegmentBuildDescriptor(); + } + + public boolean invokeCommit() + throws Exception { + return super.commitSegment("dummyUrl"); + } + + private void terminateLoopIfNecessary() { + if (_consumeOffsets.isEmpty() && _responses.isEmpty()) { + try { + _shouldStop.set(this, true); + } catch (Exception e) { + Assert.fail(); + } + } + } + + @Override + public void startConsumption() { + // Do nothing. + } + + @Override + protected boolean consumeLoop() + throws Exception { + if (_stubConsumeLoop) { + if (_throwExceptionFromConsume) { + throw new PermanentConsumerException(new Throwable("Offset out of range")); + } + setCurrentOffset(_consumeOffsets.remove().getOffset()); + terminateLoopIfNecessary(); + return true; + } + return super.consumeLoop(); + } + + @Override + protected SegmentCompletionProtocol.Response postSegmentConsumedMsg() { + SegmentCompletionProtocol.Response response = _responses.remove(); + terminateLoopIfNecessary(); + return response; + } + + @Override + protected SegmentCompletionProtocol.Response commit(String controllerVipUrl) { + return _responses.remove(); + } + + @Override + protected void postStopConsumedMsg(String reason) { + _postConsumeStoppedCalled = true; + } + + // TODO: Some of the tests rely on specific number of calls to the `now()` method in the SegmentDataManager. + // This is not a good coding practice and makes the code very fragile. This needs to be fixed. + // Invoking now() in any part of RealtimeSegmentDataManager code will break the following tests: + // 1. RealtimeSegmentDataManagerTest.testShouldNotSkipUnfilteredMessagesIfNotIndexedAndRowCountThresholdIsReached + // 2. RealtimeSegmentDataManagerTest.testShouldNotSkipUnfilteredMessagesIfNotIndexedAndTimeThresholdIsReached + @Override + protected long now() { + // now() is called in the constructor before _timeSupplier is set + if (_timeSupplier == null) { + return System.currentTimeMillis(); + } + return _timeSupplier.get(); + } + + @Override + protected void hold() { + _timeSupplier.add(5000L); + } + + @Override + protected boolean buildSegmentAndReplace() { + _buildAndReplaceCalled = true; + return true; + } + + @Override + protected SegmentBuildDescriptor buildSegmentInternal(boolean forCommit) { + _buildSegmentCalled = true; + if (_failSegmentBuild) { + return null; + } + if (!forCommit) { + return new SegmentBuildDescriptor(null, null, getCurrentOffset(), 0, 0, -1); + } + File segmentTarFile = new File(new File(TEMP_DIR, REALTIME_TABLE_NAME), "segmentFile"); + try { + segmentTarFile.createNewFile(); + } catch (IOException e) { + Assert.fail("Could not create file " + segmentTarFile); + } + return new SegmentBuildDescriptor(segmentTarFile, null, getCurrentOffset(), 0, 0, -1); + } + + @Override + protected boolean commitSegment(String controllerVipUrl) { + _commitSegmentCalled = true; + return true; + } + + @Override + protected void downloadSegmentAndReplace(SegmentZKMetadata metadata) { + _downloadAndReplaceCalled = true; + } + + @Override + public void stop() { + _timeSupplier.add(_stopWaitTimeMs); + } + + public void setCurrentOffset(long offset) { + setOffset(offset, "_currentOffset"); + } + + public void setConsumeEndTime(long endTime) { + setLong(endTime, "_consumeEndTime"); + } + + public void setNumRowsConsumed(int numRows) { + setInt(numRows, "_numRowsConsumed"); + } + + public void setNumRowsIndexed(int numRows) { + setInt(numRows, "_numRowsIndexed"); + } + + public void setFinalOffset(long offset) { + setOffset(offset, "_finalOffset"); + } + + public boolean invokeEndCriteriaReached() { + Method endCriteriaReached = null; + try { + endCriteriaReached = RealtimeSegmentDataManager.class.getDeclaredMethod("endCriteriaReached"); + endCriteriaReached.setAccessible(true); + Boolean result = (Boolean) endCriteriaReached.invoke(this); + return result; + } catch (NoSuchMethodException e) { + Assert.fail(); + } catch (InvocationTargetException e) { + Assert.fail(); + } catch (IllegalAccessException e) { + Assert.fail(); + } + throw new RuntimeException("Cannot get here"); + } + + public void setSegmentMaxRowCount(int numRows) { + setInt(numRows, "_segmentMaxRowCount"); + } + + private void setLong(long value, String fieldName) { + try { + Field field = RealtimeSegmentDataManager.class.getDeclaredField(fieldName); + field.setAccessible(true); + field.setLong(this, value); + } catch (NoSuchFieldException e) { + Assert.fail(); + } catch (IllegalAccessException e) { + Assert.fail(); + } + } + + private void setOffset(long value, String fieldName) { + try { + Field field = RealtimeSegmentDataManager.class.getDeclaredField(fieldName); + field.setAccessible(true); + StreamPartitionMsgOffset offset = (StreamPartitionMsgOffset) field.get(this); +// if (offset == null) { + field.set(this, new LongMsgOffset(value)); +// } else { +// offset.setOffset(value); +// } + } catch (NoSuchFieldException e) { + Assert.fail(); + } catch (IllegalAccessException e) { + Assert.fail(); + } + } + + private void setInt(int value, String fieldName) { + try { + Field field = RealtimeSegmentDataManager.class.getDeclaredField(fieldName); + field.setAccessible(true); + field.setInt(this, value); + } catch (NoSuchFieldException e) { + Assert.fail(); + } catch (IllegalAccessException e) { + Assert.fail(); + } + } + + @Override + public void close() { + offload(); + destroy(); + } + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/RealtimeTableDataManagerTest.java b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/RealtimeTableDataManagerTest.java index dbb4d0a86d26..ea266df007a6 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/RealtimeTableDataManagerTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/RealtimeTableDataManagerTest.java @@ -18,207 +18,46 @@ */ package org.apache.pinot.core.data.manager.realtime; -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import org.apache.commons.io.FileUtils; -import org.apache.helix.AccessOption; -import org.apache.helix.HelixManager; -import org.apache.helix.store.zk.ZkHelixPropertyStore; -import org.apache.helix.zookeeper.datamodel.ZNRecord; -import org.apache.pinot.common.metadata.ZKMetadataProvider; import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; -import org.apache.pinot.common.metrics.ServerMetrics; -import org.apache.pinot.common.utils.HLCSegmentName; -import org.apache.pinot.common.utils.LLCSegmentName; -import org.apache.pinot.common.utils.SchemaUtils; -import org.apache.pinot.common.utils.TarGzCompressionUtils; -import org.apache.pinot.common.utils.config.TableConfigUtils; -import org.apache.pinot.core.data.manager.TableDataManagerTestUtils; -import org.apache.pinot.segment.local.data.manager.TableDataManagerConfig; -import org.apache.pinot.segment.local.data.manager.TableDataManagerParams; -import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; -import org.apache.pinot.segment.local.segment.index.loader.IndexLoadingConfig; -import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; -import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; -import org.apache.pinot.segment.spi.creator.SegmentVersion; -import org.apache.pinot.segment.spi.index.metadata.SegmentMetadataImpl; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.data.DateTimeFieldSpec; import org.apache.pinot.spi.data.FieldSpec; import org.apache.pinot.spi.data.Schema; -import org.apache.pinot.spi.data.readers.GenericRow; -import org.apache.pinot.spi.metrics.PinotMetricUtils; -import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; -import org.apache.pinot.util.TestUtils; -import org.testng.annotations.AfterMethod; -import org.testng.annotations.BeforeMethod; +import org.joda.time.DateTimeZone; +import org.joda.time.format.DateTimeFormat; import org.testng.annotations.Test; -import static org.apache.pinot.spi.utils.CommonConstants.Segment.Realtime.Status; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; +import static org.testng.Assert.assertNotNull; public class RealtimeTableDataManagerTest { - private static final File TEMP_DIR = new File(FileUtils.getTempDirectory(), "RealtimeTableDataManagerTest"); - private static final String TABLE_NAME = "table01"; - private static final String TABLE_NAME_WITH_TYPE = "table01_REALTIME"; - private static final File TABLE_DATA_DIR = new File(TEMP_DIR, TABLE_NAME_WITH_TYPE); - private static final String STRING_COLUMN = "col1"; - private static final String[] STRING_VALUES = {"A", "D", "E", "B", "C"}; - private static final String LONG_COLUMN = "col2"; - private static final long[] LONG_VALUES = {10000L, 20000L, 50000L, 40000L, 30000L}; - - @BeforeMethod - public void setUp() - throws Exception { - TestUtils.ensureDirectoriesExistAndEmpty(TEMP_DIR); - TableDataManagerTestUtils.initSegmentFetcher(); - } - - @AfterMethod - public void tearDown() - throws Exception { - FileUtils.deleteDirectory(TEMP_DIR); - } @Test - public void testAddSegmentUseBackupCopy() - throws Exception { - RealtimeTableDataManager tmgr = new RealtimeTableDataManager(null); - TableDataManagerConfig tableDataManagerConfig = createTableDataManagerConfig(); - ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); - TableConfig tableConfig = setupTableConfig(propertyStore); - Schema schema = setupSchema(propertyStore); - tmgr.init(tableDataManagerConfig, "server01", propertyStore, - new ServerMetrics(PinotMetricUtils.getPinotMetricsRegistry()), mock(HelixManager.class), null, - new TableDataManagerParams(0, false, -1)); - - // Create a dummy local segment. - String segName = "seg01"; - SegmentZKMetadata segmentZKMetadata = new SegmentZKMetadata(segName); - segmentZKMetadata.setStatus(Status.DONE); - File localSegDir = createSegment(tableConfig, schema, segName); - long segCrc = TableDataManagerTestUtils.getCRC(localSegDir, SegmentVersion.v3); - segmentZKMetadata.setCrc(segCrc); - - // Move the segment to the backup location. - File backup = new File(TABLE_DATA_DIR, segName + CommonConstants.Segment.SEGMENT_BACKUP_DIR_SUFFIX); - localSegDir.renameTo(backup); - assertEquals(localSegDir, new File(TABLE_DATA_DIR, segName)); - assertFalse(localSegDir.exists()); - IndexLoadingConfig indexLoadingConfig = - TableDataManagerTestUtils.createIndexLoadingConfig("default", tableConfig, schema); - tmgr.addSegment(segName, indexLoadingConfig, segmentZKMetadata); - // Segment data is put back the default location, and backup location is deleted. - assertTrue(localSegDir.exists()); - assertFalse(backup.exists()); - SegmentMetadataImpl llmd = new SegmentMetadataImpl(new File(TABLE_DATA_DIR, segName)); - assertEquals(llmd.getTotalDocs(), 5); - } + public void testSetDefaultTimeValueIfInvalid() { + SegmentZKMetadata segmentZKMetadata = mock(SegmentZKMetadata.class); + long currentTimeMs = System.currentTimeMillis(); + when(segmentZKMetadata.getCreationTime()).thenReturn(currentTimeMs); - @Test - public void testAddSegmentNoBackupCopy() - throws Exception { - RealtimeTableDataManager tmgr = new RealtimeTableDataManager(null); - TableDataManagerConfig tableDataManagerConfig = createTableDataManagerConfig(); - ZkHelixPropertyStore propertyStore = mock(ZkHelixPropertyStore.class); - TableConfig tableConfig = setupTableConfig(propertyStore); - Schema schema = setupSchema(propertyStore); - tmgr.init(tableDataManagerConfig, "server01", propertyStore, - new ServerMetrics(PinotMetricUtils.getPinotMetricsRegistry()), mock(HelixManager.class), null, - new TableDataManagerParams(0, false, -1)); - - // Create a raw segment and put it in deep store backed by local fs. - String segName = "seg01"; - SegmentZKMetadata segmentZKMetadata = - TableDataManagerTestUtils.makeRawSegment(segName, createSegment(tableConfig, schema, segName), - new File(TEMP_DIR, segName + TarGzCompressionUtils.TAR_GZ_FILE_EXTENSION), true); - segmentZKMetadata.setStatus(Status.DONE); - - // Local segment dir doesn't exist, thus downloading from deep store. - File localSegDir = new File(TABLE_DATA_DIR, segName); - assertFalse(localSegDir.exists()); - IndexLoadingConfig indexLoadingConfig = - TableDataManagerTestUtils.createIndexLoadingConfig("default", tableConfig, schema); - tmgr.addSegment(segName, indexLoadingConfig, segmentZKMetadata); - // Segment data is put on default location. - assertTrue(localSegDir.exists()); - SegmentMetadataImpl llmd = new SegmentMetadataImpl(new File(TABLE_DATA_DIR, segName)); - assertEquals(llmd.getTotalDocs(), 5); - } - - @Test - public void testAllowDownload() { - RealtimeTableDataManager mgr = new RealtimeTableDataManager(null); - - String groupId = "myTable_REALTIME_1234567_0"; - String partitionRange = "ALL"; - String sequenceNumber = "1234567"; - HLCSegmentName hlc = new HLCSegmentName(groupId, partitionRange, sequenceNumber); - assertFalse(mgr.allowDownload(hlc.getSegmentName(), null)); - - LLCSegmentName llc = new LLCSegmentName("tbl01", 0, 1000000, System.currentTimeMillis()); - SegmentZKMetadata zkmd = mock(SegmentZKMetadata.class); - when(zkmd.getStatus()).thenReturn(Status.IN_PROGRESS); - assertFalse(mgr.allowDownload(llc.getSegmentName(), zkmd)); - - when(zkmd.getStatus()).thenReturn(Status.DONE); - when(zkmd.getDownloadUrl()).thenReturn(""); - assertFalse(mgr.allowDownload(llc.getSegmentName(), zkmd)); - - when(zkmd.getDownloadUrl()).thenReturn("remote"); - assertTrue(mgr.allowDownload(llc.getSegmentName(), zkmd)); - } - - private static File createSegment(TableConfig tableConfig, Schema schema, String segName) - throws Exception { - SegmentGeneratorConfig config = new SegmentGeneratorConfig(tableConfig, schema); - config.setOutDir(TABLE_DATA_DIR.getAbsolutePath()); - config.setSegmentName(segName); - config.setSegmentVersion(SegmentVersion.v3); - List rows = new ArrayList<>(3); - for (int i = 0; i < STRING_VALUES.length; i++) { - GenericRow row = new GenericRow(); - row.putValue(STRING_COLUMN, STRING_VALUES[i]); - row.putValue(LONG_COLUMN, LONG_VALUES[i]); - rows.add(row); - } - SegmentIndexCreationDriverImpl driver = new SegmentIndexCreationDriverImpl(); - driver.init(config, new GenericRowRecordReader(rows)); - driver.build(); - return new File(TABLE_DATA_DIR, segName); - } - - private static TableDataManagerConfig createTableDataManagerConfig() { - TableDataManagerConfig tableDataManagerConfig = mock(TableDataManagerConfig.class); - when(tableDataManagerConfig.getTableName()).thenReturn(TABLE_NAME_WITH_TYPE); - when(tableDataManagerConfig.getDataDir()).thenReturn(TABLE_DATA_DIR.getAbsolutePath()); - return tableDataManagerConfig; - } - - private static TableConfig setupTableConfig(ZkHelixPropertyStore propertyStore) - throws Exception { TableConfig tableConfig = - new TableConfigBuilder(TableType.REALTIME).setTableName(TABLE_NAME).setSchemaName(TABLE_NAME).build(); - ZNRecord tableConfigZNRecord = TableConfigUtils.toZNRecord(tableConfig); - when(propertyStore.get(ZKMetadataProvider.constructPropertyStorePathForResourceConfig(TABLE_NAME_WITH_TYPE), null, - AccessOption.PERSISTENT)).thenReturn(tableConfigZNRecord); - return tableConfig; - } - - private static Schema setupSchema(ZkHelixPropertyStore propertyStore) { - Schema schema = new Schema.SchemaBuilder().setSchemaName(TABLE_NAME) - .addSingleValueDimension(STRING_COLUMN, FieldSpec.DataType.STRING) - .addMetric(LONG_COLUMN, FieldSpec.DataType.LONG).build(); - ZNRecord schemaZNRecord = SchemaUtils.toZNRecord(schema); - when(propertyStore.get(ZKMetadataProvider.constructPropertyStorePathForSchema(TABLE_NAME), null, - AccessOption.PERSISTENT)).thenReturn(schemaZNRecord); - return schema; + new TableConfigBuilder(TableType.OFFLINE).setTableName("testTable").setTimeColumnName("timeColumn").build(); + Schema schema = new Schema.SchemaBuilder().setSchemaName("testTable") + .addDateTime("timeColumn", FieldSpec.DataType.TIMESTAMP, "TIMESTAMP", "1:MILLISECONDS").build(); + RealtimeTableDataManager.setDefaultTimeValueIfInvalid(tableConfig, schema, segmentZKMetadata); + DateTimeFieldSpec timeFieldSpec = schema.getSpecForTimeColumn("timeColumn"); + assertNotNull(timeFieldSpec); + assertEquals(timeFieldSpec.getDefaultNullValue(), currentTimeMs); + + schema = new Schema.SchemaBuilder().setSchemaName("testTable") + .addDateTime("timeColumn", FieldSpec.DataType.INT, "SIMPLE_DATE_FORMAT|yyyyMMdd", "1:DAYS").build(); + RealtimeTableDataManager.setDefaultTimeValueIfInvalid(tableConfig, schema, segmentZKMetadata); + timeFieldSpec = schema.getSpecForTimeColumn("timeColumn"); + assertNotNull(timeFieldSpec); + assertEquals(timeFieldSpec.getDefaultNullValue(), + Integer.parseInt(DateTimeFormat.forPattern("yyyyMMdd").withZone(DateTimeZone.UTC).print(currentTimeMs))); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/SegmentCommitterFactoryTest.java b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/SegmentCommitterFactoryTest.java new file mode 100644 index 000000000000..65a2b16b9611 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/SegmentCommitterFactoryTest.java @@ -0,0 +1,110 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.data.manager.realtime; + +import com.google.common.collect.ImmutableMap; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; +import org.apache.pinot.common.metrics.ServerMetrics; +import org.apache.pinot.common.protocols.SegmentCompletionProtocol; +import org.apache.pinot.segment.local.segment.index.loader.IndexLoadingConfig; +import org.apache.pinot.server.realtime.ServerSegmentCompletionProtocolHandler; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.stream.StreamConfigProperties; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.mockito.Mockito; +import org.slf4j.Logger; +import org.testng.Assert; +import org.testng.annotations.Test; + + +public class SegmentCommitterFactoryTest { + + private Map getMinimumStreamConfigMap() { + return ImmutableMap.of( + "streamType", "kafka", + "stream.kafka.consumer.type", "simple", + "stream.kafka.topic.name", "ignore", + "stream.kafka.decoder.class.name", "org.apache.pinot.plugin.inputformat.json.JsonMessageDecoder"); + } + + private TableConfigBuilder createRealtimeTableConfig(String tableName) { + return createRealtimeTableConfig(tableName, getMinimumStreamConfigMap()); + } + + private TableConfigBuilder createRealtimeTableConfig(String tableName, Map realtimeStreamConfig) { + return new TableConfigBuilder(TableType.REALTIME).setTableName(tableName).setStreamConfigs(realtimeStreamConfig); + } + + @Test(description = "when controller supports split commit, server should always use split segment commit") + public void testSplitSegmentCommitterIsDefault() + throws URISyntaxException { + TableConfig config = createRealtimeTableConfig("test").build(); + ServerSegmentCompletionProtocolHandler protocolHandler = + new ServerSegmentCompletionProtocolHandler(Mockito.mock(ServerMetrics.class), "test_REALTIME"); + String controllerVipUrl = "http://localhost:1234"; + SegmentCompletionProtocol.Request.Params requestParams = new SegmentCompletionProtocol.Request.Params(); + SegmentCommitterFactory factory = new SegmentCommitterFactory(Mockito.mock(Logger.class), protocolHandler, config, + Mockito.mock(IndexLoadingConfig.class), Mockito.mock(ServerMetrics.class)); + SegmentCommitter committer = factory.createSegmentCommitter(requestParams, controllerVipUrl); + Assert.assertNotNull(committer); + Assert.assertTrue(committer instanceof SplitSegmentCommitter); + } + + @Test(description = "use upload to deepstore when either serverUploadToDeepStore is set or peer segment download " + + "scheme is non-null") + public void testUploadToDeepStoreConfig() + throws URISyntaxException { + ServerSegmentCompletionProtocolHandler protocolHandler = + new ServerSegmentCompletionProtocolHandler(Mockito.mock(ServerMetrics.class), "test_REALTIME"); + String controllerVipUrl = "http://localhost:1234"; + SegmentCompletionProtocol.Request.Params requestParams = new SegmentCompletionProtocol.Request.Params(); + + // No peer segment download scheme, serverUploadToDeepStore = true + Map streamConfigMap = new HashMap<>(getMinimumStreamConfigMap()); + streamConfigMap.put(StreamConfigProperties.SERVER_UPLOAD_TO_DEEPSTORE, "true"); + TableConfig config = createRealtimeTableConfig("testDeepStoreConfig", streamConfigMap).build(); + IndexLoadingConfig indexLoadingConfig = Mockito.mock(IndexLoadingConfig.class); + Mockito.when(indexLoadingConfig.getSegmentStoreURI()).thenReturn("file:///path/to/segment/store.txt"); + + SegmentCommitterFactory factory = new SegmentCommitterFactory(Mockito.mock(Logger.class), protocolHandler, config, + indexLoadingConfig, Mockito.mock(ServerMetrics.class)); + SegmentCommitter committer = factory.createSegmentCommitter(requestParams, controllerVipUrl); + Assert.assertNotNull(committer); + Assert.assertTrue(committer instanceof SplitSegmentCommitter); + Assert.assertTrue(((SplitSegmentCommitter) committer).getSegmentUploader() instanceof PinotFSSegmentUploader); + + // Peer segment download scheme is set, serverUploadToDeepStore = false (for backwards compatibility) + Map streamConfigMap1 = new HashMap<>(getMinimumStreamConfigMap()); + streamConfigMap1.put(StreamConfigProperties.SERVER_UPLOAD_TO_DEEPSTORE, "false"); + TableConfig config1 = createRealtimeTableConfig("testDeepStoreConfig", streamConfigMap1) + .setPeerSegmentDownloadScheme("http") + .build(); + + factory = new SegmentCommitterFactory(Mockito.mock(Logger.class), protocolHandler, config1, + indexLoadingConfig, Mockito.mock(ServerMetrics.class)); + committer = factory.createSegmentCommitter(requestParams, controllerVipUrl); + Assert.assertNotNull(committer); + Assert.assertTrue(committer instanceof SplitSegmentCommitter); + Assert.assertTrue(((SplitSegmentCommitter) committer).getSegmentUploader() instanceof PinotFSSegmentUploader); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/SegmentCompletionUtilsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/SegmentCompletionUtilsTest.java new file mode 100644 index 000000000000..270f763dc31a --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/SegmentCompletionUtilsTest.java @@ -0,0 +1,49 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.data.manager.realtime; + +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +public class SegmentCompletionUtilsTest { + + @Test + public void testGenerateSegmentFilePrefix() { + String segmentName = "segment"; + assertEquals(SegmentCompletionUtils.getTmpSegmentNamePrefix(segmentName), "segment.tmp."); + } + + @Test + public void testGenerateSegmentLocation() { + String segmentName = "segment"; + String segmentNamePrefix = SegmentCompletionUtils.getTmpSegmentNamePrefix(segmentName); + assertTrue(SegmentCompletionUtils.generateTmpSegmentFileName(segmentName).startsWith(segmentNamePrefix)); + } + + @Test + public void testIsTmpFile() { + assertTrue(SegmentCompletionUtils.isTmpFile("hdfs://foo.tmp.550e8400-e29b-41d4-a716-446655440000")); + assertFalse(SegmentCompletionUtils.isTmpFile("hdfs://foo.tmp.")); + assertFalse(SegmentCompletionUtils.isTmpFile(".tmp.550e8400-e29b-41d4-a716-446655440000")); + assertFalse(SegmentCompletionUtils.isTmpFile("hdfs://foo.tmp.55")); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/Server2ControllerSegmentUploaderTest.java b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/Server2ControllerSegmentUploaderTest.java index 86a7088d941d..3dbcfa506c39 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/Server2ControllerSegmentUploaderTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/data/manager/realtime/Server2ControllerSegmentUploaderTest.java @@ -91,7 +91,7 @@ public void testUploadSuccess() throws URISyntaxException { Server2ControllerSegmentUploader uploader = new Server2ControllerSegmentUploader(_logger, _fileUploadDownloadClient, GOOD_CONTROLLER_VIP, "segmentName", - 10000, mock(ServerMetrics.class), null); + 10000, mock(ServerMetrics.class), null, _llcSegmentName.getTableName()); URI segmentURI = uploader.uploadSegment(_file, _llcSegmentName); Assert.assertEquals(segmentURI.toString(), SEGMENT_LOCATION); } @@ -101,7 +101,7 @@ public void testUploadFailure() throws URISyntaxException { Server2ControllerSegmentUploader uploader = new Server2ControllerSegmentUploader(_logger, _fileUploadDownloadClient, BAD_CONTROLLER_VIP, "segmentName", - 10000, mock(ServerMetrics.class), null); + 10000, mock(ServerMetrics.class), null, _llcSegmentName.getTableName()); URI segmentURI = uploader.uploadSegment(_file, _llcSegmentName); Assert.assertNull(segmentURI); } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/function/scalar/SketchFunctionsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/function/scalar/SketchFunctionsTest.java new file mode 100644 index 000000000000..11662741db9a --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/function/scalar/SketchFunctionsTest.java @@ -0,0 +1,149 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.function.scalar; + +import com.dynatrace.hash4j.distinctcount.UltraLogLog; +import java.math.BigDecimal; +import org.apache.datasketches.cpc.CpcSketch; +import org.apache.datasketches.memory.Memory; +import org.apache.datasketches.theta.Sketch; +import org.apache.datasketches.theta.Sketches; +import org.apache.pinot.core.common.ObjectSerDeUtils; +import org.apache.pinot.segment.local.utils.UltraLogLogUtils; +import org.testng.Assert; +import org.testng.annotations.Test; + + +public class SketchFunctionsTest { + + private double thetaEstimate(byte[] bytes) { + return ObjectSerDeUtils.DATA_SKETCH_THETA_SER_DE.deserialize(bytes).getEstimate(); + } + + byte[] _bytes = {1, 2, 3}; + + Object[] _inputs = { + "string", 1, 1L, 1.0f, 1.0d, BigDecimal.valueOf(1), _bytes + }; + + @Test + public void testThetaSketchCreation() { + for (Object i : _inputs) { + Assert.assertEquals(thetaEstimate(SketchFunctions.toThetaSketch(i)), 1.0); + Assert.assertEquals(thetaEstimate(SketchFunctions.toThetaSketch(i, 1024)), 1.0); + } + Assert.assertEquals(thetaEstimate(SketchFunctions.toThetaSketch(null)), 0.0); + Assert.assertEquals(thetaEstimate(SketchFunctions.toThetaSketch(null, 1024)), 0.0); + Assert.assertThrows(IllegalArgumentException.class, () -> SketchFunctions.toThetaSketch(new Object())); + Assert.assertThrows(IllegalArgumentException.class, () -> SketchFunctions.toThetaSketch(new Object(), 1024)); + } + + @Test + public void thetaThetaSketchSummary() { + for (Object i : _inputs) { + Sketch sketch = Sketches.wrapSketch(Memory.wrap(SketchFunctions.toThetaSketch(i))); + Assert.assertEquals(SketchFunctions.thetaSketchToString(sketch), sketch.toString()); + } + Assert.assertThrows(RuntimeException.class, () -> SketchFunctions.thetaSketchToString(new Object())); + } + + private long hllEstimate(byte[] bytes) { + return ObjectSerDeUtils.HYPER_LOG_LOG_SER_DE.deserialize(bytes).cardinality(); + } + + @Test + public void hllCreation() { + for (Object i : _inputs) { + Assert.assertEquals(hllEstimate(SketchFunctions.toHLL(i)), 1); + Assert.assertEquals(hllEstimate(SketchFunctions.toHLL(i, 8)), 1); + } + Assert.assertEquals(hllEstimate(SketchFunctions.toHLL(null)), 0); + Assert.assertEquals(hllEstimate(SketchFunctions.toHLL(null, 8)), 0); + } + + private double intTupleEstimate(byte[] bytes) { + return ObjectSerDeUtils.DATA_SKETCH_INT_TUPLE_SER_DE.deserialize(bytes).getEstimate(); + } + + @Test + public void intTupleSumCreation() { + for (Object i : _inputs) { + Assert.assertEquals(intTupleEstimate(SketchFunctions.toIntegerSumTupleSketch(i, 1)), 1.0d); + Assert.assertEquals(intTupleEstimate(SketchFunctions.toIntegerSumTupleSketch(i, 1, 16)), 1.0d); + } + Assert.assertEquals(intTupleEstimate(SketchFunctions.toIntegerSumTupleSketch(null, 1)), 0.0d); + Assert.assertEquals(intTupleEstimate(SketchFunctions.toIntegerSumTupleSketch(null, 1, 16)), 0.0d); + Assert.assertThrows(IllegalArgumentException.class, () -> SketchFunctions.toIntegerSumTupleSketch(new Object(), 1)); + Assert.assertThrows(IllegalArgumentException.class, + () -> SketchFunctions.toIntegerSumTupleSketch(new Object(), 1, 1024)); + } + + private double cpcEstimate(byte[] bytes) { + return ObjectSerDeUtils.DATA_SKETCH_CPC_SER_DE.deserialize(bytes).getEstimate(); + } + + @Test + public void testCpcCreation() { + for (Object i : _inputs) { + Assert.assertEquals(cpcEstimate(SketchFunctions.toCpcSketch(i)), 1.0); + Assert.assertEquals(cpcEstimate(SketchFunctions.toCpcSketch(i, 11)), 1.0); + } + Assert.assertEquals(cpcEstimate(SketchFunctions.toCpcSketch(null)), 0.0); + Assert.assertEquals(cpcEstimate(SketchFunctions.toCpcSketch(null, 11)), 0.0); + Assert.assertThrows(IllegalArgumentException.class, () -> SketchFunctions.toCpcSketch(new Object())); + Assert.assertThrows(IllegalArgumentException.class, () -> SketchFunctions.toCpcSketch(new Object(), 11)); + } + + @Test + public void thetaCpcSketchToString() { + for (Object i : _inputs) { + CpcSketch sketch = CpcSketch.heapify(Memory.wrap(SketchFunctions.toCpcSketch(i))); + Assert.assertEquals(SketchFunctions.cpcSketchToString(sketch), sketch.toString()); + } + Assert.assertThrows(RuntimeException.class, () -> SketchFunctions.cpcSketchToString(new Object())); + } + + private long ullEstimate(byte[] bytes) { + // round it to a long to make it easier to assert on + return Math.round(ObjectSerDeUtils.ULTRA_LOG_LOG_OBJECT_SER_DE.deserialize(bytes).getDistinctCountEstimate()); + } + + @Test + public void testULLCreation() { + for (Object i : _inputs) { + Assert.assertEquals(ullEstimate(SketchFunctions.toULL(i)), 1); + Assert.assertEquals(ullEstimate(SketchFunctions.toULL(i, 11)), 1); + } + Assert.assertEquals(ullEstimate(SketchFunctions.toULL(null)), 0); + Assert.assertEquals(ullEstimate(SketchFunctions.toULL(null, 11)), 0); + Assert.assertThrows(IllegalArgumentException.class, () -> SketchFunctions.toULL(new Object())); + Assert.assertThrows(IllegalArgumentException.class, () -> SketchFunctions.toULL(new Object(), 11)); + } + + @Test + public void testULLLoading() { + for (Object i : _inputs) { + UltraLogLog ull = UltraLogLog.create(12); + UltraLogLogUtils.hashObject(i).ifPresent(ull::add); + byte[] loaded = SketchFunctions.fromULL(ull.getState()); + UltraLogLog deserialized = ObjectSerDeUtils.ULTRA_LOG_LOG_OBJECT_SER_DE.deserialize(loaded); + Assert.assertEquals(deserialized.getState(), ull.getState()); + } + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/minion/SegmentPurgerTest.java b/pinot-core/src/test/java/org/apache/pinot/core/minion/SegmentPurgerTest.java index 1866623fb65b..2dd6b37a3e42 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/minion/SegmentPurgerTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/minion/SegmentPurgerTest.java @@ -31,10 +31,10 @@ import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; import org.apache.pinot.segment.local.segment.readers.PinotSegmentRecordReader; import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; +import org.apache.pinot.segment.spi.index.StandardIndexes; import org.apache.pinot.segment.spi.index.metadata.SegmentMetadataImpl; import org.apache.pinot.segment.spi.loader.SegmentDirectoryLoaderContext; import org.apache.pinot.segment.spi.loader.SegmentDirectoryLoaderRegistry; -import org.apache.pinot.segment.spi.store.ColumnIndexType; import org.apache.pinot.segment.spi.store.SegmentDirectory; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; @@ -67,6 +67,7 @@ public class SegmentPurgerTest { private static final String D2 = "d2"; private TableConfig _tableConfig; + private Schema _schema; private File _originalIndexDir; private int _expectedNumRecordsPurged; private int _expectedNumRecordsModified; @@ -79,7 +80,7 @@ public void setUp() _tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME) .setInvertedIndexColumns(Collections.singletonList(D1)).setCreateInvertedIndexDuringSegmentGeneration(true) .build(); - Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(D1, FieldSpec.DataType.INT) + _schema = new Schema.SchemaBuilder().addSingleValueDimension(D1, FieldSpec.DataType.INT) .addSingleValueDimension(D2, FieldSpec.DataType.INT).build(); List rows = new ArrayList<>(NUM_ROWS); @@ -98,7 +99,7 @@ public void setUp() } GenericRowRecordReader genericRowRecordReader = new GenericRowRecordReader(rows); - SegmentGeneratorConfig config = new SegmentGeneratorConfig(_tableConfig, schema); + SegmentGeneratorConfig config = new SegmentGeneratorConfig(_tableConfig, _schema); config.setOutDir(ORIGINAL_SEGMENT_DIR.getPath()); config.setSegmentName(SEGMENT_NAME); @@ -125,7 +126,7 @@ public void testPurgeSegment() }; SegmentPurger segmentPurger = - new SegmentPurger(_originalIndexDir, PURGED_SEGMENT_DIR, _tableConfig, recordPurger, recordModifier); + new SegmentPurger(_originalIndexDir, PURGED_SEGMENT_DIR, _tableConfig, _schema, recordPurger, recordModifier); File purgedIndexDir = segmentPurger.purgeSegment(); // Check the purge/modify counter in segment purger @@ -167,8 +168,8 @@ public void testPurgeSegment() .load(purgedIndexDir.toURI(), new SegmentDirectoryLoaderContext.Builder().setTableConfig(_tableConfig) .setSegmentName(purgedSegmentMetadata.getName()).setSegmentDirectoryConfigs(new PinotConfiguration(props)) .build()); SegmentDirectory.Reader reader = segmentDirectory.createReader()) { - assertTrue(reader.hasIndexFor(D1, ColumnIndexType.INVERTED_INDEX)); - assertFalse(reader.hasIndexFor(D2, ColumnIndexType.INVERTED_INDEX)); + assertTrue(reader.hasIndexFor(D1, StandardIndexes.inverted())); + assertFalse(reader.hasIndexFor(D2, StandardIndexes.inverted())); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/blocks/results/ResultsBlockUtilsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/blocks/results/ResultsBlockUtilsTest.java index eaf9d3fbe318..d51b0247be65 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/blocks/results/ResultsBlockUtilsTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/blocks/results/ResultsBlockUtilsTest.java @@ -21,6 +21,7 @@ import java.io.IOException; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.request.context.utils.QueryContextConverterUtils; import org.testng.annotations.Test; @@ -35,20 +36,20 @@ public void testBuildEmptyQueryResults() throws IOException { // Selection QueryContext queryContext = QueryContextConverterUtils.getQueryContext("SELECT * FROM testTable WHERE foo = 'bar'"); - DataTable dataTable = ResultsBlockUtils.buildEmptyQueryResults(queryContext).getDataTable(queryContext); + DataTable dataTable = ResultsBlockUtils.buildEmptyQueryResults(queryContext).getDataTable(); DataSchema dataSchema = dataTable.getDataSchema(); assertEquals(dataSchema.getColumnNames(), new String[]{"*"}); - assertEquals(dataSchema.getColumnDataTypes(), new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.STRING}); + assertEquals(dataSchema.getColumnDataTypes(), new ColumnDataType[]{ColumnDataType.STRING}); assertEquals(dataTable.getNumberOfRows(), 0); // Aggregation queryContext = QueryContextConverterUtils.getQueryContext("SELECT COUNT(*), SUM(a), MAX(b) FROM testTable WHERE foo = 'bar'"); - dataTable = ResultsBlockUtils.buildEmptyQueryResults(queryContext).getDataTable(queryContext); + dataTable = ResultsBlockUtils.buildEmptyQueryResults(queryContext).getDataTable(); dataSchema = dataTable.getDataSchema(); - assertEquals(dataSchema.getColumnNames(), new String[]{"count_star", "sum_a", "max_b"}); - assertEquals(dataSchema.getColumnDataTypes(), new DataSchema.ColumnDataType[]{ - DataSchema.ColumnDataType.LONG, DataSchema.ColumnDataType.DOUBLE, DataSchema.ColumnDataType.DOUBLE + assertEquals(dataSchema.getColumnNames(), new String[]{"count(*)", "sum(a)", "max(b)"}); + assertEquals(dataSchema.getColumnDataTypes(), new ColumnDataType[]{ + ColumnDataType.LONG, ColumnDataType.DOUBLE, ColumnDataType.DOUBLE }); assertEquals(dataTable.getNumberOfRows(), 1); assertEquals(dataTable.getLong(0, 0), 0L); @@ -58,22 +59,22 @@ public void testBuildEmptyQueryResults() // Group-by queryContext = QueryContextConverterUtils.getQueryContext( "SELECT c, d, COUNT(*), SUM(a), MAX(b) FROM testTable WHERE foo = 'bar' GROUP BY c, d"); - dataTable = ResultsBlockUtils.buildEmptyQueryResults(queryContext).getDataTable(queryContext); + dataTable = ResultsBlockUtils.buildEmptyQueryResults(queryContext).getDataTable(); dataSchema = dataTable.getDataSchema(); assertEquals(dataSchema.getColumnNames(), new String[]{"c", "d", "count(*)", "sum(a)", "max(b)"}); - assertEquals(dataSchema.getColumnDataTypes(), new DataSchema.ColumnDataType[]{ - DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.LONG, - DataSchema.ColumnDataType.DOUBLE, DataSchema.ColumnDataType.DOUBLE + assertEquals(dataSchema.getColumnDataTypes(), new ColumnDataType[]{ + ColumnDataType.STRING, ColumnDataType.STRING, ColumnDataType.LONG, ColumnDataType.DOUBLE, ColumnDataType.DOUBLE }); assertEquals(dataTable.getNumberOfRows(), 0); // Distinct queryContext = QueryContextConverterUtils.getQueryContext("SELECT DISTINCT a, b FROM testTable WHERE foo = 'bar'"); - dataTable = ResultsBlockUtils.buildEmptyQueryResults(queryContext).getDataTable(queryContext); + dataTable = ResultsBlockUtils.buildEmptyQueryResults(queryContext).getDataTable(); dataSchema = dataTable.getDataSchema(); assertEquals(dataSchema.getColumnNames(), new String[]{"a", "b"}); - assertEquals(dataSchema.getColumnDataTypes(), new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.STRING, - DataSchema.ColumnDataType.STRING}); + assertEquals(dataSchema.getColumnDataTypes(), new ColumnDataType[]{ + ColumnDataType.STRING, ColumnDataType.STRING + }); assertEquals(dataTable.getNumberOfRows(), 0); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/combine/CombineErrorOperatorsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/combine/CombineErrorOperatorsTest.java index da8cb4835b1a..a8ac2b81512f 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/combine/CombineErrorOperatorsTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/combine/CombineErrorOperatorsTest.java @@ -47,7 +47,12 @@ public class CombineErrorOperatorsTest { private static final int NUM_OPERATORS = 10; private static final int NUM_THREADS = 2; - private static final long TIMEOUT_MS = 1000L; + private static final QueryContext QUERY_CONTEXT = + QueryContextConverterUtils.getQueryContext("SELECT * FROM testTable"); + + static { + QUERY_CONTEXT.setEndTimeMs(Long.MAX_VALUE); + } private ExecutorService _executorService; @@ -63,10 +68,8 @@ public void testCombineExceptionOperator() { operators.add(new RegularOperator()); } operators.add(new ExceptionOperator()); - QueryContext queryContext = QueryContextConverterUtils.getQueryContext("SELECT * FROM testTable"); - queryContext.setEndTimeMs(System.currentTimeMillis() + TIMEOUT_MS); SelectionOnlyCombineOperator combineOperator = - new SelectionOnlyCombineOperator(operators, queryContext, _executorService); + new SelectionOnlyCombineOperator(operators, QUERY_CONTEXT, _executorService); BaseResultsBlock resultsBlock = combineOperator.nextBlock(); assertTrue(resultsBlock instanceof ExceptionResultsBlock); List processingExceptions = resultsBlock.getProcessingExceptions(); @@ -84,10 +87,8 @@ public void testCombineErrorOperator() { operators.add(new RegularOperator()); } operators.add(new ErrorOperator()); - QueryContext queryContext = QueryContextConverterUtils.getQueryContext("SELECT * FROM testTable"); - queryContext.setEndTimeMs(System.currentTimeMillis() + TIMEOUT_MS); SelectionOnlyCombineOperator combineOperator = - new SelectionOnlyCombineOperator(operators, queryContext, _executorService); + new SelectionOnlyCombineOperator(operators, QUERY_CONTEXT, _executorService); BaseResultsBlock resultsBlock = combineOperator.nextBlock(); assertTrue(resultsBlock instanceof ExceptionResultsBlock); List processingExceptions = resultsBlock.getProcessingExceptions(); @@ -106,10 +107,8 @@ public void testCombineExceptionAndErrorOperator() { } operators.add(new ExceptionOperator()); operators.add(new ErrorOperator()); - QueryContext queryContext = QueryContextConverterUtils.getQueryContext("SELECT * FROM testTable"); - queryContext.setEndTimeMs(System.currentTimeMillis() + TIMEOUT_MS); SelectionOnlyCombineOperator combineOperator = - new SelectionOnlyCombineOperator(operators, queryContext, _executorService); + new SelectionOnlyCombineOperator(operators, QUERY_CONTEXT, _executorService); BaseResultsBlock resultsBlock = combineOperator.nextBlock(); assertTrue(resultsBlock instanceof ExceptionResultsBlock); List processingExceptions = resultsBlock.getProcessingExceptions(); @@ -176,7 +175,7 @@ private static class RegularOperator extends BaseOperator { protected Block getNextBlock() { return new SelectionResultsBlock( new DataSchema(new String[]{"myColumn"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT}), - new ArrayList<>()); + new ArrayList<>(), QUERY_CONTEXT); } @Override diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/combine/CombineSlowOperatorsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/combine/CombineSlowOperatorsTest.java index 10a06d2fd84a..f01465e5a6f0 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/combine/CombineSlowOperatorsTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/combine/CombineSlowOperatorsTest.java @@ -41,7 +41,6 @@ import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.segment.spi.datasource.DataSourceMetadata; import org.apache.pinot.spi.exception.EarlyTerminationException; -import org.apache.pinot.spi.exception.QueryCancelledException; import org.apache.pinot.util.TestUtils; import org.testng.Assert; import org.testng.annotations.AfterClass; @@ -117,7 +116,7 @@ public void testCancelSelectionOnlyCombineOperator() { queryContext.setEndTimeMs(System.currentTimeMillis() + 10000); SelectionOnlyCombineOperator combineOperator = new SelectionOnlyCombineOperator(operators, queryContext, _executorService); - testCancelCombineOperator(combineOperator, ready, "Cancelled while merging results blocks"); + testCancelCombineOperator(combineOperator, ready); } @Test @@ -128,7 +127,7 @@ public void testCancelSelectionOrderByCombineOperator() { queryContext.setEndTimeMs(System.currentTimeMillis() + 10000); SelectionOrderByCombineOperator combineOperator = new SelectionOrderByCombineOperator(operators, queryContext, _executorService); - testCancelCombineOperator(combineOperator, ready, "Cancelled while merging results blocks"); + testCancelCombineOperator(combineOperator, ready); } @Test @@ -146,9 +145,9 @@ public void testCancelMinMaxValueBasedSelectionOrderByCombineOperator() { }); QueryContext queryContext = QueryContextConverterUtils.getQueryContext("SELECT * FROM testTable ORDER BY column"); queryContext.setEndTimeMs(System.currentTimeMillis() + 10000); - SelectionOrderByCombineOperator combineOperator = - new SelectionOrderByCombineOperator(operators, queryContext, _executorService); - testCancelCombineOperator(combineOperator, ready, "Cancelled while merging results blocks"); + MinMaxValueBasedSelectionOrderByCombineOperator combineOperator = + new MinMaxValueBasedSelectionOrderByCombineOperator(operators, queryContext, _executorService); + testCancelCombineOperator(combineOperator, ready); } @Test @@ -159,7 +158,7 @@ public void testCancelAggregationOnlyCombineOperator() { queryContext.setEndTimeMs(System.currentTimeMillis() + 10000); AggregationCombineOperator combineOperator = new AggregationCombineOperator(operators, queryContext, _executorService); - testCancelCombineOperator(combineOperator, ready, "Cancelled while merging results blocks"); + testCancelCombineOperator(combineOperator, ready); } @Test @@ -170,12 +169,13 @@ public void testCancelGroupByOrderByCombineOperator() { QueryContextConverterUtils.getQueryContext("SELECT COUNT(*) FROM testTable GROUP BY column"); queryContext.setEndTimeMs(System.currentTimeMillis() + 10000); GroupByCombineOperator combineOperator = new GroupByCombineOperator(operators, queryContext, _executorService); - testCancelCombineOperator(combineOperator, ready, "Cancelled while merging results blocks"); + testCancelCombineOperator(combineOperator, ready); } - private void testCancelCombineOperator(BaseCombineOperator combineOperator, CountDownLatch ready, String errMsg) { + private void testCancelCombineOperator(BaseCombineOperator combineOperator, CountDownLatch ready) { AtomicReference exp = new AtomicReference<>(); - ExecutorService combineExecutor = Executors.newSingleThreadExecutor(); + // Avoid early finalization by not using Executors.newSingleThreadExecutor (java <= 20, JDK-8145304) + ExecutorService combineExecutor = Executors.newFixedThreadPool(1); try { Future future = combineExecutor.submit(() -> { try { @@ -194,9 +194,8 @@ private void testCancelCombineOperator(BaseCombineOperator combineOperator, Coun } finally { combineExecutor.shutdownNow(); } - TestUtils.waitForCondition((aVoid) -> exp.get() instanceof QueryCancelledException, 10_000, + TestUtils.waitForCondition((aVoid) -> exp.get() instanceof EarlyTerminationException, 10_000, "Should have been cancelled"); - assertEquals(exp.get().getMessage(), errMsg); } /** diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/combine/SelectionCombineOperatorTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/combine/SelectionCombineOperatorTest.java index 88b81d5b886d..ef3c0e1e540b 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/combine/SelectionCombineOperatorTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/combine/SelectionCombineOperatorTest.java @@ -21,13 +21,10 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.PriorityQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.commons.io.FileUtils; -import org.apache.pinot.common.metadata.segment.SegmentZKMetadata; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; import org.apache.pinot.core.plan.CombinePlanNode; @@ -36,15 +33,12 @@ import org.apache.pinot.core.plan.maker.PlanMaker; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.request.context.utils.QueryContextConverterUtils; +import org.apache.pinot.core.util.QueryMultiThreadingUtils; import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; -import org.apache.pinot.segment.local.indexsegment.mutable.MutableSegmentImpl; -import org.apache.pinot.segment.local.io.writer.impl.DirectMemoryManager; -import org.apache.pinot.segment.local.realtime.impl.RealtimeSegmentConfig; -import org.apache.pinot.segment.local.realtime.impl.RealtimeSegmentStatsHistory; import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; import org.apache.pinot.segment.spi.IndexSegment; -import org.apache.pinot.segment.spi.MutableSegment; +import org.apache.pinot.segment.spi.SegmentContext; import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; @@ -58,9 +52,6 @@ import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; @@ -75,9 +66,7 @@ public class SelectionCombineOperatorTest { private static final String SEGMENT_NAME_PREFIX = "testSegment_"; // Create (MAX_NUM_THREADS_PER_QUERY * 2) segments so that each thread needs to process 2 segments - private static final int NUM_SEGMENTS = CombineOperatorUtils.MAX_NUM_THREADS_PER_QUERY * 2; - private static final int NUM_CONSUMING_SEGMENTS = NUM_SEGMENTS / 2; - private static final String REALTIME_TABLE_NAME = RAW_TABLE_NAME + "_REALTIME"; + private static final int NUM_SEGMENTS = QueryMultiThreadingUtils.MAX_NUM_THREADS_PER_QUERY * 2; private static final int NUM_RECORDS_PER_SEGMENT = 100; private static final String INT_COLUMN = "intColumn"; @@ -96,38 +85,9 @@ public void setUp() throws Exception { FileUtils.deleteDirectory(TEMP_DIR); _indexSegments = new ArrayList<>(NUM_SEGMENTS); - for (int i = 0; i < NUM_SEGMENTS / 2; i++) { + for (int i = 0; i < NUM_SEGMENTS; i++) { _indexSegments.add(createOfflineSegment(i)); } - - for (int i = NUM_CONSUMING_SEGMENTS; i < NUM_SEGMENTS; i++) { - _indexSegments.add(createRealtimeSegment(i)); - } - } - - private IndexSegment createRealtimeSegment(int index) - throws Exception { - RealtimeSegmentStatsHistory statsHistory = mock(RealtimeSegmentStatsHistory.class); - when(statsHistory.getEstimatedCardinality(anyString())).thenReturn(200); - when(statsHistory.getEstimatedAvgColSize(anyString())).thenReturn(32); - - String segmentName = SEGMENT_NAME_PREFIX + index; - - RealtimeSegmentConfig realtimeSegmentConfig = new RealtimeSegmentConfig.Builder() - .setTableNameWithType(REALTIME_TABLE_NAME).setSegmentName(segmentName).setSchema(SCHEMA).setCapacity(100000) - .setAvgNumMultiValues(2).setNoDictionaryColumns(Collections.emptySet()) - .setJsonIndexConfigs(Collections.emptyMap()).setVarLengthDictionaryColumns(Collections.emptySet()) - .setInvertedIndexColumns(Collections.emptySet()).setSegmentZKMetadata(new SegmentZKMetadata(segmentName)) - .setMemoryManager(new DirectMemoryManager(segmentName)).setStatsHistory(statsHistory).setAggregateMetrics(false) - .setNullHandlingEnabled(true).setIngestionAggregationConfigs(Collections.emptyList()).build(); - MutableSegment mutableSegmentImpl = new MutableSegmentImpl(realtimeSegmentConfig, null); - int baseValue = index * NUM_RECORDS_PER_SEGMENT / 2; - for (int i = 0; i < NUM_RECORDS_PER_SEGMENT; i++) { - GenericRow record = new GenericRow(); - record.putValue(INT_COLUMN, baseValue + i); - mutableSegmentImpl.index(record, null); - } - return mutableSegmentImpl; } private IndexSegment createOfflineSegment(int index) @@ -178,27 +138,26 @@ public void testSelectionOnly() { // Should early-terminate after processing the result of the first segment. Each thread should process at most 1 // segment. long numDocsScanned = combineResult.getNumDocsScanned(); - assertTrue(numDocsScanned >= 10 && numDocsScanned <= CombineOperatorUtils.MAX_NUM_THREADS_PER_QUERY * 10); + assertTrue(numDocsScanned >= 10 && numDocsScanned <= QueryMultiThreadingUtils.MAX_NUM_THREADS_PER_QUERY * 10); assertEquals(combineResult.getNumEntriesScannedInFilter(), 0); assertEquals(combineResult.getNumEntriesScannedPostFilter(), numDocsScanned); assertEquals(combineResult.getNumSegmentsProcessed(), NUM_SEGMENTS); - assertEquals(combineResult.getNumConsumingSegmentsProcessed(), NUM_CONSUMING_SEGMENTS); int numSegmentsMatched = combineResult.getNumSegmentsMatched(); - assertTrue(numSegmentsMatched >= 1 && numSegmentsMatched <= CombineOperatorUtils.MAX_NUM_THREADS_PER_QUERY); + assertTrue(numSegmentsMatched >= 1 && numSegmentsMatched <= QueryMultiThreadingUtils.MAX_NUM_THREADS_PER_QUERY); // The check below depends on the order of segment processing. When segments# <= 10 (the value of // CombinePlanNode.TARGET_NUM_PLANS_PER_THREAD to be specific), the segments are processed in the order as they // are prepared, which is OFFLINE segments followed by RT segments and this case makes the value here equal to 0. // But when segments# > 10, the segments are processed in a different order and some RT segments can be processed - // ahead of the other OFFLINE segments, but no more than CombineOperatorUtils.MAX_NUM_THREADS_PER_QUERY for sure + // ahead of the other OFFLINE segments, but no more than TaskUtils.MAX_NUM_THREADS_PER_QUERY for sure // as each thread only processes one segment. int numConsumingSegmentsMatched = combineResult.getNumConsumingSegmentsMatched(); if (NUM_SEGMENTS <= 10) { assertEquals(numConsumingSegmentsMatched, 0, "numSegments: " + NUM_SEGMENTS); } else { assertTrue(numConsumingSegmentsMatched >= 0 - && numConsumingSegmentsMatched <= CombineOperatorUtils.MAX_NUM_THREADS_PER_QUERY, String + && numConsumingSegmentsMatched <= QueryMultiThreadingUtils.MAX_NUM_THREADS_PER_QUERY, String .format("numConsumingSegmentsMatched: %d, maxThreadsPerQuery: %d, numSegments: %d", - combineResult.getNumConsumingSegmentsMatched(), CombineOperatorUtils.MAX_NUM_THREADS_PER_QUERY, + combineResult.getNumConsumingSegmentsMatched(), QueryMultiThreadingUtils.MAX_NUM_THREADS_PER_QUERY, NUM_SEGMENTS)); } assertEquals(combineResult.getNumTotalDocs(), NUM_SEGMENTS * NUM_RECORDS_PER_SEGMENT); @@ -214,8 +173,6 @@ public void testSelectionOnly() { assertEquals(combineResult.getNumEntriesScannedPostFilter(), numDocsScanned); assertEquals(combineResult.getNumSegmentsProcessed(), NUM_SEGMENTS); assertEquals(combineResult.getNumSegmentsMatched(), NUM_SEGMENTS); - assertEquals(combineResult.getNumConsumingSegmentsProcessed(), NUM_CONSUMING_SEGMENTS); - assertEquals(combineResult.getNumConsumingSegmentsMatched(), NUM_CONSUMING_SEGMENTS); assertEquals(combineResult.getNumTotalDocs(), NUM_SEGMENTS * NUM_RECORDS_PER_SEGMENT); } @@ -224,57 +181,52 @@ public void testSelectionOrderBy() { SelectionResultsBlock combineResult = getCombineResult("SELECT * FROM testTable ORDER BY intColumn"); assertEquals(combineResult.getDataSchema(), new DataSchema(new String[]{INT_COLUMN}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT})); - PriorityQueue selectionResult = combineResult.getRowsAsPriorityQueue(); - assertNotNull(selectionResult); - assertEquals(selectionResult.size(), 10); - int expectedValue = 9; - while (!selectionResult.isEmpty()) { - assertEquals((int) selectionResult.poll()[0], expectedValue--); + List rows = combineResult.getRows(); + assertNotNull(rows); + assertEquals(rows.size(), 10); + for (int i = 0; i < 10; i++) { + assertEquals((int) rows.get(i)[0], i); } // Should early-terminate after processing the result of the first segment. Each thread should process at most 1 // segment. long numDocsScanned = combineResult.getNumDocsScanned(); // Need to scan 10 documents per segment because 'intColumn' is sorted - assertTrue(numDocsScanned >= 10 && numDocsScanned <= CombineOperatorUtils.MAX_NUM_THREADS_PER_QUERY * 10); + assertTrue(numDocsScanned >= 10 && numDocsScanned <= QueryMultiThreadingUtils.MAX_NUM_THREADS_PER_QUERY * 10); assertEquals(combineResult.getNumEntriesScannedInFilter(), 0); assertEquals(combineResult.getNumEntriesScannedPostFilter(), numDocsScanned); assertEquals(combineResult.getNumSegmentsProcessed(), NUM_SEGMENTS); - assertEquals(combineResult.getNumConsumingSegmentsProcessed(), NUM_CONSUMING_SEGMENTS); - assertEquals(combineResult.getNumConsumingSegmentsMatched(), 0); int numSegmentsMatched = combineResult.getNumSegmentsMatched(); - assertTrue(numSegmentsMatched >= 1 && numSegmentsMatched <= CombineOperatorUtils.MAX_NUM_THREADS_PER_QUERY); + assertTrue(numSegmentsMatched >= 1 && numSegmentsMatched <= QueryMultiThreadingUtils.MAX_NUM_THREADS_PER_QUERY); assertEquals(combineResult.getNumTotalDocs(), NUM_SEGMENTS * NUM_RECORDS_PER_SEGMENT); combineResult = getCombineResult("SELECT * FROM testTable ORDER BY intColumn DESC"); assertEquals(combineResult.getDataSchema(), new DataSchema(new String[]{INT_COLUMN}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT})); - selectionResult = combineResult.getRowsAsPriorityQueue(); - assertNotNull(selectionResult); - assertEquals(selectionResult.size(), 10); - expectedValue = NUM_SEGMENTS * NUM_RECORDS_PER_SEGMENT / 2 + 40; - while (!selectionResult.isEmpty()) { - assertEquals((int) selectionResult.poll()[0], expectedValue++); + rows = combineResult.getRows(); + assertNotNull(rows); + assertEquals(rows.size(), 10); + int expectedValue = NUM_SEGMENTS * NUM_RECORDS_PER_SEGMENT / 2 + 49; + for (int i = 0; i < 10; i++) { + assertEquals((int) rows.get(i)[0], expectedValue - i); } // Should early-terminate after processing the result of the first segment. Each thread should process at most 1 // segment. numDocsScanned = combineResult.getNumDocsScanned(); assertTrue(numDocsScanned >= NUM_RECORDS_PER_SEGMENT - && numDocsScanned <= CombineOperatorUtils.MAX_NUM_THREADS_PER_QUERY * NUM_RECORDS_PER_SEGMENT); + && numDocsScanned <= QueryMultiThreadingUtils.MAX_NUM_THREADS_PER_QUERY * NUM_RECORDS_PER_SEGMENT); assertEquals(combineResult.getNumEntriesScannedInFilter(), 0); assertEquals(combineResult.getNumEntriesScannedPostFilter(), numDocsScanned); assertEquals(combineResult.getNumSegmentsProcessed(), NUM_SEGMENTS); - assertEquals(combineResult.getNumConsumingSegmentsProcessed(), NUM_CONSUMING_SEGMENTS); - assertEquals(combineResult.getNumConsumingSegmentsMatched(), NUM_CONSUMING_SEGMENTS); numSegmentsMatched = combineResult.getNumSegmentsMatched(); - assertTrue(numSegmentsMatched >= 1 && numSegmentsMatched <= CombineOperatorUtils.MAX_NUM_THREADS_PER_QUERY); + assertTrue(numSegmentsMatched >= 1 && numSegmentsMatched <= QueryMultiThreadingUtils.MAX_NUM_THREADS_PER_QUERY); assertEquals(combineResult.getNumTotalDocs(), NUM_SEGMENTS * NUM_RECORDS_PER_SEGMENT); combineResult = getCombineResult("SELECT * FROM testTable ORDER BY intColumn DESC LIMIT 10000"); assertEquals(combineResult.getDataSchema(), new DataSchema(new String[]{INT_COLUMN}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT})); - selectionResult = combineResult.getRowsAsPriorityQueue(); - assertNotNull(selectionResult); - assertEquals(selectionResult.size(), NUM_SEGMENTS * NUM_RECORDS_PER_SEGMENT); + rows = combineResult.getRows(); + assertNotNull(rows); + assertEquals(rows.size(), NUM_SEGMENTS * NUM_RECORDS_PER_SEGMENT); // Should not early-terminate numDocsScanned = combineResult.getNumDocsScanned(); assertEquals(numDocsScanned, NUM_SEGMENTS * NUM_RECORDS_PER_SEGMENT); @@ -282,8 +234,6 @@ public void testSelectionOrderBy() { assertEquals(combineResult.getNumEntriesScannedPostFilter(), numDocsScanned); assertEquals(combineResult.getNumSegmentsProcessed(), NUM_SEGMENTS); assertEquals(combineResult.getNumSegmentsMatched(), NUM_SEGMENTS); - assertEquals(combineResult.getNumConsumingSegmentsProcessed(), NUM_CONSUMING_SEGMENTS); - assertEquals(combineResult.getNumConsumingSegmentsMatched(), NUM_CONSUMING_SEGMENTS); assertEquals(combineResult.getNumTotalDocs(), NUM_SEGMENTS * NUM_RECORDS_PER_SEGMENT); } @@ -291,7 +241,7 @@ private SelectionResultsBlock getCombineResult(String query) { QueryContext queryContext = QueryContextConverterUtils.getQueryContext(query); List planNodes = new ArrayList<>(NUM_SEGMENTS); for (IndexSegment indexSegment : _indexSegments) { - planNodes.add(PLAN_MAKER.makeSegmentPlanNode(indexSegment, queryContext)); + planNodes.add(PLAN_MAKER.makeSegmentPlanNode(new SegmentContext(indexSegment), queryContext)); } queryContext.setEndTimeMs(System.currentTimeMillis() + Server.DEFAULT_QUERY_EXECUTOR_TIMEOUT_MS); CombinePlanNode combinePlanNode = new CombinePlanNode(planNodes, queryContext, EXECUTOR, null); diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/AndFilterOperatorTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/AndFilterOperatorTest.java index 6481ae2256c8..230a3ead557e 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/AndFilterOperatorTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/AndFilterOperatorTest.java @@ -18,7 +18,10 @@ */ package org.apache.pinot.core.operator.filter; +import com.google.common.collect.ImmutableList; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import org.apache.pinot.core.common.BlockDocIdIterator; import org.apache.pinot.segment.spi.Constants; @@ -31,13 +34,14 @@ public class AndFilterOperatorTest { @Test public void testIntersectionForTwoLists() { + int numDocs = 40; int[] docIds1 = new int[]{2, 3, 10, 15, 16, 28}; int[] docIds2 = new int[]{3, 6, 8, 20, 28}; List operators = new ArrayList<>(); - operators.add(new TestFilterOperator(docIds1)); - operators.add(new TestFilterOperator(docIds2)); - AndFilterOperator andOperator = new AndFilterOperator(operators); + operators.add(new TestFilterOperator(docIds1, numDocs)); + operators.add(new TestFilterOperator(docIds2, numDocs)); + AndFilterOperator andOperator = new AndFilterOperator(operators, null, numDocs, false); BlockDocIdIterator iterator = andOperator.nextBlock().getBlockDocIdSet().iterator(); Assert.assertEquals(iterator.next(), 3); @@ -47,15 +51,16 @@ public void testIntersectionForTwoLists() { @Test public void testIntersectionForThreeLists() { + int numDocs = 40; int[] docIds1 = new int[]{2, 3, 6, 10, 15, 16, 28}; int[] docIds2 = new int[]{3, 6, 8, 20, 28}; int[] docIds3 = new int[]{1, 2, 3, 6, 30}; List operators = new ArrayList<>(); - operators.add(new TestFilterOperator(docIds1)); - operators.add(new TestFilterOperator(docIds2)); - operators.add(new TestFilterOperator(docIds3)); - AndFilterOperator andOperator = new AndFilterOperator(operators); + operators.add(new TestFilterOperator(docIds1, numDocs)); + operators.add(new TestFilterOperator(docIds2, numDocs)); + operators.add(new TestFilterOperator(docIds3, numDocs)); + AndFilterOperator andOperator = new AndFilterOperator(operators, null, numDocs, false); BlockDocIdIterator iterator = andOperator.nextBlock().getBlockDocIdSet().iterator(); Assert.assertEquals(iterator.next(), 3); @@ -65,19 +70,20 @@ public void testIntersectionForThreeLists() { @Test public void testComplex() { + int numDocs = 40; int[] docIds1 = new int[]{2, 3, 6, 10, 15, 16, 28}; int[] docIds2 = new int[]{3, 6, 8, 20, 28}; int[] docIds3 = new int[]{1, 2, 3, 6, 30}; List childOperators = new ArrayList<>(); - childOperators.add(new TestFilterOperator(docIds1)); - childOperators.add(new TestFilterOperator(docIds2)); - AndFilterOperator childAndOperator = new AndFilterOperator(childOperators); + childOperators.add(new TestFilterOperator(docIds1, numDocs)); + childOperators.add(new TestFilterOperator(docIds2, numDocs)); + AndFilterOperator childAndOperator = new AndFilterOperator(childOperators, null, numDocs, false); List operators = new ArrayList<>(); operators.add(childAndOperator); - operators.add(new TestFilterOperator(docIds3)); - AndFilterOperator andOperator = new AndFilterOperator(operators); + operators.add(new TestFilterOperator(docIds3, numDocs)); + AndFilterOperator andOperator = new AndFilterOperator(operators, null, numDocs, false); BlockDocIdIterator iterator = andOperator.nextBlock().getBlockDocIdSet().iterator(); Assert.assertEquals(iterator.next(), 3); @@ -112,8 +118,8 @@ void testAndDocIdSetReordering() { numDocs)); } - AndFilterOperator andFilterOperator1 = new AndFilterOperator(childOperators1); - AndFilterOperator andFilterOperator2 = new AndFilterOperator(childOperators2); + AndFilterOperator andFilterOperator1 = new AndFilterOperator(childOperators1, null, numDocs, false); + AndFilterOperator andFilterOperator2 = new AndFilterOperator(childOperators2, null, numDocs, false); BlockDocIdIterator iterator1 = andFilterOperator1.getNextBlock().getBlockDocIdSet().iterator(); BlockDocIdIterator iterator2 = andFilterOperator2.getNextBlock().getBlockDocIdSet().iterator(); Assert.assertEquals(iterator1.next(), 0); @@ -133,19 +139,20 @@ void testAndDocIdSetReordering() { @Test public void testComplexWithOr() { + int numDocs = 40; int[] docIds1 = new int[]{2, 3, 6, 10, 15, 16, 28}; int[] docIds2 = new int[]{3, 6, 8, 20, 28}; int[] docIds3 = new int[]{1, 2, 3, 6, 30}; List childOperators = new ArrayList<>(); - childOperators.add(new TestFilterOperator(docIds3)); - childOperators.add(new TestFilterOperator(docIds2)); - OrFilterOperator childOrOperator = new OrFilterOperator(childOperators, 40); + childOperators.add(new TestFilterOperator(docIds3, numDocs)); + childOperators.add(new TestFilterOperator(docIds2, numDocs)); + OrFilterOperator childOrOperator = new OrFilterOperator(childOperators, null, numDocs, false); List operators = new ArrayList<>(); operators.add(childOrOperator); - operators.add(new TestFilterOperator(docIds1)); - AndFilterOperator andOperator = new AndFilterOperator(operators); + operators.add(new TestFilterOperator(docIds1, numDocs)); + AndFilterOperator andOperator = new AndFilterOperator(operators, null, numDocs, false); BlockDocIdIterator iterator = andOperator.nextBlock().getBlockDocIdSet().iterator(); Assert.assertEquals(iterator.next(), 2); @@ -154,4 +161,77 @@ public void testComplexWithOr() { Assert.assertEquals(iterator.next(), 28); Assert.assertEquals(iterator.next(), Constants.EOF); } + + @Test + public void testAndWithNull() { + int numDocs = 10; + int[] docIds1 = new int[]{1, 2, 3}; + int[] docIds2 = new int[]{0, 1, 2}; + int[] nullDocIds1 = new int[]{4, 5, 6}; + int[] nullDocIds2 = new int[]{3, 4, 5, 6, 7}; + + AndFilterOperator andFilterOperator = new AndFilterOperator( + Arrays.asList(new TestFilterOperator(docIds1, nullDocIds1, numDocs), + new TestFilterOperator(docIds2, nullDocIds2, numDocs)), null, numDocs, true); + + Assert.assertEquals(TestUtils.getDocIds(andFilterOperator.getTrues()), ImmutableList.of(1, 2)); + Assert.assertEquals(TestUtils.getDocIds(andFilterOperator.getFalses()), ImmutableList.of(0, 7, 8, 9)); + } + + @Test + public void testAndWithNullHandlingDisabled() { + int numDocs = 4; + int[] docIds1 = new int[]{0, 3}; + int[] docIds2 = new int[]{0, 1}; + int[] nullDocIds1 = new int[]{}; + int[] nullDocIds2 = new int[]{}; + + AndFilterOperator andFilterOperator = new AndFilterOperator( + Arrays.asList(new TestFilterOperator(docIds1, nullDocIds1, numDocs), + new TestFilterOperator(docIds2, nullDocIds2, numDocs)), null, numDocs, false); + + Assert.assertEquals(TestUtils.getDocIds(andFilterOperator.getTrues()), ImmutableList.of(0)); + Assert.assertEquals(TestUtils.getDocIds(andFilterOperator.getFalses()), ImmutableList.of(1, 2, 3)); + } + + @Test + public void testAndWithNullOneFilterIsEmpty() { + int numDocs = 10; + int[] docIds1 = new int[]{1, 2, 3}; + int[] nullDocIds1 = new int[]{4, 5, 6}; + + AndFilterOperator andFilterOperator = new AndFilterOperator( + Arrays.asList(new TestFilterOperator(docIds1, nullDocIds1, numDocs), EmptyFilterOperator.getInstance()), null, + numDocs, true); + + Assert.assertEquals(TestUtils.getDocIds(andFilterOperator.getTrues()), Collections.emptyList()); + Assert.assertEquals(TestUtils.getDocIds(andFilterOperator.getFalses()), + ImmutableList.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)); + } + + @Test + public void testAndWithNullOneFilterMatchesAll() { + int numDocs = 10; + int[] docIds1 = new int[]{1, 2, 3}; + int[] nullDocIds1 = new int[]{4, 5, 6}; + + AndFilterOperator andFilterOperator = new AndFilterOperator( + Arrays.asList(new TestFilterOperator(docIds1, nullDocIds1, numDocs), new MatchAllFilterOperator(numDocs)), null, + numDocs, true); + + Assert.assertEquals(TestUtils.getDocIds(andFilterOperator.getTrues()), ImmutableList.of(1, 2, 3)); + Assert.assertEquals(TestUtils.getDocIds(andFilterOperator.getFalses()), ImmutableList.of(0, 7, 8, 9)); + } + + @Test + public void testAndWithAllMatchesAll() { + int numDocs = 10; + AndFilterOperator andFilterOperator = + new AndFilterOperator(Arrays.asList(new MatchAllFilterOperator(numDocs), new MatchAllFilterOperator(numDocs)), + null, numDocs, true); + + Assert.assertEquals(TestUtils.getDocIds(andFilterOperator.getTrues()), + ImmutableList.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)); + Assert.assertEquals(TestUtils.getDocIds(andFilterOperator.getFalses()), Collections.emptyList()); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/BaseFilterOperatorTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/BaseFilterOperatorTest.java new file mode 100644 index 000000000000..4d9ecf5cc4b7 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/BaseFilterOperatorTest.java @@ -0,0 +1,79 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.filter; + +import com.google.common.collect.ImmutableList; +import java.util.Collections; +import org.testng.Assert; +import org.testng.annotations.Test; + + +public class BaseFilterOperatorTest { + + @Test + public void testBaseWithFalses() { + int numDocs = 10; + int[] docIds = new int[]{0, 1, 2, 3}; + TestFilterOperator testFilterOperator = new TestFilterOperator(docIds, numDocs); + + Assert.assertEquals(TestUtils.getDocIds(testFilterOperator.getTrues()), ImmutableList.of(0, 1, 2, 3)); + Assert.assertEquals(TestUtils.getDocIds(testFilterOperator.getFalses()), ImmutableList.of(4, 5, 6, 7, 8, 9)); + } + + @Test + public void testBaseWithNullHandling() { + int numDocs = 10; + int[] docIds = new int[]{0, 1, 2, 3}; + int[] nullDocIds = new int[]{4, 5, 6, 7, 8, 9}; + TestFilterOperator testFilterOperator = new TestFilterOperator(docIds, nullDocIds, numDocs); + + Assert.assertEquals(TestUtils.getDocIds(testFilterOperator.getTrues()), ImmutableList.of(0, 1, 2, 3)); + Assert.assertEquals(TestUtils.getDocIds(testFilterOperator.getFalses()), Collections.emptyList()); + } + + @Test + public void testBaseWithAllTrue() { + int numDocs = 10; + int[] docIds = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + TestFilterOperator testFilterOperator = new TestFilterOperator(docIds, numDocs); + Assert.assertEquals(TestUtils.getDocIds(testFilterOperator.getTrues()), + ImmutableList.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)); + Assert.assertEquals(TestUtils.getDocIds(testFilterOperator.getFalses()), Collections.emptyList()); + } + + @Test + public void testBaseWithAllFalse() { + int numDocs = 10; + int[] docIds = new int[]{}; + TestFilterOperator testFilterOperator = new TestFilterOperator(docIds, numDocs); + Assert.assertEquals(TestUtils.getDocIds(testFilterOperator.getTrues()), Collections.emptyList()); + Assert.assertEquals(TestUtils.getDocIds(testFilterOperator.getFalses()), + ImmutableList.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)); + } + + @Test + public void testBaseWithAllNulls() { + int numDocs = 10; + int[] docIds = new int[]{}; + int[] nullDocIds = new int[]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + TestFilterOperator testFilterOperator = new TestFilterOperator(docIds, nullDocIds, numDocs); + Assert.assertEquals(TestUtils.getDocIds(testFilterOperator.getTrues()), Collections.emptyList()); + Assert.assertEquals(TestUtils.getDocIds(testFilterOperator.getFalses()), Collections.emptyList()); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/FilterOperatorUtilsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/FilterOperatorUtilsTest.java index ef5f781fcea0..7e40b24fddad 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/FilterOperatorUtilsTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/FilterOperatorUtilsTest.java @@ -18,12 +18,21 @@ */ package org.apache.pinot.core.operator.filter; +import com.google.common.collect.Lists; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.List; +import java.util.OptionalInt; +import org.apache.pinot.core.common.Operator; +import org.apache.pinot.core.operator.blocks.FilterBlock; import org.apache.pinot.core.query.request.context.QueryContext; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; @@ -32,7 +41,8 @@ public class FilterOperatorUtilsTest { private static final int NUM_DOCS = 10; private static final BaseFilterOperator EMPTY_FILTER_OPERATOR = EmptyFilterOperator.getInstance(); private static final BaseFilterOperator MATCH_ALL_FILTER_OPERATOR = new MatchAllFilterOperator(NUM_DOCS); - private static final BaseFilterOperator REGULAR_FILTER_OPERATOR = new TestFilterOperator(new int[]{1, 4, 7}); + private static final BaseFilterOperator REGULAR_FILTER_OPERATOR = + new TestFilterOperator(new int[]{1, 4, 7}, NUM_DOCS); @Test public void testGetAndFilterOperator() { @@ -101,4 +111,84 @@ public void testGetOrFilterOperator() { Arrays.asList(MATCH_ALL_FILTER_OPERATOR, REGULAR_FILTER_OPERATOR), NUM_DOCS); assertTrue(filterOperator instanceof MatchAllFilterOperator); } + + @DataProvider + public static Object[][] priorities() { + SortedIndexBasedFilterOperator sorted = mock(SortedIndexBasedFilterOperator.class); + BitmapBasedFilterOperator bitmap = mock(BitmapBasedFilterOperator.class); + RangeIndexBasedFilterOperator range = mock(RangeIndexBasedFilterOperator.class); + TextContainsFilterOperator textContains = mock(TextContainsFilterOperator.class); + TextMatchFilterOperator textMatch = mock(TextMatchFilterOperator.class); + JsonMatchFilterOperator jsonMatch = mock(JsonMatchFilterOperator.class); + H3IndexFilterOperator h3 = mock(H3IndexFilterOperator.class); + H3InclusionIndexFilterOperator h3Inclusion = mock(H3InclusionIndexFilterOperator.class); + AndFilterOperator andFilterOperator = mock(AndFilterOperator.class); + OrFilterOperator orFilterOperator = mock(OrFilterOperator.class); + NotFilterOperator notWithHighPriority = new NotFilterOperator(sorted, NUM_DOCS, false); + NotFilterOperator notWithLowPriority = new NotFilterOperator(orFilterOperator, NUM_DOCS, false); + + ExpressionFilterOperator expression = mock(ExpressionFilterOperator.class); + BaseFilterOperator unknown = mock(BaseFilterOperator.class); + + MockedPrioritizedFilterOperator prioritizedBetweenSortedAndBitmap = mock(MockedPrioritizedFilterOperator.class); + OptionalInt betweenSortedAndBitmapPriority = + OptionalInt.of((PrioritizedFilterOperator.HIGH_PRIORITY + PrioritizedFilterOperator.MEDIUM_PRIORITY) / 2); + when(prioritizedBetweenSortedAndBitmap.getPriority()).thenReturn(betweenSortedAndBitmapPriority); + + MockedPrioritizedFilterOperator notPrioritized = mock(MockedPrioritizedFilterOperator.class); + when(prioritizedBetweenSortedAndBitmap.getPriority()) + .thenReturn(OptionalInt.empty()); + + List> expectedOrder = Lists.newArrayList( + Lists.newArrayList(sorted, notWithHighPriority), + Lists.newArrayList(bitmap), + Lists.newArrayList(range, textContains, textMatch, jsonMatch, h3, h3Inclusion), + Lists.newArrayList(andFilterOperator), + Lists.newArrayList(orFilterOperator, notWithLowPriority), + Lists.newArrayList(expression), + Lists.newArrayList(unknown, notPrioritized) + ); + + List cases = new ArrayList<>(); + for (int i = 0; i < expectedOrder.size(); i++) { + List currentOps = expectedOrder.get(i); + for (BaseFilterOperator highPriorityOp : currentOps) { + for (int j = i + 1; j < expectedOrder.size(); j++) { + List lowerPriorityOps = expectedOrder.get(j); + for (BaseFilterOperator lowerPriorityOp : lowerPriorityOps) { + cases.add(new Object[] {highPriorityOp, lowerPriorityOp}); + } + } + } + } + return cases.toArray(new Object[][]{}); + } + + @Test(dataProvider = "priorities") + public void testPriority(BaseFilterOperator highPriorty, BaseFilterOperator lowerPriorty) { + ArrayList unsorted = Lists.newArrayList(lowerPriorty, highPriorty); + BaseFilterOperator filterOperator = + FilterOperatorUtils.getAndFilterOperator(QUERY_CONTEXT, unsorted, NUM_DOCS); + assertTrue(filterOperator instanceof AndFilterOperator); + List actualChildOperators = ((AndFilterOperator) filterOperator).getChildOperators(); + assertEquals(actualChildOperators, Lists.newArrayList(highPriorty, lowerPriorty), "Filter " + highPriorty + + " should have more priority than filter " + lowerPriorty); + } + + private void assertOrder(BaseFilterOperator first, BaseFilterOperator second) { + BaseFilterOperator filterOperator = + FilterOperatorUtils.getAndFilterOperator(QUERY_CONTEXT, Lists.newArrayList(second, first), NUM_DOCS); + assertTrue(filterOperator instanceof AndFilterOperator); + List actualChildOperators = ((AndFilterOperator) filterOperator).getChildOperators(); + assertEquals(actualChildOperators, Lists.newArrayList(first, second), "Filter " + first + " should have " + + "more priority than filter " + second); + } + + private static abstract class MockedPrioritizedFilterOperator extends BaseFilterOperator + implements PrioritizedFilterOperator { + public MockedPrioritizedFilterOperator() { + // This filter operator does not support AND/OR/NOT operations. + super(0, false); + } + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/NotFilterOperatorTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/NotFilterOperatorTest.java index 893389bb9bbc..e0f3609825d4 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/NotFilterOperatorTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/NotFilterOperatorTest.java @@ -18,10 +18,11 @@ */ package org.apache.pinot.core.operator.filter; +import com.google.common.collect.ImmutableList; import java.util.Arrays; -import java.util.HashSet; +import java.util.Collections; import java.util.Iterator; -import java.util.Set; +import java.util.List; import org.apache.pinot.core.common.BlockDocIdIterator; import org.apache.pinot.segment.spi.Constants; import org.testng.Assert; @@ -33,14 +34,36 @@ public class NotFilterOperatorTest { @Test public void testNotOperator() { int[] docIds1 = new int[]{2, 3, 10, 15, 16, 17, 18, 21, 22, 23, 24, 26, 28}; - Set expectedResult = new HashSet(); - expectedResult.addAll(Arrays.asList(0, 1, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 19, 20, 25, 27, 29)); + List expectedResult = Arrays.asList(0, 1, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 19, 20, 25, 27, 29); Iterator expectedIterator = expectedResult.iterator(); - NotFilterOperator notFilterOperator = new NotFilterOperator(new TestFilterOperator(docIds1), 30); + NotFilterOperator notFilterOperator = new NotFilterOperator(new TestFilterOperator(docIds1, 30), 30, false); BlockDocIdIterator iterator = notFilterOperator.nextBlock().getBlockDocIdSet().iterator(); int docId; while ((docId = iterator.next()) != Constants.EOF) { Assert.assertEquals(docId, expectedIterator.next().intValue()); } } + + @Test + public void testNotWithNull() { + int numDocs = 10; + int[] docIds = new int[]{0, 1, 2, 3}; + int[] nullDocIds = new int[]{4, 5, 6}; + + NotFilterOperator notFilterOperator = + new NotFilterOperator(new TestFilterOperator(docIds, nullDocIds, numDocs), numDocs, true); + + Assert.assertEquals(TestUtils.getDocIds(notFilterOperator.getTrues()), ImmutableList.of(7, 8, 9)); + Assert.assertEquals(TestUtils.getDocIds(notFilterOperator.getFalses()), ImmutableList.of(0, 1, 2, 3)); + } + + @Test + public void testNotEmptyFilterOperator() { + int numDocs = 5; + + NotFilterOperator notFilterOperator = new NotFilterOperator(EmptyFilterOperator.getInstance(), numDocs, true); + + Assert.assertEquals(TestUtils.getDocIds(notFilterOperator.getTrues()), ImmutableList.of(0, 1, 2, 3, 4)); + Assert.assertEquals(TestUtils.getDocIds(notFilterOperator.getFalses()), Collections.emptyList()); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/OrFilterOperatorTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/OrFilterOperatorTest.java index c16b4789cf20..b1c4aac1f803 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/OrFilterOperatorTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/OrFilterOperatorTest.java @@ -18,8 +18,10 @@ */ package org.apache.pinot.core.operator.filter; +import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.TreeSet; @@ -34,6 +36,7 @@ public class OrFilterOperatorTest { @Test public void testUnionForTwoLists() { + int numDocs = 40; int[] docIds1 = new int[]{2, 3, 10, 15, 16, 28}; int[] docIds2 = new int[]{3, 6, 8, 20, 28}; TreeSet treeSet = new TreeSet<>(); @@ -42,9 +45,9 @@ public void testUnionForTwoLists() { Iterator expectedIterator = treeSet.iterator(); List operators = new ArrayList<>(); - operators.add(new TestFilterOperator(docIds1)); - operators.add(new TestFilterOperator(docIds2)); - OrFilterOperator orOperator = new OrFilterOperator(operators, 40); + operators.add(new TestFilterOperator(docIds1, numDocs)); + operators.add(new TestFilterOperator(docIds2, numDocs)); + OrFilterOperator orOperator = new OrFilterOperator(operators, null, numDocs, false); BlockDocIdIterator iterator = orOperator.nextBlock().getBlockDocIdSet().iterator(); int docId; @@ -55,6 +58,7 @@ public void testUnionForTwoLists() { @Test public void testUnionForThreeLists() { + int numDocs = 40; int[] docIds1 = new int[]{2, 3, 6, 10, 15, 16, 28}; int[] docIds2 = new int[]{3, 6, 8, 20, 28}; int[] docIds3 = new int[]{1, 2, 3, 6, 30}; @@ -65,10 +69,10 @@ public void testUnionForThreeLists() { Iterator expectedIterator = treeSet.iterator(); List operators = new ArrayList<>(); - operators.add(new TestFilterOperator(docIds1)); - operators.add(new TestFilterOperator(docIds2)); - operators.add(new TestFilterOperator(docIds3)); - OrFilterOperator orOperator = new OrFilterOperator(operators, 40); + operators.add(new TestFilterOperator(docIds1, numDocs)); + operators.add(new TestFilterOperator(docIds2, numDocs)); + operators.add(new TestFilterOperator(docIds3, numDocs)); + OrFilterOperator orOperator = new OrFilterOperator(operators, null, numDocs, false); BlockDocIdIterator iterator = orOperator.nextBlock().getBlockDocIdSet().iterator(); int docId; @@ -79,6 +83,7 @@ public void testUnionForThreeLists() { @Test public void testComplex() { + int numDocs = 40; int[] docIds1 = new int[]{2, 3, 6, 10, 15, 16, 28}; int[] docIds2 = new int[]{3, 6, 8, 20, 28}; int[] docIds3 = new int[]{1, 2, 3, 6, 30}; @@ -89,14 +94,14 @@ public void testComplex() { Iterator expectedIterator = treeSet.iterator(); List childOperators = new ArrayList<>(); - childOperators.add(new TestFilterOperator(docIds1)); - childOperators.add(new TestFilterOperator(docIds2)); - OrFilterOperator childOrOperator = new OrFilterOperator(childOperators, 40); + childOperators.add(new TestFilterOperator(docIds1, numDocs)); + childOperators.add(new TestFilterOperator(docIds2, numDocs)); + OrFilterOperator childOrOperator = new OrFilterOperator(childOperators, null, numDocs, false); List operators = new ArrayList<>(); operators.add(childOrOperator); - operators.add(new TestFilterOperator(docIds3)); - OrFilterOperator orOperator = new OrFilterOperator(operators, 40); + operators.add(new TestFilterOperator(docIds3, numDocs)); + OrFilterOperator orOperator = new OrFilterOperator(operators, null, numDocs, false); BlockDocIdIterator iterator = orOperator.nextBlock().getBlockDocIdSet().iterator(); int docId; @@ -104,4 +109,76 @@ public void testComplex() { Assert.assertEquals(docId, expectedIterator.next().intValue()); } } + + @Test + public void testOrWithNull() { + int numDocs = 10; + int[] docIds1 = new int[]{1, 2, 3}; + int[] docIds2 = new int[]{0, 1, 2}; + int[] nullDocIds1 = new int[]{4, 5, 6}; + int[] nullDocIds2 = new int[]{3, 4, 5, 6, 7}; + + OrFilterOperator orFilterOperator = new OrFilterOperator( + Arrays.asList(new TestFilterOperator(docIds1, nullDocIds1, numDocs), + new TestFilterOperator(docIds2, nullDocIds2, numDocs)), null, numDocs, true); + + Assert.assertEquals(TestUtils.getDocIds(orFilterOperator.getTrues()), ImmutableList.of(0, 1, 2, 3)); + Assert.assertEquals(TestUtils.getDocIds(orFilterOperator.getFalses()), ImmutableList.of(8, 9)); + } + + @Test + public void testOrWithNullHandlingDisabled() { + int numDocs = 10; + int[] docIds1 = new int[]{1, 2, 3}; + int[] docIds2 = new int[]{0, 1, 2}; + int[] nullDocIds1 = new int[]{}; + int[] nullDocIds2 = new int[]{}; + + OrFilterOperator orFilterOperator = new OrFilterOperator( + Arrays.asList(new TestFilterOperator(docIds1, nullDocIds1, numDocs), + new TestFilterOperator(docIds2, nullDocIds2, numDocs)), null, numDocs, false); + + Assert.assertEquals(TestUtils.getDocIds(orFilterOperator.getTrues()), ImmutableList.of(0, 1, 2, 3)); + Assert.assertEquals(TestUtils.getDocIds(orFilterOperator.getFalses()), ImmutableList.of(4, 5, 6, 7, 8, 9)); + } + + @Test + public void testOrWithNullOneFilterIsEmpty() { + int numDocs = 10; + int[] docIds1 = new int[]{1, 2, 3}; + int[] nullDocIds1 = new int[]{4, 5, 6}; + + OrFilterOperator orFilterOperator = new OrFilterOperator( + Arrays.asList(new TestFilterOperator(docIds1, nullDocIds1, numDocs), EmptyFilterOperator.getInstance()), null, + numDocs, true); + + Assert.assertEquals(TestUtils.getDocIds(orFilterOperator.getTrues()), Arrays.asList(1, 2, 3)); + Assert.assertEquals(TestUtils.getDocIds(orFilterOperator.getFalses()), Arrays.asList(0, 7, 8, 9)); + } + + @Test + public void testOrWithNullOneFilterMatchesAll() { + int numDocs = 10; + int[] docIds1 = new int[]{1, 2, 3}; + int[] nullDocIds1 = new int[]{4, 5, 6}; + + OrFilterOperator orFilterOperator = new OrFilterOperator( + Arrays.asList(new TestFilterOperator(docIds1, nullDocIds1, numDocs), new MatchAllFilterOperator(numDocs)), null, + numDocs, true); + + Assert.assertEquals(TestUtils.getDocIds(orFilterOperator.getTrues()), Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)); + Assert.assertEquals(TestUtils.getDocIds(orFilterOperator.getFalses()), Collections.emptyList()); + } + + @Test + public void testOrWithAllEmpty() { + int numDocs = 10; + + OrFilterOperator orFilterOperator = + new OrFilterOperator(Arrays.asList(EmptyFilterOperator.getInstance(), EmptyFilterOperator.getInstance()), null, + numDocs, true); + + Assert.assertEquals(TestUtils.getDocIds(orFilterOperator.getTrues()), Collections.emptyList()); + Assert.assertEquals(TestUtils.getDocIds(orFilterOperator.getFalses()), Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/TestFilterOperator.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/TestFilterOperator.java deleted file mode 100644 index 4bcc9381187c..000000000000 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/TestFilterOperator.java +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.operator.filter; - -import java.util.Collections; -import java.util.List; -import org.apache.pinot.core.common.BlockDocIdIterator; -import org.apache.pinot.core.common.Operator; -import org.apache.pinot.core.operator.blocks.FilterBlock; -import org.apache.pinot.core.operator.docidsets.FilterBlockDocIdSet; -import org.apache.pinot.segment.spi.Constants; - - -public class TestFilterOperator extends BaseFilterOperator { - private static final String EXPLAIN_NAME = "FILTER_TEST"; - - private final int[] _docIds; - - public TestFilterOperator(int[] docIds) { - _docIds = docIds; - } - - @Override - protected FilterBlock getNextBlock() { - return new FilterBlock(new FilterBlockDocIdSet() { - @Override - public BlockDocIdIterator iterator() { - return new BlockDocIdIterator() { - private final int _numDocIds = _docIds.length; - private int _nextIndex = 0; - - @Override - public int next() { - if (_nextIndex < _numDocIds) { - return _docIds[_nextIndex++]; - } else { - return Constants.EOF; - } - } - - @Override - public int advance(int targetDocId) { - while (_nextIndex < _numDocIds) { - int docId = _docIds[_nextIndex++]; - if (docId >= targetDocId) { - return docId; - } - } - return Constants.EOF; - } - }; - } - - @Override - public long getNumEntriesScannedInFilter() { - return 0L; - } - }); - } - - @Override - public String toExplainString() { - return EXPLAIN_NAME; - } - - @Override - public List getChildOperators() { - return Collections.emptyList(); - } -} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/TestUtils.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/TestUtils.java new file mode 100644 index 000000000000..061d542e54ee --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/TestUtils.java @@ -0,0 +1,42 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.filter; + +import java.util.ArrayList; +import java.util.List; +import org.apache.pinot.core.common.BlockDocIdIterator; +import org.apache.pinot.core.common.BlockDocIdSet; +import org.apache.pinot.segment.spi.Constants; + + +public class TestUtils { + private TestUtils() { + } + + public static List getDocIds(BlockDocIdSet blockDocIdSet) { + BlockDocIdIterator iterator = blockDocIdSet.iterator(); + List docIds = new ArrayList<>(); + int curr = iterator.next(); + while (curr != Constants.EOF) { + docIds.add(curr); + curr = iterator.next(); + } + return docIds; + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/predicate/InPredicateEvaluatorFactoryTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/predicate/InPredicateEvaluatorFactoryTest.java new file mode 100644 index 000000000000..8e83e2b21774 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/predicate/InPredicateEvaluatorFactoryTest.java @@ -0,0 +1,115 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.filter.predicate; + +import com.google.common.collect.Lists; +import java.math.BigDecimal; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.request.context.predicate.InPredicate; +import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.data.MultiValueVisitor; +import org.mockito.Mockito; +import org.testng.Assert; +import org.testng.annotations.Test; + + +public class InPredicateEvaluatorFactoryTest { + + MultiValueVisitor createValueLengthVisitor() { + return new MultiValueVisitor() { + @Override + public Integer visitInt(int[] value) { + return value.length; + } + + @Override + public Integer visitLong(long[] value) { + return value.length; + } + + @Override + public Integer visitFloat(float[] value) { + return value.length; + } + + @Override + public Integer visitDouble(double[] value) { + return value.length; + } + + @Override + public Integer visitBigDecimal(BigDecimal[] value) { + return value.length; + } + + @Override + public Integer visitBoolean(boolean[] value) { + return value.length; + } + + @Override + public Integer visitTimestamp(long[] value) { + return value.length; + } + + @Override + public Integer visitString(String[] value) { + return value.length; + } + + @Override + public Integer visitJson(String[] value) { + return value.length; + } + + @Override + public Integer visitBytes(byte[][] value) { + return value.length; + } + }; + } + + @Test + void canBeVisited() { + // Given a visitor + MultiValueVisitor valueLengthVisitor = Mockito.spy(createValueLengthVisitor()); + + // When int predicate is used + InPredicate predicate = new InPredicate(ExpressionContext.forIdentifier("ident"), Lists.newArrayList("1", "2")); + + InPredicateEvaluatorFactory.InRawPredicateEvaluator intEvaluator = + InPredicateEvaluatorFactory.newRawValueBasedEvaluator(predicate, FieldSpec.DataType.INT); + + // Only the int[] method is called + int length = intEvaluator.accept(valueLengthVisitor); + Assert.assertEquals(length, 2); + Mockito.verify(valueLengthVisitor).visitInt(new int[] {2, 1}); + Mockito.verifyNoMoreInteractions(valueLengthVisitor); + + // And given a string predicate + InPredicateEvaluatorFactory.InRawPredicateEvaluator strEvaluator = + InPredicateEvaluatorFactory.newRawValueBasedEvaluator(predicate, FieldSpec.DataType.STRING); + + // Only the string[] method is called + length = strEvaluator.accept(valueLengthVisitor); + Assert.assertEquals(length, 2); + Mockito.verify(valueLengthVisitor).visitString(new String[] {"2", "1"}); + Mockito.verifyNoMoreInteractions(valueLengthVisitor); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/predicate/NoDictionaryEqualsPredicateEvaluatorsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/predicate/NoDictionaryEqualsPredicateEvaluatorsTest.java index d2f1eb4a3a86..8168a8686bad 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/predicate/NoDictionaryEqualsPredicateEvaluatorsTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/predicate/NoDictionaryEqualsPredicateEvaluatorsTest.java @@ -20,8 +20,8 @@ import java.math.BigDecimal; import java.util.Random; -import org.apache.commons.lang.ArrayUtils; -import org.apache.commons.lang.RandomStringUtils; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.RandomStringUtils; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.predicate.EqPredicate; import org.apache.pinot.common.request.context.predicate.NotEqPredicate; @@ -248,6 +248,46 @@ public void testStringPredicateEvaluators() { } } + @Test + public void testJsonPredicateEvaluators() { + String jsonStringTemplate = "{\"id\": %s, \"name\": %s}"; + String jsonString = String.format(jsonStringTemplate, + RandomStringUtils.randomAlphanumeric(MAX_STRING_LENGTH), + RandomStringUtils.randomAlphanumeric(MAX_STRING_LENGTH)); + + EqPredicate eqPredicate = new EqPredicate(COLUMN_EXPRESSION, jsonString); + PredicateEvaluator eqPredicateEvaluator = + EqualsPredicateEvaluatorFactory.newRawValueBasedEvaluator(eqPredicate, FieldSpec.DataType.JSON); + + NotEqPredicate notEqPredicate = new NotEqPredicate(COLUMN_EXPRESSION, jsonString); + PredicateEvaluator neqPredicateEvaluator = + NotEqualsPredicateEvaluatorFactory.newRawValueBasedEvaluator(notEqPredicate, FieldSpec.DataType.JSON); + + Assert.assertTrue(eqPredicateEvaluator.applySV(jsonString)); + Assert.assertFalse(neqPredicateEvaluator.applySV(jsonString)); + + String[] randomJsonStrings = new String[NUM_MULTI_VALUES]; + PredicateEvaluatorTestUtils.fillRandomJson(randomJsonStrings, jsonStringTemplate, 2, MAX_STRING_LENGTH); + randomJsonStrings[_random.nextInt(NUM_MULTI_VALUES)] = jsonString; + + Assert.assertTrue(eqPredicateEvaluator.applyMV(randomJsonStrings, NUM_MULTI_VALUES)); + Assert.assertFalse(neqPredicateEvaluator.applyMV(randomJsonStrings, NUM_MULTI_VALUES)); + + for (int i = 0; i < 100; i++) { + String randomJson = String.format(jsonStringTemplate, + RandomStringUtils.randomAlphanumeric(MAX_STRING_LENGTH), + RandomStringUtils.randomAlphanumeric(MAX_STRING_LENGTH)); + Assert.assertEquals(eqPredicateEvaluator.applySV(randomJson), (randomJson.equals(jsonString))); + Assert.assertEquals(neqPredicateEvaluator.applySV(randomJson), (!randomJson.equals(jsonString))); + + PredicateEvaluatorTestUtils.fillRandomJson(randomJsonStrings, jsonStringTemplate, 2, MAX_STRING_LENGTH); + Assert.assertEquals(eqPredicateEvaluator.applyMV(randomJsonStrings, NUM_MULTI_VALUES), + ArrayUtils.contains(randomJsonStrings, jsonString)); + Assert.assertEquals(neqPredicateEvaluator.applyMV(randomJsonStrings, NUM_MULTI_VALUES), + !ArrayUtils.contains(randomJsonStrings, jsonString)); + } + } + @Test public void testBytesPredicateEvaluators() { byte[] bytesValue = RandomStringUtils.random(MAX_STRING_LENGTH).getBytes(); diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/predicate/NoDictionaryInPredicateEvaluatorTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/predicate/NoDictionaryInPredicateEvaluatorTest.java index 080ef5826caf..ebdbf57bd577 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/predicate/NoDictionaryInPredicateEvaluatorTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/predicate/NoDictionaryInPredicateEvaluatorTest.java @@ -33,7 +33,7 @@ import java.util.Random; import java.util.Set; import java.util.TreeSet; -import org.apache.commons.lang.RandomStringUtils; +import org.apache.commons.lang3.RandomStringUtils; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.predicate.InPredicate; import org.apache.pinot.common.request.context.predicate.NotInPredicate; @@ -283,6 +283,50 @@ public void testStringPredicateEvaluators() { Assert.assertFalse(notInPredicateEvaluator.applyMV(multiValues, NUM_MULTI_VALUES)); } + @Test + public void testJsonPredicateEvaluators() { + String jsonStringTemplate = "{\"id\": %s, \"name\": %s}"; + + List jsonValues = new ArrayList<>(NUM_PREDICATE_VALUES); + Set jsonValueSet = new HashSet<>(); + + for (int i = 0; i < 100; i++) { + String jsonString = String.format(jsonStringTemplate, + RandomStringUtils.randomAlphanumeric(MAX_STRING_LENGTH), + RandomStringUtils.randomAlphanumeric(MAX_STRING_LENGTH)); + jsonValues.add(jsonString); + jsonValueSet.add(jsonString); + } + + InPredicate inPredicate = new InPredicate(COLUMN_EXPRESSION, jsonValues); + PredicateEvaluator inPredicateEvaluator = + InPredicateEvaluatorFactory.newRawValueBasedEvaluator(inPredicate, FieldSpec.DataType.JSON); + + NotInPredicate notInPredicate = new NotInPredicate(COLUMN_EXPRESSION, jsonValues); + PredicateEvaluator notInPredicateEvaluator = + NotInPredicateEvaluatorFactory.newRawValueBasedEvaluator(notInPredicate, FieldSpec.DataType.JSON); + + for (String value : jsonValueSet) { + Assert.assertTrue(inPredicateEvaluator.applySV(value)); + Assert.assertFalse(notInPredicateEvaluator.applySV(value)); + } + + for (int i = 0; i < 100; i++) { + String randomJsonValue = String.format(jsonStringTemplate, + RandomStringUtils.randomAlphanumeric(MAX_STRING_LENGTH), + RandomStringUtils.randomAlphanumeric(MAX_STRING_LENGTH)); + Assert.assertEquals(inPredicateEvaluator.applySV(randomJsonValue), jsonValueSet.contains(randomJsonValue)); + Assert.assertEquals(notInPredicateEvaluator.applySV(randomJsonValue), !jsonValueSet.contains(randomJsonValue)); + } + + String[] multiValues = new String[NUM_MULTI_VALUES]; + PredicateEvaluatorTestUtils.fillRandomJson(multiValues, jsonStringTemplate, 2, MAX_STRING_LENGTH); + multiValues[_random.nextInt(NUM_MULTI_VALUES)] = jsonValues.get(_random.nextInt(NUM_PREDICATE_VALUES)); + + Assert.assertTrue(inPredicateEvaluator.applyMV(multiValues, NUM_MULTI_VALUES)); + Assert.assertFalse(notInPredicateEvaluator.applyMV(multiValues, NUM_MULTI_VALUES)); + } + @Test public void testBytesPredicateEvaluators() { List stringValues = new ArrayList<>(NUM_PREDICATE_VALUES); diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/predicate/PredicateEvaluatorTestUtils.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/predicate/PredicateEvaluatorTestUtils.java index 4e949c9393c4..9d9e3cfc5867 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/predicate/PredicateEvaluatorTestUtils.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/predicate/PredicateEvaluatorTestUtils.java @@ -20,7 +20,7 @@ import java.math.BigDecimal; import java.util.Random; -import org.apache.commons.lang.RandomStringUtils; +import org.apache.commons.lang3.RandomStringUtils; public class PredicateEvaluatorTestUtils { @@ -70,4 +70,15 @@ public static void fillRandom(byte[][] randomValues, int maxStringLength) { randomValues[i] = RandomStringUtils.random(maxStringLength).getBytes(); } } + + public static void fillRandomJson(String[] randomValues, String jsonStringTemplate, int numPlaceholders, + int maxStringLength) { + for (int i = 0; i < randomValues.length; i++) { + Object[] randomPlaceholderValues = new String[numPlaceholders]; + for (int j = 0; j < numPlaceholders; j++) { + randomPlaceholderValues[j] = RandomStringUtils.randomAlphanumeric(maxStringLength); + } + randomValues[i] = String.format(jsonStringTemplate, randomPlaceholderValues); + } + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/predicate/RangeOfflineDictionaryPredicateEvaluatorTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/predicate/RangeOfflineDictionaryPredicateEvaluatorTest.java index bb5e6e910094..e1c8a501cecf 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/predicate/RangeOfflineDictionaryPredicateEvaluatorTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/filter/predicate/RangeOfflineDictionaryPredicateEvaluatorTest.java @@ -270,6 +270,6 @@ private RangePredicate createPredicate(int lower, boolean inclLower, int upper, if (upper == DICT_LEN - 1 && inclUpper) { upperStr = "*"; } - return new RangePredicate(COLUMN_EXPRESSION, inclLower, lowerStr, inclUpper, upperStr); + return new RangePredicate(COLUMN_EXPRESSION, inclLower, lowerStr, inclUpper, upperStr, DataType.STRING); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/query/LinearSelectionOrderByOperatorTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/query/LinearSelectionOrderByOperatorTest.java index b7e2a2b6deba..b7ed828aa2fb 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/query/LinearSelectionOrderByOperatorTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/query/LinearSelectionOrderByOperatorTest.java @@ -19,9 +19,7 @@ package org.apache.pinot.core.operator.query; import java.util.Comparator; -import java.util.HashSet; import java.util.List; -import java.util.Set; import org.apache.pinot.core.operator.query.LinearSelectionOrderByOperator.PartiallySortedListBuilder; import org.apache.pinot.core.operator.query.LinearSelectionOrderByOperator.TotallySortedListBuilder; import org.testng.annotations.Test; @@ -67,7 +65,7 @@ public void testTotallySortedListBuilder() { public void testPartiallySortedListBuilder() { int maxNumRows = 10; Comparator partitionComparator = Comparator.comparingInt(row -> (Integer) row[0]); - Comparator unsortedComparator = (row1, row2) -> Integer.compare((Integer) row2[1], (Integer) row1[1]); + Comparator unsortedComparator = Comparator.comparingInt(row -> (Integer) row[1]); // Enough rows collected without tie rows PartiallySortedListBuilder listBuilder = @@ -81,7 +79,7 @@ public void testPartiallySortedListBuilder() { List rows = listBuilder.build(); assertEquals(rows.size(), maxNumRows); for (int i = 0; i < maxNumRows; i++) { - assertEquals(rows.get(i), new Object[]{i / 2, maxNumRows - i}); + assertEquals(rows.get(i), new Object[]{i / 2, i % 2 == 0 ? maxNumRows - i - 1 : maxNumRows - i + 1}); } // Enough rows collected with tie rows @@ -98,19 +96,13 @@ public void testPartiallySortedListBuilder() { rows = listBuilder.build(); assertEquals(rows.size(), maxNumRows); // For the last partition, should contain unsorted value 0 and 1 - Set unsortedValues = new HashSet<>(); for (int i = 0; i < maxNumRows; i++) { if (i / 2 != lastPartitionValue) { - assertEquals(rows.get(i), new Object[]{i / 2, maxNumRows - i}); + assertEquals(rows.get(i), new Object[]{i / 2, i % 2 == 0 ? maxNumRows - i - 1 : maxNumRows - i + 1}); } else { - Object[] row = rows.get(i); - assertEquals(row[0], lastPartitionValue); - int unsortedValue = (int) row[1]; - assertTrue(unsortedValue == 0 || unsortedValue == 1); - unsortedValues.add(unsortedValue); + assertEquals(rows.get(i), new Object[]{lastPartitionValue, i % 2}); } } - assertEquals(unsortedValues.size(), 2); // Not enough rows collected with tie rows listBuilder = new PartiallySortedListBuilder(maxNumRows, partitionComparator, unsortedComparator); @@ -125,18 +117,12 @@ public void testPartiallySortedListBuilder() { rows = listBuilder.build(); assertEquals(rows.size(), maxNumRows); // For the last partition, should contain unsorted value 0 and 1 - unsortedValues = new HashSet<>(); for (int i = 0; i < maxNumRows; i++) { if (i / 2 != lastPartitionValue) { - assertEquals(rows.get(i), new Object[]{i / 2, maxNumRows - i}); + assertEquals(rows.get(i), new Object[]{i / 2, i % 2 == 0 ? maxNumRows - i - 1 : maxNumRows - i + 1}); } else { - Object[] row = rows.get(i); - assertEquals(row[0], lastPartitionValue); - int unsortedValue = (int) row[1]; - assertTrue(unsortedValue == 0 || unsortedValue == 1); - unsortedValues.add(unsortedValue); + assertEquals(rows.get(i), new Object[]{lastPartitionValue, i % 2}); } } - assertEquals(unsortedValues.size(), 2); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/query/SelectionOrderByOperatorTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/query/SelectionOrderByOperatorTest.java new file mode 100644 index 000000000000..a99c0412a3b2 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/query/SelectionOrderByOperatorTest.java @@ -0,0 +1,179 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.query; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import org.apache.commons.io.FileUtils; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.operator.BaseProjectOperator; +import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; +import org.apache.pinot.core.plan.DocIdSetPlanNode; +import org.apache.pinot.core.plan.ProjectPlanNode; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.query.request.context.utils.QueryContextConverterUtils; +import org.apache.pinot.core.query.selection.SelectionOperatorUtils; +import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; +import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; +import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; +import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.spi.data.readers.GenericRow; +import org.apache.pinot.spi.utils.ReadMode; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; + + +public class SelectionOrderByOperatorTest { + private static File _tempDir; + private static final String RAW_TABLE_NAME = "testTable"; + private static final String SEGMENT_NAME = "testSegment"; + + private static final String COL_1 = "col1"; + private static final String COL_2 = "col2"; + private static final TableConfig TABLE_CONFIG = new TableConfigBuilder(TableType.OFFLINE) + .setTableName(RAW_TABLE_NAME) + .setSortedColumn(COL_1) + .build(); + private static final Schema SCHEMA = new Schema.SchemaBuilder() + .addSingleValueDimension(COL_1, FieldSpec.DataType.INT) + .addSingleValueDimension(COL_2, FieldSpec.DataType.INT) + .build(); + private IndexSegment _segmentWithNullValues; + + @BeforeClass + public void setUp() + throws Exception { + _tempDir = Files.createTempDirectory("SelectionOrderByOperatorTest").toFile(); + _segmentWithNullValues = createOfflineSegmentWithNullValue(); + } + + @Test + public void testTotalSortNullWithNullHandling() { + QueryContext queryContext = QueryContextConverterUtils.getQueryContext( + "SELECT col1, col2 " + + "FROM testTable " + + "ORDER BY col1, col2 " + + "LIMIT 1"); + queryContext.setNullHandlingEnabled(true); + List rows = executeQuery(queryContext); + assertNull(rows.get(0)[0], "Column 'col1' value should be 'null' when null handling is enabled"); + assertNull(rows.get(0)[1], "Column 'col2' value should be 'null' when null handling is enabled"); + } + + @Test + public void testTotalSortNullWithoutNullHandling() { + QueryContext queryContext = QueryContextConverterUtils.getQueryContext( + "SELECT col1, col2 " + + "FROM testTable " + + "ORDER BY col1, col2 " + + "LIMIT 1"); + queryContext.setNullHandlingEnabled(false); + List rows = executeQuery(queryContext); + assertNotNull(rows.get(0)[0], "Column 'col1' value should not be 'null' when null handling is disabled"); + assertNotNull(rows.get(0)[1], "Column 'col2' value should not be 'null' when null handling is disabled"); + } + + @Test + public void testPartialSortNullWithNullHandling() { + QueryContext queryContext = QueryContextConverterUtils.getQueryContext( + "SELECT col1, col2 " + + "FROM testTable " + + "ORDER BY col1 " + + "LIMIT 1"); + queryContext.setNullHandlingEnabled(true); + List rows = executeQuery(queryContext); + assertNull(rows.get(0)[0], "Column 'col1' value should be 'null' when null handling is enabled"); + assertNull(rows.get(0)[1], "Column 'col2' value should be 'null' when null handling is enabled"); + } + + @Test + public void testPartialSortNullWithoutNullHandling() { + QueryContext queryContext = QueryContextConverterUtils.getQueryContext( + "SELECT col1, col2 " + + "FROM testTable " + + "ORDER BY col1 " + + "LIMIT 1"); + queryContext.setNullHandlingEnabled(false); + List rows = executeQuery(queryContext); + assertNotNull(rows.get(0)[0], "Column 'col1' value should not be 'null' when null handling is disabled"); + assertNotNull(rows.get(0)[1], "Column 'col2' value should not be 'null' when null handling is disabled"); + } + + private List executeQuery(QueryContext queryContext) { + List expressions = + SelectionOperatorUtils.extractExpressions(queryContext, _segmentWithNullValues); + BaseProjectOperator projectOperator = + new ProjectPlanNode(new SegmentContext(_segmentWithNullValues), queryContext, expressions, + DocIdSetPlanNode.MAX_DOC_PER_CALL).run(); + + SelectionOrderByOperator operator = new SelectionOrderByOperator( + _segmentWithNullValues, + queryContext, + expressions, + projectOperator + ); + SelectionResultsBlock block = operator.getNextBlock(); + return block.getRows(); + } + + private IndexSegment createOfflineSegmentWithNullValue() + throws Exception { + + List records = new ArrayList<>(); + + // Add one row with null value + GenericRow record = new GenericRow(); + record.addNullValueField(COL_1); + record.addNullValueField(COL_2); + records.add(record); + + SegmentGeneratorConfig segmentGeneratorConfig = new SegmentGeneratorConfig(TABLE_CONFIG, SCHEMA); + segmentGeneratorConfig.setTableName(RAW_TABLE_NAME); + segmentGeneratorConfig.setSegmentName(SEGMENT_NAME); + segmentGeneratorConfig.setNullHandlingEnabled(true); + segmentGeneratorConfig.setOutDir(_tempDir.getPath()); + + SegmentIndexCreationDriverImpl driver = new SegmentIndexCreationDriverImpl(); + driver.init(segmentGeneratorConfig, new GenericRowRecordReader(records)); + driver.build(); + + return ImmutableSegmentLoader.load(new File(_tempDir, SEGMENT_NAME), ReadMode.mmap); + } + + @AfterClass + public void tearDown() + throws IOException { + _segmentWithNullValues.destroy(); + FileUtils.deleteDirectory(_tempDir); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/streaming/StreamingSelectionOnlyOperatorTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/streaming/StreamingSelectionOnlyOperatorTest.java new file mode 100644 index 000000000000..d21978b1ee4e --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/streaming/StreamingSelectionOnlyOperatorTest.java @@ -0,0 +1,124 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.streaming; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import org.apache.commons.io.FileUtils; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.operator.BaseProjectOperator; +import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; +import org.apache.pinot.core.plan.DocIdSetPlanNode; +import org.apache.pinot.core.plan.ProjectPlanNode; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.query.request.context.utils.QueryContextConverterUtils; +import org.apache.pinot.core.query.selection.SelectionOperatorUtils; +import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; +import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; +import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; +import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.spi.data.readers.GenericRow; +import org.apache.pinot.spi.utils.ReadMode; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertNull; + + +public class StreamingSelectionOnlyOperatorTest { + private static final File TEMP_DIR = new File(FileUtils.getTempDirectory(), "StreamingSelectionOperatorTest"); + private static final String RAW_TABLE_NAME = "testTable"; + private static final String SEGMENT_NAME = "testSegment"; + + private static final String INT_COLUMN = "intColumn"; + private static final TableConfig TABLE_CONFIG = + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + private static final Schema SCHEMA = + new Schema.SchemaBuilder().addSingleValueDimension(INT_COLUMN, FieldSpec.DataType.INT).build(); + private IndexSegment _segmentWithNullValues; + + @BeforeClass + public void setUp() + throws Exception { + FileUtils.deleteDirectory(TEMP_DIR); + _segmentWithNullValues = createOfflineSegmentWithNullValue(); + } + + @Test + public void testNullHandling() { + QueryContext queryContext = + QueryContextConverterUtils.getQueryContext("SELECT * FROM testTable WHERE intColumn IS NULL"); + queryContext.setNullHandlingEnabled(true); + List expressions = + SelectionOperatorUtils.extractExpressions(queryContext, _segmentWithNullValues); + BaseProjectOperator projectOperator = + new ProjectPlanNode(new SegmentContext(_segmentWithNullValues), queryContext, expressions, + DocIdSetPlanNode.MAX_DOC_PER_CALL).run(); + + StreamingSelectionOnlyOperator operator = new StreamingSelectionOnlyOperator( + _segmentWithNullValues, + queryContext, + expressions, + projectOperator + ); + SelectionResultsBlock block = operator.getNextBlock(); + List rows = block.getRows(); + assertNull(rows.get(0)[0], "Column value should be 'null' when null handling is enabled"); + } + + private IndexSegment createOfflineSegmentWithNullValue() + throws Exception { + + List records = new ArrayList<>(); + + // Add one row with null value + GenericRow record = new GenericRow(); + record.addNullValueField(INT_COLUMN); + records.add(record); + + SegmentGeneratorConfig segmentGeneratorConfig = new SegmentGeneratorConfig(TABLE_CONFIG, SCHEMA); + segmentGeneratorConfig.setTableName(RAW_TABLE_NAME); + segmentGeneratorConfig.setSegmentName(SEGMENT_NAME); + segmentGeneratorConfig.setNullHandlingEnabled(true); + segmentGeneratorConfig.setOutDir(TEMP_DIR.getPath()); + + SegmentIndexCreationDriverImpl driver = new SegmentIndexCreationDriverImpl(); + driver.init(segmentGeneratorConfig, new GenericRowRecordReader(records)); + driver.build(); + + return ImmutableSegmentLoader.load(new File(TEMP_DIR, SEGMENT_NAME), ReadMode.mmap); + } + + @AfterClass + public void tearDown() + throws IOException { + _segmentWithNullValues.destroy(); + FileUtils.deleteDirectory(TEMP_DIR); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/AdditionTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/AdditionTransformFunctionTest.java index 744aa1cec4fe..f0142d917d99 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/AdditionTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/AdditionTransformFunctionTest.java @@ -22,6 +22,7 @@ import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.RequestContextUtils; import org.apache.pinot.spi.exception.BadQueryRequestException; +import org.roaringbitmap.RoaringBitmap; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -68,8 +69,8 @@ public void testAdditionTransformFunction() { testTransformFunction(transformFunction, expectedBigDecimalValues); expression = RequestContextUtils.getExpression( - String.format("add(add(12,%s),%s,add(add(%s,%s),'12110.34556677889901122335678',%s),%s)", STRING_SV_COLUMN, - DOUBLE_SV_COLUMN, FLOAT_SV_COLUMN, LONG_SV_COLUMN, INT_SV_COLUMN, BIG_DECIMAL_SV_COLUMN)); + String.format("add(add(12,%s),%s,add(add(%s,%s),cast('12110.34556677889901122335678' as decimal),%s),%s)", + STRING_SV_COLUMN, DOUBLE_SV_COLUMN, FLOAT_SV_COLUMN, LONG_SV_COLUMN, INT_SV_COLUMN, BIG_DECIMAL_SV_COLUMN)); transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); Assert.assertTrue(transformFunction instanceof AdditionTransformFunction); BigDecimal val4 = new BigDecimal("12110.34556677889901122335678"); @@ -99,4 +100,39 @@ public Object[][] testIllegalArguments() { } }; } + + @Test + public void testAdditionNullLiteral() { + ExpressionContext expression = RequestContextUtils.getExpression(String.format("add(%s,null)", INT_SV_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof AdditionTransformFunction); + Assert.assertEquals(transformFunction.getName(), AdditionTransformFunction.FUNCTION_NAME); + double[] expectedValues = new double[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = _intSVValues[i]; + } + RoaringBitmap roaringBitmap = new RoaringBitmap(); + roaringBitmap.add(0L, NUM_ROWS); + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } + + @Test + public void testAdditionNullColumn() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("add(%s,%s)", INT_SV_COLUMN, INT_SV_NULL_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof AdditionTransformFunction); + Assert.assertEquals(transformFunction.getName(), AdditionTransformFunction.FUNCTION_NAME); + double[] expectedValues = new double[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + expectedValues[i] = (double) Integer.MIN_VALUE + (double) _intSVValues[i]; + roaringBitmap.add(i); + } else { + expectedValues[i] = (double) _intSVValues[i] * 2; + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/AndOperatorTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/AndOperatorTransformFunctionTest.java index a0aece3a1f86..17057f5b1db3 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/AndOperatorTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/AndOperatorTransformFunctionTest.java @@ -19,6 +19,11 @@ package org.apache.pinot.core.operator.transform.function; import org.apache.pinot.common.function.TransformFunctionType; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.request.context.RequestContextUtils; +import org.roaringbitmap.RoaringBitmap; +import org.testng.Assert; +import org.testng.annotations.Test; public class AndOperatorTransformFunctionTest extends LogicalOperatorTransformFunctionTest { @@ -32,4 +37,43 @@ boolean getExpectedValue(boolean left, boolean right) { String getFunctionName() { return TransformFunctionType.AND.getName(); } + + @Test + public void testAndNullLiteral() { + ExpressionContext expression = RequestContextUtils.getExpression(String.format("and(%s,null)", INT_SV_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof AndOperatorTransformFunction); + Assert.assertEquals(transformFunction.getName(), TransformFunctionType.AND.getName()); + int[] expectedValues = new int[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (_intSVValues[i] == 0) { + expectedValues[i] = 0; + } else { + roaringBitmap.add(i); + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } + + @Test + public void testAndNullColumn() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("and(%s,%s)", INT_SV_COLUMN, INT_SV_NULL_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof AndOperatorTransformFunction); + Assert.assertEquals(transformFunction.getName(), TransformFunctionType.AND.getName()); + int[] expectedValues = new int[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (_intSVValues[i] == 0) { + expectedValues[i] = 0; + } else if (isNullRow(i)) { + roaringBitmap.add(i); + } else { + expectedValues[i] = 1; + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ArrayBaseTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ArrayBaseTransformFunctionTest.java index 870861c884dd..bf5a63dd61ed 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ArrayBaseTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ArrayBaseTransformFunctionTest.java @@ -22,6 +22,7 @@ import org.apache.pinot.common.request.context.RequestContextUtils; import org.apache.pinot.spi.data.FieldSpec; import org.apache.pinot.spi.exception.BadQueryRequestException; +import org.roaringbitmap.RoaringBitmap; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -34,7 +35,7 @@ public void testArrayTransformFunction() { ExpressionContext expression = RequestContextUtils.getExpression(String.format("%s(%s)", getFunctionName(), INT_MV_COLUMN)); TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); - Assert.assertEquals(transformFunction.getClass().getName(), getArrayFunctionClass().getName()); + Assert.assertEquals(transformFunction.getClass(), getArrayFunctionClass()); Assert.assertEquals(transformFunction.getName(), getFunctionName()); Assert.assertEquals(transformFunction.getResultMetadata().getDataType(), getResultDataType(FieldSpec.DataType.INT)); Assert.assertTrue(transformFunction.getResultMetadata().isSingleValue()); @@ -76,6 +77,71 @@ public void testArrayTransformFunction() { } } + @Test + public void testArrayNullColumn() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("%s(%s)", getFunctionName(), INT_MV_NULL_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertEquals(transformFunction.getClass(), getArrayFunctionClass()); + Assert.assertEquals(transformFunction.getName(), getFunctionName()); + Assert.assertEquals(transformFunction.getResultMetadata().getDataType(), getResultDataType(FieldSpec.DataType.INT)); + Assert.assertTrue(transformFunction.getResultMetadata().isSingleValue()); + Assert.assertFalse(transformFunction.getResultMetadata().hasDictionary()); + + RoaringBitmap expectedNulls = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + expectedNulls.add(i); + } + } + testNullBitmap(transformFunction, expectedNulls); + + switch (getResultDataType(FieldSpec.DataType.INT)) { + case INT: + int[] intValues = transformFunction.transformToIntValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + if (!isNullRow(i)) { + Assert.assertEquals(intValues[i], getExpectResult(_intMVValues[i])); + } + } + break; + case LONG: + long[] longValues = transformFunction.transformToLongValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + if (!isNullRow(i)) { + Assert.assertEquals(longValues[i], getExpectResult(_intMVValues[i])); + } + } + break; + case FLOAT: + float[] floatValues = transformFunction.transformToFloatValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + if (!isNullRow(i)) { + Assert.assertEquals(floatValues[i], getExpectResult(_intMVValues[i])); + } + } + break; + case DOUBLE: + double[] doubleValues = transformFunction.transformToDoubleValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + if (!isNullRow(i)) { + Assert.assertEquals(doubleValues[i], getExpectResult(_intMVValues[i])); + } + } + break; + case STRING: + String[] stringValues = transformFunction.transformToStringValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + if (!isNullRow(i)) { + Assert.assertEquals(stringValues[i], getExpectResult(_intMVValues[i])); + } + } + break; + default: + break; + } + } + @Test(dataProvider = "testIllegalArguments", expectedExceptions = {BadQueryRequestException.class}) public void testIllegalArguments(String expressionStr) { ExpressionContext expression = RequestContextUtils.getExpression(expressionStr); diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ArrayLiteralTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ArrayLiteralTransformFunctionTest.java new file mode 100644 index 000000000000..2bbdab74e0d1 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ArrayLiteralTransformFunctionTest.java @@ -0,0 +1,166 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.transform.function; + +import java.util.ArrayList; +import java.util.List; +import org.apache.pinot.common.request.Literal; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.mockito.Mockito.when; + + +public class ArrayLiteralTransformFunctionTest { + private static final int NUM_DOCS = 100; + private AutoCloseable _mocks; + + @Mock + private ProjectionBlock _projectionBlock; + + @BeforeMethod + public void setUp() { + _mocks = MockitoAnnotations.openMocks(this); + when(_projectionBlock.getNumDocs()).thenReturn(NUM_DOCS); + } + + @AfterMethod + public void tearDown() + throws Exception { + _mocks.close(); + } + + @Test + public void testIntArrayLiteralTransformFunction() { + List arrayExpressions = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + arrayExpressions.add(ExpressionContext.forLiteral(DataType.INT, i)); + } + + ArrayLiteralTransformFunction intArray = new ArrayLiteralTransformFunction(arrayExpressions); + Assert.assertEquals(intArray.getResultMetadata().getDataType(), DataType.INT); + Assert.assertEquals(intArray.getIntArrayLiteral(), new int[]{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + }); + } + + @Test + public void testLongArrayLiteralTransformFunction() { + List arrayExpressions = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + arrayExpressions.add(ExpressionContext.forLiteral(DataType.LONG, (long) i)); + } + + ArrayLiteralTransformFunction longArray = new ArrayLiteralTransformFunction(arrayExpressions); + Assert.assertEquals(longArray.getResultMetadata().getDataType(), DataType.LONG); + Assert.assertEquals(longArray.getLongArrayLiteral(), new long[]{ + 0L, 1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L, 9L + }); + } + + @Test + public void testFloatArrayLiteralTransformFunction() { + List arrayExpressions = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + arrayExpressions.add(ExpressionContext.forLiteral(DataType.FLOAT, (float) i)); + } + + ArrayLiteralTransformFunction floatArray = new ArrayLiteralTransformFunction(arrayExpressions); + Assert.assertEquals(floatArray.getResultMetadata().getDataType(), DataType.FLOAT); + Assert.assertEquals(floatArray.getFloatArrayLiteral(), new float[]{ + 0f, 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f, 9f + }); + } + + @Test + public void testDoubleArrayLiteralTransformFunction() { + List arrayExpressions = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + arrayExpressions.add(ExpressionContext.forLiteral(DataType.DOUBLE, (double) i)); + } + + ArrayLiteralTransformFunction doubleArray = new ArrayLiteralTransformFunction(arrayExpressions); + Assert.assertEquals(doubleArray.getResultMetadata().getDataType(), DataType.DOUBLE); + Assert.assertEquals(doubleArray.getDoubleArrayLiteral(), new double[]{ + 0d, 1d, 2d, 3d, 4d, 5d, 6d, 7d, 8d, 9d + }); + } + + @Test + public void testStringArrayLiteralTransformFunction() { + List arrayExpressions = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + arrayExpressions.add(ExpressionContext.forLiteral(new Literal(Literal._Fields.STRING_VALUE, String.valueOf(i)))); + } + + ArrayLiteralTransformFunction stringArray = new ArrayLiteralTransformFunction(arrayExpressions); + Assert.assertEquals(stringArray.getResultMetadata().getDataType(), DataType.STRING); + Assert.assertEquals(stringArray.getStringArrayLiteral(), new String[]{ + "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" + }); + } + + @Test + public void testEmptyArrayTransform() { + List arrayExpressions = new ArrayList<>(); + ArrayLiteralTransformFunction emptyLiteral = new ArrayLiteralTransformFunction(arrayExpressions); + Assert.assertEquals(emptyLiteral.getIntArrayLiteral(), new int[0]); + Assert.assertEquals(emptyLiteral.getLongArrayLiteral(), new long[0]); + Assert.assertEquals(emptyLiteral.getFloatArrayLiteral(), new float[0]); + Assert.assertEquals(emptyLiteral.getDoubleArrayLiteral(), new double[0]); + Assert.assertEquals(emptyLiteral.getStringArrayLiteral(), new String[0]); + + int[][] ints = emptyLiteral.transformToIntValuesMV(_projectionBlock); + Assert.assertEquals(ints.length, NUM_DOCS); + for (int i = 0; i < NUM_DOCS; i++) { + Assert.assertEquals(ints[i].length, 0); + } + + long[][] longs = emptyLiteral.transformToLongValuesMV(_projectionBlock); + Assert.assertEquals(longs.length, NUM_DOCS); + for (int i = 0; i < NUM_DOCS; i++) { + Assert.assertEquals(longs[i].length, 0); + } + + float[][] floats = emptyLiteral.transformToFloatValuesMV(_projectionBlock); + Assert.assertEquals(floats.length, NUM_DOCS); + for (int i = 0; i < NUM_DOCS; i++) { + Assert.assertEquals(floats[i].length, 0); + } + + double[][] doubles = emptyLiteral.transformToDoubleValuesMV(_projectionBlock); + Assert.assertEquals(doubles.length, NUM_DOCS); + for (int i = 0; i < NUM_DOCS; i++) { + Assert.assertEquals(doubles[i].length, 0); + } + + String[][] strings = emptyLiteral.transformToStringValuesMV(_projectionBlock); + Assert.assertEquals(strings.length, NUM_DOCS); + for (int i = 0; i < NUM_DOCS; i++) { + Assert.assertEquals(strings[i].length, 0); + } + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/BaseTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/BaseTransformFunctionTest.java index 0aa57af3c276..5451e77eb3b1 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/BaseTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/BaseTransformFunctionTest.java @@ -18,6 +18,8 @@ */ package org.apache.pinot.core.operator.transform.function; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.File; import java.math.BigDecimal; import java.sql.Timestamp; @@ -34,6 +36,7 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.RandomUtils; import org.apache.pinot.core.operator.DocIdSetOperator; import org.apache.pinot.core.operator.ProjectionOperator; import org.apache.pinot.core.operator.blocks.ProjectionBlock; @@ -45,6 +48,7 @@ import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; import org.apache.pinot.segment.spi.datasource.DataSource; +import org.apache.pinot.spi.config.table.FieldConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.data.FieldSpec; @@ -56,6 +60,7 @@ import org.apache.pinot.spi.utils.JsonUtils; import org.apache.pinot.spi.utils.ReadMode; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.roaringbitmap.RoaringBitmap; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; @@ -63,38 +68,57 @@ public abstract class BaseTransformFunctionTest { - private static final String SEGMENT_NAME = "testSegment"; - private static final String INDEX_DIR_PATH = FileUtils.getTempDirectoryPath() + File.separator + SEGMENT_NAME; - private static final Random RANDOM = new Random(); - protected static final int NUM_ROWS = 1000; protected static final int MAX_NUM_MULTI_VALUES = 5; protected static final int MAX_MULTI_VALUE = 10; + protected static final int VECTOR_DIM_SIZE = 512; protected static final String INT_SV_COLUMN = "intSV"; + // INT_SV_NULL_COLUMN's even row equals to INT_SV_COLUMN. odd row is null. + protected static final String INT_SV_NULL_COLUMN = "intSVNull"; protected static final String LONG_SV_COLUMN = "longSV"; protected static final String FLOAT_SV_COLUMN = "floatSV"; protected static final String DOUBLE_SV_COLUMN = "doubleSV"; protected static final String BIG_DECIMAL_SV_COLUMN = "bigDecimalSV"; protected static final String STRING_SV_COLUMN = "stringSV"; + protected static final String JSON_STRING_SV_COLUMN = "jsonSV"; + protected static final String STRING_SV_NULL_COLUMN = "stringSVNull"; protected static final String BYTES_SV_COLUMN = "bytesSV"; + protected static final String VECTOR_1_COLUMN = "vector1"; + protected static final String VECTOR_2_COLUMN = "vector2"; + protected static final String ZERO_VECTOR_COLUMN = "zeroVector"; protected static final String STRING_ALPHANUM_SV_COLUMN = "stringAlphaNumSV"; + protected static final String STRING_ALPHANUM_NULL_SV_COLUMN = "stringAlphaNumSVNull"; protected static final String INT_MV_COLUMN = "intMV"; + protected static final String INT_MV_NULL_COLUMN = "intMVNull"; protected static final String LONG_MV_COLUMN = "longMV"; protected static final String FLOAT_MV_COLUMN = "floatMV"; protected static final String DOUBLE_MV_COLUMN = "doubleMV"; protected static final String STRING_MV_COLUMN = "stringMV"; protected static final String STRING_ALPHANUM_MV_COLUMN = "stringAlphaNumMV"; protected static final String STRING_LONG_MV_COLUMN = "stringLongMV"; + // deterministic MV is useful for testing IndexOf and IndexOfAll + protected static final String STRING_ALPHANUM_MV_COLUMN_2 = "stringAlphaNumMV2"; protected static final String TIME_COLUMN = "timeColumn"; protected static final String TIMESTAMP_COLUMN = "timestampColumn"; + protected static final String TIMESTAMP_COLUMN_NULL = "timestampColumnNull"; + protected static final String INT_MONO_INCREASING_MV_1 = "intMonoIncreasingMV1"; + protected static final String INT_MONO_INCREASING_MV_2 = "intMonoIncreasingMV2"; + protected static final String LONG_MV_COLUMN_2 = "longMV2"; + protected static final String FLOAT_MV_COLUMN_2 = "floatMV2"; + protected static final String DOUBLE_MV_COLUMN_2 = "doubleMV2"; protected static final String JSON_COLUMN = "json"; protected static final String DEFAULT_JSON_COLUMN = "defaultJson"; + private static final String SEGMENT_NAME = "testSegment"; + private static final String INDEX_DIR_PATH = FileUtils.getTempDirectoryPath() + File.separator + SEGMENT_NAME; + private static final Random RANDOM = new Random(); protected final int[] _intSVValues = new int[NUM_ROWS]; protected final long[] _longSVValues = new long[NUM_ROWS]; protected final float[] _floatSVValues = new float[NUM_ROWS]; protected final double[] _doubleSVValues = new double[NUM_ROWS]; protected final BigDecimal[] _bigDecimalSVValues = new BigDecimal[NUM_ROWS]; protected final String[] _stringSVValues = new String[NUM_ROWS]; + protected final String[] _jsonSVValues = new String[NUM_ROWS]; + protected final String[] _jsonArrayValues = new String[NUM_ROWS]; protected final String[] _stringAlphaNumericSVValues = new String[NUM_ROWS]; protected final byte[][] _bytesSVValues = new byte[NUM_ROWS][]; protected final int[][] _intMVValues = new int[NUM_ROWS][]; @@ -103,9 +127,17 @@ public abstract class BaseTransformFunctionTest { protected final double[][] _doubleMVValues = new double[NUM_ROWS][]; protected final String[][] _stringMVValues = new String[NUM_ROWS][]; protected final String[][] _stringAlphaNumericMVValues = new String[NUM_ROWS][]; + protected final String[][] _stringAlphaNumericMV2Values = new String[NUM_ROWS][]; protected final String[][] _stringLongFormatMVValues = new String[NUM_ROWS][]; protected final long[] _timeValues = new long[NUM_ROWS]; protected final String[] _jsonValues = new String[NUM_ROWS]; + protected final float[][] _vector1Values = new float[NUM_ROWS][]; + protected final float[][] _vector2Values = new float[NUM_ROWS][]; + protected final int[][] _intMonoIncreasingMV1Values = new int[NUM_ROWS][]; + protected final int[][] _intMonoIncreasingMV2Values = new int[NUM_ROWS][]; + protected final long[][] _longMV2Values = new long[NUM_ROWS][]; + protected final float[][] _floatMV2Values = new float[NUM_ROWS][]; + protected final double[][] _doubleMV2Values = new double[NUM_ROWS][]; protected Map _dataSourceMap; protected ProjectionBlock _projectionBlock; @@ -124,6 +156,17 @@ public void setUp() _doubleSVValues[i] = _intSVValues[i] * RANDOM.nextDouble(); _bigDecimalSVValues[i] = BigDecimal.valueOf(RANDOM.nextDouble()).multiply(BigDecimal.valueOf(_intSVValues[i])); _stringSVValues[i] = df.format(_intSVValues[i] * RANDOM.nextDouble()); + _jsonSVValues[i] = String.format( + "{\"intVal\":%s, \"longVal\":%s, \"floatVal\":%s, \"doubleVal\":%s, \"bigDecimalVal\":%s, " + + "\"stringVal\":\"%s\", \"arrayField\": [{\"arrIntField\": 1, \"arrStringField\": \"abc\"}, " + + "{\"arrIntField\": 2, \"arrStringField\": \"xyz\"}," + + "{\"arrIntField\": 5, \"arrStringField\": \"wxy\"}," + + "{\"arrIntField\": 0}], " + + "\"intVals\":[0,1], \"longVals\":[0,1], \"floatVals\":[0.0,1.0], \"doubleVals\":[0.0,1.0], " + + "\"bigDecimalVals\":[0.0,1.0], \"stringVals\":[\"0\",\"1\"]}", + RANDOM.nextInt(), RANDOM.nextLong(), RANDOM.nextFloat(), RANDOM.nextDouble(), + BigDecimal.valueOf(RANDOM.nextDouble()).multiply(BigDecimal.valueOf(RANDOM.nextInt())), + df.format(RANDOM.nextInt() * RANDOM.nextDouble())); _stringAlphaNumericSVValues[i] = RandomStringUtils.randomAlphanumeric(26); _bytesSVValues[i] = RandomStringUtils.randomAlphanumeric(26).getBytes(); @@ -134,7 +177,15 @@ public void setUp() _doubleMVValues[i] = new double[numValues]; _stringMVValues[i] = new String[numValues]; _stringAlphaNumericMVValues[i] = new String[numValues]; + _stringAlphaNumericMV2Values[i] = new String[numValues]; _stringLongFormatMVValues[i] = new String[numValues]; + _vector1Values[i] = new float[VECTOR_DIM_SIZE]; + _vector2Values[i] = new float[VECTOR_DIM_SIZE]; + _intMonoIncreasingMV1Values[i] = new int[numValues]; + _intMonoIncreasingMV2Values[i] = new int[numValues]; + _longMV2Values[i] = new long[numValues]; + _floatMV2Values[i] = new float[numValues]; + _doubleMV2Values[i] = new double[numValues]; for (int j = 0; j < numValues; j++) { _intMVValues[i][j] = 1 + RANDOM.nextInt(MAX_MULTI_VALUE); @@ -143,7 +194,18 @@ public void setUp() _doubleMVValues[i][j] = 1 + RANDOM.nextDouble(); _stringMVValues[i][j] = df.format(_intSVValues[i] * RANDOM.nextDouble()); _stringAlphaNumericMVValues[i][j] = RandomStringUtils.randomAlphanumeric(26); + _stringAlphaNumericMV2Values[i][j] = "a"; _stringLongFormatMVValues[i][j] = df.format(_intSVValues[i] * RANDOM.nextLong()); + _intMonoIncreasingMV1Values[i][j] = j; + _intMonoIncreasingMV2Values[i][j] = j + 1; + _longMV2Values[i][j] = 1L; + _floatMV2Values[i][j] = 1.0f; + _doubleMV2Values[i][j] = 1.0; + } + + for (int j = 0; j < VECTOR_DIM_SIZE; j++) { + _vector1Values[i][j] = Math.abs(RandomUtils.nextFloat(0.0f, 1.0f)); + _vector2Values[i][j] = Math.abs(RandomUtils.nextFloat(0.0f, 1.0f)); } // Time in the past year @@ -154,50 +216,112 @@ public void setUp() for (int i = 0; i < NUM_ROWS; i++) { Map map = new HashMap<>(); map.put(INT_SV_COLUMN, _intSVValues[i]); + if (isNullRow(i)) { + map.put(INT_SV_NULL_COLUMN, null); + } else { + map.put(INT_SV_NULL_COLUMN, _intSVValues[i]); + } map.put(LONG_SV_COLUMN, _longSVValues[i]); map.put(FLOAT_SV_COLUMN, _floatSVValues[i]); map.put(DOUBLE_SV_COLUMN, _doubleSVValues[i]); map.put(BIG_DECIMAL_SV_COLUMN, _bigDecimalSVValues[i]); map.put(STRING_SV_COLUMN, _stringSVValues[i]); + if (isNullRow(i)) { + map.put(STRING_SV_NULL_COLUMN, null); + } else { + map.put(STRING_SV_NULL_COLUMN, _stringSVValues[i]); + } map.put(STRING_ALPHANUM_SV_COLUMN, _stringAlphaNumericSVValues[i]); + if (isNullRow(i)) { + map.put(STRING_ALPHANUM_NULL_SV_COLUMN, null); + } else { + map.put(STRING_ALPHANUM_NULL_SV_COLUMN, _stringAlphaNumericSVValues[i]); + } map.put(BYTES_SV_COLUMN, _bytesSVValues[i]); + map.put(INT_MV_COLUMN, ArrayUtils.toObject(_intMVValues[i])); + if (isNullRow(i)) { + map.put(INT_MV_NULL_COLUMN, null); + } else { + map.put(INT_MV_NULL_COLUMN, ArrayUtils.toObject(_intMVValues[i])); + } map.put(LONG_MV_COLUMN, ArrayUtils.toObject(_longMVValues[i])); map.put(FLOAT_MV_COLUMN, ArrayUtils.toObject(_floatMVValues[i])); + map.put(VECTOR_1_COLUMN, ArrayUtils.toObject(_vector1Values[i])); + map.put(VECTOR_2_COLUMN, ArrayUtils.toObject(_vector2Values[i])); + map.put(ZERO_VECTOR_COLUMN, ArrayUtils.toObject(new float[VECTOR_DIM_SIZE])); map.put(DOUBLE_MV_COLUMN, ArrayUtils.toObject(_doubleMVValues[i])); map.put(STRING_MV_COLUMN, _stringMVValues[i]); map.put(STRING_ALPHANUM_MV_COLUMN, _stringAlphaNumericMVValues[i]); + map.put(STRING_ALPHANUM_MV_COLUMN_2, _stringAlphaNumericMV2Values[i]); map.put(STRING_LONG_MV_COLUMN, _stringLongFormatMVValues[i]); map.put(TIMESTAMP_COLUMN, _timeValues[i]); + if (isNullRow(i)) { + map.put(TIMESTAMP_COLUMN_NULL, null); + } else { + map.put(TIMESTAMP_COLUMN_NULL, _timeValues[i]); + } map.put(TIME_COLUMN, _timeValues[i]); _jsonValues[i] = JsonUtils.objectToJsonNode(map).toString(); map.put(JSON_COLUMN, _jsonValues[i]); + map.put(INT_MONO_INCREASING_MV_1, ArrayUtils.toObject(_intMonoIncreasingMV1Values[i])); + map.put(INT_MONO_INCREASING_MV_2, ArrayUtils.toObject(_intMonoIncreasingMV2Values[i])); + map.put(LONG_MV_COLUMN_2, ArrayUtils.toObject(_longMV2Values[i])); + map.put(FLOAT_MV_COLUMN_2, ArrayUtils.toObject(_floatMV2Values[i])); + map.put(DOUBLE_MV_COLUMN_2, ArrayUtils.toObject(_doubleMV2Values[i])); + map.put(JSON_STRING_SV_COLUMN, _jsonSVValues[i]); GenericRow row = new GenericRow(); row.init(map); rows.add(row); } Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(INT_SV_COLUMN, FieldSpec.DataType.INT) + .addSingleValueDimension(INT_SV_NULL_COLUMN, FieldSpec.DataType.INT) .addSingleValueDimension(LONG_SV_COLUMN, FieldSpec.DataType.LONG) .addSingleValueDimension(FLOAT_SV_COLUMN, FieldSpec.DataType.FLOAT) .addSingleValueDimension(DOUBLE_SV_COLUMN, FieldSpec.DataType.DOUBLE) .addMetric(BIG_DECIMAL_SV_COLUMN, FieldSpec.DataType.BIG_DECIMAL) .addSingleValueDimension(STRING_SV_COLUMN, FieldSpec.DataType.STRING) + .addSingleValueDimension(JSON_STRING_SV_COLUMN, FieldSpec.DataType.STRING, 5000, "{}") + .addSingleValueDimension(STRING_SV_NULL_COLUMN, FieldSpec.DataType.STRING) .addSingleValueDimension(STRING_ALPHANUM_SV_COLUMN, FieldSpec.DataType.STRING) + .addSingleValueDimension(STRING_ALPHANUM_NULL_SV_COLUMN, FieldSpec.DataType.STRING) .addSingleValueDimension(BYTES_SV_COLUMN, FieldSpec.DataType.BYTES) .addSingleValueDimension(JSON_COLUMN, FieldSpec.DataType.JSON) .addSingleValueDimension(DEFAULT_JSON_COLUMN, FieldSpec.DataType.JSON) .addMultiValueDimension(INT_MV_COLUMN, FieldSpec.DataType.INT) + .addMultiValueDimension(INT_MV_NULL_COLUMN, FieldSpec.DataType.INT) .addMultiValueDimension(LONG_MV_COLUMN, FieldSpec.DataType.LONG) .addMultiValueDimension(FLOAT_MV_COLUMN, FieldSpec.DataType.FLOAT) .addMultiValueDimension(DOUBLE_MV_COLUMN, FieldSpec.DataType.DOUBLE) .addMultiValueDimension(STRING_MV_COLUMN, FieldSpec.DataType.STRING) .addMultiValueDimension(STRING_ALPHANUM_MV_COLUMN, FieldSpec.DataType.STRING) + .addMultiValueDimension(STRING_ALPHANUM_MV_COLUMN_2, FieldSpec.DataType.STRING) .addMultiValueDimension(STRING_LONG_MV_COLUMN, FieldSpec.DataType.STRING) + .addMultiValueDimension(VECTOR_1_COLUMN, FieldSpec.DataType.FLOAT) + .addMultiValueDimension(VECTOR_2_COLUMN, FieldSpec.DataType.FLOAT) + .addMultiValueDimension(ZERO_VECTOR_COLUMN, FieldSpec.DataType.FLOAT) + .addMultiValueDimension(INT_MONO_INCREASING_MV_1, FieldSpec.DataType.INT) + .addMultiValueDimension(INT_MONO_INCREASING_MV_2, FieldSpec.DataType.INT) + .addMultiValueDimension(LONG_MV_COLUMN_2, FieldSpec.DataType.LONG) + .addMultiValueDimension(FLOAT_MV_COLUMN_2, FieldSpec.DataType.FLOAT) + .addMultiValueDimension(DOUBLE_MV_COLUMN_2, FieldSpec.DataType.DOUBLE) .addDateTime(TIMESTAMP_COLUMN, FieldSpec.DataType.TIMESTAMP, "1:MILLISECONDS:EPOCH", "1:MILLISECONDS") + .addDateTime(TIMESTAMP_COLUMN_NULL, FieldSpec.DataType.TIMESTAMP, "1:MILLISECONDS:EPOCH", "1:MILLISECONDS") .addTime(new TimeGranularitySpec(FieldSpec.DataType.LONG, TimeUnit.MILLISECONDS, TIME_COLUMN), null).build(); + + List fieldConfigList = new ArrayList<>(); + ObjectNode jsonIndexProps = JsonNodeFactory.instance.objectNode(); + jsonIndexProps.put("disableCrossArrayUnnest", true); + ObjectNode indexNode = JsonNodeFactory.instance.objectNode(); + indexNode.put("json", jsonIndexProps); + FieldConfig jsonFieldConfig = + new FieldConfig(JSON_STRING_SV_COLUMN, FieldConfig.EncodingType.DICTIONARY, null, null, null, null, indexNode, + null, null); + fieldConfigList.add(jsonFieldConfig); TableConfig tableConfig = - new TableConfigBuilder(TableType.OFFLINE).setTableName("test").setTimeColumnName(TIME_COLUMN).build(); + new TableConfigBuilder(TableType.OFFLINE).setTableName("test").setTimeColumnName(TIME_COLUMN) + .setFieldConfigList(fieldConfigList).setNullHandlingEnabled(true).build(); SegmentGeneratorConfig config = new SegmentGeneratorConfig(tableConfig, schema); config.setOutDir(INDEX_DIR_PATH); @@ -217,6 +341,17 @@ public void setUp() new DocIdSetOperator(new MatchAllFilterOperator(NUM_ROWS), DocIdSetPlanNode.MAX_DOC_PER_CALL)).nextBlock(); } + protected boolean isNullRow(int i) { + return i % 2 != 0; + } + + protected void testNullBitmap(TransformFunction transformFunction, RoaringBitmap expectedNull) { + RoaringBitmap nullBitmap = transformFunction.getNullBitmap(_projectionBlock); + if (nullBitmap != null && !nullBitmap.isEmpty() && expectedNull != null && !expectedNull.isEmpty()) { + assertEquals(nullBitmap, expectedNull); + } + } + protected void testTransformFunction(TransformFunction transformFunction, int[] expectedValues) { int[] intValues = transformFunction.transformToIntValuesSV(_projectionBlock); long[] longValues = transformFunction.transformToLongValuesSV(_projectionBlock); @@ -232,6 +367,30 @@ protected void testTransformFunction(TransformFunction transformFunction, int[] assertEquals(bigDecimalValues[i].intValue(), expectedValues[i]); assertEquals(stringValues[i], Integer.toString(expectedValues[i])); } + testNullBitmap(transformFunction, null); + } + + protected void testTransformFunctionWithNull(TransformFunction transformFunction, int[] expectedValues, + RoaringBitmap expectedNull) { + int[] intValues = transformFunction.transformToIntValuesSV(_projectionBlock); + long[] longValues = transformFunction.transformToLongValuesSV(_projectionBlock); + float[] floatValues = transformFunction.transformToFloatValuesSV(_projectionBlock); + double[] doubleValues = transformFunction.transformToDoubleValuesSV(_projectionBlock); + BigDecimal[] bigDecimalValues = transformFunction.transformToBigDecimalValuesSV(_projectionBlock); + String[] stringValues = transformFunction.transformToStringValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + if (expectedNull.contains(i)) { + continue; + } + // only compare the rows that are not null. + assertEquals(intValues[i], expectedValues[i]); + assertEquals(longValues[i], expectedValues[i]); + assertEquals(floatValues[i], (float) expectedValues[i]); + assertEquals(doubleValues[i], (double) expectedValues[i]); + assertEquals(bigDecimalValues[i].intValue(), expectedValues[i]); + assertEquals(stringValues[i], Integer.toString(expectedValues[i])); + } + testNullBitmap(transformFunction, expectedNull); } protected void testTransformFunction(TransformFunction transformFunction, long[] expectedValues) { @@ -249,6 +408,30 @@ protected void testTransformFunction(TransformFunction transformFunction, long[] assertEquals(bigDecimalValues[i].longValue(), expectedValues[i]); assertEquals(stringValues[i], Long.toString(expectedValues[i])); } + testNullBitmap(transformFunction, null); + } + + protected void testTransformFunctionWithNull(TransformFunction transformFunction, long[] expectedValues, + RoaringBitmap expectedNull) { + int[] intValues = transformFunction.transformToIntValuesSV(_projectionBlock); + long[] longValues = transformFunction.transformToLongValuesSV(_projectionBlock); + float[] floatValues = transformFunction.transformToFloatValuesSV(_projectionBlock); + double[] doubleValues = transformFunction.transformToDoubleValuesSV(_projectionBlock); + BigDecimal[] bigDecimalValues = transformFunction.transformToBigDecimalValuesSV(_projectionBlock); + String[] stringValues = transformFunction.transformToStringValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + if (expectedNull.contains(i)) { + continue; + } + // only compare the rows that are not null. + assertEquals(intValues[i], (int) expectedValues[i]); + assertEquals(longValues[i], expectedValues[i]); + assertEquals(floatValues[i], (float) expectedValues[i]); + assertEquals(doubleValues[i], (double) expectedValues[i]); + assertEquals(bigDecimalValues[i].longValue(), expectedValues[i]); + assertEquals(stringValues[i], Long.toString(expectedValues[i])); + } + testNullBitmap(transformFunction, expectedNull); } protected void testTransformFunction(TransformFunction transformFunction, float[] expectedValues) { @@ -266,6 +449,7 @@ protected void testTransformFunction(TransformFunction transformFunction, float[ assertEquals(bigDecimalValues[i].floatValue(), expectedValues[i]); assertEquals(stringValues[i], Float.toString(expectedValues[i])); } + testNullBitmap(transformFunction, null); } protected void testTransformFunction(TransformFunction transformFunction, double[] expectedValues) { @@ -292,6 +476,39 @@ protected void testTransformFunction(TransformFunction transformFunction, double } assertEquals(stringValues[i], Double.toString(expectedValues[i])); } + testNullBitmap(transformFunction, null); + } + + protected void testTransformFunctionWithNull(TransformFunction transformFunction, double[] expectedValues, + RoaringBitmap expectedNull) { + int[] intValues = transformFunction.transformToIntValuesSV(_projectionBlock); + long[] longValues = transformFunction.transformToLongValuesSV(_projectionBlock); + float[] floatValues = transformFunction.transformToFloatValuesSV(_projectionBlock); + double[] doubleValues = transformFunction.transformToDoubleValuesSV(_projectionBlock); + BigDecimal[] bigDecimalValues = null; + try { + // 1- Some transform functions cannot work with BigDecimal (e.g. exp, ln, and sqrt). + // 2- NumberFormatException is thrown when converting double.NaN, Double.POSITIVE_INFINITY, + // or Double.NEGATIVE_INFINITY. + bigDecimalValues = transformFunction.transformToBigDecimalValuesSV(_projectionBlock); + } catch (UnsupportedOperationException | NumberFormatException ignored) { + } + String[] stringValues = transformFunction.transformToStringValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + // only compare the results for non-null rows. + if (expectedNull.contains(i)) { + continue; + } + assertEquals(intValues[i], (int) expectedValues[i]); + assertEquals(longValues[i], (long) expectedValues[i]); + assertEquals(floatValues[i], (float) expectedValues[i]); + assertEquals(doubleValues[i], expectedValues[i]); + if (bigDecimalValues != null) { + assertEquals(bigDecimalValues[i].doubleValue(), expectedValues[i]); + } + assertEquals(stringValues[i], Double.toString(expectedValues[i])); + } + testNullBitmap(transformFunction, expectedNull); } protected void testTransformFunction(TransformFunction transformFunction, BigDecimal[] expectedValues) { @@ -311,6 +528,7 @@ protected void testTransformFunction(TransformFunction transformFunction, BigDec assertEquals((new BigDecimal(stringValues[i])).compareTo(expectedValues[i]), 0); assertEquals(BigDecimalUtils.deserialize(bytesValues[i]).compareTo(expectedValues[i]), 0); } + testNullBitmap(transformFunction, null); } protected void testTransformFunction(TransformFunction transformFunction, boolean[] expectedValues) { @@ -320,15 +538,35 @@ protected void testTransformFunction(TransformFunction transformFunction, boolea double[] doubleValues = transformFunction.transformToDoubleValuesSV(_projectionBlock); BigDecimal[] bigDecimalValues = transformFunction.transformToBigDecimalValuesSV(_projectionBlock); // TODO: Support implicit cast from BOOLEAN to STRING -// String[] stringValues = transformFunction.transformToStringValuesSV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { assertEquals(intValues[i] == 1, expectedValues[i]); assertEquals(longValues[i] == 1, expectedValues[i]); assertEquals(floatValues[i] == 1, expectedValues[i]); assertEquals(doubleValues[i] == 1, expectedValues[i]); assertEquals(bigDecimalValues[i].intValue() == 1, expectedValues[i]); -// assertEquals(stringValues[i], Boolean.toString(expectedValues[i])); } + testNullBitmap(transformFunction, null); + } + + protected void testTransformFunctionWithNull(TransformFunction transformFunction, boolean[] expectedValues, + RoaringBitmap expectedNulls) { + int[] intValues = transformFunction.transformToIntValuesSV(_projectionBlock); + long[] longValues = transformFunction.transformToLongValuesSV(_projectionBlock); + float[] floatValues = transformFunction.transformToFloatValuesSV(_projectionBlock); + double[] doubleValues = transformFunction.transformToDoubleValuesSV(_projectionBlock); + BigDecimal[] bigDecimalValues = transformFunction.transformToBigDecimalValuesSV(_projectionBlock); + + for (int i = 0; i < NUM_ROWS; i++) { + if (expectedNulls.contains(i)) { + continue; + } + assertEquals(intValues[i] == 1, expectedValues[i]); + assertEquals(longValues[i] == 1, expectedValues[i]); + assertEquals(floatValues[i] == 1, expectedValues[i]); + assertEquals(doubleValues[i] == 1, expectedValues[i]); + assertEquals(bigDecimalValues[i].intValue() == 1, expectedValues[i]); + } + testNullBitmap(transformFunction, expectedNulls); } protected void testTransformFunction(TransformFunction transformFunction, Timestamp[] expectedValues) { @@ -347,6 +585,7 @@ protected void testTransformFunction(TransformFunction transformFunction, Timest assertEquals(bigDecimalValues[i], BigDecimal.valueOf(expectedValues[i].getTime())); // assertEquals(stringValues[i], expectedValues[i].toString()); } + testNullBitmap(transformFunction, null); } protected void testTransformFunction(TransformFunction transformFunction, String[] expectedValues) { @@ -354,6 +593,19 @@ protected void testTransformFunction(TransformFunction transformFunction, String for (int i = 0; i < NUM_ROWS; i++) { assertEquals(stringValues[i], expectedValues[i]); } + testNullBitmap(transformFunction, null); + } + + protected void testTransformFunctionWithNull(TransformFunction transformFunction, String[] expectedValues, + RoaringBitmap expectedNulls) { + String[] stringValues = transformFunction.transformToStringValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + if (expectedNulls.contains(i)) { + continue; + } + assertEquals(stringValues[i], expectedValues[i]); + } + testNullBitmap(transformFunction, expectedNulls); } protected void testTransformFunction(TransformFunction transformFunction, byte[][] expectedValues) { @@ -363,15 +615,24 @@ protected void testTransformFunction(TransformFunction transformFunction, byte[] assertEquals(bytesValues[i], BytesUtils.toBytes(stringValues[i])); assertEquals(bytesValues[i], expectedValues[i]); } + testNullBitmap(transformFunction, null); } protected void testTransformFunctionMV(TransformFunction transformFunction, int[][] expectedValues) { + testTransformFunctionMVWithNull(transformFunction, expectedValues, null); + } + + protected void testTransformFunctionMVWithNull(TransformFunction transformFunction, int[][] expectedValues, + RoaringBitmap expectedNull) { int[][] intValuesMV = transformFunction.transformToIntValuesMV(_projectionBlock); long[][] longValuesMV = transformFunction.transformToLongValuesMV(_projectionBlock); float[][] floatValuesMV = transformFunction.transformToFloatValuesMV(_projectionBlock); double[][] doubleValuesMV = transformFunction.transformToDoubleValuesMV(_projectionBlock); String[][] stringValuesMV = transformFunction.transformToStringValuesMV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { + if (expectedNull != null && expectedNull.contains(i)) { + continue; + } int[] expectedValueMV = expectedValues[i]; int numValues = expectedValueMV.length; assertEquals(intValuesMV[i].length, numValues); @@ -387,15 +648,25 @@ protected void testTransformFunctionMV(TransformFunction transformFunction, int[ assertEquals(stringValuesMV[i][j], Integer.toString(expectedValues[i][j])); } } + + testNullBitmap(transformFunction, expectedNull); } protected void testTransformFunctionMV(TransformFunction transformFunction, long[][] expectedValues) { + testTransformFunctionMVWithNull(transformFunction, expectedValues, null); + } + + protected void testTransformFunctionMVWithNull(TransformFunction transformFunction, long[][] expectedValues, + RoaringBitmap expectedNull) { int[][] intValuesMV = transformFunction.transformToIntValuesMV(_projectionBlock); long[][] longValuesMV = transformFunction.transformToLongValuesMV(_projectionBlock); float[][] floatValuesMV = transformFunction.transformToFloatValuesMV(_projectionBlock); double[][] doubleValuesMV = transformFunction.transformToDoubleValuesMV(_projectionBlock); String[][] stringValuesMV = transformFunction.transformToStringValuesMV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { + if (expectedNull != null && expectedNull.contains(i)) { + continue; + } long[] expectedValueMV = expectedValues[i]; int numValues = expectedValueMV.length; assertEquals(intValuesMV[i].length, numValues); @@ -411,15 +682,24 @@ protected void testTransformFunctionMV(TransformFunction transformFunction, long assertEquals(stringValuesMV[i][j], Long.toString(expectedValues[i][j])); } } + testNullBitmap(transformFunction, expectedNull); } protected void testTransformFunctionMV(TransformFunction transformFunction, float[][] expectedValues) { + testTransformFunctionMVWithNull(transformFunction, expectedValues, null); + } + + protected void testTransformFunctionMVWithNull(TransformFunction transformFunction, float[][] expectedValues, + RoaringBitmap expectedNull) { int[][] intValuesMV = transformFunction.transformToIntValuesMV(_projectionBlock); long[][] longValuesMV = transformFunction.transformToLongValuesMV(_projectionBlock); float[][] floatValuesMV = transformFunction.transformToFloatValuesMV(_projectionBlock); double[][] doubleValuesMV = transformFunction.transformToDoubleValuesMV(_projectionBlock); String[][] stringValuesMV = transformFunction.transformToStringValuesMV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { + if (expectedNull != null && expectedNull.contains(i)) { + continue; + } float[] expectedValueMV = expectedValues[i]; int numValues = expectedValueMV.length; assertEquals(intValuesMV[i].length, numValues); @@ -435,15 +715,25 @@ protected void testTransformFunctionMV(TransformFunction transformFunction, floa assertEquals(stringValuesMV[i][j], Float.toString(expectedValues[i][j])); } } + testNullBitmap(transformFunction, null); } protected void testTransformFunctionMV(TransformFunction transformFunction, double[][] expectedValues) { + testTransformFunctionlMVWithNull(transformFunction, expectedValues, null); + } + + protected void testTransformFunctionlMVWithNull(TransformFunction transformFunction, double[][] expectedValues, + RoaringBitmap expectedNull) { int[][] intValuesMV = transformFunction.transformToIntValuesMV(_projectionBlock); long[][] longValuesMV = transformFunction.transformToLongValuesMV(_projectionBlock); float[][] floatValuesMV = transformFunction.transformToFloatValuesMV(_projectionBlock); double[][] doubleValuesMV = transformFunction.transformToDoubleValuesMV(_projectionBlock); String[][] stringValuesMV = transformFunction.transformToStringValuesMV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { + if (expectedNull != null && expectedNull.contains(i)) { + continue; + } + double[] expectedValueMV = expectedValues[i]; int numValues = expectedValueMV.length; assertEquals(intValuesMV[i].length, numValues); @@ -459,13 +749,23 @@ protected void testTransformFunctionMV(TransformFunction transformFunction, doub assertEquals(stringValuesMV[i][j], Double.toString(expectedValues[i][j])); } } + testNullBitmap(transformFunction, expectedNull); } protected void testTransformFunctionMV(TransformFunction transformFunction, String[][] expectedValues) { + testTransformFunctionMVWithNull(transformFunction, expectedValues, null); + } + + protected void testTransformFunctionMVWithNull(TransformFunction transformFunction, String[][] expectedValues, + RoaringBitmap expectedNull) { String[][] stringValuesMV = transformFunction.transformToStringValuesMV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { + if (expectedNull != null && expectedNull.contains(i)) { + continue; + } assertEquals(stringValuesMV[i], expectedValues[i]); } + testNullBitmap(transformFunction, expectedNull); } @AfterClass diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/BinaryOperatorTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/BinaryOperatorTransformFunctionTest.java index 974812d8f2ba..dcf22ecfc343 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/BinaryOperatorTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/BinaryOperatorTransformFunctionTest.java @@ -23,6 +23,7 @@ import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.exception.BadQueryRequestException; +import org.roaringbitmap.RoaringBitmap; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -118,6 +119,34 @@ public void testBinaryOperatorTransformFunction() { expectedValues[i] = getExpectedValue(Double.compare(_doubleSVValues[i], _longSVValues[0])); } testTransformFunction(transformFunction, expectedValues); + + // Test with null literal + expression = RequestContextUtils.getExpression( + String.format("%s(%s, null)", functionName, DOUBLE_SV_COLUMN)); + transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + RoaringBitmap bitmap = new RoaringBitmap(); + bitmap.add(0L, NUM_ROWS); + testTransformFunctionWithNull(transformFunction, expectedValues, bitmap); + + // Test with null column. + expression = RequestContextUtils.getExpression( + String.format("%s(%s, %d)", functionName, INT_SV_NULL_COLUMN, _intSVValues[0])); + transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + assertEquals(transformFunction.getName(), functionName); + resultMetadata = transformFunction.getResultMetadata(); + assertEquals(resultMetadata.getDataType(), DataType.BOOLEAN); + assertTrue(resultMetadata.isSingleValue()); + assertFalse(resultMetadata.hasDictionary()); + expectedValues = new boolean[NUM_ROWS]; + bitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + bitmap.add(i); + } else { + expectedValues[i] = getExpectedValue(Integer.compare(_intSVValues[i], _intSVValues[0])); + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, bitmap); } @Test(dataProvider = "testIllegalArguments", expectedExceptions = {BadQueryRequestException.class}) diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/CaseTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/CaseTransformFunctionTest.java index a9e02d3a2669..315b53e9e08d 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/CaseTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/CaseTransformFunctionTest.java @@ -19,18 +19,24 @@ package org.apache.pinot.core.operator.transform.function; import java.math.BigDecimal; +import java.sql.Timestamp; import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Random; import java.util.stream.Stream; import org.apache.pinot.common.function.TransformFunctionType; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.RequestContextUtils; import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.utils.BytesUtils; +import org.roaringbitmap.RoaringBitmap; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotEquals; public class CaseTransformFunctionTest extends BaseTransformFunctionTest { @@ -43,24 +49,22 @@ public class CaseTransformFunctionTest extends BaseTransformFunctionTest { @DataProvider public Object[][] params() { - return Stream.of(INT_SV_COLUMN, LONG_SV_COLUMN, FLOAT_SV_COLUMN, DOUBLE_SV_COLUMN) - .flatMap(col -> Stream.of( - new int[] {3, 2, 1}, - new int[] {1, 2, 3}, - new int[] {Integer.MAX_VALUE / 2, Integer.MAX_VALUE / 4, 0}, - new int[] {0, Integer.MAX_VALUE / 4, Integer.MAX_VALUE / 2}, - new int[] {0, Integer.MIN_VALUE / 4, Integer.MIN_VALUE}, - new int[] {Integer.MIN_VALUE, 0, 1}, - new int[] {Integer.MAX_VALUE, Integer.MIN_VALUE, 1}, - new int[] {Integer.MAX_VALUE, Integer.MAX_VALUE - 1, Integer.MAX_VALUE - 2} - ).map(thresholds -> new Object[]{col, thresholds[0], thresholds[1], thresholds[2]})) + return Stream.of(INT_SV_COLUMN, LONG_SV_COLUMN, FLOAT_SV_COLUMN, DOUBLE_SV_COLUMN).flatMap( + col -> Stream.of(new int[]{3, 2, 1}, new int[]{1, 2, 3}, + new int[]{Integer.MAX_VALUE / 2, Integer.MAX_VALUE / 4, 0}, + new int[]{0, Integer.MAX_VALUE / 4, Integer.MAX_VALUE / 2}, + new int[]{0, Integer.MIN_VALUE / 4, Integer.MIN_VALUE}, new int[]{Integer.MIN_VALUE, 0, 1}, + new int[]{Integer.MAX_VALUE, Integer.MIN_VALUE, 1}, + new int[]{Integer.MAX_VALUE, Integer.MAX_VALUE - 1, Integer.MAX_VALUE - 2}) + .map(thresholds -> new Object[]{col, thresholds[0], thresholds[1], thresholds[2]})) .toArray(Object[][]::new); } @Test(dataProvider = "params") public void testCasePriorityObserved(String column, int threshold1, int threshold2, int threshold3) { - String statement = String.format("CASE WHEN %s > %d THEN 3 WHEN %s > %d THEN 2 WHEN %s > %d THEN 1 ELSE -1 END", - column, threshold1, column, threshold2, column, threshold3); + String statement = + String.format("CASE WHEN %s > %d THEN 3 WHEN %s > %d THEN 2 WHEN %s > %d THEN 1 ELSE -1 END", column, + threshold1, column, threshold2, column, threshold3); ExpressionContext expression = RequestContextUtils.getExpression(statement); TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); int[] expectedIntResults = new int[NUM_ROWS]; @@ -91,699 +95,734 @@ public void testCasePriorityObserved(String column, int threshold1, int threshol @Test public void testCaseTransformFunctionWithIntResults() { - int[] expectedIntResults = new int[NUM_ROWS]; - Arrays.fill(expectedIntResults, 100); - testCaseQueryWithIntResults("true", expectedIntResults); - Arrays.fill(expectedIntResults, 10); - testCaseQueryWithIntResults("false", expectedIntResults); + boolean[] predicateResults = new boolean[NUM_ROWS]; + Arrays.fill(predicateResults, true); + testCaseQueries("true", predicateResults); + Arrays.fill(predicateResults, false); + testCaseQueries("false", predicateResults); for (TransformFunctionType functionType : BINARY_OPERATOR_TRANSFORM_FUNCTIONS) { - testCaseQueryWithIntResults(String.format("%s(%s, %s)", functionType.getName(), INT_SV_COLUMN, - String.format("%d", _intSVValues[INDEX_TO_COMPARE])), getExpectedIntResults(INT_SV_COLUMN, functionType)); - testCaseQueryWithIntResults(String.format("%s(%s, %s)", functionType.getName(), LONG_SV_COLUMN, - String.format("%d", _longSVValues[INDEX_TO_COMPARE])), getExpectedIntResults(LONG_SV_COLUMN, functionType)); - testCaseQueryWithIntResults(String.format("%s(%s, %s)", functionType.getName(), FLOAT_SV_COLUMN, - String.format("%f", _floatSVValues[INDEX_TO_COMPARE])), getExpectedIntResults(FLOAT_SV_COLUMN, functionType)); - testCaseQueryWithIntResults(String.format("%s(%s, %s)", functionType.getName(), DOUBLE_SV_COLUMN, + testCaseQueries(String.format("%s(%s, %s)", functionType.getName(), INT_SV_COLUMN, + String.format("%d", _intSVValues[INDEX_TO_COMPARE])), getPredicateResults(INT_SV_COLUMN, functionType)); + testCaseQueries(String.format("%s(%s, %s)", functionType.getName(), LONG_SV_COLUMN, + String.format("%d", _longSVValues[INDEX_TO_COMPARE])), getPredicateResults(LONG_SV_COLUMN, functionType)); + testCaseQueries(String.format("%s(%s, %s)", functionType.getName(), FLOAT_SV_COLUMN, + "CAST(" + String.format("%f", _floatSVValues[INDEX_TO_COMPARE]) + " AS FLOAT)"), + getPredicateResults(FLOAT_SV_COLUMN, functionType)); + testCaseQueries(String.format("%s(%s, %s)", functionType.getName(), DOUBLE_SV_COLUMN, String.format("%.20f", _doubleSVValues[INDEX_TO_COMPARE])), - getExpectedIntResults(DOUBLE_SV_COLUMN, functionType)); - testCaseQueryWithIntResults(String.format("%s(%s, %s)", functionType.getName(), STRING_SV_COLUMN, + getPredicateResults(DOUBLE_SV_COLUMN, functionType)); + testCaseQueries(String.format("%s(%s, %s)", functionType.getName(), STRING_SV_COLUMN, String.format("'%s'", _stringSVValues[INDEX_TO_COMPARE])), - getExpectedIntResults(STRING_SV_COLUMN, functionType)); + getPredicateResults(STRING_SV_COLUMN, functionType)); } } @Test - public void testCaseTransformFunctionWithFloatResults() { - float[] expectedFloatResults = new float[NUM_ROWS]; - Arrays.fill(expectedFloatResults, 100); - testCaseQueryWithFloatResults("true", expectedFloatResults); - Arrays.fill(expectedFloatResults, 10); - testCaseQueryWithFloatResults("false", expectedFloatResults); + public void testCaseTransformFunctionWithoutCastForFloatValues() { + boolean[] predicateResults = new boolean[1]; + Arrays.fill(predicateResults, true); + int[] expectedValues = new int[1]; + int index = -1; + for (int i = 0; i < NUM_ROWS; i++) { + if (Double.compare(_floatSVValues[i], Double.parseDouble(String.format("%f", _floatSVValues[i]))) != 0) { + index = i; + expectedValues[0] = predicateResults[0] ? _intSVValues[i] : 10; + break; + } + } - for (TransformFunctionType functionType : BINARY_OPERATOR_TRANSFORM_FUNCTIONS) { - testCaseQueryWithFloatResults(String.format("%s(%s, %s)", functionType.getName(), INT_SV_COLUMN, - String.format("%d", _intSVValues[INDEX_TO_COMPARE])), getExpectedFloatResults(INT_SV_COLUMN, functionType)); - testCaseQueryWithFloatResults(String.format("%s(%s, %s)", functionType.getName(), LONG_SV_COLUMN, - String.format("%d", _longSVValues[INDEX_TO_COMPARE])), getExpectedFloatResults(LONG_SV_COLUMN, functionType)); - testCaseQueryWithFloatResults(String.format("%s(%s, %s)", functionType.getName(), FLOAT_SV_COLUMN, - String.format("%f", _floatSVValues[INDEX_TO_COMPARE])), - getExpectedFloatResults(FLOAT_SV_COLUMN, functionType)); - testCaseQueryWithFloatResults(String.format("%s(%s, %s)", functionType.getName(), DOUBLE_SV_COLUMN, - String.format("%.20f", _doubleSVValues[INDEX_TO_COMPARE])), - getExpectedFloatResults(DOUBLE_SV_COLUMN, functionType)); - testCaseQueryWithFloatResults(String.format("%s(%s, %s)", functionType.getName(), STRING_SV_COLUMN, - String.format("'%s'", _stringSVValues[INDEX_TO_COMPARE])), - getExpectedFloatResults(STRING_SV_COLUMN, functionType)); + if (index != -1) { + String predicate = String.format("%s(%s, %s)", TransformFunctionType.EQUALS, FLOAT_SV_COLUMN, + String.format("%f", _floatSVValues[index])); + String expression = String.format("CASE WHEN %s THEN %s ELSE 10 END", predicate, INT_SV_COLUMN); + ExpressionContext expressionContext = RequestContextUtils.getExpression(expression); + TransformFunction transformFunction = TransformFunctionFactory.get(expressionContext, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof CaseTransformFunction); + assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.INT); + int[] intValues = transformFunction.transformToIntValuesSV(_projectionBlock); + assertNotEquals(intValues[index], expectedValues[0]); } } - @Test - public void testCaseTransformFunctionWithBigDecimalResults() { - BigDecimal val1 = new BigDecimal("100.99887766554433221"); - BigDecimal val2 = new BigDecimal("10.1122334455667788909"); - BigDecimal[] expectedBigDecimalResults = new BigDecimal[NUM_ROWS]; - Arrays.fill(expectedBigDecimalResults, val1); - testCaseQueryWithBigDecimalResults("true", expectedBigDecimalResults); - Arrays.fill(expectedBigDecimalResults, val2); - testCaseQueryWithBigDecimalResults("false", expectedBigDecimalResults); + @DataProvider + public static String[] illegalExpressions() { + //@formatter:off + return new String[] { + // '10.0' cannot be parsed as INT/LONG/TIMESTAMP/BYTES + String.format("CASE WHEN true THEN %s ELSE '10.0' END", INT_SV_COLUMN), + String.format("CASE WHEN true THEN %s ELSE '10.0' END", LONG_SV_COLUMN), + String.format("CASE WHEN true THEN %s ELSE '10.0' END", TIMESTAMP_COLUMN), + String.format("CASE WHEN true THEN %s ELSE '10.0' END", BYTES_SV_COLUMN), + // 'abc' cannot be parsed as any type other than STRING + String.format("CASE WHEN true THEN %s ELSE 'abc' END", INT_SV_COLUMN), + String.format("CASE WHEN true THEN %s ELSE 'abc' END", LONG_SV_COLUMN), + String.format("CASE WHEN true THEN %s ELSE 'abc' END", FLOAT_SV_COLUMN), + String.format("CASE WHEN true THEN %s ELSE 'abc' END", DOUBLE_SV_COLUMN), + String.format("CASE WHEN true THEN %s ELSE 'abc' END", BIG_DECIMAL_SV_COLUMN), + String.format("CASE WHEN true THEN %s ELSE 'abc' END", TIMESTAMP_COLUMN), + String.format("CASE WHEN true THEN %s ELSE 'abc' END", BYTES_SV_COLUMN), + // Cannot mix 2 types that are not both numeric + String.format("CASE WHEN true THEN %s ELSE %s END", INT_SV_COLUMN, TIMESTAMP_COLUMN), + String.format("CASE WHEN true THEN %s ELSE %s END", INT_SV_COLUMN, STRING_SV_COLUMN), + String.format("CASE WHEN true THEN %s ELSE %s END", INT_SV_COLUMN, BYTES_SV_COLUMN), + String.format("CASE WHEN true THEN 100 ELSE %s END", TIMESTAMP_COLUMN), + String.format("CASE WHEN true THEN 100 ELSE %s END", STRING_SV_COLUMN), + String.format("CASE WHEN true THEN 100 ELSE %s END", BYTES_SV_COLUMN) + }; + //@formatter:on + } - for (TransformFunctionType functionType : BINARY_OPERATOR_TRANSFORM_FUNCTIONS) { - testCaseQueryWithBigDecimalResults(String.format("%s(%s, %s)", functionType.getName(), INT_SV_COLUMN, - String.format("%d", _intSVValues[INDEX_TO_COMPARE])), - getExpectedBigDecimalResults(INT_SV_COLUMN, functionType)); - testCaseQueryWithBigDecimalResults(String.format("%s(%s, %s)", functionType.getName(), LONG_SV_COLUMN, - String.format("%d", _longSVValues[INDEX_TO_COMPARE])), - getExpectedBigDecimalResults(LONG_SV_COLUMN, functionType)); - testCaseQueryWithBigDecimalResults(String.format("%s(%s, %s)", functionType.getName(), FLOAT_SV_COLUMN, - String.format("%f", _floatSVValues[INDEX_TO_COMPARE])), - getExpectedBigDecimalResults(FLOAT_SV_COLUMN, functionType)); - testCaseQueryWithBigDecimalResults(String.format("%s(%s, %s)", functionType.getName(), DOUBLE_SV_COLUMN, - String.format("%.20f", _doubleSVValues[INDEX_TO_COMPARE])), - getExpectedBigDecimalResults(DOUBLE_SV_COLUMN, functionType)); - testCaseQueryWithBigDecimalResults(String.format("%s(%s, %s)", functionType.getName(), BIG_DECIMAL_SV_COLUMN, - String.format("'%s'", _bigDecimalSVValues[INDEX_TO_COMPARE].toPlainString())), - getExpectedBigDecimalResults(BIG_DECIMAL_SV_COLUMN, functionType)); - testCaseQueryWithBigDecimalResults(String.format("%s(%s, %s)", functionType.getName(), STRING_SV_COLUMN, - String.format("'%s'", _stringSVValues[INDEX_TO_COMPARE])), - getExpectedBigDecimalResults(STRING_SV_COLUMN, functionType)); - } + @Test(dataProvider = "illegalExpressions", expectedExceptions = Exception.class) + public void testInvalidCaseTransformFunction(String expression) { + TransformFunctionFactory.get(RequestContextUtils.getExpression(expression), _dataSourceMap); } @Test - public void testCaseTransformFunctionWithStringResults() { - String[] expectedStringResults = new String[NUM_ROWS]; - Arrays.fill(expectedStringResults, "aaa"); - testCaseQueryWithStringResults("true", expectedStringResults); - Arrays.fill(expectedStringResults, "bbb"); - testCaseQueryWithStringResults("false", expectedStringResults); + public void testCaseTransformationWithNullColumn() { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("CASE WHEN %s IS NULL THEN 'aaa' ELSE 'bbb' END", STRING_ALPHANUM_NULL_SV_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.getNullHandlingEnabled(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof CaseTransformFunction); + Assert.assertEquals(transformFunction.getName(), "case"); + Assert.assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.STRING); - for (TransformFunctionType functionType : BINARY_OPERATOR_TRANSFORM_FUNCTIONS) { - testCaseQueryWithStringResults(String.format("%s(%s, %s)", functionType.getName(), INT_SV_COLUMN, - String.format("%d", _intSVValues[INDEX_TO_COMPARE])), getExpectedStringResults(INT_SV_COLUMN, functionType)); - testCaseQueryWithStringResults(String.format("%s(%s, %s)", functionType.getName(), LONG_SV_COLUMN, - String.format("%d", _longSVValues[INDEX_TO_COMPARE])), - getExpectedStringResults(LONG_SV_COLUMN, functionType)); - testCaseQueryWithStringResults(String.format("%s(%s, %s)", functionType.getName(), FLOAT_SV_COLUMN, - String.format("%f", _floatSVValues[INDEX_TO_COMPARE])), - getExpectedStringResults(FLOAT_SV_COLUMN, functionType)); - testCaseQueryWithStringResults(String.format("%s(%s, %s)", functionType.getName(), DOUBLE_SV_COLUMN, - String.format("%.20f", _doubleSVValues[INDEX_TO_COMPARE])), - getExpectedStringResults(DOUBLE_SV_COLUMN, functionType)); - testCaseQueryWithStringResults(String.format("%s(%s, %s)", functionType.getName(), STRING_SV_COLUMN, - String.format("'%s'", _stringSVValues[INDEX_TO_COMPARE])), - getExpectedStringResults(STRING_SV_COLUMN, functionType)); + String[] expectedValues = new String[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + expectedValues[i] = "aaa"; + } else { + expectedValues[i] = "bbb"; + } } + testTransformFunctionWithNull(transformFunction, expectedValues, new RoaringBitmap()); } - private void testCaseQueryWithIntResults(String predicate, int[] expectedValues) { - ExpressionContext expression = - RequestContextUtils.getExpression(String.format("CASE WHEN %s THEN 100 ELSE 10 END", predicate)); - TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + @Test + public void testCaseTransformationWithNullThenClause() { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("CASE WHEN %s IS NULL THEN NULL ELSE 'bbb' END", STRING_ALPHANUM_NULL_SV_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.getNullHandlingEnabled(expression, _dataSourceMap); Assert.assertTrue(transformFunction instanceof CaseTransformFunction); - assertEquals(transformFunction.getName(), CaseTransformFunction.FUNCTION_NAME); - assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.INT); - testTransformFunction(transformFunction, expectedValues); + Assert.assertEquals(transformFunction.getName(), "case"); + Assert.assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.STRING); + String[] expectedValues = new String[NUM_ROWS]; + RoaringBitmap bitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + bitmap.add(i); + } else { + expectedValues[i] = "bbb"; + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, bitmap); } - private void testCaseQueryWithFloatResults(String predicate, float[] expectedValues) { - ExpressionContext expression = - RequestContextUtils.getExpression(String.format("CASE WHEN %s THEN 100.0 ELSE 10.0 END", predicate)); - TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + @Test + public void testCaseTransformationWithNullElseClause() { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("CASE WHEN %s IS NULL THEN 'aaa' END", STRING_ALPHANUM_NULL_SV_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.getNullHandlingEnabled(expression, _dataSourceMap); Assert.assertTrue(transformFunction instanceof CaseTransformFunction); - assertEquals(transformFunction.getName(), CaseTransformFunction.FUNCTION_NAME); - assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.FLOAT); - testTransformFunction(transformFunction, expectedValues); + Assert.assertEquals(transformFunction.getName(), "case"); + Assert.assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.STRING); + + String[] expectedValues = new String[NUM_ROWS]; + RoaringBitmap bitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + expectedValues[i] = "aaa"; + } else { + bitmap.add(i); + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, bitmap); } - private void testCaseQueryWithBigDecimalResults(String predicate, BigDecimal[] expectedValues) { - // Note: defining decimal literals within quotes preserves precision. - ExpressionContext expression = RequestContextUtils.getExpression( - String.format("CASE WHEN %s THEN '100.99887766554433221' ELSE '10.1122334455667788909' END", predicate)); - TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); - Assert.assertTrue(transformFunction instanceof CaseTransformFunction); - assertEquals(transformFunction.getName(), CaseTransformFunction.FUNCTION_NAME); - assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.BIG_DECIMAL); - testTransformFunction(transformFunction, expectedValues); + private void testCaseQueries(String predicate, boolean[] predicateResults) { + // INT + { + List expressions = + Arrays.asList(String.format("CASE WHEN %s THEN %s ELSE 10 END", predicate, INT_SV_COLUMN), + String.format("CASE WHEN %s THEN %s ELSE '10' END", predicate, INT_SV_COLUMN)); + int[] expectedValues = new int[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? _intSVValues[i] : 10; + } + testCaseQueryWithIntResults(expressions, expectedValues); + } + { + List expressions = + Arrays.asList(String.format("CASE WHEN %s THEN 100 ELSE %s END", predicate, INT_SV_COLUMN), + String.format("CASE WHEN %s THEN '100' ELSE %s END", predicate, INT_SV_COLUMN)); + int[] expectedValues = new int[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? 100 : _intSVValues[i]; + } + testCaseQueryWithIntResults(expressions, expectedValues); + } + + // LONG + { + List expressions = + Arrays.asList(String.format("CASE WHEN %s THEN %s ELSE 10 END", predicate, LONG_SV_COLUMN), + String.format("CASE WHEN %s THEN %s ELSE '10' END", predicate, LONG_SV_COLUMN)); + long[] expectedValues = new long[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? _longSVValues[i] : 10L; + } + testCaseQueryWithLongResults(expressions, expectedValues); + } + { + List expressions = + Arrays.asList(String.format("CASE WHEN %s THEN 100 ELSE %s END", predicate, LONG_SV_COLUMN), + String.format("CASE WHEN %s THEN '100' ELSE %s END", predicate, LONG_SV_COLUMN)); + long[] expectedValues = new long[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? 100L : _longSVValues[i]; + } + testCaseQueryWithLongResults(expressions, expectedValues); + } + // Cast INT to LONG + { + List expressions = + Arrays.asList(String.format("CASE WHEN %s THEN CAST(%s AS LONG) ELSE 10 END", predicate, INT_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS LONG) ELSE '10' END", predicate, INT_SV_COLUMN)); + long[] expectedValues = new long[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? _intSVValues[i] : 10L; + } + testCaseQueryWithLongResults(expressions, expectedValues); + } + // Literal upcast INT to LONG + { + List expressions = Collections.singletonList( + String.format("CASE WHEN %s THEN %s ELSE %d END", predicate, INT_SV_COLUMN, 10L + Integer.MAX_VALUE)); + long[] expectedValues = new long[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? _intSVValues[i] : 10L + Integer.MAX_VALUE; + } + testCaseQueryWithLongResults(expressions, expectedValues); + } + + // FLOAT + { + List expressions = + Arrays.asList(String.format("CASE WHEN %s THEN %s ELSE 10 END", predicate, FLOAT_SV_COLUMN), + String.format("CASE WHEN %s THEN %s ELSE '10' END", predicate, FLOAT_SV_COLUMN), + String.format("CASE WHEN %s THEN %s ELSE '10.0' END", predicate, FLOAT_SV_COLUMN)); + float[] expectedValues = new float[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? _floatSVValues[i] : 10.0f; + } + testCaseQueryWithFloatResults(expressions, expectedValues); + } + { + List expressions = + Arrays.asList(String.format("CASE WHEN %s THEN 100 ELSE %s END", predicate, FLOAT_SV_COLUMN), + String.format("CASE WHEN %s THEN '100' ELSE %s END", predicate, FLOAT_SV_COLUMN), + String.format("CASE WHEN %s THEN '100.0' ELSE %s END", predicate, FLOAT_SV_COLUMN)); + float[] expectedValues = new float[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? 100.0f : _floatSVValues[i]; + } + testCaseQueryWithFloatResults(expressions, expectedValues); + } + { + List expressions = + Collections.singletonList(String.format("CASE WHEN %s THEN %s ELSE '1.23' END", predicate, FLOAT_SV_COLUMN)); + float[] expectedValues = new float[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? _floatSVValues[i] : 1.23f; + } + testCaseQueryWithFloatResults(expressions, expectedValues); + } + // Cast INT/LONG to FLOAT + { + List expressions = + Arrays.asList(String.format("CASE WHEN %s THEN CAST(%s AS FLOAT) ELSE 10 END", predicate, INT_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS FLOAT) ELSE '10' END", predicate, INT_SV_COLUMN)); + float[] expectedValues = new float[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? _intSVValues[i] : 10.0f; + } + testCaseQueryWithFloatResults(expressions, expectedValues); + } + { + List expressions = + Arrays.asList(String.format("CASE WHEN %s THEN CAST(%s AS FLOAT) ELSE 10 END", predicate, LONG_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS FLOAT) ELSE '10' END", predicate, LONG_SV_COLUMN)); + float[] expectedValues = new float[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? _longSVValues[i] : 10.0f; + } + testCaseQueryWithFloatResults(expressions, expectedValues); + } + + // DOUBLE + { + List expressions = + Arrays.asList(String.format("CASE WHEN %s THEN %s ELSE 10 END", predicate, DOUBLE_SV_COLUMN), + String.format("CASE WHEN %s THEN %s ELSE 10.0 END", predicate, DOUBLE_SV_COLUMN), + String.format("CASE WHEN %s THEN %s ELSE '10' END", predicate, DOUBLE_SV_COLUMN), + String.format("CASE WHEN %s THEN %s ELSE '10.0' END", predicate, DOUBLE_SV_COLUMN)); + double[] expectedValues = new double[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? _doubleSVValues[i] : 10.0; + } + testCaseQueryWithDoubleResults(expressions, expectedValues); + } + { + List expressions = + Arrays.asList(String.format("CASE WHEN %s THEN 100 ELSE %s END", predicate, DOUBLE_SV_COLUMN), + String.format("CASE WHEN %s THEN 100.0 ELSE %s END", predicate, DOUBLE_SV_COLUMN), + String.format("CASE WHEN %s THEN '100' ELSE %s END", predicate, DOUBLE_SV_COLUMN), + String.format("CASE WHEN %s THEN '100.0' ELSE %s END", predicate, DOUBLE_SV_COLUMN)); + double[] expectedValues = new double[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? 100.0 : _doubleSVValues[i]; + } + testCaseQueryWithDoubleResults(expressions, expectedValues); + } + { + List expressions = + Arrays.asList(String.format("CASE WHEN %s THEN %s ELSE 1.23 END", predicate, DOUBLE_SV_COLUMN), + String.format("CASE WHEN %s THEN %s ELSE '1.23' END", predicate, DOUBLE_SV_COLUMN)); + double[] expectedValues = new double[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? _doubleSVValues[i] : 1.23; + } + testCaseQueryWithDoubleResults(expressions, expectedValues); + } + // Cast INT/LONG/FLOAT to DOUBLE + { + List expressions = + Arrays.asList(String.format("CASE WHEN %s THEN CAST(%s AS DOUBLE) ELSE 10 END", predicate, INT_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS DOUBLE) ELSE 10.0 END", predicate, INT_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS DOUBLE) ELSE '10' END", predicate, INT_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS DOUBLE) ELSE '10.0' END", predicate, INT_SV_COLUMN)); + double[] expectedValues = new double[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? _intSVValues[i] : 10.0; + } + testCaseQueryWithDoubleResults(expressions, expectedValues); + } + { + List expressions = + Arrays.asList(String.format("CASE WHEN %s THEN CAST(%s AS DOUBLE) ELSE 10 END", predicate, LONG_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS DOUBLE) ELSE 10.0 END", predicate, LONG_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS DOUBLE) ELSE '10' END", predicate, LONG_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS DOUBLE) ELSE '10.0' END", predicate, LONG_SV_COLUMN)); + double[] expectedValues = new double[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? _longSVValues[i] : 10.0; + } + testCaseQueryWithDoubleResults(expressions, expectedValues); + } + { + List expressions = + Arrays.asList(String.format("CASE WHEN %s THEN CAST(%s AS DOUBLE) ELSE 10 END", predicate, FLOAT_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS DOUBLE) ELSE 10.0 END", predicate, FLOAT_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS DOUBLE) ELSE '10' END", predicate, FLOAT_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS DOUBLE) ELSE '10.0' END", predicate, FLOAT_SV_COLUMN)); + double[] expectedValues = new double[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? _floatSVValues[i] : 10.0; + } + testCaseQueryWithDoubleResults(expressions, expectedValues); + } + // Literal upcast INT/LONG/FLOAT to DOUBLE + { + List expressions = + Collections.singletonList(String.format("CASE WHEN %s THEN %s ELSE 1.23 END", predicate, INT_SV_COLUMN)); + double[] expectedValues = new double[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? _intSVValues[i] : 1.23; + } + testCaseQueryWithDoubleResults(expressions, expectedValues); + } + { + List expressions = + Collections.singletonList(String.format("CASE WHEN %s THEN %s ELSE 1.23 END", predicate, LONG_SV_COLUMN)); + double[] expectedValues = new double[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? _longSVValues[i] : 1.23; + } + testCaseQueryWithDoubleResults(expressions, expectedValues); + } + { + List expressions = + Collections.singletonList(String.format("CASE WHEN %s THEN %s ELSE 1.23 END", predicate, FLOAT_SV_COLUMN)); + double[] expectedValues = new double[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? _floatSVValues[i] : 1.23; + } + testCaseQueryWithDoubleResults(expressions, expectedValues); + } + + // BIG_DECIMAL + { + List expressions = + Arrays.asList(String.format("CASE WHEN %s THEN %s ELSE 10 END", predicate, BIG_DECIMAL_SV_COLUMN), + String.format("CASE WHEN %s THEN %s ELSE 10.0 END", predicate, BIG_DECIMAL_SV_COLUMN), + String.format("CASE WHEN %s THEN %s ELSE '10' END", predicate, BIG_DECIMAL_SV_COLUMN), + String.format("CASE WHEN %s THEN %s ELSE '10.0' END", predicate, BIG_DECIMAL_SV_COLUMN)); + BigDecimal[] expectedValues = new BigDecimal[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? _bigDecimalSVValues[i] : BigDecimal.TEN; + } + testCaseQueryWithBigDecimalResults(expressions, expectedValues); + } + { + List expressions = + Arrays.asList(String.format("CASE WHEN %s THEN 100 ELSE %s END", predicate, BIG_DECIMAL_SV_COLUMN), + String.format("CASE WHEN %s THEN 100.0 ELSE %s END", predicate, BIG_DECIMAL_SV_COLUMN), + String.format("CASE WHEN %s THEN '100' ELSE %s END", predicate, BIG_DECIMAL_SV_COLUMN), + String.format("CASE WHEN %s THEN '100.0' ELSE %s END", predicate, BIG_DECIMAL_SV_COLUMN)); + BigDecimal[] expectedValues = new BigDecimal[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? BigDecimal.valueOf(100) : _bigDecimalSVValues[i]; + } + testCaseQueryWithBigDecimalResults(expressions, expectedValues); + } + { + List expressions = + Arrays.asList(String.format("CASE WHEN %s THEN %s ELSE 1.23 END", predicate, BIG_DECIMAL_SV_COLUMN), + String.format("CASE WHEN %s THEN %s ELSE '1.23' END", predicate, BIG_DECIMAL_SV_COLUMN)); + BigDecimal[] expectedValues = new BigDecimal[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? _bigDecimalSVValues[i] : new BigDecimal("1.23"); + } + testCaseQueryWithBigDecimalResults(expressions, expectedValues); + } + // Cast INT/LONG/FLOAT/DOUBLE to BIG_DECIMAL + { + List expressions = + Arrays.asList(String.format("CASE WHEN %s THEN CAST(%s AS DECIMAL) ELSE 10 END", predicate, INT_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS DECIMAL) ELSE 10.0 END", predicate, INT_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS DECIMAL) ELSE '10' END", predicate, INT_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS DECIMAL) ELSE '10.0' END", predicate, INT_SV_COLUMN)); + BigDecimal[] expectedValues = new BigDecimal[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? BigDecimal.valueOf(_intSVValues[i]) : BigDecimal.TEN; + } + testCaseQueryWithBigDecimalResults(expressions, expectedValues); + } + { + List expressions = + Arrays.asList(String.format("CASE WHEN %s THEN CAST(%s AS DECIMAL) ELSE 10 END", predicate, LONG_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS DECIMAL) ELSE 10.0 END", predicate, LONG_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS DECIMAL) ELSE '10' END", predicate, LONG_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS DECIMAL) ELSE '10.0' END", predicate, LONG_SV_COLUMN)); + BigDecimal[] expectedValues = new BigDecimal[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? BigDecimal.valueOf(_longSVValues[i]) : BigDecimal.TEN; + } + testCaseQueryWithBigDecimalResults(expressions, expectedValues); + } + { + List expressions = + Arrays.asList(String.format("CASE WHEN %s THEN CAST(%s AS DECIMAL) ELSE 10 END", predicate, FLOAT_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS DECIMAL) ELSE 10.0 END", predicate, FLOAT_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS DECIMAL) ELSE '10' END", predicate, FLOAT_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS DECIMAL) ELSE '10.0' END", predicate, FLOAT_SV_COLUMN)); + BigDecimal[] expectedValues = new BigDecimal[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? BigDecimal.valueOf(_floatSVValues[i]) : BigDecimal.TEN; + } + testCaseQueryWithBigDecimalResults(expressions, expectedValues); + } + { + List expressions = + Arrays.asList(String.format("CASE WHEN %s THEN CAST(%s AS DECIMAL) ELSE 10 END", predicate, DOUBLE_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS DECIMAL) ELSE 10.0 END", predicate, DOUBLE_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS DECIMAL) ELSE '10' END", predicate, DOUBLE_SV_COLUMN), + String.format("CASE WHEN %s THEN CAST(%s AS DECIMAL) ELSE '10.0' END", predicate, DOUBLE_SV_COLUMN)); + BigDecimal[] expectedValues = new BigDecimal[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? BigDecimal.valueOf(_doubleSVValues[i]) : BigDecimal.TEN; + } + testCaseQueryWithBigDecimalResults(expressions, expectedValues); + } + + // STRING + { + List expressions = + Collections.singletonList(String.format("CASE WHEN %s THEN %s ELSE '10' END", predicate, STRING_SV_COLUMN)); + String[] expectedValues = new String[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? _stringSVValues[i] : "10"; + } + testCaseQueryWithStringResults(expressions, expectedValues); + } + { + List expressions = + Collections.singletonList(String.format("CASE WHEN %s THEN '100' ELSE %s END", predicate, STRING_SV_COLUMN)); + String[] expectedValues = new String[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? "100" : _stringSVValues[i]; + } + testCaseQueryWithStringResults(expressions, expectedValues); + } + // Cast INT to STRING + { + List expressions = Collections.singletonList( + String.format("CASE WHEN %s THEN CAST(%s AS STRING) ELSE '10' END", predicate, INT_SV_COLUMN)); + String[] expectedValues = new String[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? Integer.toString(_intSVValues[i]) : "10"; + } + testCaseQueryWithStringResults(expressions, expectedValues); + } + + // BYTES + { + List expressions = + Collections.singletonList(String.format("CASE WHEN %s THEN %s ELSE '10' END", predicate, BYTES_SV_COLUMN)); + byte[][] expectedValues = new byte[NUM_ROWS][]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? _bytesSVValues[i] : BytesUtils.toBytes("10"); + } + testCaseQueryWithBytesResults(expressions, expectedValues); + } + + // TIMESTAMP + { + long currentTimeMs = System.currentTimeMillis(); + String timestamp = new Timestamp(currentTimeMs).toString(); + List expressions = + Arrays.asList(String.format("CASE WHEN %s THEN %s ELSE '%s' END", predicate, TIMESTAMP_COLUMN, timestamp), + String.format("CASE WHEN %s THEN %s ELSE '%d' END", predicate, TIMESTAMP_COLUMN, currentTimeMs)); + long[] expectedValues = new long[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? _timeValues[i] : currentTimeMs; + } + testCaseQueryWithTimestampResults(expressions, expectedValues); + } + // Cast LONG to TIMESTAMP + { + long currentTimeMs = System.currentTimeMillis(); + String timestamp = new Timestamp(currentTimeMs).toString(); + List expressions = Arrays.asList( + String.format("CASE WHEN %s THEN CAST(%s AS TIMESTAMP) ELSE '%s' END", predicate, LONG_SV_COLUMN, timestamp), + String.format("CASE WHEN %s THEN CAST(%s AS TIMESTAMP) ELSE '%d' END", predicate, LONG_SV_COLUMN, + currentTimeMs)); + long[] expectedValues = new long[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = predicateResults[i] ? _longSVValues[i] : currentTimeMs; + } + testCaseQueryWithTimestampResults(expressions, expectedValues); + } } - private void testCaseQueryWithStringResults(String predicate, String[] expectedValues) { - ExpressionContext expression = - RequestContextUtils.getExpression(String.format("CASE WHEN %s THEN 'aaa' ELSE 'bbb' END", predicate)); - TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); - Assert.assertTrue(transformFunction instanceof CaseTransformFunction); - assertEquals(transformFunction.getName(), CaseTransformFunction.FUNCTION_NAME); - assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.STRING); - testTransformFunction(transformFunction, expectedValues); + private void testCaseQueryWithIntResults(List expressions, int[] expectedValues) { + for (String expression : expressions) { + ExpressionContext expressionContext = RequestContextUtils.getExpression(expression); + TransformFunction transformFunction = TransformFunctionFactory.get(expressionContext, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof CaseTransformFunction); + assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.INT); + testTransformFunction(transformFunction, expectedValues); + } } - private int[] getExpectedIntResults(String column, TransformFunctionType type) { - int[] result = new int[NUM_ROWS]; - for (int i = 0; i < NUM_ROWS; i++) { - switch (column) { - case INT_SV_COLUMN: - switch (type) { - case EQUALS: - result[i] = (_intSVValues[i] == _intSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case NOT_EQUALS: - result[i] = (_intSVValues[i] != _intSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case GREATER_THAN: - result[i] = (_intSVValues[i] > _intSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case GREATER_THAN_OR_EQUAL: - result[i] = (_intSVValues[i] >= _intSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case LESS_THAN: - result[i] = (_intSVValues[i] < _intSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case LESS_THAN_OR_EQUAL: - result[i] = (_intSVValues[i] <= _intSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - default: - throw new IllegalStateException("Not supported type - " + type); - } - break; - case LONG_SV_COLUMN: - switch (type) { - case EQUALS: - result[i] = (_longSVValues[i] == _longSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case NOT_EQUALS: - result[i] = (_longSVValues[i] != _longSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case GREATER_THAN: - result[i] = (_longSVValues[i] > _longSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case GREATER_THAN_OR_EQUAL: - result[i] = (_longSVValues[i] >= _longSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case LESS_THAN: - result[i] = (_longSVValues[i] < _longSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case LESS_THAN_OR_EQUAL: - result[i] = (_longSVValues[i] <= _longSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - default: - throw new IllegalStateException("Not supported type - " + type); - } - break; - case FLOAT_SV_COLUMN: - switch (type) { - case EQUALS: - result[i] = (_floatSVValues[i] == _floatSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case NOT_EQUALS: - result[i] = (_floatSVValues[i] != _floatSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case GREATER_THAN: - result[i] = (_floatSVValues[i] > _floatSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case GREATER_THAN_OR_EQUAL: - result[i] = (_floatSVValues[i] >= _floatSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case LESS_THAN: - result[i] = (_floatSVValues[i] < _floatSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case LESS_THAN_OR_EQUAL: - result[i] = (_floatSVValues[i] <= _floatSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - default: - throw new IllegalStateException("Not supported type - " + type); - } - break; - case DOUBLE_SV_COLUMN: - switch (type) { - case EQUALS: - result[i] = (_doubleSVValues[i] == _doubleSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case NOT_EQUALS: - result[i] = (_doubleSVValues[i] != _doubleSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case GREATER_THAN: - result[i] = (_doubleSVValues[i] > _doubleSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case GREATER_THAN_OR_EQUAL: - result[i] = (_doubleSVValues[i] >= _doubleSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case LESS_THAN: - result[i] = (_doubleSVValues[i] < _doubleSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case LESS_THAN_OR_EQUAL: - result[i] = (_doubleSVValues[i] <= _doubleSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - default: - throw new IllegalStateException("Not supported type - " + type); - } - break; - case STRING_SV_COLUMN: - switch (type) { - case EQUALS: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) == 0) ? 100 : 10; - break; - case NOT_EQUALS: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) != 0) ? 100 : 10; - break; - case GREATER_THAN: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) > 0) ? 100 : 10; - break; - case GREATER_THAN_OR_EQUAL: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) >= 0) ? 100 : 10; - break; - case LESS_THAN: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) < 0) ? 100 : 10; - break; - case LESS_THAN_OR_EQUAL: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) <= 0) ? 100 : 10; - break; - default: - throw new IllegalStateException("Not supported type - " + type); - } - break; - default: - break; - } + private void testCaseQueryWithLongResults(List expressions, long[] expectedValues) { + for (String expression : expressions) { + ExpressionContext expressionContext = RequestContextUtils.getExpression(expression); + TransformFunction transformFunction = TransformFunctionFactory.get(expressionContext, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof CaseTransformFunction); + assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.LONG); + testTransformFunction(transformFunction, expectedValues); } - return result; } - private float[] getExpectedFloatResults(String column, TransformFunctionType type) { - float[] result = new float[NUM_ROWS]; - for (int i = 0; i < NUM_ROWS; i++) { - switch (column) { - case INT_SV_COLUMN: - switch (type) { - case EQUALS: - result[i] = (_intSVValues[i] == _intSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case NOT_EQUALS: - result[i] = (_intSVValues[i] != _intSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case GREATER_THAN: - result[i] = (_intSVValues[i] > _intSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case GREATER_THAN_OR_EQUAL: - result[i] = (_intSVValues[i] >= _intSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case LESS_THAN: - result[i] = (_intSVValues[i] < _intSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case LESS_THAN_OR_EQUAL: - result[i] = (_intSVValues[i] <= _intSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - default: - throw new IllegalStateException("Not supported type - " + type); - } - break; - case LONG_SV_COLUMN: - switch (type) { - case EQUALS: - result[i] = (_longSVValues[i] == _longSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case NOT_EQUALS: - result[i] = (_longSVValues[i] != _longSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case GREATER_THAN: - result[i] = (_longSVValues[i] > _longSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case GREATER_THAN_OR_EQUAL: - result[i] = (_longSVValues[i] >= _longSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case LESS_THAN: - result[i] = (_longSVValues[i] < _longSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case LESS_THAN_OR_EQUAL: - result[i] = (_longSVValues[i] <= _longSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - default: - throw new IllegalStateException("Not supported type - " + type); - } - break; - case FLOAT_SV_COLUMN: - switch (type) { - case EQUALS: - result[i] = (_floatSVValues[i] == _floatSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case NOT_EQUALS: - result[i] = (_floatSVValues[i] != _floatSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case GREATER_THAN: - result[i] = (_floatSVValues[i] > _floatSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case GREATER_THAN_OR_EQUAL: - result[i] = (_floatSVValues[i] >= _floatSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case LESS_THAN: - result[i] = (_floatSVValues[i] < _floatSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case LESS_THAN_OR_EQUAL: - result[i] = (_floatSVValues[i] <= _floatSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - default: - throw new IllegalStateException("Not supported type - " + type); - } - break; - case DOUBLE_SV_COLUMN: - switch (type) { - case EQUALS: - result[i] = (_doubleSVValues[i] == _doubleSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case NOT_EQUALS: - result[i] = (_doubleSVValues[i] != _doubleSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case GREATER_THAN: - result[i] = (_doubleSVValues[i] > _doubleSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case GREATER_THAN_OR_EQUAL: - result[i] = (_doubleSVValues[i] >= _doubleSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case LESS_THAN: - result[i] = (_doubleSVValues[i] < _doubleSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - case LESS_THAN_OR_EQUAL: - result[i] = (_doubleSVValues[i] <= _doubleSVValues[INDEX_TO_COMPARE]) ? 100 : 10; - break; - default: - throw new IllegalStateException("Not supported type - " + type); - } - break; - case STRING_SV_COLUMN: - switch (type) { - case EQUALS: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) == 0) ? 100 : 10; - break; - case NOT_EQUALS: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) != 0) ? 100 : 10; - break; - case GREATER_THAN: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) > 0) ? 100 : 10; - break; - case GREATER_THAN_OR_EQUAL: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) >= 0) ? 100 : 10; - break; - case LESS_THAN: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) < 0) ? 100 : 10; - break; - case LESS_THAN_OR_EQUAL: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) <= 0) ? 100 : 10; - break; - default: - throw new IllegalStateException("Not supported type - " + type); - } - break; - default: - break; - } + private void testCaseQueryWithFloatResults(List expressions, float[] expectedValues) { + for (String expression : expressions) { + ExpressionContext expressionContext = RequestContextUtils.getExpression(expression); + TransformFunction transformFunction = TransformFunctionFactory.get(expressionContext, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof CaseTransformFunction); + assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.FLOAT); + testTransformFunction(transformFunction, expectedValues); } - return result; } - private BigDecimal[] getExpectedBigDecimalResults(String column, TransformFunctionType type) { - BigDecimal[] result = new BigDecimal[NUM_ROWS]; - BigDecimal val1 = new BigDecimal("100.99887766554433221"); - BigDecimal val2 = new BigDecimal("10.1122334455667788909"); - for (int i = 0; i < NUM_ROWS; i++) { - switch (column) { - case INT_SV_COLUMN: - switch (type) { - case EQUALS: - result[i] = (_intSVValues[i] == _intSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - case NOT_EQUALS: - result[i] = (_intSVValues[i] != _intSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - case GREATER_THAN: - result[i] = (_intSVValues[i] > _intSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - case GREATER_THAN_OR_EQUAL: - result[i] = (_intSVValues[i] >= _intSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - case LESS_THAN: - result[i] = (_intSVValues[i] < _intSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - case LESS_THAN_OR_EQUAL: - result[i] = (_intSVValues[i] <= _intSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - default: - throw new IllegalStateException("Not supported type - " + type); - } - break; - case LONG_SV_COLUMN: - switch (type) { - case EQUALS: - result[i] = (_longSVValues[i] == _longSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - case NOT_EQUALS: - result[i] = (_longSVValues[i] != _longSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - case GREATER_THAN: - result[i] = (_longSVValues[i] > _longSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - case GREATER_THAN_OR_EQUAL: - result[i] = (_longSVValues[i] >= _longSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - case LESS_THAN: - result[i] = (_longSVValues[i] < _longSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - case LESS_THAN_OR_EQUAL: - result[i] = (_longSVValues[i] <= _longSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - default: - throw new IllegalStateException("Not supported type - " + type); - } - break; - case FLOAT_SV_COLUMN: - switch (type) { - case EQUALS: - result[i] = (_floatSVValues[i] == _floatSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - case NOT_EQUALS: - result[i] = (_floatSVValues[i] != _floatSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - case GREATER_THAN: - result[i] = (_floatSVValues[i] > _floatSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - case GREATER_THAN_OR_EQUAL: - result[i] = (_floatSVValues[i] >= _floatSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - case LESS_THAN: - result[i] = (_floatSVValues[i] < _floatSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - case LESS_THAN_OR_EQUAL: - result[i] = (_floatSVValues[i] <= _floatSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - default: - throw new IllegalStateException("Not supported type - " + type); - } - break; - case DOUBLE_SV_COLUMN: - switch (type) { - case EQUALS: - result[i] = (_doubleSVValues[i] == _doubleSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - case NOT_EQUALS: - result[i] = (_doubleSVValues[i] != _doubleSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - case GREATER_THAN: - result[i] = (_doubleSVValues[i] > _doubleSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - case GREATER_THAN_OR_EQUAL: - result[i] = (_doubleSVValues[i] >= _doubleSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - case LESS_THAN: - result[i] = (_doubleSVValues[i] < _doubleSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - case LESS_THAN_OR_EQUAL: - result[i] = (_doubleSVValues[i] <= _doubleSVValues[INDEX_TO_COMPARE]) ? val1 : val2; - break; - default: - throw new IllegalStateException("Not supported type - " + type); - } - break; - case BIG_DECIMAL_SV_COLUMN: - switch (type) { - case EQUALS: - result[i] = _bigDecimalSVValues[i].compareTo(_bigDecimalSVValues[INDEX_TO_COMPARE]) == 0 ? val1 : val2; - break; - case NOT_EQUALS: - result[i] = _bigDecimalSVValues[i].compareTo(_bigDecimalSVValues[INDEX_TO_COMPARE]) != 0 ? val1 : val2; - break; - case GREATER_THAN: - result[i] = _bigDecimalSVValues[i].compareTo(_bigDecimalSVValues[INDEX_TO_COMPARE]) > 0 ? val1 : val2; - break; - case GREATER_THAN_OR_EQUAL: - result[i] = _bigDecimalSVValues[i].compareTo(_bigDecimalSVValues[INDEX_TO_COMPARE]) >= 0 ? val1 : val2; - break; - case LESS_THAN: - result[i] = _bigDecimalSVValues[i].compareTo(_bigDecimalSVValues[INDEX_TO_COMPARE]) < 0 ? val1 : val2; - break; - case LESS_THAN_OR_EQUAL: - result[i] = _bigDecimalSVValues[i].compareTo(_bigDecimalSVValues[INDEX_TO_COMPARE]) <= 0 ? val1 : val2; - break; - default: - throw new IllegalStateException("Not supported type - " + type); - } - break; - case STRING_SV_COLUMN: - switch (type) { - case EQUALS: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) == 0) ? val1 : val2; - break; - case NOT_EQUALS: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) != 0) ? val1 : val2; - break; - case GREATER_THAN: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) > 0) ? val1 : val2; - break; - case GREATER_THAN_OR_EQUAL: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) >= 0) ? val1 : val2; - break; - case LESS_THAN: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) < 0) ? val1 : val2; - break; - case LESS_THAN_OR_EQUAL: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) <= 0) ? val1 : val2; - break; - default: - throw new IllegalStateException("Not supported type - " + type); - } - break; - default: - break; - } + private void testCaseQueryWithDoubleResults(List expressions, double[] expectedValues) { + for (String expression : expressions) { + ExpressionContext expressionContext = RequestContextUtils.getExpression(expression); + TransformFunction transformFunction = TransformFunctionFactory.get(expressionContext, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof CaseTransformFunction); + assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.DOUBLE); + testTransformFunction(transformFunction, expectedValues); } - return result; } - private String[] getExpectedStringResults(String column, TransformFunctionType type) { - String[] result = new String[NUM_ROWS]; + private void testCaseQueryWithBigDecimalResults(List expressions, BigDecimal[] expectedValues) { + for (String expression : expressions) { + ExpressionContext expressionContext = RequestContextUtils.getExpression(expression); + TransformFunction transformFunction = TransformFunctionFactory.get(expressionContext, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof CaseTransformFunction); + assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.BIG_DECIMAL); + testTransformFunction(transformFunction, expectedValues); + } + } + + private void testCaseQueryWithStringResults(List expressions, String[] expectedValues) { + for (String expression : expressions) { + ExpressionContext expressionContext = RequestContextUtils.getExpression(expression); + TransformFunction transformFunction = TransformFunctionFactory.get(expressionContext, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof CaseTransformFunction); + assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.STRING); + testTransformFunction(transformFunction, expectedValues); + } + } + + private void testCaseQueryWithBytesResults(List expressions, byte[][] expectedValues) { + for (String expression : expressions) { + ExpressionContext expressionContext = RequestContextUtils.getExpression(expression); + TransformFunction transformFunction = TransformFunctionFactory.get(expressionContext, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof CaseTransformFunction); + assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.BYTES); + testTransformFunction(transformFunction, expectedValues); + } + } + + private void testCaseQueryWithTimestampResults(List expressions, long[] expectedValues) { + for (String expression : expressions) { + ExpressionContext expressionContext = RequestContextUtils.getExpression(expression); + TransformFunction transformFunction = TransformFunctionFactory.get(expressionContext, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof CaseTransformFunction); + assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.TIMESTAMP); + testTransformFunction(transformFunction, expectedValues); + } + } + + private boolean[] getPredicateResults(String column, TransformFunctionType type) { + boolean[] results = new boolean[NUM_ROWS]; for (int i = 0; i < NUM_ROWS; i++) { switch (column) { case INT_SV_COLUMN: switch (type) { case EQUALS: - result[i] = (_intSVValues[i] == _intSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _intSVValues[i] == _intSVValues[INDEX_TO_COMPARE]; break; case NOT_EQUALS: - result[i] = (_intSVValues[i] != _intSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _intSVValues[i] != _intSVValues[INDEX_TO_COMPARE]; break; case GREATER_THAN: - result[i] = (_intSVValues[i] > _intSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _intSVValues[i] > _intSVValues[INDEX_TO_COMPARE]; break; case GREATER_THAN_OR_EQUAL: - result[i] = (_intSVValues[i] >= _intSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _intSVValues[i] >= _intSVValues[INDEX_TO_COMPARE]; break; case LESS_THAN: - result[i] = (_intSVValues[i] < _intSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _intSVValues[i] < _intSVValues[INDEX_TO_COMPARE]; break; case LESS_THAN_OR_EQUAL: - result[i] = (_intSVValues[i] <= _intSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _intSVValues[i] <= _intSVValues[INDEX_TO_COMPARE]; break; default: - throw new IllegalStateException("Not supported type - " + type); + throw new IllegalStateException(); } break; case LONG_SV_COLUMN: switch (type) { case EQUALS: - result[i] = (_longSVValues[i] == _longSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _longSVValues[i] == _longSVValues[INDEX_TO_COMPARE]; break; case NOT_EQUALS: - result[i] = (_longSVValues[i] != _longSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _longSVValues[i] != _longSVValues[INDEX_TO_COMPARE]; break; case GREATER_THAN: - result[i] = (_longSVValues[i] > _longSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _longSVValues[i] > _longSVValues[INDEX_TO_COMPARE]; break; case GREATER_THAN_OR_EQUAL: - result[i] = (_longSVValues[i] >= _longSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _longSVValues[i] >= _longSVValues[INDEX_TO_COMPARE]; break; case LESS_THAN: - result[i] = (_longSVValues[i] < _longSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _longSVValues[i] < _longSVValues[INDEX_TO_COMPARE]; break; case LESS_THAN_OR_EQUAL: - result[i] = (_longSVValues[i] <= _longSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _longSVValues[i] <= _longSVValues[INDEX_TO_COMPARE]; break; default: - throw new IllegalStateException("Not supported type - " + type); + throw new IllegalStateException(); } break; case FLOAT_SV_COLUMN: switch (type) { case EQUALS: - result[i] = (_floatSVValues[i] == _floatSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _floatSVValues[i] == _floatSVValues[INDEX_TO_COMPARE]; break; case NOT_EQUALS: - result[i] = (_floatSVValues[i] != _floatSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _floatSVValues[i] != _floatSVValues[INDEX_TO_COMPARE]; break; case GREATER_THAN: - result[i] = (_floatSVValues[i] > _floatSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _floatSVValues[i] > _floatSVValues[INDEX_TO_COMPARE]; break; case GREATER_THAN_OR_EQUAL: - result[i] = (_floatSVValues[i] >= _floatSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _floatSVValues[i] >= _floatSVValues[INDEX_TO_COMPARE]; break; case LESS_THAN: - result[i] = (_floatSVValues[i] < _floatSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _floatSVValues[i] < _floatSVValues[INDEX_TO_COMPARE]; break; case LESS_THAN_OR_EQUAL: - result[i] = (_floatSVValues[i] <= _floatSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _floatSVValues[i] <= _floatSVValues[INDEX_TO_COMPARE]; break; default: - throw new IllegalStateException("Not supported type - " + type); + throw new IllegalStateException(); } break; case DOUBLE_SV_COLUMN: switch (type) { case EQUALS: - result[i] = (_doubleSVValues[i] == _doubleSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _doubleSVValues[i] == _doubleSVValues[INDEX_TO_COMPARE]; break; case NOT_EQUALS: - result[i] = (_doubleSVValues[i] != _doubleSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _doubleSVValues[i] != _doubleSVValues[INDEX_TO_COMPARE]; break; case GREATER_THAN: - result[i] = (_doubleSVValues[i] > _doubleSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _doubleSVValues[i] > _doubleSVValues[INDEX_TO_COMPARE]; break; case GREATER_THAN_OR_EQUAL: - result[i] = (_doubleSVValues[i] >= _doubleSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _doubleSVValues[i] >= _doubleSVValues[INDEX_TO_COMPARE]; break; case LESS_THAN: - result[i] = (_doubleSVValues[i] < _doubleSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _doubleSVValues[i] < _doubleSVValues[INDEX_TO_COMPARE]; break; case LESS_THAN_OR_EQUAL: - result[i] = (_doubleSVValues[i] <= _doubleSVValues[INDEX_TO_COMPARE]) ? "aaa" : "bbb"; + results[i] = _doubleSVValues[i] <= _doubleSVValues[INDEX_TO_COMPARE]; break; default: - throw new IllegalStateException("Not supported type - " + type); + throw new IllegalStateException(); } break; case STRING_SV_COLUMN: switch (type) { case EQUALS: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) == 0) ? "aaa" : "bbb"; + results[i] = _stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) == 0; break; case NOT_EQUALS: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) != 0) ? "aaa" : "bbb"; + results[i] = _stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) != 0; break; case GREATER_THAN: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) > 0) ? "aaa" : "bbb"; + results[i] = _stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) > 0; break; case GREATER_THAN_OR_EQUAL: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) >= 0) ? "aaa" : "bbb"; + results[i] = _stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) >= 0; break; case LESS_THAN: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) < 0) ? "aaa" : "bbb"; + results[i] = _stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) < 0; break; case LESS_THAN_OR_EQUAL: - result[i] = (_stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) <= 0) ? "aaa" : "bbb"; + results[i] = _stringSVValues[i].compareTo(_stringSVValues[INDEX_TO_COMPARE]) <= 0; break; default: - throw new IllegalStateException("Not supported type - " + type); + throw new IllegalStateException(); } break; default: - break; + throw new IllegalStateException(); } } - return result; + return results; } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/CastTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/CastTransformFunctionTest.java index f24c3bcaf526..7ae8af7b713a 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/CastTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/CastTransformFunctionTest.java @@ -25,6 +25,8 @@ import org.apache.pinot.common.request.context.RequestContextUtils; import org.apache.pinot.spi.data.FieldSpec; import org.apache.pinot.spi.utils.ArrayCopyUtils; +import org.roaringbitmap.RoaringBitmap; +import org.testng.Assert; import org.testng.annotations.Test; import static org.apache.pinot.common.function.scalar.DataTypeConversionFunctions.cast; @@ -33,7 +35,6 @@ public class CastTransformFunctionTest extends BaseTransformFunctionTest { - @Test public void testCastTransformFunction() { ExpressionContext expression = @@ -255,4 +256,35 @@ public void testCastTransformFunctionMV() { } testTransformFunction(transformFunction, expectedArraySums); } + + @Test + public void testCastNullLiteral() { + ExpressionContext expression = RequestContextUtils.getExpression("cast(null AS INT)"); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof CastTransformFunction); + Assert.assertEquals(transformFunction.getName(), CastTransformFunction.FUNCTION_NAME); + int[] expectedValues = new int[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + roaringBitmap.add(0L, NUM_ROWS); + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } + + @Test + public void testCastNullColumn() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("cast(%s AS INT)", INT_SV_NULL_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof CastTransformFunction); + Assert.assertEquals(transformFunction.getName(), CastTransformFunction.FUNCTION_NAME); + int[] expectedValues = new int[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + roaringBitmap.add(i); + } else { + expectedValues[i] = _intSVValues[i]; + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ClpTransformFunctionsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ClpTransformFunctionsTest.java new file mode 100644 index 000000000000..67c1a6ef0890 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ClpTransformFunctionsTest.java @@ -0,0 +1,333 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.transform.function; + +import com.yscope.clp.compressorfrontend.BuiltInVariableHandlingRuleVersions; +import com.yscope.clp.compressorfrontend.EncodedMessage; +import com.yscope.clp.compressorfrontend.MessageEncoder; +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import org.apache.commons.io.FileUtils; +import org.apache.pinot.common.function.TransformFunctionType; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.request.context.RequestContextUtils; +import org.apache.pinot.core.operator.DocIdSetOperator; +import org.apache.pinot.core.operator.ProjectionOperator; +import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.core.operator.filter.MatchAllFilterOperator; +import org.apache.pinot.core.plan.DocIdSetPlanNode; +import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; +import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; +import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; +import org.apache.pinot.segment.spi.datasource.DataSource; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.spi.data.readers.GenericRow; +import org.apache.pinot.spi.exception.BadQueryRequestException; +import org.apache.pinot.spi.utils.ReadMode; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import static org.apache.pinot.spi.data.FieldSpec.DEFAULT_DIMENSION_NULL_VALUE_OF_STRING; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertThrows; +import static org.testng.Assert.fail; + + +public class ClpTransformFunctionsTest { + private static final String SEGMENT_NAME = "testSegmentForClpDecode"; + private static final String INDEX_DIR_PATH = FileUtils.getTempDirectoryPath() + File.separator + SEGMENT_NAME; + private static final String TIMESTAMP_COLUMN = "timestampColumn"; + private static final String LOGTYPE_COLUMN = "field_logtype"; + private static final String DICT_VARS_COLUMN = "field_dictionaryVars"; + private static final String ENCODED_VARS_COLUMN = "field_encodedVars"; + private static final String TEST_MESSAGE = "Started job_123 on node-987: 4 cores, 8 threads and 51.4% memory used."; + private static final int NUM_ROWS = 1000; + + private final long[] _timestampValues = new long[NUM_ROWS]; + + private final String[] _logtypeValues = new String[NUM_ROWS]; + private final String[][] _dictVarValues = new String[NUM_ROWS][]; + + private final Long[][] _encodedVarValues = new Long[NUM_ROWS][]; + + SegmentGeneratorConfig _segmentGenConfig; + protected Map _dataSourceMap; + protected ProjectionBlock _projectionBlock; + + @BeforeClass + public void setup() + throws Exception { + // Setup the schema and table config + Schema schema = new Schema.SchemaBuilder() + .addSingleValueDimension(LOGTYPE_COLUMN, FieldSpec.DataType.STRING) + .addMultiValueDimension(DICT_VARS_COLUMN, FieldSpec.DataType.STRING) + .addMultiValueDimension(ENCODED_VARS_COLUMN, FieldSpec.DataType.LONG) + .addDateTime(TIMESTAMP_COLUMN, FieldSpec.DataType.TIMESTAMP, "1:MILLISECONDS:EPOCH", "1:MILLISECONDS") + .build(); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName("testTableForClpDecode") + .setTimeColumnName(TIMESTAMP_COLUMN) + .build(); + _segmentGenConfig = new SegmentGeneratorConfig(tableConfig, schema); + _segmentGenConfig.setOutDir(INDEX_DIR_PATH); + _segmentGenConfig.setSegmentName(SEGMENT_NAME); + + // Create the rows + long currentTimeMs = System.currentTimeMillis(); + Random randomNumGen = new Random(); + MessageEncoder clpMessageEncoder = new MessageEncoder(BuiltInVariableHandlingRuleVersions.VariablesSchemaV2, + BuiltInVariableHandlingRuleVersions.VariableEncodingMethodsV1); + EncodedMessage clpEncodedMessage = new EncodedMessage(); + clpMessageEncoder.encodeMessage(TEST_MESSAGE, clpEncodedMessage); + for (int i = 0; i < NUM_ROWS; i++) { + _timestampValues[i] = currentTimeMs + randomNumGen.nextInt(365 * 24 * 3600) * 1000L; + _logtypeValues[i] = clpEncodedMessage.getLogTypeAsString(); + _dictVarValues[i] = clpEncodedMessage.getDictionaryVarsAsStrings(); + _encodedVarValues[i] = clpEncodedMessage.getEncodedVarsAsBoxedLongs(); + } + // Replace the last row with a null row + clpMessageEncoder.encodeMessage(DEFAULT_DIMENSION_NULL_VALUE_OF_STRING, clpEncodedMessage); + _logtypeValues[NUM_ROWS - 1] = clpEncodedMessage.getLogTypeAsString(); + _dictVarValues[NUM_ROWS - 1] = clpEncodedMessage.getDictionaryVarsAsStrings(); + _encodedVarValues[NUM_ROWS - 1] = clpEncodedMessage.getEncodedVarsAsBoxedLongs(); + // Corrupt a row, so we can test the default value + // NOTE: We don't corrupt the encoded variables column since that would cause clpEncodedVarsMatch to detect an + // error and abandon the batch, rendering the test useless. + _dictVarValues[NUM_ROWS - 2] = null; + + List rows = new ArrayList<>(NUM_ROWS); + for (int i = 0; i < NUM_ROWS; i++) { + GenericRow row = new GenericRow(); + row.putValue(TIMESTAMP_COLUMN, _timestampValues[i]); + row.putValue(LOGTYPE_COLUMN, _logtypeValues[i]); + row.putValue(DICT_VARS_COLUMN, _dictVarValues[i]); + row.putValue(ENCODED_VARS_COLUMN, _encodedVarValues[i]); + rows.add(row); + } + + // Build a segment + SegmentIndexCreationDriverImpl driver = new SegmentIndexCreationDriverImpl(); + try { + driver.init(_segmentGenConfig, new GenericRowRecordReader(rows)); + driver.build(); + + IndexSegment indexSegment = ImmutableSegmentLoader.load(new File(INDEX_DIR_PATH, SEGMENT_NAME), ReadMode.heap); + Set columnNames = indexSegment.getPhysicalColumnNames(); + _dataSourceMap = new HashMap<>(columnNames.size()); + for (String columnName : columnNames) { + _dataSourceMap.put(columnName, indexSegment.getDataSource(columnName)); + } + } catch (Exception ex) { + fail("Failed to build and load segment", ex); + } + + _projectionBlock = new ProjectionOperator(_dataSourceMap, + new DocIdSetOperator(new MatchAllFilterOperator(NUM_ROWS), DocIdSetPlanNode.MAX_DOC_PER_CALL)).nextBlock(); + } + + @BeforeTest + public void deleteOldIndex() { + FileUtils.deleteQuietly(new File(INDEX_DIR_PATH)); + } + + @Test + public void testClpDecode() { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("%s(%s,%s,%s)", TransformFunctionType.CLP_DECODE.getName(), LOGTYPE_COLUMN, DICT_VARS_COLUMN, + ENCODED_VARS_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof CLPDecodeTransformFunction); + + String[] expectedValues = new String[NUM_ROWS]; + Arrays.fill(expectedValues, TEST_MESSAGE); + expectedValues[NUM_ROWS - 2] = DEFAULT_DIMENSION_NULL_VALUE_OF_STRING; + expectedValues[NUM_ROWS - 1] = DEFAULT_DIMENSION_NULL_VALUE_OF_STRING; + testStringTransformFunc(transformFunction, expectedValues); + } + + @Test + public void testClpDecodeWithDefaultValue() { + String defaultValue = "default"; + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("%s(%s,%s,%s,'%s')", TransformFunctionType.CLP_DECODE.getName(), LOGTYPE_COLUMN, DICT_VARS_COLUMN, + ENCODED_VARS_COLUMN, defaultValue)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof CLPDecodeTransformFunction); + + String[] expectedValues = new String[NUM_ROWS]; + Arrays.fill(expectedValues, TEST_MESSAGE); + expectedValues[NUM_ROWS - 2] = defaultValue; + expectedValues[NUM_ROWS - 1] = DEFAULT_DIMENSION_NULL_VALUE_OF_STRING; + testStringTransformFunc(transformFunction, expectedValues); + } + + @Test + public void testClpDecodeWithInvalidArg() { + String defaultValue = "default"; + + // 1st parameter literal + assertThrows(BadQueryRequestException.class, () -> { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("%s('%s',%s,%s,'%s')", TransformFunctionType.CLP_DECODE.getName(), LOGTYPE_COLUMN, + DICT_VARS_COLUMN, ENCODED_VARS_COLUMN, defaultValue)); + TransformFunctionFactory.get(expression, _dataSourceMap); + }); + + // 2nd parameter literal + assertThrows(BadQueryRequestException.class, () -> { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("%s(%s,'%s',%s,'%s')", TransformFunctionType.CLP_DECODE.getName(), LOGTYPE_COLUMN, + DICT_VARS_COLUMN, ENCODED_VARS_COLUMN, defaultValue)); + TransformFunctionFactory.get(expression, _dataSourceMap); + }); + + // 3rd parameter literal + assertThrows(BadQueryRequestException.class, () -> { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("%s(%s,%s,'%s','%s')", TransformFunctionType.CLP_DECODE.getName(), LOGTYPE_COLUMN, + DICT_VARS_COLUMN, ENCODED_VARS_COLUMN, defaultValue)); + TransformFunctionFactory.get(expression, _dataSourceMap); + }); + + // 4th parameter identifier + assertThrows(BadQueryRequestException.class, () -> { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("%s(%s,%s,%s,%s)", TransformFunctionType.CLP_DECODE.getName(), LOGTYPE_COLUMN, + DICT_VARS_COLUMN, ENCODED_VARS_COLUMN, defaultValue)); + TransformFunctionFactory.get(expression, _dataSourceMap); + }); + + // Missing arg + assertThrows(BadQueryRequestException.class, () -> { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("%s(%s,%s)", TransformFunctionType.CLP_DECODE.getName(), LOGTYPE_COLUMN, DICT_VARS_COLUMN)); + TransformFunctionFactory.get(expression, _dataSourceMap); + }); + } + + @Test + public void testClpEncodedVarsMatch() { + String wildcardQuery; + + // Test query which will match + wildcardQuery = "*51*"; + // The query should generate three subqueries: One for an encoded integer var, one for an encoded float var, and one + // for a dictionary var, in that order. + testClpEncodedVarsMatch(wildcardQuery, 0, false); + testClpEncodedVarsMatch(wildcardQuery, 1, true); + } + + @Test + public void testClpEncodedVarsMatchWithInvalidArg() { + String wildcardQuery = "*123*"; + long subqueryIdx = 0; + + // 1st parameter literal + assertThrows(BadQueryRequestException.class, () -> { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("%s('%s',%s,'%s',%s)", TransformFunctionType.CLP_ENCODED_VARS_MATCH.getName(), LOGTYPE_COLUMN, + ENCODED_VARS_COLUMN, wildcardQuery, subqueryIdx)); + TransformFunctionFactory.get(expression, _dataSourceMap); + }); + + // 2nd parameter literal + assertThrows(BadQueryRequestException.class, () -> { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("%s(%s,'%s','%s',%s)", TransformFunctionType.CLP_ENCODED_VARS_MATCH.getName(), LOGTYPE_COLUMN, + ENCODED_VARS_COLUMN, wildcardQuery, subqueryIdx)); + TransformFunctionFactory.get(expression, _dataSourceMap); + }); + + // 3rd parameter identifier + assertThrows(BadQueryRequestException.class, () -> { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("%s(%s,%s,%s,%s)", TransformFunctionType.CLP_ENCODED_VARS_MATCH.getName(), LOGTYPE_COLUMN, + ENCODED_VARS_COLUMN, ENCODED_VARS_COLUMN, subqueryIdx)); + TransformFunctionFactory.get(expression, _dataSourceMap); + }); + + // 4th parameter identifier + assertThrows(BadQueryRequestException.class, () -> { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("%s('%s',%s,'%s',%s)", TransformFunctionType.CLP_ENCODED_VARS_MATCH.getName(), + LOGTYPE_COLUMN, ENCODED_VARS_COLUMN, wildcardQuery, ENCODED_VARS_COLUMN)); + TransformFunctionFactory.get(expression, _dataSourceMap); + }); + + // Missing args + assertThrows(BadQueryRequestException.class, () -> { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("%s(%s)", TransformFunctionType.CLP_ENCODED_VARS_MATCH.getName(), LOGTYPE_COLUMN)); + TransformFunctionFactory.get(expression, _dataSourceMap); + }); + assertThrows(BadQueryRequestException.class, () -> { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("%s(%s,%s)", TransformFunctionType.CLP_ENCODED_VARS_MATCH.getName(), LOGTYPE_COLUMN, + ENCODED_VARS_COLUMN)); + TransformFunctionFactory.get(expression, _dataSourceMap); + }); + assertThrows(BadQueryRequestException.class, () -> { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("%s(%s,%s,'%s')", TransformFunctionType.CLP_ENCODED_VARS_MATCH.getName(), LOGTYPE_COLUMN, + ENCODED_VARS_COLUMN, wildcardQuery)); + TransformFunctionFactory.get(expression, _dataSourceMap); + }); + } + + private void testClpEncodedVarsMatch(String wildcardQuery, int subqueryIdx, boolean shouldMatch) { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("%s(%s,%s,'%s',%s)", TransformFunctionType.CLP_ENCODED_VARS_MATCH.getName(), LOGTYPE_COLUMN, + ENCODED_VARS_COLUMN, wildcardQuery, subqueryIdx)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof ClpEncodedVarsMatchTransformFunction); + + int[] expectedValues = new int[NUM_ROWS]; + Arrays.fill(expectedValues, shouldMatch ? 1 : 0); + // The last row won't match since it's a null + expectedValues[NUM_ROWS - 1] = 0; + testIntTransformFunc(transformFunction, expectedValues); + } + + private void testStringTransformFunc(TransformFunction transformFunction, String[] expectedValues) { + String[] values = transformFunction.transformToStringValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + assertEquals(values[i], expectedValues[i]); + } + } + + private void testIntTransformFunc(TransformFunction transformFunction, int[] expectedValues) { + int[] values = transformFunction.transformToIntValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + assertEquals(values[i], expectedValues[i]); + } + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/CoalesceTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/CoalesceTransformFunctionTest.java index d604044d18cc..20b83f8770ca 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/CoalesceTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/CoalesceTransformFunctionTest.java @@ -18,452 +18,89 @@ */ package org.apache.pinot.core.operator.transform.function; -import java.io.File; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.Set; -import org.apache.commons.io.FileUtils; -import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.RequestContextUtils; -import org.apache.pinot.core.operator.DocIdSetOperator; -import org.apache.pinot.core.operator.ProjectionOperator; -import org.apache.pinot.core.operator.blocks.ProjectionBlock; -import org.apache.pinot.core.operator.filter.MatchAllFilterOperator; -import org.apache.pinot.core.plan.DocIdSetPlanNode; -import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; -import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; -import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; -import org.apache.pinot.segment.spi.IndexSegment; -import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; -import org.apache.pinot.segment.spi.datasource.DataSource; -import org.apache.pinot.spi.config.table.TableConfig; -import org.apache.pinot.spi.config.table.TableType; -import org.apache.pinot.spi.data.FieldSpec; -import org.apache.pinot.spi.data.Schema; -import org.apache.pinot.spi.data.readers.GenericRow; -import org.apache.pinot.spi.utils.ReadMode; -import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.roaringbitmap.RoaringBitmap; import org.testng.Assert; -import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; public class CoalesceTransformFunctionTest extends BaseTransformFunctionTest { - private static final String ENABLE_NULL_SEGMENT_NAME = "testSegment1"; - private static final String DISABLE_NULL_SEGMENT_NAME = "testSegment2"; - private static final Random RANDOM = new Random(); - - private static final int NUM_ROWS = 1000; - private static final String INT_SV_COLUMN1 = "intSV1"; - private static final String INT_SV_COLUMN2 = "intSV2"; - private static final String STRING_SV_COLUMN1 = "StringSV1"; - private static final String STRING_SV_COLUMN2 = "StringSV2"; - private static final String BIG_DECIMAL_SV_COLUMN1 = "BigDecimalSV1"; - private static final String BIG_DECIMAL_SV_COLUMN2 = "BigDecimalSV2"; - private static final String LONG_SV_COLUMN1 = "LongSV1"; - private static final String LONG_SV_COLUMN2 = "LongSV2"; - private static final String DOUBLE_SV_COLUMN1 = "DoubleSV1"; - private static final String DOUBLE_SV_COLUMN2 = "DoubleSV2"; - - private static final String FLOAT_SV_COLUMN1 = "FloatSV1"; - private static final String FLOAT_SV_COLUMN2 = "FLoatSV2"; - private final int[] _intSVValues = new int[NUM_ROWS]; - private final double[] _doubleValues = new double[NUM_ROWS]; - private final float[] _floatValues = new float[NUM_ROWS]; - private final String[] _stringSVValues = new String[NUM_ROWS]; - private Map _enableNullDataSourceMap; - private Map _disableNullDataSourceMap; - private ProjectionBlock _enableNullProjectionBlock; - private ProjectionBlock _disableNullProjectionBlock; - // Mod decides whether the first column of the same type should be null. - private static final int NULL_MOD1 = 3; - // Mod decides whether the second column of the same type should be null. - private static final int NULL_MOD2 = 5; - // Difference between two same type numeric columns. - private static final int INT_VALUE_SHIFT = 2; - private static final double DOUBLE_VALUE_SHIFT = 0.1; - private static final float FLOAT_VALUE_SHIFT = 0.1f; - - // Suffix for second string column. - private static final String SUFFIX = "column2"; - - private static String getIndexDirPath(String segmentName) { - return FileUtils.getTempDirectoryPath() + File.separator + segmentName; - } - - private static Map getDataSourceMap(Schema schema, List rows, String segmentName) - throws Exception { - TableConfig tableConfig = - new TableConfigBuilder(TableType.OFFLINE).setTableName(segmentName).setNullHandlingEnabled(true).build(); - SegmentGeneratorConfig config = new SegmentGeneratorConfig(tableConfig, schema); - config.setOutDir(getIndexDirPath(segmentName)); - config.setSegmentName(segmentName); - SegmentIndexCreationDriverImpl driver = new SegmentIndexCreationDriverImpl(); - driver.init(config, new GenericRowRecordReader(rows)); - driver.build(); - IndexSegment indexSegment = - ImmutableSegmentLoader.load(new File(getIndexDirPath(segmentName), segmentName), ReadMode.heap); - Set columnNames = indexSegment.getPhysicalColumnNames(); - Map enableNullDataSourceMap = new HashMap<>(columnNames.size()); - for (String columnName : columnNames) { - enableNullDataSourceMap.put(columnName, indexSegment.getDataSource(columnName)); - } - return enableNullDataSourceMap; - } - - private static ProjectionBlock getProjectionBlock(Map dataSourceMap) { - return new ProjectionOperator(dataSourceMap, - new DocIdSetOperator(new MatchAllFilterOperator(NUM_ROWS), DocIdSetPlanNode.MAX_DOC_PER_CALL)).nextBlock(); - } - - private static boolean isColumn1Null(int i) { - return i % NULL_MOD1 == 0; - } - - private static boolean isColumn2Null(int i) { - return i % NULL_MOD2 == 0; - } - - @BeforeClass - public void setup() - throws Exception { - // Set up two tables: one with null option enable, the other with null option disable. - // Each table one string column, and one int column with some rows set to null. - FileUtils.deleteQuietly(new File(getIndexDirPath(DISABLE_NULL_SEGMENT_NAME))); - FileUtils.deleteQuietly(new File(getIndexDirPath(ENABLE_NULL_SEGMENT_NAME))); - for (int i = 0; i < NUM_ROWS; i++) { - _intSVValues[i] = RANDOM.nextInt(); - _doubleValues[i] = RANDOM.nextDouble(); - _floatValues[i] = RANDOM.nextFloat(); - _stringSVValues[i] = "a" + RANDOM.nextInt(); - } - List rows = new ArrayList<>(NUM_ROWS); - for (int i = 0; i < NUM_ROWS; i++) { - Map map = new HashMap<>(); - map.put(INT_SV_COLUMN1, _intSVValues[i]); - map.put(INT_SV_COLUMN2, _intSVValues[i] + INT_VALUE_SHIFT); - map.put(DOUBLE_SV_COLUMN1, _doubleValues[i]); - map.put(DOUBLE_SV_COLUMN2, _doubleValues[i] + DOUBLE_VALUE_SHIFT); - map.put(FLOAT_SV_COLUMN1, _floatValues[i]); - map.put(FLOAT_SV_COLUMN2, _floatValues[i] + FLOAT_VALUE_SHIFT); - map.put(STRING_SV_COLUMN1, _stringSVValues[i]); - map.put(STRING_SV_COLUMN2, _stringSVValues[i] + SUFFIX); - map.put(BIG_DECIMAL_SV_COLUMN1, BigDecimal.valueOf(_intSVValues[i])); - map.put(BIG_DECIMAL_SV_COLUMN2, BigDecimal.valueOf(_intSVValues[i] + INT_VALUE_SHIFT)); - map.put(LONG_SV_COLUMN1, _intSVValues[i]); - map.put(LONG_SV_COLUMN2, _intSVValues[i] + INT_VALUE_SHIFT); - - if (isColumn1Null(i)) { - map.put(INT_SV_COLUMN1, null); - map.put(STRING_SV_COLUMN1, null); - map.put(BIG_DECIMAL_SV_COLUMN1, null); - map.put(LONG_SV_COLUMN1, null); - map.put(DOUBLE_SV_COLUMN1, null); - map.put(FLOAT_SV_COLUMN1, null); - } - if (isColumn2Null(i)) { - map.put(INT_SV_COLUMN2, null); - map.put(STRING_SV_COLUMN2, null); - map.put(LONG_SV_COLUMN2, null); - map.put(BIG_DECIMAL_SV_COLUMN2, null); - map.put(DOUBLE_SV_COLUMN2, null); - map.put(FLOAT_SV_COLUMN2, null); - } - GenericRow row = new GenericRow(); - row.init(map); - rows.add(row); - } - Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(INT_SV_COLUMN1, FieldSpec.DataType.INT) - .addSingleValueDimension(INT_SV_COLUMN2, FieldSpec.DataType.INT) - .addSingleValueDimension(STRING_SV_COLUMN1, FieldSpec.DataType.STRING) - .addSingleValueDimension(STRING_SV_COLUMN2, FieldSpec.DataType.STRING) - .addSingleValueDimension(LONG_SV_COLUMN1, FieldSpec.DataType.LONG) - .addSingleValueDimension(LONG_SV_COLUMN2, FieldSpec.DataType.LONG) - .addSingleValueDimension(DOUBLE_SV_COLUMN1, FieldSpec.DataType.DOUBLE) - .addSingleValueDimension(DOUBLE_SV_COLUMN2, FieldSpec.DataType.DOUBLE) - .addSingleValueDimension(FLOAT_SV_COLUMN1, FieldSpec.DataType.FLOAT) - .addSingleValueDimension(FLOAT_SV_COLUMN2, FieldSpec.DataType.FLOAT) - .addMetric(BIG_DECIMAL_SV_COLUMN1, FieldSpec.DataType.BIG_DECIMAL) - .addMetric(BIG_DECIMAL_SV_COLUMN2, FieldSpec.DataType.BIG_DECIMAL).build(); - _enableNullDataSourceMap = getDataSourceMap(schema, rows, ENABLE_NULL_SEGMENT_NAME); - _enableNullProjectionBlock = getProjectionBlock(_enableNullDataSourceMap); - _disableNullDataSourceMap = getDataSourceMap(schema, rows, DISABLE_NULL_SEGMENT_NAME); - _disableNullProjectionBlock = getProjectionBlock(_disableNullDataSourceMap); - } - - private static void testIntTransformFunction(ExpressionContext expression, int[] expectedValues, - ProjectionBlock projectionBlock, Map dataSourceMap) - throws Exception { - int[] actualValues = - TransformFunctionFactory.get(expression, dataSourceMap).transformToIntValuesSV(projectionBlock); - for (int i = 0; i < NUM_ROWS; i++) { - Assert.assertEquals(actualValues[i], expectedValues[i]); - } - } - - private static void testStringTransformFunction(ExpressionContext expression, String[] expectedValues, - ProjectionBlock projectionBlock, Map dataSourceMap) - throws Exception { - String[] actualValues = - TransformFunctionFactory.get(expression, dataSourceMap).transformToStringValuesSV(projectionBlock); - for (int i = 0; i < NUM_ROWS; i++) { - Assert.assertEquals(actualValues[i], expectedValues[i]); - } - } - - private static void testLongTransformFunction(ExpressionContext expression, long[] expectedValues, - ProjectionBlock projectionBlock, Map dataSourceMap) - throws Exception { - long[] actualValues = - TransformFunctionFactory.get(expression, dataSourceMap).transformToLongValuesSV(projectionBlock); - for (int i = 0; i < NUM_ROWS; i++) { - Assert.assertEquals(actualValues[i], expectedValues[i]); - } - } - - private static void testDoubleTransformFunction(ExpressionContext expression, double[] expectedValues, - ProjectionBlock projectionBlock, Map dataSourceMap) - throws Exception { - double[] actualValues = - TransformFunctionFactory.get(expression, dataSourceMap).transformToDoubleValuesSV(projectionBlock); - for (int i = 0; i < NUM_ROWS; i++) { - Assert.assertEquals(actualValues[i], expectedValues[i]); - } - } - - private static void testFloatTransformFunction(ExpressionContext expression, float[] expectedValues, - ProjectionBlock projectionBlock, Map dataSourceMap) - throws Exception { - float[] actualValues = - TransformFunctionFactory.get(expression, dataSourceMap).transformToFloatValuesSV(projectionBlock); - for (int i = 0; i < NUM_ROWS; i++) { - Assert.assertEquals(actualValues[i], expectedValues[i]); - } - } - - private static void testBigDecimalTransformFunction(ExpressionContext expression, BigDecimal[] expectedValues, - ProjectionBlock projectionBlock, Map dataSourceMap) - throws Exception { - BigDecimal[] actualValues = - TransformFunctionFactory.get(expression, dataSourceMap).transformToBigDecimalValuesSV(projectionBlock); - for (int i = 0; i < NUM_ROWS; i++) { - Assert.assertEquals(actualValues[i], expectedValues[i]); - } - } - - // Test the Coalesce on two Int columns where one or the other or both can be null. @Test - public void testCoalesceIntColumns() - throws Exception { - ExpressionContext coalesceExpr = - RequestContextUtils.getExpression(String.format("COALESCE(%s,%s)", INT_SV_COLUMN1, INT_SV_COLUMN2)); - TransformFunction coalesceTransformFunction = TransformFunctionFactory.get(coalesceExpr, _enableNullDataSourceMap); - Assert.assertEquals(coalesceTransformFunction.getName(), "coalesce"); - int[] expectedResults = new int[NUM_ROWS]; - for (int i = 0; i < NUM_ROWS; i++) { - if (isColumn1Null(i) && isColumn2Null(i)) { - expectedResults[i] = CoalesceTransformFunction.NULL_INT; - } else if (isColumn1Null(i)) { - expectedResults[i] = _intSVValues[i] + INT_VALUE_SHIFT; - } else if (isColumn2Null(i)) { - expectedResults[i] = _intSVValues[i]; - } else { - expectedResults[i] = _intSVValues[i]; - } - } - testIntTransformFunction(coalesceExpr, expectedResults, _enableNullProjectionBlock, _enableNullDataSourceMap); - testIntTransformFunction(coalesceExpr, expectedResults, _disableNullProjectionBlock, _disableNullDataSourceMap); - } + public void testCoalesceIntColumns() { + TransformFunction coalesceFunc = TransformFunctionFactory.get( + RequestContextUtils.getExpression(String.format("COALESCE(%s,%s)", INT_SV_NULL_COLUMN, LONG_SV_COLUMN)), + _dataSourceMap); - // Test the Coalesce on two long columns where one or the other or both can be null. - @Test - public void testCoalesceLongColumns() - throws Exception { - ExpressionContext coalesceExpr = - RequestContextUtils.getExpression(String.format("COALESCE(%s,%s)", LONG_SV_COLUMN1, LONG_SV_COLUMN2)); - TransformFunction coalesceTransformFunction = TransformFunctionFactory.get(coalesceExpr, _enableNullDataSourceMap); - Assert.assertEquals(coalesceTransformFunction.getName(), "coalesce"); long[] expectedResults = new long[NUM_ROWS]; for (int i = 0; i < NUM_ROWS; i++) { - if (isColumn1Null(i) && isColumn2Null(i)) { - expectedResults[i] = CoalesceTransformFunction.NULL_LONG; - } else if (isColumn1Null(i)) { - expectedResults[i] = _intSVValues[i] + INT_VALUE_SHIFT; - } else if (isColumn2Null(i)) { - expectedResults[i] = _intSVValues[i]; + if (isNullRow(i)) { + expectedResults[i] = _longSVValues[i]; } else { expectedResults[i] = _intSVValues[i]; } } - testLongTransformFunction(coalesceExpr, expectedResults, _enableNullProjectionBlock, _enableNullDataSourceMap); - testLongTransformFunction(coalesceExpr, expectedResults, _disableNullProjectionBlock, _disableNullDataSourceMap); - } - - // Test the Coalesce on two float columns where one or the other or both can be null. - @Test - public void testCoalesceFloatColumns() - throws Exception { - ExpressionContext coalesceExpr = - RequestContextUtils.getExpression(String.format("COALESCE(%s,%s)", FLOAT_SV_COLUMN1, FLOAT_SV_COLUMN2)); - TransformFunction coalesceTransformFunction = TransformFunctionFactory.get(coalesceExpr, _enableNullDataSourceMap); - Assert.assertEquals(coalesceTransformFunction.getName(), "coalesce"); - float[] expectedResults = new float[NUM_ROWS]; - for (int i = 0; i < NUM_ROWS; i++) { - if (isColumn1Null(i) && isColumn2Null(i)) { - expectedResults[i] = CoalesceTransformFunction.NULL_FLOAT; - } else if (isColumn1Null(i)) { - expectedResults[i] = _floatValues[i] + FLOAT_VALUE_SHIFT; - } else if (isColumn2Null(i)) { - expectedResults[i] = _floatValues[i]; - } else { - expectedResults[i] = _floatValues[i]; - } - } - testFloatTransformFunction(coalesceExpr, expectedResults, _enableNullProjectionBlock, _enableNullDataSourceMap); - testFloatTransformFunction(coalesceExpr, expectedResults, _disableNullProjectionBlock, _disableNullDataSourceMap); + testTransformFunction(coalesceFunc, expectedResults); } - // Test the Coalesce on two double columns where one or the other or both can be null. @Test - public void testCoalesceDoubleColumns() - throws Exception { - ExpressionContext coalesceExpr = - RequestContextUtils.getExpression(String.format("COALESCE(%s,%s)", DOUBLE_SV_COLUMN1, DOUBLE_SV_COLUMN2)); - TransformFunction coalesceTransformFunction = TransformFunctionFactory.get(coalesceExpr, _enableNullDataSourceMap); - Assert.assertEquals(coalesceTransformFunction.getName(), "coalesce"); - double[] expectedResults = new double[NUM_ROWS]; - for (int i = 0; i < NUM_ROWS; i++) { - if (isColumn1Null(i) && isColumn2Null(i)) { - expectedResults[i] = CoalesceTransformFunction.NULL_DOUBLE; - } else if (isColumn1Null(i)) { - expectedResults[i] = _doubleValues[i] + DOUBLE_VALUE_SHIFT; - } else if (isColumn2Null(i)) { - expectedResults[i] = _doubleValues[i]; - } else { - expectedResults[i] = _doubleValues[i]; - } - } - testDoubleTransformFunction(coalesceExpr, expectedResults, _enableNullProjectionBlock, _enableNullDataSourceMap); - testDoubleTransformFunction(coalesceExpr, expectedResults, _disableNullProjectionBlock, _disableNullDataSourceMap); - } - - // Test the Coalesce on two big decimal columns where one or the other or both can be null. - @Test - public void testCoalesceBigDecimalColumns() - throws Exception { - ExpressionContext coalesceExpr = RequestContextUtils.getExpression( - String.format("COALESCE(%s,%s)", BIG_DECIMAL_SV_COLUMN1, BIG_DECIMAL_SV_COLUMN2)); - TransformFunction coalesceTransformFunction = TransformFunctionFactory.get(coalesceExpr, _enableNullDataSourceMap); - Assert.assertEquals(coalesceTransformFunction.getName(), "coalesce"); - BigDecimal[] expectedResults = new BigDecimal[NUM_ROWS]; + public void testCoalesceIntColumnsAndLiterals() { + final int intLiteral = 313; + TransformFunction coalesceFunc = TransformFunctionFactory.get( + RequestContextUtils.getExpression(String.format("COALESCE(%s,%s)", INT_SV_NULL_COLUMN, intLiteral)), + _dataSourceMap); + Assert.assertEquals(coalesceFunc.getName(), "coalesce"); + int[] expectedResults = new int[NUM_ROWS]; for (int i = 0; i < NUM_ROWS; i++) { - if (isColumn1Null(i) && isColumn2Null(i)) { - expectedResults[i] = CoalesceTransformFunction.NULL_BIG_DECIMAL; - } else if (isColumn1Null(i)) { - expectedResults[i] = BigDecimal.valueOf(_intSVValues[i] + INT_VALUE_SHIFT); - } else if (isColumn2Null(i)) { - expectedResults[i] = BigDecimal.valueOf(_intSVValues[i]); + if (isNullRow(i)) { + expectedResults[i] = intLiteral; } else { - expectedResults[i] = BigDecimal.valueOf(_intSVValues[i]); + expectedResults[i] = _intSVValues[i]; } } - testBigDecimalTransformFunction(coalesceExpr, expectedResults, _enableNullProjectionBlock, - _enableNullDataSourceMap); - testBigDecimalTransformFunction(coalesceExpr, expectedResults, _disableNullProjectionBlock, - _disableNullDataSourceMap); + testTransformFunction(coalesceFunc, expectedResults); } - // Test the Coalesce on two String columns where one or the other or both can be null. @Test - public void testCoalesceStringColumns() - throws Exception { - ExpressionContext coalesceExpr = - RequestContextUtils.getExpression(String.format("COALESCE(%s,%s)", STRING_SV_COLUMN1, STRING_SV_COLUMN2)); - TransformFunction coalesceTransformFunction = TransformFunctionFactory.get(coalesceExpr, _enableNullDataSourceMap); - Assert.assertEquals(coalesceTransformFunction.getName(), "coalesce"); + public void testDifferentLiteralArgs() { + TransformFunction coalesceFunc = TransformFunctionFactory.get( + RequestContextUtils.getExpression(String.format("COALESCE(%s, '%s')", STRING_SV_NULL_COLUMN, 234)), + _dataSourceMap); String[] expectedResults = new String[NUM_ROWS]; for (int i = 0; i < NUM_ROWS; i++) { - if (isColumn1Null(i) && isColumn2Null(i)) { - expectedResults[i] = CoalesceTransformFunction.NULL_STRING; - } else if (isColumn1Null(i)) { - expectedResults[i] = _stringSVValues[i] + SUFFIX; - } else if (isColumn2Null(i)) { - expectedResults[i] = _stringSVValues[i]; + if (isNullRow(i)) { + expectedResults[i] = "234"; } else { expectedResults[i] = _stringSVValues[i]; } } - testStringTransformFunction(coalesceExpr, expectedResults, _enableNullProjectionBlock, _enableNullDataSourceMap); - testStringTransformFunction(coalesceExpr, expectedResults, _disableNullProjectionBlock, _disableNullDataSourceMap); + testTransformFunction(coalesceFunc, expectedResults); } - // Test that non-column-names appear in one of the argument. - @Test - public void testIllegalColumnName() - throws Exception { - ExpressionContext coalesceExpr = - RequestContextUtils.getExpression(String.format("COALESCE(%s,%s)", _stringSVValues[0], STRING_SV_COLUMN1)); - Assert.assertThrows(RuntimeException.class, () -> { - TransformFunctionFactory.get(coalesceExpr, _enableNullDataSourceMap); - }); - Assert.assertThrows(RuntimeException.class, () -> { - TransformFunctionFactory.get(coalesceExpr, _disableNullDataSourceMap); - }); - } - - // Test that wrong data type is illegal argument. - @Test - public void testIllegalArgType() - throws Exception { - ExpressionContext coalesceExpr = - RequestContextUtils.getExpression(String.format("COALESCE(%s,%s)", TIMESTAMP_COLUMN, STRING_SV_COLUMN)); - Assert.assertThrows(RuntimeException.class, () -> { - TransformFunctionFactory.get(coalesceExpr, _enableNullDataSourceMap); - }); - Assert.assertThrows(RuntimeException.class, () -> { - TransformFunctionFactory.get(coalesceExpr, _disableNullDataSourceMap); - }); - } - // Test the Coalesce on two Int columns (where one or the other or both can be null) and litrals. @Test - public void testCoalesceIntColumnsAndLiterals() - throws Exception { - ExpressionContext coalesceExpr = - RequestContextUtils.getExpression(String.format("COALESCE(%s,%s,%s)", INT_SV_COLUMN1, INT_SV_COLUMN2, 314)); - TransformFunction coalesceTransformFunction = TransformFunctionFactory.get(coalesceExpr, _enableNullDataSourceMap); - Assert.assertEquals(coalesceTransformFunction.getName(), "coalesce"); - int[] expectedResults = new int[NUM_ROWS]; + public void testCoalesceNullLiteral() { + TransformFunction coalesceFunc = TransformFunctionFactory.get( + RequestContextUtils.getExpression(String.format("COALESCE((1 + null), %s)", INT_SV_NULL_COLUMN)), + _dataSourceMap); + double[] expectedResults = new double[NUM_ROWS]; + RoaringBitmap expectedNull = new RoaringBitmap(); for (int i = 0; i < NUM_ROWS; i++) { - if (isColumn1Null(i) && isColumn2Null(i)) { - expectedResults[i] = 314; - } else if (isColumn1Null(i)) { - expectedResults[i] = _intSVValues[i] + INT_VALUE_SHIFT; - } else if (isColumn2Null(i)) { - expectedResults[i] = _intSVValues[i]; + if (isNullRow(i)) { + expectedNull.add(i); } else { expectedResults[i] = _intSVValues[i]; } } - testIntTransformFunction(coalesceExpr, expectedResults, _enableNullProjectionBlock, _enableNullDataSourceMap); - testIntTransformFunction(coalesceExpr, expectedResults, _disableNullProjectionBlock, _disableNullDataSourceMap); + testTransformFunctionWithNull(coalesceFunc, expectedResults, expectedNull); } - // Test that all arguments have to be same type. @Test - public void testDifferentArgumentType() - throws Exception { - ExpressionContext coalesceExpr = - RequestContextUtils.getExpression(String.format("COALESCE(%s,%s)", INT_SV_COLUMN1, STRING_SV_COLUMN1)); - Assert.assertThrows(RuntimeException.class, () -> { - TransformFunctionFactory.get(coalesceExpr, _enableNullDataSourceMap); - }); - Assert.assertThrows(RuntimeException.class, () -> { - TransformFunctionFactory.get(coalesceExpr, _disableNullDataSourceMap); - }); + public void testCoalesceNullNullLiteral() { + TransformFunction coalesceFunc = TransformFunctionFactory.get( + RequestContextUtils.getExpression("COALESCE(null, null)"), _dataSourceMap); + double[] expectedResults = new double[NUM_ROWS]; + RoaringBitmap expectedNull = new RoaringBitmap(); + expectedNull.add(0L, NUM_ROWS); + testTransformFunctionWithNull(coalesceFunc, expectedResults, expectedNull); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/DateTimeConversionTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/DateTimeConversionTransformFunctionTest.java index 2e1375f5e5e0..1d400922c80d 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/DateTimeConversionTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/DateTimeConversionTransformFunctionTest.java @@ -24,6 +24,7 @@ import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.exception.BadQueryRequestException; +import org.roaringbitmap.RoaringBitmap; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -65,10 +66,33 @@ public Object[][] testIllegalArguments() { }, new Object[]{"dateTimeConvert(5,'1:MILLISECONDS:EPOCH','1:MINUTES:EPOCH','1:MINUTES')"}, new Object[]{ String.format("dateTimeConvert(%s,'1:MILLISECONDS:EPOCH','1:MINUTES:EPOCH','1:MINUTES')", INT_MV_COLUMN) }, new Object[]{ - String.format("dateTimeConvert(%s,'1:MILLISECONDS:EPOCH','1:MINUTES:EPOCH','MINUTES')", TIME_COLUMN) + String.format("dateTimeConvert(%s,'1:MILLISECONDS:EPOCH','1:MINUTES:EPOCH','MINUTES:1')", TIME_COLUMN) }, new Object[]{ String.format("dateTimeConvert(%s,%s,'1:MINUTES:EPOCH','1:MINUTES')", TIME_COLUMN, INT_SV_COLUMN) } }; } + + @Test + public void testDateTimeConversionTransformFunctionNullColumn() { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("dateTimeConvert(%s,'1:MILLISECONDS:EPOCH','1:MINUTES:EPOCH','1:MINUTES')", + TIMESTAMP_COLUMN_NULL)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + assertTrue(transformFunction instanceof DateTimeConversionTransformFunction); + assertEquals(transformFunction.getName(), DateTimeConversionTransformFunction.FUNCTION_NAME); + TransformResultMetadata resultMetadata = transformFunction.getResultMetadata(); + assertTrue(resultMetadata.isSingleValue()); + assertEquals(resultMetadata.getDataType(), DataType.LONG); + long[] expectedValues = new long[NUM_ROWS]; + RoaringBitmap expectedNulls = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + expectedNulls.add(i); + } else { + expectedValues[i] = TimeUnit.MILLISECONDS.toMinutes(_timeValues[i]); + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, expectedNulls); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/DateTimeConversionWindowHopTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/DateTimeConversionWindowHopTransformFunctionTest.java new file mode 100644 index 000000000000..61c6a1e71d6e --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/DateTimeConversionWindowHopTransformFunctionTest.java @@ -0,0 +1,141 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.transform.function; + +import java.util.concurrent.TimeUnit; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.request.context.RequestContextUtils; +import org.apache.pinot.core.operator.transform.TransformResultMetadata; +import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.exception.BadQueryRequestException; +import org.joda.time.DateTimeZone; +import org.joda.time.format.DateTimeFormat; +import org.joda.time.format.DateTimeFormatter; +import org.roaringbitmap.RoaringBitmap; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + + +public class DateTimeConversionWindowHopTransformFunctionTest extends BaseTransformFunctionTest { + @Test + public void testDateTimeConversionWindowHopEpochTransformFunction() { + // NOTE: functionality of DateTimeConverterWindowHop is covered in DateTimeConverterWindowHop + ExpressionContext expression = RequestContextUtils.getExpression(String.format( + "dateTimeConvertWindowHop(%s,'1:MILLISECONDS:EPOCH'," + "'1:MINUTES:EPOCH','1:MINUTES', '2:MINUTES')", + TIME_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + + assertTrue(transformFunction instanceof DateTimeConversionHopTransformFunction); + assertEquals(transformFunction.getName(), DateTimeConversionHopTransformFunction.FUNCTION_NAME); + + TransformResultMetadata resultMetadata = transformFunction.getResultMetadata(); + assertFalse(resultMetadata.isSingleValue()); + assertEquals(resultMetadata.getDataType(), FieldSpec.DataType.LONG); + long[][] expectedValues = new long[NUM_ROWS][]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = new long[2]; + expectedValues[i][0] = TimeUnit.MILLISECONDS.toMinutes(_timeValues[i]); + expectedValues[i][1] = TimeUnit.MILLISECONDS.toMinutes(_timeValues[i]) - 1; + } + testTransformFunctionMV(transformFunction, expectedValues); + } + + @Test + public void testDateTimeWindowHopSDFTransformFunction() { + // NOTE: functionality of DateTimeConverterWindowHop is covered in DateTimeConverterWindowHop + ExpressionContext expression = RequestContextUtils.getExpression(String.format( + "dateTimeConvertWindowHop(%s,'1:MILLISECONDS:EPOCH'," + + "'1:MINUTES:SIMPLE_DATE_FORMAT:yyyy-MM-dd HH:mm tz(GMT)','1:MINUTES', '2:MINUTES')", TIME_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + + assertTrue(transformFunction instanceof DateTimeConversionHopTransformFunction); + assertEquals(transformFunction.getName(), DateTimeConversionHopTransformFunction.FUNCTION_NAME); + + TransformResultMetadata resultMetadata = transformFunction.getResultMetadata(); + assertFalse(resultMetadata.isSingleValue()); + assertEquals(resultMetadata.getDataType(), FieldSpec.DataType.STRING); + + DateTimeFormatter formatter = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm").withZone(DateTimeZone.UTC); + + String[][] expectedValues = new String[NUM_ROWS][]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = new String[2]; + expectedValues[i][0] = formatter.print(_timeValues[i]); + expectedValues[i][1] = formatter.print(_timeValues[i] - 60 * 1000); + } + testTransformFunctionMV(transformFunction, expectedValues); + } + + @Test(dataProvider = "testIllegalArguments", expectedExceptions = {BadQueryRequestException.class}) + public void testIllegalArguments(String expressionStr) { + ExpressionContext expression = RequestContextUtils.getExpression(expressionStr); + TransformFunctionFactory.get(expression, _dataSourceMap); + } + + @DataProvider(name = "testIllegalArguments") + public Object[][] testIllegalArguments() { + return new Object[][]{ + new Object[]{ + String.format("dateTimeConvertWindowHop(%s,'1:MILLISECONDS:EPOCH','1:MINUTES:EPOCH', '1:MINUTE')", + TIME_COLUMN) + }, new Object[]{ + "dateTimeConvertWindowHop(5,'1:MILLISECONDS:EPOCH','1:MINUTES:EPOCH','1:MINUTES', '2:MINUTES')" + }, new Object[]{ + String.format( + "dateTimeConvertWindowHop(%s,'1:MILLISECONDS:EPOCH'," + "'1:MINUTES:EPOCH','1:MINUTES', '2:MINUTES')", + LONG_MV_COLUMN) + }, new Object[]{ + String.format( + "dateTimeConvertWindowHop(%s,'1:MILLISECONDS:EPOCH'," + "'1:MINUTES:EPOCH','MINUTES:1', '2:MINUTES')", + TIME_COLUMN) + }, new Object[]{ + String.format("dateTimeConvertWindowHop(%s, %s,'1:MINUTES:EPOCH'," + "'1:MINUTES', '2:MINUTES')", TIME_COLUMN, + INT_SV_COLUMN) + } + }; + } + + @Test + public void testDateTimeConversionTransformFunctionNullColumn() { + ExpressionContext expression = RequestContextUtils.getExpression(String.format( + "dateTimeConvertWindowHop(%s,'1:MILLISECONDS:EPOCH','1:MINUTES:EPOCH'," + "'1:MINUTES', '2:MINUTES')", + TIMESTAMP_COLUMN_NULL)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + assertTrue(transformFunction instanceof DateTimeConversionHopTransformFunction); + assertEquals(transformFunction.getName(), DateTimeConversionHopTransformFunction.FUNCTION_NAME); + TransformResultMetadata resultMetadata = transformFunction.getResultMetadata(); + assertEquals(resultMetadata.getDataType(), FieldSpec.DataType.LONG); + RoaringBitmap expectedNulls = new RoaringBitmap(); + long[][] expectedValues = new long[NUM_ROWS][]; + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + expectedNulls.add(i); + } else { + expectedValues[i] = new long[2]; + expectedValues[i][0] = TimeUnit.MILLISECONDS.toMinutes(_timeValues[i]); + expectedValues[i][1] = TimeUnit.MILLISECONDS.toMinutes(_timeValues[i]) - 1; + } + } + testTransformFunctionMVWithNull(transformFunction, expectedValues, expectedNulls); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/DateTimeTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/DateTimeTransformFunctionTest.java index 7918f269fca3..f9325c0e04c0 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/DateTimeTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/DateTimeTransformFunctionTest.java @@ -22,11 +22,13 @@ import org.apache.pinot.common.function.scalar.DateTimeFunctions; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.RequestContextUtils; +import org.roaringbitmap.RoaringBitmap; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; public class DateTimeTransformFunctionTest extends BaseTransformFunctionTest { @@ -89,6 +91,25 @@ public void testUTC(String function, LongToIntFunction expected, Class expectedClass) { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("%s(%s)", function, TIMESTAMP_COLUMN_NULL)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(expectedClass.isInstance(transformFunction)); + int[] values = transformFunction.transformToIntValuesSV(_projectionBlock); + RoaringBitmap nullBitmap = transformFunction.getNullBitmap(_projectionBlock); + + for (int i = 0; i < _projectionBlock.getNumDocs(); i++) { + if (isNullRow(i)) { + assertTrue(nullBitmap.contains(i)); + } else { + assertEquals(values[i], expected.applyAsInt(_timeValues[i])); + } + } + } + @Test(dataProvider = "testCasesZoned") public void testZoned(String function, ZonedTimeFunction expected, Class expectedClass) { for (String zone : new String[]{"Europe/Berlin", "America/New_York", "Asia/Katmandu"}) { diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/DistinctFromTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/DistinctFromTransformFunctionTest.java index d9badf9e9986..654314398117 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/DistinctFromTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/DistinctFromTransformFunctionTest.java @@ -51,22 +51,25 @@ import org.testng.annotations.Test; -public class DistinctFromTransformFunctionTest { - private static final String ENABLE_NULL_SEGMENT_NAME = "testSegment1"; - private static final String DISABLE_NULL_SEGMENT_NAME = "testSegment2"; - private static final String IS_DISTINCT_FROM_EXPR = "%s IS DISTINCT FROM %s"; - private static final String IS_NOT_DISTINCT_FROM_EXPR = "%s IS NOT DISTINCT FROM %s"; - private static final Random RANDOM = new Random(); - - private static final int NUM_ROWS = 1000; +public abstract class DistinctFromTransformFunctionTest { + private static final String SEGMENT_NAME = "testSegment"; private static final String INT_SV_COLUMN = "intSV"; private static final String INT_SV_NULL_COLUMN = "intSV2"; + private static final Random RANDOM = new Random(); + private static final int NUM_ROWS = 1000; + private static final int VALUE_MOD = 3; + + private final boolean _isDistinctFrom; + private final String _expression; private final int[] _intSVValues = new int[NUM_ROWS]; - private Map _enableNullDataSourceMap; - private Map _disableNullDataSourceMap; - private ProjectionBlock _enableNullProjectionBlock; - private ProjectionBlock _disableNullProjectionBlock; - protected static final int VALUE_MOD = 3; + + private Map _dataSourceMap; + private ProjectionBlock _projectionBlock; + + DistinctFromTransformFunctionTest(boolean isDistinctFrom) { + _isDistinctFrom = isDistinctFrom; + _expression = _isDistinctFrom ? "%s IS DISTINCT FROM %s" : "%s IS NOT DISTINCT FROM %s"; + } private static String getIndexDirPath(String segmentName) { return FileUtils.getTempDirectoryPath() + File.separator + segmentName; @@ -112,12 +115,9 @@ private static boolean isNullRow(int i) { @BeforeClass public void setup() throws Exception { - // Set up two tables: one with null option enable, the other with null option disable. - // Each table has two int columns. - // One column with every row filled in with random integer number. - // The other column has 1/3 rows equal to first column, 1/3 rows not equal to first column and 1/3 null rows. - FileUtils.deleteQuietly(new File(getIndexDirPath(DISABLE_NULL_SEGMENT_NAME))); - FileUtils.deleteQuietly(new File(getIndexDirPath(ENABLE_NULL_SEGMENT_NAME))); + // Sets up a table with two integer columns: one column with every row filled in with an integer number; the other + // column with 1/3 rows equal to first column, 1/3 rows not equal to first column, and 1/3 rows as null. + FileUtils.deleteQuietly(new File(getIndexDirPath(SEGMENT_NAME))); for (int i = 0; i < NUM_ROWS; i++) { _intSVValues[i] = RANDOM.nextInt(); } @@ -139,10 +139,8 @@ public void setup() Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(INT_SV_COLUMN, FieldSpec.DataType.INT) .addSingleValueDimension(INT_SV_NULL_COLUMN, FieldSpec.DataType.INT).build(); - _enableNullDataSourceMap = getDataSourceMap(schema, rows, ENABLE_NULL_SEGMENT_NAME); - _enableNullProjectionBlock = getProjectionBlock(_enableNullDataSourceMap); - _disableNullDataSourceMap = getDataSourceMap(schema, rows, DISABLE_NULL_SEGMENT_NAME); - _disableNullProjectionBlock = getProjectionBlock(_disableNullDataSourceMap); + _dataSourceMap = getDataSourceMap(schema, rows, SEGMENT_NAME); + _projectionBlock = getProjectionBlock(_dataSourceMap); } protected void testTransformFunction(ExpressionContext expression, boolean[] expectedValues, @@ -172,143 +170,119 @@ private TransformFunction getTransformFunctionInstance(ExpressionContext express return TransformFunctionFactory.get(expression, dataSourceMap); } - // Test that left column of the operator has null values and right column is not null. @Test public void testDistinctFromLeftNull() throws Exception { - ExpressionContext isDistinctFromExpression = - RequestContextUtils.getExpression(String.format(IS_DISTINCT_FROM_EXPR, INT_SV_NULL_COLUMN, INT_SV_COLUMN)); - TransformFunction isDistinctFromTransformFunction = - TransformFunctionFactory.get(isDistinctFromExpression, _enableNullDataSourceMap); - Assert.assertEquals(isDistinctFromTransformFunction.getName(), "is_distinct_from"); - ExpressionContext isNotDistinctFromExpression = - RequestContextUtils.getExpression(String.format(IS_NOT_DISTINCT_FROM_EXPR, INT_SV_NULL_COLUMN, INT_SV_COLUMN)); - TransformFunction isNotDistinctFromTransformFunction = - TransformFunctionFactory.get(isNotDistinctFromExpression, _enableNullDataSourceMap); - Assert.assertEquals(isNotDistinctFromTransformFunction.getName(), "is_not_distinct_from"); - boolean[] isDistinctFromExpectedIntValues = new boolean[NUM_ROWS]; - boolean[] isNotDistinctFromExpectedIntValues = new boolean[NUM_ROWS]; + ExpressionContext expression = + RequestContextUtils.getExpression(String.format(_expression, INT_SV_NULL_COLUMN, INT_SV_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertEquals(transformFunction.getName(), _isDistinctFrom ? "is_distinct_from" : "is_not_distinct_from"); + boolean[] expectedIntValues = new boolean[NUM_ROWS]; for (int i = 0; i < NUM_ROWS; i++) { if (isEqualRow(i)) { - isDistinctFromExpectedIntValues[i] = false; - isNotDistinctFromExpectedIntValues[i] = true; + expectedIntValues[i] = !_isDistinctFrom; } else if (isNotEqualRow(i)) { - isDistinctFromExpectedIntValues[i] = true; - isNotDistinctFromExpectedIntValues[i] = false; + expectedIntValues[i] = _isDistinctFrom; } else if (isNullRow(i)) { - isDistinctFromExpectedIntValues[i] = true; - isNotDistinctFromExpectedIntValues[i] = false; + expectedIntValues[i] = _isDistinctFrom; } } - testTransformFunction(isDistinctFromExpression, isDistinctFromExpectedIntValues, _enableNullProjectionBlock, - _enableNullDataSourceMap); - testTransformFunction(isNotDistinctFromExpression, isNotDistinctFromExpectedIntValues, _enableNullProjectionBlock, - _enableNullDataSourceMap); - testTransformFunction(isDistinctFromExpression, isDistinctFromExpectedIntValues, _disableNullProjectionBlock, - _disableNullDataSourceMap); - testTransformFunction(isNotDistinctFromExpression, isNotDistinctFromExpectedIntValues, _disableNullProjectionBlock, - _disableNullDataSourceMap); + + testTransformFunction(expression, expectedIntValues, _projectionBlock, _dataSourceMap); } - // Test that right column of the operator has null values and left column is not null. @Test public void testDistinctFromRightNull() throws Exception { - ExpressionContext isDistinctFromExpression = - RequestContextUtils.getExpression(String.format(IS_DISTINCT_FROM_EXPR, INT_SV_COLUMN, INT_SV_NULL_COLUMN)); - TransformFunction isDistinctFromTransformFunction = - TransformFunctionFactory.get(isDistinctFromExpression, _enableNullDataSourceMap); - Assert.assertEquals(isDistinctFromTransformFunction.getName(), "is_distinct_from"); - ExpressionContext isNotDistinctFromExpression = - RequestContextUtils.getExpression(String.format(IS_NOT_DISTINCT_FROM_EXPR, INT_SV_COLUMN, INT_SV_NULL_COLUMN)); - TransformFunction isNotDistinctFromTransformFunction = - TransformFunctionFactory.get(isNotDistinctFromExpression, _enableNullDataSourceMap); - Assert.assertEquals(isNotDistinctFromTransformFunction.getName(), "is_not_distinct_from"); - boolean[] isDistinctFromExpectedIntValues = new boolean[NUM_ROWS]; - boolean[] isNotDistinctFromExpectedIntValues = new boolean[NUM_ROWS]; + ExpressionContext expression = + RequestContextUtils.getExpression(String.format(_expression, INT_SV_COLUMN, INT_SV_NULL_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertEquals(transformFunction.getName(), _isDistinctFrom ? "is_distinct_from" : "is_not_distinct_from"); + boolean[] expectedIntValues = new boolean[NUM_ROWS]; for (int i = 0; i < NUM_ROWS; i++) { if (isEqualRow(i)) { - isDistinctFromExpectedIntValues[i] = false; - isNotDistinctFromExpectedIntValues[i] = true; + expectedIntValues[i] = !_isDistinctFrom; } else if (isNotEqualRow(i)) { - isDistinctFromExpectedIntValues[i] = true; - isNotDistinctFromExpectedIntValues[i] = false; + expectedIntValues[i] = _isDistinctFrom; } else if (isNullRow(i)) { - isDistinctFromExpectedIntValues[i] = true; - isNotDistinctFromExpectedIntValues[i] = false; + expectedIntValues[i] = _isDistinctFrom; } } - testTransformFunction(isDistinctFromExpression, isDistinctFromExpectedIntValues, _enableNullProjectionBlock, - _enableNullDataSourceMap); - testTransformFunction(isNotDistinctFromExpression, isNotDistinctFromExpectedIntValues, _enableNullProjectionBlock, - _enableNullDataSourceMap); - testTransformFunction(isDistinctFromExpression, isDistinctFromExpectedIntValues, _disableNullProjectionBlock, - _disableNullDataSourceMap); - testTransformFunction(isNotDistinctFromExpression, isNotDistinctFromExpectedIntValues, _disableNullProjectionBlock, - _disableNullDataSourceMap); + + testTransformFunction(expression, expectedIntValues, _projectionBlock, _dataSourceMap); } - // Test the cases where both left and right columns of th operator has null values. @Test public void testDistinctFromBothNull() throws Exception { - ExpressionContext isDistinctFromExpression = - RequestContextUtils.getExpression(String.format(IS_DISTINCT_FROM_EXPR, INT_SV_NULL_COLUMN, INT_SV_NULL_COLUMN)); - TransformFunction isDistinctFromTransformFunction = - TransformFunctionFactory.get(isDistinctFromExpression, _enableNullDataSourceMap); - Assert.assertEquals(isDistinctFromTransformFunction.getName(), "is_distinct_from"); - ExpressionContext isNotDistinctFromExpression = RequestContextUtils.getExpression( - String.format(IS_NOT_DISTINCT_FROM_EXPR, INT_SV_NULL_COLUMN, INT_SV_NULL_COLUMN)); - TransformFunction isNotDistinctFromTransformFunction = - TransformFunctionFactory.get(isNotDistinctFromExpression, _enableNullDataSourceMap); - Assert.assertEquals(isNotDistinctFromTransformFunction.getName(), "is_not_distinct_from"); - boolean[] isDistinctFromExpectedIntValues = new boolean[NUM_ROWS]; - boolean[] isNotDistinctFromExpectedIntValues = new boolean[NUM_ROWS]; + ExpressionContext expression = + RequestContextUtils.getExpression(String.format(_expression, INT_SV_NULL_COLUMN, INT_SV_NULL_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertEquals(transformFunction.getName(), _isDistinctFrom ? "is_distinct_from" : "is_not_distinct_from"); + boolean[] expectedIntValues = new boolean[NUM_ROWS]; for (int i = 0; i < NUM_ROWS; i++) { - isDistinctFromExpectedIntValues[i] = false; - isNotDistinctFromExpectedIntValues[i] = true; + expectedIntValues[i] = !_isDistinctFrom; } - testTransformFunction(isDistinctFromExpression, isDistinctFromExpectedIntValues, _enableNullProjectionBlock, - _enableNullDataSourceMap); - testTransformFunction(isNotDistinctFromExpression, isNotDistinctFromExpectedIntValues, _enableNullProjectionBlock, - _enableNullDataSourceMap); - testTransformFunction(isDistinctFromExpression, isDistinctFromExpectedIntValues, _disableNullProjectionBlock, - _disableNullDataSourceMap); - testTransformFunction(isNotDistinctFromExpression, isNotDistinctFromExpectedIntValues, _disableNullProjectionBlock, - _disableNullDataSourceMap); + + testTransformFunction(expression, expectedIntValues, _projectionBlock, _dataSourceMap); } - // Test that non-column-names appear in one side of the operator. @Test - public void testIllegalColumnName() + public void testDistinctFromLeftLiteralRightIdentifier() throws Exception { - ExpressionContext isDistinctFromExpression = - RequestContextUtils.getExpression(String.format(IS_DISTINCT_FROM_EXPR, _intSVValues[0], INT_SV_NULL_COLUMN)); - ExpressionContext isNotDistinctFromExpression = RequestContextUtils.getExpression( - String.format(IS_NOT_DISTINCT_FROM_EXPR, _intSVValues[0], INT_SV_NULL_COLUMN)); + ExpressionContext expression = + RequestContextUtils.getExpression(String.format(_expression, "NULL", INT_SV_NULL_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertEquals(transformFunction.getName(), _isDistinctFrom ? "is_distinct_from" : "is_not_distinct_from"); + boolean[] expectedIntValues = new boolean[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + expectedIntValues[i] = !_isDistinctFrom; + } else { + expectedIntValues[i] = _isDistinctFrom; + } + } - Assert.assertThrows(RuntimeException.class, () -> { - TransformFunctionFactory.get(isDistinctFromExpression, _enableNullDataSourceMap); - }); - Assert.assertThrows(RuntimeException.class, () -> { - TransformFunctionFactory.get(isNotDistinctFromExpression, _enableNullDataSourceMap); - }); + testTransformFunction(expression, expectedIntValues, _projectionBlock, _dataSourceMap); } - // Test that more than 2 arguments appear for the operator. @Test - public void testIllegalNumArgs() + public void testDistinctFromLeftFunctionRightIdentifier() throws Exception { - ExpressionContext isDistinctFromExpression = RequestContextUtils.getExpression( - String.format("is_distinct_from(%s, %s, %s)", INT_SV_COLUMN, INT_SV_NULL_COLUMN, INT_SV_COLUMN)); - ExpressionContext isNotDistinctFromExpression = RequestContextUtils.getExpression( - String.format("is_not_distinct_from(%s, %s, %s)", INT_SV_COLUMN, INT_SV_NULL_COLUMN, INT_SV_COLUMN)); + ExpressionContext expression = + RequestContextUtils.getExpression(String.format(_expression, "NULL + 1", INT_SV_NULL_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertEquals(transformFunction.getName(), _isDistinctFrom ? "is_distinct_from" : "is_not_distinct_from"); + boolean[] expectedIntValues = new boolean[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + expectedIntValues[i] = !_isDistinctFrom; + } else { + expectedIntValues[i] = _isDistinctFrom; + } + } + + testTransformFunction(expression, expectedIntValues, _projectionBlock, _dataSourceMap); + } + + @Test + public void testIllegalNumberOfArgs() { + String expressionTemplate = _isDistinctFrom ? "is_distinct_from(%s, %s, %s)" : "is_not_distinct_from(%s, %s, %s)"; + ExpressionContext expression = RequestContextUtils.getExpression( + String.format(expressionTemplate, INT_SV_COLUMN, INT_SV_NULL_COLUMN, INT_SV_COLUMN)); Assert.assertThrows(RuntimeException.class, () -> { - TransformFunctionFactory.get(isDistinctFromExpression, _enableNullDataSourceMap); - }); - Assert.assertThrows(RuntimeException.class, () -> { - TransformFunctionFactory.get(isNotDistinctFromExpression, _enableNullDataSourceMap); + TransformFunctionFactory.get(expression, _dataSourceMap); }); } + + @Test + public void testGetNullBitmapReturnsNull() { + ExpressionContext isDistinctFromExpression = + RequestContextUtils.getExpression(String.format(_expression, INT_SV_NULL_COLUMN, INT_SV_COLUMN)); + TransformFunction isDistinctFromTransformFunction = + TransformFunctionFactory.get(isDistinctFromExpression, _dataSourceMap); + + Assert.assertNull(isDistinctFromTransformFunction.getNullBitmap(_projectionBlock)); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/DivisionTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/DivisionTransformFunctionTest.java index 40048eaeded5..908634cdc03d 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/DivisionTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/DivisionTransformFunctionTest.java @@ -23,6 +23,7 @@ import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.RequestContextUtils; import org.apache.pinot.spi.exception.BadQueryRequestException; +import org.roaringbitmap.RoaringBitmap; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -99,7 +100,7 @@ public void testDivisionTransformFunction() { testTransformFunction(transformFunction, expectedBigDecimalValues); expression = RequestContextUtils.getExpression( - String.format("div(div(div('3430.34473923874923809',div(%s,0.34)),%s),%s)", STRING_SV_COLUMN, + String.format("div(div(div(cast('3430.34473923874923809' as decimal),div(%s,0.34)),%s),%s)", STRING_SV_COLUMN, BIG_DECIMAL_SV_COLUMN, INT_SV_COLUMN)); transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); Assert.assertTrue(transformFunction instanceof DivisionTransformFunction); @@ -130,4 +131,38 @@ public Object[][] testIllegalArguments() { } }; } + + @Test + public void testDivisionNullLiteral() { + ExpressionContext expression = RequestContextUtils.getExpression(String.format("div(%s,null)", INT_SV_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof DivisionTransformFunction); + Assert.assertEquals(transformFunction.getName(), DivisionTransformFunction.FUNCTION_NAME); + double[] expectedValues = new double[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = _intSVValues[i] / Integer.MIN_VALUE; + } + RoaringBitmap roaringBitmap = new RoaringBitmap(); + roaringBitmap.add(0L, NUM_ROWS); + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } + + @Test + public void testDivisionNullColumn() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("div(%s,%s)", INT_SV_COLUMN, INT_SV_NULL_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof DivisionTransformFunction); + Assert.assertEquals(transformFunction.getName(), DivisionTransformFunction.FUNCTION_NAME); + double[] expectedValues = new double[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + roaringBitmap.add(i); + } else { + expectedValues[i] = 1; + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/EqualsTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/EqualsTransformFunctionTest.java index 65e9596fb27e..b0cae0ba0d68 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/EqualsTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/EqualsTransformFunctionTest.java @@ -18,6 +18,12 @@ */ package org.apache.pinot.core.operator.transform.function; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.request.context.RequestContextUtils; +import org.roaringbitmap.RoaringBitmap; +import org.testng.Assert; +import org.testng.annotations.Test; + public class EqualsTransformFunctionTest extends BinaryOperatorTransformFunctionTest { @Override @@ -29,4 +35,36 @@ boolean getExpectedValue(int compareResult) { String getFunctionName() { return new EqualsTransformFunction().getName(); } + + @Test + public void testEqualsNullLiteral() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("equals(null, %s)", INT_SV_NULL_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof EqualsTransformFunction); + Assert.assertEquals(transformFunction.getName(), "equals"); + int[] expectedValues = new int[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + roaringBitmap.add(0L, NUM_ROWS); + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } + + @Test + public void testEqualsNullColumn() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("equals(%s, %s)", INT_SV_NULL_COLUMN, INT_SV_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof EqualsTransformFunction); + Assert.assertEquals(transformFunction.getName(), "equals"); + int[] expectedValues = new int[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + roaringBitmap.add(i); + } else { + expectedValues[i] = 1; + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ExtractTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ExtractTransformFunctionTest.java index 1c78b0def8f1..96eda1c96c68 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ExtractTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ExtractTransformFunctionTest.java @@ -22,11 +22,13 @@ import org.apache.pinot.common.function.scalar.DateTimeFunctions; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.RequestContextUtils; +import org.roaringbitmap.RoaringBitmap; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; public class ExtractTransformFunctionTest extends BaseTransformFunctionTest { @@ -36,15 +38,19 @@ public static Object[][] testCases() { return new Object[][]{ //@formatter:off {"year", (LongToIntFunction) DateTimeFunctions::year}, + {"quarter", (LongToIntFunction) timestamp -> (DateTimeFunctions.monthOfYear(timestamp) - 1) / 3 + 1}, {"month", (LongToIntFunction) DateTimeFunctions::monthOfYear}, + {"week", (LongToIntFunction) DateTimeFunctions::weekOfYear}, {"day", (LongToIntFunction) DateTimeFunctions::dayOfMonth}, + {"doy", (LongToIntFunction) DateTimeFunctions::dayOfYear}, + {"dow", (LongToIntFunction) DateTimeFunctions::dayOfWeek}, {"hour", (LongToIntFunction) DateTimeFunctions::hour}, {"minute", (LongToIntFunction) DateTimeFunctions::minute}, {"second", (LongToIntFunction) DateTimeFunctions::second}, // TODO: Need to add timezone_hour and timezone_minute // "timezone_hour", // "timezone_minute", - //@formatter:on + //@formatter:on }; } @@ -63,4 +69,24 @@ public void testExtractTransformFunction(String field, LongToIntFunction expecte assertEquals(value[i], expected.applyAsInt(_timeValues[i])); } } + + @Test(dataProvider = "testCases") + public void testExtractTransformFunctionNull(String field, LongToIntFunction expected) { + // NOTE: functionality of ExtractTransformFunction is covered in ExtractTransformFunctionTest + // SELECT EXTRACT(YEAR FROM '2017-10-10') + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("extract(%s FROM %s)", field, TIMESTAMP_COLUMN_NULL)); + + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof ExtractTransformFunction); + int[] values = transformFunction.transformToIntValuesSV(_projectionBlock); + RoaringBitmap nullBitmap = transformFunction.getNullBitmap(_projectionBlock); + for (int i = 0; i < _projectionBlock.getNumDocs(); i++) { + if (isNullRow(i)) { + assertTrue(nullBitmap.contains(i)); + } else { + assertEquals(values[i], expected.applyAsInt(_timeValues[i])); + } + } + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/GreaterThanOrEqualTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/GreaterThanOrEqualTransformFunctionTest.java index 83e60cdd7cf4..4a5058e6368a 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/GreaterThanOrEqualTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/GreaterThanOrEqualTransformFunctionTest.java @@ -18,6 +18,13 @@ */ package org.apache.pinot.core.operator.transform.function; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.request.context.RequestContextUtils; +import org.roaringbitmap.RoaringBitmap; +import org.testng.Assert; +import org.testng.annotations.Test; + + public class GreaterThanOrEqualTransformFunctionTest extends BinaryOperatorTransformFunctionTest { @Override @@ -29,4 +36,36 @@ boolean getExpectedValue(int compareResult) { String getFunctionName() { return new GreaterThanOrEqualTransformFunction().getName(); } + + @Test + public void testGreaterThanOrEqualNullLiteral() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("greater_than_or_equal(null, %s)", INT_SV_NULL_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof GreaterThanOrEqualTransformFunction); + Assert.assertEquals(transformFunction.getName(), "greater_than_or_equal"); + int[] expectedValues = new int[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + roaringBitmap.add(0L, NUM_ROWS); + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } + + @Test + public void testEqualsNullColumn() { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("greater_than_or_equal(%s, %s)", INT_SV_NULL_COLUMN, INT_SV_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof GreaterThanOrEqualTransformFunction); + Assert.assertEquals(transformFunction.getName(), "greater_than_or_equal"); + int[] expectedValues = new int[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + roaringBitmap.add(i); + } else { + expectedValues[i] = 1; + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/GreaterThanTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/GreaterThanTransformFunctionTest.java index 06e8effacf3e..dc3e9019905b 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/GreaterThanTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/GreaterThanTransformFunctionTest.java @@ -18,6 +18,13 @@ */ package org.apache.pinot.core.operator.transform.function; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.request.context.RequestContextUtils; +import org.roaringbitmap.RoaringBitmap; +import org.testng.Assert; +import org.testng.annotations.Test; + + public class GreaterThanTransformFunctionTest extends BinaryOperatorTransformFunctionTest { @Override @@ -29,4 +36,36 @@ boolean getExpectedValue(int compareResult) { String getFunctionName() { return new GreaterThanTransformFunction().getName(); } + + @Test + public void testGreaterThanNullLiteral() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("greater_than(null, %s)", INT_SV_NULL_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof GreaterThanTransformFunction); + Assert.assertEquals(transformFunction.getName(), "greater_than"); + int[] expectedValues = new int[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + roaringBitmap.add(0L, NUM_ROWS); + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } + + @Test + public void testGreaterThanNullColumn() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("greater_than(%s, %s)", INT_SV_NULL_COLUMN, INT_SV_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof GreaterThanTransformFunction); + Assert.assertEquals(transformFunction.getName(), "greater_than"); + int[] expectedValues = new int[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (i % 2 != 0) { + roaringBitmap.add(i); + } else { + expectedValues[i] = 0; + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/IdentifierTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/IdentifierTransformFunctionTest.java new file mode 100644 index 000000000000..69b838b6db2e --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/IdentifierTransformFunctionTest.java @@ -0,0 +1,107 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.transform.function; + +import org.apache.pinot.core.common.BlockValSet; +import org.apache.pinot.core.operator.ColumnContext; +import org.apache.pinot.core.operator.blocks.ProjectionBlock; +import org.apache.pinot.segment.spi.datasource.DataSource; +import org.apache.pinot.segment.spi.datasource.DataSourceMetadata; +import org.apache.pinot.segment.spi.index.reader.Dictionary; +import org.apache.pinot.spi.data.FieldSpec; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.roaringbitmap.RoaringBitmap; +import org.testng.Assert; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import static org.mockito.Mockito.when; + + +public class IdentifierTransformFunctionTest { + private static final int NUM_DOCS = 100; + + private static final int[] INT_VALUES; + + private static final RoaringBitmap NULL_BITMAP; + + static { + INT_VALUES = new int[100]; + NULL_BITMAP = new RoaringBitmap(); + for (int i = 0; i < NUM_DOCS; i++) { + INT_VALUES[i] = i; + if (i % 2 == 0) { + NULL_BITMAP.add(i); + } + } + } + + private static final String TEST_COLUMN_NAME = "testColumn"; + + private AutoCloseable _mocks; + + @Mock + private ColumnContext _columnContext; + + @Mock + private ProjectionBlock _projectionBlock; + + @Mock + private BlockValSet _blockValSet; + + @Mock + private DataSource _dataSource; + + @Mock + private Dictionary _dictionary; + + @Mock + private DataSourceMetadata _metadata; + + @BeforeMethod + public void setUp() { + _mocks = MockitoAnnotations.openMocks(this); + when(_projectionBlock.getNumDocs()).thenReturn(NUM_DOCS); + when(_blockValSet.getIntValuesSV()).thenReturn(INT_VALUES); + when(_blockValSet.getNullBitmap()).thenReturn(NULL_BITMAP); + when(_projectionBlock.getBlockValueSet(TEST_COLUMN_NAME)).thenReturn(_blockValSet); + when(_columnContext.getDataSource()).thenReturn(_dataSource); + when(_dataSource.getDictionary()).thenReturn(_dictionary); + when(_dataSource.getDataSourceMetadata()).thenReturn(_metadata); + when(_metadata.getDataType()).thenReturn(FieldSpec.DataType.INT); + when(_metadata.isSingleValue()).thenReturn(true); + } + + @AfterMethod + public void tearDown() + throws Exception { + _mocks.close(); + } + + @Test + public void testNullBitmap() { + IdentifierTransformFunction identifierTransformFunction = + new IdentifierTransformFunction(TEST_COLUMN_NAME, _columnContext); + RoaringBitmap bitmap = identifierTransformFunction.getNullBitmap(_projectionBlock); + Assert.assertEquals(bitmap, NULL_BITMAP); + Assert.assertEquals(identifierTransformFunction.transformToIntValuesSV(_projectionBlock), INT_VALUES); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/InTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/InTransformFunctionTest.java index 950c8da400fb..983758c3fc2f 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/InTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/InTransformFunctionTest.java @@ -19,20 +19,34 @@ package org.apache.pinot.core.operator.transform.function; import com.google.common.collect.Sets; +import java.util.Arrays; import java.util.Set; +import java.util.stream.Collectors; import org.apache.pinot.common.function.TransformFunctionType; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.RequestContextUtils; import org.apache.pinot.spi.utils.ByteArray; import org.apache.pinot.spi.utils.BytesUtils; +import org.apache.pinot.spi.utils.CommonConstants.NullValuePlaceHolder; +import org.roaringbitmap.RoaringBitmap; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; public class InTransformFunctionTest extends BaseTransformFunctionTest { + protected int getNotInIntSV() { + Set intSVValues = Arrays.stream(_intSVValues).boxed().collect(Collectors.toSet()); + int notIn = Integer.MAX_VALUE; + while (intSVValues.contains(notIn)) { + --notIn; + } + return notIn; + } + @Test public void testIntInTransformFunction() { String expressionStr = @@ -122,7 +136,6 @@ public void testLongInTransformFunction() { TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); assertTrue(transformFunction instanceof InTransformFunction); assertEquals(transformFunction.getName(), TransformFunctionType.IN.getName()); - Set inValues = Sets.newHashSet(_longSVValues[2], _longSVValues[7], _longSVValues[11]); int[] intValues = transformFunction.transformToIntValuesSV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { @@ -209,4 +222,170 @@ public void testBytesInTransformFunction() { assertEquals(intValues[i], inValues.contains(new ByteArray(_bytesSVValues[i])) ? 1 : 0); } } + + @Test + public void testInTransformFunctionNullLiteralReturnsNull() { + String expressionStr = String.format("%s IN (%s)", "NULL", INT_SV_NULL_COLUMN); + ExpressionContext expression = RequestContextUtils.getExpression(expressionStr); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + int[] expectedIntValues = new int[NUM_ROWS]; + RoaringBitmap expectedNullBitmap = new RoaringBitmap(); + expectedNullBitmap.add((long) 0, (long) NUM_ROWS); + + testTransformFunctionWithNull(transformFunction, expectedIntValues, expectedNullBitmap); + } + + @Test + public void testInTransformFunctionFillsNullWithPlaceholder() { + String expressionStr = String.format("%s IN (%s)", "NULL", INT_SV_NULL_COLUMN); + ExpressionContext expression = RequestContextUtils.getExpression(expressionStr); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + int[] expectedIntValues = new int[NUM_ROWS]; + Arrays.fill(expectedIntValues, NullValuePlaceHolder.INT); + + assertEquals(transformFunction.transformToIntValuesSV(_projectionBlock), expectedIntValues); + } + + @Test + public void testNotInTransformFunctionNullLiteralReturnsNull() { + String expressionStr = String.format("%s NOT IN (%s)", "NULL", INT_SV_NULL_COLUMN); + ExpressionContext expression = RequestContextUtils.getExpression(expressionStr); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + int[] expectedIntValues = new int[NUM_ROWS]; + RoaringBitmap expectedNullBitmap = new RoaringBitmap(); + expectedNullBitmap.add((long) 0, (long) NUM_ROWS); + + testTransformFunctionWithNull(transformFunction, expectedIntValues, expectedNullBitmap); + } + + @Test + public void testInTransformFunctionNullIdentifierReturnsNull() { + String expressionStr = String.format("%s IN (%s)", INT_SV_NULL_COLUMN, INT_SV_COLUMN); + ExpressionContext expression = RequestContextUtils.getExpression(expressionStr); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + int[] expectedIntValues = new int[NUM_ROWS]; + RoaringBitmap expectedNullBitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + expectedNullBitmap.add(i); + } else { + expectedIntValues[i] = 1; + } + } + + testTransformFunctionWithNull(transformFunction, expectedIntValues, expectedNullBitmap); + } + + @Test + public void testInTransformFunctionLiteralNotInValuesThatContainNullReturnsNull() { + String expressionStr = String.format("%s IN (%s)", getNotInIntSV(), INT_SV_NULL_COLUMN); + ExpressionContext expression = RequestContextUtils.getExpression(expressionStr); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + int[] expectedIntValues = new int[NUM_ROWS]; + RoaringBitmap expectedNullBitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + expectedNullBitmap.add(i); + } else { + expectedIntValues[i] = 0; + } + } + + testTransformFunctionWithNull(transformFunction, expectedIntValues, expectedNullBitmap); + } + + @Test + public void testNotInTransformFunctionLiteralNotInValuesThatContainNullReturnsNull() { + String expressionStr = String.format("%s NOT IN (%s)", getNotInIntSV(), INT_SV_NULL_COLUMN); + ExpressionContext expression = RequestContextUtils.getExpression(expressionStr); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + int[] expectedIntValues = new int[NUM_ROWS]; + RoaringBitmap expectedNullBitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + expectedNullBitmap.add(i); + } else { + expectedIntValues[i] = 1; + } + } + + testTransformFunctionWithNull(transformFunction, expectedIntValues, expectedNullBitmap); + } + + @Test + public void testInTransformFunctionIdentifierInValuesThatContainNullReturnsTrue() { + String expressionStr = String.format("%s IN (%s, %s)", INT_SV_COLUMN, INT_SV_COLUMN, INT_SV_NULL_COLUMN); + ExpressionContext expression = RequestContextUtils.getExpression(expressionStr); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + int[] expectedIntValues = new int[NUM_ROWS]; + Arrays.fill(expectedIntValues, 1); + RoaringBitmap expectedNullBitmap = new RoaringBitmap(); + + testTransformFunctionWithNull(transformFunction, expectedIntValues, expectedNullBitmap); + } + + @Test + public void testInTransformFunctionIdentifierInAllLiteralValuesThatContainNullReturnsTrue() { + String expressionStr = String.format("%s IN (%s, %s, null)", INT_SV_COLUMN, _intSVValues[0], _intSVValues[0] + 1); + ExpressionContext expression = RequestContextUtils.getExpression(expressionStr); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + + assertEquals(transformFunction.transformToIntValuesSV(_projectionBlock)[0], 1); + assertFalse(transformFunction.getNullBitmap(_projectionBlock).contains(0)); + } + + @Test + public void testInTransformFunctionIdentifierNotInAllLiteralValuesThatContainNullReturnsNull() { + String expressionStr = + String.format("%s IN (%s, %s, NULL)", INT_SV_COLUMN, _intSVValues[0] + 1, _intSVValues[0] + 2); + ExpressionContext expression = RequestContextUtils.getExpression(expressionStr); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + + assertTrue(transformFunction.getNullBitmap(_projectionBlock).contains(0)); + } + + @Test + public void testInTransformFunctionLiteralInAllLiteralValuesThatContainNullReturnsTrue() { + String expressionStr = "1 IN (1, 2, NULL)"; + ExpressionContext expression = RequestContextUtils.getExpression(expressionStr); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + int[] expectedIntValues = new int[NUM_ROWS]; + Arrays.fill(expectedIntValues, 1); + RoaringBitmap expectedNullBitmap = new RoaringBitmap(); + + testTransformFunctionWithNull(transformFunction, expectedIntValues, expectedNullBitmap); + } + + @Test + public void testInTransformFunctionLiteralNullAndAllLiteralValuesReturnsNull() { + String expressionStr = "NULL IN (1, 2, 3)"; + ExpressionContext expression = RequestContextUtils.getExpression(expressionStr); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + RoaringBitmap expectedNullBitmap = new RoaringBitmap(); + expectedNullBitmap.add((long) 0, (long) NUM_ROWS); + + testNullBitmap(transformFunction, expectedNullBitmap); + } + + @Test + public void testInTransformFunctionLiteralNotInAllLiteralValuesThatContainNullReturnsNull() { + String expressionStr = "1 IN (2, 3, NULL)"; + ExpressionContext expression = RequestContextUtils.getExpression(expressionStr); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + RoaringBitmap expectedNullBitmap = new RoaringBitmap(); + expectedNullBitmap.add((long) 0, (long) NUM_ROWS); + + testNullBitmap(transformFunction, expectedNullBitmap); + } + + @Test + public void testNotInTransformFunctionLiteralNotInAllLiteralValuesThatContainNullReturnsNull() { + String expressionStr = "1 NOT IN (2, 3, NULL)"; + ExpressionContext expression = RequestContextUtils.getExpression(expressionStr); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + RoaringBitmap expectedNullBitmap = new RoaringBitmap(); + expectedNullBitmap.add((long) 0, (long) NUM_ROWS); + + testNullBitmap(transformFunction, expectedNullBitmap); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/IsDistinctFromTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/IsDistinctFromTransformFunctionTest.java new file mode 100644 index 000000000000..628c206d64cd --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/IsDistinctFromTransformFunctionTest.java @@ -0,0 +1,27 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.transform.function; + + +public class IsDistinctFromTransformFunctionTest extends DistinctFromTransformFunctionTest { + + IsDistinctFromTransformFunctionTest() { + super(true); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/IsNotDistinctFromTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/IsNotDistinctFromTransformFunctionTest.java new file mode 100644 index 000000000000..1cab532d1651 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/IsNotDistinctFromTransformFunctionTest.java @@ -0,0 +1,27 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.transform.function; + + +public class IsNotDistinctFromTransformFunctionTest extends DistinctFromTransformFunctionTest { + + IsNotDistinctFromTransformFunctionTest() { + super(false); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/JsonExtractIndexTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/JsonExtractIndexTransformFunctionTest.java new file mode 100644 index 000000000000..7fcfb31b5335 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/JsonExtractIndexTransformFunctionTest.java @@ -0,0 +1,426 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.transform.function; + +import com.jayway.jsonpath.Configuration; +import com.jayway.jsonpath.JsonPath; +import com.jayway.jsonpath.Option; +import com.jayway.jsonpath.ParseContext; +import com.jayway.jsonpath.TypeRef; +import com.jayway.jsonpath.spi.json.JacksonJsonProvider; +import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import org.apache.pinot.common.function.JsonPathCache; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.request.context.RequestContextUtils; +import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + + +public class JsonExtractIndexTransformFunctionTest extends BaseTransformFunctionTest { + private static final TypeRef> INTEGER_LIST_TYPE = new TypeRef>() { + }; + private static final TypeRef> LONG_LIST_TYPE = new TypeRef>() { + }; + private static final TypeRef> FLOAT_LIST_TYPE = new TypeRef>() { + }; + private static final TypeRef> DOUBLE_LIST_TYPE = new TypeRef>() { + }; + private static final TypeRef> STRING_LIST_TYPE = new TypeRef>() { + }; + + // Used to verify index value extraction + private static final ParseContext JSON_PARSER_CONTEXT = JsonPath.using( + new Configuration.ConfigurationBuilder().jsonProvider(new JacksonJsonProvider()) + .mappingProvider(new JacksonMappingProvider()).options(Option.SUPPRESS_EXCEPTIONS).build()); + + @Test(dataProvider = "testJsonExtractIndexTransformFunction") + public void testJsonExtractIndexTransformFunction(String expressionStr, String jsonPathString, + DataType resultsDataType, boolean isSingleValue) { + ExpressionContext expression = RequestContextUtils.getExpression(expressionStr); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof JsonExtractIndexTransformFunction); + Assert.assertEquals(transformFunction.getName(), JsonExtractIndexTransformFunction.FUNCTION_NAME); + Assert.assertEquals(transformFunction.getResultMetadata().getDataType(), resultsDataType); + Assert.assertEquals(transformFunction.getResultMetadata().isSingleValue(), isSingleValue); + JsonPath jsonPath = JsonPathCache.INSTANCE.getOrCompute(jsonPathString); + if (isSingleValue) { + switch (resultsDataType) { + case INT: + int[] intValues = transformFunction.transformToIntValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + Assert.assertEquals(intValues[i], Integer.parseInt(getValueForKey(_jsonSVValues[i], jsonPath))); + } + break; + case LONG: + long[] longValues = transformFunction.transformToLongValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + Assert.assertEquals(longValues[i], Long.parseLong(getValueForKey(_jsonSVValues[i], jsonPath))); + } + break; + case FLOAT: + float[] floatValues = transformFunction.transformToFloatValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + Assert.assertEquals(floatValues[i], Float.parseFloat(getValueForKey(_jsonSVValues[i], jsonPath))); + } + break; + case DOUBLE: + double[] doubleValues = transformFunction.transformToDoubleValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + Assert.assertEquals(doubleValues[i], Double.parseDouble(getValueForKey(_jsonSVValues[i], jsonPath))); + } + break; + case BIG_DECIMAL: + BigDecimal[] bigDecimalValues = transformFunction.transformToBigDecimalValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + Assert.assertEquals(bigDecimalValues[i], new BigDecimal(getValueForKey(_jsonSVValues[i], jsonPath))); + } + break; + case STRING: + String[] stringValues = transformFunction.transformToStringValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + Assert.assertEquals(stringValues[i], getValueForKey(_jsonSVValues[i], jsonPath)); + } + break; + default: + throw new UnsupportedOperationException("Not support data type - " + resultsDataType); + } + } else { + switch (resultsDataType) { + case INT: + int[][] intValues = transformFunction.transformToIntValuesMV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + List values = getValueForKey(_jsonSVValues[i], jsonPath, INTEGER_LIST_TYPE); + Assert.assertEquals(intValues[i].length, values.size()); + for (int j = 0; j < intValues[i].length; j++) { + Assert.assertEquals(intValues[i][j], values.get(j)); + } + } + break; + case LONG: + long[][] longValues = transformFunction.transformToLongValuesMV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + List values = getValueForKey(_jsonSVValues[i], jsonPath, LONG_LIST_TYPE); + Assert.assertEquals(longValues[i].length, values.size()); + for (int j = 0; j < longValues[i].length; j++) { + Assert.assertEquals(longValues[i][j], values.get(j)); + } + } + break; + case FLOAT: + float[][] floatValues = transformFunction.transformToFloatValuesMV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + List values = getValueForKey(_jsonSVValues[i], jsonPath, FLOAT_LIST_TYPE); + Assert.assertEquals(floatValues[i].length, values.size()); + for (int j = 0; j < floatValues[i].length; j++) { + Assert.assertEquals(floatValues[i][j], values.get(j)); + } + } + break; + case DOUBLE: + double[][] doubleValues = transformFunction.transformToDoubleValuesMV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + List values = getValueForKey(_jsonSVValues[i], jsonPath, DOUBLE_LIST_TYPE); + Assert.assertEquals(doubleValues[i].length, values.size()); + for (int j = 0; j < doubleValues[i].length; j++) { + Assert.assertEquals(doubleValues[i][j], values.get(j)); + } + } + break; + case STRING: + String[][] stringValues = transformFunction.transformToStringValuesMV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + List values = getValueForKey(_jsonSVValues[i], jsonPath, STRING_LIST_TYPE); + Assert.assertEquals(stringValues[i].length, values.size()); + for (int j = 0; j < stringValues[i].length; j++) { + Assert.assertEquals(stringValues[i][j], values.get(j)); + } + } + break; + default: + throw new UnsupportedOperationException("Not support data type - " + resultsDataType); + } + } + } + + @DataProvider(name = "testJsonExtractIndexTransformFunction") + public Object[][] testJsonExtractIndexTransformFunctionDataProvider() { + List testArguments = new ArrayList<>(); + // Without default value + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','INT')", JSON_STRING_SV_COLUMN, + "$.intVal"), "$.intVal", DataType.INT, true + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','LONG')", JSON_STRING_SV_COLUMN, + "$.longVal"), "$.longVal", DataType.LONG, true + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','FLOAT')", JSON_STRING_SV_COLUMN, + "$.floatVal"), "$.floatVal", DataType.FLOAT, true + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','DOUBLE')", JSON_STRING_SV_COLUMN, + "$.doubleVal"), "$.doubleVal", DataType.DOUBLE, true + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','BIG_DECIMAL')", JSON_STRING_SV_COLUMN, + "$.bigDecimalVal"), "$.bigDecimalVal", DataType.BIG_DECIMAL, true + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','STRING')", JSON_STRING_SV_COLUMN, + "$.stringVal"), "$.stringVal", DataType.STRING, true + }); + + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','INT')", JSON_STRING_SV_COLUMN, + "$.intVals[0]"), "$.intVals[0]", DataType.INT, true + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','LONG')", JSON_STRING_SV_COLUMN, + "$.longVals[1]"), "$.longVals[1]", DataType.LONG, true + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','FLOAT')", JSON_STRING_SV_COLUMN, + "$.floatVals[0]"), "$.floatVals[0]", DataType.FLOAT, true + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','DOUBLE')", JSON_STRING_SV_COLUMN, + "$.doubleVals[1]"), "$.doubleVals[1]", DataType.DOUBLE, true + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','BIG_DECIMAL')", JSON_STRING_SV_COLUMN, + "$.bigDecimalVals[0]"), "$.bigDecimalVals[0]", DataType.BIG_DECIMAL, true + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','STRING')", JSON_STRING_SV_COLUMN, + "$.stringVals[1]"), "$.stringVals[1]", DataType.STRING, true + }); + + addMvTests(testArguments); + return testArguments.toArray(new Object[0][]); + } + + private void addMvTests(List testArguments) { + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','INT_ARRAY')", JSON_STRING_SV_COLUMN, + "$.intVals[*]"), "$.intVals[*]", DataType.INT, false + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','LONG_ARRAY')", JSON_STRING_SV_COLUMN, + "$.longVals[*]"), "$.longVals[*]", DataType.LONG, false + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','DOUBLE_ARRAY')", JSON_STRING_SV_COLUMN, + "$.doubleVals[*]"), "$.doubleVals[*]", DataType.DOUBLE, false + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','STRING_ARRAY')", JSON_STRING_SV_COLUMN, + "$.stringVals[*]"), "$.stringVals[*]", DataType.STRING, false + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','INT_ARRAY')", JSON_STRING_SV_COLUMN, + "$.arrayField[*].arrIntField"), "$.arrayField[*].arrIntField", DataType.INT, false + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','STRING_ARRAY')", JSON_STRING_SV_COLUMN, + "$.arrayField[*].arrStringField"), "$.arrayField[*].arrStringField", DataType.STRING, false + }); + + // MV with filters + testArguments.add(new Object[]{ + String.format( + "jsonExtractIndex(%s,'%s','INT_ARRAY', '[]', 'REGEXP_LIKE(\"$.arrayField[*].arrStringField\", ''.*y.*'')')", + JSON_STRING_SV_COLUMN, + "$.arrayField[*].arrIntField"), "$.arrayField[?(@.arrStringField =~ /.*y.*/)].arrIntField", DataType.INT, + false + }); + + testArguments.add(new Object[]{ + String.format( + "jsonExtractIndex(%s,'%s','STRING_ARRAY', '[]', '\"$.arrayField[*].arrIntField\" > 2')", + JSON_STRING_SV_COLUMN, + "$.arrayField[*].arrStringField"), "$.arrayField[?(@.arrIntField > 2)].arrStringField", DataType.STRING, + false + }); + } + + @Test(dataProvider = "testJsonExtractIndexDefaultValue") + public void testJsonExtractIndexDefaultValue(String expressionStr, String jsonPathString, DataType resultsDataType, + boolean isSingleValue, Object expectedDefaultValue) { + ExpressionContext expression = RequestContextUtils.getExpression(expressionStr); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof JsonExtractIndexTransformFunction); + Assert.assertEquals(transformFunction.getName(), JsonExtractIndexTransformFunction.FUNCTION_NAME); + Assert.assertEquals(transformFunction.getResultMetadata().getDataType(), resultsDataType); + Assert.assertEquals(transformFunction.getResultMetadata().isSingleValue(), isSingleValue); + JsonPath jsonPath = JsonPathCache.INSTANCE.getOrCompute(jsonPathString); + if (isSingleValue) { + switch (resultsDataType) { + case INT: + int[] intValues = transformFunction.transformToIntValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + Assert.assertEquals(intValues[i], expectedDefaultValue); + } + break; + case LONG: + long[] longValues = transformFunction.transformToLongValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + Assert.assertEquals(longValues[i], expectedDefaultValue); + } + break; + case FLOAT: + float[] floatValues = transformFunction.transformToFloatValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + Assert.assertEquals(floatValues[i], expectedDefaultValue); + } + break; + case DOUBLE: + double[] doubleValues = transformFunction.transformToDoubleValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + Assert.assertEquals(doubleValues[i], expectedDefaultValue); + } + break; + case BIG_DECIMAL: + BigDecimal[] bigDecimalValues = transformFunction.transformToBigDecimalValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + Assert.assertEquals(bigDecimalValues[i], expectedDefaultValue); + } + break; + case STRING: + String[] stringValues = transformFunction.transformToStringValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + Assert.assertEquals(stringValues[i], expectedDefaultValue); + } + break; + default: + throw new UnsupportedOperationException("Not support data type - " + resultsDataType); + } + } else { + switch (resultsDataType) { + case INT: + int[][] intValues = transformFunction.transformToIntValuesMV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + Assert.assertEquals(intValues[i], expectedDefaultValue); + } + break; + case LONG: + long[][] longValues = transformFunction.transformToLongValuesMV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + Assert.assertEquals(longValues[i], expectedDefaultValue); + } + break; + case FLOAT: + float[][] floatValues = transformFunction.transformToFloatValuesMV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + Assert.assertEquals(floatValues[i], expectedDefaultValue); + } + break; + case DOUBLE: + double[][] doubleValues = transformFunction.transformToDoubleValuesMV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + Assert.assertEquals(doubleValues[i], expectedDefaultValue); + } + break; + case STRING: + String[][] stringValues = transformFunction.transformToStringValuesMV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + Assert.assertEquals(stringValues[i], expectedDefaultValue); + } + break; + default: + throw new UnsupportedOperationException("Not support data type - " + resultsDataType); + } + } + } + + @DataProvider(name = "testJsonExtractIndexDefaultValue") + public Object[][] testJsonExtractIndexDefaultValueDataProvider() { + List testArguments = new ArrayList<>(); + // With default value + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','INT',0)", JSON_STRING_SV_COLUMN, + "$.noField"), "$.noField", DataType.INT, true, 0 + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','LONG',0)", JSON_STRING_SV_COLUMN, + "$.noField"), "$.noField", DataType.LONG, true, 0L + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','FLOAT',0)", JSON_STRING_SV_COLUMN, + "$.noField"), "$.noField", DataType.FLOAT, true, (float) 0 + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','DOUBLE',0)", JSON_STRING_SV_COLUMN, + "$.noField"), "$.noField", DataType.DOUBLE, true, (double) 0 + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','BIG_DECIMAL',0)", JSON_STRING_SV_COLUMN, + "$.noField"), "$.noField", DataType.BIG_DECIMAL, true, new BigDecimal(0) + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','STRING','null')", JSON_STRING_SV_COLUMN, + "$.noField"), "$.noField", DataType.STRING, true, "null" + }); + addMvDefaultValueTests(testArguments); + return testArguments.toArray(new Object[0][]); + } + + private void addMvDefaultValueTests(List testArguments) { + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','INT_ARRAY', '%s')", JSON_STRING_SV_COLUMN, "$.noField", + "[1, 2, 3]"), "$.noField", DataType.INT, false, new Integer[]{1, 2, 3} + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','LONG_ARRAY', '%s')", JSON_STRING_SV_COLUMN, "$.noField", + "[1, 5, 6]"), "$.noField", DataType.LONG, false, new Long[]{1L, 5L, 6L} + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','FLOAT_ARRAY', '%s')", JSON_STRING_SV_COLUMN, "$.noField", + "[1.2, 3.1, 1.6]"), "$.noField", DataType.FLOAT, false, new Float[]{1.2f, 3.1f, 1.6f} + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','DOUBLE_ARRAY', '%s')", JSON_STRING_SV_COLUMN, "$.noField", + "[1.5, 3.4, 1.6]"), "$.noField", DataType.DOUBLE, false, new Double[]{1.5d, 3.4d, 1.6d} + }); + testArguments.add(new Object[]{ + String.format("jsonExtractIndex(%s,'%s','STRING_ARRAY', '%s')", JSON_STRING_SV_COLUMN, "$.noField", + "[\"randomString1\", \"randomString2\"]"), "$.noField", DataType.STRING, false, + new String[]{"randomString1", "randomString2"} + }); + } + + // get value for key, excluding nested + private String getValueForKey(String blob, JsonPath path) { + Object out = JSON_PARSER_CONTEXT.parse(blob).read(path); + if (out == null || out instanceof HashMap || out instanceof Object[]) { + return null; + } + return out.toString(); + } + + private T getValueForKey(String blob, JsonPath path, TypeRef typeRef) { + return JSON_PARSER_CONTEXT.parse(blob).read(path, typeRef); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/LessThanOrEqualTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/LessThanOrEqualTransformFunctionTest.java index 652ced40b396..4ccc9e55360f 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/LessThanOrEqualTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/LessThanOrEqualTransformFunctionTest.java @@ -18,8 +18,14 @@ */ package org.apache.pinot.core.operator.transform.function; -public class LessThanOrEqualTransformFunctionTest extends BinaryOperatorTransformFunctionTest { +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.request.context.RequestContextUtils; +import org.roaringbitmap.RoaringBitmap; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class LessThanOrEqualTransformFunctionTest extends BinaryOperatorTransformFunctionTest { @Override boolean getExpectedValue(int compareResult) { return compareResult <= 0; @@ -29,4 +35,36 @@ boolean getExpectedValue(int compareResult) { String getFunctionName() { return new LessThanOrEqualTransformFunction().getName(); } + + @Test + public void testLessThanOrEqualNullLiteral() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("less_than_or_equal(null, %s)", INT_SV_NULL_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof LessThanOrEqualTransformFunction); + Assert.assertEquals(transformFunction.getName(), "less_than_or_equal"); + int[] expectedValues = new int[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + roaringBitmap.add(0L, NUM_ROWS); + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } + + @Test + public void testLessThanOrEqualsNullColumn() { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("less_than_or_equal(%s, %s)", INT_SV_NULL_COLUMN, INT_SV_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof LessThanOrEqualTransformFunction); + Assert.assertEquals(transformFunction.getName(), "less_than_or_equal"); + int[] expectedValues = new int[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + roaringBitmap.add(i); + } else { + expectedValues[i] = 1; + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/LessThanTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/LessThanTransformFunctionTest.java index 556ecdd5b94c..d4f6d79f3eb0 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/LessThanTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/LessThanTransformFunctionTest.java @@ -18,6 +18,13 @@ */ package org.apache.pinot.core.operator.transform.function; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.request.context.RequestContextUtils; +import org.roaringbitmap.RoaringBitmap; +import org.testng.Assert; +import org.testng.annotations.Test; + + public class LessThanTransformFunctionTest extends BinaryOperatorTransformFunctionTest { @Override @@ -29,4 +36,36 @@ boolean getExpectedValue(int compareResult) { String getFunctionName() { return new LessThanTransformFunction().getName(); } + + @Test + public void testLessThanNullLiteral() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("less_than(null, %s)", INT_SV_NULL_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof LessThanTransformFunction); + Assert.assertEquals(transformFunction.getName(), "less_than"); + int[] expectedValues = new int[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + roaringBitmap.add(0L, NUM_ROWS); + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } + + @Test + public void testLessThanNullColumn() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("less_than(%s, %s)", INT_SV_NULL_COLUMN, INT_SV_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof LessThanTransformFunction); + Assert.assertEquals(transformFunction.getName(), "less_than"); + int[] expectedValues = new int[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + roaringBitmap.add(i); + } else { + expectedValues[i] = 0; + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/LiteralTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/LiteralTransformFunctionTest.java index 5fccb613ec1e..34980f1906f0 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/LiteralTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/LiteralTransformFunctionTest.java @@ -18,28 +18,130 @@ */ package org.apache.pinot.core.operator.transform.function; +import java.math.BigDecimal; import org.apache.pinot.common.request.context.LiteralContext; +import org.apache.pinot.core.operator.blocks.ValueBlock; +import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.spi.data.FieldSpec.DataType; -import org.testng.Assert; +import org.apache.pinot.spi.utils.BytesUtils; +import org.apache.pinot.spi.utils.CommonConstants.NullValuePlaceHolder; +import org.roaringbitmap.RoaringBitmap; import org.testng.annotations.Test; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.testng.Assert.*; + public class LiteralTransformFunctionTest { + private static final int NUM_DOCS = 100; @Test public void testLiteralTransformFunction() { - Assert.assertEquals(LiteralTransformFunction.inferLiteralDataType("abc"), DataType.STRING); - Assert.assertEquals(LiteralTransformFunction.inferLiteralDataType("123"), DataType.INT); - Assert.assertEquals(LiteralTransformFunction.inferLiteralDataType("2147483649"), DataType.LONG); - Assert.assertEquals(LiteralTransformFunction.inferLiteralDataType("1.2"), DataType.FLOAT); - Assert.assertEquals(LiteralTransformFunction.inferLiteralDataType("41241241.2412"), DataType.DOUBLE); - Assert.assertEquals(LiteralTransformFunction.inferLiteralDataType("1.7976931348623159e+308"), DataType.BIG_DECIMAL); - Assert.assertEquals(LiteralTransformFunction.inferLiteralDataType("2020-02-02 20:20:20.20"), DataType.TIMESTAMP); - LiteralTransformFunction trueBoolean = new LiteralTransformFunction(new LiteralContext(DataType.BOOLEAN, true)); - Assert.assertEquals(trueBoolean.getResultMetadata().getDataType(), DataType.BOOLEAN); - Assert.assertEquals(trueBoolean.getLiteral(), "true"); - LiteralTransformFunction falseBoolean = new LiteralTransformFunction(new LiteralContext(DataType.BOOLEAN, false)); - Assert.assertEquals(falseBoolean.getResultMetadata().getDataType(), DataType.BOOLEAN); - Assert.assertEquals(falseBoolean.getLiteral(), "false"); + LiteralTransformFunction function = new LiteralTransformFunction(new LiteralContext(DataType.STRING, "1234")); + assertFalse(function.getBooleanLiteral()); + assertEquals(function.getIntLiteral(), 1234); + assertEquals(function.getLongLiteral(), 1234L); + assertEquals(function.getFloatLiteral(), 1234.0f); + assertEquals(function.getDoubleLiteral(), 1234.0); + assertEquals(function.getBigDecimalLiteral(), new BigDecimal("1234")); + assertEquals(function.getStringLiteral(), "1234"); + assertEquals(function.getBytesLiteral(), BytesUtils.toBytes("1234")); + assertFalse(function.isNull()); + TransformResultMetadata resultMetadata = function.getResultMetadata(); + assertEquals(resultMetadata.getDataType(), DataType.STRING); + assertTrue(resultMetadata.isSingleValue()); + assertFalse(resultMetadata.hasDictionary()); + + ValueBlock valueBlock = mock(ValueBlock.class); + when(valueBlock.getNumDocs()).thenReturn(NUM_DOCS); + int[] intValues = function.transformToIntValuesSV(valueBlock); + assertEquals(intValues.length, NUM_DOCS); + for (int i = 0; i < NUM_DOCS; i++) { + assertEquals(intValues[i], 1234); + } + long[] longValues = function.transformToLongValuesSV(valueBlock); + assertEquals(longValues.length, NUM_DOCS); + for (int i = 0; i < NUM_DOCS; i++) { + assertEquals(longValues[i], 1234L); + } + float[] floatValues = function.transformToFloatValuesSV(valueBlock); + assertEquals(floatValues.length, NUM_DOCS); + for (int i = 0; i < NUM_DOCS; i++) { + assertEquals(floatValues[i], 1234.0f); + } + double[] doubleValues = function.transformToDoubleValuesSV(valueBlock); + assertEquals(doubleValues.length, NUM_DOCS); + for (int i = 0; i < NUM_DOCS; i++) { + assertEquals(doubleValues[i], 1234.0); + } + String[] stringValues = function.transformToStringValuesSV(valueBlock); + assertEquals(stringValues.length, NUM_DOCS); + for (int i = 0; i < NUM_DOCS; i++) { + assertEquals(stringValues[i], "1234"); + } + byte[][] bytesValues = function.transformToBytesValuesSV(valueBlock); + assertEquals(bytesValues.length, NUM_DOCS); + for (int i = 0; i < NUM_DOCS; i++) { + assertEquals(bytesValues[i], BytesUtils.toBytes("1234")); + } + assertNull(function.getNullBitmap(valueBlock)); + } + + @Test + public void testNullLiteral() { + LiteralTransformFunction function = new LiteralTransformFunction(new LiteralContext(DataType.UNKNOWN, null)); + assertFalse(function.getBooleanLiteral()); + assertEquals(function.getIntLiteral(), NullValuePlaceHolder.INT); + assertEquals(function.getLongLiteral(), NullValuePlaceHolder.LONG); + assertEquals(function.getFloatLiteral(), NullValuePlaceHolder.FLOAT); + assertEquals(function.getDoubleLiteral(), NullValuePlaceHolder.DOUBLE); + assertEquals(function.getBigDecimalLiteral(), NullValuePlaceHolder.BIG_DECIMAL); + assertEquals(function.getStringLiteral(), NullValuePlaceHolder.STRING); + assertEquals(function.getBytesLiteral(), NullValuePlaceHolder.BYTES); + assertTrue(function.isNull()); + TransformResultMetadata resultMetadata = function.getResultMetadata(); + assertEquals(resultMetadata.getDataType(), DataType.UNKNOWN); + assertTrue(resultMetadata.isSingleValue()); + assertFalse(resultMetadata.hasDictionary()); + + ValueBlock valueBlock = mock(ValueBlock.class); + when(valueBlock.getNumDocs()).thenReturn(NUM_DOCS); + int[] intValues = function.transformToIntValuesSV(valueBlock); + assertEquals(intValues.length, NUM_DOCS); + for (int i = 0; i < NUM_DOCS; i++) { + assertEquals(intValues[i], NullValuePlaceHolder.INT); + } + long[] longValues = function.transformToLongValuesSV(valueBlock); + assertEquals(longValues.length, NUM_DOCS); + for (int i = 0; i < NUM_DOCS; i++) { + assertEquals(longValues[i], NullValuePlaceHolder.LONG); + } + float[] floatValues = function.transformToFloatValuesSV(valueBlock); + assertEquals(floatValues.length, NUM_DOCS); + for (int i = 0; i < NUM_DOCS; i++) { + assertEquals(floatValues[i], NullValuePlaceHolder.FLOAT); + } + double[] doubleValues = function.transformToDoubleValuesSV(valueBlock); + assertEquals(doubleValues.length, NUM_DOCS); + for (int i = 0; i < NUM_DOCS; i++) { + assertEquals(doubleValues[i], NullValuePlaceHolder.DOUBLE); + } + String[] stringValues = function.transformToStringValuesSV(valueBlock); + assertEquals(stringValues.length, NUM_DOCS); + for (int i = 0; i < NUM_DOCS; i++) { + assertEquals(stringValues[i], NullValuePlaceHolder.STRING); + } + byte[][] bytesValues = function.transformToBytesValuesSV(valueBlock); + assertEquals(bytesValues.length, NUM_DOCS); + for (int i = 0; i < NUM_DOCS; i++) { + assertEquals(bytesValues[i], NullValuePlaceHolder.BYTES); + } + RoaringBitmap nullBitmap = function.getNullBitmap(valueBlock); + assertNotNull(nullBitmap); + assertEquals(nullBitmap.getCardinality(), NUM_DOCS); + for (int i = 0; i < NUM_DOCS; i++) { + assertTrue(nullBitmap.contains(i)); + } } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/LogicalOperatorTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/LogicalOperatorTransformFunctionTest.java index 8093cad19d96..3d1845b97fe7 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/LogicalOperatorTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/LogicalOperatorTransformFunctionTest.java @@ -27,6 +27,7 @@ import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.spi.data.FieldSpec; import org.apache.pinot.spi.exception.BadQueryRequestException; +import org.roaringbitmap.RoaringBitmap; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -85,4 +86,51 @@ public Object[][] testIllegalArguments() { String intEqualsExpr = String.format("EQUALS(%s, %d)", INT_SV_COLUMN, _intSVValues[0]); return new Object[][]{new Object[]{intEqualsExpr}, new Object[]{intEqualsExpr, STRING_SV_COLUMN}}; } + + @Test + public void testLogicalOperatorNullLiteral() { + ExpressionContext intEqualsExpr = + RequestContextUtils.getExpression(String.format("EQUALS(%s, null)", INT_SV_COLUMN)); + ExpressionContext longEqualsExpr = + RequestContextUtils.getExpression(String.format("EQUALS(%s, null)", LONG_SV_COLUMN)); + String functionName = getFunctionName(); + ExpressionContext expression = ExpressionContext.forFunction( + new FunctionContext(FunctionContext.Type.TRANSFORM, functionName, + Arrays.asList(intEqualsExpr, longEqualsExpr))); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + assertEquals(transformFunction.getName(), functionName); + TransformResultMetadata resultMetadata = transformFunction.getResultMetadata(); + assertEquals(resultMetadata.getDataType(), FieldSpec.DataType.BOOLEAN); + assertTrue(resultMetadata.isSingleValue()); + assertFalse(resultMetadata.hasDictionary()); + boolean[] expectedValues = new boolean[NUM_ROWS]; + RoaringBitmap nullBitmap = new RoaringBitmap(); + nullBitmap.add(0L, NUM_ROWS); + testTransformFunctionWithNull(transformFunction, expectedValues, nullBitmap); + } + + @Test + public void testLogicalOperatorNullColumn() { + ExpressionContext intEqualsExpr = + RequestContextUtils.getExpression(String.format("EQUALS(%s, %s)", INT_SV_COLUMN, INT_SV_NULL_COLUMN)); + String functionName = getFunctionName(); + ExpressionContext expression = ExpressionContext.forFunction( + new FunctionContext(FunctionContext.Type.TRANSFORM, functionName, Arrays.asList(intEqualsExpr, intEqualsExpr))); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + assertEquals(transformFunction.getName(), functionName); + TransformResultMetadata resultMetadata = transformFunction.getResultMetadata(); + assertEquals(resultMetadata.getDataType(), FieldSpec.DataType.BOOLEAN); + assertTrue(resultMetadata.isSingleValue()); + assertFalse(resultMetadata.hasDictionary()); + boolean[] expectedValues = new boolean[NUM_ROWS]; + RoaringBitmap nullBitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + nullBitmap.add(i); + } else { + expectedValues[i] = true; + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, nullBitmap); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ModuloTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ModuloTransformFunctionTest.java index ed9e2c5cd6df..c71ac818f758 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ModuloTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ModuloTransformFunctionTest.java @@ -21,6 +21,7 @@ import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.RequestContextUtils; import org.apache.pinot.spi.exception.BadQueryRequestException; +import org.roaringbitmap.RoaringBitmap; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -104,4 +105,36 @@ public Object[][] testIllegalArguments() { } }; } + + @Test + public void testModuloNullLiteral() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("mod(null, %s)", INT_SV_NULL_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof ModuloTransformFunction); + Assert.assertEquals(transformFunction.getName(), "mod"); + double[] expectedValues = new double[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + roaringBitmap.add(0L, NUM_ROWS); + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } + + @Test + public void testModuloNullColumn() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("mod(%s, %s)", INT_SV_NULL_COLUMN, LONG_SV_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof ModuloTransformFunction); + Assert.assertEquals(transformFunction.getName(), "mod"); + RoaringBitmap roaringBitmap = new RoaringBitmap(); + double[] expectedValues = new double[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + roaringBitmap.add(i); + } else { + expectedValues[i] = (double) _intSVValues[i] % (double) _longSVValues[i]; + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/MultiplicationTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/MultiplicationTransformFunctionTest.java index 1edbd49ab8bc..b5158062f7ae 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/MultiplicationTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/MultiplicationTransformFunctionTest.java @@ -22,13 +22,13 @@ import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.RequestContextUtils; import org.apache.pinot.spi.exception.BadQueryRequestException; +import org.roaringbitmap.RoaringBitmap; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class MultiplicationTransformFunctionTest extends BaseTransformFunctionTest { - @Test public void testMultiplicationTransformFunction() { ExpressionContext expression = RequestContextUtils.getExpression( @@ -84,6 +84,37 @@ public void testMultiplicationTransformFunction() { } } + @Test + public void testMultiplicationNullLiteral() { + ExpressionContext expression = RequestContextUtils.getExpression(String.format("mult(null, 0)")); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof MultiplicationTransformFunction); + Assert.assertEquals(transformFunction.getName(), "mult"); + double[] expectedValues = new double[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + roaringBitmap.add(0L, NUM_ROWS); + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } + + @Test + public void testModuloNullColumn() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("mult(%s, %s)", INT_SV_NULL_COLUMN, INT_SV_NULL_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof MultiplicationTransformFunction); + Assert.assertEquals(transformFunction.getName(), "mult"); + RoaringBitmap roaringBitmap = new RoaringBitmap(); + double[] expectedValues = new double[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + roaringBitmap.add(i); + } else { + expectedValues[i] = (double) _intSVValues[i] * (double) _intSVValues[i]; + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } + @Test(dataProvider = "testIllegalArguments", expectedExceptions = {BadQueryRequestException.class}) public void testIllegalArguments(String expressionStr) { ExpressionContext expression = RequestContextUtils.getExpression(expressionStr); diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/NotEqualsTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/NotEqualsTransformFunctionTest.java index 9f3c4fa0dd5a..cb6acc1897f9 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/NotEqualsTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/NotEqualsTransformFunctionTest.java @@ -18,6 +18,13 @@ */ package org.apache.pinot.core.operator.transform.function; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.request.context.RequestContextUtils; +import org.roaringbitmap.RoaringBitmap; +import org.testng.Assert; +import org.testng.annotations.Test; + + public class NotEqualsTransformFunctionTest extends BinaryOperatorTransformFunctionTest { @Override @@ -29,4 +36,36 @@ boolean getExpectedValue(int compareResult) { String getFunctionName() { return new NotEqualsTransformFunction().getName(); } + + @Test + public void testNotEqualsNullLiteral() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("not_equals(null, %s)", INT_SV_NULL_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof NotEqualsTransformFunction); + Assert.assertEquals(transformFunction.getName(), "not_equals"); + int[] expectedValues = new int[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + roaringBitmap.add(0L, NUM_ROWS); + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } + + @Test + public void testNotEqualsNullColumn() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("not_equals(%s, %s)", INT_SV_NULL_COLUMN, INT_SV_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof NotEqualsTransformFunction); + Assert.assertEquals(transformFunction.getName(), "not_equals"); + int[] expectedValues = new int[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + roaringBitmap.add(i); + } else { + expectedValues[i] = 0; + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/NotOperatorTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/NotOperatorTransformFunctionTest.java index 088192200cd9..c8d460ea77e3 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/NotOperatorTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/NotOperatorTransformFunctionTest.java @@ -24,6 +24,8 @@ import org.apache.pinot.common.request.context.RequestContextUtils; import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.spi.data.FieldSpec; +import org.roaringbitmap.RoaringBitmap; +import org.testng.Assert; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; @@ -89,4 +91,34 @@ public void testIllegalNotOperatorTransformFunction() { TransformFunctionFactory.get(exprNumArg, _dataSourceMap); }); } + + @Test + public void testNotNullLiteral() { + ExpressionContext expression = RequestContextUtils.getExpression(String.format("Not(null)", INT_SV_NULL_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof NotOperatorTransformFunction); + Assert.assertEquals(transformFunction.getName(), "not"); + int[] expectedValues = new int[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + roaringBitmap.add(0L, NUM_ROWS); + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } + + @Test + public void testNotNullColumn() { + ExpressionContext expression = RequestContextUtils.getExpression(String.format("Not(%s)", INT_SV_NULL_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof NotOperatorTransformFunction); + Assert.assertEquals(transformFunction.getName(), "not"); + int[] expectedValues = new int[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + roaringBitmap.add(i); + } else { + expectedValues[i] = _intSVValues[i] == 0 ? 1 : 0; + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/NullHandlingTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/NullHandlingTransformFunctionTest.java index 4cb00e657fbd..b412a6489fdf 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/NullHandlingTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/NullHandlingTransformFunctionTest.java @@ -193,6 +193,23 @@ public void testIsNullTransformFunction(String columnName) testTransformFunction(expression, expectedValues); } + @Test + public void testIsNullTransformFunctionNullLiteral() + throws Exception { + ExpressionContext expression = RequestContextUtils.getExpression(String.format("null IS NULL")); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + assertTrue(transformFunction instanceof IsNullTransformFunction); + assertEquals(transformFunction.getName(), TransformFunctionType.IS_NULL.getName()); + TransformResultMetadata resultMetadata = transformFunction.getResultMetadata(); + assertEquals(resultMetadata.getDataType(), DataType.BOOLEAN); + assertTrue(resultMetadata.isSingleValue()); + boolean[] expectedValues = new boolean[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = true; + } + testTransformFunction(expression, expectedValues); + } + @Test public void testIsNotNullTransformFunction() throws Exception { @@ -221,6 +238,23 @@ public void testIsNotNullTransformFunction(String columnName) testTransformFunction(expression, expectedValues); } + @Test + public void testIsNotNullTransformFunctionNullLiteral() + throws Exception { + ExpressionContext expression = RequestContextUtils.getExpression(String.format("null IS NOT NULL")); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + assertTrue(transformFunction instanceof IsNotNullTransformFunction); + assertEquals(transformFunction.getName(), TransformFunctionType.IS_NOT_NULL.getName()); + TransformResultMetadata resultMetadata = transformFunction.getResultMetadata(); + assertEquals(resultMetadata.getDataType(), DataType.BOOLEAN); + assertTrue(resultMetadata.isSingleValue()); + boolean[] expectedValues = new boolean[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = false; + } + testTransformFunction(expression, expectedValues); + } + protected void testTransformFunction(ExpressionContext expression, boolean[] expectedValues) throws Exception { int[] intValues = getTransformFunctionInstance(expression).transformToIntValuesSV(_projectionBlock); @@ -229,16 +263,14 @@ protected void testTransformFunction(ExpressionContext expression, boolean[] exp double[] doubleValues = getTransformFunctionInstance(expression).transformToDoubleValuesSV(_projectionBlock); BigDecimal[] bigDecimalValues = getTransformFunctionInstance(expression).transformToBigDecimalValuesSV(_projectionBlock); - // TODO: Support implicit cast from BOOLEAN to STRING -// String[] stringValues = getTransformFunctionInstance(expression).transformToStringValuesSV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { assertEquals(intValues[i] == 1, expectedValues[i]); assertEquals(longValues[i] == 1, expectedValues[i]); assertEquals(floatValues[i] == 1, expectedValues[i]); assertEquals(doubleValues[i] == 1, expectedValues[i]); assertEquals(bigDecimalValues[i].intValue() == 1, expectedValues[i]); -// assertEquals(stringValues[i], Boolean.toString(expectedValues[i])); } + assertEquals(getTransformFunctionInstance(expression).getNullBitmap(_projectionBlock), null); } private TransformFunction getTransformFunctionInstance(ExpressionContext expression) { diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/OrOperatorTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/OrOperatorTransformFunctionTest.java index fbf4271a0481..8ef27239b7c2 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/OrOperatorTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/OrOperatorTransformFunctionTest.java @@ -19,6 +19,11 @@ package org.apache.pinot.core.operator.transform.function; import org.apache.pinot.common.function.TransformFunctionType; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.request.context.RequestContextUtils; +import org.roaringbitmap.RoaringBitmap; +import org.testng.Assert; +import org.testng.annotations.Test; public class OrOperatorTransformFunctionTest extends LogicalOperatorTransformFunctionTest { @@ -32,4 +37,43 @@ boolean getExpectedValue(boolean left, boolean right) { String getFunctionName() { return TransformFunctionType.OR.getName(); } + + @Test + public void testOrNullLiteral() { + ExpressionContext expression = RequestContextUtils.getExpression(String.format("or(%s,null)", INT_SV_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof OrOperatorTransformFunction); + Assert.assertEquals(transformFunction.getName(), TransformFunctionType.OR.getName()); + int[] expectedValues = new int[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (_intSVValues[i] != 0) { + expectedValues[i] = 1; + } else { + roaringBitmap.add(i); + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } + + @Test + public void testOrNullColumn() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("or(%s,%s)", INT_SV_COLUMN, INT_SV_NULL_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof OrOperatorTransformFunction); + Assert.assertEquals(transformFunction.getName(), TransformFunctionType.OR.getName()); + int[] expectedValues = new int[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (_intSVValues[i] != 0) { + expectedValues[i] = 1; + } else if (isNullRow(i)) { + roaringBitmap.add(i); + } else { + expectedValues[i] = 0; + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/PowerTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/PowerTransformFunctionTest.java index 3c3b29f8de24..928a4c719c25 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/PowerTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/PowerTransformFunctionTest.java @@ -18,8 +18,10 @@ */ package org.apache.pinot.core.operator.transform.function; +import org.apache.pinot.common.function.TransformFunctionType; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.RequestContextUtils; +import org.roaringbitmap.RoaringBitmap; import org.testng.Assert; import org.testng.annotations.Test; @@ -86,8 +88,42 @@ public void testPowerTransformFunctionWithLiteralExponents() { Assert.assertEquals(transformFunction.getName(), PowerTransformFunction.FUNCTION_NAME); double[] expectedValues = new double[NUM_ROWS]; for (int i = 0; i < NUM_ROWS; i++) { - expectedValues[i] = Math.pow((double) _intSVValues[i], exponent); + expectedValues[i] = Math.pow(_intSVValues[i], exponent); } testTransformFunction(transformFunction, expectedValues); } + + @Test + public void testPowerNullLiteral() { + ExpressionContext expression = RequestContextUtils.getExpression(String.format("power(%s,null)", INT_SV_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof PowerTransformFunction); + Assert.assertEquals(transformFunction.getName(), TransformFunctionType.POWER.getName()); + double[] expectedValues = new double[NUM_ROWS]; + for (int i = 0; i < NUM_ROWS; i++) { + expectedValues[i] = 1; + } + RoaringBitmap roaringBitmap = new RoaringBitmap(); + roaringBitmap.add(0L, NUM_ROWS); + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } + + @Test + public void testPowerNullColumn() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("power(%s,%s)", INT_SV_NULL_COLUMN, 0)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof PowerTransformFunction); + Assert.assertEquals(transformFunction.getName(), TransformFunctionType.POWER.getName()); + double[] expectedValues = new double[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + roaringBitmap.add(i); + } else { + expectedValues[i] = 1; + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/RoundDecimalTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/RoundDecimalTransformFunctionTest.java index 9fe3d8151ffa..3a2ab38e2d36 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/RoundDecimalTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/RoundDecimalTransformFunctionTest.java @@ -20,8 +20,10 @@ import java.math.BigDecimal; import java.math.RoundingMode; +import org.apache.pinot.common.function.TransformFunctionType; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.RequestContextUtils; +import org.roaringbitmap.RoaringBitmap; import org.testng.Assert; import org.testng.annotations.Test; @@ -59,10 +61,41 @@ public void testRoundDecimalTransformFunction() { for (int i = 0; i < NUM_ROWS; i++) { expectedValues[i] = round(_doubleSVValues[i], 0); } - testTransformFunction(transformFunction, expectedValues); } public Double round(double a, int b) { return BigDecimal.valueOf(a).setScale(b, RoundingMode.HALF_UP).doubleValue(); } + + @Test + public void testRoundDecimalNullLiteral() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("round_decimal(null)", INT_SV_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof RoundDecimalTransformFunction); + Assert.assertEquals(transformFunction.getName(), TransformFunctionType.ROUND_DECIMAL.getName()); + double[] expectedValues = new double[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + roaringBitmap.add(0L, NUM_ROWS); + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } + + @Test + public void testRoundDecimalNullColumn() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("round_decimal(%s)", INT_SV_NULL_COLUMN, 0)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof RoundDecimalTransformFunction); + Assert.assertEquals(transformFunction.getName(), TransformFunctionType.ROUND_DECIMAL.getName()); + double[] expectedValues = new double[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + roaringBitmap.add(i); + } else { + expectedValues[i] = _intSVValues[i]; + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ScalarTransformFunctionWrapperTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ScalarTransformFunctionWrapperTest.java index cade1b2c6ff4..49a86abc65a4 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ScalarTransformFunctionWrapperTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/ScalarTransformFunctionWrapperTest.java @@ -30,10 +30,11 @@ import org.apache.commons.lang3.StringUtils; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.RequestContextUtils; -import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.utils.ArrayCopyUtils; import org.apache.pinot.spi.utils.BigDecimalUtils; +import org.apache.pinot.spi.utils.CommonConstants.NullValuePlaceHolder; +import org.roaringbitmap.RoaringBitmap; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; @@ -879,8 +880,7 @@ public void testArrayElementAtInt() { assertTrue(transformFunction.getResultMetadata().isSingleValue()); int[] expectedValues = new int[NUM_ROWS]; for (int i = 0; i < NUM_ROWS; i++) { - expectedValues[i] = _intMVValues[i].length > index ? _intMVValues[i][index] - : (Integer) DataSchema.ColumnDataType.INT.getNullPlaceholder(); + expectedValues[i] = _intMVValues[i].length > index ? _intMVValues[i][index] : NullValuePlaceHolder.INT; } testTransformFunction(transformFunction, expectedValues); } @@ -897,8 +897,7 @@ public void testArrayElementAtLong() { assertTrue(transformFunction.getResultMetadata().isSingleValue()); long[] expectedValues = new long[NUM_ROWS]; for (int i = 0; i < NUM_ROWS; i++) { - expectedValues[i] = _longMVValues[i].length > index ? _longMVValues[i][index] - : (Long) DataSchema.ColumnDataType.LONG.getNullPlaceholder(); + expectedValues[i] = _longMVValues[i].length > index ? _longMVValues[i][index] : NullValuePlaceHolder.LONG; } testTransformFunction(transformFunction, expectedValues); } @@ -915,8 +914,7 @@ public void testArrayElementAtFloat() { assertTrue(transformFunction.getResultMetadata().isSingleValue()); float[] expectedValues = new float[NUM_ROWS]; for (int i = 0; i < NUM_ROWS; i++) { - expectedValues[i] = _floatMVValues[i].length > index ? _floatMVValues[i][index] - : (Float) DataSchema.ColumnDataType.FLOAT.getNullPlaceholder(); + expectedValues[i] = _floatMVValues[i].length > index ? _floatMVValues[i][index] : NullValuePlaceHolder.FLOAT; } testTransformFunction(transformFunction, expectedValues); } @@ -933,8 +931,7 @@ public void testArrayElementAtDouble() { assertTrue(transformFunction.getResultMetadata().isSingleValue()); double[] expectedValues = new double[NUM_ROWS]; for (int i = 0; i < NUM_ROWS; i++) { - expectedValues[i] = _doubleMVValues[i].length > index ? _doubleMVValues[i][index] - : (Double) DataSchema.ColumnDataType.DOUBLE.getNullPlaceholder(); + expectedValues[i] = _doubleMVValues[i].length > index ? _doubleMVValues[i][index] : NullValuePlaceHolder.DOUBLE; } testTransformFunction(transformFunction, expectedValues); } @@ -951,12 +948,127 @@ public void testArrayElementAtString() { assertTrue(transformFunction.getResultMetadata().isSingleValue()); String[] expectedValues = new String[NUM_ROWS]; for (int i = 0; i < NUM_ROWS; i++) { - expectedValues[i] = _stringMVValues[i].length > index ? _stringMVValues[i][index] - : (String) DataSchema.ColumnDataType.STRING.getNullPlaceholder(); + expectedValues[i] = _stringMVValues[i].length > index ? _stringMVValues[i][index] : NullValuePlaceHolder.STRING; } testTransformFunction(transformFunction, expectedValues); } + @Test + public void testArrayIndexOfAllInt() { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("array_indexes_of_int(%s, 0)", INT_MONO_INCREASING_MV_1)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper); + assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.INT); + assertFalse(transformFunction.getResultMetadata().isSingleValue()); + int[][] expectedValues = new int[NUM_ROWS][]; + for (int i = 0; i < NUM_ROWS; i++) { + int[] expectedValue = {0}; + expectedValues[i] = expectedValue; + } + testTransformFunctionMV(transformFunction, expectedValues); + } + + @Test + public void testArrayIndexOfAllLong() { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("array_indexes_of_long(%s, 1)", LONG_MV_COLUMN_2)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper); + assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.INT); + assertFalse(transformFunction.getResultMetadata().isSingleValue()); + int[][] expectedValues = new int[NUM_ROWS][]; + for (int i = 0; i < NUM_ROWS; i++) { + int len = _longMV2Values[i].length; + int[] expectedValue = new int[len]; + for (int j = 0; j < len; j++) { + expectedValue[j] = j; + } + expectedValues[i] = expectedValue; + } + testTransformFunctionMV(transformFunction, expectedValues); + } + + @Test + public void testArrayIndexOfAllFloat() { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("array_indexes_of_float(%s, 1.0)", FLOAT_MV_COLUMN_2)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper); + assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.INT); + assertFalse(transformFunction.getResultMetadata().isSingleValue()); + int[][] expectedValues = new int[NUM_ROWS][]; + for (int i = 0; i < NUM_ROWS; i++) { + int len = _floatMV2Values[i].length; + int[] expectedValue = new int[len]; + for (int j = 0; j < len; j++) { + expectedValue[j] = j; + } + expectedValues[i] = expectedValue; + } + testTransformFunctionMV(transformFunction, expectedValues); + } + + @Test + public void testArrayIndexOfAllDouble() { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("array_indexes_of_double(%s, 1.0)", DOUBLE_MV_COLUMN_2)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper); + assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.INT); + assertFalse(transformFunction.getResultMetadata().isSingleValue()); + int[][] expectedValues = new int[NUM_ROWS][]; + for (int i = 0; i < NUM_ROWS; i++) { + int len = _doubleMV2Values[i].length; + int[] expectedValue = new int[len]; + for (int j = 0; j < len; j++) { + expectedValue[j] = j; + } + expectedValues[i] = expectedValue; + } + testTransformFunctionMV(transformFunction, expectedValues); + } + + @Test + public void testArrayIndexOfAllString() { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("array_indexes_of_string(%s, 'a')", STRING_ALPHANUM_MV_COLUMN_2)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper); + assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.INT); + assertFalse(transformFunction.getResultMetadata().isSingleValue()); + int[][] expectedValues = new int[NUM_ROWS][]; + for (int i = 0; i < NUM_ROWS; i++) { + int len = _stringAlphaNumericMV2Values[i].length; + int[] expectedValue = new int[len]; + for (int j = 0; j < len; j++) { + expectedValue[j] = j; + } + expectedValues[i] = expectedValue; + } + testTransformFunctionMV(transformFunction, expectedValues); + } + + @Test + public void testIntersectIndices() { + ExpressionContext expression = RequestContextUtils.getExpression( + String.format("intersect_indices(%s, %s)", INT_MONO_INCREASING_MV_1, INT_MONO_INCREASING_MV_2)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper); + assertEquals(transformFunction.getResultMetadata().getDataType(), DataType.INT); + assertFalse(transformFunction.getResultMetadata().isSingleValue()); + int[][] expectedValues = new int[NUM_ROWS][]; + for (int i = 0; i < NUM_ROWS; i++) { + int len = _intMonoIncreasingMV1Values[i].length; + int[] expectedValue = new int[len - 1]; + for (int j = 0; j < expectedValue.length; j++) { + expectedValue[j] = j + 1; + } + expectedValues[i] = expectedValue; + } + testTransformFunctionMV(transformFunction, expectedValues); + } + @Test public void testBase64TransformFunction() { ExpressionContext expression = RequestContextUtils.getExpression(String.format("toBase64(%s)", BYTES_SV_COLUMN)); @@ -1012,4 +1124,36 @@ public void testBigDecimalSerDeTransformFunction() { assertEquals(transformFunction.getName(), "bytesToBigDecimal"); testTransformFunction(transformFunction, _bigDecimalSVValues); } + + @Test + public void testStringLowerTransformFunctionNullLiteral() { + ExpressionContext expression = + RequestContextUtils.getExpression("lower(null)"); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper); + assertEquals(transformFunction.getName(), "lower"); + String[] expectedValues = new String[NUM_ROWS]; + RoaringBitmap bitmap = new RoaringBitmap(); + bitmap.add(0L, NUM_ROWS); + testTransformFunctionWithNull(transformFunction, expectedValues, bitmap); + } + + @Test + public void testStringLowerTransformFunctionNullColumn() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("lower(%s)", STRING_ALPHANUM_NULL_SV_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + assertTrue(transformFunction instanceof ScalarTransformFunctionWrapper); + assertEquals(transformFunction.getName(), "lower"); + String[] expectedValues = new String[NUM_ROWS]; + RoaringBitmap bitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + bitmap.add(i); + } else { + expectedValues[i] = _stringAlphaNumericSVValues[i].toLowerCase(); + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, bitmap); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/SingleParamMathTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/SingleParamMathTransformFunctionTest.java index 1acf26db602b..581ba61c213c 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/SingleParamMathTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/SingleParamMathTransformFunctionTest.java @@ -31,12 +31,12 @@ import org.apache.pinot.core.operator.transform.function.SingleParamMathTransformFunction.Log2TransformFunction; import org.apache.pinot.core.operator.transform.function.SingleParamMathTransformFunction.SignTransformFunction; import org.apache.pinot.core.operator.transform.function.SingleParamMathTransformFunction.SqrtTransformFunction; +import org.roaringbitmap.RoaringBitmap; import org.testng.Assert; import org.testng.annotations.Test; public class SingleParamMathTransformFunctionTest extends BaseTransformFunctionTest { - @Test public void testAbsTransformFunction() { ExpressionContext expression = RequestContextUtils.getExpression(String.format("abs(%s)", INT_SV_COLUMN)); @@ -89,6 +89,19 @@ public void testAbsTransformFunction() { expectedBigDecimalValues[i] = _bigDecimalSVValues[i].abs(); } testTransformFunction(transformFunction, expectedBigDecimalValues); + + expression = RequestContextUtils.getExpression(String.format("abs(%s)", INT_SV_NULL_COLUMN)); + transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof AbsTransformFunction); + RoaringBitmap bitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + bitmap.add(i); + } else { + expectedValues[i] = Math.abs(_intSVValues[i]); + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, bitmap); } @Test @@ -143,6 +156,18 @@ public void testCeilTransformFunction() { expectedBigDecimalValues[i] = _bigDecimalSVValues[i].setScale(0, RoundingMode.CEILING); } testTransformFunction(transformFunction, expectedBigDecimalValues); + expression = RequestContextUtils.getExpression(String.format("ceil(%s)", INT_SV_NULL_COLUMN)); + transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof CeilTransformFunction); + RoaringBitmap bitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + bitmap.add(i); + } else { + expectedValues[i] = Math.ceil(_intSVValues[i]); + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, bitmap); } @Test @@ -197,6 +222,19 @@ public void testExpTransformFunction() { expectedValues[i] = Math.exp(_bigDecimalSVValues[i].doubleValue()); } testTransformFunction(transformFunction, expectedValues); + + expression = RequestContextUtils.getExpression(String.format("exp(%s)", INT_SV_NULL_COLUMN)); + transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof ExpTransformFunction); + RoaringBitmap bitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + bitmap.add(i); + } else { + expectedValues[i] = Math.exp(_intSVValues[i]); + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, bitmap); } @Test @@ -252,6 +290,19 @@ public void testFloorTransformFunction() { ; } testTransformFunction(transformFunction, expectedBigDecimalValues); + + expression = RequestContextUtils.getExpression(String.format("floor(%s)", INT_SV_NULL_COLUMN)); + transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof FloorTransformFunction); + RoaringBitmap bitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + bitmap.add(i); + } else { + expectedValues[i] = Math.floor(_intSVValues[i]); + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, bitmap); } @Test @@ -306,6 +357,19 @@ public void testLnTransformFunction() { expectedValues[i] = Math.log(_bigDecimalSVValues[i].doubleValue()); } testTransformFunction(transformFunction, expectedValues); + + expression = RequestContextUtils.getExpression(String.format("ln(%s)", INT_SV_NULL_COLUMN)); + transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof LnTransformFunction); + RoaringBitmap bitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + bitmap.add(i); + } else { + expectedValues[i] = Math.log(_intSVValues[i]); + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, bitmap); } @Test @@ -360,6 +424,19 @@ public void testSqrtTransformFunction() { expectedValues[i] = Math.sqrt(_bigDecimalSVValues[i].doubleValue()); } testTransformFunction(transformFunction, expectedValues); + + expression = RequestContextUtils.getExpression(String.format("sqrt(%s)", INT_SV_NULL_COLUMN)); + transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof SqrtTransformFunction); + RoaringBitmap bitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + bitmap.add(i); + } else { + expectedValues[i] = Math.sqrt(_intSVValues[i]); + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, bitmap); } @Test @@ -382,6 +459,19 @@ public void testCombinationMathTransformFunctions() { .add(BigDecimal.valueOf(Math.ceil(_intSVValues[i]))); } testTransformFunction(transformFunction, expectedBigDecimalValues); + + expression = RequestContextUtils.getExpression( + String.format("add(ceil(%s), sqrt(%s))", INT_SV_NULL_COLUMN, INT_SV_NULL_COLUMN)); + transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + RoaringBitmap bitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + bitmap.add(i); + } else { + expectedValues[i] = Math.sqrt(_intSVValues[i]) + Math.ceil(_intSVValues[i]); + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, bitmap); } @Test @@ -427,6 +517,18 @@ public void testLog10TransformFunction() { expectedValues[i] = Math.log10(Double.parseDouble(_stringSVValues[i])); } testTransformFunction(transformFunction, expectedValues); + + expression = RequestContextUtils.getExpression(String.format("log10(%s)", INT_SV_NULL_COLUMN)); + transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + RoaringBitmap bitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + bitmap.add(i); + } else { + expectedValues[i] = Math.log10(_intSVValues[i]); + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, bitmap); } @Test @@ -472,6 +574,18 @@ public void testLog2TransformFunction() { expectedValues[i] = Math.log(Double.parseDouble(_stringSVValues[i])) / Math.log(2); } testTransformFunction(transformFunction, expectedValues); + + expression = RequestContextUtils.getExpression(String.format("log2(%s)", INT_SV_NULL_COLUMN)); + transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + RoaringBitmap bitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + bitmap.add(i); + } else { + expectedValues[i] = Math.log(_intSVValues[i]) / Math.log(2); + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, bitmap); } @Test @@ -517,5 +631,16 @@ public void testSignTransformFunction() { expectedValues[i] = Math.signum(Double.parseDouble(_stringSVValues[i])); } testTransformFunction(transformFunction, expectedValues); + expression = RequestContextUtils.getExpression(String.format("sign(%s)", INT_SV_NULL_COLUMN)); + transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + RoaringBitmap bitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + bitmap.add(i); + } else { + expectedValues[i] = Math.signum(_intSVValues[i]); + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, bitmap); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/SubtractionTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/SubtractionTransformFunctionTest.java index 97f9b0f4071e..fc2dfefd1255 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/SubtractionTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/SubtractionTransformFunctionTest.java @@ -19,16 +19,17 @@ package org.apache.pinot.core.operator.transform.function; import java.math.BigDecimal; +import org.apache.pinot.common.function.TransformFunctionType; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.RequestContextUtils; import org.apache.pinot.spi.exception.BadQueryRequestException; +import org.roaringbitmap.RoaringBitmap; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; public class SubtractionTransformFunctionTest extends BaseTransformFunctionTest { - @Test public void testSubtractionTransformFunction() { ExpressionContext expression = @@ -117,4 +118,35 @@ public Object[][] testIllegalArguments() { } }; } + + @Test + public void testSubtractionNullLiteral() { + ExpressionContext expression = RequestContextUtils.getExpression("sub(null, 1)"); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof SubtractionTransformFunction); + Assert.assertEquals(transformFunction.getName(), TransformFunctionType.SUB.getName()); + double[] expectedValues = new double[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + roaringBitmap.add(0L, NUM_ROWS); + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } + + @Test + public void testSubtractionNullColumn() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("sub(%s, 0)", INT_SV_NULL_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof SubtractionTransformFunction); + Assert.assertEquals(transformFunction.getName(), TransformFunctionType.SUB.getName()); + double[] expectedValues = new double[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + roaringBitmap.add(i); + } else { + expectedValues[i] = _intSVValues[i]; + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/TimeConversionTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/TimeConversionTransformFunctionTest.java index 2540f6c9cfcc..5cf5e06dbfc1 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/TimeConversionTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/TimeConversionTransformFunctionTest.java @@ -24,6 +24,7 @@ import org.apache.pinot.core.operator.transform.TransformResultMetadata; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.exception.BadQueryRequestException; +import org.roaringbitmap.RoaringBitmap; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -51,6 +52,28 @@ public void testTimeConversionTransformFunction(String expressionStr) { testTransformFunction(transformFunction, expectedValues); } + @Test(dataProvider = "testTimeConversionTransformFunctionNull") + public void testTimeConversionTransformFunctionNullColumn(String expressionStr) { + ExpressionContext expression = RequestContextUtils.getExpression(expressionStr); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + assertTrue(transformFunction instanceof TimeConversionTransformFunction); + assertEquals(transformFunction.getName(), TimeConversionTransformFunction.FUNCTION_NAME); + TransformResultMetadata resultMetadata = transformFunction.getResultMetadata(); + assertEquals(resultMetadata.getDataType(), DataType.LONG); + assertTrue(resultMetadata.isSingleValue()); + assertFalse(resultMetadata.hasDictionary()); + long[] expectedValues = new long[NUM_ROWS]; + RoaringBitmap expectedNull = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + expectedNull.add(i); + } else { + expectedValues[i] = TimeUnit.MILLISECONDS.toDays(_timeValues[i]); + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, expectedNull); + } + @DataProvider(name = "testTimeConversionTransformFunction") public Object[][] testTimeConversionTransformFunction() { return new Object[][]{ @@ -64,6 +87,19 @@ public Object[][] testTimeConversionTransformFunction() { }; } + @DataProvider(name = "testTimeConversionTransformFunctionNull") + public Object[][] testTimeConversionTransformFunctionNull() { + return new Object[][]{ + new Object[]{ + String.format("timeConvert(%s,'MILLISECONDS','DAYS')", TIMESTAMP_COLUMN_NULL) + }, new Object[]{ + String.format( + "timeConvert(timeConvert(timeConvert(%s,'MILLISECONDS','SECONDS'),'SECONDS','HOURS'),'HOURS','DAYS')", + TIMESTAMP_COLUMN_NULL) + } + }; + } + @Test(dataProvider = "testIllegalArguments", expectedExceptions = {BadQueryRequestException.class}) public void testIllegalArguments(String expressionStr) { ExpressionContext expression = RequestContextUtils.getExpression(expressionStr); diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/TrigonometricFunctionsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/TrigonometricFunctionsTest.java index ad467f6263ec..fdfb3c60842a 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/TrigonometricFunctionsTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/TrigonometricFunctionsTest.java @@ -33,6 +33,7 @@ import org.apache.pinot.core.operator.transform.function.TrigonometricTransformFunctions.SinhTransformFunction; import org.apache.pinot.core.operator.transform.function.TrigonometricTransformFunctions.TanTransformFunction; import org.apache.pinot.core.operator.transform.function.TrigonometricTransformFunctions.TanhTransformFunction; +import org.roaringbitmap.RoaringBitmap; import org.testng.Assert; import org.testng.annotations.Test; @@ -159,5 +160,20 @@ public void testTrigonometricTransformFunction(Class clazz, String sqlFunctio expectedValues[i] = op.applyAsDouble(Double.parseDouble(_stringSVValues[i])); } testTransformFunction(transformFunction, expectedValues); + + expression = RequestContextUtils.getExpression(String.format("%s(%s)", sqlFunction, INT_SV_NULL_COLUMN)); + transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(clazz.isInstance(transformFunction)); + Assert.assertEquals(transformFunction.getName(), sqlFunction); + expectedValues = new double[NUM_ROWS]; + RoaringBitmap bitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + bitmap.add(i); + } else { + expectedValues[i] = op.applyAsDouble(_intSVValues[i]); + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, bitmap); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/TruncateDecimalTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/TruncateDecimalTransformFunctionTest.java index 46e26bff6a4f..05ea6c888861 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/TruncateDecimalTransformFunctionTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/TruncateDecimalTransformFunctionTest.java @@ -20,8 +20,10 @@ import java.math.BigDecimal; import java.math.RoundingMode; +import org.apache.pinot.common.function.TransformFunctionType; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.request.context.RequestContextUtils; +import org.roaringbitmap.RoaringBitmap; import org.testng.Assert; import org.testng.annotations.Test; @@ -65,4 +67,35 @@ public void testTruncateDecimalTransformFunction() { public Double truncate(double a, int b) { return BigDecimal.valueOf(a).setScale(b, RoundingMode.DOWN).doubleValue(); } + + @Test + public void testTruncateNullLiteral() { + ExpressionContext expression = RequestContextUtils.getExpression("truncate(null, 1)"); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof TruncateDecimalTransformFunction); + Assert.assertEquals(transformFunction.getName(), TransformFunctionType.TRUNCATE.getName()); + double[] expectedValues = new double[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + roaringBitmap.add(0L, NUM_ROWS); + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } + + @Test + public void testTruncateNullColumn() { + ExpressionContext expression = + RequestContextUtils.getExpression(String.format("truncate(%s, 0)", INT_SV_NULL_COLUMN)); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + Assert.assertTrue(transformFunction instanceof TruncateDecimalTransformFunction); + Assert.assertEquals(transformFunction.getName(), TransformFunctionType.TRUNCATE.getName()); + double[] expectedValues = new double[NUM_ROWS]; + RoaringBitmap roaringBitmap = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + roaringBitmap.add(i); + } else { + expectedValues[i] = _intSVValues[i]; + } + } + testTransformFunctionWithNull(transformFunction, expectedValues, roaringBitmap); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/TupleSelectionTransformFunctionsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/TupleSelectionTransformFunctionsTest.java index 74552aae865e..e3952bafd0b4 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/TupleSelectionTransformFunctionsTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/TupleSelectionTransformFunctionsTest.java @@ -24,6 +24,7 @@ import org.apache.pinot.common.request.context.RequestContextUtils; import org.apache.pinot.spi.data.FieldSpec; import org.apache.pinot.spi.exception.BadQueryRequestException; +import org.roaringbitmap.RoaringBitmap; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; @@ -35,37 +36,36 @@ public class TupleSelectionTransformFunctionsTest extends BaseTransformFunctionT @DataProvider public static Object[][] rejectedParameters() { - return new Object[][] { - {"()"}, - {String.format("(%s)", INT_MV_COLUMN)}, - {String.format("(%s)", LONG_MV_COLUMN)}, - {String.format("(%s)", FLOAT_MV_COLUMN)}, - {String.format("(%s)", DOUBLE_MV_COLUMN)}, - {String.format("(%s)", STRING_MV_COLUMN)}, - {String.format("(%s, %s)", INT_MV_COLUMN, INT_SV_COLUMN)}, - {String.format("(%s, %s)", STRING_SV_COLUMN, INT_SV_COLUMN)}, - {String.format("(%s, %s)", STRING_SV_COLUMN, LONG_SV_COLUMN)}, - {String.format("(%s, %s)", STRING_SV_COLUMN, FLOAT_SV_COLUMN)}, - {String.format("(%s, %s)", STRING_SV_COLUMN, DOUBLE_SV_COLUMN)}, - {String.format("(%s, %s)", STRING_SV_COLUMN, TIMESTAMP_COLUMN)}, - {String.format("(%s, %s)", INT_SV_COLUMN, TIMESTAMP_COLUMN)}, - {String.format("(%s, %s)", FLOAT_SV_COLUMN, TIMESTAMP_COLUMN)}, - {String.format("(%s, %s)", DOUBLE_SV_COLUMN, TIMESTAMP_COLUMN)}, - {String.format("(%s, %s)", TIMESTAMP_COLUMN, INT_SV_COLUMN)}, - {String.format("(%s, %s)", INT_SV_COLUMN, INT_MV_COLUMN)}, - {String.format("(%s, %s)", INT_SV_COLUMN, STRING_SV_COLUMN)}, - {String.format("(%s, %s)", TIMESTAMP_COLUMN, INT_SV_COLUMN)}, - {String.format("(%s, %s)", TIMESTAMP_COLUMN, FLOAT_SV_COLUMN)}, - {String.format("(%s, %s)", TIMESTAMP_COLUMN, DOUBLE_SV_COLUMN)}, - {String.format("(%s, %s)", TIMESTAMP_COLUMN, STRING_SV_COLUMN)}, - {String.format("(%s, %s)", TIMESTAMP_COLUMN, TIME_COLUMN)} + return new Object[][]{ + {"()"}, {String.format("(%s)", INT_MV_COLUMN)}, {String.format("(%s)", LONG_MV_COLUMN)}, { + String.format("(%s)", FLOAT_MV_COLUMN) + }, {String.format("(%s)", DOUBLE_MV_COLUMN)}, {String.format("(%s)", STRING_MV_COLUMN)}, { + String.format("(%s, %s)", INT_MV_COLUMN, INT_SV_COLUMN) + }, {String.format("(%s, %s)", STRING_SV_COLUMN, INT_SV_COLUMN)}, { + String.format("(%s, %s)", STRING_SV_COLUMN, LONG_SV_COLUMN) + }, {String.format("(%s, %s)", STRING_SV_COLUMN, FLOAT_SV_COLUMN)}, { + String.format("(%s, %s)", STRING_SV_COLUMN, DOUBLE_SV_COLUMN) + }, {String.format("(%s, %s)", STRING_SV_COLUMN, TIMESTAMP_COLUMN)}, { + String.format("(%s, %s)", INT_SV_COLUMN, TIMESTAMP_COLUMN) + }, {String.format("(%s, %s)", FLOAT_SV_COLUMN, TIMESTAMP_COLUMN)}, { + String.format("(%s, %s)", DOUBLE_SV_COLUMN, TIMESTAMP_COLUMN) + }, {String.format("(%s, %s)", TIMESTAMP_COLUMN, INT_SV_COLUMN)}, { + String.format("(%s, %s)", INT_SV_COLUMN, INT_MV_COLUMN) + }, {String.format("(%s, %s)", INT_SV_COLUMN, STRING_SV_COLUMN)}, { + String.format("(%s, %s)", TIMESTAMP_COLUMN, INT_SV_COLUMN) + }, {String.format("(%s, %s)", TIMESTAMP_COLUMN, FLOAT_SV_COLUMN)}, { + String.format("(%s, %s)", TIMESTAMP_COLUMN, DOUBLE_SV_COLUMN) + }, {String.format("(%s, %s)", TIMESTAMP_COLUMN, STRING_SV_COLUMN)}, { + String.format("(%s, %s)", TIMESTAMP_COLUMN, TIME_COLUMN) + } }; } @Test public void testLeastTransformFunctionInt() { - TransformFunction transformFunction = testLeastPreconditions( - String.format("least(%s, %d, cast(%s as INT))", INT_SV_COLUMN, -1, FLOAT_SV_COLUMN)); + // -1 will be passed in as a long. + TransformFunction transformFunction = + testLeastPreconditions(String.format("least(%s, %d, cast(%s as INT))", INT_SV_COLUMN, -1, FLOAT_SV_COLUMN)); assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.INT); int[] intValues = transformFunction.transformToIntValuesSV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { @@ -88,8 +88,7 @@ public void testLeastTransformFunctionString() { @Test public void testLeastTransformFunctionUnaryInt() { - TransformFunction transformFunction = testLeastPreconditions( - String.format("least(%s)", INT_SV_COLUMN)); + TransformFunction transformFunction = testLeastPreconditions(String.format("least(%s)", INT_SV_COLUMN)); assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.INT); int[] intValues = transformFunction.transformToIntValuesSV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { @@ -99,8 +98,8 @@ public void testLeastTransformFunctionUnaryInt() { @Test public void testLeastTransformFunctionIntLong() { - TransformFunction transformFunction = testLeastPreconditions( - String.format("least(%s, %d, %s)", INT_SV_COLUMN, -1, LONG_SV_COLUMN)); + TransformFunction transformFunction = + testLeastPreconditions(String.format("least(%s, %d, %s)", INT_SV_COLUMN, -1, LONG_SV_COLUMN)); assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.LONG); long[] longValues = transformFunction.transformToLongValuesSV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { @@ -110,8 +109,7 @@ public void testLeastTransformFunctionIntLong() { @Test public void testLeastTransformFunctionUnaryLong() { - TransformFunction transformFunction = testLeastPreconditions( - String.format("least(%s)", LONG_SV_COLUMN)); + TransformFunction transformFunction = testLeastPreconditions(String.format("least(%s)", LONG_SV_COLUMN)); assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.LONG); long[] longValues = transformFunction.transformToLongValuesSV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { @@ -121,19 +119,18 @@ public void testLeastTransformFunctionUnaryLong() { @Test public void testLeastTransformFunctionIntFloat() { - TransformFunction transformFunction = testLeastPreconditions( - String.format("least(%s, %d, %s)", INT_SV_COLUMN, -1, FLOAT_SV_COLUMN)); + TransformFunction transformFunction = + testLeastPreconditions(String.format("least(%s, %d, %s)", INT_SV_COLUMN, -1, FLOAT_SV_COLUMN)); assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.DOUBLE); double[] doubleValues = transformFunction.transformToDoubleValuesSV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { - assertEquals(doubleValues[i], Math.min(Math.min(_intSVValues[i], -1), _doubleSVValues[i])); + assertEquals(doubleValues[i], Math.min(Math.min(_intSVValues[i], -1), Double.valueOf(_floatSVValues[i]))); } } @Test public void testLeastTransformFunctionUnaryFloat() { - TransformFunction transformFunction = testLeastPreconditions( - String.format("least(%s)", FLOAT_SV_COLUMN)); + TransformFunction transformFunction = testLeastPreconditions(String.format("least(%s)", FLOAT_SV_COLUMN)); assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.FLOAT); float[] floatValues = transformFunction.transformToFloatValuesSV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { @@ -143,8 +140,8 @@ public void testLeastTransformFunctionUnaryFloat() { @Test public void testLeastTransformFunctionIntDouble() { - TransformFunction transformFunction = testLeastPreconditions( - String.format("least(%s, %d, %s)", INT_SV_COLUMN, -1, DOUBLE_SV_COLUMN)); + TransformFunction transformFunction = + testLeastPreconditions(String.format("least(%s, %d, %s)", INT_SV_COLUMN, -1, DOUBLE_SV_COLUMN)); assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.DOUBLE); double[] doubleValues = transformFunction.transformToDoubleValuesSV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { @@ -154,8 +151,7 @@ public void testLeastTransformFunctionIntDouble() { @Test public void testLeastTransformFunctionUnaryDouble() { - TransformFunction transformFunction = testLeastPreconditions( - String.format("least(%s)", DOUBLE_SV_COLUMN)); + TransformFunction transformFunction = testLeastPreconditions(String.format("least(%s)", DOUBLE_SV_COLUMN)); assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.DOUBLE); double[] doubleValues = transformFunction.transformToDoubleValuesSV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { @@ -177,8 +173,8 @@ public void testLeastTransformFunctionNumericTypes1() { @Test public void testLeastTransformFunctionNumericTypes2() { - TransformFunction transformFunction = testLeastPreconditions( - String.format("least(%s, %s, %s)", INT_SV_COLUMN, FLOAT_SV_COLUMN, LONG_SV_COLUMN)); + TransformFunction transformFunction = + testLeastPreconditions(String.format("least(%s, %s, %s)", INT_SV_COLUMN, FLOAT_SV_COLUMN, LONG_SV_COLUMN)); // Note: In the current code, least return type is DOUBLE if there is a float input, but output values are float! assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.DOUBLE); float[] floatValues = transformFunction.transformToFloatValuesSV(_projectionBlock); @@ -189,8 +185,8 @@ public void testLeastTransformFunctionNumericTypes2() { @Test public void testLeastTransformFunctionTime() { - TransformFunction transformFunction = testLeastPreconditions( - String.format("least(%s, cast(%s AS TIMESTAMP))", TIMESTAMP_COLUMN, LONG_SV_COLUMN)); + TransformFunction transformFunction = + testLeastPreconditions(String.format("least(%s, cast(%s AS TIMESTAMP))", TIMESTAMP_COLUMN, LONG_SV_COLUMN)); assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.TIMESTAMP); long[] longValues = transformFunction.transformToLongValuesSV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { @@ -210,8 +206,8 @@ public void testLeastTransformFunctionUnaryString() { @Test public void testLeastTransformFunctionTimestampWithLegacyTimeColumn() { - TransformFunction transformFunction = testLeastPreconditions( - String.format("least(%s, cast(%s as TIMESTAMP))", TIMESTAMP_COLUMN, TIME_COLUMN)); + TransformFunction transformFunction = + testLeastPreconditions(String.format("least(%s, cast(%s as TIMESTAMP))", TIMESTAMP_COLUMN, TIME_COLUMN)); assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.TIMESTAMP); long[] longValues = transformFunction.transformToLongValuesSV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { @@ -219,6 +215,60 @@ public void testLeastTransformFunctionTimestampWithLegacyTimeColumn() { } } + @Test + public void testLeastTransformFunctionNullLiteral() { + TransformFunction transformFunction = testLeastPreconditionsNullHandlingEnabled( + String.format("least(%s, null, %s)", INT_SV_COLUMN, DOUBLE_SV_COLUMN)); + assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.DOUBLE); + RoaringBitmap nullBitmap = new RoaringBitmap(); + nullBitmap.add(0, (long) NUM_ROWS); + + testNullBitmap(transformFunction, nullBitmap); + } + + @Test + public void testLeastTransformFunctionNullColumn() { + TransformFunction transformFunction = testLeastPreconditionsNullHandlingEnabled( + String.format("least(%s, %s)", INT_SV_NULL_COLUMN, DOUBLE_SV_COLUMN)); + assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.DOUBLE); + double[] doubleValues = transformFunction.transformToDoubleValuesSV(_projectionBlock); + RoaringBitmap nullBitmap = transformFunction.getNullBitmap(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + assertTrue(nullBitmap.contains(i)); + } else { + assertEquals(doubleValues[i], Math.min(_intSVValues[i], _doubleSVValues[i])); + } + } + } + + @Test + public void testLeastTransformFunctionAllNulls() { + TransformFunction transformFunction = + testLeastPreconditionsNullHandlingEnabled(String.format("least(null, null, null)")); + assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.UNKNOWN); + RoaringBitmap expectedNull = new RoaringBitmap(); + expectedNull.add(0L, NUM_ROWS); + assertEquals(transformFunction.getNullBitmap(_projectionBlock), expectedNull); + } + + @Test + public void testLeastTransformFunctionPartialAllNulls() { + TransformFunction transformFunction = testLeastPreconditionsNullHandlingEnabled( + String.format("least(%s, %s, %s)", INT_SV_NULL_COLUMN, INT_SV_NULL_COLUMN, INT_SV_NULL_COLUMN)); + assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.INT); + int[] intValues = transformFunction.transformToIntValuesSV(_projectionBlock); + RoaringBitmap expectedNull = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + expectedNull.add(i); + } else { + assertEquals(intValues[i], _intSVValues[i]); + } + } + testNullBitmap(transformFunction, expectedNull); + } + @Test(dataProvider = "rejectedParameters", expectedExceptions = BadQueryRequestException.class) public void testRejectLeast(String params) { testGreatestPreconditions("least" + params); @@ -247,8 +297,8 @@ public void testGreatestTransformFunctionUnaryInt() { @Test public void testGreatestTransformFunctionIntLong() { - TransformFunction transformFunction = testGreatestPreconditions( - String.format("greatest(%s, %d, %s)", INT_SV_COLUMN, -1, LONG_SV_COLUMN)); + TransformFunction transformFunction = + testGreatestPreconditions(String.format("greatest(%s, %d, %s)", INT_SV_COLUMN, -1, LONG_SV_COLUMN)); assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.LONG); long[] longValues = transformFunction.transformToLongValuesSV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { @@ -268,12 +318,12 @@ public void testGreatestTransformFunctionUnaryLong() { @Test public void testGreatestTransformFunctionIntFloat() { - TransformFunction transformFunction = testGreatestPreconditions( - String.format("greatest(%s, %d, %s)", INT_SV_COLUMN, -1, FLOAT_SV_COLUMN)); + TransformFunction transformFunction = + testGreatestPreconditions(String.format("greatest(%s, %d, %s)", INT_SV_COLUMN, -1, FLOAT_SV_COLUMN)); assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.DOUBLE); double[] doubleValues = transformFunction.transformToDoubleValuesSV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { - assertEquals(doubleValues[i], Math.max(Math.max(_intSVValues[i], -1), _doubleSVValues[i])); + assertEquals(doubleValues[i], Math.max(Math.max(_intSVValues[i], -1), Double.valueOf(_floatSVValues[i]))); } } @@ -299,8 +349,8 @@ public void testGreatestTransformFunctionUnaryString() { @Test public void testGreatestTransformFunctionIntDouble() { - TransformFunction transformFunction = testGreatestPreconditions( - String.format("greatest(%s, %d, %s)", INT_SV_COLUMN, -1, DOUBLE_SV_COLUMN)); + TransformFunction transformFunction = + testGreatestPreconditions(String.format("greatest(%s, %d, %s)", INT_SV_COLUMN, -1, DOUBLE_SV_COLUMN)); assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.DOUBLE); double[] doubleValues = transformFunction.transformToDoubleValuesSV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { @@ -308,6 +358,61 @@ public void testGreatestTransformFunctionIntDouble() { } } + @Test + public void testGreatestTransformFunctionNullLiteral() { + TransformFunction transformFunction = testGreatestPreconditionsNullHandlingEnabled( + String.format("greatest(%s, null, %s)", INT_SV_COLUMN, DOUBLE_SV_COLUMN)); + assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.DOUBLE); + RoaringBitmap nullBitmap = new RoaringBitmap(); + nullBitmap.add(0, (long) NUM_ROWS); + + testNullBitmap(transformFunction, nullBitmap); + } + + @Test + public void testGreatestTransformFunctionNullColumn() { + TransformFunction transformFunction = testGreatestPreconditionsNullHandlingEnabled( + String.format("greatest(%s, %s)", INT_SV_NULL_COLUMN, DOUBLE_SV_COLUMN)); + assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.DOUBLE); + double[] doubleValues = transformFunction.transformToDoubleValuesSV(_projectionBlock); + RoaringBitmap nullBitmap = transformFunction.getNullBitmap(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + assertTrue(nullBitmap.contains(i)); + } else { + assertEquals(doubleValues[i], Math.max(_intSVValues[i], _doubleSVValues[i])); + } + } + } + + @Test + public void testGreatestTransformFunctionAllNulls() { + TransformFunction transformFunction = + testGreatestPreconditionsNullHandlingEnabled(String.format("greatest(null, null, null)")); + assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.UNKNOWN); + double[] doubleValues = transformFunction.transformToDoubleValuesSV(_projectionBlock); + RoaringBitmap expectedNull = new RoaringBitmap(); + expectedNull.add(0L, NUM_ROWS); + testNullBitmap(transformFunction, expectedNull); + } + + @Test + public void testGreatestTransformFunctionPartialAllNulls() { + TransformFunction transformFunction = testGreatestPreconditionsNullHandlingEnabled( + String.format("greatest(%s, %s, %s)", INT_SV_NULL_COLUMN, INT_SV_NULL_COLUMN, INT_SV_NULL_COLUMN)); + assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.INT); + int[] intValues = transformFunction.transformToIntValuesSV(_projectionBlock); + RoaringBitmap expectedNull = new RoaringBitmap(); + for (int i = 0; i < NUM_ROWS; i++) { + if (isNullRow(i)) { + expectedNull.add(i); + } else { + assertEquals(intValues[i], _intSVValues[i]); + } + } + testNullBitmap(transformFunction, expectedNull); + } + @Test public void testGreatestTransformFunctionString() { TransformFunction transformFunction = testGreatestPreconditions( @@ -323,8 +428,7 @@ public void testGreatestTransformFunctionString() { @Test public void testGreatestTransformFunctionUnaryDouble() { - TransformFunction transformFunction = testGreatestPreconditions( - String.format("greatest(%s)", DOUBLE_SV_COLUMN)); + TransformFunction transformFunction = testGreatestPreconditions(String.format("greatest(%s)", DOUBLE_SV_COLUMN)); assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.DOUBLE); double[] doubleValues = transformFunction.transformToDoubleValuesSV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { @@ -334,8 +438,8 @@ public void testGreatestTransformFunctionUnaryDouble() { @Test public void testGreatestTransformFunctionUnaryBigDecimal() { - TransformFunction transformFunction = testGreatestPreconditions( - String.format("greatest(%s)", BIG_DECIMAL_SV_COLUMN)); + TransformFunction transformFunction = + testGreatestPreconditions(String.format("greatest(%s)", BIG_DECIMAL_SV_COLUMN)); assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.BIG_DECIMAL); BigDecimal[] bigDecimalValues = transformFunction.transformToBigDecimalValuesSV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { @@ -368,8 +472,8 @@ public void testGreatestTransformFunctionTime() { @Test public void testGreatestTransformFunctionTimestampWithLegacyTimeColumn() { - TransformFunction transformFunction = testGreatestPreconditions( - String.format("greatest(%s, cast(%s as TIMESTAMP))", TIMESTAMP_COLUMN, TIME_COLUMN)); + TransformFunction transformFunction = + testGreatestPreconditions(String.format("greatest(%s, cast(%s as TIMESTAMP))", TIMESTAMP_COLUMN, TIME_COLUMN)); assertEquals(transformFunction.getResultMetadata().getDataType(), FieldSpec.DataType.TIMESTAMP); long[] longValues = transformFunction.transformToLongValuesSV(_projectionBlock); for (int i = 0; i < NUM_ROWS; i++) { @@ -397,4 +501,20 @@ private TransformFunction testGreatestPreconditions(String expressionStr) { assertEquals(transformFunction.getName(), TransformFunctionType.GREATEST.getName()); return transformFunction; } + + private TransformFunction testLeastPreconditionsNullHandlingEnabled(String expressionStr) { + ExpressionContext expression = RequestContextUtils.getExpression(expressionStr); + TransformFunction transformFunction = TransformFunctionFactory.getNullHandlingEnabled(expression, _dataSourceMap); + assertTrue(transformFunction instanceof LeastTransformFunction); + assertEquals(transformFunction.getName(), TransformFunctionType.LEAST.getName()); + return transformFunction; + } + + private TransformFunction testGreatestPreconditionsNullHandlingEnabled(String expressionStr) { + ExpressionContext expression = RequestContextUtils.getExpression(expressionStr); + TransformFunction transformFunction = TransformFunctionFactory.getNullHandlingEnabled(expression, _dataSourceMap); + assertTrue(transformFunction instanceof GreatestTransformFunction); + assertEquals(transformFunction.getName(), TransformFunctionType.GREATEST.getName()); + return transformFunction; + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/VectorTransformFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/VectorTransformFunctionTest.java new file mode 100644 index 000000000000..56ab53e28530 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/function/VectorTransformFunctionTest.java @@ -0,0 +1,84 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.operator.transform.function; + +import org.apache.commons.lang3.StringUtils; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.request.context.RequestContextUtils; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + + +public class VectorTransformFunctionTest extends BaseTransformFunctionTest { + + @Test(dataProvider = "testVectorTransformFunctionDataProvider") + public void testVectorTransformFunction(String expressionStr, double lowerBound, double upperBound) { + ExpressionContext expression = RequestContextUtils.getExpression(expressionStr); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + double[] doubleValuesSV = transformFunction.transformToDoubleValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + assertTrue(doubleValuesSV[i] >= lowerBound, doubleValuesSV[i] + " < " + lowerBound); + assertTrue(doubleValuesSV[i] <= upperBound, doubleValuesSV[i] + " > " + upperBound); + } + } + + @Test + public void testVectorDimsTransformFunction() { + ExpressionContext expression = RequestContextUtils.getExpression("vectorDims(vector1)"); + TransformFunction transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + int[] intValuesSV = transformFunction.transformToIntValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + assertEquals(intValuesSV[i], VECTOR_DIM_SIZE); + } + + expression = RequestContextUtils.getExpression("vectorDims(vector2)"); + transformFunction = TransformFunctionFactory.get(expression, _dataSourceMap); + intValuesSV = transformFunction.transformToIntValuesSV(_projectionBlock); + for (int i = 0; i < NUM_ROWS; i++) { + assertEquals(intValuesSV[i], VECTOR_DIM_SIZE); + } + } + + @DataProvider(name = "testVectorTransformFunctionDataProvider") + public Object[][] testVectorTransformFunctionDataProvider() { + String zeroVectorLiteral = "ARRAY[0.0" + + StringUtils.repeat(",0.0", VECTOR_DIM_SIZE - 1) + + "]"; + return new Object[][]{ + new Object[]{"cosineDistance(vector1, vector2)", 0.1, 0.4}, + new Object[]{"cosineDistance(vector1, vector2, 0)", 0.1, 0.4}, + new Object[]{"cosineDistance(vector1, zeroVector, 0)", 0.0, 0.0}, + new Object[]{"innerProduct(vector1, vector2)", 100, 160}, + new Object[]{"l1Distance(vector1, vector2)", 140, 210}, + new Object[]{"l2Distance(vector1, vector2)", 8, 11}, + new Object[]{"vectorNorm(vector1)", 10, 16}, + new Object[]{"vectorNorm(vector2)", 10, 16}, + + new Object[]{String.format("cosineDistance(vector1, %s, 0)", zeroVectorLiteral), 0.0, 0.0}, + new Object[]{String.format("innerProduct(vector1, %s)", zeroVectorLiteral), 0.0, 0.0}, + new Object[]{String.format("l1Distance(vector1, %s)", zeroVectorLiteral), 0, VECTOR_DIM_SIZE}, + new Object[]{String.format("l2Distance(vector1, %s)", zeroVectorLiteral), 0, VECTOR_DIM_SIZE}, + new Object[]{String.format("vectorDims(%s)", zeroVectorLiteral), VECTOR_DIM_SIZE, VECTOR_DIM_SIZE}, + new Object[]{String.format("vectorNorm(%s)", zeroVectorLiteral), 0.0, 0.0}, + }; + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/transformer/datetimehopwindow/DateTimeConverterHopWindowTest.java b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/transformer/datetimehopwindow/DateTimeConverterHopWindowTest.java new file mode 100644 index 000000000000..235f68e42b42 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/operator/transform/transformer/datetimehopwindow/DateTimeConverterHopWindowTest.java @@ -0,0 +1,283 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.operator.transform.transformer.datetimehopwindow; + +import java.util.ArrayList; +import java.util.List; +import org.apache.pinot.core.operator.transform.transformer.datetimehop.BaseDateTimeWindowHopTransformer; +import org.apache.pinot.core.operator.transform.transformer.datetimehop.DateTimeWindowHopTransformerFactory; +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + + +public class DateTimeConverterHopWindowTest { + @SuppressWarnings("unchecked") + @Test(dataProvider = "testDateTimeHopWindowConversion") + public void testDateTimeHopWindowConversion(String inputFormat, String outputFormat, String outputGranularity, + String windowGranularity, Object input, Object expected) { + BaseDateTimeWindowHopTransformer converter = + DateTimeWindowHopTransformerFactory.getDateTimeTransformer(inputFormat, outputFormat, outputGranularity, + windowGranularity); + int length; + Object output; + if (expected instanceof long[][]) { + length = ((long[][]) expected).length; + output = new long[length][]; + for (int i = 0; i < length; i++) { + ((long[][]) output)[i] = new long[((long[][]) expected)[i].length]; + } + } else { + length = ((String[][]) expected).length; + output = new String[length][]; + } + converter.transform(input, output, length); + Assert.assertEquals(output, expected); + } + + @DataProvider(name = "testDateTimeHopWindowConversion") + public Object[][] testDateTimeHopWindowConversion() { + List entries = new ArrayList<>(); + + /*************** Epoch to Epoch ***************/ + { + // Test bucketing to 15 minutes with 1h hop window, for one value + long[] input = { + 1696587946000L /* Fri Oct 06 2023 10:25:46 GMT+0000 */ + }; + long[][] expected = { + new long[]{ + 1696587300000L /* Fri Oct 06 2023 10:15:00 GMT+0000 */, 1696586400000L /* Fri Oct 06 2023 10:00:00 + GMT+0000 */, 1696585500000L /* Fri Oct 06 2023 09:45:00 GMT+0000 */, 1696584600000L /* Fri Oct 06 2023 + 09:30:00 GMT+0000 */, + } + }; + entries.add(new Object[]{ + "EPOCH|MILLISECONDS", "EPOCH|MILLISECONDS", "MINUTES|15", "HOURS", input, expected + }); + } + { + // Test bucketing to 15 minutes with 1h hop window, multiple values + long[] input = { + 1696587946000L /* Fri Oct 06 2023 10:25:46 GMT+0000 */, 1693998340000L /* Wed Sep 06 2023 11:05:40 GMT+0000 + */, + }; + long[][] expected = { + new long[]{ + 1696587300000L /* Fri Oct 06 2023 10:15:00 GMT+0000 */, 1696586400000L /* Fri Oct 06 2023 10:00:00 + GMT+0000 */, 1696585500000L /* Fri Oct 06 2023 09:45:00 GMT+0000 */, 1696584600000L /* Fri Oct 06 2023 + 09:30:00 GMT+0000 */, + }, new long[]{ + 1693998000000L /* Fri Oct 06 2023 10:15:00 GMT+0000 */, 1693997100000L /* Fri Oct 06 2023 10:00:00 GMT+0000 + */, 1693996200000L /* Fri Oct 06 2023 09:45:00 GMT+0000 */, 1693995300000L /* Fri Oct 06 2023 09:30:00 + GMT+0000 */, + } + }; + entries.add(new Object[]{ + "EPOCH|MILLISECONDS", "EPOCH|MILLISECONDS", "MINUTES|15", "HOURS", input, expected + }); + } + { + // Test bucketing with conversion to hours with 1h hop window, 15m bucketing + long[] input = { + 1696587946000L /* Fri Oct 06 2023 10:25:46 GMT+0000 */ + }; + long[][] expected = { + new long[]{ + 471274L /* Fri Oct 06 2023 10:00:00 GMT+0000 */, 471274L /* Fri Oct 06 2023 10:00:00 GMT+0000 */, + 471273L /* Fri Oct 06 2023 09:00:00 GMT+0000 */, 471273L /* Fri Oct 06 2023 09:00:00 GMT+0000 */, + } + }; + entries.add(new Object[]{"EPOCH|MILLISECONDS", "EPOCH|HOURS", "MINUTES|15", "HOURS", input, expected}); + } + { + // Test bucketing with conversion to 15 min with 1h hop window, 15m bucketing + long[] input = { + 1696587946000L /* Fri Oct 06 2023 10:25:46 GMT+0000 */ + }; + long[][] expected = { + new long[]{ + 1885097L /* Fri Oct 06 2023 10:15:00 GMT+0000 */, 1885096L /* Fri Oct 06 2023 10:00:00 GMT+0000 */, + 1885095L /* Fri Oct 06 2023 09:45:00 GMT+0000 */, 1885094L /* Fri Oct 06 2023 09:30:00 GMT+0000 */, + } + }; + entries.add(new Object[]{"EPOCH|MILLISECONDS", "EPOCH|MINUTES|15", "MINUTES|15", "HOURS", input, expected}); + } + { + { + // Test bucketing to 1hour with 15m hop window. Since there is no intersection - empty array is expected + long[] input = { + 1696587946000L /* Fri Oct 06 2023 10:25:46 GMT+0000 */ + }; + long[][] expected = { + new long[]{} + }; + entries.add(new Object[]{ + "EPOCH|MILLISECONDS", "EPOCH|MILLISECONDS", "MINUTES|60", "MINUTES|15", input, expected + }); + } + } + { + { + // Test bucketing with non-aligned window + long[] input = { + 1696587946000L /* Fri Oct 06 2023 10:25:46 GMT+0000 */ + }; + long[][] expected = { + new long[]{ + 1696587300000L /* Fri Oct 06 2023 10:15:00 GMT+0000 */, 1696586400000L /* Fri Oct 06 2023 10:00:00 + GMT+0000 */, 1696585500000L /* Fri Oct 06 2023 09:45:00 GMT+0000 */, + } + }; + entries.add(new Object[]{ + "EPOCH|MILLISECONDS", "EPOCH|MILLISECONDS", "MINUTES|15", "MINUTES|55", input, expected + }); + } + } + /*************** Epoch to SDF ***************/ + { + { + // Test conversion from millis since epoch to simple date format (GMT timezone) + long[] input = { + 1696587946000L /* Fri Oct 06 2023 10:25:46 GMT+0000 */ + }; + String[][] expected = { + {"2023-10-06-10:15", "2023-10-06-10:00", "2023-10-06-09:45", "2023-10-06-09:30"} + }; + entries.add(new Object[]{ + "EPOCH|MILLISECONDS", "SIMPLE_DATE_FORMAT|yyyy-MM-dd-HH:mm|GMT", "MINUTES|15", "HOURS|1", + input, expected + }); + } + } + { + { + // Test multiple values conversion from millis since epoch to simple date format (GMT timezone) + long[] input = { + 1696587946000L, /* Fri Oct 06 2023 10:25:46 GMT+0000 */ + 1696582946000L /* Fri Oct 06 2023 09:02:26 GMT+0000 */ + }; + String[][] expected = { + {"2023-10-06-10:15", "2023-10-06-10:00", "2023-10-06-09:45", "2023-10-06-09:30"}, { + "2023-10-06-09:00", "2023-10-06-08:45", "2023-10-06-08:30", "2023-10-06-08:15" + } + }; + entries.add(new Object[]{ + "EPOCH|MILLISECONDS", "SIMPLE_DATE_FORMAT|yyyy-MM-dd-HH:mm|GMT", "MINUTES|15", "HOURS|1", input, expected + }); + } + } + { + { + // Test single value conversion from hours to simple date format (Los Angeles timezone) + long[] input = { + 471274L, /* Fri Oct 06 2023 10:00:00 GMT+0000 */ + }; + String[][] expected = { + {"2023-10-06-10:00", "2023-10-06-09:45", "2023-10-06-09:30", "2023-10-06-09:15"} + }; + entries.add(new Object[]{ + "EPOCH|HOURS", "SIMPLE_DATE_FORMAT|yyyy-MM-dd-HH:mm|GMT", "MINUTES|15", "HOURS|1", input, expected + }); + } + } + { + { + // Test single value conversion from 2 hours to simple date format (Los Angeles timezone) + long[] input = { + 235637L, /* Fri Oct 06 2023 10:00:00 GMT+0000 */ + }; + String[][] expected = { + {"2023-10-06-10:00", "2023-10-06-09:45", "2023-10-06-09:30", "2023-10-06-09:15"} + }; + entries.add(new Object[]{ + "EPOCH|HOURS|2", "SIMPLE_DATE_FORMAT|yyyy-MM-dd-HH:mm|GMT", "MINUTES|15", "HOURS|1", input, expected + }); + } + } + + /*************** SDF to EPOCH ***************/ + { + // Test conversion from simple date format (GMT timezone) to millis since epoch with 1h window and 15m hop + String[] input = { + "2023-10-06 10:25:46" + }; + long[][] expected = { + { + 1696587300000L /* Fri Oct 06 2023 10:00:00 GMT+0000 */, 1696586400000L /* Fri Oct 06 2023 09:45:00 + GMT+0000 */, 1696585500000L /* Fri Oct 06 2023 09:30:00 GMT+0000 */, 1696584600000L /* Fri Oct 06 2023 + 09:15:00 GMT+0000 */ + } + }; + entries.add(new Object[]{ + "SIMPLE_DATE_FORMAT|yyyy-MM-dd HH:mm:ss|GMT", "EPOCH|MILLISECONDS|1", "MINUTES|15", "MINUTES|60", + input, expected + }); + } + { + // Test conversion from simple date format (GMT timezone) to millis since epoch with 1h window + // and 15m hop with 1:HOURS input granularity + String[] input = { + "2023-10-06 10:25:46" + }; + long[][] expected = { + { + 1696587300000L /* Fri Oct 06 2023 10:00:00 GMT+0000 */, 1696586400000L /* Fri Oct 06 2023 09:45:00 + GMT+0000 */, 1696585500000L /* Fri Oct 06 2023 09:30:00 GMT+0000 */, 1696584600000L /* Fri Oct 06 2023 + 09:15:00 GMT+0000 */ + } + }; + entries.add(new Object[]{ + "SIMPLE_DATE_FORMAT|yyyy-MM-dd HH:mm:ss|GMT", "EPOCH|MILLISECONDS|1", "MINUTES|15", "MINUTES|60", + input, expected + }); + } + + /*************** SDF to SDF ***************/ + { + // Test conversion from one simple date format to another with 1h window and 15m hop + String[] input = { + "2023-10-06 10:12:46" + }; + String[][] expected = { + {"2023-10-06 10:00", "2023-10-06 09:45", "2023-10-06 09:30", "2023-10-06 09:15"} + }; + entries.add(new Object[]{ + "SIMPLE_DATE_FORMAT|yyyy-MM-dd HH:mm:ss|SECONDS|1", "SIMPLE_DATE_FORMAT|yyyy-MM-dd HH:mm|GMT|MINUTES|1", + "MINUTES|15", "MINUTES|60", input, expected + }); + } + { + // Test conversion from one simple date format to another with 1h window and 15m hop. Different timezone + String[] input = { + "2023-10-06 10:12:46" + }; + String[][] expected = { + {"2023-10-06 03:00", "2023-10-06 02:45", "2023-10-06 02:30", "2023-10-06 02:15"} + }; + entries.add(new Object[]{ + "SIMPLE_DATE_FORMAT|yyyy-MM-dd HH:mm:ss|SECONDS|1", "SIMPLE_DATE_FORMAT|yyyy-MM-dd " + + "HH:mm|America/Los_Angeles", "MINUTES|15", "MINUTES|60", input, expected + }); + } + + return entries.toArray(new Object[entries.size()][]); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/plan/CombinePlanNodeTest.java b/pinot-core/src/test/java/org/apache/pinot/core/plan/CombinePlanNodeTest.java index 63da946a5ce7..8dcf6c135721 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/plan/CombinePlanNodeTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/plan/CombinePlanNodeTest.java @@ -140,7 +140,8 @@ public void testCancelPlanNode() { _queryContext.setEndTimeMs(System.currentTimeMillis() + Server.DEFAULT_QUERY_EXECUTOR_TIMEOUT_MS); CombinePlanNode combinePlanNode = new CombinePlanNode(planNodes, _queryContext, _executorService, null); AtomicReference exp = new AtomicReference<>(); - ExecutorService combineExecutor = Executors.newSingleThreadExecutor(); + // Avoid early finalization by not using Executors.newSingleThreadExecutor (java <= 20, JDK-8145304) + ExecutorService combineExecutor = Executors.newFixedThreadPool(1); try { Future future = combineExecutor.submit(() -> { try { diff --git a/pinot-core/src/test/java/org/apache/pinot/core/plan/FilterPlanNodeTest.java b/pinot-core/src/test/java/org/apache/pinot/core/plan/FilterPlanNodeTest.java index 8a44e830757b..4f86f8d85ae5 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/plan/FilterPlanNodeTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/plan/FilterPlanNodeTest.java @@ -20,12 +20,14 @@ import java.util.concurrent.atomic.AtomicInteger; import org.apache.pinot.core.common.BlockDocIdIterator; +import org.apache.pinot.core.common.BlockDocIdSet; import org.apache.pinot.core.operator.blocks.FilterBlock; -import org.apache.pinot.core.operator.docidsets.FilterBlockDocIdSet; import org.apache.pinot.core.operator.filter.BaseFilterOperator; import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.segment.local.upsert.UpsertUtils; import org.apache.pinot.segment.spi.Constants; import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; import org.apache.pinot.segment.spi.SegmentMetadata; import org.apache.pinot.segment.spi.index.mutable.ThreadSafeMutableRoaringBitmap; import org.mockito.stubbing.Answer; @@ -48,8 +50,8 @@ public void testConsistentSnapshot() when(segment.getValidDocIds()).thenReturn(bitmap); AtomicInteger numDocs = new AtomicInteger(0); when(meta.getTotalDocs()).then((Answer) invocationOnMock -> numDocs.get()); - QueryContext ctx = mock(QueryContext.class); - when(ctx.getFilter()).thenReturn(null); + QueryContext queryContext = mock(QueryContext.class); + when(queryContext.getFilter()).thenReturn(null); numDocs.set(3); bitmap.add(0); @@ -69,18 +71,20 @@ public void testConsistentSnapshot() // Result should be invariant - always exactly 3 docs for (int i = 0; i < 10_000; i++) { - assertEquals(getNumberOfFilteredDocs(segment, ctx), 3); + SegmentContext segmentContext = new SegmentContext(segment); + segmentContext.setQueryableDocIdsSnapshot(UpsertUtils.getQueryableDocIdsSnapshotFromSegment(segment)); + assertEquals(getNumberOfFilteredDocs(segmentContext, queryContext), 3); } updater.join(); } - private int getNumberOfFilteredDocs(IndexSegment segment, QueryContext ctx) { - FilterPlanNode node = new FilterPlanNode(segment, ctx); + private int getNumberOfFilteredDocs(SegmentContext segmentContext, QueryContext queryContext) { + FilterPlanNode node = new FilterPlanNode(segmentContext, queryContext); BaseFilterOperator op = node.run(); int numDocsFiltered = 0; FilterBlock block = op.nextBlock(); - FilterBlockDocIdSet blockIds = block.getBlockDocIdSet(); + BlockDocIdSet blockIds = block.getBlockDocIdSet(); BlockDocIdIterator it = blockIds.iterator(); while (it.next() != Constants.EOF) { numDocsFiltered++; diff --git a/pinot-core/src/test/java/org/apache/pinot/core/plan/maker/MetadataAndDictionaryAggregationPlanMakerTest.java b/pinot-core/src/test/java/org/apache/pinot/core/plan/maker/MetadataAndDictionaryAggregationPlanMakerTest.java index f6df4e9b4ef7..e1b9008799c0 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/plan/maker/MetadataAndDictionaryAggregationPlanMakerTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/plan/maker/MetadataAndDictionaryAggregationPlanMakerTest.java @@ -21,7 +21,6 @@ import java.io.File; import java.net.URL; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; @@ -39,11 +38,15 @@ import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; import org.apache.pinot.segment.local.upsert.ConcurrentMapPartitionUpsertMetadataManager; +import org.apache.pinot.segment.local.upsert.UpsertContext; +import org.apache.pinot.segment.local.upsert.UpsertUtils; import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; import org.apache.pinot.segment.spi.creator.SegmentIndexCreationDriver; +import org.apache.pinot.segment.spi.index.StandardIndexes; import org.apache.pinot.segment.spi.index.mutable.ThreadSafeMutableRoaringBitmap; -import org.apache.pinot.spi.config.table.HashFunction; +import org.apache.pinot.spi.config.table.IndexConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.config.table.ingestion.IngestionConfig; @@ -53,7 +56,6 @@ import org.apache.pinot.spi.data.TimeGranularitySpec; import org.apache.pinot.spi.utils.ReadMode; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; -import org.mockito.Mockito; import org.testng.annotations.AfterClass; import org.testng.annotations.AfterTest; import org.testng.annotations.BeforeClass; @@ -61,6 +63,7 @@ import org.testng.annotations.DataProvider; import org.testng.annotations.Test; +import static org.mockito.Mockito.mock; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertTrue; @@ -113,8 +116,8 @@ public void buildSegment() segmentGeneratorConfig.setTableName("testTable"); segmentGeneratorConfig.setSegmentName(SEGMENT_NAME); segmentGeneratorConfig.setOutDir(INDEX_DIR.getAbsolutePath()); - segmentGeneratorConfig.setInvertedIndexCreationColumns( - Arrays.asList("column6", "column7", "column11", "column17", "column18")); + segmentGeneratorConfig.setIndexOn(StandardIndexes.inverted(), IndexConfig.ENABLED, "column6", "column7", "column11", + "column17", "column18"); // Build the index segment. SegmentIndexCreationDriver driver = new SegmentIndexCreationDriverImpl(); @@ -125,17 +128,24 @@ public void buildSegment() @BeforeClass public void loadSegment() throws Exception { + ServerMetrics.register(mock(ServerMetrics.class)); _indexSegment = ImmutableSegmentLoader.load(new File(INDEX_DIR, SEGMENT_NAME), ReadMode.heap); - ServerMetrics serverMetrics = Mockito.mock(ServerMetrics.class); _upsertIndexSegment = ImmutableSegmentLoader.load(new File(INDEX_DIR, SEGMENT_NAME), ReadMode.heap); - ((ImmutableSegmentImpl) _upsertIndexSegment).enableUpsert( - new ConcurrentMapPartitionUpsertMetadataManager("testTable_REALTIME", 0, Collections.singletonList("column6"), - "daysSinceEpoch", HashFunction.NONE, null, false, serverMetrics), new ThreadSafeMutableRoaringBitmap()); + UpsertContext upsertContext = + new UpsertContext.Builder().setTableConfig(mock(TableConfig.class)).setSchema(mock(Schema.class)) + .setPrimaryKeyColumns(Collections.singletonList("column6")) + .setComparisonColumns(Collections.singletonList("daysSinceEpoch")).setTableIndexDir(INDEX_DIR).build(); + ConcurrentMapPartitionUpsertMetadataManager upsertMetadataManager = + new ConcurrentMapPartitionUpsertMetadataManager("testTable_REALTIME", 0, upsertContext); + ((ImmutableSegmentImpl) _upsertIndexSegment).enableUpsert(upsertMetadataManager, + new ThreadSafeMutableRoaringBitmap(), null); } @AfterClass public void destroySegment() { _indexSegment.destroy(); + _upsertIndexSegment.offload(); + _upsertIndexSegment.destroy(); } @AfterTest @@ -147,9 +157,12 @@ public void deleteSegment() { public void testPlanMaker(String query, Class> operatorClass, Class> upsertOperatorClass) { QueryContext queryContext = QueryContextConverterUtils.getQueryContext(query); - Operator operator = PLAN_MAKER.makeSegmentPlanNode(_indexSegment, queryContext).run(); + Operator operator = PLAN_MAKER.makeSegmentPlanNode(new SegmentContext(_indexSegment), queryContext).run(); assertTrue(operatorClass.isInstance(operator)); - Operator upsertOperator = PLAN_MAKER.makeSegmentPlanNode(_upsertIndexSegment, queryContext).run(); + + SegmentContext segmentContext = new SegmentContext(_upsertIndexSegment); + segmentContext.setQueryableDocIdsSnapshot(UpsertUtils.getQueryableDocIdsSnapshotFromSegment(_upsertIndexSegment)); + Operator upsertOperator = PLAN_MAKER.makeSegmentPlanNode(segmentContext, queryContext).run(); assertTrue(upsertOperatorClass.isInstance(upsertOperator)); } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/plan/maker/QueryOverrideWithHintsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/plan/maker/QueryOverrideWithHintsTest.java index 35ae0d685d57..c6750af635aa 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/plan/maker/QueryOverrideWithHintsTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/plan/maker/QueryOverrideWithHintsTest.java @@ -89,6 +89,12 @@ public ThreadSafeMutableRoaringBitmap getValidDocIds() { return null; } + @Nullable + @Override + public ThreadSafeMutableRoaringBitmap getQueryableDocIds() { + return null; + } + @Override public GenericRow getRecord(int docId, GenericRow reuse) { return null; @@ -99,6 +105,10 @@ public Object getValue(int docId, String column) { return null; } + @Override + public void offload() { + } + @Override public void destroy() { } @@ -116,32 +126,32 @@ public void testExpressionContextHashcode() { expressionContext2 = ExpressionContext.forIdentifier(""); assertNotEquals(expressionContext1, expressionContext2); assertNotEquals(expressionContext1.hashCode(), expressionContext2.hashCode()); - expressionContext1 = ExpressionContext.forLiteralContext(FieldSpec.DataType.STRING, "abc"); - expressionContext2 = ExpressionContext.forLiteralContext(FieldSpec.DataType.STRING, "abc"); + expressionContext1 = ExpressionContext.forLiteral(FieldSpec.DataType.STRING, "abc"); + expressionContext2 = ExpressionContext.forLiteral(FieldSpec.DataType.STRING, "abc"); assertEquals(expressionContext1, expressionContext2); assertEquals(expressionContext1.hashCode(), expressionContext2.hashCode()); - expressionContext2 = ExpressionContext.forLiteralContext(FieldSpec.DataType.STRING, "abcd"); + expressionContext2 = ExpressionContext.forLiteral(FieldSpec.DataType.STRING, "abcd"); assertNotEquals(expressionContext1, expressionContext2); assertNotEquals(expressionContext1.hashCode(), expressionContext2.hashCode()); - expressionContext2 = ExpressionContext.forLiteralContext(FieldSpec.DataType.STRING, ""); + expressionContext2 = ExpressionContext.forLiteral(FieldSpec.DataType.STRING, ""); assertNotEquals(expressionContext1, expressionContext2); assertNotEquals(expressionContext1.hashCode(), expressionContext2.hashCode()); expressionContext1 = ExpressionContext.forFunction(new FunctionContext(FunctionContext.Type.TRANSFORM, "func1", ImmutableList.of(ExpressionContext.forIdentifier("abc"), - ExpressionContext.forLiteralContext(FieldSpec.DataType.STRING, "abc")))); + ExpressionContext.forLiteral(FieldSpec.DataType.STRING, "abc")))); expressionContext2 = ExpressionContext.forFunction(new FunctionContext(FunctionContext.Type.TRANSFORM, "func1", ImmutableList.of(ExpressionContext.forIdentifier("abc"), - ExpressionContext.forLiteralContext(FieldSpec.DataType.STRING, "abc")))); + ExpressionContext.forLiteral(FieldSpec.DataType.STRING, "abc")))); assertEquals(expressionContext1, expressionContext2); assertEquals(expressionContext1.hashCode(), expressionContext2.hashCode()); expressionContext1 = ExpressionContext.forFunction(new FunctionContext(FunctionContext.Type.TRANSFORM, "datetrunc", - ImmutableList.of(ExpressionContext.forLiteralContext(FieldSpec.DataType.STRING, "DAY"), - ExpressionContext.forLiteralContext(FieldSpec.DataType.STRING, "event_time_ts")))); + ImmutableList.of(ExpressionContext.forLiteral(FieldSpec.DataType.STRING, "DAY"), + ExpressionContext.forLiteral(FieldSpec.DataType.STRING, "event_time_ts")))); expressionContext2 = ExpressionContext.forFunction(new FunctionContext(FunctionContext.Type.TRANSFORM, "datetrunc", - ImmutableList.of(ExpressionContext.forLiteralContext(FieldSpec.DataType.STRING, "DAY"), - ExpressionContext.forLiteralContext(FieldSpec.DataType.STRING, "event_time_ts")))); + ImmutableList.of(ExpressionContext.forLiteral(FieldSpec.DataType.STRING, "DAY"), + ExpressionContext.forLiteral(FieldSpec.DataType.STRING, "event_time_ts")))); assertEquals(expressionContext1, expressionContext2); assertEquals(expressionContext1.hashCode(), expressionContext2.hashCode()); } @@ -150,13 +160,13 @@ public void testExpressionContextHashcode() { public void testOverrideFilterWithExpressionOverrideHints() { ExpressionContext dateTruncFunctionExpr = ExpressionContext.forFunction( new FunctionContext(FunctionContext.Type.TRANSFORM, "dateTrunc", new ArrayList<>(new ArrayList<>( - ImmutableList.of(ExpressionContext.forLiteralContext(FieldSpec.DataType.STRING, "MONTH"), + ImmutableList.of(ExpressionContext.forLiteral(FieldSpec.DataType.STRING, "MONTH"), ExpressionContext.forIdentifier("ts")))))); ExpressionContext timestampIndexColumn = ExpressionContext.forIdentifier("$ts$MONTH"); ExpressionContext equalsExpression = ExpressionContext.forFunction( new FunctionContext(FunctionContext.Type.TRANSFORM, "EQUALS", new ArrayList<>( ImmutableList.of(dateTruncFunctionExpr, - ExpressionContext.forLiteralContext(FieldSpec.DataType.INT, 1000))))); + ExpressionContext.forLiteral(FieldSpec.DataType.INT, 1000))))); FilterContext filter = RequestContextUtils.getFilter(equalsExpression); Map hints = ImmutableMap.of(dateTruncFunctionExpr, timestampIndexColumn); InstancePlanMakerImplV2.overrideWithExpressionHints(filter, _indexSegment, hints); @@ -176,33 +186,33 @@ public void testOverrideFilterWithExpressionOverrideHints() { public void testOverrideWithExpressionOverrideHints() { ExpressionContext dateTruncFunctionExpr = ExpressionContext.forFunction( new FunctionContext(FunctionContext.Type.TRANSFORM, "dateTrunc", new ArrayList<>( - ImmutableList.of(ExpressionContext.forLiteralContext(FieldSpec.DataType.STRING, "MONTH"), + ImmutableList.of(ExpressionContext.forLiteral(FieldSpec.DataType.STRING, "MONTH"), ExpressionContext.forIdentifier("ts"))))); ExpressionContext timestampIndexColumn = ExpressionContext.forIdentifier("$ts$MONTH"); ExpressionContext equalsExpression = ExpressionContext.forFunction( new FunctionContext(FunctionContext.Type.TRANSFORM, "EQUALS", new ArrayList<>( ImmutableList.of(dateTruncFunctionExpr, - ExpressionContext.forLiteralContext(FieldSpec.DataType.INT, 1000))))); + ExpressionContext.forLiteral(FieldSpec.DataType.INT, 1000))))); Map hints = ImmutableMap.of(dateTruncFunctionExpr, timestampIndexColumn); ExpressionContext newEqualsExpression = InstancePlanMakerImplV2.overrideWithExpressionHints(equalsExpression, _indexSegment, hints); assertEquals(newEqualsExpression.getFunction().getFunctionName(), "equals"); assertEquals(newEqualsExpression.getFunction().getArguments().get(0), timestampIndexColumn); assertEquals(newEqualsExpression.getFunction().getArguments().get(1), - ExpressionContext.forLiteralContext(FieldSpec.DataType.INT, 1000)); + ExpressionContext.forLiteral(FieldSpec.DataType.INT, 1000)); } @Test public void testNotOverrideWithExpressionOverrideHints() { ExpressionContext dateTruncFunctionExpr = ExpressionContext.forFunction( new FunctionContext(FunctionContext.Type.TRANSFORM, "dateTrunc", new ArrayList<>( - ImmutableList.of(ExpressionContext.forLiteralContext(FieldSpec.DataType.STRING, "DAY"), + ImmutableList.of(ExpressionContext.forLiteral(FieldSpec.DataType.STRING, "DAY"), ExpressionContext.forIdentifier("ts"))))); ExpressionContext timestampIndexColumn = ExpressionContext.forIdentifier("$ts$DAY"); ExpressionContext equalsExpression = ExpressionContext.forFunction( new FunctionContext(FunctionContext.Type.TRANSFORM, "EQUALS", new ArrayList<>( ImmutableList.of(dateTruncFunctionExpr, - ExpressionContext.forLiteralContext(FieldSpec.DataType.INT, 1000))))); + ExpressionContext.forLiteral(FieldSpec.DataType.INT, 1000))))); Map hints = ImmutableMap.of(dateTruncFunctionExpr, timestampIndexColumn); ExpressionContext newEqualsExpression = InstancePlanMakerImplV2.overrideWithExpressionHints(equalsExpression, _indexSegment, hints); @@ -210,16 +220,16 @@ public void testNotOverrideWithExpressionOverrideHints() { // No override as the physical column is not in the index segment. assertEquals(newEqualsExpression.getFunction().getArguments().get(0), dateTruncFunctionExpr); assertEquals(newEqualsExpression.getFunction().getArguments().get(1), - ExpressionContext.forLiteralContext(FieldSpec.DataType.INT, 1000)); + ExpressionContext.forLiteral(FieldSpec.DataType.INT, 1000)); } @Test public void testRewriteExpressionsWithHints() { PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery( "SELECT datetrunc('MONTH', ts), count(*), sum(abc) from myTable group by datetrunc('MONTH', ts) "); - Expression dateTruncFunctionExpr = RequestUtils.getFunctionExpression("datetrunc"); - dateTruncFunctionExpr.getFunctionCall().setOperands(new ArrayList<>( - ImmutableList.of(RequestUtils.getLiteralExpression("MONTH"), RequestUtils.getIdentifierExpression("ts")))); + Expression dateTruncFunctionExpr = + RequestUtils.getFunctionExpression("datetrunc", RequestUtils.getLiteralExpression("MONTH"), + RequestUtils.getIdentifierExpression("ts")); Expression timestampIndexColumn = RequestUtils.getIdentifierExpression("$ts$MONTH"); pinotQuery.setExpressionOverrideHints(ImmutableMap.of(dateTruncFunctionExpr, timestampIndexColumn)); QueryContext queryContext = QueryContextConverterUtils.getQueryContext(pinotQuery); @@ -232,9 +242,9 @@ public void testRewriteExpressionsWithHints() { public void testNotRewriteExpressionsWithHints() { PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery( "SELECT datetrunc('DAY', ts), count(*), sum(abc) from myTable group by datetrunc('DAY', ts)"); - Expression dateTruncFunctionExpr = RequestUtils.getFunctionExpression("datetrunc"); - dateTruncFunctionExpr.getFunctionCall().setOperands(new ArrayList<>( - ImmutableList.of(RequestUtils.getLiteralExpression("DAY"), RequestUtils.getIdentifierExpression("ts")))); + Expression dateTruncFunctionExpr = + RequestUtils.getFunctionExpression("datetrunc", RequestUtils.getLiteralExpression("DAY"), + RequestUtils.getIdentifierExpression("ts")); Expression timestampIndexColumn = RequestUtils.getIdentifierExpression("$ts$DAY"); pinotQuery.setExpressionOverrideHints(ImmutableMap.of(dateTruncFunctionExpr, timestampIndexColumn)); QueryContext queryContext = QueryContextConverterUtils.getQueryContext(pinotQuery); diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/AbstractAggregationFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/AbstractAggregationFunctionTest.java new file mode 100644 index 000000000000..122374d224c1 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/AbstractAggregationFunctionTest.java @@ -0,0 +1,121 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.query.aggregation.function; + +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.apache.commons.io.FileUtils; +import org.apache.pinot.queries.FluentQueryTest; +import org.apache.pinot.spi.config.table.FieldConfig; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; + + +public abstract class AbstractAggregationFunctionTest { + + protected File _baseDir; + + private static final FieldSpec.DataType[] VALID_DATA_TYPES = new FieldSpec.DataType[] { + FieldSpec.DataType.INT, + FieldSpec.DataType.LONG, + FieldSpec.DataType.FLOAT, + FieldSpec.DataType.DOUBLE, + FieldSpec.DataType.STRING, + FieldSpec.DataType.BYTES, + FieldSpec.DataType.BIG_DECIMAL, + FieldSpec.DataType.TIMESTAMP, + FieldSpec.DataType.BOOLEAN + }; + + protected static final Map SINGLE_FIELD_NULLABLE_SCHEMAS = Arrays.stream(VALID_DATA_TYPES) + .collect(Collectors.toMap(dt -> dt, dt -> new Schema.SchemaBuilder() + .setSchemaName("testTable") + .setEnableColumnBasedNullHandling(true) + .addDimensionField("myField", dt, f -> f.setNullable(true)) + .build())); + + protected static final TableConfig SINGLE_FIELD_TABLE_CONFIG = new TableConfigBuilder(TableType.OFFLINE) + .setTableName("testTable") + .build(); + + protected FluentQueryTest.DeclaringTable givenSingleNullableFieldTable(FieldSpec.DataType dataType, + boolean nullHandlingEnabled) { + return givenSingleNullableFieldTable(dataType, nullHandlingEnabled, null); + } + + protected FluentQueryTest.DeclaringTable givenSingleNullableFieldTable(FieldSpec.DataType dataType, + boolean nullHandlingEnabled, @Nullable Consumer customize) { + TableConfig tableConfig; + if (customize == null) { + tableConfig = SINGLE_FIELD_TABLE_CONFIG; + } else { + TableConfigBuilder builder = new TableConfigBuilder(TableType.OFFLINE).setTableName("testTable"); + FieldConfig.Builder fieldConfigBuilder = new FieldConfig.Builder("myField"); + customize.accept(fieldConfigBuilder); + FieldConfig fieldConfig = fieldConfigBuilder.build(); + builder.setFieldConfigList(Collections.singletonList(fieldConfig)); + + tableConfig = builder.build(); + } + + return FluentQueryTest.withBaseDir(_baseDir) + .withNullHandling(nullHandlingEnabled) + .givenTable(SINGLE_FIELD_NULLABLE_SCHEMAS.get(dataType), tableConfig); + } + + protected FluentQueryTest.DeclaringTable givenSingleNullableIntFieldTable(boolean nullHandling) { + return givenSingleNullableFieldTable(FieldSpec.DataType.INT, nullHandling, null); + } + + protected FluentQueryTest.DeclaringTable givenSingleNullableIntFieldTable(boolean nullHandling, + @Nullable Consumer customize) { + return givenSingleNullableFieldTable(FieldSpec.DataType.INT, nullHandling, customize); + } + + @BeforeClass + void createBaseDir() { + try { + _baseDir = Files.createTempDirectory(getClass().getSimpleName()).toFile(); + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + @AfterClass + void destroyBaseDir() + throws IOException { + if (_baseDir != null) { + FileUtils.deleteDirectory(_baseDir); + } + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/AbstractPercentileAggregationFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/AbstractPercentileAggregationFunctionTest.java new file mode 100644 index 000000000000..fe9cc09f26a9 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/AbstractPercentileAggregationFunctionTest.java @@ -0,0 +1,333 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.query.aggregation.function; + +import org.apache.pinot.queries.FluentQueryTest; +import org.apache.pinot.spi.data.FieldSpec; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + + +public abstract class AbstractPercentileAggregationFunctionTest extends AbstractAggregationFunctionTest { + + @DataProvider(name = "scenarios") + Object[] scenarios() { + return new Object[] { + new Scenario(FieldSpec.DataType.INT), + new Scenario(FieldSpec.DataType.LONG), + new Scenario(FieldSpec.DataType.FLOAT), + new Scenario(FieldSpec.DataType.DOUBLE), + }; + } + + public abstract String callStr(String column, int percent); + + public String getFinalResultColumnType() { + return "DOUBLE"; + } + + public class Scenario { + private final FieldSpec.DataType _dataType; + + public Scenario(FieldSpec.DataType dataType) { + _dataType = dataType; + } + + public FieldSpec.DataType getDataType() { + return _dataType; + } + + public FluentQueryTest.DeclaringTable getDeclaringTable(boolean nullHandlingEnabled) { + return givenSingleNullableFieldTable(_dataType, nullHandlingEnabled); + } + + @Override + public String toString() { + return "Scenario{" + "dt=" + _dataType + '}'; + } + } + + FluentQueryTest.TableWithSegments withDefaultData(Scenario scenario, boolean nullHandlingEnabled) { + return scenario.getDeclaringTable(nullHandlingEnabled) + .onFirstInstance("myField", + "null", + "0", + "null", + "1", + "null", + "2", + "null", + "3", + "null", + "4", + "null" + ).andSegment("myField", + "null", + "5", + "null", + "6", + "null", + "7", + "null", + "8", + "null", + "9", + "null" + ); + } + + String minValue(FieldSpec.DataType dataType) { + switch (dataType) { + case INT: return "-2.147483648E9"; + case LONG: return "-9.223372036854776E18"; + case FLOAT: return "-Infinity"; + case DOUBLE: return "-Infinity"; + default: + throw new IllegalArgumentException("Unexpected type " + dataType); + } + } + + String expectedAggrWithoutNull10(Scenario scenario) { + return minValue(scenario._dataType); + } + + String expectedAggrWithoutNull15(Scenario scenario) { + return minValue(scenario._dataType); + } + + String expectedAggrWithoutNull30(Scenario scenario) { + return minValue(scenario._dataType); + } + + String expectedAggrWithoutNull35(Scenario scenario) { + return minValue(scenario._dataType); + } + + String expectedAggrWithoutNull50(Scenario scenario) { + return minValue(scenario._dataType); + } + + String expectedAggrWithoutNull55(Scenario scenario) { + return "0"; + } + + String expectedAggrWithoutNull70(Scenario scenario) { + return "3"; + } + + String expectedAggrWithoutNull75(Scenario scenario) { + return "4"; + } + + String expectedAggrWithoutNull90(Scenario scenario) { + return "7"; + } + + String expectedAggrWithoutNull100(Scenario scenario) { + return "9"; + } + + @Test(dataProvider = "scenarios") + void aggrWithoutNull(Scenario scenario) { + + FluentQueryTest.TableWithSegments instance = withDefaultData(scenario, false); + + instance + .whenQuery("select " + callStr("myField", 10) + " from testTable") + .thenResultIs(getFinalResultColumnType(), expectedAggrWithoutNull10(scenario)); + + instance + .whenQuery("select " + callStr("myField", 15) + " from testTable") + .thenResultIs(getFinalResultColumnType(), expectedAggrWithoutNull15(scenario)); + + instance + .whenQuery("select " + callStr("myField", 30) + " from testTable") + .thenResultIs(getFinalResultColumnType(), expectedAggrWithoutNull30(scenario)); + instance + .whenQuery("select " + callStr("myField", 35) + " from testTable") + .thenResultIs(getFinalResultColumnType(), expectedAggrWithoutNull35(scenario)); + + instance + .whenQuery("select " + callStr("myField", 50) + " from testTable") + .thenResultIs(getFinalResultColumnType(), expectedAggrWithoutNull50(scenario)); + instance + .whenQuery("select " + callStr("myField", 55) + " from testTable") + .thenResultIs(getFinalResultColumnType(), expectedAggrWithoutNull55(scenario)); + + instance + .whenQuery("select " + callStr("myField", 70) + " from testTable") + .thenResultIs(getFinalResultColumnType(), expectedAggrWithoutNull70(scenario)); + + instance + .whenQuery("select " + callStr("myField", 75) + " from testTable") + .thenResultIs(getFinalResultColumnType(), expectedAggrWithoutNull75(scenario)); + + instance + .whenQuery("select " + callStr("myField", 90) + " from testTable") + .thenResultIs(getFinalResultColumnType(), expectedAggrWithoutNull90(scenario)); + + instance + .whenQuery("select " + callStr("myField", 100) + " from testTable") + .thenResultIs(getFinalResultColumnType(), expectedAggrWithoutNull100(scenario)); + } + + String expectedAggrWithNull10(Scenario scenario) { + return "1"; + } + + @Test(dataProvider = "scenarios") + void aggrWithNull10(Scenario scenario) { + withDefaultData(scenario, true) + .whenQuery("select " + callStr("myField", 10) + " from testTable") + .thenResultIs(getFinalResultColumnType(), expectedAggrWithNull10(scenario)); + } + + String expectedAggrWithNull15(Scenario scenario) { + return "1"; + } + + @Test(dataProvider = "scenarios") + void aggrWithNull15(Scenario scenario) { + withDefaultData(scenario, true) + .whenQuery("select " + callStr("myField", 15) + " from testTable") + .thenResultIs(getFinalResultColumnType(), expectedAggrWithNull15(scenario)); + } + + String expectedAggrWithNull30(Scenario scenario) { + return "3"; + } + + @Test(dataProvider = "scenarios") + void aggrWithNull30(Scenario scenario) { + withDefaultData(scenario, true) + .whenQuery("select " + callStr("myField", 30) + " from testTable") + .thenResultIs(getFinalResultColumnType(), expectedAggrWithNull30(scenario)); + } + + String expectedAggrWithNull35(Scenario scenario) { + return "3"; + } + + @Test(dataProvider = "scenarios") + void aggrWithNull35(Scenario scenario) { + withDefaultData(scenario, true) + .whenQuery("select " + callStr("myField", 35) + " from testTable") + .thenResultIs(getFinalResultColumnType(), expectedAggrWithNull35(scenario)); + } + + String expectedAggrWithNull50(Scenario scenario) { + return "5"; + } + + @Test(dataProvider = "scenarios") + void aggrWithNull50(Scenario scenario) { + withDefaultData(scenario, true) + .whenQuery("select " + callStr("myField", 50) + " from testTable") + .thenResultIs(getFinalResultColumnType(), expectedAggrWithNull50(scenario)); + } + + String expectedAggrWithNull55(Scenario scenario) { + return "5"; + } + + @Test(dataProvider = "scenarios") + void aggrWithNull55(Scenario scenario) { + withDefaultData(scenario, true) + .whenQuery("select " + callStr("myField", 55) + " from testTable") + .thenResultIs(getFinalResultColumnType(), expectedAggrWithNull55(scenario)); + } + + String expectedAggrWithNull70(Scenario scenario) { + return "7"; + } + + @Test(dataProvider = "scenarios") + void aggrWithNull70(Scenario scenario) { + withDefaultData(scenario, true) + .whenQuery("select " + callStr("myField", 70) + " from testTable") + .thenResultIs(getFinalResultColumnType(), expectedAggrWithNull70(scenario)); + } + + String expectedAggrWithNull75(Scenario scenario) { + return "7"; + } + + @Test(dataProvider = "scenarios") + void aggrWithNull75(Scenario scenario) { + withDefaultData(scenario, true) + .whenQuery("select " + callStr("myField", 75) + " from testTable") + .thenResultIs(getFinalResultColumnType(), expectedAggrWithNull75(scenario)); + } + + String expectedAggrWithNull100(Scenario scenario) { + return "9"; + } + + @Test(dataProvider = "scenarios") + void aggrWithNull100(Scenario scenario) { + withDefaultData(scenario, true) + .whenQuery("select " + callStr("myField", 100) + " from testTable") + .thenResultIs(getFinalResultColumnType(), expectedAggrWithNull100(scenario)); + } + + @Test(dataProvider = "scenarios") + void aggrSvWithoutNull(Scenario scenario) { + scenario.getDeclaringTable(false) + .onFirstInstance("myField", + "null", + "1", + "null" + ).andSegment("myField", + "9" + ).andSegment("myField", + "null", + "null", + "null" + ).whenQuery("select $segmentName, " + callStr("myField", 50) + " from testTable " + + "group by $segmentName order by $segmentName") + .thenResultIs("STRING | " + getFinalResultColumnType(), + "testTable_0 | " + minValue(scenario._dataType), + "testTable_1 | 9", + "testTable_2 | " + minValue(scenario._dataType) + ); + } + + @Test(dataProvider = "scenarios") + void aggrSvWithNull(Scenario scenario) { + scenario.getDeclaringTable(true) + .onFirstInstance("myField", + "null", + "1", + "null" + ).andSegment("myField", + "9" + ).andSegment("myField", + "null", + "null", + "null" + ).whenQuery("select $segmentName, " + callStr("myField", 50) + " from testTable " + + "group by $segmentName order by $segmentName") + .thenResultIs("STRING | " + getFinalResultColumnType(), + "testTable_0 | 1", + "testTable_1 | 9", + "testTable_2 | null" + ); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionFactoryTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionFactoryTest.java index a29694ef79e8..0caf40536bcf 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionFactoryTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/AggregationFunctionFactoryTest.java @@ -20,8 +20,6 @@ import org.apache.pinot.common.request.context.FunctionContext; import org.apache.pinot.common.request.context.RequestContextUtils; -import org.apache.pinot.core.query.request.context.QueryContext; -import org.apache.pinot.core.query.request.context.utils.QueryContextConverterUtils; import org.apache.pinot.segment.spi.AggregationFunctionType; import org.testng.annotations.Test; @@ -33,445 +31,440 @@ public class AggregationFunctionFactoryTest { private static final String ARGUMENT_COLUMN = "(column)"; private static final String ARGUMENT_STAR = "(*)"; - private static final QueryContext DUMMY_QUERY_CONTEXT = - QueryContextConverterUtils.getQueryContext("SELECT * FROM testTable"); @Test public void testGetAggregationFunction() { FunctionContext function = getFunction("CoUnT", ARGUMENT_STAR); - AggregationFunction aggregationFunction = - AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + AggregationFunction aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof CountAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.COUNT); - assertEquals(aggregationFunction.getColumnName(), "count_star"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("MiN"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof MinAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.MIN); - assertEquals(aggregationFunction.getColumnName(), "min_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("MaX"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof MaxAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.MAX); - assertEquals(aggregationFunction.getColumnName(), "max_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("SuM"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof SumAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.SUM); - assertEquals(aggregationFunction.getColumnName(), "sum_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("SuMPreCIsiON"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof SumPrecisionAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.SUMPRECISION); - assertEquals(aggregationFunction.getColumnName(), "sumPrecision_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("AvG"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof AvgAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.AVG); - assertEquals(aggregationFunction.getColumnName(), "avg_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("MoDe"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof ModeAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.MODE); - assertEquals(aggregationFunction.getColumnName(), "mode_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("FiRsTwItHtImE", "(column,timeColumn,'BOOLEAN')"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof FirstIntValueWithTimeAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.FIRSTWITHTIME); - assertEquals(aggregationFunction.getColumnName(), "firstWithTime_column_timeColumn_BOOLEAN"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("FiRsTwItHtImE", "(column,timeColumn,'INT')"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof FirstIntValueWithTimeAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.FIRSTWITHTIME); - assertEquals(aggregationFunction.getColumnName(), "firstWithTime_column_timeColumn_INT"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("FiRsTwItHtImE", "(column,timeColumn,'LONG')"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof FirstLongValueWithTimeAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.FIRSTWITHTIME); - assertEquals(aggregationFunction.getColumnName(), "firstWithTime_column_timeColumn_LONG"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("FiRsTwItHtImE", "(column,timeColumn,'FLOAT')"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof FirstFloatValueWithTimeAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.FIRSTWITHTIME); - assertEquals(aggregationFunction.getColumnName(), "firstWithTime_column_timeColumn_FLOAT"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("FiRsTwItHtImE", "(column,timeColumn,'DOUBLE')"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof FirstDoubleValueWithTimeAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.FIRSTWITHTIME); - assertEquals(aggregationFunction.getColumnName(), "firstWithTime_column_timeColumn_DOUBLE"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("FiRsTwItHtImE", "(column,timeColumn,'STRING')"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof FirstStringValueWithTimeAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.FIRSTWITHTIME); - assertEquals(aggregationFunction.getColumnName(), "firstWithTime_column_timeColumn_STRING"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("LaStWiThTiMe", "(column,timeColumn,'BOOLEAN')"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof LastIntValueWithTimeAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.LASTWITHTIME); - assertEquals(aggregationFunction.getColumnName(), "lastWithTime_column_timeColumn_BOOLEAN"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("LaStWiThTiMe", "(column,timeColumn,'INT')"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof LastIntValueWithTimeAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.LASTWITHTIME); - assertEquals(aggregationFunction.getColumnName(), "lastWithTime_column_timeColumn_INT"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("LaStWiThTiMe", "(column,timeColumn,'LONG')"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof LastLongValueWithTimeAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.LASTWITHTIME); - assertEquals(aggregationFunction.getColumnName(), "lastWithTime_column_timeColumn_LONG"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("LaStWiThTiMe", "(column,timeColumn,'FLOAT')"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof LastFloatValueWithTimeAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.LASTWITHTIME); - assertEquals(aggregationFunction.getColumnName(), "lastWithTime_column_timeColumn_FLOAT"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("LaStWiThTiMe", "(column,timeColumn,'DOUBLE')"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof LastDoubleValueWithTimeAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.LASTWITHTIME); - assertEquals(aggregationFunction.getColumnName(), "lastWithTime_column_timeColumn_DOUBLE"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("LaStWiThTiMe", "(column,timeColumn,'STRING')"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof LastStringValueWithTimeAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.LASTWITHTIME); - assertEquals(aggregationFunction.getColumnName(), "lastWithTime_column_timeColumn_STRING"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("MiNmAxRaNgE"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof MinMaxRangeAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.MINMAXRANGE); - assertEquals(aggregationFunction.getColumnName(), "minMaxRange_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("DiStInCtCoUnT"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof DistinctCountAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.DISTINCTCOUNT); - assertEquals(aggregationFunction.getColumnName(), "distinctCount_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("DiStInCtCoUnThLl"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof DistinctCountHLLAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.DISTINCTCOUNTHLL); - assertEquals(aggregationFunction.getColumnName(), "distinctCountHLL_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("DiStInCtCoUnTrAwHlL"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof DistinctCountRawHLLAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.DISTINCTCOUNTRAWHLL); - assertEquals(aggregationFunction.getColumnName(), "distinctCountRawHLL_column"); + assertEquals(aggregationFunction.getResultColumnName(), function.toString()); + + function = getFunction("DiStInCtCoUnThLlPlUs"); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); + assertTrue(aggregationFunction instanceof DistinctCountHLLPlusAggregationFunction); + assertEquals(aggregationFunction.getType(), AggregationFunctionType.DISTINCTCOUNTHLLPLUS); + assertEquals(aggregationFunction.getResultColumnName(), function.toString()); + + function = getFunction("DiStInCtCoUnTrAwHlLpLuS"); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); + assertTrue(aggregationFunction instanceof DistinctCountRawHLLPlusAggregationFunction); + assertEquals(aggregationFunction.getType(), AggregationFunctionType.DISTINCTCOUNTRAWHLLPLUS); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("FaStHlL"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof FastHLLAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.FASTHLL); - assertEquals(aggregationFunction.getColumnName(), "fastHLL_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("PeRcEnTiLe5"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof PercentileAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILE); - assertEquals(aggregationFunction.getColumnName(), "percentile5_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("PeRcEnTiLeEsT50"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof PercentileEstAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILEEST); - assertEquals(aggregationFunction.getColumnName(), "percentileEst50_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("PeRcEnTiLeRaWEsT50"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof PercentileRawEstAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILERAWEST); - assertEquals(aggregationFunction.getColumnName(), "percentileRawEst50_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("PeRcEnTiLeTdIgEsT99"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof PercentileTDigestAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILETDIGEST); - assertEquals(aggregationFunction.getColumnName(), "percentileTDigest99_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("PeRcEnTiLeRaWTdIgEsT99"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof PercentileRawTDigestAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILERAWTDIGEST); - assertEquals(aggregationFunction.getColumnName(), "percentileRawTDigest99_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("PeRcEnTiLe", "(column, 5)"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof PercentileAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILE); - assertEquals(aggregationFunction.getColumnName(), "percentile5.0_column"); assertEquals(aggregationFunction.getResultColumnName(), "percentile(column, 5.0)"); function = getFunction("PeRcEnTiLe", "(column, 5.5)"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof PercentileAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILE); - assertEquals(aggregationFunction.getColumnName(), "percentile5.5_column"); assertEquals(aggregationFunction.getResultColumnName(), "percentile(column, 5.5)"); function = getFunction("PeRcEnTiLeEsT", "(column, 50)"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof PercentileEstAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILEEST); - assertEquals(aggregationFunction.getColumnName(), "percentileEst50.0_column"); assertEquals(aggregationFunction.getResultColumnName(), "percentileest(column, 50.0)"); function = getFunction("PeRcEnTiLeRaWeSt", "(column, 50)"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof PercentileRawEstAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILERAWEST); - assertEquals(aggregationFunction.getColumnName(), "percentileRawEst50.0_column"); assertEquals(aggregationFunction.getResultColumnName(), "percentilerawest(column, 50.0)"); function = getFunction("PeRcEnTiLeEsT", "(column, 55.555)"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof PercentileEstAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILEEST); - assertEquals(aggregationFunction.getColumnName(), "percentileEst55.555_column"); assertEquals(aggregationFunction.getResultColumnName(), "percentileest(column, 55.555)"); function = getFunction("PeRcEnTiLeRaWeSt", "(column, 55.555)"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof PercentileRawEstAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILERAWEST); - assertEquals(aggregationFunction.getColumnName(), "percentileRawEst55.555_column"); assertEquals(aggregationFunction.getResultColumnName(), "percentilerawest(column, 55.555)"); function = getFunction("PeRcEnTiLeTdIgEsT", "(column, 99)"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof PercentileTDigestAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILETDIGEST); - assertEquals(aggregationFunction.getColumnName(), "percentileTDigest99.0_column"); assertEquals(aggregationFunction.getResultColumnName(), "percentiletdigest(column, 99.0)"); function = getFunction("PeRcEnTiLeTdIgEsT", "(column, 99.9999)"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof PercentileTDigestAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILETDIGEST); - assertEquals(aggregationFunction.getColumnName(), "percentileTDigest99.9999_column"); assertEquals(aggregationFunction.getResultColumnName(), "percentiletdigest(column, 99.9999)"); + function = getFunction("PeRcEnTiLeTdIgEsT", "(column, 99.9999, 1000)"); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); + assertTrue(aggregationFunction instanceof PercentileTDigestAggregationFunction); + assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILETDIGEST); + assertEquals(aggregationFunction.getResultColumnName(), "percentiletdigest(column, 99.9999, 1000)"); + function = getFunction("PeRcEnTiLeRaWtDiGeSt", "(column, 99)"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof PercentileRawTDigestAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILERAWTDIGEST); - assertEquals(aggregationFunction.getColumnName(), "percentileRawTDigest99.0_column"); assertEquals(aggregationFunction.getResultColumnName(), "percentilerawtdigest(column, 99.0)"); function = getFunction("PeRcEnTiLeRaWtDiGeSt", "(column, 99.9999)"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); + assertTrue(aggregationFunction instanceof PercentileRawTDigestAggregationFunction); + assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILERAWTDIGEST); + assertEquals(aggregationFunction.getResultColumnName(), "percentilerawtdigest(column, 99.9999)"); + + function = getFunction("PeRcEntiLEkll", "(column, 99.9999)"); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); + assertTrue(aggregationFunction instanceof PercentileKLLAggregationFunction); + assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILEKLL); + assertEquals(aggregationFunction.getResultColumnName(), "percentilekll(column, 99.9999)"); + + function = getFunction("PeRcEnTiLeRaWtDiGeSt", "(column, 99.9999, 500)"); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); + assertTrue(aggregationFunction instanceof PercentileRawTDigestAggregationFunction); + assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILERAWTDIGEST); + assertEquals(aggregationFunction.getResultColumnName(), "percentilerawtdigest(column, 99.9999, 500)"); + + function = getFunction("PeRcEnTiLeRaWtDiGeSt", "(column, 99.9999, 100)"); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof PercentileRawTDigestAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILERAWTDIGEST); - assertEquals(aggregationFunction.getColumnName(), "percentileRawTDigest99.9999_column"); assertEquals(aggregationFunction.getResultColumnName(), "percentilerawtdigest(column, 99.9999)"); function = getFunction("CoUnTmV"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof CountMVAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.COUNTMV); - assertEquals(aggregationFunction.getColumnName(), "countMV_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("MiNmV"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof MinMVAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.MINMV); - assertEquals(aggregationFunction.getColumnName(), "minMV_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("MaXmV"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof MaxMVAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.MAXMV); - assertEquals(aggregationFunction.getColumnName(), "maxMV_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("SuMmV"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof SumMVAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.SUMMV); - assertEquals(aggregationFunction.getColumnName(), "sumMV_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("AvGmV"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof AvgMVAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.AVGMV); - assertEquals(aggregationFunction.getColumnName(), "avgMV_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("AvG_mV"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof AvgMVAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.AVGMV); - assertEquals(aggregationFunction.getColumnName(), "avgMV_column"); - assertEquals(aggregationFunction.getResultColumnName(), "avgmv(column)"); + assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("MiNmAxRaNgEmV"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof MinMaxRangeMVAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.MINMAXRANGEMV); - assertEquals(aggregationFunction.getColumnName(), "minMaxRangeMV_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("DiStInCtCoUnTmV"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof DistinctCountMVAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.DISTINCTCOUNTMV); - assertEquals(aggregationFunction.getColumnName(), "distinctCountMV_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("DiStInCtCoUnThLlMv"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof DistinctCountHLLMVAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.DISTINCTCOUNTHLLMV); - assertEquals(aggregationFunction.getColumnName(), "distinctCountHLLMV_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("DiStInCt_CoUnT_hLl_Mv"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof DistinctCountHLLMVAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.DISTINCTCOUNTHLLMV); - assertEquals(aggregationFunction.getColumnName(), "distinctCountHLLMV_column"); - assertEquals(aggregationFunction.getResultColumnName(), "distinctcounthllmv(column)"); + assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("DiStInCtCoUnTrAwHlLmV"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof DistinctCountRawHLLMVAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.DISTINCTCOUNTRAWHLLMV); - assertEquals(aggregationFunction.getColumnName(), "distinctCountRawHLLMV_column"); + assertEquals(aggregationFunction.getResultColumnName(), function.toString()); + + function = getFunction("DiStInCt_CoUnT_hLl_PlUs_Mv"); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); + assertTrue(aggregationFunction instanceof DistinctCountHLLPlusMVAggregationFunction); + assertEquals(aggregationFunction.getType(), AggregationFunctionType.DISTINCTCOUNTHLLPLUSMV); + assertEquals(aggregationFunction.getResultColumnName(), function.toString()); + + function = getFunction("DiStInCtCoUnTrAwHlLpLuS_mV"); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); + assertTrue(aggregationFunction instanceof DistinctCountRawHLLPlusMVAggregationFunction); + assertEquals(aggregationFunction.getType(), AggregationFunctionType.DISTINCTCOUNTRAWHLLPLUSMV); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("PeRcEnTiLe10Mv"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof PercentileMVAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILEMV); - assertEquals(aggregationFunction.getColumnName(), "percentile10MV_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("PeRcEnTiLeEsT90mV"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof PercentileEstMVAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILEESTMV); - assertEquals(aggregationFunction.getColumnName(), "percentileEst90MV_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("PeRcEnTiLeTdIgEsT95mV"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof PercentileTDigestMVAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILETDIGESTMV); - assertEquals(aggregationFunction.getColumnName(), "percentileTDigest95MV_column"); assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("PeRcEnTiLe_TdIgEsT_95_mV"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof PercentileTDigestMVAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILETDIGESTMV); - assertEquals(aggregationFunction.getColumnName(), "percentileTDigest95MV_column"); - assertEquals(aggregationFunction.getResultColumnName(), "percentiletdigest95mv(column)"); + assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("PeRcEnTiLeMv", "(column, 10)"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof PercentileMVAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILEMV); - assertEquals(aggregationFunction.getColumnName(), "percentile10.0MV_column"); assertEquals(aggregationFunction.getResultColumnName(), "percentilemv(column, 10.0)"); function = getFunction("PeRcEnTiLeEsTmV", "(column, 90)"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof PercentileEstMVAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILEESTMV); - assertEquals(aggregationFunction.getColumnName(), "percentileEst90.0MV_column"); assertEquals(aggregationFunction.getResultColumnName(), "percentileestmv(column, 90.0)"); function = getFunction("PeRcEnTiLeTdIgEsTmV", "(column, 95)"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof PercentileTDigestMVAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILETDIGESTMV); - assertEquals(aggregationFunction.getColumnName(), "percentileTDigest95.0MV_column"); assertEquals(aggregationFunction.getResultColumnName(), "percentiletdigestmv(column, 95.0)"); + function = getFunction("PeRcEnTiLeTdIgEsTmV", "(column, 95, 1000)"); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); + assertTrue(aggregationFunction instanceof PercentileTDigestMVAggregationFunction); + assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILETDIGESTMV); + assertEquals(aggregationFunction.getResultColumnName(), "percentiletdigestmv(column, 95.0, 1000)"); + function = getFunction("PeRcEnTiLe_TdIgEsT_mV", "(column, 95)"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof PercentileTDigestMVAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILETDIGESTMV); - assertEquals(aggregationFunction.getColumnName(), "percentileTDigest95.0MV_column"); assertEquals(aggregationFunction.getResultColumnName(), "percentiletdigestmv(column, 95.0)"); + function = getFunction("PeRcEnTiLe_TdIgEsT_mV", "(column, 95, 200)"); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); + assertTrue(aggregationFunction instanceof PercentileTDigestMVAggregationFunction); + assertEquals(aggregationFunction.getType(), AggregationFunctionType.PERCENTILETDIGESTMV); + assertEquals(aggregationFunction.getResultColumnName(), "percentiletdigestmv(column, 95.0, 200)"); + function = getFunction("bool_and"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof BooleanAndAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.BOOLAND); - assertEquals(aggregationFunction.getColumnName(), "boolAnd_column"); - assertEquals(aggregationFunction.getResultColumnName(), "booland(column)"); + assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("bool_or"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof BooleanOrAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.BOOLOR); - assertEquals(aggregationFunction.getColumnName(), "boolOr_column"); - assertEquals(aggregationFunction.getResultColumnName(), "boolor(column)"); + assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("skewness"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof FourthMomentAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.SKEWNESS); - assertEquals(aggregationFunction.getColumnName(), "skewness_column"); - assertEquals(aggregationFunction.getResultColumnName(), "skewness(column)"); + assertEquals(aggregationFunction.getResultColumnName(), function.toString()); function = getFunction("kurtosis"); - aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, DUMMY_QUERY_CONTEXT); + aggregationFunction = AggregationFunctionFactory.getAggregationFunction(function, false); assertTrue(aggregationFunction instanceof FourthMomentAggregationFunction); assertEquals(aggregationFunction.getType(), AggregationFunctionType.KURTOSIS); - assertEquals(aggregationFunction.getColumnName(), "kurtosis_column"); - assertEquals(aggregationFunction.getResultColumnName(), "kurtosis(column)"); + assertEquals(aggregationFunction.getResultColumnName(), function.toString()); } private FunctionContext getFunction(String functionName) { @@ -481,16 +474,4 @@ private FunctionContext getFunction(String functionName) { private FunctionContext getFunction(String functionName, String args) { return RequestContextUtils.getExpression(functionName + args).getFunction(); } - - @Test - public void testAggregationFunctionWithMultipleArgs() { - QueryContext queryContext = - QueryContextConverterUtils.getQueryContext("SELECT DISTINCT column1, column2, column3 FROM testTable"); - AggregationFunction aggregationFunction = AggregationFunctionFactory - .getAggregationFunction(queryContext.getSelectExpressions().get(0).getFunction(), queryContext); - assertTrue(aggregationFunction instanceof DistinctAggregationFunction); - assertEquals(aggregationFunction.getType(), AggregationFunctionType.DISTINCT); - assertEquals(aggregationFunction.getColumnName(), "distinct_column1:column2:column3"); - assertEquals(aggregationFunction.getResultColumnName(), "distinct(column1:column2:column3)"); - } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/CountAggregationFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/CountAggregationFunctionTest.java new file mode 100644 index 000000000000..5f60e756a1db --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/CountAggregationFunctionTest.java @@ -0,0 +1,158 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.query.aggregation.function; + +import org.apache.pinot.queries.FluentQueryTest; +import org.apache.pinot.spi.data.FieldSpec; +import org.testng.annotations.Test; + + +public class CountAggregationFunctionTest extends AbstractAggregationFunctionTest { + + @Test + public void list() { + FluentQueryTest.withBaseDir(_baseDir) + .withNullHandling(false) + .givenTable(SINGLE_FIELD_NULLABLE_SCHEMAS.get(FieldSpec.DataType.INT), SINGLE_FIELD_TABLE_CONFIG) + .onFirstInstance( + new Object[] {1} + ) + .andOnSecondInstance( + new Object[] {2}, + new Object[] {null} + ) + .whenQuery("select myField from testTable order by myField") + .thenResultIs("INTEGER", + "-2147483648", + "1", + "2" + ); + } + + @Test + public void listNullHandlingEnabled() { + FluentQueryTest.withBaseDir(_baseDir) + .withNullHandling(true) + .givenTable(SINGLE_FIELD_NULLABLE_SCHEMAS.get(FieldSpec.DataType.INT), SINGLE_FIELD_TABLE_CONFIG) + .onFirstInstance( + new Object[] {1} + ) + .andOnSecondInstance( + new Object[] {2}, + new Object[] {null} + ) + .whenQuery("select myField from testTable order by myField") + .thenResultIs("INTEGER", + "1", + "2", + "null" + ); + } + + @Test + public void countNullWhenHandlingDisabled() { + FluentQueryTest.withBaseDir(_baseDir) + .withNullHandling(false) + .givenTable(SINGLE_FIELD_NULLABLE_SCHEMAS.get(FieldSpec.DataType.INT), SINGLE_FIELD_TABLE_CONFIG) + .onFirstInstance( + "myField", + "1" + ) + .andOnSecondInstance( + "myField", + "2", + "null" + ) + .whenQuery("select myField, COUNT(myField) from testTable group by myField order by myField") + .thenResultIs("INTEGER | LONG", + "-2147483648 | 1", + "1 | 1", + "2 | 1" + ); + } + + + @Test + public void countNullWhenHandlingEnabled() { + FluentQueryTest.withBaseDir(_baseDir) + .withNullHandling(true) + .givenTable(SINGLE_FIELD_NULLABLE_SCHEMAS.get(FieldSpec.DataType.INT), SINGLE_FIELD_TABLE_CONFIG) + .onFirstInstance( + "myField", + "1" + ) + .andOnSecondInstance( + "myField", + "2", + "null" + ) + .whenQuery("select myField, COUNT(myField) from testTable group by myField order by myField") + .thenResultIs( + "INTEGER | LONG", + "1 | 1", + "2 | 1", + "null | 0" + ); + } + + @Test + public void countStarNullWhenHandlingDisabled() { + FluentQueryTest.withBaseDir(_baseDir) + .withNullHandling(false) + .givenTable(SINGLE_FIELD_NULLABLE_SCHEMAS.get(FieldSpec.DataType.INT), SINGLE_FIELD_TABLE_CONFIG) + .onFirstInstance( + "myField", + "1" + ) + .andOnSecondInstance( + "myField", + "2", + "null" + ) + .whenQuery("select myField, COUNT(*) from testTable group by myField order by myField") + .thenResultIs("INTEGER | LONG", + "-2147483648 | 1", + "1 | 1", + "2 | 1" + ); + } + + @Test + public void countStarNullWhenHandlingEnabled() { + FluentQueryTest.withBaseDir(_baseDir) + .withNullHandling(true) + .givenTable(SINGLE_FIELD_NULLABLE_SCHEMAS.get(FieldSpec.DataType.INT), SINGLE_FIELD_TABLE_CONFIG) + .onFirstInstance( + "myField", + "1" + ) + .andOnSecondInstance( + "myField", + "2", + "null" + ) + .whenQuery("select myField, COUNT(*) from testTable group by myField order by myField") + .thenResultIs("INTEGER | LONG", + "1 | 1", + "2 | 1", + "null | 1" + );; + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/FirstWithTimeAggregationFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/FirstWithTimeAggregationFunctionTest.java new file mode 100644 index 000000000000..541b007d4784 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/FirstWithTimeAggregationFunctionTest.java @@ -0,0 +1,186 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.query.aggregation.function; + +import org.apache.pinot.common.utils.PinotDataType; +import org.apache.pinot.queries.FluentQueryTest; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + + +public class FirstWithTimeAggregationFunctionTest extends AbstractAggregationFunctionTest { + + @DataProvider(name = "scenarios") + Object[] scenarios() { + return new Object[] { + new Scenario(FieldSpec.DataType.INT, "1", "2", "-2147483648"), + new Scenario(FieldSpec.DataType.LONG, "1", "2", "-9223372036854775808"), + new Scenario(FieldSpec.DataType.FLOAT, "1", "2", "-Infinity"), + new Scenario(FieldSpec.DataType.DOUBLE, "1", "2", "-Infinity"), + new Scenario(FieldSpec.DataType.STRING, "a", "b", "\"null\""), + }; + } + + public class Scenario { + private final PinotDataType _pinotDataType; + private final FieldSpec.DataType _dataType; + private final String _valAsStr1; + private final String _valAsStr2; + private final String _defaultNullValue; + + public Scenario(FieldSpec.DataType dataType, String valAsStr1, String valAsStr2, String defaultNullValue) { + _dataType = dataType; + _valAsStr1 = valAsStr1; + _valAsStr2 = valAsStr2; + _defaultNullValue = defaultNullValue; + _pinotDataType = + _dataType == FieldSpec.DataType.INT ? PinotDataType.INTEGER : PinotDataType.valueOf(_dataType.name()); + } + + public FluentQueryTest.DeclaringTable getDeclaringTable(boolean nullHandlingEnabled) { + Schema schema = new Schema.SchemaBuilder() + .setSchemaName("testTable") + .setEnableColumnBasedNullHandling(true) + .addDimensionField("myField", _dataType, f -> f.setNullable(true)) + .addDimensionField("timeField", FieldSpec.DataType.TIMESTAMP) + .build(); + TableConfigBuilder tableConfigBuilder = new TableConfigBuilder(TableType.OFFLINE) + .setTableName("testTable"); + + return FluentQueryTest.withBaseDir(_baseDir) + .withNullHandling(nullHandlingEnabled) + .givenTable(schema, tableConfigBuilder.build()); + } + + @Override + public String toString() { + return "Scenario{" + "dt=" + _dataType + ", val1='" + _valAsStr1 + '\'' + ", val2='" + + _valAsStr2 + '\'' + '}'; + } + } + + @Test(dataProvider = "scenarios") + void aggrWithoutNull(Scenario scenario) { + scenario.getDeclaringTable(false) + .onFirstInstance("myField | timeField", + "null | 1", + scenario._valAsStr1 + " | 2", + "null | 3" + ).andOnSecondInstance("myField | timeField", + "null | 4", + scenario._valAsStr2 + " | 5", + "null | 6" + ) + .whenQuery("select FIRST_WITH_TIME(myField, timeField, '" + scenario._dataType + "') from testTable") + .thenResultIs(scenario._pinotDataType.name(), scenario._defaultNullValue); + } + + @Test(dataProvider = "scenarios") + void aggrWithNull(Scenario scenario) { + scenario.getDeclaringTable(true) + .onFirstInstance("myField | timeField", + "null | 1", + scenario._valAsStr1 + " | 2", + "null | 3" + ).andOnSecondInstance("myField | timeField", + "null | 4", + scenario._valAsStr2 + " | 5", + "null | 6" + ) + .whenQuery("select FIRST_WITH_TIME(myField, timeField, '" + scenario._dataType + "') from testTable") + .thenResultIs(scenario._pinotDataType.name(), scenario._valAsStr1); + } + + @Test(dataProvider = "scenarios") + void aggrSvWithoutNull(Scenario scenario) { + scenario.getDeclaringTable(false) + .onFirstInstance("myField | timeField", + "null | 1", + scenario._valAsStr1 + " | 2", + "null | 3" + ).andOnSecondInstance("myField | timeField", + "null | 4", + scenario._valAsStr2 + " | 5", + "null | 6" + ).whenQuery("select 'cte', FIRST_WITH_TIME(myField, timeField, '" + scenario._dataType + "') as mode " + + "from testTable " + + "group by 'cte'") + .thenResultIs("STRING | " + scenario._pinotDataType.name(), "cte | " + scenario._defaultNullValue); + } + + @Test(dataProvider = "scenarios") + void aggrSvWithNull(Scenario scenario) { + scenario.getDeclaringTable(true) + .onFirstInstance("myField | timeField", + "null | 1", + scenario._valAsStr1 + " | 2", + "null | 3" + ).andOnSecondInstance("myField | timeField", + "null | 4", + scenario._valAsStr2 + " | 5", + "null | 6" + ).whenQuery("select 'cte', FIRST_WITH_TIME(myField, timeField, '" + scenario._dataType + "') as mode " + + "from testTable " + + "group by 'cte'") + .thenResultIs("STRING | " + scenario._pinotDataType.name(), "cte | " + scenario._valAsStr1); + } + + @Test(dataProvider = "scenarios") + void aggrMvWithoutNull(Scenario scenario) { + scenario.getDeclaringTable(false) + .onFirstInstance("myField | timeField", + "null | 1", + scenario._valAsStr1 + " | 2", + "null | 3" + ).andOnSecondInstance("myField | timeField", + "null | 4", + scenario._valAsStr2 + " | 5", + "null | 6" + ).whenQuery("select 'cte1' as cte1, 'cte2' as cte2, " + + "FIRST_WITH_TIME(myField, timeField, '" + scenario._dataType + "') as mode " + + "from testTable " + + "group by 'cte'") + .thenResultIs("STRING | STRING | " + scenario._pinotDataType.name(), + "cte1 | cte2 | " + scenario._defaultNullValue); + } + + @Test(dataProvider = "scenarios") + void aggrMvWithNull(Scenario scenario) { + scenario.getDeclaringTable(true) + .onFirstInstance("myField | timeField", + "null | 1", + scenario._valAsStr1 + " | 2", + "null | 3" + ).andOnSecondInstance("myField | timeField", + "null | 4", + scenario._valAsStr2 + " | 5", + "null | 6" + ).whenQuery("select 'cte1' as cte1, 'cte2' as cte2, " + + "FIRST_WITH_TIME(myField, timeField, '" + scenario._dataType + "') as mode " + + "from testTable " + + "group by 'cte'") + .thenResultIs("STRING | STRING | " + scenario._pinotDataType.name(), + "cte1 | cte2 | " + scenario._valAsStr1); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/LastWithTimeAggregationFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/LastWithTimeAggregationFunctionTest.java new file mode 100644 index 000000000000..9c612ae53829 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/LastWithTimeAggregationFunctionTest.java @@ -0,0 +1,174 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.query.aggregation.function; + +import org.apache.pinot.common.utils.PinotDataType; +import org.apache.pinot.queries.FluentQueryTest; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + + +public class LastWithTimeAggregationFunctionTest extends AbstractAggregationFunctionTest { + + @DataProvider(name = "scenarios") + Object[] scenarios() { + return new Object[] { + new Scenario(FieldSpec.DataType.INT, "1", "2", "-2147483648"), + new Scenario(FieldSpec.DataType.LONG, "1", "2", "-9223372036854775808"), + new Scenario(FieldSpec.DataType.FLOAT, "1", "2", "-Infinity"), + new Scenario(FieldSpec.DataType.DOUBLE, "1", "2", "-Infinity"), + new Scenario(FieldSpec.DataType.STRING, "a", "b", "\"null\""), + }; + } + + public class Scenario { + private final PinotDataType _pinotDataType; + private final FieldSpec.DataType _dataType; + private final String _valAsStr1; + private final String _valAsStr2; + private final String _defaultNullValue; + + public Scenario(FieldSpec.DataType dataType, String valAsStr1, String valAsStr2, String defaultNullValue) { + _dataType = dataType; + _valAsStr1 = valAsStr1; + _valAsStr2 = valAsStr2; + _defaultNullValue = defaultNullValue; + _pinotDataType = + _dataType == FieldSpec.DataType.INT ? PinotDataType.INTEGER : PinotDataType.valueOf(_dataType.name()); + } + + public FluentQueryTest.DeclaringTable getDeclaringTable(boolean nullHandlingEnabled) { + Schema schema = new Schema.SchemaBuilder() + .setSchemaName("testTable") + .setEnableColumnBasedNullHandling(true) + .addDimensionField("myField", _dataType, f -> f.setNullable(true)) + .addDimensionField("timeField", FieldSpec.DataType.TIMESTAMP) + .build(); + TableConfigBuilder tableConfigBuilder = new TableConfigBuilder(TableType.OFFLINE) + .setTableName("testTable"); + + return FluentQueryTest.withBaseDir(_baseDir) + .withNullHandling(nullHandlingEnabled) + .givenTable(schema, tableConfigBuilder.build()); + } + + @Override + public String toString() { + return "Scenario{" + "dt=" + _dataType + ", val1='" + _valAsStr1 + '\'' + ", val2='" + + _valAsStr2 + '\'' + '}'; + } + } + + @Test(dataProvider = "scenarios") + void aggrWithoutNull(Scenario scenario) { + scenario.getDeclaringTable(false) + .onFirstInstance("myField | timeField", + scenario._valAsStr1 + " | 2", + "null | 3" + ).andOnSecondInstance("myField | timeField", + scenario._valAsStr2 + " | 5", + "null | 6" + ) + .whenQuery("select LAST_WITH_TIME(myField, timeField, '" + scenario._dataType + "') from testTable") + .thenResultIs(scenario._pinotDataType.name(), scenario._defaultNullValue); + } + + @Test(dataProvider = "scenarios") + void aggrWithNull(Scenario scenario) { + scenario.getDeclaringTable(true) + .onFirstInstance("myField | timeField", + scenario._valAsStr1 + " | 2", + "null | 3" + ).andOnSecondInstance("myField | timeField", + scenario._valAsStr2 + " | 5", + "null | 6" + ) + .whenQuery("select LAST_WITH_TIME(myField, timeField, '" + scenario._dataType + "') from testTable") + .thenResultIs(scenario._pinotDataType.name(), scenario._valAsStr2); + } + + @Test(dataProvider = "scenarios") + void aggrSvWithoutNull(Scenario scenario) { + scenario.getDeclaringTable(false) + .onFirstInstance("myField | timeField", + scenario._valAsStr1 + " | 2", + "null | 3" + ).andOnSecondInstance("myField | timeField", + scenario._valAsStr2 + " | 5", + "null | 6" + ).whenQuery("select 'cte', LAST_WITH_TIME(myField, timeField, '" + scenario._dataType + "') as mode " + + "from testTable " + + "group by 'cte'") + .thenResultIs("STRING | " + scenario._pinotDataType.name(), "cte | " + scenario._defaultNullValue); + } + + @Test(dataProvider = "scenarios") + void aggrSvWithNull(Scenario scenario) { + scenario.getDeclaringTable(true) + .onFirstInstance("myField | timeField", + scenario._valAsStr1 + " | 2", + "null | 3" + ).andOnSecondInstance("myField | timeField", + scenario._valAsStr2 + " | 5", + "null | 6" + ).whenQuery("select 'cte', LAST_WITH_TIME(myField, timeField, '" + scenario._dataType + "') as mode " + + "from testTable " + + "group by 'cte'") + .thenResultIs("STRING | " + scenario._pinotDataType.name(), "cte | " + scenario._valAsStr2); + } + + @Test(dataProvider = "scenarios") + void aggrMvWithoutNull(Scenario scenario) { + scenario.getDeclaringTable(false) + .onFirstInstance("myField | timeField", + scenario._valAsStr1 + " | 2", + "null | 3" + ).andOnSecondInstance("myField | timeField", + scenario._valAsStr2 + " | 5", + "null | 6" + ).whenQuery("select 'cte1' as cte1, 'cte2' as cte2, " + + "LAST_WITH_TIME(myField, timeField, '" + scenario._dataType + "') as mode " + + "from testTable " + + "group by 'cte'") + .thenResultIs("STRING | STRING | " + scenario._pinotDataType.name(), + "cte1 | cte2 | " + scenario._defaultNullValue); + } + + @Test(dataProvider = "scenarios") + void aggrMvWithNull(Scenario scenario) { + scenario.getDeclaringTable(true) + .onFirstInstance("myField | timeField", + scenario._valAsStr1 + " | 2", + "null | 3" + ).andOnSecondInstance("myField | timeField", + scenario._valAsStr2 + " | 5", + "null | 6" + ).whenQuery("select 'cte1' as cte1, 'cte2' as cte2, " + + "LAST_WITH_TIME(myField, timeField, '" + scenario._dataType + "') as mode " + + "from testTable " + + "group by 'cte'") + .thenResultIs("STRING | STRING | " + scenario._pinotDataType.name(), + "cte1 | cte2 | " + scenario._valAsStr2); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/MinMaxRangeAggregationFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/MinMaxRangeAggregationFunctionTest.java new file mode 100644 index 000000000000..822399d66fbf --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/MinMaxRangeAggregationFunctionTest.java @@ -0,0 +1,195 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.query.aggregation.function; + +import org.apache.pinot.common.utils.PinotDataType; +import org.apache.pinot.queries.FluentQueryTest; +import org.apache.pinot.spi.data.FieldSpec; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + + +public class MinMaxRangeAggregationFunctionTest extends AbstractAggregationFunctionTest { + + @DataProvider(name = "scenarios") + Object[] scenarios() { + return new Object[] { + new Scenario(FieldSpec.DataType.INT), + new Scenario(FieldSpec.DataType.LONG), + new Scenario(FieldSpec.DataType.FLOAT), + new Scenario(FieldSpec.DataType.DOUBLE), + }; + } + + public class Scenario { + private final FieldSpec.DataType _dataType; + + public Scenario(FieldSpec.DataType dataType) { + _dataType = dataType; + } + + public FluentQueryTest.DeclaringTable getDeclaringTable(boolean nullHandlingEnabled) { + return givenSingleNullableFieldTable(_dataType, nullHandlingEnabled); + } + + @Override + public String toString() { + return "Scenario{" + "dt=" + _dataType + '}'; + } + } + + String diffBetweenMinAnd9(FieldSpec.DataType dt) { + switch (dt) { + case INT: return "2.147483657E9"; + case LONG: return "9.223372036854776E18"; + case FLOAT: return "Infinity"; + case DOUBLE: return "Infinity"; + default: throw new IllegalArgumentException(dt.toString()); + } + } + + @Test(dataProvider = "scenarios") + void aggrWithoutNull(Scenario scenario) { + scenario.getDeclaringTable(false) + .onFirstInstance("myField", + "null", + "1", + "null" + ).andOnSecondInstance("myField", + "null", + "9", + "null" + ) + .whenQuery("select minmaxrange(myField) from testTable") + .thenResultIs("DOUBLE", diffBetweenMinAnd9(scenario._dataType)); + } + + @Test(dataProvider = "scenarios") + void aggrWithNull(Scenario scenario) { + scenario.getDeclaringTable(true) + .onFirstInstance("myField", + "null", + "1", + "null" + ).andOnSecondInstance("myField", + "null", + "9", + "null" + ).whenQuery("select minmaxrange(myField) from testTable") + .thenResultIs("DOUBLE", "8"); + } + + @Test(dataProvider = "scenarios") + void aggrSvWithoutNull(Scenario scenario) { + scenario.getDeclaringTable(false) + .onFirstInstance("myField", + "null", + "1", + "null" + ).andOnSecondInstance("myField", + "null", + "9", + "null" + ).whenQuery("select 'cte', minmaxrange(myField) from testTable group by 'cte'") + .thenResultIs("STRING | DOUBLE", "cte | " + diffBetweenMinAnd9(scenario._dataType)); + } + + @Test(dataProvider = "scenarios") + void aggrSvWithNull(Scenario scenario) { + scenario.getDeclaringTable(true) + .onFirstInstance("myField", + "null", + "1", + "null" + ).andOnSecondInstance("myField", + "null", + "9", + "null" + ).whenQuery("select 'cte', minmaxrange(myField) from testTable group by 'cte'") + .thenResultIs("STRING | DOUBLE", "cte | 8"); + } + + String aggrSvSelfWithoutNullResult(FieldSpec.DataType dt) { + switch (dt) { + case INT: return "0"; + case LONG: return "0"; + case FLOAT: return "NaN"; + case DOUBLE: return "NaN"; + default: throw new IllegalArgumentException(dt.toString()); + } + } + + @Test(dataProvider = "scenarios") + void aggrSvSelfWithoutNull(Scenario scenario) { + PinotDataType pinotDataType = scenario._dataType == FieldSpec.DataType.INT + ? PinotDataType.INTEGER : PinotDataType.valueOf(scenario._dataType.name()); + + Object defaultNullValue; + switch (scenario._dataType) { + case INT: + defaultNullValue = Integer.MIN_VALUE; + break; + case LONG: + defaultNullValue = Long.MIN_VALUE; + break; + case FLOAT: + defaultNullValue = Float.NEGATIVE_INFINITY; + break; + case DOUBLE: + defaultNullValue = Double.NEGATIVE_INFINITY; + break; + default: + throw new IllegalArgumentException("Unexpected scenario data type " + scenario._dataType); + } + + scenario.getDeclaringTable(false) + .onFirstInstance("myField", + "null", + "1", + "2" + ).andOnSecondInstance("myField", + "null", + "1", + "2" + ).whenQuery("select myField, minmaxrange(myField) from testTable group by myField order by myField") + .thenResultIs(pinotDataType + " | DOUBLE", + defaultNullValue + " | " + aggrSvSelfWithoutNullResult(scenario._dataType), + "1 | 0", + "2 | 0"); + } + + @Test(dataProvider = "scenarios") + void aggrSvSelfWithNull(Scenario scenario) { + PinotDataType pinotDataType = scenario._dataType == FieldSpec.DataType.INT + ? PinotDataType.INTEGER : PinotDataType.valueOf(scenario._dataType.name()); + + scenario.getDeclaringTable(true) + .onFirstInstance("myField", + "null", + "1", + "2" + ).andOnSecondInstance("myField", + "null", + "1", + "2" + ).whenQuery("select myField, minmaxrange(myField) from testTable group by myField order by myField") + .thenResultIs(pinotDataType + " | DOUBLE", "1 | 0", "2 | 0", "null | null"); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/ModeAggregationFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/ModeAggregationFunctionTest.java new file mode 100644 index 000000000000..fb637bedc1b2 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/ModeAggregationFunctionTest.java @@ -0,0 +1,273 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.query.aggregation.function; + +import org.apache.pinot.common.utils.PinotDataType; +import org.apache.pinot.queries.FluentQueryTest; +import org.apache.pinot.spi.config.table.FieldConfig; +import org.apache.pinot.spi.data.FieldSpec; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + + +public class ModeAggregationFunctionTest extends AbstractAggregationFunctionTest { + + @DataProvider(name = "scenarios") + Object[] scenarios() { + return new Object[] { + new Scenario(FieldSpec.DataType.INT, true), + + new Scenario(FieldSpec.DataType.INT, false), + new Scenario(FieldSpec.DataType.LONG, false), + new Scenario(FieldSpec.DataType.FLOAT, false), + new Scenario(FieldSpec.DataType.DOUBLE, false), + }; + } + + public class Scenario { + private final FieldSpec.DataType _dataType; + private final boolean _dictionary; + + public Scenario(FieldSpec.DataType dataType, boolean dictionary) { + _dataType = dataType; + _dictionary = dictionary; + } + + public FluentQueryTest.DeclaringTable getDeclaringTable(boolean nullHandlingEnabled) { + FieldConfig.EncodingType encodingType = + _dictionary ? FieldConfig.EncodingType.DICTIONARY : FieldConfig.EncodingType.RAW; + return givenSingleNullableFieldTable(_dataType, nullHandlingEnabled, builder -> { + builder.withEncodingType(encodingType); + builder.withCompressionCodec(FieldConfig.CompressionCodec.PASS_THROUGH); + }); + } + + @Override + public String toString() { + return "Scenario{" + "dt=" + _dataType + ", dict=" + _dictionary + '}'; + } + } + + @Test(dataProvider = "scenarios") + void aggrWithoutNullAndEmptySegments(Scenario scenario) { + scenario.getDeclaringTable(false) + .onFirstInstance("myField", + "null", + "null" + ).andOnSecondInstance("myField", + "null", + "null" + ).whenQuery("select mode(myField) as mode from testTable") + .thenResultIs("DOUBLE", aggrWithoutNullResult(scenario._dataType)); + } + + @Test(dataProvider = "scenarios") + void aggrWithNullAndEmptySegments(Scenario scenario) { + scenario.getDeclaringTable(true) + .onFirstInstance("myField", + "null", + "null" + ).andOnSecondInstance("myField", + "null", + "null" + ).whenQuery("select mode(myField) as mode from testTable") + .thenResultIs("DOUBLE", "null"); + } + + String aggrWithoutNullResult(FieldSpec.DataType dt) { + switch (dt) { + case INT: return "-2.147483648E9"; + case LONG: return "-9.223372036854776E18"; + case FLOAT: return "-Infinity"; + case DOUBLE: return "-Infinity"; + default: throw new IllegalArgumentException(dt.toString()); + } + } + + @Test(dataProvider = "scenarios") + void aggrWithoutNull(Scenario scenario) { + scenario.getDeclaringTable(false) + .onFirstInstance("myField", + "null", + "1", + "null" + ).andOnSecondInstance("myField", + "null", + "1", + "null" + ) + .whenQuery("select mode(myField) as mode from testTable") + .thenResultIs("DOUBLE", aggrWithoutNullResult(scenario._dataType)); + } + + @Test(dataProvider = "scenarios") + void aggrWithNull(Scenario scenario) { + scenario.getDeclaringTable(true) + .onFirstInstance("myField", + "null", + "1", + "null" + ).andOnSecondInstance("myField", + "null", + "1", + "null" + ).whenQuery("select mode(myField) as mode from testTable") + .thenResultIs("DOUBLE", "1"); + } + + String aggrSvWithoutNullResult(FieldSpec.DataType dt) { + switch (dt) { + case INT: return "-2.147483648E9"; + case LONG: return "-9.223372036854776E18"; + case FLOAT: return "-Infinity"; + case DOUBLE: return "-Infinity"; + default: throw new IllegalArgumentException(dt.toString()); + } + } + + @Test(dataProvider = "scenarios") + void aggrSvWithoutNull(Scenario scenario) { + scenario.getDeclaringTable(false) + .onFirstInstance("myField", + "null", + "1", + "null" + ).andOnSecondInstance("myField", + "null", + "1", + "null" + ).whenQuery("select 'cte', mode(myField) as mode from testTable group by 'cte'") + .thenResultIs("STRING | DOUBLE", "cte | " + aggrSvWithoutNullResult(scenario._dataType)); + } + + @Test(dataProvider = "scenarios") + void aggrSvWithNull(Scenario scenario) { + scenario.getDeclaringTable(true) + .onFirstInstance("myField", + "null", + "1", + "null" + ).andOnSecondInstance("myField", + "null", + "1", + "null" + ).whenQuery("select 'cte', mode(myField) as mode from testTable group by 'cte'") + .thenResultIs("STRING | DOUBLE", "cte | 1"); + } + + @Test(dataProvider = "scenarios") + void aggrSvSelfWithoutNull(Scenario scenario) { + PinotDataType pinotDataType = scenario._dataType == FieldSpec.DataType.INT + ? PinotDataType.INTEGER : PinotDataType.valueOf(scenario._dataType.name()); + + Object defaultNullValue; + switch (scenario._dataType) { + case INT: + defaultNullValue = Integer.MIN_VALUE; + break; + case LONG: + defaultNullValue = Long.MIN_VALUE; + break; + case FLOAT: + defaultNullValue = Float.NEGATIVE_INFINITY; + break; + case DOUBLE: + defaultNullValue = Double.NEGATIVE_INFINITY; + break; + default: + throw new IllegalArgumentException("Unexpected scenario data type " + scenario._dataType); + } + + scenario.getDeclaringTable(false) + .onFirstInstance("myField", + "null", + "1", + "2" + ).andOnSecondInstance("myField", + "null", + "1", + "2" + ).whenQuery("select myField, mode(myField) as mode from testTable group by myField order by myField") + .thenResultIs(pinotDataType + " | DOUBLE", + defaultNullValue + " | " + aggrSvWithoutNullResult(scenario._dataType), + "1 | 1", + "2 | 2"); + } + + @Test(dataProvider = "scenarios") + void aggrSvSelfWithNull(Scenario scenario) { + PinotDataType pinotDataType = scenario._dataType == FieldSpec.DataType.INT + ? PinotDataType.INTEGER : PinotDataType.valueOf(scenario._dataType.name()); + + scenario.getDeclaringTable(true) + .onFirstInstance("myField", + "null", + "1", + "2" + ).andOnSecondInstance("myField", + "null", + "1", + "2" + ).whenQuery("select myField, mode(myField) as mode from testTable group by myField order by myField") + .thenResultIs(pinotDataType + " | DOUBLE", "1 | 1", "2 | 2", "null | null"); + } + + String aggrMvWithoutNullResult(FieldSpec.DataType dt) { + switch (dt) { + case INT: return "-2.147483648E9"; + case LONG: return "-9.223372036854776E18"; + case FLOAT: return "-Infinity"; + case DOUBLE: return "-Infinity"; + default: throw new IllegalArgumentException(dt.toString()); + } + } + + @Test(dataProvider = "scenarios") + void aggrMvWithoutNull(Scenario scenario) { + // TODO: This test is not actually exercising aggregateGroupByMV + scenario.getDeclaringTable(false) + .onFirstInstance("myField", + "null", + "1", + "null" + ).andOnSecondInstance("myField", + "null", + "1", + "null" + ).whenQuery("select 'cte1' as cte1, 'cte2' as cte2, mode(myField) as mode from testTable group by cte1, cte2") + .thenResultIs("STRING | STRING | DOUBLE", "cte1 | cte2 | " + aggrMvWithoutNullResult(scenario._dataType)); + } + + @Test(dataProvider = "scenarios") + void aggrMvWithNull(Scenario scenario) { + // TODO: This test is not actually exercising aggregateGroupByMV + scenario.getDeclaringTable(true) + .onFirstInstance("myField", + "null", + "1", + "null" + ).andOnSecondInstance("myField", + "null", + "1", + "null" + ).whenQuery("select 'cte1' as cte1, 'cte2' as cte2, mode(myField) as mode from testTable group by cte1, cte2") + .thenResultIs("STRING | STRING | DOUBLE", "cte1 | cte2 | 1"); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileAggregationFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileAggregationFunctionTest.java new file mode 100644 index 000000000000..3c2ecdde0112 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileAggregationFunctionTest.java @@ -0,0 +1,27 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.query.aggregation.function; + +public class PercentileAggregationFunctionTest extends AbstractPercentileAggregationFunctionTest { + @Override + public String callStr(String column, int percent) { + return "PERCENTILE(" + column + ", " + percent + ")"; + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileEstAggregationFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileEstAggregationFunctionTest.java new file mode 100644 index 000000000000..4dda1614b7c8 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileEstAggregationFunctionTest.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + +import org.apache.pinot.spi.data.FieldSpec; + + +public class PercentileEstAggregationFunctionTest extends AbstractPercentileAggregationFunctionTest { + @Override + public String callStr(String column, int percent) { + return "PERCENTILEEST(" + column + ", " + percent + ")"; + } + + @Override + public String getFinalResultColumnType() { + return "LONG"; + } + + String minValue(FieldSpec.DataType dataType) { + switch (dataType) { + case INT: return "-2147483648"; + case LONG: return "-9223372036854775808"; + case FLOAT: return "-9223372036854775808"; + case DOUBLE: return "-9223372036854775808"; + default: + throw new IllegalArgumentException("Unexpected type " + dataType); + } + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileKLLAggregationFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileKLLAggregationFunctionTest.java new file mode 100644 index 000000000000..1eb6c991c22f --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileKLLAggregationFunctionTest.java @@ -0,0 +1,47 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + + +public class PercentileKLLAggregationFunctionTest extends AbstractPercentileAggregationFunctionTest { + @Override + public String callStr(String column, int percent) { + return "PERCENTILEKLL(" + column + ", " + percent + ")"; + } + + @Override + String expectedAggrWithNull10(Scenario scenario) { + return "0"; + } + + @Override + String expectedAggrWithNull30(Scenario scenario) { + return "2"; + } + + @Override + String expectedAggrWithNull50(Scenario scenario) { + return "4"; + } + + @Override + String expectedAggrWithNull70(Scenario scenario) { + return "6"; + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileSmartTDigestAggregationFunctionTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileSmartTDigestAggregationFunctionTest.java new file mode 100644 index 000000000000..68a180ea886d --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/function/PercentileSmartTDigestAggregationFunctionTest.java @@ -0,0 +1,82 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.aggregation.function; + + +public class PercentileSmartTDigestAggregationFunctionTest { + + public static class WithHighThreshold extends AbstractPercentileAggregationFunctionTest { + @Override + public String callStr(String column, int percent) { + return "PERCENTILESMARTTDIGEST(" + column + ", " + percent + ", 'THRESHOLD=10000')"; + } + } + + public static class WithSmallThreshold extends AbstractPercentileAggregationFunctionTest { + @Override + public String callStr(String column, int percent) { + return "PERCENTILESMARTTDIGEST(" + column + ", " + percent + ", 'THRESHOLD=1')"; + } + + @Override + String expectedAggrWithNull10(Scenario scenario) { + return "0.5"; + } + + @Override + String expectedAggrWithNull30(Scenario scenario) { + return "2.5"; + } + + @Override + String expectedAggrWithNull50(Scenario scenario) { + return "4.5"; + } + + @Override + String expectedAggrWithNull70(Scenario scenario) { + return "6.5"; + } + + @Override + String expectedAggrWithoutNull55(Scenario scenario) { + switch (scenario.getDataType()) { + case INT: + return "-6.442450943999939E8"; + case LONG: + return "-2.7670116110564065E18"; + case FLOAT: + case DOUBLE: + return "-Infinity"; + default: + throw new IllegalArgumentException("Unsupported datatype " + scenario.getDataType()); + } + } + + @Override + String expectedAggrWithoutNull75(Scenario scenario) { + return "4.0"; + } + + @Override + String expectedAggrWithoutNull90(Scenario scenario) { + return "7.100000000000001"; + } + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/groupby/DictionaryBasedGroupKeyGeneratorTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/groupby/DictionaryBasedGroupKeyGeneratorTest.java index 5412d34c17c3..a0d61c0e52e4 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/groupby/DictionaryBasedGroupKeyGeneratorTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/groupby/DictionaryBasedGroupKeyGeneratorTest.java @@ -29,12 +29,12 @@ import java.util.Random; import java.util.Set; import org.apache.commons.io.FileUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.pinot.common.request.context.ExpressionContext; -import org.apache.pinot.core.operator.blocks.TransformBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.plan.DocIdSetPlanNode; -import org.apache.pinot.core.plan.TransformPlanNode; +import org.apache.pinot.core.plan.ProjectPlanNode; import org.apache.pinot.core.plan.maker.InstancePlanMakerImplV2; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.request.context.utils.QueryContextConverterUtils; @@ -42,6 +42,7 @@ import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; @@ -78,8 +79,8 @@ public class DictionaryBasedGroupKeyGeneratorTest { private final Random _random = new Random(_randomSeed); private final String _errorMessage = "Random seed is: " + _randomSeed; - private TransformOperator _transformOperator; - private TransformBlock _transformBlock; + private BaseProjectOperator _projectOperator; + private ValueBlock _valueBlock; @BeforeClass private void setup() @@ -151,10 +152,10 @@ private void setup() for (String column : MV_COLUMNS) { expressions.add(ExpressionContext.forIdentifier(column)); } - TransformPlanNode transformPlanNode = - new TransformPlanNode(indexSegment, queryContext, expressions, DocIdSetPlanNode.MAX_DOC_PER_CALL); - _transformOperator = transformPlanNode.run(); - _transformBlock = _transformOperator.nextBlock(); + ProjectPlanNode projectPlanNode = new ProjectPlanNode(new SegmentContext(indexSegment), queryContext, expressions, + DocIdSetPlanNode.MAX_DOC_PER_CALL); + _projectOperator = projectPlanNode.run(); + _valueBlock = _projectOperator.nextBlock(); } @Test @@ -164,14 +165,14 @@ public void testArrayBasedSingleValue() { // Test initial status DictionaryBasedGroupKeyGenerator dictionaryBasedGroupKeyGenerator = - new DictionaryBasedGroupKeyGenerator(_transformOperator, getExpressions(groupByColumns), + new DictionaryBasedGroupKeyGenerator(_projectOperator, getExpressions(groupByColumns), InstancePlanMakerImplV2.DEFAULT_NUM_GROUPS_LIMIT, InstancePlanMakerImplV2.DEFAULT_MAX_INITIAL_RESULT_HOLDER_CAPACITY); assertEquals(dictionaryBasedGroupKeyGenerator.getGlobalGroupKeyUpperBound(), UNIQUE_ROWS, _errorMessage); assertEquals(dictionaryBasedGroupKeyGenerator.getCurrentGroupKeyUpperBound(), UNIQUE_ROWS, _errorMessage); // Test group key generation - dictionaryBasedGroupKeyGenerator.generateKeysForBlock(_transformBlock, SV_GROUP_KEY_BUFFER); + dictionaryBasedGroupKeyGenerator.generateKeysForBlock(_valueBlock, SV_GROUP_KEY_BUFFER); assertEquals(dictionaryBasedGroupKeyGenerator.getCurrentGroupKeyUpperBound(), UNIQUE_ROWS, _errorMessage); compareSingleValueBuffer(); testGetGroupKeys(dictionaryBasedGroupKeyGenerator.getGroupKeys(), 2); @@ -184,7 +185,7 @@ public void testIntMapBasedSingleValue() { // Test initial status DictionaryBasedGroupKeyGenerator dictionaryBasedGroupKeyGenerator = - new DictionaryBasedGroupKeyGenerator(_transformOperator, getExpressions(groupByColumns), + new DictionaryBasedGroupKeyGenerator(_projectOperator, getExpressions(groupByColumns), InstancePlanMakerImplV2.DEFAULT_NUM_GROUPS_LIMIT, InstancePlanMakerImplV2.DEFAULT_MAX_INITIAL_RESULT_HOLDER_CAPACITY); assertEquals(dictionaryBasedGroupKeyGenerator.getGlobalGroupKeyUpperBound(), @@ -192,7 +193,7 @@ public void testIntMapBasedSingleValue() { assertEquals(dictionaryBasedGroupKeyGenerator.getCurrentGroupKeyUpperBound(), 0, _errorMessage); // Test group key generation - dictionaryBasedGroupKeyGenerator.generateKeysForBlock(_transformBlock, SV_GROUP_KEY_BUFFER); + dictionaryBasedGroupKeyGenerator.generateKeysForBlock(_valueBlock, SV_GROUP_KEY_BUFFER); assertEquals(dictionaryBasedGroupKeyGenerator.getCurrentGroupKeyUpperBound(), 2, _errorMessage); compareSingleValueBuffer(); testGetGroupKeys(dictionaryBasedGroupKeyGenerator.getGroupKeys(), 2); @@ -205,7 +206,7 @@ public void testLongMapBasedSingleValue() { // Test initial status DictionaryBasedGroupKeyGenerator dictionaryBasedGroupKeyGenerator = - new DictionaryBasedGroupKeyGenerator(_transformOperator, getExpressions(groupByColumns), + new DictionaryBasedGroupKeyGenerator(_projectOperator, getExpressions(groupByColumns), InstancePlanMakerImplV2.DEFAULT_NUM_GROUPS_LIMIT, InstancePlanMakerImplV2.DEFAULT_MAX_INITIAL_RESULT_HOLDER_CAPACITY); assertEquals(dictionaryBasedGroupKeyGenerator.getGlobalGroupKeyUpperBound(), @@ -213,7 +214,7 @@ public void testLongMapBasedSingleValue() { assertEquals(dictionaryBasedGroupKeyGenerator.getCurrentGroupKeyUpperBound(), 0, _errorMessage); // Test group key generation - dictionaryBasedGroupKeyGenerator.generateKeysForBlock(_transformBlock, SV_GROUP_KEY_BUFFER); + dictionaryBasedGroupKeyGenerator.generateKeysForBlock(_valueBlock, SV_GROUP_KEY_BUFFER); assertEquals(dictionaryBasedGroupKeyGenerator.getCurrentGroupKeyUpperBound(), 2, _errorMessage); compareSingleValueBuffer(); testGetGroupKeys(dictionaryBasedGroupKeyGenerator.getGroupKeys(), 2); @@ -226,7 +227,7 @@ public void testArrayMapBasedSingleValue() { // Test initial status DictionaryBasedGroupKeyGenerator dictionaryBasedGroupKeyGenerator = - new DictionaryBasedGroupKeyGenerator(_transformOperator, getExpressions(groupByColumns), + new DictionaryBasedGroupKeyGenerator(_projectOperator, getExpressions(groupByColumns), InstancePlanMakerImplV2.DEFAULT_NUM_GROUPS_LIMIT, InstancePlanMakerImplV2.DEFAULT_MAX_INITIAL_RESULT_HOLDER_CAPACITY); assertEquals(dictionaryBasedGroupKeyGenerator.getGlobalGroupKeyUpperBound(), @@ -234,7 +235,7 @@ public void testArrayMapBasedSingleValue() { assertEquals(dictionaryBasedGroupKeyGenerator.getCurrentGroupKeyUpperBound(), 0, _errorMessage); // Test group key generation - dictionaryBasedGroupKeyGenerator.generateKeysForBlock(_transformBlock, SV_GROUP_KEY_BUFFER); + dictionaryBasedGroupKeyGenerator.generateKeysForBlock(_valueBlock, SV_GROUP_KEY_BUFFER); assertEquals(dictionaryBasedGroupKeyGenerator.getCurrentGroupKeyUpperBound(), 2, _errorMessage); compareSingleValueBuffer(); testGetGroupKeys(dictionaryBasedGroupKeyGenerator.getGroupKeys(), 2); @@ -261,14 +262,14 @@ public void testArrayBasedMultiValue() { // Test initial status DictionaryBasedGroupKeyGenerator dictionaryBasedGroupKeyGenerator = - new DictionaryBasedGroupKeyGenerator(_transformOperator, getExpressions(groupByColumns), + new DictionaryBasedGroupKeyGenerator(_projectOperator, getExpressions(groupByColumns), InstancePlanMakerImplV2.DEFAULT_NUM_GROUPS_LIMIT, InstancePlanMakerImplV2.DEFAULT_MAX_INITIAL_RESULT_HOLDER_CAPACITY); int groupKeyUpperBound = dictionaryBasedGroupKeyGenerator.getGlobalGroupKeyUpperBound(); assertEquals(dictionaryBasedGroupKeyGenerator.getCurrentGroupKeyUpperBound(), groupKeyUpperBound, _errorMessage); // Test group key generation - dictionaryBasedGroupKeyGenerator.generateKeysForBlock(_transformBlock, MV_GROUP_KEY_BUFFER); + dictionaryBasedGroupKeyGenerator.generateKeysForBlock(_valueBlock, MV_GROUP_KEY_BUFFER); int numUniqueKeys = MV_GROUP_KEY_BUFFER[0].length + MV_GROUP_KEY_BUFFER[1].length; assertEquals(dictionaryBasedGroupKeyGenerator.getCurrentGroupKeyUpperBound(), groupKeyUpperBound, _errorMessage); compareMultiValueBuffer(); @@ -282,7 +283,7 @@ public void tesIntMapBasedMultiValue() { // Test initial status DictionaryBasedGroupKeyGenerator dictionaryBasedGroupKeyGenerator = - new DictionaryBasedGroupKeyGenerator(_transformOperator, getExpressions(groupByColumns), + new DictionaryBasedGroupKeyGenerator(_projectOperator, getExpressions(groupByColumns), InstancePlanMakerImplV2.DEFAULT_NUM_GROUPS_LIMIT, InstancePlanMakerImplV2.DEFAULT_MAX_INITIAL_RESULT_HOLDER_CAPACITY); assertEquals(dictionaryBasedGroupKeyGenerator.getGlobalGroupKeyUpperBound(), @@ -290,7 +291,7 @@ public void tesIntMapBasedMultiValue() { assertEquals(dictionaryBasedGroupKeyGenerator.getCurrentGroupKeyUpperBound(), 0, _errorMessage); // Test group key generation - dictionaryBasedGroupKeyGenerator.generateKeysForBlock(_transformBlock, MV_GROUP_KEY_BUFFER); + dictionaryBasedGroupKeyGenerator.generateKeysForBlock(_valueBlock, MV_GROUP_KEY_BUFFER); int numUniqueKeys = MV_GROUP_KEY_BUFFER[0].length + MV_GROUP_KEY_BUFFER[1].length; assertEquals(dictionaryBasedGroupKeyGenerator.getCurrentGroupKeyUpperBound(), numUniqueKeys, _errorMessage); compareMultiValueBuffer(); @@ -305,7 +306,7 @@ public void testLongMapBasedMultiValue() { // Test initial status DictionaryBasedGroupKeyGenerator dictionaryBasedGroupKeyGenerator = - new DictionaryBasedGroupKeyGenerator(_transformOperator, getExpressions(groupByColumns), + new DictionaryBasedGroupKeyGenerator(_projectOperator, getExpressions(groupByColumns), InstancePlanMakerImplV2.DEFAULT_NUM_GROUPS_LIMIT, InstancePlanMakerImplV2.DEFAULT_MAX_INITIAL_RESULT_HOLDER_CAPACITY); assertEquals(dictionaryBasedGroupKeyGenerator.getGlobalGroupKeyUpperBound(), @@ -313,7 +314,7 @@ public void testLongMapBasedMultiValue() { assertEquals(dictionaryBasedGroupKeyGenerator.getCurrentGroupKeyUpperBound(), 0, _errorMessage); // Test group key generation - dictionaryBasedGroupKeyGenerator.generateKeysForBlock(_transformBlock, MV_GROUP_KEY_BUFFER); + dictionaryBasedGroupKeyGenerator.generateKeysForBlock(_valueBlock, MV_GROUP_KEY_BUFFER); int numUniqueKeys = MV_GROUP_KEY_BUFFER[0].length + MV_GROUP_KEY_BUFFER[1].length; assertEquals(dictionaryBasedGroupKeyGenerator.getCurrentGroupKeyUpperBound(), numUniqueKeys, _errorMessage); compareMultiValueBuffer(); @@ -327,7 +328,7 @@ public void testArrayMapBasedMultiValue() { // Test initial status DictionaryBasedGroupKeyGenerator dictionaryBasedGroupKeyGenerator = - new DictionaryBasedGroupKeyGenerator(_transformOperator, getExpressions(groupByColumns), + new DictionaryBasedGroupKeyGenerator(_projectOperator, getExpressions(groupByColumns), InstancePlanMakerImplV2.DEFAULT_NUM_GROUPS_LIMIT, InstancePlanMakerImplV2.DEFAULT_MAX_INITIAL_RESULT_HOLDER_CAPACITY); assertEquals(dictionaryBasedGroupKeyGenerator.getGlobalGroupKeyUpperBound(), @@ -335,7 +336,7 @@ public void testArrayMapBasedMultiValue() { assertEquals(dictionaryBasedGroupKeyGenerator.getCurrentGroupKeyUpperBound(), 0, _errorMessage); // Test group key generation - dictionaryBasedGroupKeyGenerator.generateKeysForBlock(_transformBlock, MV_GROUP_KEY_BUFFER); + dictionaryBasedGroupKeyGenerator.generateKeysForBlock(_valueBlock, MV_GROUP_KEY_BUFFER); int numUniqueKeys = MV_GROUP_KEY_BUFFER[0].length + MV_GROUP_KEY_BUFFER[1].length; assertEquals(dictionaryBasedGroupKeyGenerator.getCurrentGroupKeyUpperBound(), numUniqueKeys, _errorMessage); compareMultiValueBuffer(); @@ -348,13 +349,13 @@ public void testNumGroupsLimit() { int numGroupsLimit = 1; // NOTE: arrayBasedThreshold must be smaller or equal to numGroupsLimit DictionaryBasedGroupKeyGenerator dictionaryBasedGroupKeyGenerator = - new DictionaryBasedGroupKeyGenerator(_transformOperator, getExpressions(groupByColumns), numGroupsLimit, + new DictionaryBasedGroupKeyGenerator(_projectOperator, getExpressions(groupByColumns), numGroupsLimit, numGroupsLimit); assertEquals(dictionaryBasedGroupKeyGenerator.getGlobalGroupKeyUpperBound(), numGroupsLimit, _errorMessage); assertEquals(dictionaryBasedGroupKeyGenerator.getCurrentGroupKeyUpperBound(), 0, _errorMessage); // Test group key generation - dictionaryBasedGroupKeyGenerator.generateKeysForBlock(_transformBlock, MV_GROUP_KEY_BUFFER); + dictionaryBasedGroupKeyGenerator.generateKeysForBlock(_valueBlock, MV_GROUP_KEY_BUFFER); assertEquals(dictionaryBasedGroupKeyGenerator.getCurrentGroupKeyUpperBound(), numGroupsLimit, _errorMessage); // Only the first key should be 0, all others should be GroupKeyGenerator.INVALID_ID (-1) boolean firstKey = true; diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/groupby/GroupByTrimTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/groupby/GroupByTrimTest.java index 62236f3a4b6b..1cebefe52a11 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/groupby/GroupByTrimTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/groupby/GroupByTrimTest.java @@ -29,11 +29,11 @@ import java.util.concurrent.Executors; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.tuple.Pair; +import org.apache.pinot.core.common.Operator; import org.apache.pinot.core.data.table.Record; import org.apache.pinot.core.data.table.Table; import org.apache.pinot.core.operator.blocks.results.GroupByResultsBlock; import org.apache.pinot.core.operator.combine.GroupByCombineOperator; -import org.apache.pinot.core.operator.query.GroupByOperator; import org.apache.pinot.core.plan.GroupByPlanNode; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.request.context.utils.QueryContextConverterUtils; @@ -41,6 +41,7 @@ import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.data.FieldSpec; @@ -50,13 +51,12 @@ import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.spi.utils.ReadMode; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; -import static org.testng.Assert.assertEquals; - /** * Unit test for GroupBy Trim functionalities. @@ -120,7 +120,8 @@ void testGroupByTrim(QueryContext queryContext, int minSegmentGroupTrimSize, int queryContext.setMinServerGroupTrimSize(minServerGroupTrimSize); // Create a query operator - GroupByOperator groupByOperator = new GroupByPlanNode(_indexSegment, queryContext).run(); + Operator groupByOperator = + new GroupByPlanNode(new SegmentContext(_indexSegment), queryContext).run(); GroupByCombineOperator combineOperator = new GroupByCombineOperator(Collections.singletonList(groupByOperator), queryContext, _executorService); @@ -130,7 +131,7 @@ void testGroupByTrim(QueryContext queryContext, int minSegmentGroupTrimSize, int // Extract the execution result List> extractedResult = extractTestResult(resultsBlock.getTable()); - assertEquals(extractedResult, expectedResult); + Assert.assertEquals(extractedResult, expectedResult); } /** diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/groupby/NoDictionaryGroupKeyGeneratorTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/groupby/NoDictionaryGroupKeyGeneratorTest.java index b93135db105e..fa2882c192b2 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/groupby/NoDictionaryGroupKeyGeneratorTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/aggregation/groupby/NoDictionaryGroupKeyGeneratorTest.java @@ -31,10 +31,10 @@ import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.apache.pinot.common.request.context.ExpressionContext; -import org.apache.pinot.core.operator.blocks.TransformBlock; -import org.apache.pinot.core.operator.transform.TransformOperator; +import org.apache.pinot.core.operator.BaseProjectOperator; +import org.apache.pinot.core.operator.blocks.ValueBlock; import org.apache.pinot.core.plan.DocIdSetPlanNode; -import org.apache.pinot.core.plan.TransformPlanNode; +import org.apache.pinot.core.plan.ProjectPlanNode; import org.apache.pinot.core.plan.maker.InstancePlanMakerImplV2; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.request.context.utils.QueryContextConverterUtils; @@ -42,6 +42,7 @@ import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; @@ -96,8 +97,8 @@ public class NoDictionaryGroupKeyGeneratorTest { private final String[][] _stringValues = new String[NUM_UNIQUE_RECORDS][NUM_COLUMNS]; private IndexSegment _indexSegment; - private TransformOperator _transformOperator; - private TransformBlock _transformBlock; + private BaseProjectOperator _projectOperator; + private ValueBlock _valueBlock; @BeforeClass public void setUp() @@ -154,10 +155,10 @@ public void setUp() for (String column : COLUMNS) { expressions.add(ExpressionContext.forIdentifier(column)); } - TransformPlanNode transformPlanNode = - new TransformPlanNode(_indexSegment, queryContext, expressions, DocIdSetPlanNode.MAX_DOC_PER_CALL); - _transformOperator = transformPlanNode.run(); - _transformBlock = _transformOperator.nextBlock(); + ProjectPlanNode projectPlanNode = new ProjectPlanNode(new SegmentContext(_indexSegment), queryContext, expressions, + DocIdSetPlanNode.MAX_DOC_PER_CALL); + _projectOperator = projectPlanNode.run(); + _valueBlock = _projectOperator.nextBlock(); } /** @@ -197,7 +198,7 @@ private void testGroupKeyGenerator(int[] groupByColumnIndexes) { int numGroupByColumns = groupByColumnIndexes.length; GroupKeyGenerator groupKeyGenerator; if (numGroupByColumns == 1) { - groupKeyGenerator = new NoDictionarySingleColumnGroupKeyGenerator(_transformOperator, + groupKeyGenerator = new NoDictionarySingleColumnGroupKeyGenerator(_projectOperator, ExpressionContext.forIdentifier(COLUMNS.get(groupByColumnIndexes[0])), InstancePlanMakerImplV2.DEFAULT_NUM_GROUPS_LIMIT, false); } else { @@ -205,10 +206,10 @@ private void testGroupKeyGenerator(int[] groupByColumnIndexes) { for (int i = 0; i < numGroupByColumns; i++) { groupByExpressions[i] = ExpressionContext.forIdentifier(COLUMNS.get(groupByColumnIndexes[i])); } - groupKeyGenerator = new NoDictionaryMultiColumnGroupKeyGenerator(_transformOperator, groupByExpressions, - InstancePlanMakerImplV2.DEFAULT_NUM_GROUPS_LIMIT); + groupKeyGenerator = new NoDictionaryMultiColumnGroupKeyGenerator(_projectOperator, groupByExpressions, + InstancePlanMakerImplV2.DEFAULT_NUM_GROUPS_LIMIT, false); } - groupKeyGenerator.generateKeysForBlock(_transformBlock, new int[NUM_RECORDS]); + groupKeyGenerator.generateKeysForBlock(_valueBlock, new int[NUM_RECORDS]); // Assert total number of group keys is as expected Set expectedGroupKeys = getExpectedGroupKeys(groupByColumnIndexes); diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/executor/QueryExecutorExceptionsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/executor/QueryExecutorExceptionsTest.java index 82202007abcb..c664c2f013c5 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/query/executor/QueryExecutorExceptionsTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/executor/QueryExecutorExceptionsTest.java @@ -26,11 +26,10 @@ import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.commons.configuration2.PropertiesConfiguration; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.apache.helix.HelixManager; -import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.pinot.common.exception.QueryException; import org.apache.pinot.common.metrics.ServerMetrics; import org.apache.pinot.common.request.InstanceRequest; @@ -39,10 +38,10 @@ import org.apache.pinot.core.operator.blocks.InstanceResponseBlock; import org.apache.pinot.core.query.request.ServerQueryRequest; import org.apache.pinot.segment.local.data.manager.TableDataManager; -import org.apache.pinot.segment.local.data.manager.TableDataManagerConfig; import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; import org.apache.pinot.segment.local.segment.creator.SegmentTestUtils; import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; +import org.apache.pinot.segment.local.utils.SegmentLocks; import org.apache.pinot.segment.spi.ImmutableSegment; import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; @@ -53,8 +52,8 @@ import org.apache.pinot.spi.data.IngestionSchemaValidator; import org.apache.pinot.spi.data.Schema; import org.apache.pinot.spi.data.readers.FileFormat; +import org.apache.pinot.spi.env.CommonsConfigurationUtils; import org.apache.pinot.spi.env.PinotConfiguration; -import org.apache.pinot.spi.metrics.PinotMetricUtils; import org.apache.pinot.spi.utils.ReadMode; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.apache.pinot.spi.utils.builder.TableNameBuilder; @@ -85,12 +84,13 @@ public class QueryExecutorExceptionsTest { private final List _indexSegments = new ArrayList<>(NUM_SEGMENTS_TO_GENERATE); private final List _segmentNames = new ArrayList<>(NUM_SEGMENTS_TO_GENERATE); - private ServerMetrics _serverMetrics; private QueryExecutor _queryExecutor; @BeforeClass public void setUp() throws Exception { + ServerMetrics.register(mock(ServerMetrics.class)); + // Set up the segments FileUtils.deleteQuietly(INDEX_DIR); assertTrue(INDEX_DIR.mkdirs()); @@ -131,21 +131,11 @@ public void setUp() } // Mock the instance data manager - _serverMetrics = new ServerMetrics(PinotMetricUtils.getPinotMetricsRegistry()); - TableDataManagerConfig tableDataManagerConfig = mock(TableDataManagerConfig.class); - when(tableDataManagerConfig.getTableName()).thenReturn(OFFLINE_TABLE_NAME); - when(tableDataManagerConfig.getTableType()).thenReturn(TableType.OFFLINE); - when(tableDataManagerConfig.getDataDir()).thenReturn(FileUtils.getTempDirectoryPath()); InstanceDataManagerConfig instanceDataManagerConfig = mock(InstanceDataManagerConfig.class); - when(instanceDataManagerConfig.getMaxParallelSegmentBuilds()).thenReturn(4); - when(instanceDataManagerConfig.getStreamSegmentDownloadUntarRateLimit()).thenReturn(-1L); - when(instanceDataManagerConfig.getMaxParallelSegmentDownloads()).thenReturn(-1); - when(instanceDataManagerConfig.isStreamSegmentDownloadUntar()).thenReturn(false); - TableDataManagerProvider.init(instanceDataManagerConfig); - @SuppressWarnings("unchecked") + when(instanceDataManagerConfig.getInstanceDataDir()).thenReturn(INDEX_DIR.getAbsolutePath()); TableDataManager tableDataManager = - TableDataManagerProvider.getTableDataManager(tableDataManagerConfig, "testInstance", - mock(ZkHelixPropertyStore.class), mock(ServerMetrics.class), mock(HelixManager.class), null); + new TableDataManagerProvider(instanceDataManagerConfig, mock(HelixManager.class), + new SegmentLocks()).getTableDataManager(tableConfig); tableDataManager.start(); //we don't add index segments to the data manager to simulate numSegmentsAcquired < numSegmentsQueried InstanceDataManager instanceDataManager = mock(InstanceDataManager.class); @@ -154,11 +144,10 @@ public void setUp() // Set up the query executor resourceUrl = getClass().getClassLoader().getResource(QUERY_EXECUTOR_CONFIG_PATH); assertNotNull(resourceUrl); - PropertiesConfiguration queryExecutorConfig = new PropertiesConfiguration(); - queryExecutorConfig.setDelimiterParsingDisabled(false); - queryExecutorConfig.load(new File(resourceUrl.getFile())); + PropertiesConfiguration queryExecutorConfig = + CommonsConfigurationUtils.fromFile(new File(resourceUrl.getFile())); _queryExecutor = new ServerQueryExecutorV1Impl(); - _queryExecutor.init(new PinotConfiguration(queryExecutorConfig), instanceDataManager, _serverMetrics); + _queryExecutor.init(new PinotConfiguration(queryExecutorConfig), instanceDataManager, ServerMetrics.get()); } /** @@ -190,6 +179,6 @@ public void tearDown() { } private ServerQueryRequest getQueryRequest(InstanceRequest instanceRequest) { - return new ServerQueryRequest(instanceRequest, _serverMetrics, System.currentTimeMillis()); + return new ServerQueryRequest(instanceRequest, ServerMetrics.get(), System.currentTimeMillis()); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/executor/QueryExecutorTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/executor/QueryExecutorTest.java index f0cf7beecd50..d59b6df68111 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/query/executor/QueryExecutorTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/executor/QueryExecutorTest.java @@ -24,10 +24,9 @@ import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.commons.configuration2.PropertiesConfiguration; import org.apache.commons.io.FileUtils; import org.apache.helix.HelixManager; -import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.pinot.common.metrics.ServerMetrics; import org.apache.pinot.common.request.InstanceRequest; import org.apache.pinot.core.data.manager.InstanceDataManager; @@ -36,10 +35,10 @@ import org.apache.pinot.core.operator.blocks.results.AggregationResultsBlock; import org.apache.pinot.core.query.request.ServerQueryRequest; import org.apache.pinot.segment.local.data.manager.TableDataManager; -import org.apache.pinot.segment.local.data.manager.TableDataManagerConfig; import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; import org.apache.pinot.segment.local.segment.creator.SegmentTestUtils; import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; +import org.apache.pinot.segment.local.utils.SegmentLocks; import org.apache.pinot.segment.spi.ImmutableSegment; import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; @@ -50,8 +49,8 @@ import org.apache.pinot.spi.data.IngestionSchemaValidator; import org.apache.pinot.spi.data.Schema; import org.apache.pinot.spi.data.readers.FileFormat; +import org.apache.pinot.spi.env.CommonsConfigurationUtils; import org.apache.pinot.spi.env.PinotConfiguration; -import org.apache.pinot.spi.metrics.PinotMetricUtils; import org.apache.pinot.spi.utils.ReadMode; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.apache.pinot.spi.utils.builder.TableNameBuilder; @@ -71,7 +70,7 @@ public class QueryExecutorTest { private static final String AVRO_DATA_PATH = "data/simpleData200001.avro"; private static final String EMPTY_JSON_DATA_PATH = "data/test_empty_data.json"; private static final String QUERY_EXECUTOR_CONFIG_PATH = "conf/query-executor.properties"; - private static final File INDEX_DIR = new File(FileUtils.getTempDirectory(), "QueryExecutorTest"); + private static final File TEMP_DIR = new File(FileUtils.getTempDirectory(), "QueryExecutorTest"); private static final String RAW_TABLE_NAME = "testTable"; private static final String OFFLINE_TABLE_NAME = TableNameBuilder.OFFLINE.tableNameWithType(RAW_TABLE_NAME); private static final int NUM_SEGMENTS_TO_GENERATE = 2; @@ -81,25 +80,27 @@ public class QueryExecutorTest { private final List _indexSegments = new ArrayList<>(NUM_SEGMENTS_TO_GENERATE); private final List _segmentNames = new ArrayList<>(NUM_SEGMENTS_TO_GENERATE); - private ServerMetrics _serverMetrics; private QueryExecutor _queryExecutor; @BeforeClass public void setUp() throws Exception { + ServerMetrics.register(mock(ServerMetrics.class)); + // Set up the segments - FileUtils.deleteQuietly(INDEX_DIR); - assertTrue(INDEX_DIR.mkdirs()); + FileUtils.deleteQuietly(TEMP_DIR); + assertTrue(TEMP_DIR.mkdirs()); URL resourceUrl = getClass().getClassLoader().getResource(AVRO_DATA_PATH); Assert.assertNotNull(resourceUrl); File avroFile = new File(resourceUrl.getFile()); Schema schema = SegmentTestUtils.extractSchemaFromAvroWithoutTime(avroFile); TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + File tableDataDir = new File(TEMP_DIR, OFFLINE_TABLE_NAME); int i = 0; for (; i < NUM_SEGMENTS_TO_GENERATE; i++) { SegmentGeneratorConfig config = - SegmentTestUtils.getSegmentGeneratorConfig(avroFile, FileFormat.AVRO, INDEX_DIR, RAW_TABLE_NAME, tableConfig, - schema); + SegmentTestUtils.getSegmentGeneratorConfig(avroFile, FileFormat.AVRO, tableDataDir, RAW_TABLE_NAME, + tableConfig, schema); config.setSegmentNamePostfix(Integer.toString(i)); SegmentIndexCreationDriver driver = new SegmentIndexCreationDriverImpl(); driver.init(config); @@ -109,7 +110,7 @@ public void setUp() Assert.assertFalse(ingestionSchemaValidator.getSingleValueMultiValueFieldMismatchResult().isMismatchDetected()); Assert.assertFalse(ingestionSchemaValidator.getMultiValueStructureMismatchResult().isMismatchDetected()); Assert.assertFalse(ingestionSchemaValidator.getMissingPinotColumnResult().isMismatchDetected()); - _indexSegments.add(ImmutableSegmentLoader.load(new File(INDEX_DIR, driver.getSegmentName()), ReadMode.mmap)); + _indexSegments.add(ImmutableSegmentLoader.load(new File(tableDataDir, driver.getSegmentName()), ReadMode.mmap)); _segmentNames.add(driver.getSegmentName()); } resourceUrl = getClass().getClassLoader().getResource(EMPTY_JSON_DATA_PATH); @@ -117,32 +118,22 @@ public void setUp() File jsonFile = new File(resourceUrl.getFile()); for (; i < NUM_SEGMENTS_TO_GENERATE + NUM_EMPTY_SEGMENTS_TO_GENERATE; i++) { SegmentGeneratorConfig config = - SegmentTestUtils.getSegmentGeneratorConfig(jsonFile, FileFormat.JSON, INDEX_DIR, RAW_TABLE_NAME, tableConfig, - schema); + SegmentTestUtils.getSegmentGeneratorConfig(jsonFile, FileFormat.JSON, tableDataDir, RAW_TABLE_NAME, + tableConfig, schema); config.setSegmentNamePostfix(Integer.toString(i)); SegmentIndexCreationDriver driver = new SegmentIndexCreationDriverImpl(); driver.init(config); driver.build(); - _indexSegments.add(ImmutableSegmentLoader.load(new File(INDEX_DIR, driver.getSegmentName()), ReadMode.mmap)); + _indexSegments.add(ImmutableSegmentLoader.load(new File(tableDataDir, driver.getSegmentName()), ReadMode.mmap)); _segmentNames.add(driver.getSegmentName()); } // Mock the instance data manager - _serverMetrics = new ServerMetrics(PinotMetricUtils.getPinotMetricsRegistry()); - TableDataManagerConfig tableDataManagerConfig = mock(TableDataManagerConfig.class); - when(tableDataManagerConfig.getTableName()).thenReturn(OFFLINE_TABLE_NAME); - when(tableDataManagerConfig.getTableType()).thenReturn(TableType.OFFLINE); - when(tableDataManagerConfig.getDataDir()).thenReturn(FileUtils.getTempDirectoryPath()); InstanceDataManagerConfig instanceDataManagerConfig = mock(InstanceDataManagerConfig.class); - when(instanceDataManagerConfig.getMaxParallelSegmentBuilds()).thenReturn(4); - when(instanceDataManagerConfig.getStreamSegmentDownloadUntarRateLimit()).thenReturn(-1L); - when(instanceDataManagerConfig.getMaxParallelSegmentDownloads()).thenReturn(-1); - when(instanceDataManagerConfig.isStreamSegmentDownloadUntar()).thenReturn(false); - TableDataManagerProvider.init(instanceDataManagerConfig); - @SuppressWarnings("unchecked") + when(instanceDataManagerConfig.getInstanceDataDir()).thenReturn(TEMP_DIR.getAbsolutePath()); TableDataManager tableDataManager = - TableDataManagerProvider.getTableDataManager(tableDataManagerConfig, "testInstance", - mock(ZkHelixPropertyStore.class), mock(ServerMetrics.class), mock(HelixManager.class), null); + new TableDataManagerProvider(instanceDataManagerConfig, mock(HelixManager.class), + new SegmentLocks()).getTableDataManager(tableConfig); tableDataManager.start(); for (ImmutableSegment indexSegment : _indexSegments) { tableDataManager.addSegment(indexSegment); @@ -153,11 +144,9 @@ public void setUp() // Set up the query executor resourceUrl = getClass().getClassLoader().getResource(QUERY_EXECUTOR_CONFIG_PATH); Assert.assertNotNull(resourceUrl); - PropertiesConfiguration queryExecutorConfig = new PropertiesConfiguration(); - queryExecutorConfig.setDelimiterParsingDisabled(false); - queryExecutorConfig.load(new File(resourceUrl.getFile())); + PropertiesConfiguration queryExecutorConfig = CommonsConfigurationUtils.fromFile(new File(resourceUrl.getFile())); _queryExecutor = new ServerQueryExecutorV1Impl(); - _queryExecutor.init(new PinotConfiguration(queryExecutorConfig), instanceDataManager, _serverMetrics); + _queryExecutor.init(new PinotConfiguration(queryExecutorConfig), instanceDataManager, ServerMetrics.get()); } @Test @@ -205,10 +194,10 @@ public void tearDown() { for (IndexSegment segment : _indexSegments) { segment.destroy(); } - FileUtils.deleteQuietly(INDEX_DIR); + FileUtils.deleteQuietly(TEMP_DIR); } private ServerQueryRequest getQueryRequest(InstanceRequest instanceRequest) { - return new ServerQueryRequest(instanceRequest, _serverMetrics, System.currentTimeMillis()); + return new ServerQueryRequest(instanceRequest, ServerMetrics.get(), System.currentTimeMillis()); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/optimizer/QueryOptimizerTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/optimizer/QueryOptimizerTest.java index 6054de965823..25a8109237e2 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/query/optimizer/QueryOptimizerTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/optimizer/QueryOptimizerTest.java @@ -73,16 +73,14 @@ public void testFlattenAndOrFilter() { assertEquals(secondChildFunction.getOperator(), FilterKind.AND.name()); List secondChildChildren = secondChildFunction.getOperands(); assertEquals(secondChildChildren.size(), 3); - assertEquals(secondChildChildren.get(0), getEqFilterExpression("long", 5L)); - assertEquals(secondChildChildren.get(1), getEqFilterExpression("float", 9f)); + assertEquals(secondChildChildren.get(0), getEqFilterExpression("long", 5)); + assertEquals(secondChildChildren.get(1), getEqFilterExpression("float", 9)); assertEquals(secondChildChildren.get(2), getEqFilterExpression("double", 7.5)); } private static Expression getEqFilterExpression(String column, Object value) { - Expression eqFilterExpression = RequestUtils.getFunctionExpression(FilterKind.EQUALS.name()); - eqFilterExpression.getFunctionCall().setOperands( - Arrays.asList(RequestUtils.getIdentifierExpression(column), RequestUtils.getLiteralExpression(value))); - return eqFilterExpression; + return RequestUtils.getFunctionExpression(FilterKind.EQUALS.name(), RequestUtils.getIdentifierExpression(column), + RequestUtils.getLiteralExpression(value)); } @Test @@ -97,7 +95,7 @@ public void testMergeEqInFilter() { List children = filterFunction.getOperands(); assertEquals(children.size(), 3); assertEquals(children.get(0), getEqFilterExpression("int", 1)); - checkInFilterFunction(children.get(1).getFunctionCall(), "long", Arrays.asList(2L, 3L, 4L)); + checkInFilterFunction(children.get(1).getFunctionCall(), "long", Arrays.asList(2, 3, 4)); Function thirdChildFunction = children.get(2).getFunctionCall(); assertEquals(thirdChildFunction.getOperator(), FilterKind.OR.name()); @@ -154,7 +152,7 @@ public void testMergeRangeFilter() { assertEquals(secondChildFunction.getOperator(), FilterKind.AND.name()); List secondChildChildren = secondChildFunction.getOperands(); assertEquals(secondChildChildren.size(), 2); - assertEquals(secondChildChildren.get(0), getEqFilterExpression("float", 6f)); + assertEquals(secondChildChildren.get(0), getEqFilterExpression("float", 6)); assertEquals(secondChildChildren.get(1), getRangeFilterExpression("float", "[6.0\0006.5)")); // Range filter on multi-value column should not be merged ([-5, 10] can match this filter) @@ -166,11 +164,24 @@ public void testMergeRangeFilter() { assertEquals(fourthChildChildren.get(1).getFunctionCall().getOperator(), FilterKind.LESS_THAN.name()); } + @Test + public void testMergeTextMatchFilter() { + String query = + "SELECT * FROM testTable WHERE TEXT_MATCH(string, 'foo') AND TEXT_MATCH(string, 'bar') OR TEXT_MATCH(string, " + + "'baz')"; + PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(query); + OPTIMIZER.optimize(pinotQuery, SCHEMA); + Function filterFunction = pinotQuery.getFilterExpression().getFunctionCall(); + assertEquals(filterFunction.getOperator(), FilterKind.TEXT_MATCH.name()); + List operands = filterFunction.getOperands(); + assertEquals(operands.size(), 2); + assertEquals(operands.get(0), RequestUtils.getIdentifierExpression("string")); + assertEquals(operands.get(1), RequestUtils.getLiteralExpression("((foo AND bar) OR baz)")); + } + private static Expression getRangeFilterExpression(String column, String rangeString) { - Expression rangeFilterExpression = RequestUtils.getFunctionExpression(FilterKind.RANGE.name()); - rangeFilterExpression.getFunctionCall().setOperands( - Arrays.asList(RequestUtils.getIdentifierExpression(column), RequestUtils.getLiteralExpression(rangeString))); - return rangeFilterExpression; + return RequestUtils.getFunctionExpression(FilterKind.RANGE.name(), RequestUtils.getIdentifierExpression(column), + RequestUtils.getLiteralExpression(rangeString)); } @Test @@ -225,9 +236,71 @@ public void testQueries() { testQuery( "SELECT * FROM testTable WHERE int >= 20 AND (int > 10 AND (int IN (1, 2) OR (int = 2 OR int = 3)) AND int <=" + " 30)", "SELECT * FROM testTable WHERE int BETWEEN 20 AND 30 AND int IN (1, 2, 3)"); + + // IdenticalPredicateOptimizer + testQuery("SELECT * FROM testTable WHERE 1=1", "SELECT * FROM testTable WHERE true"); + testQuery("SELECT * FROM testTable WHERE 1!=1", "SELECT * FROM testTable WHERE false"); + testQuery("SELECT * FROM testTable WHERE 1=1 AND 1!=1", "SELECT * FROM testTable WHERE false"); + testQuery("SELECT * FROM testTable WHERE 1=1 OR 1!=1", "SELECT * FROM testTable WHERE true"); + + testQuery("SELECT * FROM testTable WHERE \"a\"=\"a\"", "SELECT * FROM testTable WHERE true"); + testQuery("SELECT * FROM testTable WHERE \"a\"!=\"a\"", "SELECT * FROM testTable WHERE false"); + testQuery("SELECT * FROM testTable WHERE \"a\"=\"a\" AND \"a\"!=\"a\"", "SELECT * FROM testTable WHERE false"); + testQuery("SELECT * FROM testTable WHERE \"a\"=\"a\" OR \"a\"!=\"a\"", "SELECT * FROM testTable WHERE true"); + + testQuery("SELECT * FROM testTable WHERE 1=1 AND \"a\"=\"a\"", "SELECT * FROM testTable WHERE true"); + testQuery("SELECT * FROM testTable WHERE 1=1 OR \"a\"=\"a\"", "SELECT * FROM testTable WHERE true"); + testQuery("SELECT * FROM testTable WHERE 1!=1 AND \"a\"=\"a\"", "SELECT * FROM testTable WHERE false"); + testQuery("SELECT * FROM testTable WHERE 1=1 AND \"a\"!=\"a\"", "SELECT * FROM testTable WHERE false"); + testQuery("SELECT * FROM testTable WHERE 1!=1 OR \"a\"=\"a\"", "SELECT * FROM testTable WHERE true"); + testQuery("SELECT * FROM testTable WHERE 1=1 OR \"a\"!=\"a\"", "SELECT * FROM testTable WHERE true"); + + testQuery("SELECT * FROM testTable WHERE 1.0=1.0", "SELECT * FROM testTable WHERE true"); + testQuery("SELECT * FROM testTable WHERE 1.0=1", "SELECT * FROM testTable WHERE true"); + testQuery("SELECT * FROM testTable WHERE 1.01=1", "SELECT * FROM testTable WHERE false"); + + testQuery("SELECT * FROM testTable WHERE 1=1 AND true", "SELECT * FROM testTable WHERE true"); + testQuery("SELECT * FROM testTable WHERE \"a\"=\"a\" AND true", "SELECT * FROM testTable WHERE true"); + + // TextMatchFilterOptimizer + testQuery("SELECT * FROM testTable WHERE TEXT_MATCH(string, 'foo') AND TEXT_MATCH(string, 'bar')", + "SELECT * FROM testTable WHERE TEXT_MATCH(string, '(foo AND bar)')"); + testQuery("SELECT * FROM testTable WHERE TEXT_MATCH(string, '\"foo bar\"') AND TEXT_MATCH(string, 'baz')", + "SELECT * FROM testTable WHERE TEXT_MATCH(string, '(\"foo bar\" AND baz)')"); + testQuery("SELECT * FROM testTable WHERE TEXT_MATCH(string, '\"foo bar\"') AND TEXT_MATCH(string, '/.*ooba.*/')", + "SELECT * FROM testTable WHERE TEXT_MATCH(string, '(\"foo bar\" AND /.*ooba.*/)')"); + testQuery("SELECT * FROM testTable WHERE int = 1 AND TEXT_MATCH(string, 'foo') AND TEXT_MATCH(string, 'bar')", + "SELECT * FROM testTable WHERE int = 1 AND TEXT_MATCH(string, '(foo AND bar)')"); + testQuery("SELECT * FROM testTable WHERE int = 1 OR TEXT_MATCH(string, 'foo') AND TEXT_MATCH(string, 'bar')", + "SELECT * FROM testTable WHERE int = 1 OR TEXT_MATCH(string, '(foo AND bar)')"); + testQuery("SELECT * FROM testTable WHERE TEXT_MATCH(string, 'foo') AND NOT TEXT_MATCH(string, 'bar')", + "SELECT * FROM testTable WHERE TEXT_MATCH(string, '(foo AND NOT bar)')"); + testQuery("SELECT * FROM testTable WHERE NOT TEXT_MATCH(string, 'foo') AND TEXT_MATCH(string, 'bar')", + "SELECT * FROM testTable WHERE TEXT_MATCH(string, '(NOT foo AND bar)')"); + testQuery("SELECT * FROM testTable WHERE NOT TEXT_MATCH(string, 'foo') AND NOT TEXT_MATCH(string, 'bar')", + "SELECT * FROM testTable WHERE NOT TEXT_MATCH(string, '(foo OR bar)')"); + testQuery("SELECT * FROM testTable WHERE NOT TEXT_MATCH(string, 'foo') OR NOT TEXT_MATCH(string, 'bar')", + "SELECT * FROM testTable WHERE NOT TEXT_MATCH(string, '(foo AND bar)')"); + testQuery("SELECT * FROM testTable WHERE TEXT_MATCH(string, 'foo') AND TEXT_MATCH(string, 'bar') OR " + + "TEXT_MATCH(string, 'baz')", "SELECT * FROM testTable WHERE TEXT_MATCH(string, '((foo AND bar) OR baz)')"); + testQuery("SELECT * FROM testTable WHERE TEXT_MATCH(string, 'foo') AND (TEXT_MATCH(string, 'bar') OR " + + "TEXT_MATCH(string, 'baz'))", "SELECT * FROM testTable WHERE TEXT_MATCH(string, '(foo AND (bar OR baz))')"); + testQuery("SELECT * FROM testTable WHERE TEXT_MATCH(string1, 'foo1') AND TEXT_MATCH(string1, 'bar1') OR " + + "TEXT_MATCH(string1, 'baz1') AND TEXT_MATCH(string2, 'foo')", + "SELECT * FROM testTable WHERE TEXT_MATCH(string1, '(foo1 AND bar1)') OR TEXT_MATCH(string1, 'baz1') AND " + + "TEXT_MATCH(string2, 'foo')"); + testQuery("SELECT * FROM testTable WHERE TEXT_MATCH(string1, 'foo1') AND TEXT_MATCH(string1, 'bar1')" + + "AND TEXT_MATCH(string2, 'foo2') AND TEXT_MATCH(string2, 'bar2')", + "SELECT * FROM testTable WHERE TEXT_MATCH(string1, '(foo1 AND bar1)') AND TEXT_MATCH(string2, '(foo2 AND " + + "bar2)')"); + testCannotOptimizeQuery("SELECT * FROM testTable WHERE TEXT_MATCH(string1, 'foo') OR TEXT_MATCH(string2, 'bar')"); + testCannotOptimizeQuery( + "SELECT * FROM testTable WHERE int = 1 AND TEXT_MATCH(string, 'foo') OR TEXT_MATCH(string, 'bar')"); + testCannotOptimizeQuery("SELECT * FROM testTable WHERE NOT TEXT_MATCH(string, 'foo')"); } private static void testQuery(String actual, String expected) { + assertNotEquals(actual, expected, "You must provide different queries to test"); PinotQuery actualPinotQuery = CalciteSqlParser.compileToPinotQuery(actual); OPTIMIZER.optimize(actualPinotQuery, SCHEMA); // Also optimize the expected query because the expected range can only be generate via optimizer @@ -236,6 +309,13 @@ private static void testQuery(String actual, String expected) { comparePinotQuery(actualPinotQuery, expectedPinotQuery); } + private static void testCannotOptimizeQuery(String query) { + PinotQuery actualPinotQuery = CalciteSqlParser.compileToPinotQuery(query); + OPTIMIZER.optimize(actualPinotQuery, SCHEMA); + PinotQuery expectedPinotQuery = CalciteSqlParser.compileToPinotQuery(query); + comparePinotQuery(actualPinotQuery, expectedPinotQuery); + } + private static void comparePinotQuery(PinotQuery actual, PinotQuery expected) { if (expected.getFilterExpression() == null) { assertNull(actual.getFilterExpression()); @@ -245,30 +325,37 @@ private static void comparePinotQuery(PinotQuery actual, PinotQuery expected) { } private static void compareFilterExpression(Expression actual, Expression expected) { - Function actualFilterFunction = actual.getFunctionCall(); - Function expectedFilterFunction = expected.getFunctionCall(); - FilterKind actualFilterKind = FilterKind.valueOf(actualFilterFunction.getOperator()); - FilterKind expectedFilterKind = FilterKind.valueOf(expectedFilterFunction.getOperator()); - List actualOperands = actualFilterFunction.getOperands(); - List expectedOperands = expectedFilterFunction.getOperands(); - if (!actualFilterKind.isRange()) { - assertEquals(actualFilterKind, expectedFilterKind); - assertEquals(actualOperands.size(), expectedOperands.size()); - if (actualFilterKind == FilterKind.AND || actualFilterKind == FilterKind.OR) { - compareFilterExpressionChildren(actualOperands, expectedOperands); - } else { - assertEquals(actualOperands.get(0), expectedOperands.get(0)); - if (actualFilterKind == FilterKind.IN || actualFilterKind == FilterKind.NOT_IN) { - // Handle different order of values - assertEqualsNoOrder(actualOperands.toArray(), expectedOperands.toArray()); + if (actual.isSetLiteral()) { + assertNull(actual.getFunctionCall()); + assertNull(expected.getFunctionCall()); + assertTrue(expected.isSetLiteral()); + assertEquals(actual.getLiteral(), expected.getLiteral()); + } else { + Function actualFilterFunction = actual.getFunctionCall(); + Function expectedFilterFunction = expected.getFunctionCall(); + FilterKind actualFilterKind = FilterKind.valueOf(actualFilterFunction.getOperator()); + FilterKind expectedFilterKind = FilterKind.valueOf(expectedFilterFunction.getOperator()); + List actualOperands = actualFilterFunction.getOperands(); + List expectedOperands = expectedFilterFunction.getOperands(); + if (!actualFilterKind.isRange()) { + assertEquals(actualFilterKind, expectedFilterKind); + assertEquals(actualOperands.size(), expectedOperands.size()); + if (actualFilterKind == FilterKind.AND || actualFilterKind == FilterKind.OR) { + compareFilterExpressionChildren(actualOperands, expectedOperands); } else { - assertEquals(actualOperands, expectedOperands); + assertEquals(actualOperands.get(0), expectedOperands.get(0)); + if (actualFilterKind == FilterKind.IN || actualFilterKind == FilterKind.NOT_IN) { + // Handle different order of values + assertEqualsNoOrder(actualOperands.toArray(), expectedOperands.toArray()); + } else { + assertEquals(actualOperands, expectedOperands); + } } + } else { + assertTrue(expectedFilterKind.isRange()); + assertEquals(getRangeString(actualFilterKind, actualOperands), + getRangeString(expectedFilterKind, expectedOperands)); } - } else { - assertTrue(expectedFilterKind.isRange()); - assertEquals(getRangeString(actualFilterKind, actualOperands), - getRangeString(expectedFilterKind, expectedOperands)); } } @@ -300,16 +387,16 @@ private static void compareFilterExpressionChildren(List actual, Lis private static String getRangeString(FilterKind filterKind, List operands) { switch (filterKind) { case GREATER_THAN: - return Range.LOWER_EXCLUSIVE + operands.get(1).getLiteral().getFieldValue().toString() + Range.UPPER_UNBOUNDED; + return Range.LOWER_EXCLUSIVE + RequestUtils.getLiteralString(operands.get(1)) + Range.UPPER_UNBOUNDED; case GREATER_THAN_OR_EQUAL: - return Range.LOWER_INCLUSIVE + operands.get(1).getLiteral().getFieldValue().toString() + Range.UPPER_UNBOUNDED; + return Range.LOWER_INCLUSIVE + RequestUtils.getLiteralString(operands.get(1)) + Range.UPPER_UNBOUNDED; case LESS_THAN: - return Range.LOWER_UNBOUNDED + operands.get(1).getLiteral().getFieldValue().toString() + Range.UPPER_EXCLUSIVE; + return Range.LOWER_UNBOUNDED + RequestUtils.getLiteralString(operands.get(1)) + Range.UPPER_EXCLUSIVE; case LESS_THAN_OR_EQUAL: - return Range.LOWER_UNBOUNDED + operands.get(1).getLiteral().getFieldValue().toString() + Range.UPPER_INCLUSIVE; + return Range.LOWER_UNBOUNDED + RequestUtils.getLiteralString(operands.get(1)) + Range.UPPER_INCLUSIVE; case BETWEEN: - return Range.LOWER_INCLUSIVE + operands.get(1).getLiteral().getFieldValue().toString() + Range.DELIMITER - + operands.get(2).getLiteral().getFieldValue().toString() + Range.UPPER_INCLUSIVE; + return Range.LOWER_INCLUSIVE + RequestUtils.getLiteralString(operands.get(1)) + Range.DELIMITER + + RequestUtils.getLiteralString(operands.get(2)) + Range.UPPER_INCLUSIVE; case RANGE: return operands.get(1).getLiteral().getStringValue(); default: diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/optimizer/filter/NumericalFilterOptimizerTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/optimizer/filter/NumericalFilterOptimizerTest.java index 913d14e61d15..e947740e2423 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/query/optimizer/filter/NumericalFilterOptimizerTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/optimizer/filter/NumericalFilterOptimizerTest.java @@ -94,45 +94,45 @@ public void testEqualsRewrites() { Assert.assertEquals(rewrite("SELECT * FROM testTable WHERE longColumn != 5.5"), "Expression(type:LITERAL, literal:)"); - // Test float column equals valid long value. + // Test float column equals valid int value. Assert.assertEquals(rewrite("SELECT * FROM testTable WHERE floatColumn = 5"), "Expression(type:FUNCTION, functionCall:Function(operator:EQUALS, operands:[Expression(type:IDENTIFIER, " - + "identifier:Identifier(name:floatColumn)), Expression(type:LITERAL, literal:)" + + "identifier:Identifier(name:floatColumn)), Expression(type:LITERAL, literal:)" + + "]))"); + + // Test float column not equals valid int value + Assert.assertEquals(rewrite("SELECT * FROM testTable WHERE floatColumn != 5"), + "Expression(type:FUNCTION, functionCall:Function(operator:NOT_EQUALS, operands:[Expression(type:IDENTIFIER, " + + "identifier:Identifier(name:floatColumn)), Expression(type:LITERAL, literal:)" + "]))"); // Test float column equals invalid long value. - Assert.assertEquals(rewrite("SELECT * FROM testTable WHERE floatColumn = " + String.valueOf(Long.MAX_VALUE)), + Assert.assertEquals(rewrite("SELECT * FROM testTable WHERE floatColumn = " + Long.MAX_VALUE), "Expression(type:LITERAL, literal:)"); - // Test float column not equals valid long value - Assert.assertEquals(rewrite("SELECT * FROM testTable WHERE floatColumn != " + String.valueOf(Long.MAX_VALUE)), + // Test float column not equals invalid long value + Assert.assertEquals(rewrite("SELECT * FROM testTable WHERE floatColumn != " + Long.MAX_VALUE), "Expression(type:LITERAL, literal:)"); - // Test float column not equals valid long value - Assert.assertEquals(rewrite("SELECT * FROM testTable WHERE floatColumn != 5"), - "Expression(type:FUNCTION, functionCall:Function(operator:NOT_EQUALS, operands:[Expression(type:IDENTIFIER, " - + "identifier:Identifier(name:floatColumn)), Expression(type:LITERAL, literal:)" - + "]))"); - - // test double column equals valid long value. + // test double column equals valid int value. Assert.assertEquals(rewrite("SELECT * FROM testTable WHERE doubleColumn = 5"), "Expression(type:FUNCTION, functionCall:Function(operator:EQUALS, operands:[Expression(type:IDENTIFIER, " - + "identifier:Identifier(name:doubleColumn)), Expression(type:LITERAL, literal:)" + + "identifier:Identifier(name:doubleColumn)), Expression(type:LITERAL, literal:)" + + "]))"); + + // Test double column not equals valid int value. + Assert.assertEquals(rewrite("SELECT * FROM testTable WHERE doubleColumn != 5"), + "Expression(type:FUNCTION, functionCall:Function(operator:NOT_EQUALS, operands:[Expression(type:IDENTIFIER, " + + "identifier:Identifier(name:doubleColumn)), Expression(type:LITERAL, literal:)" + "]))"); // Test double column equals invalid long value. - Assert.assertEquals(rewrite("SELECT * FROM testTable WHERE doubleColumn = " + String.valueOf(Long.MAX_VALUE)), + Assert.assertEquals(rewrite("SELECT * FROM testTable WHERE doubleColumn = " + Long.MAX_VALUE), "Expression(type:LITERAL, literal:)"); - // Test double column not equals valid long value - Assert.assertEquals(rewrite("SELECT * FROM testTable WHERE doubleColumn != " + String.valueOf(Long.MAX_VALUE)), + // Test double column not equals invalid long value + Assert.assertEquals(rewrite("SELECT * FROM testTable WHERE doubleColumn != " + Long.MAX_VALUE), "Expression(type:LITERAL, literal:)"); - - // Test double column not equals invalid long value. - Assert.assertEquals(rewrite("SELECT * FROM testTable WHERE doubleColumn != 5"), - "Expression(type:FUNCTION, functionCall:Function(operator:NOT_EQUALS, operands:[Expression(type:IDENTIFIER, " - + "identifier:Identifier(name:doubleColumn)), Expression(type:LITERAL, literal:)" - + "]))"); } @Test @@ -216,29 +216,29 @@ public void testRangeRewrites() { + " identifier:Identifier(name:floatColumn)), Expression(type:LITERAL, literal:)]))"); - // Test FLOAT column with DOUBLE value that is within bounds of FLOAT. + // Test FLOAT column with DOUBLE value: no rewrite. Assert.assertEquals(rewrite("SELECT * FROM testTable WHERE floatColumn > -2100000000.5"), - "Expression(type:FUNCTION, functionCall:Function(operator:GREATER_THAN_OR_EQUAL, operands:[Expression" + "Expression(type:FUNCTION, functionCall:Function(operator:GREATER_THAN, operands:[Expression" + "(type:IDENTIFIER, identifier:Identifier(name:floatColumn)), Expression(type:LITERAL, literal:)]))"); + + "doubleValue:-2.1000000005E9>)]))"); - // Test FLOAT column with DOUBLE value that is within bounds of FLOAT. + // Test FLOAT column with DOUBLE value: no rewrite. Assert.assertEquals(rewrite("SELECT * FROM testTable WHERE floatColumn < -2100000000.5"), "Expression(type:FUNCTION, functionCall:Function(operator:LESS_THAN, operands:[Expression(type:IDENTIFIER, " - + "identifier:Identifier(name:floatColumn)), Expression(type:LITERAL, literal:)]))"); + + "identifier:Identifier(name:floatColumn)), Expression(type:LITERAL, literal:)]))"); - // Test FLOAT column with DOUBLE value that is within bounds of FLOAT. + // Test FLOAT column with DOUBLE value: no rewrite. Assert.assertEquals(rewrite("SELECT * FROM testTable WHERE floatColumn <= 2100000000.5"), "Expression(type:FUNCTION, functionCall:Function(operator:LESS_THAN_OR_EQUAL, operands:[Expression" + "(type:IDENTIFIER, identifier:Identifier(name:floatColumn)), Expression(type:LITERAL, literal:)]))"); + + "doubleValue:2.1000000005E9>)]))"); - // Test FLOAT column with DOUBLE value that is within bounds of FLOAT. + // Test FLOAT column with DOUBLE value: no rewrite. Assert.assertEquals(rewrite("SELECT * FROM testTable WHERE floatColumn >= 2100000000.5"), - "Expression(type:FUNCTION, functionCall:Function(operator:GREATER_THAN, operands:[Expression(type:IDENTIFIER," - + " identifier:Identifier(name:floatColumn)), Expression(type:LITERAL, literal:)]))"); + "Expression(type:FUNCTION, functionCall:Function(operator:GREATER_THAN_OR_EQUAL, operands:[Expression(" + + "type:IDENTIFIER, identifier:Identifier(name:floatColumn)), Expression(type:LITERAL, literal:)]))"); // Test LONG column with DOUBLE value greater than Long.MAX_VALUE. Assert.assertEquals(rewrite("SELECT * FROM testTable WHERE longColumn > 999999999999999999999999999999.9999"), diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/prefetch/DefaultFetchPlannerTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/prefetch/DefaultFetchPlannerTest.java index 602a466a789f..7b1605989808 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/query/prefetch/DefaultFetchPlannerTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/prefetch/DefaultFetchPlannerTest.java @@ -26,8 +26,9 @@ import org.apache.pinot.segment.spi.FetchContext; import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.segment.spi.datasource.DataSource; +import org.apache.pinot.segment.spi.index.IndexType; +import org.apache.pinot.segment.spi.index.StandardIndexes; import org.apache.pinot.segment.spi.index.reader.BloomFilterReader; -import org.apache.pinot.segment.spi.store.ColumnIndexType; import org.testng.annotations.Test; import static org.mockito.Mockito.mock; @@ -49,7 +50,7 @@ public void testPlanFetchForProcessing() { QueryContext queryContext = QueryContextConverterUtils.getQueryContext(query); FetchContext fetchContext = planner.planFetchForProcessing(indexSegment, queryContext); assertEquals(fetchContext.getSegmentName(), "s0"); - Map> columns = fetchContext.getColumnToIndexList(); + Map>> columns = fetchContext.getColumnToIndexList(); assertEquals(columns.size(), 3); // null means to get all index types created for the column. assertNull(columns.get("c0")); @@ -81,10 +82,10 @@ public void testPlanFetchForPruning() { fetchContext = planner.planFetchForPruning(indexSegment, queryContext); assertFalse(fetchContext.isEmpty()); assertEquals(fetchContext.getSegmentName(), "s0"); - Map> columns = fetchContext.getColumnToIndexList(); + Map>> columns = fetchContext.getColumnToIndexList(); assertEquals(columns.size(), 1); - List idxTypes = columns.get("c0"); + List> idxTypes = columns.get("c0"); assertEquals(idxTypes.size(), 1); - assertEquals(idxTypes.get(0), ColumnIndexType.BLOOM_FILTER); + assertEquals(idxTypes.get(0), StandardIndexes.bloomFilter()); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/pruner/BloomFilterSegmentPrunerTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/pruner/BloomFilterSegmentPrunerTest.java new file mode 100644 index 000000000000..75b2beea0fd2 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/pruner/BloomFilterSegmentPrunerTest.java @@ -0,0 +1,216 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.pruner; + +import com.google.common.base.Charsets; +import com.google.common.collect.ImmutableSet; +import com.google.common.hash.BloomFilter; +import com.google.common.hash.Funnels; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.query.request.context.utils.QueryContextConverterUtils; +import org.apache.pinot.segment.local.segment.index.readers.bloom.OnHeapGuavaBloomFilterReader; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentMetadata; +import org.apache.pinot.segment.spi.datasource.DataSource; +import org.apache.pinot.segment.spi.datasource.DataSourceMetadata; +import org.apache.pinot.segment.spi.index.reader.BloomFilterReader; +import org.apache.pinot.segment.spi.memory.PinotDataBuffer; +import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.env.PinotConfiguration; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + + +public class BloomFilterSegmentPrunerTest { + private static final BloomFilterSegmentPruner PRUNER = new BloomFilterSegmentPruner(); + + @BeforeClass + public void setUp() { + Map properties = new HashMap<>(); + // override default value + properties.put(ColumnValueSegmentPruner.IN_PREDICATE_THRESHOLD, 5); + PinotConfiguration configuration = new PinotConfiguration(properties); + PRUNER.init(configuration); + } + + @Test + public void testBloomFilterPruning() + throws IOException { + IndexSegment indexSegment = mockIndexSegment(new String[]{"1.0", "2.0", "3.0", "5.0", "7.0", "21.0"}); + + // all out the bloom filter + assertTrue(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column IN (0.0)")); + assertTrue(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column = 0.0")); + assertTrue(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column = 6.0")); + assertTrue(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column IN (6.0)")); + assertTrue(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column = 0.0 OR column = 6.0")); + + // all in the bloom filter + assertFalse(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column = 5.0")); + assertFalse(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column IN (5.0)")); + assertFalse(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column = 5.0 OR column = 7.0")); + assertFalse(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column IN (5.0, 7.0)")); + assertFalse(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column = 5.0 AND column = 7.0")); + + // some in the bloom filter with IN/OR + assertFalse(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column IN (0.0, 3.0, 4.0)")); + assertFalse(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column = 1.0")); + assertFalse(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column IN (1.0)")); + assertFalse(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column = 21.0")); + assertFalse(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column IN (21.0)")); + assertFalse(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column IN (21.0, 30.0)")); + assertFalse(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column = 21.0 OR column = 30.0")); + // 30 out the bloom filter with AND + assertTrue(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column = 21.0 AND column = 30.0")); + } + + @Test(expectedExceptions = RuntimeException.class) + public void testQueryTimeoutOnPruning() + throws IOException { + IndexSegment indexSegment = mockIndexSegment(new String[]{"1.0", "2.0", "3.0", "5.0", "7.0", "21.0"}); + DataSource dataSource = mock(DataSource.class); + when(indexSegment.getDataSource("column")).thenReturn(dataSource); + runPruner(Collections.singletonList(indexSegment), + "SELECT COUNT(*) FROM testTable WHERE column = 5.0 OR column = 0.0", 1); + } + + @Test + public void testParallelPrune() + throws IOException { + List segments = new ArrayList<>(); + for (int i = 0; i < 35; i++) { + IndexSegment indexSegment = mockIndexSegment(new String[]{"1.0", "2.0", "3.0", "5.0", "7.0", "21.0"}); + segments.add(indexSegment); + } + assertTrue( + runPruner(segments, "SELECT COUNT(*) FROM testTable WHERE column = 21.0 AND column = 30.0", 5000).isEmpty()); + + IndexSegment indexSegment = mockIndexSegment(new String[]{"1.0", "2.0", "3.0", "5.0", "7.0", "21.0", "30.0"}); + segments.add(indexSegment); + List selected = + runPruner(segments, "SELECT COUNT(*) FROM testTable WHERE column = 21.0 AND column = 30.0", 5000); + assertEquals(selected.size(), 1); + } + + @Test + public void testIsApplicableTo() { + // EQ and IN (with small number of values) are applicable for bloom filter based pruning. + QueryContext queryContext = + QueryContextConverterUtils.getQueryContext("SELECT COUNT(*) FROM testTable WHERE column = 1"); + assertTrue(PRUNER.isApplicableTo(queryContext)); + queryContext = QueryContextConverterUtils.getQueryContext("SELECT COUNT(*) FROM testTable WHERE column IN (1, 2)"); + assertTrue(PRUNER.isApplicableTo(queryContext)); + + // NOT is not applicable + queryContext = QueryContextConverterUtils.getQueryContext("SELECT COUNT(*) FROM testTable WHERE NOT column = 1"); + assertFalse(PRUNER.isApplicableTo(queryContext)); + // Too many values for IN clause + queryContext = QueryContextConverterUtils.getQueryContext( + "SELECT COUNT(*) FROM testTable WHERE column IN (1, 2, 3, 4, 5, 6, 7)"); + assertFalse(PRUNER.isApplicableTo(queryContext)); + // Other predicate types are not applicable + queryContext = QueryContextConverterUtils.getQueryContext("SELECT COUNT(*) FROM testTable WHERE column LIKE 5"); + assertFalse(PRUNER.isApplicableTo(queryContext)); + + // AND with one applicable child filter is applicable + queryContext = QueryContextConverterUtils.getQueryContext( + "SELECT COUNT(*) FROM testTable WHERE column NOT IN (1, 2) AND column = 3"); + assertTrue(PRUNER.isApplicableTo(queryContext)); + + // OR with one child filter that's not applicable is not applicable + queryContext = QueryContextConverterUtils.getQueryContext( + "SELECT COUNT(*) FROM testTable WHERE column = 3 OR column NOT IN (1, 2)"); + assertFalse(PRUNER.isApplicableTo(queryContext)); + + // Nested with AND/OR + queryContext = QueryContextConverterUtils.getQueryContext( + "SELECT COUNT(*) FROM testTable WHERE column = 3 OR (column NOT IN (1, 2) AND column = 4)"); + assertTrue(PRUNER.isApplicableTo(queryContext)); + } + + private IndexSegment mockIndexSegment(String[] values) + throws IOException { + IndexSegment indexSegment = mock(IndexSegment.class); + when(indexSegment.getColumnNames()).thenReturn(ImmutableSet.of("column")); + SegmentMetadata segmentMetadata = mock(SegmentMetadata.class); + when(segmentMetadata.getTotalDocs()).thenReturn(20); + when(indexSegment.getSegmentMetadata()).thenReturn(segmentMetadata); + + DataSource dataSource = mock(DataSource.class); + when(indexSegment.getDataSource("column")).thenReturn(dataSource); + // Add support for bloom filter + DataSourceMetadata dataSourceMetadata = mock(DataSourceMetadata.class); + BloomFilterReaderBuilder builder = new BloomFilterReaderBuilder(); + for (String v : values) { + builder.put(v); + } + when(dataSourceMetadata.getDataType()).thenReturn(DataType.DOUBLE); + when(dataSource.getDataSourceMetadata()).thenReturn(dataSourceMetadata); + when(dataSource.getBloomFilter()).thenReturn(builder.build()); + + return indexSegment; + } + + private boolean runPruner(IndexSegment segment, String query) { + return runPruner(Collections.singletonList(segment), query, 5000).isEmpty(); + } + + private List runPruner(List segments, String query, long queryTimeout) { + QueryContext queryContext = QueryContextConverterUtils.getQueryContext(query); + queryContext.setEndTimeMs(System.currentTimeMillis() + queryTimeout); + return PRUNER.prune(segments, queryContext, Executors.newCachedThreadPool()); + } + + private static class BloomFilterReaderBuilder { + private BloomFilter _bloomfilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), 100, 0.01); + public BloomFilterReaderBuilder put(String value) { + _bloomfilter.put(value); + return this; + } + + public BloomFilterReader build() throws IOException { + File file = Files.createTempFile("test", ".bloom").toFile(); + try (FileOutputStream fos = new FileOutputStream(file)) { + _bloomfilter.writeTo(fos); + try (PinotDataBuffer pinotDataBuffer = PinotDataBuffer.loadBigEndianFile(file)) { + // on heap filter should never use the buffer, so we can close it and delete the file + return new OnHeapGuavaBloomFilterReader(pinotDataBuffer); + } + } finally { + file.delete(); + } + } + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/pruner/ColumnValueSegmentPrunerTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/pruner/ColumnValueSegmentPrunerTest.java index 5edbdb32a331..5a5d7bd50576 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/query/pruner/ColumnValueSegmentPrunerTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/pruner/ColumnValueSegmentPrunerTest.java @@ -18,30 +18,21 @@ */ package org.apache.pinot.core.query.pruner; -import com.google.common.base.Charsets; import com.google.common.collect.ImmutableSet; -import com.google.common.hash.BloomFilter; -import com.google.common.hash.Funnels; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.file.Files; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.request.context.utils.QueryContextConverterUtils; -import org.apache.pinot.segment.local.segment.index.readers.bloom.OnHeapGuavaBloomFilterReader; import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.segment.spi.SegmentMetadata; import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.segment.spi.datasource.DataSourceMetadata; -import org.apache.pinot.segment.spi.index.reader.BloomFilterReader; -import org.apache.pinot.segment.spi.memory.PinotDataBuffer; import org.apache.pinot.segment.spi.partition.PartitionFunctionFactory; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.env.PinotConfiguration; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import static org.mockito.Mockito.mock; @@ -53,14 +44,17 @@ public class ColumnValueSegmentPrunerTest { private static final ColumnValueSegmentPruner PRUNER = new ColumnValueSegmentPruner(); - @Test - public void testMinMaxValuePruning() { + @BeforeClass + public void setUp() { Map properties = new HashMap<>(); - //override default value - properties.put(PRUNER.IN_PREDICATE_THRESHOLD, 5); + // override default value + properties.put(ColumnValueSegmentPruner.IN_PREDICATE_THRESHOLD, 5); PinotConfiguration configuration = new PinotConfiguration(properties); PRUNER.init(configuration); + } + @Test + public void testMinMaxValuePruning() { IndexSegment indexSegment = mockIndexSegment(); DataSource dataSource = mock(DataSource.class); @@ -147,66 +141,42 @@ public void testPartitionPruning() { } @Test - public void testBloomFilterPredicatePruning() - throws IOException { - Map properties = new HashMap<>(); - // override default value - properties.put(ColumnValueSegmentPruner.IN_PREDICATE_THRESHOLD, 5); - PinotConfiguration configuration = new PinotConfiguration(properties); - PRUNER.init(configuration); - - IndexSegment indexSegment = mockIndexSegment(); - DataSource dataSource = mock(DataSource.class); - when(indexSegment.getDataSource("column")).thenReturn(dataSource); - // Add support for bloom filter - DataSourceMetadata dataSourceMetadata = mock(DataSourceMetadata.class); - BloomFilterReader bloomFilterReader = new BloomFilterReaderBuilder() - .put("1.0") - .put("2.0") - .put("3.0") - .put("5.0") - .put("7.0") - .put("21.0") - .build(); - - when(dataSourceMetadata.getDataType()).thenReturn(DataType.DOUBLE); - when(dataSource.getDataSourceMetadata()).thenReturn(dataSourceMetadata); - when(dataSource.getBloomFilter()).thenReturn(bloomFilterReader); - when(dataSourceMetadata.getMinValue()).thenReturn(5.0); - when(dataSourceMetadata.getMaxValue()).thenReturn(10.0); - - // all out the bloom filter and out of range - assertTrue(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column IN (0.0)")); - assertTrue(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column = 0.0")); - assertTrue(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column IN (0.0, 3.0, 4.0)")); - - // some in the bloom filter but all out of range - assertTrue(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column = 1.0")); - assertTrue(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column IN (1.0)")); - assertTrue(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column = 21.0")); - assertTrue(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column IN (21.0)")); - assertTrue(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column IN (21.0, 30.0)")); - assertTrue(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column = 21.0 AND column = 30.0")); - assertTrue(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column = 21.0 OR column = 30.0")); - - // all out the bloom filter but some in range - assertTrue(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column = 6.0")); - assertTrue(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column IN (6.0)")); - assertTrue(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column = 0.0 OR column = 6.0")); - - // all in the bloom filter and in range - assertFalse(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column = 5.0")); - assertFalse(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column IN (5.0)")); - assertFalse(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column = 5.0 OR column = 7.0")); - assertFalse(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column IN (5.0, 7.0)")); - assertFalse(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column = 5.0 AND column = 7.0")); - - // some in the bloom filter and in range - assertFalse( - runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column IN (0.0, 5.0)")); - assertFalse(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column = 5.0 OR column = 0.0")); - assertTrue(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column = 5.0 AND column = 0.0")); - assertTrue(runPruner(indexSegment, "SELECT COUNT(*) FROM testTable WHERE column IN (8.0, 10.0)")); + public void testIsApplicableTo() { + // EQ, RANGE and IN (with small number of values) are applicable for min/max/partitionId based pruning. + QueryContext queryContext = + QueryContextConverterUtils.getQueryContext("SELECT COUNT(*) FROM testTable WHERE column = 1"); + assertTrue(PRUNER.isApplicableTo(queryContext)); + queryContext = QueryContextConverterUtils.getQueryContext("SELECT COUNT(*) FROM testTable WHERE column IN (1, 2)"); + assertTrue(PRUNER.isApplicableTo(queryContext)); + queryContext = + QueryContextConverterUtils.getQueryContext("SELECT COUNT(*) FROM testTable WHERE column BETWEEN 1 AND 2"); + assertTrue(PRUNER.isApplicableTo(queryContext)); + + // NOT is not applicable + queryContext = QueryContextConverterUtils.getQueryContext("SELECT COUNT(*) FROM testTable WHERE NOT column = 1"); + assertFalse(PRUNER.isApplicableTo(queryContext)); + // Too many values for IN clause + queryContext = QueryContextConverterUtils.getQueryContext( + "SELECT COUNT(*) FROM testTable WHERE column IN (1, 2, 3, 4, 5, 6, 7)"); + assertFalse(PRUNER.isApplicableTo(queryContext)); + // Other predicate types are not applicable + queryContext = QueryContextConverterUtils.getQueryContext("SELECT COUNT(*) FROM testTable WHERE column LIKE 5"); + assertFalse(PRUNER.isApplicableTo(queryContext)); + + // AND with one applicable child filter is applicable + queryContext = QueryContextConverterUtils.getQueryContext( + "SELECT COUNT(*) FROM testTable WHERE column NOT IN (1, 2) AND column = 3"); + assertTrue(PRUNER.isApplicableTo(queryContext)); + + // OR with one child filter that's not applicable is not applicable + queryContext = QueryContextConverterUtils.getQueryContext( + "SELECT COUNT(*) FROM testTable WHERE column = 3 OR column NOT IN (1, 2)"); + assertFalse(PRUNER.isApplicableTo(queryContext)); + + // Nested with AND/OR + queryContext = QueryContextConverterUtils.getQueryContext( + "SELECT COUNT(*) FROM testTable WHERE column = 3 OR (column NOT IN (1, 2) AND column BETWEEN 4 AND 5)"); + assertTrue(PRUNER.isApplicableTo(queryContext)); } private IndexSegment mockIndexSegment() { @@ -222,25 +192,4 @@ private boolean runPruner(IndexSegment indexSegment, String query) { QueryContext queryContext = QueryContextConverterUtils.getQueryContext(query); return PRUNER.prune(Arrays.asList(indexSegment), queryContext).isEmpty(); } - - private static class BloomFilterReaderBuilder { - private BloomFilter _bloomfilter = BloomFilter.create(Funnels.stringFunnel(Charsets.UTF_8), 100, 0.01); - public BloomFilterReaderBuilder put(String value) { - _bloomfilter.put(value); - return this; - } - - public BloomFilterReader build() throws IOException { - File file = Files.createTempFile("test", ".bloom").toFile(); - try (FileOutputStream fos = new FileOutputStream(file)) { - _bloomfilter.writeTo(fos); - try (PinotDataBuffer pinotDataBuffer = PinotDataBuffer.loadBigEndianFile(file)) { - // on heap filter should never use the buffer, so we can close it and delete the file - return new OnHeapGuavaBloomFilterReader(pinotDataBuffer); - } - } finally { - file.delete(); - } - } - } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/reduce/BrokerReduceServiceTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/reduce/BrokerReduceServiceTest.java index f7656a55d957..ed08918251ae 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/query/reduce/BrokerReduceServiceTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/reduce/BrokerReduceServiceTest.java @@ -24,6 +24,7 @@ import java.util.Map; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.exception.QueryException; +import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.common.response.broker.BrokerResponseNative; import org.apache.pinot.common.response.broker.QueryProcessingException; @@ -34,10 +35,11 @@ import org.apache.pinot.core.transport.ServerRoutingInstance; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.env.PinotConfiguration; -import org.apache.pinot.spi.utils.CommonConstants; +import org.apache.pinot.spi.utils.CommonConstants.Broker; import org.apache.pinot.sql.parsers.CalciteSqlCompiler; import org.testng.annotations.Test; +import static org.mockito.Mockito.mock; import static org.testng.Assert.assertEquals; @@ -46,10 +48,8 @@ public class BrokerReduceServiceTest { @Test public void testReduceTimeout() throws IOException { - Map properties = new HashMap<>(); - properties.put(CommonConstants.Broker.CONFIG_OF_MAX_REDUCE_THREADS_PER_QUERY, 2); - BrokerReduceService brokerReduceService = new BrokerReduceService(new PinotConfiguration(properties)); - + BrokerReduceService brokerReduceService = + new BrokerReduceService(new PinotConfiguration(Map.of(Broker.CONFIG_OF_MAX_REDUCE_THREADS_PER_QUERY, 2))); BrokerRequest brokerRequest = CalciteSqlCompiler.compileToBrokerRequest("SELECT COUNT(*) FROM testTable GROUP BY col1"); DataSchema dataSchema = @@ -71,9 +71,12 @@ public void testReduceTimeout() } long reduceTimeoutMs = 1; BrokerResponseNative brokerResponse = - brokerReduceService.reduceOnDataTable(brokerRequest, brokerRequest, dataTableMap, reduceTimeoutMs, null); - List processingExceptions = brokerResponse.getProcessingExceptions(); - assertEquals(processingExceptions.size(), 1); - assertEquals(processingExceptions.get(0).getErrorCode(), QueryException.BROKER_TIMEOUT_ERROR_CODE); + brokerReduceService.reduceOnDataTable(brokerRequest, brokerRequest, dataTableMap, reduceTimeoutMs, + mock(BrokerMetrics.class)); + brokerReduceService.shutDown(); + + List exceptions = brokerResponse.getExceptions(); + assertEquals(exceptions.size(), 1); + assertEquals(exceptions.get(0).getErrorCode(), QueryException.BROKER_TIMEOUT_ERROR_CODE); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/reduce/HavingFilterHandlerTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/reduce/HavingFilterHandlerTest.java index 6c9b47b7cce6..174b54072ee2 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/query/reduce/HavingFilterHandlerTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/reduce/HavingFilterHandlerTest.java @@ -97,4 +97,35 @@ public void testHavingFilter() { assertFalse(havingFilterHandler.isMatch(new Object[]{11, 11L, 10.5f, 10.5, "11", new byte[]{16}, 5})); } } + + @Test + public void testIsNullWhenNullHandlingEnabled() { + QueryContext queryContext = QueryContextConverterUtils.getQueryContext( + "SELECT col1, COUNT(col2) FROM testTable GROUP BY col1 HAVING col1 IS NULL OPTION(enableNullHandling=true)"); + DataSchema dataSchema = new DataSchema(new String[]{"col1", "count(col2)"}, new ColumnDataType[]{ + ColumnDataType.INT, ColumnDataType.INT + }); + PostAggregationHandler postAggregationHandler = new PostAggregationHandler(queryContext, dataSchema); + HavingFilterHandler havingFilterHandler = + new HavingFilterHandler(queryContext.getHavingFilter(), postAggregationHandler, true); + assertTrue(havingFilterHandler.isMatch(new Object[]{null, 1})); + assertFalse(havingFilterHandler.isMatch(new Object[]{1, 1})); + assertFalse(havingFilterHandler.isMatch(new Object[]{Integer.MIN_VALUE, 1})); + } + + @Test + public void testIsNotNullWhenNullHandlingEnabled() { + QueryContext queryContext = QueryContextConverterUtils.getQueryContext( + "SELECT col1, COUNT(col2) FROM testTable GROUP BY col1 HAVING col1 IS NOT NULL OPTION" + + "(enableNullHandling=true)"); + DataSchema dataSchema = new DataSchema(new String[]{"col1", "count(col2)"}, new ColumnDataType[]{ + ColumnDataType.INT, ColumnDataType.INT + }); + PostAggregationHandler postAggregationHandler = new PostAggregationHandler(queryContext, dataSchema); + HavingFilterHandler havingFilterHandler = + new HavingFilterHandler(queryContext.getHavingFilter(), postAggregationHandler, true); + assertFalse(havingFilterHandler.isMatch(new Object[]{null, 1})); + assertTrue(havingFilterHandler.isMatch(new Object[]{1, 1})); + assertTrue(havingFilterHandler.isMatch(new Object[]{Integer.MIN_VALUE, 1})); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/reduce/ReducerDataSchemaUtilsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/reduce/ReducerDataSchemaUtilsTest.java new file mode 100644 index 000000000000..d21d4f277998 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/reduce/ReducerDataSchemaUtilsTest.java @@ -0,0 +1,107 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.reduce; + +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.query.request.context.utils.QueryContextConverterUtils; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; + + +public class ReducerDataSchemaUtilsTest { + + @Test + public void testCanonicalizeDataSchemaForAggregation() { + QueryContext queryContext = QueryContextConverterUtils.getQueryContext("SELECT SUM(col1 + col2) FROM testTable"); + // Intentionally make data schema not matching the string representation of the expression + DataSchema dataSchema = new DataSchema(new String[]{"sum(col1+col2)"}, new ColumnDataType[]{ColumnDataType.DOUBLE}); + DataSchema canonicalDataSchema = + ReducerDataSchemaUtils.canonicalizeDataSchemaForAggregation(queryContext, dataSchema); + assertEquals(canonicalDataSchema, + new DataSchema(new String[]{"sum(plus(col1,col2))"}, new ColumnDataType[]{ColumnDataType.DOUBLE})); + + queryContext = QueryContextConverterUtils.getQueryContext("SELECT SUM(col1 + 1), MIN(col2 + 2) FROM testTable"); + // Intentionally make data schema not matching the string representation of the expression + dataSchema = new DataSchema(new String[]{"sum(col1+1)", "min(col2+2)"}, + new ColumnDataType[]{ColumnDataType.DOUBLE, ColumnDataType.DOUBLE}); + canonicalDataSchema = ReducerDataSchemaUtils.canonicalizeDataSchemaForAggregation(queryContext, dataSchema); + assertEquals(canonicalDataSchema, new DataSchema(new String[]{"sum(plus(col1,'1'))", "min(plus(col2,'2'))"}, + new ColumnDataType[]{ColumnDataType.DOUBLE, ColumnDataType.DOUBLE})); + + queryContext = QueryContextConverterUtils.getQueryContext( + "SELECT MAX(col1 + 1) FILTER(WHERE col3 > 0) - MIN(col2 + 2) FILTER(WHERE col4 > 0) FROM testTable"); + // Intentionally make data schema not matching the string representation of the expression + dataSchema = new DataSchema(new String[]{"max(col1+1)", "min(col2+2)"}, + new ColumnDataType[]{ColumnDataType.DOUBLE, ColumnDataType.DOUBLE}); + canonicalDataSchema = ReducerDataSchemaUtils.canonicalizeDataSchemaForAggregation(queryContext, dataSchema); + assertEquals(canonicalDataSchema, new DataSchema( + new String[]{"max(plus(col1,'1')) FILTER(WHERE col3 > '0')", "min(plus(col2,'2')) FILTER(WHERE col4 > '0')"}, + new ColumnDataType[]{ColumnDataType.DOUBLE, ColumnDataType.DOUBLE})); + } + + @Test + public void testCanonicalizeDataSchemaForGroupBy() { + QueryContext queryContext = QueryContextConverterUtils.getQueryContext( + "SELECT SUM(col1 + col2) FROM testTable GROUP BY col3 + col4 ORDER BY col3 + col4"); + // Intentionally make data schema not matching the string representation of the expression + DataSchema dataSchema = new DataSchema(new String[]{"add(col3+col4)", "sum(col1+col2)"}, + new ColumnDataType[]{ColumnDataType.DOUBLE, ColumnDataType.DOUBLE}); + DataSchema canonicalDataSchema = ReducerDataSchemaUtils.canonicalizeDataSchemaForGroupBy(queryContext, dataSchema); + assertEquals(canonicalDataSchema, new DataSchema(new String[]{"plus(col3,col4)", "sum(plus(col1,col2))"}, + new ColumnDataType[]{ColumnDataType.DOUBLE, ColumnDataType.DOUBLE})); + + queryContext = QueryContextConverterUtils.getQueryContext( + "SELECT SUM(col1 + 1), MIN(col2 + 2), col4 FROM testTable GROUP BY col3, col4"); + // Intentionally make data schema not matching the string representation of the expression + dataSchema = new DataSchema(new String[]{"col3", "col4", "sum(col1+1)", "min(col2+2)"}, + new ColumnDataType[]{ColumnDataType.INT, ColumnDataType.LONG, ColumnDataType.DOUBLE, ColumnDataType.DOUBLE}); + canonicalDataSchema = ReducerDataSchemaUtils.canonicalizeDataSchemaForGroupBy(queryContext, dataSchema); + assertEquals(canonicalDataSchema, + new DataSchema(new String[]{"col3", "col4", "sum(plus(col1,'1'))", "min(plus(col2,'2'))"}, new ColumnDataType[]{ + ColumnDataType.INT, ColumnDataType.LONG, ColumnDataType.DOUBLE, ColumnDataType.DOUBLE + })); + + queryContext = QueryContextConverterUtils.getQueryContext( + "SELECT col3 + col4, MAX(col1 + 1) FILTER(WHERE col3 > 0) - MIN(col2 + 2) FILTER(WHERE col4 > 0) FROM " + + "testTable GROUP BY col3 + col4"); + // Intentionally make data schema not matching the string representation of the expression + dataSchema = new DataSchema(new String[]{"add(col3+col4)", "max(col1+1)", "min(col2+2)"}, + new ColumnDataType[]{ColumnDataType.DOUBLE, ColumnDataType.DOUBLE, ColumnDataType.DOUBLE}); + canonicalDataSchema = ReducerDataSchemaUtils.canonicalizeDataSchemaForGroupBy(queryContext, dataSchema); + assertEquals(canonicalDataSchema, new DataSchema(new String[]{ + "plus(col3,col4)", "max(plus(col1,'1')) FILTER(WHERE col3 > '0')", + "min(plus(col2,'2')) FILTER" + "(WHERE col4 > '0')" + }, new ColumnDataType[]{ColumnDataType.DOUBLE, ColumnDataType.DOUBLE, ColumnDataType.DOUBLE})); + } + + @Test + public void testCanonicalizeDataSchemaForDistinct() { + QueryContext queryContext = + QueryContextConverterUtils.getQueryContext("SELECT DISTINCT col1, col2 + col3 FROM testTable"); + // Intentionally make data schema not matching the string representation of the expression + DataSchema dataSchema = new DataSchema(new String[]{"col1", "add(col2+col3)"}, + new ColumnDataType[]{ColumnDataType.INT, ColumnDataType.DOUBLE}); + DataSchema canonicalDataSchema = ReducerDataSchemaUtils.canonicalizeDataSchemaForDistinct(queryContext, dataSchema); + assertEquals(canonicalDataSchema, new DataSchema(new String[]{"col1", "plus(col2,col3)"}, + new ColumnDataType[]{ColumnDataType.INT, ColumnDataType.DOUBLE})); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/reduce/StreamingReduceServiceTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/reduce/StreamingReduceServiceTest.java index 9946ddfa15ad..a48b12563d15 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/query/reduce/StreamingReduceServiceTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/reduce/StreamingReduceServiceTest.java @@ -56,7 +56,7 @@ public void testThreadExceptionTransfer() { threadPoolService, ImmutableMap.of(routingInstance, mockedResponse), 1000, - mock(BaseReduceService.ExecutionStatsAggregator.class)); + mock(ExecutionStatsAggregator.class)); return null; }, cause -> cause.getMessage().contains(exceptionMessage)) ); @@ -85,7 +85,7 @@ public Void answer(InvocationOnMock invocationOnMock) threadPoolService, ImmutableMap.of(routingInstance, mockedResponse), 10, - mock(BaseReduceService.ExecutionStatsAggregator.class)); + mock(ExecutionStatsAggregator.class)); return null; }, (cause) -> cause instanceof TimeoutException)); diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/request/context/predicate/PredicateTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/request/context/predicate/PredicateTest.java index 6b52d415463d..9a1ee8a07e11 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/query/request/context/predicate/PredicateTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/request/context/predicate/PredicateTest.java @@ -25,6 +25,7 @@ import org.apache.pinot.common.request.context.RequestContextUtils; import org.apache.pinot.common.request.context.predicate.Predicate; import org.apache.pinot.common.request.context.predicate.RangePredicate; +import org.apache.pinot.spi.data.FieldSpec; import org.apache.pinot.sql.parsers.CalciteSqlParser; import org.testng.annotations.Test; @@ -74,7 +75,8 @@ public void testSerDe() { // Non-standard RangePredicate (merged ranges) RangePredicate rangePredicate = - new RangePredicate(ExpressionContext.forIdentifier("foo"), true, "123", false, "456"); + new RangePredicate(ExpressionContext.forIdentifier("foo"), true, "123", false, "456", + FieldSpec.DataType.STRING); String predicateExpression = rangePredicate.toString(); assertEquals(predicateExpression, "(foo >= '123' AND foo < '456')"); Expression thriftExpression = CalciteSqlParser.compileToExpression(predicateExpression); diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/request/context/utils/BrokerRequestToQueryContextConverterTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/request/context/utils/BrokerRequestToQueryContextConverterTest.java index 018bb275f457..ef331ebf59c5 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/query/request/context/utils/BrokerRequestToQueryContextConverterTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/request/context/utils/BrokerRequestToQueryContextConverterTest.java @@ -67,6 +67,7 @@ public void testHardcodedQueries() { assertEquals(selectExpressions.size(), 1); assertEquals(selectExpressions.get(0), ExpressionContext.forIdentifier("*")); assertEquals(selectExpressions.get(0).toString(), "*"); + assertFalse(queryContext.isDistinct()); assertEquals(getAliasCount(queryContext.getAliasList()), 0); assertNull(queryContext.getFilter()); assertNull(queryContext.getGroupByExpressions()); @@ -91,6 +92,7 @@ public void testHardcodedQueries() { new FunctionContext(FunctionContext.Type.AGGREGATION, "count", Collections.singletonList(ExpressionContext.forIdentifier("*"))))); assertEquals(selectExpressions.get(0).toString(), "count(*)"); + assertFalse(queryContext.isDistinct()); assertEquals(getAliasCount(queryContext.getAliasList()), 0); assertNull(queryContext.getFilter()); assertNull(queryContext.getGroupByExpressions()); @@ -115,6 +117,7 @@ public void testHardcodedQueries() { assertEquals(selectExpressions.get(0).toString(), "foo"); assertEquals(selectExpressions.get(1), ExpressionContext.forIdentifier("bar")); assertEquals(selectExpressions.get(1).toString(), "bar"); + assertFalse(queryContext.isDistinct()); assertEquals(getAliasCount(queryContext.getAliasList()), 0); assertNull(queryContext.getFilter()); List orderByExpressions = queryContext.getOrderByExpressions(); @@ -139,12 +142,14 @@ public void testHardcodedQueries() { QueryContext queryContext = QueryContextConverterUtils.getQueryContext(query); assertEquals(queryContext.getTableName(), "testTable"); List selectExpressions = queryContext.getSelectExpressions(); - assertEquals(selectExpressions.size(), 1); - assertEquals(selectExpressions.get(0), ExpressionContext.forFunction( - new FunctionContext(FunctionContext.Type.AGGREGATION, "distinct", - Arrays.asList(ExpressionContext.forIdentifier("foo"), ExpressionContext.forIdentifier("bar"), - ExpressionContext.forIdentifier("foobar"))))); - assertEquals(selectExpressions.get(0).toString(), "distinct(foo,bar,foobar)"); + assertEquals(selectExpressions.size(), 3); + assertEquals(selectExpressions.get(0), ExpressionContext.forIdentifier("foo")); + assertEquals(selectExpressions.get(0).toString(), "foo"); + assertEquals(selectExpressions.get(1), ExpressionContext.forIdentifier("bar")); + assertEquals(selectExpressions.get(1).toString(), "bar"); + assertEquals(selectExpressions.get(2), ExpressionContext.forIdentifier("foobar")); + assertEquals(selectExpressions.get(2).toString(), "foobar"); + assertTrue(queryContext.isDistinct()); assertEquals(getAliasCount(queryContext.getAliasList()), 0); assertNull(queryContext.getFilter()); assertNull(queryContext.getGroupByExpressions()); @@ -179,13 +184,14 @@ public void testHardcodedQueries() { Arrays.asList(ExpressionContext.forIdentifier("foo"), ExpressionContext.forFunction( new FunctionContext(FunctionContext.Type.TRANSFORM, "add", Arrays.asList(ExpressionContext.forIdentifier("bar"), - ExpressionContext.forLiteralContext(FieldSpec.DataType.LONG, Long.valueOf(123))))))))); + ExpressionContext.forLiteral(FieldSpec.DataType.INT, Integer.valueOf(123))))))))); assertEquals(selectExpressions.get(0).toString(), "add(foo,add(bar,'123'))"); assertEquals(selectExpressions.get(1), ExpressionContext.forFunction( new FunctionContext(FunctionContext.Type.TRANSFORM, "sub", - Arrays.asList(ExpressionContext.forLiteralContext(FieldSpec.DataType.STRING, "456"), + Arrays.asList(ExpressionContext.forLiteral(FieldSpec.DataType.STRING, "456"), ExpressionContext.forIdentifier("foobar"))))); assertEquals(selectExpressions.get(1).toString(), "sub('456',foobar)"); + assertFalse(queryContext.isDistinct()); assertEquals(getAliasCount(queryContext.getAliasList()), 0); assertNull(queryContext.getFilter()); assertNull(queryContext.getGroupByExpressions()); @@ -194,7 +200,7 @@ public void testHardcodedQueries() { assertEquals(orderByExpressions.size(), 1); assertEquals(orderByExpressions.get(0), new OrderByExpressionContext(ExpressionContext.forFunction( new FunctionContext(FunctionContext.Type.TRANSFORM, "sub", - Arrays.asList(ExpressionContext.forLiteralContext(FieldSpec.DataType.LONG, Long.valueOf(456)), + Arrays.asList(ExpressionContext.forLiteral(FieldSpec.DataType.INT, Integer.valueOf(456)), ExpressionContext.forIdentifier("foobar")))), true)); assertEquals(orderByExpressions.get(0).toString(), "sub('456',foobar) ASC"); assertNull(queryContext.getHavingFilter()); @@ -213,7 +219,7 @@ public void testHardcodedQueries() { assertEquals(queryContext.getTableName(), "testTable"); List selectExpressions = queryContext.getSelectExpressions(); assertEquals(selectExpressions.size(), 1); - assertEquals(selectExpressions.get(0), ExpressionContext.forLiteralContext(FieldSpec.DataType.BOOLEAN, true)); + assertEquals(selectExpressions.get(0), ExpressionContext.forLiteral(FieldSpec.DataType.BOOLEAN, true)); assertEquals(selectExpressions.get(0).toString(), "'true'"); } @@ -225,22 +231,19 @@ public void testHardcodedQueries() { QueryContext queryContext = QueryContextConverterUtils.getQueryContext(query); assertEquals(queryContext.getTableName(), "testTable"); List selectExpressions = queryContext.getSelectExpressions(); - int numSelectExpressions = selectExpressions.size(); - assertTrue(numSelectExpressions == 1 || numSelectExpressions == 3); - ExpressionContext aggregationExpression = selectExpressions.get(numSelectExpressions - 1); - assertEquals(aggregationExpression, ExpressionContext.forFunction( + assertEquals(selectExpressions.size(), 3); + assertEquals(selectExpressions.get(0), ExpressionContext.forFunction( + new FunctionContext(FunctionContext.Type.TRANSFORM, "sub", + Arrays.asList(ExpressionContext.forIdentifier("foo"), ExpressionContext.forIdentifier("bar"))))); + assertEquals(selectExpressions.get(0).toString(), "sub(foo,bar)"); + assertEquals(selectExpressions.get(1), ExpressionContext.forIdentifier("bar")); + assertEquals(selectExpressions.get(1).toString(), "bar"); + assertEquals(selectExpressions.get(2), ExpressionContext.forFunction( new FunctionContext(FunctionContext.Type.AGGREGATION, "sum", Collections.singletonList( ExpressionContext.forFunction(new FunctionContext(FunctionContext.Type.TRANSFORM, "add", Arrays.asList(ExpressionContext.forIdentifier("foo"), ExpressionContext.forIdentifier("bar")))))))); - assertEquals(aggregationExpression.toString(), "sum(add(foo,bar))"); - if (numSelectExpressions == 3) { - assertEquals(selectExpressions.get(0), ExpressionContext.forFunction( - new FunctionContext(FunctionContext.Type.TRANSFORM, "sub", - Arrays.asList(ExpressionContext.forIdentifier("foo"), ExpressionContext.forIdentifier("bar"))))); - assertEquals(selectExpressions.get(0).toString(), "sub(foo,bar)"); - assertEquals(selectExpressions.get(1), ExpressionContext.forIdentifier("bar")); - assertEquals(selectExpressions.get(1).toString(), "bar"); - } + assertEquals(selectExpressions.get(2).toString(), "sum(add(foo,bar))"); + assertFalse(queryContext.isDistinct()); assertEquals(getAliasCount(queryContext.getAliasList()), 0); assertNull(queryContext.getFilter()); List groupByExpressions = queryContext.getGroupByExpressions(); @@ -284,23 +287,24 @@ public void testHardcodedQueries() { assertEquals(selectExpressions.size(), 1); assertEquals(selectExpressions.get(0), ExpressionContext.forIdentifier("*")); assertEquals(selectExpressions.get(0).toString(), "*"); + assertFalse(queryContext.isDistinct()); assertEquals(getAliasCount(queryContext.getAliasList()), 0); FilterContext filter = queryContext.getFilter(); assertNotNull(filter); assertEquals(filter.getType(), FilterContext.Type.AND); List children = filter.getChildren(); assertEquals(children.size(), 2); - assertEquals(children.get(0), new FilterContext(FilterContext.Type.PREDICATE, null, - new RangePredicate(ExpressionContext.forIdentifier("foo"), false, "15", false, "*"))); + assertEquals(children.get(0), FilterContext.forPredicate( + new RangePredicate(ExpressionContext.forIdentifier("foo"), false, "15", false, "*", FieldSpec.DataType.INT))); FilterContext orFilter = children.get(1); assertEquals(orFilter.getType(), FilterContext.Type.OR); assertEquals(orFilter.getChildren().size(), 2); - assertEquals(orFilter.getChildren().get(0), new FilterContext(FilterContext.Type.PREDICATE, null, - new RangePredicate(ExpressionContext.forFunction(new FunctionContext(FunctionContext.Type.TRANSFORM, "div", + assertEquals(orFilter.getChildren().get(0), FilterContext.forPredicate(new RangePredicate( + ExpressionContext.forFunction(new FunctionContext(FunctionContext.Type.TRANSFORM, "div", Arrays.asList(ExpressionContext.forIdentifier("bar"), ExpressionContext.forIdentifier("foo")))), true, - "10", true, "20"))); - assertEquals(orFilter.getChildren().get(1), new FilterContext(FilterContext.Type.PREDICATE, null, - new TextMatchPredicate(ExpressionContext.forIdentifier("foobar"), "potato"))); + "10", true, "20", FieldSpec.DataType.INT))); + assertEquals(orFilter.getChildren().get(1), + FilterContext.forPredicate(new TextMatchPredicate(ExpressionContext.forIdentifier("foobar"), "potato"))); assertEquals(filter.toString(), "(foo > '15' AND (div(bar,foo) BETWEEN '10' AND '20' OR text_match(foobar,'potato')))"); assertNull(queryContext.getGroupByExpressions()); @@ -317,7 +321,8 @@ public void testHardcodedQueries() { // Alias // NOTE: All the references to the alias should already be converted to the original expressions. { - String query = "SELECT SUM(foo) AS a, bar AS b FROM testTable WHERE b IN (5, 10, 15) GROUP BY b ORDER BY a DESC"; + String query = + "SELECT SUM(foo) AS a, bar AS b FROM testTable WHERE bar IN (5, 10, 15) GROUP BY b ORDER BY a DESC"; QueryContext queryContext = QueryContextConverterUtils.getQueryContext(query); assertEquals(queryContext.getTableName(), "testTable"); List selectExpressions = queryContext.getSelectExpressions(); @@ -328,13 +333,14 @@ public void testHardcodedQueries() { assertEquals(selectExpressions.get(0).toString(), "sum(foo)"); assertEquals(selectExpressions.get(1), ExpressionContext.forIdentifier("bar")); assertEquals(selectExpressions.get(1).toString(), "bar"); + assertFalse(queryContext.isDistinct()); List aliasList = queryContext.getAliasList(); assertEquals(aliasList.size(), 2); assertEquals(aliasList.get(0), "a"); assertEquals(aliasList.get(1), "b"); FilterContext filter = queryContext.getFilter(); assertNotNull(filter); - assertEquals(filter, new FilterContext(FilterContext.Type.PREDICATE, null, + assertEquals(filter, FilterContext.forPredicate( new InPredicate(ExpressionContext.forIdentifier("bar"), Arrays.asList("5", "10", "15")))); assertEquals(filter.toString(), "bar IN ('5','10','15')"); List groupByExpressions = queryContext.getGroupByExpressions(); @@ -371,6 +377,7 @@ public void testHardcodedQueries() { assertEquals(selectExpressions.get(0).toString(), "sum(foo)"); assertEquals(selectExpressions.get(1), ExpressionContext.forIdentifier("bar")); assertEquals(selectExpressions.get(1).toString(), "bar"); + assertFalse(queryContext.isDistinct()); assertEquals(getAliasCount(queryContext.getAliasList()), 0); assertNull(queryContext.getFilter()); List groupByExpressions = queryContext.getGroupByExpressions(); @@ -381,8 +388,8 @@ public void testHardcodedQueries() { assertNull(queryContext.getOrderByExpressions()); FilterContext havingFilter = queryContext.getHavingFilter(); assertNotNull(havingFilter); - assertEquals(havingFilter, new FilterContext(FilterContext.Type.PREDICATE, null, new InPredicate( - ExpressionContext.forFunction(new FunctionContext(FunctionContext.Type.AGGREGATION, "sum", + assertEquals(havingFilter, FilterContext.forPredicate(new InPredicate(ExpressionContext.forFunction( + new FunctionContext(FunctionContext.Type.AGGREGATION, "sum", Collections.singletonList(ExpressionContext.forIdentifier("foo")))), Arrays.asList("5", "10", "15")))); assertEquals(havingFilter.toString(), "sum(foo) IN ('5','10','15')"); assertEquals(queryContext.getLimit(), 10); @@ -473,21 +480,21 @@ public void testHardcodedQueries() { assertEquals(aggregationFunctions[3].getResultColumnName(), "sum(col4)"); assertEquals(aggregationFunctions[4].getResultColumnName(), "max(col4)"); assertEquals(aggregationFunctions[5].getResultColumnName(), "max(col1)"); - Map aggregationFunctionIndexMap = queryContext.getAggregationFunctionIndexMap(); - assertNotNull(aggregationFunctionIndexMap); - assertEquals(aggregationFunctionIndexMap.size(), 6); - assertEquals((int) aggregationFunctionIndexMap.get(new FunctionContext(FunctionContext.Type.AGGREGATION, "sum", - Collections.singletonList(ExpressionContext.forIdentifier("col1")))), 0); - assertEquals((int) aggregationFunctionIndexMap.get(new FunctionContext(FunctionContext.Type.AGGREGATION, "max", - Collections.singletonList(ExpressionContext.forIdentifier("col2")))), 1); - assertEquals((int) aggregationFunctionIndexMap.get(new FunctionContext(FunctionContext.Type.AGGREGATION, "min", - Collections.singletonList(ExpressionContext.forIdentifier("col2")))), 2); - assertEquals((int) aggregationFunctionIndexMap.get(new FunctionContext(FunctionContext.Type.AGGREGATION, "sum", - Collections.singletonList(ExpressionContext.forIdentifier("col4")))), 3); - assertEquals((int) aggregationFunctionIndexMap.get(new FunctionContext(FunctionContext.Type.AGGREGATION, "max", - Collections.singletonList(ExpressionContext.forIdentifier("col4")))), 4); - assertEquals((int) aggregationFunctionIndexMap.get(new FunctionContext(FunctionContext.Type.AGGREGATION, "max", - Collections.singletonList(ExpressionContext.forIdentifier("col1")))), 5); + Map, Integer> indexMap = queryContext.getFilteredAggregationsIndexMap(); + assertNotNull(indexMap); + assertEquals(indexMap.size(), 6); + assertEquals((int) indexMap.get(Pair.of(new FunctionContext(FunctionContext.Type.AGGREGATION, "sum", + Collections.singletonList(ExpressionContext.forIdentifier("col1"))), null)), 0); + assertEquals((int) indexMap.get(Pair.of(new FunctionContext(FunctionContext.Type.AGGREGATION, "max", + Collections.singletonList(ExpressionContext.forIdentifier("col2"))), null)), 1); + assertEquals((int) indexMap.get(Pair.of(new FunctionContext(FunctionContext.Type.AGGREGATION, "min", + Collections.singletonList(ExpressionContext.forIdentifier("col2"))), null)), 2); + assertEquals((int) indexMap.get(Pair.of(new FunctionContext(FunctionContext.Type.AGGREGATION, "sum", + Collections.singletonList(ExpressionContext.forIdentifier("col4"))), null)), 3); + assertEquals((int) indexMap.get(Pair.of(new FunctionContext(FunctionContext.Type.AGGREGATION, "max", + Collections.singletonList(ExpressionContext.forIdentifier("col4"))), null)), 4); + assertEquals((int) indexMap.get(Pair.of(new FunctionContext(FunctionContext.Type.AGGREGATION, "max", + Collections.singletonList(ExpressionContext.forIdentifier("col1"))), null)), 5); } // DistinctCountThetaSketch (string literal and escape quote) @@ -500,12 +507,10 @@ public void testHardcodedQueries() { assertEquals(function.getFunctionName(), "distinctcountthetasketch"); List arguments = function.getArguments(); assertEquals(arguments.get(0), ExpressionContext.forIdentifier("foo")); - assertEquals(arguments.get(1), - ExpressionContext.forLiteralContext(FieldSpec.DataType.STRING, "nominalEntries=1000")); - assertEquals(arguments.get(2), ExpressionContext.forLiteralContext(FieldSpec.DataType.STRING, "bar='a'")); - assertEquals(arguments.get(3), ExpressionContext.forLiteralContext(FieldSpec.DataType.STRING, "bar='b'")); - assertEquals(arguments.get(4), - ExpressionContext.forLiteralContext(FieldSpec.DataType.STRING, "SET_INTERSECT($1, $2)")); + assertEquals(arguments.get(1), ExpressionContext.forLiteral(FieldSpec.DataType.STRING, "nominalEntries=1000")); + assertEquals(arguments.get(2), ExpressionContext.forLiteral(FieldSpec.DataType.STRING, "bar='a'")); + assertEquals(arguments.get(3), ExpressionContext.forLiteral(FieldSpec.DataType.STRING, "bar='b'")); + assertEquals(arguments.get(4), ExpressionContext.forLiteral(FieldSpec.DataType.STRING, "SET_INTERSECT($1, $2)")); assertEquals(queryContext.getColumns(), new HashSet<>(Arrays.asList("foo", "bar"))); assertFalse(QueryContextUtils.isSelectionQuery(queryContext)); assertTrue(QueryContextUtils.isAggregationQuery(queryContext)); @@ -535,21 +540,10 @@ public void testFilteredAggregations() { assertTrue(filteredAggregationFunctions.get(1).getLeft() instanceof CountAggregationFunction); assertEquals(filteredAggregationFunctions.get(1).getRight().toString(), "foo < '6'"); - Map aggregationIndexMap = queryContext.getAggregationFunctionIndexMap(); - assertNotNull(aggregationIndexMap); - assertEquals(aggregationIndexMap.size(), 1); - for (Map.Entry entry : aggregationIndexMap.entrySet()) { - FunctionContext aggregation = entry.getKey(); - int index = entry.getValue(); - assertEquals(aggregation.toString(), "count(*)"); - assertTrue(index == 0 || index == 1); - } - - Map, Integer> filteredAggregationsIndexMap = - queryContext.getFilteredAggregationsIndexMap(); - assertNotNull(filteredAggregationsIndexMap); - assertEquals(filteredAggregationsIndexMap.size(), 2); - for (Map.Entry, Integer> entry : filteredAggregationsIndexMap.entrySet()) { + Map, Integer> indexMap = queryContext.getFilteredAggregationsIndexMap(); + assertNotNull(indexMap); + assertEquals(indexMap.size(), 2); + for (Map.Entry, Integer> entry : indexMap.entrySet()) { Pair pair = entry.getKey(); FunctionContext aggregation = pair.getLeft(); FilterContext filter = pair.getRight(); @@ -595,32 +589,10 @@ public void testFilteredAggregations() { assertTrue(filteredAggregationFunctions.get(3).getLeft() instanceof MinAggregationFunction); assertEquals(filteredAggregationFunctions.get(3).getRight().toString(), "salary > '50000'"); - Map aggregationIndexMap = queryContext.getAggregationFunctionIndexMap(); - assertNotNull(aggregationIndexMap); - assertEquals(aggregationIndexMap.size(), 2); - for (Map.Entry entry : aggregationIndexMap.entrySet()) { - FunctionContext aggregation = entry.getKey(); - int index = entry.getValue(); - switch (index) { - case 0: - case 1: - assertEquals(aggregation.toString(), "sum(salary)"); - break; - case 2: - case 3: - assertEquals(aggregation.toString(), "min(salary)"); - break; - default: - fail(); - break; - } - } - - Map, Integer> filteredAggregationsIndexMap = - queryContext.getFilteredAggregationsIndexMap(); - assertNotNull(filteredAggregationsIndexMap); - assertEquals(filteredAggregationsIndexMap.size(), 4); - for (Map.Entry, Integer> entry : filteredAggregationsIndexMap.entrySet()) { + Map, Integer> indexMap = queryContext.getFilteredAggregationsIndexMap(); + assertNotNull(indexMap); + assertEquals(indexMap.size(), 4); + for (Map.Entry, Integer> entry : indexMap.entrySet()) { Pair pair = entry.getKey(); FunctionContext aggregation = pair.getLeft(); FilterContext filter = pair.getRight(); @@ -649,4 +621,193 @@ public void testFilteredAggregations() { } } } + + @Test + public void testDeduplicateOrderByExpressions() { + String query = "SELECT name FROM employees ORDER BY name DESC NULLS LAST, name ASC"; + + QueryContext queryContext = QueryContextConverterUtils.getQueryContext(query); + + assertNotNull(queryContext.getOrderByExpressions()); + assertEquals(queryContext.getOrderByExpressions().size(), 1); + } + + @Test + public void testRemoveOrderByFunctions() { + String query = "SELECT A FROM testTable ORDER BY datetrunc(A) DESC NULLS LAST"; + + QueryContext queryContext = QueryContextConverterUtils.getQueryContext(query); + + assertNotNull(queryContext.getOrderByExpressions()); + List orderByExpressionContexts = queryContext.getOrderByExpressions(); + assertEquals(orderByExpressionContexts.size(), 1); + OrderByExpressionContext orderByExpressionContext = orderByExpressionContexts.get(0); + assertFalse(orderByExpressionContext.isAsc()); + assertTrue(orderByExpressionContext.isNullsLast()); + assertEquals(orderByExpressionContext.getExpression().getFunction().getFunctionName(), "datetrunc"); + assertEquals(orderByExpressionContext.getExpression().getFunction().getArguments().get(0).getIdentifier(), "A"); + } + + @Test + public void testOrderByDefault() { + String query = "SELECT A FROM testTable ORDER BY A"; + + QueryContext queryContext = QueryContextConverterUtils.getQueryContext(query); + + assertNotNull(queryContext.getOrderByExpressions()); + List orderByExpressionContexts = queryContext.getOrderByExpressions(); + assertEquals(orderByExpressionContexts.size(), 1); + OrderByExpressionContext orderByExpressionContext = orderByExpressionContexts.get(0); + assertTrue(orderByExpressionContext.isAsc()); + assertTrue(orderByExpressionContext.isNullsLast()); + } + + @Test + public void testOrderByNullsLast() { + String query = "SELECT A FROM testTable ORDER BY A NULLS LAST"; + + QueryContext queryContext = QueryContextConverterUtils.getQueryContext(query); + + assertNotNull(queryContext.getOrderByExpressions()); + List orderByExpressionContexts = queryContext.getOrderByExpressions(); + assertEquals(orderByExpressionContexts.size(), 1); + OrderByExpressionContext orderByExpressionContext = orderByExpressionContexts.get(0); + assertTrue(orderByExpressionContext.isAsc()); + assertTrue(orderByExpressionContext.isNullsLast()); + } + + @Test + public void testOrderByNullsFirst() { + String query = "SELECT A FROM testTable ORDER BY A NULLS FIRST"; + + QueryContext queryContext = QueryContextConverterUtils.getQueryContext(query); + + assertNotNull(queryContext.getOrderByExpressions()); + List orderByExpressionContexts = queryContext.getOrderByExpressions(); + assertEquals(orderByExpressionContexts.size(), 1); + OrderByExpressionContext orderByExpressionContext = orderByExpressionContexts.get(0); + assertTrue(orderByExpressionContext.isAsc()); + assertFalse(orderByExpressionContext.isNullsLast()); + } + + @Test + public void testOrderByAscNullsFirst() { + String query = "SELECT A FROM testTable ORDER BY A ASC NULLS FIRST"; + + QueryContext queryContext = QueryContextConverterUtils.getQueryContext(query); + + assertNotNull(queryContext.getOrderByExpressions()); + List orderByExpressionContexts = queryContext.getOrderByExpressions(); + assertEquals(orderByExpressionContexts.size(), 1); + OrderByExpressionContext orderByExpressionContext = orderByExpressionContexts.get(0); + assertTrue(orderByExpressionContext.isAsc()); + assertFalse(orderByExpressionContext.isNullsLast()); + } + + @Test + public void testOrderByAscNullsLast() { + String query = "SELECT A FROM testTable ORDER BY A ASC NULLS LAST"; + + QueryContext queryContext = QueryContextConverterUtils.getQueryContext(query); + + assertNotNull(queryContext.getOrderByExpressions()); + List orderByExpressionContexts = queryContext.getOrderByExpressions(); + assertEquals(orderByExpressionContexts.size(), 1); + OrderByExpressionContext orderByExpressionContext = orderByExpressionContexts.get(0); + assertTrue(orderByExpressionContext.isAsc()); + assertTrue(orderByExpressionContext.isNullsLast()); + } + + @Test + public void testOrderByDescNullsFirst() { + String query = "SELECT A FROM testTable ORDER BY A DESC NULLS FIRST"; + + QueryContext queryContext = QueryContextConverterUtils.getQueryContext(query); + + assertNotNull(queryContext.getOrderByExpressions()); + List orderByExpressionContexts = queryContext.getOrderByExpressions(); + assertEquals(orderByExpressionContexts.size(), 1); + OrderByExpressionContext orderByExpressionContext = orderByExpressionContexts.get(0); + assertFalse(orderByExpressionContext.isAsc()); + assertFalse(orderByExpressionContext.isNullsLast()); + } + + @Test + public void testOrderByDescNullsLast() { + String query = "SELECT A FROM testTable ORDER BY A DESC NULLS LAST"; + + QueryContext queryContext = QueryContextConverterUtils.getQueryContext(query); + + assertNotNull(queryContext.getOrderByExpressions()); + List orderByExpressionContexts = queryContext.getOrderByExpressions(); + assertEquals(orderByExpressionContexts.size(), 1); + OrderByExpressionContext orderByExpressionContext = orderByExpressionContexts.get(0); + assertFalse(orderByExpressionContext.isAsc()); + assertTrue(orderByExpressionContext.isNullsLast()); + } + + @Test + public void testDistinctOrderByNullsLast() { + String query = "SELECT DISTINCT A FROM testTable ORDER BY A NULLS LAST"; + + QueryContext queryContext = QueryContextConverterUtils.getQueryContext(query); + + assertNotNull(queryContext.getOrderByExpressions()); + List orderByExpressionContexts = queryContext.getOrderByExpressions(); + assertEquals(orderByExpressionContexts.size(), 1); + OrderByExpressionContext orderByExpressionContext = orderByExpressionContexts.get(0); + assertTrue(orderByExpressionContext.isAsc()); + assertTrue(orderByExpressionContext.isNullsLast()); + } + + @Test + public void testConstantFilter() { + String query = "SELECT * FROM testTable WHERE true"; + QueryContext queryContext = QueryContextConverterUtils.getQueryContext(query); + assertNull(queryContext.getFilter()); + + query = "SELECT * FROM testTable WHERE false"; + queryContext = QueryContextConverterUtils.getQueryContext(query); + assertSame(queryContext.getFilter(), FilterContext.CONSTANT_FALSE); + + query = "SELECT * FROM testTable WHERE 'true'"; + queryContext = QueryContextConverterUtils.getQueryContext(query); + assertNull(queryContext.getFilter()); + + query = "SELECT * FROM testTable WHERE 'false'"; + queryContext = QueryContextConverterUtils.getQueryContext(query); + assertSame(queryContext.getFilter(), FilterContext.CONSTANT_FALSE); + + query = "SELECT * FROM testTable WHERE 1 = 1"; + queryContext = QueryContextConverterUtils.getQueryContext(query); + assertNull(queryContext.getFilter()); + + query = "SELECT * FROM testTable WHERE 1 = 0"; + queryContext = QueryContextConverterUtils.getQueryContext(query); + assertSame(queryContext.getFilter(), FilterContext.CONSTANT_FALSE); + + query = "SELECT * FROM testTable WHERE 1 = 1 AND 2 = 2"; + queryContext = QueryContextConverterUtils.getQueryContext(query); + assertNull(queryContext.getFilter()); + + query = "SELECT * FROM testTable WHERE 1 = 1 AND 2 = 3"; + queryContext = QueryContextConverterUtils.getQueryContext(query); + assertSame(queryContext.getFilter(), FilterContext.CONSTANT_FALSE); + + query = "SELECT * FROM testTable WHERE 1 = 1 OR 2 = 3"; + queryContext = QueryContextConverterUtils.getQueryContext(query); + assertNull(queryContext.getFilter()); + + query = "SELECT * FROM testTable WHERE 1 = 0 OR 2 = 3"; + queryContext = QueryContextConverterUtils.getQueryContext(query); + assertSame(queryContext.getFilter(), FilterContext.CONSTANT_FALSE); + + query = "SELECT * FROM testTable WHERE NOT 1 = 1"; + queryContext = QueryContextConverterUtils.getQueryContext(query); + assertSame(queryContext.getFilter(), FilterContext.CONSTANT_FALSE); + + query = "SELECT * FROM testTable WHERE NOT 1 = 0"; + queryContext = QueryContextConverterUtils.getQueryContext(query); + assertNull(queryContext.getFilter()); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/scheduler/PrioritySchedulerTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/scheduler/PrioritySchedulerTest.java index 01e5920aa87f..caeb453c5533 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/query/scheduler/PrioritySchedulerTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/scheduler/PrioritySchedulerTest.java @@ -20,7 +20,6 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.Uninterruptibles; -import io.grpc.stub.StreamObserver; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -37,16 +36,16 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.LongAccumulator; import javax.annotation.Nullable; -import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.commons.configuration2.PropertiesConfiguration; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.datatable.DataTable.MetadataKey; import org.apache.pinot.common.datatable.DataTableFactory; import org.apache.pinot.common.exception.QueryException; import org.apache.pinot.common.metrics.ServerMetrics; -import org.apache.pinot.common.proto.Server; import org.apache.pinot.core.data.manager.InstanceDataManager; import org.apache.pinot.core.operator.blocks.InstanceResponseBlock; import org.apache.pinot.core.query.executor.QueryExecutor; +import org.apache.pinot.core.query.executor.ResultsBlockStreamer; import org.apache.pinot.core.query.request.ServerQueryRequest; import org.apache.pinot.core.query.scheduler.resources.PolicyBasedResourceManager; import org.apache.pinot.core.query.scheduler.resources.ResourceLimitPolicy; @@ -298,7 +297,7 @@ public void shutDown() { @Override public InstanceResponseBlock execute(ServerQueryRequest queryRequest, ExecutorService executorService, - @Nullable StreamObserver responseObserver) { + @Nullable ResultsBlockStreamer streamer) { if (_useBarrier) { try { _startupBarrier.await(); diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/selection/SelectionOperatorServiceTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/selection/SelectionOperatorServiceTest.java index df95b6a5e164..c0305f0a7872 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/query/selection/SelectionOperatorServiceTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/selection/SelectionOperatorServiceTest.java @@ -18,18 +18,22 @@ */ package org.apache.pinot.core.query.selection; +import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; import java.util.List; -import java.util.PriorityQueue; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.request.context.utils.QueryContextConverterUtils; +import org.apache.pinot.core.query.utils.OrderByComparatorFactory; import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.spi.utils.BytesUtils; import org.testng.annotations.BeforeClass; @@ -38,6 +42,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertSame; import static org.testng.Assert.assertTrue; @@ -48,45 +53,30 @@ */ public class SelectionOperatorServiceTest { private final String[] _columnNames = { - "int", "long", "float", "double", "string", "int_array", "long_array", "float_array", "double_array", - "string_array", "bytes" + "int", "long", "float", "double", "big_decimal", "string", "bytes", "int_array", "long_array", "float_array", + "double_array", "string_array" }; - private final DataSchema.ColumnDataType[] _columnDataTypes = { - DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.LONG, DataSchema.ColumnDataType.FLOAT, - DataSchema.ColumnDataType.DOUBLE, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT_ARRAY, - DataSchema.ColumnDataType.LONG_ARRAY, DataSchema.ColumnDataType.FLOAT_ARRAY, - DataSchema.ColumnDataType.DOUBLE_ARRAY, DataSchema.ColumnDataType.STRING_ARRAY, DataSchema.ColumnDataType.BYTES + private final ColumnDataType[] _columnDataTypes = { + ColumnDataType.INT, ColumnDataType.LONG, ColumnDataType.FLOAT, ColumnDataType.DOUBLE, + ColumnDataType.BIG_DECIMAL, ColumnDataType.STRING, ColumnDataType.BYTES, ColumnDataType.INT_ARRAY, + ColumnDataType.LONG_ARRAY, ColumnDataType.FLOAT_ARRAY, ColumnDataType.DOUBLE_ARRAY, ColumnDataType.STRING_ARRAY }; private final DataSchema _dataSchema = new DataSchema(_columnNames, _columnDataTypes); - private final DataSchema.ColumnDataType[] _compatibleColumnDataTypes = { - DataSchema.ColumnDataType.LONG, DataSchema.ColumnDataType.FLOAT, DataSchema.ColumnDataType.DOUBLE, - DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.LONG_ARRAY, - DataSchema.ColumnDataType.FLOAT_ARRAY, DataSchema.ColumnDataType.DOUBLE_ARRAY, - DataSchema.ColumnDataType.INT_ARRAY, DataSchema.ColumnDataType.STRING_ARRAY, DataSchema.ColumnDataType.BYTES - }; - private final DataSchema _compatibleDataSchema = new DataSchema(_columnNames, _compatibleColumnDataTypes); - private final DataSchema.ColumnDataType[] _upgradedColumnDataTypes = new DataSchema.ColumnDataType[]{ - DataSchema.ColumnDataType.LONG, DataSchema.ColumnDataType.DOUBLE, DataSchema.ColumnDataType.DOUBLE, - DataSchema.ColumnDataType.DOUBLE, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.LONG_ARRAY, - DataSchema.ColumnDataType.DOUBLE_ARRAY, DataSchema.ColumnDataType.DOUBLE_ARRAY, - DataSchema.ColumnDataType.DOUBLE_ARRAY, DataSchema.ColumnDataType.STRING_ARRAY, DataSchema.ColumnDataType.BYTES - }; - private final DataSchema _upgradedDataSchema = new DataSchema(_columnNames, _upgradedColumnDataTypes); private final Object[] _row1 = { - 0, 1L, 2.0F, 3.0, "4", new int[]{5}, new long[]{6L}, new float[]{7.0F}, new double[]{8.0}, new String[]{"9"}, - BytesUtils.toByteArray("1020") + 0, 1L, 2.0F, 3.0, new BigDecimal(4), "5", BytesUtils.toByteArray( + "0606"), new int[]{7}, new long[]{8L}, new float[]{9.0F}, new double[]{10.0}, new String[]{"11"} }; private final Object[] _row2 = { - 10, 11L, 12.0F, 13.0, "14", new int[]{15}, new long[]{16L}, new float[]{17.0F}, new double[]{18.0}, - new String[]{"19"}, BytesUtils.toByteArray("3040") + 10, 11L, 12.0F, 13.0, new BigDecimal(14), "15", BytesUtils.toByteArray( + "1616"), new int[]{17}, new long[]{18L}, new float[]{19.0F}, new double[]{20.0}, new String[]{"21"} }; - private final Object[] _compatibleRow1 = { - 1L, 2.0F, 3.0, 4, "5", new long[]{6L}, new float[]{7.0F}, new double[]{8.0}, new int[]{9}, new String[]{"10"}, - BytesUtils.toByteArray("5060") + private final Object[] _row3 = { + 1, 2L, 3.0F, 4.0, new BigDecimal(5), "6", BytesUtils.toByteArray( + "0707"), new int[]{8}, new long[]{9L}, new float[]{10.0F}, new double[]{11.0}, new String[]{"12"} }; - private final Object[] _compatibleRow2 = { - 11L, 12.0F, 13.0, 14, "15", new long[]{16L}, new float[]{17.0F}, new double[]{18.0}, new int[]{19}, - new String[]{"20"}, BytesUtils.toByteArray("7000") + private final Object[] _row4 = { + 11, 12L, 13.0F, 14.0, new BigDecimal(15), "16", BytesUtils.toByteArray( + "1717"), new int[]{18}, new long[]{19L}, new float[]{20.0F}, new double[]{21.0}, new String[]{"22"} }; private QueryContext _queryContext; @@ -167,59 +157,53 @@ public void testGetSelectionColumns() { } @Test - public void testCompatibleRowsMergeWithoutOrdering() { - ArrayList mergedRows = new ArrayList<>(2); + public void testRowsMergeWithoutOrdering() { + List mergedRows = new ArrayList<>(2); mergedRows.add(_row1); mergedRows.add(_row2); - Collection rowsToMerge = new ArrayList<>(2); - rowsToMerge.add(_compatibleRow1); - rowsToMerge.add(_compatibleRow2); - SelectionOperatorUtils.mergeWithoutOrdering(mergedRows, rowsToMerge, 3); + SelectionResultsBlock mergedBlock = new SelectionResultsBlock(_dataSchema, mergedRows, _queryContext); + List rowsToMerge = new ArrayList<>(2); + rowsToMerge.add(_row3); + rowsToMerge.add(_row4); + SelectionResultsBlock blockToMerge = new SelectionResultsBlock(_dataSchema, rowsToMerge, _queryContext); + SelectionOperatorUtils.mergeWithoutOrdering(mergedBlock, blockToMerge, 3); assertEquals(mergedRows.size(), 3); assertSame(mergedRows.get(0), _row1); assertSame(mergedRows.get(1), _row2); - assertSame(mergedRows.get(2), _compatibleRow1); + assertSame(mergedRows.get(2), _row3); } @Test - public void testCompatibleRowsMergeWithOrdering() { - SelectionOperatorService selectionOperatorService = new SelectionOperatorService(_queryContext, _dataSchema); - PriorityQueue mergedRows = selectionOperatorService.getRows(); + public void testRowsMergeWithOrdering() { + assertNotNull(_queryContext.getOrderByExpressions()); + Comparator comparator = + OrderByComparatorFactory.getComparator(_queryContext.getOrderByExpressions(), false); int maxNumRows = _queryContext.getOffset() + _queryContext.getLimit(); - Collection rowsToMerge1 = new ArrayList<>(2); - rowsToMerge1.add(_row1); - rowsToMerge1.add(_row2); - SelectionOperatorUtils.mergeWithOrdering(mergedRows, rowsToMerge1, maxNumRows); - Collection rowsToMerge2 = new ArrayList<>(2); - rowsToMerge2.add(_compatibleRow1); - rowsToMerge2.add(_compatibleRow2); - SelectionOperatorUtils.mergeWithOrdering(mergedRows, rowsToMerge2, maxNumRows); + SelectionResultsBlock mergedBlock = + new SelectionResultsBlock(_dataSchema, Collections.emptyList(), comparator, _queryContext); + List rowsToMerge1 = Arrays.asList(_row2, _row1); + SelectionResultsBlock blockToMerge1 = + new SelectionResultsBlock(_dataSchema, rowsToMerge1, comparator, _queryContext); + SelectionOperatorUtils.mergeWithOrdering(mergedBlock, blockToMerge1, maxNumRows); + List rowsToMerge2 = Arrays.asList(_row4, _row3); + SelectionResultsBlock blockToMerge2 = + new SelectionResultsBlock(_dataSchema, rowsToMerge2, comparator, _queryContext); + SelectionOperatorUtils.mergeWithOrdering(mergedBlock, blockToMerge2, maxNumRows); + List mergedRows = mergedBlock.getRows(); assertEquals(mergedRows.size(), 3); - assertSame(mergedRows.poll(), _compatibleRow1); - assertSame(mergedRows.poll(), _row2); - assertSame(mergedRows.poll(), _compatibleRow2); + assertSame(mergedRows.get(0), _row4); + assertSame(mergedRows.get(1), _row2); + assertSame(mergedRows.get(2), _row3); } @Test - public void testCompatibleRowsDataTableTransformation() + public void testExtractRowFromDataTable() throws Exception { Collection rows = new ArrayList<>(2); rows.add(_row1); - rows.add(_compatibleRow1); - DataSchema dataSchema = _dataSchema.clone(); - assertTrue(dataSchema.isTypeCompatibleWith(_compatibleDataSchema)); - dataSchema = DataSchema.upgradeToCover(dataSchema, _compatibleDataSchema); - assertEquals(dataSchema, _upgradedDataSchema); - DataTable dataTable = SelectionOperatorUtils.getDataTableFromRows(rows, dataSchema, false); - Object[] expectedRow1 = { - 0L, 1.0, 2.0, 3.0, "4", new long[]{5L}, new double[]{6.0}, new double[]{7.0}, new double[]{8.0}, - new String[]{"9"}, BytesUtils.toByteArray("1020") - }; - Object[] expectedCompatibleRow1 = { - 1L, 2.0, 3.0, 4.0, "5", new long[]{6L}, new double[]{7.0}, new double[]{8.0}, new double[]{9.0}, - new String[]{"10"}, BytesUtils.toByteArray("5060") - }; - assertTrue(Arrays.deepEquals(SelectionOperatorUtils.extractRowFromDataTable(dataTable, 0), expectedRow1)); - assertTrue(Arrays.deepEquals(SelectionOperatorUtils.extractRowFromDataTable(dataTable, 1), expectedCompatibleRow1)); + rows.add(_row2); + DataTable dataTable = SelectionOperatorUtils.getDataTableFromRows(rows, _dataSchema, false); + assertTrue(Arrays.deepEquals(SelectionOperatorUtils.extractRowFromDataTable(dataTable, 0), _row1)); + assertTrue(Arrays.deepEquals(SelectionOperatorUtils.extractRowFromDataTable(dataTable, 1), _row2)); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/selection/SelectionOperatorUtilsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/selection/SelectionOperatorUtilsTest.java new file mode 100644 index 000000000000..196f4dd168e9 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/selection/SelectionOperatorUtilsTest.java @@ -0,0 +1,210 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.query.selection; + +import org.apache.commons.lang3.tuple.Pair; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; +import org.apache.pinot.core.query.request.context.QueryContext; +import org.apache.pinot.core.query.request.context.utils.QueryContextConverterUtils; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; + + +public class SelectionOperatorUtilsTest { + + @Test + public void testGetResultTableColumnIndices() { + // Select * without order-by + QueryContext queryContext = QueryContextConverterUtils.getQueryContext("SELECT * FROM testTable"); + DataSchema dataSchema = new DataSchema(new String[]{"col1", "col2", "col3"}, new ColumnDataType[]{ + ColumnDataType.INT, ColumnDataType.LONG, ColumnDataType.DOUBLE + }); + Pair pair = + SelectionOperatorUtils.getResultTableDataSchemaAndColumnIndices(queryContext, dataSchema); + assertEquals(pair.getLeft(), new DataSchema(new String[]{"col1", "col2", "col3"}, new ColumnDataType[]{ + ColumnDataType.INT, ColumnDataType.LONG, ColumnDataType.DOUBLE + })); + assertEquals(pair.getRight(), new int[]{0, 1, 2}); + + // Select * without order-by, all the segments are pruned on the server side + dataSchema = new DataSchema(new String[]{"*"}, new ColumnDataType[]{ColumnDataType.STRING}); + pair = SelectionOperatorUtils.getResultTableDataSchemaAndColumnIndices(queryContext, dataSchema); + assertEquals(pair.getLeft(), new DataSchema(new String[]{"*"}, new ColumnDataType[]{ColumnDataType.STRING})); + + // Select * with order-by but LIMIT 0 + queryContext = QueryContextConverterUtils.getQueryContext("SELECT * FROM testTable ORDER BY col1 LIMIT 0"); + dataSchema = new DataSchema(new String[]{"col1", "col2", "col3"}, new ColumnDataType[]{ + ColumnDataType.INT, ColumnDataType.LONG, ColumnDataType.DOUBLE + }); + pair = SelectionOperatorUtils.getResultTableDataSchemaAndColumnIndices(queryContext, dataSchema); + assertEquals(pair.getLeft(), new DataSchema(new String[]{"col1", "col2", "col3"}, new ColumnDataType[]{ + ColumnDataType.INT, ColumnDataType.LONG, ColumnDataType.DOUBLE + })); + assertEquals(pair.getRight(), new int[]{0, 1, 2}); + + // Select * with order-by but LIMIT 0, all the segments are pruned on the server side + dataSchema = new DataSchema(new String[]{"*"}, new ColumnDataType[]{ColumnDataType.STRING}); + pair = SelectionOperatorUtils.getResultTableDataSchemaAndColumnIndices(queryContext, dataSchema); + assertEquals(pair.getLeft(), new DataSchema(new String[]{"*"}, new ColumnDataType[]{ColumnDataType.STRING})); + + // Select columns without order-by + queryContext = QueryContextConverterUtils.getQueryContext("SELECT col1 + 1, col2 + 2 FROM testTable"); + // Intentionally make data schema not matching the string representation of the expression + dataSchema = new DataSchema(new String[]{"add(col1+1)", "add(col2+2)"}, new ColumnDataType[]{ + ColumnDataType.DOUBLE, ColumnDataType.DOUBLE + }); + pair = SelectionOperatorUtils.getResultTableDataSchemaAndColumnIndices(queryContext, dataSchema); + assertEquals(pair.getLeft(), new DataSchema(new String[]{"plus(col1,'1')", "plus(col2,'2')"}, new ColumnDataType[]{ + ColumnDataType.DOUBLE, ColumnDataType.DOUBLE + })); + assertEquals(pair.getRight(), new int[]{0, 1}); + + // Select columns without order-by, all the segments are pruned on the server side + // Intentionally make data schema not matching the string representation of the expression + dataSchema = new DataSchema(new String[]{"add(col1+1)", "add(col2+2)"}, new ColumnDataType[]{ + ColumnDataType.STRING, ColumnDataType.STRING + }); + pair = SelectionOperatorUtils.getResultTableDataSchemaAndColumnIndices(queryContext, dataSchema); + assertEquals(pair.getLeft(), new DataSchema(new String[]{"plus(col1,'1')", "plus(col2,'2')"}, new ColumnDataType[]{ + ColumnDataType.STRING, ColumnDataType.STRING + })); + + // Select duplicate columns without order-by + queryContext = QueryContextConverterUtils.getQueryContext("SELECT col1 + 1, col2 + 2, col1 + 1 FROM testTable"); + // Intentionally make data schema not matching the string representation of the expression + dataSchema = new DataSchema(new String[]{"add(col1+1)", "add(col2+2)"}, new ColumnDataType[]{ + ColumnDataType.DOUBLE, ColumnDataType.DOUBLE + }); + pair = SelectionOperatorUtils.getResultTableDataSchemaAndColumnIndices(queryContext, dataSchema); + assertEquals(pair.getLeft(), + new DataSchema(new String[]{"plus(col1,'1')", "plus(col2,'2')", "plus(col1,'1')"}, new ColumnDataType[]{ + ColumnDataType.DOUBLE, ColumnDataType.DOUBLE, ColumnDataType.DOUBLE + })); + assertEquals(pair.getRight(), new int[]{0, 1, 0}); + + // Select duplicate columns without order-by, all the segments are pruned on the server side + // Intentionally make data schema not matching the string representation of the expression + dataSchema = new DataSchema(new String[]{"add(col1+1)", "add(col2+2)", "add(col1+1)"}, new ColumnDataType[]{ + ColumnDataType.STRING, ColumnDataType.STRING, ColumnDataType.STRING + }); + pair = SelectionOperatorUtils.getResultTableDataSchemaAndColumnIndices(queryContext, dataSchema); + assertEquals(pair.getLeft(), + new DataSchema(new String[]{"plus(col1,'1')", "plus(col2,'2')", "plus(col1,'1')"}, new ColumnDataType[]{ + ColumnDataType.STRING, ColumnDataType.STRING, ColumnDataType.STRING + })); + + // Select * with order-by + queryContext = QueryContextConverterUtils.getQueryContext("SELECT * FROM testTable ORDER BY col3"); + dataSchema = new DataSchema(new String[]{"col3", "col1", "col2"}, new ColumnDataType[]{ + ColumnDataType.DOUBLE, ColumnDataType.INT, ColumnDataType.LONG + }); + pair = SelectionOperatorUtils.getResultTableDataSchemaAndColumnIndices(queryContext, dataSchema); + assertEquals(pair.getLeft(), new DataSchema(new String[]{"col1", "col2", "col3"}, new ColumnDataType[]{ + ColumnDataType.INT, ColumnDataType.LONG, ColumnDataType.DOUBLE + })); + assertEquals(pair.getRight(), new int[]{1, 2, 0}); + + // Select * with order-by, all the segments are pruned on the server side + dataSchema = new DataSchema(new String[]{"*"}, new ColumnDataType[]{ColumnDataType.STRING}); + pair = SelectionOperatorUtils.getResultTableDataSchemaAndColumnIndices(queryContext, dataSchema); + assertEquals(pair.getLeft(), new DataSchema(new String[]{"*"}, new ColumnDataType[]{ColumnDataType.STRING})); + + // Select * ordering on function + queryContext = QueryContextConverterUtils.getQueryContext("SELECT * FROM testTable ORDER BY col1 + col2"); + // Intentionally make data schema not matching the string representation of the expression + dataSchema = new DataSchema(new String[]{"add(col1+col2)", "col1", "col2", "col3"}, new ColumnDataType[]{ + ColumnDataType.DOUBLE, ColumnDataType.INT, ColumnDataType.LONG, ColumnDataType.DOUBLE + }); + pair = SelectionOperatorUtils.getResultTableDataSchemaAndColumnIndices(queryContext, dataSchema); + assertEquals(pair.getLeft(), new DataSchema(new String[]{"col1", "col2", "col3"}, new ColumnDataType[]{ + ColumnDataType.INT, ColumnDataType.LONG, ColumnDataType.DOUBLE + })); + assertEquals(pair.getRight(), new int[]{1, 2, 3}); + + // Select * ordering on function, all the segments are pruned on the server side + dataSchema = new DataSchema(new String[]{"*"}, new ColumnDataType[]{ColumnDataType.STRING}); + pair = SelectionOperatorUtils.getResultTableDataSchemaAndColumnIndices(queryContext, dataSchema); + assertEquals(pair.getLeft(), new DataSchema(new String[]{"*"}, new ColumnDataType[]{ColumnDataType.STRING})); + + // Select * ordering on both column and function + queryContext = QueryContextConverterUtils.getQueryContext("SELECT * FROM testTable ORDER BY col1 + col2, col2"); + // Intentionally make data schema not matching the string representation of the expression + dataSchema = new DataSchema(new String[]{"add(col1+col2)", "col2", "col1", "col3"}, new ColumnDataType[]{ + ColumnDataType.DOUBLE, ColumnDataType.LONG, ColumnDataType.INT, ColumnDataType.DOUBLE + }); + pair = SelectionOperatorUtils.getResultTableDataSchemaAndColumnIndices(queryContext, dataSchema); + assertEquals(pair.getLeft(), new DataSchema(new String[]{"col1", "col2", "col3"}, new ColumnDataType[]{ + ColumnDataType.INT, ColumnDataType.LONG, ColumnDataType.DOUBLE + })); + assertEquals(pair.getRight(), new int[]{2, 1, 3}); + + // Select * ordering on both column and function, all the segments are pruned on the server side + dataSchema = new DataSchema(new String[]{"*"}, new ColumnDataType[]{ColumnDataType.STRING}); + pair = SelectionOperatorUtils.getResultTableDataSchemaAndColumnIndices(queryContext, dataSchema); + assertEquals(pair.getLeft(), new DataSchema(new String[]{"*"}, new ColumnDataType[]{ColumnDataType.STRING})); + + // Select columns with order-by + queryContext = QueryContextConverterUtils.getQueryContext( + "SELECT col1 + 1, col3, col2 + 2 FROM testTable ORDER BY col2 + 2, col4"); + // Intentionally make data schema not matching the string representation of the expression + dataSchema = new DataSchema(new String[]{"add(col2+2)", "col4", "add(col1+1)", "col3"}, new ColumnDataType[]{ + ColumnDataType.DOUBLE, ColumnDataType.STRING, ColumnDataType.DOUBLE, ColumnDataType.DOUBLE + }); + pair = SelectionOperatorUtils.getResultTableDataSchemaAndColumnIndices(queryContext, dataSchema); + assertEquals(pair.getLeft(), new DataSchema(new String[]{"plus(col1,'1')", "col3", "plus(col2,'2')"}, + new ColumnDataType[]{ColumnDataType.DOUBLE, ColumnDataType.DOUBLE, ColumnDataType.DOUBLE})); + assertEquals(pair.getRight(), new int[]{2, 3, 0}); + + // Select columns with order-by, all the segments are pruned on the server side + // Intentionally make data schema not matching the string representation of the expression + dataSchema = new DataSchema(new String[]{"add(col1+1)", "col3", "add(col2+2)"}, new ColumnDataType[]{ + ColumnDataType.STRING, ColumnDataType.STRING, ColumnDataType.STRING + }); + pair = SelectionOperatorUtils.getResultTableDataSchemaAndColumnIndices(queryContext, dataSchema); + assertEquals(pair.getLeft(), new DataSchema(new String[]{"plus(col1,'1')", "col3", "plus(col2,'2')"}, + new ColumnDataType[]{ColumnDataType.STRING, ColumnDataType.STRING, ColumnDataType.STRING})); + + // Select duplicate columns with order-by + queryContext = QueryContextConverterUtils.getQueryContext( + "SELECT col1 + 1, col2 + 2, col1 + 1 FROM testTable ORDER BY col2 + 2, col4"); + // Intentionally make data schema not matching the string representation of the expression + dataSchema = new DataSchema(new String[]{"add(col2+2)", "col4", "add(col1+1)"}, new ColumnDataType[]{ + ColumnDataType.DOUBLE, ColumnDataType.STRING, ColumnDataType.DOUBLE + }); + pair = SelectionOperatorUtils.getResultTableDataSchemaAndColumnIndices(queryContext, dataSchema); + assertEquals(pair.getLeft(), + new DataSchema(new String[]{"plus(col1,'1')", "plus(col2,'2')", "plus(col1,'1')"}, new ColumnDataType[]{ + ColumnDataType.DOUBLE, ColumnDataType.DOUBLE, ColumnDataType.DOUBLE + })); + assertEquals(pair.getRight(), new int[]{2, 0, 2}); + + // Select duplicate columns with order-by, all the segments are pruned on the server side + // Intentionally make data schema not matching the string representation of the expression + dataSchema = new DataSchema(new String[]{"add(col1+1)", "add(col2+2)", "add(col1+1)"}, new ColumnDataType[]{ + ColumnDataType.STRING, ColumnDataType.STRING, ColumnDataType.STRING + }); + pair = SelectionOperatorUtils.getResultTableDataSchemaAndColumnIndices(queryContext, dataSchema); + assertEquals(pair.getLeft(), + new DataSchema(new String[]{"plus(col1,'1')", "plus(col2,'2')", "plus(col1,'1')"}, new ColumnDataType[]{ + ColumnDataType.STRING, ColumnDataType.STRING, ColumnDataType.STRING + })); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/query/utils/OrderByComparatorFactoryTest.java b/pinot-core/src/test/java/org/apache/pinot/core/query/utils/OrderByComparatorFactoryTest.java new file mode 100644 index 000000000000..54864862debf --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/query/utils/OrderByComparatorFactoryTest.java @@ -0,0 +1,108 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.core.query.utils; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.pinot.common.request.context.ExpressionContext; +import org.apache.pinot.common.request.context.OrderByExpressionContext; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; + + +public class OrderByComparatorFactoryTest { + private static final boolean ENABLE_NULL_HANDLING = true; + private static final boolean ASC = true; + private static final boolean DESC = false; + private static final boolean NULLS_LAST = true; + private static final boolean NULLS_FIRST = false; + private static final ExpressionContext COLUMN1 = ExpressionContext.forIdentifier("Column1"); + private static final ExpressionContext COLUMN2 = ExpressionContext.forIdentifier("Column2"); + private static final int COLUMN1_INDEX = 0; + private static final int COLUMN2_INDEX = 1; + + private List _rows; + + public void setUpSingleColumnRows() { + _rows = Arrays.asList(new Object[]{1}, new Object[]{2}, new Object[]{null}); + } + + private List extractColumn(List rows, int columnIndex) { + return rows.stream().map(row -> row[columnIndex]).collect(Collectors.toList()); + } + + @Test + public void testAscNullsLast() { + List orderBys = + Collections.singletonList(new OrderByExpressionContext(COLUMN1, ASC, NULLS_LAST)); + setUpSingleColumnRows(); + + _rows.sort(OrderByComparatorFactory.getComparator(orderBys, ENABLE_NULL_HANDLING)); + + assertEquals(extractColumn(_rows, COLUMN1_INDEX), Arrays.asList(1, 2, null)); + } + + @Test + public void testAscNullsFirst() { + List orderBys = + Collections.singletonList(new OrderByExpressionContext(COLUMN1, ASC, NULLS_FIRST)); + setUpSingleColumnRows(); + + _rows.sort(OrderByComparatorFactory.getComparator(orderBys, ENABLE_NULL_HANDLING)); + + assertEquals(extractColumn(_rows, COLUMN1_INDEX), Arrays.asList(null, 1, 2)); + } + + @Test + public void testDescNullsLast() { + List orderBys = + Collections.singletonList(new OrderByExpressionContext(COLUMN1, DESC, NULLS_LAST)); + setUpSingleColumnRows(); + + _rows.sort(OrderByComparatorFactory.getComparator(orderBys, ENABLE_NULL_HANDLING)); + + assertEquals(extractColumn(_rows, COLUMN1_INDEX), Arrays.asList(2, 1, null)); + } + + @Test + public void testDescNullsFirst() { + List orderBys = + Collections.singletonList(new OrderByExpressionContext(COLUMN1, DESC, NULLS_FIRST)); + setUpSingleColumnRows(); + + _rows.sort(OrderByComparatorFactory.getComparator(orderBys, ENABLE_NULL_HANDLING)); + + assertEquals(extractColumn(_rows, COLUMN1_INDEX), Arrays.asList(null, 2, 1)); + } + + @Test + public void testTwoNullsCompareNextColumn() { + List orderBys = Arrays.asList(new OrderByExpressionContext(COLUMN1, ASC, NULLS_LAST), + new OrderByExpressionContext(COLUMN2, ASC, NULLS_LAST)); + _rows = Arrays.asList(new Object[]{null, 2}, new Object[]{null, 3}, new Object[]{1, 1}); + + _rows.sort(OrderByComparatorFactory.getComparator(orderBys, ENABLE_NULL_HANDLING)); + + assertEquals(extractColumn(_rows, COLUMN2_INDEX), Arrays.asList(1, 2, 3)); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakePartitionLevelConsumer.java b/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakePartitionLevelConsumer.java index cbb77066f2a8..3f52ca4740f2 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakePartitionLevelConsumer.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakePartitionLevelConsumer.java @@ -24,7 +24,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.concurrent.TimeoutException; import org.apache.avro.file.DataFileStream; import org.apache.avro.generic.GenericDatumWriter; import org.apache.avro.generic.GenericRecord; @@ -34,7 +33,7 @@ import org.apache.pinot.plugin.inputformat.avro.AvroUtils; import org.apache.pinot.spi.stream.LongMsgOffset; import org.apache.pinot.spi.stream.MessageBatch; -import org.apache.pinot.spi.stream.PartitionLevelConsumer; +import org.apache.pinot.spi.stream.PartitionGroupConsumer; import org.apache.pinot.spi.stream.StreamConfig; import org.apache.pinot.spi.stream.StreamPartitionMsgOffset; import org.slf4j.Logger; @@ -44,15 +43,14 @@ /** - * Implementation of {@link PartitionLevelConsumer} for fake stream + * Implementation of {@link PartitionGroupConsumer} for fake stream * Unpacks tar files in /resources/data/On_Time_Performance_2014_partition_.tar.gz as source of messages */ -public class FakePartitionLevelConsumer implements PartitionLevelConsumer { +public class FakePartitionLevelConsumer implements PartitionGroupConsumer { + private static final Logger LOGGER = LoggerFactory.getLogger(FakePartitionLevelConsumer.class); - private static final Logger LOGGER = LoggerFactory.getLogger(FakePartitionLevelConsumer.class.getName()); - - private List _messageOffsets = new ArrayList<>(); - private List _messageBytes = new ArrayList<>(); + private final List _messageOffsets = new ArrayList<>(); + private final List _messageBytes = new ArrayList<>(); private final int _defaultBatchSize; FakePartitionLevelConsumer(int partition, StreamConfig streamConfig, int defaultBatchSize) { @@ -95,32 +93,15 @@ public class FakePartitionLevelConsumer implements PartitionLevelConsumer { } } - public MessageBatch fetchMessages(long startOffset, long endOffset, int timeoutMillis) - throws TimeoutException { - throw new UnsupportedOperationException("This method is deprecated"); - } - @Override - public MessageBatch fetchMessages(StreamPartitionMsgOffset startOffset, StreamPartitionMsgOffset endOffset, - int timeoutMillis) - throws TimeoutException { - if (startOffset.compareTo(FakeStreamConfigUtils.getLargestOffset()) >= 0) { - return new FakeStreamMessageBatch(Collections.emptyList(), Collections.emptyList()); - } - if (startOffset.compareTo(FakeStreamConfigUtils.getSmallestOffset()) < 0) { - startOffset = FakeStreamConfigUtils.getSmallestOffset(); - } - if (endOffset == null || endOffset.compareTo(FakeStreamConfigUtils.getLargestOffset()) > 0) { - endOffset = FakeStreamConfigUtils.getLargestOffset(); - } + public MessageBatch fetchMessages(StreamPartitionMsgOffset startOffset, int timeoutMs) { int startOffsetInt = (int) ((LongMsgOffset) startOffset).getOffset(); - int endOffsetInt = (int) ((LongMsgOffset) endOffset).getOffset(); - if (endOffsetInt > _messageOffsets.size() && _defaultBatchSize > 0) { - // Hack to get multiple batches - endOffsetInt = startOffsetInt + _defaultBatchSize; + if (startOffsetInt >= _messageOffsets.size()) { + return new FakeStreamMessageBatch(Collections.emptyList(), Collections.emptyList(), startOffsetInt); } - return new FakeStreamMessageBatch(_messageOffsets.subList(startOffsetInt, endOffsetInt), - _messageBytes.subList(startOffsetInt, endOffsetInt)); + int endOffsetInt = Math.min(startOffsetInt + _defaultBatchSize, _messageOffsets.size()); + return new FakeStreamMessageBatch(_messageBytes.subList(startOffsetInt, endOffsetInt), + _messageOffsets.subList(startOffsetInt, endOffsetInt), endOffsetInt); } @Override diff --git a/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakeStreamConfigUtils.java b/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakeStreamConfigUtils.java index 75de0fbee94b..af9c4c860247 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakeStreamConfigUtils.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakeStreamConfigUtils.java @@ -140,12 +140,8 @@ private static File getResourceFile(String fileName) { */ public static StreamConfig getDefaultLowLevelStreamConfigs(int numPartitions) { Map streamConfigMap = getDefaultStreamConfigs(); - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(STREAM_TYPE, StreamConfigProperties.STREAM_CONSUMER_TYPES), - StreamConfig.ConsumerType.LOWLEVEL.toString()); streamConfigMap.put(StreamConfigProperties.constructStreamProperty(STREAM_TYPE, NUM_PARTITIONS_KEY), String.valueOf(numPartitions)); - return new StreamConfig(TABLE_NAME_WITH_TYPE, streamConfigMap); } @@ -156,18 +152,6 @@ public static StreamConfig getDefaultLowLevelStreamConfigs() { return getDefaultLowLevelStreamConfigs(DEFAULT_NUM_PARTITIONS); } - /** - * Generate fake stream configs for high level stream - */ - public static StreamConfig getDefaultHighLevelStreamConfigs() { - Map streamConfigMap = getDefaultStreamConfigs(); - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(STREAM_TYPE, StreamConfigProperties.STREAM_CONSUMER_TYPES), - StreamConfig.ConsumerType.HIGHLEVEL.toString()); - - return new StreamConfig(TABLE_NAME_WITH_TYPE, streamConfigMap); - } - private static Map getDefaultStreamConfigs() { Map streamConfigMap = new HashMap<>(); streamConfigMap.put(StreamConfigProperties.STREAM_TYPE, STREAM_TYPE); diff --git a/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakeStreamConsumerFactory.java b/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakeStreamConsumerFactory.java index 1923a4d822af..3888a8cfe5c0 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakeStreamConsumerFactory.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakeStreamConsumerFactory.java @@ -18,23 +18,10 @@ */ package org.apache.pinot.core.realtime.impl.fakestream; -import java.util.Set; -import org.apache.pinot.segment.local.utils.IngestionUtils; -import org.apache.pinot.spi.config.table.TableConfig; -import org.apache.pinot.spi.data.Schema; -import org.apache.pinot.spi.data.readers.GenericRow; -import org.apache.pinot.spi.stream.LongMsgOffset; -import org.apache.pinot.spi.stream.MessageBatch; -import org.apache.pinot.spi.stream.OffsetCriteria; -import org.apache.pinot.spi.stream.PartitionLevelConsumer; -import org.apache.pinot.spi.stream.StreamConfig; +import org.apache.pinot.spi.stream.PartitionGroupConsumer; +import org.apache.pinot.spi.stream.PartitionGroupConsumptionStatus; import org.apache.pinot.spi.stream.StreamConsumerFactory; -import org.apache.pinot.spi.stream.StreamConsumerFactoryProvider; -import org.apache.pinot.spi.stream.StreamDecoderProvider; -import org.apache.pinot.spi.stream.StreamLevelConsumer; -import org.apache.pinot.spi.stream.StreamMessageDecoder; import org.apache.pinot.spi.stream.StreamMetadataProvider; -import org.apache.pinot.spi.stream.StreamPartitionMsgOffset; /** @@ -45,17 +32,6 @@ */ public class FakeStreamConsumerFactory extends StreamConsumerFactory { - @Override - public PartitionLevelConsumer createPartitionLevelConsumer(String clientId, int partition) { - return new FakePartitionLevelConsumer(partition, _streamConfig, FakeStreamConfigUtils.MESSAGE_BATCH_SIZE); - } - - @Override - public StreamLevelConsumer createStreamLevelConsumer(String clientId, String tableName, Set fieldsToRead, - String groupId) { - return new FakeStreamLevelConsumer(); - } - @Override public StreamMetadataProvider createPartitionMetadataProvider(String clientId, int partition) { return new FakeStreamMetadataProvider(_streamConfig); @@ -66,43 +42,10 @@ public StreamMetadataProvider createStreamMetadataProvider(String clientId) { return new FakeStreamMetadataProvider(_streamConfig); } - public static void main(String[] args) - throws Exception { - String clientId = "client_id_localhost_tester"; - - // stream config - int numPartitions = 5; - StreamConfig streamConfig = FakeStreamConfigUtils.getDefaultLowLevelStreamConfigs(numPartitions); - - // stream consumer factory - StreamConsumerFactory streamConsumerFactory = StreamConsumerFactoryProvider.create(streamConfig); - - // stream metadata provider - StreamMetadataProvider streamMetadataProvider = streamConsumerFactory.createStreamMetadataProvider(clientId); - int partitionCount = streamMetadataProvider.fetchPartitionCount(10_000); - System.out.println(partitionCount); - - // Partition metadata provider - int partition = 3; - StreamMetadataProvider partitionMetadataProvider = - streamConsumerFactory.createPartitionMetadataProvider(clientId, partition); - StreamPartitionMsgOffset partitionOffset = - partitionMetadataProvider.fetchStreamPartitionOffset(OffsetCriteria.SMALLEST_OFFSET_CRITERIA, 10_000); - System.out.println(partitionOffset); - - // Partition level consumer - PartitionLevelConsumer partitionLevelConsumer = - streamConsumerFactory.createPartitionLevelConsumer(clientId, partition); - MessageBatch messageBatch = - partitionLevelConsumer.fetchMessages(new LongMsgOffset(10), new LongMsgOffset(40), 10_000); - - // Message decoder - Schema pinotSchema = FakeStreamConfigUtils.getPinotSchema(); - TableConfig tableConfig = FakeStreamConfigUtils.getTableConfig(); - StreamMessageDecoder streamMessageDecoder = StreamDecoderProvider.create(streamConfig, - IngestionUtils.getFieldsForRecordExtractor(tableConfig.getIngestionConfig(), pinotSchema)); - GenericRow decodedRow = new GenericRow(); - streamMessageDecoder.decode(messageBatch.getMessageAtIndex(0), decodedRow); - System.out.println(decodedRow); + @Override + public PartitionGroupConsumer createPartitionGroupConsumer(String clientId, + PartitionGroupConsumptionStatus partitionGroupConsumptionStatus) { + return new FakePartitionLevelConsumer(partitionGroupConsumptionStatus.getPartitionGroupId(), _streamConfig, + FakeStreamConfigUtils.MESSAGE_BATCH_SIZE); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakeStreamLevelConsumer.java b/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakeStreamLevelConsumer.java deleted file mode 100644 index 3331c2407433..000000000000 --- a/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakeStreamLevelConsumer.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.pinot.core.realtime.impl.fakestream; - -import org.apache.pinot.spi.data.readers.GenericRow; -import org.apache.pinot.spi.stream.StreamLevelConsumer; - - -/** - * Test implementation of {@link StreamLevelConsumer} - * This is currently a no-op - */ -public class FakeStreamLevelConsumer implements StreamLevelConsumer { - @Override - public void start() - throws Exception { - } - - @Override - public GenericRow next(GenericRow destination) { - return destination; - } - - @Override - public void commit() { - } - - @Override - public void shutdown() - throws Exception { - } -} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakeStreamMessageBatch.java b/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakeStreamMessageBatch.java index 134aac9fd84a..41cb158e63f6 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakeStreamMessageBatch.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakeStreamMessageBatch.java @@ -19,50 +19,43 @@ package org.apache.pinot.core.realtime.impl.fakestream; import java.util.List; +import org.apache.pinot.spi.stream.BytesStreamMessage; import org.apache.pinot.spi.stream.LongMsgOffset; import org.apache.pinot.spi.stream.MessageBatch; +import org.apache.pinot.spi.stream.StreamMessageMetadata; import org.apache.pinot.spi.stream.StreamPartitionMsgOffset; /** * MessageBatch implementation for the fake stream */ -public class FakeStreamMessageBatch implements MessageBatch { - private List _messageOffsets; - private List _messageBytes; - - FakeStreamMessageBatch(List messageOffsets, List messageBytes) { - _messageOffsets = messageOffsets; - _messageBytes = messageBytes; +class FakeStreamMessageBatch implements MessageBatch { + private final List _values; + private final List _offsets; + private final int _offsetOfNextBatch; + + FakeStreamMessageBatch(List values, List offsets, int offsetOfNextBatch) { + _values = values; + _offsets = offsets; + _offsetOfNextBatch = offsetOfNextBatch; } @Override public int getMessageCount() { - return _messageOffsets.size(); - } - - @Override - public byte[] getMessageAtIndex(int index) { - return _messageBytes.get(index); - } - - @Override - public int getMessageOffsetAtIndex(int index) { - return _messageOffsets.get(index); - } - - @Override - public int getMessageLengthAtIndex(int index) { - return _messageBytes.get(index).length; + return _values.size(); } @Override - public long getNextStreamMessageOffsetAtIndex(int index) { - throw new UnsupportedOperationException("This method is deprecated"); + public BytesStreamMessage getStreamMessage(int index) { + byte[] value = _values.get(index); + int offset = _offsets.get(index); + return new BytesStreamMessage(value, + new StreamMessageMetadata.Builder().setOffset(new LongMsgOffset(offset), new LongMsgOffset(offset + 1)) + .build()); } @Override - public StreamPartitionMsgOffset getNextStreamPartitionMsgOffsetAtIndex(int index) { - return new LongMsgOffset(_messageOffsets.get(index) + 1); + public StreamPartitionMsgOffset getOffsetOfNextBatch() { + return new LongMsgOffset(_offsetOfNextBatch); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakeStreamMetadataProvider.java b/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakeStreamMetadataProvider.java index 77abf423c952..c59c15a028a6 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakeStreamMetadataProvider.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/realtime/impl/fakestream/FakeStreamMetadataProvider.java @@ -19,6 +19,9 @@ package org.apache.pinot.core.realtime.impl.fakestream; import java.io.IOException; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import org.apache.pinot.spi.stream.OffsetCriteria; import org.apache.pinot.spi.stream.StreamConfig; import org.apache.pinot.spi.stream.StreamMetadataProvider; @@ -40,6 +43,11 @@ public int fetchPartitionCount(long timeoutMillis) { return _numPartitions; } + @Override + public Set fetchPartitionIds(long timeoutMillis) { + return IntStream.range(0, _numPartitions).boxed().collect(Collectors.toSet()); + } + @Override public StreamPartitionMsgOffset fetchStreamPartitionOffset(OffsetCriteria offsetCriteria, long timeoutMillis) { if (offsetCriteria.isSmallest()) { diff --git a/pinot-core/src/test/java/org/apache/pinot/core/realtime/stream/StreamConfigTest.java b/pinot-core/src/test/java/org/apache/pinot/core/realtime/stream/StreamConfigTest.java index 2b164ac6545f..d95ca53a2263 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/realtime/stream/StreamConfigTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/realtime/stream/StreamConfigTest.java @@ -23,14 +23,16 @@ import org.apache.pinot.core.realtime.impl.fakestream.FakeStreamConsumerFactory; import org.apache.pinot.core.realtime.impl.fakestream.FakeStreamMessageDecoder; import org.apache.pinot.spi.stream.OffsetCriteria; -import org.apache.pinot.spi.stream.PartitionLevelStreamConfig; import org.apache.pinot.spi.stream.StreamConfig; import org.apache.pinot.spi.stream.StreamConfigProperties; import org.apache.pinot.spi.utils.DataSizeUtils; import org.apache.pinot.spi.utils.TimeUtils; -import org.testng.Assert; import org.testng.annotations.Test; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + public class StreamConfigTest { @@ -39,116 +41,76 @@ public class StreamConfigTest { */ @Test public void testStreamConfig() { - boolean exception = false; - StreamConfig streamConfig; String streamType = "fakeStream"; String topic = "fakeTopic"; String tableName = "fakeTable_REALTIME"; - String consumerType = StreamConfig.ConsumerType.LOWLEVEL.toString(); String consumerFactoryClass = FakeStreamConsumerFactory.class.getName(); String decoderClass = FakeStreamMessageDecoder.class.getName(); // test with empty map try { Map streamConfigMap = new HashMap<>(); - streamConfig = new StreamConfig(tableName, streamConfigMap); + new StreamConfig(tableName, streamConfigMap); + fail(); } catch (NullPointerException e) { - exception = true; + // Expected } - Assert.assertTrue(exception); // All mandatory properties set Map streamConfigMap = new HashMap<>(); streamConfigMap.put(StreamConfigProperties.STREAM_TYPE, streamType); - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_TOPIC_NAME), - topic); - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_TYPES), - consumerType); - streamConfigMap.put(StreamConfigProperties - .constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_FACTORY_CLASS), - consumerFactoryClass); - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_DECODER_CLASS), - decoderClass); - streamConfig = new StreamConfig(tableName, streamConfigMap); + streamConfigMap.put( + StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_TOPIC_NAME), topic); + streamConfigMap.put(StreamConfigProperties.constructStreamProperty(streamType, + StreamConfigProperties.STREAM_CONSUMER_FACTORY_CLASS), consumerFactoryClass); + streamConfigMap.put( + StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_DECODER_CLASS), + decoderClass); + StreamConfig streamConfig = new StreamConfig(tableName, streamConfigMap); + assertEquals(streamConfig.getType(), streamType); + assertEquals(streamConfig.getTopicName(), topic); + assertEquals(streamConfig.getConsumerFactoryClassName(), consumerFactoryClass); + assertEquals(streamConfig.getDecoderClass(), decoderClass); // Missing streamType streamConfigMap.remove(StreamConfigProperties.STREAM_TYPE); - exception = false; try { - streamConfig = new StreamConfig(tableName, streamConfigMap); + new StreamConfig(tableName, streamConfigMap); + fail(); } catch (NullPointerException e) { - exception = true; + // Expected } - Assert.assertTrue(exception); // Missing stream topic streamConfigMap.put(StreamConfigProperties.STREAM_TYPE, streamType); - streamConfigMap - .remove(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_TOPIC_NAME)); - exception = false; - try { - streamConfig = new StreamConfig(tableName, streamConfigMap); - } catch (NullPointerException e) { - exception = true; - } - Assert.assertTrue(exception); - - // Missing consumer type - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_TOPIC_NAME), - topic); streamConfigMap.remove( - StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_TYPES)); - exception = false; - try { - streamConfig = new StreamConfig(tableName, streamConfigMap); - } catch (NullPointerException e) { - exception = true; - } - Assert.assertTrue(exception); - - // Missing consumer factory - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_TYPES), - consumerType); - streamConfigMap.remove(StreamConfigProperties - .constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_FACTORY_CLASS)); - exception = false; + StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_TOPIC_NAME)); try { - streamConfig = new StreamConfig(tableName, streamConfigMap); + new StreamConfig(tableName, streamConfigMap); + fail(); } catch (NullPointerException e) { - exception = true; + // Expected } - Assert.assertFalse(exception); - Assert.assertEquals(streamConfig.getConsumerFactoryClassName(), - StreamConfig.DEFAULT_CONSUMER_FACTORY_CLASS_NAME_STRING); + + // Missing consumer factory - allowed + streamConfigMap.put( + StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_TOPIC_NAME), topic); + streamConfigMap.remove(StreamConfigProperties.constructStreamProperty(streamType, + StreamConfigProperties.STREAM_CONSUMER_FACTORY_CLASS)); + streamConfig = new StreamConfig(tableName, streamConfigMap); + assertEquals(streamConfig.getConsumerFactoryClassName(), StreamConfig.DEFAULT_CONSUMER_FACTORY_CLASS_NAME_STRING); // Missing decoder class - streamConfigMap.put(StreamConfigProperties - .constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_FACTORY_CLASS), - consumerFactoryClass); + streamConfigMap.put(StreamConfigProperties.constructStreamProperty(streamType, + StreamConfigProperties.STREAM_CONSUMER_FACTORY_CLASS), consumerFactoryClass); streamConfigMap.remove( StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_DECODER_CLASS)); - exception = false; try { - streamConfig = new StreamConfig(tableName, streamConfigMap); + new StreamConfig(tableName, streamConfigMap); + fail(); } catch (NullPointerException e) { - exception = true; + // Expected } - Assert.assertTrue(exception); - - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_DECODER_CLASS), - decoderClass); - streamConfig = new StreamConfig(tableName, streamConfigMap); - Assert.assertEquals(streamConfig.getType(), streamType); - Assert.assertEquals(streamConfig.getTopicName(), topic); - Assert.assertEquals(streamConfig.getConsumerTypes().get(0), StreamConfig.ConsumerType.LOWLEVEL); - Assert.assertEquals(streamConfig.getConsumerFactoryClassName(), consumerFactoryClass); - Assert.assertEquals(streamConfig.getDecoderClass(), decoderClass); } /** @@ -158,45 +120,35 @@ public void testStreamConfig() { public void testStreamConfigDefaults() { String streamType = "fakeStream"; String topic = "fakeTopic"; - String consumerType = "simple"; String tableName = "fakeTable_REALTIME"; String consumerFactoryClass = FakeStreamConsumerFactory.class.getName(); String decoderClass = FakeStreamMessageDecoder.class.getName(); Map streamConfigMap = new HashMap<>(); streamConfigMap.put(StreamConfigProperties.STREAM_TYPE, streamType); - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_TOPIC_NAME), - topic); - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_TYPES), - consumerType); - streamConfigMap.put(StreamConfigProperties - .constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_FACTORY_CLASS), - consumerFactoryClass); - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_DECODER_CLASS), - decoderClass); + streamConfigMap.put( + StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_TOPIC_NAME), topic); + streamConfigMap.put(StreamConfigProperties.constructStreamProperty(streamType, + StreamConfigProperties.STREAM_CONSUMER_FACTORY_CLASS), consumerFactoryClass); + streamConfigMap.put( + StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_DECODER_CLASS), + decoderClass); // Mandatory values + defaults StreamConfig streamConfig = new StreamConfig(tableName, streamConfigMap); - Assert.assertEquals(streamConfig.getType(), streamType); - Assert.assertEquals(streamConfig.getTopicName(), topic); - Assert.assertEquals(streamConfig.getConsumerTypes().get(0), StreamConfig.ConsumerType.LOWLEVEL); - Assert.assertEquals(streamConfig.getConsumerFactoryClassName(), consumerFactoryClass); - Assert.assertEquals(streamConfig.getDecoderClass(), decoderClass); - Assert.assertEquals(streamConfig.getDecoderProperties().size(), 0); - Assert - .assertEquals(streamConfig.getOffsetCriteria(), new OffsetCriteria.OffsetCriteriaBuilder().withOffsetLargest()); - Assert - .assertEquals(streamConfig.getConnectionTimeoutMillis(), StreamConfig.DEFAULT_STREAM_CONNECTION_TIMEOUT_MILLIS); - Assert.assertEquals(streamConfig.getFetchTimeoutMillis(), StreamConfig.DEFAULT_STREAM_FETCH_TIMEOUT_MILLIS); - Assert.assertEquals(streamConfig.getFlushThresholdRows(), StreamConfig.DEFAULT_FLUSH_THRESHOLD_ROWS); - Assert.assertEquals(streamConfig.getFlushThresholdTimeMillis(), StreamConfig.DEFAULT_FLUSH_THRESHOLD_TIME_MILLIS); - Assert.assertEquals(streamConfig.getFlushThresholdSegmentSizeBytes(), - StreamConfig.DEFAULT_FLUSH_THRESHOLD_SEGMENT_SIZE_BYTES); - - consumerType = "lowLevel,highLevel"; + assertEquals(streamConfig.getType(), streamType); + assertEquals(streamConfig.getTopicName(), topic); + assertEquals(streamConfig.getConsumerFactoryClassName(), consumerFactoryClass); + assertEquals(streamConfig.getDecoderClass(), decoderClass); + assertEquals(streamConfig.getDecoderProperties().size(), 0); + assertEquals(streamConfig.getOffsetCriteria(), new OffsetCriteria.OffsetCriteriaBuilder().withOffsetLargest()); + assertEquals(streamConfig.getConnectionTimeoutMillis(), StreamConfig.DEFAULT_STREAM_CONNECTION_TIMEOUT_MILLIS); + assertEquals(streamConfig.getFetchTimeoutMillis(), StreamConfig.DEFAULT_STREAM_FETCH_TIMEOUT_MILLIS); + assertEquals(streamConfig.getFlushThresholdTimeMillis(), StreamConfig.DEFAULT_FLUSH_THRESHOLD_TIME_MILLIS); + assertEquals(streamConfig.getFlushThresholdRows(), -1); + assertEquals(streamConfig.getFlushThresholdSegmentRows(), -1); + assertEquals(streamConfig.getFlushThresholdSegmentSizeBytes(), -1); + String offsetCriteria = "smallest"; String decoderProp1Key = "prop1"; String decoderProp1Value = "decoderValueString"; @@ -205,62 +157,53 @@ public void testStreamConfigDefaults() { String flushThresholdTime = "2h"; String flushThresholdRows = "500"; String flushSegmentSize = "20M"; - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_TYPES), - consumerType); streamConfigMap.put( StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.DECODER_PROPS_PREFIX) + "." + decoderProp1Key, decoderProp1Value); - streamConfigMap.put(StreamConfigProperties - .constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_OFFSET_CRITERIA), offsetCriteria); - streamConfigMap.put(StreamConfigProperties - .constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONNECTION_TIMEOUT_MILLIS), - connectionTimeout); + streamConfigMap.put(StreamConfigProperties.constructStreamProperty(streamType, + StreamConfigProperties.STREAM_CONSUMER_OFFSET_CRITERIA), offsetCriteria); + streamConfigMap.put(StreamConfigProperties.constructStreamProperty(streamType, + StreamConfigProperties.STREAM_CONNECTION_TIMEOUT_MILLIS), connectionTimeout); streamConfigMap.put( StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_FETCH_TIMEOUT_MILLIS), fetchTimeout); - streamConfigMap.put(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_ROWS, flushThresholdRows); streamConfigMap.put(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_TIME, flushThresholdTime); + streamConfigMap.put(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_ROWS, flushThresholdRows); streamConfigMap.put(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_SEGMENT_SIZE, flushSegmentSize); streamConfig = new StreamConfig(tableName, streamConfigMap); - Assert.assertEquals(streamConfig.getType(), streamType); - Assert.assertEquals(streamConfig.getTopicName(), topic); - Assert.assertEquals(streamConfig.getConsumerTypes().get(0), StreamConfig.ConsumerType.LOWLEVEL); - Assert.assertEquals(streamConfig.getConsumerTypes().get(1), StreamConfig.ConsumerType.HIGHLEVEL); - Assert.assertEquals(streamConfig.getConsumerFactoryClassName(), consumerFactoryClass); - Assert.assertEquals(streamConfig.getDecoderClass(), decoderClass); - Assert.assertEquals(streamConfig.getDecoderProperties().size(), 1); - Assert.assertEquals(streamConfig.getDecoderProperties().get(decoderProp1Key), decoderProp1Value); - Assert.assertTrue(streamConfig.getOffsetCriteria().isSmallest()); - Assert.assertEquals(streamConfig.getConnectionTimeoutMillis(), Long.parseLong(connectionTimeout)); - Assert.assertEquals(streamConfig.getFetchTimeoutMillis(), Integer.parseInt(fetchTimeout)); - Assert.assertEquals(streamConfig.getFlushThresholdRows(), Integer.parseInt(flushThresholdRows)); - Assert.assertEquals(streamConfig.getFlushThresholdTimeMillis(), + assertEquals(streamConfig.getType(), streamType); + assertEquals(streamConfig.getTopicName(), topic); + assertEquals(streamConfig.getConsumerFactoryClassName(), consumerFactoryClass); + assertEquals(streamConfig.getDecoderClass(), decoderClass); + assertEquals(streamConfig.getDecoderProperties().size(), 1); + assertEquals(streamConfig.getDecoderProperties().get(decoderProp1Key), decoderProp1Value); + assertTrue(streamConfig.getOffsetCriteria().isSmallest()); + assertEquals(streamConfig.getConnectionTimeoutMillis(), Long.parseLong(connectionTimeout)); + assertEquals(streamConfig.getFetchTimeoutMillis(), Integer.parseInt(fetchTimeout)); + assertEquals(streamConfig.getFlushThresholdTimeMillis(), (long) TimeUtils.convertPeriodToMillis(flushThresholdTime)); - Assert.assertEquals(streamConfig.getFlushThresholdSegmentSizeBytes(), DataSizeUtils.toBytes(flushSegmentSize)); + assertEquals(streamConfig.getFlushThresholdRows(), Integer.parseInt(flushThresholdRows)); + assertEquals(streamConfig.getFlushThresholdSegmentRows(), -1); + assertEquals(streamConfig.getFlushThresholdSegmentSizeBytes(), DataSizeUtils.toBytes(flushSegmentSize)); // Backward compatibility check for flushThresholdTime flushThresholdTime = "18000000"; streamConfigMap.put(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_TIME, flushThresholdTime); streamConfig = new StreamConfig(tableName, streamConfigMap); - Assert.assertEquals(streamConfig.getFlushThresholdTimeMillis(), Long.parseLong(flushThresholdTime)); - flushThresholdTime = "invalid input"; - streamConfigMap.put(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_TIME, flushThresholdTime); - streamConfig = new StreamConfig(tableName, streamConfigMap); - Assert.assertEquals(streamConfig.getFlushThresholdTimeMillis(), StreamConfig.DEFAULT_FLUSH_THRESHOLD_TIME_MILLIS); + assertEquals(streamConfig.getFlushThresholdTimeMillis(), Long.parseLong(flushThresholdTime)); // Backward compatibility check for flush threshold rows streamConfigMap.remove(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_ROWS); streamConfigMap.put(StreamConfigProperties.DEPRECATED_SEGMENT_FLUSH_THRESHOLD_ROWS, "10000"); streamConfig = new StreamConfig(tableName, streamConfigMap); - Assert.assertEquals(streamConfig.getFlushThresholdRows(), 10000); + assertEquals(streamConfig.getFlushThresholdRows(), 10000); // Backward compatibility check for flush threshold segment size streamConfigMap.remove(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_SEGMENT_SIZE); streamConfigMap.put(StreamConfigProperties.DEPRECATED_SEGMENT_FLUSH_DESIRED_SIZE, "10M"); streamConfig = new StreamConfig(tableName, streamConfigMap); - Assert.assertEquals(streamConfig.getFlushThresholdSegmentSizeBytes(), DataSizeUtils.toBytes("10M")); + assertEquals(streamConfig.getFlushThresholdSegmentSizeBytes(), DataSizeUtils.toBytes("10M")); } /** @@ -268,11 +211,8 @@ public void testStreamConfigDefaults() { */ @Test public void testStreamConfigValidations() { - boolean exception; - StreamConfig streamConfig; String streamType = "fakeStream"; String topic = "fakeTopic"; - String consumerType = "simple"; String tableName = "fakeTable_REALTIME"; String consumerFactoryClass = FakeStreamConsumerFactory.class.getName(); String decoderClass = FakeStreamMessageDecoder.class.getName(); @@ -280,83 +220,85 @@ public void testStreamConfigValidations() { // All mandatory properties set Map streamConfigMap = new HashMap<>(); streamConfigMap.put(StreamConfigProperties.STREAM_TYPE, streamType); - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_TOPIC_NAME), - topic); - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_TYPES), - consumerType); - streamConfigMap.put(StreamConfigProperties - .constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_FACTORY_CLASS), - consumerFactoryClass); - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_DECODER_CLASS), - decoderClass); - streamConfig = new StreamConfig(tableName, streamConfigMap); - - // Invalid consumer type - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_TYPES), - "invalidConsumerType"); - exception = false; - try { - streamConfig = new StreamConfig(tableName, streamConfigMap); - } catch (IllegalArgumentException e) { - exception = true; - } - Assert.assertTrue(exception); + streamConfigMap.put( + StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_TOPIC_NAME), topic); + streamConfigMap.put(StreamConfigProperties.constructStreamProperty(streamType, + StreamConfigProperties.STREAM_CONSUMER_FACTORY_CLASS), consumerFactoryClass); + streamConfigMap.put( + StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_DECODER_CLASS), + decoderClass); + new StreamConfig(tableName, streamConfigMap); // Invalid fetch timeout - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_TYPES), - consumerType); streamConfigMap.put( StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_FETCH_TIMEOUT_MILLIS), "timeout"); - streamConfig = new StreamConfig(tableName, streamConfigMap); - Assert.assertEquals(streamConfig.getFetchTimeoutMillis(), StreamConfig.DEFAULT_STREAM_FETCH_TIMEOUT_MILLIS); + StreamConfig streamConfig = new StreamConfig(tableName, streamConfigMap); + assertEquals(streamConfig.getFetchTimeoutMillis(), StreamConfig.DEFAULT_STREAM_FETCH_TIMEOUT_MILLIS); // Invalid connection timeout streamConfigMap.remove( StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_FETCH_TIMEOUT_MILLIS)); - streamConfigMap.put(StreamConfigProperties - .constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONNECTION_TIMEOUT_MILLIS), "timeout"); + streamConfigMap.put(StreamConfigProperties.constructStreamProperty(streamType, + StreamConfigProperties.STREAM_CONNECTION_TIMEOUT_MILLIS), "timeout"); streamConfig = new StreamConfig(tableName, streamConfigMap); - Assert - .assertEquals(streamConfig.getConnectionTimeoutMillis(), StreamConfig.DEFAULT_STREAM_CONNECTION_TIMEOUT_MILLIS); + assertEquals(streamConfig.getConnectionTimeoutMillis(), StreamConfig.DEFAULT_STREAM_CONNECTION_TIMEOUT_MILLIS); + + // Invalid flush threshold time + streamConfigMap.remove(StreamConfigProperties.constructStreamProperty(streamType, + StreamConfigProperties.STREAM_CONNECTION_TIMEOUT_MILLIS)); + streamConfigMap.put(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_TIME, "time"); + try { + new StreamConfig(tableName, streamConfigMap); + fail(); + } catch (IllegalArgumentException e) { + // Expected + assertTrue(e.getMessage().contains(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_TIME)); + } // Invalid flush threshold rows - deprecated property - streamConfigMap.remove(StreamConfigProperties - .constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONNECTION_TIMEOUT_MILLIS)); + streamConfigMap.remove(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_TIME); streamConfigMap.put(StreamConfigProperties.DEPRECATED_SEGMENT_FLUSH_THRESHOLD_ROWS, "rows"); - streamConfig = new StreamConfig(tableName, streamConfigMap); - Assert.assertEquals(streamConfig.getFlushThresholdRows(), StreamConfig.DEFAULT_FLUSH_THRESHOLD_ROWS); + try { + new StreamConfig(tableName, streamConfigMap); + fail(); + } catch (IllegalArgumentException e) { + // Expected + assertTrue(e.getMessage().contains(StreamConfigProperties.DEPRECATED_SEGMENT_FLUSH_THRESHOLD_ROWS)); + } // Invalid flush threshold rows - new property streamConfigMap.remove(StreamConfigProperties.DEPRECATED_SEGMENT_FLUSH_THRESHOLD_ROWS); streamConfigMap.put(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_ROWS, "rows"); - streamConfig = new StreamConfig(tableName, streamConfigMap); - Assert.assertEquals(streamConfig.getFlushThresholdRows(), StreamConfig.DEFAULT_FLUSH_THRESHOLD_ROWS); - - // Invalid flush threshold time - streamConfigMap.remove(StreamConfigProperties.DEPRECATED_SEGMENT_FLUSH_THRESHOLD_ROWS); - streamConfigMap.put(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_TIME, "time"); - streamConfig = new StreamConfig(tableName, streamConfigMap); - Assert.assertEquals(streamConfig.getFlushThresholdTimeMillis(), StreamConfig.DEFAULT_FLUSH_THRESHOLD_TIME_MILLIS); + try { + new StreamConfig(tableName, streamConfigMap); + fail(); + } catch (IllegalArgumentException e) { + // Expected + assertTrue(e.getMessage().contains(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_ROWS)); + } // Invalid flush segment size - deprecated property - streamConfigMap.remove(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_TIME); + streamConfigMap.remove(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_ROWS); streamConfigMap.put(StreamConfigProperties.DEPRECATED_SEGMENT_FLUSH_DESIRED_SIZE, "size"); - streamConfig = new StreamConfig(tableName, streamConfigMap); - Assert.assertEquals(streamConfig.getFlushThresholdSegmentSizeBytes(), - StreamConfig.DEFAULT_FLUSH_THRESHOLD_SEGMENT_SIZE_BYTES); + try { + new StreamConfig(tableName, streamConfigMap); + fail(); + } catch (IllegalArgumentException e) { + // Expected + assertTrue(e.getMessage().contains(StreamConfigProperties.DEPRECATED_SEGMENT_FLUSH_DESIRED_SIZE)); + } // Invalid flush segment size - new property streamConfigMap.remove(StreamConfigProperties.DEPRECATED_SEGMENT_FLUSH_DESIRED_SIZE); streamConfigMap.put(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_SEGMENT_SIZE, "size"); - streamConfig = new StreamConfig(tableName, streamConfigMap); - Assert.assertEquals(streamConfig.getFlushThresholdSegmentSizeBytes(), - StreamConfig.DEFAULT_FLUSH_THRESHOLD_SEGMENT_SIZE_BYTES); + try { + new StreamConfig(tableName, streamConfigMap); + fail(); + } catch (IllegalArgumentException e) { + // Expected + assertTrue(e.getMessage().contains(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_SEGMENT_SIZE)); + } } /** @@ -368,7 +310,6 @@ public void testFlushThresholdStreamConfigs() { String streamType = "fakeStream"; String topic = "fakeTopic"; String tableName = "fakeTable_REALTIME"; - String consumerType = "lowlevel"; String consumerFactoryClass = FakeStreamConsumerFactory.class.getName(); String decoderClass = FakeStreamMessageDecoder.class.getName(); String flushThresholdRows = "200"; @@ -378,74 +319,53 @@ public void testFlushThresholdStreamConfigs() { Map streamConfigMap = new HashMap<>(); streamConfigMap.put(StreamConfigProperties.STREAM_TYPE, streamType); - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_TOPIC_NAME), - topic); - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_TYPES), - consumerType); - streamConfigMap.put(StreamConfigProperties - .constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_FACTORY_CLASS), - consumerFactoryClass); - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_DECODER_CLASS), - decoderClass); - - // use defaults if nothing provided + streamConfigMap.put( + StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_TOPIC_NAME), topic); + streamConfigMap.put(StreamConfigProperties.constructStreamProperty(streamType, + StreamConfigProperties.STREAM_CONSUMER_FACTORY_CLASS), consumerFactoryClass); + streamConfigMap.put( + StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_DECODER_CLASS), + decoderClass); + + // Use default values if nothing provided streamConfig = new StreamConfig(tableName, streamConfigMap); - Assert.assertEquals(streamConfig.getFlushThresholdRows(), StreamConfig.DEFAULT_FLUSH_THRESHOLD_ROWS); - Assert.assertEquals(streamConfig.getFlushThresholdTimeMillis(), StreamConfig.DEFAULT_FLUSH_THRESHOLD_TIME_MILLIS); + assertEquals(streamConfig.getFlushThresholdTimeMillis(), StreamConfig.DEFAULT_FLUSH_THRESHOLD_TIME_MILLIS); + assertEquals(streamConfig.getFlushThresholdRows(), -1); + assertEquals(streamConfig.getFlushThresholdSegmentRows(), -1); + assertEquals(streamConfig.getFlushThresholdSegmentSizeBytes(), -1); - // use base values if provided + // Use regular values if provided streamConfigMap.put(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_ROWS, flushThresholdRows); streamConfigMap.put(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_TIME, flushThresholdTime); streamConfig = new StreamConfig(tableName, streamConfigMap); - Assert.assertEquals(streamConfig.getFlushThresholdRows(), Integer.parseInt(flushThresholdRows)); - Assert.assertEquals(streamConfig.getFlushThresholdTimeMillis(), + assertEquals(streamConfig.getFlushThresholdTimeMillis(), (long) TimeUtils.convertPeriodToMillis(flushThresholdTime)); + assertEquals(streamConfig.getFlushThresholdRows(), Integer.parseInt(flushThresholdRows)); + assertEquals(streamConfig.getFlushThresholdSegmentRows(), -1); + assertEquals(streamConfig.getFlushThresholdSegmentSizeBytes(), -1); - // llc overrides provided, but base values will be picked in base StreamConfigs - streamConfigMap - .put(StreamConfigProperties.DEPRECATED_SEGMENT_FLUSH_THRESHOLD_ROWS + StreamConfigProperties.LLC_SUFFIX, - flushThresholdRowsLLC); + // Use regular values if both regular and llc config exists + streamConfigMap.put( + StreamConfigProperties.DEPRECATED_SEGMENT_FLUSH_THRESHOLD_ROWS + StreamConfigProperties.LLC_SUFFIX, + flushThresholdRowsLLC); streamConfigMap.put(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_TIME + StreamConfigProperties.LLC_SUFFIX, flushThresholdTimeLLC); streamConfig = new StreamConfig(tableName, streamConfigMap); - Assert.assertEquals(streamConfig.getFlushThresholdRows(), Integer.parseInt(flushThresholdRows)); - Assert.assertEquals(streamConfig.getFlushThresholdTimeMillis(), + assertEquals(streamConfig.getFlushThresholdTimeMillis(), (long) TimeUtils.convertPeriodToMillis(flushThresholdTime)); + assertEquals(streamConfig.getFlushThresholdRows(), Integer.parseInt(flushThresholdRows)); + assertEquals(streamConfig.getFlushThresholdSegmentRows(), -1); + assertEquals(streamConfig.getFlushThresholdSegmentSizeBytes(), -1); - // llc overrides provided, no base values, defaults will be picked + // Use llc values if only llc config exists streamConfigMap.remove(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_ROWS); streamConfigMap.remove(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_TIME); streamConfig = new StreamConfig(tableName, streamConfigMap); - Assert.assertEquals(streamConfig.getFlushThresholdRows(), StreamConfig.DEFAULT_FLUSH_THRESHOLD_ROWS); - Assert.assertEquals(streamConfig.getFlushThresholdTimeMillis(), StreamConfig.DEFAULT_FLUSH_THRESHOLD_TIME_MILLIS); - - // PartitionLevel stream config will retrieve the llc overrides - PartitionLevelStreamConfig partitionLevelStreamConfig = new PartitionLevelStreamConfig(tableName, streamConfigMap); - Assert.assertEquals(partitionLevelStreamConfig.getFlushThresholdRows(), Integer.parseInt(flushThresholdRowsLLC)); - Assert.assertEquals(partitionLevelStreamConfig.getFlushThresholdTimeMillis(), + assertEquals(streamConfig.getFlushThresholdTimeMillis(), (long) TimeUtils.convertPeriodToMillis(flushThresholdTimeLLC)); - - // PartitionLevelStreamConfig should use base values if llc overrides not provided - streamConfigMap - .remove(StreamConfigProperties.DEPRECATED_SEGMENT_FLUSH_THRESHOLD_ROWS + StreamConfigProperties.LLC_SUFFIX); - streamConfigMap.remove(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_TIME + StreamConfigProperties.LLC_SUFFIX); - streamConfigMap.put(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_ROWS, flushThresholdRows); - streamConfigMap.put(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_TIME, flushThresholdTime); - partitionLevelStreamConfig = new PartitionLevelStreamConfig(tableName, streamConfigMap); - Assert.assertEquals(partitionLevelStreamConfig.getFlushThresholdRows(), Integer.parseInt(flushThresholdRows)); - Assert.assertEquals(partitionLevelStreamConfig.getFlushThresholdTimeMillis(), - (long) TimeUtils.convertPeriodToMillis(flushThresholdTime)); - - // PartitionLevelStreamConfig should use defaults if nothing provided - streamConfigMap.remove(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_ROWS); - streamConfigMap.remove(StreamConfigProperties.SEGMENT_FLUSH_THRESHOLD_TIME); - partitionLevelStreamConfig = new PartitionLevelStreamConfig(tableName, streamConfigMap); - Assert.assertEquals(partitionLevelStreamConfig.getFlushThresholdRows(), StreamConfig.DEFAULT_FLUSH_THRESHOLD_ROWS); - Assert.assertEquals(partitionLevelStreamConfig.getFlushThresholdTimeMillis(), - StreamConfig.DEFAULT_FLUSH_THRESHOLD_TIME_MILLIS); + assertEquals(streamConfig.getFlushThresholdRows(), Integer.parseInt(flushThresholdRowsLLC)); + assertEquals(streamConfig.getFlushThresholdSegmentRows(), -1); + assertEquals(streamConfig.getFlushThresholdSegmentSizeBytes(), -1); } @Test @@ -458,61 +378,57 @@ public void testConsumerTypes() { Map streamConfigMap = new HashMap<>(); streamConfigMap.put(StreamConfigProperties.STREAM_TYPE, streamType); - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_TOPIC_NAME), - topic); - streamConfigMap.put(StreamConfigProperties - .constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_FACTORY_CLASS), - consumerFactoryClass); - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_DECODER_CLASS), - decoderClass); + streamConfigMap.put( + StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_TOPIC_NAME), topic); + streamConfigMap.put(StreamConfigProperties.constructStreamProperty(streamType, + StreamConfigProperties.STREAM_CONSUMER_FACTORY_CLASS), consumerFactoryClass); + streamConfigMap.put( + StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_DECODER_CLASS), + decoderClass); String consumerType = "simple"; - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_TYPES), - consumerType); - StreamConfig streamConfig = new StreamConfig(tableName, streamConfigMap); - Assert.assertEquals(streamConfig.getConsumerTypes().get(0), StreamConfig.ConsumerType.LOWLEVEL); - Assert.assertTrue(streamConfig.hasLowLevelConsumerType()); - Assert.assertFalse(streamConfig.hasHighLevelConsumerType()); + streamConfigMap.put( + StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_TYPES), + consumerType); + new StreamConfig(tableName, streamConfigMap); consumerType = "lowlevel"; - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_TYPES), - consumerType); - streamConfig = new StreamConfig(tableName, streamConfigMap); - Assert.assertEquals(streamConfig.getConsumerTypes().get(0), StreamConfig.ConsumerType.LOWLEVEL); - Assert.assertTrue(streamConfig.hasLowLevelConsumerType()); - Assert.assertFalse(streamConfig.hasHighLevelConsumerType()); - - consumerType = "highLevel"; - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_TYPES), - consumerType); - streamConfig = new StreamConfig(tableName, streamConfigMap); - Assert.assertEquals(streamConfig.getConsumerTypes().get(0), StreamConfig.ConsumerType.HIGHLEVEL); - Assert.assertFalse(streamConfig.hasLowLevelConsumerType()); - Assert.assertTrue(streamConfig.hasHighLevelConsumerType()); - - consumerType = "highLevel,simple"; - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_TYPES), - consumerType); - streamConfig = new StreamConfig(tableName, streamConfigMap); - Assert.assertEquals(streamConfig.getConsumerTypes().get(0), StreamConfig.ConsumerType.HIGHLEVEL); - Assert.assertEquals(streamConfig.getConsumerTypes().get(1), StreamConfig.ConsumerType.LOWLEVEL); - Assert.assertTrue(streamConfig.hasLowLevelConsumerType()); - Assert.assertTrue(streamConfig.hasHighLevelConsumerType()); - - consumerType = "highLevel,lowlevel"; - streamConfigMap - .put(StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_TYPES), - consumerType); - streamConfig = new StreamConfig(tableName, streamConfigMap); - Assert.assertEquals(streamConfig.getConsumerTypes().get(0), StreamConfig.ConsumerType.HIGHLEVEL); - Assert.assertEquals(streamConfig.getConsumerTypes().get(1), StreamConfig.ConsumerType.LOWLEVEL); - Assert.assertTrue(streamConfig.hasLowLevelConsumerType()); - Assert.assertTrue(streamConfig.hasHighLevelConsumerType()); + streamConfigMap.put( + StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_TYPES), + consumerType); + new StreamConfig(tableName, streamConfigMap); + + try { + consumerType = "highLevel"; + streamConfigMap.put( + StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_TYPES), + consumerType); + new StreamConfig(tableName, streamConfigMap); + fail("Invalid consumer type(s) " + consumerType + " in stream config"); + } catch (Exception e) { + // expected + } + + try { + consumerType = "highLevel,simple"; + streamConfigMap.put( + StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_TYPES), + consumerType); + new StreamConfig(tableName, streamConfigMap); + fail("Invalid consumer type(s) " + consumerType + " in stream config"); + } catch (Exception e) { + // expected + } + + try { + consumerType = "highLevel,lowlevel"; + streamConfigMap.put( + StreamConfigProperties.constructStreamProperty(streamType, StreamConfigProperties.STREAM_CONSUMER_TYPES), + consumerType); + new StreamConfig(tableName, streamConfigMap); + fail("Invalid consumer type(s) " + consumerType + " in stream config"); + } catch (Exception e) { + // expected + } } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/segment/processing/framework/PartitionerTest.java b/pinot-core/src/test/java/org/apache/pinot/core/segment/processing/framework/PartitionerTest.java index b81170aa42a0..6f8bc96dce4f 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/segment/processing/framework/PartitionerTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/segment/processing/framework/PartitionerTest.java @@ -18,7 +18,7 @@ */ package org.apache.pinot.core.segment.processing.framework; -import org.apache.commons.lang.RandomStringUtils; +import org.apache.commons.lang3.RandomStringUtils; import org.apache.pinot.core.segment.processing.partitioner.ColumnValuePartitioner; import org.apache.pinot.core.segment.processing.partitioner.NoOpPartitioner; import org.apache.pinot.core.segment.processing.partitioner.Partitioner; diff --git a/pinot-core/src/test/java/org/apache/pinot/core/segment/processing/framework/SegmentMapperTest.java b/pinot-core/src/test/java/org/apache/pinot/core/segment/processing/framework/SegmentMapperTest.java index 3616e190d667..db040232f986 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/segment/processing/framework/SegmentMapperTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/segment/processing/framework/SegmentMapperTest.java @@ -48,6 +48,7 @@ import org.apache.pinot.spi.data.Schema; import org.apache.pinot.spi.data.readers.GenericRow; import org.apache.pinot.spi.data.readers.RecordReader; +import org.apache.pinot.spi.data.readers.RecordReaderFileConfig; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; @@ -141,7 +142,8 @@ public void segmentMapperTest(SegmentProcessorConfig processorConfig, Map partitionToFileManagerMap = segmentMapper.map(); segmentRecordReader.close(); diff --git a/pinot-core/src/test/java/org/apache/pinot/core/segment/processing/framework/SegmentProcessorFrameworkTest.java b/pinot-core/src/test/java/org/apache/pinot/core/segment/processing/framework/SegmentProcessorFrameworkTest.java index 0b73d1f18c1d..6de710a03df7 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/segment/processing/framework/SegmentProcessorFrameworkTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/segment/processing/framework/SegmentProcessorFrameworkTest.java @@ -18,11 +18,16 @@ */ package org.apache.pinot.core.segment.processing.framework; +import com.google.common.collect.ImmutableList; import java.io.File; import java.io.IOException; +import java.net.URL; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.IntStream; import org.apache.commons.io.FileUtils; import org.apache.pinot.core.segment.processing.timehandler.TimeHandler; @@ -40,19 +45,27 @@ import org.apache.pinot.segment.spi.index.reader.NullValueVectorReader; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.config.table.ingestion.ComplexTypeConfig; +import org.apache.pinot.spi.config.table.ingestion.IngestionConfig; import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.spi.data.readers.FileFormat; import org.apache.pinot.spi.data.readers.GenericRow; import org.apache.pinot.spi.data.readers.RecordReader; +import org.apache.pinot.spi.data.readers.RecordReaderFactory; +import org.apache.pinot.spi.data.readers.RecordReaderFileConfig; import org.apache.pinot.spi.utils.ReadMode; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertThrows; import static org.testng.Assert.assertTrue; @@ -65,6 +78,8 @@ public class SegmentProcessorFrameworkTest { private List _singleSegment; private List _multipleSegments; private List _multiValueSegments; + private List _recordReaderWithComplexType; + private TableConfig _tableConfig; private TableConfig _tableConfigNullValueEnabled; @@ -73,6 +88,7 @@ public class SegmentProcessorFrameworkTest { private Schema _schema; private Schema _schemaMV; + private Schema _schemaWithComplexType; private final List _rawData = Arrays.asList(new Object[]{"abc", 1000, 1597719600000L}, new Object[]{null, 2000, 1597773600000L}, @@ -104,21 +120,34 @@ public void setup() _tableConfigWithFixedSegmentName.getIndexingConfig().setSegmentNameGeneratorType("fixed"); _schema = - new Schema.SchemaBuilder().setSchemaName("mySchema").addSingleValueDimension("campaign", DataType.STRING, "") + new Schema.SchemaBuilder().setSchemaName("mySchema") + .addSingleValueDimension("campaign", DataType.STRING, "") + .addSingleValueDimension("campaign.inner1", DataType.STRING) + .addSingleValueDimension("campaign.inner1.inner2", DataType.STRING) // NOTE: Intentionally put 1000 as default value to test skipping null values during rollup .addMetric("clicks", DataType.INT, 1000) .addDateTime("time", DataType.LONG, "1:MILLISECONDS:EPOCH", "1:MILLISECONDS").build(); _schemaMV = - new Schema.SchemaBuilder().setSchemaName("mySchema").addMultiValueDimension("campaign", DataType.STRING, "") + new Schema.SchemaBuilder().setSchemaName("mySchema") + .addMultiValueDimension("campaign", DataType.STRING, "") + // NOTE: Intentionally put 1000 as default value to test skipping null values during rollup + .addMetric("clicks", DataType.INT, 1000) + .addDateTime("time", DataType.LONG, "1:MILLISECONDS:EPOCH", "1:MILLISECONDS").build(); + _schemaWithComplexType = + new Schema.SchemaBuilder().setSchemaName("mySchema") + .addSingleValueDimension("campaign", DataType.JSON) + .addSingleValueDimension("campaign.inner1", DataType.STRING) + .addSingleValueDimension("campaign.inner1.inner2", DataType.STRING) + .addSingleValueDimension("targetusers.user", DataType.STRING) // NOTE: Intentionally put 1000 as default value to test skipping null values during rollup .addMetric("clicks", DataType.INT, 1000) .addDateTime("time", DataType.LONG, "1:MILLISECONDS:EPOCH", "1:MILLISECONDS").build(); - // create segments in many folders _singleSegment = createInputSegments(new File(TEMP_DIR, "single_segment"), _rawData, 1, _schema); _multipleSegments = createInputSegments(new File(TEMP_DIR, "multiple_segments"), _rawData, 3, _schema); _multiValueSegments = createInputSegments(new File(TEMP_DIR, "multi_value_segment"), _rawDataMultiValue, 1, _schemaMV); + _recordReaderWithComplexType = createRecordReaderWithComplexType(); } private List createInputSegments(File inputDir, List rawData, int numSegments, Schema schema) @@ -160,6 +189,33 @@ private List createInputSegments(File inputDir, List raw return segmentRecordReaders; } + private List createRecordReaderWithComplexType() { + GenericRow genericRow = new GenericRow(); + genericRow.putValue("a", 1L); + Map map1 = new HashMap<>(); + genericRow.putValue("campaign", map1); + map1.put("inner", "innerv"); + Map innerMap1 = new HashMap<>(); + innerMap1.put("inner2", "inner2v"); + + map1.put("inner1", innerMap1); + Map map2 = new HashMap<>(); + map2.put("c", 3); + genericRow.putValue("map2", map2); + + + //list with two map entries inside + List> list = new ArrayList<>(); + Map map3 = new HashMap<>(); + map3.put("user", "foobar"); + list.add(map3); + Map map4 = new HashMap<>(); + map4.put("user", "barfoo"); + list.add(map4); + genericRow.putValue("targetusers", list); + return List.of(new GenericRowRecordReader(List.of(genericRow))); + } + private GenericRow getGenericRow(Object[] rawRow) { GenericRow row = new GenericRow(); row.putValue("campaign", rawRow[0]); @@ -175,6 +231,76 @@ private void rewindRecordReaders(List recordReaders) } } + /** + * Test lazy initialization of record readers. Here we create + * RecoderReaderFileConfig and the actual reader is initialized during the + * map phase. + * @throws Exception + */ + @Test + public void testRecordReaderFileConfigInit() throws Exception { + File workingDir = new File(TEMP_DIR, "segmentOutput"); + FileUtils.forceMkdir(workingDir); + ClassLoader classLoader = getClass().getClassLoader(); + URL resource = classLoader.getResource("data/dimBaseballTeams.csv"); + RecordReader recordReader = RecordReaderFactory.getRecordReader(FileFormat.CSV, new File(resource.toURI()), + null, null); + RecordReaderFileConfig recordReaderFileConfig = new RecordReaderFileConfig(FileFormat.CSV, + new File(resource.toURI()), + null, null, recordReader); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName("myTable"). + setTimeColumnName("time").build(); + + Schema schema = + new Schema.SchemaBuilder().setSchemaName("mySchema").addSingleValueDimension("teamId", + DataType.STRING, "") + .addSingleValueDimension("teamName", DataType.STRING, "") + .addDateTime("time", DataType.LONG, "1:MILLISECONDS:EPOCH", "1:MILLISECONDS").build(); + + SegmentProcessorConfig config = + new SegmentProcessorConfig.Builder().setTableConfig(tableConfig).setSchema(schema).build(); + SegmentProcessorFramework framework = new SegmentProcessorFramework(config, workingDir, + ImmutableList.of(recordReaderFileConfig), Collections.emptyList(), null); + List outputSegments = framework.process(); + assertEquals(outputSegments.size(), 1); + ImmutableSegment segment = ImmutableSegmentLoader.load(outputSegments.get(0), ReadMode.mmap); + SegmentMetadata segmentMetadata = segment.getSegmentMetadata(); + assertEquals(segmentMetadata.getTotalDocs(), 52); + // Verify reader is closed + assertEquals(recordReaderFileConfig.isRecordReaderClosedFromRecordReaderFileConfig(), true); + } + + @Test + public void testSegmentGenerationWithComplexType() throws Exception { + File workingDir = new File(TEMP_DIR, "single_segment_complex_type_output"); + FileUtils.forceMkdir(workingDir); + IngestionConfig ingestionConfig = new IngestionConfig(); + List fieldsToUnnest = new ArrayList<>(); + fieldsToUnnest.add("targetusers"); + + ingestionConfig.setComplexTypeConfig( + new ComplexTypeConfig(fieldsToUnnest, ".", null, null)); + _tableConfig.setIngestionConfig(ingestionConfig); + // Default configs + SegmentProcessorConfig config = + new SegmentProcessorConfig.Builder().setTableConfig(_tableConfig).setSchema(_schemaWithComplexType).build(); + SegmentProcessorFramework framework = + new SegmentProcessorFramework(_recordReaderWithComplexType, config, workingDir); + List outputSegments = framework.process(); + ImmutableSegment segment = ImmutableSegmentLoader.load(outputSegments.get(0), ReadMode.mmap); + SegmentMetadata segmentMetadata = segment.getSegmentMetadata(); + // Pick the column created from complex type + ColumnMetadata campaignInner2Metadata = segmentMetadata.getColumnMetadataFor("campaign.inner1.inner2"); + // Verify we see a specific value parsed from the complexType + Assert.assertEquals(campaignInner2Metadata.getMinValue().compareTo("inner2v"), 0); + ColumnMetadata campaignMetadata = segmentMetadata.getColumnMetadataFor("campaign"); + Assert.assertEquals( + campaignMetadata.getMinValue().compareTo("{\"inner1\":{\"inner2\":\"inner2v\"},\"inner\":\"innerv\"}"), 0); + + ColumnMetadata listMetadata = segmentMetadata.getColumnMetadataFor("targetusers.user"); + Assert.assertEquals(listMetadata.getMinValue().compareTo("barfoo"), 0); + } + @Test public void testSingleSegment() throws Exception { @@ -219,6 +345,8 @@ public void testSingleSegment() framework = new SegmentProcessorFramework(_singleSegment, config, workingDir); outputSegments = framework.process(); assertEquals(outputSegments.size(), 1); + String[] outputDirs = workingDir.list(); + assertTrue(outputDirs != null && outputDirs.length == 1, Arrays.toString(outputDirs)); segment = ImmutableSegmentLoader.load(outputSegments.get(0), ReadMode.mmap); segmentMetadata = segment.getSegmentMetadata(); assertEquals(segmentMetadata.getTotalDocs(), 10); @@ -255,6 +383,8 @@ public void testSingleSegment() framework = new SegmentProcessorFramework(_singleSegment, config, workingDir); outputSegments = framework.process(); assertEquals(outputSegments.size(), 1); + outputDirs = workingDir.list(); + assertTrue(outputDirs != null && outputDirs.length == 1, Arrays.toString(outputDirs)); segment = ImmutableSegmentLoader.load(outputSegments.get(0), ReadMode.mmap); segmentMetadata = segment.getSegmentMetadata(); assertEquals(segmentMetadata.getName(), "myTable_segment_0001"); @@ -269,6 +399,8 @@ public void testSingleSegment() framework = new SegmentProcessorFramework(_singleSegment, config, workingDir); outputSegments = framework.process(); assertEquals(outputSegments.size(), 1); + outputDirs = workingDir.list(); + assertTrue(outputDirs != null && outputDirs.length == 1, Arrays.toString(outputDirs)); segmentMetadata = new SegmentMetadataImpl(outputSegments.get(0)); assertEquals(segmentMetadata.getTotalDocs(), 5); timeMetadata = segmentMetadata.getColumnMetadataFor("time"); @@ -286,6 +418,8 @@ public void testSingleSegment() framework = new SegmentProcessorFramework(_singleSegment, config, workingDir); outputSegments = framework.process(); assertEquals(outputSegments.size(), 1); + outputDirs = workingDir.list(); + assertTrue(outputDirs != null && outputDirs.length == 1, Arrays.toString(outputDirs)); segmentMetadata = new SegmentMetadataImpl(outputSegments.get(0)); assertEquals(segmentMetadata.getTotalDocs(), 5); timeMetadata = segmentMetadata.getColumnMetadataFor("time"); @@ -303,6 +437,8 @@ public void testSingleSegment() framework = new SegmentProcessorFramework(_singleSegment, config, workingDir); outputSegments = framework.process(); assertTrue(outputSegments.isEmpty()); + outputDirs = workingDir.list(); + assertTrue(outputDirs != null && outputDirs.length == 1, Arrays.toString(outputDirs)); FileUtils.cleanDirectory(workingDir); rewindRecordReaders(_singleSegment); @@ -313,6 +449,8 @@ public void testSingleSegment() framework = new SegmentProcessorFramework(_singleSegment, config, workingDir); outputSegments = framework.process(); assertEquals(outputSegments.size(), 1); + outputDirs = workingDir.list(); + assertTrue(outputDirs != null && outputDirs.length == 1, Arrays.toString(outputDirs)); segmentMetadata = new SegmentMetadataImpl(outputSegments.get(0)); assertEquals(segmentMetadata.getTotalDocs(), 10); timeMetadata = segmentMetadata.getColumnMetadataFor("time"); @@ -329,6 +467,8 @@ public void testSingleSegment() framework = new SegmentProcessorFramework(_singleSegment, config, workingDir); outputSegments = framework.process(); assertEquals(outputSegments.size(), 3); + outputDirs = workingDir.list(); + assertTrue(outputDirs != null && outputDirs.length == 1, Arrays.toString(outputDirs)); outputSegments.sort(null); // segment 0 segmentMetadata = new SegmentMetadataImpl(outputSegments.get(0)); @@ -366,6 +506,8 @@ public void testSingleSegment() framework = new SegmentProcessorFramework(_singleSegment, config, workingDir); outputSegments = framework.process(); assertEquals(outputSegments.size(), 2); + outputDirs = workingDir.list(); + assertTrue(outputDirs != null && outputDirs.length == 1, Arrays.toString(outputDirs)); outputSegments.sort(null); // segment 0 segment = ImmutableSegmentLoader.load(outputSegments.get(0), ReadMode.mmap); @@ -429,6 +571,8 @@ public void testSingleSegment() framework = new SegmentProcessorFramework(_singleSegment, config, workingDir); outputSegments = framework.process(); assertEquals(outputSegments.size(), 1); + outputDirs = workingDir.list(); + assertTrue(outputDirs != null && outputDirs.length == 1, Arrays.toString(outputDirs)); segmentMetadata = new SegmentMetadataImpl(outputSegments.get(0)); assertEquals(segmentMetadata.getTotalDocs(), 8); assertEquals(segmentMetadata.getName(), "myTable_1597708800000_1597881600000_0"); @@ -442,6 +586,8 @@ public void testSingleSegment() framework = new SegmentProcessorFramework(_singleSegment, config, workingDir); outputSegments = framework.process(); assertEquals(outputSegments.size(), 3); + outputDirs = workingDir.list(); + assertTrue(outputDirs != null && outputDirs.length == 1, Arrays.toString(outputDirs)); outputSegments.sort(null); segmentMetadata = new SegmentMetadataImpl(outputSegments.get(0)); assertEquals(segmentMetadata.getTotalDocs(), 4); @@ -462,6 +608,8 @@ public void testSingleSegment() framework = new SegmentProcessorFramework(_singleSegment, config, workingDir); outputSegments = framework.process(); assertEquals(outputSegments.size(), 3); + outputDirs = workingDir.list(); + assertTrue(outputDirs != null && outputDirs.length == 1, Arrays.toString(outputDirs)); outputSegments.sort(null); segmentMetadata = new SegmentMetadataImpl(outputSegments.get(0)); assertEquals(segmentMetadata.getTotalDocs(), 4); @@ -488,6 +636,8 @@ public void testMultipleSegments() SegmentProcessorFramework framework = new SegmentProcessorFramework(_multipleSegments, config, workingDir); List outputSegments = framework.process(); assertEquals(outputSegments.size(), 1); + String[] outputDirs = workingDir.list(); + assertTrue(outputDirs != null && outputDirs.length == 1, Arrays.toString(outputDirs)); SegmentMetadata segmentMetadata = new SegmentMetadataImpl(outputSegments.get(0)); assertEquals(segmentMetadata.getTotalDocs(), 10); assertEquals(segmentMetadata.getName(), "myTable_1597719600000_1597892400000_0"); @@ -501,6 +651,8 @@ public void testMultipleSegments() framework = new SegmentProcessorFramework(_multipleSegments, config, workingDir); outputSegments = framework.process(); assertEquals(outputSegments.size(), 3); + outputDirs = workingDir.list(); + assertTrue(outputDirs != null && outputDirs.length == 1, Arrays.toString(outputDirs)); outputSegments.sort(null); segmentMetadata = new SegmentMetadataImpl(outputSegments.get(0)); assertEquals(segmentMetadata.getTotalDocs(), 2); @@ -515,6 +667,207 @@ public void testMultipleSegments() rewindRecordReaders(_multipleSegments); } + @Test + public void testConfigurableMapperOutputSize() + throws Exception { + File workingDir = new File(TEMP_DIR, "configurable_mapper_test_output"); + FileUtils.forceMkdir(workingDir); + int expectedTotalDocsCount = 10; + + // Test 1 : Default case i.e. no limit to mapper output file size (single record reader). + + SegmentProcessorConfig config = + new SegmentProcessorConfig.Builder().setTableConfig(_tableConfig).setSchema(_schema).build(); + SegmentProcessorFramework framework = new SegmentProcessorFramework(_singleSegment, config, workingDir); + List outputSegments = framework.process(); + assertEquals(outputSegments.size(), 1); + String[] outputDirs = workingDir.list(); + assertTrue(outputDirs != null && outputDirs.length == 1, Arrays.toString(outputDirs)); + SegmentMetadata segmentMetadata = new SegmentMetadataImpl(outputSegments.get(0)); + assertEquals(segmentMetadata.getTotalDocs(), expectedTotalDocsCount); + assertEquals(segmentMetadata.getName(), "myTable_1597719600000_1597892400000_0"); + FileUtils.cleanDirectory(workingDir); + rewindRecordReaders(_singleSegment); + + // Test 2 : Default case i.e. no limit to mapper output file size (multiple record readers). + config = new SegmentProcessorConfig.Builder().setTableConfig(_tableConfig).setSchema(_schema).build(); + framework = new SegmentProcessorFramework(_multipleSegments, config, workingDir); + outputSegments = framework.process(); + assertEquals(outputSegments.size(), 1); + outputDirs = workingDir.list(); + assertTrue(outputDirs != null && outputDirs.length == 1, Arrays.toString(outputDirs)); + segmentMetadata = new SegmentMetadataImpl(outputSegments.get(0)); + assertEquals(segmentMetadata.getTotalDocs(), expectedTotalDocsCount); + assertEquals(segmentMetadata.getName(), "myTable_1597719600000_1597892400000_0"); + FileUtils.cleanDirectory(workingDir); + rewindRecordReaders(_multipleSegments); + + // Test 3 : Test mapper with threshold output size (single record reader). + + // Create a segmentConfig with intermediate mapper output size threshold set to the number of bytes in each row + // from the data. In this way, we can test if each row is written to a separate segment. + SegmentConfig segmentConfig = + new SegmentConfig.Builder().setIntermediateFileSizeThreshold(16).setSegmentNamePrefix("testPrefix") + .setSegmentNamePostfix("testPostfix").build(); + config = new SegmentProcessorConfig.Builder().setSegmentConfig(segmentConfig).setTableConfig(_tableConfig) + .setSchema(_schema).build(); + framework = new SegmentProcessorFramework(_singleSegment, config, workingDir); + outputSegments = framework.process(); + assertEquals(outputSegments.size(), expectedTotalDocsCount); + outputDirs = workingDir.list(); + assertTrue(outputDirs != null && outputDirs.length == 1, Arrays.toString(outputDirs)); + + // Verify that each segment has only one row, and the segment name is correct. + + for (int i = 0; i < expectedTotalDocsCount; i++) { + segmentMetadata = new SegmentMetadataImpl(outputSegments.get(i)); + assertEquals(segmentMetadata.getTotalDocs(), 1); + assertTrue(segmentMetadata.getName().matches("testPrefix_.*_testPostfix_" + i)); + } + FileUtils.cleanDirectory(workingDir); + rewindRecordReaders(_singleSegment); + + // Test 4 : Test mapper with threshold output size (multiple record readers). + + // Create a segmentConfig with intermediate mapper output size threshold set to the number of bytes in each row + // from the data. In this way, we can test if each row is written to a separate segment. + segmentConfig = new SegmentConfig.Builder().setIntermediateFileSizeThreshold(16).setSegmentNamePrefix("testPrefix") + .setSegmentNamePostfix("testPostfix").build(); + config = new SegmentProcessorConfig.Builder().setSegmentConfig(segmentConfig).setTableConfig(_tableConfig) + .setSchema(_schema).build(); + framework = new SegmentProcessorFramework(_multipleSegments, config, workingDir); + outputSegments = framework.process(); + assertEquals(outputSegments.size(), expectedTotalDocsCount); + outputDirs = workingDir.list(); + assertTrue(outputDirs != null && outputDirs.length == 1, Arrays.toString(outputDirs)); + + // Verify that each segment has only one row, and the segment name is correct. + + for (int i = 0; i < expectedTotalDocsCount; i++) { + segmentMetadata = new SegmentMetadataImpl(outputSegments.get(i)); + assertEquals(segmentMetadata.getTotalDocs(), 1); + assertTrue(segmentMetadata.getName().matches("testPrefix_.*_testPostfix_" + i)); + } + FileUtils.cleanDirectory(workingDir); + rewindRecordReaders(_multipleSegments); + + // Test 5 : Test with injected failure in mapper to verify output directory is cleaned up. + + List testList = new ArrayList<>(_multipleSegments); + testList.set(1, null); + segmentConfig = new SegmentConfig.Builder().setIntermediateFileSizeThreshold(16).setSegmentNamePrefix("testPrefix") + .setSegmentNamePostfix("testPostfix").build(); + config = new SegmentProcessorConfig.Builder().setSegmentConfig(segmentConfig).setTableConfig(_tableConfig) + .setSchema(_schema).build(); + SegmentProcessorFramework failureTest = new SegmentProcessorFramework(testList, config, workingDir); + assertThrows(NullPointerException.class, failureTest::process); + assertTrue(FileUtils.isEmptyDirectory(workingDir)); + rewindRecordReaders(_multipleSegments); + + // Test 6: RecordReader should be closed when recordReader is created inside RecordReaderFileConfig (without mapper + // output size threshold configured). + + ClassLoader classLoader = getClass().getClassLoader(); + URL resource = classLoader.getResource("data/dimBaseballTeams.csv"); + RecordReaderFileConfig recordReaderFileConfig = + new RecordReaderFileConfig(FileFormat.CSV, new File(resource.toURI()), null, null, null); + TableConfig tableConfig = + new TableConfigBuilder(TableType.OFFLINE).setTableName("myTable").setTimeColumnName("time").build(); + Schema schema = + new Schema.SchemaBuilder().setSchemaName("mySchema").addSingleValueDimension("teamId", DataType.STRING, "") + .addSingleValueDimension("teamName", DataType.STRING, "") + .addDateTime("time", DataType.LONG, "1:MILLISECONDS:EPOCH", "1:MILLISECONDS").build(); + + config = new SegmentProcessorConfig.Builder().setTableConfig(tableConfig).setSchema(schema).build(); + + SegmentProcessorFramework frameworkWithRecordReaderFileConfig = + new SegmentProcessorFramework(config, workingDir, ImmutableList.of(recordReaderFileConfig), + Collections.emptyList(), null); + outputSegments = frameworkWithRecordReaderFileConfig.process(); + + // Verify the number of segments created and the total docs. + assertEquals(outputSegments.size(), 1); + ImmutableSegment segment = ImmutableSegmentLoader.load(outputSegments.get(0), ReadMode.mmap); + segmentMetadata = segment.getSegmentMetadata(); + assertEquals(segmentMetadata.getTotalDocs(), 52); + + // Verify that the record reader is closed from RecordReaderFileConfig. + assertTrue(recordReaderFileConfig.isRecordReaderClosedFromRecordReaderFileConfig()); + FileUtils.cleanDirectory(workingDir); + + // Test 7: RecordReader should not be closed when recordReader is passed to RecordReaderFileConfig. (Without + // mapper output size threshold configured) + + RecordReader recordReader = recordReaderFileConfig.getRecordReader(); + recordReader.rewind(); + + // Pass the recordReader to RecordReaderFileConfig. + recordReaderFileConfig = new RecordReaderFileConfig(recordReader); + SegmentProcessorFramework frameworkWithDelegateRecordReader = + new SegmentProcessorFramework(config, workingDir, ImmutableList.of(recordReaderFileConfig), + Collections.emptyList(), null); + outputSegments = frameworkWithDelegateRecordReader.process(); + + // Verify the number of segments created and the total docs. + assertEquals(outputSegments.size(), 1); + segment = ImmutableSegmentLoader.load(outputSegments.get(0), ReadMode.mmap); + segmentMetadata = segment.getSegmentMetadata(); + assertEquals(segmentMetadata.getTotalDocs(), 52); + + // Verify that the record reader is not closed from RecordReaderFileConfig. + assertFalse(recordReaderFileConfig.isRecordReaderClosedFromRecordReaderFileConfig()); + FileUtils.cleanDirectory(workingDir); + + // Test 8: RecordReader should be closed when recordReader is created inside RecordReaderFileConfig (With mapper + // output size threshold configured). + + expectedTotalDocsCount = 52; + recordReaderFileConfig = new RecordReaderFileConfig(FileFormat.CSV, new File(resource.toURI()), null, null, null); + + segmentConfig = new SegmentConfig.Builder().setIntermediateFileSizeThreshold(19).setSegmentNamePrefix("testPrefix") + .setSegmentNamePostfix("testPostfix").build(); + config = new SegmentProcessorConfig.Builder().setSegmentConfig(segmentConfig).setTableConfig(tableConfig) + .setSchema(schema).build(); + + frameworkWithRecordReaderFileConfig = + new SegmentProcessorFramework(config, workingDir, ImmutableList.of(recordReaderFileConfig), + Collections.emptyList(), null); + outputSegments = frameworkWithRecordReaderFileConfig.process(); + + // Verify that each segment has only one row. + for (int i = 0; i < expectedTotalDocsCount; i++) { + segmentMetadata = new SegmentMetadataImpl(outputSegments.get(i)); + assertEquals(segmentMetadata.getTotalDocs(), 1); + } + + // Verify that the record reader is closed from RecordReaderFileConfig. + assertTrue(recordReaderFileConfig.isRecordReaderClosedFromRecordReaderFileConfig()); + FileUtils.cleanDirectory(workingDir); + + // Test 9: RecordReader should not be closed when recordReader is passed to RecordReaderFileConfig (With mapper + // output size threshold configured). + + recordReader = recordReaderFileConfig.getRecordReader(); + recordReader.rewind(); + + // Pass the recordReader to RecordReaderFileConfig. + recordReaderFileConfig = new RecordReaderFileConfig(recordReader); + frameworkWithDelegateRecordReader = + new SegmentProcessorFramework(config, workingDir, ImmutableList.of(recordReaderFileConfig), + Collections.emptyList(), null); + outputSegments = frameworkWithDelegateRecordReader.process(); + + // Verify that each segment has only one row. + for (int i = 0; i < expectedTotalDocsCount; i++) { + segmentMetadata = new SegmentMetadataImpl(outputSegments.get(i)); + assertEquals(segmentMetadata.getTotalDocs(), 1); + } + + // Verify that the record reader is not closed from RecordReaderFileConfig. + assertFalse(recordReaderFileConfig.isRecordReaderClosedFromRecordReaderFileConfig()); + FileUtils.cleanDirectory(workingDir); + } + @Test public void testMultiValue() throws Exception { @@ -528,6 +881,8 @@ public void testMultiValue() SegmentProcessorFramework framework = new SegmentProcessorFramework(_multiValueSegments, config, workingDir); List outputSegments = framework.process(); assertEquals(outputSegments.size(), 1); + String[] outputDirs = workingDir.list(); + assertTrue(outputDirs != null && outputDirs.length == 1, Arrays.toString(outputDirs)); ImmutableSegment segment = ImmutableSegmentLoader.load(outputSegments.get(0), ReadMode.mmap); SegmentMetadataImpl segmentMetadata = (SegmentMetadataImpl) segment.getSegmentMetadata(); assertEquals(segmentMetadata.getTotalDocs(), 2); @@ -562,6 +917,8 @@ public void testMultiValue() framework = new SegmentProcessorFramework(_multiValueSegments, config, workingDir); outputSegments = framework.process(); assertEquals(outputSegments.size(), 1); + outputDirs = workingDir.list(); + assertTrue(outputDirs != null && outputDirs.length == 1, Arrays.toString(outputDirs)); segment = ImmutableSegmentLoader.load(outputSegments.get(0), ReadMode.mmap); segmentMetadata = (SegmentMetadataImpl) segment.getSegmentMetadata(); assertEquals(segmentMetadata.getTotalDocs(), 2); diff --git a/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/BaseStarTreeV2Test.java b/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/BaseStarTreeV2Test.java index efe2196bb080..732b8bc570e9 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/BaseStarTreeV2Test.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/BaseStarTreeV2Test.java @@ -29,11 +29,11 @@ import java.util.Map; import java.util.Random; import java.util.Set; +import javax.annotation.Nullable; import org.apache.commons.io.FileUtils; import org.apache.pinot.common.request.context.ExpressionContext; import org.apache.pinot.core.common.BlockDocIdIterator; import org.apache.pinot.core.plan.FilterPlanNode; -import org.apache.pinot.core.plan.PlanNode; import org.apache.pinot.core.query.aggregation.function.AggregationFunction; import org.apache.pinot.core.query.request.context.QueryContext; import org.apache.pinot.core.query.request.context.utils.QueryContextConverterUtils; @@ -48,6 +48,7 @@ import org.apache.pinot.segment.spi.AggregationFunctionType; import org.apache.pinot.segment.spi.Constants; import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; import org.apache.pinot.segment.spi.datasource.DataSource; import org.apache.pinot.segment.spi.index.reader.Dictionary; @@ -55,6 +56,8 @@ import org.apache.pinot.segment.spi.index.reader.ForwardIndexReaderContext; import org.apache.pinot.segment.spi.index.startree.AggregationFunctionColumnPair; import org.apache.pinot.segment.spi.index.startree.StarTreeV2; +import org.apache.pinot.spi.config.table.FieldConfig.CompressionCodec; +import org.apache.pinot.spi.config.table.StarTreeAggregationConfig; import org.apache.pinot.spi.config.table.StarTreeIndexConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; @@ -90,29 +93,45 @@ abstract class BaseStarTreeV2Test { private static final int NUM_SEGMENT_RECORDS = 100_000; private static final int MAX_LEAF_RECORDS = RANDOM.nextInt(100) + 1; - private static final String DIMENSION_D1 = "d1"; - private static final String DIMENSION_D2 = "d2"; + // Using column names with '__' to make sure regular table columns with '__' in the name aren't wrongly interpreted + // as AggregationFunctionColumnPair + private static final String DIMENSION1 = "d1__COLUMN_NAME"; + private static final String DIMENSION2 = "DISTINCTCOUNTRAWHLL__d2"; private static final int DIMENSION_CARDINALITY = 100; private static final String METRIC = "m"; // Supported filters - private static final String QUERY_FILTER_AND = " WHERE d1 = 0 AND d2 < 10"; + private static final String QUERY_FILTER_AND = String.format(" WHERE %1$s = 0 AND %2$s < 10", DIMENSION1, DIMENSION2); // StarTree supports OR predicates only on a single dimension - private static final String QUERY_FILTER_OR = " WHERE d1 > 10 OR d1 < 50"; - private static final String QUERY_FILTER_COMPLEX_OR_MULTIPLE_DIMENSIONS = " WHERE d2 < 95 AND (d1 > 10 OR d1 < 50)"; + private static final String QUERY_FILTER_OR = String.format(" WHERE %1$s > 10 OR %1$s < 50", DIMENSION1); + private static final String QUERY_FILTER_NOT = String.format(" WHERE NOT %s > 10", DIMENSION1); + private static final String QUERY_FILTER_AND_NOT = + String.format(" WHERE %1$s > 10 AND NOT %2$s < 10", DIMENSION1, DIMENSION2); + private static final String QUERY_FILTER_OR_NOT = String.format(" WHERE %1$s > 50 OR NOT %1$s > 10", DIMENSION1); + private static final String QUERY_NOT_NOT = String.format(" WHERE NOT NOT %s > 10", DIMENSION1); + private static final String QUERY_FILTER_COMPLEX_OR_MULTIPLE_DIMENSIONS = + String.format(" WHERE %2$s < 95 AND (NOT %1$s > 10 OR %1$s > 50)", DIMENSION1, DIMENSION2); private static final String QUERY_FILTER_COMPLEX_AND_MULTIPLE_DIMENSIONS_THREE_PREDICATES = - " WHERE d2 < 95 AND d2 > 25 AND (d1 > 10 OR d1 < 50)"; + String.format(" WHERE %2$s < 95 AND NOT %2$s < 25 AND (%1$s > 10 OR %1$s < 50)", DIMENSION1, DIMENSION2); private static final String QUERY_FILTER_COMPLEX_OR_MULTIPLE_DIMENSIONS_THREE_PREDICATES = - " WHERE (d2 > 95 OR d2 < 25) AND (d1 > 10 OR d1 < 50)"; - private static final String QUERY_FILTER_COMPLEX_OR_SINGLE_DIMENSION = " WHERE d1 = 95 AND (d1 > 90 OR d1 < 100)"; + String.format(" WHERE (%2$s > 95 OR %2$s < 25) AND (%1$s > 10 OR %1$s < 50)", DIMENSION1, DIMENSION2); + private static final String QUERY_FILTER_COMPLEX_OR_SINGLE_DIMENSION = + String.format(" WHERE NOT %1$s = 95 AND (%1$s > 90 OR %1$s < 100)", DIMENSION1); // Unsupported filters - private static final String QUERY_FILTER_OR_MULTIPLE_DIMENSIONS = " WHERE d1 > 10 OR d2 < 50"; - private static final String QUERY_FILTER_OR_ON_AND = " WHERE (d1 > 10 AND d1 < 50) OR d1 < 50"; - private static final String QUERY_FILTER_OR_ON_NOT = " WHERE (NOT d1 > 10) OR d1 < 50"; - - private static final String QUERY_GROUP_BY = " GROUP BY d2"; - private static final String FILTER_AGG_CLAUSE = " FILTER(WHERE d1 > 10)"; + private static final String QUERY_FILTER_OR_MULTIPLE_DIMENSIONS = + String.format(" WHERE %1$s > 10 OR %2$s < 50", DIMENSION1, DIMENSION2); + private static final String QUERY_FILTER_OR_ON_AND = + String.format(" WHERE (%1$s > 10 AND %1$s < 50) OR %1$s < 50", DIMENSION1); + private static final String QUERY_FILTER_NOT_ON_AND = + String.format(" WHERE NOT (%1$s > 10 AND %1$s < 50)", DIMENSION1); + private static final String QUERY_FILTER_NOT_ON_OR = String.format(" WHERE NOT (%1$s < 10 OR %1$s > 50)", DIMENSION1); + // Always false filters + private static final String QUERY_FILTER_ALWAYS_FALSE = String.format(" WHERE %s > 100", DIMENSION1); + private static final String QUERY_FILTER_OR_ALWAYS_FALSE = String.format(" WHERE %1$s > 100 OR %1$s < 0", DIMENSION1); + + private static final String QUERY_GROUP_BY = " GROUP BY " + DIMENSION2; + private static final String FILTER_AGG_CLAUSE = String.format(" FILTER(WHERE %s > 10)", DIMENSION1); private ValueAggregator _valueAggregator; private DataType _aggregatedValueType; @@ -125,19 +144,10 @@ public void setUp() throws Exception { _valueAggregator = getValueAggregator(); _aggregatedValueType = _valueAggregator.getAggregatedValueType(); - AggregationFunctionType aggregationType = _valueAggregator.getAggregationType(); - if (aggregationType == AggregationFunctionType.COUNT) { - _aggregation = "COUNT(*)"; - } else if (aggregationType == AggregationFunctionType.PERCENTILEEST - || aggregationType == AggregationFunctionType.PERCENTILETDIGEST) { - // Append a percentile number for percentile functions - _aggregation = String.format("%s(%s, 50)", aggregationType.getName(), METRIC); - } else { - _aggregation = String.format("%s(%s)", aggregationType.getName(), METRIC); - } + _aggregation = getAggregation(_valueAggregator.getAggregationType()); - Schema.SchemaBuilder schemaBuilder = new Schema.SchemaBuilder().addSingleValueDimension(DIMENSION_D1, DataType.INT) - .addSingleValueDimension(DIMENSION_D2, DataType.INT); + Schema.SchemaBuilder schemaBuilder = new Schema.SchemaBuilder().addSingleValueDimension(DIMENSION1, DataType.INT) + .addSingleValueDimension(DIMENSION2, DataType.INT); DataType rawValueType = getRawValueType(); // Raw value type will be null for COUNT aggregation function if (rawValueType != null) { @@ -149,8 +159,8 @@ public void setUp() List segmentRecords = new ArrayList<>(NUM_SEGMENT_RECORDS); for (int i = 0; i < NUM_SEGMENT_RECORDS; i++) { GenericRow segmentRecord = new GenericRow(); - segmentRecord.putValue(DIMENSION_D1, RANDOM.nextInt(DIMENSION_CARDINALITY)); - segmentRecord.putValue(DIMENSION_D2, RANDOM.nextInt(DIMENSION_CARDINALITY)); + segmentRecord.putValue(DIMENSION1, RANDOM.nextInt(DIMENSION_CARDINALITY)); + segmentRecord.putValue(DIMENSION2, RANDOM.nextInt(DIMENSION_CARDINALITY)); if (rawValueType != null) { segmentRecord.putValue(METRIC, getRandomRawValue(RANDOM)); } @@ -164,10 +174,9 @@ public void setUp() driver.init(segmentGeneratorConfig, new GenericRowRecordReader(segmentRecords)); driver.build(); - StarTreeIndexConfig starTreeIndexConfig = new StarTreeIndexConfig(Arrays.asList(DIMENSION_D1, DIMENSION_D2), null, - Collections.singletonList( - new AggregationFunctionColumnPair(_valueAggregator.getAggregationType(), METRIC).toColumnName()), - MAX_LEAF_RECORDS); + StarTreeIndexConfig starTreeIndexConfig = new StarTreeIndexConfig(Arrays.asList(DIMENSION1, DIMENSION2), null, null, + Collections.singletonList(new StarTreeAggregationConfig(METRIC, _valueAggregator.getAggregationType().getName(), + getCompressionCodec(), true, getIndexVersion(), null, null)), MAX_LEAF_RECORDS); File indexDir = new File(TEMP_DIR, SEGMENT_NAME); // Randomly build star-tree using on-heap or off-heap mode MultipleTreesBuilder.BuildMode buildMode = @@ -181,12 +190,27 @@ public void setUp() _starTreeV2 = _indexSegment.getStarTrees().get(0); } + String getAggregation(AggregationFunctionType aggregationType) { + if (aggregationType == AggregationFunctionType.COUNT) { + return "COUNT(*)"; + } else if (aggregationType == AggregationFunctionType.PERCENTILEEST + || aggregationType == AggregationFunctionType.PERCENTILETDIGEST) { + // Append a percentile number for percentile functions + return String.format("%s(%s, 50)", aggregationType.getName(), METRIC); + } else { + return String.format("%s(%s)", aggregationType.getName(), METRIC); + } + } + @Test public void testUnsupportedFilters() { String query = String.format("SELECT %s FROM %s", _aggregation, TABLE_NAME); testUnsupportedFilter(query + QUERY_FILTER_OR_MULTIPLE_DIMENSIONS); testUnsupportedFilter(query + QUERY_FILTER_OR_ON_AND); - testUnsupportedFilter(query + QUERY_FILTER_OR_ON_NOT); + testUnsupportedFilter(query + QUERY_FILTER_NOT_ON_AND); + testUnsupportedFilter(query + QUERY_FILTER_NOT_ON_OR); + testUnsupportedFilter(query + QUERY_FILTER_ALWAYS_FALSE); + testUnsupportedFilter(query + QUERY_FILTER_OR_ALWAYS_FALSE); } @Test @@ -198,6 +222,10 @@ public void testQueries() testQuery(query); testQuery(query + QUERY_FILTER_AND); testQuery(query + QUERY_FILTER_OR); + testQuery(query + QUERY_FILTER_NOT); + testQuery(query + QUERY_FILTER_AND_NOT); + testQuery(query + QUERY_FILTER_OR_NOT); + testQuery(query + QUERY_NOT_NOT); testQuery(query + QUERY_FILTER_COMPLEX_OR_MULTIPLE_DIMENSIONS); testQuery(query + QUERY_FILTER_COMPLEX_AND_MULTIPLE_DIMENSIONS_THREE_PREDICATES); testQuery(query + QUERY_FILTER_COMPLEX_OR_MULTIPLE_DIMENSIONS_THREE_PREDICATES); @@ -216,7 +244,7 @@ public void tearDown() private void testUnsupportedFilter(String query) { QueryContext queryContext = QueryContextConverterUtils.getQueryContext(query); - FilterPlanNode filterPlanNode = new FilterPlanNode(_indexSegment, queryContext); + FilterPlanNode filterPlanNode = new FilterPlanNode(new SegmentContext(_indexSegment), queryContext); filterPlanNode.run(); Map> predicateEvaluatorsMap = StarTreeUtils.extractPredicateEvaluatorsMap(_indexSegment, queryContext.getFilter(), @@ -248,7 +276,7 @@ private void testQuery(String query) List groupByColumns = new ArrayList<>(groupByColumnSet); // Filter - FilterPlanNode filterPlanNode = new FilterPlanNode(_indexSegment, queryContext); + FilterPlanNode filterPlanNode = new FilterPlanNode(new SegmentContext(_indexSegment), queryContext); filterPlanNode.run(); Map> predicateEvaluatorsMap = StarTreeUtils.extractPredicateEvaluatorsMap(_indexSegment, queryContext.getFilter(), @@ -256,7 +284,7 @@ private void testQuery(String query) assertNotNull(predicateEvaluatorsMap); // Extract values with star-tree - PlanNode starTreeFilterPlanNode = + StarTreeFilterPlanNode starTreeFilterPlanNode = new StarTreeFilterPlanNode(queryContext, _starTreeV2, predicateEvaluatorsMap, groupByColumnSet); List starTreeAggregationColumnReaders = new ArrayList<>(numAggregations); for (AggregationFunctionColumnPair aggregationFunctionColumnPair : aggregationFunctionColumnPairs) { @@ -271,7 +299,7 @@ private void testQuery(String query) computeStarTreeResult(starTreeFilterPlanNode, starTreeAggregationColumnReaders, starTreeGroupByColumnReaders); // Extract values without star-tree - PlanNode nonStarTreeFilterPlanNode = new FilterPlanNode(_indexSegment, queryContext); + FilterPlanNode nonStarTreeFilterPlanNode = new FilterPlanNode(new SegmentContext(_indexSegment), queryContext); List nonStarTreeAggregationColumnReaders = new ArrayList<>(numAggregations); List nonStarTreeAggregationColumnDictionaries = new ArrayList<>(numAggregations); for (AggregationFunctionColumnPair aggregationFunctionColumnPair : aggregationFunctionColumnPairs) { @@ -305,7 +333,7 @@ private void testQuery(String query) } } - private Map, List> computeStarTreeResult(PlanNode starTreeFilterPlanNode, + private Map, List> computeStarTreeResult(StarTreeFilterPlanNode starTreeFilterPlanNode, List aggregationColumnReaders, List groupByColumnReaders) throws IOException { Map, List> result = new HashMap<>(); @@ -372,7 +400,7 @@ private Object getAggregatedValue(int docId, ForwardIndexReader reader, ForwardI } } - private Map, List> computeNonStarTreeResult(PlanNode nonStarTreeFilterPlanNode, + private Map, List> computeNonStarTreeResult(FilterPlanNode nonStarTreeFilterPlanNode, List aggregationColumnReaders, List aggregationColumnDictionaries, List groupByColumnReaders) throws IOException { @@ -451,6 +479,26 @@ private Object getNextRawValue(int docId, ForwardIndexReader reader, ForwardInde return dictionary.get(reader.getDictId(docId, readerContext)); } + /** + * Can be overridden to force the compression codec. + */ + @Nullable + CompressionCodec getCompressionCodec() { + CompressionCodec[] compressionCodecs = CompressionCodec.values(); + CompressionCodec compressionCodec = compressionCodecs[RANDOM.nextInt(compressionCodecs.length)]; + return compressionCodec.isApplicableToRawIndex() ? compressionCodec : null; + } + + /** + * Can be overridden to force the index version. + */ + @Nullable + Integer getIndexVersion() { + // Allow 2, 3, 4 or null + int version = 1 + RANDOM.nextInt(4); + return version > 1 ? version : null; + } + abstract ValueAggregator getValueAggregator(); abstract DataType getRawValueType(); diff --git a/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/DistinctCountCPCSketchStarTreeV2Test.java b/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/DistinctCountCPCSketchStarTreeV2Test.java new file mode 100644 index 000000000000..c7129a71f21b --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/DistinctCountCPCSketchStarTreeV2Test.java @@ -0,0 +1,68 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.startree.v2; + +import java.util.Collections; +import java.util.Random; +import org.apache.datasketches.cpc.CpcSketch; +import org.apache.datasketches.cpc.CpcUnion; +import org.apache.pinot.segment.local.aggregator.DistinctCountCPCSketchValueAggregator; +import org.apache.pinot.segment.local.aggregator.ValueAggregator; +import org.apache.pinot.spi.data.FieldSpec.DataType; + +import static org.testng.Assert.assertEquals; + + +public class DistinctCountCPCSketchStarTreeV2Test extends BaseStarTreeV2Test { + + @Override + ValueAggregator getValueAggregator() { + return new DistinctCountCPCSketchValueAggregator(Collections.emptyList()); + } + + @Override + DataType getRawValueType() { + return DataType.INT; + } + + @Override + Object getRandomRawValue(Random random) { + return random.nextInt(100); + } + + @Override + void assertAggregatedValue(Object starTreeResult, Object nonStarTreeResult) { + // Use error at (lgK=12, stddev=2) from: + // https://datasketches.apache.org/docs/CPC/CpcPerformance.html + double delta = (1 << 12) * 0.01; + assertEquals((long) toSketch(starTreeResult).getEstimate(), (long) toSketch(nonStarTreeResult).getEstimate(), + delta); + } + + private CpcSketch toSketch(Object value) { + if (value instanceof CpcUnion) { + return ((CpcUnion) value).getResult(); + } else if (value instanceof CpcSketch) { + return (CpcSketch) value; + } else { + throw new IllegalStateException( + "Unsupported data type for CPC Sketch aggregation: " + value.getClass().getSimpleName()); + } + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/DistinctCountHLLStarTreeV2Test.java b/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/DistinctCountHLLStarTreeV2Test.java index d903f505b413..9bc103ffd247 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/DistinctCountHLLStarTreeV2Test.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/DistinctCountHLLStarTreeV2Test.java @@ -19,6 +19,7 @@ package org.apache.pinot.core.startree.v2; import com.clearspring.analytics.stream.cardinality.HyperLogLog; +import java.util.Collections; import java.util.Random; import org.apache.pinot.segment.local.aggregator.DistinctCountHLLValueAggregator; import org.apache.pinot.segment.local.aggregator.ValueAggregator; @@ -31,7 +32,7 @@ public class DistinctCountHLLStarTreeV2Test extends BaseStarTreeV2Test getValueAggregator() { - return new DistinctCountHLLValueAggregator(); + return new DistinctCountHLLValueAggregator(Collections.emptyList()); } @Override diff --git a/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/DistinctCountIntegerSumTupleSketchStarTreeV2Test.java b/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/DistinctCountIntegerSumTupleSketchStarTreeV2Test.java new file mode 100644 index 000000000000..d10efb94595a --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/DistinctCountIntegerSumTupleSketchStarTreeV2Test.java @@ -0,0 +1,72 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.startree.v2; + +import java.util.Random; +import org.apache.datasketches.tuple.Sketch; +import org.apache.datasketches.tuple.Union; +import org.apache.datasketches.tuple.aninteger.IntegerSketch; +import org.apache.datasketches.tuple.aninteger.IntegerSummary; +import org.apache.pinot.core.common.ObjectSerDeUtils; +import org.apache.pinot.segment.local.aggregator.IntegerTupleSketchValueAggregator; +import org.apache.pinot.segment.local.aggregator.ValueAggregator; +import org.apache.pinot.spi.data.FieldSpec.DataType; + +import static org.testng.Assert.assertEquals; + + +public class DistinctCountIntegerSumTupleSketchStarTreeV2Test extends BaseStarTreeV2Test { + + @Override + ValueAggregator getValueAggregator() { + return new IntegerTupleSketchValueAggregator(IntegerSummary.Mode.Sum); + } + + @Override + DataType getRawValueType() { + return DataType.BYTES; + } + + @Override + byte[] getRandomRawValue(Random random) { + IntegerSketch is = new IntegerSketch(4, IntegerSummary.Mode.Sum); + is.update(random.nextInt(100), random.nextInt(100)); + return ObjectSerDeUtils.DATA_SKETCH_INT_TUPLE_SER_DE.serialize(is.compact()); + } + + @Override + void assertAggregatedValue(Object starTreeResult, Object nonStarTreeResult) { + // Use error at (lgK=14, stddev=2) from: + // https://datasketches.apache.org/docs/Theta/ThetaErrorTable.html + double delta = (1 << 14) * 0.01563; + assertEquals(toSketch(starTreeResult).getEstimate(), toSketch(nonStarTreeResult).getEstimate(), delta); + } + + @SuppressWarnings("unchecked") + private Sketch toSketch(Object value) { + if (value instanceof Union) { + return ((Union) value).getResult(); + } else if (value instanceof Sketch) { + return ((Sketch) value); + } else { + throw new IllegalStateException( + "Unsupported data type for Integer Tuple Sketch aggregation: " + value.getClass().getSimpleName()); + } + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/DistinctCountRawHLLStarTreeV2Test.java b/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/DistinctCountRawHLLStarTreeV2Test.java new file mode 100644 index 000000000000..b1772d0e36af --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/DistinctCountRawHLLStarTreeV2Test.java @@ -0,0 +1,58 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.startree.v2; + +import com.clearspring.analytics.stream.cardinality.HyperLogLog; +import java.util.Collections; +import java.util.Random; +import org.apache.pinot.segment.local.aggregator.DistinctCountHLLValueAggregator; +import org.apache.pinot.segment.local.aggregator.ValueAggregator; +import org.apache.pinot.segment.spi.AggregationFunctionType; +import org.apache.pinot.spi.data.FieldSpec.DataType; + +import static org.testng.Assert.assertEquals; + + +public class DistinctCountRawHLLStarTreeV2Test extends BaseStarTreeV2Test { + + @Override + String getAggregation(AggregationFunctionType aggregationType) { + return "distinctCountRawHLL(m)"; + } + + @Override + ValueAggregator getValueAggregator() { + return new DistinctCountHLLValueAggregator(Collections.emptyList()); + } + + @Override + DataType getRawValueType() { + return DataType.INT; + } + + @Override + Object getRandomRawValue(Random random) { + return random.nextInt(100); + } + + @Override + void assertAggregatedValue(HyperLogLog starTreeResult, HyperLogLog nonStarTreeResult) { + assertEquals(starTreeResult.cardinality(), nonStarTreeResult.cardinality()); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/DistinctCountThetaSketchStarTreeV2Test.java b/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/DistinctCountThetaSketchStarTreeV2Test.java new file mode 100644 index 000000000000..9fd34dc8c075 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/DistinctCountThetaSketchStarTreeV2Test.java @@ -0,0 +1,66 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.startree.v2; + +import java.util.Random; +import org.apache.datasketches.theta.Sketch; +import org.apache.datasketches.theta.Union; +import org.apache.pinot.segment.local.aggregator.DistinctCountThetaSketchValueAggregator; +import org.apache.pinot.segment.local.aggregator.ValueAggregator; +import org.apache.pinot.spi.data.FieldSpec.DataType; + +import static org.testng.Assert.assertEquals; + + +public class DistinctCountThetaSketchStarTreeV2Test extends BaseStarTreeV2Test { + + @Override + ValueAggregator getValueAggregator() { + return new DistinctCountThetaSketchValueAggregator(); + } + + @Override + DataType getRawValueType() { + return DataType.INT; + } + + @Override + Object getRandomRawValue(Random random) { + return random.nextInt(100); + } + + @Override + void assertAggregatedValue(Object starTreeResult, Object nonStarTreeResult) { + // Use error at (lgK=14, stddev=2) from: + // https://datasketches.apache.org/docs/Theta/ThetaErrorTable.html + double delta = (1 << 14) * 0.01563; + assertEquals(toSketch(starTreeResult).getEstimate(), toSketch(nonStarTreeResult).getEstimate(), delta); + } + + private Sketch toSketch(Object value) { + if (value instanceof Union) { + return ((Union) value).getResult(); + } else if (value instanceof Sketch) { + return (Sketch) value; + } else { + throw new IllegalStateException( + "Unsupported data type for Theta Sketch aggregation: " + value.getClass().getSimpleName()); + } + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/DistinctCountULLStarTreeV2Test.java b/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/DistinctCountULLStarTreeV2Test.java new file mode 100644 index 000000000000..8f83162b5048 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/DistinctCountULLStarTreeV2Test.java @@ -0,0 +1,52 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.startree.v2; + +import com.dynatrace.hash4j.distinctcount.UltraLogLog; +import java.util.Collections; +import java.util.Random; +import org.apache.pinot.segment.local.aggregator.DistinctCountULLValueAggregator; +import org.apache.pinot.segment.local.aggregator.ValueAggregator; +import org.apache.pinot.spi.data.FieldSpec.DataType; + +import static org.testng.Assert.assertEquals; + + +public class DistinctCountULLStarTreeV2Test extends BaseStarTreeV2Test { + + @Override + ValueAggregator getValueAggregator() { + return new DistinctCountULLValueAggregator(Collections.emptyList()); + } + + @Override + DataType getRawValueType() { + return DataType.INT; + } + + @Override + Object getRandomRawValue(Random random) { + return random.nextInt(100); + } + + @Override + void assertAggregatedValue(UltraLogLog starTreeResult, UltraLogLog nonStarTreeResult) { + assertEquals(starTreeResult.getDistinctCountEstimate(), nonStarTreeResult.getDistinctCountEstimate()); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/PreAggregatedDistinctCountHLLStarTreeV2Test.java b/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/PreAggregatedDistinctCountHLLStarTreeV2Test.java index 354497b9cb75..531348c77157 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/PreAggregatedDistinctCountHLLStarTreeV2Test.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/PreAggregatedDistinctCountHLLStarTreeV2Test.java @@ -19,6 +19,7 @@ package org.apache.pinot.core.startree.v2; import com.clearspring.analytics.stream.cardinality.HyperLogLog; +import java.util.Collections; import java.util.Random; import org.apache.pinot.core.common.ObjectSerDeUtils; import org.apache.pinot.segment.local.aggregator.DistinctCountHLLValueAggregator; @@ -34,7 +35,7 @@ public class PreAggregatedDistinctCountHLLStarTreeV2Test extends BaseStarTreeV2T @Override ValueAggregator getValueAggregator() { - return new DistinctCountHLLValueAggregator(); + return new DistinctCountHLLValueAggregator(Collections.emptyList()); } @Override diff --git a/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/SumPrecisionStarTreeV2Test.java b/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/SumPrecisionStarTreeV2Test.java index f99aa3df1f0f..87c74aa59152 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/SumPrecisionStarTreeV2Test.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/startree/v2/SumPrecisionStarTreeV2Test.java @@ -19,6 +19,7 @@ package org.apache.pinot.core.startree.v2; import java.math.BigDecimal; +import java.util.Collections; import java.util.Random; import org.apache.pinot.segment.local.aggregator.SumPrecisionValueAggregator; import org.apache.pinot.segment.local.aggregator.ValueAggregator; @@ -31,7 +32,7 @@ public class SumPrecisionStarTreeV2Test extends BaseStarTreeV2Test getValueAggregator() { - return new SumPrecisionValueAggregator(); + return new SumPrecisionValueAggregator(Collections.emptyList()); } @Override diff --git a/pinot-core/src/test/java/org/apache/pinot/core/transport/QueryRoutingTest.java b/pinot-core/src/test/java/org/apache/pinot/core/transport/QueryRoutingTest.java index 5e1fa041768d..cec413e42484 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/transport/QueryRoutingTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/transport/QueryRoutingTest.java @@ -19,15 +19,19 @@ package org.apache.pinot.core.transport; import com.google.common.util.concurrent.Futures; +import java.io.IOException; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.commons.lang3.tuple.Pair; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.datatable.DataTable.MetadataKey; import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.metrics.ServerMetrics; import org.apache.pinot.common.request.BrokerRequest; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.common.datatable.DataTableBuilder; import org.apache.pinot.core.common.datatable.DataTableBuilderFactory; import org.apache.pinot.core.query.scheduler.QueryScheduler; import org.apache.pinot.core.transport.server.routing.stats.ServerRoutingStatsManager; @@ -38,6 +42,7 @@ import org.apache.pinot.sql.parsers.CalciteSqlCompiler; import org.apache.pinot.util.TestUtils; import org.testng.annotations.AfterClass; +import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -59,8 +64,8 @@ public class QueryRoutingTest { SERVER_INSTANCE.toServerRoutingInstance(TableType.REALTIME, ServerInstance.RoutingType.NETTY); private static final BrokerRequest BROKER_REQUEST = CalciteSqlCompiler.compileToBrokerRequest("SELECT * FROM testTable"); - private static final Map> ROUTING_TABLE = - Collections.singletonMap(SERVER_INSTANCE, Collections.emptyList()); + private static final Map, List>> ROUTING_TABLE = + Collections.singletonMap(SERVER_INSTANCE, Pair.of(Collections.emptyList(), Collections.emptyList())); private QueryRouter _queryRouter; private ServerRoutingStatsManager _serverRoutingStatsManager; @@ -71,16 +76,27 @@ public void setUp() { Map properties = new HashMap<>(); properties.put(CommonConstants.Broker.AdaptiveServerSelector.CONFIG_OF_ENABLE_STATS_COLLECTION, true); PinotConfiguration cfg = new PinotConfiguration(properties); - _serverRoutingStatsManager = new ServerRoutingStatsManager(cfg); + _serverRoutingStatsManager = new ServerRoutingStatsManager(cfg, mock(BrokerMetrics.class)); _serverRoutingStatsManager.init(); _queryRouter = new QueryRouter("testBroker", mock(BrokerMetrics.class), _serverRoutingStatsManager); _requestCount = 0; } + @AfterMethod + void deregisterServerMetrics() { + ServerMetrics.deregister(); + } + private QueryServer getQueryServer(int responseDelayMs, byte[] responseBytes) { + return getQueryServer(responseDelayMs, responseBytes, TEST_PORT); + } + + private QueryServer getQueryServer(int responseDelayMs, byte[] responseBytes, int port) { + ServerMetrics serverMetrics = mock(ServerMetrics.class); InstanceRequestHandler handler = new InstanceRequestHandler("server01", new PinotConfiguration(), - mockQueryScheduler(responseDelayMs, responseBytes), mock(ServerMetrics.class), mock(AccessControl.class)); - return new QueryServer(TEST_PORT, null, handler); + mockQueryScheduler(responseDelayMs, responseBytes), serverMetrics, mock(AccessControl.class)); + ServerMetrics.register(serverMetrics); + return new QueryServer(port, null, handler); } private QueryScheduler mockQueryScheduler(int responseDelayMs, byte[] responseBytes) { @@ -277,6 +293,89 @@ public void testServerDown() assertEquals(_serverRoutingStatsManager.fetchNumInFlightRequestsForServer(serverId).intValue(), 0); } + @Test + public void testSkipUnavailableServer() + throws IOException, InterruptedException { + // Using a different port is a hack to avoid resource conflict with other tests, ideally queryServer.shutdown() + // should ensure there is no possibility of resource conflict. + int port = 12346; + ServerInstance serverInstance1 = new ServerInstance("localhost", port); + ServerInstance serverInstance2 = new ServerInstance("localhost", port + 1); + ServerRoutingInstance serverRoutingInstance1 = + serverInstance1.toServerRoutingInstance(TableType.OFFLINE, ServerInstance.RoutingType.NETTY); + ServerRoutingInstance serverRoutingInstance2 = + serverInstance2.toServerRoutingInstance(TableType.OFFLINE, ServerInstance.RoutingType.NETTY); + Map, List>> routingTable = + Map.of(serverInstance1, Pair.of(Collections.emptyList(), Collections.emptyList()), serverInstance2, + Pair.of(Collections.emptyList(), Collections.emptyList())); + + long requestId = 123; + DataSchema dataSchema = + new DataSchema(new String[]{"column1"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.STRING}); + DataTableBuilder builder = DataTableBuilderFactory.getDataTableBuilder(dataSchema); + builder.startRow(); + builder.setColumn(0, "value1"); + builder.finishRow(); + DataTable dataTableSuccess = builder.build(); + Map dataTableMetadata = dataTableSuccess.getMetadata(); + dataTableMetadata.put(MetadataKey.REQUEST_ID.getName(), Long.toString(requestId)); + byte[] successResponseBytes = dataTableSuccess.toBytes(); + + // Only start a single QueryServer, on port from serverInstance1 + QueryServer queryServer = getQueryServer(500, successResponseBytes, port); + queryServer.start(); + + // Submit the query with skipUnavailableServers=true, the single started server should return a valid response + BrokerRequest brokerRequest = + CalciteSqlCompiler.compileToBrokerRequest("SET skipUnavailableServers=true; SELECT * FROM testTable"); + long startTime = System.currentTimeMillis(); + AsyncQueryResponse asyncQueryResponse = + _queryRouter.submitQuery(requestId, "testTable", brokerRequest, routingTable, null, null, 10_000L); + Map response = asyncQueryResponse.getFinalResponses(); + assertEquals(response.size(), 2); + assertTrue(response.containsKey(serverRoutingInstance1)); + assertTrue(response.containsKey(serverRoutingInstance2)); + + ServerResponse serverResponse1 = response.get(serverRoutingInstance1); + ServerResponse serverResponse2 = response.get(serverRoutingInstance2); + assertNotNull(serverResponse1.getDataTable()); + assertNull(serverResponse2.getDataTable()); + assertTrue(serverResponse1.getResponseDelayMs() > 500); // > response delay set by getQueryServer + assertTrue(serverResponse2.getResponseDelayMs() < 100); // connection refused, no delay + assertTrue(System.currentTimeMillis() - startTime > 500); // > response delay set by getQueryServer + _requestCount += 4; + waitForStatsUpdate(_requestCount); + assertEquals( + _serverRoutingStatsManager.fetchNumInFlightRequestsForServer(serverInstance1.getInstanceId()).intValue(), 0); + assertEquals( + _serverRoutingStatsManager.fetchNumInFlightRequestsForServer(serverInstance2.getInstanceId()).intValue(), 0); + + // Submit the same query without skipUnavailableServers, the servers should not return any response + brokerRequest = CalciteSqlCompiler.compileToBrokerRequest("SELECT * FROM testTable"); + startTime = System.currentTimeMillis(); + asyncQueryResponse = + _queryRouter.submitQuery(requestId, "testTable", brokerRequest, routingTable, null, null, 10_000L); + response = asyncQueryResponse.getFinalResponses(); + assertEquals(response.size(), 2); + assertTrue(response.containsKey(serverRoutingInstance1)); + assertTrue(response.containsKey(serverRoutingInstance2)); + + serverResponse1 = response.get(serverRoutingInstance1); + serverResponse2 = response.get(serverRoutingInstance2); + assertNull(serverResponse1.getDataTable()); + assertNull(serverResponse2.getDataTable()); + assertTrue(serverResponse1.getResponseDelayMs() < 100); + assertTrue(serverResponse2.getResponseDelayMs() < 100); + assertTrue(System.currentTimeMillis() - startTime < 100); + _requestCount += 4; + waitForStatsUpdate(_requestCount); + assertEquals( + _serverRoutingStatsManager.fetchNumInFlightRequestsForServer(serverInstance1.getInstanceId()).intValue(), 0); + assertEquals( + _serverRoutingStatsManager.fetchNumInFlightRequestsForServer(serverInstance2.getInstanceId()).intValue(), 0); + queryServer.shutDown(); + } + private void waitForStatsUpdate(long taskCount) { TestUtils.waitForCondition(aVoid -> { return (_serverRoutingStatsManager.getCompletedTaskCount() == taskCount); diff --git a/pinot-core/src/test/java/org/apache/pinot/core/transport/QueryServerTest.java b/pinot-core/src/test/java/org/apache/pinot/core/transport/QueryServerTest.java new file mode 100644 index 000000000000..bef260906735 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/transport/QueryServerTest.java @@ -0,0 +1,79 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.transport; + +import io.netty.channel.ChannelHandler; +import java.net.InetSocketAddress; +import java.net.Socket; +import org.apache.commons.io.IOUtils; +import org.apache.pinot.common.config.NettyConfig; +import org.apache.pinot.common.config.TlsConfig; +import org.apache.pinot.common.metrics.ServerMetrics; +import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.metrics.PinotMetricUtils; +import org.apache.pinot.spi.metrics.PinotMetricsRegistry; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import static org.mockito.Mockito.mock; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + + +public class QueryServerTest { + @DataProvider + public Object[][] parameters() { + return new Object[][]{ + new Object[]{true}, new Object[]{false}, + }; + } + + @Test(dataProvider = "parameters") + public void startAndStop(final boolean nativeTransportEnabled) { + PinotMetricUtils.init(new PinotConfiguration()); + PinotMetricsRegistry registry = PinotMetricUtils.getPinotMetricsRegistry(); + ServerMetrics.register(new ServerMetrics(registry)); + NettyConfig nettyConfig = new NettyConfig(); + nettyConfig.setNativeTransportsEnabled(nativeTransportEnabled); + TlsConfig tlsConfig = new TlsConfig(); + ChannelHandler channelHandler = mock(ChannelHandler.class); + + QueryServer server = new QueryServer(0, nettyConfig, tlsConfig, channelHandler); + server.start(); + + final InetSocketAddress serverAddress = server.getChannel().localAddress(); + + assertTrue(connectionOk(serverAddress)); + + server.shutDown(); + assertFalse(connectionOk(serverAddress)); + } + + private static boolean connectionOk(InetSocketAddress address) { + Socket s = null; + try { + s = new Socket(address.getHostName(), address.getPort()); + return s.isConnected(); + } catch (Exception e) { + return false; + } finally { + IOUtils.closeQuietly(s); + } + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/transport/ServerChannelsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/transport/ServerChannelsTest.java new file mode 100644 index 000000000000..abbbe5be56c3 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/transport/ServerChannelsTest.java @@ -0,0 +1,85 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.transport; + +import com.sun.net.httpserver.HttpServer; +import java.net.InetSocketAddress; +import org.apache.pinot.common.config.NettyConfig; +import org.apache.pinot.common.metrics.BrokerMetrics; +import org.apache.pinot.common.request.BrokerRequest; +import org.apache.pinot.common.request.InstanceRequest; +import org.apache.pinot.spi.config.table.TableType; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import static org.mockito.Mockito.mock; + + +public class ServerChannelsTest { + private static HttpServer _dummyServer; + + @DataProvider + public Object[][] parameters() { + return new Object[][]{ + new Object[]{true}, new Object[]{false}, + }; + } + + @BeforeClass + public void setUp() + throws Exception { + _dummyServer = HttpServer.create(); + _dummyServer.bind(new InetSocketAddress("localhost", 0), 0); + _dummyServer.start(); + } + + @AfterClass + public void tearDown() + throws Exception { + if (_dummyServer != null) { + _dummyServer.stop(0); + } + } + + @Test(dataProvider = "parameters") + public void testConnect(boolean nativeTransportEnabled) + throws Exception { + BrokerMetrics brokerMetrics = mock(BrokerMetrics.class); + NettyConfig nettyConfig = new NettyConfig(); + nettyConfig.setNativeTransportsEnabled(nativeTransportEnabled); + QueryRouter queryRouter = mock(QueryRouter.class); + + ServerRoutingInstance serverRoutingInstance = + new ServerRoutingInstance("localhost", _dummyServer.getAddress().getPort(), TableType.REALTIME); + ServerChannels serverChannels = new ServerChannels(queryRouter, brokerMetrics, nettyConfig, null); + serverChannels.connect(serverRoutingInstance); + + final long requestId = System.currentTimeMillis(); + + AsyncQueryResponse asyncQueryResponse = mock(AsyncQueryResponse.class); + BrokerRequest brokerRequest = new BrokerRequest(); + InstanceRequest instanceRequest = new InstanceRequest(); + instanceRequest.setRequestId(requestId); + instanceRequest.setQuery(brokerRequest); + serverChannels.sendRequest("dummy_table_name", asyncQueryResponse, serverRoutingInstance, instanceRequest, 1000); + serverChannels.shutDown(); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/transport/ServerInstanceTest.java b/pinot-core/src/test/java/org/apache/pinot/core/transport/ServerInstanceTest.java new file mode 100644 index 000000000000..f529c3294207 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/transport/ServerInstanceTest.java @@ -0,0 +1,31 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.transport; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.testng.annotations.Test; + + +public class ServerInstanceTest { + @Test + public void equalsVerifier() { + EqualsVerifier.configure().forClass(ServerInstance.class).withOnlyTheseFields("_instanceId") + .withNonnullFields("_instanceId").verify(); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/transport/ServerRoutingInstanceTest.java b/pinot-core/src/test/java/org/apache/pinot/core/transport/ServerRoutingInstanceTest.java new file mode 100644 index 000000000000..991cd19181e8 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/transport/ServerRoutingInstanceTest.java @@ -0,0 +1,32 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.transport; + +import nl.jqno.equalsverifier.EqualsVerifier; +import nl.jqno.equalsverifier.Warning; +import org.testng.annotations.Test; + + +public class ServerRoutingInstanceTest { + @Test + public void equalsVerifier() { + EqualsVerifier.configure().forClass(ServerRoutingInstance.class) + .withOnlyTheseFields("_hostname", "_port", "_tableType").suppress(Warning.NULL_FIELDS).verify(); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/transport/server/routing/stats/ServerRoutingStatsManagerTest.java b/pinot-core/src/test/java/org/apache/pinot/core/transport/server/routing/stats/ServerRoutingStatsManagerTest.java index 162beb610f57..1ef3022a0997 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/transport/server/routing/stats/ServerRoutingStatsManagerTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/transport/server/routing/stats/ServerRoutingStatsManagerTest.java @@ -18,13 +18,18 @@ */ package org.apache.pinot.core.transport.server.routing.stats; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.lang3.tuple.Pair; +import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.spi.env.PinotConfiguration; +import org.apache.pinot.spi.metrics.PinotMetricUtils; +import org.apache.pinot.spi.metrics.PinotMetricsRegistry; import org.apache.pinot.spi.utils.CommonConstants; import org.apache.pinot.util.TestUtils; +import org.testng.annotations.BeforeTest; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; @@ -34,20 +39,44 @@ public class ServerRoutingStatsManagerTest { + private BrokerMetrics _brokerMetrics; + + @BeforeTest + public void initBrokerMetrics() { + // Set up metric registry and broker metrics + PinotConfiguration brokerConfig = new PinotConfiguration(); + PinotMetricsRegistry metricsRegistry = PinotMetricUtils.getPinotMetricsRegistry( + brokerConfig.subset(CommonConstants.Broker.METRICS_CONFIG_PREFIX)); + _brokerMetrics = new BrokerMetrics( + brokerConfig.getProperty( + CommonConstants.Broker.CONFIG_OF_METRICS_NAME_PREFIX, + CommonConstants.Broker.DEFAULT_METRICS_NAME_PREFIX), + metricsRegistry, + brokerConfig.getProperty( + CommonConstants.Broker.CONFIG_OF_ENABLE_TABLE_LEVEL_METRICS, + CommonConstants.Broker.DEFAULT_ENABLE_TABLE_LEVEL_METRICS), + brokerConfig.getProperty( + CommonConstants.Broker.CONFIG_OF_ALLOWED_TABLES_FOR_EMITTING_METRICS, + Collections.emptyList())); + _brokerMetrics.initializeGlobalMeters(); + BrokerMetrics.register(_brokerMetrics); + } + @Test public void testInitAndShutDown() { Map properties = new HashMap<>(); // Test 1: Test disabled. properties.put(CommonConstants.Broker.AdaptiveServerSelector.CONFIG_OF_ENABLE_STATS_COLLECTION, false); - ServerRoutingStatsManager manager = new ServerRoutingStatsManager(new PinotConfiguration(properties)); + ServerRoutingStatsManager manager = new ServerRoutingStatsManager(new PinotConfiguration(properties), + _brokerMetrics); assertFalse(manager.isEnabled()); manager.init(); assertFalse(manager.isEnabled()); // Test 2: Test enabled. properties.put(CommonConstants.Broker.AdaptiveServerSelector.CONFIG_OF_ENABLE_STATS_COLLECTION, true); - manager = new ServerRoutingStatsManager(new PinotConfiguration(properties)); + manager = new ServerRoutingStatsManager(new PinotConfiguration(properties), _brokerMetrics); assertFalse(manager.isEnabled()); manager.init(); assertTrue(manager.isEnabled()); @@ -64,7 +93,8 @@ public void testInitAndShutDown() { public void testEmptyStats() { Map properties = new HashMap<>(); properties.put(CommonConstants.Broker.AdaptiveServerSelector.CONFIG_OF_ENABLE_STATS_COLLECTION, true); - ServerRoutingStatsManager manager = new ServerRoutingStatsManager(new PinotConfiguration(properties)); + ServerRoutingStatsManager manager = new ServerRoutingStatsManager(new PinotConfiguration(properties), + _brokerMetrics); manager.init(); List> numInFlightReqList = manager.fetchNumInFlightRequestsForAllServers(); @@ -94,13 +124,14 @@ public void testQuerySubmitAndCompletionStats() { properties.put(CommonConstants.Broker.AdaptiveServerSelector.CONFIG_OF_WARMUP_DURATION_MS, 0); properties.put(CommonConstants.Broker.AdaptiveServerSelector.CONFIG_OF_AVG_INITIALIZATION_VAL, 0.0); properties.put(CommonConstants.Broker.AdaptiveServerSelector.CONFIG_OF_HYBRID_SCORE_EXPONENT, 3); - ServerRoutingStatsManager manager = new ServerRoutingStatsManager(new PinotConfiguration(properties)); + ServerRoutingStatsManager manager = new ServerRoutingStatsManager(new PinotConfiguration(properties), + _brokerMetrics); manager.init(); int requestId = 0; // Submit stats for server1. - manager.recordStatsAfterQuerySubmission(requestId++, "server1"); + manager.recordStatsForQuerySubmission(requestId++, "server1"); waitForStatsUpdate(manager, requestId); List> numInFlightReqList = manager.fetchNumInFlightRequestsForAllServers(); @@ -125,7 +156,7 @@ public void testQuerySubmitAndCompletionStats() { assertEquals(score, 0.0); // Submit more stats for server 1. - manager.recordStatsAfterQuerySubmission(requestId++, "server1"); + manager.recordStatsForQuerySubmission(requestId++, "server1"); waitForStatsUpdate(manager, requestId); numInFlightReqList = manager.fetchNumInFlightRequestsForAllServers(); @@ -150,15 +181,17 @@ public void testQuerySubmitAndCompletionStats() { assertEquals(score, 0.0); // Add a new server server2. - manager.recordStatsAfterQuerySubmission(requestId++, "server2"); + manager.recordStatsForQuerySubmission(requestId++, "server2"); waitForStatsUpdate(manager, requestId); numInFlightReqList = manager.fetchNumInFlightRequestsForAllServers(); - assertEquals(numInFlightReqList.get(0).getLeft(), "server2"); - assertEquals(numInFlightReqList.get(0).getRight().intValue(), 1); - assertEquals(numInFlightReqList.get(1).getLeft(), "server1"); - assertEquals(numInFlightReqList.get(1).getRight().intValue(), 2); + int server2Index = numInFlightReqList.get(0).getLeft().equals("server2") ? 0 : 1; + int server1Index = 1 - server2Index; + assertEquals(numInFlightReqList.get(server2Index).getLeft(), "server2"); + assertEquals(numInFlightReqList.get(server2Index).getRight().intValue(), 1); + assertEquals(numInFlightReqList.get(server1Index).getLeft(), "server1"); + assertEquals(numInFlightReqList.get(server1Index).getRight().intValue(), 2); numInFlightReq = manager.fetchNumInFlightRequestsForServer("server2"); assertEquals(numInFlightReq.intValue(), 1); @@ -166,10 +199,12 @@ public void testQuerySubmitAndCompletionStats() { assertEquals(numInFlightReq.intValue(), 2); latencyList = manager.fetchEMALatencyForAllServers(); - assertEquals(latencyList.get(0).getLeft(), "server2"); - assertEquals(latencyList.get(0).getRight().doubleValue(), 0.0); - assertEquals(latencyList.get(1).getLeft(), "server1"); - assertEquals(latencyList.get(1).getRight().doubleValue(), 0.0); + server2Index = latencyList.get(0).getLeft().equals("server2") ? 0 : 1; + server1Index = 1 - server2Index; + assertEquals(latencyList.get(server2Index).getLeft(), "server2"); + assertEquals(latencyList.get(server2Index).getRight().doubleValue(), 0.0); + assertEquals(latencyList.get(server1Index).getLeft(), "server1"); + assertEquals(latencyList.get(server1Index).getRight().doubleValue(), 0.0); latency = manager.fetchEMALatencyForServer("server2"); assertEquals(latency, 0.0); @@ -177,10 +212,12 @@ public void testQuerySubmitAndCompletionStats() { assertEquals(latency, 0.0); scoreList = manager.fetchHybridScoreForAllServers(); - assertEquals(scoreList.get(0).getLeft(), "server2"); - assertEquals(scoreList.get(0).getRight().doubleValue(), 0.0); - assertEquals(scoreList.get(1).getLeft(), "server1"); - assertEquals(scoreList.get(1).getRight().doubleValue(), 0.0); + server2Index = scoreList.get(0).getLeft().equals("server2") ? 0 : 1; + server1Index = 1 - server2Index; + assertEquals(scoreList.get(server2Index).getLeft(), "server2"); + assertEquals(scoreList.get(server2Index).getRight().doubleValue(), 0.0); + assertEquals(scoreList.get(server1Index).getLeft(), "server1"); + assertEquals(scoreList.get(server1Index).getRight().doubleValue(), 0.0); score = manager.fetchHybridScoreForServer("server2"); assertEquals(score, 0.0); @@ -192,10 +229,12 @@ public void testQuerySubmitAndCompletionStats() { waitForStatsUpdate(manager, requestId); numInFlightReqList = manager.fetchNumInFlightRequestsForAllServers(); - assertEquals(numInFlightReqList.get(0).getLeft(), "server2"); - assertEquals(numInFlightReqList.get(0).getRight().intValue(), 1); - assertEquals(numInFlightReqList.get(1).getLeft(), "server1"); - assertEquals(numInFlightReqList.get(1).getRight().intValue(), 1); + server2Index = numInFlightReqList.get(0).getLeft().equals("server2") ? 0 : 1; + server1Index = 1 - server2Index; + assertEquals(numInFlightReqList.get(server2Index).getLeft(), "server2"); + assertEquals(numInFlightReqList.get(server2Index).getRight().intValue(), 1); + assertEquals(numInFlightReqList.get(server1Index).getLeft(), "server1"); + assertEquals(numInFlightReqList.get(server1Index).getRight().intValue(), 1); numInFlightReq = manager.fetchNumInFlightRequestsForServer("server2"); assertEquals(numInFlightReq.intValue(), 1); @@ -203,10 +242,12 @@ public void testQuerySubmitAndCompletionStats() { assertEquals(numInFlightReq.intValue(), 1); latencyList = manager.fetchEMALatencyForAllServers(); - assertEquals(latencyList.get(0).getLeft(), "server2"); - assertEquals(latencyList.get(0).getRight().doubleValue(), 0.0); - assertEquals(latencyList.get(1).getLeft(), "server1"); - assertEquals(latencyList.get(1).getRight().doubleValue(), 2.0); + server2Index = latencyList.get(0).getLeft().equals("server2") ? 0 : 1; + server1Index = 1 - server2Index; + assertEquals(latencyList.get(server2Index).getLeft(), "server2"); + assertEquals(latencyList.get(server2Index).getRight().doubleValue(), 0.0); + assertEquals(latencyList.get(server1Index).getLeft(), "server1"); + assertEquals(latencyList.get(server1Index).getRight().doubleValue(), 2.0); latency = manager.fetchEMALatencyForServer("server2"); assertEquals(latency, 0.0); @@ -214,10 +255,12 @@ public void testQuerySubmitAndCompletionStats() { assertEquals(latency, 2.0); scoreList = manager.fetchHybridScoreForAllServers(); - assertEquals(scoreList.get(0).getLeft(), "server2"); - assertEquals(scoreList.get(0).getRight().doubleValue(), 0.0); - assertEquals(scoreList.get(1).getLeft(), "server1"); - assertEquals(scoreList.get(1).getRight().doubleValue(), 54.0); + server2Index = scoreList.get(0).getLeft().equals("server2") ? 0 : 1; + server1Index = 1 - server2Index; + assertEquals(scoreList.get(server2Index).getLeft(), "server2"); + assertEquals(scoreList.get(server2Index).getRight().doubleValue(), 0.0); + assertEquals(scoreList.get(server1Index).getLeft(), "server1"); + assertEquals(scoreList.get(server1Index).getRight().doubleValue(), 54.0); score = manager.fetchHybridScoreForServer("server2"); assertEquals(score, 0.0); @@ -229,10 +272,12 @@ public void testQuerySubmitAndCompletionStats() { waitForStatsUpdate(manager, requestId); numInFlightReqList = manager.fetchNumInFlightRequestsForAllServers(); - assertEquals(numInFlightReqList.get(0).getLeft(), "server2"); - assertEquals(numInFlightReqList.get(0).getRight().intValue(), 0); - assertEquals(numInFlightReqList.get(1).getLeft(), "server1"); - assertEquals(numInFlightReqList.get(1).getRight().intValue(), 1); + server2Index = numInFlightReqList.get(0).getLeft().equals("server2") ? 0 : 1; + server1Index = 1 - server2Index; + assertEquals(numInFlightReqList.get(server2Index).getLeft(), "server2"); + assertEquals(numInFlightReqList.get(server2Index).getRight().intValue(), 0); + assertEquals(numInFlightReqList.get(server1Index).getLeft(), "server1"); + assertEquals(numInFlightReqList.get(server1Index).getRight().intValue(), 1); numInFlightReq = manager.fetchNumInFlightRequestsForServer("server2"); assertEquals(numInFlightReq.intValue(), 0); @@ -240,10 +285,12 @@ public void testQuerySubmitAndCompletionStats() { assertEquals(numInFlightReq.intValue(), 1); latencyList = manager.fetchEMALatencyForAllServers(); - assertEquals(latencyList.get(0).getLeft(), "server2"); - assertEquals(latencyList.get(0).getRight().doubleValue(), 10.0); - assertEquals(latencyList.get(1).getLeft(), "server1"); - assertEquals(latencyList.get(1).getRight().doubleValue(), 2.0); + server2Index = latencyList.get(0).getLeft().equals("server2") ? 0 : 1; + server1Index = 1 - server2Index; + assertEquals(latencyList.get(server2Index).getLeft(), "server2"); + assertEquals(latencyList.get(server2Index).getRight().doubleValue(), 10.0); + assertEquals(latencyList.get(server1Index).getLeft(), "server1"); + assertEquals(latencyList.get(server1Index).getRight().doubleValue(), 2.0); latency = manager.fetchEMALatencyForServer("server2"); assertEquals(latency, 10.0); @@ -251,10 +298,12 @@ public void testQuerySubmitAndCompletionStats() { assertEquals(latency, 2.0); scoreList = manager.fetchHybridScoreForAllServers(); - assertEquals(scoreList.get(0).getLeft(), "server2"); - assertEquals(scoreList.get(0).getRight().doubleValue(), 10.0, manager.getServerRoutingStatsStr()); - assertEquals(scoreList.get(1).getLeft(), "server1"); - assertEquals(scoreList.get(1).getRight().doubleValue(), 54.0, manager.getServerRoutingStatsStr()); + server2Index = scoreList.get(0).getLeft().equals("server2") ? 0 : 1; + server1Index = 1 - server2Index; + assertEquals(scoreList.get(server2Index).getLeft(), "server2"); + assertEquals(scoreList.get(server2Index).getRight().doubleValue(), 10.0, manager.getServerRoutingStatsStr()); + assertEquals(scoreList.get(server1Index).getLeft(), "server1"); + assertEquals(scoreList.get(server1Index).getRight().doubleValue(), 54.0, manager.getServerRoutingStatsStr()); score = manager.fetchHybridScoreForServer("server2"); assertEquals(score, 10.0); diff --git a/pinot-core/src/test/java/org/apache/pinot/core/util/CrcUtilsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/util/CrcUtilsTest.java index 0589ecfceb56..120f84e07e6d 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/util/CrcUtilsTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/util/CrcUtilsTest.java @@ -19,71 +19,49 @@ package org.apache.pinot.core.util; import java.io.File; +import java.net.URL; import java.util.concurrent.TimeUnit; import org.apache.commons.io.FileUtils; -import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; import org.apache.pinot.segment.local.segment.creator.SegmentTestUtils; -import org.apache.pinot.segment.local.segment.creator.impl.SegmentCreationDriverFactory; +import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; +import org.apache.pinot.segment.local.segment.index.converter.SegmentV1V2ToV3FormatConverter; import org.apache.pinot.segment.local.utils.CrcUtils; -import org.apache.pinot.segment.spi.IndexSegment; -import org.apache.pinot.segment.spi.SegmentMetadata; import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; import org.apache.pinot.segment.spi.creator.SegmentIndexCreationDriver; -import org.apache.pinot.spi.utils.ReadMode; import org.apache.pinot.util.TestUtils; -import org.testng.Assert; import org.testng.annotations.Test; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; -/** - * Dec 4, 2014 - */ public class CrcUtilsTest { - + private static final File INDEX_DIR = new File(FileUtils.getTempDirectory(), "CrcUtilsTest"); private static final String AVRO_DATA = "data/test_data-mv.avro"; - private static final File INDEX_DIR = new File("/tmp/testingCrc"); + private static final long EXPECTED_V1_CRC = 2708456273L; + private static final long EXPECTED_V3_CRC = 2796149869L; @Test - public void test1() - throws Exception { - if (INDEX_DIR.exists()) { - FileUtils.deleteQuietly(INDEX_DIR); - } - - final CrcUtils u1 = CrcUtils.forAllFilesInFolder(new File(makeSegmentAndReturnPath())); - final long crc1 = u1.computeCrc(); - final String md51 = u1.computeMD5(); - - FileUtils.deleteQuietly(INDEX_DIR); - - final CrcUtils u2 = CrcUtils.forAllFilesInFolder(new File(makeSegmentAndReturnPath())); - final long crc2 = u2.computeCrc(); - final String md52 = u2.computeMD5(); - - Assert.assertEquals(crc1, crc2); - Assert.assertEquals(md51, md52); - - FileUtils.deleteQuietly(INDEX_DIR); - - final IndexSegment segment = ImmutableSegmentLoader.load(new File(makeSegmentAndReturnPath()), ReadMode.mmap); - final SegmentMetadata m = segment.getSegmentMetadata(); - - FileUtils.deleteQuietly(INDEX_DIR); - } - - private String makeSegmentAndReturnPath() + public void testCrc() throws Exception { - final String filePath = TestUtils.getFileFromResourceUrl(CrcUtils.class.getClassLoader().getResource(AVRO_DATA)); - - final SegmentGeneratorConfig config = SegmentTestUtils - .getSegmentGenSpecWithSchemAndProjectedColumns(new File(filePath), INDEX_DIR, "daysSinceEpoch", TimeUnit.DAYS, - "testTable"); - config.setSegmentNamePostfix("1"); - final SegmentIndexCreationDriver driver = SegmentCreationDriverFactory.get(null); + FileUtils.deleteDirectory(INDEX_DIR); + + URL resource = getClass().getClassLoader().getResource(AVRO_DATA); + assertNotNull(resource); + String filePath = TestUtils.getFileFromResourceUrl(resource); + SegmentGeneratorConfig config = + SegmentTestUtils.getSegmentGenSpecWithSchemAndProjectedColumns(new File(filePath), INDEX_DIR, "daysSinceEpoch", + TimeUnit.DAYS, "testTable"); + SegmentIndexCreationDriver driver = new SegmentIndexCreationDriverImpl(); driver.init(config); driver.build(); - return new File(INDEX_DIR, driver.getSegmentName()).getAbsolutePath(); + File indexDir = driver.getOutputDirectory(); + assertEquals(CrcUtils.forAllFilesInFolder(indexDir).computeCrc(), EXPECTED_V1_CRC); + + new SegmentV1V2ToV3FormatConverter().convert(indexDir); + assertEquals(CrcUtils.forAllFilesInFolder(indexDir).computeCrc(), EXPECTED_V3_CRC); + + FileUtils.deleteDirectory(INDEX_DIR); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/util/IntDoubleIndexedPriorityQueueTest.java b/pinot-core/src/test/java/org/apache/pinot/core/util/IntDoubleIndexedPriorityQueueTest.java similarity index 97% rename from pinot-core/src/test/java/org/apache/pinot/util/IntDoubleIndexedPriorityQueueTest.java rename to pinot-core/src/test/java/org/apache/pinot/core/util/IntDoubleIndexedPriorityQueueTest.java index 7e9ca3e78e36..642f44464bae 100644 --- a/pinot-core/src/test/java/org/apache/pinot/util/IntDoubleIndexedPriorityQueueTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/util/IntDoubleIndexedPriorityQueueTest.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.util; +package org.apache.pinot.core.util; import it.unimi.dsi.fastutil.ints.Int2DoubleMap; import it.unimi.dsi.fastutil.ints.Int2DoubleOpenHashMap; @@ -24,7 +24,6 @@ import java.util.Collections; import java.util.List; import java.util.Random; -import org.apache.pinot.core.util.IntDoubleIndexedPriorityQueue; import org.apache.pinot.spi.utils.Pairs; import org.testng.Assert; import org.testng.annotations.Test; diff --git a/pinot-core/src/test/java/org/apache/pinot/util/IntObjectIndexedPriorityQueueTest.java b/pinot-core/src/test/java/org/apache/pinot/core/util/IntObjectIndexedPriorityQueueTest.java similarity index 97% rename from pinot-core/src/test/java/org/apache/pinot/util/IntObjectIndexedPriorityQueueTest.java rename to pinot-core/src/test/java/org/apache/pinot/core/util/IntObjectIndexedPriorityQueueTest.java index e67e8d18cf0e..aeb51ff7c108 100644 --- a/pinot-core/src/test/java/org/apache/pinot/util/IntObjectIndexedPriorityQueueTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/util/IntObjectIndexedPriorityQueueTest.java @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.pinot.util; +package org.apache.pinot.core.util; import java.util.ArrayList; import java.util.Collections; @@ -24,7 +24,6 @@ import java.util.List; import java.util.Map; import java.util.Random; -import org.apache.pinot.core.util.IntObjectIndexedPriorityQueue; import org.apache.pinot.segment.local.customobject.AvgPair; import org.apache.pinot.spi.utils.Pairs; import org.testng.Assert; diff --git a/pinot-core/src/test/java/org/apache/pinot/core/util/PeerServerSegmentFinderTest.java b/pinot-core/src/test/java/org/apache/pinot/core/util/PeerServerSegmentFinderTest.java index 4b6c6fb910c3..2af972695ffa 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/util/PeerServerSegmentFinderTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/util/PeerServerSegmentFinderTest.java @@ -19,103 +19,93 @@ package org.apache.pinot.core.util; import java.net.URI; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import org.apache.helix.HelixAdmin; import org.apache.helix.HelixManager; import org.apache.helix.model.ExternalView; -import org.apache.helix.model.HelixConfigScope; import org.apache.helix.model.InstanceConfig; import org.apache.pinot.spi.utils.CommonConstants; -import org.apache.pinot.spi.utils.StringUtil; -import org.testng.Assert; +import org.apache.pinot.spi.utils.CommonConstants.Helix.Instance; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; public class PeerServerSegmentFinderTest { - private static final String TABLE_NAME_WITH_TYPE = "testTable_REALTIME"; - private static final String SEGMENT_1 = "testTable__0__0__t11"; - private static final String SEGMENT_2 = "testTable__0__1__t11"; - private static final String CLUSTER_NAME = "dummyCluster"; - private static final String INSTANCE_ID1 = "Server_localhost_1000"; - private static final String INSTANCE_ID2 = "Server_localhost_1001"; - private static final String INSTANCE_ID3 = "Server_localhost_1003"; - public static final String ADMIN_PORT = "1008"; - public static final String HOST_1_NAME = "s1"; - public static final String HOST_2_NAME = "s2"; - public static final String HOST_3_NAME = "s3"; + private static final String CLUSTER_NAME = "testCluster"; + private static final String REALTIME_TABLE_NAME = "testTable_REALTIME"; + private static final String SEGMENT_1 = "testSegment1"; + private static final String SEGMENT_2 = "testSegment2"; + private static final String INSTANCE_ID_1 = "Server_s1_1007"; + private static final String INSTANCE_ID_2 = "Server_s2_1007"; + private static final String INSTANCE_ID_3 = "Server_s3_1007"; + private static final String HOSTNAME_1 = "s1"; + private static final String HOSTNAME_2 = "s2"; + private static final String HOSTNAME_3 = "s3"; + private static final int HELIX_PORT = 1007; + private static final int HTTP_ADMIN_PORT = 1008; + private static final int HTTPS_ADMIN_PORT = 1009; + private HelixManager _helixManager; @BeforeClass - public void initSegmentFetcherFactoryWithPeerServerSegmentFetcher() - throws Exception { - HelixAdmin helixAdmin; - { - ExternalView ev = new ExternalView(TABLE_NAME_WITH_TYPE); - ev.setState(SEGMENT_1, INSTANCE_ID1, "ONLINE"); - ev.setState(SEGMENT_1, INSTANCE_ID2, "OFFLINE"); - ev.setState(SEGMENT_1, INSTANCE_ID3, "ONLINE"); - ev.setState(SEGMENT_2, INSTANCE_ID1, "OFFLINE"); - ev.setState(SEGMENT_2, INSTANCE_ID2, "OFFLINE"); - _helixManager = mock(HelixManager.class); - helixAdmin = mock(HelixAdmin.class); - when(_helixManager.getClusterManagmentTool()).thenReturn(helixAdmin); - when(_helixManager.getClusterName()).thenReturn(CLUSTER_NAME); - when(helixAdmin.getResourceExternalView(CLUSTER_NAME, TABLE_NAME_WITH_TYPE)).thenReturn(ev); - when(helixAdmin.getConfigKeys(any(HelixConfigScope.class))).thenReturn(new ArrayList<>()); - Map instanceConfigMap = new HashMap<>(); - instanceConfigMap.put(CommonConstants.Helix.Instance.ADMIN_PORT_KEY, ADMIN_PORT); - when(helixAdmin.getConfig(any(HelixConfigScope.class), any(List.class))).thenReturn(instanceConfigMap); - InstanceConfig instanceConfig1 = new InstanceConfig(INSTANCE_ID1); - instanceConfig1.setHostName(HOST_1_NAME); - instanceConfig1.setPort("1000"); - when(helixAdmin.getInstanceConfig(any(String.class), eq(INSTANCE_ID1))).thenReturn(instanceConfig1); + public void initSegmentFetcherFactoryWithPeerServerSegmentFetcher() { + ExternalView externalView = new ExternalView(REALTIME_TABLE_NAME); + externalView.setState(SEGMENT_1, INSTANCE_ID_1, "ONLINE"); + externalView.setState(SEGMENT_1, INSTANCE_ID_2, "OFFLINE"); + externalView.setState(SEGMENT_1, INSTANCE_ID_3, "ONLINE"); + externalView.setState(SEGMENT_2, INSTANCE_ID_1, "OFFLINE"); + externalView.setState(SEGMENT_2, INSTANCE_ID_2, "OFFLINE"); - InstanceConfig instanceConfig2 = new InstanceConfig(INSTANCE_ID2); - instanceConfig2.setHostName(HOST_2_NAME); - instanceConfig2.setPort("1000"); - when(helixAdmin.getInstanceConfig(any(String.class), eq(INSTANCE_ID2))).thenReturn(instanceConfig2); + _helixManager = mock(HelixManager.class); + HelixAdmin helixAdmin = mock(HelixAdmin.class); + when(_helixManager.getClusterManagmentTool()).thenReturn(helixAdmin); + when(_helixManager.getClusterName()).thenReturn(CLUSTER_NAME); + when(helixAdmin.getResourceExternalView(CLUSTER_NAME, REALTIME_TABLE_NAME)).thenReturn(externalView); + when(helixAdmin.getInstanceConfig(CLUSTER_NAME, INSTANCE_ID_1)).thenReturn( + getInstanceConfig(INSTANCE_ID_1, HOSTNAME_1)); + when(helixAdmin.getInstanceConfig(CLUSTER_NAME, INSTANCE_ID_2)).thenReturn( + getInstanceConfig(INSTANCE_ID_2, HOSTNAME_2)); + when(helixAdmin.getInstanceConfig(CLUSTER_NAME, INSTANCE_ID_3)).thenReturn( + getInstanceConfig(INSTANCE_ID_3, HOSTNAME_3)); + } - InstanceConfig instanceConfig3 = new InstanceConfig(INSTANCE_ID3); - instanceConfig3.setHostName(HOST_3_NAME); - instanceConfig3.setPort("1000"); - when(helixAdmin.getInstanceConfig(any(String.class), eq(INSTANCE_ID3))).thenReturn(instanceConfig3); - } + private static InstanceConfig getInstanceConfig(String instanceId, String hostName) { + InstanceConfig instanceConfig = new InstanceConfig(instanceId); + instanceConfig.setHostName(hostName); + instanceConfig.setPort(Integer.toString(HELIX_PORT)); + instanceConfig.getRecord().setIntField(Instance.ADMIN_PORT_KEY, HTTP_ADMIN_PORT); + instanceConfig.getRecord().setIntField(Instance.ADMIN_HTTPS_PORT_KEY, HTTPS_ADMIN_PORT); + return instanceConfig; } @Test public void testSegmentFoundSuccessfully() throws Exception { // SEGMENT_1 has only 2 online replicas. - List httpServerURIs = - PeerServerSegmentFinder.getPeerServerURIs(SEGMENT_1, CommonConstants.HTTP_PROTOCOL, _helixManager); - assertEquals(2, httpServerURIs.size()); - httpServerURIs.contains(new URI( - StringUtil.join("/", "http://" + HOST_1_NAME + ":" + ADMIN_PORT, "segments", TABLE_NAME_WITH_TYPE, SEGMENT_1))); - httpServerURIs.contains(new URI( - StringUtil.join("/", "http://" + HOST_3_NAME + ":" + ADMIN_PORT, "segments", TABLE_NAME_WITH_TYPE, SEGMENT_1))); - List httpsServerURIs = - PeerServerSegmentFinder.getPeerServerURIs(SEGMENT_1, CommonConstants.HTTPS_PROTOCOL, _helixManager); - assertEquals(2, httpsServerURIs.size()); - httpServerURIs.contains(new URI(StringUtil - .join("/", "https://" + HOST_1_NAME + ":" + ADMIN_PORT, "segments", TABLE_NAME_WITH_TYPE, SEGMENT_1))); - httpServerURIs.contains(new URI(StringUtil - .join("/", "https://" + HOST_3_NAME + ":" + ADMIN_PORT, "segments", TABLE_NAME_WITH_TYPE, SEGMENT_1))); + List httpServerURIs = PeerServerSegmentFinder.getPeerServerURIs(_helixManager, REALTIME_TABLE_NAME, SEGMENT_1, + CommonConstants.HTTP_PROTOCOL); + assertEquals(httpServerURIs.size(), 2); + assertTrue(httpServerURIs.contains(new URI( + String.format("http://%s:%d/segments/%s/%s", HOSTNAME_1, HTTP_ADMIN_PORT, REALTIME_TABLE_NAME, SEGMENT_1)))); + assertTrue(httpServerURIs.contains(new URI( + String.format("http://%s:%d/segments/%s/%s", HOSTNAME_3, HTTP_ADMIN_PORT, REALTIME_TABLE_NAME, SEGMENT_1)))); + List httpsServerURIs = PeerServerSegmentFinder.getPeerServerURIs(_helixManager, REALTIME_TABLE_NAME, SEGMENT_1, + CommonConstants.HTTPS_PROTOCOL); + assertEquals(httpsServerURIs.size(), 2); + assertTrue(httpsServerURIs.contains(new URI( + String.format("https://%s:%d/segments/%s/%s", HOSTNAME_1, HTTPS_ADMIN_PORT, REALTIME_TABLE_NAME, SEGMENT_1)))); + assertTrue(httpsServerURIs.contains(new URI( + String.format("https://%s:%d/segments/%s/%s", HOSTNAME_3, HTTPS_ADMIN_PORT, REALTIME_TABLE_NAME, SEGMENT_1)))); } @Test - public void testSegmentNotFound() - throws Exception { - Assert.assertEquals(0, - PeerServerSegmentFinder.getPeerServerURIs(SEGMENT_2, CommonConstants.HTTP_PROTOCOL, _helixManager).size()); + public void testSegmentNotFound() { + assertTrue(PeerServerSegmentFinder.getPeerServerURIs(_helixManager, REALTIME_TABLE_NAME, SEGMENT_2, + CommonConstants.HTTP_PROTOCOL).isEmpty()); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/core/util/QueryMultiThreadingUtilsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/util/QueryMultiThreadingUtilsTest.java new file mode 100644 index 000000000000..0c5c6ca555da --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/core/util/QueryMultiThreadingUtilsTest.java @@ -0,0 +1,83 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.core.util; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import org.testng.Assert; +import org.testng.annotations.Test; + + +public class QueryMultiThreadingUtilsTest { + @Test + public void testGetNumTasksForQuery() { + Assert.assertEquals(QueryMultiThreadingUtils.getNumTasksForQuery(1, 2), 1); + Assert.assertEquals(QueryMultiThreadingUtils.getNumTasksForQuery(3, 2), 2); + Assert.assertEquals(QueryMultiThreadingUtils.getNumTasksForQuery(0, -1), 1); + int numOps = QueryMultiThreadingUtils.MAX_NUM_THREADS_PER_QUERY; + // TaskUtils.MAX_NUM_THREADS_PER_QUERY at max + Assert.assertEquals(QueryMultiThreadingUtils.getNumTasksForQuery(numOps + 10, -1), + QueryMultiThreadingUtils.MAX_NUM_THREADS_PER_QUERY); + // But 1 at min + Assert.assertEquals(QueryMultiThreadingUtils.getNumTasksForQuery(numOps - 1, -1), Math.max(1, numOps - 1)); + } + + @Test + public void testGetNumTasks() { + Assert.assertEquals(QueryMultiThreadingUtils.getNumTasks(2, 3, 4), 1); + Assert.assertEquals(QueryMultiThreadingUtils.getNumTasks(7, 3, 4), 3); + Assert.assertEquals(QueryMultiThreadingUtils.getNumTasks(9, 3, 4), 3); + Assert.assertEquals(QueryMultiThreadingUtils.getNumTasks(10, 3, 4), 4); + Assert.assertEquals(QueryMultiThreadingUtils.getNumTasks(100, 3, 4), 4); + int targetPerThread = 5; + int numWorkUnits = QueryMultiThreadingUtils.MAX_NUM_THREADS_PER_QUERY * targetPerThread; + Assert.assertEquals(QueryMultiThreadingUtils.getNumTasks(numWorkUnits + 10, targetPerThread, -1), + QueryMultiThreadingUtils.MAX_NUM_THREADS_PER_QUERY); + } + + @Test + public void testRunTasksWithDeadline() { + ExecutorService exec = Executors.newCachedThreadPool(); + AtomicInteger sum = new AtomicInteger(0); + QueryMultiThreadingUtils.runTasksWithDeadline(5, index -> index, sum::addAndGet, e -> { + }, exec, System.currentTimeMillis() + 500); + // sum of 0, 1, .., 4 indices. + Assert.assertEquals(sum.get(), 10); + + // Task throws exception before timeout. + Exception[] err = new Exception[1]; + QueryMultiThreadingUtils.runTasksWithDeadline(5, index -> { + throw new RuntimeException("oops: " + index); + }, sum::addAndGet, e -> err[0] = e, exec, System.currentTimeMillis() + 500); + Assert.assertTrue(err[0].getMessage().contains("oops")); + + // Task timed out. + QueryMultiThreadingUtils.runTasksWithDeadline(5, index -> { + try { + Thread.sleep(10_000); + return index; + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }, sum::addAndGet, e -> err[0] = e, exec, System.currentTimeMillis() + 500); + Assert.assertTrue(err[0] instanceof TimeoutException); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/core/util/SchemaUtilsTest.java b/pinot-core/src/test/java/org/apache/pinot/core/util/SchemaUtilsTest.java index 115f50188fd6..e1dd69f7717a 100644 --- a/pinot-core/src/test/java/org/apache/pinot/core/util/SchemaUtilsTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/core/util/SchemaUtilsTest.java @@ -22,7 +22,9 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; import org.apache.pinot.segment.local.utils.SchemaUtils; import org.apache.pinot.spi.config.table.TableConfig; @@ -136,13 +138,17 @@ public void testCompatibilityWithTableConfig() { schema = new Schema.SchemaBuilder().setSchemaName(TABLE_NAME) .addDateTime(TIME_COLUMN, DataType.LONG, "1:MILLISECONDS:EPOCH", "1:HOURS").build(); tableConfig = - new TableConfigBuilder(TableType.REALTIME).setTableName(TABLE_NAME).setTimeColumnName(TIME_COLUMN).build(); + new TableConfigBuilder(TableType.REALTIME).setTableName(TABLE_NAME) + .setStreamConfigs(getStreamConfigs()) + .setTimeColumnName(TIME_COLUMN).build(); SchemaUtils.validate(schema, Lists.newArrayList(tableConfig)); // schema doesn't have destination columns from transformConfigs schema = new Schema.SchemaBuilder().setSchemaName(TABLE_NAME) .addDateTime(TIME_COLUMN, DataType.LONG, "1:MILLISECONDS:EPOCH", "1:HOURS").build(); - tableConfig = new TableConfigBuilder(TableType.REALTIME).setTableName(TABLE_NAME).setTimeColumnName(TIME_COLUMN) + tableConfig = new TableConfigBuilder(TableType.REALTIME).setTableName(TABLE_NAME) + .setStreamConfigs(getStreamConfigs()) + .setTimeColumnName(TIME_COLUMN) .setIngestionConfig(ingestionConfig).build(); try { SchemaUtils.validate(schema, Lists.newArrayList(tableConfig)); @@ -155,6 +161,42 @@ public void testCompatibilityWithTableConfig() { .addDateTime(TIME_COLUMN, DataType.LONG, "1:MILLISECONDS:EPOCH", "1:HOURS") .addSingleValueDimension("colA", DataType.STRING).build(); SchemaUtils.validate(schema, Lists.newArrayList(tableConfig)); + + schema = new Schema.SchemaBuilder().setSchemaName(TABLE_NAME).addMetric("double", DataType.DOUBLE, "NaN").build(); + tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build(); + try { + SchemaUtils.validate(schema, Lists.newArrayList(tableConfig)); + Assert.fail("Should fail schema validation, as double has NaN default value"); + } catch (IllegalStateException e) { + // expected + Assert.assertTrue(e.getMessage().startsWith("NaN as null default value is not managed yet for")); + } + + schema = new Schema.SchemaBuilder().setSchemaName(TABLE_NAME).addMetric("float", DataType.FLOAT, "NaN").build(); + tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build(); + try { + SchemaUtils.validate(schema, Lists.newArrayList(tableConfig)); + Assert.fail("Should fail schema validation, as float has NaN default value"); + } catch (IllegalStateException e) { + // expected + Assert.assertTrue(e.getMessage().startsWith("NaN as null default value is not managed yet for")); + } + + schema = + new Schema.SchemaBuilder().setSchemaName(TABLE_NAME).addSingleValueDimension("string", DataType.STRING, "NaN") + .build(); + tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build(); + SchemaUtils.validate(schema, Lists.newArrayList(tableConfig)); + } + + private Map getStreamConfigs() { + Map streamConfigs = new HashMap<>(); + streamConfigs.put("streamType", "kafka"); + streamConfigs.put("stream.kafka.consumer.type", "lowlevel"); + streamConfigs.put("stream.kafka.topic.name", "test"); + streamConfigs.put("stream.kafka.decoder.class.name", + "org.apache.pinot.plugin.stream.kafka.KafkaJSONMessageDecoder"); + return streamConfigs; } /** @@ -248,6 +290,19 @@ public void testValidateDateTimeFieldSpec() { checkValidationFails(pinotSchema); } + @Test + public void testValidateCaseInsensitive() { + Schema pinotSchema; + pinotSchema = + new Schema.SchemaBuilder().addTime(new TimeGranularitySpec(DataType.LONG, TimeUnit.MILLISECONDS, "incoming"), + new TimeGranularitySpec(DataType.INT, TimeUnit.DAYS, "outgoing")) + .addSingleValueDimension("dim1", DataType.INT) + .addSingleValueDimension("Dim1", DataType.INT) + .build(); + + checkValidationFails(pinotSchema, true); + } + @Test public void testValidatePrimaryKeyColumns() { Schema pinotSchema; @@ -416,12 +471,16 @@ public void testColumnNameValidation() checkValidationFails(pinotSchema); } - private void checkValidationFails(Schema pinotSchema) { + private void checkValidationFails(Schema pinotSchema, boolean isIgnoreCase) { try { - SchemaUtils.validate(pinotSchema); + SchemaUtils.validate(pinotSchema, isIgnoreCase); Assert.fail("Schema validation should have failed."); } catch (IllegalArgumentException | IllegalStateException e) { // expected } } + + private void checkValidationFails(Schema pinotSchema) { + checkValidationFails(pinotSchema, false); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/AllNullQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/AllNullQueriesTest.java index 960bde9ccac3..a2a720edfc21 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/AllNullQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/AllNullQueriesTest.java @@ -26,7 +26,6 @@ import java.util.List; import java.util.Map; import org.apache.commons.io.FileUtils; -import org.apache.pinot.common.datatable.DataTableFactory; import org.apache.pinot.common.response.broker.BrokerResponseNative; import org.apache.pinot.common.response.broker.ResultTable; import org.apache.pinot.common.utils.DataSchema; @@ -46,6 +45,7 @@ import org.apache.pinot.spi.utils.ReadMode; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; @@ -121,8 +121,8 @@ private void setUp(TableConfig tableConfig, DataType dataType, File indexDir) _indexSegments = Arrays.asList(immutableSegment, immutableSegment); } - @Test - public void testQueriesWithDictLongColumn() + @Test(dataProvider = "queries") + public void testQueriesWithDictLongColumn(Query query) throws Exception { ColumnDataType columnDataType = ColumnDataType.LONG; TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE) @@ -130,11 +130,11 @@ public void testQueriesWithDictLongColumn() .build(); File indexDir = new File(FileUtils.getTempDirectory(), "AllNullWithDictLongColumnQueriesTest"); setUp(tableConfig, columnDataType.toDataType(), indexDir); - testQueries(columnDataType, indexDir); + testQueries(columnDataType, indexDir, query); } - @Test(priority = 1) - public void testQueriesWithNoDictLongColumn() + @Test(priority = 1, dataProvider = "queries") + public void testQueriesWithNoDictLongColumn(Query query) throws Exception { ColumnDataType columnDataType = ColumnDataType.LONG; List noDictionaryColumns = new ArrayList(); @@ -145,11 +145,11 @@ public void testQueriesWithNoDictLongColumn() .build(); File indexDir = new File(FileUtils.getTempDirectory(), "AllNullWithNoDictLongColumnQueriesTest"); setUp(tableConfig, columnDataType.toDataType(), indexDir); - testQueries(columnDataType, indexDir); + testQueries(columnDataType, indexDir, query); } - @Test(priority = 2) - public void testQueriesWithDictFloatColumn() + @Test(priority = 2, dataProvider = "queries") + public void testQueriesWithDictFloatColumn(Query query) throws Exception { ColumnDataType columnDataType = ColumnDataType.FLOAT; TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE) @@ -157,11 +157,11 @@ public void testQueriesWithDictFloatColumn() .build(); File indexDir = new File(FileUtils.getTempDirectory(), "AllNullWithDictFloatColumnQueriesTest"); setUp(tableConfig, columnDataType.toDataType(), indexDir); - testQueries(columnDataType, indexDir); + testQueries(columnDataType, indexDir, query); } - @Test(priority = 3) - public void testQueriesWithNoDictFloatColumn() + @Test(priority = 3, dataProvider = "queries") + public void testQueriesWithNoDictFloatColumn(Query query) throws Exception { ColumnDataType columnDataType = ColumnDataType.FLOAT; List noDictionaryColumns = new ArrayList(); @@ -172,11 +172,11 @@ public void testQueriesWithNoDictFloatColumn() .build(); File indexDir = new File(FileUtils.getTempDirectory(), "AllNullWithNoDictFloatColumnQueriesTest"); setUp(tableConfig, columnDataType.toDataType(), indexDir); - testQueries(columnDataType, indexDir); + testQueries(columnDataType, indexDir, query); } - @Test(priority = 4) - public void testQueriesWithDictDoubleColumn() + @Test(priority = 4, dataProvider = "queries") + public void testQueriesWithDictDoubleColumn(Query query) throws Exception { ColumnDataType columnDataType = ColumnDataType.DOUBLE; TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE) @@ -184,11 +184,11 @@ public void testQueriesWithDictDoubleColumn() .build(); File indexDir = new File(FileUtils.getTempDirectory(), "AllNullWithDictDoubleColumnQueriesTest"); setUp(tableConfig, columnDataType.toDataType(), indexDir); - testQueries(columnDataType, indexDir); + testQueries(columnDataType, indexDir, query); } - @Test(priority = 5) - public void testQueriesWithNoDictDoubleColumn() + @Test(priority = 5, dataProvider = "queries") + public void testQueriesWithNoDictDoubleColumn(Query query) throws Exception { ColumnDataType columnDataType = ColumnDataType.DOUBLE; List noDictionaryColumns = new ArrayList(); @@ -199,11 +199,11 @@ public void testQueriesWithNoDictDoubleColumn() .build(); File indexDir = new File(FileUtils.getTempDirectory(), "AllNullWithNoDictDoubleColumnQueriesTest"); setUp(tableConfig, columnDataType.toDataType(), indexDir); - testQueries(columnDataType, indexDir); + testQueries(columnDataType, indexDir, query); } - @Test(priority = 6) - public void testQueriesWithDictIntColumn() + @Test(priority = 6, dataProvider = "queries") + public void testQueriesWithDictIntColumn(Query query) throws Exception { ColumnDataType columnDataType = ColumnDataType.INT; TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE) @@ -211,11 +211,11 @@ public void testQueriesWithDictIntColumn() .build(); File indexDir = new File(FileUtils.getTempDirectory(), "AllNullWithDictIntColumnQueriesTest"); setUp(tableConfig, columnDataType.toDataType(), indexDir); - testQueries(columnDataType, indexDir); + testQueries(columnDataType, indexDir, query); } - @Test(priority = 7) - public void testQueriesWithNoDictIntColumn() + @Test(priority = 7, dataProvider = "queries") + public void testQueriesWithNoDictIntColumn(Query query) throws Exception { ColumnDataType columnDataType = ColumnDataType.INT; List noDictionaryColumns = new ArrayList(); @@ -226,11 +226,11 @@ public void testQueriesWithNoDictIntColumn() .build(); File indexDir = new File(FileUtils.getTempDirectory(), "AllNullWithNoDictIntColumnQueriesTest"); setUp(tableConfig, columnDataType.toDataType(), indexDir); - testQueries(columnDataType, indexDir); + testQueries(columnDataType, indexDir, query); } - @Test(priority = 8) - public void testQueriesWithDictBigDecimalColumn() + @Test(priority = 8, dataProvider = "queries") + public void testQueriesWithDictBigDecimalColumn(Query query) throws Exception { ColumnDataType columnDataType = ColumnDataType.BIG_DECIMAL; TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE) @@ -238,11 +238,11 @@ public void testQueriesWithDictBigDecimalColumn() .build(); File indexDir = new File(FileUtils.getTempDirectory(), "AllNullWithDictBigDecimalColumnQueriesTest"); setUp(tableConfig, columnDataType.toDataType(), indexDir); - testQueries(columnDataType, indexDir); + testQueries(columnDataType, indexDir, query); } - @Test(priority = 9) - public void testQueriesWithNoDictBigDecimalColumn() + @Test(priority = 9, dataProvider = "queries") + public void testQueriesWithNoDictBigDecimalColumn(Query query) throws Exception { ColumnDataType columnDataType = ColumnDataType.BIG_DECIMAL; List noDictionaryColumns = new ArrayList(); @@ -253,11 +253,11 @@ public void testQueriesWithNoDictBigDecimalColumn() .build(); File indexDir = new File(FileUtils.getTempDirectory(), "AllNullWithNoDictBigDecimalColumnQueriesTest"); setUp(tableConfig, columnDataType.toDataType(), indexDir); - testQueries(columnDataType, indexDir); + testQueries(columnDataType, indexDir, query); } - @Test(priority = 10) - public void testQueriesWithDictStringColumn() + @Test(priority = 10, dataProvider = "queries") + public void testQueriesWithDictStringColumn(Query query) throws Exception { ColumnDataType columnDataType = ColumnDataType.STRING; TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE) @@ -265,11 +265,11 @@ public void testQueriesWithDictStringColumn() .build(); File indexDir = new File(FileUtils.getTempDirectory(), "AllNullWithDictStringColumnQueriesTest"); setUp(tableConfig, columnDataType.toDataType(), indexDir); - testQueries(columnDataType, indexDir); + testQueries(columnDataType, indexDir, query); } - @Test(priority = 11) - public void testQueriesWithNoDictStringColumn() + @Test(priority = 11, dataProvider = "queries") + public void testQueriesWithNoDictStringColumn(Query query) throws Exception { ColumnDataType columnDataType = ColumnDataType.STRING; List noDictionaryColumns = new ArrayList(); @@ -280,305 +280,384 @@ public void testQueriesWithNoDictStringColumn() .build(); File indexDir = new File(FileUtils.getTempDirectory(), "AllNullWithNoDictStringColumnQueriesTest"); setUp(tableConfig, columnDataType.toDataType(), indexDir); - testQueries(columnDataType, indexDir); + testQueries(columnDataType, indexDir, query); } - public void testQueries(ColumnDataType columnDataType, File indexDir) - throws IOException { - DataTableBuilderFactory.setDataTableVersion(DataTableFactory.VERSION_4); - Map queryOptions = new HashMap<>(); - queryOptions.put("enableNullHandling", "true"); - DataType dataType = columnDataType.toDataType(); - { - String query = String.format("SELECT %s FROM testTable WHERE %s is null limit 5000", COLUMN_NAME, COLUMN_NAME); - BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); - ResultTable resultTable = brokerResponse.getResultTable(); - DataSchema dataSchema = resultTable.getDataSchema(); - assertEquals(dataSchema, - new DataSchema(new String[]{COLUMN_NAME}, new ColumnDataType[]{columnDataType})); - List rows = resultTable.getRows(); - assertEquals(rows.size(), 4000); - for (Object[] row : rows) { - assertNull(row[0]); - } - } - { - String query = String.format("SELECT %s FROM testTable WHERE %s is not null limit 5000", - COLUMN_NAME, COLUMN_NAME); - BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); - ResultTable resultTable = brokerResponse.getResultTable(); - DataSchema dataSchema = resultTable.getDataSchema(); - assertEquals(dataSchema, - new DataSchema(new String[]{COLUMN_NAME}, new ColumnDataType[]{columnDataType})); - List rows = resultTable.getRows(); - assertEquals(rows.size(), 0); - } - if (columnDataType != ColumnDataType.STRING) { - { - String query = String.format( - "SELECT count(*) as count1, count(%s) as count2, min(%s) as min, max(%s) as max FROM testTable", - COLUMN_NAME, COLUMN_NAME, COLUMN_NAME); - BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); - ResultTable resultTable = brokerResponse.getResultTable(); - DataSchema dataSchema = resultTable.getDataSchema(); - assertEquals(dataSchema, new DataSchema(new String[]{"count1", "count2", "min", "max"}, new ColumnDataType[]{ - ColumnDataType.LONG, ColumnDataType.LONG, ColumnDataType.DOUBLE, ColumnDataType.DOUBLE - })); - List rows = resultTable.getRows(); - assertEquals(rows.size(), 1); - Object[] row = rows.get(0); - assertEquals(row.length, 4); - // Note: count(*) returns total number of docs (nullable and non-nullable). - assertEquals((long) row[0], 1000 * 4); - // count(col) returns the count of non-nullable docs. - assertEquals((long) row[1], 0); - assertNull(row[2]); - assertNull(row[3]); - } - } - { - String query = "SELECT * FROM testTable"; - BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); - ResultTable resultTable = brokerResponse.getResultTable(); - DataSchema dataSchema = resultTable.getDataSchema(); - assertEquals(dataSchema, new DataSchema(new String[]{COLUMN_NAME}, new ColumnDataType[]{columnDataType})); - List rows = resultTable.getRows(); - assertEquals(rows.size(), 10); - for (int i = 0; i < 10; i++) { - Object[] row = rows.get(i); - assertEquals(row.length, 1); - if (row[0] != null) { - assertEquals(row[0], i); - } - } - } - { - String query = String.format("SELECT * FROM testTable ORDER BY %s DESC LIMIT 4000", COLUMN_NAME); - // getBrokerResponseForSqlQuery(query) runs SQL query on multiple index segments. The result should be equivalent - // to querying 4 identical index segments. - BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); - ResultTable resultTable = brokerResponse.getResultTable(); - DataSchema dataSchema = resultTable.getDataSchema(); - assertEquals(dataSchema, - new DataSchema(new String[]{COLUMN_NAME}, new ColumnDataType[]{columnDataType})); - List rows = resultTable.getRows(); - assertEquals(rows.size(), 4000); - for (int i = 0; i < 4000; i += 4) { - for (int j = 0; j < 4; j++) { - Object[] values = rows.get(i + j); - assertEquals(values.length, 1); - assertNull(values[0]); - } - } - } - { - String query = String.format("SELECT DISTINCT %s FROM testTable ORDER BY %s", COLUMN_NAME, COLUMN_NAME); - BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); - ResultTable resultTable = brokerResponse.getResultTable(); - DataSchema dataSchema = resultTable.getDataSchema(); - assertEquals(dataSchema, - new DataSchema(new String[]{COLUMN_NAME}, new ColumnDataType[]{columnDataType})); - List rows = resultTable.getRows(); - assertEquals(rows.size(), 1); - for (Object[] row : rows) { - assertEquals(row.length, 1); - assertNull(row[0]); - } - } - { - int limit = 40; - String query = String.format("SELECT DISTINCT %s FROM testTable ORDER BY %s LIMIT %d", COLUMN_NAME, COLUMN_NAME, - limit); - BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); - ResultTable resultTable = brokerResponse.getResultTable(); - DataSchema dataSchema = resultTable.getDataSchema(); - assertEquals(dataSchema, - new DataSchema(new String[]{COLUMN_NAME}, new ColumnDataType[]{columnDataType})); - List rows = resultTable.getRows(); - for (Object[] row : rows) { - assertEquals(row.length, 1); - assertNull(row[0]); - } - } - { - // This test case was added to validate path-code for distinct w/o order by. - int limit = 40; - String query = String.format("SELECT DISTINCT %s FROM testTable LIMIT %d", COLUMN_NAME, limit); - BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); - ResultTable resultTable = brokerResponse.getResultTable(); - DataSchema dataSchema = resultTable.getDataSchema(); - assertEquals(dataSchema, - new DataSchema(new String[]{COLUMN_NAME}, new ColumnDataType[]{columnDataType})); - List rows = resultTable.getRows(); - assertEquals(rows.size(), 1); - } - if (columnDataType != ColumnDataType.STRING) { - { - String query = String.format("SELECT COUNT(%s) AS count, MIN(%s) AS min, MAX(%s) AS max, AVG(%s) AS avg," - + " SUM(%s) AS sum FROM testTable LIMIT 1000", COLUMN_NAME, COLUMN_NAME, COLUMN_NAME, COLUMN_NAME, - COLUMN_NAME); - BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); - ResultTable resultTable = brokerResponse.getResultTable(); - DataSchema dataSchema = resultTable.getDataSchema(); - assertEquals(dataSchema, new DataSchema(new String[]{"count", "min", "max", "avg", "sum"}, - new ColumnDataType[] { - ColumnDataType.LONG, ColumnDataType.DOUBLE, ColumnDataType.DOUBLE, ColumnDataType.DOUBLE, - ColumnDataType.DOUBLE - })); - List rows = resultTable.getRows(); - assertEquals(rows.size(), 1); - assertEquals((long) rows.get(0)[0], 0); - assertNull(rows.get(0)[1]); - assertNull(rows.get(0)[2]); - assertNull(rows.get(0)[3]); - assertNull(rows.get(0)[4]); - } - } - { - String query = String.format("SELECT %s FROM testTable GROUP BY %s ORDER BY %s DESC", COLUMN_NAME, COLUMN_NAME, - COLUMN_NAME); - BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); - ResultTable resultTable = brokerResponse.getResultTable(); - DataSchema dataSchema = resultTable.getDataSchema(); - assertEquals(dataSchema, new DataSchema(new String[]{COLUMN_NAME}, new ColumnDataType[]{columnDataType})); - List rows = resultTable.getRows(); - assertEquals(rows.size(), 1); - assertEquals(rows.get(0).length, 1); - assertNull(rows.get(0)[0]); - } - { - String query = String.format( - "SELECT COUNT(*) AS count, %s FROM testTable GROUP BY %s ORDER BY %s DESC LIMIT 1000", COLUMN_NAME, - COLUMN_NAME, COLUMN_NAME); - BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); - ResultTable resultTable = brokerResponse.getResultTable(); - DataSchema dataSchema = resultTable.getDataSchema(); - assertEquals(dataSchema, new DataSchema(new String[]{"count", COLUMN_NAME}, - new ColumnDataType[]{ColumnDataType.LONG, columnDataType})); - List rows = resultTable.getRows(); - assertEquals(rows.size(), 1); - Object[] row = rows.get(0); - assertEquals(row[0], 4000L); - assertNull(row[1]); - } - { - String query = String.format("SELECT SUMPRECISION(%s) AS sum FROM testTable", COLUMN_NAME); - BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); - ResultTable resultTable = brokerResponse.getResultTable(); - DataSchema dataSchema = resultTable.getDataSchema(); - assertEquals(dataSchema, new DataSchema(new String[]{"sum"}, new ColumnDataType[]{ColumnDataType.STRING})); - List rows = resultTable.getRows(); - assertEquals(rows.size(), 1); - assertNull(rows.get(0)[0]); + public static abstract class Query { + private final String _query; + + public Query(String query) { + _query = query; } - if (columnDataType != ColumnDataType.STRING) { - { - // Note: in Presto, inequality, equality, and IN comparison with nulls always returns false: - long lowerLimit = 69; - String query = - String.format("SELECT %s FROM testTable WHERE %s > '%s' LIMIT 50", COLUMN_NAME, COLUMN_NAME, lowerLimit); - BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); - ResultTable resultTable = brokerResponse.getResultTable(); - DataSchema dataSchema = resultTable.getDataSchema(); - assertEquals(dataSchema, new DataSchema(new String[]{COLUMN_NAME}, new ColumnDataType[]{columnDataType})); - // Pinot loops through the column values from smallest to biggest. Null comparison always returns false. - List rows = resultTable.getRows(); - assertEquals(rows.size(), 0); - } + + @Override + public String toString() { + return "Query{" + _query + '}'; } - { - String query = String.format("SELECT %s FROM testTable WHERE %s = '%s'", COLUMN_NAME, COLUMN_NAME, 68); - BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); - ResultTable resultTable = brokerResponse.getResultTable(); - DataSchema dataSchema = resultTable.getDataSchema(); - assertEquals(dataSchema, - new DataSchema(new String[]{COLUMN_NAME}, new ColumnDataType[]{columnDataType})); - List rows = resultTable.getRows(); - assertEquals(rows.size(), 0); + + public String getQuery() { + return _query; } - { - String query = String.format("SELECT %s FROM testTable WHERE %s = '%s'", COLUMN_NAME, COLUMN_NAME, 69); - BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); - ResultTable resultTable = brokerResponse.getResultTable(); - DataSchema dataSchema = resultTable.getDataSchema(); - assertEquals(dataSchema, - new DataSchema(new String[]{COLUMN_NAME}, new ColumnDataType[]{columnDataType})); - List rows = resultTable.getRows(); - assertEquals(rows.size(), 0); + + public boolean skip(ColumnDataType columnDataType) { + return false; } - if (columnDataType != ColumnDataType.STRING) { - { - String query = String.format("SELECT COUNT(%s) AS count, MIN(%s) AS min, MAX(%s) AS max, SUM(%s) AS sum" + " " - + "FROM testTable GROUP BY %s ORDER BY max", COLUMN_NAME, COLUMN_NAME, COLUMN_NAME, COLUMN_NAME, - COLUMN_NAME); - BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); - ResultTable resultTable = brokerResponse.getResultTable(); - DataSchema dataSchema = resultTable.getDataSchema(); - assertEquals(dataSchema, new DataSchema(new String[]{"count", "min", "max", "sum"}, new ColumnDataType[]{ - ColumnDataType.LONG, ColumnDataType.DOUBLE, ColumnDataType.DOUBLE, ColumnDataType.DOUBLE - })); - List rows = resultTable.getRows(); - assertEquals(rows.size(), 1); - Object[] row = rows.get(0); - assertEquals(row.length, 4); - // Count(column) return 0 if all values are nulls. - assertEquals(row[0], 0L); - assertNull(row[1]); - assertNull(row[2]); - assertNull(row[3]); - } - { - String query = String.format( - "SELECT AVG(%s) AS avg FROM testTable GROUP BY %s ORDER BY avg LIMIT 20", COLUMN_NAME, COLUMN_NAME); - BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); - ResultTable resultTable = brokerResponse.getResultTable(); - DataSchema dataSchema = resultTable.getDataSchema(); - assertEquals(dataSchema, new DataSchema(new String[]{"avg"}, new ColumnDataType[]{ColumnDataType.DOUBLE})); - List rows = resultTable.getRows(); - assertEquals(rows.size(), 1); - Object[] row = rows.get(0); - assertEquals(row.length, 1); - assertNull(row[0]); - } - { - // MODE cannot handle BIG_DECIMAL yet. - if (dataType != DataType.BIG_DECIMAL) { - String query = String.format("SELECT AVG(%s) AS avg, MODE(%s) AS mode, DISTINCTCOUNT(%s) as distinct_count" - + " FROM testTable GROUP BY %s ORDER BY %s LIMIT 200", COLUMN_NAME, COLUMN_NAME, COLUMN_NAME, - COLUMN_NAME, COLUMN_NAME); - BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); - ResultTable resultTable = brokerResponse.getResultTable(); - DataSchema dataSchema = resultTable.getDataSchema(); - assertEquals(dataSchema, new DataSchema(new String[]{"avg", "mode", "distinct_count"}, - new ColumnDataType[]{ColumnDataType.DOUBLE, ColumnDataType.DOUBLE, ColumnDataType.INT})); - List rows = resultTable.getRows(); - assertEquals(rows.size(), 1); - Object[] row = rows.get(0); - assertEquals(row.length, 3); - assertNull(row[0]); - // TODO: this should return null instead of default value. - if (dataType == DataType.DOUBLE || dataType == DataType.FLOAT) { - assertEquals(row[1], Double.NEGATIVE_INFINITY); - } else if (dataType == DataType.LONG) { - assertEquals(((Double) row[1]).longValue(), Long.MIN_VALUE); + + public abstract void verify(ColumnDataType columnDataType, BrokerResponseNative brokerResponse); + } + + @DataProvider(name = "queries") + public static Query[] queries() { + return new Query[] { + new Query(String.format("SELECT %s FROM testTable WHERE %s is null limit 5000", COLUMN_NAME, COLUMN_NAME)) { + @Override + public void verify(ColumnDataType columnDataType, BrokerResponseNative brokerResponse) { + ResultTable resultTable = brokerResponse.getResultTable(); + DataSchema dataSchema = resultTable.getDataSchema(); + assertEquals(dataSchema, + new DataSchema(new String[]{COLUMN_NAME}, new ColumnDataType[]{columnDataType})); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 4000); + for (Object[] row : rows) { + assertNull(row[0]); + } + } + }, + new Query(String.format("SELECT %s FROM testTable WHERE %s is not null limit 5000", COLUMN_NAME, COLUMN_NAME)) { + @Override + public void verify(ColumnDataType columnDataType, BrokerResponseNative brokerResponse) { + ResultTable resultTable = brokerResponse.getResultTable(); + DataSchema dataSchema = resultTable.getDataSchema(); + assertEquals(dataSchema, + new DataSchema(new String[]{COLUMN_NAME}, new ColumnDataType[]{columnDataType})); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 0); + } + }, + new Query(String.format( + "SELECT count(*) as count1, count(%s) as count2, min(%s) as min, max(%s) as max FROM testTable", + COLUMN_NAME, COLUMN_NAME, COLUMN_NAME)) { + @Override + public boolean skip(ColumnDataType columnDataType) { + return columnDataType != ColumnDataType.STRING; + } + + @Override + public void verify(ColumnDataType columnDataType, BrokerResponseNative brokerResponse) { + ResultTable resultTable = brokerResponse.getResultTable(); + DataSchema dataSchema = resultTable.getDataSchema(); + assertEquals(dataSchema, new DataSchema(new String[]{"count1", "count2", "min", "max"}, + new ColumnDataType[]{ + ColumnDataType.LONG, ColumnDataType.LONG, ColumnDataType.DOUBLE, ColumnDataType.DOUBLE + })); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + Object[] row = rows.get(0); + assertEquals(row.length, 4); + // Note: count(*) returns total number of docs (nullable and non-nullable). + assertEquals((long) row[0], 1000 * 4); + // count(col) returns the count of non-nullable docs. + assertEquals((long) row[1], 0); + assertNull(row[2]); + assertNull(row[3]); + } + }, + new Query("SELECT * FROM testTable") { + @Override + public void verify(ColumnDataType columnDataType, BrokerResponseNative brokerResponse) { + ResultTable resultTable = brokerResponse.getResultTable(); + DataSchema dataSchema = resultTable.getDataSchema(); + assertEquals(dataSchema, new DataSchema(new String[]{COLUMN_NAME}, new ColumnDataType[]{columnDataType})); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 10); + for (int i = 0; i < 10; i++) { + Object[] row = rows.get(i); + assertEquals(row.length, 1); + if (row[0] != null) { + assertEquals(row[0], i); + } + } + } + }, + new Query(String.format("SELECT * FROM testTable ORDER BY %s DESC LIMIT 4000", COLUMN_NAME)) { + // getBrokerResponseForSqlQuery(query) runs SQL query on multiple index segments. The result should + // be equivalent to querying 4 identical index segments. + @Override + public void verify(ColumnDataType columnDataType, BrokerResponseNative brokerResponse) { + ResultTable resultTable = brokerResponse.getResultTable(); + DataSchema dataSchema = resultTable.getDataSchema(); + assertEquals(dataSchema, + new DataSchema(new String[]{COLUMN_NAME}, new ColumnDataType[]{columnDataType})); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 4000); + for (int i = 0; i < 4000; i += 4) { + for (int j = 0; j < 4; j++) { + Object[] values = rows.get(i + j); + assertEquals(values.length, 1); + assertNull(values[0]); + } + } + } + }, + new Query(String.format("SELECT DISTINCT %s FROM testTable ORDER BY %s", COLUMN_NAME, COLUMN_NAME)) { + @Override + public void verify(ColumnDataType columnDataType, BrokerResponseNative brokerResponse) { + ResultTable resultTable = brokerResponse.getResultTable(); + DataSchema dataSchema = resultTable.getDataSchema(); + assertEquals(dataSchema, + new DataSchema(new String[]{COLUMN_NAME}, new ColumnDataType[]{columnDataType})); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + for (Object[] row : rows) { + assertEquals(row.length, 1); + assertNull(row[0]); + } + } + }, + new Query(String.format("SELECT DISTINCT %s FROM testTable ORDER BY %s LIMIT %d", + COLUMN_NAME, COLUMN_NAME, 40)) { + @Override + public void verify(ColumnDataType columnDataType, BrokerResponseNative brokerResponse) { + ResultTable resultTable = brokerResponse.getResultTable(); + DataSchema dataSchema = resultTable.getDataSchema(); + assertEquals(dataSchema, + new DataSchema(new String[]{COLUMN_NAME}, new ColumnDataType[]{columnDataType})); + List rows = resultTable.getRows(); + for (Object[] row : rows) { + assertEquals(row.length, 1); + assertNull(row[0]); + } + } + }, + new Query(String.format("SELECT DISTINCT %s FROM testTable LIMIT %d", COLUMN_NAME, 40)) { + @Override + public void verify(ColumnDataType columnDataType, BrokerResponseNative brokerResponse) { + ResultTable resultTable = brokerResponse.getResultTable(); + DataSchema dataSchema = resultTable.getDataSchema(); + assertEquals(dataSchema, + new DataSchema(new String[]{COLUMN_NAME}, new ColumnDataType[]{columnDataType})); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + } + }, + new Query( + String.format("SELECT COUNT(%s) AS count, MIN(%s) AS min, MAX(%s) AS max, AVG(%s) AS avg," + + " SUM(%s) AS sum FROM testTable LIMIT 1000", COLUMN_NAME, COLUMN_NAME, COLUMN_NAME, COLUMN_NAME, + COLUMN_NAME) + ) { + @Override + public boolean skip(ColumnDataType columnDataType) { + return columnDataType != ColumnDataType.STRING; + } + @Override + public void verify(ColumnDataType columnDataType, BrokerResponseNative brokerResponse) { + ResultTable resultTable = brokerResponse.getResultTable(); + DataSchema dataSchema = resultTable.getDataSchema(); + assertEquals(dataSchema, new DataSchema(new String[]{"count", "min", "max", "avg", "sum"}, + new ColumnDataType[] { + ColumnDataType.LONG, ColumnDataType.DOUBLE, ColumnDataType.DOUBLE, ColumnDataType.DOUBLE, + ColumnDataType.DOUBLE + })); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + assertEquals((long) rows.get(0)[0], 0); + assertNull(rows.get(0)[1]); + assertNull(rows.get(0)[2]); + assertNull(rows.get(0)[3]); + assertNull(rows.get(0)[4]); + } + }, + new Query(String.format("SELECT %s FROM testTable GROUP BY %s ORDER BY %s DESC", COLUMN_NAME, COLUMN_NAME, + COLUMN_NAME)) { + @Override + public void verify(ColumnDataType columnDataType, BrokerResponseNative brokerResponse) { + ResultTable resultTable = brokerResponse.getResultTable(); + DataSchema dataSchema = resultTable.getDataSchema(); + assertEquals(dataSchema, new DataSchema(new String[]{COLUMN_NAME}, new ColumnDataType[]{columnDataType})); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + assertEquals(rows.get(0).length, 1); + assertNull(rows.get(0)[0]); + } + }, + new Query(String.format( + "SELECT COUNT(*) AS count, %s FROM testTable GROUP BY %s ORDER BY %s DESC LIMIT 1000", COLUMN_NAME, + COLUMN_NAME, COLUMN_NAME)) { + @Override + public void verify(ColumnDataType columnDataType, BrokerResponseNative brokerResponse) { + ResultTable resultTable = brokerResponse.getResultTable(); + DataSchema dataSchema = resultTable.getDataSchema(); + assertEquals(dataSchema, new DataSchema(new String[]{"count", COLUMN_NAME}, + new ColumnDataType[]{ColumnDataType.LONG, columnDataType})); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + Object[] row = rows.get(0); + assertEquals(row[0], 4000L); + assertNull(row[1]); + } + }, + new Query(String.format("SELECT SUMPRECISION(%s) AS sum FROM testTable", COLUMN_NAME)) { + @Override + public void verify(ColumnDataType columnDataType, BrokerResponseNative brokerResponse) { + ResultTable resultTable = brokerResponse.getResultTable(); + DataSchema dataSchema = resultTable.getDataSchema(); + assertEquals(dataSchema, new DataSchema(new String[]{"sum"}, new ColumnDataType[]{ColumnDataType.STRING})); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + assertNull(rows.get(0)[0]); + } + }, + new Query(String.format("SELECT %s FROM testTable WHERE %s > '%s' LIMIT 50", COLUMN_NAME, COLUMN_NAME, 69)) { + // Note: in Presto, inequality, equality, and IN comparison with nulls always returns false: + @Override + public boolean skip(ColumnDataType columnDataType) { + return columnDataType != ColumnDataType.STRING; + } + + @Override + public void verify(ColumnDataType columnDataType, BrokerResponseNative brokerResponse) { + ResultTable resultTable = brokerResponse.getResultTable(); + DataSchema dataSchema = resultTable.getDataSchema(); + assertEquals(dataSchema, new DataSchema(new String[]{COLUMN_NAME}, new ColumnDataType[]{columnDataType})); + // Pinot loops through the column values from smallest to biggest. Null comparison always returns false. + List rows = resultTable.getRows(); + assertEquals(rows.size(), 0); + } + }, + new Query(String.format("SELECT %s FROM testTable WHERE %s = '%s'", COLUMN_NAME, COLUMN_NAME, 68)) { + @Override + public void verify(ColumnDataType columnDataType, BrokerResponseNative brokerResponse) { + ResultTable resultTable = brokerResponse.getResultTable(); + DataSchema dataSchema = resultTable.getDataSchema(); + assertEquals(dataSchema, + new DataSchema(new String[]{COLUMN_NAME}, new ColumnDataType[]{columnDataType})); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 0); + } + }, + new Query(String.format("SELECT %s FROM testTable WHERE %s = '%s'", COLUMN_NAME, COLUMN_NAME, 69)) { + @Override + public void verify(ColumnDataType columnDataType, BrokerResponseNative brokerResponse) { + ResultTable resultTable = brokerResponse.getResultTable(); + DataSchema dataSchema = resultTable.getDataSchema(); + assertEquals(dataSchema, new DataSchema(new String[]{"count", "min", "max", "sum"}, new ColumnDataType[]{ + ColumnDataType.LONG, ColumnDataType.DOUBLE, ColumnDataType.DOUBLE, ColumnDataType.DOUBLE + })); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + Object[] row = rows.get(0); + assertEquals(row.length, 4); + // Count(column) return 0 if all values are nulls. + assertEquals(row[0], 0L); + assertNull(row[1]); + assertNull(row[2]); + assertNull(row[3]); + } + }, + new Query( + String.format("SELECT COUNT(%s) AS count, MIN(%s) AS min, MAX(%s) AS max, SUM(%s) AS sum" + " " + + "FROM testTable GROUP BY %s ORDER BY max", COLUMN_NAME, COLUMN_NAME, COLUMN_NAME, COLUMN_NAME, + COLUMN_NAME) + ) { + @Override + public boolean skip(ColumnDataType columnDataType) { + return columnDataType != ColumnDataType.STRING; + } + @Override + public void verify(ColumnDataType columnDataType, BrokerResponseNative brokerResponse) { + ResultTable resultTable = brokerResponse.getResultTable(); + DataSchema dataSchema = resultTable.getDataSchema(); + assertEquals(dataSchema, new DataSchema(new String[]{"count", "min", "max", "sum"}, new ColumnDataType[]{ + ColumnDataType.LONG, ColumnDataType.DOUBLE, ColumnDataType.DOUBLE, ColumnDataType.DOUBLE + })); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + Object[] row = rows.get(0); + assertEquals(row.length, 4); + // Count(column) return 0 if all values are nulls. + assertEquals(row[0], 0L); + assertNull(row[1]); + assertNull(row[2]); + assertNull(row[3]); + } + }, + new Query(String.format( + "SELECT AVG(%s) AS avg FROM testTable GROUP BY %s ORDER BY avg LIMIT 20", COLUMN_NAME, COLUMN_NAME)) { + + @Override + public boolean skip(ColumnDataType columnDataType) { + return columnDataType != ColumnDataType.STRING; + } + + @Override + public void verify(ColumnDataType columnDataType, BrokerResponseNative brokerResponse) { + ResultTable resultTable = brokerResponse.getResultTable(); + DataSchema dataSchema = resultTable.getDataSchema(); + assertEquals(dataSchema, new DataSchema(new String[]{"avg"}, new ColumnDataType[]{ColumnDataType.DOUBLE})); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + Object[] row = rows.get(0); + assertEquals(row.length, 1); + assertNull(row[0]); + } + }, + new Query(String.format("SELECT AVG(%s) AS avg, MODE(%s) AS mode, DISTINCTCOUNT(%s) as distinct_count" + + " FROM testTable GROUP BY %s ORDER BY %s LIMIT 200", COLUMN_NAME, COLUMN_NAME, COLUMN_NAME, + COLUMN_NAME, COLUMN_NAME)) { + @Override + public boolean skip(ColumnDataType columnDataType) { + return columnDataType != ColumnDataType.STRING && columnDataType != ColumnDataType.BIG_DECIMAL; + } + + @Override + public void verify(ColumnDataType columnDataType, BrokerResponseNative brokerResponse) { + ResultTable resultTable = brokerResponse.getResultTable(); + DataSchema dataSchema = resultTable.getDataSchema(); + assertEquals(dataSchema, new DataSchema(new String[]{"avg", "mode", "distinct_count"}, + new ColumnDataType[]{ColumnDataType.DOUBLE, ColumnDataType.DOUBLE, ColumnDataType.INT})); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + Object[] row = rows.get(0); + assertEquals(row.length, 3); + assertNull(row[0]); + assertNull(row[1]); + assertEquals(row[2], 0); + } + }, + new Query(String.format("SELECT MAX(%s) AS max, %s FROM testTable GROUP BY %s ORDER BY max LIMIT 501", + COLUMN_NAME, COLUMN_NAME, COLUMN_NAME)) { + + @Override + public boolean skip(ColumnDataType columnDataType) { + return columnDataType != ColumnDataType.STRING; + } + @Override + public void verify(ColumnDataType columnDataType, BrokerResponseNative brokerResponse) { + ResultTable resultTable = brokerResponse.getResultTable(); + DataSchema dataSchema = resultTable.getDataSchema(); + assertEquals(dataSchema, new DataSchema(new String[]{"max", COLUMN_NAME}, + new ColumnDataType[]{ColumnDataType.DOUBLE, columnDataType})); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + assertNull(rows.get(0)[0]); } - assertEquals(row[2], 1); } - } - { - // If updated limit to include all records, I get back results unsorted. - String query = String.format("SELECT MAX(%s) AS max, %s FROM testTable GROUP BY %s ORDER BY max LIMIT 501", - COLUMN_NAME, COLUMN_NAME, COLUMN_NAME); - BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); - ResultTable resultTable = brokerResponse.getResultTable(); - DataSchema dataSchema = resultTable.getDataSchema(); - assertEquals(dataSchema, new DataSchema(new String[]{"max", COLUMN_NAME}, - new ColumnDataType[]{ColumnDataType.DOUBLE, columnDataType})); - List rows = resultTable.getRows(); - assertEquals(rows.size(), 1); - assertNull(rows.get(0)[0]); - } + }; + } + + public void testQueries(ColumnDataType columnDataType, File indexDir, Query query) + throws IOException { + Map queryOptions = new HashMap<>(); + queryOptions.put("enableNullHandling", "true"); + + if (!query.skip(columnDataType)) { + return; } + String queryStr = query.getQuery(); + BrokerResponseNative brokerResponse = getBrokerResponse(queryStr, queryOptions); + + query.verify(columnDataType, brokerResponse); + DataTableBuilderFactory.setDataTableVersion(DataTableBuilderFactory.DEFAULT_VERSION); _indexSegment.destroy(); FileUtils.deleteDirectory(indexDir); diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/ArrayAggQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/ArrayAggQueriesTest.java new file mode 100644 index 000000000000..5dc8bc0a113f --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/queries/ArrayAggQueriesTest.java @@ -0,0 +1,209 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.queries; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; +import org.apache.commons.io.FileUtils; +import org.apache.pinot.common.response.broker.ResultTable; +import org.apache.pinot.common.utils.HashUtil; +import org.apache.pinot.core.operator.blocks.results.AggregationResultsBlock; +import org.apache.pinot.core.operator.query.AggregationOperator; +import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; +import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; +import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; +import org.apache.pinot.segment.spi.ImmutableSegment; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.spi.data.readers.GenericRow; +import org.apache.pinot.spi.utils.ReadMode; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + + +/** + * Queries test for ArrayAgg queries. + */ +@SuppressWarnings("unchecked") +public class ArrayAggQueriesTest extends BaseQueriesTest { + private static final File INDEX_DIR = new File(FileUtils.getTempDirectory(), "ArrayAggQueriesTest"); + private static final String RAW_TABLE_NAME = "testTable"; + private static final String SEGMENT_NAME = "testSegment"; + private static final Random RANDOM = new Random(); + + private static final int NUM_RECORDS = 2000; + private static final int MAX_VALUE = 1000; + + private static final String INT_COLUMN = "intColumn"; + private static final String LONG_COLUMN = "longColumn"; + private static final String FLOAT_COLUMN = "floatColumn"; + private static final String DOUBLE_COLUMN = "doubleColumn"; + private static final String STRING_COLUMN = "stringColumn"; + private static final Schema SCHEMA = new Schema.SchemaBuilder().addSingleValueDimension(INT_COLUMN, DataType.INT) + .addSingleValueDimension(LONG_COLUMN, DataType.LONG).addSingleValueDimension(FLOAT_COLUMN, DataType.FLOAT) + .addSingleValueDimension(DOUBLE_COLUMN, DataType.DOUBLE).addSingleValueDimension(STRING_COLUMN, DataType.STRING) + .build(); + private static final TableConfig TABLE_CONFIG = + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + + private Set _values; + private int[] _expectedCardinalityResults; + private IndexSegment _indexSegment; + private List _indexSegments; + + @Override + protected String getFilter() { + return ""; + } + + @Override + protected IndexSegment getIndexSegment() { + return _indexSegment; + } + + @Override + protected List getIndexSegments() { + return _indexSegments; + } + + @BeforeClass + public void setUp() + throws Exception { + FileUtils.deleteDirectory(INDEX_DIR); + + List records = new ArrayList<>(NUM_RECORDS); + int hashMapCapacity = HashUtil.getHashMapCapacity(MAX_VALUE); + _values = new HashSet<>(hashMapCapacity); + Set longResultSet = new HashSet<>(hashMapCapacity); + Set floatResultSet = new HashSet<>(hashMapCapacity); + Set doubleResultSet = new HashSet<>(hashMapCapacity); + Set stringResultSet = new HashSet<>(hashMapCapacity); + for (int i = 0; i < NUM_RECORDS; i++) { + int value = RANDOM.nextInt(MAX_VALUE); + GenericRow record = new GenericRow(); + record.putValue(INT_COLUMN, value); + _values.add(Integer.hashCode(value)); + record.putValue(LONG_COLUMN, (long) value); + longResultSet.add(Long.hashCode(value)); + record.putValue(FLOAT_COLUMN, (float) value); + floatResultSet.add(Float.hashCode(value)); + record.putValue(DOUBLE_COLUMN, (double) value); + doubleResultSet.add(Double.hashCode(value)); + String stringValue = Integer.toString(value); + record.putValue(STRING_COLUMN, stringValue); + stringResultSet.add(stringValue.hashCode()); + records.add(record); + } + _expectedCardinalityResults = new int[]{ + _values.size(), longResultSet.size(), floatResultSet.size(), doubleResultSet.size(), stringResultSet.size() + }; + + SegmentGeneratorConfig segmentGeneratorConfig = new SegmentGeneratorConfig(TABLE_CONFIG, SCHEMA); + segmentGeneratorConfig.setTableName(RAW_TABLE_NAME); + segmentGeneratorConfig.setSegmentName(SEGMENT_NAME); + segmentGeneratorConfig.setOutDir(INDEX_DIR.getPath()); + + SegmentIndexCreationDriverImpl driver = new SegmentIndexCreationDriverImpl(); + driver.init(segmentGeneratorConfig, new GenericRowRecordReader(records)); + driver.build(); + + ImmutableSegment immutableSegment = ImmutableSegmentLoader.load(new File(INDEX_DIR, SEGMENT_NAME), ReadMode.mmap); + _indexSegment = immutableSegment; + _indexSegments = Arrays.asList(immutableSegment, immutableSegment); + } + + @Test + public void testArrayAggNonDistinct() { + String query = + "SELECT ArrayAgg(intColumn, 'INT'), ArrayAgg(longColumn, 'LONG'), ArrayAgg(floatColumn, 'FLOAT'), " + + "ArrayAgg(doubleColumn, 'DOUBLE'), ArrayAgg(stringColumn, 'STRING')" + + " FROM testTable"; + + // Inner segment + AggregationOperator aggregationOperator = getOperator(query); + AggregationResultsBlock resultsBlock = aggregationOperator.nextBlock(); + QueriesTestUtils.testInnerSegmentExecutionStatistics(aggregationOperator.getExecutionStatistics(), NUM_RECORDS, 0, + 5 * NUM_RECORDS, NUM_RECORDS); + List aggregationResult = resultsBlock.getResults(); + assertNotNull(aggregationResult); + for (int i = 0; i < 5; i++) { + assertEquals(((List) aggregationResult.get(i)).size(), NUM_RECORDS); + } + + // Inter segments + ResultTable resultTable = getBrokerResponse(query).getResultTable(); + assertEquals(resultTable.getRows().get(0).length, 5); + assertEquals(((int[]) resultTable.getRows().get(0)[0]).length, 4 * NUM_RECORDS); + assertEquals(((long[]) resultTable.getRows().get(0)[1]).length, 4 * NUM_RECORDS); + assertEquals(((float[]) resultTable.getRows().get(0)[2]).length, 4 * NUM_RECORDS); + assertEquals(((double[]) resultTable.getRows().get(0)[3]).length, 4 * NUM_RECORDS); + assertEquals(((String[]) resultTable.getRows().get(0)[4]).length, 4 * NUM_RECORDS); + } + + @Test + public void testArrayAggDistinct() { + String query = + "SELECT ArrayAgg(intColumn, 'INT', true), ArrayAgg(longColumn, 'LONG', true), " + + "ArrayAgg(floatColumn, 'FLOAT', true), ArrayAgg(doubleColumn, 'DOUBLE', true), " + + "ArrayAgg(stringColumn, 'STRING', true)" + + " FROM testTable"; + + // Inner segment + AggregationOperator aggregationOperator = getOperator(query); + AggregationResultsBlock resultsBlock = aggregationOperator.nextBlock(); + QueriesTestUtils.testInnerSegmentExecutionStatistics(aggregationOperator.getExecutionStatistics(), NUM_RECORDS, 0, + 5 * NUM_RECORDS, NUM_RECORDS); + List aggregationResult = resultsBlock.getResults(); + assertNotNull(aggregationResult); + for (int i = 0; i < 5; i++) { + assertEquals(((Set) aggregationResult.get(i)).size(), _expectedCardinalityResults[i]); + } + + // Inter segments + ResultTable resultTable = getBrokerResponse(query).getResultTable(); + assertEquals(resultTable.getRows().get(0).length, 5); + assertEquals(((int[]) resultTable.getRows().get(0)[0]).length, _expectedCardinalityResults[0]); + assertEquals(((long[]) resultTable.getRows().get(0)[1]).length, _expectedCardinalityResults[1]); + assertEquals(((float[]) resultTable.getRows().get(0)[2]).length, _expectedCardinalityResults[2]); + assertEquals(((double[]) resultTable.getRows().get(0)[3]).length, _expectedCardinalityResults[3]); + assertEquals(((String[]) resultTable.getRows().get(0)[4]).length, _expectedCardinalityResults[4]); + } + + @AfterClass + public void tearDown() + throws IOException { + _indexSegment.destroy(); + FileUtils.deleteDirectory(INDEX_DIR); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/BaseFunnelCountQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/BaseFunnelCountQueriesTest.java new file mode 100644 index 000000000000..c1a966a9b53f --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/queries/BaseFunnelCountQueriesTest.java @@ -0,0 +1,255 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.queries; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Random; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import org.apache.commons.io.FileUtils; +import org.apache.pinot.common.utils.HashUtil; +import org.apache.pinot.core.common.Operator; +import org.apache.pinot.core.operator.blocks.results.AggregationResultsBlock; +import org.apache.pinot.core.operator.blocks.results.GroupByResultsBlock; +import org.apache.pinot.core.operator.query.AggregationOperator; +import org.apache.pinot.core.operator.query.GroupByOperator; +import org.apache.pinot.core.query.aggregation.groupby.AggregationGroupByResult; +import org.apache.pinot.core.query.aggregation.groupby.GroupKeyGenerator; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.spi.data.readers.GenericRow; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + + +/** + * Base queries test for FUNNEL_COUNT queries. + * Each strategy gets its own test. + */ +@SuppressWarnings("rawtypes") +abstract public class BaseFunnelCountQueriesTest extends BaseQueriesTest { + protected static final File INDEX_DIR = + new File(FileUtils.getTempDirectory(), "FunnelCountQueriesTest"); + protected static final String RAW_TABLE_NAME = "testTable"; + protected static final String SEGMENT_NAME = "testSegment"; + protected static final Random RANDOM = new Random(); + + protected static final int NUM_RECORDS = 2000; + protected static final int MAX_VALUE = 1000; + protected static final int NUM_GROUPS = 100; + protected static final int FILTER_LIMIT = 50; + protected static final String ID_COLUMN = "idColumn"; + protected static final String STEP_COLUMN = "stepColumn"; + protected static final String[] STEPS = {"A", "B"}; + protected static final Schema SCHEMA = new Schema.SchemaBuilder() + .addSingleValueDimension(ID_COLUMN, DataType.INT) + .addSingleValueDimension(STEP_COLUMN, DataType.STRING) + .build(); + protected static final TableConfigBuilder TABLE_CONFIG_BUILDER = + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME); + + private Set[] _values = new Set[2]; + private List _all = new ArrayList<>(); + private IndexSegment _indexSegment; + private List _indexSegments; + + protected abstract int getExpectedNumEntriesScannedInFilter(); + protected abstract int getExpectedInterSegmentMultiplier(); + protected abstract TableConfig getTableConfig(); + protected abstract IndexSegment buildSegment(List records) throws Exception; + protected abstract void assertIntermediateResult(Object intermediateResult, long[] expectedCounts); + + protected abstract String getSettings(); + + @Override + protected String getFilter() { + return String.format(" WHERE idColumn >= %s", FILTER_LIMIT); + } + + @Override + protected IndexSegment getIndexSegment() { + return _indexSegment; + } + + @Override + protected List getIndexSegments() { + return _indexSegments; + } + + @BeforeClass + public void setUp() + throws Exception { + FileUtils.deleteDirectory(INDEX_DIR); + + List records = genereateRows(); + _indexSegment = buildSegment(records); + _indexSegments = Arrays.asList(_indexSegment, _indexSegment); + } + + private List genereateRows() { + List records = new ArrayList<>(NUM_RECORDS); + int hashMapCapacity = HashUtil.getHashMapCapacity(MAX_VALUE); + _values[0] = new HashSet<>(hashMapCapacity); + _values[1] = new HashSet<>(hashMapCapacity); + for (int i = 0; i < NUM_RECORDS; i++) { + int value = RANDOM.nextInt(MAX_VALUE); + GenericRow record = new GenericRow(); + record.putValue(ID_COLUMN, value); + record.putValue(STEP_COLUMN, STEPS[i % 2]); + records.add(record); + _all.add(Integer.hashCode(value)); + _values[i % 2].add(Integer.hashCode(value)); + } + return records; + } + + private String getFunnelCountSql() { + return "FUNNEL_COUNT( " + + "STEPS(stepColumn = 'A', stepColumn = 'B'), " + + "CORRELATE_BY(idColumn), " + + getSettings() + + ") "; + } + @Test + public void testAggregationOnly() { + String query = String.format("SELECT " + getFunnelCountSql() + "FROM testTable"); + + // Inner segment + Predicate filter = id -> id >= FILTER_LIMIT; + long expectedFilteredNumDocs = _all.stream().filter(filter).count(); + Set filteredA = _values[0].stream().filter(filter).collect(Collectors.toSet()); + Set filteredB = _values[1].stream().filter(filter).collect(Collectors.toSet()); + Set intersection = new HashSet<>(filteredA); + intersection.retainAll(filteredB); + long[] expectedResult = { filteredA.size(), intersection.size() }; + + Operator operator = getOperatorWithFilter(query); + assertTrue(operator instanceof AggregationOperator); + AggregationResultsBlock resultsBlock = ((AggregationOperator) operator).nextBlock(); + QueriesTestUtils.testInnerSegmentExecutionStatistics(operator.getExecutionStatistics(), + expectedFilteredNumDocs, getExpectedNumEntriesScannedInFilter(), 2 * expectedFilteredNumDocs, NUM_RECORDS); + List aggregationResult = resultsBlock.getResults(); + assertNotNull(aggregationResult); + assertEquals(aggregationResult.size(), 1); + assertIntermediateResult(aggregationResult.get(0), expectedResult); + + // Inter segments + for (int i = 0; i < 2; i++) { + expectedResult[i] = expectedResult[i] * getExpectedInterSegmentMultiplier(); + } + Object[] expectedResults = { expectedResult }; + + QueriesTestUtils.testInterSegmentsResult(getBrokerResponseWithFilter(query), + 4 * expectedFilteredNumDocs, 4 * getExpectedNumEntriesScannedInFilter(), 4 * 2 * expectedFilteredNumDocs, + 4 * NUM_RECORDS, expectedResults); + } + + @Test + public void testAggregationGroupBy() { + String query = String.format("SELECT " + + "MOD(idColumn, %s), " + + getFunnelCountSql() + + "FROM testTable " + + "WHERE idColumn >= %s " + + "GROUP BY 1 ORDER BY 1 LIMIT %s", NUM_GROUPS, FILTER_LIMIT, NUM_GROUPS); + + // Inner segment + Set[] filteredA = new Set[NUM_GROUPS]; + Set[] filteredB = new Set[NUM_GROUPS]; + Set[] intersection = new Set[NUM_GROUPS]; + long[][] expectedResult = new long[NUM_GROUPS][]; + + long expectedFilteredNumDocs = _all.stream().filter(id -> id >= FILTER_LIMIT).count(); + + int expectedNumGroups = 0; + for (int i = 0; i < NUM_GROUPS; i++) { + final int group = i; + Predicate filter = id -> id >= FILTER_LIMIT && id % NUM_GROUPS == group; + filteredA[group] = _values[0].stream().filter(filter).collect(Collectors.toSet()); + filteredB[group] = _values[1].stream().filter(filter).collect(Collectors.toSet()); + intersection[group] = new HashSet<>(filteredA[group]); + intersection[group].retainAll(filteredB[group]); + if (!filteredA[i].isEmpty() || !filteredB[i].isEmpty()) { + expectedNumGroups++; + expectedResult[group] = new long[] { filteredA[group].size(), intersection[group].size() }; + } + } + + // Inner segment + GroupByOperator groupByOperator = getOperator(query); + GroupByResultsBlock resultsBlock = groupByOperator.nextBlock(); + QueriesTestUtils.testInnerSegmentExecutionStatistics(groupByOperator.getExecutionStatistics(), + expectedFilteredNumDocs, getExpectedNumEntriesScannedInFilter(), 2 * expectedFilteredNumDocs, NUM_RECORDS); + + AggregationGroupByResult aggregationGroupByResult = resultsBlock.getAggregationGroupByResult(); + assertNotNull(aggregationGroupByResult); + int numGroups = 0; + Iterator groupKeyIterator = aggregationGroupByResult.getGroupKeyIterator(); + while (groupKeyIterator.hasNext()) { + numGroups++; + GroupKeyGenerator.GroupKey groupKey = groupKeyIterator.next(); + int key = ((Double) groupKey._keys[0]).intValue(); + assertIntermediateResult( + aggregationGroupByResult.getResultForGroupId(0, groupKey._groupId), + expectedResult[key]); + } + assertEquals(numGroups, expectedNumGroups); + + // Inter segments + List expectedRows = new ArrayList<>(); + for (int i = 0; i < NUM_GROUPS; i++) { + if (expectedResult[i] == null) { + continue; + } + for (int step = 0; step < 2; step++) { + expectedResult[i][step] = expectedResult[i][step] * getExpectedInterSegmentMultiplier(); + } + Object[] expectedRow = { Double.valueOf(i), expectedResult[i] }; + expectedRows.add(expectedRow); + } + + // Inter segments (expect 4 * inner segment result) + QueriesTestUtils.testInterSegmentsResult(getBrokerResponse(query), + 4 * expectedFilteredNumDocs, 4 * getExpectedNumEntriesScannedInFilter(), 4 * 2 * expectedFilteredNumDocs, + 4 * NUM_RECORDS, expectedRows); + } + + @AfterClass + public void tearDown() + throws IOException { + _indexSegment.destroy(); + FileUtils.deleteDirectory(INDEX_DIR); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/BaseMultiValueQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/BaseMultiValueQueriesTest.java index 3f12baefb350..671894eaa0d1 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/BaseMultiValueQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/BaseMultiValueQueriesTest.java @@ -32,6 +32,8 @@ import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; import org.apache.pinot.segment.spi.creator.SegmentIndexCreationDriver; +import org.apache.pinot.segment.spi.index.StandardIndexes; +import org.apache.pinot.spi.config.table.IndexConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.config.table.ingestion.IngestionConfig; @@ -117,8 +119,8 @@ public void buildSegment() segmentGeneratorConfig.setInputFilePath(filePath); segmentGeneratorConfig.setTableName("testTable"); segmentGeneratorConfig.setOutDir(INDEX_DIR.getAbsolutePath()); - segmentGeneratorConfig.setInvertedIndexCreationColumns(Arrays.asList("column3", "column7", "column8", "column9")); - + segmentGeneratorConfig.setIndexOn(StandardIndexes.inverted(), IndexConfig.ENABLED, + "column3", "column7", "column8", "column9"); // Build the index segment. SegmentIndexCreationDriver driver = new SegmentIndexCreationDriverImpl(); driver.init(segmentGeneratorConfig); diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/BaseMultiValueRawQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/BaseMultiValueRawQueriesTest.java index 4e75b7a2c465..3cf4270cb8b9 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/BaseMultiValueRawQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/BaseMultiValueRawQueriesTest.java @@ -32,6 +32,8 @@ import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; import org.apache.pinot.segment.spi.creator.SegmentIndexCreationDriver; +import org.apache.pinot.segment.spi.index.StandardIndexes; +import org.apache.pinot.spi.config.table.IndexConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.config.table.ingestion.IngestionConfig; @@ -117,7 +119,8 @@ public void buildSegment() segmentGeneratorConfig.setInputFilePath(filePath); segmentGeneratorConfig.setTableName("testTable"); segmentGeneratorConfig.setOutDir(INDEX_DIR.getAbsolutePath()); - segmentGeneratorConfig.setInvertedIndexCreationColumns(Arrays.asList("column3", "column8", "column9")); + segmentGeneratorConfig.setIndexOn(StandardIndexes.inverted(), IndexConfig.ENABLED, + "column3", "column8", "column9"); segmentGeneratorConfig.setRawIndexCreationColumns(Arrays.asList("column5", "column6", "column7")); // Build the index segment. diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/BaseQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/BaseQueriesTest.java index fa5a5068dfae..72af8ab860c6 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/BaseQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/BaseQueriesTest.java @@ -19,7 +19,7 @@ package org.apache.pinot.queries; import java.io.File; -import java.util.Collections; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -29,6 +29,7 @@ import javax.annotation.Nullable; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.datatable.DataTableFactory; +import org.apache.pinot.common.metrics.BrokerMetrics; import org.apache.pinot.common.request.BrokerRequest; import org.apache.pinot.common.request.PinotQuery; import org.apache.pinot.common.response.broker.BrokerResponseNative; @@ -49,6 +50,7 @@ import org.apache.pinot.segment.local.segment.index.loader.SegmentPreProcessor; import org.apache.pinot.segment.spi.ImmutableSegment; import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; import org.apache.pinot.segment.spi.loader.SegmentDirectoryLoaderContext; import org.apache.pinot.segment.spi.loader.SegmentDirectoryLoaderRegistry; import org.apache.pinot.segment.spi.store.SegmentDirectory; @@ -56,12 +58,14 @@ import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.data.Schema; import org.apache.pinot.spi.env.PinotConfiguration; -import org.apache.pinot.spi.utils.CommonConstants; +import org.apache.pinot.spi.utils.CommonConstants.Broker; import org.apache.pinot.spi.utils.CommonConstants.Server; import org.apache.pinot.spi.utils.ReadMode; import org.apache.pinot.sql.parsers.CalciteSqlCompiler; import org.apache.pinot.sql.parsers.CalciteSqlParser; +import static org.mockito.Mockito.mock; + /** * Base class for queries tests. @@ -69,8 +73,8 @@ public abstract class BaseQueriesTest { protected static final PlanMaker PLAN_MAKER = new InstancePlanMakerImplV2(); protected static final QueryOptimizer OPTIMIZER = new QueryOptimizer(); - protected static final ExecutorService EXECUTOR_SERVICE = Executors.newFixedThreadPool(2); + protected static final BrokerMetrics BROKER_METRICS = mock(BrokerMetrics.class); protected abstract String getFilter(); @@ -79,7 +83,7 @@ public abstract class BaseQueriesTest { protected abstract List getIndexSegments(); protected List> getDistinctInstances() { - return Collections.singletonList(getIndexSegments()); + return List.of(getIndexSegments()); } /** @@ -91,7 +95,7 @@ protected T getOperator(String query) { PinotQuery pinotQuery = CalciteSqlParser.compileToPinotQuery(query); PinotQuery serverPinotQuery = GapfillUtils.stripGapfill(pinotQuery); QueryContext queryContext = QueryContextConverterUtils.getQueryContext(serverPinotQuery); - return (T) PLAN_MAKER.makeSegmentPlanNode(getIndexSegment(), queryContext).run(); + return (T) PLAN_MAKER.makeSegmentPlanNode(new SegmentContext(getIndexSegment()), queryContext).run(); } /** @@ -206,7 +210,8 @@ private BrokerResponseNative getBrokerResponse(PinotQuery pinotQuery, PlanMaker // Server side serverQueryContext.setEndTimeMs(System.currentTimeMillis() + Server.DEFAULT_QUERY_EXECUTOR_TIMEOUT_MS); - Plan plan = planMaker.makeInstancePlan(getIndexSegments(), serverQueryContext, EXECUTOR_SERVICE, null); + Plan plan = + planMaker.makeInstancePlan(getSegmentContexts(getIndexSegments()), serverQueryContext, EXECUTOR_SERVICE, null); InstanceResponseBlock instanceResponse; try { instanceResponse = @@ -216,9 +221,6 @@ private BrokerResponseNative getBrokerResponse(PinotQuery pinotQuery, PlanMaker } // Broker side - // Use 2 Threads for 2 data-tables - BrokerReduceService brokerReduceService = new BrokerReduceService(new PinotConfiguration( - Collections.singletonMap(CommonConstants.Broker.CONFIG_OF_MAX_REDUCE_THREADS_PER_QUERY, 2))); Map dataTableMap = new HashMap<>(); try { // For multi-threaded BrokerReduceService, we cannot reuse the same data-table @@ -233,11 +235,23 @@ private BrokerResponseNative getBrokerResponse(PinotQuery pinotQuery, PlanMaker BrokerRequest brokerRequest = CalciteSqlCompiler.convertToBrokerRequest(pinotQuery); BrokerRequest serverBrokerRequest = serverPinotQuery == pinotQuery ? brokerRequest : CalciteSqlCompiler.convertToBrokerRequest(serverPinotQuery); + return reduceOnDataTable(brokerRequest, serverBrokerRequest, dataTableMap); + } + + private static List getSegmentContexts(List indexSegments) { + List segmentContexts = new ArrayList<>(indexSegments.size()); + indexSegments.forEach(s -> segmentContexts.add(new SegmentContext(s))); + return segmentContexts; + } + + protected BrokerResponseNative reduceOnDataTable(BrokerRequest brokerRequest, BrokerRequest serverBrokerRequest, + Map dataTableMap) { + BrokerReduceService brokerReduceService = + new BrokerReduceService(new PinotConfiguration(Map.of(Broker.CONFIG_OF_MAX_REDUCE_THREADS_PER_QUERY, 2))); BrokerResponseNative brokerResponse = brokerReduceService.reduceOnDataTable(brokerRequest, serverBrokerRequest, dataTableMap, - CommonConstants.Broker.DEFAULT_BROKER_TIMEOUT_MS, null); + Broker.DEFAULT_BROKER_TIMEOUT_MS, BROKER_METRICS); brokerReduceService.shutDown(); - return brokerResponse; } @@ -297,8 +311,10 @@ private BrokerResponseNative getBrokerResponseDistinctInstances(PinotQuery pinot List> instances = getDistinctInstances(); // Server side serverQueryContext.setEndTimeMs(System.currentTimeMillis() + Server.DEFAULT_QUERY_EXECUTOR_TIMEOUT_MS); - Plan plan1 = planMaker.makeInstancePlan(instances.get(0), serverQueryContext, EXECUTOR_SERVICE, null); - Plan plan2 = planMaker.makeInstancePlan(instances.get(1), serverQueryContext, EXECUTOR_SERVICE, null); + Plan plan1 = + planMaker.makeInstancePlan(getSegmentContexts(instances.get(0)), serverQueryContext, EXECUTOR_SERVICE, null); + Plan plan2 = + planMaker.makeInstancePlan(getSegmentContexts(instances.get(1)), serverQueryContext, EXECUTOR_SERVICE, null); InstanceResponseBlock instanceResponse1; try { @@ -316,9 +332,6 @@ private BrokerResponseNative getBrokerResponseDistinctInstances(PinotQuery pinot } // Broker side - // Use 2 Threads for 2 data-tables - BrokerReduceService brokerReduceService = new BrokerReduceService(new PinotConfiguration( - Collections.singletonMap(CommonConstants.Broker.CONFIG_OF_MAX_REDUCE_THREADS_PER_QUERY, 2))); Map dataTableMap = new HashMap<>(); try { // For multi-threaded BrokerReduceService, we cannot reuse the same data-table @@ -334,10 +347,6 @@ private BrokerResponseNative getBrokerResponseDistinctInstances(PinotQuery pinot BrokerRequest brokerRequest = CalciteSqlCompiler.convertToBrokerRequest(pinotQuery); BrokerRequest serverBrokerRequest = serverPinotQuery == pinotQuery ? brokerRequest : CalciteSqlCompiler.convertToBrokerRequest(serverPinotQuery); - BrokerResponseNative brokerResponse = - brokerReduceService.reduceOnDataTable(brokerRequest, serverBrokerRequest, dataTableMap, - CommonConstants.Broker.DEFAULT_BROKER_TIMEOUT_MS, null); - brokerReduceService.shutDown(); - return brokerResponse; + return reduceOnDataTable(brokerRequest, serverBrokerRequest, dataTableMap); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/BaseSingleValueQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/BaseSingleValueQueriesTest.java index 24983bfbce80..990f0f5b4dd2 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/BaseSingleValueQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/BaseSingleValueQueriesTest.java @@ -32,6 +32,8 @@ import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; import org.apache.pinot.segment.spi.creator.SegmentIndexCreationDriver; +import org.apache.pinot.segment.spi.index.StandardIndexes; +import org.apache.pinot.spi.config.table.IndexConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.config.table.ingestion.IngestionConfig; @@ -119,7 +121,7 @@ public void buildSegment() segmentGeneratorConfig.setInputFilePath(filePath); segmentGeneratorConfig.setTableName("testTable"); segmentGeneratorConfig.setOutDir(INDEX_DIR.getAbsolutePath()); - segmentGeneratorConfig.setInvertedIndexCreationColumns( + segmentGeneratorConfig.setIndexOn(StandardIndexes.inverted(), IndexConfig.ENABLED, Arrays.asList("column6", "column7", "column11", "column17", "column18")); // Build the index segment. diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/BigDecimalQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/BigDecimalQueriesTest.java index d44a266b02a5..3d50c89484b5 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/BigDecimalQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/BigDecimalQueriesTest.java @@ -28,7 +28,6 @@ import java.util.Map; import java.util.Random; import org.apache.commons.io.FileUtils; -import org.apache.pinot.common.datatable.DataTableFactory; import org.apache.pinot.common.response.broker.BrokerResponseNative; import org.apache.pinot.common.response.broker.ResultTable; import org.apache.pinot.common.utils.DataSchema; @@ -139,9 +138,7 @@ private void setUp(TableConfig tableConfig) @Test public void testQueriesWithDictColumn() throws Exception { - TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE) - .setTableName(RAW_TABLE_NAME) - .build(); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); setUp(tableConfig); testQueries(); } @@ -151,16 +148,13 @@ public void testQueriesWithNoDictColumn() throws Exception { List noDictionaryColumns = new ArrayList(); noDictionaryColumns.add(BIG_DECIMAL_COLUMN); - TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE) - .setTableName(RAW_TABLE_NAME) - .setNoDictionaryColumns(noDictionaryColumns) - .build(); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) + .setNoDictionaryColumns(noDictionaryColumns).build(); setUp(tableConfig); testQueries(); } public void testQueries() { - DataTableBuilderFactory.setDataTableVersion(DataTableFactory.VERSION_4); Map queryOptions = new HashMap<>(); queryOptions.put("enableNullHandling", "true"); { @@ -176,12 +170,13 @@ public void testQueries() { Object[] row = rows.get(i); assertEquals(row.length, 1); if (row[0] != null) { - assertEquals(row[0], BASE_BIG_DECIMAL.add(BigDecimal.valueOf(i))); + assertEquals(row[0], BASE_BIG_DECIMAL.add(BigDecimal.valueOf(i)).toPlainString()); } } } { - String query = String.format("SELECT * FROM testTable ORDER BY %s DESC LIMIT 4000", BIG_DECIMAL_COLUMN); + String query = + String.format("SELECT * FROM testTable ORDER BY %s DESC NULLS LAST LIMIT 4000", BIG_DECIMAL_COLUMN); // getBrokerResponseForSqlQuery(query) runs SQL query on multiple index segments. The result should be equivalent // to querying 4 identical index segments. BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); @@ -191,9 +186,8 @@ public void testQueries() { new DataSchema(new String[]{BIG_DECIMAL_COLUMN}, new ColumnDataType[]{ColumnDataType.BIG_DECIMAL})); List rows = resultTable.getRows(); assertEquals(rows.size(), 4000); - // Note 1: we inserted 250 nulls in _records, and since we query 4 identical index segments, the number of null - // values is: 250 * 4 = 1000. - // Note 2: The default null ordering is 'NULLS LAST', regardless of the ordering direction. + // We inserted 250 nulls in _records, and since we query 4 identical index segments, the number of null values is: + // 250 * 4 = 1000. int k = 0; for (int i = 0; i < 4000; i += 4) { // Null values are inserted at indices where: index % 4 equals 3. Skip null values. @@ -206,14 +200,14 @@ public void testQueries() { if (k >= NUM_RECORDS) { assertNull(values[0]); } else { - assertEquals(values[0], BASE_BIG_DECIMAL.add(BigDecimal.valueOf(NUM_RECORDS - 1 - k))); + assertEquals(values[0], BASE_BIG_DECIMAL.add(BigDecimal.valueOf(NUM_RECORDS - 1 - k)).toPlainString()); } } k++; } } { - String query = String.format("SELECT DISTINCT %s FROM testTable ORDER BY %s", BIG_DECIMAL_COLUMN, + String query = String.format("SELECT DISTINCT %s FROM testTable ORDER BY %s LIMIT 4000", BIG_DECIMAL_COLUMN, BIG_DECIMAL_COLUMN); BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); ResultTable resultTable = brokerResponse.getResultTable(); @@ -221,7 +215,6 @@ public void testQueries() { assertEquals(dataSchema, new DataSchema(new String[]{BIG_DECIMAL_COLUMN}, new ColumnDataType[]{ColumnDataType.BIG_DECIMAL})); List rows = resultTable.getRows(); - assertEquals(rows.size(), 10); int i = 0; for (int index = 0; index < rows.size() - 1; index++) { Object[] row = rows.get(index); @@ -231,7 +224,7 @@ public void testQueries() { if (i % 4 == 3) { i++; } - assertEquals(row[0], BASE_BIG_DECIMAL.add(BigDecimal.valueOf(i))); + assertEquals(row[0], BASE_BIG_DECIMAL.add(BigDecimal.valueOf(i)).toPlainString()); i++; } // The default null ordering is 'NULLS LAST'. Therefore, null will appear as the last record. @@ -239,8 +232,8 @@ public void testQueries() { } { int limit = 40; - String query = String.format("SELECT DISTINCT %s FROM testTable ORDER BY %s LIMIT %d", - BIG_DECIMAL_COLUMN, BIG_DECIMAL_COLUMN, limit); + String query = String.format("SELECT DISTINCT %s FROM testTable ORDER BY %s LIMIT %d", BIG_DECIMAL_COLUMN, + BIG_DECIMAL_COLUMN, limit); BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); ResultTable resultTable = brokerResponse.getResultTable(); DataSchema dataSchema = resultTable.getDataSchema(); @@ -258,12 +251,10 @@ public void testQueries() { if (i % 4 == 3) { i++; } - assertEquals(row[0], BASE_BIG_DECIMAL.add(BigDecimal.valueOf(i))); + assertEquals(row[0], BASE_BIG_DECIMAL.add(BigDecimal.valueOf(i)).toPlainString()); i++; index++; } - // The default null ordering is 'NULLS LAST'. Therefore, null will appear as the last record. - assertNull(rows.get(rows.size() - 1)[0]); } { // This test case was added to validate path-code for distinct w/o order by. See: @@ -290,13 +281,14 @@ public void testQueries() { assertEquals((long) rows.get(0)[0], 3 * NUM_RECORDS); } { - String query = String.format("SELECT %s FROM testTable GROUP BY %s ORDER BY %s DESC", - BIG_DECIMAL_COLUMN, BIG_DECIMAL_COLUMN, BIG_DECIMAL_COLUMN); + String query = + String.format("SELECT %s FROM testTable GROUP BY %s ORDER BY %s DESC", BIG_DECIMAL_COLUMN, BIG_DECIMAL_COLUMN, + BIG_DECIMAL_COLUMN); BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); ResultTable resultTable = brokerResponse.getResultTable(); DataSchema dataSchema = resultTable.getDataSchema(); - assertEquals(dataSchema, new DataSchema(new String[]{BIG_DECIMAL_COLUMN}, - new ColumnDataType[]{ColumnDataType.BIG_DECIMAL})); + assertEquals(dataSchema, + new DataSchema(new String[]{BIG_DECIMAL_COLUMN}, new ColumnDataType[]{ColumnDataType.BIG_DECIMAL})); List rows = resultTable.getRows(); assertEquals(rows.size(), 10); // The default null ordering is 'NULLS LAST'. Therefore, null will appear as the last record. @@ -309,14 +301,14 @@ public void testQueries() { } Object[] row = rows.get(index); assertEquals(row.length, 1); - assertEquals(row[0], BASE_BIG_DECIMAL.add(BigDecimal.valueOf(NUM_RECORDS - i - 1))); + assertEquals(row[0], BASE_BIG_DECIMAL.add(BigDecimal.valueOf(NUM_RECORDS - i - 1)).toPlainString()); index++; i++; } } { String query = String.format( - "SELECT COUNT(*) AS count, %s FROM testTable GROUP BY %s ORDER BY %s DESC LIMIT 1000", + "SELECT COUNT(*) AS count, %s FROM testTable GROUP BY %s ORDER BY %s DESC NULLS LAST LIMIT 1000", BIG_DECIMAL_COLUMN, BIG_DECIMAL_COLUMN, BIG_DECIMAL_COLUMN); BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); ResultTable resultTable = brokerResponse.getResultTable(); @@ -351,8 +343,9 @@ public void testQueries() { { // Note: defining decimal literals within quotes preserves precision. BigDecimal lowerLimit = BASE_BIG_DECIMAL.add(BigDecimal.valueOf(69)); - String query = String.format("SELECT %s FROM testTable WHERE %s > '%s' LIMIT 30", - BIG_DECIMAL_COLUMN, BIG_DECIMAL_COLUMN, lowerLimit); + String query = + String.format("SELECT %s FROM testTable WHERE %s > '%s' LIMIT 30", BIG_DECIMAL_COLUMN, BIG_DECIMAL_COLUMN, + lowerLimit); BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); ResultTable resultTable = brokerResponse.getResultTable(); DataSchema dataSchema = resultTable.getDataSchema(); @@ -368,14 +361,14 @@ public void testQueries() { // Null values are inserted at: index % 4 == 3. i++; } - assertEquals(row[0], BASE_BIG_DECIMAL.add(BigDecimal.valueOf(69 + i + 1))); + assertEquals(row[0], BASE_BIG_DECIMAL.add(BigDecimal.valueOf(69 + i + 1)).toPlainString()); i++; } } { // Note: defining decimal literals within quotes preserves precision. - String query = String.format("SELECT %s FROM testTable WHERE %s = '%s'", - BIG_DECIMAL_COLUMN, BIG_DECIMAL_COLUMN, BASE_BIG_DECIMAL.add(BigDecimal.valueOf(69))); + String query = String.format("SELECT %s FROM testTable WHERE %s = '%s'", BIG_DECIMAL_COLUMN, BIG_DECIMAL_COLUMN, + BASE_BIG_DECIMAL.add(BigDecimal.valueOf(69))); BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); ResultTable resultTable = brokerResponse.getResultTable(); DataSchema dataSchema = resultTable.getDataSchema(); @@ -386,31 +379,17 @@ public void testQueries() { for (int i = 0; i < 4; i++) { Object[] row = rows.get(i); assertEquals(row.length, 1); - assertEquals(row[0], BASE_BIG_DECIMAL.add(BigDecimal.valueOf(69))); + assertEquals(row[0], BASE_BIG_DECIMAL.add(BigDecimal.valueOf(69)).toPlainString()); } } { - // This returns currently 25 rows instead of a single row! -// int limit = 25; -// String query = String.format( -// "SELECT SUMPRECISION(%s) AS sum FROM (SELECT %s FROM testTable ORDER BY %s LIMIT %d)", -// BIG_DECIMAL_COLUMN, BIG_DECIMAL_COLUMN, BIG_DECIMAL_COLUMN, limit); -// BrokerResponseNative brokerResponse = getBrokerResponse(query); -// ResultTable resultTable = brokerResponse.getResultTable(); -// DataSchema dataSchema = resultTable.getDataSchema(); -// assertEquals(dataSchema, new DataSchema(new String[]{"sum"}, new ColumnDataType[]{ColumnDataType.BIG_DECIMAL})); -// List rows = resultTable.getRows(); -// assertEquals(rows.size(), 1); - } - { - String query = String.format( - "SELECT MAX(%s) AS maxValue FROM testTable GROUP BY %s HAVING maxValue < %s ORDER BY maxValue", - BIG_DECIMAL_COLUMN, BIG_DECIMAL_COLUMN, BASE_BIG_DECIMAL.add(BigDecimal.valueOf(5))); + String query = + String.format("SELECT MAX(%s) AS maxValue FROM testTable GROUP BY %s HAVING maxValue < %s ORDER BY maxValue", + BIG_DECIMAL_COLUMN, BIG_DECIMAL_COLUMN, BASE_BIG_DECIMAL.add(BigDecimal.valueOf(5))); BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); ResultTable resultTable = brokerResponse.getResultTable(); DataSchema dataSchema = resultTable.getDataSchema(); - assertEquals(dataSchema, - new DataSchema(new String[]{"maxValue"}, new ColumnDataType[]{ColumnDataType.DOUBLE})); + assertEquals(dataSchema, new DataSchema(new String[]{"maxValue"}, new ColumnDataType[]{ColumnDataType.DOUBLE})); List rows = resultTable.getRows(); // The default null ordering is: 'NULLS LAST'. This is why the number of returned value is 4 and not 5. assertEquals(rows.size(), 4); @@ -428,14 +407,13 @@ public void testQueries() { } { int lowerLimit = 991; - String query = String.format( - "SELECT MAX(%s) AS maxValue FROM testTable GROUP BY %s HAVING maxValue > %s ORDER BY maxValue", - BIG_DECIMAL_COLUMN, BIG_DECIMAL_COLUMN, BASE_BIG_DECIMAL.add(BigDecimal.valueOf(lowerLimit))); + String query = + String.format("SELECT MAX(%s) AS maxValue FROM testTable GROUP BY %s HAVING maxValue > %s ORDER BY maxValue", + BIG_DECIMAL_COLUMN, BIG_DECIMAL_COLUMN, BASE_BIG_DECIMAL.add(BigDecimal.valueOf(lowerLimit))); BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); ResultTable resultTable = brokerResponse.getResultTable(); DataSchema dataSchema = resultTable.getDataSchema(); - assertEquals(dataSchema, - new DataSchema(new String[]{"maxValue"}, new ColumnDataType[]{ColumnDataType.DOUBLE})); + assertEquals(dataSchema, new DataSchema(new String[]{"maxValue"}, new ColumnDataType[]{ColumnDataType.DOUBLE})); List rows = resultTable.getRows(); assertEquals(rows.size(), 6); int i = lowerLimit; diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/BooleanAggQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/BooleanAggQueriesTest.java index 3e87d7446b83..e8913e0a1b56 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/BooleanAggQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/BooleanAggQueriesTest.java @@ -42,6 +42,7 @@ import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; import org.apache.pinot.segment.spi.ImmutableSegment; import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.SegmentContext; import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; @@ -165,7 +166,7 @@ protected T getOperator(String query, boolean enableNullHan PinotQuery serverPinotQuery = GapfillUtils.stripGapfill(pinotQuery); QueryContext queryContext = QueryContextConverterUtils.getQueryContext(serverPinotQuery); - return (T) PLAN_MAKER.makeSegmentPlanNode(getIndexSegment(), queryContext).run(); + return (T) PLAN_MAKER.makeSegmentPlanNode(new SegmentContext(getIndexSegment()), queryContext).run(); } @Test(dataProvider = "nullHandling") diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/BooleanNullEnabledQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/BooleanNullEnabledQueriesTest.java index 63443449d95a..481634f11680 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/BooleanNullEnabledQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/BooleanNullEnabledQueriesTest.java @@ -27,7 +27,6 @@ import java.util.List; import java.util.Map; import org.apache.commons.io.FileUtils; -import org.apache.pinot.common.datatable.DataTableFactory; import org.apache.pinot.common.response.broker.BrokerResponseNative; import org.apache.pinot.common.response.broker.ResultTable; import org.apache.pinot.common.utils.DataSchema; @@ -181,7 +180,6 @@ public void testQueriesWithNoDictColumn() } public void testQueries() { - DataTableBuilderFactory.setDataTableVersion(DataTableFactory.VERSION_4); Map queryOptions = new HashMap<>(); queryOptions.put("enableNullHandling", "true"); HashSet trueIndices = new HashSet(Arrays.asList(1, 3, 5)); @@ -361,7 +359,7 @@ public void testQueries() { } } { - String query = "SELECT * FROM testTable ORDER BY booleanColumn DESC LIMIT 4000"; + String query = "SELECT * FROM testTable ORDER BY booleanColumn DESC NULLS LAST LIMIT 4000"; BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); ResultTable resultTable = brokerResponse.getResultTable(); DataSchema dataSchema = resultTable.getDataSchema(); @@ -382,7 +380,6 @@ public void testQueries() { for (int i = _trueValuesCount * 4 + _falseValuesCount * 4; i < 4000; i++) { Object[] row = rows.get(i); assertEquals(row.length, 1); - // Note 2: The default null ordering is 'NULLS LAST', regardless of the ordering direction. assertNull(row[0]); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/DistinctCountThetaSketchQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/DistinctCountThetaSketchQueriesTest.java index 35fc8127bcf9..98e9b90900ec 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/DistinctCountThetaSketchQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/DistinctCountThetaSketchQueriesTest.java @@ -37,6 +37,7 @@ import org.apache.pinot.core.operator.query.GroupByOperator; import org.apache.pinot.core.query.aggregation.groupby.AggregationGroupByResult; import org.apache.pinot.core.query.aggregation.groupby.GroupKeyGenerator; +import org.apache.pinot.segment.local.customobject.ThetaSketchAccumulator; import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; @@ -172,13 +173,13 @@ public void testAggregationOnly() { assertNotNull(aggregationResult); assertEquals(aggregationResult.size(), 11); for (int i = 0; i < 11; i++) { - List sketches = (List) aggregationResult.get(i); - assertEquals(sketches.size(), 1); - Sketch sketch = sketches.get(0); + List accumulators = (List) aggregationResult.get(i); + assertEquals(accumulators.size(), 1); + ThetaSketchAccumulator accumulator = accumulators.get(0); if (i < 5) { - assertEquals(Math.round(sketch.getEstimate()), NUM_RECORDS); + assertEquals(Math.round(accumulator.getResult().getEstimate()), NUM_RECORDS); } else { - assertEquals(Math.round(sketch.getEstimate()), 3 * NUM_RECORDS); + assertEquals(Math.round(accumulator.getResult().getEstimate()), 3 * NUM_RECORDS); } } @@ -220,9 +221,10 @@ public void testAggregationGroupBy() { numGroups++; GroupKeyGenerator.GroupKey groupKey = groupKeyIterator.next(); for (int i = 0; i < 6; i++) { - List sketches = (List) aggregationGroupByResult.getResultForGroupId(i, groupKey._groupId); - assertEquals(sketches.size(), 1); - Sketch sketch = sketches.get(0); + List accumulators = + (List) aggregationGroupByResult.getResultForGroupId(i, groupKey._groupId); + assertEquals(accumulators.size(), 1); + Sketch sketch = accumulators.get(0).getResult(); if (i < 5) { assertEquals(Math.round(sketch.getEstimate()), 1); } else { @@ -279,13 +281,13 @@ public void testPostAggregation() { List aggregationResult = resultsBlock.getResults(); assertNotNull(aggregationResult); assertEquals(aggregationResult.size(), 1); - List sketches = (List) aggregationResult.get(0); - assertEquals(sketches.size(), 5); - assertTrue(sketches.get(0).isEmpty()); - assertEquals(Math.round(sketches.get(1).getEstimate()), 300); - assertEquals(Math.round(sketches.get(2).getEstimate()), 450); - assertEquals(Math.round(sketches.get(3).getEstimate()), 175); - assertEquals(Math.round(sketches.get(4).getEstimate()), 100); + List accumulators = (List) aggregationResult.get(0); + assertEquals(accumulators.size(), 5); + assertTrue(accumulators.get(0).getResult().isEmpty()); + assertEquals(Math.round(accumulators.get(1).getResult().getEstimate()), 300); + assertEquals(Math.round(accumulators.get(2).getResult().getEstimate()), 450); + assertEquals(Math.round(accumulators.get(3).getResult().getEstimate()), 175); + assertEquals(Math.round(accumulators.get(4).getResult().getEstimate()), 100); // Inter segments Object[] expectedResults = new Object[]{225L}; @@ -299,7 +301,7 @@ public void testDistinctCountRawThetaSketch() { String query = "SELECT DISTINCT_COUNT_RAW_THETA_SKETCH(intSVColumn) FROM testTable"; BrokerResponseNative brokerResponse = getBrokerResponse(query); String serializedSketch = (String) brokerResponse.getResultTable().getRows().get(0)[0]; - Sketch sketch = ObjectSerDeUtils.DATA_SKETCH_SER_DE.deserialize(Base64.getDecoder().decode(serializedSketch)); + Sketch sketch = ObjectSerDeUtils.DATA_SKETCH_THETA_SER_DE.deserialize(Base64.getDecoder().decode(serializedSketch)); assertEquals(Math.round(sketch.getEstimate()), NUM_RECORDS); } diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/DistinctQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/DistinctQueriesTest.java index 0d01502b2119..47e8f7792f69 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/DistinctQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/DistinctQueriesTest.java @@ -28,7 +28,7 @@ import java.util.List; import java.util.Set; import org.apache.commons.io.FileUtils; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.pinot.common.response.broker.ResultTable; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; @@ -1251,7 +1251,7 @@ private void testDistinctInterSegmentHelper(String[] queries) { assertEquals(((Long) row[1]).intValue(), intValue); assertEquals(((Float) row[2]).intValue(), intValue); assertEquals(((Double) row[3]).intValue(), intValue); - assertEquals(((BigDecimal) row[4]).intValue(), intValue); + assertEquals(Integer.parseInt((String) row[4]), intValue); assertEquals(Integer.parseInt((String) row[5]), intValue); assertEquals(new String(BytesUtils.toBytes((String) row[6]), UTF_8).trim(), row[5]); actualValues.add(intValue); @@ -1319,7 +1319,7 @@ private void testDistinctInterSegmentHelper(String[] queries) { for (Object[] row : rows) { int intValue = ((Long) row[0]).intValue(); List actualValueList = - Arrays.asList(intValue, ((BigDecimal) row[1]).intValue(), ((Float) row[2]).intValue(), + Arrays.asList(intValue, Integer.parseInt((String) row[1]), ((Float) row[2]).intValue(), Integer.parseInt((String) row[3])); assertEquals((int) actualValueList.get(1), intValue); List expectedMVValues = new ArrayList<>(2); @@ -1433,9 +1433,10 @@ private void testDistinctInterSegmentHelper(String[] queries) { { ResultTable resultTable = getBrokerResponse(queries[7]).getResultTable(); - // Check data schema, where data type should be STRING for all columns + // Check data schema + // NOTE: Segment pruner is not wired up in QueriesTest, and the correct column data types should be returned. DataSchema expectedDataSchema = new DataSchema(new String[]{"floatColumn", "longMVColumn"}, - new ColumnDataType[]{ColumnDataType.STRING, ColumnDataType.STRING}); + new ColumnDataType[]{ColumnDataType.FLOAT, ColumnDataType.LONG}); assertEquals(resultTable.getDataSchema(), expectedDataSchema); // Check values, where no record should be returned @@ -1522,7 +1523,7 @@ private void testDistinctInterSegmentHelper(String[] queries) { for (Object[] row : rows) { int intValue = ((Long) row[0]).intValue(); List actualValueList = - Arrays.asList(intValue, ((BigDecimal) row[1]).intValue(), ((Float) row[2]).intValue(), + Arrays.asList(intValue, Integer.parseInt((String) row[1]), ((Float) row[2]).intValue(), Integer.parseInt((String) row[3])); assertEquals((int) actualValueList.get(1), intValue); List expectedMVValues = new ArrayList<>(2); @@ -1589,9 +1590,10 @@ private void testDistinctInterSegmentHelper(String[] queries) { { ResultTable resultTable = getBrokerResponse(queries[13]).getResultTable(); - // Check data schema, where data type should be STRING for all columns + // Check data schema + // NOTE: Segment pruner is not wired up in QueriesTest, and the correct column data types should be returned. DataSchema expectedDataSchema = new DataSchema(new String[]{"floatColumn", "rawLongMVColumn"}, - new ColumnDataType[]{ColumnDataType.STRING, ColumnDataType.STRING}); + new ColumnDataType[]{ColumnDataType.FLOAT, ColumnDataType.LONG}); assertEquals(resultTable.getDataSchema(), expectedDataSchema); // Check values, where no record should be returned diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/ExplainPlanQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/ExplainPlanQueriesTest.java index b28debf5e39b..23fdd0c54516 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/ExplainPlanQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/ExplainPlanQueriesTest.java @@ -21,18 +21,16 @@ import java.io.File; import java.net.URL; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import org.apache.commons.configuration.PropertiesConfiguration; +import org.apache.commons.configuration2.PropertiesConfiguration; import org.apache.commons.io.FileUtils; import org.apache.helix.HelixManager; -import org.apache.helix.store.zk.ZkHelixPropertyStore; import org.apache.pinot.common.datatable.DataTable; import org.apache.pinot.common.datatable.DataTableFactory; import org.apache.pinot.common.metrics.ServerMetrics; @@ -41,6 +39,7 @@ import org.apache.pinot.common.response.broker.BrokerResponseNative; import org.apache.pinot.common.response.broker.ResultTable; import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.common.utils.DataSchema.ColumnDataType; import org.apache.pinot.core.common.ExplainPlanRows; import org.apache.pinot.core.data.manager.InstanceDataManager; import org.apache.pinot.core.data.manager.offline.TableDataManagerProvider; @@ -51,11 +50,11 @@ import org.apache.pinot.core.query.request.ServerQueryRequest; import org.apache.pinot.core.transport.ServerRoutingInstance; import org.apache.pinot.segment.local.data.manager.TableDataManager; -import org.apache.pinot.segment.local.data.manager.TableDataManagerConfig; import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; import org.apache.pinot.segment.local.segment.index.loader.IndexLoadingConfig; import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; +import org.apache.pinot.segment.local.utils.SegmentLocks; import org.apache.pinot.segment.spi.ImmutableSegment; import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; @@ -63,12 +62,12 @@ import org.apache.pinot.spi.config.table.IndexingConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; -import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.data.FieldSpec.DataType; import org.apache.pinot.spi.data.Schema; import org.apache.pinot.spi.data.readers.GenericRow; +import org.apache.pinot.spi.env.CommonsConfigurationUtils; import org.apache.pinot.spi.env.PinotConfiguration; -import org.apache.pinot.spi.metrics.PinotMetricUtils; -import org.apache.pinot.spi.utils.CommonConstants; +import org.apache.pinot.spi.utils.CommonConstants.Broker; import org.apache.pinot.spi.utils.ReadMode; import org.apache.pinot.spi.utils.builder.TableConfigBuilder; import org.apache.pinot.spi.utils.builder.TableNameBuilder; @@ -81,8 +80,9 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; + public class ExplainPlanQueriesTest extends BaseQueriesTest { - private static final File INDEX_DIR = new File(FileUtils.getTempDirectory(), "ExplainPlanQueriesTest"); + private static final File TEMP_DIR = new File(FileUtils.getTempDirectory(), "ExplainPlanQueriesTest"); private static final String QUERY_EXECUTOR_CONFIG_PATH = "conf/query-executor.properties"; private static final ExecutorService QUERY_RUNNERS = Executors.newFixedThreadPool(20); @@ -94,6 +94,7 @@ public class ExplainPlanQueriesTest extends BaseQueriesTest { private static final String SEGMENT_NAME_4 = "testSegment4"; private static final int NUM_RECORDS = 10; + private final static String COL1_RAW = "rawCol1"; private final static String COL1_NO_INDEX = "noIndexCol1"; private final static String COL2_NO_INDEX = "noIndexCol2"; private final static String COL3_NO_INDEX = "noIndexCol3"; @@ -107,35 +108,45 @@ public class ExplainPlanQueriesTest extends BaseQueriesTest { private final static String COL1_SORTED_INDEX = "sortedIndexCol1"; private final static String COL1_JSON_INDEX = "jsonIndexCol1"; private final static String COL1_TEXT_INDEX = "textIndexCol1"; + private final static String MV_COL1_RAW = "mvRawCol1"; + private final static String MV_COL1_NO_INDEX = "mvNoIndexCol1"; + //@formatter:off private static final Schema SCHEMA = new Schema.SchemaBuilder().setSchemaName(RAW_TABLE_NAME) - .addSingleValueDimension(COL1_NO_INDEX, FieldSpec.DataType.INT) - .addSingleValueDimension(COL2_NO_INDEX, FieldSpec.DataType.INT) - .addSingleValueDimension(COL3_NO_INDEX, FieldSpec.DataType.INT) - .addSingleValueDimension(COL4_NO_INDEX, FieldSpec.DataType.BOOLEAN) - .addSingleValueDimension(COL1_INVERTED_INDEX, FieldSpec.DataType.DOUBLE) - .addSingleValueDimension(COL2_INVERTED_INDEX, FieldSpec.DataType.INT) - .addSingleValueDimension(COL3_INVERTED_INDEX, FieldSpec.DataType.STRING) - .addSingleValueDimension(COL1_RANGE_INDEX, FieldSpec.DataType.DOUBLE) - .addSingleValueDimension(COL2_RANGE_INDEX, FieldSpec.DataType.INT) - .addSingleValueDimension(COL3_RANGE_INDEX, FieldSpec.DataType.INT) - .addSingleValueDimension(COL1_SORTED_INDEX, FieldSpec.DataType.DOUBLE) - .addSingleValueDimension(COL1_JSON_INDEX, FieldSpec.DataType.JSON) - .addSingleValueDimension(COL1_TEXT_INDEX, FieldSpec.DataType.STRING).build(); - - private static final DataSchema DATA_SCHEMA = new DataSchema(new String[]{"Operator", "Operator_Id", "Parent_Id"}, - new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT, - DataSchema.ColumnDataType.INT}); + .addSingleValueDimension(COL1_RAW, DataType.INT) + .addSingleValueDimension(COL1_NO_INDEX, DataType.INT) + .addSingleValueDimension(COL2_NO_INDEX, DataType.INT) + .addSingleValueDimension(COL3_NO_INDEX, DataType.INT) + .addSingleValueDimension(COL4_NO_INDEX, DataType.BOOLEAN) + .addSingleValueDimension(COL1_INVERTED_INDEX, DataType.DOUBLE) + .addSingleValueDimension(COL2_INVERTED_INDEX, DataType.INT) + .addSingleValueDimension(COL3_INVERTED_INDEX, DataType.STRING) + .addSingleValueDimension(COL1_RANGE_INDEX, DataType.DOUBLE) + .addSingleValueDimension(COL2_RANGE_INDEX, DataType.INT) + .addSingleValueDimension(COL3_RANGE_INDEX, DataType.INT) + .addSingleValueDimension(COL1_SORTED_INDEX, DataType.DOUBLE) + .addSingleValueDimension(COL1_JSON_INDEX, DataType.JSON) + .addSingleValueDimension(COL1_TEXT_INDEX, DataType.STRING) + .addMultiValueDimension(MV_COL1_RAW, DataType.INT) + .addMultiValueDimension(MV_COL1_NO_INDEX, DataType.INT) + .build(); + + private static final DataSchema DATA_SCHEMA = new DataSchema( + new String[]{"Operator", "Operator_Id", "Parent_Id"}, + new ColumnDataType[]{ColumnDataType.STRING, ColumnDataType.INT, ColumnDataType.INT} + ); + //@formatter:on private static final TableConfig TABLE_CONFIG = - new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + new TableConfigBuilder(TableType.OFFLINE).setNoDictionaryColumns(List.of(COL1_RAW, MV_COL1_RAW)) + .setTableName(RAW_TABLE_NAME).build(); private IndexSegment _indexSegment; private List _indexSegments; private List _segmentNames; - private ServerMetrics _serverMetrics; private QueryExecutor _queryExecutor; + private QueryExecutor _queryExecutorWithPrefetchEnabled; private BrokerReduceService _brokerReduceService; @Override @@ -153,12 +164,14 @@ protected List getIndexSegments() { return _indexSegments; } - GenericRow createMockRecord(int noIndexCol1, int noIndexCol2, int noIndexCol3, - boolean noIndexCol4, double invertedIndexCol1, int invertedIndexCol2, String intervedIndexCol3, - double rangeIndexCol1, int rangeIndexCol2, int rangeIndexCol3, double sortedIndexCol1, String jsonIndexCol1, - String textIndexCol1) { + GenericRow createMockRecord(int noIndexCol1, int noIndexCol2, int noIndexCol3, boolean noIndexCol4, + double invertedIndexCol1, int invertedIndexCol2, String invertedIndexCol3, double rangeIndexCol1, + int rangeIndexCol2, int rangeIndexCol3, double sortedIndexCol1, String jsonIndexCol1, String textIndexCol1, + int rawCol1, Object[] mvRawCol1, Object[] mvNoIndexCol1) { GenericRow record = new GenericRow(); + record.putValue(COL1_RAW, rawCol1); + record.putValue(COL1_NO_INDEX, noIndexCol1); record.putValue(COL2_NO_INDEX, noIndexCol2); record.putValue(COL3_NO_INDEX, noIndexCol3); @@ -166,7 +179,7 @@ GenericRow createMockRecord(int noIndexCol1, int noIndexCol2, int noIndexCol3, record.putValue(COL1_INVERTED_INDEX, invertedIndexCol1); record.putValue(COL2_INVERTED_INDEX, invertedIndexCol2); - record.putValue(COL3_INVERTED_INDEX, intervedIndexCol3); + record.putValue(COL3_INVERTED_INDEX, invertedIndexCol3); record.putValue(COL1_RANGE_INDEX, rangeIndexCol1); record.putValue(COL2_RANGE_INDEX, rangeIndexCol2); @@ -177,6 +190,9 @@ GenericRow createMockRecord(int noIndexCol1, int noIndexCol2, int noIndexCol3, record.putValue(COL1_JSON_INDEX, jsonIndexCol1); record.putValue(COL1_TEXT_INDEX, textIndexCol1); + record.putValue(MV_COL1_RAW, mvRawCol1); + record.putValue(MV_COL1_NO_INDEX, mvNoIndexCol1); + return record; } @@ -184,23 +200,24 @@ ImmutableSegment createImmutableSegment(List records, String segment throws Exception { IndexingConfig indexingConfig = TABLE_CONFIG.getIndexingConfig(); - List invertedIndexColumns = Arrays.asList(COL1_INVERTED_INDEX, COL2_INVERTED_INDEX, COL3_INVERTED_INDEX); + List invertedIndexColumns = List.of(COL1_INVERTED_INDEX, COL2_INVERTED_INDEX, COL3_INVERTED_INDEX); indexingConfig.setInvertedIndexColumns(invertedIndexColumns); - List rangeIndexColumns = Arrays.asList(COL1_RANGE_INDEX, COL2_RANGE_INDEX, COL3_RANGE_INDEX); + List rangeIndexColumns = List.of(COL1_RANGE_INDEX, COL2_RANGE_INDEX, COL3_RANGE_INDEX); indexingConfig.setRangeIndexColumns(rangeIndexColumns); - List sortedIndexColumns = Collections.singletonList(COL1_SORTED_INDEX); + List sortedIndexColumns = List.of(COL1_SORTED_INDEX); indexingConfig.setSortedColumn(sortedIndexColumns); - List jsonIndexColumns = Arrays.asList(COL1_JSON_INDEX); + List jsonIndexColumns = List.of(COL1_JSON_INDEX); indexingConfig.setJsonIndexColumns(jsonIndexColumns); - List textIndexColumns = Arrays.asList(COL1_TEXT_INDEX); + List textIndexColumns = List.of(COL1_TEXT_INDEX); + File tableDataDir = new File(TEMP_DIR, OFFLINE_TABLE_NAME); SegmentGeneratorConfig segmentGeneratorConfig = new SegmentGeneratorConfig(TABLE_CONFIG, SCHEMA); segmentGeneratorConfig.setSegmentName(segmentName); - segmentGeneratorConfig.setOutDir(INDEX_DIR.getPath()); + segmentGeneratorConfig.setOutDir(tableDataDir.getPath()); SegmentIndexCreationDriverImpl driver = new SegmentIndexCreationDriverImpl(); driver.init(segmentGeneratorConfig, new GenericRowRecordReader(records)); @@ -216,70 +233,71 @@ ImmutableSegment createImmutableSegment(List records, String segment _segmentNames.add(segmentName); - return ImmutableSegmentLoader.load(new File(INDEX_DIR, segmentName), indexLoadingConfig); + return ImmutableSegmentLoader.load(new File(tableDataDir, segmentName), indexLoadingConfig); } @BeforeClass public void setUp() throws Exception { - FileUtils.deleteDirectory(INDEX_DIR); + ServerMetrics.register(mock(ServerMetrics.class)); + + FileUtils.deleteDirectory(TEMP_DIR); _segmentNames = new ArrayList<>(); List records = new ArrayList<>(NUM_RECORDS); records.add(createMockRecord(1, 2, 3, true, 1.1, 2, "daffy", 10.1, 20, 30, 100.1, - "{\"first\": \"daffy\", \"last\": " + "\"duck\"}", "daffy")); + "{\"first\": \"daffy\", \"last\": \"duck\"}", "daffy", 1, new Object[]{1, 2, 3}, new Object[]{1, 2, 3})); records.add(createMockRecord(0, 1, 2, false, 0.1, 1, "mickey", 0.1, 10, 20, 100.2, - "{\"first\": \"mickey\", \"last\": " + "\"mouse\"}", "mickey")); + "{\"first\": \"mickey\", \"last\": \"mouse\"}", "mickey", 0, new Object[]{2, 3, 4}, new Object[]{2, 3, 4})); records.add(createMockRecord(3, 4, 5, true, 2.1, 3, "mickey", 20.1, 30, 40, 100.3, - "{\"first\": \"mickey\", \"last\": " + "\"mouse\"}", "mickey")); + "{\"first\": \"mickey\", \"last\": \"mouse\"}", "mickey", 3, new Object[]{3, 4, 5}, new Object[]{3, 4, 5})); ImmutableSegment immutableSegment1 = createImmutableSegment(records, SEGMENT_NAME_1); List records2 = new ArrayList<>(NUM_RECORDS); records2.add(createMockRecord(5, 2, 3, true, 1.1, 2, "pluto", 10.1, 20, 30, 100.1, - "{\"first\": \"pluto\", \"last\": " + "\"dog\"}", "pluto")); + "{\"first\": \"pluto\", \"last\": \"dog\"}", "pluto", 5, new Object[]{100, 200, 300}, + new Object[]{100, 200, 300})); records2.add(createMockRecord(6, 1, 2, false, 0.1, 1, "pluto", 0.1, 10, 20, 100.2, - "{\"first\": \"pluto\", \"last\": " + "\"dog\"}", "pluto")); + "{\"first\": \"pluto\", \"last\": \"dog\"}", "pluto", 6, new Object[]{200, 300, 400}, + new Object[]{200, 300, 400})); records2.add(createMockRecord(8, 4, 5, true, 2.1, 3, "pluto", 20.1, 30, 40, 100.3, - "{\"first\": \"pluto\", \"last\": " + "\"dog\"}", "pluto")); + "{\"first\": \"pluto\", \"last\": \"dog\"}", "pluto", 8, new Object[]{300, 400, 500}, + new Object[]{300, 400, 500})); ImmutableSegment immutableSegment2 = createImmutableSegment(records2, SEGMENT_NAME_2); List records3 = new ArrayList<>(NUM_RECORDS); records3.add(createMockRecord(5, 2, 3, true, 1.5, 2, "donald", 10.1, 20, 30, 100.1, - "{\"first\": \"donald\", \"last\": " + "\"duck\"}", "donald")); + "{\"first\": \"donald\", \"last\": \"duck\"}", "donald", 1, new Object[]{100, 200, 300}, + new Object[]{100, 200, 300})); records3.add(createMockRecord(6, 1, 2, false, 0.1, 1, "goofy", 0.1, 10, 20, 100.2, - "{\"first\": \"goofy\", \"last\": " + "\"dog\"}", "goofy")); + "{\"first\": \"goofy\", \"last\": \"dog\"}", "goofy", 1, new Object[]{100, 200, 300}, + new Object[]{100, 200, 300})); records3.add(createMockRecord(7, 4, 5, true, 2.1, 3, "minnie", 20.1, 30, 40, 100.3, - "{\"first\": \"minnie\", \"last\": " + "\"mouse\"}", "minnie")); + "{\"first\": \"minnie\", \"last\": \"mouse\"}", "minnie", 1, new Object[]{1000, 2000, 3000}, + new Object[]{1000, 2000, 3000})); ImmutableSegment immutableSegment3 = createImmutableSegment(records3, SEGMENT_NAME_3); List records4 = new ArrayList<>(NUM_RECORDS); records4.add(createMockRecord(5, 2, 3, true, 1.1, 2, "tweety", 10.1, 20, 30, 100.1, - "{\"first\": \"tweety\", \"last\": " + "\"bird\"}", "tweety")); + "{\"first\": \"tweety\", \"last\": \"bird\"}", "tweety", 5, new Object[]{100, 200, 300}, + new Object[]{100, 200, 300})); records4.add(createMockRecord(6, 1, 2, false, 0.1, 1, "bugs", 0.1, 10, 20, 100.2, - "{\"first\": \"bugs\", \"last\": " + "\"bunny\"}", "bugs")); + "{\"first\": \"bugs\", \"last\": \"bunny\"}", "bugs", 6, new Object[]{100, 200, 300}, + new Object[]{100, 200, 300})); records4.add(createMockRecord(7, 4, 5, true, 2.1, 3, "sylvester", 20.1, 30, 40, 100.3, - "{\"first\": \"sylvester\", \"last\": " + "\"cat\"}", "sylvester")); + "{\"first\": \"sylvester\", \"last\": \"cat\"}", "sylvester", 7, new Object[]{1000, 2000, 3000}, + new Object[]{1000, 2000, 3000})); ImmutableSegment immutableSegment4 = createImmutableSegment(records4, SEGMENT_NAME_4); _indexSegment = immutableSegment1; - _indexSegments = Arrays.asList(immutableSegment1, immutableSegment2, immutableSegment3, immutableSegment4); + _indexSegments = List.of(immutableSegment1, immutableSegment2, immutableSegment3, immutableSegment4); // Mock the instance data manager - _serverMetrics = new ServerMetrics(PinotMetricUtils.getPinotMetricsRegistry()); - TableDataManagerConfig tableDataManagerConfig = mock(TableDataManagerConfig.class); - when(tableDataManagerConfig.getTableName()).thenReturn(OFFLINE_TABLE_NAME); - when(tableDataManagerConfig.getTableType()).thenReturn(TableType.OFFLINE); - when(tableDataManagerConfig.getDataDir()).thenReturn(FileUtils.getTempDirectoryPath()); InstanceDataManagerConfig instanceDataManagerConfig = mock(InstanceDataManagerConfig.class); - when(instanceDataManagerConfig.getMaxParallelSegmentBuilds()).thenReturn(4); - when(instanceDataManagerConfig.getStreamSegmentDownloadUntarRateLimit()).thenReturn(-1L); - when(instanceDataManagerConfig.getMaxParallelSegmentDownloads()).thenReturn(-1); - when(instanceDataManagerConfig.isStreamSegmentDownloadUntar()).thenReturn(false); - TableDataManagerProvider.init(instanceDataManagerConfig); - @SuppressWarnings("unchecked") + when(instanceDataManagerConfig.getInstanceDataDir()).thenReturn(TEMP_DIR.getAbsolutePath()); TableDataManager tableDataManager = - TableDataManagerProvider.getTableDataManager(tableDataManagerConfig, "testInstance", - mock(ZkHelixPropertyStore.class), mock(ServerMetrics.class), mock(HelixManager.class), null); + new TableDataManagerProvider(instanceDataManagerConfig, mock(HelixManager.class), + new SegmentLocks()).getTableDataManager(TABLE_CONFIG); tableDataManager.start(); for (IndexSegment indexSegment : _indexSegments) { tableDataManager.addSegment((ImmutableSegment) indexSegment); @@ -290,19 +308,61 @@ public void setUp() // Set up the query executor URL resourceUrl = getClass().getClassLoader().getResource(QUERY_EXECUTOR_CONFIG_PATH); Assert.assertNotNull(resourceUrl); - PropertiesConfiguration queryExecutorConfig = new PropertiesConfiguration(); - queryExecutorConfig.setDelimiterParsingDisabled(false); - queryExecutorConfig.load(new File(resourceUrl.getFile())); + PropertiesConfiguration queryExecutorConfig = CommonsConfigurationUtils.fromFile(new File(resourceUrl.getFile())); _queryExecutor = new ServerQueryExecutorV1Impl(); - _queryExecutor.init(new PinotConfiguration(queryExecutorConfig), instanceDataManager, _serverMetrics); + _queryExecutor.init(new PinotConfiguration(queryExecutorConfig), instanceDataManager, ServerMetrics.get()); + + PinotConfiguration prefetchEnabledConf = new PinotConfiguration(queryExecutorConfig); + prefetchEnabledConf.setProperty(ServerQueryExecutorV1Impl.ENABLE_PREFETCH, "true"); + _queryExecutorWithPrefetchEnabled = new ServerQueryExecutorV1Impl(); + _queryExecutorWithPrefetchEnabled.init(prefetchEnabledConf, instanceDataManager, ServerMetrics.get()); // Create the BrokerReduceService - _brokerReduceService = new BrokerReduceService(new PinotConfiguration( - Collections.singletonMap(CommonConstants.Broker.CONFIG_OF_MAX_REDUCE_THREADS_PER_QUERY, 2))); + _brokerReduceService = + new BrokerReduceService(new PinotConfiguration(Map.of(Broker.CONFIG_OF_MAX_REDUCE_THREADS_PER_QUERY, 2))); + } + + private ResultTable getPrefetchEnabledResulTable(ResultTable resultTable) { + // TODO does not work for cases where different segments have different plans (more than 1 PLAN_START rows) + List newRows = new ArrayList<>(); + int acquireOpParentId = -1; + + Iterator it = resultTable.getRows().iterator(); + // After each PLAN_START, we need to add ACQUIRE_RELEASE_COLUMNS_SEGMENT (unles ALL_SEGMENTS_PRUNED_ON_SERVER), + // and all following op should have their ids incremented + + while (it.hasNext()) { + Object[] row = it.next(); + String op = row[0].toString(); + newRows.add(row); + acquireOpParentId = Math.max(acquireOpParentId, (int) row[1]); + if (op.startsWith("PLAN_START")) { + newRows.add(new Object[]{"ACQUIRE_RELEASE_COLUMNS_SEGMENT", acquireOpParentId + 1, acquireOpParentId}); + break; + } + } + + while (it.hasNext()) { + Object[] row = it.next(); + newRows.add(new Object[]{row[0].toString(), ((int) row[1] + 1), ((int) row[2]) + 1}); + } + + return new ResultTable(resultTable.getDataSchema(), newRows); } /** Checks the correctness of EXPLAIN PLAN output. */ private void check(String query, ResultTable expected) { + check(query, expected, false); + } + + private void check(String query, ResultTable expected, boolean checkPrefetchEnabled) { + checkWithQueryExecutor(query, expected, _queryExecutor); + if (checkPrefetchEnabled) { + checkWithQueryExecutor(query, getPrefetchEnabledResulTable(expected), _queryExecutorWithPrefetchEnabled); + } + } + + private void checkWithQueryExecutor(String query, ResultTable expected, QueryExecutor queryExecutor) { BrokerRequest brokerRequest = CalciteSqlCompiler.compileToBrokerRequest(query); int segmentsForServer1 = _segmentNames.size() / 2; @@ -320,10 +380,10 @@ private void check(String query, ResultTable expected) { brokerRequest.getPinotQuery().getDataSource().setTableName(OFFLINE_TABLE_NAME); InstanceRequest instanceRequest1 = new InstanceRequest(0L, brokerRequest); instanceRequest1.setSearchSegments(indexSegmentsForServer1); - InstanceResponseBlock instanceResponse1 = _queryExecutor.execute(getQueryRequest(instanceRequest1), QUERY_RUNNERS); + InstanceResponseBlock instanceResponse1 = queryExecutor.execute(getQueryRequest(instanceRequest1), QUERY_RUNNERS); InstanceRequest instanceRequest2 = new InstanceRequest(0L, brokerRequest); instanceRequest2.setSearchSegments(indexSegmentsForServer2); - InstanceResponseBlock instanceResponse2 = _queryExecutor.execute(getQueryRequest(instanceRequest2), QUERY_RUNNERS); + InstanceResponseBlock instanceResponse2 = queryExecutor.execute(getQueryRequest(instanceRequest2), QUERY_RUNNERS); // Broker side // Use 2 Threads for 2 data-tables @@ -346,13 +406,13 @@ private void check(String query, ResultTable expected) { brokerRequest.getPinotQuery().getDataSource().setTableName(RAW_TABLE_NAME); BrokerResponseNative brokerResponse = _brokerReduceService.reduceOnDataTable(brokerRequest, brokerRequest, dataTableMap, - CommonConstants.Broker.DEFAULT_BROKER_TIMEOUT_MS, null); + Broker.DEFAULT_BROKER_TIMEOUT_MS, BROKER_METRICS); - QueriesTestUtils.testInterSegmentsResult(brokerResponse, expected); + QueriesTestUtils.testExplainSegmentsResult(brokerResponse, expected); } private ServerQueryRequest getQueryRequest(InstanceRequest instanceRequest) { - return new ServerQueryRequest(instanceRequest, _serverMetrics, System.currentTimeMillis()); + return new ServerQueryRequest(instanceRequest, ServerMetrics.get(), System.currentTimeMillis()); } @Test @@ -363,60 +423,62 @@ public void testSelect() { List result1 = new ArrayList<>(); result1.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result1.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); - result1.add(new Object[]{ - "SELECT(selectList:invertedIndexCol1, invertedIndexCol2, invertedIndexCol3, jsonIndexCol1, " - + "noIndexCol1, noIndexCol2, noIndexCol3, noIndexCol4, rangeIndexCol1, rangeIndexCol2, rangeIndexCol3, " - + "sortedIndexCol1, textIndexCol1)", 3, 2}); - result1.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, invertedIndexCol2, invertedIndexCol3, " - + "jsonIndexCol1, noIndexCol1, noIndexCol2, noIndexCol3, noIndexCol4, rangeIndexCol1, rangeIndexCol2, " - + "rangeIndexCol3, sortedIndexCol1, textIndexCol1)", 4, 3}); - result1.add(new Object[]{"PROJECT(noIndexCol4, sortedIndexCol1, noIndexCol3, rangeIndexCol1, rangeIndexCol2, " - + "invertedIndexCol1, noIndexCol2, invertedIndexCol2, noIndexCol1, rangeIndexCol3, textIndexCol1, " - + "jsonIndexCol1, invertedIndexCol3)", 5, 4}); - result1.add(new Object[]{"DOC_ID_SET", 6, 5}); - result1.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 7, 6}); - check(query1, new ResultTable(DATA_SCHEMA, result1)); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); + result1.add(new Object[]{ + "SELECT(selectList:invertedIndexCol1, invertedIndexCol2, invertedIndexCol3, jsonIndexCol1, mvNoIndexCol1, " + + "mvRawCol1, noIndexCol1, noIndexCol2, noIndexCol3, noIndexCol4, rangeIndexCol1, rangeIndexCol2, " + + "rangeIndexCol3, rawCol1, sortedIndexCol1, textIndexCol1)", 3, 2 + }); + result1.add(new Object[]{ + "PROJECT(noIndexCol4, rawCol1, sortedIndexCol1, noIndexCol3, mvNoIndexCol1" + + ", rangeIndexCol1, rangeIndexCol2, invertedIndexCol1, noIndexCol2, invertedIndexCol2, noIndexCol1, " + + "rangeIndexCol3, textIndexCol1, mvRawCol1, jsonIndexCol1, invertedIndexCol3)", 4, 3 + }); + result1.add(new Object[]{"DOC_ID_SET", 5, 4}); + result1.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 6, 5}); + check(query1, new ResultTable(DATA_SCHEMA, result1), true); String query2 = "EXPLAIN PLAN FOR SELECT 'mickey' FROM testTable"; List result2 = new ArrayList<>(); result2.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result2.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result2.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result2.add(new Object[]{"SELECT(selectList:'mickey')", 3, 2}); result2.add(new Object[]{"TRANSFORM('mickey')", 4, 3}); result2.add(new Object[]{"PROJECT()", 5, 4}); result2.add(new Object[]{"DOC_ID_SET", 6, 5}); result2.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 7, 6}); - check(query2, new ResultTable(DATA_SCHEMA, result2)); + check(query2, new ResultTable(DATA_SCHEMA, result2), true); String query3 = "EXPLAIN PLAN FOR SELECT invertedIndexCol1, noIndexCol1 FROM testTable LIMIT 100"; List result3 = new ArrayList<>(); result3.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result3.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result3.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result3.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result3.add(new Object[]{"SELECT(selectList:invertedIndexCol1, noIndexCol1)", 3, 2}); - result3.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1)", 4, 3}); - result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 5, 4}); - result3.add(new Object[]{"DOC_ID_SET", 6, 5}); - result3.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 7, 6}); - check(query3, new ResultTable(DATA_SCHEMA, result3)); + result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 4, 3}); + result3.add(new Object[]{"DOC_ID_SET", 5, 4}); + result3.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 6, 5}); + check(query3, new ResultTable(DATA_SCHEMA, result3), true); String query4 = "EXPLAIN PLAN FOR SELECT DISTINCT invertedIndexCol1, noIndexCol1 FROM testTable LIMIT 100"; List result4 = new ArrayList<>(); result4.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result4.add(new Object[]{"COMBINE_DISTINCT", 2, 1}); - result4.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result4.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result4.add(new Object[]{"DISTINCT(keyColumns:invertedIndexCol1, noIndexCol1)", 3, 2}); - result4.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1)", 4, 3}); - result4.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 5, 4}); - result4.add(new Object[]{"DOC_ID_SET", 6, 5}); - result4.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 7, 6}); - check(query4, new ResultTable(DATA_SCHEMA, result4)); + result4.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 4, 3}); + result4.add(new Object[]{"DOC_ID_SET", 5, 4}); + result4.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 6, 5}); + check(query4, new ResultTable(DATA_SCHEMA, result4), true); } @Test @@ -427,28 +489,30 @@ public void testSelectVerbose() { List result1 = new ArrayList<>(); result1.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result1.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); - result1.add(new Object[]{ - "SELECT(selectList:invertedIndexCol1, invertedIndexCol2, invertedIndexCol3, jsonIndexCol1, " - + "noIndexCol1, noIndexCol2, noIndexCol3, noIndexCol4, rangeIndexCol1, rangeIndexCol2, rangeIndexCol3, " - + "sortedIndexCol1, textIndexCol1)", 3, 2}); - result1.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, invertedIndexCol2, invertedIndexCol3, " - + "jsonIndexCol1, noIndexCol1, noIndexCol2, noIndexCol3, noIndexCol4, rangeIndexCol1, rangeIndexCol2, " - + "rangeIndexCol3, sortedIndexCol1, textIndexCol1)", 4, 3}); - result1.add(new Object[]{"PROJECT(noIndexCol4, sortedIndexCol1, noIndexCol3, rangeIndexCol1, rangeIndexCol2, " - + "invertedIndexCol1, noIndexCol2, invertedIndexCol2, noIndexCol1, rangeIndexCol3, textIndexCol1, " - + "jsonIndexCol1, invertedIndexCol3)", 5, 4}); - result1.add(new Object[]{"DOC_ID_SET", 6, 5}); - result1.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 7, 6}); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); + result1.add(new Object[]{ + "SELECT(selectList:invertedIndexCol1, invertedIndexCol2, invertedIndexCol3, jsonIndexCol1, mvNoIndexCol1, " + + "mvRawCol1, noIndexCol1, noIndexCol2, noIndexCol3, noIndexCol4, rangeIndexCol1, rangeIndexCol2, " + + "rangeIndexCol3, rawCol1, sortedIndexCol1, textIndexCol1)", 3, 2 + }); + result1.add(new Object[]{ + "PROJECT(noIndexCol4, rawCol1, sortedIndexCol1, noIndexCol3, mvNoIndexCol1, " + + "rangeIndexCol1, rangeIndexCol2, invertedIndexCol1, noIndexCol2, invertedIndexCol2, noIndexCol1, " + + "rangeIndexCol3, textIndexCol1, mvRawCol1, jsonIndexCol1, invertedIndexCol3)", 4, 3 + }); + result1.add(new Object[]{"DOC_ID_SET", 5, 4}); + result1.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 6, 5}); check(query1, new ResultTable(DATA_SCHEMA, result1)); String query2 = "SET explainPlanVerbose=true; EXPLAIN PLAN FOR SELECT 'mickey' FROM testTable"; List result2 = new ArrayList<>(); result2.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result2.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result2.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result2.add(new Object[]{"SELECT(selectList:'mickey')", 3, 2}); result2.add(new Object[]{"TRANSFORM('mickey')", 4, 3}); result2.add(new Object[]{"PROJECT()", 5, 4}); @@ -461,13 +525,13 @@ public void testSelectVerbose() { List result3 = new ArrayList<>(); result3.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result3.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result3.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result3.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result3.add(new Object[]{"SELECT(selectList:invertedIndexCol1, noIndexCol1)", 3, 2}); - result3.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1)", 4, 3}); - result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 5, 4}); - result3.add(new Object[]{"DOC_ID_SET", 6, 5}); - result3.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 7, 6}); + result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 4, 3}); + result3.add(new Object[]{"DOC_ID_SET", 5, 4}); + result3.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 6, 5}); check(query3, new ResultTable(DATA_SCHEMA, result3)); String query4 = "SET explainPlanVerbose=true; EXPLAIN PLAN FOR SELECT DISTINCT invertedIndexCol1, noIndexCol1 " @@ -475,13 +539,13 @@ public void testSelectVerbose() { List result4 = new ArrayList<>(); result4.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result4.add(new Object[]{"COMBINE_DISTINCT", 2, 1}); - result4.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result4.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result4.add(new Object[]{"DISTINCT(keyColumns:invertedIndexCol1, noIndexCol1)", 3, 2}); - result4.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1)", 4, 3}); - result4.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 5, 4}); - result4.add(new Object[]{"DOC_ID_SET", 6, 5}); - result4.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 7, 6}); + result4.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 4, 3}); + result4.add(new Object[]{"DOC_ID_SET", 5, 4}); + result4.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 6, 5}); check(query4, new ResultTable(DATA_SCHEMA, result4)); } @@ -493,8 +557,9 @@ public void testSelectTransformFunction() { List result1 = new ArrayList<>(); result1.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result1.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result1.add(new Object[]{"SELECT(selectList:case(less_than(noIndexCol1,'10'),'less','more'))", 3, 2}); result1.add(new Object[]{"TRANSFORM(case(less_than(noIndexCol1,'10'),'less','more'))", 4, 3}); result1.add(new Object[]{"PROJECT(noIndexCol1)", 5, 4}); @@ -506,8 +571,9 @@ public void testSelectTransformFunction() { List result2 = new ArrayList<>(); result2.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result2.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result2.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result2.add(new Object[]{"SELECT(selectList:concat(textIndexCol1,textIndexCol1,':'))", 3, 2}); result2.add(new Object[]{"TRANSFORM(concat(textIndexCol1,textIndexCol1,':'))", 4, 3}); result2.add(new Object[]{"PROJECT(textIndexCol1)", 5, 4}); @@ -525,8 +591,9 @@ public void testSelectTransformFunctionVerbose() { List result1 = new ArrayList<>(); result1.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result1.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result1.add(new Object[]{"SELECT(selectList:case(less_than(noIndexCol1,'10'),'less','more'))", 3, 2}); result1.add(new Object[]{"TRANSFORM(case(less_than(noIndexCol1,'10'),'less','more'))", 4, 3}); result1.add(new Object[]{"PROJECT(noIndexCol1)", 5, 4}); @@ -539,8 +606,9 @@ public void testSelectTransformFunctionVerbose() { List result2 = new ArrayList<>(); result2.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result2.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result2.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result2.add(new Object[]{"SELECT(selectList:concat(textIndexCol1,textIndexCol1,':'))", 3, 2}); result2.add(new Object[]{"TRANSFORM(concat(textIndexCol1,textIndexCol1,':'))", 4, 3}); result2.add(new Object[]{"PROJECT(textIndexCol1)", 5, 4}); @@ -556,11 +624,12 @@ public void testSelectOrderBy() { String query1 = "EXPLAIN PLAN FOR SELECT CASE WHEN noIndexCol1 < 10 THEN 'less' ELSE 'more' END FROM testTable " + "ORDER BY 1"; List result1 = new ArrayList<>(); - result1 - .add(new Object[]{"BROKER_REDUCE(sort:[case(less_than(noIndexCol1,'10'),'less','more') ASC],limit:10)", 1, 0}); + result1.add( + new Object[]{"BROKER_REDUCE(sort:[case(less_than(noIndexCol1,'10'),'less','more') ASC],limit:10)", 1, 0}); result1.add(new Object[]{"COMBINE_SELECT_ORDERBY", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result1.add(new Object[]{"SELECT_ORDERBY(selectList:case(less_than(noIndexCol1,'10'),'less','more'))", 3, 2}); result1.add(new Object[]{"TRANSFORM(case(less_than(noIndexCol1,'10'),'less','more'))", 4, 3}); result1.add(new Object[]{"PROJECT(noIndexCol1)", 5, 4}); @@ -572,8 +641,9 @@ public void testSelectOrderBy() { List result2 = new ArrayList<>(); result2.add(new Object[]{"BROKER_REDUCE(sort:[concat(textIndexCol1,textIndexCol1,':') DESC],limit:10)", 1, 0}); result2.add(new Object[]{"COMBINE_SELECT_ORDERBY", 2, 1}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result2.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result2.add(new Object[]{"SELECT_ORDERBY(selectList:concat(textIndexCol1,textIndexCol1,':'))", 3, 2}); result2.add(new Object[]{"TRANSFORM(concat(textIndexCol1,textIndexCol1,':'))", 4, 3}); result2.add(new Object[]{"PROJECT(textIndexCol1)", 5, 4}); @@ -589,11 +659,12 @@ public void testSelectOrderByVerbose() { String query1 = "SET explainPlanVerbose=true; EXPLAIN PLAN FOR SELECT CASE WHEN noIndexCol1 < 10 THEN 'less' ELSE " + "'more' END FROM testTable ORDER BY 1"; List result1 = new ArrayList<>(); - result1 - .add(new Object[]{"BROKER_REDUCE(sort:[case(less_than(noIndexCol1,'10'),'less','more') ASC],limit:10)", 1, 0}); + result1.add( + new Object[]{"BROKER_REDUCE(sort:[case(less_than(noIndexCol1,'10'),'less','more') ASC],limit:10)", 1, 0}); result1.add(new Object[]{"COMBINE_SELECT_ORDERBY", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result1.add(new Object[]{"SELECT_ORDERBY(selectList:case(less_than(noIndexCol1,'10'),'less','more'))", 3, 2}); result1.add(new Object[]{"TRANSFORM(case(less_than(noIndexCol1,'10'),'less','more'))", 4, 3}); result1.add(new Object[]{"PROJECT(noIndexCol1)", 5, 4}); @@ -606,8 +677,9 @@ public void testSelectOrderByVerbose() { List result2 = new ArrayList<>(); result2.add(new Object[]{"BROKER_REDUCE(sort:[concat(textIndexCol1,textIndexCol1,':') DESC],limit:10)", 1, 0}); result2.add(new Object[]{"COMBINE_SELECT_ORDERBY", 2, 1}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result2.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result2.add(new Object[]{"SELECT_ORDERBY(selectList:concat(textIndexCol1,textIndexCol1,':'))", 3, 2}); result2.add(new Object[]{"TRANSFORM(concat(textIndexCol1,textIndexCol1,':'))", 4, 3}); result2.add(new Object[]{"PROJECT(textIndexCol1)", 5, 4}); @@ -628,13 +700,13 @@ public void testSelectColumnsUsingFilter() { List result1 = new ArrayList<>(); result1.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result1.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result1.add(new Object[]{"SELECT(selectList:noIndexCol1, noIndexCol2, sortedIndexCol1)", 3, 2}); - result1.add(new Object[]{"TRANSFORM_PASSTHROUGH(noIndexCol1, noIndexCol2, sortedIndexCol1)", 4, 3}); - result1.add(new Object[]{"PROJECT(sortedIndexCol1, noIndexCol2, noIndexCol1)", 5, 4}); - result1.add(new Object[]{"DOC_ID_SET", 6, 5}); - result1.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 7, 6}); + result1.add(new Object[]{"PROJECT(sortedIndexCol1, noIndexCol2, noIndexCol1)", 4, 3}); + result1.add(new Object[]{"DOC_ID_SET", 5, 4}); + result1.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 6, 5}); check(query1, new ResultTable(DATA_SCHEMA, result1)); // The FILTER_AND is part of the query plan for all segments as both predicates are expressions @@ -644,18 +716,18 @@ public void testSelectColumnsUsingFilter() { List result2 = new ArrayList<>(); result2.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result2.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result2.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result2.add(new Object[]{"SELECT(selectList:noIndexCol1, noIndexCol2)", 3, 2}); - result2.add(new Object[]{"TRANSFORM_PASSTHROUGH(noIndexCol1, noIndexCol2)", 4, 3}); - result2.add(new Object[]{"PROJECT(noIndexCol2, noIndexCol1)", 5, 4}); - result2.add(new Object[]{"DOC_ID_SET", 6, 5}); - result2.add(new Object[]{"FILTER_AND", 7, 6}); + result2.add(new Object[]{"PROJECT(noIndexCol2, noIndexCol1)", 4, 3}); + result2.add(new Object[]{"DOC_ID_SET", 5, 4}); + result2.add(new Object[]{"FILTER_AND", 6, 5}); + result2.add(new Object[]{ + "FILTER_EXPRESSION(operator:RANGE,predicate:div(noIndexCol1,noIndexCol2) BETWEEN '10' AND '20')", 7, 6 + }); result2.add( - new Object[]{"FILTER_EXPRESSION(operator:RANGE,predicate:div(noIndexCol1,noIndexCol2) BETWEEN '10' AND '20')", - 8, 7}); - result2 - .add(new Object[]{"FILTER_EXPRESSION(operator:RANGE,predicate:times(invertedIndexCol1,'5') < '1000')", 9, 7}); + new Object[]{"FILTER_EXPRESSION(operator:RANGE,predicate:times(invertedIndexCol1,'5') < '1000')", 8, 6}); check(query2, new ResultTable(DATA_SCHEMA, result2)); // All segments have a match for noIndexCol2 'between 2 and 101' @@ -669,15 +741,15 @@ public void testSelectColumnsUsingFilter() { List result3 = new ArrayList<>(); result3.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result3.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result3.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result3.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result3.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1)", 3, 2}); - result3.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1)", 4, 3}); - result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 5, 4}); - result3.add(new Object[]{"DOC_ID_SET", 6, 5}); - result3.add(new Object[]{"FILTER_OR", 7, 6}); - result3.add(new Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:noIndexCol1 > '1')", 8, 7}); - result3.add(new Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:noIndexCol2 BETWEEN '2' AND '101')", 9, 7}); + result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 4, 3}); + result3.add(new Object[]{"DOC_ID_SET", 5, 4}); + result3.add(new Object[]{"FILTER_OR", 6, 5}); + result3.add(new Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:noIndexCol1 > '1')", 7, 6}); + result3.add(new Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:noIndexCol2 BETWEEN '2' AND '101')", 8, 6}); check(query3, new ResultTable(DATA_SCHEMA, result3)); // All segments have a match for noIndexCol2 'between 2 and 101' @@ -690,16 +762,16 @@ public void testSelectColumnsUsingFilter() { List result4 = new ArrayList<>(); result4.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result4.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result4.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result4.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result4.add(new Object[]{"SELECT(selectList:invertedIndexCol1, noIndexCol1)", 3, 2}); - result4.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1)", 4, 3}); - result4.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 5, 4}); - result4.add(new Object[]{"DOC_ID_SET", 6, 5}); - result4.add(new Object[]{"FILTER_OR", 7, 6}); - result4.add(new Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:noIndexCol1 > '1')", 8, 7}); - result4.add(new Object[]{"FILTER_EXPRESSION(operator:EQ,predicate:contains(textIndexCol1,'daff') = 'true')", 9, 7}); - result4.add(new Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:noIndexCol2 BETWEEN '2' AND '101')", 10, 7}); + result4.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 4, 3}); + result4.add(new Object[]{"DOC_ID_SET", 5, 4}); + result4.add(new Object[]{"FILTER_OR", 6, 5}); + result4.add(new Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:noIndexCol1 > '1')", 7, 6}); + result4.add(new Object[]{"FILTER_EXPRESSION(operator:EQ,predicate:contains(textIndexCol1,'daff') = 'true')", 8, 6}); + result4.add(new Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:noIndexCol2 BETWEEN '2' AND '101')", 9, 6}); check(query4, new ResultTable(DATA_SCHEMA, result4)); // All segments match for a full scan since noIndexCol4 has at least one row value set to 'true' across all segments @@ -707,13 +779,13 @@ public void testSelectColumnsUsingFilter() { List result5 = new ArrayList<>(); result5.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result5.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result5.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result5.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result5.add(new Object[]{"SELECT(selectList:invertedIndexCol1, noIndexCol1)", 3, 2}); - result5.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1)", 4, 3}); - result5.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 5, 4}); - result5.add(new Object[]{"DOC_ID_SET", 6, 5}); - result5.add(new Object[]{"FILTER_FULL_SCAN(operator:EQ,predicate:noIndexCol4 = 'true')", 7, 6}); + result5.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 4, 3}); + result5.add(new Object[]{"DOC_ID_SET", 5, 4}); + result5.add(new Object[]{"FILTER_FULL_SCAN(operator:EQ,predicate:noIndexCol4 = 'true')", 6, 5}); check(query5, new ResultTable(DATA_SCHEMA, result5)); // The FILTER_AND is part of the query plan for all segments as the first predicate is an expression and the second @@ -723,16 +795,17 @@ public void testSelectColumnsUsingFilter() { List result6 = new ArrayList<>(); result6.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result6.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result6.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result6.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result6.add(new Object[]{"SELECT(selectList:invertedIndexCol1, noIndexCol1)", 3, 2}); - result6.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1)", 4, 3}); - result6.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 5, 4}); - result6.add(new Object[]{"DOC_ID_SET", 6, 5}); - result6.add(new Object[]{"FILTER_AND", 7, 6}); - result6.add(new Object[]{"FILTER_FULL_SCAN(operator:EQ,predicate:noIndexCol4 = 'true')", 8, 7}); - result6.add(new Object[]{"FILTER_EXPRESSION(operator:EQ,predicate:startswith(textIndexCol1,'daff') = 'true')", 9, - 7}); + result6.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 4, 3}); + result6.add(new Object[]{"DOC_ID_SET", 5, 4}); + result6.add(new Object[]{"FILTER_AND", 6, 5}); + result6.add(new Object[]{"FILTER_FULL_SCAN(operator:EQ,predicate:noIndexCol4 = 'true')", 7, 6}); + result6.add(new Object[]{ + "FILTER_EXPRESSION(operator:EQ,predicate:startswith(textIndexCol1,'daff') = 'true')", 8, 6 + }); check(query6, new ResultTable(DATA_SCHEMA, result6)); } @@ -749,13 +822,13 @@ public void testSelectColumnsUsingFilterVerbose() { List result1 = new ArrayList<>(); result1.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result1.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result1.add(new Object[]{"SELECT(selectList:noIndexCol1, noIndexCol2, sortedIndexCol1)", 3, 2}); - result1.add(new Object[]{"TRANSFORM_PASSTHROUGH(noIndexCol1, noIndexCol2, sortedIndexCol1)", 4, 3}); - result1.add(new Object[]{"PROJECT(sortedIndexCol1, noIndexCol2, noIndexCol1)", 5, 4}); - result1.add(new Object[]{"DOC_ID_SET", 6, 5}); - result1.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 7, 6}); + result1.add(new Object[]{"PROJECT(sortedIndexCol1, noIndexCol2, noIndexCol1)", 4, 3}); + result1.add(new Object[]{"DOC_ID_SET", 5, 4}); + result1.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 6, 5}); check(query1, new ResultTable(DATA_SCHEMA, result1)); // The FILTER_AND is part of the query plan for all segments as both predicates are expressions @@ -765,18 +838,18 @@ public void testSelectColumnsUsingFilterVerbose() { List result2 = new ArrayList<>(); result2.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result2.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result2.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result2.add(new Object[]{"SELECT(selectList:noIndexCol1, noIndexCol2)", 3, 2}); - result2.add(new Object[]{"TRANSFORM_PASSTHROUGH(noIndexCol1, noIndexCol2)", 4, 3}); - result2.add(new Object[]{"PROJECT(noIndexCol2, noIndexCol1)", 5, 4}); - result2.add(new Object[]{"DOC_ID_SET", 6, 5}); - result2.add(new Object[]{"FILTER_AND", 7, 6}); + result2.add(new Object[]{"PROJECT(noIndexCol2, noIndexCol1)", 4, 3}); + result2.add(new Object[]{"DOC_ID_SET", 5, 4}); + result2.add(new Object[]{"FILTER_AND", 6, 5}); + result2.add(new Object[]{ + "FILTER_EXPRESSION(operator:RANGE,predicate:div(noIndexCol1,noIndexCol2) BETWEEN '10' AND '20')", 7, 6 + }); result2.add( - new Object[]{"FILTER_EXPRESSION(operator:RANGE,predicate:div(noIndexCol1,noIndexCol2) BETWEEN '10' AND '20')", - 8, 7}); - result2 - .add(new Object[]{"FILTER_EXPRESSION(operator:RANGE,predicate:times(invertedIndexCol1,'5') < '1000')", 9, 7}); + new Object[]{"FILTER_EXPRESSION(operator:RANGE,predicate:times(invertedIndexCol1,'5') < '1000')", 8, 6}); check(query2, new ResultTable(DATA_SCHEMA, result2)); // All segments have a match for noIndexCol2 'between 2 and 101' @@ -789,22 +862,22 @@ public void testSelectColumnsUsingFilterVerbose() { List result3 = new ArrayList<>(); result3.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result3.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result3.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result3.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result3.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1)", 3, 2}); - result3.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1)", 4, 3}); - result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 5, 4}); - result3.add(new Object[]{"DOC_ID_SET", 6, 5}); - result3.add(new Object[]{"FILTER_OR", 7, 6}); - result3.add(new Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:noIndexCol1 > '1')", 8, 7}); - result3.add(new Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:noIndexCol2 BETWEEN '2' AND '101')", 9, 7}); - result3.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 4, 3}); + result3.add(new Object[]{"DOC_ID_SET", 5, 4}); + result3.add(new Object[]{"FILTER_OR", 6, 5}); + result3.add(new Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:noIndexCol1 > '1')", 7, 6}); + result3.add(new Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:noIndexCol2 BETWEEN '2' AND '101')", 8, 6}); + result3.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result3.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1)", 3, 2}); - result3.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1)", 4, 3}); - result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 5, 4}); - result3.add(new Object[]{"DOC_ID_SET", 6, 5}); - result3.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 7, 6}); + result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 4, 3}); + result3.add(new Object[]{"DOC_ID_SET", 5, 4}); + result3.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 6, 5}); check(query3, new ResultTable(DATA_SCHEMA, result3)); // All segments have a match for noIndexCol2 'between 2 and 101' @@ -817,23 +890,23 @@ public void testSelectColumnsUsingFilterVerbose() { List result4 = new ArrayList<>(); result4.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result4.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result4.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result4.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result4.add(new Object[]{"SELECT(selectList:invertedIndexCol1, noIndexCol1)", 3, 2}); - result4.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1)", 4, 3}); - result4.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 5, 4}); - result4.add(new Object[]{"DOC_ID_SET", 6, 5}); - result4.add(new Object[]{"FILTER_OR", 7, 6}); - result4.add(new Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:noIndexCol1 > '1')", 8, 7}); - result4.add(new Object[]{"FILTER_EXPRESSION(operator:EQ,predicate:contains(textIndexCol1,'daff') = 'true')", 9, 7}); - result4.add(new Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:noIndexCol2 BETWEEN '2' AND '101')", 10, 7}); - result4.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result4.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 4, 3}); + result4.add(new Object[]{"DOC_ID_SET", 5, 4}); + result4.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 6, 5}); + result4.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result4.add(new Object[]{"SELECT(selectList:invertedIndexCol1, noIndexCol1)", 3, 2}); - result4.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1)", 4, 3}); - result4.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 5, 4}); - result4.add(new Object[]{"DOC_ID_SET", 6, 5}); - result4.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 7, 6}); + result4.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 4, 3}); + result4.add(new Object[]{"DOC_ID_SET", 5, 4}); + result4.add(new Object[]{"FILTER_OR", 6, 5}); + result4.add(new Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:noIndexCol1 > '1')", 7, 6}); + result4.add(new Object[]{"FILTER_EXPRESSION(operator:EQ,predicate:contains(textIndexCol1,'daff') = 'true')", 8, 6}); + result4.add(new Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:noIndexCol2 BETWEEN '2' AND '101')", 9, 6}); check(query4, new ResultTable(DATA_SCHEMA, result4)); // All segments match since noIndexCol4 has at least one row value set to 'true' across all segments @@ -842,13 +915,13 @@ public void testSelectColumnsUsingFilterVerbose() { List result5 = new ArrayList<>(); result5.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result5.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result5.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result5.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result5.add(new Object[]{"SELECT(selectList:invertedIndexCol1, noIndexCol1)", 3, 2}); - result5.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1)", 4, 3}); - result5.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 5, 4}); - result5.add(new Object[]{"DOC_ID_SET", 6, 5}); - result5.add(new Object[]{"FILTER_FULL_SCAN(operator:EQ,predicate:noIndexCol4 = 'true')", 7, 6}); + result5.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 4, 3}); + result5.add(new Object[]{"DOC_ID_SET", 5, 4}); + result5.add(new Object[]{"FILTER_FULL_SCAN(operator:EQ,predicate:noIndexCol4 = 'true')", 6, 5}); check(query5, new ResultTable(DATA_SCHEMA, result5)); // The FILTER_AND is part of the query plan for all segments as the first predicate is an expression and the second @@ -858,16 +931,17 @@ public void testSelectColumnsUsingFilterVerbose() { List result6 = new ArrayList<>(); result6.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result6.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result6.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result6.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result6.add(new Object[]{"SELECT(selectList:invertedIndexCol1, noIndexCol1)", 3, 2}); - result6.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1)", 4, 3}); - result6.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 5, 4}); - result6.add(new Object[]{"DOC_ID_SET", 6, 5}); - result6.add(new Object[]{"FILTER_AND", 7, 6}); - result6.add(new Object[]{"FILTER_FULL_SCAN(operator:EQ,predicate:noIndexCol4 = 'true')", 8, 7}); - result6.add(new Object[]{"FILTER_EXPRESSION(operator:EQ,predicate:startswith(textIndexCol1,'daff') = 'true')", 9, - 7}); + result6.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 4, 3}); + result6.add(new Object[]{"DOC_ID_SET", 5, 4}); + result6.add(new Object[]{"FILTER_AND", 6, 5}); + result6.add(new Object[]{"FILTER_FULL_SCAN(operator:EQ,predicate:noIndexCol4 = 'true')", 7, 6}); + result6.add(new Object[]{ + "FILTER_EXPRESSION(operator:EQ,predicate:startswith(textIndexCol1,'daff') = 'true')", 8, 6 + }); check(query6, new ResultTable(DATA_SCHEMA, result6)); } @@ -883,17 +957,19 @@ public void testSelectColumnsUsingFilterOnInvertedIndexColumn() { List result1 = new ArrayList<>(); result1.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result1.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result1.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1, sortedIndexCol1)", 3, 2}); - result1.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1, sortedIndexCol1)", 4, 3}); - result1.add(new Object[]{"PROJECT(sortedIndexCol1, invertedIndexCol1, noIndexCol1)", 5, 4}); - result1.add(new Object[]{"DOC_ID_SET", 6, 5}); - result1.add(new Object[]{"FILTER_AND", 7, 6}); + result1.add(new Object[]{"PROJECT(sortedIndexCol1, invertedIndexCol1, noIndexCol1)", 4, 3}); + result1.add(new Object[]{"DOC_ID_SET", 5, 4}); + result1.add(new Object[]{"FILTER_AND", 6, 5}); + result1.add(new Object[]{ + "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:sortedIndexCol1 = '100.1')", 7, 6 + }); result1.add(new Object[]{ - "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:sortedIndexCol1 = " + "'100.1')", 8, 7}); - result1.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ," - + "predicate:invertedIndexCol1 = '1.1')", 9, 7}); + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1 = '1.1')", 8, 6 + }); check(query1, new ResultTable(DATA_SCHEMA, result1)); // Segments 1, 2, 4 result in a FILTER_OR plan which matches all the segments and all four predicates. @@ -906,21 +982,24 @@ public void testSelectColumnsUsingFilterOnInvertedIndexColumn() { List result2 = new ArrayList<>(); result2.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result2.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result2.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result2.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1, sortedIndexCol1)", 3, 2}); - result2.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1, sortedIndexCol1)", 4, 3}); - result2.add(new Object[]{"PROJECT(sortedIndexCol1, invertedIndexCol1, noIndexCol1)", 5, 4}); - result2.add(new Object[]{"DOC_ID_SET", 6, 5}); - result2.add(new Object[]{"FILTER_OR", 7, 6}); - result2.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ," - + "predicate:invertedIndexCol1 = '1.1')", 8, 7}); + result2.add(new Object[]{"PROJECT(sortedIndexCol1, invertedIndexCol1, noIndexCol1)", 4, 3}); + result2.add(new Object[]{"DOC_ID_SET", 5, 4}); + result2.add(new Object[]{"FILTER_OR", 6, 5}); + result2.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1 = '1.1')", 7, 6 + }); result2.add(new Object[]{ - "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:sortedIndexCol1 = " + "'100.2')", 9, 7}); - result2 - .add(new Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:invertedIndexCol1 BETWEEN '0.2' AND '5')", 10, 7}); + "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:sortedIndexCol1 = '100.2')", 8, 6 + }); + result2.add( + new Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:invertedIndexCol1 BETWEEN '0.2' AND '5')", 9, 6}); result2.add(new Object[]{ - "FILTER_RANGE_INDEX(indexLookUp:range_index,operator:RANGE,predicate:rangeIndexCol1 > " + "'20')", 11, 7}); + "FILTER_RANGE_INDEX(indexLookUp:range_index,operator:RANGE,predicate:rangeIndexCol1 > '20')", 10, 6 + }); check(query2, new ResultTable(DATA_SCHEMA, result2)); // Segments 2, 3, 4 result in a MatchAllOperator because 'invertedIndexCol3 NOT IN ('foo', 'mickey')' matches all @@ -933,17 +1012,21 @@ public void testSelectColumnsUsingFilterOnInvertedIndexColumn() { List result3 = new ArrayList<>(); result3.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result3.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result3.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result3.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result3.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1)", 3, 2}); - result3.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1)", 4, 3}); - result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 5, 4}); - result3.add(new Object[]{"DOC_ID_SET", 6, 5}); - result3.add(new Object[]{"FILTER_OR", 7, 6}); - result3.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:IN," - + "predicate:invertedIndexCol2 IN ('1','2','30'))", 8, 7}); - result3.add(new Object[]{"FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:NOT_IN," - + "predicate:invertedIndexCol3 NOT IN ('foo','mickey'))", 9, 7}); + result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 4, 3}); + result3.add(new Object[]{"DOC_ID_SET", 5, 4}); + result3.add(new Object[]{"FILTER_OR", 6, 5}); + result3.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:IN," + + "predicate:invertedIndexCol2 IN ('1','2','30'))", 7, 6 + }); + result3.add(new Object[]{ + "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:NOT_IN,predicate:invertedIndexCol3 NOT IN " + + "('foo','mickey'))", 8, 6 + }); check(query3, new ResultTable(DATA_SCHEMA, result3)); } @@ -958,24 +1041,26 @@ public void testSelectColumnsUsingFilterOnInvertedIndexColumnVerbose() { List result1 = new ArrayList<>(); result1.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result1.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result1.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1, sortedIndexCol1)", 3, 2}); - result1.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1, sortedIndexCol1)", 4, 3}); - result1.add(new Object[]{"PROJECT(sortedIndexCol1, invertedIndexCol1, noIndexCol1)", 5, 4}); - result1.add(new Object[]{"DOC_ID_SET", 6, 5}); - result1.add(new Object[]{"FILTER_AND", 7, 6}); + result1.add(new Object[]{"PROJECT(sortedIndexCol1, invertedIndexCol1, noIndexCol1)", 4, 3}); + result1.add(new Object[]{"DOC_ID_SET", 5, 4}); + result1.add(new Object[]{"FILTER_EMPTY", 6, 5}); result1.add(new Object[]{ - "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:sortedIndexCol1 = " + "'100.1')", 8, 7}); - result1.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ," - + "predicate:invertedIndexCol1 = '1.1')", 9, 7}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + "PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result1.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1, sortedIndexCol1)", 3, 2}); - result1.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1, sortedIndexCol1)", 4, 3}); - result1.add(new Object[]{"PROJECT(sortedIndexCol1, invertedIndexCol1, noIndexCol1)", 5, 4}); - result1.add(new Object[]{"DOC_ID_SET", 6, 5}); - result1.add(new Object[]{"FILTER_EMPTY", 7, 6}); + result1.add(new Object[]{"PROJECT(sortedIndexCol1, invertedIndexCol1, noIndexCol1)", 4, 3}); + result1.add(new Object[]{"DOC_ID_SET", 5, 4}); + result1.add(new Object[]{"FILTER_AND", 6, 5}); + result1.add(new Object[]{ + "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:sortedIndexCol1 = '100.1')", 7, 6 + }); + result1.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1 = '1.1')", 8, 6 + }); check(query1, new ResultTable(DATA_SCHEMA, result1)); // Segments 1, 2, 4 result in a FILTER_OR plan which matches all the segments and all four predicates. @@ -987,35 +1072,40 @@ public void testSelectColumnsUsingFilterOnInvertedIndexColumnVerbose() { List result2 = new ArrayList<>(); result2.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result2.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result2.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result2.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1, sortedIndexCol1)", 3, 2}); - result2.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1, sortedIndexCol1)", 4, 3}); - result2.add(new Object[]{"PROJECT(sortedIndexCol1, invertedIndexCol1, noIndexCol1)", 5, 4}); - result2.add(new Object[]{"DOC_ID_SET", 6, 5}); - result2.add(new Object[]{"FILTER_OR", 7, 6}); - result2.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ," - + "predicate:invertedIndexCol1 = '1.1')", 8, 7}); + result2.add(new Object[]{"PROJECT(sortedIndexCol1, invertedIndexCol1, noIndexCol1)", 4, 3}); + result2.add(new Object[]{"DOC_ID_SET", 5, 4}); + result2.add(new Object[]{"FILTER_OR", 6, 5}); result2.add(new Object[]{ - "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:sortedIndexCol1 = " + "'100.2')", 9, 7}); - result2 - .add(new Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:invertedIndexCol1 BETWEEN '0.2' AND '5')", 10, 7}); + "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:sortedIndexCol1 = '100.2')", 7, 6 + }); + result2.add(new Object[]{ + "FILTER_FULL_SCAN(operator:RANGE,predicate:invertedIndexCol1 BETWEEN '0.2' AND '5')", 8, 6 + }); + result2.add(new Object[]{ + "FILTER_RANGE_INDEX(indexLookUp:range_index,operator:RANGE,predicate:rangeIndexCol1 > '20')", 9, 6 + }); result2.add(new Object[]{ - "FILTER_RANGE_INDEX(indexLookUp:range_index,operator:RANGE,predicate:rangeIndexCol1 > " + "'20')", 11, 7}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + "PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result2.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1, sortedIndexCol1)", 3, 2}); - result2.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1, sortedIndexCol1)", 4, 3}); - result2.add(new Object[]{"PROJECT(sortedIndexCol1, invertedIndexCol1, noIndexCol1)", 5, 4}); - result2.add(new Object[]{"DOC_ID_SET", 6, 5}); - result2.add(new Object[]{"FILTER_OR", 7, 6}); - result2.add(new Object[]{"FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:sortedIndexCol1 " - + "= '100.2')", 8, 7}); + result2.add(new Object[]{"PROJECT(sortedIndexCol1, invertedIndexCol1, noIndexCol1)", 4, 3}); + result2.add(new Object[]{"DOC_ID_SET", 5, 4}); + result2.add(new Object[]{"FILTER_OR", 6, 5}); + result2.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1 = '1.1')", 7, 6 + }); + result2.add(new Object[]{ + "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:sortedIndexCol1 = '100.2')", 8, 6 + }); + result2.add( + new Object[]{"FILTER_FULL_SCAN(operator:RANGE,predicate:invertedIndexCol1 BETWEEN '0.2' AND '5')", 9, 6}); result2.add(new Object[]{ - "FILTER_FULL_SCAN(operator:RANGE,predicate:invertedIndexCol1 BETWEEN '0.2' AND '5')", 9, 7}); - result2 - .add(new Object[]{"FILTER_RANGE_INDEX(indexLookUp:range_index,operator:RANGE,predicate:rangeIndexCol1 > " - + "'20')", 10, 7}); + "FILTER_RANGE_INDEX(indexLookUp:range_index,operator:RANGE,predicate:rangeIndexCol1 > '20')", 10, 6 + }); check(query2, new ResultTable(DATA_SCHEMA, result2)); // Segments 2, 3, 4 result in a MatchAllOperator because 'invertedIndexCol3 NOT IN ('foo', 'mickey')' matches all @@ -1028,24 +1118,28 @@ public void testSelectColumnsUsingFilterOnInvertedIndexColumnVerbose() { List result3 = new ArrayList<>(); result3.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result3.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result3.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result3.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result3.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1)", 3, 2}); - result3.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1)", 4, 3}); - result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 5, 4}); - result3.add(new Object[]{"DOC_ID_SET", 6, 5}); - result3.add(new Object[]{"FILTER_OR", 7, 6}); - result3.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:IN," - + "predicate:invertedIndexCol2 IN ('1','2','30'))", 8, 7}); - result3.add(new Object[]{"FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:NOT_IN," - + "predicate:invertedIndexCol3 NOT IN ('foo','mickey'))", 9, 7}); - result3.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 4, 3}); + result3.add(new Object[]{"DOC_ID_SET", 5, 4}); + result3.add(new Object[]{"FILTER_OR", 6, 5}); + result3.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:IN," + + "predicate:invertedIndexCol2 IN ('1','2','30'))", 7, 6 + }); + result3.add(new Object[]{ + "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:NOT_IN,predicate:invertedIndexCol3 NOT IN " + + "('foo','mickey'))", 8, 6 + }); + result3.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result3.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1)", 3, 2}); - result3.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1)", 4, 3}); - result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 5, 4}); - result3.add(new Object[]{"DOC_ID_SET", 6, 5}); - result3.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 7, 6}); + result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 4, 3}); + result3.add(new Object[]{"DOC_ID_SET", 5, 4}); + result3.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 6, 5}); check(query3, new ResultTable(DATA_SCHEMA, result3)); } @@ -1060,20 +1154,24 @@ public void testSelectColumnUsingFilterOnRangeIndexColumn() { List result1 = new ArrayList<>(); result1.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result1.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result1.add(new Object[]{"SELECT(selectList:invertedIndexCol1, noIndexCol1, rangeIndexCol1)", 3, 2}); - result1.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1, rangeIndexCol1)", 4, 3}); - result1.add(new Object[]{"PROJECT(rangeIndexCol1, invertedIndexCol1, noIndexCol1)", 5, 4}); - result1.add(new Object[]{"DOC_ID_SET", 6, 5}); - result1.add(new Object[]{"FILTER_OR", 7, 6}); - result1.add(new Object[]{"FILTER_AND", 8, 7}); + result1.add(new Object[]{"PROJECT(rangeIndexCol1, invertedIndexCol1, noIndexCol1)", 4, 3}); + result1.add(new Object[]{"DOC_ID_SET", 5, 4}); + result1.add(new Object[]{"FILTER_OR", 6, 5}); + result1.add(new Object[]{"FILTER_AND", 7, 6}); + result1.add(new Object[]{ + "FILTER_RANGE_INDEX(indexLookUp:range_index,operator:RANGE,predicate:rangeIndexCol1 > '10.1')", 8, 7 + }); result1.add(new Object[]{ - "FILTER_RANGE_INDEX(indexLookUp:range_index,operator:RANGE,predicate:rangeIndexCol1 > '10" + ".1')", 9, 8}); + "FILTER_RANGE_INDEX(indexLookUp:range_index,operator:RANGE,predicate:rangeIndexCol2 >= '15')", 9, 7 + }); result1.add(new Object[]{ - "FILTER_RANGE_INDEX(indexLookUp:range_index,operator:RANGE,predicate:rangeIndexCol2 >= " + "'15')", 10, 8}); - result1.add(new Object[]{"FILTER_RANGE_INDEX(indexLookUp:range_index,operator:RANGE,predicate:rangeIndexCol3 " - + "BETWEEN '21' AND '45')", 11, 7}); + "FILTER_RANGE_INDEX(indexLookUp:range_index,operator:RANGE,predicate:rangeIndexCol3 " + + "BETWEEN '21' AND '45')", 10, 6 + }); check(query1, new ResultTable(DATA_SCHEMA, result1)); } @@ -1089,20 +1187,24 @@ public void testSelectColumnUsingFilterOnRangeIndexColumnVerbose() { List result1 = new ArrayList<>(); result1.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result1.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result1.add(new Object[]{"SELECT(selectList:invertedIndexCol1, noIndexCol1, rangeIndexCol1)", 3, 2}); - result1.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1, rangeIndexCol1)", 4, 3}); - result1.add(new Object[]{"PROJECT(rangeIndexCol1, invertedIndexCol1, noIndexCol1)", 5, 4}); - result1.add(new Object[]{"DOC_ID_SET", 6, 5}); - result1.add(new Object[]{"FILTER_OR", 7, 6}); - result1.add(new Object[]{"FILTER_AND", 8, 7}); + result1.add(new Object[]{"PROJECT(rangeIndexCol1, invertedIndexCol1, noIndexCol1)", 4, 3}); + result1.add(new Object[]{"DOC_ID_SET", 5, 4}); + result1.add(new Object[]{"FILTER_OR", 6, 5}); + result1.add(new Object[]{"FILTER_AND", 7, 6}); result1.add(new Object[]{ - "FILTER_RANGE_INDEX(indexLookUp:range_index,operator:RANGE,predicate:rangeIndexCol1 > '10" + ".1')", 9, 8}); + "FILTER_RANGE_INDEX(indexLookUp:range_index,operator:RANGE,predicate:rangeIndexCol1 > '10.1')", 8, 7 + }); + result1.add(new Object[]{ + "FILTER_RANGE_INDEX(indexLookUp:range_index,operator:RANGE,predicate:rangeIndexCol2 >= '15')", 9, 7 + }); result1.add(new Object[]{ - "FILTER_RANGE_INDEX(indexLookUp:range_index,operator:RANGE,predicate:rangeIndexCol2 >= " + "'15')", 10, 8}); - result1.add(new Object[]{"FILTER_RANGE_INDEX(indexLookUp:range_index,operator:RANGE,predicate:rangeIndexCol3 " - + "BETWEEN '21' AND '45')", 11, 7}); + "FILTER_RANGE_INDEX(indexLookUp:range_index,operator:RANGE,predicate:rangeIndexCol3 " + + "BETWEEN '21' AND '45')", 10, 6 + }); check(query1, new ResultTable(DATA_SCHEMA, result1)); } @@ -1121,11 +1223,10 @@ public void testSelectAggregateUsingFilterOnTextIndexColumn() { result1.add(new Object[]{ "GROUP_BY(groupKeys:noIndexCol1, noIndexCol2, aggregations:max(noIndexCol2), min(noIndexCol3))", 3, 2 }); - result1.add(new Object[]{"TRANSFORM_PASSTHROUGH(noIndexCol1, noIndexCol2, noIndexCol3)", 4, 3}); - result1.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 5, 4}); - result1.add(new Object[]{"DOC_ID_SET", 6, 5}); + result1.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 4, 3}); + result1.add(new Object[]{"DOC_ID_SET", 5, 4}); result1.add(new Object[]{ - "FILTER_TEXT_INDEX(indexLookUp:text_index,operator:TEXT_MATCH,predicate:text_match(textIndexCol1,'foo'))", 7, 6 + "FILTER_TEXT_INDEX(indexLookUp:text_index,operator:TEXT_MATCH,predicate:text_match(textIndexCol1,'foo'))", 6, 5 }); check(query1, new ResultTable(DATA_SCHEMA, result1)); @@ -1142,11 +1243,10 @@ public void testSelectAggregateUsingFilterOnTextIndexColumn() { result2.add(new Object[]{ "GROUP_BY(groupKeys:noIndexCol1, noIndexCol2, aggregations:max(noIndexCol2), min(noIndexCol3))", 3, 2 }); - result2.add(new Object[]{"TRANSFORM_PASSTHROUGH(noIndexCol1, noIndexCol2, noIndexCol3)", 4, 3}); - result2.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 5, 4}); - result2.add(new Object[]{"DOC_ID_SET", 6, 5}); + result2.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 4, 3}); + result2.add(new Object[]{"DOC_ID_SET", 5, 4}); result2.add(new Object[]{ - "FILTER_TEXT_INDEX(indexLookUp:text_index,operator:TEXT_MATCH,predicate:text_match(textIndexCol1,'foo'))", 7, 6 + "FILTER_TEXT_INDEX(indexLookUp:text_index,operator:TEXT_MATCH,predicate:text_match(textIndexCol1,'foo'))", 6, 5 }); check(query2, new ResultTable(DATA_SCHEMA, result2)); } @@ -1154,42 +1254,42 @@ public void testSelectAggregateUsingFilterOnTextIndexColumn() { @Test public void testSelectAggregateUsingFilterOnTextIndexColumnVerbose() { // All segments match the same plan for these queries - String query1 = - "SET explainPlanVerbose=true; EXPLAIN PLAN FOR SELECT noIndexCol1, noIndexCol2, max(noIndexCol2), " - + "min(noIndexCol3) FROM testTable WHERE TEXT_MATCH(textIndexCol1, 'foo') GROUP BY noIndexCol1, " - + "noIndexCol2"; + String query1 = "SET explainPlanVerbose=true; EXPLAIN PLAN FOR SELECT noIndexCol1, noIndexCol2, max(noIndexCol2), " + + "min(noIndexCol3) FROM testTable WHERE TEXT_MATCH(textIndexCol1, 'foo') GROUP BY noIndexCol1, " + + "noIndexCol2"; List result1 = new ArrayList<>(); result1.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result1.add(new Object[]{"COMBINE_GROUP_BY", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); result1.add(new Object[]{ - "GROUP_BY(groupKeys:noIndexCol1, noIndexCol2, aggregations:max(noIndexCol2), min" - + "(noIndexCol3))", 3, 2}); - result1.add(new Object[]{"TRANSFORM_PASSTHROUGH(noIndexCol1, noIndexCol2, noIndexCol3)", 4, 3}); - result1.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 5, 4}); - result1.add(new Object[]{"DOC_ID_SET", 6, 5}); - result1.add(new Object[]{"FILTER_TEXT_INDEX(indexLookUp:text_index,operator:TEXT_MATCH,predicate:text_match" - + "(textIndexCol1,'foo'))", 7, 6}); + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); + result1.add(new Object[]{ + "GROUP_BY(groupKeys:noIndexCol1, noIndexCol2, aggregations:max(noIndexCol2), min(noIndexCol3))", 3, 2 + }); + result1.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 4, 3}); + result1.add(new Object[]{"DOC_ID_SET", 5, 4}); + result1.add(new Object[]{ + "FILTER_TEXT_INDEX(indexLookUp:text_index,operator:TEXT_MATCH,predicate:text_match(textIndexCol1,'foo'))", 6, 5 + }); check(query1, new ResultTable(DATA_SCHEMA, result1)); - String query2 = - "SET explainPlanVerbose=true; EXPLAIN PLAN FOR SELECT noIndexCol1, max(noIndexCol2) AS mymax, " - + "min(noIndexCol3) AS mymin FROM testTable WHERE TEXT_MATCH (textIndexCol1, 'foo') GROUP BY " - + "noIndexCol1, noIndexCol2 ORDER BY noIndexCol1, max(noIndexCol2)"; + String query2 = "SET explainPlanVerbose=true; EXPLAIN PLAN FOR SELECT noIndexCol1, max(noIndexCol2) AS mymax, " + + "min(noIndexCol3) AS mymin FROM testTable WHERE TEXT_MATCH (textIndexCol1, 'foo') GROUP BY " + + "noIndexCol1, noIndexCol2 ORDER BY noIndexCol1, max(noIndexCol2)"; List result2 = new ArrayList<>(); result2.add(new Object[]{"BROKER_REDUCE(sort:[noIndexCol1 ASC, max(noIndexCol2) ASC],limit:10)", 1, 0}); result2.add(new Object[]{"COMBINE_GROUP_BY", 2, 1}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); result2.add(new Object[]{ - "GROUP_BY(groupKeys:noIndexCol1, noIndexCol2, aggregations:max(noIndexCol2), min" - + "(noIndexCol3))", 3, 2}); - result2.add(new Object[]{"TRANSFORM_PASSTHROUGH(noIndexCol1, noIndexCol2, noIndexCol3)", 4, 3}); - result2.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 5, 4}); - result2.add(new Object[]{"DOC_ID_SET", 6, 5}); - result2.add(new Object[]{"FILTER_TEXT_INDEX(indexLookUp:text_index,operator:TEXT_MATCH,predicate:text_match" - + "(textIndexCol1,'foo'))", 7, 6}); + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); + result2.add(new Object[]{ + "GROUP_BY(groupKeys:noIndexCol1, noIndexCol2, aggregations:max(noIndexCol2), min(noIndexCol3))", 3, 2 + }); + result2.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 4, 3}); + result2.add(new Object[]{"DOC_ID_SET", 5, 4}); + result2.add(new Object[]{ + "FILTER_TEXT_INDEX(indexLookUp:text_index,operator:TEXT_MATCH,predicate:text_match(textIndexCol1,'foo'))", 6, 5 + }); check(query2, new ResultTable(DATA_SCHEMA, result2)); } @@ -1205,18 +1305,21 @@ public void testSelectColumnUsingFilterOnJsonIndexColumn() { List result = new ArrayList<>(); result.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1)", 3, 2}); - result.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1)", 4, 3}); - result.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 5, 4}); - result.add(new Object[]{"DOC_ID_SET", 6, 5}); - result.add(new Object[]{"FILTER_AND", 7, 6}); - result.add(new Object[]{"FILTER_JSON_INDEX(indexLookUp:json_index,operator:JSON_MATCH,predicate:json_match" - + "(jsonIndexCol1,'key=1'))", 8, 7}); - result.add(new Object[]{"FILTER_TEXT_INDEX(indexLookUp:text_index,operator:TEXT_MATCH,predicate:text_match" - + "(textIndexCol1,'foo'))", 9, 7}); - result.add(new Object[]{"FILTER_FULL_SCAN(operator:NOT_IN,predicate:noIndexCol1 NOT IN ('1','20','30'))", 10, 7}); + result.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 4, 3}); + result.add(new Object[]{"DOC_ID_SET", 5, 4}); + result.add(new Object[]{"FILTER_AND", 6, 5}); + result.add(new Object[]{ + "FILTER_JSON_INDEX(indexLookUp:json_index,operator:JSON_MATCH,predicate:json_match(jsonIndexCol1,'key=1'))", + 7, 6 + }); + result.add(new Object[]{ + "FILTER_TEXT_INDEX(indexLookUp:text_index,operator:TEXT_MATCH,predicate:text_match(textIndexCol1,'foo'))", 8, 6 + }); + result.add(new Object[]{"FILTER_FULL_SCAN(operator:NOT_IN,predicate:noIndexCol1 NOT IN ('1','20','30'))", 9, 6}); check(query, new ResultTable(DATA_SCHEMA, result)); } @@ -1224,37 +1327,42 @@ public void testSelectColumnUsingFilterOnJsonIndexColumn() { public void testSelectColumnUsingFilterOnJsonIndexColumnVerbose() { // Segment 2, 3, 4 don't match 'noIndexCol1 NOT IN (1, 20, 30)' so they return a plan without this OR predicate // Segments 1 matches all three predicates so returns a plan with all three - String query = - "SET explainPlanVerbose=true; EXPLAIN PLAN FOR SELECT noIndexCol1, invertedIndexCol1 FROM testTable " - + "WHERE (invertedIndexCol1 IN (10, 20, 30) AND sortedIndexCol1 != 100) OR (noIndexCol1 NOT IN (1, 20, " - + "30) AND rangeIndexCol1 != 20 AND JSON_MATCH(jsonIndexCol1, 'key=1') AND TEXT_MATCH(textIndexCol1, " - + "'foo'))"; + String query = "SET explainPlanVerbose=true; EXPLAIN PLAN FOR SELECT noIndexCol1, invertedIndexCol1 FROM testTable " + + "WHERE (invertedIndexCol1 IN (10, 20, 30) AND sortedIndexCol1 != 100) OR (noIndexCol1 NOT IN (1, 20, " + + "30) AND rangeIndexCol1 != 20 AND JSON_MATCH(jsonIndexCol1, 'key=1') AND TEXT_MATCH(textIndexCol1, " + + "'foo'))"; List result = new ArrayList<>(); result.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1)", 3, 2}); - result.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1)", 4, 3}); - result.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 5, 4}); - result.add(new Object[]{"DOC_ID_SET", 6, 5}); - result.add(new Object[]{"FILTER_AND", 7, 6}); - result.add(new Object[]{"FILTER_JSON_INDEX(indexLookUp:json_index,operator:JSON_MATCH,predicate:json_match" - + "(jsonIndexCol1,'key=1'))", 8, 7}); - result.add(new Object[]{"FILTER_TEXT_INDEX(indexLookUp:text_index,operator:TEXT_MATCH,predicate:text_match" - + "(textIndexCol1,'foo'))", 9, 7}); - result.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 4, 3}); + result.add(new Object[]{"DOC_ID_SET", 5, 4}); + result.add(new Object[]{"FILTER_AND", 6, 5}); + result.add(new Object[]{ + "FILTER_JSON_INDEX(indexLookUp:json_index,operator:JSON_MATCH,predicate:json_match(jsonIndexCol1,'key=1'))", + 7, 6 + }); + result.add(new Object[]{ + "FILTER_TEXT_INDEX(indexLookUp:text_index,operator:TEXT_MATCH,predicate:text_match(textIndexCol1,'foo'))", 8, 6 + }); + result.add(new Object[]{"FILTER_FULL_SCAN(operator:NOT_IN,predicate:noIndexCol1 NOT IN ('1','20','30'))", 9, 6}); + result.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1)", 3, 2}); - result.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, noIndexCol1)", 4, 3}); - result.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 5, 4}); - result.add(new Object[]{"DOC_ID_SET", 6, 5}); - result.add(new Object[]{"FILTER_AND", 7, 6}); - result.add(new Object[]{"FILTER_JSON_INDEX(indexLookUp:json_index,operator:JSON_MATCH,predicate:json_match" - + "(jsonIndexCol1,'key=1'))", 8, 7}); - result.add(new Object[]{"FILTER_TEXT_INDEX(indexLookUp:text_index,operator:TEXT_MATCH,predicate:text_match" - + "(textIndexCol1,'foo'))", 9, 7}); - result.add(new Object[]{"FILTER_FULL_SCAN(operator:NOT_IN,predicate:noIndexCol1 NOT IN ('1','20','30'))", 10, 7}); + result.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1)", 4, 3}); + result.add(new Object[]{"DOC_ID_SET", 5, 4}); + result.add(new Object[]{"FILTER_AND", 6, 5}); + result.add(new Object[]{ + "FILTER_JSON_INDEX(indexLookUp:json_index,operator:JSON_MATCH,predicate:json_match(jsonIndexCol1,'key=1'))", + 7, 6 + }); + result.add(new Object[]{ + "FILTER_TEXT_INDEX(indexLookUp:text_index,operator:TEXT_MATCH,predicate:text_match(textIndexCol1,'foo'))", 8, 6 + }); check(query, new ResultTable(DATA_SCHEMA, result)); } @@ -1269,14 +1377,15 @@ public void testSelectColumnsVariationsOfOrOperators() { List result1 = new ArrayList<>(); result1.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result1.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result1.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1, invertedIndexCol3)", 3, 2}); - result1.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, invertedIndexCol3, noIndexCol1)", 4, 3}); - result1.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 5, 4}); - result1.add(new Object[]{"DOC_ID_SET", 6, 5}); - result1.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1" - + " = '1.5')", 7, 6}); + result1.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 4, 3}); + result1.add(new Object[]{"DOC_ID_SET", 5, 4}); + result1.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1 = '1.5')", 6, 5 + }); check(query1, new ResultTable(DATA_SCHEMA, result1)); // Segment 1 matches both OR predicates so returns a FILTER_OR tree @@ -1288,17 +1397,19 @@ public void testSelectColumnsVariationsOfOrOperators() { List result2 = new ArrayList<>(); result2.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result2.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result2.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result2.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1, invertedIndexCol3)", 3, 2}); - result2.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, invertedIndexCol3, noIndexCol1)", 4, 3}); - result2.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 5, 4}); - result2.add(new Object[]{"DOC_ID_SET", 6, 5}); - result2.add(new Object[]{"FILTER_OR", 7, 6}); - result2.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1" - + " = '1.1')", 8, 7}); - result2.add(new Object[]{"FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:invertedIndexCol3" - + " = 'mickey')", 9, 7}); + result2.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 4, 3}); + result2.add(new Object[]{"DOC_ID_SET", 5, 4}); + result2.add(new Object[]{"FILTER_OR", 6, 5}); + result2.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1 = '1.1')", 7, 6 + }); + result2.add(new Object[]{ + "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:invertedIndexCol3 = 'mickey')", 8, 6 + }); check(query2, new ResultTable(DATA_SCHEMA, result2)); // An OR query that matches all predicates on all segments @@ -1307,17 +1418,19 @@ public void testSelectColumnsVariationsOfOrOperators() { List result3 = new ArrayList<>(); result3.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result3.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result3.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result3.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result3.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1, invertedIndexCol2)", 3, 2}); - result3.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, invertedIndexCol2, noIndexCol1)", 4, 3}); - result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1, invertedIndexCol2)", 5, 4}); - result3.add(new Object[]{"DOC_ID_SET", 6, 5}); - result3.add(new Object[]{"FILTER_OR", 7, 6}); - result3.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1" - + " = '0.1')", 8, 7}); - result3.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol2" - + " = '2')", 9, 7}); + result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1, invertedIndexCol2)", 4, 3}); + result3.add(new Object[]{"DOC_ID_SET", 5, 4}); + result3.add(new Object[]{"FILTER_OR", 6, 5}); + result3.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1 = '0.1')", 7, 6 + }); + result3.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol2 = '2')", 8, 6 + }); check(query3, new ResultTable(DATA_SCHEMA, result3)); // Segment 2 matches all on the 'pluto' predicate @@ -1329,13 +1442,13 @@ public void testSelectColumnsVariationsOfOrOperators() { List result4 = new ArrayList<>(); result4.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result4.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result4.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result4.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result4.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol3)", 3, 2}); - result4.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol3, noIndexCol1)", 4, 3}); - result4.add(new Object[]{"PROJECT(invertedIndexCol3, noIndexCol1)", 5, 4}); - result4.add(new Object[]{"DOC_ID_SET", 6, 5}); - result4.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 7, 6}); + result4.add(new Object[]{"PROJECT(invertedIndexCol3, noIndexCol1)", 4, 3}); + result4.add(new Object[]{"DOC_ID_SET", 5, 4}); + result4.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 6, 5}); check(query4, new ResultTable(DATA_SCHEMA, result4)); } @@ -1349,28 +1462,29 @@ public void testSelectColumnsVariationsOfOrOperatorsVerbose() { List result1 = new ArrayList<>(); result1.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result1.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result1.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1, invertedIndexCol3)", 3, 2}); - result1.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, invertedIndexCol3, noIndexCol1)", 4, 3}); - result1.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 5, 4}); - result1.add(new Object[]{"DOC_ID_SET", 6, 5}); - result1.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1" - + " = '1.5')", 7, 6}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result1.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 4, 3}); + result1.add(new Object[]{"DOC_ID_SET", 5, 4}); + result1.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1 = '1.5')", 6, 5 + }); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result1.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1, invertedIndexCol3)", 3, 2}); - result1.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, invertedIndexCol3, noIndexCol1)", 4, 3}); - result1.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 5, 4}); - result1.add(new Object[]{"DOC_ID_SET", 6, 5}); - result1.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 7, 6}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:2)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result1.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 4, 3}); + result1.add(new Object[]{"DOC_ID_SET", 5, 4}); + result1.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 6, 5}); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:2)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result1.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1, invertedIndexCol3)", 3, 2}); - result1.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, invertedIndexCol3, noIndexCol1)", 4, 3}); - result1.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 5, 4}); - result1.add(new Object[]{"DOC_ID_SET", 6, 5}); - result1.add(new Object[]{"FILTER_EMPTY", 7, 6}); + result1.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 4, 3}); + result1.add(new Object[]{"DOC_ID_SET", 5, 4}); + result1.add(new Object[]{"FILTER_EMPTY", 6, 5}); check(query1, new ResultTable(DATA_SCHEMA, result1)); // Segment 1 matches both OR predicates so returns a FILTER_OR tree @@ -1381,32 +1495,35 @@ public void testSelectColumnsVariationsOfOrOperatorsVerbose() { List result2 = new ArrayList<>(); result2.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result2.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:2)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result2.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result2.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1, invertedIndexCol3)", 3, 2}); - result2.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, invertedIndexCol3, noIndexCol1)", 4, 3}); - result2.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 5, 4}); - result2.add(new Object[]{"DOC_ID_SET", 6, 5}); - result2.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1" - + " = '1.1')", 7, 6}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result2.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 4, 3}); + result2.add(new Object[]{"DOC_ID_SET", 5, 4}); + result2.add(new Object[]{"FILTER_OR", 6, 5}); + result2.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1 = '1.1')", 7, 6 + }); + result2.add(new Object[]{ + "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:invertedIndexCol3 = 'mickey')", 8, 6 + }); + result2.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:2)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result2.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1, invertedIndexCol3)", 3, 2}); - result2.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, invertedIndexCol3, noIndexCol1)", 4, 3}); - result2.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 5, 4}); - result2.add(new Object[]{"DOC_ID_SET", 6, 5}); - result2.add(new Object[]{"FILTER_OR", 7, 6}); - result2.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1" - + " = '1.1')", 8, 7}); - result2.add(new Object[]{"FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:invertedIndexCol3" - + " = 'mickey')", 9, 7}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result2.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 4, 3}); + result2.add(new Object[]{"DOC_ID_SET", 5, 4}); + result2.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1 = '1.1')", 6, 5 + }); + result2.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result2.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1, invertedIndexCol3)", 3, 2}); - result2.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, invertedIndexCol3, noIndexCol1)", 4, 3}); - result2.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 5, 4}); - result2.add(new Object[]{"DOC_ID_SET", 6, 5}); - result2.add(new Object[]{"FILTER_EMPTY", 7, 6}); + result2.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 4, 3}); + result2.add(new Object[]{"DOC_ID_SET", 5, 4}); + result2.add(new Object[]{"FILTER_EMPTY", 6, 5}); check(query2, new ResultTable(DATA_SCHEMA, result2)); // An OR query that matches all predicates on all segments @@ -1415,17 +1532,19 @@ public void testSelectColumnsVariationsOfOrOperatorsVerbose() { List result3 = new ArrayList<>(); result3.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result3.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result3.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result3.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result3.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1, invertedIndexCol2)", 3, 2}); - result3.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, invertedIndexCol2, noIndexCol1)", 4, 3}); - result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1, invertedIndexCol2)", 5, 4}); - result3.add(new Object[]{"DOC_ID_SET", 6, 5}); - result3.add(new Object[]{"FILTER_OR", 7, 6}); - result3.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1" - + " = '0.1')", 8, 7}); - result3.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol2" - + " = '2')", 9, 7}); + result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1, invertedIndexCol2)", 4, 3}); + result3.add(new Object[]{"DOC_ID_SET", 5, 4}); + result3.add(new Object[]{"FILTER_OR", 6, 5}); + result3.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1 = '0.1')", 7, 6 + }); + result3.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol2 = '2')", 8, 6 + }); check(query3, new ResultTable(DATA_SCHEMA, result3)); // Segment 2 matches all on the 'pluto' predicate @@ -1436,20 +1555,20 @@ public void testSelectColumnsVariationsOfOrOperatorsVerbose() { List result4 = new ArrayList<>(); result4.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result4.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result4.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result4.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result4.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol3)", 3, 2}); - result4.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol3, noIndexCol1)", 4, 3}); - result4.add(new Object[]{"PROJECT(invertedIndexCol3, noIndexCol1)", 5, 4}); - result4.add(new Object[]{"DOC_ID_SET", 6, 5}); - result4.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 7, 6}); - result4.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result4.add(new Object[]{"PROJECT(invertedIndexCol3, noIndexCol1)", 4, 3}); + result4.add(new Object[]{"DOC_ID_SET", 5, 4}); + result4.add(new Object[]{"FILTER_EMPTY", 6, 5}); + result4.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result4.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol3)", 3, 2}); - result4.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol3, noIndexCol1)", 4, 3}); - result4.add(new Object[]{"PROJECT(invertedIndexCol3, noIndexCol1)", 5, 4}); - result4.add(new Object[]{"DOC_ID_SET", 6, 5}); - result4.add(new Object[]{"FILTER_EMPTY", 7, 6}); + result4.add(new Object[]{"PROJECT(invertedIndexCol3, noIndexCol1)", 4, 3}); + result4.add(new Object[]{"DOC_ID_SET", 5, 4}); + result4.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 6, 5}); check(query4, new ResultTable(DATA_SCHEMA, result4)); } @@ -1464,13 +1583,13 @@ public void testSelectColumnsVariationsOfAndOperators() { List result1 = new ArrayList<>(); result1.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result1.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:2)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:2)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result1.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1, invertedIndexCol3)", 3, 2}); - result1.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, invertedIndexCol3, noIndexCol1)", 4, 3}); - result1.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 5, 4}); - result1.add(new Object[]{"DOC_ID_SET", 6, 5}); - result1.add(new Object[]{"FILTER_EMPTY", 7, 6}); + result1.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 4, 3}); + result1.add(new Object[]{"DOC_ID_SET", 5, 4}); + result1.add(new Object[]{"FILTER_EMPTY", 6, 5}); check(query1, new ResultTable(DATA_SCHEMA, result1)); // Segment 1 has a match for both predicates so it return a FILTER_AND plan @@ -1482,17 +1601,19 @@ public void testSelectColumnsVariationsOfAndOperators() { List result2 = new ArrayList<>(); result2.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result2.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result2.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result2.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1, invertedIndexCol3)", 3, 2}); - result2.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, invertedIndexCol3, noIndexCol1)", 4, 3}); - result2.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 5, 4}); - result2.add(new Object[]{"DOC_ID_SET", 6, 5}); - result2.add(new Object[]{"FILTER_AND", 7, 6}); - result2.add(new Object[]{"FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:invertedIndexCol3" - + " = 'mickey')", 8, 7}); - result2.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1" - + " = '1.1')", 9, 7}); + result2.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 4, 3}); + result2.add(new Object[]{"DOC_ID_SET", 5, 4}); + result2.add(new Object[]{"FILTER_AND", 6, 5}); + result2.add(new Object[]{ + "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:invertedIndexCol3 = 'mickey')", 7, 6 + }); + result2.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1 = '1.1')", 8, 6 + }); check(query2, new ResultTable(DATA_SCHEMA, result2)); // An AND query that matches all predicates on all segments @@ -1501,17 +1622,19 @@ public void testSelectColumnsVariationsOfAndOperators() { List result3 = new ArrayList<>(); result3.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result3.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result3.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result3.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result3.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1, invertedIndexCol2)", 3, 2}); - result3.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, invertedIndexCol2, noIndexCol1)", 4, 3}); - result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1, invertedIndexCol2)", 5, 4}); - result3.add(new Object[]{"DOC_ID_SET", 6, 5}); - result3.add(new Object[]{"FILTER_AND", 7, 6}); - result3.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1" - + " = '0.1')", 8, 7}); - result3.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol2" - + " = '1')", 9, 7}); + result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1, invertedIndexCol2)", 4, 3}); + result3.add(new Object[]{"DOC_ID_SET", 5, 4}); + result3.add(new Object[]{"FILTER_AND", 6, 5}); + result3.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1 = '0.1')", 7, 6 + }); + result3.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol2 = '1')", 8, 6 + }); check(query3, new ResultTable(DATA_SCHEMA, result3)); // Segment 2 matches all on the first predicate 'pluto' which is removed from the AND, and matches '8' for the @@ -1523,14 +1646,15 @@ public void testSelectColumnsVariationsOfAndOperators() { List result4 = new ArrayList<>(); result4.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result4.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result4.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result4.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result4.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol3)", 3, 2}); - result4.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol3, noIndexCol1)", 4, 3}); - result4.add(new Object[]{"PROJECT(invertedIndexCol3, noIndexCol1)", 5, 4}); - result4.add(new Object[]{"DOC_ID_SET", 6, 5}); - result4.add(new Object[]{"FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:noIndexCol1 = '8')", - 7, 6}); + result4.add(new Object[]{"PROJECT(invertedIndexCol3, noIndexCol1)", 4, 3}); + result4.add(new Object[]{"DOC_ID_SET", 5, 4}); + result4.add(new Object[]{ + "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:noIndexCol1 = '8')", 6, 5 + }); check(query4, new ResultTable(DATA_SCHEMA, result4)); } @@ -1544,13 +1668,13 @@ public void testSelectColumnsVariationsOfAndOperatorsVerbose() { List result1 = new ArrayList<>(); result1.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result1.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:2)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:2)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result1.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1, invertedIndexCol3)", 3, 2}); - result1.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, invertedIndexCol3, noIndexCol1)", 4, 3}); - result1.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 5, 4}); - result1.add(new Object[]{"DOC_ID_SET", 6, 5}); - result1.add(new Object[]{"FILTER_EMPTY", 7, 6}); + result1.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 4, 3}); + result1.add(new Object[]{"DOC_ID_SET", 5, 4}); + result1.add(new Object[]{"FILTER_EMPTY", 6, 5}); check(query1, new ResultTable(DATA_SCHEMA, result1)); // Segment 1 has a match for both predicates so it return a FILTER_AND plan @@ -1561,24 +1685,26 @@ public void testSelectColumnsVariationsOfAndOperatorsVerbose() { List result2 = new ArrayList<>(); result2.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result2.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result2.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result2.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1, invertedIndexCol3)", 3, 2}); - result2.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, invertedIndexCol3, noIndexCol1)", 4, 3}); - result2.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 5, 4}); - result2.add(new Object[]{"DOC_ID_SET", 6, 5}); - result2.add(new Object[]{"FILTER_AND", 7, 6}); - result2.add(new Object[]{"FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:invertedIndexCol3" - + " = 'mickey')", 8, 7}); - result2.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1" - + " = '1.1')", 9, 7}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:2)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result2.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 4, 3}); + result2.add(new Object[]{"DOC_ID_SET", 5, 4}); + result2.add(new Object[]{"FILTER_AND", 6, 5}); + result2.add(new Object[]{ + "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:invertedIndexCol3 = 'mickey')", 7, 6 + }); + result2.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1 = '1.1')", 8, 6 + }); + result2.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:2)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result2.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1, invertedIndexCol3)", 3, 2}); - result2.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, invertedIndexCol3, noIndexCol1)", 4, 3}); - result2.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 5, 4}); - result2.add(new Object[]{"DOC_ID_SET", 6, 5}); - result2.add(new Object[]{"FILTER_EMPTY", 7, 6}); + result2.add(new Object[]{"PROJECT(invertedIndexCol3, invertedIndexCol1, noIndexCol1)", 4, 3}); + result2.add(new Object[]{"DOC_ID_SET", 5, 4}); + result2.add(new Object[]{"FILTER_EMPTY", 6, 5}); check(query2, new ResultTable(DATA_SCHEMA, result2)); // An AND query that matches all predicates on all segments @@ -1587,17 +1713,19 @@ public void testSelectColumnsVariationsOfAndOperatorsVerbose() { List result3 = new ArrayList<>(); result3.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result3.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result3.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result3.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result3.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol1, invertedIndexCol2)", 3, 2}); - result3.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol1, invertedIndexCol2, noIndexCol1)", 4, 3}); - result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1, invertedIndexCol2)", 5, 4}); - result3.add(new Object[]{"DOC_ID_SET", 6, 5}); - result3.add(new Object[]{"FILTER_AND", 7, 6}); - result3.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1" - + " = '0.1')", 8, 7}); - result3.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol2" - + " = '1')", 9, 7}); + result3.add(new Object[]{"PROJECT(invertedIndexCol1, noIndexCol1, invertedIndexCol2)", 4, 3}); + result3.add(new Object[]{"DOC_ID_SET", 5, 4}); + result3.add(new Object[]{"FILTER_AND", 6, 5}); + result3.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1 = '0.1')", 7, 6 + }); + result3.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol2 = '1')", 8, 6 + }); check(query3, new ResultTable(DATA_SCHEMA, result3)); // Segment 2 matches all on the first predicate 'pluto' which is removed from the AND, and matches '8' for the @@ -1608,16 +1736,18 @@ public void testSelectColumnsVariationsOfAndOperatorsVerbose() { List result4 = new ArrayList<>(); result4.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result4.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result4.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result4.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result4.add(new Object[]{"SELECT(selectList:noIndexCol1, invertedIndexCol3)", 3, 2}); - result4.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol3, noIndexCol1)", 4, 3}); - result4.add(new Object[]{"PROJECT(invertedIndexCol3, noIndexCol1)", 5, 4}); - result4.add(new Object[]{"DOC_ID_SET", 6, 5}); - result4.add(new Object[]{"FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:noIndexCol1 = '8')", - 7, 6}); - result4.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:2)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result4.add(new Object[]{"PROJECT(invertedIndexCol3, noIndexCol1)", 4, 3}); + result4.add(new Object[]{"DOC_ID_SET", 5, 4}); + result4.add(new Object[]{ + "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:noIndexCol1 = '8')", 6, 5 + }); + result4.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:2)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result4.add(new Object[]{"ALL_SEGMENTS_PRUNED_ON_SERVER", 3, 2}); check(query4, new ResultTable(DATA_SCHEMA, result4)); } @@ -1630,8 +1760,9 @@ public void testSelectAggregate() { List result1 = new ArrayList<>(); result1.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result1.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result1.add(new Object[]{"FAST_FILTERED_COUNT", 3, 2}); result1.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 4, 3}); check(query1, new ResultTable(DATA_SCHEMA, result1)); @@ -1641,8 +1772,9 @@ public void testSelectAggregate() { List result2 = new ArrayList<>(); result2.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result2.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result2.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result2.add(new Object[]{"AGGREGATE_NO_SCAN", 3, 2}); check(query2, new ResultTable(DATA_SCHEMA, result2)); @@ -1653,14 +1785,14 @@ public void testSelectAggregate() { List result3 = new ArrayList<>(); result3.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result3.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result3.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result3.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result3.add( new Object[]{"AGGREGATE(aggregations:count(*), max(noIndexCol1), sum(noIndexCol2), avg(noIndexCol2))", 3, 2}); - result3.add(new Object[]{"TRANSFORM_PASSTHROUGH(noIndexCol1, noIndexCol2)", 4, 3}); - result3.add(new Object[]{"PROJECT(noIndexCol2, noIndexCol1)", 5, 4}); - result3.add(new Object[]{"DOC_ID_SET", 6, 5}); - result3.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 7, 6}); + result3.add(new Object[]{"PROJECT(noIndexCol2, noIndexCol1)", 4, 3}); + result3.add(new Object[]{"DOC_ID_SET", 5, 4}); + result3.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 6, 5}); check(query3, new ResultTable(DATA_SCHEMA, result3)); // All segment plans for this queries generate a plan using the MatchAllFilterOperator as it selects and aggregates @@ -1670,16 +1802,67 @@ public void testSelectAggregate() { List result4 = new ArrayList<>(); result4.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result4.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result4.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); - result4.add(new Object[]{"AGGREGATE(aggregations:sum(add(noIndexCol1,noIndexCol2)), min(add(div(noIndexCol1," - + "noIndexCol2),noIndexCol3)))", 3, 2}); + result4.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); + result4.add(new Object[]{ + "AGGREGATE(aggregations:sum(add(noIndexCol1,noIndexCol2)), " + + "min(add(div(noIndexCol1,noIndexCol2),noIndexCol3)))", 3, 2 + }); result4.add( new Object[]{"TRANSFORM(add(div(noIndexCol1,noIndexCol2),noIndexCol3), add(noIndexCol1,noIndexCol2))", 4, 3}); result4.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 5, 4}); result4.add(new Object[]{"DOC_ID_SET", 6, 5}); result4.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 7, 6}); check(query4, new ResultTable(DATA_SCHEMA, result4)); + + // No scan required as metadata is sufficient to answer the query for all segments + String query5 = "EXPLAIN PLAN FOR SELECT DISTINCTSUM(invertedIndexCol1) FROM testTable"; + List result5 = new ArrayList<>(); + result5.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); + result5.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); + result5.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); + result5.add(new Object[]{"AGGREGATE_NO_SCAN", 3, 2}); + check(query5, new ResultTable(DATA_SCHEMA, result5)); + + String query6 = "EXPLAIN PLAN FOR SELECT DISTINCTSUMMV(mvNoIndexCol1) FROM testTable"; + List result6 = new ArrayList<>(); + result6.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); + result6.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); + result6.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); + result6.add(new Object[]{"AGGREGATE_NO_SCAN", 3, 2}); + check(query6, new ResultTable(DATA_SCHEMA, result6)); + + // Full scan required for distinctavg as the column does not have a dictionary. + String query7 = "EXPLAIN PLAN FOR SELECT DISTINCTAVG(rawCol1) FROM testTable"; + List result7 = new ArrayList<>(); + result7.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); + result7.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); + result7.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); + result7.add(new Object[]{"AGGREGATE(aggregations:distinctAvg(rawCol1))", 3, 2}); + result7.add(new Object[]{"PROJECT(rawCol1)", 4, 3}); + result7.add(new Object[]{"DOC_ID_SET", 5, 4}); + result7.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 6, 5}); + check(query7, new ResultTable(DATA_SCHEMA, result7)); + + String query8 = "EXPLAIN PLAN FOR SELECT DISTINCTAVGMV(mvRawCol1) FROM testTable"; + List result8 = new ArrayList<>(); + result8.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); + result8.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); + result8.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); + result8.add(new Object[]{"AGGREGATE(aggregations:distinctAvgMV(mvRawCol1))", 3, 2}); + result8.add(new Object[]{"PROJECT(mvRawCol1)", 4, 3}); + result8.add(new Object[]{"DOC_ID_SET", 5, 4}); + result8.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 6, 5}); + check(query8, new ResultTable(DATA_SCHEMA, result8)); } @Test @@ -1690,8 +1873,9 @@ public void testSelectAggregateVerbose() { List result1 = new ArrayList<>(); result1.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result1.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result1.add(new Object[]{"FAST_FILTERED_COUNT", 3, 2}); result1.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 4, 3}); check(query1, new ResultTable(DATA_SCHEMA, result1)); @@ -1701,8 +1885,9 @@ public void testSelectAggregateVerbose() { List result2 = new ArrayList<>(); result2.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result2.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result2.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result2.add(new Object[]{"AGGREGATE_NO_SCAN", 3, 2}); check(query2, new ResultTable(DATA_SCHEMA, result2)); @@ -1714,14 +1899,14 @@ public void testSelectAggregateVerbose() { List result3 = new ArrayList<>(); result3.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result3.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result3.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result3.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result3.add( new Object[]{"AGGREGATE(aggregations:count(*), max(noIndexCol1), sum(noIndexCol2), avg(noIndexCol2))", 3, 2}); - result3.add(new Object[]{"TRANSFORM_PASSTHROUGH(noIndexCol1, noIndexCol2)", 4, 3}); - result3.add(new Object[]{"PROJECT(noIndexCol2, noIndexCol1)", 5, 4}); - result3.add(new Object[]{"DOC_ID_SET", 6, 5}); - result3.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 7, 6}); + result3.add(new Object[]{"PROJECT(noIndexCol2, noIndexCol1)", 4, 3}); + result3.add(new Object[]{"DOC_ID_SET", 5, 4}); + result3.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 6, 5}); check(query3, new ResultTable(DATA_SCHEMA, result3)); // All segment plans for this queries generate a plan using the MatchAllFilterOperator as it selects and aggregates @@ -1731,10 +1916,13 @@ public void testSelectAggregateVerbose() { List result4 = new ArrayList<>(); result4.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result4.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result4.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); - result4.add(new Object[]{"AGGREGATE(aggregations:sum(add(noIndexCol1,noIndexCol2)), min(add(div(noIndexCol1," - + "noIndexCol2),noIndexCol3)))", 3, 2}); + result4.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); + result4.add(new Object[]{ + "AGGREGATE(aggregations:sum(add(noIndexCol1,noIndexCol2)), " + + "min(add(div(noIndexCol1,noIndexCol2),noIndexCol3)))", 3, 2 + }); result4.add( new Object[]{"TRANSFORM(add(div(noIndexCol1,noIndexCol2),noIndexCol3), add(noIndexCol1,noIndexCol2))", 4, 3}); result4.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 5, 4}); @@ -1785,7 +1973,7 @@ public void testSelectAggregateUsingFilterGroupByVerbose() { "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS }); result1.add(new Object[]{ - "GROUP_BY(groupKeys:noIndexCol2, aggregations:sum(add(noIndexCol1," + "noIndexCol2)), min(noIndexCol3))", 3, 2 + "GROUP_BY(groupKeys:noIndexCol2, aggregations:sum(add(noIndexCol1,noIndexCol2)), min(noIndexCol3))", 3, 2 }); result1.add(new Object[]{"TRANSFORM(add(noIndexCol1,noIndexCol2), noIndexCol2, noIndexCol3)", 4, 3}); result1.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 5, 4}); @@ -1804,11 +1992,13 @@ public void testSelectAggregateUsingFilterIndex() { List result1 = new ArrayList<>(); result1.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result1.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result1.add(new Object[]{"FAST_FILTERED_COUNT", 3, 2}); - result1.add(new Object[]{"FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:invertedIndexCol3 " - + "= 'mickey')", 4, 3}); + result1.add(new Object[]{ + "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:invertedIndexCol3 = 'mickey')", 4, 3 + }); check(query1, new ResultTable(DATA_SCHEMA, result1)); // Segment 2 is pruned because 'mickey' is not within range (and all values in that segment are 'pluto') @@ -1819,14 +2009,15 @@ public void testSelectAggregateUsingFilterIndex() { List result2 = new ArrayList<>(); result2.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result2.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result2.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result2.add(new Object[]{"AGGREGATE(aggregations:sum(noIndexCol2))", 3, 2}); - result2.add(new Object[]{"TRANSFORM_PASSTHROUGH(noIndexCol2)", 4, 3}); - result2.add(new Object[]{"PROJECT(noIndexCol2)", 5, 4}); - result2.add(new Object[]{"DOC_ID_SET", 6, 5}); + result2.add(new Object[]{"PROJECT(noIndexCol2)", 4, 3}); + result2.add(new Object[]{"DOC_ID_SET", 5, 4}); result2.add(new Object[]{ - "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:invertedIndexCol3 = " + "'mickey')", 7, 6}); + "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:invertedIndexCol3 = 'mickey')", 6, 5 + }); check(query2, new ResultTable(DATA_SCHEMA, result2)); // None of the segments have a value of '20' for the noIndexCol1 so this part of the OR predicate is removed for @@ -1839,16 +2030,16 @@ public void testSelectAggregateUsingFilterIndex() { List result3 = new ArrayList<>(); result3.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result3.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result3.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result3.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result3.add( new Object[]{"AGGREGATE(aggregations:count(*), max(noIndexCol1), sum(noIndexCol2), avg(noIndexCol3))", 3, 2}); - result3.add(new Object[]{"TRANSFORM_PASSTHROUGH(noIndexCol1, noIndexCol2, noIndexCol3)", 4, 3}); - result3.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 5, 4}); - result3.add(new Object[]{"DOC_ID_SET", 6, 5}); + result3.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 4, 3}); + result3.add(new Object[]{"DOC_ID_SET", 5, 4}); result3.add(new Object[]{ - "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1 = '1" - + ".1')", 7, 6}); + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1 = '1.1')", 6, 5 + }); check(query3, new ResultTable(DATA_SCHEMA, result3)); // Use a Transform function in filter on an indexed column. @@ -1860,43 +2051,46 @@ public void testSelectAggregateUsingFilterIndex() { List result4 = new ArrayList<>(); result4.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result4.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result4.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result4.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result4.add(new Object[]{"SELECT(selectList:invertedIndexCol3)", 3, 2}); - result4.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol3)", 4, 3}); - result4.add(new Object[]{"PROJECT(invertedIndexCol3)", 5, 4}); - result4.add(new Object[]{"DOC_ID_SET", 6, 5}); - result4.add(new Object[]{"FILTER_OR", 7, 6}); + result4.add(new Object[]{"PROJECT(invertedIndexCol3)", 4, 3}); + result4.add(new Object[]{"DOC_ID_SET", 5, 4}); + result4.add(new Object[]{"FILTER_OR", 6, 5}); result4.add(new Object[]{ - "FILTER_EXPRESSION(operator:EQ,predicate:concat(invertedIndexCol3,'test','-') = " + "'mickey-test')", 8, 7}); - result4.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ," - + "predicate:invertedIndexCol1 = '1.1')", 9, 7}); + "FILTER_EXPRESSION(operator:EQ,predicate:concat(invertedIndexCol3,'test','-') = 'mickey-test')", 7, 6 + }); + result4.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1 = '1.1')", 8, 6 + }); check(query4, new ResultTable(DATA_SCHEMA, result4)); // Segments 1, 2, 4 have an EmptyFilterOperator plan for this query as '1.5' is within the min-max range but // doesn't exist as a value in any row // Segment 3 contains a row with the value as '1.5' so a FILTERED_INVERTED_INDEX is returned for 1 segment - String query5 = - "EXPLAIN PLAN FOR SELECT count(*) FROM testTable WHERE invertedIndexCol1 = 1.5 LIMIT 100"; + String query5 = "EXPLAIN PLAN FOR SELECT count(*) FROM testTable WHERE invertedIndexCol1 = 1.5 LIMIT 100"; List result5 = new ArrayList<>(); result5.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result5.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result5.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result5.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result5.add(new Object[]{"FAST_FILTERED_COUNT", 3, 2}); - result5.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:" - + "invertedIndexCol1 = '1.5')", 4, 3}); + result5.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1 = '1.5')", 4, 3 + }); check(query5, new ResultTable(DATA_SCHEMA, result5)); // All segments have a EmptyFilterOperator plan for this query as '1.7' is within the min-max range but doesn't // exist as a value in any row - String query6 = - "EXPLAIN PLAN FOR SELECT count(*) FROM testTable WHERE invertedIndexCol1 = 1.7 LIMIT 100"; + String query6 = "EXPLAIN PLAN FOR SELECT count(*) FROM testTable WHERE invertedIndexCol1 = 1.7 LIMIT 100"; List result6 = new ArrayList<>(); result6.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result6.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result6.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result6.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result6.add(new Object[]{"FAST_FILTERED_COUNT", 3, 2}); result6.add(new Object[]{"FILTER_EMPTY", 4, 3}); check(query6, new ResultTable(DATA_SCHEMA, result6)); @@ -1906,13 +2100,13 @@ public void testSelectAggregateUsingFilterIndex() { // Segment 4 has an EmptyFilterOperator plan as 'pluto' doesn't exist but is within the value ranges // Only the MatchAllOperator plan is returned as it has higher precedence than no matching segment and verbose is // disabled - String query7 = - "EXPLAIN PLAN FOR SELECT count(*) FROM testTable WHERE invertedIndexCol3 = 'pluto' LIMIT 100"; + String query7 = "EXPLAIN PLAN FOR SELECT count(*) FROM testTable WHERE invertedIndexCol3 = 'pluto' LIMIT 100"; List result7 = new ArrayList<>(); result7.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result7.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result7.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result7.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result7.add(new Object[]{"FAST_FILTERED_COUNT", 3, 2}); result7.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 4, 3}); check(query7, new ResultTable(DATA_SCHEMA, result7)); @@ -1921,13 +2115,13 @@ public void testSelectAggregateUsingFilterIndex() { // The other segments are pruned as '2' is less than the min for these segments // Only the EmptyFilterOperator plan is returned as it has higher precedence than no matching segment and verbose // is disabled - String query8 = - "EXPLAIN PLAN FOR SELECT count(*) FROM testTable WHERE noIndexCol1 = 2 LIMIT 100"; + String query8 = "EXPLAIN PLAN FOR SELECT count(*) FROM testTable WHERE noIndexCol1 = 2 LIMIT 100"; List result8 = new ArrayList<>(); result8.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result8.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result8.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result8.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result8.add(new Object[]{"FAST_FILTERED_COUNT", 3, 2}); result8.add(new Object[]{"FILTER_EMPTY", 4, 3}); check(query8, new ResultTable(DATA_SCHEMA, result8)); @@ -1938,18 +2132,18 @@ public void testSelectAggregateUsingFilterIndex() { // Segment 4 has an EmptyFilterOperator plan as neither 'minnie' nor 'pluto' exists but are within the value ranges // Only the FILTERED_SORTED_COUNT plan is returned as it has higher precedence than the others and verbose is // disabled - String query9 = - "EXPLAIN PLAN FOR SELECT count(*) FROM testTable WHERE invertedIndexCol3 = 'pluto' OR " - + "invertedIndexCol3 = 'minnie' LIMIT 100"; + String query9 = "EXPLAIN PLAN FOR SELECT count(*) FROM testTable WHERE invertedIndexCol3 = 'pluto' OR " + + "invertedIndexCol3 = 'minnie' LIMIT 100"; List result9 = new ArrayList<>(); result9.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result9.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result9.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result9.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result9.add(new Object[]{"FAST_FILTERED_COUNT", 3, 2}); - result9.add( - new Object[]{"FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:invertedIndexCol3 = 'minnie')", - 4, 3}); + result9.add(new Object[]{ + "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:invertedIndexCol3 = 'minnie')", 4, 3 + }); check(query9, new ResultTable(DATA_SCHEMA, result9)); // All segments are pruned @@ -1957,8 +2151,9 @@ public void testSelectAggregateUsingFilterIndex() { + "noIndexCol1 = 100 LIMIT 100"; List result10 = new ArrayList<>(); result10.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); - result10.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result10.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result10.add(new Object[]{"ALL_SEGMENTS_PRUNED_ON_SERVER", 2, 1}); check(query10, new ResultTable(DATA_SCHEMA, result10)); } @@ -1973,15 +2168,18 @@ public void testSelectAggregateUsingFilterIndexVerbose() { List result1 = new ArrayList<>(); result1.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result1.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:2)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:2)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result1.add(new Object[]{"FAST_FILTERED_COUNT", 3, 2}); result1.add(new Object[]{"FILTER_EMPTY", 4, 3}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result1.add(new Object[]{"FAST_FILTERED_COUNT", 3, 2}); - result1.add(new Object[]{"FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:invertedIndexCol3 " - + "= 'mickey')", 4, 3}); + result1.add(new Object[]{ + "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:invertedIndexCol3 = 'mickey')", 4, 3 + }); check(query1, new ResultTable(DATA_SCHEMA, result1)); // Segment 2 is pruned because 'mickey' is not within range (and all values in that segment are 'pluto') @@ -1992,21 +2190,22 @@ public void testSelectAggregateUsingFilterIndexVerbose() { List result2 = new ArrayList<>(); result2.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result2.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result2.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result2.add(new Object[]{"AGGREGATE(aggregations:sum(noIndexCol2))", 3, 2}); - result2.add(new Object[]{"TRANSFORM_PASSTHROUGH(noIndexCol2)", 4, 3}); - result2.add(new Object[]{"PROJECT(noIndexCol2)", 5, 4}); - result2.add(new Object[]{"DOC_ID_SET", 6, 5}); + result2.add(new Object[]{"PROJECT(noIndexCol2)", 4, 3}); + result2.add(new Object[]{"DOC_ID_SET", 5, 4}); result2.add(new Object[]{ - "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:invertedIndexCol3 = " + "'mickey')", 7, 6}); - result2.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:2)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:invertedIndexCol3 = 'mickey')", 6, 5 + }); + result2.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:2)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result2.add(new Object[]{"AGGREGATE(aggregations:sum(noIndexCol2))", 3, 2}); - result2.add(new Object[]{"TRANSFORM_PASSTHROUGH(noIndexCol2)", 4, 3}); - result2.add(new Object[]{"PROJECT(noIndexCol2)", 5, 4}); - result2.add(new Object[]{"DOC_ID_SET", 6, 5}); - result2.add(new Object[]{"FILTER_EMPTY", 7, 6}); + result2.add(new Object[]{"PROJECT(noIndexCol2)", 4, 3}); + result2.add(new Object[]{"DOC_ID_SET", 5, 4}); + result2.add(new Object[]{"FILTER_EMPTY", 6, 5}); check(query2, new ResultTable(DATA_SCHEMA, result2)); // None of the segments have a value of '20' for the noIndexCol1 so this part of the OR predicate is removed for @@ -2018,24 +2217,24 @@ public void testSelectAggregateUsingFilterIndexVerbose() { List result3 = new ArrayList<>(); result3.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result3.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result3.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result3.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result3.add( new Object[]{"AGGREGATE(aggregations:count(*), max(noIndexCol1), sum(noIndexCol2), avg(noIndexCol3))", 3, 2}); - result3.add(new Object[]{"TRANSFORM_PASSTHROUGH(noIndexCol1, noIndexCol2, noIndexCol3)", 4, 3}); - result3.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 5, 4}); - result3.add(new Object[]{"DOC_ID_SET", 6, 5}); - result3.add(new Object[]{"FILTER_EMPTY", 7, 6}); - result3.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result3.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 4, 3}); + result3.add(new Object[]{"DOC_ID_SET", 5, 4}); + result3.add(new Object[]{"FILTER_EMPTY", 6, 5}); + result3.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result3.add( new Object[]{"AGGREGATE(aggregations:count(*), max(noIndexCol1), sum(noIndexCol2), avg(noIndexCol3))", 3, 2}); - result3.add(new Object[]{"TRANSFORM_PASSTHROUGH(noIndexCol1, noIndexCol2, noIndexCol3)", 4, 3}); - result3.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 5, 4}); - result3.add(new Object[]{"DOC_ID_SET", 6, 5}); + result3.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 4, 3}); + result3.add(new Object[]{"DOC_ID_SET", 5, 4}); result3.add(new Object[]{ - "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1 = '1" - + ".1')", 7, 6}); + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1 = '1.1')", 6, 5 + }); check(query3, new ResultTable(DATA_SCHEMA, result3)); // Use a Transform function in filter on an indexed column. @@ -2046,25 +2245,28 @@ public void testSelectAggregateUsingFilterIndexVerbose() { List result4 = new ArrayList<>(); result4.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result4.add(new Object[]{"COMBINE_SELECT", 2, 1}); - result4.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result4.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result4.add(new Object[]{"SELECT(selectList:invertedIndexCol3)", 3, 2}); - result4.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol3)", 4, 3}); - result4.add(new Object[]{"PROJECT(invertedIndexCol3)", 5, 4}); - result4.add(new Object[]{"DOC_ID_SET", 6, 5}); + result4.add(new Object[]{"PROJECT(invertedIndexCol3)", 4, 3}); + result4.add(new Object[]{"DOC_ID_SET", 5, 4}); result4.add(new Object[]{ - "FILTER_EXPRESSION(operator:EQ,predicate:concat(invertedIndexCol3,'test','-') = " + "'mickey-test')", 7, 6}); - result4.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + "FILTER_EXPRESSION(operator:EQ,predicate:concat(invertedIndexCol3,'test','-') = 'mickey-test')", 6, 5 + }); + result4.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result4.add(new Object[]{"SELECT(selectList:invertedIndexCol3)", 3, 2}); - result4.add(new Object[]{"TRANSFORM_PASSTHROUGH(invertedIndexCol3)", 4, 3}); - result4.add(new Object[]{"PROJECT(invertedIndexCol3)", 5, 4}); - result4.add(new Object[]{"DOC_ID_SET", 6, 5}); - result4.add(new Object[]{"FILTER_OR", 7, 6}); + result4.add(new Object[]{"PROJECT(invertedIndexCol3)", 4, 3}); + result4.add(new Object[]{"DOC_ID_SET", 5, 4}); + result4.add(new Object[]{"FILTER_OR", 6, 5}); + result4.add(new Object[]{ + "FILTER_EXPRESSION(operator:EQ,predicate:concat(invertedIndexCol3,'test','-') = 'mickey-test')", 7, 6 + }); result4.add(new Object[]{ - "FILTER_EXPRESSION(operator:EQ,predicate:concat(invertedIndexCol3,'test','-') = " + "'mickey-test')", 8, 7}); - result4.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ," - + "predicate:invertedIndexCol1 = '1.1')", 9, 7}); + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1 = '1.1')", 8, 6 + }); check(query4, new ResultTable(DATA_SCHEMA, result4)); // Segments 1, 2, 4 have an EmptyFilterOperator plan for this query as '1.5' is within the min-max range but @@ -2076,15 +2278,18 @@ public void testSelectAggregateUsingFilterIndexVerbose() { List result5 = new ArrayList<>(); result5.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result5.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result5.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result5.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:3)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result5.add(new Object[]{"FAST_FILTERED_COUNT", 3, 2}); result5.add(new Object[]{"FILTER_EMPTY", 4, 3}); - result5.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result5.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result5.add(new Object[]{"FAST_FILTERED_COUNT", 3, 2}); - result5.add(new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:" - + "invertedIndexCol1 = '1.5')", 4, 3}); + result5.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol1 = '1.5')", 4, 3 + }); check(query5, new ResultTable(DATA_SCHEMA, result5)); // All segments have a EmptyFilterOperator plan for this query as '1.7' is within the min-max range but doesn't @@ -2095,8 +2300,9 @@ public void testSelectAggregateUsingFilterIndexVerbose() { List result6 = new ArrayList<>(); result6.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result6.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result6.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result6.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result6.add(new Object[]{"FAST_FILTERED_COUNT", 3, 2}); result6.add(new Object[]{"FILTER_EMPTY", 4, 3}); check(query6, new ResultTable(DATA_SCHEMA, result6)); @@ -2110,12 +2316,14 @@ public void testSelectAggregateUsingFilterIndexVerbose() { List result7 = new ArrayList<>(); result7.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result7.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result7.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result7.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result7.add(new Object[]{"FAST_FILTERED_COUNT", 3, 2}); result7.add(new Object[]{"FILTER_EMPTY", 4, 3}); - result7.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result7.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result7.add(new Object[]{"FAST_FILTERED_COUNT", 3, 2}); result7.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 4, 3}); check(query7, new ResultTable(DATA_SCHEMA, result7)); @@ -2127,12 +2335,14 @@ public void testSelectAggregateUsingFilterIndexVerbose() { List result8 = new ArrayList<>(); result8.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result8.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result8.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result8.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result8.add(new Object[]{"FAST_FILTERED_COUNT", 3, 2}); result8.add(new Object[]{"FILTER_EMPTY", 4, 3}); - result8.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:2)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result8.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:2)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result8.add(new Object[]{"ALL_SEGMENTS_PRUNED_ON_SERVER", 3, 2}); check(query8, new ResultTable(DATA_SCHEMA, result8)); @@ -2146,18 +2356,21 @@ public void testSelectAggregateUsingFilterIndexVerbose() { List result9 = new ArrayList<>(); result9.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); result9.add(new Object[]{"COMBINE_AGGREGATE", 2, 1}); - result9.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result9.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result9.add(new Object[]{"FAST_FILTERED_COUNT", 3, 2}); - result9.add( - new Object[]{"FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:invertedIndexCol3 = 'minnie')", - 4, 3}); - result9.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result9.add(new Object[]{ + "FILTER_SORTED_INDEX(indexLookUp:sorted_index,operator:EQ,predicate:invertedIndexCol3 = 'minnie')", 4, 3 + }); + result9.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result9.add(new Object[]{"FAST_FILTERED_COUNT", 3, 2}); result9.add(new Object[]{"FILTER_EMPTY", 4, 3}); - result9.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result9.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:1)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result9.add(new Object[]{"FAST_FILTERED_COUNT", 3, 2}); result9.add(new Object[]{"FILTER_MATCH_ENTIRE_SEGMENT(docs:3)", 4, 3}); check(query9, new ResultTable(DATA_SCHEMA, result9)); @@ -2167,8 +2380,9 @@ public void testSelectAggregateUsingFilterIndexVerbose() { + "invertedIndexCol3 = 'roadrunner' AND noIndexCol1 = 100 LIMIT 100"; List result10 = new ArrayList<>(); result10.add(new Object[]{"BROKER_REDUCE(limit:100)", 1, 0}); - result10.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); + result10.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); result10.add(new Object[]{"ALL_SEGMENTS_PRUNED_ON_SERVER", 2, 1}); check(query10, new ResultTable(DATA_SCHEMA, result10)); } @@ -2181,17 +2395,17 @@ public void testSelectAggregateUsingFilterIndexGroupBy() { List result1 = new ArrayList<>(); result1.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result1.add(new Object[]{"COMBINE_GROUP_BY", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); result1.add(new Object[]{ - "GROUP_BY(groupKeys:noIndexCol1, aggregations:max(noIndexCol2), min(noIndexCol3)" - + ")", 3, 2}); - result1.add(new Object[]{"TRANSFORM_PASSTHROUGH(noIndexCol1, noIndexCol2, noIndexCol3)", 4, 3}); - result1.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 5, 4}); - result1.add(new Object[]{"DOC_ID_SET", 6, 5}); - result1.add( - new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol2 = '1')", - 7, 6}); + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); + result1.add(new Object[]{ + "GROUP_BY(groupKeys:noIndexCol1, aggregations:max(noIndexCol2), min(noIndexCol3))", 3, 2 + }); + result1.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 4, 3}); + result1.add(new Object[]{"DOC_ID_SET", 5, 4}); + result1.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol2 = '1')", 6, 5 + }); check(query1, new ResultTable(DATA_SCHEMA, result1)); } @@ -2203,17 +2417,17 @@ public void testSelectAggregateUsingFilterIndexGroupByVerbose() { List result1 = new ArrayList<>(); result1.add(new Object[]{"BROKER_REDUCE(limit:10)", 1, 0}); result1.add(new Object[]{"COMBINE_GROUP_BY", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); result1.add(new Object[]{ - "GROUP_BY(groupKeys:noIndexCol1, aggregations:max(noIndexCol2), min(noIndexCol3)" - + ")", 3, 2}); - result1.add(new Object[]{"TRANSFORM_PASSTHROUGH(noIndexCol1, noIndexCol2, noIndexCol3)", 4, 3}); - result1.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 5, 4}); - result1.add(new Object[]{"DOC_ID_SET", 6, 5}); - result1.add( - new Object[]{"FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol2 = '1')", - 7, 6}); + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); + result1.add(new Object[]{ + "GROUP_BY(groupKeys:noIndexCol1, aggregations:max(noIndexCol2), min(noIndexCol3))", 3, 2 + }); + result1.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 4, 3}); + result1.add(new Object[]{"DOC_ID_SET", 5, 4}); + result1.add(new Object[]{ + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol2 = '1')", 6, 5 + }); check(query1, new ResultTable(DATA_SCHEMA, result1)); } @@ -2228,16 +2442,19 @@ public void testSelectAggregateUsingFilterIndexGroupByOrderBy() { result1.add( new Object[]{"BROKER_REDUCE(sort:[noIndexCol1 ASC, concat(invertedIndexCol3,'test','-') ASC],limit:10)", 1, 0}); result1.add(new Object[]{"COMBINE_GROUP_BY", 2, 1}); - result1.add(new Object[]{"PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, - ExplainPlanRows.PLAN_START_IDS}); - result1.add(new Object[]{"GROUP_BY(groupKeys:noIndexCol1, concat(invertedIndexCol3,'test','-'), " - + "aggregations:count(*))", 3, 2}); + result1.add(new Object[]{ + "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS + }); + result1.add(new Object[]{ + "GROUP_BY(groupKeys:noIndexCol1, concat(invertedIndexCol3,'test','-'), aggregations:count(*))", 3, 2 + }); result1.add(new Object[]{"TRANSFORM(concat(invertedIndexCol3,'test','-'), noIndexCol1)", 4, 3}); result1.add(new Object[]{"PROJECT(invertedIndexCol3, noIndexCol1)", 5, 4}); result1.add(new Object[]{"DOC_ID_SET", 6, 5}); result1.add(new Object[]{ "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:NOT_EQ,predicate:invertedIndexCol2 !=" - + " '1')", 7, 6}); + + " '1')", 7, 6 + }); check(query1, new ResultTable(DATA_SCHEMA, result1)); } @@ -2256,7 +2473,7 @@ public void testSelectAggregateUsingFilterIndexGroupByOrderByVerbose() { "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS }); result1.add(new Object[]{ - "GROUP_BY(groupKeys:noIndexCol1, concat(invertedIndexCol3,'test','-'), " + "aggregations:count(*))", 3, 2 + "GROUP_BY(groupKeys:noIndexCol1, concat(invertedIndexCol3,'test','-'), aggregations:count(*))", 3, 2 }); result1.add(new Object[]{"TRANSFORM(concat(invertedIndexCol3,'test','-'), noIndexCol1)", 4, 3}); result1.add(new Object[]{"PROJECT(invertedIndexCol3, noIndexCol1)", 5, 4}); @@ -2282,11 +2499,10 @@ public void testSelectAggregateUsingFilterIndexGroupByHaving() { result.add(new Object[]{ "GROUP_BY(groupKeys:noIndexCol3, aggregations:max(noIndexCol1), min(noIndexCol2))", 3, 2 }); - result.add(new Object[]{"TRANSFORM_PASSTHROUGH(noIndexCol1, noIndexCol2, noIndexCol3)", 4, 3}); - result.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 5, 4}); - result.add(new Object[]{"DOC_ID_SET", 6, 5}); + result.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 4, 3}); + result.add(new Object[]{"DOC_ID_SET", 5, 4}); result.add(new Object[]{ - "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol2 = '1')", 7, 6 + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol2 = '1')", 6, 5 }); check(query, new ResultTable(DATA_SCHEMA, result)); } @@ -2305,13 +2521,12 @@ public void testSelectAggregateUsingFilterIndexGroupByHavingVerbose() { "PLAN_START(numSegmentsForThisPlan:4)", ExplainPlanRows.PLAN_START_IDS, ExplainPlanRows.PLAN_START_IDS }); result.add(new Object[]{ - "GROUP_BY(groupKeys:noIndexCol3, aggregations:max(noIndexCol1), min(noIndexCol2)" + ")", 3, 2 + "GROUP_BY(groupKeys:noIndexCol3, aggregations:max(noIndexCol1), min(noIndexCol2))", 3, 2 }); - result.add(new Object[]{"TRANSFORM_PASSTHROUGH(noIndexCol1, noIndexCol2, noIndexCol3)", 4, 3}); - result.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 5, 4}); - result.add(new Object[]{"DOC_ID_SET", 6, 5}); + result.add(new Object[]{"PROJECT(noIndexCol3, noIndexCol2, noIndexCol1)", 4, 3}); + result.add(new Object[]{"DOC_ID_SET", 5, 4}); result.add(new Object[]{ - "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol2 = '1')", 7, 6 + "FILTER_INVERTED_INDEX(indexLookUp:inverted_index,operator:EQ,predicate:invertedIndexCol2 = '1')", 6, 5 }); check(query, new ResultTable(DATA_SCHEMA, result)); } @@ -2320,9 +2535,10 @@ public void testSelectAggregateUsingFilterIndexGroupByHavingVerbose() { public void tearDown() { _brokerReduceService.shutDown(); _queryExecutor.shutDown(); + _queryExecutorWithPrefetchEnabled.shutDown(); for (IndexSegment segment : _indexSegments) { segment.destroy(); } - FileUtils.deleteQuietly(INDEX_DIR); + FileUtils.deleteQuietly(TEMP_DIR); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/ExprMinMaxTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/ExprMinMaxTest.java new file mode 100644 index 000000000000..121ed8c56374 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/queries/ExprMinMaxTest.java @@ -0,0 +1,646 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.queries; + +import java.io.File; +import java.io.IOException; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.apache.commons.io.FileUtils; +import org.apache.pinot.common.response.BrokerResponse; +import org.apache.pinot.common.response.broker.BrokerResponseNative; +import org.apache.pinot.common.response.broker.ResultTable; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.query.utils.rewriter.ResultRewriterFactory; +import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; +import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; +import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; +import org.apache.pinot.segment.spi.ImmutableSegment; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.data.FieldSpec.DataType; +import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.spi.data.readers.GenericRow; +import org.apache.pinot.spi.exception.BadQueryRequestException; +import org.apache.pinot.spi.utils.ReadMode; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.apache.pinot.sql.parsers.rewriter.QueryRewriterFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.apache.pinot.spi.utils.CommonConstants.RewriterConstants.*; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.fail; + + +/** + * Queries test for exprmin/exprmax functions. + */ +public class ExprMinMaxTest extends BaseQueriesTest { + private static final Logger LOGGER = LoggerFactory.getLogger(ExprMinMaxTest.class); + private static final File INDEX_DIR = new File(FileUtils.getTempDirectory(), "ExprMinMaxTest"); + private static final String RAW_TABLE_NAME = "testTable"; + private static final String SEGMENT_NAME = "testSegment"; + + private static final int NUM_RECORDS = 2000; + + private static final String INT_COLUMN = "intColumn"; + private static final String LONG_COLUMN = "longColumn"; + private static final String FLOAT_COLUMN = "floatColumn"; + private static final String DOUBLE_COLUMN = "doubleColumn"; + private static final String MV_DOUBLE_COLUMN = "mvDoubleColumn"; + private static final String MV_INT_COLUMN = "mvIntColumn"; + private static final String MV_BYTES_COLUMN = "mvBytesColumn"; + private static final String MV_STRING_COLUMN = "mvStringColumn"; + private static final String STRING_COLUMN = "stringColumn"; + private static final String GROUP_BY_INT_COLUMN = "groupByIntColumn"; + private static final String GROUP_BY_MV_INT_COLUMN = "groupByMVIntColumn"; + private static final String GROUP_BY_INT_COLUMN2 = "groupByIntColumn2"; + private static final String BIG_DECIMAL_COLUMN = "bigDecimalColumn"; + private static final String TIMESTAMP_COLUMN = "timestampColumn"; + private static final String BOOLEAN_COLUMN = "booleanColumn"; + private static final String JSON_COLUMN = "jsonColumn"; + + private static final Schema SCHEMA = new Schema.SchemaBuilder().addSingleValueDimension(INT_COLUMN, DataType.INT) + .addSingleValueDimension(LONG_COLUMN, DataType.LONG).addSingleValueDimension(FLOAT_COLUMN, DataType.FLOAT) + .addSingleValueDimension(DOUBLE_COLUMN, DataType.DOUBLE).addMultiValueDimension(MV_INT_COLUMN, DataType.INT) + .addMultiValueDimension(MV_BYTES_COLUMN, DataType.BYTES) + .addMultiValueDimension(MV_STRING_COLUMN, DataType.STRING) + .addSingleValueDimension(STRING_COLUMN, DataType.STRING) + .addSingleValueDimension(GROUP_BY_INT_COLUMN, DataType.INT) + .addMultiValueDimension(GROUP_BY_MV_INT_COLUMN, DataType.INT) + .addSingleValueDimension(GROUP_BY_INT_COLUMN2, DataType.INT) + .addSingleValueDimension(BIG_DECIMAL_COLUMN, DataType.BIG_DECIMAL) + .addSingleValueDimension(TIMESTAMP_COLUMN, DataType.TIMESTAMP) + .addSingleValueDimension(BOOLEAN_COLUMN, DataType.BOOLEAN) + .addMultiValueDimension(MV_DOUBLE_COLUMN, DataType.DOUBLE) + .addSingleValueDimension(JSON_COLUMN, DataType.JSON) + .build(); + private static final TableConfig TABLE_CONFIG = + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + + private IndexSegment _indexSegment; + private List _indexSegments; + + @Override + protected String getFilter() { + return " WHERE intColumn >= 500"; + } + + @Override + protected IndexSegment getIndexSegment() { + return _indexSegment; + } + + @Override + protected List getIndexSegments() { + return _indexSegments; + } + + @BeforeClass + public void setUp() + throws Exception { + FileUtils.deleteDirectory(INDEX_DIR); + + List records = new ArrayList<>(NUM_RECORDS); + String[] stringSVVals = new String[]{"a2", "a3", "a4", "a5", "a6", "a7", "a8", "a9", "a11", "a22"}; + int j = 1; + for (int i = 0; i < NUM_RECORDS; i++) { + GenericRow record = new GenericRow(); + record.putValue(INT_COLUMN, i); + record.putValue(LONG_COLUMN, (long) i - NUM_RECORDS / 2); + record.putValue(FLOAT_COLUMN, (float) i * 0.5); + record.putValue(DOUBLE_COLUMN, (double) i); + record.putValue(MV_INT_COLUMN, Arrays.asList(i, i + 1, i + 2)); + record.putValue(MV_BYTES_COLUMN, Arrays.asList(String.valueOf(i).getBytes(), String.valueOf(i + 1).getBytes(), + String.valueOf(i + 2).getBytes())); + record.putValue(MV_STRING_COLUMN, Arrays.asList("a" + i, "a" + i + 1, "a" + i + 2)); + if (i < 20) { + record.putValue(STRING_COLUMN, stringSVVals[i % stringSVVals.length]); + } else { + record.putValue(STRING_COLUMN, "a33"); + } + record.putValue(GROUP_BY_INT_COLUMN, i % 5); + record.putValue(GROUP_BY_MV_INT_COLUMN, Arrays.asList(i % 10, (i + 1) % 10)); + if (i == j) { + j *= 2; + } + record.putValue(GROUP_BY_INT_COLUMN2, j); + record.putValue(BIG_DECIMAL_COLUMN, new BigDecimal(-i * i + 1200 * i)); + record.putValue(TIMESTAMP_COLUMN, 1683138373879L - i); + record.putValue(BOOLEAN_COLUMN, i % 2); + record.putValue(MV_DOUBLE_COLUMN, Arrays.asList((double) i, (double) i * i, (double) i * i * i)); + record.putValue(JSON_COLUMN, "{\"name\":\"John\", \"age\":" + i + ", \"car\":null}"); + records.add(record); + } + + SegmentGeneratorConfig segmentGeneratorConfig = new SegmentGeneratorConfig(TABLE_CONFIG, SCHEMA); + segmentGeneratorConfig.setTableName(RAW_TABLE_NAME); + segmentGeneratorConfig.setSegmentName(SEGMENT_NAME); + segmentGeneratorConfig.setOutDir(INDEX_DIR.getPath()); + + SegmentIndexCreationDriverImpl driver = new SegmentIndexCreationDriverImpl(); + driver.init(segmentGeneratorConfig, new GenericRowRecordReader(records)); + driver.build(); + + ImmutableSegment immutableSegment = ImmutableSegmentLoader.load(new File(INDEX_DIR, SEGMENT_NAME), ReadMode.mmap); + _indexSegment = immutableSegment; + _indexSegments = Arrays.asList(immutableSegment, immutableSegment); + + QueryRewriterFactory.init(String.join(",", QueryRewriterFactory.DEFAULT_QUERY_REWRITERS_CLASS_NAMES) + + ",org.apache.pinot.sql.parsers.rewriter.ExprMinMaxRewriter"); + ResultRewriterFactory + .init("org.apache.pinot.core.query.utils.rewriter.ParentAggregationResultRewriter"); + } + + @Test + public void invalidParamTest() { + String query = "SELECT expr_max(intColumn) FROM testTable"; + try { + getBrokerResponse(query); + fail("Should have failed for invalid params"); + } catch (Exception e) { + Assert.assertTrue(e.getMessage().contains("Invalid number of arguments for exprmax")); + } + + query = "SELECT expr_max() FROM testTable"; + try { + getBrokerResponse(query); + fail("Should have failed for invalid params"); + } catch (Exception e) { + Assert.assertTrue(e.getMessage().contains("Invalid number of arguments for exprmax")); + } + + query = "SELECT expr_max(mvDoubleColumn, mvDoubleColumn) FROM testTable"; + BrokerResponse brokerResponse = getBrokerResponse(query); + Assert.assertTrue(brokerResponse.getExceptions().get(0).getMessage().contains( + "java.lang.IllegalStateException: ExprMinMax only supports single-valued measuring columns" + )); + + query = "SELECT expr_max(mvDoubleColumn, jsonColumn) FROM testTable"; + brokerResponse = getBrokerResponse(query); + Assert.assertTrue(brokerResponse.getExceptions().get(0).getMessage().contains( + "Cannot compute exprminMax measuring on non-comparable type: JSON" + )); + } + + @Test + public void testAggregationInterSegment() { + // Simple inter segment aggregation test + String query = "SELECT expr_max(longColumn, intColumn) FROM testTable"; + + BrokerResponseNative brokerResponse = getBrokerResponse(query); + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + + assertEquals(rows.get(0)[0], 999L); + assertEquals(rows.get(1)[0], 999L); + assertEquals(rows.size(), 2); + + // Inter segment data type test + query = "SELECT expr_max(longColumn, intColumn), expr_max(floatColumn, intColumn), " + + "expr_max(doubleColumn, intColumn), expr_min(mvIntColumn, intColumn), " + + "expr_min(mvStringColumn, intColumn), expr_min(intColumn, intColumn), " + + "expr_max(bigDecimalColumn, bigDecimalColumn), expr_max(doubleColumn, bigDecimalColumn)," + + "expr_min(timestampColumn, timestampColumn), expr_max(mvDoubleColumn, bigDecimalColumn)," + + "expr_max(jsonColumn, bigDecimalColumn)" + + " FROM testTable"; + + brokerResponse = getBrokerResponse(query); + resultTable = brokerResponse.getResultTable(); + rows = resultTable.getRows(); + + assertEquals(resultTable.getDataSchema().getColumnName(0), "exprmax(longColumn,intColumn)"); + assertEquals(resultTable.getDataSchema().getColumnName(1), "exprmax(floatColumn,intColumn)"); + assertEquals(resultTable.getDataSchema().getColumnName(2), "exprmax(doubleColumn,intColumn)"); + assertEquals(resultTable.getDataSchema().getColumnName(3), "exprmin(mvIntColumn,intColumn)"); + assertEquals(resultTable.getDataSchema().getColumnName(4), "exprmin(mvStringColumn,intColumn)"); + assertEquals(resultTable.getDataSchema().getColumnName(5), "exprmin(intColumn,intColumn)"); + assertEquals(resultTable.getDataSchema().getColumnName(6), "exprmax(bigDecimalColumn,bigDecimalColumn)"); + assertEquals(resultTable.getDataSchema().getColumnName(7), "exprmax(doubleColumn,bigDecimalColumn)"); + assertEquals(resultTable.getDataSchema().getColumnName(8), "exprmin(timestampColumn,timestampColumn)"); + assertEquals(resultTable.getDataSchema().getColumnName(9), "exprmax(mvDoubleColumn,bigDecimalColumn)"); + assertEquals(resultTable.getDataSchema().getColumnName(10), "exprmax(jsonColumn,bigDecimalColumn)"); + + assertEquals(rows.size(), 2); + assertEquals(rows.get(0)[0], 999L); + assertEquals(rows.get(1)[0], 999L); + assertEquals(rows.get(0)[1], 999.5F); + assertEquals(rows.get(1)[1], 999.5F); + assertEquals(rows.get(0)[2], 1999D); + assertEquals(rows.get(1)[2], 1999D); + assertEquals(rows.get(0)[3], new Integer[]{0, 1, 2}); + assertEquals(rows.get(1)[3], new Integer[]{0, 1, 2}); + assertEquals(rows.get(0)[4], new String[]{"a0", "a01", "a02"}); + assertEquals(rows.get(1)[4], new String[]{"a0", "a01", "a02"}); + assertEquals(rows.get(0)[5], 0); + assertEquals(rows.get(1)[5], 0); + assertEquals(rows.get(0)[6], "360000"); + assertEquals(rows.get(1)[6], "360000"); + assertEquals(rows.get(0)[7], 600D); + assertEquals(rows.get(1)[7], 600D); + assertEquals(rows.get(0)[8], 1683138373879L - 1999L); + assertEquals(rows.get(1)[8], 1683138373879L - 1999L); + assertEquals(rows.get(0)[9], new Double[]{600D, 600D * 600D, 600D * 600D * 600D}); + assertEquals(rows.get(1)[9], new Double[]{600D, 600D * 600D, 600D * 600D * 600D}); + assertEquals(rows.get(0)[10], "{\"name\":\"John\",\"age\":600,\"car\":null}"); + assertEquals(rows.get(1)[10], "{\"name\":\"John\",\"age\":600,\"car\":null}"); + + // Inter segment data type test for boolean column + query = "SELECT expr_max(booleanColumn, booleanColumn) FROM testTable"; + + brokerResponse = getBrokerResponse(query); + resultTable = brokerResponse.getResultTable(); + rows = resultTable.getRows(); + + assertEquals(rows.size(), 2000); + for (int i = 0; i < 2000; i++) { + assertEquals(rows.get(i)[0], 1); + } + + // Inter segment mix aggregation function with different result length + // Inter segment string column comparison test, with dedupe + query = "SELECT sum(intColumn), exprmin(doubleColumn, stringColumn), exprmin(stringColumn, stringColumn), " + + "exprmin(doubleColumn, stringColumn, doubleColumn) FROM testTable"; + + brokerResponse = getBrokerResponse(query); + resultTable = brokerResponse.getResultTable(); + rows = resultTable.getRows(); + + assertEquals(rows.size(), 4); + + assertEquals(rows.get(0)[0], 7996000D); + assertEquals(rows.get(0)[1], 8D); + assertEquals(rows.get(0)[2], "a11"); + assertEquals(rows.get(0)[3], 8D); + + assertEquals(rows.get(1)[0], 7996000D); + assertEquals(rows.get(1)[1], 18D); + assertEquals(rows.get(1)[2], "a11"); + assertEquals(rows.get(1)[3], 8D); + + assertEquals(rows.get(2)[0], 7996000D); + assertEquals(rows.get(2)[1], 8D); + assertEquals(rows.get(2)[2], "a11"); + assertNull(rows.get(2)[3]); + + assertEquals(rows.get(3)[0], 7996000D); + assertEquals(rows.get(3)[1], 18D); + assertEquals(rows.get(3)[2], "a11"); + assertNull(rows.get(3)[3]); + + // Test transformation function inside exprmax/exprmin, for both projection and measuring + // the max of 3000x-x^2 is 2250000, which is the max of 3000x-x^2 + query = "SELECT sum(intColumn), exprmax(doubleColumn, 3000 * doubleColumn - intColumn * intColumn)," + + "exprmax(3000 * doubleColumn - intColumn * intColumn, 3000 * doubleColumn - intColumn * intColumn)," + + "exprmax(doubleColumn, 3000 * doubleColumn - intColumn * intColumn), " + + "exprmin(replace(stringColumn, \'a\', \'bb\'), replace(stringColumn, \'a\', \'bb\'))" + + "FROM testTable"; + + brokerResponse = getBrokerResponse(query); + resultTable = brokerResponse.getResultTable(); + rows = resultTable.getRows(); + + assertEquals(rows.size(), 4); + + assertEquals(rows.get(0)[0], 7996000D); + assertEquals(rows.get(0)[1], 1500D); + assertEquals(rows.get(0)[2], 2250000D); + assertEquals(rows.get(0)[3], "bb11"); + assertEquals(rows.get(1)[0], 7996000D); + assertEquals(rows.get(1)[1], 1500D); + assertEquals(rows.get(1)[2], 2250000D); + assertEquals(rows.get(1)[3], "bb11"); + assertEquals(rows.get(2)[0], 7996000D); + assertNull(rows.get(2)[1]); + assertEquals(rows.get(2)[3], "bb11"); + assertEquals(rows.get(3)[0], 7996000D); + assertNull(rows.get(3)[1]); + assertEquals(rows.get(3)[3], "bb11"); + + // Inter segment mix aggregation function with CASE statement + query = "SELECT exprmin(stringColumn, CASE WHEN stringColumn = 'a33' THEN 'b' WHEN stringColumn = 'a22' THEN 'a' " + + "ELSE 'c' END), exprmin(CASE WHEN stringColumn = 'a33' THEN 'b' WHEN stringColumn = 'a22' THEN 'a' " + + "ELSE 'c' END, CASE WHEN stringColumn = 'a33' THEN 'b' WHEN stringColumn = 'a22' THEN 'a' ELSE 'c' END) " + + "FROM testTable"; + + brokerResponse = getBrokerResponse(query); + resultTable = brokerResponse.getResultTable(); + rows = resultTable.getRows(); + + assertEquals(rows.size(), 4); + + for (int i = 0; i < 4; i++) { + assertEquals(rows.get(i)[0], "a22"); + assertEquals(rows.get(i)[1], "a"); + } + + // TODO: The following query throws an exception, + // requires fix for multi-value bytes column serialization in DataBlock + query = "SELECT expr_min(mvBytesColumn, intColumn) FROM testTable"; + + try { + getBrokerResponse(query); + fail("remove this test case, now mvBytesColumn works correctly in serialization"); + } catch (Exception e) { + assertTrue(e.getMessage().contains("Unsupported stored type: BYTES_ARRAY")); + } + } + + @Test + public void testAggregationDedupe() { + // Inter segment dedupe test1 without dedupe + String query = "SELECT " + + "exprmin(intColumn, booleanColumn, bigDecimalColumn) FROM testTable WHERE doubleColumn <= 1200"; + + BrokerResponseNative brokerResponse = getBrokerResponse(query); + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + + assertEquals(rows.size(), 4); + + assertEquals(rows.get(0)[0], 0); + assertEquals(rows.get(1)[0], 1200); + assertEquals(rows.get(2)[0], 0); + assertEquals(rows.get(3)[0], 1200); + + // test1, with dedupe + query = "SELECT " + + "exprmin(intColumn, booleanColumn, bigDecimalColumn, doubleColumn) FROM testTable WHERE doubleColumn <= 1200"; + + brokerResponse = getBrokerResponse(query); + resultTable = brokerResponse.getResultTable(); + rows = resultTable.getRows(); + + assertEquals(rows.size(), 2); + + assertEquals(rows.get(0)[0], 0); + assertEquals(rows.get(1)[0], 0); + + // test2, with dedupe + query = "SELECT " + + "exprmin(intColumn, booleanColumn, bigDecimalColumn, 0-doubleColumn) FROM testTable WHERE doubleColumn <= " + + "1200"; + + brokerResponse = getBrokerResponse(query); + resultTable = brokerResponse.getResultTable(); + rows = resultTable.getRows(); + + assertEquals(rows.size(), 2); + + assertEquals(rows.get(0)[0], 1200); + assertEquals(rows.get(1)[0], 1200); + } + + @Test + public void testEmptyAggregation() { + // Inter segment mix aggregation with no documents after filtering + String query = + "SELECT expr_max(longColumn, intColumn), exprmin(stringColumn, CASE WHEN stringColumn = 'a33' THEN 'b' " + + "WHEN stringColumn = 'a22' THEN 'a' ELSE 'c' END) FROM testTable where intColumn > 10000"; + + BrokerResponseNative brokerResponse = getBrokerResponse(query); + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + assertNull(rows.get(0)[0]); + assertNull(rows.get(0)[1]); + assertEquals(resultTable.getDataSchema().getColumnName(0), "exprmax(longColumn,intColumn)"); + assertEquals(resultTable.getDataSchema().getColumnName(1), + "exprmin(stringColumn,case(equals(stringColumn,'a33'),'b',equals(stringColumn,'a22'),'a','c'))"); + Assert.assertEquals(resultTable.getDataSchema().getColumnDataType(0), DataSchema.ColumnDataType.STRING); + Assert.assertEquals(resultTable.getDataSchema().getColumnDataType(1), DataSchema.ColumnDataType.STRING); + } + + @Test + public void testGroupByInterSegment() { + // Simple inter segment group by + String query = "SELECT groupByIntColumn, expr_max(longColumn, intColumn) FROM testTable GROUP BY groupByIntColumn"; + + BrokerResponseNative brokerResponse = getBrokerResponse(query); + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + + assertEquals(rows.size(), 10); + + for (int i = 0; i < 10; i++) { + int group = ((i + 2) / 2) % 5; + assertEquals(rows.get(i)[0], group); + assertEquals(rows.get(i)[1], 995L + group); + } + + // Simple inter segment group by with limit + query = + "SELECT groupByIntColumn2, expr_max(doubleColumn, longColumn) FROM testTable GROUP BY groupByIntColumn2 ORDER " + + "BY groupByIntColumn2 LIMIT 15"; + + brokerResponse = getBrokerResponse(query); + resultTable = brokerResponse.getResultTable(); + rows = resultTable.getRows(); + + assertEquals(rows.size(), 24); + + for (int i = 0; i < 22; i++) { + double group = Math.pow(2, i / 2); + assertEquals(rows.get(i)[0], (int) group); + assertEquals(rows.get(i)[1], group - 1); + } + + assertEquals(rows.get(22)[0], 2048); + assertEquals(rows.get(22)[1], 1999D); + + assertEquals(rows.get(23)[0], 2048); + assertEquals(rows.get(23)[1], 1999D); + + // MV inter segment group by + query = "SELECT groupByMVIntColumn, expr_min(doubleColumn, intColumn) FROM testTable GROUP BY groupByMVIntColumn"; + + brokerResponse = getBrokerResponse(query); + resultTable = brokerResponse.getResultTable(); + rows = resultTable.getRows(); + + assertEquals(rows.size(), 20); + + for (int i = 0; i < 18; i++) { + int group = i / 2 + 1; + assertEquals(rows.get(i)[0], group); + assertEquals(rows.get(i)[1], (double) group - 1); + } + + assertEquals(rows.get(18)[0], 0); + assertEquals(rows.get(18)[1], 0D); + + assertEquals(rows.get(19)[0], 0); + assertEquals(rows.get(19)[1], 0D); + + // MV inter segment group by with projection on MV column + query = "SELECT groupByMVIntColumn, expr_min(mvIntColumn, intColumn), " + + "expr_max(mvStringColumn, intColumn) FROM testTable GROUP BY groupByMVIntColumn"; + + brokerResponse = getBrokerResponse(query); + resultTable = brokerResponse.getResultTable(); + rows = resultTable.getRows(); + assertEquals(rows.size(), 20); + + for (int i = 0; i < 18; i++) { + int group = i / 2 + 1; + assertEquals(rows.get(i)[0], group); + assertEquals(rows.get(i)[1], new Object[]{group - 1, group, group + 1}); + assertEquals(rows.get(i)[2], new Object[]{"a199" + group, "a199" + group + 1, "a199" + group + 2}); + } + + assertEquals(rows.get(18)[0], 0); + assertEquals(rows.get(18)[1], new Object[]{0, 1, 2}); + assertEquals(rows.get(18)[2], new Object[]{"a1999", "a19991", "a19992"}); + } + + @Test + public void testGroupByInterSegmentWithValueIn() { + // MV VALUE_IN segment group by + String query = + "SELECT stringColumn, expr_min(VALUE_IN(mvIntColumn,16,17,18,19,20,21,22,23,24,25,26,27), intColumn), " + + "expr_max(VALUE_IN(mvIntColumn,16,17,18,19,20,21,22,23,24,25,26,27), intColumn) " + + "FROM testTable WHERE mvIntColumn in (16,17,18,19,20,21,22,23,24,25,26,27) GROUP BY stringColumn"; + + BrokerResponse brokerResponse = getBrokerResponse(query); + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 14); + assertEquals(rows.get(4)[0], "a33"); + assertEquals(rows.get(4)[1], new Object[]{20, 21, 22}); + assertEquals(rows.get(4)[2], new Object[]{27}); + + // TODO: The following query works because whenever we find an empty array in the result, we use null + // (see exprminMaxProjectionValSetWrapper). Ideally, we should be able to serialize empty array. + // requires fix for empty int arrays ser/de in DataBlock + query = + "SELECT stringColumn, expr_min(VALUE_IN(mvIntColumn,16,17,18,19,20,21,22,23,24,25,26,27), intColumn), " + + "expr_max(VALUE_IN(mvIntColumn,16,17,18,19,20,21,22,23,24,25,26,27), intColumn) " + + "FROM testTable GROUP BY stringColumn"; + + brokerResponse = getBrokerResponse(query); + resultTable = brokerResponse.getResultTable(); + rows = resultTable.getRows(); + assertEquals(rows.size(), 20); + assertEquals(rows.get(8)[0], "a33"); + assertEquals(rows.get(8)[1], new Object[]{20, 21, 22}); + assertEquals(rows.get(8)[2], new Object[]{}); + } + + @Test + public void explainPlanTest() { + String query = "EXPLAIN PLAN FOR SELECT groupByMVIntColumn, expr_min(mvIntColumn, intColumn), " + + "expr_min(mvStringColumn, intColumn, doubleColumn) FROM testTable GROUP BY groupByMVIntColumn"; + BrokerResponseNative brokerResponse = getBrokerResponse(query); + Object groupByExplainPlan = brokerResponse.getResultTable().getRows().get(3)[0]; + String explainPlan = groupByExplainPlan.toString(); + Assert.assertTrue( + explainPlan.contains(CHILD_AGGREGATION_NAME_PREFIX + "_exprMin('0', mvIntColumn, mvIntColumn, intColumn)")); + Assert.assertTrue(explainPlan.contains( + CHILD_AGGREGATION_NAME_PREFIX + "_exprMin('1', mvStringColumn, mvStringColumn, intColumn, doubleColumn)")); + Assert.assertTrue( + explainPlan.contains(PARENT_AGGREGATION_NAME_PREFIX + "_exprMin('0', '1', intColumn, mvIntColumn)")); + Assert.assertTrue(explainPlan.contains( + PARENT_AGGREGATION_NAME_PREFIX + "_exprMin('1', '2', intColumn, doubleColumn, mvStringColumn)")); + } + + @Test + public void testEmptyGroupByInterSegment() { + // Simple inter segment group by with no documents after filtering + String query = "SELECT groupByIntColumn, expr_max(longColumn, intColumn) FROM testTable " + + " where intColumn > 10000 GROUP BY groupByIntColumn"; + + BrokerResponseNative brokerResponse = getBrokerResponse(query); + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + + assertEquals(resultTable.getDataSchema().getColumnName(0), "groupByIntColumn"); + assertEquals(resultTable.getDataSchema().getColumnName(1), "exprmax(longColumn,intColumn)"); + assertEquals(resultTable.getDataSchema().getColumnDataType(0), DataSchema.ColumnDataType.INT); + assertEquals(resultTable.getDataSchema().getColumnDataType(1), DataSchema.ColumnDataType.STRING); + assertEquals(rows.size(), 0); + + // Simple inter segment group by with no documents after filtering + query = "SELECT groupByIntColumn, expr_max(longColumn, intColumn), sum(longColumn), expr_min(longColumn, intColumn)" + + " FROM testTable " + + " where intColumn > 10000 GROUP BY groupByIntColumn"; + + brokerResponse = getBrokerResponse(query); + resultTable = brokerResponse.getResultTable(); + rows = resultTable.getRows(); + assertEquals(resultTable.getDataSchema().getColumnName(0), "groupByIntColumn"); + assertEquals(resultTable.getDataSchema().getColumnName(1), "exprmax(longColumn,intColumn)"); + assertEquals(resultTable.getDataSchema().getColumnName(2), "sum(longColumn)"); + assertEquals(resultTable.getDataSchema().getColumnName(3), "exprmin(longColumn,intColumn)"); + assertEquals(resultTable.getDataSchema().getColumnDataType(0), DataSchema.ColumnDataType.INT); + assertEquals(resultTable.getDataSchema().getColumnDataType(1), DataSchema.ColumnDataType.STRING); + assertEquals(resultTable.getDataSchema().getColumnDataType(0), DataSchema.ColumnDataType.INT); + assertEquals(resultTable.getDataSchema().getColumnDataType(1), DataSchema.ColumnDataType.STRING); + assertEquals(rows.size(), 0); + } + + @Test + public void testAlias() { + // Using exprmin/exprmax with alias will fail, since the alias will not be resolved by the rewriter + try { + String query = "SELECT groupByIntColumn, expr_max(longColumn, intColumn) AS" + + " exprmax1 FROM testTable GROUP BY groupByIntColumn"; + BrokerResponseNative brokerResponse = getBrokerResponse(query); + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + fail(); + } catch (BadQueryRequestException e) { + assertTrue( + e.getMessage().contains("Aggregation function: EXPRMAX is only supported in selection without alias.")); + } + } + + @Test + public void testOrderBy() { + // Using exprmin/exprmax with order by will fail, since the ordering on a multi-row projection is not well-defined + try { + String query = "SELECT groupByIntColumn, expr_max(longColumn, intColumn) FROM testTable " + + "GROUP BY groupByIntColumn ORDER BY expr_max(longColumn, intColumn)"; + BrokerResponseNative brokerResponse = getBrokerResponse(query); + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + fail(); + } catch (Exception e) { + assertTrue( + e.getMessage().contains("Aggregation function: EXPRMAX is only supported in selection without alias.")); + } + } + + @AfterClass + public void tearDown() + throws IOException { + _indexSegment.destroy(); + FileUtils.deleteDirectory(INDEX_DIR); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/FastHllQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/FastHllQueriesTest.java index b817adb28140..f2f1370c5b32 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/FastHllQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/FastHllQueriesTest.java @@ -193,8 +193,6 @@ private void buildAndLoadSegment() segmentGeneratorConfig.setInputFilePath(filePath); segmentGeneratorConfig.setTableName("testTable"); segmentGeneratorConfig.setOutDir(INDEX_DIR.getAbsolutePath()); - segmentGeneratorConfig.setInvertedIndexCreationColumns( - Arrays.asList("column6", "column7", "column11", "column17", "column18")); // Build the index segment SegmentIndexCreationDriver driver = new SegmentIndexCreationDriverImpl(); diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/FilteredAggregationsTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/FilteredAggregationsTest.java index 2fc9ad1fa6d3..d4f5516e2817 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/FilteredAggregationsTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/FilteredAggregationsTest.java @@ -26,8 +26,8 @@ import java.util.List; import java.util.Set; import org.apache.commons.io.FileUtils; -import org.apache.commons.lang.RandomStringUtils; -import org.apache.commons.lang.math.RandomUtils; +import org.apache.commons.lang3.RandomStringUtils; +import org.apache.commons.lang3.RandomUtils; import org.apache.pinot.common.response.broker.ResultTable; import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; @@ -94,6 +94,7 @@ public void setUp() invertedIndexCols.add(INT_COL_NAME); indexLoadingConfig.setInvertedIndexColumns(invertedIndexCols); + indexLoadingConfig.setRangeIndexColumns(invertedIndexCols); ImmutableSegment firstImmutableSegment = ImmutableSegmentLoader.load(new File(INDEX_DIR, FIRST_SEGMENT_NAME), indexLoadingConfig); ImmutableSegment secondImmutableSegment = @@ -161,51 +162,84 @@ private void testQuery(String filterQuery, String nonFilterQuery) { @Test public void testSimpleQueries() { - String filterQuery = "SELECT SUM(INT_COL) FILTER(WHERE INT_COL > 9999) FROM MyTable WHERE INT_COL < 1000000"; - String nonFilterQuery = "SELECT SUM(INT_COL) FROM MyTable WHERE INT_COL > 9999 AND INT_COL < 1000000"; + String filterQuery = "SELECT SUM(INT_COL) FILTER(WHERE INT_COL > 9999) sum1 FROM MyTable WHERE INT_COL < 1000000"; + String nonFilterQuery = "SELECT SUM(INT_COL) sum1 FROM MyTable WHERE INT_COL > 9999 AND INT_COL < 1000000"; + testQuery(filterQuery, nonFilterQuery); + + filterQuery = "SELECT SUM(INT_COL) FILTER(WHERE INT_COL < 3) sum1 FROM MyTable WHERE INT_COL > 1"; + nonFilterQuery = "SELECT SUM(INT_COL) sum1 FROM MyTable WHERE INT_COL > 1 AND INT_COL < 3"; + testQuery(filterQuery, nonFilterQuery); + + filterQuery = "SELECT COUNT(*) FILTER(WHERE INT_COL = 4) count1 FROM MyTable"; + nonFilterQuery = "SELECT COUNT(*) count1 FROM MyTable WHERE INT_COL = 4"; testQuery(filterQuery, nonFilterQuery); - filterQuery = "SELECT SUM(INT_COL) FILTER(WHERE INT_COL < 3) FROM MyTable WHERE INT_COL > 1"; - nonFilterQuery = "SELECT SUM(INT_COL) FROM MyTable WHERE INT_COL > 1 AND INT_COL < 3"; + filterQuery = "SELECT SUM(INT_COL) FILTER(WHERE INT_COL > 8000) sum1 FROM MyTable "; + nonFilterQuery = "SELECT SUM(INT_COL) sum1 FROM MyTable WHERE INT_COL > 8000"; testQuery(filterQuery, nonFilterQuery); - filterQuery = "SELECT COUNT(*) FILTER(WHERE INT_COL = 4) FROM MyTable"; - nonFilterQuery = "SELECT COUNT(*) FROM MyTable WHERE INT_COL = 4"; + filterQuery = "SELECT SUM(INT_COL) FILTER(WHERE NO_INDEX_COL <= 1) sum1 FROM MyTable WHERE INT_COL > 1"; + nonFilterQuery = "SELECT SUM(INT_COL) sum1 FROM MyTable WHERE NO_INDEX_COL <= 1 AND INT_COL > 1"; testQuery(filterQuery, nonFilterQuery); - filterQuery = "SELECT SUM(INT_COL) FILTER(WHERE INT_COL > 8000) FROM MyTable "; - nonFilterQuery = "SELECT SUM(INT_COL) FROM MyTable WHERE INT_COL > 8000"; + filterQuery = "SELECT AVG(NO_INDEX_COL) avg1 FROM MyTable WHERE NO_INDEX_COL > -1"; + nonFilterQuery = "SELECT AVG(NO_INDEX_COL) avg1 FROM MyTable"; + testQuery(filterQuery, nonFilterQuery); + + filterQuery = "SELECT AVG(INT_COL) FILTER(WHERE NO_INDEX_COL > -1) avg1 FROM MyTable"; + nonFilterQuery = "SELECT AVG(INT_COL) avg1 FROM MyTable"; + testQuery(filterQuery, nonFilterQuery); + + filterQuery = + "SELECT MIN(INT_COL) FILTER(WHERE NO_INDEX_COL > 29990) min1, MAX(INT_COL) FILTER(WHERE INT_COL > 29990) max1" + + " FROM MyTable"; + nonFilterQuery = "SELECT MIN(INT_COL) min1, MAX(INT_COL) max1 FROM MyTable WHERE INT_COL > 29990"; testQuery(filterQuery, nonFilterQuery); - filterQuery = "SELECT SUM(INT_COL) FILTER(WHERE NO_INDEX_COL <= 1) FROM MyTable WHERE INT_COL > 1"; - nonFilterQuery = "SELECT SUM(INT_COL) FROM MyTable WHERE NO_INDEX_COL <= 1 AND INT_COL > 1"; + filterQuery = "SELECT SUM(INT_COL) FILTER(WHERE BOOLEAN_COL) sum1 FROM MyTable"; + nonFilterQuery = "SELECT SUM(INT_COL) sum1 FROM MyTable WHERE BOOLEAN_COL=true"; testQuery(filterQuery, nonFilterQuery); - filterQuery = "SELECT AVG(NO_INDEX_COL) FROM MyTable WHERE NO_INDEX_COL > -1"; - nonFilterQuery = "SELECT AVG(NO_INDEX_COL) FROM MyTable"; + filterQuery = "SELECT SUM(INT_COL) FILTER(WHERE BOOLEAN_COL AND STARTSWITH(STRING_COL, 'abc')) sum1 FROM MyTable"; + nonFilterQuery = "SELECT SUM(INT_COL) sum1 FROM MyTable WHERE BOOLEAN_COL=true AND STARTSWITH(STRING_COL, 'abc')"; testQuery(filterQuery, nonFilterQuery); - filterQuery = "SELECT AVG(INT_COL) FILTER(WHERE NO_INDEX_COL > -1) FROM MyTable"; - nonFilterQuery = "SELECT AVG(INT_COL) FROM MyTable"; + filterQuery = + "SELECT SUM(INT_COL) FILTER(WHERE BOOLEAN_COL AND STARTSWITH(REVERSE(STRING_COL), 'abc')) sum1 FROM MyTable"; + nonFilterQuery = + "SELECT SUM(INT_COL) sum1 FROM MyTable WHERE BOOLEAN_COL=true AND STARTSWITH(REVERSE(STRING_COL), " + "'abc')"; testQuery(filterQuery, nonFilterQuery); + } - filterQuery = "SELECT MIN(INT_COL) FILTER(WHERE NO_INDEX_COL > 29990), MAX(INT_COL) FILTER(WHERE INT_COL > 29990) " - + "FROM MyTable"; - nonFilterQuery = "SELECT MIN(INT_COL), MAX(INT_COL) FROM MyTable WHERE INT_COL > 29990"; + @Test + public void testFilterResultColumnNameGroupBy() { + String filterQuery = + "SELECT SUM(INT_COL) FILTER(WHERE INT_COL > 9999) FROM MyTable WHERE INT_COL < 1000000 GROUP BY BOOLEAN_COL"; + String nonFilterQuery = + "SELECT SUM(INT_COL) \"sum(INT_COL) FILTER(WHERE INT_COL > '9999')\" FROM MyTable WHERE INT_COL > 9999 AND " + + "INT_COL < 1000000 GROUP BY BOOLEAN_COL"; testQuery(filterQuery, nonFilterQuery); - filterQuery = "SELECT SUM(INT_COL) FILTER(WHERE BOOLEAN_COL) FROM MyTable"; - nonFilterQuery = "SELECT SUM(INT_COL) FROM MyTable WHERE BOOLEAN_COL=true"; + filterQuery = + "SELECT SUM(INT_COL) FILTER(WHERE INT_COL > 9999 AND INT_COL < 1000000) FROM MyTable GROUP BY BOOLEAN_COL"; + nonFilterQuery = + "SELECT SUM(INT_COL) \"sum(INT_COL) FILTER(WHERE (INT_COL > '9999' AND INT_COL < '1000000'))\" FROM MyTable " + + "WHERE INT_COL > 9999 AND INT_COL < 1000000 GROUP BY BOOLEAN_COL"; testQuery(filterQuery, nonFilterQuery); + } - filterQuery = "SELECT SUM(INT_COL) FILTER(WHERE BOOLEAN_COL AND STARTSWITH(STRING_COL, 'abc')) FROM MyTable"; - nonFilterQuery = "SELECT SUM(INT_COL) FROM MyTable WHERE BOOLEAN_COL=true AND STARTSWITH(STRING_COL, 'abc')"; + @Test + public void testFilterResultColumnNameNonGroupBy() { + String filterQuery = "SELECT SUM(INT_COL) FILTER(WHERE INT_COL > 9999) FROM MyTable WHERE INT_COL < 1000000"; + String nonFilterQuery = + "SELECT SUM(INT_COL) \"sum(INT_COL) FILTER(WHERE INT_COL > '9999')\" FROM MyTable WHERE INT_COL > 9999 AND " + + "INT_COL < 1000000"; testQuery(filterQuery, nonFilterQuery); - filterQuery = "SELECT SUM(INT_COL) FILTER(WHERE BOOLEAN_COL AND STARTSWITH(REVERSE(STRING_COL), 'abc')) FROM " - + "MyTable"; - nonFilterQuery = "SELECT SUM(INT_COL) FROM MyTable WHERE BOOLEAN_COL=true AND STARTSWITH(REVERSE(STRING_COL), " - + "'abc')"; + filterQuery = "SELECT SUM(INT_COL) FILTER(WHERE INT_COL > 9999 AND INT_COL < 1000000) FROM MyTable"; + nonFilterQuery = + "SELECT SUM(INT_COL) \"sum(INT_COL) FILTER(WHERE (INT_COL > '9999' AND INT_COL < '1000000'))\" FROM MyTable " + + "WHERE INT_COL > 9999 AND INT_COL < 1000000"; testQuery(filterQuery, nonFilterQuery); } @@ -305,9 +339,9 @@ public void testFilterVsCase() { @Test public void testMultipleAggregationsOnSameFilter() { - String filterQuery = "SELECT MIN(INT_COL) FILTER(WHERE NO_INDEX_COL > 29990), " - + "MAX(INT_COL) FILTER(WHERE INT_COL > 29990) FROM MyTable"; - String nonFilterQuery = "SELECT MIN(INT_COL), MAX(INT_COL) FROM MyTable WHERE INT_COL > 29990"; + String filterQuery = "SELECT MIN(INT_COL) FILTER(WHERE NO_INDEX_COL > 29990) testMin, " + + "MAX(INT_COL) FILTER(WHERE INT_COL > 29990) testMax FROM MyTable"; + String nonFilterQuery = "SELECT MIN(INT_COL) testMin, MAX(INT_COL) testMax FROM MyTable WHERE INT_COL > 29990"; testQuery(filterQuery, nonFilterQuery); filterQuery = "SELECT MIN(INT_COL) FILTER(WHERE NO_INDEX_COL > 29990) AS total_min, " @@ -321,6 +355,26 @@ public void testMultipleAggregationsOnSameFilter() { testQuery(filterQuery, nonFilterQuery); } + @Test + public void testMultipleAggregationsOnSameFilterOrderByFiltered() { + String filterQuery = "SELECT MIN(INT_COL) FILTER(WHERE NO_INDEX_COL > 29990) testMin, " + + "MAX(INT_COL) FILTER(WHERE INT_COL > 29990) testMax FROM MyTable ORDER BY testMax"; + String nonFilterQuery = + "SELECT MIN(INT_COL) testMin, MAX(INT_COL) testMax FROM MyTable WHERE INT_COL > 29990 ORDER BY testMax"; + testQuery(filterQuery, nonFilterQuery); + + filterQuery = "SELECT MIN(INT_COL) FILTER(WHERE NO_INDEX_COL > 29990) AS total_min, " + + "MAX(INT_COL) FILTER(WHERE INT_COL > 29990) AS total_max, " + + "SUM(INT_COL) FILTER(WHERE NO_INDEX_COL < 5000) AS total_sum, " + + "MAX(NO_INDEX_COL) FILTER(WHERE NO_INDEX_COL < 5000) AS total_max2 FROM MyTable ORDER BY total_sum"; + nonFilterQuery = "SELECT MIN(CASE WHEN (NO_INDEX_COL > 29990) THEN INT_COL ELSE 99999 END) AS total_min, " + + "MAX(CASE WHEN (INT_COL > 29990) THEN INT_COL ELSE 0 END) AS total_max, " + + "SUM(CASE WHEN (NO_INDEX_COL < 5000) THEN INT_COL ELSE 0 END) AS total_sum, " + + "MAX(CASE WHEN (NO_INDEX_COL < 5000) THEN NO_INDEX_COL ELSE 0 END) AS total_max2 FROM MyTable ORDER BY " + + "total_sum"; + testQuery(filterQuery, nonFilterQuery); + } + @Test public void testMixedAggregationsOfSameType() { String filterQuery = "SELECT SUM(INT_COL), SUM(INT_COL) FILTER(WHERE INT_COL > 25000) AS total_sum FROM MyTable"; @@ -335,10 +389,99 @@ public void testMixedAggregationsOfSameType() { testQuery(filterQuery, nonFilterQuery); } - @Test(expectedExceptions = IllegalStateException.class) - public void testGroupBySupport() { - String filterQuery = "SELECT MIN(INT_COL) FILTER(WHERE NO_INDEX_COL > 2), MAX(INT_COL) FILTER(WHERE INT_COL > 2) " - + "FROM MyTable WHERE INT_COL < 1000 GROUP BY INT_COL"; - getBrokerResponse(filterQuery); + @Test + public void testGroupBy() { + String filterQuery = "SELECT SUM(INT_COL) FILTER(WHERE INT_COL > 25000) testSum FROM MyTable GROUP BY BOOLEAN_COL " + + "ORDER BY BOOLEAN_COL"; + String nonFilterQuery = + "SELECT SUM(INT_COL) testSum FROM MyTable WHERE INT_COL > 25000 GROUP BY BOOLEAN_COL ORDER BY BOOLEAN_COL"; + testQuery(filterQuery, nonFilterQuery); + } + + @Test + public void testGroupByMultipleColumns() { + String filterQuery = + "SELECT SUM(INT_COL) FILTER(WHERE INT_COL > 25000) testSum FROM MyTable GROUP BY BOOLEAN_COL, STRING_COL " + + "ORDER BY BOOLEAN_COL, STRING_COL"; + String nonFilterQuery = + "SELECT SUM(INT_COL) testSum FROM MyTable WHERE INT_COL > 25000 GROUP BY BOOLEAN_COL, STRING_COL " + + "ORDER BY BOOLEAN_COL, STRING_COL"; + testQuery(filterQuery, nonFilterQuery); + } + + @Test + public void testGroupByCaseAlternative() { + String filterQuery = "SELECT SUM(INT_COL), SUM(INT_COL) FILTER(WHERE INT_COL > 25000) AS total_sum FROM MyTable " + + "GROUP BY BOOLEAN_COL ORDER BY BOOLEAN_COL"; + String nonFilterQuery = + "SELECT SUM(INT_COL), SUM(CASE WHEN INT_COL > 25000 THEN INT_COL ELSE 0 END) AS total_sum FROM MyTable " + + "GROUP BY BOOLEAN_COL ORDER BY BOOLEAN_COL"; + testQuery(filterQuery, nonFilterQuery); + } + + @Test + public void testGroupBySameFilter() { + String filterQuery = + "SELECT AVG(INT_COL) FILTER(WHERE INT_COL > 25000) testAvg, SUM(INT_COL) FILTER(WHERE INT_COL > 25000) testSum " + + "FROM MyTable GROUP BY BOOLEAN_COL ORDER BY BOOLEAN_COL"; + String nonFilterQuery = "SELECT AVG(INT_COL) testAvg, SUM(INT_COL) testSum FROM MyTable WHERE INT_COL > 25000 " + + "GROUP BY BOOLEAN_COL ORDER BY BOOLEAN_COL"; + testQuery(filterQuery, nonFilterQuery); + } + + @Test + public void testMultipleAggregationsOnSameFilterGroupBy() { + String filterQuery = "SELECT MIN(INT_COL) FILTER(WHERE NO_INDEX_COL > 29990) testMin, " + + "MAX(INT_COL) FILTER(WHERE INT_COL > 29990) testMax FROM MyTable GROUP BY BOOLEAN_COL ORDER BY BOOLEAN_COL"; + String nonFilterQuery = + "SELECT MIN(INT_COL) testMin, MAX(INT_COL) testMax FROM MyTable WHERE INT_COL > 29990 GROUP BY BOOLEAN_COL " + + "ORDER BY BOOLEAN_COL"; + testQuery(filterQuery, nonFilterQuery); + + filterQuery = "SELECT MIN(INT_COL) FILTER(WHERE NO_INDEX_COL > 29990) AS total_min, " + + "MAX(INT_COL) FILTER(WHERE INT_COL > 29990) AS total_max, " + + "SUM(INT_COL) FILTER(WHERE NO_INDEX_COL < 5000) AS total_sum, " + + "MAX(NO_INDEX_COL) FILTER(WHERE NO_INDEX_COL < 5000) AS total_max2 " + + "FROM MyTable GROUP BY BOOLEAN_COL ORDER BY BOOLEAN_COL"; + nonFilterQuery = "SELECT MIN(CASE WHEN (NO_INDEX_COL > 29990) THEN INT_COL ELSE 99999 END) AS total_min, " + + "MAX(CASE WHEN (INT_COL > 29990) THEN INT_COL ELSE 0 END) AS total_max, " + + "SUM(CASE WHEN (NO_INDEX_COL < 5000) THEN INT_COL ELSE 0 END) AS total_sum, " + + "MAX(CASE WHEN (NO_INDEX_COL < 5000) THEN NO_INDEX_COL ELSE 0 END) AS total_max2 " + + "FROM MyTable GROUP BY BOOLEAN_COL ORDER BY BOOLEAN_COL"; + testQuery(filterQuery, nonFilterQuery); + } + + @Test + public void testGroupBySameFilterOrderByFiltered() { + String filterQuery = + "SELECT AVG(INT_COL) FILTER(WHERE INT_COL > 25000) testAvg, SUM(INT_COL) FILTER(WHERE INT_COL > 25000) " + + "testSum FROM MyTable GROUP BY BOOLEAN_COL ORDER BY testAvg"; + String nonFilterQuery = + "SELECT AVG(INT_COL) testAvg, SUM(INT_COL) testSum FROM MyTable WHERE INT_COL > 25000 GROUP BY BOOLEAN_COL " + + "ORDER BY testAvg"; + testQuery(filterQuery, nonFilterQuery); + } + + @Test + public void testSameNumScannedFilteredAggMatchAll() { + // For a single filtered aggregation, the same number of docs should be scanned regardless of which portions of + // the filter are in the filter expression Vs. the main predicate i.e. the applied filters are commutative. + String filterQuery = "SELECT SUM(INT_COL) FILTER(WHERE INT_COL > 25000) testSum FROM MyTable"; + String nonFilterQuery = "SELECT SUM(INT_COL) testSum FROM MyTable WHERE INT_COL > 25000"; + long filterQueryDocsScanned = getBrokerResponse(filterQuery).getNumDocsScanned(); + long nonFilterQueryDocsScanned = getBrokerResponse(nonFilterQuery).getNumDocsScanned(); + assertEquals(filterQueryDocsScanned, nonFilterQueryDocsScanned); + } + + @Test + public void testSameNumScannedFilteredAgg() { + // For a single filtered aggregation, the same number of docs should be scanned regardless of which portions of + // the filter are in the filter expression Vs. the main predicate i.e. the applied filters are commutative. + String filterQuery = + "SELECT SUM(INT_COL) FILTER(WHERE INT_COL > 25000) testSum FROM MyTable WHERE INT_COL < 1000000"; + String nonFilterQuery = "SELECT SUM(INT_COL) testSum FROM MyTable WHERE INT_COL > 25000 AND INT_COL < 1000000"; + long filterQueryDocsScanned = getBrokerResponse(filterQuery).getNumDocsScanned(); + long nonFilterQueryDocsScanned = getBrokerResponse(nonFilterQuery).getNumDocsScanned(); + assertEquals(filterQueryDocsScanned, nonFilterQueryDocsScanned); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/FluentQueryTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/FluentQueryTest.java new file mode 100644 index 000000000000..c453c93d1caf --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/queries/FluentQueryTest.java @@ -0,0 +1,434 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.pinot.queries; + +import com.google.common.collect.Lists; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.CSVPrinter; +import org.apache.pinot.common.response.BrokerResponse; +import org.apache.pinot.common.response.broker.BrokerResponseNative; +import org.apache.pinot.common.utils.PinotDataType; +import org.apache.pinot.plugin.inputformat.csv.CSVRecordReaderConfig; +import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; +import org.apache.pinot.segment.local.segment.creator.SegmentTestUtils; +import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; +import org.apache.pinot.segment.spi.ImmutableSegment; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; +import org.apache.pinot.segment.spi.creator.SegmentIndexCreationDriver; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.spi.data.readers.FileFormat; +import org.apache.pinot.spi.utils.ReadMode; +import org.testng.Assert; + + +public class FluentQueryTest { + + private final FluentBaseQueriesTest _baseQueriesTest; + private final File _baseDir; + private final Map _extraQueryOptions = new HashMap<>(); + + private FluentQueryTest(FluentBaseQueriesTest baseQueriesTest, File baseDir) { + _baseQueriesTest = baseQueriesTest; + _baseDir = baseDir; + } + + public static FluentQueryTest withBaseDir(File baseDir) { + return new FluentQueryTest(new FluentBaseQueriesTest(), baseDir); + } + + public FluentQueryTest withExtraQueryOptions(Map extraQueryOptions) { + _extraQueryOptions.clear(); + _extraQueryOptions.putAll(extraQueryOptions); + return this; + } + + public FluentQueryTest withNullHandling(boolean enabled) { + _extraQueryOptions.put("enableNullHandling", Boolean.toString(enabled)); + return this; + } + + public DeclaringTable givenTable(Schema schema, TableConfig tableConfig) { + return new DeclaringTable(_baseQueriesTest, tableConfig, schema, _baseDir, _extraQueryOptions); + } + + public static class DeclaringTable { + private final FluentBaseQueriesTest _baseQueriesTest; + private final TableConfig _tableConfig; + private final Schema _schema; + private final File _baseDir; + private final Map _extraQueryOptions; + + DeclaringTable(FluentBaseQueriesTest baseQueriesTest, TableConfig tableConfig, Schema schema, File baseDir, + Map extraQueryOptions) { + _baseQueriesTest = baseQueriesTest; + _tableConfig = tableConfig; + _schema = schema; + _baseDir = baseDir; + _extraQueryOptions = extraQueryOptions; + } + + public OnFirstInstance onFirstInstance(String... content) { + return new OnFirstInstance(_tableConfig, _schema, _baseDir, false, _baseQueriesTest, _extraQueryOptions) + .andSegment(content); + } + + public OnFirstInstance onFirstInstance(Object[]... content) { + return new OnFirstInstance(_tableConfig, _schema, _baseDir, false, _baseQueriesTest, _extraQueryOptions) + .andSegment(content); + } + } + + public static class TableWithSegments { + protected final TableConfig _tableConfig; + protected final Schema _schema; + protected final File _indexDir; + protected final boolean _onSecondInstance; + protected final FluentBaseQueriesTest _baseQueriesTest; + protected final List _segmentContents = new ArrayList<>(); + protected final Map _extraQueryOptions; + + TableWithSegments(TableConfig tableConfig, Schema schema, File baseDir, boolean onSecondInstance, + FluentBaseQueriesTest baseQueriesTest, Map extraQueryOptions) { + _extraQueryOptions = extraQueryOptions; + try { + _tableConfig = tableConfig; + _schema = schema; + _indexDir = Files.createTempDirectory(baseDir.toPath(), schema.getSchemaName()).toFile(); + _onSecondInstance = onSecondInstance; + _baseQueriesTest = baseQueriesTest; + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + } + + TableWithSegments andSegment(String... tableText) { + _segmentContents.add(new FakeSegmentContent(_schema, tableText)); + return this; + } + + public TableWithSegments andSegment(Object[]... content) { + _segmentContents.add(new FakeSegmentContent(content)); + return this; + } + + protected void processSegments() { + List indexSegments = new ArrayList<>(_segmentContents.size()); + + try { + for (int i = 0; i < _segmentContents.size(); i++) { + FakeSegmentContent segmentContent = _segmentContents.get(i); + File inputFile = Files.createTempFile(_indexDir.toPath(), "data", ".csv").toFile(); + try (CSVPrinter csvPrinter = new CSVPrinter(new FileWriter(inputFile), CSVFormat.DEFAULT)) { + for (List row : segmentContent) { + if (row.stream().anyMatch(Objects::isNull)) { + List newRow = row.stream().map(o -> o == null ? "null" : o).collect(Collectors.toList()); + csvPrinter.printRecord(newRow); + } else { + csvPrinter.printRecord(row); + } + } + } catch (IOException ex) { + throw new UncheckedIOException(ex); + } + String tableName = _schema.getSchemaName(); + SegmentGeneratorConfig config = + SegmentTestUtils.getSegmentGeneratorConfig(inputFile, FileFormat.CSV, _indexDir, tableName, _tableConfig, + _schema); + CSVRecordReaderConfig csvRecordReaderConfig = new CSVRecordReaderConfig(); + String header = String.join(",", _schema.getPhysicalColumnNames()); + csvRecordReaderConfig.setHeader(header); + csvRecordReaderConfig.setSkipHeader(false); + csvRecordReaderConfig.setNullStringValue("null"); + config.setReaderConfig(csvRecordReaderConfig); + config.setSegmentNamePostfix(Integer.toString(i)); + SegmentIndexCreationDriver driver = new SegmentIndexCreationDriverImpl(); + driver.init(config); + driver.build(); + + indexSegments.add(ImmutableSegmentLoader.load(new File(_indexDir, driver.getSegmentName()), ReadMode.mmap)); + } + } catch (Exception ex) { + throw new RuntimeException(ex); + } + if (_onSecondInstance) { + _baseQueriesTest._segments2.addAll(indexSegments); + } else { + _baseQueriesTest._segments1.addAll(indexSegments); + } + _segmentContents.clear(); + } + + public QueryExecuted whenQuery(String query) { + processSegments(); + BrokerResponseNative brokerResponse = _baseQueriesTest.getBrokerResponse(query, _extraQueryOptions); + return new QueryExecuted(_baseQueriesTest, brokerResponse, _extraQueryOptions); + } + + public DeclaringTable givenTable(Schema schema, TableConfig tableConfig) { + return new DeclaringTable(_baseQueriesTest, tableConfig, schema, _indexDir.getParentFile(), _extraQueryOptions); + } + } + + public static class OnFirstInstance extends TableWithSegments { + OnFirstInstance(TableConfig tableConfig, Schema schema, File baseDir, boolean onSecondInstance, + FluentBaseQueriesTest baseQueriesTest, Map extraQueryOptions) { + super(tableConfig, schema, baseDir, onSecondInstance, baseQueriesTest, extraQueryOptions); + } + + public OnFirstInstance andSegment(Object[]... content) { + _segmentContents.add(new FakeSegmentContent(content)); + return this; + } + + public OnFirstInstance andSegment(String... tableText) { + super.andSegment(tableText); + return this; + } + + public OnSecondInstance andOnSecondInstance(Object[]... content) { + processSegments(); + return new OnSecondInstance( + _tableConfig, _schema, _indexDir.getParentFile(), !_onSecondInstance, _baseQueriesTest, _extraQueryOptions) + .andSegment(content); + } + + public OnSecondInstance andOnSecondInstance(String... content) { + processSegments(); + return new OnSecondInstance( + _tableConfig, _schema, _indexDir.getParentFile(), !_onSecondInstance, _baseQueriesTest, _extraQueryOptions) + .andSegment(content); + } + } + + public static class OnSecondInstance extends TableWithSegments { + OnSecondInstance(TableConfig tableConfig, Schema schema, File baseDir, boolean onSecondInstance, + FluentBaseQueriesTest baseQueriesTest, Map extraQueryOptions) { + super(tableConfig, schema, baseDir, onSecondInstance, baseQueriesTest, extraQueryOptions); + } + + public OnSecondInstance andSegment(Object[]... content) { + _segmentContents.add(new FakeSegmentContent(content)); + return this; + } + + public OnSecondInstance andSegment(String... tableText) { + super.andSegment(tableText); + return this; + } + } + + public static class QueryExecuted { + private final FluentBaseQueriesTest _baseQueriesTest; + private final BrokerResponse _brokerResponse; + private final Map _extraQueryOptions; + + public QueryExecuted(FluentBaseQueriesTest baseQueriesTest, BrokerResponse brokerResponse, + Map extraQueryOptions) { + _baseQueriesTest = baseQueriesTest; + _brokerResponse = brokerResponse; + _extraQueryOptions = extraQueryOptions; + } + + public QueryExecuted thenResultIs(String... tableText) { + Object[][] rows = tableAsRows( + headerCells -> Arrays.stream(headerCells) + .map(String::trim) + .map(txt -> txt.toUpperCase(Locale.US)) + .map(PinotDataType::valueOf) + .collect(Collectors.toList()), + tableText + ); + thenResultIs(rows); + + return this; + } + + public QueryExecuted thenResultIs(Object[]... expectedResult) { + if (_brokerResponse.getExceptionsSize() > 0) { + Assert.fail("Query failed with " + _brokerResponse.getExceptions()); + } + + List actualRows = _brokerResponse.getResultTable().getRows(); + int rowsToAnalyze = Math.min(actualRows.size(), expectedResult.length); + for (int i = 0; i < rowsToAnalyze; i++) { + Object[] actualRow = actualRows.get(i); + Object[] expectedRow = expectedResult[i]; + for (int j = 0; j < actualRow.length; j++) { + Object actualCell = actualRow[j]; + Object expectedCell = expectedRow[j]; + if (actualCell != null && expectedCell != null) { + Assert.assertEquals(actualCell.getClass(), expectedCell.getClass(), "On row " + i + " and column " + j); + } + if (expectedCell == null) { + Assert.assertNull(actualCell, "On row " + i + " and column " + j + ". " + + "Actual value is '" + actualCell + "', which is not null"); + } else if (actualCell == null) { + Assert.fail("On row " + i + " and column " + j + ". Actual value is null when expecting not null " + + "value '" + expectedCell + "'"); + } else { + Assert.assertEquals(actualCell, expectedCell, "On row " + i + " and column " + j); + } + } + } + Assert.assertEquals(actualRows.size(), expectedResult.length, "Unexpected number of rows"); + return this; + } + + public QueryExecuted withExtraQueryOptions(Map extraQueryOptions) { + _extraQueryOptions.clear(); + _extraQueryOptions.putAll(extraQueryOptions); + return this; + } + + public QueryExecuted withNullHandling(boolean enabled) { + _extraQueryOptions.put("enableNullHandling", Boolean.toString(enabled)); + return this; + } + + public QueryExecuted whenQuery(String query) { + BrokerResponseNative brokerResponse = _baseQueriesTest.getBrokerResponse(query); + return new QueryExecuted(_baseQueriesTest, brokerResponse, _extraQueryOptions); + } + } + + public static Object[][] tableAsRows(Function> extractDataTypes, String... tableText) { + String header = tableText[0]; + String[] headerCells = header.split("\\|"); + + List dataTypes = extractDataTypes.apply(headerCells); + + for (int i = 0; i < dataTypes.size(); i++) { + PinotDataType dataType = dataTypes.get(i); + if (!dataType.isSingleValue()) { + throw new IllegalArgumentException( + "Multi value columns are not supported and the " + i + "th column is of type " + dataType + + " which is multivalued"); + } + } + + Object[][] rows = new Object[tableText.length - 1][]; + for (int i = 1; i < tableText.length; i++) { + String[] rawCells = tableText[i].split("\\|"); + Object[] convertedRow = new Object[dataTypes.size()]; + for (int col = 0; col < rawCells.length; col++) { + String rawCell = rawCells[col].trim(); + Object converted; + if (rawCell.equalsIgnoreCase("null")) { + converted = null; + } else if (rawCell.equalsIgnoreCase("\"null\"")) { + converted = dataTypes.get(col).convert("null", PinotDataType.STRING); + } else { + converted = dataTypes.get(col).convert(rawCell, PinotDataType.STRING); + } + convertedRow[col] = converted; + } + rows[i - 1] = convertedRow; + } + return rows; + } + + public static class FakeSegmentContent extends ArrayList> { + + public FakeSegmentContent(Schema schema, String... tableText) { + super(tableText.length - 1); + + Object[][] rows = FluentQueryTest.tableAsRows( + headerCells -> { + List dataTypes = new ArrayList<>(); + for (String headerCell : headerCells) { + String columnName = headerCell.trim(); + FieldSpec fieldSpec = schema.getFieldSpecFor(columnName); + if (fieldSpec.isVirtualColumn()) { + throw new IllegalArgumentException("Virtual columns like " + columnName + " cannot be set here"); + } + if (!fieldSpec.isSingleValueField()) { + throw new IllegalArgumentException( + "Multi valued columns like " + columnName + " cannot be set as text"); + } + dataTypes.add(PinotDataType.getPinotDataTypeForIngestion(fieldSpec)); + } + return dataTypes; + }, + tableText); + + for (Object[] row : rows) { + add(Arrays.asList(row)); + } + } + + public FakeSegmentContent(Object[]... rows) { + super(rows.length); + for (Object[] row : rows) { + add(Arrays.asList(row)); + } + } + } + + protected static class FluentBaseQueriesTest extends BaseQueriesTest { + List _segments1 = new ArrayList<>(); + List _segments2 = new ArrayList<>(); + + @Override + protected String getFilter() { + return ""; + } + + @Override + protected IndexSegment getIndexSegment() { + return _segments1.get(0); + } + + @Override + protected List getIndexSegments() { + if (_segments2.isEmpty()) { + return _segments1; + } + ArrayList segments = new ArrayList<>(_segments1.size() + _segments2.size()); + segments.addAll(_segments1); + segments.addAll(_segments2); + return segments; + } + + @Override + protected List> getDistinctInstances() { + if (_segments2.isEmpty()) { + return super.getDistinctInstances(); + } + return Lists.newArrayList(_segments1, _segments2); + } + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/ForwardIndexDisabledMultiValueQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/ForwardIndexDisabledMultiValueQueriesTest.java index f51bb8e60e4e..4d392f9766b5 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/ForwardIndexDisabledMultiValueQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/ForwardIndexDisabledMultiValueQueriesTest.java @@ -39,7 +39,10 @@ import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; import org.apache.pinot.segment.spi.creator.SegmentIndexCreationDriver; +import org.apache.pinot.segment.spi.index.ForwardIndexConfig; +import org.apache.pinot.segment.spi.index.StandardIndexes; import org.apache.pinot.spi.config.table.FieldConfig; +import org.apache.pinot.spi.config.table.IndexConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.data.FieldSpec; @@ -52,11 +55,7 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; +import static org.testng.Assert.*; /** @@ -154,9 +153,10 @@ private void createSegment(String filePath, String segmentName, Schema schema) segmentGeneratorConfig.setOutDir(INDEX_DIR.getAbsolutePath()); segmentGeneratorConfig.setSegmentName(segmentName); _invertedIndexColumns = Arrays.asList("column3", "column6", "column7", "column8", "column9"); - segmentGeneratorConfig.setInvertedIndexCreationColumns(_invertedIndexColumns); + segmentGeneratorConfig.setIndexOn(StandardIndexes.inverted(), IndexConfig.ENABLED, _invertedIndexColumns); _forwardIndexDisabledColumns = new ArrayList<>(Arrays.asList("column6", "column7")); - segmentGeneratorConfig.setForwardIndexDisabledColumns(_forwardIndexDisabledColumns); + segmentGeneratorConfig.setIndexOn(StandardIndexes.forward(), ForwardIndexConfig.DISABLED, + _forwardIndexDisabledColumns); // The segment generation code in SegmentColumnarIndexCreator will throw // exception if start and end time in time column are not in acceptable // range. For this test, we first need to fix the input avro data @@ -287,8 +287,8 @@ public void testSelectQueries() { // Selection query without filters and without columns with forwardIndexDisabled enabled on either segment String query = "SELECT column1, column5, column9, column10 FROM testTable ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -297,8 +297,8 @@ public void testSelectQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 400_120L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); DataSchema dataSchema = new DataSchema(new String[]{"column1", "column5", "column9", "column10"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.INT}); @@ -335,8 +335,8 @@ public void testSelectQueries() { + " AND column3 <> 'w'" + " AND daysSinceEpoch = 1756015683 ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -345,8 +345,8 @@ public void testSelectQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 62_820); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 304_120L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); DataSchema dataSchema = new DataSchema(new String[]{"column1", "column5", "column9", "column10"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.INT}); @@ -369,8 +369,8 @@ public void testSelectQueries() { String query = "SELECT column1, column5, column9, column10 FROM testTable WHERE column6 = 1001 " + "ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 8); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -379,8 +379,8 @@ public void testSelectQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 32L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); DataSchema dataSchema = new DataSchema(new String[]{"column1", "column5", "column9", "column10"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.INT}); @@ -400,8 +400,8 @@ public void testSelectQueries() { String query = "SELECT column1, column5, column9, column10 FROM testTable WHERE column7 != 201 " + "ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -410,8 +410,8 @@ public void testSelectQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 400_016L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); DataSchema dataSchema = new DataSchema(new String[]{"column1", "column5", "column9", "column10"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.INT}); @@ -434,8 +434,8 @@ public void testSelectQueries() { String query = "SELECT column1, column5, column9, column10 FROM testTable WHERE column7 IN (201, 2147483647) " + "ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -444,8 +444,8 @@ public void testSelectQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 199_980L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); DataSchema dataSchema = new DataSchema(new String[]{"column1", "column5", "column9", "column10"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.INT}); @@ -468,8 +468,8 @@ public void testSelectQueries() { String query = "SELECT column1, column5, column9, column10 FROM testTable WHERE column6 NOT IN " + "(1001, 2147483647) ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -478,8 +478,8 @@ public void testSelectQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 174_672L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); DataSchema dataSchema = new DataSchema(new String[]{"column1", "column5", "column9", "column10"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.INT}); @@ -500,8 +500,8 @@ public void testSelectQueries() { // Query with literal only in SELECT String query = "SELECT 'marvin' from testTable ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -510,8 +510,8 @@ public void testSelectQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 400_000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"'marvin'"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.STRING})); List resultRows = resultTable.getRows(); @@ -547,8 +547,8 @@ public void testSelectQueries() { // Select query with a filter on a column which doesn't have forwardIndexDisabled enabled String query = "SELECT column1, column5, column9 from testTable WHERE column9 < 3890167 ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -557,8 +557,8 @@ public void testSelectQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 128L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 400_000L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column1", "column5", "column9"}, new DataSchema.ColumnDataType[]{ DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT @@ -576,8 +576,8 @@ public void testSelectQueries() { // Transform function on a filter clause for forwardIndexDisabled column in transform String query = "SELECT column1, column10 from testTable WHERE ARRAYLENGTH(column6) = 2"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() != null - && brokerResponseNative.getProcessingExceptions().size() > 0); + assertTrue(brokerResponseNative.getExceptions() != null + && brokerResponseNative.getExceptions().size() > 0); } } @@ -625,8 +625,8 @@ public void testSelectWithGroupByOrderByQueries() { String query = "SELECT column1, column5 FROM testTable GROUP BY column1, column5 ORDER BY column1, column5 " + " LIMIT 10"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -635,8 +635,8 @@ public void testSelectWithGroupByOrderByQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 800000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column1", "column5"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING})); List resultRows = resultTable.getRows(); @@ -680,8 +680,8 @@ public void testSelectWithAggregationQueries() { String query = "SELECT maxmv(column7), minmv(column6), minmaxrangemv(column6), distinctcountmv(column7), " + "distinctcounthllmv(column6), distinctcountrawhllmv(column7) from testTable"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 1); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -690,8 +690,8 @@ public void testSelectWithAggregationQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 0L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"maxmv(column7)", "minmv(column6)", "minmaxrangemv(column6)", "distinctcountmv(column7)", "distinctcounthllmv(column6)", "distinctcountrawhllmv(column7)"}, @@ -784,8 +784,8 @@ public void testSelectWithAggregationQueries() { // column String query = "SELECT max(column1), sum(column9) from testTable WHERE column7 = 2147483647"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 1); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -794,8 +794,8 @@ public void testSelectWithAggregationQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 399_512L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"max(column1)", "sum(column9)"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.DOUBLE, DataSchema.ColumnDataType.DOUBLE})); List resultRows = resultTable.getRows(); @@ -811,8 +811,8 @@ public void testSelectWithAggregationQueries() { String query = "SELECT column1, max(column1), sum(column9) from testTable WHERE column7 = 2147483647 GROUP BY " + "column1 ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -821,8 +821,8 @@ public void testSelectWithAggregationQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 399_512L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column1", "max(column1)", "sum(column9)"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.DOUBLE, DataSchema.ColumnDataType.DOUBLE})); diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/ForwardIndexDisabledMultiValueQueriesWithReloadTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/ForwardIndexDisabledMultiValueQueriesWithReloadTest.java index 0181575637fc..5f32ac11c8a2 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/ForwardIndexDisabledMultiValueQueriesWithReloadTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/ForwardIndexDisabledMultiValueQueriesWithReloadTest.java @@ -40,7 +40,10 @@ import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; import org.apache.pinot.segment.spi.creator.SegmentIndexCreationDriver; +import org.apache.pinot.segment.spi.index.ForwardIndexConfig; +import org.apache.pinot.segment.spi.index.StandardIndexes; import org.apache.pinot.spi.config.table.FieldConfig; +import org.apache.pinot.spi.config.table.IndexConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.data.FieldSpec; @@ -53,11 +56,7 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; +import static org.testng.Assert.*; /** @@ -153,9 +152,10 @@ private void createSegment(String filePath, String segmentName) segmentGeneratorConfig.setOutDir(INDEX_DIR.getAbsolutePath()); segmentGeneratorConfig.setSegmentName(segmentName); _invertedIndexColumns = Arrays.asList("column3", "column6", "column8", "column9"); - segmentGeneratorConfig.setInvertedIndexCreationColumns(_invertedIndexColumns); + segmentGeneratorConfig.setIndexOn(StandardIndexes.inverted(), IndexConfig.ENABLED, _invertedIndexColumns); _forwardIndexDisabledColumns = new ArrayList<>(Arrays.asList("column6")); - segmentGeneratorConfig.setForwardIndexDisabledColumns(_forwardIndexDisabledColumns); + segmentGeneratorConfig.setIndexOn(StandardIndexes.forward(), ForwardIndexConfig.DISABLED, + _forwardIndexDisabledColumns); segmentGeneratorConfig.setRawIndexCreationColumns(_noDictionaryColumns); // The segment generation code in SegmentColumnarIndexCreator will throw // exception if start and end time in time column are not in acceptable @@ -224,8 +224,8 @@ public void testSelectQueriesWithReload() // This is just a sanity check to ensure the query works when forward index is enabled String query = "SELECT column1, column5, column7 FROM testTable WHERE column7 != 201 ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -255,14 +255,14 @@ public void testSelectQueriesWithReload() // Run the same query and validate that an exception is thrown since column7 has forward index disabled brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() != null - && brokerResponseNative.getProcessingExceptions().size() > 0); + assertTrue(brokerResponseNative.getExceptions() != null + && brokerResponseNative.getExceptions().size() > 0); // Run a query which uses column7 in the where clause and not in the select list. This should work query = "SELECT column1, column5 FROM testTable WHERE column7 IN (201, 2147483647) ORDER BY column1"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -288,8 +288,8 @@ public void testSelectQueriesWithReload() // Transform function on a filter clause for forwardIndexDisabled column in transform query = "SELECT column1, column10 from testTable WHERE ARRAYLENGTH(column7) = 2"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() != null - && brokerResponseNative.getProcessingExceptions().size() > 0); + assertTrue(brokerResponseNative.getExceptions() != null + && brokerResponseNative.getExceptions().size() > 0); // Re-enable forward index for column7 and column6 reenableForwardIndexForSomeColumns(); @@ -297,8 +297,8 @@ public void testSelectQueriesWithReload() // Selection query without filters including column7 query = "SELECT column1, column5, column7, column6 FROM testTable WHERE column7 != 201 ORDER BY column1"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -327,8 +327,8 @@ public void testSelectQueriesWithReload() query = "SELECT column1, column10 from testTable WHERE ARRAYLENGTH(column7) = 2 AND ARRAYLENGTH(column6) = 2 " + "ORDER BY column1"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -356,8 +356,8 @@ public void testSelectAllResultsQueryWithReload() // Select query with order by on column9 with limit == totalDocs String query = "SELECT column7 FROM testTable ORDER BY column1 LIMIT 400000"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 400_000); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -366,8 +366,8 @@ public void testSelectAllResultsQueryWithReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 800_000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); DataSchema dataSchema = new DataSchema(new String[]{"column7"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT_ARRAY}); assertEquals(resultTable.getDataSchema(), dataSchema); @@ -382,16 +382,16 @@ public void testSelectAllResultsQueryWithReload() // Run the same query and validate that an exception is thrown since we are running select query on forward index // disabled column brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() != null - && brokerResponseNative.getProcessingExceptions().size() > 0); + assertTrue(brokerResponseNative.getExceptions() != null + && brokerResponseNative.getExceptions().size() > 0); // Re-enable forward index for column7 and column6 reenableForwardIndexForSomeColumns(); // The first query should work now brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 400_000); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -400,8 +400,8 @@ public void testSelectAllResultsQueryWithReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 800_000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); dataSchema = new DataSchema(new String[]{"column7"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT_ARRAY}); assertEquals(resultTable.getDataSchema(), dataSchema); @@ -434,8 +434,8 @@ public void testSelectWithDistinctQueriesWithReload() // This is just a sanity check to ensure the query works when forward index is enabled String query = "SELECT DISTINCT column1, column7, column9 FROM testTable ORDER BY column1 LIMIT 10"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -473,8 +473,8 @@ public void testSelectWithDistinctQueriesWithReload() // Distinct query without filters including column7 query = "SELECT DISTINCT column1, column7, column9, column6 FROM testTable ORDER BY column1 LIMIT 10"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -504,8 +504,8 @@ public void testSelectWithGroupByOrderByQueriesWithReload() String query = "SELECT column1, column7 FROM testTable GROUP BY column1, column7 ORDER BY column1, column7 " + " LIMIT 10"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -553,8 +553,8 @@ public void testSelectWithGroupByOrderByQueriesWithReload() query = "SELECT column1, column7, column6 FROM testTable GROUP BY column1, column7, column6 ORDER BY column1, " + "column7, column6 LIMIT 10"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -578,8 +578,8 @@ public void testSelectWithGroupByOrderByQueriesWithReload() query = "SELECT ARRAYLENGTH(column7) FROM testTable GROUP BY ARRAYLENGTH(column7) ORDER BY " + "ARRAYLENGTH(column7) LIMIT 10"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -605,8 +605,8 @@ public void testSelectWithAggregationQueriesWithReload() // This is just a sanity check to ensure the query works when forward index is enabled String query = "SELECT MAX(ARRAYLENGTH(column7)) from testTable LIMIT 10"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 1); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -627,8 +627,8 @@ public void testSelectWithAggregationQueriesWithReload() // This is just a sanity check to ensure the query works when forward index is enabled query = "SELECT summv(column7), avgmv(column7) from testTable"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 1); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -663,8 +663,8 @@ public void testSelectWithAggregationQueriesWithReload() query = "SELECT column1, max(column1), sum(column9) from testTable WHERE column7 = 2147483647 GROUP BY " + "column1 ORDER BY column1"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -699,8 +699,8 @@ public void testSelectWithAggregationQueriesWithReload() query = "SELECT MAX(ARRAYLENGTH(column7)) from testTable LIMIT 10"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 1); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -720,8 +720,8 @@ public void testSelectWithAggregationQueriesWithReload() // Not allowed aggregation functions on non-forwardIndexDisabled columns query = "SELECT summv(column7), avgmv(column7), summv(column6) from testTable"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 1); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -754,7 +754,7 @@ private void disableForwardIndexForSomeColumns() Set forwardIndexDisabledColumns = new HashSet<>(_forwardIndexDisabledColumns); forwardIndexDisabledColumns.add("column7"); indexLoadingConfig.setForwardIndexDisabledColumns(forwardIndexDisabledColumns); - indexLoadingConfig.getNoDictionaryColumns().remove("column7"); + indexLoadingConfig.removeNoDictionaryColumns("column7"); indexLoadingConfig.setReadMode(ReadMode.heap); // Reload the segments to pick up the new configs @@ -785,7 +785,7 @@ private void reenableForwardIndexForSomeColumns() Set forwardIndexDisabledColumns = new HashSet<>(_forwardIndexDisabledColumns); forwardIndexDisabledColumns.remove("column6"); indexLoadingConfig.setForwardIndexDisabledColumns(forwardIndexDisabledColumns); - indexLoadingConfig.getNoDictionaryColumns().add("column7"); + indexLoadingConfig.addNoDictionaryColumns("column7"); indexLoadingConfig.setReadMode(ReadMode.heap); // Reload the segments to pick up the new configs diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/ForwardIndexDisabledSingleValueQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/ForwardIndexDisabledSingleValueQueriesTest.java index 51718d06e7ea..c37f14a449c9 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/ForwardIndexDisabledSingleValueQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/ForwardIndexDisabledSingleValueQueriesTest.java @@ -40,7 +40,11 @@ import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; import org.apache.pinot.segment.spi.creator.SegmentIndexCreationDriver; +import org.apache.pinot.segment.spi.index.ForwardIndexConfig; +import org.apache.pinot.segment.spi.index.RangeIndexConfig; +import org.apache.pinot.segment.spi.index.StandardIndexes; import org.apache.pinot.spi.config.table.FieldConfig; +import org.apache.pinot.spi.config.table.IndexConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.data.FieldSpec; @@ -168,12 +172,14 @@ private void createSegment(String filePath, String segmentName) // is explicitly disabled segmentGeneratorConfig.setSkipTimeValueCheck(true); _invertedIndexColumns = Arrays.asList("column6", "column7", "column11", "column17", "column18"); - segmentGeneratorConfig.setInvertedIndexCreationColumns(_invertedIndexColumns); + segmentGeneratorConfig.setIndexOn(StandardIndexes.inverted(), IndexConfig.ENABLED, _invertedIndexColumns); segmentGeneratorConfig.setRawIndexCreationColumns(_noDictionaryColumns); _forwardIndexDisabledColumns = new ArrayList<>(Arrays.asList("column6", "column7")); - segmentGeneratorConfig.setForwardIndexDisabledColumns(_forwardIndexDisabledColumns); - segmentGeneratorConfig.setRangeIndexCreationColumns(Arrays.asList("column6")); + segmentGeneratorConfig.setIndexOn(StandardIndexes.forward(), ForwardIndexConfig.DISABLED, + _forwardIndexDisabledColumns); + RangeIndexConfig rangeIndexConfig = RangeIndexConfig.DEFAULT; + segmentGeneratorConfig.setIndexOn(StandardIndexes.range(), rangeIndexConfig, "column6"); // Build the index segment. SegmentIndexCreationDriver driver = new SegmentIndexCreationDriverImpl(); @@ -300,8 +306,8 @@ public void testSelectQueries() { // Selection query without filters and without columns with forwardIndexDisabled enabled on either segment String query = "SELECT column1, column5, column9, column11 FROM testTable ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -310,8 +316,8 @@ public void testSelectQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 120_120L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); DataSchema dataSchema = new DataSchema(new String[]{"column1", "column5", "column9", "column11"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING}); @@ -345,8 +351,8 @@ public void testSelectQueries() { // Transform function on a selection clause without a forwardIndexDisabled column in transform String query = "SELECT CONCAT(column5, column9, '-') from testTable ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -355,8 +361,8 @@ public void testSelectQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 120_080L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); DataSchema dataSchema = new DataSchema(new String[]{"concat(column5,column9,'-')"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.STRING}); assertEquals(resultTable.getDataSchema(), dataSchema); @@ -374,8 +380,8 @@ public void testSelectQueries() { + " AND column5 = 'gFuH'" + " AND daysSinceEpoch = 126164076 ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -384,8 +390,8 @@ public void testSelectQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 42_488); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 192744L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); DataSchema dataSchema = new DataSchema(new String[]{"column1", "column5", "column9", "column11"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING}); @@ -409,8 +415,8 @@ public void testSelectQueries() { String query = "SELECT column1, column5, column9, column11 FROM testTable WHERE column6 = 2147458029 " + "ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -419,8 +425,8 @@ public void testSelectQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 64L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); DataSchema dataSchema = new DataSchema(new String[]{"column1", "column5", "column9", "column11"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING}); @@ -440,8 +446,8 @@ public void testSelectQueries() { String query = "SELECT column1, column5, column9, column11 FROM testTable WHERE column7 != 675695 " + "ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -450,8 +456,8 @@ public void testSelectQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 120_028L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); DataSchema dataSchema = new DataSchema(new String[]{"column1", "column5", "column9", "column11"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING}); @@ -475,8 +481,8 @@ public void testSelectQueries() { String query = "SELECT column1, column5, column9, column11 FROM testTable WHERE column7 IN (675695, 2137685743) " + "ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -485,8 +491,8 @@ public void testSelectQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 948L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); DataSchema dataSchema = new DataSchema(new String[]{"column1", "column5", "column9", "column11"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING}); @@ -510,8 +516,8 @@ public void testSelectQueries() { String query = "SELECT column1, column5, column9, column11 FROM testTable WHERE column6 NOT IN " + "(1689277, 2147458029) ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -520,8 +526,8 @@ public void testSelectQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 120_100L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); DataSchema dataSchema = new DataSchema(new String[]{"column1", "column5", "column9", "column11"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING}); @@ -543,8 +549,8 @@ public void testSelectQueries() { // Query with literal only in SELECT String query = "SELECT 'marvin' from testTable ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -553,8 +559,8 @@ public void testSelectQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 120_000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"'marvin'"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.STRING})); List resultRows = resultTable.getRows(); @@ -568,8 +574,8 @@ public void testSelectQueries() { String query = "SELECT column1, column5, column9, column11 FROM testTable WHERE column6 < 2147458029 AND " + "column6 > 1699000 ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -578,8 +584,8 @@ public void testSelectQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 120_100L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); DataSchema dataSchema = new DataSchema(new String[]{"column1", "column5", "column9", "column11"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING}); @@ -612,8 +618,8 @@ public void testSelectQueries() { // Select query with a filter on a column which doesn't have forwardIndexDisabled enabled String query = "SELECT column1, column5, column9 from testTable WHERE column9 < 50000 ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 4); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -622,8 +628,8 @@ public void testSelectQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 12L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 120000L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column1", "column5", "column9"}, new DataSchema.ColumnDataType[]{ DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT @@ -640,16 +646,16 @@ public void testSelectQueries() { // Transform function on a filter clause for forwardIndexDisabled column in transform String query = "SELECT column1, column11 from testTable WHERE CONCAT(column6, column9, '-') = '1689277-11270'"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() != null - && brokerResponseNative.getProcessingExceptions().size() > 0); + assertTrue(brokerResponseNative.getExceptions() != null + && brokerResponseNative.getExceptions().size() > 0); } { // Transform function on a filter clause for a non-forwardIndexDisabled column in transform String query = "SELECT column1, column11 from testTable WHERE CONCAT(column5, column9, '-') = 'gFuH-11270' " + "ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 4); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -658,8 +664,8 @@ public void testSelectQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 8L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 120000L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column1", "column11"}, new DataSchema.ColumnDataType[]{ DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING @@ -677,16 +683,16 @@ public void testSelectQueries() { String query = "SELECT column1, column11 from testTable WHERE CONCAT(ADD(column6, column1), column9, '-') = " + "'1689277-11270'"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() != null - && brokerResponseNative.getProcessingExceptions().size() > 0); + assertTrue(brokerResponseNative.getExceptions() != null + && brokerResponseNative.getExceptions().size() > 0); } { // Transform function on a filter clause for a non-forwardIndexDisabled column in transform String query = "SELECT column1, column11 from testTable WHERE CONCAT(column5, ADD(column9, column1), '-') = " + "'gFuH-2.96708164E8' ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -695,8 +701,8 @@ public void testSelectQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 56L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 120000L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column1", "column11"}, new DataSchema.ColumnDataType[]{ DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING @@ -719,8 +725,8 @@ public void testSelectQueriesWithReload() String query = "SELECT column1, column5, column9, column11 FROM testTable WHERE column6 < 2147458029 AND " + "column6 > 1699000 ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -729,8 +735,8 @@ public void testSelectQueriesWithReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 120_100L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); DataSchema dataSchema = new DataSchema(new String[]{"column1", "column5", "column9", "column11"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING}); @@ -753,14 +759,14 @@ public void testSelectQueriesWithReload() // Run the same query and validate that an exception is thrown since both column9 and column11 are on the SELECT // list brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() != null - && brokerResponseNative.getProcessingExceptions().size() > 0); + assertTrue(brokerResponseNative.getExceptions() != null + && brokerResponseNative.getExceptions().size() > 0); // Select query with a filter on a column9 (which will use the range index). This should work query = "SELECT column1, column5 from testTable WHERE column9 > 11270 ORDER BY column1"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -769,8 +775,8 @@ public void testSelectQueriesWithReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 120_036L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column1", "column5"}, new DataSchema.ColumnDataType[]{ DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING @@ -786,8 +792,8 @@ public void testSelectQueriesWithReload() // Select query with a filter on a column11. This should work query = "SELECT column1, column5 from testTable WHERE column11 IN('o', 'P') ORDER BY column1"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -796,8 +802,8 @@ public void testSelectQueriesWithReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 99376L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column1", "column5"}, new DataSchema.ColumnDataType[]{ DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING @@ -815,15 +821,15 @@ public void testSelectQueriesWithReload() query = "SELECT column1, column5 from testTable WHERE CONCAT(column5, ADD(column9, column1), '-') = " + "'gFuH-2.96708164E8' ORDER BY column1"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() != null - && brokerResponseNative.getProcessingExceptions().size() > 0); + assertTrue(brokerResponseNative.getExceptions() != null + && brokerResponseNative.getExceptions().size() > 0); // Transform function on a filter clause for a non-forwardIndexDisabled transform should not fail query = "SELECT column1, column5 from testTable WHERE CONCAT(column5, ADD(column1, column1), '-') = " + "'gFuH-481056.0' ORDER BY column1"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -832,8 +838,8 @@ public void testSelectQueriesWithReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 56L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 120000L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column1", "column5"}, new DataSchema.ColumnDataType[]{ DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING @@ -853,8 +859,8 @@ public void testSelectQueriesWithReload() query = "SELECT column1, column5, column9, column11, column6 FROM testTable WHERE column6 < 2147458029 AND " + "column6 > 1699000 ORDER BY column1"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -863,8 +869,8 @@ public void testSelectQueriesWithReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 120_140L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); dataSchema = new DataSchema(new String[]{"column1", "column5", "column9", "column11", "column6"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT}); @@ -885,8 +891,8 @@ public void testSelectQueriesWithReload() query = "SELECT column1, column11 from testTable WHERE CONCAT(column5, ADD(column9, column1), '-') = " + "'gFuH-2.96708164E8' ORDER BY column1"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -895,8 +901,8 @@ public void testSelectQueriesWithReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 56L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 120000L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column1", "column11"}, new DataSchema.ColumnDataType[]{ DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING @@ -916,8 +922,8 @@ public void testSelectAllResultsQueryWithReload() // Select query with order by on column9 with limit == totalDocs String query = "SELECT column9 FROM testTable ORDER BY column9 LIMIT 120000"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 120_000); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -926,8 +932,8 @@ public void testSelectAllResultsQueryWithReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 120_000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); DataSchema dataSchema = new DataSchema(new String[]{"column9"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT}); assertEquals(resultTable.getDataSchema(), dataSchema); @@ -957,8 +963,8 @@ public void testSelectAllResultsQueryWithReload() // The first query should work now brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 120_000); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -967,8 +973,8 @@ public void testSelectAllResultsQueryWithReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 120_000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); dataSchema = new DataSchema(new String[]{"column9"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT}); assertEquals(resultTable.getDataSchema(), dataSchema); @@ -1004,8 +1010,8 @@ public void testSelectWithDistinctQueries() { // Select non-forwardIndexDisabled columns with distinct String query = "SELECT DISTINCT column1, column5, column9 FROM testTable ORDER BY column1 LIMIT 10"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1014,8 +1020,8 @@ public void testSelectWithDistinctQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 360_000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column1", "column5", "column9"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT})); @@ -1037,8 +1043,8 @@ public void testSelectWithDistinctQueriesWithReload() // This is just a sanity check to ensure the query works when forward index is enabled String query = "SELECT DISTINCT column1, column5, column9 FROM testTable ORDER BY column1 LIMIT 10"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1047,8 +1053,8 @@ public void testSelectWithDistinctQueriesWithReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 360_000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column1", "column5", "column9"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT})); @@ -1078,8 +1084,8 @@ public void testSelectWithDistinctQueriesWithReload() // Select non-forwardIndexDisabled columns with distinct query = "SELECT DISTINCT column6, column5, column9 FROM testTable ORDER BY column6 LIMIT 10"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1088,8 +1094,8 @@ public void testSelectWithDistinctQueriesWithReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 360_000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column6", "column5", "column9"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT})); @@ -1134,8 +1140,8 @@ public void testSelectWithGroupByOrderByQueries() { String query = "SELECT column1, column5 FROM testTable GROUP BY column1, column5 ORDER BY column1, column5 " + " LIMIT 10"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1144,8 +1150,8 @@ public void testSelectWithGroupByOrderByQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 240000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column1", "column5"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING})); List resultRows = resultTable.getRows(); @@ -1162,8 +1168,8 @@ public void testSelectWithGroupByOrderByQueries() { String query = "SELECT column9, column5 FROM testTable GROUP BY column9, column5 ORDER BY column9, column5 " + " LIMIT 10"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1172,8 +1178,8 @@ public void testSelectWithGroupByOrderByQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 240000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column9", "column5"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING})); List resultRows = resultTable.getRows(); @@ -1202,8 +1208,8 @@ public void testSelectWithGroupByOrderByQueries() { String query = "SELECT CONCAT(column1, column5, '-') FROM testTable GROUP BY CONCAT(column1, column5, '-') " + "ORDER BY CONCAT(column1, column5, '-') LIMIT 10"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1212,8 +1218,8 @@ public void testSelectWithGroupByOrderByQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 240000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"concat(column1,column5,'-')"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.STRING})); List resultRows = resultTable.getRows(); @@ -1239,8 +1245,8 @@ public void testSelectWithGroupByOrderByQueries() { String query = "SELECT CONCAT(ADD(column1, column9), column5, '-') FROM testTable GROUP BY " + "CONCAT(ADD(column1, column9), column5, '-') ORDER BY CONCAT(ADD(column1, column9), column5, '-') LIMIT 10"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1249,8 +1255,8 @@ public void testSelectWithGroupByOrderByQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 360000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"concat(add(column1,column9),column5,'-')"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.STRING})); List resultRows = resultTable.getRows(); @@ -1269,8 +1275,8 @@ public void testSelectWithGroupByOrderByQueriesWithReload() String query = "SELECT CONCAT(ADD(column1, column9), column5, '-') FROM testTable GROUP BY " + "CONCAT(ADD(column1, column9), column5, '-') ORDER BY CONCAT(ADD(column1, column9), column5, '-') LIMIT 10"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1279,8 +1285,8 @@ public void testSelectWithGroupByOrderByQueriesWithReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 360000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"concat(add(column1,column9),column5,'-')"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.STRING})); List resultRows = resultTable.getRows(); @@ -1305,8 +1311,8 @@ public void testSelectWithGroupByOrderByQueriesWithReload() query = "SELECT CONCAT(ADD(column1, column1), column5, '-') FROM testTable GROUP BY " + "CONCAT(ADD(column1, column1), column5, '-') ORDER BY CONCAT(ADD(column1, column1), column5, '-') LIMIT 10"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1315,8 +1321,8 @@ public void testSelectWithGroupByOrderByQueriesWithReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 240000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"concat(add(column1,column1),column5,'-')"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.STRING})); resultRows = resultTable.getRows(); @@ -1342,8 +1348,8 @@ public void testSelectWithGroupByOrderByQueriesWithReload() query = "SELECT CONCAT(ADD(column6, column9), column5, '-') FROM testTable GROUP BY " + "CONCAT(ADD(column6, column9), column5, '-') ORDER BY CONCAT(ADD(column6, column9), column5, '-') LIMIT 10"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1352,8 +1358,8 @@ public void testSelectWithGroupByOrderByQueriesWithReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 360000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"concat(add(column6,column9),column5,'-')"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.STRING})); resultRows = resultTable.getRows(); @@ -1366,8 +1372,8 @@ public void testSelectWithGroupByOrderByQueriesWithReload() query = "SELECT column9, column5, column6 FROM testTable GROUP BY column9, column5, column6 ORDER BY column9, " + "column5, column6 LIMIT 10"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1376,8 +1382,8 @@ public void testSelectWithGroupByOrderByQueriesWithReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 360000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column9", "column5", "column6"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.STRING, DataSchema.ColumnDataType.INT})); @@ -1399,8 +1405,8 @@ public void testSelectWithAggregationQueries() { + "distinctcount(column7), distinctcounthll(column6), distinctcountrawhll(column7), " + "distinctcountsmarthll(column6) from testTable"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 1); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1409,8 +1415,8 @@ public void testSelectWithAggregationQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 0L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"max(column7)", "min(column7)", "count(*)", "minmaxrange(column7)", "distinctcount(column7)", "distinctcounthll(column6)", "distinctcountrawhll(column7)", "distinctcountsmarthll(column6)"}, @@ -1505,8 +1511,8 @@ public void testSelectWithAggregationQueries() { // column String query = "SELECT max(column1), sum(column9) from testTable WHERE column7 = 675695"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 1); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1515,8 +1521,8 @@ public void testSelectWithAggregationQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 184L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"max(column1)", "sum(column9)"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.DOUBLE, DataSchema.ColumnDataType.DOUBLE})); List resultRows = resultTable.getRows(); @@ -1532,8 +1538,8 @@ public void testSelectWithAggregationQueries() { String query = "SELECT column1, max(column1), sum(column9) from testTable WHERE column7 = 675695 GROUP BY " + "column1 ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1542,8 +1548,8 @@ public void testSelectWithAggregationQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 184L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column1", "max(column1)", "sum(column9)"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.DOUBLE, DataSchema.ColumnDataType.DOUBLE})); @@ -1583,8 +1589,8 @@ public void testSelectWithAggregationQueries() { // Transform inside aggregation not involving any forwardIndexDisabled columns String query = "SELECT MAX(ADD(column1, column9)) from testTable LIMIT 10"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 1); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1593,8 +1599,8 @@ public void testSelectWithAggregationQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 240000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"max(add(column1,column9))"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.DOUBLE})); List resultRows = resultTable.getRows(); @@ -1618,8 +1624,8 @@ public void testSelectWithAggregationQueries() { // Transform inside aggregation not involving any forwardIndexDisabled column with group by String query = "SELECT column1, MAX(ADD(column1, column9)) from testTable GROUP BY column1 LIMIT 10"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1628,8 +1634,8 @@ public void testSelectWithAggregationQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 240000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column1", "max(add(column1,column9))"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.DOUBLE})); List resultRows = resultTable.getRows(); @@ -1654,8 +1660,8 @@ public void testSelectWithAggregationQueries() { String query = "SELECT column1, MAX(ADD(column1, column9)) from testTable GROUP BY column1 ORDER BY column1 " + "DESC LIMIT 10"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1664,8 +1670,8 @@ public void testSelectWithAggregationQueries() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 240000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column1", "max(add(column1,column9))"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.DOUBLE})); List resultRows = resultTable.getRows(); @@ -1686,8 +1692,8 @@ public void testSelectWithAggregationQueriesWithReload() String query = "SELECT column1, MAX(ADD(column1, column9)) from testTable GROUP BY column1 ORDER BY column1 " + "DESC LIMIT 10"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1696,8 +1702,8 @@ public void testSelectWithAggregationQueriesWithReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 240000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column1", "max(add(column1,column9))"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.DOUBLE})); List resultRows = resultTable.getRows(); @@ -1721,8 +1727,8 @@ public void testSelectWithAggregationQueriesWithReload() query = "SELECT max(column9), min(column9) from testTable"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 1); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1731,8 +1737,8 @@ public void testSelectWithAggregationQueriesWithReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 0L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"max(column9)", "min(column9)"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.DOUBLE, DataSchema.ColumnDataType.DOUBLE})); resultRows = resultTable.getRows(); @@ -1746,8 +1752,8 @@ public void testSelectWithAggregationQueriesWithReload() // column query = "SELECT max(column1) from testTable WHERE column9 > 50000"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 1); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1756,8 +1762,8 @@ public void testSelectWithAggregationQueriesWithReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 119996L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"max(column1)"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.DOUBLE})); resultRows = resultTable.getRows(); @@ -1773,8 +1779,8 @@ public void testSelectWithAggregationQueriesWithReload() query = "SELECT column6, MAX(ADD(column1, column9)) from testTable GROUP BY column6 ORDER BY column6 " + "DESC LIMIT 10"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1783,8 +1789,8 @@ public void testSelectWithAggregationQueriesWithReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 360000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column6", "max(add(column1,column9))"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.DOUBLE})); resultRows = resultTable.getRows(); @@ -1827,8 +1833,8 @@ public void testSelectWithAggregationGroupByHaving() { String query = "SELECT min(column9), column1 from testTable GROUP BY column1, column9 HAVING min(column9) " + "> 11270 ORDER BY column9 DESC LIMIT 10"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1837,8 +1843,8 @@ public void testSelectWithAggregationGroupByHaving() { assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 240000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"min(column9)", "column1"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.DOUBLE, DataSchema.ColumnDataType.INT})); List resultRows = resultTable.getRows(); @@ -1859,8 +1865,8 @@ public void testSelectWithAggregationGroupByHavingWithReload() String query = "SELECT min(column9), column1 from testTable GROUP BY column1, column9 HAVING min(column9) " + "> 11270 ORDER BY column9 DESC LIMIT 10"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1869,8 +1875,8 @@ public void testSelectWithAggregationGroupByHavingWithReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 240000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"min(column9)", "column1"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.DOUBLE, DataSchema.ColumnDataType.INT})); List resultRows = resultTable.getRows(); @@ -1899,8 +1905,8 @@ public void testSelectWithAggregationGroupByHavingWithReload() query = "SELECT min(column9), sum(column6), column1 from testTable GROUP BY column1, column9 HAVING min(column9) " + "> 11270 ORDER BY column9 DESC LIMIT 10"; brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 120_000L); @@ -1909,8 +1915,8 @@ public void testSelectWithAggregationGroupByHavingWithReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 360000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"min(column9)", "sum(column6)", "column1"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.DOUBLE, DataSchema.ColumnDataType.DOUBLE, DataSchema.ColumnDataType.INT})); @@ -1937,7 +1943,7 @@ private void disableForwardIndexForSomeColumns() forwardIndexDisabledColumns.add("column11"); indexLoadingConfig.setForwardIndexDisabledColumns(forwardIndexDisabledColumns); indexLoadingConfig.setRangeIndexColumns(new HashSet<>(Arrays.asList("column6", "column9"))); - indexLoadingConfig.getNoDictionaryColumns().remove("column9"); + indexLoadingConfig.removeNoDictionaryColumns("column9"); indexLoadingConfig.setReadMode(ReadMode.heap); // Reload the segments to pick up the new configs @@ -1977,7 +1983,7 @@ private void reenableForwardIndexForSomeColumns() forwardIndexDisabledColumns.remove("column6"); indexLoadingConfig.setForwardIndexDisabledColumns(forwardIndexDisabledColumns); indexLoadingConfig.setRangeIndexColumns(new HashSet<>(Collections.singletonList("column6"))); - indexLoadingConfig.getNoDictionaryColumns().add("column9"); + indexLoadingConfig.addNoDictionaryColumns("column9"); indexLoadingConfig.setReadMode(ReadMode.heap); // Reload the segments to pick up the new configs diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/ForwardIndexHandlerReloadQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/ForwardIndexHandlerReloadQueriesTest.java index 297282e09c38..147158dca7e7 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/ForwardIndexHandlerReloadQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/ForwardIndexHandlerReloadQueriesTest.java @@ -40,7 +40,10 @@ import org.apache.pinot.segment.spi.IndexSegment; import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; import org.apache.pinot.segment.spi.creator.SegmentIndexCreationDriver; +import org.apache.pinot.segment.spi.index.RangeIndexConfig; +import org.apache.pinot.segment.spi.index.StandardIndexes; import org.apache.pinot.spi.config.table.FieldConfig; +import org.apache.pinot.spi.config.table.IndexConfig; import org.apache.pinot.spi.config.table.TableConfig; import org.apache.pinot.spi.config.table.TableType; import org.apache.pinot.spi.data.FieldSpec; @@ -52,11 +55,7 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; +import static org.testng.Assert.*; /** @@ -185,9 +184,10 @@ private void createSegment(String filePath, String segmentName) segmentGeneratorConfig.setOutDir(INDEX_DIR.getAbsolutePath()); segmentGeneratorConfig.setSegmentName(segmentName); _invertedIndexColumns = Arrays.asList("column8", "column9"); - segmentGeneratorConfig.setInvertedIndexCreationColumns(_invertedIndexColumns); + segmentGeneratorConfig.setIndexOn(StandardIndexes.inverted(), IndexConfig.ENABLED, _invertedIndexColumns); segmentGeneratorConfig.setRawIndexCreationColumns(_noDictionaryColumns); - segmentGeneratorConfig.setRangeIndexCreationColumns(_rangeIndexColumns); + RangeIndexConfig config = RangeIndexConfig.DEFAULT; + segmentGeneratorConfig.setIndexOn(StandardIndexes.range(), config, _rangeIndexColumns); // The segment generation code in SegmentColumnarIndexCreator will throw // exception if start and end time in time column are not in acceptable // range. For this test, we first need to fix the input avro data @@ -256,8 +256,8 @@ public void testSelectQueries() + "column1 > 100000000 AND column2 BETWEEN 20000000 AND 1000000000 AND column3 <> 'w' AND (column6 < " + "500000 OR column7 NOT IN (225, 407)) AND daysSinceEpoch = 1756015683 ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -266,7 +266,7 @@ public void testSelectQueries() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 1384L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 913464L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); + assertNotNull(brokerResponseNative.getExceptions()); DataSchema dataSchema = new DataSchema(new String[]{ "column1", "column2", "column3", "column6", "column7", "column10" @@ -281,8 +281,8 @@ public void testSelectQueries() // Run the same query again. brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -291,7 +291,7 @@ public void testSelectQueries() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 1384L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 250896L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); + assertNotNull(brokerResponseNative.getExceptions()); dataSchema = new DataSchema(new String[]{ "column1", "column2", "column3", "column6", "column7", "column10" @@ -312,8 +312,8 @@ public void testSelectWithDistinctQueries() "SELECT DISTINCT column1, column2, column3, column6, column7, column9, column10 FROM testTable ORDER BY " + "column1 LIMIT 10"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -322,8 +322,8 @@ public void testSelectWithDistinctQueries() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 2800000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); DataSchema dataSchema = new DataSchema(new String[]{ "column1", "column2", "column3", "column6", "column7", "column9", "column10" }, new DataSchema.ColumnDataType[]{ @@ -337,8 +337,8 @@ public void testSelectWithDistinctQueries() changePropertiesAndReloadSegment(); brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -347,8 +347,8 @@ public void testSelectWithDistinctQueries() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 2800000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); dataSchema = new DataSchema(new String[]{ "column1", "column2", "column3", "column6", "column7", "column9", "column10" }, new DataSchema.ColumnDataType[]{ @@ -377,8 +377,8 @@ public void testSelectWithGroupByOrderByQueries() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 1200000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column1", "column7", "column9"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.INT})); @@ -401,8 +401,8 @@ public void testSelectWithGroupByOrderByQueries() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 1200000L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column1", "column7", "column9"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.INT})); @@ -424,8 +424,8 @@ public void testAllSelectAggregations() "SELECT MAX(column1), MIN(column1), MAX(column2), MIN(column2), MAXMV(column6), MINMV(column6), MAXMV" + "(column7), MINMV(column7), MAX(column9), MIN(column9), MAX(column10), MIN(column10) FROM testTable"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 1); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -434,8 +434,8 @@ public void testAllSelectAggregations() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 0); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{ "max(column1)", "min(column1)", "max" + "(column2)", "min(column2)", "maxmv(column6)", "minmv(column6)", @@ -453,8 +453,8 @@ public void testAllSelectAggregations() brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 1); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -463,8 +463,8 @@ public void testAllSelectAggregations() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 0); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{ "max(column1)", "min(column1)", "max" + "(column2)", "min(column2)", "maxmv(column6)", "minmv(column6)", @@ -486,8 +486,8 @@ public void testMaxArrayLengthAggregation() // TEST1 - Before Reload: Test for column7. String query1 = "SELECT MAX(ARRAYLENGTH(column7)) from testTable LIMIT 10"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query1); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 1); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -496,8 +496,8 @@ public void testMaxArrayLengthAggregation() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 400000); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"max(arraylength(column7))"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.DOUBLE})); List beforeResultRows1 = resultTable.getRows(); @@ -505,8 +505,8 @@ public void testMaxArrayLengthAggregation() // TEST2 - Before Reload: Test for column6. String query2 = "SELECT MAX(ARRAYLENGTH(column6)) from testTable LIMIT 10"; brokerResponseNative = getBrokerResponse(query2); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 1); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -515,8 +515,8 @@ public void testMaxArrayLengthAggregation() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 400000); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"max(arraylength(column6))"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.DOUBLE})); List beforeResultRows2 = resultTable.getRows(); @@ -525,8 +525,8 @@ public void testMaxArrayLengthAggregation() // TEST1 - After Reload: Test for column7. brokerResponseNative = getBrokerResponse(query1); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 1); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -535,8 +535,8 @@ public void testMaxArrayLengthAggregation() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 400000); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"max(arraylength(column7))"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.DOUBLE})); List afterResultRows1 = resultTable.getRows(); @@ -544,8 +544,8 @@ public void testMaxArrayLengthAggregation() // TEST2 - After Reload: Test for column6. brokerResponseNative = getBrokerResponse(query2); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 1); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -554,8 +554,8 @@ public void testMaxArrayLengthAggregation() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 400000); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"max(arraylength(column6))"}, new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.DOUBLE})); List afterResultRows2 = resultTable.getRows(); @@ -569,8 +569,8 @@ public void testSelectWithAggregationQueries() String query1 = "SET \"timeoutMs\" = 30000; SELECT column1, max(column1), sum(column10) from testTable WHERE " + "column7 = 2147483647 GROUP BY column1 ORDER BY column1"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query1); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -579,8 +579,8 @@ public void testSelectWithAggregationQueries() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 399_512L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 536360L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column1", "max(column1)", "sum(column10)"}, new DataSchema.ColumnDataType[]{ DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.DOUBLE, DataSchema.ColumnDataType.DOUBLE @@ -592,8 +592,8 @@ public void testSelectWithAggregationQueries() String query2 = "SELECT column1, max(column1), sum(column10) from testTable WHERE column6 = 1001 GROUP BY " + "column1 ORDER BY column1"; brokerResponseNative = getBrokerResponse(query2); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 1); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -602,8 +602,8 @@ public void testSelectWithAggregationQueries() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 16L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 426752L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column1", "max(column1)", "sum(column10)"}, new DataSchema.ColumnDataType[]{ DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.DOUBLE, DataSchema.ColumnDataType.DOUBLE @@ -614,8 +614,8 @@ public void testSelectWithAggregationQueries() // TEST1 - After reload. Test where column7 is in filter. brokerResponseNative = getBrokerResponse(query1); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 10); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -624,8 +624,8 @@ public void testSelectWithAggregationQueries() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 399_512L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column1", "max(column1)", "sum(column10)"}, new DataSchema.ColumnDataType[]{ DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.DOUBLE, DataSchema.ColumnDataType.DOUBLE @@ -636,8 +636,8 @@ public void testSelectWithAggregationQueries() // TEST2 - After Reload: Test where column6 is in filter. brokerResponseNative = getBrokerResponse(query2); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 1); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -646,8 +646,8 @@ public void testSelectWithAggregationQueries() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 16L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 426752); - assertNotNull(brokerResponseNative.getProcessingExceptions()); - assertEquals(brokerResponseNative.getProcessingExceptions().size(), 0); + assertNotNull(brokerResponseNative.getExceptions()); + assertEquals(brokerResponseNative.getExceptions().size(), 0); assertEquals(resultTable.getDataSchema(), new DataSchema(new String[]{"column1", "max(column1)", "sum(column10)"}, new DataSchema.ColumnDataType[]{ DataSchema.ColumnDataType.INT, DataSchema.ColumnDataType.DOUBLE, DataSchema.ColumnDataType.DOUBLE @@ -662,8 +662,8 @@ public void testRangeIndexAfterReload() throws Exception { String query = "select count(*) from testTable where column10 > 674022574 and column9 < 674022574"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable1 = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 1); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -672,7 +672,7 @@ public void testRangeIndexAfterReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 0L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 479412L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); + assertNotNull(brokerResponseNative.getExceptions()); DataSchema dataSchema = new DataSchema(new String[]{ "count(*)" @@ -685,8 +685,8 @@ public void testRangeIndexAfterReload() changePropertiesAndReloadSegment(); brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); resultTable1 = brokerResponseNative.getResultTable(); assertEquals(brokerResponseNative.getNumRowsResultSet(), 1); assertEquals(brokerResponseNative.getTotalDocs(), 400_000L); @@ -695,7 +695,7 @@ public void testRangeIndexAfterReload() assertEquals(brokerResponseNative.getNumSegmentsMatched(), 4L); assertEquals(brokerResponseNative.getNumEntriesScannedPostFilter(), 0L); assertEquals(brokerResponseNative.getNumEntriesScannedInFilter(), 0L); - assertNotNull(brokerResponseNative.getProcessingExceptions()); + assertNotNull(brokerResponseNative.getExceptions()); dataSchema = new DataSchema(new String[]{ "count(*)" @@ -744,15 +744,15 @@ private void changePropertiesAndReloadSegment() invertedIndexEnabledColumns.add("column2"); invertedIndexEnabledColumns.add("column7"); indexLoadingConfig.setInvertedIndexColumns(invertedIndexEnabledColumns); - indexLoadingConfig.getInvertedIndexColumns().remove("column9"); + indexLoadingConfig.removeInvertedIndexColumns("column9"); Set noDictionaryColumns = new HashSet<>(_noDictionaryColumns); indexLoadingConfig.setNoDictionaryColumns(noDictionaryColumns); - indexLoadingConfig.getNoDictionaryColumns().remove("column2"); - indexLoadingConfig.getNoDictionaryColumns().remove("column3"); - indexLoadingConfig.getNoDictionaryColumns().remove("column7"); - indexLoadingConfig.getNoDictionaryColumns().remove("column10"); - indexLoadingConfig.getNoDictionaryColumns().add("column6"); - indexLoadingConfig.getNoDictionaryColumns().add("column9"); + indexLoadingConfig.removeNoDictionaryColumns("column2"); + indexLoadingConfig.removeNoDictionaryColumns("column3"); + indexLoadingConfig.removeNoDictionaryColumns("column7"); + indexLoadingConfig.removeNoDictionaryColumns("column10"); + indexLoadingConfig.addNoDictionaryColumns("column6"); + indexLoadingConfig.addNoDictionaryColumns("column9"); Set rangeIndexColumns = new HashSet<>(_rangeIndexColumns); indexLoadingConfig.setRangeIndexColumns(rangeIndexColumns); indexLoadingConfig.setReadMode(ReadMode.heap); diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/FrequentItemsSketchQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/FrequentItemsSketchQueriesTest.java new file mode 100644 index 000000000000..deee8f64a3fc --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/queries/FrequentItemsSketchQueriesTest.java @@ -0,0 +1,387 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.queries; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.commons.io.FileUtils; +import org.apache.datasketches.common.ArrayOfStringsSerDe; +import org.apache.datasketches.frequencies.ErrorType; +import org.apache.datasketches.frequencies.ItemsSketch; +import org.apache.datasketches.frequencies.LongsSketch; +import org.apache.datasketches.memory.Memory; +import org.apache.pinot.common.response.BrokerResponse; +import org.apache.pinot.common.response.broker.ResultTable; +import org.apache.pinot.core.operator.blocks.results.AggregationResultsBlock; +import org.apache.pinot.core.operator.query.AggregationOperator; +import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; +import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; +import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; +import org.apache.pinot.segment.spi.ImmutableSegment; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.data.DimensionFieldSpec; +import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.data.MetricFieldSpec; +import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.spi.data.readers.GenericRow; +import org.apache.pinot.spi.data.readers.RecordReader; +import org.apache.pinot.spi.utils.ReadMode; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + + +/** + * Tests for FREQUENT_STRINGS_SKETCH and FREQUENT_LONGS_SKETCH aggregation functions. + * + *
      + *
    • Generates a segment with LONG, STRING, SKETCH and a group-by column
    • + *
    • Runs aggregation and group-by queries on the generated segment
    • + *
    • Compares the results from sketches to exact calculations via count()
    • + *
    + */ +public class FrequentItemsSketchQueriesTest extends BaseQueriesTest { + protected static final File INDEX_DIR = new File(FileUtils.getTempDirectory(), "FrequentItemsQueriesTest"); + protected static final String TABLE_NAME = "testTable"; + protected static final String SEGMENT_NAME = "testSegment"; + + protected static final int MAX_MAP_SIZE = 64; + protected static final String LONG_COLUMN = "longColumn"; + protected static final String STRING_COLUMN = "stringColumn"; + protected static final String STRING_SKETCH_COLUMN = "stringSketchColumn"; + protected static final String LONG_SKETCH_COLUMN = "longSketchColumn"; + protected static final String GROUP_BY_COLUMN = "groupByColumn"; + + private IndexSegment _indexSegment; + private List _indexSegments; + + @Override + protected String getFilter() { + return ""; // No filtering required for this test. + } + + @Override + protected IndexSegment getIndexSegment() { + return _indexSegment; + } + + @Override + protected List getIndexSegments() { + return _indexSegments; + } + + @BeforeClass + public void setUp() + throws Exception { + FileUtils.deleteQuietly(INDEX_DIR); + + buildSegment(); + ImmutableSegment immutableSegment = ImmutableSegmentLoader.load(new File(INDEX_DIR, SEGMENT_NAME), ReadMode.mmap); + _indexSegment = immutableSegment; + _indexSegments = Arrays.asList(immutableSegment, immutableSegment); + } + + protected void buildSegment() throws Exception { + + // Values chosen with distinct frequencies not to create ambiguity in testing + String[] strValues = new String[] {"a", "a", "a", "b", "b", "a", "d", "d", "c", "d"}; + Long[] longValues = new Long[] {1L, 2L, 1L, 1L, 1L, 2L, 5L, 4L, 4L, 4L}; + String[] groups = new String[] {"g1", "g1", "g1", "g1", "g1", "g1", "g2", "g2", "g2", "g2"}; + + List rows = new ArrayList<>(strValues.length); + for (int i = 0; i < strValues.length; i++) { + GenericRow row = new GenericRow(); + + row.putValue(LONG_COLUMN, longValues[i]); + row.putValue(STRING_COLUMN, strValues[i]); + + LongsSketch longSketch = new LongsSketch(MAX_MAP_SIZE); + longSketch.update(longValues[i]); + row.putValue(LONG_SKETCH_COLUMN, longSketch.toByteArray()); + + ItemsSketch strSketch = new ItemsSketch<>(MAX_MAP_SIZE); + strSketch.update(strValues[i]); + row.putValue(STRING_SKETCH_COLUMN, strSketch.toByteArray(new ArrayOfStringsSerDe())); + + row.putValue(GROUP_BY_COLUMN, groups[i]); + + rows.add(row); + } + + Schema schema = new Schema(); + schema.addField(new DimensionFieldSpec(LONG_COLUMN, FieldSpec.DataType.LONG, true)); + schema.addField(new DimensionFieldSpec(STRING_COLUMN, FieldSpec.DataType.STRING, true)); + schema.addField(new MetricFieldSpec(LONG_SKETCH_COLUMN, FieldSpec.DataType.BYTES)); + schema.addField(new MetricFieldSpec(STRING_SKETCH_COLUMN, FieldSpec.DataType.BYTES)); + schema.addField(new DimensionFieldSpec(GROUP_BY_COLUMN, FieldSpec.DataType.STRING, true)); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build(); + + SegmentGeneratorConfig config = new SegmentGeneratorConfig(tableConfig, schema); + config.setOutDir(INDEX_DIR.getPath()); + config.setTableName(TABLE_NAME); + config.setSegmentName(SEGMENT_NAME); + + SegmentIndexCreationDriverImpl driver = new SegmentIndexCreationDriverImpl(); + try (RecordReader recordReader = new GenericRowRecordReader(rows)) { + driver.init(config, recordReader); + driver.build(); + } + } + + @Test + public void testAggregationForStringValues() { + // Fetch the sketch object which collects Frequent Items + String query = String.format( + "SELECT FREQUENTSTRINGSSKETCH(%1$s) FROM %2$s", STRING_COLUMN, TABLE_NAME); + AggregationOperator aggregationOperator = getOperator(query); + AggregationResultsBlock resultsBlock = aggregationOperator.nextBlock(); + List aggregationResult = resultsBlock.getResults(); + + assertNotNull(aggregationResult); + assertEquals(aggregationResult.size(), 1); + + // Fetch the exact list by count/group-by and compare + String[] exactOrdered = getExactOrderedStrings(); + assertStringsSketch((ItemsSketch) aggregationResult.get(0), exactOrdered); + } + + @Test + public void testAggregationForLongValues() { + // Fetch the sketch object which collects Frequent Items + String query = String.format( + "SELECT FREQUENTLONGSSKETCH(%1$s) FROM %2$s", LONG_COLUMN, TABLE_NAME); + AggregationOperator aggregationOperator = getOperator(query); + AggregationResultsBlock resultsBlock = aggregationOperator.nextBlock(); + List aggregationResult = resultsBlock.getResults(); + + assertNotNull(aggregationResult); + assertEquals(aggregationResult.size(), 1); + + // Fetch the exact list by count/group-by and compare + Long[] exactOrdered = getExactOrderedLongs(); + assertLongsSketch((LongsSketch) aggregationResult.get(0), exactOrdered); + } + + @Test + public void testAggregationForStringSketches() { + // Retrieve sketches calculated by: 1) merger of sketches, 2) from plain values + String query = String.format( + "SELECT FREQUENTSTRINGSSKETCH(%1$s), FREQUENTSTRINGSSKETCH(%2$s) FROM %3$s", + STRING_SKETCH_COLUMN, STRING_COLUMN, TABLE_NAME); + AggregationOperator aggregationOperator = getOperator(query); + AggregationResultsBlock resultsBlock = aggregationOperator.nextBlock(); + List aggregationResult = resultsBlock.getResults(); + + assertNotNull(aggregationResult); + assertEquals(aggregationResult.size(), 2); + + // Assert the sketches are equivalent + ItemsSketch sketch1 = (ItemsSketch) aggregationResult.get(0); + ItemsSketch sketch2 = (ItemsSketch) aggregationResult.get(1); + assertEquals( + sketch1.getFrequentItems(ErrorType.NO_FALSE_NEGATIVES), + sketch2.getFrequentItems(ErrorType.NO_FALSE_NEGATIVES)); + } + + @Test + public void testAggregationForLongSketches() { + // Retrieve sketches calculated by: 1) merger of sketches, 2) from plain values + String query = String.format( + "SELECT FREQUENTLONGSSKETCH(%1$s), FREQUENTLONGSSKETCH(%2$s) FROM %3$s", + LONG_SKETCH_COLUMN, LONG_COLUMN, TABLE_NAME); + AggregationOperator aggregationOperator = getOperator(query); + AggregationResultsBlock resultsBlock = aggregationOperator.nextBlock(); + List aggregationResult = resultsBlock.getResults(); + + assertNotNull(aggregationResult); + assertEquals(aggregationResult.size(), 2); + + // Assert the sketches are equivalent + LongsSketch sketch1 = (LongsSketch) aggregationResult.get(0); + LongsSketch sketch2 = (LongsSketch) aggregationResult.get(1); + assertEquals( + sketch1.getFrequentItems(ErrorType.NO_FALSE_NEGATIVES), + sketch2.getFrequentItems(ErrorType.NO_FALSE_NEGATIVES)); + } + + @Test + public void testGroupByStringSketches() { + // Fetch the sketch object which collects Frequent Items + String query = String.format( + "SELECT %1$s, FREQUENTSTRINGSSKETCH(%2$s) FROM %3$s GROUP BY 1", + GROUP_BY_COLUMN, STRING_COLUMN, TABLE_NAME); + BrokerResponse brokerResponse = getBrokerResponse(query); + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + + assertNotNull(rows); + assertEquals(rows.size(), 2); // should be 2 groups + + // Fetch the exact list by count/group-by and compare + Map> exactOrdered = getExactOrderedStringGroups(); + for (Object[] row: rows) { + String group = (String) row[0]; + ItemsSketch sketch = decodeStringsSketch((String) row[1]); + List exactOrder = exactOrdered.get(group); + assertStringsSketch(sketch, exactOrder); + } + } + + @Test + public void testGroupByLongSketches() { + // Fetch the sketch object which collects Frequent Items + String query = String.format( + "SELECT %1$s, FREQUENTLONGSSKETCH(%2$s) FROM %3$s GROUP BY 1", + GROUP_BY_COLUMN, LONG_COLUMN, TABLE_NAME); + BrokerResponse brokerResponse = getBrokerResponse(query); + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + + assertNotNull(rows); + assertEquals(rows.size(), 2); // should be 2 groups + + // Fetch the exact list by count/group-by and compare + Map> exactOrdered = getExactOrderedLongGroups(); + for (Object[] row: rows) { + String group = (String) row[0]; + LongsSketch sketch = decodeLongsSketch((String) row[1]); + List exactOrder = exactOrdered.get(group); + assertLongsSketch(sketch, exactOrder); + } + } + + + private String[] getExactOrderedStrings() { + Object[] objects = getExactOrderForColumn(STRING_COLUMN); + return Arrays.copyOf(objects, objects.length, String[].class); + } + + private Long[] getExactOrderedLongs() { + Object[] objects = getExactOrderForColumn(LONG_COLUMN); + return Arrays.copyOf(objects, objects.length, Long[].class); + } + + private Object[] getExactOrderForColumn(String col) { + String query = String.format( + "SELECT %1$s, COUNT(1) FROM %2$s GROUP BY 1 ORDER BY 2 DESC", col, TABLE_NAME); + BrokerResponse resp = getBrokerResponse(query); + ResultTable results = resp.getResultTable(); + List rows = results.getRows(); + return rows.stream().map((Object[] row) -> row[0]).toArray(); + } + + private Object[] getExactOrderForColumn2(String query) { + BrokerResponse resp = getBrokerResponse(query); + ResultTable results = resp.getResultTable(); + List rows = results.getRows(); + return rows.stream().map((Object[] row) -> row[0]).toArray(); + } + + private Map> getExactOrderedStringGroups() { + String query = String.format( + "SELECT %1$s, %2$s, COUNT(1) FROM %3$s GROUP BY 1,2 ORDER BY 3 DESC", + GROUP_BY_COLUMN, STRING_COLUMN, TABLE_NAME); + BrokerResponse resp = getBrokerResponse(query); + ResultTable results = resp.getResultTable(); + List rows = results.getRows(); + Map> order = new HashMap<>(); + for (Object[] row: rows) { + String group = (String) row[0]; + if (!order.containsKey(group)) { + order.put(group, new ArrayList<>()); + } + order.get(group).add((String) row[1]); + } + return order; + } + + private Map> getExactOrderedLongGroups() { + String query = String.format( + "SELECT %1$s, %2$s, COUNT(1) FROM %3$s GROUP BY 1,2 ORDER BY 3 DESC", + GROUP_BY_COLUMN, LONG_COLUMN, TABLE_NAME); + BrokerResponse resp = getBrokerResponse(query); + ResultTable results = resp.getResultTable(); + List rows = results.getRows(); + Map> order = new HashMap<>(); + for (Object[] row: rows) { + String group = (String) row[0]; + if (!order.containsKey(group)) { + order.put(group, new ArrayList<>()); + } + order.get(group).add((Long) row[1]); + } + return order; + } + + private void assertStringsSketch(ItemsSketch sketch, List exact) { + String[] arr = new String[exact.size()]; + exact.toArray(arr); + assertStringsSketch(sketch, arr); + } + + private void assertStringsSketch(ItemsSketch sketch, String[] exact) { + ItemsSketch.Row[] items = sketch.getFrequentItems(ErrorType.NO_FALSE_NEGATIVES); + assertEquals(exact.length, items.length); + for (int i = 0; i < exact.length; i++) { + assertEquals((String) items[i].getItem(), exact[i]); + } + } + + private void assertLongsSketch(LongsSketch sketch, List exact) { + Long[] arr = new Long[exact.size()]; + exact.toArray(arr); + assertLongsSketch(sketch, arr); + } + + private void assertLongsSketch(LongsSketch sketch, Long[] exact) { + LongsSketch.Row[] items = sketch.getFrequentItems(ErrorType.NO_FALSE_NEGATIVES); + assertEquals(exact.length, items.length); + for (int i = 0; i < exact.length; i++) { + assertEquals((Long) items[i].getItem(), exact[i]); + } + } + + private ItemsSketch decodeStringsSketch(String encodedSketch) { + byte[] byteArr = Base64.getDecoder().decode(encodedSketch); + return ItemsSketch.getInstance(Memory.wrap(byteArr), new ArrayOfStringsSerDe()); + } + + private LongsSketch decodeLongsSketch(String encodedSketch) { + byte[] byteArr = Base64.getDecoder().decode(encodedSketch); + return LongsSketch.getInstance(Memory.wrap(byteArr)); + } + + @AfterClass + public void tearDown() { + _indexSegment.destroy(); + FileUtils.deleteQuietly(INDEX_DIR); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/FunnelCountQueriesBitmapTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/FunnelCountQueriesBitmapTest.java new file mode 100644 index 000000000000..1e36838f3b6e --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/queries/FunnelCountQueriesBitmapTest.java @@ -0,0 +1,83 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.queries; + +import java.util.Collections; +import java.util.List; +import org.apache.pinot.segment.local.indexsegment.mutable.MutableSegmentImplTestUtils; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.MutableSegment; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.data.readers.GenericRow; +import org.roaringbitmap.RoaringBitmap; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + + +/** + * Queries test for FUNNEL_COUNT queries. + */ +@SuppressWarnings("rawtypes") +public class FunnelCountQueriesBitmapTest extends BaseFunnelCountQueriesTest { + + @Override + protected String getSettings() { + return "SETTINGS('bitmap')"; + } + + @Override + protected int getExpectedNumEntriesScannedInFilter() { + return NUM_RECORDS; + } + + @Override + protected int getExpectedInterSegmentMultiplier() { + return 1; + } + + @Override + protected TableConfig getTableConfig() { + return TABLE_CONFIG_BUILDER.build(); + } + + @Override + protected IndexSegment buildSegment(List records) + throws Exception { + MutableSegment mutableSegment = + MutableSegmentImplTestUtils.createMutableSegmentImpl(SCHEMA, Collections.emptySet(), Collections.emptySet(), + Collections.emptySet(), false); + for (GenericRow record : records) { + mutableSegment.index(record, null); + } + return mutableSegment; + } + + @Override + protected void assertIntermediateResult(Object intermediateResult, long[] expectedCounts) { + assertTrue(intermediateResult instanceof List); + List bitmaps = (List) intermediateResult; + // First step should match + assertEquals(Math.round(bitmaps.get(0).getCardinality()), expectedCounts[0]); + for (int i = 1; i < bitmaps.size(); i++) { + // Sets are yet to be intersected, we check that they are at least the size of the expected counts at this stage. + assertTrue(Math.round(bitmaps.get(i).getCardinality()) >= expectedCounts[i]); + } + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/FunnelCountQueriesPartitionedSortedTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/FunnelCountQueriesPartitionedSortedTest.java new file mode 100644 index 000000000000..a453886050c0 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/queries/FunnelCountQueriesPartitionedSortedTest.java @@ -0,0 +1,85 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.queries; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import java.io.File; +import java.util.Comparator; +import java.util.List; +import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; +import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; +import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.data.readers.GenericRow; +import org.apache.pinot.spi.utils.ReadMode; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + + +/** + * Queries test for FUNNEL_COUNT queries using sorted strategy. + */ +@SuppressWarnings("rawtypes") +public class FunnelCountQueriesPartitionedSortedTest extends BaseFunnelCountQueriesTest { + + @Override + protected String getSettings() { + return "SETTINGS('partitioned', 'sorted')"; + } + + @Override + protected int getExpectedNumEntriesScannedInFilter() { + return 0; + } + + @Override + protected int getExpectedInterSegmentMultiplier() { + return 4; + } + + @Override + protected TableConfig getTableConfig() { + return TABLE_CONFIG_BUILDER.setSortedColumn(ID_COLUMN).build(); + } + + @Override + protected IndexSegment buildSegment(List records) + throws Exception { + // Simulate PinotSegmentSorter + records.sort(Comparator.comparingInt(rec -> (Integer) rec.getValue(ID_COLUMN))); + + SegmentGeneratorConfig segmentGeneratorConfig = new SegmentGeneratorConfig(getTableConfig(), SCHEMA); + segmentGeneratorConfig.setTableName(RAW_TABLE_NAME); + segmentGeneratorConfig.setSegmentName(SEGMENT_NAME); + segmentGeneratorConfig.setOutDir(INDEX_DIR.getPath()); + SegmentIndexCreationDriverImpl driver = new SegmentIndexCreationDriverImpl(); + driver.init(segmentGeneratorConfig, new GenericRowRecordReader(records)); + driver.build(); + return ImmutableSegmentLoader.load(new File(INDEX_DIR, SEGMENT_NAME), ReadMode.mmap); + } + + @Override + protected void assertIntermediateResult(Object intermediateResult, long[] expectedCounts) { + assertTrue(intermediateResult instanceof LongArrayList); + assertEquals(((LongArrayList) intermediateResult).elements(), expectedCounts); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/FunnelCountQueriesPartitionedTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/FunnelCountQueriesPartitionedTest.java new file mode 100644 index 000000000000..da3f436b58dc --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/queries/FunnelCountQueriesPartitionedTest.java @@ -0,0 +1,77 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.queries; + +import it.unimi.dsi.fastutil.longs.LongArrayList; +import java.util.Collections; +import java.util.List; +import org.apache.pinot.segment.local.indexsegment.mutable.MutableSegmentImplTestUtils; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.MutableSegment; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.data.readers.GenericRow; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + + +/** + * Queries test for FUNNEL_COUNT queries. + */ +@SuppressWarnings("rawtypes") +public class FunnelCountQueriesPartitionedTest extends BaseFunnelCountQueriesTest { + + @Override + protected String getSettings() { + return "SETTINGS('partitioned')"; + } + + @Override + protected int getExpectedNumEntriesScannedInFilter() { + return NUM_RECORDS; + } + + @Override + protected int getExpectedInterSegmentMultiplier() { + return 4; + } + + @Override + protected TableConfig getTableConfig() { + return TABLE_CONFIG_BUILDER.build(); + } + + @Override + protected IndexSegment buildSegment(List records) + throws Exception { + MutableSegment mutableSegment = + MutableSegmentImplTestUtils.createMutableSegmentImpl(SCHEMA, Collections.emptySet(), Collections.emptySet(), + Collections.emptySet(), false); + for (GenericRow record : records) { + mutableSegment.index(record, null); + } + return mutableSegment; + } + + @Override + protected void assertIntermediateResult(Object intermediateResult, long[] expectedCounts) { + assertTrue(intermediateResult instanceof LongArrayList); + assertEquals(((LongArrayList) intermediateResult).elements(), expectedCounts); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/FunnelCountQueriesSetTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/FunnelCountQueriesSetTest.java new file mode 100644 index 000000000000..b9361bc6183e --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/queries/FunnelCountQueriesSetTest.java @@ -0,0 +1,83 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.queries; + +import java.util.Collections; +import java.util.List; +import java.util.Set; +import org.apache.pinot.segment.local.indexsegment.mutable.MutableSegmentImplTestUtils; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.MutableSegment; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.data.readers.GenericRow; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + + +/** + * Queries test for FUNNEL_COUNT queries. + */ +@SuppressWarnings("rawtypes") +public class FunnelCountQueriesSetTest extends BaseFunnelCountQueriesTest { + + @Override + protected String getSettings() { + return "SETTINGS('set')"; + } + + @Override + protected int getExpectedNumEntriesScannedInFilter() { + return NUM_RECORDS; + } + + @Override + protected int getExpectedInterSegmentMultiplier() { + return 1; + } + + @Override + protected TableConfig getTableConfig() { + return TABLE_CONFIG_BUILDER.build(); + } + + @Override + protected IndexSegment buildSegment(List records) + throws Exception { + MutableSegment mutableSegment = + MutableSegmentImplTestUtils.createMutableSegmentImpl(SCHEMA, Collections.emptySet(), Collections.emptySet(), + Collections.emptySet(), false); + for (GenericRow record : records) { + mutableSegment.index(record, null); + } + return mutableSegment; + } + + @Override + protected void assertIntermediateResult(Object intermediateResult, long[] expectedCounts) { + assertTrue(intermediateResult instanceof List); + List sets = (List) intermediateResult; + // First step should match + assertEquals(Math.round(sets.get(0).size()), expectedCounts[0]); + for (int i = 1; i < sets.size(); i++) { + // Sets are yet to be intersected, we check that they are at least the size of the expected counts at this stage. + assertTrue(Math.round(sets.get(i).size()) >= expectedCounts[i]); + } + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/FunnelCountQueriesThetaSketchTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/FunnelCountQueriesThetaSketchTest.java new file mode 100644 index 000000000000..ef5515328078 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/queries/FunnelCountQueriesThetaSketchTest.java @@ -0,0 +1,83 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.queries; + +import java.util.Collections; +import java.util.List; +import org.apache.datasketches.theta.Sketch; +import org.apache.pinot.segment.local.indexsegment.mutable.MutableSegmentImplTestUtils; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.MutableSegment; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.data.readers.GenericRow; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertTrue; + + +/** + * Queries test for FUNNEL_COUNT queries. + */ +@SuppressWarnings("rawtypes") +public class FunnelCountQueriesThetaSketchTest extends BaseFunnelCountQueriesTest { + + @Override + protected int getExpectedNumEntriesScannedInFilter() { + return NUM_RECORDS; + } + + @Override + protected int getExpectedInterSegmentMultiplier() { + return 1; + } + + @Override + protected TableConfig getTableConfig() { + return TABLE_CONFIG_BUILDER.build(); + } + + @Override + protected IndexSegment buildSegment(List records) + throws Exception { + MutableSegment mutableSegment = + MutableSegmentImplTestUtils.createMutableSegmentImpl(SCHEMA, Collections.emptySet(), Collections.emptySet(), + Collections.emptySet(), false); + for (GenericRow record : records) { + mutableSegment.index(record, null); + } + return mutableSegment; + } + + @Override + protected void assertIntermediateResult(Object intermediateResult, long[] expectedCounts) { + assertTrue(intermediateResult instanceof List); + List sketches = (List) intermediateResult; + // First step should match + assertEquals(Math.round(sketches.get(0).getEstimate()), expectedCounts[0]); + for (int i = 1; i < sketches.size(); i++) { + // Sets are yet to be intersected, we check that they are at least the size of the expected counts at this stage. + assertTrue(Math.round(sketches.get(i).getEstimate()) >= expectedCounts[i]); + } + } + + @Override + protected String getSettings() { + return "SETTINGS('theta_sketch')"; + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/GapfillQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/GapfillQueriesTest.java index 01bad2f18904..91cce6866163 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/GapfillQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/GapfillQueriesTest.java @@ -128,6 +128,11 @@ public void setUp() records.add(createRow("2021-11-07 10:33:00.000", 1, 0, false)); records.add(createRow("2021-11-07 11:54:00.000", 0, 1, false)); records.add(createRow("2021-11-07 11:57:00.000", 1, 1, false)); + records.add(createRow("2023-09-07 04:01:00.000", 1, 1, false)); + records.add(createRow("2023-09-07 04:02:00.000", 1, 1, true)); + records.add(createRow("2023-09-07 05:11:00.000", 1, 1, false)); + records.add(createRow("2023-09-07 07:07:00.000", 1, 1, true)); + records.add(createRow("2023-09-07 09:37:00.000", 1, 1, false)); SegmentGeneratorConfig segmentGeneratorConfig = new SegmentGeneratorConfig(TABLE_CONFIG, SCHEMA); segmentGeneratorConfig.setTableName(RAW_TABLE_NAME); @@ -4028,6 +4033,44 @@ public void datetimeconvertGapfillTestAggregateAggregateWithExtraData() { } } + @Test + public void datetimeconvertGapfillTestAggregateAggregateOutOfBoundary() { + String gapfillQuery1 = "SELECT " + + "time_col, SUM(occupied) as occupied_slots_count " + + "FROM (" + + " SELECT GapFill(time_col, '1:MILLISECONDS:EPOCH', 1694066400000, 1694077200000, '1:HOURS', '1:HOURS'," + + " FILL(occupied, 'FILL_PREVIOUS_VALUE'), TIMESERIESON(levelId, lotId)) AS time_col," + + " occupied, lotId, levelId" + + " FROM (" + + " SELECT DATETRUNC('hour', eventTime, 'milliseconds') AS time_col," + + " lastWithTime(isOccupied, eventTime, 'INT') as occupied, lotId, levelId" + + " FROM parkingData " + + " WHERE eventTime >= 1694044800000 AND eventTime <= 1694131200000 " + + " GROUP BY time_col, levelId, lotId " + + " ORDER BY time_col " + + " LIMIT 200 " + + " ) " + + " LIMIT 200 " + + ") " + + " GROUP BY time_col " + + " LIMIT 200 "; + + BrokerResponseNative gapfillBrokerResponse1 = getBrokerResponse(gapfillQuery1); + + double[] expectedOccupiedSlotsCounts1 = new double[]{0, 1, 1}; + ResultTable gapFillResultTable1 = gapfillBrokerResponse1.getResultTable(); + List gapFillRows1 = gapFillResultTable1.getRows(); + Assert.assertEquals(gapFillRows1.size(), expectedOccupiedSlotsCounts1.length); + DateTimeGranularitySpec dateTimeGranularity = new DateTimeGranularitySpec("1:HOURS"); + long start = 1694066400000L; + for (int i = 0; i < expectedOccupiedSlotsCounts1.length; i++) { + long timeStamp = (Long) gapFillRows1.get(i)[0]; + Assert.assertEquals(timeStamp, start); + Assert.assertEquals(expectedOccupiedSlotsCounts1[i], gapFillRows1.get(i)[1]); + start += dateTimeGranularity.granularityToMillis(); + } + } + @AfterClass public void tearDown() throws IOException { diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/HistogramQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/HistogramQueriesTest.java index 9fe29ec5d09a..c400546c4f3e 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/HistogramQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/HistogramQueriesTest.java @@ -312,7 +312,7 @@ public void testInvalidInput() { operator.nextBlock(); } catch (Exception e) { assertEquals(e.getMessage(), - "Invalid aggregation function: histogram(intColumn,arrayvalueconstructor('0')); Reason: The number of " + "Invalid aggregation function: histogram(intColumn,'[0]'); Reason: The number of " + "bin edges must be greater than 1"); } @@ -333,7 +333,7 @@ public void testInvalidInput() { operator.nextBlock(); } catch (Exception e) { assertEquals(e.getMessage(), - "Invalid aggregation function: histogram(intColumn,arrayvalueconstructor('0','0','1','2')); Reason: The " + "Invalid aggregation function: histogram(intColumn,'[0, 0, 1, 2]'); Reason: The " + "bin edges must be strictly increasing"); } diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/InnerSegmentAggregationSingleValueQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/InnerSegmentAggregationSingleValueQueriesTest.java index 8d0e187521a3..dbf9565f67c5 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/InnerSegmentAggregationSingleValueQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/InnerSegmentAggregationSingleValueQueriesTest.java @@ -66,8 +66,8 @@ public void testFilteredAggregations() { + "FROM testTable WHERE column3 > 0"; FilteredAggregationOperator aggregationOperator = getOperator(query); AggregationResultsBlock resultsBlock = aggregationOperator.nextBlock(); - QueriesTestUtils.testInnerSegmentExecutionStatistics(aggregationOperator.getExecutionStatistics(), 180000L, 0L, - 540000L, 30000L); + QueriesTestUtils.testInnerSegmentExecutionStatistics(aggregationOperator.getExecutionStatistics(), 150000L, 0L, + 120000L, 30000L); QueriesTestUtils.testInnerSegmentAggregationResult(resultsBlock.getResults(), 22266008882250L, 30000, 2147419555, 32289159189150L, 28175373944314L, 30000L); @@ -76,8 +76,8 @@ public void testFilteredAggregations() { + "FROM testTable"; aggregationOperator = getOperator(query); resultsBlock = aggregationOperator.nextBlock(); - QueriesTestUtils.testInnerSegmentExecutionStatistics(aggregationOperator.getExecutionStatistics(), 180000L, 0L, - 540000L, 30000L); + QueriesTestUtils.testInnerSegmentExecutionStatistics(aggregationOperator.getExecutionStatistics(), 150000L, 0L, + 120000L, 30000L); QueriesTestUtils.testInnerSegmentAggregationResult(resultsBlock.getResults(), 22266008882250L, 30000, 2147419555, 32289159189150L, 28175373944314L, 30000L); @@ -86,8 +86,8 @@ public void testFilteredAggregations() { + "SUM(column3), AVG(column7) FILTER(WHERE column7 > 0 AND column7 < 100) FROM testTable"; aggregationOperator = getOperator(query); resultsBlock = aggregationOperator.nextBlock(); - QueriesTestUtils.testInnerSegmentExecutionStatistics(aggregationOperator.getExecutionStatistics(), 150000L, 0L, - 450000L, 30000L); + QueriesTestUtils.testInnerSegmentExecutionStatistics(aggregationOperator.getExecutionStatistics(), 120000L, 0L, + 90000L, 30000L); QueriesTestUtils.testInnerSegmentAggregationResult(resultsBlock.getResults(), 22266008882250L, 30000, 2147419555, 32289159189150L, 0L, 0L); } diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/InnerSegmentSelectionMultiValueQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/InnerSegmentSelectionMultiValueQueriesTest.java index 876baf7a9860..a2c62d888d32 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/InnerSegmentSelectionMultiValueQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/InnerSegmentSelectionMultiValueQueriesTest.java @@ -21,7 +21,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.PriorityQueue; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.operator.BaseOperator; import org.apache.pinot.core.operator.ExecutionStatistics; @@ -175,7 +174,7 @@ public void testSelectionOnly() { assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column1")), DataSchema.ColumnDataType.INT); assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column6")), DataSchema.ColumnDataType.INT_ARRAY); - selectionResult = (List) resultsBlock.getRows(); + selectionResult = resultsBlock.getRows(); assertEquals(selectionResult.size(), 10); firstRow = selectionResult.get(0); assertEquals(firstRow.length, 3); @@ -203,9 +202,9 @@ public void testSelectionOrderBy() { assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column1")), DataSchema.ColumnDataType.INT); assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column6")), DataSchema.ColumnDataType.INT_ARRAY); - PriorityQueue selectionResult = resultsBlock.getRowsAsPriorityQueue(); + List selectionResult = resultsBlock.getRows(); assertEquals(selectionResult.size(), 10); - Object[] lastRow = selectionResult.peek(); + Object[] lastRow = selectionResult.get(9); assertEquals(lastRow.length, 4); assertEquals((String) lastRow[columnIndexMap.get("column5")], "AKXcXcIqsqOJFsdwxZ"); assertEquals(lastRow[columnIndexMap.get("column6")], new int[]{1252}); @@ -228,9 +227,9 @@ public void testSelectionOrderBy() { assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column1")), DataSchema.ColumnDataType.INT); assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column6")), DataSchema.ColumnDataType.INT_ARRAY); - selectionResult = resultsBlock.getRowsAsPriorityQueue(); + selectionResult = resultsBlock.getRows(); assertEquals(selectionResult.size(), 10); - lastRow = selectionResult.peek(); + lastRow = selectionResult.get(9); assertEquals(lastRow.length, 4); assertEquals((String) lastRow[columnIndexMap.get("column5")], "AKXcXcIqsqOJFsdwxZ"); assertEquals(lastRow[columnIndexMap.get("column6")], new int[]{2147483647}); diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/InnerSegmentSelectionMultiValueRawQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/InnerSegmentSelectionMultiValueRawQueriesTest.java index 9e3abdc92912..c28e8d9d9a8b 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/InnerSegmentSelectionMultiValueRawQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/InnerSegmentSelectionMultiValueRawQueriesTest.java @@ -21,7 +21,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.PriorityQueue; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.core.operator.BaseOperator; import org.apache.pinot.core.operator.ExecutionStatistics; @@ -203,9 +202,9 @@ public void testSelectionOrderBy() { assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column1")), DataSchema.ColumnDataType.INT); assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column6")), DataSchema.ColumnDataType.INT_ARRAY); - PriorityQueue selectionResult = resultsBlock.getRowsAsPriorityQueue(); + List selectionResult = resultsBlock.getRows(); assertEquals(selectionResult.size(), 10); - Object[] lastRow = selectionResult.peek(); + Object[] lastRow = selectionResult.get(9); assertEquals(lastRow.length, 4); assertEquals((String) lastRow[columnIndexMap.get("column5")], "AKXcXcIqsqOJFsdwxZ"); assertEquals(lastRow[columnIndexMap.get("column6")], new int[]{1252}); @@ -228,9 +227,9 @@ public void testSelectionOrderBy() { assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column1")), DataSchema.ColumnDataType.INT); assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column6")), DataSchema.ColumnDataType.INT_ARRAY); - selectionResult = resultsBlock.getRowsAsPriorityQueue(); + selectionResult = resultsBlock.getRows(); assertEquals(selectionResult.size(), 10); - lastRow = selectionResult.peek(); + lastRow = selectionResult.get(9); assertEquals(lastRow.length, 4); assertEquals((String) lastRow[columnIndexMap.get("column5")], "AKXcXcIqsqOJFsdwxZ"); assertEquals(lastRow[columnIndexMap.get("column6")], new int[]{2147483647}); diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/InnerSegmentSelectionSingleValueQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/InnerSegmentSelectionSingleValueQueriesTest.java index 9506bcbb9f98..92d24fc72c05 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/InnerSegmentSelectionSingleValueQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/InnerSegmentSelectionSingleValueQueriesTest.java @@ -21,7 +21,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.PriorityQueue; import org.apache.pinot.common.response.broker.BrokerResponseNative; import org.apache.pinot.common.utils.DataSchema; import org.apache.pinot.common.utils.DataSchema.ColumnDataType; @@ -108,7 +107,7 @@ private void verifySelectionOrderByAgoFunctionResult(SelectionResultsBlock resul assertTrue(columnIndexMap.containsKey("daysSinceEpoch")); assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("daysSinceEpoch")), ColumnDataType.INT); - PriorityQueue selectionResult = resultsBlock.convertToPriorityQueueBased().getRowsAsPriorityQueue(); + List selectionResult = resultsBlock.getRows(); assertEquals(selectionResult.size(), 10); for (Object[] row : selectionResult) { assertEquals(row.length, 1); @@ -232,9 +231,9 @@ public void testSelectionOrderBy() { assertTrue(columnIndexMap.containsKey("column1")); assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column6")), ColumnDataType.INT); assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column1")), ColumnDataType.INT); - PriorityQueue selectionResult = resultsBlock.getRowsAsPriorityQueue(); + List selectionResult = resultsBlock.getRows(); assertEquals(selectionResult.size(), 10); - Object[] lastRow = selectionResult.peek(); + Object[] lastRow = selectionResult.get(9); assertEquals(lastRow.length, 4); assertEquals(((Integer) lastRow[columnIndexMap.get("column6")]).intValue(), 6043515); assertEquals(((Integer) lastRow[columnIndexMap.get("column1")]).intValue(), 10542595); @@ -256,9 +255,9 @@ public void testSelectionOrderBy() { assertTrue(columnIndexMap.containsKey("column1")); assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column6")), ColumnDataType.INT); assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column1")), ColumnDataType.INT); - selectionResult = resultsBlock.getRowsAsPriorityQueue(); + selectionResult = resultsBlock.getRows(); assertEquals(selectionResult.size(), 10); - lastRow = selectionResult.peek(); + lastRow = selectionResult.get(9); assertEquals(lastRow.length, 4); assertEquals(((Integer) lastRow[columnIndexMap.get("column6")]).intValue(), 6043515); assertEquals(((Integer) lastRow[columnIndexMap.get("column1")]).intValue(), 462769197); @@ -281,9 +280,9 @@ public void testSelectionOrderBySortedColumn() { assertEquals(dataSchema.getColumnNames(), new String[]{"column5", "column1", "column11"}); assertEquals(dataSchema.getColumnDataTypes(), new ColumnDataType[]{ColumnDataType.STRING, ColumnDataType.INT, ColumnDataType.STRING}); - PriorityQueue selectionResult = resultsBlock.convertToPriorityQueueBased().getRowsAsPriorityQueue(); + List selectionResult = resultsBlock.getRows(); assertEquals(selectionResult.size(), 10); - Object[] lastRow = selectionResult.peek(); + Object[] lastRow = selectionResult.get(9); assertEquals(lastRow.length, 3); assertEquals(lastRow[0], "gFuH"); @@ -302,9 +301,9 @@ public void testSelectionOrderBySortedColumn() { assertEquals(dataSchema.getColumnNames(), new String[]{"column5", "column1", "column11"}); assertEquals(dataSchema.getColumnDataTypes(), new ColumnDataType[]{ColumnDataType.STRING, ColumnDataType.INT, ColumnDataType.STRING}); - selectionResult = resultsBlock.convertToPriorityQueueBased().getRowsAsPriorityQueue(); + selectionResult = resultsBlock.getRows(); assertEquals(selectionResult.size(), 10); - lastRow = selectionResult.peek(); + lastRow = selectionResult.get(9); assertEquals(lastRow.length, 3); assertEquals(lastRow[0], "gFuH"); @@ -322,9 +321,9 @@ public void testSelectionOrderBySortedColumn() { dataSchema = resultsBlock.getDataSchema(); assertEquals(dataSchema.getColumnNames(), new String[]{"column5", "daysSinceEpoch"}); assertEquals(dataSchema.getColumnDataTypes(), new ColumnDataType[]{ColumnDataType.STRING, ColumnDataType.INT}); - selectionResult = resultsBlock.convertToPriorityQueueBased().getRowsAsPriorityQueue(); + selectionResult = resultsBlock.getRows(); assertEquals(selectionResult.size(), 10); - lastRow = selectionResult.peek(); + lastRow = selectionResult.get(9); assertEquals(lastRow.length, 2); assertEquals(lastRow[0], "gFuH"); assertEquals(lastRow[1], 126164076); @@ -343,9 +342,9 @@ public void testSelectionOrderBySortedColumn() { dataSchema = resultsBlock.getDataSchema(); assertEquals(dataSchema.getColumnNames(), new String[]{"column5", "daysSinceEpoch"}); assertEquals(dataSchema.getColumnDataTypes(), new ColumnDataType[]{ColumnDataType.STRING, ColumnDataType.INT}); - selectionResult = resultsBlock.convertToPriorityQueueBased().getRowsAsPriorityQueue(); + selectionResult = resultsBlock.getRows(); assertEquals(selectionResult.size(), 10); - lastRow = selectionResult.peek(); + lastRow = selectionResult.get(9); assertEquals(lastRow.length, 2); assertEquals(lastRow[0], "gFuH"); assertEquals(lastRow[1], 167572854); @@ -364,9 +363,9 @@ public void testSelectionOrderBySortedColumn() { dataSchema = resultsBlock.getDataSchema(); assertEquals(dataSchema.getColumnNames(), new String[]{"column5", "daysSinceEpoch"}); assertEquals(dataSchema.getColumnDataTypes(), new ColumnDataType[]{ColumnDataType.STRING, ColumnDataType.INT}); - selectionResult = resultsBlock.convertToPriorityQueueBased().getRowsAsPriorityQueue(); + selectionResult = resultsBlock.getRows(); assertEquals(selectionResult.size(), 10); - lastRow = selectionResult.peek(); + lastRow = selectionResult.get(9); assertEquals(lastRow.length, 2); assertEquals(lastRow[0], "gFuH"); assertEquals(lastRow[1], 167572854); @@ -386,9 +385,9 @@ public void testSelectionOrderBySortedColumn() { assertEquals(dataSchema.getColumnNames(), new String[]{"column5", "column6", "column1"}); assertEquals(dataSchema.getColumnDataTypes(), new ColumnDataType[]{ColumnDataType.STRING, ColumnDataType.INT, ColumnDataType.INT}); - selectionResult = resultsBlock.convertToPriorityQueueBased().getRowsAsPriorityQueue(); + selectionResult = resultsBlock.getRows(); assertEquals(selectionResult.size(), 10); - lastRow = selectionResult.peek(); + lastRow = selectionResult.get(9); assertEquals(lastRow.length, 3); assertEquals(lastRow[0], "gFuH"); // Unsorted column values should be the same as ordering by their own @@ -410,9 +409,9 @@ public void testSelectionOrderBySortedColumn() { assertEquals(dataSchema.getColumnNames(), new String[]{"column5", "column6", "column1"}); assertEquals(dataSchema.getColumnDataTypes(), new ColumnDataType[]{ColumnDataType.STRING, ColumnDataType.INT, ColumnDataType.INT}); - selectionResult = resultsBlock.convertToPriorityQueueBased().getRowsAsPriorityQueue(); + selectionResult = resultsBlock.getRows(); assertEquals(selectionResult.size(), 10); - lastRow = selectionResult.peek(); + lastRow = selectionResult.get(9); assertEquals(lastRow.length, 3); assertEquals(lastRow[0], "gFuH"); // Unsorted column values should be the same as ordering by their own @@ -440,9 +439,9 @@ public void testSelectStarOrderBy() { assertTrue(columnIndexMap.containsKey("column1")); assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column6")), ColumnDataType.INT); assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column1")), ColumnDataType.INT); - PriorityQueue selectionResult = resultsBlock.getRowsAsPriorityQueue(); + List selectionResult = resultsBlock.getRows(); assertEquals(selectionResult.size(), 10); - Object[] lastRow = selectionResult.peek(); + Object[] lastRow = selectionResult.get(9); assertEquals(lastRow.length, 11); assertEquals(((Integer) lastRow[columnIndexMap.get("column6")]).intValue(), 6043515); assertEquals(((Integer) lastRow[columnIndexMap.get("column1")]).intValue(), 10542595); @@ -465,9 +464,9 @@ public void testSelectStarOrderBy() { assertTrue(columnIndexMap.containsKey("column1")); assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column6")), ColumnDataType.INT); assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column1")), ColumnDataType.INT); - selectionResult = resultsBlock.getRowsAsPriorityQueue(); + selectionResult = resultsBlock.getRows(); assertEquals(selectionResult.size(), 10); - lastRow = selectionResult.peek(); + lastRow = selectionResult.get(9); assertEquals(lastRow.length, 11); assertEquals(((Integer) lastRow[columnIndexMap.get("column6")]).intValue(), 6043515); assertEquals(((Integer) lastRow[columnIndexMap.get("column1")]).intValue(), 462769197); @@ -493,9 +492,9 @@ public void testSelectStarOrderBySortedColumn() { assertEquals(selectionDataSchema.size(), 11); assertTrue(columnIndexMap.containsKey("column5")); assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column5")), ColumnDataType.STRING); - PriorityQueue selectionResult = resultsBlock.convertToPriorityQueueBased().getRowsAsPriorityQueue(); + List selectionResult = resultsBlock.getRows(); assertEquals(selectionResult.size(), 10); - Object[] lastRow = selectionResult.peek(); + Object[] lastRow = selectionResult.get(9); assertEquals(lastRow.length, 11); assertEquals((lastRow[columnIndexMap.get("column5")]), "gFuH"); @@ -515,9 +514,9 @@ public void testSelectStarOrderBySortedColumn() { assertEquals(selectionDataSchema.size(), 11); assertTrue(columnIndexMap.containsKey("column5")); assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column5")), ColumnDataType.STRING); - selectionResult = resultsBlock.convertToPriorityQueueBased().getRowsAsPriorityQueue(); + selectionResult = resultsBlock.getRows(); assertEquals(selectionResult.size(), 10); - lastRow = selectionResult.peek(); + lastRow = selectionResult.get(9); assertEquals(lastRow.length, 11); assertEquals((lastRow[columnIndexMap.get("column5")]), "gFuH"); } @@ -544,9 +543,9 @@ public void testSelectStarOrderByLargeOffsetLimit() { assertTrue(columnIndexMap.containsKey("column1")); assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column6")), ColumnDataType.INT); assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column1")), ColumnDataType.INT); - PriorityQueue selectionResult = resultsBlock.getRowsAsPriorityQueue(); + List selectionResult = resultsBlock.getRows(); assertEquals(selectionResult.size(), 12000); - Object[] lastRow = selectionResult.peek(); + Object[] lastRow = selectionResult.get(11999); assertEquals(lastRow.length, 11); assertEquals((int) lastRow[columnIndexMap.get("column6")], 296467636); assertEquals((int) lastRow[columnIndexMap.get("column1")], 1715964282); @@ -569,9 +568,9 @@ public void testSelectStarOrderByLargeOffsetLimit() { assertTrue(columnIndexMap.containsKey("column1")); assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column6")), ColumnDataType.INT); assertEquals(selectionDataSchema.getColumnDataType(columnIndexMap.get("column1")), ColumnDataType.INT); - selectionResult = resultsBlock.getRowsAsPriorityQueue(); + selectionResult = resultsBlock.getRows(); assertEquals(selectionResult.size(), 6129); - lastRow = selectionResult.peek(); + lastRow = selectionResult.get(6128); assertEquals(lastRow.length, 11); assertEquals((int) lastRow[columnIndexMap.get("column6")], 499968041); assertEquals((int) lastRow[columnIndexMap.get("column1")], 335520083); diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/InterSegmentAggregationMultiValueQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/InterSegmentAggregationMultiValueQueriesTest.java index 668aecd0ba42..f974aa854237 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/InterSegmentAggregationMultiValueQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/InterSegmentAggregationMultiValueQueriesTest.java @@ -121,7 +121,8 @@ public void testCountMV() { public void testMaxMV() { String query = "SELECT MAXMV(column6) AS value FROM testTable"; - // Without filter, query should be answered by DictionaryBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // Without filter, query should be answered by NonScanBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // for dictionary based columns. BrokerResponseNative brokerResponse = getBrokerResponse(query); DataSchema expectedDataSchema = new DataSchema(new String[]{"value"}, new ColumnDataType[]{ColumnDataType.DOUBLE}); ResultTable expectedResultTable = @@ -148,7 +149,8 @@ public void testMaxMV() { public void testMinMV() { String query = "SELECT MINMV(column6) AS value FROM testTable"; - // Without filter, query should be answered by DictionaryBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // Without filter, query should be answered by NonScanBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // for dictionary based columns. BrokerResponseNative brokerResponse = getBrokerResponse(query); DataSchema expectedDataSchema = new DataSchema(new String[]{"value"}, new ColumnDataType[]{ColumnDataType.DOUBLE}); Object[] expectedResults = new Object[]{1001.0}; @@ -245,7 +247,8 @@ public void testAvgMV() { public void testMinMaxRangeMV() { String query = "SELECT MINMAXRANGEMV(column6) AS value FROM testTable"; - // Without filter, query should be answered by DictionaryBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // Without filter, query should be answered by NonScanBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // for dictionary based columns. BrokerResponseNative brokerResponse = getBrokerResponse(query); DataSchema expectedDataSchema = new DataSchema(new String[]{"value"}, new ColumnDataType[]{ColumnDataType.DOUBLE}); Object[] expectedResults = new Object[]{2147482646.0}; @@ -277,7 +280,8 @@ public void testMinMaxRangeMV() { public void testDistinctCountMV() { String query = "SELECT DISTINCTCOUNTMV(column6) AS value FROM testTable"; - // Without filter, query should be answered by DictionaryBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // Without filter, query should be answered by NonScanBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // for dictionary based columns. BrokerResponseNative brokerResponse = getBrokerResponse(query); DataSchema expectedDataSchema = new DataSchema(new String[]{"value"}, new ColumnDataType[]{ColumnDataType.INT}); Object[] expectedResults = new Object[]{18499}; @@ -305,11 +309,78 @@ public void testDistinctCountMV() { QueriesTestUtils.testInterSegmentsResult(brokerResponse, 62480L, 455552L, 124960L, 400000L, expectedResultTable); } + @Test + public void testDistinctSumMV() { + String query = "SELECT DISTINCTSUMMV(column6) AS value FROM testTable"; + + // Without filter, query should be answered by NonScanBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // for dictionary based columns. + BrokerResponseNative brokerResponse = getBrokerResponse(query); + DataSchema expectedDataSchema = new DataSchema(new String[]{"value"}, new ColumnDataType[]{ColumnDataType.DOUBLE}); + Object[] expectedResults = new Object[]{24592775810.0}; + ResultTable expectedResultTable = new ResultTable(expectedDataSchema, Collections.singletonList(expectedResults)); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 400000L, 0L, 0L, 400000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + FILTER); + expectedResults[0] = 2578123532.0; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 62480L, 455552L, 62480L, 400000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + SV_GROUP_BY); + expectedResults[0] = 6304833321.0; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 400000L, 0L, 800000L, 400000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + FILTER + SV_GROUP_BY); + expectedResults[0] = 2578123532.0; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 62480L, 455552L, 124960L, 400000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + MV_GROUP_BY); + expectedResults[0] = 8999975927.0; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 400000L, 0L, 800000L, 400000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + FILTER + MV_GROUP_BY); + expectedResults[0] = 2478539095.0; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 62480L, 455552L, 124960L, 400000L, expectedResultTable); + } + + @Test + public void testDistinctAvgMV() { + String query = "SELECT DISTINCTAVGMV(column6) AS value FROM testTable"; + + // Without filter, query should be answered by NonScanBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // for dictionary based columns. + BrokerResponseNative brokerResponse = getBrokerResponse(query); + DataSchema expectedDataSchema = new DataSchema(new String[]{"value"}, new ColumnDataType[]{ColumnDataType.DOUBLE}); + Object[] expectedResults = new Object[]{1329411.0930320558}; + ResultTable expectedResultTable = new ResultTable(expectedDataSchema, Collections.singletonList(expectedResults)); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 400000L, 0L, 0L, 400000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + FILTER); + expectedResults[0] = 2173797.244519393; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 62480L, 455552L, 62480L, 400000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + SV_GROUP_BY); + expectedResults[0] = 2147483647.0; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 400000L, 0L, 800000L, 400000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + FILTER + SV_GROUP_BY); + expectedResults[0] = 2147483647.0; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 62480L, 455552L, 124960L, 400000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + MV_GROUP_BY); + expectedResults[0] = 2147483647.0; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 400000L, 0L, 800000L, 400000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + FILTER + MV_GROUP_BY); + expectedResults[0] = 2147483647.0; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 62480L, 455552L, 124960L, 400000L, expectedResultTable); + } + @Test public void testDistinctCountHLLMV() { String query = "SELECT DISTINCTCOUNTHLLMV(column6) AS value FROM testTable"; - // Without filter, query should be answered by DictionaryBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // Without filter, query should be answered by NonScanBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // for dictionary based columns. BrokerResponseNative brokerResponse = getBrokerResponse(query); DataSchema expectedDataSchema = new DataSchema(new String[]{"value"}, new ColumnDataType[]{ColumnDataType.LONG}); Object[] expectedResults = new Object[]{20039L}; @@ -337,13 +408,47 @@ public void testDistinctCountHLLMV() { QueriesTestUtils.testInterSegmentsResult(brokerResponse, 62480L, 455552L, 124960L, 400000L, expectedResultTable); } + @Test + public void testDistinctCountHLLPlusMV() { + String query = "SELECT DISTINCTCOUNTHLLPLUSMV(column6) AS value FROM testTable"; + + // Without filter, query should be answered by NonScanBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // for dictionary based columns. + BrokerResponseNative brokerResponse = getBrokerResponse(query); + DataSchema expectedDataSchema = new DataSchema(new String[]{"value"}, new ColumnDataType[]{ColumnDataType.LONG}); + Object[] expectedResults = new Object[]{18651L}; + ResultTable expectedResultTable = new ResultTable(expectedDataSchema, Collections.singletonList(expectedResults)); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 400000L, 0L, 0L, 400000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + FILTER); + expectedResults[0] = 1176L; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 62480L, 455552L, 62480L, 400000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + SV_GROUP_BY); + expectedResults[0] = 4796L; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 400000L, 0L, 800000L, 400000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + FILTER + SV_GROUP_BY); + expectedResults[0] = 1176L; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 62480L, 455552L, 124960L, 400000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + MV_GROUP_BY); + expectedResults[0] = 3457L; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 400000L, 0L, 800000L, 400000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + FILTER + MV_GROUP_BY); + expectedResults[0] = 579L; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 62480L, 455552L, 124960L, 400000L, expectedResultTable); + } + @Test public void testDistinctCountRawHLLMV() { String query = "SELECT DISTINCTCOUNTRAWHLLMV(column6) AS value FROM testTable"; Function cardinalityExtractor = value -> ObjectSerDeUtils.HYPER_LOG_LOG_SER_DE.deserialize(BytesUtils.toBytes((String) value)).cardinality(); - // Without filter, query should be answered by DictionaryBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // Without filter, query should be answered by NonScanBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // for dictionary based columns. BrokerResponseNative brokerResponse = getBrokerResponse(query); DataSchema expectedDataSchema = new DataSchema(new String[]{"value"}, new ColumnDataType[]{ColumnDataType.LONG}); Object[] expectedResults = new Object[]{20039L}; @@ -377,6 +482,48 @@ public void testDistinctCountRawHLLMV() { cardinalityExtractor); } + @Test + public void testDistinctCountRawHLLPLUSMV() { + String query = "SELECT DISTINCTCOUNTRAWHLLPLUSMV(column6) AS value FROM testTable"; + Function cardinalityExtractor = + value -> ObjectSerDeUtils.HYPER_LOG_LOG_PLUS_SER_DE.deserialize(BytesUtils.toBytes((String) value)) + .cardinality(); + + // Without filter, query should be answered by NonScanBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // for dictionary based columns. + BrokerResponseNative brokerResponse = getBrokerResponse(query); + DataSchema expectedDataSchema = new DataSchema(new String[]{"value"}, new ColumnDataType[]{ColumnDataType.LONG}); + Object[] expectedResults = new Object[]{18651L}; + ResultTable expectedResultTable = new ResultTable(expectedDataSchema, Collections.singletonList(expectedResults)); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 400000L, 0L, 0L, 400000L, expectedResultTable, + cardinalityExtractor); + + brokerResponse = getBrokerResponse(query + FILTER); + expectedResults[0] = 1176L; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 62480L, 455552L, 62480L, 400000L, expectedResultTable, + cardinalityExtractor); + + brokerResponse = getBrokerResponse(query + SV_GROUP_BY); + expectedResults[0] = 4796L; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 400000L, 0L, 800000L, 400000L, expectedResultTable, + cardinalityExtractor); + + brokerResponse = getBrokerResponse(query + FILTER + SV_GROUP_BY); + expectedResults[0] = 1176L; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 62480L, 455552L, 124960L, 400000L, expectedResultTable, + cardinalityExtractor); + + brokerResponse = getBrokerResponse(query + MV_GROUP_BY); + expectedResults[0] = 3457L; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 400000L, 0L, 800000L, 400000L, expectedResultTable, + cardinalityExtractor); + + brokerResponse = getBrokerResponse(query + FILTER + MV_GROUP_BY); + expectedResults[0] = 579L; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 62480L, 455552L, 124960L, 400000L, expectedResultTable, + cardinalityExtractor); + } + @Test public void testPercentileMV() { List queries = Arrays.asList("SELECT PERCENTILE50MV(column6) AS value FROM testTable", @@ -478,6 +625,11 @@ public void testPercentileRawTDigestMV() { testPercentileRawTDigestMV(90); testPercentileRawTDigestMV(95); testPercentileRawTDigestMV(99); + + testPercentileRawTDigestCustomCompression(50, 150); + testPercentileRawTDigestCustomCompression(90, 500); + testPercentileRawTDigestCustomCompression(95, 200); + testPercentileRawTDigestCustomCompression(99, 1000); } private void testPercentileRawTDigestMV(int percentile) { @@ -501,6 +653,25 @@ private void testPercentileRawTDigestMV(int percentile) { getBrokerResponse(regularQuery + FILTER + MV_GROUP_BY), quantileExtractor, PERCENTILE_TDIGEST_DELTA); } + private void testPercentileRawTDigestCustomCompression(int percentile, int compressionFactor) { + Function quantileExtractor = + value -> ObjectSerDeUtils.TDIGEST_SER_DE.deserialize(BytesUtils.toBytes((String) value)) + .quantile(percentile / 100.0); + + String rawQuery = String.format("SELECT PERCENTILERAWTDIGESTMV(column6, %d, %d) AS value FROM testTable", + percentile, compressionFactor); + String regularQuery = String.format("SELECT PERCENTILETDIGESTMV(column6, %d, %d) AS value FROM testTable", + percentile, compressionFactor); + QueriesTestUtils.testInterSegmentsResult(getBrokerResponse(rawQuery), getBrokerResponse(regularQuery), + quantileExtractor, PERCENTILE_TDIGEST_DELTA); + QueriesTestUtils.testInterSegmentsResult(getBrokerResponse(rawQuery + FILTER), + getBrokerResponse(regularQuery + FILTER), quantileExtractor, PERCENTILE_TDIGEST_DELTA); + QueriesTestUtils.testInterSegmentsResult(getBrokerResponse(rawQuery + MV_GROUP_BY), + getBrokerResponse(regularQuery + MV_GROUP_BY), quantileExtractor, PERCENTILE_TDIGEST_DELTA); + QueriesTestUtils.testInterSegmentsResult(getBrokerResponse(rawQuery + FILTER + MV_GROUP_BY), + getBrokerResponse(regularQuery + FILTER + MV_GROUP_BY), quantileExtractor, PERCENTILE_TDIGEST_DELTA); + } + @Test public void testNumGroupsLimit() { String query = "SELECT COUNT(*) FROM testTable GROUP BY column6"; @@ -519,10 +690,11 @@ public void testNumGroupsLimit() { public void testFilteredAggregations() { String query = "SELECT COUNT(*) FILTER(WHERE column1 > 5) FROM testTable WHERE column3 > 0"; BrokerResponseNative brokerResponse = getBrokerResponse(query); - DataSchema expectedDataSchema = new DataSchema(new String[]{"count(*)"}, new ColumnDataType[]{ColumnDataType.LONG}); + DataSchema expectedDataSchema = new DataSchema(new String[]{"count(*) FILTER(WHERE column1 > '5')"}, + new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.LONG}); ResultTable expectedResultTable = new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{370236L})); - QueriesTestUtils.testInterSegmentsResult(brokerResponse, 740472L, 400000L, 0L, 400000L, expectedResultTable); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 370236L, 400000L, 0L, 400000L, expectedResultTable); } @Test diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/InterSegmentAggregationMultiValueRawQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/InterSegmentAggregationMultiValueRawQueriesTest.java index 7b4325df6da7..591b5ffffa7e 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/InterSegmentAggregationMultiValueRawQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/InterSegmentAggregationMultiValueRawQueriesTest.java @@ -18,10 +18,13 @@ */ package org.apache.pinot.queries; +import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.function.Function; +import org.apache.calcite.avatica.util.Base64; +import org.apache.datasketches.kll.KllDoublesSketch; import org.apache.pinot.common.response.broker.BrokerResponseNative; import org.apache.pinot.common.response.broker.ResultTable; import org.apache.pinot.common.utils.DataSchema; @@ -41,6 +44,8 @@ public class InterSegmentAggregationMultiValueRawQueriesTest extends BaseMultiVa // Allow 5% quantile error due to the randomness of TDigest merge private static final double PERCENTILE_TDIGEST_DELTA = 0.05 * Integer.MAX_VALUE; + // Allow 2% quantile error due to the randomness of KLL merge + private static final double PERCENTILE_KLL_DELTA = 0.02 * Integer.MAX_VALUE; @Test public void testCountMV() { @@ -345,6 +350,39 @@ public void testDistinctCountHLLMV() { QueriesTestUtils.testInterSegmentsResult(brokerResponse, 62480L, 519028L, 124960L, 400000L, expectedResultTable); } + @Test + public void testDistinctCountHLLPLUSMV() { + String query = "SELECT DISTINCTCOUNTHLLPLUSMV(column6) AS value FROM testTable"; + + // Without filter, query should be answered by DictionaryBasedAggregationOperator (numEntriesScannedPostFilter = 0) + BrokerResponseNative brokerResponse = getBrokerResponse(query); + DataSchema expectedDataSchema = new DataSchema(new String[]{"value"}, new DataSchema.ColumnDataType[] + {DataSchema.ColumnDataType.LONG}); + Object[] expectedResults = new Object[]{18651L}; + ResultTable expectedResultTable = new ResultTable(expectedDataSchema, Collections.singletonList(expectedResults)); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 400000L, 0L, 400000L, 400000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + FILTER); + expectedResults[0] = 1176L; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 62480L, 519028L, 62480L, 400000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + SV_GROUP_BY); + expectedResults[0] = 4796L; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 400000L, 0L, 800000L, 400000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + FILTER + SV_GROUP_BY); + expectedResults[0] = 1176L; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 62480L, 519028L, 124960L, 400000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + MV_GROUP_BY); + expectedResults[0] = 3457L; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 400000L, 0L, 800000L, 400000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + FILTER + MV_GROUP_BY); + expectedResults[0] = 579L; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 62480L, 519028L, 124960L, 400000L, expectedResultTable); + } + @Test public void testDistinctCountRawHLLMV() { String query = "SELECT DISTINCTCOUNTRAWHLLMV(column6) AS value FROM testTable"; @@ -386,6 +424,48 @@ public void testDistinctCountRawHLLMV() { cardinalityExtractor); } + @Test + public void testDistinctCountRawHLLPLUSMV() { + String query = "SELECT DISTINCTCOUNTRAWHLLPLUSMV(column6) AS value FROM testTable"; + Function cardinalityExtractor = + value -> ObjectSerDeUtils.HYPER_LOG_LOG_PLUS_SER_DE.deserialize(BytesUtils.toBytes((String) value)) + .cardinality(); + + // Without filter, query should be answered by DictionaryBasedAggregationOperator (numEntriesScannedPostFilter = 0) + BrokerResponseNative brokerResponse = getBrokerResponse(query); + DataSchema expectedDataSchema = new DataSchema(new String[]{"value"}, new DataSchema.ColumnDataType[] + {DataSchema.ColumnDataType.LONG}); + Object[] expectedResults = new Object[]{18651L}; + ResultTable expectedResultTable = new ResultTable(expectedDataSchema, Collections.singletonList(expectedResults)); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 400000L, 0L, 400000L, 400000L, expectedResultTable, + cardinalityExtractor); + + brokerResponse = getBrokerResponse(query + FILTER); + expectedResults[0] = 1176L; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 62480L, 519028L, 62480L, 400000L, expectedResultTable, + cardinalityExtractor); + + brokerResponse = getBrokerResponse(query + SV_GROUP_BY); + expectedResults[0] = 4796L; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 400000L, 0L, 800000L, 400000L, expectedResultTable, + cardinalityExtractor); + + brokerResponse = getBrokerResponse(query + FILTER + SV_GROUP_BY); + expectedResults[0] = 1176L; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 62480L, 519028L, 124960L, 400000L, expectedResultTable, + cardinalityExtractor); + + brokerResponse = getBrokerResponse(query + MV_GROUP_BY); + expectedResults[0] = 3457L; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 400000L, 0L, 800000L, 400000L, expectedResultTable, + cardinalityExtractor); + + brokerResponse = getBrokerResponse(query + FILTER + MV_GROUP_BY); + expectedResults[0] = 579L; + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 62480L, 519028L, 124960L, 400000L, expectedResultTable, + cardinalityExtractor); + } + @Test public void testPercentileMV() { List queries = Arrays.asList("SELECT PERCENTILE50MV(column6) AS value FROM testTable", @@ -489,6 +569,11 @@ public void testPercentileRawTDigestMV() { testPercentileRawTDigestMV(90); testPercentileRawTDigestMV(95); testPercentileRawTDigestMV(99); + + testPercentileRawTDigestCustomCompressionMV(50, 150); + testPercentileRawTDigestCustomCompressionMV(90, 500); + testPercentileRawTDigestCustomCompressionMV(95, 200); + testPercentileRawTDigestCustomCompressionMV(99, 1000); } private void testPercentileRawTDigestMV(int percentile) { @@ -512,6 +597,63 @@ private void testPercentileRawTDigestMV(int percentile) { getBrokerResponse(regularQuery + FILTER + MV_GROUP_BY), quantileExtractor, PERCENTILE_TDIGEST_DELTA); } + private void testPercentileRawTDigestCustomCompressionMV(int percentile, int compressionFactor) { + Function quantileExtractor = + value -> ObjectSerDeUtils.TDIGEST_SER_DE.deserialize(BytesUtils.toBytes((String) value)) + .quantile(percentile / 100.0); + + String rawQuery = String.format("SELECT PERCENTILERAWTDIGESTMV(column6, %d, %d) AS value FROM testTable", + percentile, compressionFactor); + String regularQuery = String.format("SELECT PERCENTILETDIGESTMV(column6, %d, %d) AS value FROM testTable", + percentile, compressionFactor); + QueriesTestUtils.testInterSegmentsResult(getBrokerResponse(rawQuery), getBrokerResponse(regularQuery), + quantileExtractor, PERCENTILE_TDIGEST_DELTA); + QueriesTestUtils.testInterSegmentsResult(getBrokerResponse(rawQuery + FILTER), + getBrokerResponse(regularQuery + FILTER), quantileExtractor, PERCENTILE_TDIGEST_DELTA); + QueriesTestUtils.testInterSegmentsResult(getBrokerResponse(rawQuery + SV_GROUP_BY), + getBrokerResponse(regularQuery + SV_GROUP_BY), quantileExtractor, PERCENTILE_TDIGEST_DELTA); + QueriesTestUtils.testInterSegmentsResult(getBrokerResponse(rawQuery + FILTER + SV_GROUP_BY), + getBrokerResponse(regularQuery + FILTER + SV_GROUP_BY), quantileExtractor, PERCENTILE_TDIGEST_DELTA); + QueriesTestUtils.testInterSegmentsResult(getBrokerResponse(rawQuery + MV_GROUP_BY), + getBrokerResponse(regularQuery + MV_GROUP_BY), quantileExtractor, PERCENTILE_TDIGEST_DELTA); + QueriesTestUtils.testInterSegmentsResult(getBrokerResponse(rawQuery + FILTER + MV_GROUP_BY), + getBrokerResponse(regularQuery + FILTER + MV_GROUP_BY), quantileExtractor, PERCENTILE_TDIGEST_DELTA); + } + + @Test + public void testPercentileRawKLLMV() { + testPercentileRawKLLMV(50); + testPercentileRawKLLMV(90); + testPercentileRawKLLMV(95); + testPercentileRawKLLMV(99); + } + + private void testPercentileRawKLLMV(int percentile) { + Function quantileExtractor = value -> { + try { + KllDoublesSketch sketch = ObjectSerDeUtils.KLL_SKETCH_SER_DE.deserialize(Base64.decode((String) value)); + return sketch.getQuantile(percentile / 100.0); + } catch (IOException e) { + return null; + } + }; + + String rawKllQuery = String.format("SELECT PERCENTILERAWKLLMV(column6, %d) AS value FROM testTable", percentile); + String regularQuery = String.format("SELECT PERCENTILEMV(column6, %d) AS value FROM testTable", percentile); + QueriesTestUtils.testInterSegmentsResult(getBrokerResponse(rawKllQuery), getBrokerResponse(regularQuery), + quantileExtractor, PERCENTILE_KLL_DELTA); + QueriesTestUtils.testInterSegmentsResult(getBrokerResponse(rawKllQuery + FILTER), + getBrokerResponse(regularQuery + FILTER), quantileExtractor, PERCENTILE_KLL_DELTA); + QueriesTestUtils.testInterSegmentsResult(getBrokerResponse(rawKllQuery + SV_GROUP_BY), + getBrokerResponse(regularQuery + SV_GROUP_BY), quantileExtractor, PERCENTILE_KLL_DELTA); + QueriesTestUtils.testInterSegmentsResult(getBrokerResponse(rawKllQuery + FILTER + SV_GROUP_BY), + getBrokerResponse(regularQuery + FILTER + SV_GROUP_BY), quantileExtractor, PERCENTILE_KLL_DELTA); + QueriesTestUtils.testInterSegmentsResult(getBrokerResponse(rawKllQuery + MV_GROUP_BY), + getBrokerResponse(regularQuery + MV_GROUP_BY), quantileExtractor, PERCENTILE_KLL_DELTA); + QueriesTestUtils.testInterSegmentsResult(getBrokerResponse(rawKllQuery + FILTER + MV_GROUP_BY), + getBrokerResponse(regularQuery + FILTER + MV_GROUP_BY), quantileExtractor, PERCENTILE_KLL_DELTA); + } + @Test public void testNumGroupsLimit() { String query = "SELECT COUNT(*) FROM testTable GROUP BY column6"; @@ -530,11 +672,11 @@ public void testNumGroupsLimit() { public void testFilteredAggregations() { String query = "SELECT COUNT(*) FILTER(WHERE column1 > 5) FROM testTable WHERE column3 > 0"; BrokerResponseNative brokerResponse = getBrokerResponse(query); - DataSchema expectedDataSchema = new DataSchema(new String[]{"count(*)"}, new DataSchema.ColumnDataType[] - {DataSchema.ColumnDataType.LONG}); + DataSchema expectedDataSchema = new DataSchema(new String[]{"count(*) FILTER(WHERE column1 > '5')"}, + new DataSchema.ColumnDataType[]{DataSchema.ColumnDataType.LONG}); ResultTable expectedResultTable = new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{370236L})); - QueriesTestUtils.testInterSegmentsResult(brokerResponse, 740472L, 400000L, 0L, 400000L, expectedResultTable); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 370236L, 400000L, 0L, 400000L, expectedResultTable); } @Test diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/InterSegmentAggregationSingleValueQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/InterSegmentAggregationSingleValueQueriesTest.java index bd8752bd16dc..a2c74071c98d 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/InterSegmentAggregationSingleValueQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/InterSegmentAggregationSingleValueQueriesTest.java @@ -40,6 +40,8 @@ public class InterSegmentAggregationSingleValueQueriesTest extends BaseSingleVal // Allow 5% quantile error due to the randomness of TDigest merge private static final double PERCENTILE_TDIGEST_DELTA = 0.05 * Integer.MAX_VALUE; + // Allow 2% quantile error due to the randomness of KLL merge + private static final double PERCENTILE_KLL_DELTA = 0.02 * Integer.MAX_VALUE; @Test public void testCount() { @@ -90,7 +92,8 @@ public void testCount() { public void testMax() { String query = "SELECT MAX(column1) AS v1, MAX(column3) AS v2 FROM testTable"; - // Without filter, query should be answered by DictionaryBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // Without filter, query should be answered by NonScanBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // for dictionary based columns. BrokerResponseNative brokerResponse = getBrokerResponse(query); DataSchema expectedDataSchema = new DataSchema(new String[]{"v1", "v2"}, new ColumnDataType[]{ColumnDataType.DOUBLE, ColumnDataType.DOUBLE}); @@ -118,7 +121,8 @@ public void testMax() { public void testMin() { String query = "SELECT MIN(column1) AS v1, MIN(column3) AS v2 FROM testTable"; - // Without filter, query should be answered by DictionaryBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // Without filter, query should be answered by NonScanBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // for dictionary based columns. BrokerResponseNative brokerResponse = getBrokerResponse(query); DataSchema expectedDataSchema = new DataSchema(new String[]{"v1", "v2"}, new ColumnDataType[]{ColumnDataType.DOUBLE, ColumnDataType.DOUBLE}); @@ -202,7 +206,8 @@ public void testAvg() { public void testMinMaxRange() { String query = "SELECT MINMAXRANGE(column1) AS v1, MINMAXRANGE(column3) AS v2 FROM testTable"; - // Without filter, query should be answered by DictionaryBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // Without filter, query should be answered by NonScanBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // for dictionary based columns. BrokerResponseNative brokerResponse = getBrokerResponse(query); DataSchema expectedDataSchema = new DataSchema(new String[]{"v1", "v2"}, new ColumnDataType[]{ColumnDataType.DOUBLE, ColumnDataType.DOUBLE}); @@ -230,7 +235,8 @@ public void testMinMaxRange() { public void testDistinctCount() { String query = "SELECT DISTINCTCOUNT(column1) AS v1, DISTINCTCOUNT(column3) AS v2 FROM testTable"; - // Without filter, query should be answered by DictionaryBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // Without filter, query should be answered by NonScanBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // for dictionary based columns. BrokerResponseNative brokerResponse = getBrokerResponse(query); DataSchema expectedDataSchema = new DataSchema(new String[]{"v1", "v2"}, new ColumnDataType[]{ColumnDataType.INT, ColumnDataType.INT}); @@ -255,7 +261,8 @@ public void testDistinctCount() { public void testDistinctCountHLL() { String query = "SELECT DISTINCTCOUNTHLL(column1) AS v1, DISTINCTCOUNTHLL(column3) AS v2 FROM testTable"; - // Without filter, query should be answered by DictionaryBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // Without filter, query should be answered by NonScanBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // for dictionary based columns. BrokerResponseNative brokerResponse = getBrokerResponse(query); DataSchema expectedDataSchema = new DataSchema(new String[]{"v1", "v2"}, new ColumnDataType[]{ColumnDataType.LONG, ColumnDataType.LONG}); @@ -276,13 +283,40 @@ public void testDistinctCountHLL() { QueriesTestUtils.testInterSegmentsResult(brokerResponse, 24516L, 252256L, 73548L, 120000L, expectedResultTable); } + @Test + public void testDistinctCountHLLPlus() { + String query = "SELECT DISTINCTCOUNTHLLPLUS(column1) AS v1, DISTINCTCOUNTHLLPLUS(column3) AS v2 FROM testTable"; + + // Without filter, query should be answered by NonScanBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // for dictionary based columns. + BrokerResponseNative brokerResponse = getBrokerResponse(query); + DataSchema expectedDataSchema = + new DataSchema(new String[]{"v1", "v2"}, new ColumnDataType[]{ColumnDataType.LONG, ColumnDataType.LONG}); + ResultTable expectedResultTable = + new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{6595L, 21822L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 120000L, 0L, 0L, 120000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + FILTER); + expectedResultTable = new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{1885L, 4545L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 24516L, 252256L, 49032L, 120000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + GROUP_BY); + expectedResultTable = new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{3495L, 12031L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 120000L, 0L, 360000L, 120000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + FILTER + GROUP_BY); + expectedResultTable = new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{1273L, 3284L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 24516L, 252256L, 73548L, 120000L, expectedResultTable); + } + @Test public void testDistinctCountRawHLL() { String query = "SELECT DISTINCTCOUNTRAWHLL(column1) AS v1, DISTINCTCOUNTRAWHLL(column3) AS v2 FROM testTable"; Function cardinalityExtractor = value -> ObjectSerDeUtils.HYPER_LOG_LOG_SER_DE.deserialize(BytesUtils.toBytes((String) value)).cardinality(); - // Without filter, query should be answered by DictionaryBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // Without filter, query should be answered by NonScanBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // for dictionary based columns. BrokerResponseNative brokerResponse = getBrokerResponse(query); DataSchema expectedDataSchema = new DataSchema(new String[]{"v1", "v2"}, new ColumnDataType[]{ColumnDataType.LONG, ColumnDataType.LONG}); @@ -307,6 +341,40 @@ public void testDistinctCountRawHLL() { cardinalityExtractor); } + @Test + public void testDistinctCountRawHLLPlus() { + String query = + "SELECT DISTINCTCOUNTRAWHLLPLUS(column1) AS v1, DISTINCTCOUNTRAWHLLPLUS(column3) AS v2 FROM testTable"; + Function cardinalityExtractor = + value -> ObjectSerDeUtils.HYPER_LOG_LOG_PLUS_SER_DE.deserialize(BytesUtils.toBytes((String) value)) + .cardinality(); + + // Without filter, query should be answered by NonScanBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // for dictionary based columns. + BrokerResponseNative brokerResponse = getBrokerResponse(query); + DataSchema expectedDataSchema = + new DataSchema(new String[]{"v1", "v2"}, new ColumnDataType[]{ColumnDataType.LONG, ColumnDataType.LONG}); + ResultTable expectedResultTable = + new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{6595L, 21822L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 120000L, 0L, 0L, 120000L, expectedResultTable, + cardinalityExtractor); + + brokerResponse = getBrokerResponse(query + FILTER); + expectedResultTable = new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{1885L, 4545L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 24516L, 252256L, 49032L, 120000L, expectedResultTable, + cardinalityExtractor); + + brokerResponse = getBrokerResponse(query + GROUP_BY); + expectedResultTable = new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{3495L, 12031L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 120000L, 0L, 360000L, 120000L, expectedResultTable, + cardinalityExtractor); + + brokerResponse = getBrokerResponse(query + FILTER + GROUP_BY); + expectedResultTable = new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{1273L, 3284L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 24516L, 252256L, 73548L, 120000L, expectedResultTable, + cardinalityExtractor); + } + @Test public void testPercentile() { List queries = @@ -532,6 +600,11 @@ public void testPercentileRawTDigest() { testPercentileRawTDigest(90); testPercentileRawTDigest(95); testPercentileRawTDigest(99); + + testPercentileRawTDigestCustomCompression(50, 150); + testPercentileRawTDigestCustomCompression(90, 500); + testPercentileRawTDigestCustomCompression(95, 200); + testPercentileRawTDigestCustomCompression(99, 1000); } private void testPercentileRawTDigest(int percentile) { @@ -555,6 +628,138 @@ private void testPercentileRawTDigest(int percentile) { getBrokerResponse(regularQuery + FILTER + GROUP_BY), quantileExtractor, PERCENTILE_TDIGEST_DELTA); } + private void testPercentileRawTDigestCustomCompression(int percentile, int compressionFactor) { + Function quantileExtractor = + value -> ObjectSerDeUtils.TDIGEST_SER_DE.deserialize(BytesUtils.toBytes((String) value)) + .quantile(percentile / 100.0); + + String rawQuery = String.format( + "SELECT PERCENTILERAWTDIGEST(column1, %d, %d) AS v1, PERCENTILERAWTDIGEST(column3, %d, %d) AS v2 " + + "FROM testTable", + percentile, compressionFactor, percentile, compressionFactor); + String regularQuery = + String.format("SELECT PERCENTILETDIGEST(column1, %d, %d) AS v1, PERCENTILETDIGEST(column3, %d, %d) AS v2 " + + "FROM testTable", + percentile, compressionFactor, percentile, compressionFactor); + QueriesTestUtils.testInterSegmentsResult(getBrokerResponse(rawQuery), getBrokerResponse(regularQuery), + quantileExtractor, PERCENTILE_TDIGEST_DELTA); + QueriesTestUtils.testInterSegmentsResult(getBrokerResponse(rawQuery + FILTER), + getBrokerResponse(regularQuery + FILTER), quantileExtractor, PERCENTILE_TDIGEST_DELTA); + QueriesTestUtils.testInterSegmentsResult(getBrokerResponse(rawQuery + GROUP_BY), + getBrokerResponse(regularQuery + GROUP_BY), quantileExtractor, PERCENTILE_TDIGEST_DELTA); + QueriesTestUtils.testInterSegmentsResult(getBrokerResponse(rawQuery + FILTER + GROUP_BY), + getBrokerResponse(regularQuery + FILTER + GROUP_BY), quantileExtractor, PERCENTILE_TDIGEST_DELTA); + } + + @Test + public void testPercentileKLL() { + String query = "SELECT PERCENTILEKLL(column1, 50) AS v1, PERCENTILEKLL(column3, 50) AS v2 FROM testTable"; + + BrokerResponseNative brokerResponse = getBrokerResponse(query); + DataSchema expectedDataSchema = + new DataSchema(new String[]{"v1", "v2"}, new ColumnDataType[]{ColumnDataType.DOUBLE, ColumnDataType.DOUBLE}); + ResultTable expectedResultTable = + new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{1107310944L, 1082130431L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 120000L, 0L, + 240000L, 120000L, expectedResultTable, PERCENTILE_KLL_DELTA); + + brokerResponse = getBrokerResponse(query + FILTER); + expectedResultTable = + new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{1139674505L, 509607935L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 24516L, 252256L, + 49032L, 120000L, expectedResultTable, PERCENTILE_KLL_DELTA); + + brokerResponse = getBrokerResponse(query + GROUP_BY); + expectedResultTable = + new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{2146791843L, 1418523221L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 120000L, 0L, + 360000L, 120000L, expectedResultTable, PERCENTILE_KLL_DELTA); + + brokerResponse = getBrokerResponse(query + FILTER + GROUP_BY); + expectedResultTable = + new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{2142595699L, 334963174L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 24516L, 252256L, + 73548L, 120000L, expectedResultTable, PERCENTILE_KLL_DELTA); + + query = "SELECT PERCENTILEKLL(column1, 90) AS v1, PERCENTILEKLL(column3, 90) AS v2 FROM testTable"; + + brokerResponse = getBrokerResponse(query); + expectedResultTable = + new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{1946157055L, 1946157055L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 120000L, 0L, + 240000L, 120000L, expectedResultTable, PERCENTILE_KLL_DELTA); + + brokerResponse = getBrokerResponse(query + FILTER); + expectedResultTable = + new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{1939865599L, 902299647L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 24516L, 252256L, + 49032L, 120000L, expectedResultTable, PERCENTILE_KLL_DELTA); + + brokerResponse = getBrokerResponse(query + GROUP_BY); + expectedResultTable = + new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{2146791843L, 1418523221L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 120000L, 0L, + 360000L, 120000L, expectedResultTable, PERCENTILE_KLL_DELTA); + + brokerResponse = getBrokerResponse(query + FILTER + GROUP_BY); + expectedResultTable = + new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{2142595699L, 334963174L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 24516L, 252256L, + 73548L, 120000L, expectedResultTable, PERCENTILE_KLL_DELTA); + + query = "SELECT PERCENTILEKLL(column1, 95) AS v1, PERCENTILEKLL(column3, 95) AS v2 FROM testTable"; + + brokerResponse = getBrokerResponse(query); + expectedResultTable = + new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{2080374783L, 2051014655L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 120000L, 0L, + 240000L, 120000L, expectedResultTable, PERCENTILE_KLL_DELTA); + + brokerResponse = getBrokerResponse(query + FILTER); + expectedResultTable = + new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{2109734911L, 950009855L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 24516L, 252256L, + 49032L, 120000L, expectedResultTable, PERCENTILE_KLL_DELTA); + + brokerResponse = getBrokerResponse(query + GROUP_BY); + expectedResultTable = + new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{2146791843L, 1418523221L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 120000L, 0L, + 360000L, 120000L, expectedResultTable, PERCENTILE_KLL_DELTA); + + brokerResponse = getBrokerResponse(query + FILTER + GROUP_BY); + expectedResultTable = + new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{2142595699L, 334963174L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 24516L, 252256L, + 73548L, 120000L, expectedResultTable, PERCENTILE_KLL_DELTA); + + query = "SELECT PERCENTILEKLL(column1, 99) AS v1, PERCENTILEKLL(column3, 99) AS v2 FROM testTable"; + + brokerResponse = getBrokerResponse(query); + expectedResultTable = + new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{2143289343L, 2143289343L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 120000L, 0L, + 240000L, 120000L, expectedResultTable, PERCENTILE_KLL_DELTA); + + brokerResponse = getBrokerResponse(query + FILTER); + expectedResultTable = + new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{2146232405L, 991952895L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 24516L, 252256L, + 49032L, 120000L, expectedResultTable, PERCENTILE_KLL_DELTA); + + brokerResponse = getBrokerResponse(query + GROUP_BY); + expectedResultTable = + new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{2146791843L, 1418523221L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 120000L, 0L, + 360000L, 120000L, expectedResultTable, PERCENTILE_KLL_DELTA); + + brokerResponse = getBrokerResponse(query + FILTER + GROUP_BY); + expectedResultTable = + new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{2146232405L, 993001471L})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 24516L, 252256L, + 73548L, 120000L, expectedResultTable, PERCENTILE_KLL_DELTA); + } + @Test public void testNumGroupsLimit() { String query = "SELECT COUNT(*) FROM testTable GROUP BY column1"; @@ -568,4 +773,66 @@ public void testNumGroupsLimit() { InstancePlanMakerImplV2.DEFAULT_GROUPBY_TRIM_THRESHOLD)); assertTrue(brokerResponse.isNumGroupsLimitReached()); } + + @Test + public void testDistinctSum() { + String query = "select DISTINCTSUM(column1) as v1, DISTINCTSUM(column3) as v2 from testTable"; + + // Without filter, query should be answered by NonScanBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // for dictionary based columns. + BrokerResponseNative brokerResponse = getBrokerResponse(query); + DataSchema expectedDataSchema = + new DataSchema(new String[]{"v1", "v2"}, new ColumnDataType[]{ColumnDataType.DOUBLE, ColumnDataType.DOUBLE}); + ResultTable expectedResultTable = + new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{7074556592262.0, 23553878404013.0})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 120000L, 0L, 0L, 120000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + FILTER); + expectedResultTable = new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{2062916453604.0, + 2334011146274.0})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 24516L, 252256L, 49032L, 120000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + GROUP_BY); + expectedResultTable = new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{3745055692019.0, + 12836683389098.0})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 120000L, 0L, 360000L, 120000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + FILTER + GROUP_BY); + expectedResultTable = new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{1397706323624.0, + 1686328722268.0})); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 24516L, 252256L, 73548L, 120000L, expectedResultTable); + } + + @Test + public void testDistinctAvg() { + String query = "select DISTINCTAVG(column1) as v1, DISTINCTAVG(column3) as v2 from testTable"; + + // Without filter, query should be answered by NonScanBasedAggregationOperator (numEntriesScannedPostFilter = 0) + // for dictionary based columns. + BrokerResponseNative brokerResponse = getBrokerResponse(query); + DataSchema expectedDataSchema = + new DataSchema(new String[]{"v1", "v2"}, new ColumnDataType[]{ColumnDataType.DOUBLE, ColumnDataType.DOUBLE}); + ResultTable expectedResultTable = new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{ + 1074833879.1039197, 1075028681.150753 + })); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 120000L, 0L, 0L, 120000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + FILTER); + expectedResultTable = new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{ + 1101985285.0448718, 512293930.26207197 + })); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 24516L, 252256L, 49032L, 120000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + GROUP_BY); + expectedResultTable = new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{ + 2142595699.0, 334963174.0 + })); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 120000L, 0L, 360000L, 120000L, expectedResultTable); + + brokerResponse = getBrokerResponse(query + FILTER + GROUP_BY); + expectedResultTable = new ResultTable(expectedDataSchema, Collections.singletonList(new Object[]{ + 2142595699.0, 334963174.0 + })); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, 24516L, 252256L, 73548L, 120000L, expectedResultTable); + } } diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/JsonIngestionFromAvroQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/JsonIngestionFromAvroQueriesTest.java index b53f3a35d45e..a6f376c6cdac 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/JsonIngestionFromAvroQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/JsonIngestionFromAvroQueriesTest.java @@ -37,6 +37,7 @@ import org.apache.avro.file.DataFileWriter; import org.apache.avro.generic.GenericData; import org.apache.avro.generic.GenericDatumWriter; +import org.apache.avro.generic.GenericRecordBuilder; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.tuple.Pair; import org.apache.pinot.common.function.scalar.StringFunctions; @@ -80,6 +81,7 @@ public class JsonIngestionFromAvroQueriesTest extends BaseQueriesTest { private static final String JSON_COLUMN_2 = "jsonColumn2"; // for testing ENUM private static final String JSON_COLUMN_3 = "jsonColumn3"; // for testing FIXED private static final String JSON_COLUMN_4 = "jsonColumn4"; // for testing BYTES + private static final String JSON_COLUMN_5 = "jsonColumn5"; // for testing ARRAY of MAPS private static final String STRING_COLUMN = "stringColumn"; private static final org.apache.pinot.spi.data.Schema SCHEMA = new org.apache.pinot.spi.data.Schema.SchemaBuilder().addSingleValueDimension(INT_COLUMN, FieldSpec.DataType.INT) @@ -87,6 +89,7 @@ public class JsonIngestionFromAvroQueriesTest extends BaseQueriesTest { .addSingleValueDimension(JSON_COLUMN_2, FieldSpec.DataType.JSON) .addSingleValueDimension(JSON_COLUMN_3, FieldSpec.DataType.JSON) .addSingleValueDimension(JSON_COLUMN_4, FieldSpec.DataType.JSON) + .addSingleValueDimension(JSON_COLUMN_5, FieldSpec.DataType.JSON) .addSingleValueDimension(STRING_COLUMN, FieldSpec.DataType.STRING).build(); private static final TableConfig TABLE_CONFIG = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); @@ -111,7 +114,7 @@ protected List getIndexSegments() { /** @return {@link GenericRow} representing a row in Pinot table. */ private static GenericRow createTableRecord(int intValue, String stringValue, Object jsonValue, - GenericData.EnumSymbol enumValue, GenericData.Fixed fixedValue, byte[] bytesValue) { + GenericData.EnumSymbol enumValue, GenericData.Fixed fixedValue, byte[] bytesValue, List arrayValue) { GenericRow record = new GenericRow(); record.putValue(INT_COLUMN, intValue); record.putValue(STRING_COLUMN, stringValue); @@ -119,6 +122,7 @@ private static GenericRow createTableRecord(int intValue, String stringValue, Ob record.putValue(JSON_COLUMN_2, enumValue); record.putValue(JSON_COLUMN_3, fixedValue); record.putValue(JSON_COLUMN_4, ByteBuffer.wrap(bytesValue)); + record.putValue(JSON_COLUMN_5, arrayValue); return record; } @@ -137,6 +141,13 @@ private static Schema createRecordSchema() { return createRecord("record", "doc", JsonIngestionFromAvroQueriesTest.class.getCanonicalName(), false, fields); } + private static Schema createJson5RecordSchema() { + List fields = new ArrayList<>(); + fields.add(new Field("timestamp", create(Type.LONG))); + fields.add(new Field("data", createMap(create(Type.STRING)))); + return createRecord("record", "doc", "JsonIngestionFromAvroQueriesTest$Json5", false, fields); + } + private static GenericData.Record createRecordField(String k1, int v1, String k2, String v2) { GenericData.Record record = new GenericData.Record(createRecordSchema()); record.put(k1, v1); @@ -163,41 +174,65 @@ private static void createInputFile() new Field(INT_COLUMN, createUnion(Lists.newArrayList(create(Type.INT), create(Type.NULL))), null, null), new Field(STRING_COLUMN, createUnion(Lists.newArrayList(create(Type.STRING), create(Type.NULL))), null, null), new Field(JSON_COLUMN_1, - createUnion(createArray(create(Type.STRING)), createMap(create(Type.STRING)), createRecordSchema(), - create(Type.STRING), create(Type.NULL))), new Field(JSON_COLUMN_2, enumSchema), + createUnion( + createArray(create(Type.STRING)), + createMap(create(Type.STRING)), + createRecordSchema(), + create(Type.STRING), + create(Type.NULL))), + new Field(JSON_COLUMN_2, enumSchema), new Field(JSON_COLUMN_3, fixedSchema), - new Field(JSON_COLUMN_4, create(Type.BYTES))); + new Field(JSON_COLUMN_4, create(Type.BYTES)), + new Field(JSON_COLUMN_5, createArray(createJson5RecordSchema())) + ); avroSchema.setFields(fields); List inputRecords = new ArrayList<>(); // Insert ARRAY inputRecords.add( createTableRecord(1, "daffy duck", Arrays.asList("this", "is", "a", "test"), createEnumField(enumSchema, "UP"), - createFixedField(fixedSchema, 1), new byte[] {0, 0, 0, 1})); + createFixedField(fixedSchema, 1), new byte[] {0, 0, 0, 1}, Arrays.asList( + new GenericRecordBuilder(createJson5RecordSchema()) + .set("timestamp", 1719390721) + .set("data", createMapField(new Pair[]{Pair.of("a", "1"), Pair.of("b", "2")})).build()))); // Insert MAP inputRecords.add( createTableRecord(2, "mickey mouse", createMapField(new Pair[]{Pair.of("a", "1"), Pair.of("b", "2")}), - createEnumField(enumSchema, "DOWN"), createFixedField(fixedSchema, 2), new byte[] {0, 0, 0, 2})); + createEnumField(enumSchema, "DOWN"), createFixedField(fixedSchema, 2), new byte[] {0, 0, 0, 2}, + Arrays.asList(new GenericRecordBuilder(createJson5RecordSchema()).set("timestamp", 1719390722) + .set("data", createMapField(new Pair[]{Pair.of("a", "2"), Pair.of("b", "4")})).build()))); + inputRecords.add( createTableRecord(3, "donald duck", createMapField(new Pair[]{Pair.of("a", "1"), Pair.of("b", "2")}), - createEnumField(enumSchema, "UP"), createFixedField(fixedSchema, 3), new byte[] {0, 0, 0, 3})); + createEnumField(enumSchema, "UP"), createFixedField(fixedSchema, 3), new byte[] {0, 0, 0, 3}, Arrays.asList( + new GenericRecordBuilder(createJson5RecordSchema()).set("timestamp", 1719390723) + .set("data", createMapField(new Pair[]{Pair.of("a", "3"), Pair.of("b", "6")})).build()))); + inputRecords.add( createTableRecord(4, "scrooge mcduck", createMapField(new Pair[]{Pair.of("a", "1"), Pair.of("b", "2")}), - createEnumField(enumSchema, "LEFT"), createFixedField(fixedSchema, 4), new byte[] {0, 0, 0, 4})); + createEnumField(enumSchema, "LEFT"), createFixedField(fixedSchema, 4), new byte[] {0, 0, 0, 4}, + Arrays.asList(new GenericRecordBuilder(createJson5RecordSchema()).set("timestamp", 1719390724) + .set("data", createMapField(new Pair[]{Pair.of("a", "4"), Pair.of("b", "8")})).build()))); // insert RECORD inputRecords.add(createTableRecord(5, "minney mouse", createRecordField("id", 1, "name", "minney"), - createEnumField(enumSchema, "RIGHT"), createFixedField(fixedSchema, 5), new byte[] {0, 0, 0, 5})); + createEnumField(enumSchema, "RIGHT"), createFixedField(fixedSchema, 5), new byte[] {0, 0, 0, 5}, Arrays.asList( + new GenericRecordBuilder(createJson5RecordSchema()).set("timestamp", 1719390725) + .set("data", createMapField(new Pair[]{Pair.of("a", "5"), Pair.of("b", "10")})).build()))); // Insert simple Java String (gets converted into JSON value) inputRecords.add( createTableRecord(6, "pluto", "test", createEnumField(enumSchema, "DOWN"), createFixedField(fixedSchema, 6), - new byte[] {0, 0, 0, 6})); + new byte[] {0, 0, 0, 6}, Arrays.asList( + new GenericRecordBuilder(createJson5RecordSchema()).set("timestamp", 1719390726) + .set("data", createMapField(new Pair[]{Pair.of("a", "6"), Pair.of("b", "12")})).build()))); // Insert JSON string (gets converted into JSON document) inputRecords.add( createTableRecord(7, "scooby doo", "{\"name\":\"scooby\",\"id\":7}", createEnumField(enumSchema, "UP"), - createFixedField(fixedSchema, 7), new byte[] {0, 0, 0, 7})); + createFixedField(fixedSchema, 7), new byte[] {0, 0, 0, 7}, Arrays.asList( + new GenericRecordBuilder(createJson5RecordSchema()).set("timestamp", 1719390727) + .set("data", createMapField(new Pair[]{Pair.of("a", "7"), Pair.of("b", "14")})).build()))); try (DataFileWriter fileWriter = new DataFileWriter<>(new GenericDatumWriter<>(avroSchema))) { fileWriter.create(avroSchema, AVRO_DATA_FILE); @@ -209,6 +244,7 @@ private static void createInputFile() record.put(JSON_COLUMN_2, inputRecord.getValue(JSON_COLUMN_2)); record.put(JSON_COLUMN_3, inputRecord.getValue(JSON_COLUMN_3)); record.put(JSON_COLUMN_4, inputRecord.getValue(JSON_COLUMN_4)); + record.put(JSON_COLUMN_5, inputRecord.getValue(JSON_COLUMN_5)); fileWriter.append(record); } } @@ -223,6 +259,7 @@ private static RecordReader createRecordReader() set.add(JSON_COLUMN_2); set.add(JSON_COLUMN_3); set.add(JSON_COLUMN_4); + set.add(JSON_COLUMN_5); AvroRecordReader avroRecordReader = new AvroRecordReader(); avroRecordReader.init(AVRO_DATA_FILE, set, null); return avroRecordReader; @@ -336,6 +373,31 @@ public void testSimpleSelectOnBytesJsonColumn() { testByteArray("select jsonColumn4 FROM testTable"); } + @Test + public void testComplexSelectOnJsonColumn() { + Operator operator = getOperator( + "select jsonColumn5 FROM testTable"); + SelectionResultsBlock block = operator.nextBlock(); + Collection rows = block.getRows(); + Assert.assertEquals(block.getDataSchema().getColumnDataType(0), DataSchema.ColumnDataType.JSON); + + List expecteds = Arrays.asList( + "[[{\"data\":{\"a\":\"1\",\"b\":\"2\"},\"timestamp\":1719390721}]]", + "[[{\"data\":{\"a\":\"2\",\"b\":\"4\"},\"timestamp\":1719390722}]]", + "[[{\"data\":{\"a\":\"3\",\"b\":\"6\"},\"timestamp\":1719390723}]]", + "[[{\"data\":{\"a\":\"4\",\"b\":\"8\"},\"timestamp\":1719390724}]]", + "[[{\"data\":{\"a\":\"5\",\"b\":\"10\"},\"timestamp\":1719390725}]]", + "[[{\"data\":{\"a\":\"6\",\"b\":\"12\"},\"timestamp\":1719390726}]]", + "[[{\"data\":{\"a\":\"7\",\"b\":\"14\"},\"timestamp\":1719390727}]]"); + + int index = 0; + Iterator iterator = rows.iterator(); + while (iterator.hasNext()) { + Object[] row = iterator.next(); + Assert.assertEquals(Arrays.toString(row), expecteds.get(index++)); + } + } + private void testByteArray(String query) { Operator operator = getOperator(query); SelectionResultsBlock block = operator.nextBlock(); diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/JsonMalformedIndexTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/JsonMalformedIndexTest.java new file mode 100644 index 000000000000..6a9c0a28cd3b --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/queries/JsonMalformedIndexTest.java @@ -0,0 +1,131 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.queries; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import org.apache.commons.io.FileUtils; +import org.apache.pinot.common.response.broker.BrokerResponseNative; +import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; +import org.apache.pinot.segment.local.segment.creator.impl.ColumnJsonParserException; +import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; +import org.apache.pinot.segment.local.segment.index.loader.IndexLoadingConfig; +import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; +import org.apache.pinot.segment.spi.ImmutableSegment; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.spi.data.readers.GenericRow; +import org.apache.pinot.spi.utils.ReadMode; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +public class JsonMalformedIndexTest extends BaseQueriesTest { + private static final String RAW_TABLE_NAME = "testTable"; + private static final String SEGMENT_NAME = "testSegment"; + private static final String STRING_COLUMN = "stringColumn"; + private static final String JSON_COLUMN = "jsonColumn"; + private static final Schema SCHEMA = new Schema.SchemaBuilder().setSchemaName(RAW_TABLE_NAME) + .addSingleValueDimension(STRING_COLUMN, FieldSpec.DataType.STRING) + .addSingleValueDimension(JSON_COLUMN, FieldSpec.DataType.STRING).build(); + private static final TableConfig TABLE_CONFIG = + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + private IndexSegment _indexSegment; + private List _indexSegments; + private final List _records = new ArrayList<>(); + + @BeforeClass + public void setUp() + throws Exception { + _records.add(createRecord("ludwik von drake", + "{\"name\": {\"first\": \"ludwik\", \"last\": \"von drake\"}, \"id\": 181, " + + "\"data\": [\"l\", \"b\", \"c\", \"d\"]")); + } + + protected void checkResult(String query, Object[][] expectedResults) { + BrokerResponseNative brokerResponse = getBrokerResponseForOptimizedQuery(query, TABLE_CONFIG, SCHEMA); + QueriesTestUtils.testInterSegmentsResult(brokerResponse, Arrays.asList(expectedResults)); + } + + File indexDir() { + return new File(FileUtils.getTempDirectory(), getClass().getSimpleName()); + } + + GenericRow createRecord(String stringValue, String jsonValue) { + GenericRow record = new GenericRow(); + record.putValue(STRING_COLUMN, stringValue); + record.putValue(JSON_COLUMN, jsonValue); + return record; + } + + @Test(expectedExceptions = ColumnJsonParserException.class, + expectedExceptionsMessageRegExp = "Column: jsonColumn.*") + public void testJsonIndexBuild() + throws Exception { + File indexDir = indexDir(); + FileUtils.deleteDirectory(indexDir); + + List jsonIndexColumns = new ArrayList<>(); + jsonIndexColumns.add("jsonColumn"); + TABLE_CONFIG.getIndexingConfig().setJsonIndexColumns(jsonIndexColumns); + SegmentGeneratorConfig segmentGeneratorConfig = new SegmentGeneratorConfig(TABLE_CONFIG, SCHEMA); + segmentGeneratorConfig.setTableName(RAW_TABLE_NAME); + segmentGeneratorConfig.setSegmentName(SEGMENT_NAME); + segmentGeneratorConfig.setOutDir(indexDir.getPath()); + + SegmentIndexCreationDriverImpl driver = new SegmentIndexCreationDriverImpl(); + driver.init(segmentGeneratorConfig, new GenericRowRecordReader(_records)); + driver.build(); + + IndexLoadingConfig indexLoadingConfig = new IndexLoadingConfig(); + indexLoadingConfig.setTableConfig(TABLE_CONFIG); + indexLoadingConfig.setJsonIndexColumns(new HashSet<>(jsonIndexColumns)); + indexLoadingConfig.setReadMode(ReadMode.mmap); + + ImmutableSegment immutableSegment = + ImmutableSegmentLoader.load(new File(indexDir, SEGMENT_NAME), indexLoadingConfig); + _indexSegment = immutableSegment; + _indexSegments = Arrays.asList(immutableSegment, immutableSegment); + + Object[][] expecteds1 = {{"von drake"}, {"von drake"}, {"von drake"}, {"von drake"}}; + checkResult("SELECT jsonextractscalar(jsonColumn, '$.name.last', 'STRING') FROM testTable", expecteds1); + } + + @Override + protected String getFilter() { + return ""; + } + + @Override + protected IndexSegment getIndexSegment() { + return _indexSegment; + } + + @Override + protected List getIndexSegments() { + return _indexSegments; + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/JsonUnnestIngestionFromAvroQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/JsonUnnestIngestionFromAvroQueriesTest.java new file mode 100644 index 000000000000..bdbad4b80392 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/queries/JsonUnnestIngestionFromAvroQueriesTest.java @@ -0,0 +1,394 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.queries; + +import com.google.common.collect.Lists; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.apache.avro.Schema; +import org.apache.avro.file.DataFileWriter; +import org.apache.avro.generic.GenericData; +import org.apache.avro.generic.GenericDatumWriter; +import org.apache.avro.generic.GenericRecordBuilder; +import org.apache.commons.io.FileUtils; +import org.apache.pinot.common.utils.DataSchema; +import org.apache.pinot.core.common.Operator; +import org.apache.pinot.core.operator.blocks.results.SelectionResultsBlock; +import org.apache.pinot.plugin.inputformat.avro.AvroRecordReader; +import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; +import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; +import org.apache.pinot.segment.local.segment.index.loader.IndexLoadingConfig; +import org.apache.pinot.segment.spi.ImmutableSegment; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.config.table.ingestion.ComplexTypeConfig; +import org.apache.pinot.spi.config.table.ingestion.IngestionConfig; +import org.apache.pinot.spi.config.table.ingestion.TransformConfig; +import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.data.readers.GenericRow; +import org.apache.pinot.spi.data.readers.RecordReader; +import org.apache.pinot.spi.utils.ReadMode; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.apache.avro.Schema.*; + + +/** + * Test if ComplexType (RECORD, ARRAY, MAP, UNION, ENUM, and FIXED) field from an AVRO file can be ingested into a JSON + * column in a Pinot segment. + */ +public class JsonUnnestIngestionFromAvroQueriesTest extends BaseQueriesTest { + private static final File INDEX_DIR = new File(FileUtils.getTempDirectory(), "JsonIngestionFromAvroTest"); + private static final File AVRO_DATA_FILE = new File(INDEX_DIR, "JsonIngestionFromAvroTest.avro"); + private static final String RAW_TABLE_NAME = "testTable"; + private static final String SEGMENT_NAME = "testSegment"; + + private static final String INT_COLUMN = "intColumn"; + private static final String JSON_COLUMN = "jsonColumn"; // for testing ARRAY of MAPS + private static final String STRING_COLUMN = "stringColumn"; + private static final String EVENTTIME_JSON_COLUMN = "eventTimeColumn"; + private static final org.apache.pinot.spi.data.Schema SCHEMA = + new org.apache.pinot.spi.data.Schema.SchemaBuilder() + .addSingleValueDimension(INT_COLUMN, FieldSpec.DataType.INT) + .addSingleValueDimension(STRING_COLUMN, FieldSpec.DataType.STRING) + .addSingleValueDimension(JSON_COLUMN, FieldSpec.DataType.JSON) + .addSingleValueDimension("jsonColumn.timestamp", FieldSpec.DataType.TIMESTAMP) + .addSingleValueDimension("jsonColumn.data", FieldSpec.DataType.JSON) + .addSingleValueDimension("jsonColumn.data.a", FieldSpec.DataType.STRING) + .addSingleValueDimension("jsonColumn.data.b", FieldSpec.DataType.STRING) + .addSingleValueDimension(EVENTTIME_JSON_COLUMN, FieldSpec.DataType.TIMESTAMP) + .addSingleValueDimension("eventTimeColumn_10m", FieldSpec.DataType.TIMESTAMP) + .build(); + private static final TableConfig TABLE_CONFIG = + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setIngestionConfig( + new IngestionConfig(null, null, null, null, + List.of(new TransformConfig("eventTimeColumn", "eventTimeColumn.seconds * 1000"), + new TransformConfig("eventTimeColumn_10m", "round(eventTimeColumn, 60000)")), + new ComplexTypeConfig(List.of(JSON_COLUMN), null, null, null), null, null, null) + ).build(); + + private IndexSegment _indexSegment; + private List _indexSegments; + + @Override + protected String getFilter() { + return ""; + } + + @Override + protected IndexSegment getIndexSegment() { + return _indexSegment; + } + + @Override + protected List getIndexSegments() { + return _indexSegments; + } + + /** @return {@link GenericRow} representing a row in Pinot table. */ + private static GenericRow createTableRecord(int intValue, String stringValue, List arrayValue, + Object eventTimeValue) { + GenericRow record = new GenericRow(); + record.putValue(INT_COLUMN, intValue); + record.putValue(STRING_COLUMN, stringValue); + record.putValue(JSON_COLUMN, arrayValue); + record.putValue(EVENTTIME_JSON_COLUMN, eventTimeValue); + return record; + } + + private static Schema createJsonRecordSchema() { + List fields = new ArrayList<>(); + fields.add(new Field("timestamp", create(Type.LONG))); + fields.add(new Field("data", createMap(create(Type.STRING)))); + return createRecord("record", "doc", JsonUnnestIngestionFromAvroQueriesTest.class.getCanonicalName() + "$Json", + false, fields); + } + + private static Schema createEventTimeRecordSchema() { + List fields = new ArrayList<>(); + fields.add(new Field("seconds", create(Type.LONG))); + return createRecord("record", "doc", JsonUnnestIngestionFromAvroQueriesTest.class.getCanonicalName() + "$EventTime", + false, fields); + } + + private static void createInputFile() + throws IOException { + INDEX_DIR.mkdir(); + Schema avroSchema = createRecord("eventsRecord", null, null, false); + List fields = Arrays.asList( + new Field(INT_COLUMN, createUnion(Lists.newArrayList(create(Type.INT), create(Type.NULL))), null, null), + new Field(STRING_COLUMN, createUnion(Lists.newArrayList(create(Type.STRING), create(Type.NULL))), null, null), + new Field(JSON_COLUMN, createArray(createJsonRecordSchema())), + new Field(EVENTTIME_JSON_COLUMN, createEventTimeRecordSchema()) + + ); + avroSchema.setFields(fields); + List inputRecords = new ArrayList<>(); + // Insert ARRAY + inputRecords.add( + createTableRecord(1, "daffy duck", Arrays.asList( + new GenericRecordBuilder(createJsonRecordSchema()) + .set("timestamp", 1719390721) + .set("data", Map.of("a", "1", "b", "2")) + .build(), + new GenericRecordBuilder(createJsonRecordSchema()) + .set("timestamp", 1719390722) + .set("data", Map.of("a", "2", "b", "4")) + .build() + ), + new GenericRecordBuilder(createEventTimeRecordSchema()) + .set("seconds", 1719390721) + .build())); + + // Insert MAP + inputRecords.add( + createTableRecord(2, "mickey mouse", Arrays.asList( + new GenericRecordBuilder(createJsonRecordSchema()) + .set("timestamp", 1719390722) + .set("data", Map.of("a", "2", "b", "4")) + .build(), + new GenericRecordBuilder(createJsonRecordSchema()) + .set("timestamp", 1719390723) + .set("data", Map.of("a", "3", "b", "6")) + .build() + ), + new GenericRecordBuilder(createEventTimeRecordSchema()) + .set("seconds", 1719390722) + .build())); + + inputRecords.add( + createTableRecord(3, "donald duck", Arrays.asList( + new GenericRecordBuilder(createJsonRecordSchema()) + .set("timestamp", 1719390723) + .set("data", Map.of("a", "3", "b", "6")) + .build(), + new GenericRecordBuilder(createJsonRecordSchema()) + .set("timestamp", 1719390724) + .set("data", Map.of("a", "4", "b", "8")) + .build() + ), + new GenericRecordBuilder(createEventTimeRecordSchema()) + .set("seconds", 1719390723) + .build())); + + inputRecords.add( + createTableRecord(4, "scrooge mcduck", Arrays.asList( + new GenericRecordBuilder(createJsonRecordSchema()) + .set("timestamp", 1719390724) + .set("data", Map.of("a", "4", "b", "8")) + .build(), + new GenericRecordBuilder(createJsonRecordSchema()) + .set("timestamp", 1719390725) + .set("data", Map.of("a", "5", "b", "10")) + .build() + ), + new GenericRecordBuilder(createEventTimeRecordSchema()) + .set("seconds", 1719390724) + .build())); + + // insert RECORD + inputRecords.add(createTableRecord(5, "minney mouse", Arrays.asList( + new GenericRecordBuilder(createJsonRecordSchema()) + .set("timestamp", 1719390725) + .set("data", Map.of("a", "5", "b", "10")) + .build(), + new GenericRecordBuilder(createJsonRecordSchema()) + .set("timestamp", 1719390726) + .set("data", Map.of("a", "6", "b", "12")) + .build() + ), + new GenericRecordBuilder(createEventTimeRecordSchema()) + .set("seconds", 1719390725) + .build())); + + // Insert simple Java String (gets converted into JSON value) + inputRecords.add( + createTableRecord(6, "pluto", Arrays.asList( + new GenericRecordBuilder(createJsonRecordSchema()) + .set("timestamp", 1719390726) + .set("data", Map.of("a", "6", "b", "12")) + .build(), + new GenericRecordBuilder(createJsonRecordSchema()) + .set("timestamp", 1719390727) + .set("data", Map.of("a", "7", "b", "14")) + .build() + ), + new GenericRecordBuilder(createEventTimeRecordSchema()) + .set("seconds", 1719390726) + .build())); + + // Insert JSON string (gets converted into JSON document) + inputRecords.add( + createTableRecord(7, "scooby doo", Arrays.asList( + new GenericRecordBuilder(createJsonRecordSchema()) + .set("timestamp", 1719390727) + .set("data", Map.of("a", "7", "b", "14")) + .build(), + new GenericRecordBuilder(createJsonRecordSchema()) + .set("timestamp", 1719390728) + .set("data", Map.of("a", "8", "b", "16")) + .build() + ), + new GenericRecordBuilder(createEventTimeRecordSchema()) + .set("seconds", 1719390727) + .build())); + + try (DataFileWriter fileWriter = new DataFileWriter<>(new GenericDatumWriter<>(avroSchema))) { + fileWriter.create(avroSchema, AVRO_DATA_FILE); + for (GenericRow inputRecord : inputRecords) { + GenericData.Record record = new GenericData.Record(avroSchema); + record.put(INT_COLUMN, inputRecord.getValue(INT_COLUMN)); + record.put(STRING_COLUMN, inputRecord.getValue(STRING_COLUMN)); + record.put(JSON_COLUMN, inputRecord.getValue(JSON_COLUMN)); + record.put(EVENTTIME_JSON_COLUMN, inputRecord.getValue(EVENTTIME_JSON_COLUMN)); + fileWriter.append(record); + } + } + } + + private static RecordReader createRecordReader() + throws IOException { + Set set = new HashSet<>(); + set.add(INT_COLUMN); + set.add(STRING_COLUMN); + set.add(JSON_COLUMN); + set.add(EVENTTIME_JSON_COLUMN); + AvroRecordReader avroRecordReader = new AvroRecordReader(); + avroRecordReader.init(AVRO_DATA_FILE, set, null); + return avroRecordReader; + } + + /** Create an AVRO file and then ingest it into Pinot while creating a JsonIndex. */ + @BeforeClass + public void setUp() + throws Exception { + FileUtils.deleteDirectory(INDEX_DIR); + createInputFile(); + + List jsonIndexColumns = Arrays.asList(JSON_COLUMN); + TABLE_CONFIG.getIndexingConfig().setJsonIndexColumns(jsonIndexColumns); + SegmentGeneratorConfig segmentGeneratorConfig = new SegmentGeneratorConfig(TABLE_CONFIG, SCHEMA); + segmentGeneratorConfig.setTableName(RAW_TABLE_NAME); + segmentGeneratorConfig.setSegmentName(SEGMENT_NAME); + segmentGeneratorConfig.setOutDir(INDEX_DIR.getPath()); + segmentGeneratorConfig.setInputFilePath(AVRO_DATA_FILE.getPath()); + + SegmentIndexCreationDriverImpl driver = new SegmentIndexCreationDriverImpl(); + driver.init(segmentGeneratorConfig, createRecordReader()); + driver.build(); + + IndexLoadingConfig indexLoadingConfig = new IndexLoadingConfig(); + indexLoadingConfig.setTableConfig(TABLE_CONFIG); + indexLoadingConfig.setJsonIndexColumns(new HashSet<>(jsonIndexColumns)); + indexLoadingConfig.setReadMode(ReadMode.mmap); + + ImmutableSegment immutableSegment = + ImmutableSegmentLoader.load(new File(INDEX_DIR, SEGMENT_NAME), indexLoadingConfig); + _indexSegment = immutableSegment; + _indexSegments = Arrays.asList(immutableSegment, immutableSegment); + } + + @Test + public void testComplexSelectOnJsonColumn() { + Operator operator = getOperator( + "select intColumn, stringColumn, jsonColumn, \"jsonColumn.timestamp\", jsonColumn.data, jsonColumn.data.a, " + + "jsonColumn.data.b, eventTimeColumn, eventTimeColumn_10m FROM testTable LIMIT 1000"); + SelectionResultsBlock block = operator.nextBlock(); + Collection rows = block.getRows(); + Assert.assertEquals(block.getDataSchema().getColumnDataType(0), DataSchema.ColumnDataType.INT); + Assert.assertEquals(block.getDataSchema().getColumnDataType(1), DataSchema.ColumnDataType.STRING); + Assert.assertEquals(block.getDataSchema().getColumnDataType(2), DataSchema.ColumnDataType.JSON); + Assert.assertEquals(block.getDataSchema().getColumnDataType(3), DataSchema.ColumnDataType.TIMESTAMP); + Assert.assertEquals(block.getDataSchema().getColumnDataType(4), DataSchema.ColumnDataType.JSON); + Assert.assertEquals(block.getDataSchema().getColumnDataType(5), DataSchema.ColumnDataType.STRING); + Assert.assertEquals(block.getDataSchema().getColumnDataType(6), DataSchema.ColumnDataType.STRING); + Assert.assertEquals(block.getDataSchema().getColumnDataType(7), DataSchema.ColumnDataType.TIMESTAMP); + Assert.assertEquals(block.getDataSchema().getColumnDataType(8), DataSchema.ColumnDataType.TIMESTAMP); + + List expecteds = Arrays.asList( + "[1, daffy duck, [{\"data\":{\"a\":\"1\",\"b\":\"2\"},\"timestamp\":1719390721},{\"data\":{\"a\":\"2\"," + + "\"b\":\"4\"},\"timestamp\":1719390722}], 1719390721, {\"a\":\"1\",\"b\":\"2\"}, 1, 2, 1719390721000, " + + "1719390720000]", + "[1, daffy duck, [{\"data\":{\"a\":\"1\",\"b\":\"2\"},\"timestamp\":1719390721},{\"data\":{\"a\":\"2\"," + + "\"b\":\"4\"},\"timestamp\":1719390722}], 1719390722, {\"a\":\"2\",\"b\":\"4\"}, 2, 4, 1719390721000, " + + "1719390720000]", + "[2, mickey mouse, [{\"data\":{\"a\":\"2\",\"b\":\"4\"},\"timestamp\":1719390722},{\"data\":{\"a\":\"3\"," + + "\"b\":\"6\"},\"timestamp\":1719390723}], 1719390722, {\"a\":\"2\",\"b\":\"4\"}, 2, 4, 1719390722000, " + + "1719390720000]", + "[2, mickey mouse, [{\"data\":{\"a\":\"2\",\"b\":\"4\"},\"timestamp\":1719390722},{\"data\":{\"a\":\"3\"," + + "\"b\":\"6\"},\"timestamp\":1719390723}], 1719390723, {\"a\":\"3\",\"b\":\"6\"}, 3, 6, 1719390722000, " + + "1719390720000]", + "[3, donald duck, [{\"data\":{\"a\":\"3\",\"b\":\"6\"},\"timestamp\":1719390723},{\"data\":{\"a\":\"4\"," + + "\"b\":\"8\"},\"timestamp\":1719390724}], 1719390723, {\"a\":\"3\",\"b\":\"6\"}, 3, 6, 1719390723000, " + + "1719390720000]", + "[3, donald duck, [{\"data\":{\"a\":\"3\",\"b\":\"6\"},\"timestamp\":1719390723},{\"data\":{\"a\":\"4\"," + + "\"b\":\"8\"},\"timestamp\":1719390724}], 1719390724, {\"a\":\"4\",\"b\":\"8\"}, 4, 8, 1719390723000, " + + "1719390720000]", + "[4, scrooge mcduck, [{\"data\":{\"a\":\"4\",\"b\":\"8\"},\"timestamp\":1719390724},{\"data\":{\"a\":\"5\"," + + "\"b\":\"10\"},\"timestamp\":1719390725}], 1719390724, {\"a\":\"4\",\"b\":\"8\"}, 4, 8, 1719390724000, " + + "1719390720000]", + "[4, scrooge mcduck, [{\"data\":{\"a\":\"4\",\"b\":\"8\"},\"timestamp\":1719390724},{\"data\":{\"a\":\"5\"," + + "\"b\":\"10\"},\"timestamp\":1719390725}], 1719390725, {\"a\":\"5\",\"b\":\"10\"}, 5, 10, " + + "1719390724000, 1719390720000]", + "[5, minney mouse, [{\"data\":{\"a\":\"5\",\"b\":\"10\"},\"timestamp\":1719390725},{\"data\":{\"a\":\"6\"," + + "\"b\":\"12\"},\"timestamp\":1719390726}], 1719390725, {\"a\":\"5\",\"b\":\"10\"}, 5, 10, " + + "1719390725000, 1719390720000]", + "[5, minney mouse, [{\"data\":{\"a\":\"5\",\"b\":\"10\"},\"timestamp\":1719390725},{\"data\":{\"a\":\"6\"," + + "\"b\":\"12\"},\"timestamp\":1719390726}], 1719390726, {\"a\":\"6\",\"b\":\"12\"}, 6, 12, " + + "1719390725000, 1719390720000]", + "[6, pluto, [{\"data\":{\"a\":\"6\",\"b\":\"12\"},\"timestamp\":1719390726},{\"data\":{\"a\":\"7\"," + + "\"b\":\"14\"},\"timestamp\":1719390727}], 1719390726, {\"a\":\"6\",\"b\":\"12\"}, 6, 12, " + + "1719390726000, 1719390720000]", + "[6, pluto, [{\"data\":{\"a\":\"6\",\"b\":\"12\"},\"timestamp\":1719390726},{\"data\":{\"a\":\"7\"," + + "\"b\":\"14\"},\"timestamp\":1719390727}], 1719390727, {\"a\":\"7\",\"b\":\"14\"}, 7, 14, " + + "1719390726000, 1719390720000]", + "[7, scooby doo, [{\"data\":{\"a\":\"7\",\"b\":\"14\"},\"timestamp\":1719390727},{\"data\":{\"a\":\"8\"," + + "\"b\":\"16\"},\"timestamp\":1719390728}], 1719390727, {\"a\":\"7\",\"b\":\"14\"}, 7, 14, " + + "1719390727000, 1719390720000]", + "[7, scooby doo, [{\"data\":{\"a\":\"7\",\"b\":\"14\"},\"timestamp\":1719390727},{\"data\":{\"a\":\"8\"," + + "\"b\":\"16\"},\"timestamp\":1719390728}], 1719390728, {\"a\":\"8\",\"b\":\"16\"}, 8, 16, " + + "1719390727000, 1719390720000]"); + Assert.assertEquals(rows.size(), 14); + int index = 0; + for (Object[] row : rows) { + System.out.println(Arrays.toString(row)); + Assert.assertEquals(Arrays.toString(row), expecteds.get(index++)); + } + } + + @AfterClass + public void tearDown() + throws IOException { + _indexSegment.destroy(); + FileUtils.deleteDirectory(INDEX_DIR); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/MultiValueRawQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/MultiValueRawQueriesTest.java index 45b70cea7404..e0b8078b0e25 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/MultiValueRawQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/MultiValueRawQueriesTest.java @@ -49,11 +49,7 @@ import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertThrows; -import static org.testng.Assert.assertTrue; +import static org.testng.Assert.*; public class MultiValueRawQueriesTest extends BaseQueriesTest { @@ -439,8 +435,8 @@ public void testSelectionOrderBy() { String query = "SELECT ARRAYLENGTH(mvRawLongCol), ARRAYLENGTH(mvLongCol) from testTable ORDER BY " + "ARRAYLENGTH(mvRawLongCol), ARRAYLENGTH(mvLongCol) LIMIT 10"; BrokerResponseNative brokerResponseNative = getBrokerResponse(query); - assertTrue(brokerResponseNative.getProcessingExceptions() == null - || brokerResponseNative.getProcessingExceptions().size() == 0); + assertTrue(brokerResponseNative.getExceptions() == null + || brokerResponseNative.getExceptions().size() == 0); ResultTable resultTable = brokerResponseNative.getResultTable(); assertEquals(resultTable.getRows().size(), 10); List recordRows = resultTable.getRows(); @@ -2154,15 +2150,16 @@ private String getConcatNestingQueryString(String concatFunction, String col1, S private String getConcatUseInWhereQueryString(String concatFunction, String col1, String col2, String concatCol, String table, String compareOperator, int mvPerArray, int limit) { - return String.format("SELECT %s(%s, %s) AS %s FROM %s WHERE arraylength(%s) %s %d LIMIT %d", concatFunction, col1, - col2, concatCol, table, concatCol, compareOperator, mvPerArray, limit); + String function = String.format("%s(%s, %s)", concatFunction, col1, col2); + return String.format("SELECT %s AS %s FROM %s WHERE arraylength(%s) %s %d LIMIT %d", function, concatCol, table, + function, compareOperator, mvPerArray, limit); } private String getConcatGroupByQueryString(String concatFunction, String col1, String col2, String concatCol, String table, String compareOperator, int mvPerArray, int limit) { - return String.format( - "SELECT %s(%s, %s) AS %s, sum(svIntCol) FROM %s WHERE arraylength(%s) %s %d GROUP BY %s LIMIT %d", - concatFunction, col1, col2, concatCol, table, concatCol, compareOperator, mvPerArray, concatCol, limit); + String function = String.format("%s(%s, %s)", concatFunction, col1, col2); + return String.format("SELECT %s AS %s, sum(svIntCol) FROM %s WHERE arraylength(%s) %s %d GROUP BY %s LIMIT %d", + function, concatCol, table, function, compareOperator, mvPerArray, concatCol, limit); } @Test diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/NativeAndLuceneComparisonTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/NativeAndLuceneComparisonTest.java index 53db2f7abcd9..a066a45dc403 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/NativeAndLuceneComparisonTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/NativeAndLuceneComparisonTest.java @@ -57,8 +57,10 @@ public class NativeAndLuceneComparisonTest extends BaseQueriesTest { private static final String TABLE_NAME = "MyTable"; private static final String SEGMENT_NAME_LUCENE = "testSegmentLucene"; private static final String SEGMENT_NAME_NATIVE = "testSegmentNative"; - private static final String DOMAIN_NAMES_COL_LUCENE = "DOMAIN_NAMES_LUCENE"; - private static final String DOMAIN_NAMES_COL_NATIVE = "DOMAIN_NAMES_NATIVE"; + private static final String QUOTES_COL_LUCENE = "QUOTES_LUCENE"; + private static final String QUOTES_COL_NATIVE = "QUOTES_NATIVE"; + private static final String QUOTES_COL_LUCENE_MV = "QUOTES_LUCENE_MV"; + private static final String QUOTES_COL_NATIVE_MV = "QUOTES_NATIVE_MV"; private static final Integer NUM_ROWS = 1024; private IndexSegment _indexSegment; @@ -107,8 +109,8 @@ public void tearDown() { FileUtils.deleteQuietly(INDEX_DIR); } - private List getDomainNames() { - return Arrays.asList("Prince Andrew kept looking with an amused smile from Pierre", + private String[] getTextData() { + return new String[]{"Prince Andrew kept looking with an amused smile from Pierre", "vicomte and from the vicomte to their hostess. In the first moment of", "Pierre’s outburst Anna Pávlovna, despite her social experience, was", "horror-struck. But when she saw that Pierre’s sacrilegious words", @@ -116,17 +118,34 @@ private List getDomainNames() { "impossible to stop him, she rallied her forces and joined the vicomte in", "a vigorous attack on the orator", "horror-struck. But when she", "she rallied her forces and joined", "outburst Anna Pávlovna", "she rallied her forces and", "despite her social experience", "had not exasperated the vicomte", - " despite her social experience", "impossible to stop him", "despite her social experience"); + " despite her social experience", "impossible to stop him", "despite her social experience"}; + } + + private String[][] getMVTextData() { + return new String[][]{ + {"Prince Andrew kept", "looking with an"}, {"amused smile", "from Pierre"}, {"vicomte and from the"}, { + "vicomte to", "their hostess."}, {"In the first moment of"}, {"Pierre’s outburst Anna Pávlovna,"}, { + "despite her", "social", "experience, was"}, {"horror-struck.", "But when she"}, {"saw that Pierre’s"}, { + "sacrilegious words"}, {"had not exasperated the vicomte, and had convinced herself that it was"}, { + "impossible to stop him,", "she rallied her"}, {"forces and joined the vicomte in", "a vigorous attack on " + + "the orator"}, {"horror-struck. But when she", "she rallied her forces and joined", "outburst Anna " + + "Pávlovna"}, {"she rallied her forces and", "despite her social experience", "had not exasperated the " + + "vicomte"}, {"despite her social experience", "impossible to stop him", "despite her social experience"} + }; } private List createTestData(int numRows) { List rows = new ArrayList<>(); - List domainNames = getDomainNames(); + String[] textData = getTextData(); + String[][] mvTextData = getMVTextData(); for (int i = 0; i < numRows; i++) { - String domain = domainNames.get(i % domainNames.size()); + String doc = textData[i % textData.length]; + String[] mvDoc = mvTextData[i % mvTextData.length]; GenericRow row = new GenericRow(); - row.putField(DOMAIN_NAMES_COL_LUCENE, domain); - row.putField(DOMAIN_NAMES_COL_NATIVE, domain); + row.putValue(QUOTES_COL_LUCENE, doc); + row.putValue(QUOTES_COL_NATIVE, doc); + row.putValue(QUOTES_COL_LUCENE_MV, mvDoc); + row.putValue(QUOTES_COL_NATIVE_MV, mvDoc); rows.add(row); } @@ -139,13 +158,18 @@ private void buildLuceneSegment() List fieldConfigs = new ArrayList<>(); fieldConfigs.add( - new FieldConfig(DOMAIN_NAMES_COL_LUCENE, FieldConfig.EncodingType.DICTIONARY, FieldConfig.IndexType.TEXT, null, + new FieldConfig(QUOTES_COL_LUCENE, FieldConfig.EncodingType.DICTIONARY, FieldConfig.IndexType.TEXT, null, + null)); + fieldConfigs.add( + new FieldConfig(QUOTES_COL_LUCENE_MV, FieldConfig.EncodingType.DICTIONARY, FieldConfig.IndexType.TEXT, null, null)); TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME) - .setInvertedIndexColumns(Arrays.asList(DOMAIN_NAMES_COL_LUCENE)).setFieldConfigList(fieldConfigs).build(); + .setInvertedIndexColumns(Arrays.asList(QUOTES_COL_LUCENE, QUOTES_COL_LUCENE_MV)) + .setFieldConfigList(fieldConfigs).build(); Schema schema = new Schema.SchemaBuilder().setSchemaName(TABLE_NAME) - .addSingleValueDimension(DOMAIN_NAMES_COL_LUCENE, FieldSpec.DataType.STRING).build(); + .addSingleValueDimension(QUOTES_COL_LUCENE, FieldSpec.DataType.STRING) + .addMultiValueDimension(QUOTES_COL_LUCENE_MV, FieldSpec.DataType.STRING).build(); SegmentGeneratorConfig config = new SegmentGeneratorConfig(tableConfig, schema); config.setOutDir(INDEX_DIR.getPath()); config.setTableName(TABLE_NAME); @@ -168,13 +192,18 @@ private void buildNativeTextIndexSegment() propertiesMap.put(FieldConfig.TEXT_FST_TYPE, FieldConfig.TEXT_NATIVE_FST_LITERAL); fieldConfigs.add( - new FieldConfig(DOMAIN_NAMES_COL_NATIVE, FieldConfig.EncodingType.DICTIONARY, FieldConfig.IndexType.TEXT, null, + new FieldConfig(QUOTES_COL_NATIVE, FieldConfig.EncodingType.DICTIONARY, FieldConfig.IndexType.TEXT, null, + propertiesMap)); + fieldConfigs.add( + new FieldConfig(QUOTES_COL_NATIVE_MV, FieldConfig.EncodingType.DICTIONARY, FieldConfig.IndexType.TEXT, null, propertiesMap)); TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME) - .setInvertedIndexColumns(Arrays.asList(DOMAIN_NAMES_COL_NATIVE)).setFieldConfigList(fieldConfigs).build(); + .setInvertedIndexColumns(Arrays.asList(QUOTES_COL_NATIVE, QUOTES_COL_NATIVE_MV)) + .setFieldConfigList(fieldConfigs).build(); Schema schema = new Schema.SchemaBuilder().setSchemaName(TABLE_NAME) - .addSingleValueDimension(DOMAIN_NAMES_COL_NATIVE, FieldSpec.DataType.STRING).build(); + .addSingleValueDimension(QUOTES_COL_NATIVE, FieldSpec.DataType.STRING) + .addMultiValueDimension(QUOTES_COL_NATIVE_MV, FieldSpec.DataType.STRING).build(); SegmentGeneratorConfig config = new SegmentGeneratorConfig(tableConfig, schema); config.setOutDir(INDEX_DIR.getPath()); config.setTableName(TABLE_NAME); @@ -192,10 +221,12 @@ private ImmutableSegment loadLuceneSegment() throws Exception { IndexLoadingConfig indexLoadingConfig = new IndexLoadingConfig(); Set textIndexCols = new HashSet<>(); - textIndexCols.add(DOMAIN_NAMES_COL_LUCENE); + textIndexCols.add(QUOTES_COL_LUCENE); + textIndexCols.add(QUOTES_COL_LUCENE_MV); indexLoadingConfig.setTextIndexColumns(textIndexCols); Set invertedIndexCols = new HashSet<>(); - invertedIndexCols.add(DOMAIN_NAMES_COL_LUCENE); + invertedIndexCols.add(QUOTES_COL_LUCENE); + invertedIndexCols.add(QUOTES_COL_LUCENE_MV); indexLoadingConfig.setInvertedIndexColumns(invertedIndexCols); return ImmutableSegmentLoader.load(new File(INDEX_DIR, SEGMENT_NAME_LUCENE), indexLoadingConfig); } @@ -209,13 +240,16 @@ private ImmutableSegment loadNativeIndexSegment() Map> columnPropertiesParentMap = new HashMap<>(); Set textIndexCols = new HashSet<>(); - textIndexCols.add(DOMAIN_NAMES_COL_NATIVE); + textIndexCols.add(QUOTES_COL_NATIVE); + textIndexCols.add(QUOTES_COL_NATIVE_MV); indexLoadingConfig.setTextIndexColumns(textIndexCols); indexLoadingConfig.setFSTIndexType(fstType); Set invertedIndexCols = new HashSet<>(); - invertedIndexCols.add(DOMAIN_NAMES_COL_NATIVE); + invertedIndexCols.add(QUOTES_COL_NATIVE); + invertedIndexCols.add(QUOTES_COL_NATIVE_MV); indexLoadingConfig.setInvertedIndexColumns(invertedIndexCols); - columnPropertiesParentMap.put(DOMAIN_NAMES_COL_NATIVE, propertiesMap); + columnPropertiesParentMap.put(QUOTES_COL_NATIVE, propertiesMap); + columnPropertiesParentMap.put(QUOTES_COL_NATIVE_MV, propertiesMap); indexLoadingConfig.setColumnProperties(columnPropertiesParentMap); return ImmutableSegmentLoader.load(new File(INDEX_DIR, SEGMENT_NAME_NATIVE), indexLoadingConfig); } @@ -247,29 +281,51 @@ private void testSelectionResults(String nativeQuery, String luceneQuery) { } } } - @Test public void testQueries() { - String nativeQuery = "SELECT * FROM MyTable WHERE TEXT_CONTAINS(DOMAIN_NAMES_NATIVE, 'vico.*') LIMIT 50000"; - String luceneQuery = "SELECT * FROM MyTable WHERE TEXT_MATCH(DOMAIN_NAMES_LUCENE, 'vico*') LIMIT 50000"; + + String nativeQuery = "SELECT * FROM MyTable WHERE TEXT_CONTAINS(QUOTES_NATIVE, 'vico.*') LIMIT 50000"; + String luceneQuery = "SELECT * FROM MyTable WHERE TEXT_MATCH(QUOTES_LUCENE, 'vico*') LIMIT 50000"; testSelectionResults(nativeQuery, luceneQuery); - nativeQuery = "SELECT * FROM MyTable WHERE TEXT_CONTAINS(DOMAIN_NAMES_NATIVE, 'convi.*ced') LIMIT 50000"; - luceneQuery = "SELECT * FROM MyTable WHERE TEXT_MATCH(DOMAIN_NAMES_LUCENE, 'convi*ced') LIMIT 50000"; + nativeQuery = "SELECT * FROM MyTable WHERE TEXT_CONTAINS(QUOTES_NATIVE, 'convi.*ced') LIMIT 50000"; + luceneQuery = "SELECT * FROM MyTable WHERE TEXT_MATCH(QUOTES_LUCENE, 'convi*ced') LIMIT 50000"; testSelectionResults(nativeQuery, luceneQuery); - nativeQuery = "SELECT * FROM MyTable WHERE TEXT_CONTAINS(DOMAIN_NAMES_NATIVE, 'vicomte') AND " - + "TEXT_CONTAINS(DOMAIN_NAMES_NATIVE, 'hos.*') LIMIT 50000"; - luceneQuery = "SELECT * FROM MyTable WHERE TEXT_MATCH(DOMAIN_NAMES_LUCENE, 'vicomte AND hos*') LIMIT 50000"; + nativeQuery = "SELECT * FROM MyTable WHERE TEXT_CONTAINS(QUOTES_NATIVE, 'vicomte') AND " + + "TEXT_CONTAINS(QUOTES_NATIVE, 'hos.*') LIMIT 50000"; + luceneQuery = "SELECT * FROM MyTable WHERE TEXT_MATCH(QUOTES_LUCENE, 'vicomte AND hos*') LIMIT 50000"; testSelectionResults(nativeQuery, luceneQuery); - nativeQuery = "SELECT * FROM MyTable WHERE TEXT_CONTAINS(DOMAIN_NAMES_NATIVE, 'sac.*') OR " - + "TEXT_CONTAINS(DOMAIN_NAMES_NATIVE, 'herself') LIMIT 50000"; - luceneQuery = "SELECT * FROM MyTable WHERE TEXT_MATCH(DOMAIN_NAMES_LUCENE, 'sac* OR herself') LIMIT 50000"; + nativeQuery = "SELECT * FROM MyTable WHERE TEXT_CONTAINS(QUOTES_NATIVE, 'sac.*') OR " + + "TEXT_CONTAINS(QUOTES_NATIVE, 'herself') LIMIT 50000"; + luceneQuery = "SELECT * FROM MyTable WHERE TEXT_MATCH(QUOTES_LUCENE, 'sac* OR herself') LIMIT 50000"; testSelectionResults(nativeQuery, luceneQuery); - nativeQuery = "SELECT * FROM MyTable WHERE TEXT_CONTAINS(DOMAIN_NAMES_NATIVE, 'vicomte') LIMIT 50000"; - luceneQuery = "SELECT * FROM MyTable WHERE TEXT_MATCH(DOMAIN_NAMES_LUCENE, 'vicomte') LIMIT 50000"; + nativeQuery = "SELECT * FROM MyTable WHERE TEXT_CONTAINS(QUOTES_NATIVE, 'vicomte') LIMIT 50000"; + luceneQuery = "SELECT * FROM MyTable WHERE TEXT_MATCH(QUOTES_LUCENE, 'vicomte') LIMIT 50000"; testSelectionResults(nativeQuery, luceneQuery); + + String nativeMVQuery = "SELECT * FROM MyTable WHERE TEXT_CONTAINS(QUOTES_NATIVE_MV, 'vico.*') LIMIT 50000"; + String luceneMVQuery = "SELECT * FROM MyTable WHERE TEXT_MATCH(QUOTES_LUCENE_MV, 'vico*') LIMIT 50000"; + testSelectionResults(nativeMVQuery, luceneMVQuery); + + nativeMVQuery = "SELECT * FROM MyTable WHERE TEXT_CONTAINS(QUOTES_NATIVE_MV, 'convi.*ced') LIMIT 50000"; + luceneMVQuery = "SELECT * FROM MyTable WHERE TEXT_MATCH(QUOTES_LUCENE_MV, 'convi*ced') LIMIT 50000"; + testSelectionResults(nativeMVQuery, luceneMVQuery); + + nativeMVQuery = "SELECT * FROM MyTable WHERE TEXT_CONTAINS(QUOTES_NATIVE_MV, 'vicomte') AND " + + "TEXT_CONTAINS(QUOTES_NATIVE_MV, 'hos.*') LIMIT 50000"; + luceneMVQuery = "SELECT * FROM MyTable WHERE TEXT_MATCH(QUOTES_LUCENE_MV, 'vicomte AND hos*') LIMIT 50000"; + testSelectionResults(nativeMVQuery, luceneMVQuery); + + nativeMVQuery = "SELECT * FROM MyTable WHERE TEXT_CONTAINS(QUOTES_NATIVE_MV, 'sac.*') OR " + + "TEXT_CONTAINS(QUOTES_NATIVE_MV, 'herself') LIMIT 50000"; + luceneMVQuery = "SELECT * FROM MyTable WHERE TEXT_MATCH(QUOTES_LUCENE_MV, 'sac* OR herself') LIMIT 50000"; + testSelectionResults(nativeMVQuery, luceneMVQuery); + + nativeMVQuery = "SELECT * FROM MyTable WHERE TEXT_CONTAINS(QUOTES_NATIVE_MV, 'vicomte') LIMIT 50000"; + luceneMVQuery = "SELECT * FROM MyTable WHERE TEXT_MATCH(QUOTES_LUCENE_MV, 'vicomte') LIMIT 50000"; + testSelectionResults(nativeMVQuery, luceneMVQuery); } } diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/NoDictionaryCompressionQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/NoDictionaryCompressionQueriesTest.java index 6007a11b1553..7ae5dbe2cff1 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/NoDictionaryCompressionQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/NoDictionaryCompressionQueriesTest.java @@ -69,16 +69,18 @@ public class NoDictionaryCompressionQueriesTest extends BaseQueriesTest { private static final String PASS_THROUGH_STRING = "PASS_THROUGH_STRING"; private static final String ZSTANDARD_STRING = "ZSTANDARD_STRING"; private static final String LZ4_STRING = "LZ4_STRING"; + private static final String GZIP_STRING = "GZIP_STRING"; private static final String SNAPPY_LONG = "SNAPPY_LONG"; private static final String PASS_THROUGH_LONG = "PASS_THROUGH_LONG"; private static final String ZSTANDARD_LONG = "ZSTANDARD_LONG"; private static final String LZ4_LONG = "LZ4_LONG"; - + private static final String GZIP_LONG = "GZIP_LONG"; private static final String SNAPPY_INTEGER = "SNAPPY_INTEGER"; private static final String PASS_THROUGH_INTEGER = "PASS_THROUGH_INTEGER"; private static final String ZSTANDARD_INTEGER = "ZSTANDARD_INTEGER"; private static final String LZ4_INTEGER = "LZ4_INTEGER"; + private static final String GZIP_INTEGER = "GZIP_INTEGER"; private static final List RAW_SNAPPY_INDEX_COLUMNS = Arrays.asList(SNAPPY_STRING, SNAPPY_LONG, SNAPPY_INTEGER); @@ -90,6 +92,7 @@ public class NoDictionaryCompressionQueriesTest extends BaseQueriesTest { Arrays.asList(PASS_THROUGH_STRING, PASS_THROUGH_LONG, PASS_THROUGH_INTEGER); private static final List RAW_LZ4_INDEX_COLUMNS = Arrays.asList(LZ4_STRING, LZ4_LONG, LZ4_INTEGER); + private static final List RAW_GZIP_INDEX_COLUMNS = Arrays.asList(GZIP_STRING, GZIP_LONG, GZIP_INTEGER); private IndexSegment _indexSegment; private List _indexSegments; @@ -123,8 +126,9 @@ public void setUp() indexColumns.addAll(RAW_PASS_THROUGH_INDEX_COLUMNS); indexColumns.addAll(RAW_ZSTANDARD_INDEX_COLUMNS); indexColumns.addAll(RAW_LZ4_INDEX_COLUMNS); + indexColumns.addAll(RAW_GZIP_INDEX_COLUMNS); - indexLoadingConfig.getNoDictionaryColumns().addAll(indexColumns); + indexLoadingConfig.addNoDictionaryColumns(indexColumns); ImmutableSegment immutableSegment = ImmutableSegmentLoader.load(new File(INDEX_DIR, SEGMENT_NAME), indexLoadingConfig); _indexSegment = immutableSegment; @@ -143,7 +147,7 @@ private void buildSegment() List fieldConfigs = new ArrayList<>( RAW_SNAPPY_INDEX_COLUMNS.size() + RAW_ZSTANDARD_INDEX_COLUMNS.size() + RAW_PASS_THROUGH_INDEX_COLUMNS.size() - + RAW_LZ4_INDEX_COLUMNS.size()); + + RAW_LZ4_INDEX_COLUMNS.size() + RAW_GZIP_INDEX_COLUMNS.size()); for (String indexColumn : RAW_SNAPPY_INDEX_COLUMNS) { fieldConfigs.add(new FieldConfig(indexColumn, FieldConfig.EncodingType.RAW, Collections.emptyList(), @@ -165,11 +169,17 @@ private void buildSegment() FieldConfig.CompressionCodec.LZ4, null)); } + for (String indexColumn : RAW_GZIP_INDEX_COLUMNS) { + fieldConfigs.add(new FieldConfig(indexColumn, FieldConfig.EncodingType.RAW, Collections.emptyList(), + FieldConfig.CompressionCodec.GZIP, null)); + } + List noDictionaryColumns = new ArrayList<>(); noDictionaryColumns.addAll(RAW_SNAPPY_INDEX_COLUMNS); noDictionaryColumns.addAll(RAW_ZSTANDARD_INDEX_COLUMNS); noDictionaryColumns.addAll(RAW_PASS_THROUGH_INDEX_COLUMNS); noDictionaryColumns.addAll(RAW_LZ4_INDEX_COLUMNS); + noDictionaryColumns.addAll(RAW_GZIP_INDEX_COLUMNS); TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).setNoDictionaryColumns(noDictionaryColumns) @@ -179,14 +189,17 @@ private void buildSegment() .addSingleValueDimension(PASS_THROUGH_STRING, FieldSpec.DataType.STRING) .addSingleValueDimension(ZSTANDARD_STRING, FieldSpec.DataType.STRING) .addSingleValueDimension(LZ4_STRING, FieldSpec.DataType.STRING) + .addSingleValueDimension(GZIP_STRING, FieldSpec.DataType.STRING) .addSingleValueDimension(SNAPPY_INTEGER, FieldSpec.DataType.INT) .addSingleValueDimension(ZSTANDARD_INTEGER, FieldSpec.DataType.INT) .addSingleValueDimension(PASS_THROUGH_INTEGER, FieldSpec.DataType.INT) .addSingleValueDimension(LZ4_INTEGER, FieldSpec.DataType.INT) + .addSingleValueDimension(GZIP_INTEGER, FieldSpec.DataType.INT) .addSingleValueDimension(SNAPPY_LONG, FieldSpec.DataType.LONG) .addSingleValueDimension(ZSTANDARD_LONG, FieldSpec.DataType.LONG) .addSingleValueDimension(PASS_THROUGH_LONG, FieldSpec.DataType.LONG) - .addSingleValueDimension(LZ4_LONG, FieldSpec.DataType.LONG).build(); + .addSingleValueDimension(LZ4_LONG, FieldSpec.DataType.LONG) + .addSingleValueDimension(GZIP_LONG, FieldSpec.DataType.LONG).build(); SegmentGeneratorConfig config = new SegmentGeneratorConfig(tableConfig, schema); config.setOutDir(INDEX_DIR.getPath()); config.setTableName(TABLE_NAME); @@ -227,14 +240,17 @@ private List createTestData() { row.putValue(ZSTANDARD_STRING, tempStringRows[i]); row.putValue(PASS_THROUGH_STRING, tempStringRows[i]); row.putValue(LZ4_STRING, tempStringRows[i]); + row.putValue(GZIP_STRING, tempStringRows[i]); row.putValue(SNAPPY_INTEGER, tempIntRows[i]); row.putValue(ZSTANDARD_INTEGER, tempIntRows[i]); row.putValue(PASS_THROUGH_INTEGER, tempIntRows[i]); row.putValue(LZ4_INTEGER, tempIntRows[i]); + row.putValue(GZIP_INTEGER, tempIntRows[i]); row.putValue(SNAPPY_LONG, tempLongRows[i]); row.putValue(ZSTANDARD_LONG, tempLongRows[i]); row.putValue(PASS_THROUGH_LONG, tempLongRows[i]); row.putValue(LZ4_LONG, tempLongRows[i]); + row.putValue(GZIP_LONG, tempLongRows[i]); rows.add(row); } return rows; @@ -246,18 +262,19 @@ private List createTestData() { @Test public void testQueriesWithCompressionCodec() { String query = "SELECT SNAPPY_STRING, ZSTANDARD_STRING, PASS_THROUGH_STRING, LZ4_STRING, " - + "SNAPPY_INTEGER, ZSTANDARD_INTEGER, PASS_THROUGH_INTEGER, LZ4_INTEGER, " - + "SNAPPY_LONG, ZSTANDARD_LONG, PASS_THROUGH_LONG, LZ4_LONG FROM MyTable LIMIT 1000"; + + "GZIP_STRING, SNAPPY_INTEGER, ZSTANDARD_INTEGER, PASS_THROUGH_INTEGER, LZ4_INTEGER, " + + "GZIP_INTEGER, SNAPPY_LONG, ZSTANDARD_LONG, PASS_THROUGH_LONG, LZ4_LONG, GZIP_LONG FROM MyTable LIMIT 1000"; ArrayList expected = new ArrayList<>(); for (GenericRow row : _rows) { expected.add(new Serializable[]{ - String.valueOf(row.getValue(SNAPPY_STRING)), String.valueOf(row.getValue(ZSTANDARD_STRING)), - String.valueOf(row.getValue(PASS_THROUGH_STRING)), String.valueOf(row.getValue(LZ4_STRING)), - (Integer) row.getValue(SNAPPY_INTEGER), (Integer) row.getValue(ZSTANDARD_INTEGER), - (Integer) row.getValue(PASS_THROUGH_INTEGER), (Integer) row.getValue(LZ4_INTEGER), - (Long) row.getValue(SNAPPY_LONG), (Long) row.getValue(ZSTANDARD_LONG), (Long) row.getValue(PASS_THROUGH_LONG), - (Long) row.getValue(LZ4_LONG) + String.valueOf(row.getValue(SNAPPY_STRING)), String.valueOf(row.getValue(ZSTANDARD_STRING)), String.valueOf( + row.getValue(PASS_THROUGH_STRING)), String.valueOf(row.getValue(LZ4_STRING)), String.valueOf( + row.getValue(GZIP_STRING)), (Integer) row.getValue(SNAPPY_INTEGER), (Integer) row.getValue( + ZSTANDARD_INTEGER), (Integer) row.getValue(PASS_THROUGH_INTEGER), (Integer) row.getValue( + LZ4_INTEGER), (Integer) row.getValue(GZIP_INTEGER), (Long) row.getValue(SNAPPY_LONG), (Long) row.getValue( + ZSTANDARD_LONG), (Long) row.getValue(PASS_THROUGH_LONG), (Long) row.getValue(LZ4_LONG), (Long) row.getValue( + GZIP_LONG) }); } testSelectQueryHelper(query, expected.size(), expected); @@ -297,6 +314,23 @@ public void testLZ4IntegerFilterQueriesWithCompressionCodec() { testSelectQueryHelper(query, expected.size(), expected); } + /** + * Tests for filter over integer values GZIP compression codec queries. + */ + @Test + public void testGZIPIntegerFilterQueriesWithCompressionCodec() { + String query = "SELECT GZIP_INTEGER FROM MyTable WHERE GZIP_INTEGER > 1000 LIMIT 1000"; + ArrayList expected = new ArrayList<>(); + + for (GenericRow row : _rows) { + int value = (Integer) row.getValue(GZIP_INTEGER); + if (value > 1000) { + expected.add(new Serializable[]{value}); + } + } + testSelectQueryHelper(query, expected.size(), expected); + } + /** * Tests for filter over integer values compression codec queries. */ @@ -365,6 +399,23 @@ public void testLZ4StringFilterQueriesWithCompressionCodec() { testSelectQueryHelper(query, expected.size(), expected); } + /** + * Tests for filter over string values GZIP compression codec queries. + */ + @Test + public void testGZIPStringFilterQueriesWithCompressionCodec() { + String query = "SELECT GZIP_STRING FROM MyTable WHERE GZIP_STRING = 'hello_world_123' LIMIT 1000"; + ArrayList expected = new ArrayList<>(); + + for (GenericRow row : _rows) { + String value = String.valueOf(row.getValue(GZIP_STRING)); + if (value.equals("hello_world_123")) { + expected.add(new Serializable[]{value}); + } + } + testSelectQueryHelper(query, expected.size(), expected); + } + /** * Tests for filter over string values snappy compression codec queries. */ diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/NullEnabledQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/NullEnabledQueriesTest.java index a9ba3aa65414..41a664c5f119 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/NullEnabledQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/NullEnabledQueriesTest.java @@ -28,7 +28,6 @@ import java.util.Map; import java.util.Random; import org.apache.commons.io.FileUtils; -import org.apache.pinot.common.datatable.DataTableFactory; import org.apache.pinot.common.response.broker.BrokerResponseNative; import org.apache.pinot.common.response.broker.ResultTable; import org.apache.pinot.common.utils.DataSchema; @@ -103,7 +102,7 @@ public void createRecords(Number baseValue, boolean generateNulls) _records = new ArrayList<>(NUM_RECORDS); for (int i = 0; i < NUM_RECORDS; i++) { GenericRow record = new GenericRow(); - double value = baseValue.doubleValue() + i; + double value = baseValue instanceof Float ? baseValue.floatValue() + i : baseValue.doubleValue() + i; if (i % 2 == 0) { record.putValue(COLUMN_NAME, value); _sumPrecision = _sumPrecision.add(BigDecimal.valueOf(value)); @@ -281,7 +280,6 @@ public void testQueriesWithNoDictDoubleColumnNoNullValues() } public void testQueries(Number baseValue, ColumnDataType dataType, boolean nullValuesExist) { - DataTableBuilderFactory.setDataTableVersion(DataTableFactory.VERSION_4); Map queryOptions = new HashMap<>(); queryOptions.put("enableNullHandling", "true"); { @@ -374,7 +372,7 @@ public void testQueries(Number baseValue, ColumnDataType dataType, boolean nullV } } { - String query = String.format("SELECT * FROM testTable ORDER BY %s DESC LIMIT 4000", COLUMN_NAME); + String query = String.format("SELECT * FROM testTable ORDER BY %s DESC NULLS LAST LIMIT 4000", COLUMN_NAME); // getBrokerResponseForSqlQuery(query) runs SQL query on multiple index segments. The result should be equivalent // to querying 4 identical index segments. BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); @@ -399,9 +397,8 @@ public void testQueries(Number baseValue, ColumnDataType dataType, boolean nullV } k++; } - // Note 1: we inserted 500 nulls in _records, and since we query 4 identical index segments, the number of null - // values is: 500 * 4 = 2000. - // Note 2: The default null ordering is 'NULLS LAST', regardless of the ordering direction. + // We inserted 500 nulls in _records, and since we query 4 identical index segments, the number of null values is: + // 500 * 4 = 2000. for (int i = 2000; i < rowsCount; i++) { Object[] values = rows.get(i); assertEquals(values.length, 2); @@ -431,13 +428,9 @@ public void testQueries(Number baseValue, ColumnDataType dataType, boolean nullV i++; index++; } - // The default null ordering is 'NULLS LAST'. Therefore, null will appear as the last record. - if (nullValuesExist) { - assertNull(rows.get(rows.size() - 1)[0]); - } } { - int limit = 40; + int limit = NUM_RECORDS / 2 + 1; String query = String.format("SELECT DISTINCT %s FROM testTable ORDER BY %s LIMIT %d", COLUMN_NAME, COLUMN_NAME, limit); BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); @@ -495,7 +488,9 @@ public void testQueries(Number baseValue, ColumnDataType dataType, boolean nullV assertTrue(Math.abs((Double) rows.get(0)[1] - min) < 1e-1); double max = baseValue.doubleValue() + 998; assertTrue(Math.abs((Double) rows.get(0)[2] - max) < 1e-1); - double avg = _sum / (double) _records.size(); + // Nulls are added for all records where index % 2 is false, so half of the records are null. + double numNonNullRecords = nullValuesExist ? (_records.size() / 2.0) : _records.size(); + double avg = _sum / numNonNullRecords; assertTrue(Math.abs((Double) rows.get(0)[3] - avg) < 1e-1); assertTrue(Math.abs((Double) rows.get(0)[4] - (4 * _sum)) < 1e-1); } @@ -528,7 +523,7 @@ public void testQueries(Number baseValue, ColumnDataType dataType, boolean nullV } { String query = String.format( - "SELECT COUNT(*) AS count, %s FROM testTable GROUP BY %s ORDER BY %s DESC LIMIT 1000", COLUMN_NAME, + "SELECT COUNT(*) AS count, %s FROM testTable GROUP BY %s ORDER BY %s DESC NULLS LAST LIMIT 1000", COLUMN_NAME, COLUMN_NAME, COLUMN_NAME); BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); ResultTable resultTable = brokerResponse.getResultTable(); @@ -603,8 +598,8 @@ public void testQueries(Number baseValue, ColumnDataType dataType, boolean nullV // 9.500 //(1 row) // - String query = String.format("SELECT %s FROM testTable WHERE %s > '%s' LIMIT 50", COLUMN_NAME, COLUMN_NAME, - baseValue.doubleValue() + 69); + String query = String.format("SELECT %s FROM testTable WHERE %s > %s LIMIT 50", COLUMN_NAME, COLUMN_NAME, + baseValue instanceof Float ? baseValue.floatValue() + 69 : baseValue.doubleValue() + 69); BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); ResultTable resultTable = brokerResponse.getResultTable(); DataSchema dataSchema = resultTable.getDataSchema(); @@ -625,8 +620,8 @@ public void testQueries(Number baseValue, ColumnDataType dataType, boolean nullV } } { - String query = String.format("SELECT %s FROM testTable WHERE %s = '%s'", COLUMN_NAME, COLUMN_NAME, - baseValue.doubleValue() + 68); + String query = String.format("SELECT %s FROM testTable WHERE %s = %s", COLUMN_NAME, COLUMN_NAME, + baseValue instanceof Float ? baseValue.floatValue() + 68 : baseValue.doubleValue() + 68); BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); ResultTable resultTable = brokerResponse.getResultTable(); DataSchema dataSchema = resultTable.getDataSchema(); @@ -640,8 +635,8 @@ public void testQueries(Number baseValue, ColumnDataType dataType, boolean nullV } } { - String query = String.format("SELECT %s FROM testTable WHERE %s = '%s'", COLUMN_NAME, COLUMN_NAME, - baseValue.doubleValue() + 69); + String query = String.format("SELECT %s FROM testTable WHERE %s = %s", COLUMN_NAME, COLUMN_NAME, + baseValue instanceof Float ? baseValue.floatValue() + 69 : baseValue.doubleValue() + 69); BrokerResponseNative brokerResponse = getBrokerResponse(query, queryOptions); ResultTable resultTable = brokerResponse.getResultTable(); DataSchema dataSchema = resultTable.getDataSchema(); @@ -702,8 +697,10 @@ public void testQueries(Number baseValue, ColumnDataType dataType, boolean nullV } Object[] row = rows.get(index); assertEquals(row.length, 3); - assertTrue(Math.abs((Double) row[0] - (baseValue.doubleValue() + i)) < 1e-1); - assertTrue(Math.abs((Double) row[1] - (baseValue.doubleValue() + i)) < 1e-1); + + double expected = baseValue.doubleValue() + i; + assertTrue(Math.abs((Double) row[0] - expected) < 1e-1, "Col 0: Expected " + expected + " found " + row[0]); + assertTrue(Math.abs((Double) row[1] - expected) < 1e-1, "Col 1: Expected " + expected + " found " + row[1]); assertEquals(row[2], 1); i++; } diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/NullHandlingEnabledQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/NullHandlingEnabledQueriesTest.java new file mode 100644 index 000000000000..fb4e2e5dd1cc --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/queries/NullHandlingEnabledQueriesTest.java @@ -0,0 +1,1655 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.queries; + +import com.google.common.collect.ImmutableMap; +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import org.apache.commons.io.FileUtils; +import org.apache.pinot.common.response.broker.BrokerResponseNative; +import org.apache.pinot.common.response.broker.ResultTable; +import org.apache.pinot.core.plan.DocIdSetPlanNode; +import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; +import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; +import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; +import org.apache.pinot.segment.spi.ImmutableSegment; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.spi.data.readers.GenericRow; +import org.apache.pinot.spi.utils.ReadMode; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.AssertJUnit.assertArrayEquals; +import static org.testng.AssertJUnit.assertTrue; + + +public class NullHandlingEnabledQueriesTest extends BaseQueriesTest { + private static final File INDEX_DIR = new File(FileUtils.getTempDirectory(), "NullHandlingEnabledQueriesTest"); + private static final String RAW_TABLE_NAME = "testTable"; + private static final String SEGMENT_NAME = "testSegment"; + private static final String COLUMN1 = "column1"; + private static final String COLUMN2 = "column2"; + private static final int NUM_OF_SEGMENT_COPIES = 4; + private final List _rows = new ArrayList<>(); + private static final ImmutableMap QUERY_OPTIONS = ImmutableMap.of("enableNullHandling", "true"); + + private IndexSegment _indexSegment; + private List _indexSegments; + + @Override + protected String getFilter() { + return ""; + } + + @Override + protected IndexSegment getIndexSegment() { + return _indexSegment; + } + + @Override + protected List getIndexSegments() { + return _indexSegments; + } + + private void setUpSegments(TableConfig tableConfig, Schema schema) + throws Exception { + FileUtils.deleteDirectory(INDEX_DIR); + SegmentGeneratorConfig segmentGeneratorConfig = new SegmentGeneratorConfig(tableConfig, schema); + segmentGeneratorConfig.setTableName(RAW_TABLE_NAME); + segmentGeneratorConfig.setSegmentName(SEGMENT_NAME); + segmentGeneratorConfig.setNullHandlingEnabled(true); + segmentGeneratorConfig.setOutDir(INDEX_DIR.getPath()); + + SegmentIndexCreationDriverImpl driver = new SegmentIndexCreationDriverImpl(); + driver.init(segmentGeneratorConfig, new GenericRowRecordReader(_rows)); + driver.build(); + + ImmutableSegment immutableSegment = ImmutableSegmentLoader.load(new File(INDEX_DIR, SEGMENT_NAME), ReadMode.mmap); + _indexSegment = immutableSegment; + _indexSegments = Arrays.asList(immutableSegment, immutableSegment); + } + + private void initializeRows() { + _rows.clear(); + } + + private void insertRow(Object value) { + GenericRow row = new GenericRow(); + row.putValue(COLUMN1, value); + _rows.add(row); + } + + private void insertRowWithTwoColumns(Object column1Value, Object column2Value) { + GenericRow row = new GenericRow(); + row.putValue(COLUMN1, column1Value); + row.putValue(COLUMN2, column2Value); + _rows.add(row); + } + + @DataProvider(name = "BooleanAssertionFunctions") + public static Object[][] getBooleanAssertionFunctionsParameters() { + return new Object[][]{ + {"istrue", true, true}, + {"istrue", false, false}, + {"istrue", null, false}, + {"isnottrue", true, false}, + {"isnottrue", false, true}, + {"isnottrue", null, true}, + {"isfalse", true, false}, + {"isfalse", false, true}, + {"isfalse", null, false}, + {"isnotfalse", true, true}, + {"isnotfalse", false, false}, + {"isnotfalse", null, true} + }; + } + + @Test(dataProvider = "BooleanAssertionFunctions") + public void testBooleanAssertionFunctions(String function, Boolean data, Boolean queryResult) + throws Exception { + initializeRows(); + insertRow(data); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.BOOLEAN).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT %s(%s) FROM testTable LIMIT 1", function, COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + assertEquals(resultTable.getRows().get(0)[0], queryResult); + } + + @Test + public void testGroupByOrderByNullsLastUsingOrdinal() + throws Exception { + initializeRows(); + insertRow(null); + insertRow(null); + insertRow(null); + insertRow(1); + insertRow(2); + insertRow(2); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT %s, COUNT(*) FROM testTable GROUP BY %s ORDER BY 1 DESC NULLS LAST", COLUMN1, COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 3); + assertArrayEquals(rows.get(0), new Object[]{2, (long) 2 * NUM_OF_SEGMENT_COPIES}); + assertArrayEquals(rows.get(1), new Object[]{1, (long) NUM_OF_SEGMENT_COPIES}); + assertArrayEquals(rows.get(2), new Object[]{null, (long) 3 * NUM_OF_SEGMENT_COPIES}); + } + + @Test + public void testHavingFilterIsNull() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(1, 1); + insertRowWithTwoColumns(null, 1); + insertRowWithTwoColumns(null, 1); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT %s, COUNT(%s) FROM testTable GROUP BY %s HAVING %s IS NULL LIMIT 100", COLUMN1, COLUMN2, + COLUMN1, COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + assertArrayEquals(rows.get(0), new Object[]{null, (long) 2 * NUM_OF_SEGMENT_COPIES}); + } + + @Test + public void testHavingFilterIsNotNull() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(1, 1); + insertRowWithTwoColumns(null, 1); + insertRowWithTwoColumns(null, 1); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT %s, COUNT(%s) FROM testTable GROUP BY %s HAVING %s IS NOT NULL LIMIT 100", COLUMN1, + COLUMN2, COLUMN1, COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + assertArrayEquals(rows.get(0), new Object[]{1, (long) NUM_OF_SEGMENT_COPIES}); + } + + @Test + public void testHavingFilterNotOfColumnIsNull() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(true, 1); + insertRowWithTwoColumns(null, 1); + insertRowWithTwoColumns(null, 1); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.BOOLEAN) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT %s, COUNT(%s) FROM testTable GROUP BY %s HAVING (NOT %s) IS NULL LIMIT 100", COLUMN1, + COLUMN2, COLUMN1, COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + assertArrayEquals(rows.get(0), new Object[]{null, (long) 2 * NUM_OF_SEGMENT_COPIES}); + } + + @Test + public void testHavingFilterNotColumnIsNull() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(true, 1); + insertRowWithTwoColumns(null, 1); + insertRowWithTwoColumns(null, 1); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.BOOLEAN) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT %s, COUNT(%s) FROM testTable GROUP BY %s HAVING NOT (%s IS NULL) LIMIT 100", COLUMN1, + COLUMN2, COLUMN1, COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + assertArrayEquals(rows.get(0), new Object[]{true, (long) NUM_OF_SEGMENT_COPIES}); + } + + @Test + public void testHavingFilterIsNullAndIsNotNull() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(1, 1); + insertRowWithTwoColumns(null, 1); + insertRowWithTwoColumns(null, 1); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = String.format( + "SELECT %s, COUNT(%s) FROM testTable GROUP BY %s HAVING (%s IS NULL) AND (COUNT(%s) is NOT NULL) LIMIT 100", + COLUMN1, COLUMN2, COLUMN1, COLUMN1, COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + assertArrayEquals(rows.get(0), new Object[]{null, (long) 2 * NUM_OF_SEGMENT_COPIES}); + } + + @Test + public void testHavingFilterIsNullOrIsNull() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(1, 1); + insertRowWithTwoColumns(null, 1); + insertRowWithTwoColumns(null, 1); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = String.format( + "SELECT %s, COUNT(%s) FROM testTable GROUP BY %s HAVING (%s IS NULL) OR (COUNT(%s) is NULL) LIMIT 100", COLUMN1, + COLUMN2, COLUMN1, COLUMN1, COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + assertArrayEquals(rows.get(0), new Object[]{null, (long) 2 * NUM_OF_SEGMENT_COPIES}); + } + + @Test + public void testSelectDistinctOrderByNullsFirst() + throws Exception { + FileUtils.deleteDirectory(INDEX_DIR); + initializeRows(); + insertRow(1); + insertRow(null); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT DISTINCT %s FROM testTable ORDER BY %s NULLS FIRST", COLUMN1, COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + assertNull(resultTable.getRows().get(0)[0]); + assertNotNull(resultTable.getRows().get(1)[0]); + } + + @Test + public void testSelectDistinctOrderByNullsLast() + throws Exception { + FileUtils.deleteDirectory(INDEX_DIR); + initializeRows(); + insertRow(1); + insertRow(null); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT DISTINCT %s FROM testTable ORDER BY %s NULLS LAST", COLUMN1, COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + assertNotNull(resultTable.getRows().get(0)[0]); + assertNull(resultTable.getRows().get(1)[0]); + } + + @Test + public void testSelectDistinctIntegerMinValueDiffersFromNull() + throws Exception { + FileUtils.deleteDirectory(INDEX_DIR); + initializeRows(); + insertRow(Integer.MIN_VALUE); + insertRow(null); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT DISTINCT %s FROM testTable", COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + assertEquals(resultTable.getRows().size(), 2); + } + + @Test + public void testSelectDistinctMultiColumn() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(1, 1); + insertRowWithTwoColumns(1, 1); + insertRowWithTwoColumns(null, 1); + insertRowWithTwoColumns(null, 1); + insertRowWithTwoColumns(null, 2); + insertRowWithTwoColumns(null, null); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT DISTINCT %s,%s FROM testTable ORDER BY %s,%s", COLUMN1, COLUMN2, COLUMN1, COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + assertEquals(resultTable.getRows().size(), 4); + } + + @Test + public void testSelectDistinctOrderByMultiColumn() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(null, 1); + insertRowWithTwoColumns(null, 2); + insertRowWithTwoColumns(null, 2); + insertRowWithTwoColumns(1, 1); + insertRowWithTwoColumns(null, null); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT DISTINCT %s,%s FROM testTable ORDER BY %s,%s", COLUMN1, COLUMN2, COLUMN1, COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + assertEquals(resultTable.getRows().size(), 4); + assertEquals(resultTable.getRows().get(0), new Object[]{1, 1}); + assertEquals(resultTable.getRows().get(1), new Object[]{null, 1}); + assertEquals(resultTable.getRows().get(2), new Object[]{null, 2}); + assertEquals(resultTable.getRows().get(3), new Object[]{null, null}); + } + + @DataProvider(name = "NumberTypes") + public static Object[][] getPrimitiveDataTypes() { + return new Object[][]{ + {FieldSpec.DataType.INT}, {FieldSpec.DataType.LONG}, {FieldSpec.DataType.DOUBLE}, {FieldSpec.DataType.FLOAT} + }; + } + + @Test(dataProvider = "NumberTypes") + public void testSelectDistinctWithLimit(FieldSpec.DataType dataType) + throws Exception { + initializeRows(); + insertRow(null); + insertRow(1); + insertRow(2); + insertRow(3); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, dataType).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT DISTINCT %s FROM testTable ORDER BY %s LIMIT 3", COLUMN1, COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + assertEquals(resultTable.getRows().size(), 3); + } + + @Test(dataProvider = "NumberTypes") + public void testSelectDistinctOrderByWithLimit(FieldSpec.DataType dataType) + throws Exception { + double delta = 0.01; + initializeRows(); + insertRow(null); + insertRow(1); + insertRow(2); + insertRow(3); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, dataType).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT DISTINCT %s FROM testTable ORDER BY %s LIMIT 3", COLUMN1, COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + assertEquals(resultTable.getRows().size(), 3); + assertTrue(Math.abs(((Number) resultTable.getRows().get(0)[0]).doubleValue() - 1.0) < delta); + assertTrue(Math.abs(((Number) resultTable.getRows().get(1)[0]).doubleValue() - 2.0) < delta); + assertTrue(Math.abs(((Number) resultTable.getRows().get(2)[0]).doubleValue() - 3.0) < delta); + } + + @DataProvider(name = "ObjectTypes") + public static Object[][] getObjectDataTypes() { + return new Object[][]{ + {FieldSpec.DataType.STRING, "a"}, { + FieldSpec.DataType.BIG_DECIMAL, 1 + }, { + FieldSpec.DataType.BYTES, "a string".getBytes() + } + }; + } + + @Test(dataProvider = "ObjectTypes") + public void testObjectSingleColumnDistinctOrderByNullsFirst(FieldSpec.DataType dataType, Object value) + throws Exception { + initializeRows(); + insertRow(null); + insertRow(value); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, dataType).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT DISTINCT %s FROM testTable ORDER BY %s NULLS FIRST LIMIT 1", COLUMN1, COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + assertEquals(resultTable.getRows().size(), 1); + assertNull(resultTable.getRows().get(0)[0]); + } + + @Test(dataProvider = "ObjectTypes") + public void testObjectSingleColumnDistinctOrderByNullsLast(FieldSpec.DataType dataType, Object value) + throws Exception { + initializeRows(); + insertRow(null); + insertRow(value); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, dataType).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT DISTINCT %s FROM testTable ORDER BY %s NULLS LAST LIMIT 1", COLUMN1, COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + assertEquals(resultTable.getRows().size(), 1); + assertNotNull(resultTable.getRows().get(0)[0]); + } + + @Test(dataProvider = "NumberTypes") + public void testDistinctCountDictNumberTypes(FieldSpec.DataType dataType) + throws Exception { + initializeRows(); + insertRow(null); + insertRow(1); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, dataType).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT DISTINCTCOUNT(%s) FROM testTable", COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + assertEquals(resultTable.getRows().size(), 1); + assertEquals(resultTable.getRows().get(0)[0], 1); + } + + @Test(dataProvider = "NumberTypes") + public void testDistinctCountNonDictNumberTypes(FieldSpec.DataType dataType) + throws Exception { + initializeRows(); + insertRow(null); + insertRow(1); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) + .setNoDictionaryColumns(Collections.singletonList(COLUMN1)).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, dataType).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT DISTINCTCOUNT(%s) FROM testTable", COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + assertEquals(resultTable.getRows().size(), 1); + assertEquals(resultTable.getRows().get(0)[0], 1); + } + + @Test(dataProvider = "NumberTypes") + public void testGroupByDistinctCountDictNumberTypes(FieldSpec.DataType dataType) + throws Exception { + initializeRows(); + insertRowWithTwoColumns(null, "key"); + insertRowWithTwoColumns(1, "key"); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, dataType) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.STRING).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT DISTINCTCOUNT(%s), %s FROM testTable GROUP BY %s", COLUMN1, COLUMN2, COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + assertEquals(resultTable.getRows().size(), 1); + assertArrayEquals(resultTable.getRows().get(0), new Object[]{1, "key"}); + } + + @Test(dataProvider = "NumberTypes") + public void testGroupByDistinctCountNonDictNumberTypes(FieldSpec.DataType dataType) + throws Exception { + initializeRows(); + insertRowWithTwoColumns(null, "key"); + insertRowWithTwoColumns(1, "key"); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) + .setNoDictionaryColumns(Collections.singletonList(COLUMN1)).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, dataType) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.STRING).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT DISTINCTCOUNT(%s), %s FROM testTable GROUP BY %s", COLUMN1, COLUMN2, COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + assertEquals(resultTable.getRows().size(), 1); + assertArrayEquals(resultTable.getRows().get(0), new Object[]{1, "key"}); + } + + @Test(dataProvider = "NumberTypes") + public void testGroupByMvDistinctCountNonDictNumberTypes(FieldSpec.DataType dataType) + throws Exception { + initializeRows(); + insertRowWithTwoColumns(null, new String[]{"key1", "key2"}); + insertRowWithTwoColumns(1, new String[]{"key1", "key2"}); + insertRowWithTwoColumns(2, new String[]{"key2"}); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) + .setNoDictionaryColumns(Collections.singletonList(COLUMN1)).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, dataType) + .addMultiValueDimension(COLUMN2, FieldSpec.DataType.STRING).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT DISTINCTCOUNT(%s), %s FROM testTable GROUP BY %s ORDER BY %s", COLUMN1, COLUMN2, COLUMN2, + COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + assertEquals(resultTable.getRows().get(0), new Object[]{1, "key1"}); + assertEquals(resultTable.getRows().get(1), new Object[]{2, "key2"}); + } + + @Test(dataProvider = "NumberTypes") + public void testGroupByMvDistinctCountDictNumberTypes(FieldSpec.DataType dataType) + throws Exception { + initializeRows(); + insertRowWithTwoColumns(null, new String[]{"key1", "key2"}); + insertRowWithTwoColumns(1, new String[]{"key1", "key2"}); + insertRowWithTwoColumns(2, new String[]{"key2"}); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, dataType) + .addMultiValueDimension(COLUMN2, FieldSpec.DataType.STRING).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT DISTINCTCOUNT(%s), %s FROM testTable GROUP BY %s ORDER BY %s", COLUMN1, COLUMN2, COLUMN2, + COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + assertEquals(resultTable.getRows().get(0), new Object[]{1, "key1"}); + assertEquals(resultTable.getRows().get(1), new Object[]{2, "key2"}); + } + + @DataProvider(name = "DistinctCountObjectTypes") + public static Object[][] getDistinctAggregationObjectTypes() { + return new Object[][]{ + {FieldSpec.DataType.STRING, "a"}, { + FieldSpec.DataType.BYTES, "a string".getBytes() + } + }; + } + + @Test(dataProvider = "DistinctCountObjectTypes") + public void testObjectDistinctCountObjectTypes(FieldSpec.DataType dataType, Object value) + throws Exception { + initializeRows(); + insertRow(null); + insertRow(value); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, dataType).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT DISTINCTCOUNT(%s) FROM testTable", COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + assertEquals(resultTable.getRows().size(), 1); + assertEquals(resultTable.getRows().get(0)[0], 1); + } + + @Test(dataProvider = "DistinctCountObjectTypes") + public void testGroupByDistinctCountObjectTypes(FieldSpec.DataType dataType, Object value) + throws Exception { + initializeRows(); + insertRowWithTwoColumns(null, "key"); + insertRowWithTwoColumns(value, "key"); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, dataType) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.STRING).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT DISTINCTCOUNT(%s), %s FROM testTable GROUP BY %s", COLUMN1, COLUMN2, COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + assertEquals(resultTable.getRows().size(), 1); + assertArrayEquals(resultTable.getRows().get(0), new Object[]{1, "key"}); + } + + @Test + public void testDistinctSum() + throws Exception { + initializeRows(); + insertRow(null); + insertRow(1); + insertRow(2); + insertRow(2); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT DISTINCTSUM(%s) FROM testTable", COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + assertEquals(resultTable.getRows().size(), 1); + assertEquals(resultTable.getRows().get(0)[0], (double) 3); + } + + @Test + public void testDistinctAvg() + throws Exception { + initializeRows(); + insertRow(null); + insertRow(1); + insertRow(2); + insertRow(2); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT DISTINCTAVG(%s) FROM testTable", COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + assertEquals(resultTable.getRows().size(), 1); + assertEquals(resultTable.getRows().get(0)[0], 1.5); + } + + @Test + public void testTransformBlockValSetGetNullBitmap() + throws Exception { + initializeRows(); + insertRow(null); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT (CASE WHEN %s IS NULL THEN 1 END) FROM testTable", COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + assertEquals(resultTable.getRows().size(), NUM_OF_SEGMENT_COPIES); + assertEquals(resultTable.getRows().get(0)[0], 1); + } + + private boolean contains(List rows, Object[] target) { + for (Object[] row : rows) { + if (Arrays.equals(row, target)) { + return true; + } + } + return false; + } + + @Test + public void testMultiColumnGroupBy() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(null, null); + insertRowWithTwoColumns(null, 1); + insertRowWithTwoColumns(null, 1); + insertRowWithTwoColumns(1, 1); + insertRowWithTwoColumns(1, null); + insertRowWithTwoColumns(1, Integer.MIN_VALUE); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT count(*), %s, %s FROM testTable GROUP BY %s, %s", COLUMN1, COLUMN2, COLUMN1, + COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 5); + assertTrue(contains(rows, new Object[]{(long) NUM_OF_SEGMENT_COPIES, null, null})); + assertTrue(contains(rows, new Object[]{(long) 2 * NUM_OF_SEGMENT_COPIES, null, 1})); + assertTrue(contains(rows, new Object[]{(long) NUM_OF_SEGMENT_COPIES, 1, 1})); + assertTrue(contains(rows, new Object[]{(long) NUM_OF_SEGMENT_COPIES, 1, null})); + assertTrue(contains(rows, new Object[]{(long) NUM_OF_SEGMENT_COPIES, 1, Integer.MIN_VALUE})); + } + + @Test + public void testMultiColumnGroupByWithLimit() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(null, null); + insertRowWithTwoColumns(null, 1); + insertRowWithTwoColumns(null, 1); + insertRowWithTwoColumns(1, 1); + insertRowWithTwoColumns(1, null); + insertRowWithTwoColumns(1, Integer.MIN_VALUE); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT count(*), %s, %s FROM testTable GROUP BY %s, %s LIMIT 3", COLUMN1, COLUMN2, COLUMN1, + COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 3); + } + + @Test + public void testGroupByOrderBy() + throws Exception { + initializeRows(); + insertRow(null); + insertRow(1); + insertRow(1); + insertRow(2); + insertRow(3); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT count(*), %s FROM testTable GROUP BY %s ORDER BY %s ASC NULLS LAST", COLUMN1, COLUMN1, + COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 4); + assertArrayEquals(rows.get(0), new Object[]{(long) 2 * NUM_OF_SEGMENT_COPIES, 1}); + assertArrayEquals(rows.get(1), new Object[]{(long) NUM_OF_SEGMENT_COPIES, 2}); + assertArrayEquals(rows.get(2), new Object[]{(long) NUM_OF_SEGMENT_COPIES, 3}); + assertArrayEquals(rows.get(3), new Object[]{(long) NUM_OF_SEGMENT_COPIES, null}); + } + + @Test + public void testGroupByOrderByWithLimit() + throws Exception { + initializeRows(); + insertRow(null); + insertRow(1); + insertRow(1); + insertRow(2); + insertRow(3); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT count(*), %s FROM testTable GROUP BY %s ORDER BY %s DESC NULLS FIRST LIMIT 3", COLUMN1, + COLUMN1, COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 3); + assertArrayEquals(rows.get(0), new Object[]{(long) NUM_OF_SEGMENT_COPIES, null}); + assertArrayEquals(rows.get(1), new Object[]{(long) NUM_OF_SEGMENT_COPIES, 3}); + assertArrayEquals(rows.get(2), new Object[]{(long) NUM_OF_SEGMENT_COPIES, 2}); + } + + @Test + public void testNestedCaseTransformFunction() + throws Exception { + initializeRows(); + insertRow(null); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT (CASE WHEN %s = -2147483648 THEN 1 ELSE 2 END) + 0 FROM testTable", COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertArrayEquals(rows.get(0), new Object[]{(double) 2}); + } + + @Test + public void testFilteringOnInvertedIndexColumn() + throws Exception { + initializeRows(); + insertRow(null); + insertRow(false); + insertRow(true); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) + .setInvertedIndexColumns(Collections.singletonList(COLUMN1)).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.BOOLEAN).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT * FROM testTable WHERE %s = false", COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), NUM_OF_SEGMENT_COPIES); + assertArrayEquals(rows.get(0), new Object[]{false}); + } + + @Test + public void testFilteringOnSortedColumn() + throws Exception { + initializeRows(); + insertRow(null); + insertRow(false); + insertRow(true); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) + .setSortedColumn(COLUMN1).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.BOOLEAN).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT * FROM testTable WHERE %s = false", COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), NUM_OF_SEGMENT_COPIES); + assertArrayEquals(rows.get(0), new Object[]{false}); + } + + @Test + public void testRangeFiltering() + throws Exception { + initializeRows(); + insertRow(-1); + insertRow(null); + TableConfig tableConfig = + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setSortedColumn(COLUMN1).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT * FROM testTable WHERE %s < 0", COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), NUM_OF_SEGMENT_COPIES); + assertArrayEquals(rows.get(0), new Object[]{-1}); + } + + @Test + public void testEqualFiltering() + throws Exception { + initializeRows(); + insertRow(null); + insertRow(Integer.MIN_VALUE); + TableConfig tableConfig = + new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).setSortedColumn(COLUMN1).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT * FROM testTable WHERE %s = %d", COLUMN1, Integer.MIN_VALUE); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), NUM_OF_SEGMENT_COPIES); + assertArrayEquals(rows.get(0), new Object[]{Integer.MIN_VALUE}); + } + + @Test + public void testOrFiltering() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(null, null); + insertRowWithTwoColumns(null, 1); + insertRowWithTwoColumns(1, -1); + insertRowWithTwoColumns(-1, null); + insertRowWithTwoColumns(-1, 1); + insertRowWithTwoColumns(1, null); + insertRowWithTwoColumns(null, -1); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT %s, %s FROM testTable WHERE OR(%s > 0, %s < 0) LIMIT 100", COLUMN1, COLUMN2, COLUMN1, + COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), NUM_OF_SEGMENT_COPIES * 3); + } + + @Test + public void testNotFiltering() + throws Exception { + initializeRows(); + insertRow(null); + insertRow(-1); + insertRow(1); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT %s FROM testTable WHERE NOT(%s = 1) LIMIT 100", COLUMN1, COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), NUM_OF_SEGMENT_COPIES); + assertArrayEquals(rows.get(0), new Object[]{-1}); + } + + @Test + public void testNotAndFiltering() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(null, null); + insertRowWithTwoColumns(null, 1); + insertRowWithTwoColumns(1, -1); + insertRowWithTwoColumns(-1, null); + insertRowWithTwoColumns(-1, 1); + insertRowWithTwoColumns(1, null); + insertRowWithTwoColumns(null, -1); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT %s, %s FROM testTable WHERE NOT(AND(%s > 0, %s < 0)) LIMIT 100", COLUMN1, COLUMN2, + COLUMN1, COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 3 * NUM_OF_SEGMENT_COPIES); + } + + @Test + public void testNotOrFiltering() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(null, null); + insertRowWithTwoColumns(null, 1); + insertRowWithTwoColumns(1, -1); + insertRowWithTwoColumns(-1, null); + insertRowWithTwoColumns(-1, 1); + insertRowWithTwoColumns(1, null); + insertRowWithTwoColumns(null, -1); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT %s, %s FROM testTable WHERE NOT(OR(%s > 0, %s < 0)) LIMIT 100", COLUMN1, COLUMN2, COLUMN1, + COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), NUM_OF_SEGMENT_COPIES); + assertArrayEquals(rows.get(0), new Object[]{-1, 1}); + } + + @Test + public void testBaseColumnFilterOperatorGetNullBitmapIsNull() + throws Exception { + initializeRows(); + insertRow(false); + insertRow(true); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME) + .setInvertedIndexColumns(Collections.singletonList(COLUMN1)).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.BOOLEAN).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT * FROM testTable WHERE NOT(%s = false)", COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), NUM_OF_SEGMENT_COPIES); + assertArrayEquals(rows.get(0), new Object[]{true}); + } + + @Test + public void testAdditionExpressionFilterOperator() + throws Exception { + initializeRows(); + insertRow(null); + insertRow(Integer.MIN_VALUE); + insertRow(1); + insertRow(-1); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT %s FROM testTable WHERE add(%s, 0) < 0", COLUMN1, COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), NUM_OF_SEGMENT_COPIES * 2); + } + + @Test + public void testAdditionExpressionFilterOperatorInsideNotFilterOperator() + throws Exception { + initializeRows(); + insertRow(null); + insertRow(Integer.MIN_VALUE); + insertRow(1); + insertRow(-1); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT %s FROM testTable WHERE NOT(add(%s, 0) > 0)", COLUMN1, COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), NUM_OF_SEGMENT_COPIES * 2); + } + + @Test + public void testGreatestExpressionFilterOperator() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(null, null); + insertRowWithTwoColumns(Integer.MIN_VALUE, Integer.MIN_VALUE); + insertRowWithTwoColumns(null, 1); + insertRowWithTwoColumns(1, null); + insertRowWithTwoColumns(-1, -1); + insertRowWithTwoColumns(-1, null); + insertRowWithTwoColumns(null, -1); + insertRowWithTwoColumns(1, 1); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT %s, %s FROM testTable WHERE GREATEST(%s, %s) < 0 LIMIT 100", COLUMN1, COLUMN2, COLUMN1, + COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), NUM_OF_SEGMENT_COPIES * 2); + } + + @Test + public void testExpressionFilterOperatorResultIsInSecondProjectionBlock() + throws Exception { + initializeRows(); + for (int i = 0; i < DocIdSetPlanNode.MAX_DOC_PER_CALL; i++) { + insertRowWithTwoColumns(null, i); + } + insertRowWithTwoColumns(1, DocIdSetPlanNode.MAX_DOC_PER_CALL); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT %s, %s FROM testTable WHERE add(%s, 0) > 0 LIMIT 10", COLUMN1, COLUMN2, COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), NUM_OF_SEGMENT_COPIES); + assertArrayEquals(rows.get(0), new Object[]{1, DocIdSetPlanNode.MAX_DOC_PER_CALL}); + } + + @Test + public void testExpressionFilterOperatorApplyAndForGetFalses() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(null, null); + insertRowWithTwoColumns(1, null); + insertRowWithTwoColumns(-1, 1); + insertRowWithTwoColumns(Integer.MIN_VALUE, null); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT %s FROM testTable WHERE NOT(add(%s, 0) > 0) AND %s IS NULL", COLUMN1, COLUMN1, COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), NUM_OF_SEGMENT_COPIES); + assertArrayEquals(rows.get(0), new Object[]{Integer.MIN_VALUE}); + } + + @Test + public void testExpressionFilterOperatorNotFilterOnMultiValue() + throws Exception { + initializeRows(); + insertRow(new Integer[]{1, 2, 3}); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addMultiValueDimension(COLUMN1, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT * FROM testTable WHERE NOT(VALUEIN(%s, 2, 3) > 2) LIMIT 100", COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 0); + } + + @Test + public void testExpressionFilterOperatoIsNullPredicate() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(null, 1); + insertRowWithTwoColumns(1, 2); + insertRowWithTwoColumns(-1, 3); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT %s, %s FROM testTable WHERE ADD(%s, 0) IS NULL LIMIT 100", COLUMN1, COLUMN2, COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), NUM_OF_SEGMENT_COPIES); + assertArrayEquals(rows.get(0), new Object[]{null, 1}); + } + + @Test + public void testExpressionFilterOperatorIsNotNullPredicate() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(null, 1); + insertRowWithTwoColumns(null, 2); + insertRowWithTwoColumns(1, 3); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT %s, %s FROM testTable WHERE ADD(%s, 0) IS NOT NULL LIMIT 100", COLUMN1, COLUMN2, COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), NUM_OF_SEGMENT_COPIES); + assertArrayEquals(rows.get(0), new Object[]{1, 3}); + } + + @Test + public void testExpressionFilterOperatorIsNullPredicateInsideNotFilterOperator() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(null, 1); + insertRowWithTwoColumns(null, 2); + insertRowWithTwoColumns(1, 3); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT %s, %s FROM testTable WHERE NOT(ADD(%s, 0) IS NULL) LIMIT 100", COLUMN1, COLUMN2, + COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), NUM_OF_SEGMENT_COPIES); + assertArrayEquals(rows.get(0), new Object[]{1, 3}); + } + + @Test + public void testExpressionFilterOperatorIsNotNullPredicateInsideNotFilterOperator() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(null, 1); + insertRowWithTwoColumns(2, 3); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT %s, %s FROM testTable WHERE NOT(ADD(%s, 0) IS NOT NULL) LIMIT 100", COLUMN1, COLUMN2, + COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), NUM_OF_SEGMENT_COPIES); + assertArrayEquals(rows.get(0), new Object[]{null, 1}); + } + + @Test + public void testExpressionFilterOperatorApplyIsNullPredicateToNotOfColumn() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(true, 1); + insertRowWithTwoColumns(null, 2); + insertRowWithTwoColumns(false, 3); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.BOOLEAN) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT %s, %s FROM testTable WHERE (NOT %s) IS NULL LIMIT 100", COLUMN1, COLUMN2, COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), NUM_OF_SEGMENT_COPIES); + assertArrayEquals(rows.get(0), new Object[]{null, 2}); + } + + @Test + public void testExpressionFilterOperatorApplyAndForGetNulls() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(Integer.MIN_VALUE, null); + insertRowWithTwoColumns(1, null); + insertRowWithTwoColumns(-1, 1); + insertRowWithTwoColumns(null, null); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT %s, %s FROM testTable WHERE (add(%s, 0) IS NULL) AND (%s IS NULL)", COLUMN1, COLUMN2, + COLUMN1, COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), NUM_OF_SEGMENT_COPIES); + assertArrayEquals(rows.get(0), new Object[]{null, null}); + } + + @Test + public void testExpressionFilterOperatorOnMultiValue() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(new Integer[]{1, 2, 3}, 1); + insertRowWithTwoColumns(new Integer[]{2, 3, 4}, null); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addMultiValueDimension(COLUMN1, FieldSpec.DataType.INT) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT * FROM testTable WHERE (VALUEIN(%s, 2, 3) IN (2, 3)) AND (%s = 1)", COLUMN1, COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), NUM_OF_SEGMENT_COPIES); + assertArrayEquals(rows.get(0), new Object[]{new Integer[]{1, 2, 3}, 1}); + } + + @Test + public void testExpressionFilterOperatorMultiValueIsNull() + throws Exception { + initializeRows(); + insertRow(new Integer[]{1, 2, 3}); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addMultiValueDimension(COLUMN1, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT * FROM testTable WHERE (VALUEIN(%s, 2, 3) IS NULL)", COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 0); + } + + @Test + public void testExpressionFilterOperatorMultiValueIsNotNull() + throws Exception { + initializeRows(); + insertRow(new Integer[]{1, 2, 3}); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addMultiValueDimension(COLUMN1, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT * FROM testTable WHERE (VALUEIN(%s, 2, 3) IS NOT NULL)", COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), NUM_OF_SEGMENT_COPIES); + assertArrayEquals(rows.get(0), new Object[]{new Integer[]{1, 2, 3}}); + } + + @Test + public void testScalarFunctionStringNullLiteral() + throws Exception { + initializeRows(); + insertRow("abc"); + insertRow(null); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.STRING).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT STARTSWITH(%s, NULL) FROM testTable", COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 2 * NUM_OF_SEGMENT_COPIES); + for (int i = 0; i < 2 * NUM_OF_SEGMENT_COPIES; i++) { + assertArrayEquals(rows.get(i), new Object[]{null}); + } + } + + @Test + public void testScalarFunctionIntNullLiteral() + throws Exception { + initializeRows(); + insertRow(1); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT between(%s, NULL, 2) FROM testTable", COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), NUM_OF_SEGMENT_COPIES); + assertArrayEquals(rows.get(0), new Object[]{null}); + } + + @Test(dataProvider = "NumberTypes") + public void testStddevPop(FieldSpec.DataType dataType) + throws Exception { + initializeRows(); + insertRow(null); + insertRow(1); + insertRow(2); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, dataType).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT STDDEV_POP(%s) FROM testTable", COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + assertEquals(rows.get(0)[0], 0.5); + } + + @Test(dataProvider = "NumberTypes") + public void testGroupByStddevPop(FieldSpec.DataType dataType) + throws Exception { + initializeRows(); + insertRowWithTwoColumns(null, "key"); + insertRowWithTwoColumns(1, "key"); + insertRowWithTwoColumns(2, "key"); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, dataType) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.STRING).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT STDDEV_POP(%s), %s FROM testTable GROUP BY %s", COLUMN1, COLUMN2, COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + assertArrayEquals(rows.get(0), new Object[]{0.5, "key"}); + } + + @Test(dataProvider = "NumberTypes") + public void testGroupByMvStddevPop(FieldSpec.DataType dataType) + throws Exception { + initializeRows(); + insertRowWithTwoColumns(null, new String[]{"key1", "key2"}); + insertRowWithTwoColumns(1, new String[]{"key1", "key2"}); + insertRowWithTwoColumns(2, new String[]{"key1"}); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, dataType) + .addMultiValueDimension(COLUMN2, FieldSpec.DataType.STRING).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT STDDEV_POP(%s), %s FROM testTable GROUP BY %s ORDER BY %s", COLUMN1, COLUMN2, COLUMN2, + COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 2); + assertArrayEquals(rows.get(0), new Object[]{0.5, "key1"}); + assertArrayEquals(rows.get(1), new Object[]{0.0, "key2"}); + } + + @Test + public void testAllNullGroupByStddevPopReturnsNull() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(null, "key1"); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.INT) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.STRING).build(); + setUpSegments(tableConfig, schema); + String query = + String.format("SELECT STDDEV_POP(%s), %s FROM testTable GROUP BY %s ORDER BY %s", COLUMN1, COLUMN2, COLUMN2, + COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + assertEquals(rows.get(0)[0], null); + } + + @Test + public void testAllNullStddevPopReturnsNull() + throws Exception { + initializeRows(); + insertRow(null); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.DOUBLE).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT STDDEV_POP(%s) FROM testTable", COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + assertEquals(rows.get(0)[0], null); + } + + @Test + public void testNoMatchingRowNullHandlingDisabledStddevPopReturnsNull() + throws Exception { + initializeRows(); + insertRow(1); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.DOUBLE).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT STDDEV_POP(%s) FROM testTable WHERE %s != 1", COLUMN1, COLUMN1); + + BrokerResponseNative brokerResponse = getBrokerResponse(query); + + ResultTable resultTable = brokerResponse.getResultTable(); + List rows = resultTable.getRows(); + assertEquals(rows.size(), 1); + assertEquals(rows.get(0)[0], Double.NEGATIVE_INFINITY); + } + + @Test + public void testTrueAndNullReturnsNull() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(true, null); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.BOOLEAN) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.BOOLEAN).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT AND(%s, %s) FROM testTable LIMIT 1", COLUMN1, COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + assertEquals(brokerResponse.getResultTable().getRows().get(0)[0], null); + } + + @Test + public void testFalseAndNullReturnsFalse() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(false, null); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.BOOLEAN) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.BOOLEAN).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT AND(%s, %s) FROM testTable LIMIT 1", COLUMN1, COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + assertEquals(brokerResponse.getResultTable().getRows().get(0)[0], false); + } + + @Test + public void testNullAndNullReturnsNull() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(null, null); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.BOOLEAN) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.BOOLEAN).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT AND(%s, %s) FROM testTable LIMIT 1", COLUMN1, COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + assertEquals(brokerResponse.getResultTable().getRows().get(0)[0], null); + } + + @Test + public void testTrueOrNullReturnsTrue() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(true, null); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.BOOLEAN) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.BOOLEAN).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT OR(%s, %s) FROM testTable LIMIT 1", COLUMN1, COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + assertEquals(brokerResponse.getResultTable().getRows().get(0)[0], true); + } + + @Test + public void testFalseOrNullReturnsNull() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(false, null); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.BOOLEAN) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.BOOLEAN).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT OR(%s, %s) FROM testTable LIMIT 1", COLUMN1, COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + assertEquals(brokerResponse.getResultTable().getRows().get(0)[0], null); + } + + @Test + public void testNullOrNullReturnsNull() + throws Exception { + initializeRows(); + insertRowWithTwoColumns(null, null); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(RAW_TABLE_NAME).build(); + Schema schema = new Schema.SchemaBuilder().addSingleValueDimension(COLUMN1, FieldSpec.DataType.BOOLEAN) + .addSingleValueDimension(COLUMN2, FieldSpec.DataType.BOOLEAN).build(); + setUpSegments(tableConfig, schema); + String query = String.format("SELECT OR(%s, %s) FROM testTable LIMIT 1", COLUMN1, COLUMN2); + + BrokerResponseNative brokerResponse = getBrokerResponse(query, QUERY_OPTIONS); + + assertEquals(brokerResponse.getResultTable().getRows().get(0)[0], null); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/PercentileKLLMVQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/PercentileKLLMVQueriesTest.java new file mode 100644 index 000000000000..9a0473ea5add --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/queries/PercentileKLLMVQueriesTest.java @@ -0,0 +1,94 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.queries; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.apache.datasketches.kll.KllDoublesSketch; +import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; +import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; +import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.data.DimensionFieldSpec; +import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.data.MetricFieldSpec; +import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.spi.data.readers.GenericRow; +import org.apache.pinot.spi.data.readers.RecordReader; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; + + +/** + * Variation of the PercentileKLLQueriesTest suite which tests PERCENTILE_KLL_MV + */ +public class PercentileKLLMVQueriesTest extends PercentileKLLQueriesTest { + private static final int MAX_NUM_MULTI_VALUES = 10; + + @Override + protected void buildSegment() + throws Exception { + List rows = new ArrayList<>(NUM_ROWS); + for (int i = 0; i < NUM_ROWS; i++) { + GenericRow row = new GenericRow(); + + int numMultiValues = RANDOM.nextInt(MAX_NUM_MULTI_VALUES) + 1; + Double[] values = new Double[numMultiValues]; + KllDoublesSketch sketch = KllDoublesSketch.newHeapInstance(); + for (int j = 0; j < numMultiValues; j++) { + double value = RANDOM.nextDouble() * VALUE_RANGE; + values[j] = value; + sketch.update(value); + } + row.putValue(DOUBLE_COLUMN, values); + row.putValue(KLL_COLUMN, sketch.toByteArray()); + + String group = GROUPS[RANDOM.nextInt(GROUPS.length)]; + row.putValue(GROUP_BY_COLUMN, group); + + rows.add(row); + } + + Schema schema = new Schema(); + schema.addField(new DimensionFieldSpec(DOUBLE_COLUMN, FieldSpec.DataType.DOUBLE, false)); + schema.addField(new MetricFieldSpec(KLL_COLUMN, FieldSpec.DataType.BYTES)); + schema.addField(new DimensionFieldSpec(GROUP_BY_COLUMN, FieldSpec.DataType.STRING, true)); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build(); + + SegmentGeneratorConfig config = new SegmentGeneratorConfig(tableConfig, schema); + config.setOutDir(INDEX_DIR.getPath()); + config.setTableName(TABLE_NAME); + config.setSegmentName(SEGMENT_NAME); + config.setRawIndexCreationColumns(Collections.singletonList(KLL_COLUMN)); + + SegmentIndexCreationDriverImpl driver = new SegmentIndexCreationDriverImpl(); + try (RecordReader recordReader = new GenericRowRecordReader(rows)) { + driver.init(config, recordReader); + driver.build(); + } + } + + @Override + protected String getAggregationQuery(int percentile) { + return String.format( + "SELECT PERCENTILEMV(%2$s, %1$d), PERCENTILEKLLMV(%2$s, %1$d), PERCENTILEKLL(%3$s, %1$d) FROM %4$s", percentile, + DOUBLE_COLUMN, KLL_COLUMN, TABLE_NAME); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/PercentileKLLQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/PercentileKLLQueriesTest.java new file mode 100644 index 000000000000..2f5b1e67dc81 --- /dev/null +++ b/pinot-core/src/test/java/org/apache/pinot/queries/PercentileKLLQueriesTest.java @@ -0,0 +1,244 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.pinot.queries; + +import it.unimi.dsi.fastutil.doubles.DoubleList; +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Random; +import org.apache.commons.io.FileUtils; +import org.apache.datasketches.kll.KllDoublesSketch; +import org.apache.pinot.common.response.broker.BrokerResponseNative; +import org.apache.pinot.core.operator.blocks.results.AggregationResultsBlock; +import org.apache.pinot.core.operator.blocks.results.GroupByResultsBlock; +import org.apache.pinot.core.operator.query.AggregationOperator; +import org.apache.pinot.core.operator.query.GroupByOperator; +import org.apache.pinot.core.query.aggregation.groupby.AggregationGroupByResult; +import org.apache.pinot.core.query.aggregation.groupby.GroupKeyGenerator; +import org.apache.pinot.segment.local.indexsegment.immutable.ImmutableSegmentLoader; +import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; +import org.apache.pinot.segment.local.segment.readers.GenericRowRecordReader; +import org.apache.pinot.segment.spi.ImmutableSegment; +import org.apache.pinot.segment.spi.IndexSegment; +import org.apache.pinot.segment.spi.creator.SegmentGeneratorConfig; +import org.apache.pinot.spi.config.table.TableConfig; +import org.apache.pinot.spi.config.table.TableType; +import org.apache.pinot.spi.data.DimensionFieldSpec; +import org.apache.pinot.spi.data.FieldSpec; +import org.apache.pinot.spi.data.MetricFieldSpec; +import org.apache.pinot.spi.data.Schema; +import org.apache.pinot.spi.data.readers.GenericRow; +import org.apache.pinot.spi.data.readers.RecordReader; +import org.apache.pinot.spi.utils.ReadMode; +import org.apache.pinot.spi.utils.builder.TableConfigBuilder; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; + + +/** + * Tests for PERCENTILE_KLL aggregation function. + * + *
      + *
    • Generates a segment with a double column, a KLL column and a group-by column
    • + *
    • Runs aggregation and group-by queries on the generated segment
    • + *
    • + * Compares the results for PERCENTILE_KLL on double column and KLL column with results for PERCENTILE on + * double column + *
    • + *
    + */ +public class PercentileKLLQueriesTest extends BaseQueriesTest { + protected static final File INDEX_DIR = new File(FileUtils.getTempDirectory(), "PercentileKllQueriesTest"); + protected static final String TABLE_NAME = "testTable"; + protected static final String SEGMENT_NAME = "testSegment"; + + protected static final int NUM_ROWS = 1000; + protected static final int VALUE_RANGE = Integer.MAX_VALUE; + protected static final double DELTA = 0.05 * VALUE_RANGE; // Allow 5% delta + protected static final String DOUBLE_COLUMN = "doubleColumn"; + protected static final String KLL_COLUMN = "kllColumn"; + protected static final String GROUP_BY_COLUMN = "groupByColumn"; + protected static final String[] GROUPS = new String[]{"G1", "G2", "G3"}; + protected static final long RANDOM_SEED = System.nanoTime(); + protected static final Random RANDOM = new Random(RANDOM_SEED); + protected static final String ERROR_MESSAGE = "Random seed: " + RANDOM_SEED; + + private IndexSegment _indexSegment; + private List _indexSegments; + + @Override + protected String getFilter() { + return ""; // No filtering required for this test. + } + + @Override + protected IndexSegment getIndexSegment() { + return _indexSegment; + } + + @Override + protected List getIndexSegments() { + return _indexSegments; + } + + @BeforeClass + public void setUp() + throws Exception { + FileUtils.deleteQuietly(INDEX_DIR); + + buildSegment(); + ImmutableSegment immutableSegment = ImmutableSegmentLoader.load(new File(INDEX_DIR, SEGMENT_NAME), ReadMode.mmap); + _indexSegment = immutableSegment; + _indexSegments = Arrays.asList(immutableSegment, immutableSegment); + } + + protected void buildSegment() + throws Exception { + List rows = new ArrayList<>(NUM_ROWS); + for (int i = 0; i < NUM_ROWS; i++) { + GenericRow row = new GenericRow(); + + double value = RANDOM.nextDouble() * VALUE_RANGE; + row.putValue(DOUBLE_COLUMN, value); + + KllDoublesSketch sketch = KllDoublesSketch.newHeapInstance(); + sketch.update(value); + row.putValue(KLL_COLUMN, sketch.toByteArray()); + + String group = GROUPS[RANDOM.nextInt(GROUPS.length)]; + row.putValue(GROUP_BY_COLUMN, group); + + rows.add(row); + } + + Schema schema = new Schema(); + schema.addField(new MetricFieldSpec(DOUBLE_COLUMN, FieldSpec.DataType.DOUBLE)); + schema.addField(new MetricFieldSpec(KLL_COLUMN, FieldSpec.DataType.BYTES)); + schema.addField(new DimensionFieldSpec(GROUP_BY_COLUMN, FieldSpec.DataType.STRING, true)); + TableConfig tableConfig = new TableConfigBuilder(TableType.OFFLINE).setTableName(TABLE_NAME).build(); + + SegmentGeneratorConfig config = new SegmentGeneratorConfig(tableConfig, schema); + config.setOutDir(INDEX_DIR.getPath()); + config.setTableName(TABLE_NAME); + config.setSegmentName(SEGMENT_NAME); + config.setRawIndexCreationColumns(Collections.singletonList(KLL_COLUMN)); + + SegmentIndexCreationDriverImpl driver = new SegmentIndexCreationDriverImpl(); + try (RecordReader recordReader = new GenericRowRecordReader(rows)) { + driver.init(config, recordReader); + driver.build(); + } + } + + @Test + public void testInnerSegmentAggregation() { + // For inner segment case, percentile does not affect the intermediate result + AggregationOperator aggregationOperator = getOperator(getAggregationQuery(0)); + AggregationResultsBlock resultsBlock = aggregationOperator.nextBlock(); + List aggregationResult = resultsBlock.getResults(); + assertNotNull(aggregationResult); + assertEquals(aggregationResult.size(), 3); + DoubleList doubleList0 = (DoubleList) aggregationResult.get(0); + Collections.sort(doubleList0); + assertSketch((KllDoublesSketch) aggregationResult.get(1), doubleList0); + assertSketch((KllDoublesSketch) aggregationResult.get(2), doubleList0); + } + + @Test + public void testInterSegmentAggregation() { + for (int percentile = 0; percentile <= 100; percentile++) { + BrokerResponseNative brokerResponse = getBrokerResponse(getAggregationQuery(percentile)); + Object[] results = brokerResponse.getResultTable().getRows().get(0); + assertEquals(results.length, 3); + double expectedResult = (Double) results[0]; + for (int i = 1; i < 3; i++) { + assertEquals((Double) results[i], expectedResult, DELTA, ERROR_MESSAGE); + } + } + } + + @Test + public void testInnerSegmentGroupBy() { + // For inner segment case, percentile does not affect the intermediate result + GroupByOperator groupByOperator = getOperator(getGroupByQuery(0)); + GroupByResultsBlock resultsBlock = groupByOperator.nextBlock(); + AggregationGroupByResult groupByResult = resultsBlock.getAggregationGroupByResult(); + assertNotNull(groupByResult); + Iterator groupKeyIterator = groupByResult.getGroupKeyIterator(); + while (groupKeyIterator.hasNext()) { + int groupId = groupKeyIterator.next()._groupId; + DoubleList doubleList0 = (DoubleList) groupByResult.getResultForGroupId(0, groupId); + Collections.sort(doubleList0); + assertSketch((KllDoublesSketch) groupByResult.getResultForGroupId(1, groupId), doubleList0); + assertSketch((KllDoublesSketch) groupByResult.getResultForGroupId(2, groupId), doubleList0); + } + } + + @Test + public void testInterSegmentGroupBy() { + for (int percentile = 0; percentile <= 100; percentile++) { + BrokerResponseNative brokerResponse = getBrokerResponse(getGroupByQuery(percentile)); + List rows = brokerResponse.getResultTable().getRows(); + assertEquals(rows.size(), 3); + for (Object[] row : rows) { + assertEquals(row.length, 3); + double expectedResult = (Double) row[0]; + for (int i = 1; i < 3; i++) { + assertEquals((Double) row[i], expectedResult, DELTA, ERROR_MESSAGE); + } + } + } + } + + protected String getAggregationQuery(int percentile) { + return String.format( + "SELECT PERCENTILE(%2$s, %1$d), PERCENTILEKLL(%2$s, %1$d), PERCENTILEKLL(%3$s, %1$d) FROM %4$s", percentile, + DOUBLE_COLUMN, KLL_COLUMN, TABLE_NAME); + } + + private String getGroupByQuery(int percentile) { + return String.format("%s GROUP BY %s", getAggregationQuery(percentile), GROUP_BY_COLUMN); + } + + private void assertSketch(KllDoublesSketch sketch, DoubleList doubleList) { + for (int percentile = 0; percentile <= 100; percentile++) { + double expected; + if (percentile == 100) { + expected = doubleList.getDouble(doubleList.size() - 1); + } else { + expected = doubleList.getDouble(doubleList.size() * percentile / 100); + } + assertEquals(sketch.getQuantile(percentile / 100.0), expected, DELTA, ERROR_MESSAGE); + } + } + + @AfterClass + public void tearDown() { + _indexSegment.destroy(); + FileUtils.deleteQuietly(INDEX_DIR); + } +} diff --git a/pinot-core/src/test/java/org/apache/pinot/queries/PercentileTDigestMVQueriesTest.java b/pinot-core/src/test/java/org/apache/pinot/queries/PercentileTDigestMVQueriesTest.java index 9fe38aab344f..77292937de2e 100644 --- a/pinot-core/src/test/java/org/apache/pinot/queries/PercentileTDigestMVQueriesTest.java +++ b/pinot-core/src/test/java/org/apache/pinot/queries/PercentileTDigestMVQueriesTest.java @@ -21,7 +21,7 @@ import com.tdunning.math.stats.TDigest; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Collections; +import java.util.Arrays; import java.util.List; import org.apache.pinot.core.query.aggregation.function.PercentileTDigestAggregationFunction; import org.apache.pinot.segment.local.segment.creator.impl.SegmentIndexCreationDriverImpl; @@ -42,7 +42,10 @@ * Tests for PERCENTILE_TDIGEST and PERCENTILE_TDIGEST_MV aggregation functions. * *